17  pandas 中的变量转换

17.1 介绍

在数据分析中,最常见的任务之一是转换数据集中的变量。pandas 库提供了简洁高效的方法来完成这项任务。

17.2 学习目标

  • 理解如何在 DataFrame 中创建新变量。
  • 学习如何修改现有变量。
  • 处理修改视图上变量的潜在问题。

17.3 导入

首先,让我们导入 pandas 包:

import pandas as pd

现在我们将设置一个重要选项,这将帮助我们避免后续的一些警告。在课程的后面部分,我们会更详细地讨论这个选项。

pd.options.mode.copy_on_write = True

17.4 数据集

在本课中,我们将使用包含人口和经济数据的美国县的数据集。您可以通过以下链接下载该数据集:https://github.com/the-graph-courses/idap_book/raw/refs/heads/main/data/us_counties_data.zip

下载文件后,解压缩并将 us_counties_data.csv 文件放在项目的 data 文件夹中。

counties = pd.read_csv("data/us_counties_data.csv")
counties
state county pop_20 area_sq_miles hh_inc_21 econ_type unemp_20 foreign_born_num pop_change_2010_2020 pct_emp_change_2010_2021
0 AL Autauga, AL 58877.0 594.456107 66444.0 Nonspecialized 5.4 1241.0 7.758700 9.0
1 AL Baldwin, AL 233140.0 1589.836014 65658.0 Recreation 6.2 7938.0 27.159356 28.2
... ... ... ... ... ... ... ... ... ... ...
3224 PR Yabucoa, PR 30364.0 55.214614 NaN NaN NaN NaN -19.807069 0.1
3225 PR Yauco, PR 34062.0 67.711484 NaN NaN NaN NaN -18.721309 -5.3

3226 rows × 10 columns

数据集中的变量包括:

  • state: 美国州
  • county: 美国县
  • pop_20: 2020 年人口估计
  • area_sq_miles: 平方英里面积
  • hh_inc_21: 2021 年家庭收入中位数
  • econ_type: 县的经济类型
  • pop_change_2010_2020: 2010 年至 2020 年人口变化(%)
  • unemp_20: 2020 年失业率(%)
  • pct_emp_change_2010_2021: 2010 年至 2021 年就业变化百分比(%)
  • foreign_born_num: 外国出生居民数量

让我们创建一个仅包含面积和人口列的数据集子集作为示例。

# Small subset for illustration
area_df = counties[["county", "area_sq_miles", "pop_20"]]
area_df
county area_sq_miles pop_20
0 Autauga, AL 594.456107 58877.0
1 Baldwin, AL 1589.836014 233140.0
... ... ... ...
3224 Yabucoa, PR 55.214614 30364.0
3225 Yauco, PR 67.711484 34062.0

3226 rows × 3 columns

17.5 创建新变量

假设我们想将面积从平方英里转换为平方公里。由于 1 平方英里约等于 2.59 平方公里,我们可以通过将 area_sq_miles 列乘以 2.59 来创建一个新变量 area_sq_km

area_df["area_sq_km"] = area_df["area_sq_miles"] * 2.59
area_df
county area_sq_miles pop_20 area_sq_km
0 Autauga, AL 594.456107 58877.0 1539.641317
1 Baldwin, AL 1589.836014 233140.0 4117.675277
... ... ... ... ...
3224 Yabucoa, PR 55.214614 30364.0 143.005851
3225 Yauco, PR 67.711484 34062.0 175.372743

3226 rows × 4 columns

语法非常易于理解,虽然有点难以输入。

使用 area_df["area_sq_km"],我们表示要创建一个名为 area_sq_km 的新列,然后 area_df["area_sq_miles"] * 2.59 是计算该新列值的表达式。

让我们再添加一个变量,这次以公顷为单位。转换因子是 1 平方英里 = 259 公顷。

# Convert area to hectares as well
area_df["area_hectares"] = area_df["area_sq_miles"] * 259
area_df
county area_sq_miles pop_20 area_sq_km area_hectares
0 Autauga, AL 594.456107 58877.0 1539.641317 153964.131747
1 Baldwin, AL 1589.836014 233140.0 4117.675277 411767.527703
... ... ... ... ... ...
3224 Yabucoa, PR 55.214614 30364.0 143.005851 14300.585058
3225 Yauco, PR 67.711484 34062.0 175.372743 17537.274254

3226 rows × 5 columns

练习

17.5.1 练习题:以英亩为单位的面积

使用 area_df 数据集,通过将 area_sq_miles 变量乘以 640,创建一个名为 area_acres 的新列。将结果存回 area_df 并显示 DataFrame。

# Your code here

17.6 修改现有变量

假设我们想将 area_sq_km 变量四舍五入到小数点后一位。我们可以在 area_sq_km 列上调用 round 方法。

area_df["area_sq_km"] = area_df["area_sq_km"].round(1)
area_df
county area_sq_miles pop_20 area_sq_km area_hectares
0 Autauga, AL 594.456107 58877.0 1539.6 153964.131747
1 Baldwin, AL 1589.836014 233140.0 4117.7 411767.527703
... ... ... ... ... ...
3224 Yabucoa, PR 55.214614 30364.0 143.0 14300.585058
3225 Yauco, PR 67.711484 34062.0 175.4 17537.274254

3226 rows × 5 columns

练习

17.6.1 练习题:对 area_acres 进行四舍五入

使用 area_df 数据集,将 area_acres 变量四舍五入到小数点后一位。更新 DataFrame area_df 并显示它。

# Your code here

17.7 多变量计算

我们可以基于多个现有变量创建新变量。

例如,让我们计算每平方公里的人口密度。

area_df["pop_per_sq_km"] = area_df["pop_20"] / area_df["area_sq_km"]
area_df
county area_sq_miles pop_20 area_sq_km area_hectares pop_per_sq_km
0 Autauga, AL 594.456107 58877.0 1539.6 153964.131747 38.241751
1 Baldwin, AL 1589.836014 233140.0 4117.7 411767.527703 56.618986
... ... ... ... ... ... ...
3224 Yabucoa, PR 55.214614 30364.0 143.0 14300.585058 212.335664
3225 Yauco, PR 67.711484 34062.0 175.4 17537.274254 194.196123

3226 rows × 6 columns

我们可以将在此输出后添加 round 方法,将结果四舍五入到小数点后一位。

area_df["pop_per_sq_km"] = (area_df["pop_20"] / area_df["area_sq_km"]).round(1)
area_df
county area_sq_miles pop_20 area_sq_km area_hectares pop_per_sq_km
0 Autauga, AL 594.456107 58877.0 1539.6 153964.131747 38.2
1 Baldwin, AL 1589.836014 233140.0 4117.7 411767.527703 56.6
... ... ... ... ... ... ...
3224 Yabucoa, PR 55.214614 30364.0 143.0 14300.585058 212.3
3225 Yauco, PR 67.711484 34062.0 175.4 17537.274254 194.2

3226 rows × 6 columns

或者,如果您愿意,您可以将其分两步完成:

area_df["pop_per_sq_km"] = area_df["pop_20"] / area_df["area_sq_km"]
area_df["pop_per_sq_km"] = area_df["pop_per_sq_km"].round(1)
area_df
county area_sq_miles pop_20 area_sq_km area_hectares pop_per_sq_km
0 Autauga, AL 594.456107 58877.0 1539.6 153964.131747 38.2
1 Baldwin, AL 1589.836014 233140.0 4117.7 411767.527703 56.6
... ... ... ... ... ... ...
3224 Yabucoa, PR 55.214614 30364.0 143.0 14300.585058 212.3
3225 Yauco, PR 67.711484 34062.0 175.4 17537.274254 194.2

3226 rows × 6 columns

在计算人口密度之后,我们可能希望根据这个新变量对 DataFrame 进行排序。让我们按降序排序。

# Sort by population density in descending order
area_df = area_df.sort_values("pop_per_sq_km", ascending=False)
area_df
county area_sq_miles pop_20 area_sq_km area_hectares pop_per_sq_km
1863 New York, NY 22.656266 1687834.0 58.7 5867.972888 28753.6
1856 Kings, NY 69.376570 2727393.0 179.7 17968.531752 15177.5
... ... ... ... ... ... ...
98 Wrangell-Petersburg, AK NaN NaN NaN NaN NaN
2921 Bedford, VA NaN NaN NaN NaN NaN

3226 rows × 6 columns

我们看到纽约县在数据集中拥有最高的人口密度。

练习

17.7.1 练习题:计算外籍出生人口百分比

使用 counties 数据集计算每个县的外籍出生居民百分比。变量 foreign_born_num 显示外籍出生居民的数量,pop_20 显示总人口。按外籍出生居民百分比的降序对 DataFrame 进行排序。哪两个县的外籍出生居民比例最高?

# Your code here

17.8 创建布尔变量

有时创建布尔变量以根据条件对数据进行分类或标记是很有用的。布尔变量是仅取两个值的变量:TrueFalse

考虑 counties 数据集中的 pop_change_2010_2020 变量,它显示了 2010 年至 2020 年之间的人口百分比变化。

changes_df = counties[["county", "pop_change_2010_2020", "pct_emp_change_2010_2021"]]
changes_df
county pop_change_2010_2020 pct_emp_change_2010_2021
0 Autauga, AL 7.758700 9.0
1 Baldwin, AL 27.159356 28.2
... ... ... ...
3224 Yabucoa, PR -19.807069 0.1
3225 Yauco, PR -18.721309 -5.3

3226 rows × 3 columns

我们可能想创建一个布尔变量来标记人口是否增加。为此,如果人口增加,我们将 pop_increase 变量设为 True,否则设为 False

运行表达式 changes_df["pop_change_2010_2020"] > 0 会返回一系列布尔值:

changes_df["pop_change_2010_2020"] > 0
0        True
1        True
        ...  
3224    False
3225    False
Name: pop_change_2010_2020, Length: 3226, dtype: bool

我们可以将这一系列布尔值赋给 pop_increase 变量。

changes_df["pop_increase"] = changes_df["pop_change_2010_2020"] > 0
changes_df
county pop_change_2010_2020 pct_emp_change_2010_2021 pop_increase
0 Autauga, AL 7.758700 9.0 True
1 Baldwin, AL 27.159356 28.2 True
... ... ... ... ...
3224 Yabucoa, PR -19.807069 0.1 False
3225 Yauco, PR -18.721309 -5.3 False

3226 rows × 4 columns

同样,我们可以为就业变化创建布尔变量 emp_increase

changes_df["emp_increase"] = changes_df["pct_emp_change_2010_2021"] > 0
changes_df
county pop_change_2010_2020 pct_emp_change_2010_2021 pop_increase emp_increase
0 Autauga, AL 7.758700 9.0 True True
1 Baldwin, AL 27.159356 28.2 True True
... ... ... ... ... ...
3224 Yabucoa, PR -19.807069 0.1 False True
3225 Yauco, PR -18.721309 -5.3 False False

3226 rows × 5 columns

现在,我们可以筛选出人口增加但就业减少的县。

# Counties where population increased but employment decreased
pop_up_emp_down = changes_df.query("pop_increase == True & emp_increase == False")
pop_up_emp_down
county pop_change_2010_2020 pct_emp_change_2010_2021 pop_increase emp_increase
71 Bethel, AK 9.716099 -0.7 True False
75 Dillingham, AK 0.206313 -16.1 True False
... ... ... ... ... ...
3127 Campbell, WY 1.935708 -14.8 True False
3137 Natrona, WY 5.970842 -0.2 True False

242 rows × 5 columns

您也可以简写如下:

# Counties where population increased but employment decreased
pop_up_emp_down = changes_df.query("pop_increase & ~(emp_increase)")
pop_up_emp_down
county pop_change_2010_2020 pct_emp_change_2010_2021 pop_increase emp_increase
71 Bethel, AK 9.716099 -0.7 True False
75 Dillingham, AK 0.206313 -16.1 True False
... ... ... ... ... ...
3127 Campbell, WY 1.935708 -14.8 True False
3137 Natrona, WY 5.970842 -0.2 True False

242 rows × 5 columns

有几个这样的县,这些县可能值得进一步分析。

练习

17.8.1 练习题:按外籍出生人口分类县

在之前的练习题中,我们计算了每个县的外籍出生居民百分比。现在,创建一个布尔变量 foreign_born_pct_gt_30,如果百分比大于 30%,则为 True

完成后,查询 DataFrame 以仅显示 foreign_born_pct_gt_30True 的县。您应该得到 24 行。

# Your code here

17.9 写时复制警告

在本课的前面部分,我们启用了“写时复制”模式。让我们看看当此功能被禁用时会发生什么。

pd.set_option("mode.copy_on_write", False)

# Create a small subset of our data
subset = counties.query("state == 'AL'")

subset
state county pop_20 area_sq_miles hh_inc_21 econ_type unemp_20 foreign_born_num pop_change_2010_2020 pct_emp_change_2010_2021
0 AL Autauga, AL 58877.0 594.456107 66444.0 Nonspecialized 5.4 1241.0 7.758700 9.0
1 AL Baldwin, AL 233140.0 1589.836014 65658.0 Recreation 6.2 7938.0 27.159356 28.2
... ... ... ... ... ... ... ... ... ... ...
65 AL Wilcox, AL 10518.0 887.857611 30071.0 Manufacturing 16.2 36.0 -9.168809 3.8
66 AL Winston, AL 23491.0 612.998002 47176.0 Manufacturing 5.2 408.0 -3.855579 18.1

67 rows × 10 columns

当我们尝试修改子集时,会收到一个警告:

# Modify the subset
subset['unemp_20'] = subset['unemp_20'].round(0)
/var/folders/vr/shb6ffvj2rl61kh7qqczhrgh0000gp/T/ipykernel_11078/317403666.py:2: SettingWithCopyWarning:


A value is trying to be set on a copy of a slice from a DataFrame.
Try using .loc[row_indexer,col_indexer] = value instead

See the caveats in the documentation: https://pandas.pydata.org/pandas-docs/stable/user_guide/indexing.html#returning-a-view-versus-a-copy

虽然我们不会深入探讨此警告的技术细节(因为它涉及复杂的 pandas 内部机制),但值得注意的是,该警告包含一个指向 pandas 文档的链接。该文档包含了我们在课程开始时使用的设置。

如果您需要再次参考此设置,您只需点击警告消息中的链接即可访问文档。文档页面还提供了有关此特定问题的更详细信息。

另请注意,从 Pandas 3.0(可能在 2025 年发布)开始,将移除此警告,因为默认行为将改为写时复制。

17.10 总结

转换数据是任何数据分析工作流程中的基本步骤。pandas 使得使用简单直观的语法在您的 DataFrame 中创建和修改变量变得直接。

在本课中,您学习了如何:

  • 通过分配给新列创建新变量。
  • 修改现有变量。
  • 执行涉及多个变量的计算。
  • 基于条件创建布尔变量。

祝贺您完成本课!