<jupyter_start><jupyter_text>Credit Card User Segmentation<NAME> - Insight Data Fellow, Boston  In this problem, we want to segement credit card users for more targeted marketing strategies. A general approach would be to target three classes: Low-, Medium-, and High-Value customers. To do this, I'll first try an RFM approach to cluster customers by Recency, Frequency, and Monetary Value. We know that there are 8950 rows of data, one per customer, with data on balances, spending, limits, tenure, cash advances, and tenure.  Metadata- CUST_ID : Identification of Credit Card holder (Categorical)- BALANCE : Balance amount left in their account to make purchases- BALANCE_FREQUENCY : How frequently the Balance is updated, score between 0 and 1 (1 = frequently updated, 0 = not frequently updated)- PURCHASES : Amount of purchases made from account- ONEOFF_PURCHASES : Maximum purchase amount done in one-go- INSTALLMENTS_PURCHASES : Amount of purchase done in installment- CASH_ADVANCE : Cash in advance given by the user- PURCHASES_FREQUENCY : How frequently the Purchases are being made, score between 0 and 1 (1 = frequently purchased, 0 = not frequently purchased)- ONEOFFPURCHASESFREQUENCY : How frequently Purchases are happening in one-go (1 = frequently purchased, 0 = not frequently purchased)- PURCHASESINSTALLMENTSFREQUENCY : How frequently purchases in installments are being done (1 = frequently done, 0 = not frequently done)- CASHADVANCEFREQUENCY : How frequently the cash in advance being paid- CASHADVANCETRX : Number of Transactions made with "Cash in Advanced"- PURCHASES_TRX : Number of purchase transactions made- CREDIT_LIMIT : Limit of Credit Card for user- PAYMENTS : Amount of Payment done by user- MINIMUM_PAYMENTS : Minimum amount of payments made by user- PRCFULLPAYMENT : Percent of full payment paid by user- TENURE : Tenure of credit card service for user<jupyter_code>import pandas as pd
CCdf = pd.read_csv("/home/tjd/InsightFiles/cc_info.csv")
CCdf.head(7)<jupyter_output><empty_output><jupyter_text>There are not necessarily data columns for recency, frequency, and monetary value, so I will engineer some proxy features to represent each of these important categories. But first, I'll inspect the data to see if it needs any cleaning or if anything is amiss.<jupyter_code>CCdf.CUST_ID.nunique() # there are the same number of rows as expected customers
descrip = CCdf[["BALANCE", "TENURE", "PURCHASES_TRX", "CREDIT_LIMIT"]]
descrip.describe()<jupyter_output><empty_output><jupyter_text>It looks like some customers have zero balance and purchases while others have tens of thousands in balance and limit; there should be signal in the data to cluster into segments. Also, the tenure values are assumed to be either months or years.<jupyter_code>import numpy as np
CCdf.replace([np.inf, -np.inf], np.nan)
CCdf.isna().sum() # check for NA values<jupyter_output><empty_output><jupyter_text>I don't plan on initially using the Minimum_Payments feature to cluster so I won't worry about that but will drop the one customer without limit data.<jupyter_code>CCdf = CCdf[CCdf["CREDIT_LIMIT"].notna()]<jupyter_output><empty_output><jupyter_text>Next, I'll plot the columns to see what outliers and distributions of data look like.<jupyter_code>import matplotlib.pyplot as plt
import seaborn as sns
boxplots = CCdf.select_dtypes(include='number')
fig, axes = plt.subplots(nrows=3, ncols=5, figsize = (20,18))
for i, column in enumerate(boxplots.columns):
    sns.boxplot(boxplots[column],ax=axes[i//5,i%5])

distplots = CCdf.select_dtypes(include='number')
distplots = distplots.dropna()
fig, axes = plt.subplots(nrows=3, ncols=5, figsize = (20,18))
for i, column in enumerate(distplots.columns):
    sns.distplot(distplots[column], ax=axes[i//5,i%5])<jupyter_output><empty_output><jupyter_text>It looks like the columns really vary in scale and all are skewed. This will need to be accounted for if I decide to use any type of regression modeling.  Feature Engineering for RFM clustering Proxy for Recency<jupyter_code># First divide the number of purchases by tenure to get a sense of how active each user is, as a proxy of recency. 
# I make the assumption here that higher values are more likely to indicate recent purchases.

CCdf['actproxy'] = CCdf["PURCHASES_TRX"]/CCdf["TENURE"]
CCdf.head()
CCdf.actproxy.describe()
import matplotlib

params = {'axes.titlesize':'28',
          'xtick.labelsize':'23',
          'ytick.labelsize':'23'}
matplotlib.rcParams.update(params)
CCdf['actproxy'].hist(figsize=(20, 10), range=[0, 20])
plt.xlabel('Activity Over Tenure', fontsize=24)
plt.ylabel('Count of Customers', fontsize=24)<jupyter_output><empty_output><jupyter_text>Most users have activity scores between zero and two, but some have scores up to 29; this should lend well to segmentation of customers.<jupyter_code>from sklearn.cluster import KMeans
import warnings; warnings.simplefilter('ignore')

sse={}
cc_recency = CCdf[['actproxy']]
for k in range(1, 10):
    kmeans = KMeans(n_clusters=k, max_iter=1000).fit(cc_recency)
    cc_recency["clusters"] = kmeans.labels_
    sse[k] = kmeans.inertia_ 
plt.figure()
plt.plot(list(sse.keys()), list(sse.values()))
plt.xlabel("Number of clusters", fontsize=24)
plt.show()
# Use this function to order the new clusters
def order_cluster(cluster_field_name, target_field_name,df,ascending):
    new_cluster_field_name = 'new_' + cluster_field_name
    df_new = df.groupby(cluster_field_name)[target_field_name].mean().reset_index()
    df_new = df_new.sort_values(by=target_field_name,ascending=ascending).reset_index(drop=True)
    df_new['index'] = df_new.index
    df_final = pd.merge(df,df_new[[cluster_field_name,'index']], on=cluster_field_name)
    df_final = df_final.drop([cluster_field_name],axis=1)
    df_final = df_final.rename(columns={"index":cluster_field_name})
    return df_final
kmeans = KMeans(n_clusters=4)
kmeans.fit(CCdf[['actproxy']])
CCdf['RecencyClusters'] = kmeans.predict(CCdf[['actproxy']])
CCdf = order_cluster('RecencyClusters', 'actproxy', CCdf, True)
CCdf.groupby('RecencyClusters')['actproxy'].describe()<jupyter_output><empty_output><jupyter_text>Great, cluster 3 represents more active users whereas cluster 0 represents rather inactive users.  Proxy for Frequency Now I'll make a feature for frquency by summing the frequencies of purchases, one-time, installments, and cash advances to derive a score that indicates how frequently customers uses their card.<jupyter_code>CCdf['freqscore'] = CCdf.iloc[:, 7:10].sum(axis=1)         
CCdf.freqscore.describe()
CCdf['freqscore'].hist(figsize=(20, 10), range=[0, 5])
plt.xlabel('Sum of Frequencies', fontsize=24)
plt.ylabel('Count of Customers', fontsize=24)
sse={}
cc_freq = CCdf[['freqscore']]
for k in range(1, 10):
    kmeans = KMeans(n_clusters=k, max_iter=1000).fit(cc_freq)
    cc_recency["clusters"] = kmeans.labels_
    sse[k] = kmeans.inertia_ 
plt.figure()
plt.plot(list(sse.keys()), list(sse.values()))
plt.xlabel("Number of clusters", fontsize=24)
plt.show()
kmeans = KMeans(n_clusters=4)
kmeans.fit(CCdf[['freqscore']])
CCdf['FreqClusters'] = kmeans.predict(CCdf[['freqscore']])
CCdf = order_cluster('FreqClusters', 'freqscore', CCdf, True)
CCdf.groupby('FreqClusters')['freqscore'].describe()<jupyter_output><empty_output><jupyter_text>Great, cluster 3 represents greater frequency of card use whereas cluster 0 represents infrequent use.  Proxy for Monetary Value Credit card companies make money on balances left on cards, and are likely to make money from users that spend more money. Hence, I will make a feature that represents balance on card and total dollar value. Add these makes sense becasue some customers will pay interest ona balance they carry, but not make new purchases and others will spend a lot of money, but never carry a balance.<jupyter_code>CCdf['cardbalance'] = CCdf['CREDIT_LIMIT'] - CCdf['BALANCE'] # First calculate the balance held on card
CCdf['revenue'] = CCdf['cardbalance'] + CCdf['PURCHASES']    # Make revenue proxy
CCdf.revenue.describe()
CCdf['revenue'].hist(figsize=(20, 10), range=[0, 30000])
plt.xlabel('Total Revenue', fontsize=24)
plt.ylabel('Count of Customers', fontsize=24)
sse={}
cc_revenue= CCdf[['revenue']]
for k in range(1, 10):
    kmeans = KMeans(n_clusters=k, max_iter=1000).fit(cc_revenue)
    cc_recency["clusters"] = kmeans.labels_
    sse[k] = kmeans.inertia_ 
plt.figure()
plt.plot(list(sse.keys()), list(sse.values()))
plt.xlabel("Number of clusters", fontsize=24)
plt.show()
kmeans = KMeans(n_clusters=4)
kmeans.fit(CCdf[['revenue']])
CCdf['RevenueClusters'] = kmeans.predict(CCdf[['revenue']])
CCdf = order_cluster('RevenueClusters', 'revenue', CCdf, True)
CCdf.groupby('RevenueClusters')['revenue'].describe()<jupyter_output><empty_output><jupyter_text>Perfect, cluster 3 represents greater greater total revenue, as a representation of card balance held and total dollar value of purchases; cluster 0 represents lower revenue customers.  Scoring Clusters for SegmentationNow that we have clusters for activity, frequency, and revenue, we can add up the cluster indexes per customer to get overall scores that will be useful when grouping customers into Low-, Mid-, and High-Value groups.<jupyter_code>CCdf['Score'] = CCdf['RevenueClusters'] + CCdf['FreqClusters'] + CCdf['RecencyClusters']
CCdf.groupby('Score')['actproxy', 'revenue', 'freqscore'].mean()<jupyter_output><empty_output><jupyter_text>The table above contains a lot of information about the behavior of different users: clearly some are of High Value and others are of Low Value, which really helps to target strategies and incentives for each type of user. To group the users there seems to be natural splits in the groups that we can use to catgeorize:- 0 to 2 = Low Value- 3 to 5 = Mid Value- 6 to 9 = High Value<jupyter_code>CCdf['Segments'] = 'Low-Value'
CCdf.loc[CCdf['Score'] >3, 'Segments'] = 'Mid-Value'
CCdf.loc[CCdf['Score'] >6, 'Segments'] = 'High-Value'<jupyter_output><empty_output><jupyter_text>Next we can plot the segments and ivestigate the data with tooltips. When we hover over any of the points, altair returns info on the customer ID, their tenure in months, and their credit limit.<jupyter_code>import altair as alt

alt.data_transformers.enable('csv')
alt.Chart(CCdf).mark_circle(size=60).encode(
    x='freqscore',
    y='revenue',
    color='Segments',
    tooltip=['CUST_ID', 'CREDIT_LIMIT', 'TENURE']
).interactive()
alt.Chart(CCdf).mark_circle(size=60).encode(
    x='freqscore',
    y='actproxy',
    color='Segments',
    tooltip=['CUST_ID', 'CREDIT_LIMIT', 'TENURE']
).interactive()
alt.Chart(CCdf).mark_circle(size=60).encode(
    x='revenue',
    y='actproxy',
    color='Segments',
    tooltip=['CUST_ID', 'CREDIT_LIMIT', 'TENURE']
).interactive()<jupyter_output><empty_output>
{'fraction_non_alphanumeric': 0.11029955621301775, 'fraction_numerical': 0.012111686390532544, 'mean_word_length': 6.010369410239792, 'pattern_counts': {'":': 1, '<': 50, '<?xml version=': 0, '>': 52, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '12', 'dir': 'jupyter-structured-clean-dedup', 'id': '104168', 'license': 'MIT', 'n_tokens_mistral': 3596, 'n_tokens_neox': 3388, 'n_words': 1100, 'path': '.ipynb_checkpoints/DC2C_Timothy_Divoll-checkpoint.ipynb', 'repo_name': 'tdivoll/Segmentation-Data-Challenge', 'size': '10824'}