3 Day 3: NumPy and pandas
3.1 Learning objectives
By the end of this day you should be able to:
- Create and operate on NumPy arrays and relate them to R vectors and matrices.
- Build a pandas
SeriesandDataFrameand relate them to the R tibble. - Select rows and columns with
locandilocand relate them todplyr::filteranddplyr::select. - Aggregate with
groupbyand relate it togroup_byandsummarize. - Combine tables with
mergeand relate it todplyrjoins. - Handle missing data and read a method chain as the counterpart of the pipe.
3.2 Lecture
We begin with NumPy, because pandas is built on it and because it restores the vectorized arithmetic an R user expects.
3.2.1 NumPy arrays versus R vectors and matrices
A NumPy array is a homogeneous, n-dimensional block of numbers. A one-dimensional array plays the role of an R atomic vector; a two-dimensional array plays the role of an R matrix. Unlike a Python list, an array vectorizes.
import numpy as np
x = np.array([1, 2, 3])
x * 2 # array([2, 4, 6]), elementwise
x.mean() # 2.0x <- c(1, 2, 3)
x * 2 # 2 4 6
mean(x)We note that array indexing is zero-based and that broadcasting, NumPy’s rule for combining arrays of different shapes, is the analogue of R’s recycling but stricter.
3.2.2 pandas Series and DataFrame versus the tibble
A pandas Series is a one-dimensional labeled array; a DataFrame is a two-dimensional table of columns, each a Series. The DataFrame is the working counterpart of the tibble, with one notable addition: an explicit row index.
import pandas as pd
df = pd.DataFrame({
'id': [1, 2, 3],
'arm': ['A', 'B', 'A'],
'age': [34, 41, 29],
})
df.head()df <- tibble::tibble(
id = c(1, 2, 3),
arm = c('A', 'B', 'A'),
age = c(34, 41, 29)
)The pandas row index is a feature with no clean tibble counterpart. Many surprises for R users trace back to an index that is carried along silently; reset_index() is often the fix.
3.2.3 Indexing with loc and iloc versus filter and select
pandas separates label-based selection (loc) from position-based selection (iloc). Row filtering uses a boolean mask, which reads much like dplyr::filter; column selection by name reads like dplyr::select.
df.loc[df['age'] > 30] # filter rows
df.loc[:, ['id', 'arm']] # select columns
df.iloc[0:2] # first two rows by positiondplyr::filter(df, age > 30)
dplyr::select(df, id, arm)3.2.4 groupby versus group_by and summarize
The split-apply-combine pattern an R user knows from group_by plus summarize is groupby plus an aggregation in pandas.
df.groupby('arm')['age'].mean() # mean age by armdf |>
dplyr::group_by(arm) |>
dplyr::summarize(age = mean(age))3.2.5 merge versus joins
Table joins use merge, whose how argument names the join type an R user knows from dplyr::left_join and its relatives.
pd.merge(df, sites, on='id', how='left')dplyr::left_join(df, sites, by = 'id')3.2.6 Missing data
pandas marks missing values with NaN (and, in newer types, pd.NA), the counterpart of R’s NA. We note that most pandas aggregations skip missing values by default, which matches the behavior of R functions called with na.rm = TRUE.
df['age'].isna().sum() # count missing
df['age'].fillna(df['age'].mean()) # impute with the mean3.2.7 Method chaining versus the pipe
Where R threads a data frame through dplyr verbs with the pipe, pandas threads a DataFrame through methods with a chain. Wrapping the chain in parentheses lets it span lines readably.
result = (
df
.loc[df['age'] > 30]
.groupby('arm')['age']
.mean()
)result <- df |>
dplyr::filter(age > 30) |>
dplyr::group_by(arm) |>
dplyr::summarize(age = mean(age))Read a pandas method chain exactly as you read a dplyr pipeline: top to bottom, one verb per line. The parentheses are only there to let the chain break across lines.
3.3 Worked example: a grouped summary end to end
We take a task an R user would write as a dplyr pipeline, computing mean age by arm among subjects over thirty, and build it in pandas so the correspondence is explicit line by line.
The R version the reader already knows:
enroll |>
dplyr::filter(age > 30) |>
dplyr::group_by(arm) |>
dplyr::summarize(mean_age = mean(age), n = dplyr::n())The pandas translation, built as a chain:
summary = (
enroll
.loc[enroll['age'] > 30]
.groupby('arm')
.agg(mean_age=('age', 'mean'), n=('age', 'size'))
.reset_index()
)
summaryWe note two points of friction for an R user. First, the grouped result carries arm in its index, so we call reset_index() to return it to an ordinary column, recovering a tibble-shaped result. Second, the named aggregation syntax agg(mean_age=('age', 'mean')) is the pandas counterpart of naming summaries inside summarize. With those two mappings in hand, the pipeline and the chain say the same thing.
3.4 Homework
Attempt each problem before checking the solution. Use a small DataFrame of your own construction unless one is given.
Array arithmetic. Create a NumPy array of the integers 1 through 10 and compute its mean and standard deviation. Show the R vector code that does the same.
Build a DataFrame. Construct a
DataFramewith columnsid,site, andweightfor five subjects. Display its shape and its column dtypes, and name the tibble code that corresponds.loc versus iloc. From your
DataFrame, select the rows whereweightexceeds its mean usingloc, and the first three rows usingiloc. Explain the difference between label-based and position-based selection.groupby. Compute the mean
weightbysite. Then add a count of subjects per site in the same result. Show thedplyrequivalent.merge. Build a second table mapping
siteto a region and left-join it onto your subject table. Predict what happens to a subject whose site is absent from the mapping.Missing data. Set one
weighttoNaN. Count the missing values, then compute the mean with and without the missing value included, and relate the behavior tona.rm.Method chain. Rewrite problems 3 through 5 as a single parenthesized method chain and compare its readability with the equivalent
dplyrpipeline.
3.5 Solutions
3.6 What’s next
Day 4 turns to the scientific stack: plotting with matplotlib, seaborn, and plotnine as the counterpart of ggplot2; modeling with the statsmodels formula API as the counterpart of lm and glm; the basics of scikit-learn; scipy.stats; and reticulate for calling Python from R.