量化投资与Python——pandas
简介:

为什么学习pandas
- numpy已经可以帮助我们进行数据的处理了,那么学习pandas的目的是什么呢?
- numpy能够帮助我们处理的是数值型的数据,当然在数据分析中除了数值型的数据还有好多其他类型的数据(字符串,时间序列),那么pandas就可以帮我们很好的处理除了数值型的其他数据!
什么是pandas?
- 首先先来认识pandas中的两个常用的类
- Series
- DataFrame
Series
- Series是一种类似与一维数组的对象,由下面两个部分组成:
- values:一组数据(ndarray类型)
- index:相关的数据索引标签
- Series的创建
- 由列表或 numpy 数组创建
- 由字典创建
import pandas as pd
a = pd.Series([1,2,3,4],index=['a','b','c','d']) # 创建数组,并指定索引,默认为 0-n
print(a)
a 1
b 2
c 3
d 4
dtype: int64
切片
import pandas as pd
a = pd.Series([1,2,3,4],index=['a','b','c','d']) # 创建数组,并指定索引,默认为 0-n
print(a)
a 1
b 2
c 3
d 4
dtype: int64
a[1] # 2
a['b'] # 2
a[0:2]
a 1
b 2
dtype: int64
常用属性
a.shape # (4,)
a.size # 4
a.index # Index(['a', 'b', 'c', 'd'], dtype='object')
a.values # array([1, 2, 3, 4], dtype=int64)
a.dtype # dtype('int64') 注意:dtype('O') 为字符串类型
Series的常用方法
- head(),tail() # 取前(后)N 项
- unique() # 去重
- isnull(),notnull() # isnull() 判断每一个元素是否为空,notnull() 是否为非空
- add() sub() mul() div() # 加减乘除
DataFrame
- DataFrame是一个【表格型】的数据结构。DataFrame由按一定顺序排列的多列数据组成。设计初衷是将Series的使用场景从一维拓展到多维。DataFrame既有行索引,也有列索引。
- 行索引:index
- 列索引:columns
- 值:values
df3 = DataFrame(data=data)
# df3 = DataFrame(data=data, index=['a', 'b', 'c'])
# print(df3.index) Index(['a', 'b', 'c'], dtype='object')
print(df3)
print(df3.index) # RangeIndex(start=0, stop=3, step=1) 索引 从 0 开始 3 结束 步长 1
print(df3.shape) # (3, 2) 三行二列
print(df3.columns) # Index(['name', 'salay'], dtype='object')
print(df3.values)
# [['动作' 1000]
# ['情感' 1001]
# ['喜剧' 1002]]
DataFrame的创建
- ndarray创建
- 字典创建
df = DataFrame(data=[[1,2,3,4,5],['a','b','c','d']])
print(df)
df2 = DataFrame(data=np.random.randint(0,100,size=(3,6)))
print(df2)
0 1 2 3 4
0 1 2 3 4 5.0
1 a b c d NaN
0 1 2 3 4 5
0 8 55 48 78 94 78
1 97 56 34 74 15 9
2 74 76 27 46 5 83
DataFrame索引操作
- 对行进行索引
- 队列进行索引
- 对元素进行索引
- iloc:
- 通过隐式索引取行
- loc:
- 通过显示索引取行
注意:在使用 iloc 与 loc 时,应该保持索引是否是显隐性的一致。
# 数据
from pandas import DataFrame
df = DataFrame(data=np.random.randint(10,50,size=(3,6)),index=['a','b','c',])
df
0 1 2 3 4 5
a 26 29 26 39 31 29
b 20 43 40 19 48 22
c 11 42 19 22 23 47
# 对行进行索引
df.iloc[[2]] # 取单行
0 1 2 3 4 5
df.iloc[[0,2]] # 取多行
df[[1]] # 取单列
1
a 29
b 43
c 42
df[[1,3]] # 取多列
1 3
a 29 39
b 43 19
c 42 22
# 队列进行索引
df[2][3]
# 注意
- 通过隐式索引取行
- 通过显示索引取行
DataFrame的切片操作
- df索引和切片操作
- 索引:
- df[col]:取列
- df.loc[index]:取行
- df.iloc[index,col]:取元素
- 切片:
- df[index1:index3]:切行
- df.iloc[:,col1:col3]:切列
案例:
from pandas import DataFrame
df = DataFrame(data=np.random.randint(10,50,size=(3,6)),index=['a','b','c',])
df
0 1 2 3 4 5
a 24 25 18 23 23 20
b 21 32 48 40 46 28
c 41 17 44 13 29 20
- 对行进行切片
df[0:2]
0 1 2 3 4 5
a 24 25 18 23 23 20
b 21 32 48 40 46 28
- 对列进行切片
df.iloc[:,0:2]
0 1
a 24 25
b 21 32
c 41 17
练习:
1. 假设ddd是期中考试成绩,ddd2是期末考试成绩,请自由创建ddd2,并将其与ddd相加,求期中期末平均值。
date = {
'张三':[132,132,132,132,132],
'李四':[128,128,128,128,128],
}
ddd = DataFrame(data=date,index=['语文','数学','英语','化学','物理'])
ddd
张三 李四
语文 132 128
数学 132 128
英语 132 128
化学 132 128
物理 132 128
date2 = {
'张三':[150,150,150,150,150],
'李四':[130,130,130,130,130],
ddd = DataFrame(data=date2,index=['语文','数学','英语','化学','物理'])
ddd2
张三 李四
语文 150 130
数学 150 130
英语 150 130
化学 150 130
物理 150 130
(ddd+ddd2)/2
张三 李四
语文 141.0 129.0
数学 141.0 129.0
英语 141.0 129.0
化学 141.0 129.0
物理 141.0 129.0
2. 假设张三期中考试数学被发现作弊,要记为0分,如何实现?
ddd.iloc[1,0] = 0
ddd
张三 李四
语文 132 128
数学 0 128
英语 132 128
化学 132 128
物理 132 128
3. 李四因为举报张三作弊立功,期中考试所有科目加100分,如何实现?
ddd[['李四']] += 100
ddd
张三 李四
语文 132 228
数学 0 228
英语 132 228
化学 132 228
物理 132 228
4. 后来老师发现有一道题出错了,为了安抚学生情绪,给每位学生每个科目都加10分,如何实现?\n
ddd+=10
ddd
张三 李四
语文 142 238
数学 10 238
英语 142 238
化学 142 238
物理 142 238
数据清洗——删除行数据
df = DataFrame(data=np.random.randint(0, 100, size=(5, 7)))
df.loc[1, 3] = None
df.loc[3, 2] = np.nan
df.loc[0, 6] = None
# 过滤方法
# 方法一:删除空值所在的行
# 技术:isnull any,notnull all
# any:用于检测行或列中是否存在 True,如果有 True,返回 True,无 True,则返回 False。
# df.loc[df.isnull().any(axis=1)] # True 对应的行就是有缺失数据的行
drop_index = df.loc[df.isnull().any(axis=1)].index # 取出有缺失数据的行索引
print(df.drop(labels=drop_index, axis=0)) # 删除缺失数据的行
print(df.loc[df.notnull().all(axis=1)]) # 1 是行,0 是列
# 方法二:dropna 直接删除有数据缺失的行或列
print(df.dropna(axis=0)) # 0 是行,1 是列
数据清洗——替代数据
# 对缺失值进行覆盖
# 使用指定值来覆盖
print(df.fillna(value=666))
# 水平覆盖
print(df.fillna(method='ffill',axis=1)) # 使用水平方向的向前填充
print(df.fillna(method='bfill',axis=1)) # 使用水平方向的向后填充
# 垂直覆盖
print(df.fillna(method='ffill',axis=0)) # 使用垂直方向的向下填充
print(df.fillna(method='bfill',axis=0)) # 使用垂直方向的向上填充
清洗案例
数据文件
# 数据清洗,1 2 3 4 列
# 方法一:删除
df = pd.read_excel('./testData.xlsx')
df = df[[1,2,3,4]]
print(df.dropna(axis=0))
# 方法二:替换
# 采用垂直填充,因为每分钟前后取得的检测值一般差别不大
df = df.fillna(method='ffill',axis=0).fillna(method='bfill',axis=0)
print(df)
# 检测是否还有空值
print(df.isnull().any(axis=0))
重复数据与异常数据的清洗
重复数据处理
# 准备数据
import numpy as np
from pandas import DataFrame
df = DataFrame(data=np.random.randint(0,100,size=(7,3)))
df.iloc[1]=(0,0,0)
df.iloc[3]=(0,0,0)
df.iloc[5]=(0,0,0)
df=df.drop_duplicates(keep='first') # keep='first' 保留第一次出现的行数值,默认值
df=df.drop_duplicates(keep='last') # keep='last' 保留最后一次出现的行数值,默认值
df=df.drop_duplicates(keep=False) # keep=False 删除所有行数值,默认值
df
异常数据处理
自定义一个 1000行3列(A、B、C)取值范围 0-1 的数据源,然后将 C 列中的大于其两倍标准差的异常数据进行清理
# 数据准备
import numpy as np
from pandas import DataFrame
df = DataFrame(data = np.random.random(size=(1000,3)),columns=['A','B','C'])
# 设定异常值条件
two =df['C'].std()*2
df[~(df['C'] > two)]
级联与合并
级联
匹配级联
import pandas as pd
import numpy as np
from pandas import DataFrame
df = DataFrame(data=np.random.randint(1,100,size=(5,3)),columns=('a','b','c'))
df2 = DataFrame(data=np.random.randint(1,100,size=(5,3)),columns=('a','b','c'))
pd.concat((df,df2),axis=0) # 纵向级联
pd.concat((df,df2),axis=1) # 横向级联
不匹配级联
即索引不一致,有两种连接方式:内连接、外连接(默认)。
import pandas as pd
import numpy as np
from pandas import DataFrame
df = DataFrame(data=np.random.randint(1,100,size=(5,3)),columns=('a','b','c'))
df2 = DataFrame(data=np.random.randint(1,100,size=(5,3)),columns=('a','d','c'))
pd.concat((df,df2),axis=1) # 横向级联
pd.concat((df,df2),axis=0) # 纵向级联
执行结果:
# 横向级联
a b c a d c
0 79 77 64 65 57 76
1 83 67 72 61 51 82
2 5 44 50 86 80 44
3 66 14 24 92 88 27
4 52 10 8 3 91 58
# 纵向级联
a b c d
0 79 77.0 64 NaN
1 83 67.0 72 NaN
2 5 44.0 50 NaN
3 66 14.0 24 NaN
4 52 10.0 8 NaN
0 65 NaN 76 57.0
1 61 NaN 82 51.0
2 86 NaN 44 80.0
3 92 NaN 27 88.0
4 3 NaN 58 91.0
内连接与外连接
外连接:保留数据的完整性
a b c d
0 79 77.0 64 NaN
1 83 67.0 72 NaN
2 5 44.0 50 NaN
3 66 14.0 24 NaN
4 52 10.0 8 NaN
0 65 NaN 76 57.0
1 61 NaN 82 51.0
2 86 NaN 44 80.0
3 92 NaN 27 88.0
4 3 NaN 58 91.0
内连接:存在数据丢失
a c
0 79 64
1 83 72
2 5 50
3 66 24
4 52 8
0 65 76
1 61 82
2 86 44
3 92 27
4 3 58
合并
merge 与 concat 的区别值于,merge 需要依据某一共同列来进行合并
使用 pd.merge() 合并时,会自动根据两者相同 column 名称的列作为 key 来进行合并
注意每一列元素的顺序不要求一致,与数据库中 外连接、内连接一样,对数据的合并。
# 语法操作
pd.merge(df,df2,on='列名') # 存在相同列名称
pd.merge(df,df2,how='outer') # 外连接
pd.merge(df,df2,how='inner') # 内连接
pd.merge(df,df2,left_on='列名',right_on='列名2') # 存在不同列名称
替换
替换操作
- 替换操作可以同步作用于Series和DataFrame中
- 单值替换
- 普通替换: 替换所有符合要求的元素:to_replace=15,value='e'
- 按列指定单值替换: to_replace={列标签:替换值} value='value'
- 多值替换
- 列表替换: to_replace=[] value=[]
- 字典替换(推荐) to_replace={to_replace:value,to_replace:value}
操作
# 数据准备
import pandas as pd
import numpy as np
from pandas import DataFrame
df = DataFrame(np.random.randint(0,100,size=(3,6)))
# 替换方法
df = df.replace(to_replace=28,value='two')
df = df.replace(to_replace={95:'one'})
df = df.replace(to_replace={5:98},value='three') # 指定列替换
映射
映射操作
- 概念:创建一个映射关系列表,把values元素和一个特定的标签或者字符串绑定(给一个元素值提供不同的表现形式)
- 创建一个df,两列分别是姓名和薪资,然后给其名字起对应的英文名
data={
'name':['张三','李四','张三'],
'manay':[12000,13000,12000]
}
df = DataFrame(data=data)
# 映射关系表
dic={
'张三':'tom',
'李四':'jack'
}
df['e_name'] = df['name'].map(dic)
df
name manay e_name
0 张三 12000 tom
1 李四 13000 jack
2 张三 12000 tom
注意:map是Series的方法,只能被Series调用
运算工具
def after(s):
return s-(s-3000)*0.5
df['after_manay'] = df['manay'].map(after)
df
结果:
name manay e_name after_manay
0 张三 12000 tom 7500.0
1 李四 13000 jack 8000.0
2 张三 12000 tom 7500.0
排序实现的随机抽样
take()
np.random.permutation()
df2 = DataFrame(np.random.randint(0,100,size=(100,3)),columns=['A','B','C'])
df2
# 随机打乱数组
# rp = np.random.permutation(3) # 生成随机数列
df2=df2.take(np.random.permutation(3),axis=1) # 按照隐式随机数列进行打乱
df2 = df2.take(np.random.permutation(3),axis=1).take(np.random.permutation(100),axis=0) # 同时打乱列与行
df2[5:10] # 切取 5-10 行
数据的分类处理
- 数据分类处理的核心:
- groupby() 函数
- groups 属性查看分组情况
# 数据准备
df = DataFrame({'item':['Apple','Banana','Orange','Banana','Orange','Apple'],
'price':[4,3,3,2.5,4,2],
'color':['red','yellow','yellow','green','green','green'],
'weight':[12,20,50,30,20,44]})
item price color weight
0 Apple 4.0 red 12
1 Banana 3.0 yellow 20
2 Orange 3.0 yellow 50
3 Banana 2.5 green 30
4 Orange 4.0 green 20
5 Apple 2.0 green 44
# 计算每种水果的平均价格
df.groupby(by='item')['price'].mean()
item
Apple 3.00
Banana 2.75
Orange 3.50
Name: price, dtype: float64
# 计算每一种颜色对应水果的平均总量
df_werght = df.groupby(by='color')['weight'].mean()
df_werght
color
green 31.333333
red 12.000000
yellow 35.000000
Name: weight, dtype: float64
# 将计算的出的平均总量汇总到原数据中
df['mena_w'] = df['color'].map(df_werght)
tem price color weight mena_w
0 Apple 4.0 red 12 12.000000
1 Banana 3.0 yellow 20 35.000000
2 Orange 3.0 yellow 50 35.000000
3 Banana 2.5 green 30 31.333333
4 Orange 4.0 green 20 31.333333
5 Apple 2.0 green 44 31.333333
高级数据聚合
- 使用groupby分组后,也可以使用transform和apply提供自定义函数实现更多的运算
- df.groupby('item')['price'].sum() <==> df.groupby('item')['price'].apply(sum)
- transform和apply都会进行运算,在transform或者apply中传入函数即可
- transform和apply也可以传入一个lambda表达式
def my_mean(s):
m_sum = 0
for i in s:
m_sum += i
return m_sum/len(s)
df.groupby(by='item')['weight'].transform(my_mean)
df
item price color weight mena_w
0 Apple 4.0 red 12 12.000000
1 Banana 3.0 yellow 20 35.000000
2 Orange 3.0 yellow 50 35.000000
3 Banana 2.5 green 30 31.333333
4 Orange 4.0 green 20 31.333333
5 Apple 2.0 green 44 31.333333
df.groupby(by='item')['weight'].apply(my_mean)
df
item price color weight mena_w
0 Apple 4.0 red 12 12.000000
1 Banana 3.0 yellow 20 35.000000
2 Orange 3.0 yellow 50 35.000000
3 Banana 2.5 green 30 31.333333
4 Orange 4.0 green 20 31.333333
5 Apple 2.0 green 44 31.333333
数据加载
源文件:
你好-我好-她也好
你好-我好-她也好
你好-我好-她也好
默认读取:两行一列
现在改为三行三列
df2 = pd.read_csv(r'H:\py\高级\数据分析\科学计算基础包-numpy\test.csv',header=None,sep='-')
透视表
- 透视表是一种可以对数据动态排布并且分类汇总的表格格式。或许大多数人都在Excel使用过数据透视表,也体会到它的强大功能,而在pandas中它被称作pivot_table。
- 透视表的优点:
- 灵活性高,可以随意定制你的分析计算要求
- 脉络清晰易于理解数据
- 操作性强,报表神器
数据文件:透视表案例原数据
import pandas as pd
from pandas import DataFrame
data = pd.read_csv(r'H:\py\高级\数据分析\科学计算基础包-numpy\透视表-篮球赛.csv')
df = DataFrame(data=data)
df
pivot_table有四个最重要的参数index、values、columns、aggfunc
- index参数:分类汇总的分类条件
- 每个pivot_table必须拥有一个index。如果想查看哈登对阵每个队伍的得分则需要对每一个队进行分类并计算其各类得分的平均值:
- 想看看哈登对阵同一对手在不同主客场下的数据,分类条件为对手和主客场
df.pivot_table(index=['对手','主客场'])
- values参数:需要对计算的数据进行筛选
- 如果我们只需要哈登在主客场和不同胜负情况下的得分、篮板与助攻三项数据:
df.pivot_table(index=['主客场','胜负'],values=['得分','篮板','助攻'])
- Aggfunc参数:设置我们对数据聚合时进行的函数操作
- 当我们未设置aggfunc时,它默认aggfunc='mean'计算均值。
- 如果我们只需要哈登在主客场和不同胜负情况下的总得分、总篮板与总助攻三项数据:
df.pivot_table(index=['主客场','胜负'],values=['得分','篮板','助攻'],aggfunc='sum')
- Columns:可以设置列层次字段
- 对values字段进行分类
# 获取所有队主客场的总得分
df.pivot_table(index=['主客场'],values=['得分'],aggfunc='sum')
# 获取每个队主客场的总得分
df.pivot_table(index=['主客场'],values=['得分'],aggfunc='sum',columns='对手',fill_value=0)
交叉表
- 是一种用于计算分组的特殊透视图,对数据进行汇总
- pd.crosstab(index,colums)
- index:分组数据,交叉表的行索引
- columns:交叉表的列索引
df = DataFrame({'sex':['man','man','women','women','man','women','man','women','women'],
'age':[15,23,25,17,35,57,24,31,22],
'smoke':[True,False,False,True,True,False,False,True,False],
'height':[168,179,181,166,173,178,188,190,160]})
df
# 求出各个性别抽烟的人数
pd.crosstab(df.smoke,df.sex)
# 求出各个年龄段抽烟人情况
pd.crosstab(df.age,df.sex)
案例
# -*- coding: utf-8 -*-
# @File : pandas-基础.py
# @Date : 2020-06-09
# @Author : Administrator
import pandas as pd
import dateutil
from datetime import datetime
# ################ Series ################
a = pd.Series([4, 5, 6, 3])
# print(a)
# 0 4
# 1 5
# 2 6
# 3 3
# dtype: int64
b = pd.Series([4, 5, 6, 3], index=['a', 'b', 'c', 'd'])
print(b)
# a 4
# b 5
# c 6
# d 3
# 既可以通过下标访问,也可以通过 key 来访问
# print(b[2],b['c']) # 6 6
# 按照标签进行累加
c = pd.Series([1, 2, 3, 4], index=['a', 'b', 'c', 'd'])
d = pd.Series([1, 2, 3, 4], index=['b', 'c', 'a', 'd'])
print(c+d)
# b 3
# c 5
# d 8
# 如果两个列表的标签不一致,则只累加标签一致的结果,其它为空
e = pd.Series([1, 2, 3, 4], index=['b', 'b', 'a', 'd'])
print(e+c)
# a 4.0
# b 3.0
# b 4.0
# c NaN
# d 8.0
# dtype: float64
f = pd.DataFrame({'one': [1, 2, 3], 'two': [4, 5, 6]})
print(f)
# one two
# 0 1 4
# 1 2 5
# 2 3 6
g = pd.DataFrame({'one': [1, 2, 3], 'two': [4, 5, 6]}, index=['a', 'b', 'c'])
print(g)
# a 1 4
# b 2 5
# c 3 6
h = pd.DataFrame({'one': pd.Series([1, 2, 3], index=['a', 'b', 'c']),'two': pd.Series([1, 2, 3, 4], index=['a', 'b', 'c', 'd'])})
print(h)
# a 1.0 1
# b 2.0 2
# c 3.0 3
# d NaN 4
print(h.keys()) # Index(['one', 'two'], dtype='object')
print(h.values)
# [[ 1. 1.]
# [ 2. 2.]
# [ 3. 3.]
# [nan 4.]]
print(h.T)
# a b c d
# one 1.0 2.0 3.0 NaN
# two 1.0 2.0 3.0 4.0
print(h.describe())
# one two
# count 3.0 4.000000
# mean 2.0 2.500000
# std 1.0 1.290994
# min 1.0 1.000000
# 25% 1.5 1.750000
# 50% 2.0 2.500000
# 75% 2.5 3.250000
# max 3.0 4.000000
# 时间处理
now = datetime.now()
time = dateutil.parser.parser('2001-01-01')
print(time) # <dateutil.parser._parser.parser object at 0x0000025D7A7A05C0>
pd_time = pd.to_datetime([now])
print(pd_time) # DatetimeIndex(['2020-06-28 13:43:48.554969'], dtype='datetime64[ns]', freq=None)
# 批量生成时间队列
time_list = pd.date_range('2020-01-01',periods=30,freq='W-MON')
print(time_list)
# DatetimeIndex(['2020-01-06', '2020-01-13', '2020-01-20', '2020-01-27',
# '2020-02-03', '2020-02-10', '2020-02-17', '2020-02-24',
# '2020-03-02', '2020-03-09', '2020-03-16', '2020-03-23',
# '2020-03-30', '2020-04-06', '2020-04-13', '2020-04-20',
# '2020-04-27', '2020-05-04', '2020-05-11', '2020-05-18',
# '2020-05-25', '2020-06-01', '2020-06-08', '2020-06-15',
# '2020-06-22', '2020-06-29', '2020-07-06', '2020-07-13',
# '2020-07-20', '2020-07-27'],
# dtype='datetime64[ns]', freq='W-MON')
时间数据类型的转换
- 时间数据类型的转换
- pd.to_datetime(col)
- 将某一列设置为行索引
- df.set_index()
data = {
"time":['2020-06-30','2020-06-29','2020-06-28'],
"num":[31,32,33]
}
dd = DataFrame(data=data)
print(dd)
time num
0 2020-06-30 31
1 2020-06-29 32
2 2020-06-28 33
# 将 time 列的字符串类型转为时间序列类型
dd['time'] = pd.to_datetime(dd['time'])
print(dd)
print(dd['time'].dtype)
time num
0 2020-06-30 31
1 2020-06-29 32
2 2020-06-28 33
datetime64[ns]
# 将 time 列作为原数据的索引列
dd.set_index('time')
num
time
2020-06-30 31
2020-06-29 32
2020-06-28 33
# 改变原数据 inplace=True
dd.set_index('time',inplace=True)
print(dd)