Pandas 02 - 进阶操作和数据可视化

193 阅读13分钟

进阶操作和数据可视化

import numpy as np
import pandas as pd
import matplotlib
import matplotlib.pyplot as plt
%matplotlib inline

一、进阶操作

主要包括了数据删减和数据填充。

1. 数据删减

首先我们先导入数据,使用 pd.read_csv 传入对应的文件路径或者URL进行导入。

df = pd.read_csv("https://labfile.oss.aliyuncs.com/courses/906/los_census.csv")
df
.dataframe tbody tr th:only-of-type { vertical-align: middle; } .dataframe tbody tr th { vertical-align: top; } .dataframe thead th { text-align: right; }
Zip Code Total Population Median Age Total Males Total Females Total Households Average Household Size
0 91371 1 73.5 0 1 1 1.00
1 90001 57110 26.6 28468 28642 12971 4.40
2 90002 51223 25.5 24876 26347 11731 4.36
3 90003 66266 26.3 32631 33635 15642 4.22
4 90004 62180 34.8 31302 30878 22547 2.73
... ... ... ... ... ... ... ...
314 93552 38158 28.4 18711 19447 9690 3.93
315 93553 2138 43.3 1121 1017 816 2.62
316 93560 18910 32.4 9491 9419 6469 2.92
317 93563 388 44.5 263 125 103 2.53
318 93591 7285 30.9 3653 3632 1982 3.67

319 rows × 7 columns

虽然我们可以通过数据选择方法从一个完整的数据集中拿到我们需要的数据,但有的时候直接删除不需要的数据更加简单直接。Pandas 中,以 .drop 开头的方法都与数据删减有关。

DataFrame.drop 可以直接去掉数据集中指定的列和行。一般在使用时,我们指定 labels 标签参数,然后再通过 axis 指定按列或按行删除即可。当然,你也可以通过索引参数删除数据,具体查看官方文档 pandas.pydata.org/pandas-docs…

# axis默认为 0, 是索引(行), 1 是列
# 注意此处的删除方法并不会改变原来的DataFrame,只会返回一个新的DataFrame结果
res = df.drop(labels=['Median Age', 'Total Males'], axis=1)
res
.dataframe tbody tr th:only-of-type { vertical-align: middle; } .dataframe tbody tr th { vertical-align: top; } .dataframe thead th { text-align: right; }
Zip Code Total Population Total Females Total Households Average Household Size
0 91371 1 1 1 1.00
1 90001 57110 28642 12971 4.40
2 90002 51223 26347 11731 4.36
3 90003 66266 33635 15642 4.22
4 90004 62180 30878 22547 2.73
... ... ... ... ... ...
314 93552 38158 19447 9690 3.93
315 93553 2138 1017 816 2.62
316 93560 18910 9419 6469 2.92
317 93563 388 125 103 2.53
318 93591 7285 3632 1982 3.67

319 rows × 5 columns

DataFrame.drop_duplicates 则通常用于数据去重,即剔除数据集中的重复值。使用方法非常简单,默认情况下,它会根据所有列删除重复的行。也可以使用 subset 指定要删除的特定列上的重复项,要删除重复项并保留最后一次出现,请使用 keep='last'。具体可查看官方文档:pandas.pydata.org/pandas-docs…

d = pd.DataFrame([[1, 2, 3], [1, 2, 3]])
d
.dataframe tbody tr th:only-of-type { vertical-align: middle; } .dataframe tbody tr th { vertical-align: top; } .dataframe thead th { text-align: right; }
0 1 2
0 1 2 3
1 1 2 3
res = d.drop_duplicates()
res
.dataframe tbody tr th:only-of-type { vertical-align: middle; } .dataframe tbody tr th { vertical-align: top; } .dataframe thead th { text-align: right; }
0 1 2
0 1 2 3

可以看见重复的行被去掉了

res = df.drop_duplicates()
res
.dataframe tbody tr th:only-of-type { vertical-align: middle; } .dataframe tbody tr th { vertical-align: top; } .dataframe thead th { text-align: right; }
Zip Code Total Population Median Age Total Males Total Females Total Households Average Household Size
0 91371 1 73.5 0 1 1 1.00
1 90001 57110 26.6 28468 28642 12971 4.40
2 90002 51223 25.5 24876 26347 11731 4.36
3 90003 66266 26.3 32631 33635 15642 4.22
4 90004 62180 34.8 31302 30878 22547 2.73
... ... ... ... ... ... ... ...
314 93552 38158 28.4 18711 19447 9690 3.93
315 93553 2138 43.3 1121 1017 816 2.62
316 93560 18910 32.4 9491 9419 6469 2.92
317 93563 388 44.5 263 125 103 2.53
318 93591 7285 30.9 3653 3632 1982 3.67

319 rows × 7 columns

除此之外,另一个用于数据删减的方法 DataFrame.dropna 也十分常用,其主要的用途是删除缺少值,即数据集中空缺的数据列或行。官方文档如下:pandas.pydata.org/pandas-docs…

df.dropna()
.dataframe tbody tr th:only-of-type { vertical-align: middle; } .dataframe tbody tr th { vertical-align: top; } .dataframe thead th { text-align: right; }
Zip Code Total Population Median Age Total Males Total Females Total Households Average Household Size
0 91371 1 73.5 0 1 1 1.00
1 90001 57110 26.6 28468 28642 12971 4.40
2 90002 51223 25.5 24876 26347 11731 4.36
3 90003 66266 26.3 32631 33635 15642 4.22
4 90004 62180 34.8 31302 30878 22547 2.73
... ... ... ... ... ... ... ...
314 93552 38158 28.4 18711 19447 9690 3.93
315 93553 2138 43.3 1121 1017 816 2.62
316 93560 18910 32.4 9491 9419 6469 2.92
317 93563 388 44.5 263 125 103 2.53
318 93591 7285 30.9 3653 3632 1982 3.67

319 rows × 7 columns

s_1 = pd.Series({'a':1, 'b':0})
s_2 = pd.Series({'c':0, 'd':1})

d = pd.DataFrame({'one':s_1, 'two':s_2})
d
.dataframe tbody tr th:only-of-type { vertical-align: middle; } .dataframe tbody tr th { vertical-align: top; } .dataframe thead th { text-align: right; }
one two
a 1.0 NaN
b 0.0 NaN
c NaN 0.0
d NaN 1.0
# 被删完了
d.dropna()
.dataframe tbody tr th:only-of-type { vertical-align: middle; } .dataframe tbody tr th { vertical-align: top; } .dataframe thead th { text-align: right; }
one two

2. 数据填充

既然提到了数据删减,反之则可能会遇到数据填充的情况。而对于一个给定的数据集而言,我们一般不会乱填数据,而更多的是对缺失值进行填充。

在真实的生产环境中,我们需要处理的数据文件往往没有想象中的那么美好。其中,很大几率会遇到的情况就是缺失值。缺失值主要是指数据丢失的现象,也就是数据集中的某一块数据不存在。除此之外、存在但明显不正确的数据也被归为缺失值一类。例如,在一个时间序列数据集中,某一段数据突然发生了时间流错乱,那么这一小块数据就是毫无意义的,可以被归为缺失值。

a. 检测缺失值

Pandas 为了更方便地检测缺失值,将不同类型数据的缺失均采用 NaN 标记。这里的 NaN 代表 Not a Number,它仅仅是作为一个标记。例外是,在时间序列里,时间戳的丢失采用 NaT 标记。

Pandas 中用于检测缺失值主要用到两个方法,分别是:isna() 和 notna(),故名思意就是「是缺失值」和「不是缺失值」。默认会返回布尔值用于判断。

接下来,我们人为生成一组包含缺失值的示例数据。

df = pd.DataFrame(np.random.rand(9, 5), columns=list('ABCDE'))
# 插入 T 列,并打上时间戳,位于第一列,列名为Time,值格式为 year-month-day
df.insert(value=pd.Timestamp('2017-10-1'), loc=0, column='Time')
# 将 1, 3, 5 列的 2,4,6,8 行置为缺失值
df.iloc[[1, 3, 5, 7], [0, 2, 4]] = np.nan
# 将 2, 4, 6 列的 3,5,7,9 行置为缺失值
df.iloc[[2, 4, 6, 8], [1, 3, 5]] = np.nan
df
.dataframe tbody tr th:only-of-type { vertical-align: middle; } .dataframe tbody tr th { vertical-align: top; } .dataframe thead th { text-align: right; }
Time A B C D E
0 2017-10-01 0.507580 0.874961 0.419794 0.060446 0.305354
1 NaT 0.894893 NaN 0.878649 NaN 0.369033
2 2017-10-01 NaN 0.552961 NaN 0.861484 NaN
3 NaT 0.584948 NaN 0.395033 NaN 0.521180
4 2017-10-01 NaN 0.989159 NaN 0.394060 NaN
5 NaT 0.611802 NaN 0.851199 NaN 0.837661
6 2017-10-01 NaN 0.206924 NaN 0.318263 NaN
7 NaT 0.809371 NaN 0.858904 NaN 0.673465
8 2017-10-01 NaN 0.151308 NaN 0.896287 NaN

然后,通过 isna() 或 notna() 中的一个即可确定数据集中的缺失值。

df.isna()
.dataframe tbody tr th:only-of-type { vertical-align: middle; } .dataframe tbody tr th { vertical-align: top; } .dataframe thead th { text-align: right; }
Time A B C D E
0 False False False False False False
1 True False True False True False
2 False True False True False True
3 True False True False True False
4 False True False True False True
5 True False True False True False
6 False True False True False True
7 True False True False True False
8 False True False True False True
df.notna()
.dataframe tbody tr th:only-of-type { vertical-align: middle; } .dataframe tbody tr th { vertical-align: top; } .dataframe thead th { text-align: right; }
Time A B C D E
0 True True True True True True
1 False True False True False True
2 True False True False True False
3 False True False True False True
4 True False True False True False
5 False True False True False True
6 True False True False True False
7 False True False True False True
8 True False True False True False

上面已经对缺省值的产生、检测进行了介绍。实际上,面对缺失值一般就是填充和剔除两项操作。填充和清除都是两个极端。如果你感觉有必要保留缺失值所在的列或行,那么就需要对缺失值进行填充。如果没有必要保留,就可以选择清除缺失值。

其中,缺失值剔除的方法 dropna() 已经在上面介绍过了。下面来看一看填充缺失值 fillna() 方法

首先,我们可以用相同的标量值替换 NaN,比如用 0。

# 同理fillna也不会改变原DataFrame
df.fillna(0)
.dataframe tbody tr th:only-of-type { vertical-align: middle; } .dataframe tbody tr th { vertical-align: top; } .dataframe thead th { text-align: right; }
Time A B C D E
0 2017-10-01 00:00:00 0.507580 0.874961 0.419794 0.060446 0.305354
1 0 0.894893 0.000000 0.878649 0.000000 0.369033
2 2017-10-01 00:00:00 0.000000 0.552961 0.000000 0.861484 0.000000
3 0 0.584948 0.000000 0.395033 0.000000 0.521180
4 2017-10-01 00:00:00 0.000000 0.989159 0.000000 0.394060 0.000000
5 0 0.611802 0.000000 0.851199 0.000000 0.837661
6 2017-10-01 00:00:00 0.000000 0.206924 0.000000 0.318263 0.000000
7 0 0.809371 0.000000 0.858904 0.000000 0.673465
8 2017-10-01 00:00:00 0.000000 0.151308 0.000000 0.896287 0.000000
df
.dataframe tbody tr th:only-of-type { vertical-align: middle; } .dataframe tbody tr th { vertical-align: top; } .dataframe thead th { text-align: right; }
Time A B C D E
0 2017-10-01 0.507580 0.874961 0.419794 0.060446 0.305354
1 NaT 0.894893 NaN 0.878649 NaN 0.369033
2 2017-10-01 NaN 0.552961 NaN 0.861484 NaN
3 NaT 0.584948 NaN 0.395033 NaN 0.521180
4 2017-10-01 NaN 0.989159 NaN 0.394060 NaN
5 NaT 0.611802 NaN 0.851199 NaN 0.837661
6 2017-10-01 NaN 0.206924 NaN 0.318263 NaN
7 NaT 0.809371 NaN 0.858904 NaN 0.673465
8 2017-10-01 NaN 0.151308 NaN 0.896287 NaN

除了直接填充值,我们还可以通过参数,将缺失值前面或者后面的值填充给相应的缺失值。例如使用缺失值前面的值进行填充:

# 使用上面的值而非左边的值
df.fillna(method='pad')
.dataframe tbody tr th:only-of-type { vertical-align: middle; } .dataframe tbody tr th { vertical-align: top; } .dataframe thead th { text-align: right; }
Time A B C D E
0 2017-10-01 0.507580 0.874961 0.419794 0.060446 0.305354
1 2017-10-01 0.894893 0.874961 0.878649 0.060446 0.369033
2 2017-10-01 0.894893 0.552961 0.878649 0.861484 0.369033
3 2017-10-01 0.584948 0.552961 0.395033 0.861484 0.521180
4 2017-10-01 0.584948 0.989159 0.395033 0.394060 0.521180
5 2017-10-01 0.611802 0.989159 0.851199 0.394060 0.837661
6 2017-10-01 0.611802 0.206924 0.851199 0.318263 0.837661
7 2017-10-01 0.809371 0.206924 0.858904 0.318263 0.673465
8 2017-10-01 0.809371 0.151308 0.858904 0.896287 0.673465
# 使用下面的值而非右边的值
df.fillna(method='bfill')
.dataframe tbody tr th:only-of-type { vertical-align: middle; } .dataframe tbody tr th { vertical-align: top; } .dataframe thead th { text-align: right; }
Time A B C D E
0 2017-10-01 0.507580 0.874961 0.419794 0.060446 0.305354
1 2017-10-01 0.894893 0.552961 0.878649 0.861484 0.369033
2 2017-10-01 0.584948 0.552961 0.395033 0.861484 0.521180
3 2017-10-01 0.584948 0.989159 0.395033 0.394060 0.521180
4 2017-10-01 0.611802 0.989159 0.851199 0.394060 0.837661
5 2017-10-01 0.611802 0.206924 0.851199 0.318263 0.837661
6 2017-10-01 0.809371 0.206924 0.858904 0.318263 0.673465
7 2017-10-01 0.809371 0.151308 0.858904 0.896287 0.673465
8 2017-10-01 NaN 0.151308 NaN 0.896287 NaN
na = np.linspace(-100,100,25)
na = np.reshape(na,(5,5))
d = pd.DataFrame(na)
d
.dataframe tbody tr th:only-of-type { vertical-align: middle; } .dataframe tbody tr th { vertical-align: top; } .dataframe thead th { text-align: right; }
0 1 2 3 4
0 -100.000000 -91.666667 -83.333333 -75.000000 -66.666667
1 -58.333333 -50.000000 -41.666667 -33.333333 -25.000000
2 -16.666667 -8.333333 0.000000 8.333333 16.666667
3 25.000000 33.333333 41.666667 50.000000 58.333333
4 66.666667 75.000000 83.333333 91.666667 100.000000
# 不能对Padnas里面的数据直接进行切片
# 要使用iloc[]或者loc[]
d.iloc[:,0:5:2] = np.nan
d
.dataframe tbody tr th:only-of-type { vertical-align: middle; } .dataframe tbody tr th { vertical-align: top; } .dataframe thead th { text-align: right; }
0 1 2 3 4
0 NaN -91.666667 NaN -75.000000 NaN
1 NaN -50.000000 NaN -33.333333 NaN
2 NaN -8.333333 NaN 8.333333 NaN
3 NaN 33.333333 NaN 50.000000 NaN
4 NaN 75.000000 NaN 91.666667 NaN
d.iloc[4,0:5:2] = 1 
d
.dataframe tbody tr th:only-of-type { vertical-align: middle; } .dataframe tbody tr th { vertical-align: top; } .dataframe thead th { text-align: right; }
0 1 2 3 4
0 NaN -91.666667 NaN -75.000000 NaN
1 NaN -50.000000 NaN -33.333333 NaN
2 NaN -8.333333 NaN 8.333333 NaN
3 NaN 33.333333 NaN 50.000000 NaN
4 1.0 75.000000 1.0 91.666667 1.0
d.fillna(method='bfill')
.dataframe tbody tr th:only-of-type { vertical-align: middle; } .dataframe tbody tr th { vertical-align: top; } .dataframe thead th { text-align: right; }
0 1 2 3 4
0 1.0 -91.666667 1.0 -75.000000 1.0
1 1.0 -50.000000 1.0 -33.333333 1.0
2 1.0 -8.333333 1.0 8.333333 1.0
3 1.0 33.333333 1.0 50.000000 1.0
4 1.0 75.000000 1.0 91.666667 1.0

可以看到,连续缺失值也是按照前序数值进行填充的,并且完全填充。这里,我们可以通过 limit= 参数设置连续填充的限制数量。

# 只让它填充两行
d.fillna(method = 'bfill', limit = 2)
.dataframe tbody tr th:only-of-type { vertical-align: middle; } .dataframe tbody tr th { vertical-align: top; } .dataframe thead th { text-align: right; }
0 1 2 3 4
0 NaN -91.666667 NaN -75.000000 NaN
1 NaN -50.000000 NaN -33.333333 NaN
2 1.0 -8.333333 1.0 8.333333 1.0
3 1.0 33.333333 1.0 50.000000 1.0
4 1.0 75.000000 1.0 91.666667 1.0

除了上面的填充方式,还可以通过 Pandas 自带的求平均值方法等来填充特定列或行。举个例子:

df.fillna(df.mean()['C':'E'])
F:\Temp1/ipykernel_3532/3952353766.py:1: FutureWarning: DataFrame.mean and DataFrame.median with numeric_only=None will include datetime64 and datetime64tz columns in a future version.
  df.fillna(df.mean()['C':'E'])
.dataframe tbody tr th:only-of-type { vertical-align: middle; } .dataframe tbody tr th { vertical-align: top; } .dataframe thead th { text-align: right; }
Time A B C D E
0 2017-10-01 0.507580 0.874961 0.419794 0.060446 0.305354
1 NaT 0.894893 NaN 0.878649 0.506108 0.369033
2 2017-10-01 NaN 0.552961 0.680716 0.861484 0.541338
3 NaT 0.584948 NaN 0.395033 0.506108 0.521180
4 2017-10-01 NaN 0.989159 0.680716 0.394060 0.541338
5 NaT 0.611802 NaN 0.851199 0.506108 0.837661
6 2017-10-01 NaN 0.206924 0.680716 0.318263 0.541338
7 NaT 0.809371 NaN 0.858904 0.506108 0.673465
8 2017-10-01 NaN 0.151308 0.680716 0.896287 0.541338

上面是对 C 列到 E 列用平均值填充。

d = pd.DataFrame(np.ones((3,3)))
d.iloc[::2] = np.nan
d
.dataframe tbody tr th:only-of-type { vertical-align: middle; } .dataframe tbody tr th { vertical-align: top; } .dataframe thead th { text-align: right; }
0 1 2
0 NaN NaN NaN
1 1.0 1.0 1.0
2 NaN NaN NaN
d.mean()
0    1.0
1    1.0
2    1.0
dtype: float64

可以看出mean方法是按列并剔除了NAN进行求得均值,并返回一个Series一维数组

d.mean()[0:2]
0    1.0
1    1.0
dtype: float64

b. 插值填充

插值是数值分析中一种方法。简而言之,就是借助于一个函数(线性或非线性),再根据已知数据去求解未知数据的值。插值在数据领域非常常见,它的好处在于,可以尽量去还原数据本身的样子。(DIP中也会使用到)

我们可以通过 interpolate() 方法完成线性插值。当然,其他一些插值算法可以阅读官方文档了解。

# 生成一个 DataFrame
df = pd.DataFrame({'A': [1.1, 2.2, np.nan, 4.5, 5.7, 6.9],
                   'B': [.21, np.nan, np.nan, 3.1, 11.7, 13.2]})
df
.dataframe tbody tr th:only-of-type { vertical-align: middle; } .dataframe tbody tr th { vertical-align: top; } .dataframe thead th { text-align: right; }
A B
0 1.1 0.21
1 2.2 NaN
2 NaN NaN
3 4.5 3.10
4 5.7 11.70
5 6.9 13.20

对于上面存在的缺失值,如果通过前后值,或者平均值来填充是不太能反映出趋势的。这时候,插值最好使。我们用默认的线性插值试一试。

df_interpolate = df.interpolate()
df_interpolate
.dataframe tbody tr th:only-of-type { vertical-align: middle; } .dataframe tbody tr th { vertical-align: top; } .dataframe thead th { text-align: right; }
A B
0 1.10 0.210000
1 2.20 1.173333
2 3.35 2.136667
3 4.50 3.100000
4 5.70 11.700000
5 6.90 13.200000

下图展示了插值后的数据,明显看出插值结果符合数据的变化趋势。如果按照前后数据顺序填充,则无法做到这一点。

# 谨记:iloc是索引数字切片,loc是标记切片可以为字符串
plt.plot(list(df_interpolate.loc[:,'A']),list(df_interpolate.loc[:,'B']) )
plt.grid(True)
plt.show()

download.png

对于 interpolate() 支持的插值算法,也就是 method= 。下面给出几条选择的建议:

  1. 如果你的数据增长速率越来越快,可以选择 method='quadratic' 二次插值。
  2. 如果数据集呈现出累计分布的样子,推荐选择 method='pchip'
  3. 如果需要填补缺省值,以平滑绘图为目标,推荐选择 method='akima'

当然,最后提到的 method='akima',需要你的环境中安装了 Scipy 库。除此之外,method='barycentric'method='pchip' 同样也需要 Scipy 才能使用。

二、数据可视化

NumPy,Pandas,Matplotlib 构成了一个完善的数据分析生态圈,所以 3 个工具的兼容性也非常好,甚至共享了大量的接口。当我们的数据是以 DataFrame 格式呈现时,可以直接使用 Pandas 提供的 DataFrame.plot 方法调用 Matplotlib 接口绘制常见的图形。

例如,我们使用上面插值后的数据 df_interpolate 绘制线形图。

df_interpolate.plot()
<AxesSubplot:>




download.png

其他样式的图形也很简单,指定 kind= 参数即可。

df_interpolate.plot(kind='bar')
<AxesSubplot:>




download.png

更多的图形样式和参数,阅读官方文档中的详细说明。Pandas 绘图虽然不可能做到 Matplotlib 的灵活性,但是其简单易用,适合于数据的快速呈现和预览。官方文档:pandas.pydata.org/pandas-docs…

三、补充用法

由于 Pandas 包含的内容实在太多,除了阅读完整的官方文档,很难做到通过一个实验或者一个课程进行全面了解。当然,本课程的目的是带大家熟悉 Pandas 的常用基础方法,至少你大致清楚了 Pandas 是什么,能干什么。

除了上面提到的一些方法和技巧,实际上 Pandas 常用的还有:

  1. 数据计算,例如:DataFrame.add 等。pandas.pydata.org/pandas-docs…
  2. 数据聚合,例如:DataFrame.groupby 等。pandas.pydata.org/pandas-docs…
  3. 统计分析,例如:DataFrame.abs 等。pandas.pydata.org/pandas-docs…
  4. 时间序列,例如:DataFrame.shift 等。pandas.pydata.org/pandas-docs…

四、小结

本次实验,我们着重介绍了 Pandas 的数据结构,你需要对 Series 和 DataFrame 有深入的认识,才能对后面采用 Pandas 进行数据预处理有更深刻的理解。除此之外,我们了解了 Pandas 数据读取、数据选择、数据删减、数据填充的方法和技巧,希望大家后续结合官方文档进一步深入理解。