- 在线时间
- 1630 小时
- 最后登录
- 2024-1-29
- 注册时间
- 2017-5-16
- 听众数
- 82
- 收听数
- 1
- 能力
- 120 分
- 体力
- 564661 点
- 威望
- 12 点
- 阅读权限
- 255
- 积分
- 174621
- 相册
- 1
- 日志
- 0
- 记录
- 0
- 帖子
- 5313
- 主题
- 5273
- 精华
- 3
- 分享
- 0
- 好友
- 163
TA的每日心情 | 开心 2021-8-11 17:59 |
|---|
签到天数: 17 天 [LV.4]偶尔看看III 网络挑战赛参赛者 网络挑战赛参赛者 - 自我介绍
- 本人女,毕业于内蒙古科技大学,担任文职专业,毕业专业英语。
 群组: 2018美赛大象算法课程 群组: 2018美赛护航培训课程 群组: 2019年 数学中国站长建 群组: 2019年数据分析师课程 群组: 2018年大象老师国赛优 |
! p+ s/ s0 x( G, a6 Y
; M0 K) f- _" v# R, Q8 I
% _; e) ]" {* u文章目录3 R4 b" g/ { U' L, Y
第八章 文本数据
" H6 Q9 V8 ]0 m4 s. [" t8.1 str对象) N4 D6 Z1 ]; G- f1 U
8.1.1 str对象的设计意图
/ Q% t" o5 ?* t7 ~* {8.1.3 string类型
1 g7 K" i) B8 k5 I5 N8.2 正则表达式基础
# E3 T$ E: z9 ~" e; i8.2.1 . 一般字符的匹配9 |1 k+ V+ F0 \# M" f) B
8.2.2 元字符基础2 L( ]! ?! y2 {& Q6 L" f
8.2.3 简写字符集
% H R7 v/ H/ |4 L6 L; T8.3 文本处理的五类操作
- a9 L2 N. p$ F$ u8.3.1 `str.split `拆分" e! n4 L7 M/ H$ l2 V$ j! N
8.3.2 `str.join` 或 `str.cat `合并
8 {5 j; {- X/ b8.3.3 匹配
) C' X7 y0 U6 S( U$ X) C/ B4 X8.3.5 提取
$ S5 U% F4 A6 x3 X- H6 g8.4、常用字符串函数
% S8 Q2 ]) d# g4 e7 q$ {' S8.4.1 字母型函数
% ^8 {9 Y& i# j& t8.4.2 数值型函数
: L: b9 j# j$ Y9 l/ M( i8 s8.4.3 统计型函数
% F4 [1 V$ S0 h5 L; S8.4.4 格式型函数
" k/ y6 ]) q5 f" Z8.5 练习1 e, L) J; n0 g+ C. U4 n7 V! ?/ n2 }8 T
Ex1:房屋信息数据集
# k: X; D! x+ a0 n! e% jEx2:《权力的游戏》剧本数据集- B w. `$ ?2 q& `# P% d
第九章 分类数据
. f8 B0 f" M9 Z/ [5 e/ ]9.1 cat对象$ E; q7 }+ a; R" f0 |
9.1.1 cat对象的属性: n4 r' R1 X1 g1 C3 |
9.1.2 类别的增加、删除和修改
+ ?: n% h- t/ u; V# M% \4 }4 p9.2 有序分类
$ z0 N1 w. j/ z; ?9.2.1 序的建立
+ H! Q* N0 j9 F* @# ~. Q9.2.2 排序和比较6 ]7 }/ [6 M7 s2 X( c
9.3 区间类别
8 ]- H1 {2 k$ a* m9.3.1 利用cut和qcut进行区间构造, A+ R3 s! H# g+ M
9.3.2 一般区间的构造
, z' j& s; o* J" e% l" C: t9.3.3 区间的属性与方法 \! G7 J4 O6 w5 p$ d5 i$ `; z
9.4 练习+ ?4 u5 k- m' C
Ex1: 统计未出现的类别
( ~$ P! K3 ?# M# Q6 VEx2: 钻石数据集
) K! }$ N! ?& r5 {# q X2 q第十章 时序数据
; b' `. w6 z+ R0 q" q& k10.1 时序中的基本对象& M% u$ |/ I) }" U; \- Y" |( T6 A
10.2 时间戳( ^) _. x$ G, _2 q0 g' n2 j* I
10.2.1 Timestamp的构造与属性
( U Z& K! Q* l( u1 s10.2.2 Datetime序列的生成
6 ]% q) V6 J6 i$ E10.2.3 dt对象) w4 Z6 _! S1 I* G( K8 Y
10.2.4 时间戳的切片与索引: T# s" X- m/ {# E- Y. g. @' h( n& U
10.3 时间差( c" [: B5 ^8 ~
10.3.1 Timedelta的生成
1 k w# z! T# C% K8 P- j10.2.2 Timedelta的运算/ r# _' k* f) w" @) z
10.4 日期偏置
" [3 T+ c( C- s+ m& Q: z10.4.1 Offset对象
. W% Q; N( m1 y5 c; z10.4.2 偏置字符串
' Z0 l" l6 v) ]10.5、时序中的滑窗与分组
! t. [7 \+ L Z$ U( s5 q. |10.5.1 滑动窗口3 L) [3 Q8 Q$ a' ~" H) [
10.5.2 重采样2 u9 N/ r( L, `* l! X2 Q; q
10.6 练习
! g1 L. r4 H' z2 D* F3 g) D+ rEx1:太阳辐射数据集1 N) F3 ]7 e. {, V
Ex2:水果销量数据集6 x+ w, r" F1 ]! P5 g
课程资料《pandas数据处理与分析》、github地址、讲解视频、习题参考答案 、pandas官网: E: o+ W6 a6 v% Q/ D# S
传送门:
% a6 U4 R% X9 I4 p2 p7 |3 O5 Y! T4 ^( Y% k8 T9 s
datawhale8月组队学习《pandas数据处理与分析》(上)(基础、索引、分组)
; f8 o7 d% k% udatawhale8月组队学习《pandas数据处理与分析》(中)(变形、连接、缺失数据)3 k9 [$ {! W) O% y) c" ~' e' N8 @
第八章 文本数据
) I- M2 r# v7 u V8.1 str对象
% V: L" s4 T0 O4 ~0 p6 R, |8.1.1 str对象的设计意图, i$ I% T6 g* P. j
str 对象是定义在 Index 或 Series上的属性,专门用于处理每个元素的文本内容,其内部定义了大量方法,因此对一个序列进行文本处理,首先需要获取其 str 对象。在Python标准库中也有 str 模块,为了使用上的便利,在 pandas 的50个 str 对象方法中,有31个是和标准库中的 str 模块方法同名且功能一致,例如字母转为大写的操作:
* H, B6 `& Q6 J. {4 o$ e
6 t8 _+ x2 ^( J5 yvar = 'abcd'& ]5 Y7 p& I2 u* E
str.upper(var) # Python内置str模块. | m: x* n/ I( I
Out[4]: 'ABCD'
" Z& C+ y) `8 u6 F( E3 N' _& k) ?& k+ T x" Q6 k7 I- f9 J1 S
s = pd.Series(['abcd', 'efg', 'hi'])* D9 a; \" {& }* p5 A8 j3 ~; Y4 s( V
2 w' s9 B$ y; {% k# V
s.str" f2 ?+ L2 R: Y$ l
Out[6]: <pandas.core.strings.accessor.StringMethods at 0x2b796892d60>5 W2 w5 t8 J4 k, v9 k- b" _
6 S8 B% R1 G/ E! p% {" Ns.str.upper() # pandas中str对象上的upper方法- w" ?! o( V/ J) \$ |
Out[7]:
( N/ Z; w/ Q, {) F$ K0 ABCD
' Q" c4 T4 u0 l! y7 L# T( ~1 EFG
+ p8 \0 B4 U; m; y6 z2 K2 HI; T m5 p% m3 Y. T. G9 _8 m
dtype: object
i+ P/ C, [& Z- Y: K1
$ Z( v2 k" U; {( P. }# y21 x4 b8 N8 c u2 ?: l. C
3
- N, C1 ?, D' L( r! I& R" m4
) N" S$ R3 x& o% S* ]* q5$ ?9 {% J/ |: E9 ?: [
6* L; }0 e! t* p: _7 d! O
7
7 x1 K4 @+ m" |+ |) O8 i" J" f0 F8: _! \" n) r! \1 k- J" I# a
9. O6 J* p" R4 k, J: m
10% L' r0 W Z9 ]# E
113 {3 w9 t* R) ~
124 q1 y9 T" P6 P2 J t
138 { W' @6 U: g" w) c* @! C/ v+ r
14& j" B' D0 {+ W4 k$ r
15
6 s0 |( ^9 x$ M! Q8.1.2 []索引器" v* |, ]% m m) ]& K# S b% ^
对于 str 对象而言,可理解为其对字符串进行了序列化的操作,例如在一般的字符串中,通过 [] 可以取出某个位置的元素,同时也能通过切片得到子串。6 K7 F/ |$ ?5 a4 R6 y% Q1 q
pandas中过对 str 对象使用 [] 索引器,可以完成完全一致的功能,并且如果超出范围则返回缺失值:
6 P1 [5 @; o: I2 `
" u9 Y* N% m' W9 fs.str[0]7 |) \) G, ?' J. B b5 Y
Out[10]:
; }0 t1 N' r2 z4 R f) t; i0 a% j9 H$ r9 ?6 E) M# I3 v
1 e7 M8 C* A* |+ A
2 h
7 X' w3 T& F( \1 u( Vdtype: object
; M& T5 K/ p( A! U7 l5 w+ c% ^. X2 B( t" R! U( W
s.str[-1: 0: -2]
' c' s4 K: |8 ?% q4 P$ ^Out[11]:
7 i4 n) d% l* k8 r) y. Q0 db
/ @# s) e1 O5 N5 \' q4 `1 G1 g
9 e6 Q# E0 g/ n; |3 d+ ^$ z2 i7 Q, e3 F% U/ z0 v- X
dtype: object
7 r8 R* d& ] k$ d& {. K
9 ]) W* d( D0 Q0 s) ws.str[2]
+ ]) F( d, h9 C! m- u7 x" i* pOut[12]: 4 L V# J: F/ r. ~4 `
0 c
5 Z( B' [' w8 l/ V9 v1 g$ W7 A; Y+ W& P# m/ v7 D
2 NaN: e- n3 ~5 ?2 Y4 ], d j
dtype: object
! n1 o# _- ^! K
# p: M ]; B/ E1' ?" o5 V# n6 A
2
# F4 v& O+ {4 Q# v r3 K0 f3 ] j3
2 M" G% X4 S2 A/ _$ V4
}8 C y3 F/ k2 J) j5) M/ _* R* v8 E
6
5 o7 s+ Z, G0 Y4 s% c P7
3 G; o3 D- @/ O8$ n) C0 b( M4 w
9
; o+ \. Q8 a' n5 Z1 u$ B. A10
0 S' {6 Z, c h4 Z, N% D11( Z! g( c4 _- ~; x. {% E$ Y
12
$ Z4 G" F+ S) p; ]5 r13
' h; d" Q: ?) ?; H9 L8 H; [5 l14
; b9 o% q# Z' I, }9 n. c- ^, w15
! C# K* d6 E4 j3 g9 |) Z4 F' x/ ?, ?7 A# X16
- R5 V0 z" q! K7 p) z) I$ X/ K17: B; Q7 ?# H$ y9 P# A- O
18# }3 m( \, K7 X- g
19* B% ]& u. ~5 D- u8 Y* ~$ ~ @1 c5 R' ]
206 A0 P8 q/ u5 }# |" [: h
import numpy as np5 k3 `( h9 l$ ?0 `
import pandas as pd$ X6 b5 \+ Q$ u+ L' x; ]7 q0 |
) Z5 q; m( E2 U0 c) K" w- n
s = pd.Series(['abcd', 'efg', 'hi'])
. ~& k0 @$ a `5 ?6 z1 @3 T8 Gs.str[0]* v1 X9 q$ x0 Z8 V3 G# ]9 }/ g
1
- v1 I- X& F0 g4 ^1 n2
8 q7 o' W5 ?% H3
- @3 h" P9 D$ y f% ^4
1 e7 e9 o+ N( {- {; k3 `5
* S2 }2 y; d% U2 C: t6 B- B0 R0 a" o% B. [) @0 L4 }& x: \8 n$ t. y. L
1 e8 z/ y! B9 ]9 a2 Z
2 h L# |- O) L9 r e5 T
dtype: object# R- P! `- b- N2 r
12 g, }% g1 ^1 d8 u
2% w$ E$ ^; Q6 o2 V& H
3% {/ B! [1 ?, `
4
' K: p# w5 m d6 m' ~- R! @, D8.1.3 string类型
3 d/ O, q* @4 \$ T2 @. K. _ 在上一章提到,从 pandas 的 1.0.0 版本开始,引入了 string 类型,其引入的动机在于:原来所有的字符串类型都会以 object 类型的 Series 进行存储,但 object 类型只应当存储混合类型,例如同时存储浮点、字符串、字典、列表、自定义类型等,因此字符串有必要同数值型或 category 一样,具有自己的数据存储类型,从而引入了 string 类型。
! y* x9 H9 q- b 总体上说,绝大多数对于 object 和 string 类型的序列使用 str 对象方法产生的结果是一致,但是在下面提到的两点上有较大差异:/ \' U! Z) ]- j0 v0 i: b) n7 X( U
& q% @: q) k6 u4 m
二者对于某些对象的 str 序列化方法不同。
' ~+ y3 h1 y; B8 ^2 w/ B( K6 M; \可迭代(Iterable)对象包括但不限于字符串、字典、列表。对于一个可迭代对象, string 类型和 object 类型对它们的序列化方式不同,序列化后str对象返回结果也可能不同。例如:
* O6 Q2 E* y& `( I o% w7 ds = pd.Series([{1: 'temp_1', 2: 'temp_2'}, ['a', 'b'], 0.5, 'my_string'])
2 Q+ q. H1 t$ v* q: H! V; k5 ^s
6 o/ h! l( O' A1 g+ ~- B5 t9 z# r' K- _
2" \- R, ~4 r/ z" w+ j+ t2 u
0 {1: 'temp_1', 2: 'temp_2'}
6 g* S1 @' w2 K1 _2 r1 [a, b]5 `! k$ k/ K( t [1 z) c
2 0.57 D( w: ^ P/ @% t9 s: ?2 S+ w
3 my_string% J0 ^1 I5 ]8 a3 Y
dtype: object! S! l K" i# H
1
+ L% I. D8 v; q' m( U% j4 t2
- P" k, P) q _) C2 v5 ?' X3( E8 k3 G6 V* |2 D8 e
41 i2 ]% t7 l, }; f
5: g8 @3 b7 m" p- D+ D" G7 `
s.str[1] # 对每个元素取[1]的操作
* Z$ W2 v) Q. o1 C) _2 g1
( g% L$ c9 l* Y! B) w% _2 o0 temp_18 ^ E* |. n0 X( m q
1 b. `+ c! }0 |" l. R
2 NaN
. X% Y' K6 h" v$ ~2 A* h: m3 y% z0 s3 H" |& _! n+ a; R% A; v
dtype: object
0 ^3 `! V$ r; ~1 r4 D; x( g1, z: g9 X6 Z( }3 x6 c1 Z# }4 o9 s
2
4 [5 l' S5 C% f! b3
( t9 I* Y% G% _3 P& f* x4 ]& @4 u4
8 P$ B$ B8 M( l1 @! u7 B5
/ R( C1 {! Y2 J: L+ Y U, Vs.astype('string').str[1]
" E- H6 j+ ]1 u6 K% w9 t0 i- z+ W( P( o' U1! C) _9 D4 }2 t* ]# y) G
0 1
' H6 `7 j1 \1 l0 ^1 '6 b( k8 H# Z1 ^5 l( e' w# X0 V. T
2 .
2 f- S5 v/ M* j3 y1 @( N1 n4 C# F; y
dtype: string
. T, M* Y/ `( @7 f& ]: {5 p1
! C: S2 L- L; q: E% h; H0 i2
. w% ^7 q( M1 x9 P8 h8 M/ O3
) x. ^% D2 Z. S9 C3 d4
: d6 t2 Y3 z6 O. v0 a6 x9 D' {5# A* m& k# s' }4 {) K7 |0 G
除了最后一个字符串元素,前三个元素返回的值都不同,其原因在于:
- y* P$ O7 E! a* ]$ S
" i3 h# u$ h1 ]! q3 q当序列类型为 object 时,是对于每一个元素进行 [] 索引,因此对于字典而言,返回temp_1字符串,对于列表则返回第二个值,而第三个为不可迭代对象,返回缺失值,第四个是对字符串进行 [] 索引。; W2 ]; b: I/ P8 V- v6 x% ^8 D I6 m
string 类型的 str 对象先把整个元素转为字面意义的字符串,例如对于列表而言,第一个元素即 “{”,而对于最后一个字符串元素而言,恰好转化前后的表示方法一致,因此结果和 object 类型一致。
1 }5 I( x8 l# G3 n* Mstring 类型是 Nullable 类型,但 object 不是7 ^; d- X; j+ z- _ @2 W/ R
这意味着 string 类型的序列,如果调用的 str 方法返回值为整数 Series 和布尔 Series 时,其分别对应的 dtype 是 Int 和 boolean 的 Nullable 类型,而 object 类型则会分别返回 int/float 和 bool/object ,不过这取决于缺失值的存在与否。% B1 y: P: E# X9 ?& M/ j, d
同时,字符串的比较操作,也具有相似的特性, string 返回 Nullable 类型,但 object 不会。, v. X6 K+ T6 T- \1 c7 w
s = pd.Series(['a'])
2 w0 O" c5 p' y
# c9 P. i8 e5 S, {4 q' _s.str.len()
6 a: O7 }" G! m2 f3 s3 F, ~/ ^Out[17]: ; w# i- G) c* m. }- n
0 1
8 E, \/ o' l& ]$ o9 Wdtype: int64) q/ I+ J" Z" c( F4 e
; q4 r e0 b( |7 Y5 }
s.astype('string').str.len()" B2 t% X- r, ^/ x( @6 n+ \3 _
Out[18]:
/ @9 _0 z$ e) d; }# u0 1
/ Q" n1 }4 K2 t4 Mdtype: Int64/ f' f( g4 U2 `, W ?3 @2 O7 j
0 o; n! M; d+ i5 N1 t1 l, S
s == 'a'
0 ^- ]1 o5 c* {$ kOut[19]:
( p" L# B$ ?! r0 True9 z5 I) w/ A3 u) r
dtype: bool+ D c( J; n8 [1 U3 a. V1 g
, l! f: s7 H7 f. W3 l
s.astype('string') == 'a'% R3 O) ~" d$ I1 r8 p$ V8 l) P
Out[20]: 0 ]- j3 l) F6 }' y N) z
0 True
. ]1 c! X4 B: e* Idtype: boolean
, v# p: l- P5 M, m) Q" L) P' g) e! P6 w
s = pd.Series(['a', np.nan]) # 带有缺失值! l. P' O$ _& y1 c$ i: g @
, W& I c0 k3 R0 W+ Qs.str.len()
* q, r {* O% v6 s; Z# J% ROut[22]: 8 y+ l% [3 M+ t
0 1.0$ O7 ] R9 x$ Y
1 NaN
2 o: ?* T$ A% O3 k& qdtype: float645 V8 K+ S8 G! L4 F# F0 P
O8 O8 S/ c$ s: q0 ^s.astype('string').str.len()
; F# D7 A1 ^* i9 c, y. {Out[23]:
) ?2 g1 \# l7 Z' B0 m) x: g5 ^0 1
N. O7 n; b" L# e) A9 E1 Z1 <NA>% K2 N6 e" w0 s) V
dtype: Int64' p9 [# @+ u! S7 m
) Q7 n# n! O: Ks == 'a'
g( T; R2 _7 O7 e: y+ |/ c! qOut[24]:
# b I3 E: C+ D! I# j Q0 True
& b p0 ~8 S% k4 j1 False9 Q+ ]$ N; E$ f5 h2 f! q M% ]2 |* n
dtype: bool
+ [# f! S' k& X9 H- L& L# F$ u( a3 y9 [7 j0 C
s.astype('string') == 'a'5 M! B, a; t7 e2 Q/ v3 N
Out[25]:
' @; o+ q+ d7 M$ z7 w* j0 True/ A# {- G6 U9 T8 \ e6 p$ W* s
1 <NA>
' U- o4 q# D( `, {( A2 Gdtype: boolean) L4 z% P2 w4 @3 L- k% ~
. N4 N% s* V% k3 @1" D. E' E3 k- i( j
2$ P4 |/ q- F. |6 _0 a
3
5 f0 J: \1 E/ o' {4, {( C- E" z+ A; D ~1 A
5
4 }6 K, g2 c3 R6
; M" f; j2 p: ]- {+ J; R7) ]5 Z+ q! p( G
8
$ }% {! I, @5 R, c9
: ]+ d0 u8 i5 v3 A; j. F: @+ F0 F# C104 M6 j) [2 H/ d2 Q# Y
11
. Y. b/ N" g) o0 g12
( z9 p# Y) v. w4 O13
|- A M2 n1 Z+ L) I14
9 p& Q9 o7 m3 I4 Z% ]3 u, G15
. `7 |1 ?" f4 B& I) r9 ^: z1 K$ ]% i160 [$ U4 s! Y+ Q4 m: @+ `6 |
175 C9 p3 P; X, I# D3 l8 i' M
18
4 k" J2 r/ m% r$ A) O7 D! }' |; ~19) H6 I3 q8 y9 K- o( y% ~
20' h1 S+ w+ J3 H5 `! U
21- ?. Z4 w6 g" Y; p
22
. @' n! g$ Q r% v. L23
. y- k) T0 o: c8 g* x24+ L3 u8 p2 G% I `
25
( h8 V' O$ f9 ^: X$ k% k+ @26
" ^# h8 s) _/ _27
4 D; a8 e/ \$ N28: Q5 W9 v6 v. @* V
29
. u: H2 i- {/ O3 ~- x& b0 j30
Y1 G5 A5 f, G+ b+ Y; l. C31& a4 A7 }$ V0 L6 _& ]( ^7 y3 ~
329 [0 ]! \- V" K/ k: `6 Z
338 l! Y2 j# { I+ m( m- t
34# @" g3 ?; K+ a; @8 Q
35
8 Q7 R1 F6 H @# C4 u4 b4 ?) x362 n$ P" g; D& k% H2 c+ S8 |
37' L' `. H! A& [) |
38. j1 b" V. j# A6 r2 o$ e y$ g; \
398 G+ H, z$ w; B; \) S0 @: }9 {
40$ ~% k% b8 D2 A3 j. m
41% i: T/ t/ [( m' G" ?9 J
42
, m k. n7 O+ [% U K" e: S" O5 G43! ?; D8 U/ G( ]: ^
44: E5 }: r- `: V7 l3 f
45
4 I3 m* W; o$ i2 j" \5 b" C46/ W$ K; P& ?6 _4 a7 {
473 Y+ d, p. p% r0 k8 p& r( x; Z- q
对于全体元素为数值类型的序列,即使其类型为 object 或者 category 也不允许直接使用 str 属性。如果需要把数字当成 string 类型处理,可以使用 astype 强制转换为 string 类型的 Series :
& E' {# w! ]( d! n9 V& z
9 r: f5 Q1 c. }5 g' T, r/ V. r M, _s = pd.Series([12, 345, 6789])8 [; m5 ~) D8 X7 u2 S f* {& h
: {8 _6 P, {5 c4 ]+ b0 M7 }2 Q7 as.astype('string').str[1]
- s% ]0 P* \5 ]# e# vOut[27]:
6 T- ?0 y+ T6 M4 n) v: f' f0 23 y' d& r3 E. i" K4 P6 B- d% n
1 4
& O! g" o- g. B6 K! V( C- Y2 78 Q. @! b" \+ ~2 [/ R8 p
dtype: string
, x0 S; L# F) [1 o- M! h1
2 k7 x6 O& L+ J" k9 `2& x) B5 J, A; ~8 n3 H7 L* r
3
! k# G! Y: L4 [$ W j4
" \; u% C# e4 c* C, W! R6 @57 ?' E+ A: @+ M6 o5 {4 H2 c) B5 M
6
" }' ^ I! f; q5 y4 V7
& m3 u; }" U2 n; ], h/ R8
6 Y6 z; }1 \& V3 R: R8 V3 @8.2 正则表达式基础4 D# A# N2 r/ @. \9 W- v3 j0 s
这一节的两个表格来自于 learn-regex-zh 这个关于正则表达式项目,其使用 MIT 开源许可协议。这里只是介绍正则表达式的基本用法,需要系统学习的读者可参考《Python3 正则表达式》,或者《 正则表达式必知必会 》这本书* r% b) Q a, f
3 |5 t; \( Z( h7 [" D' J2 }
8.2.1 . 一般字符的匹配" I: a6 p2 S: Z( h, j; e' w
正则表达式是一种按照某种正则模式,从左到右匹配字符串中内容的一种工具。对于一般的字符而言,它可以找到其所在的位置,这里为了演示便利,使用了 python 中 re 模块的 findall 函数来匹配所有出现过但不重叠的模式,第一个参数是正则表达式,第二个参数是待匹配的字符串。例如,在下面的字符串中找出 apple :, I6 U, C- S6 t
. F) l: o8 y+ r8 Pimport re" T @ p+ u: O( d
+ Q# h, r+ i. u
re.findall(r'Apple', 'Apple! This Is an Apple!') # 字符串从左到右依次匹配6 w2 |0 Q7 l& K9 E
Out[29]: ['Apple', 'Apple']' i' ?5 }9 M# I: i( w' o
1
?1 y+ z. ]+ b J2 c( H2* p$ N$ ?3 H o c
3
$ ]! n: J' l7 o$ N4
9 [1 W- W4 J, {" |4 u! @8.2.2 元字符基础
% }% X0 ?& B- E9 x; n6 P6 P元字符 描述
: |% [; i- a# X: M0 x) u C. 匹配除换行符以外的任意字符
# X5 \* p. |& g) L[ ] 字符类,匹配方括号中包含的任意字符
3 }8 f& N1 }3 X; Q! A( }& X) p[^ ] 否定字符类,匹配方括号中不包含的任意字符; H1 w# Q6 a* `! x& i1 T% \; l
* 匹配前面的子表达式零次或多次6 t6 B1 X) v) E& t; A, B. ^/ }$ W
+ 匹配前面的子表达式一次或多次。比如r’d+'就是匹配数字串,r’d’就是匹配单个数字
8 o* V8 |0 @" [- L8 G/ y/ q? 匹配前面的子表达式零次或一次,非贪婪方式& I; g, j7 F! K; _8 b5 X
{n,m} 花括号,匹配 n 到 m 次由前面的正则表达式定义的片段,贪婪方式* c8 o* f% X) X: l, d$ `- n e
(xyz) 字符组,按照确切的顺序匹配字符xyz; Z! q# N/ g, U: Z9 }3 n
| 分支结构,匹配符号之前的字符或后面的字符& g' R; D: i% t
\ 转义符,它可以还原元字符原来的含义: b. k; h8 U3 R" l0 o5 z" {& F3 U! s
^ 匹配行的开始
# |& d* X7 E8 L9 i! ]$ 匹配行的结束+ E& t, w& q9 Y) y: }* b
import re
( p0 M& r6 i6 R6 Vre.findall(r'.', 'abc')
- z, f( }+ M& p: N w6 [# A2 U& B7 z- QOut[30]: ['a', 'b', 'c']; q0 |$ N7 W9 O( a
' P/ U' a7 |' l( {- u$ u. xre.findall(r'[ac]', 'abc') # []中有的子串都匹配
7 q; J9 v' d% k/ |% lOut[31]: ['a', 'c']
& s9 r% b, I4 h/ R. F& l" z/ o1 o! M0 o( U) {
re.findall(r'[^ac]', 'abc')
0 K8 h% _1 N0 @5 BOut[32]: ['b']
: u! \7 Z) D9 k. ~, s& ^0 v# e( K/ q% P$ V/ ~% j" y
re.findall(r'[ab]{2}', 'aaaabbbb') # {n}指匹配n次
/ |" ]" a% Z7 cOut[33]: ['aa', 'aa', 'bb', 'bb']
1 _8 Z1 @. X: N9 g$ L6 K6 N5 \% ]8 c/ k% R& n% K+ B- A7 J( ^1 ?1 ~
re.findall(r'aaa|bbc|ca', 'aacabbcbbc') # 匹配前面的或者后面的字符串3 A$ M6 r; k3 E( g/ }
Out[34]: ['ca', 'bbc', 'bbc']7 e1 v j" M W' M. ^3 O* F
+ M: `6 r r2 |4 Q0 S# 上面的元字符都有特殊含义,要匹配其本来的意思就得用\进行转义。: ?- \9 N" P. a* j
"""
/ P) J4 G6 ?+ y4 k* o4 t1. ?匹配的是前一个字符,即被转义的\,所以|前面的内容就是匹配a\或者a,但是结果里面没有a\,相当于只能匹配a。
% V$ u, M6 d' p- M, u( _2. |右边是a\*,转义之后匹配a*,对于竖线而言左边优先级高于右边
; y i( ^! c+ k v( Y3. 然后看目标字符串aa?a*a,第一个a匹配左边,第二个a匹配左边,第三个a虽然后面有*,
5 R5 y( l; G: @; d0 L0 ?但是左边优先级高, 还是匹配左边,剩下一个a还是左边,所以结果是四个a
1 n( ]+ Z3 @; e. N7 Q' _7 n"""7 v+ c) O' p% T8 V
f! e, T3 L$ h7 g8 k' E, K& Nre.findall(r'a\\?|a\*', 'aa?a*a') # 第二次先匹配到a,就不会匹配a?。a*同理。
: m- O/ H! L9 u1 F$ F* zOut[35]: ['a', 'a', 'a', 'a']
% `+ }5 q: ~9 S
: V$ ?8 `/ _7 D7 @& d' i/ R; b- k# 这里匹配不到是因为目标串'aa\a*a'中,\a是python的转义字符(\a\b\t\n等),所以匹配不到。8 w. u2 t! a7 o
# 如果是'aa\s*a'之内非python的转义字符,或者'aa\\s*a',或者r'aa\\s*a'就可以匹配到\字符。6 s# ]2 r" f# |) |# _% n: [
re.findall(r'\\', 'aa\a*a')
! W: N8 s7 u) Y) d. k[]
7 V. I4 Z1 \* f) C8 A) ]# Q* i. ?
9 V \" D: a% p Wre.findall(r'a?.', 'abaacadaae')0 l) Z6 Z8 f; l$ n' K$ Z
Out[36]: ['ab', 'aa', 'c', 'ad', 'aa', 'e']/ s2 k" Z1 f: ^( W3 ]3 z
- k& {/ v# N8 S* [' N$ x4 ]9 Qre.findall(r'(\w+)=(\d+)', 'set width=20 and height=10') # 多个匹配模式,返回元组列表" y' [' ` e+ O, f* s- {/ q
[('width', '20'), ('height', '10')]* c! J; G, j$ W& l1 m3 x6 C0 D
0 ^2 `, v0 p9 z% V
1
6 S: ^ e' L( a2
2 [9 G% H$ l, `3/ A, B. y+ ~7 ^# ^
4
; F: s1 P* x5 a50 l8 f2 x8 l$ H0 x0 S8 |
6
b0 W' K# X( J3 K) [7 E7
$ ]' a# s* q% Z9 A& ~6 g87 d" L. H; K! A
9
! R m5 J* x8 C+ V) B* l10
' @1 g5 q n+ `4 {11
! P' E/ ^4 L! t8 M0 `12% E: w+ X1 ~4 ~% P" k; f) q
13# k- J9 h) P' a/ X
14 U$ x1 p5 |! y6 ^: D8 n) `
15& r1 v+ p7 k% K1 y6 }8 H
16 P7 Q7 I- r6 I7 x
17# i1 K. c* B. d; | i% S3 Z# z
18
1 x; l' y# ?) L- p19! E" e& {3 ~3 t# R/ A
20
: l" h8 p, C, m7 x( i21
( X7 `" P: }: Y22
% Q; r! h1 p3 `/ f( p23 `: [# a; C t7 o; q0 x9 \' e7 C6 i
24" k& @3 {* d/ ?6 k% p" W K
25. u# t( }- u. Z, z3 f
26, o; z! n# z9 y! K! [5 ?8 |
27 w: b& R' k9 h
28
& c; s# d/ A( K H& `- l29
7 v3 w" Z" f, p( s. a5 _30
, J+ u7 I V. t' S; \- H7 Z, S# X: j311 K* ]1 ]4 k* _( j
32
* ~4 P) z4 ?3 Q5 {, i& g% q1 ?331 _# S3 h7 D" H3 ?& v0 I
347 b5 E+ E/ H+ U4 H' ]: e( A. E% C6 x" W
352 l/ t2 j% P p; F; P
36
/ K h0 c/ g" q: k9 s1 s w4 K+ F37
% D5 c+ I9 O4 @8 ]' G9 g8.2.3 简写字符集8 }$ H0 o* c+ s0 ~! V( ^2 X9 W
则表达式中还有一类简写字符集,其等价于一组字符的集合:
* {& R/ ~8 E! Q9 P' h
8 J& S& ~! w7 a2 ~" ~5 Y简写 描述' ]4 \! V: _# l
\w 匹配所有字母、数字、下划线: [a-zA-Z0-9_]
1 k# B* p) `/ ?2 _) l6 v1 h5 W\W 匹配非字母和数字的字符: [^\w]: v5 k v# x8 \6 O* K" N3 k
\d 匹配数字: [0-9]$ r5 @* I9 J: U/ `" d; |+ O
\D 匹配非数字: [^\d]
& v- U: L9 q' M/ X) f# Y\s 匹配空格符: [\t\n\f\r\p{Z}]& @1 O; T3 n2 J# M
\S 匹配非空格符: [^\s]+ e) {; C! \" ^- `: ?6 Y, `
\B 匹配非单词边界。‘er\B’ 能匹配 “verb” 中的 ‘er’,但不能匹配 “never” 中的 ‘er’。
4 i7 d& F7 y: w6 V8 E$ q5 Wre.findall(r'.s', 'Apple! This Is an Apple!')
. w. v4 M8 ?" x3 j# O F! _Out[37]: ['is', 'Is']' T$ ?- N+ {: I; T
% Y" G% z& R8 S% B- sre.findall(r'\w{2}', '09 8? 7w c_ 9q p@') # 匹配任意数字字母下划线的组合,但必须是两次( x, `% }& x' I8 i& `* C* ~/ o
Out[38]: ['09', '7w', 'c_', '9q']
) q. _. ?$ `; }. p7 f1 I3 W$ p3 O8 Y! T- o& T% W
re.findall(r'\w\W\B', '09 8? 7w c_ 9q p@') # 匹配的是两个字符串,前一个是任意数字字母下划线(\W),后一个不是(\W). R3 S1 \/ F' n
Out[39]: ['8?', 'p@']% |" y n/ y, _3 t+ _$ C, f( e
0 H& \1 n1 F3 y a I+ C* \" I
re.findall(r'.\s.', 'Constant dropping wears the stone.')4 e" C& V$ G4 \' u/ ]
Out[40]: ['t d', 'g w', 's t', 'e s']
9 N; b' J* D( w% M3 F. W* a0 @6 g/ m
re.findall(r'上海市(.{2,3}区)(.{2,3}路)(\d+号)',0 P5 k3 @. }# {$ _
'上海市黄浦区方浜中路249号 上海市宝山区密山路5号')5 o1 d9 U* ?, T" p5 ?- f; m
, r5 d) c2 ^$ o/ d1 w$ u
Out[41]: [('黄浦区', '方浜中路', '249号'), ('宝山区', '密山路', '5号')]
# U/ F% }, G' f
4 h$ F: T4 R( [1
/ p) s5 R0 X$ g% C% }2
. s0 U1 L7 L* }0 C3 b, G* }* }3* x- H0 S& F4 C! ^( o
4- _. f1 s7 O: [- i4 Q% H0 W0 ~
5# u* K! Y& i# Y+ d2 a
6
6 H1 t5 f+ F9 q7 d7" F* I* D" Z" G" L( y) Q
8
+ n3 A" t: t4 r& a9
/ |& W F. V) i: O L# ^102 }% ]7 L' }' U5 Q0 K
113 G# H& ?6 i: X9 y$ O
12! B8 G& {! `/ j+ @+ f- e% {
130 }( ?5 ~* S5 J. g/ ~# L( f8 ]. n
14+ }0 c3 v6 W1 k, ]
15
6 m8 r, H- W4 l% \# Y0 {9 t- _16
7 S, @( p& _9 i+ J8.3 文本处理的五类操作
Z% P+ [3 r2 v3 u8.3.1 str.split 拆分: S) }; k* m: {: Q- G$ }% Y! r
str.split 能够把字符串的列进行拆分,其中第一个参数为正则表达式,可选参数包括从左到右的最大拆分次数 n ,是否展开为多个列 expand 。8 ~9 r# j% ` ?" t* a4 L! J& s
* s* E0 X9 o/ Q' t3 Fs = pd.Series(['上海市黄浦区方浜中路249号',1 ~8 n6 ]% t i
'上海市宝山区密山路5号'])3 u0 K' [% I$ V
0 r \) z$ G! \
$ q8 g1 o" X9 P: i+ Q. C, Z2 S0 gs.str.split('[市区路]') # 每条结果为一行,相当于Series ~( c q9 m7 t V. t1 {
Out[43]:
6 H% q1 @0 t4 o( V6 k* \4 @0 [上海, 黄浦, 方浜中, 249号]
. Z# p% X6 k0 v! x! |6 a9 d1 [上海, 宝山, 密山, 5号]
w3 u$ S# q, l$ n* qdtype: object6 i5 }: w! m% ^1 R; l4 b
* @3 n. }3 A% [0 is.str.split('[市区路]', n=2, expand=True) # 结果分成多个列展示,结果相当于DataFrame0 w+ ^( `" f9 v1 b: F4 }
Out[44]: " @( }4 F9 _ q; I
0 1 2! U4 B4 i) }- W q# P* T1 s1 K% | ?
0 上海 黄浦 方浜中路249号
q/ U7 n* f" Q" w! |- R# T3 r3 w/ R9 o1 上海 宝山 密山路5号0 w; p, {/ V' i3 E3 j1 Y5 f" A
1! p* T5 o5 Q1 v7 [6 W
22 U( F5 C: c% @/ r' |4 R; I
38 a) b$ e5 r; V% H
4
5 R7 m/ H6 Q7 k/ { u5
) T2 ^' b5 L) r: {6
! H( R9 R; \$ t% N' x3 x5 u: N5 Z7
$ Z7 d7 Q* z$ h2 S0 r8
T: L; i2 f& ?4 l/ M99 h+ b! W3 M2 M- A6 }
10' w/ s4 Q' k! X" o2 I
11/ a- d; L* u: y* ^
12
$ m+ y& f* ^* U n# B13
" C+ k! e; t N! X: Q- q3 _4 R14/ r) h M% g2 T9 T6 m
15
2 m# T& a. S7 N( s' y6 U 类似的函数是 str.rsplit ,其区别在于使用 n 参数的时候是从右到左限制最大拆分次数。但是当前版本下 rsplit 因为 bug 而无法使用正则表达式进行分割:$ x4 a* d* K. X7 B# ]2 n; i
5 f* I/ b5 N4 ^4 ^2 d2 t
s.str.rsplit('[市区路]', n=2, expand=True)0 ~: l- s! ^2 {5 h- c7 S
Out[45]: 0 o4 E7 Z' f. h5 r1 c. ?
03 Y) b$ ]* G. Q6 m1 }
0 上海市黄浦区方浜中路249号
9 w. w7 }' c8 w* j' p; J9 N2 f1 上海市宝山区密山路5号8 E% ^0 E3 J( h
1/ ?' w; `7 L0 I' Y
25 F+ F/ I* o1 I
3
% m, g6 L, g, I8 l3 {4 N4: ^! O5 M4 ~- d7 g
5
2 T' [. b2 |! u. B' y6 V8.3.2 str.join 或 str.cat 合并
8 ]; l# f9 F* s) G# `! v; W" tstr.join 表示用某个连接符把 Series 中的字符串列表连接起来,如果列表中出现了非字符串元素则返回缺失值。. Y: x$ P7 D2 {" E* D; _/ `
str.cat 用于合并两个序列,主要参数为:
8 S* U& s# m; vsep:连接符、( S2 D$ s# [) w+ A
join:连接形式默认为以索引为键的左连接
0 ^" s, y N! w; L, e* una_rep:缺失值替代符号8 g n8 N3 x6 W- ^ w" [) B
s = pd.Series([['a','b'], [1, 'a'], [['a', 'b'], 'c']])
# h1 {1 n; n/ _( as.str.join('-')
* p% _& r* j; oOut[47]: : M4 l) w; r" x: V( t$ \
0 a-b
- d* n/ }3 D% i: c* B1 NaN9 a7 O% n; z3 S3 p* ?
2 NaN5 ~8 A/ L) f: J" F3 t, m! A. L
dtype: object' g6 A( T3 B0 G) ~5 m1 `. ?
1$ L4 c" P e4 m$ Q
28 c" |* w, Z! \9 {
35 @, ~+ R/ m7 x" `) S% d9 @* H' Y
4
) C7 x% Y( t2 F' z4 Z H51 z: \5 P& R/ p3 s3 |3 D7 y; v
65 n+ b& `) f. v5 M2 F+ x; T' H J& n
78 ^: L. H- ]8 Y2 ]7 C6 N/ _
s1 = pd.Series(['a','b']) d$ @3 `* |7 p- Y: F- q3 B9 j6 `
s2 = pd.Series(['cat','dog'])
* q8 J( T- Y% h$ o: a# H/ zs1.str.cat(s2,sep='-')$ @: B7 `- W8 \8 ]& R8 l
Out[50]: % c" f' j- `$ ]! x% `0 S
0 a-cat# ^" V4 f, q/ Z( }! @2 [
1 b-dog9 [! t: g9 M; T: a
dtype: object5 [3 K0 t6 D* C0 }' j
, w( G* T! ^3 ~7 R* os2.index = [1, 2]
- R: M/ H$ w" u# p+ @, Bs1.str.cat(s2, sep='-', na_rep='?', join='outer')
0 x: Q# o% U9 b1 [$ nOut[52]:
& A, ~. ]: f* ~+ j4 H6 V0 a-?
7 b# K* x9 l, l {2 t) g1 b-cat
' v- l( t/ g. E3 [9 c2 ?-dog; o6 `' j9 ^1 l+ @. E5 q' x
dtype: object2 f+ u/ J; ]* r J$ t4 k
1
6 x$ k8 z" B- @9 N0 n* b2
. T) s5 X. T& i1 x* m33 z9 |% Y% F' I1 B* R2 L
4
( B. m% B, d# x+ ?5, h3 ^. m% _7 g! o& T
66 l! ^3 h& v' ^, A. t( v; ?$ Z
7
, ^) Y3 S$ R* o$ N4 Z; `8) e0 ?) U) E/ k" p1 R) H
9
/ C( v. O7 `/ ~0 T9 G1 q10
+ [, l7 T0 ~8 R5 n" E7 v' }8 F11
/ ~$ m- a$ E! j& t1 k z; Q12$ \0 s: l P% T6 d" n
138 f7 N d$ D) Z. F
14
' t0 A6 p' y4 \& X" e: g4 L M2 k15
/ `) V; P- a z( ]/ v q; d8.3.3 匹配
& X' ]# P( ~5 W8 d- \5 Y' ?3 Cstr.contains返回了每个字符串是否包含正则模式的布尔序列:' m6 n; B; ?- b9 s7 c' I2 Q* L- w: } `
s = pd.Series(['my cat', 'he is fat', 'railway station'])
+ I6 ? e% _$ a, T! @/ Y. v+ v: Vs.str.contains('\s\wat')
) y/ b# Z( h1 \+ n, Z
4 q% }" @ a7 T! o$ {( ?: Q) R0 True* A) Q7 X/ \3 T3 N
1 True6 S( o& E6 F I3 a
2 False P3 ?' b q0 {9 l
dtype: bool& f' Z' N& V4 ?2 _4 n/ ^$ S
1& ], g& a- D- e$ I
2
@% ~% q9 r+ l- x- y. l) S* b30 l( l, s% w+ P V% X9 ?
4- C7 x0 F% c. n3 k5 |& h
5# Y* A( E0 A' P4 ]7 [/ }8 t& m
6
; H6 ^ c% q- w0 A& k3 ?2 _7; K) @; Y' o) V5 a* }
str.startswith和str.endswith返回了每个字符串以给定模式为开始和结束的布尔序列,它们都不支持正则表达式:
; V6 b) B& [1 E( v( ys.str.startswith('my'); l- v/ v' f2 d! T, B
/ ?% _- s: T6 |+ o1 T# c0 True
- Y8 z8 m2 ]3 s+ ]1 False
9 h% `: s3 _8 C' @: F* w2 False C' k# ]# o- [) w
dtype: bool# W9 f5 i$ h6 M$ }. v
1
- B9 o: e- s4 r. J( J2' e) i2 t, b* y4 t3 G0 M
3
7 c8 x. o: f. S4
- `! r G+ ^4 g# M3 ?, g8 V/ J5; w2 w% r- K! [) Y
6% k& F Z3 B6 D9 w" ^0 ?
s.str.endswith('t')
' Q& n2 a# ^9 L7 R& Q5 ^& }* D0 i4 o" P; B; d9 y
0 True
1 X4 l, `6 e% ^6 l1 True
0 m7 |8 i' U! n+ _/ \2 False
6 N. [$ Z9 ~# Y2 u# l0 Xdtype: bool( i8 f8 B: g0 f1 S; w" d
1
4 w9 m! R. W* S$ o/ Y% Q2# S5 l, D" H3 d% j
3
. C4 h! d$ T) [8 c* ^+ j& ^4) x' w2 _' o- W, ^! o+ c, U% a8 Z
5; H4 d& J4 w8 \" L! P
6
! W3 D) M$ t& X5 wstr.match可以用正则表达式来检测开始或结束字符串的模式,其返回了每个字符串起始处是否符合给定正则模式的布尔序列。当然,这些也能通过在str.contains的正则中使用^和$来实现。(貌似没有python里的search方法)6 w2 c4 |" b2 r3 M. U& |! @
s.str.match('m|h')
2 V* P$ y A7 H4 _ l* ks.str.contains('^[m|h]') # 二者等价* M$ P/ u# k! E; H. \: y9 A
, k! j* w4 ^$ r8 p/ E2 [+ z @0 True5 Y4 ^5 y, e7 |% w8 F+ {
1 True. `. x" s; J" K0 Q% \
2 False
6 H, x8 r- j2 x9 z; ddtype: bool4 u+ g" o v( s; e3 {' |/ d4 e
1& P& x# ^5 g; Z1 T/ r y/ D
2
* }$ M2 r% l5 E: }/ v8 }" r3
! f+ x6 K$ J, q) H+ T2 s# p; {- R4
* p9 `$ v- V. ]! T8 S; b5
0 `+ F( ?" L F7 D6
$ M& m9 @% e9 J5 i; _% S& g9 ~( U/ U7
# K+ Z$ D& h& A6 }% n6 A; B; _' r' w1 l( ds.str[::-1].str.match('ta[f|g]|n') # 反转后匹配: n! O) i& v6 x% C* X$ ~2 u
s.str.contains('[f|g]at|n$') # 二者等价
# B* t+ \/ Y& O% t1 d
4 t1 I+ d: n* ^) @0 False7 G4 }. T- Z0 i$ S' l% F# P& I
1 True
9 G2 X0 g$ [& [, ?& t' i2 True
6 O q! u: o* T/ E7 Kdtype: bool+ y% T) s$ i1 M( k( u' D( F
1# W! \' ~ ~% ]5 @( ~. Y* Q
2
3 [+ C* n! |! v* {/ {3
( F$ P* _* x7 u' g* u9 x! s4
5 o+ E$ X4 [' g J5
* x1 A& `3 ?8 O/ X4 w& E; @6# N& `, T8 i& J
7; K! P, ?0 B5 z; t7 K
str.find与str.rfind返回索引的匹配函数,其分别返回从左到右和从右到左第一次匹配的位置的索引,未找到则返回-1。需要注意的是这两个函数不支持正则匹配,只能用于字符子串的匹配:% ?; N* a& v) x
s = pd.Series(['This is an apple. That is not an apple.'])
Q7 n+ S% k+ W- e* P8 @/ W" {) n7 Y; I: j- V) E) j
s.str.find('apple')- k4 V* j Z: Q2 q" m8 N6 N
Out[62]:
4 R. O' h6 A4 d* b( y0 114 {0 z0 a: k. x- R1 A
dtype: int64. S: l; l+ [7 f# `
& L3 H, w, l3 G8 u# hs.str.rfind('apple')
# S9 @+ o$ ]2 sOut[63]: * }8 a# i- S& y5 _! R
0 33, L, K) r( r7 g6 K7 w$ v
dtype: int644 z0 Y8 _8 B3 m9 t( w! e2 T7 P
1
) D; r/ o$ | D4 \' V4 L% |& S2# f; e! L4 k8 x* }1 g# s/ b. v
3
2 g! w, \+ c( S6 s& Z/ j4
. l$ v W6 b4 l1 {& k5. `: F, D: k* u9 y/ h. O6 [
6
% _' _3 a8 R2 @. \. S4 m$ k C7
& Y: R& Q: a+ }# v" L0 _( p8
3 _1 b# p q! x6 l) g9 g9, `/ M+ z9 P* x& ~3 Q; W, g8 X
10
8 P( J) [0 c( V; C3 S" d8 A9 r4 `11
% |, N1 N+ n" Z替换
' h5 U1 F: b4 f/ X9 Jstr.replace和replace并不是一个函数,在使用字符串替换时应当使用前者。
X& r5 I- B5 k7 Es = pd.Series(['a_1_b','c_?'])1 g. j) N( E& y. F, [
# regex默认为True,表示是正则模式,否则第一个参数内容表示是单纯的字符串,也就是匹配字符串\d|\?
; [9 n: K) g" cs.str.replace('\d|\?', 'new', regex=True)
9 V8 T' L( M Q1 U$ |8 T% ~/ N
" A* Q9 R2 \5 {1 y2 I+ m4 l+ U" Z: j) X0 a_new_b
1 Z9 J0 L" ]+ w! [1 c_new
4 E) p. X6 F" w) U/ Ydtype: object6 ?/ M$ y! A' K) n0 M& M: o+ y
1
+ I8 X, u/ ~8 ? K2( @" p* o1 X/ Z& v0 D$ }$ z0 p
3
( Z. W6 [8 R+ }, k0 M2 ?4* i& ]% w s* O* F1 v7 s- u8 ?7 U0 }
5! t, Z; }3 `6 P2 [0 j4 T
6. w. k3 G" y! C4 a- k" v- i0 U
7
. q6 ?! E! a ` 当需要对不同部分进行有差别的替换时,可以利用子组的方法,并且此时可以通过传入自定义的替换函数来分别进行处理,注意group(k)代表匹配到的第k个子组(圆括号之间的内容):' k! v& s6 A, N, V
- w# A( \' v, J0 E D x4 F+ hs = pd.Series(['上海市黄浦区方浜中路249号',
/ @9 x+ v( v# H; g, I$ b% K3 g2 e '上海市宝山区密山路5号',: i9 T9 b% A: a Z
'北京市昌平区北农路2号'])# S4 O, z1 t8 X! [2 L, J
pat = '(\w+市)(\w+区)(\w+路)(\d+号)'4 o9 \9 c2 C) C8 I/ m; x
city = {'上海市': 'Shanghai', '北京市': 'Beijing'}
0 Y: {4 j' w" U- d4 c+ i; adistrict = {'昌平区': 'CP District',+ p. a4 Q+ H, ?* m
'黄浦区': 'HP District',1 E' n$ ]1 X3 }7 K
'宝山区': 'BS District'}( k7 s# r0 V! i# |1 Z0 t
road = {'方浜中路': 'Mid Fangbin Road',
7 X0 J) T' w! O& o, e3 a0 ] '密山路': 'Mishan Road',
$ `/ \( k/ r( N4 D4 s- Q '北农路': 'Beinong Road'}
: R- ~# z" b! |0 V) x" _5 kdef my_func(m):
/ q' b+ L- E" } str_city = city[m.group(1)]* _/ _. P5 B& W1 q6 C% o* L, i
str_district = district[m.group(2)]3 C, x1 A- C7 `4 _6 h5 P
str_road = road[m.group(3)]
# J9 e) K9 y: c1 A str_no = 'No. ' + m.group(4)[:-1]. }! {* |8 |' T0 Z" A& o$ W
return ' '.join([str_city,( n _+ m' [3 X8 O+ g
str_district,
( m$ l% g4 g9 I8 |* q# | str_road,
, G0 \( N5 P( J! J0 t! W9 f8 B" _ str_no])' Z' a; f: I# Y K: w# f! ~
s.str.replace(pat, my_func, regex=True)
% f6 i8 ]: t6 V. ? {$ e" n( u% l4 z
1( c7 I8 B# P2 G0 z: B
2
, U' ^% s% B; J, l* S. Z0 l2 t8 q3
3 X0 Y. ^- x: a" x) F5 E& H; z4
/ Z9 n- U" E+ G/ l9 \5/ `) Q7 w% w# @
6* M- Z& i) C/ J& ?/ `: |
7
9 v4 V! }. A+ M" z# M" Q+ ]9 Y. H80 H( Y3 D$ K) S" } _' t9 \" U
9
! d6 e+ d: F, l4 z5 G( A }10
) l' U; A4 `+ @, @( s. p11
) B. k5 L& Q) b) G* Z0 |12
/ b1 f. m8 H3 U9 Q3 A13; k: k; n1 A4 }5 t' } i
14
. ] P' ^; c) }$ C15
. {# I2 p( s# g: ?1 K; o16
! }, q" U8 R0 A9 S6 `17
3 X5 k! w! _4 I* c' H18
$ G6 w, `7 H6 b19: T% F; d% o! c% C! g, Q, e
20) u+ b) f( L# ]# q4 W
21
: a; Z, f3 m2 x3 E& k( S4 C0 Shanghai HP District Mid Fangbin Road No. 2496 E* U# b' N: J( E( D
1 Shanghai BS District Mishan Road No. 5! e; X9 ^5 q" d+ A- X' `
2 Beijing CP District Beinong Road No. 2# ~- x* p a/ b+ C) P9 \$ o
dtype: object
. |" I, D8 ^* |5 w3 f( d, I. F1
% I3 U2 M( N6 w D, N- W' l2
! y' a. G, L+ W3 ~9 ]1 @/ x3
2 x6 Y) j* H* W# N: \* j4* N) v- {. ^# e; e$ ~
这里的数字标识并不直观,可以使用命名子组更加清晰地写出子组代表的含义:9 e$ _( C7 Y% r6 Y0 O( Z% n
0 S8 y" M2 y" u3 ^9 \1 V( ?" |) n
# 将各个子组进行命名
. H, z1 |/ \, X& D2 D/ ~pat = '(?P<市名>\w+市)(?P<区名>\w+区)(?P<路名>\w+路)(?P<编号>\d+号)'
, F n; `6 t+ i4 sdef my_func(m):
T! d) z( l: v8 W% { str_city = city[m.group('市名')]
& b' V" T. S2 c" t str_district = district[m.group('区名')]
( K8 W; D9 {' ~! U9 z% L str_road = road[m.group('路名')]* s* P% E% V/ I% h' g& \+ U4 d e1 f
str_no = 'No. ' + m.group('编号')[:-1]5 Q8 K% |% e) q! O5 K
return ' '.join([str_city,( h) q+ a! B0 N/ E5 Q$ w& M: b
str_district,, g0 s1 g( Z. O8 z
str_road,
2 P w, K$ |, g3 ]' I3 o* J str_no])
( n. {! { K9 G( is.str.replace(pat, my_func, regex=True)
$ \: k) |: b: w3 o& {% b( e1) s( H7 k+ l! ]0 h$ w. t
2 s: }3 P& A0 L" ]& ~" V; Q8 q
3) l/ k$ h: I8 g# q& ~/ R* h5 [) b" ^% N
4: T @8 `& [) ?$ y- b
5* {) [ Q! l- ~8 C3 N' h2 b
6
" b8 a' K1 @7 u9 e7* f5 F: ?( c5 N# M$ ~
8
% x/ ?, b% R% S# H, Z* n6 t9 ^$ l f9
) {( O/ l# I7 @/ f& q4 `, W) G5 b10! p3 j$ d; B, M" a* r' `9 d
11, Z$ F4 B4 X. r7 b
120 u0 M) L* c+ ^+ W2 k6 O3 s& s
0 Shanghai HP District Mid Fangbin Road No. 2493 U8 X4 W! A8 Q3 q
1 Shanghai BS District Mishan Road No. 5
! m) Z* q/ s6 C; H0 y: F) b2 Beijing CP District Beinong Road No. 26 U. | r* K( \+ r9 m
dtype: object3 z- i, [$ W1 q1 z+ ^
1
( x4 M0 w1 e8 }5 Q2" [& y- e7 C, n5 M9 |" d6 d
3
# u( [6 n! J/ `) C- G41 L& k$ s# M! x" E
这里虽然看起来有些繁杂,但是实际数据处理中对应的替换,一般都会通过代码来获取数据从而构造字典映射,在具体写法上会简洁的多。7 ^- O+ K/ y/ X9 \5 z. x# E: I
; X0 v4 h9 p6 X5 [0 m" R
8.3.5 提取
" j- O8 \: j, _str.extract进行提取:提取既可以认为是一种返回具体元素值(而不是布尔值或元素对应的索引位置)的匹配操作,也可以认为是一种特殊的拆分操作。前面提到的str.split例子中会把分隔符去除,这并不是用户想要的效果,这时候就可以用str.extract进行提取:
; f) |+ m' Z7 s+ E5 ts.str.split('[市区路]'), `& Z, `2 p+ k: Z. q. [6 I
Out[43]:
; n- @, ? I; j U1 h: p+ o0 [上海, 黄浦, 方浜中, 249号]
- x+ ~$ M2 z2 [# a2 m/ V1 [上海, 宝山, 密山, 5号]
. q' ~ z! z. M/ ~& x5 l$ ]dtype: object& x% u# ~ I% W7 {# T! q
4 `' K! Q& i5 _- |# E2 s6 mpat = '(\w+市)(\w+区)(\w+路)(\d+号)'0 F6 p8 w( |8 ^4 J7 r) n
s.str.extract(pat)
+ x; E: K$ B+ e5 n% POut[78]:
* k* J( T) B( N# B0 ]5 M6 m 0 1 2 3
7 p) P( Q" S: ?8 ?; h0 z/ k1 J5 ~0 上海市 黄浦区 方浜中路 249号( r5 B' ` u* G$ T N8 M
1 上海市 宝山区 密山路 5号$ O3 q, ]" E; p" _$ ]
2 北京市 昌平区 北农路 2号1 `( J+ P7 P: M1 I6 W( d* b
1+ e p1 g* Y# `! [$ J9 f' s$ W
2# O2 Y7 e, @3 {; A, [
3; }$ a$ y; T) H2 z* e' ^" z
4
7 |2 i9 I- N& N/ f, Y% l8 _3 I6 Z5* Z4 v& [( K! ~5 b, [/ `
6
1 V% m1 R# Y3 m" e' M9 W6 d7( H! |( r4 m: h7 a. y7 _% `7 G
8
2 D3 E# y, Q9 ~0 }* x. ?, u9
: E x Y$ `$ T) Z* E6 c* H- B; g4 s10
) g4 C7 B& T) k2 U; s11
9 O, \# @2 u: _# j& Y; N i12
' L' h" q" t; N) s; U/ _+ k13
. S3 r4 m6 l4 m3 ^2 t# j通过子组的命名,可以直接对新生成DataFrame的列命名:
: P- C$ a0 u1 n6 ?( N p. y- a# i- H' e' p" g
pat = '(?P<市名>\w+市)(?P<区名>\w+区)(?P<路名>\w+路)(?P<编号>\d+号)'
! d4 h( I2 t& ~7 k' T3 e$ _s.str.extract(pat)
$ z( i/ o# U4 Y: u8 u/ WOut[79]:
4 K% N b! X4 I* u, G 市名 区名 路名 编号& o9 U# d- K/ _0 d% k% a- j
0 上海市 黄浦区 方浜中路 249号
3 \- E: r6 `7 c5 ?4 ?+ S1 B1 上海市 宝山区 密山路 5号
* t' D: n- w, C; `! V2 北京市 昌平区 北农路 2号) `, S3 Q) e' U. @
12 @* W: M0 k. i, n9 b0 L+ m- R
2
* F8 j* w' A* h. t* H( n3
* {1 }- a3 h- g" N/ p' D2 F4 l7 R4( ~& w: \" {6 N0 R$ u4 Q6 h
5. J0 p6 n" ?4 R# e4 B* m5 r5 ~
6' o0 T% T1 R, U( k, u0 u
72 A6 g- ]8 X) }: S' o* ~
str.extractall:不同于str.extract只匹配一次,它会把所有符合条件的模式全部匹配出来,如果存在多个结果,则以多级索引的方式存储:6 r9 Z2 I2 d" c; P
s = pd.Series(['A135T15,A26S5','B674S2,B25T6'], index = ['my_A','my_B'])
& G+ ]7 k* P' m) `8 m) l* {pat = '[A|B](\d+)[T|S](\d+)', E5 y3 B4 M( a# m
s.str.extractall(pat)
0 G& ]# b7 I4 k. v0 cOut[83]:- V J: E' c0 y# e! A
0 1$ J7 n! M1 ? O( Z
match
3 k( W0 N1 e4 e& z/ D% ~my_A 0 135 157 x( s& G' V& n6 I: N% @7 O/ O/ J
1 26 5
4 x8 L3 ^" }- Umy_B 0 674 21 w' w% [! S) @, J4 g
1 25 6
! L# s9 ~2 o2 V; p1 q5 E9 C9 B1 N% \3 P
2
% Z) u# n U4 g3
5 y, G" j" Y# U- {5 @4$ u8 T4 Q* ]% n% b& K
5
- ]# L! d# C0 K, ~; @- K6
2 M6 P- f/ R x' C) N& u7% s2 `/ S2 L$ Q, x2 ]
86 t# n. T* _, L) M
9. ~' Z# w3 R. ]/ I& ]( q* y4 X
10) O( ?& T' |5 h" |9 p+ o. O
pat_with_name = '[A|B](?P<name1>\d+)[T|S](?P<name2>\d+)'
: A7 k9 v& E9 ? Ws.str.extractall(pat_with_name)
9 |$ R _( l, c% Q6 T$ F$ GOut[84]:
9 |6 i, Y% h: q6 K% {% K" } name1 name2
1 ?) m/ s- l5 |+ ~4 W6 d: i3 k match & S0 D) `" R. @+ a& I6 ^! {5 p3 C4 z
my_A 0 135 15" q3 |' Y" l$ C* W9 o( M
1 26 5
1 L4 B; ~# t1 e- t" Qmy_B 0 674 2
8 h5 l: x3 T) y6 g1 N( @+ f+ X p& Y 1 25 65 P' Q6 p" K8 s& _ S/ u
1
1 N; }+ h' p, w4 g1 x2
' q- ^' L# m# ?( T3
& N; q) p- T- b+ C9 k1 n4# o- T! E4 G$ _, }
5
0 M- L7 Y) `0 D% ?* D' P6
9 @& y1 r Z, p; K B' |& F; V2 ]4 y7 ^7
9 }- o V5 D9 d; Q8
, `# c4 M+ S) J9 D3 r- N; `9
3 ~0 `( H# O" o$ b9 ? A5 B8 vstr.findall:功能类似于str.extractall,区别在于前者把结果存入列表中,而后者处理为多级索引,每个行只对应一组匹配,而不是把所有匹配组合构成列表。! c/ g6 \: o) s. U
s.str.findall(pat)0 M5 n! G7 c8 S) U4 ?
14 U$ U" P5 U3 r4 u. |7 N' L" l
my_A [(135, 15), (26, 5)]
# {2 Q' Z7 n7 t: l& \' Z. x4 i5 jmy_B [(674, 2), (25, 6)]
- u8 O8 a0 M/ g4 L; z1 \$ W+ [dtype: object
* T9 w; L' h9 v9 K. c1
; g1 d9 Y2 g3 k2
6 ^( t1 l, L, d# N0 t3
. G! ~) |2 o$ F* n0 Z9 d8.4、常用字符串函数
( K/ c' [' B! j& F- ?( R; i8 M 除了上述介绍的五类字符串操作有关的函数之外,str对象上还定义了一些实用的其他方法,在此进行介绍。9 J% B" b4 N" Z
7 H) m `8 y5 v3 h. Y% `' R
8.4.1 字母型函数3 I3 f ^- o0 W$ M% Y, a
upper, lower, title, capitalize, swapcase这五个函数主要用于字母的大小写转化,从下面的例子中就容易领会其功能: D/ t0 m+ \ ^0 a: s
% ]& f# T" f7 z4 g q/ ms = pd.Series(['lower', 'CAPITALS', 'this is a sentence', 'SwApCaSe'])
9 u. D0 P; q8 v' u' P( b; u
0 P% t0 @; `0 y$ _ ?7 k' Ms.str.upper()% ~1 A- e( @5 e/ i3 B/ n' L: V
Out[87]: ~/ b' P9 S; @2 s
0 LOWER
$ [6 S' R. O- d4 B8 R1 CAPITALS
1 Z4 |5 O2 F0 i2 THIS IS A SENTENCE" e# k5 ^) C7 X* [
3 SWAPCASE" a7 {; L( n) y$ b
dtype: object
% q1 G# o% c' g) a" g: m) d8 \, Z3 Y" @( D
s.str.lower()
" G+ s% d6 q8 \/ w4 f9 rOut[88]:
6 g. h( i" X- b0 lower
5 @6 _& F# C* a: M2 h* I1 capitals
+ M+ S7 W0 N$ k; r& `2 this is a sentence- j' c7 Y' _5 `
3 swapcase
0 \: t; X" [" ^! S7 O4 Sdtype: object" H! A' B3 c- J' ^* `
( e' I# O' p: ^# m. ?1 ?: Os.str.title() # 首字母大写! Z) ~' M& N# \) e9 o6 C: U
Out[89]:
, J) `" Z9 U4 F8 @. u S, `- P6 u0 Lower
0 j2 v/ m7 j# e1 Capitals: E- Y ^1 _5 h" J
2 This Is A Sentence; u5 H5 B; C# v- q& v7 U
3 Swapcase, I! D, _" F# K0 {
dtype: object. n) |5 C' T9 W2 k- \, _# K
( N- `4 p, }' U
s.str.capitalize() # 句首大写
1 u/ Q: v% m% w. T2 COut[90]:
, m" B7 q# ], A& H4 S3 b0 Lower
/ w# F- E! x# L, `( I2 V1 Capitals3 G1 N# N" M/ p2 q# \
2 This is a sentence
" s9 z3 J1 H' b L6 {$ e! ~3 P* ^3 Swapcase
, Q* |% M" [; n% Z8 k3 ^9 V- V7 Sdtype: object9 S4 x% y; G$ m' e# X& }/ R
" u& [2 A2 m2 y
s.str.swapcase() # 将大写转换为小写,将小写转换为大写。
+ c! Y2 X6 v$ ^4 A! WOut[91]:
% K8 z" x% y1 `( P" X2 A/ u! k% Q0 LOWER
, G1 c0 E: N3 X' ~0 r1 W1 capitals+ x9 L% ?/ C C, m" i1 v. l
2 THIS IS A SENTENCE
" l1 `! A2 l. h+ h3 sWaPcAsE+ @0 z3 S' z- G& _$ u; S
dtype: object
7 l7 O( N- C1 G4 i! e/ J
$ w) P3 {: M5 Ss.str.casefold() # 去除字符串中所有大小写区别9 [$ p0 g1 O& `9 ~7 X& v
b2 D/ I% b C' U
0 lower
# j# w1 l0 A `- V- U2 {3 |, b1 u' i1 capitals& V0 A) L& I' D- K. B& E! S' `1 H' e D
2 this is a sentence
6 N) L5 r# \$ `* z' {6 r' g* Q) a' r3 swapcase4 B5 O% `7 N) X, C( P
& n& Z/ N2 J5 h% C1 Y$ @1
9 E' I) g0 P8 a# y/ s* `2
$ w( D6 w" Q- q+ A# [9 ^+ E* r3
. e9 Y% l0 p* S+ ^5 ?4 I1 c1 ~& L3 v3 S
5
4 \, f3 c" ]6 ~$ e2 p61 G4 ?' a, _6 l* f, p5 n
7" ]. D" E+ e2 m4 r1 Y% O" [% a
8 {5 ^: L( {: r5 n* Y
9
/ v: u# s% W* e) m; K( I- O10: w5 _! q' i% g
11, a6 l% |2 N8 ]. x
12
4 @+ c' } m, ~3 G0 j! A13+ i! d% g z+ _( m0 w
14
' E" g3 ?+ W/ w( d% w( b& g15
C; v! p- @5 Y4 N4 k5 a6 Y0 i5 L7 i16/ P! R/ J; r2 b7 V6 k& f- [
17
3 q( c4 R8 s) e18% K, V5 C( h! H: a: F
19
7 I u2 O P( F2 E6 s2 B20
4 y! o$ h1 K/ E5 B# C218 }+ E* _; k4 f1 l% ^$ a4 a" F
22, Z& `' n3 ~; x, O! V* |8 E
23
6 W0 Q9 E9 |/ |% E0 ]24
& B6 U: ~( c6 ^/ V251 C) }8 Z3 ^ e/ v. F
26
- F* I; B2 X% F27
: _" u$ J x; n) u- q4 Z28' U3 W* K. O1 ?& }9 h8 a$ s
29
5 D3 w- f- Q0 t8 a30
2 O+ H5 U3 y7 A4 B31
% x1 [- M/ K$ W' O6 ?9 r32
# c0 q" R7 l* w( x7 P- g b0 E33
) d6 }" A# y" u34
( T# B6 F1 d& u, @/ R6 J% y35
! x, S+ _" |! f# P" M36. z6 M3 O$ M& @3 M+ m
37! u1 y0 F5 k: K/ w! f. x! G
38
5 N; \4 T2 O5 p1 {6 x39
1 a* Z# Q4 S3 @ s% u9 e40
$ c, n: L1 z* j) z" j; H$ E41
4 p- o* Z: F: J# m427 H6 g4 b- A* `# j5 C1 o
43/ m! t" U* s4 j5 ?- w, t
44
K s, r" U* e8 s4 A- x" x45( B6 A) x, {1 M+ j
46' y( t- b1 a# S! l
47
4 a+ z3 f4 {0 j48
. j) G3 S6 [ M9 {8.4.2 数值型函数' M* h1 e1 J" p
这里着重需要介绍的是pd.to_numeric方法,它虽然不是str对象上的方法,但是能够对字符格式的数值进行快速转换和筛选。其主要参数包括:
- e, M7 e* g) [% k- N- d# ?% |% D' I8 }9 b' ^* X2 R2 \1 Q) K h6 p
errors:非数值的处理模式。对于不能转换为数值的有三种errors选项:
: ?6 l b, a/ M( W% \raise:直接报错,默认选项4 F; b$ \( P6 Q' G
coerce:设为缺失值
$ j. W6 U# n$ _3 o0 i4 ]1 rignore:保持原来的字符串。
" q$ {1 F" c4 idowncast:转换类型,转成 ‘integer’, ‘signed’, ‘unsigned’, 或 ‘float’的最小dtype。比如可以转成float32就不会转成float64。 t* ]% Y; r. _8 i9 ^ N: d* }
s = pd.Series(['1', '2.2', '2e', '??', '-2.1', '0']) ^! ~' L# |7 ^
; a: T& l ]; h! B- t& ?
pd.to_numeric(s, errors='ignore')
6 @2 {1 C' k2 |( dOut[93]:
3 t" H- O+ I/ U* d6 e3 ^7 \8 r0 1
$ k. S$ N5 T! H2 ^1 2.2
* P8 V6 O* p6 [/ y+ h/ `4 R3 x" J2 2e0 ]5 |& `3 V) h* q! c
3 ??
8 b7 d$ F1 T+ |8 O0 a( `8 A4 -2.14 f$ d. m/ N5 n3 f! W
5 0
& q$ j. {* l, Y& q# N8 @- odtype: object
3 f/ C/ a( ^& B; q9 U
2 n' h: @( S/ M, Z/ m7 rpd.to_numeric(s, errors='coerce')0 x9 g: }4 ]9 w; ?& {, V! P# V
Out[94]: ' F {3 F5 @! U' A9 m9 u2 ^, R
0 1.0
, p( m' H# ~% C6 p" R; W1 2.28 B. F* u1 }+ r/ w7 Z
2 NaN
# G8 w; P; t0 l% |3 NaN! b" x! j+ P" [
4 -2.1
! {* K- ^5 r1 ]3 r9 d5 0.0
5 x* V5 P6 k* @# w7 [& ldtype: float64
* t7 l3 ?' m1 b: T2 y$ K" d: h
8 |' x; j3 m* Z: P, s1
& Y6 |6 ]: \, A: K4 [2
7 i1 w$ O+ I: h4 f6 E: U1 ~8 w3
. P& A8 n* G5 t v' ?2 F6 M, g4 s! v% e4
; r E$ A$ @( b& }5 u5 _) C& R53 M, i6 O$ K& F( \ ~9 G
6
9 y. Q7 G' J$ o# S7
1 w- o# W6 C4 U$ C: S0 E85 h& j7 M6 ~- B, y
90 x3 c0 Y& ^! |$ |1 T1 v
10
* P" V* `3 J, ]2 g11( O: W; |+ y$ x3 }7 @ g
12 l" \; X% Z* {6 k. E- F# z( m5 A; Z
13
9 u" u; z H! C5 i% Z8 k, @ t14
! q) E: s) ?+ f15# H3 R. W' H0 t* `) H
16
1 L: C( I4 L( Q0 @1 `/ x% `2 p17% o+ P) c+ o" A, S. g, K
187 y6 P M. U: p
19
4 o4 y7 ~7 t5 P20! M! `+ s3 N% `: Q
21 _. f! k9 V8 ~% y8 x# M1 h
在数据清洗时,可以利用coerce的设定,快速查看非数值型的行:# D# t. A$ a- n9 {* b- W) n5 Y- t
5 T5 w& z& d6 f6 b' |7 ?# is[pd.to_numeric(s, errors='coerce').isna()]# Z4 i' \3 G5 W, r: o
Out[95]: & \) ?: I4 z5 t
2 2e
9 Y( ^. n+ W. G) J# `: i5 B3 ??
* U6 e& Q% v( X Z0 k+ V+ Zdtype: object
7 B0 O, w, A% ?0 `. w8 K* @7 _1* o l: s3 V0 ^( ^, W0 k4 U$ O
2
" j) \: h$ [1 n' |0 t# m6 {3( p$ v' C, J$ ~2 z
4
2 U+ E# p( z7 D1 s5; D" O f; C8 e0 s d
8.4.3 统计型函数5 v: v) o5 A9 s6 z/ O$ X( Z
count和len的作用分别是返回出现正则模式的次数和字符串的长度:
" [4 P- I; X/ t7 Z d) s; e) C3 j+ _
# D" o1 q# g( W% k! P$ as = pd.Series(['cat rat fat at', 'get feed sheet heat'])' N" O+ q+ g& J+ V* I. r$ ^
1 q; C& S& `/ ^s.str.count('[r|f]at|ee') # |左右两种子串都匹配了两次& ]. G. |1 Z @% h( Z
Out[97]:
/ m4 M4 b, o' W' o9 a3 j+ l0 2
5 Z% N, [- t- {% `1 2
! a% _3 [0 x- Y( _/ z* @/ Hdtype: int64
1 q5 C$ U) B, Z7 g9 t# n1 c0 R: e% P+ O: [& A9 I- Y
s.str.len()
! B0 c7 q' e; q- bOut[98]: 2 J2 y" x; d" ?: o* m* f1 g5 o! | @
0 14* B( K0 h3 D6 E
1 19
2 e; k. c& i$ T6 R5 M$ idtype: int64* A6 P' t4 p) R( A' c
1, k& \* K) o/ J, f/ W b
2
9 O6 z, w; P# U+ J3
. ]2 R; `: E& C" K, N4/ z) d. F- y" @5 ]% o8 }: V1 @/ o
5
! u' V+ X8 i- z+ ~2 }, o9 @9 s B6* c. m2 K& i2 O, P% d; s; R& _
7
2 r" I+ [& {; t/ |8& m$ R M8 w8 d9 f
9+ H7 t* ]5 Q: z% ~/ {
10
, n4 J' C" ?" s5 P11
) p+ K5 W: O* Q8 o12
7 u: W B+ Y" ^" c13- v2 A" \" {6 C% i3 E4 @
8.4.4 格式型函数7 q% Z2 A0 h& U" t7 ~& w# Z6 a
格式型函数主要分为两类,第一种是除空型,第二种是填充型。其中,第一类函数一共有三种,它们分别是strip, rstrip, lstrip,分别代表去除两侧空格、右侧空格和左侧空格。这些函数在数据清洗时是有用的,特别是列名含有非法空格的时候。
( U' s( w( Z1 ?) l; Q
2 u6 v5 Q' K! G9 zmy_index = pd.Index([' col1', 'col2 ', ' col3 '])
; y$ C7 j* c W8 P6 O9 Y9 M1 L% W0 v- F3 d- c6 F
my_index.str.strip().str.len(). G: d0 P+ j: v; O' h
Out[100]: Int64Index([4, 4, 4], dtype='int64')" t" C3 F/ a2 \" }( U+ W# y: \! _9 _2 C: x
. Y& f) N0 Y# u9 |/ C% omy_index.str.rstrip().str.len()2 |% k0 y1 V& a! [1 R
Out[101]: Int64Index([5, 4, 5], dtype='int64')
+ u$ ^1 H1 x0 i# U
) _, |% e I2 x7 d7 ]my_index.str.lstrip().str.len()$ T+ v9 F& Y4 I- i: ~9 z$ x7 g
Out[102]: Int64Index([4, 5, 5], dtype='int64')
' d5 s( }9 i; p! f) T, |* d& K1& H# j3 I9 D- g, i
2
$ e% `, ^9 y( e3
1 l, G7 x) w) E- A# w4. e8 }5 L$ O; H4 i! J2 p
5
( h# q) B3 x2 s$ A6
9 w! K8 @' u! t: Z( d6 t/ ]7
9 q# l) e- `$ X4 j+ y* z4 G1 _8% i- Q8 g, u& z7 H: [6 H$ [3 |
9) q' o* ]- _8 W8 {3 C# o3 q7 f
10
. u- `5 ^" X8 }2 Q0 E0 I) Y 对于填充型函数而言,pad是最灵活的,它可以选定字符串长度、填充的方向和填充内容:
9 M0 a2 s V/ D! \3 |# z3 _8 a; V( E6 A) T. z8 Y, t0 p
s = pd.Series(['a','b','c'])
: e& s& i6 S2 U$ D
# v: r3 B- X: w! e) S9 ?" Ns.str.pad(5,'left','*') ?6 `* _, ~( |/ B' { e" ]) N) r
Out[104]: 0 j" F% M# q) t$ X3 V
0 ****a
v- E% x+ V9 z# |7 n2 Z8 t# O1 ****b
/ m/ [) O6 e s, F9 T; V+ F2 ****c
% \+ o( I- w! j# v$ w9 ldtype: object
8 k3 R3 T. s3 V6 W0 l) C* Y. u6 t8 }' N7 X8 {7 m: Y
s.str.pad(5,'right','*')4 I+ v9 _8 x, r4 B
Out[105]: 4 p! n! B, K4 U2 K i' t
0 a****0 K$ [4 @1 u+ c! B% m) q# Y {/ w
1 b****
/ ?+ Q; n. T2 @& S2 c****2 Z. H/ f% Q: a) n2 k6 V. S
dtype: object
- s" f4 v+ I( {4 v5 [8 z; o
0 j' j# P$ I( W0 V! us.str.pad(5,'both','*')
0 j( n6 V1 o! h. {6 nOut[106]: ; o7 h& v! z2 k, K% D8 a
0 **a**2 K+ l' k% b- }+ _" `* ?+ W) l
1 **b**
" P4 D& u1 q) i2 **c**5 M0 I+ l1 H3 ~
dtype: object+ Q% x$ d+ b& z
+ `, d! V! a' H
1
6 d( S1 N1 S* H9 ^) q29 y" ^$ s$ p$ T+ H( l
3
9 `$ O" l. Z& D3 v, t; o47 G5 T1 ?% g6 O# _
5
- t" K6 ~, N; m( X$ U! R- N6
7 l8 |9 s' G; ?/ D: c7! [; Z$ Z% j2 Q2 W, x
8
8 m6 ]3 k- i$ c$ P9, X; ?8 r" }1 |& a' U* A; X; n4 F
10
# ]0 A. r E) U3 n% X* y11
' S9 ^8 k$ q$ u& g; k7 Y4 U12% l) A* Y* Z2 o( P3 N+ |
13
% E4 h/ \( c3 U7 D$ B4 y14+ ]4 {1 C. R/ b+ ]1 u3 j" J& U
15- I$ B1 M6 ?5 {7 e0 L- K+ }* W
16; A q8 h# g' O9 B: C
174 ^! a. b2 v' S/ M
18* c6 D& b# I+ U l
197 h }9 w7 y* H
205 ]% o. D% a9 m* G7 v( i5 u4 S
21
0 a9 |' q8 Z- U22. D8 N% j/ H( D/ A
上述的三种情况可以分别用rjust, ljust, center来等效完成,需要注意ljust是指右侧填充而不是左侧填充:3 P; K3 {' S; d2 u' I; Z+ a
, L# ^' G. _) _4 U% C4 J+ r# }s.str.rjust(5, '*')7 P% ?0 g9 i4 V/ a9 F
Out[107]: + {' c: k- A; q7 p9 e7 s- i
0 ****a
4 K: i$ E" K. @# d# V6 t1 ****b- O8 y- k5 o' g# c
2 ****c/ Y z. |! a, a1 |" S
dtype: object
7 T& [5 O5 y+ E5 Q+ P" i( n3 O5 C
' K$ u& |; c- @3 d, R4 Gs.str.ljust(5, '*')
( I9 e3 z T# ?8 [Out[108]:
' [# Z+ m4 ?7 ], |, N6 [9 {6 j- W! N+ ^1 r0 a****6 B a0 \1 |9 j; h) r# o
1 b****
, ~' U8 p t" ^ P4 S1 {$ X/ n2 c****
; B) Z+ `/ O) Qdtype: object9 q8 ] _, M8 c2 T/ x# D! P
0 O W! g) G* J. Ss.str.center(5, '*')! c; ?3 G$ s6 Y5 v: z
Out[109]:
( e( v! y8 f0 |) \( P1 E0 **a**! {8 W- S$ d/ b$ p0 J
1 **b**
6 r; K, |8 f& l/ T& H2 **c**8 n/ |) K) n9 ?9 D# Z0 p" T
dtype: object+ l0 \; K! c! R, T; H
! z: G5 m' ^+ E7 s" L- l- X5 A/ g1
4 k* U8 V% C$ ]2: ]' c, T( p7 Z. v
3$ ]. l. S) G; s% J" d
4. w/ K( s/ ?) m. M) T* m ]
5
1 P Q8 {4 D9 T6
$ A7 F9 d: u3 M. r: @1 `; H9 m4 |7+ T& a: L: i( \# W. b% K
8% ^5 E% I: c2 z, |' w- I M7 r
9' g4 q& O; {+ R) H( X
10+ B: m! b' Q6 C! u- p2 }' k
11
( E/ c B* ^( {6 @% E' v5 R12
( [8 R( f7 w3 Y0 D7 V) a- D13
% T; c, v" V' N! Q* o, K145 y! E+ T" u. Q* R" l/ B
15
6 A+ Q; ^( m9 F16
+ q/ q1 J( L- L1 {17
2 d9 F, Q6 N4 z& Z5 |4 X7 z18
. ^) p9 ]7 u7 x. B; U# ~19
2 C. T. e& b' P- n20) ]4 [' y! C7 b+ n
在读取excel文件时,经常会出现数字前补0的需求,例如证券代码读入的时候会把"000007"作为数值7来处理,pandas中除了可以使用上面的左侧填充函数进行操作之外,还可用zfill来实现。
0 T5 K- L; F* c! s% c8 `# i" h7 c: g7 o7 L+ t5 q! q% w
s = pd.Series([7, 155, 303000]).astype('string')
5 a1 `1 l1 r6 _( A1 k8 Q- l0 g* U& k# ]8 {) n" ]- D) Q
s.str.pad(6,'left','0')* _! n3 F- J; C' m
Out[111]:
. O4 u- P9 ] y0 000007
( s+ ^, k& J) N& c3 v& @5 S1 000155" U3 l6 N5 r/ v
2 303000, K, [6 B' {( T, D0 I
dtype: string
& p/ b" u: |( v2 i/ k* ~# X: Y: q
_! A. N- d5 r8 h& Q5 b/ ts.str.rjust(6,'0')
9 O) X7 f; j& mOut[112]:
1 h8 z; d: m5 U$ o/ c" p0 0000070 Y. t' b9 F, ?/ c9 O$ A
1 000155
5 h. F2 y! S: r' }) } Y2 303000
& O5 J- [8 X: A* Z; d( tdtype: string8 S$ {4 c8 u9 `, U) q
3 \- Q! f+ ?6 z& R4 Y8 e2 i
s.str.zfill(6)* Z4 Z" w# V% l2 K1 j. _& H
Out[113]: 7 Z$ a" |7 r5 `, M G
0 000007
5 G% b, y. b: B% {1 0001554 p& x1 b( {" u/ p
2 3030006 Q2 l8 Q a7 C! `
dtype: string: u* m; [2 Y, q
# f, m! ?3 v. H4 N
10 S8 G( j4 {4 k
2
: X' e0 {6 j$ M7 n' X( t2 m* E- o, B* B3) V0 w4 ~- m) Z4 a6 L! `
4( B" p( S7 y* S( P9 ~. Q
5
e C2 l+ p( P. {, {' r6
! k" o$ O" G) f( Q6 w7. ?7 E- ]2 A: I" L+ A
8
* h& r5 q' u6 ? Q7 \1 x9
- Z( d. @7 W4 @6 |* f10! T7 _3 d5 H, j: F0 m
11# G0 ]# b2 S. a% H0 g
12
# ?! k/ [8 F$ C, `7 C3 S% z13
7 c1 c; t$ e# E# @. ~0 T( y145 {; t H" k; x( y" H. g
15
% S! s7 ^: ]7 r: P% e, s0 G16* ?/ j( t% p. t; G, ^
17# r6 q: Y( G6 Y C( t
187 }! S) Z! m3 R' V
19
5 K3 d' q/ h5 k! j7 |' U20
4 w8 ~) \+ l6 m( t217 `& f& S" t) F
22
7 z4 L" @* |6 H! @8.5 练习! ~/ X& _$ ^0 @9 m2 K+ H9 y5 ~
Ex1:房屋信息数据集5 f5 f& k! b, e7 t3 u
现有一份房屋信息数据集如下:( l7 R" T0 A \. Q* o/ o
6 n K) n6 I {) l+ C# K7 ]: w
df = pd.read_excel('../data/house_info.xls', usecols=['floor','year','area','price'])" n2 s- t, O( c! W6 s3 L0 R; |
df.head(3) X2 }/ |* M+ Z2 M
Out[115]:
; q: E. J" G& b3 q) t: |$ C- v& B floor year area price
) {8 p6 o( M# p4 I$ l- H% Q0 高层(共6层) 1986年建 58.23㎡ 155万
* q) z$ j- ?, z3 G1 中层(共20层) 2020年建 88㎡ 155万5 O: z6 [) f' o2 j4 N7 J
2 低层(共28层) 2010年建 89.33㎡ 365万
K; h! B5 h9 F% W! o17 x4 J* n& H b' v
2
6 `) a) m3 Z% E7 P7 m3
J1 ]/ G+ y( D; I42 o, b0 P' P. z: D$ c: [& G! ]
5
; ^# D* t j; R+ V1 J. c6) i3 v- K! O7 s; q# W5 {. Y: u3 C7 K
7
% X( }! n' g3 j o将year列改为整数年份存储。! C1 R8 _; D0 J7 j4 B: B+ V- [
将floor列替换为Level, Highest两列,其中的元素分别为string类型的层类别(高层、中层、低层)与整数类型的最高层数。
' S! b" l/ N8 q9 \+ a r- ^: j计算房屋每平米的均价avg_price,以***元/平米的格式存储到表中,其中***为整数7 Y" X8 f! A3 t2 o9 O- K0 D
将year列改为整数年份存储。; d. V d/ c6 V7 |( p: @
"""
2 a% S4 {5 N! F( P) b/ R: h整个序列需要先转成Nullable类型的String类型,取出年份,再将年份转为Int64类型。- P& U7 K5 u5 G1 N# I
注意,转换的类型是Int64不是int,否则报错。即使astype加参数errors='ignore'跳过缺失值,6 H% W, A2 d. l1 R5 w5 Q& h
转成int后,序列还有缺失值所以,还是变成了object。' k& O/ K( k! ?, l
而整个序列转为Int,就还是Int类型,缺失值变成了 pd.NA 。$ g- T5 g5 b5 K
"""
& Z5 Y% s2 _ ]- s' t* @df = df.convert_dtypes()
2 ?" W. Z3 H: s1 Adf['year']=df['year'].str.replace('\D','',regex=True).astype('Int64')
$ @3 _8 u5 P3 Sdf.loc[df.year.notna()]['year'].head()
( I# E. ~, A5 g+ T1 B" W
& v# {8 d0 g, t' V% r. c0 1986
6 F0 s. {# [' q: q! z2 ]9 k1 2020: K5 I5 [2 a" z2 M
2 2010
6 S7 D+ o. J }/ ?3 s! }" w6 y3 2014
+ r& V8 J: l% u4 2015
0 I0 x# f7 B5 Z1 j1 B$ xName: year, Length: 12850, dtype: Int643 L. @, a( G0 m- Q% Q+ U) y9 x
0 V- {! a/ n3 s# O& R1 }1" L: {# e- S1 @& ^- k
2' [% ~* t1 @: S0 S" v1 r. |
3" r' D. t! F5 p/ ]2 [
4
) E; a( R4 a% | r* _! H5
' X- P& e- v% U6
; N+ r' }9 `0 f; n' M S7, L& h& r# q7 {
8: S; {3 f- ], c, z8 I; H
9
. @3 O" A0 z5 K4 U, E103 O: r# J( r9 y# } V* @
11
9 D; G3 P/ \4 q( x- v" G12
$ l0 r& ~. }2 ]+ }* _5 q6 E13 D/ r, }( F) u* c: Z% P: t& z
143 ~/ ?9 o2 `7 S6 o8 Q% {' |4 ~; w
15
! D6 r' a J3 Z* q& J9 z16
9 }1 w3 t) k9 @. Z3 P参考答案:
$ ]. s( `8 u |" r& W6 q: o3 @& k/ B4 `) n0 [. e6 r! y4 J
不知道为啥pd.to_numeric(df.year.str[:-2],downcast="integer")类型为float32,不应该是整型么 k* J5 s3 J8 ?0 G5 h9 ~
0 f; m* _3 M5 d4 `df.year = pd.to_numeric(df.year.str[:-2]).astype('Int64') 5 \+ C$ X! g' \) Y0 y. q% d5 K8 a
df.loc[df.year.notna()]['year']
# E% }! u: r( C6 O4 E1/ r1 Z: l0 e2 T( R0 T
27 v! X( n- |& }0 ^ r6 a
将floor列替换为Level, Highest两列,其中的元素分别为string类型的层类别(高层、中层、低层)与整数类型的最高层数。1 M) N5 z8 b! M8 o; Q
pat = '(?P<Level>\w+层)(?P<Highest>\(\w+层)'. O: s2 {- `" u% j" |* ^# _( f- ^# b
df2=df['floor'].str.extract(pat) # 拆分成两列,第二列还是(共6层得形式,所以还的替换一次9 [0 Q* q6 t" N" m( \/ b
df=pd.concat([df,df2],axis=1).convert_dtypes() # 新增列拼接在后面,再次转为Nullable类型1 u+ w, ^8 `' O* ?7 v2 D2 `+ e
df['Highest']=df['Highest'].str.replace('\D+','',regex=True).astype('Int64') 1 D( A! c; T; m' k7 p' B9 g7 J% y. a
df=df[['Level','Highest','year','area','price']]: n( c1 t( @& @& f' Y J4 O. o
df.head()
, O' B" F3 H4 M: a* s7 }: n. [/ v' H" x! Y6 t1 s+ A( j. B
Level Highest year area price
' Z2 l' e0 n, ] L4 R0 高层 6 1986 58.23㎡ 155万
' O6 d7 K$ I( i) E) [9 U- s1 中层 20 2020 88㎡ 155万3 j' c) g" n0 c# q# H; j. g
2 低层 28 2010 89.33㎡ 365万
% D4 u6 G7 Q0 P6 _3 低层 20 2014 82㎡ 308万) e- f4 I3 @, V7 d& j( E
4 高层 1 2015 98㎡ 117万
v% H1 S+ [0 ^( t! E& s! [- I1
+ i. Z* k' l3 z3 G; e* ]* ]2
1 n {) d( ^! e6 `4 m3
; g+ x% p' H( n3 h4
& k0 P0 h# i% M6 B0 k" x5
* n5 R; X# ~/ |6 y2 ~! S P6
6 q3 `/ E0 A, M4 @ q* n7
7 b, r: }# u2 L' A7 |( j) I) K8, E8 B6 j) `. q+ F t6 ^5 I& d
9
& p4 a a* f! d' F1 y. e- f8 }" v10. V' Q+ M% {' f! _1 \
11
; `. i/ T' y8 `12
! F0 g) [ M" J/ ^- ~* |- y2 U13
s3 j0 u1 D; H; z9 |# 参考答案。感觉是第二个字段加了中文的()可以准备匹配出数字,但是不好直接命令子组了' C3 K4 x2 U) i7 X2 U) J
pat = '(\w层)(共(\d+)层)') s% ]& ~* T$ X- N
new_cols = df.floor.str.extract(pat).rename(( F& G5 M5 r- p$ v- {
columns={0:'Level', 1:'Highest'})
. O6 Q- q$ }/ ~& R& y
" l( s8 L3 S9 t& N3 i8 ydf = pd.concat([df.drop(columns=['floor']), new_cols], 1)1 G7 @7 H2 u. S3 Y9 q
df.head(3): o3 \; R3 ]( ?
1 J, Y* f( ^" S$ cOut[163]:
. V: Z2 ^& b# b- j- w7 J year area price Level Highest0 W+ ~2 x. d7 V1 P
0 1986 58.23㎡ 155万 高层 6
- b3 a7 L! D7 t* ^! U2 @1 2020 88㎡ 155万 中层 202 {' Y1 b S6 |7 A4 n
2 2010 89.33㎡ 365万 低层 28# }4 Q" r" v) E3 a
1
3 m: f( l2 I, ], D" Z2 G9 N6 }4 e2; N4 I3 y/ g9 F3 ]
37 D$ T* ^9 W0 w y1 Q' E; S2 g& f
4
9 l# Y" l6 i3 v9 J/ u52 z6 P3 W9 u) N. M! E: D$ S3 k
6
/ F* [9 }% }& f- @* ^& N7* x, @* e( h$ \4 Q. b: E
8
0 w) W x$ a# ]. K9 V5 O+ G4 }* G$ T. k: v1 N
10# P$ v D1 s, r* m2 A Y. ?- ~
11+ a7 ^" M/ ?2 K& R( P7 O
124 R7 r% D# ]" F
13
( @9 |8 G( S1 k- z( L2 M2 G计算房屋每平米的均价avg_price,以***元/平米的格式存储到表中,其中***为整数。
, Q4 i: O, K, z/ o( I( C"""
" h. H" H* y1 l. ?/ n1 M# q1 R( ]str.findall返回的结果都是列表,只能用apply取值去掉列表形式
3 @ c' ^9 p5 I! [( j参考答案用pd.to_numeric(df.area.str[:-1])更简洁
7 F( `5 O. @; \由于area和price都没有缺失值,所以可以直接转类型
6 U7 u1 m6 ?, Y9 o. R+ t1 d"""$ ?* Q; t; U1 V& A8 f
df['new_area']=df['area'].str.findall(r'\d+.\d+|\d+').apply(lambda x:float(x[0]))
: K# @) P7 C! L- R; Z8 tdf['new_price']=df['price'].str.replace('\D+','',regex=True).astype('int64')' i/ ~+ l' S J# L6 W5 t9 A( q1 O1 S( a
df.eval('avg_price=10000*new_price/new_area',inplace=True)
I, z) f, s+ Z5 s) a: ]% k/ a# 最后均价这一列小数转整型直接用.astype('int')就行,我还准备.apply(lambda x:int(round(x,0)))
" j& y" b2 {9 [* O, h# 最后数字+元/平米写法更简单
+ @/ N" Y! e0 odf['avg_price']=df['avg_price'].astype('int').astype('string')+'元/平米'
; B a" S/ H! t* }' |) L i9 [del df['new_area'],df['new_price']
$ q) I! ]6 {5 p) q4 ddf.head()
, x d2 Z h* S- o+ u* x7 f1 R* }3 K5 x3 E- x
Level Highest year area price avg_price
8 B; n# L2 ^4 K. N% ^8 R0 高层 6 1986 58.23㎡ 155万 26618元/平米0 _. c3 I9 b& z2 G1 q$ j& w
1 中层 20 2020 88㎡ 155万 17613元/平米; c7 Q$ T- c1 }) T( t9 X0 ^
2 低层 28 2010 89.33㎡ 365万 40859元/平米7 A4 {5 L! n8 M ?
3 低层 20 2014 82㎡ 308万 37560元/平米. S% O3 z* b/ p: X" G9 R! d
4 高层 1 2015 98㎡ 117万 11938元/平米# k3 b5 B+ O, t' c: J3 K d( _* h" B
) M* A% p) a; T) k) J* \1
( H9 ^- l! N: k4 {% b2& u9 d+ R/ d9 j/ r: g! V6 N& E
3* G5 \& u1 d# o% A% F
40 J1 [9 U; m4 a
5
% a8 K0 Y" ]: u% L6 i: l6- l3 } l/ i2 C- x9 S8 {, @; H
7
0 K* ?! O2 s9 k4 h7 s9 l* p8
6 X+ N1 E7 ~* ~* z3 t/ T9
* X4 l) I' o+ c3 U/ [9 m9 e10
. |# c! d# h- @7 Z7 [( t; u7 ?2 I11
, D/ f+ k1 h2 l3 w. ]# c1 r' F12" w7 ~' W* b6 d# k
13' Z, t* ~5 r, N5 a! P/ M* ?
14; c" G7 u4 W0 N9 f& [% B
15) V$ i/ i; s. }
16
+ S" W% N/ |$ b, U0 F& `173 j1 P. R) h2 F6 Y: p
183 G" |8 K( {5 P+ ^- C3 v/ g J1 E
19' J/ B3 @6 g0 \
20
: C% n, t8 j) P8 E" o# 参考答案# `7 L( e& r2 p$ [$ Z; ^
s_area = pd.to_numeric(df.area.str[:-1])2 u( @- I+ Y" N p8 P
s_price = pd.to_numeric(df.price.str[:-1]): H: _% }8 c4 b. @: J" D9 T" C
df['avg_price'] = ((s_price/s_area)*10000).astype(
$ B U3 W/ i% R9 H 'int').astype('string') + '元/平米'' s( ~9 \4 l) H5 N V
' o3 h. ^& H6 D2 B# d0 j; X4 edf.head(3)4 w" h# Q _) c
Out[167]: [7 \& {4 |; u6 G/ A2 x" V$ I2 M
year area price Level Highest avg_price
% o) M" f* h) j. S2 j) \0 1986 58.23㎡ 155万 高层 6 26618元/平米
Y$ G8 J' H2 z1 2020 88㎡ 155万 中层 20 17613元/平米& ?; h5 @: b1 v, ?
2 2010 89.33㎡ 365万 低层 28 40859元/平米# t6 m. Z0 x1 T7 D0 q
1# B4 |0 X; J+ {) D
2- ` C6 i: K/ }2 ^2 w, e& X. v
3
/ y6 ~$ m `4 \) o49 N- y9 n* o* Z& s/ M
5) m8 \" ^9 p& z; _* n% a
6
/ h1 k' k9 T! ^, Q0 t3 O9 _7
7 w1 k7 U$ K- S# x. j8
) V' s& I* O/ Y95 t7 C6 |/ D+ \' ?* w
10. E, [$ R p* }4 T
11
% W& s8 _+ |; y$ @- Z( ~128 S8 k [8 }# o1 g7 R7 ^) S
Ex2:《权力的游戏》剧本数据集
1 k( B- b) I+ w+ @现有一份权力的游戏剧本数据集如下:! ]1 u' W: Y" I5 y
0 i( K) y6 o' w! v% Q* Zdf = pd.read_csv('../data/script.csv')$ Q( V0 E+ w% n& Z2 a1 k
df.head(3)
, q7 p i0 ^0 F# _0 g2 N
, h) W: g0 p4 Y" s9 O; q! i4 ^Out[115]: 4 E" X; J# F( M, ?+ h' e
Out[117]: 4 ^( `6 m4 j) w' A: \9 K' g
Release Date Season Episode Episode Title Name Sentence8 }1 P' n: Y& H+ b i0 f
0 2011-04-17 Season 1 Episode 1 Winter is Coming waymar royce What do you expect? They're savages. One lot s...
4 i# j4 T! X: H0 @: v; Z4 n6 w1 2011-04-17 Season 1 Episode 1 Winter is Coming will I've never seen wildlings do a thing like this.../ o) C! r3 X6 l8 T
2 2011-04-17 Season 1 Episode 1 Winter is Coming waymar royce
' F* W% b; _! P1 F! ]; a- g+ |1 w1
% d( a8 o5 M! b E6 F" @1 v2
. D1 o6 u4 a! O; B% @# d# M3
# ~; J" j! }5 h0 h4
6 U& v& n8 b4 D3 H+ b* G7 U2 x( D X5
) k9 o* F! C* {" r: n9 p% L63 U7 T4 @+ ?, V& J4 X8 A
7; l5 `# Q# B" w3 C. l
87 P; D i( A6 F: p% f4 K1 \
97 V+ C- Q1 h) N) e1 f+ r* n ~
计算每一个Episode的台词条数。8 t9 v+ s$ \+ z+ g6 d& p8 X
以空格为单词的分割符号,请求出单句台词平均单词量最多的前五个人。5 Q: C9 ?/ N- ^1 i) F
若某人的台词中含有问号,那么下一个说台词的人即为回答者。若上一人台词中含有 𝑛 个问号,则认为回答者回答了 𝑛 个问题,请求出回答最多问题的前五个人。
2 v- o) b6 j( ^8 b( G计算每一个Episode的台词条数。
! X% T, o5 C4 L/ T3 N: fdf.columns =df.columns.str.strip() # 列名中有空格
# u% S4 z. A( \4 b% t+ hdf.groupby(['Season','Episode'])['Sentence'].count().sort_values(ascending=False).head(), w3 c+ V7 {1 b- N
0 u" n9 |" H9 q X
season Episode 7 Q `( b& d8 m: E$ t! x( A& N
Season 7 Episode 5 505* y5 q! Q% l1 S
Season 3 Episode 2 480+ \4 v2 i1 ?8 H; D. J7 R
Season 4 Episode 1 475. X1 S2 A7 E% K' H+ n _# b E8 y
Season 3 Episode 5 440
5 L! X6 y s5 m) H1 {& ]8 X/ G; ], `Season 2 Episode 2 432
, X0 L! m: e$ t1 O: m; z19 X5 j* [6 p: P& d& t' T
2
( r1 }8 h, Y9 Q6 S3# ^3 `5 L1 g$ H$ ?
42 N$ v+ D9 x8 T% Z
5
5 w' E' W( v: n" \* Q6
1 J0 e v v/ R* S0 {0 g& g9 D& b' b7
8 ^% b1 I. L+ g6 c85 {2 D$ S3 x' x9 j) z7 g
9* |9 u; z* I. L! y7 e, P
以空格为单词的分割符号,请求出单句台词平均单词量最多的前五个人。
# a6 @" m1 w8 N$ @8 C& G6 i# str.count是可以计算每个字符串被正则匹配了多少次,+1就是单词数: a9 j1 r; |$ k4 J2 A0 }# \
df['len_words']=df['Sentence'].str.count(r' ')+1
' c0 `2 B% }4 C% v" _5 Jdf.groupby(['Name'])['len_words'].mean().sort_values(ascending=False).head()9 g8 W4 }" w7 F3 t* h7 S
" G+ N" |# E5 k2 N
Name5 t9 V2 L+ t8 n& a" d! w
male singer 109.000000
( v$ Q# z2 Z! Hslave owner 77.0000000 f9 e& f N; Z
manderly 62.0000004 Y- |! S; @+ H1 i5 ~, L. K( _1 s
lollys stokeworth 62.000000
$ D- D& t1 o8 g2 X2 jdothraki matron 56.666667
# f9 K' `2 S0 XName: len_words, dtype: float64
9 y8 F8 G; j4 A6 F: U1
/ s# n# N3 \3 L- H24 k4 D5 Q) i, Y) t
3
4 `; Z8 D% `, t) h5 o44 w6 h: ^5 b; q# N* F0 ~
5. O3 @, S5 R% x3 Q9 V
6
5 E) j* B) }2 X* K8 ^7
6 w& s+ ]- Y& v, x H1 G& o88 T$ G0 s- ]( r& g
9% n) \& f: d+ W, {( {- ?" D
10 A9 w% u8 `0 w1 I
115 r7 |% S- x4 @8 {) q
若某人的台词中含有问号,那么下一个说台词的人即为回答者。若上一人台词中含有n nn个问号,则认为回答者回答了n nn个问题,请求出回答最多问题的前五个人。) A# B3 s2 ^& G- f* R, p
df['Sentence'].str.count(r'\?') # 计算每人提问数
" _7 l& Q* J4 O3 q* cls=pd.concat([pd.Series(0),ls]).reset_index(drop=True)# 首行填0
( w0 p& ?, C6 ddel ls[23911] # 末行删去! x' Y0 O* r9 [% n1 ?: ~
df['len_questions']=ls
( z- o2 Q; Z. ~- a. Mdf.groupby(['Name'])['len_questions'].sum().sort_values(ascending=False).head()6 U0 b3 W# L6 O- a" b
9 [$ J1 o% S8 G1 R7 G0 {* @! ?Name
7 n e- W: ~/ G# t! utyrion lannister 527! y" h9 ?6 j. J7 d$ X4 Z
jon snow 374& J; l+ W2 I) l0 e2 r$ F
jaime lannister 283& L; g) b$ \3 ?
arya stark 2650 S6 l' z/ T1 w k
cersei lannister 2468 w9 i9 ~" |! g
Name: len_questions, dtype: int64
' h0 R' Y* b/ J9 [2 T0 j
6 M! ~8 }" @* q% `# 参考答案- S% j- P C0 ?5 W; o. i
s = pd.Series(df.Sentence.values, index=df.Name.shift(-1))
7 i- x' t0 Z* c* `, ^s.str.count('\?').groupby('Name').sum().sort_values(ascending=False).head(): d- l: Q7 Q/ ?. d' a0 K. \! Y( q
: a) z/ o! a; a1
+ j" L. N, A* m8 r) B2+ L \" W1 C F X8 F9 G+ T
3/ w0 H: Z7 j" z; V. U! F
4
& p: g& e# W2 ^5+ V& ] h; ]( J% Y9 I& J+ B
6
, W' [) g. ^4 S/ ^2 }5 L7
$ J7 T9 G. N; k( [- Z m. d$ D' F6 {8( ? e5 t1 B$ _
90 h* B+ B. b- N6 Z, Y: u/ y
10
8 p# s& b7 I/ S; ~! F11 B! ^ u% H* {6 N% W* c$ k
12
' k) V* u4 \0 R9 t6 W# M13
3 J2 |3 h0 o6 D5 n& c14( K4 a# C6 w/ Y& M, p
15
2 m& {. [% @, |) {7 q16
: ~; s t* e i) I3 f: h17" }. U$ o" e: N. Q; u0 @
第九章 分类数据$ R8 ]% _) ?* Z# X9 w& \5 p
import numpy as np" } k+ V) A: U2 k. L" t: _! L/ f
import pandas as pd
- G% x. E* y; C: P6 s1
& h4 |; d) Q3 |% ?, a: o2
6 E S: ?7 p; t' p7 Y! B' G+ `; g) @9.1 cat对象
; B0 V: w3 D& d3 [/ P9.1.1 cat对象的属性- Z. R5 }% n+ D
在pandas中提供了category类型,使用户能够处理分类类型的变量,将一个普通序列转换成分类变量可以使用astype方法。2 D8 ]( i2 G' }+ S
|( m4 j1 }8 I# X
df = pd.read_csv('data/learn_pandas.csv',) E! f1 h: b5 Q+ Z4 r
usecols = ['Grade', 'Name', 'Gender', 'Height', 'Weight'])
: m/ L# z- j$ ss = df.Grade.astype('category')
1 Z: U$ C) w* `# S) q" g: H& r# E* E/ G3 l( P
s.head()
* b- a; R L% p$ ^+ g$ ]4 r+ tOut[5]:
7 o! b' Z3 }- D' s, M$ f& F0 Freshman9 O# Q+ b" a5 T/ n; O. y( b- `
1 Freshman5 Q1 N% t9 _: L; ]7 }
2 Senior
, I, y4 O/ f; X, O, @3 r3 Sophomore
7 i0 M, _' r$ A9 W4 Sophomore
+ |( y4 w' N a* `4 zName: Grade, dtype: category {: }6 R( B# z2 e! s
Categories (4, object): ['Freshman', 'Junior', 'Senior', 'Sophomore']4 f0 A/ \3 x2 o. _ g# s& h4 n* t/ Z
1+ J7 R" {. I5 l# o
2
4 t& z/ ^# x5 v& ~/ E- [3 L* |3) J7 I0 J- ?9 C- |9 \
4
7 E# C" J( q. t3 M0 V% m5: q- u9 g( n! L5 T% R- H
6- A/ Y% x- _9 Y/ F& c3 R3 r
7
( @ O4 t. u4 y) M' o2 N82 D r$ ^$ \- m8 i8 P; z
9- J5 Y0 E$ m8 L9 h' H( B, o. q
10' K7 ]! {5 Y. x6 ~
11# Y2 ]- I6 Q% f. _
12
& [+ i/ V: N0 n2 p {8 Z13
$ i5 H$ {& k2 W, o- x 在一个分类类型的Series中定义了cat对象,它和上一章中介绍的str对象类似,定义了一些属性和方法来进行分类类别的操作。
& X0 h( [2 }( `+ U/ j
& n+ S B) E: g: i( Es.cat0 |# d/ s. {' [1 N w& K1 P/ E
Out[6]: <pandas.core.arrays.categorical.CategoricalAccessor object at 0x000002B7974C20A0>$ }5 A" X! a4 V
1
t; B9 |: _' n3 C, m, v22 G$ s1 {8 E# p
cat的属性:# V% E, Z- [4 s) w
4 z- Z9 j4 w: m8 |* Jcat.categories:查看类别的本身,它以Index类型存储
% w8 O3 w# _, \+ Ucat.ordered:类别是否有序) s& j. Z7 j' n- Q5 ~& J' g
cat.codes:访问类别编号。每一个序列的类别会被赋予唯一的整数编号,它们的编号取决于cat.categories中的顺序% ~5 T2 s/ G& l" q- u2 C# D
s.cat.categories
) e& G- e; W4 Y6 [# L' nOut[7]: Index(['Freshman', 'Junior', 'Senior', 'Sophomore'], dtype='object')3 A. a' o" |7 @# x+ O$ X6 X
% A7 L4 S9 |7 e7 z3 G
s.cat.ordered5 a3 g7 Y, `: g. ^* y+ {: B
Out[8]: False
) X$ Q, }+ t* ]* a# u4 P1 Z8 w. B2 N
s.cat.codes.head()7 Z/ d' b- a C# k7 s
Out[9]:
2 q- P, ~0 j3 Z0 0
( Z" k J# i5 X6 }0 m' U1 0( \' R, w: P$ M- P
2 25 U6 j5 B; u" b- X
3 3
' t. v' [: |, f; l5 Z# J4 3
" g* q: Y- _* A$ }% ?dtype: int8% K4 ]$ ^* v- k) Y$ c3 N( m7 E) H
1
8 k0 s; _" p7 _2
+ `2 E; ~4 i0 s& ? D; x& k* H33 J1 t" J% F$ I" n8 m" ]& }! I
46 j& G6 O% i. a8 b. ~
5% W2 I0 ?+ s" }+ O) G- c: n
6+ Z% ]. V) w4 e0 w
7$ p, C' s$ P: x3 y
8
( r$ k/ d' Q# Z+ @, ~: G9
& L: r0 C, l' n8 ]7 J' F- H10
9 J4 F0 C! _4 e" i11
5 F! l# H" e3 x8 a, N2 S; A5 T12+ L8 H3 l# g; d0 T% q/ E
13- U7 Q/ z# F ?7 ]+ U% |
14! n* g9 z+ o1 ]
9.1.2 类别的增加、删除和修改
2 }* g# D2 [9 Y, G1 |+ ^% Q8 u: x 通过cat对象的categories属性能够完成对类别的查询,那么应该如何进行“增改查删”的其他三个操作呢?% J7 |& u( |8 B( w
, v- R% J, g/ O3 A【NOTE】类别不得直接修改
. |8 ~; D; w/ s' r在第三章中曾提到,索引 Index 类型是无法用 index_obj[0] = item 来修改的,而 categories 被存储在 Index 中,因此 pandas 在 cat 属性上定义了若干方法来达到相同的目的。# I# `: m: m0 A. G$ f* H& a+ a1 w6 g
& e k; W" o8 x6 P( U9 L
add_categories:增加类别4 g' T, }- u( p& }* T5 ?
s = s.cat.add_categories('Graduate') # 增加一个毕业生类别! ?4 } `/ F' A _% A% x+ Q, h
s.cat.categories P# \. F1 T3 B
; i$ A9 e' a# S. b3 c9 B( A' l1 }
Index(['Freshman', 'Junior', 'Senior', 'Sophomore', 'Graduate'], dtype='object')
: [$ Z+ F2 w3 a1
! g9 _/ y$ V9 t+ K7 s' O' i: _2
! u) s( w0 N" @) W, [6 Z3
% D: y& i; C7 U* l' s" W# n4& Y$ I3 T5 \* ^5 S6 B
remove_categories:删除类别。同时所有原来序列中的该类会被设置为缺失。
: f5 |! G# a; m1 bs = s.cat.remove_categories('Freshman')
& i/ n, l5 _$ w: W6 S# n/ Y" g& b8 |$ f
s.cat.categories4 w- |# H; h% O; s% a8 T: l8 T
Out[13]: Index(['Junior', 'Senior', 'Sophomore', 'Graduate'], dtype='object')
+ ?+ P: Z. D4 O- s2 [: ^5 o6 Z7 X& i0 z6 k p1 m, b
s.head()
K5 R$ p5 _3 w7 ]% D- |4 r( L# q+ COut[14]: " H4 _& }' g' U1 x1 c
0 NaN
: f! L: `- A1 x: n1 NaN) d# o8 L& m) W' F Z
2 Senior
% Q, j; o+ `8 ]8 e6 q3 Sophomore
+ q7 b% h+ K( u: [; F4 Sophomore* f+ s2 E; ?. I _! E2 x, v
Name: Grade, dtype: category! k4 q, C! R* Z' ?! s7 A) x- N2 y3 B
Categories (4, object): ['Junior', 'Senior', 'Sophomore', 'Graduate']
& e3 X0 ]( M7 P, x5 d1, [3 W8 N9 s" S" F |- b7 b
2
7 M3 `$ S2 h0 y: Z3
( r. A( C l$ z0 L% L( ?4
( }: v3 a3 L& G50 y9 D, M( D% k3 m* h3 F# C
6
% D: J) T& E/ S' U3 p. t& j7) q2 W/ u2 @$ V/ e5 ] H* \
8
2 H5 X0 _/ R( f$ S9
8 n. I8 |$ `# u8 K2 ?! o% b10
+ y: T) b' V2 K/ A# b* J11
" {& [7 O9 c) e, g+ P12
* @0 i3 r. j, n4 j13" |/ g i2 [1 _5 ?+ y( j. M. d: g
14
6 }+ a( b$ Y6 V" u' ? T' z- uset_categories:直接设置序列的新类别,原来的类别中如果存在元素不属于新类别,那么会被设置为缺失。相当于索引重设。" Z [- C4 I$ q I
s = s.cat.set_categories(['Sophomore','PhD']) # 新类别为大二学生和博士
5 R# S$ R) P3 [( U1 P$ os.cat.categories8 R8 G& D: |; w
Out[16]: Index(['Sophomore', 'PhD'], dtype='object')" V( X3 [0 I8 l
+ N6 z! ]) m; z& D5 @" S
s.head()" y2 H0 W# G- A" Z4 M
Out[17]:
) {2 h2 i; O/ ]% d( [ q0 NaN
' b; Z9 f5 h( R2 Z+ c1 NaN& h ~) b+ ~$ _: ^
2 NaN
9 `- i9 d! `4 U2 T4 z/ I3 Sophomore
$ u) U$ r. u- T: |# p! O" |4 Sophomore; ^8 P& h0 O E( \* p
Name: Grade, dtype: category
+ d0 O/ @! a% w, Z& `. x* JCategories (2, object): ['Sophomore', 'PhD']$ [. O6 J* j, D3 }% `$ V) z& {
1) K2 h8 k/ O: h
2
. N3 j2 P1 N7 \+ n' O. _- k9 O+ e* `39 p: n0 Y6 o" G8 c7 z. \+ }) s( _
4
% E6 r- N( x# L; ~! \5
' }. u; A$ F; |* B, g# s6. b; L7 P9 t$ y( [1 P
71 b4 ^9 N' [9 i- @
8% ?* h+ ^# ]6 H+ p( z% y
96 h1 J7 f! a$ H. K1 K; s }$ [6 x
10# k! d" z* l' i' I5 C
11! a5 y2 _4 u2 K9 d9 x
12" D1 _) G. ?# p! R: ]/ N1 u: {
138 K- Z0 N2 s; y% i! { N: O8 T: ?
remove_unused_categories:删除未出现在序列中的类别
6 F- }/ @% c C3 q. W7 d9 js = s.cat.remove_unused_categories() # 移除了未出现的博士生类别
5 @- f0 y% D9 `" N% S, {s.cat.categories
! i) ~4 s$ X8 r) C/ m/ p% F4 r, }& {# E3 e# r
Index(['Sophomore'], dtype='object')
6 _$ V4 O. e: b3 o# @) j! k. K1
! L, X) l% u, H: u9 }' l( |& y+ D7 T- ~2 ?8 V2 J) x) Y, G) F- s N
3: {/ V2 p, c/ M7 @
4& y; ? S( g$ \# R* l% O. F
rename_categories:修改序列的类别。注意,这个方法会对原序列的对应值也进行相应修改。例如,现在把Sophomore改成中文的本科二年级学生:
( D' P& B: g4 Q% _5 C/ y# ?* ys = s.cat.rename_categories({'Sophomore':'本科二年级学生'})
$ s( _3 s3 F" a" K- y/ e, ts.head()0 F# ]1 y, a8 l6 u$ i2 X
" k* i5 U# V" {
0 NaN0 G- j7 `: Z+ o4 V6 F7 u9 Q
1 NaN' }/ t6 \$ p. ^- {! I0 B5 n1 q
2 NaN( m! c# |1 \" I$ Q
3 本科二年级学生
3 m# Z8 U$ w& E d3 e4 本科二年级学生: o; b% B! \- w7 q% t/ h% N
Name: Grade, dtype: category
: b/ |$ [3 Q% o4 XCategories (1, object): ['本科二年级学生']0 Q1 H" E. _& V7 e; _5 J2 j0 m. D
1+ y. T5 a r( o+ j) E% {+ c. G1 _
28 m4 |1 e$ R; ?" \- t( N! |" i
3
3 I. q- H, R0 S2 M1 P' ^7 Q4
) x/ j% y/ Q+ c; ^9 A5! h8 h2 e, D, }0 g3 P$ v& @5 r1 w0 j, _) o
6
4 K$ f* b* v3 m' n8 ?& d2 ~7- l; ~) v( U# ~5 f; l
8
6 G2 V5 x: h( o9; [( h2 l% i1 [: D; V0 W
10; @0 Y8 j: N l: N
9.2 有序分类2 v8 C' h% t6 M9 @. s3 `
9.2.1 序的建立
6 s% Z# X ?6 q* ] 有序类别和无序类别可以通过as_unordered和reorder_categories互相转化。reorder_categories传入的参数必须是由当前序列的无序类别构成的列表,不能够新增或减少原先的类别,且必须指定参数ordered=True,否则方法无效。例如,对年级高低进行相对大小的类别划分,然后再恢复无序状态:( }& \% ]& c6 E) E
w! }1 k, d, m) ^
s = df.Grade.astype('category')
, S: }9 a- B& ^, `2 J' Os = s.cat.reorder_categories(['Freshman', 'Sophomore',* S J- M5 m0 U3 m) o# l/ u
'Junior', 'Senior'],ordered=True)
# i& C1 n. f+ e( c: j4 G" Ps.head()
. N4 d2 `' U4 z& {Out[24]: * y0 q+ n% r$ V
0 Freshman
7 b7 k' t5 o5 [! @# T1 Freshman
4 Q7 x+ e) E) k5 h% K2 Senior/ D4 I4 S' K& S) K" j3 ~/ _# Y, G
3 Sophomore2 D0 o' ~0 w# s
4 Sophomore8 [& ^# ?# d c' I9 n" D! X
Name: Grade, dtype: category
/ k9 b' T' k9 K( E7 B! P- {1 ]7 yCategories (4, object): ['Freshman' < 'Sophomore' < 'Junior' < 'Senior']
7 J o8 s P9 k+ A- @( H5 a) V& h9 g: d$ ?$ L! t- ]
s.cat.as_unordered().head()
5 ^- x7 _2 |- b! S) z- cOut[25]: ; I; J6 g, Z! k4 C1 @! X# P
0 Freshman
5 J/ ]0 w" T! _2 j3 ]1 Freshman
6 h! Y, U! R% I3 T( G5 W9 ^7 }% \2 Senior
* b( i: g: G& _& H3 Sophomore/ q# t* ]3 J* @( v1 ^0 l- A
4 Sophomore
9 u5 I1 N0 U0 o6 K' f* [' {Name: Grade, dtype: category, }8 G+ g. o" q2 V5 O3 f0 A2 M
Categories (4, object): ['Freshman', 'Sophomore', 'Junior', 'Senior']
; j' \; l6 U2 k) _5 B" F1 a# I7 |+ t. ^, K V
1
B3 q) j( ]! R. |3 u9 i6 y# d2
! M* ~! ~- l8 c, z" F) ^3# P* q, |" O N- H" {9 i
4
2 Y9 x( F+ z7 a0 y7 p5
7 |2 p# {& t3 }67 l$ v, |: u9 Y5 K6 z
7
/ a1 s1 o0 ?( g; p8
, {5 g; _1 `, ]( m7 c/ N v$ a9
) R( O; q C. }( _+ @! _- ?10
0 n/ C8 }6 G4 N$ j* r11 D6 N7 I2 N. S% m2 Z* D. m
126 X# ^( [# Y Q( {
13
0 Q$ e$ q) E5 ^ @1 q) i141 q) I' ~4 s/ ~/ R' Q1 }7 i
15' r: F0 I: O3 ~5 |8 n9 H
160 @" _, o$ y( d& S
170 j$ a5 o; O! I7 ~6 W1 [
18% ^0 T; g! b1 P. c1 M
19% a; k- w% j' p; G$ T( S7 V
20; d2 r4 W* s* v! I9 l8 ^
215 L7 \) M4 s/ O9 `& }
222 t' Y" Q- F2 x% |( h5 M6 x" d
如果不想指定ordered=True参数,那么可以先用s.cat.as_ordered()转化为有序类别,再利用reorder_categories进行具体的相对大小调整。
2 ?6 i; }2 c \! m4 J3 a4 f0 W3 O# S
9.2.2 排序和比较! X, R) X% `0 c2 C6 Y4 ~
在第二章中,曾提到了字符串和数值类型序列的排序。前者按照字母顺序排序,后者按照数值大小排序。
9 `+ C! j4 ]8 z; J+ S4 R. f
$ |/ c) d: c; t5 C5 f# J* H. { 分类变量排序,只需把列的类型修改为category后,再赋予相应的大小关系,就能正常地使用sort_index和sort_values。例如,对年级进行排序:
( s1 s. L( }; _& ?
% X" D, b3 |! J `/ m- H% \# H* ?' xdf.Grade = df.Grade.astype('category')! J1 q4 q, N c( i5 X
df.Grade = df.Grade.cat.reorder_categories(['Freshman', 'Sophomore', 'Junior', 'Senior'],ordered=True)
/ w" d3 x' I: T% S1 L% L1 |df.sort_values('Grade').head() # 值排序
! \. H2 T- ~1 F; T% @% MOut[28]: - k3 N0 A4 b/ B4 z$ E
Grade Name Gender Height Weight
' L' d0 M% W2 I5 d9 Y$ }0 Freshman Gaopeng Yang Female 158.9 46.0
" m& a: u/ j6 {; ^4 V( `7 Q( ]105 Freshman Qiang Shi Female 164.5 52.03 _2 T P" d, F. }3 I( @# _
96 Freshman Changmei Feng Female 163.8 56.0
. Z# H5 p: J0 \; _1 r* {88 Freshman Xiaopeng Han Female 164.1 53.09 ^+ V6 [3 V4 @3 M$ b$ x) z
81 Freshman Yanli Zhang Female 165.1 52.0
O5 {% O$ M& o0 T
G/ G, G+ c9 H; z2 Fdf.set_index('Grade').sort_index().head() # 索引排序9 _) L: ?; ^, ^3 |
Out[29]: / p6 Z9 ?; i) n+ n/ L0 D5 z8 ]; }
Name Gender Height Weight- l. Y! [4 a2 ?3 R8 `3 t# ]
Grade
8 T" z# T5 M3 q9 q' R! QFreshman Gaopeng Yang Female 158.9 46.0
; e x; f1 _$ d k8 LFreshman Qiang Shi Female 164.5 52.0
. V1 U1 o9 Y4 x4 J7 A+ WFreshman Changmei Feng Female 163.8 56.0
; `, c2 M4 E1 A" x' E! }$ h( tFreshman Xiaopeng Han Female 164.1 53.03 o* b$ L l3 I1 d5 Z1 [; \
Freshman Yanli Zhang Female 165.1 52.0
4 h( }% |+ h j5 z$ R2 O, A, V/ u) I) |* Y
1
$ i- `. I) N4 C; ` ^28 c( |7 a g0 v7 p0 |0 u
3
- F2 y: z: c: @5 G- U4& p* p- V$ z1 V z4 `; d( n
5 b2 u6 G" ^ z2 Y1 u! c) N0 g: ?, [
6
& l' o; l: j+ _/ c, W6 I7
5 h p8 {: z# B' B: y1 D8
8 x# [% f3 Y c/ Z8 L9 t2 r: C9
* ~ h0 U! H$ ~! p& L+ U10) |2 @( V1 F; o2 t" D+ ^
11& Q5 y0 z1 I2 Y- ?4 h0 o
12
1 N1 \2 P* S; {) P13
4 m; Q& e1 y* t/ ]% c/ G5 S, I; D14; c, i! E* x0 ]* b
15( G0 d9 z6 L6 G9 p
16
. N& W1 |$ u1 L& m) m; _17
! u% K" S( J, Y; i% K: T4 e3 B18
& d0 x7 n) m2 o19
b. ^7 l; e. m# z) P20
; @$ y- p" r* z/ Q7 a 由于序的建立,因此就可以进行比较操作,方便后续索引操作。分类变量的比较操作分为两类:6 r; s3 G- Y2 H; t, K3 R; j
1 F) t1 M" u! F5 Z0 ]/ e Y==或!=关系的比较,比较的对象可以是标量或者同长度的Series(或list)。(无序时也可以比较)
& {& O/ D$ K. T. I>,>=,<,<=四类大小关系的比较,比较的对象和第一种类似,但是所有参与比较的元素必须属于原序列的categories,同时要和原序列具有相同的索引。
' j" ?$ [! `4 t7 g3 Kres1 = df.Grade == 'Sophomore'. v) r' ~( e. b8 T( q' g
/ q% }2 ?. J; l# F' v L# lres1.head()0 c2 h7 E. @+ L' i& A6 ^
Out[31]: - S! x& j8 Y3 R" R
0 False
' k! Z+ _, K' W0 C& N1 False
1 A9 O P6 g$ c' A% t. o; Z8 o2 False
+ ^0 i2 P9 I: F* b! ?* N3 True
/ n% c$ v5 ?/ r) \2 w7 E& Z4 True
% g. l5 ^4 m' fName: Grade, dtype: bool' M- F/ a/ T. F, I; n$ v% }
8 J: ]' Z: g) T( mres2 = df.Grade == ['PhD']*df.shape[0]
" l8 T" b7 O( Q1 r( r1 e: N
/ n9 l, N0 \. x$ ^- C2 [$ C* Pres2.head()- k) J+ n7 N% c" T. B
Out[33]: ; P! L+ e( ~6 K w2 @
0 False
+ \0 |2 p* o- p1 False
+ p* m# e. `& U" S6 i* D& t( I2 False
# K- Z# O f+ E1 l% V3 False, M# E, Q' ? s9 K
4 False
" W( J. n- Z6 o4 MName: Grade, dtype: bool+ H6 _ l) W6 o( P& I! U$ u
' W; n, d2 |7 S' z( W, N7 \
res3 = df.Grade <= 'Sophomore'
5 b' E. o( ^8 N. o" w% B
: `$ _& P! A; c+ v' }5 D7 K' ures3.head()) L! L" l$ Z, p- V# J. `
Out[35]:
8 y3 z% [9 o9 w3 \: b0 True
5 l! W+ R+ J4 H1 Y8 l, q6 _1 True8 F; }3 e& x; ~/ w4 _: x
2 False
M* j& ~( \1 i: u3 |; P3 True# X G+ ^8 L7 \1 e+ i: a
4 True
3 c, h6 S' g' r; Z0 [$ ^- ?/ sName: Grade, dtype: bool. x+ l# S7 \- u e5 g+ [/ x
0 L' Y: m+ a$ l9 Y7 y
# sample(frac=1)表示将序列随机打乱。打乱之后索引也是乱序的,直接比较会出错,必须重置索引。3 i0 Y) v7 e6 ~) T$ o: m6 j
res4 = df.Grade <= df.Grade.sample(frac=1).reset_index(drop=True)
6 e& ~6 `& B8 {: z5 w" B: V7 A
6 j6 @. j' _) Y! z& b- Rres4.head()) ^8 z" b3 L. X
Out[37]:
2 A3 a; q/ t1 c5 e! h. _0 True
' q C( t, K' g. ^8 c; ~1 True, Y' L, ]! p3 u; F: \+ v
2 False Z U( `* T1 z# H& G* [
3 True
; z; m% I3 L7 B7 e9 s8 O# Z4 True o) v" r' f- x. y- {- p+ P
Name: Grade, dtype: bool
h- q) p6 Q! K+ ]1 a" s
' v; H' G$ f/ N' r% y7 m" ~; j; j9 P: e1
|9 N% A% S5 H' X3 C* U- W2$ f: C2 e' m! _& V. u# Q }
3
. T9 G+ u" o* m: Z- b4+ l9 a R( P6 O9 [6 t2 V/ H
5
( }/ V, A2 Q$ ]6! k) `$ L% {6 _: G1 U3 B
7+ `- c( v& w6 f$ d+ O) s& P: f
8
: o7 @* G& R- | e- p9% T, B- B$ F# y5 Z( N
10! m* k. e' ^3 W2 T6 c' m( e
112 |" q! r" z% f; g
12
* r+ w: W6 m7 o0 H6 S133 p1 g' g8 S; r! D
14
6 L0 N8 f) d7 j( }$ y, p15
( P ]' y, Q- Y0 |16
# z! l+ M0 A- g, H" X8 ~17
8 \; K8 t- e* g- k18
: A% H# k4 u: S0 L% U& H4 S* b191 V: }0 G/ h) J T! ? J3 l
200 u; `7 O# P: ~" L+ Y$ C3 x# u
21
6 f1 a) w* V$ O22( o5 m. I: T/ I0 _# c
23
% R- ^+ A: g4 M& f, |0 Q24 Q1 `( t* j: g
25: m" V2 K% h5 O$ I& ]- ~
264 a- m9 ^" {* A Q- {# q
27
* E% v1 [6 P4 Y+ e \# ?8 g, E9 _283 f1 M% [/ A" q, b4 q
29 b4 v/ L6 i; b2 l" ?" U
30
, W9 @4 [$ V$ E$ Z31* U5 _7 w- C- T2 \. q. E! | |* N
32
- G( G9 L2 G! [: g33
/ i3 H7 h8 m$ h& d1 I34& v" n9 T8 v$ f
353 p8 S/ K) u" w: T
367 m0 _3 T( V! [# M# o) F9 M& n' M
37
; j l% H% y0 U* f382 T7 Y: f9 A2 v, s' l1 C# Y$ X+ M$ u
39 `) s6 a9 @' [
404 e: Y( l; L: J( p H
41' o( N# Y+ X" H$ |3 W0 h# S
42
4 N7 s/ N+ m$ o) ?438 P8 y( o+ }" U8 Y9 R+ d Y
44
- z9 x, I, F! Z- v6 f$ K) a4 x9.3 区间类别
7 k% d5 s9 [( b- `# q9.3.1 利用cut和qcut进行区间构造
& [; J {% t, U J 区间是一种特殊的类别,在实际数据分析中,区间序列往往是通过cut和qcut方法进行构造的,这两个函数能够把原序列的数值特征进行装箱,即用区间位置来代替原来的具体数值。# V, K$ }9 U# p/ o) O7 O
2 P7 B8 S+ a; j* acut函数常用参数有:8 R0 B8 j6 b1 s' [2 D1 z- v& g: w/ d
bins:最重要的参数。
/ ]9 L2 a; b( P& B如果传入整数n,则表示把整个传入数组按照最大和最小值等间距地分为n段。默认right=True,即区间是左开右闭,需要在调整时把最小值包含进去。(在pandas中的解决方案是在值最小的区间左端点再减去0.001*(max-min)。)4 i( Q+ ]' j& k, O# D! X
也可以传入列表,表示按指定区间分割点分割。
0 a1 A! J. y- \% \, X 如果对序列[1,2]划分为2个箱子时,第一个箱子的范围(0.999,1.5],第二个箱子的范围是(1.5,2]。4 X9 x0 i$ s, R! ~7 q8 I
如果需要指定区间为左闭右开,需要把right参数设置为False,相应的区间调整方法是在值最大的区间右端点再加上0.001*(max-min)。
0 \6 s9 _1 A, [( U
4 J, K' W& W% Z5 {s = pd.Series([1,2])5 U0 T1 E/ {5 C. W. w+ J
# bin传入整数
9 d, H' ]2 S K1 {
3 a7 h9 I# W& W) }8 \6 I/ u% Ipd.cut(s, bins=2). i1 Q0 I0 G6 S' o r
Out[39]: % N7 O( r. P/ i# d
0 (0.999, 1.5]
6 S1 J& N( d9 e1 (1.5, 2.0]* S/ k. J! J8 V# G0 V
dtype: category
3 q2 ~( ^! W0 R, cCategories (2, interval[float64]): [(0.999, 1.5] < (1.5, 2.0]]
7 g+ J8 h* M% R! Z \4 t4 _( g# Q. d/ t# j1 Z
pd.cut(s, bins=2, right=False)
8 n! \8 L* O8 h; H! Y- f! G( JOut[40]:
' ]; p. k- {4 X G0 [1.0, 1.5)% n/ W0 G2 P* T
1 [1.5, 2.001)
7 Y9 [% @( B& V3 z/ f# {dtype: category
$ a: r y8 e9 y ]' _Categories (2, interval[float64]): [[1.0, 1.5) < [1.5, 2.001)]& I) q% d, E1 e1 M. P. J5 i o
9 @) \5 U6 f. f; J
2 A& [) s8 \- |( c% _: u# bin传入分割点列表(使用`np.infty`可以表示无穷大):
; g( \- X8 D1 S/ O1 C$ h1 ^8 l! Kpd.cut(s, bins=[-np.infty, 1.2, 1.8, 2.2, np.infty])! F. e6 o8 Y3 z5 t6 G" r- y( N
Out[41]:
: i) j. \/ r: t0 (-inf, 1.2]; | x$ V! a# l/ \4 d8 m5 ?
1 (1.8, 2.2]
' B; M7 E3 ]" K- n9 c5 ?; E7 Y& Cdtype: category
4 L" d7 [" M' ^5 p3 wCategories (4, interval[float64]): [(-inf, 1.2] < (1.2, 1.8] < (1.8, 2.2] < (2.2, inf]]: K x) _+ N- B8 \( P
$ c- e' l7 M7 d4 [4 h+ H% D
1: x B0 i) W1 T( X- ~
2
& u2 e: J3 P' x3
3 F7 x& M$ G" W5 P1 e4
- `. |- O% L, V: x. w1 x52 h- n: R4 k0 Y$ }' X( c
6
: P+ x: l9 t/ E! v. B/ Z! N75 ~( a. `9 U. E* l. ?( V
8% W1 l4 J" G* s" ^5 O. b
9
; Z: g, j) u" q10
3 n& t8 C1 t1 ]; f( h11
7 M" _ t! g* @7 w1 r, R/ X& `0 i12
4 |0 b# w6 U m1 K% ?136 M: R" r# \; ^5 c* w# J5 P
14
. A, u& K! E+ C153 |# {% [+ f# Z6 I, C( Z* o n
16
0 T' t& u" T4 ^$ J5 ^" n7 ?7 J17+ t+ O$ P) @* t2 i; A* L/ D
18
q# r' m- \; ^9 d. `$ D19
4 H$ _- r+ g+ h$ e- M20
" H" p3 K3 g7 w4 r4 @6 b21
7 `; |2 @- g# f* g22- |+ c6 Q; R5 u. w
23
. Q4 b4 J1 r+ o9 Y- m; _6 l24
7 n f' K% C. n: i0 |6 c25- S# f7 q1 b( o5 i ]2 r, M
labels:区间的名字
3 k9 A j" y0 ?3 J* Y' x: r6 Tretbins:是否返回分割点(默认不返回)
/ u) E/ m& H; W% E+ y2 ^ Z默认retbins=Flase时,返回每个元素所属区间的列表- @, [4 @* N' J% O1 x8 W
retbins=True时,返回的是元组,两个元素分别是元素所属区间和分割点。所属区间可再次用索引取值) h5 c' V3 D" `2 v) A
, \- i& K& m! [4 T5 x
s = df.Weight
6 C; S+ s+ [' C1 P* Yres = pd.cut(s, bins=3, labels=['small', 'mid','big'],retbins=True)5 i( Q% E6 ^. @/ \# F9 ^+ p+ h/ P
res[0][:2]
% `5 S6 c2 e; Y! ?
+ W9 J4 U" @+ W! ^+ a$ S4 P; ?Out[44]: 1 e# @ C/ p! x
0 small
3 ^( S+ J( T: J! T8 j: t1 big
0 B$ E$ o8 B7 I. T# n6 gdtype: category
0 z* K/ a$ n% O% ?Categories (2, object): ['small' < 'big']1 H$ u. X P7 O2 F3 g
4 }: p4 O4 B# n) T$ s- A
res[1] # 该元素为返回的分割点, k9 [) Y7 ]4 M. v0 v6 v
Out[45]: array([0.999, 1.5 , 2. ])+ k* r1 x' Q0 l- [
1% y1 h5 X" |5 }4 X( p
2
2 d- O3 z$ Z% a$ Q% A3
1 ]3 U7 T- O7 ]; e4( @: `0 i( v0 X E) c
54 [7 T) t, _) @6 M( O/ z9 }
61 J$ j: J8 ?0 |$ ]3 [
79 Q* x: M( W# K2 @/ v! @$ X
8
/ f' \% x0 b+ K. c1 n& a9 B$ D/ {9
- f! m+ U u4 @! @1 \104 u& I8 E2 A8 }. R1 l( B
11* p+ Y+ I3 Y" _; ], S8 ?, b
123 W) {3 l( J8 L3 Q8 r+ o* X
qcut函数。其用法cut几乎没有差别,只是把bins参数变成q参数(quantile)。
" ?* t# |$ L$ ^# o$ F2 w: _5 b: G9 x- Bq为整数n时,指按照n等分位数把数据分箱. D8 V$ ^; |2 E8 t. R& u
q为浮点列表时,表示相应的分位数分割点。+ X& S$ R! W3 L" ?
s = df.Weight( j) y) v, k5 H- N' r! A& N
" ?( e% L+ m+ P
pd.qcut(s, q=3).head()9 i! L1 c4 G7 O% ]5 X, S- Q8 M. s: {
Out[47]:
0 j$ o9 W J& G4 @0 (33.999, 48.0]+ l$ F6 q2 O% ~4 h( w1 m- Q- u# {
1 (55.0, 89.0]& \/ w8 b2 R4 V9 e4 {. y
2 (55.0, 89.0]0 H- _$ B l/ ^8 o
3 (33.999, 48.0]/ C9 y2 W: r% v7 M$ I
4 (55.0, 89.0]' l9 ?- D# G& |- F! s8 G
Name: Weight, dtype: category* J* E, t" j+ w! i& h2 c h$ ]
Categories (3, interval[float64]): [(33.999, 48.0] < (48.0, 55.0] < (55.0, 89.0]]
0 T' r+ V: d- G# `
: `3 T. s/ _2 Q4 g/ Vpd.qcut(s, q=[0,0.2,0.8,1]).head()* e/ |8 _4 v. P
Out[48]: $ _" h s _" J# \
0 (44.0, 69.4] I. E1 T: g$ u4 C) u
1 (69.4, 89.0]& w! S! N8 ]4 x% g
2 (69.4, 89.0]7 X \# }& n. p" P5 K/ s% {
3 (33.999, 44.0]- t5 f9 D- g. l' X* Y* _9 m
4 (69.4, 89.0]' w S& J r1 @& r# F+ J. ~4 w" K
Name: Weight, dtype: category e5 N$ D' r# r: C" d
Categories (3, interval[float64]): [(33.999, 44.0] < (44.0, 69.4] < (69.4, 89.0]]
: ~) ]" o" @, p& @# n& R7 K& A* }" p. z
1, f0 t. V- w h
2% U, L8 q! N- T3 X) x
3$ U) T' e! }& o2 ?$ _. r- m- `& B
4) }- l! R2 B! ~* f8 O; B/ j
58 |- u! B9 C6 Z$ c* [2 Y- s: E
6' z" {! ?0 o. V+ n
7
( ]1 {$ H- ~ r/ Z# |# H85 c4 t/ x% C- w2 m: Y7 h
9, |7 ^+ Z0 R8 M# m: C
101 P& L; L& z" }7 I3 l, Z' Q
11
& F$ d) _! ]& o1 T, r8 y( F122 [; s) t* ?7 T1 X9 L
13' R8 R {# c7 n
14
2 V' o) P2 L+ y15
! d" @8 y$ ?8 O0 ^16* m! m# t8 d( x2 ~( r; E" U
177 d) H; d7 I" a; l3 e
18
6 r- o; l: D% e# h4 K19
0 x! j, H% U5 U2 j20, `) o1 C4 i& |, U& l
217 \3 O1 l% G, J7 C
9.3.2 一般区间的构造
) p% ~8 ~4 w; Q pandas的单个区间用Interval表示,对于某一个具体的区间而言,其具备三个要素,即左端点、右端点和端点的开闭状态。5 x2 v! ~* ~0 ?# G$ m
; Z) |! S) `/ I
开闭状态:包含四种,即right(左开右闭), left(左闭右开), both(两边都闭), neither(两边都开)。
1 b4 b3 L! J: v5 r+ y A: K% Lmy_interval = pd.Interval(0, 1, 'right')
0 @) N, F& k- u4 l. f3 W" \ [5 r6 ]. i; T
my_interval4 d& o! k9 [1 x+ D% t! d
Out[50]: Interval(0, 1, closed='right')
r. G. Y4 D: `" I1
5 ?: J9 @2 q0 j2
% |' A# @% w. n+ S0 c, h7 a' f! u# ^3* s& o( g) {0 y. o/ u( v" Q/ W) [
4$ r l# O6 |! r, X8 V
区间属性:包含left,mid,right,length,closed,,分别表示左中右端点、长度和开闭状态。- @2 F; v0 @4 l- N1 h& I+ s
使用in可以判断元素是否属于区间
* ]4 ?9 @) _- y9 }0 A* G: A- B用overlaps可以判断两个区间是否有交集:
0 \) `5 o$ k4 `5 U5 N' y: _0 d0.5 in my_interval6 e5 g" Z5 s- N+ l* `8 I/ ?3 c
- w Y( C0 p( }6 L) c# e3 `
True
7 c0 J& n$ k7 E1 B/ q: P1
7 {9 Y4 v7 m, J5 a2
" K0 `, ]% N P36 b1 B6 S2 P8 X; m- D$ ?% [
my_interval_2 = pd.Interval(0.5, 1.5, 'left')
# V7 T7 A# c7 y7 g7 m6 V! D+ `/ Nmy_interval.overlaps(my_interval_2), l) @! c) }$ q! P5 n
9 v( R5 v8 t! ], _3 xTrue$ M" n9 ^8 i" O0 D0 C% p
1* z. w5 k: B5 x
26 U" J: x6 U8 h) W, f
3
8 k4 X' d) R% X$ e7 j, Z4
2 I8 W- J0 G) V5 P: z' O pd.IntervalIndex对象有四类方法生成,分别是from_breaks, from_arrays, from_tuples, interval_range,它们分别应用于不同的情况:
4 }8 |/ P: m& ?- x7 c
7 j9 c/ M! g( |. |0 y7 K Xfrom_breaks:类似于cut或qcut函数,只不过后两个是通过计算得到的分割点,而前者是直接传入自定义的分割点:4 `/ v9 J; E' u( K6 w" L
pd.IntervalIndex.from_breaks([1,3,6,10], closed='both')5 } r% j# B6 B5 d) B/ G4 P0 C
, \, ^( `8 J) l
IntervalIndex([[1, 3], [3, 6], [6, 10]],
, Y9 P: V) ]1 s. c2 `0 c closed='both',
$ |/ Q4 { p( C: x6 ` dtype='interval[int64]')4 k6 O) P0 e; |" o! T0 m
1
; U& B5 Y9 L# I+ b2 J1 W K2
& D# t+ @* z6 C! o7 }3
$ a; l" W1 T$ @$ Z7 f4 z) m( q4
& p4 |, h1 m/ C) O56 x5 v& h7 N C0 T! ?& D
from_arrays:分别传入左端点和右端点的列表,适用于有交集并且知道起点和终点的情况:/ K4 G' I$ |& Z4 G$ f% Y" A+ |
pd.IntervalIndex.from_arrays(left = [1,3,6,10], right = [5,4,9,11], closed = 'neither')
; S8 \' [/ r& e' e% }& d4 R) F& _5 {4 c! n: T2 \
IntervalIndex([(1, 5), (3, 4), (6, 9), (10, 11)],
L+ v( E9 d, _6 O& i( p4 E* @ closed='neither',
* m* }& e3 x- ]8 ~% T, H: J dtype='interval[int64]')
# X4 q: H% P# M1 j5 C1& x" T( D( T1 w" q6 U
2
9 n7 l0 L5 X- d8 ^% f5 I, S6 r L; ]3
. E/ W* w2 X; I4- u: q% x) @8 a% r& o& q
5
+ c& t6 M7 L* |. n; w0 W" `from_tuples:传入起点和终点元组构成的列表:
$ r' O l0 p3 Jpd.IntervalIndex.from_tuples([(1,5),(3,4),(6,9),(10,11)], closed='neither')
: ]- n, o+ H" ]
7 y& H% U" P* n& C, m9 vIntervalIndex([(1, 5), (3, 4), (6, 9), (10, 11)],! q/ f9 F$ z0 M( i
closed='neither',% F* v$ V% c+ ^
dtype='interval[int64]')* Q" r- l4 F# S3 B2 V" s1 p
1
5 e- a( z3 b( J+ t28 Z% c, v0 x% m9 O& L; Q4 Z7 D0 I
3% g, {1 [) U; |/ B* Q
4" y) d7 T% `# l% j+ F
5) C$ b' G6 m8 }/ I, [: C. c
interval_range:生成等差区间。其参数有四个:start, end, periods, freq。分别表示等差区间的起点、终点、区间个数和区间长度。其中三个量确定的情况下,剩下一个量就确定了,从而就能构造出相应的区间:
* y8 x# W0 }! ~. D# L% ?3 Xpd.interval_range(start=1,end=5,periods=8) # 启起点终点和区间个数- y0 H+ A* J' h+ R+ Z
Out[57]:
) M+ D% z+ t1 f6 A9 U# m4 aIntervalIndex([(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]],6 D+ q- ^8 a, Q2 q
closed='right',
. R N% O9 P5 @1 x# [/ [ dtype='interval[float64]') V C' n8 c) }( c% c
3 [5 r1 |4 v6 X/ J9 Z3 j
pd.interval_range(end=5,periods=8,freq=0.5) # 启起点终点和区间长度
/ f* u. d2 X2 z- }7 k2 X6 _$ o/ oOut[58]:
) J7 i4 P; o P- i o0 `' fIntervalIndex([(1.0, 1.5], (1.5, 2.0], (2.0, 2.5], (2.5, 3.0], (3.0, 3.5], (3.5, 4.0], (4.0, 4.5], (4.5, 5.0]],
1 I" o8 ~3 T/ Z0 L closed='right',
2 D- P2 _# X1 ?8 Z* D dtype='interval[float64]')
0 D0 ?$ g9 k; ~. |$ U* u5 H1" P% _- }8 |! t0 d; j+ u1 g8 y
2
/ w0 L8 y4 Q% s; C r3
/ y1 D" A k+ e! v; }0 J- u40 e* T% L6 s, ~2 [% `
5
5 y) c# P. K& w* @6 m& R6
7 l4 `# H2 T1 `9 x+ {. Y7: O. @5 T! \. D3 l8 u7 x$ L
8
- }; j0 r# Y1 h7 I m% h- [. Z9% b2 x/ t5 o ?4 {0 Y( r
10: L2 G- l/ o; d6 K% I1 |2 x
11
7 J4 Z9 x4 q) m: M2 h' T! E/ k【练一练】
( i$ B) F7 \& P) i9 o% v# \9 B( M 无论是interval_range还是下一章时间序列中的date_range都是给定了等差序列中四要素中的三个,从而确定整个序列。请回顾等差数列中的首项、末项、项数和公差的联系,写出interval_range中四个参数之间的恒等关系。
. q. q: c c- b7 p6 p% ]. _0 G* n3 M+ k0 b
除此之外,如果直接使用pd.IntervalIndex([...], closed=...),把Interval类型的列表组成传入其中转为区间索引,那么所有的区间会被强制转为指定的closed类型,因为pd.IntervalIndex只允许存放同一种开闭区间的Interval对象。
* n- W0 F+ K8 d$ G/ i' Y& r6 O2 c
9 L# ~ K$ h9 a. \* s5 B5 V% \my_interval7 O4 |8 I2 V# o; Z
Out[59]: Interval(0, 1, closed='right')8 I9 r( H$ h3 W; _' _3 p: A
( s, r$ P$ `; F% m) X+ l) C5 N7 S4 C
my_interval_2( g; K5 M# Q! P
Out[60]: Interval(0.5, 1.5, closed='left')
; [+ |& Q+ n6 o5 \$ q( j4 c1 Y% c( _& V% g7 d1 l
pd.IntervalIndex([my_interval, my_interval_2], closed='left')
( p+ }! d& G7 R6 H* R" }Out[61]:
9 G% ^% W2 j4 Q5 s4 RIntervalIndex([[0.0, 1.0), [0.5, 1.5)],
3 g3 R% j+ ~+ ^; h closed='left',$ Y! o: W1 w+ |/ y
dtype='interval[float64]')
# x- [# j3 k6 u6 g4 L) q6 [: v" O+ W* M1
" l+ H& K5 c7 x! d/ u# s, |4 [1 V2
0 k1 J* M6 A+ x3 X% P3
5 W0 y+ _0 h3 ?' `4
1 t" u5 ^* v1 a" s1 x) y5
& z& g4 D4 U7 J% R9 \6! s9 t9 Z w. J* @% u- y8 o7 h
7
/ m; X# _2 a, Y2 x% I8
( u! ^' |( l7 b# ] u4 S E9
1 ?- c/ u6 D# B; X( u; l" |0 I10
% V) @! m* |% M; _8 |11% y( p* N* Z9 G( a
9.3.3 区间的属性与方法
) L4 H# ^3 m; P! E IntervalIndex上也定义了一些有用的属性和方法。同时,如果想要具体利用cut或者qcut的结果进行分析,那么需要先将其转为该种索引类型:
5 T3 v8 e6 s4 g% L
( i2 o; B$ j$ M7 {7 Ls=df.Weight" |1 M# m( k3 a
id_interval = pd.IntervalIndex(pd.cut(s, 3)) # 返回的是每个元素所属区间,用具体数值(x,y]表示 `* @2 t/ a/ u, j
id_interval[:3]
- _% c9 Z9 |5 o' p$ ]2 l, C3 O
! L; O2 K3 C: C4 Q6 @2 q1 ^IntervalIndex([(33.945, 52.333], (52.333, 70.667], (70.667, 89.0]],9 u9 ~7 f' B+ }
closed='right',
0 t7 N8 f& Z. V0 x, d3 A name='Weight',* z+ {# D2 m, e: q$ V
dtype='interval[float64]') Z. X* F, i& n. e
1
6 w- g, a# z, b' t) N$ U0 M1 d6 u2& J1 c6 W% u, O0 J6 U- h2 k
3$ v- h1 }5 f, N
4
) [- S3 P, r3 q5
- ^; D- B8 @, W4 E1 _6# U/ d9 d2 U7 D" r) T
7- f$ p# i9 c+ K: e" N! T u5 a2 u
8 o: ? q/ s% E/ |& j
与单个Interval类型相似,IntervalIndex有若干常用属性:left, right, mid, length,分别表示左右端点、两 点均值和区间长度。
- c. p$ W4 _+ I* o' f, F, r% \: Tid_demo = id_interval[:5] # 选出前5个展示
% @5 t6 R; g" I: M
" ]4 u1 Q [/ [7 b8 lid_demo
- ~) ]8 x5 d) T! C+ u# ^* A- [+ EOut[64]: 3 H3 b+ {3 @/ {/ W$ o8 m, W' \& R
IntervalIndex([(33.945, 52.333], (52.333, 70.667], (70.667, 89.0], (33.945, 52.333], (70.667, 89.0]],
. f* P1 c9 h0 _7 V# p, A closed='right',
4 D- E1 @7 ^+ n9 J& g name='Weight',
$ J& [9 h( k( I2 { dtype='interval[float64]') G, [4 S% A: H) |4 ]
0 W J) }# z3 `7 N5 h T
id_demo.left # 获取这五个区间的左端点, v( s$ z( ~* _# ]
Out[65]: Float64Index([33.945, 52.333, 70.667, 33.945, 70.667], dtype='float64')) T& m8 f" C* e7 z) F* N$ q5 X
; i4 s E0 D9 p# n1 j$ t5 j
id_demo.right # 获取这五个区间的右端点
! K1 Z6 R+ b$ T+ @Out[66]: Float64Index([52.333, 70.667, 89.0, 52.333, 89.0], dtype='float64')
: p- f4 V; u1 g( j9 d/ c7 R; t
0 C1 r# ]3 m- U# s" Mid_demo.mid
6 C% d# i: t: Q4 tOut[67]: Float64Index([43.138999999999996, 61.5, 79.8335, 43.138999999999996, 79.8335], dtype='float64')
5 p9 V1 }% E& S/ }2 ]4 d! s& n$ q& y4 U5 n7 m
id_demo.length+ N3 q5 r& k! n1 [! J
Out[68]:
: g/ l( f; {" C3 h) c- R- MFloat64Index([18.387999999999998, 18.334000000000003, 18.333,
" p6 k4 V+ K- @. p 18.387999999999998, 18.333],/ ]# q. |+ ?/ y8 D! w8 M+ j! |
dtype='float64')6 _5 e) o4 Q/ d% c
/ t& s2 q; R( y* c7 p7 y
1
# ^/ T) p3 A/ X! y' w# q) k5 B/ o1 x2
# _8 d$ d) b' R0 |3
; d: P9 w2 s' b% B4 M. ^7 }4
[, t1 e) ?! o+ h: u% J52 { G& U; c8 O, m: X% K! w
6( [4 Y- Z- q* m' T& X) x2 o
7
- f6 _9 Q6 d3 u9 k' A8
7 u' i0 w0 q2 N0 V T1 t9
5 E0 f# Q9 c4 E( q10
: f& l5 `# R; B5 u11) c0 x1 ]# w2 J2 A0 }" }
12 C; R* a! Z' i+ }) Z+ x/ ~) H1 ]+ V
13( H6 I6 g$ e( w1 u/ T# e
14
6 N+ ^* D @. C* z- M# B( d15! M d7 L: O; j! V' q7 m' Q
16 i2 X( F% O/ ^( O# ]1 k3 [' w
17
# \; J6 S. B ?$ w0 i8 B8 F7 c$ L18
0 v; U$ P# `6 q) ]4 H+ c8 m19
5 G4 _, e( n/ @* ?2 q4 _, r20
2 J1 v' K8 u( a' D" M+ O$ K21
1 r$ x, z# e; C% E5 g# m22+ V' J9 x' M. J d& ]
23
1 M7 _7 Y7 r/ f* B# ]- _! nIntervalIndex还有两个常用方法:
( F. v( h5 y A6 scontains:逐个判断每个区间是否包含某元素
^- z, c2 J. X# F/ ~9 L9 koverlaps:是否和一个pd.Interval对象有交集。& e5 i4 R$ J# K4 U# K3 A- o
id_demo.contains(50)3 |5 N. r, F) U* Y
Out[69]: array([ True, False, False, True, False])
$ Y7 j0 A1 N2 W" z5 w
, X/ V( }& Q* }: X" W7 Aid_demo.overlaps(pd.Interval(40,60))) J# M% ?' q& Y! X: @9 y
Out[70]: array([ True, True, False, True, False])
5 W, C5 d7 Q5 @9 A- G2 }' J4 Z. d1
3 M3 s: m" o3 p2
) |7 ]' D- f) b! f7 @3% P- a( \9 a+ o/ ?+ q! |/ a
49 P3 N* Y2 a" u! C6 v0 N# W: {4 n
5
" ~# r8 r) u- I& Y8 |$ o7 ^( h9.4 练习
7 w5 G: w4 q6 p( w" ] Z2 j$ JEx1: 统计未出现的类别" C1 b: \% u/ ~
在第五章中介绍了crosstab函数,在默认参数下它能够对两个列的组合出现的频数进行统计汇总:( N& g; q5 |# b+ u
, C6 s$ O3 a# q4 N! h+ x# X9 K# fdf = pd.DataFrame({'A':['a','b','c','a'], 'B':['cat','cat','dog','cat']})8 ~0 E* _/ N! T5 D% m* W) v
pd.crosstab(df.A, df.B)) M) F4 q9 ] S, G. [! ?+ t6 _+ p2 n2 ~
4 {' q7 z" P5 L0 A6 e7 i
Out[72]:
3 H3 u! t5 ]2 C8 M" kB cat dog
; p( K# m+ A8 oA
8 m2 C" L* D3 L: r7 oa 2 0) O0 ^+ {2 t! H! H6 [# p( r
b 1 0. w0 H9 n" g- T6 N
c 0 1
% I* L; ?1 F0 s1 d3 Y( V5 c14 S& J0 g/ D* G2 e# h" a( V4 m8 R
21 M, I7 B: q8 g |9 P
3
2 g) b" a& S$ v3 A$ F7 q3 z4 V+ m7 s0 _' `2 m% _4 Q
5
! r. d+ |% ~; Q6
" G: t+ b: }% @77 E1 N7 i9 B7 O8 Q+ s0 l
8
1 x( R" H# I, B$ G/ i) b( ^9 a9$ K% j$ A8 G' g" `4 d2 R
但事实上有些列存储的是分类变量,列中并不一定包含所有的类别,此时如果想要对这些未出现的类别在crosstab结果中也进行汇总,则可以指定dropna参数为False:
0 Z' ?6 W. F0 w; e
5 d: L2 k6 |# G, P1 M* z2 Y7 P9 \% Jdf.B = df.B.astype('category').cat.add_categories('sheep')/ j7 U7 P0 {! X9 M( J
pd.crosstab(df.A, df.B, dropna=False); }5 [' v7 F+ E* Z1 T2 p
2 H3 F L% e2 g/ q
Out[74]: & g' i) r. C. H3 |. |: {
B cat dog sheep
* h" [. Z0 N3 Q3 {( Q& S0 SA
! S8 x; b' j$ G3 q3 `6 I9 \0 i1 g: za 2 0 0/ b1 W! U) ]; Y/ b' Q
b 1 0 02 X! N3 Q7 w2 X4 F3 n7 [; B
c 0 1 0
# x0 l% z5 k# z) E8 ^' r% e1
( U6 `3 Y n9 N# ~, X) t, G% \2$ t+ Y( M6 e2 K- t
3
0 C1 c9 a+ T9 k5 K7 c3 ^4
' |" J) g3 h* L* a5
& i6 ?: U# }8 A: F2 v% j6
2 X& \2 H6 B$ o8 z4 ~72 f+ s3 x3 r$ O4 `$ y0 c
8
" L8 ^5 m3 I7 U, Z+ A9
2 c+ g& Z2 P9 t3 X4 R请实现一个带有dropna参数的my_crosstab函数来完成上面的功能。9 }, w4 A* o, O
& B' Q, l* X% `: g7 w) ~( rEx2: 钻石数据集
+ S# T" t, P7 t 现有一份关于钻石的数据集,其中carat, cut, clarity, price分别表示克拉重量、切割质量、纯净度和价格,样例如下:" [5 ~% p! b) y2 r# j" |' n
0 a$ w- p, S1 ^+ H- ]
df = pd.read_csv('../data/diamonds.csv') & i; H/ y0 g, W q$ \
df.head(3)* m& Z5 c* ~7 C) @ |
( [- m. c; U! B0 t" P' DOut[76]: 4 C' J. i$ d# z, j: s
carat cut clarity price
# G3 k6 x: ^ B( `0 0.23 Ideal SI2 326
& Y: }3 t1 ~3 n9 H$ u1 0.21 Premium SI1 326" a( b5 R% _8 _9 p9 J, j
2 0.23 Good VS1 327# J G; ^# w& r/ ~# H; ?% y
19 U) @" f4 K/ B+ n$ j
2
2 ]4 b2 A, g" H' t" F36 b; w) ~5 ]) X2 T# X8 G1 D: a1 \
45 A! g% e M1 f
5
$ M, q. @, R, Q! g% g& ?64 G/ z7 a3 W( ?* _( ~. O4 n
7% R% K- L: A, n5 m* c1 {
8
# b2 C) ?' L' T7 x9 f分别对df.cut在object类型和category类型下使用nunique函数,并比较它们的性能。
" Q& D4 Z5 k( [: [1 }9 \钻石的切割质量可以分为五个等级,由次到好分别是Fair, Good, Very Good, Premium, Ideal,纯净度有八个等级,由次到好分别是I1, SI2, SI1, VS2, VS1, VVS2, VVS1, IF,请对切割质量按照由好到次的顺序排序,相同切割质量的钻石,按照纯净度进行由次到好的排序。
; u8 j! G. s' e0 i分别采用两种不同的方法,把cut, clarity这两列按照由好到次的顺序,映射到从0到n-1的整数,其中n表示类别的个数。) D O4 a: d1 [4 Z3 d7 W6 D# j
对每克拉的价格分别按照分位数(q=[0.2, 0.4, 0.6, 0.8])与[1000, 3500, 5500, 18000]割点进行分箱得到五个类别Very Low, Low, Mid, High, Very High,并把按这两种分箱方法得到的category序列依次添加到原表中。
4 d# {1 M+ B/ P: M- Z; ]1 q第4问中按照整数分箱得到的序列中,是否出现了所有的类别?如果存在没有出现的类别请把该类别删除。
$ ~4 O9 n5 `: A: ]对第4问中按照分位数分箱得到的序列,求每个样本对应所在区间的左右端点值和长度。4 E, I( P+ F Y* j6 n( T& L" r- u
先看看数据结构:$ f" u$ p N5 l$ |* b! B
2 X4 K- c7 x7 l$ bdf.info()
1 d, I8 k0 n9 `) e) O) w7 a; |) c$ Q9 qData columns (total 4 columns):$ G% U2 _2 j0 B4 u2 G- U/ z$ c
# Column Non-Null Count Dtype
1 I' N! c/ [& w5 K6 V3 m* s--- ------ -------------- ----- 9 @0 V: {; e8 j! R$ {. I% p+ |
0 carat 53940 non-null float64
e* w; @5 Y: V2 M 1 cut 53940 non-null object , G4 w' O0 p2 _. @% [
2 clarity 53940 non-null object
4 c- Y' z* Z0 {# d( h; {+ s 3 price 53940 non-null int64 + R8 I! ? H8 ]' K
dtypes: float64(1), int64(1), object(2). c( ^. R0 j7 i0 q) w
1
5 v; P$ t; s$ ~2. A" m) c/ ?( E. b2 K! z
3
; k) }% E* ` C T7 f; i+ S s4
, v9 x' a, u; b0 i |6 d& Z$ Z5; R( d `8 q1 R! n% n; ~& f; {5 J
6
* G# y5 R# j% ?( \7
, Z; F* M9 M! S8. M5 n- @6 a7 L- p9 y. ?4 v
97 k& m0 ?3 v3 f. `0 q7 d: k x4 P
比较两种操作的性能% y H2 E. f) ]) }& t8 H
%time df.cut.unique()" m: h9 I: R( l8 t6 M, G9 K9 r
0 c6 @" p7 B x& ]
Wall time: 5.98 ms4 N+ ?' U, t! {% j% m$ q
array(['Ideal', 'Premium', 'Good', 'Very Good', 'Fair'], dtype=object)
# g. ]) s) U' d. G1
8 c% Y+ x) p' H- U2. [/ t5 \2 U8 l# J" u2 i9 I7 Y
3: l- x! C3 T: {" q9 a! E7 E: l
4
& J- n/ b2 q, j3 }" O" B2 d3 ]%time df.cut.astype('category').unique()
5 Q5 j0 ?) A+ k Q& R s8 n: I- n* k
3 Y3 f* g1 W8 ]8 R- LWall time: 8.01 ms # 转换类型加统计类别,一共8ms
; j$ E& r" j, V; A7 C& ~['Ideal', 'Premium', 'Good', 'Very Good', 'Fair']
1 ]5 u, s1 c$ s2 E$ i! `Categories (5, object): ['Ideal', 'Premium', 'Good', 'Very Good', 'Fair']
! k! b& o/ h/ s1( R3 c+ n, i' }/ O
2% s" V) ]! x- E& J
3- v; A! } k6 \
4
0 `- f4 c3 ?1 `; P9 ~- {5
1 @) g6 T- r. q9 i6 A: Hdf.cut=df.cut.astype('category')
7 A$ A! } }# q8 i1 @%time df.cut.unique() # 类别属性统计,2ms
7 c; I) N8 {3 o7 I) T
+ z0 w0 r5 L$ v) aWall time: 2 ms
6 W( E# M7 B5 e' {0 `; S['Ideal', 'Premium', 'Good', 'Very Good', 'Fair']
; M: d+ l, h3 V2 u, w- L" E* N: qCategories (5, object): ['Ideal', 'Premium', 'Good', 'Very Good', 'Fair']
# m, k4 s% \3 ]' @5 K6 _: d1
" e" r! i: c; r; t1 \2
5 K3 _' Y% s% H- Q4 z3# h! j, q; t# v' V2 y! `
44 {- P! ^9 E# i1 j$ l4 X7 d F/ c" c
5
4 c6 y' Y; D" b0 G9 `64 I* i' `% O5 d6 m
对切割质量按照由好到次的顺序排序,相同切割质量的钻石,按照纯净度进行由次到好的排序。7 l l! t A# Y$ [) D, a2 G/ e) X
ls_cut=['Fair', 'Good', 'Very Good', 'Premium', 'Ideal']& r) `- X1 w! M
ls_clarity=['I1','SI2', 'SI1', 'VS2', 'VS1', 'VVS2', 'VVS1', 'IF']
% S$ o2 d! O7 v- q/ J, ~df.cut=df.cut.astype('category').cat.reorder_categories(ls_cut,ordered=True) # 转换后还是得进行替换! E9 y2 l3 ~9 ^+ |) K* w; j/ K
df.clarity=df.clarity.astype('category').cat.reorder_categories(ls_clarity,ordered=True)9 r Y5 m; |; W& L# i2 z
$ @- ?8 b Q1 b( D: G3 I$ \
df.sort_values(['cut','clarity'],ascending=[False,True]).head(3)
( `0 G& X4 h3 p" o) ~2 i0 S5 d' y4 S8 t$ c+ D6 u6 x
carat cut clarity price) m$ \6 `' O- S7 ?
315 0.96 Ideal I1 2801& t$ n" a3 M! U% x* x; U
535 0.96 Ideal I1 2826
A( _0 H8 T% c# i; Z. D6 w551 0.97 Ideal I1 2830
X: j( m$ J/ z$ ?8 |$ j10 u) @! B( {" ?- k% x
2+ Q2 c, C* F5 ^ ~
3
1 ~6 P; v! X* c; l" \& x0 e4
; i% k8 r9 r& c5 k4 z* E- M5
@0 d5 x8 N+ ]$ B+ X4 E/ v6
/ B" G) S( Q+ ^' |, t+ i6 n* P7$ V* H5 l3 |' X) T
8. F( I% I- A$ k) W
9
7 ?2 O9 B$ d5 y9 y$ ^% P$ i9 r10+ t' @. r* K F, T4 T& o- T7 Q. b
11
3 m- K; \5 d9 k$ E分别采用两种不同的方法,把 cut, clarity 这两列按照 由好到次 的顺序,映射到从0到n-1的整数,其中n表示类别的个数。4 l6 g5 j% j6 G) Z1 H+ F q
# 第一种是将类别重命名为整数
2 c: M) M5 P4 I. P' h* Mdict1=dict(zip(ls_cut,[x for x in range (4,-1,-1)]))) F/ h3 ], {- V
dict2=dict(zip(ls_clarity,[x for x in range (7,-1,-1)]))3 c' ]; {. K/ y7 t$ l- l
2 O5 g; `/ X$ D9 ?9 j3 ?df.cut=df.cut.cat.rename_categories(dict1) F. J: W+ ^1 y& E1 e4 S# W7 S
df.clarity=df.clarity.cat.rename_categories(dict2)
6 }& ]( g# @# Wdf.head(3)0 `, B+ Q: l* p8 l Z2 c
+ _* N% r, _% {* u9 v carat cut clarity price) K: | d2 Y: m% o
0 0.23 0 6 326
; T3 J2 ~5 l! H# {* z. F {' i1 0.21 1 5 326: F% D$ a) u9 E) ~. _$ \* l
2 0.23 3 3 327
6 S4 j. m% e/ b& U1, M9 F+ N" a) M" v/ ` G, E
2
1 f; ?( C' P y* g5 V3
3 u5 }3 X1 {' W J4
/ s7 ]' k) Y. z4 g! S v6 g0 I58 x0 z3 ~& I* f( `% p
6
3 B% h& N0 V6 ]. F. a+ ]! T3 h _+ m7
/ V; }+ n8 Z9 M9 P* S: e$ b87 {0 S0 V7 ?- V. r% Z8 N6 _
99 V) e$ l% ^0 @( F9 j! X# B4 E
10
1 a% {# f& {8 ]* ^. e11
; c! w+ d: P* R. n8 u f12. L; w, X5 u: E% x$ |4 \2 c
# 第二种应该是报错object属性,然后直接进行替换
4 Q+ i, h4 r* i, I2 M6 ?- W6 Jdf = pd.read_csv('data/diamonds.csv')' P* _$ X% K6 l9 @- |
for i,j in enumerate(ls_cut[::-1]):" {7 A7 G) P" C: L% P0 N) |2 h
df.loc[df.cut==j,'cut']=i ! Y& e. @% g0 V" k- C! |/ Z# l- ~
/ _$ s0 v3 V) h) v0 i. k: Kfor k,l in enumerate(ls_clarity[::-1]):
+ A5 t1 `1 w! w2 ]# s4 y df.loc[df.clarity==l,'clarity']=k, j2 e" h+ X$ d% W9 z
df.head(3)
2 |+ _+ X5 L$ e0 ]! X
8 i: c0 S |2 g# k0 f carat cut clarity price" f# ~! k" U. E g
0 0.23 0 6 326( g6 d( Q; q7 {9 d) g( C1 m
1 0.21 1 5 3265 d# d( E% T7 j ?1 D
2 0.23 3 3 327. K& a+ [/ g) N
1# i: y3 c- H* E; D3 L; v g
2/ T% l7 @) n) B+ W
3
+ \8 Z4 e- l2 d8 j0 j# ~6 T4
! ]3 T+ a! v; ?1 y* x6 l5
& K% |0 k+ f: |/ W" z6
+ E& n' I# y L" H' P6 A) R7, e4 G$ @1 G. r/ L3 H4 f
8
! h. V) i' p$ R/ \" y9
4 }7 D& a: [$ n4 m$ |' m10. r5 A: e J$ U3 ^3 K2 ]
11
* O {! J: w! `9 C) @& x12
, i8 l" ]2 \5 _+ f% N13! p( L' A, `; J ^* L4 Z
对每克拉的价格分别按照分位数(q=[0.2, 0.4, 0.6, 0.8])与[1000, 3500, 5500, 18000]割点进行分箱得到五个类别Very Low, Low, Mid, High, Very High,并把按这两种分箱方法得到的category序列依次添加到原表中。
! ?; L' E; x; _! {# retbins=True返回的是元组,第一个才是要的序列,第二个元素是分割点) {" l+ T, {. a1 `: U; y
avg=df.price/df.carat; H: T- L ~1 I% a. ]8 B7 z
, l7 B8 |* x: r [! k
df['price_quantile']=pd.qcut(avg, q=[0,0.2,0.4,0.6,0.8,1],/ \6 ^/ k, G2 f9 `
labels=['Very Low', 'Low', 'Mid', 'High', 'Very High'],retbins=True)[0]! g4 ]+ b( w$ s x: d( M# D8 X
5 t: z4 J% R7 O# idf['price_list']=pd.cut(avg, bins=[-np.infty,1000, 3500, 5500, 18000,np.infty],
/ X% H( o0 b$ D) H. {/ c( P labels=['Very Low', 'Low', 'Mid', 'High', 'Very High'],retbins=True)[0]! A' V- P) ]6 Z7 C d1 ]; p
df.head()
- q+ G$ ~( O+ f' C( d0 x
0 J# s1 T( z5 u" S9 K, [0 X carat cut clarity price price_quantile price_list
9 J$ N: T+ a+ Y" f8 y0 0.23 0 6 326 Very Low Low: I: M2 K* H E# ~3 O4 D2 U8 c) o9 z' a
1 0.21 1 5 326 Very Low Low9 ~/ D# H4 j; k" z* e. k
2 0.23 3 3 327 Very Low Low
# K" k( B1 b% z8 C8 Y; q3 0.29 1 4 334 Very Low Low
9 _3 P; d* x' X6 @ {' r4 0.31 3 6 335 Very Low Low 9 c' X3 n! N9 {. R- j# J; m8 r
! B/ j0 O# q, u- K# U8 ?- e1
- j8 Z1 [ y- }) i2! I8 R4 o; l5 H4 R
3
/ R3 _- z6 f7 Z! Y0 Z/ G4
8 j* ?9 F( o6 t7 j9 p B7 v& z) ?5# i9 [0 ]' _8 a
6
# h- I& E R$ Z/ [& ~( S: k: R$ K7' ~' G* N* a4 ]1 Y
8( j6 f8 B8 B3 |
9/ k8 I( F4 i' g8 ] n+ m/ p9 y( e
10+ Q) h4 o; J {4 \, I- `2 Z
111 e2 g+ ]2 [3 I# W. }: @! n9 \
12
0 t# x" S' T$ \( x7 W13: t9 C4 i2 E% p% b
14. W. E% n' r* r7 n
154 @, r' \' r. X% r9 D, f7 C
16
, i/ G5 _/ n" P7 ^% c. W分割点分别是:+ x& H8 P O) T/ l; v* [+ S
3 A6 Q5 N5 _& f5 A& s
array([ 1051.16 , 2295. , 3073.29, 4031.68, 5456.34, 17828.84])/ H T, h/ S/ D4 Y
array([ -inf, 1000., 3500., 5500., 18000., inf])
, i; v* k( U5 _- A- S: w1
; |* D- D4 G9 H: r+ h21 o: Z/ V" M! t' K9 S" Y7 C" T
第4问中按照整数分箱得到的序列中,是否出现了所有的类别?如果存在没有出现的类别请把该类别删除。
5 P; V+ _; R8 Jdf['price_list'].cat.categories # 原先设定的类别数2 z, k3 P* n, `8 x
Index(['Very Low', 'Low', 'Mid', 'High', 'Very High'], dtype='object')
# a6 @9 a, d) _2 L1 K
2 D( D( e+ k; |1 Odf['price_list'].cat.remove_unused_categories().cat.categories # 移除未出现的类别
8 k0 T0 ?$ [! D D4 p6 h- V) m/ Z( X; QIndex(['Low', 'Mid', 'High'], dtype='object') # 首尾两个类别未出现
! I6 T. u. S, |% |18 d9 O% u+ K8 J: c
26 L. s5 y. e$ \9 h2 O! z; \3 [
3$ B) {; Y: @ _4 n. Q. s1 t" O
4
1 R; g8 ^: l% I7 v( B. T5( i7 ~' N2 H! p( C3 f e1 n* Z
avg.sort_values() # 可见首尾区间确实是没有的
6 @+ S; \# |- }& A: d& D31962 1051.162791
. i, K: ^( V' {/ q% |1 V15 1078.125000
9 I& e$ {+ C% [5 T, p4 1080.645161" N4 k4 a; w" S" I! F
28285 1109.090909& Z3 U3 F4 O( V7 C1 D
13 1109.677419* Z/ S( x/ D: j! W5 u4 I
...
* t$ I, D- Q$ O4 F( \1 q8 ?8 X26998 16764.705882
$ h/ S9 b0 f2 p- x, ~$ L9 D: Z27457 16928.971963
$ q. f9 f$ `. E5 c) J27226 17077.669903
. ~: K* ~& F/ Z27530 17083.177570
; Y5 j: C0 v) U! Z M27635 17828.846154% Z# h% h3 N- {. }* M) r) b+ }
1
9 z7 _ r m' R# K0 H' Z7 y% @( ^20 x7 E. Y f3 w+ z u
3/ k' [! ?$ C; Q4 l y
41 K$ u1 D0 E0 e
5- W. z2 e7 [; j" J* T4 I
68 P+ u0 c: R6 \8 }& n, A
7$ u! X9 C: z i$ g5 j
8
" P2 s! ^8 P$ Q, o- r9
- F& L# G$ `7 y& B7 b10
: H' u; P2 _: s2 G0 S: ~11. F; A7 I( v& C+ Y2 ^- }
128 H- H/ h, b0 N+ h0 r4 Q- k
对第4问中按照分位数分箱得到的序列,求每个样本对应所在区间的左右端点值和长度。+ B/ X: u* {- H0 Y( M- b4 t: S9 b
# 分割时区间不能有命名,否则字符串传入错误。2 _8 w$ `6 K5 M- W+ n3 \% H, }6 Q4 P
id_interval=pd.IntervalIndex(
/ Y4 l- {3 {9 O6 p pd.qcut(avg, q=[0,0.2,0.4,0.6,0.8,1],retbins=True)[0]6 {6 ~% [- G& ~& ~* k
)0 D5 p& @4 l" [1 d n) @6 E
id_interval.left
1 U4 e" w- L: Z8 F9 Bid_interval.right3 H, x, a% Q' w0 q5 t& h d
id_interval.length
9 X; D$ l% M: J* f% n* M0 g7 Y1
6 I- p/ `5 t" J- n9 u2& v! Z v' {# G5 I, d6 B7 s& k
3( j( u4 L/ W" L3 \4 Q$ K8 L
40 Y7 N. H( m. ~# C
5
4 C6 l3 g: ^* w V% r- u1 Q6" h' P( H& }' x {( p
7
$ z( x' t9 N2 W- p( D第十章 时序数据
- m5 ^6 V+ g b$ a7 e+ bimport numpy as np+ i+ n# N% C/ E" U+ G1 P
import pandas as pd
( V1 L/ g( M( c3 U2 B* A! H; e15 ]* M. }5 U$ ~/ r% p2 u, `. B
2. @' H7 k$ R- F, @% E
1 K( q; y) S) x( W6 `
6 Z0 U& S: g# w10.1 时序中的基本对象2 P* C" }$ T/ d. _" |' K
时间序列的概念在日常生活中十分常见,但对于一个具体的时序事件而言,可以从多个时间对象的角度来描述。例如2020年9月7日周一早上8点整需要到教室上课,这个课会在当天早上10点结束,其中包含了哪些时间概念?
( h" L9 B t1 l, ?5 U; ?6 F6 e) T/ O5 }
会出现时间戳(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的简写。2 R, F" o! i4 L9 e/ |9 E3 A
% I% F2 d2 g( Z
会出现时间差(Time deltas)的概念,即上课需要的时间,两个Timestamp做差就得到了时间差,pandas中利用Timedelta来表示。类似的,一系列的时间差就组成了TimedeltaIndex, 而将它放到Series中后,Series的类型就变为了timedelta64[ns]。
& k! C- x/ C/ q4 G: o( v! _
3 r1 y' d) v v- v会出现时间段(Time spans)的概念,即在8点到10点这个区间都会持续地在上课,在pandas利用Period来表示。类似的,一系列的时间段就组成了PeriodIndex, 而将它放到Series中后,Series的类型就变为了Period。/ K' A6 L: l c9 ?9 K0 h3 Q
1 r7 ]( p/ m1 j/ n2 A9 X" f会出现日期偏置(Date offsets)的概念,假设你只知道9月的第一个周一早上8点要去上课,但不知道具体的日期,那么就需要一个类型来处理此类需求。再例如,想要知道2020年9月7日后的第30个工作日是哪一天,那么时间差就解决不了你的问题,从而pandas中的DateOffset就出现了。同时,pandas中没有为一列时间偏置专门设计存储类型,理由也很简单,因为需求比较奇怪,一般来说我们只需要对一批时间特征做一个统一的特殊日期偏置。
# F; p$ m- N* G# @- r% r. {# O- k
. B6 D9 \& ?: i o- W. M, u+ n5 R; X 通过这个简单的例子,就能够容易地总结出官方文档中的这个表格:
8 M: L- m* u) O( j' o
$ H) l+ ]" ?& k( t5 U9 N概念 单元素类型 数组类型 pandas数据类型/ J$ t5 s2 q' }9 b- i
Date times Timestamp DatetimeIndex datetime64[ns]
& f; u3 i. j+ Y6 L+ J/ }Time deltas Timedelta TimedeltaIndex timedelta64[ns]0 j8 N- _0 M8 s& O& m
Time spans Period PeriodIndex period[freq]
$ y5 @# Z: H& N5 j3 G+ iDate offsets DateOffset None None
0 M9 E7 Q8 g3 w% f$ J0 E5 p2 G+ k! T 由于时间段对象Period/PeriodIndex的使用频率并不高,因此将不进行讲解,而只涉及时间戳序列、时间差序列和日期偏置的相关内容。
1 b. v5 @! U2 n- ^8 c/ D/ o8 o4 C+ u0 C% i( y: y; |
10.2 时间戳
6 i) O( v; `3 Q8 _" [2 X10.2.1 Timestamp的构造与属性: F) k8 P8 b" c% _
单个时间戳的生成利用pd.Timestamp实现,一般而言的常见日期格式都能被成功地转换:' ]$ I; g2 M" k* w; p$ ]* z
4 R7 |/ ^$ d/ |, }! }ts = pd.Timestamp('2020/1/1')8 e. L- j* f( Y/ B% \. S
. t' [! F0 m" l' J4 |- `. Nts: n1 o* t/ f* s' `/ b3 K
Out[4]: Timestamp('2020-01-01 00:00:00')5 V- n l8 [% i, m$ u7 U
+ @5 P4 _% a# b' E% xts = pd.Timestamp('2020-1-1 08:10:30')
( [' V; Q4 M, z: v
1 x/ [* r; j! D+ Gts
7 n J" k& W3 R: c/ q! p5 QOut[6]: Timestamp('2020-01-01 08:10:30')
9 }% Y% s" E& F- n! ?12 k. }2 }! T& d6 {% [/ f) v( L
2
3 F- |: x5 N& M8 J/ M) i& @3
# y& z8 b# o- J l$ g4' O( b1 O1 {8 H8 m& E1 A
5
3 |" z* L) L6 Y: {6/ X6 h% b I' E4 j' S' O7 q
77 Z* T$ B" k1 G! }4 c4 }1 Y
8
) {! f) A& h* x3 |- n: ^, a3 n96 j' t. j$ J$ h9 q
通过year, month, day, hour, min, second可以获取具体的数值:
- O+ Q9 J" s- \, L2 L+ B( Q- A2 A" _
ts.year
, w% x; I( x: D" z* ~/ Z5 ?2 bOut[7]: 20203 X/ {/ L! f2 J, D
9 {, j- v3 U7 M6 J
ts.month# _2 K: ]; }" J4 T
Out[8]: 1: K8 d6 Y Q+ F% a" R0 c
+ h E+ O+ Y6 ?. s1 |. pts.day( O+ j; I# p7 M" X y+ S
Out[9]: 1
+ A' A6 q6 {; [3 n1 u7 [8 D
2 o/ ]# N7 u& V, Gts.hour4 a/ X2 d( G2 ?$ N
Out[10]: 8# R' c& v6 k4 E8 a; b, O
5 L/ X v9 G, L
ts.minute
7 ^: B' L& S% Q! O6 L: H- NOut[11]: 10
1 b h7 X( l, E$ E4 S- q. s. \5 k8 G
ts.second
^/ Z$ l+ C0 ~Out[12]: 307 F4 r0 v$ N2 P {3 M
9 ~( ~8 y! p) @4 a- G- i! c
1
6 a# h' @9 \8 Z' h2
j- S) }* `2 r5 U5 ~3+ F1 k* z; p7 Y7 f+ K9 {* U5 [
4
7 @$ A; ^( C" T# N; @2 _6 o1 M5/ N/ s4 V+ c( k0 \: O
6
) ~& P$ l9 m# M: u0 `7# r7 ?! @0 E' b* o1 W- v
87 B" W% r5 w& Q! M J
9
& m6 M" s. }& b101 x$ _8 b* g2 \: s! ~. F5 e
116 j" b$ c M" D/ h( S$ i1 z; m
12
, \' R$ |- y I- ^/ X6 H13
9 A& @+ h: |+ |% a/ Q1 t14
f+ r6 T, s) }, }( I15, d4 ?* e" K, g+ @# x
16( y, y9 R" D i. k& F( S7 b
17* T- {, H" x, Q9 d; j
# 获取当前时间
3 Q2 g& c6 R/ n4 u8 Inow=pd.Timestamp.now()
1 J& I, h) E3 V: ^1
7 _& ]" k ?8 M9 y, O2
% q" ~- A% U6 m5 }在pandas中,时间戳的最小精度为纳秒ns,由于使用了64位存储,可以表示的时间范围大约可以如下计算:
) \8 ~, M! t% x- d4 ?7 y: q. \( BT 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)
, @& [8 S/ O2 U7 j' [& \TimeRange= " e' G4 k% r' y# d7 L0 u
10 # t7 M7 e7 O3 r& Y2 w, H
9
% n* Z/ r9 }( f" l; M8 L9 D ×60×60×24×365) l9 u9 T" y9 {' c( p/ \9 H6 \) p
2
4 P0 v5 F! [9 `; W, j64
% R) l. a/ b7 G7 `) n
! B* h; P( g2 s6 u" T" W; J$ Y9 ]
; r- M8 V' T/ e( i) J8 w2 W ≈585(Years)5 O! l' n2 H! y8 b
+ f5 W! w6 [4 l. R8 \3 ^通过pd.Timestamp.max和pd.Timestamp.min可以获取时间戳表示的范围,可以看到确实表示的区间年数大小正如上述计算结果:
! K$ W! y6 I/ m; A* t3 m3 W3 s K
pd.Timestamp.max$ W$ {* Z" q# s! F
Out[13]: Timestamp('2262-04-11 23:47:16.854775807')
( k W+ F/ C4 }/ X y
) n5 X5 l$ u$ f) p& R* w( dpd.Timestamp.min
9 B& \" G% @9 p) I' rOut[14]: Timestamp('1677-09-21 00:12:43.145225') n% }8 O9 @% @4 |) E4 d! |( G$ U* s2 ~
% k) ~& l4 s g" v4 e8 u" G+ e/ ~
pd.Timestamp.max.year - pd.Timestamp.min.year
' b( O4 U0 G' k4 x; g& sOut[15]: 585* A% N4 T0 w( L
1
3 C' j6 {* W/ L& ?29 Z( O8 n! y6 v4 C3 h% ~
3
2 L W0 E( M% Y- q5 x( p4
) p% Y E3 ~, M. w0 i; h5
4 u- D% D( p) u" p& A6
* a, Y! H: c! ~7
4 b' Y9 A/ V# _6 D8" I: A" q T! {
10.2.2 Datetime序列的生成
' k$ C* ?" H! Z2 N/ M8 U9 g1 n" ~pandas.to_datetime(arg, errors='raise', dayfirst=False, yearfirst=False, utc=None, format=None,( w& y7 G" Q, Z( Y) C
exact=True, unit=None, infer_datetime_format=False, origin='unix', cache=True)% @! C; f0 ^* d
1) T% ^3 @- G0 E9 n; `9 C) T* R
2$ ?: g( @; r4 l: b ~0 m
pandas.to_datetime将arg转换为日期时间。! S4 D* ^2 ^2 C( w/ H
9 C9 G' D8 Z( x( n. {7 }. M: H
arg:可以是argint、float、str、datetime、list、tuple、一维数组、Series、DataFrame/dict-like等要转换为日期时间的对象。如果提供了 DataFrame,则该方法至少需要以下列:“年”、“月”、“日”。
/ s$ A! L) j3 v8 \( Jerrors:3 K* W- b& M f3 ]2 ]
- ‘raise’:默认值,无效解析将引发异常. U$ }5 l E4 [: j- \$ B
- ‘raise’:无效解析将返回输入
4 O; v$ e& X [! s- ‘coerce’:无效解析将被设置为NaT0 a) M' @! J9 _4 T0 N* j
dayfirst:如果 arg 是 str 或类似列表时使用,表示日期解析顺序,默认为 False。如果为 True,则首先解析日期,例如“10/11/12”被解析为 2012-11-10。如果无法根据给定的 dayfirst 选项解析分隔日期字符串,会显示警告。* Q4 s% X5 H) T: C9 B
yearfirst:如果 arg 是 str 或类似列表时使用,表示日期解析顺序,默认为 False。如果为 True,则首先解析年份,例如“10/11/12”被解析为2010-11-12。无法正确解析时会显示警告。(如果 dayfirst 和 yearfirst 都为 True,则 yearfirst 优先(与 dateutil 相同)。)3 t9 G( \1 H, L7 R( C
utcbool:默认None,控制时区相关的解析、本地化和转换。请参阅:pandas 有关时区转换和本地化的一般文档* j2 B# f/ e/ ^8 O3 {
format:str格式,默认None。时间戳的格式不满足转换时,可以强制使用format进行匹配。
4 c4 J T3 g# }+ C! W. {unitstr:默认“ns”。它是arg (D,s,ms,us,ns) 的表示单位,可以是整数或浮点数。这将基于原点。例如,使用 unit=‘ms’ 和 origin=‘unix’ (默认值),这将计算到 unix 开始的毫秒数。 [* b6 Q; i7 q2 K* [: t/ M
to_datetime能够把一列时间戳格式的对象转换成为datetime64[ns]类型的时间序列:8 A" x0 c1 P( h' d
pd.to_datetime(['2020-1-1', '2020-1-3', '2020-1-6']): r& A! W' a7 \' R+ c0 Z
( B, j* d1 h: E: [DatetimeIndex(['2020-01-01', '2020-01-03', '2020-01-06'], dtype='datetime64[ns]', freq=None)
# ~& U% A. {* \" n8 D1
2 a) h0 V8 b7 b' g: H( ?: @! y2
9 u) a+ K9 B$ w/ w U$ n37 h7 e2 D r4 }3 \3 c8 P" }
在极少数情况,时间戳的格式不满足转换时,可以强制使用format进行匹配:" x/ N2 D3 M* M; U
; e% j4 l& L/ h% utemp = pd.to_datetime(['2020\\1\\1','2020\\1\\3'],format='%Y\\%m\\%d')$ T# e8 a0 k+ s% }$ C+ _
temp
$ h" g2 b' F) D' y1 m
3 p! V; j/ N( Q- w7 `3 O' uDatetimeIndex(['2020-01-01', '2020-01-03'], dtype='datetime64[ns]', freq=None)
% X- ]* h7 K- d* J4 I U, O1
+ l! [5 Z- H$ _% K2 q26 x" A. j! ]3 M0 H }, n
3 a w r- S' N+ ?( b
4
$ d% }( r k) X8 u* ^, o1 U- ^ 注意上面由于传入的是列表,而非pandas内部的Series,因此返回的是DatetimeIndex,如果想要转为datetime64[ns]的序列,需要显式用Series转化:
3 p% C6 J2 M0 q8 q' }+ I1 ]6 `0 n
9 e! G* y% C- ^2 J! @pd.Series(temp).head() Z5 {! n2 x0 T! d5 ]0 {
- c- f' v0 Z' y8 [! x4 Z+ I+ ~
0 2020-01-01
+ y% G2 o0 \ |0 V; k( r8 v3 @4 x1 2020-01-03
& k5 P% k3 k# K3 qdtype: datetime64[ns]6 M$ y# P$ y# [$ o
1
2 k- S8 F! {4 `2
8 w- M+ b+ @9 t3- h- D1 i7 Q2 k, ?0 Y' e3 A
4% H, V$ d9 M% ^' q; y7 D3 p G
5; h. H5 F% p% r
下面的序列本身就是Series,所以不需要再转化。
& O+ T9 \! E! l2 |
8 q5 p# d% g7 v, Sdf = pd.read_csv('../data/learn_pandas.csv')5 c& [0 x4 `7 \# n R6 z( _. U
s = pd.to_datetime(df.Test_Date)7 D7 A1 l9 |- h8 c" ]
s.head()
1 R0 d, Y# j3 L, x/ \/ Y* [! f7 F( p- A+ i4 n
0 2019-10-05
' |& j2 r! k- q- M# c1 2019-09-04
0 U5 ~, X6 z* O( D9 P2 2019-09-12+ _, }8 y' w+ W' \& ?
3 2020-01-03
; O' n& E! Y, V4 2019-11-067 S5 w( I- D3 t3 }4 g
Name: Test_Date, dtype: datetime64[ns]
- N0 l# e" u1 \) J( h. a8 E4 C1
L9 k9 z# c! U: x$ V+ _4 J2! `# e5 v: O9 X$ y$ B6 |
3
# a; }) t6 {' f" ~7 b3 c" g4
* J, x% Z1 }$ T2 _! B) B5
1 z8 l/ D5 B; O1 d6& |- a- @( ~ H1 L! O5 T9 b
7
. N- p0 q5 f; m9 `85 Q* V {' N" m6 l" K* q
9& N5 _9 N2 u: P4 o8 y) f
10
3 k7 R3 {4 L+ J) y* V; d- }; I把表的多列时间属性拼接转为时间序列的to_datetime,此时的列名必须和以下给定的时间关键词列名一致:9 Y; a2 b" h" ?- Q5 G* b
df_date_cols = pd.DataFrame({'year': [2020, 2020],
* Q* f9 n0 k. }( U' b 'month': [1, 1],
; \0 W) s( |3 q, g( i 'day': [1, 2],& q9 G# i; E8 ^
'hour': [10, 20],
4 Z8 W* {9 C M/ a9 f# I 'minute': [30, 50],
) y% r3 s3 I9 O& _: c$ Z* K. t. f 'second': [20, 40]})
* a2 t0 U/ H E" \pd.to_datetime(df_date_cols)
4 u3 H4 m j" w+ |$ C7 p7 {, q5 ? O
0 2020-01-01 10:30:20
8 V# k& s2 N+ K2 G4 g' M1 2020-01-02 20:50:40
/ C+ q4 [3 X; [0 wdtype: datetime64[ns]
/ M4 v8 L# O% d% r0 p5 p1 A: F1
# \' Y. c. F1 F4 T7 N `+ s20 d! W" ?( p- \- P
3
7 r4 j" C! I9 I( ~" r8 u3 m4/ L: Z/ P3 Q: O( a
52 W! y, {! b6 b
6
1 \5 D( d, J; ]7
" e) |1 v8 Z* s: @! F8
& I6 B) W4 [- b( X9
1 b; i- I6 V9 u: R108 d8 Q G* w& E+ s: z
11
. a- ~" _, `3 D. G: ydate_range是一种生成连续间隔时间的一种方法,其重要的参数为start, end, freq, periods,它们分别表示开始时间,结束时间,时间间隔,时间戳个数。其中,四个中的三个参数决定了,那么剩下的一个就随之确定了。这里要注意,开始或结束日期如果作为端点则它会被包含:/ g/ M5 [" f; c9 I- Q
pd.date_range('2020-1-1','2020-1-21', freq='10D') # 包含8 N7 L; `' r0 g7 f6 I; p, d$ v% e ]
Out[25]: DatetimeIndex(['2020-01-01', '2020-01-11', '2020-01-21'], dtype='datetime64[ns]', freq='10D')
* {6 u0 H! k( T
8 v" N, {' u% C. n H" w! \3 |pd.date_range('2020-1-1','2020-2-28', freq='10D')
# R% r6 `7 g4 T& f5 UOut[26]: . ^0 D2 V; r$ k3 Q
DatetimeIndex(['2020-01-01', '2020-01-11', '2020-01-21', '2020-01-31',
' z. W2 M; H9 H* }) B2 X '2020-02-10', '2020-02-20'],1 f3 X0 s- u" |; i' Q
dtype='datetime64[ns]', freq='10D'). [. V: Q; o! e/ u& G
% s3 v3 `; g7 K/ s6 ~
pd.date_range('2020-1-1',/ K& |1 {# [/ n1 B# H: ?2 Y
'2020-2-28', periods=6) # 由于结束日期无法取到,freq不为10天. S0 C/ G- u% q. [) l' T2 J v1 g
( ^! Y, j3 J/ R9 b. ] X) N; MOut[27]: # {, g4 M0 s' b7 Q4 R& V
DatetimeIndex(['2020-01-01 00:00:00', '2020-01-12 14:24:00',8 U6 ^+ Z% w& B6 H' t
'2020-01-24 04:48:00', '2020-02-04 19:12:00',7 p" h3 n4 e) m3 C- P- \" b
'2020-02-16 09:36:00', '2020-02-28 00:00:00'],$ B) ~/ y; v+ f1 r% R+ f
dtype='datetime64[ns]', freq=None)
) L3 G0 Q+ U% X9 F
5 z0 c: N+ { d& ]( ~1 [/ ~. t# q' Z1
7 ?: T/ U, n$ D3 E0 m1 S$ R+ \20 W2 T+ R5 O4 Z. A: C
34 K$ {& W" N% Z: b. B
4) G" S! w/ c# z4 y
50 A4 Y! J) d( Y' m
68 z2 E1 q$ `% L) R0 z3 U9 i
7+ N) w% ]9 ?2 H7 r
86 w* ?& `" E8 P& j
9, C8 b) i- }! t/ M
10: ]4 t' y: K% u
11
0 ]# X6 \2 H) P120 E8 x, g! z$ l
13/ l! b# d2 g$ } C$ R9 I) l& D
149 @5 C% Y2 {( H/ M+ X8 y% q
15
6 V1 t* g& S) d4 ]8 v0 a16- h2 e6 f6 x5 D N% c
17
$ J* m& C& Q( A" t) H; k( k. e7 E这里的freq参数与DateOffset对象紧密相关,将在第四节介绍其具体的用法。; \; } w; k; u2 r& T
5 D) @9 W1 j/ x4 Z; e, I
【练一练】/ S6 t; ?6 [" F5 j
Timestamp上定义了一个value属性,其返回的整数值代表了从1970年1月1日零点到给定时间戳相差的纳秒数,请利用这个属性构造一个随机生成给定日期区间内日期序列的函数。
' T* i, @3 ?1 Q# H
j3 r% t5 z3 z. m w6 G% bls=['2020-01-01','2020-02-20']
# g6 i' K+ K8 ]% V3 p4 g, J5 Z7 ]def dates(ls,n):
+ l( e4 s$ c+ k: b' a min=pd.Timestamp(ls[0]).value/10**93 \, T) n+ y& u
max=pd.Timestamp(ls[1]).value/10**9" ~0 r9 e3 L V
times=np.random.randint(min,max+1,n)# h: J( {/ E9 z# g4 ?* C
return pd.to_datetime(times,unit='s')
# M* N, m: p% y9 ^# G8 ndates(ls,10)
, Q" S3 l9 F' u$ G6 c. q* [: l, a J5 E4 L7 |7 n9 @
DatetimeIndex(['2020-02-16 09:25:30', '2020-01-29 07:00:04',
* R# q! |8 E% p. u q* Y '2020-01-21 12:26:02', '2020-02-08 20:34:08',
- Q1 G( O) c4 B, ` '2020-02-15 00:18:33', '2020-02-11 02:18:07',
3 J: m2 D& o1 a3 D* W5 g '2020-01-12 21:48:59', '2020-01-12 00:39:24',0 W) b! ^' b- n6 z
'2020-02-14 20:55:20', '2020-01-26 15:44:13'],
; U5 K Z" R5 T7 c( U dtype='datetime64[ns]', freq=None)
# O4 c: H3 A* m: s4 Y( A6 k1' i' j: o$ d( ]9 o0 y5 `
2+ o7 g: o; f0 K. J. V9 q
3
{8 ?" J5 c/ }" ?& Y4
! \3 f( f# c* ~: \: t& b1 D58 v; V$ B9 @' ^8 B
6" g' L4 A7 m n. U
7- c3 m+ _2 g' @, Q- ]
83 L6 m# d5 y% f+ Q) ]
99 h( h0 L7 M2 t3 @
10! E) A. o- |, P: [; N/ z6 P0 H8 o
11/ z. |! |' W0 k+ c
12- X' l& z2 B" l9 F2 E
13
( L* u4 z/ z0 X/ }14. D2 Z" d- X2 r4 @: q: w
asfreq:改变序列采样频率的方法,能够根据给定的freq对序列进行类似于reindex的操作:
2 m! i8 ]) G- v2 L6 C/ Is = pd.Series(np.random.rand(5),/ _8 J- A6 p# f7 J7 s( a+ F: R
index=pd.to_datetime([
' M2 q z( I* ~0 p- T' B# J( C '2020-1-%d'%i for i in range(1,10,2)]))' h1 ]. W/ c* }4 d) S% `
! A/ g6 C, o: X( z- n- p K: N/ G% p L, Q. Z! v
s.head()
$ v0 E. U$ W# s: {9 O( ^6 H5 FOut[29]:
5 [1 k, u; t: G3 g& @# P: [9 }2020-01-01 0.8365788 v7 S( {+ b: t6 x. r+ t
2020-01-03 0.678419, f: I$ B# X6 z/ [
2020-01-05 0.711897
8 W1 f0 \, \/ U0 D' K* o6 V2 e2 r7 A! e2020-01-07 0.487429
, T8 X/ ], z) r2020-01-09 0.604705, n$ b M& B R+ k% Q' o" x
dtype: float64& L5 F' i- d) K% t$ F9 g
6 T2 `5 h I8 S% r' z/ |s.asfreq('D').head()! }4 J( ]3 } d& `7 q& [
Out[30]:
- L" H R2 _% h) ^, k; J2020-01-01 0.8365788 U2 U% m# Z% \* C2 |# ?
2020-01-02 NaN
* k; d$ {" E, m2020-01-03 0.678419
& u* l \& U& P3 `8 J2020-01-04 NaN, u7 I: ~: W& ^5 [8 G
2020-01-05 0.7118977 D5 j6 o, i- Z+ P9 T: _$ b
Freq: D, dtype: float64# U/ q# R: f+ h$ B" ?+ P) v, Y
8 o: ~" R+ X! v5 t2 @* Y3 z& t: y
s.asfreq('12H').head() x) V& [1 l2 G0 N1 V* ~; d
Out[31]: / b- z' \3 q7 z, R( ^5 I
2020-01-01 00:00:00 0.836578
7 ]: c7 e4 g# C# S9 y; a$ _# y2020-01-01 12:00:00 NaN
& j& R4 g1 o0 ]; S2020-01-02 00:00:00 NaN
3 l) O9 E# S, c' |0 g: [# t2020-01-02 12:00:00 NaN
3 D8 L5 Q: g! m, @2020-01-03 00:00:00 0.6784193 ]+ A6 N' x; ^8 p5 D7 p' D6 ]
Freq: 12H, dtype: float64, e" c/ |$ u$ l1 s
+ e2 k& |) P( t5 `1 W) h2 C
1
6 e* p i) R" m27 H8 |: m* O8 i% [
3- L5 Y% |1 l; x/ ]1 {! X/ `$ D
4
, H! {6 ^. W% Z56 Y8 x6 [5 f+ Z* }
6' `( t8 |5 ?$ U7 O( J$ s$ a) Y
7. [, T1 M# i* u @ x- Q
8, q+ a( j$ G* o- R% e
9
. y7 f* f* M4 ^5 ~8 L0 G( E10
$ s H% G' b, s11
" y8 b3 Q/ C& P2 s" n12
% y3 W5 ]: ?" [7 c* F& }13
7 U1 V: Q+ }2 _; {& R/ l142 K8 Q; s' g* h1 j7 M3 {' l3 z
15" n2 G1 t3 G1 U6 {. } F
16
% `% C. q# |2 W& s) u, S17
" B0 ~& L& n' b& q' W18 J+ E. W/ _& \5 D" D* F- v( M) v
199 e$ m9 k- n: B) w' ~8 \4 e( D
20
: E$ W4 x- d: \! J2 j216 {# c9 F' @! h% Z
22
7 p, \# e% K( ?% z: L% L23
) B0 b6 T4 F |. k) q8 q+ r245 Z% z9 i# Z/ O' Y$ e) t
25' `$ Y! ]! I0 G6 y
26' o: U2 c* G" E0 N2 ^! o
27( S4 C, \( o5 ~7 w% G" u* |, d
28
+ T; w' i# w2 a. o+ e29
) p8 m' ]+ X- J30/ A3 S. {* _! I* l% L* n1 ]) ?
31( W2 S9 Q5 u; [
【NOTE】datetime64[ns] 序列的极值与均值
+ v; X) M7 Q, v4 u! D. }0 E, L 前面提到了datetime64[ns]本质上可以理解为一个整数,即从1970年1月1日零点到给定时间戳相差的纳秒数。所以对于一个datetime64[ns]序列,可以使用max, min, mean,来取得最大时间戳、最小时间戳和“平均”时间戳。
+ W4 l6 k$ @, H4 c% E- r1 x
' t3 ~, a) E9 L, G4 D$ T. z10.2.3 dt对象( q$ x8 F7 k3 V( h
如同category, string的序列上定义了cat, str来完成分类数据和文本数据的操作,在时序类型的序列上定义了dt对象来完成许多时间序列的相关操作。这里对于datetime64[ns]类型而言,可以大致分为三类操作:取出时间相关的属性、判断时间戳是否满足条件、取整操作。
% j3 E8 z4 A+ C1 n) `* A# Z$ T0 u; M7 k7 ?
第一类操作的常用属性包括:date, time, year, month, day, hour, minute, second, microsecond, nanosecond, dayofweek, dayofyear, weekofyear, daysinmonth, quarter,其中daysinmonth, quarter分别表示该月一共有几天和季度。3 `# c$ a: F7 Q; f6 R
s = pd.Series(pd.date_range('2020-1-1','2020-1-3', freq='D'))
/ g) Q8 T6 ?5 [! J) N: s$ |9 G3 Q
0 l5 w( j. ?- s- Bs.dt.date
0 L0 E/ m! {2 _Out[33]:
$ K% e0 D8 P# N& I0 2020-01-01
K& r) \) I; `1 2020-01-02# A* q# I1 o* r7 Y6 y
2 2020-01-03. A2 Z% h+ T, B$ r6 g7 A
dtype: object2 g) U2 l$ d/ Q$ B, q
7 Q5 e4 o: M4 \( j" f# Es.dt.time
* E2 p: U8 i- K& W& k& u9 iOut[34]: : U6 ^: ^" V% Z
0 00:00:00! i; @) u0 [0 v5 \: }* h2 f
1 00:00:00
1 W. E+ Z9 J2 B; p. Y2 00:00:000 E% C: n7 C9 h" i) J
dtype: object" o0 o" U; F( Y3 g G
3 a- v9 v. C9 p7 Cs.dt.day
$ f$ @+ F8 d. y7 S' {5 i. MOut[35]: 7 X" _( T+ ^* q( K+ J1 E
0 1
o# E2 _. p, ~# q u3 M i$ s* Z8 F1 2
8 ]8 L0 \- a0 F6 V) K3 g# a) E2 3
/ Q! `; Q9 P% E/ v+ }5 i$ V; kdtype: int64
V$ _9 E; t2 D: _! B; f3 _+ G! ?. D% G$ K$ v, e; l, K
s.dt.daysinmonth
" T3 p9 a; t( S5 f+ A; A5 k( Y2 cOut[36]: 3 r% u" _1 @8 _
0 31
5 A" k) {1 D- G& ^4 o1 31) Q' r' A/ a: |* ~9 H
2 31
2 m! p/ F/ M4 A# Y2 h5 Pdtype: int640 A/ q. E1 P! y8 G
/ t0 M6 l+ {* l6 ~6 a, k( e
18 [2 B- r% p( U& U) P: u" d9 e& Y
2
% Q! Q0 M: r2 v2 Z3
" u3 p1 e* O: ]7 g1 k. P/ p4
1 h. d6 I w2 ^2 s X" K3 N5) G; t7 [9 c* w+ j3 a, F
6
: g: Z8 F' b& b7$ t' b2 |5 \# @: m P
8% w" z5 ~ \9 a3 }* N+ E
99 n: e$ g% G: E* T% T
10: g: \! s- O! p- X, `
117 f9 ^0 l1 \. s3 |. z1 P* D3 a# i
120 y0 i! C$ y& h% i' v- x: X
13
' ~$ y1 J0 c# A1 n14
7 q. l$ |. m; @) I# k0 B* Y15
/ ]' t, `: l* d S- `16
$ X1 M' O( E3 C- X( }179 _4 X4 R+ B7 i9 ^
18. H: U! ?( a& j, t' i- M
19' f, Q9 A# V% y. B
20
1 U# |& s* }) B& H21
' k- q0 f8 F0 j$ C( O% Z22
3 a; J8 V V' U4 q9 o5 {6 ^4 r23
# T* d! f7 m( U3 G. s245 a# d) ]3 L% x4 o* K
25
$ L( Z; i, ~ P4 C26
7 W( K2 W. C, D27
7 O, a' a9 d; n& F6 }28+ n8 a0 ?" \) g+ G4 R4 c2 Z
29
1 L, h3 `1 @$ `' r8 Y 在这些属性中,经常使用的是dayofweek,它返回了周中的星期情况,周一为0、周二为1,以此类推。此外,还可以通过month_name, day_name返回英文的月名和星期名,注意它们是方法而不是属性:
% _& u4 f: i9 j/ i2 S1 q
1 _9 V* \+ F! x1 rs.dt.dayofweek+ I3 O1 V q/ r- x
Out[37]: 6 l% ]+ Y) o' P, o, p
0 22 ]; Q' L" ~) `& `5 e9 f
1 3
& |! R2 s5 {4 r* f( k0 ^: |5 R6 {2 44 U, a; X& e. a) G/ u7 f
dtype: int643 f5 O3 m' L. j% Y
6 a( v. c3 B- l. X. o- C1 F0 R
s.dt.month_name()( N5 E. P6 ` [
Out[38]:
; k8 a$ e. f8 P; U0 January2 f' h& f! B0 {1 X1 L
1 January5 c) j: E& K- r, P) p
2 January+ J& e; [4 Y5 d I$ K
dtype: object
# Z" |4 e8 ?. _+ G5 R
; [% H6 v C4 L, H% Qs.dt.day_name()
1 F' ~7 X0 |) h: m' t' }Out[39]:
0 i4 N# ~4 _# [3 o8 ]* m% o+ Q x0 Wednesday/ p8 R8 D) f, j$ K9 ^
1 Thursday( j" p! t# r- q4 A
2 Friday
' z8 l/ ~8 h* f0 Mdtype: object
/ P* Y4 l+ v! Y S: \- l: X
3 A5 B- a+ g8 }7 W) _- Q19 ~3 S1 @8 U0 N% k+ a: s' I$ u
2
- ^, k! E9 F2 U3
0 ]5 `1 b) d" t0 k48 ~& T- ?2 z, {" M7 O% V/ C
5" Q: y8 a) R Z# L3 u% M/ m7 H
6
6 }5 ~" E- A! A$ {# F7
8 o1 k! t. W, K; Q4 }& s- B( |8
& b; X/ g4 }3 K8 j5 M. E8 o+ }9' H, R0 l# m1 Y, \2 `
102 E6 _% k+ r9 N. c8 `( V- K
11
- b7 L2 j4 A/ y2 y6 `$ K129 @2 Y+ m% g& R- `
13/ U# O( Z8 G1 J+ Y' n* t
149 M. u* ]/ @8 R0 `
15. T; H& S: ]& B. w6 N9 Q0 A
16) ^* P/ L Q5 v$ K/ Z8 W ]/ g# r; z
17+ h% }" m9 N Y R; U1 y7 Q
18
3 }9 s! K: ^9 {/ K; H; P19 x, @, g- b' J' C
20
, K8 r8 d2 A, n7 C% ?第二类判断操作主要用于测试是否为月/季/年的第一天或者最后一天:8 b3 e, H! V. C/ V: e0 n/ n! @
s.dt.is_year_start # 还可选 is_quarter/month_start
0 x, P# @# h; g& h& f1 MOut[40]:
4 J- t, m: E; u0 True5 m0 N" N/ n" `; t( f! |: S0 {- X8 X Z
1 False
) [$ v3 T4 M# s8 V2 False; @- S. ?/ j% M% n4 Z& P( z* {
dtype: bool
2 R! O3 Q1 C5 a% \3 h. d" P
- W2 {8 U! U7 J" _s.dt.is_year_end # 还可选 is_quarter/month_end
) M7 |' N% @1 Q" ` @: W/ Y- U/ c- DOut[41]:
: u/ P* e& H R7 W/ P) q9 {# Q1 h0 False
: D& S$ m4 Y6 y% A" v1 False
; k( t/ u7 @* u+ \4 Y# A3 d2 False8 V4 a" V5 T) U3 }
dtype: bool1 a: t; v! b4 s% K
1; h) C2 l) W7 y" P+ F
2* ~3 B9 i+ t) _' h$ D
3
S \5 }2 A) |& E/ K8 G4
5 X; O& C W" P/ D' x' D( K5$ u0 p' ~2 v( y' p
6
) v& R2 ?: u: @75 {! D$ P; W9 s4 F: m
8. B; l$ `4 H' M R) U
9
$ @9 C# \. D* Y" q; a7 ~1 Y5 E$ J10+ u( G( L0 V6 S0 n8 u
11
: U b" H8 t& ~1 V125 U; w9 m- r8 J0 q7 q+ b1 {! o
13
7 e8 h0 M6 ~' I* Q u第三类的取整操作包含round, ceil, floor,它们的公共参数为freq,常用的包括H, min, S(小时、分钟、秒),所有可选的freq可参考此处。
. H* v7 F& m! Fs = pd.Series(pd.date_range('2020-1-1 20:35:00',2 s7 [* U; c. i |, `& q5 d& g7 y
'2020-1-1 22:35:00',
. v# q4 m1 j) V* M* V freq='45min'))
: u" S& R5 [7 d' S: |7 L7 Y
& Z _- ]+ r" w) t3 t/ @2 P0 _
% x* j( i9 a3 T8 i) As
8 y4 p0 R; Y6 j6 uOut[43]: $ D" _% o$ _& N6 D r
0 2020-01-01 20:35:004 B2 e6 b- X7 B: `' K7 H, n" J$ q
1 2020-01-01 21:20:00; A+ S2 X; T" A( P% t
2 2020-01-01 22:05:00: Q+ R% D5 }# f% |9 ^8 p
dtype: datetime64[ns]# L, z8 m( d% \: @/ L
% N# Y# N& k2 ]2 C% {5 ps.dt.round('1H')0 r2 V0 }. ]( O4 u; w1 q- U
Out[44]:
. O6 ^7 ~7 Q1 T6 n, d6 q0 2020-01-01 21:00:00
( y- S2 N( m, B) @- v1 T1 2020-01-01 21:00:00- x6 J% [2 s6 t2 X w9 d
2 2020-01-01 22:00:00! M: m `! j W$ x
dtype: datetime64[ns]. {# |! X$ S( _. T" J2 R- M% t. Y/ M, p
$ ^% F# ?9 F* t2 k) `
s.dt.ceil('1H')
4 L0 x+ F0 g+ [- w' d sOut[45]:
, N2 E( w% L s4 d0 2020-01-01 21:00:00
$ I7 O& a* H) m8 [ D6 U6 ^2 g! A1 2020-01-01 22:00:009 |" m1 h3 T& s T0 N
2 2020-01-01 23:00:00
: y1 a! v9 k- J: Z% gdtype: datetime64[ns], V0 ?) p* l/ R- M/ ?
# W- V" {% M6 xs.dt.floor('1H')! z* W, q7 U& v- @ f6 T3 k
Out[46]:
f, {! u# T$ ? k0 2020-01-01 20:00:00
* R3 ]* j2 m7 ~# [# H1 2020-01-01 21:00:00; U) _6 d9 W) \# V
2 2020-01-01 22:00:00
/ y# I I9 F: z" }3 Q! k8 g" \" mdtype: datetime64[ns]! I1 h' H# V" T W% ?. i, q
! A+ I* `! k% m* w+ B
1
$ W6 j8 X& n. c& E% ~2. e+ R; a6 w; m
3
" s' m" L+ L$ p; x1 U) V4, g6 B- o% s2 A
5+ i z6 q' s9 H
68 S' P. H. u6 i5 l& J
7
" ^6 s$ N8 Q" i( n/ [( e8 Y1 F! d- n8
8 f3 c/ e6 M5 N; d9
; a6 ~7 l: o3 m5 W! v1 U- o" ?10
; h# k" t6 E1 T; P* o11
3 S; w( E" Y% h3 P3 t( \12) w* u- m4 k: @" Q/ Q
131 K% o; Y- N# j& C- v7 Y
148 s0 B7 r. [" i0 p+ ^* C- p$ L
15
4 K6 {; I2 e2 V1 c. a' [16* u! a. I' e0 E! i" _, p
17
( N: h2 y1 a8 o3 \187 R1 A+ C! ^: l" o, o( P4 ]
196 Z4 W' D4 W1 j) w
200 d, Y; s' m# r
21( E9 H/ \' y1 t
22& @8 }; @" X" s+ x8 `# E# \# d, _ u% R
23
. |& R( \3 w, \+ Z( T245 g I; f' i8 [5 K
25
) s% U' F; F; m+ A26. J( E( T3 ?2 P5 e2 n- `
27* A" c* M. j% j; Y4 q% ?* e' q2 ~
28* K6 v: M- Y' e* {2 Y7 }( R
29
1 _6 ~( F) u: `7 B2 [3 w+ _- Y30
. W: s J8 W' C% y31$ m4 c6 ?& |7 b$ I- a% \# q
32
' |& k" [8 m5 c5 V! L10.2.4 时间戳的切片与索引4 O7 i$ Z; K4 X6 @
一般而言,时间戳序列作为索引使用。如果想要选出某个子时间戳序列,有两种方法:( d+ f- }% t7 Q w; D
* j2 a: d2 E0 U" @5 F7 C- j0 j利用dt对象和布尔条件联合使用
$ A* Q* D. y3 ?: O( i0 [利用切片,后者常用于连续时间戳。
H3 N1 w! T- |# M# {s = pd.Series(np.random.randint(2,size=366), index=pd.date_range('2020-01-01','2020-12-31'))
& g( C0 O( e7 G) ]5 aidx = pd.Series(s.index).dt
9 t% y \, S2 Y' f \1 @. O( zs.head()! P1 d* h% K- s$ i% H
& m' |; ]! C. b" R. i; X
2020-01-01 0
8 J# V4 W- i. {8 s' `' \2020-01-02 1; y" ]% |1 r$ [* W, }- o
2020-01-03 1
: V, D q# B0 [' c/ m% U3 t2020-01-04 0
5 u# n3 @5 A" ?; w6 b5 h1 A$ L2020-01-05 0
3 d% Z6 `, X6 i" b! BFreq: D, dtype: int32
8 k% b$ x0 _- z; Z3 Y14 t% F q$ k- \
21 {; T {+ P1 s2 y
3
% e r/ O" S( W( O+ M2 l4& a% J( g# `! ^5 Y4 x! ]- H
5
; m9 m% ^+ K6 L4 U6 v: j9 U6% x+ d' S, F9 Z# T, _, R
7
" |) t! F+ ?- Y" c# \5 X: Q& l5 k8. C* k) S( ]( `9 l" y, ]; U% v9 W
9# D" k% @ Y' ?
104 y5 m6 v9 U; M, d
Example1:每月的第一天或者最后一天" c5 f6 @4 _3 z; S, t4 g
! {+ G( [: p( A O5 V) R
s[(idx.is_month_start|idx.is_month_end).values].head() # 必须要写.values+ }+ g5 l M$ q
Out[50]:
. ~" j/ ^; K/ e) n. j2020-01-01 1
2 a7 F7 T: C& Q7 e2020-01-31 08 a2 p: A, \% a4 \
2020-02-01 1
% ?1 } i4 T7 r- \" R- W5 ], d7 s2020-02-29 1
8 H% c5 z: ~8 D b1 I" r2020-03-01 0
5 J, O+ \1 T. c ldtype: int32+ k4 H' a4 u4 a0 m8 ]) u
1: o5 X; Q. } A3 Y
2! D- `9 v# V! x) h! V- `- @
3( x* W6 O- n1 h2 `; K$ W7 Q5 `
4! w" C* C9 g4 J _ m! g
5* F; N7 Z4 g# X# N! T" K
6
4 S1 Y1 {: o$ p1 C7
( F4 K* j# S3 x2 N. ^8; Y5 M, K6 e5 A, D6 F
Example2:双休日) h$ n; O+ r' X# l/ S
% l3 A6 |3 L: D! ~% i Y! D8 ?& M# ds[idx.dayofweek.isin([5,6]).values].head()! V% {% _: x2 x2 T2 _( R! P
Out[51]: : Y% K4 B" W: p& b
2020-01-04 1
4 _- D1 }' Y" l O" L8 P) ^; N2020-01-05 08 C1 K0 }. L5 K0 q
2020-01-11 09 c" J5 n. |" p. r n
2020-01-12 1
* D5 Q: ]& s) I/ I0 x2020-01-18 1
! X4 \& V7 @+ }- _! C: h0 l; Jdtype: int32/ I$ R& J- M/ q z; B
19 [4 e g. _% C7 f9 o- k/ t& O s. m
2/ x. }1 a: y9 N! t
30 j3 Y6 c( s4 Z( P
4# H" Q8 N: _4 W" U% S6 ?
55 u) }2 j' S8 ?- ~* P: n) Z# _
6
7 I. t4 a' s/ _, J) R& s' L5 b# {7
5 G0 |0 i. A4 P. A1 ?8
& e3 O' b7 I) r" n5 z) K9 C7 K" n4 aExample3:取出单日值
" z" K) x; s w+ x F- F1 e7 K' _0 _
0 N; b7 a0 U1 Q8 J# t8 k' Ws['2020-01-01'] X+ V F, a& ^8 w) D
Out[52]: 1
: u, o- u5 d# t/ j& R9 @4 W
- |: p. i$ l7 }9 Hs['20200101'] # 自动转换标准格式
* u' w& a6 W9 O( v+ g+ yOut[53]: 14 s5 X; A: p9 a
1; M1 p6 j9 U& L- U$ s# |
2
( B% I! P3 N0 I' w3
- P5 B1 k" D4 @3 j( ^+ Y4
' I9 L" Y* H9 Z# R0 p& c+ T5
( h% n' f2 I1 e" W5 H- |Example4:取出七月
4 s0 q; S, B8 R4 X0 J1 [0 V |8 t$ G6 t
s['2020-07'].head()
8 C0 D! m7 a- q5 i% x9 r; X. lOut[54]:
0 R% O. Q( r% m- Y2020-07-01 0
( g/ V- J* Q4 O0 H3 U+ h2020-07-02 1
: [( x' T4 Q* ?( M, a7 `) ]2020-07-03 06 s6 L7 S1 S) n! F: k: ]
2020-07-04 0. @/ X, R" \* [
2020-07-05 0 c1 ` P2 a, d3 v w
Freq: D, dtype: int32
1 m: a, A# P' K) B, c1; K* ` n, I5 \5 U! U( P
2
( G% m+ P* H5 k$ Z6 ~! f3 z3
5 k: u4 m! q: G' j! G- b. U4* S, h4 |& J: i v* i, }
59 L3 [; X3 H3 B. U) H$ K
67 l( O0 W x! v6 K( q# l8 A% a
76 _2 j# @6 c2 K+ H
8
* D9 m0 v2 B" J Z% uExample5:取出5月初至7月15日0 p$ z% X; O, u3 ~0 x& Q% ?
2 n. N; Q q; b. f- H8 K: V+ r3 j
s['2020-05':'2020-7-15'].head()- j8 T: L$ \+ z9 R5 d9 T
Out[55]:
; s8 Y7 y/ _( \& K* V& G9 d6 F8 r) s5 x2020-05-01 08 G4 z; l1 ]8 n1 V- n
2020-05-02 1& T( b9 Z/ ~6 y# D' r* t3 w/ H
2020-05-03 0
+ j" J& L/ \' t2 x1 R9 y5 ?2020-05-04 1
" A% q' O- ~, ]- ]2 v2020-05-05 14 ~* Q- ]2 j$ Z* E. \! j/ u
Freq: D, dtype: int328 Y W1 \9 @, p- J! R
& ?5 k0 F( y9 ]' n2 H8 ?
s['2020-05':'2020-7-15'].tail()
6 D7 E5 a# C7 y1 G. E* V+ fOut[56]: ) F# c- K" Y$ Z, k8 D
2020-07-11 0' U9 [% l& ]/ Y/ C6 c1 G0 Z. X( `
2020-07-12 03 X+ g* ?' b2 Q. W" d0 E# R
2020-07-13 1
) j- R2 G' I0 j% v7 w* @2020-07-14 0" @3 U8 x0 b& i/ n. w! H
2020-07-15 12 |9 M) f1 O! ?
Freq: D, dtype: int32
- |2 |% q" w/ A/ \
3 c/ U# h% s0 Q4 v15 n' W/ h- a G/ T \+ J6 f
2
/ d( w# C9 K6 y9 C3 o$ \3+ h1 g+ }! J: ~# z, w5 P
46 p3 _4 e+ [* |, f8 h
5
" J/ D% ?& z+ i6
$ J% Z# m9 P# w' z7
: W. P m9 ^) I0 x83 J5 @. U) _% P) [" X
9
1 j0 K6 E C3 \10
: b5 f8 B# U9 D" @3 u11
% \7 h1 H) ] I- G; t2 L6 e! @12
" P' F5 d( _5 p/ G13
! J7 f5 p9 g2 C7 T14( L! e+ X i( E9 z4 X- f/ M3 z
15. l3 K" @3 R1 V& K- c
16& R; S0 G1 z! ]8 l
171 P. `, X8 s1 ?+ w
10.3 时间差4 Z* d$ d1 P c
10.3.1 Timedelta的生成* C! y% X8 K) t) ]
pandas.Timedelta(value=<object object>, unit=None, **kwargs)
; x6 ^- w. }6 Y) s6 `, I unit:字符串格式,默认 ‘ns’。如果输入是整数,则表示输入的单位。; s$ @; p& y* N
可能的值有:+ N. q, `# Q7 H
7 F9 }% }6 D. j: I' q+ \, i‘W’, ‘D’, ‘T’, ‘S’, ‘L’, ‘U’, or ‘N’
- W) l0 o2 K' m) {$ a! D‘days’ or ‘day’& G& n5 {( @4 B
‘hours’, ‘hour’, ‘hr’, or ‘h’
! A( m. X3 I% j9 ^. H ~‘minutes’, ‘minute’, ‘min’, or ‘m’
( k \& o! i0 [& B% N3 o; u+ m‘seconds’, ‘second’, or ‘sec’* x. F4 n* f2 _
毫秒‘milliseconds’, ‘millisecond’, ‘millis’, or ‘milli’/ ~' V, D( Y8 N% T
微秒‘microseconds’, ‘microsecond’, ‘micros’, or ‘micro’; o: z: ~# c; y/ w
纳秒 ‘nanoseconds’, ‘nanosecond’, ‘nanos’, ‘nano’, or ‘ns’.
8 g _- D3 @& r0 O" W- _+ o" D时间差可以理解为两个时间戳的差,可以通过pd.Timedelta来构造:
4 I5 ~9 W& F0 ?0 y1 w* ]# Vpd.Timestamp('20200102 08:00:00')-pd.Timestamp('20200101 07:35:00')
+ o, a+ k) O0 C3 D# c0 ]Out[57]: Timedelta('1 days 00:25:00')' P6 e' B3 a# o: O6 T0 w
3 F0 q+ J# ~3 j! V" M8 hpd.Timedelta(days=1, minutes=25) # 需要注意加s
% m- J) Z! }+ pOut[58]: Timedelta('1 days 00:25:00')
3 W- Z0 H) Q N Y3 S7 s- Y9 X3 S5 g
pd.Timedelta('1 days 25 minutes') # 字符串生成
7 k# l# U* J' a) I) E3 ]6 XOut[59]: Timedelta('1 days 00:25:00')
: M ~( c/ S/ d
4 Z# p! N7 t9 ~( z f2 R% L! }pd.Timedelta(1, "d")
3 k9 a6 h7 u& ?Out[58]: Timedelta('1 days 00:00:00')
+ A* `0 k% P: Z- q11 a8 e/ F' k) S5 ` X# f
2
* j! P# o' m! C3
7 S4 z( [! L7 \2 k$ V" \5 y$ @4
9 {0 A9 L3 b" S4 X& m: M5
! U1 T5 b/ [- |1 N9 e9 k6
/ g" S3 ?7 x; }8 I! \" ~1 `. l7
& ^$ g. |; i ^2 M8 W% A* F2 S% A* a" Y7 ~ Y) D
9
. t9 r' O" N0 @10
6 X8 v* } P/ R7 |0 s W11: {7 ]. y; P* x
生成时间差序列的主要方式是 pd.to_timedelta ,其类型为 timedelta64[ns] :' u, O4 W7 h; z5 k- C
s = pd.to_timedelta(df.Time_Record)4 W1 [7 e, P3 C/ J' A2 b! b
' a2 ?# W7 `2 v- s/ h
s.head()
" c* X! ?! ]+ c$ h' Z, |, nOut[61]: f1 `0 g [( K6 P& e* m) t
0 0 days 00:04:34
$ @" K5 c. k4 n: n1 0 days 00:04:20$ g9 d+ o+ ~+ N& e" g
2 0 days 00:05:229 V0 X, S1 l% w y9 O' r
3 0 days 00:04:08
" W% K7 a) y6 D4 0 days 00:05:22
J( m# `# v/ B* J7 u% c3 `Name: Time_Record, dtype: timedelta64[ns]# I- b2 I6 ?" I# x( Z) p2 w* Z. }
1, `! I- ? q, M* l
28 A# Q$ ^4 R; N5 V. ?* _
3$ A6 B% a+ s3 Q# f8 e7 H5 @
4
% r/ N+ P; ]! n0 M* Q1 n; N0 O" E7 u5" X9 P- Q7 y: d. [. @
6
$ M2 M$ _' l5 c& s" u$ Y1 d3 Z' E4 t7$ \ w; N( h, V* M3 u1 o
87 `% V$ {- [# l
9, F! @" J# q3 v* r
10
* O# |3 g# J" E Y3 P, f7 d7 F与date_range一样,时间差序列也可以用timedelta_range来生成,它们两者具有一致的参数:
; Y; _7 i4 X3 Y$ R7 ?pd.timedelta_range('0s', '1000s', freq='6min')
) }# Q' @- h2 b& C2 _8 d! r1 F# S. Q4 OOut[62]: TimedeltaIndex(['0 days 00:00:00', '0 days 00:06:00', '0 days 00:12:00'], dtype='timedelta64[ns]', freq='6T')
6 e' j/ l+ D. N0 X9 ~# t2 S+ X2 O# g& o1 j1 Q; P
pd.timedelta_range('0s', '1000s', periods=3)7 g$ h$ w$ W; F4 o6 J
Out[63]: TimedeltaIndex(['0 days 00:00:00', '0 days 00:08:20', '0 days 00:16:40'], dtype='timedelta64[ns]', freq=None)
. r" e( U/ T8 p4 G m ?1$ G7 v8 j: p- g' q* P
2
. L% i6 \5 h" K* m5 G6 i3
, x% U2 k! p3 f. T& l/ _4# `0 [& t4 q- T! Z- n
5
, x2 L, y: Y5 O对于Timedelta序列,同样也定义了dt对象,上面主要定义了的属性包括days, seconds, mircroseconds(毫秒), nanoseconds(纳秒),它们分别返回了对应的时间差特征。需要注意的是,这里的seconds不是指单纯的秒,而是对天数取余后剩余的秒数:
9 i8 d4 n- T& @) T+ u, ws.dt.seconds.head()- y) I+ [) x: g' h1 N% g2 m
Out[64]: # ^+ T5 P- o& w8 ~' d
0 2746 P& ~& l: B- }. l! }
1 260
) A1 B4 i5 K0 R9 y4 A" a2 322
; h+ {" |* r6 x4 G5 r3 2484 B: _* N- Z L+ g
4 3228 |" w, t9 q* O& G" t
Name: Time_Record, dtype: int648 k# ]# g( s4 O6 F8 t
1
' S& F1 x2 l" M2 e c2" v2 l) B9 r( Q" M. P% x
3: r5 ^/ N8 a, a: M, }. ^) R- x
4
& R g% k& f0 @0 V5! {3 n! C" q1 Z9 f3 B: M$ q
6# ^- x5 ~8 \9 d! X* S% T
7
; t7 r5 ~( E3 o& z: A: R8 {8. U: `, g0 H8 J$ @
如果不想对天数取余而直接对应秒数,可以使用total_seconds8 T# ^# v3 S/ H3 b8 s+ w6 z! A/ P
% w: \7 w: V6 T2 H
s.dt.total_seconds().head()% |. C) b$ K( d2 u9 I
Out[65]: $ T# M9 S1 C9 R: R
0 274.0
. b+ f5 E% C( u+ B1 260.0
' ^2 W7 R5 }7 n& Q* E/ X2 322.0
1 ^: Q+ f5 N, o- g3 248.0+ j' f( q0 n; L4 z
4 322.0% c' x4 C8 O: q# J$ L
Name: Time_Record, dtype: float64& J z% Q( R5 l, A% D1 ~
1# W+ D* F9 N+ l( s2 P0 g: ^! M" N
2% I# ~2 J. B: X. X: q; b
3+ [1 e- H' J8 q3 E; k6 S/ x, [
4
& h& h! a* _) T1 ^' S# r5
$ [' L7 a8 z# H T$ s6
8 o2 }, d2 A$ T2 s7
8 L4 K- n) m3 g$ U- f8
) _8 Z7 I P) p0 U+ a与时间戳序列类似,取整函数也是可以在dt对象上使用的:- j( z: i0 y0 k$ u
& @0 y% A- w l( c
pd.to_timedelta(df.Time_Record).dt.round('min').head()+ [4 T( c7 T3 A1 A9 b
Out[66]:
9 y0 r8 m8 | u; U0 0 days 00:05:00
1 Z$ ?4 }* ~- r7 b8 V: h0 C ?7 o; J1 0 days 00:04:008 i7 c' P4 q0 Q1 `3 _" R
2 0 days 00:05:00
8 N7 U w/ Z" O; x3 0 days 00:04:00
G* m0 C3 K3 D9 Q) M4 0 days 00:05:009 {. h- }" v( {# B
Name: Time_Record, dtype: timedelta64[ns]
. e7 q' k# d% ~# W. f1& t3 D# X8 A# H( t7 C
2
1 K, u: }( C# m' A- F7 |3
" ~ {8 k8 X. Y" @! H$ n# f4
6 Q- x1 n: \7 d" T; l5" K$ w/ b9 O; @9 z
66 Q, H# C% S2 f1 D2 k# t
74 \2 G( y0 C) x; C1 w% n
8
( S# Y# n4 E" @2 L" j) |10.2.2 Timedelta的运算
# I; R- p- n# c( b0 g- j% V单个时间差的常用运算,有三类:与标量的乘法运算、与时间戳的加减法运算、与时间差的加减法与除法运算:
; ?. Y! H. k! X) |* w9 A4 xtd1 = pd.Timedelta(days=1)
/ D8 z8 {( H9 y$ a, \td2 = pd.Timedelta(days=3): s( u" \( k' ?5 K/ P0 u6 p; C2 j
ts = pd.Timestamp('20200101')
; \' T" b% v5 C
0 Q' `! Y6 n S7 vtd1 * 22 a# W: F8 I& o' S% F1 v6 y5 P
Out[70]: Timedelta('2 days 00:00:00')
9 V: C. I" B* T, B: L# z6 [: a
9 a3 _+ E& }) d* ?$ L& K( rtd2 - td1$ ^& w, f* q; ^- }2 d
Out[71]: Timedelta('2 days 00:00:00'), P; D$ D8 W4 G$ `
) J5 k) T/ D4 M. S! i
ts + td11 S: J( P$ b( [) j% P
Out[72]: Timestamp('2020-01-02 00:00:00')( H; L3 y; o7 w A) j/ ]8 b) b
$ q5 B) G' ]' M. w. ^9 Rts - td19 j/ I, Q- q6 H" _+ Q
Out[73]: Timestamp('2019-12-31 00:00:00')
# W" m- N3 G3 [3 m- X$ K6 s4 }1% ?" C1 ?' u9 Z" p
2
0 T3 N8 G1 |7 D4 E3
& C. `! r( i& S6 E' z) j4! j8 u2 B" ?1 _, j. j9 t& _
55 z7 U9 A6 g' o0 P
6
4 r/ N) D1 k. w# O; U0 r7
9 v6 v r, ?) i5 Z9 v6 X# K: t3 Q8! t( P' S; p6 c7 f( J5 |. W
9
/ O* L) y8 { \5 b4 l: m' f10
1 A) u7 s( W, p9 ?, U11
( K- a6 k4 {0 ]" i3 k" d* T1 j12* B- ~; `6 s; ?8 g2 i$ c, \$ X) o
13
! Q; n& p! ?; q' {. [14
+ `& D6 b; J _6 f: p) ^7 o15- X/ P! p5 |9 T6 Q. _( V& K
时间差的序列的运算,和上面方法相同:
' c5 s( q, g5 Z# h6 ^td1 = pd.timedelta_range(start='1 days', periods=5)% ~$ Z( J2 ]3 N1 R3 d( P
td2 = pd.timedelta_range(start='12 hours',
! O! [$ d6 u7 H freq='2H',
6 `# t* S* s/ e6 U! v! M4 e# \/ T periods=5)' o1 _, d" o1 \2 U5 T' L
ts = pd.date_range('20200101', '20200105')$ }+ A0 H5 B! s
td1,td2,ts+ N* I' \6 w# ~/ G, L$ J2 u" {
9 l- A8 `6 p+ s9 ~6 p& \
TimedeltaIndex(['1 days', '2 days', '3 days', '4 days', '5 days'], dtype='timedelta64[ns]', freq='D')9 | i+ V6 H: ^3 F8 o1 [
TimedeltaIndex(['0 days 12:00:00', '0 days 14:00:00', '0 days 16:00:00',) }; p/ n6 f: d
'0 days 18:00:00', '0 days 20:00:00'], dtype='timedelta64[ns]', freq='2H')
0 p- z7 I6 u" r6 r: [, G. c. eDatetimeIndex(['2020-01-01', '2020-01-02', '2020-01-03', '2020-01-04',
' z' Q' R/ t) ?3 M '2020-01-05'],/ H! u/ m6 R8 s
dtype='datetime64[ns]', freq='D')7 q: f8 ]8 W. k# g& F% I
1+ G8 k& }% a( [5 c$ k4 s
2( _% g9 [5 ^, X: M% q
30 R( o" T1 E9 b7 s$ I0 V" x
4
( q$ S& H6 y! `7 Z7 Z! h54 y# R) Y' p. t9 `2 |: g+ v9 ~
6# y# O& m) l8 D. O7 P/ G: Y
7
! m: @9 o$ z# X1 Y: a$ C0 R87 t+ ^) @" x1 w$ _
9
3 h9 Z/ M" B7 B5 y N V8 C7 U- C- t10
8 v. y3 q1 Y+ s* s. h113 t: h& ?! `3 q, H
12! b* [. B1 v4 M. p. X
13! F/ R' R: u( N1 g8 Y) b( @. v
td1 * 5 p7 ` r9 G5 [
Out[77]: TimedeltaIndex(['5 days', '10 days', '15 days', '20 days', '25 days'], dtype='timedelta64[ns]', freq='5D')
3 l# Q, J5 ^& r0 @( y
* F* A6 f5 ^% P ]: i* L1 itd1 * pd.Series(list(range(5))) # 逐个相乘
( O7 A3 Y, `: S1 ~2 sOut[78]: & l3 b4 D7 p5 W6 ]7 c; k
0 0 days
+ U- X, G7 V9 i! j! E7 x; C1 2 days5 c* u0 r4 t7 o% J, A8 i
2 6 days7 W% m2 v# B- z2 x! q- ]
3 12 days6 P2 C1 Q% d. t0 ^
4 20 days
C# X' ]/ J! F0 \dtype: timedelta64[ns]
$ x5 U5 ?+ q! Q, o
% |% U8 E% i r) K' I: E! C6 ]td1 - td2
/ `; K; X2 L3 v: A3 g0 T4 c6 P! FOut[79]: & G* V @% {( Q) [% Z/ `
TimedeltaIndex(['0 days 12:00:00', '1 days 10:00:00', '2 days 08:00:00',2 Q2 G& d" E9 o+ \
'3 days 06:00:00', '4 days 04:00:00'],
, z) q }0 D* Y* e dtype='timedelta64[ns]', freq=None)1 M: ~) W: X# l6 G0 U7 q
6 c: k4 [+ k) Y% r2 ptd1 + pd.Timestamp('20200101')! z1 H) G) Q7 j- Y
Out[80]: / u& M6 l1 m, z- c
DatetimeIndex(['2020-01-02', '2020-01-03', '2020-01-04', '2020-01-05',1 P( O O# v8 o0 }
'2020-01-06'],dtype='datetime64[ns]', freq='D')
9 G2 ] ^; T2 s8 i0 _3 K% T$ g1 T* [8 C9 y* a
td1 + ts # 逐个相加
$ h2 n2 v2 G6 Y1 v' j% G, b* k; B' dOut[81]:
- O" q8 }- K3 n& w" O" s7 r) dDatetimeIndex(['2020-01-02', '2020-01-04', '2020-01-06', '2020-01-08',
! y- ?2 z, O# R2 X0 V7 g '2020-01-10'],
) ?7 k% U2 a: ~! Z9 n2 M4 H. U) ? dtype='datetime64[ns]', freq=None)
' J! p# Z0 ]! g% A/ e, N* d6 M( d" N. S$ P; [$ k! k
10 o/ S. n2 G* U. H: L
2
; i( W, _' D) N. b- \6 T3
" }6 j! G5 N3 ?( g7 x9 N7 x4: m$ z% i. E( ]( d3 s
5
i. O& k2 A7 {$ I) u+ |$ `) V6 d" f% w) g9 ?! ^; \+ |1 L
7/ _# G3 S+ U9 ~
8
. }( x) n# H0 q) Q+ [9
- k9 f/ b2 s" c4 u4 ~# G# Z4 O10
# u0 H+ Z/ {/ @1 k- N4 g6 L6 V# h4 T11, B3 J7 Q- \. H* h4 t
12
8 r! y% ?1 r" l# {! R" g13
; b1 d) E2 W1 Y! l: q" n1 t14
% S& |$ O _; l l15
6 f2 q& j$ W9 `: J1 X8 q! X2 ~16
E. x; F# n6 f- I0 F! U17. o6 w) Q0 V$ w
18, G5 c5 ]& o1 G
198 h. Q. c( Q8 Q7 @
204 E( ~6 A2 s5 j/ x
21
: X6 `0 \; d+ y221 h4 h0 w' @6 T% b0 i; g
23
" V( J0 Z& T% x$ c7 L8 a24
' w2 k& `; U1 h$ J251 O- A' Q" L+ _! L0 X1 v
26' g' o* Q& j0 y8 O7 W
27$ Z0 p, ^' X) }8 V+ f, |! b% I
28( f# s1 a( Y4 s. p6 d& t/ d
10.4 日期偏置
: g6 z% k- O! v/ F10.4.1 Offset对象- g! A0 v; D$ ~
日期偏置是一种和日历相关的特殊时间差,例如回到第一节中的两个问题:如何求2020年9月第一个周一的日期,以及如何求2020年9月7日后的第30个工作日是哪一天。+ h' J" t, D/ H p5 k" o
; J, p& f* Z r# O) J; GDateOffset 类有10个属性,假设s=pd.offsets.WeekOfMonth(week=0,weekday=0),则:( L( G* B3 y: F4 U# ?
# ?9 r9 s. _" T9 [6 Z* ds.base:<WeekOfMonth: week=0, weekday=0>,返回 n=1 且所有其他属性一样的副本1 W- Z2 `+ G+ m
s.kwds:{‘week’: 0, ‘weekday’: 0}
. Y3 b* \: A3 Hs.wek/s.weekday:顾名思义* ?2 B; G# {0 R* L
有14个方法,包括:
0 w: ^" q. }5 e q. R3 ~
6 z- F, u, d2 X) aDateOffset.is_month_start、DateOffset.is_month_end、DateOffset.is_quarter_start、DateOffset.is_quarter_end、DateOffset.is_year_start、DateOffset.is_year_end等等。
2 o3 e- z+ _1 G4 Ypandas.tseries.offsets.WeekOfMonth(week,weekday):描述每月的日期,例如“每月第二周的星期二”。 y( c+ P4 V% q r* v$ T9 E
5 F' Z, `# T! C( n) W0 f
有两个参数:) W: B/ [0 V* l% K7 }& b
week:整型,表示一个月的第几周。例如 0 是一个月的第 1 周,1 是第 2 周,以此类推。" s2 t+ W% v+ ]4 O F2 g5 L7 l
weekday:整型,取值为[0,1,…6],表示周一到周日,默认取值为0(星期一)
2 |' T6 U; \% I, cpandas.tseries.offsets.BusinessDay(n):相当于pd.offsets.BDay(n),DateOffset 子类,表示可能的 n 个工作日。
" Z6 J1 X# C3 ~" |4 f
3 G: @! I9 Z. [' O$ a& Kpd.Timestamp('20200831') + pd.offsets.WeekOfMonth(week=0,weekday=0) J0 F4 d& F; ?3 [, x: |- t
Out[82]: Timestamp('2020-09-07 00:00:00')0 f- X: d! J% J- J
, `% K: _ O* n) q1 @ s$ h
pd.Timestamp('20200907') + pd.offsets.BDay(30)' l/ a A+ x" M' f- F7 c# m+ I. O
Out[83]: Timestamp('2020-10-19 00:00:00')
1 u, P5 o7 @9 F! O1
* X4 `! u! o/ o3 E21 p4 @- K; z7 R( b u( d
30 j6 F0 F1 a P
4
$ n& Q: b x$ W% s1 r( v0 C, ?5
2 ?$ i1 K/ G" x4 V9 f1 j9 s 从上面的例子中可以看到,Offset对象在pd.offsets中被定义。当使用+时获取离其最近的下一个日期,当使用-时获取离其最近的上一个日期:3 _( A }- d2 h. X6 O! t
$ N0 v& S6 }" n5 n* x" ?7 D: v
pd.Timestamp('20200831') - pd.offsets.WeekOfMonth(week=0,weekday=0)
4 r+ @8 ?( {' P3 s9 {$ |Out[84]: Timestamp('2020-08-03 00:00:00')
2 ?: p- A, Z1 i9 U/ @7 N' ]. m. J
% U; g7 h/ ~- s: |0 apd.Timestamp('20200907') - pd.offsets.BDay(30)" H* L5 G/ P' z2 C
Out[85]: Timestamp('2020-07-27 00:00:00')" T8 ~" o1 x; y* L
* ~# S" K) W, b5 j# `+ {8 i
pd.Timestamp('20200907') + pd.offsets.MonthEnd(), H& ]/ T$ Y. y, p2 U5 y' I B+ A8 [
Out[86]: Timestamp('2020-09-30 00:00:00')
, l1 f2 L& m7 M, j& c. o; N) N1
3 G% u: z$ ^) h) P% Q24 H9 k. b* G1 T# u; P! C. \) f4 `
3. V' }# m: E. g5 x
4
0 }. Q. n; y; y5
0 f0 J o, v; G: C6% w, }' q- j3 N6 j
7; @4 D# @* o. d- j% S! ^5 ]
8
7 v% P) `; ?5 w 常用的日期偏置如下可以查阅这里的DateOffset 文档描述。在文档罗列的Offset中,需要介绍一个特殊的Offset对象CDay。CDay 或 CustomBusinessDay 类提供了一个参数化的 BusinessDay 类,可用于创建自定义的工作日日历,该日历说明当地假期和当地周末惯例。
% }( @, R/ a0 R( m: D 其中的holidays, weekmask参数能够分别对自定义的日期和星期进行过滤,前者传入了需要过滤的日期列表,后者传入的是三个字母的星期缩写构成的星期字符串,其作用是只保留字符串中出现的星期:! I! t/ E+ j* s4 s
! E7 ~1 W" P1 e4 m: e% |8 _" h
my_filter = pd.offsets.CDay(n=1,weekmask='Wed Fri',holidays=['20200109'])
" s4 e5 c# |7 E$ K, M9 C+ Zdr = pd.date_range('20200108', '20200111') Q8 n; N, _) ^, |3 o5 V; h
6 M; x* W9 q( K5 r* Y& C6 q
dr.to_series().dt.dayofweek
( U3 n3 [# S# \0 BOut[89]: , c& E( }/ F" Q- {. ^; e c& N0 l
2020-01-08 2! E, ~- f0 r. o
2020-01-09 36 L5 j4 K0 U2 l# g( n" j
2020-01-10 49 W/ z4 x( b- p, [& _. J
2020-01-11 5+ q" w, x- O7 a- s2 L; J
Freq: D, dtype: int640 T! G# {. u* r3 |0 k
0 c* C) |7 M) B[i + my_filter for i in dr]
0 v% R6 k$ S1 _% `Out[90]:
8 k& _0 z! E5 c* C[Timestamp('2020-01-10 00:00:00'),
1 G/ M1 Z7 o% Z2 V Timestamp('2020-01-10 00:00:00'),( U5 G3 ~9 ^, P; z) ]
Timestamp('2020-01-15 00:00:00'),
" \3 E7 x- t( T6 X# Z Timestamp('2020-01-15 00:00:00')]) y6 a( x* m, y7 m. d, I9 Y
6 ^7 Z% u+ o; Y1 h( G18 Q$ i+ i9 z, U& y
2, s; V2 j) q- w7 I8 l& `
37 S& B: B% h9 I8 V3 U& w# ?" ~8 A/ `
40 }5 H8 ^- k, o* j* ^/ P4 F
5! S5 I. h! l3 U2 ~9 B+ A0 m
6. a+ K* l5 |5 w8 M, E
7$ a+ A; t' h* j7 H( y8 d5 K
8
, Q/ a: I1 |# C9; u2 W( v3 _: j8 c
10# N1 s, N; E# o8 T: Z& R% J# x
11
9 a$ n6 t) C- g2 x2 P2 s12
2 e6 M; z* D, ^131 f" `4 @! Q- X1 v1 W
14
- k$ ^* q$ T4 m5 `5 _7 ]" d: j15+ d" u$ }4 |, O' ~
16
& ]& f' M0 V0 R) L( ~8 J `' b17
/ A1 E5 o! p6 p. W 上面的例子中,n表示增加一天CDay,dr中的第一天为20200108,但由于下一天20200109被排除了,并且20200110是合法的周五,因此转为20200110,其他后面的日期处理类似。6 m0 M$ }4 d3 G/ e( F% Y
. R, J5 `' Y$ h e2 I: A
【CAUTION】不要使用部分Offset" a- j! S, y9 D# e9 k4 y; m9 o6 m
在当前版本下由于一些 bug ,不要使用 Day 级别以下的 Offset 对象,比如 Hour, Second 等,请使用对应的 Timedelta 对象来代替。& a9 n% E, e. ]
. A; b* [6 ` Q5 ]. Z. N10.4.2 偏置字符串$ B4 F0 Q L3 O: v
前面提到了关于date_range的freq取值可用Offset对象,同时在pandas中几乎每一个Offset对象绑定了日期偏置字符串(frequencies strings/offset aliases),可以指定Offset对应的字符串来替代使用。下面举一些常见的例子。
- `% \' U; h8 m: d
$ ]3 ]. ^$ Y, @ v" ~% u; M Offset aliases:pd.date_range函数中的freq参数,为常见时间序列频率提供了许多字符串别名。 也称为偏移别名Offset aliases。偏移别名列表点此参看(大概27个)。
9 n$ M8 m" H5 S( X6 q# _2 _6 s8 }6 Z' G: F& e. \6 b* D
pd.date_range('20200101','20200331', freq='MS') # 月初
/ Q, d N5 z2 n% \Out[91]: DatetimeIndex(['2020-01-01', '2020-02-01', '2020-03-01'], dtype='datetime64[ns]', freq='MS')' {9 q6 |, P& f" V7 p. r
( H8 F& j5 f+ E
pd.date_range('20200101','20200331', freq='M') # 月末
% {" z2 m5 V; ~: B! U( b# aOut[92]: DatetimeIndex(['2020-01-31', '2020-02-29', '2020-03-31'], dtype='datetime64[ns]', freq='M')
: D$ |+ D/ E6 a: x" \) v" l7 ]1 O! D0 Z9 O/ C5 o4 o& G5 o; z
pd.date_range('20200101','20200110', freq='B') # 工作日
' \! ~( O" W1 L: zOut[93]: ! N2 P3 J* {2 R. P' N
DatetimeIndex(['2020-01-01', '2020-01-02', '2020-01-03', '2020-01-06',- P: n& `1 z z6 E
'2020-01-07', '2020-01-08', '2020-01-09', '2020-01-10'],/ y D3 m; Y' \/ \
dtype='datetime64[ns]', freq='B')
+ ^5 [+ f8 k+ o$ M- m; l0 {/ R4 ~% P7 ~0 s, U1 ~" h
pd.date_range('20200101','20200201', freq='W-MON') # 周一
- H/ a/ k$ q+ H7 g" F% FOut[94]: DatetimeIndex(['2020-01-06', '2020-01-13', '2020-01-20', '2020-01-27'], dtype='datetime64[ns]', freq='W-MON')
7 b( ]4 @# T( X$ m' a7 c+ h
3 K2 C) ]; t0 \) I" Kpd.date_range('20200101','20200201',
- {( l, p) R4 P+ T; R6 R# A freq='WOM-1MON') # 每月第一个周一
. [( I+ T* f. b( q1 [4 b- t, I, u# o; x! F
Out[95]: DatetimeIndex(['2020-01-06'], dtype='datetime64[ns]', freq='WOM-1MON')
6 u0 N' L* A/ V( e# e# G5 U% X' Y* C0 t; r
1! }" D$ ]: {! y& b6 T2 u
2! i( c( {0 {5 F+ O, S+ b7 \0 r
39 \! o' ~7 A! r: `& q8 a
4
c F4 Y+ t; z; H' W54 H5 L. H- _1 K* v3 B. e
6
1 ?0 N: A: s- o* y: l. O2 o7
4 i& K5 I! h% `1 f0 Q0 G6 s87 W* a _1 O1 x
90 A' h' U3 @0 F: z7 z( `7 x: v2 x
10
( ^& [8 I% L/ u& w$ \110 f3 J0 ^/ {3 E) U
12
# p# B( s% f$ U6 }$ ]13
* _8 g% w; ]! |9 S4 i! U4 Y! c14
% i: J) a! A7 x- w15
: E; m0 l; U) j6 A& o16
0 [7 U& L( k' g* C! k17# |6 t# W3 B3 r$ f
18; L/ u/ J3 H5 Z! N, a" L' a _- e% P
19& N, r8 q; E- r( {
上面的这些字符串,等价于使用如下的 Offset 对象:9 h& L' K+ Y$ v0 Q3 E* }
1 i9 S' A; }0 n3 `& ]$ b9 |, |! O% Vpd.date_range('20200101','20200331',
0 U8 y4 I! I% V3 p" { freq=pd.offsets.MonthBegin()), n* |+ P4 D3 ?; p4 u. f
: Z0 F# Q Y7 a1 h$ @
Out[96]: DatetimeIndex(['2020-01-01', '2020-02-01', '2020-03-01'], dtype='datetime64[ns]', freq='MS')
+ l1 O- H! U$ ^% H( D0 e; B8 z+ j9 l# A
pd.date_range('20200101','20200331',
, W2 D' I# e+ A5 Q; j2 P freq=pd.offsets.MonthEnd())
& l" e1 @" R% b- g
; }5 u5 P# k. v9 o! U# }Out[97]: DatetimeIndex(['2020-01-31', '2020-02-29', '2020-03-31'], dtype='datetime64[ns]', freq='M')
% U5 h) b8 h: l7 b, _5 C% R4 P r8 u4 ~) |; S
pd.date_range('20200101','20200110', freq=pd.offsets.BDay())# I( G; r( x. j
Out[98]:
: u5 X3 q2 s0 G$ L* tDatetimeIndex(['2020-01-01', '2020-01-02', '2020-01-03', '2020-01-06',3 n+ q% P8 N9 S* T! X9 |
'2020-01-07', '2020-01-08', '2020-01-09', '2020-01-10'],& L: O9 V8 Q! X6 h8 p3 }6 I
dtype='datetime64[ns]', freq='B')
4 x9 T( d! H2 s/ m/ D
. r9 K- Y1 Q/ ?' i/ R# ~! w3 z0 Npd.date_range('20200101','20200201',8 @" U: N, w, h2 b/ m6 d- r% V
freq=pd.offsets.CDay(weekmask='Mon'))' {7 Z0 p2 @3 M/ ?+ D h
* j3 A* u$ w- c+ E n
Out[99]: DatetimeIndex(['2020-01-06', '2020-01-13', '2020-01-20', '2020-01-27'], dtype='datetime64[ns]', freq='C'). _8 z L8 {2 D# J. q& U
# M/ p8 {( w/ r3 z6 z' p' s
pd.date_range('20200101','20200201',
$ u: k% d( b: T1 l& q7 c freq=pd.offsets.WeekOfMonth(week=0,weekday=0))" v E4 d% s6 t1 Q- o
! ^- A: n& z" [
Out[100]: DatetimeIndex(['2020-01-06'], dtype='datetime64[ns]', freq='WOM-1MON'), r" W, a% y8 x) K7 z7 t
* [# J) i" x& p1 X; e1# a) }" g/ O9 g3 N
2
! S5 m; _. V. M7 _& i) r+ R$ M3
+ `! z4 G) W9 M+ H: z$ d$ ^4! L- J( I1 ?0 G6 w, _
5& D0 H/ y6 J# f% ` E* |
6
( w; q1 U9 O: d7
/ P% ?9 Y K" i8! k3 b6 |8 k1 M2 s1 f% A
9
" P, A0 [0 v9 W T0 @& u4 z10+ g; c& Q! \1 M2 m, X
11# k0 E, o D3 |# s8 P8 Y
12
/ e: v, D; p, p) L2 ^6 z13
. h3 b( k2 [0 ?# H6 O9 {- A$ G14
3 X* |8 I& L$ M" E$ h+ }150 Y# G5 y" l3 R' k0 }
16
s: c, E5 ?6 ?! A$ Q17
" M" v7 B1 r8 E1 q7 f9 {18) w- M; W/ A0 g
19) U' I# q, S G2 }' t
205 c0 [; {: |" V* t1 ]9 f* r
21
8 O) x% Y1 g7 O0 ]22& H9 `0 L% @; F: N4 H( Y
23
" G3 n- @9 M' T7 N' _1 }6 B: V2 X24: i0 {6 A g2 S- p4 {& \/ I7 N( l
251 c n# Y9 K% M, K; ^/ G) @
【CAUTION】关于时区问题的说明
, D. }) e$ i- f- g: }4 C/ ^' N 各类时间对象的开发,除了使用python内置的datetime模块,pandas还利用了dateutil模块,很大一部分是为了处理时区问题。总所周知,我国是没有夏令时调整时间一说的,但有些国家会有这种做法,导致了相对而言一天里可能会有23/24/25个小时,也就是relativedelta,这使得Offset对象和Timedelta对象有了对同一问题处理产生不同结果的现象,其中的规则也较为复杂,官方文档的写法存在部分描述错误,并且难以对描述做出统一修正,因为牵涉到了Offset相关的很多组件。因此,本教程完全不考虑时区处理,如果对时区处理的时间偏置有兴趣了解讨论,可以联系我或者参见这里的讨论。
+ ?, @) I8 c2 ?$ o& e# I3 j7 X* K
+ i' ?7 S- G& T2 Y7 d) ]10.5、时序中的滑窗与分组
& N( q: {1 W7 J+ c10.5.1 滑动窗口
- |# @& O0 o- {8 M 所谓时序的滑窗函数,即把滑动窗口windows用freq关键词代替,下面给出一个具体的应用案例:在股票市场中有一个指标为BOLL指标,它由中轨线、上轨线、下轨线这三根线构成,具体的计算方法分别是N日均值线、N日均值加两倍N日标准差线、N日均值减两倍N日标准差线。利用rolling对象计算N=30的BOLL指标可以如下写出:
) g' W0 O% A- U0 ]
+ F) P; R, o2 P0 v1 oimport matplotlib.pyplot as plt" d0 U8 R; L$ o$ W Y% t: @* j
idx = pd.date_range('20200101', '20201231', freq='B')
; d: i. ? M5 B; L7 Unp.random.seed(2020)- Q- d+ k1 M: v9 ~7 T
- l S3 l* g. V6 ]data = np.random.randint(-1,2,len(idx)).cumsum() # 随机游动构造模拟序列,cumsum表示累加2 N- [1 [! |, }' n. v
s = pd.Series(data,index=idx)
4 p) O3 u, |( b; cs.head()4 t1 X6 S, {& A. c/ i7 ?: r
Out[106]:
7 i& [# {, Q8 Z' D: N. o' l2020-01-01 -1
% s; \3 Z% n [' u; S5 r% d8 S2020-01-02 -2# o* _* e0 j: X# l( X* B6 S- d
2020-01-03 -1
4 p, J+ A S$ T) U' a X6 m2020-01-06 -1: B7 ~ T! f/ ?# r3 d6 U( A. u0 X
2020-01-07 -2
# a7 Q) P. {: j0 _Freq: B, dtype: int327 q5 F0 @" g: s( }: [
r = s.rolling('30D')# rolling可以指定freq或者offset对象$ k. U# s) Y O% d! B
( g5 i0 n2 Y, u. ^1 z. G% d0 Oplt.plot(s) # 蓝色线; M3 L5 e" A0 H" Q+ a
Out[108]: [<matplotlib.lines.Line2D at 0x2116d887eb0>]* w9 ]4 s! U* F: \* ]7 ]
plt.title('BOLL LINES')
. ~/ ?+ P Y8 |0 [' dOut[109]: Text(0.5, 1.0, 'BOLL LINES')# M/ g( u! i3 S1 \+ b. K* h$ }
0 V, Y3 U6 V8 ^
plt.plot(r.mean()) #橙色线0 {, Y/ A7 V& b9 t- X1 Y! H! L$ S
Out[110]: [<matplotlib.lines.Line2D at 0x2116d8eeb80>]
- m6 f7 ~/ F( H) R9 F) R% W/ P* b
; B+ n3 O, ?6 A: Y* G4 u8 Y! eplt.plot(r.mean()+r.std()*2) # 绿色线1 ?6 D. C, n3 C4 X1 m7 h" C
Out[111]: [<matplotlib.lines.Line2D at 0x2116d87efa0>]
; X& T: A, Z# S2 w9 r6 ?9 r0 B
- }. h7 u& A) p2 _. N- [plt.plot(r.mean()-r.std()*2) # 红色线
) n# ~( o1 ^2 `; c2 @* gOut[112]: [<matplotlib.lines.Line2D at 0x2116d90d2e0>]# }* o9 P! }- V5 Q3 T9 K
% n) ^6 K3 {) a, R1
% ~8 D7 i. v( U/ d4 b+ q9 F* ~" R2* o% J- r; l3 Z( V
3. A3 H6 s6 E% j3 g
4+ k0 t# d0 D/ F
5
! G% Q' `) A( m6
' z. J2 y2 m+ Y5 z* V, O/ i7
# v- b" ]" O% i* b8
4 ]* i" ]" e! m4 k- a- _8 d9 l; h5 n7 W' O# y5 v6 ~7 n0 a( p. _2 r
107 K$ A4 p. V! S) @* o* L
11
: B! j2 ]' r, C2 o' Q12
& o- @3 i1 t( |( W, N13
5 v3 U( f4 o# h14
0 h4 q" b7 ?8 p7 x6 a+ o8 C15
8 A( G) a, W6 |$ n @ s: l: {16
3 D3 \$ D* g6 T17
: V) y# ?1 F4 P& p0 |18* v O* c, y! @& }: Z4 m
19
# x/ ~3 e5 F0 k* h3 I% g6 c20/ u; g' ]2 a8 x# s( T( v$ }! S. I+ j
21
1 f- G- ]# D' h! v# Q22
* E( {; b7 a) ^; g8 Q8 i233 M4 N6 k/ I+ U0 l( A; y6 C* j
246 } i1 I( W; [
255 Q) |' n) m) V9 B
26! d* Q9 ~$ w i9 d+ {
274 P, a+ ]9 a* s) }
28
4 X% p* J; h$ O/ ?$ ?0 i29
G) I7 O$ W, o! W' J$ B9 k8 s1 \) W& c1 S( F+ j- M- d
这里需要注意的是,pandas没有实现非固定采样频率的时间序列滑窗,及此时无法通过传入freq字段来得到滑窗结果。例如统计近7个工作日的交易总额。此时可以通过传入多个函数的组合来实现此功能。3 [( V- Z3 M8 h; S! X
首先选出所有工作日,接着用普通滑窗进行7日滑窗加和,最后用reindex()恢复索引,对于双休日使用前一个工作日的结果进行填充。8 K8 H7 W; m: h t! z+ t2 x/ {' N
* ^9 b; o+ a% O( lselect_bday=s[~s.index.to_series().dt.dayofweek.isin([5,6])]
. P* {+ ^* X: M6 ^ ^$ Xbday_sum=select_bday.rolling(7,min_periods=1).sum()# M' q% T N) t m0 v& D
result=bday_sum.reindex().ffill()3 {# P9 T+ F+ M- Y4 G
result
3 U4 Q& X* `1 c- w8 J4 s$ R, W* ]/ Q% O" z# l
2020-01-01 -1.0
% D& o( f- V: {- F2020-01-02 -3.00 \2 R* G) \& C. b4 g& B* S$ x
2020-01-03 -4.07 k' _! a# \: L" J! L
2020-01-06 -5.0, H# C: f0 z* K
2020-01-07 -7.0
/ s, w5 `- q9 L5 g ... " K2 d1 w0 d$ S. \
2020-12-25 136.0
" Q% Y# n9 g6 [- P% b2020-12-28 133.0
+ O C6 N9 N: u Q) i2020-12-29 131.0
9 I) { f' F l2 x1 s/ B2020-12-30 130.0
2 n/ Q: b& V! ^8 A2020-12-31 128.0, H( |4 ^2 _: P% _: K
Freq: B, Length: 262, dtype: float64
! e! n" H( c1 M0 }' u
; G: q% u; U. Y4 s* M$ Q: G& l5 F3 @1
8 H) M3 e1 C S# ~0 E5 D; n7 ^2
7 q F, N& ?0 k; z+ Q3
6 z* b7 ~' [1 y: q. W t: z4
, _9 r Y; R* p+ C5/ N- Y/ s/ f( B4 _
6. d7 N$ A) ^9 X1 p8 } _) W; ?
7; M# \8 d9 S: n! J& T
8
+ u, S) B( w" S( }9* b6 e7 ^" H3 I* V. W2 r+ v
10 {1 ? |* u) u6 f) w
11! X" Z* N( R2 m; U2 l
12
5 ^2 w" z" q d: } u9 L5 }13. H, |4 w9 V. {
14* M1 Y, \/ Y1 f" R4 E# A' `
157 _9 Q( z0 K5 r' e5 X3 q
16+ P+ e# M2 |; V
17' m9 d0 r2 m% k
shift, diff, pct_change 是一组类滑窗函数,它们的公共参数为 periods=n ,默认为1,分别表示取向前第 n 个元素的值、与向前第 n 个元素做差(与 Numpy 中不同,后者表示 n 阶差分)、与向前第 n 个元素相比计算增长率。这里的 n 可以为负,表示反方向的类似操作。
8 S# ~- G6 ~# T7 Q" L/ U* n* S# ?1 X, f$ m: `* m9 l
对于shift函数而言,作用在datetime64为索引(不是value)的序列上时,可以指定freq单位进行滑动:
, q# I/ u k# X: S" \" D1 C* ?. \* g) ]6 m3 @' e1 u; w, @
s.shift(freq='50D').head()
+ h7 y) x* D- r; YOut[113]:
6 @. C4 r/ ^% Q; q% R9 R1 K2020-02-20 -1
7 f. j0 g& P% o; {( I$ g% L2020-02-21 -25 m2 p6 u- u; m/ w/ F
2020-02-22 -1
# b1 I( A( @7 F1 r% g* Z2020-02-25 -15 k$ z: a7 A6 J9 k
2020-02-26 -2
6 ~8 y) j; b9 Q( X+ j! Ydtype: int32
. ~- y$ U: {3 K( P4 P9 a; g1' e% F7 K; S$ \
2
% D, M0 x: }2 |+ g3% R- r6 `5 I( @
4
7 z8 }( k4 \: F1 ?3 f58 o) _% G9 \9 ~( U* E
6
9 Q* j- E l/ H [7 ]4 L7, j0 t' y7 J/ H& A; o) y. X, r1 j6 c
8. L5 O$ s3 {! r9 s# }
另外,datetime64[ns]的序列进行diff(前后做差)后就能够得到timedelta64[ns]的序列,这能够使用户方便地观察有序时间序列的间隔:/ q) h" g/ U) r2 G
# N' q/ d4 B3 @, m
my_series = pd.Series(s.index)
. `$ H: b0 h1 p8 E0 ^8 Amy_series.head(); J( B8 z/ @+ R, D7 V
Out[115]: ) C$ y5 `( B( ^* L9 c9 s+ D& t
0 2020-01-01
: b, l7 Z" I. p: v- O F1 2020-01-02
- F5 V& j2 H1 K4 |2 2020-01-03' H) x9 K4 T( j
3 2020-01-06, Q. g [ \* t# n5 O
4 2020-01-07" o) z7 \7 u7 \" Z ^4 s& |
dtype: datetime64[ns]6 O4 p, `5 o l S5 o9 G
! I b; \. Z. b6 I
my_series.diff(1).head()6 B; Z0 ~1 q# i8 W# D& S7 ?
Out[116]:
0 V2 ] Y+ \. ~5 [- B0 NaT, T! [" T9 r# O. `% U
1 1 days
1 J3 F1 V" @! { A0 M6 l2 1 days
5 Z5 u6 i5 c) {( }* l3 3 days. B# ]6 p! n) N# v4 O
4 1 days' P6 U. N! D* E8 N4 i; `
dtype: timedelta64[ns]
6 l1 V5 C, I0 p: \$ W1 [
+ @7 T4 E0 q9 H' @" M7 t$ V13 I$ w0 q5 j4 M( q2 Y. w
25 ~. u+ c( Y1 \: p5 G; ~
33 N! c# |) g! x% D. a
4) C$ M4 Z# b9 T# m( G
5
- y* u$ d, Z( {# n! G( L, K$ J63 e% ^* @( T& W& _
7
& |- [9 u' X1 u) n. R8 L) y8
2 w. m" z7 v4 T. B/ G, F9
: b6 L+ W: E) H G& p! [106 t8 e6 \; s+ Q$ Z! g1 l
11' m3 ?, Z1 l. I
12
- c3 Y1 \* [" I) m8 P13, I8 Z1 o# J" L. d# {9 h
147 t, w) S- L0 Y8 X: [3 ~
15
# L4 L0 z2 ~' N) }. f2 j160 S5 r9 |6 @: `! S4 F
17) \4 T3 J/ b# f* c0 t
18% ]! w, n6 s. z7 D
10.5.2 重采样9 O3 j8 X% j4 j
DataFrame.resample(rule, axis=0, closed=None, label=None, convention=‘start’, kind=None, loffset=None, base=None, on=None, level=None, origin=‘start_day’, offset=None)
$ o" q/ L, \# H常用参数有:
7 p3 ?) o$ v# v; H9 B1 e6 t, E6 X9 G, G! J+ B/ Y8 ^
rule:DateOffset, Timedelta or str类型。表示偏移量字符串或对象6 C% e- e9 I* z2 U# B
axis:{0 or ‘index’, 1 or ‘columns’}, default 0。使用哪个轴进行上采样或下采样
! f% G) O. V% Y. d: S( Xclosed:{‘right’, ‘left’},默认None。表示bin 区间的哪一侧是闭合的。所有offsets的默认值为“left”,但“M”、“A”、“Q”、“BM”、“BA”、“BQ”和“W”均默认为“right”。% H( Q$ `! [) ~: G
label:{‘right’, ‘left’}, 默认 None。hich bin edge label to label bucket with,所有offsets的默认值为“left”,但“M”、“A”、“Q”、“BM”、“BA”、“BQ”和“W”均默认为“right”。5 h$ t5 Y/ z8 r
convention{:‘start’, ‘end’, ‘s’, ‘e’}, default ‘start’。仅针对 PeriodIndex,控制是使用rule的开始还是结尾。9 N: Q8 B; q: H3 ?/ Q, n
on:字符串类型,可选。对于 DataFrame,使用列而不是索引进行重采样。列必须类似于日期时间。
3 }9 T8 D, k7 _) [level:str 或 int,可选表示多重索引MultiIndex的级别,这个级别的索引必须类似于日期时间。
8 n0 U3 J9 t# P) K2 y9 ?8 vorigin参数有5种取值:
8 `. @$ B$ M: ?9 X& T% ~# c) m0 V‘epoch’:从 1970-01-01开始算起% A7 T2 Y6 i+ g% k4 d' M, T2 H
‘start’:原点是时间序列的第一个值7 ?$ t# t. l% O e7 P) G% l
‘start_day’:默认值,表示原点是时间序列第一天的午夜。
; g) q" r- ^; Z# `'end':原点是时间序列的最后一个值(1.3.0版本才有)2 F! O, l/ j3 a# o' v! m) {! }- b
‘end_day’:原点是序列最后一天的午夜(1.3.0版本才有)
% O C# R- A# U. eoffset:Timedelta 或 str,默认为 None,表示对时间原点的偏移量,很有用。
# M, Y* b5 ~7 I7 b5 P! n closed和计算有关,label和显示有关,closed才有开闭。4 p) U. L4 ~) X7 b& a
label指这个区间值算出来了,索引放区间的左端点还是右端点,closed是指算的时候左端点或右端点是不是包含。
% t, J& _: C7 o) _
( x7 z3 a R: A. s6 @( L% w重采样对象resample和第四章中分组对象groupby的用法类似,resample是针对时间序列的分组计算而设计的分组对象。例如,对上面的序列计算每10天的均值:
5 s i; _" R3 X7 R, u7 Ys.resample('10D').mean().head()
; i. u0 ]; j# K9 V; ^6 DOut[117]:
6 L! k- y: p! t* b8 P2020-01-01 -2.000000% E1 q# [ ?" c- V3 g7 Q
2020-01-11 -3.166667
# s6 P! H, r- ?, N) Q3 H" _- h: \2020-01-21 -3.625000
' `1 c/ z* y1 n3 G3 y- A% l- y/ R/ u2020-01-31 -4.000000& D) c7 x; u$ X) }5 y! T# ?
2020-02-10 -0.3750001 O& C+ m3 v% {# z+ F" K
Freq: 10D, dtype: float64
% F! Q) m+ _$ ]1
+ b4 v& o5 G; S9 Q" K% E2
1 q) `8 T x6 M( Z# E! N! }4 `3- u* N8 ~ |# {2 j! J; {
4
3 z5 q$ c* H- [/ {5
9 M, ^) o. {: X, q M' M5 l3 E6
; u" Z' B$ e9 \0 ?+ F7' K/ `$ Y0 X$ F4 o/ u$ v: B& C; r( `
8
% f1 x8 ]# M* P1 S8 ?1 x8 S: i可以通过apply方法自定义处理函数:& b, a% G: ^! m
s.resample('10D').apply(lambda x:x.max()-x.min()).head() # 极差. Y- p4 t0 E. t. F% h( X
! U9 {& q: i+ E0 {6 y: i; J# qOut[118]:
5 m; K) X' \- f$ o' ~2020-01-01 3
( ?0 u3 a; r: {9 r& k g8 I2020-01-11 4; C9 y# v4 n2 U" I
2020-01-21 4
3 x& b+ {' A- }! P* C' \: M @2020-01-31 25 I& ]6 {5 u) e; G/ v+ f
2020-02-10 4
9 d. D2 D) D, ^0 h* S9 |. J4 DFreq: 10D, dtype: int32/ @" @) u0 g! g3 K3 |+ g" i5 D/ {
1* b! _2 p6 [" y3 |( v
2
% t# K6 ? G3 c" T8 w R3, ]4 H0 C# }" d0 K& t
4
8 E" ?8 F- B- c5 B55 B! D' @! L2 [3 `
6
$ g' ]! J. }2 s1 U; Q6 @7( ~6 a, I# v! F
8
0 U! t# c0 {% A1 J0 O: M90 K& {& b) o; I! z' w
在resample中要特别注意组边界值的处理情况,默认情况下起始值的计算方法是从最小值时间戳对应日期的午夜00:00:00开始增加freq,直到不超过该最小时间戳的最大时间戳,由此对应的时间戳为起始值,然后每次累加freq参数作为分割结点进行分组,区间情况为左闭右开。下面构造一个不均匀的例子:
! U2 L; z' ^3 B+ U: h$ Z/ B- h2 G' |. x1 J& M4 w8 y) M- c& |9 f! w
idx = pd.date_range('20200101 8:26:35', '20200101 9:31:58', freq='77s'); @- Y3 X2 b1 l6 A
data = np.random.randint(-1,2,len(idx)).cumsum()( G/ U' Q$ B9 Y) K
s = pd.Series(data,index=idx)2 C1 b u/ |* X
s.head()
, m U( y! |" ^" N N
$ d, g j/ k: v: S; O+ V) oOut[122]: 1 f5 E, N1 j3 p5 s. t$ x& O2 b
2020-01-01 08:26:35 -1
4 @+ i( A6 {: @# V4 y2020-01-01 08:27:52 -1
' \ k* n' V4 J- F$ w2020-01-01 08:29:09 -2 I( i( `+ C4 M1 `( e
2020-01-01 08:30:26 -3
5 L& t! Z: q9 K5 O2020-01-01 08:31:43 -4
# u9 c P6 \1 l" U4 ~ g7 OFreq: 77S, dtype: int326 L9 ]4 n/ g% \2 @$ r, c# N% \6 R
1/ [3 k+ j/ z/ k9 n# \3 W
2# s7 Y' f% e; f
3
" V3 A, p I) [7 L! |! W4% E7 B8 p9 a4 _+ f- ~# l
5* j. t& n9 B F& I
66 ^9 B$ r) g! K/ w: ], E. M
7
) g8 V2 m4 V9 w: D! Y9 X8+ Q7 U/ u6 P8 H8 P
9
( O5 ]) a5 u" _( B6 E+ ?& v10+ ]' Y$ L+ q( | b, k
11+ I2 Y- q& T% G& ?" P
12( w$ z+ p! j. `9 i' \: p5 ^! h
下面对应的第一个组起始值为08:24:00,其是从当天0点增加72个freq=7 min得到的,如果再增加一个freq则超出了序列的最小时间戳08:26:35:1 y* u H4 s& H+ o8 C. w3 P
9 c( ]3 J9 ^0 C6 y( {( L, ?5 A
s.resample('7min').mean().head()) ^7 B& }4 |" |$ V: c5 M+ @
Out[123]:
! t) y+ {6 z+ A* n) {1 L( S2020-01-01 08:24:00 -1.750000 # 起始值,终点值包含最后一个值4 ~* p% M, [/ O
2020-01-01 08:31:00 -2.600000 o6 E; @% y5 N) u
2020-01-01 08:38:00 -2.1666672 p# S: g4 [3 c1 s' B
2020-01-01 08:45:00 0.200000
9 ]8 y. k, d% h0 N% k: b# l2020-01-01 08:52:00 2.833333
; ~. |* `$ P' Q8 SFreq: 7T, dtype: float649 l' F A8 t- t; _
1
( W, J1 U9 N3 J* e; L2
6 B% C7 m/ O0 v' l3
2 j7 t) B# n/ E" t4
, `+ I2 j$ m) e' R' _5
2 j$ v0 m+ O6 q U) }- {; o$ z1 x6/ ~' M! Q; W3 k/ {
73 s( [/ ^6 H% N. y- s
8) i9 }* `. Q, x5 }7 o" B& w j
有时候,用户希望从序列的最小时间戳开始依次增加freq进行分组,此时可以指定origin参数为start:
% Z# W0 @+ w6 T( c6 F
* u& | T' x, E- M3 bs.resample('7min', origin='start').mean().head()9 t% q+ `% Y9 H4 u3 O0 g
Out[124]: 8 s3 K& D+ G: l9 s+ R5 E4 \
2020-01-01 08:26:35 -2.333333
' Q, I6 @6 w# U a5 A2020-01-01 08:33:35 -2.400000
5 x3 Y! j, e0 }% J6 _" `( G2020-01-01 08:40:35 -1.3333336 `- ?& [$ j/ _1 s+ ~& p0 U
2020-01-01 08:47:35 1.200000' N4 [/ [# S& k6 l- G! \+ h
2020-01-01 08:54:35 3.166667
2 C) @: f1 O7 F9 `4 B) x/ ~Freq: 7T, dtype: float64. v: u$ p. |$ v* `8 _; y8 |
14 W; Z1 |% R) B& [% a4 y
2
6 G( V( Y6 k, G2 F5 [3. m0 K+ t5 R& f: I/ p f; @
45 k1 s" T5 P5 v
53 V) M: B* F# F; w' ?
6+ r# U: j( x0 E( ?9 V
7: g& G2 e6 Z4 x2 Y. C! J! j& h
8
h" R6 |' ~# g N 在返回值中,要注意索引一般是取组的第一个时间戳,但M, A, Q, BM, BA, BQ, W这七个是取对应区间的最后一个时间戳。如果想要得到正常索引,用’MS’就行。
1 @; S+ g! e5 Y3 t, w4 {- z9 F' g4 i5 P3 ?, E) e! N2 p
s = pd.Series(np.random.randint(2,size=366),+ F0 j. h6 A9 r+ b8 E. D6 u
index=pd.date_range('2020-01-01',
& j' h) ]" M6 {; Y! r8 o. V7 p '2020-12-31'))
! P6 ]1 Z' Z) j2 I
+ U' S: o$ H0 k% s' @! Z0 D Q6 v) u( v `; K
s.resample('M').mean().head()
+ r0 D2 p% v7 G2 f4 f$ g- }1 }Out[126]: ; G4 _( h* W" F, v" h
2020-01-31 0.451613
# X; z; i7 r( d5 P2020-02-29 0.448276
7 a- w$ K: C; v' w2020-03-31 0.516129& [4 y1 h8 S9 e7 L( |
2020-04-30 0.566667
9 ?4 a. ~. j# S7 B) @7 v2020-05-31 0.451613
) T c/ h/ j8 x3 tFreq: M, dtype: float64" \8 s q4 u& A# B! A
4 b& E6 k, H6 L r* P( ]- Ts.resample('MS').mean().head() # 结果一样,但索引是跟正常一样# [6 R R0 b8 h9 i _
Out[127]:
/ N& {" p% R4 s" z2020-01-01 0.451613
! A3 l5 Q! t( I; z5 X# E: b" f2020-02-01 0.4482768 j% o$ s a5 `* h
2020-03-01 0.516129$ v+ P' \; B ^+ V' @
2020-04-01 0.5666679 @" z6 Q. T9 M0 R, q) n3 c
2020-05-01 0.451613
8 p a1 g" \8 l [% IFreq: MS, dtype: float64
- s/ V @; ~) \2 `
, ^, \5 x! o6 b1 j4 p: v1
3 p8 V7 b5 x, o L0 V3 b2
4 O9 F' l. C- }" s, l3
& }& h$ d1 [# x% j. q44 H# J" y5 H) w6 a. m/ ^
5* _: C% v. J+ R/ w" C
6
1 F6 f: b! P) L7
8 Z( S( O' e: @2 _0 [83 ?/ }& z- ?- c
9
; K' l1 o$ q' V' B8 f10
/ z5 L" t3 ~' R/ h0 o3 t6 Q. ?# v11" j# `4 p. {) I1 x$ Y0 Z
124 C" |! z5 S6 g2 k+ g
13
8 h! u$ m5 M3 K) z9 I4 f. P9 O5 {14
/ ^5 V0 ^0 R5 s( y7 y* O15* W# J2 v2 Z. S4 e! G
161 Z( x5 H, z3 f3 O$ X+ z7 p' V
17
6 r: E9 J& x& u9 M. l3 l" B" y. y18# ~8 a: d! a1 }! n% M. @- r4 G
19
. |: _, m& d/ [, H20
8 v' J6 u2 Q# w5 R21
U6 O# \8 \9 n220 |& W2 A, u# \: D
对于 DataFrame 对象,关键字 on 可用于指定列而不是索引以进行重采样:9 b6 y/ Z' _2 q1 ]! S
d = {'price': [10, 11, 9, 13, 14, 18, 17, 19],
; h7 o/ i1 S% Z+ w 'volume': [50, 60, 40, 100, 50, 100, 40, 50]}7 U& l& Y; b" X' @5 Q1 I3 l, n
df = pd.DataFrame(d)
5 n2 t& v. l7 ]8 l. sdf['week_starting'] = pd.date_range('01/01/2018',
k$ N! M* ^9 ]; o4 G periods=8," O* l: y+ G% R
freq='W')0 a" ^# Y0 j+ o$ N+ R
df
/ u. ?& J' u( {8 m, Z9 {. B price volume week_starting
% U4 C9 S7 }- u2 V. `0 10 50 2018-01-07/ P+ I2 P( v" N5 M5 W
1 11 60 2018-01-14
' U" m9 k* T0 \; P2 9 40 2018-01-21* q: a" M- k. a3 o& O0 p" H- V
3 13 100 2018-01-28
; O/ B8 A5 \) b4 14 50 2018-02-04' Q% `* C0 I7 k Y! s l
5 18 100 2018-02-11" a* }- V' j* d
6 17 40 2018-02-18
4 u/ V( I3 g( X! x+ f7 19 50 2018-02-25& s! w d! H/ p( @- L! K
df.resample('M', on='week_starting').mean()( o6 i- {8 {$ G+ e: ^8 Q: l
price volume
. _! ^5 H/ k& ~+ } Sweek_starting( S2 k5 i! w6 x
2018-01-31 10.75 62.5
: Q- h' }: W# l0 a* m7 H2018-02-28 17.00 60.0( R. C9 L* k7 N' ?1 B8 l& ~
) e& B# g; W4 L3 K15 o u, @+ H3 O) u/ o
2/ ^$ Q9 J3 H ?1 W; m
3. q% z) A V; d" }$ B
4
. w7 G2 u! @0 G0 I! U9 y9 J3 g5
& x6 l; @8 _2 J4 ]% b6
( s. o+ C& v. m7
/ q Q0 i; ~7 s8
& D8 v3 v4 Y1 n5 f7 s% f) _' u6 R9
% x+ H, p0 @4 y7 V1 n# F10
. y- k: d4 E* k$ a1 `11" |$ Q! H8 K6 \1 H* e! v
12# F$ `; h! O' p+ j k. J- Y+ ^
13
9 Y% y7 H# T! R3 e! f9 u5 J14
9 I6 _3 A4 ?8 h$ \15& ^, r3 \, |0 |% {2 G: d
16
1 @$ Q' K" W- D, k# p& K17& v! A2 F+ _$ ~( S9 [& [( E4 W. o
18: a+ g6 i- o. Z6 w9 R7 }7 _
19
7 Y8 ?' q/ j' e* i3 N- V20
: J$ c, Q2 @" B$ V8 J- `. Q21
" ~+ |/ _7 ?# `) l& w- `对于具有 MultiIndex 的 DataFrame,关键字 level 可用于指定需要在哪个级别进行重采样。# t; W' ]; v* @( H& j$ C3 S1 f
days = pd.date_range('1/1/2000', periods=4, freq='D')
, k+ l4 W Z, H8 {, nd2 = {'price': [10, 11, 9, 13, 14, 18, 17, 19],
) q, W w# w1 @0 c 'volume': [50, 60, 40, 100, 50, 100, 40, 50]}
- @/ Q; @- x& D) z/ K. pdf2 = pd.DataFrame(
4 t8 s% A$ h. Y d2,
+ a5 s0 W9 {( q index=pd.MultiIndex.from_product(% e- ?+ L" C: Y7 y- z' x' P' F0 W1 F
[days, ['morning', 'afternoon']] E( Q! g. g: v- J3 Q' c) R
)
# \1 T' k0 Z1 k& V5 R/ p/ S% K+ K)
" h5 Z2 _. D5 [$ p; a) W$ t+ udf2
5 U4 n) ], ~1 n price volume; d1 H4 \) N7 `# p, C: D1 {! M
2000-01-01 morning 10 50. Z7 l7 J, O' @: W7 W$ C& e: G
afternoon 11 60$ _1 _: ^6 F8 ~+ V( f7 u8 x: T7 a: c
2000-01-02 morning 9 40# _4 q6 a9 R9 p! y6 j2 V
afternoon 13 1007 t/ m! b$ B% C/ u1 o6 P2 C o
2000-01-03 morning 14 50) Z- E1 O/ U& V4 Q/ ?" x7 P* ]
afternoon 18 100
5 v5 d$ X% {' F; X/ V/ i! A2000-01-04 morning 17 40
B$ y# O4 d+ ~ afternoon 19 50; B" ~$ Q+ t4 f% E7 M
df2.resample('D', level=0).sum()/ H! p8 j& J! }" I% z2 d
price volume: @6 y. X1 N6 @. H
2000-01-01 21 110
5 A; x& T$ q. X) W$ Z7 b2000-01-02 22 140; c5 K& U" r) y K* B
2000-01-03 32 1501 P, e: |0 g3 e: U8 a9 C Z
2000-01-04 36 906 t; i5 ?3 I: ^
+ b3 g- }+ \ w1 u) q8 B: [
1( Z* k+ K; }* p7 Q) j3 V4 v; S3 d$ ]
26 o4 I, q5 q( }' o* ^
36 [) H2 N3 k U# Q2 J: X
4, d) B3 H9 }0 x7 A
5' d X3 u+ ?4 a) c
6 q5 f4 v9 }; Y$ J
7
7 o# [8 V" I3 A/ F1 k( y80 u" w) r; L! u) M( H p
94 Z0 G) X% Q0 w
10. s; Y/ H: l2 W$ t0 G
11# |4 g |. g' I7 C( u
12
& r1 Q9 v {4 g133 H7 m' P3 D: Z4 Q4 Y. H. g
14
; {0 K- G) A3 V+ p15
9 I; r- G3 s' b( p( q16: _; _$ j) [: d9 j( O: s
17
( D2 c d P2 ~7 a$ f D0 Y5 T18
) C" u# J8 m3 }5 k; `19
; `) @% _& E2 ^6 j% t20* o" w, K* N# u, |
21: M- K0 ]* N5 t- H. Y" c+ V8 ~
22% s8 K. m+ P% h0 P5 Y( e% ]
235 _' w: K6 E/ D5 o- k
24 }0 V. h1 K. b$ \& }# K. t$ I
252 Q. B: `0 t. o' U) m7 O" ? M) [
根据固定时间戳调整 bin 的开始:% @6 X* j6 b5 l* e6 V& {6 T
start, end = '2000-10-01 23:30:00', '2000-10-02 00:30:00'! p! |7 {5 \6 b) l, e- l) _
rng = pd.date_range(start, end, freq='7min')
3 ?6 q, W. @) v' K5 o! T+ Nts = pd.Series(np.arange(len(rng)) * 3, index=rng)3 `7 z! ^7 Y7 {# A
ts: K, Z# K6 O5 r# p3 y$ S" {
2000-10-01 23:30:00 02 m% C2 P% I1 k6 g: \
2000-10-01 23:37:00 3
- H, `8 U! }8 o/ i! U3 v! t2000-10-01 23:44:00 6, q2 o: K5 F& r# E! K5 l! l1 `, T
2000-10-01 23:51:00 96 v, A; M R7 l; J0 d! n
2000-10-01 23:58:00 12+ s* X: Z/ W0 K) r) @5 _, E
2000-10-02 00:05:00 15
! c) [& N! B& P, Y2000-10-02 00:12:00 18
, N: h# M# G1 M8 P2000-10-02 00:19:00 215 |( ?6 C& O" v# [! w
2000-10-02 00:26:00 24
* d8 A, ]; X2 ^+ l& E9 @Freq: 7T, dtype: int64
# G6 _1 s0 M# E( V
5 N# Q3 T {1 V: D; xts.resample('17min').sum()! f" _: k" K. L7 b$ h2 a1 ^' h g
2000-10-01 23:14:00 0
( A# ~6 `5 d" h d3 U& j2000-10-01 23:31:00 92 M! L5 k5 W2 D" q: F" b R5 U0 O
2000-10-01 23:48:00 21, i7 w6 x+ s x# B7 e/ w
2000-10-02 00:05:00 54+ N. A, _& U' V' p7 l v5 [! o
2000-10-02 00:22:00 24
# C$ c, K1 S" `3 _8 r9 Q7 T0 RFreq: 17T, dtype: int641 |" A' z+ `) F
- \& b3 J2 [' kts.resample('17min', origin='epoch').sum()
) W! S: w( o! e' R6 g1 c8 r2000-10-01 23:18:00 0
% |* z1 _0 ~; F* e0 R: ?2000-10-01 23:35:00 18
9 l, P5 z" w6 \" [/ r I2000-10-01 23:52:00 27
+ f3 P3 C$ W0 h$ ^! s2000-10-02 00:09:00 392 O6 g5 b" i" X' Q; A& i+ Y0 m
2000-10-02 00:26:00 24
7 P9 S5 q9 f4 m4 L$ ?0 C# ~Freq: 17T, dtype: int64
/ [3 D5 c$ g, z# m# Q; F: P3 S+ M% r& O2 b& O9 j
ts.resample('17min', origin='2000-01-01').sum()
: g! s8 G0 v4 a" Z6 D3 G2000-10-01 23:24:00 32 v# e. r8 H( R
2000-10-01 23:41:00 15+ |$ h) i- s4 [
2000-10-01 23:58:00 45
: y/ {% V# H; q. [9 m9 z2000-10-02 00:15:00 45
1 k9 z; t* X/ Y# vFreq: 17T, dtype: int64) j! l! ^8 a, L. u |
2 z R7 {# P# O6 `. }5 }' M1
3 n3 J9 e* y1 T% Y21 t0 g) z) x( t
35 n8 ]# k* {3 g) q- F1 d
4) w5 [) n8 t+ E3 {# s, i7 l
5& g1 w; W& U' X! E- p! v# s
6
+ s0 q; F3 H9 J1 B/ s; O7
4 _3 X2 b" i: Y+ ~) u8- |" u* \/ Y% J) ]
9: j( N! d5 @; w
10
- p L2 h1 d# s0 Q8 n/ a11
' i/ J& V: ?+ s' ]125 t% Z0 F/ A6 Z+ p; a
13
7 n4 p) _0 ?) k Z2 _14' T; C! e5 J# a% Y
15
* k- b, j. @2 ?6 J# B' p161 a0 ~1 \ d- [$ w
171 T! ]8 d7 ^. C' ~+ b2 A+ p/ [ ~
18& M' R- z* N% t' K( r) V& ]
19( l Z/ v& s2 i* a, e; n7 q8 b
20
3 A. O8 m6 o7 q4 [: P21
) L7 Y& D/ r8 q. c22
% y1 X3 }9 _; h, s2 ~7 s23
/ {5 F1 w B* r* j$ a8 E24
@, s& {8 |8 t, T& G0 _25
3 K/ j" W- Q& T26
5 O# R6 S, h" A! A/ B a27
* C7 B" Q7 p% {, d: t4 B2 p6 ^28
2 U8 f- e, X3 d29 J, |$ k3 o i3 o# q! M
30% Y+ \8 p) g; }$ R6 M% G
315 N4 Y* k' W p) u) U5 l0 s( S
325 B* H7 G& s0 H0 h
33) `- Y2 h* E5 {! S, ]
34% N; D" j" u2 n( z' E9 t; t
35" W" p( ^; w- E/ h3 l
366 Z5 n( ?2 c3 w/ p+ _1 r
37
- e" s3 b- H3 O( u! N( i0 P4 T" ~如果要使用偏移 Timedelta 调整 bin 的开始,则以下两行是等效的:
2 I" y5 j! N% o8 s' _% Fts.resample('17min', origin='start').sum()" V: @* p4 K7 z1 a7 {$ k& y
ts.resample('17min', offset='23h30min').sum()
" o* y: [1 n+ h! L% y& E) U2000-10-01 23:30:00 9; @& ?3 Q% h* n/ z/ u5 d
2000-10-01 23:47:00 21% `% q$ k# c# m4 y; A; I
2000-10-02 00:04:00 54
6 U9 W# t- d$ M( B& b n2000-10-02 00:21:00 248 l0 f) P& I7 @: |9 ?9 ]# d7 ~
Freq: 17T, dtype: int64
7 ?# b- G4 n" k2 X' g6 ]1
/ @( T- d! V+ N2
7 f/ s* f+ k* e3
9 l0 j0 i. ~- k# ]7 D2 b4
! E6 W' e1 W2 K$ J6 M1 Y' ~5
8 @# f: T2 U, p! ]1 B% D a1 @! q, ]) ]6. ^0 u' C6 ?7 A! F. k
7( I6 l* b, _! Z! r2 y6 {
10.6 练习
' }$ {6 O# E) {7 N* w" R, Y" [Ex1:太阳辐射数据集
( a% l; ]5 |$ D) ?/ \& q现有一份关于太阳辐射的数据集:, ^4 y& F3 m' z' K, u9 M% P" [3 h- e
3 F' v$ r% b5 I3 l2 m5 U7 A& O
df = pd.read_csv('../data/solar.csv', usecols=['Data','Time','Radiation','Temperature'])
0 N3 O$ T, w' f; Fdf.head(3)
/ H$ a8 K2 Z6 I( `
0 r/ M" d0 S, K+ }! k! [0 WOut[129]: # g0 D& |# S5 ~& ~( l2 ~8 {
Data Time Radiation Temperature
: ~* K7 \/ I" i4 @' H1 {/ a( o% t9 Y0 9/29/2016 12:00:00 AM 23:55:26 1.21 48
9 @9 O# s( V4 z1 9/29/2016 12:00:00 AM 23:50:23 1.21 48; C& V) D# _0 L8 A5 }8 S
2 9/29/2016 12:00:00 AM 23:45:26 1.23 483 m; t/ w: ^+ n/ V
1: q/ \/ V1 k0 P
2- S# e) m! R6 z: E
3
( \; d) n, D; D' p4+ l- u( E5 | q
5
+ z0 S P: S% I( U! ~6
" F* }+ Z; K: [; W; {+ `- j4 {7
: v4 ~4 P# y7 J* d84 e- Q& j$ U M% V/ x# S6 P
将Datetime, Time合并为一个时间列Datetime,同时把它作为索引后排序。: U# B5 _1 r2 p) ]
每条记录时间的间隔显然并不一致,请解决如下问题:+ z2 o' }2 }- _- s+ J; D6 P
找出间隔时间的前三个最大值所对应的三组时间戳。2 m5 V; {+ d* u% k/ m
是否存在一个大致的范围,使得绝大多数的间隔时间都落在这个区间中?如果存在,请对此范围内的样本间隔秒数画出柱状图,设置bins=50。0 y% S* d( L8 W, `0 P0 e
求如下指标对应的Series:
. a1 `$ @" Y3 w3 f1 N温度与辐射量的6小时滑动相关系数3 |/ U0 |; m% I( u- w
以三点、九点、十五点、二十一点为分割,该观测所在时间区间的温度均值序列5 ~. N9 _) K4 Z' N. G2 Z
每个观测6小时前的辐射量(一般而言不会恰好取到,此时取最近时间戳对应的辐射量)
/ r O0 j7 H. b4 \5 dimport numpy as np
- p. V2 g7 h2 H9 I" {import pandas as pd: l7 s# C5 |. z6 u% z
1
, x* k3 U, F# Z- w) h% \6 \7 f/ W2
0 I; B2 n j/ S1 Z将Datetime, Time合并为一个时间列Datetime,同时把它作为索引后排序。/ z3 r O% y7 I/ s
data=pd.to_datetime(df.Data) # 本身是object对象,要先转为时间序列
5 M' w; }! u+ ]6 Y! t1 Itimes=pd.to_timedelta(df.Time)* Z# ]: N- Y- X* H8 X5 F- E' J
df.Data=data+times: }; t* g& @. O6 \9 l4 i9 h- y
del df['Time']
, S7 N3 W# B, R6 H; T7 ]% j0 L% u: k) H+ Ldf=df.set_index('Data').sort_index() # 如果写的是set_index(df.Data),那么Data作为索引之外,这个列还另外保留
: H; p/ b& P" L0 ?9 {! wdf3 ~* \' c l9 i d) G
Radiation Temperature
$ `* ?! O( W) G1 [Data , r7 e, i8 }/ E) j6 |' W
2016-09-01 00:00:08 2.58 51& Z z, {, Y I+ W; C2 C3 {! I
2016-09-01 00:05:10 2.83 51
) i2 K/ t% h, I. d9 o* H8 [2016-09-01 00:20:06 2.16 51& ~) A9 C# ]- V- ]7 O ]& A
2016-09-01 00:25:05 2.21 51
$ l) B5 m& @6 s& J2016-09-01 00:30:09 2.25 51& k+ f6 r$ h! M7 D
... ... ...
* q! S7 x8 M5 H" W$ }2016-12-31 23:35:02 1.22 41+ O$ ^; n0 o8 t' Z+ `4 U! d
2016-12-31 23:40:01 1.21 41: r2 u s7 D a5 }" ?
2016-12-31 23:45:04 1.21 428 l3 d" d+ K8 r. P
2016-12-31 23:50:03 1.19 41
7 v Y/ X6 {/ ]1 |' R& j9 g2016-12-31 23:55:01 1.21 41
( T) ^4 m3 F5 E, n$ o+ ]0 e4 N
4 m2 Q* |, w0 F& u12 [$ `; r2 u7 p: N9 P1 }
2
& x* ^" X3 _4 `& H' Z: p0 K g% V3) v- p2 j) V' K
43 z3 R. t/ r. x+ F j
5
* B; V3 I+ J, c9 M4 a8 U6& R& ?" T( \ {6 A
76 x+ D' l7 p! P. x4 `
8! r& L. X. y, X% a$ j! A2 b" _
93 B' I- G) k+ v6 D
10
1 x3 H- R2 X F; A11
1 q$ a; Q1 c6 t$ ?, i8 N12
# M1 c4 }# k8 X0 D( @0 ^13/ c& `- |* r J5 K
14! y! I6 ^$ @. G) ?/ ^ l
15: O* j/ z3 q+ D/ _" p8 n
16* B$ _/ N0 m& {4 d: f, C
17
# x5 a+ g+ ^0 T; J6 |" |$ ?, S) i18
, `0 ]" m9 G8 X& d0 L5 b+ F19
4 e. n9 {8 X# Z3 h: l2 S( P+ { Z每条记录时间的间隔显然并不一致,请解决如下问题:
' ~4 x1 @5 J9 Y; d f" d找出间隔时间的前三个最大值所对应的三组时间戳。9 H% m- g( a1 A) w
# 第一次做错了,不是找三组时间戳" M7 N4 c( t* `- N( C! t
idxmax3=pd.Series(df.index).diff(1).sort_values(ascending=False).index[:3]+ Y1 G. C" g& i1 B6 k. G2 Y9 q
df.reset_index().Data[idxmax3,idxmax3-1]
# G4 H- J: A4 ^" w* H& C& d2 |4 j# c0 y3 S3 f: ]$ s5 V
25923 2016-12-08 11:10:422 P6 i0 Z9 y; B9 j# F* f( l2 _
24522 2016-12-01 00:00:02
' A W8 N6 b, i: z7417 2016-10-01 00:00:19# W2 @ K* X1 t
Name: Data, dtype: datetime64[ns]2 H8 N5 k& P+ k& y
17 e( g4 v4 i" S6 U+ {5 ?4 _: \
2
! h+ @- {; D8 N$ q: L' B* M! r, T3 D3
4 s- q, F4 B9 d o4
% O D% g, K6 m, `52 w: ]! h1 a! ?& b9 ~9 C8 T9 P( B
6
- i5 H' }. C5 ?0 i; C3 d7: u2 @& n; a* ?3 o6 ]( S& C
8& r7 W ~2 X% H+ M: u" \9 O
idxmax3=pd.Series(df.index).diff(1).sort_values(ascending=False).index[:3]
. k8 ]# S; U3 k* `% J `list(zip(df.reset_index().Data[idxmax3],df.reset_index().Data[idxmax3-1]))1 e* K+ \: M2 x8 l
2 F$ n5 C/ i& n" ]) Q' A[(Timestamp('2016-12-08 11:10:42'), Timestamp('2016-12-05 20:45:53')),1 c/ l% P" [! u, f! Q2 d
(Timestamp('2016-12-01 00:00:02'), Timestamp('2016-11-29 19:05:02')),
/ c& g" |( w3 I1 R8 q- j (Timestamp('2016-10-01 00:00:19'), Timestamp('2016-09-29 23:55:26'))]
0 c/ u& X; y1 B. u1
1 e- h2 D! e \3 d8 u* |& W2" B* j/ y7 c" O3 O3 o5 `
3; s" ~! D( F: E
4, \) ~. N" b/ m+ ^
5& J$ S6 x" X6 `$ P) ~4 C6 N+ r
6" G. T8 S. O; ~- z
参考答案:, k1 s1 U# Q8 K8 J; J6 J; j
' C% G0 u7 S- V- K9 ?" \s = df.index.to_series().reset_index(drop=True).diff().dt.total_seconds()
0 _. R- w1 i6 R* l) ~max_3 = s.nlargest(3).index* ^, z" d- y8 A% [ l
df.index[max_3.union(max_3-1)]( J! E2 e4 x6 d& n" n9 A0 u
- Y* U; f% q! K3 V7 G( l; uOut[215]:
& b8 v; W, r, O( a8 v/ MDatetimeIndex(['2016-09-29 23:55:26', '2016-10-01 00:00:19',2 @! a! e) C) H+ ^6 x* R) ?- \
'2016-11-29 19:05:02', '2016-12-01 00:00:02',
% @. K7 f1 X A* v '2016-12-05 20:45:53', '2016-12-08 11:10:42'],
8 l( n( v; n1 } dtype='datetime64[ns]', name='Datetime', freq=None)% _( B2 x8 F2 q: U
15 I/ G5 r& W( R& q
2$ O0 ]2 m* _/ U% s" y
3
3 O% E; z6 D( Y8 _4 J4
7 z1 V* X3 E c) r- q0 d5
9 L' Y0 L% s7 ^3 c: k6: T2 Q% d% ^6 y/ X/ q1 J# b* y
7
7 C# n2 ]' [. H: f; W8
7 J6 F9 t! U) H9
# [) p' L6 ?) I6 P* c+ Z/ n! V是否存在一个大致的范围,使得绝大多数的间隔时间都落在这个区间中?如果存在,请对此范围内的样本间隔秒数画出柱状图,设置bins=50。- ^3 s; f) v9 }/ ^0 ?& A/ |& K
# 将df的indexydiff做差,转为秒数后排序。再求几个分位数确定取值区间1 i, M1 g; z; t7 s
s=pd.Series(df.index).diff(1).dt.total_seconds().sort_values(ascending=False)
- T5 u& F5 J. p& j$ T/ Ws.quantile(0.9),s.quantile(0.95),s.quantile(0.99),s.quantile(0.01),s.quantile(0.03),s.quantile(0.05), \7 G1 E7 X& N4 ? A
: M5 N9 x8 h; g) G7 e2 W
(304.0, 309.0, 337.15999999999985, 285.0, 290.0, 292.0)
5 h% G& n9 y* p$ ~ X7 q' R5 T17 F" w0 E, v$ q8 O; v/ c
27 X X% _/ w4 J5 ?; l7 C
3 l: G/ `+ n0 _/ p
4% ]* X) P8 W: q) |4 S# U& l& [
56 t. `" J& W: [
%pylab inline$ N. U7 u* L5 Q W, M% S- a3 ]
_ = plt.hist(ss[(s.values<337)&(s.values>285)],bins=50)9 Q, q d& _" u& F8 p, ] }" g
plt.xlabel(' Timedelta')
; @4 ~4 E1 Z9 E/ w, w( u% J# [- I& rplt.title(" Timedelta of solar")# E ^7 v: ], G5 R4 P, Z
1/ w5 o. U; _; b \2 d; G0 n* r
22 h+ n7 J0 d( I7 y
3
# n7 K& N+ P$ K3 L& g4( P& J+ N1 @# W/ e: ~
0 t$ y2 T% T* {- B
+ v) R7 @* B' g. D: E
求如下指标对应的Series:( N9 N- \1 k, X2 f+ ^4 d: |% ~
温度与辐射量的6小时滑动相关系数! i+ c0 v# V, x \7 J# P$ L
以三点、九点、十五点、二十一点为分割,该观测所在时间区间的温度均值序列
; w$ r& b! N* v& m: \, l+ X每个观测6小时前的辐射量(一般而言不会恰好取到,此时取最近时间戳对应的辐射量); s3 U9 g( \) C" ^
df.Radiation.rolling('6H').corr(df.Temperature).tail()
/ i6 i: b5 R& D
9 d% _$ c! C5 \5 x7 `! b! f: GData7 @- w' L7 v" m, A, y
2016-12-31 23:35:02 0.416187
T) a4 l- j% _1 U2016-12-31 23:40:01 0.4165656 s; F9 |& o% z# o
2016-12-31 23:45:04 0.3285742 J' {. H, e8 i P, {
2016-12-31 23:50:03 0.261883
" F5 b& C( i8 v4 H; r2016-12-31 23:55:01 0.262406 ?+ `+ r" y" t" B. j; z& `/ ?6 [+ _
dtype: float64
, B- i( G* Q1 k: |1& X" J/ D" G3 d3 j+ {, D
2
& Z, h: i/ K$ e; o3' {" w( p7 ~/ m" [8 V9 Q/ _2 v# o
4
& y+ J$ b; O" i1 _/ Y' Q5
% n/ D3 O; ]4 S$ S8 _# H6& o* A: R" E4 B1 @
7( N5 p* H- u* T( E6 K G1 v
8& j9 @: i; N p
9
5 `0 [# n0 N5 M+ T0 I$ D; d, mdf['Temperature'].resample('6H',offset='3H').mean().head()
, {8 q9 a7 D, u, p% C, i; k/ f9 C; j2 Y5 w- R
Data, P/ k/ O+ ^8 q7 Q5 Y' ]5 v0 a
2016-08-31 21:00:00 51.218750* k4 ]2 {; o% U! L/ ]* Q
2016-09-01 03:00:00 50.033333
1 v- Y, F1 F0 N$ n( U* e2016-09-01 09:00:00 59.379310
$ _# ~# s+ Z, g. y) S5 H$ E x2016-09-01 15:00:00 57.9843751 {/ a8 v/ f! H: x
2016-09-01 21:00:00 51.393939; ^: q- t* K- l" O4 u
Freq: 6H, Name: Temperature, dtype: float643 f5 \; B& I/ M2 e0 F# k
13 s0 i4 \0 j# f( c) ^
2
* c( u% Y8 ]" u5 \! [) P$ d. b3
; `( f: }3 K9 G9 I4 B4
% D& K* k9 ?9 x3 i4 {% O5# {: b! a& Y/ z# E/ E4 |
6' ^. w5 e5 W" v
7
' {4 a8 q% E8 L% J8( i3 S9 b _6 B$ ]7 R0 x
9
3 a6 k5 x) k1 J2 F/ a* w9 k3 n- |最后一题参考答案:
$ n+ c* b2 t' N+ u' H# o
) g/ A$ q6 T# }! u+ I# 非常慢9 l* g( ]9 \) G5 { l# e% h9 ~! h
my_dt = df.index.shift(freq='-6H')
( r- D" h, \4 Q+ _int_loc = [df.index.get_indexer([i], method='nearest') for i in my_dt]/ C1 h/ p7 {/ n1 l
int_loc = np.array(int_loc).reshape(-1)- B/ e! Y3 d+ i0 b* w
res = df.Radiation.iloc[int_loc]$ s; w2 \ u. s: u
res.index = df.index
; c0 a3 d4 L N' S+ }res.tail(3)/ H1 p. F9 S. s8 V, `4 o
1
% k) I4 |) D* y8 j24 ?4 W. Z4 D# g1 K, N
3
5 V2 P+ d/ ]$ c8 q+ v4" c& O7 N, Q& I$ d4 d
5( _( U/ h4 j$ {1 C' ~: n1 B
6: k/ @+ y4 v( {! ?
7
! o4 B& J# g1 H+ U* T# 纸质版上介绍了merge_asof,性能差距可以达到3-4个数量级' }6 y1 m$ S% M
target = pd.DataFrame(
* W& ~* H. l2 f0 p5 [ {& t! h" Y/ g% b5 h% D Q
"Time": df.index.shift(freq='-6H'),0 N/ W" [( \# a9 G r) ]
"Datetime": df.index,
' {4 |( {0 U6 ^8 X0 ~ }0 V4 W! q* y, s. q3 j) I
)6 P/ y' W' X2 o( r8 T. H
* [) C" n- v9 Q
res = pd.merge_asof(6 j6 d4 c% a$ n) ~2 D
target,3 ?' I8 J( V% }, w
df.reset_index().rename(columns={"Datetime": "Time"}),* f! S% E7 ?0 W* l8 E
left_on="Time", V6 q! G6 J* R7 y! A" {% y, D: B
right_on="Time",
+ E" ^& j. y8 w0 F) u, j direction="nearest"
$ y6 [6 u$ S3 L& y).set_index("Datetime").Radiation
0 Q8 X7 P, A; _; O7 l1 C/ h- `8 d& F
& K+ |& G. D' o$ f+ S5 T/ Wres.tail(3)
# p2 k5 a9 P: T3 p4 N% jOut[224]:
9 Y! ~: W) `; Q) E& nDatetime
. j9 p2 ~% T0 k& `5 W, [% L2016-12-31 23:45:04 9.33
; @6 z' B! e7 i" K5 i2016-12-31 23:50:03 8.49: K, a$ J* C6 F6 s/ x
2016-12-31 23:55:01 5.846 R* @# l1 `% [ x0 F
Name: Radiation, dtype: float64
5 C5 i4 u/ i/ I- |# h9 j, E/ q+ g' Q6 P7 a4 v$ w
13 R9 r: J% Y9 l! K$ y; M, Z
2
0 e: f% E) i0 v/ k! d7 Z' h: s3
# M4 E( F( _( F* b! W7 e4 x6 f, a. O* G: H
5
9 p, q% M2 t: D K* W3 z69 R; v' E: o) q; ]5 F
7. P0 L3 y. |9 ~. U2 n, @; w
8% F. O$ V, @; \( M9 a+ j) n
9# M+ ]7 p2 H4 Z1 h( F
100 N. D) y9 E, q, B& U1 ~
11& K9 X5 y& P' S4 C6 j
12& {2 @& a8 C: Y, w: }$ A/ z- f9 d
13' K0 k. \1 k" R7 }* O1 w
14; g. g) }& R' ?6 w( y2 g- E
15) q" [' e; Q, M, x3 S) w: @1 G7 \% @
16
y! i4 M( G0 i8 A6 n. n17
+ }3 C- o, T, o18
6 Q. K, r( [, I$ N* v- ^19
6 o7 V* E6 j2 X8 ?4 u( U/ c20
^) L" t6 b% V3 X5 \1 _21
1 @7 K$ [" W* x* v22
$ I n8 K, t/ I7 W236 F+ M( k4 x8 l; m
Ex2:水果销量数据集 h2 t5 h7 g) l, O" |6 P
现有一份2019年每日水果销量记录表:& c! J0 i, c b: B& r% H
( _, V/ J) o b/ Y {+ I4 Bdf = pd.read_csv('../data/fruit.csv')
3 C4 Z7 G! T J9 Y4 ydf.head(3)
- V1 A. B3 |) ~ z+ L+ i
; A. L3 Q1 C5 ^" G2 POut[131]:
* `# u$ |7 W6 w0 u% u Date Fruit Sale( | \; E/ B, }% c
0 2019-04-18 Peach 153 }3 b5 b6 j* W8 h3 e) ]6 k
1 2019-12-29 Peach 15
$ I) W& ~7 B# u0 U Y$ A! l2 2019-06-05 Peach 19
8 k# A3 u6 G1 e1 F I/ q1
/ F4 Q. B- {: d9 q7 x ]* X% C1 g) W2# k) U! i# y" I
3# e; U0 M! W6 P( b4 h" D& p2 @" S
4. r' E0 A- x6 L* m2 K) T( }3 S
5
, D, y; o* M6 \1 X+ E6" w5 s7 z+ \+ o/ Q
7
% U- b0 b" Q8 Z/ S: o/ w8
1 {& e4 V9 a. T M2 z5 y K统计如下指标:
- y7 B2 ^' j6 q, {& C每月上半月(15号及之前)与下半月葡萄销量的比值
- ?0 R6 | C! F) P每月最后一天的生梨销量总和- _; ~( i( i6 L1 z, y, S5 _
每月最后一天工作日的生梨销量总和- k' ~1 ]5 k- Y/ i7 m
每月最后五天的苹果销量均值
: \3 v: v5 J b5 A按月计算周一至周日各品种水果的平均记录条数,行索引外层为水果名称,内层为月份,列索引为星期。- C. n+ f$ U: {" \
按天计算向前10个工作日窗口的苹果销量均值序列,非工作日的值用上一个工作日的结果填充。
3 ^6 Z; M3 o6 C( a; W% i: b0 uimport numpy as np* e! r# f: S( v- s+ l, P7 o
import pandas as pd
* o: F$ B9 d; Y$ _1
- q( h B Q( n1 w, ?2
) y4 _* p( w, h统计如下指标:
9 |! R4 Y, F t0 J; x) r每月上半月(15号及之前)与下半月葡萄销量的比值1 g! X6 e$ A1 N, Z, v* O
每月最后一天的生梨销量总和4 l* q6 o, t# o5 ?7 q
每月最后一天工作日的生梨销量总和# x( z b' t) c# B5 ]
每月最后五天的苹果销量均值
3 s9 J$ l' o, y' h* g/ f I& n& u# 每月上半月(15号及之前)与下半月葡萄销量的比值
/ W; w; n# E8 [ e9 Sdf.Date=pd.to_datetime(df.Date)' \! |7 Z. D& Y
sale=df.query('Fruit == "Grape"').groupby([df.Date.dt.month,df.Date.dt.day<=15])['Sale'].sum()
$ c& B$ M c6 X* R- wsale.columns=['Month','15Dayes','Sale'] # 为啥这么改没用啊
% Y1 A/ O7 w0 H. E$ I8 Gsale=pd.DataFrame(sale)
/ G4 n! k% d& L ~; X( `+ i+ Vsale=sale.unstack(1).rename_axis(index={'Date':'Month'},2 ^7 |- Z. h* U& R8 \( x" S$ L1 }
columns={'Date':'15Days'}).stack(1).reset_index() # unstack主要是两个索引都是Date无法直接重命名9 @+ J' c' I; i5 J7 C
sale.head() # 每个月上下半月的销量% N, |! H0 f2 V* Z; H6 s6 w) t" e
9 ~3 I5 I: `5 V Month 15Days Sale% A5 e+ ?8 V' s& G; u
0 1 False 105030 @# \$ l* c2 J( Y9 f
1 1 True 12341+ W I2 ~% E) ^' I! q Q" x, O" z
2 2 False 10001
3 Z& I+ J9 A8 b# j6 q3 2 True 10106% a7 k4 i0 t: D9 v
4 3 False 12814, `* N2 R/ Q: {2 X- U% i- f, G
V" A1 ]( r3 ^0 ~- j7 M
# 使用自定义聚合函数,分组后每组就上半月和下半月两个值,根据索引位置判断求比值时的分子分母顺序2 _* R* S$ S( Z% Q$ v
sale.groupby(sale['Month'])['Sale'].agg(0 e# I* C( Y+ G1 b7 U
lambda x: x.max()/x.min() if x.idxmax()>x.idxmin() else x.min()/x.max())
% O! U/ c" ?# ~- C: f' k0 @; Q0 A$ Y" m5 W( M) a m6 M9 q: e
Month/ P% J* V% U- n4 h! U8 d
1 1.1749986 _6 x: d% j$ o
2 1.010499
4 w. a0 v1 ~5 a/ n+ ^8 m4 w/ g3 0.776338' y2 U0 e, b8 d; J4 b; O
4 1.0263455 O; ^ A6 d$ d" _& m& c
5 0.900534
& ?$ v& ]: ~# |, O! J: D6 0.980136
1 x/ u& g) j9 m+ G6 u' J6 H2 ^1 H7 1.350960
& b$ R% X! j0 W( S2 n& ?. t8 1.091584
; ^. V1 q. F) Q4 B j- q# l9 1.116508
. h# i% \% z: K; u: ~10 1.020784
1 w# F! X7 A# s' e( g! E11 1.275911+ {; u3 z& W/ m
12 0.989662
8 u8 a, j# V, fName: Sale, dtype: float64
4 Y9 [: } A4 ~: P# q( l6 M: L
: y% l/ O2 s7 [8 m1
/ X6 U6 u% ~' f2% p. {* L, P: e! @* y
3: `8 r; H' I' i# k
4
& D0 H' _" u) ^# q! A7 M5: S ]7 [% H/ E- L
6
4 ~6 _ J- k" @' k( P# D73 i; v5 B+ S6 M9 Y. h. V( v' d7 W, @( U& A
8. S2 b7 W7 [/ e8 \# t6 T# G& Y
93 m/ m* Z* {9 \ m- V) }; ]/ z
10
5 l' }0 c: Q, t4 O& w' \' G8 n. p11, D+ ^; ?( F7 s ]. o f- _# }
12# i0 y$ z5 T3 R C& K- ] m
131 c) ~6 y7 _/ C
14
4 E4 ]% Q: o7 y15
( [3 c8 P- Q6 ]8 v3 d162 Z3 O. E7 U( P* ]6 L
17, Q* Z1 S5 O- E( }+ G8 h
18
2 n `3 u! ~$ k6 | L- ?& m( Q( d) d19
' t q+ ]% [+ f4 E5 N20. |0 r: C3 v# b* C& b6 b5 w# S
212 g1 E+ ?+ g$ `3 f$ |
22
8 e0 U4 q. P7 j8 i& ]23) N$ y; t9 W$ s) q9 E) `8 M
24
# w. d1 l: v( D N/ B9 L25" Z: E) Y! W$ _. W' U
262 k, E3 b' r% c2 ~3 V1 |3 C
27
' B, B! l2 x* q# Z( v28
& U6 u6 I3 ?: j3 n; p3 G$ |3 L29# W+ g! ~6 i! T/ R, J: [' c
30' S& }% J- O" ]3 ~4 ?+ x; d
31
1 w; B& f$ [( r2 a32! _* {. M3 J( [/ ~ O$ N' e0 S, C1 w
33
3 j8 p/ r( I f, \3 f6 \34: \6 N( E0 |. O; r7 l
# 每月最后一天的生梨销量总和- N' f3 ], r5 v" W" X; r
df[df.Date.dt.is_month_end].loc[df.Fruit=='Pear'].groupby('Date')['Sale'].sum()
; h! D' u$ A5 f- a/ I! n
+ ` E( I5 Z2 {3 V* B8 nDate" U! S, L8 |7 u# \9 e/ o- R. M
2019-01-31 847: @: h# L. U6 c3 a& `
2019-02-28 7742 N3 F" j' v" y i7 |
2019-03-31 761
+ r; |9 p1 e$ c" C; ^# \4 \2019-04-30 648
3 t7 Y2 X# a" {* x; b5 |$ c2019-05-31 616
2 }( \# T* J9 E5 a: V" N1# h/ ?) Y1 N( D/ \" m
2
0 f: ]- Z; h9 ?9 t- S" S9 k33 G6 r# [$ L* c3 k9 V2 q
4$ z' Y# _. [+ @" v, x: W0 M
5
9 T$ M4 n2 |$ Z5 E5 a) P6
7 o1 J4 G5 D, }) y2 r7/ M: Y6 v) h' {
8
7 u4 d4 m" e0 | M- ]. }5 {, }$ }9
" Z; d# S I5 q# 每月最后一天工作日的生梨销量总和
) {" i) k- I, @7 kls=df.Date+pd.offsets.BMonthEnd()# d( K+ C- L3 \$ Z; m; Y8 k+ C% U: F
my_filter=pd.to_datetime(ls.unique())
6 D$ o! D$ _7 _/ n9 s8 Z1 b9 | rdf[df.Date.isin(my_filter)].loc[df.Fruit=='Pear'].groupby('Date')['Sale'].sum() A3 q" { n; y6 @: P, r
& L3 I$ w! L+ B) z1 ~Date0 z# D; s) r/ k5 V: s
2019-01-31 847
- Z7 j! y( h/ O6 o; \! A2019-02-28 774% t/ J$ c1 I6 b. C7 ?" l/ m. F
2019-03-29 510- k" ^0 |6 W( k; L3 I- d* M% Z
2019-04-30 648
! e% N: r5 O% S6 H2019-05-31 6161 b& v5 D" `; W$ _. B
1' o3 v) [/ F3 x1 h4 Z
2
9 `& J& y9 l1 z3 C8 U n8 b3
3 p/ b A+ c/ k2 a q! S4- ]# i0 A, Y- x- U2 A# w
5
6 l) C. o+ h3 p h5 w p& W6 h$ a6
! S9 t8 P' U. L3 T% V1 R7
4 \, E! V) r6 w4 w1 K. \1 K8 C4 K, k- c( H- W, C9 B
9- U% m* v: V* Y( I8 f4 n# a- w$ s
10; v. K7 H" R& A& {: ~
11
* I4 ]+ k% ^2 d& w! V# 每月最后五天的苹果销量均值
$ }# u8 G& D; O, v! O0 Vstart, end = '2019-01-01', '2019-12-31'2 x6 E; V. Z P# ^' q& [! M
end = pd.date_range(start, end, freq='M')' b' E4 \) l! G' k* B; I
end=end.repeat(5) # 每月最后一天的日期列表,重复5次方便做差
/ @! T8 c, u4 y+ _6 O: \( o1 \! I# O/ u) k7 ]( `
td= pd.Series(pd.timedelta_range(start='0 days', periods=5),)
# C; B6 @' D9 X' ^3 `! C Ftd=pd.concat([td]*12) # 日期偏置,最后一天减去0-4天
w, d0 S% ^6 I9 ]end5=(end-td).reset_index(drop=True) # 每个月最后5天的列表
q8 \7 k2 B( m+ ?, k' @& L1 U+ l: s& l1 N. ~' k" I9 i8 v6 k
apple5=df[df.Date.isin(end5)].query("Fruit == 'Apple'") # 每月最后五天苹果销量
8 Y8 d3 P6 q @( e! [apple5.groupby(apple5.Date.dt.month)['Sale'].mean().head()) E+ b: N j1 g7 r- K/ V
! t" d: H+ Y8 Q N3 _$ ~5 W4 J/ ZDate- ]! y) U! ~: [) y' ]! k$ }
1 65.313725 U; X( K+ T% {& Z8 @( P4 u
2 54.061538# p4 l( W1 P( D3 e$ K( y% K$ q
3 59.325581! M$ Y9 m2 \/ D# A/ E4 H
4 65.795455
$ A8 ^7 A6 R( W" t5 57.465116
& V' i" E$ v# k0 y8 B2 o/ V* ?$ B/ Q
- C: ?" M6 W, w6 Z3 h1 o1
/ b5 E6 P4 B# A7 M+ w: f6 I2' E& I A% _; s% \2 C. N# R6 y
3
4 H8 l, o# b, P" U4
$ [; y+ h1 c; x8 E- i% S5
$ C% a) e& ^0 N: i/ G6
+ g( Q( [/ y: F% m7' T; B7 j% ~4 A
8& q' t% r8 m1 g) P
9; c: z" V% }5 H" ]
10
) }9 i- \, V/ Y" G% r; Z. r11
; l6 f! ~4 c3 z) _9 o! P& z! `12
' z; j4 [) ?5 X. i13: X% b' D' b( p* ^* ]' P
14/ h! M1 Y0 O. s! S! F
15
& b& e; w7 J; k/ [4 s6 r+ @16
& _# E6 v/ f5 V/ p) ~8 N+ D177 o/ j$ R5 s. x8 s
18" X. s9 D& r% s' a; @
# 参考答案:
v/ t; u+ |3 \$ Mtarget_dt = df.drop_duplicates().groupby(df.Date.drop_duplicates( a0 y/ {1 {' |" M
).dt.month)['Date'].nlargest(5).reset_index(drop=True), r% l" W. g0 j$ T% K4 V
& U; C0 w2 h3 W4 k) H8 e" _res = df.set_index('Date').loc[target_dt].reset_index($ @8 z; @8 r* h# U& |
).query("Fruit == 'Apple'")3 G" |( u/ Y' {7 g* ^ ]2 V3 r+ Z8 s
# Q3 C+ B' k3 d3 h' q) D5 Sres = res.groupby(res.Date.dt.month)['Sale'].mean(: R+ b5 S8 U' G9 d8 K- W0 ^
).rename_axis('Month')
' ]; g2 U7 b$ V2 q
9 v" Q* K. o4 Z4 L6 P. f
) [2 k7 U$ V. M+ {4 s, j7 t) Ires.head()& y" w1 t! r# u N; Z
Out[236]: 8 t" \. \7 ^& C& I( O
Month6 U$ u6 q3 O! M# R" |- z" g
1 65.313725
- I8 E5 `) R0 p- `8 l6 i2 54.061538; c8 E; E! P+ @
3 59.3255812 `" o* w. B8 b" X0 f
4 65.795455
8 ~' ^7 i- Z: {, D! _! }5 57.465116" T6 f2 Y. v" n* y
Name: Sale, dtype: float64
/ V& X' ]6 n# V+ j/ H0 E2 R" i& b0 W, x( P7 m* q j( F
1
1 W: s: j; n* X# O3 w9 c( T2
" M: @5 q1 t' q39 {! W) t0 x7 g
44 J4 F& z# E) V: |1 X: y1 E
55 ~0 a( T9 ] U# A% W
6+ U9 n- e2 ^8 O# h# G: n9 O
7
: p1 ]1 q! H# d9 ?9 ]8 X8 ^8. K+ G* n& L7 ?3 G- W. m
9
, ~' E; f @% R. ?% Q( \! T100 R# i: U% W0 H5 L) O
11
. O4 L) x9 k4 P/ ^12
6 a+ k2 {% h" o4 C* Z9 E13
# O1 c# r1 M0 F w( |14: C: o, \2 U6 ~
15
3 d$ ?0 U" R# v( L p$ `8 ^16
p' P1 _% X* d) j, q2 a. @17
: c, D$ [+ i5 H" a* T18
. S/ D( Y8 I Y, n, I19
0 E; ?4 z* o; F! p2 t( P1 J20* M6 h6 J$ A8 h
按月计算周一至周日各品种水果的平均记录条数,行索引外层为水果名称,内层为月份,列索引为星期。
% E) Z5 M4 n4 D9 s# Presult=pd.DataFrame(df.groupby([df.Date.dt.month,df.Date.3 p4 b' a4 x& k8 V4 f# b m+ ?
dt.dayofweek,df.Fruit])['Sale'].count()) # 分组统计 - _9 W9 l) A9 `0 H( r( ~- Z: R
4 i4 f4 C( x( d0 l) Kresult=result.unstack(1).rename_axis(index={'Date':'Month'},
/ D3 J. K7 ?7 h2 t: ?- r# v+ V columns={'Date':'Week'}) # 两个index名字都是Date,只能转一个到列,分开来改名字.
2 V q1 a' I! Fresult=result.swaplevel(0,1,axis=0).droplevel(0,axis=1)8 p7 C7 m1 @; N1 X+ m2 ^
result.head() # 索引名有空再改吧6 E- U8 n! X) ~+ a: P. _% S* @
4 I8 k* w0 c' t r' r; M# Q Week 0 1 2 3 4 5 6
7 A$ y% m E1 F$ a) P$ k! n7 l ^Fruit Month
' N% l) N3 m6 tApple 1 46 50 50 45 32 42 23
7 r7 B( C0 c; C1 p* x# s( }9 W1 y; HBanana 1 27 29 24 42 36 24 35
, p% P7 v& D* }8 w5 D2 a* JGrape 1 42 75 53 63 36 57 46
* Q" E* j2 D4 G& Y8 jPeach 1 67 78 73 88 59 49 72- m7 p: v4 s9 L& O* {; A
Pear 1 39 69 51 54 48 36 40
. a' I/ `1 t+ k: T- \6 m) \* F1+ F3 S2 Q( `0 Z2 g, Y
2
E Q) Q- W6 a7 i$ o3
; o) z( P. |! r3 f, i! c4
' k: W- R! l0 L; d/ S5 V: x5
) [$ b" ?$ x3 w, i5 W9 n69 J# ?- O3 Q( y* r: {! g! Y
7
, h. E8 o1 h! D! Y81 o4 g8 U/ R% A
9
5 M" C; X& f z' Y1 k' M. |/ a3 H10# R- m+ t7 `+ e# Z: N9 U8 n7 v ]
11- P) u& a: L- y- D8 O
12
% a; l Q0 R6 \/ _2 X5 A( ?13
* |3 n/ q) f4 Q14
4 Z, Q2 B# C; N: E15
+ k- |* v+ \# V+ r按天计算向前10个工作日窗口的苹果销量均值序列,非工作日的值用上一个工作日的结果填充。
3 {) _$ r$ @' b5 O4 G# 工作日苹果销量按日期排序2 V- N( m( J& t& T; _
select_bday=df[~df.Date.dt.dayofweek.isin([5,6])].query('Fruit=="Apple"').set_index('Date').sort_index()4 _ j2 `" n- S) Q4 }+ `7 d) P" v
select_bday=select_bday.groupby(select_bday.index)['Sale'].sum() # 每天的销量汇总
4 F6 q( m1 l0 t( t2 ^2 \select_bday.head()& G6 n4 o1 Y7 y, Q3 O
8 N' u& [3 J. v/ d& y9 k! y. D# HDate$ Q! g8 A) |4 L$ Z
2019-01-01 189/ r4 t; T/ L6 s; g( N
2019-01-02 482
. f' P# m# o1 w* W+ t2019-01-03 8904 | G O" q" d' x* Z( T" _7 P
2019-01-04 550) F# N. g# u0 }8 A* z8 C
2019-01-07 4949 G/ a0 |: a" o( P7 f( q, X% j
4 N( [) W' b1 Q% g7 i
# 此时已经是工作日,正常滑窗。结果重设索引,对周末进行向后填充。6 r6 Y" L V$ h9 @7 R1 n
select_bday.rolling('10D').mean().reindex(df.Date.unique()).sort_index().ffill().head()5 h) d% t* E$ s6 \
' V# B2 ^* S7 Q
Date
& p+ t8 G. m: U- ~/ ]- u7 t) F2019-01-01 189.000000
& c* a( l& ` `2 f, b8 D* p+ Z2019-01-02 335.500000 t: t" b i" a4 G+ x4 h
2019-01-03 520.3333331 `* R* s' n% T+ }% n' x Z
2019-01-04 527.750000% L7 ]" ^" ]) V+ s( G
2019-01-05 527.750000
3 k& W) t, [' A* ~/ c+ C+ {
/ `: b) N, ~/ Y7 u! e————————————————
' F1 j7 S( k" o. ~" N: Y3 i# ^版权声明:本文为CSDN博主「神洛华」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
4 e7 B$ M( @( ]4 @( }8 f4 q原文链接:https://blog.csdn.net/qq_56591814/article/details/126633913
* o8 z' d, j) E: t
, Z% X5 y- \: p+ b" F' w
% [$ v$ M! H/ ]6 b2 z( R: v |
zan
|