DS Capestone PDF
DS Capestone PDF
1.Domain Introduction
localhost:8888/notebooks/Downloads/project_preventing_customer_from_unscribing_a_telecom_plan.ipynb#This-project-consists-of-3000-mark… 1/41
9/9/2019 project_preventing_customer_from_unscribing_a_telecom_plan
We have the customer data for a telecom company which offers many services like phone, internet, TV
Streaming and Movie Streaming.
2.Problem Statement
"Find the Best model to predict behavior to retain customers. You can analyze all relevant customer data and
develop focused customer retention programs."
3. Data Source
Available at : IBM watson analytics page (https://community.watsonanalytics.com/wp-
content/uploads/2015/03/WA_Fn-UseC_-Telco-Customer-Churn.csv?
cm_mc_uid=14714377267115403444551&cm_mc_sid_50200000=12578191540344455127&cm_mc_sid_526400
4. Data Description
This data set provides info to help you predict behavior to retain customers. You can analyze all relevant
customer data and develop focused customer retention programs.
A telecommunications company is concerned about the number of customers leaving their landline business for
cable competitors. They need to understand who is leaving. Imagine that you’re an analyst at this company and
you have to find out who is leaving and why.
Customers who left within the last month – the column is called Churn
Services that each customer has signed up for – phone, multiple lines, internet, online security, online backup,
device protection, tech support, and streaming TV and movies
Customer account information – how long they’ve been a customer, contract, payment method, paperless
billing, monthly charges, and total charges
Demographic info about customers – gender, age range, and if they have partners and dependents
localhost:8888/notebooks/Downloads/project_preventing_customer_from_unscribing_a_telecom_plan.ipynb#This-project-consists-of-3000-mark… 2/41
9/9/2019 project_preventing_customer_from_unscribing_a_telecom_plan
In [0]:
In [0]:
df = pd.read_csv('../datasets/WA_Fn-UseC_-Telco-Customer-Churn.csv',index_col='customerID')
df.head()
Out[3]:
customerID
7590- No phone
Female 0 Yes No 1 No
VHVEG service
5575-
Male 0 No No 34 Yes No
GNVDE
3668-
Male 0 No No 2 Yes No
QPYBK
7795- No phone
Male 0 No No 45 No
CFOCW service
9237-
Female 0 No No 2 Yes No
HQITU
localhost:8888/notebooks/Downloads/project_preventing_customer_from_unscribing_a_telecom_plan.ipynb#This-project-consists-of-3000-mark… 3/41
9/9/2019 project_preventing_customer_from_unscribing_a_telecom_plan
In [0]:
df.info()
<class 'pandas.core.frame.DataFrame'>
Index: 7043 entries, 7590-VHVEG to 3186-AJIEK
Data columns (total 20 columns):
gender 7043 non-null object
SeniorCitizen 7043 non-null int64
Partner 7043 non-null object
Dependents 7043 non-null object
tenure 7043 non-null int64
PhoneService 7043 non-null object
MultipleLines 7043 non-null object
InternetService 7043 non-null object
OnlineSecurity 7043 non-null object
OnlineBackup 7043 non-null object
DeviceProtection 7043 non-null object
TechSupport 7043 non-null object
StreamingTV 7043 non-null object
StreamingMovies 7043 non-null object
Contract 7043 non-null object
PaperlessBilling 7043 non-null object
PaymentMethod 7043 non-null object
MonthlyCharges 7043 non-null float64
TotalCharges 7043 non-null object
Churn 7043 non-null object
dtypes: float64(1), int64(2), object(17)
memory usage: 1.1+ MB
df.describe()
In [0]:
df.describe(include=object)
Out[6]:
unique 2 2 2 2 3 3
8. Data Manipulation
Data Manipulation
localhost:8888/notebooks/Downloads/project_preventing_customer_from_unscribing_a_telecom_plan.ipynb#This-project-consists-of-3000-mark… 4/41
9/9/2019 project_preventing_customer_from_unscribing_a_telecom_plan
In [0]:
df.isna().any()
Out[7]:
gender False
SeniorCitizen False
Partner False
Dependents False
tenure False
PhoneService False
MultipleLines False
InternetService False
OnlineSecurity False
OnlineBackup False
DeviceProtection False
TechSupport False
StreamingTV False
StreamingMovies False
Contract False
PaperlessBilling False
PaymentMethod False
MonthlyCharges False
TotalCharges False
Churn False
dtype: bool
In [0]:
df[df['TotalCharges'].isna()]
Out[8]:
customerID
In [0]:
len(df[df['TotalCharges'].isna()])
Out[9]:
Here we can see that Total Charges is an object variable. Let's Change it to float
localhost:8888/notebooks/Downloads/project_preventing_customer_from_unscribing_a_telecom_plan.ipynb#This-project-consists-of-3000-mark… 5/41
9/9/2019 project_preventing_customer_from_unscribing_a_telecom_plan
In [0]:
df.info()
<class 'pandas.core.frame.DataFrame'>
Index: 7043 entries, 7590-VHVEG to 3186-AJIEK
Data columns (total 20 columns):
gender 7043 non-null object
SeniorCitizen 7043 non-null int64
Partner 7043 non-null object
Dependents 7043 non-null object
tenure 7043 non-null int64
PhoneService 7043 non-null object
MultipleLines 7043 non-null object
InternetService 7043 non-null object
OnlineSecurity 7043 non-null object
OnlineBackup 7043 non-null object
DeviceProtection 7043 non-null object
TechSupport 7043 non-null object
StreamingTV 7043 non-null object
StreamingMovies 7043 non-null object
Contract 7043 non-null object
PaperlessBilling 7043 non-null object
PaymentMethod 7043 non-null object
MonthlyCharges 7043 non-null float64
TotalCharges 7032 non-null float64
Churn 7043 non-null object
dtypes: float64(2), int64(2), object(16)
memory usage: 1.1+ MB
every missing value record comes from customers who has not opted out
** Imputation **
In [0]:
df['TotalCharges'] = df['TotalCharges'].fillna((df['TotalCharges'].mean()))
** Data formating **
column_categorical = df_categorical.columns
localhost:8888/notebooks/Downloads/project_preventing_customer_from_unscribing_a_telecom_plan.ipynb#This-project-consists-of-3000-mark… 6/41
9/9/2019 project_preventing_customer_from_unscribing_a_telecom_plan
In [0]:
df_categorical.head()
Out[13]:
customerID
7590- No phone
Female Yes No No DSL
VHVEG service
5575-
Male No No Yes No DSL
GNVDE
3668-
Male No No Yes No DSL
QPYBK
7795- No phone
Male No No No DSL
CFOCW service
9237-
Female No No Yes No Fiber optic
HQITU
In [0]:
column_numerical = df_numerical.columns
In [0]:
df_numerical.head()
Out[15]:
MonthlyCharges TotalCharges
customerID
Univariate Analysis
localhost:8888/notebooks/Downloads/project_preventing_customer_from_unscribing_a_telecom_plan.ipynb#This-project-consists-of-3000-mark… 7/41
9/9/2019 project_preventing_customer_from_unscribing_a_telecom_plan
In [0]:
Return
No object returned but visualized plot will return based on specified inputs
"""
n = 0
this = []
if object_mode:
nrows = 4
ncols = 4
width = 20
height = 20
else:
nrows = 2
ncols = 2
width = 14
height = 10
else:
if (df[column].dtypes != 'O'):
this.append(column)
localhost:8888/notebooks/Downloads/project_preventing_customer_from_unscribing_a_telecom_plan.ipynb#This-project-consists-of-3000-mark… 8/41
9/9/2019 project_preventing_customer_from_unscribing_a_telecom_plan
In [0]:
localhost:8888/notebooks/Downloads/project_preventing_customer_from_unscribing_a_telecom_plan.ipynb#This-project-consists-of-3000-mark… 9/41
9/9/2019 project_preventing_customer_from_unscribing_a_telecom_plan
In [0]:
feature Engineering
Based on the value of the services the subscribers subscribed to, there are yes, no, and no phone / internet
localhost:8888/notebooks/Downloads/project_preventing_customer_from_unscribing_a_telecom_plan.ipynb#This-project-consists-of-3000-mar… 10/41
9/9/2019 project_preventing_customer_from_unscribing_a_telecom_plan
service. These are somewhat related to primary products. Examples are illustrated through panda crosstab
function below:
If the subscribers have phone service, they may have multiple lines (yes or no).
But if the subscribers don't have phone service, the subscribers will never have multiple lines.
In [0]:
PhoneService
No 0 682 0
2. Internet Service (Primary) and other services, let's say streaming TV (secondary)
If the subscribers have Internet services (either DSL or Fiber optic), the subscribers may opt to have
other services related to Internet (i.e. streaming TV, device protection).
But if the subscribers don't have the Internet services, this secondary service will not be available for
the subscribers.
In [0]:
InternetService
No 0 1526 0
With this conclusion, I opt to transform the feature value of No Phone / Internet service to be the same No
because it can be used another features (hence, phone service and internet service column) to explain.
localhost:8888/notebooks/Downloads/project_preventing_customer_from_unscribing_a_telecom_plan.ipynb#This-project-consists-of-3000-mar… 11/41
9/9/2019 project_preventing_customer_from_unscribing_a_telecom_plan
In [0]:
In [0]:
df = convert_no_service(df)
In [0]:
localhost:8888/notebooks/Downloads/project_preventing_customer_from_unscribing_a_telecom_plan.ipynb#This-project-consists-of-3000-mar… 12/41
9/9/2019 project_preventing_customer_from_unscribing_a_telecom_plan
In [0]:
We can See that Gender Does'nt Play an important Role in Predicting Our Target Variable.
localhost:8888/notebooks/Downloads/project_preventing_customer_from_unscribing_a_telecom_plan.ipynb#This-project-consists-of-3000-mar… 13/41
9/9/2019 project_preventing_customer_from_unscribing_a_telecom_plan
In [0]:
# Contract Vs Churn
Most of the People that Left were the Ones who had Month-to-Month Contract.
localhost:8888/notebooks/Downloads/project_preventing_customer_from_unscribing_a_telecom_plan.ipynb#This-project-consists-of-3000-mar… 14/41
9/9/2019 project_preventing_customer_from_unscribing_a_telecom_plan
In [0]:
localhost:8888/notebooks/Downloads/project_preventing_customer_from_unscribing_a_telecom_plan.ipynb#This-project-consists-of-3000-mar… 15/41
9/9/2019 project_preventing_customer_from_unscribing_a_telecom_plan
In [0]:
# Partner Vs Dependents
We can See Partners had a much larger percent of Dependents than Non-Partner this tells us that Most
Partners might be Married.
localhost:8888/notebooks/Downloads/project_preventing_customer_from_unscribing_a_telecom_plan.ipynb#This-project-consists-of-3000-mar… 16/41
9/9/2019 project_preventing_customer_from_unscribing_a_telecom_plan
In [0]:
# Partner Vs Churn
In [0]:
plt.figure(figsize=(17,8))
sns.countplot(x=df['tenure'],hue=df.Partner);
Most of the People that Were Partner will Stay Longer with The Company. So Being a Partner is a Plus-
localhost:8888/notebooks/Downloads/project_preventing_customer_from_unscribing_a_telecom_plan.ipynb#This-project-consists-of-3000-mar… 17/41
9/9/2019 project_preventing_customer_from_unscribing_a_telecom_plan
Point For the Company as they will Stay Longer with Them.
In [0]:
# Partner Vs Churn
In [0]:
localhost:8888/notebooks/Downloads/project_preventing_customer_from_unscribing_a_telecom_plan.ipynb#This-project-consists-of-3000-mar… 18/41
9/9/2019 project_preventing_customer_from_unscribing_a_telecom_plan
Let's Check for Outliers in Monthly Charges And Total Charges Using Box Plots
In [0]:
df.boxplot('MonthlyCharges');
Monthly Charges don't have any Outliers so we don't have to Get into Extracting Information from
Outliers.
In [0]:
## correlation matrix
Here We can See Tenure and Total Charges are correlated and also Monthly charges and Total Charges
are also correlated with each other.
we can assume from our domain expertise that , Total Charges ~ Monthly Charges * Tenure + Additional
Charges(Tax).
localhost:8888/notebooks/Downloads/project_preventing_customer_from_unscribing_a_telecom_plan.ipynb#This-project-consists-of-3000-mar… 19/41
9/9/2019 project_preventing_customer_from_unscribing_a_telecom_plan
Bucketing
In [0]:
if telcom["tenure"] <= 12 :
return "Tenure_0-12"
elif (telcom["tenure"] > 12) & (telcom["tenure"] <= 24 ):
return "Tenure_12-24"
elif (telcom["tenure"] > 24) & (telcom["tenure"] <= 48) :
return "Tenure_24-48"
elif (telcom["tenure"] > 48) & (telcom["tenure"] <= 60) :
return "Tenure_48-60"
elif telcom["tenure"] > 60 :
return "Tenure_gt_60"
#replace values
df["SeniorCitizen"] = df["SeniorCitizen"].replace({1:"Yes",0:"No"})
In [0]:
#customer id col
Id_col = ['customerID']
#Target columns
target_col = ["Churn"]
#categorical columns
cat_cols = df.nunique()[df.nunique() < 6].keys().tolist()
cat_cols = [x for x in cat_cols if x not in target_col]
#numerical columns
num_cols = [x for x in df.columns if x not in cat_cols + target_col + Id_col]
#Binary columns with 2 values
bin_cols = df.nunique()[df.nunique() == 2].keys().tolist()
#Columns more than 2 values
multi_cols = [i for i in cat_cols if i not in bin_cols]
localhost:8888/notebooks/Downloads/project_preventing_customer_from_unscribing_a_telecom_plan.ipynb#This-project-consists-of-3000-mar… 20/41
9/9/2019 project_preventing_customer_from_unscribing_a_telecom_plan
Normalizing features
In [0]:
/home/ubuntu/.virtualenvs/Data_Science/lib/python3.6/site-packages/sklearn/p
reprocessing/data.py:617: DataConversionWarning: Data with input dtype int6
4, float64 were all converted to float64 by StandardScaler.
return self.partial_fit(X, y)
/home/ubuntu/.virtualenvs/Data_Science/lib/python3.6/site-packages/sklearn/b
ase.py:462: DataConversionWarning: Data with input dtype int64, float64 were
all converted to float64 by StandardScaler.
return self.fit(X, **fit_params).transform(X)
# Machine learning
from sklearn import
from sklearn.svm import
from sklearn.ensemble import
from sklearn.neighbors import
from sklearn.naive_bayes import
from sklearn.linear_model import
from sklearn.tree import
from xgboost.sklearn import
In [0]:
# validation
from sklearn import
In [0]:
localhost:8888/notebooks/Downloads/project_preventing_customer_from_unscribing_a_telecom_plan.ipynb#This-project-consists-of-3000-mar… 21/41
9/9/2019 project_preventing_customer_from_unscribing_a_telecom_plan
In [0]:
# Metrics
from sklearn.metrics import
In [0]:
#utilities
import time
import io, os, sys, types, time, datetime, math, random
In [0]:
# calculate the fpr and tpr for all thresholds of the classification
# Function that runs the requested algorithm and returns the accuracy metrics
localhost:8888/notebooks/Downloads/project_preventing_customer_from_unscribing_a_telecom_plan.ipynb#This-project-consists-of-3000-mar… 22/41
9/9/2019 project_preventing_customer_from_unscribing_a_telecom_plan
In [0]:
clf = DummyClassifier(strategy='most_frequent',random_state=0)
clf.fit(X_train, y_train)
Out[48]:
In [0]:
0.7535491198182851
localhost:8888/notebooks/Downloads/project_preventing_customer_from_unscribing_a_telecom_plan.ipynb#This-project-consists-of-3000-mar… 23/41
9/9/2019 project_preventing_customer_from_unscribing_a_telecom_plan
In [0]:
preds = clf.predict(X_test)
# dummyistic Regression
start_time = time.time()
train_pred_dummy, test_pred_dummy, acc_dum
my, acc_cv_dummy, probs_dummy = fit_ml_algo
(DummyClassifier(strategy='most_frequent',
random_state=0),
X_train, y_train, X_test, 10)
Accuracy: 75.35
Accuracy CV 10-Fold: 72.83
Running Time: 0:00:03.575734
precision recall f1-score support
/home/ubuntu/.virtualenvs/Data_Science/lib/python3.6/site-packages/sklear
n/metrics/classification.py:1143: UndefinedMetricWarning: Precision and F-
score are ill-defined and being set to 0.0 in labels with no predicted sam
ples.
'precision', 'predicted', average, warn_for)
/home/ubuntu/.virtualenvs/Data_Science/lib/python3.6/site-packages/sklear
n/metrics/classification.py:1143: UndefinedMetricWarning: Precision and F-
score are ill-defined and being set to 0.0 in labels with no predicted sam
ples.
'precision', 'predicted', average, warn_for)
/home/ubuntu/.virtualenvs/Data_Science/lib/python3.6/site-packages/sklear
n/metrics/classification.py:1143: UndefinedMetricWarning: Precision and F-
score are ill-defined and being set to 0.0 in labels with no predicted sam
ples.
'precision', 'predicted', average, warn_for)
localhost:8888/notebooks/Downloads/project_preventing_customer_from_unscribing_a_telecom_plan.ipynb#This-project-consists-of-3000-mar… 24/41
9/9/2019 project_preventing_customer_from_unscribing_a_telecom_plan
/home/ubuntu/.virtualenvs/Data_Science/lib/python3.6/site-packages/sklear
n/metrics/classification.py:1143: UndefinedMetricWarning: Precision and F-
score are ill-defined and being set to 0.0 in labels with no predicted sam
ples.
'precision', 'predicted', average, warn_for)
/home/ubuntu/.virtualenvs/Data_Science/lib/python3.6/site-packages/sklear
n/metrics/classification.py:1143: UndefinedMetricWarning: Precision and F-
score are ill-defined and being set to 0.0 in labels with no predicted sam
ples.
'precision', 'predicted', average, warn_for)
/home/ubuntu/.virtualenvs/Data_Science/lib/python3.6/site-packages/sklear
n/metrics/classification.py:1143: UndefinedMetricWarning: Precision and F-
score are ill-defined and being set to 0.0 in labels with no predicted sam
ples.
'precision' 'predicted' average warn for)
1. KNN
2. Logistic Regression
3. Random Forest
4. Naive Bayes
6. Linear SVC
7. Decision Tree
localhost:8888/notebooks/Downloads/project_preventing_customer_from_unscribing_a_telecom_plan.ipynb#This-project-consists-of-3000-mar… 25/41
9/9/2019 project_preventing_customer_from_unscribing_a_telecom_plan
In [0]:
/home/ubuntu/.virtualenvs/Data_Science/lib/python3.6/site-packages/sklearn/m
odel_selection/_split.py:1943: FutureWarning: You should specify a value for
'cv' instead of relying on the default value. The default value will change
from 3 to 5 in version 0.22.
warnings.warn(CV_WARNING, FutureWarning)
/home/ubuntu/.virtualenvs/Data_Science/lib/python3.6/site-packages/sklearn/l
inear_model/logistic.py:432: FutureWarning: Default solver will be changed t
o 'lbfgs' in 0.22. Specify a solver to silence this warning.
FutureWarning)
localhost:8888/notebooks/Downloads/project_preventing_customer_from_unscribing_a_telecom_plan.ipynb#This-project-consists-of-3000-mar… 26/41
9/9/2019 project_preventing_customer_from_unscribing_a_telecom_plan
In [0]:
/home/ubuntu/.virtualenvs/Data_Science/lib/python3.6/site-packages/sklearn/l
inear_model/logistic.py:432: FutureWarning: Default solver will be changed t
o 'lbfgs' in 0.22. Specify a solver to silence this warning.
FutureWarning)
/home/ubuntu/.virtualenvs/Data_Science/lib/python3.6/site-packages/sklearn/l
inear_model/logistic.py:1296: UserWarning: 'n_jobs' > 1 does not have any ef
fect when 'solver' is set to 'liblinear'. Got 'n_jobs' = 12.
" = {}.".format(effective_n_jobs(self.n_jobs)))
Accuracy: 80.86
Accuracy CV 10-Fold: 80.1
Running Time: 0:00:00.576369
precision recall f1-score support
localhost:8888/notebooks/Downloads/project_preventing_customer_from_unscribing_a_telecom_plan.ipynb#This-project-consists-of-3000-mar… 27/41
9/9/2019 project_preventing_customer_from_unscribing_a_telecom_plan
localhost:8888/notebooks/Downloads/project_preventing_customer_from_unscribing_a_telecom_plan.ipynb#This-project-consists-of-3000-mar… 28/41
9/9/2019 project_preventing_customer_from_unscribing_a_telecom_plan
In [0]:
Accuracy: 74.56
Accuracy CV 10-Fold: 74.93
Running Time: 0:00:00.601969
precision recall f1-score support
localhost:8888/notebooks/Downloads/project_preventing_customer_from_unscribing_a_telecom_plan.ipynb#This-project-consists-of-3000-mar… 29/41
9/9/2019 project_preventing_customer_from_unscribing_a_telecom_plan
In [0]:
Accuracy: 73.65
Accuracy CV 10-Fold: 74.67
Running Time: 0:00:00.113730
precision recall f1-score support
localhost:8888/notebooks/Downloads/project_preventing_customer_from_unscribing_a_telecom_plan.ipynb#This-project-consists-of-3000-mar… 30/41
9/9/2019 project_preventing_customer_from_unscribing_a_telecom_plan
In [0]:
Accuracy: 74.11
Accuracy CV 10-Fold: 71.53
Running Time: 0:00:00.152194
precision recall f1-score support
localhost:8888/notebooks/Downloads/project_preventing_customer_from_unscribing_a_telecom_plan.ipynb#This-project-consists-of-3000-mar… 31/41
9/9/2019 project_preventing_customer_from_unscribing_a_telecom_plan
In [0]:
/home/ubuntu/.virtualenvs/Data_Science/lib/python3.6/site-packages/sklearn/m
odel_selection/_split.py:1943: FutureWarning: You should specify a value for
'cv' instead of relying on the default value. The default value will change
from 3 to 5 in version 0.22.
warnings.warn(CV_WARNING, FutureWarning)
localhost:8888/notebooks/Downloads/project_preventing_customer_from_unscribing_a_telecom_plan.ipynb#This-project-consists-of-3000-mar… 32/41
9/9/2019 project_preventing_customer_from_unscribing_a_telecom_plan
In [0]:
Accuracy: 80.01
Accuracy CV 10-Fold: 78.66
Running Time: 0:00:00.250799
precision recall f1-score support
localhost:8888/notebooks/Downloads/project_preventing_customer_from_unscribing_a_telecom_plan.ipynb#This-project-consists-of-3000-mar… 33/41
9/9/2019 project_preventing_customer_from_unscribing_a_telecom_plan
In [0]:
Accuracy: 80.58
Accuracy CV 10-Fold: 80.18
Running Time: 0:00:02.742447
precision recall f1-score support
localhost:8888/notebooks/Downloads/project_preventing_customer_from_unscribing_a_telecom_plan.ipynb#This-project-consists-of-3000-mar… 34/41
9/9/2019 project_preventing_customer_from_unscribing_a_telecom_plan
localhost:8888/notebooks/Downloads/project_preventing_customer_from_unscribing_a_telecom_plan.ipynb#This-project-consists-of-3000-mar… 35/41
9/9/2019 project_preventing_customer_from_unscribing_a_telecom_plan
In [0]:
localhost:8888/notebooks/Downloads/project_preventing_customer_from_unscribing_a_telecom_plan.ipynb#This-project-consists-of-3000-mar… 36/41
9/9/2019 project_preventing_customer_from_unscribing_a_telecom_plan
localhost:8888/notebooks/Downloads/project_preventing_customer_from_unscribing_a_telecom_plan.ipynb#This-project-consists-of-3000-mar… 37/41
9/9/2019 project_preventing_customer_from_unscribing_a_telecom_plan
In [0]:
localhost:8888/notebooks/Downloads/project_preventing_customer_from_unscribing_a_telecom_plan.ipynb#This-project-consists-of-3000-mar… 38/41
9/9/2019 project_preventing_customer_from_unscribing_a_telecom_plan
localhost:8888/notebooks/Downloads/project_preventing_customer_from_unscribing_a_telecom_plan.ipynb#This-project-consists-of-3000-mar… 39/41
9/9/2019 project_preventing_customer_from_unscribing_a_telecom_plan
In [0]:
Out[64]:
Model Score
0 KNN 74.56
localhost:8888/notebooks/Downloads/project_preventing_customer_from_unscribing_a_telecom_plan.ipynb#This-project-consists-of-3000-mar… 40/41
9/9/2019 project_preventing_customer_from_unscribing_a_telecom_plan
In [0]:
In [0]:
Interpretation
In [0]:
localhost:8888/notebooks/Downloads/project_preventing_customer_from_unscribing_a_telecom_plan.ipynb#This-project-consists-of-3000-mar… 41/41