在线时间 1630 小时 最后登录 2024-1-29 注册时间 2017-5-16 听众数 82 收听数 1 能力 120 分 体力 564703 点 威望 12 点 阅读权限 255 积分 174634 相册 1 日志 0 记录 0 帖子 5313 主题 5273 精华 3 分享 0 好友 163
TA的每日心情 开心 2021-8-11 17:59
签到天数: 17 天
[LV.4]偶尔看看III
网络挑战赛参赛者
网络挑战赛参赛者
自我介绍 本人女,毕业于内蒙古科技大学,担任文职专业,毕业专业英语。
群组 : 2018美赛大象算法课程
群组 : 2018美赛护航培训课程
群组 : 2019年 数学中国站长建
群组 : 2019年数据分析师课程
群组 : 2018年大象老师国赛优
% p+ G& ?2 v2 H- x& S3 R
$ E5 z" V* r- t& \- }5 p. G+ C 3 u: G% d: Y( z! I
文章目录
' ~" O: I5 g- y" _. r8 u: h 第八章 文本数据! F5 I. S, J A
8.1 str对象& N3 Z& G3 I2 S, K; B6 r) m- o
8.1.1 str对象的设计意图" ?2 x/ `% o* Y B, x
8.1.3 string类型
1 r; A/ R {2 t- Y6 f 8.2 正则表达式基础
5 K3 k5 s9 `2 y' g* ]6 |8 e 8.2.1 . 一般字符的匹配, g6 |2 y5 M9 ]
8.2.2 元字符基础 y3 b! z9 ~" `. Y7 e7 U
8.2.3 简写字符集
( X& H0 l: u: x- Y 8.3 文本处理的五类操作7 i8 p) P! ` }5 I! @( b. i
8.3.1 `str.split `拆分% _% M* I' |5 i/ V. j; N
8.3.2 `str.join` 或 `str.cat `合并
7 d0 g. v9 I! E, ]. m$ Q, {0 ^ 8.3.3 匹配 t/ c. S' e/ n0 j6 j9 j' J7 x
8.3.5 提取
. @ \! y' Q5 D7 n& d 8.4、常用字符串函数' O3 n( P/ ?0 g P
8.4.1 字母型函数7 X; I8 j2 R$ ?
8.4.2 数值型函数
2 K- q4 [% B( z 8.4.3 统计型函数
* E/ b/ z6 `1 ~3 } 8.4.4 格式型函数! ~# w i" V: e
8.5 练习& g8 |% p% U l5 x
Ex1:房屋信息数据集0 e$ N' q$ _7 \! K. d! Q
Ex2:《权力的游戏》剧本数据集
4 W0 q, V9 r# Y; H" V- y 第九章 分类数据* a) z! H# q6 A' | }9 T L
9.1 cat对象3 A; u2 z0 ?: i; i8 @8 f3 W3 E
9.1.1 cat对象的属性
5 I3 Y* f/ ~8 F& K/ o 9.1.2 类别的增加、删除和修改& P& p# y; D, S( H
9.2 有序分类
$ p" ] U" y, z6 Y2 _& O 9.2.1 序的建立- N- `, Y! M# w. a
9.2.2 排序和比较8 O2 c; _0 b, c6 z
9.3 区间类别
T+ g" O: `- R1 K4 L 9.3.1 利用cut和qcut进行区间构造5 |0 j1 X; i! k$ o
9.3.2 一般区间的构造$ V' W5 X. K/ X5 a v' q" j
9.3.3 区间的属性与方法+ j- L" O L' F' b0 w8 @
9.4 练习/ F2 U/ q& [7 ]! U/ a6 a
Ex1: 统计未出现的类别# ?3 C' `3 J: i% _( x$ ^
Ex2: 钻石数据集
3 c7 E6 G$ G3 y+ c% O) a" H0 q5 P 第十章 时序数据
+ t# l! h& v- L' Z- f/ B 10.1 时序中的基本对象4 C/ f3 \0 V/ m& {0 d$ g
10.2 时间戳
. J ?4 E2 ?' v' K+ c- F$ J 10.2.1 Timestamp的构造与属性
' A( x" ^% x. E% P3 f1 [; r 10.2.2 Datetime序列的生成# E0 Z( v0 i( u& o1 b7 `
10.2.3 dt对象% O; f" z. z, `: p4 }6 s3 b+ R
10.2.4 时间戳的切片与索引; O$ V5 X3 u+ ?1 r3 K, }7 Y
10.3 时间差7 \8 n. b I" n4 A4 k! h
10.3.1 Timedelta的生成. B5 ?1 B7 f$ b% z/ S* t Q! q
10.2.2 Timedelta的运算3 J. o% }4 n7 o; r
10.4 日期偏置* o2 A" ^4 h( {& Q# C1 }. u
10.4.1 Offset对象
: ]6 k7 V i! q& p' }% w 10.4.2 偏置字符串' h3 t2 x. o5 u7 k' I
10.5、时序中的滑窗与分组
* Z# ~( x, Q Y |/ r" H) k& a# a 10.5.1 滑动窗口
+ d1 c5 ?$ q1 h 10.5.2 重采样
" t( ^3 M) I, P1 U 10.6 练习; C1 f" V9 b) Q
Ex1:太阳辐射数据集
% v9 q3 S3 T8 p4 r1 O3 r2 _2 l' D2 ~ Ex2:水果销量数据集
/ v& q M) B' L, C 课程资料《pandas数据处理与分析》、github地址、讲解视频、习题参考答案 、pandas官网0 f/ o9 G, |" V
传送门:0 W8 ]" |1 T' C9 Z
. ?0 N- h" h3 w6 Z- y5 E! q, x. {
datawhale8月组队学习《pandas数据处理与分析》(上)(基础、索引、分组)- `5 {3 t; _/ Q6 C
datawhale8月组队学习《pandas数据处理与分析》(中)(变形、连接、缺失数据)( u; f$ C3 x4 k/ t( J4 J O
第八章 文本数据- m! ^) T6 `' I8 N
8.1 str对象* w* _6 H+ I9 q. a
8.1.1 str对象的设计意图( g; J! Y q' u( `8 z
str 对象是定义在 Index 或 Series上的属性,专门用于处理每个元素的文本内容,其内部定义了大量方法,因此对一个序列进行文本处理,首先需要获取其 str 对象。在Python标准库中也有 str 模块,为了使用上的便利,在 pandas 的50个 str 对象方法中,有31个是和标准库中的 str 模块方法同名且功能一致,例如字母转为大写的操作:" ?7 B% o) l# O8 o2 {' o% S' t
V. O4 [- [2 Z# i! F var = 'abcd'8 o/ ?1 U, V: D. e
str.upper(var) # Python内置str模块
. K- T! U% T9 Z6 s Out[4]: 'ABCD'
. Z2 H( T L4 G/ X+ D+ e $ f' F9 m# Y, \7 E0 s2 y
s = pd.Series(['abcd', 'efg', 'hi'])- W" i! [+ b6 d; `2 `6 [9 P
1 x. H9 ^& M# } s.str4 w4 L! y4 ?& @. x+ {2 s$ Z
Out[6]: <pandas.core.strings.accessor.StringMethods at 0x2b796892d60>
' V: }: t& ?; d% D ' n( x. Y2 R9 U6 J! E, ~
s.str.upper() # pandas中str对象上的upper方法% u+ G. w. J; v. K- ]' u
Out[7]: + H4 U8 R5 @% Z5 |7 T. u6 F9 I0 u
0 ABCD. J& O* h( j0 n6 K
1 EFG1 f* X( M3 a. E1 f1 R4 n8 b( M
2 HI
0 W L6 H2 X- ^/ l0 k& Z" A dtype: object
9 K; z* Q8 D- V 1
( ]' M. C8 S) ~2 t6 c 2
: W& q% J4 T+ x% p 35 Y$ R/ K6 J7 M+ b. \
4
! v) d+ S: ~3 s0 l 54 D' N9 y5 N" O0 V2 l
63 N# S8 p# L# b1 }( C F
7
5 K6 S5 c: B" u# R* }+ T& y7 C& S 8
8 X; x( ~, v9 N6 k- \ 9
$ R; Y5 G4 D ]$ ? 100 d/ I- L9 d, Z4 w" X% J
114 q+ ~1 U' J- d. _) n
12
* @2 K2 ]$ T- o% h- C 13# C/ e) R& W8 P; V% \- e+ |: }
14
& p& M1 F! l+ i2 G9 o 15; f* t' G! s$ y, c( ^
8.1.2 []索引器/ I$ Q# u2 V" f0 r& ?0 D6 I
对于 str 对象而言,可理解为其对字符串进行了序列化的操作,例如在一般的字符串中,通过 [] 可以取出某个位置的元素,同时也能通过切片得到子串。
' k& z. w+ \. L, T pandas中过对 str 对象使用 [] 索引器,可以完成完全一致的功能,并且如果超出范围则返回缺失值: p0 ?. U2 s" R) d( s* h
7 t% H8 j# Z7 k# [. n' c* y1 v+ e s.str[0]
' E4 ~3 x' s- C; [9 h) j Out[10]:
+ u; y8 d- c a5 Y( h" \ 0 a
% i5 g2 R9 c/ r 1 e
6 E- ~1 Q0 e4 n; q! F4 ~+ F t 2 h
6 s: S+ d. S/ Y3 v% |! k dtype: object" A9 |8 g. R" b) G: I5 v# x
8 @$ L) E R1 _0 W1 d s.str[-1: 0: -2]
: R3 M5 x% ~/ I5 k; S0 D Out[11]:
1 P3 T& Q. z3 Q4 t X- U 0 db+ f) S9 L, ]& m6 ~
1 g
; n1 U6 N8 Z+ ^$ ?% O& u 2 i
7 [- X( h( @5 S) ]5 I/ X4 U' }" [ dtype: object
8 I, d0 U6 t$ U: `' @
, L; J9 S9 Z1 z% c! }0 v$ D; z s.str[2]
5 y) x+ R* @ a Out[12]:
2 A. s+ @7 m5 x L D$ M 0 c
- f9 Z: R2 Q/ T' D( d+ \8 x 1 g- `- z+ o' @) j+ c1 E
2 NaN
' _4 E1 o* Y i. `! {6 u1 v dtype: object% j, n4 P5 e' j1 S
, a: S0 v5 i: v- Q' i
1
5 \1 P q0 b" s2 E1 Q 2
3 Q; I- Z+ H5 G( P: a9 } j' \- ? 3
" [4 F- j5 H- @( p. | 4: S9 G' |4 r; A
5
9 |3 p: \7 i: A5 b" X9 Q 69 A4 M, b2 ?/ P7 |' I1 q3 W8 r- X
7$ l, V0 `! L+ V" y# o) L
8
0 W5 s9 V z. E4 T2 W 9+ M3 B' i$ _4 D5 q( i. [
10
1 j; G. B# t/ L% T- F 11
) l& G. G k. ]* e" } e0 @% U 12/ P! n% p# U$ K; w- v
13' a" q+ J% F- b) h; e+ K% T. ^
14
( b+ x! K/ u9 ^ 15
3 S) f6 l+ b. @: P k 16' F+ }& J* ^; g' E
17
/ Y" v5 D' S; T$ H4 N! d9 s 18' @6 g0 a* T) \6 \% r, V' w
197 A; M7 V" u* [+ [* x9 R
20$ v$ h% P, \0 @
import numpy as np
" C" I( b* d" c2 B- H import pandas as pd9 a5 N8 e( @( E* X% [9 v1 p' a
' V, Y+ E4 G" |: v+ x+ d
s = pd.Series(['abcd', 'efg', 'hi'])
' }! l9 Y$ L2 w2 l! O, _ s.str[0]
, f& t3 r; U: _. R" ]$ t 1
3 M/ r5 K; \ F4 R6 R. ?! ^ 24 l1 i; i1 ]3 _/ e8 ?" X K
39 S9 S6 {2 }0 Y& T1 I4 N \
40 I9 w/ L$ o0 b
54 D- c3 z: S# g% m2 v
0 a, |2 Y4 v: s+ N g) U( ?
1 e
/ a, J, Z" y0 D% ` 2 h( w p3 o- a! s4 d
dtype: object+ J/ h% N* R6 L" G6 B
1
: ?- ? C3 F4 m+ l" T6 F2 p4 A 25 m& _+ Z+ F+ v5 K
3+ V+ b" j% s6 D
4; ?7 i! h3 P4 d- j) g! Y
8.1.3 string类型
# \6 I B9 W' F6 b) h' U/ y M. p 在上一章提到,从 pandas 的 1.0.0 版本开始,引入了 string 类型,其引入的动机在于:原来所有的字符串类型都会以 object 类型的 Series 进行存储,但 object 类型只应当存储混合类型,例如同时存储浮点、字符串、字典、列表、自定义类型等,因此字符串有必要同数值型或 category 一样,具有自己的数据存储类型,从而引入了 string 类型。. f; g. T7 t* |3 f
总体上说,绝大多数对于 object 和 string 类型的序列使用 str 对象方法产生的结果是一致,但是在下面提到的两点上有较大差异:! Q0 Q. n% d1 Q
% Q% J3 L! n, D4 s( D' r9 j 二者对于某些对象的 str 序列化方法不同。, \7 d% M* N$ T( b
可迭代(Iterable)对象包括但不限于字符串、字典、列表。对于一个可迭代对象, string 类型和 object 类型对它们的序列化方式不同,序列化后str对象返回结果也可能不同。例如:
$ C) R2 P4 j/ n s = pd.Series([{1: 'temp_1', 2: 'temp_2'}, ['a', 'b'], 0.5, 'my_string'])+ J, W6 K+ U5 A9 H
s
: B" u: Z: b0 t. p6 l 1
. n' l# Q; h0 G0 B" K 2+ Y! [5 V' d% X; o! Y
0 {1: 'temp_1', 2: 'temp_2'}% w. I b/ K6 _) }! d6 `5 ~
1 [a, b]
$ `. q# x9 @" Z6 B$ v 2 0.5! \1 r5 N/ E J
3 my_string
) v8 {- x7 L$ h dtype: object9 I/ k `( J# j
16 B7 ^' n* n ~$ G/ j, ~$ T
2
- X4 d3 r T8 I2 D( y 35 Y7 O, h4 }8 {7 y, H
4
l k3 Q- a O' j0 V6 ? 5/ R- V, Z7 X' v) w i8 ?# }
s.str[1] # 对每个元素取[1]的操作
. g: ]0 z1 g2 q5 a! z% w0 Y7 j 1, X6 X# a0 s* N6 n& c7 \
0 temp_1* x q. @' d/ v5 j
1 b
5 W$ `+ o) `. N 2 NaN
6 O4 R i4 r- U4 S 3 y# X7 c, z* k& T" M8 Z
dtype: object
' m: l8 x3 l; b* i5 ? k0 ? 1
- c: k: h5 A' A& d- F 2
5 Q8 l; p p3 U6 B4 l 34 w2 f* U" @% y7 M2 o: e7 X+ a; B
4
0 r8 D! h: h6 q3 R) m9 e# X 5! V9 ~6 V$ X! b* @) {/ R& B
s.astype('string').str[1]
) G7 |# }0 P( I" ]( y0 D3 W 1$ X& ~ B8 t9 s8 K T! E
0 1
2 f, l& R9 ^" O8 Q8 K# d* y 1 '5 W+ S1 _' y, y# N _: O5 X
2 .
- ]5 j. l8 n$ p6 U6 e 3 y7 B g( I* Y' U
dtype: string$ [9 E8 g7 F6 b( u x, H1 X
1' O& @" R/ q3 @. O
2
0 h) g$ w6 e% H0 D. d4 g2 K 3
3 A) B$ W4 N9 _ 4
( D' j$ p1 m% [0 w2 A7 @5 J 57 b. u8 C3 @' R" C3 m
除了最后一个字符串元素,前三个元素返回的值都不同,其原因在于:% S' \$ o; q! z
" J4 T+ o/ q, ]5 `, L# }
当序列类型为 object 时,是对于每一个元素进行 [] 索引,因此对于字典而言,返回temp_1字符串,对于列表则返回第二个值,而第三个为不可迭代对象,返回缺失值,第四个是对字符串进行 [] 索引。
; F- i K! K7 n string 类型的 str 对象先把整个元素转为字面意义的字符串,例如对于列表而言,第一个元素即 “{”,而对于最后一个字符串元素而言,恰好转化前后的表示方法一致,因此结果和 object 类型一致。
3 ^5 F$ O" S; M string 类型是 Nullable 类型,但 object 不是
* {8 Z5 o0 G J& ]' o# i5 ^8 X 这意味着 string 类型的序列,如果调用的 str 方法返回值为整数 Series 和布尔 Series 时,其分别对应的 dtype 是 Int 和 boolean 的 Nullable 类型,而 object 类型则会分别返回 int/float 和 bool/object ,不过这取决于缺失值的存在与否。/ H4 z( I! A4 }1 b2 }3 U
同时,字符串的比较操作,也具有相似的特性, string 返回 Nullable 类型,但 object 不会。- A/ T. E" X- G- [
s = pd.Series(['a']). X' u9 `9 v+ e( c1 u& @0 T* I3 ^, l; G
2 m. R5 N& Q: H4 O& z s.str.len()2 d7 T: R; ?8 E
Out[17]: ( J, u/ J1 k# V2 A1 h' M" |
0 1
! G! x* u7 x% @! m( E dtype: int64- \5 E) h1 H2 r( C6 h# T8 k
( ~4 [# C3 J+ Y; f: [3 U% } N s.astype('string').str.len()6 g& V) }2 \/ w- f5 Q/ Z$ e
Out[18]:
- r k; m' m9 X9 ~1 e7 c 0 1
* B+ _/ C* e1 p. g dtype: Int64
, q" ^, l: Q# ~ # P: z! ^; k& I7 V* u- f
s == 'a'' {$ x; I% C0 k$ t
Out[19]:
6 P8 e& _% R7 ]8 r) o1 S 0 True
- j$ e6 F; p# R; }+ a dtype: bool" f. d2 A( Z* E# Q' o
, x, m; ~6 Y8 U2 c) Y s.astype('string') == 'a'# U m Q; V0 w) E( j( W, b
Out[20]:
Z8 ?7 Z$ V' c9 b! j y1 r 0 True
6 \$ w% X L$ E dtype: boolean
; j$ v3 r& v) ] D9 W/ T
# z. L7 U" g4 d+ K: u: w! A s = pd.Series(['a', np.nan]) # 带有缺失值
' V* D, s# s3 I" [! s0 ?4 B ; l$ _0 B7 p/ ? m: z+ U8 }
s.str.len()6 l: x, D6 R" B
Out[22]: $ ~" J" Z3 ], Y/ h# f9 M; o1 g0 M3 a
0 1.0
8 l% ], ?/ Q$ q/ n% O7 B" [ 1 NaN! o, p- I2 Z& J
dtype: float64
2 o, j: {& F+ I( h8 n
+ \* X Q1 V) E0 X s.astype('string').str.len()
1 ?% s# G- Y# c& G$ _' A% z0 R; R# Y Out[23]:
c2 i! S. ]! U- g- L 0 1
/ I! C" c/ W& u3 H1 h8 X 1 <NA>2 _# d! u3 {6 x+ L8 p6 `* Z" l2 b1 |
dtype: Int64
5 o$ F$ y! J5 t. M p$ v- c1 ~5 g& q ! i5 X1 @9 A. Q' C6 ~4 y
s == 'a'
x' U; T, U- m( z3 B/ s Out[24]: . a* f' X" v$ N- Y+ N6 W( b
0 True
& u/ U" _7 p9 G% M 1 False- @1 Y$ z1 k2 ?
dtype: bool8 V) Q5 `0 u, O8 P" W) K1 p
$ o5 b$ r( l- }! x' A1 _) r/ U s.astype('string') == 'a'
) m7 L/ W% ~; r Out[25]: 0 H6 o+ j# x3 [( M, W; H* K- e
0 True
8 g6 E6 q: w [ @$ H 1 <NA>
% i J, P+ m: Q# q3 n! P dtype: boolean( j' k% _3 u' ?7 U5 f. T
) m @. `; L$ n7 N3 u' j9 ~
1
6 r& r+ }" `8 V0 ` 2
+ s, d7 X5 j, \3 B3 m2 ?0 X9 [) R 39 S# U$ Q) E) n( `9 ?! x
4- T8 d4 i2 p; w1 L$ e
56 }( X& R5 z; w7 b* n% T* k. h
6
" X" p$ M& I; d% u9 _ 7) s3 b% W+ o9 X. T4 y- V! @
8+ J0 ~# i( _: |: ]
9
6 K& G- Q: j9 p# ~ 10
. o3 C% Q$ _ H( g% T 11
7 [ y! E2 @5 Z; y2 z 12' o7 { x* ?0 {) N+ j
13( G, J# P( P" I1 A) T+ F5 s& q
14( Q% d! m/ e. X8 ` V# Q( @7 Q' o
15% N3 m2 ], \% G! J! v
16# V) j3 {/ A( U5 D- S8 N
17
; m) L5 E' j+ t: ^% }' T% _ 18# _' a9 R+ m5 h: s) {
19+ [ A9 h/ m5 s- S; G8 B, i0 @% ^; t
20
$ C2 W, |8 @' K8 K9 |/ X 21( R. D$ O/ k$ y( {' P5 x9 Y
22
% G( H) Y3 b' p( a( C 23! r( s# x0 t: J6 O* X
24
5 B- I: {. W1 i% K 25
- S/ {% C2 _- Z- L5 ~/ T# k6 O& @( I 26
4 O" z7 j2 v0 s4 C5 Z 27
% M1 z7 w( z# w4 `0 L2 k$ K/ O 282 q- x. v+ l* G3 {2 t5 S# k
291 V, F& D6 s, V0 a, D( p2 _4 R
30
. b* v' ~+ ^- Q; N; W% W 31
9 E3 y( J+ v7 |7 e+ | 32
( l3 J2 z; f0 V' I ?1 S( J _, A. @% e" s 33, i5 [$ ?7 x. K% N3 Q& `6 y
34$ q; j% `2 e* |3 }- }3 x2 [& F
35. X; W% j% l2 w3 d8 d+ v8 \8 q# T
36
5 g3 Q6 T3 U* B6 C 37
. x" D3 @) ?9 M9 U 38
: v8 p! e6 N. P, G( g: t5 U* g 39+ H( Z& k+ n# g) L9 x
40
- H4 L6 C) G9 ?! c) T$ i% a0 w 41# p# u4 C1 x/ A/ |* P
42
- y6 S) i4 e6 Z! \* |% K 43
0 U% R% I# ]* X: ~% J' \% h 44
`3 m9 ~8 r) G. ?8 c3 \( t 45
. d. g2 |: w6 c$ d; q3 U 46
- X. \4 u" M" p; ?1 d 47
8 ]& n! ^2 l5 S. d 对于全体元素为数值类型的序列,即使其类型为 object 或者 category 也不允许直接使用 str 属性。如果需要把数字当成 string 类型处理,可以使用 astype 强制转换为 string 类型的 Series :
$ Q4 B0 L2 ]' L4 P
N3 H% u4 ~/ Y5 n" t* ` s = pd.Series([12, 345, 6789])
2 `3 e1 ^4 f( ^) M, O+ @+ G ; \- W- @; Z7 P- k2 n+ K
s.astype('string').str[1]
J) ^% z. A; H C0 h9 O$ E& \ Out[27]: A3 W( [7 M) Z. g; j1 d
0 2) q3 M5 _$ @" I( m5 u
1 4' b# e5 B3 U! W! y. z
2 7
3 ~7 @- V" W" T! C5 S A dtype: string3 @. _" B6 |9 @4 r4 A
1
5 z) g6 o# i, G' L9 h 2
" v6 K" k: _ V7 v5 ~ 3' r4 O$ C& P8 X n3 q
4
& Y5 c b" B$ S0 X- f% [ 5
$ l$ S) l6 T$ X7 [, k- E 63 S( }+ r! D/ v& `
7
3 C6 o ?- O" m* |5 V- ^' S3 X% P 8
; l% j& n' D9 ?5 F 8.2 正则表达式基础
* v: d# X$ \2 p. _% s5 H: e# L 这一节的两个表格来自于 learn-regex-zh 这个关于正则表达式项目,其使用 MIT 开源许可协议。这里只是介绍正则表达式的基本用法,需要系统学习的读者可参考《Python3 正则表达式》,或者《 正则表达式必知必会 》这本书. h; [0 K% h* D7 u
& Z$ ^$ J. S$ i 8.2.1 . 一般字符的匹配
+ _; A7 p; N8 Y. D# d% j) _: Z 正则表达式是一种按照某种正则模式,从左到右匹配字符串中内容的一种工具。对于一般的字符而言,它可以找到其所在的位置,这里为了演示便利,使用了 python 中 re 模块的 findall 函数来匹配所有出现过但不重叠的模式,第一个参数是正则表达式,第二个参数是待匹配的字符串。例如,在下面的字符串中找出 apple :
, b3 h9 }: j# z* S+ U
7 x. ]3 @0 t g v8 ^: ^+ n import re& X }) h C( Z
5 u7 w, t2 ^3 ~5 D8 O
re.findall(r'Apple', 'Apple! This Is an Apple!') # 字符串从左到右依次匹配4 A2 x2 F4 ~8 H3 K1 J2 k7 [5 p
Out[29]: ['Apple', 'Apple']9 `( n% k* l6 A* M! k. Z/ ]
1
9 E2 Z, r) L9 M! z 2+ G' i! g% J) D
3 s" x+ o H% l, j/ \
40 e2 x/ U. A; o8 Y) a
8.2.2 元字符基础1 U% U1 \. C/ Q3 B8 O5 e4 p3 ?
元字符 描述
& E1 d3 [3 K' E! B: ]* E . 匹配除换行符以外的任意字符% Q/ P+ _- z+ H3 p" i/ c
[ ] 字符类,匹配方括号中包含的任意字符6 c/ I0 o7 K" d, U
[^ ] 否定字符类,匹配方括号中不包含的任意字符( \( K. p6 z1 n( j( Q
* 匹配前面的子表达式零次或多次
: K' k7 i; M& ~( V1 F% F9 j O2 X% S + 匹配前面的子表达式一次或多次。比如r’d+'就是匹配数字串,r’d’就是匹配单个数字. {6 g; e* L1 q) N( }
? 匹配前面的子表达式零次或一次,非贪婪方式
; t F0 I, ]( z% M0 q/ ~( V' B {n,m} 花括号,匹配 n 到 m 次由前面的正则表达式定义的片段,贪婪方式
x& t& S" U( A7 d (xyz) 字符组,按照确切的顺序匹配字符xyz+ A; ^5 H* p5 }' s& {
| 分支结构,匹配符号之前的字符或后面的字符 P6 X, U D, \
\ 转义符,它可以还原元字符原来的含义# @: L, T9 \! A
^ 匹配行的开始3 h5 ]% g/ s4 w% ^1 m- m- z( B
$ 匹配行的结束3 [: q! N7 Q" p; g& _% @$ {& h+ l- e; X8 r
import re5 V5 r0 N& K4 f; C- o
re.findall(r'.', 'abc')
3 Y- j4 \3 g. c- k# M) T Out[30]: ['a', 'b', 'c']2 _4 v, Q3 o# V& c6 d K
9 Y6 H) y- o; k! o$ q re.findall(r'[ac]', 'abc') # []中有的子串都匹配1 J. s" f, ?+ D+ q6 Z) U- B
Out[31]: ['a', 'c']1 @; `9 o! o$ W( {, l; g/ X
2 S3 k2 h, i" G re.findall(r'[^ac]', 'abc') / N0 ?2 f% s! g+ R, T2 C% a
Out[32]: ['b']
0 [ ]7 g B, I
( n, j; l8 y5 ^ re.findall(r'[ab]{2}', 'aaaabbbb') # {n}指匹配n次- [& o, q, z( t( }
Out[33]: ['aa', 'aa', 'bb', 'bb']2 y, B' B, h& P! {: i4 Q% L4 ?
( _. {. b3 d/ v, W1 n
re.findall(r'aaa|bbc|ca', 'aacabbcbbc') # 匹配前面的或者后面的字符串
5 ^& y7 A1 s& h Out[34]: ['ca', 'bbc', 'bbc']# D/ i# }' T0 N
h' R+ |4 l$ k @8 ]0 M# m2 I
# 上面的元字符都有特殊含义,要匹配其本来的意思就得用\进行转义。: P4 |: w/ `) ~! K
"""+ M0 a* j# Z. j( @
1. ?匹配的是前一个字符,即被转义的\,所以|前面的内容就是匹配a\或者a,但是结果里面没有a\,相当于只能匹配a。
% v. |! o1 f! X* i! b 2. |右边是a\*,转义之后匹配a*,对于竖线而言左边优先级高于右边, b0 ^7 A9 D; y/ |. ~
3. 然后看目标字符串aa?a*a,第一个a匹配左边,第二个a匹配左边,第三个a虽然后面有*,6 N( ~4 [* S* w+ Q% B' B' V! }
但是左边优先级高, 还是匹配左边,剩下一个a还是左边,所以结果是四个a
2 U: H U" L8 r( O! \ """0 d" P$ ]' A/ a w0 V+ u- }
/ f; ~: m8 k N0 q
re.findall(r'a\\?|a\*', 'aa?a*a') # 第二次先匹配到a,就不会匹配a?。a*同理。
; d4 H6 g+ e2 `# S' C Out[35]: ['a', 'a', 'a', 'a']4 M3 l$ y- _3 b
& N$ l) `; z. o& \" G( L" D8 q3 f
# 这里匹配不到是因为目标串'aa\a*a'中,\a是python的转义字符(\a\b\t\n等),所以匹配不到。# k2 J P) {6 J% z7 G' b3 C
# 如果是'aa\s*a'之内非python的转义字符,或者'aa\\s*a',或者r'aa\\s*a'就可以匹配到\字符。: d) Y& {- s* T) J
re.findall(r'\\', 'aa\a*a') 3 \# D( t, x( G
[]
- }( ]. Z9 N2 F2 d. {0 |# ?! K % P# `1 M' ~9 u% w% v+ T. H6 P
re.findall(r'a?.', 'abaacadaae')
) C" O( S1 ]6 k- K5 x; R2 ` A$ t Out[36]: ['ab', 'aa', 'c', 'ad', 'aa', 'e']6 b! Z/ S/ v8 ~5 l
& A+ i3 o4 E" B+ c& S) Z- H
re.findall(r'(\w+)=(\d+)', 'set width=20 and height=10') # 多个匹配模式,返回元组列表
$ u2 ^- q# b6 j( Z6 X7 ` [('width', '20'), ('height', '10')]% G* U) Y8 y* f7 G2 [0 q
6 u6 S0 h4 c. @# h& e% Y
13 ~1 x3 ?4 J a$ `6 L; @
2
1 i+ h' u/ i8 Z K6 y 3
! N# b/ ]" l) c+ x5 \ 4
$ B- b% F& g% u- c1 N% L 5+ p& o/ u5 w% p# V4 S8 O7 G
6
( r2 |# c& u# w 7% f6 C! [0 f9 n B% E& q3 K
81 ~8 t; j. E) {- C, c, ?
9
+ o, ]# Y* I4 y. P 10
( E4 u& U5 o2 ~+ `+ o# X; v 11
# d4 n2 O8 J! y 12! L, F3 [! y" V* B
13
* c" C0 i! ]1 x) N. Z+ u% t- B 14
/ h9 e7 N; r+ E6 Y5 j 15
4 i. K. f2 Q/ S1 \- w4 Q5 c 167 d) Y' ]7 E t& U
172 m H- D# U+ e
18! K7 q3 [6 Y |0 I/ L: l9 t7 F+ p
19" z; E5 ?: M; J& M4 T# Q( V, B
20
0 h2 r3 Z. F. g- A0 u4 V4 x, _ 21* d! {; s) s4 @: Z) b" |- N
221 L& _% u# z4 P2 O. y$ G* z
23
; z: E# ^+ d" U5 |; o 249 f. e' L) J) Z$ K: D( ]5 m! F
25
% F5 p* S5 d3 B 26
? }- O/ ^ e 27
% q& x# s; Y/ p3 x 284 S/ \1 Q+ C. L' k7 ]
29
/ n9 K h" Q4 {0 r7 k7 o& \2 v& h( ~3 ` 30
7 J+ L% D+ O, l% q% O 310 Q+ g9 L8 Z2 O# N# K8 [
32
, o1 I8 [, t. X( X 338 }4 [- q! {/ g+ X5 g
34" b2 M) r! D' K! U" K4 k* H
35- `# h( W1 I& {; R2 T
36
3 C9 i5 ]4 X3 @- n" Z, W 37
6 u" |& J4 H5 l9 y. o) U+ Y1 U 8.2.3 简写字符集
1 l, s% z: s0 e/ o+ w# z 则表达式中还有一类简写字符集,其等价于一组字符的集合:
" Y6 o/ ~% E6 X+ }! P 0 q+ }: D& b+ [% `0 w
简写 描述
8 a$ b" t0 ^0 K9 g: Y: u \w 匹配所有字母、数字、下划线: [a-zA-Z0-9_]( s/ h7 _, }# l6 J8 ]
\W 匹配非字母和数字的字符: [^\w]0 C1 }2 p0 i! j; Q% e+ T
\d 匹配数字: [0-9] G/ }( e3 @* N/ b. h9 S
\D 匹配非数字: [^\d]
- a7 F( u( S0 @2 ` \s 匹配空格符: [\t\n\f\r\p{Z}] }+ _1 _' e1 B/ `- g5 G6 m D
\S 匹配非空格符: [^\s]* y+ S) u! P$ N/ L" i# T# p8 i
\B 匹配非单词边界。‘er\B’ 能匹配 “verb” 中的 ‘er’,但不能匹配 “never” 中的 ‘er’。 ?! Y% x, J( E
re.findall(r'.s', 'Apple! This Is an Apple!'): |$ {4 Z. w _' d4 f! N7 r1 e
Out[37]: ['is', 'Is']6 {8 [. O7 Z1 `- _4 C& B: A9 S* H
9 k- I. Y o3 N" T) O L o
re.findall(r'\w{2}', '09 8? 7w c_ 9q p@') # 匹配任意数字字母下划线的组合,但必须是两次, [7 D& ~3 K4 m/ Y
Out[38]: ['09', '7w', 'c_', '9q']
7 W1 Y- B3 R3 E: N- \' [# J& j * _0 W1 x% C y+ p
re.findall(r'\w\W\B', '09 8? 7w c_ 9q p@') # 匹配的是两个字符串,前一个是任意数字字母下划线(\W),后一个不是(\W)
- ~' \, V2 l# J8 Y: l Out[39]: ['8?', 'p@']
# L. y* \! i* C7 p1 c
( _% E$ M% Z! m+ l re.findall(r'.\s.', 'Constant dropping wears the stone.')' s& J) I: A# O) D3 Z+ C
Out[40]: ['t d', 'g w', 's t', 'e s']
& s" U4 T8 g3 W; V* o1 G! b
* G2 S+ K, j) t& V re.findall(r'上海市(.{2,3}区)(.{2,3}路)(\d+号)',
1 G$ l" Q# M& f- z# t '上海市黄浦区方浜中路249号 上海市宝山区密山路5号')
0 J+ o$ w @1 W # \: |! E, x. J+ V6 f' c
Out[41]: [('黄浦区', '方浜中路', '249号'), ('宝山区', '密山路', '5号')]" c4 N4 Z9 a" }# r0 ?" ]; t/ n
. g5 ]6 ? \% D! k5 M 1
$ S; V% s. B4 i* G, y+ r 2% w; n- ]4 J+ [2 L
3
! Q1 `% }- b1 H9 v) B 4
3 Q- k& _$ E- ^6 T' D 5
# e! ?+ o3 Y% w2 |0 U! t( v 6
1 i) [. `# B3 v- l9 M 79 Y' T5 |6 o' j! l' X1 t/ Y7 P
8. Q0 V4 X; o8 D d% D
9* D1 a0 O7 q, S- a6 _4 @0 f, ~
10
5 t& o5 x$ x A; } 11
4 N+ J! d4 z* q 12, S; u: T$ h! ]8 E6 ~) y
136 r% q; Y" z- C. O
14# x9 I. X, P" V& [
15
: c q0 a3 F: x0 _2 J2 }4 J' L 16% s) f# n) a; @0 V9 x6 @8 e* U" `
8.3 文本处理的五类操作# o( g) z( R6 Q- Q, i: N. k
8.3.1 str.split 拆分
1 I7 f+ f. n. Y1 }% z8 Q str.split 能够把字符串的列进行拆分,其中第一个参数为正则表达式,可选参数包括从左到右的最大拆分次数 n ,是否展开为多个列 expand 。# \; n5 |9 p+ w
K$ D Y0 r) s. q' Q s = pd.Series(['上海市黄浦区方浜中路249号',9 L# `/ v ?6 }
'上海市宝山区密山路5号']); e3 B& @9 g3 p% w
4 }8 m# {; f0 D
2 R% w: V6 H9 s0 W" h s.str.split('[市区路]') # 每条结果为一行,相当于Series0 r3 s- z) r1 c. R# Y6 D" f
Out[43]:
" i! {1 {0 ^# e! u* R 0 [上海, 黄浦, 方浜中, 249号]
; t" f0 t# ?& o i7 ]8 @ 1 [上海, 宝山, 密山, 5号]6 ]; P E! Y% h% f y( q
dtype: object
% J' [) w3 ? V* w/ o2 N0 y
* c5 s! i' i; P$ | s.str.split('[市区路]', n=2, expand=True) # 结果分成多个列展示,结果相当于DataFrame% N6 V1 U7 m0 u! W$ x. c
Out[44]: 4 X% ?# \, Q0 X3 K9 j- u' q: @
0 1 27 _/ ]; X# M, p& [$ z
0 上海 黄浦 方浜中路249号/ U9 V0 |6 \: X7 Q; w' w
1 上海 宝山 密山路5号
7 h4 \0 S7 }. _) O7 I8 M 1- c" E, p: z( }
2
: |2 ~# J! u5 A# y3 c# M 3
4 l9 t! Q8 C1 o5 s% f1 Z 42 n! K3 l1 C ?3 S
5
9 y1 G4 Z6 B7 E, [# Q" m 61 d D; V( l1 l$ v
7; G9 n5 P6 H" a' B8 F! }
8$ `! Y6 Q1 _( p4 y
9: E7 \3 z- u% J+ N. K, |
10; |5 M2 w3 n* {) z
11
" F$ Q4 s' ]9 y& A6 J 12
5 h& P- F. u4 Y8 Y; P 13
! k8 L5 Z$ p: R8 |$ f/ Z 14
& D! l0 r* @- l$ \/ {# c0 h) O 15; f( T5 t7 V! O: I; {7 i, J( H2 C* o
类似的函数是 str.rsplit ,其区别在于使用 n 参数的时候是从右到左限制最大拆分次数。但是当前版本下 rsplit 因为 bug 而无法使用正则表达式进行分割:
! l' J' |$ B8 u. \ 0 o- c2 W8 @$ ~5 }; N$ ?
s.str.rsplit('[市区路]', n=2, expand=True)
) c. n3 A. t' ~: N8 z2 ] Out[45]: ) |6 |9 I( y2 W- n* d
0
: Y" Y- B6 y# r. D5 N$ B 0 上海市黄浦区方浜中路249号
5 w; S* u4 @7 W( O 1 上海市宝山区密山路5号
0 O: m3 k" F9 R- c% S4 N. o# O/ l 17 G- x: c* q4 g5 ~/ ?/ c
2& }0 T8 s: F, o) z- Y7 c
3
s( y# l/ k6 D7 l' t7 I 4
' ~5 y. }. r: ]$ j9 T 5
3 U8 w& V; E0 v 8.3.2 str.join 或 str.cat 合并7 C- x: i+ C* V3 X$ i/ ^
str.join 表示用某个连接符把 Series 中的字符串列表连接起来,如果列表中出现了非字符串元素则返回缺失值。
, q+ q: m6 ~1 @& p& q+ j. {: d6 { str.cat 用于合并两个序列,主要参数为:
' o4 D6 Q) S$ G sep:连接符、# V' w) Y! e! ~& _& o: v' u) {) R
join:连接形式默认为以索引为键的左连接
9 l* e w0 \; z: d B na_rep:缺失值替代符号
( L# W* j3 y3 g1 E! H s = pd.Series([['a','b'], [1, 'a'], [['a', 'b'], 'c']])* n- n" d0 [" U6 n! \
s.str.join('-')
% r: h$ C6 v7 a& o' A! ` Out[47]:
+ B# q) ] w% \( C, [ 0 a-b) X, ^. t/ w3 H* U1 m* B7 F
1 NaN
# i$ F( ?$ b v3 X 2 NaN
$ C" T6 F- C: E8 p* B/ o dtype: object
& n- v- u2 a7 x' z7 x 16 X* X% @9 a( c: K2 l
2
* r1 s4 u. W3 u( ^( u 3; o. ?" h) m& ?1 u3 o# P3 v& _: K
4
$ [( k" [9 }5 I6 ^( r9 M 50 c+ R) Z8 A% L! Q" \ l" l% }
6/ y6 I! g$ g! @2 E$ n7 W! D
74 f) W2 F; {; j3 S W
s1 = pd.Series(['a','b'])7 }; n# l; Y% G( a9 Y
s2 = pd.Series(['cat','dog'])) x/ g. P% q( z7 x X4 U
s1.str.cat(s2,sep='-')$ q$ t% v- |0 }% k( g1 j( z
Out[50]: " a: e6 E& K2 \1 `, C/ @
0 a-cat* k. V4 v# L( b5 I* f3 Z6 ^( N
1 b-dog8 s; m; O2 Q4 ^; s' x2 a" X
dtype: object
1 ^/ y! ~0 c6 T2 A$ M0 b: y. o q$ x6 Z) I( V4 r
s2.index = [1, 2]
3 b- R7 s$ k$ s' U) d7 q/ _ s1.str.cat(s2, sep='-', na_rep='?', join='outer'). J9 \4 Z. K, H" Y
Out[52]: 2 Y1 d+ Q. ~* T6 L- C" e, \
0 a-?
. l% t1 Y+ V* W% Y5 g 1 b-cat& A) m1 G G) q$ S: G1 ~
2 ?-dog' P0 e; A0 b0 A
dtype: object$ m. b H! G4 N
1
2 {% L2 w1 s$ e6 J2 b6 T( C 2
6 Y% h- M4 x# T( T( V- ] 34 d: Z1 ^# \( o1 T0 ^, O' G% H
4
8 G) l! x& B3 o8 A; n* L; p 5
* D$ x0 f- t6 j( b2 G9 } 6
+ R2 ^5 S+ q6 K+ D 7
$ ^) w4 J3 @$ A 8+ g/ [# c }, J. U3 z+ ?5 S* v# y
9
+ L [0 C; x$ C5 |" g 10
1 g; X# y K% L. ~; N, g+ c$ q 11
$ |$ ~& S6 x# }2 z" b6 d' S 12- `9 Q6 b( G" | Z
13& i. p9 m1 @% f V( {
14
" H1 h0 o% e% g1 p7 s1 K$ \- Y 15" U: Q6 D0 n) D& c$ G; T
8.3.3 匹配2 H( `, O% N2 Q* r! H
str.contains返回了每个字符串是否包含正则模式的布尔序列:
" i0 w( L; X% V4 V7 |* M$ e0 Y" O s = pd.Series(['my cat', 'he is fat', 'railway station'])
% o' N- x; K0 p s.str.contains('\s\wat')
9 T2 q6 ^$ ?$ I 2 t3 r$ T& S# O! Q" ~7 @" l) [! Z
0 True
, ]% b+ H& Z( f# B. M 1 True: k1 ^" O3 }: O7 ^' r
2 False
# _ w2 r* G5 Z( Y- J dtype: bool
$ v) x A: J; Q x) ?/ d 1
5 v$ L, N" E5 D* @; V' R8 U 2" A9 [; I1 f+ `% C. P' F. R; Q
3* O" |8 [# r3 T5 u0 E
4
" P4 M$ A. n5 U+ C: x+ i 5& C# Q& g: H* q% q U( ?1 D# B
6: a4 j( l9 d0 N) D% W( w
73 W' u2 Y5 Y) F7 }
str.startswith和str.endswith返回了每个字符串以给定模式为开始和结束的布尔序列,它们都不支持正则表达式:# Z& B+ X+ Y8 S6 _8 X, K5 a
s.str.startswith('my')' ]6 l( X* ?4 C
9 p2 z# F) x9 O
0 True% _" o3 C2 J( Y" ?0 H2 W
1 False
3 W4 u2 [" v$ [ D 2 False
- L* c' l' I& U$ [4 l$ p* g6 y Y dtype: bool
i& d$ s$ i1 O 1
! e" J' u9 k0 u 2
8 U" F; K6 F; n y8 Y 39 H, M+ y T) O4 @1 C q8 n I% b
4. F& |" T U, |: V
5
/ ~& T$ `9 R, G" ^ 6
' V5 S7 z, G- P4 O% E2 \7 W s.str.endswith('t')
8 c) T# q" D1 K/ V & P* ^# E5 e0 F# M H
0 True
$ [! _( b6 Q! q* ]- _; \; R 1 True- e1 b. t* a6 ^. Z/ F- W, G: d
2 False
2 y" h2 g5 x: A- |4 t# Q/ `" t dtype: bool
% m0 s: v- ~* ]) d; P 10 k7 n i7 t3 \+ I
23 N' E! E Q2 }* F( ]% e
3
6 o e2 a! }" Q o c 4) n4 z* v. I3 b% r; R1 E
53 V0 r, g, i, h$ n
6
6 Y" b# D0 Y) W. }6 }6 h% R+ T str.match可以用正则表达式来检测开始或结束字符串的模式,其返回了每个字符串起始处是否符合给定正则模式的布尔序列。当然,这些也能通过在str.contains的正则中使用^和$来实现。(貌似没有python里的search方法)
7 Y6 X- g4 x$ Y) P6 {4 F s.str.match('m|h')
5 b6 }- q2 f5 R) z7 d s.str.contains('^[m|h]') # 二者等价/ b% R# d6 Y W
, ]7 t+ s5 t. B6 r5 U. s9 V Y
0 True
! K; D; d. s5 c' y1 C+ n 1 True
6 `0 P( [: [ q6 s" x: O+ [ 2 False/ E7 X/ T; w6 }" j/ _ x. w8 C
dtype: bool
0 [ i; _$ e6 ^2 ^0 T 1
\+ a- @9 N Z' ~ C( c% _" Q+ r 2
$ K7 T1 A6 C+ ? 3$ _: Q" Y- m. Z% E1 r/ j
4$ `3 a+ k; `4 v4 C* e: ?! ]
5
; L2 ]. P1 G4 ?: ~% `" O 63 `( P8 w1 i4 M! m2 D
7' x( C% a; Z4 t, |7 ]+ {/ b
s.str[::-1].str.match('ta[f|g]|n') # 反转后匹配5 ]4 _- p* O6 |+ T( `
s.str.contains('[f|g]at|n$') # 二者等价- r8 s+ w/ ?; d& E. E5 r
1 W6 t( a+ B& Z# z2 t
0 False) b4 y% a1 h" X- G2 f0 O
1 True/ c, [1 d' x V- k Z! N) r
2 True$ y3 W; P n; @" C4 h5 T
dtype: bool F& ~( l' v5 j& k; @
1
; M) H+ S% W$ ?6 } 2
, D3 X7 G, l6 ?9 x; T2 O8 w 31 X* n! m0 R6 c: g( ?
4
$ K9 t1 V) L* b- P+ A 5
, }) O. H* @# [$ o# c* d7 t) }& a' b 6, X& ?+ R+ x! [8 b3 r, Y
7
& j3 g, [; C$ |$ k9 u( g; @ S( F str.find与str.rfind返回索引的匹配函数,其分别返回从左到右和从右到左第一次匹配的位置的索引,未找到则返回-1。需要注意的是这两个函数不支持正则匹配,只能用于字符子串的匹配:! \. G& x# w4 h0 u# |2 R" c
s = pd.Series(['This is an apple. That is not an apple.']): D, `+ ?4 q+ ?% I0 t2 k
9 s4 t, t! E/ z, K" _ N8 V
s.str.find('apple')6 l2 Q) g( l; W
Out[62]: 5 F, o+ r7 L- w
0 117 h* B% f. _+ l9 `" K/ k" B7 w
dtype: int64
1 O" }7 O) m7 {2 M) a( U( v% e1 `+ U( x
" E4 F6 C6 ^2 g s.str.rfind('apple')$ d0 C7 j9 U* v. t* t% B
Out[63]: 3 }8 x8 T, b, ]1 |+ s
0 33# P: x1 \1 K, ~! z# {6 W7 K2 ^
dtype: int64
5 q; X# ]. ^( X# J9 M0 E. x 1
! m) \/ b+ w5 S# { 2# H: y/ j: V# L+ h" \! e& p" V! h
3
* o0 q1 r3 |: T! }+ ]$ } 4
! L# w* W8 A3 ?& e$ m0 \( q 5
2 h+ ~1 d2 G1 v 6
+ c, V4 g5 Y$ E0 l' r" J 7- s* j. e Q5 |) @; D
8
j+ A" H/ @8 d" g 9# _7 n \ ~( Q& c+ o
100 s* ] c& X: f: B7 m, ?1 u
11
3 u |1 ^ z; a6 k& u 替换
! ?* G f1 N" T! s8 C str.replace和replace并不是一个函数,在使用字符串替换时应当使用前者。5 B8 a- v% w; V Z0 H8 y" f
s = pd.Series(['a_1_b','c_?']), `9 Y) p9 x' S# k
# regex默认为True,表示是正则模式,否则第一个参数内容表示是单纯的字符串,也就是匹配字符串\d|\?
) `+ Q3 F8 s& X" O9 C s.str.replace('\d|\?', 'new', regex=True)
$ d3 k! d8 v1 _/ H6 o
* k- j- f8 z* G- v9 l, f 0 a_new_b
9 ^# Y9 {1 |0 `5 Y# n: y 1 c_new# y! J& o8 d I" v7 U" W/ D' y$ B# w
dtype: object
5 r$ v, H8 {& u# {, I s2 E 1
- }% E" }: {2 G# p 2
+ W7 D2 E ?4 s5 y2 N- A) d3 o& G 3
- U% e8 u- z' V2 ^ 44 h% ?. s: t5 M; m5 X2 f
5
5 \" r/ E8 E& k7 X0 H 6$ W3 Q @* w# I4 z
7
1 C1 |3 W, f8 ^% Q. Y9 ~ 当需要对不同部分进行有差别的替换时,可以利用子组的方法,并且此时可以通过传入自定义的替换函数来分别进行处理,注意group(k)代表匹配到的第k个子组(圆括号之间的内容):( ^4 x% p" Y$ n. M2 h
5 F6 L+ I8 G; O# \. |5 b" b
s = pd.Series(['上海市黄浦区方浜中路249号',2 @/ J5 \+ m F3 ^$ ^
'上海市宝山区密山路5号',
7 W- S" |: f* ~' l& k0 T: [5 } '北京市昌平区北农路2号'])
( w n1 O+ V4 z" b# O pat = '(\w+市)(\w+区)(\w+路)(\d+号)'
8 i( ?5 j, O B. l8 z1 b city = {'上海市': 'Shanghai', '北京市': 'Beijing'}' A2 N% \2 o6 Y
district = {'昌平区': 'CP District', Y- X- z2 Y2 G7 C9 w0 V' j
'黄浦区': 'HP District'," d4 Y7 h2 c2 `% u
'宝山区': 'BS District'}
) z% B0 o, X& _9 @+ a; O# _0 T road = {'方浜中路': 'Mid Fangbin Road',9 V% K! t% E1 |) e7 W* G
'密山路': 'Mishan Road',
/ g4 q2 e: {( K5 a' l '北农路': 'Beinong Road'}
9 Q( b4 J. [0 s' n- O- G% U" ` def my_func(m):$ U9 e4 T. n* ?" j& N; u8 W
str_city = city[m.group(1)]
, V% ` l% r8 _! E0 [6 Y! Q str_district = district[m.group(2)]
6 v6 T. }& a, G" p& H str_road = road[m.group(3)]
2 a3 v1 H2 r1 m" |0 @ str_no = 'No. ' + m.group(4)[:-1]( v: J0 x2 F* t9 p) F1 `: b
return ' '.join([str_city,6 h ` D* h' t- o; z
str_district,' [+ P6 B( ?0 I. h# Z9 ]! d* L) V
str_road,
6 K4 U3 L/ t# ~ v: d: Y# B str_no])
, w: ~- t1 ?- F! ^) G s.str.replace(pat, my_func, regex=True)% s' |9 o2 v% A; L
5 t7 Z ?. o# X m1 O! ~
14 o8 i; ?3 t( V& E, g
2
1 K5 P) @" e' `" X% S- P 30 \& D9 n( _ ^
4
6 @+ n! ?/ L; t. j! e" ` 5' i+ U1 c) ]" X3 C* T
6! w2 @6 i( G5 z# D. m
7$ X; X0 s% z9 S, K4 o+ O3 D
89 v s9 M- `2 l. h) I6 O
9* ]2 i1 k0 a# q" @: a
10) v3 J9 o! R* _% [- U6 n
11
$ B5 n; a1 O. C 12
1 X; ~* a( O/ g5 p: Y4 b3 t1 n 130 D$ H* I/ G6 f+ r+ V" o6 L
14
f- H3 Q8 j' I% Q/ v* F. Z 15
" v7 I) l+ K( @7 Z3 O/ F- \+ ] 16! {" C: f; }% l l, T% r
17$ I- _$ q# |% x, w
18
) c3 l. a# Z7 m" _8 E+ @* B, v 19
8 W4 F* p: ~& s, V# A* ~ 20
9 H. W- t6 B. r0 R; w 21
" s# ~7 [- I# S; u( [( | 0 Shanghai HP District Mid Fangbin Road No. 249
: @/ Y& y4 }6 R: `3 u: o 1 Shanghai BS District Mishan Road No. 5# Q$ H! Q1 E2 f5 i0 Z
2 Beijing CP District Beinong Road No. 2' x2 w9 w2 w3 d( z* x8 D
dtype: object" U8 `: s/ V; Y0 V7 L
1
! _. N. u+ M# {% B0 k! ?/ ~# b 2
9 \+ C- a: k! J* i Z! i) d 3
: x& Y& K/ i; Y9 ~0 _% z! r( T 45 e5 x$ C- ]+ s4 Q
这里的数字标识并不直观,可以使用命名子组更加清晰地写出子组代表的含义: r! N( A- K" _% F8 K, Z
2 ^& C" ?0 p/ d# Z: @$ K# v # 将各个子组进行命名/ q' R, j/ [( w. K1 l
pat = '(?P<市名>\w+市)(?P<区名>\w+区)(?P<路名>\w+路)(?P<编号>\d+号)'
6 u ?( d/ H& C def my_func(m):
; P# Y/ O; Y" z" F5 N. Z. w. P str_city = city[m.group('市名')]3 f9 k7 t% W- g) }+ s5 r0 `8 S
str_district = district[m.group('区名')]: T5 l" Z5 _/ [, {9 C" X+ Z
str_road = road[m.group('路名')], ]9 j" g( r4 h/ P1 s Z( J
str_no = 'No. ' + m.group('编号')[:-1]
9 n; }! a' M3 g! b ] return ' '.join([str_city,) Q1 t: y1 q1 m6 f9 N9 a
str_district,
& M0 L& D# l; ~/ H7 {4 }7 l str_road,* c5 w& b- S4 @4 }( z
str_no])
+ F7 n# N/ S7 ?: E' L s.str.replace(pat, my_func, regex=True)! \2 n' f. N+ I
1
/ M2 \( D1 Y+ @8 j, c 27 k* A' i4 `: c8 @
3
8 t- s3 v( ^: J4 z/ g$ m$ f, t 4
9 D7 u* @. \! R: B% [2 | 5
& Q. U6 d/ h: i 6
; n. P) e2 R# O4 @: I; D 7& Q h6 J2 g$ W/ l# ?7 Y6 m
8
9 J( \' R' S3 i4 P, L9 u 9! \# S* T& ?, W$ O. }
10& S+ g, C" ?5 L3 T/ Z" n
11
" U5 ^, y+ t& H$ e1 `# ?# T* }8 E2 b 12
6 D$ V% S# \# O/ Z8 V% x% \ 0 Shanghai HP District Mid Fangbin Road No. 249: G. @7 h) ^) }" X' u
1 Shanghai BS District Mishan Road No. 5
. A2 G+ F3 b- y& A8 U" C- Z) r 2 Beijing CP District Beinong Road No. 2
7 V3 d" i1 {; O7 |/ y dtype: object
5 I8 E1 B+ Z- f" P 1
6 C1 H- f4 G9 }# r- W4 g. r 2
6 k1 T: Y) e" U8 s$ o) Y 3/ q. r P1 m3 v4 {4 H
4
' g. L5 K! _& t9 c- v! [5 H, k( \ 这里虽然看起来有些繁杂,但是实际数据处理中对应的替换,一般都会通过代码来获取数据从而构造字典映射,在具体写法上会简洁的多。
& S) c+ ~# X) t
% n F2 `, c6 Q 8.3.5 提取! Y' B6 l1 W$ k' k8 G
str.extract进行提取:提取既可以认为是一种返回具体元素值(而不是布尔值或元素对应的索引位置)的匹配操作,也可以认为是一种特殊的拆分操作。前面提到的str.split例子中会把分隔符去除,这并不是用户想要的效果,这时候就可以用str.extract进行提取:: D8 `' S$ z4 ^
s.str.split('[市区路]')
1 D4 z; s) S8 k; J/ q5 j Out[43]: * C2 E3 N% T3 n
0 [上海, 黄浦, 方浜中, 249号]% i0 k/ e0 |6 b! }3 D+ m4 M; w U; j. w
1 [上海, 宝山, 密山, 5号] V) c+ h3 g8 z7 l3 A
dtype: object
" o1 {" H _3 l0 X
: _" P. {1 {- D. Z- T1 R7 R pat = '(\w+市)(\w+区)(\w+路)(\d+号)'
( |% S$ [0 `& ]" ? ` s.str.extract(pat)- v3 j* y: ~7 ?
Out[78]:
2 i& b i* l/ \, h4 I' c4 ?3 O 0 1 2 3
T P! ^% M' f 0 上海市 黄浦区 方浜中路 249号% _5 Z9 G' E6 v
1 上海市 宝山区 密山路 5号! ~: d, L, J4 c4 {: @
2 北京市 昌平区 北农路 2号
! E, p9 }$ N+ T 1
% W2 P' s9 _% S2 o5 F" K 2: h/ k& D) L' ?4 f
3
4 ~/ Q0 b: @5 X5 v) f 4$ B R# q) V) X' R3 h( m; r
5& w m8 _' v9 k; D; E
66 Z* _2 r" Q7 L1 z
76 P E8 w' L- R5 ]0 c
8/ P1 `' q4 S. {* | a- N! k
9 P! c! O y5 e0 K' s
10' q& F+ `# {7 W# N; G2 a
11
& p! w2 C2 E3 v 12
& G- H/ |/ \' {. R4 y; Y4 o 13
/ Y# n# P$ m; a- \! G, k9 w 通过子组的命名,可以直接对新生成DataFrame的列命名:
; U( _7 b8 T, T1 }' `9 Z% P 6 e/ l/ `, `$ B- x3 E. S
pat = '(?P<市名>\w+市)(?P<区名>\w+区)(?P<路名>\w+路)(?P<编号>\d+号)'1 V, ?, E, [( @% @# H- A) A5 @% z
s.str.extract(pat)
! L! R' k# d3 T1 W Out[79]:
' _- k1 o% h- z 市名 区名 路名 编号7 w, l' m! K7 I2 m
0 上海市 黄浦区 方浜中路 249号
5 J9 V E8 I) Z7 U8 F0 M 1 上海市 宝山区 密山路 5号
- j! Q3 V+ o: u$ P5 L8 `. d" ] 2 北京市 昌平区 北农路 2号
- p9 W3 F3 r% l5 P P1 b" o 1
- L4 D2 f7 F+ }- y9 m3 m 2" i, S/ i1 ?( y' y
35 c' f0 U# O* Y/ }
4; O2 p: m w% h6 ~0 p- K# j
5. A, `! N, \% I, n+ l: F) P
6
; `* q G# m- E8 Y 7 \' z; d: k) N! S, N; Q& z
str.extractall:不同于str.extract只匹配一次,它会把所有符合条件的模式全部匹配出来,如果存在多个结果,则以多级索引的方式存储:
8 b% u% s: M8 y4 O3 V; W" K s = pd.Series(['A135T15,A26S5','B674S2,B25T6'], index = ['my_A','my_B'])* N6 m8 w; u# [) o1 o3 R
pat = '[A|B](\d+)[T|S](\d+)') B: Q5 F+ ]6 C+ S3 l
s.str.extractall(pat)
& e/ G _1 n& d Out[83]:
+ U" j U% {( Z 0 1
- \' m/ w' }; n1 M7 z" o- x: q match 0 K: g/ o* W3 M: u
my_A 0 135 15
* j# _; n( m, P& M2 p 1 26 53 d* n z' p, b2 `9 M
my_B 0 674 2" b% h% S$ K5 U. h. B
1 25 6% O% y4 U% n4 R+ q# u1 u
1
& n0 G& f' c; D0 i- ]) B 23 S% B& d( s. s- R
3/ I% g* U+ X) R
4) Q" V' f4 \7 y0 M$ t; s
52 @. q: r- Y/ X; V/ m
6
- w# b! Q0 [1 N X* P# e. o 7
& b1 x9 Q+ L9 H! O/ E 8
$ c, D* D5 {1 q% P 9
6 G7 t' s: V9 \1 @ 10/ L% I6 C1 h; a
pat_with_name = '[A|B](?P<name1>\d+)[T|S](?P<name2>\d+)'
8 ]9 y# |- f' X7 P% Y s.str.extractall(pat_with_name)
4 s0 `: N; P. Z7 @+ G; i Out[84]: # T: @# A7 v9 Q4 `! H7 j% n
name1 name2
) D$ G' }. b O match w8 d9 N- U% c" `; y5 Z# S
my_A 0 135 15" F( k1 ~" |! A6 e
1 26 5
- A9 B2 m0 _! @1 W9 C. p my_B 0 674 2$ h2 ~. x% W7 Y: O& r
1 25 6
' n& n3 x% _; N4 M4 m 1 b! D* X& x! P8 T) x) }
2
3 C N4 F: Z, ^+ z 3
, C$ z6 g$ \/ u* M& c7 Q0 L! q: s 4" e3 H7 [( c1 n6 J. _ j
5" F) |8 v9 p& m! o; V" |7 v7 y% N
6
9 o4 m1 u1 R+ E+ d: S' Y 7
& j ^1 Q @! B+ P 8+ y4 U+ ~- v7 C7 l' }$ @
9- u+ f6 @5 R; z B7 {
str.findall:功能类似于str.extractall,区别在于前者把结果存入列表中,而后者处理为多级索引,每个行只对应一组匹配,而不是把所有匹配组合构成列表。" q+ i! B1 f% z
s.str.findall(pat)
* T% f n3 ?, Z- ^, A" f4 M 1
8 b* y4 @! U, R2 }5 \ my_A [(135, 15), (26, 5)]& K. w; F- d$ y" o9 b( G
my_B [(674, 2), (25, 6)]7 t) L) x" S! I
dtype: object
2 Q: _9 r: W( y9 P: y 16 A; D' U" a+ b2 r% Z& h, _) ^. n& E
2 x$ O+ K; U; A; S8 v% {: d
3 C5 M; B' [9 I! }/ u; X& @0 G
8.4、常用字符串函数
. S: ^+ E. ~1 h! {9 X 除了上述介绍的五类字符串操作有关的函数之外,str对象上还定义了一些实用的其他方法,在此进行介绍。
# K) I4 l9 _# r7 S! r4 m' v/ d / \% r/ [8 ]* [2 m! E f7 s
8.4.1 字母型函数
) n1 p" l* r5 @7 U( R. X5 x# J upper, lower, title, capitalize, swapcase这五个函数主要用于字母的大小写转化,从下面的例子中就容易领会其功能:. E; ]3 J# k) l# [1 r. p
$ V9 I- e- J: g s = pd.Series(['lower', 'CAPITALS', 'this is a sentence', 'SwApCaSe'])
8 E! g' d: \/ L
: H' T' \, M/ @7 ~ s.str.upper()
$ ^% d! M6 u' X8 c Out[87]: . s, `' Q6 D6 n5 b- L3 u; A
0 LOWER9 r1 X- Z% z9 `! R3 Z6 m6 b
1 CAPITALS- ?* M- k) D+ ?* G& y
2 THIS IS A SENTENCE
6 i3 r1 h7 X% R) X: Y, B 3 SWAPCASE1 G7 T$ `7 {6 C; M% O
dtype: object
8 I \' N5 z4 r' T# }; k' D! a) W8 ]
9 s+ _6 d6 Z7 \: T* v% F0 w s.str.lower()
+ l3 v& w% V: [% F/ ?% K/ n0 k J Out[88]:
# x7 t: g+ ^. e% g 0 lower
8 a1 |$ T; w4 k$ R% D1 G8 c 1 capitals
4 z; r' z$ g5 ` 2 this is a sentence8 I4 ~7 P" J k; U5 K8 [" C
3 swapcase
% q1 S3 ~; _0 ? dtype: object
# [2 [( b* F4 @
. V$ v V5 X5 x1 L. C7 h2 k3 L/ U s.str.title() # 首字母大写- A( }. L( p# h1 k* F5 ~
Out[89]:
& {* ^' P5 t9 ~2 C& { 0 Lower
1 D# _9 e. _9 G, Z 1 Capitals1 {0 A" l* B; T8 j0 r" B* g: }
2 This Is A Sentence
% L- |- ~* l5 ~% h 3 Swapcase4 g/ b3 [ d2 b2 b; _
dtype: object- i- D5 U1 Q8 D) Q
& @ o$ ~& u7 s' b: k0 {
s.str.capitalize() # 句首大写/ ^) }# m! c# p; K5 R
Out[90]: # J$ V- R1 [4 P+ h" R
0 Lower
; G/ T& ]9 }- q" r. v 1 Capitals# S/ e X- Z0 K2 R
2 This is a sentence; O5 k& |' W) S/ u
3 Swapcase* v$ B3 ~3 b* U c C
dtype: object
" u- X: `5 F/ k) i! A' P1 M: o+ y 8 E% }' _* p) J4 d. n# ?! Z
s.str.swapcase() # 将大写转换为小写,将小写转换为大写。: w, x. {( m! t; l% N* y
Out[91]: ( f, p# Q7 T6 u) q9 E' q, C
0 LOWER
, i5 R) U+ t7 J 1 capitals
! G2 c4 P6 u. N# _3 o" {3 M 2 THIS IS A SENTENCE* ^: f" U1 W! P6 t* {& s
3 sWaPcAsE1 _. \4 z: i' u! z
dtype: object
# |' p: s v8 A( d
7 j1 t2 o5 s* n4 { s.str.casefold() # 去除字符串中所有大小写区别" M( u4 n+ W, N) N6 t# H
7 J1 C# V5 B7 U9 o
0 lower
/ `# @7 Z# V: l7 ` 1 capitals
' C1 ^8 h# O+ P [ 2 this is a sentence
. t; h' n! J- ^/ A' V 3 swapcase9 I! b, E8 }7 W
; Z2 o `3 l. X
1( m4 D# V, Z9 l" {2 k
2/ N Y. j& c! C: q% _7 w
3
) O. W' ]! b0 N$ E- x- S 43 `9 Z" M1 u0 |
5! P8 c4 \6 W# X2 k% f3 c
65 \, U! F' d1 F$ P, G1 b0 e# Q
7+ B- G2 ^; ?4 j$ z. _: A7 [
8. U$ C* v, G) ~
9
) Z B1 V' o7 w/ k. h8 N 10/ m( _7 u- R+ |0 i% H
11/ S/ L: u1 U1 O1 K+ r4 }" g$ S
12
+ D1 S/ N: m; F, p 136 V! r3 ?: a9 ~4 L% Y0 Q
14
" j2 h- g, V% r' x+ ]1 L 15
5 t q0 x0 u7 j" q% n2 E& M: ~ 16
& m* B# a6 [- Q! ^ 17
* y$ J! V6 @9 v; _( ~9 `) K6 D 18& @& ^, r/ b/ V/ M. Y4 w! K
19. W6 C% d8 m! d
20$ W6 W( j* o$ D
21$ j! z* C% v" u0 x- F: T& {4 p
229 b f% J6 i+ \# }. i
23
2 o/ y1 m* _4 t* q; Y, s8 { 24
v+ ^- J% R- e- a4 E 25 K$ w9 a& a* ~8 E4 _+ @2 O7 w
26
- {1 ~& H0 Z+ S0 P. i$ U 27: l* i4 y5 u0 G( n
285 j, n* B1 Z; k
294 q' y) k5 n. c+ W" _. i
30
' u4 }7 h b. [! Z" s 31
* v* j. c4 ?2 a 322 K6 J* J v) @. K, [* C9 r& y
33: O E3 Q) ]7 ]6 M9 F4 B6 U3 E( _
34
( {, @6 u0 t( r- Q' d: Z' [ 35
! Q& ~, c% u% `2 _: a 36
% ^! @& s+ c1 I$ C 37
9 \1 j5 G5 z% t 386 f$ {; c8 \1 u4 c) G. v
39! Y$ _- t8 i$ |- q/ X4 L% s
40
2 ] J9 d h9 F) R1 U9 r2 B 419 B8 Q4 Z5 v) b
42$ W2 o% I* B6 ~0 _8 Q
431 H% P3 x$ u- Y M0 g( ^4 V
44. @ M: g7 t: s( W) L$ s
45/ g/ a; B, \3 `3 j0 e+ M; M
46
8 G0 x. Y# |: X! b4 N: ~% c& c 47# O8 ^/ i5 X8 y0 |
48) }* ?3 R6 ^; t* v" P% [& w7 X
8.4.2 数值型函数
# L0 U* {5 ?( Z' d6 }6 r% d+ g# D 这里着重需要介绍的是pd.to_numeric方法,它虽然不是str对象上的方法,但是能够对字符格式的数值进行快速转换和筛选。其主要参数包括:
0 [1 r t# |" m4 }+ |. x ; m, A* x+ O4 }8 _! j V
errors:非数值的处理模式。对于不能转换为数值的有三种errors选项:# I4 r; p( K: q$ @
raise:直接报错,默认选项& j. G9 _! f3 c3 k8 J
coerce:设为缺失值
0 `9 u) M& X# ?3 Z; s: h; q ignore:保持原来的字符串。* a' Z# m. z% N0 O! { O! b8 F8 ~
downcast:转换类型,转成 ‘integer’, ‘signed’, ‘unsigned’, 或 ‘float’的最小dtype。比如可以转成float32就不会转成float64。. a9 x- F( Z5 S) W/ `. F
s = pd.Series(['1', '2.2', '2e', '??', '-2.1', '0'])+ I- l0 L+ t F7 z. q
% }+ s9 e. S6 o) }: d
pd.to_numeric(s, errors='ignore')
Z6 _0 U5 h7 ^+ [- i( _# L Out[93]:
: i! K% l+ F4 D9 P! b( N% |& K) ? 0 16 l$ m# Z, \( y- U- x1 ^/ K
1 2.2: W2 `! B" ^# x1 r: ?& h
2 2e
6 b, \% z, _0 C7 v+ d 3 ??( A" d$ n! o6 v! N) h( k* A- |
4 -2.12 ~ S$ c/ L, r/ \; y8 G( j
5 0' L+ W3 Y/ E# p, L! z
dtype: object
5 _+ P5 E2 x1 e. b8 w+ r 5 o! t# o" ]. I3 S% Y4 G- y
pd.to_numeric(s, errors='coerce') R! F- c- j- ~: B8 d, e
Out[94]:
; q1 i) S3 T$ y% n) j+ H 0 1.0
p7 Y# _3 a% d# R0 N$ F/ E# l 1 2.2, L/ P& c, B/ R3 r8 I8 x# u8 Q+ U
2 NaN
. U& s( o$ g" Q |- l! z( h- ] 3 NaN+ h- r' u" N, Y- C+ k& x9 G
4 -2.10 W F- @/ ^; x2 v' x
5 0.0% @+ a/ i, |+ h4 X6 g/ l: Y
dtype: float643 V- j% Y' Y/ k% P
8 F) a0 r7 n$ Q g6 r, K# D
1% l! {, M! i3 u7 o7 i! I
2; p9 `6 ~: N/ p. n% p# [# _
37 k( h4 j# m6 \: K8 E
4' ~! P# K P [1 ]" D
5
8 K8 V1 U" {: ]/ W/ B 6: M) |3 o3 H) p' W
70 Y" K+ b; Y# T* ~4 h; @
8
) K# w, D. G+ W& ]: f 9; w- |3 R7 W& x
10
- \, ]) D, U$ f2 g$ S; k8 \9 w4 n 11
' B! R7 y: |! y3 G 12
1 g& {+ i) `- _9 q- t) V/ b( a% b 13. _& {# T) D: E% A* A% S i# ]
14( y! f9 y% N+ M& H$ D
15 j0 N4 l8 x9 P% z
16+ l$ g1 _+ Y3 f
17
4 E/ e' ^' F6 ]# B \ 18
- e$ H* H( [& r( p( j( ^5 v 19
2 D& {0 K) r d! E/ `& [6 R% | 20+ W3 O+ L$ F; Q
21
( b% T( t3 F* x3 q; F' x 在数据清洗时,可以利用coerce的设定,快速查看非数值型的行:
2 T% X: P/ S n' x) D
( l$ j- ?+ l$ r7 W6 X* ]0 j s[pd.to_numeric(s, errors='coerce').isna()]& ]5 g$ ?# Q: a' Q' h" g
Out[95]: 1 @- m% d; D" T
2 2e
2 _/ b3 }8 {" Z! x5 r 3 ??2 b; f$ B# i, C/ j+ }. u. a
dtype: object
; @8 i6 l I/ B+ m) Q' w+ x* l) _- e 1
1 N& z5 h: I' k0 ~ 2
% l- r5 R. ~0 c9 b! `$ p* \ 3
2 w; X) M, R) X1 R. i2 Q% r' ?0 j/ u 4
$ C& q4 K! S; Q$ a 5
! C, I* l1 ^$ A+ M9 W 8.4.3 统计型函数; o2 t; z* |6 m& `
count和len的作用分别是返回出现正则模式的次数和字符串的长度:
0 Y, A/ ^: _; n ; c6 |% h4 u- M- T$ U
s = pd.Series(['cat rat fat at', 'get feed sheet heat'])% u# c) P+ o6 k3 L3 ]
: u! H' k6 T1 T& n- z3 c6 g# _ s.str.count('[r|f]at|ee') # |左右两种子串都匹配了两次
* d3 j& W8 X Z3 ~. Z; r8 m Out[97]:
9 e$ T& Y a* X, g! f' R& g' G 0 2
6 T" G$ |$ d' R: t 1 2
' f, r+ a- l& b3 @ F dtype: int64
1 I0 e9 v- i9 ~; }# {
2 Y: O1 x0 v- ~- q8 H; r3 l s.str.len()7 i5 l1 y& Z/ T
Out[98]: 8 M0 o* D3 v m4 P' e+ |% V
0 142 _! [+ M) o* K/ M# T: x: ~
1 19" {0 n+ z9 y5 t" f) ]* \" e! j3 n
dtype: int64$ I" W1 C" v, u" n
1. s3 H) r2 f# b- [
2' ?" H& B) @, a2 L2 l
3
+ x% F8 e' y4 G0 z4 B 4
' v% j2 o3 F5 m1 g 5! f. i! t, Z9 m0 `( W
6& n- k; W. A2 S
7, a& t& H. ^2 ]; \
8
2 u2 c5 t/ W6 {. \+ x+ p+ z& [1 q" ^: C 9
9 c" b) \: A6 K5 B) O. D. Y 10* M c; u1 a) W! v8 n
11
8 B$ V6 ^1 G" S# U8 {7 g 120 Q* ~; J5 d% L
13
9 K) e9 B# P ]9 ]$ n* M 8.4.4 格式型函数" q( b- `' ]+ m/ }( i) U7 E* \7 K
格式型函数主要分为两类,第一种是除空型,第二种是填充型。其中,第一类函数一共有三种,它们分别是strip, rstrip, lstrip,分别代表去除两侧空格、右侧空格和左侧空格。这些函数在数据清洗时是有用的,特别是列名含有非法空格的时候。
' a3 Z9 n3 E' _; v T4 E
3 E$ F& B e1 R7 d1 X my_index = pd.Index([' col1', 'col2 ', ' col3 '])
/ W" c2 m1 }) ? ' ]8 y# |1 C% w, Q% W2 e; L
my_index.str.strip().str.len()
: T- @1 }. K' G+ v. r Out[100]: Int64Index([4, 4, 4], dtype='int64')
7 w7 f# E6 K5 j& k H# u$ z
5 l0 ]" y. ~* ~ my_index.str.rstrip().str.len()
0 p5 t/ E' J. l: \4 ? Out[101]: Int64Index([5, 4, 5], dtype='int64')$ L; T, N0 Z h Y
& D6 i$ `- K4 [5 y8 r; f5 q6 u4 _. B my_index.str.lstrip().str.len()) I8 F; ?; s5 b' l2 [% Q7 B
Out[102]: Int64Index([4, 5, 5], dtype='int64')1 u1 Z: g# k; O; I* ^
1
3 e$ d4 L1 y+ c4 a5 d$ ? 2& U h! t3 h, |5 g" r
34 S7 P0 P' j2 @* S. T5 b% J& H) I
4- h0 X L3 a3 ?5 @& d# [
5
+ ^ I y' j, z- |% D+ [$ t9 k 6
& J- T; A0 u/ t3 M3 e 7 ?6 m. Q# ]; E' P& @
81 s+ c7 z9 y1 a v/ L5 [8 b* |: [
9( `& O. y7 z, l. U, X
10
# K( @$ A7 p- ~3 U0 D* T# n 对于填充型函数而言,pad是最灵活的,它可以选定字符串长度、填充的方向和填充内容:
9 Q! a4 e% a( M8 n5 `" u' r" E7 c 8 b' G$ h( ]: R; ]3 v
s = pd.Series(['a','b','c'])
, C3 A1 g1 F1 f* B
% O& x# W* |: E |) w2 T s.str.pad(5,'left','*')' s! n8 O' ~6 A6 K( C% D5 y
Out[104]:
) D T# O2 p% ?+ R" d5 Z9 E% u9 Z 0 ****a
$ |' h5 G$ c/ _, {; h+ Z$ @ 1 ****b' G" Y' C$ D3 C! H
2 ****c2 y, T/ w- J- Q+ k2 g
dtype: object4 i5 I3 V. e9 T' {2 N) w
( M) o; F3 f: t* D4 B; o
s.str.pad(5,'right','*')
J- u3 t% N# ^0 a Out[105]: ' \% t, x; U0 i/ u* N1 s& u
0 a****; `8 j/ o- l# X$ [
1 b****
) c$ W# s# ~! D! R 2 c****) j5 } ?8 F& R" \( X0 w8 @: G3 ?
dtype: object7 J3 T4 ]9 J# t) z# X2 Y: L
. t6 @1 c/ a7 _ s.str.pad(5,'both','*')
+ L7 I5 s2 G( T Out[106]:
J# ^2 C: |$ L: Q 0 **a**+ N% x$ U: U$ G1 b% J4 j8 g; V, ~
1 **b**/ ?, ~. r8 ^% F! e
2 **c**3 K9 W$ E1 ]& D7 [0 B) v
dtype: object
0 g9 x6 L" |0 @
5 w! {8 s+ e+ I2 w1 r+ D a$ C 1: Y# Z* b1 j4 X0 r
2" H, w }5 A, t8 f! u2 L" D
3( ~2 r' p" O5 a$ w4 Y" c
4 M6 Y6 D0 M* t' |) ^
50 l. c) L1 B5 j# g3 {9 r6 G# k1 D
6
' B6 H$ G3 d" G# F+ v 74 i$ ~9 Y `" U/ |
8
7 d9 a |6 I8 l* Q; j 95 G5 D! U# u* p
10
4 ?. {# E& W/ w: ?9 P9 @1 \ 11- `# t6 X1 l! J$ @: [1 E& z+ g! a
12
* X: \) C& K5 A3 Z2 C0 Y2 T 13) A/ G$ J, f& I0 R9 }1 q
14; m! Z" x% O: @2 [9 K
15) R7 o6 `: A' M" Q# o Z$ m! ?* Z2 H
16
. m) ?/ b* I5 N$ v3 n$ X R 179 g( }$ P' S% [
18
) b! [# H4 a' M! h- B+ @4 ~5 B3 @ 19$ L8 F- E6 g! _( c6 `+ R
20/ @+ r7 t Y5 C1 @( X H( S9 J5 K
213 w- G( r: ~" N1 X- u, g/ b/ U. V
222 `* S+ x. Q) z: ~" c+ W, E
上述的三种情况可以分别用rjust, ljust, center来等效完成,需要注意ljust是指右侧填充而不是左侧填充:! N6 r* l ~% ?" x1 O
+ T, a! p3 W, D8 e- }* _0 t6 P: V s.str.rjust(5, '*')2 T" ~# j, Y, N( _% `5 l. t
Out[107]:
3 B* l3 n m- N5 e 0 ****a
7 L# m! t. z- j& c/ z 1 ****b
+ k+ @6 |; i1 ]* q: z% H ? 2 ****c6 }, b* `/ Y: M/ f0 Y
dtype: object
8 x! Q$ @/ h) {( n7 T/ c. J8 z % E* q, `! f$ ] C( j3 u* u& |
s.str.ljust(5, '*')
7 M( e; Z3 {/ P9 [$ U/ Y/ J Out[108]:
8 A4 D8 T8 e. s# B1 l8 g 0 a****0 e. f4 k0 L- }6 s& A. K
1 b****" D/ U; x+ D* X3 _; \" G& J: ^
2 c****0 n6 V- v$ w3 n8 y4 I: w
dtype: object" @$ x; n' u6 L& K% c! G( z
# s. j$ G, {% W" g s.str.center(5, '*')
* n) a6 K" N6 x+ g3 c A4 X& p Out[109]:
9 X5 j- q. C, M3 H" q& Q: I& i 0 **a**; p( j1 ?5 b9 k0 {, i2 K, g
1 **b**
, h( y3 ~* D7 g7 E* O3 R 2 **c**& V. i" Y @) L6 B5 ~ k* @
dtype: object7 ^7 J( M, l* i& B
" E. q. R9 `* g4 Z/ L, s# \* P6 c 1
$ d. X1 C" ]: n; c# b1 L 2
w9 X" L+ W8 _6 e 3
1 R3 \; @7 r" ^2 t1 | 42 C+ A' H2 T. t, R
5' ?9 ~+ b4 {- y
6
8 L1 H+ ~ L0 ?' W( q 76 O+ x& ?) }/ V9 o5 q
8
7 }& e8 p. } A! Z 9" C9 t* b7 Q6 F: l. ^* t
10
. ~, b* z, W8 b+ D7 t 11: T/ V9 _4 I( y$ A
12. O( A2 l- ]5 | j( v4 |
13
5 _3 V T" S0 R7 k5 x, A5 c0 ^9 J- K 14
6 G% U7 l' y9 f6 b" o4 p$ x 15" Z' {* |$ @2 u$ d, k! T, n
16
) @! j: O. @7 J ? 17
$ I' ~; j5 |! B& `5 }) b5 ^" T 18
$ ^. ?- B5 l8 a9 z/ W$ a 19
1 y; c/ V. v8 s9 Y$ S L# X- x, S! i 20
) r* r9 _' ^0 H3 j 在读取excel文件时,经常会出现数字前补0的需求,例如证券代码读入的时候会把"000007"作为数值7来处理,pandas中除了可以使用上面的左侧填充函数进行操作之外,还可用zfill来实现。4 `/ f3 w& _ u, X2 P) Z+ u$ l
n/ s' S) d1 @; Y7 \4 [
s = pd.Series([7, 155, 303000]).astype('string')
; l5 {! {, {) J2 o
3 r7 t0 g: \3 y# f s.str.pad(6,'left','0') L% k& @$ y. Q& J7 d
Out[111]:
* Q0 o. Y, S( P, P2 V 0 000007
( b& X; {" _2 a; B# n9 Y$ D 1 000155
$ `. J0 R9 d3 h' o 2 303000 h# b3 O+ X2 }0 L2 m+ R2 m
dtype: string
, r* r0 ]( v! K! D9 k
; u3 Q# P' s" z0 c- M) Y7 V9 V" z s.str.rjust(6,'0')
9 \* U! p3 O) \0 ] Out[112]:
$ k8 l3 v. `+ z. W7 t5 u 0 000007
/ b( h% a! T" L" l8 @; p 1 000155* {; h) b Q! J2 l7 |" ?# j
2 303000
! Q# k1 V: G, o- l5 e2 q- J$ v dtype: string
' r# @& h# e# K, O" w2 ]% B) j4 ~" R # s- y1 i6 H% i
s.str.zfill(6): _3 W. y3 j" p9 E
Out[113]: ! n B1 Q2 n1 _7 Y2 [, z
0 000007! ?5 D" `5 M" H [* f7 Q$ k: a/ [
1 0001556 M: G1 |8 s/ Y* |# n. F: S# E9 C
2 303000
: V1 E0 w- `; W, A/ a3 @ dtype: string& w( j: d) W0 p ?* J$ Z5 z4 m
4 {5 H) r9 L4 y% ?
17 n! J3 K& _$ A+ {5 }6 |( S8 M5 u- S
2' G. U4 {3 P$ {. i
33 b: s3 Q; D# s
4( C' a! g3 w6 T
5
. D" C; d* w6 T 6
( A2 Y1 s7 f) [9 P6 p 7
; P( N; V2 b l, W3 \+ e* Q 8
# ]/ i* p5 k( l. G# @" N 97 S3 P& v, L$ U/ m0 ~
10, F' G% a9 D7 r# m: x+ \
119 I: d& c+ n; G
12% ?" F( F. J2 Z% r' Q8 z
13
; p# `8 s& e9 T2 I* V 14
7 {& h, }- h& P 15
6 H% q+ p; @" z Q% w1 W 16
! M' Q, G* W7 d* q 17' T; k0 r) D9 o
182 L% M- e) X2 M8 S7 j
19, o8 g* m f4 ^: Y+ B5 `8 L$ T
20
: v7 d& p3 C* _7 L% u 21# J, e& Q6 `" o; e* D9 D
22
$ G% ]% X( m( u9 v( ]( p0 \/ B 8.5 练习8 @6 @+ x# T8 y& V3 q7 K; _' y |
Ex1:房屋信息数据集' s P2 s8 \- y1 V6 U2 g) F
现有一份房屋信息数据集如下:& R$ r, Y {, s
6 o3 m' _+ ^; N; J; E( e df = pd.read_excel('../data/house_info.xls', usecols=['floor','year','area','price'])
5 g0 k* Q* T6 `; F5 j- Z" ]4 F& _4 \ df.head(3)9 |+ i! w! | i) ?6 \: y
Out[115]: & D2 M2 _- K# [1 b) m5 j* V
floor year area price
& z1 s$ {0 f& p/ s. t( c+ } 0 高层(共6层) 1986年建 58.23㎡ 155万. D9 F' K/ K9 R; s, O/ @. G
1 中层(共20层) 2020年建 88㎡ 155万# h. h/ W4 l) G3 _+ q2 d0 S6 T% D
2 低层(共28层) 2010年建 89.33㎡ 365万
7 A# A2 W: @( p5 Q6 Q* E, x 1# o) C( O, h, Q2 l9 O' _9 s
2
8 g# r( `1 h$ n 3: ?- D1 v8 Z' m1 D6 {) B
4
9 s4 M. k+ F, l s# |& |/ n" m 59 `* ~9 e+ Y, v& h
6& k7 h% f( m6 |/ N9 m" y6 \' `6 Q
7+ c! k) y9 o1 V/ A4 S* d, c
将year列改为整数年份存储。
' d/ |" O5 O4 v' J' o8 R* w 将floor列替换为Level, Highest两列,其中的元素分别为string类型的层类别(高层、中层、低层)与整数类型的最高层数。
l- y+ q' @3 z7 l; y1 z8 y3 r 计算房屋每平米的均价avg_price,以***元/平米的格式存储到表中,其中***为整数# D' B4 T1 Y4 G' [: A7 l& r0 e
将year列改为整数年份存储。
& m: H1 T% k& B6 x7 w% s """
' V" X, z6 Q1 n O' \6 i 整个序列需要先转成Nullable类型的String类型,取出年份,再将年份转为Int64类型。
7 R" Y5 U+ j4 V: {0 { 注意,转换的类型是Int64不是int,否则报错。即使astype加参数errors='ignore'跳过缺失值,6 m# w$ l! }& ~$ ?# F
转成int后,序列还有缺失值所以,还是变成了object。
# r3 h" u. o1 [ 而整个序列转为Int,就还是Int类型,缺失值变成了 pd.NA 。$ ^5 `2 ^' q7 R, W
"""/ t9 I4 t$ Z3 C, n7 W
df = df.convert_dtypes()) o- G# h/ k' Y: G! z# e
df['year']=df['year'].str.replace('\D','',regex=True).astype('Int64')# {4 m4 Q; t; p3 W) S; x9 W4 b+ s
df.loc[df.year.notna()]['year'].head()
# w: Y/ t' |4 D: A1 |& i2 q 4 r* I# X& Y0 o( {, ?
0 1986( V7 x m+ ~2 m, G9 V5 b4 e; u
1 2020
) N5 B4 b. s2 k& F; X 2 20100 ]9 i6 D- c7 H, A8 k
3 20149 \0 X1 O! ]6 @# ~
4 2015
9 l8 `, c4 E1 { Name: year, Length: 12850, dtype: Int64- y1 p! Y, n* u! y3 z5 S) i
! d* X( T0 r2 ~7 C" l, m% n- p 1
4 x9 ^7 m* |. q; S 2$ v: C7 C! l: ]0 ^: K
3& V( Z) }( r0 X2 G3 r+ ]! d3 _
4 Z: v, f% Q0 r; b% p6 Z: s
52 v6 c) E6 ?1 N: d& P
6
5 z2 h: H% N+ T 7& Y, _ L8 s/ A0 {1 u
8
0 a% y: O6 Z. { 9
8 o. H/ [/ R( m5 {0 H% k4 n! Y. l 10$ Q! n+ K! Y) B7 I
11
* Q% u& J# k0 _7 s 12# \4 D1 ^/ W0 b! y& z ~# Z
138 `3 I+ d7 r+ |7 D# l
143 C9 F h; q$ y8 R! G! P' S5 ?
15. z, s$ y1 M2 G. [4 _8 T ?
166 M: v1 U& B, {' O& R3 ?
参考答案:
' [' K" O# K+ b2 J : C; m9 W# e9 t7 D
不知道为啥pd.to_numeric(df.year.str[:-2],downcast="integer")类型为float32,不应该是整型么
. ^( \+ C% j0 d" r0 {5 F$ ?: ~ + i+ s6 S, r" d# t/ q6 T1 G
df.year = pd.to_numeric(df.year.str[:-2]).astype('Int64')
; n- Z8 ?, N O7 u) G( j. ] df.loc[df.year.notna()]['year']. [: y, M" ~- E9 f4 n& \
1
0 k, M. J8 A: v7 \ 2! g- \5 J; C) e7 r a( u& r7 p
将floor列替换为Level, Highest两列,其中的元素分别为string类型的层类别(高层、中层、低层)与整数类型的最高层数。
/ ^# O5 C2 v& B6 q6 ^2 c pat = '(?P<Level>\w+层)(?P<Highest>\(\w+层)'( Z0 |; O& r1 G6 w
df2=df['floor'].str.extract(pat) # 拆分成两列,第二列还是(共6层得形式,所以还的替换一次
! D' C8 u& \1 q df=pd.concat([df,df2],axis=1).convert_dtypes() # 新增列拼接在后面,再次转为Nullable类型
( I. t+ I: Y. b& r0 Y) { df['Highest']=df['Highest'].str.replace('\D+','',regex=True).astype('Int64') ; d) O0 s. H# ], t2 O
df=df[['Level','Highest','year','area','price']]
% \) o- N N5 C6 Y8 u- j' ]" N# r df.head()
& `! _' y$ U1 \4 D1 O" ^8 G , x1 `, w# b# c" X: ^
Level Highest year area price+ \- q0 ]) J1 G* {6 O9 v
0 高层 6 1986 58.23㎡ 155万
5 y$ E9 _" R' }0 R8 @( S. R( M 1 中层 20 2020 88㎡ 155万0 Y P. b# d5 M& ? J
2 低层 28 2010 89.33㎡ 365万8 R" Y+ T6 ?0 R9 t+ w# v
3 低层 20 2014 82㎡ 308万
+ Q) r }- m7 d. f/ V; e0 M 4 高层 1 2015 98㎡ 117万
6 l4 x) `8 u, \4 t3 O 1
, |6 W% j" n9 h% K) R( T2 [ 2
2 S1 V5 Y( w- Z# R- J 3* q1 V! W9 k) U- C* k: O# M ]
4
, n7 J0 A! k" g n- R, r 5
2 c( }. h! U; M1 P/ F+ X' N( q3 W 61 s6 U( L4 }" D; j' t
7. s: H$ J- e: k! ^- G, C
8
( `: ~7 d6 E( { Z" h# Z- n2 P$ b( P 9
! x( Q- F D1 ~9 z8 a 103 Y. R" [6 J$ v- [- {
11! p2 |( k3 @7 h: g. _
12
% H* N' c8 \5 S$ C4 |0 h: e7 H 130 L4 _3 h/ ]1 b) P! @
# 参考答案。感觉是第二个字段加了中文的()可以准备匹配出数字,但是不好直接命令子组了
" h9 z! ~% P- A, X pat = '(\w层)(共(\d+)层)'
' m4 T& ~3 ?, p% L5 G: T% x new_cols = df.floor.str.extract(pat).rename($ a+ k1 @2 @& I& @) ^* O+ b. \
columns={0:'Level', 1:'Highest'})
% i; S+ K9 A/ X4 T) M* B , x4 C! s( ?' {' A. o
df = pd.concat([df.drop(columns=['floor']), new_cols], 1)
3 Z& C: x9 L# {/ ]2 j/ e df.head(3)3 c k4 m7 n0 H! U+ N r
1 j6 k# l$ o* ~: e
Out[163]: : X/ n$ B' b( Y |: c
year area price Level Highest
' P3 }) y8 B, \. ~ 0 1986 58.23㎡ 155万 高层 6
' ]5 }" g( R' E4 j 1 2020 88㎡ 155万 中层 20
' ^; Z* V& G9 t a5 j9 [/ ` 2 2010 89.33㎡ 365万 低层 28
4 v' G) E; M9 z; \( h8 q 1
. n& ?5 S& ^; m) R2 {& F7 Q# ]( E 2' u0 }$ e# ~( ]
3& ~6 X) G5 C" M1 c
4
9 u# v/ \* q6 B2 O/ j* S& b% i/ } 5
' A% G8 k6 _$ m: h; ]7 h8 s( U 6
% o7 @: ?* U6 p9 s 7, b+ C2 @- q" a$ z
8# @0 K( Y7 T2 E
9% Y4 @0 I! \* ~
10
9 m! K# C& k4 x% P4 O 11# V6 t- L) {" }1 M# B3 d) T3 R: u# |
125 W# }% [) F) E
13- t% {! ?' e0 D' {
计算房屋每平米的均价avg_price,以***元/平米的格式存储到表中,其中***为整数。
+ e3 b# {$ p0 g) X5 a4 w """; ~# }' n, H: a2 l# [
str.findall返回的结果都是列表,只能用apply取值去掉列表形式
6 Y8 @, o; ~, F8 f* a; F, W 参考答案用pd.to_numeric(df.area.str[:-1])更简洁
( L# b- n* J! V) w 由于area和price都没有缺失值,所以可以直接转类型
8 C5 M4 q3 R: n9 h" S """( `; z0 T' a1 p5 A5 T: h+ ^: [
df['new_area']=df['area'].str.findall(r'\d+.\d+|\d+').apply(lambda x:float(x[0]))
2 r' Y, V9 ~# h3 y$ O8 ^( A( ^8 v& _ df['new_price']=df['price'].str.replace('\D+','',regex=True).astype('int64')
) r/ m/ ~( f' l3 s1 D df.eval('avg_price=10000*new_price/new_area',inplace=True)
`& l2 Z# G, x8 Q5 y s. {# u # 最后均价这一列小数转整型直接用.astype('int')就行,我还准备.apply(lambda x:int(round(x,0)))1 z* X! T4 k0 j: ~9 U
# 最后数字+元/平米写法更简单 v- i* W' W: M$ L2 i
df['avg_price']=df['avg_price'].astype('int').astype('string')+'元/平米'
, e* Y7 D f+ F del df['new_area'],df['new_price']
: @: }4 |+ L/ V$ b! B df.head()2 W) b! ?1 p. W5 V
5 d, F1 @; L4 `0 z' h, ] Level Highest year area price avg_price* N/ U4 V# w! K- l% M7 d
0 高层 6 1986 58.23㎡ 155万 26618元/平米
\3 r+ s% c8 Z( ~% Y9 ^ 1 中层 20 2020 88㎡ 155万 17613元/平米- W. m) {# L' I( Z2 j! K0 y, O
2 低层 28 2010 89.33㎡ 365万 40859元/平米 S4 ]/ z, ]: z6 }9 p5 P
3 低层 20 2014 82㎡ 308万 37560元/平米
5 o, c" f8 E% h 4 高层 1 2015 98㎡ 117万 11938元/平米
% ]: {0 w/ w$ b* z n5 U7 R$ G
, |6 Y" T. r- k3 t6 U* q 15 F# O. c, p8 ^: W( U% B
2
( p# w# H5 ]3 T C- x8 t* A. G; F, k 3
. n4 F( }: [! T5 a, W( j/ | 4
6 m! b* b$ X# Y" ?5 R+ T$ I3 y 5+ H9 @6 n$ Y _: b' W# W
6
0 P8 W8 b) w/ V 7
: D u$ Q: r; c. a 8% X2 Y8 i3 U" d
9
. k* ^ J A8 P. z 10
) c" f# X) e- X) Z. y 11
. ]! Z1 P& p; V; G; W2 Q5 | 12
! v. _- b3 M, T 13* M$ n9 j4 B! C- I
14 l5 B$ N/ Z% @8 }# F) O: x8 N
151 F6 h% ?1 X v' M% F4 d" y$ X
16
2 {7 p( e+ g( h( T, e' G 17% w4 D9 S2 b7 ] y5 i
185 r l) d' @7 s. h! Z
19
* ~4 R3 g1 C- ~ 20
% W7 w: t( a+ }# U- |: } # 参考答案8 X4 J+ V$ r& d4 B* d* b
s_area = pd.to_numeric(df.area.str[:-1]), M+ t0 P+ u9 Z% b1 }0 @7 Q
s_price = pd.to_numeric(df.price.str[:-1])
+ c) g" _& u+ y/ E; m" R df['avg_price'] = ((s_price/s_area)*10000).astype(
+ `: j' E# R) q$ D/ g& J+ L$ R 'int').astype('string') + '元/平米'
% j9 }/ y% E4 L6 ?* T5 y' I
1 C% j s' Y, R# S: N8 w df.head(3)3 P( A. J2 D, }" B" j5 n
Out[167]: . o" u8 K& @6 K+ B
year area price Level Highest avg_price4 X% k* }6 v+ i* J
0 1986 58.23㎡ 155万 高层 6 26618元/平米0 Y i/ A& s: `3 T! w
1 2020 88㎡ 155万 中层 20 17613元/平米* ` N4 Q5 y# C/ w
2 2010 89.33㎡ 365万 低层 28 40859元/平米
h8 N+ ~" i+ r7 L2 L0 y% U- |; a$ h 11 i+ r/ j" \/ E% f2 t# V
2+ v; Z! C, a8 d; t
3! Q" I/ c* f6 }
4
: q% P9 Y; B4 k5 _6 m" U; v 5
% V* b8 |7 O Y" W/ K 6
/ {: a/ ]0 v* z F8 h 7" e" j# h2 m7 ~( c7 g3 A
8( x7 _2 ?$ c1 c+ i2 r7 T. N9 O
9, T2 D+ j7 Z+ r' Z" c5 w8 e/ _
10
5 v& w' X; n: P% r8 u1 f/ b" x 111 n' P, ^) f" M& I* x( U
12. [ f5 z; d/ _' d
Ex2:《权力的游戏》剧本数据集
6 k y t h0 a) [7 m& Y 现有一份权力的游戏剧本数据集如下:* R' G2 `; a! l
3 B$ q" u7 f" w1 f- b df = pd.read_csv('../data/script.csv')* t2 Q; f3 W' j
df.head(3): G/ z( `, S& p ]$ j
9 h' `+ k; I, N- t+ k" z8 c: v! s Out[115]: 1 } [5 B5 N( Z0 M
Out[117]:
! D, H+ J% w0 O' Y Release Date Season Episode Episode Title Name Sentence+ w1 \# a& f' E+ I" h: T
0 2011-04-17 Season 1 Episode 1 Winter is Coming waymar royce What do you expect? They're savages. One lot s...
) M S/ k! b! B4 x 1 2011-04-17 Season 1 Episode 1 Winter is Coming will I've never seen wildlings do a thing like this...
1 e: H& N% X# s+ t 2 2011-04-17 Season 1 Episode 1 Winter is Coming waymar royce 8 F" n6 ]. z$ s; T) k; P k: ~2 R$ v
1! d+ }& t: ]9 R" T
2! V' b7 m0 g. T3 j
3, M. A* o2 s4 |" O, L4 ?3 z
4
& R- D: U+ r) T, J; h 5
: m \' s$ E( m1 U9 {6 ?8 f 61 J9 \: r$ \0 i9 Z V! A
7/ C* D" |2 E1 U4 R. O
8
. ^ W0 U- c! g% d 9
* f0 X+ ^# H0 Z+ S- F2 u+ o. H 计算每一个Episode的台词条数。
, R' \7 L- D- J6 q* n3 ^) r: q* Y 以空格为单词的分割符号,请求出单句台词平均单词量最多的前五个人。
" Z* u" P5 l+ k. x" L7 A 若某人的台词中含有问号,那么下一个说台词的人即为回答者。若上一人台词中含有 𝑛 个问号,则认为回答者回答了 𝑛 个问题,请求出回答最多问题的前五个人。. W$ S) z. A" v1 L) \! \, O+ T) B' c' ?
计算每一个Episode的台词条数。
: d9 o" @: [# k9 c2 b# | df.columns =df.columns.str.strip() # 列名中有空格0 E6 D1 t {) W/ P- n: \
df.groupby(['Season','Episode'])['Sentence'].count().sort_values(ascending=False).head()
$ \& C X9 ~ g+ Y* C
* j# Y4 A0 t$ M! U; D9 l season Episode - |. H! d; k! m" Q: g, m
Season 7 Episode 5 505
5 ~% M" V# s# W" v& a: N Season 3 Episode 2 480. P6 Z& |. e+ H' J
Season 4 Episode 1 475
$ j) o% b K# w, K Season 3 Episode 5 4405 Q2 u# a$ }9 i+ S$ N7 K1 Q
Season 2 Episode 2 432
- a' \5 u$ Z3 z: m) Q 1# o5 Q5 f8 }3 D7 j( X6 \
2* v; G8 o$ y' S% ^
3
' w" A3 \+ a: a j0 Y: ^ 4" R! C, c9 p% ]0 X; b; h+ j3 z( `
5
0 V) B6 t' `: f7 E6 w$ E/ Z 6* d8 U" o8 h' s
7" B5 F; ~% t& Y2 b+ n
8
0 g$ | C7 I- f7 k- n9 R/ P4 F4 U7 | 9# B2 O2 B0 {2 I$ ?
以空格为单词的分割符号,请求出单句台词平均单词量最多的前五个人。2 W# g2 T$ b6 f" ^" e4 {
# str.count是可以计算每个字符串被正则匹配了多少次,+1就是单词数
1 ~. {: x6 Q3 k/ w5 V df['len_words']=df['Sentence'].str.count(r' ')+1 d9 a; i3 V0 Z: N0 v t
df.groupby(['Name'])['len_words'].mean().sort_values(ascending=False).head()4 e9 X& q" A& @$ X; l9 C
. V+ X3 J z7 F; ?' A Name0 Y4 T/ I$ Y% \5 z3 r2 @
male singer 109.000000
7 J# A5 Z. L% d" ^' U slave owner 77.000000
. C1 Y- D) d5 J/ } manderly 62.000000
0 \1 N0 g% W+ `6 `( T lollys stokeworth 62.0000001 \& g% G$ y4 H! E8 \
dothraki matron 56.6666671 R- i5 `+ C( n% {3 g
Name: len_words, dtype: float64
5 f u4 F5 P5 q5 |* v( @% B8 |0 F 1
[! p' H$ q7 w3 F" r 2
& `8 T4 H" z( A- n) e, z/ E 3
/ M& y+ h; @$ f4 }8 x+ u 4' x( T: x: h8 t0 Q- p4 m7 I# d
5
2 a9 D( ]. Q# Z/ s/ }' \ 6
: Z4 @( Y: d9 |0 b& q* b 7
! C5 }' ~" ]* N ^" S; a 8% Q+ R$ b0 x9 Q/ i) ]( ~
9) f! d; w* x7 }1 X+ W( l
10, u8 g3 Y8 J- E! R/ t* a9 e. _
11$ n& ]# O% c- m3 x: y
若某人的台词中含有问号,那么下一个说台词的人即为回答者。若上一人台词中含有n nn个问号,则认为回答者回答了n nn个问题,请求出回答最多问题的前五个人。( `6 i& Y3 T8 j) U
df['Sentence'].str.count(r'\?') # 计算每人提问数
5 |8 T6 t, x. M1 q# t ls=pd.concat([pd.Series(0),ls]).reset_index(drop=True)# 首行填0! R; a( Q. j, v& E. p& ^
del ls[23911] # 末行删去
+ Z4 A9 H$ m# u0 X, ?8 x$ h# c df['len_questions']=ls
! p7 `5 b: k' p! T2 z9 ~3 G# X( }) U df.groupby(['Name'])['len_questions'].sum().sort_values(ascending=False).head()9 H$ r* I! y! E. S
' [; q: u0 B7 Y! K: G, K
Name$ o- H# A/ s7 d$ @5 T. z4 E% x1 W
tyrion lannister 527
4 m7 T! d% \! e3 h4 s$ o jon snow 374
$ Y, ^7 Y( q$ r3 E+ d jaime lannister 2832 x# ^1 ?$ ^" U- S
arya stark 265( h1 e$ t" q& `( x
cersei lannister 2469 K( P6 e' _, b7 X
Name: len_questions, dtype: int64 l; i6 S) z3 X
1 k( L2 N6 Q8 t& x/ d6 i+ m* }" W, w # 参考答案
; L1 u7 W: C1 u; x& [7 A% Z K s = pd.Series(df.Sentence.values, index=df.Name.shift(-1))
+ q+ o/ ]1 M! Y% v* I5 d s.str.count('\?').groupby('Name').sum().sort_values(ascending=False).head()1 \# P# v2 x$ k8 X0 V
- e/ A9 R* h u! v- M" t
1
# w/ y, i4 ^4 e* i 2
/ P1 g0 t a! C3 @ 3
' p) {" ?, z! g' R8 ? 4# |) S" H4 _$ ]6 v2 Y
5) q1 {" q( ^9 L8 X! |1 L
6" d- O* r) r9 Y$ V( e: i
7
2 u# T @6 ~9 N 86 C% G& [( L- p Q& W
99 z( N) K9 q5 O; ~4 _
10
: L# s2 o* ~* y3 r; ? 11
% O% @# E6 @; E5 T 12
( x n; ^; T* F1 G, W 13
z5 _# w: F: Y& C. L% @ 142 Z, U9 a' j) S# ?# ^2 _$ G
15
4 Z# F: _8 p7 W) b 16
0 Z$ [% s7 Y3 O }) x 17, C t, j% T7 `4 S, s3 o+ H4 a
第九章 分类数据
+ M8 _, T9 r- C5 F. X4 d import numpy as np
# q! e% f; R* D! w import pandas as pd# P9 a( K. _: Y7 }3 E+ ?
1
! r# U, b7 \; n0 w& k6 i 2
4 y) f% q& S1 o4 T8 ]. W8 \ 9.1 cat对象/ }! C7 a, |9 d* R: ]8 y
9.1.1 cat对象的属性
2 F: L- Z! R7 c4 ^ {# l) [ 在pandas中提供了category类型,使用户能够处理分类类型的变量,将一个普通序列转换成分类变量可以使用astype方法。
/ {2 d$ [+ L0 u! @
# B% {/ @! N! v/ d df = pd.read_csv('data/learn_pandas.csv',, s7 e% `7 j& A7 k
usecols = ['Grade', 'Name', 'Gender', 'Height', 'Weight'])
" S1 A4 T" A: R) i% I0 O s = df.Grade.astype('category')
5 X: ?& l: q' Z
8 I z! ^8 X+ c3 D2 D# Z& C s.head() E2 J! J% q5 x' m) l
Out[5]: ) }. B6 H8 O5 I7 Q1 `+ Y6 S; v
0 Freshman; R# r1 T& y- F4 M7 B% n9 j
1 Freshman! G# k J, x& l2 w
2 Senior7 C$ r% a4 w. i/ d* U7 F
3 Sophomore S/ O0 E8 V! h1 D
4 Sophomore
! M5 B+ Q# |" V* N- l6 L# ~ Name: Grade, dtype: category
3 n$ J8 Q! x4 n8 ~' A Categories (4, object): ['Freshman', 'Junior', 'Senior', 'Sophomore']! f- t' \: D! G' B! u5 S
1
0 t4 N& ^+ D! t( H8 k2 O) c" \9 g 2
3 L1 t6 A* Z7 | 3
3 _5 ~! U ~% K7 G6 Y( R 49 Q& T0 A8 E3 C1 c6 l
5
/ R# {' ^+ h z% k/ b9 h5 y8 G" v 6
1 z2 Q8 \$ r6 B# v9 Q 7
* D( D5 H+ o' k, D# z 85 Y! e# {% b% |/ |3 V4 b
9
a1 D1 E1 s' n: T/ P 10
4 r) ?! i, `2 _. U/ a' k: k 11
, x. K+ J8 E w: R 12) }/ z7 r) Q* z- l- k+ q
13
2 ? i9 d Y' W: d3 l 在一个分类类型的Series中定义了cat对象,它和上一章中介绍的str对象类似,定义了一些属性和方法来进行分类类别的操作。2 E- `2 h6 h0 R% j/ k) z$ `
; d. k+ \: Z% | i- N9 ] s.cat5 n; x, }0 V+ i
Out[6]: <pandas.core.arrays.categorical.CategoricalAccessor object at 0x000002B7974C20A0>
' A2 z/ e2 `* |/ S4 j/ T( I* ^) | 1
$ l/ J" W4 A1 j3 M 20 K7 |$ F' X. w' H; b3 j
cat的属性:
: `& `; P/ P. |4 Z , [. G. b$ z+ v; Q; M V& ^3 i
cat.categories:查看类别的本身,它以Index类型存储! ~9 ?. w+ j' e/ ?
cat.ordered:类别是否有序
+ ?! j' {* u1 D3 V) a, F* m cat.codes:访问类别编号。每一个序列的类别会被赋予唯一的整数编号,它们的编号取决于cat.categories中的顺序' w- q. O/ O0 w7 h
s.cat.categories) q1 m2 y# m' S& K+ Y, J8 Q( D
Out[7]: Index(['Freshman', 'Junior', 'Senior', 'Sophomore'], dtype='object')2 Y! n6 v2 P' T( z
& T/ t9 F8 b% I) u/ m
s.cat.ordered% h, B, m+ v( o: _( b& F
Out[8]: False. Z; y0 @' \% m: Q
! [6 i/ S: M* }+ }& Q* g7 |6 [
s.cat.codes.head()
1 C; v3 I, b8 M& c% y Out[9]:
3 x% @7 t6 \8 ~( |8 n! P 0 0' f% v3 Q0 \ E7 b8 u& ~
1 0
4 \. J, b3 F1 E* s; u! s6 ~! P 2 2, r% C Z ~2 i" ~
3 3
0 P5 g: ?/ [, J/ X9 I* u 4 3
' B1 T" W# [/ Z9 G dtype: int80 W' s" F. H( A$ I9 _ p& L
1! T7 g5 L; w- X4 Y) D2 w/ f
2) s5 j# G' L( J# |1 [ r" }' M
33 y! O+ Y2 z' k/ \' O1 B
4' u: i6 h8 N# S4 O2 r0 x& d6 v
5 L0 Z6 ?+ F' ~% ]- ~% F
60 v+ F( P4 D( }% H: o
7& c- E) c, p1 y! F
8
9 C" _ {8 S* \6 `) L6 ? 9
2 y3 @1 f/ \9 D+ y2 |; h! Z' y 108 z0 l1 W! `" S. B7 {+ U; s+ ^9 ~
11
2 P4 A7 Q1 c8 N2 ^ T! } 12" v/ E! q; C0 {. Q# ~& m
13
9 D- z/ e5 j7 x8 J4 j' E% Z+ J5 d" L% ` 149 C1 [6 R7 ~5 M1 C Q) I
9.1.2 类别的增加、删除和修改# I. D8 Z7 w; K2 r
通过cat对象的categories属性能够完成对类别的查询,那么应该如何进行“增改查删”的其他三个操作呢?
& t! Z7 U: n9 V7 [
! t# \+ n' A6 F& V0 y8 Y 【NOTE】类别不得直接修改 I: I6 q$ Z; d
在第三章中曾提到,索引 Index 类型是无法用 index_obj[0] = item 来修改的,而 categories 被存储在 Index 中,因此 pandas 在 cat 属性上定义了若干方法来达到相同的目的。
! F1 I0 p; a$ u' X% w
+ u; ^2 z- F' U( r. s add_categories:增加类别9 Y3 a5 N& E, ?6 k
s = s.cat.add_categories('Graduate') # 增加一个毕业生类别
o1 J( D5 _* \1 U4 e7 Q8 o s.cat.categories
$ N) p# K* }. ^# u X( b
/ {" ^; Q% o/ |/ d, V3 K i5 u, R Index(['Freshman', 'Junior', 'Senior', 'Sophomore', 'Graduate'], dtype='object')
% `0 Z: F& y3 C 1
/ s5 ^ }: A( W8 y* M$ j 2/ _- z9 f& K7 W% D1 k' u* {% V
3. o3 b3 j# n, A/ X; ~9 }
4
3 ]% `& l( z" \7 E" M remove_categories:删除类别。同时所有原来序列中的该类会被设置为缺失。
9 b- K' A8 E: w! ^, B s = s.cat.remove_categories('Freshman')
7 ~: R6 K$ `5 K! h( W
( k# W" D9 ~) W1 `) T: h5 H$ ~! ~ s.cat.categories
7 W. _$ v8 F3 V9 D3 @3 g6 Z Out[13]: Index(['Junior', 'Senior', 'Sophomore', 'Graduate'], dtype='object')# a4 W+ e& }' \4 D3 A: d# ^
7 y0 U" P" |( Q, v# b w s.head()9 r7 c2 }$ `' u: ~. y6 c
Out[14]: 2 W. s* F/ T: Y/ V
0 NaN
+ g& I0 } Z7 e- q ] 1 NaN
8 p" f& q3 l- w9 L& p 2 Senior
3 _0 D, x' P0 g. M6 Q' H5 V 3 Sophomore$ l' v, s- E4 a `" T' w7 P! W
4 Sophomore, V' I# s# n+ ~6 B
Name: Grade, dtype: category& ~- l" g" X, N8 t
Categories (4, object): ['Junior', 'Senior', 'Sophomore', 'Graduate']
& d" ~' ?2 w5 L _$ U7 ]$ K 1! N; Q5 ~* \3 ^5 r) D) o, ~
2
1 O; v- k1 C5 V# o! Q 35 B# z: ~% S+ W* C4 {. p
44 l: t9 S; r; T" {& s/ r f# X
5/ S- Q& C0 @8 T! _& a7 ? N
6
; X4 @2 A8 D8 ?7 k( L2 q$ d 7; W" P5 L- S" v2 B( u
80 N' A8 b- O! h+ g# r c0 ^
9" _/ k5 z& y# w0 K* N( R
105 F8 s$ Q* \! q7 P0 l0 K0 f3 O
11
' E. w6 H; {/ C: I$ r# B 12, ]; { a" f3 T
13
* A* |/ ?# R4 E0 R7 \ 14
0 o) h" O$ m( g K set_categories:直接设置序列的新类别,原来的类别中如果存在元素不属于新类别,那么会被设置为缺失。相当于索引重设。
+ }- S% }+ K8 ^- L" @; l s = s.cat.set_categories(['Sophomore','PhD']) # 新类别为大二学生和博士
/ U$ T% v/ L- i s.cat.categories6 W$ V4 I& m, p: ~( P
Out[16]: Index(['Sophomore', 'PhD'], dtype='object')
( e: T9 M" l8 M 5 W9 x4 I8 I6 @' u4 O
s.head()
" R! c2 {7 b: M- X/ J- R Out[17]: " W1 G. s, I0 {" @1 r
0 NaN4 _4 D z* d1 k& r$ y% [
1 NaN. n) p2 {3 ]. `
2 NaN/ [/ {( n( W1 T
3 Sophomore
/ N. }0 G q# W+ V% @ 4 Sophomore2 U$ Z7 H" H& K B' S
Name: Grade, dtype: category u% v8 x! W; R' d( ~8 Q
Categories (2, object): ['Sophomore', 'PhD']
! }7 A% ?. N: M) G 1
9 @3 S: b( A8 H. i+ X6 y0 Y* S 2- l& O3 t6 Q, Z. k" Z
35 A5 E. i: U7 {' w
4
8 ? Q. w: `7 t% d. j& w, E9 w 5& W) n! R9 N8 W3 X9 V
6) ]( J C7 N4 j1 y
7
/ ~' C9 Q2 k( n+ n+ \! S4 V- k 8
( M0 v' H l5 [/ Z2 F& ]. J1 o 9
) u- Z& v, ^3 }( Z- B1 w 10
6 ~) {5 o/ R$ V+ |0 A 11
( ~) u9 Q9 K! s: O( p4 u0 r 124 ]8 x$ V, `6 ~- k) Z4 [
137 e' z4 ~$ G6 c* a
remove_unused_categories:删除未出现在序列中的类别) w4 s+ \" G* q% V
s = s.cat.remove_unused_categories() # 移除了未出现的博士生类别, ?- b7 D3 c+ ^* U; |' c+ _
s.cat.categories
( z* L: \8 Q' |- | 8 E/ q3 |3 b- I* a. Y& m7 w
Index(['Sophomore'], dtype='object')
; d# [0 x- Q) |: j( B; ^: R 1$ K2 A! c5 B9 R; P' b
2
) o! h% D3 i4 s4 X4 J 3
4 ]9 w% p$ l& o8 C 4
' Y$ \* J& b8 i a' N1 y- Q rename_categories:修改序列的类别。注意,这个方法会对原序列的对应值也进行相应修改。例如,现在把Sophomore改成中文的本科二年级学生:
9 J" C# G1 |: x5 ~6 x s = s.cat.rename_categories({'Sophomore':'本科二年级学生'})
8 ~5 r+ N* w m* e1 H% y s.head()/ E5 W( j9 q# H u9 u
0 m# J) U; L! _ I) h$ x8 E0 J( W6 n* b 0 NaN
7 ?- N# O8 R( B* p6 w X 1 NaN; N" B$ b) d7 T
2 NaN& R P- W: g% M1 l! m& L9 @) Q
3 本科二年级学生, h% g/ ?8 h: E1 X1 i/ M0 S/ ?( {
4 本科二年级学生$ W: v& T; `" p8 T$ X% o- X* o5 O
Name: Grade, dtype: category
$ L. u" ?8 Y, x# L. B Categories (1, object): ['本科二年级学生']& t4 F; N* x' R& k8 c
17 X W/ n" L. R: f" j' }
2
0 ?; \# z0 E L& I% G; P8 e5 h6 y 3, f: J+ U- Z( J. j
4
" h) F8 n! x. q! l" N+ R 5. n9 ]% w( c# S- |
6# o+ ^8 s" O( j0 U
7
. b2 }) \% A3 k' y" m3 \) s* J 8
- T1 n2 M- n' X3 C% ]6 h+ d 92 z6 _; v; E" r }: N. f
10
1 @# j: |; T9 e5 p6 J 9.2 有序分类
6 @2 r" V+ G4 G% x$ U 9.2.1 序的建立7 s: o% m) ]* a h G+ b
有序类别和无序类别可以通过as_unordered和reorder_categories互相转化。reorder_categories传入的参数必须是由当前序列的无序类别构成的列表,不能够新增或减少原先的类别,且必须指定参数ordered=True,否则方法无效。例如,对年级高低进行相对大小的类别划分,然后再恢复无序状态:
! \3 x6 `% r8 O8 {* m
$ F3 _ |' K8 \9 G$ M- ~ s = df.Grade.astype('category')8 S1 N' u h! _' T3 g
s = s.cat.reorder_categories(['Freshman', 'Sophomore',
7 c# n$ Y: l, F( }1 O; I) Z 'Junior', 'Senior'],ordered=True)! Z& Y5 |' d- v5 ~" C# j6 N
s.head()
$ i) R! d( \ z, f/ u) p0 L* l Out[24]:
3 B5 ?( J% G5 V' K" C' J) r, @8 l( [ 0 Freshman
( M" K$ n6 o7 c 1 Freshman& s% i& y: [3 W$ [; m- c+ ]- \0 N
2 Senior1 E0 h6 f; ?9 e! G
3 Sophomore+ W/ \: `+ n# k1 v
4 Sophomore9 Z: Z: S7 l' f, I4 O& c/ f
Name: Grade, dtype: category
9 w( D6 c4 _8 y J7 D5 o Categories (4, object): ['Freshman' < 'Sophomore' < 'Junior' < 'Senior']5 }2 {( p' e: P, i( K$ }0 e" j
! d0 n+ C+ m( B3 N: v7 p( A
s.cat.as_unordered().head()
# Y) O8 w: a4 W" d' { Out[25]: : K* i' M6 Y1 D- A& _4 h, K/ t
0 Freshman# Y, y# E5 V4 S# O* j) G
1 Freshman/ N" l9 t( `/ o% M" }0 t
2 Senior
8 X A5 B2 e; l$ |+ q 3 Sophomore1 r& ~. P" J0 @9 ]; M2 q7 N
4 Sophomore) q3 \6 s. n4 W9 y4 ^* q
Name: Grade, dtype: category6 ~2 `, t3 p8 V; L
Categories (4, object): ['Freshman', 'Sophomore', 'Junior', 'Senior']* z, L6 H7 @0 |
I. I3 U1 E* a! \ 1
6 m+ Y, ]* M0 W& M$ ]1 g2 m+ x 2 D! N5 ]8 d" H( ^( ^
3
* `- ^: X! R3 [# e; ^; h8 M2 \; Q I 4
% z1 `1 r. C* M: |* Y: j1 w- | 5
& f0 a9 H8 z: Y4 ~4 d& F 6
' e0 A: L% A' X- S( y 7
! m0 @. ]) d0 M( s z 8# u# t6 H7 v4 [, C+ d
9
! k `0 q0 G0 Z# _3 O 10
9 o9 z3 Q! M& R; ?( S 11; F' }- ^! p" c! R7 J( C
12
: u3 l+ [/ ~* p& [4 ~ 13$ a& L0 p! h- ^+ @8 x3 `
14
) e8 p0 t [8 [- b2 l" F& P8 [7 g 154 P1 U+ B; X, Y+ S
16
% R9 u5 g8 Y, O9 _' W8 I# t W 17
4 q. \6 k4 l2 r) G! m6 G9 } 18
# N1 Y4 A" ]# Z9 G M2 g 19+ U* k/ s7 x5 T( x7 i' [3 S6 F
20! D& Q4 A+ q) h t" }1 ~
21' n3 F+ X8 S# N2 U; e* Z4 N
22) p6 I8 ?: F- W
如果不想指定ordered=True参数,那么可以先用s.cat.as_ordered()转化为有序类别,再利用reorder_categories进行具体的相对大小调整。
. Q8 A. h1 m9 B2 T9 V% B+ A % N) }+ C! `# S: r% b x( j5 w
9.2.2 排序和比较
! R6 m( @# m+ E# B" b 在第二章中,曾提到了字符串和数值类型序列的排序。前者按照字母顺序排序,后者按照数值大小排序。
0 s# p* w; R0 S1 r2 C
+ B2 l! q! h _ 分类变量排序,只需把列的类型修改为category后,再赋予相应的大小关系,就能正常地使用sort_index和sort_values。例如,对年级进行排序:
, X6 P5 Z/ Y* o" R' b* Z
( _# b1 `! u: N df.Grade = df.Grade.astype('category')6 _; h+ k) E) q) |; i2 o
df.Grade = df.Grade.cat.reorder_categories(['Freshman', 'Sophomore', 'Junior', 'Senior'],ordered=True): I; ^6 i9 h- n X
df.sort_values('Grade').head() # 值排序( W& H" e: h1 A! M( j% V# T( h
Out[28]:
7 F' G7 R1 ?- U5 P0 D Grade Name Gender Height Weight
, d6 v. |- g4 _' d( b, H 0 Freshman Gaopeng Yang Female 158.9 46.06 p. [" ? I: |& s
105 Freshman Qiang Shi Female 164.5 52.08 R" E/ j. r/ }# o' J! D$ `
96 Freshman Changmei Feng Female 163.8 56.0
$ v, f' I' c- o* {, } 88 Freshman Xiaopeng Han Female 164.1 53.0
0 G4 n" r* |+ L; f 81 Freshman Yanli Zhang Female 165.1 52.0
+ h0 M& Z( q7 A5 G4 {
. k& S! Y& ^2 r, r+ e/ e, @/ Q( o df.set_index('Grade').sort_index().head() # 索引排序
4 c/ y2 `5 |0 X ^% c Out[29]: / D7 ^& F( i) H1 o: Q6 n9 g! p; h
Name Gender Height Weight
- I/ s, s0 P# V1 P5 ?+ @3 u Grade 1 G! v( D+ _4 i
Freshman Gaopeng Yang Female 158.9 46.0, u2 o: Q& z4 s$ W! F% b: R
Freshman Qiang Shi Female 164.5 52.07 [7 U9 ?: f* v. t: g
Freshman Changmei Feng Female 163.8 56.09 m( y& N$ q8 R$ c- ^7 U4 [
Freshman Xiaopeng Han Female 164.1 53.04 {2 ^5 {' Z0 m% r# R. v
Freshman Yanli Zhang Female 165.1 52.0
0 p3 n4 k5 u+ R+ {" [- J0 D
/ |) h# c" ^2 i% f2 E$ q 1# V" o& o% s% s1 E% l& c
2/ K. d' w9 @! E* g' X* f
32 ^) s2 \+ K( v; n
4& z' l- u1 b- [' [/ F- N$ r; k% ~
51 }$ G( r a9 W
6( k$ N7 O$ i8 s
7/ \: s! T5 w$ ^3 Y" n, _4 C' J; h# ^
8% v3 u) ~$ p% |$ e5 N5 C5 p
9
! K: j# l0 s$ V' A) O% }. H 106 U! x r7 X" W5 {$ Z% F: }& L, h! [: y
11& D. i6 T: K9 o; Q9 E
12' D8 e; x$ P# A: _# Q6 Q8 j
13
# N; ? B2 v4 q; H$ D4 T 14
4 l7 Y6 L* \) {; H: ~; [' S 152 m% d( J+ O' |/ i: o" r" [, `
16
4 y# j3 O7 T6 `( m# h% a 17, c+ c" Z& n# U2 H
18
8 a, u8 @1 v- r7 S 19
; n& \, H# [- f( p6 i 20
2 i7 O7 w* g' m# Y* G4 x) n2 S 由于序的建立,因此就可以进行比较操作,方便后续索引操作。分类变量的比较操作分为两类:
( \; T0 D9 v" z/ u
7 D. P8 X+ h* t6 L- }. G) _ ==或!=关系的比较,比较的对象可以是标量或者同长度的Series(或list)。(无序时也可以比较)! }7 S) D0 |7 S1 k
>,>=,<,<=四类大小关系的比较,比较的对象和第一种类似,但是所有参与比较的元素必须属于原序列的categories,同时要和原序列具有相同的索引。, V1 `7 a, P/ x1 \% `/ a
res1 = df.Grade == 'Sophomore'5 v5 l, G& z4 C& u9 O$ _7 L9 T
( r/ D8 }; x. D$ q5 } res1.head()
1 F5 V. p6 v2 x9 r% C: O0 t Out[31]: ! J% j7 h; l, R# P: x" u
0 False
) p0 |3 i1 \4 s 1 False
6 w3 f. o( S+ {' U! y, c! w 2 False
0 c9 A- p3 L5 e) v7 P, q+ i 3 True" X9 H3 \- Z* {
4 True% G$ t3 S( ~! ^( ?- s* f/ k4 J( k
Name: Grade, dtype: bool
/ P2 _0 ], B, y , a$ m! B5 V+ Z2 [
res2 = df.Grade == ['PhD']*df.shape[0], s2 E( k2 {) T+ S& J8 x1 T
# v, b) I$ N6 E0 i+ }6 B7 E# D) H F res2.head()
4 e7 i* S& r5 [5 h6 u2 [ Out[33]:
* Y6 ^, y# l. }& o! X) t4 f$ [ 0 False- s; L( U6 Q/ V0 o8 N
1 False% H! H3 F: H$ S1 F7 V
2 False7 G; E4 u s9 N- q
3 False# N5 a0 a1 n) J; [4 \1 A+ L. g
4 False' a7 b) {( v0 m1 d8 V5 s3 s9 r
Name: Grade, dtype: bool
a3 W& d2 i; {5 P; y( p# q7 A
$ v3 C2 K# S( J9 Z res3 = df.Grade <= 'Sophomore': M' H' |- H. x1 S/ R; ?/ Y
9 ` J) K Z# C8 V; w
res3.head()1 X" l# g4 y& t
Out[35]: % c! I& i( L9 M: ~
0 True
3 \$ Z1 m7 k2 G2 K; s 1 True; d! L; @3 H' q
2 False0 ]7 W, s7 L( D
3 True
. u K: l! |$ X- Q2 H9 A 4 True
2 _) ? t' r+ m3 Q9 O Name: Grade, dtype: bool9 R" Z! o- N7 U1 V2 r4 h% s: K
1 P8 I, S% q& M' j
# sample(frac=1)表示将序列随机打乱。打乱之后索引也是乱序的,直接比较会出错,必须重置索引。
+ {5 Q) i' ?2 Q) f res4 = df.Grade <= df.Grade.sample(frac=1).reset_index(drop=True)
6 C4 ?; d% F2 H7 @ 7 f' t& u6 e2 `! E/ ?
res4.head()4 o8 y2 @: a2 e& Q0 j
Out[37]:
! t- d4 M5 a* o" H: \8 V& C 0 True$ G! b* u e# o
1 True
3 l# r5 d9 T6 i 2 False
. |) h" h- w# k4 Y/ {" I% m# a 3 True6 P1 _; R3 }/ A# m |/ V9 c9 @
4 True4 g# f0 Q- d1 E& T$ @; x
Name: Grade, dtype: bool
6 f" r9 p* h3 D' `, G5 T7 M @9 r) _ 9 w% H$ T" ?% l' b$ L1 {; d4 V; x6 p
15 E. `, I2 _ T- d% d7 x- e
2
, e6 u+ K8 e. t 34 J8 T( A2 B2 h& {3 \
4
! o! T8 f5 H: W8 |2 X 5- g( F) }4 z6 ~! ]- g u2 m$ [
6
6 ]8 W1 M9 z' k7 \ 70 ^& [+ L/ Y" P2 l1 V0 Y7 A
8
* b) b% [+ N' l, _3 _( ] 9
( h4 @+ d) `0 B6 E% C2 L; q 10
7 U+ ^8 f9 R/ S! l% U1 a 11, Z8 j; }# x4 V+ q; Q0 L' Y( b
12
' {- D* d' r( I7 @% }: y 13
+ {5 d, I+ s' {+ o 14
; f$ g9 e/ M5 }/ t 15
5 W3 n( L0 W) F- m( Q 16
M. O) J# _2 U, p, R. w* |& ] 17
! ?" u; R1 O: q6 P, _ 18
[' u& h3 Q- n; H. l 190 d2 d. q5 D5 A2 s, T/ H
20
0 p: f( P0 Q+ T0 T' k8 N$ s 21
0 o# r1 v' Z# }( m7 m3 h1 f 220 k! o- Q1 g2 q) e
23
! j5 ~' v5 n/ x' n- }8 ?0 N 24
$ O) _: h' ^" I( k" u 25
( v8 x- g# ~/ i/ }3 N 26" k7 I, n2 n5 p4 \- h" ]
27
, r# F) f& k0 V/ P9 v 28! R+ t. d; ]' w/ [; G
29% A9 D h8 ]( w. Z* \# o/ K
30
/ `, D0 o+ b. C( j0 V 31* t9 u4 n& D0 h
32" y9 Q+ E5 l- C% R) y, Z' c# s
33
9 @5 X8 O8 r o2 x) f$ m 34% e* F1 V* [ T6 B0 {7 c
350 b& w+ w8 m: x5 Q' u1 l% F- Z9 A
36# U0 H( D, ?7 J& z5 q2 e
37
3 p( J: J! _& z0 F. r h 38' k& [! F8 x$ E. e# F }- T
39- L! z N* V1 h' `, U1 Y6 {
40
9 a6 N- a' K! k! {' K 41
2 _0 d1 L; P3 O" n4 V5 R7 ~% o 42
9 ? Z# l+ w2 y 43
( z" {/ a9 }4 p4 x 44. T& G" h" F9 i* u) t
9.3 区间类别
, v6 M) B# j' _7 ?% G+ R 9.3.1 利用cut和qcut进行区间构造; ]" P3 D( r$ _- W
区间是一种特殊的类别,在实际数据分析中,区间序列往往是通过cut和qcut方法进行构造的,这两个函数能够把原序列的数值特征进行装箱,即用区间位置来代替原来的具体数值。! H4 B! U3 o1 R, j! l: ^! b
! a$ ^* S: A) t cut函数常用参数有:
" g- ]% V5 S* m; P' o bins:最重要的参数。
/ ~+ o! _/ _# g! e! C+ j) F 如果传入整数n,则表示把整个传入数组按照最大和最小值等间距地分为n段。默认right=True,即区间是左开右闭,需要在调整时把最小值包含进去。(在pandas中的解决方案是在值最小的区间左端点再减去0.001*(max-min)。)7 |7 ^+ t: N1 g/ f
也可以传入列表,表示按指定区间分割点分割。- a' @* W7 D+ Y' c/ G9 W
如果对序列[1,2]划分为2个箱子时,第一个箱子的范围(0.999,1.5],第二个箱子的范围是(1.5,2]。& l, a% j8 M# q# X* V4 q4 L w
如果需要指定区间为左闭右开,需要把right参数设置为False,相应的区间调整方法是在值最大的区间右端点再加上0.001*(max-min)。
- t9 d6 S4 b& U$ D; x
8 C) Z1 U( J K" c7 F$ A s = pd.Series([1,2]) {7 @0 {. O: @! k+ H
# bin传入整数7 D& C1 m: M" H! ^ P( O% s& n
6 [+ S6 ] j. z2 r" `7 H* t& L pd.cut(s, bins=2). g# a7 X4 {' p1 k( A
Out[39]: 4 T6 a }( V+ m6 T
0 (0.999, 1.5]* T1 R6 R2 ~- {$ B
1 (1.5, 2.0]
% n+ r7 @. v4 G! \" f dtype: category' a, k0 x" r6 J. D/ ~. I
Categories (2, interval[float64]): [(0.999, 1.5] < (1.5, 2.0]]
# J8 e$ a, v$ H' E- @; Z
' D9 p6 R! j2 ^, `/ \0 V pd.cut(s, bins=2, right=False)
( \8 _1 s/ B8 I5 L Out[40]: ( u% i( z! }' y' b
0 [1.0, 1.5): M F# _# M' e! r- Z) n$ u, S
1 [1.5, 2.001)
) Y, n o$ r9 E, \. T+ W dtype: category
! Z" b7 Y: v4 M+ x! ]% c Categories (2, interval[float64]): [[1.0, 1.5) < [1.5, 2.001)]/ ]5 s( X3 p) T" s9 ^
( a3 ^+ _0 _+ {0 J " Y8 i2 ]; C" O; i4 F1 Z
# bin传入分割点列表(使用`np.infty`可以表示无穷大):
( Y' m( j1 g( n$ S9 {$ y7 A2 t pd.cut(s, bins=[-np.infty, 1.2, 1.8, 2.2, np.infty])
9 l% e% e2 Y) v& {8 Z& p Out[41]: & m+ F# t: z' B
0 (-inf, 1.2]3 @( O9 u3 j1 D e4 p) @" @0 \
1 (1.8, 2.2]0 o, R; C9 P& ?8 O4 D! g2 i2 A8 t
dtype: category8 O0 l1 f- g& l! F5 v3 S
Categories (4, interval[float64]): [(-inf, 1.2] < (1.2, 1.8] < (1.8, 2.2] < (2.2, inf]]
2 H" d3 v: z$ E ( y, Y5 U% g+ f) n8 N& Q/ F" s
1
$ V" n; f2 j# x. _+ \/ e 2
: n$ {1 `0 c9 B1 T$ v n 3, Q9 H: ]% Y, D$ {( {5 a( K
4
) h9 j; `4 D' t* l1 G7 | 5' `/ q% i$ D+ q z+ ^
6
4 V6 x0 P! L: J6 z4 s 7
* k- Z$ [8 l0 k) J4 c4 c8 e. z* g5 k 8
8 \1 ~ J; B ], n4 v0 b9 V! I 9
) ~/ ?5 k7 a3 f0 N- y 108 ~ o* h2 U( U& g
11! F2 L" @: i' E- a
12$ \7 [* v" g4 Q; @8 `9 c, ~4 `
13; }9 l5 y1 X# N' o' s& M' f6 j- }8 Z, b
14
: k& L( j# j' m( _ 15
3 K# F O1 c, m' j" e2 U 16
/ C& |! ]* _' i9 r; f1 c2 z 17) U3 p5 ?" j h i6 F
18# ~, y3 k7 k o* B* x0 Z
19
, u' @' v! H; f# P 20# J' _1 W$ G. r4 S! g
218 r; R6 G! T. e2 r7 V
22
1 f/ U' `2 V1 M* l 23
4 H$ y W! n& I1 \% A: S 24
( p5 l2 b" J2 L0 S 252 q [0 o$ `2 F( a! {. h: i
labels:区间的名字0 M& K' T( n& C, f
retbins:是否返回分割点(默认不返回)! U- \$ r, H7 b) i e+ [3 Z' S
默认retbins=Flase时,返回每个元素所属区间的列表
5 L; ^0 t6 q5 S% Q( T$ H9 `- B retbins=True时,返回的是元组,两个元素分别是元素所属区间和分割点。所属区间可再次用索引取值
5 I6 {, W6 ~0 t1 p0 d7 |; ~
+ W, Q# H. ? L' Y5 Z: v s = df.Weight
( `% j# J' s1 j1 d5 x6 U( y) b+ ? res = pd.cut(s, bins=3, labels=['small', 'mid','big'],retbins=True)
2 `1 l8 a$ i/ g- t res[0][:2]
6 b# Y! O& |- R6 E" N. i1 m
8 O+ u6 e: c) P/ v Out[44]:
; x9 I2 K- Q+ J 0 small
1 t; f0 _7 J8 N" ^8 k 1 big
2 o) [, x5 i4 o9 F! A dtype: category, K3 Y( L4 G$ c# q, e
Categories (2, object): ['small' < 'big']
$ m; H/ |4 G* V% {6 N
9 B) k3 y, A* g# ] res[1] # 该元素为返回的分割点 a9 w" r0 b1 B3 w; a# h; Q
Out[45]: array([0.999, 1.5 , 2. ])3 ?' S% H( v& g
1
0 J- M4 X5 g+ ~- H 2( W( D0 s& _) h+ I J+ I
3
, h$ K9 P1 C4 G/ a 4# k7 C9 M5 q- P0 |" _
5
* s$ q4 f; c4 k, i 63 d# [# T1 P# t% R) v1 T& T
7/ V C. A6 V8 ^3 \6 c( E5 d
84 J; \7 n. R0 E5 l1 [0 q: H
91 B8 B7 a6 E7 Z- e4 N! \/ U
10* y! ~) P1 F7 Z9 H% ^' M
11
+ h. k- j- H+ g; q! \8 x 125 m* S* t; A$ K8 X5 S
qcut函数。其用法cut几乎没有差别,只是把bins参数变成q参数(quantile)。4 L m& N6 ~& K" v0 R5 [
q为整数n时,指按照n等分位数把数据分箱
) v; ^, t/ A. u( w. R0 o }7 c q为浮点列表时,表示相应的分位数分割点。( o! b' N$ A5 R
s = df.Weight8 P: @5 m6 S( z7 _
3 ^5 v7 \/ G+ D
pd.qcut(s, q=3).head()
& V7 x. ]) Q) \0 C+ J; {0 } Out[47]: 2 [2 N, I# g, N
0 (33.999, 48.0]- A7 w N8 p6 h+ h0 d& @
1 (55.0, 89.0]: W. G+ p0 Y. B4 [, C; K
2 (55.0, 89.0]
% j @# w- _; J. `, Z" T- Y' z. h 3 (33.999, 48.0]( G( I3 V& C# ]4 x/ Z& |
4 (55.0, 89.0]
. z9 C$ |: H' r2 \- B Name: Weight, dtype: category
# x. A3 g" S7 Q- W7 F2 n- U Categories (3, interval[float64]): [(33.999, 48.0] < (48.0, 55.0] < (55.0, 89.0]]
8 c6 h: _. X: ]& X7 _
' _' _& i+ d3 I8 C7 c7 L pd.qcut(s, q=[0,0.2,0.8,1]).head()
( w/ Z3 i% t9 v1 s5 q5 L. L Out[48]: 6 {/ U5 `. q, p% ` m/ G2 y" {
0 (44.0, 69.4]
, {( q( z0 u- W6 v 1 (69.4, 89.0]
7 n% {8 f; R- n 2 (69.4, 89.0]8 N0 l2 | L6 P/ a0 T' Z% \. l
3 (33.999, 44.0]$ b. O- n8 m/ H7 J* t/ W. F! T
4 (69.4, 89.0]( Q L7 I& Z. r4 k0 w# J
Name: Weight, dtype: category
6 j0 c( x1 D5 g! F0 K w Categories (3, interval[float64]): [(33.999, 44.0] < (44.0, 69.4] < (69.4, 89.0]]+ s9 }( t _6 G4 M) u' a: y# w
- s/ y% g" g+ Y" \$ C q9 w1 \/ c
1! E0 h$ H, n3 N# @+ y& [+ e
2* K3 t4 J- }* j
3- F$ }) J7 q9 T4 v7 q6 c
43 e7 P. ^/ k R2 @
52 K( U/ Z' L0 G
67 P1 T. }$ ?; ?: W
71 y1 O$ v1 ]% q, O
8
4 H- r% T n0 b d 9
0 c0 }' ^7 e7 ~8 C% W 10
/ ~) t4 C/ t" a/ o8 P 11
6 o$ W# S, A) p; |; s# W 128 Z# t& d4 K1 `; i
13
7 A# k0 R, x0 i) O 144 d' |! O' q3 j" P0 u: z. u
15
! \' Z0 J4 v- W( ~- G. b- o 16
m% M9 c# f( T0 v1 d- l- p' n* ^5 ? 17
% X1 y+ G3 U% i: e: T 18
7 D+ x: v1 W: E& D+ W; n8 w9 Y 19( q T) A+ s q
20
: U D+ O( Y+ D& i* K6 {2 i. L0 h4 D 21
1 P9 K( D9 K" X7 O 9.3.2 一般区间的构造8 M' }0 U" f; c2 z6 k: X6 }
pandas的单个区间用Interval表示,对于某一个具体的区间而言,其具备三个要素,即左端点、右端点和端点的开闭状态。
8 H+ g# A2 T& U1 ]* r
2 J2 y& z8 k( R3 R! c 开闭状态:包含四种,即right(左开右闭), left(左闭右开), both(两边都闭), neither(两边都开)。
G4 R: i' V D my_interval = pd.Interval(0, 1, 'right')( J& n3 g* W# ?# E3 A1 S" }
+ M1 T9 @( l+ m, e( o& |
my_interval# b5 }* j7 j; M1 }
Out[50]: Interval(0, 1, closed='right')
1 k E8 y9 G6 f 1
4 M( T8 s; S# D% ]! T9 q 2* e8 p7 y+ k6 W
3- g% d {2 ]1 w' K' ~) k# Z
4- l3 w& e8 k0 i4 F3 e) B6 F
区间属性:包含left,mid,right,length,closed,,分别表示左中右端点、长度和开闭状态。
: u4 p% |% q& n- y @ 使用in可以判断元素是否属于区间
) n" L' _" z6 z 用overlaps可以判断两个区间是否有交集:
( H- G7 t+ l5 M+ g" u' m% p d N 0.5 in my_interval+ V* O. ~0 _% F/ o8 X
4 L* n" |2 b+ g r7 ~8 G True+ V9 ?( w0 K& d0 }7 v( [
16 K) U2 _. Q+ @3 ?8 t3 s
2
9 D* J& }2 F, o1 A2 ]' T8 a6 w% ` 3
& M& s% a4 `& g: o0 D my_interval_2 = pd.Interval(0.5, 1.5, 'left'): u H+ e/ V, ~
my_interval.overlaps(my_interval_2), d: {" m1 ^9 V4 R3 @- M- _
4 k7 z) Q( s( [. k
True
- l; h4 @3 V+ p3 `3 X1 Y2 p9 n A 1
# v/ V+ O- [$ a, j: F* r8 _8 Y 2
: ^- f1 b" {1 |1 U3 [! [ 3
$ E' p9 x# \$ m4 m8 I" i 4; q9 Q' Y9 |. c; u! h" C
pd.IntervalIndex对象有四类方法生成,分别是from_breaks, from_arrays, from_tuples, interval_range,它们分别应用于不同的情况:
" D0 L5 }. L9 S7 x/ L0 J
6 _) }$ [; @3 t/ V H8 f: a k+ B from_breaks:类似于cut或qcut函数,只不过后两个是通过计算得到的分割点,而前者是直接传入自定义的分割点:
! ?) W' r, s; i ~ pd.IntervalIndex.from_breaks([1,3,6,10], closed='both')
) G/ K; G. [, S" U: Y % N* C Q. g6 h
IntervalIndex([[1, 3], [3, 6], [6, 10]],
' K) m& U( S1 z. m& ^/ s5 @ closed='both',
+ m: I; }' T- L0 }* Q4 U* ^. o dtype='interval[int64]')
( `/ b+ n$ I0 n, U1 x9 ]# G 1" v0 y& ]- Z- U
2. N8 ?) G4 a) |
3
$ i. H5 B2 I, b; e | 41 p% I$ l1 F! S3 p7 O' F
53 D/ Y2 a' w- @6 Q4 o* u
from_arrays:分别传入左端点和右端点的列表,适用于有交集并且知道起点和终点的情况:
2 c- R. z4 O, V7 ]3 Q( S4 T7 w7 Q1 d pd.IntervalIndex.from_arrays(left = [1,3,6,10], right = [5,4,9,11], closed = 'neither')
, e" y' \, q/ H8 ?" s % @, t0 O) u9 A9 H. s: _3 C
IntervalIndex([(1, 5), (3, 4), (6, 9), (10, 11)],) n: }8 ]# i$ h. A+ n- c5 [
closed='neither',
$ e$ E9 a( \+ q dtype='interval[int64]')1 `9 J7 g. n% t5 m7 O3 I5 w
1: h% f9 M! P) `+ s6 ~' P& f
2
$ y$ s$ H$ [; u" F 3
! p) _" u+ F; {; | 4* M* D# g6 r6 Y! k! O( T, N( @0 R
5: u C q! n" e5 I# i; i* Z- a: s6 L
from_tuples:传入起点和终点元组构成的列表:2 i& r( g. \/ O# i D
pd.IntervalIndex.from_tuples([(1,5),(3,4),(6,9),(10,11)], closed='neither')
6 l6 D+ ^# B8 w# x * F) d$ S. @/ B! X
IntervalIndex([(1, 5), (3, 4), (6, 9), (10, 11)],
$ Q; F2 t' m( W7 y g+ @7 C closed='neither',! t8 b+ |7 o" `; f9 t0 E. e/ ~# z
dtype='interval[int64]')0 \ M( E1 C8 l1 Y
1. u+ Z7 d7 c4 o6 f6 ]
2
, s" K# M0 e! g O( g. V 3
5 U( j$ @& H+ D/ S* ? 4
- J9 \$ K9 n8 s8 Z [5 v9 ?; [ 5
; C4 t/ b; C; ^) G interval_range:生成等差区间。其参数有四个:start, end, periods, freq。分别表示等差区间的起点、终点、区间个数和区间长度。其中三个量确定的情况下,剩下一个量就确定了,从而就能构造出相应的区间:
; t! ^' c2 z0 ?" u' C pd.interval_range(start=1,end=5,periods=8) # 启起点终点和区间个数
' J/ S9 n3 O; q1 f# ?; J Out[57]:
6 C9 Q; N0 n( v: W* [' p# J. f- k IntervalIndex([(1.0, 1.5], (1.5, 2.0], (2.0, 2.5], (2.5, 3.0], (3.0, 3.5], (3.5, 4.0], (4.0, 4.5], (4.5, 5.0]],
. a* I4 j5 ~/ o' B; W, \ closed='right'," w2 E' E U: H, a
dtype='interval[float64]')& e+ P A9 z) ?2 `2 @" g w
& O" L3 \ a) ]% h; ^ pd.interval_range(end=5,periods=8,freq=0.5) # 启起点终点和区间长度+ E/ R. F. I; o4 S& n5 j
Out[58]:
; U, ^4 [. w" k2 H$ c% @0 E IntervalIndex([(1.0, 1.5], (1.5, 2.0], (2.0, 2.5], (2.5, 3.0], (3.0, 3.5], (3.5, 4.0], (4.0, 4.5], (4.5, 5.0]],- B8 a/ U s( W+ c( Y& r
closed='right',
, [, G! Z- m5 k3 h( \# L dtype='interval[float64]')% n" \) ~4 k& h0 T# |5 W8 ~
1
2 M% ]+ |7 `% j$ O# Y0 }0 @, u5 b 2
4 x. `9 u6 e ? 32 K) ^9 ?. W, F. o1 b: E: U$ r% |
4
' r: S2 v, B) w4 _ 53 ?+ T; \% v" G( N9 C/ N
6
- ~& z. b3 J% f* g6 n4 T+ l: [2 j, U 7
) O+ {9 N2 _0 A3 ]3 \8 n1 }; h, E 8
& P c+ C4 Q) h: S: K 9
8 Z# F( v' s( b$ H 10* |; z) m/ J3 k) y. p
11
9 q/ E( D. p) c+ U( S 【练一练】$ a, L/ ~( N6 F E+ ^* v6 W" h$ _2 `
无论是interval_range还是下一章时间序列中的date_range都是给定了等差序列中四要素中的三个,从而确定整个序列。请回顾等差数列中的首项、末项、项数和公差的联系,写出interval_range中四个参数之间的恒等关系。
& L! D4 g' |' G# A
0 j1 `( Q& D/ T/ A 除此之外,如果直接使用pd.IntervalIndex([...], closed=...),把Interval类型的列表组成传入其中转为区间索引,那么所有的区间会被强制转为指定的closed类型,因为pd.IntervalIndex只允许存放同一种开闭区间的Interval对象。0 }' B# w8 B) X+ i+ x- H4 W
. o3 U( P* D4 k' A j" @$ v1 l
my_interval
5 R; I$ _- }& _1 R/ R8 X Out[59]: Interval(0, 1, closed='right')8 ~ F8 o. W& P ^2 s0 d
. N* ~( O' I3 ]) N
my_interval_2) b6 {/ {0 @! h9 C* j/ f
Out[60]: Interval(0.5, 1.5, closed='left')
$ w" P: {) d5 ?/ p) r5 y5 } # \% m" V8 X5 E
pd.IntervalIndex([my_interval, my_interval_2], closed='left')0 n9 ^, Q( D0 p: o% X& R
Out[61]:
5 F8 X( V/ r& \2 i IntervalIndex([[0.0, 1.0), [0.5, 1.5)],
, A* `& c) ^9 Q! i* L- j4 g closed='left',; Q) ~$ S! K1 b
dtype='interval[float64]')9 J# ^1 r9 u% c
14 _, Y l3 r" j* X
2
; g! a, t: ~/ N% n2 s4 B 3
0 b( N- O7 f: w* V- e- V1 U- J: D2 }8 W+ f 4
; Q! B7 m2 y/ _# W6 h2 _ 57 l8 I: `1 L6 j& i) L
6
, E P! B# G7 d/ D4 e9 b7 V 7
. _0 J+ c" [- T, P8 o, b 84 g+ D" U' j3 T. k* D
9
6 L" {7 b# h* p' G' h% K6 { 103 s: d/ O2 N" h8 C3 Q2 K
119 T6 a8 Y( O, ]: E( F* q# N; P5 A
9.3.3 区间的属性与方法8 B0 i' d8 i- e$ U
IntervalIndex上也定义了一些有用的属性和方法。同时,如果想要具体利用cut或者qcut的结果进行分析,那么需要先将其转为该种索引类型:
6 Y% B/ L" I, E' m
+ X, G# X# Z1 p( T0 n" K' n6 G4 N s=df.Weight
; b! A: o0 k1 k4 x) r id_interval = pd.IntervalIndex(pd.cut(s, 3)) # 返回的是每个元素所属区间,用具体数值(x,y]表示+ L3 z; s$ |# I$ r( g+ \
id_interval[:3]
% W3 u9 f+ d, V; ]# z* s / e( G" r6 X9 O2 T$ Z( ~
IntervalIndex([(33.945, 52.333], (52.333, 70.667], (70.667, 89.0]],: W! e2 `+ ?# X) S* i; T1 r7 O
closed='right',
9 @4 T; v3 W1 \& @+ T" D( Z5 g) Y name='Weight',
8 i# P: Q1 _' k' g! x7 ~. A dtype='interval[float64]')
. O: P! @3 g% U# }6 `2 I 1
% g l) p% o" _ M/ _$ U3 T1 i# i 24 J7 C! z/ d3 ` W. x
3
# Q( D2 h u/ H: d/ j 46 H0 [) |( n( C
5, j( U4 d/ A+ i$ n, o2 I2 i
6" u4 O! T/ I+ R5 D
74 k0 G$ ]6 d2 ^) d7 H/ D
8, p, W" ~8 v2 q; `# r
与单个Interval类型相似,IntervalIndex有若干常用属性:left, right, mid, length,分别表示左右端点、两 点均值和区间长度。
3 ^: t3 T" u2 k1 | id_demo = id_interval[:5] # 选出前5个展示4 b& i7 `' b ~7 j' n: j
. b+ ~3 Y8 p2 Z$ b/ X4 a
id_demo
* A7 d0 W, W2 [. H" |4 _: O" i" g- f, m Out[64]:
3 I+ Y4 _& V% G: N/ |0 m. T+ Q2 D IntervalIndex([(33.945, 52.333], (52.333, 70.667], (70.667, 89.0], (33.945, 52.333], (70.667, 89.0]],7 I k, P' T, B9 D
closed='right',6 J8 t, ~+ n: N- U) ~8 f1 d
name='Weight',
# s, K7 O( z1 [! P) d; f6 { dtype='interval[float64]')4 N g# u2 K+ v) T" d) |/ f( t
+ f; R1 c7 r$ |- S" x id_demo.left # 获取这五个区间的左端点& L& g$ m" q4 G1 x+ Y
Out[65]: Float64Index([33.945, 52.333, 70.667, 33.945, 70.667], dtype='float64')& C) F) e' Y! M/ I3 W
9 ?3 u6 L2 } s! O. i C
id_demo.right # 获取这五个区间的右端点
0 R1 D) ~0 I5 R8 i- m$ Z- J Out[66]: Float64Index([52.333, 70.667, 89.0, 52.333, 89.0], dtype='float64')3 F' r2 e6 `2 G" D
/ n. p% }' k9 q7 A) T* M
id_demo.mid
4 n/ B0 m: e, ~, S% T& [ Out[67]: Float64Index([43.138999999999996, 61.5, 79.8335, 43.138999999999996, 79.8335], dtype='float64'), Q/ B& T6 N% X. m; O! [2 p) i
+ Z* w5 ?5 C. y id_demo.length8 g$ G+ j# ~$ a# {; H* L2 J5 q
Out[68]: ' ]9 Y1 d0 _' S z* l5 v
Float64Index([18.387999999999998, 18.334000000000003, 18.333,
* J8 f& A+ X6 {0 F+ G" F 18.387999999999998, 18.333],; O5 v$ S- ~- P: {% D' |0 v
dtype='float64')/ P5 z" E8 v) A8 u
- V) t* ~& @6 y$ K P" ~" y 1* _& A6 b' i3 F% ~: {
2" Z% D2 w& D" ]' v& w$ r
3/ ^8 }! S% f7 n( v
4
4 n; p# m# I# h8 S2 e 5: n3 j6 l; J" L$ B( @3 k
6+ v* c3 K5 M7 m4 Y) w2 ~
7
# o' U7 L( Z9 M4 j0 _' t* I 89 u( j' Y4 ]$ X4 x* M% x3 N/ m
9
& v; v& ]$ |1 N 10
7 k- Y7 v Z: U q0 n6 u- G7 k 11( G u( n- X9 Q1 }
12$ L2 O2 F- S$ r. b1 }, |
13/ c2 H7 \8 L- m6 `7 R3 t
14+ G) @+ w: B. s+ u5 }
15
! I& C% v- c) q$ B4 ` `+ |0 {; O 16
+ B. A& O8 E( N7 D3 | 175 s. b5 y6 _7 f# d$ d/ i9 G4 s$ |! {
188 d2 `3 b- U' o
19
; k; C* U9 I' v 20( z+ N( M5 w( J, ~- W6 ] w
21 f9 s+ t/ S: p( y7 G4 r
22/ |& T5 [& D. P2 o B; l: d: `
23' C5 U0 F/ z0 c1 x# J4 C- m
IntervalIndex还有两个常用方法:, I u: e" F- W
contains:逐个判断每个区间是否包含某元素* R$ c5 P! u( l! l/ Y+ h- \: T
overlaps:是否和一个pd.Interval对象有交集。
4 P* M6 }1 q% X/ [7 s2 w V id_demo.contains(50)0 a1 Z' [( g- R0 T. r
Out[69]: array([ True, False, False, True, False])
7 e8 {8 Z( a; ? c1 v1 Z0 N
7 U r: p) n8 J3 M; J id_demo.overlaps(pd.Interval(40,60))
1 y' p! S ]) y1 i1 m Out[70]: array([ True, True, False, True, False])
+ z" X2 V5 A ]" T# h 1
2 P6 J5 M+ z m5 R) K y. c- z. U 2
' E3 V: H; A/ j5 e; G I 3# h' a1 v H+ N
4
8 y8 W4 K0 r, ]6 _; s. s! t 53 j3 M* I2 Y% [9 K5 j* H
9.4 练习
' U* o9 L) x% Y: c) ~( c Ex1: 统计未出现的类别& G* B8 \ T8 w: v5 J: N
在第五章中介绍了crosstab函数,在默认参数下它能够对两个列的组合出现的频数进行统计汇总:3 i, C1 o4 H' @2 ^* B8 i
# [1 Z" \0 c6 q9 ?* |+ g j df = pd.DataFrame({'A':['a','b','c','a'], 'B':['cat','cat','dog','cat']}), I( A7 g: i: k1 N: h
pd.crosstab(df.A, df.B)% n* ^' k+ s2 L3 B
* p5 J z7 @) Z; f% V
Out[72]:
5 Q1 R6 R* o& B1 Q# b B cat dog
0 V& L4 R, H3 n4 t# F* p" R/ g6 t* C A
: b" G: N" i* u+ m j I8 D3 {* Q/ T a 2 0$ z ]0 W0 F- t" M& }
b 1 0
# B+ l- Z+ `. G) Y" J$ E% R3 k5 b c 0 1
9 U+ y" \2 s9 b8 l 1
) }8 o: v$ ^0 @% Y 2
8 C4 b7 F7 |; G) A9 U; }+ G 33 g. w: A: s) j) |' ~1 q
4
; |( g6 Z+ B" H 5
( G8 s! T( s- L) Z* ?0 |* o 6
$ Z* O; j6 }8 { 78 J9 }5 n" C3 F( x W: H7 c5 t C2 d
87 s# e! c" s Q3 L1 D5 W
9
: S$ A8 x! o o 但事实上有些列存储的是分类变量,列中并不一定包含所有的类别,此时如果想要对这些未出现的类别在crosstab结果中也进行汇总,则可以指定dropna参数为False:
8 `: L/ a8 Y7 g5 l5 d' \ Q3 e% u ' J/ }8 {& `* l/ S
df.B = df.B.astype('category').cat.add_categories('sheep')! o+ `9 q9 I8 t+ m& c8 T: ~4 `1 O
pd.crosstab(df.A, df.B, dropna=False)
; d, r2 K( G. ` # l0 s7 `; C1 ~- G
Out[74]: $ A7 u3 O# z0 J& M# U
B cat dog sheep2 O( f, W9 x2 W6 V3 t9 N
A ! U/ U# J& v7 }+ p+ p( k
a 2 0 0; s: T# r; B- b- K
b 1 0 0
$ l& H' A6 ?* d" `. i$ N& N* K c 0 1 0! V: L# m- \2 I$ T# |( Z
14 O! G0 X7 Q3 {/ Z5 h+ _. F
2
' V! D; m& `6 d% X* Q3 p _ 3+ i" e G( A4 X) G4 t( q1 X
4# s9 m! D* d* }; O2 b
5; x/ u7 O# R/ q) B
6
/ C4 H; n. P: O7 j 7
/ R0 P0 H! H u1 a" A# F. t* c4 `' S 8- E( O2 T+ @( V1 v6 P7 R; V, a' i" y
98 @ o: M, z1 G# e" ?' R
请实现一个带有dropna参数的my_crosstab函数来完成上面的功能。
6 u" L& x0 C/ D; U. W+ V! Q/ ] % F7 f. }6 E, t# }
Ex2: 钻石数据集$ n+ I+ ~0 H6 y( t O1 V, q
现有一份关于钻石的数据集,其中carat, cut, clarity, price分别表示克拉重量、切割质量、纯净度和价格,样例如下:
( f& v. d+ \' z. l4 A& C# g - }: ]) S: }# p6 Z' [+ c
df = pd.read_csv('../data/diamonds.csv') ' a- |7 h) q% Q8 F/ j
df.head(3)
5 L4 v6 X" l' Q2 l + v* Z" H! X& I: }
Out[76]:
* D C) F% T$ k: S! X! E, e& n carat cut clarity price7 @' t8 P; W0 V" V2 s) v) C4 A
0 0.23 Ideal SI2 326
# U5 l% K* H6 W0 w 1 0.21 Premium SI1 326 e" Z" S% T5 z8 z# N- i! W: Z7 \
2 0.23 Good VS1 3274 a+ V0 O6 q h4 w( W
1
6 K" W9 N, h- ^% n: B 27 \1 V/ B) j o) U K
3
) u$ j T! E8 ~( H" t0 Q3 R 4# T1 q& Z/ N+ b0 Y1 _/ M
5
5 Z6 n9 M& F) J3 G 6
7 W8 L- ^" O* l9 O1 P3 z4 U 78 N* q' G: a9 I0 I# w
8
, ?) y' D- ~0 C9 m 分别对df.cut在object类型和category类型下使用nunique函数,并比较它们的性能。3 C6 g+ E* R: T
钻石的切割质量可以分为五个等级,由次到好分别是Fair, Good, Very Good, Premium, Ideal,纯净度有八个等级,由次到好分别是I1, SI2, SI1, VS2, VS1, VVS2, VVS1, IF,请对切割质量按照由好到次的顺序排序,相同切割质量的钻石,按照纯净度进行由次到好的排序。# X$ i/ V8 B [0 z1 s8 x4 k2 P
分别采用两种不同的方法,把cut, clarity这两列按照由好到次的顺序,映射到从0到n-1的整数,其中n表示类别的个数。6 ^2 p" m& K5 ~2 x$ G
对每克拉的价格分别按照分位数(q=[0.2, 0.4, 0.6, 0.8])与[1000, 3500, 5500, 18000]割点进行分箱得到五个类别Very Low, Low, Mid, High, Very High,并把按这两种分箱方法得到的category序列依次添加到原表中。
# u+ e' m( K# d' V 第4问中按照整数分箱得到的序列中,是否出现了所有的类别?如果存在没有出现的类别请把该类别删除。
6 I0 I S! D" e- r: M 对第4问中按照分位数分箱得到的序列,求每个样本对应所在区间的左右端点值和长度。
9 C* Y- m! J3 M5 x3 j) |$ y 先看看数据结构:# l8 l+ J# s/ n; @0 i! ^3 S
- s2 F7 _: {" K% G' w df.info()& p' I" W5 Y7 Y! V0 U" j3 L/ B
Data columns (total 4 columns):
" ?; Z [) t' y" U! | # Column Non-Null Count Dtype
5 A9 Y0 R8 n$ v; j/ k1 i9 h) a --- ------ -------------- -----
3 q# n7 W% c' F( W6 L4 | 0 carat 53940 non-null float646 j* L' D4 l- D+ l$ h7 @
1 cut 53940 non-null object
# N6 O. J; F( k& ` 2 clarity 53940 non-null object
g* V8 i. R) n2 `. l" ^ 3 price 53940 non-null int64
5 i8 q6 U* a5 W& v" \ dtypes: float64(1), int64(1), object(2)9 x6 a I' j" s2 L+ ]3 A7 _
1
% _: c% y' t- c6 ]: _' c 2
5 l+ h- x8 j! Y! ?( E/ X! | 3
$ Y" U; P# @# E) k4 o* @ 4, [8 d* n2 m7 O8 O8 y
5
8 U3 p, C# ]4 Q* f) a6 a% ^ 67 h+ l) F( b: d0 ?: |: a, t3 U% q
7
8 J7 d! ], |- \6 c. v* I. P 8
8 Z' O B6 D3 ^1 W- ^& { 9
3 d4 I) @2 m, j6 F" R: K. j! V 比较两种操作的性能% d! u e/ A$ f" |+ e9 Z4 l8 G
%time df.cut.unique()
, b6 m+ d, [1 t! }" O - S+ l. \1 Q2 N* R
Wall time: 5.98 ms1 g) |! Y r8 o# S: ?$ }( w
array(['Ideal', 'Premium', 'Good', 'Very Good', 'Fair'], dtype=object)2 e- I% Z9 M9 R/ d g
12 R" x1 W7 |# |: ]0 j2 a5 p/ X. f: b
2
1 z* L9 P, L1 B; S0 R$ G' z9 v) u 3
( M7 K: u) Q9 i6 K: N 4
& K( p4 A. `7 d- M$ a! u; {- _* q t+ s ~ %time df.cut.astype('category').unique()
, j+ y* N% Q3 q5 F$ l0 i1 \1 i 9 h# T+ r7 u, Q9 L! y" V
Wall time: 8.01 ms # 转换类型加统计类别,一共8ms
2 P7 H/ I. a$ d* d" G9 J ['Ideal', 'Premium', 'Good', 'Very Good', 'Fair']4 m: j f, P* |& _# u6 ~
Categories (5, object): ['Ideal', 'Premium', 'Good', 'Very Good', 'Fair']
+ ~; f" Z# f2 c 18 r9 g& y v& a3 o( a% a) v i
25 ~& [" d. O$ y. P+ ~" I1 @
33 b, v9 @. d9 w4 K# W% p8 o
4. F% z9 v2 H5 v3 I# ~
5
9 Q: v" I9 b# U: G" I! A df.cut=df.cut.astype('category'), T- v( m: C" X. C) E% K7 D, H1 B
%time df.cut.unique() # 类别属性统计,2ms; g( w2 T9 l6 h9 o- _
R$ @4 j% i7 m* j Wall time: 2 ms
. y5 ?0 `$ ?) p0 ` ['Ideal', 'Premium', 'Good', 'Very Good', 'Fair']) S% l ?$ ?* j+ t, @
Categories (5, object): ['Ideal', 'Premium', 'Good', 'Very Good', 'Fair']
7 P4 z% O2 j1 H) G# u- q 19 \, f. o! \" P I
2
- W, [+ D# U3 F- l$ A 3* L7 r8 A1 d( }) X
4
# o2 G# B! z' B2 g8 a 5( O* R6 U8 G( r! G
6! r! S+ M4 }3 d+ m
对切割质量按照由好到次的顺序排序,相同切割质量的钻石,按照纯净度进行由次到好的排序。
0 X) T u6 l. U+ H; j. _. H ls_cut=['Fair', 'Good', 'Very Good', 'Premium', 'Ideal']$ e1 Q1 F# `8 ]& m5 t8 E1 x# e
ls_clarity=['I1','SI2', 'SI1', 'VS2', 'VS1', 'VVS2', 'VVS1', 'IF'] D/ {4 ?1 j& a9 g" t
df.cut=df.cut.astype('category').cat.reorder_categories(ls_cut,ordered=True) # 转换后还是得进行替换
7 X) |( O7 E* V' Z! E4 |" ?1 Z df.clarity=df.clarity.astype('category').cat.reorder_categories(ls_clarity,ordered=True)! P0 G. e2 u; h' v
6 g. ~2 a' E2 _$ D0 A/ F
df.sort_values(['cut','clarity'],ascending=[False,True]).head(3)( q7 Z: N- R; \7 a6 ~! S
0 w4 Q; P7 e# e" j8 b carat cut clarity price
: _# Z! j M" W9 r$ g( G 315 0.96 Ideal I1 2801 v8 G9 u4 ?6 r
535 0.96 Ideal I1 2826
" L' O) R/ h1 Y% R: [2 v* |8 q% f 551 0.97 Ideal I1 2830
/ q) ?& [! |. A N/ V2 s+ ? 1* H+ d6 L/ e) n7 y" T
2
! M1 ~7 w' L+ r" y- |9 O 3
) ~* A+ U9 t: Q- L$ t 4' I1 d! t9 N# k! H4 l
5, v3 g# x" J$ u2 i9 K, D5 Q& Q
6
) d& v7 l9 [# N- Q5 W 77 T7 X. a4 H9 ]& ]2 n
8
, h5 I% F, ]7 W6 H, V4 }) V 9" W8 U' _ f$ ~
10
' \3 B& h, W) Z" S% F/ x9 c 118 i# H+ V% |$ o' R( N
分别采用两种不同的方法,把 cut, clarity 这两列按照 由好到次 的顺序,映射到从0到n-1的整数,其中n表示类别的个数。
8 Y: \8 a* c: q- d/ S0 { # 第一种是将类别重命名为整数
; ~0 i" R. e+ l$ w dict1=dict(zip(ls_cut,[x for x in range (4,-1,-1)]))+ K; J% n& S) {- }8 d% u/ F. W6 M
dict2=dict(zip(ls_clarity,[x for x in range (7,-1,-1)])): m( X$ I4 n3 m( G. O
: y0 b' k3 P$ D2 I4 o df.cut=df.cut.cat.rename_categories(dict1)" m3 J3 _+ A" K/ q+ z1 k/ Y5 d
df.clarity=df.clarity.cat.rename_categories(dict2)% k5 S( w' P. g/ g j- T5 o
df.head(3)) k3 c& c O! ?: e ~8 n# b' {
2 Y5 {3 H& V4 w# K% J carat cut clarity price
. `. `2 g! i8 S& Y5 J 0 0.23 0 6 326: h% E2 }$ O) P) e% o7 ^& L. {: h
1 0.21 1 5 3269 y8 B9 ~+ p7 D# |
2 0.23 3 3 327: }1 |- ?1 }7 S3 k L+ [9 ]
1
R( ^1 y% e) f 2 H4 A: t' O7 n; i' |
3( u6 g9 m' Q( M, k2 t2 l: T) ]
46 @: T# z+ d! B2 q+ P
5
5 C+ n X! m' @( m 6$ P% c+ a! @) ~. c4 ]7 W/ a
7
9 K$ h, i0 U& n0 k1 J0 } 81 T2 e/ Z7 A3 C( d0 @! `
96 ^7 W7 g4 [% M& a& A$ e. U
10, U2 Q5 B1 |5 Z) V/ o3 s
11" K! D. x' I \* Q
12
4 n" ]% k' }$ \: L: u # 第二种应该是报错object属性,然后直接进行替换
- V( ~7 P" X3 v( Q& z df = pd.read_csv('data/diamonds.csv')4 F7 X5 M. S9 s8 B# p
for i,j in enumerate(ls_cut[::-1]):
- K/ ~% ^4 N' } m0 X df.loc[df.cut==j,'cut']=i
: E. `7 ^* c: |
1 C' a1 m% K% }8 f' ^3 }/ [ for k,l in enumerate(ls_clarity[::-1]):: a2 W6 Q. ]" [( B' c# Q3 B% [
df.loc[df.clarity==l,'clarity']=k4 }2 m- Q4 f3 N( e$ K
df.head(3). A+ R2 L' E4 E% V: x A" [
/ Z( f9 S9 `' ?2 \6 b! T; t
carat cut clarity price' w! y" L& E3 V* J% [ }( v
0 0.23 0 6 326
7 f; q0 k5 E/ c; R$ F 1 0.21 1 5 326/ s1 f3 A' l* P/ ]( | C4 n
2 0.23 3 3 327/ R) J2 e% u m+ {6 ~
1
# ]4 X3 p) H$ q5 K( d7 J: v 21 R% }& s& h4 ?9 K/ C* M
3
( t* @3 j8 H7 a, {: b0 A 4
( x4 @, \7 _) Z. K8 S 52 V. G% z/ x0 k( G" ?
6
0 ?- I4 C$ f& a; N9 ] 7
1 G1 V4 U- p+ ]8 y. v 8' s8 }$ J2 u5 e+ z& E3 e4 _) i
9
( j6 d: p* S A0 W% R 10) L! |* `& h' L, z7 i# @2 @# @
11* R" `! z- X2 @' z4 Q5 t
12
; D5 w, D, V4 { N( e8 C3 W* P4 E2 O0 v 13
" [4 _; K; M" c9 n- a2 g' f 对每克拉的价格分别按照分位数(q=[0.2, 0.4, 0.6, 0.8])与[1000, 3500, 5500, 18000]割点进行分箱得到五个类别Very Low, Low, Mid, High, Very High,并把按这两种分箱方法得到的category序列依次添加到原表中。
/ ?* S+ C# q/ R8 O3 L. z # retbins=True返回的是元组,第一个才是要的序列,第二个元素是分割点/ a* |4 \) E3 l
avg=df.price/df.carat; Q( s: ?& g. j* [
! u1 \+ Y2 c8 p! @' u! k3 p |' W
df['price_quantile']=pd.qcut(avg, q=[0,0.2,0.4,0.6,0.8,1],
9 }. }3 X3 C! I& I/ ? labels=['Very Low', 'Low', 'Mid', 'High', 'Very High'],retbins=True)[0]$ {; e* W* E# M q- E
' v& L& Z6 j4 h, t4 D# }
df['price_list']=pd.cut(avg, bins=[-np.infty,1000, 3500, 5500, 18000,np.infty],
* X1 V4 { {, h labels=['Very Low', 'Low', 'Mid', 'High', 'Very High'],retbins=True)[0]
`9 Q" f( V H- n/ T- t& N df.head()6 \8 T( s2 b8 n: ^* b' q, ?. J, G
. D a- y1 Y) X
carat cut clarity price price_quantile price_list- w4 G4 A7 ~7 U4 ~1 i7 h
0 0.23 0 6 326 Very Low Low
, R$ r( \' T( R% c7 x! u2 f 1 0.21 1 5 326 Very Low Low
' O. w5 D# o; a" H 2 0.23 3 3 327 Very Low Low
9 r3 E r1 X0 Q4 O 3 0.29 1 4 334 Very Low Low
/ U2 F, ]- D6 b4 S% N) W 4 0.31 3 6 335 Very Low Low
" ]! U7 w. @2 b5 L% _7 p3 O
) w. ~! w5 b" r7 K+ ^ 15 | p9 M+ z: g9 {/ z- m \( ]) W
2
1 S# L! \& \" g 3
9 b2 n* J u) @- T" @; R2 ]- `" y 4
8 d# l7 Q; i( n- N: u& w8 a 56 a* @% I2 Q6 O3 e5 v
6& i' j9 @; R2 P& m2 t
75 h% j3 F! Q9 R; ^! X1 r( l
8
4 k& _. P; D8 f( `) Z 9$ n1 s1 D' E& r+ j% R1 z
10( H3 U4 M& c4 L, \& b+ G
11
: `' H9 X/ U- u 12
3 c0 N0 N5 _3 v4 ? 13
; S( `" s9 R% a. y 143 [) @- |9 X3 g! L* o
156 L W- \2 p2 R8 E2 Z
16
: W8 U+ I' x& J3 j 分割点分别是:6 N1 f8 L' i# P" h4 T3 c: N- N( @
' x' N* n0 e1 X0 ^7 }+ Y% [ array([ 1051.16 , 2295. , 3073.29, 4031.68, 5456.34, 17828.84])- j$ M0 u6 \! E' e: [
array([ -inf, 1000., 3500., 5500., 18000., inf])
6 i3 n) n. g, x3 E8 Q 1
# U( w- s8 ?8 R 2
2 p) G# }9 A( i/ q# h 第4问中按照整数分箱得到的序列中,是否出现了所有的类别?如果存在没有出现的类别请把该类别删除。
- H: C( _5 F6 b" J- N9 C+ ^ df['price_list'].cat.categories # 原先设定的类别数/ m* a0 j. Z" {. U# a' Y
Index(['Very Low', 'Low', 'Mid', 'High', 'Very High'], dtype='object')
8 g% r# R0 }1 I( c7 Q! t
" ^7 L5 \0 a9 L: g# m5 ]- ]4 s df['price_list'].cat.remove_unused_categories().cat.categories # 移除未出现的类别% r/ n( h1 c, u k0 k& F+ m
Index(['Low', 'Mid', 'High'], dtype='object') # 首尾两个类别未出现
+ J- x$ C0 r: B* x 1! Q* M# e. D, b+ m$ F
2- s) l5 G9 y- c
3/ y" M+ [9 d: ] Q
45 |7 \ t% U. j! c" N: R$ A
59 J: g0 @- S6 L& o v# m
avg.sort_values() # 可见首尾区间确实是没有的
4 j O* y* E6 I 31962 1051.162791
; [& ^* s; |- i2 j) F 15 1078.125000
" E. n2 f9 `' R% h 4 1080.645161
( U& C, t0 [5 i, Z1 l 28285 1109.090909$ E% c# u) P) x( b
13 1109.677419! @7 K5 Q3 M) J6 J* J( |% `6 ~8 j* Z
... # B# t9 V$ ] E7 ]$ B5 |) r
26998 16764.705882" I: q- B4 k q* b! a* }, y$ N! O
27457 16928.971963
6 C C3 }7 k, r 27226 17077.6699030 k! z/ q: Z. n/ F0 O; C0 c, J& O
27530 17083.177570+ b/ }$ N" B0 o7 m: d4 o
27635 17828.846154& |! o$ E1 O! \ i5 M2 j$ c
1% t( V5 e3 {+ @5 e- m
2# E$ p" K: Y0 E9 d, M) ]
3: G4 C5 t- `% }1 ^8 [1 t6 ^1 y
4, V* [7 v3 }1 E4 G" i" S
5
/ l. S; x; K; `" C+ x6 C% I3 ?- c 6; y d: J: {5 Y' i6 }/ p! R7 r# Z4 `, Y
7# v1 L' R3 N& ]; a. X% u
8
/ N) T% w0 |# |: Y6 Q- z 9
8 R8 r' u( H! V, r9 v 106 x5 @ ~& [4 Y' J. z
115 t5 L' ^" O% `6 \. h7 V3 H" {2 F- k- P
12
: R; y: R0 N2 ^, t8 v4 G q 对第4问中按照分位数分箱得到的序列,求每个样本对应所在区间的左右端点值和长度。
. W# q6 z( ]# R # 分割时区间不能有命名,否则字符串传入错误。+ ?6 [- L' M, Z0 ?) s' n
id_interval=pd.IntervalIndex(1 _$ e) v& }0 a U/ G
pd.qcut(avg, q=[0,0.2,0.4,0.6,0.8,1],retbins=True)[0]
# l+ j) X: A- W6 m5 ^9 A6 n )2 L! _0 t, s: \+ T% Z, x
id_interval.left7 P6 T9 ^+ m& S5 @
id_interval.right' y; M. R$ Y0 @7 q( e6 `6 p: @$ O
id_interval.length
" m% Q; _ B; |9 K8 X" z. { 14 V: h' z! j+ Y" h
21 @& L8 C/ a8 A" V2 \% P/ i
3- g' Y* n6 L3 m* Q+ w5 d, Q
4$ |5 K& P2 C# c& Z4 Y( e
5
4 Q8 E) P# g- W0 B8 O 60 b3 u1 c$ h& x+ V; G5 Y
7
0 s. M6 q+ F# v" ^ w 第十章 时序数据
4 i) S# @: O6 ?9 u. t. E$ w g import numpy as np6 ^' J7 E7 N, P0 G4 k. ]% r
import pandas as pd6 Q# ~# A; d' u3 ^
1
) k# w: K# r/ B# k; |& f/ p 2
$ W1 P1 B& j/ y$ U$ m5 X/ b7 P
% |9 N! H! }( U5 \7 ^
6 Q, j" B. Y# S; s) } 10.1 时序中的基本对象$ b5 Y- Q- H9 l- K }' W( f
时间序列的概念在日常生活中十分常见,但对于一个具体的时序事件而言,可以从多个时间对象的角度来描述。例如2020年9月7日周一早上8点整需要到教室上课,这个课会在当天早上10点结束,其中包含了哪些时间概念?0 ?. S& Y: H0 Z" i+ `+ F
! }9 N8 y2 ~8 ]5 B6 w5 D
会出现时间戳(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的简写。4 m* N% Z6 e Q) ^
9 D z5 X* j5 M& ^$ u, y+ D& S
会出现时间差(Time deltas)的概念,即上课需要的时间,两个Timestamp做差就得到了时间差,pandas中利用Timedelta来表示。类似的,一系列的时间差就组成了TimedeltaIndex, 而将它放到Series中后,Series的类型就变为了timedelta64[ns]。) p) ?& t2 Q. u
+ x" |4 m+ Y1 x0 d; ]5 q6 p 会出现时间段(Time spans)的概念,即在8点到10点这个区间都会持续地在上课,在pandas利用Period来表示。类似的,一系列的时间段就组成了PeriodIndex, 而将它放到Series中后,Series的类型就变为了Period。4 P7 h/ G* J/ F$ c/ s* m
; B: }2 D4 o: R1 A! s
会出现日期偏置(Date offsets)的概念,假设你只知道9月的第一个周一早上8点要去上课,但不知道具体的日期,那么就需要一个类型来处理此类需求。再例如,想要知道2020年9月7日后的第30个工作日是哪一天,那么时间差就解决不了你的问题,从而pandas中的DateOffset就出现了。同时,pandas中没有为一列时间偏置专门设计存储类型,理由也很简单,因为需求比较奇怪,一般来说我们只需要对一批时间特征做一个统一的特殊日期偏置。
4 A" J; a& E- W# V4 z D( ]2 m3 J 1 b% U7 e# h+ f- [9 ^
通过这个简单的例子,就能够容易地总结出官方文档中的这个表格:
5 g6 H4 g/ A2 i5 ]! E ' g, i! t: ^2 W+ t/ v2 o7 T6 \
概念 单元素类型 数组类型 pandas数据类型
9 Z" M. \5 {/ l) v7 r% x& P Date times Timestamp DatetimeIndex datetime64[ns]0 [8 h1 g# I; f) ?
Time deltas Timedelta TimedeltaIndex timedelta64[ns]5 q1 ^/ Q g! M* _, q
Time spans Period PeriodIndex period[freq]- d. f, S$ w7 U. [- w! H( C# C
Date offsets DateOffset None None3 x8 g) C' B- u( a7 a
由于时间段对象Period/PeriodIndex的使用频率并不高,因此将不进行讲解,而只涉及时间戳序列、时间差序列和日期偏置的相关内容。 }3 V0 `& R6 m) A* e# D
; b# j" b0 |$ W% T* U
10.2 时间戳7 ^) ^. N N$ [) T7 E0 s
10.2.1 Timestamp的构造与属性; n: x3 m b9 N; s
单个时间戳的生成利用pd.Timestamp实现,一般而言的常见日期格式都能被成功地转换:. D, E/ R, K' B" Q5 E
1 k2 \ h0 Y! L7 @+ }
ts = pd.Timestamp('2020/1/1')
- ^# K% s( y' Z( R& H; e6 ]) i
- c8 z/ s4 Q! X8 i ts
) q! o. Q8 R! T% v% C Out[4]: Timestamp('2020-01-01 00:00:00')& ^6 t: B" X* T Q- R; t. A( N
- Y8 ]* ?7 G: n/ { E ts = pd.Timestamp('2020-1-1 08:10:30')6 N/ n' v$ Q! W, V2 h/ r; o. g
+ _- d& x: j4 @2 e
ts
: P( n' n" Q+ @& I' W. F Out[6]: Timestamp('2020-01-01 08:10:30')
: D8 _. a5 {2 l8 E z+ |0 S, E* F 1# R3 u6 R1 z* W
2
/ I* i B; B9 w6 T, K 3
, B6 a2 Q2 w _5 L5 m 4' r. N" u2 J& k# p
54 m" {/ g7 Q* M8 u3 Y+ ]5 }
6) U/ m) I) l, _0 `% C# S
7& W( H1 Y0 T- s
81 h7 S* i% k+ n
9
* C+ a3 l3 V: B! R% f 通过year, month, day, hour, min, second可以获取具体的数值:5 a1 D) g; V. N. M0 J- [ z
3 A/ `: t# @' W4 c( s: `
ts.year
C- o# H# O4 T$ k Out[7]: 2020
) _) {0 Q4 m2 W. o3 F& y - ?. z. J. P# C* w
ts.month) C5 O0 Z' | L, D! i# @# s1 z+ s: E
Out[8]: 1! U' f1 f3 A: g' q
* L3 y: I8 k4 Z. ~2 r; \6 x ts.day9 S u1 Z9 ]- v
Out[9]: 18 Z" j. a5 f. m8 }! J2 |
/ V0 q+ p5 w4 N4 [* M! O
ts.hour1 w& i4 m$ q# @! I
Out[10]: 8- z7 A: ?6 N. b! W# }
# N% j: D" b" Z: f3 E' {/ G
ts.minute+ W$ b( C6 Q: G; i
Out[11]: 106 }% X6 E0 q) v- u
$ k1 b% I& z3 \" v+ S! K' v
ts.second- T- F" f7 _% S/ ^" M$ w, c
Out[12]: 30
: O/ L2 i2 }! o, A( s! {
5 Y) o) g, [, ~% Y9 ^2 a 1 s: d5 Q$ O, e/ }+ N/ o0 d
2
7 c5 w: u+ l0 N6 z3 `+ M5 Z: X+ Q 3
( E+ \/ b4 N& H$ ]$ Y$ w K( Q' S" l 45 j1 Y$ n. O7 P/ @+ [+ t. H- ]
5
/ Z5 B: C9 y8 R; O6 j 6% R' [! ]% K: W! U# P) j
7+ c2 l6 U; C5 S/ A: k' c
8
" J3 g, e1 B8 u6 p# T) ~: b8 n 9
0 R( e! M. H6 Q 10+ L. }! s5 ^; _, l" u
118 O( e4 e5 |; h- p: F* k8 y: V
12
) D& @9 S' p% P 133 D5 ]8 z" O- T) M, D
14
8 C0 j. W, U' y9 M 15- M0 J" Y S0 g
16
, H9 E1 |% ^* H" K& e/ [; o 17- }. i3 w$ g0 [4 Y. W
# 获取当前时间" x( Y8 {0 u. b' @' H
now=pd.Timestamp.now()0 n; O% ?' g5 i( ]; O$ x
1/ F0 T) l: E+ `; t# c
2% e! @8 F$ V1 \" O7 N8 c( b) a) j
在pandas中,时间戳的最小精度为纳秒ns,由于使用了64位存储,可以表示的时间范围大约可以如下计算:
8 h5 i+ M9 j; i9 D T i m e R a n g e = 2 64 1 0 9 × 60 × 60 × 24 × 365 ≈ 585 ( Y e a r s ) \rm Time\,Range = \frac{2^{64}}{10^9\times 60\times 60\times 24\times 365} \approx 585 (Years): @2 t# n K- r/ u0 x3 D
TimeRange=
# m/ t1 ?# @4 x0 Z8 s( B5 ~9 H 10 0 i0 i H& r& A- L% e: ?5 i* U
9/ y% q+ g) i, |. b i/ y: P
×60×60×24×3650 f" f: c' K% Z9 w R3 S% R5 E
2 . g& V! H( w* W* w6 @$ Q; v8 s( R- n
64
; w& }3 z) }% t) H4 p1 W3 _( S/ h
& A9 A9 L c4 \( e: K ( ]1 i/ H1 R, J1 g3 c9 o
≈585(Years)
# T0 s- X$ j2 ^, u; T) {+ d6 ~4 c ! z, P9 W' [( v5 q3 g1 A; q
通过pd.Timestamp.max和pd.Timestamp.min可以获取时间戳表示的范围,可以看到确实表示的区间年数大小正如上述计算结果:, _& `3 X0 d3 t* B5 I& o
% Y2 L2 P% J) F! K9 ~( @ pd.Timestamp.max& G$ }2 U/ }9 ?" i" Z2 ^+ \
Out[13]: Timestamp('2262-04-11 23:47:16.854775807') x/ ~! p7 U, H' W: \
2 g& Z3 q6 F4 t/ m* p9 K pd.Timestamp.min( M5 E7 `" p! f) v* F( {2 t4 z
Out[14]: Timestamp('1677-09-21 00:12:43.145225')
( h% U* S% U$ M" F/ L
0 h7 v# v r+ q& a. \! K pd.Timestamp.max.year - pd.Timestamp.min.year
: i6 y" S3 n+ _7 N) u, a Out[15]: 585
, @- E" K1 |+ E4 O3 } 1
; p9 j/ C6 O$ I4 d( D. f* K 2
. V* @9 _4 E5 M* g6 A& X 3
3 M0 J- F; Z% k1 ]& M, D. @ 4' T4 {0 j" J9 }' l8 y: O% a
5
' Y: k: H) n8 l 6, q7 R0 W" Z( D! ~+ H. g9 f6 |
74 z7 A+ s; Q7 p6 ]- @, K, c r
88 `* C# I4 L( [, M: r6 A
10.2.2 Datetime序列的生成; e+ \" l" ]% n6 r4 u" \
pandas.to_datetime(arg, errors='raise', dayfirst=False, yearfirst=False, utc=None, format=None,
# O Y: \; C+ _, Y! D' ]- D exact=True, unit=None, infer_datetime_format=False, origin='unix', cache=True)! m7 ~5 G, Q( a6 c- f
1
' l( c# ?, X6 u 21 E' N+ @8 Q7 z4 b3 `* W
pandas.to_datetime将arg转换为日期时间。! {- Y: [4 B+ {( C, Y; h
8 S5 o2 v' Q4 W& ]1 t arg:可以是argint、float、str、datetime、list、tuple、一维数组、Series、DataFrame/dict-like等要转换为日期时间的对象。如果提供了 DataFrame,则该方法至少需要以下列:“年”、“月”、“日”。
5 V& g$ v' B$ D5 C; ` errors:0 a1 a1 a3 y* r( v1 A% S
- ‘raise’:默认值,无效解析将引发异常2 v: y/ q! B9 t6 [! k: V
- ‘raise’:无效解析将返回输入* w% O& E6 {" M: W' a4 _' t
- ‘coerce’:无效解析将被设置为NaT
i# ^4 Z' y* B. G; E ~9 ^ dayfirst:如果 arg 是 str 或类似列表时使用,表示日期解析顺序,默认为 False。如果为 True,则首先解析日期,例如“10/11/12”被解析为 2012-11-10。如果无法根据给定的 dayfirst 选项解析分隔日期字符串,会显示警告。+ ~' \4 N8 `% i
yearfirst:如果 arg 是 str 或类似列表时使用,表示日期解析顺序,默认为 False。如果为 True,则首先解析年份,例如“10/11/12”被解析为2010-11-12。无法正确解析时会显示警告。(如果 dayfirst 和 yearfirst 都为 True,则 yearfirst 优先(与 dateutil 相同)。)& v7 k$ h, v: l5 n/ V
utcbool:默认None,控制时区相关的解析、本地化和转换。请参阅:pandas 有关时区转换和本地化的一般文档
, U) E' N1 O1 p4 E# B4 m0 S% | format:str格式,默认None。时间戳的格式不满足转换时,可以强制使用format进行匹配。. ]' y: x2 b+ r/ {9 s2 A
unitstr:默认“ns”。它是arg (D,s,ms,us,ns) 的表示单位,可以是整数或浮点数。这将基于原点。例如,使用 unit=‘ms’ 和 origin=‘unix’ (默认值),这将计算到 unix 开始的毫秒数。" j6 h# \0 _$ I T+ v
to_datetime能够把一列时间戳格式的对象转换成为datetime64[ns]类型的时间序列:- x* y1 i) Y3 y1 M
pd.to_datetime(['2020-1-1', '2020-1-3', '2020-1-6'])' k: Z7 a8 J' D1 r$ Q
; n5 y C1 q o) r$ E DatetimeIndex(['2020-01-01', '2020-01-03', '2020-01-06'], dtype='datetime64[ns]', freq=None)# X7 H1 i/ ?0 z
11 V: _( ?% X5 W9 W- b
2! q; R- |, H/ p5 Q! @( O2 O. {
3 z) \- a; h# s& m
在极少数情况,时间戳的格式不满足转换时,可以强制使用format进行匹配:6 h/ j: z. }! y0 v" d$ \& g0 P" L
; }) g# A+ ~" q9 ~0 Z- s. ~ temp = pd.to_datetime(['2020\\1\\1','2020\\1\\3'],format='%Y\\%m\\%d')
1 d" z' q( }! i' V# J s4 [ temp7 q' B$ E: k, F5 K1 j7 @' N2 k$ u, J
5 J7 `; F ?( `# t DatetimeIndex(['2020-01-01', '2020-01-03'], dtype='datetime64[ns]', freq=None)) x7 x5 ^# x5 z) Y: ~
1 E- D7 c8 L$ ~% k
20 a/ ~9 t8 V+ b8 z
3
% z3 Q* A* F2 E3 `' X+ s* W0 w9 ? 4
$ t; v; _, w: Q7 K* T) c- ]( q: d 注意上面由于传入的是列表,而非pandas内部的Series,因此返回的是DatetimeIndex,如果想要转为datetime64[ns]的序列,需要显式用Series转化:9 Q' P: G$ o% @- k; n8 p
, X) j T* S, a; c
pd.Series(temp).head()
9 E, r. r- t9 U/ l, V# T3 @ , ^" r5 V* w: i7 @( F; F
0 2020-01-01# m* Z8 q& y$ P, Z, Q# d
1 2020-01-03
1 a: d' f1 `! r4 o2 q dtype: datetime64[ns]
) Q z, \( v- w8 l; ? U# u 1
( k+ s& a/ _" [, `: l: \ 2
) O" D+ t8 D0 `2 y, y4 c 3
/ i5 I. W8 M# L4 [ 4
# u1 [7 Z5 y6 b3 D 5
. | B+ T' j, C 下面的序列本身就是Series,所以不需要再转化。0 b" \4 F& i6 h( y- s& ?& M" Y
6 }$ ]. L, r% J: g df = pd.read_csv('../data/learn_pandas.csv')8 ~! Z; i, w* |/ N6 B
s = pd.to_datetime(df.Test_Date)3 M. y+ |. h; t0 \, L! H. F, j% g
s.head()
" H# F0 `3 J( Z# u
+ G% K* f& U4 `3 r 0 2019-10-05
: X9 n* _; [4 k7 @4 e' h' g( U% ~ 1 2019-09-04$ ]8 r* c/ J6 g2 Y: N
2 2019-09-12
0 `. L8 H1 e6 [+ R7 [ 3 2020-01-03
! o4 C/ ~* I4 q+ ?9 v 4 2019-11-06
: `4 M" N7 m6 ~ {! t Name: Test_Date, dtype: datetime64[ns]' P3 _3 t. b( i* W8 m9 H
1
' {/ V5 K! U j/ U9 J 2
1 N; K" _/ j! e* R( X" Y 3! |" M2 C9 P9 I/ Z/ @8 e
40 t: j: d2 R* h& b
5% P" i4 p4 ~, C' B4 o
6! R, P' w) ^ x* E/ \) o
7
' B( Z/ t6 l) P% A! l1 @( G 85 p9 x% [: x8 B2 G/ j5 I" s6 p
9
1 x. _$ a/ {3 z0 L1 w( k& z 104 S2 |. p; G& S' V; s' M
把表的多列时间属性拼接转为时间序列的to_datetime,此时的列名必须和以下给定的时间关键词列名一致:% c9 p' c8 o" M' y9 k
df_date_cols = pd.DataFrame({'year': [2020, 2020],& R, u) I1 R; P" i& ~3 U0 ]
'month': [1, 1],
9 w6 u( I; |% Y( \. ~8 P' w 'day': [1, 2],# x/ Y0 [2 k( c3 w1 Y' L# r
'hour': [10, 20],' \/ m: ?$ z: o7 ?
'minute': [30, 50],' K% p9 ^6 ~* D8 H. {& B
'second': [20, 40]}) t* P- g% W. v6 z- `
pd.to_datetime(df_date_cols)0 r% `( O8 x# Y+ @: [2 D: @& V
4 ~' [1 Z; j/ K) P8 Z4 M7 M 0 2020-01-01 10:30:20
# B5 s$ O0 d8 B0 d9 Z$ u6 p 1 2020-01-02 20:50:409 w* l3 R5 p0 h% {+ r
dtype: datetime64[ns]9 B: c; _6 f, q3 l0 b! }3 C
14 u D8 w0 O9 Y: T
29 c# K1 E% a1 |9 |2 M2 \' b
35 a. ?) E6 c' f& D& j
4, h" \) ]- s+ J5 E
5* c0 }8 U" U% A: P% S) K1 v& g& m% K) \6 v
6
! y% n4 s0 r1 f: u$ Z! Z* s( u 7' R" K) `* s( m0 X9 l8 B
8
+ X4 ^. | t3 [5 D4 e# F3 s 9
. O* g9 ?& l! X9 @/ r; M 10) A0 `( D% G; G5 O1 R) G
11
; u/ K: t& Y9 t, d: `: Q date_range是一种生成连续间隔时间的一种方法,其重要的参数为start, end, freq, periods,它们分别表示开始时间,结束时间,时间间隔,时间戳个数。其中,四个中的三个参数决定了,那么剩下的一个就随之确定了。这里要注意,开始或结束日期如果作为端点则它会被包含:# p5 v0 Q7 d" p+ _
pd.date_range('2020-1-1','2020-1-21', freq='10D') # 包含$ p: Q* Q: u: i" u
Out[25]: DatetimeIndex(['2020-01-01', '2020-01-11', '2020-01-21'], dtype='datetime64[ns]', freq='10D')' S$ Q' U$ x) k2 b* [' x
7 k4 @0 K# J, k% F; n5 k" G/ a8 }
pd.date_range('2020-1-1','2020-2-28', freq='10D')+ U) n8 Y- d! {) R
Out[26]: : |# \1 f4 g D
DatetimeIndex(['2020-01-01', '2020-01-11', '2020-01-21', '2020-01-31',
$ G, T# I% M2 K6 w. l% l '2020-02-10', '2020-02-20'],
% f2 l; a c5 r3 m; S# j! _ dtype='datetime64[ns]', freq='10D')
$ `) ^# E( n% b. m" r + E# n4 f- s. U# |, m
pd.date_range('2020-1-1',, d- c' Z0 @9 k( X! n, ?
'2020-2-28', periods=6) # 由于结束日期无法取到,freq不为10天
1 f% Y2 u, w6 |; X! W+ j/ w- @6 w5 K ' f/ f0 H" S! D% Q% k y
Out[27]:
' I/ I2 X/ m p0 T DatetimeIndex(['2020-01-01 00:00:00', '2020-01-12 14:24:00',
9 E8 I& h6 I) r k! l" {2 G- g '2020-01-24 04:48:00', '2020-02-04 19:12:00',
5 M! v/ p# y- O '2020-02-16 09:36:00', '2020-02-28 00:00:00'],3 ~: w# E* V$ ^. @. @7 \* V; v; f
dtype='datetime64[ns]', freq=None)
7 P8 x; S8 s4 I @! v( a+ K+ ?/ C& t
- U4 A2 G; o2 p8 r3 { 13 ]( m& [2 S9 \4 C' C2 I0 @1 H
2
, a1 _) `( M4 F3 v+ \! r7 { 3
8 b2 n# h7 |2 x, J 4
9 ~, X) h/ H& ~ 5
a0 J7 ^ V- v; [8 X2 w: \' | 6
' K" p' |9 S/ t9 L# g' c' \ \ 7
, ?- d6 L6 B, N 8) y" V: i( y8 _
9
% T" `2 o- D8 e" g0 H 10; S/ o1 j4 P2 y, a/ T
11! z* m( R0 V0 s7 s& D
12
- T6 u' o1 g3 v" i 13
! s% G+ l% w+ y5 D. r. x( f 142 G' J/ d: ^4 e: {( \9 m" ~
15
( M5 E$ Q4 ]* q3 f! \ 16
1 ~& X4 k: ~7 i( g6 g 17" x& O" p! e/ F
这里的freq参数与DateOffset对象紧密相关,将在第四节介绍其具体的用法。
4 V/ N4 r! S: w1 ?( i # C' `* v1 e8 ?% N/ o$ x( q! i' b
【练一练】1 V5 f$ H8 y8 Q6 |
Timestamp上定义了一个value属性,其返回的整数值代表了从1970年1月1日零点到给定时间戳相差的纳秒数,请利用这个属性构造一个随机生成给定日期区间内日期序列的函数。
' k4 M# M6 \1 A/ O% j+ k7 }/ N. f 2 C+ \$ L, O( D! J. h5 @; v* C0 P, E$ g, o
ls=['2020-01-01','2020-02-20']0 |- G3 p5 g! {4 e& j/ ^
def dates(ls,n):9 v/ A" r! H! c
min=pd.Timestamp(ls[0]).value/10**9
) N2 {: l% y- t# w4 A; E" y max=pd.Timestamp(ls[1]).value/10**9
1 U' B5 U- S* O3 I- M times=np.random.randint(min,max+1,n)7 y, v3 x* g5 L* E, h2 t+ m
return pd.to_datetime(times,unit='s') D- }6 {* Q* [! Y* K" D" F/ e
dates(ls,10) m H0 k2 S: Z4 [3 s& g
1 g# J2 V; `5 q; }& W2 e
DatetimeIndex(['2020-02-16 09:25:30', '2020-01-29 07:00:04',
0 H1 w. l, y8 S% Z! c '2020-01-21 12:26:02', '2020-02-08 20:34:08',2 ]3 y1 O Z0 p& O4 s5 h
'2020-02-15 00:18:33', '2020-02-11 02:18:07',! U4 `$ D# y6 O) `1 T) `
'2020-01-12 21:48:59', '2020-01-12 00:39:24',; J$ S4 G6 |- F( ?7 Q( n6 K
'2020-02-14 20:55:20', '2020-01-26 15:44:13'],
: S* B; y& Y4 _ dtype='datetime64[ns]', freq=None)! }3 \# f: o9 _7 D3 h( V
1
B$ W$ W+ p; Q! {- F 2
! [% K% L4 h4 u3 g$ H 3
. i( R. v( A8 w' w9 U9 p2 U1 q 4
9 Q& N/ W1 K) K$ T 55 F& {- u& Q# N
6# }# B# e! m6 I3 L% Y* A+ n
7% G0 P3 j; [6 h1 T) Z: ^
8. r, L+ w3 H0 l o1 c4 ?9 e8 V
9
q |: w- P1 d- q0 O4 h 10
+ |2 `$ w* Q3 y' v X% M 116 @$ Z. b4 `! b5 ?/ Y
12* w1 l' F- R9 y" I G3 m
134 r; @4 W) v1 f
14
# P4 { e/ L7 r7 `2 f asfreq:改变序列采样频率的方法,能够根据给定的freq对序列进行类似于reindex的操作:. S" [; J) B$ P
s = pd.Series(np.random.rand(5),
, ~7 ]% k$ D# ?' @+ I( D index=pd.to_datetime([
9 a2 z4 ^6 ^8 O- b6 L) r. @ '2020-1-%d'%i for i in range(1,10,2)]))
: e/ A' I1 _: {, N T& p& B 4 W9 B! y X1 h' U+ @/ [% B3 J! q
- _9 c* p! A! x7 Y% s s.head()
5 x- S) r0 e! S+ ?3 f2 l Out[29]: ) I- R; x8 o3 \: R% Z j4 k
2020-01-01 0.836578# t7 P) }1 c9 Y" o
2020-01-03 0.678419
, y# S/ Z/ K- N% \# v- ~ 2020-01-05 0.7118976 k* V: _) k( N1 p
2020-01-07 0.487429
& ^' C+ S$ q7 e, w 2020-01-09 0.604705, t" z, ]4 y* _0 a+ [' T
dtype: float64
2 N- A* T& S! U. x" z& K
0 J9 H$ k- G. G( @ s.asfreq('D').head()
! _; K, p7 b" I2 h# x& } Out[30]: # I( \) a: E* u& M+ {& D
2020-01-01 0.836578
- g; A: G+ U4 {* V& y 2020-01-02 NaN
% h/ O/ ?/ @' S- H8 _- @ 2020-01-03 0.678419
# P/ J- v$ J5 x# R) T; U6 J 2020-01-04 NaN' u! Q; F [9 [2 e! R# g
2020-01-05 0.711897
# p8 f" C$ M( A& I7 C$ i2 d' l Freq: D, dtype: float64
* L; P. n1 [+ n& |, H+ ^ ; p( k* w N$ l
s.asfreq('12H').head()$ w) F" z2 h; N: L0 O }! `
Out[31]:
: h- u7 D6 e7 f6 N 2020-01-01 00:00:00 0.836578" T( f5 G5 h) H. h0 C
2020-01-01 12:00:00 NaN
. x9 {" x1 ]! H 2020-01-02 00:00:00 NaN% E; i6 G( Q1 v. M7 ^5 t7 F
2020-01-02 12:00:00 NaN1 G! X$ ^2 H) i( i6 v' p+ v/ d: X
2020-01-03 00:00:00 0.6784197 h3 B8 x8 i$ Z4 \# X8 ?
Freq: 12H, dtype: float64; B3 s' f6 w$ M7 l
) e0 A* C! r7 V/ _ 1& d: e u, V4 n* S# T
2* Y- k" S; C, s& [ g1 D
3
; o r9 I; F8 b) x8 R) L 4, ?9 H' b# ?$ I
5
, K8 `: [# p1 u4 l9 ?. m4 h9 i 6, m: {( G6 E# Q- t/ i& J
7
$ p$ R6 O0 d8 Q7 o* M( ] 8
8 a+ a4 V" I# a* l' O/ r 9, }" @; f6 X. S0 t
10( }5 h2 z* F8 {: Y
11- g6 N5 P2 G" `
12
1 A+ y, a m$ F, A 13* R k4 t. L4 @ q- {
14! E6 f. o9 f$ ~# H
15
* I' M' M- {1 S0 N! P 16( b6 i. a, ~3 p* U0 z
17" j# d2 n# b/ {! |0 ?, H
18% v" y3 k6 S# E
19
v5 i% T( @! J, u$ z2 A6 i& h 20
6 I( v; O- w/ p0 p" Z 21
( I1 O. v, c/ m 22
* B( C. |5 }( n4 \2 Z4 v+ \4 ?3 ^0 \ 235 h A$ h8 ?* c, m
24
+ C% P3 R7 ?+ L 25# x3 L& F+ `) p4 ?: u# B9 `" u
265 Y8 X; Q: D; s
27 ?- j; f4 |/ ?; e8 }& E% T& B' Q
28: K/ u, B# u! W* v+ ]& b f
29
" X! K1 _( P" y% }% ~ 30& }/ X6 l6 v8 r1 `0 K+ H
31
" t- P* D- @# J: ~- q7 P 【NOTE】datetime64[ns] 序列的极值与均值
0 j+ ?6 [3 ^5 C5 A" w$ S, P 前面提到了datetime64[ns]本质上可以理解为一个整数,即从1970年1月1日零点到给定时间戳相差的纳秒数。所以对于一个datetime64[ns]序列,可以使用max, min, mean,来取得最大时间戳、最小时间戳和“平均”时间戳。$ Q7 R7 a1 z- a
, S) w; |. W& |9 M7 d) i
10.2.3 dt对象" {. N1 q% t# U. }( o$ @( B
如同category, string的序列上定义了cat, str来完成分类数据和文本数据的操作,在时序类型的序列上定义了dt对象来完成许多时间序列的相关操作。这里对于datetime64[ns]类型而言,可以大致分为三类操作:取出时间相关的属性、判断时间戳是否满足条件、取整操作。3 l/ J/ e ^- A$ D: D2 W4 |' W
; U( Q7 j+ G) m; |4 ^! e 第一类操作的常用属性包括:date, time, year, month, day, hour, minute, second, microsecond, nanosecond, dayofweek, dayofyear, weekofyear, daysinmonth, quarter,其中daysinmonth, quarter分别表示该月一共有几天和季度。+ o! e+ z. o/ k" S) b: f
s = pd.Series(pd.date_range('2020-1-1','2020-1-3', freq='D'))8 X1 t& Z7 R& E5 V: j
4 ` b' D8 T1 N6 i7 _# e& g! e- P$ M s.dt.date0 \, \: S9 {+ v Y7 v1 z) V$ W( l" d
Out[33]:
j$ U$ Q f0 O1 v& K' V 0 2020-01-01
( R% p- {. B4 f7 @& L 1 2020-01-02
! P' z1 L2 ], ]% z* j) ~/ e$ V$ T 2 2020-01-03- q# x p8 j0 E
dtype: object
* ^/ X% c4 u& s* | . O# C/ K. u& u) \' ~& u
s.dt.time
9 e! C7 N+ ?% Q' B2 S# u2 N3 \ Out[34]: : }/ b. s" K' A# c; c
0 00:00:00
4 i. h/ M- ]4 r" h3 @ 1 00:00:00- o3 D& G3 w, I: K7 A
2 00:00:00
" {# Y) X) y8 _' E9 v% j' t3 m- L dtype: object
) [# T4 | Z% A" p5 ]: t* m
; ?% W7 q6 r' O C9 V8 h s.dt.day
6 |3 i# X8 F6 a( E1 R7 `% F/ | Out[35]: 5 _7 m4 H* s7 i; K K
0 1
' p9 C2 c& P5 N) c; S- A. p: B# X3 Z 1 2
$ g+ `' r: [2 q2 ]% u- o4 m 2 3
1 q$ i5 e0 N" ?0 m4 [/ z. b dtype: int64
0 }# ?% m' I- t0 F l
% }7 o) M- M p) k* ] s.dt.daysinmonth' a4 V0 y0 T! t' D3 |! f% F* s0 x9 c/ `
Out[36]:
) P2 F: l. X9 u! T9 N8 ]8 S 0 31/ S- G2 G% Y' H8 Q/ Q* i, C
1 31
0 X( m. T8 z4 X& ?; g j 2 31
" {) Y3 k' N4 `& K( w dtype: int647 W- d1 ~1 G E
2 |1 E# @/ C# ^- }2 Z
12 M* Z$ p( e9 p4 t
2* T; N5 w) P3 C% X( ^' O/ E
3! l7 y, w1 [3 t/ N
4& I- l6 W% d F9 J5 @. }8 t' ] x
59 Z% Y6 L4 q! h z% q' y1 o
6! t5 p/ Y% Z( U- b2 A
7
( \" y% ^8 U! o. ?, l 8# L; A0 U" [2 ^ v2 h; k( e
9
U! L' f2 H1 t/ y c! S/ N) c% G 108 w! w6 l# u6 M" T$ d: {
11
: a1 M9 j; i( r) N: l 128 Q$ H+ |+ u& j v
13* b* x, |6 z, s% ?! o, R* x9 c. x9 y
14
: ^: R3 `5 t! X3 s" v 15" H0 H- x7 w% j6 ~! H7 E( E
16
) Q5 _, E$ c; m) g2 M7 t( w6 r 174 e* G! O! ^/ t
18
+ o1 ^) t; D) M$ ]0 T 19
3 y( R! M1 m7 @& C% \% S. C 203 c) R. ?+ I6 A" K8 v8 |8 y
21
$ q0 ~0 l3 p0 G2 c0 X V 22
- h o6 T O7 I% I5 _$ j- L 23- O5 _9 [3 C" g
24
/ h3 I* A b- \' _1 R) _ 25
( ~& l3 S' b' Y% J 26
* E) F+ T8 ^+ G 27
" Q4 y& K! Y+ s& m) u# Y 28
* u- M$ o4 z( z0 s; \. u 299 ^; v6 k1 l& E
在这些属性中,经常使用的是dayofweek,它返回了周中的星期情况,周一为0、周二为1,以此类推。此外,还可以通过month_name, day_name返回英文的月名和星期名,注意它们是方法而不是属性:5 }, Y W6 M) M0 S: {# _& [8 i4 A/ ~
9 ]8 c8 c. b7 ]& m7 D: z s.dt.dayofweek
: T* d4 n: u: _/ g+ n% A' J Out[37]:
9 w* P4 I. b. g. i' G& l 0 2
& A8 ^8 D& s6 Q0 ~/ y 1 3# @1 v) x5 p/ D Z0 Z6 y: q* _
2 4- ~6 H: P+ f) Y
dtype: int64
; ?" W* m5 K$ p/ v0 z4 f& n4 C" r 5 H0 E. N- u4 S1 ]
s.dt.month_name()) F- [5 H& \. t" Q2 _6 `8 t
Out[38]:
8 ~, o( V4 x* v 0 January
- [4 `$ x8 }/ ?1 {% P1 a 1 January! u7 S4 C7 A+ L% |; u5 [
2 January/ O/ w5 w+ _) X8 M( P
dtype: object
# ?' z r: E+ @" N" W6 H6 Y4 R
, J7 H; ]! W/ a" e m: l1 M7 S s.dt.day_name()5 B1 \5 K0 J9 k+ a2 @
Out[39]: 5 @% z) \6 u* ?, d0 k* ?9 j5 q0 p& {
0 Wednesday9 C& }0 H( s E. @0 J& d
1 Thursday
. g) d! D* Q5 L1 J+ S 2 Friday5 ?* M7 r6 j8 C4 b6 ?. u
dtype: object9 ~3 [- Y6 f8 ^! _3 y
) L9 L; e, v' j+ D# f
1
, b3 k8 K0 H8 \# c/ h& e 2
7 N# y0 `, v4 q/ U) @ 3; s! q5 e Z8 w( k+ [
4! n4 Y& K+ w5 m
5
- F0 x" M) @2 P+ Q* X9 x* N3 F 69 W, b* Y( Y% [2 h) c
7
1 V) ~- }- @. C 8" @' K, L, K% C- P
96 w% X9 v, @9 t8 } C
109 v; a7 g2 l8 |% v2 p! l- z
112 g5 ]0 l$ a- U( Q7 U, @
12
$ t9 D" Y( J9 p" k; n. j, N; g 13
" P) b" B7 y( T& }6 |/ p2 G+ O0 S7 j 14! ?! S" w- z& k
158 j* D2 b( m+ K ^! f
16
& g( \# M. H6 ]" a: ~0 t 17- u% R3 {( P0 j* R: B
18
0 Z) E# c: q& C. [: z% L 19
1 P. p" n! u+ F9 t. }7 x8 } 20
" U5 b' k+ s5 b 第二类判断操作主要用于测试是否为月/季/年的第一天或者最后一天:
# B! f5 X6 o4 y. ^ s.dt.is_year_start # 还可选 is_quarter/month_start. j" }3 m7 o5 }1 K$ ~" v' M
Out[40]: t Z. `) [$ H6 i5 O
0 True4 Z" ?1 q6 {7 k4 M3 _& G# e- L; A% U
1 False- K+ [9 v, m% x5 \" i+ {
2 False
/ f) L9 I* T& ` G/ {& ~) y, Q2 i% T dtype: bool
* [. p# Z! u% b$ w
; Y9 Z2 l( ~. w$ h0 f5 J( I m s.dt.is_year_end # 还可选 is_quarter/month_end3 r$ F' ^1 t0 I" |7 B: u
Out[41]:
! \5 W! A A6 [2 h# C c- Y9 b 0 False
7 N( l* A$ J5 o( p4 ?4 }7 m 1 False
( | G2 N5 B) K, u8 {4 S& Y2 U 2 False
; l# _ m' |1 ?- ]/ V dtype: bool
, X! b$ e5 ?0 C7 e8 S, L, d t% E 1( h/ R, p5 g- V# o9 g
2, _0 }- D& M# @' y: D2 T j
3
8 b9 S. h& y, x3 ^, t 4
8 f3 @: b* r, l( V% P 5
: R4 ^$ M2 T; E! B1 w& p. H5 E% i 64 Q- `+ o' F: i4 I$ ?, }
7' s x& c5 a/ a1 q) w# H
8
( J+ G( o6 F3 N" t/ r: Y+ G 9
/ ^, `- o& Q4 E+ V) t) n 10
6 g9 Q/ o0 c7 F: h- ~ 11. @# W0 G; f4 ?% j
126 t7 r; m3 T5 X! O6 e
13
2 I1 Z6 s! A% ?) x 第三类的取整操作包含round, ceil, floor,它们的公共参数为freq,常用的包括H, min, S(小时、分钟、秒),所有可选的freq可参考此处。. E. I9 B% L7 a- ~7 v
s = pd.Series(pd.date_range('2020-1-1 20:35:00',
2 U# r0 H" B2 N5 E/ p '2020-1-1 22:35:00',
3 p6 Q- q, P6 r2 r$ o2 b& X0 H freq='45min')), j9 F* r- M0 [2 V3 k5 `9 _
: ^7 z1 s, F( m
E* K4 y$ d1 ]# y! P) l9 s s
/ o+ ?$ v$ c! V3 `' \8 w! X* A9 u4 L Out[43]: 6 S& K, V$ h6 G
0 2020-01-01 20:35:00
) q1 s v, Y; Z2 D3 a8 m 1 2020-01-01 21:20:00
! F- H. C! B. F* W4 d 2 2020-01-01 22:05:00+ F' K3 q! c3 J) ]. X6 ?2 T) L/ B+ A
dtype: datetime64[ns]+ v6 Z( S$ X. T% d
( I* Z2 j4 f+ G! g" t
s.dt.round('1H')7 V' |4 J* ?# \. @8 _
Out[44]:
( U$ f) z; Z& W" r 0 2020-01-01 21:00:00
* T. i$ }3 d' P" H, ~ 1 2020-01-01 21:00:002 a* d ]2 q8 w# r! b1 V( k
2 2020-01-01 22:00:00
1 A4 }- o) Q6 {8 W dtype: datetime64[ns]
" H1 U6 ~$ M9 J# h+ G . C8 F5 }: m, o0 n+ Q! p% K
s.dt.ceil('1H')' @$ j0 h+ m7 k! R* w" R
Out[45]:
7 x7 L! R2 c7 U( Z& Y/ m; l! s 0 2020-01-01 21:00:00
, x1 V9 F7 i; l4 _, A+ _; b6 N 1 2020-01-01 22:00:00) t f$ S. r3 `) R, z5 m( j
2 2020-01-01 23:00:00" u3 |9 j- b. E. L( T" u) V
dtype: datetime64[ns]
$ m1 i2 v- B: h( E# Z% ~
) h: g3 x: J5 R% z s.dt.floor('1H')
( P+ A! H6 } d Out[46]:
8 {0 b9 b: | ?6 R( { 0 2020-01-01 20:00:00
9 b5 N& m! ~ ] d4 M( ? 1 2020-01-01 21:00:00
" w1 \% ^7 G' z$ U- t 2 2020-01-01 22:00:00+ b" N0 W( M! {: a3 m7 F1 G2 ?
dtype: datetime64[ns]
3 x# |9 b( X2 B! D; y' S 4 y6 B! H0 n- i: R3 c9 B/ L
1
. k4 a3 n8 I+ n7 }" r& y 2
9 z; [. t" I- B, T9 b/ f 3
/ Y- O% k% A8 m/ ~ 4
. `7 j' D# M+ n 58 N% B f/ O n6 Y) V" ~2 D
6
( E/ x! }1 l( S( p 7
$ p3 x2 h4 b0 f7 p7 o+ O" J 8
4 h9 ?% n0 }2 M& P 9+ d) P- Z/ C0 W# H8 o* v
10, m) z6 \( D) J! k9 M" W6 L1 l! v
11
! m: C" Q; M: ]! `; n" b, O 125 G! B' U$ N6 f) d% Q. g
130 y# \% z' V6 m) r* K
149 p3 m( t. j% P- a; ` d
15
" P7 Q( S( [5 s# [: D) B 16" F, N7 d+ S2 _) m8 t
17
' a1 f8 O0 T9 f+ E$ V @, ? 18& G" M$ B; B4 g7 [8 N
19
: S: b8 y# l7 |5 ` 20+ ]: ~6 g7 X$ W% A" u) c
21. f4 W0 g1 `6 s; w
22
& L5 f7 F& I' ]$ i. O$ L 23
! P7 `8 H, I( S3 w: b8 C% ?# U7 m 24
" b+ y& ~ s$ M3 Y1 g 25
4 I, N0 h& Q4 v5 M 26
5 j7 e6 a& `0 `0 L9 ^ 27
8 G2 H0 I) d: V, b6 w# [. [7 u 28
% p3 M( r H% h1 S& a* E 29
. ?9 n' ?; h; f 301 C f/ J# j1 m* [
31
& a& P. k: ~3 h: p0 T" |) @; H 32 O7 a N* N" u" m' r
10.2.4 时间戳的切片与索引
, W0 ~. J: e5 C, F0 C1 I- T 一般而言,时间戳序列作为索引使用。如果想要选出某个子时间戳序列,有两种方法:) P/ q" f# A) x5 U4 ?" q* Y5 t* I
7 @; t5 w+ _/ ~. C* t- Y4 Z3 n7 w 利用dt对象和布尔条件联合使用4 w- D3 Y w/ L. y( @( p. W) P
利用切片,后者常用于连续时间戳。5 p6 J) I' S* x1 Q4 e$ N
s = pd.Series(np.random.randint(2,size=366), index=pd.date_range('2020-01-01','2020-12-31'))
5 A2 @& o- x4 o- v( F2 |2 y idx = pd.Series(s.index).dt1 D, E3 @, x& l6 U
s.head()
4 x% L4 y( p3 O+ k
1 k1 D6 J. O/ A 2020-01-01 0
. @+ `8 d; P" A" d v [5 N 2020-01-02 1 J% _+ y4 r5 L
2020-01-03 1* e2 [& a* c S
2020-01-04 0
7 K% A. ?/ U: T3 S d" v 2020-01-05 0, r7 O7 ~% W1 E- t: d, M4 ?
Freq: D, dtype: int32, |6 L; B c* D
1
# F; y b, C8 A# o4 h' } 27 ?: u) Q# U7 s8 Y" q
3
, |7 O7 i7 Z6 {' h+ e+ G 4
* T ]3 d. J' \. \( } 5
+ a3 N2 j0 \! r1 H" M 6# B. t4 ?( e9 L2 V H1 X
7
7 S7 \7 n+ x2 X 8( w. U9 C' A& l G; D! T
90 \) ~! _+ J; n8 h" @" ^; t
10 G9 q' p" F$ j* g" | n
Example1:每月的第一天或者最后一天" w V o. G2 W" }- [" E/ j0 `
3 u1 ~- Z/ _* [2 @ ]) K0 a
s[(idx.is_month_start|idx.is_month_end).values].head() # 必须要写.values
- C$ f$ X7 V$ c1 ~ Out[50]:
7 N& ~5 G8 h. @; y1 o6 E 2020-01-01 1- |& @; ]( V1 f& R+ L6 [
2020-01-31 0* \3 N1 D3 K. s, Z; X
2020-02-01 15 G4 {9 d' i% i" v+ b8 }9 V2 _
2020-02-29 1
) F/ }6 t+ o* s+ ^& ^+ b 2020-03-01 04 M# H0 m2 W; ^
dtype: int32 P+ H9 x$ S$ W+ T& \
18 H, F9 p0 g5 h" e. f: S
2. K% N8 X6 t2 s
3& J; v$ [: {! m+ R, p8 ?" m
4
* [- \/ p% i3 Y# m; {) T( e, S 5
$ J/ ~8 D3 d( ^9 k# f$ e 6, s: h' Z2 i" _/ H3 h4 P P3 Y
7& V0 ~- ?% K4 W \! |& w
8
9 E. g; _! y/ }9 I Example2:双休日
; H/ v( X% N3 t7 x
) X o( Z- A# B2 L' C) I s[idx.dayofweek.isin([5,6]).values].head()
5 h1 [! N7 ]6 ]; C' m Out[51]: 7 Y% g h$ a0 V3 w; z
2020-01-04 1
3 o% D$ M# m5 p- b# w- } 2020-01-05 0
" e9 X) w1 Z5 g: n8 E 2020-01-11 0- c/ F3 L; x9 k9 t8 _4 Y& z4 A& F! f
2020-01-12 1" G% V0 _# B, a6 |2 b+ C
2020-01-18 17 _' R6 s: I& S. m
dtype: int32
1 v9 u/ [) R1 i. E; B" b3 @ 1, m( [" N6 A/ [$ K y1 s$ i
2; [3 e' b* {6 |2 t5 ?: L- u* I
3
; P( q, J& Q7 Z- B" r 4
h. \" H$ v" ~5 c2 ] 5. y6 [# b, c+ a0 M
6
8 K2 m5 r; ?( A! e( x7 _ 7+ n9 G/ S9 a7 R, G% g& @& M% i/ J7 r
8
( N+ q& W) m# [. @' G, E# p Example3:取出单日值" ?4 X. P; o) v1 s" ?) n/ X/ Q$ O7 w
+ u( m, B( P! w0 Z9 L4 ]. n" t: {- z- j s['2020-01-01']: x# {/ I6 W" E; c0 E( N
Out[52]: 1
/ }3 a$ a/ ^' z4 e6 b
; i& `. _( T/ j' @0 d: H7 u s['20200101'] # 自动转换标准格式 a! m o( }. ^: b3 {/ B
Out[53]: 1
, A# o2 G/ x, N7 u. k 1
/ n+ D+ n' w; r/ ^3 L 2
* D+ f0 E) R% Y5 k7 U 3+ R3 J3 T8 j7 o0 G: h: q1 S, Z: [8 x
4; v# D$ k2 {' K t# ?
5( l: J! n! O% n6 g5 r- p( ]
Example4:取出七月
( I# C( U: t. u5 l: F + I7 ?4 W9 C: b$ F: c5 u' Z
s['2020-07'].head()
# G) k. J9 E( V3 _8 r Out[54]: $ V/ S8 }: X. F
2020-07-01 0
$ y( Y" p& ]/ w- Z5 Y k9 Q 2020-07-02 1
0 Q. Z) j1 N" U 2020-07-03 0+ }$ N1 _: ` e4 c5 A
2020-07-04 0) S2 P E1 V T/ r) R7 d8 }
2020-07-05 07 r1 D/ h- L) r. Z
Freq: D, dtype: int32 C; ~# I7 u. u
1
/ W, C* l, u8 y& h8 d 2$ B3 @2 w3 b! v- U5 o% ]
3, z; q) b8 Z0 B0 J) X8 L$ G0 A# ~
4
" b) ~5 \8 q* T" M 5
. T ~# p7 n' P( w$ M) d 61 k2 {% }" t! p' _/ Q
77 c, e" U2 C0 `0 H/ h. A
8
; _! J7 q9 ~/ x) J Example5:取出5月初至7月15日" p* u2 X" M2 |! p2 D& _" C
! @& ~. f% U" o+ l
s['2020-05':'2020-7-15'].head()
, d* v* `7 b; D! O: p Out[55]:
2 L7 o8 u, a3 v. `! V6 c- p0 ? 2020-05-01 0! s" K: h2 U% w
2020-05-02 1
, e. Z9 _$ q! d8 {3 ^' e. y 2020-05-03 0# j2 j% }) ~2 M/ g
2020-05-04 1
. C3 Z3 {0 x; p2 j 2020-05-05 1
8 j- l2 O. z1 k$ ?) Z Freq: D, dtype: int32- \4 @5 q& J4 p) y$ N) q; J4 L* K
7 q" [: D& E% p3 }! d) t* D6 y( e7 d s['2020-05':'2020-7-15'].tail()
9 A( A. \* q% o0 _0 D3 q% J Out[56]: ) ~$ U- L5 W( n: H( x% Y
2020-07-11 09 b0 h2 n) e9 n4 _+ A1 Y
2020-07-12 0
7 j" s. K1 t5 z5 o 2020-07-13 1 B, {* ]; B! i1 R+ D. }8 \
2020-07-14 0. E. @- ~# u y- m' U
2020-07-15 1
8 v$ M, A+ u" r, k( v1 D Freq: D, dtype: int329 ^. b k" W( t* s/ O7 ~) n, {# ~+ g
4 _4 W4 E; `6 R' z9 Z7 `* R, X
13 I- y) O# k: y; V* D2 e- F J! U
20 E5 L' N8 @* L
3
~1 G7 \- Y) b: e 4
7 r) t, @0 W4 e% ?, b% ~ 57 G7 ? Q5 g' Z0 T5 o' X
6" E" O6 u2 t8 Z9 z
7+ N* N; s8 Z& J; J5 L( x) u) N
8' X3 d7 G- `- S( Z% C
9
! t9 i) I1 X8 t8 }2 o( g& y 10
5 [( i/ q/ h6 H, U* J 11
4 }' j0 p7 N' ~4 H* W 12
, l/ P0 X1 D3 G0 y0 a" } 13* M d5 i5 O, s4 ?7 \
143 L* ^. E, l, c% S+ N9 b# @! _9 P
157 `- ]1 R: _0 @; [5 f# O9 {# p
16- q; u+ U: i$ w3 Z; D9 f
176 i3 v7 J+ B1 ^2 P' R; v8 M
10.3 时间差
/ i( y% }/ J' r9 X6 u# [ 10.3.1 Timedelta的生成+ e7 N8 r7 V4 W1 ~8 a
pandas.Timedelta(value=<object object>, unit=None, **kwargs): V B. w& w. x: u6 c
unit:字符串格式,默认 ‘ns’。如果输入是整数,则表示输入的单位。 b: M6 \$ e/ V$ I T. z, c" r
可能的值有:
. c1 I+ P) G; }: q
6 b# L$ y, j7 e ‘W’, ‘D’, ‘T’, ‘S’, ‘L’, ‘U’, or ‘N’
5 u z2 C+ v5 T4 Z) k) @( z+ T ‘days’ or ‘day’
( O2 p. a8 p: V7 A2 R5 F1 G6 F ‘hours’, ‘hour’, ‘hr’, or ‘h’- y, h: \8 g5 k; \5 X$ Z9 ~4 t9 i8 K
‘minutes’, ‘minute’, ‘min’, or ‘m’
+ ~, C( r- S. c/ e G7 w! {! a ‘seconds’, ‘second’, or ‘sec’0 e+ h5 f+ _7 ~, X9 ?7 V2 D% o9 r
毫秒‘milliseconds’, ‘millisecond’, ‘millis’, or ‘milli’5 @4 }9 P% O; ~0 q* D& K
微秒‘microseconds’, ‘microsecond’, ‘micros’, or ‘micro’& g# p. l2 f% Z7 @8 @9 g
纳秒 ‘nanoseconds’, ‘nanosecond’, ‘nanos’, ‘nano’, or ‘ns’.
. K7 l9 K ^9 M, S 时间差可以理解为两个时间戳的差,可以通过pd.Timedelta来构造:; V2 w& X, s. I2 ~! Y" y: }
pd.Timestamp('20200102 08:00:00')-pd.Timestamp('20200101 07:35:00')1 C1 T! J9 [; a) C! C- n
Out[57]: Timedelta('1 days 00:25:00')
3 u" |4 B& G2 B" @2 h) y/ F0 n : E6 k: A5 \+ B" E+ v
pd.Timedelta(days=1, minutes=25) # 需要注意加s ~) V& g, F. ?: `' o2 y2 m" W
Out[58]: Timedelta('1 days 00:25:00')
/ B6 W. }" k* P6 l5 C1 F5 {' ?) A* _
/ d0 F* e4 p# ~: V& Z9 W& X pd.Timedelta('1 days 25 minutes') # 字符串生成
2 x, ~' J7 O% _6 x! ^5 A/ T Out[59]: Timedelta('1 days 00:25:00')
9 f/ Z( Y0 x. v ' X- u" B8 r) `9 ^" x, v1 u
pd.Timedelta(1, "d")/ j9 I1 J7 j2 `& U' v! c O
Out[58]: Timedelta('1 days 00:00:00')8 {8 X( c4 E% M9 k7 l( j4 c1 p
1( d* q+ O$ H+ }4 {& p3 p
2
! [7 w( |$ r- p5 | 3; G% y9 ` k$ r7 R; y2 S
4
; {' i" h) F2 T7 m( S7 [& E; o- L' @- ] 5
; u' Z2 J9 m# N2 q- ~* S k! Y! Q 6
8 a. W9 D7 ~+ b- J 7
1 f L2 V q! w1 E* {1 Z) x' | 8, d. X: i; Y3 H9 F
96 C6 E5 o. S7 L; G+ j$ ~
10
8 L6 \$ {7 O# T% U! T8 Y& c3 w0 M 11
3 X4 Y8 j% b! Q5 ]8 z6 M) L: S% Z6 a 生成时间差序列的主要方式是 pd.to_timedelta ,其类型为 timedelta64[ns] :
7 S5 T" Z9 H9 Y8 d t s = pd.to_timedelta(df.Time_Record) C3 w' z6 A6 b% D; F
+ e3 I: Q P0 s8 A& H4 U
s.head()0 |, @; ^2 j; y5 h: o
Out[61]: , I* b: f k# C& J' f
0 0 days 00:04:34
, o! `$ g+ Z4 \" v2 ^5 v% P 1 0 days 00:04:20
7 g S2 j1 g) | 2 0 days 00:05:22
: L4 L, d+ o% s1 ^+ R* q1 o 3 0 days 00:04:08% ]3 C( h& l0 d! g# Y- t* g
4 0 days 00:05:22
( ^9 L/ ^* L! A% n: ^ Name: Time_Record, dtype: timedelta64[ns]1 c' j# _6 i( k% M! X8 A7 D
1
" U9 |$ `- T$ ?3 a$ G/ d6 H 2; ]0 r8 y; X9 i! v! J) X- R0 h4 A
3* y( B0 u! K+ C
4
% }. z: X% f9 C/ O0 F 5+ S; c/ a- w* J
6% E8 O. X8 |' `& H) Q" x7 J
7
# H3 \ d; K r' ~0 E& O2 F/ r: b 8
3 t7 l0 }+ R9 [+ ?2 @$ N( ^$ H 9; v3 O7 p" S1 \) o
10
" J. [5 e! v$ Z 与date_range一样,时间差序列也可以用timedelta_range来生成,它们两者具有一致的参数:
% |# P' [( r, G5 S5 m$ Y pd.timedelta_range('0s', '1000s', freq='6min')
8 ~% X/ J: l8 M2 G+ s# k; U Out[62]: TimedeltaIndex(['0 days 00:00:00', '0 days 00:06:00', '0 days 00:12:00'], dtype='timedelta64[ns]', freq='6T'); M$ N, V. K* e q: E% M
5 h5 K1 b, w' } pd.timedelta_range('0s', '1000s', periods=3)& p7 P" Y! z. A& M
Out[63]: TimedeltaIndex(['0 days 00:00:00', '0 days 00:08:20', '0 days 00:16:40'], dtype='timedelta64[ns]', freq=None)
1 f5 [$ t( R$ Y) o! y+ L" Y 1
8 K. i x3 d$ k! X, H! i- l; V 2
& a+ V- U3 d% S 3
# s+ y2 G' o2 ~ 4
) `3 |; w: x) ^5 [( X( @$ J5 J 5
# w. k9 N6 |: T V, Z! W6 ~ 对于Timedelta序列,同样也定义了dt对象,上面主要定义了的属性包括days, seconds, mircroseconds(毫秒), nanoseconds(纳秒),它们分别返回了对应的时间差特征。需要注意的是,这里的seconds不是指单纯的秒,而是对天数取余后剩余的秒数:# w! q& V! R: _4 A3 Q
s.dt.seconds.head(), ]) t T7 g$ W. }% E: [- t7 z
Out[64]:
6 v0 D/ q3 o/ h& S 0 274" i# B j& @/ e9 k6 C, x
1 260/ h F* N u1 x. ?1 e
2 322" ~; J: d3 Y8 ~7 y3 o
3 248, h( }* i& [* E
4 322" w8 T$ p8 b* o4 w! {. z% g3 V- W
Name: Time_Record, dtype: int64
7 d( W4 c3 M1 ^9 L2 t 1
0 h. r' h9 Q1 w! | 2
/ A. c$ `% K* r# R 39 p' s% L4 R+ Q% D
4! `1 N9 U) o4 B' d7 k3 D
5- F9 a3 _' B3 @3 o7 N
67 C* j/ \( u3 {( R9 A9 ~
7- ?' F. z9 r1 g. x3 @. |$ m
81 g3 e( {5 R( k2 D, \
如果不想对天数取余而直接对应秒数,可以使用total_seconds! a) z& q x3 q) R& b# Z0 B' p
$ g D. J# G" _" b; w& K4 y: S# o s.dt.total_seconds().head(); h+ g& U3 N5 e9 h& W
Out[65]:
9 V4 }) E; N6 v& x) B 0 274.0: q* O/ J% P/ ^, S; S
1 260.0 U) K1 \) u% S6 T `) e
2 322.0
8 }3 x) J# ~, s4 O% p y 3 248.0
$ r8 I6 ?" ?+ T: ^3 f 4 322.0; p# O0 G) f* v
Name: Time_Record, dtype: float64
3 F" o% \' p: L: Z 1
+ @6 o* b/ v& r: V. P: x 2+ I2 ~, E/ R# j* w, `
3
: M* u! [, o9 N0 g! w7 ~! u 4* ?& r* {/ I4 s5 ^! h
51 Z' a8 ^( F2 r5 r3 D2 Z
6
2 S$ q c2 T2 j; p7 G- k 7
8 p, |# z) h* c B 8
- X$ {. p- T7 A* _% x5 O/ @) q7 q 与时间戳序列类似,取整函数也是可以在dt对象上使用的:
" V$ @$ \6 C& X& E( { 4 g% H& V2 t! H4 y
pd.to_timedelta(df.Time_Record).dt.round('min').head()
7 L% _) ?( M( q; ?9 m Out[66]: ( H% k- O4 M3 v4 b9 K
0 0 days 00:05:00
& k& P/ U6 E4 ]# n 1 0 days 00:04:00
: L6 |- P7 i8 `' }$ X 2 0 days 00:05:004 {3 Z4 r* _2 P# D
3 0 days 00:04:00
1 X4 V4 \0 L& e* p 4 0 days 00:05:007 c$ Y W0 q4 U% w* O9 t
Name: Time_Record, dtype: timedelta64[ns]
- {( b3 x& d' n: _! d1 o6 z: h 1
9 y- d. O( n3 `$ |) x4 v 2
1 F( \7 h! j, @- ~6 V% B- U 3% k! c- P( f, |! a7 }( E
4
' r. F1 m5 l; K9 H9 F, ?8 x9 Y' b3 x 5% U4 w% u1 ^( T
6
- H+ z4 u6 i7 e: Q2 O 7% Z' [; H5 P9 P+ I! \' Y5 r6 X
8
" v# s) T( ^: f$ h" M 10.2.2 Timedelta的运算. |( t& B$ l% ~: X9 h8 ?/ O
单个时间差的常用运算,有三类:与标量的乘法运算、与时间戳的加减法运算、与时间差的加减法与除法运算:
2 A- D$ K* N4 Y7 j5 o, I4 Y% M5 Q td1 = pd.Timedelta(days=1)4 [0 K+ w- \& J3 F# K
td2 = pd.Timedelta(days=3) ` T. w- D1 `) b0 i) P
ts = pd.Timestamp('20200101')
) C9 c& x; A/ _, R$ a# \; P; U ( R& E7 M1 f. h* s
td1 * 26 O, w3 e. u* _% z; q7 X
Out[70]: Timedelta('2 days 00:00:00'). b4 ]# a$ ^ i9 i
$ x* `6 N% j! d8 h% E0 e$ ?
td2 - td1/ a6 v3 U: P& M6 l ?1 g! ?6 X4 u
Out[71]: Timedelta('2 days 00:00:00')
& G& q3 v/ m) c& | k+ f2 ~% W % }" v1 d5 U8 q$ x8 b' o
ts + td1" h i* f. t- z( G. P
Out[72]: Timestamp('2020-01-02 00:00:00'). `! o3 a: `5 x# K" M; f4 L) j
$ z& ~( R$ |/ L1 e* Q. f3 d ts - td1
) ^# l+ D2 _' i Out[73]: Timestamp('2019-12-31 00:00:00')' {$ o( a, Y2 e' _5 S2 V
1# b- p4 ~' W7 {" c( D6 _& |0 `) p
2
/ z' t1 A3 ?# ^& C" n 3
# _$ R; Y6 l, s( v) }1 [ 40 D7 h2 H4 {8 |, B& u# q* d
5! p0 C- u' b8 k( m4 g' o" U8 H
62 p# j H4 B" R' \$ Q. r
7
: u' l" B/ e* n) M 8
; w) q! N0 n- ^/ m" K4 L9 b' V 90 l/ F. A ^' w6 ~/ a5 Z9 |( l4 |( E
10
# _% m) |: B* a/ n# v 11
) S; o8 \3 ?9 S; ]. F 12
" s8 ~9 u V) [- X: Y( d: ^ 13
7 d% H, C4 }" @( h 14" x: A! B. f% u8 M# V( i* e
15
0 W M+ ^5 J) i# ^0 W' b 时间差的序列的运算,和上面方法相同:1 m9 p) g1 H* H4 F! o0 l8 Q* t+ F0 U
td1 = pd.timedelta_range(start='1 days', periods=5). ~6 d% i2 s# n" V
td2 = pd.timedelta_range(start='12 hours',% B# e: m8 n) i: `* _% _
freq='2H',6 y4 V8 p) t9 a" Q* Q- g! s+ |
periods=5)# q2 D4 q8 p2 s4 L6 d
ts = pd.date_range('20200101', '20200105')
+ S5 y9 K0 Z* q td1,td2,ts
; {: p! P$ ?& R& z0 Z , T, |2 `/ S7 @" w4 ~; ?
TimedeltaIndex(['1 days', '2 days', '3 days', '4 days', '5 days'], dtype='timedelta64[ns]', freq='D')
0 l9 j( |9 ?% b' c+ m4 f% @5 I TimedeltaIndex(['0 days 12:00:00', '0 days 14:00:00', '0 days 16:00:00',1 _7 z/ p- f2 r5 t Z9 d) y' h
'0 days 18:00:00', '0 days 20:00:00'], dtype='timedelta64[ns]', freq='2H'): p' s9 o3 e; l# w5 G( G( w4 O
DatetimeIndex(['2020-01-01', '2020-01-02', '2020-01-03', '2020-01-04',: i/ U# X) W8 o. q$ F9 |9 \
'2020-01-05'],. k% M$ L% {" T
dtype='datetime64[ns]', freq='D')- [ W# D5 k$ K/ `
1' a( c7 N5 `8 S' @" Z; D
2
$ W/ I8 B- C1 |- H, G 33 Z7 z. u/ }) y5 j' R
4/ g# P3 u4 D* N
5, c) y2 D2 L8 A9 z: x
6; p7 O, h" U& A
7
( v* z1 G6 ~; t, \. P 8
7 q$ ~! R3 U8 H: E& R; V 9
/ w) Y) I1 Z% N 10! A) T7 Q! w. Y5 ^
11
, l# F% J% D0 a 12
2 |1 q \ i0 n {* [+ d/ s0 W, e 13' ~. c7 w" ], m
td1 * 5 d- g" \; q0 L. v1 o, ?
Out[77]: TimedeltaIndex(['5 days', '10 days', '15 days', '20 days', '25 days'], dtype='timedelta64[ns]', freq='5D')0 q3 G! j' H1 H) O, b, Z+ P/ Q' B* r: I
) f" D* j8 ~' {
td1 * pd.Series(list(range(5))) # 逐个相乘( s4 a! C% ^5 x, a) O
Out[78]:
[7 |- T& ]8 v* e& x! u9 ?3 B 0 0 days
/ h3 B9 T8 V8 ]& h* y 1 2 days
" N! C, A/ d3 X/ h( Q$ } 2 6 days) m% C% v$ ~- F4 O+ G
3 12 days0 b& r5 Y9 @/ e7 m
4 20 days
7 }( w" {6 b2 A/ n dtype: timedelta64[ns]
1 T# Y H8 h" w5 M
0 W5 B* d0 k4 v# x9 o: O td1 - td21 @; q2 w3 K9 `
Out[79]:
: K* b: r1 W( A. H TimedeltaIndex(['0 days 12:00:00', '1 days 10:00:00', '2 days 08:00:00',! n8 O a* ? R
'3 days 06:00:00', '4 days 04:00:00'],( G T$ h4 _3 |' i& S8 k
dtype='timedelta64[ns]', freq=None)
$ Y; g& G9 x5 k5 ^' ~" E9 P% h ( q: d- Q) _, ]2 z) S) }! Y
td1 + pd.Timestamp('20200101')
( T! d s: a" s& C$ t# e Out[80]: : K7 _# Z# `$ K# [* r6 M
DatetimeIndex(['2020-01-02', '2020-01-03', '2020-01-04', '2020-01-05',4 G+ w9 o0 `* k
'2020-01-06'],dtype='datetime64[ns]', freq='D')! u l0 ~% S/ u ~9 J& e0 A
) t) A# P' N& O$ a6 m td1 + ts # 逐个相加+ \* \# j( w# s- K/ j2 {. C$ E
Out[81]: + G) e/ [6 y" q; X/ G- B9 m0 X
DatetimeIndex(['2020-01-02', '2020-01-04', '2020-01-06', '2020-01-08',
" s4 J' v3 E- @, t+ k( _7 \3 u6 t '2020-01-10'],' v' P7 r2 h0 c% Y3 F, I8 e; v
dtype='datetime64[ns]', freq=None)3 R0 m: K! Q& S! g, T' e
8 @( t: o/ b2 s9 C6 r; q
1
# N: r2 P9 x% o/ F& p5 \ 2
$ g! `$ E$ l+ T& A6 p 3- R' A: M- \1 Q V. j/ O1 @
48 O8 Q; g7 Q& j& ?! R
5
# U- t% |+ l' v9 q \) E 6' ^* U/ N2 K' ]* {
7" `# z* b# c: T' p- z' D# l6 C7 R
8- z3 T# _" T, t. H' e8 P) b5 s
95 f, C, l& S8 B: n
10- I% |. C7 V, ?( e1 ~ l9 e0 g; w
11
. I! f! E" T' r5 S+ M, b6 G& ` 123 H7 A% ] [; u; S3 m
137 T( p; ~9 H7 Y6 I, X% C
14, J8 e8 H8 S5 ~ L( }- u9 x
15
$ B9 f# ]/ ^- F+ ~. k* l8 c 16
# b0 V5 w. i2 f 17
( S' L; y! l& _$ b4 }6 L" @" n 18
3 s8 B+ o. c" y! Y7 t, D/ J) Q 193 z" g) x5 E' U" `9 V
209 x- Q4 L0 E( V
212 g# _) @) F- A0 ~- p5 D8 `
22
4 N& l' M* F& e( R) V: O$ w+ | 23) s% i8 c( n4 Y. v6 b
24
7 R2 L0 o7 t( i0 m. f0 s 254 x% C( R9 G2 F
26
! Z; ~8 {! k1 _' R1 v/ ~ 27( A1 p& |9 N3 s% J6 R e; t
28
, K: |1 r% I7 f6 k8 T 10.4 日期偏置
/ V; m7 z! g5 \/ V4 @ 10.4.1 Offset对象
0 l s# @8 X5 V1 g8 @ 日期偏置是一种和日历相关的特殊时间差,例如回到第一节中的两个问题:如何求2020年9月第一个周一的日期,以及如何求2020年9月7日后的第30个工作日是哪一天。/ c. c7 [2 p4 r, N) ^3 H' Z
6 I7 l6 P% Z) f9 K$ k4 E5 B
DateOffset 类有10个属性,假设s=pd.offsets.WeekOfMonth(week=0,weekday=0),则:
* D8 \) h% F2 c9 m2 k: L0 o1 B . c8 l5 T& D3 J& i1 I/ a) |
s.base:<WeekOfMonth: week=0, weekday=0>,返回 n=1 且所有其他属性一样的副本; b$ w( S* j/ q! e. N" E
s.kwds:{‘week’: 0, ‘weekday’: 0}) j& J; c) h [% B/ ]; _
s.wek/s.weekday:顾名思义
( t4 V+ ?7 q2 \, c- F 有14个方法,包括:1 j( J% L1 A( P% p! s+ K, A# x1 f
' o& M# u& f: O% D2 o
DateOffset.is_month_start、DateOffset.is_month_end、DateOffset.is_quarter_start、DateOffset.is_quarter_end、DateOffset.is_year_start、DateOffset.is_year_end等等。/ F0 H3 z5 k& i/ N" O9 X
pandas.tseries.offsets.WeekOfMonth(week,weekday):描述每月的日期,例如“每月第二周的星期二”。
) H. R4 Y J- [2 P* ?4 u / d( I" m- ?! h+ s& t, |( I
有两个参数:/ f0 N) O1 N' q) O) f0 F* |- _$ H
week:整型,表示一个月的第几周。例如 0 是一个月的第 1 周,1 是第 2 周,以此类推。
& u( e# F" [+ m5 E weekday:整型,取值为[0,1,…6],表示周一到周日,默认取值为0(星期一)
. G2 }7 r k& p6 c pandas.tseries.offsets.BusinessDay(n):相当于pd.offsets.BDay(n),DateOffset 子类,表示可能的 n 个工作日。% e8 \( ~$ h! R" {1 @) { c' T
' L$ H- h1 @5 u$ a/ v
pd.Timestamp('20200831') + pd.offsets.WeekOfMonth(week=0,weekday=0)5 K6 B3 p5 `$ V# Y9 w% T4 j2 y
Out[82]: Timestamp('2020-09-07 00:00:00')
/ d0 H7 G" l7 @9 A, j5 d * H. \3 n5 b/ Z& B$ \) m5 x+ f
pd.Timestamp('20200907') + pd.offsets.BDay(30)2 o8 n: g+ Z/ \
Out[83]: Timestamp('2020-10-19 00:00:00')
5 P+ U' g5 V- ^7 _# L* q0 h9 s 1 h+ Q( Y, k: H# \
2
; u4 F, ^; X4 G: p2 A8 V' I 3 v7 B& @: [9 y, v# }: R" r7 Z" i
4
. e5 Q. v0 o% U$ I 5' a$ |9 t" o! \' E; U2 s% o2 G
从上面的例子中可以看到,Offset对象在pd.offsets中被定义。当使用+时获取离其最近的下一个日期,当使用-时获取离其最近的上一个日期:
. \3 H5 O% g8 Z" ? 8 y0 L- c- r& j1 }- b: T/ D
pd.Timestamp('20200831') - pd.offsets.WeekOfMonth(week=0,weekday=0)/ \7 a, f/ V4 Q. @# D$ `) M3 i3 P7 R2 M
Out[84]: Timestamp('2020-08-03 00:00:00')
8 g1 K; I- I( n; X7 y {0 B1 c
& ?5 _! b7 p: x pd.Timestamp('20200907') - pd.offsets.BDay(30)
% d; W3 T' P; X ]% D5 G/ {# k; E8 X Out[85]: Timestamp('2020-07-27 00:00:00')
b3 r) S! Q( D 4 U9 F- B$ \6 _, S
pd.Timestamp('20200907') + pd.offsets.MonthEnd()
2 D/ ]1 Z' w$ C: f; b Out[86]: Timestamp('2020-09-30 00:00:00')6 G% u; x3 S- \& _
1# B0 @8 ]" K7 u9 W& A- C
2/ L( g( Q6 x0 _7 L' V7 q8 E/ A8 O2 z
3( k. Q0 z" e; {7 j( C
4) y- v; s1 c! f( v: H8 [
5
% E' d6 ^, j; I 65 q' A1 i7 Q$ F4 R
7% N( R$ L: Z* d B- F3 ~3 ~
8
7 g7 c; c, W9 G' g- T 常用的日期偏置如下可以查阅这里的DateOffset 文档描述。在文档罗列的Offset中,需要介绍一个特殊的Offset对象CDay。CDay 或 CustomBusinessDay 类提供了一个参数化的 BusinessDay 类,可用于创建自定义的工作日日历,该日历说明当地假期和当地周末惯例。
2 H& Y2 d% G, r8 r8 R 其中的holidays, weekmask参数能够分别对自定义的日期和星期进行过滤,前者传入了需要过滤的日期列表,后者传入的是三个字母的星期缩写构成的星期字符串,其作用是只保留字符串中出现的星期:+ N& l* ~ @# |: K3 d
( C' p. x) H# P; S/ ~) A
my_filter = pd.offsets.CDay(n=1,weekmask='Wed Fri',holidays=['20200109'])
; T9 N7 V, Q7 w2 F0 c. v dr = pd.date_range('20200108', '20200111')1 e4 a/ T. x( g( m
2 L1 x$ P9 F& b& s- m9 O# V2 O* s
dr.to_series().dt.dayofweek5 A8 F# C- U+ E. P" H
Out[89]:
& _ B4 I* A5 w; z, \ 2020-01-08 2
* c5 ~& k4 j; n3 Z 2020-01-09 3: I2 ]& B6 {. E: w& t0 F; x4 u
2020-01-10 4' x5 ]/ F; Q2 r( r2 R5 Z9 p) Q
2020-01-11 5
+ g* x/ R' @- a" W6 l Freq: D, dtype: int64: |& L$ r& h2 u: r
9 e( b# g. p8 _1 ]5 ^( F4 c
[i + my_filter for i in dr]. B2 Y& P9 }2 I) V" n+ |* t7 |8 W+ X
Out[90]: ; _/ c# F9 b6 n
[Timestamp('2020-01-10 00:00:00'),
# \+ G2 Z5 _4 _9 U Timestamp('2020-01-10 00:00:00'),
7 z$ C( r8 O: s8 i O Timestamp('2020-01-15 00:00:00'),5 K& K1 [8 i6 V
Timestamp('2020-01-15 00:00:00')]: v' D1 y1 A, d8 P8 u
, V& @4 S6 V l3 a6 ~. D 1& ^# g, w8 C/ z$ r
2
% {6 Q- g% M: A9 r+ O/ B) c' O 3. Q/ M& N( n5 h; f
4( x* P' A+ o, L. }' h/ V* f+ c( Z: t
5
- S2 G" l! v. A# A0 i& t9 p1 z 6
% _6 c$ ?: Z9 m: i/ a. s) h 7
3 H, H0 t7 q4 k& ^ 8
7 v, g: E4 p: c4 G 9
" W6 E' r) U# H; ~' b; y# N6 t 10+ L g: j) c5 Q) X s
11
; C6 T7 L& R9 c 12; F7 d1 D8 C! l! ^; Z" Y
13
& C: C$ s3 d; u5 x0 M) J% L) W% F# X 14
0 q( W* g K f& ^ 15
' p' |* f1 _5 l$ [5 S 16
% [) J& O6 c) V1 l( f0 p$ T 17
" h+ g: H$ o& l 上面的例子中,n表示增加一天CDay,dr中的第一天为20200108,但由于下一天20200109被排除了,并且20200110是合法的周五,因此转为20200110,其他后面的日期处理类似。
! x- {8 I1 u- y% K9 G4 H# u * G) U' h n" G, T6 M, W( B
【CAUTION】不要使用部分Offset* d( X* @8 P1 o" |- P
在当前版本下由于一些 bug ,不要使用 Day 级别以下的 Offset 对象,比如 Hour, Second 等,请使用对应的 Timedelta 对象来代替。
" R+ {: t, J( Y& T: o ) o. t" E. o4 U4 ^# ~4 i
10.4.2 偏置字符串/ @8 z# z8 P* {
前面提到了关于date_range的freq取值可用Offset对象,同时在pandas中几乎每一个Offset对象绑定了日期偏置字符串(frequencies strings/offset aliases),可以指定Offset对应的字符串来替代使用。下面举一些常见的例子。; _: e& a3 I1 e M/ e( u
2 X) R/ Y2 r( ^6 g4 ]! r. o2 Z
Offset aliases:pd.date_range函数中的freq参数,为常见时间序列频率提供了许多字符串别名。 也称为偏移别名Offset aliases。偏移别名列表点此参看(大概27个)。
, L* q; U0 w$ M% ^" W2 C! L
^% K! c6 k9 B' W4 { pd.date_range('20200101','20200331', freq='MS') # 月初
# }: V: c. j% r9 r( V6 j9 w Out[91]: DatetimeIndex(['2020-01-01', '2020-02-01', '2020-03-01'], dtype='datetime64[ns]', freq='MS')
/ U F/ N u* D6 S0 z
, T' S! d: Q9 @) h& w5 i9 ]& ?- H pd.date_range('20200101','20200331', freq='M') # 月末; `/ t N7 o. T# S& X
Out[92]: DatetimeIndex(['2020-01-31', '2020-02-29', '2020-03-31'], dtype='datetime64[ns]', freq='M')9 E8 Y% t! a( b2 Z( f4 B
9 [% { h2 }# f" i! }
pd.date_range('20200101','20200110', freq='B') # 工作日
, z: F p# v. V D1 y Out[93]: - R$ {* J2 ^: Q
DatetimeIndex(['2020-01-01', '2020-01-02', '2020-01-03', '2020-01-06',# t& `4 l( B/ A, b( T
'2020-01-07', '2020-01-08', '2020-01-09', '2020-01-10'],) f4 R8 e# c: i: o
dtype='datetime64[ns]', freq='B')& I. n; \0 M7 l- W$ f/ r
: P5 g4 P2 V* I pd.date_range('20200101','20200201', freq='W-MON') # 周一
5 H9 U. z, C: r6 | Out[94]: DatetimeIndex(['2020-01-06', '2020-01-13', '2020-01-20', '2020-01-27'], dtype='datetime64[ns]', freq='W-MON')
! J/ v0 u) D8 Q- U5 B, j
! @7 g6 O, }, P9 Q# h2 q& T pd.date_range('20200101','20200201',
7 y; }% V' F5 a2 z8 E freq='WOM-1MON') # 每月第一个周一0 u! M; b$ Z- S6 [ J
6 a! Z% G O: P1 S* u- d$ L Out[95]: DatetimeIndex(['2020-01-06'], dtype='datetime64[ns]', freq='WOM-1MON')
0 \" H# Y9 ]! M7 x' ^, `
/ r3 d. N' }8 A5 {. v 1
& G2 e, Z+ r& o3 K C 2
7 g( I* w; v6 Q8 E2 F. z 38 ?; Q! u7 h; z
4
; s! U1 `8 T p1 @+ b* s 5
* A& W. G. M- l( _ 62 |. V6 @& E( d: U/ ^" f( P
7
& F, X4 w& [- ^5 ]' Z" C 8
5 }5 \1 U8 P3 h. X1 J! H, Y 92 P: c9 W' E. q. h
10
- K2 ~" \& ?( ^$ v0 H B) z. W 11
0 R- q, j0 ~* e: L) ~! W& F; r 12
K3 R- D$ e2 d3 ?( l 13
% q( ?1 N" O2 n8 f# P 14
/ l! `9 c# J; |7 W! i! E 15
8 N& a, L- v% S) H 16: I! Z0 a5 @5 Z* A' i3 r: c4 }
17
% n) `, t+ S- }/ }7 B1 S d 18) O. o# |. I& M5 ~1 S: b
19
/ E: ~ ]" s: g7 W: Z 上面的这些字符串,等价于使用如下的 Offset 对象:" z, E( J. X) w! e. a
" V+ y: P# q' j0 H" I pd.date_range('20200101','20200331',3 p" T0 m* X- i* p2 g5 a
freq=pd.offsets.MonthBegin())2 E& `0 y8 |' _# _- r7 e: A
3 T8 n# F7 |8 C! z Out[96]: DatetimeIndex(['2020-01-01', '2020-02-01', '2020-03-01'], dtype='datetime64[ns]', freq='MS')
8 n: ]# i, o" R0 o! B$ h6 P% U0 |- A1 [
4 ^& }- X2 M+ a: `9 W( K6 c pd.date_range('20200101','20200331'," c8 f' e1 j- @6 u
freq=pd.offsets.MonthEnd())
$ R; w7 t+ x( x% \+ N3 {$ f! p
$ O( A( |% `' {" X; l Out[97]: DatetimeIndex(['2020-01-31', '2020-02-29', '2020-03-31'], dtype='datetime64[ns]', freq='M')8 s0 S. g3 Y+ V* N
0 R9 ~% y% e- B* R2 n pd.date_range('20200101','20200110', freq=pd.offsets.BDay())
- y, W7 Q8 y' o/ F: i! U Out[98]: 6 E' G/ R( Y* X$ Z" r, w5 M
DatetimeIndex(['2020-01-01', '2020-01-02', '2020-01-03', '2020-01-06',3 U( D4 X* H7 l5 X5 B# {
'2020-01-07', '2020-01-08', '2020-01-09', '2020-01-10'],7 P3 z' ^% L$ [" y
dtype='datetime64[ns]', freq='B')
& p& ?4 W$ ~9 F ~; l& R: W
3 S+ Q4 y+ p0 c: d, R0 ~6 J6 w) F' F pd.date_range('20200101','20200201'," u" ^5 b- h2 z/ \
freq=pd.offsets.CDay(weekmask='Mon'))
. p0 g# m, B) b9 P: S x( I
% j& |/ C T3 ^- j0 D2 K5 n/ } Out[99]: DatetimeIndex(['2020-01-06', '2020-01-13', '2020-01-20', '2020-01-27'], dtype='datetime64[ns]', freq='C')
6 G% G0 a5 L! N' \9 S2 Q: n
2 ^) U: k' k2 P2 r/ G pd.date_range('20200101','20200201',7 F/ A+ [! L- C: S
freq=pd.offsets.WeekOfMonth(week=0,weekday=0))7 n9 c+ [, `% S7 q# j# W$ ]7 U+ L, m
) M3 D2 \" b7 B: n' [ Out[100]: DatetimeIndex(['2020-01-06'], dtype='datetime64[ns]', freq='WOM-1MON')% V! R6 j# P2 y5 {* `* k1 J
0 J9 G: d/ x h 1
7 ~! u( O1 v) r! R. _7 x3 y; d( L 2( W' |3 ?8 S: l
3
, ?. S/ e/ @' E& u; @# E 4$ f0 F$ u+ n: F- L4 m- ~
5: i f& Y& S2 Q1 N% l/ t9 d
6
5 t$ I+ P& ?8 c+ h; k8 @ 7" x. c" K, P( Z0 _& I
81 X# n9 Y& s/ y4 [
9
' j4 L$ e# X# k% I( P8 Z 10
5 j0 D0 |4 U! F/ N 11. _9 g' Q, a4 }, j+ x3 [; \* \
12/ C9 N( \, c s* }0 w
13* n7 w4 d0 V1 P5 H- U' p
14; q4 Q3 h7 [7 A; x
15
2 k) C! i4 Z+ F- S$ o- | 16: j# m5 g# m9 e* [% V
17
# N$ T# r: L9 a- {) J2 T 18* w9 Y) w* h# _# x9 }: ~1 I" x
19* m/ l* D; L% R3 ~3 \2 P: J
206 }1 h4 E" d/ W- G
21
6 O k! y; v* R- e) f& a. L: K. ~ 229 Y" R& s3 n# {9 H# v
23
& Z/ Z$ v* W: T+ }9 [ 24
3 y; z5 N6 I+ a% m2 x2 c 25
6 a b6 m" f0 |3 H 【CAUTION】关于时区问题的说明
: E ?# J) z: z( x 各类时间对象的开发,除了使用python内置的datetime模块,pandas还利用了dateutil模块,很大一部分是为了处理时区问题。总所周知,我国是没有夏令时调整时间一说的,但有些国家会有这种做法,导致了相对而言一天里可能会有23/24/25个小时,也就是relativedelta,这使得Offset对象和Timedelta对象有了对同一问题处理产生不同结果的现象,其中的规则也较为复杂,官方文档的写法存在部分描述错误,并且难以对描述做出统一修正,因为牵涉到了Offset相关的很多组件。因此,本教程完全不考虑时区处理,如果对时区处理的时间偏置有兴趣了解讨论,可以联系我或者参见这里的讨论。
% g+ G4 p, b& ` / c e8 `, w. u. d9 ~
10.5、时序中的滑窗与分组$ l" A: L" X' R7 m( i9 X
10.5.1 滑动窗口' Q8 @& |( s$ ~9 n# b8 L+ I
所谓时序的滑窗函数,即把滑动窗口windows用freq关键词代替,下面给出一个具体的应用案例:在股票市场中有一个指标为BOLL指标,它由中轨线、上轨线、下轨线这三根线构成,具体的计算方法分别是N日均值线、N日均值加两倍N日标准差线、N日均值减两倍N日标准差线。利用rolling对象计算N=30的BOLL指标可以如下写出:
* o2 J# n" s+ G# y4 S- b 6 u7 Z$ E+ v7 p" t
import matplotlib.pyplot as plt9 J3 {& \$ o# M8 `& i
idx = pd.date_range('20200101', '20201231', freq='B'); F$ H' {, x# x
np.random.seed(2020)8 o% N! A% h; ]4 v* i
1 v9 |- P+ _5 r' Z5 L
data = np.random.randint(-1,2,len(idx)).cumsum() # 随机游动构造模拟序列,cumsum表示累加 k; n, s% A6 T& ?% D5 a& q( ?
s = pd.Series(data,index=idx)
1 n* [1 }! `6 J0 ~; T s.head()
3 e" W( Q8 ?& | d Out[106]: 1 w; p; ?* F& x9 V% e7 v
2020-01-01 -16 p6 R' I/ B3 H0 ]4 `
2020-01-02 -2
6 X: ]% q+ }& W8 W6 `6 E) k1 ^ 2020-01-03 -1
7 W( u, W3 G5 G' \. ]& Q2 W. l 2020-01-06 -1( j2 X# O I& t7 U3 M9 q
2020-01-07 -2/ a; E5 s4 q' c
Freq: B, dtype: int328 m% }# w- E2 C: `( _) K+ u
r = s.rolling('30D')# rolling可以指定freq或者offset对象9 N. o% \1 ~$ v2 d# x8 u2 i( f
' f& K/ T7 k$ X1 I4 ^ plt.plot(s) # 蓝色线
6 ~5 x; i8 M; L Out[108]: [<matplotlib.lines.Line2D at 0x2116d887eb0>] V7 I* [$ ]3 _5 n1 c" O
plt.title('BOLL LINES')/ |* |4 y- l: G$ ~$ O* S; X2 I
Out[109]: Text(0.5, 1.0, 'BOLL LINES')
# @# q- v! i" f; G$ B+ P 5 Z' q/ Q( I ?" L
plt.plot(r.mean()) #橙色线( P# C0 I& ]" e2 A! x8 |; a2 a
Out[110]: [<matplotlib.lines.Line2D at 0x2116d8eeb80>]
# B$ B7 M! u4 H9 Z
0 h3 u* G" X5 H) Q9 U3 ? plt.plot(r.mean()+r.std()*2) # 绿色线
3 n/ s( S( S# C* ?! v! H; o Out[111]: [<matplotlib.lines.Line2D at 0x2116d87efa0>]) ~. U( g v T' q
. p0 o1 h( `0 @2 u' i) k- V
plt.plot(r.mean()-r.std()*2) # 红色线
$ I9 G5 C6 {0 d. J5 c- f# w Out[112]: [<matplotlib.lines.Line2D at 0x2116d90d2e0>]
! _. I% M# ?3 Z" [4 t2 z
* J H( ~+ o8 r. K2 s: J 1
2 D* [2 V! O# m2 }7 D 2
% K. Q z9 `* y' h, v 3
4 T2 k& I- ~) ? 4
5 K6 Z+ N2 E6 H( j+ B# ~" ^( i 5
8 L5 J, T/ K; E 6
0 w* Z U3 d+ Y% J7 Y% s: c* B9 B 7* z7 b: J4 P* @) g$ B" f [
8 d, I+ j6 @3 q9 d
9' A3 {, E: x# g7 w# Y. _! `
10
9 T8 q9 q. N. b l \' ]! @8 x0 H 11
7 ~4 }7 b% ~9 } 12
5 N" m/ Q$ M" ~ 13+ e% u& e2 k% c
14
3 N. O8 S) v" Z& S) A 15
: w. G1 Y9 J. {) _, g" w 16
5 R) o( T# e+ ^5 L; u* g( M% p! b& s 17
' u; j' M6 U8 `3 \ 182 d' P; p- n) i! ]2 v
19. e( H7 C0 Q6 k& N: s" b4 g5 V
20
- k/ E- B" Q1 o' N 21* a; Z4 d$ y* T& C2 X9 V$ `
22
) d ?* m4 v/ z' W) _" q: A 23
- [- M& G4 f+ w( m4 Y: o$ Y 24
& V! c% Y6 G, `# ~, U% {! a 25+ v8 L5 ^) i* E
26
5 l% q" s% Q* l/ v/ ] 27
1 Y& F4 D9 v2 K( b2 r1 ?+ @2 T$ E8 S* p 28
! w; x) l% N" \$ e) }$ C 29: \6 W( C F$ i9 X( v$ c# r# }! {
/ N& d$ o* T+ P% f5 g
这里需要注意的是,pandas没有实现非固定采样频率的时间序列滑窗,及此时无法通过传入freq字段来得到滑窗结果。例如统计近7个工作日的交易总额。此时可以通过传入多个函数的组合来实现此功能。
5 ?- X Q5 N5 Z; } 首先选出所有工作日,接着用普通滑窗进行7日滑窗加和,最后用reindex()恢复索引,对于双休日使用前一个工作日的结果进行填充。
5 n& P9 S, u6 y/ C' a2 z, G. Z/ b D6 C" z/ E! v3 l
select_bday=s[~s.index.to_series().dt.dayofweek.isin([5,6])]! d# r# b& C4 a" f3 y) X2 z
bday_sum=select_bday.rolling(7,min_periods=1).sum()9 B1 V" k* P# Z- ]
result=bday_sum.reindex().ffill()
/ g( l$ D( S2 s! @2 y* J' F5 N result8 o8 e% U; s' }" R; Y# ?
& v) q8 \3 A. j0 X+ Z
2020-01-01 -1.0
8 y4 ~% ~2 I! D, n; r# x5 l4 d. X 2020-01-02 -3.0" r2 M- }6 W. v2 S7 M: v) Y: o
2020-01-03 -4.0
3 t! b9 _8 [7 c0 |" _ 2020-01-06 -5.0
' q) a. d! g: U/ ]5 d' V" k 2020-01-07 -7.0
7 s/ h4 G$ X6 V% l/ L$ g ... 8 l9 v$ i1 e; _ q+ Y
2020-12-25 136.0
5 l7 G6 K) i6 ? 2020-12-28 133.09 U: ^7 h/ c* A7 E
2020-12-29 131.04 ^. ^4 S: [. y+ i, b' N
2020-12-30 130.0
" n' K4 L) ]! ?2 X% F 2020-12-31 128.05 N) A( I" J6 w- M
Freq: B, Length: 262, dtype: float64- u1 D8 D4 W4 g' ^. U2 I
/ h7 U4 @5 R+ |
1% U6 `/ \; H) y" R3 E+ r
2, d4 X) t/ t9 v7 v
3( }, ~- H3 B0 s+ ~6 J
48 T! J" p: {2 `4 H9 [
5
( r3 {$ }% y. v. ^6 p 6# D: r+ V5 n; f
7
$ y1 J: V6 r; {- i0 v. @% y 8
) a! k7 Q! \$ c 9- X, b* q5 P$ u$ S2 q2 t B8 N
10
9 h4 A- M( m ?* Z& r- c/ a) Q 11* L U2 Z" W/ Q
12
4 z5 Z8 i ^' a! ~7 E- Q; O% Q% U 13
( n1 x+ n6 ]: I 14
& r' [- b( F8 i$ T. S5 f: E 151 t. f4 [: B5 n/ `3 b- X3 ^+ ]9 ]
16* n3 B) D. ^4 r' j1 `# E2 l
17$ P4 \% P! z& w1 F6 L
shift, diff, pct_change 是一组类滑窗函数,它们的公共参数为 periods=n ,默认为1,分别表示取向前第 n 个元素的值、与向前第 n 个元素做差(与 Numpy 中不同,后者表示 n 阶差分)、与向前第 n 个元素相比计算增长率。这里的 n 可以为负,表示反方向的类似操作。+ _$ `6 i; {6 P& w; h% Y
' A5 w7 Z. \/ I 对于shift函数而言,作用在datetime64为索引(不是value)的序列上时,可以指定freq单位进行滑动:! E7 }) c" \4 }/ a- J' K+ z( q
- w, b9 |' r7 m" n/ g s.shift(freq='50D').head()$ N _2 `2 c! G7 K7 z. {6 v$ @
Out[113]:
' ^1 f$ X# ]' S5 Y4 z& f 2020-02-20 -12 h R+ A6 \: I2 D+ C8 j3 U) G
2020-02-21 -2; `# O' ] B: t6 \
2020-02-22 -1
0 \$ @; ]7 O0 T5 l% e 2020-02-25 -1
( S% a/ i) f. c3 g. H/ ?7 x9 k5 [ 2020-02-26 -2* w" T" }5 w/ b: H K9 b1 x* r
dtype: int32
. s4 ?8 c1 k' Q' I* }# J U 1: o+ Z5 Y, s! l' F
2
1 g0 e) X) v9 j7 R* N. K) ]2 P. b7 f 3
9 j3 z Z/ ? p9 |/ z1 n) d 4
3 g. I* }( S% } O5 z% t+ k 5
- \1 ?" C% [* i5 w, B7 F 6" C6 T+ a0 w$ |" d' C
71 ]% e) y B8 q6 D% O0 ?3 V
8
% n% a( S# s( j3 `/ B2 Q 另外,datetime64[ns]的序列进行diff(前后做差)后就能够得到timedelta64[ns]的序列,这能够使用户方便地观察有序时间序列的间隔:, V" Z4 `' F+ Z q( i- o7 P' x
; Y4 K' p( l8 S) V% N% J my_series = pd.Series(s.index)
5 B! ]3 C, K8 d my_series.head()6 ^+ O7 ?: K/ _6 s7 G* J# w) C
Out[115]: * p& w4 @2 e0 D6 f# g* Q$ H
0 2020-01-01+ h5 g1 V: F" k* U5 Z! C- M
1 2020-01-02
$ D# }( s. F5 N+ A8 }5 n/ q: [ 2 2020-01-03
- ]/ X8 ~& t3 _' J 3 2020-01-06# W7 ~. y& k% \, e
4 2020-01-07: T/ X& _# H( L6 D- z
dtype: datetime64[ns]
& `5 m9 }; t, ~' W7 z4 c5 v
6 i( Z4 v9 n0 g my_series.diff(1).head()
9 Q. K% A; I9 D: G1 j Out[116]: : @. u7 N8 }; n* h) }
0 NaT
! q8 z# r! H T& ^; L, S 1 1 days
) O% N) S( Y% I% h( t& H% Z 2 1 days; R8 Y$ o( D9 A, ?* p
3 3 days9 }/ j! X. ~) J( p
4 1 days
4 Q6 [, T, {$ d* {, V* C dtype: timedelta64[ns]& R$ ^9 h- n" k
3 V: K+ v }# r7 @7 s
1
4 ^* X0 l6 e7 ~8 N/ w+ R! _) L s 2
V8 b; J( z9 f0 z/ g 3
% R" C$ c8 H# ~/ Q2 ] y 4: R& P' h" N# W: [/ }
5
/ F& p- f& l+ }- |* ~ 6
( \1 }/ D- I& b! B2 b8 L 7
, X6 s ~% i* T- @ l! ?4 H% i2 k 8
$ J6 g2 t) p2 H1 J+ t 9
: T, \4 l9 q0 Q6 e; N, S3 f$ n 10& `% p# o0 V5 W3 a4 d* `, U* A* ^
111 I4 a T m* f* Q9 F
12( @! M: k, N+ {; m6 ?: J
13! _# H9 A" \& z6 t! |
148 p- k5 j: i9 b9 v' ~
15
: |; K/ ?, n4 v8 L; T: j 16, h% G8 V' X8 p7 J' d
176 Z3 F+ m1 q+ G, E# y7 Q/ M
18
G% ~& @( V+ y8 b- d: v( J- s 10.5.2 重采样/ r7 b8 f0 i, o( E' r' G; s
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)6 v8 Z# h/ k) T T/ @4 f4 y
常用参数有:
6 B, Z7 u2 M$ s4 m d7 r' J3 u 6 U7 e8 C, U3 Z: w: A( Y
rule:DateOffset, Timedelta or str类型。表示偏移量字符串或对象% r" [, o, K* @. T# g! I' @
axis:{0 or ‘index’, 1 or ‘columns’}, default 0。使用哪个轴进行上采样或下采样
( N, [0 P9 l8 ]5 N- B4 \5 { closed:{‘right’, ‘left’},默认None。表示bin 区间的哪一侧是闭合的。所有offsets的默认值为“left”,但“M”、“A”、“Q”、“BM”、“BA”、“BQ”和“W”均默认为“right”。% q2 `' z, r3 a& ^0 E3 n
label:{‘right’, ‘left’}, 默认 None。hich bin edge label to label bucket with,所有offsets的默认值为“left”,但“M”、“A”、“Q”、“BM”、“BA”、“BQ”和“W”均默认为“right”。, D2 p7 b }+ R3 o. x+ r1 A2 Z
convention{:‘start’, ‘end’, ‘s’, ‘e’}, default ‘start’。仅针对 PeriodIndex,控制是使用rule的开始还是结尾。
5 W! |# r) d0 V5 ]6 U; p on:字符串类型,可选。对于 DataFrame,使用列而不是索引进行重采样。列必须类似于日期时间。
/ y4 ^$ h9 d! { p+ {& i. `! ] level:str 或 int,可选表示多重索引MultiIndex的级别,这个级别的索引必须类似于日期时间。
' y. s& Z/ i' V( S* i8 a origin参数有5种取值: \! l- {7 ~" e" H0 ]
‘epoch’:从 1970-01-01开始算起
6 D) H0 s5 n) z ‘start’:原点是时间序列的第一个值& f3 ?; s, S- s
‘start_day’:默认值,表示原点是时间序列第一天的午夜。% G# c$ L2 h; e6 k5 N/ i1 }
'end':原点是时间序列的最后一个值(1.3.0版本才有)
/ N2 X5 t" P0 ^0 W% p( H" [ ‘end_day’:原点是序列最后一天的午夜(1.3.0版本才有)
' E# T: n, ]; o9 K, s offset:Timedelta 或 str,默认为 None,表示对时间原点的偏移量,很有用。) m3 l/ _) K1 s' m+ m4 y% F
closed和计算有关,label和显示有关,closed才有开闭。
- ~, u: V2 X; Z1 Q$ r2 ~6 p& B" u# E label指这个区间值算出来了,索引放区间的左端点还是右端点,closed是指算的时候左端点或右端点是不是包含。, D& L' l$ V* U/ W; u5 e
. M6 z0 b$ q' P3 t
重采样对象resample和第四章中分组对象groupby的用法类似,resample是针对时间序列的分组计算而设计的分组对象。例如,对上面的序列计算每10天的均值:
4 a5 F$ [2 O: z4 Y6 f2 G; p' B s.resample('10D').mean().head()/ K1 S, c) L, a5 h- |+ A
Out[117]: 5 x0 t* l* r' |$ ^( k
2020-01-01 -2.000000
7 l9 P/ {6 Z2 | 2020-01-11 -3.1666678 Z. ^" ^# }3 o. a% a h
2020-01-21 -3.625000
) M) N; a5 d& |* ^) m 2020-01-31 -4.000000$ a/ s% w3 T+ {: i& y
2020-02-10 -0.375000
8 `# ~& L$ e# f2 p Freq: 10D, dtype: float647 m3 N7 ~0 g4 O& }" z; H' z) G
1$ y3 t) q7 R1 \" c0 ^$ {
2/ c D% }+ I& U: [
3
% c% M; K2 O! h. x/ Z8 p* P- l 4- G( C/ H8 ?4 n
5) N8 R; n6 ^# H3 a! Y
6
! A* N4 t, E: \. V2 E 7+ o: i: H( ]* [* B" S. h( T
8
3 i, Z) e( {; Q1 {9 m2 q 可以通过apply方法自定义处理函数:9 S3 ?; c4 Y( }& ^
s.resample('10D').apply(lambda x:x.max()-x.min()).head() # 极差
3 t9 s7 [# r' R/ E2 x# N) ?# B
$ N) r3 D+ D9 X3 j5 t- { Out[118]:
3 W' W& _6 a. V 2020-01-01 3" M/ B: L A/ d- `- R' H
2020-01-11 4, \) @4 l, C5 g' T6 Z* K6 g: K& |
2020-01-21 42 }& m$ z0 W; Z( R
2020-01-31 2. z" w) x3 A$ ], Z T7 W2 M, m5 C
2020-02-10 45 h' G2 G6 X) P0 Y
Freq: 10D, dtype: int32 B! m; e8 d$ F6 b4 I
1
3 B3 r$ [; C+ c 2! _- `/ l! d1 P1 \
3$ L, W5 {; _- D0 Q) }
42 Z7 t q# V, e
5
8 R5 H( L6 F( L$ [5 A 6' b# P: ^% h/ l7 T) ?7 @2 W' E
7
# F, M; i3 X( A( I. p 8
8 J' E; y4 E- W. ~' p 9
8 k1 q$ U$ z# y) v 在resample中要特别注意组边界值的处理情况,默认情况下起始值的计算方法是从最小值时间戳对应日期的午夜00:00:00开始增加freq,直到不超过该最小时间戳的最大时间戳,由此对应的时间戳为起始值,然后每次累加freq参数作为分割结点进行分组,区间情况为左闭右开。下面构造一个不均匀的例子:
7 M3 B. X( q' i 1 _: ~0 F J) N8 |% z1 b% o6 u
idx = pd.date_range('20200101 8:26:35', '20200101 9:31:58', freq='77s'): J+ W" z+ {& X8 {
data = np.random.randint(-1,2,len(idx)).cumsum()
7 K& [: D7 _2 u5 v/ b- `$ `) p s = pd.Series(data,index=idx): L2 _4 M; ?3 o1 n- e8 I1 y
s.head()
6 t, x/ G2 ^ W# \- ]
1 T- \6 h% `+ F4 \0 G Out[122]: 7 C( q' D" w# `" X: O! d6 [, a
2020-01-01 08:26:35 -1
5 V* n2 W1 o6 [4 v8 K0 K0 U 2020-01-01 08:27:52 -1: Q& _$ O4 T$ m6 b* I
2020-01-01 08:29:09 -2
! ^0 I) S+ R5 }/ [# r. C 2020-01-01 08:30:26 -3/ }3 v7 s$ a3 d2 K+ o" }6 C# q
2020-01-01 08:31:43 -47 v- ]& ~( X$ @8 C1 a
Freq: 77S, dtype: int32
^ W6 M2 e8 n1 r+ z 1
4 C1 |7 U& C+ o+ p! [3 ? 2
7 K7 J" L: y0 G' t" h% ^ 3: W8 {0 v" b3 a1 K' E8 ~
4
# p+ M$ o$ I. Y' e1 l/ x2 J 5
( `4 ?2 [# Z" ^ | 6/ S' q& W2 M+ [) e- G
7
3 ` ^; z, a/ w4 e$ i 8
! k# X4 g4 q* b* A: R# ]6 [ 91 U1 S2 c9 i% ?0 G( ^4 U
10
$ {# U/ Z! h- g 11 B6 X) m8 o, v! \5 a
12
$ S* g0 \ R! b- X, } 下面对应的第一个组起始值为08:24:00,其是从当天0点增加72个freq=7 min得到的,如果再增加一个freq则超出了序列的最小时间戳08:26:35:
; k$ A4 Z$ a6 g8 h5 D " B/ t$ W- q0 _" C6 R8 N# ^/ a
s.resample('7min').mean().head()
~) j/ \# J3 N# l4 g Out[123]: ! l( e6 V+ r. q8 h: D9 a3 ?
2020-01-01 08:24:00 -1.750000 # 起始值,终点值包含最后一个值
3 W8 l2 H. V# n# I9 B% {6 n 2020-01-01 08:31:00 -2.600000# ]. @" w& w p5 A
2020-01-01 08:38:00 -2.166667
7 h! G4 U; c( }( W' J. m7 @/ d 2020-01-01 08:45:00 0.200000! S# m2 M: u7 d* i" F1 o& _
2020-01-01 08:52:00 2.833333
, N8 H9 O7 }; s6 I6 f2 m Freq: 7T, dtype: float64, X' w k3 l1 t6 e/ Z& j% @% L
1' t8 Y8 i4 Q( E/ g8 ~, D3 O+ G) F
22 }9 \3 h/ ^+ I) c
3* Y* X8 A$ d+ ?8 J' r; j
4' s! }- N) a& W' x7 a$ _! P
5: s$ X; Q0 x* q4 d+ w
6. T" K4 \2 ~; P
7
. U' J' }7 U) f- `* {6 @ 8: T0 a& d3 T t/ m$ d/ f5 E
有时候,用户希望从序列的最小时间戳开始依次增加freq进行分组,此时可以指定origin参数为start:/ K( a6 n+ |. ^5 h8 u# m" Z/ Q/ X
1 W! `/ }5 Z7 Y# P$ Q5 R$ H9 t s.resample('7min', origin='start').mean().head()
- W& q1 d+ {5 b# \+ A* E+ { Out[124]:
" N: r+ v4 k2 @6 u$ W" _8 T7 M; I; c6 L 2020-01-01 08:26:35 -2.333333
; R; w, X. J( e# o' z2 U 2020-01-01 08:33:35 -2.4000005 g- t2 I" R/ c% w! |
2020-01-01 08:40:35 -1.333333
" F7 a, |- s( d } 2020-01-01 08:47:35 1.200000
8 X7 ~# A! {+ i, H5 P: n 2020-01-01 08:54:35 3.1666676 o: j4 g/ Y- I1 ?" i# ]- j& e
Freq: 7T, dtype: float642 o) T; `* y6 e0 o6 w
10 F% R0 D9 K/ p9 o7 d! G# K7 a
29 G/ f& d) M& L1 `0 [4 f
3
9 `% D* s0 D) [. F( f& _ 45 I9 D( X. ]/ Z3 P8 k& e
5
- r1 C4 U% v: L/ E 6+ H& f4 l2 ?2 e
7
) s+ C0 U+ J/ d* Z 8
6 M, H6 A$ t# ^! ` 在返回值中,要注意索引一般是取组的第一个时间戳,但M, A, Q, BM, BA, BQ, W这七个是取对应区间的最后一个时间戳。如果想要得到正常索引,用’MS’就行。
5 m" u6 T; E9 F& P6 A) v* Z! e . ^1 W4 d* ^. X. B& S0 V, g# n
s = pd.Series(np.random.randint(2,size=366),
* x& x2 R) G( S index=pd.date_range('2020-01-01',
& ~+ `8 a6 J( Y' i: J- c2 O '2020-12-31'))
: x' u. _4 A: D8 b+ H/ n% Y0 y7 S) _9 ^ + G" {' k8 e( o9 g A9 [
w% |2 p, z& Q; _
s.resample('M').mean().head() y m6 d% \- f% F' t% B
Out[126]: ( r* f8 q4 f6 ~5 E5 g
2020-01-31 0.451613( I6 \1 f5 F' R3 L$ Q! Q" w. a7 e
2020-02-29 0.448276
1 K3 t& I+ J; v/ `* \* L z, b 2020-03-31 0.5161293 d9 E9 ^9 Z& b4 C* P r
2020-04-30 0.566667& X9 V; w/ E" D: }" q" z8 x4 [
2020-05-31 0.451613) C7 V& ?$ e/ O( ]: l5 @
Freq: M, dtype: float64( }. c J- g' s g! Z; a1 O/ I
e- l# g+ _0 G& Q- D5 Z s.resample('MS').mean().head() # 结果一样,但索引是跟正常一样! c/ @) y9 G* V+ g! y9 N
Out[127]:
$ k, p7 u% q! g9 H5 N. D3 ~. E2 t/ V# E 2020-01-01 0.451613* U/ l7 F( P; Z9 G& w
2020-02-01 0.448276* M/ P o7 m' [) N
2020-03-01 0.516129
' e+ n1 r0 |# W) O& t a 2020-04-01 0.5666672 s4 J. K- T( m- a
2020-05-01 0.451613* G5 J F0 t5 W
Freq: MS, dtype: float64; S9 G) y" r9 X/ r
0 L& _4 a2 p9 u& H' I0 r) i8 \ 1
" g c3 y! h" a/ h4 ` 2
! e2 s! c/ n' _! C& Y1 t 3
9 Q: z9 G/ j" r8 d8 K2 Y 4
6 P: D% ~. Q/ w 5
' X# M: x3 l" \$ V* O! { 6
( |& y: f1 u# ~/ z 7
! e5 P! q- m7 B5 H' ^ 8
; h+ j/ c( _+ B, v* P$ d. l 9
) k& G M; q, p0 i/ R; S2 ~ 10& ?6 P2 _' B: ^+ C" O
11
! v h" [) W6 {1 T! g6 O( p! n 12: c1 |0 a; }' R; M& T
139 S* U& s4 V2 b3 P* w1 M1 I& c
14
8 b5 N8 [, t+ t J 15
# o7 y0 F8 C: k; P+ p. x 16
; G! c3 m9 U" M9 K- l 17( W0 _3 N, Q+ p) a5 U3 p
18: n+ w) G) T; d% T& u
19( I' U0 L8 r1 m
20$ b; M* z4 U: _8 X6 m/ {% `
21' R* ^# y* v7 `* e n: d( C
22! J% H+ r5 a. M k0 \
对于 DataFrame 对象,关键字 on 可用于指定列而不是索引以进行重采样:
: P0 ^* S `) g% Z2 ~) X; J8 } d = {'price': [10, 11, 9, 13, 14, 18, 17, 19],6 Q0 n) g$ ^) u# h
'volume': [50, 60, 40, 100, 50, 100, 40, 50]}
) ^ G+ o6 M+ T$ W, F$ j7 H df = pd.DataFrame(d)
+ x+ f9 z+ K) f& h df['week_starting'] = pd.date_range('01/01/2018',
/ O. c; v y/ e( R periods=8,
9 v1 k! k) N- g! z, F4 z freq='W')
: T- D+ L+ j7 c5 b( k df
- ^4 {) e' v# q/ y5 R price volume week_starting3 ?' {4 R$ l& J) l3 n
0 10 50 2018-01-07
& P. K. y5 `7 }* r/ C 1 11 60 2018-01-14% R2 v$ K9 V; q6 P, W+ G7 \
2 9 40 2018-01-21! Q8 w9 S+ d: I, k: a, x5 K; x
3 13 100 2018-01-28
+ Q' y7 h0 W( W, [" @ 4 14 50 2018-02-04
+ ^* Y1 S! i. [3 ]2 d" L Z 5 18 100 2018-02-11
6 @) t5 h2 }+ z1 o" F+ X 6 17 40 2018-02-18
3 o/ ~# s% y. b( Q9 Q' a8 M$ p: X 7 19 50 2018-02-25
|+ L* v' x4 U& l% _ df.resample('M', on='week_starting').mean()
9 E! y' l m$ a# S price volume
# Q6 W' O. ~ `& F( ^, F week_starting4 C" e0 i8 W- |, l4 I" j1 V9 b& y
2018-01-31 10.75 62.5
) O: z5 y2 g |* F; Y; l# I# p 2018-02-28 17.00 60.0
2 d- K' `! I2 C1 I& A n8 J 6 P4 n9 C/ d" M8 \( X* F/ g
1
$ f6 H. X _$ n# c 2
8 B& o) T9 l$ } 3) Q& w+ l5 W# p% @# C& A r
4
3 n3 A0 T, F6 }* e/ R 5
u6 Y( O+ ^' N+ U) v3 e" _3 F' j& v" V 6& A% x% u F' O8 H; W- U. [0 l
7
5 L! C, H8 M- J, Z Q2 W 8; {# h6 F# {% S# w Y* w! w
9
) o: P1 W% M/ q- C5 T2 M 10; h1 z) t. s, e
11
" H, U/ N$ M6 P) S. v 129 E1 K! J" {: S8 G/ u, h0 x
139 P0 z0 ^$ b, W2 E" M. P
14
# B* J' f. |: h n. }% @: W1 E+ h 152 b: k: R# p& m v9 T
16$ P2 f: u$ A' E% x$ K
17
+ I" H# c8 ^% x1 A" B# E8 g2 Y9 ~ 181 O, L2 G2 q$ e/ O
193 l$ L, A5 K Y$ U1 {6 Y
20
; k' o, @ ~) G 21
. M5 w. B( }+ l1 @ l 对于具有 MultiIndex 的 DataFrame,关键字 level 可用于指定需要在哪个级别进行重采样。" M( c1 y4 j2 W3 S" o1 [. j+ ~ B
days = pd.date_range('1/1/2000', periods=4, freq='D')2 u% R( h2 d) ^7 e- a
d2 = {'price': [10, 11, 9, 13, 14, 18, 17, 19], S& K) n( M, w1 j3 w/ N9 k
'volume': [50, 60, 40, 100, 50, 100, 40, 50]}4 U. H+ K4 Z2 @+ [: R8 f. }+ j
df2 = pd.DataFrame(
* e& T# `" B$ s( m. n$ w d2,
" N2 y) P% S& z; G1 v index=pd.MultiIndex.from_product(
3 n3 u- L- Q4 O- q; R6 ] [days, ['morning', 'afternoon']]
( `! y4 X8 P, S )( n% Y" X) p) @. U9 v. J
)8 y! |8 Q; p1 {; F0 p& a( G
df2, I* x1 _+ r6 D) t' V' h
price volume
' d, R3 z6 H+ w9 i) B& K3 G6 M7 ~ 2000-01-01 morning 10 50
& `4 w+ W! H" r9 E) I3 w7 X afternoon 11 60
% P/ B0 ?8 N8 g5 M, w6 l1 q 2000-01-02 morning 9 40
. W' v0 ~% \% E9 E) ? afternoon 13 100. D+ E0 [! Z7 e. @/ G- ?4 V
2000-01-03 morning 14 50$ E8 h9 S1 D; p
afternoon 18 1004 X4 ^. t: Z- @6 [% g" K' @* D
2000-01-04 morning 17 40
2 }3 p( t, Y6 O afternoon 19 50
( f2 [ g! `+ D& g+ k' m! h* F df2.resample('D', level=0).sum()8 D" r$ d2 R# K7 K6 i
price volume8 y6 _& ~1 V) X: x9 B- h3 p
2000-01-01 21 1106 a- m* ^4 K) Q- a% S
2000-01-02 22 140, x; v& I8 {0 Q: s2 u& w5 y
2000-01-03 32 150+ M/ J' Y& q4 h2 q T1 }. j( z8 q
2000-01-04 36 90
! L0 f9 |4 H% A& ~+ h
+ x* G7 n6 {4 l 18 y2 D3 }8 I0 b/ R- K
2$ G! h2 Q/ z7 L: l V
3# b4 ~+ T2 l2 G. O# ]% ~
4
& q! S4 @4 k1 Q8 [7 ] 5
6 ]8 I8 n) _9 o4 |4 m) u2 ~* k 60 F8 f7 t/ h- [) L- x, Y
7 a( q m- k6 r0 \" W
8
C" S% ~/ W6 {* ` 98 x2 M) D0 i+ j! w
10
1 Z4 y& H6 `0 W 11
- ^+ A; q+ w: l" {" c 12
$ @7 R2 f# G9 O" z2 @! V2 ^; r 13
) H7 h J9 a& r9 A1 f) ? 147 ^2 g M6 p" r7 `4 B# n9 A
15
4 ]# y8 t! h& I$ |$ L 168 _, v& p, f& o! m- C/ N' `& ^
17& D$ y2 v/ M! ?1 P; M4 Z
185 T- c( Q, L7 p7 `3 i
197 [% w% c. ?* y& d7 r
203 C0 {* g9 T; D
21
3 A: S3 s# Q$ m 22* A: m7 W2 M) [6 ~, x. J
23
) f; H; b+ k5 U. ~/ t 24
3 p) Z+ l$ q% ]1 B 256 P0 z {3 l$ Q! a6 b
根据固定时间戳调整 bin 的开始:
9 Q s. f9 Q$ s" _ J6 o, y0 s, s start, end = '2000-10-01 23:30:00', '2000-10-02 00:30:00'3 R4 l8 G; Q/ Z
rng = pd.date_range(start, end, freq='7min')7 Z5 j% y+ M, D- a
ts = pd.Series(np.arange(len(rng)) * 3, index=rng)! ^+ A3 S1 P, X7 h- x K" x
ts
; ^3 R. W1 Y" g* T7 V 2000-10-01 23:30:00 0
; ~* G+ ]8 G& @: O6 v8 n3 f 2000-10-01 23:37:00 3: \) W/ x) E& } n+ X! X
2000-10-01 23:44:00 6$ ^$ p( s$ X# {2 f) D5 d! }
2000-10-01 23:51:00 9' E" c& i. B0 H/ H0 C
2000-10-01 23:58:00 12, K0 m' k; J4 M
2000-10-02 00:05:00 15+ Y+ N. z+ b4 j9 h' i: V4 C
2000-10-02 00:12:00 18! `$ t2 r/ ]" e# E$ Y0 [9 v: _% F
2000-10-02 00:19:00 216 A1 q8 J1 \$ W( m( v
2000-10-02 00:26:00 241 `, g4 E6 k0 m- U- e
Freq: 7T, dtype: int64
2 R, @, n& F, p8 f7 X1 ~9 k 6 U8 V# M7 W% F+ E5 q3 Y* m
ts.resample('17min').sum()0 {( V3 z4 ]. l1 P m8 X
2000-10-01 23:14:00 0
0 g i) q4 t& C- V+ j 2000-10-01 23:31:00 9
. u3 F: Z: V r% I r1 w 2000-10-01 23:48:00 21
/ ] ?: M# x1 r" R1 G) S- g 2000-10-02 00:05:00 54
+ S6 i e! R8 i1 ]4 c9 | 2000-10-02 00:22:00 24$ c" H4 Q |/ K s7 \/ D0 | u
Freq: 17T, dtype: int64
8 c+ F3 P9 j( A5 I: g$ L" w" V
. c% B7 v0 Q; F% S ts.resample('17min', origin='epoch').sum()
) F( [0 o3 n# a# ^5 h# A 2000-10-01 23:18:00 02 g0 ?1 V# p; R6 S9 Y5 G! l& j& h, K
2000-10-01 23:35:00 18
7 t' y3 T y# o 2000-10-01 23:52:00 27$ N+ [" |9 T3 A2 i7 f% E: b+ h% m1 x( T
2000-10-02 00:09:00 391 L! s: K9 b1 f) U
2000-10-02 00:26:00 24* c5 |* i$ P5 @1 J! ~# u
Freq: 17T, dtype: int640 B1 ^0 L" Y& w' N
* W$ C# O6 e! Z0 e( J' u
ts.resample('17min', origin='2000-01-01').sum()
/ q/ p( l/ r" P4 j 2000-10-01 23:24:00 3
% ]) I4 G _: Y 2000-10-01 23:41:00 15
9 F5 h' C2 U! b& q+ i 2000-10-01 23:58:00 45. ~6 l: x- N5 w9 W- G
2000-10-02 00:15:00 45! L& g5 S4 {3 j: b8 B' M; S
Freq: 17T, dtype: int647 b+ M9 G3 c/ h+ f3 T4 h5 l
! D# W% g! Y, R- e$ k
1
& [, P& v" W+ Y 2
8 [ }5 b8 U0 P 3
6 A- h4 J, e t- l 4
g3 r! E7 ^2 k9 { 5
& Q, k& ?. t( ^6 m4 I 6 d) Z: m' @: _+ D
77 q8 A% v% l- D0 y4 V6 \
8( t5 }& e% ?1 J/ T$ B
9
0 G; A) m- W. @7 _( v- ?, J 10 Y4 i* R# y, F0 _* H$ P
11& C, h: z+ |# m# e. O
120 j& k# D! p& O! \
13
/ V! i$ K# G/ H! U! Y 14
: S$ `: P0 u7 a) n7 j 15
: i2 k$ o5 v+ S. f4 F 16
* k. j* _. b# e' m! M2 m 17
5 `3 h6 j" c. Z. O& B6 A& j 18
6 [* c& H; x. ~( ^ 19, i4 Q0 R" X: x- t+ n
20, k& C. j/ M+ k8 Y8 Q# M
21
+ l; ?- E5 [0 [5 o! L+ B+ z" R 22
7 w3 A, }$ C3 L5 M- E9 \) B 23: j0 H; g0 L# v6 ~
24
8 T, m* u+ I( a ]& e! o0 t 25
$ ~$ }) a1 H3 a 26
7 u, G0 d4 b0 O 271 \4 _, M8 z) i
28: V8 T# ~9 ^0 p* h+ A% T3 `7 x9 H$ u
296 r+ k* R# X0 k O( ~: j
30$ e' U/ J+ m) u! p# D% T+ V( C
31+ A( l2 f1 E$ j: X
32
1 r4 U' ? y, _3 | 33. B" {9 D* u, ~1 A
34/ j' U9 X! B$ l9 w/ E
35
$ D6 G" A6 Z# H 36$ |1 D* U4 D) Z, {7 J
370 `' i1 X2 e' l, _( w
如果要使用偏移 Timedelta 调整 bin 的开始,则以下两行是等效的:
' X3 Y6 J) n) O4 h ts.resample('17min', origin='start').sum()) N5 C! a3 _- \1 j3 C0 z
ts.resample('17min', offset='23h30min').sum(), A0 G% h2 o1 p/ k: T
2000-10-01 23:30:00 97 \ N1 v: y" g8 W5 Q0 P( c X C
2000-10-01 23:47:00 21: c7 X) p& c, e o* @% N9 U3 I
2000-10-02 00:04:00 54
" D2 v1 J0 M0 Q' j2 ^ 2000-10-02 00:21:00 24. ]$ m% U0 s, a! r; o9 c& `3 p
Freq: 17T, dtype: int64
h. }7 }8 G: n, e8 w# N 1
8 {& k& v( }) I9 [# H7 X! l3 x 2& h3 c$ q; W1 u" G8 A' ~$ H
3$ w4 X! \& m) G; b
4
; L! o" [' a5 L 5' h( D( a+ @, m
6: W% u* y N$ \
76 L) I1 V0 W. i6 T: h' X
10.6 练习
$ B: y/ g. z, W Ex1:太阳辐射数据集. J' K/ R7 [ e e5 k0 [3 `3 f! Y
现有一份关于太阳辐射的数据集:
& i' E, t( s9 g* q
, j$ e5 A+ N. @6 V& ~2 D df = pd.read_csv('../data/solar.csv', usecols=['Data','Time','Radiation','Temperature'])+ z2 F9 h( Z) ?9 Y" j, j
df.head(3)
5 t; s2 m8 ]) G4 `/ a 2 z& F& U8 D$ t; e6 K$ N, l
Out[129]:
' ^1 ^7 ]5 V1 \/ q. G Data Time Radiation Temperature6 B s; z m- d% Q" k
0 9/29/2016 12:00:00 AM 23:55:26 1.21 48
) @& I' T2 E) F8 v1 e- I 1 9/29/2016 12:00:00 AM 23:50:23 1.21 48
3 v: v$ Q, i1 h1 _- V+ E' _ 2 9/29/2016 12:00:00 AM 23:45:26 1.23 48
3 ~/ b/ f% @" H# y 1
( G; W; `% `, v( j4 I" @- G 2
4 O, P6 f' e: M! @ 3
& B$ R4 M( k/ m+ e/ i 4
; x4 n9 t! Z, o/ z+ h t8 i& d 5
$ c0 O2 o1 ^& |' E. w- P 6
8 A' @9 ~ Q. L7 i( k8 E 7
$ m6 |% _: U2 z2 ?& H 81 _* D' g7 y7 }' x9 D5 Z6 ?
将Datetime, Time合并为一个时间列Datetime,同时把它作为索引后排序。
2 ]" C, [. ]. u$ q% Q) S 每条记录时间的间隔显然并不一致,请解决如下问题:
. _$ |1 v: G+ Q, } 找出间隔时间的前三个最大值所对应的三组时间戳。
9 p6 l/ b: c! P( ?& [# g+ C 是否存在一个大致的范围,使得绝大多数的间隔时间都落在这个区间中?如果存在,请对此范围内的样本间隔秒数画出柱状图,设置bins=50。5 D+ p) C" u& Q- T+ J( U% E; p
求如下指标对应的Series:
. ~' m. B2 t2 y |# m5 K+ w 温度与辐射量的6小时滑动相关系数
! w0 W) V' |, |) \5 i/ } 以三点、九点、十五点、二十一点为分割,该观测所在时间区间的温度均值序列
8 T- \. L8 P) d9 V P 每个观测6小时前的辐射量(一般而言不会恰好取到,此时取最近时间戳对应的辐射量)
5 Y7 ?3 f& z O7 c9 I1 J import numpy as np
4 j$ u( S+ t, A import pandas as pd
& v1 M% O5 k6 p r |9 R 18 j. q6 x- ^5 t" |; N1 T
2
1 v1 w/ Q( q, t5 j 将Datetime, Time合并为一个时间列Datetime,同时把它作为索引后排序。( ^3 g8 k9 l$ C0 L- L
data=pd.to_datetime(df.Data) # 本身是object对象,要先转为时间序列
( M* o3 h5 i" l times=pd.to_timedelta(df.Time), }1 R4 V7 Q( R7 n. o) C) D8 H
df.Data=data+times
6 T- f% }5 w% f2 M1 T del df['Time']
E- d0 y. F; ^6 l5 d0 X df=df.set_index('Data').sort_index() # 如果写的是set_index(df.Data),那么Data作为索引之外,这个列还另外保留
! V, Y) s8 M' {) ~# f9 s df: p& `' Y2 @6 `6 y4 p- \2 `0 Q
Radiation Temperature
$ `5 i# f0 d: q x7 i7 u3 C Data ' x7 z# P \5 \ u
2016-09-01 00:00:08 2.58 51
" t( s1 c" c* R- u 2016-09-01 00:05:10 2.83 51
$ y8 m2 M1 K' z, o 2016-09-01 00:20:06 2.16 518 e0 n# J1 P, H, @
2016-09-01 00:25:05 2.21 51
f( F5 d/ i/ F$ X1 u. e2 J9 | 2016-09-01 00:30:09 2.25 51; l7 N$ t; y- d5 U( x# J1 P
... ... ...
' j& w8 s' o5 t0 d/ n/ M 2016-12-31 23:35:02 1.22 41
7 t- g* j( V C( `3 k6 |& p2 _ 2016-12-31 23:40:01 1.21 41$ T1 t" z+ F. U3 M! L
2016-12-31 23:45:04 1.21 420 C% y: a( `/ a2 x; w
2016-12-31 23:50:03 1.19 41, y, T! b1 V2 o" W# Y
2016-12-31 23:55:01 1.21 41
# [1 t* D/ Z5 c5 x& D0 S* j& W
/ f# ]0 J& f+ A 15 P( r( P1 r6 C: D" h
2
, k8 f; J1 V1 i& t* m: k: a, R 33 @0 h/ ~6 D5 F* h
44 m0 O% I; h8 M" ^2 S9 e
5
( z. c+ f9 {0 Y( J1 a: B 6- I! c* U: I/ @8 s5 ^* d8 r
7
7 H$ J0 e1 v( u. o9 C 8
6 [0 b8 s. {2 k6 H" E 9
8 Z* t, E& g* i5 ` 10% Q* ?4 u& F! i3 m7 Y0 `/ O9 x
11
9 A/ a6 M# z/ B. M- J! U/ C 12
6 ]. S+ J1 ]+ S: B/ L. J 13
0 ?: b1 ~5 Q* Q 14: d1 k2 I+ h0 U5 ~ l
154 @ H- c8 I) @4 v0 |7 G
16
( D$ f, E% C. T6 T! y K0 A( r 17
- z% R. f0 i' z1 E 18
: }7 [& O. l3 M# ~ 19
0 l7 h' |- Q2 C- N& H8 r 每条记录时间的间隔显然并不一致,请解决如下问题:
) O5 t. Q6 j! v, H! a! |- [( y 找出间隔时间的前三个最大值所对应的三组时间戳。( q- M; z0 y! s7 W% d
# 第一次做错了,不是找三组时间戳
4 m8 S# t. D3 W' j; z idxmax3=pd.Series(df.index).diff(1).sort_values(ascending=False).index[:3]7 a0 N9 L6 V% P% V
df.reset_index().Data[idxmax3,idxmax3-1]
# [- M* w/ D$ W6 A4 P 9 k0 i* a8 H, X' x- ~
25923 2016-12-08 11:10:42
4 w. c* z# o1 G 24522 2016-12-01 00:00:02" H2 }& `+ \2 C# Z: F& U* N
7417 2016-10-01 00:00:194 t4 Q/ \$ S n: n; a
Name: Data, dtype: datetime64[ns]
! A3 E# q; g* e+ c1 ] 11 i" {( F3 M8 s) }3 V
2
P1 b' c0 C2 e7 x 3( R! t( O1 |" m8 ]
4+ @ z U. f; d
5
; e* X$ H3 Z0 K5 E3 G" U 6& v6 C& d \$ o9 \: q( S/ f5 V$ @
70 Q7 V" P) q6 ]6 G2 a
8& q) U4 F2 U6 S. i
idxmax3=pd.Series(df.index).diff(1).sort_values(ascending=False).index[:3]
1 Q8 N. i, z2 E4 o+ c1 ~- F7 s list(zip(df.reset_index().Data[idxmax3],df.reset_index().Data[idxmax3-1]))
0 J8 A% Q1 `/ }1 }# B
: W: P/ d/ G6 v [(Timestamp('2016-12-08 11:10:42'), Timestamp('2016-12-05 20:45:53')),8 E9 Z, a+ R+ q
(Timestamp('2016-12-01 00:00:02'), Timestamp('2016-11-29 19:05:02')),
, C0 K0 b) {/ o% @* i! ~* { (Timestamp('2016-10-01 00:00:19'), Timestamp('2016-09-29 23:55:26'))]
p8 V# M5 S; N& ~% s' Q 1
/ {$ Y2 @4 w) e3 Z/ R$ c& g( A, r 2
5 \) |! f* ^: E/ S- G3 F$ ^- C 3+ l& z* O! h+ F( Z2 b/ i( _/ m
4
c' Q; A; i4 k7 j; y4 y9 U: y 5
9 S3 V/ V ]/ z 6
$ \8 h+ r3 Z3 Q% l 参考答案:
' Z+ z: a2 U. Q, @7 Z 4 i8 t7 x1 @8 i
s = df.index.to_series().reset_index(drop=True).diff().dt.total_seconds()) E; ^2 ~: Y+ K$ u' o6 G6 I% k
max_3 = s.nlargest(3).index
( n, S# n. H4 K$ q8 R! L df.index[max_3.union(max_3-1)]
, y" l% r7 {/ y5 b8 y. P" J% ^" W 3 N, j: `- N; N5 i4 j
Out[215]:
4 ]! z) M4 B* u" Q$ g DatetimeIndex(['2016-09-29 23:55:26', '2016-10-01 00:00:19',
' q! ^. B8 R7 d9 O '2016-11-29 19:05:02', '2016-12-01 00:00:02',+ p, J$ |( q0 L( p
'2016-12-05 20:45:53', '2016-12-08 11:10:42'],
8 m/ n2 V3 y" d5 J! q: S- M: V% A4 f8 u: V. T dtype='datetime64[ns]', name='Datetime', freq=None)
S; x+ g2 Z( P- w/ ~& `! |+ @4 [ 15 t$ {( Y1 K1 Z4 I& \6 C
2
' ^* S, O- i1 c0 ? 3, f2 z( \. W$ z+ V1 `) C8 t: y
4) ^/ ^% j! @% q- H+ f: {" n
5
, q6 r, c1 k4 ~- c 6( {) q7 `' y. r$ |# u! z
7
( k7 L# y# R+ X& J 87 \- m* e; L; n$ j5 K$ B7 }
9
. B( [7 Z& W- a1 w! z 是否存在一个大致的范围,使得绝大多数的间隔时间都落在这个区间中?如果存在,请对此范围内的样本间隔秒数画出柱状图,设置bins=50。
3 P, C2 W- B! |5 W: v- `' u* A # 将df的indexydiff做差,转为秒数后排序。再求几个分位数确定取值区间
9 o; k# R4 _5 ^- r0 T s=pd.Series(df.index).diff(1).dt.total_seconds().sort_values(ascending=False)
4 ~0 ]& D3 _9 r# g s.quantile(0.9),s.quantile(0.95),s.quantile(0.99),s.quantile(0.01),s.quantile(0.03),s.quantile(0.05)% d: c% `6 B6 \1 j# r) j: h
! a1 Y5 B, F6 r% L! b' \# o
(304.0, 309.0, 337.15999999999985, 285.0, 290.0, 292.0); V4 {. _4 y- \1 w/ P8 ]4 Z
1
! j% S; { \; E/ ^4 @1 ^ 2) I6 w, j- m; U3 f V& b
3
' O2 S4 y( g$ q% M2 K 4# d! q z. G Q2 @6 \6 ?( _
5" `8 e2 ?; q; s, y! M
%pylab inline
: Q) D+ R8 {! o9 C' ` _ = plt.hist(ss[(s.values<337)&(s.values>285)],bins=50)
' Y% K1 v& `* |5 o/ e8 ~/ v1 T8 ~+ e# B plt.xlabel(' Timedelta')
& j4 y; i/ h0 ~. E4 ~* a- Z, Z plt.title(" Timedelta of solar")8 s( E7 L) Q" A* }1 g
14 ~) a( j; U' |1 T' c4 q: R- S
28 I2 M; m/ U3 \4 u1 ?
3. K& ?# t- P9 T( Z
4; r2 K* w. C" k
" E1 ], j" M9 [) J' ^
0 _ a, B( @( r3 n) Y' | 求如下指标对应的Series: Y3 E! ~8 J x3 K
温度与辐射量的6小时滑动相关系数
# J8 f$ B F+ H- M6 K! { 以三点、九点、十五点、二十一点为分割,该观测所在时间区间的温度均值序列
+ o8 n8 l. E& K8 E% H2 X7 M 每个观测6小时前的辐射量(一般而言不会恰好取到,此时取最近时间戳对应的辐射量)7 N. K. R) S3 V1 Z. X/ F; b
df.Radiation.rolling('6H').corr(df.Temperature).tail()
9 ?% s* ?* W. h$ ?1 M& @ * s5 C; N/ F6 v/ K: x: z) f
Data
, L$ h8 Z! W8 a, i/ A 2016-12-31 23:35:02 0.416187
* [' Y& U" B7 ? 2016-12-31 23:40:01 0.416565
' X+ g. D( Q7 O9 @- M9 [" p, U6 f' L 2016-12-31 23:45:04 0.328574
# m4 y* h* z/ e5 O) Q, q 2016-12-31 23:50:03 0.261883
/ y; u" `2 b q9 y' c 2016-12-31 23:55:01 0.262406
+ ?, }( D1 E' O. S dtype: float647 S6 ^# i8 w9 h; k7 E; s
16 x2 K# u# m- q' K& N' v
2
. J8 V z- D% Y( I 3
0 x+ p' c2 B7 h3 @, {6 b; a 4
1 m5 V' w/ z* k1 P, c' n 5
6 P, C) J' r0 B3 p# I 6# d* ~ T# Y' _9 A7 _. B
7
, _1 b* x4 a C& r6 s: N8 Z 8
. r9 Q5 x- F9 c0 x3 r! L( h6 g6 u 9
7 _% |( w" h! I: o' Q1 i8 { df['Temperature'].resample('6H',offset='3H').mean().head()
( m- G$ ^: l7 ]) L6 N
$ b( m, G' G4 a0 `: e. b5 Y$ T- u+ k' m Data( ?* _2 `& r+ F) G( v
2016-08-31 21:00:00 51.2187501 d9 }1 d! {/ ?* ^ p# l( a
2016-09-01 03:00:00 50.0333339 i' r5 m" v: d/ r9 D
2016-09-01 09:00:00 59.379310, p' z0 E/ P F% N# C' @/ R! L
2016-09-01 15:00:00 57.984375
$ P) a! c/ u% o 2016-09-01 21:00:00 51.393939
: N! v: `- U4 s% \( E) z" ~ Freq: 6H, Name: Temperature, dtype: float640 c: z6 C+ }% Q& x* `' J
1
R0 I! t/ g, B 27 y4 U7 h" k% d
3
: H. B2 j8 G7 [. {. t 4
* A, O: x7 j. J 5* P. m; |" C1 A; v
6 |) d4 o( X8 o) a% B% ^4 K! S! n
7
+ Q K5 V/ U3 R: @; V$ ]' K 8
9 w* g9 s# _, Z6 f$ @2 i 9
! K0 [7 A+ H! \: X 最后一题参考答案:
' b$ W2 W. K5 o1 M+ ~) C& n: i
* m( z% \* U C# \* d" V # 非常慢7 m3 @ \- {$ t& |) ]
my_dt = df.index.shift(freq='-6H')
' T% C1 V8 a+ ^( u* ]$ \ int_loc = [df.index.get_indexer([i], method='nearest') for i in my_dt], P% H( k! u: G5 A. c' E. A' X
int_loc = np.array(int_loc).reshape(-1)
" O; e' h! L; A% _' @$ L. z res = df.Radiation.iloc[int_loc]: X! `1 M; P0 x9 U+ m
res.index = df.index
# F( f- `: `7 _: J res.tail(3)+ i" F" f) w1 O! G! O# B
1
: H) F$ j M# @4 _+ n8 \0 _ 2
- q+ W) v2 {5 W% x 3
* G0 r2 C% J4 w5 |7 x4 v 4
8 @- h. c9 b: z1 C$ s7 Q" h5 v 55 K# s$ A4 ^. U/ I
66 q. h; p- ]1 M/ _2 U J6 T6 \
7
# x9 y# |! ~4 m' P! I0 f # 纸质版上介绍了merge_asof,性能差距可以达到3-4个数量级
8 p' H! D4 z: o7 F9 k. [ target = pd.DataFrame(
" J* ]7 y* H6 b$ A8 B% h6 A {
1 L9 r3 m2 b% T "Time": df.index.shift(freq='-6H'),5 T& c% m& ?% [& z
"Datetime": df.index,) y, q+ _' R Z7 {1 u9 Z
}
6 D5 d, \0 {- D9 O% L0 V4 n )
- u8 w1 A$ i1 O! ^
* G7 ?+ O# \: ~8 Z. C: s4 _' d4 b res = pd.merge_asof(
& I+ k3 P. ^( m* i( W8 z target,
9 V: D4 Q6 e5 x( S0 q' A df.reset_index().rename(columns={"Datetime": "Time"}),
" _; }5 [5 ?8 Q" Y' H" l8 \: R3 J left_on="Time",
) [3 j+ j/ H$ w7 e& W8 r6 e right_on="Time",
% n& f* r/ M6 F) w direction="nearest"
9 f) i, p, M9 g9 A+ M+ G' ] ).set_index("Datetime").Radiation8 J6 q( G3 [+ c, q
[* c9 H5 U: e; c
res.tail(3)" h) |* N. B' Y3 D! ^: L
Out[224]: ! _ r i C) T! y
Datetime" ^; Y9 o2 {! H& [4 m- g/ U. J
2016-12-31 23:45:04 9.33
* ` p X8 ?1 P: g- g( H 2016-12-31 23:50:03 8.492 V: O, m" }$ B* d8 i) f
2016-12-31 23:55:01 5.84/ d6 ~# i2 C2 R9 _$ E
Name: Radiation, dtype: float642 }% |# j! X2 v; b( V- M ^( e5 n
- K: `: s# M5 H$ q/ G
18 h. S4 D, n1 P3 e! B
2/ f% N+ z9 l# Q; {: W
3
7 y* G& i" w; n; W% |( J$ `9 M 4
# E. @: K/ U; |" i9 b! H 57 j/ B& r w& d. T w# r
6, O& e n: S9 B3 n
7; R# F: Q0 M" P
8, \. R0 |' h5 ?" q
9) i6 H% u, ~% e D) L* Z
10/ z9 H3 g# s, ^/ A* S/ X; t1 K, R9 e
11% U. ]1 |1 E. J9 N, Y4 K% f
12
+ n# Q! ?6 m* k% s6 G 13
! X% f% D0 G4 @( h( S 14
# `9 G: r) G- u4 |+ t, B( E$ n 15! Q. ^7 K, y% [% y+ B0 a E1 V
16
9 l- d& h+ v" c3 I$ N; {1 B) y 17% p) c" a5 o2 d4 q
180 s1 Z) M) z9 b: V& V
194 q5 a( I+ ?2 A' c5 Q1 p3 C
20. a3 Y$ [" u: T, _
21
3 f' G# m/ i9 p5 R3 u 22 W6 r/ @& {# d! ]( |
23
9 q; I9 M0 f8 X& |% C7 z' g Ex2:水果销量数据集
$ e. z& a3 g% @& V0 U5 C' e 现有一份2019年每日水果销量记录表:
0 I. ]6 a) ]/ m5 g6 C" j2 n8 g 1 U/ e- ?% Z) r t
df = pd.read_csv('../data/fruit.csv')
8 @" P0 q7 n8 b2 N7 a df.head(3)# z" {7 D: _3 x% K" c
# R: w) D) e' S' j. L' Z1 K Out[131]:
" a3 Q5 d; b5 L. l Date Fruit Sale" G& S4 |) U v o9 n! r( G8 C
0 2019-04-18 Peach 15! d* B. z9 [* s3 o! i3 a
1 2019-12-29 Peach 15( J; m3 b* x' m
2 2019-06-05 Peach 19
. Z$ i6 h" x& k) A 1
. Z( ]( p6 U! W* ` 29 J# C: S) g; g3 P5 n
33 X J4 v6 l& p( v
4% W+ c: y+ ]- a: C# Y; T: u
5* m8 B' D! a( ]7 T0 E2 ]
6
( B: ~) F6 K: x; h9 e+ a$ [' n! W 7
/ k; _5 m& a2 d3 C# [3 P 8
9 N" \ F/ V% o0 ]# W* d9 N 统计如下指标:
+ L* g' f, r& K$ o 每月上半月(15号及之前)与下半月葡萄销量的比值
7 d/ F: T+ @1 X2 Z% Q 每月最后一天的生梨销量总和
' x6 ]1 M# y5 d, F3 y8 @( S8 f: x' h 每月最后一天工作日的生梨销量总和+ g* @4 y) w6 V
每月最后五天的苹果销量均值
+ c9 m( B9 q+ t# s( z7 s6 S 按月计算周一至周日各品种水果的平均记录条数,行索引外层为水果名称,内层为月份,列索引为星期。6 {$ M1 ]* }2 J% H3 m
按天计算向前10个工作日窗口的苹果销量均值序列,非工作日的值用上一个工作日的结果填充。3 J9 s- Y% _3 K/ I- g9 f
import numpy as np
) v+ S3 u7 ~8 j3 z7 N2 }0 J { import pandas as pd& f4 W, U" ?; i* v! U* G
1
3 j+ O; u3 M( |# j 2
8 L. k- S( P: n- C u- ^- y 统计如下指标:
. p6 Y5 x% l0 T* s- s8 Z 每月上半月(15号及之前)与下半月葡萄销量的比值
+ L# F# A1 f, F9 U 每月最后一天的生梨销量总和1 F9 X' r. ]' z/ I9 @$ F
每月最后一天工作日的生梨销量总和
0 B- I9 U# i4 y 每月最后五天的苹果销量均值6 J+ e- F) W/ L8 z) y0 M
# 每月上半月(15号及之前)与下半月葡萄销量的比值9 T; Y0 f7 a9 j" N! U4 l
df.Date=pd.to_datetime(df.Date)# r I9 a( j' I& a0 [3 |* u: P
sale=df.query('Fruit == "Grape"').groupby([df.Date.dt.month,df.Date.dt.day<=15])['Sale'].sum()' R3 O; f `, }$ y9 ]* C
sale.columns=['Month','15Dayes','Sale'] # 为啥这么改没用啊
+ D7 L4 G; s8 {* L# J6 p sale=pd.DataFrame(sale)
/ E# Q- `( ?. H) Z" L sale=sale.unstack(1).rename_axis(index={'Date':'Month'},
$ Y" j- R: _7 n- B8 ]8 {. I. R columns={'Date':'15Days'}).stack(1).reset_index() # unstack主要是两个索引都是Date无法直接重命名( I7 G r: I2 s, R7 P* G2 r
sale.head() # 每个月上下半月的销量0 @% O' k. }( z- d6 ^9 X! I
- O8 g6 B! J0 m; t6 z2 F
Month 15Days Sale
0 ~2 h* a: L/ y( W7 C" b 0 1 False 10503* @1 e$ u- T0 A+ I( D( J/ b/ ~9 ]4 w
1 1 True 12341% A( w9 M' k( T! {
2 2 False 10001! b; Y% N5 m8 ]' L8 x' _
3 2 True 10106& ~$ Q r% j% b/ w
4 3 False 12814
& s+ ] s/ a8 ?( F5 n - m" ?; B* Q: @; ]
# 使用自定义聚合函数,分组后每组就上半月和下半月两个值,根据索引位置判断求比值时的分子分母顺序
5 v+ U- l+ L- a. b- Q sale.groupby(sale['Month'])['Sale'].agg(6 l$ ^' ?$ P0 {
lambda x: x.max()/x.min() if x.idxmax()>x.idxmin() else x.min()/x.max()), I! s( |4 v9 D7 m% {- Y
) k" V- t# Z- x
Month
K7 G$ ^# ]7 F+ @5 L) P 1 1.174998& G i$ D" v# q! `! k
2 1.0104990 F% l0 e7 W* c
3 0.776338
' y- F0 m! S' _' l2 \% R0 Z* m+ A 4 1.026345
- ?9 K( o4 k8 E3 W9 g V' ? 5 0.900534
|- V9 m- M) S7 ^# p$ ^" j( b 6 0.980136% j& h7 {9 Y1 S" a) E+ I7 |+ f
7 1.350960
; a! q9 E; m! d3 F! i 8 1.091584
. m6 q- Y8 r0 R 9 1.116508
/ @7 D* L5 L7 f6 m 10 1.0207843 }6 S6 r* Z& G6 p, }% h$ ]
11 1.275911' x8 w6 q5 p) m+ y
12 0.989662, Q. \7 Q6 k. k7 K' p
Name: Sale, dtype: float641 d( g x" t: i! W/ }3 b6 S
; Y1 g) B. S4 x, C
1; K5 f# [9 Q Z c' V/ D: @& j2 h
2
% o1 |/ }$ {# f6 u' {8 j! z 3
. t M1 u- B, g$ }; N! k6 V 4
; Q w# L( X6 I4 F9 v2 s 5
. k( I- o2 ?2 i4 i 6
9 X2 D9 J: u% d" Y, V. g7 m 7; v% |2 l d6 w: O( L
8! h0 W) e; M0 X' x0 _4 a
9+ v9 q( E7 B8 [2 t e4 T
10
- i1 f, Y* z8 W/ W" } 11
: H3 C% b2 R6 D! H8 m+ R" G 12
1 f. E& `% O) U1 m- R0 b 13
( { J' o+ i' R. T 14) {- `& S4 n) F
150 I& u9 G5 B4 L
16
6 f J, S7 |" q2 k! g 17
+ x0 r4 k. L2 t% s: r6 ]# K3 P 18
: ]; W4 F0 ]8 W# k% Y& m 19
+ c: {' A* `& F4 c( R* I 20
" b( Y2 Q2 y, o3 ^/ R4 Q$ X V. U 21
0 p8 S6 g* E8 I& T9 l 22
3 z+ Z! V9 `$ K+ J j7 P8 } 23
- \3 ?- ~( b1 J6 T 24
8 L7 Y8 I; h9 i3 T 25# f9 v& d& ]1 X, o# ~ d
26
+ v: d: D' Z7 U% Q; t! G 27
+ \1 E2 U: U7 ^+ z1 [ ]) Y' i. P& Y 28
2 g( B: J0 R9 f, W 29
4 Z5 p* q5 U6 K0 H, Y 30$ a+ `6 J$ f, D! C9 ?# P
31
: ]# Z" A: I" p% Z/ P% Y 32' _. R6 e& h2 b, ~$ k _' K
33' l( \! W' Z- F* m4 o
34
; Y. q. n7 B' z- a8 o$ n # 每月最后一天的生梨销量总和
+ f, I" _$ H8 \ df[df.Date.dt.is_month_end].loc[df.Fruit=='Pear'].groupby('Date')['Sale'].sum()
2 I4 f1 G- B q* u& B* h ! D/ r: {% Z( k' h/ a
Date
2 g' N3 g" X8 @ [ 2019-01-31 8479 D. D5 H3 q0 s# K" Q$ i. P
2019-02-28 7745 I0 H; L" f' l- x2 ~5 Q. e
2019-03-31 761
7 E6 }# }3 e7 R9 ~1 U 2019-04-30 648
3 p* D3 n2 f6 k6 w! o 2019-05-31 6167 F$ s# j8 N8 i
1# K' k4 U8 X7 L) C1 t# P! Y
2
# d! f \) u$ Z, d+ T: F5 c2 f 3
$ ~" S/ L$ W" O) Y9 c0 i 4
2 e- y1 e* D4 \# I$ p 5* C2 p0 D' [2 }, m2 z+ [5 k H
6
! }' _9 R0 b' Z! d 7" Q1 b/ }$ d8 w$ N9 d6 ]) ^
8
. n& K' _7 I0 u$ e8 L 90 E5 {5 T3 v7 J( x* x, @8 V# Y
# 每月最后一天工作日的生梨销量总和9 E6 p7 F- b% a, X2 F1 ~
ls=df.Date+pd.offsets.BMonthEnd()4 K: h6 r; D1 ]- [. K$ F( t
my_filter=pd.to_datetime(ls.unique())
/ \/ `0 E5 f' O% h! @ df[df.Date.isin(my_filter)].loc[df.Fruit=='Pear'].groupby('Date')['Sale'].sum()
( P( ]; h2 f, s8 S3 Z! j
3 p; B+ g- f& C5 B Date
# N/ F' { W7 ^& @* D. x3 u+ K 2019-01-31 8471 P; G; S7 N" P& H' q
2019-02-28 7748 }2 Y: ^/ i. f+ Q E* t
2019-03-29 510
$ U- P2 c! n* s9 e' k! |( | 2019-04-30 648
5 D9 B- h6 k2 {. [( q. d+ I1 } 2019-05-31 6166 P: N | w m$ Z3 ~" a
1: N+ S8 ~3 h% _; R0 c2 m0 i
2
, z4 a5 h6 M! }- H: V 3$ A0 @: l0 Y9 H% T$ h. h4 U) F
4
4 D) Z7 ?5 c0 i5 T$ O. R 5. G' J5 i1 N- y, f1 g" n" L
6* E9 v3 F, Y/ _0 A( f5 K
7: x4 ~- t, B' x" w5 b( x0 a1 c
87 X7 p. h, }- a W
9
$ ?* f# M6 B% T3 V3 [" U( L2 { 107 A1 Z+ I, s# ~; P
11* G7 \7 ^) y; E! h/ x
# 每月最后五天的苹果销量均值
# L2 X5 F a' ]+ c start, end = '2019-01-01', '2019-12-31'
1 f5 `9 B- X1 [6 S, \; F9 f7 N& O end = pd.date_range(start, end, freq='M')4 ~/ S6 V b* t$ n- y
end=end.repeat(5) # 每月最后一天的日期列表,重复5次方便做差1 n6 R# X) M) M4 B& c' t
& S. n0 J4 x9 [- ^, Q4 k. G$ X, e
td= pd.Series(pd.timedelta_range(start='0 days', periods=5),)* O3 ?& t. l- T
td=pd.concat([td]*12) # 日期偏置,最后一天减去0-4天
# Y$ M2 K, _$ a7 ^7 _3 \- A4 \; r end5=(end-td).reset_index(drop=True) # 每个月最后5天的列表4 V' R, t0 q! D! M0 Z( b
/ l' _2 ~/ n1 P: j/ L; A* T2 o# f apple5=df[df.Date.isin(end5)].query("Fruit == 'Apple'") # 每月最后五天苹果销量
! m$ [$ d& R5 K' q: ` apple5.groupby(apple5.Date.dt.month)['Sale'].mean().head()4 R7 S8 b6 v& q2 n! h$ U
& U1 v0 s5 `" U, v+ q Date
( i$ z7 i1 b6 f1 j; U" i 1 65.313725
$ S9 o, Z! W! i+ x3 Y+ t5 ~7 [, O' p 2 54.061538
8 _; p2 y' l2 ~4 C 3 59.325581
i0 g% e+ Y ` 4 65.795455- X1 k K7 Y' a: j5 m! }) Z
5 57.465116
$ n! Y( f/ Z2 O: L! w3 S
* _: C o2 C5 ?3 M5 L 1
6 a! K* n* U4 k$ F7 S 2
+ e3 U* k1 E& u) P9 S! g" A8 n3 [- T 3
9 B. K+ q( L+ W" h- B 43 g+ K% r: S; d
54 a! L6 |( C5 }& n* v6 B
6
8 y: I' }: ?; U x# _ 7
% H% w& x) o- J ` 8& y1 |% G2 c% G
9
5 a! I* m* v$ q( c 10
, p& [" G! G0 p( y8 j- t+ R4 s 11
& ]; E4 M+ f. o2 R7 n1 m( b 12; B* Q* H0 A) \2 |( T
13
1 K9 b& z3 q( m: F2 E: S0 s 14
$ [$ P, b$ T: Z/ O0 ~5 b6 a 15
9 `5 i# |' X3 P* u3 b% o" K5 F 16
( A3 a+ f/ ^3 x' @9 U: ]7 x* p 17
$ |* C9 e9 P- L( P6 z1 x) h 18+ q1 {5 u% T0 u, @( B7 R1 ?/ s5 h
# 参考答案: a+ t. ]2 W' L5 v( O3 i; P
target_dt = df.drop_duplicates().groupby(df.Date.drop_duplicates(1 f7 T. R4 X B) s4 ]! r+ z
).dt.month)['Date'].nlargest(5).reset_index(drop=True)/ r) m# L0 a; U1 M
5 N9 Z5 C2 b0 y. o' f! ?0 A res = df.set_index('Date').loc[target_dt].reset_index(
4 e$ g k5 T5 d ).query("Fruit == 'Apple'")
2 T! W/ c& X; ?
5 v% [" U, b! \. y6 s res = res.groupby(res.Date.dt.month)['Sale'].mean(8 k4 \2 ?# O0 L% U
).rename_axis('Month')6 P! R2 x; i# q4 Q k- B1 f
$ z0 ^% _ F7 g/ X2 ^* |! B
1 V" X. K2 T" q res.head()5 a, p2 E9 C! W7 f
Out[236]:
& i9 z, z* s+ E) p6 y, | Month
% f- a6 D: U S( {( I! \9 M+ a5 K 1 65.313725
" G4 K7 r: Q1 n S- w# c: c 2 54.061538
" X4 ~ W" z$ A0 E2 y 3 59.325581$ `. ? [1 A8 V, t9 z
4 65.795455# Q9 ~6 E( ^9 @1 C. O
5 57.465116& N, E3 s1 B9 T5 z8 Z4 M1 t5 c$ {
Name: Sale, dtype: float643 m2 _" l* V! E$ X/ s& d& p' M2 i
" ]7 Y( q& c. } f/ Z$ @. `
1, S3 P$ o$ Y8 s: ]9 ^
2# O7 o4 h$ _. {1 Z
3* E! x0 A8 s& f3 Q' i0 u
4
) I1 o+ }, J h) t8 L( w 5. F8 y3 ^. r- Z; q, ]& N# z7 ]
6- ]7 c4 s7 f6 I: o* g6 w
75 I- O% }; y, Y4 F2 C
8' w6 ^6 L1 w3 p
9 Z+ E. k6 k: `( d
10
N4 e9 r4 u* N" ^5 }$ ~& t 115 i; w* M7 G7 N0 k5 ?$ Y
12
8 q! k% _1 k- S3 I3 a) ]1 H6 q- m 13' C) _' a' P* |, q' Y1 n! z) |- e3 [
14
- l# e. {, o, n; V5 ] 15" N4 O5 t/ Q% E* g" B
16
* W) e/ C& T; ?* G, P! ?; Q 17
$ k0 A/ W5 p0 u" m/ K5 w1 W# d& r8 P 18
% m$ F$ k- ?6 Y7 }; h, _' q 19
% \0 l+ |2 z+ M4 N/ N: p4 u: H 20
' t) g7 ~7 y. H2 N, v6 ~, d z0 N5 c 按月计算周一至周日各品种水果的平均记录条数,行索引外层为水果名称,内层为月份,列索引为星期。( M8 F+ i2 _# k/ O/ I' k- Y) f
result=pd.DataFrame(df.groupby([df.Date.dt.month,df.Date.$ s3 M6 e+ v! v1 K1 M
dt.dayofweek,df.Fruit])['Sale'].count()) # 分组统计 5 U: f' [( v z7 z3 l
3 q% T! y# [7 d/ L( F result=result.unstack(1).rename_axis(index={'Date':'Month'},7 F( k3 s+ w M% j' Y* ]
columns={'Date':'Week'}) # 两个index名字都是Date,只能转一个到列,分开来改名字.
# m0 K* j- ?: g# s! q result=result.swaplevel(0,1,axis=0).droplevel(0,axis=1)
3 ?# |2 S/ T7 n* x% ? result.head() # 索引名有空再改吧
& T, s3 `7 \# Z' G' z) ~3 k + w3 p7 N7 g q* f1 J' n
Week 0 1 2 3 4 5 6
/ O7 Q/ R" b- H% X0 P2 Q( j' F/ ^ Fruit Month
5 [9 K. T- ?, m+ U Apple 1 46 50 50 45 32 42 23
9 j# R: v: q$ V' Y8 I s" d' R Banana 1 27 29 24 42 36 24 353 s2 D8 W' i7 t1 O9 r2 A& U: J. L" B0 F
Grape 1 42 75 53 63 36 57 46
2 u( G1 Y3 E. d- v5 t" T Peach 1 67 78 73 88 59 49 72
/ Y2 @0 C6 w/ ^9 j Pear 1 39 69 51 54 48 36 40* i; n0 v, ?4 G+ @1 ]# o
15 d# ]4 B8 W: f" X% _: A/ _% v' k1 O
2
! ~ c9 Q) ]7 R W 3
" U! f7 h; a4 H 4
: L/ d! C1 s) M 5& _) ?( Y3 W; Q
6
8 q, [2 z7 C2 B( l 7
# }+ A& N& b$ [1 U* ~9 K: _8 t 8
+ { o4 S/ i( ?. @6 u, d 9
$ H* v/ ?$ W# h; y: e; r+ y 10
1 i: [/ i# x6 B. q5 J 11* a/ q- l i& i, g4 q
12' r$ |- q7 L; W4 y7 Y' x
13
/ p; l5 C5 d5 w' ~ P7 v$ T 14
+ B& u2 @) Q! U7 ] 155 s5 ~! ~( M3 t4 C
按天计算向前10个工作日窗口的苹果销量均值序列,非工作日的值用上一个工作日的结果填充。
$ b9 l* f" ^) R4 i9 F # 工作日苹果销量按日期排序* W1 g: q( L* `- R& l- w
select_bday=df[~df.Date.dt.dayofweek.isin([5,6])].query('Fruit=="Apple"').set_index('Date').sort_index()9 G2 X7 r9 l8 Q3 D9 t q+ p- _
select_bday=select_bday.groupby(select_bday.index)['Sale'].sum() # 每天的销量汇总
$ v: L/ R$ x/ H( c select_bday.head()
& c& n5 i& G! G* N / l& ?( Z& d3 g3 Y" O3 ]6 [
Date4 t- f) O- G+ o0 U6 D- z
2019-01-01 189: `. e% e- r6 q% P5 n: p
2019-01-02 482' z" p S0 f/ `% X R! z
2019-01-03 890
J# L {6 M9 s/ [+ T) _) a- r1 D 2019-01-04 550
( {4 _8 L/ K5 {9 L. P) S! g9 ? 2019-01-07 494
2 r, x; h/ e5 {6 m. S& E: Y 2 O) D4 B' }. E: l
# 此时已经是工作日,正常滑窗。结果重设索引,对周末进行向后填充。2 s* d* @' T! s( v1 p* x
select_bday.rolling('10D').mean().reindex(df.Date.unique()).sort_index().ffill().head()
! L3 P& e/ U d. ?5 P% S% x- C* i
: f& y0 r! X6 ^# }. ]" k Date# z x0 F" e8 ^. p8 x! P/ r
2019-01-01 189.000000% \! h. {4 ^- n! N
2019-01-02 335.5000000 y; J, b" Q8 ~, ?% a% N
2019-01-03 520.3333339 P" u% o& X% P8 P# p* }! y! Y
2019-01-04 527.750000# s2 c0 X+ _: `* [, P9 e$ |
2019-01-05 527.750000
! ]$ j& M2 @. N8 [ N$ d & l: a: K/ S* M: S: O2 o; [
————————————————
8 L/ V& I6 s B* B) A! ]3 p7 P" w/ ^ 版权声明:本文为CSDN博主「神洛华」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
9 W! K; e& r5 [5 s 原文链接:https://blog.csdn.net/qq_56591814/article/details/1266339136 B* x+ N( B8 I D6 g; M6 \3 E
; N5 ]% J2 e) r" B8 n1 r# t( Q- \
& A; R) N! M4 K6 Q
zan