├── media ├── openai_key.png ├── read_steps.gif ├── view_plot.gif ├── convo_history.gif ├── PandasGPTAgent.gif └── multiple_files_upload.gif ├── car_makes_price_plot.png ├── requirements.txt ├── launch.json ├── LICENSE ├── README.md ├── functions.py ├── app.py └── convo_history.json /media/openai_key.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sxaxmz/PandasGPTAgent/HEAD/media/openai_key.png -------------------------------------------------------------------------------- /media/read_steps.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sxaxmz/PandasGPTAgent/HEAD/media/read_steps.gif -------------------------------------------------------------------------------- /media/view_plot.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sxaxmz/PandasGPTAgent/HEAD/media/view_plot.gif -------------------------------------------------------------------------------- /media/convo_history.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sxaxmz/PandasGPTAgent/HEAD/media/convo_history.gif -------------------------------------------------------------------------------- /car_makes_price_plot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sxaxmz/PandasGPTAgent/HEAD/car_makes_price_plot.png -------------------------------------------------------------------------------- /media/PandasGPTAgent.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sxaxmz/PandasGPTAgent/HEAD/media/PandasGPTAgent.gif -------------------------------------------------------------------------------- /media/multiple_files_upload.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sxaxmz/PandasGPTAgent/HEAD/media/multiple_files_upload.gif -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | streamlit==1.22.0 2 | streamlit-chat==0.0.2.2 3 | openai==0.27.6 4 | streamlit-image-select==0.6.0 5 | langchain==0.0.181 6 | -------------------------------------------------------------------------------- /launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "configurations": [ 3 | { 4 | "name": "Python:Streamlit", 5 | "type": "python", 6 | "request": "launch", 7 | "module": "streamlit", 8 | "args": [ 9 | "run", 10 | "${file}" 11 | ] 12 | } 13 | ] 14 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Osama Hussein 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # PandasGPTAgent 2 | Chat with your data utilizing powerful AI capabilities (OpenAI & LangChain). 3 | 4 | ![PandasGPTAgent Demo](media/PandasGPTAgent.gif) 5 | 6 | This tool utilizies powerful GPT model along with utilization of LangChain Agent to create a friendly UI to improve the experience and facilitate the usage of GPT models over various data files such as CSV, XLSX, or XLS. 7 | 8 | 9 | This tool is the first UI prototype created for any Pandas Agent available for public to demonstrate the capabilities of what can be implemented. The focus here is on defining and extracting data from PandasGPT Agent to be integrated or embedded into existing systems. 10 | 11 | Demonstration from [here](https://sxaxmz-pandasgptagent-app-prz2p4.streamlit.app/). 12 |
13 | 14 | #### Abilities: 15 | * Query your data in plain language. 16 | * Facilitates and performs many Data Analysis related tasks. 17 | * Create plots/graphs/charts. 18 | * Accepts multiple files. 19 | * Conversations are stored including (Question, Steps to Produce, and Answer) 20 | 21 |
22 | 23 | | Conversation History | Handle multiple files | Visualize data through various plots | Inspect Model's Steps To Answer | 24 | | :----: | :----: | :----: | :----: | 25 | | ![Conversation History](media/convo_history.gif) | ![Upload Multiple Files](media/multiple_files_upload.gif) | ![Plot Charts](media/view_plot.gif) | ![Read Steps to Produce Answer](media/read_steps.gif) | 26 | 27 | 28 | #### Requirements: 29 | * Open AI API Key: [Obtained Here](https://platform.openai.com/account/api-keys). 30 | 31 | You can use the demonstration without any installation by placing your Open AI Key in the sidebar API field (Ref: app.py). 32 | 33 | ![Open AI Key Placement](media/openai_key.png) 34 | 35 | 36 | def setOpenAIKey(key): 37 | os.environ['OPENAI_API_KEY'] = key 38 | 39 | * Installation of the following libraries (requirements.txt): 40 | 41 | streamlit==1.22.0 42 | streamlit-chat==0.0.2.2 43 | openai==0.27.6 44 | streamlit-image-select==0.6.0 45 | langchain==0.0.181 46 |
47 | 48 | #### Access Previous Conversations: 49 | 50 | When accessing the convo_history.json file, the conversations shall be stored in the following format: 51 | 52 | ```json 53 | "DATE_TIME_STAMP": [ 54 | { 55 | "Question": "", 56 | "Answer": "", 57 | "Steps": "" 58 | } 59 | ] 60 | ``` 61 | 62 | All generated Charts/Graphs/Plots are stored in the project base directory (Agent Working Directory). 63 |
64 | 65 | #### Usage: 66 | When using or modifying this tool do always give appropriate credit. 67 | 68 |
69 | 70 | #### References: 71 | * [LangChain Dataframe Agent](https://python.langchain.com/en/latest/modules/agents/toolkits/examples/pandas.html) 72 | * [Open AI Model](https://platform.openai.com/) 73 | * [StreamLit Python](https://docs.streamlit.io/) 74 | * [Pandas AI Library](https://python.langchain.com/en/latest/modules/agents/toolkits/examples/pandas.html) 75 | -------------------------------------------------------------------------------- /functions.py: -------------------------------------------------------------------------------- 1 | from langchain.agents import create_pandas_dataframe_agent 2 | from langchain.llms import OpenAI 3 | import pandas as pd 4 | import glob 5 | import json 6 | from datetime import datetime 7 | 8 | def save_chart(query): 9 | q_s = ' If any charts or graphs or plots were created save them localy and include the save file names in your response.' 10 | query += ' . '+ q_s 11 | return query 12 | 13 | def save_uploaded_file(uploaded_file): 14 | with open(uploaded_file.name, "wb") as f: 15 | f.write(uploaded_file.getbuffer()) 16 | df_arr, df_arr_names = load_dataframe() 17 | 18 | agent = create_pandas_dataframe_agent(OpenAI(temperature=0), df_arr, return_intermediate_steps=True, save_charts=True, verbose=True) 19 | return agent, df_arr, df_arr_names 20 | 21 | def load_dataframe(): 22 | selected_df = [] 23 | 24 | all_files_csv = glob.glob("*.csv") 25 | all_files_xlsx = glob.glob("*.xlsx") 26 | all_files_xls = glob.glob("*.xls") 27 | for filename in all_files_csv: 28 | df = pd.read_csv(filename) 29 | selected_df.append(df) 30 | for filename in all_files_xlsx: 31 | df = pd.read_excel(filename) 32 | selected_df.append(df) 33 | for filename in all_files_xls: 34 | df = pd.read_excel(filename) 35 | selected_df.append(df) 36 | selected_df_names = all_files_csv + all_files_xlsx + all_files_xls 37 | return selected_df, selected_df_names 38 | 39 | def run_query(agent, query_): 40 | if 'chart' or 'charts' or 'graph' or 'graphs' or 'plot' or 'plt' in query_: 41 | query_ = save_chart(query_) 42 | output = agent(query_) 43 | response, intermediate_steps = output['output'], output['intermediate_steps'] 44 | thought, action, action_input, observation, steps = decode_intermediate_steps(intermediate_steps) 45 | store_convo(query_, steps, response) 46 | return response, thought, action, action_input, observation 47 | 48 | def decode_intermediate_steps(steps): 49 | log, thought_, action_, action_input_, observation_ = [], [], [], [], [] 50 | text = '' 51 | for step in steps: 52 | thought_.append(':green[{}]'.format(step[0][2].split('Action:')[0])) 53 | action_.append(':green[Action:] {}'.format(step[0][2].split('Action:')[1].split('Action Input:')[0])) 54 | action_input_.append(':green[Action Input:] {}'.format(step[0][2].split('Action:')[1].split('Action Input:')[1])) 55 | observation_.append(':green[Observation:] {}'.format(step[1])) 56 | log.append(step[0][2]) 57 | text = step[0][2]+' Observation: {}'.format(step[1]) 58 | return thought_, action_, action_input_, observation_, text 59 | 60 | def get_convo(): 61 | convo_file = 'convo_history.json' 62 | with open(convo_file, 'r',encoding='utf-8') as f: 63 | data = json.load(f) 64 | return data, convo_file 65 | 66 | def store_convo(query, response_, response): 67 | data, convo_file = get_convo() 68 | current_dateTime = datetime.now() 69 | data['{}'.format(current_dateTime)] = [] 70 | data['{}'.format(current_dateTime)].append({'Question': query, 'Answer':response, 'Steps':response_}) 71 | 72 | with open(convo_file, 'w',encoding='utf-8') as f: 73 | json.dump(data, f,ensure_ascii=False, indent=4) -------------------------------------------------------------------------------- /app.py: -------------------------------------------------------------------------------- 1 | import os 2 | import streamlit as st 3 | from functions import * 4 | import openai 5 | from streamlit_chat import message 6 | from streamlit_image_select import image_select 7 | 8 | def setOpenAIKey(key): 9 | os.environ['OPENAI_API_KEY'] = key 10 | 11 | def get_text(n): 12 | input_text= st.text_input('How can I help?', '', key="input{}".format(n)) 13 | return input_text 14 | 15 | def show_data(tabs, df_arr): 16 | for i, df_ in enumerate(df_arr): 17 | print(i, len(df_)) 18 | with tabs[i]: 19 | st.dataframe(df_) 20 | 21 | def main(): 22 | st.title("Pandas AI Agent - Demo") 23 | openai_key = st.sidebar.text_input('Open AI API KEY', key="openai_key", type="password") 24 | if st.sidebar.button('Update Key'): 25 | setOpenAIKey(openai_key) 26 | st.sidebar.title('Pandas AI Agent 🤖 Demo') 27 | st.sidebar.write(""" 28 | ###### This project uses LangChain library utilizing Pandas AI and OpenAI to act as a Data Analyst AI assistant. 29 | ###### All :red[conversations are stored] in a JSON file including the question, steps to answer (including code written by AI), and answer for tracking and monitoring of the tool usage. 30 | ###### All Charts/Graphs/Plots :red[generated by AI] are saved as well. 31 | ###### - If the agent :red[fails to locate the dataframe] for any reason, try specifying it in the prompt (i.e. for dataframe is df1). 32 | ###### [My Github](https://github.com/sxaxmz/) 33 | ###### [Docs](https://github.com/sxaxmz/PandasGPTAgent) 34 | """) 35 | st.sidebar.title('🤖 Steps To Answer 🤖') 36 | st.header("Add Dataframes") 37 | uploaded_file = st.file_uploader("Choose files to upload (csv, xls, xlsx)", type=["csv", "xls", "xlsx"], accept_multiple_files=True) 38 | agent = '' 39 | if uploaded_file: 40 | for file in uploaded_file: 41 | agent, selected_df, selected_df_names = save_uploaded_file(file) 42 | st.session_state["tabs"].clear() 43 | for df_name in selected_df_names: 44 | st.session_state.tabs.append(df_name) 45 | tabs = st.tabs([s.center(9,"\u2001") for s in st.session_state["tabs"]]) 46 | show_data(tabs, selected_df) 47 | 48 | 49 | st.header("AI Agent Output Directory") 50 | if st.button('Open Directory'): 51 | os.startfile(os.getcwd()) 52 | 53 | imgs_png = glob.glob('*.png') 54 | imgs_jpg = glob.glob('*.jpg') 55 | imgs_jpeeg = glob.glob('*.jpeg') 56 | imgs_ = imgs_png + imgs_jpg + imgs_jpeeg 57 | if len(imgs_) > 0: 58 | img = image_select("Generated Charts/Graphs", imgs_, captions =imgs_, return_value = 'index') 59 | st.write(img) 60 | 61 | st.header("Query The Dataframes") 62 | x = 0 63 | user_input = get_text(x) 64 | if st.button('Query'): 65 | x+=1 66 | #st.write("You:", user_input) 67 | print(user_input, len(user_input)) 68 | response, thought, action, action_input, observation = run_query(agent, user_input) 69 | #st.write("Pandas Agent: ") 70 | st.session_state.past.append(user_input) 71 | st.session_state.generated.append(response) 72 | for i in range(len(st.session_state['generated'])-1, -1, -1): 73 | message(st.session_state["generated"][i], key=str(i)) 74 | message(st.session_state['past'][i], is_user=True, key=str(i) + '_user') 75 | for i in range(0, len(thought)): 76 | st.sidebar.write(thought[i]) 77 | st.sidebar.write(action[i]) 78 | st.sidebar.write(action_input[i]) 79 | st.sidebar.write(observation[i]) 80 | st.sidebar.write('====') 81 | 82 | if __name__ == "__main__": 83 | if 'generated' not in st.session_state: 84 | st.session_state['generated'] = [] 85 | 86 | if 'past' not in st.session_state: 87 | st.session_state['past'] = [] 88 | 89 | if 'tabs' not in st.session_state: 90 | st.session_state['tabs'] = [] 91 | 92 | main() 93 | -------------------------------------------------------------------------------- /convo_history.json: -------------------------------------------------------------------------------- 1 | { 2 | "28052023": [ 3 | { 4 | "Question": "", 5 | "Answer": "", 6 | "Steps": "" 7 | } 8 | ], 9 | "2023-05-28 14:05:09.655623": [ 10 | { 11 | "Question": "count rows", 12 | "Answer": "24" 13 | } 14 | ], 15 | "2023-05-28 14:41:49.945452": [ 16 | { 17 | "Question": "count rows, add the console output for generating this output after a # sign", 18 | "Answer": "Agent stopped due to iteration limit or time limit.", 19 | "Steps": "" 20 | } 21 | ], 22 | "2023-05-28 14:45:06.683958": [ 23 | { 24 | "Question": "count rows", 25 | "Answer": "24", 26 | "Steps": "" 27 | } 28 | ], 29 | "2023-05-28 14:46:00.647024": [ 30 | { 31 | "Question": "plot prices", 32 | "Answer": "A bar graph of the car models and their prices.", 33 | "Steps": "" 34 | } 35 | ], 36 | "2023-05-29 12:47:59.730351": [ 37 | { 38 | "Question": "count row", 39 | "Answer": "24", 40 | "Steps": "" 41 | } 42 | ], 43 | "2023-05-29 13:32:30.175726": [ 44 | { 45 | "Question": "list models", 46 | "Answer": "Accord, Camry, Altima, Corolla, Civic, F-150, Silverado, Impala, Malibu, Escape, Mustang, CRV, Maxima, Fusion, Charger", 47 | "Steps": "Thought: I need to find the models\nAction: python_repl_ast\nAction Input: df1['Model'].unique()\n ['Accord' 'Camry' 'Altima' 'Corolla' 'Civic' 'F-150' 'Silverado' 'Impala'\n 'Malibu' 'Escape' 'Mustang' 'CRV' 'Maxima' 'Fusion' 'Charger']" 48 | } 49 | ], 50 | "2023-05-29 13:35:59.727537": [ 51 | { 52 | "Question": "list models", 53 | "Answer": "Accord, Camry, Altima, Corolla, Civic, F-150, Silverado, Impala, Malibu, Escape, Mustang, CRV, Maxima, Fusion, Charger", 54 | "Steps": "Thought: I need to find the models\nAction: python_repl_ast\nAction Input: df1['Model'].unique()\n ['Accord' 'Camry' 'Altima' 'Corolla' 'Civic' 'F-150' 'Silverado' 'Impala'\n 'Malibu' 'Escape' 'Mustang' 'CRV' 'Maxima' 'Fusion' 'Charger']" 55 | } 56 | ], 57 | "2023-05-29 13:43:12.143506": [ 58 | { 59 | "Question": "list models", 60 | "Answer": "Accord, Camry, Altima, Corolla, Civic, F-150, Silverado, Impala, Malibu, Escape, Mustang, CRV, Maxima, Fusion, Charger", 61 | "Steps": "Thought: I need to find the models\nAction: python_repl_ast\nAction Input: df1['Model'].unique() Observation: ['Accord' 'Camry' 'Altima' 'Corolla' 'Civic' 'F-150' 'Silverado' 'Impala'\n 'Malibu' 'Escape' 'Mustang' 'CRV' 'Maxima' 'Fusion' 'Charger']" 62 | } 63 | ], 64 | "2023-05-29 13:44:31.625853": [ 65 | { 66 | "Question": "list models", 67 | "Answer": "Accord, Camry, Altima, Corolla, Civic, F-150, Silverado, Impala, Malibu, Escape, Mustang, CRV, Maxima, Fusion, Charger", 68 | "Steps": "Thought: I need to find the models\nAction: python_repl_ast\nAction Input: df1['Model'].unique() Observation: ['Accord' 'Camry' 'Altima' 'Corolla' 'Civic' 'F-150' 'Silverado' 'Impala'\n 'Malibu' 'Escape' 'Mustang' 'CRV' 'Maxima' 'Fusion' 'Charger']" 69 | } 70 | ], 71 | "2023-05-29 13:47:13.760822": [ 72 | { 73 | "Question": "list models", 74 | "Answer": "Accord, Camry, Altima, Corolla, Civic, F-150, Silverado, Impala, Malibu, Escape, Mustang, CRV, Maxima, Fusion, Charger", 75 | "Steps": "Thought: I need to find the models\nAction: python_repl_ast\nAction Input: df1['Model'].unique() Observation: ['Accord' 'Camry' 'Altima' 'Corolla' 'Civic' 'F-150' 'Silverado' 'Impala'\n 'Malibu' 'Escape' 'Mustang' 'CRV' 'Maxima' 'Fusion' 'Charger']" 76 | } 77 | ], 78 | "2023-05-29 13:49:21.513914": [ 79 | { 80 | "Question": "count the models per make and get highest and lowest price", 81 | "Answer": "Agent stopped due to iteration limit or time limit.", 82 | "Steps": " I need to find the correct file path\nAction: python_repl_ast\nAction Input: df1 = pd.read_csv('/Users/username/Documents/data/dataframes/df1.csv') Observation: FileNotFoundError: [Errno 2] No such file or directory: '/Users/username/Documents/data/dataframes/df1.csv'" 83 | } 84 | ], 85 | "2023-05-29 13:50:35.645462": [ 86 | { 87 | "Question": "count the models per make and get highest and lowest price, dataframe is df1", 88 | "Answer": "The highest and lowest price per make are: Chevrolet (min: 2340, max: 4959), Dodge (min: 2090, max: 4349), Ford (min: 2659, max: 4397), Honda (min: 2000, max: 4745), Nissan (min: 2914, max: 3826), Toyota (min: 2160, max: 2798).", 89 | "Steps": " I need to get the highest and lowest price\nAction: python_repl_ast\nAction Input: df1.groupby('Make')['Price'].agg(['min', 'max']) Observation: min max\nMake \nChevrolet 2340 4959\nDodge 2090 4349\nFord 2659 4397\nHonda 2000 4745\nNissan 2914 3826\nToyota 2160 2798" 90 | } 91 | ], 92 | "2023-05-29 13:53:25.471225": [ 93 | { 94 | "Question": "what are the most repeated models?", 95 | "Answer": "The most repeated models are Accord, Corolla, and Charger.", 96 | "Steps": " Now I can find the most repeated models\nAction: python_repl_ast\nAction Input: df['Model'].value_counts() Observation: Model\nAccord 3\nCorolla 3\nCharger 3\nAltima 2\nImpala 2\nEscape 2\nCamry 1\nCivic 1\nF-150 1\nSilverado 1\nMalibu 1\nMustang 1\nCRV 1\nMaxima 1\nFusion 1\nName: count, dtype: int64" 97 | } 98 | ], 99 | "2023-05-31 13:08:06.075219": [ 100 | { 101 | "Question": "plot histogram of the model", 102 | "Answer": "A histogram of the model is plotted.", 103 | "Steps": " I need to plot the data\nAction: python_repl_ast\nAction Input: df1['Model'].value_counts().plot(kind='hist') Observation: Axes(0.125,0.11;0.775x0.77)" 104 | } 105 | ], 106 | "2023-05-31 13:52:32.167934": [ 107 | { 108 | "Question": "plot the models in a bar chart", 109 | "Answer": "A bar chart of the models is plotted.", 110 | "Steps": " I need to plot the models in a bar chart\nAction: python_repl_ast\nAction Input: df1.groupby('Model').count()['Make'].plot(kind='bar') Observation: Axes(0.125,0.11;0.775x0.77)" 111 | } 112 | ], 113 | "2023-05-31 14:18:03.888640": [ 114 | { 115 | "Question": "what is your current working directory?", 116 | "Answer": "C:\\Users\\os-am\\OneDrive\\ML Projects\\PandasGPTAgent", 117 | "Steps": "Thought: I need to know the current working directory\nAction: python_repl_ast\nAction Input: import os; os.getcwd() Observation: C:\\Users\\os-am\\OneDrive\\ML Projects\\PandasGPTAgent" 118 | } 119 | ], 120 | "2023-05-31 14:18:43.808576": [ 121 | { 122 | "Question": "where do you save the charts you create?", 123 | "Answer": "The chart can be saved by using the command `plt.savefig('chart.png')` after importing matplotlib.pyplot as plt.", 124 | "Steps": " I can now save the figure\nAction: python_repl_ast\nAction Input: plt.savefig('chart.png') Observation: " 125 | } 126 | ], 127 | "2023-05-31 14:23:55.099902": [ 128 | { 129 | "Question": "plot car makes and color code them to accordingly with the price . If any charts or graphs or plots were created save them localy and include the save file names in your response.", 130 | "Answer": "The plot was saved as car_make_price_color.png.", 131 | "Steps": " I need to save the plot\nAction: python_repl_ast\nAction Input: plt.savefig('car_make_price_color.png') Observation: " 132 | } 133 | ], 134 | "2023-05-31 14:57:45.822912": [ 135 | { 136 | "Question": "how many rows do you have? . If any charts or graphs or plots were created save them localy and include the save file names in your response.", 137 | "Answer": "There are 24 rows in df1.", 138 | "Steps": " I need to specify which dataframe I'm looking at\nAction: python_repl_ast\nAction Input: len(df1) Observation: 24" 139 | } 140 | ], 141 | "2023-05-31 14:59:04.196851": [ 142 | { 143 | "Question": "plot car makes and color code them accordingly with the price . If any charts or graphs or plots were created save them localy and include the save file names in your response.", 144 | "Answer": "The plot was saved as 'car_makes_price_plot.png'.", 145 | "Steps": " I need to save the plot\nAction: python_repl_ast\nAction Input: plt.savefig('car_makes_price_plot.png') Observation: " 146 | } 147 | ], 148 | "2023-05-31 15:00:43.038476": [ 149 | { 150 | "Question": "count the models per make and get highest and lowest price . If any charts or graphs or plots were created save them localy and include the save file names in your response.", 151 | "Answer": "The models per make with the highest and lowest price are Chevrolet Impala (min: 2340, max: 3791), Dodge Charger (min: 2090, max: 4349), Ford Escape (min: 3196, max: 4397), Honda Accord (min: 2000, max: 4000), Nissan Altima (min: 2970, max: 3826), Toyota Camry (min: 2198, max: 2198), and Toyota Corolla (min: 2160, max: 2798). No charts or graphs were created.", 152 | "Steps": " I should now be able to group the data\nAction: python_repl_ast\nAction Input: df.groupby(['Make', 'Model'])['Price'].agg(['min', 'max']) Observation: min max\nMake Model \nChevrolet Impala 2340 3791\n Malibu 3361 3361\n Silverado 4959 4959\nDodge Charger 2090 4349\nFord Escape 3196 4397\n F-150 3950 3950\n Fusion 2659 2659\n Mustang 3706 3706\nHonda Accord 2000 4000\n CRV 4745 4745\n Civic 2723 2723\nNissan Altima 2970 3826\n Maxima 2914 2914\nToyota Camry 2198 2198\n Corolla 2160 2798" 153 | } 154 | ] 155 | } --------------------------------------------------------------------------------