第二章 pandas 基础
简单回顾和总结
- 重新回头跟着教程走了一遍,又又又复习了一次,这次感觉效果更好了。
- 后面的第二个练习用结构化的语言写了一遍,对上了答案,花了快两个多小时,哎,手生了。
- 附ipynb转md博文命令: jupyter nbconvert --to markdown E:PycharmProjectsTianChiProject 0_山枫叶纷飞competitions 08_joyful-pandas 2-pandas基础.ipynb
准备:导包
import numpy as np
import pandas as pd
准备:升级pandas
pip install pandas --upgrade
pd.__version__
'1.1.5'
一、文件的读取和写入
- 在使用 read_table 的时候需要注意,参数 sep 中使用的是正则表达式,因此需要对 | 进行转义变成 | ,否则无法读取到正确的结果。
- 一般在数据写入中,最常用的操作是把 index 设置为 False,去除索引。
- 如果想要把表格快速转换为 markdown 和 latex 语言,可以使用 to_markdown 和 to_latex 函数,此处需要安装 tabulate 包。
In [20]: print(df_csv.to_markdown())
| | 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 |
二、基本数据结构
- 一维的Series
- 二维的DataFrame
1. 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')
s
my_idx
id1 100
20 a
third {'dic1': 5}
Name: my_name, dtype: object
2. DataFrame
ataFrame 在 Series 的基础上增加了列索引,一个数据框可以由二维的 data 与行列索引来构造:
df = pd.DataFrame(data = {'col_0': [1,2,3], 'col_1':list('abc'),
'col_2': [1.2, 2.2, 3.2]},
index = ['row_%d'%i for i in range(3)])
df
col_0 | col_1 | col_2 | |
---|---|---|---|
row_0 | 1 | a | 1.2 |
row_1 | 2 | b | 2.2 |
row_2 | 3 | c | 3.2 |
三、常用基本函数
prefix_path = 'E:\PycharmProjects\DatawhaleChina\joyful-pandas\data'
df = pd.read_csv(prefix_path+'\learn_pandas.csv')
### 0. 查询基本列名称 (属性)
df.columns
Index(['School', 'Grade', 'Name', 'Gender', 'Height', 'Weight', 'Transfer',
'Test_Number', 'Test_Date', 'Time_Record'],
dtype='object')
-
汇总函数
- df.head(2)
- df.tail(3)
- info() 返回表的信息概况;
- describe() 返回表中数值列对应的主要统计量;
-
特征统计函数
- 常见的如:sum, mean, median, var, std, max, min等
- 分位数:quantile,示例df.quantile(0.75)
- 非缺失值个数:count()
- 最大值对应的索引: idxmax()
- 小结:上述操作返回的都是标量,所以又称为聚合函数,他们有一个公共参数axis,默认为0代表逐列来聚合,1代表逐行聚合!
-
唯一值函数
- 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 则使用后面最近的未被替换的值进行替换。如: s.replace([1, 2], method='ffill')
- 正则替换,暂时不可用~
df['Gender']
0 Female
1 Male
2 Male
3 Female
4 Male
...
195 Female
196 Female
197 Female
198 Male
199 Male
Name: Gender, Length: 200, dtype: object
df['Gender'].replace({'Female': 0, 'Male': 1}).head()
0 0
1 1
2 1
3 0
4 1
Name: Gender, dtype: int64
- 逻辑替换
- 包括了 where 和 mask ,这两个函数是完全对称的: where 函数在传入条件为 False 的对应行进行替换,而 mask 在传入条件为 True 的对应行进行替换,当不指定替换值时,替换为缺失值。
s = pd.Series([-1, 1.2345, 100, -50, 999])
s.where(s<0)
0 -1.0
1 NaN
2 NaN
3 -50.0
4 NaN
dtype: float64
s.where(s<0, 100)
0 -1.0
1 100.0
2 100.0
3 -50.0
4 100.0
dtype: float64
- 需要注意的是,传入的条件只需是与被调用的 Series 索引一致的布尔序列即可
s_condition= pd.Series([True,False,False,True, True],index=s.index)
s.where(s<0, 6666)
0 -1.0
1 6666.0
2 6666.0
3 -50.0
4 6666.0
dtype: float64
数值替换包含了 round, abs, clip 方法,它们分别表示取整、取绝对值和截断(比大小,超出返回的用上届或者下届来依次替代):
s = pd.Series([-1, 1.2345, 100, -50])
print('取整 保留两位', s.round(2))
print('取abs', s.abs())
print('clip大法:按照大小进行范围截取
', s.clip(0, 1))
# print('
', s.clip(0, 2))
# print('
', s.clip(0, 3))
取整 保留两位 0 -1.00
1 1.23
2 100.00
3 -50.00
dtype: float64
取abs 0 1.0000
1 1.2345
2 100.0000
3 50.0000
dtype: float64
clip大法:按照大小进行范围截取
0 0.0
1 1.0
2 1.0
3 0.0
dtype: float64
-
排序函数
- 排序共有两种方式,其一为值排序,其二为索引排序,对应的函数是 sort_values() 和 sort_index() 。
- 默认参数 ascending=True; eg: df.sort_values('Height', ascending=False).head()
- 在排序中,进场遇到多列排序的问题,比如在体重相同的情况下,对身高进行排序,并且保持身高降序排列,体重升序排列;eg: df_demo.sort_values(['Weight','Height'],ascending=[True,False]).head()
- 索引排序的用法和值排序完全一致,只不过元素的值在索引中,此时需要指定索引层的名字或者层号,用参数 level 表示。另外,需要注意的是字符串的排列顺序由字母顺序决定。eg: df_demo.sort_index(level=['Grade','Name'],ascending=[True,False]).head()
-
apply 方法
- apply的参数往往是一个以序列为输入的函数。
- apply性能较慢,自由度比较高。
df_demo = df[['Height', 'Weight']]
def my_mean(x):
return x.mean()
# axis=0,默认按列作为一个整体进行计算; axis=1,表示按行计算
df_demo.apply(my_mean, axis=0)
df_demo.apply(lambda x:x.mean(), axis=0)
Height 163.218033
Weight 55.015873
dtype: float64
四. 窗口对象
pandas中有三类窗口,分别是滑动窗口 rolling 、扩张窗口 expanding 以及指数加权窗口 ewm 。
4.1 滑动窗口对象 rolling
- 滑动窗口,参数为窗口大小 window,每次滚动包括当前行,前window-1行是没有结果的。
- 使用 .rolling 得到滑窗对象roller,roller类似于一个分组,再搭配聚合函数进行输出
- 类似的还有滑动相关系数或滑动协方差的计算,如roller.cov(s)或者roller.corr(s)
- 还支持后接上apply()方法
- 支持shift, diff, pct_change 这一组的类滑窗函数,它们的公共参数为 periods=n ,默认为 1,分别表示取向前第 n
个元素的值、与向前第 n 个元素做差(与 Numpy中的diff不同,后者表示 n 阶差分)、与向前第 n 个元素相比计
算增长率。这里的 n 可以为负,表示反方向的类似操作。
s = pd.Series([2,4,6,8,10])
s_roller_mean = s.rolling(window=3).mean()
s2 = pd.Series([1,2,6,16,30])
roller = s2.rolling(window = 3)
roller.cov(s)
roller.corr(s)
0 NaN
1 NaN
2 0.944911
3 0.970725
4 0.995402
dtype: float64
4.2 扩张窗口 expanding (从起点开始依次累加扩张指定的大小窗口——执行聚合函数)
扩张窗口又称累计窗口,可以理解为一个动态长度的窗口,其窗口的大小就是从序列开始处到具体操作的对
应位置,其使用的聚合函数会作用于这些逐步扩张的窗口上。具体地说,设序列为 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
4.3 练一练
cummax, cumsum, cumprod 函数是典型的类扩张窗口函数,请使用 expanding 对象依次实现它们。
#### 4.3.1 cummax
s = pd.Series([1, 3, 6, 10])
ret1 = s.cummax()
ret2 = s.expanding().max()
print('after cummax or expanding.max():')
print(ret1)
print(ret2)
#### 4.3.1 cumsum
#### 4.3.1 cumprod
# 略
after cummax or expanding.max():
0 1
1 3
2 6
3 10
dtype: int64
0 1.0
1 3.0
2 6.0
3 10.0
dtype: float64
五 练习
2.5.1 Ex1:口袋妖怪数据集
现有一份口袋妖怪的数据集,下面进行一些背景说明:
- 代表全国图鉴编号,不同行存在相同数字则表示为该妖怪的不同状态
- 妖怪具有单属性和双属性两种,对于单属性的妖怪,Type 2 为缺失值
- Total, HP, Attack, Defense, Sp. Atk, Sp. Def, Speed 分别代表种族值、体力、物攻、防御、特攻、特防、速度,其中种族值为后 6 项之和。
题目
- 对 HP, Attack, Defense, Sp. Atk, Sp. Def, Speed 进行加总,验证是否为 Total 值。
df = pd.read_csv('E:\PycharmProjects\DatawhaleChina\joyful-pandas\data\pokemon.csv')
df.head(3)
list_cols = ['HP', 'Attack', 'Defense', 'Sp. Atk', 'Sp. Def', 'Speed']
df['my_total'] = 0
for x in list_cols:
df['my_total'] = df['my_total'] + df[x]
if df[df['my_total']!=df['Total']].shape[0] > 0:
print('存在错误数据!', df[df['my_total']!=df['Total']])
else:
print('暂无错误数据')
del df['my_total']
暂无错误数据
- 对于 # 重复的妖怪只保留第一条记录,解决以下问题:
- (a) 求第一属性的种类数量和前三多数量对应的种类
- (b) 求第一属性和第二属性的组合种类
- (c) 求尚未出现过的属性组合
print(df.columns)
df = df.drop_duplicates('#', keep='first')
Index(['#', 'Name', 'Type 1', 'Type 2', 'Total', 'HP', 'Attack', 'Defense',
'Sp. Atk', 'Sp. Def', 'Speed'],
dtype='object')
print('第一属性的种类数量:', set(df['Type 1']).__len__())
print(df['Type 1'].value_counts()[:3])
df['Type 1'].value_counts().index[:3]
#.sort_values(by='count',ascending=False)['Type 1'][0:3]
第一属性的种类数量: 18
Water 105
Normal 93
Grass 66
Name: Type 1, dtype: int64
Index(['Water', 'Normal', 'Grass'], dtype='object')
print('多列去重 -- 求第一属性和第二属性的组合种类')
df_2 = df.drop_duplicates(['Type 1', 'Type 2'])
df_2.shape[0]
多列去重 -- 求第一属性和第二属性的组合种类
143
print('可以使用set做一个差集 -- 求尚未出现过的属性组合')
type1_name_set = set(df['Type 1'])
type2_name_set = set(df['Type 2'])
all_type_pair, now_type_pair = set(), set()
for x in type1_name_set:
for y in type2_name_set:
all_type_pair.add((x, y))
for x,y in zip(df['Type 1'], df['Type 2']):
now_type_pair.add((x,y))
print('打印差集: ')
name_diff = all_type_pair.difference(now_type_pair)
print(len(name_diff), name_diff)
可以使用set做一个差集 -- 求尚未出现过的属性组合
打印差集:
199 {('Fighting', 'Fighting'), ('Ghost', 'Ground'), ('Dragon', 'Ghost'), ('Grass', 'Dragon'), ('Flying', 'Electric'), ('Ice', 'Electric'), ('Dark', 'Fairy'), ('Grass', 'Grass'), ('Electric', 'Psychic'), ('Grass', 'Water'), ('Electric', 'Poison'), ('Bug', 'Psychic'), ('Grass', 'Bug'), ('Psychic', 'Psychic'), ('Normal', 'Poison'), ('Psychic', 'Normal'), ('Grass', 'Fire'), ('Steel', 'Dark'), ('Poison', 'Psychic'), ('Bug', 'Dragon'), ('Grass', 'Ghost'), ('Fairy', 'Fighting'), ('Psychic', 'Steel'), ('Poison', 'Normal'), ('Bug', 'Ice'), ('Ground', 'Normal'), ('Ghost', 'Fairy'), ('Dragon', 'Normal'), ('Poison', 'Steel'), ('Electric', 'Grass'), ('Electric', 'Water'), ('Psychic', 'Water'), ('Flying', 'Fighting'), ('Ice', 'Fighting'), ('Steel', 'Electric'), ('Bug', 'Bug'), ('Dragon', 'Steel'), ('Normal', 'Fighting'), ('Poison', 'Grass'), ('Ground', 'Grass'), ('Rock', 'Electric'), ('Ground', 'Water'), ('Fire', 'Grass'), ('Dragon', 'Grass'), ('Poison', 'Fire'), ('Dark', 'Electric'), ('Fighting', 'Rock'), ('Dragon', 'Water'), ('Ground', 'Fire'), ('Poison', 'Ghost'), ('Electric', 'Dark'), ('Psychic', 'Dark'), ('Fire', 'Fire'), ('Grass', 'Normal'), ('Fairy', 'Ice'), ('Fire', 'Ghost'), ('Electric', 'Ground'), ('Psychic', 'Ground'), ('Rock', 'Rock'), ('Fire', 'Dark'), ('Ghost', 'Electric'), ('Dragon', 'Dark'), ('Dark', 'Rock'), ('Flying', 'Ice'), ('Ice', 'Ice'), ('Ground', 'Ground'), ('Normal', 'Electric'), ('Fairy', 'Rock'), ('Psychic', 'Poison'), ('Bug', 'Normal'), ('Flying', 'Bug'), ('Poison', 'Poison'), ('Ground', 'Poison'), ('Fire', 'Poison'), ('Flying', 'Rock'), ('Ice', 'Rock'), ('Fighting', 'Dragon'), ('Dragon', 'Poison'), ('Fighting', 'Ice'), ('Normal', 'Rock'), ('Bug', 'Fairy'), ('Ghost', 'Fighting'), ('Electric', 'Fighting'), ('Steel', 'Ice'), ('Poison', 'Fairy'), ('Fighting', 'Grass'), ('Ground', 'Fairy'), ('Fighting', 'Bug'), ('Fairy', 'Psychic'), ('Fire', 'Fairy'), ('Dragon', 'Fairy'), ('Grass', 'Electric'), ('Fighting', 'Fire'), ('Fighting', 'Ghost'), ('Dragon', 'Fighting'), ('Bug', 'Dark'), ('Fairy', 'Dragon'), ('Fairy', 'Steel'), ('Flying', 'Flying'), ('Flying', 'Psychic'), ('Flying', 'Normal'), ('Ice', 'Normal'), ('Dark', 'Bug'), ('Fairy', 'Grass'), ('Ghost', 'Ice'), ('Ice', 'Dragon'), ('Fairy', 'Water'), ('Fairy', 'Bug'), ('Flying', 'Steel'), ('Electric', 'Electric'), ('Ice', 'Steel'), ('Psychic', 'Electric'), ('Normal', 'Dragon'), ('Normal', 'Ice'), ('Fairy', 'Fire'), ('Fairy', 'Ghost'), ('Poison', 'Electric'), ('Flying', 'Grass'), ('Ice', 'Grass'), ('Ghost', 'Bug'), ('Flying', 'Water'), ('Ice', 'Bug'), ('Fire', 'Electric'), ('Fighting', 'Poison'), ('Fighting', 'Normal'), ('Normal', 'Bug'), ('Ghost', 'Rock'), ('Flying', 'Fire'), ('Ice', 'Fire'), ('Water', 'Bug'), ('Electric', 'Rock'), ('Flying', 'Ghost'), ('Water', 'Fire'), ('Fairy', 'Ground'), ('Rock', 'Normal'), ('Flying', 'Dark'), ('Steel', 'Steel'), ('Fighting', 'Fairy'), ('Dark', 'Poison'), ('Dark', 'Normal'), ('Fighting', 'Water'), ('Dragon', 'Rock'), ('Fairy', 'Poison'), ('Flying', 'Ground'), ('Ground', 'Fighting'), ('Steel', 'Grass'), ('Fairy', 'Normal'), ('Steel', 'Water'), ('Steel', 'Bug'), ('Steel', 'Fire'), ('Ghost', 'Psychic'), ('Dark', 'Grass'), ('Flying', 'Poison'), ('Ice', 'Poison'), ('Dark', 'Water'), ('Ghost', 'Normal'), ('Rock', 'Fire'), ('Fairy', 'Fairy'), ('Normal', 'Normal'), ('Rock', 'Ghost'), ('Water', 'Normal'), ('Ghost', 'Steel'), ('Grass', 'Rock'), ('Electric', 'Dragon'), ('Fighting', 'Ground'), ('Psychic', 'Dragon'), ('Electric', 'Ice'), ('Psychic', 'Ice'), ('Normal', 'Steel'), ('Fighting', 'Electric'), ('Poison', 'Ice'), ('Flying', 'Fairy'), ('Ghost', 'Water'), ('Dark', 'Dark'), ('Ice', 'Fairy'), ('Ground', 'Ice'), ('Fire', 'Dragon'), ('Electric', 'Bug'), ('Fire', 'Ice'), ('Psychic', 'Bug'), ('Dragon', 'Dragon'), ('Fairy', 'Dark'), ('Water', 'Water'), ('Dark', 'Ground'), ('Electric', 'Fire'), ('Ghost', 'Ghost'), ('Steel', 'Poison'), ('Psychic', 'Rock'), ('Normal', 'Fire'), ('Steel', 'Normal'), ('Normal', 'Ghost'), ('Ground', 'Bug'), ('Rock', 'Poison'), ('Fire', 'Bug'), ('Poison', 'Rock'), ('Ice', 'Dark'), ('Dragon', 'Bug'), ('Normal', 'Dark'), ('Fairy', 'Electric')}
-
按照下述要求,构造 Series :
- (a) 取出物攻,超过 120 的替换为 high ,不足 50 的替换为 low ,否则设为 mid
- (b) 取出第一属性,分别用 replace 和 apply 替换所有字母为大写
- (c) 求每个妖怪六项能力的离差,即所有能力中偏离中位数最大的值,添加到 df 并从大到小排序
ret_a = df['Attack'].mask(df['Attack']>120, 'high')
.mask(df['Attack']<50, 'low')
.mask((50<=df['Attack'])&(df['Attack']<=120), 'mid')
ret_a
0 low
1 mid
2 mid
4 mid
5 mid
...
793 high
794 mid
795 mid
797 mid
799 mid
Name: Attack, Length: 721, dtype: object
# df['Type 1'].replace({i:str.upper(i) for i in df['Type 1'].unique()})
# 等价于
print(df['Type 1'])
for i in df['Type 1'].unique():
df['Type 1'].replace({i : str(i).upper()}, inplace=True)
df['Type 1']
0 Grass
1 Grass
2 Grass
4 Fire
5 Fire
...
793 Dark
794 Dragon
795 Rock
797 Psychic
799 Fire
Name: Type 1, Length: 721, dtype: object
0 GRASS
1 GRASS
2 GRASS
4 FIRE
5 FIRE
...
793 DARK
794 DRAGON
795 ROCK
797 PSYCHIC
799 FIRE
Name: Type 1, Length: 721, dtype: object
df['Type 1'].apply(lambda x : str(x).lower())
0 grass
1 grass
2 grass
4 fire
5 fire
...
793 dark
794 dragon
795 rock
797 psychic
799 fire
Name: Type 1, Length: 721, dtype: object
list_cols = ['HP', 'Attack', 'Defense', 'Sp. Atk', 'Sp. Def', 'Speed']
df['max_devision'] =
df[list_cols].apply(lambda x : np.max((x-x.median()).abs()), 1)
df.sort_values('max_devision', ascending=False).head()
# | Name | Type 1 | Type 2 | Total | HP | Attack | Defense | Sp. Atk | Sp. Def | Speed | max_devision | |
---|---|---|---|---|---|---|---|---|---|---|---|---|
230 | 213 | Shuckle | BUG | Rock | 505 | 20 | 10 | 230 | 10 | 230 | 5 | 215.0 |
121 | 113 | Chansey | NORMAL | NaN | 450 | 250 | 5 | 5 | 35 | 105 | 50 | 207.5 |
261 | 242 | Blissey | NORMAL | NaN | 540 | 255 | 10 | 10 | 75 | 135 | 55 | 190.0 |
217 | 202 | Wobbuffet | PSYCHIC | NaN | 405 | 190 | 33 | 58 | 33 | 58 | 33 | 144.5 |
223 | 208 | Steelix | STEEL | Ground | 510 | 75 | 85 | 200 | 55 | 65 | 30 | 130.0 |
六 练习
Ex2:指数加权窗口
- 作为扩张窗口的
ewm
窗口
在扩张窗口中,用户可以使用各类函数进行历史的累计指标统计,但这些内置的统计函数往往把窗口中的所有元素赋予了同样的权重。事实上,可以给出不同的权重来赋给窗口中的元素,指数加权窗口就是这样一种特殊的扩张窗口。
其中,最重要的参数是alpha
,它决定了默认情况下的窗口权重为(w_i=(1−alpha)^i,iin{0,1,...,t}),其中(i=t)表示当前元素,(i=0)表示序列的第一个元素。
从权重公式可以看出,离开当前值越远则权重越小,若记原序列为(x),更新后的当前元素为(y_t),此时通过加权公式归一化后可知:
对于Series
而言,可以用ewm
对象如下计算指数平滑后的序列:
np.random.seed(0)
s = pd.Series([-1, -1, -2, -2, -2])
s.head()
0 -1
1 -1
2 -2
3 -2
4 -2
dtype: int64
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
窗口实现 s.ewm(alpha=0.2).mean()
。
def my_ewm_fun(lst_p, alpha=0.2):
lst = list(lst_p)
sums = 0
devide = 0
ret_lst = []
for idx in range(len(lst)-1, -1, -1):
x = lst[idx]
weight = (1-alpha)**(len(lst) - idx-1)
sums += x*(weight)
devide += weight
# print(x, x*(weight))
# TypeError: must be real number, not list
# print('**** ', sums)
# print('-------', sums, ' / ', devide)
return sums/devide
s.expanding().apply(my_ewm_fun).head()
def ewm_func(x, alpha=0.2):
win = (1-alpha)**np.arange(x.shape[0])[::-1]
res = (win*x).sum()/win.sum()
# print('**** ', (win*x))
# print((win*x).sum(), '/ weights:', win.sum())
return res
s.expanding().apply(ewm_func).head()
0 -1.000000
1 -1.000000
2 -1.409836
3 -1.609756
4 -1.725845
dtype: float64
- 作为滑动窗口的
ewm
窗口
从第1问中可以看到,ewm
作为一种扩张窗口的特例,只能从序列的第一个元素开始加权。现在希望给定一个限制窗口n
,只对包含自身最近的n
个窗口进行滑动加权平滑。请根据滑窗函数,给出新的wi
与yt
的更新公式,并通过rolling
窗口实现这一功能。
s.rolling(window=4).apply(my_ewm_fun).head()
0 NaN
1 NaN
2 NaN
3 -1.609756
4 -1.826558
dtype: float64
s.rolling(window=4).apply(ewm_func).head() # 无需对原函数改动
0 NaN
1 NaN
2 NaN
3 -1.609756
4 -1.826558
dtype: float64