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