这一次的任务主要是熟悉api,了解各个函数的功能,难点还是在合理应用,还需从例子中多总结一些应用场景及特殊用法。
一、文件的读取和写入
1.文件读取
文件读取常用的函数是read_csv
、read_table
、read_txt
,分别对应的是读取csv
,excel
、txt
文件,举一个例子,其他几个函数用法类似:
import pandas as pd
import numpy as np
df_csv = pd.read_csv('data/my_csv.csv')
## 结果如下表
col1 | col2 | col3 | col4 | col5 | |
---|---|---|---|---|---|
0 | 2 | a | 1.4 | apple | 2020/1/1 |
1 | 3 | b | 3.4 | banana | 2020/1/2 |
2 | 6 | c | 2.5 | orange | 2020/1/5 |
3 | 5 | d | 3.2 | lemon | 2020/1/7 |
上述几个函数有一些常用的公共参数:
header=None
:第一行不作为列名index_col=list
:把某一列或几列作为索引,相当于把一列或者几列固定下来,如上表第一列[0,1,2,3],查询数据时总是会有这一列,同样地,index_col
中包含的列在查询数据时也总会有usecols=list
:读取列的集合,默认读取所有parse_dates
:表示需要转化为时间的列nrows
:读取数据行数
相关示例如下:
csv = pd.read_csv('data/my_csv.csv',index_col=['col1','col2'])
csv['col3']
## 结果如下
col1 col2
2 a 1.4
3 b 3.4
6 c 2.5
5 d 3.2
Name: col3, dtype: float64
pd.read_csv('data/my_csv.csv', usecols=['col1','col2', 'col5'], parse_dates=['col5'], nrows=3)
## 结果如下
col1 col2 col5
0 2 a 2020-01-01
1 3 b 2020-01-02
2 6 c 2020-01-05
读取txt
文件时常遇到非空格的分隔符,read_table
中可以通过赋值一个正则表达式给sep
参数实现自定义分割符号,例子如下:
pd.read_table('data/my_table_special_sep.txt')
## 结果如下
col1 |||| col2
0 TS |||| This is an apple.
1 GQ |||| My name is Bob.
2 WT |||| Well done!
3 PT |||| May I help you?
pd.read_table('data/my_table_special_sep.txt', sep=' \|\|\|\| ', engine='python')
## 结果如下
col1 col2
0 TS This is an apple.
1 GQ My name is Bob.
2 WT Well done!
3 PT May I help you?
2.数据写入
主要是有两个函数:df_csv.to_csv(path)
和df_excel.to_excel(path)
,pandas
没有提供to_table
函数,但是可以通过to_csv
保存成txt
文件,并且可以使用自定义分隔符。在进行写入操作时通常会把index
设置为False
,特别是当索引没有意义时。下面是课件中举的例子:
df_csv.to_csv('data/my_csv_saved.csv', index=False)
df_excel.to_excel('data/my_excel_saved.xlsx', index=False)
## 用csv函数保存为txt文件
df_txt.to_csv('data/my_txt_saved.txt', sep='\t', index=False)
二、基本数据结构
pandas
有两种基本的数据结构,存储一维数据的Series
和存储二维数据的DataFrame
,分别记录一些常用的属性和方法方便日后查阅
1、Series
Series
由四个部分组成,分别是序列的值data
、索引index
、存储类型dtype
、序列的名字name
,这些属性可以通过.
的方式获取,比如s.index
Series
大致可以看成是由key-value
构成的一个表,key
是索引值,value
是存储的值,值得注意的在Series
可以同时存储多种类型的数据
2、DataFrame
DataFrame
在 Series
的基础上增加了列索引,一个数据框可以由二维的 data
与行列索引来构造。
常见的取数据方式有以下两种:
[col_name]
:取出一列数据,相当于Series
[col_list]
:取出多列数据,还是一个DataFrame
df_csv = pd.read_csv('data/my_csv.csv')
df_csv['col1']
## 结果
0 2
1 3
2 6
3 5
Name: col1, dtype: int64
df_csv[['col1','col2']]
## 结果
col1 col2
0 2 a
1 3 b
2 6 c
3 5 d
三、常用基本函数
下面的df
为加载的一份数据集,是一个DataFrame
对象
1、汇总函数
df.head(n)
:取前n
行数据,默认n=5
df.tail(n)
:取后n
行数据,默认n=5
df.info()
:返回表的信息概况,主要是列名、不为空的数量、列的数据类型df.describe()
:返回表中列对应的主要统计量,count
、mean
、std
、max
、min
等
2、特征统计函数
在 Series
和 DataFrame
上定义了许多统计函数,从名字就很容易看出的就不写解释了:
df.sum()
df.mean()
df.median()
df.var()
df.std()
df.max()
df.min()
df.quantile(n)
:n
分位数df.count()
:非缺失值个数df.idxmax()
:最大值对应的索引df.mad()
:离差的均值,返回的是一个序列中偏离该序列均值的绝对值大小的均值
上述统计函数可以指定操作的维度axis
,当axis=0
时对每列进行操作,当axis=1
时对每行进行操作,默认为0
3、唯一值函数
df[col_name].unique()
:以列表的形式返回该列属性值df[col_name].unique()
:返回某列属性值个数df[col_name].value_count()
:统计各属性值及其出现次数
如果想要观察多个列组合的唯一值,可以使用 drop_duplicates
。其中的关键参数是 keep
,默认值 first
表示每个组合保留第一次出现的所在行, last
表示保留最后一次出现的所在行, False
表示把所有重复组合所在的行剔除。课程中例子如下:
In [60]: df_demo = df[['Gender','Transfer','Name']]
In [61]: df_demo.drop_duplicates(['Gender', 'Transfer'])
In [62]: df_demo.drop_duplicates(['Gender', 'Transfer'], keep='last')
此外, duplicated
和 drop_duplicates
的功能类似, drop_duplicates
等价于把 duplicated
为 True
的对应行剔除。课程中例子如下:
df_demo.duplicated(['Gender', 'Transfer']).head()
## 结果
0 False
1 False
2 True
3 True
4 True
dtype: bool
注意应用场景,合理的使用可以得到很好的效果,比如得到一个去重的DataFrame
4、替换函数
映射替换:replace
,用法如下:
bfill 则使用后面最近的未被替换的值进行替换## 传入字典的方式将Female替换成0,Male替换成1
df['Gender'].replace({'Female':0, 'Male':1}).head()
## 传入列表的方式将Female替换成0,Male替换成1
df['Gender'].replace(['Female', 'Male'], [0, 1]).head()
## 指定method 参数为 ffill 则为用前面一个最近的未被替换的值进行替换
s.replace([1, 2], method='ffill')
## bfill 则使用后面最近的未被替换的值进行替换
s.replace([1, 2], method='bfill')
逻辑替换:
where
:传入条件为False
的对应行进行替换mask
:传入条件为True
的对应行进行替换
用法如下:
## 不指定替换值时默认用nan替换
s.where(s<0)
## 指定条件和替换值的用法
s.where(s<0, 100)
## mask函数与之类似
s.mask(s<0)
s.mask(s<0, -50)
## 传入条件替换的例子
## 传入的条件只需是与被调用的 Series 索引一致的布尔序列
s_condition= pd.Series([True,False,False,True],index=s.index)
s.mask(s_condition, -50)
5、排序函数
排序共有两种方式,其一为值排序,其二为索引排序,对应的函数是 sort_values
和 sort_index
。用法如下:
## ascending参数为True表示升序,为False表示降序,默认为True
df_demo.sort_values('Height', ascending=False).head()
## 对多个列进行组合排序
df_demo.sort_values(['Weight','Height'],ascending=[True,False]).head()
## 索引排序的用法和值排序完全一致,只不过元素的值在索引中,此时需要指定索引层的名字或者层号,用参数 level 表示
df_demo.sort_index(level=['Grade','Name'],ascending=[True,False]).head()
6、apply方法
apply
方法常用于 DataFrame
的行迭代或者列迭代,apply
参数往往是一个以序列为输入的函数。用法如下:
## 这里的my_mean是一个函数名,可用lambda表达式来代替
df_demo.apply(my_mean)
若指定 axis=1
,那么每次传入函数的就是行元素组成的Series
四、窗口对象
pandas
中有3类窗口,分别是滑动窗口rolling
、扩张窗口expanding
以及指数加权窗口ewm
1、滑动窗口
要使用滑窗函数,就必须先要对一个序列使用.rolling
得到滑窗对象,其最重要的参数为窗口大小window=n
,相当于取n
个数进行操作,不满足n
的置为NaN
,因此返回的序列前n-1
个数为NaN
。用法如下:
s = pd.Series([1,2,3,4,5])
roller = s.rolling(window = 3)
## 滑窗对象可以使用各种聚合函数
roller.mean()
roller.sum()
## 滑动相关系数或滑动协方差的计算需要传入第二个计算对象
roller.cov(s2)
roller.corr(s2)
## 滑窗对象还支持使用 apply 传入自定义函数,其传入值是对应窗口的 Series
roller.apply(lambda x:x.mean())
类滑窗函数,公共参数为 periods=n
,这里的 n
可以为负,表示反方向的类似操作:
s.shift(n)
:向下移动n行s.diff(n)
:与向前第n
个元素做差s.pct_change(n)
:与向前第n
个元素相比计算增长率
2、扩张窗口
扩张窗口又称累计窗口,可以理解为一个动态长度的窗口,其窗口的大小就是从序列开始处到具体操作的对应位置,其使用的聚合函数会作用于这些逐步扩张的窗口上。具体地说,设序列为a1, a2, a3, a4,则其每个位置对应的窗口即[a1]、[a1, a2]、[a1, a2, a3]、[a1, a2, a3, a4]。教材中例子如下:
s = pd.Series([1, 3, 6, 10])
s.expanding().mean()
## 结果
0 1.000000
1 2.000000
2 3.333333
3 5.000000
dtype: float64
五、练习
Ex1:口袋妖怪数据集
1、对 HP, Attack, Defense, Sp. Atk, Sp. Def, Speed
进行加总,验证是否为 Total
值。
df = pd.read_csv('data/pokemon.csv')
df_demo = df[['HP','Attack','Defense', 'Sp. Atk', 'Sp. Def', 'Speed']]
df['Total'] == df_demo.sum(axis=1)
2、对于 #
重复的妖怪只保留第一条记录,解决以下问题:
df_demo = df.drop_duplicates(['#'], keep='first')
a.求第一属性的种类数量和前三多数量对应的种类
df_demo['Type 1'].nunique()
## 结果:18
df_demo['Type 1'].value_counts().head(3)
## 结果
Water 105
Normal 93
Grass 66
Name: Type 1, dtype: int64
b.求第一属性和第二属性的组合种类
df_demo[['Type 1','Type 2']].value_counts()
## 结果
Type 1 Type 2
Normal Flying 23
Grass Poison 14
Bug Flying 13
Poison 11
Water Ground 9
..
Fighting Dark 1
Psychic Ghost 1
Ground Electric 1
Electric Ghost 1
Water Steel 1
Length: 125, dtype: int64
c.求尚未出现过的属性组合
## 参考答案
## 得到所有组合数的列表
L_full = [i+" "+j for i in df_demo['Type 1'].unique() for j in (df_demo['Type 1'].unique() + [''])]
## 得到现有组合的列表
L_part = [i+" "+j for i,j in zip(df_demo['Type 1'], df_demo['Type 2'].replace(np.nan, ''))]
## 计算差集
res = set(L_full).difference(set(L_part))
len(res)
3、按照下述要求,构造 Series
:
a.取出物攻,超过120的替换为 high
,不足50的替换为 low
,否则设为 mid
df_attack = df['Attack']
df_attack.mask(df_attack < 50,'low').mask(df_attack > 120,'high').mask((df_attack>=50)&(df_attack<=120),'mid')
本来打算直接替换三次的,发现第二次替换之后有部分数据从int变成str了,因此报错,看了参考答案之后发现可以连续用,这样的实现方式确实很优雅
b.取出第一属性,分别用 replace
和 apply
替换所有字母为大写
df['Type 1'].str.upper()
## 参考答案
df['Type 1'].replace({i:str.upper(i) for i in df['Type 1'].unique()}).head()
df['Type 1'].apply(lambda x:str.upper(x)).head()
没看清题目,看到答案一脸懵逼
c.求每个妖怪六项能力的离差,即所有能力中偏离中位数最大的值,添加到 df
并从大到小排序
df['mad'] = df[['HP','Attack','Defense','Sp. Atk','Sp. Def','Speed']].mad(axis=1)
df.sort_values(['mad'], ascending=False)
Ex2:指数加权窗口
- 作为扩张窗口的
ewm
窗口
在扩张窗口中,用户可以使用各类函数进行历史的累计指标统计,但这些内置的统计函数往往把窗口中的所有元素赋予了同样的权重。事实上,可以给出不同的权重来赋给窗口中的元素,指数加权窗口就是这样一种特殊的扩张窗口。
其中,最重要的参数是 alpha
,它决定了默认情况下的窗口权重为
w
i
=
(
1
−
α
)
i
,
i
∈
0
,
1
,
.
.
.
,
t
w_i=(1−α)^i,i∈{0,1,...,t}
wi=(1−α)i,i∈0,1,...,t ,其中
i
=
t
i=t
i=t 表示当前元素,
i
=
0
i=0
i=0表示序列的第一个元素。
从权重公式可以看出,离开当前值越远则权重越小,若记原序列为 x
,更新后的当前元素为
y
t
y_t
yt ,此时通过加权公式归一化后可知:
KaTeX parse error: No such environment: split at position 7: \begin{̲s̲p̲l̲i̲t̲}̲y_t &=\frac{\su…
对于 Series
而言,可以用 ewm
对象如下计算指数平滑后的序列:请用 expanding
窗口实现。
## 参考答案
def ewm_func(x, alpha=0.2):
win = (1-alpha)**np.arange(len(x))[::-1]
res = (win*x).sum()/win.sum()
return res
s.expanding().apply(ewm_func).head()
## 结果
0 -1.000000
1 -1.000000
2 -1.262295
3 -1.390244
4 -1.464541
dtype: float64
想到了应该怎么用,没有想到怎么写apply的函数,还是python基础不太好,记录一下思路:这里利用列表实现累加,注意这里要从后往前取数,因为公式是xi * xt-i
- 作为滑动窗口的
ewm
窗口
从第1问中可以看到, ewm
作为一种扩张窗口的特例,只能从序列的第一个元素开始加权。现在希望给定一个限制窗口 n
,只对包含自身的最近的 n
个元素作为窗口进行滑动加权平滑。请根据滑窗函数,给出新的 wi 与 yt 的更新公式,并通过 rolling
窗口实现这一功能。
w i = ( 1 − α ) i , i ∈ 0 , 1 , . . . , n − 1 w_i=(1−α)^i,i∈{0,1,...,n-1} wi=(1−α)i,i∈0,1,...,n−1
KaTeX parse error: No such environment: split at position 7: \begin{̲s̲p̲l̲i̲t̲}̲y_t &=\frac{\su…
s.rolling(window=2).apply(ewm_func)
## 结果
0 NaN
1 -1.000000
2 -1.444444
3 -2.000000
4 -2.000000
dtype: float64