跳到主要内容
Python 数据分析库 Pandas 核心操作详解 | 极客日志
Python AI 算法
Python 数据分析库 Pandas 核心操作详解 综述由AI生成 Python 数据分析库 Pandas 提供了强大的数据处理功能,涵盖 Series 和 DataFrame 的核心操作。详细讲解了排序、分组、透视表、多索引(MultiIndex)等关键特性,对比了 NumPy 与 Pandas 的性能差异及缺失值处理机制。内容包括数据读写、索引对齐、算术运算、合并连接以及可视化展示技巧,旨在帮助开发者高效掌握 Pandas 在数据清洗、转换和分析中的应用场景。
月光旅人 发布于 2025/2/6 更新于 2026/5/8 8 浏览Python 数据分析库 Pandas 核心操作详解
在 Python 各个工具包中,最频繁使用的应该就是 Pandas 了。以下以图解的方式介绍 Pandas 中各种常用的操作。
第一部分:Pandas 展示
下表描述了一个在线商店的不同产品线,共有四种不同的产品。与前面的例子不同,它可以用 NumPy 数组或 Pandas DataFrame 表示。
1. 排序
使用 Pandas 按列排序更具可读性。这里 argsort(a[:, 1]) 计算使 a 的第二列按升序排序的排列,然后 a[...] 相应地对 a 的行重新排序。Pandas 可以一步完成。
2. 按多列排序
如果我们需要使用 weight 列来对价格列进行排序,情况会变得更糟。这里有几个例子来说明我们的观点:
在 NumPy 中,我们先按重量排序,然后再按价格排序。稳定排序算法保证第一次排序的结果不会在第二次排序期间丢失。NumPy 还有其他实现方法,但没有一种方法像 Pandas 那样简单优雅。
3. 添加一列
使用 Pandas 添加列在语法和架构上要好得多。下面的例子展示了如何操作:
Pandas 不需要像 NumPy 那样为整个数组重新分配内存;它只是添加了对新列的引用,并更新了列名的 registry。
4. 快速元素搜索
在 NumPy 数组中,即使你查找的是第一个元素,你仍然需要与数组大小成正比的时间来查找它。使用 Pandas,你可以索引你期望被查询最多的列,并将搜索时间减少到一个常量。
index 列有以下限制:
它需要内存和时间来构建。
它是只读的(需要在每次追加或删除操作后重新构建)。
这些值不需要是唯一的,但是只有当元素是唯一的时候加速才会发生。
它需要预热:第一次查询比 NumPy 稍慢,但后续查询明显快得多。
5. 按列连接(join)
如果你想从另一张表中获取基于同一列的信息,NumPy 几乎没有任何帮助。Pandas 更好,特别是对于 1:n 的关系。
Pandas join 具有所有熟悉的'内'、'左'、'右'和'全外部'连接模式。
6. 按列分组
数据分析中的另一个常见操作是按列分组。例如,要获得每种产品的总销量,你可以这样做:
除了 sum 之外,Pandas 还支持各种聚合函数:mean、max、min、count 等。
7. 数据透视表
Pandas 最强大的功能之一是'枢轴'表。这有点像将多维空间投影到二维平面上。
虽然用 NumPy 当然可以实现它,但这个功能没有开箱即用,尽管它存在于所有主要的关系数据库和电子表格应用程序 (Excel, WPS) 中。
Pandas 用 df.pivot_table 将分组和旋转结合在一个工具中。
简而言之,NumPy 和 Pandas 的两个主要区别如下:
现在,让我们看看这些功能是否以性能损失为代价。
8. Pandas 速度
我在 Pandas 的典型工作负载上对 NumPy 和 Pandas 进行了基准测试:5-100 列,10³- 10⁸行,整数和浮点数。下面是 1 行和 1 亿行的结果:
看起来在每一次操作中,Pandas 都比 NumPy 慢!
当列数增加时,情况不会改变 (可以预见)。至于行数,依赖关系 (在对数尺度下) 如下所示:
对于小数组 (少于 100 行),Pandas 似乎比 NumPy 慢 30 倍,对于大数组 (超过 100 万行) 则慢 3 倍。
怎么可能呢?也许是时候提交一个功能请求,建议 Pandas 通过 df.column.values.sum() 重新实现 df.column.sum() 了?这里的 values 属性提供了访问底层 NumPy 数组的方法,性能提升了 3 ~ 30 倍。
答案是否定的。Pandas 在这些基本操作方面非常缓慢,因为它正确地处理了缺失值。Pandas 需要 NaNs (not-a-number) 来实现所有这些类似数据库的机制,比如分组和旋转,而且这在现实世界中是很常见的。在 Pandas 中,我们做了大量工作来统一所有支持的数据类型对 NaN 的使用。根据定义 (在 CPU 级别上强制执行),nan+anything 会得到 nan。所以
>>> np.sum ([ , np.nan, ])
nan
1
2
>>> pd.Series([1 , np.nan, 2 ]).sum ()
3.0
一个公平的比较是使用 np.nansum 代替 np.sum,用 np.nanmean 而不是 np.mean 等等。突然间……
对于超过 100 万个元素的数组,Pandas 的速度是 NumPy 的 1.5 倍。对于较小的数组,它仍然比 NumPy 慢 15 倍,但通常情况下,无论操作在 0.5 ms 还是 0.05 ms 内完成都没有太大关系——无论如何它都是快速的。
最重要的是,如果您 100% 确定列中没有缺失值,则使用 df.column.values.sum() 而不是 df.column.sum() 可以获得 x3-x30 的性能提升。在存在缺失值的情况下,Pandas 的速度相当不错,甚至在巨大的数组 (超过 10 个同质元素) 方面优于 NumPy。
第二部分:Series 和 Index Series 是 NumPy 中的一维数组,是表示其列的 DataFrame 的基本组成部分。尽管与 DataFrame 相比,它的实际重要性正在降低 (你可以在不知道 Series 是什么的情况下完美地解决许多实际问题),但如果不首先学习 Series 和 Index,你可能很难理解 DataFrame 是如何工作的。
在内部,Series 将值存储在普通的 NumPy vector 中。因此,它继承了它的优点 (紧凑的内存布局、快速的随机访问) 和缺点 (类型同质、缓慢的删除和插入)。最重要的是,Series 允许使用类似于字典的结构 index 通过 label 访问它的值。标签可以是任何类型 (通常是字符串和时间戳)。它们不必是唯一的,但唯一性是提高查找速度所必需的,许多操作都假定唯一性。
如你所见,现在每个元素都可以通过两种替代方式寻址:通过 label (=使用索引) 和通过 position (=不使用索引):
按'位置'寻址有时被称为'位置索引',这只是增加了混淆。
S[2:3] 不是解决元素 2 最方便的方式
如果名称恰好是整数,s[1:3] 就会产生歧义。它可能意味着名称 1 到 3 包含或位置索引 1 到 3 不包含。
为了解决这些问题,Pandas 还有两种'风格'的方括号,你可以在下面看到:
.loc 总是使用标号,并且包含间隔的两端。
.iloc 总是使用'位置索引'并排除右端。
使用方括号而不是圆括号的目的是为了访问 Python 的切片约定:你可以使用单个或双冒号,其含义是熟悉的 start:stop:step。像往常一样,缺少开始 (结束) 意味着从序列的开始 (到结束)。step 参数允许使用 s.iloc[::2] 引用偶数行,并使用 s['Paris':'Oslo':-1] 以相反的顺序获取元素。
它们还支持布尔索引 (使用布尔数组进行索引),如下图所示:
你可以在下图中看到它们如何支持 fancy indexing (用整数数组进行索引):
Series 最糟糕的地方在于它的视觉表现:出于某种原因,它没有一个很好的富文本外观,所以与 DataFrame 相比,它感觉像是二等公民:
我对这个 Series 做了补丁,让它看起来更好,如下所示:
垂直线表示这是一个 Series,而不是一个 DataFrame。Footer 在这里被禁用了,但它可以用于显示 dtype,特别是分类类型。
您还可以使用 pdi.sidebyside(obj1, obj2, …) 并排显示多个 Series 或 dataframe:
pdi(代表 pandas illustrated) 是 github 上的一个开源库,具有本文所需的这个和其他功能。要使用它,就要写
pip install pandas-illustrated
索引 (Index) 负责通过标签获取元素的对象称为 index。它非常快:无论你有 5 行还是 50 亿行,你都可以在常量时间内获取一行数据。
指数是一个真正的多态生物。默认情况下,当创建一个没有索引的序列 (或 DataFrame) 时,它会初始化为一个惰性对象,类似于 Python 的 range()。和 range 一样,几乎不使用任何内存,并且与位置索引无法区分。让我们用下面的代码创建一个包含一百万个元素的序列:
>>> s = pd.Series(np.zeros(10 **6 ))
>>> s.index
RangeIndex(start=0 , stop=1000000 , step=1 )
>>> s.index.memory_usage()
128
现在,如果我们删除一个元素,索引隐式地转换为类似于 dict 的结构,如下所示:
>>> s.drop(1 , inplace=True )
>>> s.index
Int64Index([ 0 , 2 , 3 , 4 , 5 , 6 , 7 ,
...
999993 , 999994 , 999995 , 999996 , 999997 , 999998 , 999999 ],
dtype='int64' , length=999999 )
>>> s.index.memory_usage()
7999992
该结构消耗 8Mb 内存!为了摆脱它,回到轻量级的类 range 结构,添加如下代码:
>>> s.reset_index(drop=True , inplace=True )
>>> s.index
RangeIndex(start=0 , stop=999999 , step=1 )
>>> s.index.memory_usage()
128
如果你不熟悉 Pandas,你可能想知道为什么 Pandas 自己没有做到这一点?好吧,对于非数字标签,有一点很明显:为什么 (以及如何) Pandas 在删除一行后,会重新标记所有后续的行?对于数值型标签,答案就有点复杂了。
首先,正如我们已经看到的,Pandas 允许您纯粹按位置引用行,因此,如果您想在删除第 3 行之后定位第 5 行,则可以无需重新索引 (这就是 iloc 的作用)。
其次,保留原始标签是一种与过去时刻保持联系的方法,就像'保存游戏'按钮一样。假设您有一个 100x1000000 的大表,需要查找一些数据。你正在一个接一个地进行几次查询,每次都缩小了搜索范围,但只查看了一小部分列,因为同时查看数百个字段是不切实际的。现在您已经找到感兴趣的行,您希望在原始表中查看有关它们的所有信息。数字索引可以帮助您立即获得它,而无需任何额外的努力。
一般来说,在索引中保持值的唯一性是一个好主意。例如,在索引中存在重复值时,查找速度不会得到提升。Pandas 不像关系型数据库那样有'唯一约束'(该功能仍然是实验性的),但它有检查索引中的值是否唯一的函数,并以各种方式消除重复。
有时,一列不足以唯一标识一行。例如,同一个名字的城市有时会碰巧出现在不同的国家,甚至是同一个国家的不同地区。所以 (城市,州) 是一个比城市更好的标识一个地方的候选者。在数据库中,这被称为'复合主键'。在 Pandas 中,它被称为多索引 (参见下面的第 4 部分),索引中的每一列都被称为'级别'。
索引的另一个重要特性是不可变。与 DataFrame 中的普通列不同,你不能就地更改它。索引中的任何更改都涉及从旧索引中获取数据,修改它,并将新数据作为新索引重新附加。通常情况下,它是透明的,这就是为什么不能直接写 df.City.name = 'city',而必须写一个不那么明显的 df.rename(columns={'A': 'A'}, inplace=True)
Index 有一个名称 (在 MultiIndex 的情况下,每个级别都有一个名称)。不幸的是,这个名称在 Pandas 中没有得到充分使用。一旦你在索引中包含了这一列,就不能再使用 df 了。不再使用列名表示法,并且必须恢复为可读性较差的 df.loc。指数还是更通用的 df.loc。对于多索引,情况更糟。一个明显的例外是 df.Merge - 你可以通过名称指定要合并的列,无论它是否在索引中。
同样的索引机制用于标记 dataframes 的行和列,以及序列。
按值查找元素 Series 内部由一个 NumPy 数组和一个类似数组的结构 index 组成,如下所示:
Index 提供了一种通过标签查找值的方便方法。那么如何通过值查找标签呢?
s.index[s.tolist().find(x)]
s.index[np.where(s.values==x)[0 ][0 ]]
我编写了 find() 和 findall() 两个简单的封装器,它们运行速度快 (因为它们会根据序列的大小自动选择实际的命令),而且使用起来更方便。代码如下所示:
>>> import pdi
>>> pdi.find(s, 2 )
'penguin'
>>> pdi.findall(s, 4 )
Index(['cat' , 'dog' ], dtype='object' )
缺失值 Pandas 开发人员特别关注缺失值。通常,你通过向 read_csv 提供一个标志来接收一个带有 NaNs 的 dataframe。否则,可以在构造函数或赋值运算符中使用 None(尽管不同数据类型的实现略有不同,但它仍然有效)。这张有助于解释这个概念:
你可以使用 NaNs 做的第一件事是了解你是否有 NaNs。从上图可以看出,isna() 生成了一个布尔数组,而 .sum() 给出了缺失值的总数。
现在你知道了它们的存在,你可以选择用常量值填充它们或通过插值来一次性删除它们,如下所示:
另一方面,你可以继续使用它们。大多数 Pandas 函数会很高兴地忽略缺失值,如下图所示:
更高级的函数 (median、rank、quantile 等) 也可以做到这一点。
如果索引中存在非唯一值,则结果不一致。不要对索引不唯一的序列使用算术运算。
比较 比较有缺失值的数组可能会比较棘手。下面是一个例子:
>>> np.all (pd.Series([1. , None , 3. ]) ==
pd.Series([1. , None , 3. ]))
False
>>> np.all (pd.Series([1 , None , 3 ], dtype='Int64' ) ==
pd.Series([1 , None , 3 ], dtype='Int64' ))
True
>>> np.all (pd.Series(['a' , None , 'c' ]) ==
pd.Series(['a' , None , 'c' ]))
False
为了正确地比较 nan,需要用数组中一定没有的元素替换 nan。例如,使用 -1 或 ∞:
>>> np.all (s1.fillna(np.inf) == s2.fillna(np.inf))
True
或者,更好的做法是使用 NumPy 或 Pandas 的标准比较函数:
>>> s = pd.Series([1. , None , 3. ])
>>> np.array_equal(s.values, s.values, equal_nan=True )
True
>>> len (s.compare(s)) == 0
True
这里,compare 函数返回一个差异列表 (实际上是一个 DataFrame),array_equal 则直接返回一个布尔值。
当比较混合类型的 DataFrames 时,NumPy 比较失败 (issue #19205),而 Pandas 工作得很好。如下所示:
>>> df = pd.DataFrame({'a' : [1. , None , 3. ], 'b' : ['x' , None , 'z' ]})
>>> np.array_equal(df.values, df.values, equal_nan=True )
TypeError
<...>
>>> len (df.compare(df)) == 0
True
追加、插入、删除 虽然 Series 对象被认为是 size 不可变的,但它可以在原地追加、插入和删除元素,但所有这些操作都是:
慢,因为它们需要为整个对象重新分配内存和更新索引。
非常不方便。
第二种删除值的方法 (通过 drop) 比较慢,并且在索引中存在非唯一值时可能会导致复杂的错误。
Pandas 有 df.insert 方法,但它只能将列 (而不是行) 插入到 dataframe 中 (并且对 series 不起作用)。
添加和插入的另一种方法是使用 iloc 对 DataFrame 进行切片,应用必要的转换,然后使用 concat 将其放回。我实现了一个名为 insert 的函数,可以自动执行这个过程:
注意 (就像在 df.insert 中一样) 插入位置由位置 0<=i<=len(s) 指定,而不是索引中元素的标签。如下所示:
要按元素的名称插入,可以合并 pdi。用 pdi 查找。插入,如下所示:
请注意,unlikedf.insert、pdi.insert 返回一个副本,而不是原地修改 Series/DataFrame
统计数据 Pandas 提供了全方位的统计函数。它们可以让您了解百万元素序列或 DataFrame 中的内容,而无需手动滚动数据。
所有 Pandas 统计函数都会忽略 NaNs,如下所示:
注意,Pandas std 给出的结果与 NumPy std 不同,如下所示:
>>> pd.Series([1 , 2 ]).std()
0.7071067811865476
>>> pd.Series([1 , 2 ]).values.std()
0.5
这是因为 NumPy std 默认使用 N 作为分母,而 Pandas std 默认使用 N-1 作为分母。两个 std 都有一个名为 ddof (delta degrees of freedom) 的参数,NumPy 默认为 0,Pandas 默认为 1,这可以使结果一致。N-1 是你通常想要的值 (在均值未知的情况下估计样本的偏差)。这里有一篇维基百科的文章详细介绍了贝塞尔的修正。
由于序列中的每个元素都可以通过标签或位置索引访问,因此 argmin (argmax) 有一个姐妹函数 idxmin (idxmax),如下图所示:
std: 样本标准差
var,无偏方差
sem,均值的无偏标准误差
quantile 分位数,样本分位数 (s.quantile(0.5)≈s.median())
mode 是出现频率最高的值
nlargest 和 nsmallest,按出现顺序排列
diff,第一个离散差分
cumsum 和 cumprod、cumulative sum 和 product
cummin 和 cummax,累积最小值和最大值
以及一些更专业的统计函数:
pct_change,当前元素与前一个元素之间的变化百分比
skew 偏态,无偏态 (三阶矩)
kurt 或 kurtosis,无偏峰度 (四阶矩)
cov、corr 和 autocorr、协方差、相关和自相关
rolling 滚动窗口、加权窗口和指数加权窗口
重复数据 drop_duplicates 和 duplication 可以保留最后一次出现的副本,而不是第一次出现的副本。
请注意,s.a.unique() 比 np.unique 快。唯一性 (O(N) vs O(NlogN)),它会保留顺序,而不会返回排序结果。unique。
缺失值被视为普通值,有时可能会导致令人惊讶的结果。
如果你想排除 nan,需要显式地这样做。在这个例子中,是 s.loc[dropna().is_unique == True]。
s.is_monotonic_increasing()
s.is_monotonic_decreasing()
s._strict_monotonic_increasing()
s._string_monotonic_decreasing()
s.is_monotonic()。这是意料之外的,出于某种原因,这是 s.is_monotonic_increasing()。它只对单调递减序列返回 False。
分组 在数据处理中,一个常见的操作是计算一些统计量,不是针对整个数据集,而是针对其中的某些组。第一步是通过提供将一系列 (或一个 dataframe) 分解为组的标准来定义一个'智能对象'。这个 智能对象 没有立即的表示,但可以像 Series 一样查询它,以获得每个组的某个属性,如下图所示:
在这个例子中,我们根据数值除以 10 的整数部分将序列分成三组。对于每个组,我们请求每个组中元素的和、元素的数量以及平均值。
除了这些聚合函数,您还可以根据特定元素在组中的位置或相对值访问它们。如下所示:
你也可以使用 g.ag(['min', 'max']) 一次调用计算多个函数,或者使用 g.describe() 一次显示一堆统计函数。
如果这些还不够,你还可以通过自己的 Python 函数传递数据。它可以是:
一个函数 f,它接受一个组 x(一个 Series 对象) 并生成一个值 (例如 sum()) 与 g.apply(f) 一起使用。
一个函数 f,它接受一个组 x(一个 Series 对象),并与 g.transform(f) 生成一个大小与 x 相同的 Series 对象 (例如 cumsum())。
在上面的例子中,输入数据是有序的。groupby 不需要这样做。实际上,如果分组中的元素不是连续存储的,它也同样有效,因此它更接近于 collections.defaultdict,而不是 itertools.groupby。它总是返回一个没有重复项的索引。
与 defaultdict 和关系数据库 GROUP BY 子句不同,Pandas groupby 按组名对结果进行排序。可以用 sort=False 来禁用它。
免责声明:实际上,g.apply(f) 比上面描述的更通用:
如果 f(x) 返回与 x 大小相同的序列,它可以模拟 transform
如果 f(x) 返回一系列不同大小或不同的 dataframe,则会得到一个具有相应多索引的序列。
但文档警告说,这些使用方法可能比相应的 transform 和 agg 方法慢,所以要小心。
第三部分:DataFrames Pandas 的主要数据结构是 DataFrame。它将一个二维数组与它的行和列的标签捆绑在一起。它由一系列对象组成 (具有共享索引),每个对象表示一列,可能具有不同的 dtype。
读写 CSV 文件 构造 DataFrame 的一种常用方法是读取 csv(逗号分隔值) 文件,如下图所示:
pd.read_csv() 函数是一个完全自动化且可疯狂定制的工具。如果你只想学习 Pandas 的一件事,那就学习使用 read_csv——它会有回报的:)
因为 CSV 没有严格的规范,所以有时需要一些试错才能正确地阅读它。read_csv 最酷的地方在于它会自动检测很多东西:
与其他自动化一样,你最好确保它做了正确的事情。如果在 Jupyter 单元中简单地编写 df 的结果碰巧太长 (或太不完整),您可以尝试以下操作:
df.head(5) 或 df[:5] 显示前 5 行
df.dtypes 返回列的类型
df.shape 返回行数和列数
df.info() 汇总所有相关信息
将一列或几列设置为索引是一个好主意。下图展示了这个过程:
算术运算按索引对齐
它使按该列进行的查找更快,等等。
所有这些都是以较高的内存消耗和不太明显的语法为代价的。
构建 DataFrame 另一种选择是从内存中已经存储的数据中构建一个 dataframe。它的构造函数非常全能,可以转换 (或包装) 任何类型的数据:
在第一种情况下,在没有行标签的情况下,Pandas 用连续的整数标记行。在第二种情况下,它对行和列都进行了相同的操作。为 Pandas 提供列的名称总是一个好主意,而不是整数标签 (使用 columns 参数),有时也可以提供行 (使用 index 参数,尽管 rows 听起来可能更直观)。这张会有帮助:
不幸的是,无法在 DataFrame 构造函数中为索引列设置名称,所以唯一的选择是手动指定,例如,df.index.name = '城市名称'
下一种方法是使用 NumPy 向量组成的字典或二维 NumPy 数组构造一个 DataFrame:
请注意,在第二种情况下,人口数量的值被转换为浮点数。实际上,它在之前的构建 NumPy 数组时就发生过。这里需要注意的另一件事是,从 2D NumPy 数组构建 dataframe 默认是视图。这意味着改变原始数组中的值会改变 dataframe,反之亦然。另外,它节省了内存。
第一种情况 (NumPy 向量组成的字典) 也可以启用这种模式,设置 copy=False 即可。不过,它非常脆弱。简单的操作就可以把它变成副本而不需要通知。
另外两个 (不太有用的) 创建 DataFrame 的选项是:
从一个 dict 列表 (其中每个 dict 表示一行,其键是列名,其值是相应的单元格值)
来自由 Series 组成的 dict(其中每个 Series 表示一列;默认情况下,可以让它返回一个 copy=False 的视图)。
如果你'动态'注册流数据,最好的选择是使用列表的 dict 或列表的列表,因为 Python 会透明地在列表末尾预分配空间,以便快速追加。NumPy 数组和 Pandas dataframes 都不能做到这一点。另一种可能性 (如果你事先知道行数) 是用 DataFrame(np.zeros) 之类的东西手动预分配内存。
DataFrames 的基本操作 DataFrame 最好的地方 (在我看来) 是你可以:
轻松访问其列,如 d.area 返回列值 (或者 df['Area']——适用于包含空格的列名)
将列作为自变量进行操作,例如使用 afterdf.population /= 10**6 人口以百万计存储,下面的命令根据现有列中的值创建一个名为 density 的新列。更多信息见下图:
注意,创建新列时,即使列名中不包含空格,也必须使用方括号。
此外,你可以对不同 dataframe 中的列使用算术操作,只要它们的行具有有意义的标签,如下所示:
索引 DataFrames 正如我们在本系列中已经看到的,普通的方括号不足以满足索引的所有需求。你不能通过名称访问行,不能通过位置索引访问不相交的行,你甚至不能引用单个单元格,因为 df['x', 'y'] 是为多索引保留的!
为了满足这些需求,dataframes,就像 series 一样,有两种可选的索引模式:按标签索引的 loc 和按位置索引的 iloc。
在 Pandas 中,引用多行/多列是一个副本,而不是视图。但它是一种特殊的复制,允许赋值作为一个整体:
df.loc['a']=10 works (一行作为一个整体是一个可写的)
df.loc['a']['A']=10 works (元素访问传播到原始 df)
df.loc['a':'b'] = 10 works (assigning to a subar 将整个作品赋值给一个子数组)
df.loc['a':'b']['A'] = 10 doesn't (对其元素赋值不会).
在最后一种情况下,该值只会被设置在切片的副本上,而不会反映在原始 df 上 (会相应地显示一个警告)。
你想要改变原始的 df。然后使用 df.loc['a':'b', 'a'] = 10
你故意创建了一个副本,然后想要处理这个副本:df1 = df.loc['a': 'b']; df1['A']=10 # SettingWithCopy warning 要在这种情况下消除警告,请使其成为一个真正的副本:df1 = df.loc['A': 'b'].copy(); df1[A] = 10
Pandas 还支持一种方便的 NumPy 语法来进行布尔索引。
因为可能有多行匹配条件,所以 loc 返回一个序列。要从中得到标量值,你可以使用:
float(s) 或更通用的 s.item(),除非序列中只有一个值,否则都会引发 ValueError
S.iloc[0],仅在没有找到时引发异常;此外,它是唯一支持赋值的函数:df[...].iloc[0] = 100,但当你想修改所有匹配时,肯定不需要它:df[...] = 100。
df.query('name =="Vienna")
df.query('population>1e6 and area<1000') 它们更短,适合多索引,并且逻辑操作符优先于比较操作符 (=需要更少的括号),但它们只能按行过滤,并且不能通过它们修改 Dataframe。
几个第三方库允许你使用 SQL 语法直接查询 dataframe (duckdb),或者通过将 dataframe 复制到 SQLite 并将结果包装回 Pandas objects (pandasql) 来间接查询 dataframe。不出所料,直接法更快。
DataFrame 算术 你可以对 dataframes、series 和它们的组合应用普通操作,如加、减、乘、除、求模、幂等。
在 dataframe 和 Series 之间的混合操作中,Series(天知道为什么) 表现得 (和广播) 像一个行向量,并相应地对齐:
可能是为了与列表和一维 NumPy 向量保持一致 (它们不按标签对齐,并被认为是一个简单的二维 NumPy 数组的 DataFrame):
因此,在不太幸运 (也是最常见的!) 的情况下,将一个 dataframe 除以列向量序列,你必须使用方法而不是操作符,如下所示:
由于这个有问题的决定,每当你需要在 dataframe 和列式序列之间执行混合操作时,你必须在文档中查找它 (或记住它):
结合 DataFrames Pandas 有三个函数,concat、merge 和 join,它们做同样的事情:将来自多个 dataframe 的信息合并为一个。但是每个工具的实现方式都略有不同,因为它们是为不同的用例量身定制的。
垂直叠加 这可能是将两个或多个 dataframe 合并为一个的最简单方法:您获取第一个 dataframe 中的行,并将第二个 dataframe 中的行追加到底部。为了使其工作,这两个 dataframe 需要 (大致) 具有相同的列。这类似于 NumPy 中的 vstack,正如你在图像中所看到的:
索引中有重复的值是不好的。你可能会遇到各种各样的问题 (参见下面的 drop 示例)。即使你不关心索引,也要尽量避免出现重复的值:
要么使用 reset_index=True 参数
调用 df.reset_index(drop=True) 将行从 0 重新索引到 len(df)-1,
使用 keys 参数可以解决 MultiIndex 的二义性 (见下文)。
如果 dataframe 的列不能完美匹配 (不同的顺序在这里不计算在内),Pandas 可以取列的交集 (默认值 kind='inner ') 或插入 nan 来标记缺失值 (kind='outer'):
水平叠加 concat 也可以执行'水平'堆叠 (类似于 NumPy 中的 hstack):
join 比 concat 更可配置:特别是,它有五种连接模式,而 concat 只有两种。详情请参阅下面的'1:1 关系连接'部分。
基于多指数的数据叠加 如果行标签和列标签一致,concat 可以执行与垂直堆叠类似的多索引 (就像 NumPy 中的 dstack):
如果行和/或列部分重叠,Pandas 将相应地对齐名称,这很可能不是你想要的。下面的图表可以帮助你将这个过程可视化:
一般来说,如果标签重叠,这意味着 dataframe 在某种程度上彼此相关,实体之间的关系最好使用关系数据库的术语来描述。
1:1 连接的关系 当同一组对象的信息存储在几个不同的 DataFrame 中时,你希望将它们合并为一个 DataFrame。
它所做的第一件事是丢弃索引中的任何内容。然后执行联结操作。最后,将结果从 0 重新编号为 n-1。
如果列已经在索引中,则可以使用 join(这只是 merge 的别名,将 left_index 或 right_index 设置为 True,并设置不同的默认值)。
从这个简化的例子中可以看出 (参见上面的全外连接),与关系型数据库相比,Pandas 对行顺序的处理相当轻松。左外联结和右外联结比内外联结更容易预测 (至少在需要合并的列中有重复值之前是这样)。因此,如果你想保证行顺序,就必须显式地对结果进行排序。
1:n 连接的关系 这是数据库设计中使用最广泛的关系,表 A 中的一行 (例如'State') 可以与表 B 中的几行 (例如城市) 相关联,但表 B 中的每一行只能与表 A 中的一行相关联 (即一个城市只能处于一种状态,但一个状态由多个城市组成)。
就像 1:1 关系一样,在 Pandas 中连接一对 1:n 相关的表,你有两种选择。如果要合并的列或者不在索引中,并且可以丢弃碰巧在两张表的索引中都存在的列,则使用 merge。下面的例子会有所帮助:
正如我们已经看到的,merge 对行顺序的处理没有 Postgres 严格:所有声明的语句,保留的键顺序只适用于 left_index=True 和/或 right_index=True(这就是 join 的别名),并且只在要合并的列中没有重复值的情况下。这就是为什么 join 有一个 sort 参数。
现在,如果要合并的列已经在右侧 DataFrame 的索引中,可以使用 join(或者 merge with right_index=True,这是完全相同的事情):
这次 Pandas 保留了左 DataFrame 的索引值和行顺序。
注意:注意,如果第二个表有重复的索引值,你最终将在结果中得到重复的索引值,即使左表索引是唯一的!
有时,合并的 dataframe 具有同名的列。merge 和 join 都有解决二义性的方法,但语法略有不同 (默认情况下 merge 会用 _x, _y 来解决,而 join 会抛出异常),如下图所示:
合并非索引列上的连接,连接要求列被索引
merge 丢弃左 DataFrame 的索引,join 保留它
默认情况下,merge 执行内联结,join 执行左外联结
合并不保持行顺序
Join 可以保留它们 (有一些限制)
join 是合并的别名,left_index=True 和/或 right_index=True
多个连接 如上所述,当对两个 dataframe(如 df.join(df1)) 运行 join 时,它充当了合并的别名。但是 join 也有一个 multiple join 模式,它只是 concat(axis=1) 的别名。
它没有提供解析重复列的方法
它只适用于 1:1 关系 (索引到索引连接)。
因此,多个 1:n 关系应该一个接一个地连接。仓库 panda-illustrated 也提供了一个辅助方法,如下所示:
pdi.join 是 Join 的一个简单包装器,它接受 on、how 和后缀参数,以便您可以在一个命令中进行多个联结。与原始的关联操作一样,关联的是属于第一个 DataFrame 的列,其他 DataFrame 根据它们的索引进行关联操作。
插入和删除 由于 DataFrame 是列的集合,因此将这些操作应用到行上比应用到列上更容易。例如,插入一列总是在原地完成,而插入一行总是会生成一个新的 DataFrame,如下所示:
删除列通常不用担心,除了 del df['D'] 和 del df.D 则没有 (Python 级别的限制)。
使用 drop 删除行非常慢,如果原始标签不是唯一的,可能会导致复杂的 bug。下图将帮助解释这个概念:
一种解决方案是使用 ignore_index=True,它告诉 concat 在连接后重置行名称:
在这种情况下,将 name 列设置为索引将有所帮助。但对于更复杂的滤波器,它不会。
另一种快速、通用、甚至可以处理重复行名的解决方案是索引而不是删除。为了避免显式地否定条件,我写了一个 (只有一行代码的) 自动化程序。
分组 这个操作已经在 Series 部分详细描述过了。但是 DataFrame 的 groupby 在此基础上有一些特定的技巧。
首先,你可以使用一个名称来指定要分组的列,如下图所示:
如果没有 as_index=False, Pandas 将进行分组的列指定为索引。如果这不是我们想要的,可以使用 reset_index() 或指定 as_index=False。
通常,数据框中的列比你想在结果中看到的多。默认情况下,Pandas 会对所有远端可求和的东西进行求和,因此你需要缩小选择范围,如下所示:
注意,当对单个列求和时,你将得到一个 Series 而不是 DataFrame。如果出于某种原因,你想要一个 DataFrame,你可以:
使用双括号:df.groupby('product')[['quantity']].sum()
显式转换:df.groupby('product')['quantity'].sum().to_frame()
切换到数值索引也会创建一个 DataFrame:
df.groupby('product', as_index=False)['quantity'].sum()
df.groupby('product')['quantity'].sum().reset_index()
但是,尽管外观不寻常,Series 的行为就像 DataFrames 一样,所以可能对 pdi.patch_series_repr() 进行'整容'就足够了。
显然,不同的列在分组时表现不同。例如,对数量求和完全没问题,但对价格求和就没有意义了。使用。agg 可以为不同的列指定不同的聚合函数,如下图所示:
有时,预定义的函数不足以产生所需的结果。例如,在平均价格时使用权重会更好。你可以为此提供一个自定义函数。与 Series 不同的是,该函数可以访问组中的多个列 (它以子 dataframe 作为参数),如下所示:
不幸的是,你不能把预定义的聚合和几个列级的自定义函数结合在一起,比如上面的那个,因为 agg 只接受单列级的用户函数。单列范围的用户函数唯一可以访问的是索引,这在某些情况下很方便。例如,那天香蕉以 5 折的价格出售,如下图所示:
为了从自定义函数中访问 group by 列的值,它事先已经包含在索引中。
通常,定制最少的函数可以获得最好的性能。为了提高速度:
通过 g.apply() 实现多列范围的自定义函数
通过 g.agg() 实现单列范围的自定义函数 (支持使用 Cython 或 Numba 进行加速)
预定义函数 (Pandas 或 NumPy 函数对象,或其字符串名称)。
数据透视表 (pivot table) 是一种有用的工具,通常与分组一起使用,从不同的角度查看数据。
旋转和反旋转 假设你有一个变量 a,它依赖于两个参数 i 和 j。有两种等价的方法将它表示为一个表:
当数据是'密集的'(当有很少的 0 元素) 时,short 格式更合适,而当数据是'稀疏的'(大多数元素为 0,可以从表中省略) 时,long 格式更好。当有两个以上的参数时,情况会变得更加复杂。
当然,应该有一种简单的方法来转换这些格式。Pandas 为此提供了一个简单方便的解决方案:数据透视表。
作为一个不那么抽象的例子,考虑下表中的销售数据。有两个客户购买了两种产品的指定数量最初,这个数据是短格式的。要将之转换为 长格式,请使用 df.pivot:
该命令丢弃了与操作无关的任何信息 (索引、价格),并将来自三个请求列的信息转换为长格式,将客户名称放入结果的索引中,将产品名称放入列中,将销售数量放入 DataFrame 的 body 中。
至于相反的操作,你可以使用 stack。它将索引和列合并到 MultiIndex 中:
Pivot 丢失了结果的 body 的名称信息,因此无论是 stack 还是 melt,我们都必须提醒 pandas quantity 列的名称。
分组值然后旋转结果的做法是如此常见,以至于 groupby 和 pivot 被捆绑在一个专用的函数 (以及相应的 DataFrame 方法) 数据透视表中:
如果没有 columns 参数,它的行为与 groupby 类似
当没有重复的行进行分组时,它的工作原理与 pivot 类似
否则,它会进行分组和旋转
aggfunc 参数控制哪一个聚合函数应该用于分组行 (默认为均值)。
为了方便,pivot_table 可以计算小计和合计:
一旦创建,pivot 表就变成了一个普通的 DataFrame,因此可以使用前面描述的标准方法查询它。
当使用多索引时,透视表特别方便。我们已经见过很多 Pandas 函数返回多索引 DataFrame 的例子。让我们仔细看看。
第四部分:MultiIndex 对于从未听说过 Pandas 的人来说,多索引(MultiIndex)最直接的用法是使用第二个索引列作为第一个索引列的补充,以唯一地标识每行。例如,为了消除来自不同州的城市的歧义,州的名字通常附加在城市的名字后面。例如,在美国大约有 40 个 springfield(在关系型数据库中,它被称为复合主键)。
你可以在从 CSV 解析 DataFrame 后指定要包含在索引中的列,也可以立即作为 read_csv 的参数。
您还可以使用 append=True 将现有级别添加到多重索引,如下图所示:
另一个更典型的用例是表示多维。当你有一组具有特定属性的对象或者随着时间的推移而演变的对象时。例如:
社会学调查的结果
Titanic 数据集
历史天气观测
锦标赛排名的年表。
这也被称为'面板数据',Pandas 就是以此命名的。
年形成一个 (几乎连续的) 维度
城市名称沿第二条排列
第三个州的名字
特定的城市属性 ('人口'、'密度'、'面积'等) 在第四个维度上起到了'刻度线'的作用。
为了给对应列的尺寸名称留出空间,Pandas 将整个标题向上移动:
分组 关于多重索引需要注意的第一件事是,它并不按照它可能出现的情况对任何内容进行分组。在内部,它只是一个扁平的标签序列,如下所示:
你可以通过对行标签进行排序来获得相同的 groupby 效果:
你甚至可以通过设置相应的 Pandas 选项来完全禁用视觉分组:pd.options.display.multi_sparse=False。
类型转换 Pandas(以及 Python 本身) 区分数字和字符串,因此在无法自动检测数据类型时,通常最好将数字转换为字符串:
pdi.set_level(df.columns, 0 , pdi.get_level(df.columns, 0 ).astype('int' ))
df.columns = df.columns.set_levels(df.columns.levels[0 ].astype(int ), level=0 )
但为了正确使用它们,你需要理解什么是 levels 和 codes,而 pdi 允许你使用多索引,就像使用普通的列表或 NumPy 数组一样。
如果你真的想知道,levels 和 codes 是特定级别的常规标签列表被分解成的东西,以加速像 pivot、join 等操作:
pdi.get_level(df, 0) == Int64Index([2010, 2010, 2020, 2020])
df.columns.levels[0] == Int64Index([2010, 2020])
df.columns.codes[0] == Int64Index([0, 1, 0, 1])
使用多重索引构建一个 Dataframe 除了从 CSV 文件读取和从现有列构建外,还有一些方法可以创建多重索引。它们不太常用——主要用于测试和调试。
由于历史原因,使用 Panda 自己的多索引表示的最直观的方法不起作用。
这里的 Levels 和 codes(现在) 被认为是不应该暴露给最终用户的实现细节,但我们已经拥有了我们所拥有的。
这样做的缺点是必须在单独的一行中指定级别的名称。有几种可选的构造函数将名称和标签捆绑在一起。
当关卡形成规则结构时,您可以指定关键元素,并让 Pandas 自动交织它们,如下所示:
使用多重索引进行索引 通过多重索引访问 DataFrame 的好处是,您可以轻松地使用熟悉的语法一次引用所有级别 (可能省略内部级别)。
列——通过普通的方括号
行和单元格——使用.loc[]
现在,如果你想选择俄勒冈州的所有城市,或者只留下包含人口的列,该怎么办?Python 语法在这里有两个限制。
没有办法区分 df['a', 'b'] 和 df[('a', 'b')]——它是以同样的方式处理的,所以你不能只写 df[:, 'Oregon']。否则,Pandas 将永远不知道你指的是列 Oregon 还是第二级行 Oregon
Python 只允许在方括号内使用冒号,而不允许在圆括号内使用冒号,所以你不能写 df.loc[(:,'Oregon'), :]
在技术方面,这并不难安排。我给 DataFrame 打了猴补丁,添加了这样的功能,你可以在这里看到:
这种语法唯一的缺点是,当你使用两个索引器时,它返回一个副本,所以你不能写 df.mi[:, 'Oregon'].Co['population'] = 10。有许多可选的索引器,其中一些允许这样的赋值,但它们都有自己的特点:
因此,df[:, 'population'] 可以用 df.swaplevel(axis=1)['population'] 实现。
你可以使用 xs 方法:df.xs('population', level=1, axis=1)。
它给人的感觉不够 python 化,尤其是在选择多个关卡时。这种方法无法同时过滤行和列,因此名称 xs(代表'横截面') 背后的原因并不完全清楚。它不能用于设置值。
可以为 pd 创建别名。idx=pd.IndexSlice; df.loc[:, idx[:, 'population']]
这更符合 python 风格,但要访问元素,必须使用别名,这有点麻烦 (没有别名的代码太长了)。您可以同时选择行和列。可写的。
你可以学习如何使用 slice 代替冒号。如果你知道 a[3:10:2] == a[slice(3,10,2)],那么你可能也会理解下面的代码:df.loc[:, (slice(None), 'population')],但它几乎无法读懂。您可以同时选择行和列。可写的。
作为底线,Pandas 有多种使用括号使用多重索引访问 DataFrame 元素的方法,但没有一种方法足够方便,因此他们不得不采用另一种索引语法:
一个用于.query 方法的迷你语言:df.query('state=="Oregon" or city=="Portland"')。
它方便快捷,但缺乏 IDE 的支持 (没有自动补全,没有语法高亮等),而且它只过滤行,而不是列。这意味着你不能在不转置 DataFrame 的情况下用它实现 df:, 'population' 。Non-writable。
叠加与拆分 Pandas 没有针对列的 set_index。向列中添加层次的一种常见方法是将现有的层次从索引中'解栈':
Pandas 的栈与 NumPy 的栈有很大不同。让我们看看文档中对命名约定的说明:
'该函数的命名类似于重新组织的书籍集合,从水平位置并排 (dataframe 的列) 到垂直堆叠 (在 dataframe 的索引中)。'
'在上面'的部分听起来并不能让我信服,但至少这个解释有助于记住谁把东西朝哪个方向移动。顺便说一下,Series 有 unstack,但没有 stack,因为它已经'堆叠'了。由于是一维的,Series 在不同情况下可以作为行向量或列向量,但通常被认为是列向量 (例如 dataframe 列)。
您还可以通过名称或位置索引指定要堆叠/解堆叠的级别。在这个例子中,df.stack()、df.stack(1) 和 df.stack('year') 与 df1.unstack()、df1.unstack(2) 和 df1.unstack('year') 产生相同的结果。目的地总是在'最后一层之后',并且不可配置。如果需要将级别放在其他地方,可以使用 df.swaplevel().sort_index() 或 pdi.swap_level(df=True)
列必须不包含重复的值才能堆叠 (在反堆叠时,索引也是如此):
如何防止叠加/分解排序 stack 和 unstack 都有一个坏习惯,会不可预测地按字典顺序排序结果索引。这有时可能令人恼火,但这是在有大量缺失值时给出可预测结果的唯一方法。
考虑下面的例子。你希望一周中的天数以何种顺序出现在右边的表中?
你可以推测,如果 John 的星期一在 John 的星期五的左边,那么就是'Mon' < 'Fri',类似地,Silvia 的'Fri' < 'Sun',因此结果应该是'Mon' < 'Fri' < 'Sun'。这是合法的,但是如果剩余的列顺序不同,比如'Mon' < 'frii'和'Tue' < 'frii',该怎么办?或者'Mon' < 'friday'和'Wed' < 'Sat'?
好吧,一周没有那么多天,Pandas 可以根据先验知识推断出顺序。但是,人类还没有得出一个决定性的结论,那就是星期天应该作为一周的结束还是开始。Pandas 应该默认使用哪种顺序?阅读区域设置?那么不那么琐碎的顺序呢,比如美国的州的顺序?
在这种情况下,Pandas 所做的只是简单地按字母顺序排序,如下所示:
虽然这是一个合理的默认,但感觉上仍然是错误的。应该有一个解决方案!有一个。它被称为 CategoricalIndex。即使缺少一些标签,它也会记住顺序。它最近已经顺利集成到 Pandas 工具链中。它唯一缺少的是基础设施。它很难建立;它是脆弱的 (在某些操作中会退回到对象),但它是完全可用的,并且 pdi 库有一些帮助程序可以陡峭地提高学习曲线。
例如,要告诉 Pandas 锁定存储产品的简单索引的顺序 (如果你决定将一周中的天数解栈回列,则不可避免地会排序),你需要编写像 df 这样可怕的代码。index = pd.CategoricalIndex(df.index); df.index.sort = True)。它更适合多索引。
pdi 库有一个辅助函数 locked(以及一个默认为 inplace=True 的别名 lock),通过将某个多索引级别提升到 CategoricalIndex 来锁定该级别的顺序:
等级名称旁边的勾选标记表示等级被锁定。它可以使用 pdi.vis(df) 手动可视化,也可以使用 pdi.vis_patch() 对 DataFrame HTML 输出进行 monkey patch 自动可视化。应用补丁后,在 Jupyter 单元中简单地写 df 将显示锁定顺序的所有级别的复选标记。
Lock 和 locked 在简单的情况下自动工作 (如客户端名称),但在更复杂的情况下 (如缺少日期的星期几) 需要用户提示。
在级别切换到 CategoricalIndex 之后,它会在 sort_index、stack、unstack、pivot、pivot_table 等操作中保持原来的顺序。
不过,它很脆弱。即使像 df['new_col'] = 1 这样简单的操作也会破坏它。使用 pdi.insert(df.columns, 0, 'new_col', 1) 用 CategoricalIndex 正确处理级别。
操作级别
pdi.get_level(obj, level_id) 返回通过数字或名称引用的特定级别,可用于 DataFrames, Series 和 MultiIndex
pdi.set_level(obj, level_id, labels) 用给定的数组 (list, NumPy array, Series, Index 等) 替换关卡的标签
pdi.insert_level(obj, pos, labels, name) 使用给定的值添加一个层级 (必要时适当广播)
pdi.drop_level(obj, level_id) 从多重索引中删除指定的级别
pdi.swap_levels(obj, src=-2, dst=-1) 交换两个级别 (默认是两个最内层的级别)
pdi.move_level(obj, src, dst) 将特定级别 src 移动到指定位置 dst
axis=None 其中 None 对于 DataFrame 表示'列',对于 Series 表示'索引'
sort=False,可选 在操作之后对相应的多索引进行排序
inplace=False,可选地原地执行操作 (不能用于单个索引,因为它是不可变的)。
上面的所有操作都是从传统意义上理解'级别'这个词的 (级别的标签数量与数据框中的列数量相同),隐藏了索引的机制。标签和索引。来自最终用户的代码。
在极少数情况下,当移动和交换单独的关卡不够时,您可以使用纯 Pandas 调用:df.columns = df.columns.reorder_levels(['M', 'L', 'K']) 其中 ['M', 'L', 'K'] 是层的期望顺序。
通常,使用 get_level 和 set_level 对标签进行必要的修复就足够了,但如果你想一次对多索引的所有级别应用转换,Pandas 有一个 (命名不明确) 函数 rename 接受一个 dict 或一个函数:
至于重命名级别,它们的名称存储在.names 字段中。该字段不支持直接赋值 (为什么不?): df.index.names[1] = 'x' # TypeError,但可以作为一个整体替换:
将多索引转换为平面索引并恢复它 正如我们在上面看到的,便捷的查询方法只解决了处理行中的多索引的复杂性。尽管有这么多的辅助函数,但当某些 Pandas 函数返回列中的多索引时,对初学者来说会有一个震惊的效果。因此,pdi 库具有以下内容:
join_levels(obj, sep='_', name=None) 将所有多索引级别连接到一个索引
split_level(obj, sep='_', names=None) 将索引拆分回多索引
它们都有可选的 axis 和 inplace 参数。
排序 MultiIndex 由于多索引由多个级别组成,因此排序比单索引更做作。这仍然可以使用 sort_index 方法完成,但可以使用以下参数进行进一步微调。
读写多索引 dataframe 到磁盘 Pandas 可以以完全自动化的方式将具有多重索引的 DataFrame 写入 CSV 文件:df.to_csv('df.csv')。但是在读取这样的文件时,Pandas 无法自动解析多重索引,需要用户的一些提示。例如,要读取具有三层高列和四层宽索引的 DataFrame,你需要指定 pd.read_csv('df.csv', header=[0,1,2], index_col=[0,1,2,3])。
这意味着前三行包含有关列的信息,后续每一行的前四个字段包含索引级别 (如果列的级别不止一个,你不能再通过名称来引用行级别,只能通过编号)。
手动解读多索引中的层数是不方便的,所以更好的主意是在将 DataFrame 保存到 CSV 之前,stack() 所有列头层,并在读取后将它们解 stack()。
如果你需要'置之不理'的解决方案,可能需要研究二进制格式,例如 Python 的 pickle 格式:
直接调用:df.to_pickle('df.pkl'),pd.read_pickle('df.pkl')
使用 storemagic 在 Jupyter %store df 然后 %store -r df(存储在 $HOME/.ipython/profile_default/db/autorestore)
Python 的 pickle 小巧而快速,但只能在 Python 中访问。如果您需要与其他生态系统互操作,请查看更标准的格式,如 Excel 格式 (在读取 MultiIndex 时需要与 read_csv 相同的提示)。代码如下:
!pip install openpyxl
df.to_excel('df3.xlsx' )
df.to_pd.read_excel('df3.xlsx' , header=[0 ,1 ,2 ], index_col=[0 ,1 ,2 ,3 ])
MultiIndex 算术 当使用多索引数据框时,与普通数据框适用相同的规则 (见上文)。但是处理细胞的一个子集有它自己的一些特性。
用户可以通过外部的多索引级别更新部分列,如下所示:
如果想保持原始数据不变,可以使用 df1 = df.assign(population=df.population*10)。
你也可以用 density=df.population/df.area 轻松获得人口密度。
但不幸的是,你不能用 df.assign 将结果赋值给原始的 dataframe。
一种方法是将列索引的所有不相关级别堆叠到行索引中,执行必要的计算,然后将它们解堆叠回去 (使用 pdi)。锁以保持列的原始顺序)。
pdi.assign 是锁定顺序感知的,所以如果你给它一个 (多个) 锁定级别的 dataframe,它不会解锁它们或后续的栈/解栈/等操作将保持原始的列和行顺序。
总而言之,Pandas 是分析和处理数据的好工具。希望这篇文章能帮助你理解'如何'和'为什么'解决典型问题,并欣赏 Pandas 库的真正价值和美丽。
相关免费在线工具 加密/解密文本 使用加密算法(如AES、TripleDES、Rabbit或RC4)加密和解密文本明文。 在线工具,加密/解密文本在线工具,online
RSA密钥对生成器 生成新的随机RSA私钥和公钥pem证书。 在线工具,RSA密钥对生成器在线工具,online
Mermaid 预览与可视化编辑 基于 Mermaid.js 实时预览流程图、时序图等图表,支持源码编辑与即时渲染。 在线工具,Mermaid 预览与可视化编辑在线工具,online
随机西班牙地址生成器 随机生成西班牙地址(支持马德里、加泰罗尼亚、安达卢西亚、瓦伦西亚筛选),支持数量快捷选择、显示全部与下载。 在线工具,随机西班牙地址生成器在线工具,online
Gemini 图片去水印 基于开源反向 Alpha 混合算法去除 Gemini/Nano Banana 图片水印,支持批量处理与下载。 在线工具,Gemini 图片去水印在线工具,online
curl 转代码 解析常见 curl 参数并生成 fetch、axios、PHP curl 或 Python requests 示例代码。 在线工具,curl 转代码在线工具,online