๋ณธ๋ฌธ ๋ฐ”๋กœ๊ฐ€๊ธฐ
Machine Learning/Case Study ๐Ÿ‘ฉ๐Ÿป‍๐Ÿ’ป

[Kaggle] ์ด์ปค๋จธ์Šค ๋ฐ์ดํ„ฐ ๋ถ„์„ 7 (CRM Analytics ๐Ÿ›๏ธ๐Ÿ›’)

by ISLA! 2023. 10. 10.

 

Customer Lifetime Value

ํ•œ ๊ณ ๊ฐ์ด ๋‹น์‹ ์˜ ๋ธŒ๋žœ๋“œ์— ์ „ ์ƒ์• ๋™์•ˆ ์–ผ๋งˆ๋ฅผ ๊ฐ€์ ธ๋‹ค์ค„ ๊ฒƒ์ธ๊ฐ€? ์— ๋Œ€ํ•œ ๋‹ต์„ ์ฐพ๋Š” ๊ณผ์ •์ด๋‹ค.

๊ณ ๊ฐ ๋ณ„๋กœ, ์ด ๊ตฌ๋งค ๊ธฐ๊ฐ„, ์ตœ์ดˆ ๊ตฌ๋งค์ผ๋กœ๋ถ€ํ„ฐ ์ง€๊ธˆ๊นŒ์ง€ ํ๋ฅธ ์‹œ๊ฐ„, ์ฃผ๋ฌธ ํšŸ์ˆ˜, ์ด ์ฃผ๋ฌธ ๊ธˆ์•ก์„ ๊ตฌํ•ด ์ด๋ฅผ ์˜ˆ์ธกํ•ด ๋ณด์ž.

  • ๋จผ์ € ๊ณ ๊ฐ ID๋ฅผ ๊ธฐ์ค€์œผ๋กœ ๊ทธ๋ฃนํ™”๋ฅผ ์ง„ํ–‰ํ•œ๋‹ค. ์ด๋•Œ, ๊ธฐ๊ฐ„๊ณผ ๊ด€๋ จ๋œ ์ •๋ณด๋ฅผ ๋‹ค๋ฃจ๊ธฐ ์œ„ํ•ด
    • InvoiceDate์— ๋‘ ๊ฐ€์ง€ ํ•จ์ˆ˜๋ฅผ ์ ์šฉํ•œ๋‹ค. ์ด๋ฅผ ํ†ตํ•ด ๊ณ ๊ฐ๋ณ„ ๊ตฌ๋งค ๊ธฐ๊ฐ„๊ณผ ์ตœ์ดˆ ๊ตฌ๋งค์ผ๋กœ๋ถ€ํ„ฐ ํ˜„์žฌ๊นŒ์ง€์˜ ์‹œ๊ฐ„์„ ๋„์ถœํ•œ๋‹ค
    • InvoiceNo๋Š” nunique๋กœ ๊ณ ์œ ํ•œ ๊ตฌ๋งค ๋ฒˆํ˜ธ๋กœ ์ฃผ๋ฌธ ํšŸ์ˆ˜๋ฅผ ์นด์šดํŠธํ•œ๋‹ค.
    • TotalPrice ์—๋Š” Sum์„ ์ ์šฉํ•˜์—ฌ ์–ผ๋งˆ๋‚˜ ๋งŽ์€ ๋ˆ์„ ์ผ๋Š”์ง€ ์ง‘๊ณ„ํ•œ๋‹ค.
  • ๋งˆ์ง€๋ง‰์œผ๋กœ ์นผ๋Ÿผ์„ ์ •๋ฆฌํ•ด ์ฃผ๋Š”๋ฐ, droplevel(0)๋กœ ๊ทธ๋ฃนํ™”๋˜๋ฉฐ ์ƒ์„ฑ๋œ ๋ฉ€ํ‹ฐ์ธ๋ฑ์Šค๋ฅผ ์ •๋ฆฌํ•ด ์ค€๋‹ค.
  • ์นผ๋Ÿผ๋ช…์„ ์ˆ˜์ •ํ•˜๋ฉด ๋!
cltv_df = df.groupby('CustomerID').agg(
        {'InvoiceDate' : [lambda x : (x.max() - x.min()).days,     #๊ณ ๊ฐ๋ณ„ ๊ตฌ๋งค๊ธฐ๊ฐ„
                          lambda x : (today_date - x.min()).days], #๊ณ ๊ฐ๋ณ„ ์ตœ์ดˆ ๊ตฌ๋งค๋กœ๋ถ€ํ„ฐ ํ˜„์žฌ๊นŒ์ง€์˜ ์‹œ๊ฐ„
        'InvoiceNo' : 'nunique',  #๋ช‡ ๋ฒˆ ์ฃผ๋ฌธํ–ˆ๋Š”์ง€
         'TotalPrice' : 'sum'     #์–ผ๋งˆ๋‚˜ ๋งŽ์€ ๋ˆ์„ ์ผ๋Š”์ง€
        })

# ์—ด ์ด๋ฆ„์— ์žˆ๋Š” ๋‹ค์ค‘ ๋ ˆ๋ฒจ ์ธ๋ฑ์Šค์—์„œ ์ตœ์ƒ์œ„ ๋ ˆ๋ฒจ์˜ ๋ ˆ๋ฒจ์„ ์ œ๊ฑฐ
cltv_df.columns = cltv_df.columns.droplevel(0)
cltv_df.columns = ["recency", "T", "frequency", "monetary"]
cltv_df

 

 

  • ์ด ์ฃผ๋ฌธ๊ธˆ์•ก์€ ๊ฑด๋‹น ์ฃผ๋ฌธ๊ธˆ์•ก์œผ๋กœ ๋ณ€๊ฒฝํ•œ๋‹ค.
  • ์ด์ œ ์œ„์—์„œ ๋„์ถœํ•œ cltv_df์˜ ๊ธฐ๊ฐ„ ๊ด€๋ จ ์นผ๋Ÿผ์„ 7๋กœ ๋‚˜๋ˆ„์–ด, '์ฃผ'๋‹จ์œ„๋กœ ๊ธฐ๊ฐ„์„ ๋ณ€๊ฒฝํ•œ๋‹ค.
  • 2๋ฒˆ ์ด์ƒ ๊ตฌ๋งคํ•œ(์žฌ๊ตฌ๋งคํ•œ) ๊ณ ๊ฐ๋งŒ ํ•„ํ„ฐ๋งํ•˜๊ธฐ ์œ„ํ•ด ๋งˆ์ง€๋ง‰ ์กฐ๊ฑด๋„ ์ถ”๊ฐ€ํ•œ๋‹ค.
# ํ‰๊ท  ์ฃผ๋ฌธ ๊ธˆ์•ก(๊ตฌ๋งค ๊ฑด๋‹น)
cltv_df['monetary'] = cltv_df['monetary'] / cltv_df['frequency']

# ๊ตฌ๋งค ๊ธฐ๊ฐ„์„ ์ฃผ ๋‹จ์œ„๋กœ ๋ณ€๊ฒฝ(weeks)
cltv_df['recency'] = cltv_df['recency'] / 7
cltv_df['T'] = cltv_df['T'] / 7

# ์žฌ๊ตฌ๋งคํ•œ ๊ณ ๊ฐ(2๋ฒˆ ์ด์ƒ ๊ตฌ๋งคํ•œ ๊ณ ๊ฐ)๋งŒ ์ถ”์ถœ
cltv_df = cltv_df[(cltv_df['frequency'] > 1)]
cltv_df

 

 

BN/NBD

  • ์•ž์—์„œ ๊ตฌํ•œ ๋ฐ์ดํ„ฐํ”„๋ ˆ์ž„์„ ๊ฐ€์ง€๊ณ , ๊ณ ๊ฐ์˜ ๊ฑฐ๋ž˜ ํ–‰๋™์„ ์˜ˆ์ธกํ•  ๊ฒƒ์ด๋‹ค. (์ด ๋ชจ๋ธ์— ๋Œ€ํ•œ ์ƒ์„ธ ํฌ์ŠคํŒ…์€ ๋ณ„๋„๋กœ ์ง„ํ–‰ ์˜ˆ์ •)
  • BN/NBD๋Š” ๊ณ ๊ฐ ๊ฑฐ๋ž˜ ํ–‰๋™์„ ์˜ˆ์ธกํ•˜๊ธฐ ์œ„ํ•ด ์‚ฌ์šฉ๋˜๋Š” ํ™•๋ฅ  ๋ชจ๋ธ ์ค‘ ํ•˜๋‚˜๋กœ, ๊ฐ ๊ณ ๊ฐ์˜ ๊ฑฐ๋ž˜ ํŒจํ„ด์„ ์˜ˆ์ธกํ•˜๋Š” ๋ฐ ์‚ฌ์šฉ๋œ๋‹ค.
  • ์ด ๋ชจ๋ธ์„ ์‚ฌ์šฉํ•˜๋ฉด ๊ณ ๊ฐ์˜ ์ƒ์•  ๋™์•ˆ ์˜ˆ์ƒ ๊ฑฐ๋ž˜ ํšŸ์ˆ˜๋ฅผ ์ถ”์ •ํ•˜๊ณ , ๊ณ ๊ฐ ์œ ์น˜ ๋ฐ ์ดํƒˆ์„ ์˜ˆ์ธกํ•˜๋Š” ๋“ฑ์˜ ๋น„์ฆˆ๋‹ˆ์Šค ์˜์‚ฌ ๊ฒฐ์ •์„ ์ง€์›ํ•  ์ˆ˜ ์žˆ๋‹ค.
  • BetaGeoFitter์™€ lifetimes ํŒจํ‚ค์ง€๋Š” Python์„ ์‚ฌ์šฉํ•˜์—ฌ ์ด๋Ÿฌํ•œ ๋ชจ๋ธ์„ ๊ตฌ์ถ•ํ•˜๊ณ  ๋ถ„์„ํ•˜๋Š” ๋ฐ ๋„์›€์ด ๋˜๋Š” ๋„๊ตฌ์™€ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ์ด๋‹ค.
    • penalizer_coef : ๋ชจ๋ธ์˜ ๋ณต์žก์„ฑ์„ ์กฐ์ ˆํ•˜๋Š” ๋งค๊ฐœ๋ณ€์ˆ˜(๋” ๋†’์€ ๊ฐ’์„ ์‚ฌ์šฉํ•˜๋ฉด ๋ชจ๋ธ์ด ๋” ๋‹จ์ˆœํ•ด์ง€๊ณ  ๊ณผ์ ํ•ฉ์„ ์ค„์ž„)
from lifetimes import BetaGeoFitter, GammaGammaFitter
from lifetimes.plotting import plot_period_transactions
%matplotlib inline
%load_ext nb_black

BGF = BetaGeoFitter(penalizer_coef=0.001)   #๊ณผ์ ํ•ฉ ๋ฐฉ์ง€
BGF.fit(cltv_df['frequency'], cltv_df['recency'], cltv_df['T'])
<lifetimes.BetaGeoFitter: fitted with 2845 subjects, a: 0.12, alpha: 11.40, b: 2.49, r: 2.18>

 

์˜ˆ์ธก 1.  ๊ฐ€์žฅ ๋งŽ์€ ๊ฑฐ๋ž˜๋ฅผ ํ•  ๊ฒƒ์œผ๋กœ ์˜ˆ์ƒ๋˜๋Š” ๊ณ ๊ฐ(1์ฃผ์ผ ๊ธฐ์ค€)

  • 1 ๋‹จ์œ„ ์‹œ๊ฐ„ ๋™์•ˆ์˜ ๊ณ ๊ฐ๋“ค์˜ ์˜ˆ์ƒ ๊ฑฐ๋ž˜ ํšŸ์ˆ˜๋ฅผ ๊ณ„์‚ฐํ•˜๊ณ ,
  • ์ด๋ฅผ ๋‚ด๋ฆผ์ฐจ์ˆœ์œผ๋กœ ์ •๋ ฌํ•˜์—ฌ ์ƒ์œ„ 10๋ช…์˜ ๊ณ ๊ฐ์— ๋Œ€ํ•œ ์˜ˆ์ƒ ๊ฑฐ๋ž˜ ํšŸ์ˆ˜๋ฅผ ํ‘œ์‹œํ•œ๋‹ค.
BGF.conditional_expected_number_of_purchases_up_to_time( \
    1, cltv_df['frequency'], cltv_df['recency'], cltv_df['T']) \
    .sort_values(ascending = False).head(10).to_frame(
    "Expected Number of Transactions").reset_index()

 

 

์˜ˆ์ธก 2.  ๊ฐ€์žฅ ๋งŽ์€ ๊ฑฐ๋ž˜๋ฅผ ํ•  ๊ฒƒ์œผ๋กœ ์˜ˆ์ƒ๋˜๋Š” ๊ณ ๊ฐ(1 ๊ฐœ์›” ๊ธฐ์ค€)

  • ๊ธฐ์ค€ ๋ฐ์ดํ„ฐ์˜ 1๋‹จ์œ„ ๊ธฐ๊ฐ„์ด 7์ผ(์ผ์ฃผ์ผ) ์ด์—ˆ์œผ๋ฏ€๋กœ, 4๋กœ ๋ณ€๊ฒฝํ•˜๋ฉด 1๊ฐœ์›” ๊ธฐ์ค€ ๊ณ ๊ฐ ๋‹น ์˜ˆ์ƒ ๊ฑฐ๋ž˜ ํšŸ์ˆ˜๊ฐ€ ๋‚˜์˜จ๋‹ค
BGF.conditional_expected_number_of_purchases_up_to_time(
    4, cltv_df['frequency'], cltv_df['recency'], cltv_df['T']) \
    .sort_values(ascending = False).head(10).to_frame(
    "Expected Number of Transactions").reset_index()

 

 

์˜ˆ์ธก 3.  ์ฃผ์–ด์ง„ ๊ธฐ๊ฐ„ ๋‚ด ์‹ค์ œ ๊ฑฐ๋ž˜ ๋นˆ๋„์™€ ์˜ˆ์ธก๋œ ๊ฑฐ๋ž˜ ๋นˆ๋„๋ฅผ ์‹œ๊ฐํ™”

  • max_frequency=7: ์ด ๋ถ€๋ถ„์€ ๊ทธ๋ž˜ํ”„์—์„œ ํ‘œ์‹œํ•  ์ตœ๋Œ€ ์ฃผ๊ธฐ๋ฅผ ์„ค์ •
  • ์—ฌ๊ธฐ์„œ 7์€ 7์ผ ์ฃผ๊ธฐ๋กœ ๊ฑฐ๋ž˜๋ฅผ ํ”Œ๋กฏํ•˜๋ผ๋Š” ๊ฒƒ์„ ์˜๋ฏธํ•œ๋‹ค.
plot_period_transactions(BGF, max_frequency=7)
plt.show()

 

728x90