开始学习之前,检查一下pandas的版本,要求1.1.4版之上
import numpy as np
import pandas as pd
for module in np,pd:
print(module.__name__, module.__version__)
>>>
numpy 1.18.5
pandas 1.1.5
1. 文件读取
我们经常遇到的原始数据文件有csv/txt/excel等格式,pandas都有想对应的io接口,格式都一样,以csv文件为例,语法格式为:pd.read_csv(file_path)。如果文件在当前的环境目录中,直接写上文件名即可。
# 常用的数据格式有csv/txt/xls
df = pd.read_csv('job.csv')
df.head()
读取后一般都是使用head()函数查看前五行,结果如下:
点开这个函数,可以看到里面有很多可用的参数:
这里,比较常用的有:
- header="infer"为默认,表示用数据的第0行作为列名。如果不想这样,可以传入header=None表示第一行不作为列名
- sep = ',' : 分割参数,它使得用户可以自定义分割符号,一般是传入按照原始文档的分隔符
- index_col表示把某一列或几列作为索引,索引的内容将会在下一个章节中详细阐述
- usecols表示读取列的集合,默认读取所有的列,
- nrows表示读取的数据行数,当一个数据集比较大,几十个G那种,一次性读取太费时,可以指定只读取前1000行等进行查看。这个功能在大数据竞赛中经常使用。
比如,在上面的例子中,我们将职位编号这个字段作为指定的index,同时只读取前10行:
2. 文件写入
当我们希望把处理之后的数据重新保存一份时,就需要将内存中的数据框写入文件。和读取类似,pandas的文件写入io同样支持很多种文件如csv、txt、excel、joson等格式。
语法格式为: df.to_csv(file_name, index = False)
需要注意的是一般是使用index = False,即不将index写入文件
3. pandas基本数据结构
Series和DataFrame是pandas中两种最基本和最重要的数据存储结构,其中:
- Serise存储一列values
- DataFrame存储多列values
在某种意义上,可以认为DataFrame是由多个Series构成,也就是DataFrame种的任意一维都可以看成是一个单独的Series。下面通过代码来加深一下理解
data = {'city' : ['BJ', 'SH', 'GZ', 'SZ'],
'rank': ['A+', 'A+', 'A', 'A-'],
'whether': ['Bad', 'Bad','Good', 'Good' ]
}
# 通过一个列表创建Series
s = pd.Series(data['city'])
print(type(s))
s
>>>
Out[12]:
0 BJ
1 SH
2 GZ
3 SZ
dtype: object
可以通过index和values属性查看Series相应的取值
# 查看Series的index和values
print(s.values)
print(s.index)
print('*' * 10)
s1 = pd.Series(data['city'], index = [i for i in 'ABCD'])
print(s1.values)
print(s1.index)
s1
>>>
['BJ' 'SH' 'GZ' 'SZ']
RangeIndex(start=0, stop=4, step=1)
**********
['BJ' 'SH' 'GZ' 'SZ']
Index(['A', 'B', 'C', 'D'], dtype='object')
Out[11]:
A BJ
B SH
C GZ
D SZ
dtype: object
从上面的代码可以看到创建Series时默认的index为从0开始的整数,但是也可以指定。
对于DataFrame也是同样的,创建的时候可以指定index
同样的可以使用 ”."操作符来访问其属性
print(type(df1.index), df1.index)
print(type(df1.columns),df1.columns)
print(type(df1.values), df1.values)
>>>
Index(['row_0', 'row_1', 'row_2', 'row_3'], dtype='object')
Index(['city', 'rank', 'whether'], dtype='object')
[['BJ' 'A+' 'Bad']
['SH' 'A+' 'Bad']
['GZ' 'A' 'Good']
['SZ' 'A-' 'Good']]
可以看到DataFrame其内部的数据值是一个np.ndarray数据结构,即一个二维数组
4. 基本函数
- head(),tail()查看前/后n行
df = pd.read_csv('job.csv')
df.head().append(df.tail())
备注:可以使用append方法将head和tail拼接同时输出
- 基本信息查看:info/shape/describe等
print(df.shape) # 形状
print(df.columns) # 列名(字段)
print(df.info())
>>>
(975, 8)
Index(['职位编号', '职位名称', '薪资', '公司名称', '工作经验', '学历', '公司性质', '公司规模'], dtype='object')
RangeIndex: 975 entries, 0 to 974
Data columns (total 8 columns):
# Column Non-Null Count Dtype
--- ------ -------------- -----
0 职位编号 975 non-null int64
1 职位名称 975 non-null object
2 薪资 951 non-null object
3 公司名称 975 non-null object
4 工作经验 975 non-null object
5 学历 975 non-null object
6 公司性质 975 non-null object
7 公司规模 964 non-null object
dtypes: int64(1), object(7)
memory usage: 61.1+ KB
None
注意,df.info(),info后面需要加(),该函数可以快速查看是否存在缺失值情况
- 唯一值函数
对序列使用unique和nunique可以分别得到其唯一值组成的列表和唯一值的个数,若要统计每个取值的个数,则需要value_counts()函数
print(df['公司性质'].unique())
print('*' * 10)
print(df['公司性质'].nunique())
print('*' * 10)
print(df['公司性质'].value_counts())
>>>
['民营公司' '合资' '外资(欧美)' '外资(非欧美)' '国企' '上市公司' '事业单位' '创业公司']
**********
8
**********
民营公司 552
外资(欧美) 164
合资 98
外资(非欧美) 89
上市公司 39
国企 30
创业公司 2
事业单位 1
Name: 公司性质, dtype: int64
- 排序函数
排序共有两种方式,其一为值排序,其二为索引排序,对应的函数是sort_values和sort_index。
5、窗口对象
pandas
中有3类窗口,分别是滑动窗口rolling
、扩张窗口expanding
以及指数加权窗口ewm
(Exponentially Weighted Moving)。需要说明的是,以日期偏置为窗口大小的滑动窗口将在第十章讨论,指数加权窗口见本章练习。
1. 滑窗对象
要使用滑窗函数,就必须先要对一个序列使用.rolling
得到滑窗对象,其最重要的参数为窗口大小window
。
s = pd.Series([1,2,3,4,5])
roller = s.rolling(window = 3)
roller
Rolling [window=3,center=False,axis=0]
在得到了滑窗对象后,能够使用相应的聚合函数进行计算,需要注意的是窗口包含当前行所在的元素,例如在第四个位置进行均值运算时,应当计算(2+3+4)/3,而不是(1+2+3)/3:
roller.mean()
0 NaN
1 NaN
2 2.0
3 3.0
4 4.0
dtype: float64
roller.sum()
0 NaN
1 NaN
2 6.0
3 9.0
4 12.0
dtype: float64
对于滑动相关系数或滑动协方差的计算,可以如下写出:
s2 = pd.Series([1,2,6,16,30])
roller.cov(s2)
0 NaN
1 NaN
2 2.5
3 7.0
4 12.0
dtype: float64
roller.corr(s2)
0 NaN
1 NaN
2 0.944911
3 0.970725
4 0.995402
dtype: float64
此外,还支持使用apply
传入自定义函数,其传入值是对应窗口的Series
,例如上述的均值函数可以等效表示:
roller.apply(lambda x:x.mean())
0 NaN
1 NaN
2 2.0
3 3.0
4 4.0
dtype: float64
shift, diff, pct_change
是一组类滑窗函数,它们的公共参数为periods=n
,默认为1,分别表示取向前第n
个元素的值、与向前第n
个元素做差(与Numpy
中不同,后者表示n
阶差分)、与向前第n
个元素相比计算增长率。这里的n
可以为负,表示反方向的类似操作。
s = pd.Series([1,3,6,10,15])
s.shift(2)
0 NaN
1 NaN
2 1.0
3 3.0
4 6.0
dtype: float64
s.diff(3)
0 NaN
1 NaN
2 NaN
3 9.0
4 12.0
dtype: float64
s.pct_change()
0 NaN
1 2.000000
2 1.000000
3 0.666667
4 0.500000
dtype: float64
s.shift(-1)
0 3.0
1 6.0
2 10.0
3 15.0
4 NaN
dtype: float64
s.diff(-2)
0 -5.0
1 -7.0
2 -9.0
3 NaN
4 NaN
dtype: float64
作业于练习:
Ex1:口袋妖怪数据集
现有一份口袋妖怪的数据集,下面进行一些背景说明:
#
代表全国图鉴编号,不同行存在相同数字则表示为该妖怪的不同状态妖怪具有单属性和双属性两种,对于单属性的妖怪,
Type 2
为缺失值Total, HP, Attack, Defense, Sp. Atk, Sp. Def, Speed
分别代表种族值、体力、物攻、防御、特攻、特防、速度,其中种族值为后6项之和
df = pd.read_csv('../data/pokemon.csv')
df.head(3)
# | Name | Type 1 | Type 2 | Total | HP | Attack | Defense | Sp. Atk | Sp. Def | Speed | |
---|---|---|---|---|---|---|---|---|---|---|---|
0 | 1 | Bulbasaur | Grass | Poison | 318 | 45 | 49 | 49 | 65 | 65 | 45 |
1 | 2 | Ivysaur | Grass | Poison | 405 | 60 | 62 | 63 | 80 | 80 | 60 |
2 | 3 | Venusaur | Grass | Poison | 525 | 80 | 82 | 83 | 100 | 100 | 80 |
对
HP, Attack, Defense, Sp. Atk, Sp. Def, Speed
进行加总,验证是否为Total
值。对于
#
重复的妖怪只保留第一条记录,解决以下问题:
- 求第一属性的种类数量和前三多数量对应的种类
- 求第一属性和第二属性的组合种类
- 求尚未出现过的属性组合
- 按照下述要求,构造
Series
:
- 取出物攻,超过120的替换为
high
,不足50的替换为low
,否则设为mid
- 取出第一属性,分别用
replace
和apply
替换所有字母为大写 - 求每个妖怪六项能力的离差,即所有能力中偏离中位数最大的值,添加到
df
并从大到小排序
解答 :
- 取出对应
columns
的列 , 按列求和后与Total
作不等于比较 , 结果应全为False
-
False
的真值为0
, 求和后若值为0
, 则所有行加和都与Total
相同 , 否则为True
的个数 , 也是不等于Total
的个数 - 经验证 , 合为
0
, 是Total
值
(df[df.columns[5:]].sum(1) != df['Total']).sum()
0
df_2 = df.drop_duplicates('#')
df_2['Type 1'].nunique()
18
df_2['Type 1'].value_counts()[:3]
Water 105
Normal 93
Grass 66
Name: Type 1, dtype: int64
- 对
Type 1
和Type 2
两列合并去重 , 得到143
种组合
df_2_2 = df_2.drop_duplicates(['Type 1','Type 2'])[['Type 1','Type 2']]
df_2_2.shape
(143, 2)
- 分别对
Type 1
和Type 2
取唯一值后排列组合所有不同技能组合 - 其中
Type 1
唯一值18
种 ,Type 2
唯一值19
种(含一个NaN
) , 组合后的全部种类数为18*19-18=324
- 将已经存在的
143
种组合与所有组合拼接去重且不保留重复值 , 剩下的就是没有出现过的组合 , 共324-143=181
种
all_com = pd.DataFrame([[i,j] for i in df_2['Type 1'].unique() for j in df_2['Type 2'].unique()if i!=j],columns=['Type 1','Type 2'])
pd.concat([all_com,df_2_2]).drop_duplicates(keep = False)
Type 1 | Type 2 | |
---|---|---|
9 | Grass | Rock |
10 | Grass | Water |
11 | Grass | Electric |
12 | Grass | Fire |
13 | Grass | Dragon |
... | ... | ... |
318 | Flying | Fire |
320 | Flying | Dark |
321 | Flying | Ghost |
322 | Flying | Bug |
323 | Flying | Normal |
181 rows × 2 columns
- 用
mask
或where
都可以逻辑替换
a = df['Attack']
a.mask(a>120,'high').mask(a<50,'low').mask((a<=120)&(a>=50),'mid').head()
0 low
1 mid
2 mid
3 mid
4 mid
Name: Attack, dtype: object
- 对
type 1
中每个唯一值映射
df['Type 1'].replace(df['Type 1'].unique(),[*map(lambda x:x.upper(),df['Type 1'].unique())]).head()
0 GRASS
1 GRASS
2 GRASS
3 GRASS
4 FIRE
Name: Type 1, dtype: object
df['Type 1'].apply(lambda x:x.upper()).head()
0 GRASS
1 GRASS
2 GRASS
3 GRASS
4 FIRE
Name: Type 1, dtype: object
- 取出六项能力 , 按
apply
方法依次对每行求中位数 , 做差 , 绝对值 , 最大值 , 再排序
df['Deviation'] = df[df.columns[5:]].apply(lambda x:abs(x-x.median()).max(),1)
df['Deviation'].sort_values(ascending=False).head()
230 215.0
121 207.5
261 190.0
333 155.0
224 145.0
Name: Deviation, dtype: float64
Ex2:指数加权窗口
- 作为扩张窗口的
ewm
窗口
在扩张窗口中,用户可以使用各类函数进行历史的累计指标统计,但这些内置的统计函数往往把窗口中的所有元素赋予了同样的权重。事实上,可以给出不同的权重来赋给窗口中的元素,指数加权窗口就是这样一种特殊的扩张窗口。
其中,最重要的参数是alpha
,它决定了默认情况下的窗口权重为w i = ( 1 − α ) i , i ∈ { 0 , 1 , . . . , t } w_i=(1−\alpha)^i,i\in{0,1,...,t}wi=(1−α)i,i∈{0,1,...,t},其中i = 0 i=0i=0表示当前元素,i = t i=ti=t表示序列的第一个元素。
从权重公式可以看出,离开当前值越远则权重越小,若记原序列为x xx,更新后的当前元素为y t y_tyt,此时通过加权公式归一化后可知:
y t = ∑ i = 0 t w i x t − i ∑ i = 0 t w i = x t + ( 1 − α ) x t − 1 + ( 1 − α ) 2 x t − 2 + . . . + ( 1 − α ) t x 0 1 + ( 1 − α ) + ( 1 − α ) 2 + . . . + ( 1 − α ) t y_t =\frac{\sum_{i=0}^{t} w_i x_{t-i}}{\sum_{i=0}^{t} w_i} \ =\frac{x_t + (1 - \alpha)x_{t-1} + (1 - \alpha)^2 x_{t-2} + ...+ (1 - \alpha)^{t} x_{0}}{1 + (1 - \alpha) + (1 - \alpha)^2 + ...+ (1 - \alpha)^{t}}yt=∑i=0twi∑i=0twixt−i=1+(1−α)+(1−α)2+...+(1−α)txt+(1−α)xt−1+(1−α)2xt−2+...+(1−α)tx0
对于Series
而言,可以用ewm
对象如下计算指数平滑后的序列:
np.random.seed(0)
s = pd.Series(np.random.randint(-1,2,30).cumsum())
s.head()
0 -1
1 -1
2 -2
3 -2
4 -2
dtype: int32
s.ewm(alpha=0.2).mean().head()
0 -1.000000
1 -1.000000
2 -1.409836
3 -1.609756
4 -1.725845
dtype: float64
请用expanding
窗口实现。
- 作为滑动窗口的
ewm
窗口
从第1问中可以看到,ewm
作为一种扩张窗口的特例,只能从序列的第一个元素开始加权。现在希望给定一个限制窗口n
,只对包含自身最近的n
个窗口进行滑动加权平滑。请根据滑窗函数,给出新的wi
与yt
的更新公式,并通过rolling
窗口实现这一功能。
My solution :
-
expanding
得到序列的扩张对象 , 为其加权即可 , 可以用np.average
求加权平均数
- 其中
weight
是一系列1 − α 1-\alpha1−α的整数幂 , 由于权重值随序列索引值增加而增加 , 1 − α < 0 1-\alpha<01−α<0 , 所以幂指数值应当随序列的索引值增加而减小 - 幂指数值恰好与序列索引的逆序相同 , 因此有三种思路分别为 : 把序列索引值逆序后当成幂指数值 , 或按序列顺序值生成
weight
后再逆序 , 或直接把序列逆序 , 都可以得出加权平均结果
alpha = 0.2
s.expanding().apply(lambda x:np.average(x[::-1],weights = (1-alpha)**np.arange(len(x)))).head()
0 -1.000000
1 -1.000000
2 -1.409836
3 -1.609756
4 -1.725845
dtype: float64
-
expanding
得到的是扩张对象 , 序列长度呈阶梯式增加 ,rolling
窗口为固定长度的序列
- 而指数加权方式不变 , 仅仅是需要加权的序列改变 , 因此只需将
expanding
改为rolling
即可 -
wi
与yt
的更新公式则分两种情况 , 设窗口为n
, 当t < n
时 , 序列长度不够 , 可以更新为NaN
, 也可以用expanding
更新 - 当
t >= n
时 , 更新长度由t
变为n
, 更新后的yt
为 :
y t = ∑ i = 0 n − 1 w i x t − i ∑ i = 0 n − 1 w i = x t + ( 1 − α ) x t − 1 + ( 1 − α ) 2 x t − 2 + . . . + ( 1 − α ) n − 1 x t − n + 1 1 + ( 1 − α ) + ( 1 − α ) 2 + . . . + ( 1 − α ) n − 1 y_t =\frac{\sum_{i=0}^{n-1} w_i x_{t-i}}{\sum_{i=0}^{n-1} w_i} \ =\frac{x_t + (1 - \alpha)x_{t-1} + (1 - \alpha)^2 x_{t-2} + ...+ (1 - \alpha)^{n-1} x_{t-n+1}}{1 + (1 - \alpha) + (1 - \alpha)^2 + ...+ (1 - \alpha)^{n-1}}yt=∑i=0n−1wi∑i=0n−1wixt−i=1+(1−α)+(1−α)2+...+(1−α)n−1xt+(1−α)xt−1+(1−α)2xt−2+...+(1−α)n−1xt−n+1
s.rolling(window=5).apply(lambda x: np.average(x[::-1],weights = (1-alpha)**np.arange(len(x)))).head()
0 NaN
1 NaN
2 NaN
3 NaN
4 -1.725845
dtype: float64
参考:开源内容Joyful Pandas, 作者 DataWhale耿远昊
另外,更多精彩内容也可以微信搜索,并关注公众号:‘Python数据科学家之路“ ,期待您的到来和我交流