一、引言
原文:Introduction
译者:飞龙
学习成果
-
了解 Data 100 的总体目标
-
了解数据科学生命周期的阶段
数据科学是一个跨学科领域,具有各种应用,并且在解决具有挑战性的社会问题方面具有巨大潜力。通过建立数据科学技能,您可以赋予自己参与和引领塑造您的生活和整个社会对话的能力,无论是与气候变化作斗争、推出多样性倡议,还是其他方面。
这个领域正在迅速发展;现代数据科学中许多关键技术基础在 21 世纪初得到了普及。
它基本上是以人为中心的,并通过定量平衡权衡来促进决策。为了可靠地量化事物,我们必须适当地使用和分析数据,对每一步都要进行批判性思考和怀疑,并考虑我们的决定如何影响他人。
最终,数据科学是将以数据为中心的、计算性的和推理性的思维应用于:
-
了解世界(科学)。
-
解决问题(工程)。
对数据科学的真正掌握需要深刻的理论理解和对领域专业知识的牢固掌握。本课程将帮助您建立在前者基础上的技术知识,使您能够获取数据并对世界上最具挑战性和模糊的问题产生有用的见解。
课程目标
-
为您准备伯克利高级课程,包括数据管理、机器学习和统计学
-
使您能够在数据科学领域开展职业生涯
-
使您能够通过计算和推理思维解决现实世界的问题
我们将涵盖的一些主题
-
Pandas 和 NumPy
-
探索性数据分析
-
正则表达式
-
可视化
-
抽样
-
模型设计和损失公式
-
线性回归
-
梯度下降
-
逻辑回归
-
还有更多!
为了让您成功,我们将 Data 100 中的概念组织成了数据科学生命周期:一个迭代过程,涵盖了数据科学的各种统计和计算构建模块。
1.1 数据科学生命周期
数据科学生命周期是对数据科学工作流程的高级概述。这是一个数据科学家在对数据驱动的问题进行彻底分析时应该探索的阶段循环。
数据科学生命周期中存在许多关键思想的变体。在 Data 100 中,我们使用流程图来可视化生命周期的各个阶段。请注意,有两个入口点。
1.1.1 提出问题
无论是出于好奇还是出于必要,数据科学家不断提出问题。例如,在商业世界中,数据科学家可能对预测某项投资产生的利润感兴趣。在医学领域,他们可能会问一些患者是否比其他人更有可能从治疗中受益。
提出问题是数据科学生命周期开始的主要方式之一。它有助于充分定义问题。在构建问题之前,以下是一些您应该问自己的事情。
-
我们想要知道什么?
- 一个过于模糊的问题可能会导致混乱。
-
我们试图解决什么问题?
- 问一个问题的目标应该是清晰的,以便为利益相关者的努力提供合理的理由。
-
我们想要测试的假设是什么?
- 这为我们提供了一个清晰的视角,以分析最终结果。
-
我们的成功指标是什么?
- 这为我们建立了一个明确的观点,知道何时结束项目。
1.1.2 获取数据
生命周期的第二个入口是通过获取数据。对任何问题的仔细分析都需要使用数据。数据可能对我们而言是 readily available,或者我们可能不得不着手收集数据。在这样做时,至关重要的是要问以下问题:
-
我们有什么数据,我们需要什么数据?
- 定义数据的单位(人、城市、时间点等)和要测量的特征。
-
我们如何取样更多的数据?
- 抓取网页,手动收集,进行实验等。
-
我们的数据是否代表我们想研究的人群?
- 如果我们的数据不代表我们感兴趣的人群,那么我们可能得出错误的结论。
关键程序:数据获取,数据清洗
1.1.3 理解数据
原始数据本身并不具有固有的用处。如果不仔细调查,就不可能辨别出所有变量之间的模式和关系。因此,将纯数据转化为可操作的见解是数据科学家的一项关键工作。例如,我们可以选择问:
-
我们的数据是如何组织的,它包含了什么?
- 了解数据对世界有何影响有助于我们更好地理解世界。
-
我们有相关的数据吗?
- 如果我们收集的数据对于手头的问题没有用处,那么我们必须收集更多的数据。
-
数据中存在什么偏见、异常或其他问题?
- 如果忽视这些问题,可能会导致许多错误的结论,因此数据科学家必须始终注意这些问题。
-
我们如何转换数据以进行有效分析?
- 数据并不总是一眼就容易解释的,因此数据科学家应该努力揭示隐藏的见解。
关键程序:探索性数据分析,数据可视化。
1.1.4 理解世界
在观察了数据中的模式之后,我们可以开始回答我们的问题。这可能需要我们预测一个数量(机器学习),或者衡量某种处理的效果(推断)。
从这里,我们可以选择报告我们的结果,或者可能进行更多的分析。我们可能对我们的发现不满意,或者我们的初步探索可能提出了需要新数据的新问题。
-
数据对世界有何影响?
- 根据我们的模型,数据将引导我们对真实世界的某些结论。
-
它是否回答了我们的问题或准确解决了问题?
- 如果我们的模型和数据不能实现我们的目标,那么我们必须改革我们的问题、模型,或者两者兼而有之。
-
我们的结论有多可靠,我们能相信这些预测吗?
- 不准确的模型可能导致错误的结论。
关键程序:模型创建,预测,推断。
1.2 结论
数据科学生命周期旨在成为一组一般性指导方针,而不是一套硬性要求。在探索生命周期的过程中,我们将涵盖数据科学中使用的基本理论和技术。在课程结束时,我们希望您开始把自己看作是一名数据科学家。
因此,我们将首先介绍探索性数据分析中最重要的工具之一:pandas。
二、Pandas I
原文:Pandas I
译者:飞龙
学习成果
-
建立对
pandas和pandas语法的熟悉度。 -
学习关键数据结构:
DataFrame、Series和Index。 -
了解提取数据的方法:
.loc、.iloc和[]。
在这一系列讲座中,我们将让您直接探索和操纵真实世界的数据。我们将首先介绍pandas,这是一个流行的 Python 库,用于与表格数据交互。
2.1 表格数据
数据科学家使用各种格式存储的数据。本课程的主要重点是理解表格数据——存储在表格中的数据。
表格数据是数据科学家用来组织数据的最常见系统之一。这在很大程度上是因为表格的简单性和灵活性。表格允许我们将每个观察,或者从个体收集数据的实例,表示为其自己的行。我们可以将每个观察的不同特征,或者特征,记录在单独的列中。
为了看到这一点,我们将探索elections数据集,该数据集存储了以前年份竞选美国总统的政治候选人的信息。
代码
import pandas as pd
pd.read_csv("data/elections.csv")
| Year | Candidate | Party | Popular vote | Result | % | ||
|---|---|---|---|---|---|---|---|
| 0 | 1824 | Andrew Jackson | Democratic-Republican | 151271 | loss | 57.210122 | |
| 1 | 1824 | John Quincy Adams | Democratic-Republican | 113142 | win | 42.789878 | |
| 2 | 1828 | Andrew Jackson | Democratic | 642806 | win | 56.203927 | |
| 3 | 1828 | John Quincy Adams | National Republican | 500897 | loss | 43.796073 | |
| 4 | 1832 | Andrew Jackson | Democratic | 702735 | win | 54.574789 | |
| ... | ... | ... | ... | ... | ... | ... | |
| 177 | 2016 | Jill Stein | Green | 1457226 | loss | 1.073699 | |
| 178 | 2020 | Joseph Biden | Democratic | 81268924 | win | 51.311515 | |
| 179 | 2020 | Donald Trump | Republican | 74216154 | loss | 46.858542 | |
| 180 | 2020 | Jo Jorgensen | Libertarian | 1865724 | loss | 1.177979 | |
| 181 | 2020 | Howard Hawkins | Green | 405035 | loss | 0.255731 |
182 行×6 列
在elections数据集中,每一行代表一个候选人在特定年份竞选总统的一个实例。例如,第一行代表安德鲁·杰克逊在 1824 年竞选总统。每一列代表每个总统候选人的一个特征信息。例如,名为“结果”的列存储候选人是否赢得选举。
你在 Data 8 中的工作帮助你非常熟悉使用和解释以表格格式存储的数据。那时,你使用了datascience库的Table类,这是专门为 Data 8 学生创建的特殊编程库。
在 Data 100 中,我们将使用编程库pandas,这在数据科学界被普遍接受为操纵表格数据的行业和学术标准工具(也是我们熊猫吉祥物 Petey 的灵感来源)。
使用pandas,我们可以
-
以表格格式排列数据。
-
提取由特定条件过滤的有用信息。
-
对数据进行操作以获得新的见解。
-
将
NumPy函数应用于我们的数据(我们来自 Data 8 的朋友)。 -
执行矢量化计算以加快我们的分析速度(实验室 1)。
2.2 Series、DataFrame和索引
要开始我们在pandas中的工作,我们必须首先将库导入到我们的 Python 环境中。这将允许我们在我们的代码中使用pandas数据结构和方法。
# `pd` is the conventional alias for Pandas, as `np` is for NumPy
import pandas as pd
pandas中有三种基本数据结构:
-
Series:1D 带标签的数组数据;最好将其视为列数据。
-
DataFrame:带有行和列的 2D 表格数据。
-
索引:一系列行/列标签。
DataFrame,Series和索引可以在以下图表中以可视化方式表示,该图表考虑了elections数据集的前几行。
注意DataFrame是一个二维对象——它包含行和列。上面的Series是这个DataFrame的一个单独的列,即Result列。两者都包含一个索引,或者共享的行标签列表(从 0 到 4 的整数,包括 0)。
2.2.1 系列
Series 表示DataFrame的一列;更一般地,它可以是任何 1 维类似数组的对象。它包含:
-
相同类型的值序列。
-
索引称为数据标签的序列。
在下面的单元格中,我们创建了一个名为s的Series。
s = pd.Series(["welcome", "to", "data 100"])
s
0 welcome
1 to
2 data 100
dtype: object
s.values # Data values contained within the Series
array(['welcome', 'to', 'data 100'], dtype=object)
s.index # The Index of the Series
RangeIndex(start=0, stop=3, step=1)
默认情况下,Series 的索引是从 0 开始的整数的顺序列表。可以将所需索引的手动指定列表传递给index参数。
s = pd.Series([-1, 10, 2], index = ["a", "b", "c"])
s
a -1
b 10
c 2
dtype: int64
s.index
Index(['a', 'b', 'c'], dtype='object')
初始化后也可以更改索引。
s.index = ["first", "second", "third"]
s
first -1
second 10
third 2
dtype: int64
s.index
Index(['first', 'second', 'third'], dtype='object')
2.2.1.1 Series中的选择
就像在使用NumPy数组时一样,我们可以从Series中选择单个值或一组值。为此,有三种主要方法:
-
单个标签。
-
标签列表。
-
过滤条件。
为了证明这一点,让我们定义ser系列。
ser = pd.Series([4, -2, 0, 6], index = ["a", "b", "c", "d"])
ser
a 4
b -2
c 0
d 6
dtype: int64
2.2.1.1.1 单个标签
ser["a"] # We return the value stored at the Index label "a"
4
2.2.1.1.2 标签列表
ser[["a", "c"]] # We return a *Series* of the values stored at the Index labels "a" and "c"
a 4
c 0
dtype: int64
2.2.1.1.3 过滤条件
也许从Series中选择数据的最有趣(和有用)的方法是使用过滤条件。
首先,我们对Series应用布尔运算。这将创建一个新的布尔值系列。
ser > 0 # Filter condition: select all elements greater than 0
a True
b False
c False
d True
dtype: bool
然后,我们使用这个布尔条件来索引我们原始的Series。pandas将只选择原始Series中满足条件的条目。
ser[ser > 0]
a 4
d 6
dtype: int64
2.2.2 数据框
通常,我们将使用Series的角度来处理它们,认为它们是DataFrame中的列。我们可以将DataFrame视为所有共享相同索引的Series的集合。
在 Data 8 中,您遇到了datascience库的Table类,它表示表格数据。在 Data 100 中,我们将使用pandas库的DataFrame类。
2.2.2.1 创建DataFrame
有许多创建DataFrame的方法。在这里,我们将介绍最流行的方法:
-
从 CSV 文件中。
-
使用列名和列表。
-
从字典中。
-
从
Series中。
更一般地,创建DataFrame的语法是:pandas.DataFrame(data, index, columns)。
2.2.2.1.1 从 CSV 文件中
在 Data 100 中,我们的数据通常以 CSV(逗号分隔值)文件格式存储。我们可以通过将数据路径作为参数传递给以下pandas函数来将 CSV 文件导入DataFrame。
pd.read_csv("filename.csv")
现在,我们可以认识到pandas DataFrame 表示的是elections数据集。
elections = pd.read_csv("data/elections.csv")
elections
| Year | Candidate | Party | Popular vote | Result | % | |
|---|---|---|---|---|---|---|
| 0 | 1824 | Andrew Jackson | Democratic-Republican | 151271 | loss | 57.210122 |
| 1 | 1824 | John Quincy Adams | Democratic-Republican | 113142 | win | 42.789878 |
| 2 | 1828 | Andrew Jackson | Democratic | 642806 | win | 56.203927 |
| 3 | 1828 | John Quincy Adams | National Republican | 500897 | loss | 43.796073 |
| 4 | 1832 | Andrew Jackson | Democratic | 702735 | win | 54.574789 |
| ... | ... | ... | ... | ... | ... | ... |
| 177 | 2016 | Jill Stein | Green | 1457226 | loss | 1.073699 |
| 178 | 2020 | Joseph Biden | Democratic | 81268924 | win | 51.311515 |
| 179 | 2020 | Donald Trump | Republican | 74216154 | loss | 46.858542 |
| 180 | 2020 | Jo Jorgensen | Libertarian | 1865724 | loss | 1.177979 |
| 181 | 2020 | Howard Hawkins | Green | 405035 | loss | 0.255731 |
182 行×6 列
这段代码将我们的“DataFrame”对象存储在“选举”变量中。经过检查,我们的“选举”DataFrame 有 182 行和 6 列(“年份”,“候选人”,“党派”,“普选票”,“结果”,“%”)。每一行代表一条记录——在我们的例子中,是某一年的总统候选人。每一列代表记录的一个属性或特征。
2.2.2.1.2 使用列表和列名
我们现在将探讨如何使用我们自己的数据创建“DataFrame”。
考虑以下例子。第一个代码单元创建了一个只有一个列“Numbers”的“DataFrame”。第二个创建了一个有“Numbers”和“Description”两列的“DataFrame”。请注意,需要一个二维值列表来初始化第二个“DataFrame”——每个嵌套列表代表一行数据。
df_list = pd.DataFrame([1, 2, 3], columns=["Numbers"])
df_list
| Numbers | |
|---|---|
| 0 | 1 |
| 1 | 2 |
| 2 | 3 |
df_list = pd.DataFrame([[1, "one"], [2, "two"]], columns = ["Number", "Description"])
df_list
| Numbers | Description | |
|---|---|---|
| 0 | 1 | one |
| 1 | 2 | two |
2.2.2.1.3 从字典
第三种(更常见的)创建“DataFrame”的方法是使用字典。字典的键代表列名,字典的值代表列的值。
以下是实现这种方法的两种方式。第一种是基于指定“DataFrame”的列,而第二种是基于指定“DataFrame”的行。
df_dict = pd.DataFrame({"Fruit": ["Strawberry", "Orange"], "Price": [5.49, 3.99]})
df_dict
| Fruit | Price | |
|---|---|---|
| 0 | Strawberry | 5.49 |
| 1 | Orange | 3.99 |
df_dict = pd.DataFrame([{"Fruit":"Strawberry", "Price":5.49}, {"Fruit": "Orange", "Price":3.99}])
df_dict
| Fruit | Price | |
|---|---|---|
| 0 | Strawberry | 5.49 |
| 1 | Orange | 3.99 |
2.2.2.1.4 从“Series”
早些时候,我们解释了“Series”与“DataFrame”中的列是同义词。因此,“DataFrame”相当于共享相同索引的“Series”集合。
事实上,我们可以通过合并两个或更多的“Series”来初始化“DataFrame”。
# Notice how our indices, or row labels, are the same
s_a = pd.Series(["a1", "a2", "a3"], index = ["r1", "r2", "r3"])
s_b = pd.Series(["b1", "b2", "b3"], index = ["r1", "r2", "r3"])
pd.DataFrame({"A-column": s_a, "B-column": s_b})
| A-column | B-column | |
|---|---|---|
| r1 | a1 | b1 |
| r2 | a2 | b2 |
| r3 | a3 | b3 |
pd.DataFrame(s_a)
| 0 | |
|---|---|
| r1 | a1 |
| r2 | a2 |
| r3 | a3 |
s_a.to_frame()
| 0 | |
|---|---|
| r1 | a1 |
| r2 | a2 |
| r3 | a3 |
2.2.3 索引
在技术上,索引不一定是整数,也不一定是唯一的。例如,我们可以将“选举”DataFrame 的索引设置为总统候选人的名字。
# Creating a DataFrame from a CSV file and specifying the Index column
elections = pd.read_csv("data/elections.csv", index_col = "Candidate")
elections
| Year | Party | Popular vote | Result | % | |
|---|---|---|---|---|---|
| Candidate | |||||
| Andrew Jackson | 1824 | Democratic-Republican | 151271 | loss | 57.210122 |
| John Quincy Adams | 1824 | Democratic-Republican | 113142 | win | 42.789878 |
| Andrew Jackson | 1828 | Democratic | 642806 | win | 56.203927 |
| John Quincy Adams | 1828 | National Republican | 500897 | loss | 43.796073 |
| Andrew Jackson | 1832 | Democratic | 702735 | win | 54.574789 |
| ... | ... | ... | ... | ... | ... |
| Jill Stein | 2016 | Green | 1457226 | loss | 1.073699 |
| Joseph Biden | 2020 | Democratic | 81268924 | win | 51.311515 |
| Donald Trump | 2020 | Republican | 74216154 | loss | 46.858542 |
| Jo Jorgensen | 2020 | Libertarian | 1865724 | loss | 1.177979 |
| Howard Hawkins | 2020 | Green | 405035 | loss | 0.255731 |
182 行×5 列
我们还可以选择一个新的列,并将其设置为 DataFrame 的索引。例如,我们可以将“选举”DataFrame 的索引设置为候选人的党派。
elections.reset_index(inplace = True) # Resetting the index so we can set the Index again
# This sets the index to the "Party" column
elections.set_index("Party")
| Candidate | Year | Popular vote | Result | % | ||
|---|---|---|---|---|---|---|
| Party | ||||||
| Democratic-Republican | Andrew Jackson | 1824 | 151271 | loss | 57.210122 | |
| Democratic-Republican | John Quincy Adams | 1824 | 113142 | win | 42.789878 | |
| Democratic | Andrew Jackson | 1828 | 642806 | win | 56.203927 | |
| National Republican | John Quincy Adams | 1828 | 500897 | loss | 43.796073 | |
| Democratic | Andrew Jackson | 1832 | 702735 | win | 54.574789 | |
| ... | ... | ... | ... | ... | ... | |
| Green | Jill Stein | 2016 | 1457226 | loss | 1.073699 | |
| Democratic | Joseph Biden | 2020 | 81268924 | win | 51.311515 | |
| Republican | Donald Trump | 2020 | 74216154 | loss | 46.858542 | |
| Libertarian | Jo Jorgensen | 2020 | 1865724 | loss | 1.177979 | |
| Green | Howard Hawkins | 2020 | 405035 | loss | 0.255731 |
182 行×5 列
如果需要,我们可以将索引恢复为默认的整数列表。
# This resets the index to be the default list of integer
elections.reset_index(inplace=True)
elections.index
RangeIndex(start=0, stop=182, step=1)
还需要注意的是,构成索引的行标签不一定是唯一的。虽然索引值可以是唯一的和数字的,充当行号,但它们也可以是命名的和非唯一的。
这里我们看到唯一和数字的索引值。
然而,这里的索引值是非唯一的。
2.3 DataFrame属性:索引、列和形状
另一方面,DataFrame中的列名几乎总是唯一的。回顾elections数据集,有两列命名为“Candidate”是没有意义的。
有时,您可能希望提取这些不同的值,特别是行和列标签的列表。
对于索引/行标签,请使用DataFrame.index:
elections.set_index("Party", inplace = True)
elections.index
Index(['Democratic-Republican', 'Democratic-Republican', 'Democratic',
'National Republican', 'Democratic', 'National Republican',
'Anti-Masonic', 'Whig', 'Democratic', 'Whig',
...
'Constitution', 'Republican', 'Independent', 'Libertarian',
'Democratic', 'Green', 'Democratic', 'Republican', 'Libertarian',
'Green'],
dtype='object', name='Party', length=182)
对于列标签,请使用DataFrame.columns:
elections.columns
Index(['index', 'Candidate', 'Year', 'Popular vote', 'Result', '%'], dtype='object')
对于 DataFrame 的形状,我们可以使用DataFrame.shape:
elections.shape
(182, 6)
2.4 DataFrame中的切片
现在我们已经更多地了解了DataFrame,让我们深入了解它们的功能。
DataFrame类的 API(应用程序编程接口)是庞大的。在本节中,我们将讨论DataFrame API 的几种方法,这些方法允许我们提取数据子集。
操作DataFrame最简单的方法是提取行和列的子集,称为切片。
我们可能希望提取数据的常见方式包括:
-
DataFrame中的第一行或最后一行。 -
具有特定标签的数据。
-
特定位置的数据。
我们将使用 DataFrame 类的四种主要方法:
-
.head和.tail -
.loc -
.iloc -
[]
2.4.1 使用.head和.tail提取数据
我们希望提取数据的最简单的情况是当我们只想选择DataFrame的前几行或最后几行时。
要提取 DataFrame df的前n行,我们使用语法df.head(n)。
elections = pd.read_csv("data/elections.csv")
# Extract the first 5 rows of the DataFrame
elections.head(5)
| Year | Candidate | Party | Popular vote | Result | % | |
|---|---|---|---|---|---|---|
| 0 | 1824 | Andrew Jackson | Democratic-Republican | 151271 | loss | 57.210122 |
| 1 | 1824 | John Quincy Adams | Democratic-Republican | 113142 | win | 42.789878 |
| 2 | 1828 | Andrew Jackson | Democratic | 642806 | win | 56.203927 |
| 3 | 1828 | John Quincy Adams | National Republican | 500897 | loss | 43.796073 |
| 4 | 1832 | Andrew Jackson | Democratic | 702735 | win | 54.574789 |
类似地,调用df.tail(n)允许我们提取 DataFrame 的最后n行。
# Extract the last 5 rows of the DataFrame
elections.tail(5)
| Year | Candidate | Party | Popular vote | Result | % | ||
|---|---|---|---|---|---|---|---|
| 177 | 2016 | Jill Stein | Green | 1457226 | loss | 1.073699 | |
| 178 | 2020 | Joseph Biden | Democratic | 81268924 | win | 51.311515 | |
| 179 | 2020 | Donald Trump | Republican | 74216154 | loss | 46.858542 | |
| 180 | 2020 | Jo Jorgensen | Libertarian | 1865724 | loss | 1.177979 | |
| 181 | 2020 | Howard Hawkins | Green | 405035 | loss | 0.255731 |
2.4.2 基于标签的提取:使用.loc进行索引
对于使用特定列或索引标签提取数据的更复杂任务,我们可以使用.loc。.loc访问器允许我们指定我们希望提取的行和列的标签。标签(通常称为索引)是 DataFrame 最左边的粗体文本,而列标签是 DataFrame 顶部的列名。
使用.loc获取数据时,我们必须指定数据所在的行和列标签。行标签是.loc函数的第一个参数;列标签是第二个参数。
.loc的参数可以是:
-
一个单一的值。
-
一个切片。
-
一个列表。
例如,要选择单个值,我们可以从elections DataFrame中选择标记为0的行和标记为Candidate的列。
elections.loc[0, 'Candidate']
'Andrew Jackson'
请记住,只传入一个参数作为单个值将产生一个Series。下面,我们提取了"Popular vote"列的子集作为Series。
elections.loc[[87, 25, 179], "Popular vote"]
87 15761254
25 848019
179 74216154
Name: Popular vote, dtype: int64
要选择多个行和列,我们可以使用 Python 切片表示法。在这里,我们选择从标签0到3的行和从标签"Year"到"Popular vote"的列。
elections.loc[0:3, 'Year':'Popular vote']
| Year | Candidate | Party | Popular vote | |
|---|---|---|---|---|
| 0 | 1824 | Andrew Jackson | Democratic-Republican | 151271 |
| 1 | 1824 | John Quincy Adams | Democratic-Republican | 113142 |
| 2 | 1828 | Andrew Jackson | Democratic | 642806 |
| 3 | 1828 | John Quincy Adams | National Republican | 500897 |
假设相反,我们想要提取elections DataFrame 中前四行的所有列值。这时,缩写:就很有用。
elections.loc[0:3, :]
| Year | Candidate | Party | Popular vote | Result | % | |
|---|---|---|---|---|---|---|
| 0 | 1824 | Andrew Jackson | Democratic-Republican | 151271 | loss | 57.210122 |
| 1 | 1824 | John Quincy Adams | Democratic-Republican | 113142 | win | 42.789878 |
| 2 | 1828 | Andrew Jackson | Democratic | 642806 | win | 56.203927 |
| 3 | 1828 | John Quincy Adams | National Republican | 500897 | loss | 43.796073 |
我们可以使用相同的缩写来提取所有行。
elections.loc[:, ["Year", "Candidate", "Result"]]
| Year | Candidate | Result | ||
|---|---|---|---|---|
| 0 | 1824 | Andrew Jackson | loss | |
| 1 | 1824 | John Quincy Adams | win | |
| 2 | 1828 | Andrew Jackson | win | |
| 3 | 1828 | John Quincy Adams | loss | |
| 4 | 1832 | Andrew Jackson | win | |
| ... | ... | ... | ... | |
| 177 | 2016 | Jill Stein | loss | |
| 178 | 2020 | Joseph Biden | win | |
| 179 | 2020 | Donald Trump | loss | |
| 180 | 2020 | Jo Jorgensen | loss | |
| 181 | 2020 | Howard Hawkins | loss |
182 行×3 列
有几件事情我们应该注意。首先,与传统的 Python 不同,pandas允许我们切片字符串值(在我们的例子中,是列标签)。其次,使用.loc进行切片是包含的。请注意,我们的结果DataFrame包括我们指定的切片标签之间和包括这些标签的每一行和列。
同样,我们可以使用列表在elections DataFrame 中获取多行和多列。
elections.loc[[0, 1, 2, 3], ['Year', 'Candidate', 'Party', 'Popular vote']]
| Year | Candidate | Party | Popular vote | |
|---|---|---|---|---|
| 0 | 1824 | Andrew Jackson | Democratic-Republican | 151271 |
| 1 | 1824 | John Quincy Adams | Democratic-Republican | 113142 |
| 2 | 1828 | Andrew Jackson | Democratic | 642806 |
| 3 | 1828 | John Quincy Adams | National Republican | 500897 |
最后,我们可以互换列表和切片表示法。
elections.loc[[0, 1, 2, 3], :]
| Year | Candidate | Party | Popular vote | Result | % | |
|---|---|---|---|---|---|---|
| 0 | 1824 | Andrew Jackson | Democratic-Republican | 151271 | loss | 57.210122 |
| 1 | 1824 | John Quincy Adams | Democratic-Republican | 113142 | win | 42.789878 |
| 2 | 1828 | Andrew Jackson | Democratic | 642806 | win | 56.203927 |
| 3 | 1828 | John Quincy Adams | National Republican | 500897 | loss | 43.796073 |
2.4.3 基于整数的提取:使用.iloc进行索引
使用.iloc进行切片与.loc类似。但是,.iloc使用的是行和列的索引位置,而不是标签(想一想:loc 使用labels;iloc 使用indices)。.iloc函数的参数也类似地行为 —— 允许单个值、列表、索引和这些的任意组合。
让我们开始重现上面的结果。我们将从我们的elections DataFrame 中选择第一个总统候选人开始:
# elections.loc[0, "Candidate"] - Previous approach
elections.iloc[0, 1]
'Andrew Jackson'
请注意,.loc和.iloc的第一个参数是相同的。这是因为标签为 0 的行恰好在elections DataFrame 的(或者说第一个位置)上。通常情况下,任何 DataFrame 中的行标签都是从 0 开始递增的,这一点是正确的。
并且,就像以前一样,如果我们只传入一个单一的值参数,我们的结果将是一个Series。
elections.iloc[[1,2,3],1]
1 John Quincy Adams
2 Andrew Jackson
3 John Quincy Adams
Name: Candidate, dtype: object
然而,当我们使用.iloc选择前四行和列时,我们注意到了一些东西。
# elections.loc[0:3, 'Year':'Popular vote'] - Previous approach
elections.iloc[0:4, 0:4]
| Year | Candidate | Party | Popular vote | |
|---|---|---|---|---|
| 0 | 1824 | Andrew Jackson | Democratic-Republican | 151271 |
| 1 | 1824 | John Quincy Adams | Democratic-Republican | 113142 |
| 2 | 1828 | Andrew Jackson | Democratic | 642806 |
| 3 | 1828 | John Quincy Adams | National Republican | 500897 |
切片在.iloc中不再是包容的——它是排他的。换句话说,使用.iloc时,切片的右端点不包括在内。这是pandas语法的微妙之处之一;通过练习你会习惯的。
列表行为与预期的一样。
#elections.loc[[0, 1, 2, 3], ['Year', 'Candidate', 'Party', 'Popular vote']] - Previous Approach
elections.iloc[[0, 1, 2, 3], [0, 1, 2, 3]]
| Year | Candidate | Party | Popular vote | |
|---|---|---|---|---|
| 0 | 1824 | Andrew Jackson | Democratic-Republican | 151271 |
| 1 | 1824 | John Quincy Adams | Democratic-Republican | 113142 |
| 2 | 1828 | Andrew Jackson | Democratic | 642806 |
| 3 | 1828 | John Quincy Adams | National Republican | 500897 |
就像使用.loc一样,我们可以使用冒号与.iloc一起提取所有行或列。
elections.iloc[:, 0:3]
| Year | Candidate | Party | |
|---|---|---|---|
| 0 | 1824 | Andrew Jackson | Democratic-Republican |
| 1 | 1824 | John Quincy Adams | Democratic-Republican |
| 2 | 1828 | Andrew Jackson | Democratic |
| 3 | 1828 | John Quincy Adams | National Republican |
| 4 | 1832 | Andrew Jackson | Democratic |
| ... | ... | ... | ... |
| 177 | 2016 | Jill Stein | Green |
| 178 | 2020 | Joseph Biden | Democratic |
| 179 | 2020 | Donald Trump | Republican |
| 180 | 2020 | Jo Jorgensen | Libertarian |
| 181 | 2020 | Howard Hawkins | Green |
182 行×3 列
这个讨论引出了一个问题:我们什么时候应该使用.loc和.iloc?在大多数情况下,.loc通常更安全。你可以想象,当应用于数据集的顺序可能会改变时,.iloc可能会返回不正确的值。然而,.iloc仍然是有用的——例如,如果你正在查看一个排序好的电影收入的DataFrame,并且想要得到给定年份的收入中位数,你可以使用.iloc来索引到中间。
总的来说,重要的是要记住:
-
.loc执行label-based 提取。 -
.iloc执行integer-based 提取。
2.4.4 上下文相关的提取:使用[]进行索引
[]选择运算符是最令人困惑的,但也是最常用的。它只接受一个参数,可以是以下之一:
-
一系列行号。
-
一系列列标签。
-
单列标签。
也就是说,[]是上下文相关的。让我们看一些例子。
2.4.4.1 一系列行号
假设我们想要我们的elections DataFrame 的前四行。
elections[0:4]
| Year | Candidate | Party | Popular vote | Result | % | |
|---|---|---|---|---|---|---|
| 0 | 1824 | Andrew Jackson | Democratic-Republican | 151271 | loss | 57.210122 |
| 1 | 1824 | John Quincy Adams | Democratic-Republican | 113142 | win | 42.789878 |
| 2 | 1828 | Andrew Jackson | Democratic | 642806 | win | 56.203927 |
| 3 | 1828 | John Quincy Adams | National Republican | 500897 | loss | 43.796073 |
2.4.4.2 一系列列标签
假设我们现在想要前四列。
elections[["Year", "Candidate", "Party", "Popular vote"]]
| Year | Candidate | Party | Popular vote | |
|---|---|---|---|---|
| 0 | 1824 | Andrew Jackson | Democratic-Republican | 151271 |
| 1 | 1824 | John Quincy Adams | Democratic-Republican | 113142 |
| 2 | 1828 | Andrew Jackson | Democratic | 642806 |
| 3 | 1828 | John Quincy Adams | National Republican | 500897 |
| 4 | 1832 | Andrew Jackson | Democratic | 702735 |
| ... | ... | ... | ... | ... |
| 177 | 2016 | Jill Stein | Green | 1457226 |
| 178 | 2020 | Joseph Biden | Democratic | 81268924 |
| 179 | 2020 | Donald Trump | Republican | 74216154 |
| 180 | 2020 | Jo Jorgensen | Libertarian | 1865724 |
| 181 | 2020 | Howard Hawkins | Green | 405035 |
182 行×4 列
2.4.4.3 单列标签
最后,[]允许我们仅提取Candidate列。
elections["Candidate"]
0 Andrew Jackson
1 John Quincy Adams
2 Andrew Jackson
3 John Quincy Adams
4 Andrew Jackson
...
177 Jill Stein
178 Joseph Biden
179 Donald Trump
180 Jo Jorgensen
181 Howard Hawkins
Name: Candidate, Length: 182, dtype: object
输出是一个Series!在本课程中,我们将非常熟悉[],特别是用于选择列。在实践中,[]比.loc更常见,特别是因为它更加简洁。
2.5 结语
pandas库非常庞大,包含许多有用的函数。这是一个指向文档的链接。我们当然不指望您记住库中的每一个方法。
入门级的 Data 100 pandas 讲座将提供对关键数据结构和方法的高层次视图,这些将构成您pandas知识的基础。本课程的目标是帮助您建立对真实世界编程实践的熟悉度……谷歌搜索!您的问题的答案可以在文档、Stack Overflow 等地方找到。能够搜索、阅读和实施文档是任何数据科学家的重要生活技能。
有了这个,我们将继续学习 Pandas II。
三、Pandas II
原文:Pandas II
译者:飞龙
学习成果
-
继续熟悉
pandas语法。 -
使用条件选择从
DataFrame中提取数据。 -
识别聚合有用的情况,并确定执行聚合的正确技术。
上次,我们介绍了pandas库作为处理数据的工具包。我们学习了DataFrame和Series数据结构,熟悉了操作表格数据的基本语法,并开始编写我们的第一行pandas代码。
在本讲座中,我们将开始深入了解一些高级的pandas语法。当我们逐步学习这些新的代码片段时,您可能会发现跟着自己的笔记本会很有帮助。
我们将开始加载babynames数据集。
代码
# This code pulls census data and loads it into a DataFrame
# We won't cover it explicitly in this class, but you are welcome to explore it on your own
import pandas as pd
import numpy as np
import urllib.request
import os.path
import zipfile
data_url = "https://www.ssa.gov/oact/babynames/state/namesbystate.zip"
local_filename = "data/babynamesbystate.zip"
if not os.path.exists(local_filename): # If the data exists don't download again
with urllib.request.urlopen(data_url) as resp, open(local_filename, 'wb') as f:
f.write(resp.read())
zf = zipfile.ZipFile(local_filename, 'r')
ca_name = 'STATE.CA.TXT'
field_names = ['State', 'Sex', 'Year', 'Name', 'Count']
with zf.open(ca_name) as fh:
babynames = pd.read_csv(fh, header=None, names=field_names)
babynames.head()
| State | Sex | Year | Name | Count | |
|---|---|---|---|---|---|
| 0 | CA | F | 1910 | Mary | 295 |
| 1 | CA | F | 1910 | Helen | 239 |
| 2 | CA | F | 1910 | Dorothy | 220 |
| 3 | CA | F | 1910 | Margaret | 163 |
| 4 | CA | F | 1910 | Frances | 134 |
3.1 条件选择
条件选择允许我们选择满足某些指定条件的DataFrame中的行的子集。
要了解如何使用条件选择,我们必须看一下.loc和[]方法的另一个可能的输入 - 布尔数组,它只是一个数组或Series,其中每个元素都是True或False。这个布尔数组的长度必须等于DataFrame中的行数。它将返回数组中对应True值的所有行。我们在上一堂课中从Series中执行条件提取时使用了非常类似的技术。
为了看到这一点,让我们选择我们DataFrame的前 10 行中的所有偶数索引行。
# Ask yourself: why is :9 is the correct slice to select the first 10 rows?
babynames_first_10_rows = babynames.loc[:9, :]
# Notice how we have exactly 10 elements in our boolean array argument
babynames_first_10_rows[[True, False, True, False, True, False, True, False, True, False]]
| State | Sex | Year | Name | Count | |
|---|---|---|---|---|---|
| 0 | CA | F | 1910 | Mary | 295 |
| 2 | CA | F | 1910 | Dorothy | 220 |
| 4 | CA | F | 1910 | Frances | 134 |
| 6 | CA | F | 1910 | Evelyn | 126 |
| 8 | CA | F | 1910 | Virginia | 101 |
我们可以使用.loc执行类似的操作。
babynames_first_10_rows.loc[[True, False, True, False, True, False, True, False, True, False], :]
| State | Sex | Year | Name | Count | |
|---|---|---|---|---|---|
| 0 | CA | F | 1910 | Mary | 295 |
| 2 | CA | F | 1910 | Dorothy | 220 |
| 4 | CA | F | 1910 | Frances | 134 |
| 6 | CA | F | 1910 | Evelyn | 126 |
| 8 | CA | F | 1910 | Virginia | 101 |
这些技术在这个例子中运行良好,但是你可以想象在更大的DataFrame中为每一行列出True和False可能会有多么乏味。为了简化事情,我们可以提供一个逻辑条件作为.loc或[]的输入,返回一个具有必要长度的布尔数组。
例如,要返回与F性别相关的所有名称:
# First, use a logical condition to generate a boolean array
logical_operator = (babynames["Sex"] == "F")
# Then, use this boolean array to filter the DataFrame
babynames[logical_operator].head()
| State | Sex | Year | Name | Count | |
|---|---|---|---|---|---|
| 0 | CA | F | 1910 | Mary | 295 |
| 1 | CA | F | 1910 | Helen | 239 |
| 2 | CA | F | 1910 | Dorothy | 220 |
| 3 | CA | F | 1910 | Margaret | 163 |
| 4 | CA | F | 1910 | Frances | 134 |
从上一讲中回忆,.head()将只返回DataFrame中的前几行。实际上,babynames[logical operator]包含与原始babynames DataFrame中性别为"F"的条目一样多的行。
在这里,logical_operator评估为长度为 407428 的布尔值Series。
代码
print("There are a total of {} values in 'logical_operator'".format(len(logical_operator)))
There are a total of 407428 values in 'logical_operator'
从第 0 行开始到第 239536 行的行评估为True,因此在DataFrame中返回。从第 239537 行开始的行评估为False,因此在输出中被省略。
代码
print("The 0th item in this 'logical_operator' is: {}".format(logical_operator.iloc[0]))
print("The 239536th item in this 'logical_operator' is: {}".format(logical_operator.iloc[239536]))
print("The 239537th item in this 'logical_operator' is: {}".format(logical_operator.iloc[239537]))
The 0th item in this 'logical_operator' is: True
The 239536th item in this 'logical_operator' is: True
The 239537th item in this 'logical_operator' is: False
将Series作为babynames[]的参数传递与使用布尔数组具有相同的效果。实际上,[]选择运算符可以将布尔Series、数组和列表作为参数。在整个课程中,这三种方法可以互换使用。
我们也可以使用.loc来实现类似的结果。
babynames.loc[babynames["Sex"] == "F"].head()
| State | Sex | Year | Name | Count | |
|---|---|---|---|---|---|
| 0 | CA | F | 1910 | Mary | 295 |
| 1 | CA | F | 1910 | Helen | 239 |
| 2 | CA | F | 1910 | Dorothy | 220 |
| 3 | CA | F | 1910 | Margaret | 163 |
| 4 | CA | F | 1910 | Frances | 134 |
布尔条件可以使用各种位运算符进行组合,从而可以根据多个条件过滤结果。在下表中,p 和 q 是布尔数组或Series。
| 符号 | 用法 | 意义 |
|---|---|---|
~ | ~p | 返回 p 的否定 |
| | p | q | p 或 q |
& | p & q | p 和 q |
^ | p ^ q | p 异或 q(排他或) |
当使用逻辑运算符结合多个条件时,我们用一组括号()括起每个单独的条件。这样可以对pandas评估您的逻辑施加操作顺序,并可以避免代码错误。
例如,如果我们想要返回所有性别为“F”,出生在 2000 年之前的名字数据,我们可以写成:
babynames[(babynames["Sex"] == "F") & (babynames["Year"] < 2000)].head()
| State | Sex | Year | Name | Count | |
|---|---|---|---|---|---|
| 0 | CA | F | 1910 | Mary | 295 |
| 1 | CA | F | 1910 | Helen | 239 |
| 2 | CA | F | 1910 | Dorothy | 220 |
| 3 | CA | F | 1910 | Margaret | 163 |
| 4 | CA | F | 1910 | Frances | 134 |
如果我们想要返回所有性别为“F”或出生在 2000 年之前的所有名字数据,我们可以写成:
babynames[(babynames["Sex"] == "F") | (babynames["Year"] < 2000)].head()
| State | Sex | Year | Name | Count | |
|---|---|---|---|---|---|
| 0 | CA | F | 1910 | Mary | 295 |
| 1 | CA | F | 1910 | Helen | 239 |
| 2 | CA | F | 1910 | Dorothy | 220 |
| 3 | CA | F | 1910 | Margaret | 163 |
| 4 | CA | F | 1910 | Frances | 134 |
布尔数组选择是一个有用的工具,但对于复杂条件可能导致代码过于冗长。在下面的示例中,我们的布尔条件足够长,以至于需要多行代码来编写。
# Note: The parentheses surrounding the code make it possible to break the code on to multiple lines for readability
(
babynames[(babynames["Name"] == "Bella") |
(babynames["Name"] == "Alex") |
(babynames["Name"] == "Ani") |
(babynames["Name"] == "Lisa")]
).head()
| State | Sex | Year | Name | Count | |
|---|---|---|---|---|---|
| 6289 | CA | F | 1923 | Bella | 5 |
| 7512 | CA | F | 1925 | Bella | 8 |
| 12368 | CA | F | 1932 | Lisa | 5 |
| 14741 | CA | F | 1936 | Lisa | 8 |
| 17084 | CA | F | 1939 | Lisa | 5 |
幸运的是,pandas提供了许多构建布尔过滤器的替代方法。
.isin函数就是一个例子。该方法评估Series中的值是否包含在不同序列(列表、数组或Series)的值中。在下面的单元格中,我们用更简洁的代码实现了与上面的DataFrame等效的结果。
names = ["Bella", "Alex", "Narges", "Lisa"]
babynames["Name"].isin(names).head()
0 False
1 False
2 False
3 False
4 False
Name: Name, dtype: bool
babynames[babynames["Name"].isin(names)].head()
| State | Sex | Year | Name | Count | |
|---|---|---|---|---|---|
| 6289 | CA | F | 1923 | Bella | 5 |
| 7512 | CA | F | 1925 | Bella | 8 |
| 12368 | CA | F | 1932 | Lisa | 5 |
| 14741 | CA | F | 1936 | Lisa | 8 |
| 17084 | CA | F | 1939 | Lisa | 5 |
函数str.startswith可用于基于Series对象中的字符串值定义过滤器。它检查Series中的字符串值是否以特定字符开头。
# Identify whether names begin with the letter "N"
babynames["Name"].str.startswith("N").head()
0 False
1 False
2 False
3 False
4 False
Name: Name, dtype: bool
# Extracting names that begin with the letter "N"
babynames[babynames["Name"].str.startswith("N")].head()
| State | Sex | Year | Name | Count | |
|---|---|---|---|---|---|
| 76 | CA | F | 1910 | Norma | 23 |
| 83 | CA | F | 1910 | Nellie | 20 |
| 127 | CA | F | 1910 | Nina | 11 |
| 198 | CA | F | 1910 | Nora | 6 |
| 310 | CA | F | 1911 | Nellie | 23 |
3.2 添加、删除和修改列
在许多数据科学任务中,我们可能需要以某种方式更改DataFrame中包含的列。幸运的是,这样做的语法非常简单。
要向DataFrame添加新列,我们使用的语法与访问现有列时类似。通过写入df["column"]来指定新列的名称,然后将其分配给包含将填充此列的值的Series或数组。
# Create a Series of the length of each name.
babyname_lengths = babynames["Name"].str.len()
# Add a column named "name_lengths" that includes the length of each name
babynames["name_lengths"] = babyname_lengths
babynames.head(5)
| State | Sex | Year | Name | Count | name_lengths | |
|---|---|---|---|---|---|---|
| 0 | CA | F | 1910 | Mary | 295 | 4 |
| 1 | CA | F | 1910 | Helen | 239 | 5 |
| 2 | CA | F | 1910 | Dorothy | 220 | 7 |
| 3 | CA | F | 1910 | Margaret | 163 | 8 |
| 4 | CA | F | 1910 | Frances | 134 | 7 |
如果我们需要稍后修改现有列,可以通过再次引用该列的语法df["column"],然后将其重新分配给适当长度的新Series或数组来实现。
# Modify the “name_lengths” column to be one less than its original value
babynames["name_lengths"] = babynames["name_lengths"] - 1
babynames.head()
| State | Sex | Year | Name | Count | name_lengths | |
|---|---|---|---|---|---|---|
| 0 | CA | F | 1910 | Mary | 295 | 3 |
| 1 | CA | F | 1910 | Helen | 239 | 4 |
| 2 | CA | F | 1910 | Dorothy | 220 | 6 |
| 3 | CA | F | 1910 | Margaret | 163 | 7 |
| 4 | CA | F | 1910 | Frances | 134 | 6 |
我们可以使用.rename()方法重命名列。.rename()接受一个将旧列名映射到新列名的字典。
# Rename “name_lengths” to “Length”
babynames = babynames.rename(columns={"name_lengths":"Length"})
babynames.head()
| State | Sex | Year | Name | Count | Length | |
|---|---|---|---|---|---|---|
| 0 | CA | F | 1910 | Mary | 295 | 3 |
| 1 | CA | F | 1910 | Helen | 239 | 4 |
| 2 | CA | F | 1910 | Dorothy | 220 | 6 |
| 3 | CA | F | 1910 | Margaret | 163 | 7 |
| 4 | CA | F | 1910 | Frances | 134 | 6 |
如果我们想要删除DataFrame的列或行,我们可以调用.drop方法。使用axis参数来指定是应该删除列还是行。除非另有说明,否则pandas将默认假定我们要删除一行。
# Drop our new "Length" column from the DataFrame
babynames = babynames.drop("Length", axis="columns")
babynames.head(5)
| State | Sex | Year | Name | Count | |
|---|---|---|---|---|---|
| 0 | CA | F | 1910 | Mary | 295 |
| 1 | CA | F | 1910 | Helen | 239 |
| 2 | CA | F | 1910 | Dorothy | 220 |
| 3 | CA | F | 1910 | Margaret | 163 |
| 4 | CA | F | 1910 | Frances | 134 |
请注意,我们重新分配了babynames到babynames.drop(...)的结果。这是一个微妙但重要的观点:pandas表操作不会发生在原地。调用df.drop(...)将输出一个删除感兴趣的行/列的副本df,而不会修改原始的df表。
换句话说,如果我们简单地调用:
# This creates a copy of `babynames` and removes the column "Name"...
babynames.drop("Name", axis="columns")
# ...but the original `babynames` is unchanged!
# Notice that the "Name" column is still present
babynames.head(5)
| State | Sex | Year | Name | Count | |
|---|---|---|---|---|---|
| 0 | CA | F | 1910 | Mary | 295 |
| 1 | CA | F | 1910 | Helen | 239 |
| 2 | CA | F | 1910 | Dorothy | 220 |
| 3 | CA | F | 1910 | Margaret | 163 |
| 4 | CA | F | 1910 | Frances | 134 |
3.3 实用程序函数
pandas包含大量的函数库,可以帮助缩短设置和从其数据结构中获取信息的过程。在接下来的部分中,我们将概述每个主要实用程序函数,这些函数将帮助我们在 Data 100 中使用。
讨论pandas提供的所有功能可能需要一个学期的时间!我们将带领您了解最常用的功能,并鼓励您自行探索和实验。
-
NumPy和内置函数支持 -
.shape -
.size -
.describe() -
.sample() -
.value_counts() -
.unique() -
.sort_values()
pandas 文档将是 Data 100 及以后的宝贵资源。
3.3.1 NumPy
pandas旨在与您在Data 8中遇到的数组计算框架NumPy良好配合。几乎任何NumPy函数都可以应用于pandas的DataFrame和Series。
# Pull out the number of babies named Yash each year
yash_count = babynames[babynames["Name"] == "Yash"]["Count"]
yash_count.head()
331824 8
334114 9
336390 11
338773 12
341387 10
Name: Count, dtype: int64
# Average number of babies named Yash each year
np.mean(yash_count)
17.142857142857142
# Max number of babies named Yash born in any one year
np.max(yash_count)
29
3.3.2 .shape 和 .size
.shape和.size是Series和DataFrame的属性,用于测量结构中存储的数据的“数量”。调用.shape返回一个元组,其中包含DataFrame或Series中存在的行数和列数。.size用于找到结构中元素的总数,相当于行数乘以列数。
许多函数严格要求参数沿着某些轴的维度匹配。调用这些维度查找函数比手动计算所有项目要快得多。
# Return the shape of the DataFrame, in the format (num_rows, num_columns)
babynames.shape
(407428, 5)
# Return the size of the DataFrame, equal to num_rows * num_columns
babynames.size
2037140
3.3.3 .describe()
如果需要从DataFrame中获取许多统计信息(最小值,最大值,平均值等),则可以使用.describe() 一次计算所有这些统计信息。
babynames.describe()
| Year | Count | |
|---|---|---|
| count | 407428.000000 | 407428.000000 |
| mean | 1985.733609 | 79.543456 |
| std | 27.007660 | 293.698654 |
| min | 1910.000000 | 5.000000 |
| 25% | 1969.000000 | 7.000000 |
| 50% | 1992.000000 | 13.000000 |
| 75% | 2008.000000 | 38.000000 |
| max | 2022.000000 | 8260.000000 |
如果在Series上调用.describe(),将报告一组不同的统计信息。
babynames["Sex"].describe()
count 407428
unique 2
top F
freq 239537
Name: Sex, dtype: object
3.3.4 .sample()
正如我们将在本学期后面看到的,随机过程是许多数据科学技术的核心(例如,训练-测试拆分,自助法和交叉验证)。.sample() 让我们快速选择随机条目(如果从DataFrame调用,则是一行,如果从Series调用,则是一个值)。
默认情况下,.sample() 选择不替换的条目。传入参数 replace=True 以进行替换采样。
# Sample a single row
babynames.sample()
| State | Sex | Year | Name | Count | |
|---|---|---|---|---|---|
| 119438 | CA | F | 1991 | Madaline | 6 |
当然,这可以与其他方法和运算符(iloc等)链接在一起。
# Sample 5 random rows, and select all columns after column 2
babynames.sample(5).iloc[:, 2:]
| Year | Name | Count | |
|---|---|---|---|
| 360264 | 2006 | Rosalio | 7 |
| 103104 | 1987 | Paola | 86 |
| 261680 | 1950 | Perry | 62 |
| 68249 | 1973 | Lilian | 13 |
| 239652 | 1910 | Eddie | 5 |
# Randomly sample 4 names from the year 2000, with replacement, and select all columns after column 2
babynames[babynames["Year"] == 2000].sample(4, replace = True).iloc[:, 2:]
| Year | Name | Count | |
|---|---|---|---|
| 150871 | 2000 | Josette | 12 |
| 151230 | 2000 | Alanah | 9 |
| 342709 | 2000 | Conner | 147 |
| 150683 | 2000 | Kaci | 14 |
3.3.5 .value_counts()
Series.value_counts() 方法计算Series中每个唯一值的出现次数。换句话说,它计算每个唯一值出现的次数。这通常对于确定Series中最常见或最不常见的条目很有用。
在下面的示例中,我们可以通过计算每个名称在babynames的"Name"列中出现的次数来确定至少有一个人在该名称下使用了最多年份的名称。请注意,返回值也是一个Series。
babynames["Name"].value_counts().head()
Jean 223
Francis 221
Guadalupe 218
Jessie 217
Marion 214
Name: Name, dtype: int64
3.3.6 .unique()
如果我们有一个具有许多重复值的Series,那么.unique() 可以用于仅识别唯一值。在这里,我们返回babynames中所有名称的数组。
babynames["Name"].unique()
array(['Mary', 'Helen', 'Dorothy', ..., 'Zae', 'Zai', 'Zayvier'],
dtype=object)
3.3.7 .sort_values()
对DataFrame进行排序可以用于隔离极端值。例如,按降序排序的行的前 5 个条目(即从最高到最低)是最大的 5 个值。.sort_values 允许我们按指定列对DataFrame或Series进行排序。我们可以选择按升序(默认)或降序的顺序接收行。
# Sort the "Count" column from highest to lowest
babynames.sort_values(by="Count", ascending=False).head()
| State | Sex | Year | Name | Count | |
|---|---|---|---|---|---|
| 268041 | CA | M | 1957 | Michael | 8260 |
| 267017 | CA | M | 1956 | Michael | 8258 |
| 317387 | CA | M | 1990 | Michael | 8246 |
| 281850 | CA | M | 1969 | Michael | 8245 |
| 283146 | CA | M | 1970 | Michael | 8196 |
与在DataFrame上调用.value_counts()不同,当在Series上调用.value_counts()时,我们不需要显式指定用于排序的列。我们仍然可以指定排序范式 - 即值是按升序还是降序排序。
# Sort the "Name" Series alphabetically
babynames["Name"].sort_values(ascending=True).head()
366001 Aadan
384005 Aadan
369120 Aadan
398211 Aadarsh
370306 Aaden
Name: Name, dtype: object
3.4 自定义排序
现在让我们尝试应用我们刚刚学到的知识来解决一个排序问题,使用不同的方法。假设我们想要找到最长的婴儿名字,并相应地对我们的数据进行排序。
3.4.1 方法 1:创建一个临时列
其中一种方法是首先创建一个包含名字长度的列。
# Create a Series of the length of each name
babyname_lengths = babynames["Name"].str.len()
# Add a column named "name_lengths" that includes the length of each name
babynames["name_lengths"] = babyname_lengths
babynames.head(5)
| State | Sex | Year | Name | Count | name_lengths | |
|---|---|---|---|---|---|---|
| 0 | CA | F | 1910 | Mary | 295 | 4 |
| 1 | CA | F | 1910 | Helen | 239 | 5 |
| 2 | CA | F | 1910 | Dorothy | 220 | 7 |
| 3 | CA | F | 1910 | Margaret | 163 | 8 |
| 4 | CA | F | 1910 | Frances | 134 | 7 |
然后,我们可以使用.sort_values()按该列对DataFrame进行排序:
# Sort by the temporary column
babynames = babynames.sort_values(by="name_lengths", ascending=False)
babynames.head(5)
| State | Sex | Year | Name | Count | name_lengths | |
|---|---|---|---|---|---|---|
| 334166 | CA | M | 1996 | Franciscojavier | 8 | 15 |
| 337301 | CA | M | 1997 | Franciscojavier | 5 | 15 |
| 339472 | CA | M | 1998 | Franciscojavier | 6 | 15 |
| 321792 | CA | M | 1991 | Ryanchristopher | 7 | 15 |
| 327358 | CA | M | 1993 | Johnchristopher | 5 | 15 |
最后,我们可以从babynames中删除name_length列,以防止我们的表变得混乱。
# Drop the 'name_length' column
babynames = babynames.drop("name_lengths", axis='columns')
babynames.head(5)
| State | Sex | Year | Name | Count | |
|---|---|---|---|---|---|
| 334166 | CA | M | 1996 | Franciscojavier | 8 |
| 337301 | CA | M | 1997 | Franciscojavier | 5 |
| 339472 | CA | M | 1998 | Franciscojavier | 6 |
| 321792 | CA | M | 1991 | Ryanchristopher | 7 |
| 327358 | CA | M | 1993 | Johnchristopher | 5 |
3.4.2 方法 2:使用key参数进行排序
另一种方法是使用.sort_values()的key参数。在这里,我们可以指定我们想要按长度对"Name"值进行排序。
babynames.sort_values("Name", key=lambda x: x.str.len(), ascending=False).head()
| State | Sex | Year | Name | Count | |
|---|---|---|---|---|---|
| 334166 | CA | M | 1996 | Franciscojavier | 8 |
| 327472 | CA | M | 1993 | Ryanchristopher | 5 |
| 337301 | CA | M | 1997 | Franciscojavier | 5 |
| 337477 | CA | M | 1997 | Ryanchristopher | 5 |
| 312543 | CA | M | 1987 | Franciscojavier | 5 |
3.4.3 方法 3:使用map函数进行排序
我们还可以在Series上使用map函数来解决这个问题。假设我们想要按每个“名字”中的“dr”和“ea”的数量对babynames表进行排序。我们将定义函数dr_ea_count来帮助我们。
# First, define a function to count the number of times "dr" or "ea" appear in each name
def dr_ea_count(string):
return string.count('dr') + string.count('ea')
# Then, use `map` to apply `dr_ea_count` to each name in the "Name" column
babynames["dr_ea_count"] = babynames["Name"].map(dr_ea_count)
# Sort the DataFrame by the new "dr_ea_count" column so we can see our handiwork
babynames = babynames.sort_values(by="dr_ea_count", ascending=False)
babynames.head()
| State | Sex | Year | Name | Count | dr_ea_count | |
|---|---|---|---|---|---|---|
| 115957 | CA | F | 1990 | Deandrea | 5 | 3 |
| 101976 | CA | F | 1986 | Deandrea | 6 | 3 |
| 131029 | CA | F | 1994 | Leandrea | 5 | 3 |
| 108731 | CA | F | 1988 | Deandrea | 5 | 3 |
| 308131 | CA | M | 1985 | Deandrea | 6 | 3 |
我们可以在使用完dr_ea_count后删除它,以保持一个整洁的表格。
# Drop the `dr_ea_count` column
babynames = babynames.drop("dr_ea_count", axis = 'columns')
babynames.head(5)
| State | Sex | Year | Name | Count | |
|---|---|---|---|---|---|
| 115957 | CA | F | 1990 | Deandrea | 5 |
| 101976 | CA | F | 1986 | Deandrea | 6 |
| 131029 | CA | F | 1994 | Leandrea | 5 |
| 108731 | CA | F | 1988 | Deandrea | 5 |
| 308131 | CA | M | 1985 | Deandrea | 6 |
3.5 使用.groupby聚合数据
直到这一点,我们一直在处理DataFrame的单个行。作为数据科学家,我们经常希望调查我们数据的更大子集中的趋势。例如,我们可能希望计算我们DataFrame中一组行的一些摘要统计(均值、中位数、总和等)。为此,我们将使用pandas的GroupBy对象。
假设我们想要聚合babynames中给定年份的所有行。
babynames.groupby("Year")
<pandas.core.groupby.generic.DataFrameGroupBy object at 0x117197460>
这个奇怪的输出是什么意思?调用.groupby生成了一个GroupBy对象。你可以把它想象成一组“迷你”子数据框,其中每个子框包含与特定年份对应的babynames的所有行。
下面的图表显示了babynames的简化视图,以帮助说明这个想法。
创建一个 GroupBy 对象
我们不能直接使用GroupBy对象——这就是为什么你之前看到了奇怪的输出,而不是DataFrame的标准视图。要实际操作这些“迷你”DataFrame 中的值,我们需要调用聚合方法。这是一种告诉pandas如何聚合GroupBy对象中的值的方法。一旦应用了聚合,pandas将返回一个正常的(现在是分组的)DataFrame。
我们将考虑的第一种聚合方法是.agg。.agg方法将函数作为其参数;然后将该函数应用于“迷你”分组的每一列 DataFrame。我们最终得到一个新的DataFrame,每个子框架都有一行聚合。
babynames[["Year", "Count"]].groupby("Year").agg(sum).head(5)
| Count | |
|---|---|
| Year | |
| 1910 | 9163 |
| 1911 | 9983 |
| 1912 | 17946 |
| 1913 | 22094 |
| 1914 | 26926 |
我们可以将这一点与我们之前使用的图表联系起来。请记住,图表使用了“babynames”的简化版本,这就是为什么我们看到总计数的值较小。
执行聚合
调用.agg已将每个子框架压缩为单个行。这给了我们最终的输出:一个现在由“Year”索引的DataFrame,原始babynamesDataFrame 中每个唯一年份都有一行。
也许你会想:"State"、"Sex"和"Name"列去哪了?从逻辑上讲,对这些列中的字符串数据进行sum是没有意义的(我们怎么将“Mary”+“Ann”相加呢?)。因此,在对DataFrame进行聚合时,我们需要省略这些列。
# Same result, but now we explicitly tell pandas to only consider the "Count" column when summing
babynames.groupby("Year")[["Count"]].agg(sum).head(5)
| Count | |
|---|---|
| Year | |
| 1910 | 9163 |
| 1911 | 9983 |
| 1912 | 17946 |
| 1913 | 22094 |
| 1914 | 26926 |
有许多不同的聚合可以应用于分组数据。主要要求是聚合函数必须:
-
接收一系列数据(分组子框架的单个列)。
-
返回一个聚合了这个
Series的单个值。
由于这个相当广泛的要求,pandas提供了许多计算聚合的方法。
内置的 Python 操作——如sum、max和min——会被pandas自动识别。
# What is the minimum count for each name in any year?
babynames.groupby("Name")[["Count"]].agg(min).head()
| Count | |
|---|---|
| Name | |
| Aadan | 5 |
| Aadarsh | 6 |
| Aaden | 10 |
| Aadhav | 6 |
| Aadhini | 6 |
# What is the largest single-year count of each name?
babynames.groupby("Name")[["Count"]].agg(max).head()
| Count | |
|---|---|
| Name | |
| Aadan | 7 |
| Aadarsh | 6 |
| Aaden | 158 |
| Aadhav | 8 |
| Aadhini | 6 |
如前所述,NumPy库中的函数,如np.mean、np.max、np.min和np.sum,也是pandas中的合理选择。
# What is the average count for each name across all years?
babynames.groupby("Name")[["Count"]].agg(np.mean).head()
| Count | |
|---|---|
| Name | |
| Aadan | 6.000000 |
| Aadarsh | 6.000000 |
| Aaden | 46.214286 |
| Aadhav | 6.750000 |
| Aadhini | 6.000000 |
pandas还提供了许多内置函数。pandas本地的函数可以在调用.agg时使用它们的字符串名称进行引用。一些例子包括:
-
.agg("sum") -
.agg("max") -
.agg("min") -
.agg("mean") -
.agg("first") -
.agg("last")
列表中的后两个条目——“first”和“last”——是“pandas”独有的。它们返回子框架列中的第一个或最后一个条目。为什么这可能有用呢?考虑一个情况,即组中的多个列共享相同的信息。为了在分组输出中表示这些信息,我们可以简单地获取第一个或最后一个条目,我们知道它将与所有其他条目相同。
让我们举个例子来说明这一点。假设我们向“babynames”添加一个新列,其中包含每个名字的第一个字母。
# Imagine we had an additional column, "First Letter". We'll explain this code next week
babynames["First Letter"] = babynames["Name"].str[0]
# We construct a simplified DataFrame containing just a subset of columns
babynames_new = babynames[["Name", "First Letter", "Year"]]
babynames_new.head()
| Name | First Letter | Year | |
|---|---|---|---|
| 115957 | Deandrea | D | 1990 |
| 101976 | Deandrea | D | 1986 |
| 131029 | Leandrea | L | 1994 |
| 108731 | Deandrea | D | 1988 |
| 308131 | Deandrea | D | 1985 |
如果我们为数据集中的每个名称形成分组,“首字母”将对该组的所有成员都相同。这意味着如果我们只是选择组中“首字母”的第一个条目,我们将代表该组中的所有数据。
我们可以使用字典在分组期间对每列应用不同的聚合函数。
使用“first”进行聚合
babynames_new.groupby("Name").agg({"First Letter":"first", "Year":"max"}).head()
| First Letter | Year | |
|---|---|---|
| Name | ||
| Aadan | A | 2014 |
| Aadarsh | A | 2019 |
| Aaden | A | 2020 |
| Aadhav | A | 2019 |
| Aadhini | A | 2022 |
一些聚合函数非常常见,以至于pandas允许直接调用它们,而无需显式使用.agg。
babynames.groupby("Name")[["Count"]].mean().head()
| Count | |
|---|---|
| Name | |
| Aadan | 6.000000 |
| Aadarsh | 6.000000 |
| Aaden | 46.214286 |
| Aadhav | 6.750000 |
| Aadhini | 6.000000 |
我们还可以定义自己的聚合函数!这可以使用def或lambda语句来完成。同样,自定义聚合函数的条件是它必须接受一个Series并输出单个标量值。
babynames = babynames.sort_values(by="Year", ascending=True)
def ratio_to_peak(series):
return series.iloc[-1]/max(series)
babynames.groupby("Name")[["Year", "Count"]].agg(ratio_to_peak)
| Year | Count | |
|---|---|---|
| Name | ||
| Aadan | 1.0 | 0.714286 |
| Aadarsh | 1.0 | 1.000000 |
| Aaden | 1.0 | 0.063291 |
| Aadhav | 1.0 | 0.750000 |
| Aadhini | 1.0 | 1.000000 |
| ... | ... | ... |
| Zymir | 1.0 | 1.000000 |
| Zyon | 1.0 | 1.000000 |
| Zyra | 1.0 | 1.000000 |
| Zyrah | 1.0 | 0.833333 |
| Zyrus | 1.0 | 1.000000 |
20437 行×2 列
# Alternatively, using lambda
babynames.groupby("Name")[["Year", "Count"]].agg(lambda s: s.iloc[-1]/max(s))
| Year | Count | |
|---|---|---|
| Name | ||
| Aadan | 1.0 | 0.714286 |
| Aadarsh | 1.0 | 1.000000 |
| Aaden | 1.0 | 0.063291 |
| Aadhav | 1.0 | 0.750000 |
| Aadhini | 1.0 | 1.000000 |
| ... | ... | ... |
| Zymir | 1.0 | 1.000000 |
| Zyon | 1.0 | 1.000000 |
| Zyra | 1.0 | 1.000000 |
| Zyrah | 1.0 | 0.833333 |
| Zyrus | 1.0 | 1.000000 |
20437 行×2 列
3.6 结语
操纵DataFrames不是一天就能掌握的技能。由于pandas的灵活性,有许多不同的方法可以从 A 点到 B 点。我们建议尝试多种不同的方法来解决同一个问题,以获得更多的练习并更快地达到精通的水平。
接下来,我们将开始深入挖掘数据分组背后的机制。**