{ "cells": [ { "cell_type": "markdown", "id": "5619ac0c-7398-4eb5-bdc0-8d338bf4a41f", "metadata": {}, "source": [ "### Data Preprocessing" ] }, { "cell_type": "markdown", "id": "8774cfd1-91b0-4d2d-b0f1-f057a5940cea", "metadata": { "jp-MarkdownHeadingCollapsed": true }, "source": [ "### Importing Libraries" ] }, { "cell_type": "code", "execution_count": 1, "id": "b0b6e81d-c547-41ae-8a2b-4f8864cbc8d4", "metadata": {}, "outputs": [], "source": [ "import warnings\n", "warnings.filterwarnings(\"ignore\")" ] }, { "cell_type": "code", "execution_count": 2, "id": "27dd12eb-2975-4b6f-9010-845ae2d23c8f", "metadata": {}, "outputs": [], "source": [ "import pandas as pd\n", "from sklearn.model_selection import train_test_split\n", "import os" ] }, { "cell_type": "markdown", "id": "c49948e7-3018-4cf8-b3bc-0bae7e6a051f", "metadata": {}, "source": [ "### Data Preprocessing Function" ] }, { "cell_type": "code", "execution_count": 3, "id": "02ee5dbe-dc71-4839-a19b-34699133f2be", "metadata": {}, "outputs": [], "source": [ "def load_and_clean_data(file_path):\n", " \"\"\"\n", " Load and clean the data from the specified CSV file.\n", "\n", " Parameters:\n", " - file_path (str): Path to the CSV file containing the data.\n", "\n", " Returns:\n", " - DataFrame: Cleaned DataFrame containing selected columns with NaN values dropped.\n", " \"\"\"\n", " df = pd.read_csv(file_path)\n", " df['Date received'] = pd.to_datetime(df['Date received'])\n", " \n", " cols_to_consider = ['Product', 'Sub-product', 'Issue', 'Sub-issue', 'Consumer complaint narrative',\n", " 'Company public response', 'Company', 'State', 'ZIP code', 'Date received']\n", " \n", " df_new = df[cols_to_consider]\n", " \n", " df_new = df_new.dropna()\n", " \n", " return df_new" ] }, { "cell_type": "code", "execution_count": 4, "id": "413135b7-1fb8-4cef-876e-99bfd1f148ac", "metadata": {}, "outputs": [], "source": [ "def filter_by_years(df, years):\n", " \"\"\"\n", " Filter the DataFrame to include only the rows corresponding to specified years.\n", "\n", " Parameters:\n", " - df (DataFrame): The DataFrame containing data to filter.\n", " - years (list of int): List of years to filter by.\n", "\n", " Returns:\n", " - DataFrame: Filtered DataFrame containing rows corresponding to specified years.\n", " \"\"\"\n", " filtered_df = df[df['Date received'].dt.year.isin(years)].reset_index(drop=True)\n", " return filtered_df" ] }, { "cell_type": "code", "execution_count": 5, "id": "9e3f199f-0ad9-40a3-82f1-065af9efa9f5", "metadata": {}, "outputs": [], "source": [ "def map_product_column(df):\n", " \"\"\"\n", " Map values in the 'Product' column of the DataFrame to a standardized set of categories.\n", "\n", " Parameters:\n", " - df (DataFrame): The DataFrame containing the 'Product' column to map.\n", "\n", " Returns:\n", " - DataFrame: DataFrame with the 'Product' column values mapped to standardized categories.\n", " \"\"\"\n", "\n", " product_map = {'Credit reporting or other personal consumer reports': 'Credit Reporting',\n", " 'Credit reporting, credit repair services, or other personal consumer reports': 'Credit Reporting',\n", " 'Payday loan, title loan, personal loan, or advance loan': 'Loans / Mortgage',\n", " 'Payday loan, title loan, or personal loan': 'Loans / Mortgage',\n", " 'Student loan': 'Loans / Mortgage',\n", " 'Vehicle loan or lease': 'Loans / Mortgage',\n", " 'Debt collection': 'Debt collection',\n", " 'Credit card or prepaid card': 'Credit/Prepaid Card',\n", " 'Credit card': 'Credit/Prepaid Card',\n", " 'Prepaid card': 'Credit/Prepaid Card',\n", " 'Mortgage': 'Loans / Mortgage',\n", " 'Checking or savings account': 'Checking or savings account' \n", " }\n", " # Map 'Product' column\n", " df.loc[:,'Product'] = df['Product'].map(product_map)\n", " \n", " return df\n" ] }, { "cell_type": "code", "execution_count": 6, "id": "03a2e0a2-75ff-4b33-b081-58e3d5c791c9", "metadata": {}, "outputs": [], "source": [ "def clean_narrative(df):\n", "\n", " \"\"\"\n", " Clean the consumer complaint narratives in the DataFrame by excluding short and irrelevant complaints.\n", "\n", " Parameters:\n", " - df (DataFrame): The input DataFrame containing consumer complaint data.\n", "\n", " Returns:\n", " - DataFrame: DataFrame with cleaned consumer complaint narratives.\n", " \"\"\"\n", "# Compute complaint length\n", " df['complaint length'] = df['Consumer complaint narrative'].apply(lambda x : len(x))\n", "\n", " df = df[df['complaint length'] > 20]\n", " \n", " complaints_to_exclude = ['See document attached', 'See the attached documents.', 'Incorrect information on my credit report', 'incorrect information on my credit report',\n", " 'please see attached file','Please see documents Attached','Incorrect information on my credit report.', 'Please see attached file', 'see attached',\n", " 'See attached', 'SEE ATTACHED DOCUMENTS', 'See Attached', 'SEE ATTACHMENT', 'SEE ATTACHMENTS', \n", " 'XXXX XXXX XXXX XXXX XXXX XXXX XXXX XXXX XXXX XXXX']\n", " \n", " df = df[~df['Consumer complaint narrative'].isin(complaints_to_exclude)]\n", " return df" ] }, { "cell_type": "code", "execution_count": 7, "id": "0cb28135-12d8-41fc-94a8-2968e558473b", "metadata": {}, "outputs": [], "source": [ "def filter_by_frequency(df):\n", " \"\"\"\n", " Filter the DataFrame based on the frequency of sub-issues and sub-products.\n", "\n", " Parameters:\n", " - df (DataFrame): The input DataFrame containing consumer complaint data.\n", "\n", " Returns:\n", " - DataFrame: DataFrame filtered based on the frequency of sub-issues and sub-products.\n", " \"\"\"\n", " # Select sub-issues with frequency > 500\n", " sub_issues_to_consider = df['Sub-issue'].value_counts()[df['Sub-issue'].value_counts() > 500].index\n", "\n", " # Filter DataFrame based on selected sub-issues\n", " reduced_subissues = df[df['Sub-issue'].isin(sub_issues_to_consider)]\n", " # Select sub-products with frequency > 100\n", " sub_products_to_consider = reduced_subissues['Sub-product'].value_counts()[reduced_subissues['Sub-product'].value_counts() > 100].index\n", "\n", " # Filter DataFrame based on selected sub-products\n", " final_df = reduced_subissues[reduced_subissues['Sub-product'].isin(sub_products_to_consider)]\n", "\n", " return final_df" ] }, { "cell_type": "code", "execution_count": 8, "id": "e102c902-645e-453e-9a67-07781ba6fc55", "metadata": {}, "outputs": [], "source": [ "def map_issue(df):\n", " \"\"\"\n", " Map the issues to more defined and lesser number of issues in the DataFrame.\n", "\n", " Parameters:\n", " - df (DataFrame): The input DataFrame containing consumer complaint data.\n", "\n", " Returns:\n", " - DataFrame: DataFrame with the 'Issue' column mapped to appropriate issues.\n", " \"\"\"\n", " # Create a dictionary mapping issues to sub-issues\n", " issues_to_subissues = {}\n", " for issue in df['Issue'].value_counts().index:\n", " issues_to_subissues[issue] = list(df[df['Issue'] == issue]['Sub-issue'].value_counts().to_dict().keys())\n", "\n", " # Separate issues with only one sub-issue and more than one sub-issue\n", " one_subissue = {key: value for key, value in issues_to_subissues.items() if len(issues_to_subissues[key]) == 1}\n", " more_than_one_subissue = {key: value for key, value in issues_to_subissues.items() if len(issues_to_subissues[key]) > 1}\n", "\n", " # Existing issue mapping for issues with more than one sub-issue\n", " existing_issue_mapping = {issue: issue for issue in more_than_one_subissue}\n", "\n", " # Issue renaming based on provided mapping\n", " issue_renaming = {\n", " 'Problem with a lender or other company charging your account': 'Account Operations and Unauthorized Transaction Issues',\n", " 'Opening an account': 'Account Operations and Unauthorized Transaction Issues',\n", " 'Getting a credit card': 'Account Operations and Unauthorized Transaction Issues',\n", "\n", " 'Unable to get your credit report or credit score': 'Credit Report and Monitoring Issues',\n", " 'Credit monitoring or identity theft protection services': 'Credit Report and Monitoring Issues',\n", " 'Identity theft protection or other monitoring services': 'Credit Report and Monitoring Issues',\n", "\n", " 'Problem caused by your funds being low': 'Payment and Funds Management',\n", " 'Problem when making payments': 'Payment and Funds Management',\n", " 'Managing the loan or lease': 'Payment and Funds Management',\n", "\n", " 'False statements or representation': 'Disputes and Misrepresentations',\n", " 'Fees or interest': 'Disputes and Misrepresentations',\n", " 'Other features, terms, or problems': 'Disputes and Misrepresentations',\n", "\n", " 'Took or threatened to take negative or legal action': 'Legal and Threat Actions'\n", " }\n", "\n", " # Combine issue renaming and existing issue mapping\n", " issues_mapping = {**issue_renaming, **existing_issue_mapping}\n", "\n", " # Map 'Issue' column using the defined mapping dictionary\n", " df['Issue'] = df['Issue'].apply(lambda x: issues_mapping[x])\n", "\n", " return df" ] }, { "cell_type": "code", "execution_count": 9, "id": "b8a50ac3-1a5b-4c78-92bb-da14d38a679c", "metadata": {}, "outputs": [], "source": [ "def split_and_save_data(df,year, test_size=0.25, random_state=42, directory_to_save='./data_splits/'):\n", " \"\"\"\n", " Split the input DataFrame into train and test sets, and save them as CSV files with the specified year included in the file names.\n", "\n", " Parameters:\n", " - df (DataFrame): The input DataFrame containing consumer complaint data.\n", " - year (int): The year associated with the data split.\n", " - test_size (float, optional): The proportion of the dataset to include in the test split. Default is 0.25.\n", " - random_state (int, optional): The seed used by the random number generator. Default is 42.\n", " - directory_to_save (str, optional): The directory path to save the data splits. Default is './data_splits/'.\n", "\n", " Returns:\n", " - None\n", " \"\"\"\n", " # Split the data into train and test sets\n", " X = df['Consumer complaint narrative']\n", " y = df[['Product', 'Sub-product', 'Issue', 'Sub-issue']]\n", " X_train, X_test, y_train, y_test = train_test_split(X, y, stratify=y['Product'], test_size=test_size, random_state=random_state)\n", "\n", " # Concatenate X_train and y_train, and X_test and y_test respectively\n", " train_df = pd.concat([X_train, y_train], axis=1).reset_index(drop=True)\n", " test_df = pd.concat([X_test, y_test], axis=1).reset_index(drop=True)\n", "\n", " # Create directory if it doesn't exist\n", " if not os.path.exists(directory_to_save):\n", " os.makedirs(directory_to_save)\n", " \n", " # Save train and test data as CSV files with the year included in the file names\n", " train_df.to_csv(os.path.join(directory_to_save, f'train-data-split_{year}.csv'), index=False)\n", " test_df.to_csv(os.path.join(directory_to_save, f'test-data-split_{year}.csv'), index=False)" ] }, { "cell_type": "markdown", "id": "a11fbb2c-548e-4bf8-bb41-abe22a4bd485", "metadata": { "jp-MarkdownHeadingCollapsed": true }, "source": [ "### Main Cleaning Pipeline" ] }, { "cell_type": "code", "execution_count": 13, "id": "479d2872-0202-4eac-85d9-ce2b532881dd", "metadata": {}, "outputs": [], "source": [ "def main(file_path, year,year_name):\n", " # Load and clean the data\n", " df_cleaned = load_and_clean_data(file_path)\n", " \n", " # Filter the data by years\n", " df_filtered = filter_by_years(df_cleaned, year)\n", " \n", " # Map the 'Product' column\n", " df_mapped = map_product_column(df_filtered)\n", " \n", " # Clean the customer narratives in the data\n", " df_clean_narrative = clean_narrative(df_mapped)\n", "\n", " # Clean the data by frequency\n", " df_freq = filter_by_frequency(df_clean_narrative)\n", "\n", " #Mapping the Issues and filtering Sub Issues\n", " df_final = map_issue_to_subissue(df_freq)\n", " \n", " # Split and save the data\n", " split_and_save_data(df_final,year_name)\n", " return df_final" ] }, { "cell_type": "markdown", "id": "60c6f92a-de2b-4c8a-817e-fb49a68f87ba", "metadata": {}, "source": [ "### Calling the data preprocessing script" ] }, { "cell_type": "code", "execution_count": 14, "id": "76932edd-4de8-47e1-a1a1-3cc4b07d6850", "metadata": {}, "outputs": [], "source": [ "file_path = 'complaints.csv'\n", "years_to_include = [2023]\n", "year_name=2023\n", "df_final=main(file_path, years_to_include,year_name)" ] }, { "cell_type": "code", "execution_count": 15, "id": "b55f4b09-6c89-470b-a4f0-60c0148534ec", "metadata": {}, "outputs": [ { "data": { "text/plain": [ "(247517, 11)" ] }, "execution_count": 15, "metadata": {}, "output_type": "execute_result" } ], "source": [ "df_final.shape" ] }, { "cell_type": "code", "execution_count": 16, "id": "ed38bd8a-2004-435a-87b2-2bcc43dbd565", "metadata": {}, "outputs": [ { "data": { "text/plain": [ "Credit Reporting 211695\n", "Checking or savings account 12285\n", "Credit/Prepaid Card 11975\n", "Debt collection 9380\n", "Loans / Mortgage 2182\n", "Name: Product, dtype: int64" ] }, "execution_count": 16, "metadata": {}, "output_type": "execute_result" } ], "source": [ "df_final.Product.value_counts()" ] }, { "cell_type": "code", "execution_count": 17, "id": "dab29296-8a5b-44c3-8c6e-7b60e062343f", "metadata": {}, "outputs": [ { "data": { "text/plain": [ "Credit reporting 210735\n", "General-purpose credit card or charge card 10668\n", "Checking account 10409\n", "Other debt 3041\n", "I do not know 2316\n", "Credit card debt 1652\n", "Federal student loan servicing 1344\n", "Store credit card 1307\n", "Medical debt 1053\n", "Savings account 989\n", "Other personal consumer report 960\n", "Loan 732\n", "Other banking product or service 725\n", "Auto debt 581\n", "Telecommunications debt 419\n", "Rental debt 179\n", "CD (Certificate of Deposit) 162\n", "Mortgage debt 139\n", "Conventional home mortgage 106\n", "Name: Sub-product, dtype: int64" ] }, "execution_count": 17, "metadata": {}, "output_type": "execute_result" } ], "source": [ "df_final['Sub-product'].value_counts()" ] }, { "cell_type": "code", "execution_count": 19, "id": "10127c9e-ea4a-49ce-a5f2-76991abde850", "metadata": {}, "outputs": [ { "data": { "text/plain": [ "Incorrect information on your report 87200\n", "Improper use of your report 61868\n", "Problem with a credit reporting company's investigation into an existing problem 45371\n", "Problem with a company's investigation into an existing problem 20985\n", "Managing an account 7367\n", "Attempts to collect debt not owed 5453\n", "Problem with a purchase shown on your statement 3253\n", "Account Operations and Unauthorized Transaction Issues 2450\n", "Written notification about debt 2404\n", "Disputes and Misrepresentations 2311\n", "Payment and Funds Management 2259\n", "Closing an account 1975\n", "Credit Report and Monitoring Issues 1630\n", "Dealing with your lender or servicer 1293\n", "Closing your account 813\n", "Legal and Threat Actions 662\n", "Problem with a company's investigation into an existing issue 223\n", "Name: Issue, dtype: int64" ] }, "execution_count": 19, "metadata": {}, "output_type": "execute_result" } ], "source": [ "df_final['Issue'].value_counts()" ] }, { "cell_type": "code", "execution_count": 20, "id": "e774f715-6d3d-4035-9f28-753801490d13", "metadata": {}, "outputs": [ { "data": { "text/plain": [ "Information belongs to someone else 57850\n", "Reporting company used your report improperly 48732\n", "Their investigation did not fix an error on your report 45395\n", "Credit inquiries on your report that you don't recognize 13136\n", "Account status incorrect 10208\n", "Account information incorrect 9267\n", "Was not notified of investigation status or results 9200\n", "Investigation took more than 30 days 8928\n", "Personal information incorrect 5900\n", "Debt is not yours 2785\n", "Deposits and withdrawals 2626\n", "Credit card company isn't resolving a dispute about a purchase on your statement 2289\n", "Didn't receive enough information to verify debt 1777\n", "Debt was result of identity theft 1727\n", "Old information reappears or never goes away 1714\n", "Difficulty submitting a dispute or getting information about a dispute over the phone 1704\n", "Company closed your account 1517\n", "Problem using a debit or ATM card 1503\n", "Public record information inaccurate 1384\n", "Transaction was not authorized 1378\n", "Problem with personal statement of dispute 1352\n", "Other problem getting your report or credit score 1109\n", "Card was charged for something you did not purchase with the card 964\n", "Banking errors 958\n", "Funds not handled or disbursed as instructed 955\n", "Overdrafts and overdraft fees 951\n", "Debt was paid 941\n", "Information is missing that should be on the report 877\n", "Attempted to collect wrong amount 861\n", "Problem during payment process 840\n", "Fee problem 764\n", "Problem with fees 749\n", "Other problem 701\n", "Received bad information about your loan 677\n", "Funds not received from closed account 673\n", "Threatened or suggested your credit would be damaged 662\n", "Didn't receive notice of right to dispute 627\n", "Trouble with how payments are being handled 616\n", "Can't close your account 598\n", "Problem accessing account 561\n", "Account opened as a result of fraud 561\n", "Problem canceling credit monitoring or identify theft protection service 521\n", "Card opened as result of identity theft or fraud 511\n", "Billing problem 468\n", "Name: Sub-issue, dtype: int64" ] }, "execution_count": 20, "metadata": {}, "output_type": "execute_result" } ], "source": [ "df_final['Sub-issue'].value_counts()" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.9.19" } }, "nbformat": 4, "nbformat_minor": 5 }