diff --git a/ai_data_science_team/agents/data_cleaning_agent.py b/ai_data_science_team/agents/data_cleaning_agent.py index 35e6f36..0133d94 100644 --- a/ai_data_science_team/agents/data_cleaning_agent.py +++ b/ai_data_science_team/agents/data_cleaning_agent.py @@ -103,6 +103,10 @@ class DataCleaningAgent(BaseAgent): Retrieves the generated Python function used for cleaning the data. get_recommended_cleaning_steps() Retrieves the agent's recommended cleaning steps. + get_response() + Returns the response from the agent as a dictionary. + show() + Displays the agent's mermaid diagram. Examples -------- diff --git a/examples/data_cleaning_agent.ipynb b/examples/data_cleaning_agent.ipynb index fcf335b..6b24bdd 100644 --- a/examples/data_cleaning_agent.ipynb +++ b/examples/data_cleaning_agent.ipynb @@ -54,7 +54,7 @@ "import pandas as pd\n", "from pprint import pprint\n", "\n", - "from ai_data_science_team.agents import make_data_cleaning_agent" + "from ai_data_science_team.agents import DataCleaningAgent" ] }, { @@ -76,7 +76,7 @@ { "data": { "text/plain": [ - "ChatOpenAI(client=, async_client=, root_client=, root_async_client=, model_name='gpt-4o-mini', model_kwargs={}, openai_api_key=SecretStr('**********'))" + "ChatOpenAI(client=, async_client=, root_client=, root_async_client=, model_name='gpt-4o-mini', model_kwargs={}, openai_api_key=SecretStr('**********'))" ] }, "execution_count": 2, @@ -523,7 +523,7 @@ "data": { "image/png": "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", "text/plain": [ - "" + "" ] }, "execution_count": 4, @@ -532,7 +532,7 @@ } ], "source": [ - "data_cleaning_agent = make_data_cleaning_agent(\n", + "data_cleaning_agent = DataCleaningAgent(\n", " model = llm, \n", " log=LOG, \n", " log_path=LOG_PATH\n", @@ -556,7 +556,7 @@ }, { "cell_type": "code", - "execution_count": 5, + "execution_count": null, "metadata": {}, "outputs": [ { @@ -564,20 +564,46 @@ "output_type": "stream", "text": [ "---DATA CLEANING AGENT----\n", + " * RECOMMEND CLEANING STEPS\n", " * CREATE DATA CLEANER CODE\n", + " File saved to: /Users/mdancho/Desktop/course_code/ai-data-science-team/logs/data_cleaner.py\n", " * EXECUTING AGENT CODE\n", + "Missing Value Percentage:\n", + " customerID 0.0\n", + "gender 0.0\n", + "SeniorCitizen 0.0\n", + "Partner 0.0\n", + "Dependents 0.0\n", + "tenure 0.0\n", + "PhoneService 0.0\n", + "MultipleLines 0.0\n", + "InternetService 0.0\n", + "OnlineSecurity 0.0\n", + "OnlineBackup 0.0\n", + "DeviceProtection 0.0\n", + "TechSupport 0.0\n", + "StreamingTV 0.0\n", + "StreamingMovies 0.0\n", + "Contract 0.0\n", + "PaperlessBilling 0.0\n", + "PaymentMethod 0.0\n", + "MonthlyCharges 0.0\n", + "TotalCharges 0.0\n", + "Churn 0.0\n", + "dtype: float64\n", + "Cleaning process documented: {'missing_values': {'customerID': 0.0, 'gender': 0.0, 'SeniorCitizen': 0.0, 'Partner': 0.0, 'Dependents': 0.0, 'tenure': 0.0, 'PhoneService': 0.0, 'MultipleLines': 0.0, 'InternetService': 0.0, 'OnlineSecurity': 0.0, 'OnlineBackup': 0.0, 'DeviceProtection': 0.0, 'TechSupport': 0.0, 'StreamingTV': 0.0, 'StreamingMovies': 0.0, 'Contract': 0.0, 'PaperlessBilling': 0.0, 'PaymentMethod': 0.0, 'MonthlyCharges': 0.0, 'TotalCharges': 0.0, 'Churn': 0.0}, 'removed_excessive_missing_cols': [], 'converted_total_charges_type': 'TotalCharges converted to float64', 'removed_duplicate_rows': 0}\n", " * EXPLAIN AGENT CODE\n" ] } ], "source": [ "\n", - "response = data_cleaning_agent.invoke({\n", - " \"user_instructions\": \"Don't remove outliers when cleaning the data.\",\n", - " \"data_raw\": df.to_dict(),\n", - " \"max_retries\":3, \n", - " \"retry_count\":0\n", - "})" + "data_cleaning_agent.invoke(\n", + " user_instructions=\"Don't remove outliers when cleaning the data.\",\n", + " data_raw=df,\n", + " max_retries=3,\n", + " retry_count=0\n", + ") \n" ] }, { @@ -599,10 +625,14 @@ "text/plain": [ "['messages',\n", " 'user_instructions',\n", + " 'recommended_steps',\n", " 'data_raw',\n", + " 'data_cleaned',\n", + " 'all_datasets_summary',\n", " 'data_cleaner_function',\n", + " 'data_cleaner_function_path',\n", + " 'data_cleaner_function_name',\n", " 'data_cleaner_error',\n", - " 'data_cleaned',\n", " 'max_retries',\n", " 'retry_count']" ] @@ -613,6 +643,8 @@ } ], "source": [ + "response = data_cleaning_agent.get_response()\n", + "\n", "list(response.keys())" ] }, @@ -620,131 +652,20 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "### The cleaning recipe " - ] - }, - { - "cell_type": "code", - "execution_count": 7, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "('# Data Cleaning Agent:\\n'\n", - " '\\n'\n", - " ' The data cleaning steps performed in the `data_cleaner` function are as '\n", - " 'follows:\\n'\n", - " '\\n'\n", - " '1. **Remove Columns with Excessive Missing Values**: Columns with more than '\n", - " '40% missing values are dropped to retain only useful data.\\n'\n", - " '\\n'\n", - " '2. **Impute Missing Values for Numeric Columns**: Missing values in numeric '\n", - " 'columns are filled with the mean of those columns using `SimpleImputer`.\\n'\n", - " '\\n'\n", - " '3. **Impute Missing Values for Categorical Columns**: Missing values in '\n", - " 'categorical columns are filled with the mode (most frequent value) using a '\n", - " 'separate `SimpleImputer`.\\n'\n", - " '\\n'\n", - " \"4. **Convert Data Types**: The 'TotalCharges' column is converted to a \"\n", - " 'numeric type to handle any non-numeric entries by coercing errors to NaN.\\n'\n", - " '\\n'\n", - " '5. **Remove Duplicate Rows**: Duplicate rows in the dataset are removed to '\n", - " 'ensure unique entries.\\n'\n", - " '\\n'\n", - " '6. **Remove Rows with Remaining Missing Values**: Any rows that still '\n", - " 'contain missing values after previous steps are dropped.\\n'\n", - " '\\n'\n", - " '7. **Reset Index**: The index of the cleaned dataset is reset to ensure a '\n", - " 'continuous sequence without gaps.\\n'\n", - " '\\n'\n", - " 'The function ultimately returns a cleaned DataFrame ready for analysis.')\n" - ] - } - ], - "source": [ - "pprint(response['messages'][0].content)\n" + "#### Cleaned Data As Pandas Data Frame " ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "#### Data Cleaner Function " + "Use the `get_data_cleaned()` method to get the cleaned data as a pandas data frame." ] }, { "cell_type": "code", "execution_count": 8, "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "('def data_cleaner(data_raw):\\n'\n", - " ' import pandas as pd\\n'\n", - " ' import numpy as np\\n'\n", - " ' from sklearn.impute import SimpleImputer\\n'\n", - " '\\n'\n", - " '\\n'\n", - " '\\n'\n", - " ' # Step 1: Remove columns with more than 40% missing values\\n'\n", - " ' threshold = 0.4 * len(data_raw)\\n'\n", - " ' data_cleaned = data_raw.dropna(axis=1, thresh=threshold)\\n'\n", - " '\\n'\n", - " ' # Step 2: Impute missing values for numeric columns with mean\\n'\n", - " ' numeric_cols = '\n", - " 'data_cleaned.select_dtypes(include=np.number).columns.tolist()\\n'\n", - " \" imputer_numeric = SimpleImputer(strategy='mean')\\n\"\n", - " ' data_cleaned[numeric_cols] = '\n", - " 'imputer_numeric.fit_transform(data_cleaned[numeric_cols])\\n'\n", - " '\\n'\n", - " ' # Step 3: Impute missing values for categorical columns with mode\\n'\n", - " ' categorical_cols = '\n", - " \"data_cleaned.select_dtypes(include='object').columns.tolist()\\n\"\n", - " \" imputer_categorical = SimpleImputer(strategy='most_frequent')\\n\"\n", - " ' data_cleaned[categorical_cols] = '\n", - " 'imputer_categorical.fit_transform(data_cleaned[categorical_cols])\\n'\n", - " '\\n'\n", - " ' # Step 4: Convert columns to the correct data type\\n'\n", - " \" # Convert 'TotalCharges' to numeric, as it might be read as object due \"\n", - " 'to non-numeric entries\\n'\n", - " \" data_cleaned['TotalCharges'] = \"\n", - " \"pd.to_numeric(data_cleaned['TotalCharges'], errors='coerce')\\n\"\n", - " '\\n'\n", - " ' # Step 5: Remove duplicate rows\\n'\n", - " ' data_cleaned = data_cleaned.drop_duplicates()\\n'\n", - " '\\n'\n", - " ' # Step 6: Remove rows with missing values\\n'\n", - " ' data_cleaned = data_cleaned.dropna()\\n'\n", - " '\\n'\n", - " ' # Note: The user requested not to remove outliers, so we will skip that '\n", - " 'step.\\n'\n", - " '\\n'\n", - " ' # Step 7: Reset index after cleaning\\n'\n", - " ' data_cleaned.reset_index(drop=True, inplace=True)\\n'\n", - " '\\n'\n", - " ' return data_cleaned')\n" - ] - } - ], - "source": [ - "pprint(response['data_cleaner_function'])" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "#### Cleaned Data As Pandas Data Frame " - ] - }, - { - "cell_type": "code", - "execution_count": 9, - "metadata": {}, "outputs": [ { "data": { @@ -795,10 +716,10 @@ " 0\n", " 7590-VHVEG\n", " Female\n", - " 0.0\n", + " 0\n", " Yes\n", " No\n", - " 1.0\n", + " 1\n", " No\n", " No phone service\n", " DSL\n", @@ -819,10 +740,10 @@ " 1\n", " 5575-GNVDE\n", " Male\n", - " 0.0\n", + " 0\n", " No\n", " No\n", - " 34.0\n", + " 34\n", " Yes\n", " No\n", " DSL\n", @@ -843,10 +764,10 @@ " 2\n", " 3668-QPYBK\n", " Male\n", - " 0.0\n", + " 0\n", " No\n", " No\n", - " 2.0\n", + " 2\n", " Yes\n", " No\n", " DSL\n", @@ -867,10 +788,10 @@ " 3\n", " 7795-CFOCW\n", " Male\n", - " 0.0\n", + " 0\n", " No\n", " No\n", - " 45.0\n", + " 45\n", " No\n", " No phone service\n", " DSL\n", @@ -891,10 +812,10 @@ " 4\n", " 9237-HQITU\n", " Female\n", - " 0.0\n", + " 0\n", " No\n", " No\n", - " 2.0\n", + " 2\n", " Yes\n", " No\n", " Fiber optic\n", @@ -936,13 +857,13 @@ " ...\n", " \n", " \n", - " 7027\n", + " 7038\n", " 6840-RESVB\n", " Male\n", - " 0.0\n", + " 0\n", " Yes\n", " Yes\n", - " 24.0\n", + " 24\n", " Yes\n", " Yes\n", " DSL\n", @@ -960,13 +881,13 @@ " No\n", " \n", " \n", - " 7028\n", + " 7039\n", " 2234-XADUH\n", " Female\n", - " 0.0\n", + " 0\n", " Yes\n", " Yes\n", - " 72.0\n", + " 72\n", " Yes\n", " Yes\n", " Fiber optic\n", @@ -984,13 +905,13 @@ " No\n", " \n", " \n", - " 7029\n", + " 7040\n", " 4801-JZAZL\n", " Female\n", - " 0.0\n", + " 0\n", " Yes\n", " Yes\n", - " 11.0\n", + " 11\n", " No\n", " No phone service\n", " DSL\n", @@ -1008,13 +929,13 @@ " No\n", " \n", " \n", - " 7030\n", + " 7041\n", " 8361-LTMKD\n", " Male\n", - " 1.0\n", + " 1\n", " Yes\n", " No\n", - " 4.0\n", + " 4\n", " Yes\n", " Yes\n", " Fiber optic\n", @@ -1032,13 +953,13 @@ " Yes\n", " \n", " \n", - " 7031\n", + " 7042\n", " 3186-AJIEK\n", " Male\n", - " 0.0\n", + " 0\n", " No\n", " No\n", - " 66.0\n", + " 66\n", " Yes\n", " No\n", " Fiber optic\n", @@ -1057,22 +978,22 @@ " \n", " \n", "\n", - "

7032 rows × 21 columns

\n", + "

7043 rows × 21 columns

\n", "" ], "text/plain": [ " customerID gender SeniorCitizen Partner Dependents tenure \\\n", - "0 7590-VHVEG Female 0.0 Yes No 1.0 \n", - "1 5575-GNVDE Male 0.0 No No 34.0 \n", - "2 3668-QPYBK Male 0.0 No No 2.0 \n", - "3 7795-CFOCW Male 0.0 No No 45.0 \n", - "4 9237-HQITU Female 0.0 No No 2.0 \n", + "0 7590-VHVEG Female 0 Yes No 1 \n", + "1 5575-GNVDE Male 0 No No 34 \n", + "2 3668-QPYBK Male 0 No No 2 \n", + "3 7795-CFOCW Male 0 No No 45 \n", + "4 9237-HQITU Female 0 No No 2 \n", "... ... ... ... ... ... ... \n", - "7027 6840-RESVB Male 0.0 Yes Yes 24.0 \n", - "7028 2234-XADUH Female 0.0 Yes Yes 72.0 \n", - "7029 4801-JZAZL Female 0.0 Yes Yes 11.0 \n", - "7030 8361-LTMKD Male 1.0 Yes No 4.0 \n", - "7031 3186-AJIEK Male 0.0 No No 66.0 \n", + "7038 6840-RESVB Male 0 Yes Yes 24 \n", + "7039 2234-XADUH Female 0 Yes Yes 72 \n", + "7040 4801-JZAZL Female 0 Yes Yes 11 \n", + "7041 8361-LTMKD Male 1 Yes No 4 \n", + "7042 3186-AJIEK Male 0 No No 66 \n", "\n", " PhoneService MultipleLines InternetService OnlineSecurity ... \\\n", "0 No No phone service DSL No ... \n", @@ -1081,11 +1002,11 @@ "3 No No phone service DSL Yes ... \n", "4 Yes No Fiber optic No ... \n", "... ... ... ... ... ... \n", - "7027 Yes Yes DSL Yes ... \n", - "7028 Yes Yes Fiber optic No ... \n", - "7029 No No phone service DSL Yes ... \n", - "7030 Yes Yes Fiber optic No ... \n", - "7031 Yes No Fiber optic Yes ... \n", + "7038 Yes Yes DSL Yes ... \n", + "7039 Yes Yes Fiber optic No ... \n", + "7040 No No phone service DSL Yes ... \n", + "7041 Yes Yes Fiber optic No ... \n", + "7042 Yes No Fiber optic Yes ... \n", "\n", " DeviceProtection TechSupport StreamingTV StreamingMovies Contract \\\n", "0 No No No No Month-to-month \n", @@ -1094,11 +1015,11 @@ "3 Yes Yes No No One year \n", "4 No No No No Month-to-month \n", "... ... ... ... ... ... \n", - "7027 Yes Yes Yes Yes One year \n", - "7028 Yes No Yes Yes One year \n", - "7029 No No No No Month-to-month \n", - "7030 No No No No Month-to-month \n", - "7031 Yes Yes Yes Yes Two year \n", + "7038 Yes Yes Yes Yes One year \n", + "7039 Yes No Yes Yes One year \n", + "7040 No No No No Month-to-month \n", + "7041 No No No No Month-to-month \n", + "7042 Yes Yes Yes Yes Two year \n", "\n", " PaperlessBilling PaymentMethod MonthlyCharges TotalCharges \\\n", "0 Yes Electronic check 29.85 29.85 \n", @@ -1107,11 +1028,11 @@ "3 No Bank transfer (automatic) 42.30 1840.75 \n", "4 Yes Electronic check 70.70 151.65 \n", "... ... ... ... ... \n", - "7027 Yes Mailed check 84.80 1990.50 \n", - "7028 Yes Credit card (automatic) 103.20 7362.90 \n", - "7029 Yes Electronic check 29.60 346.45 \n", - "7030 Yes Mailed check 74.40 306.60 \n", - "7031 Yes Bank transfer (automatic) 105.65 6844.50 \n", + "7038 Yes Mailed check 84.80 1990.50 \n", + "7039 Yes Credit card (automatic) 103.20 7362.90 \n", + "7040 Yes Electronic check 29.60 346.45 \n", + "7041 Yes Mailed check 74.40 306.60 \n", + "7042 Yes Bank transfer (automatic) 105.65 6844.50 \n", "\n", " Churn \n", "0 No \n", @@ -1120,13 +1041,141 @@ "3 No \n", "4 Yes \n", "... ... \n", - "7027 No \n", - "7028 No \n", - "7029 No \n", - "7030 Yes \n", - "7031 No \n", + "7038 No \n", + "7039 No \n", + "7040 No \n", + "7041 Yes \n", + "7042 No \n", "\n", - "[7032 rows x 21 columns]" + "[7043 rows x 21 columns]" + ] + }, + "execution_count": 8, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "data_cleaning_agent.get_data_cleaned()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### Data Cleaner Function " + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We can use the `get_data_cleaner_function()` method to get the data cleaner function pipeline. \n", + "\n", + "- In Jupyter Notebooks, setting `markdown=True` will return the function as markdown code. \n", + "- In Streamlit apps, it's recommended to set `markdown=False`." + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": {}, + "outputs": [ + { + "data": { + "text/markdown": [ + "```python\n", + "# Disclaimer: This function was generated by AI. Please review before using.\n", + "# Agent Name: data_cleaning_agent\n", + "# Time Created: 2025-01-09 17:35:40\n", + "\n", + "def data_cleaner(data_raw):\n", + " import pandas as pd\n", + " import numpy as np\n", + "\n", + "\n", + " # Step 1: Check for missing values\n", + " missing_values = data_raw.isnull().mean() * 100\n", + " print(\"Missing Value Percentage:\\n\", missing_values)\n", + "\n", + " # Step 2: Remove columns with excessive missing values\n", + " excessive_missing_cols = missing_values[missing_values > 40].index\n", + " data_cleaned = data_raw.drop(columns=excessive_missing_cols, errors='ignore')\n", + "\n", + " # Step 3: Convert data types\n", + " data_cleaned['TotalCharges'] = pd.to_numeric(data_cleaned['TotalCharges'], errors='coerce')\n", + "\n", + " # Step 5: Remove duplicate rows\n", + " data_cleaned = data_cleaned.drop_duplicates()\n", + "\n", + " # Document the cleaning process\n", + " cleaning_steps = {\n", + " \"missing_values\": missing_values.to_dict(),\n", + " \"removed_excessive_missing_cols\": excessive_missing_cols.tolist(),\n", + " \"converted_total_charges_type\": 'TotalCharges converted to float64',\n", + " \"removed_duplicate_rows\": data_raw.shape[0] - data_cleaned.shape[0]\n", + " }\n", + "\n", + " print(\"Cleaning process documented:\", cleaning_steps)\n", + "\n", + " return data_cleaned\n", + "```" + ], + "text/plain": [ + "" + ] + }, + "execution_count": 7, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "data_cleaning_agent.get_data_cleaner_function(markdown=True)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### Recommended Steps\n", + "\n", + "To get the recommended steps during the data analysis (prior to coding), run the `get_recommended_steps()` method." + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": {}, + "outputs": [ + { + "data": { + "text/markdown": [ + "\n", + "\n", + "# Recommended Data Cleaning Steps:\n", + "1. **Check for missing values:** Review each column for missing data and document the percentage of missing values.\n", + "\n", + "2. **Remove columns with excessive missing values:** Identify and remove any columns where more than 40% of the data is missing. (In this dataset, no columns will be removed since all have 0% missing values.)\n", + "\n", + "3. **Convert data types:** \n", + " - Convert `TotalCharges` from object to float64, as it contains numeric values represented as strings.\n", + "\n", + "4. **Impute missing values:** \n", + " - Since there are no missing values in the dataset, this step is not necessary.\n", + "\n", + "5. **Remove duplicate rows:** Check for and remove any duplicate rows in the dataset.\n", + "\n", + "6. **Remove rows with missing values:** As there are no missing values, this step is not necessary.\n", + "\n", + "7. **Remove extreme outliers:** Since the user has instructed not to remove outliers, this step will be skipped.\n", + "\n", + "8. **Analyze the data for additional cleaning needs:** After the above steps, confirm that no additional cleaning steps are required, as the dataset appears to be clean and consistent.\n", + "\n", + "9. **Document the cleaning process:** Maintain a record of all steps taken in the data cleaning process for future reference." + ], + "text/plain": [ + "" ] }, "execution_count": 9, @@ -1135,7 +1184,133 @@ } ], "source": [ - "pd.DataFrame(response['data_cleaned'])\n" + "data_cleaning_agent.get_recommended_cleaning_steps(markdown=True)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### Explore the agent documentation for more information" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\u001b[0;31mType:\u001b[0m DataCleaningAgent\n", + "\u001b[0;31mString form:\u001b[0m \n", + "\u001b[0;31mFile:\u001b[0m ~/Desktop/course_code/ai-data-science-team/ai_data_science_team/agents/data_cleaning_agent.py\n", + "\u001b[0;31mDocstring:\u001b[0m \n", + "Creates a data cleaning agent that can process datasets based on user-defined instructions or default cleaning steps. \n", + "The agent generates a Python function to clean the dataset, performs the cleaning, and logs the process, including code \n", + "and errors. It is designed to facilitate reproducible and customizable data cleaning workflows.\n", + "\n", + "The agent performs the following default cleaning steps unless instructed otherwise:\n", + "\n", + "- Removing columns with more than 40% missing values.\n", + "- Imputing missing values with the mean for numeric columns.\n", + "- Imputing missing values with the mode for categorical columns.\n", + "- Converting columns to appropriate data types.\n", + "- Removing duplicate rows.\n", + "- Removing rows with missing values.\n", + "- Removing rows with extreme outliers (values 3x the interquartile range).\n", + "\n", + "User instructions can modify, add, or remove any of these steps to tailor the cleaning process.\n", + "\n", + "Parameters\n", + "----------\n", + "model : langchain.llms.base.LLM\n", + " The language model used to generate the data cleaning function.\n", + "n_samples : int, optional\n", + " Number of samples used when summarizing the dataset. Defaults to 30. Reducing this number can help \n", + " avoid exceeding the model's token limits.\n", + "log : bool, optional\n", + " Whether to log the generated code and errors. Defaults to False.\n", + "log_path : str, optional\n", + " Directory path for storing log files. Defaults to None.\n", + "file_name : str, optional\n", + " Name of the file for saving the generated response. Defaults to \"data_cleaner.py\".\n", + "overwrite : bool, optional\n", + " Whether to overwrite the log file if it exists. If False, a unique file name is created. Defaults to True.\n", + "human_in_the_loop : bool, optional\n", + " Enables user review of data cleaning instructions. Defaults to False.\n", + "bypass_recommended_steps : bool, optional\n", + " If True, skips the default recommended cleaning steps. Defaults to False.\n", + "bypass_explain_code : bool, optional\n", + " If True, skips the step that provides code explanations. Defaults to False.\n", + "\n", + "Methods\n", + "-------\n", + "update_params(**kwargs)\n", + " Updates the agent's parameters and rebuilds the compiled state graph.\n", + "ainvoke(user_instructions: str, data_raw: pd.DataFrame, max_retries=3, retry_count=0)\n", + " Cleans the provided dataset asynchronously based on user instructions.\n", + "invoke(user_instructions: str, data_raw: pd.DataFrame, max_retries=3, retry_count=0)\n", + " Cleans the provided dataset synchronously based on user instructions.\n", + "explain_cleaning_steps()\n", + " Returns an explanation of the cleaning steps performed by the agent.\n", + "get_log_summary()\n", + " Retrieves a summary of logged operations if logging is enabled.\n", + "get_state_keys()\n", + " Returns a list of keys from the state graph response.\n", + "get_state_properties()\n", + " Returns detailed properties of the state graph response.\n", + "get_data_cleaned()\n", + " Retrieves the cleaned dataset as a pandas DataFrame.\n", + "get_data_raw()\n", + " Retrieves the raw dataset as a pandas DataFrame.\n", + "get_data_cleaner_function()\n", + " Retrieves the generated Python function used for cleaning the data.\n", + "get_recommended_cleaning_steps()\n", + " Retrieves the agent's recommended cleaning steps.\n", + "\n", + "Examples\n", + "--------\n", + "```python\n", + "import pandas as pd\n", + "from langchain_openai import ChatOpenAI\n", + "from ai_data_science_team.agents import DataCleaningAgent\n", + "\n", + "llm = ChatOpenAI(model=\"gpt-4o-mini\")\n", + "\n", + "data_cleaning_agent = DataCleaningAgent(\n", + " model=llm, n_samples=50, log=True, log_path=\"logs\", human_in_the_loop=True\n", + ")\n", + "\n", + "df = pd.read_csv(\"https://raw.githubusercontent.com/business-science/ai-data-science-team/refs/heads/master/data/churn_data.csv\")\n", + "\n", + "data_cleaning_agent.invoke(\n", + " user_instructions=\"Don't remove outliers when cleaning the data.\",\n", + " data_raw=df,\n", + " max_retries=3,\n", + " retry_count=0\n", + ")\n", + "\n", + "cleaned_data = data_cleaning_agent.get_data_cleaned()\n", + "\n", + "response = data_cleaning_agent.response\n", + "```\n", + "\n", + "Returns\n", + "--------\n", + "DataCleaningAgent : langchain.graphs.CompiledStateGraph \n", + " A data cleaning agent implemented as a compiled state graph. \n", + "\u001b[0;31mInit docstring:\u001b[0m\n", + "Initialize the agent with provided parameters.\n", + "\n", + "Parameters:\n", + " **params: Arbitrary keyword arguments representing the agent's parameters." + ] + } + ], + "source": [ + "?data_cleaning_agent" ] }, {