18  变量的条件性转换

18.1 介绍

在上一课中,你学习了 pandas 中数据转换的基础知识。

在本课中,我们将探讨如何使用 replace() 和自定义函数等方法在 pandas 中条件性地转换变量

条件性转换在你需要根据特定条件重新编码变量或创建新变量时非常重要。

让我们开始吧!

18.2 学习目标

完成本课后,你将能够:

  • 使用 replace() 和字典根据条件转换或创建新变量。
  • 知道如何在 replace() 转换中处理 NaN 值。
  • 能够定义和应用自定义函数来重新编码变量。

18.3

本课将需要 pandasnumpyplotly.expressvega_datasets:

import pandas as pd
import numpy as np
import vega_datasets as vd
import plotly.express as px

18.4 replace() 简介

数据整理中的一个常见任务是根据某些条件替换列中的值。pandas 中的 replace() 方法是一个多功能的工具。

tips 数据集中,day 列包含简写的星期名称:

tips = px.data.tips()
tips['day'].unique()
array(['Sun', 'Sat', 'Thur', 'Fri'], dtype=object)

我们的目标是用完整的星期名称替换这些缩写。

我们可以创建一个将缩写名称映射到完整名称的字典:

day_mapping = {
    "Sun": "Sunday",
    "Sat": "Saturday",
    "Fri": "Friday",
    "Thur": "Thursday"
}

现在,我们使用带有字典的 replace() 方法:

tips['day_full'] = tips['day'].replace(day_mapping)
tips
total_bill tip sex smoker day time size day_full
0 16.99 1.01 Female No Sun Dinner 2 Sunday
1 10.34 1.66 Male No Sun Dinner 3 Sunday
2 21.01 3.50 Male No Sun Dinner 3 Sunday
... ... ... ... ... ... ... ... ...
241 22.67 2.00 Male Yes Sat Dinner 2 Saturday
242 17.82 1.75 Male No Sat Dinner 2 Saturday
243 18.78 3.00 Female No Thur Dinner 2 Thursday

244 rows × 8 columns

或者,我们可以在 replace() 方法中直接进行替换,而无需明确地定义字典:

tips['day_full'] = tips['day'].replace({
    "Sun": "Sunday",
    "Sat": "Saturday",
    "Fri": "Friday",
    "Thur": "Thursday"
})
tips[['day', 'day_full']].head()
day day_full
0 Sun Sunday
1 Sun Sunday
2 Sun Sunday
3 Sun Sunday
4 Sun Sunday
练习

18.5 练习题:缩写性别

使用 tips 数据集,将 sex 列中的值缩写性别:

  • "Female" 替换为 "F"
  • "Male" 替换为 "M"

将结果赋值给一个名为 sex_abbr 的新列并显示前几行。

# Your code here:

18.6 使用 replace() 处理缺失值

有时,你的数据集中可能包含缺失值(NaNNone),你希望用占位符或特定值替换它们。replace() 方法可以处理这种情况。

让我们检查 vega_datasets 中 movies 数据集的 Creative_Type 列:

movies = vd.data.movies()
movies['Creative_Type'].value_counts(dropna=False)
Creative_Type
Contemporary Fiction       1453
None                        446
Historical Fiction          350
                           ... 
Factual                      49
Super Hero                   49
Multiple Creative Types       1
Name: count, Length: 10, dtype: int64

注意 Creative_Type 列中有一些 None 值。

让我们将 None 替换为 "Unknown/Unclear":

movies['Creative_Type'] = movies['Creative_Type'].replace({
    None: "Unknown/Unclear", # 👈 在这一行,None 是键
})

现在,让我们验证替换:

movies['Creative_Type'].value_counts(dropna=False)
Creative_Type
Contemporary Fiction       1453
Unknown/Unclear             446
Historical Fiction          350
                           ... 
Factual                      49
Super Hero                   49
Multiple Creative Types       1
Name: count, Length: 10, dtype: int64

虽然 None 通常用于表示缺失的字符串,NaN 用于表示缺失的数字。考虑 US_DVD_Sales 列:

movies.query("US_DVD_Sales.isna()").shape # 检查缺失值的数量
(2637, 16)
movies['US_DVD_Sales'].tail(10) # 查看最后 10 个值。有些缺失。
3191     3273039.0
3192    22025352.0
3193           NaN
           ...    
3198     6679409.0
3199           NaN
3200           NaN
Name: US_DVD_Sales, Length: 10, dtype: float64

我们可以使用 replace()NaN 替换为 0:

movies['US_DVD_Sales'] = movies['US_DVD_Sales'].replace({
    np.nan: 0 # 👈 pandas 中用 `np.nan` 表示 `NaN`
})

让我们验证替换:

movies['US_DVD_Sales'].tail(10)
3191     3273039.0
3192    22025352.0
3193           0.0
           ...    
3198     6679409.0
3199           0.0
3200           0.0
Name: US_DVD_Sales, Length: 10, dtype: float64
movies.query("US_DVD_Sales.isna()").shape
(0, 16)

18.7 练习题:标准化 MPAA 分级

movies 数据集中,MPAA_Rating 列包含电影分级。有些条目是 None"Not Rated"。将 None"Not Rated" 都替换为 "Unrated"

然后,使用 value_counts() 查看有多少电影未分级。该类别下应该有 699 部电影。

# Your code here:

18.8 使用自定义函数对数值数据分类

回想我们上一课的内容,我们可以使用带有条件逻辑的自定义函数来转换变量。例如,我们可以根据以下标准将 US_Gross 列分类为三类:

  • 如果值小于 1000 万,类别为 "Low"
  • 如果值在 1000 万到 5000 万之间,类别为 "Medium"
  • 如果值大于 5000 万,类别为 "High"
def categ_gross(gross):
    if gross < 10000000:
        return "Low"
    elif gross >= 10000000 and gross <= 50000000:
        return "Medium"
    elif gross > 50000000:
        return "High"
    else:
        return None 


categ_gross_vec = np.vectorize(categ_gross)
附注

在上述情况下,np.vectorize 函数将返回 None 作为字符串。为了强制使用 None 类型,你可以使用 otypes 参数:

categ_gross_vec = np.vectorize(categ_gross, otypes=[object])

现在我们可以将其应用于整个列:

movies['Gross_Category'] = categ_gross_vec(movies['US_Gross'])
movies['Gross_Category'].value_counts(dropna=False)
Gross_Category
Medium    1241
Low       1046
High       907
None         7
Name: count, dtype: int64

这也可以通过 pd.cut()np.where()np.select() 实现。但自定义函数方法是最灵活的。下面我们将看到如何将其扩展到更复杂的条件。

18.9 使用自定义函数进行复杂转换

自定义函数的灵活性可以轻松扩展到更复杂的条件转换。

例如,假设我们想根据美国和全球的总收入将超级英雄电影标记为“美国动作电影”或“全球动作电影”。

  • 对于超级英雄电影,如果美国总收入和全球总收入相同(表明销售仅在美国),则电影被标记为美国动作电影
  • 对于超级英雄电影,如果全球总收入大于美国总收入,电影被标记为全球动作电影
  • 对于所有其他电影,保留空白标记。

我们可以定义一个接受三个参数并返回适当标记的函数:

# 定义根据条件标记电影的函数
def flag_movie(movie_type, us, worldwide):
    if movie_type == 'Super Hero' and us == worldwide:
        return 'US action movie'
    elif movie_type == 'Super Hero' and worldwide > us:
        return 'Global action movie'
    else:
        return None

让我们用几个值集来测试它:

print(flag_movie(movie_type='Super Hero', us=100, worldwide=100))
print(flag_movie(movie_type='Super Hero', us=100, worldwide=200))
print(flag_movie(movie_type='Comedy', us=100, worldwide=100))
US action movie
Global action movie
None

现在,让我们将其向量化:

flag_movie_vec = np.vectorize(flag_movie)

我们现在可以将其应用到列:

movies['Action_Flag'] = flag_movie_vec(movies['Creative_Type'], movies['US_Gross'], movies['Worldwide_Gross'])
movies
Title US_Gross Worldwide_Gross US_DVD_Sales Production_Budget Release_Date MPAA_Rating Running_Time_min Distributor Source Major_Genre Creative_Type Director Rotten_Tomatoes_Rating IMDB_Rating IMDB_Votes Gross_Category Action_Flag
0 The Land Girls 146083.0 146083.0 0.0 8000000.0 Jun 12 1998 R NaN Gramercy None None Unknown/Unclear None NaN 6.1 1071.0 Low None
1 First Love, Last Rites 10876.0 10876.0 0.0 300000.0 Aug 07 1998 R NaN Strand None Drama Unknown/Unclear None NaN 6.9 207.0 Low None
2 I Married a Strange Person 203134.0 203134.0 0.0 250000.0 Aug 28 1998 None NaN Lionsgate None Comedy Unknown/Unclear None NaN 6.8 865.0 Low None
... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ...
3198 Zoom 11989328.0 12506188.0 6679409.0 35000000.0 Aug 11 2006 PG NaN Sony Pictures Based on Comic/Graphic Novel Adventure Super Hero Peter Hewitt 3.0 3.4 7424.0 Medium Global action movie
3199 The Legend of Zorro 45575336.0 141475336.0 0.0 80000000.0 Oct 28 2005 PG 129.0 Sony Pictures Remake Adventure Historical Fiction Martin Campbell 26.0 5.7 21161.0 Medium None
3200 The Mask of Zorro 93828745.0 233700000.0 0.0 65000000.0 Jul 17 1998 PG-13 136.0 Sony Pictures Remake Adventure Historical Fiction Martin Campbell 82.0 6.7 4789.0 High None

3201 rows × 18 columns

要查看基于我们标记的电影类别分布,我们可以使用 value_counts():

movies['Action_Flag'].value_counts(dropna=False)
Action_Flag
None                   3152
Global action movie      42
US action movie           7
Name: count, dtype: int64

18.9.1 练习:根据评分标记电影

movies 数据集中,根据烂番茄(Rotten Tomatoes)和 IMDB 评分将电影标记为影评人友好商业化

  • 如果烂番茄评分高于 70% 且 IMDB 评分低于 5,电影被标记为影评人友好
  • 如果烂番茄评分低于 50% 且 IMDB 评分高于 7,电影被标记为商业化
  • 否则,电影分类为其他
  • 统计有多少电影是影评人友好商业化。应该有 13 部影评人友好电影和 33 部商业化电影。你认识其中的任何电影吗?
# Your code here:

18.10 总结

在本课中,你学习了如何使用以下方法在 pandas 中有条件地转换变量:

  • 使用带有字典的 replace() 方法来映射和替换特定值。
  • 在替换过程中处理缺失值(NaNNone)。
  • 定义自定义函数并应用它们来处理复杂条件。

这些技术是数据清洗和预处理的强大工具,使你能够重新塑造数据以满足分析需求。

下次见!