
Supercharge Your Tabular Data with Pandas and Generative AI
What if you could add the power of Generative AI, Amazon Bedrock, and Amazon Nova foundation models to your data workflows?
Pandas is the go-to Python library for data analysis, enabling you to slice, dice, and transform your data at scale. But what if you could go a step further and add the power of Generative AI to your data workflows?
A few people asked me how I sling tabular data, Pandas, and generative AI. So I thought I'd write a post about it.
In this post, I'll walk through how to combine Pandas with a generative AI model served through Amazon Bedrock and orchestrated by LangChain AWS . You will start by installing necessary packages, then I'll show you how to create a sample dataset, do some feature engineering, and finally apply generative AI transformations to your
DataFrame. By the end, you’ll be able to supercharge your data analysis with machine-generated insights, summaries, and even structured output to extract new columns from rich text columns.Prerequisites
- Python 3.7+
- AWS Credentials with proper permissions to access Amazon Bedrock (if you’re running locally)
- Jupyter Notebook (optional but recommended for interactivity)
- Familiarity with Pandas (if you're not, I recommend reading the Pandas documentation )
- Enabled access to the foundation model (Amazon Nova Lite) in your AWS account. See Amazon Bedrock for more information.
Step 1: Install the necessary packages:
%pip install langchain-aws pandas langchain-core boto3 pydanticStep 2: Setup a synthetic dataset
We're going to create a synthetic dataset with 10 rows and 8 columns. We'll use the
numpy library to generate random data for each column.Don't worry to much about the code for generating the dataset. We'll use the data later to apply generative AI transformations to the dataset. At this step, you would otherwise use
pd.read_csv() or another Pandas method to read your structured data.In brief, we're creating a dataset with the following columns:
bedrooms: Randomly generated number of bedrooms between 1 and 5bathrooms: Randomly generated number of bathrooms between 1 and 3sqft: Randomly generated square footage between 500 and 3500location: Randomly generated location from a list of citiesview: Randomly generated view from a list of viewsstyle: Randomly generated style from a list of stylesyear_built: Randomly generated year built between 1900 and 2024condition: Randomly generated condition from a list of conditionsparking: Randomly generated parking from a list of parking optionspool: Randomly generated pool from a list of pool optionsprice: Loosely dependent on the other features of the house
Your dataset will look different if you change the random seed.
The price of each property is calculated using the following formula:
f(price) = base_price × location_multiplier × view_multiplier × condition_multiplier1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
import pandas as pd
import numpy as np
from datetime import datetime
np.random.seed(42)
source_df = pd.DataFrame({
"bedrooms": np.random.randint(1, 6, 10),
"bathrooms": np.random.randint(1, 4, 10),
"sqft": np.random.randint(500, 3500, 10),
"location": np.random.choice(
["New York", "Los Angeles", "Chicago", "Houston", "Miami"], 10
),
"view": np.random.choice(["park", "city", "ocean", "mountain", "lake"], 10),
"style": np.random.choice(
["modern", "traditional", "art deco", "mid-century", "modern farmhouse"], 10
),
"year_built": np.random.randint(datetime.now().year - 100, datetime.now().year, 10),
"condition": np.random.choice(["excellent", "good", "fair", "poor"], 10),
"parking": np.random.choice(["garage", "parking lot", "street", "driveway"], 10),
"pool": np.random.choice(["yes", "no"], 10),
})
# Let's say price is somewhat dependent on the other features
# Create location multipliers
location_multipliers = {
"New York": 2.0,
"Los Angeles": 1.8,
"Chicago": 1.2,
"Houston": 1.0,
"Miami": 1.5,
}
# Create view multipliers
view_multipliers = {
"ocean": 1.4,
"mountain": 1.2,
"city": 1.1,
"park": 1.05,
"lake": 1.15,
}
# Create condition multipliers
condition_multipliers = {"excellent": 1.3, "good": 1.1, "fair": 0.9, "poor": 0.7}
source_df["price"] = (
source_df["bedrooms"] * 100_000
+ source_df["bathrooms"] * 20_000
+ source_df["sqft"] * 300 / 10
) * (
source_df["location"].map(location_multipliers) # Location factor
* source_df["view"].map(view_multipliers) # View factor
* source_df["condition"].map(condition_multipliers) # Condition factor
* (1 + (2024 - source_df["year_built"]) * -0.002) # Age depreciation
).round()
source_df["price"] = source_df["price"].astype(int)
source_df.head()Step 3: Feature engineering
Let's add a derived feature to the dataset. We'll add a new column called
price_category that categorizes the house price into three categories: Low, Medium, and High automatically.1
2
3
4
5
6
7
source_df["price_category"] = pd.cut(
source_df["price"],
bins=3,
labels=["Low", "Medium", "High"],
)
# show High, Medium, Low examples
source_df.head()Step 4: Initialize the LLM model
We'll use ChatBedrockConverse from LangChain AWS to call Amazon Bedrock and use Amazon Nova Lite as the LLM.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import boto3
from langchain_aws.chat_models import ChatBedrockConverse
bedrock_client = boto3.client("bedrock-runtime")
# For more model information, see [Amazon Bedrock foundation model information](https://docs.aws.amazon.com/bedrock/latest/userguide/models-supported.html)
# The `us.` prefix enables Cross Region Inference for the model. For more information, see [Cross Region Inference](https://docs.aws.amazon.com/bedrock/latest/userguide/cross-region-inference.html)
model_id = "us.amazon.nova-lite-v1:0"
# Initialize the LLM model
llm = ChatBedrockConverse(
model=model_id,
client=bedrock_client,
max_tokens=1000,
temperature=1,
)
# Test to make sure you can invoke the model
response = llm.invoke("Hello, how are you?")
print(response.content)Step 5: Generate a property description for each row
Let’s say we want to use the model to generate human-readable property descriptions based on the features. We can define a function that takes a single DataFrame row, crafts a prompt, sends it to the LLM, and returns a generated description. We'll also use our global statistics to provide context to the LLM about overall trends in the dataset.
We'll use pandas
apply to apply the function to each row in the DataFrame.1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
def generate_property_description(row: pd.Series) -> str:
# unpack the columns into variables so we can use them in the prompt neatly
location, view, style, year_built, condition = row[
["location", "view", "style", "year_built", "condition"]
]
parking, pool, bedrooms, bathrooms, sqft, price = row[
["parking", "pool", "bedrooms", "bathrooms", "sqft", "price"]
]
prompt = f"""You are a helpful real estate agent. You are given the following property attributes:"
<property_attributes>
- {location=}\n"
- {view=}\n"
- {style=}\n"
- {year_built=}\n"
- {condition=}\n"
- {parking=}\n"
- {pool=}\n"
- {bedrooms=}\n"
- {bathrooms=}\n"
- {sqft=}\n"
- {price=}\n\n"
</property_attributes>
Using the content from <property_attributes></property_attributes>,
please write a short, appealing, and exciting real estate listing description of no more than 3 sentences."""
# Use the chat_model to get a completion
response = llm.invoke(prompt)
return response.content
# apply the function and store the result in a new column in a new dataframe
df_with_descriptions = source_df.copy(deep=True)
df_with_descriptions["description"] = df_with_descriptions.apply(
generate_property_description, axis=1
)Let's check out the result!
1
2
3
from IPython.display import HTML
display(HTML(df_with_descriptions.head().to_html()))Step 6: Use groupby to generate insights for groups of data
What if you want to generate insights for groups of data? For example, you might want to generate a summary of the dataset by location. You can use the
groupby method to group the data by location and then apply to use a generative AI transformation for each group.We’ll compute some global statistics about the dataset to provide context to the LLM. This will help our LLM produce richer summaries that consider how each group compares to the entire dataset.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# Numeric statistics DataFrame
numeric_stats = source_df.describe(include=[float, int])
# remove count row from numeric_stats
numeric_stats = numeric_stats.drop(index="count")
# Categorical column frequency DataFrame
cat_cols = source_df.select_dtypes(include=["object"]).columns
cat_freq = pd.concat(
[source_df[col].value_counts() for col in cat_cols],
keys=cat_cols,
names=["column", "value"],
).to_frame("frequency")
display(numeric_stats)
display(cat_freq)Let's create a new function that takes a group of data and generates a summary of the group. We'll use the
groupby method to group the data by location and then apply to use a generative AI transformation for each group.1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
def generate_group_summary(
grouped_by: str, group_name: str, group: pd.DataFrame
) -> str:
prompt = f"""You are a helpful real estate analyst. You are given the following real estate data:"
<global_stats>
- {numeric_stats.to_json()}
- {cat_freq.to_json()}
</global_stats>
<group_data>
- {grouped_by}: {group_name}
- {group.to_json()}
</group_data>
Use the content of <global_stats></global_stats> and <group_data></group_data>
to write a concise summary of the group and how it relates to the global statistics."""
response = llm.invoke(prompt)
return response.content
# apply the function and store the result in a new column in a new dataframe
group_summaries = source_df.groupby(
"location",
).apply(lambda x: generate_group_summary("location", x.name, x), include_groups=False)Drum roll...
1
2
3
from IPython.display import Markdown
display(Markdown(group_summaries.iloc[0]))Let's try another example. What if we want to generate a summary of the dataset by price category?
1
2
3
4
5
6
7
8
price_category_summaries = source_df.groupby(
"price_category",
observed=True,
).apply(
lambda x: generate_group_summary("price_category", x.name, x), include_groups=False
)
display(Markdown(price_category_summaries.iloc[0]))Bonus! Extract structured data from a long-form text column into new columns
Let's say we have a column with a long-form text description of a property and we want to extract structured data from it. We can use a generative AI model to do this. Conceptually, we will go the opposite direction of the previous examples. Instead of generating a summary or description, we will generate structured data from a text column.
Let's start by reviewing our description column and pretend that we are starting from only the description column.
1
2
3
4
descriptions_only = df_with_descriptions["description"].to_frame()
with pd.option_context("display.max_colwidth", None):
display(descriptions_only)We can use a generative AI model to extract structured data from the text column. We'll use the
ChatBedrockConverse and Pydantic to do this.1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
from pydantic import BaseModel, Field
class PropertyData(BaseModel):
"""
Extract the property data from the provided description.
"""
bedrooms: str = Field(description="The number of bedrooms in the property")
bathrooms: str = Field(description="The number of bathrooms in the property")
sqft: str = Field(description="The square footage of the property")
price: str = Field(description="The price of the property")
llm.temperature = 0
structured_output_llm = llm.with_structured_output(PropertyData)
regenerated_df = pd.concat(
[
descriptions_only,
pd.json_normalize(
descriptions_only["description"].apply(
lambda x: structured_output_llm.invoke(x).model_dump()
)
),
],
axis=1,
)
And check out the results!
1
2
with pd.option_context("display.max_colwidth", None):
display(regenerated_df.sort_values(by="price", ascending=False))Conclusion
In this post, you've seen how to use Pandas and Generative AI with Amazon Bedrock to supercharge your data analysis. You've seen how to generate summaries, descriptions, and structured data from a dataset. You've also seen how to use
groupby to generate insights for groups of data.I hope you found this post useful for your own projects. If you have any questions, please feel free to reach out to me on LinkedIn .
Enjoyed reading this content? Let the author know!
Your likes, comments, shares, and saves help creators reach more builders.
Loading recommendations
Loading article