import%20marimo%0A%0A__generated_with%20%3D%20%220.16.0%22%0Aapp%20%3D%20marimo.App(width%3D%22medium%22)%0A%0A%0A%40app.cell%0Adef%20_()%3A%0A%20%20%20%20import%20marimo%20as%20mo%0A%20%20%20%20import%20pandas%20as%20pd%0A%20%20%20%20import%20numpy%20as%20np%0A%20%20%20%20import%20matplotlib.pyplot%20as%20plt%0A%20%20%20%20import%20seaborn%20as%20sns%0A%20%20%20%20return%20mo%2C%20np%2C%20pd%2C%20plt%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(%0A%20%20%20%20%20%20%20%20r%22%22%22%0A%20%20%20%20%23%20Machine%20Learning%20Zoomcamp%0A%0A%20%20%20%20%23%23%20Module%204%3A%20**Evaluation**%0A%20%20%20%20%22%22%22%0A%20%20%20%20)%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(pd)%3A%0A%20%20%20%20repository_root%20%3D%20%22https%3A%2F%2Fgithub.com%2FDataTalksClub%2Fmachine-learning-zoomcamp%2Fblob%2Fmaster%2F%22%0A%0A%20%20%20%20chapters%20%3D%20pd.DataFrame(%5B%0A%20%20%20%20%20%20%20%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%22title%22%3A%20%22Evaluation%20Metrics%3A%20Session%20Overview%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%22youtube_id%22%3A%20%22gmg5jw1bM8A%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%22contents%22%3A%20repository_root%2B%2204-classification%2F01-overview.md%22%0A%20%20%20%20%20%20%20%20%7D%2C%0A%20%20%20%20%20%20%20%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%22title%22%3A%20%22Accuracy%20and%20Dummy%20Model%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%22youtube_id%22%3A%20%22FW_l7lB0HUI%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%22contents%22%3A%20repository_root%2B%2204-classification%2F02-accuracy.md%22%0A%20%20%20%20%20%20%20%20%7D%2C%0A%20%20%20%20%20%20%20%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%22title%22%3A%20%22Confusion%20Table%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%22youtube_id%22%3A%20%22Jt2dDLSlBng%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%22contents%22%3A%20repository_root%2B%2204-classification%2F03-confusion-table.md%22%0A%20%20%20%20%20%20%20%20%7D%2C%0A%20%20%20%20%20%20%20%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%22title%22%3A%20%22Precision%20and%20Recall%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%22youtube_id%22%3A%20%22gRLP_mlglMM%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%22contents%22%3A%20repository_root%2B%2204-classification%2F04-precision-recall.md%22%0A%20%20%20%20%20%20%20%20%7D%2C%0A%20%20%20%20%20%20%20%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%22title%22%3A%20%22ROC%20Curves%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%22youtube_id%22%3A%20%22dnBZLk53sQI%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%22contents%22%3A%20repository_root%2B%2204-classification%2F05-roc.md%22%0A%20%20%20%20%20%20%20%20%7D%2C%0A%20%20%20%20%20%20%20%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%22title%22%3A%20%22ROC%20AUC%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%22youtube_id%22%3A%20%22hvIQPAwkVZo%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%22contents%22%3A%20repository_root%2B%2204-classification%2F06-auc.md%22%0A%20%20%20%20%20%20%20%20%7D%2C%0A%20%20%20%20%20%20%20%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%22title%22%3A%20%22Cross%20Validation%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%22youtube_id%22%3A%20%22BIIZaVtUbf4%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%22contents%22%3A%20repository_root%2B%2204-classification%2F07-cross-validation.md%22%0A%20%20%20%20%20%20%20%20%7D%2C%0A%20%20%20%20%20%20%20%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%22title%22%3A%20%22Summary%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%22youtube_id%22%3A%20%22-v8XEQ2AHvQ%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%22contents%22%3A%20repository_root%2B%2204-classification%2F08-summary%22%0A%20%20%20%20%20%20%20%20%7D%2C%0A%20%20%20%20%5D)%0A%0A%20%20%20%20chapters.insert(loc%3D0%2C%20column%3D%22snapshot%22%2C%20value%3D%22https%3A%2F%2Fimg.youtube.com%2Fvi%2F%22%2Bchapters.youtube_id.astype(str)%2B%22%2Fhqdefault.jpg%22)%0A%20%20%20%20chapters.insert(loc%3D2%2C%20column%3D%22youtube%22%2C%20value%3D%22https%3A%2F%2Fyoutube.com%2Fwatch%3Fv%3D%22%2Bchapters.youtube_id.astype(str))%0A%0A%20%20%20%20videos%20%3D%20chapters%5Bchapters%5B%22youtube_id%22%5D.notnull()%5D%0A%20%20%20%20videos%5B%5B%22snapshot%22%2C%20%22title%22%2C%20%22youtube%22%5D%5D%0A%20%20%20%20return%20(chapters%2C)%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(chapters)%3A%0A%20%20%20%20contents%20%3D%20chapters%5Bchapters%5B%22contents%22%5D.notnull()%5D%0A%20%20%20%20contents%5B%5B%22title%22%2C%20%22contents%22%5D%5D%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(%0A%20%20%20%20%20%20%20%20r%22%22%22%0A%20%20%20%20%23%23%20Evaluation%20Metrics%3A%20Session%20Overview%0A%0A%20%20%20%20In%20the%20last%20module%20we%20created%20a%20model%20that%20predicted%20if%20a%20customer%20was%20about%20to%20churn%20or%20not.%20During%20the%20last%20part%20of%20the%20module%20we%20found%20that%20our%20model%20had%20an%20accuracy%20of%20around%2081%25.%20In%20this%20module%20we'll%20try%20to%20understand%20how%20to%20decide%20wether%20that's%20a%20good%20accuracy%2C%20or%20not.%0A%0A%20%20%20%20%23%23%23%20Churn%20Predictor%0A%0A%20%20%20%20To%20get%20started%2C%20let's%20quickly%20set%20up%20our%20model%20again.%0A%0A%20%20%20%20%23%23%23%23%20Read%20and%20Standardize%20the%20Dataset%0A%20%20%20%20%22%22%22%0A%20%20%20%20)%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(pd)%3A%0A%20%20%20%20def%20standardize_column_names(dataframe%3A%20pd.DataFrame)%20-%3E%20pd.DataFrame%3A%0A%20%20%20%20%20%20%20%20standardized%20%3D%20dataframe.copy()%0A%20%20%20%20%20%20%20%20standardized.columns%20%3D%20standardized.columns.str.lower().str.replace('%20'%2C%20'_')%0A%0A%20%20%20%20%20%20%20%20return%20standardized%0A%0A%20%20%20%20def%20get_cateogorical_columns(dataframe%3A%20pd.DataFrame)%20-%3E%20list%5Bstr%5D%3A%0A%20%20%20%20%20%20%20%20return%20list(list(dataframe.dtypes%5Bdataframe.dtypes%20%3D%3D%20'object'%5D.index))%0A%0A%20%20%20%20def%20standardize_categorical_values(dataframe%3A%20pd.DataFrame)%20-%3E%20pd.DataFrame%3A%0A%20%20%20%20%20%20%20%20standardized%20%3D%20dataframe.copy()%0A%0A%20%20%20%20%20%20%20%20for%20column%20in%20get_cateogorical_columns(standardized)%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20standardized%5Bcolumn%5D%20%3D%20standardized%5Bcolumn%5D.str.lower().str.replace('%20'%2C%20'_')%0A%0A%20%20%20%20%20%20%20%20return%20standardized%0A%0A%20%20%20%20def%20standardize_non_categorical_values(dataframe%3A%20pd.DataFrame)%20-%3E%20pd.DataFrame%3A%0A%20%20%20%20%20%20%20%20totalcharges%20%3D%20pd.to_numeric(df_standardized.totalcharges%2C%20errors%3D'coerce')%0A%20%20%20%20%20%20%20%20df_standardized.totalcharges%20%3D%20totalcharges.fillna(0)%0A%0A%20%20%20%20%20%20%20%20return%20df_standardized%0A%0A%20%20%20%20df_raw%20%3D%20pd.read_csv(%22module-3%2Fdata%2Fcustomer-churn.csv%22)%0A%20%20%20%20df_standardized%20%3D%20standardize_column_names(df_raw)%0A%20%20%20%20df_standardized%20%3D%20standardize_categorical_values(df_standardized)%0A%20%20%20%20df_standardized%20%3D%20standardize_non_categorical_values(df_standardized)%0A%0A%20%20%20%20df_standardized.head()%0A%20%20%20%20return%20(df_standardized%2C)%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%23%23%23%23%20Setup%20the%20Validation%20Framework%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(df_standardized%2C%20pd)%3A%0A%20%20%20%20from%20sklearn.model_selection%20import%20train_test_split%0A%0A%20%20%20%20def%20split_dataframe(dataframe%3A%20pd.DataFrame)%20-%3E%20pd.DataFrame%3A%0A%20%20%20%20%20%20%20%20full_train%2C%20test%20%3D%20train_test_split(dataframe%2C%20test_size%3D0.2%2C%20random_state%3D1)%0A%20%20%20%20%20%20%20%20train%2C%20val%20%3D%20train_test_split(full_train%2C%20test_size%3D0.25%2C%20random_state%3D1)%0A%0A%20%20%20%20%20%20%20%20return%20train%2C%20val%2C%20test%2C%20full_train%0A%0A%20%20%20%20df_train%2C%20df_val%2C%20df_test%2C%20df_full%20%3D%20split_dataframe(df_standardized)%0A%0A%20%20%20%20df_train%20%3D%20df_train.reset_index(drop%3DTrue)%0A%20%20%20%20df_val%20%3D%20df_val.reset_index(drop%3DTrue)%0A%20%20%20%20df_test%20%3D%20df_test.reset_index(drop%3DTrue)%0A%20%20%20%20df_full%20%3D%20df_full.reset_index(drop%3DTrue)%0A%0A%20%20%20%20%7B%0A%20%20%20%20%20%20%20%20%22df_train%22%3A%20len(df_train)%2C%0A%20%20%20%20%20%20%20%20%22df_val%22%3A%20len(df_val)%2C%0A%20%20%20%20%20%20%20%20%22df_test%22%3A%20len(df_test)%2C%0A%20%20%20%20%20%20%20%20%22df_full%22%3A%20len(df_full)%2C%0A%20%20%20%20%7D%0A%20%20%20%20return%20df_full%2C%20df_val%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%23%23%23%23%20Prepare%20the%20Features%20and%20Target%20Tensors%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(df_full%2C%20df_val%2C%20pd)%3A%0A%20%20%20%20from%20sklearn.feature_extraction%20import%20DictVectorizer%0A%0A%20%20%20%20numerical_columns%20%3D%20%5B%22tenure%22%2C%20%22monthlycharges%22%2C%20%22totalcharges%22%5D%0A%0A%20%20%20%20categorical_columns%20%3D%20%5B%0A%20%20%20%20%20%20%20%20'gender'%2C%20'seniorcitizen'%2C%20'partner'%2C%20'dependents'%2C%0A%20%20%20%20%20%20%20%20'phoneservice'%2C%20'multiplelines'%2C%20'internetservice'%2C%20'onlinesecurity'%2C%0A%20%20%20%20%20%20%20%20'onlinebackup'%2C%20'deviceprotection'%2C%20'techsupport'%2C%20'streamingtv'%2C%0A%20%20%20%20%20%20%20%20'streamingmovies'%2C%20'contract'%2C%20'paperlessbilling'%2C%20'paymentmethod'%0A%20%20%20%20%5D%0A%0A%20%20%20%20def%20get_full_trained_vectorizer(dataframe%3A%20pd.DataFrame)%20-%3E%20list%5Bdict%5D%3A%0A%20%20%20%20%20%20%20%20copy%20%3D%20dataframe.copy()%0A%20%20%20%20%20%20%20%20del%20copy%5B%22churn%22%5D%0A%20%20%20%20%20%20%20%20dictionary%20%3D%20copy.to_dict(orient%3D%22records%22)%0A%0A%20%20%20%20%20%20%20%20dict_vectorizer%20%3D%20DictVectorizer(sparse%3DFalse)%0A%20%20%20%20%20%20%20%20dict_vectorizer.fit(dictionary)%0A%0A%20%20%20%20%20%20%20%20return%20dict_vectorizer%2C%20dictionary%0A%0A%20%20%20%20def%20get_features_and_target(dataframe%3A%20pd.DataFrame%2C%20dict_vectorizer%3A%20DictVectorizer%2C%20dictionary)%3A%0A%20%20%20%20%20%20%20%20X%20%3D%20dict_vectorizer.transform(dictionary)%0A%20%20%20%20%20%20%20%20y%20%3D%20dataframe.churn%20%3D%3D%20%22yes%22%0A%0A%20%20%20%20%20%20%20%20return%20X%2C%20y%0A%0A%20%20%20%20dict_vectorizer_full%2C%20dictionary_full%20%3D%20get_full_trained_vectorizer(df_full)%0A%20%20%20%20X_full%2C%20y_full%20%3D%20get_features_and_target(df_full%2C%20dict_vectorizer_full%2C%20dictionary_full)%0A%0A%20%20%20%20dictionary_val%20%3D%20df_val%5Bnumerical_columns%20%2B%20categorical_columns%5D.to_dict(orient%3D%22records%22)%0A%20%20%20%20X_val%2C%20y_val%20%3D%20get_features_and_target(df_val%2C%20dict_vectorizer_full%2C%20dictionary_val)%0A%0A%20%20%20%20%7B%0A%20%20%20%20%20%20%20%20%22X_full%22%3A%20len(X_full)%2C%0A%20%20%20%20%20%20%20%20%22y_full%22%3A%20len(y_full)%2C%0A%20%20%20%20%20%20%20%20%22X_val%22%3A%20len(X_val)%2C%0A%20%20%20%20%20%20%20%20%22y_val%22%3A%20len(y_val)%2C%0A%20%20%20%20%7D%0A%20%20%20%20return%20(%0A%20%20%20%20%20%20%20%20X_full%2C%0A%20%20%20%20%20%20%20%20X_val%2C%0A%20%20%20%20%20%20%20%20categorical_columns%2C%0A%20%20%20%20%20%20%20%20get_features_and_target%2C%0A%20%20%20%20%20%20%20%20get_full_trained_vectorizer%2C%0A%20%20%20%20%20%20%20%20numerical_columns%2C%0A%20%20%20%20%20%20%20%20y_full%2C%0A%20%20%20%20%20%20%20%20y_val%2C%0A%20%20%20%20)%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%23%23%23%23%20Train%20the%20Model%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(X_full%2C%20X_val%2C%20y_full%2C%20y_val)%3A%0A%20%20%20%20from%20sklearn.linear_model%20import%20LogisticRegression%0A%0A%20%20%20%20model%20%3D%20LogisticRegression(max_iter%3D5000)%0A%20%20%20%20model.fit(X_full%2C%20y_full)%0A%20%20%20%20model.score(X_val%2C%20y_val)%0A%20%20%20%20return%20LogisticRegression%2C%20model%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(%0A%20%20%20%20%20%20%20%20r%22%22%22%0A%20%20%20%20%23%23%20Accuracy%20and%20Dummy%20Model%0A%0A%20%20%20%20As%20part%20of%20the%20implementation%20of%20our%20predictor%2C%20we%20chose%20a%20%240.5%24%20threshold%2C%20which%20we%20interpreted%20as%20a%20probability.%0A%0A%20%20%20%20Now%20we'll%20start%20by%20created%20a%20predictor%20where%20we%20can%20manually%20set%20that%20threshold.%0A%20%20%20%20%22%22%22%0A%20%20%20%20)%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(X_val%2C%20model%2C%20np%2C%20pd%2C%20y_val)%3A%0A%20%20%20%20def%20predict(X%3A%20np.ndarray%2C%20threshold%3A%20float%20%3D%200.5)%20-%3E%20pd.DataFrame%3A%0A%20%20%20%20%20%20%20%20predictions%20%3D%20pd.DataFrame()%0A%20%20%20%20%20%20%20%20predictions%5B%22probability%22%5D%20%3D%20model.predict_proba(X)%5B%3A%2C%201%5D%0A%20%20%20%20%20%20%20%20predictions%5B%22prediction%22%5D%20%3D%20predictions%5B%22probability%22%5D%20%3E%20threshold%0A%0A%20%20%20%20%20%20%20%20return%20predictions%0A%0A%20%20%20%20def%20evaluate(X%3A%20np.ndarray%2C%20y%3A%20np.ndarray%2C%20threshold%3A%20float%20%3D%200.5)%3A%0A%20%20%20%20%20%20%20%20y_pred%20%3D%20predict(X%2C%20threshold)%0A%0A%20%20%20%20%20%20%20%20return%20(y%20%3D%3D%20y_pred.prediction).mean()%0A%0A%20%20%20%20threshold_tries%20%3D%20np.linspace(0%2C%201%2C%2021)%0A%0A%20%20%20%20accuracies_per_threshold%20%3D%20%7B%7D%0A%20%20%20%20for%20threshold%20in%20threshold_tries%3A%0A%20%20%20%20%20%20%20%20accuracies_per_threshold%5Bround(threshold%2C%202)%5D%20%3D%20round(evaluate(X_val%2C%20y_val%2C%20threshold)%2C%203)%0A%0A%20%20%20%20accuracies_per_threshold%0A%20%20%20%20return%20accuracies_per_threshold%2C%20predict%2C%20threshold_tries%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22According%20to%20this%2C%20the%20best%20threshold%20is%20%240.55%24%20with%20an%20accuracy%20of%20around%20%2481%25%24.%20We%20can%20easily%20check%20it%20by%20plotting%20those%20accuracies.%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(accuracies_per_threshold%2C%20plt%2C%20threshold_tries)%3A%0A%20%20%20%20plt.scatter(y%20%3D%20accuracies_per_threshold.values()%2C%20x%20%3D%20threshold_tries)%0A%20%20%20%20plt.xlabel(%22Threshold%22)%0A%20%20%20%20plt.ylabel(%22Accuracy%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(%0A%20%20%20%20%20%20%20%20r%22%22%22%0A%20%20%20%20The%20first%20and%20last%20thresholds%20can%20be%20interpreted%20as%20follows%3A%0A%0A%20%20%20%20*%20a%20model%20with%20a%20threshold%20of%20%240%24%20predict%20that%20almost%20every%20customer%20will%20churn%2C%20as%20it%20will%20consider%20if%20the%20score%20computed%20for%20each%20customer%20satisfies%20%24score%20%3E%200%24%0A%20%20%20%20*%20on%20the%20other%20hand%2C%20a%20model%20with%20a%20threshold%20of%20%241%24%20predict%20that%20no%20customer%20will%20churn%2C%20as%20it%20will%20consider%20if%20the%20score%20computed%20for%20each%20customer%20satisfies%20%24score%20%3E%3D%201%24%0A%0A%20%20%20%20Let's%20check%20how%20many%20records%20do%20we%20have%20with%20those%20scores.%0A%20%20%20%20%22%22%22%0A%20%20%20%20)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(X_val%2C%20predict)%3A%0A%20%20%20%20%7B%0A%20%20%20%20%20%20%20%20%22score%20%3E%200%22%3A%20(predict(X_val%2C%20threshold%20%3D%200).prediction%20%3D%3D%20True).sum()%2C%0A%20%20%20%20%20%20%20%20%22score%20%3E%201%22%3A%20(predict(X_val%2C%20threshold%20%3D%201).prediction%20%3D%3D%20True).sum()%2C%0A%20%20%20%20%7D%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(%0A%20%20%20%20%20%20%20%20r%22%22%22%0A%20%20%20%20Here%20we%20have%20to%20stop%20and%20reflect%20about%20one%20thing%3A%0A%0A%20%20%20%20*%20the%20**trained%20model**%20has%20an%20accuracy%20of%20around%20**81.3%25**%0A%20%20%20%20*%20the%20**dummy%20model**%20that%20always%20predicts%20that%20our%20customers%20won't%20churn%20has%20an%20acuracy%20of%20**72.6%25**%0A%0A%20%20%20%20Why%20should%20we%20bother%2C%20after%20all%2C%20training%20a%20model%3F%0A%0A%20%20%20%20The%20answer%2C%20as%20we'll%20see%20below%2C%20is%20that%20accuracy%20is%20not%20telling%20us%20the%20full%20story.%0A%20%20%20%20%22%22%22%0A%20%20%20%20)%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(%0A%20%20%20%20%20%20%20%20r%22%22%22%0A%20%20%20%20%23%23%20Confusion%20Matrix%0A%0A%20%20%20%20If%20we%20want%20a%20complete%20version%20of%20the%20story%2C%20we'll%20have%20to%20check%20the%20confusion%20matrix.%20It's%20the%20matrix%20that%20divides%20our%20dataset%20into%20at%20least%20four%20categories%3A%0A%0A%20%20%20%20%7C%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%7C%20Predicted%20as%3A%20Churn%20%7C%20Predicted%20as%3A%20No%20Churn%20%7C%0A%20%20%20%20%7C%20---%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%7C%20---%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%7C%20---%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%7C%0A%20%20%20%20%7C%20**Actual%20state%3A%20Churn**%20%20%20%20%7C%20True%20positive%20%20%20%20%20%20%20%7C%20False%20negative%20%20%20%20%20%20%20%20%20%7C%0A%20%20%20%20%7C%20**Actual%20state%3A%20No%20Churn**%20%7C%20False%20positive%20%20%20%20%20%20%7C%20True%20negative%20%20%20%20%20%20%20%20%20%20%7C%0A%0A%20%20%20%20In%20our%20case%20there%20are%204%20categories%20because%20we%20are%20dealing%20with%20a%20binary%20problem%20but%20in%20other%20classification%20problems%20we'll%20see%20a%20bigger%20confusion%20matrix.%0A%0A%20%20%20%20%23%23%23%20Actual%20values%0A%0A%20%20%20%20Let's%20obtain%20the%20actual%20counts%20of%20those%20four%20categories%20for%20our%20validation%20dataset%20with%20our%20%240.55%24%20chosen%20threshold.%0A%20%20%20%20%22%22%22%0A%20%20%20%20)%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(X_val%2C%20df_val%2C%20predict)%3A%0A%20%20%20%20chosen_threshold%20%3D%200.55%0A%0A%20%20%20%20TP%20%3D%20(df_val%5Bpredict(X_val%2C%20chosen_threshold).prediction%20%3D%3D%20True%5D.churn%20%3D%3D%20%22yes%22).sum()%0A%20%20%20%20FN%20%3D%20(df_val%5Bpredict(X_val%2C%20chosen_threshold).prediction%20%3D%3D%20False%5D.churn%20%3D%3D%20%22yes%22).sum()%0A%20%20%20%20FP%20%3D%20(df_val%5Bpredict(X_val%2C%20chosen_threshold).prediction%20%3D%3D%20True%5D.churn%20!%3D%20%22yes%22).sum()%0A%20%20%20%20TN%20%3D%20(df_val%5Bpredict(X_val%2C%20chosen_threshold).prediction%20%3D%3D%20False%5D.churn%20!%3D%20%22yes%22).sum()%0A%0A%20%20%20%20%7B%0A%20%20%20%20%20%20%20%20%22True%20positive%22%3A%20TP%2C%0A%20%20%20%20%20%20%20%20%22False%20negative%22%3A%20FN%2C%0A%20%20%20%20%20%20%20%20%22False%20positive%22%3A%20FP%2C%0A%20%20%20%20%20%20%20%20%22True%20negative%22%3A%20TN%2C%0A%20%20%20%20%20%20%20%20%22All%20cases%22%3A%20TP%20%2B%20FN%20%2B%20FP%20%2B%20TN%2C%0A%20%20%20%20%7D%0A%20%20%20%20return%20FN%2C%20FP%2C%20TN%2C%20TP%2C%20chosen_threshold%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(FN%2C%20FP%2C%20TN%2C%20TP%2C%20np)%3A%0A%20%20%20%20confusion_matrix%20%3D%20np.array(%5B%0A%20%20%20%20%20%20%20%20%5BTP%2C%20FN%5D%2C%0A%20%20%20%20%20%20%20%20%5BFP%2C%20TN%5D%2C%0A%20%20%20%20%5D)%0A%0A%20%20%20%20confusion_matrix%0A%20%20%20%20return%20(confusion_matrix%2C)%0A%0A%0A%40app.cell%0Adef%20_(confusion_matrix)%3A%0A%20%20%20%20(confusion_matrix%20%2F%20confusion_matrix.sum()).round(2)%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(%0A%20%20%20%20%20%20%20%20r%22%22%22%0A%20%20%20%20%23%23%20Precision%20and%20Recall%0A%0A%20%20%20%20In%20this%20section%2C%20we'll%20see%20how%20we%20can%20extract%20some%20interesting%20and%20useful%20metrics%20from%20the%20confusion%20matrix.%0A%0A%20%20%20%20%23%23%23%20Accuracy%0A%0A%20%20%20%20Actually%2C%20we've%20already%20been%20working%20with%20accuracy%2C%20which%20we%20could%20express%20in%20terms%20of%20elements%20of%20the%20confusion%20matrix%20as%20follows%3A%0A%0A%20%20%20%20%5C%5B%0A%20%20%20%20%20%20%20%20Accuracy%20%3D%20%5Cfrac%7BTP%20%2B%20TN%7D%7BTP%20%2B%20TN%20%2B%20FP%20%2B%20FN%7D%0A%20%20%20%20%5C%5D%0A%20%20%20%20%22%22%22%0A%20%20%20%20)%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(FN%2C%20FP%2C%20TN%2C%20TP)%3A%0A%20%20%20%20(TP%20%2B%20TN)%20%2F%20(TP%20%2B%20TN%20%2B%20FP%20%2B%20FN)%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(%0A%20%20%20%20%20%20%20%20r%22%22%22%0A%20%20%20%20%23%23%23%20Precision%0A%0A%20%20%20%20Precision%20tells%20us%20how%20many%20positive%20predictions%20turn%20out%20to%20be%20correct.%0A%0A%20%20%20%20%5C%5B%0A%20%20%20%20%20%20%20%20Precision%20%3D%20%5Cfrac%7BTP%7D%7BTP%20%2B%20FP%7D%0A%20%20%20%20%5C%5D%0A%20%20%20%20%22%22%22%0A%20%20%20%20)%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(FP%2C%20TP)%3A%0A%20%20%20%20TP%20%2F%20(TP%20%2B%20FP)%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(%0A%20%20%20%20%20%20%20%20r%22%22%22%0A%20%20%20%20%23%23%23%20Recall%0A%0A%20%20%20%20Recall%20tells%20us%20the%20fraction%20of%20correctly%20identified%20positive%20examples.%0A%0A%20%20%20%20%5C%5B%0A%20%20%20%20%20%20%20%20Recall%20%3D%20%5Cfrac%7BTP%7D%7BTP%20%2B%20FN%7D%0A%20%20%20%20%5C%5D%0A%20%20%20%20%22%22%22%0A%20%20%20%20)%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(FN%2C%20TP)%3A%0A%20%20%20%20TP%20%2F%20(TP%20%2B%20FN)%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22Now%20we%20can%20see%20that%20our%20model%2C%20which%20had%20an%20accuracy%20of%2081%25%20has%20a%20precision%20of%2072%25%20and%20a%20recall%20of%2051%25.%20This%20is%20telling%20us%20a%20richer%20story%20than%20the%20accuracy%20by%20itself.%20If%20we%20only%20looked%20at%20the%20accuracy%20we%20may%20have%20thought%20that%20our%20model%20was%20good%20enough%20but%20now%20we%20can%20see%20why%20the%20model%20is%20actually%20not%20that%20good.%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(%0A%20%20%20%20%20%20%20%20r%22%22%22%0A%20%20%20%20%23%23%20ROC%20Curves%0A%0A%20%20%20%20We'll%20introduce%20here%20two%20new%20metrics%20that%20we%20can%20read%20from%20the%20confusion%20matrix.%0A%0A%20%20%20%20%23%23%23%20False%20Positive%20Rate%20(FPR)%0A%0A%20%20%20%20The%20False%20Positive%20Rate%20metric%20tells%20us%20how%20many%20of%20the%20negative%20cases%20we%20predicted%20as%20positive.%0A%0A%20%20%20%20%5C%5B%0A%20%20%20%20%20%20%20%20FPR%20%3D%20%5Cfrac%7BFP%7D%7BTN%20%2B%20FP%7D%0A%20%20%20%20%5C%5D%0A%20%20%20%20%22%22%22%0A%20%20%20%20)%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(FP%2C%20TN)%3A%0A%20%20%20%20FP%20%2F%20(TN%20%2B%20FP)%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(%0A%20%20%20%20%20%20%20%20r%22%22%22%0A%20%20%20%20%23%23%23%20True%20Positive%20Rate%20(TPR)%0A%0A%20%20%20%20The%20True%20Positive%20Rate%20metric%20tells%20us%20how%20many%20of%20the%20positive%20cases%20were%20predicted%20as%20positive.%0A%0A%20%20%20%20%5C%5B%0A%20%20%20%20%20%20%20%20TPR%20%3D%20%5Cfrac%7BTP%7D%7BTP%20%2B%20FN%7D%0A%20%20%20%20%5C%5D%0A%0A%20%20%20%20This%20is%20actually%20the%20same%20as%20recall.%0A%20%20%20%20%22%22%22%0A%20%20%20%20)%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(FN%2C%20TP)%3A%0A%20%20%20%20TP%20%2F%20(TP%20%2B%20FN)%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22We%20want%20to%20plot%20FPR%20and%20TPR%20for%20each%20possible%20threshold.%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(X_val%2C%20df_val%2C%20np%2C%20plt%2C%20predict)%3A%0A%20%20%20%20def%20get_confusion_matrix(threshold)%3A%0A%20%20%20%20%20%20%20%20TP%20%3D%20(df_val%5Bpredict(X_val%2C%20threshold).prediction%20%3D%3D%20True%5D.churn%20%3D%3D%20%22yes%22).sum()%0A%20%20%20%20%20%20%20%20FN%20%3D%20(df_val%5Bpredict(X_val%2C%20threshold).prediction%20%3D%3D%20False%5D.churn%20%3D%3D%20%22yes%22).sum()%0A%20%20%20%20%20%20%20%20FP%20%3D%20(df_val%5Bpredict(X_val%2C%20threshold).prediction%20%3D%3D%20True%5D.churn%20!%3D%20%22yes%22).sum()%0A%20%20%20%20%20%20%20%20TN%20%3D%20(df_val%5Bpredict(X_val%2C%20threshold).prediction%20%3D%3D%20False%5D.churn%20!%3D%20%22yes%22).sum()%0A%0A%20%20%20%20%20%20%20%20return%20np.array(%5B%5BTP%2C%20FN%5D%2C%20%5BFP%2C%20TN%5D%5D)%0A%0A%20%20%20%20def%20track_tprs_and_fprs()%3A%0A%20%20%20%20%20%20%20%20thresholds%20%3D%20np.linspace(0%2C%201%2C%2050)%0A%20%20%20%20%20%20%20%20tprs%20%3D%20%5B%5D%0A%20%20%20%20%20%20%20%20fprs%20%3D%20%5B%5D%0A%0A%20%20%20%20%20%20%20%20for%20threshold%20in%20thresholds%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20confusion_matrix%20%3D%20get_confusion_matrix(threshold)%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20TP%20%3D%20confusion_matrix%5B0%5D%5B0%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20FN%20%3D%20confusion_matrix%5B0%5D%5B1%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20FP%20%3D%20confusion_matrix%5B1%5D%5B0%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20TN%20%3D%20confusion_matrix%5B1%5D%5B1%5D%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20tprs.append(TP%20%2F%20(TP%20%2B%20FN))%0A%20%20%20%20%20%20%20%20%20%20%20%20fprs.append(FP%20%2F%20(TN%20%2B%20FP))%0A%0A%20%20%20%20%20%20%20%20return%20thresholds%2C%20tprs%2C%20fprs%0A%0A%20%20%20%20def%20plot_tprs_and_fprs(axis)%3A%0A%20%20%20%20%20%20%20%20thresholds%2C%20tprs%2C%20fprs%20%3D%20track_tprs_and_fprs()%0A%0A%20%20%20%20%20%20%20%20axis.plot(thresholds%2C%20tprs%2C%20label%3D%22Our%20model%20TPR%22%2C%20color%3D'b')%0A%20%20%20%20%20%20%20%20axis.plot(thresholds%2C%20fprs%2C%20label%3D%22Our%20model%20FPR%22%2C%20color%3D'b'%2C%20linestyle%3D'dashed')%0A%20%20%20%20%20%20%20%20axis.legend()%0A%20%20%20%20%20%20%20%20axis.set_xlabel('Threshold')%0A%0A%20%20%20%20def%20plot_tprs_vs_fprs(axis)%3A%0A%20%20%20%20%20%20%20%20thresholds%2C%20tprs%2C%20fprs%20%3D%20track_tprs_and_fprs()%0A%0A%20%20%20%20%20%20%20%20axis.plot(fprs%2C%20tprs%2C%20label%3D%22Our%20model's%20TPR%20vs.%20FPR%22%2C%20color%3D'b')%0A%20%20%20%20%20%20%20%20axis.legend()%0A%20%20%20%20%20%20%20%20axis.set_xlabel('FPR')%0A%20%20%20%20%20%20%20%20axis.set_ylabel('TPR')%0A%0A%20%20%20%20def%20plot_model()%3A%0A%20%20%20%20%20%20%20%20fig%2C%20ax%20%3D%20plt.subplots(1%2C%202%2C%20figsize%3D(16%2C%204))%0A%0A%20%20%20%20%20%20%20%20plot_tprs_and_fprs(ax%5B0%5D)%0A%20%20%20%20%20%20%20%20plot_tprs_vs_fprs(ax%5B1%5D)%0A%0A%20%20%20%20plot_model()%0A%20%20%20%20plt.show()%0A%20%20%20%20return%20plot_tprs_and_fprs%2C%20plot_tprs_vs_fprs%2C%20track_tprs_and_fprs%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(%0A%20%20%20%20%20%20%20%20r%22%22%22%0A%20%20%20%20%23%23%23%20Random%20Model%0A%0A%20%20%20%20The%20issue%20with%20the%20TPR%20vs%20FPR%20plot%20is%20that%20we%20have%20nothing%20to%20compare%20it%20with.%20To%20fix%20that%2C%20we'll%20now%20create%20a%20model%20that%20decides%20randomly%20wether%20a%20client%20will%20churn%2C%20or%20not.%0A%20%20%20%20%22%22%22%0A%20%20%20%20)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(X_val%2C%20np%2C%20pd)%3A%0A%20%20%20%20def%20get_random_predictions(X%3A%20pd.DataFrame%2C%20threshold%20%3D%200.5)%20-%3E%20np.array%3A%0A%20%20%20%20%20%20%20%20return%20np.random.uniform(0%2C%201%2C%20size%3Dlen(X))%20%3E%20threshold%0A%0A%20%20%20%20get_random_predictions(X_val)%0A%20%20%20%20return%20(get_random_predictions%2C)%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22Let's%20quickly%20check%20the%20accuracy%20of%20this%20model%3A%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(X_val%2C%20get_random_predictions%2C%20y_val)%3A%0A%20%20%20%20(get_random_predictions(X_val)%20%3D%3D%20y_val).mean()%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22Finally%2C%20let's%20plot%20its%20corresponding%20TPR%20and%20FPR%20curves%3A%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(X_val%2C%20df_val%2C%20get_random_predictions%2C%20np%2C%20plt)%3A%0A%20%20%20%20def%20get_random_confusion_matrix(threshold)%3A%0A%20%20%20%20%20%20%20%20random_predictions%20%3D%20get_random_predictions(X_val%2C%20threshold)%0A%0A%20%20%20%20%20%20%20%20TP%20%3D%20(df_val%5Brandom_predictions%20%3D%3D%20True%5D.churn%20%3D%3D%20%22yes%22).sum()%0A%20%20%20%20%20%20%20%20FN%20%3D%20(df_val%5Brandom_predictions%20%3D%3D%20False%5D.churn%20%3D%3D%20%22yes%22).sum()%0A%20%20%20%20%20%20%20%20FP%20%3D%20(df_val%5Brandom_predictions%20%3D%3D%20True%5D.churn%20!%3D%20%22yes%22).sum()%0A%20%20%20%20%20%20%20%20TN%20%3D%20(df_val%5Brandom_predictions%20%3D%3D%20False%5D.churn%20!%3D%20%22yes%22).sum()%0A%0A%20%20%20%20%20%20%20%20return%20np.array(%5B%5BTP%2C%20FN%5D%2C%20%5BFP%2C%20TN%5D%5D)%0A%0A%20%20%20%20def%20track_random_tprs_and_fprs()%3A%0A%20%20%20%20%20%20%20%20thresholds%20%3D%20np.linspace(0%2C%201%2C%2050)%0A%20%20%20%20%20%20%20%20tprs%20%3D%20%5B%5D%0A%20%20%20%20%20%20%20%20fprs%20%3D%20%5B%5D%0A%0A%20%20%20%20%20%20%20%20for%20threshold%20in%20thresholds%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20confusion_matrix%20%3D%20get_random_confusion_matrix(threshold)%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20TP%20%3D%20confusion_matrix%5B0%5D%5B0%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20FN%20%3D%20confusion_matrix%5B0%5D%5B1%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20FP%20%3D%20confusion_matrix%5B1%5D%5B0%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20TN%20%3D%20confusion_matrix%5B1%5D%5B1%5D%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20tprs.append(TP%20%2F%20(TP%20%2B%20FN))%0A%20%20%20%20%20%20%20%20%20%20%20%20fprs.append(FP%20%2F%20(TN%20%2B%20FP))%0A%0A%20%20%20%20%20%20%20%20return%20thresholds%2C%20tprs%2C%20fprs%0A%0A%20%20%20%20def%20plot_random_tprs_and_fprs(axis)%3A%0A%20%20%20%20%20%20%20%20thresholds%2C%20tprs%2C%20fprs%20%3D%20track_random_tprs_and_fprs()%0A%0A%20%20%20%20%20%20%20%20axis.plot(thresholds%2C%20tprs%2C%20label%3D%22Random%20TPR%22%2C%20color%3D'r')%0A%20%20%20%20%20%20%20%20axis.plot(thresholds%2C%20fprs%2C%20label%3D%22Random%20FPR%22%2C%20color%3D'r'%2C%20linestyle%3D'dashed')%0A%20%20%20%20%20%20%20%20axis.legend()%0A%20%20%20%20%20%20%20%20axis.set_xlabel('Threshold')%0A%0A%20%20%20%20def%20plot_random_tprs_vs_fprs(axis)%3A%0A%20%20%20%20%20%20%20%20thresholds%2C%20tprs%2C%20fprs%20%3D%20track_random_tprs_and_fprs()%0A%0A%20%20%20%20%20%20%20%20axis.plot(fprs%2C%20tprs%2C%20label%3D%22Random%20TPR%20vs.%20FPR%22%2C%20color%3D'r')%0A%20%20%20%20%20%20%20%20axis.legend()%0A%20%20%20%20%20%20%20%20axis.set_xlabel('FPR')%0A%20%20%20%20%20%20%20%20axis.set_ylabel('TPR')%0A%0A%20%20%20%20def%20plot_random_model()%3A%0A%20%20%20%20%20%20%20%20fig%2C%20ax%20%3D%20plt.subplots(1%2C%202%2C%20figsize%3D(16%2C%204))%0A%0A%20%20%20%20%20%20%20%20plot_random_tprs_and_fprs(ax%5B0%5D)%0A%20%20%20%20%20%20%20%20plot_random_tprs_vs_fprs(ax%5B1%5D)%0A%0A%20%20%20%20plot_random_model()%0A%20%20%20%20plt.show()%0A%20%20%20%20return%20plot_random_tprs_and_fprs%2C%20plot_random_tprs_vs_fprs%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(%0A%20%20%20%20%20%20%20%20r%22%22%22%0A%20%20%20%20%23%23%23%20Ideal%20Model%0A%0A%20%20%20%20Now%20we%20have%20an%20idea%20about%20how%20the%20TPR%20vs%20FPR%20plot%20would%20look%20for%20the%20worse%20possible%20model.%20But%20we%20haven't%20seen%20how%20it%20should%20look%20like%20for%20the%20best%20model.%20Let's%20set%20it%20up.%0A%20%20%20%20%22%22%22%0A%20%20%20%20)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(X_val%2C%20np%2C%20pd%2C%20y_val)%3A%0A%20%20%20%20positive_val_count%20%3D%20(y_val%20%3D%3D%20True).sum()%0A%20%20%20%20negative_val_count%20%3D%20(y_val%20%3D%3D%20False).sum()%0A%0A%20%20%20%20y_ideal%20%3D%20np.repeat(%5BFalse%2C%20True%5D%2C%20%5Bnegative_val_count%2C%20positive_val_count%5D)%0A%0A%20%20%20%20def%20get_ideal_predictions(X%3A%20pd.DataFrame%2C%20threshold%20%3D%200.5)%20-%3E%20np.array%3A%0A%20%20%20%20%20%20%20%20predictions%20%3D%20np.linspace(0%2C%201%2C%20len(X_val))%0A%0A%20%20%20%20%20%20%20%20return%20predictions%20%3E%3D%20threshold%0A%0A%20%20%20%20get_ideal_predictions(X_val)%0A%20%20%20%20return%20(%0A%20%20%20%20%20%20%20%20get_ideal_predictions%2C%0A%20%20%20%20%20%20%20%20negative_val_count%2C%0A%20%20%20%20%20%20%20%20positive_val_count%2C%0A%20%20%20%20%20%20%20%20y_ideal%2C%0A%20%20%20%20)%0A%0A%0A%40app.cell%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22Evaluated%20at%20the%20ideal%20threshold%2C%20the%20accuracy%20of%20this%20model%20should%20be%20100%25.%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(%0A%20%20%20%20X_val%2C%0A%20%20%20%20get_ideal_predictions%2C%0A%20%20%20%20negative_val_count%2C%0A%20%20%20%20positive_val_count%2C%0A%20%20%20%20y_ideal%2C%0A)%3A%0A%20%20%20%20ideal_threshold%20%3D%20negative_val_count%20%2F%20(negative_val_count%20%2B%20positive_val_count)%0A%0A%20%20%20%20(get_ideal_predictions(X_val%2C%20ideal_threshold)%20%3D%3D%20y_ideal).mean()%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22Let's%20now%20plot%20its%20corresponding%20TPR%20and%20FPR%20curves%3A%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(X_val%2C%20get_ideal_predictions%2C%20np%2C%20plt%2C%20y_ideal)%3A%0A%20%20%20%20def%20get_ideal_confusion_matrix(threshold)%3A%0A%20%20%20%20%20%20%20%20ideal_predictions%20%3D%20get_ideal_predictions(X_val%2C%20threshold)%0A%0A%20%20%20%20%20%20%20%20TP%20%3D%20(y_ideal%5Bideal_predictions%20%3D%3D%20True%5D%20%3D%3D%20True).sum()%0A%20%20%20%20%20%20%20%20FN%20%3D%20(y_ideal%5Bideal_predictions%20%3D%3D%20False%5D%20%3D%3D%20True).sum()%0A%20%20%20%20%20%20%20%20FP%20%3D%20(y_ideal%5Bideal_predictions%20%3D%3D%20True%5D%20%3D%3D%20False).sum()%0A%20%20%20%20%20%20%20%20TN%20%3D%20(y_ideal%5Bideal_predictions%20%3D%3D%20False%5D%20%3D%3D%20False).sum()%0A%0A%20%20%20%20%20%20%20%20return%20np.array(%5B%5BTP%2C%20FN%5D%2C%20%5BFP%2C%20TN%5D%5D)%0A%0A%20%20%20%20def%20track_ideal_tprs_and_fprs()%3A%0A%20%20%20%20%20%20%20%20thresholds%20%3D%20np.linspace(0%2C%201%2C%2050)%0A%20%20%20%20%20%20%20%20tprs%20%3D%20%5B%5D%0A%20%20%20%20%20%20%20%20fprs%20%3D%20%5B%5D%0A%0A%20%20%20%20%20%20%20%20for%20threshold%20in%20thresholds%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20confusion_matrix%20%3D%20get_ideal_confusion_matrix(threshold)%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20TP%20%3D%20confusion_matrix%5B0%5D%5B0%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20FN%20%3D%20confusion_matrix%5B0%5D%5B1%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20FP%20%3D%20confusion_matrix%5B1%5D%5B0%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20TN%20%3D%20confusion_matrix%5B1%5D%5B1%5D%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20tprs.append(TP%20%2F%20(TP%20%2B%20FN))%0A%20%20%20%20%20%20%20%20%20%20%20%20fprs.append(FP%20%2F%20(TN%20%2B%20FP))%0A%0A%20%20%20%20%20%20%20%20return%20thresholds%2C%20tprs%2C%20fprs%0A%0A%20%20%20%20def%20plot_ideal_tprs_and_fprs(axis)%3A%0A%20%20%20%20%20%20%20%20thresholds%2C%20tprs%2C%20fprs%20%3D%20track_ideal_tprs_and_fprs()%0A%0A%20%20%20%20%20%20%20%20axis.plot(thresholds%2C%20tprs%2C%20label%3D%22Ideal%20TPR%22%2C%20color%3D'g')%0A%20%20%20%20%20%20%20%20axis.plot(thresholds%2C%20fprs%2C%20label%3D%22Ideal%20FPR%22%2C%20color%3D'g'%2C%20linestyle%3D'dashed')%0A%20%20%20%20%20%20%20%20axis.legend()%0A%20%20%20%20%20%20%20%20axis.set_xlabel('Threshold')%0A%0A%20%20%20%20def%20plot_ideal_tprs_vs_fprs(axis)%3A%0A%20%20%20%20%20%20%20%20thresholds%2C%20tprs%2C%20fprs%20%3D%20track_ideal_tprs_and_fprs()%0A%0A%20%20%20%20%20%20%20%20axis.plot(fprs%2C%20tprs%2C%20label%3D%22Ideal%20TPR%20vs.%20FPR%22%2C%20color%3D'g')%0A%20%20%20%20%20%20%20%20axis.legend()%0A%20%20%20%20%20%20%20%20axis.set_xlabel('FPR')%0A%20%20%20%20%20%20%20%20axis.set_ylabel('TPR')%0A%0A%20%20%20%20def%20plot_ideal_model()%3A%0A%20%20%20%20%20%20%20%20fig%2C%20ax%20%3D%20plt.subplots(1%2C%202%2C%20figsize%3D(16%2C%204))%0A%0A%20%20%20%20%20%20%20%20plot_ideal_tprs_and_fprs(ax%5B0%5D)%0A%20%20%20%20%20%20%20%20plot_ideal_tprs_vs_fprs(ax%5B1%5D)%0A%0A%20%20%20%20plot_ideal_model()%0A%20%20%20%20plt.show()%0A%20%20%20%20return%20(%0A%20%20%20%20%20%20%20%20plot_ideal_tprs_and_fprs%2C%0A%20%20%20%20%20%20%20%20plot_ideal_tprs_vs_fprs%2C%0A%20%20%20%20%20%20%20%20track_ideal_tprs_and_fprs%2C%0A%20%20%20%20)%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(%0A%20%20%20%20%20%20%20%20r%22%22%22%0A%20%20%20%20%23%23%23%20Put%20it%20All%20Together%0A%0A%20%20%20%20Now%2C%20let's%20merge%20these%20three%20plots.%0A%20%20%20%20%22%22%22%0A%20%20%20%20)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(%0A%20%20%20%20plot_ideal_tprs_and_fprs%2C%0A%20%20%20%20plot_ideal_tprs_vs_fprs%2C%0A%20%20%20%20plot_random_tprs_and_fprs%2C%0A%20%20%20%20plot_random_tprs_vs_fprs%2C%0A%20%20%20%20plot_tprs_and_fprs%2C%0A%20%20%20%20plot_tprs_vs_fprs%2C%0A%20%20%20%20plt%2C%0A)%3A%0A%20%20%20%20def%20plot_all_models()%3A%0A%20%20%20%20%20%20%20%20fig%2C%20ax%20%3D%20plt.subplots(1%2C%202%2C%20figsize%3D(16%2C%204))%0A%0A%20%20%20%20%20%20%20%20plot_tprs_and_fprs(ax%5B0%5D)%0A%20%20%20%20%20%20%20%20plot_random_tprs_and_fprs(ax%5B0%5D)%0A%20%20%20%20%20%20%20%20plot_ideal_tprs_and_fprs(ax%5B0%5D)%0A%0A%20%20%20%20%20%20%20%20plot_tprs_vs_fprs(ax%5B1%5D)%0A%20%20%20%20%20%20%20%20plot_random_tprs_vs_fprs(ax%5B1%5D)%0A%20%20%20%20%20%20%20%20plot_ideal_tprs_vs_fprs(ax%5B1%5D)%0A%0A%20%20%20%20plot_all_models()%0A%20%20%20%20plt.show()%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%23%23%23%20Plot%20ROC%20Curves%20with%20Scikit%20Learn%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(X_val%2C%20chosen_threshold%2C%20plt%2C%20predict%2C%20y_val)%3A%0A%20%20%20%20from%20sklearn.metrics%20import%20roc_curve%0A%0A%20%20%20%20def%20track_scikit_tprs_and_fprs()%3A%0A%20%20%20%20%20%20%20%20fprs%2C%20tprs%2C%20thresholds%20%3D%20roc_curve(y_val%2C%20predict(X_val%2C%20chosen_threshold).probability)%0A%0A%20%20%20%20%20%20%20%20return%20thresholds%2C%20tprs%2C%20fprs%0A%0A%20%20%20%20def%20plot_scikit_tprs_and_fprs(axis)%3A%0A%20%20%20%20%20%20%20%20thresholds%2C%20tprs%2C%20fprs%20%3D%20track_scikit_tprs_and_fprs()%0A%0A%20%20%20%20%20%20%20%20axis.plot(thresholds%2C%20tprs%2C%20label%3D%22Our%20model%20TPR%22%2C%20color%3D'b')%0A%20%20%20%20%20%20%20%20axis.plot(thresholds%2C%20fprs%2C%20label%3D%22Our%20model%20FPR%22%2C%20color%3D'b'%2C%20linestyle%3D'dashed')%0A%20%20%20%20%20%20%20%20axis.legend()%0A%20%20%20%20%20%20%20%20axis.set_xlabel('Threshold')%0A%0A%20%20%20%20def%20plot_scikit_tprs_vs_fprs(axis)%3A%0A%20%20%20%20%20%20%20%20thresholds%2C%20tprs%2C%20fprs%20%3D%20track_scikit_tprs_and_fprs()%0A%0A%20%20%20%20%20%20%20%20axis.plot(fprs%2C%20tprs%2C%20label%3D%22Our%20model%20TPR%20vs.%20FPR%22%2C%20color%3D'b')%0A%20%20%20%20%20%20%20%20axis.legend()%0A%20%20%20%20%20%20%20%20axis.set_xlabel('FPR')%0A%20%20%20%20%20%20%20%20axis.set_ylabel('TPR')%0A%0A%20%20%20%20def%20plot_scikit_model()%3A%0A%20%20%20%20%20%20%20%20fig%2C%20ax%20%3D%20plt.subplots(1%2C%202%2C%20figsize%3D(16%2C%204))%0A%0A%20%20%20%20%20%20%20%20plot_scikit_tprs_and_fprs(ax%5B0%5D)%0A%20%20%20%20%20%20%20%20plot_scikit_tprs_vs_fprs(ax%5B1%5D)%0A%0A%20%20%20%20plot_scikit_model()%0A%20%20%20%20plt.show()%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(%0A%20%20%20%20%20%20%20%20r%22%22%22%0A%20%20%20%20%23%23%20ROC%20AUC%0A%0A%20%20%20%20The%20ROC%20AUC%20refers%20to%20the%20%22area%20under%20the%20curve%22%20and%20when%20applied%20to%20the%20%22TPR%20vs.%20FPR%22%20curve%20is%20a%20good%20metric%20for%20our%20classification%20model.%20To%20get%20an%20intuition%20of%20what%20it%20measures%2C%20we%20can%20quickly%20check%20that%3A%0A%0A%20%20%20%20*%20if%20we%20applied%20it%20to%20the%20**ideal**%20model%2C%20we'd%20get%20an%20area%20of%20around%201.0%2C%20its%20maximum%20value%0A%20%20%20%20*%20if%20we%20applied%20it%20to%20the%20**random**%20model%2C%20we'd%20get%20an%20area%20of%20around%200.5%2C%20its%20minimum%20value%0A%20%20%20%20%22%22%22%0A%20%20%20%20)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(plot_ideal_tprs_vs_fprs%2C%20plot_random_tprs_vs_fprs%2C%20plt)%3A%0A%20%20%20%20fig%2C%20ax%20%3D%20plt.subplots(1%2C%201%2C%20figsize%3D(6%2C%206))%0A%0A%20%20%20%20plot_random_tprs_vs_fprs(ax)%0A%20%20%20%20plot_ideal_tprs_vs_fprs(ax)%0A%0A%20%20%20%20plt.show()%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22We'll%20use%20Scikit's%20learn%20specific%20function%20to%20obtain%20it.%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(track_ideal_tprs_and_fprs%2C%20track_tprs_and_fprs)%3A%0A%20%20%20%20from%20sklearn.metrics%20import%20auc%0A%0A%20%20%20%20def%20get_all_auc()%3A%0A%20%20%20%20%20%20%20%20_%2C%20tprs%2C%20fprs%20%3D%20track_tprs_and_fprs()%0A%20%20%20%20%20%20%20%20_%2C%20ideal_tprs%2C%20ideal_fprs%20%3D%20track_ideal_tprs_and_fprs()%0A%0A%20%20%20%20%20%20%20%20return%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%22our_model_auc%22%3A%20auc(fprs%2C%20tprs)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%22ideal_auc%22%3A%20auc(ideal_fprs%2C%20ideal_tprs)%2C%0A%20%20%20%20%20%20%20%20%7D%0A%0A%20%20%20%20get_all_auc()%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(%0A%20%20%20%20%20%20%20%20r%22%22%22%0A%20%20%20%20%23%23%23%20Interpreting%20ROC%20AUC%0A%0A%20%20%20%20The%20AUC%20metric%20tells%20us%20how%20good%20our%20model%20is%20at%20sorting%20customers%20according%20to%20how%20likely%20they%20are%20to%20churn.%20A%20way%20to%20picture%20it%20is%20to%20imagine%20that%20it%20takes%20pairs%20of%20customers%20where%20one%20of%20them%20churned%20and%20the%20other%20way%20didn't%2C%20and%20checks%20if%20our%20model%20actually%20predicted%20a%20greater%20posibility%20of%20churn%20for%20the%20positive%20one.%0A%20%20%20%20%22%22%22%0A%20%20%20%20)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(X_val%2C%20chosen_threshold%2C%20predict%2C%20y_val)%3A%0A%20%20%20%20from%20random%20import%20randint%0A%0A%20%20%20%20def%20manually_estimate_auc()%3A%0A%20%20%20%20%20%20%20%20y_pred%20%3D%20predict(X_val%2C%20chosen_threshold)%0A%0A%20%20%20%20%20%20%20%20negatives%20%3D%20y_pred%5By_val%20%3D%3D%20False%5D.probability.to_list()%0A%20%20%20%20%20%20%20%20positives%20%3D%20y_pred%5By_val%20%3D%3D%20True%5D.probability.to_list()%0A%0A%20%20%20%20%20%20%20%20n%20%3D%20100000%0A%20%20%20%20%20%20%20%20success%20%3D%200%0A%0A%20%20%20%20%20%20%20%20for%20i%20in%20range(n)%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20positive_index%20%3D%20randint(0%2C%20len(positives)%20-%201)%0A%20%20%20%20%20%20%20%20%20%20%20%20negative_index%20%3D%20randint(0%2C%20len(negatives)%20-%201)%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20positives%5Bpositive_index%5D%20%3E%20negatives%5Bnegative_index%5D%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20success%20%2B%3D%201%0A%0A%20%20%20%20%20%20%20%20return%20success%20%2F%20n%0A%0A%20%20%20%20manually_estimate_auc()%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(%0A%20%20%20%20%20%20%20%20r%22%22%22%0A%20%20%20%20%23%23%20K-Fold%20Cross%20Validation%0A%0A%20%20%20%20The%20K-Fold%20cross%20validation%20process%20consists%20in%20holding%20a%20small%20part%20of%20our%20dataset%20(the%20**test**%20split)%20and%20taking%20the%20remaining%20part.%20We%20call%20that%20part%20the%20**full**%20split%2C%20which%20results%20from%20combining%20our%20**train**%20and%20**val**%20splits.%0A%0A%20%20%20%20We'll%20split%20it%20into%20a%20certain%20**k**%20number%20of%20folds.%20Then%20we%20use%20each%20fold%20for%20a%20round%20of%20training%20where%20the%20fold%20is%20used%20as%20an%20evaluation%20split%20and%20the%20rest%20of%20the%20dataset%20is%20used%20for%20training.%20That%20way.%20we%20can%20compute%203%20independent%20AUC%20values%20for%20the%20same%20model.%0A%20%20%20%20%22%22%22%0A%20%20%20%20)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(%0A%20%20%20%20LogisticRegression%2C%0A%20%20%20%20categorical_columns%2C%0A%20%20%20%20df_full%2C%0A%20%20%20%20get_features_and_target%2C%0A%20%20%20%20get_full_trained_vectorizer%2C%0A%20%20%20%20np%2C%0A%20%20%20%20numerical_columns%2C%0A%20%20%20%20pd%2C%0A)%3A%0A%20%20%20%20from%20sklearn.model_selection%20import%20KFold%0A%20%20%20%20from%20sklearn.metrics%20import%20roc_auc_score%0A%0A%20%20%20%20def%20train_folds(df%3A%20pd.DataFrame%2C%20C%3A%20float%20%3D%201.0)%3A%0A%20%20%20%20%20%20%20%20kfolds%20%3D%20KFold(n_splits%3D5%2C%20shuffle%3DTrue)%0A%0A%20%20%20%20%20%20%20%20auc_scores%20%3D%20%5B%5D%0A%20%20%20%20%20%20%20%20for%20train_idx%2C%20val_idx%20in%20kfolds.split(df_full)%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20df_train%20%3D%20df_full.iloc%5Btrain_idx%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20df_val%20%3D%20df_full.iloc%5Bval_idx%5D%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20dict_vectorizer%2C%20dictionary%20%3D%20get_full_trained_vectorizer(df_train)%0A%20%20%20%20%20%20%20%20%20%20%20%20X_train%2C%20y_train%20%3D%20get_features_and_target(df_train%2C%20dict_vectorizer%2C%20dictionary)%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20model%20%3D%20LogisticRegression(C%3DC%2C%20max_iter%3D5000)%0A%20%20%20%20%20%20%20%20%20%20%20%20model.fit(X_train%2C%20y_train)%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20dictionary_val%20%3D%20df_val%5Bnumerical_columns%20%2B%20categorical_columns%5D.to_dict(orient%3D%22records%22)%0A%20%20%20%20%20%20%20%20%20%20%20%20X_val%2C%20y_val%20%3D%20get_features_and_target(df_val%2C%20dict_vectorizer%2C%20dictionary_val)%0A%20%20%20%20%20%20%20%20%20%20%20%20y_pred%20%3D%20model.predict_proba(X_val)%5B%3A%2C1%5D%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20auc_scores.append(roc_auc_score(y_val%2C%20y_pred))%0A%0A%20%20%20%20%20%20%20%20print(%22C%3D%7B%3A.3f%7D%3A%20%7B%3A.3f%7D%20%2B-%20%7B%3A.3f%7D%22.format(C%2C%20np.mean(auc_scores)%2C%20np.std(auc_scores)))%0A%0A%20%20%20%20%20%20%20%20return%20auc_scores%0A%0A%20%20%20%20train_folds(df_full%2C%200.1)%0A%20%20%20%20train_folds(df_full%2C%201.0)%0A%20%20%20%20return%0A%0A%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20app.run()%0A
e3f6ab65cd86b6529eace9f3746c6bb929e477ab993690df1cfd6b3ebda4e88f