- 在线时间
- 1630 小时
- 最后登录
- 2024-1-29
- 注册时间
- 2017-5-16
- 听众数
- 82
- 收听数
- 1
- 能力
- 120 分
- 体力
- 563304 点
- 威望
- 12 点
- 阅读权限
- 255
- 积分
- 174214
- 相册
- 1
- 日志
- 0
- 记录
- 0
- 帖子
- 5313
- 主题
- 5273
- 精华
- 3
- 分享
- 0
- 好友
- 163
TA的每日心情 | 开心 2021-8-11 17:59 |
|---|
签到天数: 17 天 [LV.4]偶尔看看III 网络挑战赛参赛者 网络挑战赛参赛者 - 自我介绍
- 本人女,毕业于内蒙古科技大学,担任文职专业,毕业专业英语。
 群组: 2018美赛大象算法课程 群组: 2018美赛护航培训课程 群组: 2019年 数学中国站长建 群组: 2019年数据分析师课程 群组: 2018年大象老师国赛优 |
- |6 r7 w# p2 y. s+ J7 o
! D6 s1 L i2 R/ A
; N7 ]% e# Z6 k* E7 ~) {文章目录 P1 X3 h% L4 F+ O
第八章 文本数据
3 A' J1 T' H4 f) s9 z0 X8.1 str对象
7 a" y4 Y: U G8 X8 ?, J8.1.1 str对象的设计意图0 F- X' p1 }: p2 K6 Q6 {7 L
8.1.3 string类型
6 f( i8 Q( D4 @) j8.2 正则表达式基础
/ [; M& `6 E( S: L4 g2 ~8.2.1 . 一般字符的匹配# E, g0 y4 E( O- o" E
8.2.2 元字符基础/ R* ~8 f5 z+ b. K! s% K
8.2.3 简写字符集
4 e2 [$ y5 `% t+ d: ^8.3 文本处理的五类操作
7 q& R# o+ R" I' h8.3.1 `str.split `拆分
& R1 G7 K5 s! @" k- a$ B9 R' ?8.3.2 `str.join` 或 `str.cat `合并$ I+ B; a) `# U* o; ~' q
8.3.3 匹配
' B; ]8 n% a, Z; N( ?2 R8.3.5 提取
* Z Y. Y+ F; ]1 o/ g. ]7 ^8.4、常用字符串函数
5 y0 G) e4 t8 }$ H& G6 U# H2 R& ], k8.4.1 字母型函数2 L: x' ^) e$ p2 a/ G0 P2 a
8.4.2 数值型函数
( ^* a% u4 E6 }& {% F) W8 e8.4.3 统计型函数3 B: L! G @6 v! d$ M# k% E
8.4.4 格式型函数
k# i4 ?7 a4 R; `8.5 练习
1 ? n" F# i2 F8 D( P" oEx1:房屋信息数据集
2 M% b ~8 X* T+ yEx2:《权力的游戏》剧本数据集
: ^5 t6 M1 [/ D* k9 h3 H8 N3 ^第九章 分类数据
2 P* A. S* q& v6 `8 @) i9.1 cat对象7 [0 M6 r/ L3 [7 Z0 u
9.1.1 cat对象的属性
' w6 G/ v6 P2 j9.1.2 类别的增加、删除和修改
$ Q8 l x# O* ]6 \9.2 有序分类6 m* p6 _* X- n, _& h& H
9.2.1 序的建立3 B% e- a9 Q* Z+ N' Y- u8 L w; |
9.2.2 排序和比较
2 g5 [- B5 P+ [( s9 R0 q* A* w& U9.3 区间类别
; {; V$ Z4 y' @8 ?% t4 |9.3.1 利用cut和qcut进行区间构造5 L) O9 i/ I' G% _3 N' @
9.3.2 一般区间的构造, |3 L& @( Z U) x- F H, f( j6 g
9.3.3 区间的属性与方法: f" ?: d* O8 \! }9 J9 @
9.4 练习
% t; V6 A5 ^/ @8 [Ex1: 统计未出现的类别$ x: `, I/ D5 y" V
Ex2: 钻石数据集" t, l3 ]8 }2 s) W! q
第十章 时序数据
7 Q! l0 s9 l7 ?7 \& d9 E( |& B# M% Q10.1 时序中的基本对象
) I: {1 J' y% a4 w10.2 时间戳# x8 T8 T$ y+ }7 I4 s
10.2.1 Timestamp的构造与属性' P- Y3 H/ D7 W0 U5 l& r
10.2.2 Datetime序列的生成& D5 h7 V9 w, M- I2 L8 U
10.2.3 dt对象0 j1 _/ z9 M' c
10.2.4 时间戳的切片与索引
. q* }# h1 ^7 O4 q10.3 时间差- S# X$ t* s: b
10.3.1 Timedelta的生成
* s, d9 P. H' Q10.2.2 Timedelta的运算' w$ w( V7 e8 ?- k8 T5 d
10.4 日期偏置
2 Y3 l/ R' \7 \4 V3 d8 l& ~10.4.1 Offset对象
% F( z/ h0 `4 B3 N1 H10.4.2 偏置字符串
0 ]0 `9 X, _# I p5 z0 `; [1 D$ }10.5、时序中的滑窗与分组0 p/ f$ X. e4 B3 \2 h$ H$ j+ D
10.5.1 滑动窗口/ ~+ G9 A2 n' M* c; F$ k4 N z2 x3 g
10.5.2 重采样
( M2 U' _6 O( @, B10.6 练习, B, V! G( h/ \4 Q. R0 F
Ex1:太阳辐射数据集
7 Z, [0 r ~. I1 [* J% hEx2:水果销量数据集+ J6 V! y+ \: f& D6 y
课程资料《pandas数据处理与分析》、github地址、讲解视频、习题参考答案 、pandas官网
% b4 f4 f# ?3 |' \传送门:( U) ~6 }% A- o2 c& y* J) Q- r. W% m
( h3 ]7 c4 M' n* y9 u, cdatawhale8月组队学习《pandas数据处理与分析》(上)(基础、索引、分组)
) b6 X# t! G1 T6 A5 e; G' Cdatawhale8月组队学习《pandas数据处理与分析》(中)(变形、连接、缺失数据)
7 s; u: ?; X% _4 W8 }第八章 文本数据 A. t! F8 |# R. [ A) g
8.1 str对象
4 u* Z& u4 s2 l4 N8.1.1 str对象的设计意图
0 [. j& J2 I1 Z1 ^7 F# G str 对象是定义在 Index 或 Series上的属性,专门用于处理每个元素的文本内容,其内部定义了大量方法,因此对一个序列进行文本处理,首先需要获取其 str 对象。在Python标准库中也有 str 模块,为了使用上的便利,在 pandas 的50个 str 对象方法中,有31个是和标准库中的 str 模块方法同名且功能一致,例如字母转为大写的操作:1 \3 W3 z4 _/ @
8 y1 P$ L2 x7 cvar = 'abcd'
! }7 X* O+ D4 ~str.upper(var) # Python内置str模块- [8 S6 D" r; ^8 ^
Out[4]: 'ABCD'
4 Q. K% S7 `- k4 K/ h) _# W) l, P
2 W! ? c% T7 F2 m/ ~s = pd.Series(['abcd', 'efg', 'hi'])( f6 P6 ~- F; O
. `; z6 ?1 c! W! c* Q9 m; Is.str
1 f, \7 T5 N" sOut[6]: <pandas.core.strings.accessor.StringMethods at 0x2b796892d60>$ c; o3 ?, P N# t6 d
2 P# T9 z+ a/ K- g* ? M- z
s.str.upper() # pandas中str对象上的upper方法
; m: `" j( c8 U3 \Out[7]:
4 j2 J3 {3 i4 _) ?5 f4 X, @, L' y$ G0 ABCD
6 w, t1 r+ D' N( A+ C9 e1 EFG
2 @8 f/ F# z q8 J# E2 HI0 ]9 `6 [ m. H$ G; v
dtype: object
) V" W s2 s+ y9 L0 v/ a1
& }& n3 W" S! G# [- A5 I% Z4 i. l2
" j2 u9 ^; q' E9 y: [3
2 \' B, H5 q. M5 O4+ W2 q) s& G- |% \
5/ @8 L# I$ j* ~3 [4 `0 R1 s' L
6
7 ]! F; ]' h: X4 B# }/ E1 D: i7
: H" G! U, d6 v, `3 Y0 E. }8) z' L1 i: n. i7 M3 a, X' l2 M9 R+ U
9
% B8 U, ]) c; A( W10: r- S$ {$ s4 X* c$ h; z
11$ c" R# _$ I4 D3 C } c
12
) t* r1 c g( `! H2 b8 X$ `138 o* S2 n5 B6 V w
147 J+ i3 \% p# W% }
15
5 u/ c# S) ~8 [/ R+ c; j- s8.1.2 []索引器
' E- J4 U/ I L4 F% Y* f 对于 str 对象而言,可理解为其对字符串进行了序列化的操作,例如在一般的字符串中,通过 [] 可以取出某个位置的元素,同时也能通过切片得到子串。
' N- p% y4 T2 t( ?4 u2 A9 F pandas中过对 str 对象使用 [] 索引器,可以完成完全一致的功能,并且如果超出范围则返回缺失值:
9 ?" K' \/ U! @7 u' A) Q* z+ b% V' n/ B! \+ F6 l, j( V$ {( t Q3 [
s.str[0]) @6 Z- T( x+ X
Out[10]:
2 \" R6 d: y3 m0 ^0 a: n3 v. j8 `& n8 j7 L, a$ J
1 e( ^0 P0 v- C4 E
2 h5 J: y, R+ B+ E, V
dtype: object6 N. w7 P2 h3 p5 t
9 _0 P1 d9 z- {0 O2 }s.str[-1: 0: -2]2 F2 o# t7 s# p
Out[11]: , L% {) l+ e" W9 r7 V1 R
0 db a; D0 K" o4 F' q' @( u* F
1 g7 J) A1 Q* Z, e& I' s
2 i
$ ]5 z, g/ J6 a7 Y) w0 `dtype: object
9 t. o) ^# X+ K }: D/ h$ R6 S* Q7 x* e" m" G7 j: H
s.str[2]. i- d2 e6 G$ r ]2 @
Out[12]:
Y" u$ x* ?) B6 {" G: Q$ g' Z0 c$ F/ Y9 o" d! y( o( v A2 ^
1 g. r5 K4 m5 \0 M
2 NaN
' _" r4 M3 D; J& O$ F1 tdtype: object" V9 Q- h- E1 S3 [. j W4 t
' i7 z1 [4 O4 Z, T- F$ x1
8 U( m6 _0 O, h8 l( [0 k. [2
9 G0 \# M& b$ A$ m1 D3/ J n# n$ W% L- G7 q0 m$ k% z
40 X' {: Z8 w: v+ R7 D( [
5: B7 G* X5 m5 S7 p2 v2 |6 A
6
7 m+ L2 j a' s7
( T9 U" y% {: ^! z3 ?7 K8: [( ?5 D. }0 \0 T6 P3 Q7 u
9 {) d/ Y* s9 L: F' H. V
10: V2 }) k5 w* S) t7 { L: _
11+ e2 y; ?: \7 C u2 z
122 d" k: t. G; y- ]2 H$ ?' h" J9 S
13
( ~# P& J3 V8 G( {; k6 W: E7 T& p14- C4 U6 P5 t' J9 ]5 C) v
15
; v% f0 A, U/ J/ y( i) ?+ D! E16
8 K0 m) `' |* s1 x. B6 f17
7 C( a+ y" {! E! ^% C# p8 ^181 R& ?7 G# M: r6 T
19
& ^/ V! H$ f& f20
: x5 \! Q. z) W) Vimport numpy as np
# U) K/ T1 N" _. x" j3 u8 himport pandas as pd
' P! h; g; G7 E/ ?+ B. ]. m: f
s = pd.Series(['abcd', 'efg', 'hi'])
( [* b9 g. P* Q% _: }3 i0 [s.str[0]+ Q a+ {' b+ E, j
1
) e. X. ?+ W3 ]1 P" {2
* {: s' m" ]1 L* D0 L" b31 ~9 A( v% o1 ^: r9 X' {
4
8 g# ~, l R7 x! Y3 Y2 j5
1 y8 u6 p% T* V" X0 i: c0 a3 L: l% {% ~7 W: P
1 e. s7 s# B6 b* b l* g* j
2 h, X+ Z3 B/ }, {9 U
dtype: object, k% h/ L6 R: p% D* i( o
1
$ p2 R$ t6 A- U# |8 G( }- j2
: \2 F- v3 H3 Z E, }3
# n# h* N! w, _) ~$ m+ ~4
7 A6 O4 [) b. m# K8.1.3 string类型
; m/ s; b1 ?+ ?& q0 E 在上一章提到,从 pandas 的 1.0.0 版本开始,引入了 string 类型,其引入的动机在于:原来所有的字符串类型都会以 object 类型的 Series 进行存储,但 object 类型只应当存储混合类型,例如同时存储浮点、字符串、字典、列表、自定义类型等,因此字符串有必要同数值型或 category 一样,具有自己的数据存储类型,从而引入了 string 类型。$ W+ {( k" C7 E% ~/ r, j
总体上说,绝大多数对于 object 和 string 类型的序列使用 str 对象方法产生的结果是一致,但是在下面提到的两点上有较大差异:
5 l1 Y2 ^2 s& o7 X! j
, T& P0 X. H( y% G二者对于某些对象的 str 序列化方法不同。3 B+ T( s; ~$ U$ S4 b- X* I
可迭代(Iterable)对象包括但不限于字符串、字典、列表。对于一个可迭代对象, string 类型和 object 类型对它们的序列化方式不同,序列化后str对象返回结果也可能不同。例如:* ]& I: D) {" I+ q }$ L* Q
s = pd.Series([{1: 'temp_1', 2: 'temp_2'}, ['a', 'b'], 0.5, 'my_string'])& B `) C) S" |% z# t- Y3 f: Q0 v9 E7 f
s
5 X2 Q# O$ d" ~15 a- ^% @# } N6 F: C3 H
2
. O- ]3 A5 V$ h$ L5 ~7 X$ }0 {1: 'temp_1', 2: 'temp_2'}0 i3 a. s' ~9 H
1 [a, b]
% f/ {) Y; A+ B2 0.5# v w9 m$ |2 L4 }1 J2 M# H! ~
3 my_string
8 N. @& A7 X K9 D$ g" {# ydtype: object
2 S3 o. ?# Y* F& ~ W% w1
; E* _$ T5 }, N- k: J2
( N) M& X( {1 s8 R3
6 w8 s% i* x w' [2 ~: X7 Z1 S7 `7 \42 ]$ ]5 i" z/ N4 C+ w( G
5
/ l/ ?. p9 _, Z, v$ Rs.str[1] # 对每个元素取[1]的操作2 v& a9 ~) I$ Q
1, e8 a, ]1 v+ ~5 S0 \
0 temp_1# _8 P& V7 c; C; A( B8 i& u
1 b
9 U9 P( q0 }9 P$ f4 c2 NaN
- d7 j3 I) P2 X" Y8 l. h) P3 y% v7 @- @/ t" ~+ H `& s
dtype: object4 ]( t v! t) M& Y- k
17 S. d" _9 W2 c# U* ?
2
Y4 W% e" x7 R4 d3
/ c: `6 }& s+ t' ?' }# t4
3 x8 C/ D4 ^6 G) C6 r& ]1 i' N5
$ t) Y( W `& G7 K6 K! I2 Us.astype('string').str[1]" c% h" C0 a# G5 t1 j9 I
1
* v: @: Q( m8 j3 [% H0 T$ |. m% I0 1- i7 @* e# Y' I
1 '6 X' T; f$ Z+ ]; ?- k% |
2 ./ A k9 n; D2 V- l, t k
3 y0 p% Y% G0 D+ \+ Y; |8 t
dtype: string) d6 ^' r- Z; j! n% L7 W
1
+ ^- m2 d% ^8 L# @' }" Y- R: `2
x- b- |- V- P0 z( ?39 I( O. J) J) `8 D2 D* C+ t
48 s. v3 O: P/ F7 g: }2 f! ^
5& K3 e3 F! r: b- d* h
除了最后一个字符串元素,前三个元素返回的值都不同,其原因在于:( ?0 e, V% y8 k' w d3 j
+ V; |0 g0 d5 z N" t
当序列类型为 object 时,是对于每一个元素进行 [] 索引,因此对于字典而言,返回temp_1字符串,对于列表则返回第二个值,而第三个为不可迭代对象,返回缺失值,第四个是对字符串进行 [] 索引。
2 T9 ~" J9 M2 [2 [- U* \string 类型的 str 对象先把整个元素转为字面意义的字符串,例如对于列表而言,第一个元素即 “{”,而对于最后一个字符串元素而言,恰好转化前后的表示方法一致,因此结果和 object 类型一致。
! s) e: [% k, y0 X0 h( T3 ^5 M; ^string 类型是 Nullable 类型,但 object 不是7 o# A) w( @- [$ r
这意味着 string 类型的序列,如果调用的 str 方法返回值为整数 Series 和布尔 Series 时,其分别对应的 dtype 是 Int 和 boolean 的 Nullable 类型,而 object 类型则会分别返回 int/float 和 bool/object ,不过这取决于缺失值的存在与否。 N6 m* Q( v) |* S' N1 G( ]# x
同时,字符串的比较操作,也具有相似的特性, string 返回 Nullable 类型,但 object 不会。
( B$ S* J/ _* \ n4 [8 fs = pd.Series(['a'])
4 |! O$ r( W+ `( m3 _# T' z- E9 S w# f) m+ A# w: c6 K- `. I2 n
s.str.len(), S: H7 N% V* B' Z
Out[17]:
$ W$ A; e# Z2 f0 1
( `1 _' I' m& n* ~+ {dtype: int642 ]. `, h& _/ c9 T& k
# W; J* v. C J! ]/ |4 q. Ks.astype('string').str.len(). A. ~. `- M5 j
Out[18]:
6 S" [% M, V; f1 A2 f3 R2 q2 R( F0 1
9 @ W5 Q& p2 p. h; Y2 fdtype: Int64
$ W o% P/ y% S. E$ o3 R1 i& Y t( X: p" V n
s == 'a'
; J) E0 N% u8 T) c: [; q9 S" ]1 m+ jOut[19]:
9 T+ ]; }6 v3 ^1 F- g/ @0 True* s" Z9 l! v0 c
dtype: bool! s9 r: H/ y. f M' Q
- q- K2 I$ ?) K* M( ps.astype('string') == 'a'
& G+ r( M- k7 h" Z% @) I/ lOut[20]: 9 o1 d' Q3 b. n3 W6 A/ Q1 v
0 True* g' P0 T, G3 g- d, {% N: z: c
dtype: boolean9 ~9 l2 }4 E4 M# o' d
( ~9 e& F( u, \, fs = pd.Series(['a', np.nan]) # 带有缺失值" u C+ o" f& U) B3 S+ w2 V z
; V: k: [- S8 s7 V
s.str.len(), u$ X% H; g/ O
Out[22]:
/ V1 z1 r: D- a4 N$ m0 1.0; a4 T7 v" {9 f1 I0 V! |$ d
1 NaN% b/ _& z- _8 R
dtype: float64
9 o% ?! m0 ^3 r, D
' ~* l* x! U7 `! q3 xs.astype('string').str.len()
4 F; B# A8 E( X/ y; m, h4 qOut[23]: : U6 F- s( r d7 _7 C
0 1
+ k7 |- h5 B. s7 m7 D$ A1 <NA>- s7 a: u3 G. |$ k9 u
dtype: Int64/ k' b; V3 U5 q$ u
A! O, `6 @- Y5 U, d- _' c& As == 'a': V2 U1 i% ?1 h; c. N$ ~9 z7 n
Out[24]:
6 D% t8 p7 `1 F: a9 n5 B2 P0 True
8 _0 j I; ?/ s; R2 I8 W0 U1 False# S6 k W* M4 Q' n; `, B! T
dtype: bool
Z: a$ ^! d# {- C3 L
, |0 \; Q+ z% [ r, i. Z6 is.astype('string') == 'a'
! A# _6 Y# q: _) AOut[25]:
]/ L1 C0 y" t4 V4 r" \4 J( R0 True
/ |7 u+ b2 ~. @' p y& d& j1 <NA>- J3 Y. d5 r) L6 z1 `
dtype: boolean
& B$ e S1 ]8 Q
" u" A9 _4 v- {$ r( Z) h1$ u. E# K0 V9 ^; W6 T3 {9 [
2, j; ~: A, d" x0 X7 S* ]. D8 O
3' M6 l2 l/ @/ v2 Q. `- W9 q
46 @9 W! |$ h/ N& b6 ^
5
* \# B" M1 V1 E) _, Z; ? A3 u6, S" `& R" o+ V
7/ p, ?! \& O+ C* X
8# R" c0 E, i: u& O+ c
9
0 b9 a; R K4 s# B8 n7 O. n10
' M2 ^" E. ~# @7 g5 H* x11
3 i) K8 k8 S2 o% U/ V, p& W8 j9 u12
* E% w7 M: x* C+ l8 K2 I13
; A# f: p8 Y5 ^# x14
# e$ J7 {7 W& ^, [15% g/ |& F: U( w) q
16' l. S! T; ?' p. w: a7 {
17
3 w: R! Q8 {, E18
3 a9 g) y. \5 u! G6 P' y193 k& Q- x) r# F6 v! C% o
20
! l# U6 V- s- P' ]21
6 l- v0 ]& I8 p: u5 W) T* F22
! X' Q/ a7 N1 Z0 l# j23
+ p8 z, v g% j0 d1 D5 r$ X/ @24
8 K Z6 ~2 n+ Y _( o+ Z8 y! o25
7 h' l% I8 c& O8 I$ m# ^2 h26; X# m* Q" h9 o7 `/ j
272 ?0 y a+ f# M. t! f
28
, ]+ b9 r) [& J2 W29
2 a; `( y) V! X$ r& S0 N) f308 S& r" t: s( p3 a6 _( o
31. A3 J5 x# F; C, H# V
32
4 y a0 P ?. V" g33 q3 k# h5 J1 T# b; w
34
) S, ^; ]4 t4 i* ^' Q- ?% j35
" e) H/ {; X( k5 |1 g+ y. O2 l% I36
/ |# o* U6 Q2 D7 @# N37) S7 W9 Z" A9 S% I9 W' z
38! ~ ^0 |$ B* I9 L3 z6 a1 `$ F
39& }: O9 k) _% V5 |+ @! a( ^
40
; u3 }& b: o* ?41
, X1 j# b0 l* }421 g# \. i$ h8 I' q4 K9 H
43
: W! L+ k* J# X6 \$ g44
) I0 {+ |, j' t7 ~$ U6 M p! n4 i45- c; k1 w7 _) v3 l s$ R
46
) i7 M9 ]! I$ a8 c; }% P5 u475 E( H; }* Z+ [7 h8 G; H& b. H
对于全体元素为数值类型的序列,即使其类型为 object 或者 category 也不允许直接使用 str 属性。如果需要把数字当成 string 类型处理,可以使用 astype 强制转换为 string 类型的 Series :
) \/ b& |/ q E" K9 D. U) l% A) f. w0 Q F% i) }
s = pd.Series([12, 345, 6789])1 N$ `$ x3 D7 w" r; H
+ b* m' P" R: Y+ t' L! ?5 R) q* ts.astype('string').str[1]
) y: H# b9 t, m7 S9 rOut[27]: 6 @0 i9 e/ S/ ?7 ^% V
0 2# g" A) ?' f' u; |) C; ?! x4 c% J
1 4
$ C3 U8 g3 E* K. J. \* H) j2 7
( O+ f; b' G- \$ S. g8 Pdtype: string
1 V7 D4 y7 a6 O7 Y/ M/ g w1
& e, y4 Q" J' }. L4 `2! h' E# \+ i/ j' d0 T
3- k7 D. E" I" ]+ r% Y% i- v
4$ T# x+ N# ]! d
5
) V) q& V7 P" O& o+ f6
4 Z8 K& B: |4 y; T7
+ ~% A" J- z. {( q+ V* }8
# W+ S7 w" }) N" A3 a- j* X+ ?% O8.2 正则表达式基础
" M+ d5 N, A0 ?. K9 T# f这一节的两个表格来自于 learn-regex-zh 这个关于正则表达式项目,其使用 MIT 开源许可协议。这里只是介绍正则表达式的基本用法,需要系统学习的读者可参考《Python3 正则表达式》,或者《 正则表达式必知必会 》这本书
2 |+ c1 H+ Y. H2 v
- |* i) }, Q, J7 ]5 U# o8.2.1 . 一般字符的匹配
& X7 C: V/ j0 j# ? Q0 S( M! O* ]$ q正则表达式是一种按照某种正则模式,从左到右匹配字符串中内容的一种工具。对于一般的字符而言,它可以找到其所在的位置,这里为了演示便利,使用了 python 中 re 模块的 findall 函数来匹配所有出现过但不重叠的模式,第一个参数是正则表达式,第二个参数是待匹配的字符串。例如,在下面的字符串中找出 apple :
: e% J1 [! a$ ^7 p- d0 h, s+ }9 t% v9 {
import re. b, X6 v5 a0 ~0 ^4 G
5 f! X9 |7 @! C" _0 u% k# Q, L! ~# {re.findall(r'Apple', 'Apple! This Is an Apple!') # 字符串从左到右依次匹配
4 l: H E4 i: u2 MOut[29]: ['Apple', 'Apple']
, }% M: B7 j/ C, x8 ]+ u& X% L7 \& \1
' f0 f* V4 E: h9 e$ l/ r2
! h, t; Z5 f; [0 _8 }# G' I, k3: A$ c7 k% x2 R; Y& ?5 f
4! e' D/ ?$ D9 b- Q3 W( W
8.2.2 元字符基础( J/ O+ O) S9 v% i
元字符 描述
' a- U* k$ F/ x" T5 S( H! \, X% v+ H. 匹配除换行符以外的任意字符, ~% ?* j: t9 J7 \0 U. Q
[ ] 字符类,匹配方括号中包含的任意字符, O" A$ e2 c5 y3 }
[^ ] 否定字符类,匹配方括号中不包含的任意字符
4 X* j% p8 S' v, _9 p; |* 匹配前面的子表达式零次或多次
a5 C, \' V% \+ 匹配前面的子表达式一次或多次。比如r’d+'就是匹配数字串,r’d’就是匹配单个数字
' ^2 I* R% @- e, w* ~? 匹配前面的子表达式零次或一次,非贪婪方式# J0 P5 e/ G6 ?
{n,m} 花括号,匹配 n 到 m 次由前面的正则表达式定义的片段,贪婪方式
3 H9 f6 U! T" m0 l( U(xyz) 字符组,按照确切的顺序匹配字符xyz# }- h ^: n U
| 分支结构,匹配符号之前的字符或后面的字符4 v, }7 l1 _8 x5 z3 M. c( Q% e
\ 转义符,它可以还原元字符原来的含义/ y, j2 H5 y1 w6 P* ~# V% @
^ 匹配行的开始- f' c3 h( w' R7 g" W
$ 匹配行的结束
3 e- o( A: N1 C0 ~& h! Nimport re
9 h& K4 C3 z7 ]" G) |re.findall(r'.', 'abc')
, ]7 Y0 d; M( Y) `3 o4 e! |: x2 x1 mOut[30]: ['a', 'b', 'c']. P- W8 ~ ]2 [! A
O8 k5 X) [; Gre.findall(r'[ac]', 'abc') # []中有的子串都匹配2 f i1 z) M' `- M( B' B/ r; j
Out[31]: ['a', 'c']3 q) x7 } t0 h" m8 i* H `
7 d @, y/ E7 d6 r4 }
re.findall(r'[^ac]', 'abc') . b) Y- {! B Q# K
Out[32]: ['b']
1 d5 B8 X6 V, z- H
1 Q5 n+ ~# e* O. }7 Lre.findall(r'[ab]{2}', 'aaaabbbb') # {n}指匹配n次% ?4 t/ ^6 i1 r6 b' [0 e) ~0 c. d
Out[33]: ['aa', 'aa', 'bb', 'bb']* c( b1 ^$ T; Y$ u. z9 @7 l
9 l) q! n) ^4 R
re.findall(r'aaa|bbc|ca', 'aacabbcbbc') # 匹配前面的或者后面的字符串& h# L8 J- E0 c3 h
Out[34]: ['ca', 'bbc', 'bbc']/ y) G( m; O" O& s' i
" r4 Q- X5 p% B2 B( W1 `( n" `
# 上面的元字符都有特殊含义,要匹配其本来的意思就得用\进行转义。
. o) \" C; w% k" z4 S! B* s- Y% W& a* P4 y"""& q9 ~" W! y3 V3 L
1. ?匹配的是前一个字符,即被转义的\,所以|前面的内容就是匹配a\或者a,但是结果里面没有a\,相当于只能匹配a。
$ t5 M; i1 a$ d* M2. |右边是a\*,转义之后匹配a*,对于竖线而言左边优先级高于右边1 [; [' |( e3 E) Z) `
3. 然后看目标字符串aa?a*a,第一个a匹配左边,第二个a匹配左边,第三个a虽然后面有*,
3 V. f3 ?! O9 O" _7 e但是左边优先级高, 还是匹配左边,剩下一个a还是左边,所以结果是四个a
& B* c. z; g* C/ w8 M# `1 L"""! m% a8 B5 ^, h. x7 s
. P6 }3 D( l0 h+ {5 n
re.findall(r'a\\?|a\*', 'aa?a*a') # 第二次先匹配到a,就不会匹配a?。a*同理。( q4 O1 o, o) j! [& w* o
Out[35]: ['a', 'a', 'a', 'a']1 o, `) v2 s5 x4 q& l8 p. H
m9 }/ P1 R, U1 T# 这里匹配不到是因为目标串'aa\a*a'中,\a是python的转义字符(\a\b\t\n等),所以匹配不到。2 h! U1 l0 m7 b$ I" f
# 如果是'aa\s*a'之内非python的转义字符,或者'aa\\s*a',或者r'aa\\s*a'就可以匹配到\字符。: u/ R) E' `+ G; `5 S
re.findall(r'\\', 'aa\a*a')
+ F3 F; Y% o ~3 U1 Q% A+ D[]) w( b/ {; u0 e' P; k4 Z: a/ z
' p; i' d) y7 u' I* A" }6 H9 x
re.findall(r'a?.', 'abaacadaae'); ^, z$ H6 @8 S n! {
Out[36]: ['ab', 'aa', 'c', 'ad', 'aa', 'e'] L5 d( E" L1 ?8 _
1 u; t5 M9 H3 S b& Y
re.findall(r'(\w+)=(\d+)', 'set width=20 and height=10') # 多个匹配模式,返回元组列表
8 S- e; U/ `0 S( e: W[('width', '20'), ('height', '10')]0 G- s# O" l; V2 H9 _( d
3 |4 R8 E: |3 H4 V8 L6 q7 d1
: Z5 H, \6 C2 O/ M9 x' Y% @2
% e" A( P4 D5 [# k) R' g33 p1 j3 y5 I# L/ T r1 Y) ~$ D
44 T3 r8 _9 D4 M
5
: W! O) O) j$ a6 z& P% B8 b6
. W/ N8 k+ C& g$ j7
: H0 B8 V7 T7 [4 U. V8
; z) h9 T! N+ C6 N& E9& V, ?9 {% q/ H2 ?5 @/ L
10& E* W9 R! ]9 V+ @5 ?! @& r# _
11! `2 P7 ]! P3 E5 Z
12
0 y% a$ p8 b G13: g6 t! L, H* t6 e
144 {" E& Q! V& L4 Y! M @ I) b! f
15
1 [- ^) V/ U/ j$ h+ A16
4 f1 q( F6 O) }: p) u( ^+ K17* y% { l1 y( M# n* {8 R, Y
18
% J8 Q' R; O& Y, i! V2 [) x: S6 D7 u19! `& h k( T1 s3 Z' G! m$ c5 J
20
3 t; A: x: s, }" ]! I Z219 M4 k0 s/ o1 |5 Q
22
: r% }! r5 H* |/ a235 ~/ k0 {' S2 n. ]$ ^+ W, _
24
1 I4 w4 W( f4 m1 P25
8 Q3 w0 F8 h7 W8 }9 _26& ~8 O3 C j1 ~5 l- `# ^ T
27
# R" [ Z. U# ?$ S" p# K28% s8 W9 S+ f# X9 e$ a% k
29
3 j9 z9 d( ]5 b, R) b30
4 k& h( O# o9 n" y& V8 d* z31
1 D4 U; [+ r2 ]32
2 B+ l% V) I; W! L5 b" O33
) c" e3 x5 Q6 h3 w34
. ~/ f5 Q2 S1 z+ v+ G: Q# _2 R35
6 H/ X3 t( ]: u6 ^* ~1 ?% A" G" G1 a368 x2 \: l8 q7 Z4 d' b2 t
37
6 Q' D$ u4 e8 T/ k/ S8.2.3 简写字符集4 z: Q5 X" e' H& o# U! Q
则表达式中还有一类简写字符集,其等价于一组字符的集合:4 W8 n. y5 ^7 G& v" Q% l( n @2 Z# E
* S$ {# |; \! Z, S+ Z" N
简写 描述
5 U* y. h, J6 n/ \! f\w 匹配所有字母、数字、下划线: [a-zA-Z0-9_]7 J; F. z8 V6 n G/ d
\W 匹配非字母和数字的字符: [^\w]
5 [" T5 l6 t- V! ^9 o' J\d 匹配数字: [0-9]
7 j+ @# t' z+ ~* C% ~\D 匹配非数字: [^\d]
* `# |* T3 }3 L5 `, o5 k\s 匹配空格符: [\t\n\f\r\p{Z}]
! [, w0 ~2 i3 t- p1 j$ v\S 匹配非空格符: [^\s]4 z+ ?$ k% E/ f* {6 A0 r4 d
\B 匹配非单词边界。‘er\B’ 能匹配 “verb” 中的 ‘er’,但不能匹配 “never” 中的 ‘er’。. b G3 H% g: w- D0 g( U5 w+ b
re.findall(r'.s', 'Apple! This Is an Apple!')
9 a3 a' x }( Z6 j/ _2 zOut[37]: ['is', 'Is']2 q9 f. V, O& i3 e) [$ K
* Y% u, [+ }( Y8 e1 e R8 ure.findall(r'\w{2}', '09 8? 7w c_ 9q p@') # 匹配任意数字字母下划线的组合,但必须是两次
7 K3 k% A4 a' s, e% LOut[38]: ['09', '7w', 'c_', '9q']
5 q. O1 s$ b( b! a8 n, @$ h/ Z: G9 d: `3 a3 \5 R4 p7 H
re.findall(r'\w\W\B', '09 8? 7w c_ 9q p@') # 匹配的是两个字符串,前一个是任意数字字母下划线(\W),后一个不是(\W)
_# n# `* [; u, |Out[39]: ['8?', 'p@']; v/ ~& ` O4 P9 q) K- R
{3 o, ^% z" ~. A+ C
re.findall(r'.\s.', 'Constant dropping wears the stone.')6 y+ I) |; M- }
Out[40]: ['t d', 'g w', 's t', 'e s']( a8 c1 j6 b! d# o9 s- h& g' p
3 ]2 u, \% d- k e/ p. V/ [re.findall(r'上海市(.{2,3}区)(.{2,3}路)(\d+号)',
8 a1 T+ S2 R6 f% E; u8 ? '上海市黄浦区方浜中路249号 上海市宝山区密山路5号')
: H$ O5 P r- a' _8 s- b2 F1 k) ? m. P D0 s( ^; P
Out[41]: [('黄浦区', '方浜中路', '249号'), ('宝山区', '密山路', '5号')]; P1 |; d9 h$ E6 n
% w( ]) U5 g1 B& R! y1
8 O" G, P; f% u7 O; D" J( Q25 f' j, w2 k. h( d1 d, G
30 Q" G @5 Q7 Z# ~* a
4
) }) ?+ b* \0 S, p% h53 s/ E8 ]: c# F& r2 z
6' G4 o, p9 t3 ]" l5 K
7
B+ Z' h/ g! J+ V: o0 H8
- R9 ~: f! k0 _! b! O( C2 e9
5 J @" ?" I( i$ ~/ _& T8 ^3 U10) b& [" l! G# v3 G
11
N9 p0 z6 G$ L" _8 v' g! N- Z; ~12
: R) m3 z$ ?" T4 u13" n1 X% u/ L1 l: O7 i
14
4 T6 n) e) q& l o& L; m/ N15. m- k5 e/ ]( |+ f8 } _
16
% t* H1 @* \! m6 k8 i4 f* V* J8.3 文本处理的五类操作
' v% Z* M( D! E# _6 O! |8.3.1 str.split 拆分
8 b8 o$ X5 ~9 d9 R str.split 能够把字符串的列进行拆分,其中第一个参数为正则表达式,可选参数包括从左到右的最大拆分次数 n ,是否展开为多个列 expand 。
0 f; z3 T8 R6 k& s4 V
# }9 [$ m+ U4 G2 B# j+ p& ss = pd.Series(['上海市黄浦区方浜中路249号',) Q- k2 _0 [3 Q& V: O g ^
'上海市宝山区密山路5号'])! P7 h4 |5 f2 w F- [
/ j# C! {4 r6 y ~# C2 b5 Q- O' C) K
( }- U" c# ?3 v3 w1 @/ s. E7 Ms.str.split('[市区路]') # 每条结果为一行,相当于Series, L4 B: s; t1 ?. p7 f) F L0 k% F
Out[43]: : b' M' Y. U. C$ f4 z5 o8 G4 h
0 [上海, 黄浦, 方浜中, 249号]2 `+ M1 P3 p7 p
1 [上海, 宝山, 密山, 5号]
6 j4 F; L; k0 P* o( L( i( gdtype: object
# W/ E4 t: V7 a) ?" x- g" n* ]4 S0 A1 O" {# m
s.str.split('[市区路]', n=2, expand=True) # 结果分成多个列展示,结果相当于DataFrame# c4 [4 w' u$ u0 N% O
Out[44]: w C" O/ Y6 B
0 1 2
. s) g' ?+ L A/ C! N9 K* |1 m0 上海 黄浦 方浜中路249号
9 y& b1 @( B6 S* g" {4 Z- D1 上海 宝山 密山路5号7 f- s6 i( q- I
15 l+ u$ K# A& z, o# R; W
2
4 `0 O, Z# D* _5 N6 R& ^% @! k3
* |7 e3 L A. f2 x0 P) M4! A& y" O7 F3 e8 z7 ^3 k2 Q$ {
5% F' _" J1 F! v- t
6! e! p0 n$ y8 d0 |# M
74 d- w* C" J8 ?2 \9 s i3 q) O
85 V) a0 |6 A- P! o
9
0 l: c4 B7 g& c6 L3 E10# M5 m, ^" R3 r2 I0 L) p
11
' o3 d. H1 t' V* W12$ _) r3 h. V# N8 z+ p+ o$ x
13
7 a1 V1 Q$ @; ]" ?& B& a14) y1 _6 |' U& l+ ^
15( F7 s- j, B) }0 K
类似的函数是 str.rsplit ,其区别在于使用 n 参数的时候是从右到左限制最大拆分次数。但是当前版本下 rsplit 因为 bug 而无法使用正则表达式进行分割:
! f* C! M7 S: M
O1 J+ }! g5 J% i6 i$ os.str.rsplit('[市区路]', n=2, expand=True)& K: J/ i- i% R" e" s' o1 r
Out[45]:
& r1 ~2 z- Q* G0 B. n 0
i% f5 |6 a5 x7 ?2 ~* C0 U7 T# _0 上海市黄浦区方浜中路249号* d" w+ { R# }2 p y
1 上海市宝山区密山路5号+ ^& C( P9 t4 v
1
& y* S$ u1 U/ z& z) A8 o) ]2
4 T& Z' x# E! d* x' }3
' }/ p. L) p: @' q8 p: j; {48 A# p9 q# k, O. T- r! u
5+ N4 @. y2 _' }! v' Y) N
8.3.2 str.join 或 str.cat 合并& G+ o4 ]3 y: Y
str.join 表示用某个连接符把 Series 中的字符串列表连接起来,如果列表中出现了非字符串元素则返回缺失值。
8 R1 B3 u! j; s/ A" cstr.cat 用于合并两个序列,主要参数为:
8 q! [3 ?3 X5 ~' P( E) Esep:连接符、$ I+ s1 ?6 ]% e' Q, K, _
join:连接形式默认为以索引为键的左连接
! J4 `9 P. O6 C+ zna_rep:缺失值替代符号- |; V* I3 [' l6 I! r: [
s = pd.Series([['a','b'], [1, 'a'], [['a', 'b'], 'c']])
2 n4 x) B J" { V9 C3 N) Q1 ^s.str.join('-')
% {' t0 }! b' N3 ^( EOut[47]: " g5 I' o; C* a T, L, @
0 a-b5 K5 p* g' R1 ~ O1 P2 ?0 D/ Z3 p
1 NaN
' q) Z1 y8 z5 Q/ b" {2 NaN& [ v# J. X4 _6 v7 i8 v( ]
dtype: object
e+ i# B' k G- _8 C' q1
* H& L1 I/ y8 X% u8 J2' j$ K* v; ~8 w- L/ V7 m3 z
3
6 I4 |+ s9 K- P48 `. H. O8 I; ^* e7 P% C
55 }% A" s: w2 `1 P
6
# U, Q1 u1 Z0 [3 E7
, B6 O3 f% p! i2 o' e! Ls1 = pd.Series(['a','b'])
3 ~# q8 Q$ E8 G9 Q/ cs2 = pd.Series(['cat','dog'])
8 I, E# q s& Ms1.str.cat(s2,sep='-')/ D9 c5 A: B3 u2 ^
Out[50]: ( \3 U. J: B9 L, P2 ?- ^$ N
0 a-cat
2 O9 M1 h+ P4 N& |$ m3 K1 b-dog
! g4 m$ ]0 \& G# |, s! [% f" ~4 U1 Adtype: object
/ `; D1 N% b- T- n
' S6 M% `, N; K' As2.index = [1, 2], s' p: E4 O- Q
s1.str.cat(s2, sep='-', na_rep='?', join='outer')- ]' L2 \+ j1 o& @3 _. ^4 ?2 |9 Y. A
Out[52]:
8 j1 |. C- Y i% K0 a-?
8 e5 p& a9 w- V/ G4 P! ~1 b-cat) g9 f5 ]" F$ g
2 ?-dog2 s$ H3 e5 J; K8 Q3 m
dtype: object
1 F' p# p9 @& W9 P7 V16 Y2 ]/ `( y3 i9 A8 ~
2
0 ^5 H ] _6 S: Z3
/ |. y- }$ c0 ~5 Y3 s$ G, {8 o4
; K8 o0 ]$ Q" [) J5 T: Y+ g3 w7 ]2 D: z4 k9 _( [
6
4 i: E0 G% j% N% }5 n/ N8 l- H7
$ C) W8 h# m% n' x" ^ j" g) W8
; I$ e5 P% \+ v* w( B( k7 u9
+ z1 w6 G v! m d9 Z9 o/ t103 E+ `. ^( M. ~7 m( W) D! W+ n
117 x% q' C5 L3 v" ~; S
12 `& v Y% b7 T7 q5 N
13
E( ]/ r% j9 C1 {, O; C& v14
. G, {" F: S5 Y8 R15- n% `# b4 ~& E" R2 ]
8.3.3 匹配. P% r& I K* ]! |8 ?1 E$ Z5 ~% f
str.contains返回了每个字符串是否包含正则模式的布尔序列:& P4 B* T3 T! p; t! d& P
s = pd.Series(['my cat', 'he is fat', 'railway station'])
" x1 ^ g4 r q$ |% Y7 U& ~0 Ks.str.contains('\s\wat')
& [5 U% V' K5 o1 L Q+ c
6 H# d5 G* R0 @$ o$ ]0 True' |+ s; Q; _3 }" p
1 True
) B1 \ d, z* X9 d D- W2 False5 j f, N: E1 _, g2 @! \0 t
dtype: bool
# I _& }' F/ t: O# T1
" ^& {8 e2 Y3 t5 x! R2, w# j" F6 Q$ T5 S* ]; @
3( d; ~1 U: ^% t: _+ M$ Q
4
' e% E; k) E- e4 q5$ F+ T, }5 k9 q+ y/ m* i& Z
6# s% f% ] t# c4 b% X' p
7
' L4 w% P, d6 T) q- b: S+ Ostr.startswith和str.endswith返回了每个字符串以给定模式为开始和结束的布尔序列,它们都不支持正则表达式:6 R; C+ ?* G# k3 s2 d
s.str.startswith('my')
0 B8 ?% \. P, D$ j& M- u; ?" ?
' n# f& d& i4 F8 w9 o2 A# X0 True0 Y! n, P4 q$ P! N: [* j6 z$ j
1 False
9 U7 o$ [- A* o8 E5 Q- E- l" {2 False
0 s+ N: ^7 I+ X3 m7 m+ ?dtype: bool
: C" o" D8 j9 Q/ h7 K1) B- I. h% M1 ~4 _1 h9 q
24 s% Q2 ]7 S) q2 u( ~
3
6 j: c6 a0 Z9 x+ i% V4
) V8 H; t0 ]( y6 v1 G5# k- c6 T9 j2 U% C; V
6; U# t0 i/ x& S" F* @( |, ~, _3 y2 z
s.str.endswith('t')
" U! p, U! V v+ y0 N5 ?9 ]2 K8 q: B0 ?
0 True
9 D( j9 J4 q3 }" \3 u! O1 True
. E3 X- p; I2 R1 a6 a; W/ I2 u; d2 False
% m9 B3 c- r+ N9 e; ]2 kdtype: bool
' M( [7 T- p- |/ B1! \2 ~/ [/ p/ y) _6 u$ _
2
0 {6 G8 Y& V P35 c k* X; R( m7 N
4" I+ e& B1 m# o2 Q- c6 J5 w6 {
5: o* A# i! e, D# }! ~. [2 ^
6
6 C; R$ c4 J+ Astr.match可以用正则表达式来检测开始或结束字符串的模式,其返回了每个字符串起始处是否符合给定正则模式的布尔序列。当然,这些也能通过在str.contains的正则中使用^和$来实现。(貌似没有python里的search方法)
1 b9 H* w/ H# t1 vs.str.match('m|h')
; J9 j) Q1 I3 c9 |6 r$ D# Ts.str.contains('^[m|h]') # 二者等价. d% `- Z$ x. ]" {
( X# R/ o) w- Q
0 True
9 L' S' L; u, `2 E: I/ x0 s$ f5 o3 j1 True
{. q' q/ b2 m# T s' t: D2 False& ]% T# P4 H" j) ^
dtype: bool
4 h, | T! R6 R; @1% d& N: d/ j( L6 |; o& W6 l
2
& C* _4 M0 E4 y/ f3
2 T0 f$ E& _) w0 O42 \/ N' w2 U' w) X
5- e D, J Z& G5 w# a
6
1 D- T: P( h3 z4 ]5 Y7
+ p' S. l( |6 R* ~. F4 R( ms.str[::-1].str.match('ta[f|g]|n') # 反转后匹配) s: e F9 _) G+ y, j/ j; q
s.str.contains('[f|g]at|n$') # 二者等价
# z8 A7 _1 m- m
$ w$ h' |. W+ q' d M$ a0 w7 C0 False' G% w1 |6 W6 X9 r+ a9 \) Z
1 True
! e& N0 o% ]3 N4 e8 D2 True4 M7 ^ |/ P7 M# N% ~: Q+ H
dtype: bool
& k* s; P+ o+ D- {9 o) u2 h1
6 }: l, P' L2 R0 M5 m28 v' w# P! l, U& m' z/ V; Y
3( K+ x; ^! ~( x+ t- _/ P' j6 P+ u
4/ z3 e7 Q- n% v1 ^
5, o8 m$ C/ w2 {! f
6
7 m( ~% y4 P( G- d) f5 x \7
- _9 f! B+ Z- {str.find与str.rfind返回索引的匹配函数,其分别返回从左到右和从右到左第一次匹配的位置的索引,未找到则返回-1。需要注意的是这两个函数不支持正则匹配,只能用于字符子串的匹配:$ o J; ]2 K: ~/ L+ U5 Q4 y
s = pd.Series(['This is an apple. That is not an apple.'])
1 z1 ]8 U6 ?+ O5 @3 T# d: k# _; l* ` f: o6 h! f
s.str.find('apple')
5 Z8 s* `- m2 ]0 DOut[62]:
9 b& l7 b8 t& x* S0 11
2 G+ G; ~$ Z) k! L7 B8 h" B" C6 a% |dtype: int64
$ [* d( C) V" o7 Z% R1 u; ^ P& _: B+ ~. i, j7 s
s.str.rfind('apple')
+ c' B# v: ~' a: Z8 V$ }Out[63]: / e& }& Y. j6 y) N( A; k' h: `4 n
0 33( N$ q2 N% g. i* P5 [# R! e T
dtype: int64
6 y& [% w3 Z+ x! [6 [3 ]1
: o- a' p$ O% d X: T2# c) {/ ]$ Y, ~" W& ]7 B. J+ _2 K! B
3
r% a3 f2 F4 e2 b1 h4 g+ l; a7 C, K( w) Q) p
5( M3 q. O' ?( i, ~7 r3 Y, `
6
" Q# D H5 j1 d- C( F/ u; W7 p, L7; k+ L- f3 V8 O2 m# c% y
89 {$ y# _/ A; m
9
, d& m+ f+ }6 w3 O10
$ a9 R+ _3 f: U# d; A. g( j5 }# @11$ m4 Z4 S$ w2 [& h, [7 h1 s4 K
替换! {; G3 v, X* t, Q( |/ x3 o
str.replace和replace并不是一个函数,在使用字符串替换时应当使用前者。
' @) F0 S7 z& e3 h3 A0 F# P ps = pd.Series(['a_1_b','c_?'])
2 n, J7 o" e" r: j7 d7 Y C# regex默认为True,表示是正则模式,否则第一个参数内容表示是单纯的字符串,也就是匹配字符串\d|\?- X$ x2 |: Y& y1 T @/ a" Y
s.str.replace('\d|\?', 'new', regex=True) % M; w; R, s3 W$ v6 |
/ X: ^1 n5 {! w* ]" M0 a_new_b
. R( D+ b8 T# \4 ?- t. {1 c_new$ J2 s7 T8 D% t) A% {
dtype: object
5 r4 n/ b8 z2 G, {, c1. _: X* H4 ]5 T- n4 c3 O
2
- z3 x, p# y) z+ R* \( ^3
( A/ |$ E) g5 L) R# ^5 ^' T4
$ J. p" B+ y% T/ X4 ]59 q6 K0 g8 I2 {
6
. N( G. Q; i* d& H2 |! x& i7 T7
6 a; ]( j s, ~6 `& h: @& s- P 当需要对不同部分进行有差别的替换时,可以利用子组的方法,并且此时可以通过传入自定义的替换函数来分别进行处理,注意group(k)代表匹配到的第k个子组(圆括号之间的内容):
8 u9 U: D. P7 D+ ~$ G- k$ F7 G4 a
s = pd.Series(['上海市黄浦区方浜中路249号',8 {, ]) J: p* Z$ o; y0 m. m" w
'上海市宝山区密山路5号',' Q ]0 O8 W2 @. R$ [9 w
'北京市昌平区北农路2号'])7 u; L9 p( ]! j& n4 i
pat = '(\w+市)(\w+区)(\w+路)(\d+号)': v$ m' }( W" k( r( G
city = {'上海市': 'Shanghai', '北京市': 'Beijing'}
+ q6 f& C$ b/ \district = {'昌平区': 'CP District', w4 j9 S. ?' B; J0 x
'黄浦区': 'HP District',2 A* u1 q T1 U
'宝山区': 'BS District'}* A/ N8 _0 o1 Z9 x
road = {'方浜中路': 'Mid Fangbin Road',# x( }& z! l& h' }' k
'密山路': 'Mishan Road',
* J: B b: b) o; ~ '北农路': 'Beinong Road'}
, |0 v |9 c* G! U4 fdef my_func(m):1 P0 Y" R' h/ r3 m" t# }) q0 r$ J
str_city = city[m.group(1)]
* Q$ A: c* k# w str_district = district[m.group(2)]2 H; L) C: K! b/ J
str_road = road[m.group(3)]
* t/ M1 Z' T5 o! L" z! o0 J3 y str_no = 'No. ' + m.group(4)[:-1]- V7 ?( m( R) S: {- G5 \) ]/ z
return ' '.join([str_city,
: ]. w! Q5 I# ~, e str_district,$ _- i1 {7 d, p& Q
str_road,
' m) L1 q* o; f( D str_no])
2 D, @3 P2 @" y ^) xs.str.replace(pat, my_func, regex=True)& i: E6 K* ?; d$ D8 A9 y) w
1 w; O# p3 a# z4 l
1
' n/ Y! b2 r/ d% o5 R2 f2( Z! x |( v1 `
3. O9 B$ }' ?2 p/ Z
4
5 @0 G$ [, t- X& |/ ~& B9 Z5
" g6 h4 O1 c3 s) } L6" V7 x# N) u: Q/ R* z# P9 S8 y) s4 n
7
: A0 A, O. U0 M( O3 y8
2 s: `0 x* r6 {. F/ C( `9
( ?2 n8 Y3 E/ s+ Q$ i2 Y9 B$ n. a103 b/ L$ o/ M6 q! I7 ^
11, f6 \ ]1 [. T6 S9 f) x9 N
12 S+ @# |6 a0 L5 X# }6 [% ]
13% r4 B0 `" N: V6 I" @
14
% f: u% U- o& Q& |3 K% }, ^15
& g2 F7 {0 H# e. y" U0 m166 Z; D! w1 N0 G( a w
17- D1 R1 f3 j M4 Y) k
18% w5 z5 P" m9 G( r
19
8 n6 O/ M6 b, e! Q" s20
+ W) M4 X" H. F' I6 O21
$ Q9 Z; f% p+ L# ]0 Shanghai HP District Mid Fangbin Road No. 249( D, o4 Y/ A! L
1 Shanghai BS District Mishan Road No. 5
6 X$ E8 y7 r. `1 A0 p2 Beijing CP District Beinong Road No. 25 n. o+ J5 W) g s
dtype: object6 k& Y/ `& W# z& }
1' T* `# ~7 [1 s, @/ H
2
( m O$ h( g; ?( k j- W. ?3* T y7 ?: ?: }" I$ f. Y
4
( y8 Y; U/ k* c这里的数字标识并不直观,可以使用命名子组更加清晰地写出子组代表的含义:2 o O7 ]0 e" M/ r6 e# _
, ?& e" k7 X& v5 @7 O& V; d
# 将各个子组进行命名/ D+ l/ G. I$ v0 H/ V
pat = '(?P<市名>\w+市)(?P<区名>\w+区)(?P<路名>\w+路)(?P<编号>\d+号)'" S7 e! m# W; |0 A2 B& C& g7 ]8 S
def my_func(m):1 g, p! c7 p. X& u) ]! ~0 |3 n
str_city = city[m.group('市名')]" P: \. S( e5 P/ c
str_district = district[m.group('区名')]% h2 T8 C' T* u* Y% g
str_road = road[m.group('路名')]
% P2 a1 e" Q6 Y- \* A% h2 S str_no = 'No. ' + m.group('编号')[:-1]
5 U/ ~/ }9 V; I- w! m return ' '.join([str_city,
. ~+ h$ d, w+ o1 `) a& R str_district,! A2 @9 V6 b! _# M5 n2 r( k
str_road,
; J3 B' M4 l9 E: o9 @# h2 m+ g5 F str_no])
) ?; m6 p, N4 ts.str.replace(pat, my_func, regex=True)
8 s3 A# j( H8 D4 h# I4 x1& y* {' K6 }/ }. N. ]
29 L$ C- z/ [# e$ h0 B3 t
3
3 M$ v/ K6 Q! b2 n4
: {0 }+ i! g( l) v; g* O: r; i54 c2 W8 M# g! G) y/ L
6
. J8 t+ c8 k, \- {4 f7 J7% m U- }( a8 n' k, q
8
% |% Z% c; i0 g9 ]/ o9
8 }: x* T; o" Z- Q# z108 n$ a4 N G% G
11
$ W! J: q, W8 J% a12' A8 L" A& t5 p% R
0 Shanghai HP District Mid Fangbin Road No. 249
+ G8 e( g \' }" p# G5 ?1 Shanghai BS District Mishan Road No. 5" s; g. E% ^; ]& W" t% y% o" C
2 Beijing CP District Beinong Road No. 2' {: C: Z$ A& d+ U+ [2 Q
dtype: object
4 ^( S: u5 d2 _3 }* j; ?% w* K) Z1
" j1 o' f# W8 t9 o& G" n( ?20 Q( F& @4 O% W4 ^+ v6 z d
3# C: z; L/ y2 i) X* m* m1 m# O
41 ^1 {2 |: E% s1 q8 \8 I
这里虽然看起来有些繁杂,但是实际数据处理中对应的替换,一般都会通过代码来获取数据从而构造字典映射,在具体写法上会简洁的多。
3 x# p3 h+ v8 W: |
7 M- E$ \- W" m& ], K5 ~" V8.3.5 提取
F- c' @& O) a! U* x% a3 ?str.extract进行提取:提取既可以认为是一种返回具体元素值(而不是布尔值或元素对应的索引位置)的匹配操作,也可以认为是一种特殊的拆分操作。前面提到的str.split例子中会把分隔符去除,这并不是用户想要的效果,这时候就可以用str.extract进行提取:1 K Z) m' A( T: @- G1 d+ X+ w9 S
s.str.split('[市区路]')' i. t: D, i* ^( }9 w. z
Out[43]: 3 D# J2 c- Y( X- H8 G
0 [上海, 黄浦, 方浜中, 249号]4 f4 j* q1 e' g8 P& c( l+ n# [
1 [上海, 宝山, 密山, 5号]' F) A" D- M! L# S8 \0 H; w
dtype: object
5 c% M8 C! x: M, U$ A; c
- K, _8 y) @- K7 Rpat = '(\w+市)(\w+区)(\w+路)(\d+号)'
h( {# b6 R3 k+ Q% ?# R' rs.str.extract(pat)
' Z+ W. u) Z/ N! m f1 vOut[78]:
. M/ ]! r+ ^( X8 N9 ]' f4 D& y7 m( i 0 1 2 3
& o9 g8 M. z# A. _9 x" J0 上海市 黄浦区 方浜中路 249号( C. y( |! B# _- ?
1 上海市 宝山区 密山路 5号+ o8 v6 K2 L7 w* I0 C, a
2 北京市 昌平区 北农路 2号7 x$ l3 N8 C/ L' b
1! ]. E4 I4 \' C2 e. J& n; D
29 a: n. q2 J3 I7 o- ~/ M, Y
3
% x5 [3 s, A( A) a6 ]: p& B' S2 g4) {: }2 W8 n2 L' d8 [: o
5
$ @& y/ L6 O2 ?0 P" {6
+ V* A; F8 P9 [6 X5 m( D) i$ O7
, ?* v. ] ?' K6 ]9 _% m8- y6 x; A: \. p' q
91 k. R1 u r8 `5 q3 F' x- R. `
10% C' p& E6 _# d. _' Z
11# l: v P/ c. i! E2 F
12( }. {% |/ _) H7 H j1 v2 K
13
O! K, X; J9 U8 e$ S/ s: A7 {$ M通过子组的命名,可以直接对新生成DataFrame的列命名:! h E$ _' E* d% e; N
7 I) Z: H* m) A# ] S$ Jpat = '(?P<市名>\w+市)(?P<区名>\w+区)(?P<路名>\w+路)(?P<编号>\d+号)'
8 r1 o) j2 t7 @s.str.extract(pat)
9 J2 i% ]" x! S9 v( B! S; L# tOut[79]:
( b8 B: }+ g- |* K3 t 市名 区名 路名 编号
2 M% w @7 H( E3 u# o9 l4 I0 上海市 黄浦区 方浜中路 249号
( A8 t B4 E: Y6 g* \1 上海市 宝山区 密山路 5号
2 l$ H! i& W" `" Z2 R2 北京市 昌平区 北农路 2号
# K* F3 j( k& O2 c9 h6 v8 K. @1 h' L3 b' m4 _8 M4 t
2
" K, O) @ @( v5 {4 n/ w# q" a( h! b: u3* Q1 p( Z; r }3 W) X) X& L
46 |, ?; B! b& _. ~. G. E `9 C
5
3 _" T) D0 { o. x6
) i7 }: N. w. X- {# k7
; P+ N; \6 f8 \# \$ S2 \7 zstr.extractall:不同于str.extract只匹配一次,它会把所有符合条件的模式全部匹配出来,如果存在多个结果,则以多级索引的方式存储:& h4 m7 y3 L) K4 n9 X+ D- `
s = pd.Series(['A135T15,A26S5','B674S2,B25T6'], index = ['my_A','my_B'])+ H) ]7 m3 i! e/ e+ {
pat = '[A|B](\d+)[T|S](\d+)'
4 O6 R* `# R: ?, V3 vs.str.extractall(pat)$ V5 v% X% N; g
Out[83]:* Q _' H+ F+ z$ ~6 f( H
0 1
s+ J' c! C e$ q: F* T6 y match
0 Q+ G \! b$ K1 ?my_A 0 135 152 w4 g/ N4 m! V( _
1 26 5
4 M$ H) v) B! Umy_B 0 674 2. y. u) G/ X# @$ {
1 25 6
1 e" r* \) o, y! D- `6 J9 M1 R" w1
) x9 p! b' b- H. i+ ?* J29 P: P; [ R3 A# M! d
3
* o% @0 y/ }0 V+ {7 ?: N9 c- O- @44 T* T+ W1 u4 @
5
8 G0 ]( D$ x! w7 c% U; h/ \ }6. @" V. y1 n# }$ N9 m+ k
7
U3 U$ `' ?' S1 h80 h4 ?: y1 S: E) ~2 ]: c
9& D$ f% X1 N* A. s1 C7 c' q, t/ j& |
10: |: m) B8 Z/ q4 p1 x( B# Y" C2 n
pat_with_name = '[A|B](?P<name1>\d+)[T|S](?P<name2>\d+)'4 a i" s% h3 J0 l- M/ L* w+ w
s.str.extractall(pat_with_name)/ d' W( W' e4 g) B' ^/ D; s' I; A0 U
Out[84]: 2 k: X# I3 d X6 S% [ y0 d
name1 name2
6 J' R' y2 A" `3 a match
/ h7 V+ y8 T- A: ~. W1 ymy_A 0 135 15
" T0 L, L% _2 _ 1 26 5- x( W; G7 R6 \# `
my_B 0 674 2
) Y6 _9 j: j6 u+ t 1 25 6
, n/ Z4 C. n6 Y% l+ n1
) ]5 }7 N5 ^5 R/ n9 `* \7 c6 N+ y* q27 _/ W1 ?, Q) Y& V5 m% I* V: u
3
1 \- T9 m$ c X/ f2 R4
/ T& f5 G! j: ?+ [" g- l% O0 c5
0 K0 M7 s5 F M* E* ^" {" }; ^6
5 y' u" n$ U/ {/ s( n; |' w) m7$ ]( q$ j3 b2 ]" \9 Q# w: v5 N
8
7 f3 Y$ u( D2 \, j7 e9 N9
/ O; V0 L: c, d3 dstr.findall:功能类似于str.extractall,区别在于前者把结果存入列表中,而后者处理为多级索引,每个行只对应一组匹配,而不是把所有匹配组合构成列表。 q3 ^4 I0 \3 v2 R6 p& ]
s.str.findall(pat)
3 E; ^) M: ~( B/ b" R10 X% S* O) u1 t5 W
my_A [(135, 15), (26, 5)]* a3 d) M5 b; @' X; [* o
my_B [(674, 2), (25, 6)]3 ?' \* s+ w' v: R/ Q, u$ V
dtype: object, A$ N$ Z0 G5 c7 }$ ?/ M
1
# J9 o7 q2 u6 u2* e3 [- h, b$ C1 Q( G6 s
3
9 ?7 W( N) ?' `3 X0 W8.4、常用字符串函数
! |" {& f+ @+ [7 Q# t2 o 除了上述介绍的五类字符串操作有关的函数之外,str对象上还定义了一些实用的其他方法,在此进行介绍。
! Y8 S' p$ s: x& A! M0 G
/ p; g! t: X1 [9 g! \' u/ M' n8 v8.4.1 字母型函数9 s) [3 G9 ~2 @9 @0 W/ j
upper, lower, title, capitalize, swapcase这五个函数主要用于字母的大小写转化,从下面的例子中就容易领会其功能:) \/ ^0 h6 ]# H6 s" x
! }2 u" _% \) V4 t3 [' Q
s = pd.Series(['lower', 'CAPITALS', 'this is a sentence', 'SwApCaSe']); z8 |2 V: x( |( k
}* n3 |/ Y' v" ?# E4 V$ M- X2 ]( |4 Bs.str.upper()4 N" p9 s8 Z/ Y1 i1 K
Out[87]: . z( ?' E0 [" S) k
0 LOWER
6 i8 l0 W; V7 r1 CAPITALS
9 A2 i6 e6 Z3 T$ i2 THIS IS A SENTENCE* q2 T/ l0 g/ X. _; n, Y# K' @
3 SWAPCASE! F3 d3 \: H w1 R( J
dtype: object
" O8 z% x; f; h8 x" Y
+ \2 d3 s! ]$ C v; Us.str.lower()
( N1 p1 `+ v# e# t9 ?2 {Out[88]:
: Q4 u3 R$ b1 j. l8 E2 Y/ Q% U! s ?0 lower* [9 C6 {( M5 d
1 capitals
/ I' I8 a% H, p. I* b, y7 v, i2 this is a sentence
4 E( ?3 W- e% o( o/ J: @3 swapcase
0 e+ |; a/ b6 j+ m! U* u+ Q4 Ddtype: object; f# t% |. g. R' b) F
3 o1 P+ F! `! G
s.str.title() # 首字母大写
* o6 g) t: q- n1 S9 n+ @. Z! _% sOut[89]:
, H e' i: c) i$ Z6 P0 Lower' B7 J! G- q; \( E
1 Capitals, M$ ~3 d2 f: e: |" S
2 This Is A Sentence% ~$ z( v$ R" ^' b
3 Swapcase
# E$ I$ J O( a$ Q5 l% Cdtype: object
7 w- ~1 j5 C# G+ Z; |4 g. ~
# l/ ?9 Z4 Z$ G# j/ @: Fs.str.capitalize() # 句首大写& q6 H4 W. U; o+ F R
Out[90]:
, c8 y0 H& I) R0 Lower
% s$ F* s, z2 }( x% b1 Capitals
- m9 G1 \* V: I# w0 }2 j, D2 This is a sentence# f8 \5 c2 n0 A
3 Swapcase9 G' ^% v5 A7 F
dtype: object
! I, R8 @: `, N2 n1 g, \3 N! } M- [, Z" V9 r
s.str.swapcase() # 将大写转换为小写,将小写转换为大写。& k6 `0 R, v9 P" Z0 _! r
Out[91]: 5 S9 R1 c) \* M, m/ t0 O
0 LOWER
/ Y' X+ e! j9 S. N) @( q1 capitals
% _' \8 v+ R1 H6 o3 e4 k2 THIS IS A SENTENCE
( F9 z7 m$ G: S4 z& l+ H( @+ @. x3 sWaPcAsE5 l6 C& s3 p& S. n3 t& S
dtype: object
1 }$ P, c6 }0 r* |
$ }) o0 G9 V* U" l$ Ws.str.casefold() # 去除字符串中所有大小写区别4 f) v( m4 ]0 P3 O
1 e5 ] W% z7 |, v5 r5 T
0 lower
! v; ~5 f" z6 I+ a1 capitals
# ] W# K' Q2 b) t( o8 H2 this is a sentence
! ~; g0 i7 O% \2 }( J& m" _3 swapcase
+ ]4 R$ ?/ i# a8 V# A0 j% n. B2 Y0 s9 ?8 X+ H
1
( ], w0 u- ]! x y; N/ {0 G3 j2
& {/ S' A$ y' a- ~3 U/ r3 `36 K$ S: P/ Z9 F# Z% R
49 u, F' e9 J; Z% S1 |
5
7 l9 u/ g% t5 F$ |65 h& k& k C+ r, G
7 P# D& w: l( p+ b, n; V- Y/ J) [
8
1 p6 y4 _( Q! T' V i8 ]" x9
- \0 N- E) a0 C, }7 Z, i4 j5 k \10* a0 J! Z4 A( T6 Z
11 w$ L# D1 D% o! r# X
12
+ M1 n `" X3 u" s# Z# c9 u l13
$ r& F; O5 E9 h( @% D' i142 N5 y% g3 d$ i; K* o
15
) I7 _/ ?! A6 O3 v2 l16
0 b" F, Q/ t/ o: t' ^5 G! Z17
& D/ U* {. |& u186 W$ K6 m! L ~! K: x" L- q# i
19
" `6 F3 q H9 ^205 o0 b$ U C3 u% Q, O
21
( C+ L( e9 n* P! r$ B223 v( B( t& C& c2 P, `
23
- P1 x+ f9 m$ |( g% F24/ E2 p; ~. F) j. B
25" L6 o9 S: r9 Y1 c
26) X$ C6 Q9 c" W
27
% D" Y, o/ O% e( g" t$ U28
% J& r0 |# x5 k+ Z* y29
4 ~) D0 I' M/ @$ _4 M5 u |3 \30
4 K: `- p0 I* L31$ q0 |' E* K, w7 K3 L
32
( X p! S5 s) z6 B338 w- h8 i2 n% m. {8 q+ E
34
1 T! F! a5 |6 ]35
7 |! C: p& u& B/ t- p. a. ?+ A1 N36
+ [4 W' p7 Z4 m& [2 N. Y37) E; O& X# D# Q/ b
38
: e7 K( V6 C" _7 V1 _39( |1 H/ t4 F% Y* _- v7 D
405 _7 f. a$ s" V, r5 p
41
! a8 o5 a w# d6 E% l; S42: v+ [- `% ~! v( M: v
430 m/ h X9 |; D/ ]# m
449 r& ~( c; b" g6 O5 p9 q
45
" w, ~! A3 s# Y- }8 A F469 P- v& X& g$ p& K
47
0 P ]# m( f( Z1 H9 t! m48
. u5 [9 B1 I- @6 N+ S3 d* j8.4.2 数值型函数
8 G- D! b5 e6 y2 E. r4 P' K 这里着重需要介绍的是pd.to_numeric方法,它虽然不是str对象上的方法,但是能够对字符格式的数值进行快速转换和筛选。其主要参数包括:
- K+ O0 B f6 G/ }7 m# ?
2 ^# T" }0 f4 r2 ]/ V2 u& d6 ]. K. i9 }errors:非数值的处理模式。对于不能转换为数值的有三种errors选项:- U$ ?: e8 {# ^/ ~- t, T
raise:直接报错,默认选项
, v8 n$ U( G2 ]/ S# Ycoerce:设为缺失值$ \2 }8 J3 Y/ @4 V) X
ignore:保持原来的字符串。
6 q8 `2 ]/ H& Odowncast:转换类型,转成 ‘integer’, ‘signed’, ‘unsigned’, 或 ‘float’的最小dtype。比如可以转成float32就不会转成float64。9 r$ z' f) K1 C
s = pd.Series(['1', '2.2', '2e', '??', '-2.1', '0'])" N* h" O% S& F3 Q
. _- o6 ^) g- _6 |pd.to_numeric(s, errors='ignore')
; t6 p5 a2 h+ Z' T# G; |Out[93]:
5 z" N {. `' ]; ~7 y0 C) t0 1
% ?0 \% U0 N& d, W. L1 2.2
/ C; \8 n+ m: `' m2 2e) O w; {* t" |. q2 K
3 ??
7 e0 `& }- h7 L, B4 -2.17 h& v; e5 G2 R( _$ N) u0 F ]
5 0
8 r5 ~/ d0 t, M. C" G; b* F; Edtype: object
# \: B+ D. k( {6 f% g; S& h L d1 [; ]
pd.to_numeric(s, errors='coerce')
5 d2 f" X4 X% N! P$ } F' v7 EOut[94]:
- Q* n* ^- F2 x/ Y! l2 i$ f0 1.0- K" m7 D7 F9 B& g$ w$ e
1 2.2: }/ @! z, ~+ q7 x/ u
2 NaN
# u- A# w1 E+ D0 A% d' t) b3 NaN- j( b0 e4 U5 {% g- Z6 T2 I
4 -2.1
. s! a; t2 A4 `$ b4 S4 Y5 0.08 n6 g1 ?; N2 r$ x) z( P% l
dtype: float64$ j6 z0 h( I P8 e9 M
( b8 x& \$ d8 y4 P5 e% h" F% q0 {$ i
1
# Q; _2 K: [6 t22 L' c% |% `: p0 J$ f0 m# w# s# o
3
1 E( p/ l4 L; |/ g4
/ I$ r- a1 }) T N5" ], n' Z8 t B: M% {0 }& I
67 _" v4 V! `( q7 U& {
7
' m) u* f9 j% R* T87 E+ ?% q: K+ {5 k2 H5 Q6 F, d
9
% Z3 e' t2 Y$ R% G$ Q10
& ?( T% G, w) [+ G: ^1 I11
4 B4 @! V, p _4 s12% {( l+ j" {" Y( H0 I
13
4 g' Y% ~" d6 l C. F14! [' F) @2 c0 j9 d5 v+ c$ G
15
B7 w# f9 Z$ ~- ~6 K& x/ P16
+ J0 c* k) y: U z" m5 Z6 s173 B+ E8 Q2 r+ Y3 j$ e; v$ ~% y: g
184 H9 B4 W/ K- Y$ h y
19
) D4 v* q& E% b! b" E, w20# u! i t$ x5 N
21* l3 Y0 {5 Q! C. w3 y( \
在数据清洗时,可以利用coerce的设定,快速查看非数值型的行:' `4 ]& \" J; o0 Z! }' w2 I# w
4 W* w. D T6 {5 O6 }! t' S) |9 k- d
s[pd.to_numeric(s, errors='coerce').isna()]
# t' G5 d. Z# L, ~Out[95]: / D. ^; R9 ]/ O2 n4 ]8 ?
2 2e
9 R2 _/ U5 S* l" u( l0 i0 q$ ~3 ??
* M8 B/ N4 c4 i1 x- N: hdtype: object1 V/ W. T. m. K
1
4 I' T+ u/ {5 s; S2
3 M9 o% O6 H N! T5 A7 p4 ~) F3" u1 D P' R! D6 J( v0 g
4
0 v( o' u1 j3 G& v' E# |55 e1 X7 y/ d \
8.4.3 统计型函数
$ X" `" y3 Z: N$ o3 }# j. Y count和len的作用分别是返回出现正则模式的次数和字符串的长度:
2 z' W# O5 i* X: v0 P: q% V
8 f# B- A% d4 cs = pd.Series(['cat rat fat at', 'get feed sheet heat'])) n$ P6 j1 }5 s3 v2 k9 X
0 f h( y4 W" C" w$ h/ Bs.str.count('[r|f]at|ee') # |左右两种子串都匹配了两次* K: y6 ?' N/ t
Out[97]: : E2 O; V0 q4 [6 C
0 2
, _6 o% q% T; C+ }4 B; `! q1 20 s; d6 x! `" N7 ]4 y7 i
dtype: int64
* {+ Q: P6 }4 L* @# o& ^+ ^6 {
4 y* o2 @' P) _$ Vs.str.len()
5 i( Z: E" p; {9 x2 U5 KOut[98]: ' r @: h* v% h R1 u
0 14: W+ f' a0 Y3 d$ S0 c5 `$ w
1 19' X0 [% j1 p9 x6 u
dtype: int64
, V/ d( t: ~4 y1
2 _3 u: a" w# \ S2% K- R( X P+ S- |
3
: A) Y" M: C8 e" ~4 C3 ^8 s- q4 m) N- ?! Q4 W' ~8 Y
5% n! c# ?4 R+ l l
6
3 q! n$ G, F) R0 X% o# T7! Q! \ |; D6 V+ ^
86 j7 Z C1 n, [. j" J
9
" c% E. U% e5 R5 ]( |' l0 d10
5 K+ V4 @. z* i8 G7 d5 X11& z# [5 w2 _' n) k( G" j2 j6 v5 r
12
9 g6 E- d7 C6 j7 j: Y13% V) T4 \! I& I( f' H, N
8.4.4 格式型函数8 G# E/ U# K. q# |& b5 C
格式型函数主要分为两类,第一种是除空型,第二种是填充型。其中,第一类函数一共有三种,它们分别是strip, rstrip, lstrip,分别代表去除两侧空格、右侧空格和左侧空格。这些函数在数据清洗时是有用的,特别是列名含有非法空格的时候。
2 [1 u! [4 J8 v1 V
5 P( m2 s) l+ C- j, I [+ K# Mmy_index = pd.Index([' col1', 'col2 ', ' col3 '])
8 K; S2 z3 X" C3 l1 C
2 d2 D# N+ p* J% q5 e( V8 Omy_index.str.strip().str.len()) Y o" }/ [6 ~) E: i
Out[100]: Int64Index([4, 4, 4], dtype='int64')) I# ~' T( U$ r# [/ ` M3 f, N
# q1 A [8 L0 g
my_index.str.rstrip().str.len()) t6 Q& L/ G p/ y2 s8 q1 e
Out[101]: Int64Index([5, 4, 5], dtype='int64')
6 }* n ?5 j) c! u
/ J% S; F0 h; _8 u9 D0 Hmy_index.str.lstrip().str.len()% p+ c* `1 y/ O E8 |& u
Out[102]: Int64Index([4, 5, 5], dtype='int64')( S9 O' p$ C4 ?& P
1
$ {8 y1 j) y' R) P: P7 l# k, U2
7 a( a: \( {* n2 k, d7 P1 I3
W g |4 H! w40 f: Z. y5 x0 B6 ~) ]
5
, o) v' Z$ F y. n; ?6# V! a9 v) t8 U( W4 Z; o# j3 ?
7
% }4 u% d! N, d& v$ r9 X5 `& O4 _0 F) _8
/ y1 F8 h K! p9
" G& |1 X% ?2 k102 V' }0 B0 K+ }) X$ A" g4 A
对于填充型函数而言,pad是最灵活的,它可以选定字符串长度、填充的方向和填充内容:& `. s8 m6 g& d- m
+ m; d7 p0 k. ?$ W
s = pd.Series(['a','b','c'])) Y" P9 \$ p+ L( ^
) L5 w% L" w4 C( T B/ }/ l& Ys.str.pad(5,'left','*')
7 x: k: f6 A$ F. G) G% q2 lOut[104]:
) ?- D& w2 v/ L5 ^' C0 ****a
^( @" z# d: |1 ****b
% a7 t5 |: @3 q/ @6 F( U4 B: _. g9 M2 ****c
H" E- O7 ?$ [& j. F5 R! ~# Jdtype: object; b# V7 T$ U7 d/ `3 x# i* y
; a) |- Z* o. [* [
s.str.pad(5,'right','*')
7 g* P/ K/ l# W8 t0 ~Out[105]: + s: Q, i9 y# j; y. z
0 a****
& |6 T _- u# t3 C% M0 p! f: l1 b****
- Y+ {( v P( ]) b* }$ [2 c****6 i) `/ _$ g* I% I4 {
dtype: object
; h, d$ j" V3 h# y: g/ w0 G9 K9 m1 t. v, o8 @! g5 L) ^
s.str.pad(5,'both','*')
4 ~2 Z: W z% g8 GOut[106]:
C) A' B# h+ E" }0 **a**% S$ w, H' n# T1 _% o0 K' q
1 **b**
; O* X' m; ^; c) P2 **c**
+ k* |3 z {2 ~( ndtype: object5 w* P8 t \9 q) J7 ^! `2 D
: ?! W. Z H2 G; f& q. H; ?1
1 k M8 k5 {% y& J26 O+ T% F, @' V3 H: ^
31 g* h2 L) T9 F( R7 t+ \
4+ v. B. G* l0 J' R/ G) ?$ Q
5
5 [! j/ ~4 O. m69 P5 L2 b: i* ]7 g/ z6 {
70 D' P1 x9 @( k; _
8
7 s+ E0 U" g0 s& Q$ r+ }! a; u* A; m) [9% L( | Z2 ^) F
10. S9 `0 o2 u W+ L N
11
0 w1 Q4 m2 Q* U9 H- R12
0 I) j3 U) x6 S5 p13
+ }7 [5 u4 l( z14
3 W. g6 r0 V! w% o15
# U% m' |, m& B) q, s3 `& b$ B, m16
% l8 j; |! V9 W% f17! |- U1 z, f7 \6 O d, X$ u3 E& E
18" q$ Z9 W/ B2 J$ G3 S8 U+ @0 q1 q
19
3 l' F! e# { F. g% N- @20
4 }/ j& d. y$ R7 f1 `" r21
6 |( y# l( ~) e# X/ x5 e; c w$ {221 v# u, Q1 n& p9 |8 A
上述的三种情况可以分别用rjust, ljust, center来等效完成,需要注意ljust是指右侧填充而不是左侧填充:& @8 V3 O" }: l' g! a) d# i
0 F5 ?3 f- X) D q* Q* ?
s.str.rjust(5, '*')
6 E/ L4 g, i, B( t+ v1 H8 o+ [Out[107]: # D: Y1 b- x1 E6 g7 m* V) \
0 ****a
3 a/ N/ Y& K, ?+ q. F4 f1 ****b
9 U! k g( @. y" y& b2 ****c! S" \; X4 J8 N
dtype: object
8 f9 O% q% e2 l) [0 i4 f4 h h& h' {8 z+ x& |' w; U
s.str.ljust(5, '*')
- ?9 M2 J, s% }) A$ `Out[108]:
# g8 o$ ]& M6 U% }# n4 Y0 O0 a****
3 X6 ^6 u% F; e& O. m1 j( K; g1 b****
* x; W& _4 N! _- ]1 H4 S2 c****4 u( m7 {' X% V& p
dtype: object
- w$ V# X2 [! Y" ?2 V# N( Z# e9 {7 U( ]5 H4 p/ y3 H; O
s.str.center(5, '*')8 z/ u. H2 P& B7 Q7 Z1 O
Out[109]: # h4 J) H* v$ ~9 W1 f
0 **a**2 x" F! p- ~9 b3 a
1 **b**
" \2 E6 Z2 a9 t2 C A/ W3 \2 ?! } k2 **c**
4 k3 K3 v8 W) cdtype: object
4 z& ?, L% A! n; V3 }0 h2 _' N$ u4 [
. T% H9 i+ r& @3 l. L1; B) `3 ]1 w4 a$ m9 n6 b+ q: l
2
! {2 t9 S0 ]9 @) ~) R9 o% D3
7 ~& ]* j7 c! w* h' |$ Q* X" ]41 U$ [ ]1 ~, ~& E& I* P) ^) ^
5
1 h+ Q8 u4 Y- {+ u6( \8 T6 s* o9 `* e3 _
7; q" V0 f8 p1 L( s& \) ^6 `
8
( T0 X* h0 I# H. x! F6 s3 H9
2 o3 b2 p3 N( t9 Y2 s& i- u" ^; d10/ D& r; Y; [8 ~ }" a0 w
11
" n& n, ~7 K. n% L# l12 y: q s( j1 V
13
: Z+ J9 [' i- j; O3 `14
' x" s3 s- u2 G6 u15
% p: X5 }& h3 F! |, ^" w! y16
; l3 c6 r: M8 O( ~+ [% l17# B. Y7 O% L- x5 `# t
186 t1 a X# h8 U; q8 E
19/ A. E4 I+ V q' d% g3 e1 a
20 P5 A9 m6 ^0 ?1 w3 H
在读取excel文件时,经常会出现数字前补0的需求,例如证券代码读入的时候会把"000007"作为数值7来处理,pandas中除了可以使用上面的左侧填充函数进行操作之外,还可用zfill来实现。3 n: Q j2 J. ?6 N' U
% M! ]6 D# G. `
s = pd.Series([7, 155, 303000]).astype('string')
5 _4 K" S5 ^5 c$ O( w9 q" Y& Y8 e* b" }
s.str.pad(6,'left','0')1 o& ?0 f+ R# U- w! Z
Out[111]:
1 R+ C% ^5 r1 x1 ]0 0000071 L) ^% K; C8 a( Q3 ]$ @# Y: v1 M
1 000155
+ Y: p$ A) j, x2 303000
1 t; g& w r$ K& ]) i0 ?dtype: string
/ D/ G7 ^% j1 h0 z% c8 o+ \2 D7 K" Y0 A9 X4 m- @
s.str.rjust(6,'0')
* U& w: }+ Q+ K- S+ Z, lOut[112]:
% Z' h G5 W0 a$ p& P" O0 000007
* H) S0 X# h9 O# c1 g: O1 000155+ y) `6 C5 U4 K) D V: Y
2 3030000 E5 f/ E& w. p4 }1 ~
dtype: string
7 K' M* u# H) M; z: b5 Y" r% [& H3 C+ q8 j4 g, i. y
s.str.zfill(6)5 V7 n8 A6 n- n) Y0 p
Out[113]:
' |+ P) P) ~! ~& T; P5 n* N0 0000077 f( P4 W7 a/ Q% }: K+ Q1 r
1 0001558 ~+ w8 U# H0 U: y5 k. w
2 303000; d1 {: T, p7 o
dtype: string
1 I5 M- R! K# G. H! s3 [0 F5 h/ b3 m' S3 A0 d0 q# |& \
1$ h; y. c4 r7 W: `# q$ P
2+ O3 I! D* M0 S% j/ @+ `5 w3 y% `
3+ o" M- q. Q$ U" [5 N8 `# \6 A+ \
42 K( l; H1 O3 O0 _
5
: l9 t0 Z3 T& o69 j6 m3 ?8 B S; g6 g( w0 R
7
$ ~. B0 x" B$ }2 v- V7 O8' d5 _8 r: c, l5 c. n" [' ]5 Y
99 \3 i# M0 M& F
10
$ o1 u0 n! a( J9 g11) W/ m: m' {, K& q7 p, J( w8 \
12% k) ^. o2 Z% @# A; `
13
! b3 X2 p8 O8 \; e2 [* n14/ \2 c" I+ [: g6 {- E1 N, z
15
! |) ?2 P: N' V16
1 `! Q0 _ u' b- @, K& W; B2 ^ S17$ A# s5 x7 y3 @" l" a3 l. v! Y* a
18
4 @7 o& o+ o( K# n9 G" E L* \& d19 v, A4 G3 H& D2 i" |
20
* l1 |- ~4 P8 l- ~1 x3 _$ l21# a R" Q3 W1 L, Y: j2 ]. E
22
0 q0 @: Y6 ~3 m9 T( c8.5 练习
( W. {% y7 p& A, i0 f/ QEx1:房屋信息数据集
1 s1 w9 j4 q% h3 g' R$ ]; j" ^3 N现有一份房屋信息数据集如下:
$ i N' K% [: U: K! }% {! x" R7 m' X& H
df = pd.read_excel('../data/house_info.xls', usecols=['floor','year','area','price'])
1 P% [6 P% i/ I" B: Qdf.head(3)5 ^- m2 C3 {' x3 ?* R
Out[115]:
$ s0 j8 p: w9 {; I" @' \- u floor year area price
" Y/ y& D9 ?& M) @0 高层(共6层) 1986年建 58.23㎡ 155万 a6 K: J& Q. R, f1 Z
1 中层(共20层) 2020年建 88㎡ 155万
8 a0 J: o4 R+ y6 V0 G% a3 `, G2 低层(共28层) 2010年建 89.33㎡ 365万/ V, z6 [# A* \ Y I. T" Q" A4 A2 u
1# Y( f6 S8 w5 O4 e7 K8 C
2
: j+ {5 @1 i! d! G' i0 p( O6 [- a3% f n2 A' L% @4 Z1 p7 m
4+ p% x5 @+ S+ u: Z. j' w
51 ~9 X& }! E" B% _1 m
63 y0 Z# H1 j. X5 O# f: d# z }( S0 q- p
7! d9 m* f! D; A2 q4 w1 \# @
将year列改为整数年份存储。
/ O$ `3 l* P1 y# Y将floor列替换为Level, Highest两列,其中的元素分别为string类型的层类别(高层、中层、低层)与整数类型的最高层数。
# R: [9 S' ~% M i( k% q/ d+ ^计算房屋每平米的均价avg_price,以***元/平米的格式存储到表中,其中***为整数
8 Q U+ d' a, U- j3 [! |+ p将year列改为整数年份存储。
J: ~( R N8 A" `"""/ H, r: d; r1 W* n. D
整个序列需要先转成Nullable类型的String类型,取出年份,再将年份转为Int64类型。
8 M4 b3 F) H6 |2 d注意,转换的类型是Int64不是int,否则报错。即使astype加参数errors='ignore'跳过缺失值,8 f& X6 W/ z# `+ Y* F+ }) p
转成int后,序列还有缺失值所以,还是变成了object。' M! u' M4 Q4 X+ H, q9 }
而整个序列转为Int,就还是Int类型,缺失值变成了 pd.NA 。
5 ~( S7 l/ \0 p/ A! q- {. e7 {" R0 q"""
! F" j2 _! d5 G4 D% b& }df = df.convert_dtypes()
" z1 y1 i. p! [) P8 kdf['year']=df['year'].str.replace('\D','',regex=True).astype('Int64')
. X I" W8 B* }, b( b7 |df.loc[df.year.notna()]['year'].head()& y' c1 t7 u0 x2 T+ @# ^1 m; r
! _, @, B4 _* y& F. {0 19862 K. a c- g/ }" M' @( M' ]6 ~( d
1 2020
" |* a/ a2 V* c! ~! z2 2010" S" l# M- U5 y% U" @- `7 `, f
3 2014- X/ k/ g7 ~' [1 Y( t1 \+ Z( {
4 20158 p& k6 D5 \5 M8 a( g2 Q
Name: year, Length: 12850, dtype: Int64: g/ r" t8 R/ p+ ]/ ?6 A% i
/ K9 a$ y+ f2 C' a4 t& l8 Y1
F( Q1 ~, | N. J5 e2( ~5 M' [: L7 p5 S, R
3& K- N# @$ J& a
4
* o/ t- {: Q2 `7 `3 G4 ?$ J58 L! t" F: z6 ~: R' k# o% y( k" r
6; ~) S6 r( K3 Y) ]& y* b
7" ]3 A2 v. t2 j9 V0 V+ g) T
81 v Y. L4 b2 P3 S+ I' U1 Q9 {
9
: o* A( d+ q: Z+ S# n& @10
+ v1 Y1 B) f/ G- {4 h" b11' A% x* X! m3 [7 ^
125 \" h/ _2 l7 ]: j7 k3 w
13
( i5 {2 R* s5 a a! I' K% G14% R' S8 V' l! X8 A9 J4 }
15" Q# Q8 T& w) j, o8 m
167 Q- K ?) D8 `; q
参考答案:
% a! h) q' v) B8 T1 J* S
3 X& L; ~9 R/ H5 I不知道为啥pd.to_numeric(df.year.str[:-2],downcast="integer")类型为float32,不应该是整型么 ~7 O* P4 Y4 M& |1 A/ Q
+ I- O/ K0 ^: r+ p6 W- r
df.year = pd.to_numeric(df.year.str[:-2]).astype('Int64') + e, J' ?# T- ^9 D
df.loc[df.year.notna()]['year']
& R2 M0 c! H/ v& f1. V C3 G6 a3 o. `; X4 n6 G" f- K
2
: p. K$ c' h+ [将floor列替换为Level, Highest两列,其中的元素分别为string类型的层类别(高层、中层、低层)与整数类型的最高层数。
0 `7 Y, K& N( L# U' | s" H" ~9 hpat = '(?P<Level>\w+层)(?P<Highest>\(\w+层)'
. Q3 z* W! G) F3 k! V5 adf2=df['floor'].str.extract(pat) # 拆分成两列,第二列还是(共6层得形式,所以还的替换一次
+ i$ A1 X, h; n7 m+ t; odf=pd.concat([df,df2],axis=1).convert_dtypes() # 新增列拼接在后面,再次转为Nullable类型6 L: @ V$ D% q( U5 K, g
df['Highest']=df['Highest'].str.replace('\D+','',regex=True).astype('Int64') ) x$ J& A! {0 z# e
df=df[['Level','Highest','year','area','price']], B4 O4 f. Q4 v. M/ O
df.head()
+ a! l" ?- i0 H f" A9 e* U# l7 F! |$ d7 K' R# y
Level Highest year area price
: [' D" \3 s' u9 F' E! |3 _, J$ J0 高层 6 1986 58.23㎡ 155万; G' [4 h/ T+ k' p( z
1 中层 20 2020 88㎡ 155万
/ w$ K( t3 Q3 K. J9 }2 低层 28 2010 89.33㎡ 365万
2 I( N$ L0 T R0 a6 j7 q" M5 `3 低层 20 2014 82㎡ 308万" U9 v3 ]) L& D7 }+ ]
4 高层 1 2015 98㎡ 117万. a8 R) k0 | T6 `4 B
1
- R& I' ]& o- {' I h$ ~3 v3 }9 f- m3 r2
3 Q. n. f+ ^( s; W4 O, T0 h3
! [ j) [# u' A5 ?0 K4) x/ d4 t7 K# ]# \: |4 g) `; g
5
K. u/ g/ l8 S; b' y6
& U! Z3 ]$ c% {7; X6 W) r0 n+ K: P. H/ U/ i
8
4 ^$ `! t G! x! | O* H/ B x9: B+ m$ m* R" B0 c3 u! _
10: A8 z: w' q. G0 f# a
11% h2 J; |7 s c' W. }; e- b% i
12
8 @/ @1 _4 ?( o6 Y% x M" K138 c4 U) u& ^) r& Y# X) {
# 参考答案。感觉是第二个字段加了中文的()可以准备匹配出数字,但是不好直接命令子组了
4 E6 c% w1 J3 `, x0 Rpat = '(\w层)(共(\d+)层)'3 I# y6 Q5 d0 V! |( q
new_cols = df.floor.str.extract(pat).rename(
7 Y1 L: C# Z% T2 F! K) V3 U columns={0:'Level', 1:'Highest'})$ [0 f7 M2 P$ B8 @
, U( `4 }* A. a) q" i
df = pd.concat([df.drop(columns=['floor']), new_cols], 1)& }" C0 L5 M3 d
df.head(3)
1 r a T3 O- y2 k" |! y
0 o( u( [4 p4 hOut[163]:
3 H3 K, ~+ Q, U( T year area price Level Highest
* g, x$ @; I0 v4 ?+ n4 o/ U0 1986 58.23㎡ 155万 高层 6: ~, Y" J6 \) x& V
1 2020 88㎡ 155万 中层 20) x) N( q. h( G* B' c
2 2010 89.33㎡ 365万 低层 28/ O: v( y! A* x) j
1
4 t. n6 t9 W8 q9 b8 F8 W& q- _! y2
$ w& ^3 h6 }: X5 K30 _# e& A- D6 T8 g
4) w' ]0 B8 @7 x
5
3 K& X$ u- T0 u& J$ _68 K, y2 c! F1 I! [( c* L
78 k( P) b5 P& \7 p3 R$ | k8 O" o
8
* E/ Y5 T8 M4 w' V+ d t9 g/ c; S1 ?& U/ E
10
% x, \1 T% T* p& P5 o11$ S/ D2 M, s! i u4 {
121 }! s* S/ I% X9 W$ W
13
$ B: p" e1 d' a* L8 \计算房屋每平米的均价avg_price,以***元/平米的格式存储到表中,其中***为整数。; {; I- z9 a/ |) Z) e
"""
9 t$ H; Z* u% j7 K- W# ?$ ]str.findall返回的结果都是列表,只能用apply取值去掉列表形式
$ i7 }* \+ f$ C7 ~' C参考答案用pd.to_numeric(df.area.str[:-1])更简洁4 l9 o( T: y% Z; S. r& X4 F1 K
由于area和price都没有缺失值,所以可以直接转类型
. _" S! T# D8 I# O; p- {. D$ H0 K"""! z8 O5 U0 l C2 j" q$ ^- u* x k
df['new_area']=df['area'].str.findall(r'\d+.\d+|\d+').apply(lambda x:float(x[0]))
: S( z4 q6 l! Gdf['new_price']=df['price'].str.replace('\D+','',regex=True).astype('int64')7 d, q$ I, l; i' W6 ?1 l
df.eval('avg_price=10000*new_price/new_area',inplace=True)
( k% x9 D6 }, L; H4 Q5 N# 最后均价这一列小数转整型直接用.astype('int')就行,我还准备.apply(lambda x:int(round(x,0)))
- Q8 m7 F& c3 b, O+ E1 c, {# 最后数字+元/平米写法更简单3 j' k% X! c/ D2 O9 F
df['avg_price']=df['avg_price'].astype('int').astype('string')+'元/平米'* z [8 u% c) E; D
del df['new_area'],df['new_price']
. x, |9 b. e" ?& J' D/ gdf.head(): o* j$ \4 C+ p( d; W
9 }8 f' _8 Z& f' K B Level Highest year area price avg_price" j* E3 U1 v, q# n& E% o, m0 s
0 高层 6 1986 58.23㎡ 155万 26618元/平米
% m7 Q3 t2 `- [$ p3 j/ A# K M" W1 中层 20 2020 88㎡ 155万 17613元/平米7 F' e% q$ H, L) I- _: g
2 低层 28 2010 89.33㎡ 365万 40859元/平米
. }; m6 S1 f, r5 F' v6 ^: ~3 低层 20 2014 82㎡ 308万 37560元/平米' k6 [9 l) Q% h: U& g# }. X
4 高层 1 2015 98㎡ 117万 11938元/平米7 v; l) H( a/ J3 Q$ \0 K
* I6 J9 E- ?9 @# Y9 U: I0 e1# h/ K- C( Q; d$ k
2
7 @8 G& I8 h. K0 s9 X$ O3
4 S$ K4 @8 r, G1 i& L4$ s7 I- l1 D) o" P
5
: D6 `2 \0 I! x. W6 s65 b( D' Q; B; }
7) P' S7 L5 L- ~+ v
8' s$ _$ D; D( b4 S+ U- O
9
" F0 T# u7 |% B9 u6 N10
" _$ j1 {6 @' b0 P K8 H( F11# i5 B. q o. Z% `7 m$ e, t: z6 i4 x
12; K( |7 y0 r a
13
& N; m7 T: v" C142 a0 c( B' K* d; f0 y: f
15
3 V: h) _; Z2 M! `& o- j6 K16
! l8 i0 W- D9 j9 g17
# o5 z+ ]& K% {/ Z& R( ]18 ]; X+ G" T5 S0 h7 d8 m" p9 w
19
' f) @# _) _7 p% O20
2 z P7 L9 ^# Y: n, w# 参考答案
- i; G( V" Y% @7 x j, N5 ^- F( as_area = pd.to_numeric(df.area.str[:-1]). ?4 s2 X$ T5 t, p, r
s_price = pd.to_numeric(df.price.str[:-1])4 ?, B% I) m1 B4 `4 |, N$ Z7 ?+ [% n/ k
df['avg_price'] = ((s_price/s_area)*10000).astype(
- y: X" y8 y! ?/ G; A; @6 a! e0 h 'int').astype('string') + '元/平米'
0 u5 P9 ]" z8 M
" D# s2 E2 w* L$ x- g' o9 Zdf.head(3)8 W* E9 v, ]: p: X, l; d
Out[167]: 5 _5 U6 f. ?& G# c- W, j" x5 M/ i
year area price Level Highest avg_price
8 p' k+ e" r+ {$ L9 M0 1986 58.23㎡ 155万 高层 6 26618元/平米
: J- x5 v8 W. r1 \7 E( z v1 2020 88㎡ 155万 中层 20 17613元/平米' _* O: {$ ? n8 w
2 2010 89.33㎡ 365万 低层 28 40859元/平米4 h2 f9 {) y6 G s V3 i, e* X1 X2 L
1" g! |( G" j- a4 I( ]3 a: x
2+ T1 E/ I' d# R' A
3
6 }6 \; h( g5 D. p) z& B/ b* |4
) l8 q1 H q" f( X ~6 i9 }5
, t* M) ]% w& [7 n' T61 J( g& e' _: w( g
7
4 u v( l5 e% ]) u8 z81 x4 n% ]; ?! m( g) m/ c
9
( ^ [1 Y( C9 f6 U! @% Y10
+ t% c/ j" ]* r D7 E b11; a+ w) K( j2 I2 g1 Y# I
12" Z% T9 Y5 G3 A. p1 l* r, u3 z
Ex2:《权力的游戏》剧本数据集; G+ H9 L0 T) Y
现有一份权力的游戏剧本数据集如下:: T; {3 C ?) u0 U) ]
0 Z( q: l9 Z9 Y& X; ^df = pd.read_csv('../data/script.csv')3 e" e" k7 h6 _7 y; e! i
df.head(3): s+ O; A6 F7 a @4 v- e
4 Q, v V1 L( B1 ^( c! b, G4 v
Out[115]:
$ h! |3 `9 n( x0 ?Out[117]:
& ?% u, J$ x/ B$ C3 n9 @ Release Date Season Episode Episode Title Name Sentence: |4 Z% a4 l& v6 N, X [
0 2011-04-17 Season 1 Episode 1 Winter is Coming waymar royce What do you expect? They're savages. One lot s...) U( k' k& p- s* K' b) A
1 2011-04-17 Season 1 Episode 1 Winter is Coming will I've never seen wildlings do a thing like this...! b- N D" W7 k3 [: j* j4 B7 D
2 2011-04-17 Season 1 Episode 1 Winter is Coming waymar royce 1 o+ y }9 h9 ]+ c6 Q5 e8 X
1
' ]# p6 o' h2 k6 m2
5 Y$ r. a o- G, R! ?( D1 ^3
* n' T( G0 L' ~9 O4
2 c/ p- F% C- }5
% M, R- Y8 z' [60 c" o9 O. m3 M4 |. r% }! K/ V7 ^- `
7
5 G; \9 \: u8 X; Y8
6 \" @* Y. K. N+ E5 U9. ]; ?3 d/ u% F
计算每一个Episode的台词条数。& v% t P/ G: B& Y' i
以空格为单词的分割符号,请求出单句台词平均单词量最多的前五个人。9 U: f8 {; T. |7 {
若某人的台词中含有问号,那么下一个说台词的人即为回答者。若上一人台词中含有 𝑛 个问号,则认为回答者回答了 𝑛 个问题,请求出回答最多问题的前五个人。) M4 o) I1 d! D& r( w
计算每一个Episode的台词条数。7 h+ L7 i% Q0 C4 o
df.columns =df.columns.str.strip() # 列名中有空格- t, k( w1 Q+ I6 a
df.groupby(['Season','Episode'])['Sentence'].count().sort_values(ascending=False).head()
+ {- A9 j" w% D; P7 C- I# K
$ p/ s( R( l% d" |! F0 eseason Episode
( Q8 _: ~. S- ~Season 7 Episode 5 505
0 T/ Z3 {* o1 q5 r- N5 oSeason 3 Episode 2 480
% |1 L& x. k, F! xSeason 4 Episode 1 475. o, T" v4 n. h; o5 n
Season 3 Episode 5 440
( }+ b. p% u* i" R/ c8 E. ISeason 2 Episode 2 432
5 I: N8 Q; c4 Z. _" h1: }. C7 D0 t* _" g- b
2! H! P3 \' R( s9 ^' p
3" w! H$ h; ~5 n& b4 g3 d; ~
4
$ f) h* F+ U, |; F0 A! r51 i0 z4 e' N/ U2 ~1 u, {( O& ?' r
6
4 w. R( }) p/ A, C7
* q# Q' q" B1 {8
- O& s, J5 n! o$ L9" \) y2 |1 n d4 R
以空格为单词的分割符号,请求出单句台词平均单词量最多的前五个人。
% S4 _* N) w: g+ T! Q! g# R# str.count是可以计算每个字符串被正则匹配了多少次,+1就是单词数& A% {" t7 g: I, M; c
df['len_words']=df['Sentence'].str.count(r' ')+1% \" n( s, r; c I2 a
df.groupby(['Name'])['len_words'].mean().sort_values(ascending=False).head()
3 u) \+ n$ j2 t: _! z; o8 ] l" P4 L! d3 q2 k: B
Name% F0 b: K; ?, D' ]: `1 n7 j) V
male singer 109.000000. o- d9 Y; Y: H* X5 J. e4 U0 W) ~4 p
slave owner 77.000000
3 T* a9 C, N6 I6 V3 nmanderly 62.000000
- E3 H. ]! N3 elollys stokeworth 62.000000
' i9 {. T5 h& b' O" U8 H& xdothraki matron 56.666667
' W, r; [) E. U, A0 E- WName: len_words, dtype: float645 l3 [: W" j: p4 ]2 d
1
% j( O& P0 |2 `% A- t# d. [0 D29 y9 G$ p+ j% t) n3 F
3, H4 H9 g: J. u( |" X
4
% {2 _) @6 N) G% q3 @# H7 \5
7 e3 h' _- B+ e w6
0 T4 o2 {; u* ~, d! ^0 @75 L$ T$ K* j+ }" O \, H$ A P( E
8
( I2 S' G8 K" h6 ?2 u' c& Q9
$ a1 X B7 {/ V6 H/ d y+ S- e10
. O! H1 G$ S2 }0 _3 o/ x9 { U9 F113 A) Y4 Y K* R6 {; d
若某人的台词中含有问号,那么下一个说台词的人即为回答者。若上一人台词中含有n nn个问号,则认为回答者回答了n nn个问题,请求出回答最多问题的前五个人。
. L* |0 j/ |( [; q# t9 O8 Sdf['Sentence'].str.count(r'\?') # 计算每人提问数
. @( S- o3 _0 E; lls=pd.concat([pd.Series(0),ls]).reset_index(drop=True)# 首行填0+ z$ f, [/ d# P# z# f0 V$ q. d( {
del ls[23911] # 末行删去
1 s/ E& |2 m& l+ P. Qdf['len_questions']=ls
" t4 h' c4 f; h1 T! U5 edf.groupby(['Name'])['len_questions'].sum().sort_values(ascending=False).head()8 Z. ?2 \2 Z7 n/ M' z. J& X3 f
1 K9 _! D) N' N4 Q3 K5 A
Name
$ J- V3 u/ o S" ]6 i0 ctyrion lannister 527$ F3 m8 n2 R' S N _9 {" `
jon snow 374
& [+ L/ i. G3 w" Y0 ~0 |/ x' f2 Wjaime lannister 283
" |# ~/ V- A( ^4 \8 barya stark 2652 P+ K6 F g$ ~7 ~+ w7 s* K
cersei lannister 246( A5 l5 {3 U0 l' ]# |' B
Name: len_questions, dtype: int64
- R7 p+ {$ e1 |" f) L$ N( K% k9 y
4 U o3 I3 u" o7 q7 }# 参考答案
' |! Q. f: Z9 Q @ ms = pd.Series(df.Sentence.values, index=df.Name.shift(-1))0 H6 D/ d7 A; _$ }$ K$ t( E9 V
s.str.count('\?').groupby('Name').sum().sort_values(ascending=False).head()
7 M4 t8 L; i) M$ z. {$ B1 Z) r1 p g$ D0 w
1
) I ?8 p& l+ y) X21 a9 z4 E7 Z |, l4 t
3' M- {# p3 {9 a+ m
4
4 B9 n4 I4 [# S! c5
% e8 ~- h$ s9 M! C- h) D68 I8 f$ S2 T1 j8 e. S9 T
7
. W4 F' t4 y4 f7 f9 v86 z' J a1 ] _, B: x" R
91 H3 Y Y2 H5 a) ~1 u- j
10( a, S0 b9 G. C0 i
112 M+ m7 w; z0 n( |+ i2 J8 p1 c: {
12) m: D1 q: X, R& Y5 Y
13: G9 m. G9 S1 @" \
14
$ k! v! n/ R0 f! T, ~15
' n# i0 i# T# Q% V( T( m5 i168 h+ X/ K+ X, V+ H9 F' H8 ^
177 o' B4 B6 i$ _8 X4 V: s
第九章 分类数据, C+ f/ L- y+ f- c4 P: N
import numpy as np
) Z" n& X$ f0 X/ D8 m3 Nimport pandas as pd0 x" P: M4 |8 H$ Q8 U+ m4 T& a
1; P/ W9 S9 V6 y: F/ @2 @5 b/ ^
2
( c1 T1 U' T. l" X& Z- z9.1 cat对象 @4 s! L3 R8 d
9.1.1 cat对象的属性$ v, G$ S8 V6 o! f% }
在pandas中提供了category类型,使用户能够处理分类类型的变量,将一个普通序列转换成分类变量可以使用astype方法。
. P' i. S- j Q( w( C* Y" |' I1 L0 P. F/ a
df = pd.read_csv('data/learn_pandas.csv',0 X p/ C" u/ @3 U! c: b" R8 H- i
usecols = ['Grade', 'Name', 'Gender', 'Height', 'Weight'])
* @% Z9 H1 G% D, r5 g7 X1 ps = df.Grade.astype('category')6 `7 K: w) P2 B: S: p
+ b- f) W% s% x7 f; _6 ^* |, P4 h
s.head()0 v" q5 i/ b4 }. U: w7 S
Out[5]: 5 Q: b; F8 S6 L5 S9 }
0 Freshman
; Q4 t6 y8 z% y9 e4 T1 Freshman
/ x4 ?* N2 x$ v$ V7 F9 r2 Senior$ a+ {6 I9 c/ m& |9 C
3 Sophomore/ J7 t1 I. r: ~. _, f" d
4 Sophomore
, Z6 s$ j& U* s: F+ IName: Grade, dtype: category2 v) x8 U' [2 P; ~
Categories (4, object): ['Freshman', 'Junior', 'Senior', 'Sophomore']
; G/ @; e4 h- W& W5 T1& I/ \" F2 O5 ^( G2 y
21 W/ g' R% D) Q% K' G
3
9 N( L' o5 ^; o4 [ t$ e2 f4
/ \! d2 _$ N V0 l: _: e5( }# m2 q: s& U! E3 F
6
: S" Z$ C- e$ D0 K3 ~! n73 D5 T5 s& f, e
87 r7 k- p1 K( z' s) P" j# }4 @ ~
9
7 B7 m$ I1 z0 s1 m; R' O- d108 H0 X7 z* d" u
11
6 ?. t+ e: I) m6 s: Y- a" P12
. C. `3 g& r; f) B2 q- M13 m" G5 C. i. Y2 q% ?
在一个分类类型的Series中定义了cat对象,它和上一章中介绍的str对象类似,定义了一些属性和方法来进行分类类别的操作。, G. C. R: G0 l) w$ M
( F/ R$ H0 E) k4 c- P
s.cat
3 T$ `6 D* ~' a2 i& {Out[6]: <pandas.core.arrays.categorical.CategoricalAccessor object at 0x000002B7974C20A0>' c0 X& ^5 U2 Q0 o
1
7 Y+ N( U% F( C, }2 E6 ]6 _; y8 T7 s4 `2 W: `! G& X
cat的属性:5 |) K5 o9 R( H9 P O: F$ B
+ D: l/ j) x# Y L( ^% T
cat.categories:查看类别的本身,它以Index类型存储
8 F8 Y# i D8 ~8 P& _$ v. Xcat.ordered:类别是否有序4 k! k! @+ c$ J& c4 ^
cat.codes:访问类别编号。每一个序列的类别会被赋予唯一的整数编号,它们的编号取决于cat.categories中的顺序( S5 S9 g2 [/ A% Y
s.cat.categories
2 t& A2 M* `# nOut[7]: Index(['Freshman', 'Junior', 'Senior', 'Sophomore'], dtype='object'). u3 w$ v& `( k7 _/ @6 }
+ i4 O6 m+ D$ |7 ms.cat.ordered
, M8 Y. s1 E# l C& M& oOut[8]: False
0 m+ b- Q* R! y- y$ U
+ ~; i6 I$ u- B) {s.cat.codes.head()
# V( w3 e( G% J5 ?+ q, k- fOut[9]:
! p9 i4 f! J6 @0 0
- g: J) I! i, ^6 f1 0! m; O- ?* a, \( n
2 2
) n ^* S! i9 v' j6 H, E3 3
/ [+ d0 c8 Y/ P8 \! E& W9 K5 c* o4 g5 p4 3
1 r# T/ H( N" }0 D6 |& Udtype: int87 c7 O! x+ Z F5 v0 l k/ G
1 l+ h9 T) t1 t4 j* a! ^
2+ t# Z+ F4 Y, m3 ^2 f2 T8 E
3# M, ]" ]( u: w$ n9 I3 ^
42 o& v( z( M0 h4 J$ u! h
58 J4 b$ k; ?& b& @8 l
6
% u% \0 C$ N* I. w& A6 i# M2 }72 l$ ?, r I- \9 d; d \, u& b
8
" u9 D. {- X: d4 y9
0 u6 A+ p( }: D10+ ]1 j; A4 U+ f$ [ w/ T, R }
11
% y3 K2 r# ~+ z+ h. [! ^128 o( N, ?( B2 Y+ O( N* Y* n9 L2 J
13
5 ~" T' |& F' B. c14
) h! ^/ p& _+ s8 Q9.1.2 类别的增加、删除和修改$ A' s) x/ d; J' Q' G2 h4 k. V
通过cat对象的categories属性能够完成对类别的查询,那么应该如何进行“增改查删”的其他三个操作呢?5 T" ^, V) [& l# Y
( ~# {" f, S- Y) e$ @6 F3 ^8 }8 H【NOTE】类别不得直接修改
+ e8 i6 m8 j$ P. C6 m在第三章中曾提到,索引 Index 类型是无法用 index_obj[0] = item 来修改的,而 categories 被存储在 Index 中,因此 pandas 在 cat 属性上定义了若干方法来达到相同的目的。
) G+ j" ^) b- w9 P4 x
) M' O! j4 n: r" o! |! n0 kadd_categories:增加类别3 `1 H- C/ p3 v
s = s.cat.add_categories('Graduate') # 增加一个毕业生类别6 E: l# S7 [3 |2 G) ]1 E& y
s.cat.categories! ?9 \. `6 w. E1 o! z, w
- @( `' |9 N8 s& {1 JIndex(['Freshman', 'Junior', 'Senior', 'Sophomore', 'Graduate'], dtype='object')+ [$ Y8 B3 b3 G, j6 v k
1
- _5 @/ [ k7 D! F2 X4 ]2
0 z& j, r' p( J8 }4 V( H2 q/ K3
+ X* r; \! f6 j4% [, @, v* I4 t% d/ U! r% p
remove_categories:删除类别。同时所有原来序列中的该类会被设置为缺失。 o! \/ m1 q& R
s = s.cat.remove_categories('Freshman')
% b# h- T: J& D" C5 v. F9 p _+ ^+ m
9 t5 Y0 }; W+ bs.cat.categories
+ D* ]" f7 p# s0 F, t* w6 WOut[13]: Index(['Junior', 'Senior', 'Sophomore', 'Graduate'], dtype='object')
. w, ]8 L3 M2 R' }' e1 r" a0 P3 A
* ?% @1 D6 [! U( _! l+ Xs.head()+ j! Z. u/ t4 @! _
Out[14]: ) r1 u6 ^3 j! T& G& {3 x
0 NaN/ I% }, t' h n) w- W& Y0 F$ f% K
1 NaN& H/ f" y" h9 S! u6 f2 \( m2 {- O
2 Senior
/ g+ L9 s+ I+ n* u+ F3 Sophomore
- Y& Y) W. Q" P4 Sophomore& p1 F S% i% A, r1 _
Name: Grade, dtype: category( g4 a2 P1 h4 |0 }! M* `
Categories (4, object): ['Junior', 'Senior', 'Sophomore', 'Graduate']
- ~. N3 M8 Y; _9 D1
5 H" E" a( C5 l3 G4 Q2
2 o0 \5 s0 s2 t- Y% B: p# X, l3
) J7 ^+ Z8 l. e5 f4 l44 `9 T' L8 K6 b8 h* }6 p: {
5
: s( r! P* |6 q. T6
, }' E, V: N0 P5 A( }& D7* `) c$ a$ S0 D4 v; b( u, A J
8$ u m: ~! Z" [" H4 ^+ v
9# Z' J4 {# m- l$ f
10
8 }' ]" L# D0 b: d7 ^& o110 R% p' h% Z K8 ^8 ?; D
12
3 E) _! I2 k: g. G! I% d5 G- K139 \, P+ e+ \8 v: h) H' N
14/ c+ b7 j: r& t2 K5 Y
set_categories:直接设置序列的新类别,原来的类别中如果存在元素不属于新类别,那么会被设置为缺失。相当于索引重设。
, U) v0 F: Q9 M! T/ K% Is = s.cat.set_categories(['Sophomore','PhD']) # 新类别为大二学生和博士) [/ L3 f2 ?0 l! ^ N' q/ `
s.cat.categories: E$ h4 x R( B0 C
Out[16]: Index(['Sophomore', 'PhD'], dtype='object')( d% r" ?2 T" W/ i: s
! d! M; v! j5 Y( w* n: P2 Ns.head()
" e/ |+ W% o9 h+ [8 `; g' u* WOut[17]:
* l9 C4 O5 g$ l* b) O. h' G4 X0 NaN
( k x# q5 Y, v, u- Q& W7 G) T! c1 NaN
9 E5 X5 C% f4 K% e2 NaN
8 ]* L2 z% V) ^ R3 Sophomore8 ^" Y. `# \6 q7 d. u# N
4 Sophomore f3 E4 r7 M; e& z; K: ~) o* j
Name: Grade, dtype: category: z; |: e, |7 S5 l
Categories (2, object): ['Sophomore', 'PhD']. r6 x* O4 y0 r) f
17 H+ {( _0 E6 `/ j2 K1 w" c# p5 X
2' E2 O4 y/ j+ J2 Y) e5 B
3
2 C* H* p& \6 z3 `4 G42 t# z; y/ A* {5 f. \
5% V3 [, j' H* Y& _: h
6
! X. i, e" J$ S" C8 y" C7& f9 j9 C; N* U- C- _3 k2 F
8
8 S$ B7 m* n! q* T, h w1 ^3 @* q% R9
# r3 g" F* |. C: c6 R0 l10
2 y8 g7 h3 C; Y+ O+ y0 p1 Z6 h119 n! S) O3 G) h. o8 ^
12$ B0 @8 u( ?$ C$ _3 e
13% M; d2 I x+ o% e
remove_unused_categories:删除未出现在序列中的类别9 v2 S9 Q, ^2 A1 u
s = s.cat.remove_unused_categories() # 移除了未出现的博士生类别
1 c/ M' k/ s, V; C/ \; ~( W) ]s.cat.categories- `. b% ?1 T4 E, E2 I5 t( ]
8 y* b! X: W) F, N" NIndex(['Sophomore'], dtype='object')
' W# O- h k3 w r10 f3 O' |' l$ @8 ]' Q
2
- J# g: Z" U% O( s3# t* ]7 X0 o( R0 W
4
' h3 b5 m* A( v$ u( \- h" o. qrename_categories:修改序列的类别。注意,这个方法会对原序列的对应值也进行相应修改。例如,现在把Sophomore改成中文的本科二年级学生:9 C) S1 X. p) P) ?% t. W8 J
s = s.cat.rename_categories({'Sophomore':'本科二年级学生'}). b# j: T1 P- r1 f$ q# \
s.head()
. ?% p, j) ]. U9 f3 q- D+ r2 ^- v7 p0 Q2 P& @6 k
0 NaN
, G) \+ i9 `% n% |; |! N1 NaN; {0 B- Y9 ]0 ] h3 \
2 NaN
! ~1 y9 b! S6 L6 c3 本科二年级学生& X8 x3 f2 s- r9 |
4 本科二年级学生
1 o. e8 j: a( j. O0 Y- q, k# GName: Grade, dtype: category% g9 G% w6 z' o, T- C, _
Categories (1, object): ['本科二年级学生']
2 y! ~' a4 C, C% |' Q q, U- ~+ r1& @. Q \5 e* `4 x4 F2 `) l
2
4 C& O. y: s* O8 ]) s' i3
3 a" z9 ^% o! v4 k# I; f5 B% D43 q: [9 d0 L* Q5 i
5
$ E0 {" h/ b& N% K( b9 y64 z1 }: p+ m e, n
7
/ K& }8 M: L/ U q88 u( w5 I6 w& J6 ~& J2 T1 W) t
93 c" _0 f/ `# Z4 L0 S- t: N, `
10
. x4 z) H+ A( l1 @) c f7 T9.2 有序分类
) O& E# j5 w6 d9.2.1 序的建立
8 S. l) W, }6 a+ W$ x- c% `# b 有序类别和无序类别可以通过as_unordered和reorder_categories互相转化。reorder_categories传入的参数必须是由当前序列的无序类别构成的列表,不能够新增或减少原先的类别,且必须指定参数ordered=True,否则方法无效。例如,对年级高低进行相对大小的类别划分,然后再恢复无序状态:% Q9 u l' Z3 \ v" f
4 I; U) y: C$ es = df.Grade.astype('category'); ~' r) N! Z1 k. ~- ]3 e
s = s.cat.reorder_categories(['Freshman', 'Sophomore'," `2 {# f2 A& b8 @; s, X
'Junior', 'Senior'],ordered=True)
0 M/ t7 }6 `% @. Bs.head()
0 ], u0 V2 g, G6 J. P1 O. zOut[24]:
/ t: _3 v7 f% f, L" d$ P* ?7 V0 Freshman5 D4 f; o4 K4 h9 H: i y
1 Freshman
& R( h4 G. n! q* A! d2 Senior/ z) }; j" r* T
3 Sophomore
1 G3 _' R2 p( ]1 O3 G4 Sophomore4 e5 M; U6 l! j9 a1 X5 @# P6 W
Name: Grade, dtype: category
; R0 ?8 ~9 q& f/ A/ O5 t, @Categories (4, object): ['Freshman' < 'Sophomore' < 'Junior' < 'Senior']. ^! A) b( ^$ D ?# Y/ L0 d
+ j0 S3 `' v4 [4 I5 j+ H" Es.cat.as_unordered().head()
+ E9 i. q2 \7 S6 ?Out[25]: " g9 B8 H0 L1 F0 J! C4 c' y; ]
0 Freshman6 r( n! F0 Q, B, V v
1 Freshman) x4 A0 R( u$ ~& q
2 Senior
; X& S0 U% m& s% H3 Sophomore. R9 f+ g/ c0 s$ ^- \ C- ?, P
4 Sophomore7 I1 ]4 q$ y# M
Name: Grade, dtype: category2 v" B* Q! H3 M! _# G ?$ ^
Categories (4, object): ['Freshman', 'Sophomore', 'Junior', 'Senior']3 `* {0 |$ {& ]4 _! f/ c. T0 P
/ V* v" a6 P q1+ z% N: {' q2 X
2
6 p1 v1 `+ K# u( g- `* `' F8 U3: I% J% ^7 Z( {( {) k) q. r
4- p* o& R( o; h3 r6 ^; X
5
. `0 t9 \( L% Y5 f65 l9 H H+ p! n2 W F9 l
7$ t6 B. s d: r L
8/ x% j% e3 G7 E; _9 } @/ } s( V
9
3 |+ }+ W% F2 a8 \& o; Q10
' K) `3 h& p( [4 B8 o$ G" k11 j1 f% R+ b. X. b" ~0 O+ b7 ~7 t
128 ]# @$ L. ]+ c; F# Q. P4 B
13
* D; ] ^& V$ Y9 }1 r; v- g! ~14
+ \) V7 U1 n) M% l% _. d3 I15 h+ J5 _1 ]3 n8 |+ E! C7 {' Y
16
0 `. Z' I Q0 j; |+ K1 o17
+ C/ C" c( c7 M' g) ~- A18
2 M; B4 v0 i1 [: y$ d; t5 e# m19% `0 I5 L+ o" Q+ Z% x; V( E3 Q' ^: j
20
' r& l4 \* N7 ?5 s" j21, f: v" N k: G( l% \! }8 u" ^
224 {0 f2 F% @0 W0 g/ P
如果不想指定ordered=True参数,那么可以先用s.cat.as_ordered()转化为有序类别,再利用reorder_categories进行具体的相对大小调整。
; j. X q5 a0 q' X' b/ N" d
0 e2 M8 W$ h5 I$ i1 v, [" L9.2.2 排序和比较) F5 j2 w: j; O2 S- O0 ]
在第二章中,曾提到了字符串和数值类型序列的排序。前者按照字母顺序排序,后者按照数值大小排序。
& v$ R8 P( ]/ Y' D9 `: P
9 l- l& Q i$ f& W 分类变量排序,只需把列的类型修改为category后,再赋予相应的大小关系,就能正常地使用sort_index和sort_values。例如,对年级进行排序:
- f& ^- f; F B6 `+ y( J/ {6 P
( Z* ?& E# u5 j R* Rdf.Grade = df.Grade.astype('category')
$ \8 o& T ~! a; n8 S7 xdf.Grade = df.Grade.cat.reorder_categories(['Freshman', 'Sophomore', 'Junior', 'Senior'],ordered=True)
1 e% U8 z1 S. n" d7 Jdf.sort_values('Grade').head() # 值排序% X2 }0 u5 L8 } s4 n( j
Out[28]:
3 T9 P8 e5 j& V. V- K+ U( { Grade Name Gender Height Weight
* l' \* k. D% P. q! g, d0 Freshman Gaopeng Yang Female 158.9 46.0
0 m! y6 t r. u/ r" B0 i105 Freshman Qiang Shi Female 164.5 52.0
6 G% {5 m! c0 r% w1 v96 Freshman Changmei Feng Female 163.8 56.0
0 t8 Q# H3 @& T88 Freshman Xiaopeng Han Female 164.1 53.0
4 h* J6 D5 E( ]81 Freshman Yanli Zhang Female 165.1 52.0
- O. f( n8 X! D4 ]0 k2 X, `: z) d# m P- [# l
df.set_index('Grade').sort_index().head() # 索引排序/ `" a6 Y8 B( w; C
Out[29]: # v5 R% }4 t5 L, h0 r9 x
Name Gender Height Weight% ~1 D; c+ i" f, o+ A8 p
Grade
) L) x# c) n. ~! bFreshman Gaopeng Yang Female 158.9 46.0
# j9 p: S! |8 h8 rFreshman Qiang Shi Female 164.5 52.0
; z1 c3 ]3 _ ~4 Z7 X) qFreshman Changmei Feng Female 163.8 56.0; `. f2 Y; P u2 U! {0 V
Freshman Xiaopeng Han Female 164.1 53.01 Q* X) }3 x4 S: F) B3 C; x
Freshman Yanli Zhang Female 165.1 52.0+ Z; z% R/ `! }" \( Z1 e
* Y6 ~; ^% K9 p( \1 {0 s1- Y- A, l' W4 N& L$ J) a1 r2 ?
2' H H1 T' B: z) V2 K3 v
3
6 l3 d4 {9 X4 h% x7 o7 \4
, o- w; ~2 r" z5 G0 l5
* m( E: z+ ]7 o( ^3 A6- c2 |; T1 I# c
70 f1 y c& x; J8 b$ x4 y/ i
8
^5 v( v( x; ~! \7 ]9
/ H9 V2 \8 b2 }, k$ H/ O" y/ n8 ^- W10& h9 |3 Q. f8 \$ S
113 M' b- w: W& B* L# g+ _" p
12
( N7 k! P/ p* F2 |& b, J/ w13
$ E9 y! ?2 \# a- ?( U$ k14, @8 |; g, l4 o/ S
15, x, U2 G! @, n8 M' b$ \
16. C# @: ?5 j% Q+ L" ~2 C$ n0 D. a# |8 R$ P
17% W: t5 ?3 X, w! i- G! b1 n
185 `% n- e% C$ X' A3 a
19
( h. R4 n6 A" p20
5 }- R9 a4 r9 M8 I* W; a* B 由于序的建立,因此就可以进行比较操作,方便后续索引操作。分类变量的比较操作分为两类:, N2 X9 G2 r: `7 F, s
. P H/ C( W* W; k8 { L; z==或!=关系的比较,比较的对象可以是标量或者同长度的Series(或list)。(无序时也可以比较)
+ k: S' \4 w0 B. }# _2 t>,>=,<,<=四类大小关系的比较,比较的对象和第一种类似,但是所有参与比较的元素必须属于原序列的categories,同时要和原序列具有相同的索引。
$ h" f1 L# B4 O% Zres1 = df.Grade == 'Sophomore'8 T/ z V2 b$ ~6 [
: E/ Q0 S L! F, r, Mres1.head(). i9 B, x: Y" S; O) a9 U0 N9 G; ~
Out[31]:
1 ^; f/ y7 p0 L$ h3 z/ t0 False
1 n# z5 }& z: R9 R1 False
* ~( N1 k' d6 S! G2 B2 False( @; g, N) i/ m9 i
3 True1 P& C$ X$ r6 ? D# ]
4 True/ |/ J/ N4 @/ l
Name: Grade, dtype: bool4 c2 }- \5 V9 |( ]
" q/ X' K/ M8 M$ K" K
res2 = df.Grade == ['PhD']*df.shape[0]
/ m0 i9 A7 }/ i. f) @
" |1 G$ X* m3 `0 G) zres2.head()
4 I) Z& i% j3 OOut[33]:
0 w9 W* H+ f" X# c0 False5 _- H1 l. W( {9 m, H
1 False
9 p- F1 B {& l" t) w2 False
j% m' U9 V4 I. ~+ }3 False# p% C8 j! d1 R' y$ V& o
4 False/ u0 ?. O% ^& [6 r6 H! |
Name: Grade, dtype: bool3 D X8 y# g4 [: l) x7 _8 G. i: N
5 d {; q: o& A D8 l$ O6 ~res3 = df.Grade <= 'Sophomore'
7 f, {, D$ C# W1 c) ]+ T" p4 e, n2 S/ Q5 C" s$ w
res3.head() M& R) K- r# P) u1 i
Out[35]: 5 p7 h6 S- E% T$ R# D4 G, [
0 True
$ F# [0 l2 R7 l' ~/ {- \1 C7 i/ I1 True8 h- n9 v' o( J3 W! T' R! Z
2 False
q1 b6 U+ ^. b& I* q7 _3 True4 o' t; Q4 P6 N. o/ ^: u1 |
4 True
* g8 R0 g' R A9 {1 z6 [) Z. BName: Grade, dtype: bool
, W( U8 I! P9 E& h& m( u% E P1 T& K9 D9 O I& u
# sample(frac=1)表示将序列随机打乱。打乱之后索引也是乱序的,直接比较会出错,必须重置索引。& |( k1 Q( C: s: T2 h6 W
res4 = df.Grade <= df.Grade.sample(frac=1).reset_index(drop=True) ) w& M2 t" r, r" |% X0 u
! O* _! H/ A+ q) ]" o8 R# Tres4.head()
, n4 t9 @) m! P) J% d% {1 x; iOut[37]:
) U+ {, E1 W y! \0 True
1 }/ ~" Z- r3 |2 k1 True
) |& \5 i( l# n* j" A2 False
# X; h; T7 T W1 ^8 C3 True3 |# j2 T- g* S* D4 I( \4 a" \
4 True
% U0 |5 ?* O* l8 `Name: Grade, dtype: bool
: Z& q3 R! c+ D4 k# r0 e8 _6 ^1 m1 ?$ g8 s0 u
1
3 }0 o6 H/ U7 S. l6 t2! r# C) G1 k& D, _2 E# f2 p( T1 }
36 x, h5 T! }: q2 z9 p* b
4, L9 q( Z& T; o3 X. V' I% j
5
6 S% Y6 ?& l# U0 o6
& u+ n3 c; f' w4 Y7
, }9 `8 _/ f/ F' o! ?7 a85 ]4 e0 w! |" k/ J& ~) J- v0 r
93 d* F0 ]# c' s
10$ K* x" ]7 k7 u9 `' }
11
0 x5 o5 }8 J* m& `: ^: h" ]( G; K12
- F, h* O: d6 u2 N: Y2 I# B' d/ m13
! t1 b) U1 O2 J' L14
1 y7 v% h9 E5 K" n8 s157 ~& G! i3 m8 L" x5 z0 [
16
6 ^) U* _ J' d% M p( C177 c" }$ d2 a5 l, b4 @+ u
189 j$ V, `0 M% A5 f
19. c& w+ ], U) C; a
200 X) }" B9 f/ x
21
4 ?, G( F; x& O' ~6 ?, \. B" T22" Z" @( E+ H) c+ P6 G* l4 e& L
23
) C6 j: y: ~$ h, @" D- s) R7 j242 R- y' t' |6 n; Y
25
& Z5 j: x' H6 l" {7 x j, w5 x% h261 M9 t, A9 H% S. U- T8 F
27
: l8 q4 I4 ?: r: H6 a+ z0 [28$ @4 ?) V% S- Q9 \4 y9 D# ~) k
29
8 G9 b$ B* W& o# G4 q- ^300 X( H6 E& m1 y" E4 r
31* I, p. S3 x$ Z% {6 T# K
32! V6 ~+ t7 J, |0 C, w* C( t* W
33
- K b. r, d X& y0 f3 d34
: `3 L% W1 u; z. O* i3 d4 |9 L/ e35# M: O+ V: t X8 D+ U; Q- I* B
36( w( d0 M* }1 P1 y
379 M+ v8 X; E3 j. t9 X U; {
38
& v K9 J& i5 [0 c( @0 r. \39: _" s/ E1 W3 A6 X- g5 Y8 i
40- j# Z2 A% [8 X9 [
41
4 Y% o5 K& R0 n* ^/ r" u42- i0 B+ R7 Q# h7 Z2 c/ o
43
- t, {3 Q. w' A* |' \% w/ o44
' s. C' T% F2 S3 Y0 D9.3 区间类别
: b6 g u+ p1 w/ F' b; @7 |, t: ]9.3.1 利用cut和qcut进行区间构造
* ]5 ~& |, P" j# L6 ~ d$ k 区间是一种特殊的类别,在实际数据分析中,区间序列往往是通过cut和qcut方法进行构造的,这两个函数能够把原序列的数值特征进行装箱,即用区间位置来代替原来的具体数值。: o3 ]5 Q; ?* m. r* {7 x7 A7 Q9 _
& h- _# S; b: M' W
cut函数常用参数有:
1 A) s3 }# N( Ubins:最重要的参数。
% o* h0 o$ N' `5 X/ M如果传入整数n,则表示把整个传入数组按照最大和最小值等间距地分为n段。默认right=True,即区间是左开右闭,需要在调整时把最小值包含进去。(在pandas中的解决方案是在值最小的区间左端点再减去0.001*(max-min)。)
1 n* `9 ~) Q1 r1 t& h) Y: n3 R# s# f+ V也可以传入列表,表示按指定区间分割点分割。
! u* Q- h; E3 C, a# V 如果对序列[1,2]划分为2个箱子时,第一个箱子的范围(0.999,1.5],第二个箱子的范围是(1.5,2]。
( y9 ?1 s: m0 u: `0 L. a e, s& p' @+ r 如果需要指定区间为左闭右开,需要把right参数设置为False,相应的区间调整方法是在值最大的区间右端点再加上0.001*(max-min)。5 d3 B* o4 v. @. w- u) L' ~
! L) n. {, H3 t6 Es = pd.Series([1,2])
* Z; [9 x2 S$ I8 W7 w6 g" E8 b4 p# bin传入整数
4 d! E8 j/ ^7 ?; C
2 Q/ t- @" c- m5 d, q: f1 _) Kpd.cut(s, bins=2)
9 a% R! v/ W6 s. N. L6 ^Out[39]: ) A: A& G ^9 r5 { \
0 (0.999, 1.5]
- l9 }/ f! g3 F4 C3 z, ~1 (1.5, 2.0]
& B3 ^: f( d4 o" m5 X: Adtype: category
- f/ E* C' v) Z, NCategories (2, interval[float64]): [(0.999, 1.5] < (1.5, 2.0]]$ m* x3 E3 E# d- G) w `
4 f% f- }+ W. i1 _. L, K, Q' X. o( X
pd.cut(s, bins=2, right=False)! A6 ?2 n$ R- l- U- H4 h
Out[40]: / v1 f2 R( K) ]) U( C/ b5 ^
0 [1.0, 1.5)
+ S7 U* {7 M% q1 [1.5, 2.001)
& q/ A# A3 p6 w' `) Edtype: category
- C4 R- ?4 h$ RCategories (2, interval[float64]): [[1.0, 1.5) < [1.5, 2.001)]1 P9 w+ }( x4 w6 S2 c ^/ G+ Y( G
) M ?) O3 w* U, |" R% F
$ a! k4 J3 P P# bin传入分割点列表(使用`np.infty`可以表示无穷大):$ u" n/ t; r; a) i7 @8 S+ b2 C
pd.cut(s, bins=[-np.infty, 1.2, 1.8, 2.2, np.infty])
$ v: \! `$ Z6 I! w4 pOut[41]:
8 S! p; ]6 c# G0 (-inf, 1.2]# ]- m9 w1 O: I) W
1 (1.8, 2.2]
. \7 o6 p5 c+ n( g$ Vdtype: category
# Q5 i& ^7 z( XCategories (4, interval[float64]): [(-inf, 1.2] < (1.2, 1.8] < (1.8, 2.2] < (2.2, inf]]7 N: [3 g M* }9 s: \( [
+ [3 V" S, d! M& y+ w16 K# r2 M- F. y- x9 ]
29 c& L5 ?, k5 b! f* w( o# {- P
3
( z$ \( A( ~2 ~3 h. A. @2 k1 w" K4
1 B6 g% V6 D* \, z0 u5
; ?5 M2 \- _( h0 y+ J+ j+ ]6
- |5 N f4 w. R0 _/ }3 z8 I7
- M+ L$ u$ U' `% k% [8
4 a( y7 n4 O) u q8 ?8 w; L9
8 s$ _& b" u7 |10& U+ J% f" s& J3 q6 X/ o0 m7 k
11: B, R6 w% K5 m2 W# r4 L
12
' z! ~4 Y+ j4 q/ p3 a* q( _13
. w. L8 H' W: F$ B9 \/ @ a14
% C( X6 U. z% h+ L5 i+ G" e7 d15
- j" P+ t6 X2 @' G* I16$ k9 T3 V" F: t' e5 h& J+ f# p
17
* V: {) ?( E8 X. P189 ^$ V2 z3 F. y5 w! k/ }- {
19
0 n- g- p0 e* ^2 n. ]( ]' a |20$ R; q+ b9 E* _4 Q/ _
214 U9 B% X6 t3 R- I/ i4 |& U& X# p1 R1 I
22( k: ~% \. _* g8 {1 }
238 W+ q2 {$ B7 c
24, w! c; t( {2 F2 Z$ c, e* x% |
258 A2 ]. o: |+ w! |- E
labels:区间的名字
6 N' E$ j$ e$ J& p& z3 d Oretbins:是否返回分割点(默认不返回)
# p8 l- P2 ]- e/ S8 O7 d0 C默认retbins=Flase时,返回每个元素所属区间的列表: m; s! Z- A2 Y
retbins=True时,返回的是元组,两个元素分别是元素所属区间和分割点。所属区间可再次用索引取值. ?2 Q7 H% `/ `7 w: @- t9 X
8 I6 h1 C9 a* r* t5 us = df.Weight
+ `# B1 m+ U6 E' C( m, Vres = pd.cut(s, bins=3, labels=['small', 'mid','big'],retbins=True)
/ x. X: P" _4 f1 `& ]: Mres[0][:2]- G5 L- p ~& v: g% o" a& ^8 P6 i' R
+ F9 v8 L) l) kOut[44]:
3 S W* r6 [2 b- z( y7 z0 small
6 U' S6 d2 `' o, w! W1 big
: t. X9 ^- @8 c" X5 W. |dtype: category
/ g4 p/ a7 w5 L" TCategories (2, object): ['small' < 'big']3 Y F' \( l) K! c( a8 d" s$ n
; Y$ \, O ^" Bres[1] # 该元素为返回的分割点
9 q7 F( ?% }# T$ t/ }1 d. POut[45]: array([0.999, 1.5 , 2. ])
/ Y" w, l0 t- U4 C1
8 G8 F3 P+ _( M4 D2) h7 [7 Q7 Q! n# W
3
1 [9 C' d7 ]$ `7 \4# U0 I$ b0 r7 | x
58 a' V( p1 Q: Y( L6 u
62 Q% Z* m8 o5 C6 e
75 A" E8 _( g2 I/ |* P& l+ r7 d
8 H }, e' m+ j! x
96 J% Y$ {- `' `% A+ G
10: e+ r/ y0 ?0 Y* i2 `( M4 z
11
1 V6 e9 H( M: r125 U' ^' y( L/ \+ Y' U2 s
qcut函数。其用法cut几乎没有差别,只是把bins参数变成q参数(quantile)。
8 o! n& U8 c, v1 Bq为整数n时,指按照n等分位数把数据分箱& V) S; U& h4 `; M
q为浮点列表时,表示相应的分位数分割点。
8 w( D q# e2 P) Ts = df.Weight
/ h9 C6 s7 J% K" i& E: h; R. H3 ?
pd.qcut(s, q=3).head()
+ y9 S& r, j9 \Out[47]: ; B! A3 {, N+ P5 u# ]8 q6 ~
0 (33.999, 48.0]
: y$ R. \. T, H/ [2 g6 E1 (55.0, 89.0]
* {1 @, s0 T) e, W+ f* a( u3 k2 (55.0, 89.0]
* `0 a1 n! U+ v5 f# D7 q& J4 C3 (33.999, 48.0]
! e+ r, q/ q* G- C u3 t6 p4 (55.0, 89.0]
( h$ D4 f& U" {; m9 Y: UName: Weight, dtype: category- k; R) q1 G) g6 D2 g1 v
Categories (3, interval[float64]): [(33.999, 48.0] < (48.0, 55.0] < (55.0, 89.0]]
& H1 K( _ C# q( q
5 R: P g! g! Y1 g# p5 cpd.qcut(s, q=[0,0.2,0.8,1]).head(): a. v, K/ Y& e; X% Z. M6 s ?
Out[48]: ; ^, x+ L G4 I5 N3 o
0 (44.0, 69.4] v" T* X" @: u( H5 c8 i
1 (69.4, 89.0]
; q2 U& c k, j2 x k2 (69.4, 89.0]
2 [( T: t( R0 Y3 (33.999, 44.0]
) I, c1 a6 o0 c2 r( _7 |( ?4 (69.4, 89.0]
: [2 s& h* t/ R% z. PName: Weight, dtype: category
* L: F! {6 ?* Z) Y: ?( `Categories (3, interval[float64]): [(33.999, 44.0] < (44.0, 69.4] < (69.4, 89.0]]$ ?( ~7 [3 p& [; [2 ~4 ?
9 n2 Y% f! K' a1 p* ~0 `
1
# B0 T, k+ X2 O+ y26 `( J( x8 Q( r- F6 M! D
3
: M$ _2 G, f9 U7 j/ d% p; H+ K2 U4
}+ ]/ r* L0 x9 Y: F( c7 y n5
, |* M4 P) x" q& J* Y) B$ r) Q6% R+ q5 C1 F2 g5 I( |
7
- r" h6 e% j9 W T8
5 O, E/ ^. n' }8 W0 }1 X97 g [5 \2 p0 ^+ d( j7 V6 N0 @
10( M, Q, _' A7 @! |
119 b8 b# s: X* v6 j
12
5 r8 O+ _& B8 `% b% K" p* I13
1 g3 B# E' t+ R0 d9 B9 U7 L14; c. Q( {! O; o; [- n% c* D
15
: I- N' e5 H9 F, l! B6 t9 Y3 k16' F/ z' v# {9 x" s7 g' E: H
17
$ X7 S/ Q6 ` w( Q% |5 x% m# g- }2 q18) q+ ]7 Y0 {, H# w% f# o
19/ Q" p" j# ~4 T4 m0 S' F8 R
206 Z0 D. M: |: Q% x
21
/ O( i) i- O: L9.3.2 一般区间的构造( }* X( a |8 t. {( i3 |4 Y
pandas的单个区间用Interval表示,对于某一个具体的区间而言,其具备三个要素,即左端点、右端点和端点的开闭状态。
) |# X2 G1 l! y1 K
n2 E! G' y! T开闭状态:包含四种,即right(左开右闭), left(左闭右开), both(两边都闭), neither(两边都开)。
$ M4 q9 |4 m' z+ s' Bmy_interval = pd.Interval(0, 1, 'right')7 L4 n4 {8 Q; S4 H1 X1 @
0 m6 ~' J" f0 p8 A0 ^4 u L5 s
my_interval7 {, ]& p( N7 c* b# o u4 ^. o
Out[50]: Interval(0, 1, closed='right')/ g+ i2 G. R4 |
1
[5 s* O. x0 R5 @, ]2
, |0 z, R+ T, Q v$ Q# m3
1 i; F2 g% c, d- ]4; }; D' |! L1 Q# q3 A! o
区间属性:包含left,mid,right,length,closed,,分别表示左中右端点、长度和开闭状态。
0 n, m! p! {5 I) i使用in可以判断元素是否属于区间3 ?9 y" n, s7 g) j6 \( ^7 P
用overlaps可以判断两个区间是否有交集:
0 v* [9 j" H6 J6 z, r3 G0.5 in my_interval
, ~; \+ K& [ \( X: c; b6 L, q Y+ V1 ?* d6 O
True
7 `; N. m+ h! k; X4 W1/ |% j+ u9 t4 R7 ?' D% `
2; J" f F& ~; Z" P- c; w% K8 U
3 h: v4 f( b4 G/ [0 J. A' ^" m
my_interval_2 = pd.Interval(0.5, 1.5, 'left')6 E: D/ b. u' z) m2 d
my_interval.overlaps(my_interval_2)
/ \5 r; e. h- A3 _+ f$ Z- h6 i) A# S/ N1 ^8 i, d( m! O, m* @) e
True
3 L3 ~# P- i: a1 M9 `5 G' Q' h8 _1
" F, y2 S# H1 T2
; V) h8 {' j: [* e" y) K. T+ g3# o/ b9 D# \+ i' g) s- b4 X
4+ z9 D% w% g, c3 [
pd.IntervalIndex对象有四类方法生成,分别是from_breaks, from_arrays, from_tuples, interval_range,它们分别应用于不同的情况:- U$ f6 j( b( D; i) m- t
, t4 q9 d' [0 Q7 V# n6 d
from_breaks:类似于cut或qcut函数,只不过后两个是通过计算得到的分割点,而前者是直接传入自定义的分割点:
b! k) _( v; g: }$ y7 m' jpd.IntervalIndex.from_breaks([1,3,6,10], closed='both')
( y2 q8 H% @4 h; ~* y* ?
5 u( o( S }: D; fIntervalIndex([[1, 3], [3, 6], [6, 10]],
' e8 @4 E W" e) k' h) ?; ^ closed='both', V2 ?" N% N& P" T
dtype='interval[int64]')
9 ]' D5 L C2 {: y9 W1- z: w$ l# S$ U! l. V+ |3 p4 @
2
; E7 c! Y8 a) s1 B- E% P, c3) V- j" D7 h, @, [+ G
4
# w, p; s8 k5 [& C3 Q2 E4 [' M) j5! P. S9 L, X5 s, R0 D0 J0 ~% R
from_arrays:分别传入左端点和右端点的列表,适用于有交集并且知道起点和终点的情况:' P8 s- \( L$ e( M- `- H
pd.IntervalIndex.from_arrays(left = [1,3,6,10], right = [5,4,9,11], closed = 'neither')) [+ f4 ]+ |# a- u; j) \- T
7 w* |7 J1 } I& h, y0 HIntervalIndex([(1, 5), (3, 4), (6, 9), (10, 11)],
8 q; t% v8 {2 J$ O closed='neither',
" U& x3 b5 c6 h9 @! i dtype='interval[int64]')
! g4 }. [$ E' ]9 I0 b0 m1
: r/ @ H, h9 b" Z ?2$ ^5 t9 _2 {, g4 J, b
3
- x2 l5 h1 E2 b6 N4 ]4# l1 ], W L E
5/ [! L& v1 f0 `$ O) |- {& d
from_tuples:传入起点和终点元组构成的列表:
: C- C2 ~4 C2 X4 \. X) M4 E W3 Lpd.IntervalIndex.from_tuples([(1,5),(3,4),(6,9),(10,11)], closed='neither')6 R6 @" D0 {8 j# Y# H" g
$ p0 u# | ?, b/ ?
IntervalIndex([(1, 5), (3, 4), (6, 9), (10, 11)],2 e- |3 x- O" P, y' y& N
closed='neither',+ P7 R. H% K. l9 [/ q" H1 a. F7 N, O
dtype='interval[int64]')+ J. z1 K- I: k
18 c- E% }. r G: l8 h Q9 {
24 I- @- _1 W% e( E4 ^, n
3
! R" b1 F0 `- z4 i. w9 @+ f8 j% ?- F4 J* D1 c& z
5
* |8 ^' f# w5 n/ n- ^interval_range:生成等差区间。其参数有四个:start, end, periods, freq。分别表示等差区间的起点、终点、区间个数和区间长度。其中三个量确定的情况下,剩下一个量就确定了,从而就能构造出相应的区间:! r3 j# ?8 C0 |1 n& N: h
pd.interval_range(start=1,end=5,periods=8) # 启起点终点和区间个数
r( |$ \% L/ ]6 O) }Out[57]: , e! s# R( [9 [1 J7 N; s
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]],3 l* X! i L5 Q
closed='right',
0 }1 _$ H$ a6 C r5 i dtype='interval[float64]')
7 w. d/ h1 s" O, N( v9 M9 J, f; y4 C: |, e4 r2 y, T* g+ V
pd.interval_range(end=5,periods=8,freq=0.5) # 启起点终点和区间长度
9 h* w# q; e( @. e' f9 o3 t! KOut[58]: $ x( m5 }' U% s1 H
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]],0 w( o% _4 V8 s1 d
closed='right',
{+ l5 w7 V: |9 K dtype='interval[float64]')
9 g6 P9 q0 B, x1
: K0 q' `8 Q5 r2 l- i. e; B2$ C' Z7 p$ s3 S6 K/ R
3
3 B; v2 \8 ~* F4
) y% q. R8 a7 p$ S' S: z5
, N' R! c5 A, J7 k8 w8 B$ O6
/ t+ b% t( I. a# g- K/ O& Z7
: v' m/ N a' q" v2 ?- G0 ]8- w" @; A3 M# I( u0 W8 h. D
9
' G& G) U6 i7 ^* U10: T1 m* u6 f D6 D
116 H, U7 \" Z9 c* o% S
【练一练】! _; l0 N3 y" M5 x2 P6 n2 D
无论是interval_range还是下一章时间序列中的date_range都是给定了等差序列中四要素中的三个,从而确定整个序列。请回顾等差数列中的首项、末项、项数和公差的联系,写出interval_range中四个参数之间的恒等关系。
3 _0 ~. L) D6 K5 X: G7 g) l# m' R* u" w( i4 t8 r
除此之外,如果直接使用pd.IntervalIndex([...], closed=...),把Interval类型的列表组成传入其中转为区间索引,那么所有的区间会被强制转为指定的closed类型,因为pd.IntervalIndex只允许存放同一种开闭区间的Interval对象。- g! J) D) E5 R
! d" L. R# c( j* D, `( |, m
my_interval0 b# p( @$ m+ Q7 @+ i; S
Out[59]: Interval(0, 1, closed='right')! P3 h4 f. o( G
7 ^ O4 ~! [' C1 W" _; f6 ], ?
my_interval_2- @4 N9 i+ V" `$ R" x8 \
Out[60]: Interval(0.5, 1.5, closed='left')
5 D; w) ~ Z1 v3 _4 ]/ e& r
3 E5 ]3 }2 U; hpd.IntervalIndex([my_interval, my_interval_2], closed='left')) F5 Q: q% _% \# ~6 Q A
Out[61]:
0 ?. S4 B1 T9 b3 m+ N+ ^* aIntervalIndex([[0.0, 1.0), [0.5, 1.5)],5 V; r% A- q% R- }5 }: a
closed='left',
+ E7 n* N7 a3 M dtype='interval[float64]')
! M& v2 H$ t, h! V9 y1
7 i: s3 B; r0 {( ]! R2
8 j9 N/ }$ |7 T$ B8 n35 q2 H$ j1 M9 g5 U+ [
4
! r: V) s6 D2 W- ?3 r5
5 Y) P' ?# g0 c3 H8 d- v% [" o6/ ^% M( S! ]# e
7
5 n) Q) x5 n3 S" u8 _- T8& _) p3 D& ]- Q, W1 e
90 c3 r6 M5 Z0 U! h/ ?
10) R# o9 e- z( Q* d! J% |( a, e
113 z3 B3 z% g- ?
9.3.3 区间的属性与方法, `1 Z1 v. {$ i: Y, p5 B
IntervalIndex上也定义了一些有用的属性和方法。同时,如果想要具体利用cut或者qcut的结果进行分析,那么需要先将其转为该种索引类型:
1 ~! z2 ^2 l% C0 C
9 X1 t& {0 n# @; y# j) ^s=df.Weight f: h/ k$ K( B" z
id_interval = pd.IntervalIndex(pd.cut(s, 3)) # 返回的是每个元素所属区间,用具体数值(x,y]表示
1 K* c+ q v8 a( A% `- n# M9 K& Pid_interval[:3]
. W0 h2 @+ Y4 [. f0 `9 t! Q: M0 a
1 h% c8 s3 ]2 nIntervalIndex([(33.945, 52.333], (52.333, 70.667], (70.667, 89.0]],/ N) o8 l+ W2 D8 V) b R* ]
closed='right',8 [5 o! U9 j2 ]% J
name='Weight',( I3 p$ p' X; c: m4 ~
dtype='interval[float64]')4 r- _1 s1 H; o
1
2 g3 V& I/ X& g4 A5 B2$ O% w7 |8 |8 o! c+ x
3
% [$ a e" z" x0 K; C4
( p) r \) `% V" l5
" N$ j; t* h* p7 |$ _6
* d" I" V1 m+ g: g0 Y7
- s" j4 @+ W2 _; J8
1 i' v* i: O+ X1 N* i" Q# [与单个Interval类型相似,IntervalIndex有若干常用属性:left, right, mid, length,分别表示左右端点、两 点均值和区间长度。
9 |2 L' ^' {6 A# |id_demo = id_interval[:5] # 选出前5个展示
6 J& F! f, W$ H% s
+ |. x0 M1 V- u7 W bid_demo
; g" C& ~7 _+ v b2 cOut[64]:
; O) o1 }( B" |# ZIntervalIndex([(33.945, 52.333], (52.333, 70.667], (70.667, 89.0], (33.945, 52.333], (70.667, 89.0]],: w+ t' K* S D0 |, S
closed='right',
! d3 ~8 D4 X( \! K1 y name='Weight',
2 H9 _- S) Q3 N dtype='interval[float64]')/ f: a+ I0 Q% [) T
: U- k0 C7 H8 [8 H2 o! C! |+ S8 Sid_demo.left # 获取这五个区间的左端点, p; i% j$ C. k0 Y* _% Q
Out[65]: Float64Index([33.945, 52.333, 70.667, 33.945, 70.667], dtype='float64')" v) F/ @$ ^' e6 P: P
$ y4 N" r, z! e" S0 I* D) l3 [id_demo.right # 获取这五个区间的右端点- C0 v( y* A" s9 {$ \
Out[66]: Float64Index([52.333, 70.667, 89.0, 52.333, 89.0], dtype='float64')
( h5 R m: _, Y4 _8 s9 b9 ?& m7 j0 O, w4 g X/ K$ z, T$ A
id_demo.mid
& i1 `+ l/ H' L1 V' l' gOut[67]: Float64Index([43.138999999999996, 61.5, 79.8335, 43.138999999999996, 79.8335], dtype='float64')9 R& n6 O" w! H8 v8 ?
, k0 n- G2 _# Gid_demo.length
+ Y; K! W7 q# N& I, j7 N* iOut[68]:
( N6 I9 p c( @3 A& dFloat64Index([18.387999999999998, 18.334000000000003, 18.333,
+ W8 R9 v# ?9 |. Q 18.387999999999998, 18.333],# m( u. O. ?' y( c7 ~$ o( M5 q
dtype='float64'); Y0 D3 Z3 Z1 ]: G2 L9 j# X/ C
* D7 U$ h2 G: X" m' V% O1 K4 K, Q; P1 a) w, s
2- t* l6 |8 F1 b) R" O5 a$ t
3- u I- N9 K3 M& e
48 r/ j3 b* _2 g3 [5 o
5# Q* h" D9 j4 U) |
66 O A/ ?! X% c% Z& ~& Z) D* @
7
% ~# v( p, }) }4 N8 d2 z, J8
$ A, T3 T! o/ D, b3 k9
4 d: n& v6 X! `5 C& F/ B109 m& F% Z5 X& G7 ^( \
11
7 r# I+ B: b: W6 b1 c127 v1 ?+ _0 V2 o/ F) I: [
13- F) e* Y1 _0 n+ O6 Q& a
141 x5 F2 I$ ?2 Q, l! K- Z, C/ m" L3 u
151 i: `: _3 ~; v- ?: \8 v
166 T/ l b5 u0 d- }) }- Z- P7 Z5 M
171 x6 e& v2 Y7 V" a2 }
18
2 H, s/ V$ N) c' j$ \7 @19
$ j& ]8 Y; s6 T+ O20# z% X) M; N) r9 ~
211 Q! Q4 m T7 L( c
22+ l3 B8 z- D; M
23
0 A+ W/ O- o: D" C1 t/ Q$ `IntervalIndex还有两个常用方法:/ x$ d- v4 ]. [; s! F% B$ Q4 S
contains:逐个判断每个区间是否包含某元素
, R O0 ?! q0 ]! m) f0 s* Qoverlaps:是否和一个pd.Interval对象有交集。
. I8 \- ~5 r6 w$ m% sid_demo.contains(50)
- @: z6 w( R& t, gOut[69]: array([ True, False, False, True, False])4 O+ E( K2 J- X2 o$ m
) f& H2 y9 q+ R: O
id_demo.overlaps(pd.Interval(40,60))% T1 T" M% a& K1 G0 J y! V% P
Out[70]: array([ True, True, False, True, False])
8 z/ w$ I T" `( {' p" U o1
. `# t* _/ r6 p0 H2
6 y5 H1 F8 q* x7 d( ^ k38 N/ g6 G# Y/ a, M; Q# r7 d, x
48 c7 X+ B6 e( A/ a$ K. E5 e+ u
5& @1 A8 ?" \8 p% s
9.4 练习
% t4 Z' \& z! _8 m2 HEx1: 统计未出现的类别
! O: _& p+ c, u% |! G 在第五章中介绍了crosstab函数,在默认参数下它能够对两个列的组合出现的频数进行统计汇总:
% X/ e: N+ Y: o7 F( p: f, \0 Q$ y# a
df = pd.DataFrame({'A':['a','b','c','a'], 'B':['cat','cat','dog','cat']})
7 V1 Q- \. I$ r. @% Gpd.crosstab(df.A, df.B)
|. h+ V- L. @4 z, E% g" {: a0 U, F( z( M& `) `8 U5 ^- S
Out[72]: 0 ~2 n9 I% @9 v! H
B cat dog& x% x2 C9 L& c( _4 A
A 5 \5 P+ `; \+ w* W! M! S$ }
a 2 0. K6 o4 t+ H6 ~$ J
b 1 0& C/ b' w; b) Z, _/ u" r, B U' Y
c 0 1- j/ ^; E" v) C1 j0 R- }5 D& [5 q
14 K8 ^+ p0 {5 [9 r2 [# E+ w$ V: l% t
27 [! U. Y' ~: U; E
3$ u# X* _' I$ W( {4 ^
43 F/ V4 Q3 z4 U5 _1 ~- n
5
1 s4 n' b+ K5 E6
8 w7 R- _1 g: K8 ~& k. R7
; [* b# E$ l; W8' i* T/ `* U/ w- C8 `8 i7 g
93 q- W# V; }3 T' K' a# y$ l+ M/ K: n
但事实上有些列存储的是分类变量,列中并不一定包含所有的类别,此时如果想要对这些未出现的类别在crosstab结果中也进行汇总,则可以指定dropna参数为False:
, {1 I/ f, u" j
2 {/ ]$ U, ^3 C! idf.B = df.B.astype('category').cat.add_categories('sheep')2 [- b% f+ _/ ]( h
pd.crosstab(df.A, df.B, dropna=False)
* k* d3 i6 G1 `, w, V! m
% Q2 A2 H" V3 P& j0 h" |Out[74]:
7 B2 v0 q/ P2 G4 c5 M" T, kB cat dog sheep
, e# s& y/ g- v8 M8 ]" c. PA
; ^: L, n4 R% J, i9 F+ L5 a4 a8 Ua 2 0 0
* @, u s0 ~' J; Z i( q; }b 1 0 0
" Y/ s4 I7 W+ y, ?7 q. Xc 0 1 0
+ }5 R4 x4 z0 T# E& P& I/ ~3 S6 i1) p; v, S- @ s
2
: P" V) p1 o5 y& ^* O3
6 R* t8 @7 ~& g+ u4
. D Q9 |# ^ v( ?! y0 ]4 g5$ g/ H. W, N2 N& n
6" E( P9 g2 i. C, @
7, c* e: ]4 z, N" A6 o6 K
8
" z3 _4 Q) I' @; Y! d, X9
0 X7 D g( r& r3 ]0 Q请实现一个带有dropna参数的my_crosstab函数来完成上面的功能。
3 d8 L& K. k* D( n+ e: |* H* Y4 F9 P6 w# S. C% W: _4 e6 G
Ex2: 钻石数据集
6 _8 m8 Z4 l3 U$ a; ]4 { 现有一份关于钻石的数据集,其中carat, cut, clarity, price分别表示克拉重量、切割质量、纯净度和价格,样例如下: \3 g/ o+ t1 ?' T
k. {, Q) a! s+ {4 C6 u' k1 y# f4 f
df = pd.read_csv('../data/diamonds.csv')
# w3 [2 e+ S1 j. f0 r, d1 Cdf.head(3)
/ C+ k- o1 o& h2 g ^! W% K! O- V9 {7 K7 q) y$ Y
Out[76]:
+ Y. M6 s$ m" W3 I! Z carat cut clarity price- ~- [. P( b! B% e# H
0 0.23 Ideal SI2 326
" o8 S5 S% X# z5 v b/ e+ y1 0.21 Premium SI1 326
% H7 l% |1 v- N n4 N: z2 0.23 Good VS1 327
$ L9 l) n3 \: y* h. v: F- J16 d" l* }& l0 \2 e. q- f4 }
2& |+ {$ j: R* `( e- r! W
3
. _6 g# W; m0 l+ V* R4
7 _: r( c* w/ E0 f2 h7 h5
8 f3 l6 q. A4 h1 ?) N' n6& f6 [. I ~& R4 D- c. D0 R
7
; v) {# C3 l+ F0 q$ `. ~/ q; a8
& Y! R* Z' w2 k5 g) k4 f! P分别对df.cut在object类型和category类型下使用nunique函数,并比较它们的性能。3 [4 c5 w3 J; N9 s7 J# T0 T) ]
钻石的切割质量可以分为五个等级,由次到好分别是Fair, Good, Very Good, Premium, Ideal,纯净度有八个等级,由次到好分别是I1, SI2, SI1, VS2, VS1, VVS2, VVS1, IF,请对切割质量按照由好到次的顺序排序,相同切割质量的钻石,按照纯净度进行由次到好的排序。4 Y: x8 z* a4 {( G9 h
分别采用两种不同的方法,把cut, clarity这两列按照由好到次的顺序,映射到从0到n-1的整数,其中n表示类别的个数。: r* G, ]* p( X+ u6 u' f4 K
对每克拉的价格分别按照分位数(q=[0.2, 0.4, 0.6, 0.8])与[1000, 3500, 5500, 18000]割点进行分箱得到五个类别Very Low, Low, Mid, High, Very High,并把按这两种分箱方法得到的category序列依次添加到原表中。" U) C2 G# w" ?' j6 \1 O
第4问中按照整数分箱得到的序列中,是否出现了所有的类别?如果存在没有出现的类别请把该类别删除。
5 ^; y% t" N6 ]/ u+ G) w对第4问中按照分位数分箱得到的序列,求每个样本对应所在区间的左右端点值和长度。
/ j0 F! [/ N! x: \, a先看看数据结构:; m7 |7 u3 h3 z4 Z5 ]3 i: ]! ^
J( [+ F* Z+ [9 q3 Ddf.info()! y; p) \( N5 x+ F; _; x! {6 e3 f; I. j" ~- C
Data columns (total 4 columns):
: q& H) @0 z, G, Z: J # Column Non-Null Count Dtype
7 v) `4 r+ [$ |: B2 i--- ------ -------------- ----- # x$ l* a, M3 D) Q! W% C! D$ s
0 carat 53940 non-null float64
; r; y( p- d4 l 1 cut 53940 non-null object " T, p/ u1 K! k; Q% u: u8 \: j
2 clarity 53940 non-null object
2 w& B" n" J/ P 3 price 53940 non-null int64
" O2 o$ g4 W& @- I6 S5 Z2 @" C- Pdtypes: float64(1), int64(1), object(2). D" H$ X& U. V* S) }0 J4 K7 z
1
7 u1 k8 k/ [4 h2
6 Q e; ^4 R; ~) |2 @' X36 _6 e* L" `( l
4
w) o1 ?4 c V% Q" Y1 k& w! U55 n1 O p" N+ ^$ D9 W g# h- e2 M
62 d4 B. p/ p2 V
7
0 V7 C7 _# ]* p- }* [0 Q8
6 ~0 d( ]: H7 o9! E% m& [' N7 i5 X9 s# X
比较两种操作的性能
: I4 _) N# U# u%time df.cut.unique()9 v6 f. A% v4 i% s& I2 A
' k) |9 B; g* {- m. P0 }/ p z7 q: O; vWall time: 5.98 ms2 P7 ]) `0 }" v3 y& u8 i3 N7 J
array(['Ideal', 'Premium', 'Good', 'Very Good', 'Fair'], dtype=object)0 [% r# _( C4 s P( P5 [
1
. H8 H9 P; S! T1 V6 s2 c2
' A$ I3 N/ |- O( m3 M' l3' _: y1 e) N* I- p5 h/ {) m
4# e3 q) r; y4 j$ y
%time df.cut.astype('category').unique()
) y6 A7 @6 F& Q0 h& j
* Q6 X* w0 h- Z7 x5 h: ?0 dWall time: 8.01 ms # 转换类型加统计类别,一共8ms
! i5 y. T* G% w' ?( u['Ideal', 'Premium', 'Good', 'Very Good', 'Fair']
7 }; t" ]$ q+ ^5 @8 ?Categories (5, object): ['Ideal', 'Premium', 'Good', 'Very Good', 'Fair']
9 S( Z- R5 N4 K! Q7 k" S1
' r0 H4 u- j# w' {" i2, H3 I* q3 Z; d5 P
37 D3 {5 u8 B+ s0 ]0 S+ Q* n* }
4" o- c+ H2 w' }/ S
5: z9 N0 J# d3 G% c
df.cut=df.cut.astype('category')0 Q' G7 L% [1 K3 R1 z
%time df.cut.unique() # 类别属性统计,2ms
& ?+ b9 g' I# f' \. j N/ |9 i2 E
' t$ I: s4 D3 wWall time: 2 ms
z7 H: I0 {5 J$ Q; G3 _['Ideal', 'Premium', 'Good', 'Very Good', 'Fair']) L/ e5 P+ [1 O# O' M$ k
Categories (5, object): ['Ideal', 'Premium', 'Good', 'Very Good', 'Fair']* k8 m5 M$ v* N! u" d
1
# Z+ q* c0 x) Z; F( k% B' a% P2' a7 S' k, g: Z6 Q6 V. A
3/ B" r7 Y7 o2 }9 L
4( k. e1 W5 @5 X
5
. B& p0 G" W' Z6& \3 |: V: V2 F: y. L6 J8 l
对切割质量按照由好到次的顺序排序,相同切割质量的钻石,按照纯净度进行由次到好的排序。 O: z8 m" ]7 G
ls_cut=['Fair', 'Good', 'Very Good', 'Premium', 'Ideal']2 `% E$ q* b5 J B& W
ls_clarity=['I1','SI2', 'SI1', 'VS2', 'VS1', 'VVS2', 'VVS1', 'IF'], \/ d$ s& Q2 K
df.cut=df.cut.astype('category').cat.reorder_categories(ls_cut,ordered=True) # 转换后还是得进行替换3 `* M- o& }, D7 h9 ~
df.clarity=df.clarity.astype('category').cat.reorder_categories(ls_clarity,ordered=True)
! A1 L; Q6 G) x8 p* d& q& y1 `' `. p5 I6 v3 s! N' U" u
df.sort_values(['cut','clarity'],ascending=[False,True]).head(3)
' e' C& h2 @% d6 K6 f8 C: J3 x6 }. x$ {: N
carat cut clarity price
7 L. I; v7 h4 ]315 0.96 Ideal I1 28017 l3 }* ]# m, Z4 M v
535 0.96 Ideal I1 2826" i: B- c5 C8 v$ v3 G- [+ s2 ?# q0 W
551 0.97 Ideal I1 2830' k% ^9 x. P* s! d& c- v
14 g/ P8 k5 X. [
21 @* i- e8 g* K+ c. }+ i5 R* X
3
8 |# P! F" r9 |! {0 V Y+ Z2 c4
2 e c, m6 q7 B! F+ [& A5$ r V6 v* l7 V: b
6
% L7 d; y) b) a; B- @7
8 I+ s) s5 a/ o7 c! i87 Y# R# Q8 \/ T2 C. p
9) Y5 t' n2 d1 u% ?
10, I+ h4 F! W; z e; u2 [
11& }+ z5 W& J" q7 b9 _' ]8 J
分别采用两种不同的方法,把 cut, clarity 这两列按照 由好到次 的顺序,映射到从0到n-1的整数,其中n表示类别的个数。* H9 y1 I: X: E% ~+ A. ^5 Y
# 第一种是将类别重命名为整数
8 [% T5 m8 s$ r S3 H) @ }dict1=dict(zip(ls_cut,[x for x in range (4,-1,-1)]))# ]; ^9 T3 e! Z+ W% h6 ]" ^
dict2=dict(zip(ls_clarity,[x for x in range (7,-1,-1)]))3 y; a! K' q/ @& B1 P
, B! ]8 d/ O" wdf.cut=df.cut.cat.rename_categories(dict1)
5 m& B: m: y2 d2 G$ V" y m0 q6 tdf.clarity=df.clarity.cat.rename_categories(dict2)$ I; D% W7 [) Z
df.head(3)
* d. [' g# _- y( m# F" l
9 c! [* T/ t q: Y. H# @ carat cut clarity price3 m" a. w3 o: P' S1 D
0 0.23 0 6 326
4 z4 N! `( h t7 ^, b1 0.21 1 5 326% z) m1 U3 a) j/ l& e [( P
2 0.23 3 3 327+ R) N" p7 ~+ b+ w# I$ l$ W" ^4 U
1( k5 @) P/ `! p! Z, |
2: x" i! G# {/ ]% d$ o
35 W+ T) u V' S3 _
4$ K0 ?$ n. T' ^( v
5
! T" m7 X0 x( Y6 [, `' r6
7 u; u1 }9 d1 W! M1 x7
" k ?: r- v; H3 W- M- r8
e( O1 I3 @, [" [( k9& w/ d; q5 `! q( b; c9 w
10
* y: y4 h/ u3 g) t) Z% m3 J4 i11
' \8 R. l4 P6 J# _+ i0 l$ @) g12
3 R5 ^8 I% f/ O1 c1 e' e# 第二种应该是报错object属性,然后直接进行替换
9 a) ?/ z2 M# W2 X) j/ u ^- k+ f- xdf = pd.read_csv('data/diamonds.csv')
b0 u, h H: W z# q. wfor i,j in enumerate(ls_cut[::-1]):( E, b" v; @( l$ P
df.loc[df.cut==j,'cut']=i
* H( U% E! F; i
. X: Q1 H8 S+ D9 Z3 v0 bfor k,l in enumerate(ls_clarity[::-1]):0 ?' m- q! @, O$ S' P
df.loc[df.clarity==l,'clarity']=k. l3 ^! R. D) X* _& q2 p; M
df.head(3)
, ~7 [* D2 j8 z# c: b% D. D. O# k2 m
carat cut clarity price
) m3 D4 n: _! m$ i1 m' }0 0.23 0 6 326
& o' ~1 A8 X; s) c* o4 C+ |! X1 0.21 1 5 326. b2 \3 B3 H( @; X9 f7 x" z
2 0.23 3 3 327+ Z0 o! G, u- }9 P$ W$ Q
1
* T6 m" S3 d6 o0 W. c2# I+ A" G3 l0 I+ Z- c6 c# c5 c& S
3
4 i! f1 c( o( g5 e8 m( N4
5 m9 D% K1 R& j+ y7 Z4 D8 s59 a1 t7 v! r+ ?6 a# b
6
3 V) y4 Q- \, Z' q3 t+ p# q7 S/ |$ q7
7 j$ ?$ G/ Y* B# g8 t% g* g# W1 s4 x8
- y' x; ^* K: ~! t/ s; q+ t4 T3 z9
5 h3 x+ v: u! S! C4 o) s; q6 n10
8 v7 i/ s" Z* k9 b/ a! {7 K& F11
! G1 K; }8 [5 [9 j. X12
7 p J% e O4 U; G/ _0 ^13
; s# B4 V) Q, Y+ b% g. ^+ M7 ^对每克拉的价格分别按照分位数(q=[0.2, 0.4, 0.6, 0.8])与[1000, 3500, 5500, 18000]割点进行分箱得到五个类别Very Low, Low, Mid, High, Very High,并把按这两种分箱方法得到的category序列依次添加到原表中。3 M' R) j$ u) U) A2 p
# retbins=True返回的是元组,第一个才是要的序列,第二个元素是分割点
" H9 @1 j, S4 b& r" o* t% {avg=df.price/df.carat
) \0 q, f& [, z, y
" g5 R) G3 }, k# }9 G7 X) S+ wdf['price_quantile']=pd.qcut(avg, q=[0,0.2,0.4,0.6,0.8,1],4 C5 D- L+ m) E: P0 j
labels=['Very Low', 'Low', 'Mid', 'High', 'Very High'],retbins=True)[0]
4 J& ^2 N* a# _3 L: ?* ]$ m; ^# X2 {9 O8 b+ U0 M, t, i
df['price_list']=pd.cut(avg, bins=[-np.infty,1000, 3500, 5500, 18000,np.infty],
8 ^5 P5 Y, ^. v7 Y8 d labels=['Very Low', 'Low', 'Mid', 'High', 'Very High'],retbins=True)[0]
) }# L- M. l( j7 b' Odf.head()
3 n7 C7 O! B: S8 d5 Y4 c& o) E2 `8 X
carat cut clarity price price_quantile price_list
L+ {2 X- {5 B7 h0 0.23 0 6 326 Very Low Low5 X/ a8 g8 n. o3 }9 I
1 0.21 1 5 326 Very Low Low9 D+ C7 n b7 d }6 {; Y5 E2 J
2 0.23 3 3 327 Very Low Low
- k! B j% `3 K% E3 0.29 1 4 334 Very Low Low- t' N0 k+ \, x. ^8 C% C4 `
4 0.31 3 6 335 Very Low Low
D* F; Y7 ?5 v* [$ E
1 ~6 |, B. `7 P+ M# h u1
! k) G# l4 P5 C; A22 f# d7 c/ m8 [5 M
3
# i" L% x. o) C0 o! \7 L4
) G1 Q" T8 M- ^ t5
2 D# B3 K" K6 w# w62 j: q, o* m7 n: ^; _
7& e" n: @# f3 i! n1 z, [8 H
8% z5 s8 d* @; @* N
9
4 q5 S$ j7 H/ X* f5 b% j9 ]# p10
/ R% e7 ]% L% }# s% L/ k7 I11
% h( R' ~3 K0 Y$ _12& W B' F% n- \8 ?! ], b ]) z
13
- o* x& ~! p: P! i: f5 H& |, @14* |% O: O8 t) |5 p
156 W6 @- l! t* F5 h8 j1 s J
161 a0 i% l" Q) y6 h) Q' E
分割点分别是:
6 l, _$ D! F- |9 f. [4 D& V) F; A8 f
array([ 1051.16 , 2295. , 3073.29, 4031.68, 5456.34, 17828.84])$ i, y2 Z) j: p2 v2 W
array([ -inf, 1000., 3500., 5500., 18000., inf])
/ {& P8 m( F& [" j8 d( j1
5 P y% g0 T6 r4 I' }1 W: F% ~, h2
7 m: ^ J& T. k( j3 c/ z$ G第4问中按照整数分箱得到的序列中,是否出现了所有的类别?如果存在没有出现的类别请把该类别删除。
2 t6 E! e, Q4 i ~- Odf['price_list'].cat.categories # 原先设定的类别数8 H! o+ b3 |. [ c+ ^( f
Index(['Very Low', 'Low', 'Mid', 'High', 'Very High'], dtype='object')
9 O9 {" H8 Z+ K9 e, `, T$ A8 z) `; M! R! Y7 `" U! K
df['price_list'].cat.remove_unused_categories().cat.categories # 移除未出现的类别
0 K& B. D9 ^8 n' c0 ]0 KIndex(['Low', 'Mid', 'High'], dtype='object') # 首尾两个类别未出现
- p/ ?2 }7 y8 }- Q5 r1
) f; O) L/ ?2 ` f2) |# ~: d0 z/ z! s; d; f( y
34 a4 X! }. ~3 R! c. B
4
: X0 n: d7 r q7 G. D, O* q5
( G& x/ P- Z# ?( Eavg.sort_values() # 可见首尾区间确实是没有的- p4 E, E2 j! M: H
31962 1051.162791# w$ }% e6 b; F" k0 p" x' c
15 1078.125000" E7 H. `/ L7 @8 t
4 1080.645161
* c" D' |; y% t1 ~6 q28285 1109.090909+ Q% M) B( l: L* J) g" Q& f
13 1109.677419
' h7 w. t/ q+ p' U4 T0 ]/ @ ...
, V/ B$ \- `( f4 [6 Q0 [/ Z' \ R3 y* q26998 16764.705882
6 R. w$ V9 P% p5 w# f/ h27457 16928.971963
( c, \, v0 U2 G% @+ a* J7 F) V27226 17077.6699032 B6 F: [: F5 J* n9 Q
27530 17083.1775704 E, H* A v, @" h
27635 17828.846154: }. a7 d; }' g! H# K$ \6 \
1
/ L% ^, `: K) Y3 d+ E29 j( Y; r' f' r7 T: x
37 h/ u: [; ?4 `+ R
4. K ?/ [5 ~" K; c' n4 u' E
5# n; I: ~1 z& M% U6 a* v0 R
6
8 B) Q; _' Y0 L( ]5 C+ t. n6 K7 U6 Z$ K$ |, Z7 ^8 E' N( }! d
8
8 U* B0 _& _. w7 l! p n4 _9+ E8 ` G+ X! W0 B. r: P% `3 d, V
10
7 p6 J, D" D5 e4 `# d3 @ f11: Z6 D! E! C6 L# P; T8 _) v
12
- j/ }" p; i! ~7 J% Y对第4问中按照分位数分箱得到的序列,求每个样本对应所在区间的左右端点值和长度。% n: x! J) U% O! x& |/ ?6 `+ H+ t4 W
# 分割时区间不能有命名,否则字符串传入错误。
2 j; E6 e- K+ o- r2 Y) c5 [id_interval=pd.IntervalIndex(
) x4 E. n- e- u3 J# N: e( F pd.qcut(avg, q=[0,0.2,0.4,0.6,0.8,1],retbins=True)[0]# w* u, c' ?9 K5 n+ O: I: G) O0 ?* h
)! r y. ^$ h! |3 `% G& v% s9 L
id_interval.left: |7 J2 a; W1 Z/ ?" H) E3 F. X" }
id_interval.right
5 u% ^& i% z/ ^id_interval.length ' L, k9 P. ` y1 ]( w; k
1
# I& T/ z% L _1 }" \2- F* `. W' z, m, {5 A j0 K# u1 z% K
3
* K4 |+ s$ n5 Y. T9 ?+ b8 U4( x9 Q7 v4 g4 ?0 k4 \7 _
50 r# S R: o' S. D. R$ E9 X6 m" J
61 i: g! u( X) k h
7
; n. ~4 w g9 E5 Z: ?第十章 时序数据
/ Z3 x9 G# R' t: r8 [import numpy as np
) b; Y/ E1 s! ~# a6 m) ]" U9 B7 Vimport pandas as pd% Y- b* P4 z( W0 X* j6 J8 O1 M
1, |9 G/ W% `7 y4 c
2
& |4 o) M$ R8 ]9 B1 N& ?
- B+ E' A4 \( n8 Z
+ U! e% I- c3 D$ A10.1 时序中的基本对象9 Q! d; b9 z& v( j6 H
时间序列的概念在日常生活中十分常见,但对于一个具体的时序事件而言,可以从多个时间对象的角度来描述。例如2020年9月7日周一早上8点整需要到教室上课,这个课会在当天早上10点结束,其中包含了哪些时间概念?* Y4 W2 m N0 y( _" z) {# t$ x
2 {2 {; j. T* m5 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的简写。* h5 n7 p2 S) r" e9 ?( J: g( f2 i
; |/ y; N& N" ?" y0 U% g3 U r会出现时间差(Time deltas)的概念,即上课需要的时间,两个Timestamp做差就得到了时间差,pandas中利用Timedelta来表示。类似的,一系列的时间差就组成了TimedeltaIndex, 而将它放到Series中后,Series的类型就变为了timedelta64[ns]。! m i8 ?, w# H% o6 K
) e$ y- j+ F. I& ^9 t9 }% k
会出现时间段(Time spans)的概念,即在8点到10点这个区间都会持续地在上课,在pandas利用Period来表示。类似的,一系列的时间段就组成了PeriodIndex, 而将它放到Series中后,Series的类型就变为了Period。2 p" e& y" ~- z w1 N4 ` k
: i( |% U% D6 [( d2 e1 R: s3 Y# G
会出现日期偏置(Date offsets)的概念,假设你只知道9月的第一个周一早上8点要去上课,但不知道具体的日期,那么就需要一个类型来处理此类需求。再例如,想要知道2020年9月7日后的第30个工作日是哪一天,那么时间差就解决不了你的问题,从而pandas中的DateOffset就出现了。同时,pandas中没有为一列时间偏置专门设计存储类型,理由也很简单,因为需求比较奇怪,一般来说我们只需要对一批时间特征做一个统一的特殊日期偏置。
7 c! L( v* q0 n% D( e# O2 b. I9 ?6 G1 Q) }; ^) G
通过这个简单的例子,就能够容易地总结出官方文档中的这个表格:
) j6 y# t: Q2 M- t b
* @7 J6 l4 a+ i0 E0 U; r概念 单元素类型 数组类型 pandas数据类型
5 r9 p2 }$ Z3 y Z7 W) W! _# PDate times Timestamp DatetimeIndex datetime64[ns]. q+ }1 \* Q0 `4 }( h; j% C
Time deltas Timedelta TimedeltaIndex timedelta64[ns]
0 f7 V" ~' v0 N5 c$ y( dTime spans Period PeriodIndex period[freq]3 X. A! Y) k$ l9 x# O
Date offsets DateOffset None None4 H; E" _) D- c( t, V8 e
由于时间段对象Period/PeriodIndex的使用频率并不高,因此将不进行讲解,而只涉及时间戳序列、时间差序列和日期偏置的相关内容。 y# O% R4 Q# o; w9 E" ]" e' x( D. J
% s, Q* v2 T0 p8 P- X5 O" M10.2 时间戳3 L% N) D+ Q4 P4 @/ q/ B
10.2.1 Timestamp的构造与属性/ Y# L7 O0 j# W2 z5 u. s
单个时间戳的生成利用pd.Timestamp实现,一般而言的常见日期格式都能被成功地转换:6 M( D y$ b1 r" P0 g
; s* `! I3 _5 f7 K3 H. E3 m9 U( r. Z
ts = pd.Timestamp('2020/1/1')
; L$ X3 G4 |+ A& ^4 n
4 j8 F4 m! h6 e7 S* q W# K3 Qts% o. H# Z* c; Y, a2 s- F0 g
Out[4]: Timestamp('2020-01-01 00:00:00')7 v7 z: {5 H7 T
6 [; q& v/ A/ h9 T# E' e" {" cts = pd.Timestamp('2020-1-1 08:10:30')
9 y+ M7 Q, a# @# y N8 ~' u" `% P9 u/ q; s1 u, w& B
ts
1 R3 N N/ n2 f/ t! ^1 W- I9 _Out[6]: Timestamp('2020-01-01 08:10:30'): S9 ^( X9 l/ V$ C- w6 w0 \
1
7 k- O& {1 k/ D, G( g2; e- B) f$ M1 \. d" B& t" H# Y
3
$ E' W2 Z' I2 b `7 I" Y/ u4% t. v: o7 w# M! c0 t$ h1 ^( R
56 v. D. q. `/ ~3 d
6& c4 C( l! A% y* c$ }' e; t6 F. @1 N
7
8 A8 o9 z1 N# j+ R2 j+ L5 q- z$ P84 E/ r5 L# @: I* `. q% A) |6 K8 s
9
) p$ t9 L3 v/ Q( e* i: e% e通过year, month, day, hour, min, second可以获取具体的数值:
+ W7 X0 E( W8 m5 }) j) \, V' Q( [* L$ G. e4 H, } g& n( D
ts.year
9 M8 l% T7 } Z _2 J6 Q) YOut[7]: 2020
8 U# F+ m# p! \* a$ p) f/ |* K
4 k- J( g$ {& ?$ Wts.month
( A6 Z# d. ?6 ?3 @& v5 L7 j" l- WOut[8]: 1
- V( L' J- Q3 D& A+ s% H! ^- }! s x3 \8 T, L1 A1 [
ts.day# \: e( R0 j+ N
Out[9]: 1, d& R" u2 r5 K' M& x) O
/ p: z6 t3 @/ y7 @3 e z0 i
ts.hour4 g* u$ A* A# V% }
Out[10]: 8
* \. K5 q9 v! _# [2 e/ M) s) k+ V
ts.minute+ `4 a; A2 z& d/ Z7 c. U, [' C
Out[11]: 107 v" \+ j1 z6 O3 f; M5 e
! W1 N( U4 e6 W( m: [$ o- R9 tts.second9 R. T) x3 w) g( @
Out[12]: 307 O* `. k- k# k! h0 B- G' S. K
+ q& u u: j) O$ L1
/ S4 a) J+ S0 f( K: H( n6 W# k2
- ]- \: D+ z( R6 M3
! d9 S; h6 v. r: }! Z4 o% ^3 C6 E43 p) x+ N+ E2 Z; B* v
5; W, f5 ~+ U; U& i( ?+ v- k
6
6 T8 Q: L" L4 \7 m7 Q1 L1 e9 H7
8 k0 V+ T* n1 v# ]7 z1 Y d8
8 f5 p4 q- D! `9 Y! u0 l9" k6 k4 y- f; \. a- Q+ W
10
( H0 r+ i" S5 z2 j. u; P11
' b% _6 c, }; P7 D1 f12% D, P, g# @0 f0 H! F8 W
13! Z/ n6 L: Q) V6 X& @0 t# J, d
14
. h; \' X" E. f3 e! y/ T" R8 e15& g5 N9 A, y, C- I
16
/ C" G1 i; Q0 t17$ u, M! o% R, v& C2 `$ y
# 获取当前时间" M3 d0 N ~' y8 u$ X- W
now=pd.Timestamp.now()
: _& `( @* _; p. q- s: P$ P1/ g0 Z* z! D9 d5 h: s" c( R
2% m: j6 ~$ `. x, ~
在pandas中,时间戳的最小精度为纳秒ns,由于使用了64位存储,可以表示的时间范围大约可以如下计算:
4 }: F8 e5 O( HT 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)( r* t" i6 h& Z
TimeRange=
9 S5 Q3 Z1 o. q- o" R10 4 j* Q+ w( w- T1 ~: {. R- c
96 ]7 ]1 N5 j, d5 S
×60×60×24×365
$ c9 D q7 t* T' m7 `4 Q: P$ ]' b0 |2 u# M# N7 Q- ]$ J. b
648 Z* \+ A' n( g
/ @- W+ j8 P# H
5 f7 {1 Y1 s; w6 ]$ F$ L8 _ ≈585(Years)1 G% _$ U2 ^+ O. Q M$ ?7 F4 V
* k) _* r+ |2 D& p
通过pd.Timestamp.max和pd.Timestamp.min可以获取时间戳表示的范围,可以看到确实表示的区间年数大小正如上述计算结果:8 N/ {$ v9 F: D" d( o
4 z& W/ S+ a( T* x/ c$ i- Upd.Timestamp.max# L9 \, `2 |, Z8 k% ~4 U' @
Out[13]: Timestamp('2262-04-11 23:47:16.854775807')0 g& H; O& h. s/ |0 R
7 O, ?% C& q0 W4 ]- w
pd.Timestamp.min
# A) K/ R- g! b% g! COut[14]: Timestamp('1677-09-21 00:12:43.145225')
! k0 Y) w- k% y( o4 J- ^ Y9 i. L/ |5 t/ s; b' T
pd.Timestamp.max.year - pd.Timestamp.min.year
5 C0 Z( A4 M. [0 v7 S5 o2 z% NOut[15]: 585) q2 k1 i) H" ] N* L: v5 f
19 `0 ^: }( E* Y
2/ g$ \, }' m8 {, m
3
: g' V" E, t& G% W; s( m4
9 R5 {* @2 ^4 g/ U; ~) l3 S; V1 K* U5( k, V! P' b3 I" c3 b2 h
6
2 _. n# i3 P& d$ N) ~( A; \7 j7
; u2 A- i7 D, ^/ \5 l/ z; O80 u* B% M. d- Z9 g! h4 p
10.2.2 Datetime序列的生成
5 U1 T# ~7 P0 A, opandas.to_datetime(arg, errors='raise', dayfirst=False, yearfirst=False, utc=None, format=None,
' `5 V5 N9 g0 t* I" j- n' U3 A exact=True, unit=None, infer_datetime_format=False, origin='unix', cache=True)
0 d# l& l2 W9 B( p K+ ~9 M1) _3 `4 ?$ \- f. J3 J M" Q$ P
2
2 T7 }2 L7 x# t* c# b! W5 ^pandas.to_datetime将arg转换为日期时间。- \- P( `' M) L" |
( h; b7 H& @1 E/ t+ Q& f# x- B: x
arg:可以是argint、float、str、datetime、list、tuple、一维数组、Series、DataFrame/dict-like等要转换为日期时间的对象。如果提供了 DataFrame,则该方法至少需要以下列:“年”、“月”、“日”。* |3 M! z+ h% T9 {$ |% Y
errors:
" n& d& h# K7 ~; ^* u- ‘raise’:默认值,无效解析将引发异常5 ~" \& q5 Q" {. D. p
- ‘raise’:无效解析将返回输入
: o% k0 d0 ~. b2 u- ‘coerce’:无效解析将被设置为NaT: U) z: [/ c6 f9 c- h
dayfirst:如果 arg 是 str 或类似列表时使用,表示日期解析顺序,默认为 False。如果为 True,则首先解析日期,例如“10/11/12”被解析为 2012-11-10。如果无法根据给定的 dayfirst 选项解析分隔日期字符串,会显示警告。+ H, g* U/ P7 [3 B) W! k
yearfirst:如果 arg 是 str 或类似列表时使用,表示日期解析顺序,默认为 False。如果为 True,则首先解析年份,例如“10/11/12”被解析为2010-11-12。无法正确解析时会显示警告。(如果 dayfirst 和 yearfirst 都为 True,则 yearfirst 优先(与 dateutil 相同)。)! x$ A! t0 Y' I9 F7 X
utcbool:默认None,控制时区相关的解析、本地化和转换。请参阅:pandas 有关时区转换和本地化的一般文档
1 _1 D$ s/ n5 L- a. P3 ^ Y3 |format:str格式,默认None。时间戳的格式不满足转换时,可以强制使用format进行匹配。* ~: |: |, b2 H; h( w+ h5 M
unitstr:默认“ns”。它是arg (D,s,ms,us,ns) 的表示单位,可以是整数或浮点数。这将基于原点。例如,使用 unit=‘ms’ 和 origin=‘unix’ (默认值),这将计算到 unix 开始的毫秒数。
8 Y4 ]; M4 f7 j4 r$ Vto_datetime能够把一列时间戳格式的对象转换成为datetime64[ns]类型的时间序列:( n( C: s6 o) w3 G1 D" n. n4 }
pd.to_datetime(['2020-1-1', '2020-1-3', '2020-1-6'])( U) \, @: r7 r/ J# Z
3 @4 \# H s k
DatetimeIndex(['2020-01-01', '2020-01-03', '2020-01-06'], dtype='datetime64[ns]', freq=None)
1 V& R" W% M" t, V9 k1
$ K" D9 Z0 a: r# m) V: a+ |2% B( X& f) R9 n- ^$ G s( u/ \8 D0 X6 r
3* P/ U k, r9 d( l0 P
在极少数情况,时间戳的格式不满足转换时,可以强制使用format进行匹配:
. P$ i5 R3 C H% |- b) |* ^1 M; e: |7 z+ q
temp = pd.to_datetime(['2020\\1\\1','2020\\1\\3'],format='%Y\\%m\\%d')
* u) V$ q6 B% {+ ?8 }! Gtemp+ w) i( {- o ~4 x
% |8 J2 `+ o, R4 I6 Z: c% I& I; JDatetimeIndex(['2020-01-01', '2020-01-03'], dtype='datetime64[ns]', freq=None)
7 J& A, l {$ S1
2 ~4 v6 Z J) Q/ R2/ y' h9 x* m8 C! b) P
32 N) ~4 G/ V, t+ Y; X
4
& v {4 x( A- C 注意上面由于传入的是列表,而非pandas内部的Series,因此返回的是DatetimeIndex,如果想要转为datetime64[ns]的序列,需要显式用Series转化:: E/ f. s) w! X3 g" F
' E/ }% f9 L* A$ Y' L: N
pd.Series(temp).head() ]$ v$ a8 D: C! n9 Z3 ^
2 z: d D+ m; f) C: C5 o0 2020-01-01
1 o+ f% K5 j% @: f4 `9 d4 o' L1 2020-01-03
: t- t' w# o8 q6 ndtype: datetime64[ns]
3 n. R* W# }1 d! f g9 d) I% i! I1
8 P4 d& o" u8 t4 j4 _" L2+ _2 b, e6 O9 ^4 ?$ R
35 k: Z6 E7 O5 _1 Y
46 |$ t3 K1 ]( y1 a: y
5+ A% h, H; N. g% P0 I
下面的序列本身就是Series,所以不需要再转化。. W: A* d, V! p7 W& |( C" [1 E
( R5 A Z( N2 U. s
df = pd.read_csv('../data/learn_pandas.csv'). N$ D2 H" J* P
s = pd.to_datetime(df.Test_Date)6 h) d) o3 X$ h% v* K
s.head()
+ O* s5 T k) O
5 C, O$ U1 _ w/ h; ^, ~6 `$ f0 2019-10-05
$ ], a3 H8 `3 t; S. R; X1 2019-09-04
! e% T! x/ Y) s% i7 v( a" k+ Z6 @9 }2 2019-09-12& W/ v }) P# i
3 2020-01-03; r$ a, z5 t7 s" W* D) s
4 2019-11-06) N( H1 ~# ]7 R" B! {1 L8 O% U
Name: Test_Date, dtype: datetime64[ns]
# K U# K1 h% E+ m. E3 q1
! |2 a0 F0 I4 t/ i. s7 g$ n% U2
) k& F+ v8 J) ^, o$ P2 i3
& k+ N2 ]; J) s' d* J% ~4) M0 U9 R# a0 J; G, ?* u3 M
5
' s& S$ r) [& |* ~) ?6 |' L% }6: x7 n& i# R' w
7& {$ k2 |7 }5 p: \: h
87 Q# z6 @ C( E4 B. E
9
6 \5 c! Q- @. T7 O2 C10
5 o* T |5 ?, ?5 x+ ?把表的多列时间属性拼接转为时间序列的to_datetime,此时的列名必须和以下给定的时间关键词列名一致:
: m' E2 x& W6 G" G% C9 Q$ z8 hdf_date_cols = pd.DataFrame({'year': [2020, 2020],
/ | B" O$ x0 O% z: e% @9 R1 r; ]0 c 'month': [1, 1],
! b6 K& Y7 m% d0 N0 |+ F# x+ N 'day': [1, 2],
( J4 L4 T) E) m7 {& s+ m2 s' w6 c 'hour': [10, 20],
4 W o: T1 A# S, f, v8 }. E% z$ U 'minute': [30, 50],
$ t- e: [" `* X k 'second': [20, 40]})$ j/ n1 x5 S# j. V, M
pd.to_datetime(df_date_cols)- M$ \, s S! E! e# d# _
9 Y, B2 Y0 l4 H: z8 T* o
0 2020-01-01 10:30:20* K5 t6 u# W* B6 F, P- [3 e" P
1 2020-01-02 20:50:40
# \$ p& K% R4 P6 N8 w$ rdtype: datetime64[ns]
; l/ j' t5 V3 O9 M1& p& x3 c ~. V
2
% ]5 n0 B/ j+ h* F% C37 z& B# _8 n/ `5 M# J" J
4
8 L' X2 ^( X7 p/ g5
$ y9 w1 ]5 o/ c/ ~; n6 q6
; K7 I" @* @. t- I) V7
6 `3 ]$ E/ R+ T! w( m5 y. {81 U' F7 I! P" Q' t$ y% J
9
1 ~0 q {# R8 J* T10$ w- Q h2 [. P% p/ j* m" ^) V
11" }0 e6 b/ W! o0 R$ z0 Y# r: k
date_range是一种生成连续间隔时间的一种方法,其重要的参数为start, end, freq, periods,它们分别表示开始时间,结束时间,时间间隔,时间戳个数。其中,四个中的三个参数决定了,那么剩下的一个就随之确定了。这里要注意,开始或结束日期如果作为端点则它会被包含:, y m7 A: S+ j( t1 b2 L
pd.date_range('2020-1-1','2020-1-21', freq='10D') # 包含9 T' q. j, Y% }' P: H
Out[25]: DatetimeIndex(['2020-01-01', '2020-01-11', '2020-01-21'], dtype='datetime64[ns]', freq='10D')! Y, s' H! Q7 R
; M% H5 [- }. D$ H$ Zpd.date_range('2020-1-1','2020-2-28', freq='10D')1 E# h2 t- E" p* g% A+ T, S; Z
Out[26]: ; R9 {& x2 u x$ A$ g
DatetimeIndex(['2020-01-01', '2020-01-11', '2020-01-21', '2020-01-31',
( d# B' _$ { s. Z! d '2020-02-10', '2020-02-20'],% j/ f3 A: z) R4 Z+ \
dtype='datetime64[ns]', freq='10D')
4 ]( T- W9 Q. w: C$ U$ ~4 i( l+ e# F
' y) s! K9 f f6 Dpd.date_range('2020-1-1',
# n0 Q% ?+ T: U/ z; ^ '2020-2-28', periods=6) # 由于结束日期无法取到,freq不为10天0 p" g t4 `/ z. E% o
6 A1 K7 A6 m5 F+ ^" p* u1 z
Out[27]:
/ U4 P2 q! c& pDatetimeIndex(['2020-01-01 00:00:00', '2020-01-12 14:24:00',% ?; L L/ _6 U1 D+ L3 {9 T' {) Z0 Z
'2020-01-24 04:48:00', '2020-02-04 19:12:00',( ~- o0 g5 F% U( N
'2020-02-16 09:36:00', '2020-02-28 00:00:00'],; J2 m7 v" s- Q8 F! `+ N
dtype='datetime64[ns]', freq=None)
; ^( a4 M6 j [9 k
# }" A; ]9 n- K- J, b/ Q' l1- u7 e) m/ @, i& H# y. a
22 P2 Y4 U/ T1 f+ P
3
( z9 l, e G2 A4: T! H# [( l2 J/ v# B* \2 L
5: s5 C! W" f5 ^$ P( {6 n
6
; m0 H; y t8 Y" C+ ]; S7! {3 M" L/ A) a3 I- l. \$ ?
8; n: A* u: _. I/ p1 R. J
9+ U7 \% d4 R) e6 T5 ^, F
10
1 z1 X0 N! f s" z( O) s; y, `114 R8 I4 O9 `+ B- v4 z1 G
12
, ]) Y: U0 [2 z0 r7 f+ b3 ]13
# _4 s: _* R, | ^# e0 k% |8 h14" q0 ~+ s! _7 A n
15
0 g( w9 L( ^: Y# ^+ n7 a8 D16# @: T+ m6 }! `* Q+ p) H' o& J2 S
17
3 c( T }& R8 n- L; `' W" ~2 ?# H这里的freq参数与DateOffset对象紧密相关,将在第四节介绍其具体的用法。
2 X* [" E) K0 M! p! L2 c8 c& A) x9 O- t: O3 A
【练一练】
, `5 P0 l# w1 b2 \Timestamp上定义了一个value属性,其返回的整数值代表了从1970年1月1日零点到给定时间戳相差的纳秒数,请利用这个属性构造一个随机生成给定日期区间内日期序列的函数。$ c& r! r# Z; G3 T; o+ Z: B
`) X+ d. N h
ls=['2020-01-01','2020-02-20']
7 H. I/ M; h9 Q, ^def dates(ls,n):' G% e* l5 r/ F" Z7 x+ z
min=pd.Timestamp(ls[0]).value/10**9, }: F; o8 V: j6 N% L" ^7 I! q) y/ l4 A
max=pd.Timestamp(ls[1]).value/10**90 Y: Y( @6 v& l- w$ N. U: [) ^$ A4 Z
times=np.random.randint(min,max+1,n)
2 f8 r: ^. J$ O4 p return pd.to_datetime(times,unit='s')7 F1 n; H( K4 C: t
dates(ls,10)
1 w9 p$ M( t0 \' r' k7 o0 B5 x7 Y1 C. X- I* G) j0 [
DatetimeIndex(['2020-02-16 09:25:30', '2020-01-29 07:00:04',2 j" O0 q1 k3 L8 H; N! z
'2020-01-21 12:26:02', '2020-02-08 20:34:08',5 P) k+ ]. |4 z3 K
'2020-02-15 00:18:33', '2020-02-11 02:18:07',4 k Q; i3 ^+ l1 v& {7 p# `/ O
'2020-01-12 21:48:59', '2020-01-12 00:39:24',
" F. t2 n1 O7 ^3 D2 v `& P '2020-02-14 20:55:20', '2020-01-26 15:44:13'],
) _7 b, p0 a% L1 G8 U" H# k dtype='datetime64[ns]', freq=None)
# K& {0 L4 ~- G" P1
- f5 ?9 J7 B C: _# d# g" d4 Q( g2
. ]) e, e8 n) }# V1 f7 p2 d2 C3
: E7 N" {$ n5 n5 _0 U4 k46 F# e8 z1 u" D* X5 b. u9 ]
53 c5 U* f7 @# m# L
6& C6 Y9 B* p$ M5 q) i9 w0 z
7
0 D# K7 Y7 x/ E% S; G% @# ^8
7 z$ k$ I( p. V4 n4 f9
% U( E, z" o, V6 ]2 k$ @$ B1 V10
2 R; w! t$ F4 `- D4 f11& n1 ]( k$ F) {& T* Q; S2 G/ H! k
12# O! _2 k' E& L2 C# T
13* y( }9 a/ ?$ v1 p
14
9 ~! ~1 _0 _2 Z8 ~; D2 yasfreq:改变序列采样频率的方法,能够根据给定的freq对序列进行类似于reindex的操作:2 P8 B/ f1 ?- G- _/ z
s = pd.Series(np.random.rand(5),
4 f$ m, o) c- w' V index=pd.to_datetime([
M. Z6 ^8 n$ o '2020-1-%d'%i for i in range(1,10,2)]))
$ X8 s s7 k L( N* ]8 K
. q* s! G0 @' i- J, _% n- ?/ \
s.head()8 q4 c7 Y& T3 s/ E/ _* S. p
Out[29]:
) n6 S0 X# b/ a) i. |0 [; W0 M2020-01-01 0.8365784 j5 |! `& s. H- z5 l" R8 G7 L
2020-01-03 0.678419/ Q( \6 p4 U/ M: `) a
2020-01-05 0.711897: {7 _: a- i. s0 r3 Z
2020-01-07 0.4874291 j: ^* l f: J
2020-01-09 0.604705
& _, q4 L2 N& } S6 Q2 M% a H( edtype: float64. j3 _9 z$ r- y! ]* Z( ~
! A% X, X+ g( Y; R- a, b* T
s.asfreq('D').head()
; o; g# ]) o$ NOut[30]: + o. R3 ]$ x6 W. M. o+ A( A
2020-01-01 0.836578
' _( [& {* Q" F) Y2020-01-02 NaN
( @0 s: ]$ V! C2020-01-03 0.678419
# ]4 ]/ c1 z, g* k0 d; b# G5 ]: X2020-01-04 NaN; ^6 g, V, a# I4 N m' z
2020-01-05 0.711897& L8 P4 P7 F+ m( Z4 [6 k: b' Z
Freq: D, dtype: float64" n [% Y% n! s% c+ G: m
! |) t$ S) W3 Ks.asfreq('12H').head(). t R& h Y5 e! |! w6 Z; k
Out[31]:
) Z, |" X' L! P; l" W2020-01-01 00:00:00 0.836578+ @% B2 }8 I+ c
2020-01-01 12:00:00 NaN) W5 K1 `/ K6 R$ T
2020-01-02 00:00:00 NaN
) N( v% J3 [) K, v+ v( a K2020-01-02 12:00:00 NaN
" x" [: ?& S' M2020-01-03 00:00:00 0.678419
. _; `: I5 n2 K# l( fFreq: 12H, dtype: float64
/ ~* b6 d/ c4 H! ^
! z7 j" W6 M/ s- L [4 B: x5 `1
5 y7 z! B, @, V2 W$ d ?2 v2 r* u- F2 r
37 P$ q, d% T( c2 a8 n' y
4" J2 U6 Z/ ^: u. M
5
& J a. }4 t4 t- y* J64 v, J( N( F7 w- L' L: s
7
9 D1 [# B; h; ]# i. M8$ B. e P( B0 [) B; `
98 {4 ]. W+ M! Q, ~
10) V) j; Y$ ^" [8 p& V" Q
11' V+ w" i5 I, c
12
- |- p5 w. M B9 \9 a13% _- k; v+ H2 n- U9 m: X
14
4 X2 U8 m0 J7 T0 }6 K7 E2 k15
4 G6 v: u7 R* w2 Y. X7 g- i/ {( N16
# g: C& c+ [. ^' B17
5 J1 ^( G/ \! q7 E! z S$ }' \. K185 Q( b; R! g- ~3 `
19
8 r0 |. Q6 c/ I! s1 V20
+ N6 L# i: Z, F6 v21) ~$ K! F( k( `8 M& H7 e
220 H( S- L1 d5 F) V
23
. o& @' @6 |% r% g; P24
5 ^; H% o. H+ A) I: K0 j25
& k" _0 ?, K4 o9 Q& ~2 M2 _26
1 t0 v# ]" ?: e277 U. r7 [0 A1 c
28
# k* s: p: c0 T: m- E- n5 {29
! E. m; C3 s3 b) C7 ~; |% h30* K3 A, I' t ]/ X; Y
31
" R! d' `+ g* s6 g) f【NOTE】datetime64[ns] 序列的极值与均值
1 `3 f5 Y2 i; V 前面提到了datetime64[ns]本质上可以理解为一个整数,即从1970年1月1日零点到给定时间戳相差的纳秒数。所以对于一个datetime64[ns]序列,可以使用max, min, mean,来取得最大时间戳、最小时间戳和“平均”时间戳。
/ y. {' G. l! ^5 X% C2 p, h. `4 b7 Y
10.2.3 dt对象
* L/ y$ u. ?; L- _6 V7 g 如同category, string的序列上定义了cat, str来完成分类数据和文本数据的操作,在时序类型的序列上定义了dt对象来完成许多时间序列的相关操作。这里对于datetime64[ns]类型而言,可以大致分为三类操作:取出时间相关的属性、判断时间戳是否满足条件、取整操作。
$ A- O+ C# \+ \& K. r4 h2 H: q* d& |: }& L4 j: N
第一类操作的常用属性包括:date, time, year, month, day, hour, minute, second, microsecond, nanosecond, dayofweek, dayofyear, weekofyear, daysinmonth, quarter,其中daysinmonth, quarter分别表示该月一共有几天和季度。
5 k* e( |) l5 Fs = pd.Series(pd.date_range('2020-1-1','2020-1-3', freq='D'))
9 e) d& U) [$ x
) K# R+ J! U; es.dt.date7 F! f. L" y: K
Out[33]: . X( ?3 p8 b3 d. m
0 2020-01-01. v# E8 X. ^1 A; u
1 2020-01-02
6 f( Y; }& q0 [0 s2 u2 2020-01-03
- a" T" ?; |% P: C& Cdtype: object+ D3 ]( s2 A, z: p
) L4 \6 I0 ^4 L2 } e
s.dt.time+ O! L; V/ E+ T1 Q5 u0 ?
Out[34]:
+ c3 F: Z1 Q& X E0 00:00:00
b! |& `! f6 I0 K3 g1 00:00:00; ]9 u1 u, l2 l; @# w9 W9 o4 w J+ {
2 00:00:00
' K. [ V* R/ A1 R) l% Ndtype: object$ u( m- _* b+ I
9 w( X! t1 y* z: p9 S' ps.dt.day7 X ]% E9 E8 A( T
Out[35]:
$ A" T7 ]/ K+ Y) b0 1
8 l. m! f6 p5 u# R. d" v# f @% n( q1 2) Y1 Z4 p, w9 ^
2 3) ^; D! y) A' F8 u" B# Y# a
dtype: int64- p: Z M( {$ E
2 z; o4 _* W: t. T& i4 L
s.dt.daysinmonth9 g: U9 @6 ^- ?$ c' D
Out[36]: ( P) K; z& Z% W7 o7 `6 [% V
0 31
$ `9 J) @3 k: T$ w4 @. N* A1 31! h9 _- U2 i. ?& g% z
2 318 {: r% T, W( i" f. |
dtype: int649 u6 H* P- }! M$ h2 i9 n
7 a" R$ m7 X5 m- }8 l8 t! M$ g+ B17 \- T6 Y: B+ Y$ H$ `* v
2- y: k; a/ L. B5 W
3+ s# ~- R+ @0 |* r4 K7 b
4
8 `& F9 O- R$ E6 y5% q' j% r, ?2 f- B
6
6 z. |' Q/ ?$ _" w6 v7
. k1 l* Q4 S# e' _82 I0 E/ a& Z9 K ]% K
9& A3 v; n* j5 F
100 E5 }" d5 J$ R9 r% C/ M; Y
11; g! A( p4 T r- w- q" ~0 I$ n
12
: v( @+ X, P+ |4 X5 n) r5 ^13
. E3 `" N% Y* z4 V14- e) q6 _7 L( U) S; r
155 y/ M9 @/ c, J% C3 `
164 y. ]5 E7 _* v# x" ?) J7 Z5 H5 j5 m
17
; n" w. n' P6 t18$ [2 |' `0 X) f, H
19
. R- t2 v' r- m" K* T/ w20
5 j0 V3 ]4 m- O5 W0 Y; V2 E6 V$ w; B5 B21+ {& G. x- c. W% h2 x6 I
22
) |3 \7 z) G' {% K7 \23
2 s( g8 L- P. K4 {. G! a24% B1 ?1 n* ]$ P1 u1 {) m
25' Q! T2 _% x) Z7 ~( N( ` B
26. ]' b" q2 M7 F1 }3 v& i5 l- @( ]+ d
27
7 h% y, f3 y+ n" p( B286 S( y1 c- K( Z) |8 T! C
294 [ P) \! w4 K8 O' T, M
在这些属性中,经常使用的是dayofweek,它返回了周中的星期情况,周一为0、周二为1,以此类推。此外,还可以通过month_name, day_name返回英文的月名和星期名,注意它们是方法而不是属性:
, c# I$ P1 P' {6 v! Z2 c/ y8 W H9 \( \! B. u$ B
s.dt.dayofweek
0 \5 Z0 T6 P$ ~) W) kOut[37]: " ?9 ~% g( W% Q2 e1 K
0 2
7 Z& Y. |6 [! A, L1 32 d2 G) d( M8 C1 v+ G8 [
2 4& i! f- X4 d M- [
dtype: int64
9 \+ R$ p0 d2 f0 P& Q4 l( ]: }! k# b+ Q; o6 @3 |$ w& O3 w; d
s.dt.month_name()( Q0 M L2 L7 \$ Y
Out[38]:
$ D2 Z; X5 z) T) S4 x0 January { S; r. l8 W" B
1 January
4 ? k5 f& `% u2 January
3 |, X3 S+ f5 L9 y" @& i& n! Ldtype: object+ g0 s" Y/ R4 z# [2 N$ Z+ ^
1 B( U o) p5 G/ g9 Q
s.dt.day_name()
% W ]4 Y, A8 O$ E7 h$ ZOut[39]:
4 K2 `+ }3 r4 R# M0 Wednesday
- G- Q9 c3 F4 u) p' c1 Thursday
/ u9 \9 h0 p" u1 @6 F2 Friday0 T! o- J: ]4 P/ P4 F# e
dtype: object2 c [( A: L" t) a( u, R
$ ]7 s; t; b4 ~- E. C( W; H; D1 ~7 e6 C9 h% M4 t/ u/ q' V# W
2 m9 M6 x$ e* u7 K1 ^* A
3
. G. H% |$ h0 Z; y; Q4
8 f; J- c# Z+ U0 ^# y5% y K: C5 Z+ `) r7 K: }. y+ [
6: I4 a% A+ w0 f
7
7 q2 m m4 l2 y" ?8
- q5 j9 C7 [: C" H' t t) M, D9
6 `8 ~7 U1 ?$ Y& v# n2 ?. k10+ I8 Q* D9 A( C! H% w6 k
11
5 T* M( b. A! _4 A12
3 x# o0 T8 j; P8 r6 V* G) G13
5 q% [% y! d% p! E: z8 b& U' V14# B8 n# O4 U- v( ]1 ^" I3 e1 p0 p
15
: h$ k# s7 S$ N6 c' m1 r- ]16
% q& v; s; i! H; K7 o( ?1 F2 K171 v6 I l6 s1 o0 U: @5 x
18
- V7 a! _+ v1 h% ?: i19- C+ S' `0 k3 \; V
20" [9 _# e4 S: W S( P( m+ G
第二类判断操作主要用于测试是否为月/季/年的第一天或者最后一天:
9 P$ T& G7 \! K) o. v: d$ gs.dt.is_year_start # 还可选 is_quarter/month_start4 k7 `& G6 `1 P+ K- L
Out[40]: $ a. E( q; j2 \6 E \; F
0 True5 K! ]7 N5 g& e4 f
1 False
* o& V# q* L# j" F2 False% c3 I4 \1 _& y8 s+ b5 m0 f
dtype: bool
" [, X' l& T6 A+ R. y: U5 H+ ?; H1 @3 }7 \! g2 s
s.dt.is_year_end # 还可选 is_quarter/month_end# K, \9 S# e8 R8 {+ @# u+ r
Out[41]:
" O& f4 y2 h3 G0 False
3 U9 V: o6 p% b; V/ ^: Q8 B1 False
5 N# {' G( b3 p7 E. m# s2 False
% U6 I# ?) u6 `+ D, T1 W5 e/ j# Ddtype: bool1 @2 N& w* e( p+ Z6 z% m
12 q! l* ]9 b6 R; C" Y
2
, |# H: I" I- J9 S T3/ L* b, ?) t5 v, p7 U
4$ k2 G" d+ O% a Q0 d
5
; N! e g( i: O/ o' w T2 w63 U% c. D2 f1 r' ]: s
7
% d( g! r+ B* N: O; f. Q4 S N8
# k$ `- Z6 `- F2 S$ N2 t0 m% F0 e9
' _6 e* v2 k$ v* Y; I; [& Y; i10/ z2 C6 f/ j$ I$ Q3 L: ]. p
11
0 [! D9 F+ D0 s/ E2 X! s12
9 n/ l m: H7 i* r. V13
6 x1 |0 I* {; X第三类的取整操作包含round, ceil, floor,它们的公共参数为freq,常用的包括H, min, S(小时、分钟、秒),所有可选的freq可参考此处。
+ _! Z) V5 ]4 X/ ds = pd.Series(pd.date_range('2020-1-1 20:35:00',% Y: W* e1 F" _# \; V8 U3 g6 u4 k
'2020-1-1 22:35:00',
0 U. R8 U& Q+ t freq='45min'))! Y% Z" k" V; b- `! F% S9 X
8 u3 H+ [ T' k v: R9 ^6 y3 a
* x, D1 x/ Z1 w6 y( bs6 J/ R6 }% U% S
Out[43]: ; |( E0 M( @7 G
0 2020-01-01 20:35:007 q& e7 ?' g' b8 T8 g! \* R
1 2020-01-01 21:20:002 Y) Z2 P+ U3 l0 D
2 2020-01-01 22:05:002 V) Z" T9 _4 m# Z+ }% [* v `( g( B
dtype: datetime64[ns]0 E9 k! D% G ?8 q6 ?8 [( m8 `
. C% p4 `( y- u3 c6 @- Ts.dt.round('1H'), b" n) A' |% e/ }/ ]2 a" ~
Out[44]:
+ o6 p8 A w" F2 l0 2020-01-01 21:00:00
' j0 o+ P9 I+ X+ N6 @3 M: p+ g1 2020-01-01 21:00:00! Z' S) H1 y( b! z! I9 A
2 2020-01-01 22:00:00( W! d( G3 l+ U7 B: t; d
dtype: datetime64[ns]
+ Y. q/ T* I3 n6 `. R2 d% w* M- K6 p' i& ]$ j
s.dt.ceil('1H')
G2 b( f3 J, |- h- e6 Q, r" J' C5 e; qOut[45]: . ]" u* A! A7 Z3 O
0 2020-01-01 21:00:00
E9 E ?, O1 t1 2020-01-01 22:00:00
" `/ @& c& x: o) a( ]2 V% ^; t2 2020-01-01 23:00:00 n5 M6 K9 V( Y" n$ C
dtype: datetime64[ns]
2 h* L4 J' b7 ^2 M3 l6 Y) w1 N3 e7 J+ w' |3 B+ N
s.dt.floor('1H')
3 J) D0 ^+ B Q# _ Z& [Out[46]: + H7 Q6 ?( y+ G
0 2020-01-01 20:00:003 n$ \! \& U H" v+ [$ R I: s
1 2020-01-01 21:00:00
[* n+ }9 j4 |7 ]* O2 2020-01-01 22:00:00! f& f, f4 j; U h) e" l
dtype: datetime64[ns]6 Q! @1 K) }- D4 Q, `# w* R
! l% c4 P# H: S$ S7 b) H% ^
1
% C+ t# |4 S o3 W) q2
+ I' T7 Z" B J- o4 @3
* z- P; T. v5 n4' @* b, t% ~+ L: s3 b
5
8 b1 c0 I5 t& [1 C6
+ z/ U! M$ l' ]5 T5 Y+ Y: g5 X7
& n; U& y# y2 G# Y# ^8
; T8 W4 v+ x0 @* s- J& [' n% [) K, g$ ~9$ I4 L! v& l* p( M1 J1 r
10/ o2 i4 K, x' u7 u2 d# T5 ?
11
5 x7 x/ C. Z( |( V3 y% q127 Y' l0 @% Y7 |% R# D1 t
13' j6 p9 k% X0 K+ d) ?" W
14, B7 K2 h8 S1 n$ H, c
15- [: b6 w& Y& s. b- |- b
161 u1 S) V* k9 E2 ^; `
17& b& u, {( Y2 |# t0 G( ]
18
8 z, Q3 V. h" {! \0 V. r19; G g: x; u6 u
207 y3 E9 \: ^5 w8 Q, s* W( j7 n* w/ s
21# ~% |: f* m& W3 A+ ]
22
& v) m6 P; I" x: |3 I, x( K0 g) X23$ m8 B2 G& ]$ p" R: ?3 Y/ y; Y5 C8 C
24
4 o9 o. f9 Q& ?% a- @7 B! j$ O! \1 M259 o% p' T% l8 `
26
% ]+ g/ }0 c! n# M27
4 j1 J5 D" |" N- g; P& `$ I28
$ f" J: N' Q7 Q4 C4 D6 y296 Z* r4 e, }/ f9 \ j" ]
30* k( b3 m& r8 x, s" e8 w! ]& f" b9 t
31' T7 m% f% |8 q3 Z, `
32- W4 Z7 y2 ^7 F6 I- a/ Y( V% w
10.2.4 时间戳的切片与索引
* y/ _% @3 Z% k) f3 Q5 J 一般而言,时间戳序列作为索引使用。如果想要选出某个子时间戳序列,有两种方法:
D3 L- i& i, Q" ?! Z, }0 p; a$ \- ~' E/ V* L& Z( i) }7 I
利用dt对象和布尔条件联合使用* }( d9 x% x2 s- D
利用切片,后者常用于连续时间戳。2 n# L( V1 g. }5 p0 G+ F$ I% \/ z. j
s = pd.Series(np.random.randint(2,size=366), index=pd.date_range('2020-01-01','2020-12-31'))
+ B6 g6 j, \/ Q x6 H7 Lidx = pd.Series(s.index).dt+ {, p' E4 L9 I% J* [
s.head(), o: ]1 T, w+ M! Z# g
5 Q U3 S% y% k* O7 a
2020-01-01 01 A7 B7 j; R J A# D# D* {' i
2020-01-02 16 z& {' o$ W/ `* I
2020-01-03 1$ R0 e3 R0 f1 @1 e8 P5 F, X
2020-01-04 03 b1 G$ I& X! [" w1 L1 B: J! X
2020-01-05 0
9 z" k3 \5 k" F- l- v2 { GFreq: D, dtype: int32
' o* n/ e* p l8 b4 N# _7 r# ^( N0 s8 V1# B1 m& u, k" l- _' e' S
2
. p$ t- ^! w! m0 B$ B3 j3+ ]2 n6 U4 w }6 U6 F) R6 A, A. V: s
4
' W% C# l) O/ c# z% U$ f5
: t" Q, Q, u' o& I8 |& \6
% N- j# }' s0 O+ m! p4 `) u( B$ q7
1 w7 C& L# I" N4 ]: ~% h2 @. \8
0 X" C2 I' b2 s0 p9 N0 r# x9& R3 c" g. M, c$ r) A* x
10
- G# L) N9 a6 o* `# @Example1:每月的第一天或者最后一天& n4 _% f! Y! L+ M f2 T/ }
5 K1 z7 W, q' p/ L
s[(idx.is_month_start|idx.is_month_end).values].head() # 必须要写.values {- i v: T5 Q E1 ^
Out[50]: # ^7 k2 T6 t& f& \9 _
2020-01-01 1
k0 R& ?+ k0 p' T2020-01-31 0
; @7 ^& Q6 {6 ~! {/ t$ h: r2020-02-01 1
- V/ s) R, A- z2 Q4 o2020-02-29 1
7 b* a5 O Z! b; L m: c$ F, \2020-03-01 0
8 a9 M! {3 j5 f5 ^4 Idtype: int32
# t/ I0 O( b3 B2 R16 o9 x9 Z3 r* Z
2
n/ _! t9 M n1 l. V. L; l* o3/ c+ }6 y$ |6 H8 e$ ?
4
0 z) X9 c8 R$ |& C6 _5
/ r2 H. D0 X/ V+ ~8 K6
q; o/ C5 B1 |, k7
! m# ]0 E' k/ ^: T) M85 `! _/ i T$ _6 l
Example2:双休日
/ Y& I; h, S5 ~6 O
" O9 b0 g3 E- q0 Fs[idx.dayofweek.isin([5,6]).values].head()
+ X& y. g1 ?: ?4 B# S. cOut[51]:
' I! Z s& t) R; }$ h2 j- F" ~2020-01-04 1
7 M5 Z5 H" v$ r" _- u8 `- c2020-01-05 0% X6 C+ m( P, o" Q i _: `% B
2020-01-11 0
( n H$ [) Q5 s) E2020-01-12 1
+ L4 }. g+ r3 d3 Q2020-01-18 1
- R/ q, _: I: l3 @+ R x) Ldtype: int32; B+ _7 D3 U! H# M' w- m* Z
1& L* p! C8 ~8 E4 [ V5 W" o5 g5 W
2
; ]3 d4 p: v$ Q$ k7 \; T4 u5 _3; B* B6 ?6 D# R
4# K0 f+ q* T6 z
5
3 ?0 l' N9 z9 J ?. ~6
: O" O3 u# d! ?2 |9 K7) F) A8 B& N9 h( d
8" C4 {# {0 e: g& B: {5 _6 }
Example3:取出单日值% Q5 I2 j) M) V1 V9 G
* ]+ H& g2 K3 E, R' L/ a. rs['2020-01-01']1 }* Y5 |3 O# M W. d. a6 `$ d
Out[52]: 1
, A2 @5 F+ @' F
9 o) O) a. p T2 U) N+ As['20200101'] # 自动转换标准格式7 |$ K( p; \' X9 v; c* F
Out[53]: 1
9 p% S0 i5 H5 m8 H- r0 Z6 B15 F+ u* t r: ^+ m8 A
2
+ O3 o) z* v5 j8 Q2 C. u& Y5 i$ d3
& q1 D0 K9 h$ }: ^4
1 K9 t5 f; Z2 {( [4 o5
% W% D! @+ X. RExample4:取出七月
1 y j. [% \% G/ C0 P; A! S- `0 d# f0 C5 q" o
s['2020-07'].head()
2 S4 `0 a: Q5 n; T5 W7 M. QOut[54]: 9 i% B: t- ?8 V5 `6 _& j
2020-07-01 0
8 j+ k: y* u" B4 [2020-07-02 1! h. Q9 H p M ^, x
2020-07-03 0% O' ~. h$ G! R# Q) H: [& q
2020-07-04 0
c( }7 M5 [7 Y% d0 B; a# ^2020-07-05 0
1 k/ i7 P5 N0 u# p- O1 w0 TFreq: D, dtype: int32
9 W: f, t* J+ n+ q" @8 a1
; m2 G3 b, r2 w7 a% h3 Q0 f2: l5 ~9 ~' x9 \7 R! N& H
3
8 T a# q6 N/ }4
) x& A9 H6 b. S: b- d5* M3 f3 J0 _- r6 q6 i
67 V2 U4 |9 E+ b; O% r* `
7% d2 C+ I3 G8 W4 B
8
* R% D' e* T0 `0 NExample5:取出5月初至7月15日) g7 @/ v7 ?0 R2 [, P
2 u' o; l* g" {# Is['2020-05':'2020-7-15'].head()
. l9 C+ d) k5 @7 H! _Out[55]:
, ~" R2 t! h, X# o- B6 K" P2020-05-01 0
, ?( U4 A4 [# B! c( `2020-05-02 15 i6 P! m) a4 R8 [
2020-05-03 07 h; W6 P: q/ Y6 @
2020-05-04 15 P$ x8 w- e% W
2020-05-05 1- ^0 }" L! @9 n9 |- D
Freq: D, dtype: int32
* r7 Q* t5 c) M- I5 X. t- N* [
& n- O* M1 a- D3 h) [s['2020-05':'2020-7-15'].tail()
* q( m1 Q) k' T3 H5 @Out[56]: ) a( t1 B% ~0 m2 k3 G5 h6 H
2020-07-11 02 |4 ^$ P, \0 b l+ m! c* j
2020-07-12 0! }: g y% Y) i+ W% x" M/ p' C
2020-07-13 1# M7 K- W* z- E; g' g
2020-07-14 02 M- s% Z/ h" h& w0 L' @
2020-07-15 1* Y2 [- Q+ e( Y8 y$ E. `2 _
Freq: D, dtype: int32
9 O) D2 e, }" |3 n( u8 {1 h7 b/ ~5 U0 ?. o% I) n e( p+ F! q5 M
1' u" [* O0 n; t' `/ H7 y/ }4 ?% X
2
5 R' f4 v0 U+ q* n- N6 U' O* f/ ~& p0 ^3+ z) a* N) H! o) c/ Q: E4 A2 p
4, B7 L" c2 d4 s% N" V( A, X0 u7 }
5
( K0 u: N. O* M/ i# a- L0 }6
3 k% N; q# x3 r& b, E! ^7
+ l9 K0 b( s, q- K* p; W! A& S8
1 D5 N$ @! k* U9& U" I, u, \# U7 Y: O* ]$ U
103 p5 E$ v6 d3 t m
11
3 Z0 f$ A8 @9 \/ G$ K. i" e12( w7 \5 {; ]; w, M) r1 J
130 ]. [% ]) O: t: {' {; u8 r
14. E/ [3 E5 }5 N0 W, V: Y" R
15
1 c+ s7 P$ O. T166 N( e: I# r1 |8 g
17
, y" U* _7 K. J6 g10.3 时间差( w) D9 l7 t* D6 k2 V
10.3.1 Timedelta的生成
6 a& R0 a0 z$ D$ A, {pandas.Timedelta(value=<object object>, unit=None, **kwargs)
; D1 i8 }" m9 @1 I* ^; B$ w$ \ unit:字符串格式,默认 ‘ns’。如果输入是整数,则表示输入的单位。5 ^+ ]: D5 T' r2 V h
可能的值有:
( @% ~9 F( V% B( I: r7 d( a6 x6 c5 ?3 A0 Q
‘W’, ‘D’, ‘T’, ‘S’, ‘L’, ‘U’, or ‘N’- R2 K$ U5 G5 p3 D! }: l
‘days’ or ‘day’" c; u4 l! C& \8 a% ^
‘hours’, ‘hour’, ‘hr’, or ‘h’2 T% T. h( I; Q# b- V" z
‘minutes’, ‘minute’, ‘min’, or ‘m’
) u, Q# I: t* }" a4 ~‘seconds’, ‘second’, or ‘sec’
, J" p$ b* ]9 o) C+ l! N毫秒‘milliseconds’, ‘millisecond’, ‘millis’, or ‘milli’( G1 Q3 [1 S N' e0 M9 O
微秒‘microseconds’, ‘microsecond’, ‘micros’, or ‘micro’
- k# |1 p# N) P+ d% \纳秒 ‘nanoseconds’, ‘nanosecond’, ‘nanos’, ‘nano’, or ‘ns’.3 P, t5 f& v/ w. a$ B# z+ y* x, N
时间差可以理解为两个时间戳的差,可以通过pd.Timedelta来构造:: g% j1 B5 P# p D; g0 J0 b
pd.Timestamp('20200102 08:00:00')-pd.Timestamp('20200101 07:35:00'). v9 @2 L; B' ]. E0 h! Q- K1 v" B3 H. J
Out[57]: Timedelta('1 days 00:25:00')7 _3 R& }7 ^6 a; J( P! J T
: W+ B# F$ ~ F( ?
pd.Timedelta(days=1, minutes=25) # 需要注意加s
6 K5 e( `3 G6 v. {- iOut[58]: Timedelta('1 days 00:25:00') t) ~/ w7 b: N
- L- A) t1 f0 K
pd.Timedelta('1 days 25 minutes') # 字符串生成
. H+ e2 O4 |" ?Out[59]: Timedelta('1 days 00:25:00')+ u2 @6 a, ?; ]" W
+ N3 w6 ^5 l* U9 N: ]' o
pd.Timedelta(1, "d")% Y) s) Q' n+ Q" ~. ^% g4 O! z" i$ y
Out[58]: Timedelta('1 days 00:00:00')
' l( ^! ?$ f/ J& r- d0 _1
$ R4 e% U& T% E# Z. y( K' S Z2
$ Y4 `4 G2 ~, \( v W0 d3
$ M% h# M0 `3 M: m) v1 x/ S4
9 Z. D. x/ q, r' f4 h5
6 b5 @8 Q. A& V! Z6 z6
8 @0 z* `- y% C, R0 d7
H) [; J, Y& K0 l3 b6 k! p0 \8
9 w( X1 {/ H! f9 i8 G9
% E' ?' U4 Q9 E- y/ m8 Q2 L+ g103 o; n6 W, K; V- C, Q
11
8 `; }+ ?3 g& q4 {$ k5 t生成时间差序列的主要方式是 pd.to_timedelta ,其类型为 timedelta64[ns] :# D( R2 @- ]7 T ]2 f
s = pd.to_timedelta(df.Time_Record)
' ]4 U9 w1 D' X7 a' a
) d8 y) { V0 X. r+ q& Ys.head()- M0 i, d, l8 a6 k
Out[61]:
" E* P* W/ ?1 T& Z5 Q1 D+ A, W0 0 days 00:04:34! C/ m7 @% E% |8 c9 Z7 f
1 0 days 00:04:20
. V: T% x% ^1 p P" I- i7 v2 0 days 00:05:22
5 u5 J5 |7 b+ o5 o y! e3 0 days 00:04:08! L* H0 b% Q& o2 |3 L1 h8 m+ y
4 0 days 00:05:229 q2 I L* [8 Z9 k' ]
Name: Time_Record, dtype: timedelta64[ns]
" t! S7 J$ j: V- u2 w1, Y3 b! x0 j8 t4 _
2
) N6 K( B2 a& `) A3" C" a3 S7 V9 b, C
4
0 z2 l- r$ ]& P C) X9 v5. s' L" |6 Q5 {! G% M2 s0 W
6 P- ~2 y' G {- m, R
7
# h' O% k# v$ G! R6 f \9 o8
1 ]/ H" U6 D2 |1 V/ @( Y9+ I2 a5 z0 H: f$ m( m- D1 f7 \4 y; c
10
8 K# }' W2 g8 t* i( S7 f# ]与date_range一样,时间差序列也可以用timedelta_range来生成,它们两者具有一致的参数:
/ H7 ?: E# ]% U( j6 C. b" bpd.timedelta_range('0s', '1000s', freq='6min')8 C; u2 O$ |0 w6 ?$ K5 A3 s# @( S
Out[62]: TimedeltaIndex(['0 days 00:00:00', '0 days 00:06:00', '0 days 00:12:00'], dtype='timedelta64[ns]', freq='6T')
! ~7 P M. O) `* j; L% C3 O% j" E6 ] R% D* l7 f- b
pd.timedelta_range('0s', '1000s', periods=3)) j* a6 s9 w% R, F( m( i q, g3 W
Out[63]: TimedeltaIndex(['0 days 00:00:00', '0 days 00:08:20', '0 days 00:16:40'], dtype='timedelta64[ns]', freq=None)& p3 ?) g1 M3 }3 ], U
1
, B4 n2 s/ q3 W$ ~+ r, l2
) J- T5 K$ O- r' @' [+ M3
& @3 }5 E; Y: E: p44 R0 T% R1 _4 X% f
5
/ B; p. P8 ~& L. Q' a/ L1 N对于Timedelta序列,同样也定义了dt对象,上面主要定义了的属性包括days, seconds, mircroseconds(毫秒), nanoseconds(纳秒),它们分别返回了对应的时间差特征。需要注意的是,这里的seconds不是指单纯的秒,而是对天数取余后剩余的秒数:
3 h+ L _1 v$ v2 K0 |# b. ?9 {s.dt.seconds.head()
. X% ]6 O7 q" _0 r8 LOut[64]: 8 p8 w( I) l1 g! L5 z
0 274$ m/ Q. O+ g* \* d" m9 x
1 260
# O5 I+ V( J$ X$ A% q3 k5 n2 322$ Z T+ e8 L/ ^8 p) B# Y9 z
3 248
$ m2 I) ^" G; k3 l! k9 k& c4 322
0 o' W; y6 d- p/ S1 ?Name: Time_Record, dtype: int644 r+ r/ f1 H! M- r/ {2 L
14 W5 N/ c* d3 n7 b$ u2 o" [
2, ^) f, p* e$ T
3" B/ {/ }2 z3 P0 l. T9 s
4
, ~0 ~% I" s. b5
' n/ X l& |" J1 n8 H. I! e6% [4 f2 ~: t5 R
7& D9 w4 L6 W; W+ @- H9 N
8
( g" S/ e. y% w( t" u. k, v: \, k如果不想对天数取余而直接对应秒数,可以使用total_seconds
( s8 v+ N. b& P9 |& s: }+ ?+ |; f: l( z! _
s.dt.total_seconds().head(). S% n. \4 O l4 E; r7 ~& S
Out[65]:
" F3 ~* B4 G& k% I0 274.09 |' k- G+ O7 ^; E" c3 N8 ~7 Q( X
1 260.0
2 F; Q% M* @; Y' e8 x5 n2 322.0 M/ k6 ]) l. b
3 248.0
' q; I, N+ ~+ K: t4 322.0
6 F" i1 A6 J! ?! T1 T) X4 v, D" }Name: Time_Record, dtype: float64& \) I4 X$ T$ R) ]4 _* N1 ~
1
- Y, J) ]7 h0 F2 j2 _# ~" q$ t2
u4 j6 c2 \8 r0 L) U; r) X- ]3
9 _$ p" S% e+ S6 A* @$ a# u4
[& U& x! s e" j2 L# L2 n8 b- x5, l d$ o2 I% t$ h0 p6 Y- T
6
! A8 ~! c9 u4 p! b8 {0 Z75 p7 F9 E/ H& q5 ]
81 R7 C% A6 n" l, Y1 E! }
与时间戳序列类似,取整函数也是可以在dt对象上使用的:
( z9 T9 y j, |% ~( c1 O* ^
% \& P9 U$ u& ?' n/ P2 Ppd.to_timedelta(df.Time_Record).dt.round('min').head(). m) l+ y6 c; X L$ t w0 V
Out[66]:
0 b3 Y# P: X7 \6 y# b) u# u2 S0 0 days 00:05:00; V7 A- K! C( m/ c# d3 g% x
1 0 days 00:04:00
& a" n2 U3 L6 u, r+ F& x+ k' y2 0 days 00:05:00
* a1 m+ h+ g3 q3 0 days 00:04:00$ r3 D ]0 d( t; |5 ?* y" y
4 0 days 00:05:00
: H" S& V1 N. K' x9 k6 SName: Time_Record, dtype: timedelta64[ns]0 Q6 b+ i2 c( k4 c9 b8 |( v
1: n( {7 i' b2 m# t7 J
2$ N/ E2 O( z/ ^4 P+ o4 \
35 L' E/ P# P" U4 {$ ]
4
$ X8 c4 s1 c3 D1 T8 k52 b2 \9 `$ J1 M
6
3 `6 G1 C* \4 d' i$ U# Z7
1 d: |/ i ^1 f. p$ R( H$ i8
1 @! ~( r8 O! c2 U+ v5 l3 u10.2.2 Timedelta的运算5 _' P" O2 [8 K* l& E
单个时间差的常用运算,有三类:与标量的乘法运算、与时间戳的加减法运算、与时间差的加减法与除法运算:
a1 Z7 Y8 {2 b ]+ ^: O3 P' Ztd1 = pd.Timedelta(days=1)
8 a" G- L, y4 ^8 {& M# x, Dtd2 = pd.Timedelta(days=3)
1 m ~! J* M: p& Z( A w7 F6 P$ Kts = pd.Timestamp('20200101')
4 m }0 h# N1 ?& _1 k* L! Z* ?: P9 Q5 l- t2 ]3 |1 H. |4 I4 e
td1 * 2
, \+ J5 c" l, o2 s/ w+ e) H( TOut[70]: Timedelta('2 days 00:00:00')8 f; M$ }, z0 j& g
' m, q$ l) Z1 Q* ]
td2 - td1
% b4 M( E7 H8 @( d1 x0 OOut[71]: Timedelta('2 days 00:00:00')' t: O/ }1 _2 ^$ j
# [, `4 H3 J* K; G4 }. d% J, W% i
ts + td1
" K( t) U8 @: w# Q$ ~' g; fOut[72]: Timestamp('2020-01-02 00:00:00')
1 Z9 V$ C- Y+ s9 N
6 L7 z: w" \/ k, E! n$ Ets - td1" c$ D* M( C, u* @3 s; Q
Out[73]: Timestamp('2019-12-31 00:00:00')
% Z P" @! \" H- O |1 H1# g1 _1 t4 U8 X
26 i- w: x7 T/ ^9 h$ L, { R
3
$ [% N! O! T3 c% ~' J; @ |# W4. P: u/ z% P# Y2 j
5
* T$ }0 X' f! m' F. M. a6
. d/ R2 [* q# y1 l! V7+ t+ ]& B# k, P
8' Q! \% a) c/ C _7 O
9
! a/ K- a. w& O/ \10
! G5 K+ ~4 Z* H: Q11( E3 b$ y# Q# C$ Q
12
# z5 X! g( ]; m$ S( y7 o13
) Q; o- m& g/ H: a14# i; u0 ?) ?% i% d; _+ [
15
* x% Z; D3 d) x% y% k时间差的序列的运算,和上面方法相同:
- |; {( _$ N% ~- T) ttd1 = pd.timedelta_range(start='1 days', periods=5)
0 g7 [9 r! b; f! Ytd2 = pd.timedelta_range(start='12 hours',
& x' j. C* L2 o freq='2H',
3 q5 D G9 b5 _# C5 q& J3 i7 Q periods=5)" S- i4 Z" \ F* g
ts = pd.date_range('20200101', '20200105')8 v, k/ ^5 p6 S/ O! ]
td1,td2,ts9 O9 p6 C7 C8 v3 ^: b: d. [
6 B( e4 U: Z7 N
TimedeltaIndex(['1 days', '2 days', '3 days', '4 days', '5 days'], dtype='timedelta64[ns]', freq='D'); c x. k9 q* N; Z4 y( ~
TimedeltaIndex(['0 days 12:00:00', '0 days 14:00:00', '0 days 16:00:00',
# b+ k# D6 ?' F4 w7 C5 S2 ~1 x '0 days 18:00:00', '0 days 20:00:00'], dtype='timedelta64[ns]', freq='2H')) |8 T4 D. e: m
DatetimeIndex(['2020-01-01', '2020-01-02', '2020-01-03', '2020-01-04',9 v: W; ~7 X- u; F8 t
'2020-01-05'],
/ v! ^0 l# r: n+ `0 s! ~6 E( C6 q dtype='datetime64[ns]', freq='D')
, f1 |& g. S$ V. t3 Y" t! l15 E0 \6 [' s7 s; L* n
2
: T7 |; h6 @4 H8 t% t) B0 q3% n& T2 q. M& u- [
4
f3 _& t# o7 g! _. j. H5) m! Y! y A' ~; s' R" k8 X
64 S9 ]$ p% N, ~- g+ V1 f# v5 r
7: |4 S* T+ }% n/ b- S
8
9 y8 `* a1 r# J+ _$ ]" L! v3 d9: g8 y I+ q. ~# B+ Z: Q. M
108 f( m/ I6 k" i: i- B% X2 Q
11
1 ~/ S& ~; {9 E; [* o# r12
' A2 q* |$ d9 \9 j130 u6 F. z6 a$ |9 @
td1 * 5
7 }* G4 X6 j( m/ g& h* aOut[77]: TimedeltaIndex(['5 days', '10 days', '15 days', '20 days', '25 days'], dtype='timedelta64[ns]', freq='5D')
! ~ b1 Z. r l; m! x7 S7 |6 S, \" Q4 w
td1 * pd.Series(list(range(5))) # 逐个相乘
]* c% D# O" Q* ~( _Out[78]:
5 V/ C: R* ^) I$ m O1 d! T0 0 days! T% P% Y: {; `$ n+ p6 A
1 2 days* C- Y @, k4 ]4 p& @6 t$ h
2 6 days
! |" g! A/ v% J! b+ d% j3 12 days
$ a8 c3 c+ D" |" ^3 O8 J4 20 days
" M) M( C: R' e" Z) Wdtype: timedelta64[ns]( G& _ }7 j3 X; |3 j% I9 _
( F' M$ N5 b( ttd1 - td2
8 A$ e" l- Z- }: E8 j: h; AOut[79]: % \1 J& b. l! t, s
TimedeltaIndex(['0 days 12:00:00', '1 days 10:00:00', '2 days 08:00:00',+ q6 X% i0 G& Z$ m$ q2 E2 [5 c
'3 days 06:00:00', '4 days 04:00:00'],3 p3 Y" s' o" H0 N+ l( s
dtype='timedelta64[ns]', freq=None)/ N4 r! J$ Y [' ~
& c. |% }, _5 ?td1 + pd.Timestamp('20200101'); \. B( @9 I+ ], D7 d
Out[80]:
, ~+ f9 a6 H S7 X$ kDatetimeIndex(['2020-01-02', '2020-01-03', '2020-01-04', '2020-01-05',
/ ?0 j) L! i. E( v6 j( ? '2020-01-06'],dtype='datetime64[ns]', freq='D')5 ^) i- w8 j. z( {2 O. o$ Q2 R4 y- ~
; _+ I9 m3 U" l- B: T4 U" c: b) ~td1 + ts # 逐个相加& s0 n- S$ i3 T w
Out[81]:
' L" D+ U) t; ZDatetimeIndex(['2020-01-02', '2020-01-04', '2020-01-06', '2020-01-08',/ F N4 w% y' G5 D- G
'2020-01-10'],3 e! I$ t- y+ x6 T5 A
dtype='datetime64[ns]', freq=None)
# N5 C8 u( M2 K) G! C1 L
' R- @2 u: m" Y* _: `4 p! ^1! Z* I; u. a, [) Z
2' ?' n* s) p" m2 V- D
3% x3 Q# J W! l
4
i# P/ l- ]; r5
" n3 c7 U3 B2 r. l, l6
+ `+ ?& V' I) G- y- X6 a% n7* m( t0 r7 o# O. I
8
5 @, S- A- l- | P6 x' q; [9( a# d# u- W+ c: f7 Y$ q
10
8 w* C4 v) H; \ {9 p* z$ O11
# I* i2 e9 g6 q; h+ X' @* f12
8 Z- [. Y$ ]; C5 i, [+ M13
l3 u @! F& z8 D3 p14# [! F7 o! S/ {; z5 V; y
15
& J; K2 ? p! S! n( [" i16
% r+ ~% u" l; R9 v0 L17
. k+ ~# S+ X+ F, o/ {% F8 N18
1 \/ `. B, d9 [$ c19
7 |: j& M1 \( m. _! R* S20. U: H- J/ I, a
21
( L' K' \% I$ w. N$ o' n6 i22
/ t2 a+ Q6 N! e# V1 l23
. s7 }& _, Y. j! F! L- u/ L24
0 e2 o6 T4 b- H# H3 d0 D3 Z25+ H+ f" _% j: z* o+ p
263 n# H @ H. k" Z8 X# B
27
8 _' f# j4 Y' W4 m" j& K. t28
* d" {% H j- f3 i. L# A10.4 日期偏置2 J- w% [2 A: W: i7 C* B0 z3 U
10.4.1 Offset对象* f% F" t& i( M$ _
日期偏置是一种和日历相关的特殊时间差,例如回到第一节中的两个问题:如何求2020年9月第一个周一的日期,以及如何求2020年9月7日后的第30个工作日是哪一天。3 ^3 `3 ^, K0 J0 [
. O& Q( i. q& D; P
DateOffset 类有10个属性,假设s=pd.offsets.WeekOfMonth(week=0,weekday=0),则:. Z0 ^- ?( }; n" z
) _# b9 g* r/ ~, Xs.base:<WeekOfMonth: week=0, weekday=0>,返回 n=1 且所有其他属性一样的副本
1 G5 b" W2 x. } n) P, fs.kwds:{‘week’: 0, ‘weekday’: 0}6 z, n5 } u1 b
s.wek/s.weekday:顾名思义
6 z7 L X$ {$ {' o有14个方法,包括:- w9 U( |+ E$ \1 m% P
/ k3 e' @; V/ h! C6 C+ C% [9 x
DateOffset.is_month_start、DateOffset.is_month_end、DateOffset.is_quarter_start、DateOffset.is_quarter_end、DateOffset.is_year_start、DateOffset.is_year_end等等。
8 a3 y2 S) j0 v! K$ [pandas.tseries.offsets.WeekOfMonth(week,weekday):描述每月的日期,例如“每月第二周的星期二”。
4 A3 W: U- [ r5 e2 h# a2 R# `+ ?3 K, O- c8 [! N. S: @; f
有两个参数:
1 @' J. b! d. Q% cweek:整型,表示一个月的第几周。例如 0 是一个月的第 1 周,1 是第 2 周,以此类推。2 R: }& B$ l0 I) d1 Y Y
weekday:整型,取值为[0,1,…6],表示周一到周日,默认取值为0(星期一)! k4 |; {5 X0 T7 o: J
pandas.tseries.offsets.BusinessDay(n):相当于pd.offsets.BDay(n),DateOffset 子类,表示可能的 n 个工作日。
. a4 ~, t2 V$ S- B8 I" ~
# [& W7 v S' a& ^& k3 J; wpd.Timestamp('20200831') + pd.offsets.WeekOfMonth(week=0,weekday=0)9 f% p3 G$ Y' M8 `( v
Out[82]: Timestamp('2020-09-07 00:00:00'). X6 R. j' s* b8 O4 S5 W
: [- D% |! q7 H. |
pd.Timestamp('20200907') + pd.offsets.BDay(30)) ?' {" L: E1 B2 H, \, _' d3 R
Out[83]: Timestamp('2020-10-19 00:00:00')
& o# q! A' a8 g7 ^+ C4 ~, j+ I1
$ G( P+ q0 e5 u' Z) u" J# l4 W, S3 ]2
. M1 W5 t& T. v# g2 P \3* P' c' D$ l" x* W3 m5 `' E7 u$ X
4
+ S" Q2 X- d, P0 D% V: ]% K% }7 s& a5/ {. T1 `* a4 Y3 L
从上面的例子中可以看到,Offset对象在pd.offsets中被定义。当使用+时获取离其最近的下一个日期,当使用-时获取离其最近的上一个日期:8 M8 r- }6 _; B0 U8 d
/ J- I/ j6 d( ]: v
pd.Timestamp('20200831') - pd.offsets.WeekOfMonth(week=0,weekday=0)! I( r I M3 @1 d$ v
Out[84]: Timestamp('2020-08-03 00:00:00')
: n) d' D' c& ~) Y+ H
; N/ U$ @# t `) p5 o: o9 E3 t8 Npd.Timestamp('20200907') - pd.offsets.BDay(30)
# H9 a" X& H1 Y6 @' UOut[85]: Timestamp('2020-07-27 00:00:00')
' X- W% n+ C4 @9 t) g
9 q6 a# Z( E2 ~pd.Timestamp('20200907') + pd.offsets.MonthEnd()
% U! t; J. k" W) d9 j' `5 e* j7 p- BOut[86]: Timestamp('2020-09-30 00:00:00')/ _% j7 f2 k# U0 M1 d
1
/ f( Y' o6 t' t# u: P2% o [( j1 c! O3 S4 m# j
3
+ z4 l7 \7 H1 F4- {4 _: L3 `: q1 @/ |9 H9 X% Z
5
2 d4 U; n. b! J: p* u6/ N5 o/ Q( h! {2 f& [
7/ L! ^5 [* q/ u
8. q& ^. [& C( F% j, Q4 |1 [
常用的日期偏置如下可以查阅这里的DateOffset 文档描述。在文档罗列的Offset中,需要介绍一个特殊的Offset对象CDay。CDay 或 CustomBusinessDay 类提供了一个参数化的 BusinessDay 类,可用于创建自定义的工作日日历,该日历说明当地假期和当地周末惯例。5 v1 }2 _7 Z. h! |
其中的holidays, weekmask参数能够分别对自定义的日期和星期进行过滤,前者传入了需要过滤的日期列表,后者传入的是三个字母的星期缩写构成的星期字符串,其作用是只保留字符串中出现的星期:
; m9 F; b: H3 k$ ~1 f, b2 u [+ j8 m2 L; S; u' U$ B
my_filter = pd.offsets.CDay(n=1,weekmask='Wed Fri',holidays=['20200109'])5 o0 r( _4 T/ w" v h6 n+ Q: N5 {
dr = pd.date_range('20200108', '20200111')
8 J7 H' L( O& }" K q' Q
5 o3 n+ k- ^" M, q, Jdr.to_series().dt.dayofweek3 ?1 F2 W6 E" w" Z" V* t1 J
Out[89]:
; \" }! r' F( o. g& ~" z+ Y8 k) z: H2020-01-08 2
0 i' A$ L4 F; |% t" ]3 T( R$ V2020-01-09 3
# L& O7 g+ T, t3 m. U$ a2020-01-10 4
4 W) z& D, ?7 P% T/ {: X! S2020-01-11 5: ?: `. S7 L' b: j) ]2 y
Freq: D, dtype: int64
" G6 e0 _* |8 c/ u: f. W* z6 P9 M$ Y# Z7 U: f* e7 {7 a
[i + my_filter for i in dr]
4 ~6 m. i6 Z4 N5 g6 Z7 h' u; `Out[90]: ! }6 O- ^4 u; |# G; u
[Timestamp('2020-01-10 00:00:00')," T: m; R6 c" I+ w$ G1 d; d
Timestamp('2020-01-10 00:00:00'),8 u |! f0 d( |
Timestamp('2020-01-15 00:00:00'),
0 q; G8 h. O, }! a- [5 P- y Timestamp('2020-01-15 00:00:00')]( O$ j- `: C. _2 `* A4 O
+ c# _* G A) _5 a1 l7 ]1
& F9 j2 \5 k- N* W6 u9 \( x% [7 b2# b/ [* Z% z. s9 w+ X9 r
3& V5 x' P. t" f8 e; G1 U5 \
4
5 t) U/ r5 k0 \* m4 h1 X+ c5
4 U. s+ C K: ~! z6* x2 A3 x2 A" N K1 U8 U' M
7# _. G& U9 T5 O. B1 [
81 ?+ T' U9 D1 `$ y/ `& L) N2 c
9
. i( _1 R3 }/ k3 S3 Q- W10
- E' Y N+ _% M. V11
3 I4 u( E; i/ X% C12# v) ^( X4 h4 S, }
13" Q7 {5 u3 z7 ^9 d# g
142 Y B" T% J1 X2 O
15* T9 _5 K6 @' l8 j2 `
161 z' t: x/ D0 ?0 L
17
; Y" D% ?: B/ J 上面的例子中,n表示增加一天CDay,dr中的第一天为20200108,但由于下一天20200109被排除了,并且20200110是合法的周五,因此转为20200110,其他后面的日期处理类似。( a+ u# A" C, }6 i
4 i5 L: A& i% J4 T7 K
【CAUTION】不要使用部分Offset+ n: G; _( k4 }2 L2 E/ ~; C
在当前版本下由于一些 bug ,不要使用 Day 级别以下的 Offset 对象,比如 Hour, Second 等,请使用对应的 Timedelta 对象来代替。
7 c; ~5 t9 V; G% g) d: T# X1 G( ^% y" ]! x Z( b: w! h3 ]
10.4.2 偏置字符串/ ~* t' }; x/ G) G2 b6 V
前面提到了关于date_range的freq取值可用Offset对象,同时在pandas中几乎每一个Offset对象绑定了日期偏置字符串(frequencies strings/offset aliases),可以指定Offset对应的字符串来替代使用。下面举一些常见的例子。) M( B, u3 J' x1 z; _" `) s
& f5 l5 F9 f9 ]( ^
Offset aliases:pd.date_range函数中的freq参数,为常见时间序列频率提供了许多字符串别名。 也称为偏移别名Offset aliases。偏移别名列表点此参看(大概27个)。# m a3 p9 b" U) z
5 T7 m4 `7 O6 r C1 x" }" q2 T' [- Z
pd.date_range('20200101','20200331', freq='MS') # 月初
& F! R3 w, M+ r! AOut[91]: DatetimeIndex(['2020-01-01', '2020-02-01', '2020-03-01'], dtype='datetime64[ns]', freq='MS')
0 T. W1 \# j4 o, _" ^3 G' m. x: W- D/ ?% H m0 s; r
pd.date_range('20200101','20200331', freq='M') # 月末3 f+ r" U/ N' b6 ~5 y/ [# P4 R8 K
Out[92]: DatetimeIndex(['2020-01-31', '2020-02-29', '2020-03-31'], dtype='datetime64[ns]', freq='M')' f0 k; {* [# ^. ^7 V0 V
' p/ |3 _1 U, z9 n
pd.date_range('20200101','20200110', freq='B') # 工作日
q. j' \$ u6 U1 R- Y2 XOut[93]:
# @* s$ {( o+ q* x+ [DatetimeIndex(['2020-01-01', '2020-01-02', '2020-01-03', '2020-01-06',& b1 A2 T8 X5 w. f
'2020-01-07', '2020-01-08', '2020-01-09', '2020-01-10'],
" I) z7 ]. t. [3 _, H dtype='datetime64[ns]', freq='B')
- t) v0 P) u5 ~( v% }; W, i2 c. A# q
3 F8 W! E* A: m Z5 a9 p! o: kpd.date_range('20200101','20200201', freq='W-MON') # 周一( \& l/ r R4 z' E( ?% Y, A" G
Out[94]: DatetimeIndex(['2020-01-06', '2020-01-13', '2020-01-20', '2020-01-27'], dtype='datetime64[ns]', freq='W-MON')
, b6 @: V1 ]' A& A
0 T* G9 e3 K' T9 `pd.date_range('20200101','20200201',
( o! d# K% O0 I, k ^: g( u freq='WOM-1MON') # 每月第一个周一
' U& ]) s% r0 P3 ~2 k: J7 I/ z9 s
3 ]/ V4 e8 q2 E, y5 W+ S/ N0 g5 GOut[95]: DatetimeIndex(['2020-01-06'], dtype='datetime64[ns]', freq='WOM-1MON')
4 g4 J9 N; t% E G
6 I9 E' E% {. A# }1 n; S1# d- ^& ?$ y8 g u4 A6 b
2% e( d# M5 O$ f8 d
33 q9 p- B- K ^2 j& `
42 W$ g9 o" s+ l3 g/ P
5
* a: [6 j# `) P0 u, c6
$ i" J+ f/ |1 t/ `2 ~8 B7
5 T4 j8 Y( G/ o: E& w* }81 w$ `' A. N( M8 @! R- t
9% P, o( [0 X1 { O( p
107 r6 Z7 W' a! ^5 s
11
. G0 N# l; H/ ?2 ~% A0 [" c8 G0 R+ @12
8 G! ]1 X$ ?5 z! q" {+ h13# z& y# L* ]7 ~" J
14
" Z5 x! e8 M3 v$ E6 J8 k15
% ]; O, ?( e" D' J16) s, C* I- U9 v* ^4 U m
17
" a- r/ K8 P Y, Y18
; a1 x, `* m2 l+ Z; g: U! o19
; c7 o1 }. w7 A* {, G7 J* B B- l6 p上面的这些字符串,等价于使用如下的 Offset 对象:+ }9 J+ @7 O% w' X1 O0 r
0 ]& r9 k; X$ v7 U0 n& g$ a1 i. [
pd.date_range('20200101','20200331',
7 |; f' c, B2 n k, F freq=pd.offsets.MonthBegin())
* M2 C1 C% n: c8 i# C% v2 L. @2 }' d' w
Out[96]: DatetimeIndex(['2020-01-01', '2020-02-01', '2020-03-01'], dtype='datetime64[ns]', freq='MS')
9 M( A; g; w$ C* {* {: n: [/ w5 \1 q: u- ?7 u' _ Y% Y) T* P
pd.date_range('20200101','20200331',
8 R% }2 T* _4 M2 J freq=pd.offsets.MonthEnd())
' W7 U, z4 t( n3 o# [; B, e$ L. X/ E# c% \+ j
Out[97]: DatetimeIndex(['2020-01-31', '2020-02-29', '2020-03-31'], dtype='datetime64[ns]', freq='M')
$ Z* g( _1 j9 r0 n
: o' Y" T: o0 l% rpd.date_range('20200101','20200110', freq=pd.offsets.BDay())- \) {* a' R4 t9 L( s
Out[98]: ! n! t# g' N6 k- ~
DatetimeIndex(['2020-01-01', '2020-01-02', '2020-01-03', '2020-01-06',
. T4 h) Y! N, D9 o! W '2020-01-07', '2020-01-08', '2020-01-09', '2020-01-10'],
: @" l* G- k$ p2 Z dtype='datetime64[ns]', freq='B')9 \1 _$ e7 M3 r
! m% t& L2 s% }& ?pd.date_range('20200101','20200201',
, T6 o$ a9 ]: f o3 F" _ freq=pd.offsets.CDay(weekmask='Mon'))
6 ?" L% ^' |5 q, D% N
1 \! x) t0 b6 ]Out[99]: DatetimeIndex(['2020-01-06', '2020-01-13', '2020-01-20', '2020-01-27'], dtype='datetime64[ns]', freq='C')
% T6 D# f* K% ~ }
" s. W& ?! P* p: Y7 n! Tpd.date_range('20200101','20200201',2 Y. z+ f& y* O/ q4 ^$ l
freq=pd.offsets.WeekOfMonth(week=0,weekday=0))
6 O2 G' A0 M& R9 M' s# ]
: Z3 w/ x! s2 p7 f5 _2 m* v% B/ |Out[100]: DatetimeIndex(['2020-01-06'], dtype='datetime64[ns]', freq='WOM-1MON')
$ e# e% r g* ~$ m! }! G
; s, [- I0 U/ X. \0 {1
' C. Y- Q; e/ u1 D: {2
. I$ U' r, ~' @ W! e; m3
0 [3 U% o1 o: Q9 b* ~4# u% G; w( `8 j6 \7 M2 w' w) _! M
50 x" y8 ^$ i6 ]5 N& g$ ?* {
6! V g% O9 A8 {* h6 ^2 S
70 [9 ]- l, O9 u8 f8 g. d
8
. C. u) O* n8 p9
( t; w$ R9 t# `! B10& i ~: s" G/ ]8 v5 Y
11
# i# f, l: n; r4 }122 F- r( C: D% h- d
139 K, M# Y$ y; F5 R1 z
14/ I% `5 D0 {" U' `
15
1 d7 U5 t) {, ~4 H16: z# u5 s- j! k9 ]8 a, w: n
17
+ P* M! M1 W, J. g4 C. r/ ~18
& ]" n- ~+ { r4 C% R7 y19
1 v: D- e1 W% j3 n: q% \5 x202 o$ L" C4 V1 m% A, g. I0 A; B5 C
210 r) S! S Z1 t9 Q- c# a
22$ b- r: L* U @. y! Z( n
23* R( D' ^ U s( ?3 \0 O
24
4 D0 p) P: O' I- b/ @& u! B25& O* R# }0 \& C. _9 a
【CAUTION】关于时区问题的说明
V' Y3 i/ x4 z. l/ R* I 各类时间对象的开发,除了使用python内置的datetime模块,pandas还利用了dateutil模块,很大一部分是为了处理时区问题。总所周知,我国是没有夏令时调整时间一说的,但有些国家会有这种做法,导致了相对而言一天里可能会有23/24/25个小时,也就是relativedelta,这使得Offset对象和Timedelta对象有了对同一问题处理产生不同结果的现象,其中的规则也较为复杂,官方文档的写法存在部分描述错误,并且难以对描述做出统一修正,因为牵涉到了Offset相关的很多组件。因此,本教程完全不考虑时区处理,如果对时区处理的时间偏置有兴趣了解讨论,可以联系我或者参见这里的讨论。0 I4 k; u' e7 I
2 B4 w: {$ a% G( i) ~! G# e9 ~( @
10.5、时序中的滑窗与分组
$ J* ~5 A- `4 Y K4 A10.5.1 滑动窗口
, H9 Y. D5 i" @: N" M 所谓时序的滑窗函数,即把滑动窗口windows用freq关键词代替,下面给出一个具体的应用案例:在股票市场中有一个指标为BOLL指标,它由中轨线、上轨线、下轨线这三根线构成,具体的计算方法分别是N日均值线、N日均值加两倍N日标准差线、N日均值减两倍N日标准差线。利用rolling对象计算N=30的BOLL指标可以如下写出:) R% U: y: K$ Q" X
6 Q8 ]1 @& ^6 p$ t9 U8 w
import matplotlib.pyplot as plt3 _- D. R2 R: U9 e( k/ ~
idx = pd.date_range('20200101', '20201231', freq='B')& m' F: O6 s$ D0 m- B5 ^( n
np.random.seed(2020)0 p) _# N$ J6 ]1 k8 e1 M1 W
0 ?* }0 y9 z0 b* i4 ?
data = np.random.randint(-1,2,len(idx)).cumsum() # 随机游动构造模拟序列,cumsum表示累加
. \ Y& l L( Q0 ]' l& Z9 c9 b+ hs = pd.Series(data,index=idx)4 P. x3 |0 [; [9 N
s.head()3 k* F: s- W9 ]
Out[106]:
$ F1 ~2 Z2 [$ ?! v! e2020-01-01 -1; S; n" b) S& O) X) m" I
2020-01-02 -2
3 [, l0 j3 Q( V1 N& N2020-01-03 -18 O% X# v9 |; n% ]/ d
2020-01-06 -1
6 t$ [) d( ?0 ~. V% \* E1 U5 x2020-01-07 -21 {* g: n5 A9 k( P d: |; V# O
Freq: B, dtype: int326 G1 b& T/ U2 ]* M* A# v
r = s.rolling('30D')# rolling可以指定freq或者offset对象
( {: w, E6 X2 [' V; _+ a3 k' o9 M; B: |5 }3 d$ X. W( F7 f, V
plt.plot(s) # 蓝色线
c0 I9 J% ?2 s, D# g# n; oOut[108]: [<matplotlib.lines.Line2D at 0x2116d887eb0>]
; C. `, W0 A8 @$ Iplt.title('BOLL LINES')* N/ n. _: C2 g" |
Out[109]: Text(0.5, 1.0, 'BOLL LINES')1 |5 f' d: ~0 k3 t! P* _
( o. E0 l! O) [' f
plt.plot(r.mean()) #橙色线
$ K7 k4 k' P6 u0 l [' y4 P* Y! @Out[110]: [<matplotlib.lines.Line2D at 0x2116d8eeb80>]
5 V4 D0 @ L( U0 v; t# T
0 n. Q, R# j3 C0 P/ R/ E% Hplt.plot(r.mean()+r.std()*2) # 绿色线- Y0 ~ n+ n7 H' E4 V
Out[111]: [<matplotlib.lines.Line2D at 0x2116d87efa0>]+ g+ r2 b. ]0 s' ]9 W+ u9 Y+ ?
6 ?2 d0 m, l" Y2 fplt.plot(r.mean()-r.std()*2) # 红色线
% }, k. y+ h4 ~$ i! Q5 _) _5 kOut[112]: [<matplotlib.lines.Line2D at 0x2116d90d2e0>]. `: T6 i/ P+ G, T1 y! A
; y3 U. Y6 ?( v: z( Y
1$ q2 i3 S8 K( v. F! |
20 D% I" b3 A) W, b0 R
3
5 E$ ]8 s) B# K: x* d& d4. c" D, ~0 P% V
5$ H* c% Y1 _( Q1 O2 p' |" |
6
. @5 h5 I/ A1 H. m& q4 t" D! ]73 `% i/ o3 I, k6 y0 e8 E1 s
8
: S7 F0 c7 f1 @) ^91 U4 p1 P9 S+ s* N
10
& {" D2 |" P. G9 P5 w11. f6 {; h0 {- Y# R& m
12
$ H8 D5 p z2 W+ j) v' Q) I130 i: Q) I4 t% I; J
14* k2 U. Y# X; C; A* k
15# N" T5 V7 C5 [+ `0 E
16
0 O6 {6 G" h6 p+ B17
# M+ m& e d, b' Y, q+ O18
% n; m! |8 M8 A9 j/ K* G19& Z! E! `( M+ r
20: p, E7 }+ A/ x1 x a
21
8 u# v0 Z4 s3 |5 W) F22, y; \# M& f" F6 L. F' I
23
4 i4 L- _7 s) L( X+ b24
. O# X, R% x9 k: w+ i0 B25% W3 O0 q2 z1 |0 k/ @' p
26
& G9 O* z2 y: b9 ^% J% ?27
! y- ]- M7 g7 H% [28
$ v" L$ O( O" J( z- ^29
2 O: t+ \ b, B% f- e, R
P/ p* z6 R) t; L# ] 这里需要注意的是,pandas没有实现非固定采样频率的时间序列滑窗,及此时无法通过传入freq字段来得到滑窗结果。例如统计近7个工作日的交易总额。此时可以通过传入多个函数的组合来实现此功能。
% K" s8 F* P& ~6 ^7 p# _7 W 首先选出所有工作日,接着用普通滑窗进行7日滑窗加和,最后用reindex()恢复索引,对于双休日使用前一个工作日的结果进行填充。
" b' f9 j1 ?4 @$ P4 m
3 b: e6 u8 p: \( ^select_bday=s[~s.index.to_series().dt.dayofweek.isin([5,6])]) h' w" H( Z- a0 a
bday_sum=select_bday.rolling(7,min_periods=1).sum()* i# ]9 V7 f, [* W, A* t
result=bday_sum.reindex().ffill()
! O7 |" \& `7 m2 s; W' r: R cresult
" G+ Z' _3 _6 R: i# @6 T
0 D$ @' }2 r$ j3 |! L3 O2020-01-01 -1.0
6 [" T% k" x! Y P1 r7 h2020-01-02 -3.0* e' {7 Q8 L& G6 h/ N: O
2020-01-03 -4.0
: V" V6 @0 u. j) y( E) V2020-01-06 -5.0
4 |; @) n; |$ L8 |2020-01-07 -7.0
! O/ n* \4 D- X$ D; _, n" t ... 1 m/ J9 M A& D) n
2020-12-25 136.0
2 _% ]5 y9 F4 N2020-12-28 133.0
; y( \9 k3 n. P" c6 r2020-12-29 131.0
" F3 O! ]3 j- S. V2020-12-30 130.0
3 G" @3 h8 n9 M4 j1 A( w" N+ Q2020-12-31 128.0" n7 k2 G2 a) D n! R3 d. b! T! E& U
Freq: B, Length: 262, dtype: float64
) A5 ^: |( E9 b2 |
7 w/ u1 V- F1 B, b" S* K' D11 }* y# ?& j* v. c% C+ V, Z% r/ r
2
8 l) d* e E) u6 h3
0 h4 ]. |! v4 x6 M/ P4
2 `$ o, u. F3 G! H" q3 y4 U* d5 K5
7 A/ b/ Y0 ]1 {4 y+ _$ w6! d/ D4 F9 V% f: Z: M, C
7
7 [' ^2 t5 y T1 d u2 x8
- r, g6 x; R0 ^# e9
- O% J3 u6 X* h! g# E10
: s" c1 c4 A9 c: y11+ b \* b. L0 A+ y8 A; e: [
12$ i- Q& J' d1 `
13
5 e- u, M% w2 K B8 ]14' }0 i7 B; u) v' Y& t1 m4 E6 ^
15
& k8 q; ?3 p: s+ T- e& Z$ `16' L; R8 C, j5 A8 P; ~
17
, E0 i/ R8 l! R( I0 ~& D shift, diff, pct_change 是一组类滑窗函数,它们的公共参数为 periods=n ,默认为1,分别表示取向前第 n 个元素的值、与向前第 n 个元素做差(与 Numpy 中不同,后者表示 n 阶差分)、与向前第 n 个元素相比计算增长率。这里的 n 可以为负,表示反方向的类似操作。
5 G( u1 |6 k5 g" X& h
5 X+ b3 m: v4 u# W7 z 对于shift函数而言,作用在datetime64为索引(不是value)的序列上时,可以指定freq单位进行滑动:! i6 _+ }0 e6 m1 \; H8 B
" e, l5 h6 j1 \4 U' k4 @! {+ ~$ M
s.shift(freq='50D').head()
, b! J( I/ H1 ~% h( j/ kOut[113]:
, Z& i$ ~7 |& @2020-02-20 -1
6 t, b. D) K4 m4 l+ T( f2020-02-21 -24 |# Z9 R! W# {. D: s9 I
2020-02-22 -1
! C- ^1 ]/ K2 Z& U: {$ T, h2020-02-25 -1
, |0 c A* X% O6 a; I8 A2020-02-26 -29 S. G) h' g% u/ E# N8 B* v) ?
dtype: int32# \, k# R2 u8 z; k+ {
1
* ? p) w7 M; a: k5 f- q21 Y8 L1 S0 ^& O! M7 d, j
3
b. [7 O# s/ G. @/ A$ n4* d) k7 b8 i) `& ?7 }6 n
5) z1 ?3 Z: v% h: t) o
6
) @% _' ]8 `1 C- G+ ?" @7
& q0 G5 G6 c0 S+ B& r/ u87 c, j) @0 ^# q/ K: S8 Y
另外,datetime64[ns]的序列进行diff(前后做差)后就能够得到timedelta64[ns]的序列,这能够使用户方便地观察有序时间序列的间隔:
% d* ?$ o- L! q+ |, y' k( Y4 f% p+ o- j- z
my_series = pd.Series(s.index)
+ [% A0 u5 S, ^# Gmy_series.head()$ w( F9 v8 `7 d+ q9 {. K
Out[115]: ?# l5 G% m0 f: w4 i& L
0 2020-01-01! q" ~) j0 w' A2 N' G
1 2020-01-02
m# s7 x* K! k+ c8 d2 F1 D2 2020-01-031 x1 F2 Y j- I- ]! H7 a- U' u/ I
3 2020-01-06! @5 e) z1 S; U3 Y4 J
4 2020-01-07# q% C; E% a) I- _( {" F
dtype: datetime64[ns]3 m' Y! }- w; J% ~( [) s, P9 E) x
+ D& f Z. H0 E: ^# J
my_series.diff(1).head()/ {+ n7 U0 r( g* T5 |* ]+ |9 m
Out[116]: ( G: L5 ~4 D( a* y( [* v
0 NaT7 ]* v% w+ j/ T* ]
1 1 days* M! |' W4 z% S3 M
2 1 days# y' K6 H9 C# u
3 3 days/ K! T, u% I" {1 v9 B8 f
4 1 days* Q6 x/ ?+ Q. G0 P* y
dtype: timedelta64[ns]
- w* T6 l+ i- d0 W+ o6 B$ }% }, z: R7 C5 t- w( b
1! n/ {" S! K1 \; a$ A) f" R; F$ w4 r
2- D- N! C1 J; _" L
3/ D" h7 \$ p4 b$ a+ f# [6 B+ ~5 s
46 W |! B/ u3 h" w. c
5
/ R$ S3 f6 }3 q$ E6
' ?1 O4 f- q8 r3 w- {' k7% B- ?( v- @5 `/ N+ `4 q
8
, i, Y9 S8 d1 L, l! ^! i9
" d. L# ~$ D5 |0 Y; n. z; W10
& q6 b/ u6 f, t- G9 h11% [7 k9 {/ b- V% x P! ?& r8 ^1 d
12
6 Y+ g. z, u. r0 z% z13( f5 j$ N$ G; f- }' ~
14
7 W- u9 w; Z: a+ u% h: m15
9 j+ U5 U$ C$ ?4 I0 ^- G16; e3 @ b# ?! s
17
* j& v; i3 s1 G% n+ p% X, a# ~18
& D) f- f7 v4 _" G8 q9 [" k" g10.5.2 重采样# |& a: z3 v: Z
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)
p9 N7 R) O# w, ^常用参数有:
- y# r+ @( w+ I9 ?( w6 H3 x9 V! h Z( o2 I! D6 H$ j1 K
rule:DateOffset, Timedelta or str类型。表示偏移量字符串或对象2 \& O) c2 ?- n b G% k
axis:{0 or ‘index’, 1 or ‘columns’}, default 0。使用哪个轴进行上采样或下采样
/ o( y' R* g+ B, jclosed:{‘right’, ‘left’},默认None。表示bin 区间的哪一侧是闭合的。所有offsets的默认值为“left”,但“M”、“A”、“Q”、“BM”、“BA”、“BQ”和“W”均默认为“right”。; U" B- c$ q/ v8 M) H5 _
label:{‘right’, ‘left’}, 默认 None。hich bin edge label to label bucket with,所有offsets的默认值为“left”,但“M”、“A”、“Q”、“BM”、“BA”、“BQ”和“W”均默认为“right”。
@# z4 P. c- {9 _convention{:‘start’, ‘end’, ‘s’, ‘e’}, default ‘start’。仅针对 PeriodIndex,控制是使用rule的开始还是结尾。
' x" j I3 {# [4 @4 ion:字符串类型,可选。对于 DataFrame,使用列而不是索引进行重采样。列必须类似于日期时间。
, C1 G; o1 k8 nlevel:str 或 int,可选表示多重索引MultiIndex的级别,这个级别的索引必须类似于日期时间。
$ p' n+ G# j. t, \. y2 jorigin参数有5种取值:
" o& ~. F: k% |4 t$ _3 q‘epoch’:从 1970-01-01开始算起& V% ~5 \% Q# p" u9 I. u
‘start’:原点是时间序列的第一个值
( B3 H: ^1 t4 t: {5 e/ |‘start_day’:默认值,表示原点是时间序列第一天的午夜。
* w# D: _" J; p- u3 ~; g: Q'end':原点是时间序列的最后一个值(1.3.0版本才有)# x0 c2 @2 Y) J1 t/ Y6 h S
‘end_day’:原点是序列最后一天的午夜(1.3.0版本才有)
, J0 }" Z! u! ~/ r8 G1 O) boffset:Timedelta 或 str,默认为 None,表示对时间原点的偏移量,很有用。
% `: B- e4 [- I% ?) B closed和计算有关,label和显示有关,closed才有开闭。
5 y% m& w8 |6 R; `( L) g label指这个区间值算出来了,索引放区间的左端点还是右端点,closed是指算的时候左端点或右端点是不是包含。
7 X0 I/ P: Q! S& e# G' K( D* [3 P3 }1 {
重采样对象resample和第四章中分组对象groupby的用法类似,resample是针对时间序列的分组计算而设计的分组对象。例如,对上面的序列计算每10天的均值:6 S) K: N- C1 U6 F) |
s.resample('10D').mean().head(). y. ]' L* j# I7 w9 s5 r. b
Out[117]: & N) `, ^; T: \, W
2020-01-01 -2.000000
6 M- r8 l+ l( Y0 R5 c0 _1 a- ?: Y2020-01-11 -3.1666673 d: \0 z8 G7 b: [0 Z& f; A
2020-01-21 -3.625000
* U( ?$ u& K: c5 e" R& B/ Z2020-01-31 -4.000000
7 R8 S. h. q: S8 y: l/ J& n% _4 \2020-02-10 -0.375000
; a8 D5 [3 Z4 r/ Y3 s( R9 BFreq: 10D, dtype: float64
/ F" [2 v* N6 O( y1
3 Z) C% g) e( Y) w; z2 W6 z2
$ h* e8 t8 B5 D- q; p. C3
' F$ t8 ^7 P! }6 v1 ]8 c4
* G2 s% [: I3 B7 S m9 P# |5
8 o' t: E+ A r2 R# Z# @9 P66 g+ [9 t. j& v+ L2 N
7
. R! H7 b, T" {( ?2 g8' N) s; w% ^; ^$ W
可以通过apply方法自定义处理函数:" U" d) ~$ D& H& e! F9 z
s.resample('10D').apply(lambda x:x.max()-x.min()).head() # 极差" s% s t4 B; D0 ~
4 k: O8 R; U/ A9 F, Q' ^9 h" H) ?Out[118]: " S; Y B: a3 l
2020-01-01 3
^' V9 W' n* F2 I" P2020-01-11 4
3 G8 ~, K5 G# F& M. V0 b9 _4 u H2020-01-21 4/ u- Q4 }, N% a3 U
2020-01-31 2 n$ @8 F5 s2 S' b7 N) ?
2020-02-10 4
0 |: f. |& @" N, s4 pFreq: 10D, dtype: int32% J+ `1 C# h, F( c- n
1
' f9 _% A, ` ?2
1 X0 R6 a w; J) ~1 A3
$ m" U+ m+ V' [- q! u; L0 T, C! m$ x4+ L$ Z/ k3 S$ o* U" i/ w* m# c
5
7 x: ]6 ^4 [0 t# H2 {! y& K: i6
7 G5 x# G: R5 z' W7
9 s }" g( \ B6 [' ^( L89 r e: U* u! Y, J
96 |4 L" c. `/ n7 I
在resample中要特别注意组边界值的处理情况,默认情况下起始值的计算方法是从最小值时间戳对应日期的午夜00:00:00开始增加freq,直到不超过该最小时间戳的最大时间戳,由此对应的时间戳为起始值,然后每次累加freq参数作为分割结点进行分组,区间情况为左闭右开。下面构造一个不均匀的例子:
& s4 ]* _( Q7 P- c1 G( b& L4 h& n; O% }
idx = pd.date_range('20200101 8:26:35', '20200101 9:31:58', freq='77s')& O+ p5 M) E: a# t9 N) B4 P0 N
data = np.random.randint(-1,2,len(idx)).cumsum()6 Y/ S' b, |- z2 E0 {7 A
s = pd.Series(data,index=idx)
5 a' c$ v0 |; X+ Q+ _- t5 }; |9 hs.head()
. c! {# @& S- m2 o) j0 f) }, F+ d
( L4 q( g( d/ T7 E7 t3 mOut[122]: Z/ J) w6 e! W) |; ~
2020-01-01 08:26:35 -1
% K3 y4 ]4 t9 u$ i) P2020-01-01 08:27:52 -1
- v+ @0 n- q* x6 P4 z& a3 ]2020-01-01 08:29:09 -2
' t; ^4 x/ N: a. V3 @6 q& i9 Z2020-01-01 08:30:26 -3
& ^% W2 Y- ~0 j; z: L5 p* l2020-01-01 08:31:43 -4: m0 D7 L% f* Q5 [* l% X1 r
Freq: 77S, dtype: int32
/ C4 P* n7 ]2 E# @* _8 N6 ]1* s$ Y Q/ u0 P& e
2
" Z5 A* d+ J# H1 c3( I9 S+ _- k9 o8 e# Z! d/ P6 g
4
7 [4 [1 J: G8 D8 m3 i53 B1 e; C; i6 a. k2 D/ c0 A
6
( z$ g* z$ \9 y3 q$ B+ U73 ?% R% V, E, w. } W
85 d: _$ b. c/ ]
9
0 t& x9 i: N$ L. j# H5 ~- ?0 e7 U10* S3 p! Y* p+ w6 `3 K' f8 {
11
; M0 s: O" ]# P. y, p12
, v/ p' p7 ~( t [ 下面对应的第一个组起始值为08:24:00,其是从当天0点增加72个freq=7 min得到的,如果再增加一个freq则超出了序列的最小时间戳08:26:35:
! L$ ?) V: q# w& v9 P! h3 t
# B. W, z0 L! Q6 a0 P* G2 o+ Ls.resample('7min').mean().head()
& s: d5 A# V% P6 K+ P8 `. H9 s( G7 VOut[123]:
: E0 n6 V8 t6 Y" I9 P2020-01-01 08:24:00 -1.750000 # 起始值,终点值包含最后一个值
0 `# g$ }/ ^1 c9 ]8 \2020-01-01 08:31:00 -2.600000
( _+ \% |# J0 W0 A9 A2020-01-01 08:38:00 -2.166667
. Z& n" a: g, t1 s* g+ x2020-01-01 08:45:00 0.200000: K0 m$ P# \1 W+ h& z3 B6 x' T* S
2020-01-01 08:52:00 2.833333, U# G2 I9 O* c9 Z( @
Freq: 7T, dtype: float64* n, I, E& n/ n* ]
12 b- V) H/ s6 N3 q/ q* P
2
9 M3 S5 E! |' q; F3
- X% D8 c ~9 V3 V. `4
7 D9 V, @6 H! d! t# r+ S2 ^1 P5
6 |: ?0 p& J) Z: ?6. Y7 ^ Z$ G* p
7
0 E+ C% ~( V' g3 k6 e; n8
- t5 F b+ j. f& C7 B 有时候,用户希望从序列的最小时间戳开始依次增加freq进行分组,此时可以指定origin参数为start:5 t- `9 F1 |9 @8 B) H# _& D; L* u U
: W% U4 r( a1 y5 S5 N$ e! xs.resample('7min', origin='start').mean().head()
8 J, g( E U3 S: z% v, nOut[124]: / c) g9 { {6 w) x4 \8 A4 A3 o
2020-01-01 08:26:35 -2.333333. w9 R* a' H7 e9 Y9 g9 _9 q
2020-01-01 08:33:35 -2.400000/ ?; j/ x- P# I. X8 a4 @
2020-01-01 08:40:35 -1.333333
6 x' h. a' X5 I8 J8 T5 v2020-01-01 08:47:35 1.200000, z' ]' L! ^; z! l1 m
2020-01-01 08:54:35 3.166667. j( ~2 S P9 Y' x5 v! x
Freq: 7T, dtype: float64
+ v$ y# N' c% P+ y& m' q8 Q1
: Q$ ?/ s! A7 \3 f- V0 B- {24 V/ W) R2 R' G) j8 `9 _2 T; @% M( Q
3( f6 i, P& c) M6 {
4
$ ]2 a; V, n% c, v2 p! u5 U5
+ N& O [5 D8 x) x9 D3 d; U$ D6
0 m- S* ]3 ^& @& f7 Q7 A* g( ]; m, V
8
' r# B. r8 d: W, b4 e* a q3 e% c0 Y 在返回值中,要注意索引一般是取组的第一个时间戳,但M, A, Q, BM, BA, BQ, W这七个是取对应区间的最后一个时间戳。如果想要得到正常索引,用’MS’就行。7 t8 f5 U: I4 b
% d3 c) N& g' G9 q0 @8 ws = pd.Series(np.random.randint(2,size=366),
4 s8 c" M# s5 e+ X% T index=pd.date_range('2020-01-01',! U6 p$ U' J) ]7 o8 K
'2020-12-31'))
5 L0 l3 I# X: q6 w
! C4 I1 j% [5 D" E9 A, r/ C( E
6 r! c- I/ Q# k. Ns.resample('M').mean().head()
8 ]1 k) Y/ T2 w" h* u$ R+ y' wOut[126]: , H: d9 j5 a+ r+ a9 V3 n/ ~
2020-01-31 0.451613
! Q9 U4 H( ~# n9 v6 m* Q2020-02-29 0.448276
& I, S' w7 b2 c+ T2020-03-31 0.5161290 e9 a' K+ N' p7 J; c
2020-04-30 0.566667) ?+ f' J6 {. K1 K) K6 F. `
2020-05-31 0.451613
# y) V/ a, s" d/ Q, ^. ~Freq: M, dtype: float64
6 p T0 p& Y6 D4 P# @$ s; t" S1 @2 J& R1 D3 c
s.resample('MS').mean().head() # 结果一样,但索引是跟正常一样" w h K! T; g0 \0 A
Out[127]: * r3 n( i0 ^) b H. b# `0 x8 O) C
2020-01-01 0.451613
# d& p9 W# n$ L3 T2020-02-01 0.448276% e1 L' M1 s: v/ r+ V
2020-03-01 0.5161290 `9 {; v2 F+ G3 ~' X- g
2020-04-01 0.566667# e2 c' B! o6 Q- L4 }0 Q7 o
2020-05-01 0.4516139 s" e& j4 h/ I% z; b# ^7 Q
Freq: MS, dtype: float64, I6 Y1 P |4 o. I+ e
1 d' `) S# {9 M: `2 M
15 D3 f( ^' S8 c! r# j+ o4 s9 \
2
3 f$ i7 W/ E3 `! `# @3
$ ]5 P* i! {" V3 t% \# }4
: y* p5 v+ h% z3 E- r56 }- q3 c6 g+ w1 b
6
/ P+ y# M+ W% s& b75 |1 ]/ {9 ]/ B7 q; @% d, f
8
7 L8 Y0 ^, _& L. f# i& b6 u& W9' R* s- Y9 Q$ j( K: A9 o$ x
10! K# z8 Z7 _) C. U2 O
11
" `$ D g+ A& @; ^/ p12
+ u f& {) Z6 i8 ?+ A; U13# `# j& o7 r1 x( {
143 L8 J! d" z7 G% M. I8 d( s
15
, G4 w( n$ g8 @: _16
7 U- X5 n S) f0 u3 _. ^17
" [3 r' X: t% K; q$ W+ Z* E18' d) Y! F- B, m# O; Z) o
19
( B! _! t4 W% c' f8 F( J# G204 _) e [3 \! i/ e
21
/ \1 w0 s( o0 v+ k22. [0 F$ S, y3 n8 r5 ^* a
对于 DataFrame 对象,关键字 on 可用于指定列而不是索引以进行重采样:
' ^, w9 v7 N3 h2 O4 N3 F# qd = {'price': [10, 11, 9, 13, 14, 18, 17, 19],
" }0 l1 [' G/ G' E7 G 'volume': [50, 60, 40, 100, 50, 100, 40, 50]}
( |4 S5 }/ m+ \4 rdf = pd.DataFrame(d)! W5 @# O, u8 I3 J
df['week_starting'] = pd.date_range('01/01/2018',
4 p A$ B5 Q/ P6 J& t$ O periods=8,$ b, L, t( q7 N8 J& ?% |
freq='W')
" K' T& R A" d& k' Bdf
& b8 t' R# `* H2 x price volume week_starting
2 z4 e$ o2 s m' H2 B0 10 50 2018-01-07' f* j6 b; G# G
1 11 60 2018-01-142 x; _+ @- y$ }2 Z: t9 M
2 9 40 2018-01-21
# J0 `) ?) C5 U1 C9 I3 13 100 2018-01-281 K4 [, W9 C' G' h" V/ _6 {" ^
4 14 50 2018-02-04
6 L. z* f) q5 g" k' @% n- ^" j; W5 18 100 2018-02-11
) O) P! _ y# ~! z4 y6 17 40 2018-02-18 X7 Y( a2 b ^
7 19 50 2018-02-25
5 B, `1 b; c' W- X/ P$ s+ e+ ?% d8 pdf.resample('M', on='week_starting').mean()
8 j. H6 X1 X' ~$ M3 I0 ^ price volume+ W4 _; C2 H- w3 J
week_starting
7 }1 g8 u: t- }( o9 }" ]# Y7 Z: J2018-01-31 10.75 62.5
$ u4 c' `2 f4 [7 D+ j2018-02-28 17.00 60.0
T: Q9 s/ Y! f& z2 j
/ n* p$ A' k5 V8 c" m% w! g+ K1
; M6 b3 g( B* A2
% L0 B5 S; Q) N1 r R. F- g3. _0 S" e7 Q% L* s: Z' h2 i
4
2 M: M. g* m% [5
: [8 {3 n6 H- q6 J6 G5 ~; k6
! d" }/ C9 B- {3 W- K2 ]7
! g5 C& F: B5 H9 K1 V; q84 M6 N0 T& o, G2 G& M, K
9
& D; }8 h( |& n5 e# N; u, d: v" U10. j3 |. S# b9 F: Y" k
11
, X( i7 f+ k- a& F/ C* x12
7 f( g2 c, }3 r W" f135 ?( o- q0 t' b8 P- H
14: g( K% s, u# J: }
15' I7 i: {: y, l& U" p
16
; |0 M! B, y, J* j6 @17
6 ]5 o+ a* n- t/ @3 P" b' E18
; b8 \8 g a% T0 o$ D- n$ e19+ \4 l6 K. p; @5 s+ ]: A( o
20
3 C: [" A; L6 S; w( [( W0 l21( c8 ^8 U/ \; H, c( T' D
对于具有 MultiIndex 的 DataFrame,关键字 level 可用于指定需要在哪个级别进行重采样。
/ y) y" n2 t& w( mdays = pd.date_range('1/1/2000', periods=4, freq='D')% n+ _' ^, x( _# K* I0 W8 {
d2 = {'price': [10, 11, 9, 13, 14, 18, 17, 19],
/ F/ o! ] t, t/ Z& Z4 B 'volume': [50, 60, 40, 100, 50, 100, 40, 50]}4 N4 C& i) n9 w5 _2 e* u# ]3 o* |
df2 = pd.DataFrame(* ~( `3 r+ p. }- r' {
d2,
, X' `* @9 i1 C- X) \1 V+ Y index=pd.MultiIndex.from_product() }* D4 _3 a7 A5 o
[days, ['morning', 'afternoon']]* `, b, ]4 D/ n' N6 K
)2 N+ r; L4 `3 x( b5 j
)9 d3 Z# i9 D8 j- X4 ]
df2
0 @3 A2 |6 Q# Z' ^0 l7 T price volume
& ~! M1 ] y& |3 U8 j2 _: C2000-01-01 morning 10 50& s- u1 D& B0 e; P0 m
afternoon 11 60* r' f8 b% N6 A3 O4 n! l7 Q
2000-01-02 morning 9 408 S+ ]7 m8 X& A* f0 C; ?
afternoon 13 100
- z* N" @ H' r0 a1 O, Y( I* Y2000-01-03 morning 14 50: X0 s3 P3 z) u( }/ h/ D! Q9 l
afternoon 18 100
8 W% h7 E8 i% O4 ^' p- [; y2000-01-04 morning 17 40. F! f( ^! w' Q3 f/ H8 I9 a
afternoon 19 50
; ] [7 F+ b$ r$ f6 N( qdf2.resample('D', level=0).sum()
! Z( P: i0 a4 J+ u price volume3 ^* Z# F& o1 d: U l; n
2000-01-01 21 110+ f3 ^. B# W( f) `+ [9 c
2000-01-02 22 1400 k: r+ b- R g' P% j
2000-01-03 32 150" c" c; o8 l6 G0 U+ L" z
2000-01-04 36 90
# `3 k5 g' x; Q/ K3 ?: p2 U
2 H+ ~9 A" S$ V. P1
E. W; w. e1 k E6 ]/ Z, e. c) x/ `22 H: ~; t3 z% {" W; ^
37 K. o' j; v2 M
40 A& x) j8 d& w/ }; |2 `
51 ^" l- y; ?: ]* _0 Z( W
6
& N; R2 o' c" r# I7
3 l9 y$ E( a' v9 {( ?& S, h/ v+ [. ]0 f; T8
1 k' H9 Y3 F7 G# n9
! {! N* K+ j, _2 v6 m10
& C2 U, L9 `/ N# h11; V. w0 \% |# D- l2 K1 F' w
124 X$ @/ z1 C* \, d8 F' O& u
130 A& t9 O) L8 F$ g! @
141 q5 Q7 Z. w" G2 V8 J( e
150 r% k3 c" M( h0 q5 `4 b2 z( ~' l: e
16 a. g+ G2 P* l3 z# y8 F" f9 V
17
6 I9 ~( h6 X+ ?. v5 h5 ]180 b7 E+ r4 F- Z! w% {
19
/ J) y( N) j: d3 O$ `203 T0 U% _% q: |& J) }, l
217 V' \) A+ A8 l( l' \
22
! Q( N) b1 M3 i% F23
0 ?! O4 ]- d, K* F, U8 Y# i24+ e, M) I4 K2 [
258 ?7 \4 g# D! c1 j5 x0 J
根据固定时间戳调整 bin 的开始:
3 K, r" r t' ~2 V! r5 Y Tstart, end = '2000-10-01 23:30:00', '2000-10-02 00:30:00'
$ A9 K+ c# w6 }/ a* e0 crng = pd.date_range(start, end, freq='7min')! \" e' ^! C3 F3 l) K5 F
ts = pd.Series(np.arange(len(rng)) * 3, index=rng)
$ X* |' U6 s# u2 ots& z$ |% D$ ?; ^
2000-10-01 23:30:00 0
" n& j7 _, I4 b( a# ?2 S% g2000-10-01 23:37:00 3
- M% e* W. K1 |) U. i# _2000-10-01 23:44:00 67 A" k6 H+ m( C4 ~8 q, d+ Z
2000-10-01 23:51:00 9$ m: p# v) `$ N5 o+ c- l
2000-10-01 23:58:00 125 j9 j8 W- F* b" ]+ |! |; } d
2000-10-02 00:05:00 15. X) [0 s% Q: n/ J# T
2000-10-02 00:12:00 18
5 P$ e. A- C, |5 K, h$ S2000-10-02 00:19:00 21
6 D v$ J, @# _+ r2000-10-02 00:26:00 24
' l$ d$ W9 x: D( h+ IFreq: 7T, dtype: int64
7 `- R, U% s; h8 s' ~: ?* E) c# Y$ w
ts.resample('17min').sum()( ]! O% u' Y4 J
2000-10-01 23:14:00 0
0 j/ z+ Z. I6 O. J B# t7 O& n2000-10-01 23:31:00 9/ P; y" Z& E, B9 x1 v
2000-10-01 23:48:00 213 [( l7 E+ K0 Y; V' V+ j0 T
2000-10-02 00:05:00 54
@# Q: H/ g3 R1 }( U6 a2 W2000-10-02 00:22:00 242 R% E/ ?2 r+ B0 P1 b- c$ ~+ K N
Freq: 17T, dtype: int64, f+ V) T* w: U/ G, q
4 |& N& U& _/ r4 z$ J/ I- nts.resample('17min', origin='epoch').sum()3 @6 g& B J) W0 @. e4 S- z3 R
2000-10-01 23:18:00 0
$ C% a# W% K1 D* W4 y2000-10-01 23:35:00 18; S4 j: d; H' E: q9 l. `% c
2000-10-01 23:52:00 27 n; t$ n% k, f
2000-10-02 00:09:00 39, f8 `: L g9 F1 I, ^+ s" U' k
2000-10-02 00:26:00 24
( [5 u# K! j9 O8 W( iFreq: 17T, dtype: int64
* G L# @( H O. R7 [) [% o9 q0 x0 M7 l t* W' }0 V0 n
ts.resample('17min', origin='2000-01-01').sum()
" a# ?+ z4 L0 X/ _7 o2 |2000-10-01 23:24:00 39 r( s' |3 V; y2 x
2000-10-01 23:41:00 15. [; g9 e7 }: L) [' ~
2000-10-01 23:58:00 45! n# v* Q# E0 W7 D( Z4 n& `& e/ U
2000-10-02 00:15:00 45
7 S* r9 `" f5 sFreq: 17T, dtype: int64
/ S' P: Z; ]- P# Z& [
- k) k- J0 Z2 C. M1( N7 M) F* R3 w! c% x, J' j7 b4 w" r
2$ U; Q$ A7 t0 q0 Y+ @
3
8 v+ I: J9 k- M. y4, f' D" ]% d5 d- `0 q4 @: K; f
5/ F1 e- j, b% v! g6 m
6
; }2 U4 f! U! g- K D! Q7
G; C( l. C4 S, g0 v0 g8
2 m. P; \0 T1 q7 d6 Z" M6 q9
; [/ h: ]3 Q9 i; `( }9 [10) ~" N' L( l3 T
11( V4 H. [5 d; j, b
12/ x; V; h' k2 }+ G4 E6 J" E
13& e" U# E& m( x- [* C
14& w; F9 D1 R! v; ^: S
15
5 l9 W2 Z3 b& a. W168 x! |7 E% Z4 h" Y( r
17( l' Y$ G( E2 e. P6 A
18
$ _1 q( w7 ~& t. ^# z' ~19
9 u8 j2 ]1 z3 H U20
$ D" F* A O# h0 a" a3 c211 c7 y8 W. ?- k+ c! I! y1 m
22
* t0 N N9 q) J! V! X- ?23
; ~: s! L* j: b; Q5 N+ w24
# u9 l7 V0 w* o6 @25% i$ v/ V; O$ n5 r. V
26
0 E' S2 N3 b' Z, z279 X& |" S7 _7 p, B
284 Q8 `0 L5 r# N, y' c) u
296 K+ J- C; R, [; }! [
301 o7 L, C3 \' ?9 K
31$ o3 P3 w! h. z' M) z+ P0 a1 y
32
2 R, i8 y* Z$ |, o# R33
& K" l; D% H# L6 c/ I4 k7 D349 T# R; o, ~/ O6 s
35
9 f$ Y# l) `! a Y& g36( d5 k5 v6 w2 f! v6 B. w3 S
37
: f- J& P. \ z* @2 D如果要使用偏移 Timedelta 调整 bin 的开始,则以下两行是等效的:
( I5 H6 {3 R+ k5 g+ `6 F8 Hts.resample('17min', origin='start').sum(), g# ~9 s" x: j1 F
ts.resample('17min', offset='23h30min').sum()5 e' ~2 Y( R7 C: G2 T
2000-10-01 23:30:00 92 ]- v) o5 W- S1 Z6 ]
2000-10-01 23:47:00 219 C& L5 _; y2 v( A! r* _/ b4 G
2000-10-02 00:04:00 54* G& p; d& [- q0 W3 ]
2000-10-02 00:21:00 24
) ?: b- m2 d* c9 J+ HFreq: 17T, dtype: int64
; x7 b% ^- [* c1
# b% v( G* ?1 w1 ? V$ d2
. W" P! O' {1 y# s, ~( M- o3- A% t% ^8 Q7 y4 o
4
( E9 X: Z8 x+ q3 z5% o; O' w% h4 V
6
1 U6 q1 c5 |& V7
8 P* j& W) k. f, c7 d, k' w10.6 练习
( q( K4 G' }3 @2 t; o3 d t$ D% ZEx1:太阳辐射数据集
2 t( q. s! f$ v现有一份关于太阳辐射的数据集:
2 \% N" e7 M" A! D4 A$ D& d0 {6 E A2 ~: o4 G& y
df = pd.read_csv('../data/solar.csv', usecols=['Data','Time','Radiation','Temperature'])7 T# x% S1 J0 a3 ?
df.head(3), _7 |" F( S5 A, d: A
% J, p. Z+ |9 dOut[129]: % G1 l- o) v6 W5 x6 b! h1 o; Z
Data Time Radiation Temperature
5 D; E. U% B: _* t+ }, v0 9/29/2016 12:00:00 AM 23:55:26 1.21 48& H6 A2 ]# z, L7 k2 }" f% }
1 9/29/2016 12:00:00 AM 23:50:23 1.21 48
. p/ p- o L. Q# f4 y4 C7 l7 p2 9/29/2016 12:00:00 AM 23:45:26 1.23 48
, T- F! A" w; E, |! `! }1
% I9 q L% D: U+ J2
" Y8 ~# T+ C$ w- J, I9 Q% J6 {$ |/ D3. j7 {' o d7 ?! i
45 B8 h& a# Y' x, G
5
! e& m3 `6 a+ r) m2 n+ K- g6
; t' h& E. Y8 v" z- Z7
# c4 V3 \, l Q8 j& ^, c0 Z, |8
8 q" |6 |+ |9 a: f8 a! i3 x将Datetime, Time合并为一个时间列Datetime,同时把它作为索引后排序。3 N8 ~8 S9 x- {9 E
每条记录时间的间隔显然并不一致,请解决如下问题:
& d& ?4 V* Q. j0 j8 a找出间隔时间的前三个最大值所对应的三组时间戳。4 q# O) c$ \; N7 S3 B' q8 T
是否存在一个大致的范围,使得绝大多数的间隔时间都落在这个区间中?如果存在,请对此范围内的样本间隔秒数画出柱状图,设置bins=50。
. {2 _2 T/ ~3 M# Y0 c求如下指标对应的Series:
2 G; |9 k$ O) Y7 v温度与辐射量的6小时滑动相关系数1 P1 W$ a' u% q8 I! v) v- D
以三点、九点、十五点、二十一点为分割,该观测所在时间区间的温度均值序列
' S3 X, m5 j" q每个观测6小时前的辐射量(一般而言不会恰好取到,此时取最近时间戳对应的辐射量)4 e g1 ]( |5 n
import numpy as np
; l: G$ j9 l. D$ Himport pandas as pd
+ c* Z2 W" Q+ x& k h( Y1 P1
" z3 g5 J4 R& D* i/ s! d2
) @% c4 [" U$ Z将Datetime, Time合并为一个时间列Datetime,同时把它作为索引后排序。/ ]9 x5 a1 V: D" o
data=pd.to_datetime(df.Data) # 本身是object对象,要先转为时间序列
. @) I: K1 c0 f) atimes=pd.to_timedelta(df.Time)
5 H5 M9 R0 x+ D+ M% B( Ldf.Data=data+times/ c& l7 J& W8 ^% ~( @
del df['Time']& }' v+ y- u( ~8 D& O1 {
df=df.set_index('Data').sort_index() # 如果写的是set_index(df.Data),那么Data作为索引之外,这个列还另外保留3 M2 O, s7 ]7 B4 t$ m
df
( B L! |. ?0 y" O0 v- X5 l! m, T Radiation Temperature
3 ?; w4 F0 X$ s! V0 OData ) t6 Z$ e1 D7 H% m+ f
2016-09-01 00:00:08 2.58 51
8 G# J. E. [, d/ V) ~/ J/ Q& C2016-09-01 00:05:10 2.83 51- }' M$ b: N4 | y* Q
2016-09-01 00:20:06 2.16 51
7 x0 r6 k+ m3 T2016-09-01 00:25:05 2.21 51
' _: t0 u& M2 `- M* j2016-09-01 00:30:09 2.25 51* Y6 k( _. s+ X# F8 w, t% E" U
... ... ...; c9 l$ x$ o+ W" @+ t5 G$ D3 [( e" N
2016-12-31 23:35:02 1.22 410 ~& v$ N' O) Z" L1 t, t4 [
2016-12-31 23:40:01 1.21 41
5 S4 G* m4 s' [( z! g5 }- e z2016-12-31 23:45:04 1.21 42
$ O+ @& \5 J/ o; I9 {2016-12-31 23:50:03 1.19 41
% K4 E8 B: g) p+ ]3 t3 j9 l; o2016-12-31 23:55:01 1.21 41; Z y" L) @( S$ ~
0 w' l+ O& Q( t" P1
' A l$ H4 V6 ~5 K2! y( s" p/ x, e6 I3 I: p( H3 H% O: h
3
3 p& \+ B- j* L# d4
+ }) j* D3 J b/ {4 e$ I5 ~: z; u5
# x# @" c+ b0 C v, M6
' p4 a& y5 I2 ^) v+ v; e79 J6 [4 p m+ ]; y6 n" t( O
81 e8 B$ s8 z' d( _# g
9* S+ |" k& A; @! j! h$ u
10
$ S6 b2 ^% I) f, l/ s1 k8 v11# F! k& u. F+ \9 w2 O7 i
12
7 L) e' Y' M$ H) y! n# h13
A( W8 L; M3 f9 j; W' f14
( d+ R- \- d# \* S. C. K/ V6 l$ p15* T9 D3 y( e, o, F/ }
16
. \7 Q( |2 ]5 [+ \17
6 j/ `! H7 ~) B' w9 N/ d18
: ?6 n& ]) y# f* c196 @4 O' s2 r0 r) O1 H4 b4 V
每条记录时间的间隔显然并不一致,请解决如下问题:
) p3 ?6 H. V& n2 x找出间隔时间的前三个最大值所对应的三组时间戳。$ h* F5 g8 R4 H
# 第一次做错了,不是找三组时间戳+ u$ U5 d" H9 k# j. w
idxmax3=pd.Series(df.index).diff(1).sort_values(ascending=False).index[:3]
8 o' R+ Y1 t6 [2 }' v1 I2 S) V2 Tdf.reset_index().Data[idxmax3,idxmax3-1]
9 {. A+ e1 b2 \( U, l+ a8 u5 q
% V& k' j4 r/ V& P5 L) S# S25923 2016-12-08 11:10:427 \$ i+ R% s/ z! Y% T
24522 2016-12-01 00:00:02' ^7 Q1 @- b- O7 y3 F
7417 2016-10-01 00:00:199 o3 N6 [0 H3 f# |" w4 t0 Z9 X
Name: Data, dtype: datetime64[ns]# l8 o& m7 l8 j! P. c( q
1
, w+ |# I7 L3 a0 _20 m* G! i9 f) P; }6 f g. g6 r
3& Z4 H7 }3 i+ V
4% R& @% I7 U# C7 T' U
5
, L @) }( b; `' c% I, I. x$ Q5 e6
) q( R; `/ B& g. {* K7& o4 n8 A# D' j
8
6 B) a% B5 ]6 Hidxmax3=pd.Series(df.index).diff(1).sort_values(ascending=False).index[:3]
1 ~" |/ @* u8 x# L/ klist(zip(df.reset_index().Data[idxmax3],df.reset_index().Data[idxmax3-1]))
/ o/ G3 P, {0 l" r l) q
$ [+ Q3 [# a3 G% p8 o[(Timestamp('2016-12-08 11:10:42'), Timestamp('2016-12-05 20:45:53')),
7 f' G4 m% g1 o) `7 b! D$ w (Timestamp('2016-12-01 00:00:02'), Timestamp('2016-11-29 19:05:02')),, G% [8 _& g; V# H
(Timestamp('2016-10-01 00:00:19'), Timestamp('2016-09-29 23:55:26'))]1 Z/ v- S1 h8 t5 T/ r4 y6 f
1! @; g4 o1 ]% x3 T0 O
2
, s c2 E: F5 J) g* B/ v3
# `5 _( c' }5 F5 Z7 d4
2 l% ?* ^% ]- V" }% M- W5/ f9 M' ?' |9 K( \2 N e
6
1 W* j4 ~( u. {% Y1 J, R" Q参考答案:( ]% b' r0 |* r9 k( f/ @' j) g
# f. W3 ]. }/ b7 Y% f4 {; v9 R
s = df.index.to_series().reset_index(drop=True).diff().dt.total_seconds()
( u% U$ `" L3 \7 N3 | Rmax_3 = s.nlargest(3).index( @3 ^7 ^' x" h
df.index[max_3.union(max_3-1)]
& Y; Q& W. X; Y+ S% Q2 s6 _$ s: q
/ ]; d. n3 i' WOut[215]: % w- l8 [ U1 N0 p# i4 @
DatetimeIndex(['2016-09-29 23:55:26', '2016-10-01 00:00:19',% h/ K4 x, e# M3 @: h8 D+ a; s6 B( X) _
'2016-11-29 19:05:02', '2016-12-01 00:00:02',
' a0 Z3 W/ b' v6 i '2016-12-05 20:45:53', '2016-12-08 11:10:42'],
' i( B. t6 `' k$ A5 E' Q. G7 B3 Y dtype='datetime64[ns]', name='Datetime', freq=None)% V) z) C5 M/ \# J) n, t( ?) ?
1
" M- R" {; Y$ p9 u( X, p3 ~. d28 I2 h7 T# R, V3 j
3
8 n. R7 Z3 P! z( R- s4; ?. I! ?0 I/ C+ C, N6 v9 V% o
5
3 c' B# j- y# O2 B7 ^6
1 c( { O# u: s1 p76 z/ v1 L4 d1 x1 _
8
3 K: R9 K0 ?: w9$ N1 b/ b: L& @* x2 C5 I
是否存在一个大致的范围,使得绝大多数的间隔时间都落在这个区间中?如果存在,请对此范围内的样本间隔秒数画出柱状图,设置bins=50。
- w7 w& u: d V$ W" @# 将df的indexydiff做差,转为秒数后排序。再求几个分位数确定取值区间
+ V3 h% b1 _! R w1 A* d1 ?s=pd.Series(df.index).diff(1).dt.total_seconds().sort_values(ascending=False)
1 I* l" ^! ~, E% {s.quantile(0.9),s.quantile(0.95),s.quantile(0.99),s.quantile(0.01),s.quantile(0.03),s.quantile(0.05)
2 I; S1 q/ x$ ]& z
, @$ n- O5 s( S5 }& H(304.0, 309.0, 337.15999999999985, 285.0, 290.0, 292.0)9 f- `6 R( P- Q* F
1
) r4 d4 j1 m/ O' _: |2
! B% @( c) N: y3 }$ I8 X; f1 f! h3
* p3 o- h5 u; X; d, J- v4 }& X- \& p- L; |: x& x' r
5! y& l# K$ V/ S' z; ], W% t0 {
%pylab inline! j0 W. W5 V$ N9 ?% K
_ = plt.hist(ss[(s.values<337)&(s.values>285)],bins=50)
- J+ n" J" p8 f0 @/ j6 {& {plt.xlabel(' Timedelta')
6 Y- s8 @ e3 |8 `9 v& v1 ]plt.title(" Timedelta of solar"); v' }2 l$ r; _! G& j7 E+ z$ |4 B
1
* V2 G& I: j p: @6 J @" H2) p, H+ L' y' r( E) P! f
3
' B* f2 v: [) G' c6 c/ v4% L: U( ?% N. L- k+ q, F
* Q0 H4 o* F' m, H4 ?
) \5 F- U9 Q+ f0 c, G
求如下指标对应的Series:
+ ]+ p4 `( x# _温度与辐射量的6小时滑动相关系数
9 a- a8 b% Z4 q# [5 T" u以三点、九点、十五点、二十一点为分割,该观测所在时间区间的温度均值序列6 v' }9 ]0 S; b
每个观测6小时前的辐射量(一般而言不会恰好取到,此时取最近时间戳对应的辐射量)
+ F# V2 \! _; P. W) r% udf.Radiation.rolling('6H').corr(df.Temperature).tail()' {5 u; V; J* S4 f' Z# N9 w) d$ c7 q
" N8 v' S; R' z" ^% ] r8 _* `# L- |
Data
; [2 ?3 K: H7 F' D' {& \; n8 I8 ]2016-12-31 23:35:02 0.416187( z# I) N1 a# p* _8 Q- c. u
2016-12-31 23:40:01 0.416565% r# n% O2 K, S1 J9 s0 k5 k
2016-12-31 23:45:04 0.328574$ I9 f% ?+ w# \
2016-12-31 23:50:03 0.261883
0 z) k, M% L. ]& N2016-12-31 23:55:01 0.262406/ h% C9 k1 Q, c3 {5 @8 ]$ d4 r- n
dtype: float640 c* ?' J4 A$ U" g$ _5 ]
1' n2 U) c! b5 u) |* q5 I" q
2
+ d z6 ~1 K. b9 l. `6 y3
; X. J! K6 t% R4 y2 T4
o( s3 ^: E- ?0 T5
* @" N9 U' |( j6
" R5 R; ~! ^' C/ W! P) G7. v( E. j- J! s$ R4 J, A1 V/ _
8, j7 C5 y' q+ a( W
9! p* V* R5 f( [4 E- \
df['Temperature'].resample('6H',offset='3H').mean().head()# \, E" m- P( y- m' j; D0 `
1 m4 f+ I f/ n1 L7 ~- _" b6 YData
7 e4 P+ Q' p! g" X P: V8 h2016-08-31 21:00:00 51.218750( e9 V0 T& A& U s
2016-09-01 03:00:00 50.0333336 g3 m: W9 _ P" z5 d# j4 d
2016-09-01 09:00:00 59.379310
, N7 N& R3 s3 r2 m+ f R% i2016-09-01 15:00:00 57.984375
5 u3 t8 n2 F6 n* T: h* q2016-09-01 21:00:00 51.393939
7 K5 n. v8 c5 e0 D2 \Freq: 6H, Name: Temperature, dtype: float64
$ F# t. n4 I( d% l2 C9 W4 |+ z1
7 H7 j" n$ v0 d2 w( d3 ?2! y M6 q. L0 }6 ? e
39 j# c: c* `* z( y2 `) x6 f' I8 I
4
$ v6 N. E. j. x2 k6 t& J59 m7 j' ]5 F$ w6 {' g
6$ {9 G9 U5 M& O3 x( |8 `+ C$ u5 }
7
% H- T* Q% [% {( }, I# e' [8 S8
+ \1 o7 [7 N* l9 n- e8 l9
0 S) j2 R6 R! h% g最后一题参考答案:) Q' C, M0 `& m0 q& j
. |: W1 A2 H5 y' o2 M7 }, }# 非常慢
- o0 n3 n! o+ x- z1 s0 v1 Imy_dt = df.index.shift(freq='-6H')
6 U$ C& m; j* w& Oint_loc = [df.index.get_indexer([i], method='nearest') for i in my_dt]" S6 H) K- c: r) ], m
int_loc = np.array(int_loc).reshape(-1)
3 z8 [& R" O) D$ L& j* k) Eres = df.Radiation.iloc[int_loc]
+ z6 E y r& ~$ b% Y0 w Eres.index = df.index& F8 _7 h" L7 P7 d4 x3 v0 x
res.tail(3)
6 V% k) M( d* W1
- _. L \ t! D. `7 k2 E2
$ A4 W$ ]# C( c* Q( U7 l3
5 S, v4 v* Z2 H# W8 w _4! {6 I0 K( F) _' J+ I' a4 O
5
2 U, D/ y" c' Y9 w4 o6
' r) p8 y9 |& z% i/ a( N7. ^8 K# m$ ?# U
# 纸质版上介绍了merge_asof,性能差距可以达到3-4个数量级3 a8 c1 M% V# n9 e& M. w
target = pd.DataFrame(
$ |7 ~. G/ u+ Y- D, c: M6 c. p {3 i0 U7 Q: X, P( f. d |3 y( I
"Time": df.index.shift(freq='-6H'),. E; m9 W5 }" `% ^4 |0 r% t
"Datetime": df.index,
8 F# k, a$ ]8 Z% J7 O: c/ I }' P Z; y+ [: I x
)
$ V* c+ v# c* _' \2 {' {9 y$ r3 J1 o
res = pd.merge_asof(* y8 O9 @+ ?6 N' T ?
target,0 C4 C7 B" U$ t' |7 S
df.reset_index().rename(columns={"Datetime": "Time"}),) z& E( @7 |& b: o6 B0 h
left_on="Time",; O4 l" g# `0 w% n% R4 ]: @
right_on="Time",. B6 U4 o% Y2 |% R% ]$ D* F
direction="nearest"
* o" C- s5 P' V).set_index("Datetime").Radiation
4 m2 M. [4 t; c/ {7 K' I; v( w1 ^ i: X0 f S7 D# i7 W# q
res.tail(3)
' {& G! L+ b+ v1 L# {1 P& u6 SOut[224]:
6 X. x- _: T" C, w* rDatetime% ~' n) n% p! s% @- w* y$ `, H
2016-12-31 23:45:04 9.33
- e. Y1 [, V' t" `' e/ ?2016-12-31 23:50:03 8.49
+ K5 m( R& I. l2016-12-31 23:55:01 5.84
& u* \6 \' O7 s6 hName: Radiation, dtype: float64 ^! s" P" @8 T* S5 @, h2 m
+ z! X2 m5 J% F" @. o+ A16 |- @! ^! l. x4 \5 J2 g
2
$ G3 n; V9 L1 L8 o% n5 m T3 T& W$ a: B" y1 N- ~
4
4 q$ u) r! X/ T d) E8 V% X5
0 H8 ~8 {3 C0 ]1 y; n6
$ ]2 G8 I+ R4 B2 m3 `7
% ]4 n# m1 b2 _/ |, r. }8/ R; b9 T6 x( |- a0 ]. j
9' N7 u$ @* F* @6 C" H. D5 j( ]
10% H7 O/ A+ B2 G" O
11
' c+ H. {" N$ y4 w: N3 P12& B9 H' l' f; y( P
13 q v$ {& x( ]
147 n Y' T4 Q% |6 P0 G
15
3 _+ j6 ]/ g* w16) d( |# \9 ~: r
17
+ F, B X! \) c- s8 \" H18" e- Z# [3 p+ L
19
5 ~- N0 R" Y, ^, Z% K* N' Y20; z, H7 S& t1 ?: I
21# i( t/ r6 ?/ k. O2 O9 H* p# M
22
. N, [! p5 v# u23
" |1 i- B1 y) T, g' xEx2:水果销量数据集5 W* r/ E, h7 z2 v9 ^" K" V8 }' Y
现有一份2019年每日水果销量记录表:* d# J/ Q* v* C8 n1 a$ t! j
# h9 W+ ~& r. C" q
df = pd.read_csv('../data/fruit.csv')% @; q. ~ y( W
df.head(3)
3 L% u' m0 K5 O1 l! k$ z
% P- l5 H: f4 X$ f: n" ?Out[131]:
2 q( |" s- _ R' g' A3 X, m, O" m Date Fruit Sale9 P0 {" j% f9 b% \* c2 h7 e
0 2019-04-18 Peach 15. }+ b+ y! V( N
1 2019-12-29 Peach 157 r& w+ i$ |: C' }1 {
2 2019-06-05 Peach 19$ F5 x; j$ E+ J$ h
14 S( I+ ?( _0 p' v9 Z& O# H
26 a% Y% r' V; V
33 N2 A" N- s& ^- G
4
# @; A4 _4 P; @7 H6 }2 v5
X! ~5 u/ I, Z3 O$ a, f66 U# W) P: C/ D. `9 Y) {2 |
7) Y) p7 p/ r2 r4 u8 o
8( x B5 D- x+ Q) f. o2 f
统计如下指标:
- e- O! t# n3 z每月上半月(15号及之前)与下半月葡萄销量的比值
- f' A4 z+ j9 L! E# ^8 U4 r7 o每月最后一天的生梨销量总和
; L7 `* L z% g1 n# a" }! L4 j每月最后一天工作日的生梨销量总和, l: n" \5 k5 Z; S9 o0 L5 V
每月最后五天的苹果销量均值
: Z! `0 x' `% }/ i按月计算周一至周日各品种水果的平均记录条数,行索引外层为水果名称,内层为月份,列索引为星期。
" q* e o& r; w u0 ]% f; {! D* S按天计算向前10个工作日窗口的苹果销量均值序列,非工作日的值用上一个工作日的结果填充。5 \3 n, T, `( T: t& S/ G
import numpy as np- o2 }" K; b3 X8 p; _: f
import pandas as pd, _1 d$ u3 v( z8 i! s$ s6 g# X
17 l; J( y; Y/ L( V2 E/ ?0 C; M
2
z8 \% N0 P5 v9 i. D/ ~统计如下指标:
2 f# Z" n+ t& x4 i( Q1 Z每月上半月(15号及之前)与下半月葡萄销量的比值
, ^. O; z& G. l, k8 S每月最后一天的生梨销量总和
% t: Q' H$ D9 q每月最后一天工作日的生梨销量总和' v* P4 W U1 X0 S: c" ]% g7 |
每月最后五天的苹果销量均值/ H. C' q% O! p3 d
# 每月上半月(15号及之前)与下半月葡萄销量的比值! U9 m& S# r7 n* |% n) E7 F
df.Date=pd.to_datetime(df.Date)
3 Z; w) `% D1 Csale=df.query('Fruit == "Grape"').groupby([df.Date.dt.month,df.Date.dt.day<=15])['Sale'].sum()9 b6 F k, w, L
sale.columns=['Month','15Dayes','Sale'] # 为啥这么改没用啊! u( V9 e& s% O9 B0 Q9 ?
sale=pd.DataFrame(sale)6 e( B$ a7 _0 i( Q2 A$ R; @
sale=sale.unstack(1).rename_axis(index={'Date':'Month'},0 j5 s- {2 k; g2 q7 c
columns={'Date':'15Days'}).stack(1).reset_index() # unstack主要是两个索引都是Date无法直接重命名
5 i0 W" w2 ^! Esale.head() # 每个月上下半月的销量9 X& R! R2 ^& N
7 w" i( L8 J2 `% r Month 15Days Sale4 G1 B9 y8 w+ M6 I
0 1 False 105037 D9 ~6 W) P' j
1 1 True 12341
, i6 u' P" J: P) R2 2 False 10001
. A" j- y g) E3 2 True 10106& B; l' a! c9 P5 `# c# ?
4 3 False 12814
" a4 }7 y) N5 e$ W- l
9 `5 f& K5 N9 X# 使用自定义聚合函数,分组后每组就上半月和下半月两个值,根据索引位置判断求比值时的分子分母顺序
% [+ f2 R/ ^& `# b) J [7 M. D( usale.groupby(sale['Month'])['Sale'].agg(- k5 w" g5 M# K! a$ _ ]
lambda x: x.max()/x.min() if x.idxmax()>x.idxmin() else x.min()/x.max()): {. d3 g; \' _- c' L) y& _5 ^- v: P4 v
% V6 _/ K1 ?6 S/ k2 A9 `: TMonth
8 {# e! J9 v* \! M3 k/ u1 1.1749987 r+ {7 w- z$ Z6 k( L. |* ^
2 1.010499/ R1 b- `1 {5 R5 M8 S" ?7 E
3 0.776338
8 K5 m/ Y2 ?9 H+ l Z4 1.026345
6 M$ K" n9 C' r F, G: A5 0.900534
/ i- C5 }- P" L4 E a6 0.980136
1 s3 F% e, v& r8 [* ]( N! |+ _7 L6 @7 1.350960& R3 g+ C$ j5 X. P1 s. ]! z) E$ y
8 1.091584
2 W9 u: b; w6 `9 1.1165089 r; Y. j* z: R, w" ^+ ~8 L
10 1.0207842 `( T# ~3 D- G! V6 f
11 1.275911
9 w! J; |8 U7 f- O6 O12 0.989662& v2 J+ t( l$ C9 N- v; V3 v
Name: Sale, dtype: float64' x9 e# S+ b/ B* ]2 }( _' E) T# }
8 t7 [$ E! X3 O5 r, V15 {" t4 e& m; g4 i9 L
2
, {7 G) A" L+ _" D6 V$ b" V2 m3% N! i/ }" N* b" m* ]
4) E+ y [, c9 V
5
! Z/ I2 U2 D: ^" o3 u) Q6 ]) r% a2 \* m) y
7$ |* k! M: j. Q! v
8/ {0 z, m1 ]! e
9
- ]8 S7 W* W9 Y$ W10' c; w1 _& B; V" T
11, V, x$ q; ]5 @3 x3 `
12' f( d0 L/ _" i, |
131 y0 R+ N0 b" J1 E/ w& |
14: D) K+ ^( Y( r) Y8 e
15
" W6 _- `4 l0 c( G7 K0 m/ N16
4 Z% a1 l( K+ [17( Q8 f+ P# {9 G5 m" v
18
! v$ ]' n9 A' N# o/ w19
4 k. a+ ~8 x3 |6 e. c20
$ ^" F2 M+ b$ ]6 b+ L; Z21& Z/ J: N; b& ~0 P: y
22
+ X" C4 L& p o/ b& v$ s: `23
, K: W: }/ a) n2 C/ r6 p2 v8 a24
6 M/ }) u7 }- Y$ N) k, C5 @* D25$ E$ n, }! r2 t+ v* p* n8 `; j
26! P+ {* f! T M' r; f
27
6 \- ]9 i( Q2 c3 Y28$ f# x3 Z4 `) ]7 C4 o6 Y
29
' Z3 L7 `. i0 m2 t" l. G$ A: j* x30
5 N8 ], i9 b. }4 {31
5 n5 d# @, ^0 W5 q; n32
( i# K/ x4 Y3 H) L- e4 c& u% i336 q% \, A! k6 \, c( a
34
' \# x2 _, _! K4 |" P u# 每月最后一天的生梨销量总和
0 M8 [+ B j* d! }9 h; C% v! Pdf[df.Date.dt.is_month_end].loc[df.Fruit=='Pear'].groupby('Date')['Sale'].sum()
7 d3 T5 g( y1 U: O1 T: g7 K
7 {- B4 E- P& MDate
: X2 L7 U: a7 q5 \, R2019-01-31 8470 g- C: g8 F9 x: y d% I; z
2019-02-28 774
$ c/ l" s3 f: f. t2019-03-31 761
* Y- E x7 [, ^% ^2 Z# y2019-04-30 6481 P1 x% e# K. i1 `) l/ _
2019-05-31 616
. `( X5 O" s- M4 u6 r14 N F7 Z& F& _5 i/ Y; X" h& l
2
7 h! h8 U% y- n0 p* o7 D3 V# w' N2 O" N) `" ?4 r- _) h2 _
4' p( l& N1 `- G3 t, U) \$ ~0 ]
5
* w) x! A5 s- x! F; e' W6
5 V0 {# g6 |$ ~ ?: R4 I7
0 P7 l, c8 k; P; G4 i8
9 Y+ m& i3 E. I0 |/ t9
# @% _3 U3 G9 H b/ p, j# 每月最后一天工作日的生梨销量总和9 z' ^2 _4 V( a: f/ c5 ~
ls=df.Date+pd.offsets.BMonthEnd()
5 g8 ~! W$ t) Y. g, F- j0 u' ?my_filter=pd.to_datetime(ls.unique())4 w8 e% z9 C( z+ V
df[df.Date.isin(my_filter)].loc[df.Fruit=='Pear'].groupby('Date')['Sale'].sum()
2 w) ?4 Z- T) J" f" ~/ s
7 s# C# G0 U4 c* lDate
- i* S7 J( r! S2019-01-31 847- C1 T6 U$ q( Q2 h4 @: a
2019-02-28 774
O& r' m& i0 o: C3 y2019-03-29 510
/ B- [" W4 {/ s! }2019-04-30 648
, e3 R2 S( z' {" U& r) ]* }2019-05-31 616+ _, `/ Y% |3 o! h0 o, Z
1
2 O2 E1 |! U& ~% d2
/ U6 z+ |3 N% K: M. M/ J2 X3
' t3 l7 B" D# Y- [4
. V: ?4 V$ y* Y6 r5- x. S* \$ m6 {. @8 T! x
6
$ U" s: A& ?8 s) y7
9 s; B3 C u- z2 u! t: T8, j, D3 i. r1 q; F3 z Z6 v( h
9! D) w" V0 B% y# R/ b2 f; E P
10
4 s7 x! h# f7 d. X11
! a- @0 ^7 m4 E0 ~# 每月最后五天的苹果销量均值, L' Y8 W7 S5 V5 H
start, end = '2019-01-01', '2019-12-31'
/ f1 h4 \; W2 A' u- }end = pd.date_range(start, end, freq='M')
' ~! u5 E+ }# b6 Y/ vend=end.repeat(5) # 每月最后一天的日期列表,重复5次方便做差, ]& F } m V6 V
: d3 P* p4 N" Y, \td= pd.Series(pd.timedelta_range(start='0 days', periods=5),)- R8 r$ }& }+ w1 e0 V: H7 ~3 C6 C
td=pd.concat([td]*12) # 日期偏置,最后一天减去0-4天
* ?5 o5 h1 W/ Nend5=(end-td).reset_index(drop=True) # 每个月最后5天的列表
& I" Y/ u3 r1 p9 c0 L, |8 j8 b8 Y7 e% U; p$ O6 E. ]
apple5=df[df.Date.isin(end5)].query("Fruit == 'Apple'") # 每月最后五天苹果销量7 W9 e; o1 ~) @* [. z
apple5.groupby(apple5.Date.dt.month)['Sale'].mean().head()* p( r4 L P' q' \: G' t
8 f$ D5 y( X* l/ ^, e8 _& h7 q
Date( P, J+ i' E: p! d
1 65.313725
! m# x* D6 J( |4 j2 54.0615388 U- \5 W6 {! q6 i+ |
3 59.325581% a7 d; t+ n* B( r; h
4 65.795455
* z; E" ~7 u2 Q5 z5 57.4651160 H9 b7 k; h% Z" t a0 E% D8 E& K
) v1 L' }+ K/ V* ?1
) Q3 H3 N( Z! E2 k* n2; Z! D& T1 S$ p) ~! O* X. A* t
3
J; J% k3 Z; o; z, \" { ^& a4
) c& n6 z1 C/ h! m) h# H5$ O& M) \( c* t
6
. ~8 @/ d$ O% [0 l/ b7 A* {6 Z0 V7
! @9 v& p& i" G& m8
- _ a* K( |- I9
& \! i% I. t" t10: y6 H& c0 @* d
118 S: o% h# a/ C
12$ L4 ^% P& ~" r1 J8 _; H1 T0 O
13: `1 M0 C8 e; m3 j) |$ q
14& L, K- S! C5 H% d% C
15* `+ D9 }; c) ^) Y+ ~- D
16
. v2 {$ Z8 k Q K% V5 t4 f17+ L; U! u2 u5 v
18: i4 O3 F1 ` t8 j2 ]! _! g
# 参考答案:* `; v* }7 y, v2 Q t' Y
target_dt = df.drop_duplicates().groupby(df.Date.drop_duplicates(
9 x2 t/ X+ B6 S' U% T& q ).dt.month)['Date'].nlargest(5).reset_index(drop=True)
1 z- |( y2 ?) B. S/ Y! k' w9 i O7 P2 ^0 N1 ~$ I' g, v0 \6 n8 Y
res = df.set_index('Date').loc[target_dt].reset_index(9 z7 J, W7 n/ G4 T" ]
).query("Fruit == 'Apple'")7 b8 U, d: N3 j0 q
8 M+ ^8 d) d: E8 k" q1 a' fres = res.groupby(res.Date.dt.month)['Sale'].mean(
) b b$ W! C) @6 a3 r% j# I ).rename_axis('Month')6 g: q( r% i: Z: e. p; L
! h8 G8 \- Y- ?# M. J5 i& t, J
4 F! d9 m6 C* rres.head()* Y; e/ X/ D. `; X# S: k
Out[236]: 0 X9 E! l9 {% x" t5 L* m x
Month
) p. z& t! M9 M7 Q3 j1 _2 k1 65.313725; I$ Q |4 x5 A2 c( E1 u
2 54.061538/ O. k. }2 g& M% m( U& ~6 w
3 59.325581) \4 z2 v+ _4 V. E$ L4 O
4 65.795455
; ~# D3 h) A8 f9 `5 57.465116
/ ]& y& b( l+ f. C' D1 p" c# N' x4 {Name: Sale, dtype: float645 `9 [0 u% ] ^% C0 J
( E3 }/ d" Q6 s7 z- p& S- N1
# Y! f9 c% G1 N5 H: u# o2
5 G0 Z& B; w: n( D+ r+ Y31 f* y6 P( h+ f$ K, o7 s+ E
4
8 Y1 t1 x6 {' x# W% U; L5
: H1 _3 Z! ]% C& `' P) }6' i1 ^1 L! Z6 j) w
7: g9 {) G! d5 K, u
8
; M' P; f3 O7 N% `' m) l98 b: j. s# C: ]3 L4 C
10' d8 }4 }1 q5 r. D L6 ]
110 [) s4 k/ _1 j! D# m+ ^) z: F
122 q1 z, Y; g2 m" q; o
13
5 B- }5 u* u# f14
7 j% m0 S8 G! a) S15
# C0 k. _. u7 O) P16
8 M6 c4 ^: U {3 J* T7 T# I) h$ H6 g17, R) t4 {# U# [) r. }2 C8 D
18
# f8 {. t' q* ?2 W19
% D! P$ c4 z$ s20
8 ?/ a. Z1 j, Z9 s3 a按月计算周一至周日各品种水果的平均记录条数,行索引外层为水果名称,内层为月份,列索引为星期。
* n8 Z4 ]- Y9 c$ s) ~5 [ qresult=pd.DataFrame(df.groupby([df.Date.dt.month,df.Date.6 [, p/ c) ]# y6 \" e6 A
dt.dayofweek,df.Fruit])['Sale'].count()) # 分组统计
6 d% ~ R6 K$ ?( e% B3 v
3 M* C: y$ Z4 h/ tresult=result.unstack(1).rename_axis(index={'Date':'Month'},
+ e0 ^& C. R+ n: p* M columns={'Date':'Week'}) # 两个index名字都是Date,只能转一个到列,分开来改名字.& d% ]5 l, o, y+ L. T4 [. e
result=result.swaplevel(0,1,axis=0).droplevel(0,axis=1)
3 B* N9 y- H, _' t3 j( v8 vresult.head() # 索引名有空再改吧
% {, A* l/ e. Z; }$ }, X. W- q' {! F$ D; s; I; R
Week 0 1 2 3 4 5 6
, N$ p8 v1 j3 |# @# xFruit Month " n5 i% Z5 ^8 b. R9 c5 |
Apple 1 46 50 50 45 32 42 230 q2 N6 b$ N% ?- j- w
Banana 1 27 29 24 42 36 24 35
9 g* P' ]5 Q& n1 [# h4 L2 c) h6 JGrape 1 42 75 53 63 36 57 46
: G4 @% V: J' B+ d4 APeach 1 67 78 73 88 59 49 72
' l" @+ ~$ ^$ C! E9 t0 f& \: m) s) C5 \Pear 1 39 69 51 54 48 36 40 ?( l2 ?( E8 m, A) |( e; u i
1' S7 }3 D+ Y" P/ i% M, }7 b3 F' p
2
* U/ W% d; B9 j5 H' O3# w4 Z) s) i. B# k" `0 s4 s- ]
40 b; _( n4 R8 X$ u% V
5
# ~. ?3 t$ }6 h9 n, G. o61 z$ y6 W0 v/ f* k0 j5 U* g
7" N+ m5 i& l3 c0 }, S
8
* [) o9 Y) }8 j/ \* h. u g9
, P# u+ |) h$ S1 o+ z( [* w/ x10) w+ d* @. g/ B' g* S& ?
112 [2 _2 u; V1 b7 ]- q- Z0 L
12- F0 I0 B1 L9 \1 W& O
13
( J+ q6 n; v, p( ^ b1 q) v1 x3 ~14
3 u1 P& l$ X* O15# F# S, g" \. O6 k0 ^2 I" ]: r
按天计算向前10个工作日窗口的苹果销量均值序列,非工作日的值用上一个工作日的结果填充。% i( y3 V' M$ }& j8 S4 R, C
# 工作日苹果销量按日期排序
* u8 q+ Z" h8 m* C3 v! Bselect_bday=df[~df.Date.dt.dayofweek.isin([5,6])].query('Fruit=="Apple"').set_index('Date').sort_index()
& \/ [; M8 w2 i# G% d( X" j5 n8 K! Lselect_bday=select_bday.groupby(select_bday.index)['Sale'].sum() # 每天的销量汇总1 |$ l6 g+ O+ H1 M0 f, T Q9 x
select_bday.head()" @4 Z6 G$ }: f; y4 w3 ^* y
% y3 e9 z$ J9 _% A+ L. ?8 g( b
Date0 P+ B! w7 b! R$ F u) a# |
2019-01-01 189' r2 I! } L0 c* |5 X
2019-01-02 4820 i+ N1 u1 ^' K8 D c$ C$ ?: h1 a
2019-01-03 890
' s& l1 c- k- w( e0 I9 M1 R2019-01-04 550
' L# |) ^7 Y4 d0 I0 o2019-01-07 494
: X7 _# g8 e N/ E) j
5 n( u. t% {/ k" Q# 此时已经是工作日,正常滑窗。结果重设索引,对周末进行向后填充。- n# H; Z* \! c
select_bday.rolling('10D').mean().reindex(df.Date.unique()).sort_index().ffill().head()% ~6 y4 t, Y0 R2 |5 v
" L' G# q& j* J
Date
2 _0 K6 o* F) B/ d8 Y2 o% t: V7 U2019-01-01 189.000000
) f, Y. F+ c! e* S/ C2019-01-02 335.500000/ A1 B4 k& _- N; c/ G
2019-01-03 520.333333
E" ^* |9 l7 b7 ?- ^2019-01-04 527.750000
. D! N( \2 H; B$ c, d2019-01-05 527.750000 }, r, ]. f( c5 _- g$ e' e
+ }) [1 |* D, f2 h5 f5 ~: Q————————————————
/ F+ ^/ b& k; M4 ?2 r版权声明:本文为CSDN博主「神洛华」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。- k- \# j I' h1 Q
原文链接:https://blog.csdn.net/qq_56591814/article/details/126633913) U8 W L0 N5 e" B
* N$ N8 d* K" C
' k5 B8 ?. z V1 b/ N
|
zan
|