Build a Python Data Analyzer with Pandas
pandas is Python’s standard library for working with tabular data, the kind that lives in spreadsheets, CSV files, and database tables. It is built on NumPy and gives you two core objects: a Series, which is a single labeled column of values, and a DataFrame, which is a table of Series that share one row index. Instead of looping over rows, you apply an operation to a whole column at once, so df["quantity"] * df["unit_price"] multiplies every row in a single expression. Missing values are stored as NaN and handled with dedicated methods such as isna(), fillna(), and dropna(). A typical analysis therefore follows one flow: load the data, inspect it, filter it, group it, visualize it, and clean it. This article builds a small sales analyzer that follows exactly that flow.
| Object | What it is | Example |
|---|---|---|
| Series | One labeled column of values | df["revenue"] |
| DataFrame | A table of columns that share a row index | pd.read_csv("sales.csv") |
1. Code Example
1.1 Install the dependencies
The analyzer needs two packages: pandas for the data work and matplotlib for the chart.
pip install pandas matplotlib
pandas does the loading, filtering, grouping, and cleaning, while matplotlib is used only for drawing and saving the chart. Everything else in the script comes from pandas itself, so there is nothing else to install.
1.2 The data analyzer
Save the following as analyzer.py. It creates a small sales dataset, with two cells left empty on purpose, and then works through it one step at a time.
import pandas as pd
import matplotlib.pyplot as plt
# 1. Create a dataset and save it as a CSV file (two cells are left empty on purpose)
data = {
"order_id": [101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112],
"region": ["North", "South", "East", "West", "North", "South",
"East", "West", "North", "South", "East", "West"],
"product": ["Laptop", "Phone", "Tablet", "Laptop", "Phone", "Tablet",
"Laptop", "Phone", "Tablet", "Laptop", "Phone", "Tablet"],
"quantity": [5, 12, 8, 3, 10, None, 7, 15, 6, 4, 9, 11],
"unit_price": [899.99, 599.5, 349.0, 899.99, 599.5, 349.0,
None, 599.5, 349.0, 899.99, 599.5, 349.0],
}
pd.DataFrame(data).to_csv("sales.csv", index=False)
# 2. Load the dataset and add a calculated column
df = pd.read_csv("sales.csv")
df["revenue"] = df["quantity"] * df["unit_price"]
# 3. View the data
print("=== First 5 rows ===")
print(df.head())
print("\n=== Shape (rows, columns) ===")
print(df.shape)
print("\n=== Column types and non-null counts ===")
df.info()
print("\n=== Summary statistics ===")
print(df[["quantity", "unit_price", "revenue"]].describe().round(2))
# 4. Filter rows
print("\n=== Orders from the North region ===")
print(df[df["region"] == "North"])
print("\n=== Laptop orders with quantity >= 4 ===")
print(df[(df["product"] == "Laptop") & (df["quantity"] >= 4)])
# 5. Find specific values
print("\n=== Look up order 105 ===")
print(df.loc[df["order_id"] == 105])
print("\n=== Highest-revenue order ===")
print(df.loc[df["revenue"].idxmax(), ["order_id", "region", "product", "revenue"]])
# 6. Group and aggregate
by_region = df.groupby("region")["revenue"].sum().sort_values(ascending=False)
print("\n=== Revenue by region ===")
print(by_region)
print("\n=== Summary by product ===")
print(df.groupby("product").agg(
orders=("order_id", "count"),
units=("quantity", "sum"),
avg_price=("unit_price", "mean"),
).round(2))
# 7. Visualize
by_region.plot(kind="bar", color="steelblue", title="Revenue by Region")
plt.ylabel("Revenue ($)")
plt.tight_layout()
plt.savefig("revenue_by_region.png")
plt.close()
print("\nChart saved to revenue_by_region.png")
# 8. Handle missing data
print("\n=== Missing values per column ===")
print(df.isna().sum())
df["quantity"] = df["quantity"].fillna(df["quantity"].median())
df = df.dropna(subset=["unit_price"])
df["revenue"] = df["quantity"] * df["unit_price"]
print("\n=== After cleaning: shape ===")
print(df.shape)
print("\n=== Revenue by region after cleaning ===")
print(df.groupby("region")["revenue"].sum().sort_values(ascending=False))
Step 1 builds a dictionary of equal-length lists and turns it into a DataFrame, and to_csv(index=False) writes it out without the row numbers; the None entries become empty CSV cells. Step 2 reads the file back with read_csv(), where the empty cells return as NaN and the quantity and unit_price columns become floats, and then adds a revenue column computed across all rows at once. Step 3 inspects the table: head() shows the first rows, shape gives rows and columns, info() lists each column’s type and non-null count, and describe() gives count, mean, spread, and quartiles. Step 4 filters with boolean masks: df["region"] == "North" yields one True/False per row, and wrapping it in df[...] keeps only the True rows, while the second filter joins two conditions with &, which requires each condition to sit in its own parentheses. Step 5 finds specific values, with loc[] returning the row where order_id equals 105 and idxmax() returning the index label of the largest revenue so loc[] can pull that whole row. Step 6 groups: groupby("region")["revenue"].sum() totals revenue per region and sort_values() orders the result, and agg() computes several named statistics per product in one call. Step 7 plots that grouped Series as a bar chart with plot(kind="bar") and writes it to a PNG with savefig() (swap in plt.show() to open a window instead). Step 8 deals with the gaps: isna().sum() counts the missing values per column, fillna() fills the missing quantity with the column median, dropna(subset=["unit_price"]) removes the row whose price is unknown because a revenue figure cannot be guessed, and the revenue column and regional totals are then recalculated.
1.3 Run the analyzer
Run the script from the folder where you saved it. It writes sales.csv and revenue_by_region.png next to itself.
python analyzer.py
The script takes no arguments and needs no setup beyond the two installed packages. Each section prints its own heading, so the output reads in the same order as the steps above.
1.4 Output
Running the script prints the following.
=== First 5 rows ===
order_id region product quantity unit_price revenue
0 101 North Laptop 5.0 899.99 4499.95
1 102 South Phone 12.0 599.50 7194.00
2 103 East Tablet 8.0 349.00 2792.00
3 104 West Laptop 3.0 899.99 2699.97
4 105 North Phone 10.0 599.50 5995.00
=== Shape (rows, columns) ===
(12, 6)
=== Column types and non-null counts ===
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 12 entries, 0 to 11
Data columns (total 6 columns):
# Column Non-Null Count Dtype
--- ------ -------------- -----
0 order_id 12 non-null int64
1 region 12 non-null object
2 product 12 non-null object
3 quantity 11 non-null float64
4 unit_price 11 non-null float64
5 revenue 10 non-null float64
dtypes: float64(3), int64(1), object(2)
memory usage: 704.0+ bytes
=== Summary statistics ===
quantity unit_price revenue
count 11.00 11.00 10.00
mean 8.18 590.36 4710.19
std 3.66 228.25 2191.61
min 3.00 349.00 2094.00
25% 5.50 349.00 2993.99
50% 8.00 599.50 4169.48
75% 10.50 749.74 5845.12
max 15.00 899.99 8992.50
=== Orders from the North region ===
order_id region product quantity unit_price revenue
0 101 North Laptop 5.0 899.99 4499.95
4 105 North Phone 10.0 599.50 5995.00
8 109 North Tablet 6.0 349.00 2094.00
=== Laptop orders with quantity >= 4 ===
order_id region product quantity unit_price revenue
0 101 North Laptop 5.0 899.99 4499.95
6 107 East Laptop 7.0 NaN NaN
9 110 South Laptop 4.0 899.99 3599.96
=== Look up order 105 ===
order_id region product quantity unit_price revenue
4 105 North Phone 10.0 599.5 5995.0
=== Highest-revenue order ===
order_id 108
region West
product Phone
revenue 8992.5
Name: 7, dtype: object
=== Revenue by region ===
region
West 15531.47
North 12588.95
South 10793.96
East 8187.50
Name: revenue, dtype: float64
=== Summary by product ===
orders units avg_price
product
Laptop 4 19.0 899.99
Phone 4 46.0 599.50
Tablet 4 25.0 349.00
Chart saved to revenue_by_region.png
=== Missing values per column ===
order_id 0
region 0
product 0
quantity 1
unit_price 1
revenue 2
dtype: int64
=== After cleaning: shape ===
(11, 6)
=== Revenue by region after cleaning ===
region
West 15531.47
South 13585.96
North 12588.95
East 8187.50
Name: revenue, dtype: float64
The most useful lines are the missing-data ones. In the info() table, quantity and unit_price each show 11 non-null values out of 12, while revenue shows only 10 because a NaN in either input makes the product NaN; that is also why order 107 appears in the laptop filter with NaN revenue. The first regional totals are quietly too low, because sum() skips NaN rather than warning you: South shows 10793.96 and East 8187.50. After cleaning, South rises to 13585.96 because its missing quantity was filled with the median of 8 (8 × 349.00 = 2792.00 added back), while East is unchanged because its row with the missing price was dropped, so the shape falls from (12, 6) to (11, 6). Checking isna().sum() before you trust an aggregate is the habit this example is meant to build.
2. Common Mistakes to Avoid
Most pandas bugs come from a handful of habits, and several of them can silently give you a wrong answer instead of an error.
| Mistake | What happens | Fix |
|---|---|---|
Using and / or to combine filters | Raises ValueError: The truth value of a Series is ambiguous | Use & / | and put each condition in its own parentheses, as in Step 4 |
Assigning through a chained filter, such as df[df["a"] > 1]["b"] = 0 | pandas warns with SettingWithCopyWarning and the original DataFrame is left unchanged | Select and assign in one step: df.loc[df["a"] > 1, "b"] = 0 |
Calling df.dropna() or df.fillna() without keeping the result | The call returns a new DataFrame, so the original keeps all its missing values | Assign it back, as in df = df.dropna(subset=["unit_price"]) |
| Aggregating before checking for missing values | sum() and mean() skip NaN without a warning, so totals are quietly too low | Run isna().sum() first, as in Step 8, and decide how to treat each gap |
Filling every gap with 0 | The values [10, NaN, 30] have a true mean of 20.0, but after fillna(0) the mean drops to 13.33 | Pick a fill per column, such as the median for a numeric column, or drop the row when the value cannot be estimated |
Forgetting index=False in to_csv() | The row numbers are saved as a column and come back as Unnamed: 0 when the file is reloaded | Always write to_csv("file.csv", index=False), as in Step 1 |
3. Conclusion
This analyzer covers the core pandas workflow end to end: create or load a dataset, inspect it with head(), info(), and describe(), filter with boolean masks, look up specific rows with loc[], summarize with groupby(), draw a chart, and repair missing data with fillna() and dropna(). The key lesson is that missing values change your results without raising an error, so inspect them before you aggregate. To take it further, point read_csv() at a real file, join a second table with merge(), reshape results with pivot_table(), or export the cleaned data with to_csv() or to_excel().


