import pandas as pd
17 pandas 中的变量转换
17.1 介绍
在数据分析中,最常见的任务之一是转换数据集中的变量。pandas 库提供了简洁高效的方法来完成这项任务。
17.2 学习目标
- 理解如何在 DataFrame 中创建新变量。
- 学习如何修改现有变量。
- 处理修改视图上变量的潜在问题。
17.3 导入
首先,让我们导入 pandas 包:
现在我们将设置一个重要选项,这将帮助我们避免后续的一些警告。在课程的后面部分,我们会更详细地讨论这个选项。
= True pd.options.mode.copy_on_write
17.4 数据集
在本课中,我们将使用包含人口和经济数据的美国县的数据集。您可以通过以下链接下载该数据集:https://github.com/the-graph-courses/idap_book/raw/refs/heads/main/data/us_counties_data.zip。
下载文件后,解压缩并将 us_counties_data.csv
文件放在项目的 data
文件夹中。
= pd.read_csv("data/us_counties_data.csv")
counties 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
= counties[["county", "area_sq_miles", "pop_20"]]
area_df 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_sq_km"] = area_df["area_sq_miles"] * 2.59
area_df[ 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_hectares"] = area_df["area_sq_miles"] * 259
area_df[ 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_sq_km"] = area_df["area_sq_km"].round(1)
area_df[ 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 多变量计算
我们可以基于多个现有变量创建新变量。
例如,让我们计算每平方公里的人口密度。
"pop_per_sq_km"] = area_df["pop_20"] / area_df["area_sq_km"]
area_df[ 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
方法,将结果四舍五入到小数点后一位。
"pop_per_sq_km"] = (area_df["pop_20"] / area_df["area_sq_km"]).round(1)
area_df[ 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
或者,如果您愿意,您可以将其分两步完成:
"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[ 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.sort_values("pop_per_sq_km", ascending=False)
area_df 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 创建布尔变量
有时创建布尔变量以根据条件对数据进行分类或标记是很有用的。布尔变量是仅取两个值的变量:True
或 False
。
考虑 counties
数据集中的 pop_change_2010_2020
变量,它显示了 2010 年至 2020 年之间的人口百分比变化。
= counties[["county", "pop_change_2010_2020", "pct_emp_change_2010_2021"]]
changes_df 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
会返回一系列布尔值:
"pop_change_2010_2020"] > 0 changes_df[
0 True
1 True
...
3224 False
3225 False
Name: pop_change_2010_2020, Length: 3226, dtype: bool
我们可以将这一系列布尔值赋给 pop_increase
变量。
"pop_increase"] = changes_df["pop_change_2010_2020"] > 0
changes_df[ 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
。
"emp_increase"] = changes_df["pct_emp_change_2010_2021"] > 0
changes_df[ 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
= changes_df.query("pop_increase == True & emp_increase == False")
pop_up_emp_down 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
= changes_df.query("pop_increase & ~(emp_increase)")
pop_up_emp_down 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_30
为 True
的县。您应该得到 24 行。
# Your code here
17.9 写时复制警告
在本课的前面部分,我们启用了“写时复制”模式。让我们看看当此功能被禁用时会发生什么。
"mode.copy_on_write", False)
pd.set_option(
# Create a small subset of our data
= counties.query("state == 'AL'")
subset
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
'unemp_20'] = subset['unemp_20'].round(0) subset[
/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 中创建和修改变量变得直接。
在本课中,您学习了如何:
- 通过分配给新列创建新变量。
- 修改现有变量。
- 执行涉及多个变量的计算。
- 基于条件创建布尔变量。
祝贺您完成本课!