Python을 활용한 마케팅 분석 이해 (Part 1)

이번 포스팅은 마케팅 분석 시리즈의 첫 번째 포스팅입니다(Python을 활용한 마케팅 분석 시리즈 전체에 대한 개요를 확인하려면 여기를 클릭하시면 됩니다).

Python을 사용하여 데이터 분석을 수행하는 방법을 배울 때 배우고자 하는 이가 잘 알고 있는 알려진 데이터를 사용하는 것이 가장 좋습니다. 따라서 이번 포스팅에서는 예제와 코드를 사용하여 자신이 가장 잘 아는 형태의 시뮬레이션 데이터를 생성하는 단계를 설명합니다.

아래 절차를 따라서 생성하는 가상의 데이터 세트는 상품 판매 매장에서 두 가지 경쟁 제품에 대한 주(week) 별 총 판매량 데이터입니다.

빈 데이터 구조 생성

가격 및 프로모션 상태와 함께 2년 동안 20개 매장에서 두 제품에 대한 판매 데이터를 분석하기 위한 데이터 구조를 생성하는 것부터 시작합니다.

  • 1 단계 : 몇 가지 상수, 매장 수, 각 매장의 주 별 데이터 수 설정
  • 2 단계 : 설정된 모양으로 빈 데이터 프레임 생성
  • 3단계 : 생성된 데이터 프레임 확인

import pandas as pd
import numpy as np

# 상수
N_STORES = 20
N_WEEKS = 104

# 데이터를 입력하기 위해 결측치로 이루어진 초기 데이터프레임 생성 
columns = ('storeNum','year','week','p1Sales','p2Sales','p1Price','p2Price','p1Promo','p2Promo','country')
nRows = N_STORES * N_WEEKS
storeSales = pd.DataFrame(np.empty(shape = (nRows, 10)), columns = columns)

storeSales.shape
------------------------------
(2080, 10)
storeSales.head()
---------------------------

    storeNum    	year        	week        	p1Sales     	p2Sales     	p1Price     	p2Price     	p1Promo     	p2Promo     	country
0	1.012987e-311	1.012987e-311	1.013012e-311	6.331322e-313	1.013002e-311	6.331266e-313	1.013012e-311	6.331323e-313	1.013012e-311	6.331323e-313
1	1.013002e-311	6.331266e-313	1.013012e-311	6.331323e-313	1.013012e-311	6.331323e-313	1.013002e-311	6.331266e-313	1.013012e-311	6.331323e-313
2	1.013012e-311	6.331323e-313	1.013002e-311	6.331266e-313	1.013012e-311	6.331323e-313	0.000000e+00	0.000000e+00	1.013002e-311	6.331266e-313
3	1.013012e-311	6.331323e-313	1.013012e-311	6.331322e-313	1.013002e-311	6.331266e-313	1.013012e-311	6.331323e-313	1.013012e-311	6.331323e-313
4	1.013002e-311	6.331266e-313	1.013012e-311	6.331323e-313	0.000000e+00	0.000000e+00	1.013002e-311	6.331266e-313	1.013012e-311	6.331323e-313


매장 ID 만들기

  • 4단계 : 각 매장을 식별하기 위한 매장 ‘번호’ 또는 ‘ID’ 생성
# 매장의 고유번호(101 ~ 120) 생성
storeNumbers = range(101, 101 + N_STORES)
list(storeNumbers)
-----------------------------------------------
[101,
 102,
 103,
 104,
 105,
 106,
 107,
 108,
 109,
 110,
 111,
 112,
 113,
 114,
 115,
 116,
 117,
 118,
 119,
 120]


각 매장별 국가 지정

  • 5단계 : 매장 번호와 국가를 매핑하는 딕셔너리 생성
# 각각의 매장 고유번호에 국가 맵핑
storeCountry = dict(zip(storeNumbers, ['USA','USA','USA','DEU','DEU','DEU','DEU','DEU','GBR','GBR','BRA','BRA','JPN','JPN','JPN','JPN','AUS','CHN','CHN','KOR']))
storeCountry
-----------------------------
{101: 'USA',
 102: 'USA',
 103: 'USA',
 104: 'DEU',
 105: 'DEU',
 106: 'DEU',
 107: 'DEU',
 108: 'DEU',
 109: 'GBR',
 110: 'GBR',
 111: 'BRA',
 112: 'BRA',
 113: 'JPN',
 114: 'JPN',
 115: 'JPN',
 116: 'JPN',
 117: 'AUS',
 118: 'CHN',
 119: 'CHN',
 120: 'KOR'}


칼럼별 데이터프레임 값 채우기

  • 6단계 : storeSales 데이터 프레임 채우기
# storeSales 데이터프레임에 값 채우기

i = 0
for storeNum in storeNumbers:
    for year in [1,2]:
        for week in range(1, 53):
            storeSales.loc[i, 'storeNum'] = storeNum
            storeSales.loc[i, 'year'] = year
            storeSales.loc[i, 'week'] = week
            storeSales.loc[i, 'country'] = storeCountry[storeNum]
            i += 1

storeSales.head()
--------------------------------------------------------------------------------------------------
	storeNum	year	week	p1Sales     	p2Sales     	p1Price     	p2Price     	p1Promo     	p2Promo     	country
0	101.0	    1.0 	1.0     6.331322e-313	1.013002e-311	6.331266e-313	1.013012e-311	6.331323e-313	1.013012e-311	USA
1	101.0	    1.0 	2.0 	6.331323e-313	1.013012e-311	6.331323e-313	1.013002e-311	6.331266e-313	1.013012e-311	USA
2	101.0    	1.0 	3.0 	6.331266e-313	1.013012e-311	6.331323e-313	0.000000e+00	0.000000e+00	1.013002e-311	USA
3	101.0   	1.0 	4.0 	6.331322e-313	1.013002e-311	6.331266e-313	1.013012e-311	6.331323e-313	1.013012e-311	USA
4	101.0       1.0 	5.0 	6.331323e-313	0.000000e+00	0.000000e+00	1.013002e-311	6.331266e-313	1.013012e-311	USA

  • 7단계 : 범주형으로 데이터 변환이 필요한 칼럼에 대해 데이터 유형 변환

데이터 프레임의 모든 변수 유형은 입력 데이터에 따라 결정됩니다. country 값은 문자열로 저장 되었습니다. 그러나 country 라벨은 실제로 임의의 텍스트가 아닌 개별 값입니다. 따라서 국가를 범주형 변수로 명시적으로 나타내는 것이 더 좋습니다.

마찬가지로, storeNum은 숫자가 아닌 레이블입니다. 해당 변수를 범주형 유형으로 변환하면 회귀 모델과 같은 후속 분석에서 범주형으로 처리됩니다. 변수 유형을 생성할 때 올바르게 설정하는 것이 중요합니다. 이렇게 하면 나중에 데이터 분석 시 오류를 방지하는 데 도움이 됩니다.

# 'country' 칼럼의 데이터 형식 재지정 
storeSales.country = storeSales.country.astype(pd.CategoricalDtype())
storeSales.country.head()
--------------------------------------------------------------------------
0    USA
1    USA
2    USA
3    USA
4    USA
Name: country, dtype: category
Categories (8, object): ['AUS', 'BRA', 'CHN', 'DEU', 'GBR', 'JPN', 'KOR', 'USA']
# 'storeNum' 칼럼의 데이터 형식 재지정 
storeSales.storeNum = storeSales.storeNum.astype(pd.CategoricalDtype())
storeSales.storeNum.head()
--------------------------------------------------------------------------
0    101.0
1    101.0
2    101.0
3    101.0
4    101.0
Name: storeNum, dtype: category
Categories (20, float64): [101.0, 102.0, 103.0, 104.0, ..., 117.0, 118.0, 119.0, 120.0]


데이터 포인트를 시뮬레이션하여 데이터 프레임에 데이터 값 저장

  • 8단계 : 3개 열(매출, 가격, 프로모션 상태)에 대해 매장 별 분석을 위해 무작위 데이터로 storeSales 완성
    • 8.a 단계 : 난수 생성 시드 설정
np.random.seed(37204)
  • (계속)
    • 8.b 단계 : 프로모션 열 [0 또는 1, 여부]에 대해 이항 분포에서 무작위 생성
# 임의의 1번 제품 10%에 대해 프로모션 할당
storeSales.p1Promo = np.random.binomial(n = 1, p = 0.1, size = nRows)

# 임의의 2번 제품 15%에 대해 프로모션 할당
storeSales.p1Promo = np.random.binomial(n = 1, p = 0.15, size = nRows)

storeSales.head(10)
-----------------------------------------------------------------------------
    storeNum	year	week	p1Sales	p2Sales	p1Price	p2Price	p1Promo	p2Promo     	country
0	101.0   	1.0 	1.0 	94.0	94.0	2.19	2.29	0   	1.013012e-311	USA
1	101.0   	1.0 	2.0 	93.0	119.0	2.79	2.49	0   	1.013012e-311	USA
2	101.0   	1.0 	3.0 	138.0	64.0	2.29	3.19	0   	1.013002e-311	USA
3	101.0   	1.0 	4.0 	148.0	76.0	2.29	2.99	0   	1.013012e-311	USA
4	101.0   	1.0 	5.0 	145.0	97.0	2.19	2.49	0   	1.013012e-311	USA
5	101.0   	1.0 	6.0 	252.0	77.0	2.19	3.19	0   	1.013002e-311	USA
6	101.0   	1.0 	7.0 	117.0	96.0	2.29	2.49	0   	1.013012e-311	USA
7	101.0   	1.0 	8.0 	119.0	88.0	2.29	2.29	0   	1.013002e-311	USA
8	101.0   	1.0 	9.0 	160.0	82.0	2.49	3.19	0   	1.013002e-311	USA
9	101.0   	1.0 	10.0	176.0	75.0	2.49	2.99	0       1.013012e-311	USA
  • (계속)
    • 8.c 단계 : p1 및 p2 제품 열의 가격 설정(random.choice 사용)

각 제품이 전체적으로 2.19달러에서 3.19달러 사이의 5가지 서로 다른 가격대 중 하나로 판매된다고 가정하겠습니다. 5개의 가격대로 벡터를 정의하고 np.random.choice(a, size, replacement)를 사용하여 데이터 행(size = nRows)만큼 여러 번 벡터에서 추출하여 매주 가격을 무작위로 추출합니다. 5개의 가격은 여러 번 샘플링 되므로 복원(replace = True, 기본값이므로 쓰지 않음) 추출로 선택됩니다.

storeSales.p1Price = np.random.choice([2.19, 2.29, 2.49, 2.79, 2.99], size = nRows)
storeSales.p2Price = np.random.choice([2.29, 2.49, 2.59, 2.99, 3.19], size = nRows)
storeSales.sample(5)
-------------------------------------------------------------------------------------------------------
    	storeNum	year	week	p1Sales	p2Sales	p1Price	p2Price	p1Promo	p2Promo     	country
67  	101.0   	2.0 	16.0	125.0	93.0	2.99	3.19	0   	1.013002e-311	USA
841 	109.0   	1.0 	10.0	129.0	63.0	2.99	2.29	1   	1.294417e-312	GBR
269  	103.0   	2.0 	10.0	131.0	101.0	2.19	3.19	0   	0.000000e+00	USA
1368	114.0   	1.0 	17.0	117.0	103.0	2.29	2.29	0   	6.790387e-313	JPN
1151	112.0   	1.0 	8.0 	131.0	114.0	2.99	2.29	1   	2.419075e-312	BRA
  • (계속)
    • 8.d 단계 : 포아송 분포를 사용하여 매주 판매 수치를 시뮬레이션 합니다.

품목 판매는 개수로 이루어지므로 포아송 분포를 사용하여 개수 데이터를 생성합니다(np.random.poisson(lam, size). 여기서 size는 추첨 횟수이고 lam은 포아송 분포의 정의 매개변수인 람다를 나타냅니다.

람다는 주당 단위의 예상 값 또는 평균 값을 나타냅니다. 각 행(size = nRows)에 대해 무작위 포아송 수를 뽑고 제품 1의 평균 판매량(lam)을 제품 2의 평균 판매량보다 높게 설정합니다.

salesP1 = np.random.poisson(lam = 120, size = nRows)
salesP2 = np.random.poisson(lam = 100, size = nRows)

다음으로 가격에 따라 해당 개수를 늘리거나 줄입니다. 가격 효과는 종종 선형 함수가 아닌 로그 함수를 따르므로 여기서는 np.log(price)를 사용합니다.

# 로그(가격) 비율에 따라 매출 규모 조정
logP1Price = np.log(storeSales.p1Price)
logP2Price = np.log(storeSales.p2Price)
salesP1 = salesP1 * logP2Price / logP1Price
salesP2 = salesP2 * logP1Price / logP2Price

아래의 가정으로 최종 데이터 프레임을 구성합니다.

  1. 매출은 가격의 반비례에 따라 변동한다고 가정했습니다. 즉, 제품 1의 판매량은 제품 1의 로그(가격)가 제품 2의 로그(가격)보다 낮습니다.
  2. 매장에서 각 제품을 홍보하면 매출이 30~40% 증가한다고 가정합니다. 프로모션 상태 벡터(모든 {0, 1} 값으로 구성됨)에 각각 0.3 또는 0.4를 곱한 다음 이를 판매 벡터에 곱합니다.

floor() 함수를 사용하여 분수 값을 삭제하고 주간 단위 판매에 대한 정수 개수를 확인하고 그 값을 입력합니다. 데이터 프레임에 값을 추가합니다.

# 프로모션 시 최종 판매가 30% 또는 40% 증가
storeSales.p1Sales = np.floor(salesP1 * (1 + storeSales.p1Promo * 0.3))
storeSales.p2Sales = np.floor(salesP2 * (1 + storeSales.p2Promo * 0.4))
storeSales.sample(10)
---------------------------------------------------------------------------
    	storeNum	year	week	p1Sales	p2Sales	p1Price	p2Price	p1Promo	p2Promo     	country
625 	107.0   	1.0 	2.0 	109.0	134.0	2.79	2.29	0   	1.013011e-311	DEU
829 	108.0   	2.0 	50.0	118.0	86.0	2.29	2.29	0   	6.790387e-313	DEU
1072	111.0   	1.0 	33.0	87.0	122.0	2.99	2.29	0   	2.355415e-312	BRA
1995	120.0   	1.0 	20.0	131.0	84.0	2.79	3.19	0   	2.164436e-312	KOR
448 	105.0   	1.0 	33.0	101.0	133.0	2.99	2.29	0   	1.013003e-311	DEU
692 	107.0   	2.0 	17.0	90.0	129.0	2.99	2.29	0   	1.013016e-311	DEU
567 	106.0   	1.0 	48.0	189.0	73.0	2.49	3.19	0   	6.615539e-321	DEU
918 	109.0   	2.0 	35.0	109.0	97.0	2.49	2.29	0   	6.790387e-313	GBR
207 	102.0   	2.0     52.0	149.0	70.0	2.19	2.59	0   	0.000000e+00	USA
1516	115.0   	2.0	    9.0 	122.0	112.0	2.79	2.59	0   	2.419075e-312	JPN

분석을 위한 최종 데이터를 생성하였습니다. 이 시리즈의 다음 포스팅에서 후속 단계를 살펴보겠습니다. 2부에서는 예제와 코드가 포함된 여러 함수를 활용한 사용 사례를 살펴보겠습니다. 또한, 변수와 전체 데이터프레임을 요약하고 검사하는 방법을 보여주겠습니다.

감사합니다.

답글 남기기

이메일 주소는 공개되지 않습니다. 필수 필드는 *로 표시됩니다