一、Pandas 基础
在本章中,我们将介绍以下内容:
本章的目的是通过彻底检查序列和数据帧数据结构来介绍 Pandas 的基础。 对于 Pandas 用户来说,了解序列和数据帧的每个组件,并了解 Pandas 中的每一列数据正好具有一种数据类型,这一点至关重要。
在本章中,您将学习如何从数据帧中选择一个数据列,该数据列将作为序列返回。 使用此一维对象可以轻松显示不同的方法和运算符如何工作。 许多序列方法返回另一个序列作为输出。 这导致有可能连续调用其他方法,这被称为方法链接。
序列和数据帧的索引组件是将 Pandas 与其他大多数数据分析库区分开的组件,并且是了解执行多少操作的关键。 当我们将其用作序列值的有意义的标签时,我们将瞥见这个强大的对象。 最后两个秘籍包含在数据分析期间经常发生的简单任务。
在深入研究 Pandas 之前,值得了解数据帧的组件。 在视觉上,Pandas 数据帧的输出显示(在 Jupyter 笔记本中)似乎只不过是由行和列组成的普通数据表。 隐藏在表面下方的是三个组成部分-您必须具备的索引,列和数据(也称为值)。 请注意,以便最大化数据帧的全部潜力。
此秘籍将电影数据集读入 pandas 数据帧中,并提供其所有主要成分的标签图。
read_csv
函数读取影片数据集,并使用head
方法显示前五行:>>> movie = pd.read_csv('data/movie.csv')
>>> movie.head()
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ZpZeGUoT-1681366519507)(https://gitcode.net/apachecn/apachecn-ds-zh/-/raw/master/docs/master-pandas/img/00012.jpeg)]
Pandas 首先使用出色且通用的read_csv
函数将数据从磁盘读入内存,然后读入数据帧。 列和索引的输出均以粗体显示,这使它们易于识别。 按照惯例,术语索引标签和列名分别是指索引和列的各个成员。 术语索引整体上指所有索引标签,正如术语列整体上指所有列名称一样。
列和索引用于特定目的,即为数据帧的列和行提供标签。 这些标签允许直接轻松地访问不同的数据子集。 当多个序列或数据帧组合在一起时,索引将在进行任何计算之前首先对齐。 列和索引统称为轴。
DataFrame具有两个轴:垂直轴(索引)和水平轴(列)。 Pandas 借鉴了 NumPy 的约定,并使用整数 0/1 作为引用垂直/水平轴的另一种方式。
数据帧的数据(值)始终为常规字体,并且是与列或索引完全独立的组件。 Pandas 使用NaN
(不是数字)来表示缺失值。 请注意,即使color
列仅包含字符串值,它仍使用NaN
表示缺少的值。
列中间的三个连续点表示存在至少一列,但由于列数超过了预定义的显示限制,因此未显示。
Python 标准库包含csv
模块,可用于解析和读取数据。 Pandas 的read_csv
函数比该模块提供了性能和功能上的强大提升。
head
方法接受单个参数n
,该参数控制显示的行数。 同样,tail
方法返回最后的n
行。
可以直接从数据帧访问三个数据帧组件(索引,列和数据)中的每一个。 每个组件本身都是一个 Python 对象,具有自己的独特属性和方法。 通常,您希望对单个组件而不是对整个数据帧进行操作。
此秘籍将数据帧的索引,列和数据提取到单独的变量中,然后说明如何从同一对象继承列和索引。
index
,columns
和values
将索引,列和数据分配给它们自己的变量:>>> movie = pd.read_csv('data/movie.csv')
>>> index = movie.index
>>> columns = movie.columns
>>> data = movie.values
>>> index
RangeIndex(start=0, stop=5043, step=1)
>>> columns
Index(['color', 'director_name', 'num_critic_for_reviews',
...
'imdb_score', 'aspect_ratio', 'movie_facebook_likes'],
dtype='object')
>>> data
array([['Color', 'James Cameron', 723.0, ..., 7.9, 1.78, 33000],
...,
['Color', 'Jon Gunn', 43.0, ..., 6.6, 1.85, 456]],
dtype=object)
>>> type(index)
pandas.core.indexes.range.RangeIndex
>>> type(columns)
pandas.core.indexes.base.Index
>>> type(data)
numpy.ndarray
issubclass
方法检查RangeIndex
是否确实是Index
的子类:>>> issubclass(pd.RangeIndex, pd.Index)
True
您可以使用index
,columns
和values
属性访问数据帧的三个主要组件。columns
属性的输出似乎只是列名称的序列。 从技术上讲,此列名称序列是Index
对象。 函数type
的输出是对象的完全限定的类名。
变量columns
的对象的全限定类名称为pandas.core.indexes.base.Index
。 它以包名称开头,后跟模块路径,并以类型名称结尾。 引用对象的常用方法是在包名称后加上对象类型的名称。 在这种情况下,我们将这些列称为 Pandas 的Index
对象。
内置的subclass
函数检查第一个参数是否从第二个参数继承。Index
和RangeIndex
对象非常相似,实际上,pandas 具有许多专门为索引或列保留的相似对象。 索引和列都必须都是某种Index
对象。 本质上,索引和列表示同一事物,但沿不同的轴。 有时将它们称为行索引和列索引。
在这种情况下,Index
对象是指可用于索引或列的所有可能的对象。 它们都是pd.Index
的子类。 这是Index
对象的完整列表:CategoricalIndex
,MultiIndex
,IntervalIndex
,Int64Index
,UInt64Index
,Float64Index
,RangeIndex
,TimedeltaIndex
,DatetimeIndex
,PeriodIndex
。
RangeIndex
是Index
对象的一种特殊类型,类似于 Python 的range
对象。 直到必须将其整个值序列加载到内存中为止,从而节省了内存。 它完全由其开始,停止和步长值定义。
尽可能使用哈希表实现Index
对象,以实现非常快速的选择和数据对齐。 它们与 Python 集相似,因为它们支持诸如相交和并集之类的操作,但是由于它们的排序允许重复,因此它们是不同的。
Python 字典和集合也通过哈希表实现,无论对象的大小如何,都可以在恒定时间内非常快速地进行成员资格检查。
注意values
数据帧属性如何返回 NumPy N 维数组或ndarray
。 大部分 Pandas 都严重依赖ndarray
。 在索引,列和数据之下是 NumPy ndarrays
。 可以将它们视为构建许多其他对象的 Pandas 的基本对象。 要看到这一点,我们可以查看index
和columns
的值:
>>> index.values
array([ 0, 1, 2, ..., 4913, 4914, 4915])
>>> columns.values
array(['color', 'director_name', 'num_critic_for_reviews',
...
'imdb_score', 'aspect_ratio', 'movie_facebook_likes'],
dtype=object)
用非常广泛的术语来说,数据可以分类为连续的或分类的。 连续数据始终是数字,代表某种度量,例如身高,工资或薪水。 连续数据可能具有无限数量的可能性。 另一方面,分类数据代表离散的有限数量的值,例如汽车颜色,扑克手类型或谷类食品品牌。
Pandas 没有将数据大致分为连续数据或分类数据。 相反,它对许多不同的数据类型都有精确的技术定义。 下表包含所有 pandas 数据类型,及其等效字符串,以及每种类型的一些注释:
通用数据类型名称 | NumPy / Pandas 对象 | Pandas 字符串名称 | 注释 |
---|---|---|---|
布尔 | np.bool | bool | 存储为单个字节。 |
整数 | np.int | int | 默认为 64 位。 也可以使用无符号整数 - np.uint。 |
浮点 | np.float | float | 默认为 64 位。 |
复数 | np.complex | complex | 在数据分析中很少见。 |
对象 | np.object | O和object | 通常为字符串,但是对于具有多种不同类型的列或其他 Python 对象(元组,列表,字典等)来说是万能的。 |
日期时间 | np.datetime64, pd.Timestamp | datetime64 | 具有纳秒精度的特定时间点。 |
时间增量 | np.timedelta64, pd.Timedelta | timedelta64 | 时间增量,从几天到纳秒。 |
类别 | pd.Categorical | Categorical | 仅限于 Pandas。 对于唯一值相对较少的对象列很有用。 |
在此秘籍中,我们将显示数据帧中每一列的数据类型。 了解每一列中保存的数据类型至关重要,因为它会从根本上改变可能进行的操作的类型。
dtypes
属性显示每一列及其数据类型:>>> movie = pd.read_csv('data/movie.csv')
>>> movie.dtypes
color object
director_name object
num_critic_for_reviews float64
duration float64
director_facebook_likes float64
...
title_year float64
actor_2_facebook_likes float64
imdb_score float64
aspect_ratio float64
movie_facebook_likes int64
Length: 28, dtype: object
get_dtype_counts
方法返回每种数据类型的计数:>>> movie.get_dtype_counts()
float64 13
int64 3
object 12
每个数据帧的列必须恰好是一种类型。 例如,aspect_ratio
列中的每个值都是 64 位浮点数,movie_facebook_likes
列中的每个值都是 64 位整数。 Pandas 默认使用其核心数字类型,整数,并且浮点数为 64 位,而不管所有数据放入内存所需的大小如何。 即使列完全由整数值 0 组成,数据类型仍将为int64
。get_dtype_counts
是一种方便的方法,用于直接返回数据帧中所有数据类型的计数。
同构数据是指所有具有相同类型的列的另一个术语。 整个数据帧可能包含不同列的不同数据类型的异构数据。
对象数据类型是一种与其他数据类型不同的数据类型。 对象数据类型的列可以包含任何有效 Python 对象的值。 通常,当列属于对象数据类型时,它表示整个列都是字符串。 不一定是这种情况,因为这些列可能包含整数,布尔值,字符串或其他甚至更复杂的 Python 对象(例如列表或字典)的混合物。 对象数据类型是 Pandas 无法识别为其他任何特定类型的列的全部内容。
几乎所有的 Pandas 数据类型都是直接从 NumPy 构建的。 这种紧密的集成使用户可以更轻松地集成 Pandas 和 NumPy 操作。 随着 Pandas 越来越大,越来越流行,事实证明,对象数据类型对于具有字符串值的所有列来说太通用了。 Pandas 创建了自己的分类数据类型,以处理具有固定数量的可能值的字符串(或数字)列。
序列是来自数据帧的单列数据。 它是数据的一个维度,仅由索引和数据组成。
此秘籍检查了两种不同的语法以选择“序列”,一种使用索引运算符,另一种使用点符号。
>>> movie = pd.read_csv('data/movie.csv')
>>> movie['director_name']
>>> movie.director_name
>>> type(movie['director_name'])
pandas.core.series.Series
Python 有几个内置对象用于包含数据,例如列表,元组和字典。 所有这三个对象都使用索引运算符来选择其数据。数据帧是更强大,更复杂的数据容器,但它们也使用索引运算符作为选择数据的主要方式。 将单个字符串传递给数据帧索引运算符将返回一个序列。
序列的视觉输出风格比数据帧少。 它代表一列数据。 连同索引和值一起,输出显示序列的名称,长度和数据类型。
或者,虽然不建议这样做,但可能会出错,但是可以使用带有列名作为属性的点表示法来访问数据列。 尽管它适用于此特定示例,但这不是最佳实践,并且容易出错和误用。 不能以这种方式访问带有空格或特殊字符的列名称。 如果列名称为director name
,则该操作将失败。 与数据帧方法冲突的列名,例如count
,也无法使用点符号正确选择。 分配新值或删除带有点符号的列可能会导致意外的结果。 因此,在生产代码中应避免使用点表示法访问列。
如果会引起麻烦,为什么有人会使用点符号语法呢? 程序员很懒,而且键入的字符更少。 但主要是,当您想使用自动完成智能功能时,它非常方便。 因此,在本书中有时会使用点标记进行列选择。 自动完成智能非常适合帮助您了解对象可用的所有可能的属性和方法。
在使用步骤 1 中的索引运算符后,尝试链接操作时,智能将无法工作,但将继续使用步骤 2 中的点符号。下面的屏幕快照显示了在选择了索引之后的弹出窗口。director_name
带点符号。 在点后按选项卡后,所有可能的属性和方法将显示在列表中:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8PbpJB94-1681366519508)(https://gitcode.net/apachecn/apachecn-ds-zh/-/raw/master/docs/master-pandas/img/00014.jpeg)]
在 Jupyter 笔记本中,在按下Shift + Tab + Tab
,并将光标放在对象中某处的情况下,将弹出文档字符串窗口,使该方法更易于使用。 如果您在使用索引运算符选择一列后尝试链接一个操作,则该智能再次消失。
注意点表示法的另一个原因是,它在流行的问答网站 Stack Overflow 上在线使用的数量激增。 另外,请注意,旧列名称现在是序列的name
,实际上已经成为一个属性:
>>> director = movie['director_name']
>>> director.name
'director_name'
可以使用to_frame
方法将此序列转换为单列数据帧。 此方法将使用序列名称作为新的列名称:
>>> director.to_frame()
__getitem__
特殊方法。利用一维序列是所有 Pandas 数据分析的组成部分。 典型的工作流程将使您在序列和数据帧上的执行语句之间来回切换。 调用序列方法是使用序列提供的功能的主要方法。
序列和数据帧都具有强大的函数。 我们可以使用dir
函数来揭示序列的所有属性和方法。 此外,我们可以找到序列和数据帧共有的属性和方法的数量。 这两个对象共享绝大多数的属性和方法名称:
>>> s_attr_methods = set(dir(pd.Series))
>>> len(s_attr_methods)
442
>>> df_attr_methods = set(dir(pd.DataFrame))
>>> len(df_attr_methods)
445
>>> len(s_attr_methods & df_attr_methods)
376
本秘籍涵盖了最常见且功能最强大的序列方法。 对于数据帧,许多方法几乎是等效的。
director_name
列包含字符串,形式上是对象数据类型,列actor_1_facebook_likes
包含数字数据,形式上是float64
:>>> movie = pd.read_csv('data/movie.csv')
>>> director = movie['director_name']
>>> actor_1_fb_likes = movie['actor_1_facebook_likes']
head
:>>> director.head()
0 James Cameron
1 Gore Verbinski
2 Sam Mendes
3 Christopher Nolan
4 Doug Walker
Name: director_name, dtype: object
>>> actor_1_fb_likes.head()
0 1000.0
1 40000.0
2 11000.0
3 27000.0
4 131.0
Name: actor_1_facebook_likes, dtype: float64
value_counts
,它计算每个唯一值的所有出现次数:>>> director.value_counts()
Steven Spielberg 26
Woody Allen 22
Martin Scorsese 20
Clint Eastwood 20
..
Fatih Akin 1
Analeine Cal y Mayor 1
Andrew Douglas 1
Scott Speer 1
Name: director_name, Length: 2397, dtype: int64
value_counts
方法通常对于具有对象数据类型的序列更为有用,但有时也可以提供对数值序列的深入了解。 与actor_1_fb_likes
一起使用时,似乎已将较高的数字四舍五入到最接近的千位,因为不太可能有那么多电影获得准确的 1,000 个赞:>>> actor_1_fb_likes.value_counts()
1000.0 436
11000.0 206
2000.0 189
3000.0 150
...
216.0 1
859.0 1
225.0 1
334.0 1
Name: actor_1_facebook_likes, Length: 877, dtype: int64
size
或shape
参数或len
函数对序列中的元素数进行计数:>>> director.size
4916
>>> director.shape
(4916,)
>>> len(director)
4916
count
方法,它返回非缺失值的数量:>>> director.count()
4814
>>> actor_1_fb_likes.count()
4909
min
,max
,mean
,median
,std
和sum
方法得出:>>> actor_1_fb_likes.min(), actor_1_fb_likes.max(), \
actor_1_fb_likes.mean(), actor_1_fb_likes.median(), \
actor_1_fb_likes.std(), actor_1_fb_likes.sum()
(0.0, 640000.0, 6494.488490527602, 982.0, 15106.98, 31881444.0)
describe
方法一次返回汇总统计信息和一些分位数。 当describe
与对象数据类型列一起使用时,将返回完全不同的输出:>>> actor_1_fb_likes.describe()
count 4909.000000
mean 6494.488491
std 15106.986884
min 0.000000
25% 607.000000
50% 982.000000
75% 11000.000000
max 640000.000000
Name: actor_1_facebook_likes, dtype: float64
>>> director.describe()
count 4814
unique 2397
top Steven Spielberg
freq 26
Name: director_name, dtype: object
quantile
方法用于计算数字数据的精确分位数:>>> actor_1_fb_likes.quantile(.2)
510
>>> actor_1_fb_likes.quantile([.1, .2, .3, .4, .5,
.6, .7, .8, .9])
0.1 240.0
0.2 510.0
0.3 694.0
0.4 854.0
...
0.6 1000.0
0.7 8000.0
0.8 13000.0
0.9 18000.0
Name: actor_1_facebook_likes, Length: 9, dtype: float64
count
方法返回的值小于在第 5 步中找到的序列元素的总数,因此我们知道每个序列中都有缺失的值。isnull
方法可用于确定每个单独的值是否丢失。 结果将是布尔序列,其长度与原始序列相同:>>> director.isnull()
0 False
1 False
2 False
3 False
...
4912 True
4913 False
4914 False
4915 False
Name: director_name, Length: 4916, dtype: bool
fillna
方法替换序列中的所有缺失值:>>> actor_1_fb_likes_filled = actor_1_fb_likes.fillna(0)
>>> actor_1_fb_likes_filled.count()
4916
dropna
:>>> actor_1_fb_likes_dropped = actor_1_fb_likes.dropna()
>>> actor_1_fb_likes_dropped.size
4909
将字符串传递给数据帧的索引运算符会将单个列选择为序列。 选择本秘籍中使用的方法是因为它们在数据分析中的使用频率。
本秘籍中的步骤应简单明了,并具有易于解释的输出。 即使输出易于阅读,您也可能无法跟踪返回的对象。 它是标量值,元组,另一个序列还是其他 Python 对象? 花一点时间,看看每一步之后返回的输出。 您可以命名返回的对象吗?
步骤 1 中head
方法的结果是另一个序列。value_counts
方法也产生一个序列,但具有原始序列的唯一值作为索引,计数作为其值。 在步骤 5 中,size
和count
返回标量值,但是shape
返回单项元组。
形状属性返回一个单项元组似乎很奇怪,但这是从 NumPy 借来的约定,它允许任意数量的维度的数组。
在步骤 7 中,每个方法返回一个标量值,并作为元组输出。 这是因为 Python 将仅包含逗号分隔值且不带括号的表达式视为元组。
在步骤 8 中,describe
返回一个序列,其所有摘要统计信息名称均作为索引,而实际统计信息则为值。
在步骤 9 中,quantile
是灵活的,当传递单个值时返回标量值,但在给定列表时返回序列。
从步骤 10、11 和 12,isnull
,fillna
和dropna
都返回一个序列。
value_counts
方法是最有用的序列方法之一,在探索性分析中特别是在分类列分析中被大量使用。 它默认返回计数,但是通过将normalize
参数设置为True
,则返回相对频率,这提供了另一种分布图:
>>> director.value_counts(normalize=True)
Steven Spielberg 0.005401
Woody Allen 0.004570
Martin Scorsese 0.004155
Clint Eastwood 0.004155
...
Fatih Akin 0.000208
Analeine Cal y Mayor 0.000208
Andrew Douglas 0.000208
Scott Speer 0.000208
Name: director_name, Length: 2397, dtype: float64
在此秘籍中,我们通过观察count
方法的结果与size
属性不匹配,确定该序列中缺少值。 一种更直接的方法是使用hasnans
属性:
>>> director.hasnans
True
isnull
存在一个补充:notnull
方法,该方法为所有非缺失值返回True
:
>>> director.notnull()
0 True
1 True
2 True
3 True
...
4912 False
4913 True
4914 True
4915 True
Name: director_name, Length: 4916, dtype: bool
Python 中存在大量用于操作对象的运算符。 运算符本身不是对象,而是强制对对象执行操作的语法结构和关键字。 例如,将加法运算符放在两个整数之间时,Python 会将它们加在一起。 在以下代码中查看更多运算符示例:
>>> 5 + 9 # plus operator example adds 5 and 9
14
>>> 4 ** 2 # exponentiation operator raises 4 to the second power
16
>>> a = 10 # assignment operator assigns 10 to a
>>> 5 <= 9 # less than or equal to operator returns a boolean
True
运算符可以处理任何类型的对象,而不仅仅是数字数据。 这些示例显示了正在操作的不同对象:
>>> 'abcde' + 'fg'
'abcdefg'
>>> not (5 <= 9)
False
>>> 7 in [1, 2, 6]
False
>>> set([1,2,3]) & set([2,3,4])
set([2,3])
访问 TutorialsPoint,以查看所有基本 Python 运算符的表。 并非对每个对象都实现所有运算符。 这些示例在使用运算符时都会产生错误:
>>> [1, 2, 3] - 3
TypeError: unsupported operand type(s) for -: 'list' and 'int'
>>> a = set([1,2,3])
>>> a[0] TypeError: 'set' object does not support indexing
序列和数据帧对象可与大多数 Python 运算符一起使用。
在此秘籍中,各种运算符将应用于不同的序列对象,以产生具有完全不同值的新序列。
imdb_score
列作为序列:>>> movie = pd.read_csv('data/movie.csv')
>>> imdb_score = movie['imdb_score']
>>> imdb_score
0 7.9
1 7.1
2 6.8
...
4913 6.3
4914 6.3
4915 6.6
Name: imdb_score, Length: 4916, dtype: float64
>>> imdb_score + 1
0 8.9
1 8.1
2 7.8
...
4913 7.3
4914 7.3
4915 7.6
Name: imdb_score, Length: 4916, dtype: float64
-
),乘法(*
),除法(/
)和幂(**
)与标量值相似。 在这一步中,我们将序列乘以2.5
:>>> imdb_score * 2.5
0 19.75
1 17.75
2 17.00
...
4913 15.75
4914 15.75
4915 16.50
Name: imdb_score, Length: 4916, dtype: float64
//
)进行地板除法,并使用百分号(%
)进行模数运算,这将在除法后返回余数。 序列使用这些相同的方式:>>> imdb_score // 7
0 1.0
1 1.0
2 0.0
...
4913 0.0
4914 0.0
4915 0.0
Name: imdb_score, Length: 4916, dtype: float64
>
),小于(<
),大于或等于(>=
,小于或等于(<=
),等于(==
),并且不等于(!=
)。 每个比较运算符都会根据条件的结果将序列中的每个值转换为True
或False
:>>> imdb_score > 7
0 True
1 True
2 False
...
4913 False
4914 False
4915 False
Name: imdb_score, Length: 4916, dtype: bool
>>> director = movie['director_name']
>>> director == 'James Cameron'
0 True
1 False
2 False
...
4913 False
4914 False
4915 False
Name: director_name, Length: 4916, dtype: bool
本秘籍中使用的所有运算符都将相同的操作应用于序列中的每个元素。 在本机 Python 中,这将需要一个for
循环在应用操作之前遍历序列中的每个项目。 Pandas 严重依赖 NumPy 库,该库允许进行向量化计算,也可以对整个数据序列进行操作而无需显式编写for
循环。 每个操作都返回一个具有相同索引的序列,但其值已被运算符修改。
此秘籍中使用的所有运算符都具有等效的方法,这些方法可产生完全相同的结果。 例如,在步骤 1 中,可以用add
方法再现imdb_score + 1
。 检查以下代码以查看秘籍中每个步骤的方法版本:
>>> imdb_score.add(1) # imdb_score + 1
>>> imdb_score.mul(2.5) # imdb_score * 2.5
>>> imdb_score.floordiv(7) # imdb_score // 7
>>> imdb_score.gt(7) # imdb_score > 7
>>> director.eq('James Cameron') # director == 'James Cameron'
为什么 Pandas 提供与这些运算符等效的方法? 从本质上讲,运算符只能以一种方式进行操作。 另一方面,方法可以具有允许您更改其默认行为的参数:
运算符分组 | 运算符 | 序列方法名称 |
---|---|---|
算术 | +,-,*,/,//,%,** | add,sub,mul,div,floordiv,mod,pow |
比较 | <,>,<=,>=,==,!= | lt,gt,le,ge,eq,ne |
您可能对 Python 序列对象或与此相关的任何对象如何在遇到运算符时知道该怎么办感到好奇。 例如,表达式imdb_score * 2.5
如何知道将序列中的每个元素乘以2.5
? Python 使用特殊方法为对象与运算符通信提供了一种内置的标准化方法。
特殊方法是对象在遇到运算符时在内部调用的方法。 特殊方法在 Python 数据模型中定义,这是官方文档中非常重要的一部分,并且对于整个语言中的每个对象都是相同的。 特殊方法始终以两个下划线开头和结尾。 例如,每当使用乘法运算符时,就会调用特殊方法__mul__
。 Python 将imdb_score * 2.5
表达式解释为imdb_score.__mul__(2.5)
。
使用特殊方法和使用运算符之间没有什么区别,因为它们在做完全相同的事情。 运算符只是特殊方法的语法糖。
在 Python 中,每个变量都是一个对象,并且所有对象都具有引用或返回更多对象的属性和方法。 使用点符号的方法的顺序调用称为方法链接。 Pandas 是一个很适合进行方法链接的库,因为许多序列和数据帧方法返回更多的序列和数据帧,因此可以调用更多方法。
为了激励方法链接,让我们用一个简单的英语句子将事件链转换为方法链。 考虑一下句子,“一个人开车去商店买食物,然后开车回家,在洗碗之前准备,做饭,上菜和吃食物”。
该句子的 Python 版本可能采用以下形式:
>>> person.drive('store')\
.buy('food')\
.drive('home')\
.prepare('food')\
.cook('food')\
.serve('food')\
.eat('food')\
.cleanup('dishes')
在前面的代码中,person
是调用每个方法的对象,就像人正在执行原始句子中的所有动作一样。 传递给每个方法的参数指定方法的操作方式。
尽管可以在单个连续的行中写入整个方法链,但更可取的是在每行中写入一个方法。 由于 Python 通常不允许将一个表达式写在多行上,因此您需要使用反斜杠行继续符。 或者,您可以将整个表达式用括号括起来。 为了进一步提高可读性,请将每种方法直接放在其上方的点下。 此秘籍显示了与 Pandas 序列相似的方法链接。
>>> movie = pd.read_csv('data/movie.csv')
>>> actor_1_fb_likes = movie['actor_1_facebook_likes']
>>> director = movie['director_name']
head
方法。 这抑制了长输出。 对于较短的链,则不需要将每种方法放在不同的行上:>>> director.value_counts().head(3)
Steven Spielberg 26
Woody Allen 22
Clint Eastwood 20
Name: director_name, dtype: int64
isnull
之后链接sum
方法:>>> actor_1_fb_likes.isnull().sum()
7
actor_1_fb_likes
的所有非缺失值都应为整数,因为不可能有小数 Facebook 点赞。 缺少值的任何数字列的数据类型都必须为float
。 如果我们用零填充actor_1_fb_likes
中的缺失值,则可以使用astype
方法将其转换为整数:>>> actor_1_fb_likes.dtype
dtype('float64')
>>> actor_1_fb_likes.fillna(0)\
.astype(int)\
.head()
0 1000
1 40000
2 11000
3 27000
4 131
Name: actor_1_facebook_likes, dtype: int64
所有的 Python 对象都可以进行方法链接,因为每个对象方法必须返回另一个自身将具有更多方法的对象。 该方法不必返回相同类型的对象。
步骤 2 首先使用value_counts
返回一个序列,然后链接head
方法以选择前三个元素。 最后返回的对象是一个序列,也可以在其上链接更多方法。
在步骤 3 中,isnull
方法创建一个布尔序列。 Pandas 在数值上将False
/True
求值为 0/1,因此sum
方法返回缺失值的数量。
步骤 4 中的三个链接方法中的每一个都返回一个序列。 似乎不直观,但是astype
方法返回具有不同数据类型的全新序列。
无需对第 3 步中的布尔值求和以找到缺失值的总数,我们可以采用序列的平均值来获取缺失值的百分比:
>>> actor_1_fb_likes.isnull().mean()
0.0014
如本秘籍开头所述,对于多行代码,可以使用括号而不是反斜杠。 第 4 步可以这样重写:
>>> (actor_1_fb_likes.fillna(0)
.astype(int)
.head())
并不是所有的程序员都喜欢使用方法链接,因为它有一些缺点。 这样的缺点之一是调试变得困难。 链中产生的中间对象都不存储在变量中,因此,如果出现意外结果,将很难跟踪链中发生它的确切位置。
秘籍开头的示例可以重写,以使每种方法的结果都保存为唯一变量。 这使跟踪错误更加容易,因为您可以在每个步骤检查对象:
>>> person1 = person.drive('store')
>>> person2 = person1.buy('food')
>>> person3 = person2.drive('home')
>>> person4 = person3.prepare('food')
>>> person5 = person4.cook('food')
>>> person6 = person5.serve('food')
>>> person7 = person6.eat('food')
>>> person8 = person7.cleanup('dishes')
数据帧的索引为每行提供一个标签。 如果在创建数据帧时未显式提供索引,则默认情况下,将创建RangeIndex
,其标签为从 0 到n-1
的整数,其中 n 是行数。
此秘籍将影片数据集的毫无意义的默认行索引替换为影片标题,这更有意义。
set_index
方法将每部电影的标题设置为新索引:>>> movie = pd.read_csv('data/movie.csv')
>>> movie2 = movie.set_index('movie_title')
>>> movie2
read_csv
函数的index_col
参数在初始读取时选择一列作为索引:>>> movie = pd.read_csv('data/movie.csv', index_col='movie_title')
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-K8Xzmx5S-1681366519509)(https://gitcode.net/apachecn/apachecn-ds-zh/-/raw/master/docs/master-pandas/img/00015.jpeg)]
一个有意义的索引是清楚地标识每一行的索引。 默认的范围索引不是很有帮助。 由于每一行仅标识一部电影的数据,因此使用电影标题作为标签是有意义的。 如果您提前知道哪个列将是一个很好的索引,则可以在导入时使用read_csv
函数的index_col
参数指定该索引。
默认情况下,set_index
和read_csv
都将从数据帧中删除用作索引的列。 使用set_index
,可以通过将drop
参数设置为False
将列保留在数据帧中。
相反,可以使用reset_index
方法将索引变成一列。 这将使movie_title
再次成为一列,并将索引还原回RangeIndex
。reset_index
始终将列作为数据帧中的第一个列,因此这些列可能未按其原始顺序排列:
>>> movie2.reset_index()
数据帧上最基本,最常见的操作之一是重命名行或列的名称。 好的列名是描述性的,简短的,并且在大小写,空格,下划线和其他功能方面遵循通用约定。
在此秘籍中,行名和列名均被重命名。
>>> movie = pd.read_csv('data/movie.csv', index_col='movie_title')
rename
方法接受将旧值映射到新值的字典。 让我们为行创建一个,为列创建另一个:>>> idx_rename = {'Avatar':'Ratava', 'Spectre': 'Ertceps'}
>>> col_rename = {'director_name':'Director Name',
'num_critic_for_reviews': 'Critical Reviews'}
rename
方法,并将结果分配给新变量:>>> movie_renamed = movie.rename(index=idx_rename,
columns=col_rename)
>>> movie_renamed.head()
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-B03tQmBz-1681366519509)(https://gitcode.net/apachecn/apachecn-ds-zh/-/raw/master/docs/master-pandas/img/00016.jpeg)]
数据帧的rename
方法允许使用index
和columns
参数同时重命名行标签和列标签。 这些参数中的每一个都可以设置为字典,该字典将旧标签映射到它们的新值。
重命名行标签和列标签有多种方法。 可以直接将索引和列属性重新分配给 Python 列表。 当列表具有与行和列标签相同数量的元素时,此分配有效。 以下代码在每个索引对象上使用tolist
方法来创建 Python 标签列表。 然后,它修改列表中的几个值,并将列表重新分配给属性index
和columns
:
>>> movie = pd.read_csv('data/movie.csv', index_col='movie_title')
>>> index = movie.index
>>> columns = movie.columns
>>> index_list = index.tolist()
>>> column_list = columns.tolist()
## rename the row and column labels with list assignments
>>> index_list[0] = 'Ratava'
>>> index_list[2] = 'Ertceps'
>>> column_list[1] = 'Director Name'
>>> column_list[2] = 'Critical Reviews'
>>> print(index_list)
['Ratava', "Pirates of the Caribbean: At World's End", 'Ertceps', 'The Dark Knight Rises', ... ]
>>> print(column_list)
['color', 'Director Name', 'Critical Reviews', 'duration', ...]
## finally reassign the index and columns
>>> movie.index = index_list
>>> movie.columns = column_list
在数据分析期间,极有可能需要创建新列来表示新变量。 通常,这些新列将从数据集中已有的先前列创建。 Pandas 有几种不同的方法可以向数据帧添加新列。
在此秘籍中,我们通过使用赋值在影片数据集中创建新列,然后使用drop
方法删除列。
has_seen
列以指示我们是否看过电影。 我们将为每个值分配零。 默认情况下,新列将追加到末尾:>>> movie = pd.read_csv('data/movie.csv')
>>> movie['has_seen'] = 0
actor_director_facebook_likes
列:>>> movie['actor_director_facebook_likes'] = \
(movie['actor_1_facebook_likes'] +
movie['actor_2_facebook_likes'] +
movie['actor_3_facebook_likes'] +
movie['director_facebook_likes'])
>>> movie['actor_director_facebook_likes'].isnull().sum()
122
>>> movie['actor_director_facebook_likes'] = \
movie['actor_director_facebook_likes'].fillna(0)
cast_total_facebook_likes
。 看到此列的百分比来自我们新创建的列actor_director_facebook_likes
会很有趣。 在创建百分比列之前,我们先进行一些基本数据验证。 让我们确保cast_total_facebook_likes
大于或等于actor_director_facebook_likes
:>>> movie['is_cast_likes_more'] = \
(movie['cast_total_facebook_likes'] >=
movie['actor_director_facebook_likes'])
is_cast_likes_more
现在是一列布尔值。 我们可以使用all
序列方法检查此列的所有值是否均为True
:>>> movie['is_cast_likes_more'].all()
False
actor_director_facebook_likes
比cast_total_facebook_likes
多。 导演的 Facebook 点赞可能不是演员总点赞数的一部分。 让我们回溯并删除actor_director_facebook_likes
列:>>> movie = movie.drop('actor_director_facebook_likes',
axis='columns')
>>> movie['actor_total_facebook_likes'] = \
(movie['actor_1_facebook_likes'] +
movie['actor_2_facebook_likes'] +
movie['actor_3_facebook_likes'])
>>> movie['actor_total_facebook_likes'] = \
movie['actor_total_facebook_likes'].fillna(0)
cast_total_facebook_likes
中的所有值是否都大于actor_total_facebook_likes
:>>> movie['is_cast_likes_more'] = \
(movie['cast_total_facebook_likes'] >=
movie['actor_total_facebook_likes'])
>>> movie['is_cast_likes_more'].all()
True
actor_total_facebook_likes
的cast_total_facebook_likes
的百分比:>>> movie['pct_actor_cast_like'] = \
(movie['actor_total_facebook_likes'] /
movie['cast_total_facebook_likes'])
>>> (movie['pct_actor_cast_like'].min(),
movie['pct_actor_cast_like'].max())
(0.0, 1.0)
>>> movie.set_index('movie_title')['pct_actor_cast_like'].head()
movie_title
Avatar 0.577369
Pirates of the Caribbean: At World's End 0.951396
Spectre 0.987521
The Dark Knight Rises 0.683783
Star Wars: Episode VII - The Force Awakens 0.000000
Name: pct_actor_cast_like, dtype: float64
许多 Pandas 操作都很灵活,列创建就是其中之一。 该秘籍既分配了标量值(如步骤 1 所示),又分配了序列(如步骤 2 所示),以创建新列。
步骤 2 将四个不同的序列使用加法运算符相加。 步骤 3 使用方法链来查找和填充缺失值。 步骤 4 使用大于或等于比较运算符返回布尔序列,然后在步骤 5 中使用all
方法对其进行求值,以检查每个单个值是否为True
。
drop
方法接受要删除的行或列的名称。 默认情况下是按索引名称删除行。 要删除列,必须将axis
参数设置为 1 或column
。 轴的默认值为 0 或字符串index
。
步骤 7 和 8 在没有director_facebook_likes
列的情况下将步骤 3 的工作重做到步骤 5。 第 9 步最终计算出自第 4 步以来我们想要的期望列。第 10 步验证百分比在 0 到 1 之间。
除了insert
方法的末尾,还可以将新列插入数据帧中的特定位置。insert
方法将新列的整数位置作为第一个参数,将新列的名称作为第二个参数,并将值作为第三个参数。 您将需要使用索引的get_loc
方法来查找列名称的整数位置。
insert
方法就地修改了调用的数据帧,因此不会有赋值语句。 可以通过从gross
中减去budget
并将其直接插入gross
之后,来计算每部电影的利润:
>>> profit_index = movie.columns.get_loc('gross') + 1
>>> profit_index
9
>>> movie.insert(loc=profit_index,
column='profit',
value=movie['gross'] - movie['budget'])
使用drop
方法删除列的另一种方法是使用del
语句:
>>> del movie['actor_director_facebook_likes']
在本章中,我们将介绍以下主题:
本章介绍了数据帧的许多基本操作。 许多秘籍将与第 1 章,“Pandas 基础”中的内容类似,这些内容主要涵盖序列操作。
选择单个列是通过将所需的列名作为字符串传递给数据帧的索引运算符来完成的。 在第 1 章,“Pandas 基础”的“选择序列”秘籍中对此进行了介绍。 通常需要关注当前工作数据集的一个子集,这是通过选择多个列来完成的。
在此秘籍中,将从movie
数据集中选择所有actor
和director
列。
>>> movie_actor_director = movie[['actor_1_name', 'actor_2_name',
'actor_3_name', 'director_name']]
>>> movie_actor_director.head()
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-RW7bO9TV-1681366519509)(https://gitcode.net/apachecn/apachecn-ds-zh/-/raw/master/docs/master-pandas/img/00017.jpeg)]
>>> movie[['director_name']].head()
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-qD5bfSiD-1681366519510)(https://gitcode.net/apachecn/apachecn-ds-zh/-/raw/master/docs/master-pandas/img/00018.jpeg)]
数据帧的索引运算符非常灵活,可以接受许多不同的对象。 如果传递了字符串,它将返回一维序列。 如果将列表传递给索引运算符,它将以指定顺序返回列表中所有列的数据帧。
步骤 2 显示了如何选择单个列作为数据帧而不是序列。 最常见的是,使用字符串选择单个列,从而得到一个序列。 当数据帧是所需的输出时,只需将列名放在一个单元素列表中。
在索引运算符内部传递长列表可能会导致可读性问题。 为了解决这个问题,您可以先将所有列名保存到列表变量中。 下面的代码获得与步骤 1 相同的结果:
>>> cols = ['actor_1_name', 'actor_2_name',
'actor_3_name', 'director_name']
>>> movie_actor_director = movie[cols]
KeyError
是处理 Pandas 的最常见例外之一。 此错误主要是由于列名或索引名的错误输入。 每当尝试不使用列表进行多列选择时,都会引发相同的错误:
>>> movie['actor_1_name', 'actor_2_name',
'actor_3_name', 'director_name']
KeyError: ('actor_1_name', 'actor_2_name',
'actor_3_name', 'director_name')
这是一个常见的错误,因为很容易忘记将所需的列放在列表中。 您可能想知道这里到底发生了什么。 技术上,用逗号分隔的四个字符串名称是一个元组对象。 通常,元组用开括号和闭括号括起来,但这不是必需的:
>>> tuple1 = 1, 2, 3, 'a', 'b'
>>> tuple2 = (1, 2, 3, 'a', 'b')
>>> tuple1 == tuple2
True
Pandas 正试图找到与元组('actor_1_name', 'actor_2_name', 'actor_3_name', 'director_name')
完全相同的列名。 它失败并引发KeyError
。
尽管列选择通常直接由索引运算符完成,但是有一些数据帧方法可以以替代方式方便其选择。select_dtypes
和filter
是执行此操作的两种有用方法。
您需要熟悉所有 Pandas 数据类型以及如何访问它们。 第 1 章,“Pandas 基础”中的“了解数据类型”秘籍具有包含所有 Pandas 数据类型的表。
get_dtype_counts
方法输出每种特定数据类型的列数:>>> movie = pd.read_csv('data/movie.csv',
index_col='movie_title')
>>> movie.get_dtype_counts()
float64 13
int64 3
object 11
dtype: int64
select_dtypes
方法仅选择整数列:>>> movie.select_dtypes(include=['int']).head()
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-amUmRPo4-1681366519510)(https://gitcode.net/apachecn/apachecn-ds-zh/-/raw/master/docs/master-pandas/img/00019.jpeg)]
include
参数:>>> movie.select_dtypes(include=['number']).head()
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-pzzph91C-1681366519510)(https://gitcode.net/apachecn/apachecn-ds-zh/-/raw/master/docs/master-pandas/img/00020.jpeg)]
filter
方法。 此方法很灵活,可以根据使用的参数搜索列名(或索引标签)。 在这里,我们使用like
参数搜索包含确切字符串facebook
的所有列名称:>>> movie.filter(like='facebook').head()
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6nGCfQwm-1681366519511)(https://gitcode.net/apachecn/apachecn-ds-zh/-/raw/master/docs/master-pandas/img/00021.jpeg)]
filter
方法允许使用regex
参数通过正则表达式搜索列。 在这里,我们搜索名称中某处有数字的所有列:>>> movie.filter(regex='\d').head()
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-cIf6UWiE-1681366519511)(https://gitcode.net/apachecn/apachecn-ds-zh/-/raw/master/docs/master-pandas/img/00022.jpeg)]
步骤 1 列出了所有不同数据类型的频率。 或者,您可以使用dtypes
属性来获取每一列的确切数据类型。select_dtypes
方法在其include
参数中获取数据类型的列表,并返回仅包含那些给定数据类型的列的数据帧。 列表值可以是数据类型的字符串名称,也可以是实际的 Python 对象。
filter
方法仅通过检查列名而不是实际数据值来选择列。 它具有三个互斥的参数items
,like
和regex
,一次只能使用其中一个。like
参数采用一个字符串,并尝试查找名称中某处包含该确切字符串的所有列名称。 为了获得更大的灵活性,您可以使用regex
参数代替通过正则表达式选择列名称。 这个特定的正则表达式\d
表示从零到九的所有数字,并且匹配其中至少包含一个数字的任何字符串。
正则表达式是代表搜索模式的字符序列,这些搜索模式用于选择文本的不同部分。 它们允许非常复杂和高度特定的模式匹配。
filter
方法带有另一个参数items
,该参数采用一列确切的列名。 这几乎与索引运算符完全相同,只是如果其中一个字符串与列名不匹配,则不会引发KeyError
。 例如,movie.filter(items=['actor_1_name', 'asdf'])
运行无错误,并返回单列数据帧。
select_dtypes
的一个令人困惑的方面是它同时接受字符串和 Python 对象的灵活性。 下表应阐明选择许多不同列数据类型的所有可能方法。 在 Pandas 中没有引用数据类型的标准或首选方法,因此最好同时了解两种方式:
Python 对象 | 字符串 | 注释 |
---|---|---|
np.number | number | 选择整数和浮点数,而不考虑大小 |
np.float64, np.float_, float | float64,float_,float | 仅选择 64 位浮点数 |
np.float16, np.float32, np.float128 | float16,float32,float128 | 分别选择精确的 16 位,32 位和 128 位浮点数 |
np.floating | floating | 选择所有浮点,而不管大小 |
np.int0, np.int64, np.int_, int | int0,int64,int_,int | 仅选择 64 位整数 |
np.int8, np.int16, np.int32 | int8,int16,int32 | 分别选择 8、16 和 32 位整数 |
np.integer | integer | 选择所有整数,而不考虑大小 |
np.object | object,O | 选择所有对象数据类型 |
np.datetime64 | datetime64,datetime | 所有 64 位的日期时间 |
np.timedelta64 | timedelta64,timedelta | 所有 64 位的时间增量 |
pd.Categorical | category | Pandas 特有的; NumPy 没有等效的东西 |
因为所有整数和浮点数默认为 64 位,所以可以通过使用字符串int
,或float
来选择它们,如上表所示。 如果要选择所有整数和浮点数,而不管它们的大小如何,请使用字符串number
。
select
方法还可以根据列名选择它们。最初将数据集导入为数据帧之后要考虑的首要任务之一是分析列的顺序。 这个基本任务经常被忽略,但是可以在分析进行中产生很大的不同。 计算机没有优先选择列顺序,计算也不受影响。 作为人类,我们自然地从左到右查看和阅读列,这直接影响我们对数据的解释。 杂物柱布置类似于壁橱中的杂物衣服布置。 在短裤顶部的衬衫和裤子旁边放西装是没有好处的。 考虑列顺序时,查找和解释信息要容易得多。
没有标准的规则集来规定应如何在数据集中组织列。 但是,优良作法是制定一组您始终遵循的准则以简化分析。 如果您与一组共享大量数据集的分析师合作,则尤其如此。
以下是排序列的简单指南:
本秘籍向您展示如何使用此指南排序各列。 有许多明智的可能排序。
>>> movie = pd.read_csv('data/movie.csv')
>>> movie.head()
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ilxwLp4A-1681366519511)(https://gitcode.net/apachecn/apachecn-ds-zh/-/raw/master/docs/master-pandas/img/00023.jpeg)]
>>> movie.columns
Index(['color', 'director_name', 'num_critic_for_reviews',
'duration', 'director_facebook_likes',
'actor_3_facebook_likes', 'actor_2_name',
'actor_1_facebook_likes', 'gross', 'genres',
'actor_1_name', 'movie_title', 'num_voted_users',
'cast_total_facebook_likes', 'actor_3_name',
'facenumber_in_poster', 'plot_keywords',
'movie_imdb_link', 'num_user_for_reviews', 'language',
'country', 'content_rating', 'budget', 'title_year',
'actor_2_facebook_likes', 'imdb_score', 'aspect_ratio',
'movie_facebook_likes'], dtype='object')
>>> disc_core = ['movie_title', 'title_year',
'content_rating', 'genres']
>>> disc_people = ['director_name', 'actor_1_name',
'actor_2_name', 'actor_3_name']
>>> disc_other = ['color', 'country', 'language',
'plot_keywords', 'movie_imdb_link']
>>> cont_fb = ['director_facebook_likes', 'actor_1_facebook_likes',
'actor_2_facebook_likes', 'actor_3_facebook_likes',
'cast_total_facebook_likes', 'movie_facebook_likes']
>>> cont_finance = ['budget', 'gross']
>>> cont_num_reviews = ['num_voted_users', 'num_user_for_reviews',
'num_critic_for_reviews']
>>> cont_other = ['imdb_score', 'duration',
'aspect_ratio', 'facenumber_in_poster']
>>> new_col_order = disc_core + disc_people + \
disc_other + cont_fb + \
cont_finance + cont_num_reviews + \
cont_other
>>> set(movie.columns) == set(new_col_order)
True
>>> movie2 = movie[new_col_order]
>>> movie2.head()
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-gomYSZf5-1681366519511)(https://gitcode.net/apachecn/apachecn-ds-zh/-/raw/master/docs/master-pandas/img/00024.jpeg)]
要从数据帧中选择列的子集,请使用特定列名称的列表。 例如,movie[['movie_title', 'director_name']]
仅使用movie_title
和director_name
列创建一个新的数据帧。 通过名称选择列是 Pandas 数据帧的索引运算符的默认行为。
步骤 3 根据类型(离散或连续)以及它们的数据相似程度,将所有列名称整齐地组织到单独的列表中。 最重要的列(例如电影的标题)位于第一位。
步骤 4 连接所有列名称列表,并验证此新列表是否包含与原始列名称相同的值。 Python 集是无序的,并且相等语句检查一个集的每个成员是否是另一个集的成员。 手动排序此秘籍中的列容易受到人为错误的影响,因为很容易错误地忘记新列列表中的列。
步骤 5 通过将新的列顺序作为列表传递给索引运算符来完成重新排序。 现在,这个新顺序比原来的要明智得多。
除了前面提到的简单建议外,还有其他排序列的准则。 Hadley Wickham 在有关整洁数据的开创性论文中建议将固定变量放在第一位,然后再放置测量变量。 由于此数据并非来自受控实验,因此可以灵活地确定哪些变量是固定的,哪些是测量的。 测量变量的良好候选者是我们希望预测的变量,例如gross
,总收入或imdb_score
。 例如,以这种顺序,我们可以混合离散变量和连续变量。 在该演员的名字之后直接放置 Facebook 点赞人数的列可能更有意义。 当然,由于计算部分不受列顺序的影响,因此您可以提出自己的列顺序准则。
通常,您将直接从关系数据库中提取数据。 关系数据库的一种非常常见的做法是将主键(如果存在)作为第一列,并在其后直接放置任何外键。
主键唯一地标识当前表中的行。 外键唯一地标识其他表中的行。
在第 1 章,“Pandas 基础”的“调用序列方法”秘籍中,对单列或序列数据进行操作的各种方法。 当从数据帧调用这些相同的方法时,它们会立即对每一列执行该操作。
在本秘籍中,我们将对电影数据集探索各种最常见的数据帧属性和方法。
shape
,size
和ndim
,以及运行len
函数:>>> movie = pd.read_csv('data/movie.csv')
>>> movie.shape
(4916, 28)
>>> movie.size
137648
>>> movie.ndim
2
>>> len(movie)
4916
count
方法查找每列的不丢失值的数量。 输出是一个序列,现在其旧列名称为,其索引为:>>> movie.count()
color 4897
director_name 4814
num_critic_for_reviews 4867
duration 4901
...
actor_2_facebook_likes 4903
imdb_score 4916
aspect_ratio 4590
movie_facebook_likes 4916
Length: 28, dtype: int64
min
,max
,mean
,median
和std
都返回相似的序列,其索引中的列名称及其计算结果为值:>>> movie.min()
num_critic_for_reviews 1.00
duration 7.00
director_facebook_likes 0.00
actor_3_facebook_likes 0.00
...
actor_2_facebook_likes 0.00
imdb_score 1.60
aspect_ratio 1.18
movie_facebook_likes 0.00
Length: 16, dtype: float64
describe
方法非常强大,可以一次计算前面步骤中的所有描述性统计数据和四分位数。 最终结果是一个数据帧,其描述性统计信息为,其索引为:>>> movie.describe()
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-mLNuJtGK-1681366519512)(https://gitcode.net/apachecn/apachecn-ds-zh/-/raw/master/docs/master-pandas/img/00025.jpeg)]
percentiles
参数在describe
方法中指定精确的分位数:>>> movie.describe(percentiles=[.01, .3, .99])
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8xB2tedK-1681366519512)(https://gitcode.net/apachecn/apachecn-ds-zh/-/raw/master/docs/master-pandas/img/00026.jpeg)]
步骤 1 提供有关数据集大小的基本信息。shape
属性返回行和列数的两个元素的元组。size
属性返回数据帧中元素的总数,它只是行和列数的乘积。ndim
属性返回维数,对于所有数据帧,维数均为 2。 Pandas 定义了内置的len
函数以返回行数。
步骤 2 和步骤 3 中的方法将每一列汇总为一个数字。 现在,每个列名称都是序列中的索引标签,其汇总结果为相应的值。
如果仔细观察,您会发现步骤 3 的输出缺少步骤 2 的所有对象列。其原因是对象列中缺少值,而 pandas 不知道如何处理字符串值与缺失值。 它会静默删除无法为其计算最小值的所有列。
在这种情况下,静默意味着没有引发任何错误并且没有发出警告。 这有点危险,需要用户熟悉 Pandas。
数字列也缺少值,但返回了结果。 默认情况下,pandas 通过跳过数值列来处理缺失值。 通过将skipna
参数设置为False
可以更改此行为。 如果存在至少一个缺失值,这将导致所有这些聚合方法的 Pandas 返回NaN
。
describe
方法可一次显示所有主要摘要,并且可以通过将 0 到 1 之间的数字列表传递给percentiles
参数来扩展其摘要以包含更多分位数。 默认情况下,仅在数字列上显示信息。 有关describe
方法的更多信息,请参见“开发数据分析例程”秘籍。
要查看skipna
参数如何影响结果,我们可以将其值设置为False
,然后从前面的秘籍重新运行步骤 3。 只有没有缺失值的数字列将计算结果:
>>> movie.min(skipna=False)
num_critic_for_reviews NaN
duration NaN
director_facebook_likes NaN
actor_3_facebook_likes NaN
...
actor_2_facebook_likes NaN
imdb_score 1.6
aspect_ratio NaN
movie_facebook_likes 0.0
Length: 16, dtype: float64
无论您相信方法链接是否是一种好的做法,在使用 Pandas 进行数据分析时都会遇到它是很普遍的。 第 1 章,“Pandas 基础”中的“将序列方法链接在一起”秘籍展示了链接序列方法一起的几个示例。 本章中的所有方法链都将从数据帧开始。 方法链接的关键之一是知道在链接的每个步骤中返回的确切对象。 在 Pandas 中,这几乎总是一个数据帧,序列或标量值。
在此秘籍中,我们计算移动数据集每一列中的所有缺失值。
isnull
方法以将每个数据帧值更改为布尔值。 让我们在电影数据集上调用此方法:>>> movie = pd.read_csv('data/movie.csv')
>>> movie.isnull().head()
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-hBWGv5LO-1681366519512)(https://gitcode.net/apachecn/apachecn-ds-zh/-/raw/master/docs/master-pandas/img/00027.jpeg)]
True
/False
布尔值解释为 1/0 的sum
方法。 请注意,返回了一个序列:>>> movie.isnull().sum().head()
color 19
director_name 102
num_critic_for_reviews 49
duration 15
director_facebook_likes 102
dtype: int64
>>> movie.isnull().sum().sum()
2654
any
方法来执行此操作:>>> movie.isnull().any().any()
True
isnull
方法返回一个与调用数据帧相同大小的数据帧,但所有值都转换为布尔值。 请参阅以下数据类型的计数以验证这一点:
>>> movie.isnull().get_dtype_counts()
bool 28
dtype: int64
由于布尔值的数值求值为 0/1,因此可以按列对它们进行求和,如步骤 2 所示。所得的序列本身也具有sum
方法,该方法可以使我们在数据帧中获得总计的缺失值。
在步骤 4 中,数据帧的any
方法返回布尔值序列,指示每个列是否存在至少一个True
。any
方法再次链接到该布尔结果序列上,以确定是否有任何列缺少值。 如果步骤 4 求值为True
,则整个数据帧中至少存在一个缺失值。
电影数据集中具有对象数据类型的大多数列都包含缺少的值。 默认情况下,聚合方法min
,max
和sum
不返回任何内容,如以下代码片段所示,该代码片段选择三个对象列并尝试查找每个对象的最大值:
>>> movie[['color', 'movie_title', 'color']].max()
Series([], dtype: float64)
为了迫使 Pandas 为每一列返回值,我们必须填写缺失值。 在这里,我们选择一个空字符串:
>>> movie.select_dtypes(['object']).fillna('').min()
color Color
director_name Etienne Faure
actor_2_name Zubaida Sahar
genres Western
actor_1_name Oscar Jaenada
movie_title Æon Flux
actor_3_name Oscar Jaenada
plot_keywords zombie|zombie spoof
movie_imdb_link http://www.imdb.com/title/tt5574490/?ref_=fn_t...
language Zulu
country West Germany
content_rating X
dtype: object
出于可读性考虑,方法链通常被编写为每行一个方法调用,并在末尾使用反斜杠字符以避开新行。 这样可以更轻松地阅读和插入有关链的每个步骤返回的内容的注释:
>>> # rewrite the above chain on multiple lines
>>> movie.select_dtypes(['object']) \
.fillna('') \
.min()
由于未统一定义最小值和最大值,因此汇总所有字符串的列是无类型。 尝试调用明显没有字符串解释的方法,例如查找均值或方差,将无法正常工作。
它与第 1 章,“Pandas 基础”的秘籍有关,其中提供了关于运算符的入门知识。 这里。 Python 算术和比较运算符直接在数据帧上工作,就像在序列上一样。
当数据帧直接使用算术运算符或比较运算符之一进行运算时,每列的每个值都会对其应用运算。 通常,当运算符与数据帧一起使用时,列要么全为数字,要么为所有对象(通常是字符串)。 如果数据帧不包含同类数据,则该操作很可能会失败。 让我们来看一个关于大学数据集失败的示例,该数据集同时包含数字和对象数据类型。 尝试将5
添加到数据帧的每个值都会引发TypeError
,因为不能将整数添加到字符串中:
>>> college = pd.read_csv('data/college.csv')
>>> college + 5
TypeError: Could not operate 5 with block values must be str, not int
若要成功将运算符与数据帧配合使用,请首先选择同类数据。 对于此秘籍,我们将选择以UGDS_
开头的所有列。 这些栏代表按种族划分的大学生比例。 首先,我们导入数据并使用机构名称作为索引的标签,然后使用filter
方法选择所需的列:
>>> college = pd.read_csv('data/college.csv', index_col='INSTNM')
>>> college_ugds_ = college.filter(like='UGDS_')
>>> college_ugds_.head()
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-bP326KrT-1681366519513)(https://gitcode.net/apachecn/apachecn-ds-zh/-/raw/master/docs/master-pandas/img/00028.jpeg)]
此秘籍使用多个运算符和一个数据帧将本科生的列四舍五入到最接近的百分之一。 然后,我们将看到此结果如何等效于round
方法。
.00501
添加到college_ugds_
的每个值:>>> college_ugds_ + .00501
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Fm486VmR-1681366519513)(https://gitcode.net/apachecn/apachecn-ds-zh/-/raw/master/docs/master-pandas/img/00029.jpeg)]
//
舍入到最接近的整数百分比:>>> (college_ugds_ + .00501) // .01
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Y7CD3hIa-1681366519513)(https://gitcode.net/apachecn/apachecn-ds-zh/-/raw/master/docs/master-pandas/img/00030.jpeg)]
>>> college_ugds_op_round = (college_ugds_ + .00501) // .01 / 100
>>> college_ugds_op_round.head()
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-XrUaBSDi-1681366519513)(https://gitcode.net/apachecn/apachecn-ds-zh/-/raw/master/docs/master-pandas/img/00031.jpeg)]
round
方法为我们自动进行舍入。 NumPy 四舍五入正好在两边到偶数边中间的数字。 因此,我们在舍入前添加一小部分:>>> college_ugds_round = (college_ugds_ + .00001).round(2)
equals
方法测试两个数据帧的相等性:>>> college_ugds_op_round.equals(college_ugds_round)
True
步骤 1 使用加法运算符,该运算符尝试将标量值添加到数据帧的每一列的每个值。 由于列都是数字,因此此操作按预期进行。 每列中都有一些缺失值,但在操作后它们仍然缺失。
从数学上讲,添加.005
应该足够,以便下一步的底数分割正确舍入到最接近的整数百分比。 由于浮点数的不精确性而出现问题:
>>> .045 + .005
0.049999999999999996
每个数字都有一个额外的.00001
,以确保浮点表示的前四位数字与实际值相同。 之所以可行,是因为数据集中所有点的最大精度是四个小数位。
步骤 2 将楼层除法运算符//
应用于数据帧中的所有值。 实际上,当我们除以小数时,它是将每个值乘以100
并截断任何小数。 在表达式的第一部分周围需要括号,因为底数划分的优先级高于加法。 步骤 3 使用除法运算符将小数返回正确的位置。
在步骤 4 中,我们使用round
方法重现了先前的步骤。 在执行此操作之前,由于与步骤 1 有所不同的原因,我们必须再次向每个数据帧值添加一个额外的.00001
。NumPy 和 Python 3 的舍入数字恰好位于两边到偶数之间。 这种与偶数技术的联系通常不是学校正式教的。 它不会始终将数字偏向更高端。
这里有必要四舍五入,以使两个数据帧值相等。equals
方法确定两个数据帧之间的所有元素和索引是否完全相同,并返回一个布尔值。
与序列一样,数据帧具有与运算符等效的方法。 您可以将运算符替换为其等效的方法:
>>> college_ugds_op_round_methods = college_ugds_.add(.00501) \
.floordiv(.01) \
.div(100)
>>> college_ugds_op_round_methods.equals(college_ugds_op_round)
True
Pandas 使用 NumPy NaN(np.nan
)对象表示缺失值。 这是不寻常的对象,因为它不等于其自身。 与自身相比,甚至 Python 的None
对象也将其求值为True
:
>>> np.nan == np.nan
False
>>> None == None
True
与np.nan
的所有其他比较也返回False
,除了不等于:
>>> np.nan > 5
False
>>> 5 > np.nan
False
>>> np.nan != 5
True
序列和数据帧使用等号运算符==
进行逐元素比较,以返回相同大小的对象。 此秘籍向您展示如何使用相等运算符,该运算符与equals
方法非常不同。
与前面的秘籍一样,将使用代表大学数据集中各种族学生的分数的列:
>>> college = pd.read_csv('data/college.csv', index_col='INSTNM')
>>> college_ugds_ = college.filter(like='UGDS_')
>>> college_ugds_ == .0019
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-7Dv06TYV-1681366519513)(https://gitcode.net/apachecn/apachecn-ds-zh/-/raw/master/docs/master-pandas/img/00032.jpeg)]
college_ugds_
与自身进行比较,如下所示:>>> college_self_compare = college_ugds_ == college_ugds_
>>> college_self_compare.head()
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-HvepfUMz-1681366519514)(https://gitcode.net/apachecn/apachecn-ds-zh/-/raw/master/docs/master-pandas/img/00033.jpeg)]
all
方法确定每列是否仅包含True
值会产生意外结果:>>> college_self_compare.all()
UGDS_WHITE False
UGDS_BLACK False
UGDS_HISP False
UGDS_ASIAN False
UGDS_AIAN False
UGDS_NHPI False
UGDS_2MOR False
UGDS_NRA False
UGDS_UNKN False
dtype: bool
>>> (college_ugds_ == np.nan).sum()
UGDS_WHITE 0
UGDS_BLACK 0
UGDS_HISP 0
UGDS_ASIAN 0
UGDS_AIAN 0
UGDS_NHPI 0
UGDS_2MOR 0
UGDS_NRA 0
UGDS_UNKN 0
dtype: int64
isnull
方法:>>> college_ugds_.isnull().sum()
UGDS_WHITE 661
UGDS_BLACK 661
UGDS_HISP 661
UGDS_ASIAN 661
UGDS_AIAN 661
UGDS_NHPI 661
UGDS_2MOR 661
UGDS_NRA 661
UGDS_UNKN 661
dtype: int64
equals
方法:>>> college_ugds_.equals(college_ugds_)
True
步骤 1 将一个数据帧与一个标量值进行比较,而步骤 2 将一个数据帧与另一个数据帧进行比较。 乍看之下,这两种操作都非常简单直观。 第二个操作实际上是检查数据帧是否具有相同标签的索引,以及是否具有相同数量的元素。 如果不是这种情况,操作将失败。 有关更多信息,请参见第 6 章,“索引对齐”中的“生成笛卡尔积”秘籍。
步骤 3 验证数据帧中的列均不相等。 步骤 4 进一步显示了np.nan
与它本身的不等价性。 步骤 5 验证数据帧中确实存在缺失值。 最后,第 6 步显示了将数据帧与equals
方法进行比较的正确方法,该方法始终返回布尔型标量值。
所有比较运算符都有对应的方法,可以使用更多功能。 有点令人困惑的是,数据帧的eq
方法像相等运算符一样进行逐元素比较。eq
方法与equals
方法完全不同。 它仅执行与相等运算符相似的任务。 以下代码重复了步骤 1:
>>> college_ugds_.eq(.0019) # same as college_ugds_ == .0019
在pandas.testing
子包中,存在开发人员在创建单元测试时必须使用的函数。 如果两个数据帧不相等,则assert_frame_equal
函数将引发AssertionError
。 如果传递的两个帧相等,则返回None
:
>>> from pandas.testing import assert_frame_equal
>>> assert_frame_equal(college_ugds_, college_ugds_)
单元测试是软件开发中非常重要的部分,并确保代码正确运行。 Pandas 包含成千上万的单元测试,可帮助确保其正常运行。 要了解有关 Pandas 如何运行其单元测试的更多信息,请参阅文档中的“对 Pandas 做贡献”部分。
许多数据帧方法都有一个axis
参数。 这个重要的参数控制操作的方向。 轴参数只能是两个值之一(0 或 1),并且分别作为字符串index
和column
的别名。
几乎所有的数据帧方法都将axis
参数默认为0
/index
。 此秘籍向您展示了如何调用相同的方法,但其操作方向已被调换。 为了简化练习,将仅使用引用大学数据集中每个学校的百分比种族的列。
UGDS_
开头的列代表特定种族的本科生所占的百分比。 使用filter
方法选择以下列:>>> college = pd.read_csv('data/college.csv', index_col='INSTNM')
>>> college_ugds_ = college.filter(like='UGDS_')
>>> college_ugds_.head()
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-k7vbHcz5-1681366519514)(https://gitcode.net/apachecn/apachecn-ds-zh/-/raw/master/docs/master-pandas/img/00034.jpeg)]
count
方法返回非缺失值的数量。 默认情况下,其axis
参数设置为 0:>>> college_ugds_.count()
UGDS_WHITE 6874
UGDS_BLACK 6874
UGDS_HISP 6874
UGDS_ASIAN 6874
UGDS_AIAN 6874
UGDS_NHPI 6874
UGDS_2MOR 6874
UGDS_NRA 6874
UGDS_UNKN 6874
由于axis
参数几乎总是设置为 0,因此无需执行以下操作,但出于理解的目的,第 2 步等效于college_ugds_.count(axis=0)
和college_ugds_.count(axis='index')
。
axis
参数更改为 1 /列,将对操作进行转置,以使每行数据都有其非缺失值的计数:>>> college_ugds_.count(axis='columns').head()
INSTNM
Alabama A & M University 9
University of Alabama at Birmingham 9
Amridge University 9
University of Alabama in Huntsville 9
Alabama State University 9
sum
方法可用于验证这一点:>>> college_ugds_.sum(axis='columns').head()
INSTNM
Alabama A & M University 1.0000
University of Alabama at Birmingham 0.9999
Amridge University 1.0000
University of Alabama in Huntsville 1.0000
Alabama State University 1.0000
median
方法:>>> college_ugds_.median(axis='index')
UGDS_WHITE 0.55570
UGDS_BLACK 0.10005
UGDS_HISP 0.07140
UGDS_ASIAN 0.01290
UGDS_AIAN 0.00260
UGDS_NHPI 0.00000
UGDS_2MOR 0.01750
UGDS_NRA 0.00000
UGDS_UNKN 0.01430
操作的方向是 Pandas 中比较混乱的方面之一,互联网上到处都有讨论它的解释的线程。 许多新手 Pandas 用户很难记住axis
参数的含义。 幸运的是,在 Pandas 中,一项操作可以完成两个潜在的方向。 一种可能的方法是尝试双向尝试直到获得所需结果的简单蛮力解决方案。 我记得axis
参数的含义,认为 1 看起来像一列,对axis=1
的任何操作都会返回一个新的数据列(与该列具有相同数量的项)。
这在第 3 步中得到确认,在第 3 步中,结果(没有head
方法)将返回新的数据列,并且可以根据需要轻松地将其作为列附加到数据帧中。axis
等于1
/index
的其他步骤将返回新的数据行。
使用axis=1
的cumsum
方法累积了每一行的种族百分比。 它给出的数据视图略有不同。 例如,很容易看到每所学校的白人,黑人和西班牙裔美国人的确切百分比:
>> college_ugds_cumsum = college_ugds_.cumsum(axis=1)
>>> college_ugds_cumsum.head()
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-dYvaKDlM-1681366519514)(https://gitcode.net/apachecn/apachecn-ds-zh/-/raw/master/docs/master-pandas/img/00035.jpeg)]
每年都会写很多文章,讨论多样性对大学校园的不同方面和影响。 各种组织已经开发出度量标准,以尝试测量多样性。 《美国新闻》是为许多不同类别的大学提供排名的领导者,其中之一就是多样性。 他们的多样性指数排名前十的学院如下:
>> pd.read_csv('data/college_diversity.csv', index_col='School')
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-liH8b9ud-1681366519514)(https://gitcode.net/apachecn/apachecn-ds-zh/-/raw/master/docs/master-pandas/img/00036.jpeg)]
我们的大学数据集将种族分为九个不同类别。 当尝试量化没有明显定义的事物时,例如多样性,它有助于从非常简单的事物开始。 在此秘籍中,我们的多样性指标将等于学生人数超过 15% 的种族数。
>>> college = pd.read_csv('data/college.csv', index_col='INSTNM')
>>> college_ugds_ = college.filter(like='UGDS_')
>>> college_ugds_.isnull()\
.sum(axis=1)\
.sort_values(ascending=False)\
.head()
INSTNM
Excel Learning Center-San Antonio South 9
Philadelphia College of Osteopathic Medicine 9
Assemblies of God Theological Seminary 9
Episcopal Divinity School 9
Phillips Graduate Institute 9
dtype: int64
dropna
方法删除所有缺少 9 个种族百分比的所有行。 然后,我们可以计算剩余的缺失值:>>> college_ugds_ = college_ugds_.dropna(how='all')
>>> college_ugds_.isnull().sum()
UGDS_WHITE 0
UGDS_BLACK 0
UGDS_HISP 0
UGDS_ASIAN 0
UGDS_AIAN 0
UGDS_NHPI 0
UGDS_2MOR 0
UGDS_NRA 0
UGDS_UNKN 0
dtype: int64
ge
将每个值转换为布尔值:>>> college_ugds_.ge(.15)
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-DukiwhkC-1681366519515)(https://gitcode.net/apachecn/apachecn-ds-zh/-/raw/master/docs/master-pandas/img/00037.jpeg)]
sum
方法对每个学院的True
值进行计数。 请注意,返回了一个序列:>>> diversity_metric = college_ugds_.ge(.15).sum(axis='columns')
>>> diversity_metric.head()
INSTNM
Alabama A & M University 1
University of Alabama at Birmingham 2
Amridge University 3
University of Alabama in Huntsville 1
Alabama State University 1
dtype: int64
value_counts
方法:>>> diversity_metric.value_counts()
1 3042
2 2884
3 876
4 63
0 7
5 2
dtype: int64
diversity_metric
序列进行排序,以找出它们是哪些:>>> diversity_metric.sort_values(ascending=False).head()
INSTNM
Regency Beauty Institute-Austin 5
Central Texas Beauty College-Temple 5
Sullivan and Cogliano Training Center 4
Ambria College of Nursing 4
Berkeley College-New York 4
dtype: int64
.loc
索引器用于根据索引标签专门选择:>>> college_ugds_.loc[['Regency Beauty Institute-Austin',
'Central Texas Beauty College-Temple']]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-dv9SPMHG-1681366519515)(https://gitcode.net/apachecn/apachecn-ds-zh/-/raw/master/docs/master-pandas/img/00038.jpeg)]
>>> us_news_top = ['Rutgers University-Newark',
'Andrews University',
'Stanford University',
'University of Houston',
'University of Nevada-Las Vegas']
>>> diversity_metric.loc[us_news_top]
INSTNM
Rutgers University-Newark 4
Andrews University 3
Stanford University 3
University of Houston 3
University of Nevada-Las Vegas 3
dtype: int64
第 2 步进行计数,然后显示缺失值最多的学校。 由于数据帧中有九列,因此每所学校的缺失值最大数目为九。 许多学校缺少每一列的值。 步骤 3 删除所有值均缺失的行。 步骤 3 中的dropna
方法具有how
参数,该参数默认为字符串any
,但也可以更改为all
。 设置为any
时,它将删除包含一个或多个缺失值的行。 设置为all
时,它仅删除缺少所有值的行。
在这种情况下,我们保守地删除丢失所有值的行。 这是因为某些缺失值可能仅代表 0% 。 这不是碰巧的情况,因为执行dropna
之后没有丢失值。 如果仍然缺少值,我们可以运行fillna(0)
方法用 0 填充所有剩余值。
步骤 4 使用大于或等于方法ge
开始我们的多样性指标计算。 这将导致所有布尔值的数据帧,通过设置axis='columns'
将其水平求和。
在第 5 步中使用value_counts
方法来生成我们的多样性指标的分布。 对于学校而言,很少有三个种族的大学生人数占总人数的 15% 或更多。 第 7 步和第 8 步根据我们的指标找到最多样化的两所学校。 尽管它们是多种多样的,但似乎很多种族并没有得到充分考虑,并且被默认为未知类别和两个或多个类别。
步骤 9 从“美国新闻”文章中选择排名前五的学校。 然后,从我们新创建的序列中选择其多样性指标。 事实证明,这些学校在我们的简单排名系统中也得分很高。
另外,我们可以通过按最大种族百分比对它们进行排序来找到差异最小的学校:
>>> college_ugds_.max(axis=1).sort_values(ascending=False).head(10)
INSTNM
Dewey University-Manati 1.0
Yeshiva and Kollel Harbotzas Torah 1.0
Mr Leon's School of Hair Design-Lewiston 1.0
Dewey University-Bayamon 1.0
Shepherds Theological Seminary 1.0
Yeshiva Gedolah Kesser Torah 1.0
Monteclaro Escuela de Hoteleria y Artes Culinarias 1.0
Yeshiva Shaar Hatorah 1.0
Bais Medrash Elyon 1.0
Yeshiva of Nitra Rabbinical College 1.0
dtype: float64
我们还可以确定是否有任何一所学校的所有 9 个种族类别都超过 1% :
>>> (college_ugds_ > .01).all(axis=1).any()
True
在本章中,我们将介绍以下主题:
sort_values
替代nlargest
重要的是,要考虑作为分析人员在将数据集作为数据帧导入工作区后首次遇到数据集时应采取的步骤。 您通常会首先执行一组任务来检查数据吗? 您是否了解所有可能的数据类型? 本章首先介绍您第一次遇到新的数据集时可能要执行的任务。 本章通过回答在 Pandas 中不常见的常见问题继续进行。
尽管开始数据分析时没有标准方法,但是通常最好在首次检查数据集时为自己开发一个例程。 类似于我们用于起床,洗澡,上班,吃饭等的常规例程,开始的数据分析例程可帮助人们快速熟悉新的数据集。 该例程可以表现为动态任务清单,随着您对 Pandas 的熟悉和数据分析的扩展而不断发展。
探索性数据分析(EDA)是一个术语,用于涵盖数据分析的整个过程,而无需正式使用统计测试程序。 EDA 的许多工作都涉及可视地显示数据之间的不同关系,以检测有趣的模式并提出假设。
本秘籍涵盖了 EDA 的一小部分但又是基础部分:以常规方式和系统方式收集元数据和单变量描述性统计信息。 它概述了在首次将任何数据集作为 pandas 数据帧导入时可以执行的一组常见任务。 此秘籍可能有助于形成您在首次检查数据集时可以实现的例程的基础。
元数据描述数据集,或更恰当地描述关于数据的数据。 元数据的示例包括列/行数,列名称,每列的数据类型,数据集的来源,收集日期,不同列的可接受值,等等。 单变量描述性统计信息是有关数据集的各个变量(列)的摘要统计信息,独立于所有其他变量。
首先,将收集college
数据集上的一些元数据,然后是每列的基本摘要统计信息:
head
方法查看前五行:>>> college = pd.read_college('data/college.csv')
>>> college.head()
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-DTBv50Ju-1681366519515)(https://gitcode.net/apachecn/apachecn-ds-zh/-/raw/master/docs/master-pandas/img/00039.jpeg)]
shape
属性获取数据帧的尺寸:>>> college.shape
>>> (7535, 27)
info
方法列出每一列的数据类型,非缺失值的数量以及内存使用情况:>>> college.info()
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-7GY2WETe-1681366519516)(https://gitcode.net/apachecn/apachecn-ds-zh/-/raw/master/docs/master-pandas/img/00040.jpeg)]
>>> college.describe(include=[np.number]).T
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-fMtarZj0-1681366519516)(https://gitcode.net/apachecn/apachecn-ds-zh/-/raw/master/docs/master-pandas/img/00041.jpeg)]
>>> college.describe(include=[np.object, pd.Categorical]).T
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-x12Yq41I-1681366519516)(https://gitcode.net/apachecn/apachecn-ds-zh/-/raw/master/docs/master-pandas/img/00042.jpeg)]
导入数据集后,常见的任务是打印出数据帧的前几行,以使用head
方法进行手动检查。shape
属性返回第一条元数据,即包含行数和列数的元组。
一次获取最多元数据的主要方法是info
方法。 它提供每个列的名称,非缺失值的数量,每个列的数据类型以及数据帧的近似内存使用情况。 对于所有数据帧,列值始终是一种数据类型。 关系数据库也是如此。 总体而言,数据帧可能由具有不同数据类型的列组成。
在内部,Pandas 将相同数据类型的列一起存储在块中。 要深入了解 Pandas 的内部,请参阅 Jeff Tratner 的幻灯片。
步骤 4 和步骤 5 在不同类型的列上生成单变量描述性统计信息。 强大的describe
方法根据提供给include
参数的数据类型产生不同的输出。 默认情况下,describe
输出所有数字(主要是连续)列的摘要,并静默删除任何类别列。 您可以使用np.number
或字符串number
在摘要中包含整数和浮点数。 从技术上讲,数据类型是层次结构的一部分,其中数字位于整数和浮点上方。 查看下图,以更好地了解 NumPy 数据类型层次结构:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-TM9eNHdK-1681366519517)(https://gitcode.net/apachecn/apachecn-ds-zh/-/raw/master/docs/master-pandas/img/00043.jpeg)]
一般来说,我们可以将数据分类为连续数据或分类数据。 连续数据始终是数字,通常可以具有无限多种可能性,例如身高,体重和薪水。 分类数据代表离散值,这些离散值具有有限的可能性,例如种族,就业状况和汽车颜色。 分类数据可以用数字或字符表示。
分类列通常将是np.object
或pd.Categorical
类型。 步骤 5 确保同时代表这两种类型。 在第 4 步和第 5 步中,输出数据帧均带有T
属性。 这简化了具有许多列的数据帧的可读性。
当与数字列一起使用时,可以指定从describe
方法返回的确切分位数:
>>> college.describe(include=[np.number],
percentiles=[.01, .05, .10, .25, .5,
.75, .9, .95, .99]).T
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-s6oPPWc2-1681366519517)(https://gitcode.net/apachecn/apachecn-ds-zh/-/raw/master/docs/master-pandas/img/00044.jpeg)]
数据分析的关键部分涉及创建和维护数据字典。 数据字典是元数据表和每列数据上的注释。 数据字典的主要目的之一是解释列名的含义。 高校数据集使用许多缩写,这对于首次检查它的分析师而言可能是陌生的。
以下college_data_dictionary.csv
文件中提供了大学数据集的数据字典:
>>> pd.read_csv('data/collge_data_dictionaray.csv')
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-d3IuKQgg-1681366519517)(https://gitcode.net/apachecn/apachecn-ds-zh/-/raw/master/docs/master-pandas/img/00045.jpeg)]
如您所见,它在解密缩写列名称方面非常有用。 实际上,数据帧不是存储数据字典的最佳位置。 诸如 Excel 或 Google 表格之类的平台具有易于编辑值和附加列的能力,是更好的选择。 至少,应在数据字典中包含一列以跟踪数据注释。 数据字典是您作为协作者的分析师可以共享的第一件事。
通常,您正在使用的数据集源自数据库,您必须联系该数据库的管理员才能获取更多信息。 正式的电子数据库通常具有更正式的数据表示形式,称为模式。 如果可能,请尝试与对设计有专业知识的人员一起调查您的数据集。
Pandas 并未将数据大致分为连续数据或分类数据,但对许多不同的数据类型都有精确的技术定义。
此秘籍将大学数据集中的对象列之一的数据类型更改为特殊的 Pandas 分类数据类型,以大大减少其内存使用量。
>>> college = pd.read_csv('data/college.csv')
>>> different_cols = ['RELAFFIL', 'SATMTMID', 'CURROPER',
'INSTNM', 'STABBR']
>>> col2 = college.loc[:, different_cols]
>>> col2.head()
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-I73NMWyK-1681366519517)(https://gitcode.net/apachecn/apachecn-ds-zh/-/raw/master/docs/master-pandas/img/00046.jpeg)]
>>> col2.dtypes
RELAFFIL int64
SATMTMID float64
CURROPER int64
INSTNM object
STABBR object
dtype: object
memory_usage
方法查找每一列的内存使用情况:>>> original_mem = col2.memory_usage(deep=True)
>>> original_mem
Index 80
RELAFFIL 60280
SATMTMID 60280
CURROPER 60280
INSTNM 660240
STABBR 444565
dtype: int64
RELAFFIL
列不需要使用 64 位,因为它仅包含 0/1 值。 让我们使用astype
方法将此列转换为 8 位(1 字节)整数:>>> col2['RELAFFIL'] = col2['RELAFFIL'].astype(np.int8)
dtypes
属性来确认数据类型更改:>>> col2.dtypes
RELAFFIL int8
SATMTMID float64
CURROPER int64
INSTNM object
STABBR object
dtype: object
>>> college[different_cols].memory_usage(deep=True)
Index 80
RELAFFIL 7535
SATMTMID 60280
CURROPER 60280
INSTNM 660240
STABBR 444565
>>> col2.select_dtypes(include=['object']).nunique()
INSTNM 7535
STABBR 59
dtype: int64
STABBR
列是转换为分类的很好的候选者,因为其值的唯一值少于百分之一:>>> col2['STABBR'] = col2['STABBR'].astype('category')
>>> col2.dtypes
RELAFFIL int8
SATMTMID float64
CURROPER int64
INSTNM object
STABBR category
dtype: object
>>> new_mem = col2.memory_usage(deep=True)
>>> new_mem
Index 80
RELAFFIL 7535
SATMTMID 60280
CURROPER 60280
INSTNM 660699
STABBR 13576
dtype: int64
RELAFFIL
列是其原始大小的八分之一,而STABBR
列已缩小到其原始大小的百分之三:>>> new_mem / original_mem
Index 1.000000
RELAFFIL 0.125000
SATMTMID 1.000000
CURROPER 1.000000
INSTNM 1.000695
STABBR 0.030538
dtype: float64
Pandas 将integer
和float
数据类型默认为 64 位,而不管特定数据帧的最大必要大小如何。 可以使用astype
方法将整数,浮点数甚至是布尔值强制转换为其他数据类型,并将其作为字符串或特定对象的确切类型传递给它,如步骤 4 所示。
RELAFFIL
列是转换为较小整数类型的好选择,因为数据字典说明其值必须为 0/1。 现在RELAFFIL
的内存是CURROPER
的八分之一,仍然是以前的类型。
显示的存储单位是字节而不是位。 1 个字节等于 8 位,因此当将RELAFFIL
更改为 8 位整数时,它将使用 1 个 1 字节的内存,并且由于有 7,535 行,因此其内存占用量相当于 7,535 个字节。
对象数据类型的列(例如INSTNM
)与其他 pandas 数据类型不同。 对于所有其他 Pandas 数据类型,该列中的每个值都是相同的数据类型。 例如,当列具有int64
类型时,每个单独的列值也都是int64
。 对于对象数据类型的列,情况并非如此。 每个单独的列值可以是任何类型。 对象数据类型可以混合使用字符串,数字,日期时间,甚至其他 Python 对象(例如列表或元组)。 因此,对于与任何其他数据类型都不匹配的数据列,有时将对象数据类型称为全部捕获。 但是,绝大多数时候,对象数据类型列都是字符串。
关系数据库管理系统(例如微软的 SQL Server 或 PostgreSQL)具有用于字符的特定数据类型,例如varchar
,text
或nchar
,它们通常也指定最大字符数。 Pandas 对象数据类型是更广泛的数据类型。 对象列中的每个值可以是任何数据类型。
因此,对象数据类型列中每个单独值的存储都不一致。 像其他数据类型一样,每个值都没有预定义的内存量。 为了使 Pandas 提取对象数据类型列的确切内存量,必须在memory_usage
方法中将deep
参数设置为True
。
对象列是最大节省内存的目标。 Pandas 还有 NumPy 中不提供的其他分类数据类型。 当转换为category
时,Pandas 内部会创建从整数到每个唯一字符串值的映射。 因此,每个字符串仅需要在内存中保留一次。 如您所见,这种简单的数据类型更改将内存使用量减少了 97% 。
您可能还已经注意到,索引使用的内存量极低。 如果在创建数据帧的过程中未指定索引(如本秘籍所述),pandas 会将索引默认为RangeIndex
。RangeIndex
与内置范围函数非常相似。 它按需产生值,并且仅存储创建索引所需的最少信息量。
为了更好地了解对象数据类型的列与整数和浮点数之间的区别,可以修改这些列中每个列的单个值,并显示结果的内存使用情况。CURROPER
和INSTNM
列分别为int64
和对象类型,:
>>> college.loc[0, 'CURROPER'] = 10000000
>>> college.loc[0, 'INSTNM'] = college.loc[0, 'INSTNM'] + 'a'
>>> college[['CURROPER', 'INSTNM']].memory_usage(deep=True)
Index 80
CURROPER 60280
INSTNM 660345
CURROPER
的内存使用量保持不变,因为 64 位整数足以容纳更大的数字。 另一方面,仅将一个字母添加到一个值中,INSTNM
的内存使用量增加了 105 个字节。
Python 3 使用 Unicode,这是一种标准的字符表示形式,旨在对世界上所有的书写系统进行编码。 Unicode 每个字符最多使用 4 个字节。 第一次对字符值进行修改时,Pandas 似乎有一些开销(100 字节)。 之后,每个字符增加 5 个字节。
并非所有列都可以强制转换为所需的类型。 看一下MENONLY
列,在数据字典中似乎只包含 0/1 值。 导入时该列的实际数据类型意外地为float64
。 这样做的原因是碰巧缺少值,用np.nan
表示。 没有整数表示丢失的值。 甚至只有一个缺失值的任何数字列都必须是浮点数。 此外,如果其中一个值丢失,则integer
数据类型的任何列将自动强制为浮点型:
>>> college['MENONLY'].dtype
dtype('float64')
>>> college['MENONLY'].astype(np.int8)
ValueError: Cannot convert non-finite values (NA or inf) to integer
此外,在引用数据类型时,可以用字符串名称代替 Python 对象。 例如,当在describe
数据帧方法中使用include
参数时,可以传递形式对象 NumPy / pandas 对象或其等效字符串表示形式的列表。 这些内容可在第 2 章,“基本数据帧操作”中的“用方法选择列”秘籍的开头的表格中找到。 例如,以下每个产生相同的结果:
>>> college.describe(include=['int64', 'float64']).T
>>> college.describe(include=[np.int64, np.float64]).T
>>> college.describe(include=['int', 'float']).T
>>> college.describe(include=['number']).T
在更改类型时,可以类似地使用以下字符串:
>>> college['MENONLY'] = college['MENONLY'].astype('float16')
>>> college['RELAFFIL'] = college['RELAFFIL'].astype('int8')
字符串与 Pandas 或 NumPy 纯对象的等价性出现在 Pandas 库中的其他位置,并且可能导致混乱,因为有访问同一事物的两种不同的方式。
最后,可以看到最小的RangeIndex
和Int64Index
之间存在巨大的存储差异,后者将每个行索引存储在内存中:
>>> college.index = pd.Int64Index(college.index)
>>> college.index.memory_usage() # previously was just 80
60280
此秘籍可用于创建吸引人的新闻头条,例如“在前 100 名最好的大学中,这 5 名学费最低”或“在前 50 个城市中,这 10 个是最便宜的”。 在分析期间,可能首先需要找到一个数据组,该数据组在单个列中包含最高的n
值,然后从该子集中找到最低的m
基于不同列的值。
在本秘籍中,我们利用便利的方法nlargest
和nsmallest
从前 100 名得分最高的电影中找到了前五部预算最低的电影。
movie_title
,imdb_score
和budget
列:>>> movie = pd.read_csv('data/movie.csv')
>>> movie2 = movie[['movie_title', 'imdb_score', 'budget']]
>>> movie2.head()
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-dfyuMeul-1681366519518)(https://gitcode.net/apachecn/apachecn-ds-zh/-/raw/master/docs/master-pandas/img/00047.jpeg)]
nlargest
方法通过imdb_score
选择前 100 个电影:>>> movie2.nlargest(100, 'imdb_score').head()
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-gPvrBZUD-1681366519518)(https://gitcode.net/apachecn/apachecn-ds-zh/-/raw/master/docs/master-pandas/img/00048.jpeg)]
nsmallest
方法可返回前 100 名得分最低的五部预算电影:>>> movie2.nlargest(100, 'imdb_score').nsmallest(5, 'budget')
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3xf2zwry-1681366519518)(https://gitcode.net/apachecn/apachecn-ds-zh/-/raw/master/docs/master-pandas/img/00049.jpeg)]
nlargest
方法的第一个参数n
必须为整数,并选择要返回的行数。 第二个参数columns
以列名作为字符串。 步骤 2 返回得分最高的 100 部电影。 我们可以将该中间结果另存为自己的变量,但是,我们在步骤 3 中将nsmallest
方法链接到该变量,该方法恰好返回五行,按budget
排序。
可以将列名列表传递给nlargest
/nsmallest
方法的columns
参数。 仅当在列表的第一列中存在重复的值共享第 n 个排名位的情况时,这才对打破关系有用。
在数据分析期间执行的最基本,最常见的操作之一是选择包含组中某个列的最大值的行。 例如,这就像在内容分级中查找每年评分最高的电影或票房最高的电影。 要完成此任务,我们需要对组以及用于对组中每个成员进行排名的列进行排序,然后提取每个组的最高成员。
在此秘籍中,我们将找到每年评分最高的电影。
movie_title
,title_year
和imdb_score
:>>> movie = pd.read_csv('data/movie.csv')
>>> movie2 = movie[['movie_title', 'title_year', 'imdb_score']]
sort_values
方法按title_year
对数据帧进行排序。 默认行为从最小到最大。 通过将ascending
参数设置为等于True
,可以反转此行为:>>> movie2.sort_values('title_year', ascending=False).head()
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-czFJPki7-1681366519518)(https://gitcode.net/apachecn/apachecn-ds-zh/-/raw/master/docs/master-pandas/img/00050.jpeg)]
>>> movie3 = movie2.sort_values(['title_year','imdb_score'],
ascending=False)
>>> movie3.head()
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-hl7oKHZL-1681366519519)(https://gitcode.net/apachecn/apachecn-ds-zh/-/raw/master/docs/master-pandas/img/00051.jpeg)]
drop_duplicates
方法仅保留每年的第一行:>>> movie_top_year = movie3.drop_duplicates(subset='title_year')
>>> movie_top_year.head()
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-M7RP0dpD-1681366519519)(https://gitcode.net/apachecn/apachecn-ds-zh/-/raw/master/docs/master-pandas/img/00052.jpeg)]
在第 1 步中,我们将数据集精简为仅关注重要的列。 此秘籍将与整个数据帧相同。 第 2 步显示了如何按单个列对数据帧进行排序,这并不是我们想要的。 步骤 3 同时对多个列进行排序。 它首先通过对所有title_year
排序,然后在title_year
的每个不同值内按imdb_score
排序来工作。
drop_duplicates
方法的默认行为是保留每个唯一行的第一次出现,因为每一行都是唯一的,所以不会删除任何行。 但是,subset
参数将其更改为仅考虑为其提供的列(或列列表)。 在此示例中,每年仅返回一行。 正如我们在最后一步中按年份和得分排序一样,我们获得的年度最高评分电影。
可以按升序对一列进行排序,而同时按降序对另一列进行排序。 为此,请将布尔值列表传递给ascending
参数,该参数与您希望对每一列进行排序的方式相对应。 以下title_year
和content_rating
降序排列,budget
升序排列。 然后,它查找每年和内容分级组中预算最低的电影:
>>> movie4 = movie[['movie_title', 'title_year',
'content_rating', 'budget']]
>>> movie4_sorted = movie4.sort_values(['title_year',
'content_rating', 'budget'],
ascending=[False, False, True])
>>> movie4_sorted.drop_duplicates(subset=['title_year',
'content_rating']).head(10)
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-cq9Ye2np-1681366519519)(https://gitcode.net/apachecn/apachecn-ds-zh/-/raw/master/docs/master-pandas/img/00053.jpeg)]
默认情况下,drop_duplicates
保持最开始的外观,但是可以通过在最后传递keep
参数来选择每个组的最后一行,或通过False
完全删除所有重复项来修改此行为。
sort_values
替代nlargest
前两个秘籍的工作原理类似,它们以略有不同的方式对值进行排序。 查找一列数据的顶部n
值等同于对整个列进行降序排序并获取第一个n
值。 Pandas 有许多可以通过多种方式做到这一点的行动。
在本秘籍中,我们将使用sort_values
方法复制“从最大值中选择最小值”秘籍,并探讨两者之间的区别。
>>> movie = pd.read_csv('data/movie.csv')
>>> movie2 = movie[['movie_title', 'imdb_score', 'budget']]
>>> movie_smallest_largest = movie2.nlargest(100, 'imdb_score') \
.nsmallest(5, 'budget')
>>> movie_smallest_largest
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8FF2UwDk-1681366519519)(https://gitcode.net/apachecn/apachecn-ds-zh/-/raw/master/docs/master-pandas/img/00054.jpeg)]
sort_values
复制表达式的第一部分,并使用head
方法获取第一100
行:>>> movie2.sort_values('imdb_score', ascending=False).head(100)
sort_values
与head
结合使用,以budget
来获得最低的五部电影:>>> movie2.sort_values('imdb_score', ascending=False).head(100) \
.sort_values('budget').head()
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-YqshhQuz-1681366519520)(https://gitcode.net/apachecn/apachecn-ds-zh/-/raw/master/docs/master-pandas/img/00055.jpeg)]
如步骤 2 所示,通过在操作后链接head
方法,sort_values
方法几乎可以复制nlargest
。步骤 3 通过链接另一个sort_values
可以复制nsmallest
,并且只需取前五个即可完成查询。head
方法显示行。
查看步骤 1 中第一个数据帧的输出,并将其与步骤 3 中的输出进行比较。它们是否相同? 没有! 发生了什么? 要了解为什么两个结果不相等,让我们看一下每个秘籍的中间步骤的尾部:
>>> movie2.nlargest(100, 'imdb_score').tail()
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-oqpvbiMj-1681366519520)(https://gitcode.net/apachecn/apachecn-ds-zh/-/raw/master/docs/master-pandas/img/00056.jpeg)]
>>> movie2.sort_values('imdb_score', ascending=False) \
.head(100).tail()
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6VJJqHEz-1681366519520)(https://gitcode.net/apachecn/apachecn-ds-zh/-/raw/master/docs/master-pandas/img/00057.jpeg)]
由于存在超过 100 部评分至少为8.4
的电影而引起问题。 每种方法nlargest
和sort_values
的联系均不同,导致 100 行数据帧略有不同。
如果查看nlargest
文档,则会看到keep
参数具有三个可能的值,first
,last
和False
。 据我对其他 Pandas 方法的了解,keep=False
应该允许所有纽带保留在结果中。 不幸的是,Pandas 在尝试执行此操作时会引发错误。 我在 GitHub 上给 Pandas 开发团队创建了一个问题,以进行此改进。
本质上,有无数种交易股票的策略。 许多投资者采用的一种基本交易类型是止损单。 止损单是投资者下达的买卖股票的命令,每当市场价格达到某个点时,该订单就会执行。 止损单对于防止巨大损失和保护收益都是有用的。
就本秘籍而言,我们将仅检查用于出售当前拥有股票的止损单。 在典型的止损订单中,价格在订单的整个生命周期内都不会改变。 例如,如果您以每股 100 美元的价格购买了股票,则可能希望将停止订单设置为每股 90 美元,以将下行空间限制为 10% 。
一种更高级的策略是,如果价值增加,则不断修改止损单的销售价格以跟踪股票的价值。 这称为追踪止损指令。 具体来说,如果相同的 100 美元股票增加到 120 美元,那么低于当前市场价格 10% 的追踪止损单将使销售价格上涨到 108 美元。
自购买之日起,追踪止损单永远不会向下移动,并始终与最大值挂钩。 如果股票价格从 120 美元跌至 110 美元,止损单仍将保持在 108 美元。 仅当价格升至 120 美元以上时,价格才会增加。
此秘籍需要使用第三方包pandas-datareader
来在线获取股市价格。 它没有预装在 Anaconda 发行版中。 要安装此包,只需访问命令行并运行conda install pandas-datareader
。 如果没有 Anaconda,可以通过运行pip install pandas-datareader
进行安装。 该秘籍确定给定任何股票的初始购买价格的追踪止损单价格。
>>> import pandas_datareader as pdr
>>> tsla = pdr.DataReader('tsla', data_source='google',
start='2017-1-1')
>>> tsla.head(8)
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-f0ivitV3-1681366519521)(https://gitcode.net/apachecn/apachecn-ds-zh/-/raw/master/docs/master-pandas/img/00058.jpeg)]
>>> tsla_close = tsla['Close']
cummax
方法跟踪直到当前日期的最高收盘价:>>> tsla_cummax = tsla_close.cummax()
>>> tsla_cummax.head(8)
Date
2017-01-03 216.99
2017-01-04 226.99
2017-01-05 226.99
2017-01-06 229.01
2017-01-09 231.28
2017-01-10 231.28
2017-01-11 231.28
2017-01-12 231.28
Name: Close, dtype: float64
tsla_cummax
乘以 0.9。 这将创建跟踪止损单:>>> tsla_trailing_stop = tsla_cummax * .9
>>> tsla_trailing_stop.head(8)
Date
2017-01-03 195.291
2017-01-04 204.291
2017-01-05 204.291
2017-01-06 206.109
2017-01-09 208.152
2017-01-10 208.152
2017-01-11 208.152
2017-01-12 208.152
Name: Close, dtype: float64
cummax
方法通过保留遇到的最大值直到并包括当前值来工作。 将该序列乘以 0.9 或您要使用的任何缓冲,将创建跟踪止损单。 在此特定示例中,TSLA 的值增加了,因此其尾随止损也增加了。
该秘籍仅介绍了如何使用有用的 Pandas 来交易证券,并且在计算止损单是否触发以及何时触发止损时停止了计算。 可以将此秘籍转换为接受代码,购买日期和止损百分比并返回尾随止损价格的函数:
>>> def set_trailing_loss(symbol, purchase_date, perc):
close = pdr.DataReader(symbol, 'google',
start=purchase_date)['Close']
return close.cummax() * perc
>>> msft_trailing_stop = set_trailing_loss('msft', '2017-6-1', .85)
>>> msft_trailing_stop.head()
Date
2017-06-01 59.585
2017-06-02 60.996
2017-06-05 61.438
2017-06-06 61.642
2017-06-07 61.642
Name: Close, dtype: float64
在减肥计划中可以使用非常相似的策略。 只要您偏离最小体重太远,都可以设置警告。 Pandas 为您提供了cummin
方法来跟踪最小值。 如果您连续跟踪每天的体重,则以下代码可提供比迄今为止最低记录体重高出 5% 的尾随减肥:
>>> weight.cummin() * 1.05
在本章中,我们将介绍以下主题:
序列或数据帧中数据的每个维度都通过索引对象标记。 正是这个索引将 Pandas 数据结构与 NumPy 的 n 维数组分开。 索引为数据的每一行和每一列提供了有意义的标签,而 Pandas 用户可以通过使用这些标签来选择数据。 此外,pandas 允许其用户通过行和列的整数位置选择数据。 这种双重选择功能(一种使用标签,另一种使用整数位置)使得强大而又令人困惑的语法可以选择数据子集。
通过使用标签或整数位置选择数据并非 Pandas 所独有。 Python 字典和列表是内置的数据结构,它们以下列其中一种方式选择其数据。 字典和列表都具有精确的说明,并且对于传递给索引运算符的内容都具有有限的用例。 字典的键(其标签)必须是不可变的对象,例如字符串,整数或元组。 列表必须使用整数或切片对象进行选择。 通过将键传递给索引运算符,词典一次只能选择一个对象。 从某种意义上说,Pandas 结合了使用整数(如列表)和标签(如字典)选择数据的能力。
序列和数据帧是复杂的数据容器,具有多个属性,这些属性使用索引运算符以不同方式选择数据。 除了索引运算符本身之外,.iloc
和.loc
属性也可用,并以其自己的独特方式使用索引运算符。 这些属性统称为索引器。
索引术语可能会引起混淆。 这里使用术语“索引运算符”将其与其他索引器区分开。 它指代直接在序列或数据帧之后的括号[]
。 例如,给定一个s
序列,您可以通过以下方式选择数据:s[item]
和s.loc[item]
。 第一个使用索引运算符。 第二个使用.loc
索引器。
序列和数据帧索引器允许按整数位置(如 Python 列表)和标签(如 Python 字典)进行选择。.iloc
索引器仅按整数位置选择,并且与 Python 列表类似。.loc
索引器仅按索引标签进行选择,这与 Python 词典的工作方式类似。
.loc
和。iloc
与序列和数据帧一起使用。 此秘籍展示了如何通过.iloc
通过整数位置以及通过.loc
通过标签选择序列数据。 这些索引器不仅获取标量值,还获取列表和切片。
>>> college = pd.read_csv('data/college.csv', index_col='INSTNM')
>>> city = college['CITY']
>>> city.head()
INSTNM
Alabama A & M University Normal
University of Alabama at Birmingham Birmingham
Amridge University Montgomery
University of Alabama in Huntsville Huntsville
Alabama State University Montgomery
Name: CITY, dtype: object
.iloc
索引器仅按整数位置进行选择。 向其传递整数将返回标量值:>>> city.iloc[3]
Huntsville
.iloc
。 这将返回一个序列:>>> city.iloc[[10,20,30]]
INSTNM
Birmingham Southern College Birmingham
George C Wallace State Community College-Hanceville Hanceville
Judson College Marion
Name: CITY, dtype: object
>>> city.iloc[4:50:10]
INSTNM
Alabama State University Montgomery
Enterprise State Community College Enterprise
Heritage Christian University Florence
Marion Military Institute Marion
Reid State Technical College Evergreen
Name: CITY, dtype: object
.loc
索引器,该索引器仅使用索引标签进行选择。 传递单个字符串将返回标量值:>>> city.loc['Heritage Christian University']
Florence
>>> np.random.seed(1)
>>> labels = list(np.random.choice(city.index, 4))
>>> labels
['Northwest HVAC/R Training Center',
'California State University-Dominguez Hills',
'Lower Columbia College',
'Southwest Acupuncture College-Boulder']
>>> city.loc[labels]
INSTNM
Northwest HVAC/R Training Center Spokane
California State University-Dominguez Hills Carson
Lower Columbia College Longview
Southwest Acupuncture College-Boulder Boulder
Name: CITY, dtype: object
>>> city.loc['Alabama State University':
'Reid State Technical College':10]
INSTNM
Alabama State University Montgomery
Enterprise State Community College Enterprise
Heritage Christian University Florence
Marion Military Institute Marion
Reid State Technical College Evergreen
Name: CITY, dtype: object
序列中的值由从 0 开始的整数引用。步骤 2 使用.loc
索引器选择序列的第四个元素。 步骤 3 将三个项目的整数列表传递给索引运算符,该运算符返回选择了那些整数位置的序列。 此功能是对 Python 列表的增强,它无法以这种方式选择多个不相交的项目。
在步骤 4 中,使用指定了start
,stop
和step
值的切片符号来选择序列的整个部分。
步骤 5 至 7 使用基于标签的索引器.loc
复制步骤 2 至 4。 标签必须与索引中的值完全匹配。 为了确保标签正确,我们在步骤 6 中从索引中随机选择四个标签,并将它们存储到列表中,然后再将它们的值选择为序列。 使用.loc
索引器的选择始终包含最后一个元素,如步骤 7 所示。
与步骤 2 和步骤 5 一样,当将标量值传递给索引运算符时,将返回标量值。 与其他步骤一样,传递列表或切片时,将返回一个序列。 此返回值似乎不一致,但是如果我们将序列视为将标签映射到值的类似于字典的对象,则返回值是有意义的。 要选择单个项目并将其保留在序列中,请以单项列表而不是标量值的形式传递:
>>> city.iloc[[3]]
INSTNM
University of Alabama in Huntsville Huntsville
Name: CITY, dtype: object
将切片符号与.loc
一起使用时需要格外小心。 如果start
索引出现在stop
索引之后,则返回一个空序列,而不会引发异常:
>>> city.loc['Reid State Technical College':
'Alabama State University':10]
Series([], Name: CITY, dtype: object)
选择[DataGate]行的最明确,最优选的方法是使用.iloc
和.loc
索引器。 它们能够独立且同时选择行或列。
此秘籍向您展示如何使用.iloc
和.loc
索引器从数据帧中选择行。
>>> college = pd.read_csv('data/college.csv', index_col='INSTNM')
>>> college.head()
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-SVbMLsxc-1681366519521)(https://gitcode.net/apachecn/apachecn-ds-zh/-/raw/master/docs/master-pandas/img/00059.jpeg)]
.iloc
索引器,以选择该位置的整个行:>>> college.iloc[60]
CITY Anchorage
STABBR AK
HBCU 0
...
UG25ABV 0.4386
MD_EARN_WNE_P10 42500
GRAD_DEBT_MDN_SUPP 19449.5
Name: University of Alaska Anchorage, Length: 26, dtype: object
.loc
索引器:>>> college.loc['University of Alaska Anchorage']
CITY Anchorage
STABBR AK
HBCU 0
...
UG25ABV 0.4386
MD_EARN_WNE_P10 42500
GRAD_DEBT_MDN_SUPP 19449.5
Name: University of Alaska Anchorage, Length: 26, dtype: object
.iloc
索引器:>>> college.iloc[[60, 99, 3]]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-H2ALarSx-1681366519521)(https://gitcode.net/apachecn/apachecn-ds-zh/-/raw/master/docs/master-pandas/img/00060.jpeg)]
.loc
通过将确切的机构名称列表传递给第 4 步中相同的数据帧:>>> labels = ['University of Alaska Anchorage',
'International Academy of Hair Design',
'University of Alabama in Huntsville']
>>> college.loc[labels]
.iloc
一起使用以选择整个数据段:>>> college.iloc[99:102]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-dCmqcdkA-1681366519521)(https://gitcode.net/apachecn/apachecn-ds-zh/-/raw/master/docs/master-pandas/img/00061.jpeg)]
.loc
索引器一起使用,并且包含最后一个标签:>>> start = 'International Academy of Hair Design'
>>> stop = 'Mesa Community College'
>>> college.loc[start:stop]
将标量值,标量列表或切片对象传递给.iloc
或.loc
索引器,会使 Pandas 扫描索引标签中的适当行并返回它们。 如果传递单个标量值,则返回一个序列。 如果传递了列表或切片对象,则返回一个数据帧。
在步骤 5 中,可以直接从步骤 4 中返回的数据帧中选择索引标签列表,而无需复制和粘贴:
>>> college.iloc[[60, 99, 3]].index.tolist()
['University of Alaska Anchorage',
'International Academy of Hair Design',
'University of Alabama in Huntsville']
直接使用索引运算符是从数据帧中选择一列或多列的正确方法。 但是,它不允许您同时选择行和列。 要同时选择行和列,您将需要将有效的行和列选择都用逗号传递给.iloc
或.loc
索引器。
选择行和列的通用形式将类似于以下代码:
>>> df.iloc[rows, columns]
>>> df.loc[rows, columns]
rows
和columns
变量可以是标量值,列表,切片对象或布尔序列。
第 5 章,“布尔索引”中介绍了将布尔序列传递给索引器。
在此秘籍中,每个步骤都显示使用.iloc
同时选择行和列,以及使用.loc
进行精确复制。
>>> college = pd.read_csv('data/college.csv', index_col='INSTNM')
>>> college.iloc[:3, :4]
>>> college.loc[:'Amridge University', :'MENONLY']
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-EvfvRl6U-1681366519522)(https://gitcode.net/apachecn/apachecn-ds-zh/-/raw/master/docs/master-pandas/img/00062.jpeg)]
>>> college.iloc[:, [4,6]].head()
>>> college.loc[:, ['WOMENONLY', 'SATVRMID']].head()
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-PtOhE9CB-1681366519522)(https://gitcode.net/apachecn/apachecn-ds-zh/-/raw/master/docs/master-pandas/img/00063.jpeg)]
>>> college.iloc[[100, 200], [7, 15]]
>>> rows = ['GateWay Community College',
'American Baptist Seminary of the West']
>>> columns = ['SATMTMID', 'UGDS_NHPI']
>>> college.loc[rows, columns]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-PIV074xj-1681366519522)(https://gitcode.net/apachecn/apachecn-ds-zh/-/raw/master/docs/master-pandas/img/00064.jpeg)]
>>> college.iloc[5, -4]
>>> college.loc['The University of Alabama', 'PCTFLOAN']
-.401
>>> college.iloc[90:80:-2, 5]
>>> start = 'Empire Beauty School-Flagstaff'
>>> stop = 'Arizona State University-Tempe'
>>> college.loc[start:stop:-2, 'RELAFFIL']
INSTNM
Empire Beauty School-Flagstaff 0
Charles of Italy Beauty College 0
Central Arizona College 0
University of Arizona 0
Arizona State University-Tempe 0
Name: RELAFFIL, dtype: int64
同时选择行和列的关键之一是了解方括号中逗号的用法。 逗号左侧的选择始终根据行索引选择行。 逗号右边的选择始终根据列索引选择列。
不必同时选择行和列。 步骤 2 显示了如何选择所有行和列的子集。 冒号表示一个切片对象,该对象仅返回该维度的所有值。
选择行的子集以及所有列时,不必在逗号后使用冒号。 如果没有逗号,则默认行为是选择所有列。 先前的秘籍正是以这种方式选择了行。 但是,您可以使用冒号表示所有列的一部分。 以下代码行是等效的:
>>> college.iloc[:10]
>>> college.iloc[:10, :]
.iloc
和.loc
索引器均通过整数或标签位置选择数据,但不能同时处理两种输入类型的组合。 在早期版本的 Pandas 中,可以使用另一个索引器.ix
通过整数和标签位置选择数据。 尽管这在某些特定情况下很方便,但是它本质上是模棱两可的,并且使许多 Pandas 使用者感到困惑。.ix
索引器随后被弃用,因此应避免使用。
在.ix
弃用之前,可以使用college.ix[:5, 'UGDS_WHITE':'UGDS_UNKN']
从UGDS_WHITE
到UGDS_UNKN
选择大学数据集的前五行和列。 现在不可能直接使用.loc
或.iloc
来做到这一点。 以下秘籍显示了如何找到列的整数位置,然后使用.iloc
完成选择。
INSTNM
)分配为索引:>>> college = pd.read_csv('data/college.csv', index_col='INSTNM')
get_loc
查找所需列的整数位置:>>> col_start = college.columns.get_loc('UGDS_WHITE')
>>> col_end = college.columns.get_loc('UGDS_UNKN') + 1
>>> col_start, col_end
col_start
和col_end
使用.iloc
按整数位置选择列:>>> college.iloc[:5, col_start:col_end]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ViytxqV6-1681366519522)(https://gitcode.net/apachecn/apachecn-ds-zh/-/raw/master/docs/master-pandas/img/00065.jpeg)]
步骤 2 首先通过columns
属性检索列索引。 索引具有get_loc
方法,该方法接受索引标签并返回其整数位置。 我们找到要切片的列的开始和结束整数位置。 我们添加一个是因为用.iloc
切片不包括最后一项。 步骤 3 将切片符号与行和列一起使用。
我们可以做一个非常相似的操作来使.loc
与整数和位置的混合使用。 下面显示了如何选择第 10 至 15 行(包括第 10 行)以及UGDS_WHITE
至UGDS_UNKN
列:
>>> row_start = df_college.index[10]
>>> row_end = df_college.index[15]
>>> college.loc[row_start:row_end, 'UGDS_WHITE':'UGDS_UNKN']
使用.ix
进行相同的操作(已弃用,因此请勿执行此操作)如下所示:
>>> college.ix[10:16, 'UGDS_WHITE':'UGDS_UNKN']
通过将.loc
和.iloc
链接在一起可以实现相同的结果,但是链接索引器通常不是一个好主意:
>>> college.iloc[10:16].loc[:, 'UGDS_WHITE':'UGDS_UNKN']
.iloc
和.loc
索引器都能够从序列或数据帧中选择单个元素(标量值)。 但是,存在分度器.iat
和.at
,它们分别以更快的速度实现相同的功能。 与.iloc
相似,.iat
索引器使用整数位置进行选择,并且必须传递两个以逗号分隔的整数。 与.loc
相似,.at
索引使用标签进行选择,并且必须传递一个索引和由逗号分隔的列标签。
如果计算时间至关重要,则此秘籍很有价值。 当使用标量选择时,它显示了.iat
和.at
相对于.iloc
和.loc
的性能提高。
college
记分板数据集。 将大学名称和列名称传递给.loc
,以便选择标量值:>>> college = pd.read_csv('data/college.csv', index_col='INSTNM')
>>> cn = 'Texas A & M University-College Station'
>>> college.loc[cn, 'UGDS_WHITE']
.661
.at
获得相同的结果:>>> college.at[cn, 'UGDS_WHITE']
.661
%timeit
魔术命令查找速度差异:>>> %timeit college.loc[cn, 'UGDS_WHITE']
8.97 µs ± 617 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)
>>> %timeit college.at[cn, 'UGDS_WHITE']
6.28 µs ± 214 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)
.iloc
和.iat
之间的差:>>> row_num = college.index.get_loc(cn)
>>> col_num = college.columns.get_loc('UGDS_WHITE')
>>> row_num, col_num
(3765, 10)
>>> %timeit college.iloc[row_num, col_num]
9.74 µs ± 153 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)
>>> %timeit college.iat[row_num, col_num]
7.29 µs ± 431 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)
标量索引器.iat
和.at
仅接受标量值。 如果其他任何东西传递给他们,他们就会失败。 在进行标量选择时,它们是.iloc
和.loc
的直接替代品。timeit
魔术命令在以两个百分号开头时对整个代码块计时,而在以一个百分号开头时一次。 它表明,通过切换到标量索引器,平均可节省约 2.5 微秒。 这可能并不多,但是如果在程序中重复进行标量选择,则可能会很快加起来。
.iat
和.at
都可以与序列一起使用。 给它们传递一个标量值,它们将返回一个标量:
>>> state = college['STBBR'] # Select a Series
>>> state.iat[1000]
'IL'
>>> state.at['Stanford University']
'CA'
本章前面的秘籍展示了如何使用.iloc
和.loc
索引器选择任一维度中的序列和数据帧的子集。 选择行的快捷方式仅包含索引运算符本身。 这只是显示 Pandas 其他功能的捷径,但索引运算符的主要功能实际上是选择数据帧的列。 如果要选择行,则最好使用.iloc
或.loc
,因为它们是明确的。
在此秘籍中,我们将切片对象传递给序列和数据帧索引运算符。
>>> college = pd.read_csv('data/college.csv', index_col='INSTNM')
>>> college[10:20:2]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-G5Ruwcq3-1681366519522)(https://gitcode.net/apachecn/apachecn-ds-zh/-/raw/master/docs/master-pandas/img/00066.jpeg)]
>>> city = college['CITY']
>>> city[10:20:2]
INSTNM
Birmingham Southern College Birmingham
Concordia College Alabama Selma
Enterprise State Community College Enterprise
Faulkner University Montgomery
New Beginning College of Cosmetology Albertville
Name: CITY, dtype: object
>>> start = 'Mesa Community College'
>>> stop = 'Spokane Community College'
>>> college[start:stop:1500]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-KXV4rs2d-1681366519523)(https://gitcode.net/apachecn/apachecn-ds-zh/-/raw/master/docs/master-pandas/img/00067.jpeg)]
>>> city[start:stop:1500]
INSTNM
Mesa Community College Mesa
Hair Academy Inc-New Carrollton New Carrollton
National College of Natural Medicine Portland
Name: CITY, dtype: object
索引运算符根据传递给它的对象类型来更改行为。 以下伪代码概述了数据帧索引运算符如何处理其传递的对象:
>>> df[item] # Where `df` is a DataFrame and item is some object
If item is a string then
Find a column name that matches the item exactly
Raise KeyError if there is no match
Return the column as a Series
If item is a list of strings then
Raise KeyError if one or more strings in item don't match columns
Return a DataFrame with just the columns in the list
If item is a slice object then
Works with either integer or string slices
Raise KeyError if label from label slice is not in index
Return all ROWS that are selected by the slice
If item is a list, Series or ndarray of booleans then
Raise ValueError if length of item not equal to length of DataFrame
Use the booleans to return only the rows with True in same location
前面的逻辑涵盖了所有最常见的情况,但并不详尽。 序列的逻辑与数据帧的逻辑稍有不同,实际上更为复杂。 由于其复杂性,最好避免在序列上仅使用索引运算符本身,而应使用显式的.iloc
和.loc
索引器。
序列的索引运算符的一种可接受的用例是在进行布尔索引时。 有关更多详细信息,请参见第 6 章“索引对齐”。
我在本节中将这种行切片称为惰性,因为它不使用更明确的.iloc
或.loc
。 就个人而言,我总是在对行进行切片时使用这些索引器,因为从来没有确切地知道我在做什么。
重要的是要知道,这种延迟切片不适用于列,仅适用于数据帧的行和序列,也不能同时选择行和列。 以下面的代码为例,该代码尝试选择前十行和两列:
>>> college[:10, ['CITY', 'STABBR']]
TypeError: unhashable type: 'slice'
要以这种方式进行选择,您需要使用.loc
或.iloc
。 这是一种可能的方法,该方法首先选择所有机构标签,然后使用基于标签的索引器.loc
:
>>> first_ten_instnm = college.index[:10]
>>> college.loc[first_ten_instnm, ['CITY', 'STABBR']]
.loc
索引器通常根据索引的确切字符串标签选择数据。 但是,它还允许您根据索引中值的字典顺序选择数据。 具体来说,.loc
允许您使用切片符号按词典顺序选择带有索引的所有行。 仅在对索引排序时有效。
在本秘籍中,您将首先对索引进行排序,然后在.loc
索引器中使用切片符号选择两个字符串之间的所有行。
>>> college = pd.read_csv('data/college.csv', index_col='INSTNM')
'Sp'
和'Su'
之间的大学:>>> college.loc['Sp':'Su']
KeyError: 'Sp'
>>> college = college.sort_index()
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5lawAwyM-1681366519523)(https://gitcode.net/apachecn/apachecn-ds-zh/-/raw/master/docs/master-pandas/img/00068.jpeg)]
>>> college.loc['Sp':'Su']
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8PHeQufJ-1681366519523)(https://gitcode.net/apachecn/apachecn-ds-zh/-/raw/master/docs/master-pandas/img/00069.jpeg)]
.loc
的正常行为是根据传递给它的确切标签来选择数据。 在索引中找不到这些标签时,将引发KeyError
。 但是,只要按字典顺序对索引进行排序并将切片传递给该索引,就会存在对此行为的一个特殊例外。 现在可以在切片的start
和stop
标签之间进行选择,即使它们不是索引的精确值也是如此。
使用此秘籍,可以轻松地在两个字母之间选择大学。 例如,要选择所有以字母D
至S
开头的大学,则可以使用college.loc['D':'T']
。 像这样的切片仍然包含最后一个索引,因此从技术上讲,这将返回一确切名称为T
的大学。
当索引按相反方向排序时,这种切片方式也适用。 您可以使用索引属性is_monotonic_increasing
或is_monotonic_decreasing
确定索引的排序方向。 为了使字典式切片能够正常工作,这些参数中的任何一个都必须为True
。 例如,以下代码按字典顺序对索引从Z
到A
进行排序:
>>> college = college.sort_index(ascending=False)
>>> college.index.is_monotonic_decreasing
True
>>> college.loc['E':'B']
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jwl6UKQn-1681366519523)(https://gitcode.net/apachecn/apachecn-ds-zh/-/raw/master/docs/master-pandas/img/00070.jpeg)]
Python 将所有大写字母排在小写字母之前,并将所有整数排在大写字母之前。
在本章中,我们将介绍以下主题:
WHERE
子句query
方法提高布尔索引的可读性where
方法保留序列从数据集中过滤数据是最常见的基本操作之一。 有许多方法可以使用布尔下标过滤(或子集)Pandas 中的数据。 布尔索引(也称为布尔选择)可能是一个令人困惑的术语,但出于 Pandas 的目的,它是指通过为每行提供布尔值(True
或False
)来选择行 。 这些布尔值通常存储在序列或 NumPy ndarray
中,通常是通过将布尔条件应用于数据帧中的一个或多个列来创建的。 我们首先创建布尔序列并计算它们的统计量,然后继续创建更复杂的条件,然后以多种方式使用布尔索引来过滤数据。
首次引入布尔序列时,计算有关它们的基本摘要统计信息可能会很有帮助。 布尔序列的每个值的取值为 0 或 1,因此所有适用于数值的序列方法也适用于布尔值。
在此秘籍中,我们通过将条件应用于数据列来创建布尔序列,然后从中计算汇总统计信息。
movie
数据集,将索引设置为电影标题,然后检查前几行:>>> movie = pd.read_csv('data/movie.csv', index_col='movie_title')
>>> movie.head()
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-PsaibhPa-1681366519524)(https://gitcode.net/apachecn/apachecn-ds-zh/-/raw/master/docs/master-pandas/img/00071.jpeg)]
duration
序列使用大于比较运算符,确定每部电影的时长是否大于两个小时:>>> movie_2_hours = movie['duration'] > 120
>>> movie_2_hours.head(10)
movie_title
Avatar True
Pirates of the Caribbean: At World's End True
Spectre True
The Dark Knight Rises True
Star Wars: Episode VII - The Force Awakens False
John Carter True
Spider-Man 3 True
Tangled False
Avengers: Age of Ultron True
Harry Potter and the Half-Blood Prince True
Name: duration, dtype: bool
>>> movie_2_hours.sum()
1039
mean
方法:>>> movie_2_hours.mean()
0.2114
duration
列缺少一些值。 如果回头看步骤 1 的数据帧输出,您将看到最后一行缺少duration
的值。 为此,步骤 2 中的布尔条件返回False
。 我们需要先删除丢失的值,然后求值条件并取均值:>>> movie['duration'].dropna().gt(120).mean()
.2112
describe
方法输出有关布尔序列的一些摘要统计信息:>>> movie_2_hours.describe()
count 4916
unique 2
top False
freq 3877
Name: duration, dtype: object
大多数数据帧不会像我们的电影数据集那样具有布尔值列。 产生布尔序列的最直接方法是使用比较运算符之一将条件应用于列之一。 在步骤 2 中,我们使用大于号运算符来测试每部电影的时长是否超过两个小时(120 分钟)。 第 3 步和第 4 步从布尔序列计算两个重要量,即和和均值。 这些方法是可行的,因为 Python 将False
/True
求值为 0/1。
您可以自己证明布尔级数的均值代表True
值的百分比。 为此,请使用value_counts
方法,将normalize
参数设置为True
,以获取其分布:
>>> movie_2_hours.value_counts(normalize=True)
False 0.788649
True 0.211351
Name: duration, dtype: float64
步骤 5 提醒我们步骤 4 的错误结果。即使duration
列缺少值,布尔条件也将所有这些比较与缺少的值求值为False
。 删除这些缺失值使我们能够计算出正确的统计量。 通过方法链接,只需一步即可完成。
步骤 6 显示,Pandas 通过显示频率信息对待布尔列的方式类似于对待对象数据类型的方式。 这是考虑布尔序列的自然方法,而不是像对数字数据那样显示分位数。
可以比较来自同一数据帧的两列以生成布尔序列。 例如,我们可以确定具有演员 1 的 Facebook 点赞数比演员 2 更多的电影的百分比。要做到这一点,我们将选择这两列,然后删除任何其中一部电影缺少值的行。 然后,我们将进行比较并计算均值:
>>> actors = movie[['actor_1_facebook_likes',
'actor_2_facebook_likes']].dropna()
>>> (actors['actor_1_facebook_likes'] >
actors['actor_2_facebook_likes']).mean()
.978
在 Python 中,布尔表达式使用内置的逻辑运算符and
,or
和not
。 这些关键字不适用于 Pandas 中的布尔索引,而是分别用&
,|
和~
代替。 此外,每个表达式必须用括号括起来,否则会产生错误。
为数据集构造一个精确的过滤器可能会使您将多个布尔表达式组合在一起以提取一个精确的子集。 在此秘籍中,我们将构造多个布尔表达式,然后将它们组合在一起以查找title_year
为 2000 之前或 2009 年之后,imdb_score
大于 8,并且content_rating
为PG-13
的所有电影。
>>> movie = pd.read_csv('data/movie.csv', index_col='movie_title')
>>> criteria1 = movie.imdb_score > 8
>>> criteria2 = movie.content_rating == 'PG-13'
>>> criteria3 = ((movie.title_year < 2000) |
(movie.title_year > 2009))
>>> criteria2.head() # all criteria Series look similar
movie_title
Avatar True
Pirates of the Caribbean: At World's End True
Spectre True
The Dark Knight Rises True
Star Wars: Episode VII - The Force Awakens False
Name: content_rating, dtype: bool
>>> criteria_final = criteria1 & criteria2 & criteria3
>>> criteria_final.head()
movie_title
Avatar False
Pirates of the Caribbean: At World's End False
Spectre False
The Dark Knight Rises True
Star Wars: Episode VII - The Force Awakens False
dtype: bool
可以使用标准比较运算符(<
,>
,==
,!=
,<=
和>=
)将序列中的所有值与标量值进行比较。 表达式movie.imdb_score > 8
产生布尔序列,其中所有超过 8 的imdb_score
值价格均为True
,而小于或等于 8 的价格为False
。 此布尔序列的索引保留与原始索引相同的索引,在这种情况下,为电影的标题。
criteria3
变量由两个独立的布尔表达式创建。 每个表达式必须用括号括起来才能正常运行。 管道字符|
用于在两个序列的每个值之间创建逻辑or
条件。
所有三个条件都必须为True
以匹配秘籍要求。 它们每个都与和号字符&
组合在一起,后者在每个序列值之间创建逻辑and
条件。
Pandas 对逻辑运算符使用不同语法的结果是运算符优先级不再相同。 比较运算符的优先级高于and
,or
和not
。 但是,Pandas 的新运算符(按位运算符&
,|
和~
)比比较运算符具有更高的优先级,因此需要括号。 一个例子可以帮助清除这一点。 采取以下表达式:
>>> 5 < 10 and 3 > 4
False
在前面的表达式中,首先求值5 < 10
,然后求值3 < 4
,最后求值and
。 Python 通过表达式进行如下操作:
>>> 5 < 10 and 3 > 4
>>> True and 3 > 4
>>> True and False
>>> False
让我们看一下如果criteria3
中的表达式编写如下会发生什么:
>>> movie.title_year < 2000 | movie.title_year > 2009
TypeError: cannot compare a dtyped [float64] array with a scalar of type [bool]
由于按位运算符的优先级比比较运算符的优先级高,因此2000 | movie.title_year
首先被求值,这是没有意义的,并且会产生错误。 因此,需要括号以正确的顺序求值操作。
为何 Pandas 不能使用and
,or
和not
? 当求值这些关键字时,Python 尝试查找整个对象的真实性。 因为将整个序列而不是每个元素作为True
或False
都没有意义,Pandas 都会引发错误。
Python 中的许多对象都具有布尔表示形式。 例如,除 0 以外的所有整数都被视为True
。 除空字符串外,所有字符串均为True
。 所有非空集,元组,字典和列表都是True
。 空的数据帧或序列不会求值为True
或False
,而是会引发错误。 通常,要检索 Python 对象的真实性,请将其传递给bool
函数。
序列和数据帧对象的布尔选择实际上是相同的。 两者都通过将与要过滤的对象索引相同的布尔序列传递给索引运算符来工作。
此秘籍为不同的电影组构造了两个复杂且独立的布尔标准。 第一组电影是根据之前的秘籍制作的,包括imdb_score
大于 8,content_rating
为PG-13
和title_year
在 2000 年之前或 2009 年之后的电影。第二组电影包括imdb_score
小于 5,content_rating
的 R 和title_year
在 2000 年至 2010 年之间的数据。
movie
数据集,将索引设置为movie_title
,并创建第一组条件:>>> movie = pd.read_csv('data/movie.csv', index_col='movie_title')
>>> crit_a1 = movie.imdb_score > 8
>>> crit_a2 = movie.content_rating == 'PG-13'
>>> crit_a3 = (movie.title_year < 2000) | (movie.title_year > 2009)
>>> final_crit_a = crit_a1 & crit_a2 & crit_a3
>>> crit_b1 = movie.imdb_score < 5
>>> crit_b2 = movie.content_rating == 'R'
>>> crit_b3 = ((movie.title_year >= 2000) &
(movie.title_year <= 2010))
>>> final_crit_b = crit_b1 & crit_b2 & crit_b3
or
运算符组合两组标准。 这将产生一个布尔序列,其中的任何一部电影都是这两组电影的成员:>>> final_crit_all = final_crit_a | final_crit_b
>>> final_crit_all.head()
movie_title
Avatar False
Pirates of the Caribbean: At World's End False
Spectre False
The Dark Knight Rises True
Star Wars: Episode VII - The Force Awakens False
dtype: bool
>>> movie[final_crit_all].head()
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-qDChZ2ul-1681366519524)(https://gitcode.net/apachecn/apachecn-ds-zh/-/raw/master/docs/master-pandas/img/00072.jpeg)]
.loc
索引器过滤行和列:>>> cols = ['imdb_score', 'content_rating', 'title_year']
>>> movie_filtered = movie.loc[final_crit_all, cols]
>>> movie_filtered.head(10)
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1lrQpQX8-1681366519524)(https://gitcode.net/apachecn/apachecn-ds-zh/-/raw/master/docs/master-pandas/img/00073.jpeg)]
在步骤 1 和步骤 2 中,每组条件都是从更简单的布尔表达式构建的。 不必像此处所做的那样为每个布尔表达式创建一个不同的变量,但是这样做确实使读取和调试任何逻辑错误变得容易得多。 当我们需要两组电影时,步骤 3 使用 Pandas 逻辑or
运算符将它们组合在一起。
步骤 4 显示了布尔索引工作原理的确切语法。 您只需将从第 3 步创建的布尔值序列直接传递给索引运算符即可。 仅选择final_crit_all
中具有True
值的电影。
如步骤 5 所示,布尔索引还可以与.loc
索引器配合使用,同时执行布尔索引和单个列选择。 精简的数据帧易于手动检查 逻辑是否正确实现。
布尔索引与.iloc
索引运算符不能完全兼容。 如果将布尔序列传递给它,则会引发异常。 但是,如果您传递布尔 N 维数组,则它将与其他索引器在此秘籍中的行为相同。
如前所述,可以使用一个长布尔表达式代替其他几个短布尔表达式。 要使用一长行代码复制第 1 步中的final_crit_a
变量,我们可以执行以下操作:
>>> final_crit_a2 = (movie.imdb_score > 8) & \
(movie.content_rating == 'PG-13') & \
((movie.title_year < 2000) |
(movie.title_year > 2009))
>>> final_crit_a2.equals(final_crit_a)
True
通过使用索引,可以复制布尔选择的特定情况。 通过索引进行选择更加直观,并提高了可读性。
在此秘籍中,我们使用college
数据集通过布尔索引和索引选择从特定状态中选择所有机构,然后将它们各自的性能相互比较。
college
数据集,并使用布尔索引从德克萨斯(TX
)州选择所有机构:>>> college = pd.read_csv('data/college.csv')
>>> college[college['STABBR'] == 'TX'].head()
Pandas official documentation on[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vo3LyInT-1681366519525)(https://gitcode.net/apachecn/apachecn-ds-zh/-/raw/master/docs/master-pandas/img/00074.jpeg)]
STABBR
列移入索引。 然后,我们可以在.loc
索引器中使用基于标签的选择:>>> college2 = college.set_index('STABBR')
>>> college2.loc['TX'].head()
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-KtiLoUG9-1681366519525)(https://gitcode.net/apachecn/apachecn-ds-zh/-/raw/master/docs/master-pandas/img/00075.jpeg)]
>>> %timeit college[college['STABBR'] == 'TX']
1.43 ms ± 53.5 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
>>> %timeit college2.loc['TX']
526 µs ± 6.67 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
>>> %timeit college2 = college.set_index('STABBR')
1.04 ms ± 5.37 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
步骤 1 通过确定哪些数据行具有STABBR
等于TX
来创建布尔序列。 该序列传递给索引运算符,该运算符对数据进行子集化。 可以通过将同一列移到索引,并简单地将基本的基于标签的索引选择与.loc
一起使用来复制此过程。 通过索引选择比布尔选择快得多。
此秘籍仅选择一个状态。 可以使用布尔选择和索引选择来选择多个状态。 让我们选择德州(TX
),加利福尼亚(CA
)和纽约(NY
)。 使用布尔选择时,可以使用isin
方法,但是使用索引时,只需将列表传递给.loc
即可:
>>> states = ['TX', 'CA', 'NY']
>>> college[college['STABBR'].isin(states)]
>>> college2.loc[states]
故事的内容比该秘籍的解释要多得多。 Pandas 根据索引是唯一索引还是排序索引来不同地实现索引。 有关更多详细信息,请参见以下秘籍。
当索引是唯一的或已排序时,索引选择性能会大大提高。 先前的秘籍使用了包含重复项的未排序索引,因此选择速度相对较慢。
在此秘籍中,我们使用college
数据集来形成唯一索引或排序索引,以提高索引选择的性能。 我们还将继续将性能与布尔索引进行比较。
STABBR
作为索引创建一个单独的数据帧,然后检查索引是否已排序:>>> college = pd.read_csv('data/college.csv')
>>> college2 = college.set_index('STABBR')
>>> college2.index.is_monotonic
False
college2
中的索引进行排序,并将其存储为另一个对象:>>> college3 = college2.sort_index()
>>> college3.index.is_monotonic
True
TX
)的时间:>>> %timeit college[college['STABBR'] == 'TX']
1.43 ms ± 53.5 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
>>> %timeit college2.loc['TX']
526 µs ± 6.67 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
>>> %timeit college3.loc['TX']
183 µs ± 3.67 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
>>> college_unique = college.set_index('INSTNM')
>>> college_unique.index.is_unique
True
>>> college[college['INSTNM'] == 'Stanford University']
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-BB2waF6E-1681366519525)(https://gitcode.net/apachecn/apachecn-ds-zh/-/raw/master/docs/master-pandas/img/00076.jpeg)]
>>> college_unique.loc['Stanford University']
CITY Stanford
STABBR CA
HBCU 0
...
UG25ABV 0.0401
MD_EARN_WNE_P10 86000
GRAD_DEBT_MDN_SUPP 12782
Name: Stanford University, dtype: object
>>> %timeit college[college['INSTNM'] == 'Stanford University']
1.3 ms ± 56.8 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
>>> %timeit college_unique.loc['Stanford University']
157 µs ± 682 ns per loop (mean ± std. dev. of 7 runs, 10000 loops each)
当索引未排序且包含重复项时(如college2
一样),Pandas 将需要检查索引中的每个单个值以进行正确选择。 像college3
一样对索引进行排序时,pandas 利用称为二分搜索的算法来大大提高性能。
在秘籍的后半部分,我们使用唯一列作为索引。 Pandas 通过哈希表实现唯一索引,从而使选择速度更快。 几乎可以在同一时间查找每个索引位置,而不管其长度如何。
布尔选择比索引选择具有更大的灵活性,因为可以对任意数量的列进行条件调整。 在此秘籍中,我们使用单列作为索引。 可以将多个列连接在一起以形成索引。 例如,在以下代码中,我们将索引设置为等于city
和state
列的连接:
>>> college.index = college['CITY'] + ', ' + college['STABBR']
>>> college = college.sort_index()
>>> college.head()
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-UI7PxEAp-1681366519525)(https://gitcode.net/apachecn/apachecn-ds-zh/-/raw/master/docs/master-pandas/img/00077.jpeg)]
从这里,我们可以从特定城市和州的组合中选择所有大学,而无需布尔索引。 让我们从Miami, FL
中选择所有大学:
>>> college.loc['Miami, FL'].head()
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-KjOvL58Q-1681366519525)(https://gitcode.net/apachecn/apachecn-ds-zh/-/raw/master/docs/master-pandas/img/00078.jpeg)]
我们可以将这种复合索引选择与布尔索引的速度进行比较。 有一个数量级以上的差异:
>>> %%timeit
>>> crit1 = college['CITY'] == 'Miami'
>>> crit2 = college['STABBR'] == 'FL'
>>> college[crit1 & crit2]
2.43 ms ± 80.4 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
>>> %timeit college.loc['Miami, FL']
197 µs ± 8.69 µs per loop (mean ± std. dev. of 7 runs, 10000 loops each)
购买了多头股票的投资者显然希望以历史最高价或接近历史最高价的价格出售股票。 当然,这在实践中很难做到,尤其是当股价仅将其历史的一小部分花费在一定阈值之上时。 我们可以使用布尔索引来查找股票花费高于或低于某个特定值的所有时间点。 此练习可以帮助我们了解某些股票的交易范围。
在此秘籍中,我们研究了从 2010 年初到 2017 年中期的斯伦贝谢股票。 我们使用布尔索引来提取这段时间内收盘价的最低和最高百分之十的序列。 然后,我们绘制所有点并突出显示上下百分之十的点。
Date
列放入索引,并将其转换为DatetimeIndex
:>>> slb = pd.read_csv('data/slb_stock.csv', index_col='Date',
parse_dates=['Date'])
>>> slb.head()
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-j1mPiH8b-1681366519526)(https://gitcode.net/apachecn/apachecn-ds-zh/-/raw/master/docs/master-pandas/img/00079.jpeg)]
describe
方法返回摘要统计信息作为序列:>>> slb_close = slb['Close']
>>> slb_summary = slb_close.describe(percentiles=[.1, .9])
>>> slb_summary
count 1895.000000
mean 79.121905
std 11.767802
min 51.750000
10% 64.892000
50% 78.000000
90% 93.248000
max 117.950000
Name: Close, dtype: float64
>>> upper_10 = slb_summary.loc['90%']
>>> lower_10 = slb_summary.loc['10%']
>>> criteria = (slb_close < lower_10) | (slb_close > upper_10)
>>> slb_top_bottom_10 = slb_close[criteria]
matplotlib
库在第十和第九十个百分位处绘制水平线:>>> slb_close.plot(color='black', figsize=(12,6))
>>> slb_top_bottom_10.plot(marker='o', style=' ',
ms=4, color='lightgray')
>>> xmin = criteria.index[0]
>>> xmax = criteria.index[-1]
>>> plt.hlines(y=[lower_10, upper_10], xmin=xmin,
xmax=xmax, color='black')
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-7r8rSZZO-1681366519526)(https://gitcode.net/apachecn/apachecn-ds-zh/-/raw/master/docs/master-pandas/img/00080.jpeg)]
步骤 2 中describe
方法的结果本身就是一个以识别摘要统计量作为其索引标签的序列。 该摘要序列用于将第十和九十个百分位存储为它们自己的变量。 步骤 3 使用布尔索引来仅选择分布的高和低十分之一的那些值。
序列和数据帧都具有通过plot
方法的直接绘图函数。 对plot
方法的第一个调用来自slb_close
序列,其中包含所有 SLB 收盘价。 这是绘图中的黑线。 来自slb_filtered
的点直接在收盘价上方绘制为灰色标记。style
参数设置为单个空格,因此不会画线。ms
参数设置标记大小。
Matplotlib 带有便利函数hlines
,它可以绘制水平线。 它获取y
值的列表,并将它们从xmin
绘制到xmax
。
从我们创建的图块的新角度来看,很明显地看到,尽管 SLB 的历史最高价接近每股 120 美元,但在过去七年中只有 10% 的交易日超过了 93 美元。
我们可以使用 matplotlib 的fill_between
函数,而不是在收盘价上方绘制红点(黑点)以指示上下十分之一百分位。 此函数填充两行之间的所有区域。 它带有一个可选的where
参数,该参数接受一个布尔序列,并警告其确切要填充的位置:
>>> slb_close.plot(color='black', figsize=(12,6))
>>> plt.hlines(y=[lower_10, upper_10],
xmin=xmin, xmax=xmax,color='lightgray')
>>> plt.fill_between(x=criteria.index, y1=lower_10,
y2=slb_close.values, color='black')
>>> plt.fill_between(x=criteria.index,y1=lower_10,
y2=slb_close.values, where=slb_close < lower_10,
color='lightgray')
>>> plt.fill_between(x=criteria.index, y1=upper_10,
y2=slb_close.values, where=slb_close > upper_10,
color='lightgray')
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-m4n8BwSN-1681366519526)(https://gitcode.net/apachecn/apachecn-ds-zh/-/raw/master/docs/master-pandas/img/00081.jpeg)]
许多 Pandas 用户将使用通用的结构化查询语言(SQL)直接从数据库中处理数据。 SQL 是用于定义,操作和控制存储在数据库中的数据的标准化语言。SELECT
语句是使用 SQL 选择,过滤,聚合和排序数据的最常用方法。 Pandas 可以连接数据库并向它们发送 SQL 语句。
SQL 是数据科学家要了解的非常重要的语言。 世界上许多数据都存储在数据库中,这需要 SQL 来检索,操作和执行分析。 SQL 语法非常简单易学。 Oracle,Microsoft,IBM 等公司提供了许多不同的 SQL 实现。 尽管语法在不同的实现之间不兼容,但其核心看起来几乎相同。
在 SQL SELECT
语句中,WHERE
子句非常常见,并过滤数据。 此秘籍将编写与选择雇员数据集的特定子集的 SQL 查询等效的 Pandas 代码。
无需了解任何 SQL 语法即可使用此秘籍。
假设我们有一项任务是找到所有在警察或消防部门工作的女性雇员,其基本工资在 80 到 12 万美元之间。 以下 SQL 语句将为我们回答此查询:
SELECT
UNIQUE_ID,
DEPARTMENT,
GENDER,
BASE_SALARY
FROM
EMPLOYEE
WHERE
DEPARTMENT IN ('Houston Police Department-HPD',
'Houston Fire Department (HFD)') AND
GENDER = 'Female' AND
BASE_SALARY BETWEEN 80000 AND 120000;
employee
数据集作为数据帧:>>> employee = pd.read_csv('data/employee.csv')
>>> employee.DEPARTMENT.value_counts().head()
Houston Police Department-HPD 638
Houston Fire Department (HFD) 384
Public Works & Engineering-PWE 343
Health & Human Services 110
Houston Airport System (HAS) 106
Name: DEPARTMENT, dtype: int64
>>> employee.GENDER.value_counts()
Male 1397
Female 603
>>> employee.BASE_SALARY.describe().astype(int)
count 1886
mean 55767
std 21693
min 24960
25% 40170
50% 54461
75% 66614
max 275000
Name: BASE_SALARY, dtype: int64
isin
方法测试是否等于多个值之一:>>> depts = ['Houston Police Department-HPD',
'Houston Fire Department (HFD)']
>>> criteria_dept = employee.DEPARTMENT.isin(depts)
>>> criteria_gender = employee.GENDER == 'Female'
>>> criteria_sal = (employee.BASE_SALARY >= 80000) & \
(employee.BASE_SALARY <= 120000)
>>> criteria_final = (criteria_dept &
criteria_gender &
criteria_sal)
>>> select_columns = ['UNIQUE_ID', 'DEPARTMENT',
'GENDER', 'BASE_SALARY']
>>> employee.loc[criteria_final, select_columns].head()
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Heb4p98j-1681366519526)(https://gitcode.net/apachecn/apachecn-ds-zh/-/raw/master/docs/master-pandas/img/00082.jpeg)]
在实际进行任何过滤之前,您显然需要知道将使用的确切字符串名称。 序列value_counts
方法是获取确切的字符串名称和该值的出现次数的极好方法。
isin
序列方法等效于 SQL IN
运算符,并接受要保留的所有可能值的列表。 可以使用OR
条件序列来复制此表达式,但效率不高或惯用。
薪水标准criteria_sal
是通过组合两个简单的不等式表达式形成的。 最后,所有条件都与 Pandasand
运算符&
结合在一起,以产生单个布尔序列作为过滤器。
对于许多操作,Pandas 有多种方法来做同一件事。 在前面的秘籍中,薪水标准使用两个单独的布尔表达式。 与 SQL 相似,序列具有between
方法,其工资标准等效编写如下:
>>> criteria_sal = employee.BASE_SALARY.between(80000, 120000)
isin
的另一个有用的应用是提供由其他一些 pandas 语句自动生成的值序列。 这样可以避免进行任何手动调查来查找要存储在列表中的确切字符串名称。 相反,让我们尝试从最经常出现的五个部门中排除行:
>>> top_5_depts = employee.DEPARTMENT.value_counts().index[:5]
>>> criteria = ~employee.DEPARTMENT.isin(top_5_depts)
>>> employee[criteria]
SQL 的等效项如下:
SELECT
*
FROM
EMPLOYEE
WHERE
DEPARTMENT not in
(
SELECT
DEPARTMENT
FROM (
SELECT
DEPARTMENT,
COUNT(1) as CT
FROM
EMPLOYEE
GROUP BY
DEPARTMENT
ORDER BY
CT DESC
LIMIT 5
)
);
注意,使用了 pandas 否定运算符~
,它否定了序列的所有布尔值。
isin
和between
序列方法的官方文档IN
运算符BETWEEN
运算符在基础统计教科书中,正态分布非常依赖于描述许多不同的数据种群。 尽管大多数时间里许多随机过程的确看起来像正态分布,但现实生活中往往更为复杂。 股市回报率是分布的主要示例,该分布看上去看起来很正常,但实际上相差很远。
该秘籍描述了如何查找互联网零售巨头亚马逊的每日股市收益,并非正式地测试它们是否遵循正态分布。
>>> amzn = pd.read_csv('data/amzn_stock.csv', index_col='Date',
parse_dates=['Date'])
>>> amzn.head()
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-cMjnbHtf-1681366519527)(https://gitcode.net/apachecn/apachecn-ds-zh/-/raw/master/docs/master-pandas/img/00083.jpeg)]
pct_change
方法获得每日收益率来创建序列:>>> amzn_daily_return = amzn.Close.pct_change()
>>> amzn_daily_return.head()
Date
2010-01-04 NaN
2010-01-05 0.005900
2010-01-06 -0.018116
2010-01-07 -0.017013
2010-01-08 0.027077
Name: Close, dtype: float64
>>> amzn_daily_return = amzn_daily_return.dropna()
>>> amzn_daily_return.hist(bins=20)
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ok5wGBle-1681366519527)(https://gitcode.net/apachecn/apachecn-ds-zh/-/raw/master/docs/master-pandas/img/00084.jpeg)]
>>> mean = amzn_daily_return.mean()
>>> std = amzn_daily_return.std()
z-score
的绝对值。z-score
是偏离平均值的标准差数:>>> abs_z_score = amzn_daily_return.sub(mean).abs().div(std)
>>> pcts = [abs_z_score.lt(i).mean() for i in range(1,4)]
>>> print('{:.3f} fall within 1 standard deviation. '
'{:.3f} within 2 and {:.3f} within 3'.format(*pcts))
0.787 fall within 1 standard deviation. 0.957 within 2 and 0.985 within 3
默认情况下,pct_change
序列方法计算当前元素和上一个元素之间的百分比变化。 这会将原始股票收盘价转换为每日百分比收益。 返回的序列的第一个元素是缺少值,因为没有先前的价格。
直方图是用于汇总和可视化一维数字数据的奇妙图。 从图中可以明显看出,分布是对称的,但仍然很难确定其是否为正态。 有正式的统计程序可以确定分布的正态性,但是我们仅会发现数据与 68-95-99.7 规则的匹配程度。
步骤 5 为每个观测值计算远离平均值的标准差数,称为z-score
。 此步骤使用方法而不是符号(-
和/
)进行减法和除法。 小于的方法也用于步骤 6 中的符号。
在步骤 6 中取平均值似乎有些奇怪。abs_z_score.lt(1)
表达式的结果是布尔序列。 当布尔值求值为 0 或 1 时,取该序列的平均值将返回True
元素的百分比,这就是我们所希望的。
现在,我们可以将结果数(78.7-95.7-98.5)与 68-95-99.7 规则进行比较,从而更轻松地确定收益的正态性。 百分比与 1 和 3 个标准差的规则有很大差异,我们可以得出结论,亚马逊的每日股票收益率不遵循正态分布。
为了使这一过程自动化,我们可以编写一个函数,该函数在中接收股票数据,并输出日收益率的直方图以及与平均值相差 1、2 和 3 个标准差的百分比。 下面的函数执行此操作,并将方法替换为其对应的符号:
>>> def test_return_normality(stock_data):
close = stock_data['Close']
daily_return = close.pct_change().dropna()
daily_return.hist(bins=20)
mean = daily_return.mean()
std = daily_return.std()
abs_z_score = abs(daily_return - mean) / std
pcts = [abs_z_score.lt(i).mean() for i in range(1,4)]
print('{:.3f} fall within 1 standard deviation. '
'{:.3f} within 2 and {:.3f} within 3'.format(*pcts))
>>> slb = pd.read_csv('data/slb_stock.csv', index_col='Date',
parse_dates=['Date'])
>>> test_return_normality(slb)
0.742 fall within 1 standard deviation. 0.946 within 2 and 0.986 within 3
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wnhCvSCW-1681366519527)(https://gitcode.net/apachecn/apachecn-ds-zh/-/raw/master/docs/master-pandas/img/00085.jpeg)]
query
方法提高布尔索引的可读性布尔索引不一定是读取或写入的最令人愉快的语法,尤其是在使用单行编写复杂过滤器时。 Pandas 通过数据帧的query
方法具有替代的基于字符串的语法,该语法可提供更高的清晰度。
数据帧的query
方法是实验性的,不具备布尔索引功能,因此不应用于生产代码。
此秘籍复制了本章中的较早秘籍“转换 SQL 数据帧的WHERE
子句”,但是利用了query
方法。 此处的目标是为来自警察局或消防局的,薪水在 80 至 12 万美元之间的女性雇员筛选雇员数据。
>>> employee = pd.read_csv('data/employee.csv')
>>> depts = ['Houston Police Department-HPD',
'Houston Fire Department (HFD)']
>>> select_columns = ['UNIQUE_ID', 'DEPARTMENT',
'GENDER', 'BASE_SALARY']
>>> qs = "DEPARTMENT in @depts " \
"and GENDER == 'Female' " \
"and 80000 <= BASE_SALARY <= 120000"
>>> emp_filtered = employee.query(qs)
>>> emp_filtered[select_columns].head()
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-GFFGHc30-1681366519527)(https://gitcode.net/apachecn/apachecn-ds-zh/-/raw/master/docs/master-pandas/img/00086.jpeg)]
传递给query
方法的字符串看起来比普通的 Pandas 代码更像普通的英语。 与depts
一样,可以使用 at 符号(@
)来引用 Python 变量。 通过简单地引用其名称而不用内引号,可在查询名称空间中使用所有数据帧的列名称。 如果需要一个字符串,例如Female
,则需要用引号将其引起来。
query
语法的另一个不错的功能是能够在单个表达式中编写双重不等式,并且能够理解冗长的逻辑运算符and
,or
和not
,而不是像布尔值那样的按位等效索引。
不用手动输入部门名称列表,我们可以以编程方式创建它。 例如,如果我们要按频率查找不是前十名部门成员的所有女性雇员,则可以运行以下代码:
>>> top10_depts = employee.DEPARTMENT.value_counts() \
.index[:10].tolist()
>>> qs = "DEPARTMENT not in @top10_depts and GENDER == 'Female'"
>>> employee_filtered2 = employee.query(qs)
>>> employee_filtered2.head()
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-hDpQgwo8-1681366519528)(https://gitcode.net/apachecn/apachecn-ds-zh/-/raw/master/docs/master-pandas/img/00087.jpeg)]
where
方法保留序列布尔索引必须通过删除不符合条件的所有行来过滤数据集。 除了丢弃所有这些值外,还可以使用where
方法保留它们。where
方法将保留序列或数据帧的大小,并将不符合条件的值设置为缺失或将其替换为其他值。
在此秘籍中,我们通过where
方法布尔条件,在movie
数据集中,针对演员 1 的 Facebook 点赞的最小和最大数目设置上下限。
movie
数据集,将影片标题设置为索引,然后选择actor_1_facebook_likes
列中所有不丢失的值:>>> movie = pd.read_csv('data/movie.csv', index_col='movie_title')
>>> fb_likes = movie['actor_1_facebook_likes'].dropna()
>>> fb_likes.head()
movie_title
Avatar 1000.0
Pirates of the Caribbean: At World's End 40000.0
Spectre 11000.0
The Dark Knight Rises 27000.0
Star Wars: Episode VII - The Force Awakens 131.0
Name: actor_1_facebook_likes, dtype: float64
describe
方法来了解分布情况:>>> fb_likes.describe(percentiles=[.1, .25, .5, .75, .9]) \
.astype(int)
count 4909
mean 6494
std 15106
min 0
10% 240
25% 607
50% 982
75% 11000
90% 18000
max 640000
Name: actor_1_facebook_likes, dtype: int64
>>> fb_likes.hist()
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-XSSMDuXa-1681366519528)(https://gitcode.net/apachecn/apachecn-ds-zh/-/raw/master/docs/master-pandas/img/00088.jpeg)]
>>> criteria_high = fb_likes < 20000
>>> criteria_high.mean().round(2)
.91
where
方法,该方法接受布尔条件。 默认行为是返回与原始大小相同的序列,但将所有False
位置替换为缺少的值:>>> fb_likes.where(criteria_high).head()
movie_title
Avatar 1000.0
Pirates of the Caribbean: At World's End NaN
Spectre 11000.0
The Dark Knight Rises NaN
Star Wars: Episode VII - The Force Awakens 131.0
Name: actor_1_facebook_likes, dtype: float64
where
方法的第二个参数other
允许您控制替换值。 让我们将所有缺失值更改为 20,000:>>> fb_likes.where(criteria_high, other=20000).head()
movie_title
Avatar 1000.0
Pirates of the Caribbean: At World's End 20000.0
Spectre 11000.0
The Dark Knight Rises 20000.0
Star Wars: Episode VII - The Force Awakens 131.0
Name: actor_1_facebook_likes, dtype: float64
where
方法,并将不符合条件的值替换为300
:>>> criteria_low = fb_likes > 300
>>> fb_likes_cap = fb_likes.where(criteria_high, other=20000)\
.where(criteria_low, 300)
>>> fb_likes_cap.head()
movie_title
Avatar 1000.0
Pirates of the Caribbean: At World's End 20000.0
Spectre 11000.0
The Dark Knight Rises 20000.0
Star Wars: Episode VII - The Force Awakens 300.0
Name: actor_1_facebook_likes, dtype: float64
>>> len(fb_likes), len(fb_likes_cap)
(4909, 4909)
>>> fb_likes_cap.hist()
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-SspPsPmW-1681366519528)(https://gitcode.net/apachecn/apachecn-ds-zh/-/raw/master/docs/master-pandas/img/00089.jpeg)]
where
方法再次保留调用对象的大小和形状,并且不修改传递的布尔值为True
的值。 重要的是在步骤 1 中删除丢失的值,因为where
方法最终将在以后的步骤中将其替换为有效数字。
第 2 步中的摘要统计信息为我们提供了一些直观的方法来限定数据上限。 另一方面,第 3 步中的直方图似乎会将所有数据聚集到一个桶中。 对于纯直方图,数据有太多离群值,因此无法绘制出正确的图。where
方法允许我们在数据上放置一个上限和下限,这将导致带有更多可见条的直方图。
Pandas 实际上具有复制此操作的内置方法clip
,clip_lower
和clip_upper
。clip
方法可以同时设置地板和天花板。 我们还检查此替代方法是否产生完全相同的序列,它会执行以下操作:
>>> fb_likes_cap2 = fb_likes.clip(lower=300, upper=20000)
>>> fb_likes_cap2.equals(fb_likes_cap)
True
mask
方法执行与where
方法完全相反的操作。 默认情况下,无论布尔条件为True
,它都会创建缺失值。 从本质上讲,它实际上是掩盖或掩盖数据集中的值。
在此秘籍中,我们将屏蔽 2010 年之后制作的电影数据集的所有行,然后过滤所有缺少值的行。
movie
数据集,将电影标题设置为索引,并创建条件:>>> movie = pd.read_csv('data/movie.csv', index_col='movie_title')
>>> c1 = movie['title_year'] >= 2010
>>> c2 = movie['title_year'].isnull()
>>> criteria = c1 | c2
mask
方法可以使从 2010 年开始制作的带有电影的行中的所有值都丢失。 最初具有title_year
缺失值的所有电影也会被屏蔽:>>> movie.mask(criteria).head()
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-51GtWJ5U-1681366519528)(https://gitcode.net/apachecn/apachecn-ds-zh/-/raw/master/docs/master-pandas/img/00090.jpeg)]
dropna
方法以删除所有值均缺失的行:>>> movie_mask = movie.mask(criteria).dropna(how='all')
>>> movie_mask.head()
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-NMjaD05J-1681366519529)(https://gitcode.net/apachecn/apachecn-ds-zh/-/raw/master/docs/master-pandas/img/00091.jpeg)]
>>> movie_boolean = movie[movie['title_year'] < 2010]
>>> movie_mask.equals(movie_boolean)
False
equals
方法告诉我们它们不相等。 出了点问题。 让我们进行一些完整性检查,看看它们是否具有相同的形状:>>> movie_mask.shape == movie_boolean.shape
True
mask
方法时,它创建了许多缺失值。 缺少值是float
数据类型,因此任何以前的整数列现在都是浮点数。 如果列的数据类型不同,即使值相同,equals
方法也会返回False
。 让我们检查数据类型的相等性,以查看是否发生了这种情况:>>> movie_mask.dtypes == movie_boolean.dtypes
color True
director_name True
num_critic_for_reviews True
duration True
director_facebook_likes True
actor_3_facebook_likes True
actor_2_name True
actor_1_facebook_likes True
gross True
genres True
actor_1_name True
num_voted_users False
cast_total_facebook_likes False
.....
dtype: bool
assert_frame_equal
,您可以使用它检查序列和数据帧的相等性,而无需同时检查数据类型的相等性:from pandas.testing import assert_frame_equal
>>> assert_frame_equal(movie_boolean, movie_mask, check_dtype=False)
默认情况下,mask
方法覆盖缺少值的数据。mask
方法的第一个参数是条件,该条件通常是布尔级数,例如criteria
。 因为mask
方法是从数据帧调用的,所以条件为False
的每一行中的所有值都将变为丢失。 步骤 3 使用此掩码的数据帧删除包含所有缺失值的行。 步骤 4 显示了如何使用布尔索引执行相同的过程。
在数据分析过程中,持续验证结果非常重要。 检查序列和数据帧的相等性是一种非常通用的验证方法。 我们在步骤 4 中的首次尝试产生了意外结果。 在深入研究之前,一些基本的健全性检查(例如确保行和列的数目相同或行和列的名称相同)是很好的检查。
步骤 6 将两个序列的数据类型一起比较。 在这里,我们揭示了数据帧不等效的原因。equals
方法检查值和数据类型是否相同。 步骤 7 中的assert_frame_equal
函数具有许多可用参数,可以通过各种方式测试相等性。 注意,调用assert_frame_equal
后没有输出。 当两个传递的数据帧相等时,此方法返回None
;否则,将引发错误。
让我们比较掩盖和删除丢失的行与布尔索引之间的速度差异。 在这种情况下,布尔索引大约快一个数量级:
>>> %timeit movie.mask(criteria).dropna(how='all')
11.2 ms ± 144 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
>>> %timeit movie[movie['title_year'] < 2010]
1.07 ms ± 34.9 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
第 4 章,“选择数据子集”涵盖了有关通过.iloc
和.loc
索引器选择不同数据子集的各种方法。 这两个索引器都通过整数位置或标签同时选择行和列。 这两个索引器都可以通过布尔索引进行数据选择,即使布尔不是整数也不是标签。
在本秘籍中,我们将为.iloc
和.loc
索引器使用布尔索引过滤行和列。
G
和 IMDB 得分小于4
的所有电影:>>> movie = pd.read_csv('data/movie.csv', index_col='movie_title')
>>> c1 = movie['content_rating'] == 'G'
>>> c2 = movie['imdb_score'] < 4
>>> criteria = c1 & c2
.loc
索引器以过滤行:>>> movie_loc = movie.loc[criteria]
>>> movie_loc.head()
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-IFw598S3-1681366519529)(https://gitcode.net/apachecn/apachecn-ds-zh/-/raw/master/docs/master-pandas/img/00092.jpeg)]
>>> movie_loc.equals(movie[criteria])
True
.iloc
索引器进行相同的布尔索引:>>> movie_iloc = movie.iloc[criteria]
ValueError: iLocation based boolean indexing cannot use an indexable as a mask
values
属性:>>> movie_iloc = movie.iloc[criteria.values]
>>> movie_iloc.equals(movie_loc)
True
>>> criteria_col = movie.dtypes == np.int64
>>> criteria_col.head()
color False
director_name False
num_critic_for_reviews False
duration False
director_facebook_likes False
dtype: bool
>>> movie.loc[:, criteria_col].head()
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Yng032gp-1681366519529)(https://gitcode.net/apachecn/apachecn-ds-zh/-/raw/master/docs/master-pandas/img/00093.jpeg)]
criteria_col
是一个序列,始终有一个索引,因此必须使用基础 ndarray 使其与.iloc
一起使用。 以下产生与步骤 6 相同的结果。>>> movie.iloc[:, criteria_col.values].head()
content_rating
,imdb_score
,title_year
和gross
:>>> cols = ['content_rating', 'imdb_score', 'title_year', 'gross']
>>> movie.loc[criteria, cols].sort_values('imdb_score')
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-JpXLeVoD-1681366519529)(https://gitcode.net/apachecn/apachecn-ds-zh/-/raw/master/docs/master-pandas/img/00094.jpeg)]
.iloc
复制相同的操作,但是您需要获取所有列的整数位置:>>> col_index = [movie.columns.get_loc(col) for col in cols]
>>> col_index
[20, 24, 22, 8]
>>> movie.iloc[criteria.values, col_index]
布尔索引可以用.iloc
和.loc
索引器完成,但要注意的是.iloc
不能传递给序列而是基础ndarray
。 让我们看一下条件序列的一维ndarray
:
>>> a = criteria.values
>>> a[:5]
array([False, False, False, False, False], dtype=bool)
>>> len(a), len(criteria)
(4916, 4916)
数组的长度与序列的长度相同,而序列与电影的数据帧长度相同。 布尔数组的整数位置与数据帧的整数位置对齐,并且过滤器按预期进行。 这些数组也可以与.loc
运算符一起使用,但是它们对于.iloc
是必需的。
步骤 6 和 7 显示了如何按列而不是按行进行过滤。 需要冒号:
来指示所有行的选择。 冒号后面的逗号分隔行和列的选择。 实际上,通过select_dtypes
方法可以更轻松地选择具有整数数据类型的列。
步骤 8 和 9 显示了一种同时对行和列选择进行布尔索引的非常通用和有用的方法。 您只需在行和列选择之间放置一个逗号。 第 9 步使用列表推导式遍历所有所需的列名,以使用索引方法get_loc
查找其整数位置。
实际上,可以将数组和布尔值列表传递给序列对象,这些对象的长度与您要建立索引的数据帧的长度不同。 让我们通过选择第一行和第三行以及第一列和第四列来查看一个示例:
>>> movie.loc[[True, False, True], [True, False, False, True]]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-KetMOKvY-1681366519529)(https://gitcode.net/apachecn/apachecn-ds-zh/-/raw/master/docs/master-pandas/img/00095.jpeg)]
这两个布尔列表的长度与其所索引的轴的长度不同。 列表中未明确指定布尔值的其余行和列将被删除。