이번 포스팅은 마케팅 분석 시리즈의 첫 번째 포스팅입니다(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의 로그(가격)가 제품 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부에서는 예제와 코드가 포함된 여러 함수를 활용한 사용 사례를 살펴보겠습니다. 또한, 변수와 전체 데이터프레임을 요약하고 검사하는 방법을 보여주겠습니다.
감사합니다.