- 在线时间
- 1630 小时
- 最后登录
- 2024-1-29
- 注册时间
- 2017-5-16
- 听众数
- 82
- 收听数
- 1
- 能力
- 120 分
- 体力
- 557617 点
- 威望
- 12 点
- 阅读权限
- 255
- 积分
- 172658
- 相册
- 1
- 日志
- 0
- 记录
- 0
- 帖子
- 5313
- 主题
- 5273
- 精华
- 18
- 分享
- 0
- 好友
- 163
TA的每日心情 | 开心 2021-8-11 17:59 |
---|
签到天数: 17 天 [LV.4]偶尔看看III 网络挑战赛参赛者 网络挑战赛参赛者 - 自我介绍
- 本人女,毕业于内蒙古科技大学,担任文职专业,毕业专业英语。
 群组: 2018美赛大象算法课程 群组: 2018美赛护航培训课程 群组: 2019年 数学中国站长建 群组: 2019年数据分析师课程 群组: 2018年大象老师国赛优 |
6 E; n. ?5 V; G2 v3 I
& z& J; u4 l4 ^0 C
4 q4 Z' h1 e4 \8 u
文章目录
) D3 ~$ z0 r( u( S' u1 E第八章 文本数据
+ M; u6 v' ~! ]- i8.1 str对象: E, I: H" ~# w; g) }
8.1.1 str对象的设计意图
! s& }2 R& \ L3 X% e8.1.3 string类型
- G2 `8 R) ~1 y8.2 正则表达式基础# C6 |" V9 o! l- S2 p5 Z( j0 o
8.2.1 . 一般字符的匹配
: m" K/ L/ w$ h9 h& b) y8.2.2 元字符基础
8 h y& E2 n5 c4 m) {8.2.3 简写字符集0 l: s# `" l& Z" z4 A6 F1 D% K
8.3 文本处理的五类操作
! T% \& Q3 R# L" J& u2 F8.3.1 `str.split `拆分# ]7 W. _# q1 k7 U: }3 q
8.3.2 `str.join` 或 `str.cat `合并! ]0 w. E; a6 t$ N
8.3.3 匹配
6 W7 a/ y6 K5 H3 |; `- s$ m8.3.5 提取" `" M; D' M4 Q6 g g
8.4、常用字符串函数# S) Y8 A$ e5 {. J& \9 k
8.4.1 字母型函数4 J0 K& L7 }' e& Y/ u- j6 V
8.4.2 数值型函数* c' n2 K* `' {8 ?
8.4.3 统计型函数8 g( l' |9 j! G8 `4 h3 A
8.4.4 格式型函数# j; ~/ {% K' C7 X3 M" t
8.5 练习/ w6 \2 `1 c. E( b- e. e
Ex1:房屋信息数据集
; O+ h3 d1 v6 x# _, V2 aEx2:《权力的游戏》剧本数据集; m4 j1 @2 o8 ^
第九章 分类数据
0 X" p1 Y& o3 Z( d; C. {3 ~# \9.1 cat对象
( {7 K! V% c9 F5 |9.1.1 cat对象的属性: H& N8 C6 v7 Z' i- t0 n; u" }
9.1.2 类别的增加、删除和修改3 F6 r$ G; q* U, N
9.2 有序分类
# {6 z6 U- ~; m% M) j' l9.2.1 序的建立
9 M1 e2 f6 F, W. N( j5 F9.2.2 排序和比较8 Z$ m) H" E4 d! {
9.3 区间类别% h/ e0 x0 t# N8 u
9.3.1 利用cut和qcut进行区间构造
' i" y! T; K8 T9.3.2 一般区间的构造
0 [) ^" {- [; E; I9.3.3 区间的属性与方法. k/ X, _- g( Z
9.4 练习+ T; l5 e4 y3 J, \* Z
Ex1: 统计未出现的类别
p5 Y, D+ n" @! O, hEx2: 钻石数据集( H$ C# k. `! K9 Z
第十章 时序数据
) l t/ i0 K' z# |10.1 时序中的基本对象, @5 d3 ~* s4 F s2 {* P
10.2 时间戳
2 C' z* a" t7 n3 Q& e, n10.2.1 Timestamp的构造与属性
) j2 L$ d4 [5 O( X: x; B) ~1 O% Q10.2.2 Datetime序列的生成8 r% ]4 m( M7 E+ }, H! w) w, a
10.2.3 dt对象
4 E) d8 s" M9 n+ c7 Z/ F* [10.2.4 时间戳的切片与索引
! J6 J7 e# e1 T8 \10.3 时间差
2 K5 z ?4 R0 U, _0 ~; I7 _10.3.1 Timedelta的生成
) \& o( Q( A% F10.2.2 Timedelta的运算
3 [) v) p7 Z8 O# s; M10.4 日期偏置
' E; z3 _! E8 J; h1 ]10.4.1 Offset对象3 a7 t7 W# C! o2 J' ], N Z: O
10.4.2 偏置字符串
! [1 e9 L! [) e E10.5、时序中的滑窗与分组0 W7 H* a: A5 K' b0 z
10.5.1 滑动窗口
+ ?1 Y. W- ~5 t- `10.5.2 重采样
8 _* Y" N8 U! U0 u& u$ K3 R+ u; Q10.6 练习2 _: A2 B/ r' H+ W
Ex1:太阳辐射数据集
& _9 W E, j# O) b* h4 m3 WEx2:水果销量数据集" t- p) O" @3 R, i: H/ `
课程资料《pandas数据处理与分析》、github地址、讲解视频、习题参考答案 、pandas官网
1 l$ q! O' a0 @传送门:& Q, @9 q. {& A; p8 x" }* d% J
# \- {0 k$ t# b2 t" Udatawhale8月组队学习《pandas数据处理与分析》(上)(基础、索引、分组)3 S1 Q& p7 j" T& H
datawhale8月组队学习《pandas数据处理与分析》(中)(变形、连接、缺失数据)
! u) t; I l: v6 l: m7 R' t第八章 文本数据+ ^( E8 L* q. q6 i
8.1 str对象0 C" }5 t0 R4 Z1 m Q( W" H
8.1.1 str对象的设计意图 v7 |1 Z# u/ z4 h" v) {2 G
str 对象是定义在 Index 或 Series上的属性,专门用于处理每个元素的文本内容,其内部定义了大量方法,因此对一个序列进行文本处理,首先需要获取其 str 对象。在Python标准库中也有 str 模块,为了使用上的便利,在 pandas 的50个 str 对象方法中,有31个是和标准库中的 str 模块方法同名且功能一致,例如字母转为大写的操作:
6 ~( s2 `: Z3 ?5 }- |/ G. x7 G: M
var = 'abcd'
5 H7 h% S* e5 x v3 v# l1 m- gstr.upper(var) # Python内置str模块
3 P5 D7 f8 s: C: I6 ~5 FOut[4]: 'ABCD'
0 Z% R P2 ?" D. V4 Z6 J F; t' P4 n) ^# G& K; Q' h# \
s = pd.Series(['abcd', 'efg', 'hi'])
8 [. j8 A* e$ [- P3 D5 K3 F" L9 c& j* e' f
s.str- ?2 e! Y& b3 I0 H8 G* Q! w
Out[6]: <pandas.core.strings.accessor.StringMethods at 0x2b796892d60>& \; o' U( |' d
3 [. B5 H7 W1 ~8 S k1 l
s.str.upper() # pandas中str对象上的upper方法
, s2 L6 D# H t# y' ~* eOut[7]: 8 n8 p7 l3 H [- j- u; _' c
0 ABCD
) h" R8 J6 m: P/ s% V2 {1 EFG
7 O, T' h1 x; y4 g5 i0 m2 HI
) _! O p/ L# R0 s* f+ W7 mdtype: object
1 p5 T- ?7 Y% P7 M+ L: q1
: A& q' ]' _, l9 `" I# C21 s; i8 ]- s; q& F
3) ^: |/ U, U4 A6 @+ c7 b
4
' v+ B" P& c: O& u- N5( w) t1 @5 f2 \2 v( _' t
6$ X3 f0 X4 s: [: M7 M' \. U% P
7
2 }2 K; }) n2 B4 |! e" I0 c8, X, s6 s( N3 M9 w* e( V# D& x$ m
9
% b! M7 p% j' \8 t10' ^2 N7 Z6 a3 t7 @
117 _6 `) ^( B' a3 y# ]; d9 N
125 d# \, o M' X5 m0 @7 z+ m
13
9 U, c3 u- \0 Q9 _# r14" b( d' @% s4 _- a: h$ n8 ^
15
0 {6 W! U5 @, D3 O8.1.2 []索引器
3 N/ v, f; `# q; h8 H 对于 str 对象而言,可理解为其对字符串进行了序列化的操作,例如在一般的字符串中,通过 [] 可以取出某个位置的元素,同时也能通过切片得到子串。4 U% b) l6 K% R m9 X U
pandas中过对 str 对象使用 [] 索引器,可以完成完全一致的功能,并且如果超出范围则返回缺失值:
: a- l2 q* C- V1 l. U
/ w! s- V* L xs.str[0]
$ G: J4 P) t) O) e0 TOut[10]:
4 [8 P5 I! @- X0 a. ~8 r/ v. I; _/ f
1 e
: d1 g) S+ O- o! `* s7 t2 h
8 q6 F$ D6 D8 x2 E xdtype: object
1 C( \- O, g) u% d |+ O2 R8 b; |7 n G2 e9 F
s.str[-1: 0: -2]/ i( b6 [- S* z2 W
Out[11]:
5 t3 ?/ E1 J: V& ]: _1 j7 g0 y1 E0 db$ e! _4 Y( ^: z4 J8 Q3 p/ x
1 g
5 E2 [" V( n) c2 i# k6 A% y" \4 u& \+ m
dtype: object- K7 m' `/ B8 @
( h9 u# |" C6 H5 P5 G' m6 Os.str[2]
/ }! A h' M8 Z7 eOut[12]:
6 p+ [/ v3 W/ a+ L4 ~0 c7 h n& d Q! Y: w9 [
1 g
s- o) r& G2 b C) b) Q2 NaN" n1 g$ p" J1 G. R
dtype: object
) r, T: }6 p8 H. O
6 ], x. F0 v$ j0 U5 n1 A( T" P1
( n' T% y4 {( j% e! D+ k2
( v8 N4 l1 d# x% r `. `3
' O9 O- r" }: k* F& W9 v6 {2 F4( Q$ Q7 s& ]! s, j P! B
59 R6 g8 L: m# S. w4 b3 ]
6$ d. L/ g" |* |( c* M
7. J# c- g$ B& ~' a; L
8
) i/ m Y. U4 C" ^$ C9
8 w/ X. N6 X2 f6 `$ `4 J+ y/ g: d: ^10
: p9 g9 l$ Y# e# V112 [6 _* e$ `+ [6 {& \' b
12) ?# |- ^+ W3 N2 X+ c
13
' B- R; E: w; z% ]8 R8 ?141 K2 A# f* j) A' D
15
5 F t/ K! ~. R3 L" a( \16
2 Z8 N5 @7 S3 ? [5 B& U' t17+ o; ]# n5 l1 n
18
8 O7 ^* h- \# F2 S6 Y$ m8 S: C7 ~19
1 ^& l! |5 E5 b, M6 X) ?0 b20
& L# ]3 z2 E; P5 _import numpy as np7 l! F' h! s% D P1 \
import pandas as pd- h$ s" z' u; g
+ ^$ c: i& J4 e2 h/ E# ~4 t5 l
s = pd.Series(['abcd', 'efg', 'hi'])
: ~0 _( n8 l, K+ as.str[0]) E. O3 F: J5 T5 E/ I( C& Z
1
4 ?' _# m# R: r f5 M6 }1 @24 C6 q( {* C9 p5 y1 y- T2 H
3. d- J8 K; a5 b* A7 K
4
1 A$ \6 ]8 t" v1 ?/ o/ n5) k( C" r7 @( M5 N& d/ Q; C
0 a/ c5 Q8 h$ J" V
1 e/ G8 ^2 J+ {, U- N ?: r/ f7 x
2 h
# i% m6 ~9 l$ z9 adtype: object
' B! p U2 S) v/ k! N% ~1" X* W4 ?' X% X- d4 K" g7 n9 B
2
+ Z6 W* {$ K3 V' g4 y3' t7 f! S4 w: c# {. R* E5 M
47 W4 J% [/ m2 V" I0 l. Q0 i* w
8.1.3 string类型# o: j7 }' C& V, Y
在上一章提到,从 pandas 的 1.0.0 版本开始,引入了 string 类型,其引入的动机在于:原来所有的字符串类型都会以 object 类型的 Series 进行存储,但 object 类型只应当存储混合类型,例如同时存储浮点、字符串、字典、列表、自定义类型等,因此字符串有必要同数值型或 category 一样,具有自己的数据存储类型,从而引入了 string 类型。
4 H. N9 _1 N! r& G 总体上说,绝大多数对于 object 和 string 类型的序列使用 str 对象方法产生的结果是一致,但是在下面提到的两点上有较大差异:
( |4 `; @8 \# m" B. Y3 ~% Q# D8 ?( u
二者对于某些对象的 str 序列化方法不同。3 r& o- K8 A" Q% W/ r" w
可迭代(Iterable)对象包括但不限于字符串、字典、列表。对于一个可迭代对象, string 类型和 object 类型对它们的序列化方式不同,序列化后str对象返回结果也可能不同。例如:$ Q* P. F! V! j( H
s = pd.Series([{1: 'temp_1', 2: 'temp_2'}, ['a', 'b'], 0.5, 'my_string'])
( o5 s6 k- o0 }- ^* os
- s9 T2 {5 X9 v. H+ u! G, d s5 i6 V1
) q! c; Y( J6 @( o2
& X& h! F% |0 Z1 m8 r( `! \0 {1: 'temp_1', 2: 'temp_2'}
% O# ?' J2 q" f& V# g/ Z, m1 [a, b]
* r1 R& Q" |# Z U& M2 0.5; O O8 Z# H8 _* @
3 my_string, t( G# [$ x7 Q: @1 | ~: L1 q
dtype: object" f) e6 B3 G! H2 j6 h; J
13 g4 I% w* G: J$ x
2
: v6 D& G( x2 }; F- @# A d' ?4 }3
7 @ @. y1 b& U9 J# u0 x& B4 W1 w# z4 c4
m" [, B9 }9 B- _/ ? y5
- l8 r( u& N7 v( }s.str[1] # 对每个元素取[1]的操作
) M: H' m5 M8 u" l# g1. `- u4 g. w$ v$ E- E$ l) b! K9 j6 \% o7 ^
0 temp_1
: R3 ]# T4 B. ], h2 Z1 b
1 I, s- i+ y% I+ {2 NaN
) n/ _! u% W C! ]* f/ O n, h: r9 Z' n: Q3 y
; V8 d! i' N h6 K( s* Q. Pdtype: object
8 S) X/ t0 `4 M) u, i! E$ U; ^1
. a& N8 V6 a6 I E3 X21 V$ M. C7 u3 J2 [4 g0 T5 L
39 F' `! }9 n& _ N6 N6 ]) Y
4
# X8 Q& k) P- T: X( Q& l56 w, t7 m5 u8 z' Q. ]
s.astype('string').str[1]+ x6 a0 p, [" {/ ]: i* M1 w
14 Y( ^6 K" @! [: S# B+ U
0 1
: v3 G7 I, s& o" j9 q& O1 ', Z& r; k/ N7 {8 t
2 .
- Q2 s6 e4 h8 V) M- V' s3 y
1 M8 G- c' y" A; R/ f- ^dtype: string/ w1 a: k9 ` U# _% O3 R$ E2 P6 U" n
1( }( p9 r; S: C# `" ~* j/ D
2 k7 a4 O7 B" H' e/ n* }3 H
3
( |$ P( [+ q# I. L5 Y4
/ z8 a: \/ c& E+ z5! s3 ]1 n' o) a2 C3 Q0 A
除了最后一个字符串元素,前三个元素返回的值都不同,其原因在于:
" P/ x: ? @4 h! s8 E* W) Z$ D$ v2 Q8 T; u9 H
当序列类型为 object 时,是对于每一个元素进行 [] 索引,因此对于字典而言,返回temp_1字符串,对于列表则返回第二个值,而第三个为不可迭代对象,返回缺失值,第四个是对字符串进行 [] 索引。! ?3 S. J0 R' P t/ ^: r$ Q% |
string 类型的 str 对象先把整个元素转为字面意义的字符串,例如对于列表而言,第一个元素即 “{”,而对于最后一个字符串元素而言,恰好转化前后的表示方法一致,因此结果和 object 类型一致。
A3 W6 F7 p, m/ m( Q+ zstring 类型是 Nullable 类型,但 object 不是
/ ]0 @& f {3 | 这意味着 string 类型的序列,如果调用的 str 方法返回值为整数 Series 和布尔 Series 时,其分别对应的 dtype 是 Int 和 boolean 的 Nullable 类型,而 object 类型则会分别返回 int/float 和 bool/object ,不过这取决于缺失值的存在与否。
+ t7 S1 ]8 j' q- p7 N* q" A 同时,字符串的比较操作,也具有相似的特性, string 返回 Nullable 类型,但 object 不会。
$ m! f" H8 E% T. `. ^- ?s = pd.Series(['a'])
! b1 Q3 {. R! s1 k5 X; I2 ]4 N/ p/ @% m; [1 Q
s.str.len()
5 R$ l' c# x/ b4 {% sOut[17]:
$ F; j1 u' j4 F/ Z7 C0 1
3 B9 C, b2 V- S M" W' Rdtype: int64/ c- Q: j0 j N( a. E
2 V6 p U, }8 ]' ~% ks.astype('string').str.len()
; z$ |/ L) X( d0 lOut[18]: 6 h; {; M# \+ H: c# g" O, o
0 1
l7 g& ^" ?- s! W/ \* l0 Adtype: Int64% s1 l1 x! J% |; ~( W
* x' h$ X. y2 f7 u6 n( E; a
s == 'a'7 r+ c; X5 w8 f9 p! G1 a; d
Out[19]:
; w b7 J0 C" x# [! T% `" i0 True, ?% O; O2 A( O
dtype: bool
2 m, n# ^9 ~3 m( }1 r) A9 g$ ~+ F" q+ L% Z" L* [- ^* M
s.astype('string') == 'a'* X+ F3 U, `) N: B+ w; {- R5 l
Out[20]:
( @0 v1 E) z, D/ P8 W6 Q1 d" a! {0 True+ z. y$ x/ T4 k* a+ n
dtype: boolean. p3 H: n1 Q" U/ z' I% C
" T) ~% e0 A$ z$ q. S2 ms = pd.Series(['a', np.nan]) # 带有缺失值
6 b/ G% _/ g# h0 C* b) r1 u# B- m
" ^, h( l2 o: y- ]s.str.len()
( f( s* ~5 a7 C k0 A, m1 ZOut[22]: , M1 |( m: ^5 [3 p7 g, R
0 1.0: G- T: v; V r* K
1 NaN
+ U# f! U X& {. Q( Udtype: float64! M4 N* k X& [, R
- ]+ w! [* j* J2 s
s.astype('string').str.len()+ x* w! U# D, Z7 t
Out[23]:
$ U# Q3 Z2 _% ~, m0 j- w1 @/ l0 1
9 A9 R W+ \4 O; _" u/ l# S j1 <NA>
, U) g W9 W4 Kdtype: Int64: e% f: B- E% v/ ?9 T7 a
% {$ g$ ]! b+ a7 Hs == 'a'# B+ y2 ~& V' s+ t
Out[24]:
" M Q+ _) G' [; a0 True2 S7 L6 n; O! T0 g2 i- n
1 False
4 G# @) b* N$ ?# t" Ddtype: bool
. {+ I, F! i: u
. R# Y8 |. n! g7 T0 M& ds.astype('string') == 'a'% z: N. o2 U( ?8 }+ i& D
Out[25]:
& |7 H1 H/ e* P( c3 t0 True7 U+ k4 D5 H! A' j- d( r. ~9 o
1 <NA>& O4 g/ k" a2 r! F- d* m: S
dtype: boolean( i8 J3 o" U6 @ M% X
6 w* W8 p* \& b6 R
16 ?( k b2 `# i( K# p) j
2, X& F8 q$ A W& h5 S( {6 i
33 O7 i% I( ?3 m; o5 g5 m$ e" q; j
48 k) d* |( N6 T8 r' Y3 v& r7 t
5: J0 p8 h% r1 f9 ?; b
6
- a& _! H, G% g6 u( ^2 {+ d# M7
6 I( B$ k1 |& `1 S& y' h8
4 y+ p/ W: Q9 ~' u' x% h95 x0 a% w5 b/ B+ X j' P2 g+ e
10
- g6 z' H; \. V& }117 i- l* ^8 o" l3 Y8 S# V; K g1 ~
121 L+ `* u" o: d# S& B* @) |
13
0 `' W" L& N0 c$ `14
. C" L4 x& [( ^15% f" f. u' ?6 O8 x) }7 o' ^7 s
16
& ^ ^$ |% n+ r$ C" n17
$ d, A5 F5 y2 X: ]" _18
. |3 Z v" c% m% r19" n+ P0 }/ W; n. C3 O I0 g, C
20. q( e8 S2 M% i4 o+ R* _4 t, H
21+ z4 X; G1 H% w4 d3 m8 a! J
22
; t6 x& h5 I; E/ V3 V1 I W+ m `232 ]# }1 F' x1 @
24
' M- a- f g" ]2 G( d9 i1 M2 z25
- n% Z' S3 }) y7 }. W' g V# Z26# ?& m) A6 {4 f1 j
27
' F ~7 b1 w) F% X/ K7 Q) R k28
P# f3 `4 p% W/ F9 q- s29
% }1 t# m. d) @! ~: Y) i |30
# {2 f% F6 a( F( H2 w31
+ i2 r- B' m3 B+ a3 q6 s4 l3 I: z" V; x32
0 \0 _% L i% w33( y2 _# G0 Z. c# M$ N- @
34, x9 {' _8 S' _/ ?& J7 R
354 b* h6 p' C, |' D5 J2 T
36; G" {7 g+ w6 M0 C# d+ O; B
37
9 o' [& O0 e0 G4 Y38% P! e* o- t/ d% l
39
5 m- y5 F8 A4 L9 k40+ D; a' a0 X# \, _, v
41
& J& {5 J2 ^. t1 V42
Q. D. a5 m2 X! ]; o9 A43, \. T9 y( v3 S5 B: W. A, f4 p
44: M2 R2 z+ {1 c: @4 }) A
451 W2 }" ]; E0 s- X
46
0 i7 v% s9 Q2 c. k* K8 q2 o479 z. E- R+ R( ^$ B8 u5 g
对于全体元素为数值类型的序列,即使其类型为 object 或者 category 也不允许直接使用 str 属性。如果需要把数字当成 string 类型处理,可以使用 astype 强制转换为 string 类型的 Series :
4 G9 u) u8 ]( p* T7 p% g9 q# `% R3 d
s = pd.Series([12, 345, 6789])& ^4 ^- z P9 \
' H/ Q5 l8 N3 p! [# Qs.astype('string').str[1]( h" ]0 U$ B$ A# w8 S
Out[27]:
2 j' [3 b( @4 O8 z; b! M) J0 21 Y% Q" v. ~# i# [
1 4
& W+ h2 G+ s! v- w4 X2 70 p# r' R: B5 Q: ^1 L8 P- X
dtype: string
- t% F+ z9 w& r' [1 W7 K4 I; {1; E" r6 } |7 o4 R& b2 o
2
* m) C& w/ o* w0 M' a33 ]$ m5 D1 p7 y" b: u% Y; r+ J
4
( Y( z( s. D; \. [8 o9 ]4 w0 w54 S+ t( q, ^ U5 \
6
% r2 C1 |. ?4 u( C' R7
5 ]0 F1 F: h2 t0 x8
7 g, k4 `0 O. ?: Y8.2 正则表达式基础) \4 l. H2 O( G1 S6 b* y
这一节的两个表格来自于 learn-regex-zh 这个关于正则表达式项目,其使用 MIT 开源许可协议。这里只是介绍正则表达式的基本用法,需要系统学习的读者可参考《Python3 正则表达式》,或者《 正则表达式必知必会 》这本书
0 @3 J5 T! Z* q9 f) {; O( A
' H$ q8 Q2 C" X: n8.2.1 . 一般字符的匹配$ h$ Q0 w4 L% u7 L( _& B
正则表达式是一种按照某种正则模式,从左到右匹配字符串中内容的一种工具。对于一般的字符而言,它可以找到其所在的位置,这里为了演示便利,使用了 python 中 re 模块的 findall 函数来匹配所有出现过但不重叠的模式,第一个参数是正则表达式,第二个参数是待匹配的字符串。例如,在下面的字符串中找出 apple :
?2 r+ K \- @- L1 G$ I/ J: K) \* X
import re$ M9 g" }9 ^' H" O
9 M8 p6 F+ \9 b; B
re.findall(r'Apple', 'Apple! This Is an Apple!') # 字符串从左到右依次匹配& Q7 S" T4 L" [$ b6 S8 z; G3 a
Out[29]: ['Apple', 'Apple']
$ y$ K! d% W, _6 N1
) `, i/ P4 y( H) e, T8 Z3 n5 `2
1 F3 Z) u/ z: F& Z' T8 \0 [" g6 f3
, c1 f. Q! I+ j( z& f* U: M4
1 ^2 G, r! b; ~. N8.2.2 元字符基础# g+ G# o- \5 E' ^
元字符 描述
# m( C4 s$ _. ^$ j8 i. 匹配除换行符以外的任意字符
2 V2 O: }. R7 b* V[ ] 字符类,匹配方括号中包含的任意字符/ e& h0 q9 C4 d- }% B: t
[^ ] 否定字符类,匹配方括号中不包含的任意字符
( }8 k1 r% z/ ~' I* 匹配前面的子表达式零次或多次
# V& s& [$ K' |+ 匹配前面的子表达式一次或多次。比如r’d+'就是匹配数字串,r’d’就是匹配单个数字) N# q5 {/ u0 d4 m: B' p. O
? 匹配前面的子表达式零次或一次,非贪婪方式% R9 f! c. A9 X
{n,m} 花括号,匹配 n 到 m 次由前面的正则表达式定义的片段,贪婪方式
, w" x5 |& P" o8 s$ L(xyz) 字符组,按照确切的顺序匹配字符xyz
( ?5 a* f6 n7 e" K2 [: K| 分支结构,匹配符号之前的字符或后面的字符: m% m) I: n2 Q) Z8 H$ W. a
\ 转义符,它可以还原元字符原来的含义
8 o& o& a: F% ~: R- @^ 匹配行的开始
) l* R3 s( [' F" L9 X$ 匹配行的结束4 N$ r* X- Q6 q
import re: L8 \% H" ^2 G. v; r
re.findall(r'.', 'abc')
; {: q5 x4 |9 A1 e, r! e3 W8 ?Out[30]: ['a', 'b', 'c']6 w1 ]3 ]8 ~& z, J
& |( W: V1 l0 h- c7 ?re.findall(r'[ac]', 'abc') # []中有的子串都匹配
9 D; y: c; H" {4 B4 z& `Out[31]: ['a', 'c']/ ~2 ^- s1 f Z! D3 f
! D+ Y: Y+ l" ^9 ^2 kre.findall(r'[^ac]', 'abc')
4 Q- [8 V5 y4 M: T! z1 K3 ROut[32]: ['b']' L$ U x1 Q+ b/ j; y- p& J
, Z7 [! |) a7 W; v5 b
re.findall(r'[ab]{2}', 'aaaabbbb') # {n}指匹配n次
4 V' y u+ E$ \9 t. j+ P! C9 `Out[33]: ['aa', 'aa', 'bb', 'bb']
$ W4 w9 O; M8 ] f0 y5 U- Z# K. \ G
re.findall(r'aaa|bbc|ca', 'aacabbcbbc') # 匹配前面的或者后面的字符串
$ b1 |. w7 n9 z" OOut[34]: ['ca', 'bbc', 'bbc']
5 m9 b0 T/ p8 @+ [! B* G9 c
& X/ I2 M% x. v1 x( L) B9 g! d# 上面的元字符都有特殊含义,要匹配其本来的意思就得用\进行转义。* Y& I( b) h0 S1 S
"""0 O% w. K$ [) o8 P2 S
1. ?匹配的是前一个字符,即被转义的\,所以|前面的内容就是匹配a\或者a,但是结果里面没有a\,相当于只能匹配a。
' X. I Z* c8 L7 I% s2. |右边是a\*,转义之后匹配a*,对于竖线而言左边优先级高于右边
0 V; p* v7 Z6 R3. 然后看目标字符串aa?a*a,第一个a匹配左边,第二个a匹配左边,第三个a虽然后面有*, s6 r& @3 |' P, W% f& g. T- w( e8 S
但是左边优先级高, 还是匹配左边,剩下一个a还是左边,所以结果是四个a4 ~1 s6 {' k2 q. V& h" U* B
"""
) b/ N0 D2 U% h- X+ L$ w% {
$ w; I$ y6 y$ w% T! e5 Ore.findall(r'a\\?|a\*', 'aa?a*a') # 第二次先匹配到a,就不会匹配a?。a*同理。, M9 J$ l6 Z8 Y I( N
Out[35]: ['a', 'a', 'a', 'a']
* X0 Q/ c/ v6 d$ g& u- l3 U& S+ [9 f& J0 z" _
# 这里匹配不到是因为目标串'aa\a*a'中,\a是python的转义字符(\a\b\t\n等),所以匹配不到。) Q, C) P5 o$ U/ T& V& `' F
# 如果是'aa\s*a'之内非python的转义字符,或者'aa\\s*a',或者r'aa\\s*a'就可以匹配到\字符。
( j- F, {" r/ ?* `re.findall(r'\\', 'aa\a*a')
P. D5 M# y; I* }- I[]
2 \: U: p( z! C. s+ u) K( [
2 b3 `3 r- P6 ?5 I$ M9 P0 Bre.findall(r'a?.', 'abaacadaae')
, @! d% ^) q% Z4 f X x* nOut[36]: ['ab', 'aa', 'c', 'ad', 'aa', 'e']
0 |$ n8 D" Z# v# K' {8 v: d# K. E$ c x M
re.findall(r'(\w+)=(\d+)', 'set width=20 and height=10') # 多个匹配模式,返回元组列表+ z3 O7 s i* I* D! o# j+ ~
[('width', '20'), ('height', '10')]
" M& r8 C4 U% m
* B5 Q. a, |9 ~9 t* ^1
% o( _2 P- `, u, g29 [% Z1 ^# c# u! d- r
3
8 g+ F, O6 `7 A4 q! T4 g6 ^1 W. q P) j
5
8 V, M, H9 V+ o- V0 z- `$ T) u6
6 k9 k0 J7 I( i9 [7 U& Y1 ]7
* |8 n* x; Z3 p" x4 z6 m; O80 Y3 Q3 M1 d* u' U
94 r p9 |0 x M# E8 l8 v
10' v2 j; V7 r# S/ ?
117 L- G! R7 U- U+ f- i( k5 ^
12
" F% {9 W9 w9 J; |% N V130 t" N0 d F: Y, R" S& k: t, y
14
6 G! y' o8 L+ Y0 w! I6 h$ Z15- e9 O7 r% i' Y! g
16
# B7 A- e% z! b" P* w x" J7 o4 `( o17
3 y) l' h" B/ L$ b1 y! L18, {" I$ N" C3 F; Z# I& N
19# ], x) s8 \3 T$ M( m! D
20" S/ u* E. m4 a5 K) c! ]
21; c! l ?2 L/ e( [0 F, e3 @
227 ]$ H9 E0 e1 O+ ]- y
236 s! K3 Q5 o q2 j0 D0 p
24( O. @3 w9 M1 p6 \: [$ J
25
9 F) O5 j* t+ x# U2 A# h266 u, s+ j* J. b4 ~ G
27
+ l- e) [; m6 k1 B4 S28$ L! _1 R) T9 `5 x
29
i7 D3 r4 Q$ H. q30( ~- ?9 e$ b) |5 j. @7 g
31
; J0 X7 d) e+ x7 H9 X& G. F7 l& [32
/ m9 v1 D! E. g9 N4 E/ t33* U: s; }6 w; i* i6 c
34: z9 Q4 @4 @# W3 R1 J
35
6 P7 a( W7 b0 x8 W8 A* G3 I36" e* c9 D! l, ^$ H
37
5 K, f9 H3 j$ O% |9 i# T O8.2.3 简写字符集
1 g& q; O" y. c! E: i则表达式中还有一类简写字符集,其等价于一组字符的集合:; n" \, n0 l( B# E9 K: j
* W Q% I/ d/ |! H$ o8 g! M" n简写 描述
3 I. b5 T& Q5 k( V5 y" x R\w 匹配所有字母、数字、下划线: [a-zA-Z0-9_]
* v( H8 P; Y. y. Q/ z\W 匹配非字母和数字的字符: [^\w]) ]/ p7 C; l) r4 K* p
\d 匹配数字: [0-9]8 q% u& ]) u! @; {+ f7 K
\D 匹配非数字: [^\d]7 [& v6 |) i; D4 C4 _* f9 l
\s 匹配空格符: [\t\n\f\r\p{Z}]
2 Z: R* R, u- l0 V\S 匹配非空格符: [^\s]
' g7 Z7 e4 h3 N1 B4 v$ B3 N\B 匹配非单词边界。‘er\B’ 能匹配 “verb” 中的 ‘er’,但不能匹配 “never” 中的 ‘er’。
. g2 L5 s' g0 ]. o- ure.findall(r'.s', 'Apple! This Is an Apple!')
$ h" a6 g& l9 _% D" q' i3 FOut[37]: ['is', 'Is']- ~. u& w* P! p3 f
9 [+ _1 ?6 @- M" O
re.findall(r'\w{2}', '09 8? 7w c_ 9q p@') # 匹配任意数字字母下划线的组合,但必须是两次
5 f0 B2 R$ z. d/ Y0 |) D7 lOut[38]: ['09', '7w', 'c_', '9q']9 ]7 s% u8 Y/ l
8 O( t! ~. q9 h/ x) p) [) H
re.findall(r'\w\W\B', '09 8? 7w c_ 9q p@') # 匹配的是两个字符串,前一个是任意数字字母下划线(\W),后一个不是(\W)
; W+ _. [* I8 O" G2 zOut[39]: ['8?', 'p@']
7 Y/ U% z* d: ^) a( F1 u( g- b/ F3 H/ K) Y
re.findall(r'.\s.', 'Constant dropping wears the stone.')
5 M# o. n9 l, Y4 s) ~2 F% i9 t) ZOut[40]: ['t d', 'g w', 's t', 'e s']
. n, r. }7 B5 F4 Y2 g7 U- q9 R; [/ @) F1 q% G/ G
re.findall(r'上海市(.{2,3}区)(.{2,3}路)(\d+号)',
7 m, ?5 z: K {3 q0 t+ M6 b '上海市黄浦区方浜中路249号 上海市宝山区密山路5号')/ s5 y8 V5 ]. s( f
) y! s* }* W( m8 _& \
Out[41]: [('黄浦区', '方浜中路', '249号'), ('宝山区', '密山路', '5号')]6 }% X/ t4 h8 F5 U5 I2 S/ @
" f e% k( n+ K" \8 e! G
1" ]% K% r6 t5 |( s; }
2& h! |1 @9 g6 S# P. a
3
, Y4 H6 Q/ }( {/ A& v* Z4
& x3 Q) i6 Q$ R! x, D52 u. D: U( o. E% t
67 ^7 q& h" H6 q: g2 G
7" `' {0 h" o t5 Z4 R6 N. F8 c
86 @/ j% b$ B% @7 _
9& p5 E; W8 i; c `( }; \2 R; \
10
& Q4 I7 W) s7 s) w U3 ?1 x11, ~, N& F$ q1 r
12+ _( W- B0 u$ ]3 t- X3 j
13
7 r; |- _ h9 D' m144 Y7 T0 ]: M0 F* {7 }8 }
15
# Y" c; h: e2 ]! m7 d5 G16. _* T0 K7 _9 S O& N5 _
8.3 文本处理的五类操作
* h" N8 F' D6 `5 ~8.3.1 str.split 拆分
3 s( z( t, N, P/ G# T str.split 能够把字符串的列进行拆分,其中第一个参数为正则表达式,可选参数包括从左到右的最大拆分次数 n ,是否展开为多个列 expand 。
6 M! i1 x* C3 H
- }, s6 R3 D+ V- ls = pd.Series(['上海市黄浦区方浜中路249号',; C$ C# R) H( Q
'上海市宝山区密山路5号'])1 g* k5 j7 U5 v U9 \5 P
6 n: q+ d8 O2 p2 G' n: D& L
0 B4 d ]: l* m5 V5 As.str.split('[市区路]') # 每条结果为一行,相当于Series
0 N9 v, n4 ~- \- b i! i& eOut[43]: 7 N& S. Z/ x% e+ X. @
0 [上海, 黄浦, 方浜中, 249号]
% F5 K8 y$ t6 [- r9 i1 [上海, 宝山, 密山, 5号]
9 f7 R$ i* }0 C1 ~; `dtype: object
3 Z9 _3 t" F7 z! `7 q) i0 Y7 h0 x
s.str.split('[市区路]', n=2, expand=True) # 结果分成多个列展示,结果相当于DataFrame
: L% c9 w3 ` j0 YOut[44]: 3 j7 l9 i* a3 C) ~" S
0 1 2
: R. p+ p1 R1 W+ o! K' ~" `0 上海 黄浦 方浜中路249号
2 Q) R2 n/ F, o" @- }& d2 Q1 上海 宝山 密山路5号$ ?( M" Y7 h% J4 W6 {1 W
1
0 j/ U8 a- h' o: N: j( F0 U; t27 G- }& M; ]# U! Z/ L: K
38 i; Q6 k7 e9 o" \
4$ m/ `: h" g4 A+ N
57 s0 x& b! a" _3 P8 b4 j& L
6
& T4 u# P0 ?/ X7 X3 A# A! }1 N75 \0 J) A! D8 o# r# d- u
8+ d- Z6 M; Z2 U! u- n% k5 J
9
' w' V. i- y# Z10% ], G% x& H3 \( f7 W/ j$ x
11
7 T, E# D0 b: p6 z; y) X: A$ H$ V12% G- m2 k8 |& g3 X" G
132 |, C/ ^- q3 c8 w) _
14; ^4 ~& }' L5 o8 J5 E2 }' S: U' N
15
' Y6 j& b. @7 j8 n2 }0 v- l# X2 q) T 类似的函数是 str.rsplit ,其区别在于使用 n 参数的时候是从右到左限制最大拆分次数。但是当前版本下 rsplit 因为 bug 而无法使用正则表达式进行分割:9 p8 @5 ~% R$ w! d! N) U
% m) P5 E J- A! Q/ P6 ? o: w: Is.str.rsplit('[市区路]', n=2, expand=True)3 j* Z3 {4 q d- V* `
Out[45]: 2 C; R& @ z4 U# m; p6 u! p
08 s. r+ }( B1 v3 I" q1 }/ m8 r
0 上海市黄浦区方浜中路249号
: k- Z1 @7 ~$ U- N* j. g5 f1 上海市宝山区密山路5号 \: R. l3 G: g, V/ r a/ G6 @
1
+ R. M' O4 N' G. n) E6 ~29 H3 e& x4 V- K. ]/ u4 }
3
. ^0 E5 T; A3 [, e. k, m! x4/ f2 b4 s% V. C4 U6 `- D* O
5
' P4 N" Z1 D2 R2 k8.3.2 str.join 或 str.cat 合并
$ P- w5 D* z, H$ p7 c5 u: P$ xstr.join 表示用某个连接符把 Series 中的字符串列表连接起来,如果列表中出现了非字符串元素则返回缺失值。
2 L. s7 p2 R' h, _# d- Hstr.cat 用于合并两个序列,主要参数为:1 L& F0 b' \# F: _2 {& a
sep:连接符、
; {, O) g% J9 r) y7 X' V* c% E8 _8 Ujoin:连接形式默认为以索引为键的左连接
/ ?" |$ i8 |# d5 R; q0 @na_rep:缺失值替代符号
7 j* D+ p# x7 X+ ]. W& Is = pd.Series([['a','b'], [1, 'a'], [['a', 'b'], 'c']])/ B8 K, \8 I( ?1 M
s.str.join('-')$ E7 L7 h# @8 h- H% V5 o6 \
Out[47]: & p' R- f$ i% @7 l8 L3 q
0 a-b
' z; k7 H( w3 V/ S: @: X+ A3 v( Z1 NaN
5 S' q9 Z$ Q* o( i2 e5 v9 K- b2 NaN; `: @! b( p6 V" D
dtype: object
0 r4 y3 g# _& t; A. I% P7 d R1
/ l& W) H R) T) y2
$ D: G) ?# l+ M4 Z6 k5 M3
; }6 ?: y1 y& X# f# f5 I41 z2 b! I' F2 F2 A: Q4 @
5
8 g# z& [4 l& @( p( I1 M0 o" w1 f6( ?9 x' T$ [8 E
7
$ U4 R& C2 D! t, N, f$ ~s1 = pd.Series(['a','b'])
3 v9 W* E2 X9 X( e$ Fs2 = pd.Series(['cat','dog'])
, S0 d! m8 t$ Q. L: ds1.str.cat(s2,sep='-')
! F+ c" O6 \. Q! p z+ K: [+ hOut[50]: & D, S4 Z, k# h: @9 S7 I5 _7 s
0 a-cat
5 d1 ^: l2 W5 K$ a3 c4 a" q1 b-dog
- G- S$ J1 m; u2 i$ u- Xdtype: object
# }* O! b* h& j1 k% a; h3 H2 V- S) s4 x" k8 _. E; P" Q
s2.index = [1, 2]
# _0 m' i+ R6 z0 `2 T Cs1.str.cat(s2, sep='-', na_rep='?', join='outer')( }' Z$ w, o- v, `: y
Out[52]: 7 ?$ B$ v0 ~( C) ~$ S) S
0 a-?! A8 J& l8 ^$ F
1 b-cat; c- F; N; l8 F. G7 I. S c3 Y
2 ?-dog
0 f/ F/ W+ S, p( b1 U5 Ldtype: object# O2 e8 a. b1 h; X
1+ i# [7 ]9 [3 \* W7 ? a' l2 G7 l
27 Q+ W0 {* ^, K' v5 F; T
3) e3 f1 x' c' c+ x( }8 [. N% D1 S0 n
4
+ r# `1 e2 p& K$ [, y57 f" }* H* D" H' D- q8 B& T0 Z
6# s6 ]" c9 h( X
7
9 ^9 N" K0 `; R: d6 r5 p8
# L8 Z8 R9 i2 Q9+ O' B) h# ~! e5 ?+ \. [- b) Z
10
9 \" U+ Z5 K$ a; V( }11' I+ l4 @' P! V
12
7 Z# R" x" n, p2 F7 _" _8 L, j: L W13+ w' R; Z6 M( T: T. q) C+ I
14, t; f& o+ K7 k
15
# q' [2 b1 q+ v) a! ]+ ~% l- _) u- k8.3.3 匹配/ C. `" S ^2 g: V" o1 l8 U( a
str.contains返回了每个字符串是否包含正则模式的布尔序列:2 e7 J1 b v4 h c' f
s = pd.Series(['my cat', 'he is fat', 'railway station']), W4 V! y% L/ ^5 W7 v$ w9 @. j) x. I. z
s.str.contains('\s\wat')
, X6 a" P. B; Q; S6 i, p8 W
0 C" G+ e# f* l% @9 E0 True \1 @$ h$ W( e# X2 W6 s P6 u
1 True, K1 t# L9 ]! P5 n# z& F& w
2 False
" G3 E) r& x2 ]. edtype: bool$ U$ r! M* m4 Z! O! t; ~
1
; x1 Z7 Z+ _% M8 l2; ?! ]* X+ V7 e4 y0 [& n3 v
3
$ S$ l; W! h* @% w+ K- F( p4
) M! g& S- G. G8 K% I8 C5' N9 V: A5 k. W! a& ~
6
' F! `4 x1 Y$ Q6 o! W7 C* R' O, W7 t/ i V2 O, f! _" O \2 B5 Y4 y
str.startswith和str.endswith返回了每个字符串以给定模式为开始和结束的布尔序列,它们都不支持正则表达式:
# P- b* D D0 S7 F, A; Cs.str.startswith('my')
: J N4 h) ] b" Y' f- _1 M2 {$ x" h+ \0 n0 l5 G$ s- o' V
0 True# w3 O4 a6 j& E, z
1 False+ h: }/ d3 v1 N" h$ e- f+ k3 F
2 False9 G/ X2 z, h+ i$ ^6 L% E
dtype: bool
. w x7 n9 m$ ~4 X: z3 u8 ^1 Q1
1 X7 k, k9 Y0 f" x* n1 k2
8 q; O, l: J# u0 b3
! P) D4 s+ R1 I2 I" z* H" X4: \/ Q# m: p. v* v; V: s
5
4 r, Z$ w! d4 H) g. p8 M, z62 ], `! v; N8 a/ ~* t$ G8 ^9 c
s.str.endswith('t')1 K% w4 h9 x* g* ?
: o% g+ d _* ~7 o/ ~/ s0 True
/ [3 l6 U+ q Q9 g4 V1 True! ^$ Z5 \; y' N9 C# x
2 False) y8 l; P9 f T# q5 W$ ?1 b% m- U
dtype: bool& z7 u1 r$ t( f; s# I: b
13 s4 ~& A" Q) U& n
24 m) D; K, f% Y
3# C' D1 N% \/ `1 N
48 ]; i0 _/ t" \7 f5 {7 B& K
5. N* @; Y4 Z, r1 b: Y$ ^: \
6. n/ Z" M$ b2 l/ g0 ]/ `2 _9 |2 H& ]
str.match可以用正则表达式来检测开始或结束字符串的模式,其返回了每个字符串起始处是否符合给定正则模式的布尔序列。当然,这些也能通过在str.contains的正则中使用^和$来实现。(貌似没有python里的search方法)* Y8 U; ?3 z2 {3 A
s.str.match('m|h')2 \7 i' f7 q: T% e/ k/ o* \& M
s.str.contains('^[m|h]') # 二者等价
$ i: L* q/ j6 W8 r' S- y! g. ^; c4 c9 J$ U2 O
0 True
; t$ } ]* A4 l- b" p3 C% {( [1 True
. p+ K. J0 R1 M% @9 j! w2 False
* {/ I% }' Z6 Tdtype: bool$ t3 h0 U4 N% i0 Q
1
4 c( z7 y q8 v' V2
: a- K' O( |" Z# O. U3
, T% T! n! _$ h/ i: j _ c4
7 P( U9 [+ C. M2 j5
# |: s9 O+ H% N& K, E, x5 E/ O6
& c6 T0 S' C% Q7
5 J$ K. `0 h Z$ `9 D; R2 Ws.str[::-1].str.match('ta[f|g]|n') # 反转后匹配( O; _1 f# I0 N7 R- C. k; j7 t! m2 ^2 f
s.str.contains('[f|g]at|n$') # 二者等价; q' Y' G. f5 C, J
- ]' V/ P! b8 f7 T0 False
y$ }2 H7 L0 @" ~* V. {1 True
* d$ o$ ~, M: W( |$ p6 l2 True: o6 L3 W9 s5 A
dtype: bool
: n# i- @& d9 X# v2 s( |! W1 Y. S9 D: w0 F$ ~/ @) G/ P2 f
2
8 s: f2 J( Q1 B0 ], H( M3
7 k! [/ O( R2 ]9 E! ^- z4
- X9 U( z, K: o% u8 w5
2 n( W# r: B& o% w5 @6+ {7 {0 l2 S# R5 m ?( q5 h
7
$ o/ T9 ]9 X" {8 \' o* u& m Wstr.find与str.rfind返回索引的匹配函数,其分别返回从左到右和从右到左第一次匹配的位置的索引,未找到则返回-1。需要注意的是这两个函数不支持正则匹配,只能用于字符子串的匹配:/ R# G. I8 @. u7 w* }
s = pd.Series(['This is an apple. That is not an apple.'])
! h- Q% O4 @- X
/ T. m% _. s$ D: E0 ^) Rs.str.find('apple')
/ D; J9 K. @, eOut[62]: 3 p9 z" ~4 D' ^3 l
0 11! D. A3 z6 R: @
dtype: int64
: I* n9 ]9 k7 u7 z6 J x z; y
0 U+ J1 ]; [2 S" K1 S$ v3 ms.str.rfind('apple'); W0 p7 c9 K1 ^- B( H
Out[63]: ' {; I# ?, C. ^' p. ^
0 33, t0 X+ F- R" ^% C
dtype: int64# w, }/ G5 }4 o& d; `3 u
1
$ e* L" t& }( O3 t) z: ^2
5 B& C: Y, k8 ^+ l b0 t: P0 n3
" B9 K6 I/ z6 y# |2 g4
2 n0 ^# ~4 o5 {2 G& N. T) L) r5
; y7 V3 h$ d+ M6' X- o( K% d, P
7
" U# S3 v7 h A W: D$ `- |* l8
8 k8 X) u6 s# l9
8 h2 x" j, f5 z; ]( d1 A105 M) A6 |1 C1 Q6 x
11
2 |# j- b$ ?6 z6 d0 A: A: Q- ]% g替换
- p; m# ], I6 c: Astr.replace和replace并不是一个函数,在使用字符串替换时应当使用前者。* c* n. |' g9 C" t1 j
s = pd.Series(['a_1_b','c_?'])
1 m8 e/ A; ^) k) |1 ?# regex默认为True,表示是正则模式,否则第一个参数内容表示是单纯的字符串,也就是匹配字符串\d|\?
i x @ z& O8 Os.str.replace('\d|\?', 'new', regex=True) 0 a6 h' y/ ^# H- @: L7 B
3 R: O4 v; N; X) w/ d s0 a_new_b; P. s/ z. O4 K$ u# h
1 c_new4 e3 m& b. i6 o& M
dtype: object
) N: e% C' H' q1
: a; d$ v/ a4 u, Z" M2
) h( j: u; c" U+ J1 o, Z39 [: {/ q* x9 H/ X P
47 i, {$ ]' G- [ K8 G7 O/ r% u& {
5
3 Q# y! v+ Z! r; j! P: c7 v5 @6' {& J' I7 l$ Q' T( n6 G
7( A8 z! w& [ K# S O) ^: r- F
当需要对不同部分进行有差别的替换时,可以利用子组的方法,并且此时可以通过传入自定义的替换函数来分别进行处理,注意group(k)代表匹配到的第k个子组(圆括号之间的内容):
# [9 x4 A! U. A) u1 g/ s7 f& h8 S- K: b7 t! v, \5 F
s = pd.Series(['上海市黄浦区方浜中路249号',
# x* d: @0 U [! v# t3 p( i# \ '上海市宝山区密山路5号',5 z/ Y4 e, }8 s) F A
'北京市昌平区北农路2号'])
* b. u6 s- H8 s# l1 spat = '(\w+市)(\w+区)(\w+路)(\d+号)'9 [8 M6 Y" ?' i6 j1 {
city = {'上海市': 'Shanghai', '北京市': 'Beijing'}
; b2 h& i' C: F7 wdistrict = {'昌平区': 'CP District',
7 w1 w7 n( ~5 t5 ?+ K& a- ~1 w2 C, Z '黄浦区': 'HP District',
/ [- @- |7 F- e( Q- |$ U '宝山区': 'BS District'}
; | v' q [$ o) u6 o% L; |! yroad = {'方浜中路': 'Mid Fangbin Road',
5 I: f6 P$ v% m0 A" m9 z '密山路': 'Mishan Road',
" e; ^! v% F) Q; L8 X, v4 c '北农路': 'Beinong Road'}
. t8 S5 R, n( i6 edef my_func(m):( P9 _5 T9 M: i+ z- B6 o
str_city = city[m.group(1)]# [* t3 V, b* v' Y' z6 i
str_district = district[m.group(2)]. t4 x- L; B7 w: M- f9 M
str_road = road[m.group(3)]
4 u6 I6 ]2 B2 F. s/ K7 k str_no = 'No. ' + m.group(4)[:-1]0 K: e. t0 I, e) M: [5 j
return ' '.join([str_city,) t9 P1 Q* i1 _ }0 ?, a5 z7 x! \/ {
str_district,
9 A9 r# }$ @ T- s/ B4 Q* i4 } str_road,& l. T, C9 y% I% Y; s! E4 a
str_no])
) h% t. u! J7 n% W- Zs.str.replace(pat, my_func, regex=True)( e) ^2 R3 K/ S! ?$ i$ P- c
& K6 v+ G4 c( d% \5 Y
1- E' U3 X/ c1 S* q
2$ M# o6 D3 d1 {) ~
32 n" `; p A! Z* D6 J* T
4
$ {8 e, w4 x/ t; W- @! J5. M6 X& O/ S4 \' l! O) \3 D
6$ I) ^4 H) B5 z: h" |1 M. B
7+ W8 _% i1 @2 X5 v! M! }
8
5 y: k& }! X, e- K2 \9
5 U3 q3 ^2 p: n4 c8 c10
% I- H7 a6 a$ `11
, J/ O6 ~: Q% x5 J9 z12
8 E' N& A4 ]7 ?7 f7 i y13! K" ]2 N- r8 U% J. v+ X4 c, M; ^
149 S9 Q2 _/ S, N6 A( s- l; J4 K
159 N7 g; i1 ~& P4 f: a8 ]5 O
160 a% q1 ?4 a t" h- t$ y: U* n
17& n" ?# m" Q- o9 W, D
18
2 J6 q. y3 d/ k) }6 T19; e+ n8 e+ h& e" q- S- I# i
20: Z# [% e& r9 e! R5 R( @8 s
21
/ o( K1 G6 e+ `0 Shanghai HP District Mid Fangbin Road No. 249
9 Y. O% l; ?$ e- ~8 @1 Shanghai BS District Mishan Road No. 5
% u$ N$ A3 H' g9 F6 p' U2 Beijing CP District Beinong Road No. 2
' g3 R1 |! L ~4 S. zdtype: object
- E3 q9 |' ~' Y `; d11 o+ _- V M* J- u; Z `9 O
2$ R- L! \% a! Q4 g' A R; P" \
3
6 x! Y5 X- M" ~' I1 I4" Q- q' e1 a7 v* n; t& |; K4 Q- Y
这里的数字标识并不直观,可以使用命名子组更加清晰地写出子组代表的含义:
( v- n0 g1 E: C
" S& ^- Y; ?! F. X; g# 将各个子组进行命名9 S7 L9 H, U9 R, P' L/ w
pat = '(?P<市名>\w+市)(?P<区名>\w+区)(?P<路名>\w+路)(?P<编号>\d+号)'
3 r; ~: W4 y4 _# q, ]def my_func(m):
U6 ~6 g& G G" B0 J5 m1 l str_city = city[m.group('市名')]
" X q8 k0 x2 {8 o; N, ? W& [ str_district = district[m.group('区名')]
. c0 f8 x/ s& Q. P7 A* \$ | str_road = road[m.group('路名')]
' s( h# X4 r3 l) n str_no = 'No. ' + m.group('编号')[:-1]0 j& M t1 r% u E, q$ ~4 g# C
return ' '.join([str_city,
/ b/ [ D x" ~- V5 ~$ C str_district,
% P1 ^% S, J8 r0 o2 E" I str_road,
. C& G" S/ _% l# J str_no])
/ b, p9 D, w0 ^% L) X) D% ~2 cs.str.replace(pat, my_func, regex=True)4 X" l$ x N( y+ z: K
1
3 i3 w0 b1 o) J8 R! F& z29 [+ {: h' M" C0 c- n: B% n6 _
3* d6 `* m( w. Q9 n# {" i/ a
4) j! K: y8 k& v' r D# j
5
( A! o$ v: m0 j; _6
% n: I7 q! G: U5 G7
: e8 Y8 g% B: g. f8
8 W% a- W# [( F/ M( E6 N4 B( _9$ }; y! O! j' R% `8 j1 _% H. U
105 Z2 ]2 }* _) A( ~& c7 i) I8 Y
11) d* {. i, V$ s
12
' @+ Y# r3 Y) m) k$ m- }1 W9 Y0 Shanghai HP District Mid Fangbin Road No. 249/ H- Q( X, v& y; h
1 Shanghai BS District Mishan Road No. 51 A( j& I" H4 a) H r& i2 S3 I
2 Beijing CP District Beinong Road No. 2
# ^( p6 w! W( {8 [; ~dtype: object
% I- l% E9 U. j. Z& m+ ]7 @& x1
& z/ O% S4 f' |8 j& {& o9 I" ~2
$ d, S; Y, u# L( u2 e; q+ G30 O1 P) l5 r5 F8 _" h6 i
4- ~) f. l8 |6 e" R& r$ L+ s ^
这里虽然看起来有些繁杂,但是实际数据处理中对应的替换,一般都会通过代码来获取数据从而构造字典映射,在具体写法上会简洁的多。
! x! Z Y, H& l1 @ [) B
1 I8 Z5 e2 E9 T8 F6 f3 a4 [ R8.3.5 提取) m' z% N" o: Y, F4 B2 T' d
str.extract进行提取:提取既可以认为是一种返回具体元素值(而不是布尔值或元素对应的索引位置)的匹配操作,也可以认为是一种特殊的拆分操作。前面提到的str.split例子中会把分隔符去除,这并不是用户想要的效果,这时候就可以用str.extract进行提取:! _9 z3 p p% J% F$ A) D7 t) _
s.str.split('[市区路]'), f0 B# }: j' W
Out[43]: 9 j' y1 r9 }, F9 ?+ O
0 [上海, 黄浦, 方浜中, 249号]
8 H C; Z& h! f) ?6 w; N- w$ c1 [上海, 宝山, 密山, 5号]# b6 b# o6 k. p8 `% r0 \
dtype: object' {! r/ r* ~8 c- ~! T1 z+ Q
+ I& `2 K B! \1 L3 |9 dpat = '(\w+市)(\w+区)(\w+路)(\d+号)'
8 ^& q4 t% x0 r `s.str.extract(pat)
! n4 ?, o% n9 v7 s/ l9 [Out[78]:
; K0 C4 k* k2 K 0 1 2 3+ w; {* i' F/ O
0 上海市 黄浦区 方浜中路 249号
, M4 V6 B) \# H/ M% C; o) C* V1 上海市 宝山区 密山路 5号
, X: o: o) y5 G2 北京市 昌平区 北农路 2号# X" v0 C, p" \1 `& H
1
% O! a% e/ g1 m2
: s5 p, _9 R+ q7 v1 c1 B- S3
: w5 _6 \$ Q B7 x0 J H) B43 W3 m2 ]$ {$ }! _1 d8 _% Y
5
1 x9 W1 \4 `2 K3 @6. X- S) z* T B* q+ D0 B
7, I3 d. V7 [# z8 _# i$ G/ N; a: y
8
3 r7 {5 R4 i S/ w% w9
3 k2 Z2 n# m6 _ s105 k S& C" z3 `; i$ s, c
114 u7 A# X- _- n( n* j: _2 E
12: B1 r- a6 n9 n9 }! e* |
13
; j$ ]! _* ]1 E' g# K通过子组的命名,可以直接对新生成DataFrame的列命名:
# S0 v8 a1 Q* t" |2 p8 E' z; O8 Z# I
pat = '(?P<市名>\w+市)(?P<区名>\w+区)(?P<路名>\w+路)(?P<编号>\d+号)'
5 i$ V# y6 H" w+ D% J* W: i- As.str.extract(pat)
" w+ v5 k( O; C7 l1 ?; C, nOut[79]: ) U1 M w0 W$ M$ v! Q* R W+ `) C
市名 区名 路名 编号
* T9 B2 w8 S; r" o N! ?2 T% M0 上海市 黄浦区 方浜中路 249号
; m- B& G) }2 r- k" |7 D! U1 上海市 宝山区 密山路 5号
; S) M% e" g1 w6 V8 `2 北京市 昌平区 北农路 2号( l6 |! o l% E5 k+ Z9 ~
1! H6 M6 p3 }) a9 {
2
1 S. m; F2 g: n6 G) T, q3
/ f# e* u! B, P, ~' k5 H# x/ b- }4
( G9 L: V8 P* B( T$ J5+ R( H7 v0 O, b1 h0 H2 J" y* M) ]$ Y9 U
6
" Z5 d4 w: j7 R$ @4 w& j, x( y3 |" G7/ N: W* z0 _8 ?
str.extractall:不同于str.extract只匹配一次,它会把所有符合条件的模式全部匹配出来,如果存在多个结果,则以多级索引的方式存储:; n# U' _4 F' z; l9 {2 c: m
s = pd.Series(['A135T15,A26S5','B674S2,B25T6'], index = ['my_A','my_B'])& E" A4 K" j$ X# f7 v1 m
pat = '[A|B](\d+)[T|S](\d+)'4 _$ U1 I% C: O$ {- P S* m
s.str.extractall(pat)
, N& c2 T9 D) M% I5 e. m1 C& aOut[83]:
' Z) U5 X4 Y" q3 a# ~# o 0 1: K4 b7 V% v9 L8 z o# K
match % W k5 p2 z) `3 Z
my_A 0 135 15
8 x1 E K Z- d$ }& s 1 26 5 b6 y& B; l( W$ _, Y4 c2 s
my_B 0 674 2
: v) e6 W! D6 g+ }8 s 1 25 6
$ w3 X& g" P9 d/ q1. p1 _" A+ ^+ v* ~7 S7 X
2 ~! _7 g/ U# M5 ]
3
- e5 d% G3 l6 g4
) W$ {+ U* R0 l5
# P$ z& p3 _7 O0 O: Y N$ w6& Q r" F4 B3 h
7
. O& m8 Q& J! f' q" ?3 {2 J8
# P! l V m \5 l$ x" E" d9" p2 y: i2 o: L3 B
10' R& c' {8 `8 }# A
pat_with_name = '[A|B](?P<name1>\d+)[T|S](?P<name2>\d+)'
+ S, Q' ]# K: \s.str.extractall(pat_with_name)2 ?% A) t" k! b p* o
Out[84]:
" _: O/ L6 F+ s+ }/ j4 c* W! \5 p* J name1 name2
` O* h9 M: i2 ?4 w match K* d! J' j% p6 |
my_A 0 135 15
! a& Y8 x; f, r4 h4 H 1 26 5
2 B4 O7 F8 ^. m' ~7 _6 {8 Hmy_B 0 674 2- c" }, x9 Y, s. I
1 25 6
2 b m- c* z! f+ h9 @4 k% O' I7 u12 m: F; F& w& [
2 F! [: Z3 `% |- i
35 s* o4 A( j) j: |* _$ w s! _3 ?
4
1 t6 E. P W0 l# Q _6 S8 v- `5& n$ Z& w; R0 _$ U. L
67 X! t1 Z, G7 @5 @
7
# v: z1 _' ]$ _2 s8
' P( H0 k% J9 e3 L$ H9 P. A9 ]7 u: m93 J9 M2 ^ j+ w
str.findall:功能类似于str.extractall,区别在于前者把结果存入列表中,而后者处理为多级索引,每个行只对应一组匹配,而不是把所有匹配组合构成列表。
6 D- L" L8 R! Q. B3 X/ b% g% W" ^s.str.findall(pat)
; U! h s: j- p" B12 D1 E3 |1 @) S8 M a
my_A [(135, 15), (26, 5)]
6 ? ]; z. \4 G' ]$ ~0 Lmy_B [(674, 2), (25, 6)]
q; z) w. V+ v: q$ Gdtype: object
/ N- R9 F9 }/ \5 o. F. D1) s: t, f& s8 Q# H, K9 z' y1 x
23 s% [" }, e7 q) M
3
; \) {: A1 G: Z0 a$ J8.4、常用字符串函数
+ H- r Q; ]5 ^1 B% j 除了上述介绍的五类字符串操作有关的函数之外,str对象上还定义了一些实用的其他方法,在此进行介绍。+ ^% t6 z. d, Y$ g. g) @
( P: @0 A* e. h7 b% H6 C
8.4.1 字母型函数6 ~5 h+ q* W! H7 S: K' q$ x
upper, lower, title, capitalize, swapcase这五个函数主要用于字母的大小写转化,从下面的例子中就容易领会其功能:
9 u, C' y3 k, p! m! }
& y, H3 a9 |# W5 J& P8 w; V0 Fs = pd.Series(['lower', 'CAPITALS', 'this is a sentence', 'SwApCaSe'])3 z* _ {+ g% ?
; D! n) |- z, U# j$ ]s.str.upper()
" e/ j. y$ s; tOut[87]:
5 p" p/ n3 ]0 C2 @8 r) C, r0 LOWER% U8 H1 r( h5 R3 l# h: ?+ U
1 CAPITALS/ ^2 A* E5 {3 A R1 V
2 THIS IS A SENTENCE
/ }4 _2 S% ]# i/ C3 SWAPCASE& b6 S- F: e2 P4 f& z+ }# J/ _4 [
dtype: object
% ?. y9 L Y, w; f% m: \. O0 \! O5 {/ A# ~
s.str.lower(): K8 i4 i0 t8 |# E/ \% x8 S
Out[88]: , f, m7 {: `2 I6 c2 r
0 lower" X& j6 L8 |: l. o1 Q; U; t6 s
1 capitals# \9 H/ E5 H, E! g4 ~1 ~" q: @
2 this is a sentence, z3 h' _' `$ L0 {* Z
3 swapcase
|# N. u# I& f' s* I" ^dtype: object2 y4 C6 a* E1 x/ z' k2 d
' Z! u- a" j& @/ X/ t
s.str.title() # 首字母大写1 I: N+ T0 ^$ Y4 a, X
Out[89]:
: \: ^# q/ i5 R0 Lower+ ^" I0 P& r* P9 C' C$ D4 r4 l
1 Capitals
. {" P9 o- a' O) _* b/ y, z8 c2 This Is A Sentence
$ y2 Q Z8 n* T3 Swapcase: k; U& z. v S' @5 y, b1 V
dtype: object
, D7 |8 Y1 j6 }- t
; ~! e" b( _, a$ W8 us.str.capitalize() # 句首大写5 q4 K# I+ |2 D1 g' l9 \: Z$ E
Out[90]:
0 {8 }+ O5 A9 [+ h; ^' ^0 Lower
: |: q. b% h0 V- `) m U1 Capitals/ s9 Y5 k. q% k5 Q' s0 m
2 This is a sentence
, R a; K' O3 n b3 Swapcase
- Z% P* _6 A- [' rdtype: object
# ^& z) H% j7 L) g+ R8 z$ P
; K6 ]7 t! {& Z8 Ws.str.swapcase() # 将大写转换为小写,将小写转换为大写。
" @- k6 a3 [; L9 vOut[91]:
8 \$ W |0 ]- a0 LOWER/ {$ x& B+ V1 {! S
1 capitals
/ V9 e0 r6 j# S; b2 THIS IS A SENTENCE7 a" e: Z. P$ E6 g
3 sWaPcAsE! I" b# {* ~8 v- B4 Y: s
dtype: object/ ^; F! p4 d! a* X& P& Y H3 h% h
- B3 u8 m1 T& J" w- s
s.str.casefold() # 去除字符串中所有大小写区别
& t1 l4 V+ }/ d8 S8 V0 }% a, F/ e% \( X" i( ~! _2 a% R
0 lower, z" \" s$ l7 m0 s/ {
1 capitals
$ _( h5 _3 X- C+ p2 this is a sentence! w+ o# Y% C9 j1 q
3 swapcase+ O, ?* j5 s; l: z/ T5 P2 w
^9 Z, G% R# V9 G! U! [+ l! W) w, n$ T1' i. t8 g$ r6 X* z) ~' ~. G) u3 E
2
1 R0 \ r, K. @9 f/ {* ?& z/ l& @* D3
/ P' s" P# A/ f# P; _+ o( \3 F43 W0 E6 A( W% l* K" V3 N
5# X( w7 M4 K7 ^. o' C- V# v" Q
64 v1 j' ]2 H1 J: M* I4 b
7; s/ x* Y4 _7 d
8
& n/ \ y6 R5 U# W! u9 s5 V9
3 W' X5 ~0 Q/ k10! }; c& m2 `4 o7 N1 H1 V
11* v+ y/ `3 t+ w# U2 g) G3 P4 O
12+ d, S& y5 B! |
136 w. t( ]% H. C! f
14
D4 N. _1 G: O2 u5 X15
' [8 E# N5 U0 f; Y169 X+ B+ T r4 I3 {
174 l5 r: M5 L* Z1 M; o6 y
18
( \: a+ A$ Y+ }' {7 B6 x( o- q19
5 R% ]: x+ R. O* w+ C1 P' O20+ M7 ?# V) {6 s& e, p7 V2 q$ q
21
) ^3 x& `! y& {5 E22. i% w7 o4 }8 f3 \! w7 h
23- d/ X+ p% F. i% w5 F3 N
245 C0 D) q( j; y
256 B ~. b; |* Z: t) n2 m
26# G" Q$ d$ K2 m* \$ c: d
27
4 M2 k$ R; X- x. x$ s( g3 M28
$ ?( T+ }7 {* B29. c8 l {" M) T [: [ t6 ^7 j
307 a4 D! k3 I q% f* [ f; K
31
& i1 s) B/ I' v32" I2 k8 i$ {9 T& d2 w/ n B+ w5 Q
33
0 P; v7 T6 m$ M: b34+ S+ S* G1 v) \) r& d/ Y
35; b/ I/ w g8 U2 o' W
36" z9 v9 Z& V6 b; l
37
U N! s* X' u9 q& S, K% ~38
' `! m# V4 Y9 Y- K7 p- _39+ z( [" T- L l4 O* q
402 V. K0 d! S/ N# t, A; ?
41- H! r2 o5 U/ C
429 O9 l- B, M! G6 j
43: H& e- ~7 A4 t* s. O5 H& s
44* l( `. U2 r/ Z. d, h) m
451 f5 _# f) m' Q, q/ v. I9 _
46
+ ~8 ?: r+ d7 u5 T5 d: R47. w# k5 @/ L/ m$ U1 f3 M. t
48# y4 Z+ H. D, v g9 i. e! g
8.4.2 数值型函数1 q! y! B R* h$ T* h7 L$ I
这里着重需要介绍的是pd.to_numeric方法,它虽然不是str对象上的方法,但是能够对字符格式的数值进行快速转换和筛选。其主要参数包括:; N' i g% Q8 J: A
% Z% `9 }% b* x$ T8 Z2 m8 ~# e
errors:非数值的处理模式。对于不能转换为数值的有三种errors选项:1 z& J3 |" u3 G
raise:直接报错,默认选项# U2 Q9 v; g+ V+ _. f4 n9 E" M
coerce:设为缺失值( ?8 h! g# A8 _5 X
ignore:保持原来的字符串。9 X$ E; b% F2 J% Z' q3 j" n1 f3 d
downcast:转换类型,转成 ‘integer’, ‘signed’, ‘unsigned’, 或 ‘float’的最小dtype。比如可以转成float32就不会转成float64。
, L3 \9 e6 ]$ os = pd.Series(['1', '2.2', '2e', '??', '-2.1', '0']). k5 ~0 t/ h* \. H1 E
5 x' h. |2 I6 \! t$ H7 F3 H
pd.to_numeric(s, errors='ignore')$ q% u* f* m- t5 G
Out[93]:
* u1 n8 l. U0 a1 X4 G8 y: [0 1
8 I* ?) y. M0 N I' W1 2.2
2 Q. r% p& V/ i% B0 \* b1 I. g2 2e
% U) r* z8 t) U+ y' b3 i3 ??5 ^3 S- C9 G/ N- c$ G
4 -2.1
6 m; I1 T; U- ?$ P P) Q2 T5 0
; I- n6 A9 j+ rdtype: object
! ?, t% u% f% `6 ]3 D
# j7 H* P5 p- u. [5 \pd.to_numeric(s, errors='coerce')
: D# r) q; d I6 M7 V1 bOut[94]: ' C8 [8 r6 C2 k( B" S. i) D( D
0 1.01 b8 N, F/ P @4 \, c* O
1 2.2
# A% _: \- M8 R, r: F2 NaN. i- }- j! K- p& @, O k# B5 x
3 NaN
% u. t9 i1 K- W; e, M4 -2.1
* y1 u [! ~# S1 @6 v3 I5 0.0
6 {7 h c* c7 M: p0 E3 cdtype: float64
$ U" p8 r/ ]$ N3 v: \5 |. @0 z8 r: i _6 M
1
- }( n: r7 U1 W2
! Q) F8 z( S+ p: Z e( m6 o( n3
$ ?: p6 G( ?- ]3 b2 V/ S4
9 }$ `" L$ b7 A5
$ v0 c2 \- e1 f6
: @/ I+ _, |- ~, f- t) {+ W3 [7
5 ~0 X8 h; X7 K' d' I8) j* ?+ Z+ e* U @% p
9' ?$ A# B! R5 f, y8 W" B3 v
10) j2 \' z# D+ X% M( D+ I' Y
11
# }4 B! B! X% I* j7 q12
1 y* y2 X$ f6 M; j l) n13
! c6 m! ]0 o( \2 w+ L' m9 g7 I+ m148 {* F. J8 ]/ d$ T& L
15
$ g7 X, @! I) B0 G$ H16
: f p6 T5 b2 V1 x1 s& ^17
' \( g2 L9 A/ a4 Z( i18
/ q6 |& t- p. n1 b3 Z: t19# c6 y4 x1 f! x4 t3 E+ h6 ~
209 O6 Q% F# q! L3 H/ t
21
7 N+ D, X! \8 E2 N. A 在数据清洗时,可以利用coerce的设定,快速查看非数值型的行:8 f- J- E- V- F" }% Y4 J' d: _3 F
6 T# ]' l# w/ l. M+ js[pd.to_numeric(s, errors='coerce').isna()]' h$ P+ X! P2 n! q' A \8 r# b* r
Out[95]: & k+ D- }2 t6 C; k# L
2 2e
" w' [0 f2 `$ z( l" U: F3 J& Y3 ??
* Q/ L5 c2 L- }0 R. u6 Q# Jdtype: object
+ t7 A* V! s0 g/ G; `1
% \* p/ i( e- u! ]/ ~7 J9 k2( ^3 H" O z+ A% \* E$ v3 @
3$ _4 F) o; `, t. _. }
4. w, W$ ?+ C& y- x
5( a4 O4 e0 W, X& ^% [
8.4.3 统计型函数
" d1 l! c# p, V count和len的作用分别是返回出现正则模式的次数和字符串的长度:
W/ N/ d& ?. w
& f. W/ j) o% Gs = pd.Series(['cat rat fat at', 'get feed sheet heat'])8 ]. r0 Y/ i1 W# Q$ h
3 M, a% |7 s8 s( w5 c1 I. u* t
s.str.count('[r|f]at|ee') # |左右两种子串都匹配了两次! ]4 u( |% t6 R; ^( ]: k5 T' [
Out[97]:
4 ~6 x0 F/ l/ q. I8 o4 d: m0 2
+ j) P6 V2 ~% c1 2
1 B' l& E9 ?3 Y& o% xdtype: int64' B E7 k1 F. Z( D$ B! s' v# |3 n i
" `1 G- l3 C4 u2 g
s.str.len()$ i0 y# S0 v% [
Out[98]:
! v" J' ?: l- O3 v& Q- P( z. q0 141 N* h: [4 Z0 R/ ]/ h
1 19
& V! l1 @' O' r9 e7 odtype: int646 d/ U" B& [) @ W$ [
1$ i$ ]0 C+ _4 a
2
. h" m( }! h* ^3
: d' L/ t# }8 a4! o8 _* ^8 X( o) Q, k
5! R# R4 x7 H6 d, Z
6
+ ^1 P7 w3 M% F7 F9 [" H! U5 e
8. n: U, l- q2 ^/ ~
9
. e9 r' e3 Z! l/ A1 H- l107 H2 w6 ?. }; D4 L
11
" o4 T' |% `! ?1 g12! c \% ]7 f4 P5 h% e6 y4 v
13
( v2 O4 \* Y7 s( f8 w8.4.4 格式型函数
5 S$ l5 |5 g0 Y3 Y4 a9 H 格式型函数主要分为两类,第一种是除空型,第二种是填充型。其中,第一类函数一共有三种,它们分别是strip, rstrip, lstrip,分别代表去除两侧空格、右侧空格和左侧空格。这些函数在数据清洗时是有用的,特别是列名含有非法空格的时候。, Q6 D7 u( L: f( @- A
4 Z6 r' r, C- _4 v
my_index = pd.Index([' col1', 'col2 ', ' col3 '])8 i1 |! k9 o! P# u+ u& c h9 `" ~
! G4 K9 Z" h$ q( k
my_index.str.strip().str.len(). {! o; Q. h6 p8 i( i, m
Out[100]: Int64Index([4, 4, 4], dtype='int64')' ?2 [- o2 [' @
* f/ v3 M y5 v! q" f
my_index.str.rstrip().str.len()
9 @3 Q% o: T9 b$ ?Out[101]: Int64Index([5, 4, 5], dtype='int64')$ b% q2 A" O( @3 z* B& U
& j# k8 H( ~- q% B- Q2 i
my_index.str.lstrip().str.len()
# @$ ]" P5 R' A7 C8 iOut[102]: Int64Index([4, 5, 5], dtype='int64')
/ z: ?- G% \' V6 h$ T6 ?1
1 i/ s. ~ U2 b3 Z. X2 o2 _2 P" g2
) m$ R1 E3 U8 A8 `! q, Y3
' w5 x% A: s# V4 y1 R# ? k8 k$ f4$ Q1 X1 I; `" l$ f
5* D I& e, m3 v# S0 o1 ^ `
61 \. Q, [2 ]- U: y( p% \
7( X; {8 T) i% G# j
8
w: |* c) R M; b. Y99 l7 ]% w w1 O8 L% Z" S4 X/ k
10
) t& C3 W7 g: p, d 对于填充型函数而言,pad是最灵活的,它可以选定字符串长度、填充的方向和填充内容:
! Q/ f4 K, _" s, _ I% W$ X1 N, ]9 }' j6 O
s = pd.Series(['a','b','c']): \% p3 I( @; k$ p$ J& T0 V+ H
V8 v* V2 q- Q: l4 c9 I" b
s.str.pad(5,'left','*')
3 \+ y/ T+ q. \( R9 c: zOut[104]:
9 Q' H! }1 X. S' Y" H: f: s0 ****a$ M( J2 z( x* r2 X& \/ e$ e* z
1 ****b9 W: N7 W9 v* V# }8 |' n
2 ****c, D2 h# B( v: e: B0 e! o
dtype: object
1 P; s. f& t7 _5 W* B1 k# c, B g3 a1 Y) g' K
s.str.pad(5,'right','*'); o4 W5 h8 G, Y5 D
Out[105]: 5 s) \: f0 C& a) E" h
0 a****+ ?% E. v. o/ _
1 b****
* p" G- O- a! K' X7 l2 c****
# c* C! x& V X+ Z6 A6 C2 edtype: object. Z( {' a4 a. L' A+ u1 R* \2 Z5 ]
6 h. G( s0 k$ b2 i
s.str.pad(5,'both','*'); w8 D# t; \% f- {
Out[106]: 0 m+ h: B' A9 G) `) I
0 **a**; J- }2 @6 F( X
1 **b**
8 M0 r+ }; @& s% w5 e2 **c**
4 T! k1 M( t. m# a- Cdtype: object
e3 ]) O" l& }' v7 G8 D. s9 T C# {2 E- j: Z8 p$ ^
17 B& c/ U: a( e/ _2 H' ]1 W; K
2- I4 V3 V/ O7 g, T
3" |+ W% |9 h, C1 m
4
c4 w4 b* Y& T( q2 X4 o2 a9 t2 L56 d3 ]4 s6 n2 Q1 d3 h: J( V+ v8 A
6; m/ n$ H- p. n
78 r# R! I3 s; f
8
$ j6 t: r' t, v# w4 y. g( O5 q- v, {9; X+ }. K2 t' [2 x& O, L
10
' ?& P/ K: _: f1 x1 C2 t" C119 n, D, }" j, H( v7 @
12
" J2 D+ s) Q, g* i13# Z+ ]1 C. @6 ]/ y; {
149 E" `, G; [% R7 y4 J6 W
15
/ [+ V7 A# \3 ]% g9 R" p16
( i$ r7 h/ p% K5 V17. b% z; O- Y8 B4 q7 B
185 r$ K0 q/ u+ E" |
19
1 F; Q- {; b! c' `8 a209 t% N+ i9 X3 k
21
3 `# D9 `: U* w4 b- R1 o; I5 L229 K+ {( @: G H7 R( T4 o( j# j
上述的三种情况可以分别用rjust, ljust, center来等效完成,需要注意ljust是指右侧填充而不是左侧填充:
5 T1 H$ g8 r* n; ?5 B: j+ T+ v ^
s.str.rjust(5, '*')! V8 S3 l( K- t, E X. X5 i
Out[107]: 7 P' l2 \9 j9 ^1 F9 K) L
0 ****a
) Y! i% {) N3 H) y$ g1 ****b
" U& O4 E9 l$ U7 ?2 ****c
) D) z3 F% j% L2 m3 ?: u; ]) G2 udtype: object
4 w4 s# W& A; t; B
; _# C- k& E- ^: X3 \/ L* T! ]0 ?s.str.ljust(5, '*')) Y; {/ } u/ W$ Y' L
Out[108]: 3 m9 }. k \ P% o6 L/ j9 ^0 K
0 a****
4 \1 d0 {3 B" c+ J T1 b****$ J- P8 \4 r! H4 P3 _! @7 F2 E9 P
2 c****( {- A1 \9 n4 I0 u
dtype: object) X1 N* v. M! L4 q3 ~
# w4 y* w, W. V$ x
s.str.center(5, '*')
6 `& k7 P. m1 u" B& IOut[109]:
9 j t( ~* C- y6 c0 **a**7 k5 R8 p V) q7 @. R
1 **b**
9 L/ {( B+ s$ p2 E, x6 C# S" r: t2 **c**% V- v1 d/ M9 O4 I5 F
dtype: object
% I% o6 J, p6 a# b, ^
$ c M# H% B3 C; b1; _$ x) s* ^5 t2 `1 i: p7 Z
25 }. o+ G9 m. @6 W8 `# x* N7 u! Q4 g
3
9 S$ l+ b7 x0 Y4 Z9 E4& T8 X* d* M2 C" d" V
5
% ?9 X1 k, Q. g/ |; t" \6% N& Y7 U8 A5 e$ Y/ h$ M+ Q- j
7$ l& Z; Z% ?8 r, J5 P. b1 u
86 E" K* Y/ y7 {; k, w
9$ ?/ n A. k, T2 e8 J: O5 {1 S
10
- B q) d! u7 |# \* l6 J11
' k& z1 H: S- C& m/ x12/ p6 y5 R7 G& H/ a" q
13
1 }$ ^% D2 _$ }* Y% o14
: G) S" Y3 D2 u* I; w8 c0 p. _" L15
* e" Z* r# N; L167 Q9 p# a/ \0 O# q+ T
17& H* O0 P' M3 C) n* G4 V3 _, d
18$ j$ l5 X3 H8 @
19
B1 b/ r) s3 i* c& A' Y20
' p Z5 V. `" v" D9 z 在读取excel文件时,经常会出现数字前补0的需求,例如证券代码读入的时候会把"000007"作为数值7来处理,pandas中除了可以使用上面的左侧填充函数进行操作之外,还可用zfill来实现。
% z" R3 @" J) l- ]% @( C
; v4 i& H3 z. w# s- ps = pd.Series([7, 155, 303000]).astype('string')7 q5 B6 I4 r3 ^
% g/ J" `9 Y0 o9 D% Ts.str.pad(6,'left','0')* X: e/ a" }9 C+ a2 w7 B* ?( \
Out[111]: 6 z) U5 D' D1 x+ l2 n3 I: d6 |
0 0000079 v4 W2 W% q, Z9 n: v: ^& F
1 000155
0 b& \4 R1 H+ D2 303000
3 t, }% O3 T% x: T7 v4 hdtype: string: h" N5 X/ V$ D$ R
V8 W& a6 D* t) P& Ls.str.rjust(6,'0')5 q7 z/ _8 w4 B( z( m- M f
Out[112]:
- y2 P# G" p+ `, M2 g3 d0 0000079 T' F3 Z7 r3 X1 q
1 000155. m5 a; y* w( _+ S# _
2 303000
. S5 N* U% D& h: E: udtype: string8 u+ H2 T. j; b- R4 D- E
) \! N7 v+ h8 @5 y9 J0 x; N2 f
s.str.zfill(6)
5 T9 T' G) O& y$ o! s% h3 P1 [( xOut[113]:
) a$ F* p/ G) `2 L/ N0 0000078 U' r) u" l( {
1 000155
' Y# A) w7 x; [6 K7 m( u" [2 303000
2 e& | t6 {9 a" r7 }* Cdtype: string, M# L: G6 |9 c9 ^6 O) ~4 q
, r9 s+ E* W$ M$ j" k( z
1
; q: C% \4 |* A- y; h. Z5 x/ b; D2, b2 N9 W. f, Q% F* a K
3
! @& a0 \$ S) U1 z4: Y! J' s$ t" _0 `7 i0 ]
50 i- d+ b; A, p' I* c! @3 T
6# J4 r6 g8 T' l
7
: a2 c, r: R( D9 O6 N! {86 I( p* V2 I; w/ J
9
) O) q+ t8 m. O( v( A$ z10
# p4 j- J# D' s11 P- S4 e" Q1 b( {( S% r* g
12
: s% \$ _. v% t V# S* d13+ n8 ^) U3 S" u
14
3 i" B6 @7 I5 b" w' Y% _# g15
5 f% l+ F, y6 c7 w9 q4 U- G# Y& H+ D$ s16
4 t" c' l u/ l: y/ V17* t5 d. Z# q0 v8 e' b# ]) X2 h) Z
189 M. Z$ T0 X# W" U( ~+ o
19
% {0 r* H7 Y' P# @ j20
" p' `6 ?5 y+ |, F21
: K. f' d: ]& F( c: A22" A% ~, C5 t6 |* ]( I/ T+ x
8.5 练习1 N1 m* B+ Z$ r
Ex1:房屋信息数据集
9 x1 p2 A" W Q7 M现有一份房屋信息数据集如下:8 S5 R6 @7 L6 s u$ k) Y# I8 {. G
) h: D+ e+ @6 \" h( Jdf = pd.read_excel('../data/house_info.xls', usecols=['floor','year','area','price'])
! f. Y) |6 t( U4 L- ndf.head(3)( x( {3 M |3 G6 l) P
Out[115]:
$ O; `. D7 l# O$ M5 V; \) t floor year area price1 X- R0 y4 P: y" T+ G! B+ u9 R- U
0 高层(共6层) 1986年建 58.23㎡ 155万
/ i8 R" N2 p$ Q" N1 中层(共20层) 2020年建 88㎡ 155万2 M/ X7 o( C0 U& e% ?0 E7 J
2 低层(共28层) 2010年建 89.33㎡ 365万! ~! F2 ]* e# U* B# P
1
0 y+ p: Z" N* p2
3 y4 D3 B+ R% I3/ q8 J2 h5 y; ?* A
4/ c t2 F# N. }4 S6 b- e
5* i, r6 @ C# c& _' ~; c9 O: P p
67 @9 e3 L' L1 D
7
1 m( Q; ]" T; T7 j8 ?. o将year列改为整数年份存储。
: W0 O% n4 C$ `3 k+ K) t0 b$ ]* v' F将floor列替换为Level, Highest两列,其中的元素分别为string类型的层类别(高层、中层、低层)与整数类型的最高层数。
0 h8 ~" }2 I+ f) v9 \6 c计算房屋每平米的均价avg_price,以***元/平米的格式存储到表中,其中***为整数
: q) n( G) D% L6 T" P0 B3 w6 i将year列改为整数年份存储。( Y/ l# E* n1 [& q, K3 G
"""1 _+ z. J, ^) Q9 [
整个序列需要先转成Nullable类型的String类型,取出年份,再将年份转为Int64类型。
6 C6 [- ~! x8 p3 }4 F注意,转换的类型是Int64不是int,否则报错。即使astype加参数errors='ignore'跳过缺失值,
0 ]& i& I) U4 @转成int后,序列还有缺失值所以,还是变成了object。. `$ i; \- ?7 q* c" Z. N/ x5 b% ~" M
而整个序列转为Int,就还是Int类型,缺失值变成了 pd.NA 。% D: i, ~# S j8 `9 ]
"""2 m+ T1 D4 n9 d+ a
df = df.convert_dtypes()
& b& k4 n. |; Q% I; v8 |. Sdf['year']=df['year'].str.replace('\D','',regex=True).astype('Int64')
+ h. J. L( o3 V) Z# D) _" ldf.loc[df.year.notna()]['year'].head(), U% {, R: ]. ^9 p; d
, s6 w0 B) x# w' R
0 1986
: [# ^% L0 `7 x% G% Y1 2020
7 \% a7 M9 ^ j& c9 f# V3 U$ `2 20107 `) I" T: e9 }; t
3 2014
3 ?& J% U) Y- J4 2015
& x' ~. }2 V: R1 z5 ^Name: year, Length: 12850, dtype: Int64
+ i# ^5 Q* g2 w* s
9 q/ d+ u0 e" K1+ @" y% N& z6 v. @
2
3 |# V, S$ S) |5 a+ Z3* r( H4 u( v% \, U' a
4% v# P3 L, ~& s5 M' d# g$ K% ], H
5
* K" a6 c" A# v) z4 O* q; G6
% a$ P, ?( ?7 L6 Y" f6 G7' `7 P+ c3 ]$ R! l1 `) L" j, m* ?2 X
8. [ J+ n8 q: A! _4 s
9
( O; Y: w" B. X5 k2 h0 i+ j3 s' o100 {! d7 N+ _. C( x0 h
111 \; \; @& A- |3 A
12
$ @* ^6 ]1 ]3 ?13
! a) ]8 ^/ j0 E+ {14
3 N: Z; m& C" v% I8 V3 }+ t. ^( a15
6 A" p) [( r% ]16
' h- L i9 b& m参考答案:
1 S' m6 l. ~" O
, [4 o8 h$ J7 V1 C3 [% i不知道为啥pd.to_numeric(df.year.str[:-2],downcast="integer")类型为float32,不应该是整型么4 W, }6 b( `6 s2 }
* i( S: C2 C+ U1 t
df.year = pd.to_numeric(df.year.str[:-2]).astype('Int64')
2 t' y# I6 }1 F( W+ g5 Idf.loc[df.year.notna()]['year']" e& O1 G2 \# M8 b; M3 _/ Z
1
$ m! {) e9 m" l. I24 t8 F, s9 k- Z2 K6 R* q
将floor列替换为Level, Highest两列,其中的元素分别为string类型的层类别(高层、中层、低层)与整数类型的最高层数。& l6 S; i% K1 ?7 R4 t9 Z6 U
pat = '(?P<Level>\w+层)(?P<Highest>\(\w+层)'! i f/ S! y; W3 U, X
df2=df['floor'].str.extract(pat) # 拆分成两列,第二列还是(共6层得形式,所以还的替换一次
$ |8 z J/ W. l) @1 e6 Qdf=pd.concat([df,df2],axis=1).convert_dtypes() # 新增列拼接在后面,再次转为Nullable类型
+ j3 z% J/ z8 O' Z8 q {: `2 z5 Gdf['Highest']=df['Highest'].str.replace('\D+','',regex=True).astype('Int64')
& p$ K: w7 Y. V$ }3 kdf=df[['Level','Highest','year','area','price']]
* _' \8 ]4 ]) Y0 j4 j: k% @df.head()6 ~+ j9 p/ E4 x$ I( f W( O
% T) d2 W1 L9 J6 r
Level Highest year area price
' A5 }# D' D/ B6 \* t4 |" D0 高层 6 1986 58.23㎡ 155万1 `9 ? D) W) s8 o
1 中层 20 2020 88㎡ 155万; Q, |3 M0 x6 T l4 e
2 低层 28 2010 89.33㎡ 365万
; g% R4 I0 H+ A8 ]0 q3 D5 G: ^4 n7 r3 低层 20 2014 82㎡ 308万8 O/ f. s" b! I) X6 ^$ i2 T& J
4 高层 1 2015 98㎡ 117万
( |$ }: s. D. U1
& j4 l3 a3 e3 i- S2 I2, M: \4 X" s; k% G
3
# f. X% l2 T6 s! a2 U4
3 ]0 u8 i" ~" S) y5) f" w- E, F/ h
6
6 X8 r; G5 H! ~9 o4 E2 @* s; ~7, d7 }& z5 F. \# t% J" C
8" y* V# Z9 X x
9
" a& [; s5 y6 l& l3 A2 E10
$ H$ Y* C0 p/ ^112 J: P4 K6 V! E' F W/ a# T2 f/ W& r
12& ]: _4 w+ J: D/ d% X
13
9 i4 [, o! s/ k9 b# 参考答案。感觉是第二个字段加了中文的()可以准备匹配出数字,但是不好直接命令子组了
: ^1 a8 p4 ^# ]* J* fpat = '(\w层)(共(\d+)层)'
% `9 w9 c6 s6 d) J4 Dnew_cols = df.floor.str.extract(pat).rename(
4 R0 B4 b7 X, l; M% u" ^) v k; o columns={0:'Level', 1:'Highest'})) M- h8 P" o; @4 p. ^9 \- u
0 ]. F: a2 L/ _9 w. X9 Bdf = pd.concat([df.drop(columns=['floor']), new_cols], 1); u% W4 m' H: S! V" Y* Z3 d
df.head(3); @' @/ W' n! z9 Z; @" ^- M; C
! Y+ W) i2 r- F0 x( j* }& @Out[163]: t! t# R" D( h' a) y1 @( h* n) ^0 z3 i
year area price Level Highest
3 y) C( l$ J( t* x0 1986 58.23㎡ 155万 高层 65 J1 n2 U$ a7 W3 F, ^3 N
1 2020 88㎡ 155万 中层 20
8 F! s8 w4 p! W2 2010 89.33㎡ 365万 低层 284 r+ E5 C* l) X+ g, l# v
1
3 \# j- T0 q2 ~- y% j, x0 c, o2
1 K; f% z6 R: R, \& L3
" l* V- B; F9 }$ t) E44 t% a0 y' w* b; i' k* P
5
( b5 c9 E2 R; Y# J* C. i( p3 p6- l# A: ?* J( z( Q! l0 y
7
0 F: K; n v6 B. G8 ]9 p8; J8 H) ?- l. r/ d
9
4 t" M. g% \1 g9 _" F3 d, V10
( ~+ ~- W6 U9 z0 k( u1 p- C11
6 u ^' y; k" q N12
, I) M E, k/ m5 q13( M# j9 @: }6 b: q9 }1 d
计算房屋每平米的均价avg_price,以***元/平米的格式存储到表中,其中***为整数。' J: d5 R; C, e* N- B9 N
"""' @5 Z! ?3 `& Y1 m7 W
str.findall返回的结果都是列表,只能用apply取值去掉列表形式% y+ M4 a3 [, F2 @
参考答案用pd.to_numeric(df.area.str[:-1])更简洁) ?7 G+ l: l' b- V
由于area和price都没有缺失值,所以可以直接转类型$ Y3 D% T; Z6 x4 E4 h' O
""", K8 k0 a" J' [) _; Y) k$ z- G# K6 Z" L
df['new_area']=df['area'].str.findall(r'\d+.\d+|\d+').apply(lambda x:float(x[0]))2 |) E0 N" y9 u- d) P5 J
df['new_price']=df['price'].str.replace('\D+','',regex=True).astype('int64')* U- r3 N3 _! o8 q p
df.eval('avg_price=10000*new_price/new_area',inplace=True)
4 B# R Z! }# {" F+ c" o# 最后均价这一列小数转整型直接用.astype('int')就行,我还准备.apply(lambda x:int(round(x,0)))
7 ?# K! t2 Y8 ~: C2 r0 q# 最后数字+元/平米写法更简单3 {2 {4 x, _8 j4 i! k
df['avg_price']=df['avg_price'].astype('int').astype('string')+'元/平米'
( J8 f# u" b7 J2 ~del df['new_area'],df['new_price']0 S( D% g0 [& h( u% o
df.head()) D8 l0 |: R( ~8 f
0 J* Z9 C% k8 s5 I4 e+ n
Level Highest year area price avg_price
- z, D) p% M* U! E6 ]0 高层 6 1986 58.23㎡ 155万 26618元/平米! Q5 T! u; D4 V+ R0 g/ v
1 中层 20 2020 88㎡ 155万 17613元/平米
$ _7 I5 Q' n" m0 u) e2 低层 28 2010 89.33㎡ 365万 40859元/平米
3 S% K1 L" g- L9 t* c- T3 低层 20 2014 82㎡ 308万 37560元/平米3 z) P% T! D8 a7 z$ |
4 高层 1 2015 98㎡ 117万 11938元/平米' q; F' z9 t' a/ E2 v
_2 L$ C; t" z# }& ?7 x1( J7 c' {) O9 l8 x- s
2
|$ i3 n! s! l3
7 M. Q; g+ a* x4( ~ e7 [" p$ X% B& i/ o9 i
5
0 {( e" O, u; ^6
. M+ i8 V9 C2 z7 }% F1 s3 V8 T7. m" }1 {" F: b4 o
8. W4 f1 H1 ~" S' F v
9
! m; j8 w2 d- K10. h; J. o1 c$ R$ t/ a
112 f9 T4 J, j3 q0 s: W
126 P- @' t9 w8 j0 |- y% m$ y
139 _: Q. u+ m: f, X0 w
14/ g- ]$ t8 X" }1 d# P
15/ ^4 ~2 @5 R0 L+ c, L
16
4 a* \0 k6 q/ b8 q7 W& x17
6 S; d6 V& I- s# V: U. O8 M8 \187 D) V; D, x- F ^) p w6 f, Q
19
' C1 |) d% _) ?6 k J2 h* R2 n20& H4 P% {" b4 Z* }4 |# b0 d
# 参考答案
9 V7 N. o' [8 x' q+ U7 ? C$ gs_area = pd.to_numeric(df.area.str[:-1])
8 D2 }# P" L8 Ls_price = pd.to_numeric(df.price.str[:-1])9 m: t6 m% V. D* A" ^6 L$ [% l3 `& S
df['avg_price'] = ((s_price/s_area)*10000).astype(: a% z9 l* Z. J6 x% o: w6 M* D8 f% e
'int').astype('string') + '元/平米'
, Y& v) n$ z% a. s! g, T1 r3 e ~3 j8 P, u8 p6 H0 c
df.head(3): n! C( p# V7 q8 r
Out[167]: 2 j+ `. t* B3 |& P+ r1 Y- ~
year area price Level Highest avg_price) I/ V# W# k, I* o
0 1986 58.23㎡ 155万 高层 6 26618元/平米- ]0 y7 X& _( p( l/ r
1 2020 88㎡ 155万 中层 20 17613元/平米9 O+ F6 Q- P$ c0 t/ \
2 2010 89.33㎡ 365万 低层 28 40859元/平米3 j, e+ i6 v- F
1
- x7 o& z& f% V1 {- _+ O/ f22 d v6 M1 v5 E! V0 d2 m. }
3/ ^6 i+ }/ r% j, n; k4 R
4
9 m4 }$ f: J4 j% S, V5
8 A( S0 U" \. c, j0 V, T# M8 D68 s6 ^& Z8 t) n7 t# J
7
' `8 a% b' N) ?: u& V$ U4 \( l5 t* n8
" Y& s& y) ~# O* a# B6 a, C9
& O- t+ }1 Q5 W% I$ C5 B10
7 k' }1 M7 |8 ^) _. H2 a11
6 z3 U: N. i3 |& I4 }" I12* W3 j8 z, ]/ F' ?& H/ p( r9 Y
Ex2:《权力的游戏》剧本数据集, g$ q! Z3 x0 V# y
现有一份权力的游戏剧本数据集如下:
% q* Q( ]5 ?+ n3 m9 V5 w" v: k: @9 ]6 ~1 G
df = pd.read_csv('../data/script.csv')1 X3 Z' r0 f$ V; v' r
df.head(3)
4 n% L8 a! |' _$ r9 B0 H4 J
) P7 H6 k" P& \2 u" y. [Out[115]:
& \# P" c9 B6 [! b$ qOut[117]:
; E% _2 T0 Y0 u+ b# o+ B Release Date Season Episode Episode Title Name Sentence
* T5 s- K7 S& h- e0 2011-04-17 Season 1 Episode 1 Winter is Coming waymar royce What do you expect? They're savages. One lot s...
4 T5 h$ m6 ]1 P0 q* a1 2011-04-17 Season 1 Episode 1 Winter is Coming will I've never seen wildlings do a thing like this...
# j) ?$ O! r- H- _) @2 2011-04-17 Season 1 Episode 1 Winter is Coming waymar royce
4 M! k/ p' P2 g3 ^" C0 U1; K2 `" x- `- h4 i; n# I5 B2 F% s
2% M" Y) y7 U. x6 b L
3
" {$ F. q/ u% p8 n0 Z' c/ X1 \4+ m" O# t- O0 X) f6 H% @
5# K7 u( W; W: S ]# v
6
. C6 E1 ?2 v3 H& C4 T1 T X7) M" m2 U( P) r* u
81 b: i; n( {. X; z s z1 s& z
9$ `* M$ E& x# G" [! B
计算每一个Episode的台词条数。 l. @, t, X4 E1 Y2 v& a0 ~( Z8 {
以空格为单词的分割符号,请求出单句台词平均单词量最多的前五个人。
+ w. j. I f- r( j% \; k" a8 |若某人的台词中含有问号,那么下一个说台词的人即为回答者。若上一人台词中含有 𝑛 个问号,则认为回答者回答了 𝑛 个问题,请求出回答最多问题的前五个人。& Q' V# }/ Q3 L0 a+ k* l
计算每一个Episode的台词条数。
) P: `8 D* t' b4 qdf.columns =df.columns.str.strip() # 列名中有空格4 ~, {+ M2 m: J. Y
df.groupby(['Season','Episode'])['Sentence'].count().sort_values(ascending=False).head(): {; L X; Z, Y k9 P! |
# a$ x% t9 j0 zseason Episode 8 Q0 g7 I5 ?/ r1 |1 I; @# ^8 r
Season 7 Episode 5 505/ A/ g+ E- C* {; I: G; C
Season 3 Episode 2 480
$ M9 ~' W: C. G/ T! @5 tSeason 4 Episode 1 475
1 l* a7 z8 V4 a6 P w' c1 CSeason 3 Episode 5 440% ~* t, m( V3 H/ L. @5 `
Season 2 Episode 2 432
, h$ @/ Y( ^9 _1 `5 k+ k1
2 n4 O- m9 o. `2
4 Q& f! o& E7 z3
) N& g' m% w0 [3 n$ S* U4) x: Q3 i% Q+ m
5) D: U9 E/ O4 M% m3 G# p8 x
6
9 B, V/ }( W; j! L' t7+ x5 | v0 J, S3 j1 C
8
2 R5 m" J# g9 S! Z9 L9
. t- d9 Y7 k$ a' m; O' q( g以空格为单词的分割符号,请求出单句台词平均单词量最多的前五个人。
' C. u# w/ l' p4 {& S6 Z# str.count是可以计算每个字符串被正则匹配了多少次,+1就是单词数 t* L7 K) n: D0 N$ X. y
df['len_words']=df['Sentence'].str.count(r' ')+1) t* `3 l6 a' N) L# \& U. w( N. B
df.groupby(['Name'])['len_words'].mean().sort_values(ascending=False).head()
: Z4 h' l2 Y+ Y" i0 C3 G) L5 z4 B% b( U: D( m% ]
Name
1 z% F1 G- b+ w% F# Cmale singer 109.000000( h/ r2 M; d% R4 Y5 x
slave owner 77.000000- T0 M0 w0 s* I
manderly 62.000000
1 C# f, e+ u* ?3 v6 zlollys stokeworth 62.000000
" }9 E% A3 Z9 qdothraki matron 56.666667
% G5 ~- `: |4 n/ B/ J& b1 QName: len_words, dtype: float640 E- K* D. E1 L% J, L' o5 f6 j7 l
1
9 X1 z, }) \: k! G' j26 @& x2 t' G# ^/ w
3- U& D" L( s5 b, R# L, s; F
4
8 n3 F# m5 Y/ L( }5
9 e/ L0 n3 o8 v, `) V; c69 ~9 l3 d" O! _/ i9 f9 [5 }
7: i+ h A* Q$ }; R% F3 J: r+ @% {6 h
8" j( G4 M: I3 U( p2 X3 p" P
9
6 S0 H# h) ~3 b# m% L1 a5 _10
3 [4 n" t5 h7 d11
K( K. u! r: [" P" I0 H( M: T若某人的台词中含有问号,那么下一个说台词的人即为回答者。若上一人台词中含有n nn个问号,则认为回答者回答了n nn个问题,请求出回答最多问题的前五个人。
$ g, _( \* Y. N3 V4 bdf['Sentence'].str.count(r'\?') # 计算每人提问数! b4 f( P s6 ~) H! m
ls=pd.concat([pd.Series(0),ls]).reset_index(drop=True)# 首行填0
/ m+ P* j+ p! x2 \8 b- zdel ls[23911] # 末行删去
1 [# q. W" L; W# Sdf['len_questions']=ls' W) k \% Q9 |7 f7 W
df.groupby(['Name'])['len_questions'].sum().sort_values(ascending=False).head()1 M7 ]. S P- q0 n2 U
1 y1 K9 E, D: tName
" G! v- A% ?: G4 j1 ^tyrion lannister 527
: N3 b5 R9 [$ N* D# wjon snow 374
2 t0 R, v% ], s+ djaime lannister 283
9 P; s/ J$ {! O2 [arya stark 265
8 N6 O. e. u1 ]+ J8 pcersei lannister 246. V5 Q- e: T# J @ Y. t
Name: len_questions, dtype: int64
7 ^6 S2 ]' K, {0 O& J1 s8 N
5 P. Q# L. Y9 z2 g, F# 参考答案1 k: M) v$ _" `! r3 K: |- E
s = pd.Series(df.Sentence.values, index=df.Name.shift(-1))$ I" P; z7 a ]3 ]0 g5 ?# i* c9 j
s.str.count('\?').groupby('Name').sum().sort_values(ascending=False).head()5 @, D6 u: v% ]
$ i. c& E2 a& c1 c! Z# m' w1 d+ m, |18 M0 a) N% `" c4 i9 h7 Y) E
2
3 ]: w( c1 e! c6 {' K- N/ A3$ D2 y- z! L$ t+ R
4
Q) F. J" H- M' V6 M56 k' h; ~* s+ v2 a; L
6$ W; V/ ^" g' ]+ {
7) d' N! B' {9 U
8
0 l, C. W% x& \9* l5 y. c" Y" l, M* Q5 r
10
2 J E; g Q+ X8 A1 a11
7 I! @$ y& F- N" ^& o: T12* R! u/ p0 O j) U
130 s' m. M- P' o3 w- `
14
: P" y s4 a' |: e0 P1 g15
" }2 }7 | u o4 H16( t, U: u* A% c4 j+ e( @/ C- b; x# G
17
; ~' M8 w2 Z9 r9 Y y第九章 分类数据: c, u% T9 I; o2 T% H- x: e6 \
import numpy as np- a# c4 ~* _3 n* e/ G, g6 n
import pandas as pd
0 ]9 \7 n7 U* B1 N1# m3 e0 q" k7 I4 ]3 t* j
2
5 `9 K# n6 M6 p p) c9.1 cat对象
# d. |: h2 \, ~- i2 q3 j- o0 D+ f9.1.1 cat对象的属性( |9 N: x) L, e/ a3 G' w
在pandas中提供了category类型,使用户能够处理分类类型的变量,将一个普通序列转换成分类变量可以使用astype方法。
" `; w$ a* d; Z# o& H
; s* l) n1 @* h) Edf = pd.read_csv('data/learn_pandas.csv',
6 }/ K5 Z% _6 d7 K3 e9 D0 n* ?" N usecols = ['Grade', 'Name', 'Gender', 'Height', 'Weight'])0 s1 y+ n& z. i8 {$ K6 d
s = df.Grade.astype('category')6 u/ f: D. D6 j x
0 q; V+ ^' [. Ns.head()
6 |0 j6 O+ I& hOut[5]:
( S* E5 z0 c& H3 Z% _0 Freshman T6 B, O. M* C5 |# j
1 Freshman t3 S( Y+ ^, n4 y) u! B
2 Senior9 ]3 Y7 N+ [) w8 G3 ^
3 Sophomore
& c8 U- t) S2 ~( [" D4 Sophomore
5 x+ D' n$ z! pName: Grade, dtype: category
) q$ e. v3 _+ F! o+ zCategories (4, object): ['Freshman', 'Junior', 'Senior', 'Sophomore']. C4 B( z: Q8 i4 _% d
1( }! A% g/ F* ^) ~6 r0 e" T$ I
2, u. o+ Z1 X$ h1 q* ?2 {
3# V2 r4 f2 x" b0 V8 _. _8 n1 g
4
! _! i7 a# h& G' z5
/ ?7 S( @. Y; U; Q7 A5 _* Z! z6, {1 c$ c* T U- z# Z9 m9 C/ A+ y
7
; ? P7 y9 T6 W2 p8! P1 r- [7 u, z( K5 T- M3 \4 O
9
; C0 G8 C9 A! z. P10
: ]! P9 x% V, c' M, ^11$ C1 x; g, \$ I) \9 b0 \6 _
127 b& t1 u, v* A$ ^8 G0 h8 |# w- g
13; n/ C6 T4 {* R, p
在一个分类类型的Series中定义了cat对象,它和上一章中介绍的str对象类似,定义了一些属性和方法来进行分类类别的操作。( R) e1 L; n0 k2 M/ E- q ? L3 Z$ ^
" |' M& `+ [8 R; @. `s.cat1 [9 h1 ]; M7 ` j4 S
Out[6]: <pandas.core.arrays.categorical.CategoricalAccessor object at 0x000002B7974C20A0>
4 B5 q) O' O1 U1 k/ J% m1* C% O* k3 W2 |' v
2
. t: c: _, a. L; N8 X! a/ {cat的属性:
4 [$ a2 ^: m' T0 |2 K
* e$ M t/ r$ j! a4 ^9 @* }4 Zcat.categories:查看类别的本身,它以Index类型存储. H3 `) ?9 p7 l" Q5 X
cat.ordered:类别是否有序4 l& y+ @" K" m" {. O
cat.codes:访问类别编号。每一个序列的类别会被赋予唯一的整数编号,它们的编号取决于cat.categories中的顺序
' Z9 l/ M! N; v6 I" ps.cat.categories
' U& d, r; d# W! q+ }3 `2 FOut[7]: Index(['Freshman', 'Junior', 'Senior', 'Sophomore'], dtype='object')/ h0 E( J# m. j' o3 N$ f
1 _; K- l; ?/ ~0 \( M0 h- a
s.cat.ordered
' `% k- c" I! {0 G: r- bOut[8]: False
6 U2 c/ K# w: b3 v! f z1 v4 _% p4 M, ^* Y; |, z5 U
s.cat.codes.head()0 |0 K- a* f: z" G9 R7 \( d: H
Out[9]: , q& P. Z4 a, L) j% @
0 0
. U1 _% y. p: _; F6 V1 0
6 m! ]5 y5 [0 s2 2
# x1 k# a6 C6 s9 w3 3
2 S$ |8 Q) g! \- ]- c4 3
1 {2 d* I" w, F+ vdtype: int8
, E- g& Q! Y# |, d1 H: A1
$ Z. ?& ~9 T+ `) Q2
* }" F4 |' a0 n- h9 G7 Q$ N7 y3
5 \) g- _: i. x4% m2 d0 @9 Q: x+ Q
5. ^3 @/ n+ |. x+ a# H- i
65 y! W% a( ^7 D% e# w/ P7 c) h
7% i- D2 Y' d* O2 u$ R, }
85 T9 O9 V" c! v0 Q( ?5 _
9; O; A$ Y4 z, V# O
10( U2 z) i# f# c
11
0 T4 U4 O2 A0 N. ^ n/ p" U12
0 Q# X8 [1 x/ w3 C0 v3 _5 l" g4 K$ {# B/ H13) \% ?8 K o" ?6 k- j
14& v* c6 A; A& f
9.1.2 类别的增加、删除和修改
: U, l2 d* B5 Q7 e7 V" z8 m 通过cat对象的categories属性能够完成对类别的查询,那么应该如何进行“增改查删”的其他三个操作呢?
9 q, R# m3 w( W, ]" R" I( Z m; B# Q0 }3 R$ J! y1 t) k( d
【NOTE】类别不得直接修改
: F# E. W* n2 E# E0 x4 V- ]: s在第三章中曾提到,索引 Index 类型是无法用 index_obj[0] = item 来修改的,而 categories 被存储在 Index 中,因此 pandas 在 cat 属性上定义了若干方法来达到相同的目的。
7 g2 H$ d6 C0 m& R8 D% J+ c2 c3 j6 W$ W% E' v5 b4 Q" {
add_categories:增加类别
1 M R' W; B/ ^) { { Os = s.cat.add_categories('Graduate') # 增加一个毕业生类别5 f/ Y8 C t; I9 j, C) E) c
s.cat.categories" d! j* r7 \' j- N$ X) ^4 M1 v+ q$ s
& K7 A }, I* r6 {- Y3 f
Index(['Freshman', 'Junior', 'Senior', 'Sophomore', 'Graduate'], dtype='object')- p. ^8 @) W. p* i' y) k8 C8 e1 Y
1
7 ~6 m! ^( H. f1 v( _( ?5 |7 k# l0 ?2; o" b. _- c6 h8 t. T" A0 _# ?
3( N9 n# d( G% g% ~ a" a$ K
4& O% }* P+ n }5 n n Q
remove_categories:删除类别。同时所有原来序列中的该类会被设置为缺失。& [. @0 E1 }% i. n$ h& I9 w
s = s.cat.remove_categories('Freshman')1 M$ A0 a4 J# Z S" N
/ P0 p7 f2 J+ i
s.cat.categories a3 p# Z' b% X' ?9 C7 k9 M
Out[13]: Index(['Junior', 'Senior', 'Sophomore', 'Graduate'], dtype='object')2 u' f+ D( T/ o' e. n' O; z
3 H$ X! G2 |0 p, k5 I2 L( us.head()) \' }- C% {( L1 E
Out[14]: 4 w: F6 _1 U( O) D4 a
0 NaN
% S C6 `' w& ^; ^; I+ X% k* x1 NaN) p ^% w) |! I9 @% X5 y8 F
2 Senior
' ^- p/ b$ _3 ]3 Sophomore
. v$ J) x# e [4 Sophomore
9 K5 s( x7 O) A% V1 x7 ~. G4 X0 }Name: Grade, dtype: category
* I( q5 Y3 k4 |( P0 KCategories (4, object): ['Junior', 'Senior', 'Sophomore', 'Graduate']0 I" @9 i9 J$ H+ Z' A) z2 _
1, G. \1 R5 X( O/ X% [/ ^1 p
24 C* K; w) R0 W" X" i" V
30 v3 o& C' Q0 t1 h Z( X7 d
46 }. X* d: B R
5
4 L' x6 y2 @/ A$ g61 W! m- k* g7 C' l% G9 G( X
7) f# z+ k7 C3 W5 N* n v( _1 f0 q% R7 h. {
8) r6 |: z& K, l7 o& V# k' K2 z
9
3 S- j3 r9 P1 n10% k0 s) E5 T, G1 ?7 {& Q
11
' z" I& c+ j3 `# n Z3 w% q12' k* L- n# O7 U& @! l* C+ G
13
; z1 e. k% V" s" Y ^14
( [$ h* l! Y$ rset_categories:直接设置序列的新类别,原来的类别中如果存在元素不属于新类别,那么会被设置为缺失。相当于索引重设。
9 ~. X5 C, [1 f0 m( zs = s.cat.set_categories(['Sophomore','PhD']) # 新类别为大二学生和博士
+ H1 d' I( Z! E8 Gs.cat.categories
: J+ p8 [! K! v& h! K% n( [* EOut[16]: Index(['Sophomore', 'PhD'], dtype='object')
: Q) U: n1 d- A; r, ]/ Q. ~" B7 Z/ z* E
s.head()
) z9 e' P+ Q; R( s0 gOut[17]: . V: x, G% F- Z+ s0 [2 z+ Q
0 NaN
3 L0 B9 Z* R+ _4 P% [4 r1 NaN
* E3 q8 F) E$ |* P: G+ L7 C2 NaN$ U2 m8 u4 w$ d6 d7 d; z# x
3 Sophomore; F( R2 P* `% a+ ^, r
4 Sophomore
; U, V9 p8 C: wName: Grade, dtype: category8 z' t( d, t3 I
Categories (2, object): ['Sophomore', 'PhD']
9 X5 u% {* ]7 J# E/ S5 J1 S2 T1
, |/ H% x0 t& B/ f2
- E, V( h. k( D* ?0 k3* u% J1 _5 {" j" ^ p5 i
48 D }& h% p( \
5
4 f4 m2 |& C% B: ~6! m$ j2 L' E1 I
78 f3 _% r- J" B. Z5 Y+ T3 B8 j" \
8
* n5 Z1 L; m$ e: C' @9
/ F# D' o+ {; m2 h1 H10
6 x( m3 \! j. r. h* ~6 a5 t+ d11
8 y3 K! Z# L/ {: a/ Y% o12
" ~0 @; D5 O, c5 m& B0 z13
) P. x: L1 r( j* q4 C, e) [remove_unused_categories:删除未出现在序列中的类别; N. B5 P7 i1 G: a: N- y
s = s.cat.remove_unused_categories() # 移除了未出现的博士生类别/ J2 B: `$ I7 Z. B4 a! e- }9 f+ N
s.cat.categories- Z9 l l2 u/ }' r( y* y& W
+ d( K( I0 ]% X3 q, `1 L, {
Index(['Sophomore'], dtype='object')3 H" f* B0 o% }$ k/ n
1
1 b+ z+ q( @! R& Q5 A$ g2
! T: }5 T" s3 A4 l37 L7 \' _% Z8 k t0 {; ^) k
4$ f1 r; b0 p$ J* a' G7 L: S. t& I Q
rename_categories:修改序列的类别。注意,这个方法会对原序列的对应值也进行相应修改。例如,现在把Sophomore改成中文的本科二年级学生:
b8 \" v1 k1 _! Z# F7 R e1 Ms = s.cat.rename_categories({'Sophomore':'本科二年级学生'})
! G0 d7 m) m( G0 V* `' }, Ds.head()3 x4 {% G5 v4 W$ T9 h4 G1 u
- K; _$ |# e( M# ]0 NaN+ T1 I4 O$ x& k/ b5 j, d8 V7 S
1 NaN
3 t. s* h* p9 d1 h2 NaN
; E$ L5 _6 z$ Y$ r9 p3 本科二年级学生
' A5 Q N; ?- b$ d1 ?4 本科二年级学生
2 Z1 F9 |$ R' W( h FName: Grade, dtype: category! r6 H! N# k" G6 k! {8 K4 k- {( I
Categories (1, object): ['本科二年级学生']7 Q2 `8 B3 ]. n, E6 Y
1
+ x2 y4 P @/ s2 N7 o) P( g2' D2 Q9 S! I2 L+ ?5 v. P9 N
3: A: n/ D$ |* p9 A
4* T( e$ ]' e, T" X$ \% s
5
0 @7 ~( _+ ~: I6 d6 Q* E& ~- }' q6
" e# i5 n# b0 \; [ \1 Z1 E7
8 a9 d0 C8 B' t$ M/ d: D% \" G- m82 G" E+ W3 p5 e$ _2 M w# [2 m
9
/ y2 o' [$ v) W! c, C# L3 ]& |10
7 P$ P9 u/ p, T2 F+ i9 M; K9.2 有序分类) _. o! q4 s2 {# A# J, `2 w
9.2.1 序的建立, P) D9 n- v' `
有序类别和无序类别可以通过as_unordered和reorder_categories互相转化。reorder_categories传入的参数必须是由当前序列的无序类别构成的列表,不能够新增或减少原先的类别,且必须指定参数ordered=True,否则方法无效。例如,对年级高低进行相对大小的类别划分,然后再恢复无序状态:3 m7 T3 z. @. w2 O% M% p
% Q2 U# l, U" I& {. L7 U, S9 T
s = df.Grade.astype('category')
# ]0 v' R/ a! c: a+ D' {! k r; f4 K7 es = s.cat.reorder_categories(['Freshman', 'Sophomore',
! J# x2 v- k, M& B8 }9 W 'Junior', 'Senior'],ordered=True)
/ c N+ ~- E9 x+ y5 t6 w) S& Z0 F$ js.head()
8 I2 z5 j6 {0 j& F% X9 c- Q8 ?Out[24]: j1 S, f1 k$ K# p# r6 Z! i
0 Freshman
- u) m. \4 {5 I* b! |1 Freshman
. `; @0 F4 n2 X A' Z( f2 Senior- |! H7 M4 v9 r
3 Sophomore. v' t# }, j. ~& ~( w/ |$ X
4 Sophomore r1 E& t# k' h0 Q0 x
Name: Grade, dtype: category. U+ U4 Y5 I9 m2 C7 n
Categories (4, object): ['Freshman' < 'Sophomore' < 'Junior' < 'Senior']
, [& n+ ~2 S) g* g2 u& e2 X. H* v5 X* ^4 h2 ~ m8 I9 n. r$ }! c
s.cat.as_unordered().head()
8 i* J' Y. o3 Z" n; dOut[25]: 9 l# ]3 C& Y% f6 Q6 c
0 Freshman
# T( u2 G& m+ \& g4 c1 Freshman
2 q0 @$ H8 s1 G7 `/ s2 Senior
/ f0 g3 J$ m- M9 N3 Sophomore. v( k7 X4 M/ u" ~! O$ j
4 Sophomore4 ?" v/ S m# O, K4 {! M0 J/ h
Name: Grade, dtype: category
9 q4 ]: P. S' iCategories (4, object): ['Freshman', 'Sophomore', 'Junior', 'Senior']
& T* e" f" O( o C# _/ S" r3 U: @' y% b. V
14 I4 [: i) y j/ M4 D
2
/ v2 d& x- a) y* J3
; L- ?, \9 ?* g& g: T* K4/ `( N+ |/ M d
59 T* O% F) U' A% O# w
6
+ F6 H0 p( T5 G' R$ C7
/ X, H$ k% O$ r. I3 _/ I: x( N8( W) C. Y4 E$ o1 u% d$ _6 S. m
93 U- }$ T9 p- q' j
108 Q9 p2 O) e/ {* K. _+ w. o
11' o0 u$ o7 T" G( W3 Z0 c' _
12
; D& e* |% Y; O$ D* {13& O5 b* g6 m, J# Y Z1 ~/ S2 p! N
14( F# |$ \* f/ j- c! L+ V+ v; o
156 u6 j U2 y3 _. F$ |1 o
16
! i. @/ x6 ~3 M6 |17, l I$ O0 [! e! j; X, Q j
18. q1 Y+ @3 m. H! k: F; m
19 d5 h' P0 W. b
20
: _3 z2 o3 H8 T0 R5 h217 P$ t$ u% U- T5 w2 B& N
22! R; Y/ y/ y/ a$ _' W8 @
如果不想指定ordered=True参数,那么可以先用s.cat.as_ordered()转化为有序类别,再利用reorder_categories进行具体的相对大小调整。5 Z! i3 s8 h, a
% v( z2 r& k* G( B" Q( ], ]/ y9.2.2 排序和比较6 C6 A5 R8 k* j, ]; i+ k
在第二章中,曾提到了字符串和数值类型序列的排序。前者按照字母顺序排序,后者按照数值大小排序。+ ^+ j4 x: n4 s+ ?1 i! v+ p; C
" c$ W% M$ P$ H1 B3 R. W
分类变量排序,只需把列的类型修改为category后,再赋予相应的大小关系,就能正常地使用sort_index和sort_values。例如,对年级进行排序:
; C- J3 B8 j9 g% x3 e( p. d! W" ^" Z( B
df.Grade = df.Grade.astype('category')
# E5 t- n0 w9 l# ~. zdf.Grade = df.Grade.cat.reorder_categories(['Freshman', 'Sophomore', 'Junior', 'Senior'],ordered=True)- }- u0 u9 ^9 O9 |' A
df.sort_values('Grade').head() # 值排序
( g3 L" H. D) j( a, [+ @4 ^Out[28]:
u8 c8 I, B& Y' J. A3 g Grade Name Gender Height Weight' f9 W) }2 [4 J2 M0 U5 s$ N( K
0 Freshman Gaopeng Yang Female 158.9 46.0
0 `; j& m2 b5 }% h" o+ s, E105 Freshman Qiang Shi Female 164.5 52.0
2 M3 u$ D6 a, T9 T' _& ~96 Freshman Changmei Feng Female 163.8 56.0
. ]0 W9 ]1 H. Q5 K; J, D% T* X88 Freshman Xiaopeng Han Female 164.1 53.0
% ?3 }; S% s8 X8 _& r81 Freshman Yanli Zhang Female 165.1 52.0 [) N7 D8 ~7 }
( {: o' l9 W/ V" n W% x5 q
df.set_index('Grade').sort_index().head() # 索引排序2 u- i+ L# w& e
Out[29]:
( v. X# D8 i, D. ]2 J0 V Name Gender Height Weight$ ~6 D& z6 C9 c2 R; y2 {' v$ v
Grade
6 X# |2 @1 C4 C' @2 S. g* N, i* G9 eFreshman Gaopeng Yang Female 158.9 46.0
; I+ j! u7 [8 o& \$ nFreshman Qiang Shi Female 164.5 52.09 L: F3 j; s: K1 E: t
Freshman Changmei Feng Female 163.8 56.0
' t7 [7 D* P- q# BFreshman Xiaopeng Han Female 164.1 53.0 d3 _3 R1 A8 i, f6 k! u
Freshman Yanli Zhang Female 165.1 52.0
& r+ K j- @% D: M6 c6 x' n& j( |$ B) d) d5 t3 T( _
1
9 i% X% }* b2 A( A- e- r/ I2
0 P$ @% ~7 \& r: B2 H5 r3
n$ g# g3 i. v' \' }4# s1 a8 a5 L. `1 A
5
( _' T# a3 v. h1 Q0 m* T @7 h6/ }( J/ e0 G0 c [4 X2 V1 I4 q# G' ]
7, ?3 l% F+ s, v
8
& w& w; A5 I( U1 z$ x, ?1 J l: A# B9" n2 v7 z! H3 ^& V9 U
105 A+ g' ^7 Z$ I9 b( M: \. P
11
3 e/ N/ D! X' L5 L! Q' u8 Z12$ i+ t" f, ?, W: s
13
; N& M% O: L, G* {0 z145 Q% W9 q3 T, ?: I
15' y5 f: [/ B! l) k' |
162 J$ t8 O! H: P9 a5 Q
17; X' \3 b6 X4 j* I% l) \
18! M% |% ]* ~ b, d
19# a1 e; \8 ]8 I( J9 Z
20
* ~. N5 \& {+ d6 s$ ^% i 由于序的建立,因此就可以进行比较操作,方便后续索引操作。分类变量的比较操作分为两类:+ D* X7 I) f( ]: D
3 \/ t+ X! m! ]. i==或!=关系的比较,比较的对象可以是标量或者同长度的Series(或list)。(无序时也可以比较)5 k! I2 R! A8 A/ L$ n; t$ Z
>,>=,<,<=四类大小关系的比较,比较的对象和第一种类似,但是所有参与比较的元素必须属于原序列的categories,同时要和原序列具有相同的索引。/ N9 `- t( {2 M6 u, E9 _' e8 n
res1 = df.Grade == 'Sophomore'5 Z/ S7 x+ D# d) K
# B- _; F5 o/ t l+ l! Ores1.head()
) b; u3 l: R0 O, h$ ?5 eOut[31]:
3 B6 ~4 |4 M- M6 L* ^, P0 False
$ ?1 }/ s' W g0 b: a i# T; U1 False
- ~: u3 s" T, R+ H2 False1 t6 a) M' y4 R6 X; b
3 True* N$ F6 I, b+ h5 ]4 Y6 i& g
4 True' h% ?1 T( g& b" f
Name: Grade, dtype: bool8 ?8 Q$ ?% F; k7 x5 H/ o
7 u+ I; a' p& l8 }res2 = df.Grade == ['PhD']*df.shape[0]
( Q, [ k" a1 g D3 E+ Q8 U6 [4 |# G; H
res2.head()
, X! U! X. D5 l" V. W. [Out[33]: * e- \$ r K9 S' i
0 False4 g" s9 x& w! y+ x6 ]7 d5 P1 ]
1 False
; Y& u0 Z/ D4 @2 False
: t1 C1 M& j; N2 I+ |* C5 A3 False8 h- ]5 Z7 b9 M* T- ^
4 False" J; o; i; p& {: @; j) Y
Name: Grade, dtype: bool6 {: u7 l8 P6 {( S2 c
% _! a% l }3 l& [( }res3 = df.Grade <= 'Sophomore'7 F! L5 M4 a2 r* u
# j9 R% E# [3 U; qres3.head()9 I$ t' P# E# v$ `2 U
Out[35]: , ]& F2 A0 P8 ^
0 True! E2 v% T- V* F& [( ~1 h
1 True
8 Q2 A$ l/ j5 R2 False" Q' w o4 }/ b# X' c, C5 ]6 v6 @& Q
3 True' Q; [4 \9 \! A/ e. d
4 True* {! P- @5 D( T$ [; z/ F- ?
Name: Grade, dtype: bool
/ a+ M O9 r4 X9 O7 N. m
5 V) `: F, {$ V" N3 z- g. O# sample(frac=1)表示将序列随机打乱。打乱之后索引也是乱序的,直接比较会出错,必须重置索引。
9 N7 l" ^# u& U: ures4 = df.Grade <= df.Grade.sample(frac=1).reset_index(drop=True) $ M8 D9 G8 `0 t/ R Z8 c: T
) F* x9 c' R( Q: w5 X' P+ S! h
res4.head()* o ^, h+ i3 f; M
Out[37]: * t; E( m O) A& ~5 J1 w/ C
0 True
& ~" `- V/ v& x* E5 E7 y1 True
|5 D9 n+ ]2 I8 v& V2 E2 False
5 V: k) O$ \5 N' C3 True% ~5 G! P9 A. V7 F! q" q; a
4 True- o/ j; i# @1 v+ L/ L
Name: Grade, dtype: bool n% U9 S! H6 T% D I( h
5 m" t# M. a$ Y9 T* k2 D$ N- a8 s- a
1( ]% C1 q4 |' n. B& M
2/ `+ U8 i6 j: a
3' m4 w9 p0 {* f3 R! L. Q2 ?
48 \+ }* K" X0 p' k* v# @
5& u! P9 c" z. o/ ^9 S& ^# {
6! _* n8 {4 ?! K+ q# ^- Z* X
7 S, T& g, v& U# Z
8! k h: `: v! J! i* n/ k) w5 v
9
9 {9 M/ K U) {: u105 J( _3 w4 `4 _! m* j3 F
11! c) i' R. \) U6 K2 x3 t8 p
12
9 I- [- I) C7 J' J# s7 s135 J `" e4 ^' i( q4 s* s) I% B
14& h6 t" R! _$ g0 I
15
# a4 R/ `- a. V$ p16
8 b k$ {3 B" A9 V170 X6 g! o4 w, O
181 _4 i3 K6 P# {9 B- O( m$ o
19: o% e( Z9 B$ x8 `3 }
20
! \8 J9 ?9 H) y+ n21. }$ Q7 i2 M" y' D5 k3 X% N, {
22- d4 O$ E' Y7 Y
23
) }0 F3 E% ~# N6 T24. }5 A( G5 M8 H2 _( S3 u
257 h, d0 r, B' J: O. x/ |
26# e/ x/ Y+ W! r# s1 v
273 M" }7 X {" a! A& u+ Z+ y
28; W% J( a; P$ ~
29
7 C. e6 B5 n5 g30% G$ w+ F0 l$ X9 N I( Y
31! b) W+ M5 A4 A E' C8 L. t& v8 x
32
$ j# @& w& N& K3 U9 l33
) S# ]' c; K* z34
( u6 l( a2 P/ ]: ^, f. y35
/ V6 n5 C; }1 ]7 u& N/ w% G36
8 s" ?5 D: f( N6 a37' b- n* e+ U% W( `$ m4 R) |
38
' g7 y' |) X0 W& b/ J39
+ Q! ]$ B4 s' `; Y( C( w401 @! |1 W5 G! L$ W
41) ] M- m, q% ]2 ?; b: P% B
429 a, L# n, X% t- Y6 I, i' H
43# j. e1 i) I+ w% d! B
44
7 T3 P! d3 \; n, w! L9.3 区间类别
8 u3 T0 V$ Z' m' J5 x& s% Y, d* `: H9.3.1 利用cut和qcut进行区间构造6 _. u2 C" |3 Y6 U( |2 g5 i" A x
区间是一种特殊的类别,在实际数据分析中,区间序列往往是通过cut和qcut方法进行构造的,这两个函数能够把原序列的数值特征进行装箱,即用区间位置来代替原来的具体数值。
0 \/ o. E8 V/ P2 Y" {5 ?) Y
! ?8 I D3 [5 N1 Ucut函数常用参数有:4 y; @" S f! o* Z1 p4 ^
bins:最重要的参数。6 a* A2 d+ m! m6 e8 @
如果传入整数n,则表示把整个传入数组按照最大和最小值等间距地分为n段。默认right=True,即区间是左开右闭,需要在调整时把最小值包含进去。(在pandas中的解决方案是在值最小的区间左端点再减去0.001*(max-min)。)
4 L$ Q; _( Y8 N2 V也可以传入列表,表示按指定区间分割点分割。
4 J; R2 s9 U; i% Y 如果对序列[1,2]划分为2个箱子时,第一个箱子的范围(0.999,1.5],第二个箱子的范围是(1.5,2]。! }) u; ~' S, }) y/ t% x+ M8 g
如果需要指定区间为左闭右开,需要把right参数设置为False,相应的区间调整方法是在值最大的区间右端点再加上0.001*(max-min)。2 f% I5 O& a* t1 n" x# r
4 q# h! h( D- U, O; i$ h* R8 D! L! m+ E
s = pd.Series([1,2])
& x; g; u+ c- {- S" b0 ^# bin传入整数6 A/ W3 A' i/ ~" x J/ M
' n2 X: A1 ?5 Q; mpd.cut(s, bins=2)5 q; g7 l& Q- S* [
Out[39]:
5 e. a: J# N- d& {+ @) z! f0 (0.999, 1.5]
8 r# k4 i+ e/ k7 h5 r) V. u1 (1.5, 2.0]
7 t' H; q- U( r. z! @dtype: category [! Z4 y! g: v5 f% @. @9 v+ B
Categories (2, interval[float64]): [(0.999, 1.5] < (1.5, 2.0]]' B" T- K& T( M2 f5 n8 E9 u$ Z( B& F
' B6 M/ L8 i0 i% `
pd.cut(s, bins=2, right=False)3 u' i e; u0 w T6 {% |& G
Out[40]: + W( @2 N; u# x, m& I; ?
0 [1.0, 1.5)7 h& T8 i) f0 f! s" R4 Q; P% k
1 [1.5, 2.001)
% y+ e" ^3 e( \3 ]2 ? ?dtype: category
7 g6 H4 R0 F$ UCategories (2, interval[float64]): [[1.0, 1.5) < [1.5, 2.001)]/ j+ E2 k9 D, [0 A& d
# y. O9 H1 s( ` G
0 W: B5 J" [8 v2 W# bin传入分割点列表(使用`np.infty`可以表示无穷大):
5 L T9 D" y O2 ]pd.cut(s, bins=[-np.infty, 1.2, 1.8, 2.2, np.infty])
, v7 W( T$ G: c) Q1 BOut[41]:
/ A( S0 J- D' N0 `) P0 (-inf, 1.2]
# u6 J, Y1 p) I+ y6 S8 V& c1 (1.8, 2.2]/ k0 X" M' s: g0 L
dtype: category. S( }- Y2 @9 G& Q$ [
Categories (4, interval[float64]): [(-inf, 1.2] < (1.2, 1.8] < (1.8, 2.2] < (2.2, inf]]: l. k6 _1 _, d4 g% z2 z& W
2 E. }/ b2 h: k; ?6 h9 L% y% P
1) ?1 e B" A" h' L; s1 U3 y5 ]
2/ Y% W9 Y" ?, J
3
! P* T: o4 f/ S, `9 r4
) O' B2 i2 d/ ^( o' O50 w+ p8 c$ v9 B1 X7 z. Y, y( F6 Z: d
65 c. `2 `! ^7 d1 M$ D8 e
7
! T1 Z. h$ _3 a# C8
7 I- w _( `0 g) m9) y$ F: p V" M% i6 g
10" s; k, m. a+ O9 G2 z: n
11
! ~$ p* l9 v6 C7 V/ S2 n o j123 C- y; f4 i/ V
13
/ o/ Y' ^! {, Q% K14$ O" ^6 p5 Z$ s0 q1 N
15( j" Z m6 B9 n0 s0 K0 V: T
16
" R! W. M0 |: ^6 P17* s. n2 o, ]6 @( i" R
18
- k* G% Y# j$ b: E# T3 g% v& q* ?0 h19
6 j9 r. ], b. O6 L20" `: J( c% Q0 b0 y0 t5 a. O/ T: O
21
3 u1 U( H$ G% j3 ]' e22
, l7 r, U7 P( _& M7 `3 q* A6 J236 J+ n# ~1 b a* c4 @
247 y9 s! j w9 S" ~
256 G% {5 i; K9 g- U) h
labels:区间的名字
- B+ b, s- o* a; s4 W4 X, i& Jretbins:是否返回分割点(默认不返回)$ r- \2 } F& q& A/ D; A
默认retbins=Flase时,返回每个元素所属区间的列表4 @% H4 f. j* e- m" ^/ B- N% t
retbins=True时,返回的是元组,两个元素分别是元素所属区间和分割点。所属区间可再次用索引取值$ N7 Y4 Y+ C j/ S9 F! x. K
1 k% _& f+ W* k* ^6 ^- F- u0 |s = df.Weight3 p: A. b/ u9 f) O
res = pd.cut(s, bins=3, labels=['small', 'mid','big'],retbins=True)
9 q- u* T# u: o$ o0 w6 ?res[0][:2]. ~; F- B: e4 L
; [% p! |0 F1 s4 b- _9 p
Out[44]: 0 {& k* @# p/ e
0 small0 ]. ]1 I" ~2 C! w( ~
1 big% e0 `9 f( ?+ S" T- F+ i: u
dtype: category4 T g- h/ E$ Y3 V- l9 p& H! O
Categories (2, object): ['small' < 'big']
9 l d3 j( {5 ?0 e5 i) A: b7 f# M' J( i$ q: E J
res[1] # 该元素为返回的分割点
5 F$ W1 f$ |" Y8 c6 iOut[45]: array([0.999, 1.5 , 2. ])* w( U2 A% s4 j) k1 |4 y7 `( v
1* ^( t" p, h: V% O; ~
2 t) z. p e7 q
3
9 b9 ?0 f* v) ?$ e, l8 O4
4 o2 E' a( A7 p5' L9 x0 g- P' f' `
6# k2 n; Y1 F$ r7 ~
7
( i' p! H& I7 [# Z3 D87 @& n# d, r( l' M
9* ]$ _' _! x! O* W$ [
108 Y$ i7 d; `+ Q
11
( ^# a) D* |6 `! z( x i" l3 O8 n12+ G& y% c6 x9 ]* `
qcut函数。其用法cut几乎没有差别,只是把bins参数变成q参数(quantile)。
* w' x1 }" q! B* i9 Q4 e3 Q; j8 u& U% uq为整数n时,指按照n等分位数把数据分箱2 o/ n* O4 U! H' H
q为浮点列表时,表示相应的分位数分割点。: H" ] ~$ s$ a2 P, h& q" q
s = df.Weight
) D1 `/ ~5 U9 N- {. H! w
# R( y8 Y+ _, i: U9 z4 b6 c4 H, xpd.qcut(s, q=3).head()8 Z3 ~- c1 l0 s% U
Out[47]: , j; j* S& y" S6 ]5 Z- M
0 (33.999, 48.0]
; I7 t2 T8 Q3 y+ G- L9 Y. J1 (55.0, 89.0]! u3 G6 m% w; z2 A2 y
2 (55.0, 89.0]; s/ _2 i* Y) @; B7 Q) L
3 (33.999, 48.0]
5 Q9 O& F0 l4 G0 x' n4 (55.0, 89.0]
# |8 `. l% @# u% h }0 wName: Weight, dtype: category
7 w& G3 J8 E6 a# V) ]Categories (3, interval[float64]): [(33.999, 48.0] < (48.0, 55.0] < (55.0, 89.0]]" @* h& n4 H3 m; N1 \5 V6 v3 x1 w
! l! J% C% C) x: ~. I* H+ Cpd.qcut(s, q=[0,0.2,0.8,1]).head()
6 P( X+ a. z( N1 g; w( x% FOut[48]: ( c$ }5 ]. b$ \
0 (44.0, 69.4]
2 m( b# f* c H1 (69.4, 89.0]
% i5 J* A& w! T0 L3 w3 ~* _8 u' H2 (69.4, 89.0]
2 [4 I- }7 i' t3 E% ^' U3 (33.999, 44.0]
: q0 C% e& p& j {# C8 L0 h4 (69.4, 89.0]
% d8 j3 h+ [; T$ JName: Weight, dtype: category4 e. w% j5 ]$ m9 M( |. U0 {
Categories (3, interval[float64]): [(33.999, 44.0] < (44.0, 69.4] < (69.4, 89.0]]
& F4 N3 X0 Y: j0 S& c" h0 v1 E8 G+ Y- A h* W
13 s# M4 _2 X! Z; D
2# {8 M+ E% Q2 G7 k- q
3, O+ X0 H/ J. V- v! G: A
4
) E& B2 ^5 m) \9 p" z( H5# \0 Z* c6 ?4 R
6
9 G4 x2 s8 D1 m4 j# M$ F% {7: ?0 \/ |+ k8 s0 y4 K# X
8
6 j, s5 m, q6 G/ |9! X f# Y, k- g- m2 }9 F1 n$ y; v
10
( S; b7 t3 A" U0 p ]% P11 _- }. ?# b! O$ T: ^ K( _( e
12
4 Z; Z5 ~" C0 x" O13 T7 x! u0 \" j0 P$ ~ S
14
1 e; Q- _* R1 d# x# P15* |) _8 {! m& F" t
16
7 T; u( M- |2 K8 Q A' a17
& a! |$ k5 g0 x6 }, N18
4 N; A1 ^" L2 N9 w4 K4 N0 G' ~5 U19
. v- h- q" `1 t6 L4 a8 i8 |5 k20
' j4 O' j" K6 j21' G0 o+ Z+ D, v3 Y9 i
9.3.2 一般区间的构造
8 [$ Y% \3 Y% o. o pandas的单个区间用Interval表示,对于某一个具体的区间而言,其具备三个要素,即左端点、右端点和端点的开闭状态。
1 a, K1 A& P+ E4 ?
$ s8 U8 t4 {1 f0 R% o: r开闭状态:包含四种,即right(左开右闭), left(左闭右开), both(两边都闭), neither(两边都开)。 U5 s+ z* ]( \# A
my_interval = pd.Interval(0, 1, 'right')
, \ d( z) p1 P2 T5 J- ~ o5 O( e! E6 r5 k
my_interval0 U( R$ E n. E# j: I6 t& A
Out[50]: Interval(0, 1, closed='right')* G- w. r4 V1 B, D
1) U: q1 i. L+ L* C
2
7 X& E* D$ F1 x% u4 U3
+ H! |) D& R" N) ~# B8 K8 F3 b+ l* o4
, h. s8 \1 N; K区间属性:包含left,mid,right,length,closed,,分别表示左中右端点、长度和开闭状态。
}! d2 P& t0 l7 z使用in可以判断元素是否属于区间
+ H. C! p; @: A7 h$ C' X8 ^用overlaps可以判断两个区间是否有交集:, e( J. R! N& x& D$ \8 ?
0.5 in my_interval6 ]- Q, L( w# H8 V. c5 B
: r# q& `3 y2 V" K3 Y) l2 r
True
+ T4 J- T0 F( L, I0 e# ~! ~1
# A) i- e1 \+ g0 ]9 _/ c6 r2
2 r8 D3 K/ X! a8 v& i K m3
2 \7 p& G& D6 V l0 |) n9 \/ l5 Omy_interval_2 = pd.Interval(0.5, 1.5, 'left')1 ^3 U3 J7 H% N+ ^
my_interval.overlaps(my_interval_2)8 N, }7 D6 s {
+ }5 a. j! n9 d' e/ \
True
6 {+ M+ h6 b# O; X9 j10 O6 I* S" J/ q- ?+ a" z
2
2 g" F3 m, H- T- F8 ~; F# I3% X/ w2 x$ n- `, g1 i& p- N
4
0 X0 R0 _# }* m& Q8 h pd.IntervalIndex对象有四类方法生成,分别是from_breaks, from_arrays, from_tuples, interval_range,它们分别应用于不同的情况:
5 o& W/ r' [4 }4 S( f, Q
2 @9 @6 _- _+ A9 q% l6 {from_breaks:类似于cut或qcut函数,只不过后两个是通过计算得到的分割点,而前者是直接传入自定义的分割点:
* D+ N% x" D% g- ?- q( lpd.IntervalIndex.from_breaks([1,3,6,10], closed='both')
5 j8 j2 s; _) g/ ^! w9 \
( I; ~0 u6 F( T# B( s' bIntervalIndex([[1, 3], [3, 6], [6, 10]],- S6 K* g# e7 N5 t/ _# a; p; h
closed='both',
0 I5 m A/ w# u* B0 [8 w dtype='interval[int64]'). ]) D$ s) @( l" e7 i; A% j
1( x" w6 {. M" Z2 z, H
2
. L& J9 u4 N$ A* P4 ]& H3
: I% f5 H! s. J+ R4
F6 F! r. P7 n" x8 |2 z51 `; }, w& e Z1 k4 D
from_arrays:分别传入左端点和右端点的列表,适用于有交集并且知道起点和终点的情况:4 u4 X- y( I5 R+ R" d1 ?
pd.IntervalIndex.from_arrays(left = [1,3,6,10], right = [5,4,9,11], closed = 'neither')" ?2 z3 Z3 _: I! x
; E1 _, Y; A( P6 q8 c: iIntervalIndex([(1, 5), (3, 4), (6, 9), (10, 11)],
, C k. X: t( Z) J' e1 M5 g closed='neither',
- |1 ^" d F5 C' m0 X dtype='interval[int64]')
/ K0 _0 c# u0 a1- i6 ~' R) f9 X T( d. Q' }* W
2
; b8 k! Z3 c* [3
/ f: _9 j7 c- t& A; m) T41 N$ ]8 T& e0 A, f
5
# L x4 h: Y/ |$ d# Xfrom_tuples:传入起点和终点元组构成的列表:
, M9 I" b3 m7 c: H0 v. Ipd.IntervalIndex.from_tuples([(1,5),(3,4),(6,9),(10,11)], closed='neither')
7 p D4 i- O( y4 E6 \0 P) m; N
5 |$ y$ ~0 R2 C! m: l9 UIntervalIndex([(1, 5), (3, 4), (6, 9), (10, 11)],
- Q8 p# `! z" \8 ?$ l closed='neither',
% b+ b" C$ m% C dtype='interval[int64]')
7 B, L; m+ i/ c t9 ~1
( ]# I# Z8 C, x27 n4 g3 x, v0 W2 z
3- w" l) N9 o) T* l; f7 F
4
3 }: P8 J9 Q7 z( d8 [2 j1 p( w5
( B. a! Y8 u9 `3 b+ yinterval_range:生成等差区间。其参数有四个:start, end, periods, freq。分别表示等差区间的起点、终点、区间个数和区间长度。其中三个量确定的情况下,剩下一个量就确定了,从而就能构造出相应的区间:
) s' n- g, ?( _! M7 {. j z7 w' dpd.interval_range(start=1,end=5,periods=8) # 启起点终点和区间个数
3 U3 c' L& L& X1 p$ U* AOut[57]: # M) ]* w( R! y. X" J$ a. W" c
IntervalIndex([(1.0, 1.5], (1.5, 2.0], (2.0, 2.5], (2.5, 3.0], (3.0, 3.5], (3.5, 4.0], (4.0, 4.5], (4.5, 5.0]],
. F b+ n- L/ b6 f) z closed='right',
: I0 u' `9 U4 q" b s' e dtype='interval[float64]')9 R5 e i( @+ t$ x2 p( {+ T! l
' V8 R* L r1 b2 m( h4 h
pd.interval_range(end=5,periods=8,freq=0.5) # 启起点终点和区间长度# E" J+ V% ?: w( m) `
Out[58]:
3 b% H( i; Y6 o. q& Y* FIntervalIndex([(1.0, 1.5], (1.5, 2.0], (2.0, 2.5], (2.5, 3.0], (3.0, 3.5], (3.5, 4.0], (4.0, 4.5], (4.5, 5.0]],
1 [; I9 F! o4 I' X* i; \ closed='right',
* Y. v+ m$ b3 k/ p9 i( _$ p dtype='interval[float64]')& ^, B6 T, X! Y! t# N! s$ m% f
1( ^" ^5 m8 B8 |9 A1 p: i2 T3 I* J
2
2 I1 R: B7 e, Q( J, {- o3 M4 {6 k0 c! ]0 M
4
" G1 z$ d! o3 t# U, ~& s6 l57 X4 [/ v8 j4 U+ S; [7 [& W& g5 P5 \
6
7 r4 s: S# [5 j* o" p3 a7
9 P1 k; t% B7 a J4 f) r8
4 U4 Q9 }. U/ s! H$ p4 N: U* U9 S |: [+ x- ]. V& i
10
9 i& ]# X( Z% V P; m11
8 s& a7 f" S# c$ `8 U4 g0 \5 ?【练一练】- d/ v! M0 N% k4 d7 |3 [% x d' R: h
无论是interval_range还是下一章时间序列中的date_range都是给定了等差序列中四要素中的三个,从而确定整个序列。请回顾等差数列中的首项、末项、项数和公差的联系,写出interval_range中四个参数之间的恒等关系。5 B' m' ~* J! _. t2 G/ c" y
# X' D" Y% }, G9 M, Y% T
除此之外,如果直接使用pd.IntervalIndex([...], closed=...),把Interval类型的列表组成传入其中转为区间索引,那么所有的区间会被强制转为指定的closed类型,因为pd.IntervalIndex只允许存放同一种开闭区间的Interval对象。$ q: ^5 o5 x0 \3 k. I) ~9 A
/ m' H3 N: ~; c4 }; zmy_interval2 A7 V, D2 `$ } |
Out[59]: Interval(0, 1, closed='right')
2 |* E& j3 Y7 b8 t: w: J
6 J4 x& }4 b' G' \5 |' ?' ], cmy_interval_2: L0 A- a( r) o/ T6 V
Out[60]: Interval(0.5, 1.5, closed='left')
3 h9 ], U7 n2 z# \2 o
. _6 O' [% ~! ?$ hpd.IntervalIndex([my_interval, my_interval_2], closed='left')
/ l! i, P( C# ^$ J T( y/ dOut[61]: ; H/ N" R4 O. w8 A9 x
IntervalIndex([[0.0, 1.0), [0.5, 1.5)],
" A3 Z h0 m* {; Q: _, ~/ W- z closed='left',
. S- H! H% k, j dtype='interval[float64]')" N& G2 ]+ p6 D5 i
1
. H4 h" A) |$ |: I. D m2
8 W1 J4 _, S! g* j0 Q+ p9 V34 w; O0 z7 d+ Q
4
+ N" O1 x7 P* h5% R; e2 P& E8 L. H. X
6
. u% P2 D: @8 f- L% b) r7& g3 a$ b% @+ @1 N0 i' B
8
2 f/ _9 P, [6 \/ i9
/ C, |2 |1 q* ]" o10
$ a6 B2 U+ E( _( \" J" p11
# ~! |. W# I8 F9.3.3 区间的属性与方法8 f/ l L, V! _; ^3 d6 a
IntervalIndex上也定义了一些有用的属性和方法。同时,如果想要具体利用cut或者qcut的结果进行分析,那么需要先将其转为该种索引类型:, Y: C% e. g% K* F1 m
* O X% ~& v/ F
s=df.Weight
/ y. f2 E% C% ?# }- _, }id_interval = pd.IntervalIndex(pd.cut(s, 3)) # 返回的是每个元素所属区间,用具体数值(x,y]表示' L. D5 a3 D/ _
id_interval[:3]0 J5 h5 L+ Y4 b; z7 D" y; @! t
' ~, L* A! Q! W5 n, {0 TIntervalIndex([(33.945, 52.333], (52.333, 70.667], (70.667, 89.0]],
- n, n e+ }& q B+ H closed='right',- {. S$ E' U5 i; _6 ~0 h+ i: o
name='Weight',
" J7 |7 H+ D$ M* Y0 s' [ dtype='interval[float64]')) r( M4 P7 X* E
1
' c1 K& w! i/ z" r4 z2- f2 u0 d4 z4 q: k) Y5 g
3
7 k3 F$ `) M' w! L: p4
( ]- ?, M3 r( E1 l# ]. k53 c! G/ [8 {) c z M
6) e7 C1 F* q1 W3 n: p
7
5 k. D K6 L6 d- x" y: @8( @ E# ^5 d1 n. B, u. v& }1 B' P- }
与单个Interval类型相似,IntervalIndex有若干常用属性:left, right, mid, length,分别表示左右端点、两 点均值和区间长度。
9 v! e) A# B% t" ?6 D3 `! Qid_demo = id_interval[:5] # 选出前5个展示+ m; x1 v: C& X7 d
8 Y+ {$ S5 s, j( uid_demo8 W3 ~2 H6 E, V2 R, R# Q: Q' V
Out[64]: $ E& N- o. M! a/ [ }1 R3 {
IntervalIndex([(33.945, 52.333], (52.333, 70.667], (70.667, 89.0], (33.945, 52.333], (70.667, 89.0]], F$ P/ v8 n% A& {
closed='right',6 {3 N* H: c+ D9 B# i+ ]' ?$ d0 M
name='Weight',
" G) V. t' _) V dtype='interval[float64]')
: B5 _/ g( r5 `! r
6 ~# Y) ?5 M3 f1 i2 d2 cid_demo.left # 获取这五个区间的左端点
6 C2 r8 j* F- x. c0 p" W% qOut[65]: Float64Index([33.945, 52.333, 70.667, 33.945, 70.667], dtype='float64')
$ h1 O/ k; b" G9 e
Y3 e4 E& c) H' J) [0 S) Did_demo.right # 获取这五个区间的右端点( w# O. n: C' ^
Out[66]: Float64Index([52.333, 70.667, 89.0, 52.333, 89.0], dtype='float64')) b% \5 r4 m8 m- U( F
. T* _) p4 G$ h p
id_demo.mid
' C4 X; U& A* E3 }, e8 n( n3 S. ^! e* l6 \( uOut[67]: Float64Index([43.138999999999996, 61.5, 79.8335, 43.138999999999996, 79.8335], dtype='float64')& m! o& I5 x4 M, T9 C% O0 E
& _, r' m8 l7 @% e5 Bid_demo.length0 g+ ~/ b7 u0 w( h" g, H
Out[68]:
5 |2 B& V+ s& c# u; Q' v, qFloat64Index([18.387999999999998, 18.334000000000003, 18.333,
4 l& `" l- n# ?7 n; y8 V# K: ? 18.387999999999998, 18.333],
" e/ z& _7 |' u6 w+ s dtype='float64')" l- o2 s# p" m# {
" w% J/ W& Y& r3 }; V1. A4 Y" ^$ x- x3 B( U% g# z
2
4 F& ~. ?1 D8 u% G1 c37 B0 Y2 v+ k5 Y* H7 I
4
: V* }: W0 N# ?0 q$ G+ e$ H5
q; S6 ^1 z- k9 L, T5 ]% [; \6
8 Z1 V7 `6 v) B6 I9 n7 e- o7 R9 e5 y" O- o7 G" p
8. L0 G+ g2 L9 u9 Q9 _
9
# v+ ]/ u- |$ F7 c! f' g7 l10
: T) d& p) T; J* {' ?6 S2 e2 D11
( H) R- R9 N! U% g5 A12
2 b% a: C, G' A7 z13
( U' z) n3 L0 y8 A( x14
) A! L6 P9 j6 y; q9 \15
2 x4 L& {/ u9 G# h! i+ e. N16
9 d( {! l$ b7 ^+ M+ Z17: q9 H! g" S& H: e3 B% l( }
18
) U$ f& ]% x, k W* W8 s19
$ B' U" V- M: B, v3 Y7 U20
% |& z7 A3 K2 j21
( d# P% ^7 q @5 Y22
3 T& O: r. Q8 H2 Q- W# u23
* i+ F' P6 }& U1 }1 y! vIntervalIndex还有两个常用方法:
) D+ j: d; i3 o" |5 rcontains:逐个判断每个区间是否包含某元素4 c4 ?1 Y- r8 B
overlaps:是否和一个pd.Interval对象有交集。
, ?$ N- g" b1 [* o1 w+ @1 Qid_demo.contains(50)
* E. o; Q6 C; v" mOut[69]: array([ True, False, False, True, False])
- x5 f/ q! Z5 M6 K# T" y7 v/ m% h
id_demo.overlaps(pd.Interval(40,60))
$ r/ m6 Z; X* z/ v/ H4 S1 ?3 \Out[70]: array([ True, True, False, True, False])7 ?" w; D. \6 h0 F
10 c6 d b) u O. ?
2
$ K2 {) v1 \" Q' g3 N: X/ M3# P# O+ Z" c- d+ c, S! C$ C2 C
4
I8 ^! m) m/ J5$ S7 b: l1 ~! V! }2 r
9.4 练习3 U# `4 u& p6 I* A2 ^( \6 v
Ex1: 统计未出现的类别, _: }7 S) E, w; M5 B p, K9 Z
在第五章中介绍了crosstab函数,在默认参数下它能够对两个列的组合出现的频数进行统计汇总:, |9 ?' P5 |: K- W2 @ Z1 X
8 H6 k+ U, J! ?" E9 G7 i" mdf = pd.DataFrame({'A':['a','b','c','a'], 'B':['cat','cat','dog','cat']})' q! s. [$ {( i0 b# ~* a
pd.crosstab(df.A, df.B)
1 U4 T+ U1 M6 }
1 h6 o+ @1 c- }0 @! @3 WOut[72]: ' ?" c3 L1 G6 H: ?" {0 B
B cat dog
: z; p% N( v) `0 _" ]A
$ G% x- D2 F3 W2 ?. D3 ~a 2 0: d4 N- l6 }: b4 j
b 1 0
& s7 o; m. A- ]/ K: P/ Y& |8 ^& bc 0 1
* H7 q" O5 q2 t' P# T- ]1
. @. G8 p$ w$ ^4 q2
+ C5 P. u3 G! @2 j2 u) C+ `6 z3
; U0 T, z9 i e: s4- \3 b5 L4 U: a- T
59 C2 \- Z& k1 X& E; S
6
) x f5 W) r9 \7
; V/ h; [; g. R/ [5 Q4 Q7 c8
: [5 f* Y4 r4 `, H$ c: j3 _2 c" O9& g' d# o6 M% l$ a" J
但事实上有些列存储的是分类变量,列中并不一定包含所有的类别,此时如果想要对这些未出现的类别在crosstab结果中也进行汇总,则可以指定dropna参数为False:
1 K. b# R# L) q$ m4 b6 E0 } o: t {. p) ^
df.B = df.B.astype('category').cat.add_categories('sheep')# R6 E0 z) I( l+ |/ ~
pd.crosstab(df.A, df.B, dropna=False)
) ~0 I6 h1 Q- j5 [/ o+ U$ L4 B. b. U- r& w
Out[74]:
0 q+ j( r) ^' d* y) ^B cat dog sheep! Y8 y: f5 O4 i) _
A
& \- }! `4 n3 j6 t* F1 M+ v3 A# \a 2 0 0
: @( w3 ]' r; d0 i! ?b 1 0 0
% O* ^6 h! s* K. J, j- q0 |c 0 1 0
1 Q4 C' {; g1 F) P" v; H1
- x- f2 c% v X q' t( l, y7 a, m2 L# Y2 m. b, Q5 r: Y
3
/ x7 w% S: W, w, G' l4
+ n0 p/ n9 t* k/ [' ]5
* v' ^) s. e1 k& |6; q) g4 b$ p( D
7
4 U* L8 Z# |1 l! y. n. o8
- P& o( ~ i) @1 v' n0 W9
7 N+ l8 H1 i3 ]" ?* C5 }; q请实现一个带有dropna参数的my_crosstab函数来完成上面的功能。
/ s) l$ \! Z5 x/ V! `+ B! l% g/ u
Ex2: 钻石数据集# X! I- `5 v2 Z: Z4 S: I" I( I0 |/ ^
现有一份关于钻石的数据集,其中carat, cut, clarity, price分别表示克拉重量、切割质量、纯净度和价格,样例如下:
( x9 e0 ]" O- C% A) h! x' c# w( r6 \
df = pd.read_csv('../data/diamonds.csv') $ _6 Y% \% ^3 K( a: t2 v8 K) c q
df.head(3)
, H5 Q, F2 U; j# G5 n2 R1 L1 T/ ?% o8 J6 s
Out[76]:
6 t9 f+ s$ y: t3 N" t/ U4 D( k carat cut clarity price
3 r0 x$ X4 B* t# A5 N" `0 0.23 Ideal SI2 326
$ y" d3 y3 |3 q, D/ a1 0.21 Premium SI1 326
2 j* {* v# G; Q9 ?& w2 0.23 Good VS1 327" t# s, E. E: s8 Z
1
2 k' a* j/ E N( Z8 _' E25 q5 e7 z. w0 a! I1 T& I+ Y5 G
3
, N( R, y3 k( T1 p$ `: o j: N48 h I V9 i& \, n& }- {
5( Z2 j0 U7 `8 a# W9 D3 C3 r
6
# @2 Z" ^0 I. i1 _: q; q/ L0 y C8 |7
% Y a2 L! ~# N85 \* e& p) w9 p0 \' C: P
分别对df.cut在object类型和category类型下使用nunique函数,并比较它们的性能。/ I( ~$ w" G' O7 J, u% V
钻石的切割质量可以分为五个等级,由次到好分别是Fair, Good, Very Good, Premium, Ideal,纯净度有八个等级,由次到好分别是I1, SI2, SI1, VS2, VS1, VVS2, VVS1, IF,请对切割质量按照由好到次的顺序排序,相同切割质量的钻石,按照纯净度进行由次到好的排序。
1 O- a& K0 X9 t" h6 z6 p分别采用两种不同的方法,把cut, clarity这两列按照由好到次的顺序,映射到从0到n-1的整数,其中n表示类别的个数。
^7 r% u# d8 c- f5 m对每克拉的价格分别按照分位数(q=[0.2, 0.4, 0.6, 0.8])与[1000, 3500, 5500, 18000]割点进行分箱得到五个类别Very Low, Low, Mid, High, Very High,并把按这两种分箱方法得到的category序列依次添加到原表中。
& J: D! Q9 }+ w. S: g2 `* H第4问中按照整数分箱得到的序列中,是否出现了所有的类别?如果存在没有出现的类别请把该类别删除。2 V* i G' g% K/ z6 M/ f
对第4问中按照分位数分箱得到的序列,求每个样本对应所在区间的左右端点值和长度。* c" x& u8 V+ w) Z0 Y
先看看数据结构:
0 M# N! Y7 `; f3 J/ m$ F8 U7 T' G0 P1 ^$ }
df.info()
v0 Z: s6 E0 s/ c! b; s% XData columns (total 4 columns):
) Y3 x7 r) x2 {6 [- u # Column Non-Null Count Dtype
, I( d8 J2 h7 B4 [" K$ T+ C8 H--- ------ -------------- ----- 7 s- }+ K0 M( n7 s
0 carat 53940 non-null float64
6 V3 J6 d8 f& H. `& ^; w( m, I( ^ 1 cut 53940 non-null object + p$ ^- M" s9 {( w! f. W
2 clarity 53940 non-null object & n% @+ Q4 w( e
3 price 53940 non-null int64 $ @3 `! o* _+ l5 N. I
dtypes: float64(1), int64(1), object(2)+ N& j3 d* D! U3 [% J/ [5 B. p. w, I
1
3 a6 S( X5 x) A$ F2
5 l5 R- y, ?6 d! }7 b3
6 d. R7 A0 E4 l4 y, e4
& r+ m/ F* ?0 B0 S" |) l5
; r$ c9 z8 T( F6 G6 C6
) W# S. e9 s4 W6 {) q77 G7 Z9 ]' {$ N* q( S9 h
8
% T1 o) k% `8 u5 A: D( z4 z" s9( ^ k9 P3 {4 X2 F! p6 o$ N
比较两种操作的性能
8 E! J8 B' t: N6 C9 H, u2 Z! `%time df.cut.unique()
: |/ k$ ?8 J. H# g4 x
# r9 I. C" H" _% nWall time: 5.98 ms$ N8 M9 c& c2 S7 p
array(['Ideal', 'Premium', 'Good', 'Very Good', 'Fair'], dtype=object)
, w1 v) o5 `) W) o1
8 z. `5 |* ~. v9 M" U29 x( F: ?& f) k
3
- `) M+ s5 [) h, N( z4
+ k5 b# J) U' j%time df.cut.astype('category').unique()
/ Y) @7 T8 C w/ I T- F% S0 ]
* | {4 z) Y; SWall time: 8.01 ms # 转换类型加统计类别,一共8ms" h7 R2 R6 |$ _" \4 b
['Ideal', 'Premium', 'Good', 'Very Good', 'Fair']' r) g* ~# N- q# o3 X
Categories (5, object): ['Ideal', 'Premium', 'Good', 'Very Good', 'Fair']" P' Q# R, G, ?1 y, h
17 K- D0 h1 d0 p) G9 W- t7 R
2/ s! a" h8 o0 ? {
32 R3 @" d. u& V5 o7 \
42 r- C4 ?8 G& X5 k
5
4 j) y% O2 q- odf.cut=df.cut.astype('category'): Q6 t* y7 A$ ? B
%time df.cut.unique() # 类别属性统计,2ms. a2 e9 V( ~& U4 m
1 h* ~/ m$ m' t4 P4 C# gWall time: 2 ms
8 n0 V) i1 ?( I g! F. T['Ideal', 'Premium', 'Good', 'Very Good', 'Fair']8 s; L: ]9 q3 A$ j0 d
Categories (5, object): ['Ideal', 'Premium', 'Good', 'Very Good', 'Fair']
3 Q% d1 i u ^8 d1
; Y( `" S9 l+ q0 r8 y2
" |7 y$ h; d$ O6 B5 e' w1 F% L O3
2 [! A8 H7 E; ^/ _7 n) _1 {( F4
|( T6 ]( g8 ^" r( Y58 t3 ` D5 V7 O. ~) j- V
6
`3 K4 v( i" `% ?对切割质量按照由好到次的顺序排序,相同切割质量的钻石,按照纯净度进行由次到好的排序。$ f+ j) u8 F' A3 z3 B
ls_cut=['Fair', 'Good', 'Very Good', 'Premium', 'Ideal']
( |: N" i. d/ `4 j# Y2 Lls_clarity=['I1','SI2', 'SI1', 'VS2', 'VS1', 'VVS2', 'VVS1', 'IF']
/ g2 J3 ~+ p1 L2 C* u0 udf.cut=df.cut.astype('category').cat.reorder_categories(ls_cut,ordered=True) # 转换后还是得进行替换0 T. N P: |0 i% q; x! U
df.clarity=df.clarity.astype('category').cat.reorder_categories(ls_clarity,ordered=True)+ s) Q( u$ _0 s
% a% c Y* Z2 A+ Q
df.sort_values(['cut','clarity'],ascending=[False,True]).head(3)
; H; [( {5 J+ X" f' \) }3 d2 N' T: n/ e* Z5 ^ l3 U
carat cut clarity price4 a* W% f4 u* Y/ |. C4 W; s
315 0.96 Ideal I1 2801/ t. U- I6 b1 p" X4 O+ V- r
535 0.96 Ideal I1 2826
# E# `5 A/ P2 m7 n, w, \551 0.97 Ideal I1 2830$ P1 M) V/ i! g
1
* ?! q: o j7 D+ r2
6 i1 k- t0 V2 ~ F k3
" \: `: V1 H- K6 B4; Z- x5 D9 Z; \ [) A/ M: J
5" k: M! c; j# `7 l* w8 i
6
- F4 Z2 w( D9 a7 V1 v8 P70 \- Z% g, d7 s0 V
8
/ M! {( `8 O; O. r, K9
* a7 a7 `5 I7 w5 Q2 c$ X10
& b# N- l8 h9 s( U$ r- G115 A2 \1 d0 M6 M9 w$ ]2 w! N% b
分别采用两种不同的方法,把 cut, clarity 这两列按照 由好到次 的顺序,映射到从0到n-1的整数,其中n表示类别的个数。/ j. S8 F3 F; S) h9 F' N7 }2 i
# 第一种是将类别重命名为整数
0 P2 o; D, R6 t Y9 Ldict1=dict(zip(ls_cut,[x for x in range (4,-1,-1)]))5 H8 f: K- f {
dict2=dict(zip(ls_clarity,[x for x in range (7,-1,-1)]))
9 ]7 ?0 N6 n* N e! M+ J
% O" p% o* U9 M# b) {0 tdf.cut=df.cut.cat.rename_categories(dict1)
3 {. k8 f' W6 g( i6 p; Y( q2 o. J* _df.clarity=df.clarity.cat.rename_categories(dict2)) ^% p- @7 ^* r) p& m/ C
df.head(3)+ G/ E, W* S, z0 R( K+ {! [% V9 f& X
, @; R( u9 b$ q
carat cut clarity price
Q) ] E+ S5 Y) B: S6 t0 a7 m/ h0 0.23 0 6 326; k+ w f/ _$ p" ~2 ~2 f2 I
1 0.21 1 5 326* U/ z4 A6 l0 P/ U2 U
2 0.23 3 3 327
& y' ?% L8 f1 y! H: B- k% u1
/ G( {' {6 p9 f! s3 a0 q! ~/ C0 c( `2
0 j6 H9 M" h2 S5 q3
" B1 C' ~4 D1 h5 k) c4
9 W: ^1 t" y8 J- I9 F# i! m6 l5
. t6 X, e! k1 e/ u% }- Z2 w67 S7 s9 S9 a/ @3 t2 _- K& Z
7
8 G [1 G5 Z- `% O) s# y7 x88 v" I2 w' H! m2 O# X! o
9
0 `3 E" s) r/ m: \4 m3 v. k10( b8 S( G5 y% T5 u% T/ Y0 w; I
119 p" w a1 X9 w. P- z
12
) Q( }1 g' X7 \, T# 第二种应该是报错object属性,然后直接进行替换# M+ ]3 ? ?- r4 T
df = pd.read_csv('data/diamonds.csv')
3 q# U+ f) z4 p8 j, S: e1 sfor i,j in enumerate(ls_cut[::-1]):& @7 D* k0 g/ K+ x9 Z* k4 X- B
df.loc[df.cut==j,'cut']=i
! y0 u/ [% u6 L1 u; h8 r! Q0 W* ^; a& M2 A0 @6 |6 k# V6 l2 k
for k,l in enumerate(ls_clarity[::-1]):
4 s ]6 g( a% ` df.loc[df.clarity==l,'clarity']=k
: ~$ e8 ?$ `# ]; x# `( H" bdf.head(3). h# b; Q! p2 D4 a8 B# `+ J
4 [9 b; D1 K9 y; t A6 g carat cut clarity price& e! V" O& e6 T0 k* c ^$ M# a3 k
0 0.23 0 6 326& l. k! E6 V) e
1 0.21 1 5 326
& s6 Y4 P( g+ ?. r2 0.23 3 3 3278 c) T: c; {' z& [3 |6 \
1
" v- g2 w* t* I2
8 |, `) @6 E; `% t4 j3/ X: n' \& e5 V) F: H
42 ^2 |9 }. f8 X8 r5 i% [
5. l1 y# ~5 e) `, g) \+ Y9 u# t
66 I% j, `; w, ?& @
7" ]5 X& G- T4 ]" G$ c
8
" q) x4 i6 r! H( I0 c/ n9
( N3 n6 x1 ^. i; J5 C5 K10+ U' Z9 g2 t" \1 L. T" o- B* {
118 {5 `4 j' A; }+ e2 c7 D& ~6 F4 k
12' p) X2 R( F" m& p# a+ a7 x
13
- R; |; w( Q0 E& \& n对每克拉的价格分别按照分位数(q=[0.2, 0.4, 0.6, 0.8])与[1000, 3500, 5500, 18000]割点进行分箱得到五个类别Very Low, Low, Mid, High, Very High,并把按这两种分箱方法得到的category序列依次添加到原表中。3 I! `; {; e7 {4 R# |" Q4 G1 C
# retbins=True返回的是元组,第一个才是要的序列,第二个元素是分割点/ B$ `; s5 n" O* i& s' b2 ~" N) E
avg=df.price/df.carat/ K) W* B. v% K) }4 J
& E. @" ]) }' }
df['price_quantile']=pd.qcut(avg, q=[0,0.2,0.4,0.6,0.8,1],
. W0 y1 F. E* Q1 X% k labels=['Very Low', 'Low', 'Mid', 'High', 'Very High'],retbins=True)[0]$ ^9 e$ L$ n" j- }
! {% q8 w1 z& d
df['price_list']=pd.cut(avg, bins=[-np.infty,1000, 3500, 5500, 18000,np.infty],: Z; m7 t2 S% f6 K. T* ~
labels=['Very Low', 'Low', 'Mid', 'High', 'Very High'],retbins=True)[0]) o7 u- |, ^) h2 O/ N
df.head()
- I! e# y3 f" d. W: V! R5 T* N6 [2 ~6 y* S+ r( z
carat cut clarity price price_quantile price_list0 G! `, o- a: o& }: m' m7 N: R+ E+ N
0 0.23 0 6 326 Very Low Low
+ s$ T$ F4 o* `; j# g d( B1 0.21 1 5 326 Very Low Low
, k/ y ?/ B, `* M9 C" p q2 0.23 3 3 327 Very Low Low" _ o5 ~+ S' k
3 0.29 1 4 334 Very Low Low: q6 A, z: r* L
4 0.31 3 6 335 Very Low Low
1 f) x- A2 P7 E4 r, L0 \+ t K/ L, T
1+ O: [$ D, f$ l4 V4 z: y
2- ]) z) b$ c% ^ H' \2 b
3
# K$ _. l$ A% F) [* @# _( E2 D4
. M! [* x( S; o7 S* w: u5& ~( n1 \5 \. c& E/ W, R
6$ B9 J4 N) W0 `% K; _, M" _
7# J; E' G1 r" r
8
+ _% E: S; l' J$ S& Q4 o+ A5 J9- w* g5 Y9 S; Y& M: V" n, u- a
10
5 w# J0 g" T, b11
2 g! x* C: N/ @12 V: N; Q: W% i# M1 O5 l
13+ H# v0 \9 q! z8 u5 n" u$ O
14, K/ v* U# N/ e6 a G
15
0 q+ v3 r0 _2 E* @- r16. R) A' C- N7 H T, y" M8 y
分割点分别是:
& F% J4 N9 s; F0 w2 r
2 d3 o: s& m- O( ~" O3 s3 T) @, \" Aarray([ 1051.16 , 2295. , 3073.29, 4031.68, 5456.34, 17828.84])5 X* x8 S' R1 d1 ~
array([ -inf, 1000., 3500., 5500., 18000., inf])
' |& U c0 S. q U5 J4 F% e1) u: \' }" q9 S1 G4 I f' Q+ i5 K
2# N/ c1 O1 K2 U8 \, J
第4问中按照整数分箱得到的序列中,是否出现了所有的类别?如果存在没有出现的类别请把该类别删除。
( X/ J9 p) _" z H {. A6 d/ Jdf['price_list'].cat.categories # 原先设定的类别数
! h4 ?( {+ \7 W; o0 M9 _3 B# u, h. RIndex(['Very Low', 'Low', 'Mid', 'High', 'Very High'], dtype='object')
5 A2 u& Y4 H' f
! K* y9 U& _/ z( F/ ldf['price_list'].cat.remove_unused_categories().cat.categories # 移除未出现的类别8 C! E% `' n+ {0 ?2 ^: q; l3 K
Index(['Low', 'Mid', 'High'], dtype='object') # 首尾两个类别未出现" M% _: L9 |5 ?6 i6 \5 s$ j0 ~1 g
1- Y# M! x" D+ Z& ^: d! c8 ? q: B
2
5 v& e L* ]5 m( h9 v1 O/ m3
6 w3 _3 {; i4 X# x9 G. Z( l4* n, m8 X: F9 }. D2 g5 W, h
5
% E: J r. Z" }avg.sort_values() # 可见首尾区间确实是没有的
Y( T0 p0 S5 b1 b( C1 M8 x31962 1051.1627919 a) b9 |, c6 E E( a
15 1078.125000- f, Q! w& G9 D* h
4 1080.645161
/ q( @4 f. c3 Y8 n28285 1109.090909 x. U+ |; o! [! `5 b
13 1109.677419, p1 P4 B7 @* W+ `% z3 r
... 1 U& e$ U1 ~8 d* `- a
26998 16764.7058820 ~2 b7 F8 z# R6 {& E# ]; |
27457 16928.971963" j) B0 B O* @% r6 ?$ ^( N. v" K8 X! x3 Q
27226 17077.669903
T" B7 N1 N! X( k! V3 R. C27530 17083.177570& B6 Y- H- M! |& m
27635 17828.846154
/ K+ u( N, I( r1
8 c n2 J, I2 A# g+ J% S2 M6 k2! J5 C1 w$ q8 X! g: i6 _- _8 T* S
38 p0 n! _/ @. }0 `1 P( ~8 v
4
/ f6 o) T5 i- w) _ W5 O) ^5! n; i5 L( a6 r5 J1 P
6
: i' K3 G+ x/ M6 r/ T7& J& O }3 r( B7 u: M( \! D2 N
8
0 w3 \; M- x0 M% _0 d3 S& K9
( G/ q: f! ?, C3 U/ t8 G) q10
5 ^2 D) q: b9 W( A11" F5 v1 M z+ |2 F4 ^
12! z5 g r+ N3 L' x, p( F9 s- n3 x
对第4问中按照分位数分箱得到的序列,求每个样本对应所在区间的左右端点值和长度。
3 \' F( e" A* t4 r$ }# 分割时区间不能有命名,否则字符串传入错误。, n7 ^" V! `5 j" ?
id_interval=pd.IntervalIndex(
6 P/ X& a0 u6 j6 \/ w# ` pd.qcut(avg, q=[0,0.2,0.4,0.6,0.8,1],retbins=True)[0]/ F1 {/ [, N+ w2 F& h: F+ r" A
)
7 w' d! h- |. q. y' t+ C% N$ Bid_interval.left+ u* M3 z2 j3 C9 K1 g
id_interval.right
0 w; x* y0 `$ R# Did_interval.length
9 U; D. y% a: Q" G* u/ a1
2 E; Q* c( V3 a/ C28 ~5 N" e* t- b# Y7 K
3
' C) Y9 ?4 i" N$ m! `& G, _4: g9 w: m) V! C: x
51 {: @' ]* w( f2 Q. `
6
% b7 s. @* `# \/ k3 r' p+ q7
S/ p+ _. f+ G( [& \第十章 时序数据0 D N7 B" v) B+ A( Y
import numpy as np
6 E. c1 y/ ~" ^$ T# aimport pandas as pd
; j1 o. j1 L7 Y6 d. W1
[# s( J* L+ B. |/ ?28 i8 T8 U+ c W0 ~7 p2 j7 D
8 F% e( v; _. r, O0 `3 j+ ]5 ]- t4 f) H
10.1 时序中的基本对象! r3 x7 C. [0 j0 d) T1 ]
时间序列的概念在日常生活中十分常见,但对于一个具体的时序事件而言,可以从多个时间对象的角度来描述。例如2020年9月7日周一早上8点整需要到教室上课,这个课会在当天早上10点结束,其中包含了哪些时间概念?
5 u; p/ i1 I9 C2 I. A) [4 g$ j% G
会出现时间戳(Date times)的概念,即’2020-9-7 08:00:00’和’2020-9-7 10:00:00’这两个时间点分别代表了上课和下课的时刻,在pandas中称为Timestamp。同时,一系列的时间戳可以组成DatetimeIndex,而将它放到Series中后,Series的类型就变为了datetime64[ns],如果有涉及时区则为datetime64[ns, tz],其中tz是timezone的简写。5 J# ^$ O' O( E; @# Z- H8 Z
( e! T+ O* D( J会出现时间差(Time deltas)的概念,即上课需要的时间,两个Timestamp做差就得到了时间差,pandas中利用Timedelta来表示。类似的,一系列的时间差就组成了TimedeltaIndex, 而将它放到Series中后,Series的类型就变为了timedelta64[ns]。1 q' H" G- k2 E$ G. }& G7 e
1 H; Z3 R S& p3 X! I0 H
会出现时间段(Time spans)的概念,即在8点到10点这个区间都会持续地在上课,在pandas利用Period来表示。类似的,一系列的时间段就组成了PeriodIndex, 而将它放到Series中后,Series的类型就变为了Period。
5 y5 Q6 \: g, t2 U+ D0 r# f. j2 p1 `7 X: K, `" H4 L9 b; H( }: i
会出现日期偏置(Date offsets)的概念,假设你只知道9月的第一个周一早上8点要去上课,但不知道具体的日期,那么就需要一个类型来处理此类需求。再例如,想要知道2020年9月7日后的第30个工作日是哪一天,那么时间差就解决不了你的问题,从而pandas中的DateOffset就出现了。同时,pandas中没有为一列时间偏置专门设计存储类型,理由也很简单,因为需求比较奇怪,一般来说我们只需要对一批时间特征做一个统一的特殊日期偏置。 i# S) {( M0 c# n2 B
- N G% H( n0 j% K3 Z 通过这个简单的例子,就能够容易地总结出官方文档中的这个表格:+ h! C$ s" J4 g
8 Y# b, c1 H$ u/ w概念 单元素类型 数组类型 pandas数据类型% T" C0 Y9 K; \, a- L7 f, |
Date times Timestamp DatetimeIndex datetime64[ns]
9 R% d2 H. _5 CTime deltas Timedelta TimedeltaIndex timedelta64[ns]- @1 o/ p6 F$ c5 s3 r
Time spans Period PeriodIndex period[freq]
6 I6 N7 j% E3 K6 z) m# I6 X9 H3 L. `Date offsets DateOffset None None
* Y, b) H5 i: H4 m1 \, Y 由于时间段对象Period/PeriodIndex的使用频率并不高,因此将不进行讲解,而只涉及时间戳序列、时间差序列和日期偏置的相关内容。
$ o0 C" c7 u5 w F7 J( }# m
3 ^! `5 F: M8 {( w: B10.2 时间戳" D y, m+ M. m! k
10.2.1 Timestamp的构造与属性5 E* B5 y9 Q, _ x9 G9 ?' E
单个时间戳的生成利用pd.Timestamp实现,一般而言的常见日期格式都能被成功地转换:
8 J$ m+ e {! X. m) l% f& M& C
ts = pd.Timestamp('2020/1/1')& G# n( F3 W. B5 f7 j0 X3 f
) E. ^' J6 ?* O6 W, y4 X8 ?& T, @7 R
ts# r0 g0 `; r8 P/ W- | E6 S7 j2 d
Out[4]: Timestamp('2020-01-01 00:00:00')* \8 s) r3 |4 o$ V% d! P
5 O, C+ ~5 ~. ots = pd.Timestamp('2020-1-1 08:10:30')
# O2 a& Q6 h" Q4 ]
+ N, F6 w$ Y, u- r2 p7 d7 rts5 O& Z- O3 c% H& M- o' x- z0 t
Out[6]: Timestamp('2020-01-01 08:10:30')3 y- C! H5 g. m4 x
1
3 \6 O& x; y- v4 r; q2! b" q: a5 N, s! t% V% R0 f+ x# p
3
' M8 H# I( C/ D/ x46 o; T) d; w. V2 x1 Q3 N
5
* \6 w" B& Q' q; T6
# w$ X! b5 h. J+ v7/ f* I) F. s2 G" g: v% S
8
! ]/ B9 F, C; c$ X0 W9" o% ^, R% _2 i% I+ {: {$ D; \
通过year, month, day, hour, min, second可以获取具体的数值:5 t2 J% ^9 U0 N
2 Q( z' ]5 W- H% C9 B' [$ E: m- {ts.year
# S$ p9 L6 q) M! b( TOut[7]: 2020
7 C4 N6 }( m$ o! h; f3 r6 }
?( O' E7 }8 Z* q+ v( |! P5 d, bts.month- Q9 q& Q( N6 p% w- ~
Out[8]: 1* z& S) \ C% m6 l
5 G" `' ]# R$ E1 R( u4 i0 p: z
ts.day
' w/ U+ L; A4 C9 o' {! i7 ~Out[9]: 18 F! [+ m& @& Y% S( o* e
0 t" U Z$ }; n8 c) a+ [4 C( t
ts.hour
5 m1 D" q0 L2 ]4 q4 K- r% o, u% o# @Out[10]: 8
3 I1 j6 K) e% l, f- c# H) N1 J
* N( M" ]4 L1 e1 a7 w, Hts.minute
& l4 N" Y7 W2 @Out[11]: 104 Q+ f6 \; K' Y j7 x$ @
$ ]& u! Y7 o, u: t; o5 }, Q
ts.second+ s# @9 s+ K! N* Z* z7 M
Out[12]: 30
1 \# x2 H: H" J8 j7 u( D# l/ Y( [0 A/ j
1
2 e& m$ G0 g; s$ P4 b& ?2
3 }6 a' n) P6 F- b& n5 I9 F$ Z3
% D/ N# g9 N2 n% W& d4
4 {7 ~1 t: m# t$ x C( N5 U4 u55 A8 O! ?1 t3 V6 ^( T6 x. B$ Y7 u
6, n) Q" I3 v! [
7
2 O7 _! k5 P9 s! _! Y5 i8% O7 d# l- U: `( t
9; }5 b, j2 X% J1 V7 X* T
10
5 k* [# e6 A# R# E, Z9 Q* U; Z11
, I1 J. R1 G: ^% ~' ]1 W0 B12
n& n) O8 p$ Z$ ^$ W7 K: y* g13
! V$ e Z& d8 z. j* j1 _5 [14
: Y# f- U# Y. a: s! k( h/ c6 s5 r2 j15# r6 M& R; c2 O+ k. k. p
16
! N2 m* x. j; R) c$ B! P# z17
+ k8 B0 Q; E) ~) H1 v% k7 _6 Y# D# 获取当前时间; n: w; ]( D1 |& k- m9 h; D7 r
now=pd.Timestamp.now()9 Y j1 w: ~, v7 s! `# u
1
- e; p; O/ n7 R, |& l! S6 z! ~2
, Q" p: g, ~- {在pandas中,时间戳的最小精度为纳秒ns,由于使用了64位存储,可以表示的时间范围大约可以如下计算:( R2 G: Z. x' @4 R- _$ A
T i m e R a n g e = 2 64 1 0 9 × 60 × 60 × 24 × 365 ≈ 585 ( Y e a r s ) \rm Time\,Range = \frac{2^{64}}{10^9\times 60\times 60\times 24\times 365} \approx 585 (Years)( W0 W C- i4 `( `9 n
TimeRange=
8 i4 m/ s; R2 `) b$ B5 S3 P2 }10
! c# {! \: x- F; P) r1 c93 g) L+ }# \+ _$ E- O1 d3 N
×60×60×24×3655 r$ A5 h' E' w7 Z' E
2
- s- K- ^7 |$ g64
4 v, |% h. X! o' j9 v! z# [0 D0 n6 o1 o0 |& D7 r t% |! Q. o! m
7 v& ]+ g( S+ ?5 h) W, i/ f ≈585(Years): W& P4 _4 [% j0 b g9 o/ B3 q
; C% B: M3 p4 q: D9 p
通过pd.Timestamp.max和pd.Timestamp.min可以获取时间戳表示的范围,可以看到确实表示的区间年数大小正如上述计算结果:
( r6 Y9 Q1 |) b7 h+ J3 x' M
# E. T2 K4 W. s1 |8 Z( x+ Ypd.Timestamp.max) A4 z& j, b( \# g: n
Out[13]: Timestamp('2262-04-11 23:47:16.854775807')- _! c2 p, g M: X
/ \$ x3 j6 O3 @ T% X6 s
pd.Timestamp.min
/ h7 V* ?" K8 F9 z+ X7 EOut[14]: Timestamp('1677-09-21 00:12:43.145225')
4 v# {6 _3 F3 C1 ?7 o. E
: s {5 z: x9 X' B8 Q2 Ipd.Timestamp.max.year - pd.Timestamp.min.year: G5 R u4 J8 s7 R8 N5 x8 U
Out[15]: 5853 L+ W4 p5 n/ L
16 f0 S4 R2 S+ `! t) m5 f( w F
2
8 V6 y$ @/ i& m! U7 M1 `" @6 E3
' b4 p& i- {2 ]5 N% b7 S4
4 o7 c) X7 j7 U0 Q9 Q; ^; u5$ ?$ B1 y& R: N! i2 F% _- y( ], F
6 O+ b$ j6 N [5 a
7
/ Q5 T6 i# V `. m8) X: ^9 e+ A% ?) p3 S/ E0 }
10.2.2 Datetime序列的生成3 ]$ b2 Y# m- c$ w# F
pandas.to_datetime(arg, errors='raise', dayfirst=False, yearfirst=False, utc=None, format=None,
+ \/ |* X# `8 g$ l+ C% ~5 D exact=True, unit=None, infer_datetime_format=False, origin='unix', cache=True)( M/ {, J1 { i$ N: z. E
12 p* \% F1 c" ~4 S/ N7 Q! ^3 _
21 w; Z2 h6 r( G& ]+ Y
pandas.to_datetime将arg转换为日期时间。
9 H1 [- g3 M3 {: K% [: O* D A2 h# o5 {9 @$ I! c
arg:可以是argint、float、str、datetime、list、tuple、一维数组、Series、DataFrame/dict-like等要转换为日期时间的对象。如果提供了 DataFrame,则该方法至少需要以下列:“年”、“月”、“日”。 y, }2 y1 | Z# T. X# \2 K3 {
errors:
# U2 [4 G- \( b! D0 y- ‘raise’:默认值,无效解析将引发异常$ ?% x; x8 [& `* z; q
- ‘raise’:无效解析将返回输入% B3 q# \* W* \- ~
- ‘coerce’:无效解析将被设置为NaT3 o9 q+ x0 `9 G4 R
dayfirst:如果 arg 是 str 或类似列表时使用,表示日期解析顺序,默认为 False。如果为 True,则首先解析日期,例如“10/11/12”被解析为 2012-11-10。如果无法根据给定的 dayfirst 选项解析分隔日期字符串,会显示警告。3 }7 U; | j8 I+ U. H# e
yearfirst:如果 arg 是 str 或类似列表时使用,表示日期解析顺序,默认为 False。如果为 True,则首先解析年份,例如“10/11/12”被解析为2010-11-12。无法正确解析时会显示警告。(如果 dayfirst 和 yearfirst 都为 True,则 yearfirst 优先(与 dateutil 相同)。)$ G1 g0 K+ h$ }7 i7 o3 }0 O' N) H
utcbool:默认None,控制时区相关的解析、本地化和转换。请参阅:pandas 有关时区转换和本地化的一般文档! \! z& ], M( H8 I
format:str格式,默认None。时间戳的格式不满足转换时,可以强制使用format进行匹配。
0 |7 b: S6 I- ~. \- j, g E) f; }( v6 funitstr:默认“ns”。它是arg (D,s,ms,us,ns) 的表示单位,可以是整数或浮点数。这将基于原点。例如,使用 unit=‘ms’ 和 origin=‘unix’ (默认值),这将计算到 unix 开始的毫秒数。
6 p# T! L2 y7 m# Wto_datetime能够把一列时间戳格式的对象转换成为datetime64[ns]类型的时间序列:6 p2 G/ E6 s- a( ]0 q4 P& K# }
pd.to_datetime(['2020-1-1', '2020-1-3', '2020-1-6'])5 B; b; U( d3 S- a' O
/ ~1 V* w3 h+ `; Y" m2 M
DatetimeIndex(['2020-01-01', '2020-01-03', '2020-01-06'], dtype='datetime64[ns]', freq=None)
* G1 F; `3 E: p1' Y2 \0 b% B% w
2: h/ B' {7 x y* @+ H' _
3% n' p2 x! J5 M0 ~
在极少数情况,时间戳的格式不满足转换时,可以强制使用format进行匹配:* T$ F6 V3 o$ H0 C) C" e
. M7 p/ u/ ^: z& C
temp = pd.to_datetime(['2020\\1\\1','2020\\1\\3'],format='%Y\\%m\\%d')
8 @; t) z2 C7 r. a! f: ^0 @5 K1 T' gtemp _# q5 e8 L: e; w9 I R+ h7 Q
+ o; a5 H0 s# T, P6 U1 C$ yDatetimeIndex(['2020-01-01', '2020-01-03'], dtype='datetime64[ns]', freq=None)1 t# U* W1 |) Y1 D5 [9 ^
1
+ ?6 g' K* H c2. R0 a- d$ \0 k* b) P8 O- s B- {
3& B) m& ]. G" r) X8 ]
4
+ Z: ^8 C3 G5 v$ ~) k! F 注意上面由于传入的是列表,而非pandas内部的Series,因此返回的是DatetimeIndex,如果想要转为datetime64[ns]的序列,需要显式用Series转化: e9 D; g1 p6 a7 H& p8 f. ?
, P, ~) P% T$ `/ E- l9 Z$ B: Apd.Series(temp).head(): y9 [. T3 d; O. L
: i/ f9 t+ S2 V+ E9 L3 U
0 2020-01-01, V, U2 E* F2 W/ g& g
1 2020-01-03! f, @! p# J+ C* I
dtype: datetime64[ns]
2 C+ N4 k% ]+ s0 y, A3 ]( M1; k! `+ z9 g* R, X: _6 ^% ^
2$ l9 g- ]. v0 Q) o; }7 o
3, ~" A6 v. n) w8 p9 K8 ^+ `! m
4
# \" R1 b' B4 z6 P( v54 F: `: o/ W$ a; I$ U5 t9 f9 Z u
下面的序列本身就是Series,所以不需要再转化。
2 E/ ]4 P( L5 L
4 U0 \; S% e7 h6 P5 A+ wdf = pd.read_csv('../data/learn_pandas.csv')
: r* w% d3 c) G' B Js = pd.to_datetime(df.Test_Date)
2 L! C( V, s: B8 l M. `: xs.head()
* a. H' Z' q4 ^5 q! H
6 ]" X0 a( C9 ^) J/ n0 2019-10-05
4 W* \+ M- ~* E/ i$ q9 U3 I9 A1 2019-09-04
6 M2 s5 g% k' _6 h8 ]) r2 2019-09-12 {. y5 N0 e# K8 f7 H
3 2020-01-030 A8 A6 }9 i2 ?$ s
4 2019-11-06
4 m; t5 o) }" r. y% \8 q6 F) I# d2 DName: Test_Date, dtype: datetime64[ns]" x' F6 x7 b1 z6 a- K6 ]0 Q3 ]* O
12 k9 w; w: a) o
2
7 L. n$ u$ e9 z/ j/ D3: S- [5 d( B0 B# L( E
4
: \& k+ @3 b. c0 E5$ L' O' }+ r0 c2 y2 x) W
64 y/ H/ A- y" l* s7 c
7. U* i+ Y$ m: m+ u4 M" f2 [& [
8
+ ?, C8 I0 K7 u3 G; b9
" l. n: o7 u( @% j9 }( R10
3 Y9 M3 ?1 K) ~2 _/ m- P把表的多列时间属性拼接转为时间序列的to_datetime,此时的列名必须和以下给定的时间关键词列名一致:
6 h, @/ {* p' ^( v6 ^: Ldf_date_cols = pd.DataFrame({'year': [2020, 2020],
: b6 }3 F" `3 G: h 'month': [1, 1],
8 b) `1 w$ P* O# V0 q 'day': [1, 2],
" q) N+ G* w4 g$ _0 R 'hour': [10, 20],8 [$ R N& e+ Z* m( k
'minute': [30, 50],& k: A" Q( n( ?1 q% n
'second': [20, 40]}); ?) |5 l7 |9 ^2 g
pd.to_datetime(df_date_cols)
2 V4 {, O$ {% }6 H, ^, x- d2 B3 e: B
0 2020-01-01 10:30:20
! \8 X# Y$ S% K5 x1 2020-01-02 20:50:40
7 E% ^2 D9 V! t* W( @8 gdtype: datetime64[ns]2 A: M! Z/ b7 G& O ]. v
1! b7 k+ G0 |1 O5 z; [' Y/ S
2+ K- z) z' L9 b; ^1 H, s3 y
3# i4 k+ z! N: i. }: s
48 J) R! B: q$ q; C
5* o+ Y/ q: `- F# p
6
, d0 y( O6 I0 W: k7 S: N/ J7. \0 e4 @9 i4 q- v4 W- v) j! q/ v
8
* }6 h1 Y/ g! n+ b/ u9; t4 _! a+ D9 C/ ]0 R2 A
10
: B3 o9 @& C/ u9 k# c2 ~- }( N11
6 ~5 a ?- B. I& q' [ d, S9 \date_range是一种生成连续间隔时间的一种方法,其重要的参数为start, end, freq, periods,它们分别表示开始时间,结束时间,时间间隔,时间戳个数。其中,四个中的三个参数决定了,那么剩下的一个就随之确定了。这里要注意,开始或结束日期如果作为端点则它会被包含:" I/ L/ P A8 J4 r- o
pd.date_range('2020-1-1','2020-1-21', freq='10D') # 包含
9 c- n' }" u1 Z# |0 U: J( h* B5 zOut[25]: DatetimeIndex(['2020-01-01', '2020-01-11', '2020-01-21'], dtype='datetime64[ns]', freq='10D')" K0 P7 f% Q5 } D' k
2 e! ]: z0 D6 k7 `* \- Epd.date_range('2020-1-1','2020-2-28', freq='10D')) h7 M% ^8 C% f, ~& `6 _6 |# r
Out[26]: & `7 e+ W2 E: Q3 x
DatetimeIndex(['2020-01-01', '2020-01-11', '2020-01-21', '2020-01-31'," \0 @+ r/ J& k8 R$ R
'2020-02-10', '2020-02-20'],' |) p$ ]; n3 O( P* P6 w9 F
dtype='datetime64[ns]', freq='10D'): Q% [4 W* @1 m. O1 V" v
- D8 X" p& b4 E: }# h" @pd.date_range('2020-1-1',
) z4 M. h& \4 ? '2020-2-28', periods=6) # 由于结束日期无法取到,freq不为10天
, D! W& c" [6 Q* u! P( @* v, w
E" V( J4 S2 c) @5 Z, q5 J! QOut[27]:
" ~7 S% H1 {5 v0 VDatetimeIndex(['2020-01-01 00:00:00', '2020-01-12 14:24:00',* [% h* O+ ]* h# l$ T9 C/ j, ]1 d
'2020-01-24 04:48:00', '2020-02-04 19:12:00',, m7 C: W, |1 C: Q) f9 t
'2020-02-16 09:36:00', '2020-02-28 00:00:00'],
: u7 s/ a4 f; M! r. D' d dtype='datetime64[ns]', freq=None)+ _/ a/ b8 o1 X3 Z& [
3 I1 l, d$ ~) F" I
1
; {7 p4 j3 v! _1 |+ s4 N2( e% i+ ^7 O7 g1 A* l2 `
3
' O( v9 s& }' V) W! a4
5 f% B$ K7 h9 u9 r: b5
) T( j( Q" f& V1 ~; ~; [6
& a0 g7 l4 |" { ]2 m7" A9 s% o" D5 p8 u- q2 b* u
86 U. a/ S. B& K' H' l! R6 j3 u
9% s% O" ?+ S+ p' G% ^
10
8 h0 }+ ?9 Q9 L0 o3 J115 X4 V4 ~- Q. n f1 G
12
$ ~8 Z" O+ d2 A13" L1 Y6 B, F* \1 V* i
14* j8 U6 w2 k. S1 |: \- g" V
15! Q- d* o6 M+ H9 k9 M- ?
16
+ K4 X5 Y. U: e176 U+ r" J5 U# h# `4 p/ X1 H( b4 v
这里的freq参数与DateOffset对象紧密相关,将在第四节介绍其具体的用法。
3 |( S% P2 g i6 P& L; F( Z, \3 I
) ^7 H) v v1 {2 K+ f2 s【练一练】. w6 X |: P, h% `
Timestamp上定义了一个value属性,其返回的整数值代表了从1970年1月1日零点到给定时间戳相差的纳秒数,请利用这个属性构造一个随机生成给定日期区间内日期序列的函数。
, c- u- ^0 B+ R0 y% Q/ T
. v3 C2 W9 v2 L/ J' ^1 als=['2020-01-01','2020-02-20']
! M6 v7 \( H1 F& a" {def dates(ls,n):" Q) ]$ C( |8 ~
min=pd.Timestamp(ls[0]).value/10**9- d% z' \5 N# R8 r' i7 m# r
max=pd.Timestamp(ls[1]).value/10**9
, g2 d8 |* ?" n8 _; Y9 @. ? times=np.random.randint(min,max+1,n)& b+ C7 b- l1 \( y
return pd.to_datetime(times,unit='s')
! `/ `$ c* g9 {4 T2 L$ kdates(ls,10) ~) ? E7 w1 W5 K
% a5 ?7 ]( c5 i8 {! A# i5 L
DatetimeIndex(['2020-02-16 09:25:30', '2020-01-29 07:00:04',; S) X; ?% e; l* E2 b! O' X# t
'2020-01-21 12:26:02', '2020-02-08 20:34:08',
3 Y% U1 J9 P- R8 [# Z( M' ` '2020-02-15 00:18:33', '2020-02-11 02:18:07',% [7 F+ ]& _3 ]' e
'2020-01-12 21:48:59', '2020-01-12 00:39:24',3 o L0 L7 T$ I; u
'2020-02-14 20:55:20', '2020-01-26 15:44:13'],
( X$ p: a$ a; A; i- s dtype='datetime64[ns]', freq=None)2 L2 a3 q* @: w
1
" n2 w3 ?$ p0 j# U8 Y2: _3 [; u6 J6 v1 u
3
3 Q! v1 L7 m" ?# n6 ? M4" b+ }1 F7 ^8 o4 k! j8 H
5
7 `6 F: }& B; C2 _63 a6 ~, T+ m3 p* n) C* t" ?0 K5 Q
7$ N5 O; \4 _) I) p. {
8
8 @3 z8 ]8 ~8 ]3 |. F; ]95 A A. `8 e4 |) X2 `
10
7 m3 k2 i6 i% }2 I P2 u2 T11# K- W) ?# A9 a
12
$ F0 I4 q2 @5 l: v( O" Z& [$ ], k' l13
; p2 `. ]( q; z# }, M14
6 ?5 \! ~- ~' r- G% B) A2 \1 G2 Casfreq:改变序列采样频率的方法,能够根据给定的freq对序列进行类似于reindex的操作:4 d6 K+ c5 m ~
s = pd.Series(np.random.rand(5),
( ^9 {# @& C) ]9 e( ?% r index=pd.to_datetime([; [( O9 B8 S8 s3 s+ d" s5 f4 Y: @
'2020-1-%d'%i for i in range(1,10,2)]))
/ N$ m4 R" F7 v
3 s6 j/ R/ j0 [6 x% h* b. Z8 d4 ?( J, o3 }( g
s.head()
- u( a4 C# s4 h" ]% P4 o' S# s( ~& KOut[29]:
' |; E. n" F- S% R2020-01-01 0.836578
7 Q) H3 D. j! \2020-01-03 0.678419
( i/ n: J% a* b5 }2020-01-05 0.711897
! @3 Y: o+ B, r) E6 u7 w2020-01-07 0.487429. C9 T7 A6 }7 e( d$ t! A* `
2020-01-09 0.604705
+ ~% L0 m! |! x% hdtype: float643 z# T+ h. C4 i, ~
* ]) L; t2 l1 [. N5 Cs.asfreq('D').head()1 A8 z* E4 I- j g! j" d; V. Q
Out[30]: $ \! E# H& n* W) A( _
2020-01-01 0.836578
7 s% G. j. Z/ |) [2020-01-02 NaN. V. o0 J' T. _/ V
2020-01-03 0.678419: d5 b9 `; D9 b+ T0 [0 q# z
2020-01-04 NaN) A( l) t" D" [5 U3 Z# Y; h- V4 e
2020-01-05 0.711897$ m1 \7 U, r( t
Freq: D, dtype: float64
* s( H) x9 M7 x' p6 N, s* C& x1 _
! p4 M+ t$ \ i" Q* a% W5 ?; _; ys.asfreq('12H').head()( ]- L7 @$ B/ N- N
Out[31]: 5 v" w( \: [; ~# H% l
2020-01-01 00:00:00 0.836578
6 n9 L' `7 e; c* L4 Q2020-01-01 12:00:00 NaN
( t1 u4 J* P: t6 _' b+ k) S! }2020-01-02 00:00:00 NaN
5 X$ p) ?3 b8 A9 I' b2020-01-02 12:00:00 NaN
: m* |7 s; T$ N4 H: I+ ?. l2020-01-03 00:00:00 0.678419
8 _" D7 B) b# d* o- z! mFreq: 12H, dtype: float64
# [' z6 ?- r4 c7 ]
, q: F L9 m I2 l1) x# @; O& _& b" I& `/ z
2+ {, g2 Y! H1 M% t. i
3
5 W. P2 o9 `; L- v) n8 B4
2 W: n% a9 [0 y4 T5 M2 G! u5! J9 ~, a" B: ^
6; k9 e1 B- U c2 H( b6 t- q* \. n2 |
74 n8 ?& u6 [, n0 y/ y) V H
8
b& y q' g7 Q7 w6 ]0 m7 z- |98 _3 }! f. F! q4 E- [$ r
10
# [( p3 U- _/ ]4 ]* L; Y( |7 o11
+ Z, m5 S+ }: u12+ ]" @9 }: C0 W) q9 c
13' x4 ^! o$ G3 k( O7 [; _2 Z0 j5 M
144 o' I7 J9 G) |. _
155 t/ Z# ]' h6 \, B1 q
16
( N. X: P; `& E2 H6 l4 P8 c$ ]- f3 `17
4 J& `; y8 ?% ~7 w2 p180 A, Q* N8 w; a
19
. U0 A9 F; i* }# L) e20
$ `: Z2 v# d4 s) M! p, }218 B* e1 z, B+ n7 z: }
22
7 W1 Y; p, t$ j% w23
" S3 U% f8 J# u24! j/ i; G/ @0 c+ q9 }1 t
25
' n& u( u" c+ M8 X26
2 t6 U; j( x3 b2 f27
' p% Z8 D; A' C' Y! |8 a28 _ P$ K- G3 @ C
294 M; E. i" q6 s. C5 c7 V
30% C Y6 V7 Z6 R! c5 `, r; t8 {
31- C" W' }4 I! g9 `
【NOTE】datetime64[ns] 序列的极值与均值 q, e& T. r) H7 I" e& \1 e, P) D
前面提到了datetime64[ns]本质上可以理解为一个整数,即从1970年1月1日零点到给定时间戳相差的纳秒数。所以对于一个datetime64[ns]序列,可以使用max, min, mean,来取得最大时间戳、最小时间戳和“平均”时间戳。
" z8 @; N7 N4 \
$ M) W+ \, s9 ?3 p% q! n10.2.3 dt对象
7 I1 d6 ]- B7 ?( z5 j3 l 如同category, string的序列上定义了cat, str来完成分类数据和文本数据的操作,在时序类型的序列上定义了dt对象来完成许多时间序列的相关操作。这里对于datetime64[ns]类型而言,可以大致分为三类操作:取出时间相关的属性、判断时间戳是否满足条件、取整操作。
5 c1 I+ }: A5 `) ^2 `
- p* T3 D' J5 s% _第一类操作的常用属性包括:date, time, year, month, day, hour, minute, second, microsecond, nanosecond, dayofweek, dayofyear, weekofyear, daysinmonth, quarter,其中daysinmonth, quarter分别表示该月一共有几天和季度。9 ^3 r8 f$ ?- r4 v S9 k
s = pd.Series(pd.date_range('2020-1-1','2020-1-3', freq='D'))+ X! P% ~: C( ]" V# i
$ A3 K- y E! k5 ?. o' k
s.dt.date
! A- h. v* A2 C" V" ^# c: ~5 EOut[33]: , h ]$ l) ?2 t1 J b$ h
0 2020-01-01
+ ]9 L; t, e0 n4 T* g$ N1 2020-01-02
" `# X! u4 E: D1 t, U* y. T2 2020-01-03; z/ S6 e! A" f/ y6 Z$ d5 s
dtype: object* m( w. s8 U H2 @. N
( s& f5 y. F- u$ K; w7 f3 As.dt.time
- Q4 Y' u, G3 }) `: EOut[34]:
- u7 w" Q/ F2 I' w U0 00:00:00
6 ^% ~2 P$ Q" T) d1 00:00:00% w% c6 ~% ^( X5 B5 O
2 00:00:00/ G9 |: o% e/ P# [. G0 g
dtype: object- w7 `& q0 I0 n- U5 |
# Z2 t4 \0 z1 L% Ks.dt.day# l9 }2 w" L1 e( i. i0 X
Out[35]:
' D& P7 w l0 X0 15 p1 l- ~ e+ ?5 j9 c' ~+ R8 x
1 2: o/ J$ f+ _) y8 ]8 j8 f
2 3
! H$ {! B+ x& k* tdtype: int64
Z2 r% e) r2 j# }- X! {' d$ g8 s% j5 B% d: f. a8 V% { \8 {8 J4 B
s.dt.daysinmonth+ s/ Q# m( R, e" g" E. c' H
Out[36]: 6 ^- f! f$ `9 ?4 U+ J& H
0 31
. \8 J9 C3 a) f; j! z1 31
: I# a+ T. {' Z: A* H K% Y( \2 31
0 M- U+ g1 }/ ]' Q! _6 Z5 Ydtype: int64) { e" I& H0 i3 L* h* m
?' O' x% i- K) {: s* x/ O
1
+ c. G+ H) m0 }& Q2 L4 d7 d/ u) V2; s$ {$ I5 y Z! R3 w) V
3
; G [4 y5 z3 w: Z0 a2 T/ M4
* p1 U% J# _" b57 T1 |1 G5 x. M6 n4 r* Q
60 T! ^! t2 d7 b) @0 x
7
4 y5 z* Q& Z z" X, s; y8
5 Z* _% b' s0 O$ d- @ C9
2 e0 b2 V5 Q( J6 Q! @% j105 O0 w) s/ L- ]1 E( L
11$ z8 d6 e2 A4 x; z
12
( M* c7 J4 k" e! u9 t% U13+ G- T) O' e, H4 Z/ F3 D4 h
14
. L- T0 Q& D; j151 `* |4 P1 F9 v) Y
16% w. b2 O4 u; p0 o( t; K: p
17
( T6 W% M6 l4 e* O5 y. n182 S/ W2 Z8 c) a; E, A% H) k9 h: F/ G
19! ^/ j" ^1 R( o8 ?5 O6 G
20
' F4 S( w3 n3 s1 `# b6 O218 Q) n5 A" F& |* K' \6 w
22
1 U% N0 L+ Q+ ~, B23
3 _8 k) A+ T! |9 k/ L! ^24
0 i9 u' i& m8 y. G- Q: z25! X# p; b) {+ _" q z
26
/ g1 y) [' \# T5 W+ {27" {- Q1 H' z# C: a6 g$ T8 W! ]0 n9 Q
28/ n0 i# i) {( F& E2 _7 q3 N
29
+ j; [6 z: d0 n, ], d* R5 n 在这些属性中,经常使用的是dayofweek,它返回了周中的星期情况,周一为0、周二为1,以此类推。此外,还可以通过month_name, day_name返回英文的月名和星期名,注意它们是方法而不是属性:8 j( ~% r8 p* E* i$ P' f7 D% E
; }1 _, F% m$ |, a
s.dt.dayofweek( x0 E3 ^1 `1 R0 f: {2 b7 u
Out[37]: 5 B. a0 P) Y* d! L9 k
0 2
8 h+ |+ F E) P2 t1 3' N$ X: U+ i) z+ o! l
2 4
6 z- h) s+ Z- L( h' `8 u" {( o- Ydtype: int643 W& z- I. N& i! r( c0 B4 Z4 Y9 [
. D' _% w: S, S8 [4 i% Y
s.dt.month_name()' i$ _& U* b" V! I
Out[38]: 6 l( M" V* P- ^; r# {- S$ O5 G
0 January) I, a @4 R% c9 b
1 January
/ y" O( K" B) N7 n" s, ?. `2 January
, g, H$ K I9 z& edtype: object. H8 g2 U; }& v; b6 j
2 `3 F$ p3 H8 I6 Ms.dt.day_name()' E5 k& C1 z1 N. L. I1 z8 ?; l
Out[39]:
1 M# F. \% G. m& M7 j0 Wednesday
# o$ y- o- Y" i: F1 Thursday8 _2 B1 N: N5 a, K. ^* V m
2 Friday
/ ^, t- g* q# G v1 P' Wdtype: object' m) A" M: D: U( _; }: ?
* _1 f2 l: [! n
1
* b# }: W. k4 e* K7 A2& ?* x. X. H- h4 H
3
/ T2 P7 a2 H1 m" T2 S, S! R42 a1 g8 Y2 z" v; K1 t. c
5
8 Y: g% R6 \( B& q4 w6
9 ~' ^; Y# `# C5 n/ ?7
# Z2 c7 k" P+ o2 N( d8
+ g) Q5 \% _/ W4 D& _& o9
0 I, m9 Z2 j- S( B10
* v& o0 i7 o- l118 }* V# ]2 j/ I
126 p2 w# E9 I7 x3 w" P
139 a8 Z# g- n, E( w
14
' X( j$ m i! V" g, \/ q O15
( j1 x$ G+ v3 y, Q+ g0 }16
& Y0 W- d \8 ]; ?1 p8 g9 F17( B1 s: D N, w7 d# Z8 j2 V$ ~5 L
18) P- X: T1 w" Y
197 X" {5 ~ s0 u, E5 E7 g
20& N# \+ U3 B/ |/ ^1 p+ j
第二类判断操作主要用于测试是否为月/季/年的第一天或者最后一天:' J! Q9 D! p. d ], v" I
s.dt.is_year_start # 还可选 is_quarter/month_start
" H9 N) v3 k- j, X. QOut[40]: ( F3 V; c* h9 a: k5 [2 S& x
0 True& X9 G& Z3 S5 o$ F) ^: n
1 False
$ U) ~" Q9 W' |* b3 N2 False& t3 X o; }4 P/ {5 A) I
dtype: bool: c; \* r9 [ j% H; |
/ A* r" G& M. C* E) Ks.dt.is_year_end # 还可选 is_quarter/month_end
F# ^4 B8 s9 `4 N2 V5 ROut[41]: ( @1 d7 A0 ~7 }, ?
0 False& \' W% g' q: U
1 False2 [9 b$ H8 w1 S
2 False# m8 @$ o; E& ]5 S2 e7 _
dtype: bool
, x& c4 ]" h# M% J$ o" l; \1, B4 S; X& }4 F* C
2
! W) ~& S1 f _ p* E3
r+ H5 l; u( h1 i- ^3 \40 t7 T0 a Y5 m. E
50 r, J/ O# A2 v
6" e0 }' |# K% a9 p
7
7 E, u8 @0 r. H' |/ P* J% }8
6 j y: A5 F& `$ ]& v/ F) n9
8 r2 F$ @6 }; y! z10* _+ e$ n1 a5 ?0 p
11' H8 V6 u( X5 H) A; i
12+ d! [3 n8 ?( H, y) ?
13
( |) j# Q# l2 P# G第三类的取整操作包含round, ceil, floor,它们的公共参数为freq,常用的包括H, min, S(小时、分钟、秒),所有可选的freq可参考此处。( V- ?6 r D4 B- d
s = pd.Series(pd.date_range('2020-1-1 20:35:00',0 r/ M4 [) w3 ^( p: H
'2020-1-1 22:35:00',
; y6 C( ^& K( _" I6 M# P; m freq='45min')): L. \( k/ A) Q% z, D P
% f8 p3 b& a; r1 Z% Q& R2 K+ L& v$ d, A( h7 s ?
s! L& @/ Y2 S; K
Out[43]: , d$ v- j( m8 X6 a' B2 X
0 2020-01-01 20:35:008 j( u) q; Y5 `- O$ z, X
1 2020-01-01 21:20:006 @6 E% O$ z0 `6 K. }" i( y# H! m) g* e
2 2020-01-01 22:05:003 r2 C$ k: a1 j/ |0 U7 B
dtype: datetime64[ns]
* r( {4 q% }! L; \/ i5 y: t
5 I4 ?0 W3 V: q. H5 ps.dt.round('1H'); W+ Y8 i- r) c& _8 l
Out[44]: 5 K# J4 [4 n" W3 R2 @& O
0 2020-01-01 21:00:00
- t' |1 w1 ^' H3 ~+ Q1 2020-01-01 21:00:00
) S6 K; M: Q. @. K& u9 X2 N; u9 m2 2020-01-01 22:00:00
1 Q4 _* R$ Z2 idtype: datetime64[ns]8 E+ k+ `0 g3 v2 ~
1 l' y0 s% v! I6 t8 b1 Q
s.dt.ceil('1H')+ [: z2 D$ f! ?2 c
Out[45]: 6 o& S4 B3 e; n( w' e- `5 i/ N
0 2020-01-01 21:00:00
4 |3 S" \' E G; O5 i1 2020-01-01 22:00:00/ w! \3 P! G# s9 [
2 2020-01-01 23:00:00
0 s5 H$ ^: J1 u7 Zdtype: datetime64[ns]
0 Y8 J5 c: U( `: y' a
+ h6 t6 W/ I$ O8 xs.dt.floor('1H')! I' c2 m/ }8 N3 ]
Out[46]: 6 l2 C! k% }7 e
0 2020-01-01 20:00:00
: k s S! [$ S# d s5 N1 2020-01-01 21:00:00) l" A1 m. A$ u+ E
2 2020-01-01 22:00:00 R) O0 R, `9 f8 s
dtype: datetime64[ns]
2 F% u; d0 M6 O; E8 c
- ], H G5 J( e1
9 p8 P4 H1 Y- @* l! A9 L# t" |/ ]2' G: `1 u* M1 ]1 Z8 Q: R# i D% h
3# g/ g6 Q4 C( a) `% C
4+ W/ d; |3 J: N: h& Z
5& @* _! H6 Z2 Z" C6 |* y
69 t) q: o6 \( U% N0 i' q
7$ A( D, R3 n5 z: G
8( c0 J# V2 s! _' t$ `" z
9
( E2 ]& B3 c' S9 E8 a10
0 R3 x# V1 K# P/ T' V11
" x C# l3 {: n- }( a/ j124 [% X: X" g3 s2 X, G! \! O
13( D; g P: O. h3 z
14
: I6 _2 \5 k! \9 {/ ~8 D. S15
$ _3 E2 J/ f, K% e3 N& V" k1 |16
`1 ^; K3 |9 `% @$ x; E# H' |17% @/ Z+ ]0 w4 o9 {
18
6 v+ _7 g6 U3 w( A/ e5 a19
A# _) m# \/ K: T0 h20
3 C) M, A! K/ ?' Y211 h: |+ T2 d) r# c8 G6 w) Y; w
22 @3 l) w) d( I4 I. g+ x, n5 A
23
, ?0 x8 I, s6 a3 e24
& `; y1 o+ f7 `( Z25
/ a% R$ d! C" ?/ L' c& R26) r. Z2 Z. p+ q5 L8 ?
27/ y7 Q6 p, C8 q
28
; d0 ~: e! _" E6 m$ Z y4 P$ A$ O290 C& g, l( i2 ?& R1 u E
30
- j8 [* S! c/ |- i+ d$ e) M$ g2 ]6 j; T31
4 x( ~+ I! B# n) P/ c0 ?32
% e8 M0 [5 C. q, o. D* w9 q10.2.4 时间戳的切片与索引0 N/ U5 u$ F% H$ G6 ]+ h# O5 y
一般而言,时间戳序列作为索引使用。如果想要选出某个子时间戳序列,有两种方法:
) q/ t' j Z0 L+ P) W1 O. q9 C) ]" T4 g* K
利用dt对象和布尔条件联合使用" G T/ T1 _7 E/ L
利用切片,后者常用于连续时间戳。; q& Z5 w# `, A. N" h
s = pd.Series(np.random.randint(2,size=366), index=pd.date_range('2020-01-01','2020-12-31'))$ f8 {9 r$ R9 k" M4 _% i* Q
idx = pd.Series(s.index).dt
+ j; Q: N* R% l- i, ?s.head()
( @* ?, g S2 z- R
7 Q* h) e6 U; h0 ^& w2020-01-01 08 M4 Z9 m( Q9 _
2020-01-02 1
B+ [( e7 c7 v- M! ~2 E3 o2020-01-03 1' P* C1 W& F/ o5 b; o( r( W! k
2020-01-04 0" I- F$ R( J- @+ ?: a% |
2020-01-05 0
" v. d' L- @$ }" x4 i$ O. g ]$ j. yFreq: D, dtype: int32# M7 H( G6 _% t
1$ s% S2 O* e' O' e# [* c0 o7 S
2. l w4 ^+ A# G! a8 B
3
: h; Z! p. ~( y+ H' I$ V& B4
4 y2 o' D3 ~. M% w i/ S5( E& C2 T6 `; Q0 X# {. ~
6
) L, s+ e' T4 f' x+ d! ~+ s7' F* }2 S3 b) |) p' T
8
, Z! I$ z% v( @" w* @/ t9
r6 y; K- j5 A101 b- H) j3 j' V. K5 D3 v
Example1:每月的第一天或者最后一天
- O8 a3 ?. k) W! O( _# k: e# X8 l8 q* T5 ~( R
s[(idx.is_month_start|idx.is_month_end).values].head() # 必须要写.values
( y; k$ f1 r, U0 [( U6 f/ A9 Z* iOut[50]:
/ B( |6 p9 l& {! F9 l, l2020-01-01 1( C/ g1 }5 m8 e
2020-01-31 0
( n( G6 U) s& k: c, y! ]2020-02-01 1
1 h4 y7 Y A& D! ^- J, e" f2020-02-29 19 A/ j) @7 H+ }3 R8 L
2020-03-01 0
, F' K5 H( v V3 Q6 Fdtype: int322 O6 f4 k% G2 G% c7 j! U
15 w% t: N8 V+ z) ^. X
2
$ D5 B! Y6 ?% m% `3. y" j; W' k# X5 x
4
! k9 w% A3 e- ]" A2 E5
! [. z, x$ N9 n! I9 H* \. {. [6' j' L0 C2 v" h" _; L. j
74 N% {# e% M$ `% V' g) {5 |1 q
8+ H" A2 S7 a* B8 p$ P
Example2:双休日9 d) [* @/ ^: {' }- g
; t1 r6 b' z. { m/ z
s[idx.dayofweek.isin([5,6]).values].head(); L0 {6 |0 B1 n' I5 h5 h9 N- d
Out[51]: 0 Q5 Y+ U! N N: H% ~
2020-01-04 1, _1 y. B* H7 R& V$ Y' R3 ~ i& y- s
2020-01-05 0
' y9 r( f% }0 F2 F1 a4 z; o9 B6 x2020-01-11 07 Z1 h' C: s. j, b+ w+ I, M- l
2020-01-12 1: o5 I1 U4 K+ l" C
2020-01-18 18 j# |- D) V' z% A1 v
dtype: int32
; ^- z9 ^$ z) n n+ `5 ]$ D1 h/ j/ G12 A+ m$ F2 r+ Y7 Q6 r# x7 V
21 U# W% g5 u p1 I; k! w, ?
31 m/ \- @9 @+ _( _; j, t' b
4+ D: v; ]$ [/ [3 q, e
5# i) X0 ~. g/ \8 Z* w
6
4 F9 K) c$ ?& X/ g0 G' |7
' X5 @% T& Z6 t8 M6 g$ |. C9 @8& z8 |" n# w& K! H; i4 _1 L1 `
Example3:取出单日值: W m3 o' p8 T( z4 m) z0 C
) Q: w9 S: y+ x3 ]5 Ss['2020-01-01']) A$ ^9 y3 m' Q' ~9 O5 c* j
Out[52]: 19 s, n$ |. G* w: Q2 w2 p9 F8 `
) K4 n( e$ ?' Q8 P! G
s['20200101'] # 自动转换标准格式
9 r% h0 C% R# b4 }Out[53]: 1
6 Y7 R; Q: P' R+ Q; g; x" a7 P+ ~1% X2 U& Q$ ~4 M1 ?. e& l) g" b
2
/ t5 G7 F! H* J! l& M$ u+ Y t37 C3 T* L, h% P: A
49 v) ]6 u- D1 H, _0 ~* [ d/ v
5
5 P$ Z1 ?) f, m2 cExample4:取出七月# z/ w; u- s" I) ], P2 }
5 l& Z/ o# n3 A/ z; ]; s" t( n
s['2020-07'].head()$ ], \# g$ c% C$ Z r
Out[54]:
4 K% e* y/ u8 O; D8 _& g8 k2020-07-01 0% _& q. }% I4 K" g z4 \" v/ [7 ]2 D
2020-07-02 1% d9 L+ M7 x6 C1 q
2020-07-03 0+ I( R6 t {' I& A3 ]( E
2020-07-04 0
/ E% q" u" O6 t, ~% A2020-07-05 00 e3 O n1 [5 V! [
Freq: D, dtype: int32
0 r) }; j: T& m& h% k# @: g4 \5 M+ l1
: L/ k4 o% Y) ^3 M7 ]2
1 ~) L8 P- E: c! r% I4 k3
$ R- d% d# A& U4$ O* w1 X0 |8 I% P* V
5
1 F# O6 F# J6 @. @. |67 F1 G- k9 e& M8 ?" t! Y. f
7
, i/ v7 W4 _) A1 y( J l8: e) K) w3 [! w7 ]$ p/ Q
Example5:取出5月初至7月15日
! L& b( s* l& d6 P. _- W4 A: k3 f4 T
s['2020-05':'2020-7-15'].head()
0 \ l9 W7 k6 V9 z2 l' VOut[55]:
3 G1 `) w% j- I9 c2020-05-01 0& p. m6 A& S7 s0 P
2020-05-02 18 G$ K" K' H0 f: L
2020-05-03 0
. o' K* a! S* @: l2020-05-04 1( N7 q/ {$ W4 E7 [) f. P
2020-05-05 1
0 }; o+ j/ [# l$ F# u, SFreq: D, dtype: int329 g' `( o2 \; q0 E
9 Z) p1 k; P0 C$ R A8 g3 H
s['2020-05':'2020-7-15'].tail()2 i0 H* P& v* ~+ e
Out[56]:
& X7 B8 X- m; E0 ]# h2020-07-11 0# A* k4 }* d; S, P3 J
2020-07-12 0' e& b, Y q1 p
2020-07-13 1
9 J! }; H( {7 O, c/ h, p2020-07-14 09 s5 a, }4 A% x2 w; c( q
2020-07-15 1
0 m) W- D7 g! V3 b! t. |( S- ?Freq: D, dtype: int32
( K3 M3 _% N: N9 X7 j: X; z* V( r5 D- n" l: k$ {4 i
1: {* E% f% a/ N
23 z1 Z3 ]! [& `3 C& t1 z; ^
39 I+ l! _- x4 a( n# }6 Z3 W
4" p8 E7 R! E' e9 O: ?2 t
5
# N( X7 l- i0 R6% S# i9 T0 j& q, a4 S. S2 @
7
0 Q! X& B& h# q: S0 U83 K( \' ^4 ~8 m2 g8 ]
9" N! U, R! M1 i4 k# {
10
3 u$ }$ [7 R( N9 |; C7 [6 Q: {11
1 }3 U2 Y3 _0 w3 W( `- z122 G3 b4 f0 d$ j1 k/ w8 c
13# I3 O# ?. T) v% F* V4 e$ C6 [
14
, F) ]. q# Q! m0 V% k: O/ y8 h- x8 m15/ X7 M* x& \, W1 t
165 |* ~ a6 t! y" h' ^# K) s0 S
17
0 v# y) J& @# @$ @' I10.3 时间差
& w9 u @# E0 }( k. O10.3.1 Timedelta的生成, w9 i+ \8 \2 ^: R$ v2 D* r
pandas.Timedelta(value=<object object>, unit=None, **kwargs)
; ^- Q6 e, D, B' X unit:字符串格式,默认 ‘ns’。如果输入是整数,则表示输入的单位。" c3 S. v8 {9 ^
可能的值有:, _+ R+ R* r2 E8 m5 w. X* \, I
5 |+ ?6 j7 \2 R- Y$ w
‘W’, ‘D’, ‘T’, ‘S’, ‘L’, ‘U’, or ‘N’4 F8 V& S6 ?3 z4 J8 G! x8 N8 p" l* F# i
‘days’ or ‘day’7 w) ]1 {+ y8 o6 g! P
‘hours’, ‘hour’, ‘hr’, or ‘h’8 t+ i* G2 V2 _: D/ N
‘minutes’, ‘minute’, ‘min’, or ‘m’
9 K! ?" w F% e7 I: I6 y‘seconds’, ‘second’, or ‘sec’
5 b2 @" f: G K毫秒‘milliseconds’, ‘millisecond’, ‘millis’, or ‘milli’
9 A# l; a0 L; a& Z0 N; D) m9 U微秒‘microseconds’, ‘microsecond’, ‘micros’, or ‘micro’
' E2 p, P$ O" s0 V7 c6 [纳秒 ‘nanoseconds’, ‘nanosecond’, ‘nanos’, ‘nano’, or ‘ns’.
# G5 S/ S! m4 j' N5 E5 o时间差可以理解为两个时间戳的差,可以通过pd.Timedelta来构造:8 ~1 k, n7 s! {. E8 d f
pd.Timestamp('20200102 08:00:00')-pd.Timestamp('20200101 07:35:00')0 N c; u4 O6 ?9 O
Out[57]: Timedelta('1 days 00:25:00')0 t% v$ f1 E6 \6 H$ f
, J5 u8 y9 X* u$ _3 s0 opd.Timedelta(days=1, minutes=25) # 需要注意加s
, h5 l/ c7 S9 S6 y2 DOut[58]: Timedelta('1 days 00:25:00')1 z9 S- o$ W( a5 _* _/ `
6 _2 c4 p6 M9 j3 spd.Timedelta('1 days 25 minutes') # 字符串生成
2 u- _$ L G. G* u9 @ tOut[59]: Timedelta('1 days 00:25:00')
) f: Q9 t0 f( t0 I; ~; A+ j1 E/ I! h m$ d. N
pd.Timedelta(1, "d"), k( O' H+ r/ ]: ]' W8 W
Out[58]: Timedelta('1 days 00:00:00'). I: |% f2 ^% v( H. q" s$ M2 f5 Y
1
4 j; G4 p& T$ p( O2
, b4 O8 `2 U% J! n; x3
* ]# L8 f$ p. c+ \6 Q49 i U* C/ j y- R' ^4 b5 I
5! e2 y4 Y. y( J$ U) j$ H; z7 J" z
6
. x# g1 a$ O" r2 X7
+ I+ H0 k1 x9 q! m. x9 ~( _. j8
; f, [7 N0 e. s9% f w+ `9 `/ b3 o
107 L1 c4 D( U" H9 q7 R4 ~
11# i) o- l4 A6 p( ]& s
生成时间差序列的主要方式是 pd.to_timedelta ,其类型为 timedelta64[ns] :; P$ I/ {+ s/ e9 K' D" @7 L- F, @
s = pd.to_timedelta(df.Time_Record)
! Z" b$ P0 m/ F" S. E6 p( A2 Y6 V2 h( G6 U0 ~! e0 q
s.head()2 Z, R& R- y/ s) J
Out[61]: ; }* Q" G% |, D: T& n
0 0 days 00:04:34
j3 w$ a9 s$ t% c) ?1 0 days 00:04:20
; p; O% w& X: q2 r7 _4 |2 0 days 00:05:22: w @) D) y2 l% w, g# u9 }
3 0 days 00:04:08
) R$ P8 r7 G, E0 m3 N0 o4 0 days 00:05:225 _! E7 F! S- q9 w" L
Name: Time_Record, dtype: timedelta64[ns]/ A- s3 }: H" i
1; a! l; H( b0 P$ t! W
2
L( T6 t( a0 o; l, u9 s+ C% ^) g32 A6 m) Q+ g3 ^$ \" Z
4
O% V7 T! T$ V5
8 K1 D8 ?) n* N0 A! ]0 z& J1 D60 |, E, G* }7 u: t; Z- @! F$ s
7) }/ x7 B3 j4 I% ~; y2 @
8
# q. {- h& V& t99 ^9 S L3 z: L$ j6 D2 x
10
. e/ ^8 a1 F1 h8 h$ J与date_range一样,时间差序列也可以用timedelta_range来生成,它们两者具有一致的参数:
6 z+ s( J' T. I8 v+ npd.timedelta_range('0s', '1000s', freq='6min')
5 M% e) Z5 y+ f% h" M- KOut[62]: TimedeltaIndex(['0 days 00:00:00', '0 days 00:06:00', '0 days 00:12:00'], dtype='timedelta64[ns]', freq='6T')
0 \% L- r3 R' ~, H# l0 w2 s. e/ J# U: f4 Q X2 o% o
pd.timedelta_range('0s', '1000s', periods=3)7 y( b, o' p# V9 A, |4 T ]% t
Out[63]: TimedeltaIndex(['0 days 00:00:00', '0 days 00:08:20', '0 days 00:16:40'], dtype='timedelta64[ns]', freq=None): M4 w0 c% ^( Q5 f- M: w
1( P" O& Y/ } f: W' n
27 A0 D& [" m2 m4 Y
3% }/ P& `5 s+ Y3 v, {0 ^: L6 _
4
' c2 @3 o( R! v$ z) U( E% b5: [7 U8 `6 m5 C4 N1 ]
对于Timedelta序列,同样也定义了dt对象,上面主要定义了的属性包括days, seconds, mircroseconds(毫秒), nanoseconds(纳秒),它们分别返回了对应的时间差特征。需要注意的是,这里的seconds不是指单纯的秒,而是对天数取余后剩余的秒数:
! ^$ X# t( K8 `: G3 o/ j* fs.dt.seconds.head() Q- k& V: u7 u9 w0 {- z
Out[64]: + t! |4 k g1 ]" K6 D3 [0 h
0 274
u) v0 ]! w _6 S. f+ i) H& S7 U1 260
1 H" {& J, s1 d: s; A2 322$ z$ ]+ m% Y- c3 i7 x
3 248
7 ~$ N( E- k0 E6 @5 D7 G) A" V4 3224 d2 e. b; `! J9 ~, O8 v( s8 ~
Name: Time_Record, dtype: int64
+ g. g N( R3 ]- G& k( F. C' e1
+ f6 @7 O7 }( U# ^& m2
! F M* ]+ x; |" \" O3
9 \8 J1 \4 ]5 A& c6 h4 J* O/ {8 E; t4! w7 {( B- d x: @3 A3 \- O
5
" c; k; z& J4 r& n69 Q* d9 m3 {: q8 o+ b3 r
7
c/ ^, o4 ]) \ W& l3 o8
& R# ^4 t& Z* L0 f1 Q* _7 F如果不想对天数取余而直接对应秒数,可以使用total_seconds: A7 d( [6 _' x. l
9 { N& T* g$ v. R" G
s.dt.total_seconds().head()) k3 q* c/ F5 e6 ]! U+ _
Out[65]: " y$ c/ N1 h9 @9 n1 G+ R
0 274.04 P$ _$ h. u! w/ t% l& f
1 260.0
$ u- m' m" s& ?9 p2 322.0
* F: [5 [3 Z3 j8 p0 S3 248.05 N/ w# z7 A s- P9 w9 ^
4 322.0
# W3 k+ `3 t0 `3 j$ Q1 |. y. YName: Time_Record, dtype: float64
1 e7 b/ e5 c& T) G1' [: d: k) p7 a% S% R3 m. i
2
( F; W( |1 i) \6 k8 F2 x7 V3
; N- M! v; s: M# k" I+ K4
. {) }# B [' S( p5' Z* v% _+ r( C& P9 T0 @! w
6/ e. D6 s$ d9 p, V! \
7/ y; }6 h8 M* F0 n7 K( o5 F0 ^
89 s0 `4 C# ]( F
与时间戳序列类似,取整函数也是可以在dt对象上使用的:
; Y& e/ i' q: R* Q( m+ G! c: f. O$ h7 }2 J- p% [9 \) G# Z( p
pd.to_timedelta(df.Time_Record).dt.round('min').head()
% ~: R2 t1 J8 T/ x' ^1 A9 W6 ZOut[66]: ' e8 a: ?/ I. y$ q! ? _
0 0 days 00:05:00/ B7 x0 O. k" p
1 0 days 00:04:00) d- r/ z1 G: V2 W* I P
2 0 days 00:05:00$ l* e$ `6 g4 E2 _7 \
3 0 days 00:04:00
; U" N C" A* s( A0 C4 0 days 00:05:00) |( x, P- z! Z
Name: Time_Record, dtype: timedelta64[ns]! ? t% g( l- y5 {
10 J* X4 K. |; j* Z
2
0 I o: G& V3 m2 g2 G3
' N7 ^1 S5 n3 ^$ T" F/ r! e4% R9 Z% B* r" k6 O8 Y/ a. E
5* B$ Y2 t6 G/ g2 w. m9 K& j! e+ W( @
6
. e% X' y% y. J+ ~* e( Y0 P7% g6 C2 f0 f& u) |( X
8
+ Z+ O8 z4 |6 i. M, [+ X10.2.2 Timedelta的运算1 k# L2 v3 B3 M: y1 q5 y3 v3 b8 I
单个时间差的常用运算,有三类:与标量的乘法运算、与时间戳的加减法运算、与时间差的加减法与除法运算:
- A: J8 \ O" | Z; C7 g3 V+ ytd1 = pd.Timedelta(days=1)' ?; v2 W. {6 A. B: d5 y b+ n
td2 = pd.Timedelta(days=3)2 D" P$ q) u u" w8 Q5 d7 N; o
ts = pd.Timestamp('20200101')' `% _9 D+ r4 ^1 p: j" |
- M: `# D4 l9 Z# ~$ m t* `% btd1 * 2
& Y& E. {, ^1 B! j' Q$ q1 K/ f, kOut[70]: Timedelta('2 days 00:00:00')9 F3 t c! X2 j0 [
2 M& n% ]! g J1 Utd2 - td14 P- D6 ]+ V, I! w8 B) E' l
Out[71]: Timedelta('2 days 00:00:00')
7 P# E; N7 L4 b" _7 T: {! M7 {: `* b7 o: Y F$ c' Y0 c* U/ f
ts + td1
8 a& V. y1 z: h5 j# M& \9 F. jOut[72]: Timestamp('2020-01-02 00:00:00')1 ~, a! }# ]( Y. {5 z0 X
- |& l( }! B7 Y* _ts - td12 l8 `& }; j. Z
Out[73]: Timestamp('2019-12-31 00:00:00')) I" u' B5 a# O/ q6 j8 k1 Y
1
9 J# B; K6 r; ?2 {22 R/ G3 Q; R9 f, _+ }6 K
3
( `$ ?( S/ g0 @- @2 x46 a' g' l' ?2 e
5
: _, g) l8 @4 P g r6% K; o2 y! q ~, L
73 z u# Z6 n& h4 }3 A
8
$ a: _* z7 t. A- Z/ Z: I9
( g9 ?5 @2 k# H, d) V10
3 f, |- } Z; k11
. M# k! e! p4 [% ]12
; N# H- k: N& a; ^' R5 m" y13
+ I5 Q7 r2 B! Q. Z% I/ k* a14
: t3 R. R3 R8 n& H15
# N" ^' G# e( ]5 e时间差的序列的运算,和上面方法相同:
. @* z5 q, k. C- L& j1 Q0 f& Dtd1 = pd.timedelta_range(start='1 days', periods=5)
% q4 s' d4 O# Ptd2 = pd.timedelta_range(start='12 hours',
0 B: l4 b7 a+ k% {$ V freq='2H',
& B, y3 j" I) h+ d. ^9 I periods=5)6 n+ E. A6 G: Q+ e5 p5 q: o; C
ts = pd.date_range('20200101', '20200105')5 }+ U) k$ x, ^) L) C
td1,td2,ts
1 ?* p2 b2 `6 ~- T5 }; s$ l( _! ~$ u) x' ?; K
TimedeltaIndex(['1 days', '2 days', '3 days', '4 days', '5 days'], dtype='timedelta64[ns]', freq='D')
( y. u4 J. O% ]: I5 m+ @ HTimedeltaIndex(['0 days 12:00:00', '0 days 14:00:00', '0 days 16:00:00',
& r& [; O, n; C, L" ~( A6 X '0 days 18:00:00', '0 days 20:00:00'], dtype='timedelta64[ns]', freq='2H')# z4 ^4 l4 S" X# p( {) e8 d
DatetimeIndex(['2020-01-01', '2020-01-02', '2020-01-03', '2020-01-04',3 ^! [. b) a3 }( w2 z2 Z
'2020-01-05'],0 n; s0 H: d" I# f( }% _
dtype='datetime64[ns]', freq='D')
& B3 F8 F% s2 N, x/ m% c1
% K5 s- n5 [2 O, G: }! N9 ~$ b2- ^7 v7 U3 U& d8 I
33 q7 n+ n+ l$ X1 r; I0 k3 T5 X8 o8 |
4. s! F3 Q' M5 ? o1 `! L6 U
5
. `7 G* q) `# O$ k3 O* L6
2 n% L. ~! B& K1 G, @7
! a2 I+ P3 P2 m1 X7 }* l85 S1 w8 N( {& _- h, T% l5 ^
9
# V4 a& G5 p6 P8 E10$ \5 g/ R3 o4 N+ A) `( e p8 ]8 ~
11
# u+ ^3 [0 ~ Q, O8 o& B( N& w* I12" S- s. _3 i, c2 F
13
5 ?/ Y2 e4 X6 E5 s1 o9 g7 itd1 * 5
6 c5 K2 o8 Z. {) j4 J% n) }, tOut[77]: TimedeltaIndex(['5 days', '10 days', '15 days', '20 days', '25 days'], dtype='timedelta64[ns]', freq='5D')
0 L. N& f& f6 l; c l, n: H! X* j ]2 s) g4 i' n
td1 * pd.Series(list(range(5))) # 逐个相乘
. T" k' [, x3 _. Z! g! O* h; vOut[78]:
/ m- x* ?$ T" ^/ h0 0 days
; G9 k9 D O0 ?5 r4 c' o+ D1 2 days
' p& o7 A8 h0 X5 g2 6 days$ x4 {! V4 V) o- m) N8 R
3 12 days9 R2 N9 w! b! O* N9 \* b
4 20 days
) c; k6 ]# q& y7 d: udtype: timedelta64[ns]. `! @ r6 Y2 d. \ y8 G5 W, }
$ [ i5 p2 d, S" _td1 - td2
* `4 R$ Q' k. a. Q" fOut[79]: & y4 l6 |/ w# ]9 S
TimedeltaIndex(['0 days 12:00:00', '1 days 10:00:00', '2 days 08:00:00',- a0 J( p1 K1 i7 V
'3 days 06:00:00', '4 days 04:00:00'],
# \) {5 @% i9 f" C* {; t2 A dtype='timedelta64[ns]', freq=None): a, f5 i: h' B5 _# q
; c! y# V& O5 S& A
td1 + pd.Timestamp('20200101')3 {+ M8 @6 S& S9 W8 L
Out[80]:
+ t' n* f x1 G8 {6 ]5 X) Y9 MDatetimeIndex(['2020-01-02', '2020-01-03', '2020-01-04', '2020-01-05',& i% `- U$ ~8 i" |
'2020-01-06'],dtype='datetime64[ns]', freq='D')
6 z+ d5 K* t2 }, v
+ |! h# m M) Y" v4 ~1 Atd1 + ts # 逐个相加
# W8 A, i# c0 ^" m0 `" `Out[81]: " P. f0 `# a- L& L# ?
DatetimeIndex(['2020-01-02', '2020-01-04', '2020-01-06', '2020-01-08',
4 y( N& T) `$ F/ G: F '2020-01-10'],
& P$ k: J% v Z, @: \) u dtype='datetime64[ns]', freq=None)( O2 G# I. C& _2 t2 {3 d
0 Q) c* Q2 q5 X Q1
+ M* t3 c# ]9 h; @6 k2
* t$ V5 g/ ~( a# @" R& h35 e& T$ \; }! N! @% |
4& I% R( B/ N0 ~0 j
5
' m% E- B1 \3 q& H4 G3 V67 D$ R$ T+ n- u+ Q* t- K3 l! m. W5 v/ z
7: u5 n' M+ O' J+ ^- e
8
9 {* n( b: L% M5 d9
, T2 X' s& F8 N( k5 t10
; l$ e8 X. r; g8 R' w5 b y, @9 ~11- M8 w. u: C2 B, h& N5 g0 Q, C
12' O4 o- b! x& m f
13: ]2 d v- u( U7 i, ~
14
d; ~# }1 Z9 E3 k15# A) S: C9 I& H7 ^6 h, M( u6 I- _' w
16
( o$ l0 W$ Q3 K9 m! Y17
; Y+ t4 Z( {' }7 N18) T, Y E0 {0 F3 }. `
19
0 A. l& j' a( ]20
5 s# l' h2 X, N# ~1 r) h; k/ N% \21
( y. Q" Y4 o6 P4 n) f22, B) a& A, i4 f1 q% D! H
23
- s( f8 _" r6 W0 i- ?24
& j# `" x5 d/ L5 n" ^/ z& n0 h25
1 `" J0 {5 W; S6 j& i8 i& J267 x7 P4 O U o6 O! x; H0 M
27
^- K5 M; L* }/ ^28
& [- X0 D# U! l/ D) G7 }8 z1 O% `10.4 日期偏置
/ i! g& b/ v, f10.4.1 Offset对象
8 g9 Q7 G4 z! T+ M+ B3 h 日期偏置是一种和日历相关的特殊时间差,例如回到第一节中的两个问题:如何求2020年9月第一个周一的日期,以及如何求2020年9月7日后的第30个工作日是哪一天。9 Q2 C- {: f+ e& G w; h, L8 S
/ |( S7 \. l8 b0 a6 @6 T
DateOffset 类有10个属性,假设s=pd.offsets.WeekOfMonth(week=0,weekday=0),则:2 [" P+ S" f- y( M Y5 c
. P# i& V6 T, gs.base:<WeekOfMonth: week=0, weekday=0>,返回 n=1 且所有其他属性一样的副本2 S5 b; a+ V' _: H7 u) @5 N% e" n
s.kwds:{‘week’: 0, ‘weekday’: 0}
+ n8 N" i$ E& Y7 Z, `6 Xs.wek/s.weekday:顾名思义: ?; d* ?" A" Q3 {1 }
有14个方法,包括:
0 G& N: L, w) @! M a6 m [ q( L" s$ P1 G, @
DateOffset.is_month_start、DateOffset.is_month_end、DateOffset.is_quarter_start、DateOffset.is_quarter_end、DateOffset.is_year_start、DateOffset.is_year_end等等。1 e5 u- p5 `* {
pandas.tseries.offsets.WeekOfMonth(week,weekday):描述每月的日期,例如“每月第二周的星期二”。
/ o4 L2 D4 G: j" e3 w' S* ^
3 P- h$ }# b# E6 P1 G有两个参数:& B0 N/ |7 Q: r& a
week:整型,表示一个月的第几周。例如 0 是一个月的第 1 周,1 是第 2 周,以此类推。; W0 `3 [5 x6 x) L" g+ K% v
weekday:整型,取值为[0,1,…6],表示周一到周日,默认取值为0(星期一)
! O1 o* F( f ?+ J& Q) apandas.tseries.offsets.BusinessDay(n):相当于pd.offsets.BDay(n),DateOffset 子类,表示可能的 n 个工作日。
4 t: J' m3 w$ \2 F, [/ o
* {6 G8 R# U+ K0 i9 H& J$ @; upd.Timestamp('20200831') + pd.offsets.WeekOfMonth(week=0,weekday=0)
9 C# ?" m# O1 F4 j: K* XOut[82]: Timestamp('2020-09-07 00:00:00')
. p0 t! \0 f4 A a: S( i, L% Z7 @1 }2 V3 S5 g9 c
pd.Timestamp('20200907') + pd.offsets.BDay(30)' Z1 V- i) a4 o- n5 @ }
Out[83]: Timestamp('2020-10-19 00:00:00')
0 R8 t! u; p$ u1 l a+ b: s1
8 D# C H7 ?' q* r2 c+ x2& S8 W* `) ~# Y# H7 l7 K
31 f) d( r1 X! {$ o# ? c
4
1 Z( r0 ?- X" z/ `5
. U2 d+ H$ _/ _ 从上面的例子中可以看到,Offset对象在pd.offsets中被定义。当使用+时获取离其最近的下一个日期,当使用-时获取离其最近的上一个日期:+ w5 S9 q% l7 X% e5 E
7 A2 Y8 {4 ?! E' e% e* k7 g2 u9 V
pd.Timestamp('20200831') - pd.offsets.WeekOfMonth(week=0,weekday=0)
* V! ^% e' o3 y$ y9 |9 j5 F- @( `Out[84]: Timestamp('2020-08-03 00:00:00')# l( i0 V# m: A& m! [+ @
3 e: B6 Z/ p; dpd.Timestamp('20200907') - pd.offsets.BDay(30)/ i* s& E/ Q5 S m, V5 U4 t1 s
Out[85]: Timestamp('2020-07-27 00:00:00')
& L% T0 h: Z/ ~# E6 L1 }% P
2 }- i. H* m T5 S6 E+ `* T) bpd.Timestamp('20200907') + pd.offsets.MonthEnd()4 p, H& a/ O0 I% L' Y7 |" [
Out[86]: Timestamp('2020-09-30 00:00:00')# o# I9 k( L% k1 t
1
$ U+ o0 S3 L4 A! g1 \& C! H24 L2 m9 x' E4 h( s7 t8 Y1 f L
3$ O4 r$ f: b% ] B q% N: b1 _4 {- q- M
4& j. h2 K1 V% C4 Z/ G4 g1 ?: w
5
3 t2 ^+ j1 @: y. z6
* W( Z1 q* U; T) B$ V7
; ^" ]% I0 x0 S/ E; o8 R' l6 I8
- e. D x6 J p, N4 x c$ p K 常用的日期偏置如下可以查阅这里的DateOffset 文档描述。在文档罗列的Offset中,需要介绍一个特殊的Offset对象CDay。CDay 或 CustomBusinessDay 类提供了一个参数化的 BusinessDay 类,可用于创建自定义的工作日日历,该日历说明当地假期和当地周末惯例。
6 n- X' E# n$ ]' C1 b! o 其中的holidays, weekmask参数能够分别对自定义的日期和星期进行过滤,前者传入了需要过滤的日期列表,后者传入的是三个字母的星期缩写构成的星期字符串,其作用是只保留字符串中出现的星期:
3 g" P0 H/ d# r% |: f! A) k4 y Z. r, o1 `2 q- {2 S3 [& E( y
my_filter = pd.offsets.CDay(n=1,weekmask='Wed Fri',holidays=['20200109'])
! S, `! S7 h/ F) ?4 m: m2 edr = pd.date_range('20200108', '20200111'). K* F4 ]" t E1 J5 l8 J, |
: l9 J" J3 Y$ R; v! O
dr.to_series().dt.dayofweek
8 s h3 t; \8 _7 x6 u0 JOut[89]:
2 y7 e/ {4 P9 V' o$ l2020-01-08 25 d, h' s: x) Z2 M
2020-01-09 3) Q- Z* ^7 z) O, y
2020-01-10 4+ s- i, A6 ]/ B1 F& z" f8 n; |6 Y
2020-01-11 5
H% P! N, \; A) H& q$ h: ?4 |Freq: D, dtype: int64, l3 O& J5 u R: i1 v
% }4 _1 C V6 d' \$ X
[i + my_filter for i in dr]1 K! r- c$ v$ E, [
Out[90]: ; d7 \( \$ Q& M
[Timestamp('2020-01-10 00:00:00'),) w# ?* m! F. x) U6 E, a' a
Timestamp('2020-01-10 00:00:00'),& W) U8 b# t/ [" ^9 u9 K
Timestamp('2020-01-15 00:00:00'),& \# h1 T; m- Q' Z8 [3 P1 H
Timestamp('2020-01-15 00:00:00')]( v3 }3 H' a) P, Q. F* U
3 @) m' m4 O- \+ Z/ i
1
1 j* y' G$ P# T! Y2/ B2 Q. o3 F8 @% {
3
4 |. B* F% \4 D- K4
8 X% }) c8 ` M6 J; y( x5
4 e1 J' z, m8 V1 E" @+ X; ^4 L& R# ^6
+ q4 W, i+ }! J; N( I" C7
. o# _1 r9 x3 h" o8
7 D4 D. M0 l6 S" c2 S6 o: n" {9$ a8 t2 O |5 h( g! M) g8 E, i
10
" F+ W$ n% y/ t3 j/ k3 @11
: o- w6 M7 `+ {9 _' `" e) z V" S" b12+ \ I! L' x7 M# B- {9 r4 n* p
13
. x: C; ?7 t0 f: u8 L9 S7 U5 T% F14
% c/ x. `4 [. v k4 o. I2 Z- T' q15
& n3 l4 T! ]1 l0 m- b _0 k16 z; K% A% {9 {5 @
17
( v1 `- l, y7 ?) T3 J2 e+ d 上面的例子中,n表示增加一天CDay,dr中的第一天为20200108,但由于下一天20200109被排除了,并且20200110是合法的周五,因此转为20200110,其他后面的日期处理类似。
7 J+ g' |! ]5 K4 a% u% o( y! P2 f) w
【CAUTION】不要使用部分Offset. E* I6 k7 d( m: y2 X
在当前版本下由于一些 bug ,不要使用 Day 级别以下的 Offset 对象,比如 Hour, Second 等,请使用对应的 Timedelta 对象来代替。
/ A. @) p6 o7 R) q9 K8 F
: H: w+ ]6 g7 M |" N- B10.4.2 偏置字符串
) t5 N2 ~( B( G# Y/ Z5 K9 k N 前面提到了关于date_range的freq取值可用Offset对象,同时在pandas中几乎每一个Offset对象绑定了日期偏置字符串(frequencies strings/offset aliases),可以指定Offset对应的字符串来替代使用。下面举一些常见的例子。
; j, P3 E9 e1 C4 Y+ @
9 h$ v$ r3 Q, @& B5 O Offset aliases:pd.date_range函数中的freq参数,为常见时间序列频率提供了许多字符串别名。 也称为偏移别名Offset aliases。偏移别名列表点此参看(大概27个)。
5 G& F5 d+ Y7 f3 H# P
+ N( U# y. A/ l0 M0 B$ a1 G. r/ e8 X& Hpd.date_range('20200101','20200331', freq='MS') # 月初2 M& _ j+ t6 U+ O2 ^9 f
Out[91]: DatetimeIndex(['2020-01-01', '2020-02-01', '2020-03-01'], dtype='datetime64[ns]', freq='MS')
% k0 A9 R# I2 l8 ]$ V+ m$ x2 M6 r9 t* w: {7 }) C* ?0 p
pd.date_range('20200101','20200331', freq='M') # 月末
+ h& V3 r+ P/ o6 W' t' x# ~) r& v( _Out[92]: DatetimeIndex(['2020-01-31', '2020-02-29', '2020-03-31'], dtype='datetime64[ns]', freq='M')
' A; B5 A. G8 U1 K) |$ c8 X! O( b! Z4 a, |; f9 r) W! d3 x$ i0 Z* S
pd.date_range('20200101','20200110', freq='B') # 工作日- a. ]0 I& o3 B& g) p8 C+ u
Out[93]:
/ r5 P7 ?2 B3 h, @) V1 WDatetimeIndex(['2020-01-01', '2020-01-02', '2020-01-03', '2020-01-06',
# o2 |7 _; P' d8 o! ~# Z '2020-01-07', '2020-01-08', '2020-01-09', '2020-01-10'],
; V% O3 @( J# s3 K6 | dtype='datetime64[ns]', freq='B')4 B5 J: {6 W) m5 S! F* L/ e' f/ v
4 Z7 c9 S" w1 E5 i* v- ~! v. ~7 kpd.date_range('20200101','20200201', freq='W-MON') # 周一: _" `" b% G0 c: X
Out[94]: DatetimeIndex(['2020-01-06', '2020-01-13', '2020-01-20', '2020-01-27'], dtype='datetime64[ns]', freq='W-MON')9 G' D3 h2 V, a2 f V
. c1 o5 M( @8 N0 L; I( O6 dpd.date_range('20200101','20200201',( j5 e7 t# k! f4 E
freq='WOM-1MON') # 每月第一个周一7 s/ ~3 W% z4 o0 D+ r: j
8 b. S: h" H" W6 t* I |: c7 EOut[95]: DatetimeIndex(['2020-01-06'], dtype='datetime64[ns]', freq='WOM-1MON')
* z8 [5 ]3 ?1 |+ u; w# A7 c" w
* {$ s% v B! C5 L0 J7 C1+ z) W7 k0 u5 `3 B0 |
23 E5 K# i- X7 F8 a) [. o: p) f! n
3
' s0 j5 b. J- \. O$ \4
3 ^- W4 F( X; T0 E7 W S( d% j5) R- Z5 ?% [$ c i
6
5 D K) @) }8 T$ q9 `7
% P9 r; M7 y \" u88 E4 s2 [5 x, l
9
$ H/ r9 C3 E7 g$ s( Z10
, d7 ~5 q% t/ `4 B4 [0 h- Y11: Q* r; M& f1 F/ ^, ?9 J
12) z; L3 T! U( ^8 y
13
$ T* X+ J+ Y4 f5 @3 W$ f( ]+ W14
b. N3 o5 J* f9 |; v$ c ^8 v8 Z154 Y* [$ {1 q3 C; h6 b
16
2 k; K7 P2 J( U17' O2 ^9 j) b/ _3 B; i6 s+ G4 Z
18
# O! @" D, r! Q9 G- y19: n8 J0 ~7 u9 X( \' Q( O1 n3 a
上面的这些字符串,等价于使用如下的 Offset 对象:
; ~% ]+ O0 v$ x6 @# H' [! v
2 B7 c- @3 V8 }pd.date_range('20200101','20200331',
8 S' T) k6 {+ z! X/ s+ p, B$ |6 r3 ]5 D. m freq=pd.offsets.MonthBegin())
- \3 P7 Y0 z2 g
" H' q! M* a5 Z9 i9 FOut[96]: DatetimeIndex(['2020-01-01', '2020-02-01', '2020-03-01'], dtype='datetime64[ns]', freq='MS') H. F& Q2 k' m7 e- e
& Q2 u8 ]8 \ G2 tpd.date_range('20200101','20200331',+ r8 X) Y% x/ P* v
freq=pd.offsets.MonthEnd())5 n1 r0 M4 q; i
4 g+ v. D5 t( z
Out[97]: DatetimeIndex(['2020-01-31', '2020-02-29', '2020-03-31'], dtype='datetime64[ns]', freq='M')
1 l* D0 C4 `" [* ]+ j) |7 P- u [' W) c3 b
pd.date_range('20200101','20200110', freq=pd.offsets.BDay())4 C- L. x$ O# C2 E; ?; M6 I$ s& A
Out[98]:
& n0 u. [$ E6 N% q9 \/ pDatetimeIndex(['2020-01-01', '2020-01-02', '2020-01-03', '2020-01-06',5 G; U. @2 H! r( a/ {9 w0 L
'2020-01-07', '2020-01-08', '2020-01-09', '2020-01-10'],# M0 W: [* x, N+ n4 y; P
dtype='datetime64[ns]', freq='B')4 E2 y; v& l/ L }- W3 [9 \/ E9 {
( {% x8 w% {1 B& }% Z
pd.date_range('20200101','20200201',7 d3 r. B4 R- z2 O6 Z
freq=pd.offsets.CDay(weekmask='Mon'))+ e* T1 z1 G+ n4 c& G
% c1 W8 Z0 t/ V
Out[99]: DatetimeIndex(['2020-01-06', '2020-01-13', '2020-01-20', '2020-01-27'], dtype='datetime64[ns]', freq='C')
( w! j+ V; a4 [- Z% a% y5 m* }. J, T- e3 V, f" `# F
pd.date_range('20200101','20200201', a& E9 T" s3 Y+ X
freq=pd.offsets.WeekOfMonth(week=0,weekday=0))% V, O3 j: f* C6 {3 f, C q% f
7 p8 `- E4 W& h. `Out[100]: DatetimeIndex(['2020-01-06'], dtype='datetime64[ns]', freq='WOM-1MON')
; j" @( R. J; n
4 k' _1 Y) x- s! V* F$ x1
* I* l+ W8 J% k! n2
0 c- M. F1 V" v: m9 [3
1 P; {5 G9 P2 j4 o0 G2 F/ d4
/ n, i! w& W: _( q5
/ N: Y+ ?' Q' r; y" [6
/ p6 v2 O# u" @7
7 k; _, w* B" F8
) j+ n) A" \0 b" h0 ^9# e& P$ A/ n3 `+ s4 b# w: x, x
100 l. S7 G! \$ \
11$ q- ]. A- ^. }/ N; _. L. ^0 y: y
12
% p# w- R- C7 f. H' `- I# J13
) \% ~4 N# t! z0 W: Q14
0 n4 Y& h/ @' `+ h" F$ ~155 c0 t: y0 Q, [
16; U( D% f, E+ @7 c% N
17
" w; P4 u/ h1 b; U) v5 ~6 W8 N18' f$ X2 f T5 R8 o6 b) x4 _
19; D3 z. ]7 P, c# C" B/ }
20
4 u& q* g' `0 O m+ g21! f9 z) t7 r0 I' [3 A' G r$ `
22 h- T' P3 u& `) D$ P8 X
23' p. q( }/ ~9 K0 v0 r
24
4 U: D+ Z, R2 k7 B/ d259 X4 K9 v$ [9 b5 p" w# U
【CAUTION】关于时区问题的说明
+ _. J! f0 h5 i* L2 Q6 l1 L 各类时间对象的开发,除了使用python内置的datetime模块,pandas还利用了dateutil模块,很大一部分是为了处理时区问题。总所周知,我国是没有夏令时调整时间一说的,但有些国家会有这种做法,导致了相对而言一天里可能会有23/24/25个小时,也就是relativedelta,这使得Offset对象和Timedelta对象有了对同一问题处理产生不同结果的现象,其中的规则也较为复杂,官方文档的写法存在部分描述错误,并且难以对描述做出统一修正,因为牵涉到了Offset相关的很多组件。因此,本教程完全不考虑时区处理,如果对时区处理的时间偏置有兴趣了解讨论,可以联系我或者参见这里的讨论。; u0 @0 p- B1 G$ f4 U6 J
2 G+ K5 ?6 e9 E
10.5、时序中的滑窗与分组
, h- L; T( x$ |10.5.1 滑动窗口& S! y% n7 h8 X; U5 d+ b
所谓时序的滑窗函数,即把滑动窗口windows用freq关键词代替,下面给出一个具体的应用案例:在股票市场中有一个指标为BOLL指标,它由中轨线、上轨线、下轨线这三根线构成,具体的计算方法分别是N日均值线、N日均值加两倍N日标准差线、N日均值减两倍N日标准差线。利用rolling对象计算N=30的BOLL指标可以如下写出:, E! K! n t) `" b; p
5 y+ x6 u6 ?! _
import matplotlib.pyplot as plt# |6 Z4 z8 S5 `/ N7 _2 P
idx = pd.date_range('20200101', '20201231', freq='B')
p8 F6 S- i+ Z- @np.random.seed(2020)$ ]* E8 _; G: h n6 W
( \- V" a: G; a* y5 `data = np.random.randint(-1,2,len(idx)).cumsum() # 随机游动构造模拟序列,cumsum表示累加& }; D `0 Q p+ n' a
s = pd.Series(data,index=idx)# S4 G6 |5 L$ _. k( ?
s.head()
: ?: V& L ]) {0 x+ D( u7 COut[106]:
9 R4 B, y9 k' l1 c: E2020-01-01 -1
+ \: q1 F4 L Z- v* M: `2020-01-02 -2" s( t& @1 o7 o, E/ w$ m
2020-01-03 -1
; O' X; ^" K$ ~9 F1 `2020-01-06 -1+ w/ o5 @7 @3 e
2020-01-07 -2
3 j* L& U; y9 C6 U* o# d* h4 I% AFreq: B, dtype: int32) j8 S# e" V( T
r = s.rolling('30D')# rolling可以指定freq或者offset对象 M# N2 p. E8 K8 P; t5 t0 g9 j
3 N( p# E( a; Cplt.plot(s) # 蓝色线
& s! n+ |* d( Y" f# VOut[108]: [<matplotlib.lines.Line2D at 0x2116d887eb0>]0 L$ `" e& E- i$ D4 I0 _% }$ p3 u
plt.title('BOLL LINES')
3 `2 `# Y- [3 s( z; h2 l' r% dOut[109]: Text(0.5, 1.0, 'BOLL LINES')
" y6 O2 V" x& H# p) p8 i
8 M# D7 S# ]) pplt.plot(r.mean()) #橙色线
5 Z( o5 q4 I8 I3 LOut[110]: [<matplotlib.lines.Line2D at 0x2116d8eeb80>]
( J/ t [& g8 U& v( y
/ y% I( M' G5 @9 q/ s4 }plt.plot(r.mean()+r.std()*2) # 绿色线6 }, m {& T" a! } x5 u, _1 t
Out[111]: [<matplotlib.lines.Line2D at 0x2116d87efa0>]
- S: |+ n* M+ S& b1 m* I0 e& `, r, N7 [( M5 I
plt.plot(r.mean()-r.std()*2) # 红色线9 U( k( R1 v" ~# j1 J- {
Out[112]: [<matplotlib.lines.Line2D at 0x2116d90d2e0>]4 \; c8 z. u/ N
/ U- q# B5 }! W* x$ D6 D2 m. d9 B9 D0 u `
1
6 f& J1 K$ b7 Z$ J$ F P& a2+ O7 ]& O- o) @% j
3
3 u- ~+ e( k7 i0 \. U4
* Z h, U- V0 A6 q* v: g* l5
% v4 }8 N* @3 f% ^2 o6 I6
( j0 [% e g* o( `. ?# j70 \, w' l- ]# k- \ b% }
83 ?! b8 [* d8 s. F8 o) u
9
6 I- t2 l5 k( F107 d$ F- s3 ~5 e- Y
11
; j2 z V" J6 d, G. d/ s125 f4 s& Y n$ Q
13, e% k9 e& p5 s- ?+ k |* i& ^, G
14
& n9 S3 U1 y7 Q7 P15( m7 i1 r2 p2 {3 P, J
16* e# r% V% X+ q3 t
17( S9 t8 C. M9 y) \# M- P; [8 T
18
( o5 K% A/ b1 ~$ l/ Z8 }1 F; y/ k. C19
' d4 g$ z5 ~/ j' w9 j20
+ ~( i0 F. @6 W& r! k3 M) U21
+ y( o7 h1 V0 U8 P224 Z0 ~: }" [; }; j( U8 r" s
23
, ^8 L. Q9 C$ K; L$ d24: W$ m% k7 r: \. T, s2 w0 g
25
- @4 @& C, B+ l+ q7 |0 p6 l26
7 i9 M* c% g3 u: W2 z/ C" i3 v27
6 Q; L% v! o- f0 m6 Z28' f; j. p% ~8 Y
29
7 P' t% R/ n( h+ s! P
0 M/ P6 W8 T5 V! c( k 这里需要注意的是,pandas没有实现非固定采样频率的时间序列滑窗,及此时无法通过传入freq字段来得到滑窗结果。例如统计近7个工作日的交易总额。此时可以通过传入多个函数的组合来实现此功能。
4 Z& U" L7 Q. H 首先选出所有工作日,接着用普通滑窗进行7日滑窗加和,最后用reindex()恢复索引,对于双休日使用前一个工作日的结果进行填充。7 A2 O# `/ b) R# i$ t4 B! @
6 ^7 {* z8 `! ?# B$ i2 M
select_bday=s[~s.index.to_series().dt.dayofweek.isin([5,6])]
7 A9 ]# g: D) A- dbday_sum=select_bday.rolling(7,min_periods=1).sum()1 Z% _' s2 d; C3 |
result=bday_sum.reindex().ffill()6 c* j" b" ^8 l" D
result; Q4 L! }8 Q& N' l
6 {9 p# f& a9 S! u1 A @2020-01-01 -1.0
( j3 ]5 r% b0 `8 i+ M. q( C" K2020-01-02 -3.0
' ~7 D& X" {" @' J5 c8 f2020-01-03 -4.0" B5 k- ?. h0 S: C
2020-01-06 -5.0
9 X, n% }; z) @8 V8 |% i! L. A) Z2020-01-07 -7.0
4 E) j/ [+ c. L ...
/ G" k* h* h, \$ { T' N+ Q2020-12-25 136.0
. O5 @* u; w( F+ Y" ?, T& P& Y+ M2020-12-28 133.0
3 b X1 B8 l: u+ }: Y2020-12-29 131.07 M/ `3 A: @& I& ]* M
2020-12-30 130.0
* w- a% ^ [$ b! y2020-12-31 128.0* V. J6 k8 Q9 Z+ r2 Q# J4 w
Freq: B, Length: 262, dtype: float64
# }5 U5 _/ k0 H% u
3 P0 B- `# G: J) K# J' ]; p1
0 v2 D, s- ]. }3 D! E2+ E. \1 _9 w! |5 s% @
3
8 n* Y; D: ?2 I# K3 X0 b4, p4 a) ]" X: L) K
5- x. I- C3 p: [1 g [4 g4 Q! |# `
6
. k6 ~8 `2 y9 R4 W, w/ F7
0 X4 W! F5 I5 s' l# s( T8
$ Z' u3 {# j0 K# B/ U9 p9
" n3 A* \- S: X: C5 e" \10
4 x4 t& E0 z5 h2 r" N. I+ U ^11/ K! J% F; d# R7 N0 f Z
12
* [8 x0 v$ q+ y8 h: O7 W4 q( V# F& q: f4 E13/ M* T6 r/ Q% v0 `0 K
14# q- t a2 }* D( m- H' A
15
& G! j W* R( c" H7 J4 F16) |' ?: j% L0 s/ y, L, ]1 y1 j) e
178 p; M1 ~2 S$ g3 P8 q' ]
shift, diff, pct_change 是一组类滑窗函数,它们的公共参数为 periods=n ,默认为1,分别表示取向前第 n 个元素的值、与向前第 n 个元素做差(与 Numpy 中不同,后者表示 n 阶差分)、与向前第 n 个元素相比计算增长率。这里的 n 可以为负,表示反方向的类似操作。
k: w$ U k8 _. w8 n2 x. l, v- i. R- U* Q1 Y9 a( U; K
对于shift函数而言,作用在datetime64为索引(不是value)的序列上时,可以指定freq单位进行滑动:2 h, p6 C2 I& @. G6 h# R( p, S# W
+ y+ O& Q" a( s8 Z, ~; e% l" ms.shift(freq='50D').head()
, U" g, P3 c8 A' e# eOut[113]:
* P5 j4 c3 R, z; ^/ y" `. e2020-02-20 -1: z. c2 i" S; N" K c
2020-02-21 -2
6 ]/ V+ g% z* ?0 b& [8 W5 t2020-02-22 -1
) q+ E5 a/ J; |# H3 H2020-02-25 -1+ g- t+ P7 l: ~
2020-02-26 -2
6 x3 \" k5 W! n6 \# U$ k/ idtype: int326 X7 t) g* k( M5 \+ K7 I# P
1
. l/ Z/ \" z) J( n" f& Y6 _2% ~ }- s' B* N! `2 [ N" A9 A
34 C7 B; {7 u. U1 |+ f4 N3 b6 x' M
4
# s5 e% ]' a+ B5 I* X! {5$ G8 n( I: k/ t# @" a
6
. K" E% X8 f& z% S" X7
% a0 S% E7 |* _/ p1 T81 M' @7 a: S( u% [' E. @6 T
另外,datetime64[ns]的序列进行diff(前后做差)后就能够得到timedelta64[ns]的序列,这能够使用户方便地观察有序时间序列的间隔:- `' P. S9 J7 M. A4 w
3 G+ h% N" ?: Z) j; W9 h' emy_series = pd.Series(s.index)
' g$ K! n- |; \4 Q* Xmy_series.head()* [: |: ?4 g6 E
Out[115]: 3 S: g2 e( V& q
0 2020-01-019 H6 r; r& i6 s5 o w$ w* p* \& [
1 2020-01-02( m" G! T X- z8 Q( G' x
2 2020-01-03
+ P9 F! }& z3 R7 q) G3 2020-01-06) I6 H/ f3 v E9 a% D$ l, |. \
4 2020-01-07# C1 D1 A+ Y9 ~8 l5 P1 h
dtype: datetime64[ns]
5 _% R% m8 N; h* ^! j' l/ l& t
' q3 S3 `% J5 t+ Q: r; C" wmy_series.diff(1).head()- x: ?5 D0 L" M0 ]7 @
Out[116]: + ]$ l4 b, T1 c$ G
0 NaT1 s% |9 I" D; \8 r; u
1 1 days
6 v3 f! I0 }9 y2 Y5 W- b4 L# j0 N2 @2 1 days
# D- Z) I! B- m, s5 E- f6 W3 3 days# u0 D* f! S! c7 o" z# |4 j
4 1 days
. ], `' J" m* y4 L) S9 W! Qdtype: timedelta64[ns]
& ^1 C% ~ a6 w7 [* ~
+ s: J& i; l+ q1
0 J9 C: U g5 W) X- f+ n2- ^% S) U9 T+ s! g! [
3
+ |6 d6 g1 C% ^$ `4; H! r* b$ I7 W
58 y5 _4 M# B4 t, }) I
6
K) J0 }* t' O# E. v7
( n5 s% ^6 I+ k/ P: \4 a/ b8
9 {8 T7 e/ `4 V; j& v9 Q9) Y% N3 Z& D& p9 `( e9 w
108 n8 [$ e% _( ? s8 L+ I
11$ T- t F. K9 v: m! J+ t
12
( i* d! `/ p: c13* ~- m7 R9 D" r# a: y) @. k
14+ c7 F- E3 P$ U9 q( W8 ?3 M. f
15
1 M6 `# u' A9 x0 @8 k& ^2 V R168 g. Y4 E- |! s- s& A2 ~8 N
17
9 m7 Y/ Z7 V0 I0 K& Z, K18
2 C- f2 \" E4 \. c# ~$ p5 ], p# e3 G10.5.2 重采样. s+ G6 ^' k0 | b8 I- j
DataFrame.resample(rule, axis=0, closed=None, label=None, convention=‘start’, kind=None, loffset=None, base=None, on=None, level=None, origin=‘start_day’, offset=None)
" k, l1 t3 ]9 h. b0 K( J0 C$ }常用参数有:
+ z6 k" j8 W4 O3 h" e! l# ~: M$ E( w) v
rule:DateOffset, Timedelta or str类型。表示偏移量字符串或对象
0 a8 k2 N9 R- d& Baxis:{0 or ‘index’, 1 or ‘columns’}, default 0。使用哪个轴进行上采样或下采样
! m. u; G$ U8 }& |4 Xclosed:{‘right’, ‘left’},默认None。表示bin 区间的哪一侧是闭合的。所有offsets的默认值为“left”,但“M”、“A”、“Q”、“BM”、“BA”、“BQ”和“W”均默认为“right”。- K( M5 ?* q" ^& k
label:{‘right’, ‘left’}, 默认 None。hich bin edge label to label bucket with,所有offsets的默认值为“left”,但“M”、“A”、“Q”、“BM”、“BA”、“BQ”和“W”均默认为“right”。 [4 O2 d l j0 P' i: @
convention{:‘start’, ‘end’, ‘s’, ‘e’}, default ‘start’。仅针对 PeriodIndex,控制是使用rule的开始还是结尾。
4 I' X. o9 q% P& Von:字符串类型,可选。对于 DataFrame,使用列而不是索引进行重采样。列必须类似于日期时间。+ P) n2 ]& w; a2 @
level:str 或 int,可选表示多重索引MultiIndex的级别,这个级别的索引必须类似于日期时间。
# l7 _, b( Q1 ?. c! m+ q; L, Corigin参数有5种取值:
u2 q* P( |4 x/ ?1 _‘epoch’:从 1970-01-01开始算起
. h$ X, c, S8 F+ {& k6 `‘start’:原点是时间序列的第一个值
* j' g8 T3 L# Y9 J‘start_day’:默认值,表示原点是时间序列第一天的午夜。
4 {$ N5 o7 ]. x* s! M6 a$ A$ U'end':原点是时间序列的最后一个值(1.3.0版本才有)
. d! O: c4 ?7 M! G‘end_day’:原点是序列最后一天的午夜(1.3.0版本才有)
$ `7 K; j( W* o( {offset:Timedelta 或 str,默认为 None,表示对时间原点的偏移量,很有用。4 x7 x q" _: [8 R7 j
closed和计算有关,label和显示有关,closed才有开闭。
6 Y3 U! C$ R) f* r+ w& F$ r label指这个区间值算出来了,索引放区间的左端点还是右端点,closed是指算的时候左端点或右端点是不是包含。
! W( B7 |6 Z7 C r: p8 h
_5 Q0 `0 e. k( f重采样对象resample和第四章中分组对象groupby的用法类似,resample是针对时间序列的分组计算而设计的分组对象。例如,对上面的序列计算每10天的均值:
' B0 X; T2 F1 Ps.resample('10D').mean().head()
O" Y* G) E* o5 ROut[117]:
: U% ]% Z/ u+ p" L7 w. F- q( \* M1 s2020-01-01 -2.000000
l& a. w- n, T2020-01-11 -3.1666671 M/ I* r2 F, b# n2 O+ d. u
2020-01-21 -3.625000
* G& A7 P, m1 G2020-01-31 -4.0000009 N8 x2 i9 I# S9 `# }/ T) R# M
2020-02-10 -0.375000* R! h% @, {' `% g" t( o& R1 `; X/ n
Freq: 10D, dtype: float646 p/ q# L4 K/ ]7 {7 Q6 i
1
* Q5 J! @, s. j# H( m: ~' B2
; d) Q2 i" i: b3
8 _! J S' E. z2 M48 c# t0 e: U" O: B0 j8 M) H
5# N9 H. p/ g+ _, i* S" ~1 ^
6* {& d: w) t9 L* q
7
: f3 d8 C/ t5 a8 Y/ q1 y. ?8
; Z& |6 t5 J9 k, x: D可以通过apply方法自定义处理函数:
4 T. p( [ M3 E& X8 j' i" q- os.resample('10D').apply(lambda x:x.max()-x.min()).head() # 极差: k( U: a2 ]8 K2 A% X
: x3 v) o( _' h5 EOut[118]: ; y1 j' k# z8 m; u8 g( e) a
2020-01-01 3! ?$ B0 q: T8 G2 ~" i
2020-01-11 4
, X0 _! ^2 Z% o5 ~( C D# |3 k2020-01-21 4
# b* Q* _4 `5 r. N0 g) E5 d) A2020-01-31 26 b$ f! |9 L% d0 Y o
2020-02-10 4' p6 T: F- N* S* E: N* {4 @8 [
Freq: 10D, dtype: int32% q8 I4 e0 K( U; A, t% Z7 a$ P
1: R& q: W! o- P7 p1 `3 @
2$ o2 y+ L5 u$ V$ }
3( W( D7 T# ]: l# D6 N
4
2 s* ?# N2 q6 ~) {7 `5; p+ e' ^( F% J" ~; K
6
1 o% }. |1 \9 C$ J; o% a70 K9 H- v1 J" B. a/ Y! q, S5 K4 A9 ?
8% I" l- V# D! O0 ^. t% J; _- b5 v" E
9
/ T8 Y0 _6 Q- N- G* e3 T 在resample中要特别注意组边界值的处理情况,默认情况下起始值的计算方法是从最小值时间戳对应日期的午夜00:00:00开始增加freq,直到不超过该最小时间戳的最大时间戳,由此对应的时间戳为起始值,然后每次累加freq参数作为分割结点进行分组,区间情况为左闭右开。下面构造一个不均匀的例子:7 y4 O; C& f" Q. V+ D! u. t. O1 P
( U1 y9 a. l' W, I0 U8 n5 i
idx = pd.date_range('20200101 8:26:35', '20200101 9:31:58', freq='77s')
, `# j+ z2 m. n0 ddata = np.random.randint(-1,2,len(idx)).cumsum()
2 j* ]6 B. e/ p& ns = pd.Series(data,index=idx)
1 U% s: C( X; }/ w$ S1 ws.head()
2 t: I+ ?& ?* B- y7 i/ {
% ?5 n- H1 L9 SOut[122]: ! Q9 G/ k# |. R) \7 C
2020-01-01 08:26:35 -1
7 ~3 j ]3 a: _9 X( l! M: D2020-01-01 08:27:52 -1% k u- {! \4 D$ Z! \4 ?; ]
2020-01-01 08:29:09 -2
% K, E3 `9 ~" M8 s* j2020-01-01 08:30:26 -3
8 ?6 ^6 c5 V# i4 P& g7 @2020-01-01 08:31:43 -4
' l8 K8 S+ l6 E: e6 I$ n* CFreq: 77S, dtype: int325 n, a/ [% @6 Q) K8 V% r
1
0 t+ {( Z2 ^/ \; Q# D, R8 F, y5 j2! g" z4 X6 K/ Z; z5 m
3
0 D% f* f) _: ]46 P% l9 \* P0 q3 a' K7 Q/ M
5
% X+ t9 ]+ Z4 l& v& g/ M$ P6
9 b+ o/ L' ]) S' l) \7$ I4 J! X/ {: p# K
8
& Z# T1 G1 C' D8 \/ Q/ E9. p& Y6 B3 `2 n+ Z4 W1 ]1 U' i
10: q# K: j/ e. M; h! |
110 y& ] J8 l& r0 u _; }' q# w: |
12
% V4 K, G' Z: E, j b 下面对应的第一个组起始值为08:24:00,其是从当天0点增加72个freq=7 min得到的,如果再增加一个freq则超出了序列的最小时间戳08:26:35:
9 f/ Z7 \+ @5 d$ J7 \( \7 U, s( h5 k2 ?
s.resample('7min').mean().head()' s$ J! ?- o7 ^3 m2 H
Out[123]:
; r( T9 i7 P8 t" j2020-01-01 08:24:00 -1.750000 # 起始值,终点值包含最后一个值; T. f( X! |" X2 t- c |
2020-01-01 08:31:00 -2.600000
" `; v3 H( U5 w2020-01-01 08:38:00 -2.166667$ K. G( ~: r, g9 t, ]7 d
2020-01-01 08:45:00 0.200000
: M% D" T# s+ t y- R2020-01-01 08:52:00 2.833333
' e; V8 I) u) V" hFreq: 7T, dtype: float64
& {5 U) h6 `! J: ^4 R1
' g5 _. v: ~! {. D. O( W! L26 l+ F g! B9 k! e
32 m7 x) w& w. l0 o* `
42 s1 [$ p% x3 E6 \5 i( p
5
, u$ G5 @; n6 Z- E+ t" {6
9 e8 B2 h9 ^- g' j8 @$ \74 X4 ]& e/ Z3 E& H
8
# T$ @7 H% H5 r! w 有时候,用户希望从序列的最小时间戳开始依次增加freq进行分组,此时可以指定origin参数为start:0 v, r! U9 G3 [0 O4 Y3 q2 _
/ g( s# W( g( X* f2 F; y
s.resample('7min', origin='start').mean().head()% W# }) L5 I+ V. e8 h. S
Out[124]:
# |' P8 x. z) [% @; J. b2020-01-01 08:26:35 -2.333333) i2 G3 b1 y; h
2020-01-01 08:33:35 -2.400000. s6 t/ d; W6 X2 c
2020-01-01 08:40:35 -1.333333; M) g+ P0 N0 V, I1 [4 [
2020-01-01 08:47:35 1.200000
& Y: m1 d q- _. `2020-01-01 08:54:35 3.166667( F/ k, ~9 r9 i$ ^" `2 \
Freq: 7T, dtype: float64- O, C( M7 T; a( f! t) J
1' X5 w, p0 t9 m. b' S6 Q& n
2( F# o ]9 r6 O8 n1 O+ B, x
3
# G5 M4 U- u6 @+ R4
7 O) x0 Q9 F( _5
; ?& s' r8 U4 @63 f1 _; a* O* g9 [
7
; f7 Z# d2 Y5 y% t' P& Q80 N, T% b, Y* \8 H5 R
在返回值中,要注意索引一般是取组的第一个时间戳,但M, A, Q, BM, BA, BQ, W这七个是取对应区间的最后一个时间戳。如果想要得到正常索引,用’MS’就行。
1 o0 ^% Y6 O' J9 \' P& E) O
% k; h. @: E4 i6 f2 c" }0 B+ a2 ?s = pd.Series(np.random.randint(2,size=366),
! w. \* C1 e1 y9 K5 n index=pd.date_range('2020-01-01',: |- W& _- R, z# o& W$ c( o4 |
'2020-12-31'))
/ P: ]5 z( D1 R* q- f# M5 }! U% _" ~+ m3 o+ E
5 g% o5 ~ K) `$ ns.resample('M').mean().head()
3 W+ D, B: W/ }% h, }% gOut[126]:
' {1 N8 n/ J7 f5 m2020-01-31 0.451613% z; j2 Y" `( S2 u- }/ E1 I
2020-02-29 0.448276
6 q3 X2 y& b% K5 u, O2020-03-31 0.516129' C* L _1 Z# y! l$ i4 J- }! P
2020-04-30 0.566667
! m2 \8 ?+ W8 i* J2020-05-31 0.4516131 w3 ^6 ]' h. I3 y6 U) x, U! A
Freq: M, dtype: float64
" _6 u2 {2 g% |8 r' x7 v1 w. v$ p9 R2 }& k+ c6 i9 S# L- v
s.resample('MS').mean().head() # 结果一样,但索引是跟正常一样, Q4 A* t2 B- _2 A9 y7 m& u
Out[127]: 7 |8 S0 K: g3 v% s3 L
2020-01-01 0.4516138 w) m0 o: D2 s1 H" o) ]
2020-02-01 0.448276" s$ M h) R/ ~2 w0 Z b, W" L; D
2020-03-01 0.516129
* U4 e, Y7 ], J1 R2020-04-01 0.5666677 N& ^7 n% H+ W q( D& L2 x) p
2020-05-01 0.451613
4 @- r/ x" L$ j/ W6 `0 n- d. jFreq: MS, dtype: float64
) Y1 [; V7 k5 U7 y* E" V1 J& l, s8 G' a2 S" f
1
5 R8 L. |( @2 t. m8 K, X* B2, R+ D- [, [& |3 K8 o) C
31 A' e0 b& o1 v, @" c
4
' S: h2 I# N( K* p! V5
/ U$ c: n3 W$ b. _# D/ P! {' o5 l3 t6; A# K+ s' W' k
73 v4 Y9 D* f% V+ W; h; w
8& z0 b* M- h, d! F' z+ H! g
9
; R# q! N8 ^8 a10) q$ V$ j' k& A8 k- y- O2 |
11
( ?, f1 k- D. X! k2 P0 g# ?127 ^+ G; g& g7 p4 C
13
- `- j0 X3 Y0 x- Q146 J; x/ U. ?8 C
153 g4 P M1 g" }. b
16# C5 X& j+ q1 v! b1 B5 l5 A
17+ Q' K* v. Q; K5 e" r/ \
18
* v9 Y& O3 g2 E4 \* m197 H' J+ `; t" d* j
20
% v- b6 \/ { j21
! J |3 ~- Q& x s1 @/ v22
! ^; p; i% ~" g" W+ s对于 DataFrame 对象,关键字 on 可用于指定列而不是索引以进行重采样:
7 q' }! y- f, G9 _- I1 \' _d = {'price': [10, 11, 9, 13, 14, 18, 17, 19],
, ?% J5 R, e3 [$ l+ N2 o; ` |8 M 'volume': [50, 60, 40, 100, 50, 100, 40, 50]}/ e% l1 F8 s' v. I$ [# q
df = pd.DataFrame(d)" H! Q, P9 s( O+ t7 g
df['week_starting'] = pd.date_range('01/01/2018',* C' z {* |$ u4 N' l( l
periods=8,
7 q3 ^! W. B+ j; b freq='W')
5 L9 Y" ]4 A2 fdf5 J0 i9 Z$ `6 C. G
price volume week_starting
, D5 v3 g# d7 l3 i4 r0 g0 10 50 2018-01-076 f# Z3 N O6 d) g
1 11 60 2018-01-14+ ~. J9 l. F1 P) J, _
2 9 40 2018-01-21
. @+ i" H9 A* v. V8 u& C3 13 100 2018-01-28
3 x3 R' s% S |# `) O7 g" Q7 H4 14 50 2018-02-04# o( b9 \" _; M' e a. T. Q
5 18 100 2018-02-11
" M" k; @. p0 r2 B6 17 40 2018-02-18
9 p5 ]: s- L1 T2 f; s" j0 _3 s7 19 50 2018-02-255 y: G& d5 d4 D" N
df.resample('M', on='week_starting').mean()6 c9 X* v! J8 l2 F& i6 d4 M4 v! T3 u
price volume9 H t0 Y4 o% S7 D, m- p2 k7 k* Q
week_starting" i% |6 Z" K2 p4 f4 F
2018-01-31 10.75 62.5
$ J7 R3 V: A( T" J2 J3 y5 H0 k2018-02-28 17.00 60.0( H9 K. z* @! ]3 A6 Q* Y1 W
5 R( @3 A* l0 R
18 l6 ]) T/ d/ P; i
2% v M; E' m# W
3
) k) F, _8 d, H& J4
$ K' }/ l# O0 ]/ q9 h) V5* K: J4 K0 @4 E( J, X0 Y
6
2 z5 h" j! J# x" m$ L- B* F6 k5 q7
, u( }) q# o. A* _, l/ |, K82 z. ^8 h! y, d; }9 ]+ V7 }
9. H( Z' z! V% e% f
10
8 I; Y2 p: G; _11. ^& r! ]( w9 `* G- S# o
128 N0 D7 @# W% {0 I- |
13* Y( v1 x( G D0 m+ V+ U
14" c+ E, R. `* `# B$ o9 H8 [, V$ M1 g
15* R: c, t/ [/ }& U c4 L
16
8 `- Q, f4 e9 j% o' R- }17$ C8 M' N% _+ J' B- |
18& h d6 o2 L6 K" c" x7 k
198 H' o$ E; e3 X4 k6 F" Y3 j9 z
20
9 ], M( T& `# R" A211 ~7 R- F z: K
对于具有 MultiIndex 的 DataFrame,关键字 level 可用于指定需要在哪个级别进行重采样。5 H# O+ F# } q7 e5 A
days = pd.date_range('1/1/2000', periods=4, freq='D')
+ s, {% R* K( X. T! N3 pd2 = {'price': [10, 11, 9, 13, 14, 18, 17, 19],) p& c3 q, B- Y' m
'volume': [50, 60, 40, 100, 50, 100, 40, 50]}7 x4 C9 z3 k" m6 T S% k# h/ G
df2 = pd.DataFrame(7 U& \( d; m9 p) z; d
d2,
3 _0 ]- S* k- b" O7 v2 B index=pd.MultiIndex.from_product(
( R9 {. e0 ?" H# P p5 } [days, ['morning', 'afternoon']]
. Q! m) r# H3 z. l: b )
% K) U; Y! o. }+ h)* \8 B8 U( O9 H( L5 D8 ~; v; d
df2$ w+ x9 M# X! @- I% m$ |! U6 Z% U& v
price volume6 S U, p& @/ q1 H
2000-01-01 morning 10 50) v& g7 Z5 k/ k) Q& r
afternoon 11 60$ ]/ r! E$ Q# \8 G& A
2000-01-02 morning 9 40
6 U2 W- y2 j, O4 o3 e% {1 U: ~5 K8 A afternoon 13 100! `7 L0 z3 H, E, W
2000-01-03 morning 14 500 \) z/ G8 A+ C! G& h6 E* F" g
afternoon 18 100
) k& N3 Z. Y* K% p2000-01-04 morning 17 40) i/ n9 v1 d2 c1 u) _( X
afternoon 19 500 ~+ D) L+ t! H+ ?
df2.resample('D', level=0).sum()
& y. A' n" Q2 l/ M% f, ?! _9 k" _ price volume. g5 \. I- s- ?: s
2000-01-01 21 110' s# B! p2 }* I. Z3 d9 @/ G$ j
2000-01-02 22 140
8 [% T" O3 q8 p: ?1 J" }! G: o2000-01-03 32 150
* M/ E3 t. i6 l j' d) b2000-01-04 36 90
# t r6 P( z* C ^7 Q/ E% C% x
: x; l! G" `! t4 |3 a, y1, f# \1 }# X, ~5 }( k
27 ?5 \( ~( I e0 d2 o
3
; e" `7 C5 ^- x. O4
7 A& h0 w6 f/ d5
7 [9 e4 P+ I6 C+ U) j6
4 u8 p, k4 c3 B- ?3 U1 i7
8 @! y2 A. y! u* p% O) ?8
9 k3 X: p! X1 a$ u- J4 J( R9
' P& j! M8 Q1 F+ l1 g6 @, L10
- g' i& j( R% O11/ U$ Q% D4 F4 j
12
6 G: d1 M' g6 G% M0 T @$ v13
' }8 X, J( A8 [3 s* r% I14+ j: @5 {8 M+ P# S- K+ h
15( |2 l) ]" s: [/ n8 D4 }/ W% s
16' H: k: ?+ I! V
17: s) f5 s9 T9 C+ X: a* W0 d
18
$ \' O7 C) `- ^' e5 ?19" R8 o- N* C8 m
20
0 \5 O5 z3 n' R21
- \$ n" [9 F, s- Y5 \229 W8 q* A) @% e* J# I& B
23
! s* C# S1 ]5 }$ j* W1 n' o2 x244 r4 w. P' b5 q
25
+ z% e9 l6 K1 P! _2 b根据固定时间戳调整 bin 的开始:
* U* U k; J( k+ J+ l2 h7 Y/ F l' sstart, end = '2000-10-01 23:30:00', '2000-10-02 00:30:00'
) o1 U9 X3 B1 m, y" h* jrng = pd.date_range(start, end, freq='7min')
9 R8 q$ i/ t) N/ ets = pd.Series(np.arange(len(rng)) * 3, index=rng)
. m1 K& G" R6 }+ ?1 _( ^ts8 T, d; j2 Z X2 x' `3 x/ ?
2000-10-01 23:30:00 0$ N) d8 P) |# C7 v
2000-10-01 23:37:00 3
" _) Z5 ]& a4 v* q9 `" X2000-10-01 23:44:00 6
9 p- f, r; u( J+ V2000-10-01 23:51:00 9
& ?$ `( a5 p4 v# e7 ?2000-10-01 23:58:00 12
" K. Z# t5 m* R. p9 V+ U2000-10-02 00:05:00 15
8 g+ G: N$ w- p$ |9 D; }2000-10-02 00:12:00 18* q& _" G2 ]& R& a% D* i: A
2000-10-02 00:19:00 21
% {0 O% S4 q( U8 F$ N8 M2000-10-02 00:26:00 24
8 b7 V4 e0 v- D* f! K4 Q( xFreq: 7T, dtype: int64$ s& X8 K! B( ?3 t3 f4 _
5 ]7 Y4 j, d4 T' W# F$ K
ts.resample('17min').sum()# ]. z9 l+ @0 J2 }
2000-10-01 23:14:00 07 n8 ?9 {" C, \1 j' `) r7 t
2000-10-01 23:31:00 90 D; K- _. m( }4 k- [- N! f
2000-10-01 23:48:00 219 n2 M: b! b$ r- M- @8 V3 ^$ y
2000-10-02 00:05:00 54$ u! `$ A/ |5 j* S/ \; Q( B& @
2000-10-02 00:22:00 244 b5 W+ R8 n. p( a8 ], Z
Freq: 17T, dtype: int64
7 Z5 `' k$ t* c# M7 `5 v/ z2 Y5 T& @; g2 V5 b5 J' {
ts.resample('17min', origin='epoch').sum()
- |: { N8 v* U1 a S. ]2000-10-01 23:18:00 0& c& m5 L$ B9 d0 l$ F) G8 f
2000-10-01 23:35:00 184 L/ ~+ n8 L! p& w8 U
2000-10-01 23:52:00 272 U2 K4 o: W/ w1 V$ F t" O
2000-10-02 00:09:00 39
/ R4 z0 y3 o9 ] o7 l- }1 w f' I2000-10-02 00:26:00 24: P7 R' M0 J8 E% U% }
Freq: 17T, dtype: int64
7 a" w0 R9 b6 I" F, D7 ^. U# q. p
4 |) @# L: t' |: \ \: Rts.resample('17min', origin='2000-01-01').sum()8 {7 p' n9 ~! g" f3 h3 s! Y
2000-10-01 23:24:00 3
9 t/ B% G8 c; v1 M0 G" K# N/ E2000-10-01 23:41:00 15
1 T% R3 B: l6 v3 L0 p5 H, \/ b2000-10-01 23:58:00 45
) @3 p8 _. r/ v8 Z/ f. o2000-10-02 00:15:00 45
- G, p6 g7 @" ~- @Freq: 17T, dtype: int64/ b0 {$ p, @$ m2 U P6 V4 V
! I& U J' h; m: q4 h1
3 ^8 q. ?' c# r2
/ s4 y0 C' C# |2 G/ b3
. c% y, s# N3 o2 O. t46 j! p! g7 m3 N. B0 d- W5 O
54 z. e' i+ t$ ~ Z1 n+ {1 @
6# H* p9 M6 ]2 n3 Q' i! B3 I1 M
7
8 T7 Q% V$ m8 S2 k: P8
D4 a4 w( B0 _9
4 m% ?, t" @% d0 I% O' t108 U; t/ k% z( p2 s' ?; i: r3 y0 d
11" T- Z! i* M6 I2 W
12/ c1 w. U3 n) F0 \2 {; w0 h- u
13
, K& D% i' o' |0 g14' b' S2 X, M' E- V _
15
6 e' q( H, \# g+ h( H8 a* ]16
1 L" C* {5 B4 F# @! \+ m W" [- G17, O$ y0 a( l) `: r9 d- r3 [) e( A! F; Y
18
/ F" z! R; @' {2 j19
4 E1 ~* X/ n, c* o# V8 T6 U20: g( c7 E! o1 E i, _2 g- q
21! f5 W; w& L- P, m) C" A _$ |
22
3 [7 \ i& j# R; F" X1 c, D23
& j$ H, T% L. y1 [! n2 r( Z3 U24& d5 a0 A9 B' [' |" J* B
25# P/ n0 C1 S4 N" y1 e# R
26- S& O- ~" }# u" v/ Y7 H1 }
27$ z: h s. G c1 R
28% l. F* ~7 n- d3 O
29* m7 H* |6 _: Y3 G5 R
30% `: Z! i* S' W; H1 X3 R4 ~
31
+ f, [, u T% w5 v! W5 O* K32$ I/ ?3 ?2 J2 i* s# V
33
! N8 {3 s: C! U/ H! [) z7 y34
9 g- X6 n1 w5 w) n+ ?+ ?# V5 {351 T0 N: `6 X; k
36
Z6 |# s; h; ~) e376 l5 y* d3 `# O3 p
如果要使用偏移 Timedelta 调整 bin 的开始,则以下两行是等效的:
5 Y3 {8 `& |0 V7 l. H# Lts.resample('17min', origin='start').sum()4 M( H; ^! c6 \8 p/ g& z0 X; ?; \ U
ts.resample('17min', offset='23h30min').sum()& Q0 ^% n: f. T& A. ^
2000-10-01 23:30:00 90 E- R/ r: ], z
2000-10-01 23:47:00 21" X5 j8 \2 r+ K
2000-10-02 00:04:00 545 a5 b% F# q5 U, w0 T- B
2000-10-02 00:21:00 24+ E! W9 H3 e" d6 x; X* {6 z- j& m
Freq: 17T, dtype: int64
/ q7 L! j5 Y2 I" R" s10 \1 ^, S- f' J0 r! ~5 _, B7 h# u# J+ y
27 O1 L$ ^7 z9 T8 J, \1 P( j
3
6 a% P2 F( b" Q' ]6 e2 \, z/ Z/ p4
9 T6 v k7 e; r: G2 L; o5. G$ b! O9 _6 E/ M0 B6 o7 w
6
* n- \; p7 q) Z( M0 e7
* H2 s7 x* `7 _ I5 i10.6 练习
. N3 M9 a) O$ Z; OEx1:太阳辐射数据集
+ N3 @" h- [; m# _. b: w现有一份关于太阳辐射的数据集:
I# r- O0 K5 l' G( L6 H9 R( A
/ B4 d7 Y& P# I2 {$ Ddf = pd.read_csv('../data/solar.csv', usecols=['Data','Time','Radiation','Temperature'])
' f3 k7 `$ c: |df.head(3)! v: G$ G1 N2 i
9 t X2 v) E! C9 a* o7 O
Out[129]:
0 M" d. V& i# W, y Data Time Radiation Temperature3 f4 T' V8 x% I' ?# N5 x
0 9/29/2016 12:00:00 AM 23:55:26 1.21 48
* g( C( f4 B4 r: v) ^- h. `1 9/29/2016 12:00:00 AM 23:50:23 1.21 48! G. s4 H/ b1 r) Y+ b
2 9/29/2016 12:00:00 AM 23:45:26 1.23 48: c7 t! H6 C2 N
1; |. g2 ]% J% W- |3 y z
2
- O$ L4 @7 Y. J( H* h3 i6 s' a' P! L1 V5 V- Q" X0 |
4
: m1 E. ~- t0 \6 I8 A3 B! E- h+ _5
& N+ j( I( I! u! a6 I6. z1 g8 u. y. p/ `0 N) }8 j$ {! v
7
1 u, Q* j5 z# t8$ w, ^1 G6 {9 [8 [
将Datetime, Time合并为一个时间列Datetime,同时把它作为索引后排序。3 X$ R* ]3 K+ r6 i: p
每条记录时间的间隔显然并不一致,请解决如下问题:
3 d4 h t2 v* H$ K) _( D% x找出间隔时间的前三个最大值所对应的三组时间戳。
/ [1 y0 E) ]( ]/ `" \6 C5 \- I是否存在一个大致的范围,使得绝大多数的间隔时间都落在这个区间中?如果存在,请对此范围内的样本间隔秒数画出柱状图,设置bins=50。
5 j/ P. Q, \/ p( T$ z1 A求如下指标对应的Series:
0 _4 ?- R! `0 a温度与辐射量的6小时滑动相关系数
6 a4 Y* Z( x- ?% J' V. J以三点、九点、十五点、二十一点为分割,该观测所在时间区间的温度均值序列; B2 V) D& f' d# o' \
每个观测6小时前的辐射量(一般而言不会恰好取到,此时取最近时间戳对应的辐射量)* U$ G/ a6 L6 ^; A* F
import numpy as np2 u4 M& i0 C3 E
import pandas as pd7 t0 j" W3 I9 u0 f) u! m) I |
1) e8 a# v- j4 ]
2' h( r( L1 H* D1 v! H
将Datetime, Time合并为一个时间列Datetime,同时把它作为索引后排序。1 p3 ?( N. g, l* J+ w
data=pd.to_datetime(df.Data) # 本身是object对象,要先转为时间序列
- s$ ^! u" ^/ m5 C" ^times=pd.to_timedelta(df.Time)
2 y# Y& Y/ `" Ldf.Data=data+times
- J5 K8 e7 I; V4 S* X! O' }del df['Time']. O# n1 v1 u) o6 p
df=df.set_index('Data').sort_index() # 如果写的是set_index(df.Data),那么Data作为索引之外,这个列还另外保留% O. ?' M$ i1 ]1 y0 q9 q3 H5 W
df' s: u8 L& E/ N2 | R& {+ p
Radiation Temperature+ N Y' W* Z2 t1 J
Data % a3 T; n- G$ L X
2016-09-01 00:00:08 2.58 51 M! V7 A# @& H/ X/ R; c9 |0 x5 x
2016-09-01 00:05:10 2.83 51
3 k9 h8 D/ n/ T* U) B" f2016-09-01 00:20:06 2.16 51
% X& [/ q! Z) w; y/ ^: }2016-09-01 00:25:05 2.21 51
, q3 o O2 r9 y/ \% A* R2 E2016-09-01 00:30:09 2.25 51
+ Y) f3 l1 i* E6 x" h5 `... ... ...: ]; q% i d ^+ ]6 s ^2 d
2016-12-31 23:35:02 1.22 414 r% ]; y# }7 q3 z' ?$ _' V7 l
2016-12-31 23:40:01 1.21 413 K) o8 N: S6 x! J
2016-12-31 23:45:04 1.21 42( X W( J+ R8 k# b4 M1 N, f2 v, w# p
2016-12-31 23:50:03 1.19 41 ]/ F: F: _+ t6 H6 |4 d3 {
2016-12-31 23:55:01 1.21 41
) e3 N: e! o: j9 y- p& C4 o3 D9 m6 V% B& C* o6 j, o& m+ a) P
17 C' z, `3 N' O; W+ Z) X1 V
2
6 K, N3 `7 C# w0 G) o3, D& _3 S& f( ` ]/ B
41 y0 G* A {+ B( N
5, _3 T7 y' Q) u7 s d! d4 q1 @7 a
64 e3 |/ }; L' v0 h5 U/ z
70 c! s4 _. m C3 Z! F
8
* Y0 B) f9 o1 I7 e" }- v99 r& S, z' K6 x) v. G! L6 }; I
10& T4 I" R, U! S
11
! M) t+ z1 Y. e+ e( W12" F3 |# a7 V8 u, f
13& K: w, T7 P9 J. r3 a: R$ w
14
8 w3 q. I" ~! H( F. [4 V3 u15
# R ?2 d- Z5 [+ e6 A& L165 f$ b: y8 h0 V6 C5 J s8 j
177 u2 P W$ W. V) l ~/ r$ Q
18
+ A( [! W3 e( Y0 k! y8 S9 |19
. M) ]7 \1 A) f' z0 ?每条记录时间的间隔显然并不一致,请解决如下问题:
6 D0 {; t7 }1 p+ m) m7 ]8 ~找出间隔时间的前三个最大值所对应的三组时间戳。+ L8 L. y5 m6 E8 i& G/ a. W
# 第一次做错了,不是找三组时间戳
8 _ G8 b( O* Q9 J/ V/ \; bidxmax3=pd.Series(df.index).diff(1).sort_values(ascending=False).index[:3]
; {' U; V! s; \" @; R* bdf.reset_index().Data[idxmax3,idxmax3-1]: s3 E1 u$ L$ u& c$ Y6 m2 G1 b1 p
9 g* C$ J9 @ \2 {# b3 b- J
25923 2016-12-08 11:10:42* N2 h' B0 [# [! ^) [( L
24522 2016-12-01 00:00:02% e3 p; T4 x8 \7 R
7417 2016-10-01 00:00:19
" z- h0 b2 n: P: C- M! }Name: Data, dtype: datetime64[ns]
3 c5 y7 ?* ~$ m# A1( v$ u- p: Y1 |8 C. C h" j
2
$ V, ]) t& y- O37 F: g7 I( [! U5 M. L
42 u0 z- _9 q6 O" ]
5
, ^( z) X! z- e: t( z1 w( N6
4 R" ]5 X) a" C# ?0 v$ E% Q& U7
& H1 b8 p; { @8
" ~" U8 R: J. g7 Y$ Q$ o2 x1 s8 jidxmax3=pd.Series(df.index).diff(1).sort_values(ascending=False).index[:3]
4 ]6 z( ^9 O: W. A9 plist(zip(df.reset_index().Data[idxmax3],df.reset_index().Data[idxmax3-1])); L) i, ?. g: Q
, L" K9 I2 a' y& B3 R
[(Timestamp('2016-12-08 11:10:42'), Timestamp('2016-12-05 20:45:53')),4 a9 O1 F4 x0 q
(Timestamp('2016-12-01 00:00:02'), Timestamp('2016-11-29 19:05:02')),* Z- U0 S. X/ z( r% q
(Timestamp('2016-10-01 00:00:19'), Timestamp('2016-09-29 23:55:26'))]
! ?; D: B$ ]0 H/ T; a1
% C" o# t' N# ^% }: F1 @1 G) p9 V2
$ M ~- S! c3 J: a( t. [2 A7 U3( ?% A# x% g+ \9 C& y- K
46 j N, x5 e' U# E3 i
5
* T2 E7 q/ V4 b3 i6# _/ l* P# t3 K- v9 M$ [
参考答案:
) M/ d' T- R, S* y2 b: n& ?! }$ P8 `3 r) q6 G+ n0 W
s = df.index.to_series().reset_index(drop=True).diff().dt.total_seconds()$ q7 f2 d6 B G
max_3 = s.nlargest(3).index
+ J8 i# V, [3 m( ]# N& gdf.index[max_3.union(max_3-1)]. L! f3 q! p0 V+ P
- |6 [. K# T8 t" W- H9 A# L- |; v6 uOut[215]: 9 [% r/ }( F/ J# k, Y
DatetimeIndex(['2016-09-29 23:55:26', '2016-10-01 00:00:19',+ N, G# F! A i, l- z
'2016-11-29 19:05:02', '2016-12-01 00:00:02',% _+ o9 J/ X: |, Q: N+ r( ?2 c3 `
'2016-12-05 20:45:53', '2016-12-08 11:10:42'],
6 Z& Y- [$ ]& X0 w! T6 E/ ` dtype='datetime64[ns]', name='Datetime', freq=None)( [/ F) G; }& _, i% D1 D* ] y
1
5 E, x, \$ {7 J) v. T2( Z- i. |! E1 G* w
3
2 I9 S4 w5 c4 q: W( A P, s/ A1 @0 A4, b8 ^2 L% [) G7 w. E2 E* n- s5 V
5
( M6 H( P/ M- ~6
7 M7 L f# J1 o) s8 m6 Q7
4 S7 L4 [. t# n5 k: n- a8
$ t0 `% e& G3 x* y90 s1 s" E9 s8 w% B0 V9 q" \. m
是否存在一个大致的范围,使得绝大多数的间隔时间都落在这个区间中?如果存在,请对此范围内的样本间隔秒数画出柱状图,设置bins=50。. g7 T- E6 N! a) O5 T
# 将df的indexydiff做差,转为秒数后排序。再求几个分位数确定取值区间; M7 ?9 }7 e- C% ^+ R; X7 ~
s=pd.Series(df.index).diff(1).dt.total_seconds().sort_values(ascending=False)
/ I7 f1 b" s5 i. d! {) |' A$ us.quantile(0.9),s.quantile(0.95),s.quantile(0.99),s.quantile(0.01),s.quantile(0.03),s.quantile(0.05)
2 n$ a' D/ r/ N1 Z- `) O. J2 y7 M! V" k @+ {! c
(304.0, 309.0, 337.15999999999985, 285.0, 290.0, 292.0)
( c' n9 y$ U$ G N: K15 o4 C7 C) f) F4 M8 ?
2
$ F2 @; P2 R. O3
- ]1 O I$ \; D0 X {) w4
% d7 W9 Z5 f$ [- m9 V" }9 X) z5 r9 D5
+ x& B% @ y6 M%pylab inline8 d) j0 S- {- A# m& a8 X- R
_ = plt.hist(ss[(s.values<337)&(s.values>285)],bins=50)
3 N/ K, r, U/ _6 qplt.xlabel(' Timedelta')9 u; I q* a8 }3 x+ n1 s
plt.title(" Timedelta of solar")
. I" F: Z* J+ Q( p2 S14 k; C2 \/ K( {( N: E9 e
2
6 v2 v: [( X3 H$ ?+ @* r: O39 n2 t" F7 z- r# X
4
W8 T& `, h! ?# c' p
! Q: y5 b8 v1 v( u5 _1 n8 N- d& o" W! A+ ~# r8 k
求如下指标对应的Series:; y2 J, O u2 [
温度与辐射量的6小时滑动相关系数
$ F. C$ A' ]+ I5 N; x以三点、九点、十五点、二十一点为分割,该观测所在时间区间的温度均值序列
& K( {) n8 G+ C: p每个观测6小时前的辐射量(一般而言不会恰好取到,此时取最近时间戳对应的辐射量)
/ x7 V5 m; f7 ]8 h+ N( R: mdf.Radiation.rolling('6H').corr(df.Temperature).tail()
8 E; J2 p2 L* d* E2 _. X' C- V( E% l" c2 h
Data6 i' |) {# u9 {; K) J9 P+ V4 q6 d6 J
2016-12-31 23:35:02 0.416187
& o I2 L v; }2 \2 @1 ]2016-12-31 23:40:01 0.416565: F3 J, S0 u. j" l( G t8 `
2016-12-31 23:45:04 0.328574
; j4 B+ {8 i! t7 r6 E; P8 Y( Y2016-12-31 23:50:03 0.2618831 y4 s, _" h3 ?& d, i
2016-12-31 23:55:01 0.262406 a& F* ]( |6 c
dtype: float644 d w6 [0 Q% c$ E5 Z a q
1
' M; R R7 z: c) v) z% P. T: `2
+ v3 Y* z6 w& v8 n; T9 [" ^3
* Q* O' Y: [# w8 l' e! f( Q4! y) U$ E' \2 U/ i: p c
5: y! }4 R. w# D+ J" H( v- c6 ^" P
6
' q4 \) p$ u% C7 n9 B2 U7 m7
2 n! i' p& Z+ p6 Q3 L8
, m7 e P/ m, V9 J& }" H5 m9. K0 Y! v. i, p, \; \4 i
df['Temperature'].resample('6H',offset='3H').mean().head()$ b# h: L6 ]3 L z6 ]. ]
* O$ B" s9 _2 L7 X. b1 w) S" m# _
Data. D, ?. J& A9 X2 p* b6 n' O
2016-08-31 21:00:00 51.2187506 h/ y/ J" ~8 n- P1 E0 @; _2 M
2016-09-01 03:00:00 50.033333
- M5 F1 s0 I. S5 j V' [. E2016-09-01 09:00:00 59.379310
2 R' J; j) z6 F3 a/ m2 p) W4 j; D2016-09-01 15:00:00 57.9843750 a: j0 X t2 k1 d
2016-09-01 21:00:00 51.393939
1 n) ~; I9 e$ y% S! V1 p) b! h# [/ SFreq: 6H, Name: Temperature, dtype: float64! s3 Q1 \8 p+ }2 T
1' f& t- ?3 M- a
2
. k6 j, m" ]/ ~" d5 x$ V6 i3
/ v& w6 F! E' o1 r# t6 U41 A( m6 }9 ?2 x @; Z6 I0 m
5( a. x* r( d: `( X" |
6
3 c6 G0 v7 B, j9 v7
; v& V# [) k) r8
9 Q8 f; l! T9 X5 u97 j# k: p/ D* l$ v2 Y+ q
最后一题参考答案:
3 T+ x/ f; y/ g/ x6 d3 u$ o, j$ ?$ ]5 ]
# 非常慢, o0 j. M( K# X/ x8 I$ n; G
my_dt = df.index.shift(freq='-6H')
" o/ ^% `/ Z( z( ]int_loc = [df.index.get_indexer([i], method='nearest') for i in my_dt]- A2 ?3 e# H' j- y: V$ ^
int_loc = np.array(int_loc).reshape(-1)
. E4 S4 w4 d4 } H8 q3 V& Dres = df.Radiation.iloc[int_loc]
: H8 a5 s o! A' @: jres.index = df.index
9 J8 P# i& o, H9 @3 {* U' cres.tail(3)8 h# b G: r1 M1 b/ t
1" Y+ T! |& x7 k. _
2
& Q2 K- E/ q) I2 _1 _4 x39 Q' E& B6 q- @+ j
4
8 B' N3 i" } k; J7 }5
1 K6 g* ]/ w5 D5 y: X6
, S& s l8 @& |, U' N7
: p. n% Z2 {- i0 v# 纸质版上介绍了merge_asof,性能差距可以达到3-4个数量级$ p/ `' ^# F; D
target = pd.DataFrame(+ r, Z N" ?6 b2 \5 ~: o$ j
{
$ V T8 H: W4 ]' \ "Time": df.index.shift(freq='-6H')," c# m1 t) i6 k' a# _, V
"Datetime": df.index,
! {& N/ d c( ~6 l# S }; H' T0 V( c" d1 g, ?
)
! F# T/ z) S1 _+ x7 D" O; A; ]
% X" a# E. T! i. b2 H/ Rres = pd.merge_asof(
9 b+ D) V9 _( T9 t. {" h4 n2 q target,: ]) r3 d* I, o8 a0 Q" @6 J; _8 g
df.reset_index().rename(columns={"Datetime": "Time"}),$ ?& g0 Z4 H7 B8 z C8 V; g
left_on="Time",
+ Y: \& h9 s0 }8 X' d% a& Y% J0 a right_on="Time",) ]: Z+ M. O, k
direction="nearest"
& s8 n/ v# B1 ~0 v0 L1 n) ^).set_index("Datetime").Radiation
! F; `3 D J( K& p8 b3 ?: D
% N0 K$ B Z F! E" X& r8 [res.tail(3)2 e3 U- `5 l3 p. ]' H
Out[224]:
" v7 K$ B. [+ J5 ?; a! p5 {- xDatetime, I0 l2 i- h) [$ Z( p3 ]9 u5 j
2016-12-31 23:45:04 9.331 U- i) e' Q, w2 i( t0 ~
2016-12-31 23:50:03 8.49; }8 e. }# v5 u6 G: ?$ j* z
2016-12-31 23:55:01 5.841 ^, E3 T4 x# K
Name: Radiation, dtype: float64+ W# ~' I- e. b0 h( ^
' S; Q7 \. ~3 X1 q: A, o1
$ y' W; V( \7 W7 j2" v% J, A. c% ^" W, x9 K
3
8 l; y q: V$ r" _5 y- r( ~1 j4
) \7 T+ Y2 H0 s- g. Z" p' P' \5* x* ~8 ^1 ]% y
64 f0 N$ c* s4 v l; @ r/ a0 A
7
h0 U& V! \# e2 a/ t0 H8. X/ E! x& s9 q$ N& J6 ~+ _3 d
9
6 l& N0 d2 h g; d( q10
3 U* z/ g4 Q- a( v" G3 J- f11
' v* M, A, f- [/ F( d: m) T12
% r3 Y: d+ Q2 Y0 J+ S& o7 X. m13
3 a3 ?3 {5 k; S7 e' {3 o' k14 h7 e$ W2 [% b
15
3 I$ c, a1 M5 m. {16" _$ T) q& K: n4 m* |% ?% F
17( K# Q& c9 S; e
18( y5 B, s5 j+ r" }1 [) J1 @
19
9 @ z; _- x, Z+ s0 x7 ^20/ R/ o4 ]4 B" I2 P- B% B
21# D6 ~ O0 r _; ~) W
221 K/ p+ r4 g" K; y1 w0 V
231 e5 @1 \% ~! e1 r) H" R
Ex2:水果销量数据集9 ^: x* P0 T% ^+ v
现有一份2019年每日水果销量记录表:, N: p! y6 T) Y* U
7 X- r% Y6 h* P5 edf = pd.read_csv('../data/fruit.csv')( N; t1 A P2 h8 F& z
df.head(3)' ^. r4 Q% g8 }3 d1 ^, y
) V! |8 J7 Z& D3 a' IOut[131]:
9 j- w D8 m L, |1 ^ Date Fruit Sale
7 m/ ~% W% R& L: b0 2019-04-18 Peach 15) i! ^- _/ N# @2 R6 o4 Q
1 2019-12-29 Peach 153 [2 ^( C/ `% j2 l7 o7 F. c9 e
2 2019-06-05 Peach 19/ B2 D7 T& [' m
1( s* t& o; r( u3 C7 D6 W4 B$ Y
2) E! n/ f6 ^. d& O/ e
3
; r# \" b+ h& |9 X2 l0 I8 a4* ]7 X$ K# B$ b, y" t' t
51 F, c6 e& [4 ]
6! Q( v* A8 Y4 X6 V! C0 M
7
, f8 L# t4 v! G$ G4 D+ M80 W. x0 J* d; J) P- Q4 C/ j) k$ s
统计如下指标:0 s# b. V) D0 y; I+ h- T# k/ t
每月上半月(15号及之前)与下半月葡萄销量的比值. l, J& M: O U# ?1 h$ A: t
每月最后一天的生梨销量总和
' s7 @' K! ~9 J: ^$ e每月最后一天工作日的生梨销量总和
' c+ q1 E, b8 O6 H每月最后五天的苹果销量均值1 { R2 ^) [8 D/ [/ R
按月计算周一至周日各品种水果的平均记录条数,行索引外层为水果名称,内层为月份,列索引为星期。) F! e! a% Y9 ]; S
按天计算向前10个工作日窗口的苹果销量均值序列,非工作日的值用上一个工作日的结果填充。
- `9 Z8 L6 d9 d v( Rimport numpy as np
: k1 Q2 v( g o6 v0 |# qimport pandas as pd; e) ?( g7 Y* j+ n8 t0 k" M, `
16 Q. J4 p; Q+ d3 i* f6 ]& X6 s' E' {
2
7 f( L/ D3 [9 ~" A统计如下指标:
- L" p" Z6 Q$ o每月上半月(15号及之前)与下半月葡萄销量的比值) O& D H% q* \2 z: o
每月最后一天的生梨销量总和
: I6 |# I \/ u6 c每月最后一天工作日的生梨销量总和, v( n; J) x4 I3 e# I: M
每月最后五天的苹果销量均值
, L1 W9 Q0 z& a! `% j1 Y# 每月上半月(15号及之前)与下半月葡萄销量的比值 E1 v3 G/ G8 J* w6 q; P0 r
df.Date=pd.to_datetime(df.Date)# v7 j8 ^9 b7 f7 |8 j
sale=df.query('Fruit == "Grape"').groupby([df.Date.dt.month,df.Date.dt.day<=15])['Sale'].sum()
1 _' O* x% R! a, ]- Psale.columns=['Month','15Dayes','Sale'] # 为啥这么改没用啊
7 J' U* u1 P8 N9 D3 S2 z4 c+ ]sale=pd.DataFrame(sale)
) M" a( r$ k: O0 wsale=sale.unstack(1).rename_axis(index={'Date':'Month'},
X b3 m+ Y4 f0 P8 |) b columns={'Date':'15Days'}).stack(1).reset_index() # unstack主要是两个索引都是Date无法直接重命名
y/ D5 I9 {3 V( bsale.head() # 每个月上下半月的销量7 i9 T! M2 R0 V4 f: k$ k5 p
5 x) \" I$ p7 C# M( t Month 15Days Sale
4 r# X$ j, `& j9 T+ k% y. G( r- s0 1 False 10503
: D3 e8 l5 _( D/ D* l1 1 True 12341
/ _! T4 }1 _/ X, B9 o+ v2 p2 2 False 10001
* l, N9 z9 o7 r# N a" {- X3 H3 2 True 10106
- u! q6 {" \- J3 N9 O4 3 False 12814
Z0 `4 H5 d' U$ s3 O! k4 X
H4 t; D+ T3 N' z# 使用自定义聚合函数,分组后每组就上半月和下半月两个值,根据索引位置判断求比值时的分子分母顺序
" x7 L. g5 R+ ^* @, X" Rsale.groupby(sale['Month'])['Sale'].agg(
5 W. J( P Q B4 x l lambda x: x.max()/x.min() if x.idxmax()>x.idxmin() else x.min()/x.max())% b" P8 m) X0 N1 }, x! J
' r0 U) \6 ]9 b6 @% @( O! R) T7 nMonth
, j) j7 ]5 `; L# m s1 1.174998
8 {5 A" |( |+ G! _# x1 |2 1.010499
% F# W H" O$ D0 L2 Z! K# B3 0.776338
0 c( ]6 q; o6 T& D% h6 v" o4 1.026345
# r0 j& f' R+ z5 0.900534& Q/ M S( S+ w/ i9 [! t! H
6 0.980136, m1 \5 Q) C% ^; m$ Z
7 1.3509603 y' R8 E- _) w T A
8 1.091584% B* l0 n2 N! x7 \4 z' b. x9 f0 l
9 1.1165089 {: ]$ s7 h8 {% H4 e2 X
10 1.020784
5 {6 `3 l1 S- r% h1 q: Y11 1.275911
) y) O U J$ ?( Z. _; X" }3 ]12 0.989662* l3 V9 G' f# |3 S5 n1 e" U2 v
Name: Sale, dtype: float64 b: p! O2 P$ N5 R0 @7 y3 m3 |
( Q0 [9 G9 p+ \- E
1
! @. _. l: N; O! L0 p" g/ T2( H' @9 a( Y8 b: J& W- I7 h
3
! P8 X9 ^! F T5 T" k9 w! Y4
5 ?: A: s$ F/ n( }0 x$ |+ B5/ z" k! w. W) N+ C" z! r: G
6$ S. G% _. x8 j; s0 C7 s
7; q! ]% S( I% L1 n) ]9 s) X7 w
8
" T- |2 x2 X C2 K& H7 S3 D4 w97 u3 u4 V% x% d# y
102 W4 k3 y# t, A2 @: m! D2 z
11
) m% d* y1 }( J" s. \/ \1 O9 e12
" d( A2 B' k5 h; }13, B3 Q. o T) `
14
1 K6 h+ T( Y$ m& h15 t' w& C3 N/ r; |: o+ t7 w' C
16
) G# K' ^' B4 u! c8 x+ j- Z: g4 `17# e4 L0 J& v1 A. v1 c
18
& j& y& l! i2 x* X3 T19
; f' h* Z; U/ _2 _20
; j) i) B! \1 U7 A" `$ Y214 u, ~* S$ D! z# M" _# E
22' i5 o# q" M; C, i$ f
23
( \& ~( z3 y) `! P" g- C24! _, \6 w, i) a$ f- D$ [/ T, A
25/ ?$ P3 E C4 c
26
2 d7 e5 ]( A/ [; R9 R) }5 [27; C4 r; N5 O/ J4 ^7 z, I
28. y9 ^. \/ M" ~5 X2 v/ N- u2 T
29
2 n" T6 R# |4 {9 P/ e; A4 I30
7 m, o2 @! ~8 m31
- D% o, m2 l" S) P5 u32* Z% l5 Y% W8 X4 J8 t$ @8 g
33
7 D# h K1 r/ q$ H7 Q- n34
8 s8 e( ~: y& L/ t" {+ R! r/ l# 每月最后一天的生梨销量总和
4 W, X! `, x( E5 j: i. ^3 \' P& kdf[df.Date.dt.is_month_end].loc[df.Fruit=='Pear'].groupby('Date')['Sale'].sum()1 D: G! S, @. w! d$ d# Y
9 A- h& H* Z' L9 Z5 s/ U. B- T
Date
" z" ~- k# u* s# }+ u1 n3 P2019-01-31 847- z$ @3 X& x, A
2019-02-28 7749 ^! X# M6 G! o: }! g& l- L
2019-03-31 7614 u$ s% v1 K D: S1 m
2019-04-30 6481 d6 \/ f5 S( f% b8 v+ Y
2019-05-31 616
5 g3 S/ ^! @( a- U/ l, U% ?/ [1
: q+ S7 B% }% \" H2 c: v2, s, A4 T% G4 @5 v! m
3 r& i; L5 P# J* Z; [" }
4( M3 G& K! Z. a
58 N y. Z3 @2 i% d: ^! m
6
+ P! p. P4 P9 `7
; `# y6 t/ m" T6 H& I8
7 X+ y5 w+ \0 ? t5 n9
. Y/ Q& k! o9 q0 V& G# 每月最后一天工作日的生梨销量总和
2 c2 E8 |7 q _ P- `( `, rls=df.Date+pd.offsets.BMonthEnd() {9 `7 _& W( C$ z# B3 p
my_filter=pd.to_datetime(ls.unique())
- J6 `) K5 j. }2 t7 |; D# ~) mdf[df.Date.isin(my_filter)].loc[df.Fruit=='Pear'].groupby('Date')['Sale'].sum()
2 G: b, y7 [7 w5 H; X. l8 T) U$ h8 u3 z5 w* A
Date
9 _* b+ B' x" b/ j0 ? P( u2019-01-31 847( T4 m6 U( n- O& `5 K g( F
2019-02-28 7740 i. s4 f, n4 w+ b; m7 A
2019-03-29 510
! J% ]- o# W2 A* n# k2019-04-30 648
+ t! W1 H" y: O* p9 B8 r9 ^/ R2019-05-31 616
* ?9 l8 ^! q) W% b: ~& \8 `1' G; L+ L6 P; v
2
4 e" c8 ^# {& p' O: A3: r0 F$ V' ]! D5 C4 k
4
+ Y( ]* R7 {& f7 Q/ U5
! A: Q/ g8 s6 i: w$ o6
0 }4 v/ _, B2 ?6 A" \+ f0 g7 b$ Y7
1 H6 w( g6 @& g' j1 |: m1 r% o1 {87 }, |5 V5 p! t- k
9/ n* S/ `9 G, T% u. g z7 S
10
& T' m+ p+ E, r4 {5 A9 R11. [/ G8 E7 E& a: e
# 每月最后五天的苹果销量均值3 u8 b6 S1 y( X, @
start, end = '2019-01-01', '2019-12-31'
/ ?3 c) y) V, a# Pend = pd.date_range(start, end, freq='M')# `1 {/ ^5 X. I8 b
end=end.repeat(5) # 每月最后一天的日期列表,重复5次方便做差! g* W! y7 I& Q0 ]7 {' A# v
" G4 N( e! Y3 f/ w7 ^
td= pd.Series(pd.timedelta_range(start='0 days', periods=5),)
# C. G2 [3 a o; n* Itd=pd.concat([td]*12) # 日期偏置,最后一天减去0-4天
2 s! e3 }, ^, w: D( J- yend5=(end-td).reset_index(drop=True) # 每个月最后5天的列表8 t- u' n, b+ O! M& p% b8 u
6 W0 T- b& z! g3 oapple5=df[df.Date.isin(end5)].query("Fruit == 'Apple'") # 每月最后五天苹果销量
! X6 q6 E2 ? U& c3 ?8 Mapple5.groupby(apple5.Date.dt.month)['Sale'].mean().head()
1 _' {/ N' b2 S( Q/ P
" D9 C0 u6 d5 YDate
4 O, A# C6 s' J' ^) H1 65.313725
' }6 f9 R; W7 A# M# S& A2 54.061538" n4 c6 _. V' ]! x2 P# [
3 59.3255815 ?1 n- g1 ~* \3 n I
4 65.795455- C N0 y$ X, V* M h1 E4 @* }
5 57.465116
8 W0 Y* B, X! O* ?2 L/ N8 S. W y" y5 _! }. m7 A( Q0 v
1
) o* `) u9 |4 m" y0 x) A2
) E! T5 m+ q# ]7 N3
+ `! Q ]& T3 z# W1 F/ G47 {5 L& P" D: r# }
56 W t: u) Q( v7 C: N, c7 ?
6+ N# S- Z& b' O E! `( z* R
7
& ]8 I' w7 f* g- u88 |- g: ~$ p+ j* b" @2 _
9& M+ x8 I1 `0 G' |* u
10/ `6 L- t* w+ T5 u
11$ O$ L9 H" {. Q; B0 F& D
121 l! G+ Y( t) L7 [0 Q
135 R5 j) x9 K9 R* ]7 y, @; \( @
14
1 G$ i9 g8 c5 s8 ?* G15
G- f8 K6 H6 }4 ]1 j16; p. J: ], q& s) h7 G3 L1 \2 G
17: C* b# D5 F4 D
18
+ ^( ?: ]7 @! W; t# 参考答案:, Y# S# W3 f/ w+ k( R
target_dt = df.drop_duplicates().groupby(df.Date.drop_duplicates(# g/ } B) i0 o! {3 z, `( p
).dt.month)['Date'].nlargest(5).reset_index(drop=True)
" c+ B. [4 ?! @: O- J8 V, V4 Q5 W
res = df.set_index('Date').loc[target_dt].reset_index(& }) C# J1 \# A2 t& S0 }% T
).query("Fruit == 'Apple'")
+ v' V' {: U5 r0 n
8 x, z( I, ?; M. `4 Kres = res.groupby(res.Date.dt.month)['Sale'].mean(" b/ v$ M& ?% ? M/ x8 X F/ x
).rename_axis('Month')
& p- p% [$ a: ]7 r
6 F4 S9 g/ p+ @/ l* j0 R- B8 b/ m5 b2 P) T, t
res.head()
+ F$ {7 r, K' c% h* D: zOut[236]: 3 a U- Q7 v: z' t( P6 ?
Month% O$ J# b; G( ?% ~
1 65.313725
# U* l# T* f3 N3 O- c, j2 A" j2 54.061538
5 L. Y& h0 ~8 U! f+ A _3 59.325581
# _+ z: A8 e" ^3 m6 Q) k3 C4 65.795455
y- x" Q5 M' f5 57.4651169 v- p7 p( {. q- U+ \) B
Name: Sale, dtype: float64
$ a# N; u( J1 w/ Y
, P# b6 u3 i: \3 D18 Q2 F4 p$ s5 U$ p
20 v5 E- f. I3 j% M9 `* f; W. d7 d
3
- c- f- \* [; P8 s+ ~ W4 x6 K4
# u8 Q6 D$ ?& f1 ~7 ~2 G5' |2 o5 ~- {/ \( k" S
64 D( [& A5 ]" ]7 V4 @9 Q, v
7
# B3 g, Y4 j2 L0 f: M8
! U- Q! P$ L/ C7 S2 ^8 a0 n; t& k' U- N9
! l3 _( S, F2 J, Z100 ]3 k$ N/ ?6 E, n8 l7 ]
11
# x0 ]3 [" ^7 G- }3 g12# ~; F2 S& V/ E0 k6 W! `
13& i! G6 @( r$ o0 |2 t7 P
14; U: L3 P6 r L3 Y: r+ |
15* H8 T/ y* x9 h: d
16; s2 y: u8 ], q: J
17
; X+ s; g+ c# Z/ k/ A- X3 {$ H8 U9 z185 {) |' `* ^% b& ]% v/ D
199 r6 J+ f# K6 y; p: Y
20
+ j& T* f! v8 s, Z# K0 R, L按月计算周一至周日各品种水果的平均记录条数,行索引外层为水果名称,内层为月份,列索引为星期。& p+ i0 T$ y( U! h- G4 i5 n
result=pd.DataFrame(df.groupby([df.Date.dt.month,df.Date.3 t/ [+ ~0 ]5 a' r
dt.dayofweek,df.Fruit])['Sale'].count()) # 分组统计
9 C# O8 |1 S( ~6 W; l( [ @
! h2 h& \$ ~$ ~( Z+ I2 t( K+ Fresult=result.unstack(1).rename_axis(index={'Date':'Month'},
% E0 `$ W2 h3 v* D columns={'Date':'Week'}) # 两个index名字都是Date,只能转一个到列,分开来改名字.
* ]3 s8 B! N" e, e& ?result=result.swaplevel(0,1,axis=0).droplevel(0,axis=1), ?2 F6 U" h6 W* r# z
result.head() # 索引名有空再改吧% N6 [6 s6 i8 a" N0 C L+ \
; p) A5 M* j9 w E1 W6 Y. M Week 0 1 2 3 4 5 6* u, q& l- @' Q' p& F8 s8 c3 f0 S
Fruit Month
3 m: d8 X& o# L }Apple 1 46 50 50 45 32 42 234 ~3 @: V/ f+ V0 q1 C
Banana 1 27 29 24 42 36 24 35
" _4 Z6 m: w# ^# zGrape 1 42 75 53 63 36 57 46
J4 H3 ?* P( }( q) h8 ~/ qPeach 1 67 78 73 88 59 49 72- w: V, j& A# S* l, }: j9 |5 r7 u
Pear 1 39 69 51 54 48 36 40) D7 P, A [1 E! ~
18 P s n" E& ~. ~5 @* {
2
2 b# K( o. I" s) D. T) e) H3
; y% W: [8 J0 Y0 A5 O4 k# v42 F6 F0 B3 E* s9 i2 Z* F( a
5, r2 y6 L g. s9 z$ _7 @5 n* h, c2 j
63 b+ a' V( i# @$ A8 s
7
5 z: J% L/ w- Z; f9 X8
+ Q, ^3 J: Y- ?& O95 s( f; V3 t' w3 @" H
100 {& E% I! W! M( t( o9 X# k
11) Q; H0 q/ h2 a4 X4 K
12: f/ S0 Z0 {- r5 M3 ` Y0 i
13
/ `+ W, t, i# t$ W' w; R14
3 v* M* p0 j( W. v9 F: t15% s ~( Q7 W8 _
按天计算向前10个工作日窗口的苹果销量均值序列,非工作日的值用上一个工作日的结果填充。
% U; N8 D' h2 P: v' f# G( ^8 P5 M# 工作日苹果销量按日期排序. F- F6 x! d* D" i- b5 s
select_bday=df[~df.Date.dt.dayofweek.isin([5,6])].query('Fruit=="Apple"').set_index('Date').sort_index()
& H. H; _8 [" C2 q* k! O+ k6 Sselect_bday=select_bday.groupby(select_bday.index)['Sale'].sum() # 每天的销量汇总5 L2 \) p+ b0 _! y+ O( V1 v. f" n
select_bday.head()
" ~3 C6 `$ H( L3 t3 C7 `5 Z* w+ |, E% `; W" p
Date! T" x+ l3 b% C: ]5 D( V# N+ p
2019-01-01 189
$ C+ F$ f( m% w5 L; y2019-01-02 482
8 Q( g& `5 e- z! L% v! ~2019-01-03 890
5 X1 J* `4 C! M \0 G, Z2019-01-04 550
, {' X/ g) Z2 w; Z1 U4 H9 H8 v& H2019-01-07 494& R T6 b7 v! x! C9 p) H
. v. P4 M7 I6 v$ G+ k2 r; l# c# 此时已经是工作日,正常滑窗。结果重设索引,对周末进行向后填充。! N4 D5 V! a9 O2 i. r
select_bday.rolling('10D').mean().reindex(df.Date.unique()).sort_index().ffill().head(): n+ s( w+ l+ W \3 }* x7 L" a
! y* N! _( l S, ?8 kDate' R, W% t1 |# N. @- f
2019-01-01 189.000000& {/ T, k1 H) O R
2019-01-02 335.500000) @8 W% V; O( P
2019-01-03 520.333333/ g+ u! o. [! B. k
2019-01-04 527.750000: K9 L) E- @7 m, X4 f, J$ D
2019-01-05 527.750000& X1 b/ l$ S( h4 W# o4 ]) q
4 ?7 A' ^ s! i8 L& w) Y
————————————————# c) J' k2 ?2 ]$ [6 a; H
版权声明:本文为CSDN博主「神洛华」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
C& m+ G) }5 p原文链接:https://blog.csdn.net/qq_56591814/article/details/126633913
- d$ T0 k$ O" j8 w. e- ?
1 v4 A, I5 l* G5 o7 j
6 }( V' r4 w- D! k$ |# B1 C |
zan
|