import pandas as pd
import numpy as np11 Pandas DataFrame
(CSE331) Python for Data Science
A pandas DataFrame stores data in a table with labelled rows and columns. Different columns can hold different types, such as numbers and text.
Many operations will feel familiar from R’s tibble and dplyr, although pandas has its own syntax and indexing rules. We will practise with the Palmer Penguins data.
1 Creating a DataFrame
A dictionary of equal-length lists can supply the columns:
employee_dict = {
"eid": [214, 174, 126, 227, 151, 175, 193, 146],
"name": ["Drew", "Faye", "Beth", "Chad", "Hana", "Gary", "Alex", "Emma"],
"age": [25, 19, 42, 31, 25, 28, 31, 25],
"rate": [11.50, 12.30, 14.60, 12.50, None, 15.60, 13.50, 14.30],
"hours": [38, 32, 40, 29, 40, 36, 24, 20],
}
employee_df = pd.DataFrame(employee_dict)
employee_df.head()| eid | name | age | rate | hours | |
|---|---|---|---|---|---|
| 0 | 214 | Drew | 25 | 11.5 | 38 |
| 1 | 174 | Faye | 19 | 12.3 | 32 |
| 2 | 126 | Beth | 42 | 14.6 | 40 |
| 3 | 227 | Chad | 31 | 12.5 | 29 |
| 4 | 151 | Hana | 25 | NaN | 40 |
The default row labels are 0, 1, 2, and so on. The unrecorded rate is represented as a missing value.
2 Importing Data
2.2 Other formats: reference
# Excel: openpyxl is commonly used for .xlsx; xlrd for older .xls files.
df = pd.read_excel("data.xlsx", sheet_name="Sheet1")
dfs = pd.read_excel("data.xlsx", sheet_name=None) # dictionary of all sheets
# Stata
df = pd.read_stata("data.dta")
# SPSS and SAS: requires pyreadstat
import pyreadstat
df, metadata = pyreadstat.read_sav("data.sav")
df, metadata = pyreadstat.read_sas7bdat("data.sas7bdat")
# R files: requires pyreadr; result is a mapping of object names to data
import pyreadr
result = pyreadr.read_r("file.RData")These optional readers are needed only for the corresponding file formats.
3 Penguins Data
Use the course CSV. If it is unavailable, download the Palmer Penguins source CSV and save it as data/penguins.csv relative to the working directory.
The examples use the source column names, with lowercase sex labels male and female. Each row represents one penguin.
penguins = pd.read_csv("data/penguins.csv")
penguins.head()| species | island | bill_length_mm | bill_depth_mm | flipper_length_mm | body_mass_g | sex | year | |
|---|---|---|---|---|---|---|---|---|
| 0 | Adelie | Torgersen | 39.1 | 18.7 | 181.0 | 3750.0 | male | 2007 |
| 1 | Adelie | Torgersen | 39.5 | 17.4 | 186.0 | 3800.0 | female | 2007 |
| 2 | Adelie | Torgersen | 40.3 | 18.0 | 195.0 | 3250.0 | female | 2007 |
| 3 | Adelie | Torgersen | NaN | NaN | NaN | NaN | NaN | 2007 |
| 4 | Adelie | Torgersen | 36.7 | 19.3 | 193.0 | 3450.0 | female | 2007 |
Inspect column types and nonmissing counts:
penguins.info()<class 'pandas.core.frame.DataFrame'>
RangeIndex: 344 entries, 0 to 343
Data columns (total 8 columns):
# Column Non-Null Count Dtype
--- ------ -------------- -----
0 species 344 non-null object
1 island 344 non-null object
2 bill_length_mm 342 non-null float64
3 bill_depth_mm 342 non-null float64
4 flipper_length_mm 342 non-null float64
5 body_mass_g 342 non-null float64
6 sex 333 non-null object
7 year 344 non-null int64
dtypes: float64(4), int64(1), object(3)
memory usage: 21.6+ KB
Summarize the numeric columns:
penguins.describe()| bill_length_mm | bill_depth_mm | flipper_length_mm | body_mass_g | year | |
|---|---|---|---|---|---|
| count | 342.000000 | 342.000000 | 342.000000 | 342.000000 | 344.000000 |
| mean | 43.921930 | 17.151170 | 200.915205 | 4201.754386 | 2008.029070 |
| std | 5.459584 | 1.974793 | 14.061714 | 801.954536 | 0.818356 |
| min | 32.100000 | 13.100000 | 172.000000 | 2700.000000 | 2007.000000 |
| 25% | 39.225000 | 15.600000 | 190.000000 | 3550.000000 | 2007.000000 |
| 50% | 44.450000 | 17.300000 | 197.000000 | 4050.000000 | 2008.000000 |
| 75% | 48.500000 | 18.700000 | 213.000000 | 4750.000000 | 2009.000000 |
| max | 59.600000 | 21.500000 | 231.000000 | 6300.000000 | 2009.000000 |
In describe(), count excludes missing values and std is the sample standard deviation. The source CSV also includes a year column; a numeric summary is not necessarily meaningful for every numeric variable.
4 Accessing Rows and Columns
Use .iloc[] for integer positions and .loc[] for labels:
print(penguins.iloc[2, 3]) # third row, fourth column
print(penguins.loc[4, "island"]) # row labelled 4, column named island18.0
Torgersen
Here the default row labels happen to match their positions. After filtering or sorting, that may no longer be true.
df.iloc[row_positions, column_positions]
df.loc[row_labels_or_condition, column_labels]Omitting the column selector means all columns. For example, df.loc[1] selects the row labelled 1.
4.1 Selecting columns
One pair of brackets selects a Series, a labelled one-dimensional object:
species = penguins["species"]
species.head()0 Adelie
1 Adelie
2 Adelie
3 Adelie
4 Adelie
Name: species, dtype: object
A list of column names selects a DataFrame, even for one column:
penguins[["species"]].head()| species | |
|---|---|
| 0 | Adelie |
| 1 | Adelie |
| 2 | Adelie |
| 3 | Adelie |
| 4 | Adelie |
penguins[["species", "island", "sex", "bill_length_mm", "body_mass_g"]].head()| species | island | sex | bill_length_mm | body_mass_g | |
|---|---|---|---|---|---|
| 0 | Adelie | Torgersen | male | 39.1 | 3750.0 |
| 1 | Adelie | Torgersen | female | 39.5 | 3800.0 |
| 2 | Adelie | Torgersen | female | 40.3 | 3250.0 |
| 3 | Adelie | Torgersen | NaN | NaN | NaN |
| 4 | Adelie | Torgersen | female | 36.7 | 3450.0 |
The same selection can be written using .loc:
penguins.loc[:, ["species", "sex"]].head()| species | sex | |
|---|---|---|
| 0 | Adelie | male |
| 1 | Adelie | female |
| 2 | Adelie | female |
| 3 | Adelie | NaN |
| 4 | Adelie | female |
4.2 Label slices versus position slices
Label slices include both endpoints when present. Position slices exclude the stop position.
penguins.loc[1:3, "bill_length_mm":"body_mass_g"]| bill_length_mm | bill_depth_mm | flipper_length_mm | body_mass_g | |
|---|---|---|---|---|
| 1 | 39.5 | 17.4 | 186.0 | 3800.0 |
| 2 | 40.3 | 18.0 | 195.0 | 3250.0 |
| 3 | NaN | NaN | NaN | NaN |
This selects labels 1, 2, and 3, and the columns from bill_length_mm through body_mass_g in their existing order.
penguins.iloc[1:3, 2:6]| bill_length_mm | bill_depth_mm | flipper_length_mm | body_mass_g | |
|---|---|---|---|---|
| 1 | 39.5 | 17.4 | 186.0 | 3800.0 |
| 2 | 40.3 | 18.0 | 195.0 | 3250.0 |
This selects row positions 1 and 2, and column positions 2 through 5.
5 Filtering Rows
A Boolean condition selects rows where it is true:
males = penguins[penguins["sex"] == "male"]
males.shape(168, 8)
Combine conditions using & or |, with parentheses around each comparison:
# Male penguins with body mass above 3500 g
selected = (penguins["sex"] == "male") & (penguins["body_mass_g"] > 3500)
penguins.loc[selected, ["species", "sex", "body_mass_g"]].head()| species | sex | body_mass_g | |
|---|---|---|---|
| 0 | Adelie | male | 3750.0 |
| 5 | Adelie | male | 3650.0 |
| 7 | Adelie | male | 4675.0 |
| 13 | Adelie | male | 3800.0 |
| 14 | Adelie | male | 4400.0 |
An alternative is .query():
penguins.query("sex == 'female' and body_mass_g > 3500").head()| species | island | bill_length_mm | bill_depth_mm | flipper_length_mm | body_mass_g | sex | year | |
|---|---|---|---|---|---|---|---|---|
| 1 | Adelie | Torgersen | 39.5 | 17.4 | 186.0 | 3800.0 | female | 2007 |
| 6 | Adelie | Torgersen | 38.9 | 17.8 | 181.0 | 3625.0 | female | 2007 |
| 15 | Adelie | Torgersen | 36.6 | 17.8 | 185.0 | 3700.0 | female | 2007 |
| 22 | Adelie | Biscoe | 35.9 | 19.2 | 189.0 | 3800.0 | female | 2007 |
| 25 | Adelie | Biscoe | 35.3 | 18.9 | 187.0 | 3800.0 | female | 2007 |
The word and is allowed inside a query string. For ordinary Series comparisons outside that string, use &.
6 Arranging and Slicing Rows
6.1 Sorting
penguins.sort_values(by="body_mass_g", ascending=False).head()| species | island | bill_length_mm | bill_depth_mm | flipper_length_mm | body_mass_g | sex | year | |
|---|---|---|---|---|---|---|---|---|
| 169 | Gentoo | Biscoe | 49.2 | 15.2 | 221.0 | 6300.0 | male | 2007 |
| 185 | Gentoo | Biscoe | 59.6 | 17.0 | 230.0 | 6050.0 | male | 2007 |
| 269 | Gentoo | Biscoe | 48.8 | 16.2 | 222.0 | 6000.0 | male | 2009 |
| 229 | Gentoo | Biscoe | 51.1 | 16.3 | 220.0 | 6000.0 | male | 2008 |
| 231 | Gentoo | Biscoe | 45.2 | 16.4 | 223.0 | 5950.0 | male | 2008 |
penguins.sort_values(
by=["species", "bill_length_mm"],
ascending=[True, False],
).head()| species | island | bill_length_mm | bill_depth_mm | flipper_length_mm | body_mass_g | sex | year | |
|---|---|---|---|---|---|---|---|---|
| 19 | Adelie | Torgersen | 46.0 | 21.5 | 194.0 | 4200.0 | male | 2007 |
| 73 | Adelie | Torgersen | 45.8 | 18.9 | 197.0 | 4150.0 | male | 2008 |
| 111 | Adelie | Biscoe | 45.6 | 20.3 | 191.0 | 4600.0 | male | 2009 |
| 43 | Adelie | Dream | 44.1 | 19.7 | 196.0 | 4400.0 | male | 2007 |
| 129 | Adelie | Torgersen | 44.1 | 18.0 | 210.0 | 4000.0 | male | 2009 |
The second example sorts species alphabetically, then bill length from largest to smallest within each species. These calls return sorted results; they do not change penguins unless the result is assigned. Missing values are placed last by default.
6.2 Selecting positions and sampling rows
penguins.iloc[:6] # first six rows| species | island | bill_length_mm | bill_depth_mm | flipper_length_mm | body_mass_g | sex | year | |
|---|---|---|---|---|---|---|---|---|
| 0 | Adelie | Torgersen | 39.1 | 18.7 | 181.0 | 3750.0 | male | 2007 |
| 1 | Adelie | Torgersen | 39.5 | 17.4 | 186.0 | 3800.0 | female | 2007 |
| 2 | Adelie | Torgersen | 40.3 | 18.0 | 195.0 | 3250.0 | female | 2007 |
| 3 | Adelie | Torgersen | NaN | NaN | NaN | NaN | NaN | 2007 |
| 4 | Adelie | Torgersen | 36.7 | 19.3 | 193.0 | 3450.0 | female | 2007 |
| 5 | Adelie | Torgersen | 39.3 | 20.6 | 190.0 | 3650.0 | male | 2007 |
penguins.iloc[9:15] # 10th through 15th rows, counting from 1| species | island | bill_length_mm | bill_depth_mm | flipper_length_mm | body_mass_g | sex | year | |
|---|---|---|---|---|---|---|---|---|
| 9 | Adelie | Torgersen | 42.0 | 20.2 | 190.0 | 4250.0 | NaN | 2007 |
| 10 | Adelie | Torgersen | 37.8 | 17.1 | 186.0 | 3300.0 | NaN | 2007 |
| 11 | Adelie | Torgersen | 37.8 | 17.3 | 180.0 | 3700.0 | NaN | 2007 |
| 12 | Adelie | Torgersen | 41.1 | 17.6 | 182.0 | 3200.0 | female | 2007 |
| 13 | Adelie | Torgersen | 38.6 | 21.2 | 191.0 | 3800.0 | male | 2007 |
| 14 | Adelie | Torgersen | 34.6 | 21.1 | 198.0 | 4400.0 | male | 2007 |
R uses one-based positions; Python uses zero-based positions. The stop value in .iloc slicing is excluded.
penguins.sample(n=5, random_state=42)| species | island | bill_length_mm | bill_depth_mm | flipper_length_mm | body_mass_g | sex | year | |
|---|---|---|---|---|---|---|---|---|
| 194 | Gentoo | Biscoe | 45.3 | 13.7 | 210.0 | 4300.0 | female | 2008 |
| 157 | Gentoo | Biscoe | 46.5 | 13.5 | 210.0 | 4550.0 | female | 2007 |
| 225 | Gentoo | Biscoe | 46.5 | 14.8 | 217.0 | 5200.0 | female | 2008 |
| 208 | Gentoo | Biscoe | 43.8 | 13.9 | 208.0 | 4300.0 | female | 2008 |
| 318 | Chinstrap | Dream | 50.9 | 19.1 | 196.0 | 3550.0 | male | 2008 |
random_state makes this sample reproducible for the same data and setup. Row sampling is without replacement by default.
7 Pulling a Column
masses = penguins["body_mass_g"] # Series
species_list = penguins["species"].tolist() # Python list
print(type(masses), type(species_list))
print(species_list[:6])<class 'pandas.core.series.Series'> <class 'list'>
['Adelie', 'Adelie', 'Adelie', 'Adelie', 'Adelie', 'Adelie']
8 Creating and Modifying Columns
Use a working copy so that the imported data remain available:
peng = penguins.copy()
peng = peng.assign(bill_ratio=peng["bill_length_mm"] / peng["bill_depth_mm"])
peng[["bill_length_mm", "bill_depth_mm", "bill_ratio"]].head()| bill_length_mm | bill_depth_mm | bill_ratio | |
|---|---|---|---|
| 0 | 39.1 | 18.7 | 2.090909 |
| 1 | 39.5 | 17.4 | 2.270115 |
| 2 | 40.3 | 18.0 | 2.238889 |
| 3 | NaN | NaN | NaN |
| 4 | 36.7 | 19.3 | 1.901554 |
Direct assignment is another way to create or replace a column:
peng["bill_ratio"] = peng["bill_length_mm"] / peng["bill_depth_mm"]8.1 Recoding with np.select()
np.select() chooses the result for the first true condition, similar to case_when() in dplyr.
conditions = [
peng["body_mass_g"].isna(),
peng["body_mass_g"] < 3500,
peng["body_mass_g"] < 4500,
peng["body_mass_g"] >= 4500,
]
choices = ["missing", "light", "medium", "heavy"]
peng["mass_category"] = np.select(conditions, choices, default="unknown")
peng[["body_mass_g", "mass_category"]].head(10)| body_mass_g | mass_category | |
|---|---|---|
| 0 | 3750.0 | medium |
| 1 | 3800.0 | medium |
| 2 | 3250.0 | light |
| 3 | NaN | missing |
| 4 | 3450.0 | light |
| 5 | 3650.0 | medium |
| 6 | 3625.0 | medium |
| 7 | 4675.0 | heavy |
| 8 | 3475.0 | light |
| 9 | 4250.0 | medium |
Values below 3500 are classified before the next condition is considered. Thus medium covers \(3500 \leq x < 4500\). Use .isna() to identify missing values rather than an equality comparison.
8.2 Binning with pd.cut()
For numeric intervals, pd.cut() is a concise alternative:
peng["mass_bin"] = pd.cut(
peng["body_mass_g"],
bins=[0, 3500, 4500, np.inf],
labels=["light", "medium", "heavy"],
right=False,
)
peng[["body_mass_g", "mass_bin"]].head(10)| body_mass_g | mass_bin | |
|---|---|---|
| 0 | 3750.0 | medium |
| 1 | 3800.0 | medium |
| 2 | 3250.0 | light |
| 3 | NaN | NaN |
| 4 | 3450.0 | light |
| 5 | 3650.0 | medium |
| 6 | 3625.0 | medium |
| 7 | 4675.0 | heavy |
| 8 | 3475.0 | light |
| 9 | 4250.0 | medium |
right=False creates intervals [0, 3500), [3500, 4500), and [4500, infinity). For the positive masses in this dataset, the boundaries match the np.select() example. Unlike its explicit "missing" label, pd.cut() leaves missing input as missing. Values outside the supplied bins also become missing.
9 Renaming Columns
peng = peng.rename(columns={
"bill_length_mm": "bill_length",
"bill_depth_mm": "bill_depth",
"body_mass_g": "mass_g",
})
peng.columnsIndex(['species', 'island', 'bill_length', 'bill_depth', 'flipper_length_mm',
'mass_g', 'sex', 'year', 'bill_ratio', 'mass_category', 'mass_bin'],
dtype='object')
From here onward, examples using peng use the shorter names. The original penguins still uses the imported names.
10 Counting Values
penguins["species"].value_counts()species
Adelie 152
Gentoo 124
Chinstrap 68
Name: count, dtype: int64
Count combinations of categories and return a DataFrame:
penguins.value_counts(["species", "sex"]).reset_index(name="n")| species | sex | n | |
|---|---|---|---|
| 0 | Adelie | female | 73 |
| 1 | Adelie | male | 73 |
| 2 | Gentoo | male | 61 |
| 3 | Gentoo | female | 58 |
| 4 | Chinstrap | male | 34 |
| 5 | Chinstrap | female | 34 |
Missing categories are omitted by default. Use dropna=False in value_counts() when they should be included.
11 Grouping and Aggregation
groupby().agg() combines the roles of group_by() and summarise():
peng.groupby("species", as_index=False).agg(
n=("mass_g", "size"),
n_mass=("mass_g", "count"),
mean_bill_length=("bill_length", "mean"),
mean_mass=("mass_g", "mean"),
)| species | n | n_mass | mean_bill_length | mean_mass | |
|---|---|---|---|---|---|
| 0 | Adelie | 152 | 151 | 38.791391 | 3700.662252 |
| 1 | Chinstrap | 68 | 68 | 48.833824 | 3733.088235 |
| 2 | Gentoo | 124 | 123 | 47.504878 | 5076.016260 |
Each output name is paired with (column, summary_function). as_index=False keeps the grouping label as a regular column.
sizecounts all rows in the group;countcounts nonmissing entries in the selected column.mean,std, andmedianexclude missing values.stduses denominator \(n-1\) by default.- Missing grouping labels are excluded by default;
groupby(..., dropna=False)includes them.
peng.groupby(["species", "island"], as_index=False).agg(
n=("mass_g", "size"),
mean_mass=("mass_g", "mean"),
sd_mass=("mass_g", "std"),
median_bill=("bill_length", "median"),
).sort_values(by="n", ascending=False)| species | island | n | mean_mass | sd_mass | median_bill | |
|---|---|---|---|---|---|---|
| 4 | Gentoo | Biscoe | 124 | 5076.016260 | 504.116237 | 47.30 |
| 3 | Chinstrap | Dream | 68 | 3733.088235 | 384.335081 | 49.55 |
| 1 | Adelie | Dream | 56 | 3688.392857 | 455.146437 | 38.55 |
| 2 | Adelie | Torgersen | 52 | 3706.372549 | 445.107940 | 38.90 |
| 0 | Adelie | Biscoe | 44 | 3709.659091 | 487.733722 | 38.70 |
11.1 Keeping whole groups
Keep species with at least 100 recorded masses:
grouped = peng.groupby("species").filter(
lambda group: group["mass_g"].notna().sum() >= 100
)
grouped["species"].unique()array(['Adelie', 'Gentoo'], dtype=object)
Here .filter() keeps every row of a qualifying group. It does not select rows individually. The function receives one group at a time and returns a Boolean.
12 Row and Column Summaries
Select numeric columns before calculating their means:
peng.select_dtypes("number").mean()bill_length 43.921930
bill_depth 17.151170
flipper_length_mm 200.915205
mass_g 4201.754386
year 2008.029070
bill_ratio 2.605649
dtype: float64
A small table makes the axis argument easy to see:
M = pd.DataFrame(np.arange(12).reshape(3, 4))
M| 0 | 1 | 2 | 3 | |
|---|---|---|---|---|
| 0 | 0 | 1 | 2 | 3 |
| 1 | 4 | 5 | 6 | 7 |
| 2 | 8 | 9 | 10 | 11 |
M.sum(axis=0) # one sum per column0 12
1 15
2 18
3 21
dtype: int64
M.sum(axis=1) # one sum per row0 6
1 22
2 38
dtype: int64
13 Method Chaining
A chain combines several operations into one readable sequence. Parentheses allow the expression to span multiple lines.
summary = (
peng
.dropna(subset=["mass_g", "bill_length", "bill_depth"])
.assign(bill_ratio=lambda df: df["bill_length"] / df["bill_depth"])
.groupby("species", as_index=False)
.agg(
mean_mass=("mass_g", "mean"),
mean_bill_ratio=("bill_ratio", "mean"),
)
)
summary| species | mean_mass | mean_bill_ratio | |
|---|---|---|---|
| 0 | Adelie | 3700.662252 | 2.119726 |
| 1 | Chinstrap | 3733.088235 | 2.653756 |
| 2 | Gentoo | 5076.016260 | 3.175592 |
This first removes rows missing any required measurement, then calculates the ratio and group summaries. The lambda in assign() uses the DataFrame at that stage of the chain. This example assumes positive bill depths, as in these data.
14 Combining DataFrames
pd.concat() stacks rows or places columns side by side. It aligns labels on the other axis, which matters when the inputs differ.
14.1 Stacking rows
a = penguins.iloc[:2]
b = penguins.iloc[2:4]
pd.concat([a, b], axis=0, ignore_index=True)| species | island | bill_length_mm | bill_depth_mm | flipper_length_mm | body_mass_g | sex | year | |
|---|---|---|---|---|---|---|---|---|
| 0 | Adelie | Torgersen | 39.1 | 18.7 | 181.0 | 3750.0 | male | 2007 |
| 1 | Adelie | Torgersen | 39.5 | 17.4 | 186.0 | 3800.0 | female | 2007 |
| 2 | Adelie | Torgersen | 40.3 | 18.0 | 195.0 | 3250.0 | female | 2007 |
| 3 | Adelie | Torgersen | NaN | NaN | NaN | NaN | NaN | 2007 |
axis=0 adds rows and aligns columns by name. ignore_index=True gives the result a fresh row index. Columns absent from one input receive missing values for those rows; dtypes may change to accommodate the combined data.
14.2 Placing columns side by side
x = penguins[["bill_length_mm"]]
y = penguins[["bill_depth_mm"]]
pd.concat([x, y], axis=1).head()| bill_length_mm | bill_depth_mm | |
|---|---|---|
| 0 | 39.1 | 18.7 |
| 1 | 39.5 | 17.4 |
| 2 | 40.3 | 18.0 |
| 3 | NaN | NaN |
| 4 | 36.7 | 19.3 |
axis=1 adds columns and aligns rows by index label, not simply by their current position. Here both inputs come from the same rows, so they align directly.
15 Useful R Equivalents
| tidyverse | pandas |
|---|---|
select() |
df[["col1", "col2"]] |
filter() |
df[condition] or df.query() |
arrange() |
df.sort_values() |
mutate() |
Column assignment or df.assign() |
case_when() |
np.select() |
| Numeric interval binning | pd.cut() |
rename() |
df.rename(columns={...}) |
pull() |
df["col"] or .tolist() |
slice() |
df.iloc[...] |
group_by() + summarise() |
df.groupby().agg() |
These are practical analogies; indexing and missing-value behaviour still need attention.
16 Exercises
Start with the following working data. Questions 1–8 are independent: use practice as the starting point each time. Question 9 uses the renamed result from Question 7.
practice = penguins.rename(columns={
"bill_length_mm": "bill_length",
"bill_depth_mm": "bill_depth",
"body_mass_g": "mass_g",
}).copy()- Select
species,island,bill_length,bill_depth, andmass_g. - Select Adelie penguins with mass above 3500 g. Show
species,island, andmass_g. - Sort by
mass_gdescending and display the first five rows. - Using
.iloc, select 10 rows centred in the dataset. Let the starting position be(len(practice) - 10) // 2. - Create
bill_area = bill_length * bill_depth. Treat it as a derived numerical measure rather than a measured anatomical area. - Create
size_category: below 3500 is"small"; 3500 to below 4500 is"medium"; at least 4500 is"large"; missing is"missing". - Rename
mass_gtobody_mass_gandbill_lengthtobill_len_mm. Save this result asrenamed. - Extract
speciesas a Python list. - Using
renamed, calculate the mean and sample SD ofbody_mass_gby species. Report both total rows and the number of recorded masses.
17 Summary
- Distinguish labels (
loc) from positions (iloc), and a Series from a DataFrame. - Use consistent column names as a workflow progresses.
- State bin boundaries and the treatment of missing data explicitly.
- Distinguish all-row counts from nonmissing counts in summaries.
- Remember that concatenation aligns labels on the other axis.