量化交易全流程(二)

本节目录:

随机数和分布

随机数种子

基本统计量

概率密度、分布函数

偏度,峰度,左偏右偏特征

回归分析

假设检验

缺失值的填充

中心趋势、散度度量

多重直方图、带回归直线的散点图、盒图

多重索引

数据时间周期变换

--------------------------------------------统计分析基础--------------------------------------------

基本统计概念:

随机数和分布:

1、rand和random_sample,两者都是均匀分布的产生随机数生成函数,功能基本一致,rand函数传入多个整数参数作为生成数组的维度,random_sample传入一个n维元组元素作为参数,一般推荐使用random_sample,如以下代码:

import numpy as np
import matplotlib.pyplot as plt

data_uni=np.random.random_sample((1000))
plt.hist(data_uni,bins=30)
plt.show()

2、randn和standard_normal

randn和standard_normal都是生成正态分布的随机数生成函数,传入参数类似于rand和random_sample,通常用standard_normal:

import numpy as np
import matplotlib.pyplot as plt

data_normal=np.random.standard_normal((1000))
plt.hist(data_normal,bins=30)
plt.show()

从正态分布中取样,取出一个大小为10维的一维数组,可以使用:

data=np.random.randn(10)

3、randint和random_integers

都是均匀分布的整数生成函数,一般传入三个参数:low,high,size,两者区别在于randint范围不包括最大值,而random_integers包括最大值,(即包不包含右区间的值):

x1=np.random.randint(1,10,(100))
print(x1.max())
x2=np.random.random_integers(1,10,(100))
x2.max()

4、shuffle

可以随机打乱一个数组,并且改变此数组的本身排列:

x=np.arange(10)
print(x)
np.random.shuffle(x)  #不能赋值给x,无法赋值
x

5、Premutation

返回一个打乱顺序后的数组值,但并不会改变传入参数数组本身:

x=np.arange(10)
print(x)
y=np.random.permutation(x)  #不能赋值给x,无法赋值
y

6、二项式分布binomial(1,p)与binomial(1,p,n)

N次伯努利实验结果分布即为二项分布,用binomial(1,p)即为一次二项分布,binomial(1,p,n)即为n维二项分布数组:

import seaborn as sns
x1=np.random.binomial(100,0.5,10000)
sns.distplot(x1)

7、β分布(较抽象,看不懂可跳过)

考虑二项分布的分布函数:

边缘分布存在的意义就在于让后验分布成为一个满足概率定义的数值,先验概率的已知导致仅仅只要通过较小数据量更新即可,避免了过度拟合数据的问题。

预估参数与组合运算项_{m}^{N}\textrm{C}无关,只与后面概率式有关,可以猜想:假设先验概率也是与概率式有关的分布\mu ^{a}(1-\mu )^{b},那么后验分布则只要在概率式\mu ^{m+a}(1-\mu )^{N-m+b}前面乘以一个正则项就可以获得一个后验分布,与二项分布有关的共轭分布就是β分布。

beta(a,b)从Beta(a,b)分布中生成一个随机数,Beta(a,b)、rsv(n)生成一个n维数组,每个数组都是Beta(a,b)分布中随机数。

import seaborn as sns
from scipy import stats
beta=stats.beta(3,4).rvs(1000)
sns.displot(beta)

随机数种子

计算机随即生成的随机数并不是真正的随机数,而是使用特定算法生成的伪随机数,这样会面临两个问题:

1. 如果初始值一样,嘛呢按照相同算法得到的随机数应该也一样,这样不表现随机效果。

2. 很多随机试验需要再现之前结果,因此需要重复之前随机数。

因此在生成随机数之前设定一个种子,种子一样则每次随机数生成结果一样,在numpy里面,随机数的种子用seed设立:

np.random.seed()  #空种子
np.random.randn() #首先生成

np.random.seed()
np.random.randn() #再次生成会变

np.random.seed(1)  #初始种子
np.random.randn()  #首先生成

np.random.seed(1) 
np.random.randn()  #再次生成不变

相关系数

两个随机变量其斜率就是相关系数,相关系数也和协方差有关系:

cov(x,y)=E(x-E(x))(y-E(y))

x=np.random.normal(size=1000)
y=0.5*x+0.5*np.random.normal(size=1000)
np.cov(x,y)

皮尔逊相关系数剔除了量纲上面的影响:

corr(x,y)=cov(x,y)/δxδy,

x=np.random.normal(size=1000)
y=0.5*x+0.5*np.random.normal(size=1000)
np.corrcoef(x,y)

只要两个变量存在相关关系,其相关系数就不会为0.

基本统计量

利用数据的函数变化,从某种维度来反映全体数据集的特征的一种函数

1、mean既可以做函数调用也可作数组方法调用

np.random.seed(1)
x=np.random.normal(size=10)
print(x.mean())
print(np.mean(x))
x=np.reshape(x,(5,2))
print(np.mean(x,0))
x.mean(1)

2、median计算中位数,第二个参数是选择维度,既可以做函数调用也可作数组方法调用

median(x,1)
median(x,0)

std

计算标准差

x.std()

var

计算方差

x.var()

随机变量分布:

scipy包含大量连续随机变量的函数,每个类都有对应的方法生成随机数,计算PDF.CDF,进行MLE估计,矩估计。dist代表分布名称,每种分布除了输入自身变量之外,还包括三种常用参数,具体如下:

*args:每个分部定义所需的参数,如F分布(分子自由度,分母自由度)。

loc:位置参数,用于决定分布的中心位置。

scale:比例参数,决定分布的缩放比例。

分布基本特征(概率密度,分布函数):

1、dist.rvs:生成伪随机数,调用方法:生成的是数

from scipy import stats
a=stats.chi2(10).rvs(1000)  #卡方分布
plt.hist(a,bins=50)

dist.pdf:查看概率密度,调用方法:dist.pdf(x,*args,loc=0,scale=1)   # x 是进行估计的数组

dist.logpdf:查看对数概率密度,调用方法:dist.logpdf(x,*args,loc=0,scale=1)   # x 是进行估计的数组

dist.cdf:查看累积分布函数,调用方法:dist.cdf(x,*args,loc=0,scale=1)   # x 是进行估计的数组

dist.ppf:对于一组范围为0-1的数据,估计出来的累积分布函数的反函数,调用方法:dist.ppf(p,*args,loc=0,scale=1)   # p 是进行估计的数组,在0-1范围之间

dist.fit:使用最大似然估计估计其形状、位置、比例参数,调用方法:dist.fit(x,*args,floc=0,fscale=1)   # x 是进行估计的数据,生成的结果第一个数值为卡方分布自由度估计,第二个和第三个为中心值和scale。

dist.mean():返回分布的均值,调用方法:dist.mean(*args,loc=0,scale=1) 

dist.median():返回分布的中位数,调用方法:dist.median(*args,loc=0,scale=1) 

dist.var():返回分布的方差,调用方法:dist.var(*args,loc=0,scale=1) 

dist.std():返回分布的标准差,调用方法:dist.std(*args,loc=0,scale=1) 

需要注意的是,对于随机变量 a ,你应该使用它的实例对象进行PDF和CDF的计算

from scipy import stats
np.random.seed(1)

a=stats.chi2(10)
# plt.hist(a,bins=50)
print("PDF:", a.pdf(1))
print("LOGPDF:", a.logpdf(1))
print("CDF:", a.cdf(1))
print("PPF:", a.ppf(0.618))

a=stats.chi2(10).rvs(1000)  #MLE拟合
print("FIT:",stats.chi2.fit(a))

a=stats.chi2(10)
print("mean:",a.mean())
print("median:",a.median())
print("var:",a.var())
print("std:",a.std())

衍生特征(偏度与峰度,左偏右偏、高峰):

1、skewness偏度:描述分布的不对称性,偏离均值的三次方,对称的展示(正态分布):

返回分布的n阶中心距:dist.moment(n)

求偏度:stats.skew(a)

import numpy as np
import seaborn as sns
np.random.seed(1)
x=np.random.normal(size=1000)  # 要制定size,不能仅填1000
sns.displot(x)
a=stats.chi2(10)  #分布才能求其三阶矩,数据不能返回三阶矩
x=stats.norm()
print('3阶中心矩:',x.moment(3))

a=stats.chi2(10).rvs(1000) # 数据才能画图,分布无法画图
sns.displot(a)
print('卡方分布偏度值',stats.skew(a))

2、峰度:分布函数在均值附近取值高地的统计量,正态分布峰度为0,四阶矩,峰度计算公式:

kurt=\mu ^{4}/\sigma ^{4}-3

数据画图,分布求值(偏度峰度)

import numpy as np
import seaborn as sns
np.random.seed(1)
x=np.random.normal(size=1000)  # 要制定size,不能仅填1000
sns.displot(x)
a=stats.chi2(10)  #分布才能求其三阶矩,数据不能返回三阶矩
x=stats.norm()
print('4阶中心矩:',x.moment(4))

a=stats.chi2(10).rvs(1000) # 数据才能画图,分布无法画图
sns.displot(a)
print('卡方分布峰度值',stats.kurtosis(a))  #不能简写成kurt

标准正态分布特征:均值=中位数=众数

左偏态特征:众数>中位数>均值

右偏态特征:均值>中位数>众数

左偏分布是指数据分布中心或平均值偏向于左侧(较小值)的情况。 这意味着数据集中在较大的数值上,尾部则延伸到较小的数值。 左偏分布的特点是左侧较为密集,右侧较为稀疏。 右偏分布是指数据分布中心或平均值偏向于右侧(较大值)的情况。

当偏度<0时,概率分布图左偏。

当偏度=0时,表示数据相对均匀的分布在平均值两侧,不一定是绝对的对称分布。

当偏度>0时,概率分布图右偏。

峰度的取值范围为[1,+∞),完全服从正态分布的数据的峰度值为 3,峰度值越大,概率分布图越高尖,峰度值越小,越矮胖。

回归分析:

OLS:研究变量之间的相互关系

点击直达: AKShare 指数数据 — AKShare 1.11.7 文档

# 加载所需的函数包
import pandas as pd
import datetime
import akshare as ak
from mpl_finance import candlestick_ohlc
import matplotlib as mpl
import matplotlib.dates as mdates
mpl.rcParams['font.sans-serif'] = ['SimHei'] # 指定默认字体
mpl.rcParams['axes.unicode_minus'] = False  # 解决保存图像是负号'-'显示为方块的问题

#1、获取上证股指、贵州茅台 日频行情
data = ak.index_zh_a_hist(symbol="000001", period="daily", start_date="20200901", end_date="20230901") 
data1 = ak.stock_zh_a_hist('600519', period="daily", start_date = "20200901", end_date = "20230901", adjust="qfq")

#2、数据处理
def convert_date(data):
    data_price = data[['日期','收盘']]                                                # 选取日期、收盘价格
    data_price = data_price.rename(columns={'日期': 'trading_date','收盘': 'close_price'})
    data_price['trading_date'] = data_price['trading_date'].astype(str)              # 先将日期转为字符串
    data_price.set_index('trading_date', inplace=True)                               # 将日期作为索引
    data_price = data_price.astype(float)                                            # 将价格数据类型转为浮点数
    data_price['close_price']=data_price['close_price'].pct_change()                 # 计算收益率序列,注释掉就返回价格序列
    return data_price

data_price = convert_date(data)
data_price1 = convert_date(data1)

data = pd.merge(data_price, data_price1, on = 'trading_date', how = 'right')
data=data.dropna()
print(data)

研究问题:假设无风险收益率为2%,如果大盘涨1%,贵州茅台在统计概率上会上涨多少点?

假定如下模型:r_{t}=\alpha +\beta *(r_{M}-0.02)+\xi,目标就是利用手上数据估计除上述参数:α,β,\xi,为了模型具有可解释性,使参数能最优反应相关关系,转变成一下优化问题:

min_{\alpha ,\beta }\sum (\xi _{t})^{2}

上述方法就是OLS:

import statsmodels.api as sm    #pip install statsmodels
X = sm.add_constant(data['close_price_x'])              # 在x变量加上常数
result = sm.OLS(data['close_price_y'],X).fit()          # OLS要大写
result.summary()

const常数为0.0004,上证综指的系数为1.1386,对应p值接近于0,即如果大盘涨1%,贵州茅台在统计概率上会上涨1.1386%。

假设检验:上述结论可靠度有多高?系数会不会浮动,浮动区间有多大?

有个股票大师说贵州茅台是牛股,如果大盘涨1%,贵州茅台在统计概率上会上涨2%,考察大师说法是否正确。r_{t}=\alpha +\beta *(r_{M}-0.02)+\xi

也就是说要验证大师说法是否正确:H0:β>2

上述问题就称为假设检验,用到的方法就是统计推断。

针对上述场景,我们可以提出如下这样一个通用的解决方案框架
定义有限个随机变量( x ),属于随机变量空间,对于任意随机变量,其与其他随机变量存在某种对应关系族(f),关系族由参数空间( p )定义。假定我们知道对应关系族(f),并且拥有根据随机变量生成的样本( d ),我们需要做的就是通过某种变换 g ( d | p )(即构建统计量),使得该变换的结果满足某种已知的分布(如 Asymptotic Normal Distribution )。在给定的可能性阈值情况下,计算 g ( d | p )是否属于给定的阈值范围。
其中,拒绝假设的阈值范围称为临界域( critical region ),构建的统计量称为检验统计量( teststatistics )。即假如构建的统计量属于临界域的范围之外,则我们认为该假设有很大的可能是不成立的。
针对股票大师假设的问题,我们实现上述解决方案。数学上可以证明,以下统计量服从 t ( n -1)分布:
\frac{\hat{\beta }-\beta }{s(\hat{\beta })}-t(n-k)
其中,分母为参数估计量的样本估计值, n 为数据量, k 为模型参数数量, β 为我们的假设值。


证明思路:分子服从的分布为正态分布,分母为服从自由度为 n - k 卡方分布的随机变量的1/2次幂。其中, n 为样本的个数。


如何设定阈值?这是一个充满艺术性的问题。先来看如下几个极端的假设。

1)我拒绝所有可能。即对于\frac{\hat{\beta } }{s(\hat{\beta })}的任何值,我们都不相信,于是将临界域设为全集。很明显,我们会犯第一类错误:即有可能其中有一个值是正确的,拒绝正确的假设。Type I Error.

2)我不拒绝所有可能。将临界域设为空。很明显,我们会犯第二类错误:即使假设是错误的,仍然会接受这个假设。Type II Error.

通常减少一类错误会导致另一类错误无法减少,因此尽力避免这两类错误,即:假设正确,尽最小可能拒绝,假设错误,尽最大概率拒绝。这个函数就是功效函数(Power Function).因此目标是控制第一类错误发生率尽量较少第二类错误发生率。

针对统计量\frac{\hat{\beta } }{s(\hat{\beta })}而言:要找到一个c使得第一类错误发生概率<5%,即:假设大盘涨1%,贵州茅台在统计概率上会上涨2%是正确的假设,则观测值构建的统计量将拒绝假设的概率最大为5%。

Pr(\frac{\hat{\beta }-\beta }{s(\hat{\beta })} < c|\beta ) \leq 0.05

找到这个值的代码为:

from scipy import stats
stats.t(len(data['close_price_y'])-2).ppf(0.05)         # 服从t(n-k)分布,n为数据量,k为模型参数数量

基于这个值构建临界域:假如   \frac{\hat{\beta }-\beta }{s(\hat{\beta })} < c ,我们就拒绝这个假设。

我们可以计算\frac{\hat{\beta }-\beta }{s(\hat{\beta })},代码如下:

(result.params['close_price_x']-2) / result.bse['close_price_x']  #关联自变量

结果为\frac{\hat{\beta }-\beta }{s(\hat{\beta })} < c,没有理由接受原假设。

换个原假设:H0:假设大盘涨1%,贵州茅台在统计概率上会上涨1%是正确的假设。

import statsmodels.api as sm  #pip install statsmodels

X = sm.add_constant(data['close_price_x'])              # 在x变量加上常数
result = sm.OLS(data['close_price_y'],X).fit()          # OLS要大写
# result.summary()
from scipy import stats
print('临界值c:',stats.t(len(data['close_price_y'])-1).ppf(0.05))         # 服从t(n-k)分布,n为数据量,k为模型参数数量

(result.params['close_price_x']-1) / result.bse['close_price_x']  #关联自变量

结果为\frac{\hat{\beta }-\beta }{s(\hat{\beta })} > c,没有理由拒绝原假设。

------------------------------------------------数据预处理与初步探索--------------------------------------------

现实世界中的数据量越来越大,也越来越容易受到噪声、缺失值和不一致数据等的影响。数据库太大,如若有不同的来源(多半确实会有,像 Wind 的数据,来源就十分广泛,比如交易所、各公司的年报、各政府机关网站,还有其他大大小小的供应商等),那么脏数据问题一定会存在,这是不可避免的。
为了使数据中的各种问题对我们的建模影响最小化,需要对数据进行预处理。
在收集到初步的样本数据之后,接下来需要考虑的几个问题是:样本数据的质量是否有问题,如果有问题,应该怎么处理?样本数据是否出现了意外的情况?样本数据包含哪些基本的统计特征,有没有明显的规律?为了便于后续的深入分析和建模,我们需要对数据进行哪些处理?
通过数据清洗、绘制图表,以及基本统计量的计算,我们可以对数据做一个初步的分析和探索,为后面的深入分析和建模打下基础。
在实际操作中,数据预处理通常分为两大步,一是数据清洗,二是数据的基本分析。这两步并不一定是按先后顺序进行的,通常也会相互影响。比如,有的错误数据(不可能出现的极值),必须通过基本的统计分析才能发现。
有一种说法,数据的预处理会占据绝大部分的工作量,有的甚至会达到所有工作量的80%,建模和算法真正的工作量其实只有20%。这个结论在互联网或者传统 IT 领域,特别是面对大量的非结构化数据时,确实是事实。
但在金融二级市场上就不太一样了,由于很多现成的供应商已经将数据处理好并结构化了,所以实际的数据预处理工作量并没有那么大,但40%的比例应该是有的。

数据分析界有一句很有名的谚语" Garbage in , garbage out ",意思是如果输入的数据质量很糟糕,那么即使算法再精妙也得不到有价值的结果。更有甚者,还可能会将人引向相反的结果,造成重大决策失误。这一说法在量化投资领域当然也成立——如果输入的数据存在各种错误和问题,那将会很难做出有效的投资模型。
所以第一步,也是非常关键的一步,就是数据的清理。为了清理数据,我们必须要知道可能存在的问题,才能针对相应的问题设计相应的方法。

原始数据可能存在如下三种问题。
口  数据缺失:数据缺失的问题在高频数据里面特别常见。而且由于很多投资者是自己实时下载的数据,因此即使之后发现也很难弥补。
口  噪声或者离群点:由于系统或者人为的失误,导致数据出现明显的错误,比如某支股票的价格本应在12元左右,结果突然出现了100元的价格数据。
口  数据不一致:很多投资者,为了确保数据正确性,会使用多个数据源进行交叉验证,这时往往会出现数据不一致的问题。即使是同一个数据源,有时候也会出现数据不一致的问题。比如期货行情数据, Wind 、文华、 MC 的数据都有可能出现不一致的问题,数据频率越高,不一致的可能性就越大。

针对缺失值,实际操作中,需要两套程序:一套程序是检查缺失值,一套程序是填补缺失值。一般流程是,先检查缺失值,研究缺失值,选择填补方法,进行填补,然后再次检查。这样迭代循环,直到将数据缺失控制在可接受范围内。


缺失值,也有多种类型,一种是"正常缺失",比如股票在某一天停牌,那么这一天的交易数据就是没有的。一种是"非正常缺失",比如明明有交易,但就是没有交易数据。

数据填充:

pandas提供了算法填充数据:

import pandas as pd
import numpy as np
df = pd.DataFrame([[np.nan,1,2,np.nan],[3,np.nan,4,np.nan],[np.nan,np.nan,np.nan,np.nan],[5,7,np.nan,8]])
print(df)
df.fillna(method='ffill')  #当前值向下一行值填充
df.fillna(method='backfill')  #当前值向上一行值填充
df.fillna(4.5,inplace=True)  #向NA处填充指定值
print(df)

df = pd.DataFrame([[np.nan,1,2,np.nan],[3,np.nan,4,np.nan],[np.nan,np.nan,np.nan,np.nan],[5,7,np.nan,8]])
df.fillna((df.shift(1)+df.shift(-1))/2,inplace=True) #当前值填充前后均值
print(df)

离群值删除:

import pandas as pd
df = pd.DataFrame([[11,6,2,4],[3,3,4,8],[5,89,9,6],[5,7,18,8]])
print(df)
# 假设df是你的数据框,含有一列名为"column_name"的数据列
Q1 = df[1].quantile(0.25)
Q3 = df[1].quantile(0.75)
IQR = Q3 - Q1
threshold = 1.5

# 根据筛选条件删除离群值
df = df[(df[1] > Q1 - threshold * IQR) & (df[1] < Q3 + threshold * IQR)]

# 输出删除离群值后的数据框
print(df)

描述性统计:

中心趋势度量:

以资产收益率X为例,想了解序列总体大概在哪个值,这个就反应中心趋势思想,主要有均值、中位数、众数。

均线理论就来自于算术平均值:u=∑xi/N

加权平均值:每个x有一个权重,指数平滑均线EMA就是加权思想:

u=(w1x1+w2x2+...+wixi)/(w1+w2+...+wi)

EMAt=α * CLOSEt + (1-α) * EMAt-1               ,α为平滑指数,一般取值为2/(N+1),

EMA表示了过去收盘价对当前价格的重要性随时间推移会下降。

均值受到极端值影响较大。因此我们一般会用截尾均值(去掉高、低极端值的影响),对收益率排序,缩尾处理(2%)。

中位数适用于非对称数据,众数是出现最频繁的值。

数据散度度量:

方差和标准差:指出数据的散布程度,越低意味着数据趋向于非常靠近于均值,用方差表示收益的波动性,用标准差表示风险,布林线就是均值±N倍标准差。

极差是数据中最大和最小的差值。分位数是将数据排序后,可以将数据划分成大小相等的集合。

描述性统计可视化分析:

直方图:概括数据X的分布情况,X为离散变量,如股票类型,条的高度为频数,X为连续型变量,X值域划分成不同的不相交子集,对每个子域,高度为观测到的计数。

比较两个数据集的直方图:

import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
x1=np.random.normal(-1,2,size=10000) # numpy.ndarray
x2=np.random.normal(0,1,size=10000)
#x1.plot.hist() #numpy.ndarray没有plot
x1=pd.DataFrame(x1,columns=['normal']) # convert to DaraFrame 可以赋列名
x2=pd.DataFrame(x2,columns=['normal']) 

#绝对数量
x1['normal'].plot.hist(bins=10,figsize=(8,6),alpha=0.5)  #alpha控制透明度
x2['normal'].plot.hist(bins=10,figsize=(8,6),alpha=0.5)
plt.show()

#看概率分布
x1['normal'].plot.hist(bins=10,figsize=(8,6),alpha=0.5,density=True)
x2['normal'].plot.hist(bins=10,figsize=(8,6),alpha=0.5,density=True)
plt.show()

散点图:确定X、Y是否存在关联关系,将x,y是做一个坐标对,seaborn不仅能画散点图还能降回归直线画出来。

import pandas as pd
import seaborn as sns
data = {
    'total_bill': [16.99, 10.34, 21.01, 23.68, 24.59,12,43,75,4,7,9,3],
    'tip': [1.01, 1.66, 3.50, 3.31, 3.61,6,2,1,5.6,7,4,9],
    'sex': ['Female', 'Male', 'Male', 'Male', 'Female','Female', 'Male', 'Male', 'Male', 'Female','Male', 'Female'],
    'smoker': ['No', 'No', 'Yes', 'No', 'Yes','No', 'No', 'Yes', 'No', 'Yes', 'No', 'Yes'],
    'day': ['Sun', 'Sun', 'Sat', 'Sat', 'Sun','Sun', 'Sun', 'Sat', 'Sat', 'Sun','Sun', 'Sat'],
    'time': ['Dinner', 'Dinner', 'Dinner', 'Dinner', 'Dinner','Dinner', 'Dinner', 'Dinner', 'Dinner', 'Dinner','Dinner', 'Dinner'],
    'size': [2, 3, 3, 2, 4,2, 3, 3, 2, 4,2, 4]
}

df = pd.DataFrame(data)
 
# 使用lmplot绘制散点图
sns.lmplot(x="total_bill", y="tip", data=df)

# 显示图形
plt.show()

 定义的部分:

盒图:由五数概括而成,中位数,四分位数,最大最小值。

sns.boxplot(x="day", y="tip", hue='sex',data=df,palette=['m','g'] )
plt.show()

--------------------------------------------pandas进阶与实战--------------------------------------------

多重索引:

多重索引( Multilndex )又可以称为"分层索引",是 Pandas 中的一个核心功能。在实际工作中,如果合理应用多重索引,则可以大大简化某些复杂的数据操作,特别是高维数据的操作。

多重索引在 Pandas 中也是一种对象,即 Multilndex 对象,其与标准的 Index 对象非常相似,只不过有了"多重"的概念。我们可以将多重索引对象看成是一个由元组( tuple )元素组成的数组,其中,每一个元组对象都是唯一的。

Multilndex 既可以由嵌套数组创建(使用 Multilndex . from _ arrays ),也可以由元组组成的数组创建(使用 Multilndex . from _ tuples ),或者指定每个维度的索引值,自动循环生成索引(使用 Multilndex . from _ product )。下面就来列举一个例子说明生成 Multilndex 对象的方法。

假设我们想生成一个由股票代码和不同年份组成的索引,股票代码分别是000001、000002、000003,年份分别是2016、2017。
最快的方式是使用 from _ product 方法,示例代码如下:

import pandas as pd
numbers =['000001','600000','688001']
colors =[2016.2017]
mindex = pd.MultiIndex.from_product([numbers,colors], names =['code','year'])
mindex

其中, mindex 的值如下所示

有时候数据表格式如下:

ID/年份20062007
A12
B34

我们希望转换成:

ID年份
A20061
A20072
B20063
B20074

首先生成所需要的数据框:

import pandas as pd
import numpy as np
np.random.seed(0)
# 创建一个空的数据框
cols = pd.MultiIndex.from_product([range(2016, 2024), ['Q1', 'Q2', 'Q3', 'Q4']])
rows = pd.Index(['stock_{}'.format(i) for i in range(1, 31)], name='stock_code')
df = pd.DataFrame('-', index=rows, columns=cols)

# 填充数据
for year in range(2016, 2024):
    for quarter in range(1, 5):
        col = '{}Q{}'.format(year, quarter)
        data = np.random.random(30)
        df.loc[:, (year, 'Q{}'.format(quarter))] = data

# 查看数据框
print(df)


结果如下:

year = np.arange(2016,2024,1)
quarter = np.arange(1,5,1)
col_index = pd.MultiIndex.from_product([year,quarter],names=['year','quarter'])

# 转换列名,行名
df.columns = col_index
df.index.name = 'code'
df.head

 结果如下:

第一次stack将季度由列索引转变成行索引:

# 第一次stack将季度由列索引转变成行索引
df_quarter = df.stack()
df_quarter.head()

第二次stack将年份也由列索引转变成行索引:

df_quarter1 = df_quarter.stack()
df_quarter1

此时注意df_quarter1不再是DataFrame了,而是一个series,没有columns索引了,无法引用df_quarter1.columns。

此时季度在前,年份在后,使用不习惯,转换成年份在前,季度在后:

# 转换成年份在前
df_quarter2 = df_quarter1.to_frame()
df_quarter2 = df_quarter2.swaplevel(1, 2, axis = 0)
df_quarter2

 访问上述Series中的值;

print(df_quarter2[0].values)                     # 访问Series的值
print(df_quarter2[0][1])                         # 想获取某一个特定位置的值
df_quarter2.loc[('stock_1', 2016, 1)]            # 想获取某一个特定位置的值

数据时间周期变换:

在数据分析时,经常要用到分钟级别数据,需要对该类数据进行转换,如1Min转换成5min的,先获取数据:

import akshare as ak

# 注意:该接口返回的数据只有最近一个交易日的有开盘价,其他日期开盘价为 0
# stock_zh_a_hist_min_em_df = ak.stock_zh_a_hist_min_em(symbol="000001", start_date="2021-09-01 09:32:00", end_date="2021-09-06 09:32:00", period='1', adjust='')
# print(stock_zh_a_hist_min_em_df)

# 限量: 单次返回指定最近一个交易日的股票分钟数据, 包含盘前分钟数据
data = ak.stock_zh_a_hist_pre_min_em(symbol="000001")
print(data.head())

def convert_date(data):
    data_price = data[['时间','开盘','最高','最低','收盘' ,'成交量']]                  # 选取日期、高开低收价格、成交量数据
    data_price = data_price.rename(columns={
        '时间': 'trading_date','开盘': 'open_price','最高': 'high_price',
        '最低': 'low_price','收盘': 'close_price','成交量': 'business_amount'
        })
    # data_price['trading_date'] = data_price['trading_date'].astype(str)              # 先将日期转为字符串
    # data_price.set_index('trading_date', inplace=True)                               # 将日期作为索引
    # data_price = data_price.astype(float)                                            # 将价格数据类型转为浮点数
    # 将日期格式转为 candlestick_ohlc 可识别的数值
    # data_price['trading_dateDate'] = list(map(lambda x:mdates.date2num(datetime.datetime.strptime(x,'%Y-%m-%d')),data_price.index.tolist()))
    return data_price


df = convert_date(data)


print(df)


 Pandas 提供了 resample 方法,可用于变换时间序列的周期。要想使用 resample 方法,首先必须要有 datetime 类型的 index 。这里 date 是字符串,我们需要将 date 转化为 datetime 类型,而且还要将其设为 index 。
现在,我们要调用 resample 方法了。这里是将1分钟数据转为15分钟数据,相当于每15个数据点合并为一个。需要注意的时候,不同的数据,合并的方式是不一样的。比如,对于最高价 high ,我们要选择15个数据中的最大值作为新的最高价 high 。对于成交量 volume ,我们要将三个值加总,作为新的成交量 volume 。以下是转换代码:

# 将日期作为索引
df['trading_date'] = pd.to_datetime(df['trading_date'])
df = df.set_index('trading_date')

df['open_price'] = df['open_price'].resample('15T').first()
df['high_price'] = df['high_price'].resample('15T').max()
df['low_price'] = df['low_price'].resample('15T').min()
df['close_price'] = df['close_price'].resample('15T').last()
df['business_amount'] = df['business_amount'].resample('15T').sum()
df=df.dropna()
df


#将1分钟转化为15分钟数据,相当于将15个数据点合并为1个数据点

上述就完成了时间周期转换。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值