Python 数据分析(PYDA)第三版(三)(1)https://developer.aliyun.com/article/1482379
6.2 二进制数据格式
以二进制格式存储(或序列化)数据的一种简单方法是使用 Python 的内置pickle
模块。所有 pandas 对象都有一个to_pickle
方法,它以 pickle 格式将数据写入磁盘:
In [95]: frame = pd.read_csv("examples/ex1.csv") In [96]: frame Out[96]: a b c d message 0 1 2 3 4 hello 1 5 6 7 8 world 2 9 10 11 12 foo In [97]: frame.to_pickle("examples/frame_pickle")
Pickle 文件通常只能在 Python 中读取。您可以直接使用内置的pickle
读取存储在文件中的任何“pickled”对象,或者更方便地使用pandas.read_pickle
:
In [98]: pd.read_pickle("examples/frame_pickle") Out[98]: a b c d message 0 1 2 3 4 hello 1 5 6 7 8 world 2 9 10 11 12 foo
注意
pickle
仅建议作为短期存储格式。问题在于很难保证格式随时间稳定;今天使用 pickle 的对象可能无法在以后的库版本中解除 pickle。pandas 在可能的情况下尽力保持向后兼容性,但在将来的某个时候可能需要“破坏”pickle 格式。
pandas 内置支持其他几种开源二进制数据格式,例如 HDF5、ORC 和 Apache Parquet。例如,如果安装pyarrow
包(conda install pyarrow
),则可以使用pandas.read_parquet
读取 Parquet 文件:
In [100]: fec = pd.read_parquet('datasets/fec/fec.parquet')
我将在 HDF5 格式使用中给出一些 HDF5 示例。我鼓励您探索不同的文件格式,看看它们的速度和对您的分析工作的适用性。
读取 Microsoft Excel 文件
pandas 还支持使用pandas.ExcelFile
类或pandas.read_excel
函数读取存储在 Excel 2003(及更高版本)文件中的表格数据。在内部,这些工具使用附加包xlrd
和openpyxl
来分别读取旧式 XLS 和新式 XLSX 文件。这些必须使用 pip 或 conda 单独安装,而不是从 pandas 安装:
conda install openpyxl xlrd
要使用pandas.ExcelFile
,请通过传递路径到xls
或xlsx
文件来创建一个实例:
In [101]: xlsx = pd.ExcelFile("examples/ex1.xlsx")
此对象可以显示文件中可用工作表名称的列表:
In [102]: xlsx.sheet_names Out[102]: ['Sheet1']
可以使用parse
将工作表中存储的数据读入 DataFrame:
In [103]: xlsx.parse(sheet_name="Sheet1") Out[103]: Unnamed: 0 a b c d message 0 0 1 2 3 4 hello 1 1 5 6 7 8 world 2 2 9 10 11 12 foo
此 Excel 表具有索引列,因此我们可以使用index_col
参数指示:
In [104]: xlsx.parse(sheet_name="Sheet1", index_col=0) Out[104]: a b c d message 0 1 2 3 4 hello 1 5 6 7 8 world 2 9 10 11 12 foo
如果要在一个文件中读取多个工作表,则创建pandas.ExcelFile
会更快,但您也可以简单地将文件名传递给pandas.read_excel
:
In [105]: frame = pd.read_excel("examples/ex1.xlsx", sheet_name="Sheet1") In [106]: frame Out[106]: Unnamed: 0 a b c d message 0 0 1 2 3 4 hello 1 1 5 6 7 8 world 2 2 9 10 11 12 foo
要将 pandas 数据写入 Excel 格式,必须首先创建一个ExcelWriter
,然后使用 pandas 对象的to_excel
方法将数据写入其中:
In [107]: writer = pd.ExcelWriter("examples/ex2.xlsx") In [108]: frame.to_excel(writer, "Sheet1") In [109]: writer.close()
您还可以将文件路径传递给to_excel
,避免使用ExcelWriter
:
In [110]: frame.to_excel("examples/ex2.xlsx")
使用 HDF5 格式
HDF5 是一种受尊敬的文件格式,用于存储大量科学数组数据。它作为一个 C 库可用,并且在许多其他语言中都有接口,包括 Java、Julia、MATLAB 和 Python。HDF5 中的“HDF”代表分层数据格式。每个 HDF5 文件可以存储多个数据集和支持的元数据。与更简单的格式相比,HDF5 支持各种压缩模式的即时压缩,使具有重复模式的数据能够更有效地存储。HDF5 可以是处理不适合内存的数据集的良好选择,因为您可以有效地读取和写入更大数组的小部分。
要开始使用 HDF5 和 pandas,您必须首先通过使用 conda 安装tables
包来安装 PyTables:
conda install pytables
注意
请注意,PyTables 包在 PyPI 中称为“tables”,因此如果您使用 pip 安装,您将需要运行pip install tables
。
虽然可以直接使用 PyTables 或 h5py 库访问 HDF5 文件,但 pandas 提供了一个简化存储 Series 和 DataFrame 对象的高级接口。HDFStore
类的工作方式类似于字典,并处理底层细节:
In [113]: frame = pd.DataFrame({"a": np.random.standard_normal(100)}) In [114]: store = pd.HDFStore("examples/mydata.h5") In [115]: store["obj1"] = frame In [116]: store["obj1_col"] = frame["a"] In [117]: store Out[117]: <class 'pandas.io.pytables.HDFStore'> File path: examples/mydata.h5
然后可以使用相同类似字典的 API 检索 HDF5 文件中包含的对象:
In [118]: store["obj1"] Out[118]: a 0 -0.204708 1 0.478943 2 -0.519439 3 -0.555730 4 1.965781 .. ... 95 0.795253 96 0.118110 97 -0.748532 98 0.584970 99 0.152677 [100 rows x 1 columns]
HDFStore
支持两种存储模式,"fixed"
和"table"
(默认为"fixed"
)。后者通常较慢,但支持使用特殊语法进行查询操作:
In [119]: store.put("obj2", frame, format="table") In [120]: store.select("obj2", where=["index >= 10 and index <= 15"]) Out[120]: a 10 1.007189 11 -1.296221 12 0.274992 13 0.228913 14 1.352917 15 0.886429 In [121]: store.close()
put
是store["obj2"] = frame
方法的显式版本,但允许我们设置其他选项,如存储格式。
pandas.read_hdf
函数为您提供了这些工具的快捷方式:
In [122]: frame.to_hdf("examples/mydata.h5", "obj3", format="table") In [123]: pd.read_hdf("examples/mydata.h5", "obj3", where=["index < 5"]) Out[123]: a 0 -0.204708 1 0.478943 2 -0.519439 3 -0.555730 4 1.965781
如果您愿意,可以删除您创建的 HDF5 文件,方法如下:
In [124]: import os In [125]: os.remove("examples/mydata.h5")
注意
如果您正在处理存储在远程服务器上的数据,如 Amazon S3 或 HDFS,使用设计用于分布式存储的不同二进制格式(如Apache Parquet)可能更合适。
如果您在本地处理大量数据,我建议您探索 PyTables 和 h5py,看看它们如何满足您的需求。由于许多数据分析问题受 I/O 限制(而不是 CPU 限制),使用 HDF5 等工具可以大大加速您的应用程序。
注意
HDF5 不是数据库。它最适合于一次写入,多次读取的数据集。虽然数据可以随时添加到文件中,但如果多个写入者同时这样做,文件可能会损坏。
6.3 与 Web API 交互
许多网站都有提供数据源的公共 API,可以通过 JSON 或其他格式提供数据。有许多方法可以从 Python 访问这些 API;我推荐的一种方法是requests
包,可以使用 pip 或 conda 进行安装:
conda install requests
要在 GitHub 上找到 pandas 的最近 30 个问题,我们可以使用附加的requests
库进行GET
HTTP 请求:
In [126]: import requests In [127]: url = "https://api.github.com/repos/pandas-dev/pandas/issues" In [128]: resp = requests.get(url) In [129]: resp.raise_for_status() In [130]: resp Out[130]: <Response [200]>
在使用requests.get
后,始终调用raise_for_status
以检查 HTTP 错误是一个好习惯。
响应对象的json
方法将返回一个包含解析后的 JSON 数据的 Python 对象,作为字典或列表(取决于返回的 JSON 是什么):
In [131]: data = resp.json() In [132]: data[0]["title"] Out[132]: 'BUG: DataFrame.pivot mutates empty index.name attribute with typing._L iteralGenericAlias'
由于检索到的结果基于实时数据,当您运行此代码时,您看到的结果几乎肯定会有所不同。
data
中的每个元素都是一个包含 GitHub 问题页面上找到的所有数据的字典(评论除外)。我们可以直接将data
传递给pandas.DataFrame
并提取感兴趣的字段:
In [133]: issues = pd.DataFrame(data, columns=["number", "title", .....: "labels", "state"]) In [134]: issues Out[134]: number 0 52629 \ 1 52628 2 52626 3 52625 4 52624 .. ... 25 52579 26 52577 27 52576 28 52571 29 52570 title 0 BUG: DataFrame.pivot mutates empty index.name attribute with typing._Li... \ 1 DEPR: unused keywords in DTI/TDI construtors 2 ENH: Infer best datetime format from a random sample 3 BUG: ArrowExtensionArray logical_op not working in all directions 4 ENH: pandas.core.groupby.SeriesGroupBy.apply allow raw argument .. ... 25 BUG: Axial inconsistency of pandas.diff 26 BUG: describe not respecting ArrowDtype in include/exclude 27 BUG: describe does not distinguish between Int64 and int64 28 BUG: `pandas.DataFrame.replace` silently fails to replace category type... 29 BUG: DataFrame.describe include/exclude do not work for arrow datatypes labels 0 [{'id': 76811, 'node_id': 'MDU6TGFiZWw3NjgxMQ==', 'url': 'https://api.g... \ 1 [] 2 [] 3 [{'id': 76811, 'node_id': 'MDU6TGFiZWw3NjgxMQ==', 'url': 'https://api.g... 4 [{'id': 76812, 'node_id': 'MDU6TGFiZWw3NjgxMg==', 'url': 'https://api.g... .. ... 25 [{'id': 76811, 'node_id': 'MDU6TGFiZWw3NjgxMQ==', 'url': 'https://api.g... 26 [{'id': 3303158446, 'node_id': 'MDU6TGFiZWwzMzAzMTU4NDQ2', 'url': 'http... 27 [{'id': 76811, 'node_id': 'MDU6TGFiZWw3NjgxMQ==', 'url': 'https://api.g... 28 [{'id': 76811, 'node_id': 'MDU6TGFiZWw3NjgxMQ==', 'url': 'https://api.g... 29 [{'id': 76811, 'node_id': 'MDU6TGFiZWw3NjgxMQ==', 'url': 'https://api.g... state 0 open 1 open 2 open 3 open 4 open .. ... 25 open 26 open 27 open 28 open 29 open [30 rows x 4 columns]
通过一些努力,您可以创建一些更高级的接口,用于常见的 Web API,返回 DataFrame 对象以便进行更方便的分析。
6.4 与数据库交互
在商业环境中,许多数据可能不存储在文本或 Excel 文件中。基于 SQL 的关系数据库(如 SQL Server、PostgreSQL 和 MySQL)被广泛使用,许多替代数据库也变得非常流行。数据库的选择通常取决于应用程序的性能、数据完整性和可扩展性需求。
pandas 有一些函数可以简化将 SQL 查询结果加载到 DataFrame 中。例如,我将使用 Python 内置的sqlite3
驱动程序创建一个 SQLite3 数据库:
In [135]: import sqlite3 In [136]: query = """ .....: CREATE TABLE test .....: (a VARCHAR(20), b VARCHAR(20), .....: c REAL, d INTEGER .....: );""" In [137]: con = sqlite3.connect("mydata.sqlite") In [138]: con.execute(query) Out[138]: <sqlite3.Cursor at 0x188e40ac0> In [139]: con.commit()
然后,插入一些数据行:
In [140]: data = [("Atlanta", "Georgia", 1.25, 6), .....: ("Tallahassee", "Florida", 2.6, 3), .....: ("Sacramento", "California", 1.7, 5)] In [141]: stmt = "INSERT INTO test VALUES(?, ?, ?, ?)" In [142]: con.executemany(stmt, data) Out[142]: <sqlite3.Cursor at 0x188ed02c0> In [143]: con.commit()
大多数 Python SQL 驱动程序在从表中选择数据时返回一个元组列表:
In [144]: cursor = con.execute("SELECT * FROM test") In [145]: rows = cursor.fetchall() In [146]: rows Out[146]: [('Atlanta', 'Georgia', 1.25, 6), ('Tallahassee', 'Florida', 2.6, 3), ('Sacramento', 'California', 1.7, 5)]
您可以将元组列表传递给 DataFrame 构造函数,但还需要列名,这些列名包含在游标的description
属性中。请注意,对于 SQLite3,游标的description
仅提供列名(其他字段,这些字段是 Python 的数据库 API 规范的一部分,为None
),但对于其他一些数据库驱动程序,提供了更多的列信息:
In [147]: cursor.description Out[147]: (('a', None, None, None, None, None, None), ('b', None, None, None, None, None, None), ('c', None, None, None, None, None, None), ('d', None, None, None, None, None, None)) In [148]: pd.DataFrame(rows, columns=[x[0] for x in cursor.description]) Out[148]: a b c d 0 Atlanta Georgia 1.25 6 1 Tallahassee Florida 2.60 3 2 Sacramento California 1.70 5
这是一种相当复杂的操作,您不希望每次查询数据库时都重复。SQLAlchemy 项目是一个流行的 Python SQL 工具包,它抽象了 SQL 数据库之间的许多常见差异。pandas 有一个read_sql
函数,可以让您轻松地从通用的 SQLAlchemy 连接中读取数据。您可以像这样使用 conda 安装 SQLAlchemy:
conda install sqlalchemy • 1
现在,我们将使用 SQLAlchemy 连接到相同的 SQLite 数据库,并从之前创建的表中读取数据:
In [149]: import sqlalchemy as sqla In [150]: db = sqla.create_engine("sqlite:///mydata.sqlite") In [151]: pd.read_sql("SELECT * FROM test", db) Out[151]: a b c d 0 Atlanta Georgia 1.25 6 1 Tallahassee Florida 2.60 3 2 Sacramento California 1.70 5
6.5 结论
获取数据通常是数据分析过程中的第一步。在本章中,我们已经介绍了一些有用的工具,这些工具应该可以帮助您入门。在接下来的章节中,我们将深入探讨数据整理、数据可视化、时间序列分析等主题。
七、数据清理和准备
原文:
wesmckinney.com/book/data-cleaning
译者:飞龙
此开放访问网络版本的《Python 数据分析第三版》现已作为印刷版和数字版的伴侣提供。如果您发现任何勘误,请在此处报告。请注意,由 Quarto 生成的本站点的某些方面与 O’Reilly 的印刷版和电子书版本的格式不同。
如果您发现本书的在线版本有用,请考虑订购纸质版或无 DRM 的电子书以支持作者。本网站的内容不得复制或再生产。代码示例采用 MIT 许可,可在 GitHub 或 Gitee 上找到。
在进行数据分析和建模过程中,大量时间花费在数据准备上:加载、清理、转换和重新排列。这些任务通常被报告为占据分析师 80%或更多的时间。有时,文件或数据库中存储数据的方式并不适合特定任务。许多研究人员选择使用通用编程语言(如 Python、Perl、R 或 Java)或 Unix 文本处理工具(如 sed 或 awk)对数据进行自发处理,从一种形式转换为另一种形式。幸运的是,pandas 与内置的 Python 语言功能一起,为您提供了一套高级、灵活和快速的工具,使您能够将数据转换为正确的形式。
如果您发现在本书或 pandas 库中找不到的数据操作类型,请随时在 Python 邮件列表或 pandas GitHub 网站上分享您的用例。事实上,pandas 的设计和实现很大程度上是由真实应用程序的需求驱动的。
在本章中,我讨论了有关缺失数据、重复数据、字符串操作和其他一些分析数据转换的工具。在下一章中,我将专注于以各种方式组合和重新排列数据集。
7.1 处理缺失数据
缺失数据在许多数据分析应用中很常见。pandas 的目标之一是尽可能地使处理缺失数据变得轻松。例如,默认情况下,pandas 对象上的所有描述性统计都排除缺失数据。
pandas 对象中表示缺失数据的方式有些不完美,但对于大多数真实世界的用途来说是足够的。对于float64
数据类型,pandas 使用浮点值NaN
(Not a Number)表示缺失数据。
我们称之为标记值:当存在时,表示缺失(或空)值:
In [14]: float_data = pd.Series([1.2, -3.5, np.nan, 0]) In [15]: float_data Out[15]: 0 1.2 1 -3.5 2 NaN 3 0.0 dtype: float64
isna
方法为我们提供一个布尔 Series,其中值为空时为True
:
In [16]: float_data.isna() Out[16]: 0 False 1 False 2 True 3 False dtype: bool
在 pandas 中,我们采用了 R 编程语言中使用的惯例,将缺失数据称为 NA,代表不可用。在统计应用中,NA 数据可能是不存在的数据,也可能是存在但未被观察到的数据(例如通过数据收集问题)。在清理数据进行分析时,通常重要的是对缺失数据本身进行分析,以识别数据收集问题或由缺失数据引起的数据潜在偏差。
内置的 Python None
值也被视为 NA:
In [17]: string_data = pd.Series(["aardvark", np.nan, None, "avocado"]) In [18]: string_data Out[18]: 0 aardvark 1 NaN 2 None 3 avocado dtype: object In [19]: string_data.isna() Out[19]: 0 False 1 True 2 True 3 False dtype: bool In [20]: float_data = pd.Series([1, 2, None], dtype='float64') In [21]: float_data Out[21]: 0 1.0 1 2.0 2 NaN dtype: float64 In [22]: float_data.isna() Out[22]: 0 False 1 False 2 True dtype: bool
pandas 项目已经尝试使处理缺失数据在不同数据类型之间保持一致。像pandas.isna
这样的函数抽象了许多烦人的细节。请参阅表 7.1 以获取与处理缺失数据相关的一些函数列表。
表 7.1:NA 处理对象方法
方法 | 描述 |
dropna |
根据每个标签的值是否具有缺失数据来过滤轴标签,对于可以容忍多少缺失数据有不同的阈值。 |
fillna |
使用某个值或插值方法(如 "ffill" 或 "bfill" )填充缺失数据。 |
isna |
返回指示哪些值缺失/NA 的布尔值。 |
notna |
isna 的否定,对于非 NA 值返回 True ,对于 NA 值返回 False 。 |
过滤缺失数据
有几种过滤缺失数据的方法。虽然您始终可以选择使用 pandas.isna
和布尔索引手动执行,但 dropna
可能会有所帮助。对于 Series,它返回仅具有非空数据和索引值的 Series:
In [23]: data = pd.Series([1, np.nan, 3.5, np.nan, 7]) In [24]: data.dropna() Out[24]: 0 1.0 2 3.5 4 7.0 dtype: float64
这与执行以下操作相同:
In [25]: data[data.notna()] Out[25]: 0 1.0 2 3.5 4 7.0 dtype: float64
对于 DataFrame 对象,有不同的方法可以删除缺失数据。您可能希望删除所有 NA 的行或列,或者仅删除包含任何 NA 的行或列。dropna
默认情况下会删除包含缺失值的任何行:
In [26]: data = pd.DataFrame([[1., 6.5, 3.], [1., np.nan, np.nan], ....: [np.nan, np.nan, np.nan], [np.nan, 6.5, 3.]]) In [27]: data Out[27]: 0 1 2 0 1.0 6.5 3.0 1 1.0 NaN NaN 2 NaN NaN NaN 3 NaN 6.5 3.0 In [28]: data.dropna() Out[28]: 0 1 2 0 1.0 6.5 3.0
传递 how="all"
将仅删除所有 NA 的行:
In [29]: data.dropna(how="all") Out[29]: 0 1 2 0 1.0 6.5 3.0 1 1.0 NaN NaN 3 NaN 6.5 3.0
请记住,这些函数默认情况下返回新对象,不会修改原始对象的内容。
要以相同方式删除列,请传递 axis="columns"
:
In [30]: data[4] = np.nan In [31]: data Out[31]: 0 1 2 4 0 1.0 6.5 3.0 NaN 1 1.0 NaN NaN NaN 2 NaN NaN NaN NaN 3 NaN 6.5 3.0 NaN In [32]: data.dropna(axis="columns", how="all") Out[32]: 0 1 2 0 1.0 6.5 3.0 1 1.0 NaN NaN 2 NaN NaN NaN 3 NaN 6.5 3.0
假设您只想保留包含至多一定数量缺失观察的行。您可以使用 thresh
参数指示这一点:
In [33]: df = pd.DataFrame(np.random.standard_normal((7, 3))) In [34]: df.iloc[:4, 1] = np.nan In [35]: df.iloc[:2, 2] = np.nan In [36]: df Out[36]: 0 1 2 0 -0.204708 NaN NaN 1 -0.555730 NaN NaN 2 0.092908 NaN 0.769023 3 1.246435 NaN -1.296221 4 0.274992 0.228913 1.352917 5 0.886429 -2.001637 -0.371843 6 1.669025 -0.438570 -0.539741 In [37]: df.dropna() Out[37]: 0 1 2 4 0.274992 0.228913 1.352917 5 0.886429 -2.001637 -0.371843 6 1.669025 -0.438570 -0.539741 In [38]: df.dropna(thresh=2) Out[38]: 0 1 2 2 0.092908 NaN 0.769023 3 1.246435 NaN -1.296221 4 0.274992 0.228913 1.352917 5 0.886429 -2.001637 -0.371843 6 1.669025 -0.438570 -0.539741
填充缺失数据
与过滤缺失数据(并可能连同其他数据一起丢弃)不同,您可能希望以任意方式填补任意数量的“空洞”。对于大多数情况,fillna
方法是要使用的主要函数。通过使用常量调用 fillna
可以用该值替换缺失值:
In [39]: df.fillna(0) Out[39]: 0 1 2 0 -0.204708 0.000000 0.000000 1 -0.555730 0.000000 0.000000 2 0.092908 0.000000 0.769023 3 1.246435 0.000000 -1.296221 4 0.274992 0.228913 1.352917 5 0.886429 -2.001637 -0.371843 6 1.669025 -0.438570 -0.539741
通过字典调用 fillna
,您可以为每列使用不同的填充值:
In [40]: df.fillna({1: 0.5, 2: 0}) Out[40]: 0 1 2 0 -0.204708 0.500000 0.000000 1 -0.555730 0.500000 0.000000 2 0.092908 0.500000 0.769023 3 1.246435 0.500000 -1.296221 4 0.274992 0.228913 1.352917 5 0.886429 -2.001637 -0.371843 6 1.669025 -0.438570 -0.539741
可用于重新索引的相同插值方法(请参见 表 5.3)也可用于 fillna
:
In [41]: df = pd.DataFrame(np.random.standard_normal((6, 3))) In [42]: df.iloc[2:, 1] = np.nan In [43]: df.iloc[4:, 2] = np.nan In [44]: df Out[44]: 0 1 2 0 0.476985 3.248944 -1.021228 1 -0.577087 0.124121 0.302614 2 0.523772 NaN 1.343810 3 -0.713544 NaN -2.370232 4 -1.860761 NaN NaN 5 -1.265934 NaN NaN In [45]: df.fillna(method="ffill") Out[45]: 0 1 2 0 0.476985 3.248944 -1.021228 1 -0.577087 0.124121 0.302614 2 0.523772 0.124121 1.343810 3 -0.713544 0.124121 -2.370232 4 -1.860761 0.124121 -2.370232 5 -1.265934 0.124121 -2.370232 In [46]: df.fillna(method="ffill", limit=2) Out[46]: 0 1 2 0 0.476985 3.248944 -1.021228 1 -0.577087 0.124121 0.302614 2 0.523772 0.124121 1.343810 3 -0.713544 0.124121 -2.370232 4 -1.860761 NaN -2.370232 5 -1.265934 NaN -2.370232
使用 fillna
,您可以做很多其他事情,比如使用中位数或平均统计数据进行简单的数据填充:
In [47]: data = pd.Series([1., np.nan, 3.5, np.nan, 7]) In [48]: data.fillna(data.mean()) Out[48]: 0 1.000000 1 3.833333 2 3.500000 3 3.833333 4 7.000000 dtype: float64
请参见 表 7.2 了解 fillna
函数参数的参考。
表 7.2:fillna
函数参数
参数 | 描述 |
value |
用于填充缺失值的标量值或类似字典的对象 |
method |
插值方法:可以是 "bfill" (向后填充)或 "ffill" (向前填充)之一;默认为 None |
axis |
填充的轴("index" 或 "columns" );默认为 axis="index" |
limit |
对于向前和向后填充,最大连续填充周期数 |
7.2 数据转换
到目前为止,在本章中,我们一直关注处理缺失数据。过滤、清理和其他转换是另一类重要操作。
删除重复项
DataFrame 中可能会出现重复行,原因有很多。这里是一个例子:
In [49]: data = pd.DataFrame({"k1": ["one", "two"] * 3 + ["two"], ....: "k2": [1, 1, 2, 3, 3, 4, 4]}) In [50]: data Out[50]: k1 k2 0 one 1 1 two 1 2 one 2 3 two 3 4 one 3 5 two 4 6 two 4
DataFrame 方法 duplicated
返回一个布尔 Series,指示每行是否为重复行(其列值与较早行中的值完全相等):
In [51]: data.duplicated() Out[51]: 0 False 1 False 2 False 3 False 4 False 5 False 6 True dtype: bool
相关地,drop_duplicates
返回一个 DataFrame,其中过滤掉 duplicated
数组为 False
的行:
In [52]: data.drop_duplicates() Out[52]: k1 k2 0 one 1 1 two 1 2 one 2 3 two 3 4 one 3 5 two 4
默认情况下,这两种方法都考虑所有列;或者,您可以指定任何子集来检测重复项。假设我们有一个额外的值列,并且只想基于 "k1"
列过滤重复项:
In [53]: data["v1"] = range(7) In [54]: data Out[54]: k1 k2 v1 0 one 1 0 1 two 1 1 2 one 2 2 3 two 3 3 4 one 3 4 5 two 4 5 6 two 4 6 In [55]: data.drop_duplicates(subset=["k1"]) Out[55]: k1 k2 v1 0 one 1 0 1 two 1 1
duplicated
和 drop_duplicates
默认保留第一个观察到的值组合。传递 keep="last"
将返回最后一个:
In [56]: data.drop_duplicates(["k1", "k2"], keep="last") Out[56]: k1 k2 v1 0 one 1 0 1 two 1 1 2 one 2 2 3 two 3 3 4 one 3 4 6 two 4 6
使用函数或映射转换数据
对于许多数据集,您可能希望根据数组、Series 或 DataFrame 中的值执行一些基于值的转换。考虑收集的关于各种肉类的假设数据:
In [57]: data = pd.DataFrame({"food": ["bacon", "pulled pork", "bacon", ....: "pastrami", "corned beef", "bacon", ....: "pastrami", "honey ham", "nova lox"], ....: "ounces": [4, 3, 12, 6, 7.5, 8, 3, 5, 6]}) In [58]: data Out[58]: food ounces 0 bacon 4.0 1 pulled pork 3.0 2 bacon 12.0 3 pastrami 6.0 4 corned beef 7.5 5 bacon 8.0 6 pastrami 3.0 7 honey ham 5.0 8 nova lox 6.0
假设您想要添加一个指示每种食物来自哪种动物的列。让我们写下每种不同肉类到动物种类的映射:
meat_to_animal = { "bacon": "pig", "pulled pork": "pig", "pastrami": "cow", "corned beef": "cow", "honey ham": "pig", "nova lox": "salmon" }
Series 上的 map
方法(也在 Ch 5.2.5: 函数应用和映射 中讨论)接受一个包含映射的函数或类似字典的对象,用于对值进行转换:
In [60]: data["animal"] = data["food"].map(meat_to_animal) In [61]: data Out[61]: food ounces animal 0 bacon 4.0 pig 1 pulled pork 3.0 pig 2 bacon 12.0 pig 3 pastrami 6.0 cow 4 corned beef 7.5 cow 5 bacon 8.0 pig 6 pastrami 3.0 cow 7 honey ham 5.0 pig 8 nova lox 6.0 salmon
我们也可以传递一个执行所有工作的函数:
In [62]: def get_animal(x): ....: return meat_to_animal[x] In [63]: data["food"].map(get_animal) Out[63]: 0 pig 1 pig 2 pig 3 cow 4 cow 5 pig 6 cow 7 pig 8 salmon Name: food, dtype: object
使用 map
是执行逐元素转换和其他数据清理相关操作的便捷方式。
替换值
使用 fillna
方法填充缺失数据是更一般的值替换的特殊情况。正如您已经看到的,map
可以用于修改对象中的一部分值,但 replace
提供了一种更简单、更灵活的方法。让我们考虑这个 Series:
In [64]: data = pd.Series([1., -999., 2., -999., -1000., 3.]) In [65]: data Out[65]: 0 1.0 1 -999.0 2 2.0 3 -999.0 4 -1000.0 5 3.0 dtype: float64
-999
值可能是缺失数据的标记值。要用 pandas 理解的 NA 值替换这些值,可以使用 replace
,生成一个新的 Series:
In [66]: data.replace(-999, np.nan) Out[66]: 0 1.0 1 NaN 2 2.0 3 NaN 4 -1000.0 5 3.0 dtype: float64
如果您想一次替换多个值,可以传递一个列表,然后是替代值:
In [67]: data.replace([-999, -1000], np.nan) Out[67]: 0 1.0 1 NaN 2 2.0 3 NaN 4 NaN 5 3.0 dtype: float64
要为每个值使用不同的替代值,传递一个替代列表:
In [68]: data.replace([-999, -1000], [np.nan, 0]) Out[68]: 0 1.0 1 NaN 2 2.0 3 NaN 4 0.0 5 3.0 dtype: float64
传递的参数也可以是一个字典:
In [69]: data.replace({-999: np.nan, -1000: 0}) Out[69]: 0 1.0 1 NaN 2 2.0 3 NaN 4 0.0 5 3.0 dtype: float64
注意
data.replace
方法与 data.str.replace
是不同的,后者执行逐元素的字符串替换。我们将在本章后面的 Series 中查看这些字符串方法。
重命名轴索引
与 Series 中的值类似,轴标签也可以通过函数或某种形式的映射进行类似转换,以生成新的、不同标记的对象。您还可以在原地修改轴,而不创建新的数据结构。这是一个简单的例子:
In [70]: data = pd.DataFrame(np.arange(12).reshape((3, 4)), ....: index=["Ohio", "Colorado", "New York"], ....: columns=["one", "two", "three", "four"])
与 Series 一样,轴索引具有 map
方法:
In [71]: def transform(x): ....: return x[:4].upper() In [72]: data.index.map(transform) Out[72]: Index(['OHIO', 'COLO', 'NEW '], dtype='object')
您可以分配给 index
属性,直接修改 DataFrame:
In [73]: data.index = data.index.map(transform) In [74]: data Out[74]: one two three four OHIO 0 1 2 3 COLO 4 5 6 7 NEW 8 9 10 11
如果要创建一个转换后的数据集副本而不修改原始数据集,一个有用的方法是 rename
:
In [75]: data.rename(index=str.title, columns=str.upper) Out[75]: ONE TWO THREE FOUR Ohio 0 1 2 3 Colo 4 5 6 7 New 8 9 10 11
值得注意的是,rename
可以与类似字典的对象一起使用,为轴标签的子集提供新值:
In [76]: data.rename(index={"OHIO": "INDIANA"}, ....: columns={"three": "peekaboo"}) Out[76]: one two peekaboo four INDIANA 0 1 2 3 COLO 4 5 6 7 NEW 8 9 10 11
rename
可以避免手动复制 DataFrame 并为其 index
和 columns
属性分配新值的繁琐工作。
Python 数据分析(PYDA)第三版(三)(3)https://developer.aliyun.com/article/1482382