mtcars
Under the File tab, choose Save As… and save your own copy of this file.
Then replace Your name here at the top of the file with
your name.
This document is your analysis notebook. It combines code, results, and your written interpretations in one place — making your work reproducible. Someone else can read your report, see your code, and understand exactly how you made each figure.
Large biological datasets are often too big to understand by reading numbers in a table.
In DREAM-High, we will eventually use heatmaps to look for patterns in breast cancer gene expression data from The Cancer Genome Atlas. A heatmap can help us ask:
Today we practice the same idea using a small, familiar dataset.
Main idea: A heatmap turns numbers into colors so that hidden structure becomes easier to see.
Python is a free programming language widely used for data analysis, statistics, and visualization. In this course we will use Python to explore cancer-related datasets.
You do not need to memorize every command. Your goal is to learn how to:
RStudio is the application we are using to write and run code. We are working in an R Markdown file, which lets us combine text, code, results, and plots in one report.
When you click Knit, RStudio runs all the code and produces an HTML report.
Note: This notebook runs Python inside RStudio using a package called reticulate. The setup chunk at the top loads it automatically — you do not need to install anything.
R Markdown uses simple formatting:
# creates a large heading, ## a smaller
one**bold** and *italic* for emphasisinline codeThe only difference from the R version of this activity is that
chunks begin with {python} instead of {r}. You
run them the same way — click the green arrow.
mtcarsR has built-in datasets; Python does not, but mtcars is
freely available online. The chunk below loads it and prints its
dimensions.
import pandas as pd
# Load mtcars and keep only numeric columns
mtcars = pd.read_csv(
"https://vincentarelbundock.github.io/Rdatasets/csv/datasets/mtcars.csv",
index_col=0
)
mtcars = mtcars.select_dtypes(include="number")
# dim(mtcars) in R → .shape in Python
print(mtcars.shape) # (32, 11): 32 cars, 11 features
## (32, 11)
Each row is a car; each column is a feature:
mpg = miles per gallonwt = weighthp = horsepowercyl = number of cylinders# head(mtcars) in R → mtcars.head() in Python
print(mtcars.head())
## mpg cyl disp hp drat ... qsec vs am gear carb
## rownames ...
## Mazda RX4 21.0 6 160.0 110 3.90 ... 16.46 0 1 4 4
## Mazda RX4 Wag 21.0 6 160.0 110 3.90 ... 17.02 0 1 4 4
## Datsun 710 22.8 4 108.0 93 3.85 ... 18.61 1 1 4 1
## Hornet 4 Drive 21.4 6 258.0 110 3.08 ... 19.44 1 0 3 1
## Hornet Sportabout 18.7 8 360.0 175 3.15 ... 17.02 0 0 3 2
##
## [5 rows x 11 columns]
What do you notice first when you look at this table?
Write your answer here:
When I first looked at this table, I noticed that the range of values in the data was really big. This made it hard for me to directly compare the relationships between different features just by looking.
Before making a plot, scientists usually inspect the data.
# dim(mtcars) in R → .shape in Python
print(mtcars.shape)
## (32, 11)
print()
# colnames(mtcars) in R → .columns in Python
print(mtcars.columns.tolist())
## ['mpg', 'cyl', 'disp', 'hp', 'drat', 'wt', 'qsec', 'vs', 'am', 'gear', 'carb']
print()
# class(mtcars) in R → type() in Python
print(type(mtcars))
## <class 'pandas.core.frame.DataFrame'>
View the entire dataset:
View(mtcars)
Write your answer here:
There are a total of 32 cars in the dataset (corresponding to 32 rows).Each car has 11 numerical features measured (corresponding to 11 columns).
The easiest way to get help on python functions is to move back to R
and use the function browseURL.
browseURL("https://seaborn.pydata.org/generated/seaborn.clustermap.html")
The heatmap() function in R works with a numeric matrix.
In Python, seaborn.clustermap works directly with a
DataFrame, but we convert to a NumPy array here to mirror the R
workflow.
import numpy as np
# as.matrix(mtcars) in R → .to_numpy() in Python
data = mtcars.to_numpy()
# class(data) in R → type() in Python
print(type(data))
## <class 'numpy.ndarray'>
# typeof(data) in R → .dtype in Python
print(data.dtype) # float64 = R's "double"
## float64
The assignment operator in Python is = (R uses
<-):
x = 5 # x <- 5 in R
print(x)
## 5
import seaborn as sns
import matplotlib.pyplot as plt
g = sns.clustermap(
mtcars,
figsize=(10, 12),
xticklabels=True,
yticklabels=True,
cbar=False
)
g.cax.remove()
# Remove the automatic axis titles
g.ax_heatmap.set_xlabel("")
g.ax_heatmap.set_ylabel("")
# Adjust tick label sizes
g.ax_heatmap.tick_params(axis="x", labelsize=9)
g.ax_heatmap.tick_params(axis="y", labelsize=8)
# Give the x-axis labels room
g.fig.subplots_adjust(bottom=0.15)
plt.show()
The heatmap turns numbers into colors and clusters:
The tree diagrams on the sides are called dendrograms.
Is it easy to see meaningful patterns? Why or why not?
Heat maps show us meaningful patterns, and it’s easy to spot them just by looking at the colors. Personally, I think colors convey features and situations more intuitively than numbers and text.
Let’s create a heatmap that allows us to find the true underlying patterns.
Some columns are measured on very different scales. Compare
cyl and disp:
# min(mtcars$cyl) in R → mtcars["cyl"].min() in Python
print("cyl: ", mtcars["cyl"].min(), "to", mtcars["cyl"].max())
## cyl: 4 to 8
print("disp:", mtcars["disp"].min(), "to", mtcars["disp"].max())
## disp: 71.1 to 472.0
A value of 8 is high for cyl but tiny for
disp. Without scaling, large-number columns dominate the
heatmap colors. The same problem appears in gene expression data.
What do you think will change after we scale the data?
I feel like this way all the features will be brought to the same scale. The heatmap will also show more complex and realistic clustering patterns.
scale() in R standardizes each column to a mean of 0 and
standard deviation of 1. We use the same function from the
sklearn library in Python.
from sklearn.preprocessing import scale
# scale(data) in R → scale(mtcars) from sklearn in Python
data_scaled = pd.DataFrame(
scale(mtcars),
index=mtcars.index,
columns=mtcars.columns
)
Let’s compare a few values before and after scaling.
# data[1:5, 1:5] in R → .iloc[0:5, 0:5] in Python (Python counts from 0)
print("Before scaling:")
## Before scaling:
print(mtcars.iloc[0:5, 0:5])
## mpg cyl disp hp drat
## rownames
## Mazda RX4 21.0 6 160.0 110 3.90
## Mazda RX4 Wag 21.0 6 160.0 110 3.90
## Datsun 710 22.8 4 108.0 93 3.85
## Hornet 4 Drive 21.4 6 258.0 110 3.08
## Hornet Sportabout 18.7 8 360.0 175 3.15
print()
print("After scaling:")
## After scaling:
print(data_scaled.iloc[0:5, 0:5])
## mpg cyl disp hp drat
## rownames
## Mazda RX4 0.153299 -0.106668 -0.579750 -0.543655 0.576594
## Mazda RX4 Wag 0.153299 -0.106668 -0.579750 -0.543655 0.576594
## Datsun 710 0.456737 -1.244457 -1.006026 -0.795570 0.481584
## Hornet 4 Drive 0.220730 -0.106668 0.223615 -0.543655 -0.981576
## Hornet Sportabout -0.234427 1.031121 1.059772 0.419550 -0.848562
Now let’s try the heatmap with data_scaled.
g = sns.clustermap(
data_scaled,
figsize=(10, 12),
xticklabels=True,
yticklabels=True,
cbar=False
)
g.cax.remove()
# Remove the automatic axis titles
g.ax_heatmap.set_xlabel("")
g.ax_heatmap.set_ylabel("")
# Adjust tick label sizes
g.ax_heatmap.tick_params(axis="x", labelsize=9)
g.ax_heatmap.tick_params(axis="y", labelsize=8)
# Give the x-axis labels room
g.fig.subplots_adjust(bottom=0.15)
plt.show()
mpg and wt show similar or opposite
patterns?
Yes, this heatmap clearly carries more information than the original data heatmap. Because the data has been standardized (scaled), the effects of different physical units have been eliminated, putting all features on the same comparative scale. 2.The features cyl (cylinders), disp (displacement), hp (horsepower), and wt (weight) obviously cluster together. 3.mpg (fuel efficiency) and wt (weight) show completely opposite patterns. In areas where the weight is higher, the color corresponding to fuel efficiency is noticeably lighter, visually confirming the physical rule that ‘the heavier the car, the more fuel it consumes.’
Yes, for example, heavy, high-fuel-consuming sports/luxury cars (like Maserati Bora, Ford Pantera L) naturally cluster together; while light, fuel-efficient compact family cars (like Honda Civic, Fiat 128) form another group.
We can change the color palette to make patterns easier to see. The chunk below shows a selection of available palettes.
# display.brewer.all() in R → color swatches in Python
palette_names = [
"Blues", "Greens", "Oranges", "Purples", "Reds",
"coolwarm", "rocket", "mako", "YlOrRd", "RdBu"
]
fig, axes = plt.subplots(len(palette_names), 1, figsize=(6, len(palette_names) * 0.6))
for ax, name in zip(axes, palette_names):
colors = sns.color_palette(name, 10)
ax.imshow([colors], aspect="auto")
ax.set_yticks([])
ax.set_xticks([])
ax.set_ylabel(name, rotation=0, labelpad=60, va="center", fontsize=9)
plt.tight_layout()
plt.show()
Now use a palette in a heatmap:
# col = brewer.pal(8, "Greens") in R → cmap="Greens" in Python
# Try changing "Greens" to another name from the list above
# col = brewer.pal(8, "Greens") in R → cmap="Greens" in Python
# Try changing "Greens" to another name from the list above
# Assign clustermap to a variable 'g' to access its axes
g = sns.clustermap(
data_scaled,
cmap="Greys",
figsize=(10, 12),
xticklabels=True,
yticklabels=True,
cbar=False
)
g.cax.remove()
# Remove the automatic axis titles
g.ax_heatmap.set_xlabel("")
g.ax_heatmap.set_ylabel("")
# Adjust tick label sizes
g.ax_heatmap.tick_params(axis="x", labelsize=9)
g.ax_heatmap.tick_params(axis="y", labelsize=8)
# Give the x-axis labels room
g.fig.subplots_adjust(bottom=0.15)
plt.show()
Replace "Greens" below with a palette of your choice and
run the chunk.
# Change "Greens" in the cmap to "Reds"
g = sns.clustermap(
data_scaled,
cmap="Reds",
figsize=(10, 12),
xticklabels=True,
yticklabels=True,
cbar=False
)
g.cax.remove()
# Remove the automatic axis titles
g.ax_heatmap.set_xlabel("")
g.ax_heatmap.set_ylabel("")
# Adjust tick label sizes
g.ax_heatmap.tick_params(axis="x", labelsize=9)
g.ax_heatmap.tick_params(axis="y", labelsize=8)
# Give the x-axis labels room
g.fig.subplots_adjust(bottom=0.15)
plt.show()
Which palette did you choose, and why?
Because I think bright colors like this are more eye-catching and clearer
Sometimes we care less about individual rows and more about how variables relate to one another.
Correlation refers to the statistical relationship between two variables (feature), describing how they change together. For example:
import pandas as pd
# Calculate a single correlation coefficient
correlation = mtcars["mpg"].corr(mtcars["wt"])
print("Correlation:", correlation)
## Correlation: -0.8676593765172279
This means that across cars, mpg decreases as weight increases. This matches our intuition. Consider the scatterplot where each point corresponds to a car:
import matplotlib.pyplot as plt
plt.figure(figsize=(6, 4))
plt.scatter(mtcars["wt"], mtcars["mpg"])
plt.xlabel("Weight")
plt.ylabel("Miles per Gallon")
plt.show()
A correlation matrix shows how strongly pairs of features move together.
# cor(mtcars) in R → .corr() in Python
cor_mtcars = mtcars.corr()
# round(cor_mtcars, 2) in R → .round(2) in Python
print(cor_mtcars.round(2))
## mpg cyl disp hp drat wt qsec vs am gear carb
## mpg 1.00 -0.85 -0.85 -0.78 0.68 -0.87 0.42 0.66 0.60 0.48 -0.55
## cyl -0.85 1.00 0.90 0.83 -0.70 0.78 -0.59 -0.81 -0.52 -0.49 0.53
## disp -0.85 0.90 1.00 0.79 -0.71 0.89 -0.43 -0.71 -0.59 -0.56 0.39
## hp -0.78 0.83 0.79 1.00 -0.45 0.66 -0.71 -0.72 -0.24 -0.13 0.75
## drat 0.68 -0.70 -0.71 -0.45 1.00 -0.71 0.09 0.44 0.71 0.70 -0.09
## wt -0.87 0.78 0.89 0.66 -0.71 1.00 -0.17 -0.55 -0.69 -0.58 0.43
## qsec 0.42 -0.59 -0.43 -0.71 0.09 -0.17 1.00 0.74 -0.23 -0.21 -0.66
## vs 0.66 -0.81 -0.71 -0.72 0.44 -0.55 0.74 1.00 0.17 0.21 -0.57
## am 0.60 -0.52 -0.59 -0.24 0.71 -0.69 -0.23 0.17 1.00 0.79 0.06
## gear 0.48 -0.49 -0.56 -0.13 0.70 -0.58 -0.21 0.21 0.79 1.00 0.27
## carb -0.55 0.53 0.39 0.75 -0.09 0.43 -0.66 -0.57 0.06 0.27 1.00
# heatmap(cor_mtcars, symm=TRUE) in R → sns.clustermap() in Python
# A diverging palette (blue = positive, red = negative) works well here
import seaborn as sns
import matplotlib.pyplot as plt
g = sns.clustermap(
cor_mtcars,
cmap="RdBu_r",
vmin=-1, vmax=1,
figsize=(5.5, 5.5), # smaller figure
dendrogram_ratio=0.12, # smaller dendrograms
cbar_pos=(0.02, 0.82, 0.04, 0.15) # smaller color bar
)
# Make the labels smaller
g.ax_heatmap.tick_params(axis='x', labelsize=9)
g.ax_heatmap.tick_params(axis='y', labelsize=9)
plt.show()
Find one pair of features with a strong positive relationship and one with a strong negative relationship.
A strong positive relationship is between cyl and disp, since they both increase together. A strong negative relationship is between mpg and. wt, since heavier cars tend to have lower mpg
Today we used cars because the dataset is small and easy to practice with. The same workflow applies to biological data:
patients or samples → rows
genes or features → columns
expression values → numbers in the matrix
heatmap → visual pattern discovery
clustering → grouping similar samples or genes
In breast cancer data, heatmaps can reveal groups of patients with similar molecular patterns — patterns that sometimes correspond to tumor subtypes with different clinical outcomes.
1.Yes, it is much more informative. Standardizing the data removes scale differences, making it easier to compare patterns and variations across all features. 2.Features like cyl, disp, hp, and wt group together because they show similar color patterns, reflecting related engine and design characteristics. 3.They show opposite patterns. Cars with higher weight (wt) consistently have lower fuel efficiency (mpg). 4.Yes, they make sense. Similar types of cars—such as heavy sports cars or small fuel-efficient compacts—are clustered together logically.
Click Knit → Knit to HTML. Your report should include your name, your code, your heatmaps, and your answers to all the reflection questions.
In this activity you practiced:
= for assignment and .iloc[] for
indexingComputational tools can help us discover patterns hidden inside large biological datasets.
| Task | R | Python |
|---|---|---|
| Load data | built-in mtcars |
pd.read_csv("url", index_col=0) |
| First few rows | head(mtcars) |
print(mtcars.head()) |
| Dimensions | dim(mtcars) |
print(mtcars.shape) |
| Column names | colnames(mtcars) |
print(mtcars.columns.tolist()) |
| Object class | class(x) |
print(type(x)) |
| Data type | typeof(x) |
print(x.dtype) |
| Convert to matrix | as.matrix(mtcars) |
mtcars.to_numpy() |
| Assignment | x <- 5 |
x = 5 |
| Index rows & cols | data[1:5, 1:5] |
data.iloc[0:5, 0:5] |
| Column min / max | min(mtcars$cyl) |
mtcars["cyl"].min() |
| Scale columns | scale(data) |
scale(mtcars) from sklearn |
| Correlation matrix | cor(mtcars) |
mtcars.corr() |
| Round values | round(x, 2) |
x.round(2) |
| Heatmap | heatmap(data) |
sns.clustermap(data) |
| Color palette | col = brewer.pal(8, "Greens") |
cmap="Greens" |
| Comment | # text |
# text |
| Get help | help(heatmap) |
help(sns.clustermap) |