第 7 章 数据清洗与准备
数据分析和建模需要大量的数据准备工作。
加载、清洗、转换和重新排列数据会占用分析师的大量时间 (通常占 80% 甚至更多!😮)。
数据并不总是以正确的格式存在。现实世界的数据是混乱的!
Pandas 结合 Python 的内置功能,为数据操作提供了强大的工具。
Pandas 提供了用于数据处理的高级、灵活且快速的工具。
它的设计目标是有效地处理现实世界中的数据挑战。
本章涵盖以下工具:
缺失数据在数据分析中很常见。
Pandas 的目标是使处理缺失数据尽可能容易。
默认情况下,pandas 中的描述性统计会排除缺失数据。
Pandas 使用 NaN
(Not a Number,非数字),一个浮点值,来表示缺失的数据,尤其是在处理float64
数据类型时。
NaN
标记np.nan
是一个特殊的浮点值,表示缺失数据。
它是一个标记值 – 它的存在表示一个缺失值或空值。
.isna()
检测.isna()
方法返回一个布尔类型的 Series。
True
表示缺失值 (NaN),False
表示非缺失值。
Pandas 采用 R 语言的约定:缺失数据被称为 NA (not available,不可用)。
NA 可能意味着:
分析缺失数据本身可以揭示数据收集问题或潜在的偏差。🤔
None
也是 NAPython 内置的 None
值在 pandas 中也被视为 NA。
字符串和数值类型的 Series 都可以将 None
和 NaN
作为 NA。
float_data
使用NaN
来表示缺失值。方法 | 描述 |
---|---|
dropna |
根据缺失值筛选轴标签 (行/列),并提供阈值选项。 |
fillna |
使用指定值或插值方法 (例如 “ffill”、“bfill”) 填充缺失数据。 |
isna |
返回一个布尔数组/Series,指示哪些值是缺失的/NA。 |
notna |
isna 的反操作:对于非 NA 值返回 True ,对于 NA 值返回 False 。 |
这些方法为在 pandas 中处理缺失数据提供了基础。
dropna
Series 上的 dropna()
返回一个仅包含非空数据和索引标签的新 Series。
等同于布尔索引:data[data.notna()]
。
dropna
(第 1 部分)dropna
(第 2 部分)默认情况下,dropna()
会删除包含任何 NA 值的行。
这种方式可能非常严格。
how='all'
的 dropna
how="all"
仅删除所有值都为 NA 的行。
比默认行为更宽松。
要删除列,请使用 axis="columns"
(或 axis=1
)。
how="all"
与 axis="columns"
一起使用会删除所有值都为 NA 的列。
thresh
参数的 dropna
(第 1 部分)thresh
参数的 dropna
(第 2 部分)thresh
参数保留至少具有 thresh
个非 NA 值的行。
可以更精细地控制要保留哪些行。
fillna
填充fillna(value)
将所有 NA 值替换为指定的 value
。
一个常见的选择是 0,但这取决于具体情况。
fillna
使用字典为每列指定不同的填充值。
字典的键是列标签;值是填充值。
fillna
进行插值 (第 1 部分)fillna
进行插值 (第 2 部分)method="ffill"
(前向填充) 将上一个有效观测值向前传播。-method="bfill"
(后向填充)使用下一个有效观测值来填充空缺。 - 适用于时间序列或有序数据。
limit
参数的 fillna
limit
参数限制 ffill
或 bfill
填充的连续 NA 值的数量。fillna
进行插补用平均值、中位数或其他统计量替换缺失值。
这是一种简单的插补形式。
筛选和清洗是必不可少的,但数据通常需要进一步转换。
本节涵盖:
duplicated()
识别重复项duplicated()
返回一个布尔 Series,指示每行是否为重复项 (是否在之前出现过)。drop_duplicates()
删除重复项drop_duplicates()
返回一个删除了重复行的新 DataFrame。
保留每个唯一行的第一个出现。
drop_duplicates()
(第 1 部分)drop_duplicates()
(第 2 部分)使用 subset
参数指定要检查重复项的列的子集。
这里,只考虑 "k1"
列。
keep='last'
的 drop_duplicates()
keep="last"
保留每个唯一行 (或列组合) 的最后一个出现。
默认为 keep="first"
。
.map()
Series 的 .map()
方法接受一个函数或一个类似字典的对象 (如我们的映射)。
它将映射应用于 Series 的每个元素。
.map()
一起使用我们也可以将函数与 .map()
一起使用。
该函数将 Series 中的单个元素作为输入,并返回转换后的值。
replace()
替换单个值replace(old_value, new_value)
将 old_value
的所有出现替换为 new_value
。replace()
替换多个值replace()
一起使用字典也可以与 replace()
一起使用。
键是旧值;值是新值。
.map()
修改索引 (第 1 部分).map()
修改索引 (第 2 部分)与 Series 类似,轴索引也有一个 .map()
方法。
我们应用一个函数来转换每个索引标签。
赋值给 data.index
会就地修改 DataFrame。
rename()
创建转换后的副本rename()
创建一个转换后的副本,而不修改原始 DataFrame。
index
和 columns
参数可以接受函数、字典或 Series。
rename()
rename()
一起使用可以修改轴标签的子集。连续数据通常会被离散化或分箱以进行分析。
示例:将年龄分组到年龄范围。
pd.cut(data, bins)
根据指定的 bins
边界将数据划分为多个箱。
返回一个特殊的 Categorical
对象。
Categorical
对象 (第 1 部分)Categorical
对象 (第 2 部分)codes
: 一个整数数组,表示每个值所属的箱 (从 0 开始)。categories
: 一个 IntervalIndex
对象,包含箱的区间。Categorical
上的 value_counts()
pd.value_counts(categorical)
提供 pd.cut()
结果的箱计数。圆括号 ()
表示该侧是开的 (不包含)。
方括号 []
表示该侧是闭的 (包含)。
(18, 25]
表示 “大于 18,小于等于 25”。
right=False
更改区间的闭合侧。labels
参数为箱分配自定义名称。
比默认的区间标签更具信息量。
pd.cut()
将整数个箱传递给 pd.cut()
以根据最小值/最大值计算等长的箱。
precision
限制箱标签的小数精度。
pd.qcut()
pd.qcut()
根据样本分位数对数据进行分箱。
旨在实现 (大致) 等大小的箱。
对于将数据划分为百分位数很有用。
pd.qcut()
将自定义分位数 (0 到 1 之间的值) 传递给 pd.qcut()
。
示例:划分为十分位数 (0.1, 0.2, …, 0.9)。
data.abs() > 3
: 布尔 DataFrame,指示超过 3 或 -3 的值。
any(axis="columns")
: 检查一行中是否任何值为 True
。
选择包含至少一个异常值的行。
np.sign(data)
: 对于负值返回 -1,对于正值返回 1。
将超出 [-3, 3] 区间的值限制为 -3 和 3。
排列 (随机重新排序) 行或列。
选择数据的随机子集。
permutation()
排列行 (第 1 部分)permutation()
排列行 (第 2 部分)np.random.permutation(n)
生成一个从 0 到 n-1 的整数的随机排列。
take()
或 iloc[]
可以与排列一起使用以对行进行重新排序。
axis="columns"
与 take()
一起使用。sample(n=k)
选择 k
个随机行,不放回。replace=True
允许有放回采样 (同一行可以被多次选择)。将分类变量转换为 “虚拟” 或 “指标” 矩阵。
用于统计建模和机器学习。
get_dummies()
pd.get_dummies(categorical_column)
创建一个 DataFrame,其中:
prefix
参数为虚拟变量列名添加前缀。
与原始 DataFrame 连接时很有用。
str.get_dummies()
用于多个类别str.get_dummies(separator)
处理由分隔符分隔的多个类别。add_prefix()
为虚拟变量列添加前缀。join()
将虚拟变量与原始 DataFrame 组合。get_dummies()
和 cut()
(第 1 部分)get_dummies()
和 cut()
(第 2 部分)统计应用的一个技巧:将 get_dummies()
与离散化函数 (如 cut()
) 结合使用。
为每个箱创建指标变量。
float64
。Int64Dtype
(第 1 部分)Int64Dtype
(第 2 部分)pd.Int64Dtype()
(或 "Int64"
) 创建一个具有正确 NA 处理的整数 Series。
使用 <NA>
表示缺失值 (pandas.NA 标记值)。
pd.StringDtype()
创建一个专门的字符串数据类型。pyarrow
库。astype()
(第 1 部分)astype()
(第 2 部分)扩展类型 | 描述 |
---|---|
BooleanDtype |
可空布尔数据,作为字符串传递时使用 "boolean" |
CategoricalDtype |
分类数据类型,作为字符串传递时使用 "category" |
DatetimeTZDtype |
带时区的日期时间 |
Float32Dtype |
32 位可空浮点数,作为字符串传递时使用 "Float32" |
Float64Dtype |
64 位可空浮点数,作为字符串传递时使用 "Float64" |
Int8Dtype |
8 位可空有符号整数,作为字符串传递时使用 "Int8" |
Int16Dtype |
16 位可空有符号整数,作为字符串传递时使用 "Int16" |
Int32Dtype |
32 位可空有符号整数,作为字符串传递时使用 "Int32" |
Int64Dtype |
64 位可空有符号整数,作为字符串传递时使用 "Int64" |
UInt8Dtype |
8 位可空无符号整数,作为字符串传递时使用 "UInt8" |
UInt16Dtype |
16 位可空无符号整数,作为字符串传递时使用 "UInt16" |
UInt32Dtype |
32 位可空无符号整数,作为字符串传递时使用 "UInt32" |
UInt64Dtype |
64 位可空无符号整数,作为字符串传递时使用 "UInt64" |
Python 因其字符串/文本处理能力而广受欢迎。
字符串对象方法通常就足够了。
正则表达式 (regex) 提供了更强大的功能。
Pandas 将这些功能结合起来,并优雅地处理缺失数据。
split()
: 根据分隔符将字符串拆分为子字符串列表。strip()
: 删除前导/尾随空格。通常与 split()
一起使用。join()
: 使用分隔符连接字符串的一种更 Pythonic 的方式。in
: 检查子字符串是否存在的最佳方法。
index()
: 查找子字符串的第一次出现的索引;如果未找到则引发错误。
find()
: 类似于 index()
,但如果未找到则返回 -1。
count()
和 replace()
(第 1 部分)count()
和 replace()
(第 2 部分)count()
: 计算子字符串出现的次数。replace()
: 将一个模式的出现替换为另一个模式。方法 | 描述 |
---|---|
count |
返回字符串中子字符串的不重叠出现次数 |
endswith |
如果字符串以指定后缀结尾,则返回 True |
startswith |
如果字符串以指定前缀开头,则返回 True |
join |
使用字符串作为分隔符连接其他字符串序列 |
index |
返回传递的子字符串在字符串中第一次出现的起始索引;如果未找到,则引发 ValueError 异常 |
find |
返回字符串中子字符串第一次出现的第一个字符的位置;类似于 index,但如果未找到则返回 -1 |
rfind |
返回字符串中子字符串最后一次出现的第一个字符的位置;如果未找到则返回 -1 |
replace |
将字符串的出现替换为另一个字符串 |
strip |
修剪两侧的空白字符,包括换行符 |
rstrip |
修剪右侧的空白字符 |
方法 | 描述 |
---|---|
lstrip |
修剪左侧的空白字符 |
split |
使用传递的分隔符将字符串拆分为子字符串列表 |
lower |
将字母字符转换为小写 |
upper |
将字母字符转换为大写 |
casefold |
将字符转换为小写,处理特定于区域的变体 |
ljust |
左对齐;使用空格 (或其他填充字符) 填充右侧 |
rjust |
右对齐;使用空格 (或其他填充字符) 填充左侧 |
正则表达式 (regex) 提供了一种强大的方法来搜索、匹配和操作文本模式。
Python 的内置 re
模块处理正则表达式。
正则表达式函数分为三类:
\s+
: 一个或多个空白字符的正则表达式。
re.split(pattern, text)
: 根据正则表达式模式拆分文本。
re.compile(pattern)
: 将正则表达式编译为可重用的正则表达式对象。
如果您将对多个字符串应用相同的正则表达式,则建议使用 (节省 CPU 周期)。
findall()
findall()
: 返回字符串中所有不重叠的模式匹配项的列表。text = """Dave [email protected]
Steve [email protected]
Rob [email protected]
Ryan [email protected]""" # 定义一个包含多个电子邮件地址的字符串
pattern = r"[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,4}" # 用于匹配电子邮件地址的基本正则表达式
# re.IGNORECASE 使正则表达式不区分大小写
regex = re.compile(pattern, flags=re.IGNORECASE) # 编译正则表达式,并设置 re.IGNORECASE 标志以忽略大小写
一个更复杂的正则表达式来匹配电子邮件地址。
re.IGNORECASE
标志使匹配不区分大小写。
findall()
findall()
返回所有匹配的电子邮件地址的列表。search()
(第 1 部分)search()
(第 2 部分)search()
: 返回字符串中第一个匹配项的匹配对象。
匹配对象提供匹配的开始和结束位置。
regex.match()
返回 None
。
sub()
sub(replacement, text)
: 返回一个新字符串,其中模式的出现被 replacement
字符串替换。pattern = r"([A-Z0-9._%+-]+)@([A-Z0-9.-]+)\.([A-Z]{2,4})" # 带有捕获组的正则表达式
regex = re.compile(pattern, flags=re.IGNORECASE) # 编译正则表达式,并设置 re.IGNORECASE 标志
m = regex.match("[email protected]") # 使用 .match() 方法匹配字符串的开头
m.groups() # 使用 .groups() 方法获取捕获组的内容
()
定义捕获组。groups()
方法返回一个包含捕获组内容的元组。findall()
findall()
返回一个元组列表,其中每个元组包含捕获组。sub()
sub()
中,\1
、\2
等引用捕获组 (反向引用)。方法 | 描述 |
---|---|
findall |
以列表形式返回字符串中所有不重叠的匹配模式 |
finditer |
类似于 findall ,但返回一个迭代器 |
match |
匹配字符串开头的模式,并可选择将模式组件分段到组中;如果模式匹配,则返回一个匹配对象,否则返回 None |
search |
扫描字符串以查找与模式匹配的子字符串,如果找到则返回一个匹配对象;与 match 不同,匹配可以在字符串中的任何位置 |
split |
在模式的每次出现处将字符串拆分为多个片段 |
sub , subn |
将字符串中模式的所有出现 (sub ) 或前 n 次出现 (subn ) 替换为替换表达式;使用符号 \1 、\2 等 |
Pandas 将字符串操作扩展到 Series 和 DataFrame。
优雅地处理缺失数据。
data = {"Dave": "[email protected]", "Steve": "[email protected]",
"Rob": "[email protected]", "Wes": np.nan} # 创建一个包含电子邮件地址和缺失值的字典
data = pd.Series(data) # 将字典转换为 Series
data # 显示 Series
str
访问器Series 有一个 str
属性,提供对字符串方法的访问。
这些方法会跳过并传播 NA 值。
str.contains()
str.contains(substring)
: 检查每个字符串是否包含给定的子字符串。
返回一个布尔 Series。
str
方法一起使用str
方法一起使用。flags
(如 re.IGNORECASE
)。str.get(i)
或索引到 str
属性 (str[i]
)。str.extract()
str.extract(pattern)
: 返回一个 DataFrame,其中正则表达式中的每个捕获组都成为一列。Series
字符串方法部分列表 - 总结方法 | 描述 |
---|---|
cat |
使用可选的分隔符逐元素连接字符串 |
contains |
如果每个字符串都包含模式/正则表达式,则返回布尔数组 |
count |
计算模式 |
extract |
使用带有组的正则表达式从字符串 Series 中提取一个或多个字符串;结果将是一个 DataFrame,每个组一列 |
endswith |
等效于对每个元素执行 x.endswith(pattern) |
startswith |
等效于对每个元素执行 x.startswith(pattern) |
findall |
计算每个字符串的所有模式/正则表达式匹配项的列表 |
get |
索引到每个元素 (检索第 i 个元素) |
isalnum |
等效于内置的 str.isalnum |
isalpha |
等效于内置的 str.isalpha |
isdecimal |
等效于内置的 str.isdecimal |
isdigit |
等效于内置的 str.isdigit |
islower |
等效于内置的 str.islower |
isnumeric |
等效于内置的 str.isnumeric |
isupper |
等效于内置的 str.isupper |
join |
使用传递的分隔符连接 Series 中每个元素中的字符串 |
len |
计算每个字符串的长度 |
lower, upper |
转换大小写;等效于对每个元素执行 x.lower() 或 x.upper() |
match |
对每个元素使用 re.match 和传递的正则表达式,返回 True 或 False 表示是否匹配 |
pad |
在字符串的左侧、右侧或两侧添加空白字符 |
center |
等效于 pad(side="both") |
repeat |
复制值 (例如,s.str.repeat(3) 等效于对每个字符串执行 x * 3 ) |
replace |
将模式/正则表达式的出现替换为其他字符串 |
slice |
对 Series 中的每个字符串进行切片 |
split |
使用分隔符或正则表达式拆分字符串 |
strip |
修剪两侧的空白字符,包括换行符 |
rstrip |
修剪右侧的空白字符 |
lstrip |
修剪左侧的空白字符 |
介绍 pandas Categorical
类型。
提高某些 pandas 操作的性能和内存使用。
对统计和机器学习应用有用。
列通常包含一组较小的不同值的重复实例。
维度表是数据仓库中的一种常用技术。
更高效的存储和计算。
values
: 引用维度表的整数键。
dim
: 包含不同值的维度表。
take()
恢复原始数据take()
方法可用于恢复原始字符串 Series。分类或字典编码表示:使用整数表示具有重复值的数据。
类别、字典或级别:不同值的数组。
类别代码或代码:引用类别的整数值。
显著提高分析性能。
在保持代码不变的情况下对类别进行转换。
Categorical
扩展类型 (第 1 部分)fruits = ['apple', 'orange', 'apple', 'apple'] * 2 # 创建一个水果列表
N = len(fruits) # 计算列表长度
rng = np.random.default_rng(seed=12345) # 创建一个随机数生成器,并设置种子以确保结果可重复
df = pd.DataFrame({'fruit': fruits,
'basket_id': np.arange(N),
'count': rng.integers(3, 15, size=N),
'weight': rng.uniform(0, 4, size=N)},
columns=['basket_id', 'fruit', 'count', 'weight']) # 创建一个 DataFrame
df # 显示 DataFrame
astype('category')
: 将列转换为 Categorical
类型。Categorical
对象.array
属性访问底层的 Categorical
对象。categories
和 codes
属性 (第 1 部分)categories
: 不同值。categories
和 codes
属性 (第 2 部分)codes
: 表示每个值类别的整数代码。astype('category')
的结果,将 DataFrame 列转换为分类。Categorical
Categorical
对象。from_codes()
构造函数from_codes(codes, categories)
: 从现有代码和类别创建 Categorical
。ordered=True
: 指示类别具有有意义的顺序。
默认情况下,类别是无序的。
使用 Categorical
的行为通常与非编码版本 (例如,字符串数组) 相同。
某些 pandas 函数 (如 groupby
) 在使用分类时性能更好。
qcut()
和 Categorical (第 1 部分)qcut()
和 Categorical (第 2 部分)pd.qcut()
返回一个 Categorical
对象。
labels
参数用于提供名称。
groupby()
(第 1 部分)groupby()
(第 2 部分)包含分类数据的 Series 具有特殊方法 (类似于 Series.str
)。
通过 cat
访问器访问。
cat
访问器cat
访问器提供对分类方法和属性的访问。set_categories()
set_categories()
: 更改类别集。
当数据不包含所有可能的类别时很有用。
set_categories()
的 value_counts()
value_counts()
尊重定义的类别,即使某些类别在数据中不存在。remove_unused_categories()
remove_unused_categories()
: 删除数据中未出现的类别。方法 | 描述 |
---|---|
add_categories |
在现有类别末尾追加新的 (未使用的) 类别 |
as_ordered |
将类别设置为有序 |
as_unordered |
将类别设置为无序 |
remove_categories |
删除类别,将所有已删除的值设置为 null |
remove_unused_categories |
删除数据中未出现的任何类别值 |
rename_categories |
使用指定的新类别名称集替换类别;不能更改类别的数量 |
reorder_categories |
类似于 rename_categories ,但也可以更改结果以使类别有序 |
set_categories |
使用指定的新类别集替换类别;可以添加或删除类别 |
pd.get_dummies(categorical_series)
创建一个包含虚拟变量的 DataFrame。NaN
、None
),包括 dropna
、fillna
、isna
和 notna
。duplicated
、drop_duplicates
)、映射值 (map
)、替换值 (replace
)、重命名索引 (rename
)、分箱 (cut
、qcut
)、异常值检测、排列、采样 (sample
) 和创建虚拟变量 (get_dummies
)。Int64Dtype
、StringDtype
、CategoricalDtype
等) 提供了对特定数据类型和缺失值的改进处理。re
模块) 和 pandas 的 str
访问器高效地进行字符串操作。Categorical
类型为具有重复值的数据提供了内存和性能优势,提供了诸如 cat.codes
、cat.categories
、set_categories
和 remove_unused_categories
之类的方法。Categorical
类型如何提高效率?您会在什么情况下选择使用 Categorical
数据?re
模块和 pandas 的 str
访问器的文档。您还能找到哪些其他有用的函数?cut()
和 qcut()
之间的权衡。您会在什么情况下使用其中一个而不是另一个?邱飞 💌 [email protected]