Python

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.

ObjectWhat it isExample
SeriesOne labeled column of valuesdf["revenue"]
DataFrameA table of columns that share a row indexpd.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.

MistakeWhat happensFix
Using and / or to combine filtersRaises ValueError: The truth value of a Series is ambiguousUse & / | and put each condition in its own parentheses, as in Step 4
Assigning through a chained filter, such as df[df["a"] > 1]["b"] = 0pandas warns with SettingWithCopyWarning and the original DataFrame is left unchangedSelect and assign in one step: df.loc[df["a"] > 1, "b"] = 0
Calling df.dropna() or df.fillna() without keeping the resultThe call returns a new DataFrame, so the original keeps all its missing valuesAssign it back, as in df = df.dropna(subset=["unit_price"])
Aggregating before checking for missing valuessum() and mean() skip NaN without a warning, so totals are quietly too lowRun isna().sum() first, as in Step 8, and decide how to treat each gap
Filling every gap with 0The values [10, NaN, 30] have a true mean of 20.0, but after fillna(0) the mean drops to 13.33Pick 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 reloadedAlways 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().

Yatin Batra

An experience full-stack engineer well versed with Core Java, Spring/Springboot, MVC, Security, AOP, Frontend (Angular & React), and cloud technologies (such as AWS, GCP, Jenkins, Docker, K8).
Subscribe
Notify of
guest

This site uses Akismet to reduce spam. Learn how your comment data is processed.

0 Comments
Oldest
Newest Most Voted
Back to top button