写点什么

前置机器学习(四):一文掌握 Pandas 用法

用户头像
caiyongji
关注
发布于: 2021 年 03 月 22 日
前置机器学习(四):一文掌握Pandas用法

Pandas 提供快速,灵活和富于表现力的数据结构,是强大的数据分析 Python 库。


本文收录于机器学习前置教程系列


一、Series 和 DataFrame


Pandas 建立在 NumPy 之上,更多 NumPy 相关的知识点可以参考我之前写的文章前置机器学习(三):30分钟掌握常用NumPy用法

Pandas 特别适合处理表格数据,如 SQL 表格、EXCEL 表格。有序或无序的时间序列。具有行和列标签的任意矩阵数据。


打开 Jupyter Notebook,导入 numpy 和 pandas 开始我们的教程:

import numpy as npimport pandas as pd
复制代码


1. pandas.Series


Series 是带有索引的一维 ndarray 数组。索引值可不唯一,但必须是可哈希的。

pd.Series([1, 3, 5, np.nan, 6, 8])
复制代码

输出:


0    1.01    3.02    5.03    NaN4    6.05    8.0dtype: float64
复制代码

我们可以看到默认索引值为 0、1、2、3、4、5 这样的数字。添加index属性,指定其为'c','a','i','yong','j','i'。

pd.Series([1, 3, 5, np.nan, 6, 8], index=['c','a','i','yong','j','i'])
复制代码

输出如下,我们可以看到 index 是可重复的。

c       1.0a       3.0i       5.0yong    NaNj       6.0i       8.0dtype: float64
复制代码


2. pandas.DataFrame


DataFrame 是带有行和列的表格结构。可以理解为多个 Series 对象的字典结构。


pd.DataFrame(np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]]), index=['i','ii','iii'], columns=['A', 'B', 'C'])
复制代码

输出表格如下,其中index对应它的行,columns对应它的列。


| | A | B | C |

|:----|----:|----:|----:|

| i | 1 | 2 | 3 |

| ii | 4 | 5 | 6 |

| iii | 7 | 8 | 9 |


二、Pandas 常见用法


1. 访问数据


准备数据,随机生成 6 行 4 列的二维数组,行标签为从 20210101 到 20210106 的日期,列标签为 A、B、C、D。

import numpy as npimport pandas as pdnp.random.seed(20201212)df = pd.DataFrame(np.random.randn(6, 4), index=pd.date_range('20210101', periods=6), columns=list('ABCD'))df
复制代码

展示表格如下:


| | A | B | C | D |

|:--------------------|----------:|----------:|----------:|----------:|

| 2021-01-01 | 0.270961 | -0.405463 | 0.348373 | 0.828572 |

| 2021-01-02 | 0.696541 | 0.136352 | -1.64592 | -0.69841 |

| 2021-01-03 | 0.325415 | -0.602236 | -0.134508 | 1.28121 |

| 2021-01-04 | -0.33032 | -1.40384 | -0.93809 | 1.48804 |

| 2021-01-05 | 0.348708 | 1.27175 | 0.626011 | -0.253845 |

| 2021-01-06 | -0.816064 | 1.30197 | 0.656281 | -1.2718 |


1.1 head()和 tail()


查看表格前几行:


df.head(2)
复制代码

展示表格如下:


| | A | B | C | D |

|:--------------------|---------:|----------:|----------:|----------:|

| 2021-01-01 | 0.270961 | -0.405463 | 0.348373 | 0.828572 |

| 2021-01-02 | 0.696541 | 0.136352 | -1.64592 | -0.69841 |


查看表格后几行:


df.tail(3)
复制代码

展示表格如下:


| | A | B | C | D |

|:--------------------|----------:|---------:|----------:|----------:|

| 2021-01-04 | -0.33032 | -1.40384 | -0.93809 | 1.48804 |

| 2021-01-05 | 0.348708 | 1.27175 | 0.626011 | -0.253845 |

| 2021-01-06 | -0.816064 | 1.30197 | 0.656281 | -1.2718 |


1.2 describe()


describe方法用于生成 DataFrame 的描述统计信息。可以很方便的查看数据集的分布情况。注意,这里的统计分布不包含`NaN`值。


df.describe()
复制代码

展示如下:


| | A | B | C | D |

|:------|-----------:|-----------:|----------:|----------:|

| count | 6 | 6 | 6 | 6 |

| mean | 0.0825402 | 0.0497552 | -0.181309 | 0.22896 |

| std | 0.551412 | 1.07834 | 0.933155 | 1.13114 |

| min | -0.816064 | -1.40384 | -1.64592 | -1.2718 |

| 25% | -0.18 | -0.553043 | -0.737194 | -0.587269 |

| 50% | 0.298188 | -0.134555 | 0.106933 | 0.287363 |

| 75% | 0.342885 | 0.987901 | 0.556601 | 1.16805 |

| max | 0.696541 | 1.30197 | 0.656281 | 1.48804 |


我们首先回顾一下我们掌握的数学公式。


平均数(mean)


$$\bar x = \frac{\sum{i=1}^{n}{xi}}{n}$$


方差(variance):


$$s^2 = \frac{\sum{i=1}^{n}{(xi -\bar x)^2}}{n}$$


标准差(std):


$$s = \sqrt{\frac{\sum{i=1}^{n}{(xi -\bar x)^2}}{n}}$$


我们解释一下 pandas 的 describe 统计信息各属性的意义。我们仅以 A 列为例。


  • count表示计数。A 列有 6 个数据不为空。

  • mean表示平均值。A 列所有不为空的数据平均值为 0.0825402。

  • std表示标准差。A 列的标准差为 0.551412。

  • min表示最小值。A 列最小值为-0.816064。即,0%的数据比-0.816064 小。

  • 25%表示四分之一分位数。A 列的四分之一分位数为-0.18。即,25%的数据比-0.18 小。

  • 50%表示二分之一分位数。A 列的四分之一分位数为 0.298188。即,50%的数据比 0.298188 小。

  • 75%表示四分之三分位数。A 列的四分之三分位数为 0.342885。即,75%的数据比 0.342885 小。

  • max表示最大值。A 列的最大值为 0.696541。即,100%的数据比 0.696541 小。


1.3 T

T一般表示Transpose的缩写,即转置。行列转换。


df.T
复制代码

展示表格如下:


| | 2021-01-01 | 2021-01-02 | 2021-01-03 | 2021-01-04 | 2021-01-05 | 2021-01-06 |

|:---|-------------:|-------------:|-------------:|-------------:|-------------:|-------------:|

| A | 0.270961 | 0.696541 | 0.325415 | -0.33032 | 0.348708 | -0.816064 |

| B | -0.405463 | 0.136352 | -0.602236 | -1.40384 | 1.27175 | 1.30197 |

| C | 0.348373 | -1.64592 | -0.134508 | -0.93809 | 0.626011 | 0.656281 |

| D | 0.828572 | -0.69841 | 1.28121 | 1.48804 | -0.253845 | -1.2718 |


1.4 sort_values()


指定某一列进行排序,如下代码根据C列进行正序排序。


df.sort_values(by='C')
复制代码

展示表格如下:


| | A | B | C | D |

|:-----------|----------:|----------:|----------:|----------:|

| 2021-01-02 | 0.696541 | 0.136352 | -1.64592 | -0.69841 |

| 2021-01-04 | -0.33032 | -1.40384 | -0.93809 | 1.48804 |

| 2021-01-03 | 0.325415 | -0.602236 | -0.134508 | 1.28121 |

| 2021-01-01 | 0.270961 | -0.405463 | 0.348373 | 0.828572 |

| 2021-01-05 | 0.348708 | 1.27175 | 0.626011 | -0.253845 |

| 2021-01-06 | -0.816064 | 1.30197 | 0.656281 | -1.2718 |


1.5 nlargest()


选择某列最大的 n 行数据。如:df.nlargest(2,'A')表示,返回 A 列最大的 2 行数据。


df.nlargest(2,'A')
复制代码

展示表格如下:


| | A | B | C | D |

|:-----------|---------:|---------:|----------:|----------:|

| 2021-01-02 | 0.696541 | 0.136352 | -1.64592 | -0.69841 |

| 2021-01-05 | 0.348708 | 1.27175 | 0.626011 | -0.253845 |


1.6 sample()


sample方法表示查看随机的样例数据。


df.sample(5)表示返回随机 5 行数据。

df.sample(5)
复制代码

参数frac表示 fraction,分数的意思。frac=0.01 即返回 1%的随机数据作为样例展示。

df.sample(frac=0.01)
复制代码


2. 选择数据


2.1 根据标签选择


我们输入df['A']命令选取 A 列。

df['A']
复制代码


输出 A 列数据,同时也是一个 Series 对象:

2021-01-01    0.2709612021-01-02    0.6965412021-01-03    0.3254152021-01-04   -0.3303202021-01-05    0.3487082021-01-06   -0.816064Name: A, dtype: float64
复制代码


df[0:3]该代码与`df.head(3)同理。但df[0:3]`是 NumPy 的数组选择方式,这说明了 Pandas 对于 NumPy 具有良好的支持。


df[0:3]
复制代码

展示表格如下:

| | A | B | C | D |

|:-----------|---------:|----------:|----------:|----------:|

| 2021-01-01 | 0.270961 | -0.405463 | 0.348373 | 0.828572 |

| 2021-01-02 | 0.696541 | 0.136352 | -1.64592 | -0.69841 |

| 2021-01-03 | 0.325415 | -0.602236 | -0.134508 | 1.28121 |


通过 loc 方法指定行列标签。


df.loc['2021-01-01':'2021-01-02', ['A', 'B']]
复制代码

展示表格如下:


| | A | B |

|:-----------|---------:|----------:|

| 2021-01-01 | 0.270961 | -0.405463 |

| 2021-01-02 | 0.696541 | 0.136352 |


2.2 根据位置选择


ilocloc不同。loc指定具体的标签,而iloc指定标签的索引位置。df.iloc[3:5, 0:3]表示选取索引为 3、4 的行,索引为 0、1、2 的列。即,第 4、5 行,第 1、2、3 列。

注意,索引序号从 0 开始。冒号表示区间,左右两侧分别表示开始和结束。如3:5表示左开右闭区间[3,5),即不包含 5 自身。

df.iloc[3:5, 0:3]
复制代码


| | A | B | C |

|:-----------|----------:|---------:|----------:|

| 2021-01-04 | -0.33032 | -1.40384 | -0.93809 |

| 2021-01-05 | 0.348708 | 1.27175 | 0.626011 |


df.iloc[:, 1:3]
复制代码


| | B | C |

|:-----------|----------:|----------:|

| 2021-01-01 | -0.405463 | 0.348373 |

| 2021-01-02 | 0.136352 | -1.64592 |

| 2021-01-03 | -0.602236 | -0.134508 |

| 2021-01-04 | -1.40384 | -0.93809 |

| 2021-01-05 | 1.27175 | 0.626011 |

| 2021-01-06 | 1.30197 | 0.656281 |


2.3 布尔索引


DataFrame 可根据条件进行筛选,当条件判断True时,返回。当条件判断为False时,过滤掉。


我们设置一个过滤器用来判断 A 列是否大于 0。

filter = df['A'] > 0filter
复制代码

输出结果如下,可以看到2021-01-042021-01-06的行为 False。


2021-01-01     True2021-01-02     True2021-01-03     True2021-01-04    False2021-01-05     True2021-01-06    FalseName: A, dtype: bool
复制代码


我们通过过滤器查看数据集。


df[filter]# df[df['A'] > 0]
复制代码

查看表格我们可以发现,2021-01-042021-01-06的行被过滤掉了。


| | A | B | C | D |

|:-----------|---------:|----------:|----------:|----------:|

| 2021-01-01 | 0.270961 | -0.405463 | 0.348373 | 0.828572 |

| 2021-01-02 | 0.696541 | 0.136352 | -1.64592 | -0.69841 |

| 2021-01-03 | 0.325415 | -0.602236 | -0.134508 | 1.28121 |

| 2021-01-05 | 0.348708 | 1.27175 | 0.626011 | -0.253845 |


3. 处理缺失值


准备数据。


df2 = df.copy()df2.loc[:3, 'E'] = 1.0f_series = {'2021-01-02': 1.0,'2021-01-03': 2.0,'2021-01-04': 3.0,'2021-01-05': 4.0,'2021-01-06': 5.0}df2['F'] = pd.Series(f_series)df2
复制代码

展示表格如下:


| | A | B | C | D | F | E |

|:-----------|----------:|----------:|----------:|----------:|----:|----:|

| 2021-01-01 | 0.270961 | -0.405463 | 0.348373 | 0.828572 | nan | 1 |

| 2021-01-02 | 0.696541 | 0.136352 | -1.64592 | -0.69841 | 1 | 1 |

| 2021-01-03 | 0.325415 | -0.602236 | -0.134508 | 1.28121 | 2 | 1 |

| 2021-01-04 | -0.33032 | -1.40384 | -0.93809 | 1.48804 | 3 | nan |

| 2021-01-05 | 0.348708 | 1.27175 | 0.626011 | -0.253845 | 4 | nan |

| 2021-01-06 | -0.816064 | 1.30197 | 0.656281 | -1.2718 | 5 | nan |


3.1 dropna()


使用 dropna 方法清空 NaN 值。注意:dropa 方法返回新的 DataFrame,并不会改变原有的 DataFrame。


df2.dropna(how='any')
复制代码

以上代码表示,当行数据有任意的数值为空时,删除。


| | A | B | C | D | F | E |

|:-----------|---------:|----------:|----------:|---------:|----:|----:|

| 2021-01-02 | 0.696541 | 0.136352 | -1.64592 | -0.69841 | 1 | 1 |

| 2021-01-03 | 0.325415 | -0.602236 | -0.134508 | 1.28121 | 2 | 1 |


3.2 fillna()


使用 filna 命令填补 NaN 值。


df2.fillna(df2.mean())
复制代码

以上代码表示,使用每一列的平均值来填补空缺。同样地,fillna 并不会更新原有的 DataFrame,如需更新原有 DataFrame 使用代码df2 = df2.fillna(df2.mean())


展示表格如下:


| | A | B | C | D | F | E |

|:-----------|----------:|----------:|----------:|----------:|----:|----:|

| 2021-01-01 | 0.270961 | -0.405463 | 0.348373 | 0.828572 | 3 | 1 |

| 2021-01-02 | 0.696541 | 0.136352 | -1.64592 | -0.69841 | 1 | 1 |

| 2021-01-03 | 0.325415 | -0.602236 | -0.134508 | 1.28121 | 2 | 1 |

| 2021-01-04 | -0.33032 | -1.40384 | -0.93809 | 1.48804 | 3 | 1 |

| 2021-01-05 | 0.348708 | 1.27175 | 0.626011 | -0.253845 | 4 | 1 |

| 2021-01-06 | -0.816064 | 1.30197 | 0.656281 | -1.2718 | 5 | 1 |


4. 操作方法


4.1 agg()

agg 是 Aggregate 的缩写,意为聚合。


常用聚合方法如下:


  • mean(): Compute mean of groups

  • sum(): Compute sum of group values

  • size(): Compute group sizes

  • count(): Compute count of group

  • std(): Standard deviation of groups

  • var(): Compute variance of groups

  • sem(): Standard error of the mean of groups

  • describe(): Generates descriptive statistics

  • first(): Compute first of group values

  • last(): Compute last of group values

  • nth() : Take nth value, or a subset if n is a list

  • min(): Compute min of group values

  • max(): Compute max of group values


df.mean()
复制代码

返回各列平均值


A    0.082540B    0.049755C   -0.181309D    0.228960dtype: float64
复制代码

可通过加参数 axis 查看行平均值。


df.mean(axis=1)
复制代码

输出:

2021-01-01    0.2606112021-01-02   -0.3778602021-01-03    0.2174702021-01-04   -0.2960532021-01-05    0.4981562021-01-06   -0.032404dtype: float64
复制代码


如果我们想查看某一列的多项聚合统计怎么办?

这时我们可以调用 agg 方法:


df.agg(['std','mean'])['A']
复制代码

返回结果显示标准差 std 和均值 mean:


std 0.551412mean 0.082540Name: A, dtype: float64
复制代码


对于不同的列应用不同的聚合函数:


df.agg({'A':['max','mean'],'B':['mean','std','var']})
复制代码


返回结果如下:


| | A | B |

|:-----|------------:|------------:|

| max | 0.696541 | nan |

| mean | 0.0825402 | 0.0497552 |

| std | nan | 1.07834 |

| var | nan | 1.16281 |


4.2 apply()


apply()是对方法的调用。

df.apply(np.sum)表示每一列调用 np.sum 方法,返回每一列的数值和。


df.apply(np.sum)
复制代码


输出结果为:

A    0.495241B    0.298531C   -1.087857D    1.373762dtype: float64
复制代码


apply 方法支持 lambda 表达式。


df.apply(lambda n: n*2)
复制代码


| | A | B | C | D |

|:-----------|----------:|----------:|----------:|---------:|

| 2021-01-01 | 0.541923 | -0.810925 | 0.696747 | 1.65714 |

| 2021-01-02 | 1.39308 | 0.272704 | -3.29185 | -1.39682 |

| 2021-01-03 | 0.65083 | -1.20447 | -0.269016 | 2.56242 |

| 2021-01-04 | -0.66064 | -2.80768 | -1.87618 | 2.97607 |

| 2021-01-05 | 0.697417 | 2.5435 | 1.25202 | -0.50769 |

| 2021-01-06 | -1.63213 | 2.60393 | 1.31256 | -2.5436 |


4.3 value_counts()


value_counts 方法查看各行、列的数值重复统计。

我们重新生成一些整数数据,来保证有一定的数据重复。

np.random.seed(101)df3 = pd.DataFrame(np.random.randint(0,9,size = (6,4)),columns=list('ABCD'))df3
复制代码


| | A | B | C | D |

|---:|----:|----:|----:|----:|

| 0 | 1 | 6 | 7 | 8 |

| 1 | 4 | 8 | 5 | 0 |

| 2 | 5 | 8 | 1 | 3 |

| 3 | 8 | 3 | 3 | 2 |

| 4 | 8 | 3 | 7 | 0 |

| 5 | 7 | 8 | 4 | 3 |


调用 value_counts()方法。

df3['A'].value_counts()
复制代码

查看输出我们可以看到 A 列的数字 8 有两个,其他数字的数量为 1。


8    27    15    14    11    1Name: A, dtype: int64
复制代码


4.4 str


Pandas 内置字符串处理方法。


names = pd.Series(['andrew','bobo','claire','david','4'])names.str.upper()
复制代码

通过以上代码我们将 Series 中的字符串全部设置为大写。


0    ANDREW1      BOBO2    CLAIRE3     DAVID4         4dtype: object
复制代码


首字母大写:

names.str.capitalize()
复制代码

输出为:


0    Andrew1      Bobo2    Claire3     David4         4dtype: object
复制代码

判断是否为数字:

names.str.isdigit()
复制代码

输出为:

0    False1    False2    False3    False4     Truedtype: bool
复制代码

字符串分割:

tech_finance = ['GOOG,APPL,AMZN','JPM,BAC,GS']tickers = pd.Series(tech_finance)tickers.str.split(',').str[0:2]
复制代码

以逗号分割字符串,结果为:


0    [GOOG, APPL]1      [JPM, BAC]dtype: object
复制代码


5. 合并


5.1 concat()


concat 用来将数据集串联起来。我们先准备数据。


data_one = {'Col1': ['A0', 'A1', 'A2', 'A3'],'Col2': ['B0', 'B1', 'B2', 'B3']}data_two = {'Col1': ['C0', 'C1', 'C2', 'C3'], 'Col2': ['D0', 'D1', 'D2', 'D3']}one = pd.DataFrame(data_one)two = pd.DataFrame(data_two)
复制代码

使用 concat 方法将两个数据集串联起来。


pt(pd.concat([one,two]))
复制代码

得到表格:

| | Col1 | Col2 |

|---:|:-------|:-------|

| 0 | A0 | B0 |

| 1 | A1 | B1 |

| 2 | A2 | B2 |

| 3 | A3 | B3 |

| 0 | C0 | D0 |

| 1 | C1 | D1 |

| 2 | C2 | D2 |

| 3 | C3 | D3 |


5.2 merge()


merge 相当于 SQL 操作中的 join 方法,用于将两个数据集通过某种关系连接起来

registrations = pd.DataFrame({'reg_id':[1,2,3,4],'name':['Andrew','Bobo','Claire','David']})logins = pd.DataFrame({'log_id':[1,2,3,4],'name':['Xavier','Andrew','Yolanda','Bobo']})
复制代码


我们根据name来连接两个张表,连接方式为outer

pd.merge(left=registrations, right=logins, how='outer',on='name')
复制代码

返回结果为:


| | regid | name | logid |

|---:|---------:|:--------|---------:|

| 0 | 1 | Andrew | 2 |

| 1 | 2 | Bobo | 4 |

| 2 | 3 | Claire | nan |

| 3 | 4 | David | nan |

| 4 | nan | Xavier | 1 |

| 5 | nan | Yolanda | 3 |


我们注意,how : {'left', 'right', 'outer', 'inner'} 有 4 种连接方式。表示是否选取左右两侧表的 nan 值。如 left 表示保留左侧表中所有数据,当遇到右侧表数据为 nan 值时,不显示右侧的数据。

简单来说,把 left 表和 right 表看作两个集合。


  • left 表示取左表全部集合+两表交集

  • right 表示取右表全部集合+两表交集

  • outer 表示取两表并集

  • inner 表示取两表交集


6. 分组 GroupBy


Pandas 中的分组功能非常类似于 SQL 语句SELECT Column1, Column2, mean(Column3), sum(Column4)FROM SomeTableGROUP BY Column1, Column2。即使没有接触过 SQL 也没有关系,分组就相当于把表格数据按照某一列进行拆分、统计、合并的过程。


准备数据。


np.random.seed(20201212)df = pd.DataFrame({'A': ['foo', 'bar', 'foo', 'bar', 'foo', 'bar', 'foo', 'foo'],                   'B': ['one', 'one', 'two', 'three', 'two', 'two', 'one', 'three'],                   'C': np.random.randn(8),                   'D': np.random.randn(8)})df
复制代码


可以看到,我们的 A 列和 B 列有很多重复数据。这时我们可以根据 foo/bar 或者 one/two 进行分组。


| | A | B | C | D |

|---:|:----|:------|----------:|----------:|

| 0 | foo | one | 0.270961 | 0.325415 |

| 1 | bar | one | -0.405463 | -0.602236 |

| 2 | foo | two | 0.348373 | -0.134508 |

| 3 | bar | three | 0.828572 | 1.28121 |

| 4 | foo | two | 0.696541 | -0.33032 |

| 5 | bar | two | 0.136352 | -1.40384 |

| 6 | foo | one | -1.64592 | -0.93809 |

| 7 | foo | three | -0.69841 | 1.48804 |


6.1 单列分组


我们应用groupby方法将上方表格中的数据进行分组。


df.groupby('A')
复制代码

执行上方代码可以看到,groupby 方法返回的是一个类型为DataFrameGroupBy的对象。我们无法直接查看,需要应用聚合函数。参考本文 4.1 节。


<pandas.core.groupby.generic.DataFrameGroupBy object at 0x0000014C6742E248>
复制代码


我们应用聚合函数 sum 试试。

df.groupby('A').sum()
复制代码

展示表格如下:


| A | C | D |

|:----|----------:|----------:|

| bar | 0.559461 | -0.724868 |

| foo | -1.02846 | 0.410533 |


6.2 多列分组


groupby方法支持将多个列作为参数传入。


df.groupby(['A', 'B']).sum()
复制代码


分组后显示结果如下:


| A | B | C | D |

|:-------|----------:|----------:|----------:|

| bar | one | -0.405463 | -0.602236 |

| | one | -0.405463 | -0.602236 |

| | three | 0.828572 | 1.28121 |

| | two | 0.136352 | -1.40384 |

| foo | one | -1.37496 | -0.612675 |

| | three | -0.69841 | 1.48804 |

| | two | 1.04491 | -0.464828 |


6.3 应用多聚合方法


我们应用agg(),将聚合方法数组作为参数传入方法。下方代码根据 A 分类且只统计C列的数值。


df.groupby('A')['C'].agg([np.sum, np.mean, np.std])
复制代码


可以看到 bar 组与 foo 组各聚合函数的结果如下:


| A | sum | mean | std |

|:----|----------:|----------:|---------:|

| bar | 0.559461 | 0.186487 | 0.618543 |

| foo | -1.02846 | -0.205692 | 0.957242 |


6.4 不同列进行不同聚合统计


下方代码对 C、D 列分别进行不同的聚合统计,对 C 列进行求和,对 D 列进行标准差统计。


df.groupby('A').agg({'C': 'sum', 'D': lambda x: np.std(x, ddof=1)})
复制代码

输出如下:


| A | C | D |

|:----|----------:|---------:|

| bar | 0.559461 | 1.37837 |

| foo | -1.02846 | 0.907422 |


6.5 更多


更多关于 Pandas 的goupby方法请参考官网:https://pandas.pydata.org/pandas-docs/stable/user_guide/groupby.html


三、Pandas 进阶用法


1. reshape


reshape表示重塑表格。对于复杂表格,我们需要将其转换成适合我们理解的样子,比如根据某些属性分组后进行单独统计。


1.1 stack() 和 unstack()


stack方法将表格分为索引和数据两个部分。索引各列保留,数据堆叠放置。


准备数据。


tuples = list(zip(*[['bar', 'bar', 'baz', 'baz','foo', 'foo', 'qux', 'qux'],                    ['one', 'two', 'one', 'two','one', 'two', 'one', 'two']]))index = pd.MultiIndex.from_tuples(tuples, names=['first', 'second'])
复制代码

根据上方代码,我们创建了一个复合索引。


MultiIndex([('bar', 'one'),            ('bar', 'two'),            ('baz', 'one'),            ('baz', 'two'),            ('foo', 'one'),            ('foo', 'two'),            ('qux', 'one'),            ('qux', 'two')],           names=['first', 'second'])
复制代码


我们创建一个具备复合索引的 DataFrame。


np.random.seed(20201212)df = pd.DataFrame(np.random.randn(8, 2), index=index, columns=['A', 'B'])df
复制代码

输出如下:


| A | B | C | D |

|:-------|----------:|----------:|----------:|

| bar | one | 0.270961 | -0.405463 |

| | two | 0.348373 | 0.828572 |

| baz | one | 0.696541 | 0.136352 |

| | two | -1.64592 | -0.69841 |

| foo | one | 0.325415 | -0.602236 |

| | two | -0.134508 | 1.28121 |

| qux | one | -0.33032 | -1.40384 |

| | two | -0.93809 | 1.48804 |


我们执行stack方法。


stacked = df.stack()stacked
复制代码

输出堆叠(压缩)后的表格如下。注意:你使用 Jupyter Notebook/Lab 进行的输出可能和如下结果不太一样。下方输出的各位为了方便在 Markdown 中显示有一定的调整。


first  second   bar    one     A    0.942502bar    one     B    0.060742bar    two     A    1.340975bar    two     B   -1.712152baz    one     A    1.899275baz    one     B    1.237799baz    two     A   -1.589069baz    two     B    1.288342foo    one     A   -0.326792foo    one     B    1.576351foo    two     A    1.526528foo    two     B    1.410695qux    one     A    0.420718qux    one     B   -0.288002qux    two     A    0.361586qux    two     B    0.177352dtype: float64
复制代码


我们执行 unstack 将数据进行展开。


stacked.unstack()
复制代码

输出原表格。


| A | B | C | D |

|:-------|----------:|----------:|----------:|

| bar | one | 0.270961 | -0.405463 |

| | two | 0.348373 | 0.828572 |

| baz | one | 0.696541 | 0.136352 |

| | two | -1.64592 | -0.69841 |

| foo | one | 0.325415 | -0.602236 |

| | two | -0.134508 | 1.28121 |

| qux | one | -0.33032 | -1.40384 |

| | two | -0.93809 | 1.48804 |


我们加入参数level


stacked.unstack(level=0)#stacked.unstack(level=1)
复制代码

level=0时得到如下输出,大家可以试试level=1时输出什么。


| second | first | bar | baz | foo | qux |

|:-------------|----------:|----------:|---------:|----------:|----------:|

| one| A | 0.942502 | 1.89927 | -0.326792 | 0.420718 |

| one| B | 0.060742 | 1.2378 | 1.57635 | -0.288002 |

| two| A | 1.34097 | -1.58907 | 1.52653 | 0.361586 |

| two| B | -1.71215 | 1.28834 | 1.4107 | 0.177352 |


1.2 pivot_table()


pivot_table 表示透视表,是一种对数据动态排布并且分类汇总的表格格式。


我们生成无索引列的 DataFrame。

np.random.seed(99)df = pd.DataFrame({'A': ['one', 'one', 'two', 'three'] * 3,                    'B': ['A', 'B', 'C'] * 4,                    'C': ['foo', 'foo', 'foo', 'bar', 'bar', 'bar'] * 2,                    'D': np.random.randn(12),                    'E': np.random.randn(12)})df
复制代码


展示表格如下:


| | A | B | C | D | E |

|---:|:------|:----|:----|-----------:|-----------:|

| 0 | one | A | foo | -0.142359 | 0.0235001 |

| 1 | one | B | foo | 2.05722 | 0.456201 |

| 2 | two | C | foo | 0.283262 | 0.270493 |

| 3 | three | A | bar | 1.32981 | -1.43501 |

| 4 | one | B | bar | -0.154622 | 0.882817 |

| 5 | one | C | bar | -0.0690309 | -0.580082 |

| 6 | two | A | foo | 0.75518 | -0.501565 |

| 7 | three | B | foo | 0.825647 | 0.590953 |

| 8 | one | C | foo | -0.113069 | -0.731616 |

| 9 | one | A | bar | -2.36784 | 0.261755 |

| 10 | two | B | bar | -0.167049 | -0.855796 |

| 11 | three | C | bar | 0.685398 | -0.187526 |


通过观察数据,我们可以显然得出 A、B、C 列的具备一定属性含义。我们执行pivot_table方法。


pd.pivot_table(df, values=['D','E'], index=['A', 'B'], columns=['C'])
复制代码

上方代码的意思为,将 D、E 列作为数据列,A、B 作为复合行索引,C 的数据值作为列索引。


| | ('D', 'bar') | ('D', 'foo') | ('E', 'bar') | ('E', 'foo') |

|:---------------|---------------:|---------------:|---------------:|---------------:|

| ('one', 'A') | -2.36784 | -0.142359 | 0.261755 | 0.0235001 |

| ('one', 'B') | -0.154622 | 2.05722 | 0.882817 | 0.456201 |

| ('one', 'C') | -0.0690309 | -0.113069 | -0.580082 | -0.731616 |

| ('three', 'A') | 1.32981 | nan | -1.43501 | nan |

| ('three', 'B') | nan | 0.825647 | nan | 0.590953 |

| ('three', 'C') | 0.685398 | nan | -0.187526 | nan |

| ('two', 'A') | nan | 0.75518 | nan | -0.501565 |

| ('two', 'B') | -0.167049 | nan | -0.855796 | nan |

| ('two', 'C') | nan | 0.283262 | nan | 0.270493 |


2. 时间序列


date_range是 Pandas 自带的生成日期间隔的方法。我们执行下方代码:


rng = pd.date_range('1/1/2021', periods=100, freq='S')pd.Series(np.random.randint(0, 500, len(rng)), index=rng)
复制代码

date_range 方法从 2021 年 1 月 1 日 0 秒开始,以 1 秒作为时间间隔执行 100 次时间段的划分。输出结果如下:


2021-01-01 00:00:00    4752021-01-01 00:00:01    1452021-01-01 00:00:02     132021-01-01 00:00:03    2402021-01-01 00:00:04    183                      ... 2021-01-01 00:01:35    4132021-01-01 00:01:36    3302021-01-01 00:01:37    2722021-01-01 00:01:38    3042021-01-01 00:01:39    151Freq: S, Length: 100, dtype: int32
复制代码


我们将freq的参数值从 S(second)改为 M(Month)试试看。


rng = pd.date_range('1/1/2021', periods=100, freq='M')pd.Series(np.random.randint(0, 500, len(rng)), index=rng)
复制代码


输出:


2021-01-31    3112021-02-28    2562021-03-31    3272021-04-30    1512021-05-31    484             ... 2028-12-31    1702029-01-31    4922029-02-28    2052029-03-31     902029-04-30    446Freq: M, Length: 100, dtype: int32
复制代码


我们设置可以以季度作为频率进行日期生成。


prng = pd.period_range('2018Q1', '2020Q4', freq='Q-NOV')pd.Series(np.random.randn(len(prng)), prng)
复制代码

输出 2018 第一季度到 2020 第四季度间的全部季度。


2018Q1    0.8330252018Q2   -0.5095142018Q3   -0.7355422018Q4   -0.2244032019Q1   -0.1197092019Q2   -1.3794132019Q3    0.8717412019Q4    0.8774932020Q1    0.5776112020Q2   -0.3657372020Q3   -0.4734042020Q4    0.529800Freq: Q-NOV, dtype: float64
复制代码


3. 分类


Pandas 有一种特殊的数据类型叫做"目录",即 dtype="category",我们根据将某些列设置为目录来进行分类。


准备数据。


df = pd.DataFrame({"id": [1, 2, 3, 4, 5, 6], "raw_grade": ['a', 'b', 'b', 'a', 'a', 'e']})df
复制代码


| | id | raw_grade |

|---:|-----:|:------------|

| 0 | 1 | a |

| 1 | 2 | b |

| 2 | 3 | b |

| 3 | 4 | a |

| 4 | 5 | a |

| 5 | 6 | e |


我们添加一个新列grade并将它的数据类型设置为category

df["grade"] = df["raw_grade"].astype("category")df["grade"]
复制代码

我们可以看到grade列只有 3 种值 a,b,e。

0    a1    b2    b3    a4    a5    eName: grade, dtype: categoryCategories (3, object): ['a', 'b', 'e']
复制代码

我们按顺序替换 a、b、e 为 very good、good、very bad。

df["grade"].cat.categories = ["very good", "good", "very bad"]
复制代码

此时的表格为:


| | id | raw_grade | grade |

|---:|-----:|:------------|:----------|

| 0 | 1 | a | very good |

| 1 | 2 | b | good |

| 2 | 3 | b | good |

| 3 | 4 | a | very good |

| 4 | 5 | a | very good |

| 5 | 6 | e | very bad |


我们对表格进行排序:


df.sort_values(by="grade", ascending=False)
复制代码


| | id | raw_grade | grade |

|---:|-----:|:------------|:----------|

| 5 | 6 | e | very bad |

| 1 | 2 | b | good |

| 2 | 3 | b | good |

| 0 | 1 | a | very good |

| 3 | 4 | a | very good |

| 4 | 5 | a | very good |


查看各类别的数量:


df.groupby("grade").size()
复制代码

以上代码输出为:


gradevery good    3good         2very bad     1dtype: int64
复制代码


4. IO


Pandas 支持直接从文件中读写数据,如 CSV、JSON、EXCEL 等文件格式。Pandas 支持的文件格式如下。


| Format Type | Data Description | Reader | Writer |

|:-------|----------:|----------:|----------:|

| text | CSV | readcsv | tocsv |

| text | Fixed-Width Text File | read_fwf | |

| text | JSON | readjson | tojson |

| text | HTML | readhtml | tohtml |

| text | Local clipboard | readclipboard | toclipboard |

| | MS Excel | readexcel | toexcel |

| binary | OpenDocument | read_excel | |

| binary | HDF5 Format | readhdf | tohdf |

| binary | Feather Format | readfeather | tofeather |

| binary | Parquet Format | readparquet | toparquet |

| binary | ORC Format | read_orc | |

| binary | Msgpack | readmsgpack | tomsgpack |

| binary | Stata | readstata | tostata |

| binary | SAS | read_sas | |

| binary | SPSS | read_spss | |

| binary | Python Pickle Format | readpickle | topickle |

| SQL | SQL | readsql | tosql |

| SQL | Google BigQuery | readgbq | togbq |


我们仅以 CSV 文件为例作为讲解。其他格式请参考上方表格。


我们从 CSV 文件导入数据。大家不用特别在意下方网址的域名地址

df = pd.read_csv("http://blog.caiyongji.com/assets/housing.csv")
复制代码


查看前 5 行数据:

df.head(5)
复制代码


| | longitude | latitude | housingmedianage | totalrooms | totalbedrooms | population | households | medianincome | medianhousevalue | oceanproximity |

|---:|------------:|-----------:|---------------------:|--------------:|-----------------:|-------------:|-------------:|----------------:|---------------------:|:------------------|

| 0 | -122.23 | 37.88 | 41 | 880 | 129 | 322 | 126 | 8.3252 | 452600 | NEAR BAY |

| 1 | -122.22 | 37.86 | 21 | 7099 | 1106 | 2401 | 1138 | 8.3014 | 358500 | NEAR BAY |

| 2 | -122.24 | 37.85 | 52 | 1467 | 190 | 496 | 177 | 7.2574 | 352100 | NEAR BAY |

| 3 | -122.25 | 37.85 | 52 | 1274 | 235 | 558 | 219 | 5.6431 | 341300 | NEAR BAY |

| 4 | -122.25 | 37.85 | 52 | 1627 | 280 | 565 | 259 | 3.8462 | 342200 | NEAR BAY |


5. 绘图


Pandas 支持 matplotlib,matplotlib 是功能强大的 Python 可视化工具。本节仅对 Pandas 支持的绘图方法进行简单介绍,我们将会在下一篇文章中进行 matplotlib 的详细介绍。为了不错过更新,欢迎大家关注我。


np.random.seed(999)df = pd.DataFrame(np.random.rand(10, 4), columns=['a', 'b', 'c', 'd'])
复制代码

我们直接调用plot方法进行展示。

这里有两个需要注意的地方:


  1. 该 plot 方法是通过 Pandas 调用的 plot 方法,而非 matplotlib。

  2. 我们知道 Python 语言是无需分号进行结束语句的。此处的分号表示执行绘图渲染后直接显示图像。


df.plot();
复制代码



df.plot.bar();
复制代码



df.plot.bar(stacked=True);
复制代码



四、更多


我们下篇将讲解 matplotlib 的相关知识点,欢迎关注机器学习前置教程系列,或我的个人博客[http://blog.caiyongji.com/](http://blog.caiyongji.com/)同步更新。


发布于: 2021 年 03 月 22 日阅读数: 8
用户头像

caiyongji

关注

还未添加个人签名 2017.12.28 加入

还未添加个人简介

评论

发布
暂无评论
前置机器学习(四):一文掌握Pandas用法