<jupyter_start><jupyter_text>Introduction The gym chain Model Fitness is developing a customer interaction strategy based on analytical data. One of the most common problems the gym faces is customer churn. In order to fight churn, Model Fitness has digitized a number of its customer profiles. My task is to analyze them and come up with a customer retention strategy.  Data Outline gym_churn_us.csv:'Churn' — the fact of churn for the month in question'gender''Near_Location' — whether the user lives or works in the neighborhood where the gym is located'Partner' — whether the user is an employee of a partner company (the gym has partner companies whose employees get discounts; in those cases the gym stores information on customers' employers)Promo_friends — whether the user originally signed up through a "bring a friend" offer (they used a friend's promo code when paying for their first membership)'Phone' — whether the user provided their phone number'Age''Lifetime' — the time (in months) since the customer first came to the gym'Contract_period' — 1 month, 3 months, 6 months, or 1 year'Month_to_end_contract' — the months remaining until the contract expires'Group_visits' — whether the user takes part in group sessions'Avg_class_frequency_total' — average frequency of visits per week over the customer's lifetime'Avg_class_frequency_current_month' — average frequency of visits per week over the preceding month'Avg_additional_charges_total' — the total amount of money spent on other gym services: cafe, athletic goods, cosmetics, massages, etc.  Step 1: Downloading the dataset: First, Let's download the liberies we need:<jupyter_code>pip install seaborn==0.11.0
import pandas as pd
import numpy as np
import math
import seaborn as sns
from sklearn.preprocessing import StandardScaler
from sklearn.model_selection import train_test_split
from sklearn.linear_model import LogisticRegression
from sklearn.ensemble import RandomForestClassifier
import matplotlib.pyplot as plt
from sklearn.metrics import accuracy_score, precision_score, recall_score
from scipy.cluster.hierarchy import dendrogram, linkage 
from sklearn.cluster import KMeans<jupyter_output><empty_output><jupyter_text>Let's download our dataset:<jupyter_code>try:
    data = pd.read_csv(r'C:\Users\nadav\OneDrive\מסמכים\Practicum100\databases\gym_churn_us.csv')
except:
    data = pd.read_csv('/datasets/gym_churn_us.csv')<jupyter_output><empty_output><jupyter_text>We downloaded our dataset.  Step 2: EDA Before we make any calulations based on the data, let's evaluate it first:<jupyter_code>data.info()
data.describe()<jupyter_output><empty_output><jupyter_text>Let's check for duplicates:<jupyter_code>data.duplicated().sum()<jupyter_output><empty_output><jupyter_text>First of all, We can see that we don't have any missing or duplicated values. Next, we see that all our data is numerical type. Next, we can see that nearly 85% of customers live near the gym, 48% of customers came through partner companies, 30% of customers signed through "bring a friend", 90% leave their phone number, and 41% attend group visits. We can also see that most customers have a contract period for 1 month, that the average age is 29 and most customers are around that age, That most customer's lifetime is around 3 months, and that the average visit per week is around 2. The Churn, which is our target variable, is 41%. Let's group our data by customers who left and customers who didn't and check the averages:<jupyter_code>data.groupby('Churn').mean()<jupyter_output><empty_output><jupyter_text>We can see some major differences in the averages for the 2 groups. We see that people who come with friends, spend money, have a long conttrat and attend more classes are less likley to leave. We can see that people who have a short lifetime tend to leave more often as well. Let's check the distributions of each feature in each group, and check the correlation:<jupyter_code>list = data.columns
for i in list:
    data.groupby('Churn')[i].hist()
    plt.legend(['Retention','Churn'])
    plt.title(i)
    plt.show();<jupyter_output><empty_output><jupyter_text>We see we have outliners to the right, but not many of them. Let's define outliners as values greater than the 98 percentile.<jupyter_code>outline_col = ['Lifetime','Avg_class_frequency_current_month','Avg_class_frequency_total','Avg_additional_charges_total']
percentile_98 = []
for i in outline_col:
    percentile_98.append(np.percentile(data[i],98))
for i in range(len(outline_col)):
    data = data.loc[data[outline_col[i]] < percentile_98[i]]
data.info()<jupyter_output><class 'pandas.core.frame.DataFrame'>
Int64Index: 3748 entries, 0 to 3999
Data columns (total 14 columns):
 #   Column                             Non-Null Count  Dtype  
---  ------                             --------------  -----  
 0   gender                             3748 non-null   int64  
 1   Near_Location                      3748 non-null   int64  
 2   Partner                            3748 non-null   int64  
 3   Promo_friends                      3748 non-null   int64  
 4   Phone                              3748 non-null   int64  
 5   Contract_period                    3748 non-null   int64  
 6   Group_visits                       3748 non-null   int64  
 7   Age                                3748 non-null   int64  
 8   Avg_additional_charges_total       3748 non-null   float64
 9   Month_to_end_contract              3748 non-null   float64
 10  Lifetime                           3748 non-null   int64  
 11  Avg_class_frequency_total          3748 non-null   float[...]<jupyter_text>We lost about 5% ofour data. Let's see how the distributions are looking now:<jupyter_code>for i in list:
    data.groupby('Churn')[i].hist()
    plt.legend(['Retention','Churn'])
    plt.title(i)
    plt.show();<jupyter_output><empty_output><jupyter_text>Much better! We can see a couple of interesting featurs for customers who left. First of all, Their contarct periods are much shorter than the customers that stayed. Secondly, the vast majority of them have only one month untill the end of the contract. This means customers contract's expire, and they don't extend it. Lastly, We can see that the average of class frequancy for the last month is relativly low compared to the staying customers. Let's do a correlation matrix:<jupyter_code>ax = data.corr()
plt.figure(figsize = (15,15))
sns.heatmap(ax, annot = True, square = True)
;<jupyter_output><empty_output><jupyter_text>We have 2 pairs of columns that correlate neary identicly: 'contract period' with 'month untill the end of contract' and the average class frequancy in the total lifetime with the average class frequancy of this month. Both thease pairs makes sense, As they both have affect on each other. When we build our model, we'll get rid of the correlated columns.  Step 3: Building a model Let's build a model that predicts churn for any specific customer. First, let's split our data to features and target:<jupyter_code>x = data.drop(['Contract_period','Avg_class_frequency_total','Churn'], axis = 1)
y = data['Churn']<jupyter_output><empty_output><jupyter_text>Now let's split the data to train and test:<jupyter_code>x_train,x_test,y_train,y_test = train_test_split(x,y,test_size = 0.2, random_state = 0)<jupyter_output><empty_output><jupyter_text>We will try and run a linear regression and tree models and see which one has better effiency. Let's try logistic regression first:<jupyter_code>model = LogisticRegression(random_state = 0)
model.fit(x_train,y_train)
predictions = model.predict(x_test)
print('Logistic Regression:')
print('Accuracy: {:.2f}'.format(accuracy_score(y_test, predictions)))
print('Precision: {:.2f}'.format(precision_score(y_test, predictions)))
print('Recall: {:.2f}'.format(recall_score(y_test, predictions)))
tree_model = RandomForestClassifier(random_state = 0)
tree_model.fit(x_train,y_train)
predictions = tree_model.predict(x_test)
print("Forest:")
print('Accuracy: {:.2f}'.format(accuracy_score(y_test, predictions)))
print('Precision: {:.2f}'.format(precision_score(y_test, predictions)))
print('Recall: {:.2f}'.format(recall_score(y_test, predictions)))<jupyter_output>Forest:
Accuracy: 0.87
Precision: 0.77
Recall: 0.74<jupyter_text>The 2 models are very close, but we can say that the linear model is just a bit better than the tree model. The most important metric for our task is precision, since we need to know the share of correct customers our model discovered out of all of the the customers it predicted to leave.  Step 4: User clusters Now let's build user clusters. To do that, we first need to standardize our data:<jupyter_code>scaler = StandardScaler()
x_sc = scaler.fit_transform(x)<jupyter_output><empty_output><jupyter_text>Let's create a dendogram to see how many clusters we should have:<jupyter_code>linked = linkage(x_sc, method = 'ward')
plt.figure(figsize=(15, 10))  
dendrogram(linked, orientation='top')
plt.title('Hierarchical clustering for gym customers')
plt.show()<jupyter_output><empty_output><jupyter_text>We can see that our dendogram divides our data into 5 main clusters. The 2 right ones are very simillar, as they are connected in a relativly low point. Then we have our 2 left clusters, That connect at a higher point. And finally, we have the middle cluster, That connects with the 2 right ones at a very high point. So we can conclude that we have 2 clusters that are very simillar to each other, 2 clusters that are simillar to each other but very different from the other ones, and 1 more cluster that is relativly in the middle. Let's define that we have 5 clusters. We'll add them to the table:<jupyter_code>km = KMeans(n_clusters = 5, random_state = 0)
labels = km.fit_predict(x_sc)
data['cluster'] = labels<jupyter_output><empty_output><jupyter_text>Let's check the mean values and distribution for the clusters:<jupyter_code>data.groupby('cluster').mean()
for i in data.columns:
        print(i,":")
        sns.displot(data = data,x = i, col = 'cluster', palette = 'bright')
        plt.figure(figsize = (10,10))
        plt.show();<jupyter_output>gender :<jupyter_text>We can see that we have 3 large clusters - 1,2 and 3, and 2 smaller ones - 0 and 4. Let's start with cluster 2. We can see that the main characteristics of this cluster are people who came through company deals and in addition through friends deals. This cluster has the longest contarct average, but not the highest lifetime. Cluster 3 dosen't have any main characteristics, But we see it is the leading cluster in many categories - additional charges, group visits and class frequency. We can call this cluster the 'gym loving' cluster. We can clearly see that cluster 4 is the cluster of the people who didn't give their phone number. This cluster seems relativly average in all of the featurs. Cluster 0's main feature is people who live far from the gym. This cluster has the lowest average of attending group meetings. Cluster 1's main characteristic is people with short contract - the vast majority of this cluster has a contract for a month. This cluster is by far the lowest cluster in terms of lifetime, class frequancy and additional payment.  Let's check the Churn rate for all of the clusters:<jupyter_code>churn_data = data.groupby('cluster')['Churn'].mean()
churn_data = churn_data.groupby(level = 0).apply(lambda x: "{:%}".format(float(x)))
churn_data
churn_pivot = data.pivot_table(index = 'cluster', values = 'Churn', aggfunc = 'mean')
for i in churn_pivot.index:
    print("Churn percentage for cluster",i,": {:.2%}".format(churn_pivot['Churn'][i]))<jupyter_output>Churn percentage for cluster 0 : 42.23%
Churn percentage for cluster 1 : 63.50%
Churn percentage for cluster 2 : 8.39%
Churn percentage for cluster 3 : 3.08%
Churn percentage for cluster 4 : 28.37%
{'fraction_non_alphanumeric': 0.07925388251181634, 'fraction_numerical': 0.018821742066171505, 'mean_word_length': 4.529164722351843, 'pattern_counts': {'":': 2, '<': 67, '<?xml version=': 0, '>': 66, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 6, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '17', 'dir': 'jupyter-structured-clean-dedup', 'id': '467054', 'license': 'MIT', 'n_tokens_mistral': 3644, 'n_tokens_neox': 3355, 'n_words': 1403, 'path': 'Machine Learning/Machine Learning.ipynb', 'repo_name': 'nadavkloot/Projects', 'size': '11848'}