5.2. Pandas Chart Types#

Hide code cell source

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns

np.random.seed(42)
df1 = pd.DataFrame({
    'A': np.arange(100),
    'B': np.random.randn(100).cumsum(),
    'C': np.random.randn(100).cumsum(),
})
df2 = pd.DataFrame({
    'a': np.random.rand(50),
    'b': np.random.rand(50),
    'c': np.random.rand(50),
})
df = pd.DataFrame(np.random.randn(1000, 2), columns=['a', 'b'])
tips = sns.load_dataset('tips')

5.2.1. Common Chart Types#

if "np" not in globals():
    import numpy as np
if "pd" not in globals():
    import pandas as pd
if "plt" not in globals():
    import matplotlib.pyplot as plt
if "sns" not in globals():
    import seaborn as sns

5.2.1.1. Line Plots#

flights = sns.load_dataset("flights")

### groupby() and aggregation: For each year, add up all the passenger counts from that year.
flights.groupby("year")["passengers"].sum().plot(
    kind="line",
    figsize=(5, 3),
    fontsize=8,
    title="Total Passengers per Year",
    marker="o"      # show data points
)
plt.tight_layout()
../../_images/bf4ff1e5307db88c00d8bfeed26fb5c0c27a0f74f411473ebf976686be33b511.png

5.2.1.2. Histograms#

tips = sns.load_dataset("tips")

# ax = tips["tip"].plot(        ### plot() returns the Axes object, 
                                ### so we can capture it in a variable
tips["tip"].plot(
    kind="hist",
    figsize=(4, 2.5),
    bins=20,
    edgecolor="white",
    alpha=0.8,
    fontsize=7,
    title="Distribution of Tips"
)

plt.xlabel("Tip ($)")
plt.ylabel("Frequency")
plt.tight_layout()
../../_images/5f91712909fd7469443038c6b2c389d7a6b720fee0d0f50a3b36ca72d2aeed84.png

5.2.1.3. Scatter Plot#

tips = sns.load_dataset("tips")

tips.plot(
    kind="scatter",
    x="total_bill", y="tip",
    alpha=0.5, fontsize=7,
    title="Basic",
    figsize=(4, 3),
    s=tips["size"] * 15     ### scale size by 'size' column
)
<Axes: title={'center': 'Basic'}, xlabel='total_bill', ylabel='tip'>
../../_images/2d0b3d0ec27d64e5962f84a3526b37edc9e27bfc6075c10242008b0b2de8c1da.png

5.2.1.4. Bar Plots#

Bar plots are one of the most common ways to compare categorical data visually. They represent quantities as rectangular bars whose length (or height) corresponds to the value being measured.

Both bar() and barh() create bar charts — the difference is simply orientation. Stacked bar charts, on the other hand, how multiple subcategories contribute to a total within each main category.

Plot Type

Orientation

Purpose

Best For

Bar Plot

Vertical

Compare category values

Simple category comparisons

Barh Plot

Horizontal

Compare category values with long labels

Readability and ranking-type data

Stacked Bar

Either

Show part-to-whole relationships

Composition of totals across categories

5.2.1.4.1. Vertical Bar Plot#

tips = sns.load_dataset("tips")
tips.info()
<class 'pandas.DataFrame'>
RangeIndex: 244 entries, 0 to 243
Data columns (total 7 columns):
 #   Column      Non-Null Count  Dtype   
---  ------      --------------  -----   
 0   total_bill  244 non-null    float64 
 1   tip         244 non-null    float64 
 2   sex         244 non-null    category
 3   smoker      244 non-null    category
 4   day         244 non-null    category
 5   time        244 non-null    category
 6   size        244 non-null    int64   
dtypes: category(4), float64(2), int64(1)
memory usage: 7.4 KB
### bar plot
tips.groupby("day", observed=True)["tip"].mean().plot(kind="bar", 
        figsize=(4, 3), edgecolor="white", alpha=0.9, fontsize=8)
<Axes: xlabel='day'>
../../_images/7c2aa7adc9dba432216832b103dea3528ebc62d361f138dd3db5729a1f985f47.png

5.2.1.4.2. Horizontal Bar Plot#

### bar plot horizontal

### Observed categories
### Use categories that are actually observed in the data

tips.groupby("day", observed=True)["tip"].mean().plot(kind="barh", 
        figsize=(4, 3), edgecolor="white", alpha=0.9, fontsize=8)
<Axes: ylabel='day'>
../../_images/0e664965a9e6f063a4c3999528361120006a2b9243e1859927b41d02048295b9.png

5.2.1.4.3. Stacked Bar Plot#

### titanic — survival count by gender and class
import seaborn as sns
titanic = sns.load_dataset("titanic")

titanic.groupby(["class", "sex"], observed=True)["survived"].mean()\
    .unstack().plot(kind="bar", 
    stacked=True,
    figsize=(4, 3), edgecolor="white", alpha=0.9, fontsize=8)
    

print(titanic.groupby(["class", "sex"], observed=True)["survived"].mean(), "\n")
print(titanic.groupby(["class", "sex"], observed=True)["survived"].mean().unstack())
class   sex   
First   female    0.968085
        male      0.368852
Second  female    0.921053
        male      0.157407
Third   female    0.500000
        male      0.135447
Name: survived, dtype: float64 

sex       female      male
class                     
First   0.968085  0.368852
Second  0.921053  0.157407
Third   0.500000  0.135447
../../_images/2f3d0fe794fa35c4657d301b04d4a2259888ba85343cc4d905852978f67f47f0.png

5.2.1.5. BoxPlots#

tips.boxplot(
    column="tip",
    by="sex",
    figsize=(4, 3),
    fontsize=8
)
plt.suptitle("")        # removes default "Boxplot grouped by sex" title
plt.title("Tip Distribution by Gender")
plt.xlabel("")
plt.ylabel("Tip ($)")
plt.grid(alpha=0.25)
plt.tight_layout()
../../_images/4fdbb133dfe08d7ac3458a8641740fede15ee9387299ff3e1e9a217aca12f03d.png

How to read each box:

  • The green line inside the box is the median tip: the middle value.

  • The bottom of the box is the 25th percentile: about 25% of tips are below this.

  • The top of the box is the 75th percentile: about 75% of tips are below this.

  • The box shows the middle 50% of tips.

  • The whiskers show the usual range of non-outlier values.

  • The circles are outliers: unusually high tip amounts.

5.2.1.6. Area Plot#

flights = sns.load_dataset("flights")

df = flights.pivot(index="year", columns="month", values="passengers")

df.plot(
    kind="area",
    figsize=(5, 3),
    alpha=0.5,
    fontsize=8,
    title="Passengers by Month"
)

plt.xlabel("Year")
plt.ylabel("Passengers")
plt.legend(title="Month", bbox_to_anchor=(1.05, 1), loc="upper left", fontsize=6)
plt.tight_layout()
../../_images/7064d352c1d167362c084e60cd03d25cef4f799e93e66db3c0bd017603346036.png

5.2.1.7. KDE Plot#

  • A Kernel Density Estimate (KDE) plot is smooth version of histogram.

  • Density tells you where values are concentrated. A higher curve means more values are packed in that region. The exact y-value doesn’t matter — the shape and peaks are what you read.

    • The peak shows where most values are concentrated

    • A wider curve means more spread in the data

    • The area under the curve always equals 1.0

    • The y-axis shows density — not count or percentage

# %pip install scipy    ### ensure scipy is installed; uncomment when done

# penguins — body mass or flipper length, clear multi-modal distribution
penguins = sns.load_dataset("penguins").drop(columns=\
    ["bill_length_mm", "bill_depth_mm", "flipper_length_mm"])
print(penguins.head())

penguins["body_mass_g"].plot(
    kind="kde", 
    title="Penguin Body Mass",
    figsize=(4,2.5)
    )
  species     island  body_mass_g     sex
0  Adelie  Torgersen       3750.0    Male
1  Adelie  Torgersen       3800.0  Female
2  Adelie  Torgersen       3250.0  Female
3  Adelie  Torgersen          NaN     NaN
4  Adelie  Torgersen       3450.0  Female
<Axes: title={'center': 'Penguin Body Mass'}, ylabel='Density'>
../../_images/8d6a8ed4d38193988d9903899c93ba37eae5619bea25c0c0448f3d1d093aceb8.png

We can also overlay KDE with histogram, which makes it clear that a KDE is a smoothed out version of histogram.

penguins = sns.load_dataset("penguins").drop(columns=\
    ["bill_length_mm", "bill_depth_mm", "flipper_length_mm"])

penguins["body_mass_g"].plot(
    kind="hist", bins=20,
    edgecolor="white", alpha=0.5,
    density=True,                   ### must be density=True to overlay KDE on histogram
    # title="Histogram",
    fontsize=8, 
    label="Histogram",
    figsize=(4, 2.5)
)

penguins["body_mass_g"].plot(
    kind="kde",
    # title="KDE",
    fontsize=8, 
    label="KDE"
)
plt.legend()
plt.tight_layout()
../../_images/5993920c3381a6e36cff5aedcf874734430edfa757adbf5f845fe3630319cb59.png

5.2.1.8. Hexagonal Bin Plot#

Hexagonal bin plots are useful for bivariate Data and is an alternative to scatterplot.

np.random.seed(42)
df = pd.DataFrame(np.random.randn(1000, 2), columns=['a', 'b'])
df.plot.hexbin(x='a', y='b', 
               gridsize=25, 
               cmap='Oranges',
               figsize=(5, 4),
            )
<Axes: xlabel='a', ylabel='b'>
../../_images/6a4d09132fd0503c5bdb11bcd1ff8aff3ad596932aef101689397f0e1eee1c5f.png
### EXERCISE 1: Histogram Fundamentals
# Goal: Visualize the distribution of `A` in `df1`.
# Requirements:
# 1) Plot a histogram for `df1['A']` with exactly 50 bins.
# 2) Set `figsize=(8, 4)` and `alpha=0.7`.
# 3) Add a title: "Distribution of A".
# Stretch: Add vertical dashed lines for the mean and median of `A`.

# Your code here

Hide code cell source

### Solution
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt

try:
    df1
except NameError:
    rng = np.random.default_rng(0)
    df1 = pd.DataFrame(rng.normal(size=(100, 3)), columns=['A', 'B', 'C'])

ax = df1['A'].plot(kind='hist', bins=50, figsize=(8, 4), alpha=0.7)
ax.axvline(df1['A'].mean(), linestyle='--', color='red', label='Mean')
ax.axvline(df1['A'].median(), linestyle='--', color='black', label='Median')
ax.set_title('Distribution of A')
ax.legend()
plt.show()
../../_images/180fe37f9e2374b8044ee370a973997b93eb5c5cc6e6b724a135e3b15ec14871.png
# Solution
import matplotlib.pyplot as plt
df1['A'].plot.hist(bins=50)
plt.show()
../../_images/b4c46f8012daf7c1e448c105bd00123d63fe3499eb68a2168e9ff735635a284d.png
### EXERCISE 2: Line Plot Styling
# Goal: Show trend behavior for one feature.
# Requirements:
# 1) Plot `df1['B']` as a line plot.
# 2) Use `figsize=(8, 4)` and `lw=2`.
# 3) Set x-label to "Index" and y-label to "B values".
# Stretch: Overlay a rolling mean with window=10 on the same axes.

# Your code here

Hide code cell source

### Solution
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt

try:
    df1
except NameError:
    rng = np.random.default_rng(0)
    df1 = pd.DataFrame(rng.normal(size=(100, 3)), columns=['A', 'B', 'C'])

ax = df1['B'].plot(kind='line', figsize=(8, 4), lw=2, label='B')
df1['B'].rolling(window=10).mean().plot(ax=ax, label='Rolling mean')
ax.set_xlabel('Index')
ax.set_ylabel('B values')
ax.legend()
plt.show()
../../_images/7df930200c4e0a3fe0a7a5040cea501aee09a64d1edcfc37abdb2e1dfe08b788.png
# Solution
df1.plot.line(y='B', figsize=(8,4), lw=2)
<Axes: >
../../_images/995d487da0c86fb83189f0746780722bcf6d932976cdc27d93b98efd2641fab4.png
### EXERCISE 3: Scatter Relationship
# Goal: Examine the relationship between `A` and `B`.
# Requirements:
# 1) Create a scatter plot with `x='A'` and `y='B'`.
# 2) Set `figsize=(7, 5)`.
# 3) Set title to "A vs B".
# Stretch: Add `alpha=0.6` and compare readability.

# Your code here

Hide code cell source

### Solution
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt

try:
    df1
except NameError:
    rng = np.random.default_rng(0)
    df1 = pd.DataFrame(rng.normal(size=(100, 3)), columns=['A', 'B', 'C'])

ax = df1.plot(kind='scatter', x='A', y='B', figsize=(7, 5), alpha=0.6)
ax.set_title('A vs B')
plt.show()
../../_images/4e87476f60a91e09235f7cfc574f80c9c83435f98c4912274647fb32e9cfe1bf.png

5.2.2. Section Practice#

Use this short exercise to check the main workflow from this section.

### Exercise: Plan a Pandas Chart Types Chart
# A team wants to compare sales across four regions.
# Create two lists named regions and sales. Then print the region with the highest sales.
### Your code starts here




### Your code ends here

Hide code cell source

### Solution
regions = ['North', 'South', 'East', 'West']
sales = [120, 145, 132, 118]
best_index = sales.index(max(sales))
print("Highest sales region:", regions[best_index])
Highest sales region: South
### Solution
regions = ["North", "South", "East", "West"]
sales = [240, 215, 260, 230]
best_index = sales.index(max(sales))
print(regions[best_index])
East