안녕하세요~ Everly입니다.
오늘 포스팅해볼 내용은 아주 흥미로운 내용인, 파이썬을 활용한 '시뮬레이션'입니다 :)
저는 이전에 학부생 시절, 기업 연계 프로젝트를 진행하면서 매출 시뮬레이션을 해본 적이 있었습니다. (요금 할증을 하면 수요와 매출이 어떻게 바뀔지 수요-공급 곡선을 바탕으로 시뮬레이션을 했었죠.) 하지만 아쉽게도 러프한 분석이었고, 데이터 분석이라기보단 기본 경제학 개념을 바탕으로 엑셀로 예측해본 것이었기에 활용도가 높진 않았습니다.
그래서 저도 책 '파이썬 데이터 분석 실무 테크닉 100' 의 8장-시뮬레이션을 공부하면서 현업에서 이렇게 활용하면 되겠구나! 라는 아이디어를 얻어볼 수 있었어요. 그래서 오늘 포스팅도 유용하지 않을까 생각합니다.
참고로, 이 책을 공부해보신 분들은 아시겠지만 책의 내용이 정말 유용한데, 설명이 코드 리뷰 수준에 그치는 아쉬움이 있습니다. 그래서 원리를 이해하기 위해 저 나름대로 추가로 공부한 내용을 바탕으로 자세한 설명을 덧붙이려고 노력하고 있어요! 공부하시는 많은 분들께 도움이 되었으면 좋겠습니다.
✔Table of Contents
'시뮬레이션'이란? + 오늘 살펴볼 데이터
[고객의 소리] 물류 비용 개선에서 다양한 것을 알 수 있었고, 계속해서 분석을 부탁드립니다.
이번에는 우리 회사 제품의 판매를 예측하는 것을 검토해주셨으면 합니다. 우리 제품은 대대적으로 홍보를 하진 않지만, 대부분 SNS를 통해 입소문을 타고 퍼지고 있습니다. 재구매 고객은 SNS로 연결되어 있어 파악이 가능합니다. 이를 이용해 앞으로의 매출을 예측할 수 있을까요?
오늘 살펴볼 데이터는 SNS 입소문을 타고 잘 팔리는 제품을 판매하고 있는 한 기업의 데이터입니다. 이 기업의 데이터를 활용해, 제품이 입소문을 타고 어느 정도의 시점 이후에 몇 개나 팔리게 되는지를 예측(시뮬레이션)해봅니다.
사용하는 데이터는 다음과 같습니다.
- links.csv : 재구매 고객 20명의 SNS 연결상태 데이터 (연결된 경우 1, 아니면 0)
- links_members.csv : 모든 재구매 고객 540명의 SNS 연결상태
- info_members.csv : 모든 재구매 고객 540명의 2년간 매월 이용 현황 (이용한 경우 1, 아니면 0)
여기서 '시뮬레이션' 이란 정확히 무엇일까요? 시뮬레이션(simulation)의 사전적 정의는 '실제로 실행하기 어려운 실험을 간단히 행하는 모의실험' 을 뜻합니다.
즉, 실제로 일어나지 않은 일을 미리 실험해보는 것이죠. 이 포스팅에서 진행할 시뮬레이션도 마찬가지입니다.
입소문을 타고 제품이 얼마나 홍보가 될지는 실제로 해보지 않으면 알 수 없습니다. 여기서 우리는 '확률'을 이용하여, 실제 홍보가 일어나기 전에, "이 정도의 입소문 확률을 가정한다면 고객에게 전파되는 데 얼마나 걸릴까?" 를 구해보는 것입니다. 미래를 미리 예측해볼 수 있다면, 제품이 널리 퍼지는 데 걸리는 시간을 알 수 있고, 제품을 출시할지, 아님 어떤 시점에 프로모션을 진행할지 등의 의사 결정을 하는 데 큰 도움이 되겠죠?
실제로 시뮬레이션을 해보니 결과가 정말 흥미로웠는데요! 어떤 결과가 나올지 보러 가봅시다.
Tech 71. 인간관계 네트워크 시각화하기 (python networkx)
먼저 links.csv를 'df_links'로 불러옵니다. 이 데이터는 재구매고객 20명의 연결 상태 데이터입니다.
import pandas as pd
df_links = pd.read_csv('8장/links.csv')
print(df_links.shape)
df_links.head()
데이터가 길어서 좀 짤려보이네요.
여기서 각 고객은 'Node' 로 들어가있습니다. 고객 20명은 각각 'Node0' 부터 'Node19' 까지의 이름으로 되어 있죠.
위의 데이터를 해석해보면, Node 0과 Node 1은 0이죠? → 연결이 안 된 상태이고,
Node 2와 Node 4는 1입니다. → 연결이 된 상태입니다.
이걸 데이터프레임으로 보니 한 눈에 안들어오죠. 그래서 이 연결 상태를 네트워크 시각화해봅니다. 1이면 연결되어 있으니 엣지로 이어주고, 0이면 이어주지 않습니다. 네트워크 분석은 앞에서 여러번 설명했으니 따로 설명을 붙이진 않겠습니다.
혹시 네트워크 시각화가 무엇인지 모르겠다면 이 포스팅을 참고하세요!
import networkx as nx
import matplotlib.pyplot as plt
#객체 설정
G = nx.Graph()
#노드 설정: 노드 이름에 'Node'까지 붙이면 너무 기니까 숫자만 뽑아 0~19 이름붙임
for i in range(1, len(df_links.index)+1): #df_links의 첫 열은 제외
G.add_node(df_links.columns[i].strip('Node'))
#엣지 설정: 노드 간(i, j간) 값이 1인 경우만 엣지로 연결
## df_links의 한 행씩 검사한다.
for i in range(len(df_links.index)):
for j in range(len(df_links.index)):
node_name = 'Node' + str(j)
if df_links[node_name].iloc[i] == 1:
G.add_edge(str(i), str(j))
#그리기
nx.draw_networkx(G, node_color = 'k', edge_color = 'k', font_color = 'w')
plt.show()
앞에서 네트워크 시각화를 설명할 때,
그래프 객체 설정 → 노드 설정 → 엣지 설정 → 좌표 설정 → 그리기 순서로 한다고 했었는데요.
코드를 잘 보면 이번엔 좌표를 설정하지 않았습니다. 데이터셋에 좌표정보가 없기 때문이죠.
그래서 그릴 때 draw가 아닌 draw_networkx를 사용했습니다. 얘는 좌표값이 없더라도, 다른 노드와 연결이 가장 많은 노드를 자동으로 중심에 오도록 시각화를 해줍니다.
단, 그래서 실행할 때마다 그래프 모양이 달라집니다. 그래도 노드끼리의 연결은 제대로 됩니다!
Tech 72. 입소문에 의한 전파를 시뮬레이션하고, 시각화하기 (random 함수)
위에서 현재 재구매 고객 20명의 연결 상태를 시각화해보았습니다.
그런데 사실, 고객끼리 연결이 되어있다고 해서 무조건 입소문을 타진 않을 것입니다. 어떤 고객이 올린 홍보글을 연결되어있는 다른 고객이 못볼 수도 있고, 봤더라도 안 살수도 있으니까요!
그래서 입소문을 타는 확률을 가정해봅시다. 여기서는 임의로 "10%의 확률로 입소문이 전파된다" 고 가정하고, 시뮬레이션을 해봅시다.
여기서 입소문의 전파 확률 10%는 넘파이(numpy)의 np.random.rand() 를 사용해 만듭니다.
def determine_link(percent): #percent는 우리가 임의로 0.1(10%) 로 넣을것임
rand_val = np.random.rand()
if rand_val <= percent:
return 1
else: return 0
바로 위의 함수를 만들어봤는데요, 우선 percent라는 input이 들어갑니다. 여기서 percent는 앞에서도 말했듯 10%(즉, 0.1)를 넣습니다.
위의 함수가 어떻게 10% 확률을 의미한다는 걸까요?
잘 보시면 'rand_val'은 np.random.rand()가 랜덤하게 뽑은 0~1 사이의 난수입니다. random.rand()는 0~1 사이에서 균일한 확률 분포로 실수 난수를 생성합니다. (참고로 random.rand()는 random.random()과 같습니다.)
어쨌든, rand_val의 값인 0~1 사이의 값은 나올 확률이 모두 동일하다는 것이죠. 그렇게 나온 값이 0.1보다도 작다면 1(입소문이 전파됨), 크다면 0(입소문 전파 안됨)을 반환하는 함수입니다.
직관적으로 보기에도 rand_val 이 0.1보다도 작을 가능성은 낮다는 것을 알 수 있습니다. 이제 이 함수 determine_link를 활용해 입소문 시뮬레이션 함수를 만듭니다.
#입소문 시뮬레이션
def simulate_percolation(num, list_active, percent_percolation):
for i in range(num):
if list_active[i] == 1: #어떤 사람 i의 입소문이 전달된 경우
for j in range(num): #i랑 연결된 다른 j에 대해서..
node_name = "Node" + str(j)
if df_links[node_name].iloc[i] == 1: #혹시 i랑 j 연결이 되어있니?
if determine_link(percent_percolation) == 1: #그렇다면 랜덤확률이 10%보다 작니?
list_active[j] = 1 #이것도 맞다면 j 너는 입소문이 전달된게 맞군. 1로 둔다.
return list_active
simulate_percolation의 input에 대해 설명하겠습니다.
- num: 사람 수 (여기선 20명)
- list_active: 각 노드(사람)에 입소문이 전달되었는가?의 여부를 파악하는 리스트 (총 20개의 원소로 구성된 리스트) - 1이면 전파됨, 0이면 전파 안됨
- percent_percolation: 앞에서 만든 determine_link 함수에 넣는 확률. (여기선 0.1)
처음에 list_active의 리스트 값은 모두 0입니다. 초기에는 아무에게도 전파가 안 되었으니까요.
처음엔 각 노드별로 하나씩 이를 검사합니다. 어떤 노드 i에서 입소문이 전파된 1이 나왔다고 해보죠. 그럼 노드 i와 연결된 노드 j를 모두 찾습니다. 그리고 노드 j에서 랜덤확률 rand_val을 계산합니다. 만일 1이 반환되었다면, 10% 의 확률을 뚫고 전파가 일어났다는 뜻이겠죠? 그럼 노드 j에 해당하는 list_active 값도 1로 바꿔주는 것입니다.
다만, 최초의 노드 i는 1로 두겠습니다. 0으로 하면 저 if문이 영영 실행되지 않으니까요!
이 과정을 1번 수행하면, 노드 20개에 한번씩 적용되게 되는 것이죠.
저는 입소문이 몇 개월만에 전파되는지 현황을 파악하고 싶습니다. 그래서 36번에 걸쳐 저 함수를 실행할게요. 그럼 36개월이 지나는 동안 전파 현황을 알 수 있겠죠?
percent_percolation = 0.1
T_NUM = 36
NUM = len(df_links.index) #20개 노드 (0~19)
list_active = np.zeros(NUM) #모두 0으로 초기화
list_active[0] = 1 #딱 첫번째 노드에만 1을 넣어줌. 얘가 입소문을 일으키면 36개월 후엔 어떻게 될까?
list_active
초기값을 설정합니다. 전파확률은 0.1(10%), 입소문 개월수(T_NUM)은 36, 사람 수(NUM)은 20
그리고 앞서 말했듯, list_active는 NUM 개수만큼 전부 0으로 둡니다. 그리고 딱 첫번째 노드(0번째 노드)만 1을 넣어줍니다. (그래야 simulate_percolation의 if문이 실행되니까)
이제 36개월 후엔, 전부 0이었던 list_active가 어떻게 바뀔까요?
list_ts = []
for t in range(T_NUM): #df_links 데이터로 시뮬레이션을 * 36번(T_NUM) 해봄.
list_active = simulate_percolation(NUM, list_active, percent_percolation)
list_ts.append(list_active.copy())
이렇게 하면 list_ts에는 list_active가 36(T_NUM)개 있을 것입니다. (리스트 내 리스트 36개)
list_ts
보기가 불편하니 list_ts를 시각화해봅시다.
여기서 시각화는 앞에서 했던것과 마찬가지로, 네트워크 시각화를 합니다. 단, 값이 1인 노드는 빨간색으로, 0인 노드는 검은색으로 표시를 하면 전파가 되고 있는지를 한 눈에 파악하기 쉽겠죠?
먼저 컬러링 함수를 만듭니다.
#컬러링 함수-> list_active값이 들어오면 list_color를 반환한다.
def active_node_coloring(t, list_active):
list_color = []
for i in range(len(list_ts[t])):
if list_ts[t][i] == 1:
list_color.append('r')
else:
list_color.append('k')
return list_color
active_node_coloring 이라는 컬러링 함수에는 input으로 t와 list_active 값을 받습니다.
여기서 list_active에는 우리가 만든 list_ts를 넣고, t에는 몇 번째 list_ts 값을 반환할지를 넣습니다. 즉 개월수를 의미합니다.
만일 t를 12를 넣는다면, list_ts의 12번째 리스트가 나올 것이고 13개월째의 입소문 전파 현황을 네트워크 시각화해줄 것입니다.
저는 1개월, 6개월, 12개월, 24개월, 36개월일 때의 전파모습이 궁금해서 이를 for문으로 묶어 뽑아봤습니다. 여러분도 여러분이 원하는 개월을 t에 넣어서 뽑아보세요! (당연히 아시겠지만 파이썬은 인덱스가 0부터 시작이니 6개월을 뽑고싶으면 t에 5를 넣어야 합니다!)
# 1개월, 6개월, 12개월, 24개월, 36개월일 때 전파 모습을 비교해보자.
t_list = [0, 5, 11, 23, 35]
for t in t_list:
nx.draw_networkx(G, font_color = 'w', node_color = active_node_coloring(t, list_ts))
plt.show()
이렇게 간단하게 노드 색깔에 변화만 줘도 전파되고 있는 것을 시각적으로 확인하기 정말 좋죠?
결과를 보면 첫 1개월에는 디폴트였던 노드0만 전파가 되었습니다. 하지만 6개월엔 3개, 12개월일 땐 6개, 24개월일 땐 16개, 36개월일 땐 17개까지 전파가 되네요!
한달에 전파확률이 단 10%여도 36개월이 지나니 거의 대다수에게 전파가 되었습니다. (입소문의 위력이란,,,) 저는 이걸 보면서 블로그랑 유튜브도 시간이 흐르면 많은 이들에게 전파가 되리라는 희망(?)을 가졌습니다. 후후후..
그리고 저는 결과가 이렇게 나왔는데, 이 결과는 실행할 때마다 바뀝니다. np.random 함수가 실행할 때마다 다른 값을 랜덤하게 반환하기 때문이죠! 그래서 책에 나온 결과와도 다릅니다.
이번 포스팅에서는 '전파' 확률만 살펴보았는데요!
하지만 고객들이 입소문을 타고 상품을 구매하는 경우도 있으나, 입소문과 상관없이 이탈해버리는 경우가 있죠.
그래서 다음 포스팅에선 '전파' 확률과 함께 '소멸(이탈)' 확률도 고려한 시뮬레이션을 해봅니다.
'Data Science > Analysis Study' 카테고리의 다른 글
파이썬 시뮬레이션 - SNS 입소문 전파 예측하기 (3) / 파이썬 데이터 분석 실무 테크닉 100 (0) | 2022.05.04 |
---|---|
파이썬 시뮬레이션 - SNS 입소문 전파 예측하기 (2) / 파이썬 데이터 분석 실무 테크닉 100 (0) | 2022.05.03 |
물류 네트워크 최적화 설계 예제 (python logistics_network) / 파이썬 데이터 분석 실무 테크닉 100 (0) | 2022.04.30 |
파이썬 생산계획 최적화 (python pulp, ortoolpy) / 파이썬 데이터 분석 실무 테크닉 100 (2) | 2022.04.29 |
파이썬 운송 최적화 -2편 (python pulp, ortoolpy) / 파이썬 데이터 분석 실무 테크닉 100 (2) | 2022.04.28 |