本章主要学习的pandas的相关基础,如下图
文件读写
读取文件
使用一段代码来进行演示
# 读取csv文件
df_csv = pd.read_csv('../data/my_csv.csv')
# 读取txt文件
df_txt = pd.read_table('../data/my_table.txt')
# 读取excel文件
df_excel = pd.read_excel('../data/my_excel.xlsx')
# 如果不想将第一行当作列名,可以使用header=None
df_excel = pd.read_excel('../data/my_excel.xlsx', header=None)
# index_col表示把某一列或几列作为索引
pd.read_csv('../data/my_csv.csv', index_col=['col1', 'col2'])
文件写入
一般在数据写入中,最常用的操作是把index设置为False,特别当索引没有特殊意义的时候,这样的行为能把索引在保存的时候去除。
df_csv.to_csv('../data/my_csv_saved.csv', index=False)
df_excel.to_excel('../data/my_excel_saved.xlsx', index=False)
pandas中没有定义to_table函数,但是to_csv可以保存为txt文件,并且允许自定义分隔符,常用制表符\t分割:
df_txt.to_csv('../data/my_txt_saved.txt', sep='\t', index=False)
如果想要把表格快速转换为markdown和latex语言,可以使用to_markdown和to_latex函数,此处需要安装tabulate包。
df_csv.to_markdown()
df_csv.to_latex()
基本数据结构
Pandas中定义了两种基本的数据结构,Series和DataFrame
- Series
Series一般由四个部分组成,分别是序列的值data、索引index、存储类型dtype、序列的名字name。其中,索引也可以指定它的名字,默认为空。这四个属性可以通过以下的方式获取
s = pd.Series(data = [100, 'a', {'dic1':5}],
index = pd.Index(['id1', 20, 'third'], name='my_idx'),
dtype = 'object',
name = 'my_name')
# data
s.value
# 获取index
s.index
# 获取dtype
s.dtype
# 获取name
s.name
# 通过.shape可以获取序列的长度
s.shape
其中object代表一种混合数据类型
- DataFrame
DataFrame在Series的基础上增加了列索引。可以通过
[index_item]
获取对应的列的值
data = [[1, 'a', 1.2], [2, 'b', 2.2], [3, 'c', 3.2]]
df = pd.DataFrame(data = data,
index = ['row_%d'%i for i in range(3)],
columns=['col_0', 'col_1', 'col_2'])
# 获取dataframe的值
df.value
# 获取dataframe的index
df.index
# 获取dataframe的column
df.columns
# 获取dataframe的shape
df.shape
# 获取指定的单列或多列
df['col_1'] #单列
df[['col_1', 'col_2']] #多列
# 对dataframe进行转置
df.T
常用基本函数
汇总函数
head
,tail
用于获取表或者序列的前n行和后n行,n的默认值是5
info
,describe
返回表的信息概况和表中数值列对应的主要统计量
在后面的章节中将会提高具有更高信息概括功能的pandas-profiling
包
df.head(5)
df.tail(5)
df.info()
df.describe()
特征统计函数
最常见的是sum, mean, median, var, std, max, min
"""以下函数均可通过axis参数指定操作的方向,默认是axis=0"""
df.sum()
df.mean()
df.median()
df.var()
df.std()
df.max()
df.min()
quantile, cout, idxmax, idxmin
返回的是分位数, 非缺失值个数, 最大值对应的索引及最小值对应的索引
唯一值函数
- 对序列使用
unique
和nunique
可以分别得到其唯一值组成的列表和唯一值的个数value_counts
可以得到唯一值和其对应出现的频数:- 如果想要观察多个列组合的唯一值,可以使用
drop_duplicates
。其中的关键参数是keep
,默认值first
表示每个组合保留第一次出现的所在行,last
表示保留最后一次出现的所在行,False
表示把所有重复组合所在的行剔除duplicated
和drop_duplicates
的功能类似,但前者返回了是否为唯一值的布尔列表,其keep
参数与后者一致。其返回的序列,把重复元素设为True
,否则为False
。drop_duplicates
等价于把duplicated
为True
的对应行剔除。
替换函数(映射替换)
- 在
replace
中,可以通过字典构造,或者传入两个列表来进行替换replace
还有一种特殊的方向替换,指定method
参数为ffill
则为用前面一个最近的未被替换的值进行替换,bfill
则使用后面最近的未被替换的值进行替换
df['Gender'].replace({'Female':0, 'Male':1}).head()
s = pd.Series(['a', 1, 'b', 2, 1, 1, 'a'])
s.replace([1, 2], method='ffill')
s.replace([1, 2], method='bfill')
替换函数(逻辑替换)
逻辑替换包括了where
和mask
,这两个函数是完全对称的:where
函数在传入条件为False
的对应行进行替换,而mask
在传入条件为True
的对应行进行替换,当不指定替换值时,替换为缺失值。clip
函数可以对超出范围的值进行截断
s = pd.Series([-1, 1.2345, 100, -50])
s.where(s<0)
s.where(s<0, 100)
s.mask(s<0)
s.mask(s<0, -50)
在 clip 中,超过边界的只能截断为边界值,如果要把超出边界的替换为自定义的值,应当如何做?
def my_clip(s:pd.Series, min_bound, max_bound, left_value, right_value)->pd.Series:
ret = s.clip(min_bound, max_bound)
ret = ret.mask(ret==min_bound, left_value)
ret = ret.mask(ret==max_bound, right_value)
return ret
排序函数
排序共有两种方式,其一为值排序,其二为索引排序,对应的函数是sort_values和sort_index
df_demo.sort_values(['Weight','Height'],ascending=[True,False]).head()
df_demo.sort_index(level=['Grade','Name'],ascending=[True,False]).head()
apply函数
6. apply方法
df_demo = df[['Height', 'Weight']]
def my_mean(x):
res = x.mean()
return res
df_demo.apply(my_mean)
apply
方法常用于DataFrame
的行迭代或者列迭代,它的axis
含义与第2小节中的统计聚合函数一致,apply
的参数往往是一个以序列为输入的函数
窗口对象
pandas中有3类窗口,分别是滑动窗口rolling、扩张窗口expanding以及指数加权窗口ewm
s = pd.Series([1,2,3,4,5])
roller = s.rolling(window = 3)
roller
rolling对象的默认窗口方向都是向前的,某些情况下用户需要向后的窗口,例如对1,2,3设定向后窗口为2的sum操作,结果为3,5,NaN,此时应该如何实现向后的滑窗操作?(提示:使用shift)
s = pd.Series([1,2,3])
s = s.rolling(2)
s.sum().shift(-1)
cummax, cumsum, cumprod函数是典型的类扩张窗口函数,请使用expanding对象依次实现它们。
import numpy as np
# cummax
s.expanding().max()
# cumsum
s.expanding().sum()
# cumprod
s.expanding().apply(lambda x:np.prod(x))
Ex1:口袋妖怪数据集
现有一份口袋妖怪的数据集,下面进行一些背景说明:
#
代表全国图鉴编号,不同行存在相同数字则表示为该妖怪的不同状态妖怪具有单属性和双属性两种,对于单属性的妖怪,
Type 2
为缺失值Total, HP, Attack, Defense, Sp. Atk, Sp. Def, Speed
分别代表种族值、体力、物攻、防御、特攻、特防、速度,其中种族值为后6项之和
对
HP, Attack, Defense, Sp. Atk, Sp. Def, Speed
进行加总,验证是否为Total
值。对于
#
重复的妖怪只保留第一条记录,解决以下问题:
- 求第一属性的种类数量和前三多数量对应的种类
- 求第一属性和第二属性的组合种类
- 求尚未出现过的属性组合
- 按照下述要求,构造
Series
:
- 取出物攻,超过120的替换为
high
,不足50的替换为low
,否则设为mid
- 取出第一属性,分别用
replace
和apply
替换所有字母为大写 - 求每个妖怪六项能力的离差,即所有能力中偏离中位数最大的值,添加到
df
并从大到小排序
# 验证值之和是否与Total相等
df['add'] = df[['HP', 'Attack', 'Defense', 'Sp. Atk', 'Sp. Def', 'Speed']].sum(axis=1)
(df['add'] == df['Total']).all()
# 去除多余的记录且只保留第一条记录
unique_df = df.drop_duplicates(['#'], keep='first')
# Type 1的种类数量
print(unique_df['Type 1'].nunique())
# Type 1前三多数量对应的种类
print(unique_df['Type 1'].value_counts().index[:3])
# 计算现在所有组合种类的数量
combine = unique_df.drop_duplicates(['Type 1', 'Type 2'])
num_unqiue_combine = combine.shape[0]
# 生成所有的组合
# 攻击值替换
df['Attack'].mask(df['Attack']>120, 'high').mask(df['Attack']<50, 'low').mask((50<=df['Attack'])&(df['Attack']<=120), 'mid').head()
# 取出第一属性
Type1 = df['Type 1']
# 使用replace进行大写替换
# 构造映射字典
map_dict = {}
for index in unique_df['Type 1'].value_counts().index:
map_dict[index] = index.upper()
Type1.replace(map_dict)
# 使用apply进行大写替换
Type1.apply(lambda s: s.upper())
# 计算六项能力的离差
df['Deviation'] = df[['HP', 'Attack', 'Defense', 'Sp. Atk', 'Sp. Def', 'Speed']].apply(lambda x:np.max((x-x.median()).abs()), 1)
df.sort_values('Deviation', ascending=False).head()
# 源自答案
L_full = [' '.join([i, j]) if i!=j else i for j in dp_dup['Type 1'].unique() for i in dp_dup['Type 1'].unique()]
L_part = [' '.join([i, j]) if type(j)!=float else i for i, j in zip(attr_dup['Type 1'], attr_dup['Type 2'])]
res = set(L_full).difference(set(L_part))
len(res) #
Ex2:指数加权窗口
- 作为扩张窗口的
ewm
窗口
在扩张窗口中,用户可以使用各类函数进行历史的累计指标统计,但这些内置的统计函数往往把窗口中的所有元素赋予了同样的权重。事实上,可以给出不同的权重来赋给窗口中的元素,指数加权窗口就是这样一种特殊的扩张窗口。
其中,最重要的参数是alpha
,它决定了默认情况下的窗口权重为,其中表示当前元素,表示序列的第一个元素。
从权重公式可以看出,离开当前值越远则权重越小,若记原序列为,更新后的当前元素为,此时通过加权公式归一化后可知:
对于Series
而言,可以用ewm
对象如下计算指数平滑后的序列:
np.random.seed(0)
s = pd.Series(np.random.randint(-1,2,30).cumsum())
s.head()
使用expanding窗口实现
def ewm_func(x, alpha=0.2):
weight = np.array([(1 - alpha)**i for i in range(x.shape[0]-1, -1, -1)])
return (x * weight).sum() / weight.sum()
s.expanding().apply(ewm_func).head()
- 作为滑动窗口的
ewm
窗口(参考了答案)
从第1问中可以看到,ewm
作为一种扩张窗口的特例,只能从序列的第一个元素开始加权。现在希望给定一个限制窗口n
,只对包含自身最近的n
个窗口进行滑动加权平滑。请根据滑窗函数,给出新的wi
与yt
的更新公式,并通过rolling
窗口实现这一功能。
新的权重为,更新如下:
s.rolling(window=4).apply(ewm_func).head() # 无需对原函数改动