- 在线时间
- 1630 小时
- 最后登录
- 2024-1-29
- 注册时间
- 2017-5-16
- 听众数
- 82
- 收听数
- 1
- 能力
- 120 分
- 体力
- 565164 点
- 威望
- 12 点
- 阅读权限
- 255
- 积分
- 174772
- 相册
- 1
- 日志
- 0
- 记录
- 0
- 帖子
- 5313
- 主题
- 5273
- 精华
- 3
- 分享
- 0
- 好友
- 163
TA的每日心情 | 开心 2021-8-11 17:59 |
|---|
签到天数: 17 天 [LV.4]偶尔看看III 网络挑战赛参赛者 网络挑战赛参赛者 - 自我介绍
- 本人女,毕业于内蒙古科技大学,担任文职专业,毕业专业英语。
 群组: 2018美赛大象算法课程 群组: 2018美赛护航培训课程 群组: 2019年 数学中国站长建 群组: 2019年数据分析师课程 群组: 2018年大象老师国赛优 |
7 Y' w8 g' [8 k) g
P, e" n, Q2 ?+ D# J4 h6 p: W2 F1 i+ r/ R9 l7 E& k- Z# q% `0 {
文章目录
: L0 u2 V2 N# L+ q& `, U) P0 a; @第八章 文本数据
- I' s n0 o' R. e b! O1 }8.1 str对象; @) s+ y. y# q( e0 \/ I
8.1.1 str对象的设计意图
8 P8 l9 I- Q3 a1 G0 [8.1.3 string类型
% n' z, t9 P1 w8.2 正则表达式基础7 ]: L4 [+ i3 P% w6 Z! X% w/ Y
8.2.1 . 一般字符的匹配# \+ p$ j0 Z. G5 d9 [* u; T+ g: c: N# ?
8.2.2 元字符基础+ y7 ^2 F+ i0 D8 Q( D* R1 q
8.2.3 简写字符集
7 S# ]2 Y+ d, a9 {# q, W8.3 文本处理的五类操作
+ [6 R/ [5 `, P0 @8.3.1 `str.split `拆分: W% N' s% u+ l. A
8.3.2 `str.join` 或 `str.cat `合并/ b7 k9 I4 @9 L* o5 I
8.3.3 匹配! W# i) V6 s% p, n
8.3.5 提取+ ?7 A9 h' N2 R1 b
8.4、常用字符串函数4 ?. H6 K' Q. L8 z
8.4.1 字母型函数6 q. M' U- i8 I* o
8.4.2 数值型函数
; q& n7 e" O: O/ Q7 R( y& L8.4.3 统计型函数8 E- g9 D; @- F S* h3 @& l# B
8.4.4 格式型函数0 ~/ S x: I( W
8.5 练习
i6 J% x+ h3 f5 l lEx1:房屋信息数据集
& A3 b' }2 c4 n, u0 Z3 G8 Q' y/ N/ F: ?Ex2:《权力的游戏》剧本数据集
! U1 @5 P ^ R. P* [, Y: k第九章 分类数据
$ l0 k$ ]: _/ ]* o9.1 cat对象2 v9 t8 ?7 m2 \" [) _ n
9.1.1 cat对象的属性
! n. N! c3 V. n: K, H9.1.2 类别的增加、删除和修改& b, K+ @' X' p0 u
9.2 有序分类
7 {1 s9 @ H0 n7 y$ k9 p9.2.1 序的建立
6 C' a2 Z7 \' A* y2 Q6 H* `; P8 N9.2.2 排序和比较
+ k4 {, Z* j3 t8 y9 |( m9.3 区间类别
1 X! V, t" L/ {; B u5 z) w9.3.1 利用cut和qcut进行区间构造( {, j: v; d: M
9.3.2 一般区间的构造
& b, X( Z% M: `: [8 o2 L c3 n$ V( L9.3.3 区间的属性与方法9 i6 d0 N" J$ J
9.4 练习
5 Z9 E3 Y! b7 z s; o/ TEx1: 统计未出现的类别6 r; x6 U0 B$ y4 n
Ex2: 钻石数据集
4 M3 ?! Z) f$ O第十章 时序数据
3 r& z$ G$ S5 Z10.1 时序中的基本对象8 q! o# Z& y( r* A( r2 r
10.2 时间戳& \& c5 a0 k' q* S8 W
10.2.1 Timestamp的构造与属性2 @/ B. z1 M- T0 E4 T d& |
10.2.2 Datetime序列的生成8 Q7 ^3 t6 [- \
10.2.3 dt对象9 L, u8 {% z/ P" M" L
10.2.4 时间戳的切片与索引
+ V) E8 B3 W; l( w1 B1 S10.3 时间差7 Y' O& c3 _$ d/ d+ f7 B7 J
10.3.1 Timedelta的生成8 D8 v( {1 T# I3 `1 Y- i8 t" d
10.2.2 Timedelta的运算
$ y$ B3 w7 Y# `2 L6 M( _/ @8 v4 j10.4 日期偏置, }( H2 g/ }: R! h9 Z
10.4.1 Offset对象: f% R. W6 f! ^, Y5 t& S: m) c
10.4.2 偏置字符串
! w* m$ ~! |" {- m- I9 q U3 O+ F& L10.5、时序中的滑窗与分组$ E' W: G. @7 V% i
10.5.1 滑动窗口+ E0 M7 t4 p& l) x0 m8 b# F* d# {% C) x
10.5.2 重采样 r4 Y3 e' I8 ]- l3 V
10.6 练习
# }6 ^% y, _# c( L3 WEx1:太阳辐射数据集5 F5 }! ~8 t& H0 C: T% N
Ex2:水果销量数据集; g8 P/ z8 q7 W2 @! \. ~
课程资料《pandas数据处理与分析》、github地址、讲解视频、习题参考答案 、pandas官网
; u* h# Y0 \# B* S8 g9 S传送门:" {+ D! K+ p+ x
9 S; s8 H8 Z6 F; d, Ydatawhale8月组队学习《pandas数据处理与分析》(上)(基础、索引、分组); l. s4 a! F' ^5 M6 q
datawhale8月组队学习《pandas数据处理与分析》(中)(变形、连接、缺失数据)
0 [) {' d, A# M第八章 文本数据$ d' u: q8 t' ]1 Q0 \8 b6 s
8.1 str对象
* N/ B: _6 C% c& F& Y" `8.1.1 str对象的设计意图
, U! C9 J, U9 A# ?. ?( p) B7 N$ I) C str 对象是定义在 Index 或 Series上的属性,专门用于处理每个元素的文本内容,其内部定义了大量方法,因此对一个序列进行文本处理,首先需要获取其 str 对象。在Python标准库中也有 str 模块,为了使用上的便利,在 pandas 的50个 str 对象方法中,有31个是和标准库中的 str 模块方法同名且功能一致,例如字母转为大写的操作:
8 ^; `) `7 \2 Y" \2 K6 Y: x3 ^- I) X/ C1 w0 d' R
var = 'abcd'8 [; J( V8 s% s
str.upper(var) # Python内置str模块8 O% d, Q( X& s6 b7 z5 E, L6 j
Out[4]: 'ABCD'
" w0 j' B% i/ A f. G" z: _- [2 c# J. ^3 \% ^, a
s = pd.Series(['abcd', 'efg', 'hi'])
( S- P7 ~- z6 s6 w' {1 S& {! Z+ b( G$ k" e$ {' F4 l; v# I" Y6 H" c
s.str
3 R$ O8 a7 |6 G' |' TOut[6]: <pandas.core.strings.accessor.StringMethods at 0x2b796892d60>5 S P% W6 p/ [# i3 p
9 I- t8 r1 |# M( s. ^) v- | B
s.str.upper() # pandas中str对象上的upper方法3 \* O5 N' \. x: O0 \5 M% M
Out[7]:
- o) n4 s/ w' s: S0 ABCD
5 Z* F0 n [% d+ Q$ F1 EFG
; o" o3 z6 u( k. H! a2 HI0 h# I; e0 K2 b
dtype: object |2 A6 o3 _0 ?6 z0 v) ]6 e% r: I
1
+ H& c! g+ M1 q/ W. ?2: u* w+ a- g2 X+ F7 d9 [
3, A+ P! \: j5 V/ ]. z% t* i
4
' y/ ^0 ?- S/ v/ h" `* W* k) K& s5+ A& s- D1 K0 n; q H6 ]4 V
67 `: ]7 v, P) v
7
. c7 t7 D0 O$ _& N8
- c) w) T5 j5 K. J9
( o8 a6 H, H ]7 x10
1 s3 o9 x) B! L& r' K r, G11+ \4 O& \- e; C+ V _
12$ w8 ?/ m( @ \' N! l) a1 W$ _# X3 p
13/ p4 B' }. S9 S: B3 n( |8 b
14
5 V* U/ F g5 |; }" W15
1 t" R& d4 g( e8.1.2 []索引器
6 K6 X! w4 j5 V0 m h 对于 str 对象而言,可理解为其对字符串进行了序列化的操作,例如在一般的字符串中,通过 [] 可以取出某个位置的元素,同时也能通过切片得到子串。
! V; ~ c# v+ J) G+ N pandas中过对 str 对象使用 [] 索引器,可以完成完全一致的功能,并且如果超出范围则返回缺失值:( ~8 [$ H* k7 u% ~
; j6 ~# J/ q$ n; o- is.str[0]) h, q( @. G* x: L4 n- C. }
Out[10]:
) H& J/ q# r% m5 R: K0 a
, G9 U) x+ x4 C3 g$ W* J, m1 e2 y9 n4 p, t# W
2 h; s$ t- P+ N- t, v0 |1 {
dtype: object
/ I0 f0 ]/ {: M7 }* c, ?( ?3 M: c, a( [- ^$ m2 M9 e( c
s.str[-1: 0: -2]
- A" r' w+ W) e. _) o8 LOut[11]: + N% I" V6 r/ Y% E
0 db% y* x4 _8 P% X2 q
1 g7 Y" I5 C& `" }
2 i7 O3 c. ^ s, e( J# k2 K
dtype: object) a# @( H6 R0 \( ?2 R" V J
( Q8 h- @' ^, I/ M
s.str[2]" [' n# V' f7 b# {% B
Out[12]:
' u2 |/ L9 W, V! Y: D, Z4 c0 c
9 A7 w c& q" _5 K( b5 R1 g
; o8 B' K2 `% R2 NaN. F$ A! W, n1 @2 Y9 a# Z/ G/ Q, k T1 l
dtype: object5 T1 l \4 O6 R
' |% f$ s# E9 X( H$ U
1; Z: |8 n4 }2 U/ q8 Q: { v# W
2
& w* H1 d M$ C( U; Z- D$ U3
Y/ {+ }: m) N& R5 o- @/ v& ^, J4$ f5 u6 n1 E8 S/ k5 r6 | Y
5
5 H: l1 |7 u, I) B @6/ r x" r8 j% d$ a! g# z# w; D) R2 z
7
5 \; N% w5 Q# O, x" |; X- r86 [. u( P. j: k# x( R+ V& S; ^
9
R& r3 C; x1 e: B7 E* N7 v# l& T \10
/ A, [0 H! k/ ~9 V% @110 u9 a2 u( ^- H: z& E
12 n8 R8 z& D7 S6 s
13. D- t4 q5 a7 m: G4 M0 I. Z* z, b
14
* a- ~* g2 I: }# R6 y$ N! V7 V- d15
0 e0 E5 d/ h3 v/ ]0 {: D! P16 j3 O/ m0 K3 J5 N2 ?
179 n$ k6 ^5 Z4 W
18( s" p: h* e+ Q t
195 `( ?5 [; N% F
20
, i8 v4 r9 F' j7 k/ d& Iimport numpy as np$ u- i# q* U1 U( `' }+ O7 v
import pandas as pd7 b4 A4 T N9 D1 i" l7 |; b
$ }9 D5 X& E' ~( I' X& |
s = pd.Series(['abcd', 'efg', 'hi'])/ J0 w4 f: Q2 q! K( `
s.str[0]
" }& J+ R% [; q( e; r0 r2 X$ t( |; b1( @; S1 g8 |* U E7 y1 K, r& k
2# m; L- y( m5 t+ j9 P! F
3
" n5 f2 U) X6 n k4# y+ q; u) l3 n: u3 o& d& C
5
5 b9 J) I# w) h5 Z( `* W0 a9 e" K% Z8 }7 G# A$ n. a, x0 F
1 e" |$ J4 J9 `( p& s1 m- n
2 h6 Q, O4 [ ]4 E* y5 g2 B) g, A
dtype: object
( n$ K; G; W2 T) {8 s1
4 M, T g4 u0 D. u5 o2! ^" n2 ^$ C3 \5 Z2 S* o
39 W& J. t) F! L% z# {/ r7 f
4
" A% j# W, P# i4 O: q5 c8.1.3 string类型
' T8 j$ ^7 ^8 F& d# O' s! W 在上一章提到,从 pandas 的 1.0.0 版本开始,引入了 string 类型,其引入的动机在于:原来所有的字符串类型都会以 object 类型的 Series 进行存储,但 object 类型只应当存储混合类型,例如同时存储浮点、字符串、字典、列表、自定义类型等,因此字符串有必要同数值型或 category 一样,具有自己的数据存储类型,从而引入了 string 类型。
: p, |; C( m5 K8 ]3 K, I0 ? 总体上说,绝大多数对于 object 和 string 类型的序列使用 str 对象方法产生的结果是一致,但是在下面提到的两点上有较大差异:
, y3 k1 R; s3 I- O' b
* H$ ?4 O8 @+ T( C1 U- @二者对于某些对象的 str 序列化方法不同。
: _* i1 i# c v0 g w可迭代(Iterable)对象包括但不限于字符串、字典、列表。对于一个可迭代对象, string 类型和 object 类型对它们的序列化方式不同,序列化后str对象返回结果也可能不同。例如:9 C9 l: f; u, |4 D% h
s = pd.Series([{1: 'temp_1', 2: 'temp_2'}, ['a', 'b'], 0.5, 'my_string'])0 H9 { `& z/ I6 R% A- {; n
s
O9 v" ~. K8 A% H1
]% M! c. {7 ^2
5 o G% D0 o; v2 \8 N# i8 k0 {1: 'temp_1', 2: 'temp_2'} e) g; j0 J, s, V
1 [a, b]
# j9 V( B# W* t0 s2 0.5& c; A6 B u4 }1 B1 Y8 D1 s
3 my_string
+ t0 l# I/ G: ddtype: object1 [: s: t# y3 s" Z# B$ h. K
1
. M `1 S7 A* G% C: V% o2: K4 z0 u; \% J9 n9 [8 P5 G
3
& h; i$ J4 B$ z3 {3 W4
6 U- @. H. w3 b2 W$ ^2 R5
* j% |/ S2 t k' o! z# W+ ]s.str[1] # 对每个元素取[1]的操作5 K1 E/ z9 X! P! u* n5 o
14 w( R2 b- O) t w5 C) B; e+ r, A
0 temp_1
" {; t; D6 \0 @! h3 Q' Q1 b
4 z( E+ l0 s, X- d7 b: m# ]2 NaN( A) J- p8 R1 q+ z# f, J
3 y
6 S) I0 f, T" b/ M& W7 V1 s2 qdtype: object
) j6 a. y) m- X14 ~5 ^/ `; y, q. e* Z# ]* f @
2. o" X) r' S- t1 x0 [5 S! [
36 ^5 E4 f3 H6 n ]! `
4
3 `# T4 t+ m( E: }+ V5
) o( e2 r7 {0 R) Os.astype('string').str[1]
( s* W$ Z0 X, i1* C. g" [ I/ l/ C( p% u
0 14 k+ B3 |; ?, |
1 '$ y2 c% T" c% S4 y$ M' Z
2 .
6 X! G. m- r1 y3 y# q. \; `8 z4 b
dtype: string! g$ G* Z" Y: [
1: _3 A0 T' H. D6 l. m; W$ a
2& C" [$ G2 g& Z3 K4 b! _
3; r9 G+ H f3 c* k; d; ?
4
s( |3 x% [2 V. B/ U" N( H57 a K! C0 r- V* W8 T3 o
除了最后一个字符串元素,前三个元素返回的值都不同,其原因在于:
* P! f) n) g1 b+ ]! g
/ {6 W+ w @& `9 X$ k! U当序列类型为 object 时,是对于每一个元素进行 [] 索引,因此对于字典而言,返回temp_1字符串,对于列表则返回第二个值,而第三个为不可迭代对象,返回缺失值,第四个是对字符串进行 [] 索引。
* n6 Q$ V* c, Q; Kstring 类型的 str 对象先把整个元素转为字面意义的字符串,例如对于列表而言,第一个元素即 “{”,而对于最后一个字符串元素而言,恰好转化前后的表示方法一致,因此结果和 object 类型一致。
4 Y) {3 T, h7 Jstring 类型是 Nullable 类型,但 object 不是5 @5 ?+ Q3 o1 F1 U
这意味着 string 类型的序列,如果调用的 str 方法返回值为整数 Series 和布尔 Series 时,其分别对应的 dtype 是 Int 和 boolean 的 Nullable 类型,而 object 类型则会分别返回 int/float 和 bool/object ,不过这取决于缺失值的存在与否。
3 n9 c5 I6 h" K/ A 同时,字符串的比较操作,也具有相似的特性, string 返回 Nullable 类型,但 object 不会。
$ e" G0 z9 Q, T- Ts = pd.Series(['a'])
' n5 x8 y! c9 Y, g+ \( R) R) q
s.str.len()
. h! I& d1 o+ ?7 M8 M) g, lOut[17]: & n, w2 p4 V0 [, s: b
0 18 m" R" ^; B; {6 E+ f
dtype: int64 u& n- T! l( s# U1 P, _
) Y1 P/ E$ z i
s.astype('string').str.len(); @" p$ P0 e! g* X5 Y* u* R: j
Out[18]: ' _3 M! M% Y1 @( q& L
0 1 i! u, f9 v" j$ C% _5 p, f# u. f
dtype: Int64( b% p& M3 d+ o: g7 Z6 C/ V
+ u4 H/ z4 y. d0 rs == 'a') e9 Q" z2 K4 a/ F- ]4 @
Out[19]:
/ L- `. X5 v, t( J/ B8 N# G+ R, e$ b0 True
0 p! ]- m7 Y) K3 k$ ?: L" D, b. Y2 ~7 S3 cdtype: bool& W$ a) i$ b. c
" b8 ]# B6 x4 {* I
s.astype('string') == 'a'3 G$ q! ^7 ~0 u6 V- B r! c1 s8 n
Out[20]: 5 @4 j5 h2 e( Q& ~
0 True$ m: h \, q! b5 R1 l
dtype: boolean
9 P2 q. ` Y" N" a! ?: @& m. J7 m B0 f' K4 U9 N4 H
s = pd.Series(['a', np.nan]) # 带有缺失值
\# d% f! N7 }1 v
. Q) ?- p H: p+ us.str.len()
: O) u) |) g3 WOut[22]: 1 u( g; E5 h8 V) G$ ~
0 1.0, A2 ^; {1 x* @5 l; n2 O' w8 T
1 NaN% A) `; I% ^2 D( m6 V( P s
dtype: float64& E$ h5 P% [4 f# w+ z4 l
4 x5 | |. s( I/ S! gs.astype('string').str.len()8 K% {) x4 K7 F7 ^
Out[23]: 1 {; j0 O: x# I( ]
0 1. O! P: k8 e" r' i8 j
1 <NA>
" l8 k* m) R8 G8 ~) i) ~* vdtype: Int64
, |" D* V, m" z( e$ o9 s9 m4 K/ ^1 e$ L; L+ L
s == 'a'
- A3 Y7 D* f' b3 ]* h vOut[24]: 9 z9 K w5 V( o/ r
0 True
7 \+ S/ v! @: d1 False+ w* j( |9 v9 `( X% U' e; A0 r
dtype: bool
! Y. d$ E. {& \, d; K* V. b" [! j; @* \3 ?! i. u1 O4 a
s.astype('string') == 'a'
1 I2 A6 W; x; J6 _6 jOut[25]:
9 D! P) J" ^! t& k2 {0 True% U8 _' Z, Z) O, C
1 <NA>+ R2 u" G3 J/ E4 J0 B9 e( K, O' J9 U
dtype: boolean
# d9 K6 U; Q4 q8 u" c9 M% J# P
- W/ |9 X: ~- v8 h: {% T1; P0 x' s6 f5 D" n
2( c }5 E6 b% |# s; l+ @
3
6 a L/ {+ m( x47 a# }9 m- X$ B. ~- W6 @
50 I$ @, e! }. ]/ |+ Z, j. I
65 E/ {$ f" A2 w w- c+ F
7
8 A$ ?9 V( S" c3 p, D8* |' X9 q, b6 t3 u% n+ D& z
9! ] y* c2 N/ o, E5 w# |( I
10
: Q4 q8 p' H3 w' X( i113 S6 v1 q; d7 e2 F
12
/ G* Z3 I- _ I, S13
# c1 F% I, [! G) ^; M14
# J) }' j6 ^ t156 f5 N" T; N3 v
16- s1 k* y3 |4 V) p
17 Q$ T5 D* M' y- V
18
?" H$ r+ c6 t19
* V- m2 B; F1 e0 z! J! K. S/ Y20+ y1 p: f5 h. C; @3 x9 {
21- D! x+ w4 J5 O3 e% O+ s
22, }9 N- q( A) |# N
23
9 o2 |5 u/ o: P24& N! @1 q' Q& t. g& l
25. l+ `( Q4 V4 _3 ~- I
26# T' `* O0 ?; O6 k4 y0 ~
27
# |) Z, X6 N. m+ h28
$ \/ u4 C8 c/ K: d6 }* o29: ]# s+ A/ E, V( [6 X% n
30
% r+ c- t* G: P31* Y% e) K' j5 m5 Y! f+ w8 W6 |
32
! v" ]$ I8 R2 [2 S3 v; f33
( G+ O7 W. @+ o( r2 m34
+ O ^4 U) w) O' }( R+ U. J. l35
7 z) W; F( j* R/ K365 g+ U6 F1 J& f8 t5 ^
37/ Z2 }6 c' q# p6 [, b3 B
38/ K: O+ `0 g7 R9 G4 i
39
7 i. V/ }- r9 C8 e# D" r& H2 p40! p: ]8 Y! ^0 h- G/ R
41
8 `$ n, u9 F: Y42+ K) \% J7 @/ {% L& q7 Z5 T( }, I
43$ @1 r- g' E6 N4 Y: w; y, F
44- \# B& q8 G# x- `0 |
45
" B. Q& k0 B0 n6 N- ]1 Y462 `( V8 b* B# i/ E
47* J- [# r/ G2 w. c
对于全体元素为数值类型的序列,即使其类型为 object 或者 category 也不允许直接使用 str 属性。如果需要把数字当成 string 类型处理,可以使用 astype 强制转换为 string 类型的 Series :
' f9 [5 x- O _7 L% ?, r; T% w/ s* M4 T2 c% d% R% L- C- ^( r
s = pd.Series([12, 345, 6789])6 }4 Z, U1 P! `: O
) x3 V2 {2 h! [4 o2 `s.astype('string').str[1]
9 W! K7 a4 R* \8 d7 pOut[27]: % \! _% ~6 |! C, [' N
0 23 ~: o3 I' j" Q W, r0 F9 R
1 4
' j) a% M- ] m9 i6 f0 X( O2 7
2 e9 a8 I# ]" M4 rdtype: string
! B d; [- ]7 m' d# J9 n5 i1
E% \: d3 ]/ c2 _; m( ~2
* V( J H1 U& }" h( s4 G" g3
7 y; w, w0 o Q( k4 ]! x4
& _! Y, b' W- j3 T/ t6 B5; v$ c. R5 \8 O/ P
67 c, k$ [; }% m" @$ ]
7
$ \3 A1 O. ]: R+ l& I9 h o% i1 L0 @, e5 F" z8! E+ ]2 g( P4 v, E
8.2 正则表达式基础$ \/ }4 ]8 b& y! H2 I* H
这一节的两个表格来自于 learn-regex-zh 这个关于正则表达式项目,其使用 MIT 开源许可协议。这里只是介绍正则表达式的基本用法,需要系统学习的读者可参考《Python3 正则表达式》,或者《 正则表达式必知必会 》这本书
4 B- d) A0 ]1 e- ~
0 t% O1 h# d; G5 v, k# M1 z% J8.2.1 . 一般字符的匹配+ `2 G- ]7 d v& Y/ H
正则表达式是一种按照某种正则模式,从左到右匹配字符串中内容的一种工具。对于一般的字符而言,它可以找到其所在的位置,这里为了演示便利,使用了 python 中 re 模块的 findall 函数来匹配所有出现过但不重叠的模式,第一个参数是正则表达式,第二个参数是待匹配的字符串。例如,在下面的字符串中找出 apple :
9 m. M* w5 N9 N# i# ]7 d
' Z- X" Q( K: z1 Simport re
2 r$ x: D& X0 M
2 h$ i$ q6 T% Rre.findall(r'Apple', 'Apple! This Is an Apple!') # 字符串从左到右依次匹配. X: _) x# A4 |! l2 o+ J( C# }( y0 {
Out[29]: ['Apple', 'Apple']9 y( {9 r) H3 H Y5 k9 W
1
7 f3 t( ?5 O- L2: O' ?% k; @6 |
3
" D, c, u' b6 }) m$ Y- b0 [$ w' \4& f( ~5 m+ e% H U2 S. L9 c
8.2.2 元字符基础
7 o" @ h6 g9 y7 u2 Z* ^元字符 描述
9 Q; ]" F3 U: }6 |. 匹配除换行符以外的任意字符7 d& X$ R: l |: Q
[ ] 字符类,匹配方括号中包含的任意字符
i! Y; h W4 S- u( ][^ ] 否定字符类,匹配方括号中不包含的任意字符( ?& C8 ?- v, ~" G
* 匹配前面的子表达式零次或多次" r; ~. F; D! @* m% H9 v
+ 匹配前面的子表达式一次或多次。比如r’d+'就是匹配数字串,r’d’就是匹配单个数字5 P5 j# C! U1 h) P
? 匹配前面的子表达式零次或一次,非贪婪方式3 L) x, K6 ^8 ^& l+ L
{n,m} 花括号,匹配 n 到 m 次由前面的正则表达式定义的片段,贪婪方式
& T2 L5 T9 M& ~ ](xyz) 字符组,按照确切的顺序匹配字符xyz" e7 M# f- H* z+ Q+ t% P/ }& {
| 分支结构,匹配符号之前的字符或后面的字符
+ d+ g7 _% D5 V; z! g\ 转义符,它可以还原元字符原来的含义
- j: t8 P5 y) L* K: C- }^ 匹配行的开始
& \% L. \+ @; n3 p# J$ 匹配行的结束9 @+ p( {0 R% [/ z/ f8 m! w
import re
9 _; A* H) Z" Fre.findall(r'.', 'abc')
, `% n0 U) O( ?( t0 A! R% b3 Y& b: c; rOut[30]: ['a', 'b', 'c']
- `0 [' b& K0 c, f& V+ |9 i; @7 ], n6 {1 k. V
re.findall(r'[ac]', 'abc') # []中有的子串都匹配5 S" q. L9 M1 Y8 S8 E6 D
Out[31]: ['a', 'c']
+ h! W8 l, M( Z* G5 ~
% c1 g. a1 O. B6 ^6 [re.findall(r'[^ac]', 'abc') & h+ Z# I; R2 F1 J0 G7 f
Out[32]: ['b']0 F& H/ S G! B
+ m) s& @6 s" [1 k6 f) ?: |, cre.findall(r'[ab]{2}', 'aaaabbbb') # {n}指匹配n次% @$ g5 H! m( r! E+ j
Out[33]: ['aa', 'aa', 'bb', 'bb'], Z8 m5 m, @1 S: G
; l5 q. K( B( w9 ure.findall(r'aaa|bbc|ca', 'aacabbcbbc') # 匹配前面的或者后面的字符串- m) y0 n7 F+ W: E9 }
Out[34]: ['ca', 'bbc', 'bbc']$ _8 z: P: s$ ]+ `: ~% {' Z( g* _/ s5 x
. G& a6 Q+ ]' |, i! d# 上面的元字符都有特殊含义,要匹配其本来的意思就得用\进行转义。
. c+ Y+ z* F4 V1 C6 _+ w& n [$ a8 ~"""
4 G! H3 A% w0 @. Y$ Z; l1. ?匹配的是前一个字符,即被转义的\,所以|前面的内容就是匹配a\或者a,但是结果里面没有a\,相当于只能匹配a。/ |# W. [8 V# z
2. |右边是a\*,转义之后匹配a*,对于竖线而言左边优先级高于右边
7 [" Y9 a; l$ ?, @( ~; R4 E0 K3. 然后看目标字符串aa?a*a,第一个a匹配左边,第二个a匹配左边,第三个a虽然后面有*,: H# }( t2 n9 C9 k: r* S
但是左边优先级高, 还是匹配左边,剩下一个a还是左边,所以结果是四个a1 {7 R; [' A) h$ g/ `6 C. Z
"""
" ?) `$ E& ]3 H" J7 q
/ C/ T% z# H4 ~! a. j* nre.findall(r'a\\?|a\*', 'aa?a*a') # 第二次先匹配到a,就不会匹配a?。a*同理。
Q/ J& e a; D4 sOut[35]: ['a', 'a', 'a', 'a']9 `; m# G5 ^' x3 m' I
) f! d- C/ _% W
# 这里匹配不到是因为目标串'aa\a*a'中,\a是python的转义字符(\a\b\t\n等),所以匹配不到。
! f! R" n L# i3 V5 N X& k& z# 如果是'aa\s*a'之内非python的转义字符,或者'aa\\s*a',或者r'aa\\s*a'就可以匹配到\字符。! \* \# f/ z9 P
re.findall(r'\\', 'aa\a*a')
) W6 ]/ _8 g2 q7 C) y3 [[] Z4 M/ k1 m" f% G4 I) ~( A
6 t, R, X( o% O$ j4 H" Sre.findall(r'a?.', 'abaacadaae')6 O5 `1 N5 X3 L, `1 E- n: q
Out[36]: ['ab', 'aa', 'c', 'ad', 'aa', 'e']/ [. a) ~( z: w" L( E9 X2 n
: n# p& ^8 @2 b# v. D, s& Pre.findall(r'(\w+)=(\d+)', 'set width=20 and height=10') # 多个匹配模式,返回元组列表
, m$ ^5 R) |, N9 d9 j" R2 T[('width', '20'), ('height', '10')]/ N, }8 v Z, _: V5 y. o
+ D7 w5 c; d$ l7 W. |" F; X
1& U5 l$ B: m1 C9 ~
2
$ i2 f j: m/ e1 [4 H Z3
0 P, n' M& [0 i& N- Z4
3 [7 Q2 c; K: J57 i1 y. c2 Z+ q4 E! ?
6
, L0 g* N# P j: |4 ^! |" O, a7
2 d1 f; t% n' t+ U, l' X# T, w; q, Z8) f) X: T; r+ X1 p9 _
9% R) v4 |: M/ V& N
10
5 \0 a/ t, a( f& P11* @" b9 l: W& C, h
12. a& K0 ~% _, P& P5 s0 p% {7 r' {; E
13# G% u5 p0 E1 O# L( x) p1 K6 d* B: F
148 u, U) B% e0 u/ ^
15
: T9 _+ b7 i6 M$ v16& ?5 R) |& h9 H
17
5 W, u7 G* v" ], Q8 ~, }8 i18% X6 o' K5 ^. O+ F# A8 ~
19
( r1 s" R1 C& }6 m; h20
& C! H0 c. ]. O/ a! H3 w21
! d4 s5 s1 R3 L) j, M& A7 s22+ ?/ ~5 _0 k6 u
235 @( U, W @0 r( d/ s
24
: ]& e& t. p E+ {) V) U25: k/ X4 E0 L x" V! a
26
% y1 `6 u/ [3 o& O7 m; i$ T27
) O* b+ }/ {# @( d4 `7 ]' F. o28
1 L, d% L' n* a8 K/ R29
r0 O, q2 G: ?0 k; |- ~30 u/ k) x$ t9 m, n7 U% n3 _
31
4 h0 u% J/ K! |7 R6 C32' i6 C$ T. H6 Z- f
33% T3 a Z+ x) p P+ W- V7 b4 ~9 Y
34
9 z* q u. j8 K: O* n: K: {/ R, p35
4 o- h, X% m- D367 |2 _9 G: \. N7 a+ F5 |
37; s9 w: I: N3 [" U7 K" g
8.2.3 简写字符集
1 e3 I& d( `) J9 G( b/ e则表达式中还有一类简写字符集,其等价于一组字符的集合:
" F+ n$ f0 E9 c7 h; J9 \0 a( A% q, H$ A3 p
简写 描述
3 \; v! ^% }4 E/ ^\w 匹配所有字母、数字、下划线: [a-zA-Z0-9_]
$ R- {+ [, v; U" \' I) v\W 匹配非字母和数字的字符: [^\w]* p9 ^, v) Z* U9 H8 k3 z
\d 匹配数字: [0-9]
" a, Z: P4 ]9 U) \/ D( p' o( B\D 匹配非数字: [^\d]
* N r P$ C- c0 Y\s 匹配空格符: [\t\n\f\r\p{Z}]3 {. `3 n3 z) C" r) |9 L
\S 匹配非空格符: [^\s]2 @! K$ M$ q* N" |$ n
\B 匹配非单词边界。‘er\B’ 能匹配 “verb” 中的 ‘er’,但不能匹配 “never” 中的 ‘er’。6 F2 L; Q6 ]0 o+ `
re.findall(r'.s', 'Apple! This Is an Apple!') ]4 l$ {9 v' o/ C
Out[37]: ['is', 'Is']
8 @4 @" H, N" R2 D; f- r d6 N$ L4 }5 s% c
re.findall(r'\w{2}', '09 8? 7w c_ 9q p@') # 匹配任意数字字母下划线的组合,但必须是两次6 Z1 W7 u6 G4 `% \2 D! a* l; k
Out[38]: ['09', '7w', 'c_', '9q']
* P' X: k& M1 O
9 [5 t1 [+ u3 E& D+ g6 y. S9 w r2 fre.findall(r'\w\W\B', '09 8? 7w c_ 9q p@') # 匹配的是两个字符串,前一个是任意数字字母下划线(\W),后一个不是(\W). d2 l( h( F' F. o; s3 |
Out[39]: ['8?', 'p@']
: \& T5 B2 [+ |, C9 I
. ]2 C) d/ A* yre.findall(r'.\s.', 'Constant dropping wears the stone.')0 @4 x. n2 C+ j, E
Out[40]: ['t d', 'g w', 's t', 'e s']
7 H7 y" I+ d `5 V/ a1 z# x$ a3 H, e
% `: g* f) }5 x3 m- v* y) B Q! Wre.findall(r'上海市(.{2,3}区)(.{2,3}路)(\d+号)',
9 @; N) g. M1 x1 N '上海市黄浦区方浜中路249号 上海市宝山区密山路5号')9 z7 p: ?8 G$ e$ Q: [6 }
" z# L4 Z$ B; e, m5 M
Out[41]: [('黄浦区', '方浜中路', '249号'), ('宝山区', '密山路', '5号')]
$ P" l) ?" V$ o& ^
9 g! ~5 @; g- j+ ` C1/ y6 y" J6 X) Q3 L! \
2
( }& x# q' k2 z. K3
: F" b* b5 L. S3 q+ L% d+ Z4
* W G/ J1 r e" z5
) `" @4 }& H2 G- i7 n( ^3 y* g& o6# F0 B* f9 M# y' m# n
7, N$ M# E3 ~" l# H( n1 a7 h
8
1 J& m5 N, o) Z! o2 o9
6 u* d5 s! M1 \ x( g: W5 @103 i2 L; ^( r; h+ B3 D3 W
11) T7 H4 t3 t0 o; }/ t' b X
12 d; h: r+ F" b! D5 ]! E" L e! G
13
: ?. Y$ N5 {$ j# t140 Y8 j6 e. r( b L1 l
15% i( `- F% B2 t" z* k, Q
162 p" x) @( M" [5 I& F5 Q
8.3 文本处理的五类操作 Z4 g u9 \8 u& X7 Z, S( m
8.3.1 str.split 拆分
' j# k9 r; `7 c3 ~ str.split 能够把字符串的列进行拆分,其中第一个参数为正则表达式,可选参数包括从左到右的最大拆分次数 n ,是否展开为多个列 expand 。7 d) x8 ~0 ]$ D) o
! W8 ?0 I3 u k- }+ m, L
s = pd.Series(['上海市黄浦区方浜中路249号',
7 `1 H1 k7 j' H '上海市宝山区密山路5号'])
' Z Y6 b+ D* Y; P2 b1 t1 T4 s
9 S. ~8 R; G. _6 f$ x) A6 Q) V! B& e7 B) E; ^
s.str.split('[市区路]') # 每条结果为一行,相当于Series3 D& F# a1 ]4 O" S
Out[43]:
: V S! [1 j* y: J0 [上海, 黄浦, 方浜中, 249号]7 C4 \. A) n1 c; }( h
1 [上海, 宝山, 密山, 5号]$ M3 G2 _1 G! B9 e) x& c
dtype: object. J0 u5 v& {. I+ |" i$ j
$ l R& z) i5 t) J( \ N8 y( y+ I0 u
s.str.split('[市区路]', n=2, expand=True) # 结果分成多个列展示,结果相当于DataFrame
+ B; k" ^+ e* N, dOut[44]:
+ e- \" U2 x, q3 I: x 0 1 2
8 b7 c6 P. Q5 O0 上海 黄浦 方浜中路249号
& [1 B3 [2 X2 {* [( a1 g. N4 y: i1 上海 宝山 密山路5号
; Y( I8 t ^' D! @2 c1+ |" z& F7 ], J; c! v/ x m( D% ~
2# l9 g# a# F9 {6 i4 ^/ W3 @# g
34 p) Z) |* o. y$ x
4
- c. Q2 q# m+ T* M* l& s+ r5* G9 g4 W& s8 ?! W. N
6 n0 p& y* M" V3 h" P+ F
7. q k9 F" l `9 E7 U
8
/ }8 u/ r3 K$ B. v `# A. s: C9
1 ?" h- H! L: C10$ {' p6 m. t$ N: v+ ~
11
7 S* X) D# S7 S0 @/ Y, p12
2 e: G. F4 C0 b a: R0 [1 H$ B% X13' p7 F3 P/ a4 S7 |9 J# f) _
14
# T. u: H/ L6 ]$ @: i5 i' ]158 }2 q( t) S" M V) {+ X! c
类似的函数是 str.rsplit ,其区别在于使用 n 参数的时候是从右到左限制最大拆分次数。但是当前版本下 rsplit 因为 bug 而无法使用正则表达式进行分割:& `6 S- X% K) v' A0 ]6 e1 F
# b( {5 U4 m: Is.str.rsplit('[市区路]', n=2, expand=True)
! I0 C3 Y! s! GOut[45]:
8 r" _% I2 `- m5 l' [/ g 0# ^/ e/ ^" O- k- J5 _6 _ K" e
0 上海市黄浦区方浜中路249号6 v9 H. n9 i7 v- I3 O2 k
1 上海市宝山区密山路5号1 R5 n1 ]" ]6 ?: c$ i
1
8 F7 a* E A! d# x0 j6 _& T/ S6 |2
" \( \* w9 _3 V3. }& m& A' s& `
4
& @( T0 z7 c5 s" Y+ M5
' {! t# y( V2 ?) V8.3.2 str.join 或 str.cat 合并
, g" a6 d6 A/ y5 O- C3 f+ u6 Kstr.join 表示用某个连接符把 Series 中的字符串列表连接起来,如果列表中出现了非字符串元素则返回缺失值。
9 W* o% ]$ n3 A7 B/ hstr.cat 用于合并两个序列,主要参数为:
S# E* F/ u" R6 gsep:连接符、4 @6 k" c; M% g. G
join:连接形式默认为以索引为键的左连接( r/ D" K' d2 ]# O H# ?* r# Q5 z" u
na_rep:缺失值替代符号
$ d( [9 s+ M% Q( l6 N7 cs = pd.Series([['a','b'], [1, 'a'], [['a', 'b'], 'c']])
% f1 \' S% [4 x# S1 b1 n+ gs.str.join('-')
& e7 X6 f! P$ P. hOut[47]: 5 [* r$ u* [. ~* r$ z% n
0 a-b
( U8 a5 [, @/ J0 m( ]% n; k7 e1 NaN2 v2 Q# O0 O! o) s" Q3 W0 \" m
2 NaN
+ O: J: z2 q, u9 c2 w- @* Hdtype: object
4 V* R& g: K' ]1
4 p2 @( o' F7 X) X* |( ]/ t26 ]" C5 P6 A, V4 Q0 q# m3 x
3
3 F! G/ q; H4 p$ F. R4
! W% x- c" N9 G: I/ O5
/ ` d; { k! r& b. d6 e67 _$ J2 ?6 |' [' I/ S! B
73 J' P h+ v1 l1 c: k5 ^! ~
s1 = pd.Series(['a','b'])
# v+ |% U: P; e6 n: ^+ Ys2 = pd.Series(['cat','dog'])
t: x& h0 y+ d/ i$ E6 R9 ys1.str.cat(s2,sep='-')
# g4 ~) X5 Z+ g5 y0 mOut[50]:
7 }8 g/ g& N7 t* w/ k* T0 a-cat" Y5 o3 b$ e6 p- b
1 b-dog+ S% A9 x0 p/ I4 a7 ~! v
dtype: object* f5 G* l) Z6 c# \1 ^
9 i7 k- x/ S0 t# ~7 A; K( ^s2.index = [1, 2]
, _3 } z0 {% Y( m' K; l! O& Os1.str.cat(s2, sep='-', na_rep='?', join='outer'). K$ e) s+ C+ k' w8 |8 d
Out[52]:
4 H. {& r) c; F# P' T4 o6 [, p0 a-?
* t' }9 p4 m* y$ W: w/ D# E1 b-cat
. y- p8 I: @/ k% k% p2 ?-dog
' [6 G* F2 d6 F4 G- Y/ qdtype: object4 y! r& h; o& n2 z3 E2 R7 n8 v
1
* [4 F$ X6 n2 A# F5 z2
/ ] d) r. H' y1 A3
$ [" g% ]3 C; t: {) c44 w; l. D1 L9 n% {: m: G
5 z# Z. m$ k: @
6
$ I3 h/ b. O& O; w" }7! @. h9 ~$ ]* m6 j
8
7 {' ~# z- Z, ? _, G) m9% ~& v# u1 I2 b. x0 ]1 B6 `$ V' Y
10
9 A& x# m& T1 L115 u1 W6 @5 u% y5 s" e1 U* b
12" G( f- P, A- h7 k
13
$ s: V! T9 h( q0 r( F# f14
& a2 R1 x, r1 \+ ~15
, F6 f" d+ w( m4 O& b; y: N8.3.3 匹配
5 _% R/ ~6 }$ ~, |5 r. K" z9 v; ystr.contains返回了每个字符串是否包含正则模式的布尔序列:
- v3 ?* u+ w8 [& D' vs = pd.Series(['my cat', 'he is fat', 'railway station'])
" m% l. b! {+ S& Q6 X* t3 gs.str.contains('\s\wat')% s$ |5 R$ E. d8 }
& D: P7 n- e2 ?0 True
" H% F, @* v/ n: T1 True, I. q; ~- j& C. T: v% |! a
2 False
# Q4 S7 c9 W! w" y9 Qdtype: bool
3 v* S% |8 G( ^1
1 A' [ F; V$ G+ C2( ?- a* D% M8 ]: u6 ~; ^, J
3
( C7 n9 G: q8 |6 Q# w4
3 D* T+ I9 I/ ?, F1 Z+ O* f5
9 G7 F9 g2 h- `# K( C& @' O/ ?7 |6 n5 r% e9 Q: _. U" c
7
+ J/ Y% |) i Istr.startswith和str.endswith返回了每个字符串以给定模式为开始和结束的布尔序列,它们都不支持正则表达式:+ U* Y! _. l( D+ ^" M6 @
s.str.startswith('my')+ @; m$ w! n6 L6 C
5 r, s* c2 i1 E2 q) U0 True* C6 }4 n P% f6 r
1 False, I$ X% f5 d" `' j3 @
2 False
, N* H+ G0 {5 k, G+ odtype: bool1 J& |; n% H( ^8 N. p% M, f: l
12 t( p4 Z! b6 S3 A0 E- o0 J3 l
2
3 y' H! h+ n/ V# T5 J3
: t& t/ p2 E; `; e4
0 s7 r! H1 ^7 z- d _5
. ~; ?9 ]: N6 ?& _$ m0 b66 M6 {' @% C/ Y4 S
s.str.endswith('t')
5 J! Q, o( w2 W6 h& }; m; z; L. o; r- C1 X+ _8 `
0 True
8 Z2 r: j& s/ T& R1 True
% J$ c- k; c s n: ~0 {9 M# ?2 False' e. L$ w: @. t) L$ K1 n% N* x
dtype: bool2 q3 h% L# `: j; a" J6 Q. T+ P
1% r) {6 [6 [: s; {$ j H/ L
2
0 ^- g; }- k+ C4 k3
6 P) g5 [- [ H" m4* o2 d3 s2 u7 o. l8 q+ ^
54 X( i! M& }. X7 u+ G! U) _9 i4 f" T
6
9 Z' K+ ~; `' W4 Istr.match可以用正则表达式来检测开始或结束字符串的模式,其返回了每个字符串起始处是否符合给定正则模式的布尔序列。当然,这些也能通过在str.contains的正则中使用^和$来实现。(貌似没有python里的search方法)
) H8 i/ N, r5 ]6 x5 H# Xs.str.match('m|h')
2 w* S9 g, @9 x. }! K0 M1 Cs.str.contains('^[m|h]') # 二者等价 v! [' k: c% I6 k T6 N, I$ h
$ j6 Q/ v. Y" t+ I6 [
0 True
% P; Z8 @& p4 \& L% u5 Q1 True
, _5 j- }: G- }3 d6 o9 f2 False9 z+ X% G# \2 H) v. o! |5 n; n6 I
dtype: bool) Q" h T* l" P
1 K p6 A3 M% P+ s: c5 A. q
2& V+ ]& {: D+ D4 _) ]: I$ O
3
/ K8 p; F! [) e4
+ u* `" g5 y9 c1 O3 F( Z8 c5
+ @2 d @& q `+ v0 s6' O) S# m% G4 B9 K }
7+ a$ M4 O+ v/ T: y7 T3 `
s.str[::-1].str.match('ta[f|g]|n') # 反转后匹配
1 ]; X" S4 x2 V) V1 g2 ~; h& Qs.str.contains('[f|g]at|n$') # 二者等价
L1 A- V) k- l% t' s$ j4 x
0 q* d1 F. k! J& N4 n0 False+ F0 k3 s* {6 h4 I& A! p& O' s7 _1 }. d9 G
1 True
b \6 p) l2 W. P2 True0 y/ I/ N2 B' Y) C8 U: j$ y
dtype: bool
- j9 Y3 M: G# J: v3 s1' K" U5 \0 R6 f
26 f6 G9 O" w w0 B3 R! S
3
. \; I) O3 J7 R! H, M5 b$ w4
: [. P' p, O# Y" K" P5
* \0 j8 G0 ~6 q( e6! \& r7 Q, P3 a4 `
76 L' B6 ]: X3 a# J2 ?
str.find与str.rfind返回索引的匹配函数,其分别返回从左到右和从右到左第一次匹配的位置的索引,未找到则返回-1。需要注意的是这两个函数不支持正则匹配,只能用于字符子串的匹配:( d) j6 E G0 n9 q) y' p* [ D
s = pd.Series(['This is an apple. That is not an apple.'])
9 W: R; K0 V7 s8 a- G: E& D' O) M* v
s.str.find('apple')
" F. E* Y, ^: |* x8 P. HOut[62]:
' O8 W. a7 E% ^) c7 d5 M0 11' @' ]. K7 _0 f: A; ~
dtype: int64
: w: u8 t7 Y, y- n7 V
- `5 [& L+ I, U0 gs.str.rfind('apple')
5 {2 \) E) _3 y9 d, k0 YOut[63]:
9 {* \7 x+ P1 g, E! Q3 m- @0 33
6 B: m" U# l' L+ N: Gdtype: int64
' u0 b. E @7 X* V V! ]6 b5 [12 H5 z4 i% D+ l
2# F- i3 K% {0 q+ ?8 J' L1 _8 a
3
9 e6 g* K+ i( ]) Z9 B4
; |. ^7 w# U4 }% d5 X5 f) H) p' Z5
0 t: a5 a. b6 n! l8 i! h; l, ?6
' E. n! n- g5 V( h7 F, m7; o* `8 D, B. p5 r9 u$ m" @
8
" H1 a& q d" N1 b6 p& v1 J98 @! l: C9 I& n2 k
10. _+ m/ g" n& a$ n+ D+ \% k
11
/ ~5 n. @1 L7 G# Q替换
" E C9 `$ _( S7 C0 N* Dstr.replace和replace并不是一个函数,在使用字符串替换时应当使用前者。$ G) k1 h( o5 ~0 F B5 k7 ?( Y( l
s = pd.Series(['a_1_b','c_?'])
* j2 E* s; U1 W7 F# regex默认为True,表示是正则模式,否则第一个参数内容表示是单纯的字符串,也就是匹配字符串\d|\?$ v: J0 N8 c2 l4 J- F' i7 d" U. h1 ]
s.str.replace('\d|\?', 'new', regex=True) 7 r |; ~' P' X$ t
/ ]; ?! R$ i4 Q- Q9 ^& |# L( D$ d/ x
0 a_new_b; Z7 X7 e( G& t Y
1 c_new3 d8 F' R J3 O n3 P
dtype: object/ [# s- H+ p# g. A
1
7 T0 I4 ?7 ]: r; C' N: P2
& a" V* z" F& f3
. f; O$ f4 Q; B3 N* R ]4
$ n: O' y; h* n5- s! b% ?( K/ c7 m
6) ]3 I! O$ N$ R! p0 x9 Y1 A
7
% x- G- ? j/ _ 当需要对不同部分进行有差别的替换时,可以利用子组的方法,并且此时可以通过传入自定义的替换函数来分别进行处理,注意group(k)代表匹配到的第k个子组(圆括号之间的内容):* S) w6 ^4 \ [) t
: G9 N2 }0 I4 {: d" ~8 m
s = pd.Series(['上海市黄浦区方浜中路249号',
! |% y+ g( b b' \. U '上海市宝山区密山路5号',* m! N, N. |2 r- v
'北京市昌平区北农路2号'])
& m& `4 i* K ^* \2 F* }5 j& _. tpat = '(\w+市)(\w+区)(\w+路)(\d+号)'
# w* K! b, X: c" O) acity = {'上海市': 'Shanghai', '北京市': 'Beijing'}
. j( l6 f N1 G; C4 y' e- k! jdistrict = {'昌平区': 'CP District',% e. K+ b% G) h
'黄浦区': 'HP District',# r7 d5 G: s6 Q. J) a5 h
'宝山区': 'BS District'}
) ?( q" T" F7 v) iroad = {'方浜中路': 'Mid Fangbin Road',
: i0 n3 \4 n' w! B '密山路': 'Mishan Road',
# P* ], R! ^4 e9 d7 \( M+ `, D '北农路': 'Beinong Road'}
" I: J. u+ C; |# G' b& }! B! x" Pdef my_func(m):
' C+ G% s: P. B: {1 O' w5 r str_city = city[m.group(1)]- d8 {) l# X+ q, S0 f
str_district = district[m.group(2)]6 e5 J; ^1 Y( w9 {: _* {
str_road = road[m.group(3)] g" C! ]- {6 X/ p
str_no = 'No. ' + m.group(4)[:-1]
" N) ~' w7 \* q return ' '.join([str_city,
- c- P- O& B, z* X5 V str_district,
$ H) F# F1 u, w str_road,1 ~( B( z9 p8 l' `; F5 X7 ^
str_no])! f- z+ ^$ j+ f% Z, E+ X7 M; T8 N: E
s.str.replace(pat, my_func, regex=True)5 ?4 j6 P/ p/ M5 ]) H b
9 V! U: j7 Y/ T' u% k
1
: t# [4 r' n# ~, w# E, x: Q2 L2$ f3 h' Z& u% {
3
* @( q4 F2 W: M! O5 Z5 C- Z4
" r2 f) v& _, q) u" }9 h& W4 d1 V: G5# T d6 Z; J* @& o% o
6
O( r# k6 ^! ^- _6 }- B! x7' o8 e' k F2 f$ V& j$ V
8; \ v% f5 H! D2 b1 |4 e
9 l2 K& c* o9 }/ d1 {
10
3 F1 e9 R. S3 _11
3 e& I$ S1 P. a8 H2 {; }12
8 } q( }& I6 A j0 \, V3 T$ @13) F- d: D% g$ N
147 V( i: M( I3 {* N( H( Y
15
# e. \6 Z+ M" L16( x/ ]1 b! M! G# D
176 W3 Y. k) i: H: Y2 X9 ]) C6 I
18- R( V/ U% z- E
19
0 F9 A% i6 P3 k. c9 ~1 R4 i; i20
' w( m8 Y( h; I7 k: _21
9 O0 e+ c( k0 ?4 W4 y4 ^7 x4 e v0 Shanghai HP District Mid Fangbin Road No. 249$ ^ Q# y, W% I% C6 M
1 Shanghai BS District Mishan Road No. 5
# N* s0 j- L% Q" t2 Beijing CP District Beinong Road No. 2
& @$ x9 O" |7 A8 z7 Q# udtype: object% T* e- ~4 y. j; Y9 F* z
1
- s* n' s* c5 t2 d9 v9 L2
) s9 f1 R" @* F* I3
% z3 T( i* N$ L- X0 z1 ?9 a2 q1 P4
$ H/ n7 m4 o5 K这里的数字标识并不直观,可以使用命名子组更加清晰地写出子组代表的含义:% T! B ], e/ Q& ?
5 y% M% Q' V' H: [, x# 将各个子组进行命名
; N7 C3 N# G A5 Ppat = '(?P<市名>\w+市)(?P<区名>\w+区)(?P<路名>\w+路)(?P<编号>\d+号)'. d4 D1 @. N1 g
def my_func(m): o: I3 j5 c% h" e) K
str_city = city[m.group('市名')]
. D! Y+ n' c) a& e str_district = district[m.group('区名')]
; W4 B) M( _ p' z" f1 G1 G1 u str_road = road[m.group('路名')]
8 t) G1 j. p3 A/ |2 E8 @ str_no = 'No. ' + m.group('编号')[:-1]9 z" T9 |5 Q& i- H; M+ o
return ' '.join([str_city,5 t3 \/ {! L2 i* g8 @
str_district,
2 n6 y" f- C) k0 _ str_road,
, X0 w$ t4 X$ `( @, F* c( j0 D( m str_no])
7 X( K/ }0 k3 b' F os.str.replace(pat, my_func, regex=True)6 p1 n' |1 q4 H( p* o# N
15 `# A a4 O2 ~) Z
2- D+ \1 O& J* U) z+ {
3" d% b( k3 n1 y; w) ~( q9 g; w6 \6 G
4: O% E' Q0 c1 p' }6 X' n
5
9 N: Y7 a+ {# p; M: O' \6* ^0 e3 O6 Z. t8 d) H
79 `" G5 u8 k' Y* ]( {
8
1 v2 t+ Y% ?+ Q& R" G+ U/ H3 ~% o95 @* i/ O0 S& m
10
* U. P, V) z% c& ?8 v6 G1 k3 Y115 X* E g1 k& K$ h8 f
12
4 n! `0 L. Y P0 Shanghai HP District Mid Fangbin Road No. 2494 ^/ l/ U6 _" ~( A* X# T
1 Shanghai BS District Mishan Road No. 5; b8 w t Z1 Q0 r1 `6 i, J
2 Beijing CP District Beinong Road No. 2$ j* o3 Q1 \( }* _0 E( A9 V; X
dtype: object
8 x+ P; N( ^2 x& _/ q8 s1
" M( h0 G2 I7 Z' b4 f2# @" W" Z# X7 U7 d- G- X
3
7 _& U5 N5 e a! q4
; h+ l0 ?3 | @9 ]- x6 h 这里虽然看起来有些繁杂,但是实际数据处理中对应的替换,一般都会通过代码来获取数据从而构造字典映射,在具体写法上会简洁的多。0 J- a, K' {# h$ a% X
, b* M7 v% S9 y% r7 i+ a+ C
8.3.5 提取* `) s3 H* w6 ^( S+ T, H9 g9 U
str.extract进行提取:提取既可以认为是一种返回具体元素值(而不是布尔值或元素对应的索引位置)的匹配操作,也可以认为是一种特殊的拆分操作。前面提到的str.split例子中会把分隔符去除,这并不是用户想要的效果,这时候就可以用str.extract进行提取:* `; A* J; F5 v* v) e: P$ s
s.str.split('[市区路]') ~. N6 N N5 x6 g
Out[43]:
5 U& C* O. |( z: M2 T9 |, U$ F0 [上海, 黄浦, 方浜中, 249号]: @/ v6 h8 f# a+ U; W! v
1 [上海, 宝山, 密山, 5号]3 A4 |. g# g8 t% ?0 W# e* v
dtype: object
" `/ U7 V: U/ m) w4 o" R9 ]3 M+ ]- [ L- |
pat = '(\w+市)(\w+区)(\w+路)(\d+号)'
* ]% W5 R" ~+ r- xs.str.extract(pat)8 j( T& e* `7 i1 Q
Out[78]: ^& O9 T+ l' j8 o
0 1 2 3
3 X9 v5 m, O1 U) [& E0 上海市 黄浦区 方浜中路 249号7 _5 Y9 ] R# z. |7 m. i9 }
1 上海市 宝山区 密山路 5号
7 p: B8 }9 ~/ Z+ c0 l2 北京市 昌平区 北农路 2号% F2 z' e# M( C) I( g
1
! H; i$ l$ E! [ f3 A2- M! j, V, @! H0 h
3; A1 a% K6 L8 v6 _' u
41 b: V0 w2 ?- c# b, X
5
% l/ N6 V. H1 x2 M. G3 D6
; o6 ?. z& j) U l' C& ]7( Z! B! W8 D& N6 C; L
8
4 H) I+ c1 G" g1 T% C& z6 H- g9& |' z+ ?" `6 E. S# z/ u
10
# f; t, k% q3 A1 Y11: _) y0 F7 x( S3 K! _
129 L6 P( Z: l; v( s; f+ o
13
: `( S. w n# o, S( F( k! L G通过子组的命名,可以直接对新生成DataFrame的列命名:
2 G* a( ]7 D: ~0 O8 y9 ^: R7 M! o
: M# N$ |/ c! w" `- @8 Gpat = '(?P<市名>\w+市)(?P<区名>\w+区)(?P<路名>\w+路)(?P<编号>\d+号)'
) D# e+ o: d1 j( R" A ss.str.extract(pat) O8 E- W1 d, L8 @5 Q- C' m+ @) `
Out[79]:
b: m, X5 N# I0 [% [4 J( o 市名 区名 路名 编号) ~" b/ S f* G1 H4 H
0 上海市 黄浦区 方浜中路 249号
6 v( [3 m6 T7 b( R* z1 Z1 上海市 宝山区 密山路 5号
; R! y0 R+ A1 t `, [2 北京市 昌平区 北农路 2号
; c2 f8 w f+ D9 M* @8 [4 H' x1
. W4 u2 \, k9 t$ F/ n; r2+ f2 {8 G% g. |' t t# m; b
3
& _1 l B* N- f4
7 v) [5 `$ X, f- E' X5 e/ u: l" {1 H
6
5 e# M2 ^ |5 i3 B5 M) K q7* h5 k. T' z: f/ @# ]. A' G7 A! l5 c
str.extractall:不同于str.extract只匹配一次,它会把所有符合条件的模式全部匹配出来,如果存在多个结果,则以多级索引的方式存储:6 _- P7 ^' A2 k4 v
s = pd.Series(['A135T15,A26S5','B674S2,B25T6'], index = ['my_A','my_B'])
! D( |- [) L7 x( v& m& U6 rpat = '[A|B](\d+)[T|S](\d+)'6 y+ {" V8 z+ @ L9 ^
s.str.extractall(pat)- l. o I9 l: O6 m' L) g+ ]% ?7 k
Out[83]:$ u1 }( q: d: y$ B5 |0 W
0 1
8 N5 r7 c3 n" N9 l4 w match 0 R/ k1 i1 O4 D' z- ?
my_A 0 135 15
1 \. `. Q- E1 g0 j 1 26 57 T1 A0 [0 m) X! N
my_B 0 674 2
T, q% }' T2 w" _5 E- j 1 25 62 [4 }. ~ N% }9 Z( L; |% A& c- P3 |
1
; q A4 \1 W$ W2 j! B8 f! H2
0 W7 Y- S& Q2 j S- K" d2 \5 N3 Z" s& Y. [- }7 B
4
$ b1 _( D; [7 Y5
! U# O, [1 m% T' f% C6& L. T8 a3 C6 H; Q2 s+ D, x1 p
7' z4 w% D2 o/ T
8
! k+ n$ m* S" I& w9( h) ?" v- D- q! E$ g* T
10' ]+ ]0 n- q$ ~; W% A4 h
pat_with_name = '[A|B](?P<name1>\d+)[T|S](?P<name2>\d+)'
% I! I! N4 f$ ^& u# C* Ns.str.extractall(pat_with_name)
! v" A# o6 k+ l& P" i' {Out[84]: $ ^9 W/ o. ^! ^! }! V
name1 name2. ^8 Z5 o' A% _7 S$ w
match " g4 p9 g6 R- u* ?8 T
my_A 0 135 15
6 ~$ m& A0 h3 p 1 26 56 K! n. W/ s9 g* w" j! R
my_B 0 674 2
1 k1 b# X. |1 @7 s* `7 H$ Z; C7 D 1 25 6: L; f, U2 @" F; f
1- Y# S7 {6 s/ h- c, I2 J
2
6 a& y, _- G4 [8 d+ D$ t0 l3
% V4 ?: E i5 S( b4
$ o! b1 ]) {( M6 z" V6 _5& m/ O" f: m% P7 ^& n2 C- `
68 _$ D7 i/ B. [
7+ i3 ?3 Q7 |/ v6 n o
8
, m7 j" k$ _, ?, Z+ q9
# y% M6 _+ ?0 u% wstr.findall:功能类似于str.extractall,区别在于前者把结果存入列表中,而后者处理为多级索引,每个行只对应一组匹配,而不是把所有匹配组合构成列表。
. X- I" B; [9 W V9 Ts.str.findall(pat)9 N3 @' N/ D; g! B h6 V( N2 l
1& N) u$ P3 H; A8 |
my_A [(135, 15), (26, 5)]5 L! m# X5 [( a. ^7 I; P/ J3 d. ~
my_B [(674, 2), (25, 6)]8 U! h6 e% h T9 ~
dtype: object- ]8 E% L! t& Y
1
z% i# f) B7 B, J2
# K3 i/ N' k5 z# T- J4 L1 ]3
3 Q8 Y7 n6 Q/ W, ]) S: r5 J: v7 m8.4、常用字符串函数
2 G3 B6 W$ r- t4 I; _7 Q7 V9 R; o 除了上述介绍的五类字符串操作有关的函数之外,str对象上还定义了一些实用的其他方法,在此进行介绍。+ w) E/ H* v% O- ]# Q: b; |" `
: ~' |1 q1 N! i+ x: A+ |1 M
8.4.1 字母型函数
4 ]! J/ O/ a* S) }( R7 D4 A. |' I3 Q upper, lower, title, capitalize, swapcase这五个函数主要用于字母的大小写转化,从下面的例子中就容易领会其功能:
* w. T2 g- a, U. L
! N/ c5 ]3 `" u5 k' W$ gs = pd.Series(['lower', 'CAPITALS', 'this is a sentence', 'SwApCaSe'])0 I+ f( P3 a5 `2 B4 i
8 ]& w7 I/ k+ E
s.str.upper()+ l# d6 `6 D6 y0 t# Y x
Out[87]:
+ m. O. ~: q" a0 |9 ]0 LOWER
+ Z& s9 k/ D: O* |1 CAPITALS
7 {; f5 M u2 s7 i2 THIS IS A SENTENCE* U2 @% X0 B; I" e, D
3 SWAPCASE
. [$ {' X& W6 p- z. |8 U* E" |dtype: object
/ n9 B% W+ A, z# W/ i1 i J4 s0 f
, { Q* m0 o; ?: ~$ i2 A! C" As.str.lower()" }8 ^5 H8 {4 ^$ n: X+ {- Z
Out[88]: & P% ]* t; c3 N& u q9 G0 V8 w; E
0 lower x4 S, n7 _2 x( u0 I1 |3 X
1 capitals+ g" a2 n0 A7 f7 h7 |* p' ~
2 this is a sentence3 F! V4 N2 h( l3 h3 V7 q4 z
3 swapcase% n) C; W/ Q2 e/ ?5 s2 t5 a; A
dtype: object! X3 D4 ]3 P! i0 G8 p9 P! K
7 S# {1 O9 w/ t( v* E' H0 J/ Z) hs.str.title() # 首字母大写% h: _& r; G( c- m5 c# E
Out[89]: % Z) j$ F! z* @/ R
0 Lower
% l# E! C* G1 N2 Q% p7 y1 Capitals+ @0 _" j- x/ g
2 This Is A Sentence( @- u1 ]) h( U0 k
3 Swapcase
$ S9 G9 O3 }1 }( Y1 p+ m- zdtype: object
, C! L) G- q5 k& m1 I0 M- U% Y' k1 {; S; R9 \* V
s.str.capitalize() # 句首大写
3 X/ l5 \2 u/ w" mOut[90]: ; l! x( ]" H; I. D: ^/ l
0 Lower; Q* K) Q0 |, x6 h3 C
1 Capitals
, B6 i$ k" r8 b$ {2 This is a sentence
v, p \" _1 I' ?- D3 Swapcase
' u+ G# C$ L) }dtype: object
# l0 h: s7 F4 B3 M: g% o+ v3 r8 y# r+ z. P) _' U
s.str.swapcase() # 将大写转换为小写,将小写转换为大写。
# a) C# A6 @' k& g( j; NOut[91]: , R7 X4 T ?0 C2 j* }/ p
0 LOWER9 p+ u \! w6 V
1 capitals
7 j$ F2 J; }. L+ [ y2 THIS IS A SENTENCE* Y) g8 O& I- w" N
3 sWaPcAsE
( Q1 ?8 C$ f% x& Y% w/ }dtype: object
. S( K+ J8 Q: r' ]0 v+ A# A" j. m2 t+ g- q
s.str.casefold() # 去除字符串中所有大小写区别
- f5 S4 R( U; s2 c; ]5 n- \4 q; `7 \+ i; }4 M2 X; u0 }6 T7 a
0 lower! c3 p) z4 W" | a k4 `
1 capitals
; p2 L {( m9 j# C9 a2 i. a. a2 this is a sentence
5 Z) z) X% q4 c; m" y- E( I: D3 j3 swapcase
7 h: y% Y4 w) g" @! t; R: g- U8 Z% b7 [" {! S" {" j
1
' P$ B- n% W% I. Z; C/ J2
( f( n3 {* W% ~% b3
& f+ g: X( }9 t* ]4
8 A7 h' Y, I- ^) |+ o5
) k: U0 _' ?6 a* M- K3 D+ ]" m6
5 f( V7 F0 D; z7
4 {; g# P5 Y: Q/ ?1 Z7 j1 V7 T8
4 `) J g D8 x0 J! v+ F9
$ \* G M5 {7 J- X7 i10
8 t) T" g2 y( Z' Y11
$ K3 o% n9 u* q* H/ E' P2 [7 d123 \2 ~+ Z e. [' T0 {
13
/ w7 @( S( \4 r* W2 c4 Q) x6 x140 B" q" O: E" h3 u# G# J& Z! \
15+ r" w; t: r; ^. G* S6 G: f1 s
16& E7 z6 P* p" q5 F% y; z" |" r$ Q
17
8 X3 a, q! m {3 D3 G+ r4 p, o: _18
% G/ y1 R9 |& L( H! u9 ^19$ D, x' K; s6 y6 w8 L: X7 q
20. {( D, V# D( Z
21
' J1 \3 e/ C* R+ @! |6 j22/ W8 P+ c, W3 A- O; \
23/ e. K. q: P, A4 Q; a
24. g9 g) e; e7 ?# E2 X
258 h- B# Y9 I) F" a8 W4 j: U( p
26
! ]. \3 v' \. Y9 Z' n1 Q27
: V- c; C8 z. F/ ?28; [0 D/ g( r: Z* O
29# P4 P' H% l# L) K, t& q/ c) j6 h
304 b0 v1 U) b/ |; m* _1 Y5 R2 B
31, R( S( y" b/ W* |: i
32$ `. b2 I5 j- g! L I
33$ m2 B' L: Z9 r4 d& b( f5 `
34* _& Z+ A' K& ^2 D+ f
35
/ D: t& ~8 v* v- }5 U, }36
z: z& V# m0 z7 F1 G377 W. I5 ^* Y1 J
38
1 ]4 i5 F4 `9 _, a; p39. l" {5 `- p5 I
40
" U" x/ H3 A: r/ y9 Z4 U7 t/ u/ @/ k419 y9 k* m$ U7 ^
423 a1 _: U2 P; j/ u. e: C6 `
43; J& k# P% Y& h$ V
44
1 k: E2 M" R: x) x2 |8 f45
. i) P2 D# V* w8 l# Q5 L46
! M" c$ ?0 R& }9 R3 Q) @& d* q3 `477 Y2 N$ Y2 u/ T7 _. M" Q5 M& B; L! O
486 E( t5 W4 s1 ~& J" O5 h
8.4.2 数值型函数6 S% ~ L1 J9 |6 ]9 W. d+ B5 o( K
这里着重需要介绍的是pd.to_numeric方法,它虽然不是str对象上的方法,但是能够对字符格式的数值进行快速转换和筛选。其主要参数包括:! d( S1 i% i0 q/ n, i/ {
& [9 y. v9 |( D; Y( b; G' e
errors:非数值的处理模式。对于不能转换为数值的有三种errors选项:
' p! A9 u7 X% Y0 o/ D( ^raise:直接报错,默认选项
5 m2 g2 B3 \* q3 [" n" p' Ycoerce:设为缺失值% g. Z+ l# m" m6 Z3 ^1 R7 O; \
ignore:保持原来的字符串。
* `* J& P4 W$ _- Vdowncast:转换类型,转成 ‘integer’, ‘signed’, ‘unsigned’, 或 ‘float’的最小dtype。比如可以转成float32就不会转成float64。# a; [' p! a& F, f5 c& V+ Q
s = pd.Series(['1', '2.2', '2e', '??', '-2.1', '0'])5 j( L: N, J% z ?
. _( R8 u X3 k( c0 i* z
pd.to_numeric(s, errors='ignore')
1 \* d2 p4 f4 {* z* M1 B6 cOut[93]:
3 y( Q( m' P# B) D5 P8 d7 N0 11 T$ P9 J5 f+ \) ]5 S# e% f
1 2.2+ }0 e! P4 ~ k4 W+ Z2 J- L
2 2e
' \5 B* h% ^9 |5 y( N3 ??
: O, _6 P: L, X- l3 O% R3 E# i4 -2.1
+ q; ]8 h b9 z5 0
% U F# I8 q) Q; O4 ^) Mdtype: object3 \+ e7 \& Y, q, h4 s' D& c
0 ?% }) t8 Z2 t: x
pd.to_numeric(s, errors='coerce')" U8 W4 Q( J( u) _; A1 K; X; U* C
Out[94]:
. v& M; u# h% ]' E0 1.04 b/ T) ?6 U7 |
1 2.2% P; p; y N% n, s, F
2 NaN
3 }$ Y; T' D1 K" `$ \. Z6 q+ z3 NaN
) {( _) l: M4 y4 -2.1: D4 O! n( U; E5 x- n& `) e
5 0.0
3 F: K6 ~' N( o( R7 k, m6 Wdtype: float647 M6 d) a9 A% d
+ J# c7 C; ?7 u* n" l8 k+ @1
" h9 `( M; a, U J( V$ b( G2 m2
, J" U2 L( |9 H& }3( f2 `% a9 V1 _
4
( W8 w: K/ q: n2 W/ \3 }9 b5
$ E7 x1 c) Q) U! h+ b6
& y; R1 `4 R _# L; v- U2 Q5 Q1 h1 K7
6 @! t9 v8 @) S! D8
4 [$ }, V7 \/ E3 c6 s, }* y9 E9 X9; a! q$ ~4 X, H3 c" t
10
+ C5 J: i0 W6 ~% v( A: w11
" s' x- u% W1 ]; q8 O3 }12: _4 B5 |0 X$ R
13* i3 q: ^! {4 S
14# M r* V) H6 l! M) O/ g
15
% I6 {# g! J* K5 R2 [% c( T16' Q6 [+ w5 p0 G/ y
17
1 ~: x. x: B, O5 r! h) p0 a$ Y18
: J0 X9 K" Z, k5 O. j" Q. a19
0 ]" h0 u2 G! p# T4 S20: `/ j/ n; \4 |5 l. ]% s
21
; F0 p% G' g) E" r: L5 N" q 在数据清洗时,可以利用coerce的设定,快速查看非数值型的行:
9 q# p8 d3 u; O; T4 }! M4 ?" _9 x4 o$ X( ~/ s
s[pd.to_numeric(s, errors='coerce').isna()]
3 y- z' c" j* p% W1 j! [Out[95]:
' D+ _' V3 O5 _% l2 2e1 E8 k" U, P' n7 x, v+ d6 ^) W
3 ??
k+ h+ m( `" o" jdtype: object
. a% }( [8 |. {% r; H! g1
: f$ p/ v. `. @! L& H8 o2
% L1 g' o6 U3 q; Y1 R8 M3
+ Z+ k% v# z, t1 V& X% W4
9 e+ }" K, N% J" ` {' ] {( |5/ J2 @$ @' o3 U9 G7 J
8.4.3 统计型函数
# {# M6 [. D3 C9 j* f \ count和len的作用分别是返回出现正则模式的次数和字符串的长度:' `# i, x9 x. ^) y8 R: W) t2 E8 b
; \1 m2 g: p, Q1 A0 _4 ^: F# H$ ys = pd.Series(['cat rat fat at', 'get feed sheet heat'])) u; w+ Q$ G& O' a" j
' I4 K- X- E" hs.str.count('[r|f]at|ee') # |左右两种子串都匹配了两次
$ \- p/ B! h# q# D7 F* zOut[97]:
: D" x6 D# z8 W6 d0 B1 U2 h" \0 2
5 ^: Q+ P' W: Z: Y3 @1 2
. s& K4 Y/ |! \dtype: int64
) Y) m1 z4 V& @2 d2 p& q
9 m& c0 \; I9 _9 | Es.str.len()) Q( p7 I3 t8 Q
Out[98]: 6 Y* C, B$ F" j9 t2 S0 D3 I. b; z1 \
0 14. p( x' Z5 n; n$ j# d
1 19
2 F5 V2 _. p# }. u f) u: ?dtype: int64
/ X% @. v# C" E( t1' ?' H# @( Z% \) _8 }2 f
2$ h. @2 h; q/ A6 B. y5 z
3
/ t- M9 B2 w" u4 u) E! E1 ^4
! h- q0 }3 s% \! r! d6 p- [$ A5# E2 }% S: [, ?2 y6 A
6" `/ I# C. p0 R3 M- }
72 a1 U" d# z4 ?# i( N; P6 i
80 _/ w6 n2 i$ {+ n- ]2 Q0 M3 A( @( |& g
9* F, [( b% @! N. s" L$ f, p
10
, F( h8 r8 o8 C1 C: a( f7 s5 g11& @5 d/ l/ r4 k5 w! `0 E
12( A: G+ f, U2 z0 [
137 K! V4 T/ m1 m G
8.4.4 格式型函数
5 u* x$ l# _/ F4 w3 d$ m 格式型函数主要分为两类,第一种是除空型,第二种是填充型。其中,第一类函数一共有三种,它们分别是strip, rstrip, lstrip,分别代表去除两侧空格、右侧空格和左侧空格。这些函数在数据清洗时是有用的,特别是列名含有非法空格的时候。1 t) L# u8 D+ F
) j" [' Z4 ?0 @
my_index = pd.Index([' col1', 'col2 ', ' col3 ']); f7 B$ _' u G$ T+ z: l
2 @9 F% t5 ?, L2 i
my_index.str.strip().str.len()/ q. B6 [7 g0 c' o0 `
Out[100]: Int64Index([4, 4, 4], dtype='int64')
, y8 s4 } V, R; Y4 f3 i( `! c$ U5 o- N E/ b8 s
my_index.str.rstrip().str.len()
& q5 [/ o$ |) C+ c6 `+ |# vOut[101]: Int64Index([5, 4, 5], dtype='int64')$ f2 X8 e: i/ ]
9 K& S4 _* i* `5 N `my_index.str.lstrip().str.len()
# S, u! z" r8 ?& {Out[102]: Int64Index([4, 5, 5], dtype='int64')
; p2 G$ V! ~+ w) S/ P% a1# b( ?# q$ V* G- T# I. R1 k+ m
2
- e! ?- G- v8 M7 C33 e% i4 z* r1 _) H/ i' C3 P
4
) s3 m$ o- N4 H9 B5
. }8 ]' X7 D9 g6
, ^, H7 @/ g6 ~; z7/ t% m: Y5 C% k' Q, y
8
1 a3 m! X G5 ]4 M q9
, L: u0 {3 B% B4 N; D7 t: I/ A10
. D. x9 I2 }, `) \, D 对于填充型函数而言,pad是最灵活的,它可以选定字符串长度、填充的方向和填充内容:2 N5 Q" |. q. B& B& Y3 B0 v G d# X
8 A; g1 b ~! y8 ~% C9 g7 W
s = pd.Series(['a','b','c'])) H+ _5 u; T. t+ {
* `) Q4 G2 s& X$ Z# Ts.str.pad(5,'left','*')
) Q% d- @) B% Q% Y5 c8 J& `4 w: U: u0 xOut[104]: 2 t( e5 x( v/ k! x
0 ****a+ s4 `1 n5 i& a# ]2 ^
1 ****b/ D6 r, Y) @9 L# v* W
2 ****c
0 s- T5 k: t& A5 c k* i! Wdtype: object
1 d: i% O" V7 \2 b7 G- h
7 F& i# P; R7 f( z- E. U" zs.str.pad(5,'right','*')
( _: g4 ?; ?' s& R- q4 ?, TOut[105]:
- c. i+ M) l d0 a****1 F# V" b R+ }7 H. b0 U/ `
1 b****
+ \+ T) B, I# V( b' ~2 l* N" I/ q1 v2 c****6 W; ~# U8 |8 w. w7 |1 `2 R
dtype: object
8 D3 w3 Q6 ?& v2 Y1 v; y
5 D! i: f t, J2 @' zs.str.pad(5,'both','*')
8 P* {- T6 I+ l: x- K1 `; j3 xOut[106]:
8 R) m, a+ n' H) m0 T6 D0 **a**- i& U2 M. u( Z0 d% ]! e1 z, n( @
1 **b**1 p2 v k- @% N7 r* C
2 **c**% t( t0 O9 F- p: X3 S
dtype: object
$ ]& z# v( a; O( i: e5 c `7 ^/ a; f* I) A- u
15 d E, F M7 B; N" J, D3 f$ [
2
3 o- A4 E% H; }* B7 O( A( ]3* m, Z1 z/ [/ x
4
# f$ Z9 }( F$ A9 g/ Y5 k5$ O$ W: y& K( }- ]$ X
6
' C. k1 u" D9 t% w6 Y# n7
; g' y2 N( p* `2 Y; i80 T' n& n: H; n! Y% X+ `) X7 y
9
Q8 z0 X Y1 N4 {/ r10
7 G% n- P1 t$ l. x11
g z9 x- \+ q) W12) I+ i- p* k3 X2 v/ ^; r2 a9 S
13
2 N: Y, q# u0 \14. N5 T; q" Q; Y2 k' T
155 J0 n& \8 U& y) G t
16
" M' s+ l/ w$ X- t7 a17
7 F6 b V, o S, R7 q. R* m) y18
! t" i7 u) q% q) g: o* q19
, E: A5 s2 u2 U H& ?2 a/ c20
6 M7 g0 ]: K6 N8 j0 O21, L3 h8 g. y0 r/ E3 j r. Q
22
) Y( z* A; m7 V. r' d, o- ^$ a 上述的三种情况可以分别用rjust, ljust, center来等效完成,需要注意ljust是指右侧填充而不是左侧填充:6 C2 g5 X# m2 N4 d. | M
% G) P" }; R* L. u: I, q9 l7 F8 N
s.str.rjust(5, '*')
& Z0 }1 l+ |, z5 v& WOut[107]:
4 O; t& J! x, g6 A. ?2 j- U0 ****a& }5 I. Y, i J1 b
1 ****b3 T3 X4 a3 P, _% u$ @
2 ****c7 s9 A) N/ }7 A. I4 M6 R; k
dtype: object
1 d; C. b* Y7 v6 l/ x
3 P) g P2 U2 ys.str.ljust(5, '*')* Q/ N3 _7 N' v1 f1 D$ F/ k6 E# W
Out[108]: ' {8 m3 K3 `5 Y5 V
0 a****5 v% [2 D! _ x S x' ?$ Z
1 b****
1 w& @& h, U. f, V# \" k2 c****+ j4 x& j& a2 Q
dtype: object/ K0 h. z3 U" _, b# r. \
' H: w- |3 X6 O% Y9 V
s.str.center(5, '*'). @% d4 m6 J; T+ |) T" D
Out[109]:
4 |3 d5 X6 M" ^7 E0 **a**
! a. P" E# r4 `. e7 i1 **b**, Y6 e0 g( k; V( L! r6 Z! |6 a
2 **c**
; R; b) \0 ?8 wdtype: object, Z6 x# G' h; }' h1 m
! y7 @: q2 b$ H
1
5 F# h0 T- o/ _% Q2
( Q) d7 R4 ?8 K# m2 M7 X1 R35 s0 [- ^. U8 R i4 I& E
42 q- X0 a5 _3 X( y) @
5
. ]8 m( W3 R) K$ m3 g, K6 r( j& f r: j4 Z5 ]
7. f; v% d5 J' z- F. Z& i; N4 w
89 ]$ [; F4 u, ^& S& q
9# y$ X, u9 Q2 A' [8 Q6 ]
10
' B5 K W1 U! ^4 l114 R! H( d7 g7 W5 p; p! P
12
$ f' u' ]5 q/ }1 T" s13
2 B: i4 I$ j( ] q0 F3 t148 t* F3 [9 A H; m
15
0 B# ^: O( ~" n# V% q16
, y: d; s0 n; d171 }* W: s: Y0 j& X J( {) Q! t
18
6 T& C3 \& [+ w4 A19; ^, ^% ^; E! w1 J. d) v
20# }0 O2 M5 m0 v& X4 k7 `
在读取excel文件时,经常会出现数字前补0的需求,例如证券代码读入的时候会把"000007"作为数值7来处理,pandas中除了可以使用上面的左侧填充函数进行操作之外,还可用zfill来实现。: k5 E5 ]2 x# f A$ q3 u$ W
* K$ _6 ~% H9 D! v1 `7 S# k( J
s = pd.Series([7, 155, 303000]).astype('string')
2 ~( Z: t5 R* I6 k
8 F; ]% f( g- C' Cs.str.pad(6,'left','0') j7 ?6 \5 G" H7 |; e
Out[111]:
, h" z6 w. d1 q% B3 C0 000007$ w- o3 N5 l5 {) c
1 000155
' C/ v/ f0 P/ {' k# v2 303000; @) l5 r% q( c, l8 b( Y* ^2 I: _3 Z
dtype: string/ |2 ~! n6 F ~6 E0 t4 ^/ n
7 c% y2 w* |2 {2 H8 ?" L b
s.str.rjust(6,'0')
" l9 f5 v( t; z0 U- EOut[112]: ! |( Z, T# Y5 N7 H2 e& }$ x
0 000007
+ H* C7 ^3 l- y9 \; n8 f5 Z1 000155
$ H$ a$ E5 V, Q' C! o( g/ t2 303000' R' e8 d( ~( h( V5 c
dtype: string
2 I: f0 q) Y; J- W6 h* f+ c2 S2 m% P2 Z
s.str.zfill(6)& B& L, C" {5 H; F
Out[113]: / |$ U: p1 J- e
0 000007
1 R% ]8 t- L5 o9 h) u5 n9 f& @" i1 000155
& l E: `5 O- p+ \3 j2 303000
( E U! v( O9 j: B$ N2 U. ^+ @: ~dtype: string4 x1 x" Y% T# o. l, h
3 X6 M1 r' k/ Y5 O; d1
" c( B0 x$ D7 n! S6 a1 ^! i2
" m$ q. _8 q1 @: h Y- d+ o) }; T3
/ b8 c; I+ L" x% J46 ~9 g% h% k- E, v: B5 }# v9 X
59 S. s4 E% z7 U! O0 ?! f, X0 m/ V
6
( @3 Q% ^ c) E" y7 b74 V# m" |' f) o5 ?$ |
8+ V2 o: O( ]) H, W$ ?4 y# @2 _
99 o. {; Q* b# x9 e& ?' X
10/ i6 m: W. j# a0 D$ w
11
, ^7 S) @$ u5 W& y& \& x0 M* q12" N7 {; Z8 J" R8 z% ^3 e7 C
13
, }, r* x7 f) j# X14
L) r$ ~2 D; N0 H$ g# k8 ~15% w$ ]- {! i8 e% k ]
163 m4 y. p2 |0 F ~6 W$ y
17
& m8 t! \4 a: `9 ?6 H: f18
9 j# s, M9 W& l( K$ c1 d1 Q19
" O0 b# u, @2 g20( R/ Z' V v; H7 T: K8 Z% t
21+ p' G6 k+ [7 C( d4 P. o8 M
221 [: Y! w. ?' {/ E4 ^8 _
8.5 练习: L A/ _: T1 R( [& ]! s2 M$ Y
Ex1:房屋信息数据集; {8 |9 K' L! T U. A" t4 N
现有一份房屋信息数据集如下:' [5 Y Z" Q5 H/ A
: y' x+ U" }; |' }
df = pd.read_excel('../data/house_info.xls', usecols=['floor','year','area','price'])
. I( N2 a' {1 M& ^+ j) Adf.head(3)# A+ f, \4 ]6 z7 Z% h/ g) l y
Out[115]:
' x7 y$ A% ^8 E P U" w9 [8 S, V floor year area price
2 d0 i3 j8 x' w6 `9 j0 T$ t/ V. w+ h0 高层(共6层) 1986年建 58.23㎡ 155万
2 w! x1 U% q" E# E- d+ ~1 中层(共20层) 2020年建 88㎡ 155万9 _2 L$ V& h8 S) u* ]
2 低层(共28层) 2010年建 89.33㎡ 365万0 ~7 y9 p0 |, o& \, a+ V
1
9 H g: \1 S. |* [8 B4 [2% j* L$ o/ `1 W4 @; N3 k; O
33 ~8 o4 q0 G, U- A3 x, E/ ^
4
3 r) e& ?1 h; Q0 w57 b3 a @& n' L% N: Q
6
# w4 m# ]6 ?$ p( b& Q( E4 M7
5 r4 E6 D( M" g" f2 k将year列改为整数年份存储。4 W: N1 X& m8 u# j( _. ~
将floor列替换为Level, Highest两列,其中的元素分别为string类型的层类别(高层、中层、低层)与整数类型的最高层数。& b) M* R* ^5 p o) q
计算房屋每平米的均价avg_price,以***元/平米的格式存储到表中,其中***为整数
; ]7 W' q$ b Z5 }+ ~将year列改为整数年份存储。: a" A& i& D d
"""
P' @6 S" ^8 z整个序列需要先转成Nullable类型的String类型,取出年份,再将年份转为Int64类型。* S! o: d- _+ x1 [
注意,转换的类型是Int64不是int,否则报错。即使astype加参数errors='ignore'跳过缺失值,) Q' X2 O% r1 t! y# B U
转成int后,序列还有缺失值所以,还是变成了object。
* O7 L6 j/ u: ?: A而整个序列转为Int,就还是Int类型,缺失值变成了 pd.NA 。
- r" U* g2 y- A/ {. S% {2 n1 }"""0 `8 r/ Z% o- Q4 B! x: H- M* I
df = df.convert_dtypes()
& Z0 M2 ?# G' k4 v3 \6 odf['year']=df['year'].str.replace('\D','',regex=True).astype('Int64')
8 A. L6 b2 G& s$ M( Zdf.loc[df.year.notna()]['year'].head()
0 t" D ]5 b( @+ f" ]8 E9 O$ Y" Y2 [2 i L/ P. T0 j3 r% ?+ G2 w: o( ~! `7 N
0 1986
5 H1 c! N [( Q( g1 20203 r' i. U& C) ?3 ?- V A
2 20104 R$ z2 [3 h% T- E: W( _% x
3 2014
- t- X1 T( W( X+ ~4 2015
, ^6 \3 g; X/ U8 }* w+ L3 W, i* yName: year, Length: 12850, dtype: Int64
. P* B9 d$ {- x! P. l4 L0 h
, Z) a: U Q9 P% G$ K* w& [7 M1
; u7 m9 b3 C' ~" J2
/ v) C9 F+ w5 d$ y. n- @3
% H* X! ?% a* i2 l" H' j" t3 S4
R8 @( Z+ {/ Z5 g3 t% V& e6 |: f5 {7 I
67 y: r0 }1 h( H' R# Q; n
7
+ e* d; D7 C3 i8 y" c" e/ h8$ b- R1 ~3 w8 x" i( B
9
5 N' \# u+ h$ d- R5 d10
6 O# A# { W8 S) B# K }: m11
% z9 r1 Z K5 ^. Z2 L# x* U/ ~( q12. B) f1 S% m1 K: ?5 v& U
138 d0 Y7 Y: e/ T9 n# W& T
14" \6 |( X8 l3 }8 E
158 o: I5 r( r) P! Z
16* w' |& M- P* \ N0 ^4 v {
参考答案:# N/ R/ ? j) i. c
4 A3 U. O& d c" i1 P
不知道为啥pd.to_numeric(df.year.str[:-2],downcast="integer")类型为float32,不应该是整型么
7 z, v7 l1 w/ o. S2 o! `" h$ s$ I( L1 ]
df.year = pd.to_numeric(df.year.str[:-2]).astype('Int64')
2 w. ?7 M! Z+ {, U7 R+ n: zdf.loc[df.year.notna()]['year']8 G0 k; x# j' R5 D" n! r
1
4 |2 S- H$ A+ O/ {( |8 W1 M2: E1 r _5 N+ M i1 o
将floor列替换为Level, Highest两列,其中的元素分别为string类型的层类别(高层、中层、低层)与整数类型的最高层数。
2 Y" L+ L& q) z% w$ Z0 e4 Q6 @5 Kpat = '(?P<Level>\w+层)(?P<Highest>\(\w+层)'
, o- a* x- n* i) J: O- u, Udf2=df['floor'].str.extract(pat) # 拆分成两列,第二列还是(共6层得形式,所以还的替换一次
( m& L$ r$ F2 v& ]$ M4 i6 D2 i& c0 ?df=pd.concat([df,df2],axis=1).convert_dtypes() # 新增列拼接在后面,再次转为Nullable类型
2 `+ E. E( j) f* l* g9 E- Qdf['Highest']=df['Highest'].str.replace('\D+','',regex=True).astype('Int64')
7 w' O+ d7 @/ X* `! g. Y; Sdf=df[['Level','Highest','year','area','price']]$ f9 S$ Y- e& {, Y3 I
df.head()' U! F* f5 W0 G" M. g2 Y
9 j) c6 W2 N* ~6 ~$ j. n. e. f
Level Highest year area price
- _) b! c( D8 _4 K0 高层 6 1986 58.23㎡ 155万" v. C, C) ?! k& e) Q" Q
1 中层 20 2020 88㎡ 155万5 f) C5 x5 C* Z. ?8 B
2 低层 28 2010 89.33㎡ 365万 K% p5 Z* Y6 z/ ?1 h( T) q
3 低层 20 2014 82㎡ 308万
8 [3 E- j$ j7 `% I# ~4 高层 1 2015 98㎡ 117万 H. @- Y& ]% q+ I% A2 K
18 Z" k* y7 x# l+ Q, O
2$ J8 `+ D7 v" c6 H. y! k O9 z
3
0 B9 \9 E$ J, {" R5 X4
$ ^5 u( z) F. x, L54 h& @5 f6 @% x. L) }
63 [. W+ X& e7 t( V; \
7
5 }9 e0 F g* B( A4 F$ d2 T8 D8. h! |- L' G+ u! u! _
9
: I" {1 t9 j! D% F. d1 f3 s5 J9 m10* ?/ w4 W" I, A( Q' D
11
0 f! _0 R1 G' h* K1 P12
; S- A* H" o: T5 d/ |# J13
. w; y' p( q( r% F! V# 参考答案。感觉是第二个字段加了中文的()可以准备匹配出数字,但是不好直接命令子组了% p0 u4 P/ u0 K2 ]. y
pat = '(\w层)(共(\d+)层)'6 x3 X1 @) A! e* K7 K
new_cols = df.floor.str.extract(pat).rename(7 H! O; ?6 u i+ O# l. ?$ Q
columns={0:'Level', 1:'Highest'})
: f8 ]& z$ z" d# k' [3 [ L" Q" S1 Z& y
df = pd.concat([df.drop(columns=['floor']), new_cols], 1)
3 [9 Z* ^5 D' V1 cdf.head(3)
% {2 [, k5 Q, q. B# _8 A3 _" J% }4 d9 A) ?( a9 [1 c/ U& q
Out[163]: 7 r' |& P/ ?/ x! f: F' J8 x+ X
year area price Level Highest
3 B" A0 q Z. |: p3 N+ i0 1986 58.23㎡ 155万 高层 6$ Y+ `7 `5 [9 J8 T3 _4 r
1 2020 88㎡ 155万 中层 20
% Q6 l/ j$ i/ J5 S' C2 2010 89.33㎡ 365万 低层 28& J1 y5 I) R, s; o. j1 |
1- M* Y1 x& v* i# I* i
2, f9 L" q( r2 {' T+ X+ j
3
9 m. W0 Y: y1 n: Q# @" v4. @, @0 N- H' `5 w2 o8 d% M; A* P+ ?
5% u$ N+ j+ t2 v( e# h
6
0 Q" E c( |8 ~1 o! {7
4 {, ?, |2 F/ m% x2 O/ I) `9 Q8
6 V+ U; h; d# K, b0 j9% R h& }3 V6 B* n, {; \5 e# m
10- ^. L) l5 C f
11
4 J! F8 W& D) n12
5 H$ Y- U/ l: k+ g13+ J: W! Z5 L" Q; f+ M) x" n8 W! ]
计算房屋每平米的均价avg_price,以***元/平米的格式存储到表中,其中***为整数。
" `# f( c( _5 g""". z# n4 m& M) g+ ]' C' b9 N
str.findall返回的结果都是列表,只能用apply取值去掉列表形式9 z* G+ x8 `: a) x
参考答案用pd.to_numeric(df.area.str[:-1])更简洁4 Y) l) R0 ]4 X! R5 Q% `- o' |
由于area和price都没有缺失值,所以可以直接转类型
3 c) x, ^' v; F0 s3 F4 a"""2 u, n# o* [1 }* P5 e& e ?
df['new_area']=df['area'].str.findall(r'\d+.\d+|\d+').apply(lambda x:float(x[0]))+ ]- a, ]: ]+ c( l
df['new_price']=df['price'].str.replace('\D+','',regex=True).astype('int64')0 C$ {9 I; j9 d( O9 m/ u# |3 q
df.eval('avg_price=10000*new_price/new_area',inplace=True)' Q! S2 k. z7 R1 P' i
# 最后均价这一列小数转整型直接用.astype('int')就行,我还准备.apply(lambda x:int(round(x,0))) h3 w$ H2 K- \ B
# 最后数字+元/平米写法更简单: k2 Q& S& s% \8 ]% p/ J/ [ G
df['avg_price']=df['avg_price'].astype('int').astype('string')+'元/平米'& q& Z, z, @$ ~; G( O7 ?
del df['new_area'],df['new_price']6 ]8 Y1 k8 N8 e8 @" a$ ^
df.head()& v/ g# B. Y4 e- \ L4 B/ W# i9 b
* }9 O( d, g9 V7 }& e Level Highest year area price avg_price
: g9 C0 i i) E$ r+ D& g3 O0 高层 6 1986 58.23㎡ 155万 26618元/平米+ ?: S/ t" F$ I8 C
1 中层 20 2020 88㎡ 155万 17613元/平米
! K- D3 R+ }8 |2 低层 28 2010 89.33㎡ 365万 40859元/平米& c- A1 J+ T3 u+ g, ?" P
3 低层 20 2014 82㎡ 308万 37560元/平米
4 u- P. O( R7 k1 P, F& Z% I8 {4 高层 1 2015 98㎡ 117万 11938元/平米$ S( o. O( {$ G, o0 _/ t' v
. R3 l7 D0 c: m: {+ [5 n+ f. P1
/ A8 Y. Y* ~" o0 p4 `: J2% b& F6 Y& Q! T& Z" y2 ~: p9 ]
3
) h: w; Y8 L4 c: u- j( F. {4
S5 {( }5 i0 a: s+ Y" ]5
# |' q7 a$ f/ ], ]+ X H) H0 v7 F% l9 n6# ~9 k6 t1 b& {, H* G7 {1 J
70 N r/ G! V+ e, o/ j1 _# j) @
87 S/ Q5 u. X( r
9) V. k) J$ }6 |; Q( `8 }
10
$ y& m' H y0 z" R% e/ j5 H9 _11
- `- b0 {. g6 y$ z+ Y9 ]+ F. u12+ T& O9 ?% a5 W/ a
13
9 q# s, a4 O4 m8 n5 H144 N: s% O- \/ I% s. J" \* G$ x$ F
15
# l! \9 D* d) }7 @16$ m* g1 Z- @. b6 ^- U
17- ~; D4 r6 {2 p: c
18
7 g- j' i3 `" k: f5 S" s! g19: ?; f/ F$ I. b' w) _/ }2 O5 y. K+ i
20- T# t+ V" Q x; F1 @
# 参考答案1 ~; x1 U$ y# u3 v0 g. b- w
s_area = pd.to_numeric(df.area.str[:-1])
, }% g; s1 p5 T1 D% zs_price = pd.to_numeric(df.price.str[:-1])
, P' i( M/ Z e% Ydf['avg_price'] = ((s_price/s_area)*10000).astype(
, ?' V% Z: q3 y8 E( r% y 'int').astype('string') + '元/平米'
4 d( m# K9 W( X j' ` W0 J- j5 r
2 r4 ~( X7 q' H: ]/ l: Xdf.head(3)8 G. @8 b; F7 R3 B/ t5 P0 c
Out[167]: 9 U) r5 u0 x4 x# Y$ X
year area price Level Highest avg_price
h( ~# {$ s" `% M) K4 C! c5 F0 1986 58.23㎡ 155万 高层 6 26618元/平米
3 w0 z4 f9 J6 t; j1 2020 88㎡ 155万 中层 20 17613元/平米- ~" k7 U w& _- M- b
2 2010 89.33㎡ 365万 低层 28 40859元/平米. ~% |1 @" Q" P( u3 a
1! Q! I& w+ x! z, _0 P7 p* z! d$ G
29 }0 _ ~, q2 K, f
3( g% G) f5 e1 S9 b6 V: K+ B
4& g5 Z, t. ^0 w# F" g g& k
5- K5 T: X. I& n6 l% F8 v/ g
60 y( q5 b* A2 f% y' v) H
79 X: R/ d7 b H0 q
8, A- a* p1 z* Z( X4 h
9. V7 P9 q! |1 u
10: }& ]# Q1 `: K% a; o0 ~
11& T6 S7 D$ C1 w
12
6 V+ i! C3 |7 Z! ?1 zEx2:《权力的游戏》剧本数据集
- a1 g. q) n7 o现有一份权力的游戏剧本数据集如下:
_- D7 v( R$ F. u+ v! s
+ g! ?3 s1 L( E2 Z* R8 Ddf = pd.read_csv('../data/script.csv')
+ Y- g, c$ D2 U d: H9 I8 tdf.head(3)3 E" @! O8 ]; T% z- l* \
. Y9 c* @2 \5 i+ pOut[115]: , X. J* c/ p; f- M* y
Out[117]: & D" g% h7 `; A4 a
Release Date Season Episode Episode Title Name Sentence
" L' t, ~" U6 {4 n* A5 @2 L' N0 2011-04-17 Season 1 Episode 1 Winter is Coming waymar royce What do you expect? They're savages. One lot s...% C' U% P# {3 f6 ?
1 2011-04-17 Season 1 Episode 1 Winter is Coming will I've never seen wildlings do a thing like this...
9 X2 ]8 V0 ^) ?7 Z; C* M2 2011-04-17 Season 1 Episode 1 Winter is Coming waymar royce 3 N. B) t; R: B
1
/ o/ H: Y4 ~: g+ z, M( m: y$ Q J2 @2$ Z+ Q) z: |9 a- y x9 L
3' E2 j- v) C, r# _
4
, O& p5 W; T0 @! h5
* O0 }9 }" N' T# W p/ s$ w6; j d/ [% R: `+ E ?+ M: Q1 U
7
$ a0 H$ j* T, `* Y8
$ Q! g) b& f; u9 z1 w+ C95 g4 x% S5 I; j
计算每一个Episode的台词条数。
& y/ [$ B4 D) v$ G+ |% u9 i以空格为单词的分割符号,请求出单句台词平均单词量最多的前五个人。
3 q2 a9 N4 J1 L3 F若某人的台词中含有问号,那么下一个说台词的人即为回答者。若上一人台词中含有 𝑛 个问号,则认为回答者回答了 𝑛 个问题,请求出回答最多问题的前五个人。
+ f& L; x$ Z( M% ^7 Y, I6 o, D& w% e7 \计算每一个Episode的台词条数。5 E0 E5 D& Z, R+ `; u e, r
df.columns =df.columns.str.strip() # 列名中有空格: E! F# Z) m, e* T9 D* |- t. F2 n
df.groupby(['Season','Episode'])['Sentence'].count().sort_values(ascending=False).head()2 ^; R A& l- W+ k# q+ z( v2 I
6 o* c: o: \8 G8 |- t7 c, W- L3 C
season Episode " n& q# _. A; S3 E; U0 m6 S
Season 7 Episode 5 505
- z, F) H& b( {* B4 ZSeason 3 Episode 2 480
: ?- u# L+ y: P. R+ n# t% HSeason 4 Episode 1 475
& y2 e p R9 G2 I: G% \! o$ f4 WSeason 3 Episode 5 440
3 q1 m' S/ r h5 w# xSeason 2 Episode 2 432; g" q# r5 |5 V
1. ]) d* ~6 M& I: N
2
}4 S* V( W& a4 D3
; P3 S m/ K4 U+ U4 S4
, R( O2 ^* U/ E9 V$ A5' M3 c# p* d1 Y) v
6$ {8 t u% z2 E* U$ l9 W7 R
7! x% o0 m* Y6 u
8. w& M' `, c5 C/ I; B& G% F
98 R7 V2 G t( B: R, |1 z
以空格为单词的分割符号,请求出单句台词平均单词量最多的前五个人。
9 H& ~5 K5 q* e# str.count是可以计算每个字符串被正则匹配了多少次,+1就是单词数8 |, B6 p9 F8 h6 m* \5 l2 b6 P+ ^
df['len_words']=df['Sentence'].str.count(r' ')+1
1 z; q& I* j$ m4 D' `1 hdf.groupby(['Name'])['len_words'].mean().sort_values(ascending=False).head()
* G4 w! d3 C" n$ s, i/ G) v8 Q T1 Y2 I+ V9 j' M$ q' z; e% u
Name' U& \% O$ x; B s6 K. X
male singer 109.0000002 g; d% a/ x/ p& Z! n7 @" \4 O6 Q4 K
slave owner 77.000000. S3 @8 B7 ]: N7 r; `7 ]& _
manderly 62.000000
9 }# d' F; q6 a6 T& f6 V8 D0 Llollys stokeworth 62.000000
; s+ O9 d' e. R9 w8 A: Y9 udothraki matron 56.666667
/ x+ H' E0 `4 J( s1 ]" zName: len_words, dtype: float64
1 o. b% v3 N" Y" l3 _% g1! Y8 t0 R0 y9 T- g- P( q: d
2
% g; V" L$ a- l% P# |3
9 l8 \$ s }4 a: c4/ C$ X2 x7 y4 B) }& W6 x' M. G
53 K9 w6 F: v7 U% A3 n5 ]$ w% ?
6# @. Y. ~7 F8 b" e% I
7
" s9 H* C9 K% e0 G; E8* D% Z, u- z' S. H1 F7 Q
9( c3 {; z. Y4 \- P# |! B+ l$ Z% h
101 B% m4 x; Z) ?, W- [
114 N3 q: x/ f! L4 D
若某人的台词中含有问号,那么下一个说台词的人即为回答者。若上一人台词中含有n nn个问号,则认为回答者回答了n nn个问题,请求出回答最多问题的前五个人。" N% b9 i2 C+ _2 n: M
df['Sentence'].str.count(r'\?') # 计算每人提问数
M+ Q% g: L$ [ j. J1 n+ f7 \ls=pd.concat([pd.Series(0),ls]).reset_index(drop=True)# 首行填0* I/ y! u/ H3 W; x( ?, S
del ls[23911] # 末行删去$ |% M) N# Y( j* l2 y! \2 j* h0 Y
df['len_questions']=ls- [6 C) N1 I N" b
df.groupby(['Name'])['len_questions'].sum().sort_values(ascending=False).head()
$ @' G3 |! ^+ a: R) }5 H
% `; k0 f5 w2 w( f b6 \) |Name
. h4 ~, y* I1 K, E, z; g) `) Etyrion lannister 527
% j( Z# W: d- ? _- W9 Kjon snow 374
; z, X% U m8 Ajaime lannister 283
' N& q0 G" v3 Q6 r. H \$ narya stark 265: F( p% d7 U8 u! E! X" k D
cersei lannister 246, n. S( t: P, P$ ]# N
Name: len_questions, dtype: int64
7 J3 P l2 Y* S; d1 }) e% K
6 Q( r! X4 X- v' J% Q# 参考答案 b' U& D: Y* m/ R7 D
s = pd.Series(df.Sentence.values, index=df.Name.shift(-1))9 S5 ]7 s' E' i5 s/ E p O, A
s.str.count('\?').groupby('Name').sum().sort_values(ascending=False).head()4 _8 G- U! ?% }; y1 R
/ b/ X( B& q' `4 r9 F! @' k
1
- t4 D1 {2 C; p" P2" ^0 e. S4 ~! g) K+ `
3/ e3 A# ]3 B |% {4 ~
49 i2 b9 t% b3 D( t& P
57 o5 @8 h( x. `* _+ O
6
4 J$ F; }4 g( p7' A3 [5 S7 t( b5 {& I" ?
86 @* e! q2 O5 i% g# b
9 v2 \6 `! ~: Q Z
10
1 q0 X: @: ]9 z: c8 d11
+ T5 ^ M" d& Z! b ~6 N) X& a12) Q _0 ^5 o9 o4 ]* v- C
13
% X" ~' e% W% X8 S% g) k14
& k* V' m2 R2 {% d- H |152 t C. M+ E9 w- q( O
165 |# f; ^2 H2 Z% r2 }; [& L
17
" x- @5 Q- H5 F h! F. O% m第九章 分类数据; R/ N9 E* E6 Y9 y' C
import numpy as np* N- U% ~5 O7 D; E' N
import pandas as pd7 K k* o1 A) K: P$ P
1
9 u) y/ S- c& ?2 O- ]; e& A2* m, M, K% ^' t" f: D* B4 @4 r( b
9.1 cat对象- `7 y& I: R$ G
9.1.1 cat对象的属性4 v0 E1 f7 l' a: G3 S1 P
在pandas中提供了category类型,使用户能够处理分类类型的变量,将一个普通序列转换成分类变量可以使用astype方法。
2 O. p' `( ?, a" h" H4 T% w9 G8 A/ {' f6 o' V9 n; T; }0 E9 e
df = pd.read_csv('data/learn_pandas.csv',
9 ?- w) T% S4 v+ N2 ~ usecols = ['Grade', 'Name', 'Gender', 'Height', 'Weight'])
) |5 Y) d) ~) O* z8 ts = df.Grade.astype('category')- g# }0 u R# c4 G
. S3 l/ P# I( I y: M
s.head()3 m) U0 A4 q# H- z$ f0 {
Out[5]:
9 x) y2 c& _3 `. o: Z! D2 l0 Freshman
% D/ z$ N5 Z ^& g0 M2 p# y1 Freshman: A/ T: |* }( u1 t) {' z
2 Senior9 T7 } S8 d; o
3 Sophomore' ]6 ]6 Z+ v& x* o
4 Sophomore
6 h5 c6 \" t3 Z* ~" | u( hName: Grade, dtype: category
# |, B$ G% V2 \6 lCategories (4, object): ['Freshman', 'Junior', 'Senior', 'Sophomore']- T$ [0 x- G y# F% W
1
7 F/ x! b$ X4 d8 b2 L. X2; u* k# v% R2 e! d, a& U) V; T
3( d# s! P1 G& Q; z4 Y
4& ]- Z2 Y) T/ g2 y1 Z. l8 `( ^
59 q2 F- A; u3 H
6
- B$ a% [. h4 Y6 b! ?/ m7
3 U" @: v% |5 i1 y; K3 P% K9 ^5 S; s0 N) ~8# S% I! J R6 f. V. R# H3 T5 Q( j
9" w# s- c+ U# v) E# X) N1 f. ~5 k# a
10& S0 {; N3 x. ?( W
11: C" _5 p- ?9 H* G' c/ }) V% {
12
, s9 L8 f% Z4 u9 E' t13/ T5 n, j9 d* C j* Q1 O
在一个分类类型的Series中定义了cat对象,它和上一章中介绍的str对象类似,定义了一些属性和方法来进行分类类别的操作。
7 d; ?2 a" A6 m2 y9 a. q4 j. m( c+ w- D2 H5 R3 y
s.cat
. q2 A3 D: a- q! IOut[6]: <pandas.core.arrays.categorical.CategoricalAccessor object at 0x000002B7974C20A0>$ Z+ y( m' j8 X1 ] @: |4 X% {
1: G: I) Y- `/ K4 W, i" y
2
7 I! J4 ^- n0 U8 {' w/ ?cat的属性:
% Z4 g/ C& _0 j# _9 q6 l0 I' o' ~1 m5 ^+ w y1 [8 u* K
cat.categories:查看类别的本身,它以Index类型存储
: {' _3 y5 D5 M1 ^0 M* `7 Z5 Dcat.ordered:类别是否有序
/ E( L3 h# l) M% h; Ecat.codes:访问类别编号。每一个序列的类别会被赋予唯一的整数编号,它们的编号取决于cat.categories中的顺序& x w. ?6 E$ z! ?8 j! Q% T& [
s.cat.categories
. z& Z) _1 V3 p# q; a7 QOut[7]: Index(['Freshman', 'Junior', 'Senior', 'Sophomore'], dtype='object')
# {6 e1 z9 C) d/ ^/ A& m) G" t' P n+ U" Z" i' C
s.cat.ordered! A8 |7 F9 s" J8 R; s+ c
Out[8]: False
) S# W! d* p6 ^% {$ v5 E
- V2 O$ A7 R5 ?9 T! ms.cat.codes.head()
8 ?3 }" l. f* x: V4 K2 G' |Out[9]:
5 P) r1 J; x7 N- ]" ^" D8 \' u0 0: t, ~) e1 I( U6 Y
1 0' ^4 y, o' Z) |- L0 @/ w
2 2
& V8 d1 T/ V; n' h, F6 i: N9 q3 3" k0 ?5 E, S8 {
4 3
$ H" R2 @" m1 G: X' t9 Sdtype: int89 z% g& X7 D1 F
1% L& v0 s" D! w3 g
2
. s' ?; `, \7 v+ X) ^38 A# I7 |' j2 l, B6 ?' L+ [$ k( o
4 f4 F0 [; P2 `5 y
5
( ~0 p* M( R# z/ i/ `& e6
0 a4 }' Q. P" X7 O& B0 `7 o7
8 v1 J7 Y" {: y2 e9 L9 P8 j8, P/ o# C( g; Y' T, F z
9
# E2 R2 B. g8 J- Q8 D j- I2 S( B3 N10. T5 Q, Z- @7 v; \5 ?$ x
114 K3 s3 x: E% `$ X# k5 m. s
12. {5 I* M2 W( \8 [- i1 W, U/ E. j
131 }" A7 B( u9 |* O9 s" V
143 @' B* l L; G* \
9.1.2 类别的增加、删除和修改0 F5 w1 ]3 r, h' y
通过cat对象的categories属性能够完成对类别的查询,那么应该如何进行“增改查删”的其他三个操作呢?6 e$ N0 |2 ]# f0 s( t
; f% {4 P8 n" Y& Y: j0 i9 f
【NOTE】类别不得直接修改/ v9 I- ^! j, \" M5 V
在第三章中曾提到,索引 Index 类型是无法用 index_obj[0] = item 来修改的,而 categories 被存储在 Index 中,因此 pandas 在 cat 属性上定义了若干方法来达到相同的目的。( q; h/ z1 f) j5 E. D7 V7 h) m2 b
8 h$ D+ Y: Q; U$ `7 J4 w& _. F. zadd_categories:增加类别
+ H3 a9 ]3 C" B1 K4 Bs = s.cat.add_categories('Graduate') # 增加一个毕业生类别
t6 x5 N4 o9 i9 o! ^s.cat.categories0 J9 d" e( G, A/ n ^
; q7 e' d1 g2 ^# Z4 MIndex(['Freshman', 'Junior', 'Senior', 'Sophomore', 'Graduate'], dtype='object')
$ A0 k4 ^3 k# Z' z: T2 h1* F* r7 Y, M& v) J) K1 ~, M
2
( s( ]. m9 H" `3
& w; p- R7 `4 Q7 R$ b# O4 |7 O40 I P3 w0 x9 L$ w9 p# i- Z
remove_categories:删除类别。同时所有原来序列中的该类会被设置为缺失。! p8 N- D# x+ H3 `7 f8 u. o- P
s = s.cat.remove_categories('Freshman')
: F& v6 G5 |5 \" z A( v. ]2 M' g# l: u2 G+ S0 C9 l
s.cat.categories
7 Q' R8 R! r0 Z- ^+ P. |6 pOut[13]: Index(['Junior', 'Senior', 'Sophomore', 'Graduate'], dtype='object')
v+ Y4 x; M+ t
3 F U& A# ?) O$ `' z% F9 js.head()& d) @# I% |) N" l
Out[14]:
, `% s$ c0 T/ C0 `0 NaN
! v5 \, |" [8 d0 J6 X. J5 Z. n/ q1 NaN
7 e! {! \+ |/ Q2 Senior- o, J$ k/ B' n8 f3 o/ O) }9 I& y
3 Sophomore
* w8 U- O* x- U+ ^9 R4 Sophomore$ b4 U0 B/ U4 w% l- G ^
Name: Grade, dtype: category
, u: v# X. c: s3 i% I+ q% w( HCategories (4, object): ['Junior', 'Senior', 'Sophomore', 'Graduate']
- o. H- z2 w- V; l1
# z/ X+ A) v2 v0 _# P c24 V/ o* f! _4 \6 w8 r, L) ?. {
3
* {; a' U; [' \42 W/ m' m' Z: v
5/ R4 \* Q/ d8 m( z) X
6
) M# v! O: E; w0 y! h# D7
( T) M: r. W) h h6 `8
$ r& I/ T8 U" z1 e% Q2 R* ]9
1 a2 d' s A1 X0 U& t0 W* u! [7 m10
: m4 p6 }$ i' ?11$ b$ h9 b0 E4 K* t' E
12
& Q* A+ Q. L2 D: f V) f7 r130 |7 w. m- |0 j
14
" \ B6 d1 T( j! H. Wset_categories:直接设置序列的新类别,原来的类别中如果存在元素不属于新类别,那么会被设置为缺失。相当于索引重设。" D2 z2 U- f* Z5 Z6 ^2 g
s = s.cat.set_categories(['Sophomore','PhD']) # 新类别为大二学生和博士, `4 Y8 @5 ~5 B: @" R2 }: m2 H8 ^
s.cat.categories
0 Z4 q! T9 I: @Out[16]: Index(['Sophomore', 'PhD'], dtype='object')- M- y. y. x! c7 i9 A2 u6 I
% A. g' T# i3 A2 O% M2 ?s.head()) l% B& S1 S" z7 P) ?3 U8 k
Out[17]:
% M; a9 D* |3 \/ r) K; ^# R. S0 NaN% E$ c: E* x7 ?( e4 |- C
1 NaN
+ k6 {+ g/ w: w$ u. c4 _; g2 NaN
0 N4 y& _) L9 P/ l* s* S; T) y3 Sophomore
2 d4 B3 }% C$ U- q+ R4 Sophomore* C4 y+ N; ^; K2 W! ^2 \
Name: Grade, dtype: category( \3 \1 J3 l4 h j8 y
Categories (2, object): ['Sophomore', 'PhD']
) ]7 y; H* U4 T" h+ f) P. |1
3 Y- R) L/ b: n% `2* Q1 ]6 g) T$ j6 v" n
3
/ B/ m6 c* E5 m! H' Z4) _5 I1 ~3 `6 X( l
5" d [. V a h2 b9 V; @2 @. j3 T
6
0 y \ ?# ^1 E7: a' b6 r' m: p* G5 Y
8
3 R( f% Z- Y3 r" ?' V5 u+ z# Q9
1 |0 c3 r; ]/ b103 Y0 `# X9 h' u
11& I. q* T6 @* I J, F
12( ^7 E# U: I) Y7 H
13) q$ a3 `/ B7 v5 _6 j* \; p
remove_unused_categories:删除未出现在序列中的类别
: u" U! Z; t) L4 T9 us = s.cat.remove_unused_categories() # 移除了未出现的博士生类别1 ?* H2 W9 B" o9 x" }* A; l
s.cat.categories, h7 K! U: l0 K
( _, w% y7 r( _% j& r' ~
Index(['Sophomore'], dtype='object'), O. W- R6 D$ W9 k9 k6 v
1
, L' P3 z- B" ~3 x, a2
3 G+ ]) J: y5 l {. m5 @5 ~3
# ^8 _: g# q% }6 [+ P- ~: ^4
, o% w2 E. n$ O. yrename_categories:修改序列的类别。注意,这个方法会对原序列的对应值也进行相应修改。例如,现在把Sophomore改成中文的本科二年级学生:" r" M: P) }- V4 w
s = s.cat.rename_categories({'Sophomore':'本科二年级学生'}), j6 u, I# B+ `8 I7 g$ I
s.head()
* T$ W" |; j6 S
: \( }9 M ]4 H; R1 \0 NaN
& }+ o: }8 Q1 ?& f+ i1 NaN, [& {* L7 H) c }7 ]" z' n! N) O
2 NaN
( Q+ X1 ]9 I0 N8 @. ?: a- @5 c3 本科二年级学生/ b" d3 A" T" B* _0 a
4 本科二年级学生 t; K0 h( I( }. u9 h7 C: \4 f
Name: Grade, dtype: category# ^5 Q. B6 C& d% r! S; X+ w8 d& @
Categories (1, object): ['本科二年级学生']8 ^5 s) y' u& ^, M
10 L% \( _+ f: j
2
O. z/ a8 V6 P4 I& N3. l+ _, }% J0 D1 O6 @3 {, r
4, Z) Y& }6 }9 Q* |4 C% F; b5 a
5; ~$ ~1 a8 u- S% `, f$ w
6, R/ F/ y/ N7 X: g- C% |
7) c) g/ ^$ g+ {+ E0 z
8
9 [9 h, X6 X' ~# X% u9
8 L9 }1 h% D3 A10
+ y! O, P" m8 o9.2 有序分类 ~. l- `1 R D( R/ g; p! f
9.2.1 序的建立% ^0 c. M$ P4 Z+ V4 g7 y
有序类别和无序类别可以通过as_unordered和reorder_categories互相转化。reorder_categories传入的参数必须是由当前序列的无序类别构成的列表,不能够新增或减少原先的类别,且必须指定参数ordered=True,否则方法无效。例如,对年级高低进行相对大小的类别划分,然后再恢复无序状态:3 U; n' ~& Y7 z5 I# K7 b V
2 L4 w& S7 x+ M/ b: l: m! Ms = df.Grade.astype('category')) ]& T0 l/ n+ H6 I( ?/ G
s = s.cat.reorder_categories(['Freshman', 'Sophomore',
4 y/ {" B5 y4 A8 E 'Junior', 'Senior'],ordered=True)- [# M, |& a( D4 W7 b6 y
s.head()9 t% {9 J$ [4 G V3 B
Out[24]:
, D; k9 z% U$ w% r0 b0 Freshman
& Z, R3 D: f D) i n3 e1 Freshman" Y: S# B5 P5 z" P a, }/ P
2 Senior$ \2 i( V# l9 n6 ^3 H( M
3 Sophomore3 q* @% a! Y. ~, J- b/ N/ W
4 Sophomore% W$ V- p$ K. {) j0 U, Z
Name: Grade, dtype: category
+ M- ~- ]* D' @: M. C% A, p" OCategories (4, object): ['Freshman' < 'Sophomore' < 'Junior' < 'Senior']( ?5 J2 J# c3 L9 f2 T
/ s% N: s9 ~1 V, e, l( Cs.cat.as_unordered().head()
- h7 R6 ~9 t; F& ^* C( @; {Out[25]:
& ~7 V; _5 I: }5 h1 i/ ~$ Q0 Freshman+ C: D6 F# F M* G
1 Freshman
" y8 Y4 I; Z4 Y2 Senior& ~0 n" d7 i5 a$ W2 G' R& e
3 Sophomore
" C0 ~1 K3 r0 \4 Sophomore
9 ~" u& w! R7 Z1 V% y+ P0 S2 MName: Grade, dtype: category
9 s1 g1 b: S3 S( @Categories (4, object): ['Freshman', 'Sophomore', 'Junior', 'Senior']
1 C" w; K7 M- |, G4 C$ P% p
" T4 b6 o- Q7 @1 |1
1 f) W8 g" h! Y" d @# n26 y Y" I$ G$ ~ U" ~$ G
38 W7 r1 k6 O2 X6 q( b( m
4
$ J6 K& y1 `: T5
: Z" s5 D4 K/ C v. ?69 ^2 ^7 w- E8 u( X& |
77 e- s8 y* D$ U& X' N
8) \7 S8 C( o' Z& C0 R8 k/ V8 v
9
; u& F8 r7 S' y& a7 q$ q. y6 k10
: n) |* u' I$ K7 W3 B11
) `$ O- q5 V# k3 q1 k f124 s' |& N: d% z, |* N
138 g, W {' W. L
14# G% n- e) `' J# A: _9 {
15; R) ^+ w; G' n8 C
16
6 S7 A( O L2 [5 O6 q' z17$ y. R: P0 m u- Q, L l: [3 ?% [
18
4 H+ M0 {$ R; q: ]2 R19& i- n- R3 P" h
20
; M! x/ y s" ^' u3 i21
: [" v# _) y7 n. L( O( A22; S, Y7 l' `! s, j& s4 r' S
如果不想指定ordered=True参数,那么可以先用s.cat.as_ordered()转化为有序类别,再利用reorder_categories进行具体的相对大小调整。6 [8 ^5 i9 m5 N! Q5 c% U5 {" O
3 w7 _& Q, I3 F2 d
9.2.2 排序和比较
9 U* Z0 D: X( C& r在第二章中,曾提到了字符串和数值类型序列的排序。前者按照字母顺序排序,后者按照数值大小排序。
9 U- N% T% a" ]8 \
4 @4 U1 ~2 l, ` 分类变量排序,只需把列的类型修改为category后,再赋予相应的大小关系,就能正常地使用sort_index和sort_values。例如,对年级进行排序:7 C( b: A3 n( [. b. Q
8 k$ C( B# d6 t4 N% y
df.Grade = df.Grade.astype('category'); e! T- N9 ?$ ~
df.Grade = df.Grade.cat.reorder_categories(['Freshman', 'Sophomore', 'Junior', 'Senior'],ordered=True)
' ]$ r1 H8 c/ Y* I6 I( s1 Ndf.sort_values('Grade').head() # 值排序
& G0 U u2 P! t; S( U6 GOut[28]: r# \3 z( N0 x* a- s' k1 O
Grade Name Gender Height Weight8 D. i; s4 K' t, `
0 Freshman Gaopeng Yang Female 158.9 46.0
5 M# w6 A/ i9 K105 Freshman Qiang Shi Female 164.5 52.0- B/ Q1 P* \- f( t
96 Freshman Changmei Feng Female 163.8 56.0
: R$ i" a: b/ p+ h88 Freshman Xiaopeng Han Female 164.1 53.0; ]9 Y6 f8 H: Q6 ~0 e% g9 o4 f
81 Freshman Yanli Zhang Female 165.1 52.03 Z* C' x$ c/ x4 b# c0 R" }+ s
% y, P% T- C7 X
df.set_index('Grade').sort_index().head() # 索引排序
) B9 o z" B% y, J7 DOut[29]:
" T+ ?0 t$ R# X; U; Q2 @' V( H Name Gender Height Weight
) Z O( T, u: }$ _, |' ~" gGrade
% M6 o5 d' x( M, ~5 qFreshman Gaopeng Yang Female 158.9 46.0
! l+ H) e, A, a6 X7 bFreshman Qiang Shi Female 164.5 52.0" V j; F4 q2 A8 w+ y
Freshman Changmei Feng Female 163.8 56.00 S" o! V7 P+ _; U$ T
Freshman Xiaopeng Han Female 164.1 53.0& Z5 s0 [& P1 D+ t C$ r+ w; z5 z- g
Freshman Yanli Zhang Female 165.1 52.0
3 P! f2 Z) z) [( x0 A. g; l2 R0 N. L! T( B+ o
1
8 h) M8 q/ ^: s8 _2 E5 o1 H- L6 e2
# G1 k9 ?9 s" H( T6 f3# Z* B1 Y; J7 _# k* A) C" q9 q4 H
4
L5 P) `4 `! J4 A/ H5
5 o g' A* H! _/ D2 k6
% D2 F! d" }7 {' \3 F7
( I0 j" V e4 A* W# W5 H8
2 G& n' O, h2 e' \& T7 s' Q9
7 ^) {- `* n% {$ m0 l& `7 [10' j0 m* n i: y/ _9 ^. u! p, L0 \
11
0 m4 R, X$ F4 `" Q! C; F12
$ w# A1 t8 [5 f. h13
/ F! v" n( F3 C# j$ |, z14' J1 A3 ?, h8 Q* p$ j! V7 u$ O
150 W, n2 H3 e9 P
16
: k# Y+ `$ }: ^5 I, p) ~' Y17
7 t6 e s. Q- j) Y18
8 c5 r$ s J" I, v$ G19
: \2 N4 q! L/ N20
* u: X* @3 h# q: h' K' A 由于序的建立,因此就可以进行比较操作,方便后续索引操作。分类变量的比较操作分为两类:
8 F7 h" H; g' t1 X2 o/ G8 `- E* `
7 e8 v4 ]' o9 {2 M1 W5 k5 ^==或!=关系的比较,比较的对象可以是标量或者同长度的Series(或list)。(无序时也可以比较)
) O; W3 I# y' m3 G$ |" N6 p>,>=,<,<=四类大小关系的比较,比较的对象和第一种类似,但是所有参与比较的元素必须属于原序列的categories,同时要和原序列具有相同的索引。0 W- T5 `" M2 a2 w) A5 D" t4 l3 W. z
res1 = df.Grade == 'Sophomore'4 I% ~6 o# V+ b( c8 W1 w2 F
) |% R6 F) m# C, Rres1.head()
: Q* I/ V ~1 B8 ~Out[31]:
7 r& C7 g$ s7 z( L$ @0 False5 Z' @% y8 Y0 f( O/ `
1 False2 w: _) Z0 `* X) D
2 False
6 q# |8 ?6 {) W! I2 @) s. S) r3 True" w0 L5 \, h6 x5 t
4 True
" V& j+ m6 O2 }2 d$ K2 ~Name: Grade, dtype: bool, k9 E \; }# `9 D
5 q$ {5 }& F. l4 l! o- w7 q
res2 = df.Grade == ['PhD']*df.shape[0]
& z% V$ [$ b. p, ^; X! f; r% i6 _9 j' B. K, T7 n
res2.head() c% b) u& l5 ]" M2 b
Out[33]: 3 T+ i" H7 N9 t% n( _
0 False
2 n! m6 n& |! }1 S7 }! H1 False2 y1 s8 l; z5 y: U: Q- I& w
2 False
# [& ]% I7 l9 T" S8 t3 False
$ I# n/ U2 S; {5 d0 g4 False
. `5 ]' ^- l' l; C% n! a: bName: Grade, dtype: bool
& C) }# o( [& W$ u: j! \/ K& }' s: E' x
res3 = df.Grade <= 'Sophomore'
# Y0 A& c; z% \
" Q$ N0 ^( f( j+ q, \& n, ^res3.head()
U6 k; g6 J& e+ o7 }' UOut[35]: & a3 g7 i% G+ G8 x: ?( `6 o/ }
0 True
2 {% l, B0 {2 h+ M2 A- h- x& N1 True
7 `3 J# p9 C# O5 `- ]# [2 False
$ n% O& J: N+ e1 [4 Y3 True
% N. u/ M f4 `) Z) R+ q4 True- N' n' f; b! s5 I1 }
Name: Grade, dtype: bool
7 B& r! _9 e* P8 s$ S5 k) S( o
/ y/ L& @0 U6 w% f! k; {( o# sample(frac=1)表示将序列随机打乱。打乱之后索引也是乱序的,直接比较会出错,必须重置索引。2 @7 a$ e7 L/ h
res4 = df.Grade <= df.Grade.sample(frac=1).reset_index(drop=True) * t N* C) p# g/ d# W
9 k: C, ^3 h' c n/ k: Wres4.head()
4 a( f/ m0 Y+ \6 p hOut[37]:
M9 h5 O. _5 q4 _; Q* U0 True
- j* U9 D2 h2 y5 u+ m. } j1 True
" ?2 \, D \' r, ?: b2 k* h7 Y2 False) s5 r5 M. z4 t3 ], P% A
3 True8 Z! g/ ]( J0 W
4 True
" D* q! H' I* r+ x" [+ QName: Grade, dtype: bool0 o/ S( S- O0 K7 Z
/ A" [5 b: Z6 ?- C' p' i4 k% d1' @5 `# L8 \- E/ F ?
2: P2 [6 W4 d# d, `$ X
3
/ e. y" \1 {. [ i9 e1 H4% T- }. Z$ P9 [: U
5
& [3 Q; }! q6 |# F9 J3 m64 Z+ g% c% X! e! i4 R ?- z+ q
79 J: D; R x6 E) {/ Z
8( z& P6 ~4 _* h [, e$ j
93 h3 f& _$ m( L- m4 C2 e+ ^# l4 R+ H
10
6 s5 o/ I9 j/ d( [/ o11
5 ^; i- U" @2 r9 J6 N7 b! y12) G/ i( o. V5 d# f
13# M& ]) P/ v# w. g
14: Q! |/ ]4 X: `' a. Y
15* U" i/ v# Y: y$ r( M% S& B! _
16
/ Q; B! A) P6 Y0 L2 p17# m6 \3 u2 [0 r" |& U5 o* P
188 b a& `. N! ^5 C3 C
19, @2 a) _) I' \. G. _
20
, ]8 r1 a/ ^- n21' } b; F& j5 F3 S
221 h. r% W$ K4 @
23: _4 R* I) _% l* i4 {# w
24
; Y n/ i, F0 f25
1 s0 _0 M$ |: C7 d6 w: d0 R: ^5 u v26$ p. ?6 t& j; k$ W$ }4 o: h
27 g, m3 h; f8 @+ M. ]/ |
28+ N3 C l" N( p) T3 z# {% P
29" L4 R# T: K6 f9 l' a/ ]# P
30
$ t) Q( v) o0 m31( W u f/ z1 Q& U& H( k/ x
32
" U! G+ H3 ^; z$ ?33
( z. |5 H9 x0 ]' t9 A e m341 J. e1 ]) n# E: v8 w
35
: j. |0 p- k: `+ g# e363 G( n. k0 Q$ L8 \1 v, Y( B- R K
37/ J! B# X, b8 x7 [' H( g" `% P5 N( m. T
381 D. h7 f7 u$ X8 q- H$ H
39
0 c6 J+ a5 S- ?3 ?: K40
+ z3 ~3 m. P& Y. J' A* ^! m418 m3 \1 ?* r+ w
42' f4 C4 ^. B% l9 s" {2 y0 w( X, o# u
43
, u* k# c- A2 l$ }# p448 y2 C$ k1 H! v; @' x6 Q
9.3 区间类别3 w0 @& R- n% Z9 e, P4 e) g( i9 }
9.3.1 利用cut和qcut进行区间构造
& b4 G5 `% L& x% ~ 区间是一种特殊的类别,在实际数据分析中,区间序列往往是通过cut和qcut方法进行构造的,这两个函数能够把原序列的数值特征进行装箱,即用区间位置来代替原来的具体数值。
3 c" k+ j. b7 ~# U6 b6 h" L. z
1 B5 c1 D+ B8 W; p, Zcut函数常用参数有: z, C# b/ T4 e, k7 M( ]5 f! L y
bins:最重要的参数。; |8 {- m/ D" r, d. I) A/ m) h
如果传入整数n,则表示把整个传入数组按照最大和最小值等间距地分为n段。默认right=True,即区间是左开右闭,需要在调整时把最小值包含进去。(在pandas中的解决方案是在值最小的区间左端点再减去0.001*(max-min)。)
: @1 v/ p8 T0 s7 t& g也可以传入列表,表示按指定区间分割点分割。" t* h0 h9 O4 T; W: w: T' m+ N
如果对序列[1,2]划分为2个箱子时,第一个箱子的范围(0.999,1.5],第二个箱子的范围是(1.5,2]。" H4 j0 T* l6 U' l; U% B
如果需要指定区间为左闭右开,需要把right参数设置为False,相应的区间调整方法是在值最大的区间右端点再加上0.001*(max-min)。
3 @9 O) K8 @) m- S& M
! y8 U+ z8 s, n- Ss = pd.Series([1,2])
0 C c* M# R0 |4 E/ t# bin传入整数
2 [! @* o$ A# Z' F9 X. t' e- }9 \' W/ r/ L
pd.cut(s, bins=2)
9 _" m$ W- I! Q5 J: z9 AOut[39]:
, a7 S* g% ] a+ W' {) H( L0 (0.999, 1.5]% B) T6 e8 M8 ^. {6 n2 P8 c
1 (1.5, 2.0]
$ ^2 X$ ?0 J5 V8 V- i5 ~dtype: category
, d+ Y" A. k5 f8 dCategories (2, interval[float64]): [(0.999, 1.5] < (1.5, 2.0]]/ q4 V& a4 g4 _4 x7 b$ @7 ?9 Y; o
* Y/ i, {8 i3 l0 `2 I6 Tpd.cut(s, bins=2, right=False)/ a) O( v X4 a9 ` ^
Out[40]: + d2 W8 u, x. N& o0 ^' P. e
0 [1.0, 1.5)
( P! l7 v2 R$ o+ d; ?1 [1.5, 2.001)/ X6 c1 @ u6 u% e
dtype: category$ }( s) r# w) Z$ @
Categories (2, interval[float64]): [[1.0, 1.5) < [1.5, 2.001)]
" o; J1 A4 s% Y! v& t4 r9 z' \
$ K- B+ o5 N f; D% i9 Y; y1 _: l
# bin传入分割点列表(使用`np.infty`可以表示无穷大):
- \' R3 M" f" _4 v. X) }' ipd.cut(s, bins=[-np.infty, 1.2, 1.8, 2.2, np.infty])
6 Q6 ]% y g) t" E( [# _+ TOut[41]: 0 I1 C$ A2 w. p* B, ^- m& \
0 (-inf, 1.2]
, U( T8 L4 l8 J& H1 (1.8, 2.2]
1 C! L, ^( u% H7 rdtype: category
V+ o% D. u/ Q: L" w3 r8 \; G& J8 XCategories (4, interval[float64]): [(-inf, 1.2] < (1.2, 1.8] < (1.8, 2.2] < (2.2, inf]]0 o! w5 ~- V9 n( Q* r0 _
9 r7 o" ]0 q1 C3 J Q1
+ }; z4 ^" J' ]* D3 B: Y2
1 @* C& U. V' R% I5 a5 A3
0 {' _# i) Z+ H4- V: I3 B4 |" ]' z. f9 e( t
5
$ h" s% g% F7 d6
# w# L4 G5 U7 U2 d {1 Q7
$ `+ T: _2 ]& Y5 @8
8 v; {; r( q1 g8 q4 o( F* I9 N9* E8 ]& r" ?6 R E. |7 s$ ]
10
: B( ]! n/ ?5 H/ U$ b11
7 L, Q# D3 a' D125 B' {, k: [$ E' k, @ n
13
% V3 k# ^+ O( b4 Z/ {0 T4 W$ G* T14) t/ l- j: r* t+ c& [9 C. _
154 R$ l/ {5 D* A1 u: N9 V" ~7 d
16( M A. w4 [% h Y" ?
17
2 Y8 h, `: X6 m- A/ c$ D% }; V18
2 T* V0 _4 X/ w. [5 k( A* _199 ?; c3 `* Y! @+ R2 V. z3 |
20; N; C/ `. s/ `- S' [% z7 `
21& w7 z4 u! \" x) w8 j! w
22% y9 B6 l6 M: i3 D1 [
23
/ m9 S; [8 R* E! |# X' ~) J0 f. X. L2 i+ F24* e+ V, [% V' J7 y/ C4 A% f" {6 M, u
254 N. a; k- o% N
labels:区间的名字
4 u$ u T! ^) p5 w) n. ~# c! \- cretbins:是否返回分割点(默认不返回)
! y5 f, t3 t m& \3 S$ m# g默认retbins=Flase时,返回每个元素所属区间的列表
4 ]; o- a9 {% ?) G, v8 Kretbins=True时,返回的是元组,两个元素分别是元素所属区间和分割点。所属区间可再次用索引取值
1 M. _0 ]1 K: J2 ~/ R0 J8 v, Q1 p* V7 W7 Y2 z+ P- h
s = df.Weight9 C# G& r$ X2 E) r8 @# e! z) G
res = pd.cut(s, bins=3, labels=['small', 'mid','big'],retbins=True)/ y% C( S6 _; Z1 H/ G* O; N2 w! o3 {
res[0][:2]+ u" M, C5 c# \4 W! h5 O- D1 O
/ d) W& x8 x0 B& z! e
Out[44]: * }; l6 i( x- O6 K# w
0 small
: @* Y) y% l7 |! C, w9 x: r1 big/ t1 i f2 U0 w% X: \9 z! w8 q
dtype: category/ }) D- n, a2 t8 I8 L3 ?/ P
Categories (2, object): ['small' < 'big']
& a/ M' |6 x* I. X" V8 r# O% k+ D5 l) P
res[1] # 该元素为返回的分割点7 ]; L7 g ]( G% M E9 f5 U) A
Out[45]: array([0.999, 1.5 , 2. ])5 g v# N7 h- v4 I! ^- h+ M
1" s3 J2 }% L1 T `
2
+ b; \# l2 D. c% h2 `3 Y3
: U* G8 E0 D3 i4
}( [0 E" z( f3 T) v50 x& a' d9 _$ o+ T1 G
6
. i' B& ]. f. x. ]3 C- |$ k7
+ Z" m/ @5 R- \+ e) C' d$ y8
[% z, q B R0 d5 b# I( F9
/ g7 X$ |! H+ }- I1 w7 N+ B" M10/ ?6 ^7 O2 j- l+ e v& z1 ?( B ~
11
5 a* e( w+ t/ u; o12/ m" ?& n1 b7 A- B; _! B4 T
qcut函数。其用法cut几乎没有差别,只是把bins参数变成q参数(quantile)。
6 _' Y1 `. t$ Vq为整数n时,指按照n等分位数把数据分箱
; Z$ p* q8 ]% z" ?: L" uq为浮点列表时,表示相应的分位数分割点。. m4 m* n1 g' T6 Y( b% t
s = df.Weight; D3 D d! s8 j' S& y' S9 x
8 h5 J* f: p9 G: ^, V- m$ ppd.qcut(s, q=3).head()
/ J8 a$ o. C: v n9 nOut[47]:
v% W' Y8 A- X! w0 (33.999, 48.0]
! e Z4 Y$ h1 q" o4 s0 X0 n1 (55.0, 89.0]
9 e& B, @9 S' [' D+ T- F2 (55.0, 89.0]
, K4 O/ A! ?" ~/ `3 (33.999, 48.0]; A) M6 O$ W. o2 z
4 (55.0, 89.0]
6 ^' F: L# C5 T" O- V1 |" yName: Weight, dtype: category
7 T6 _0 m! V' u: d) TCategories (3, interval[float64]): [(33.999, 48.0] < (48.0, 55.0] < (55.0, 89.0]] W: v" d6 i; W2 B( X
/ `( h4 m9 K% S+ Zpd.qcut(s, q=[0,0.2,0.8,1]).head()
! ]# R% w/ R8 a- K0 ?1 {% z. s) a( h/ HOut[48]:
2 e. T- V$ m, ~- P0 x6 j# c0 (44.0, 69.4]
# n) u/ M6 A, a" V5 P3 y* M- V! b1 (69.4, 89.0]
; ?, Z: K& k8 Q# k- `2 (69.4, 89.0]
0 }6 f- Z9 T% s0 U4 ?3 (33.999, 44.0]$ i# X- Q1 m) U6 J& G
4 (69.4, 89.0]
# s7 r6 K% a( s* S% L& N' DName: Weight, dtype: category7 ?( n2 Q0 `8 j0 O1 J9 i
Categories (3, interval[float64]): [(33.999, 44.0] < (44.0, 69.4] < (69.4, 89.0]]
& |" S' K- `4 _* p2 n
; D$ d- s; I4 k% G5 f& x13 a0 M7 Y& S& F3 y6 g, X
2
! L v2 u. j& ]) d* D4 t3
0 x3 b5 e( J! E9 }43 c) d o% V8 A3 |/ w1 ^& T
57 K& C( |4 u+ t
6, L$ _; ^3 X% ?
70 V2 D* @, Q1 Q) g5 ~, c
81 o* x/ V" Z( P! Y4 o
9
* g# R& F/ R1 d8 O/ Q) _2 Y10( l) A `8 v$ m: X
110 v) Q) [" `( H" t
122 E5 j/ D9 e: E5 l4 [
13
" f3 g9 P7 E/ y! W14
+ c" T j# r$ d$ a' b- C! U15+ W4 C( e* E* k
16
. w4 A- y1 d5 K2 u5 Z/ Q17
- ~, d. A3 R8 x, V/ ]18
! a+ ^3 T. @2 |0 f q2 D195 T" Z( S' s$ b1 S* c
20" n* |0 }; |" H) ] L
21: ?! ]1 E' X" H8 E. ? J
9.3.2 一般区间的构造: f7 v+ Y, j9 X6 a) Z* b5 ?! F
pandas的单个区间用Interval表示,对于某一个具体的区间而言,其具备三个要素,即左端点、右端点和端点的开闭状态。' E, P# \6 S* E, j3 e3 c
! K; P" a$ I" J! @ U7 k. M
开闭状态:包含四种,即right(左开右闭), left(左闭右开), both(两边都闭), neither(两边都开)。; z; F. V. A1 n# J2 t8 k$ r: w8 C
my_interval = pd.Interval(0, 1, 'right')
/ d( n# R. d; P$ Q; m2 ~- u
) ] b& \9 F! w' D* Cmy_interval
9 w; S" A5 w. X( M7 @. UOut[50]: Interval(0, 1, closed='right'). Q$ m, O3 n3 z: }' n0 w
1& x+ Y8 R2 l- L; J4 |7 N# I$ U7 z
27 g5 H3 H$ |/ [" k) L8 v/ {
3
) A6 x2 o$ J9 x @% o6 U( _4
6 w4 p5 I' ]6 P* a- l( M区间属性:包含left,mid,right,length,closed,,分别表示左中右端点、长度和开闭状态。8 `7 Q& I& `! S$ H
使用in可以判断元素是否属于区间
/ Z! h+ g# i* u用overlaps可以判断两个区间是否有交集:1 t7 k' P7 N& J, F# [
0.5 in my_interval0 Y4 x3 ^9 P6 Y- q) M( O: ~
% x- h, O3 N! H/ x. U0 NTrue
. ?- _. I8 f4 Z1 `10 T4 q: T) O0 n ]1 F& ?' ^) y5 q
24 _9 M# s6 z. A& i& x" H3 h1 |% [' O
39 L0 z4 A4 D2 v
my_interval_2 = pd.Interval(0.5, 1.5, 'left')
+ p t U+ j) l3 hmy_interval.overlaps(my_interval_2)
: I$ w7 t; W7 k1 w. @3 O7 `) Z9 O0 k0 M( e) \% [
True3 ~& j* R5 f: s" X
1; N; a9 y- H- e+ X$ Z2 d& \
2
& f/ e1 M1 I- w, Y5 ?/ r3
, I3 G& y0 M' x6 ^$ P4$ o. \5 O5 ] @$ M+ D
pd.IntervalIndex对象有四类方法生成,分别是from_breaks, from_arrays, from_tuples, interval_range,它们分别应用于不同的情况:5 D, g8 u3 p8 u- |
: t7 }3 ^" ?9 K9 x* R9 c
from_breaks:类似于cut或qcut函数,只不过后两个是通过计算得到的分割点,而前者是直接传入自定义的分割点:! X$ i' @, S" _& [. K2 D( T
pd.IntervalIndex.from_breaks([1,3,6,10], closed='both')7 d4 X( }; Z9 Z7 _# _
q. s3 G# w) Y" v& j4 {
IntervalIndex([[1, 3], [3, 6], [6, 10]],) e: K, Y) D4 V1 }7 b
closed='both',& x3 b/ h$ T- G: G4 g( C; G2 w
dtype='interval[int64]'): n6 m" M# e4 a4 \
1
: x# u" V. h& W9 w0 A2
/ q: X* O6 r; u3 i+ h: Z/ i1 K: C* B3
1 L7 {; j5 M7 ^1 ]4; ^# P8 i3 X. ^9 t( p# ]( X
5
. p6 e' @' `% z( ]from_arrays:分别传入左端点和右端点的列表,适用于有交集并且知道起点和终点的情况:9 j" T6 f' _* U2 e8 M2 O
pd.IntervalIndex.from_arrays(left = [1,3,6,10], right = [5,4,9,11], closed = 'neither') \, ?& ?4 ?0 @
" `# [& Y! P: u1 h* AIntervalIndex([(1, 5), (3, 4), (6, 9), (10, 11)],$ P1 _" d# X3 e% S
closed='neither',
1 T! k2 @0 w& U5 V) e, t' P dtype='interval[int64]'). a. Z& I& A2 h I# y9 y8 C
1
. n4 }. r5 l. e1 o: V1 t& t2
7 \( g: c& c! w6 d+ d. g3
* u/ g5 P8 _6 v3 e4' Z3 C7 O" q8 ?) ^- J
5/ b; v% p* v- U" W
from_tuples:传入起点和终点元组构成的列表:$ z4 o6 J9 c8 a) K0 A- V
pd.IntervalIndex.from_tuples([(1,5),(3,4),(6,9),(10,11)], closed='neither')! n# w+ n2 j& ?: Q& X$ w
1 I4 y5 R. @' a
IntervalIndex([(1, 5), (3, 4), (6, 9), (10, 11)],7 l: l- ~/ D; H; P" N) L
closed='neither',& q, v" X# J, r0 w% `7 V% Z$ ]4 C
dtype='interval[int64]')
0 T( Z2 d* [2 e12 P( z# G8 V2 _ F7 P
2
- t8 t% F& Y" G- t3
g# P3 s6 w0 s# V' W4
8 V( a- C9 Q+ |+ M3 z. R50 _6 ^! P3 Y% \/ _+ P9 D
interval_range:生成等差区间。其参数有四个:start, end, periods, freq。分别表示等差区间的起点、终点、区间个数和区间长度。其中三个量确定的情况下,剩下一个量就确定了,从而就能构造出相应的区间:
$ Y+ X' U v8 o1 h5 J8 [9 F+ Fpd.interval_range(start=1,end=5,periods=8) # 启起点终点和区间个数
1 E8 l0 E, ~! t, D+ m) d# AOut[57]: + X8 B3 T- i, B% m3 t) B% u
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]],' l% ]+ o: r* e
closed='right',
1 d, t6 e! A1 E" T( a' z- Q" l dtype='interval[float64]')+ i) w! t8 t" D+ C+ J
! a$ G' i( E# U1 G% u- R) D# epd.interval_range(end=5,periods=8,freq=0.5) # 启起点终点和区间长度
* I, G' t3 ^5 P( KOut[58]: + z7 \0 W5 l8 ~4 }
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]],
' H6 J( q0 v8 u8 t/ v; Y closed='right',
! o, x; D& p/ I, O( K dtype='interval[float64]')4 c4 R0 s3 p1 e6 t9 d0 _" Q$ o
1
4 g6 X1 \, K8 q$ ]6 y& L2# S: B$ a4 a! w' V7 h6 C; u
3
& I4 X$ K% P% }4 e9 K4
% l1 r7 ^; b, ~7 V5
3 P( o. q; G& w" q$ a9 M7 V7 }/ k6
& {/ x& j; |/ n6 j7: `1 m ]( Y% T
8) J0 g. U! @% W# _
9
6 ]1 e9 \% H2 e10
. w7 I" ]) e; l7 H) ^11 W3 z' V# b/ O& r& p. [
【练一练】
* Y8 H* I) G" p C* V2 w 无论是interval_range还是下一章时间序列中的date_range都是给定了等差序列中四要素中的三个,从而确定整个序列。请回顾等差数列中的首项、末项、项数和公差的联系,写出interval_range中四个参数之间的恒等关系。
3 ]5 g7 W& m' b) G: i, f% f" c2 E1 ` X3 L$ `6 n3 A8 F
除此之外,如果直接使用pd.IntervalIndex([...], closed=...),把Interval类型的列表组成传入其中转为区间索引,那么所有的区间会被强制转为指定的closed类型,因为pd.IntervalIndex只允许存放同一种开闭区间的Interval对象。 B4 o. Z @/ V1 B3 l8 h+ n
$ t' i, V) k! y8 u! a4 L4 s; e* imy_interval* n: A7 b% a, o; J2 E4 F
Out[59]: Interval(0, 1, closed='right'), K6 ? p; D# a B) ]0 ]" b
& \( D2 r- t# l% G5 N) y' `" X( X' F5 T
my_interval_29 q! C6 {4 m3 l2 v& d4 v
Out[60]: Interval(0.5, 1.5, closed='left')
; F+ M+ C3 |% J. x6 S- E. R5 K" Y( J1 n9 b; u
pd.IntervalIndex([my_interval, my_interval_2], closed='left')
: I& }2 ~/ @+ f2 ]) kOut[61]:
% X1 |: I' p, H: l( t6 }% O' eIntervalIndex([[0.0, 1.0), [0.5, 1.5)],
7 z' d7 q2 z" K! @/ L closed='left',
; U. ?1 Y( C4 M6 s dtype='interval[float64]')
0 k( k- B2 u2 ` S z, t: q1
% {9 Z8 Y9 Y6 ?; h; D, a2
3 l7 L4 j+ J2 T: [, f% T4 `1 e3/ W. a" z) l8 F/ q6 D W/ X2 ?
4- \, Q, E2 w7 a0 i3 n
5
/ _) z& F1 f+ j8 |' `6* t6 f2 d2 R- Y5 c0 N
7
+ S7 S5 }$ P( J8
& Y) g1 b9 T# X9! F. k3 `5 O- @9 C
10
0 z2 }& ^" ^" d+ w; \) h114 O: T9 F% G) u }6 o8 S+ Y
9.3.3 区间的属性与方法
: L4 j1 K; L% l$ u* Q1 \! D IntervalIndex上也定义了一些有用的属性和方法。同时,如果想要具体利用cut或者qcut的结果进行分析,那么需要先将其转为该种索引类型:: j5 O, g \9 V9 ?5 n1 G
$ E a$ Z9 {& e# N6 a- ks=df.Weight) k: [& x/ R: g) Q+ N5 y
id_interval = pd.IntervalIndex(pd.cut(s, 3)) # 返回的是每个元素所属区间,用具体数值(x,y]表示% D, X7 N O& p
id_interval[:3]! P9 n0 Y: f+ y) }5 Q6 C
* Q. ^: h- y1 E; |7 I/ |
IntervalIndex([(33.945, 52.333], (52.333, 70.667], (70.667, 89.0]],
$ V% T( a3 P: `" V( U1 [ closed='right',
- P% a; U+ q* _/ s name='Weight',4 Q: l" i8 {5 ]/ W- I* w
dtype='interval[float64]')
9 B; |6 v! Q4 U) {- i1
9 q2 p- r8 k* A: T20 B/ I" S6 ?* u; ~" P3 |% p
3
: i F* D6 C& _# [, n6 B( u, x6 j4 P: O' e, ~: c' N' y; Q2 i
5& H, o& L$ }1 r/ Q) z7 ?% H
6! a* F# f; A; V9 T/ i: f a% [. y
77 M4 U' H/ p/ U! P* s& h: p
8# q% c1 \6 D% y- M, f3 b$ \
与单个Interval类型相似,IntervalIndex有若干常用属性:left, right, mid, length,分别表示左右端点、两 点均值和区间长度。
+ S* o+ b8 r' M, |2 \( [id_demo = id_interval[:5] # 选出前5个展示( l3 `" J5 {5 V
4 R }# J5 g* xid_demo1 V2 l9 X! M' k2 _2 o" d( v
Out[64]: ! y8 D0 Y4 f( k" Y
IntervalIndex([(33.945, 52.333], (52.333, 70.667], (70.667, 89.0], (33.945, 52.333], (70.667, 89.0]],! ^* @8 K/ y; G9 e4 @1 Q) X
closed='right',2 r N3 h. H* v* e& C
name='Weight',3 |' ^" N& K1 t* x! N. B# C
dtype='interval[float64]')
& [6 j4 {! N+ x; ]( i
- `7 R o! t D6 k- l" p" Fid_demo.left # 获取这五个区间的左端点( B& l. P7 ^' a/ j+ q R
Out[65]: Float64Index([33.945, 52.333, 70.667, 33.945, 70.667], dtype='float64')0 U* e( G& d. r* y9 S
7 V" n3 y: C$ d8 ?id_demo.right # 获取这五个区间的右端点
; h& ~, _- m9 U2 A$ `Out[66]: Float64Index([52.333, 70.667, 89.0, 52.333, 89.0], dtype='float64')
' e6 _; r l0 V& q) ]& N8 ^4 [5 B: N; J% p
id_demo.mid
9 t8 i& Q( z; B! p0 AOut[67]: Float64Index([43.138999999999996, 61.5, 79.8335, 43.138999999999996, 79.8335], dtype='float64')0 H; R. y# r$ B( [* A7 R6 S" R
2 p7 l1 g! b$ }3 tid_demo.length
+ p3 H2 I3 z2 d4 }+ i0 B, N. XOut[68]:
' I) t! C! w" B* \Float64Index([18.387999999999998, 18.334000000000003, 18.333,# ^/ [& I& C. b: y
18.387999999999998, 18.333],- ]6 ?1 B4 V+ a' b* ?: E
dtype='float64')
2 O: O3 K( E+ @( [$ e. |" b% R) w& a' o' k5 k8 N6 r
1. u' S1 P' o. l) X2 x3 Z
2
0 _7 k! }7 ^7 x4 k35 R( h6 v& p& D; e
48 h' t+ C# T% G& @5 N
5
* F, ~+ ^$ X9 q3 P# U; m6
{' i$ C5 k0 R6 z# f/ c+ f: [7
+ f& H( G; n5 h81 _7 }& U3 d7 A' T0 X
9
' X( e- Z1 S0 T: {/ U10: y; U$ N7 ?* T0 y; E9 q
11
; |7 A9 a2 H" |4 L& T$ Z12
& [6 s/ E; Q ]/ p0 M# s9 T1 I13
: v* K$ d* J% z; B q" r1 Y2 _14. }# z x! F# H' e* ]: b
15
1 x- ^2 E, s4 @16
% Q( a: y# N+ D176 I1 k/ w' ?. ?; o$ b5 N& ~ Y# G; n
185 h( S. }- e5 ^- P4 T# M, Y9 S4 e
19
7 j0 Y f$ b0 ?% L$ X20) a9 ~% V% ^. j. ^4 Q$ E
21
0 ~' e# l4 a# U3 D3 B+ E22
3 `- M7 Z3 L1 |23
5 |" J% C* ?4 n4 CIntervalIndex还有两个常用方法:1 @( r. V. V \
contains:逐个判断每个区间是否包含某元素( C% U/ X, K9 j- z. J- a
overlaps:是否和一个pd.Interval对象有交集。( ?/ Y! e" m, a2 \0 h- ]
id_demo.contains(50)
+ o, ^1 N9 e6 v- C4 P% pOut[69]: array([ True, False, False, True, False])
! ~8 t# p6 Q+ ?' j. [; K7 l& U) K2 z# d" t3 k2 p( R4 I% L% f
id_demo.overlaps(pd.Interval(40,60))& Y4 n# m% i' P) R7 T3 I% m
Out[70]: array([ True, True, False, True, False]). P# k/ s9 w) O: w, }& {$ ^
12 Z& s; G5 e, S
2; u/ p3 ?" ?* p* Q
3; ~. X) j9 h, W/ b6 J5 o
4
- ]9 ?+ Q2 D+ G! ]( P, m' l5 J$ K5
" Z4 G, W/ a; D' Y" Q9.4 练习. s# S- K6 Q2 ~) s# t# n
Ex1: 统计未出现的类别; g' l/ q' C1 n* ]6 G) I5 E
在第五章中介绍了crosstab函数,在默认参数下它能够对两个列的组合出现的频数进行统计汇总:
! B( S& [- N. T% D) L2 B
+ e3 o3 v8 f( s. Sdf = pd.DataFrame({'A':['a','b','c','a'], 'B':['cat','cat','dog','cat']})( i: y) }: ~5 f. l
pd.crosstab(df.A, df.B)+ Y3 @- H: _8 ]7 e% Z4 x
8 j4 ~2 \1 D+ u2 bOut[72]: - T$ E. a) e, c0 D: B: l1 [
B cat dog
# ?, ^( X- ]' E7 i) U% J2 [2 tA
, ]; z0 @5 q# c2 qa 2 0
; M! X/ W: h5 d& _# Z9 @: A) y3 a& `b 1 0
- S7 | ~2 a6 ]) S" Z/ bc 0 1
. r# b8 [2 ^* v* N" }. B1 V3 q/ r4 H1
9 _2 e; \' c7 u: b/ _2
+ Z- A6 ?% {" n0 B3
, q' C/ a7 n; u. s4
; ^& L. Q5 u+ E! K5
4 H: R8 D7 M# b* n4 q5 }61 g7 j4 g" v- f6 B) l% \
7* x2 o, ?7 f- D
8
7 W% w& g3 o- E: K6 c& m5 W9
+ \. @5 o& ]. J; y+ j; n5 N 但事实上有些列存储的是分类变量,列中并不一定包含所有的类别,此时如果想要对这些未出现的类别在crosstab结果中也进行汇总,则可以指定dropna参数为False:5 P6 o3 Z6 X! t4 x [3 }
' P% G0 l" j. e& ^4 ^( p
df.B = df.B.astype('category').cat.add_categories('sheep')
2 p5 K+ n4 y: Z& Y3 \3 d' S1 s1 ~ F1 R' cpd.crosstab(df.A, df.B, dropna=False)3 M0 v7 D+ G' n! L/ b2 ?5 v
0 u9 L, f/ V; j" K2 }0 q4 G3 yOut[74]:
: e% L: t$ ]$ iB cat dog sheep
5 a) `: Q C: W1 _( dA
* x: s# v! q7 g6 D& t8 Ha 2 0 0. c+ ~3 w J0 c6 a5 ^
b 1 0 0: G1 t) s5 M% F. K* M
c 0 1 0
6 c+ d/ E8 `: E0 `# ]3 [16 x, \5 r4 T s' J& L
29 E5 Y! u' c5 W" P; n! u- k9 ], g
35 x% w1 T8 h( F4 d* p. P' \
4
& S6 ]6 B6 G4 D9 s/ B$ | J0 o52 p. w: |$ Y# i8 W9 x5 V
6- v, S6 @% s9 ]# T8 W- k$ K& _
7, P* A: F* G& i9 s; {
82 [ f# O' Z' |
9% P% e+ k9 X) A- _% x- b
请实现一个带有dropna参数的my_crosstab函数来完成上面的功能。/ _; [2 \2 Z: O7 E# p$ h
, a: P* i3 f! Z3 P: AEx2: 钻石数据集
8 y1 m5 h; l5 n# O' [+ G: q 现有一份关于钻石的数据集,其中carat, cut, clarity, price分别表示克拉重量、切割质量、纯净度和价格,样例如下:5 S/ d) H* h! x+ V( A
- g+ e# T7 b8 o% b5 [# _df = pd.read_csv('../data/diamonds.csv')
& ~1 O: A2 R9 |1 hdf.head(3)
- D: Q+ G8 g) ~9 ]% C# i$ ^% d0 D5 `. r% ?% W* O
Out[76]:
7 B' ~/ h& X" L/ ^3 d$ H carat cut clarity price: d! M; S" v0 R7 o# [# V& k
0 0.23 Ideal SI2 326* r3 Q+ O# s7 C/ G
1 0.21 Premium SI1 326
$ P$ ]0 Y- F8 ]0 l2 0.23 Good VS1 327( s3 ]6 ?7 X: `: ?4 l; f3 o3 y
1
, J; T, a4 g4 `7 ?( S' j7 e: w29 ~4 l5 E) n" t- v
3
( _5 i8 S) w; r: k4( K; |" b8 U% C, H' T/ P% Q+ I
5
0 G2 j4 m, u; D) P# X6
# e/ H3 V- G! E# \3 _, b7
/ P4 x6 ~5 f0 N( D# D81 a# k; L1 g. ^6 b
分别对df.cut在object类型和category类型下使用nunique函数,并比较它们的性能。
- M: Z1 _- u) O( p- U- i* y+ S钻石的切割质量可以分为五个等级,由次到好分别是Fair, Good, Very Good, Premium, Ideal,纯净度有八个等级,由次到好分别是I1, SI2, SI1, VS2, VS1, VVS2, VVS1, IF,请对切割质量按照由好到次的顺序排序,相同切割质量的钻石,按照纯净度进行由次到好的排序。- J, A/ v- z9 Q( F
分别采用两种不同的方法,把cut, clarity这两列按照由好到次的顺序,映射到从0到n-1的整数,其中n表示类别的个数。
- G1 m' o# ?& a( ~$ y; F对每克拉的价格分别按照分位数(q=[0.2, 0.4, 0.6, 0.8])与[1000, 3500, 5500, 18000]割点进行分箱得到五个类别Very Low, Low, Mid, High, Very High,并把按这两种分箱方法得到的category序列依次添加到原表中。' m1 N8 L4 \* Z3 @5 w
第4问中按照整数分箱得到的序列中,是否出现了所有的类别?如果存在没有出现的类别请把该类别删除。
# W: [; A. b. V) q2 a# u7 |对第4问中按照分位数分箱得到的序列,求每个样本对应所在区间的左右端点值和长度。
. |4 V0 w6 @- ]2 h2 k1 m6 F o先看看数据结构:/ D+ V3 M5 }0 q/ o: N! A
0 X% B+ @7 s. k9 {" R
df.info()
' Y* U, o: {4 VData columns (total 4 columns):5 n' M: f+ j, t- ~$ F# v
# Column Non-Null Count Dtype $ k4 L! c; {3 \2 s0 Q( u1 G6 n# p
--- ------ -------------- -----
8 z1 b! Y _8 N }' X6 S 0 carat 53940 non-null float64
3 g0 A, R/ V7 N! D- a9 d; l ? 1 cut 53940 non-null object , g. W6 ^1 A: H" }" a4 W6 H. b
2 clarity 53940 non-null object " e. k3 i, {( Z9 X) i
3 price 53940 non-null int64
! N% e6 e3 Y. u5 B. {" `3 y2 Rdtypes: float64(1), int64(1), object(2)
: k+ k" K4 Z- V3 X1' s* d5 s9 Q' S
28 T5 y5 x1 O& Q, u' C
3
* s8 r* E p( O3 G42 o& R4 \& c( m( s2 S
5' C; r; D) G0 f8 E6 A
6# u5 U' D6 d. H0 l
76 r* _% ~. R& E. Q; v
8& r9 e5 K4 l6 J5 F$ \; b
9
1 X& `7 D) L$ I0 b比较两种操作的性能! Q, ~' }, B" J. p
%time df.cut.unique()6 w; B! ]6 V5 a% {
; d! O" p* E8 w2 a2 U& K* _Wall time: 5.98 ms
1 }1 t0 N; B& _7 G- a; ~array(['Ideal', 'Premium', 'Good', 'Very Good', 'Fair'], dtype=object), k8 k9 q h$ [: [) b9 H
1
3 o: ^% P4 h1 w4 _0 j0 v2 g \2! X- W, E: u" i0 D5 b' n
3
9 y6 C5 r7 K I+ a3 z49 \: r' P! }# A* n& E |% D( R. Y
%time df.cut.astype('category').unique()1 _" W! R: T/ I. Z
, v+ c+ }2 L9 y0 Q: Q
Wall time: 8.01 ms # 转换类型加统计类别,一共8ms- F! _1 X. ^' i; u. N% w" e
['Ideal', 'Premium', 'Good', 'Very Good', 'Fair']) g* o$ Y- Z/ Y; r) N* _0 }# U
Categories (5, object): ['Ideal', 'Premium', 'Good', 'Very Good', 'Fair']) h! d P# j" {4 g& v1 ^9 R$ C& }
1
/ X' Q6 z8 X3 P8 W9 {( d2; R' ~5 E: G( } o$ P& M( ~
3
9 ^) F8 N4 B6 n9 B4 d6 h( Q4
/ B4 U8 D# z6 D5 e; E5/ u# |" x1 K6 F% E: [3 S
df.cut=df.cut.astype('category')0 V4 Z r0 p2 U% g8 S8 \
%time df.cut.unique() # 类别属性统计,2ms* r' ]$ d4 s- v4 N7 {% Y
* m! F+ W- ]& v+ L% S0 ]; w6 V7 c" `
Wall time: 2 ms
' r% _, \% @' K['Ideal', 'Premium', 'Good', 'Very Good', 'Fair']& m8 } x! G- h4 F8 y6 y
Categories (5, object): ['Ideal', 'Premium', 'Good', 'Very Good', 'Fair']
; x) [% h0 r N' v ~& Q# r1
+ d4 Q" s& f% H2
: s) {' M$ Q# A, O3/ R4 R0 v' S7 @5 _' s! U) b6 {
4
7 h3 F+ |/ B4 |% V# T5
; n* l* i5 E. \9 V5 w1 Y5 N7 I6( P; `0 V, K4 u
对切割质量按照由好到次的顺序排序,相同切割质量的钻石,按照纯净度进行由次到好的排序。
: }, g/ q* V' Z: ]* ^3 ?ls_cut=['Fair', 'Good', 'Very Good', 'Premium', 'Ideal']
) {7 l; t# N8 O p0 p g" fls_clarity=['I1','SI2', 'SI1', 'VS2', 'VS1', 'VVS2', 'VVS1', 'IF']
/ b2 `7 y% E% Z6 n. Kdf.cut=df.cut.astype('category').cat.reorder_categories(ls_cut,ordered=True) # 转换后还是得进行替换
4 a8 y5 G L M( [( Ldf.clarity=df.clarity.astype('category').cat.reorder_categories(ls_clarity,ordered=True)
, d( i; l. x2 ]2 y7 c5 r) O
* E% B; s- d, O. }3 i. cdf.sort_values(['cut','clarity'],ascending=[False,True]).head(3)1 ~6 Y7 O( ?- h ~( h. W6 S
+ {3 x- e4 J* z3 J carat cut clarity price
2 S( E8 [( B' g! h& F315 0.96 Ideal I1 2801
( h9 P7 l4 X( E6 o) ^. [5 K535 0.96 Ideal I1 28268 @" {0 P Z' t( T- S3 F
551 0.97 Ideal I1 2830
1 R' g: u. U0 _; u3 A; c18 n( k$ j# ]; X5 H$ T% Z% ]* H
2# w4 R C& y# c" d
3
' I$ y* z+ ]% g$ n7 V: T% Q }4. @% U; S& ~7 Y; G0 Q
5
9 l) P; n9 Z& ~6# u2 g: S- X9 y* y5 v
7 O* p; ?; e6 C8 R6 E
8
) t4 a4 R6 h9 ]: V9
: ], |. v: }; _0 W: H: N2 L# l' {6 Y) I10: {5 z) M* N- t6 m) l: L/ u' W( _
11
4 E& |: P* M& C# d* L分别采用两种不同的方法,把 cut, clarity 这两列按照 由好到次 的顺序,映射到从0到n-1的整数,其中n表示类别的个数。
/ a& y8 V/ D8 \" e# 第一种是将类别重命名为整数
- K3 Y" ~7 V# N+ ddict1=dict(zip(ls_cut,[x for x in range (4,-1,-1)]))* Y6 K9 `( g6 b
dict2=dict(zip(ls_clarity,[x for x in range (7,-1,-1)]))& g6 ]7 M9 T$ M5 x# L; i4 L8 C
$ f: ?- p2 {& m5 j( S
df.cut=df.cut.cat.rename_categories(dict1)
$ l, P0 ]9 P% U, a3 idf.clarity=df.clarity.cat.rename_categories(dict2)# t' F' }8 f+ i& ?
df.head(3)
5 b2 x% z( |7 {* h) T4 V
6 K# b2 t9 o, `$ T3 h, o" u/ X carat cut clarity price
- w2 y! m6 g$ n' y+ N5 J B$ J3 D0 0.23 0 6 326, Q! M& ?5 G% N& |0 x! h/ r) Y
1 0.21 1 5 3266 I# p% J, L+ M
2 0.23 3 3 327
4 | O* C/ E( b7 z0 u; V+ Z7 f8 x1( r$ @: h9 D- G
2% W+ a3 C- g0 K& H3 C2 m u4 D
3
& {2 b& x( @/ Y- m i4& s! w) r/ Z0 @1 P
5
5 |1 L- Q2 S4 Q# z* Y% f7 Q6- z. C7 u* r4 f% Z( n
7
7 o% E5 I c- M8
3 X7 ]" C/ y9 Z9
" n! J& [- o; v: z2 U2 c103 s" p* M( ]6 G/ p; k8 m
11
6 W+ B! ~! {7 m' |123 _. D% T8 d& J# L) |6 P
# 第二种应该是报错object属性,然后直接进行替换
4 {- f3 m: R% L1 O! ^% h0 v9 w4 Y: pdf = pd.read_csv('data/diamonds.csv')
) M/ l0 ]0 r5 W9 G* [' G3 pfor i,j in enumerate(ls_cut[::-1]):/ t4 h2 u! n& K# u% g
df.loc[df.cut==j,'cut']=i
. N3 F j6 d; ^) t* R
H/ N5 F6 S4 j4 lfor k,l in enumerate(ls_clarity[::-1]):
: z3 _/ I" w+ e! ]: z( r df.loc[df.clarity==l,'clarity']=k
3 N' N: G5 c8 d% \0 I2 H1 w& fdf.head(3)
{: i6 j% {4 M3 [
+ X6 ]" F( k( Y" i3 p K carat cut clarity price+ k$ C# ]9 Y# v% @. H
0 0.23 0 6 326* q/ p0 w+ S- Q+ Z. }9 s7 l. A; |$ m
1 0.21 1 5 326% ~9 d. ]3 m: c" m. w- M j( T( d
2 0.23 3 3 3274 t" L8 l U& O$ \! R; |
1+ Z$ a8 a2 h5 b/ E' P$ S( e" R8 m
24 Z2 g1 S3 y$ _- k1 T
3
! n: P* H+ {9 [, X$ B; W4( H- ^. U9 c# w) x; T% S; n
50 x3 x G" k9 g$ o4 d
6
. t7 Z" ^5 \' p/ T7
: f1 N$ `1 Z, f; J- S3 i O' e& r8( K O. X9 A' ~
9! p7 s" h9 E! {: u+ F+ v7 V
10
, I8 x) P) r4 ?. a5 U( Z+ x/ p5 Q11
: _) Z8 |% [- c& x125 @$ q1 S5 {0 W, O, Y
13
6 e& j& g8 B" D9 \$ s对每克拉的价格分别按照分位数(q=[0.2, 0.4, 0.6, 0.8])与[1000, 3500, 5500, 18000]割点进行分箱得到五个类别Very Low, Low, Mid, High, Very High,并把按这两种分箱方法得到的category序列依次添加到原表中。
. V) X" }+ r; k: G# retbins=True返回的是元组,第一个才是要的序列,第二个元素是分割点$ M+ o/ I! K0 ?
avg=df.price/df.carat* l! o1 h% M! H
# u& n( S7 j% c I. z( ]df['price_quantile']=pd.qcut(avg, q=[0,0.2,0.4,0.6,0.8,1],% c! q8 }. v' w5 R# c1 b5 T
labels=['Very Low', 'Low', 'Mid', 'High', 'Very High'],retbins=True)[0]
7 P# [2 n* M5 y6 J( Q2 P' y n- }
+ q5 S& D0 A! E. m$ T, zdf['price_list']=pd.cut(avg, bins=[-np.infty,1000, 3500, 5500, 18000,np.infty],
' Y. W6 }: _- ~1 u& f labels=['Very Low', 'Low', 'Mid', 'High', 'Very High'],retbins=True)[0]/ _1 o9 p1 u8 q* N5 b
df.head()1 d4 J. v6 y( ?( ^7 S, Z
5 L0 Y: x: n3 Z7 K" ~& T
carat cut clarity price price_quantile price_list2 c& n4 N* W. R& J4 h9 Z+ Z
0 0.23 0 6 326 Very Low Low$ j2 @1 j0 ~0 v9 N
1 0.21 1 5 326 Very Low Low
. p4 @# Y* |1 a: w- R5 x2 0.23 3 3 327 Very Low Low
! \( j. B: v N0 V# }3 u" d3 0.29 1 4 334 Very Low Low$ ]6 j6 K$ U% K' k) ]
4 0.31 3 6 335 Very Low Low . _: A, ]5 |" n4 H0 S1 W
6 W2 h# D) g6 t' [& U) N1
5 |( S% E8 _! X; k4 K. J' f* `6 Y0 a( K2
! _0 T" U. O9 A' \" ]; R37 q% t0 x4 F* i1 l; t
4
* G* t" h7 @- {; k: L5/ c7 W3 H2 S3 ~* a2 N/ t
6
& }& o# }* v$ u0 y0 c# K; R/ x7; r% i' m, S! \# n9 |' P
8
, d8 d: f( g- i. g96 F- L1 {1 T) \+ k& e \
10) q+ V9 V( z; N6 {1 Y
11+ W% U' z3 {* ?$ o
12% |1 X4 U; e$ L
13! g4 {+ H# P. }6 _1 X
14
) T& t' l: }! v! F15% O2 R: N: J# q1 F4 d$ [. V
16
2 u; t" G: m* \9 d: l3 ]分割点分别是:
4 v3 c5 ~# u/ `$ |' f; O; K, `& a- _1 \& l+ u$ n3 Q
array([ 1051.16 , 2295. , 3073.29, 4031.68, 5456.34, 17828.84])9 ]: p# s) X' X) D- a- L
array([ -inf, 1000., 3500., 5500., 18000., inf])) z: m5 u. ~4 ~/ k% U6 K, j
1" g4 ?! U7 h) {% _5 L( c/ d
2) v5 J" K `; d4 W7 u5 j% `
第4问中按照整数分箱得到的序列中,是否出现了所有的类别?如果存在没有出现的类别请把该类别删除。
& o* I" H" h( N# n. Ndf['price_list'].cat.categories # 原先设定的类别数
) G+ J* e# r* [5 D- x; m# W+ sIndex(['Very Low', 'Low', 'Mid', 'High', 'Very High'], dtype='object')
: o% Y9 r- f. l9 j5 j. c3 Q6 E) D+ R+ }* z: T. K
df['price_list'].cat.remove_unused_categories().cat.categories # 移除未出现的类别% v: c/ k$ L( S
Index(['Low', 'Mid', 'High'], dtype='object') # 首尾两个类别未出现
/ O, _/ `" b/ |4 C( `# P5 S* `16 A" P' P( b" |( f8 x, X! k
2
+ |! T4 J7 z% h7 Y3
" V+ ]% c. s7 y& P5 G7 r6 r9 a4' s6 K; A$ p% S. a6 O. f5 n# X$ T$ O
5
2 D+ s) S) n) N! Favg.sort_values() # 可见首尾区间确实是没有的) b+ F: W/ j3 K Z
31962 1051.162791
! H) Q2 Y! W1 I) b% S15 1078.125000
& V5 P5 s- r9 c4 1080.645161) X7 B8 K/ x: w) ^! N; X
28285 1109.090909! T. u2 \" X0 L5 x6 f$ o% A0 }
13 1109.677419
/ U( Q. O* X( t' U$ h0 N1 W: g ... * O. `7 a: x6 @( s: R/ c2 C
26998 16764.705882) \9 ]5 J: W- k, C2 L7 G' U
27457 16928.971963
8 R( v0 N! K* W1 u! w3 S( C27226 17077.669903
2 \! E. }( u: w7 @27530 17083.177570
5 Y1 s4 l5 _3 \1 W4 f27635 17828.8461543 B0 X! n. \2 | V' y2 R8 Q
1
* A# D3 J: ^4 c, m2+ A! N, I: l7 P$ [
3. w5 X+ f: w/ \! g p
42 R: B5 {1 T: Q/ o1 q
5
5 y5 `2 A; X1 M' E: v6. z3 U- `5 G$ b1 J- m- h) e a+ ^
79 n2 H! }$ z! O
8
. q. y! i$ r& z( x( m9
4 C' S( q* i; S K( K( H0 x109 h- c: ^& o, x: w5 w
11
, X" c/ L) U. l! f12# B9 Q4 s+ ?4 {8 }/ A: B
对第4问中按照分位数分箱得到的序列,求每个样本对应所在区间的左右端点值和长度。
& p9 ~' M+ }$ j& P3 T5 [8 d0 F# 分割时区间不能有命名,否则字符串传入错误。7 c" T3 q+ N# p3 Y! ]1 n/ E9 V- N
id_interval=pd.IntervalIndex(( g2 M9 M2 f: @; [9 h/ O* G. O/ E
pd.qcut(avg, q=[0,0.2,0.4,0.6,0.8,1],retbins=True)[0]2 _* ?7 v0 }) E& r
)6 _% Y* |3 c5 N, K0 Y; g* M# M
id_interval.left
, }1 W0 J1 N) x0 S3 u/ z$ H; t9 Hid_interval.right) c" ^; ^; m; G/ U' I. o/ g0 i
id_interval.length ! H/ M3 n2 e+ Y* H2 J
1/ O( y* j7 {/ \
2
, g2 w) g2 w! V7 A+ ]# Q, d: @; \! S3
9 a5 Y0 L! e* K4
& Z! C8 P1 @7 X: h6 g59 S8 F* n z5 F
6
7 u4 A5 r& ~' I4 [. i$ o- P: J# z9 E7
* U& y, C* j0 W' H; J第十章 时序数据 h, H" ~9 d- M& I0 L8 N
import numpy as np
9 ^* X+ i# U) ? f& l" | Jimport pandas as pd/ I' A/ ]/ G9 ?5 [: S5 ?
1
9 r8 V/ K9 |7 j. S& l; H2
1 s* U) f9 h/ a1 [& \% f0 o( y: O; C
: H& H, T% k+ n; f10.1 时序中的基本对象
/ c F. v0 f- p$ _: s& k( h! k% A 时间序列的概念在日常生活中十分常见,但对于一个具体的时序事件而言,可以从多个时间对象的角度来描述。例如2020年9月7日周一早上8点整需要到教室上课,这个课会在当天早上10点结束,其中包含了哪些时间概念?4 M, a& \% x2 I5 ?9 }
' x6 T; N; N( H9 q会出现时间戳(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的简写。
, \$ Y$ l7 v# x7 ^8 U4 s, E# w" i! \ d- [
会出现时间差(Time deltas)的概念,即上课需要的时间,两个Timestamp做差就得到了时间差,pandas中利用Timedelta来表示。类似的,一系列的时间差就组成了TimedeltaIndex, 而将它放到Series中后,Series的类型就变为了timedelta64[ns]。$ L& W$ z' h. c: i& S" E- L2 L
8 f. |+ l0 c; {. W. U3 s
会出现时间段(Time spans)的概念,即在8点到10点这个区间都会持续地在上课,在pandas利用Period来表示。类似的,一系列的时间段就组成了PeriodIndex, 而将它放到Series中后,Series的类型就变为了Period。
$ b F* w3 w7 k! J! k, P$ A4 k
会出现日期偏置(Date offsets)的概念,假设你只知道9月的第一个周一早上8点要去上课,但不知道具体的日期,那么就需要一个类型来处理此类需求。再例如,想要知道2020年9月7日后的第30个工作日是哪一天,那么时间差就解决不了你的问题,从而pandas中的DateOffset就出现了。同时,pandas中没有为一列时间偏置专门设计存储类型,理由也很简单,因为需求比较奇怪,一般来说我们只需要对一批时间特征做一个统一的特殊日期偏置。
' v8 n" @: t8 b$ u4 a' X
2 F1 I2 I& ^" m# e7 j 通过这个简单的例子,就能够容易地总结出官方文档中的这个表格:7 {. D$ i }0 ^4 L0 c
$ Z( [3 L0 b0 \+ V; e8 w, F概念 单元素类型 数组类型 pandas数据类型8 h( f5 K5 [) X4 ^. t6 g H C' l$ D
Date times Timestamp DatetimeIndex datetime64[ns]+ y0 z6 L% C2 w( m' H0 e9 H
Time deltas Timedelta TimedeltaIndex timedelta64[ns]+ |8 T/ {/ Z( M0 S3 ~
Time spans Period PeriodIndex period[freq]. p4 b7 w' _' `3 I# }9 V2 [
Date offsets DateOffset None None Y7 g! I( O2 }4 d
由于时间段对象Period/PeriodIndex的使用频率并不高,因此将不进行讲解,而只涉及时间戳序列、时间差序列和日期偏置的相关内容。" ?- U4 F2 d# {7 T
9 l' G/ O: D3 \0 ]8 b; `: N6 n
10.2 时间戳6 j9 g- Y& i5 n: r; Z/ N. ~% N
10.2.1 Timestamp的构造与属性
. o! v2 m& v4 D* `/ b# W; w单个时间戳的生成利用pd.Timestamp实现,一般而言的常见日期格式都能被成功地转换:5 e9 r# y& l" H! n& _
* s9 J7 M2 L0 O" g2 T% Cts = pd.Timestamp('2020/1/1'). _( X/ R( u* U( X" N. T1 {
) f2 L2 ~* f0 l2 T1 Dts
6 b Z2 D4 q# L; h" cOut[4]: Timestamp('2020-01-01 00:00:00')" d/ Z* Y4 K/ }, q0 F9 Z
: i5 P' N% Y. a0 Yts = pd.Timestamp('2020-1-1 08:10:30')# Q- a( X, ^- g. ^6 H' F$ O: w4 Y" R
v* k/ d# I7 ~8 |
ts
, @" }' e/ t+ g0 b. }& N" QOut[6]: Timestamp('2020-01-01 08:10:30')
( q- ^1 C. [1 H( {17 U: t `% u& o
2
2 f9 S4 _* ^/ G3
$ E' l& e/ g9 f' n* P4: [% \8 }5 o+ S$ y: r3 f
5
+ ]3 t- J( K% m7 _8 z6
1 r8 Z) g$ w# e+ S7; [# f5 K9 i7 O" L! g; _
8 q" K7 t) r4 _1 p2 {
9
8 B6 l- o/ h; K! E通过year, month, day, hour, min, second可以获取具体的数值:
0 ~3 b. @% q1 o3 H0 V" T$ o" V' b0 O4 X( M7 C8 |! U
ts.year" r$ {0 B1 M# g Z) ^
Out[7]: 2020
9 U+ Y7 w" i8 a$ E( @- k
" g3 T2 H& U' c; r! p2 T7 c/ ~" \, rts.month( ^, E! g6 h- ^6 `: _
Out[8]: 1, _0 Q4 x l2 |! u
( g; V9 j& Y2 s+ lts.day
2 x) s) E% u# l; a0 F3 z8 o- ~; XOut[9]: 1- i: `0 k# e1 Q2 N$ ]! y
# M1 f/ U4 m+ F9 z, Hts.hour7 H* T" e! b+ [6 K8 m
Out[10]: 8
6 K* C6 ^6 j! D4 o+ d
: w7 ]) d I8 W) ]/ tts.minute: X6 Z. t) ?* ~
Out[11]: 10, `. n2 W3 ?% q4 N* {( E
V5 g' i# P( d8 x, ^$ B) s8 l
ts.second: H2 E) {: {9 n. y5 J! }8 t8 J% w+ h
Out[12]: 306 ~1 k( a: X4 O i$ H. s
8 H9 g+ r: ~, e' C1 f1# n7 b, V3 d5 b* P8 J, i" i7 W7 V
2# u: Z/ G- \" \7 v
3
4 x0 S1 `* w, o1 o" k4 [8 G; J4
5 `' I9 d# Y; g1 H1 ^, U: M5
% s% f$ O& _' [' m0 I! p2 U9 S6$ w# k& v* j* K# B" q
70 d$ _$ J' q, J! S4 W% ~
8
; A% S- o% M/ }5 z6 a, P9
# R" ^4 c8 I) s4 h+ q' G10
% z5 ?0 X5 b; l g, g11
! p- i/ M7 c G9 M! L6 _128 G, R$ D' o$ P; z- B0 \
13+ T8 k6 h0 X6 o3 b* R6 a
14
) y) |! b+ q: x15
" U7 F4 i3 S' o( T$ K16
/ {: x3 V: E$ R3 F+ c172 q/ C% B4 _ m
# 获取当前时间9 p. J# i2 P" ]# z; G5 ?) G. ], t$ H
now=pd.Timestamp.now()
: u% n& f" z$ Z( F1& z' f0 [8 r0 w; C
21 q8 V d& B5 e& E w" p( @
在pandas中,时间戳的最小精度为纳秒ns,由于使用了64位存储,可以表示的时间范围大约可以如下计算:
7 M8 n; Q2 ?( ]" w6 `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)$ L2 S' ?, b( ?
TimeRange=
% q& E# j. G& |( X( _ u10
. t1 t4 v3 C/ E6 K1 I. _9' {/ D- s% y2 [ g. j$ O3 X; y
×60×60×24×365( P: s0 s& {- e" c3 j7 N
2 1 H3 s6 y6 M/ N! ?
64
4 Q# ~1 h& V7 S* E8 r2 N
4 Q: d( v4 J& R1 e
" k. q, {; G t, k; O) @ ≈585(Years)( X$ a1 ]8 i2 Y! [" o
3 s* ]8 y) W' s2 U- y) r8 P! a4 d通过pd.Timestamp.max和pd.Timestamp.min可以获取时间戳表示的范围,可以看到确实表示的区间年数大小正如上述计算结果:
- v7 o- T& c5 c: f: U
5 Z5 a1 |3 p; o1 q' v: mpd.Timestamp.max
4 d" b2 V/ @9 z1 V) E" fOut[13]: Timestamp('2262-04-11 23:47:16.854775807')" p- u* b9 c" Z9 G4 [+ ], |
; N) f1 U: ?; V- ^) _) A8 v# Tpd.Timestamp.min
: c9 x% a" e2 A: D$ i. q! `Out[14]: Timestamp('1677-09-21 00:12:43.145225')8 {' n( l. u w0 |2 s3 I4 _
2 p! {; m, q0 f2 i+ T! `5 U
pd.Timestamp.max.year - pd.Timestamp.min.year
7 _2 `8 K1 |+ q" M& bOut[15]: 5854 Y! V8 s) |0 `1 N7 Y- X
1
2 { S. K5 I2 e; G2
1 Y; E) ?- ^# K% v: q6 I3! p1 I- m1 T, h$ p2 b# N! w
4
: I+ _" ?6 \8 D# ~5
, M5 O" H0 M! C9 N9 i' W6, O' ?* Y8 d! V7 X- S. c
7
4 C x1 p; M$ Q2 `( t# ?! Y8
p7 o! X+ T9 `5 M* Y: j10.2.2 Datetime序列的生成# V; Z. k! z) X q/ t9 X4 m2 D
pandas.to_datetime(arg, errors='raise', dayfirst=False, yearfirst=False, utc=None, format=None,+ v2 P0 ]! Z$ X
exact=True, unit=None, infer_datetime_format=False, origin='unix', cache=True)% r3 G4 Y* O C1 d* r4 F" u
1. ^& @# J1 M' P& ^0 v1 t- `
2( c# F; C" e4 H% A n: x
pandas.to_datetime将arg转换为日期时间。
% J1 e3 w6 G( w, h; x
- h# P% w' V: ~5 farg:可以是argint、float、str、datetime、list、tuple、一维数组、Series、DataFrame/dict-like等要转换为日期时间的对象。如果提供了 DataFrame,则该方法至少需要以下列:“年”、“月”、“日”。: ^6 j( `; z% P3 }& h
errors:# l5 {' E1 H( M+ `, @
- ‘raise’:默认值,无效解析将引发异常# V; ]1 |& |9 c {% e
- ‘raise’:无效解析将返回输入) U* m' K+ C7 m) P! k7 [
- ‘coerce’:无效解析将被设置为NaT# t8 F$ Q6 J3 K+ G5 h$ o
dayfirst:如果 arg 是 str 或类似列表时使用,表示日期解析顺序,默认为 False。如果为 True,则首先解析日期,例如“10/11/12”被解析为 2012-11-10。如果无法根据给定的 dayfirst 选项解析分隔日期字符串,会显示警告。( E$ P r$ [. V" ^6 q) i9 n% ?
yearfirst:如果 arg 是 str 或类似列表时使用,表示日期解析顺序,默认为 False。如果为 True,则首先解析年份,例如“10/11/12”被解析为2010-11-12。无法正确解析时会显示警告。(如果 dayfirst 和 yearfirst 都为 True,则 yearfirst 优先(与 dateutil 相同)。)
6 }+ F1 L3 f* O6 Eutcbool:默认None,控制时区相关的解析、本地化和转换。请参阅:pandas 有关时区转换和本地化的一般文档4 p- M9 Z5 u ?& o1 r( z2 ^! D B
format:str格式,默认None。时间戳的格式不满足转换时,可以强制使用format进行匹配。
3 e0 S# Q2 c4 F2 \+ Z1 Yunitstr:默认“ns”。它是arg (D,s,ms,us,ns) 的表示单位,可以是整数或浮点数。这将基于原点。例如,使用 unit=‘ms’ 和 origin=‘unix’ (默认值),这将计算到 unix 开始的毫秒数。
4 Y0 ?, v' i* o* \( v8 Yto_datetime能够把一列时间戳格式的对象转换成为datetime64[ns]类型的时间序列:
4 D" O# C4 o4 [% m5 B( Apd.to_datetime(['2020-1-1', '2020-1-3', '2020-1-6'])% ]- b) |' O1 X- r& n$ R3 t
% Z! ~' U, c! b5 O/ b- H) wDatetimeIndex(['2020-01-01', '2020-01-03', '2020-01-06'], dtype='datetime64[ns]', freq=None)
8 ~% Z5 s X# R6 ?& C" f1
8 r/ P T; \ X" F! |1 l2! p: s+ c- I, x9 G+ K$ x& G; N
3" ~; L9 E7 L; t, N3 ~
在极少数情况,时间戳的格式不满足转换时,可以强制使用format进行匹配:# f0 m8 @7 y2 n3 m* s' o
# `+ x+ i4 F' D$ R0 p9 C" ktemp = pd.to_datetime(['2020\\1\\1','2020\\1\\3'],format='%Y\\%m\\%d')
+ N, Z f" y& m, z3 n: h( Rtemp$ s1 i# G3 G' {. C; |/ u- T
G$ Q3 p' v+ O; X' q z1 F
DatetimeIndex(['2020-01-01', '2020-01-03'], dtype='datetime64[ns]', freq=None)% q, m9 J# x4 l1 T1 f# l
1
& Z$ f) A2 ^- o5 r) Q* A27 q$ L6 } t) i7 \! R5 g" ] i
3
! s( O, r" x6 t44 B" q% x& X6 T4 q$ \
注意上面由于传入的是列表,而非pandas内部的Series,因此返回的是DatetimeIndex,如果想要转为datetime64[ns]的序列,需要显式用Series转化:
4 ?+ b& }8 ~3 A2 Y( k, N
5 g( l8 J+ Q) Kpd.Series(temp).head()
, f9 G4 {0 F7 Z: U5 w: [- F3 k/ I% ^0 m1 T, x
0 2020-01-01
1 Z: d" g, y3 i3 _1 2020-01-039 h7 @ U P' _# L& S; i& p% R v* u
dtype: datetime64[ns]
+ M' ?3 C! |1 ^1 Q7 g1
6 P! H+ O; o. i22 w* Y: l# i2 a% `, R3 ]/ H
38 Z( ?0 f9 u5 L9 Q$ h# X0 x
4
. i. N, @& h+ d8 a z6 e* l5
! t1 I, K J& T' M下面的序列本身就是Series,所以不需要再转化。
% G) A3 C" E% ~; X; B- X; Z1 F! J" M) Q
df = pd.read_csv('../data/learn_pandas.csv')+ O6 U3 D; |4 d7 @* b# V- ~
s = pd.to_datetime(df.Test_Date)% C( G4 ~9 p a& u8 q
s.head()1 X2 `. e8 y; d& |) M
& L! o' v7 Z7 r$ V
0 2019-10-05$ T& {& w# H5 X$ t o
1 2019-09-04( s# {3 K( D# R2 M L6 m% v
2 2019-09-12
* C6 b3 p* c. ~) F- c3 2020-01-034 I8 H8 _ R. y5 k
4 2019-11-06
6 ^) }6 h9 H/ d2 iName: Test_Date, dtype: datetime64[ns]; L1 M- w5 u+ ^7 N" M: s. \
15 E: z7 F! E3 u5 s7 P) w# _% {0 [
21 m" ~+ ?$ Q5 {, `: Z
3. Z# w8 x9 U! W1 q' p0 S
4
3 w8 }+ J) \5 L1 B, p) T6 t58 F. ~6 H% ^; o
6
' X2 A) l1 V! p6 [7
) U3 Y3 d) T, }! D- B9 J/ @) K8 O8
/ h B/ X {0 k9
' Y( Y8 b4 S3 _& G# m H10) ^/ V& |. E, ]5 I
把表的多列时间属性拼接转为时间序列的to_datetime,此时的列名必须和以下给定的时间关键词列名一致:
7 X C, `$ ~ i$ Z% I& f6 d- i7 vdf_date_cols = pd.DataFrame({'year': [2020, 2020],: t) G3 H3 z! d5 e5 j f& u. P& X
'month': [1, 1],( o* e+ F+ T/ q N
'day': [1, 2],1 [5 {) f4 M, l; Y( \$ w w; ^
'hour': [10, 20],, M4 _5 I5 U7 i/ E0 s
'minute': [30, 50],. @4 A9 Y$ a2 p& N6 e, w" H
'second': [20, 40]})
! X* L' Y" J/ k$ ?' w: ? M0 E+ Ipd.to_datetime(df_date_cols)9 M( y$ k2 R- y! x3 q5 B2 G: T
% r" j+ N9 Y% M( w6 A, w( G0 2020-01-01 10:30:20, b# }# a% ]# @- g; _# Y, z' z
1 2020-01-02 20:50:40
% a: x& l8 l/ k1 u1 k4 F- L" Mdtype: datetime64[ns]
* Y3 L/ `- }6 l1 K; |1+ k5 i' f# n" t
26 n# J: r9 I$ K9 M1 E
32 {( |; w# Y k* U
4
! e! R% M3 }3 s; [3 m5
& P* E- i4 a$ p4 A- `* ^6+ A! m& n! ?6 Z+ h) v. r
7
( r7 O% b- a6 ]" q* q/ T! g0 n$ |% I8
( F) @" |! X+ J0 f( @ v7 G9
2 v8 V9 y0 E* ^" b9 E* |9 r10, g0 n8 Y5 [: x6 d7 {( n
11
y7 V* ^6 h. j, M; }+ @6 t+ D1 Xdate_range是一种生成连续间隔时间的一种方法,其重要的参数为start, end, freq, periods,它们分别表示开始时间,结束时间,时间间隔,时间戳个数。其中,四个中的三个参数决定了,那么剩下的一个就随之确定了。这里要注意,开始或结束日期如果作为端点则它会被包含:
8 R$ I9 L% g" }' l0 l$ V3 mpd.date_range('2020-1-1','2020-1-21', freq='10D') # 包含: \ N, [( w0 J8 R! J( d
Out[25]: DatetimeIndex(['2020-01-01', '2020-01-11', '2020-01-21'], dtype='datetime64[ns]', freq='10D')
6 s5 [1 X4 g% ?2 B- j2 _+ D( @2 e; p$ d4 _1 Q& W, C, F8 ?5 _9 l# R( U
pd.date_range('2020-1-1','2020-2-28', freq='10D')
# t; D0 L, T9 EOut[26]: 0 S" ^) k' _. c8 e
DatetimeIndex(['2020-01-01', '2020-01-11', '2020-01-21', '2020-01-31',
0 P/ ^8 a2 a+ Y' Q '2020-02-10', '2020-02-20'],- V" P0 n4 y4 k! ]& J' @
dtype='datetime64[ns]', freq='10D')
' L; O$ Y4 s# {6 p
/ N2 Y" E7 v3 _# u1 W+ l3 \pd.date_range('2020-1-1',, q. C$ C7 w1 V6 v/ |6 _
'2020-2-28', periods=6) # 由于结束日期无法取到,freq不为10天
; K: X( W. A# A# _
4 D* R4 H' K# j/ QOut[27]: $ ?2 }/ X. |3 f3 K7 W
DatetimeIndex(['2020-01-01 00:00:00', '2020-01-12 14:24:00',' K/ f, ]; m# J- N+ b/ C
'2020-01-24 04:48:00', '2020-02-04 19:12:00',
. V' C- p0 ]( u- b% F% \ '2020-02-16 09:36:00', '2020-02-28 00:00:00'],, o% Z" x- x5 N. _+ h
dtype='datetime64[ns]', freq=None)
- v) J J7 j+ _$ |, p, V7 i d3 B. Q1 W0 z7 J, {
17 {# M' Z/ t I; P0 P( k: L
2
/ X/ z0 C/ Q4 P) D! y; w- U32 ~7 g! Z/ O3 e, j6 H( g, _
4
! D/ Z1 z: X+ q8 l K9 B1 V5 M) _6 S: K: ~* c2 G+ ^+ K+ E. v
6" h# F; @% ^, s
7
) [ D6 T5 H7 n8 w2 v. @+ R; v8: }$ _: ^: P c. R. j( R
9& U3 n! J5 |8 S7 g
10
- x S t! g1 P) T2 S- |* B& b' @11- l' y5 y1 l/ |" R- R
12
+ X% N7 v$ _0 }$ t% @13+ n* \1 t! s. n/ \/ v/ F0 x$ q
14
+ r% q1 [! c% F' u3 }% u7 z% @) x15; X' J/ L% H- Y4 W
16) h* ~: H: J( @4 U* l- R# r
17
. ?( b* N# w! S/ w+ u这里的freq参数与DateOffset对象紧密相关,将在第四节介绍其具体的用法。
% a$ @' {4 G( _2 y5 }& ]- q0 c, M* j6 h1 @/ Q+ F' `
【练一练】+ p6 q @: c6 K7 Q
Timestamp上定义了一个value属性,其返回的整数值代表了从1970年1月1日零点到给定时间戳相差的纳秒数,请利用这个属性构造一个随机生成给定日期区间内日期序列的函数。6 ?' F5 y- N- `' L% o7 m: J/ }
" R1 h8 g3 j x* o
ls=['2020-01-01','2020-02-20']
+ n# v% k. I P# Idef dates(ls,n):
% v" ]+ n% b e; g3 | min=pd.Timestamp(ls[0]).value/10**9& y: a( y3 `" r! e0 T
max=pd.Timestamp(ls[1]).value/10**9; }5 I' [0 o: k/ [$ {4 v
times=np.random.randint(min,max+1,n)
4 A1 G0 s2 j$ B return pd.to_datetime(times,unit='s')% h4 l( M3 ~! o4 I! W! P
dates(ls,10)
4 _- a3 s8 g* r: w! S% H) ?
4 @4 D; q, G5 p {0 o. ZDatetimeIndex(['2020-02-16 09:25:30', '2020-01-29 07:00:04',
5 i. i7 y0 z$ d- `7 b2 P '2020-01-21 12:26:02', '2020-02-08 20:34:08',/ U, w% v- Y; L J, Y% \! `
'2020-02-15 00:18:33', '2020-02-11 02:18:07'," n5 Y+ d; { M6 i
'2020-01-12 21:48:59', '2020-01-12 00:39:24',
& p1 {( J7 X9 l( n '2020-02-14 20:55:20', '2020-01-26 15:44:13'],
+ P6 y( _1 l& q' ]9 y dtype='datetime64[ns]', freq=None)
- _" f. P; }% d) l1
! R* G F9 [( R2- F2 ~; U7 K3 w& r
35 U- J/ _1 K- M/ t6 T; O, W1 t4 ~
4# E6 I0 d6 G* W5 u- m
51 ~( _1 E# W+ E y2 m' d
6) I2 }1 X6 K2 ?% u
74 e6 j$ H6 t5 ^* a+ P
8
9 |. c1 g( d) W- O1 c* ? w5 b) ~9
6 r1 _+ V8 T$ y10( O6 Y3 V; T- L+ A1 F3 Y
11
' E7 W! ~# `( o0 @7 |% h12
! C6 t& J# b( }- D. ]$ p, C6 h* p! @13
7 |7 x8 b; q) d+ N1 L J4 @! x14/ W6 H2 `# _% Z Z: f) l
asfreq:改变序列采样频率的方法,能够根据给定的freq对序列进行类似于reindex的操作:" J2 v4 J# g) A b) _6 C0 G
s = pd.Series(np.random.rand(5),
0 |3 \* ]( X5 q2 K k% \) D index=pd.to_datetime([
# L# P2 q1 ` F' d+ u, l( A4 Y '2020-1-%d'%i for i in range(1,10,2)]))
7 ?8 Z7 @6 j9 ~" W
8 h0 l- s. ]' d9 i, t9 A4 I t
: s$ h3 _$ D) M# ]: Ys.head()
$ [% W% P+ t8 b7 A+ J8 U* NOut[29]: 4 @/ U, ^7 q+ L8 |' H3 N6 [! o
2020-01-01 0.836578$ I/ g) e/ j y4 U& o! z
2020-01-03 0.678419
) V% r- t, ^/ U* N. S' z/ v! T5 |2020-01-05 0.711897
f5 W4 r* w( w" o/ E1 K2020-01-07 0.487429
1 y; m) j. L1 O% z6 j) p* |5 R8 D2020-01-09 0.604705
. B/ u! V* m& l$ {( C. G5 Rdtype: float64! Y1 j! L2 x! [( \! f/ y$ X. d. u: k* R
# g: T5 H/ @% p9 B8 e+ c' p& u' I/ ds.asfreq('D').head()
5 B+ p0 y A9 L7 lOut[30]:
+ W$ m. z. I& q% |2020-01-01 0.836578
$ ^# |& U9 i; @* q( @. h2020-01-02 NaN
& q( q; x2 i5 W* R- S5 I- ~; X- G4 T! D2020-01-03 0.678419
; Z0 k' H' h2 [2020-01-04 NaN" Y) z6 U7 l9 r: Z! j7 ?0 g' }& u
2020-01-05 0.711897
$ x& k! ?5 E; X* l2 V/ XFreq: D, dtype: float645 G" J( K* |1 y% p+ X+ D
9 A8 R" o1 i: [8 L* ?3 Y$ vs.asfreq('12H').head()
7 Z# o/ s- k6 Z; l3 G( {$ a$ t( sOut[31]:
6 I6 t0 @* z9 g) E2 p/ m2020-01-01 00:00:00 0.836578" f" W. N" \5 `/ m6 Q
2020-01-01 12:00:00 NaN
6 H! c2 C* l' k7 r2 K2020-01-02 00:00:00 NaN
, c. }6 P/ k; }2020-01-02 12:00:00 NaN
$ e1 K, n1 v! o! B8 p2020-01-03 00:00:00 0.678419
7 p! M! n6 B1 NFreq: 12H, dtype: float64
( H9 }9 `- A) }$ ]+ g
/ X; l0 N2 n0 ]% k5 L1! k2 H$ Y$ K; ^7 i
2
- q% M8 ~6 j K5 [7 K37 E. r7 f0 f, V8 d
4( m U$ c1 X( ?6 @# G& |
5
- e9 C/ n) M4 |9 v6
1 Q; G5 N. g( K7, B2 \( F0 G* F& P) o
82 v6 M9 t; a7 G% B0 E
9. L% v, I5 K3 Y# X& ]0 e
10
0 [4 q, s1 N$ Q; M: G11
3 i1 l4 K# k4 I) o: s12# ]6 P6 a* q0 h+ t
13* ?8 ^5 @! @$ ?3 }) A* X; M& F
14: C4 h2 Z1 r/ w" J0 T/ I
15
+ h& P X$ r9 V$ \16
: q/ f+ y/ X# Z8 E# ]. p17! X+ ~3 \# @, G7 Q# b
18
$ D5 h H* j8 Z' u6 G# O19
# }& R7 g4 z9 h$ O3 G! `, ^- ~- y20 ]8 i2 Y8 x4 F7 _$ w# l- j
21
3 Z9 C8 j' {) B' j' Z8 W$ ^. ~227 N: B- O0 s( J+ O& o
239 l; O% Q0 _' D& A* }* o
24
) U9 Q8 b. Q g. o4 b3 T25
9 k% s5 g- m+ ^. ^9 n26
4 f% v1 V) i' P: x7 I9 I5 D27. {. q* b/ l6 ^
28+ r9 }! A. G. {5 @
29
0 e% m6 Z6 L, s$ c. d30
6 y' ?9 L& Q* _2 _- d& j, K8 p31# H! C7 _ T% I
【NOTE】datetime64[ns] 序列的极值与均值
! K+ e( L5 A) B h, F% N 前面提到了datetime64[ns]本质上可以理解为一个整数,即从1970年1月1日零点到给定时间戳相差的纳秒数。所以对于一个datetime64[ns]序列,可以使用max, min, mean,来取得最大时间戳、最小时间戳和“平均”时间戳。* i* h# F' Z3 A& m U# H: o: P$ l
' Q! G* j' h; ^+ V# M10.2.3 dt对象
, v3 ]# g3 x5 s5 } 如同category, string的序列上定义了cat, str来完成分类数据和文本数据的操作,在时序类型的序列上定义了dt对象来完成许多时间序列的相关操作。这里对于datetime64[ns]类型而言,可以大致分为三类操作:取出时间相关的属性、判断时间戳是否满足条件、取整操作。
3 S4 ^ e$ ?4 e1 o
/ m) Y0 K4 [) \4 c- d第一类操作的常用属性包括:date, time, year, month, day, hour, minute, second, microsecond, nanosecond, dayofweek, dayofyear, weekofyear, daysinmonth, quarter,其中daysinmonth, quarter分别表示该月一共有几天和季度。& D( W u* K% l7 ]
s = pd.Series(pd.date_range('2020-1-1','2020-1-3', freq='D'))5 v8 O- p5 S4 d, i8 ^5 _/ w0 M
1 S. S* r& H5 r2 k9 ns.dt.date
% _0 ^" D9 h. O* n1 ZOut[33]:
: i4 k, c# A# I5 x' u; ?( q0 2020-01-01% \ [7 Q! {3 V& E* \/ d
1 2020-01-02
/ J( ^; l7 e: {2 p8 J2 2020-01-03
2 J) n" u1 I6 ^/ G. k: ydtype: object, ]( f6 r0 c# j& v+ I1 I9 `) J* H6 |
0 G, X# O4 l$ `3 {3 L8 a
s.dt.time
. H- o1 ?: {$ \: ]2 I, JOut[34]:
! V% V! B3 U ~$ b0 00:00:00
; Q! P E+ u' |. C' v% v1 00:00:002 `3 L5 I) G( G) i% p# C% \, b
2 00:00:001 J% w6 K0 d l' F
dtype: object3 K3 b$ H' r* b+ ~
8 L+ h5 T" [2 M) ^' u7 m; ms.dt.day
9 t, j$ P: b% J# i) Z2 yOut[35]: ; m7 J& k6 b5 q. ]0 ?
0 1
- F [2 m4 h/ ?8 i8 c1 2
' E$ d, ^" t* Z* j. a, `2 34 b! x# b. T& L5 a" h
dtype: int64
$ x* N( R: V% o8 h+ Q8 ?: l1 f9 i3 b4 F
s.dt.daysinmonth) y6 X- a2 t' g0 W, Z G7 S% M2 t! {
Out[36]: * j% Y. |8 E1 ]
0 316 C c6 M+ V" P6 ^
1 31# ^+ N& J1 V8 @& l! S' N/ w- K1 S
2 31; e) L1 H+ q. `
dtype: int64
* u8 G- y3 Z7 P4 C3 i6 u0 `1 L, R# } H3 R+ X* a. d0 _! y
1
% {$ {& K& |0 H25 I* h9 K: G3 _9 L7 C9 c, m2 G
3# x! q! u3 T7 t0 x) s
46 r8 w1 ~8 s# ~+ }/ [% T
5
- @+ B- i5 {, A67 q- O N- D3 [& ]4 g& n
7! k# \% q# Q) k2 I
8
0 b* l& p& B& `; o2 j5 c; Z98 o7 J9 I) S `" X3 D$ g9 [: E
10
# v3 g* f0 V' j7 @7 k112 B6 V: z: U- j% T
12
( I$ c5 \. d7 e/ B+ I& a, S: e3 K137 s# H) f# V D; W7 J1 j
14+ ]: a) i- x- A
15
1 G) ~: X* w8 z8 S; l: l1 q* e16
0 t# J- J( t0 `4 o17
, A3 L4 `" [. T$ [7 U1 v- H18
+ B( z b* y$ p) ^/ K. E19
; y7 A. n) O1 c% l* P, |6 y20
# U* Q0 y( s) J6 H% [8 H21+ Q9 v. N8 X- h2 e* b# e3 {
220 T5 W6 I# n6 X |
236 Q4 z8 w1 J: g8 S6 {* T( f" O3 |# M
24
$ c* n6 g1 ~8 O$ ?8 O/ Q253 s% Q, c- I/ @8 d
26. l5 q/ ~; `" s) I. [
27" R% X/ n% y+ ]: R; e- N# W- u
28
* _" h# s5 `$ d0 w: N) k: R29
# h! E% W) D( T4 q1 I7 b/ m 在这些属性中,经常使用的是dayofweek,它返回了周中的星期情况,周一为0、周二为1,以此类推。此外,还可以通过month_name, day_name返回英文的月名和星期名,注意它们是方法而不是属性:
5 \6 X' A' ?4 O# g
! Y6 v% ^; V T# j& M- Ps.dt.dayofweek8 k; q! }. Z* s: N. T7 n
Out[37]: * }# j0 v8 b. x6 C) r/ a
0 2
- Z5 Y" e0 |* U* [5 ~1 35 P# N. e8 R1 c7 `+ Z! }
2 4: V/ E% t* I/ x- d# A1 `
dtype: int64
6 C7 Q1 ^) q0 I' e2 a( j
2 j0 y# R* X0 b } {4 R. \s.dt.month_name()
; b( z+ R; L( l1 j0 Z* f. mOut[38]: 8 ?; [/ `2 X: b
0 January& I9 i$ p& H W% k
1 January
! f5 p! `9 |5 c* W2 January y t5 E2 |8 i/ h
dtype: object0 j8 O- t! O: l2 u. W
: M' @6 ~- ]4 L2 v* P7 g7 I1 E$ S
s.dt.day_name()% e, r5 F" p, F+ c+ {
Out[39]: 5 i% M7 o3 m) t1 T. S2 a
0 Wednesday
/ Z1 ^7 s4 f) [) C1 Thursday
* @, ~8 t! y, q. n+ F2 Friday2 U {4 X# h. ]" E
dtype: object
# U, _8 [% `9 r$ k; v/ w# H; l1 h# y2 u5 I- _! ~
18 A7 V+ b: N: L/ {: T; E
20 |+ v: A) c4 e1 k4 i
3. j6 [8 r, s0 A8 d+ ^4 o8 g
4( q, k! ?& F" B0 t( }, z! o3 B& k+ {
5
* T' @$ j l3 @ M6
; w; A: M$ C& C/ N e% R7 e7
9 D" ?: } N8 u* f3 i8
& ]$ j# w Z5 |/ p8 {9
) X- D5 u2 L2 P: F6 ]7 M& ~& B& M100 D' y/ w8 X" K
118 _- @, b; F! @# Q. q2 q% L
121 ?5 W' V" H' C) _5 R/ y
13$ p1 Y" X0 \$ b+ N- W
14
: o, m; F: t5 p! V2 O% O, D15& Z- ^4 d: J; H
16+ t+ b1 ]* C: _$ h4 v
17
% _2 ` P# J% z' U18( c/ a4 d) t2 k+ h
19
# s% i/ e* n+ [5 z% }% d9 o r20
k6 f4 p( ]& }7 ^第二类判断操作主要用于测试是否为月/季/年的第一天或者最后一天:
3 V: `2 |9 h' `0 Q- o, P, m" xs.dt.is_year_start # 还可选 is_quarter/month_start
, [, _( B& a% n5 [2 cOut[40]:
# D" Z* t& d8 G0 c0 n0 l* K5 b0 True S+ J* g# V; a6 g! r
1 False* W4 x4 x& i3 X& h8 m r9 V3 Y1 h A
2 False' t0 B( h. T1 D- L u# R
dtype: bool
, @/ @& r% a' o7 R" r2 ^3 R3 ?7 k3 o, E) e( ~. D
s.dt.is_year_end # 还可选 is_quarter/month_end) n8 ~. S# E: B1 B0 b
Out[41]: 9 {0 U! S. u9 t% K, d/ \
0 False9 e1 p8 f2 {! R) e- v
1 False$ H% g, N( m$ L# t
2 False
3 J3 Y: L `' |; h6 ?/ Ldtype: bool
0 B z( K# f/ L1
# ]1 y. a1 F; E, |9 p. l2 d& _, Z& x8 g; q1 s9 A4 Z3 g
3& ~0 @$ n( H f( n
4; N' }1 t4 o1 E t+ i
5
$ t, n! I: }: {; h7 A! t0 p& `6
% m! o- y, E* W6 `6 N9 \5 i7/ c4 U' S$ ?. u* f6 e
8+ L5 J3 L, A3 W5 `. ~
9+ Q; n) M0 d \' H, w3 d2 }* j
109 L* K# g0 R1 H( ~. h/ T
11) B; I3 Q! M& B) i! L. T# x
12
& u( _% D1 ]; p$ \% w0 T13
1 @7 ?2 H# ~7 {- ?: a, q8 j8 f第三类的取整操作包含round, ceil, floor,它们的公共参数为freq,常用的包括H, min, S(小时、分钟、秒),所有可选的freq可参考此处。
4 ~* H$ V! f) d8 D, M3 W/ M; Q1 \s = pd.Series(pd.date_range('2020-1-1 20:35:00'," e" D5 H9 j) k0 g- H. h
'2020-1-1 22:35:00',! e- ?0 d3 ^% R7 M1 _' X! u
freq='45min'))* c/ q3 p/ u9 q6 u
! r% ?+ E# S3 r7 {0 U
6 B5 n' v, s8 M' Q8 `4 S6 e, \1 fs
G8 D& S+ s& \; v+ s3 r5 [8 UOut[43]:
! @; F r4 F$ n. @' s0 2020-01-01 20:35:00+ r; M `5 s f% d
1 2020-01-01 21:20:001 @- b% e3 L0 T& d# [
2 2020-01-01 22:05:001 Q5 b0 T7 F) v- o( Z& d. N5 n8 f6 ~
dtype: datetime64[ns]
8 Y, `" `0 v( H6 }6 G H* N3 i' j! L; x5 N
s.dt.round('1H') ]. E' z. b: V( h6 @1 z2 l& @
Out[44]:
5 E J8 Q5 D$ Q0 I7 r& f0 2020-01-01 21:00:00
- m) M/ d, T# I- q% T1 2020-01-01 21:00:00. Y: A6 q$ q% H0 M5 u
2 2020-01-01 22:00:00
0 n& M( ` a9 I. ndtype: datetime64[ns]5 m' X/ n" y2 g# D3 V
4 a" H4 ?, Z7 d+ I- d7 y+ f; q3 e8 ks.dt.ceil('1H')$ h* i( P* f2 ?: D- b; G4 P4 A
Out[45]:
2 l$ T, l% Z1 _9 p, {0 C" P- i0 2020-01-01 21:00:00
4 E$ R' b- i' h( H1 x1 2020-01-01 22:00:00
2 I. i$ H& c" D2 2020-01-01 23:00:00
) @. j. X- W. Y5 k9 `dtype: datetime64[ns]% a! s) S) O- I3 G: O" C* o
& D; N" z3 q, d) xs.dt.floor('1H')3 y. {0 [: m( K+ U, b
Out[46]:
: J6 B0 J1 v6 j( j) x$ v! J# Q9 r0 2020-01-01 20:00:00
- a$ G8 D7 I- L1 2020-01-01 21:00:00$ g) ~. I6 d& V ~: @) N. W
2 2020-01-01 22:00:00
$ v' S. K/ `1 c; udtype: datetime64[ns]
# c8 E5 i3 B6 ]3 h0 K/ a/ k4 t% T1 @* O7 K$ |! q" }0 R, l
1 b$ g4 L. `) s" l1 a8 c- U
2
0 G7 o) \1 C- H z3- k4 _1 G+ w0 }4 t7 h* t2 e/ N0 H' C
4" t) j& p# i) T
5+ U+ _4 q& P4 A( C* Y) c/ ]9 Q6 u
6
) ~# I! }# I9 a7
/ m$ p2 k2 }5 k' g- m8/ f8 I; U! ]) i. P
9' n! k1 y, s. `4 B( ]
10, m/ I1 ^2 L& T; m" n
119 f; e& @$ L c" {) _
12
- U W- _% x) ]7 b k( q: h, F138 ^9 o) z2 A7 y. l$ b' m- p4 {; L
14$ V9 u0 C, I. z( l3 b# R
15
# E% a8 [( b) D% F3 ~. c/ G" E16* S" J9 g9 [8 ]* H: u$ ~ H
17
9 T0 b' @5 J# c' Z9 h$ c18$ e+ T. `/ j6 \; x5 D* C
19* z$ w% Z9 G7 Q, r
206 Q# e/ m7 w& Z! Z( S
21) {8 I6 C, x/ c4 C+ T: d% I
22! @! l; H" j+ }3 v1 `' B
23
3 @# w' B& u+ W8 O8 w24: O8 Z/ ?! M+ G$ K3 T
25( R# a* Z$ Q! D
26
" E$ T% E7 w5 \5 j9 { B0 v2 Z3 x27
% ?% e# s" ?/ [1 d28
6 I1 U6 c2 Y3 T' v7 v# v297 a# I, s6 J4 o& O0 g
30
l% N" q' w7 J' m7 @31
9 J* B. w; I/ F, U9 u" |) l5 w5 G32
0 g# b+ n' m4 n2 I10.2.4 时间戳的切片与索引
; p' o9 E4 T6 h0 T$ x* k( m 一般而言,时间戳序列作为索引使用。如果想要选出某个子时间戳序列,有两种方法:$ S6 K! B3 C& ?! e2 L
: f8 z, ^ l8 e4 r% a! c4 _7 ^1 [利用dt对象和布尔条件联合使用
( b6 D4 u. ^" o利用切片,后者常用于连续时间戳。
: `* W& a0 L) y$ G9 a8 Ss = pd.Series(np.random.randint(2,size=366), index=pd.date_range('2020-01-01','2020-12-31'))
* l) [" |8 H% T" P& T7 U! w0 bidx = pd.Series(s.index).dt/ e6 e& F6 O6 U7 `. C
s.head()
$ p4 f( \; T& B2 Q* Z Z" ]+ M6 W2 {" j+ A( D4 e2 U
2020-01-01 0$ m( k i8 a: y9 E2 ]
2020-01-02 19 m" t* o$ c& C+ p4 h
2020-01-03 1; [, A7 ]: Q# N+ c- ~0 C
2020-01-04 0* ~/ B3 z2 l! c4 `5 n
2020-01-05 0! ~5 E7 f1 d, X: K
Freq: D, dtype: int32. x& \. p/ ]+ [
1
2 F+ c/ H; C$ u$ v8 U' J2
4 T2 s% [, x2 E+ w) C& ]8 }3 M2 z* |/ v! a, }+ Q* Z3 w
42 K8 [0 h6 Z3 z. v1 O4 ?
5
& V: P" X4 Y' ~# x# S9 J6& i5 w7 u1 d3 T" { N
7
! V( ~; {, d9 F80 n$ `6 \* G2 A
9
2 r2 \: Z0 D, i10# P9 M6 V# C3 @: W
Example1:每月的第一天或者最后一天3 \$ ^8 e0 Y1 M" I" c
5 H$ x' }7 Z! a, h8 X c9 z/ ]2 [s[(idx.is_month_start|idx.is_month_end).values].head() # 必须要写.values0 X3 o+ F) t/ K8 V8 e2 ~# ^4 K. Y4 Y' U
Out[50]:
; `9 |4 p. C( C' g! X' Q8 }4 T2020-01-01 1
- ~: I7 y& R! X4 Z2020-01-31 0
- H1 [) T/ s- R, F2020-02-01 1
: s" H g6 s( q" U2 p( g2020-02-29 1
1 q: y4 n# y" q/ o7 M2020-03-01 0
5 ~0 t2 A1 a* \! h6 Q/ i4 k; ]dtype: int327 Y- E% Q, k! F
1
9 q# T; P. G9 A& k6 b5 u2
7 \( R& q5 ]) x6 _# U8 O3
& f' }9 R9 d+ H4 C: E! a D a& y4
, [( h/ x" G E! H) C; f/ |5" L0 v$ w/ G, q
6
/ r$ |+ F* ?5 J# Q7
/ t( h8 i8 H$ M0 r: ~& ?8
5 k: N3 \% J0 y. N* ?Example2:双休日& ]$ D' {* y {. B) l
9 e' e, v7 x9 C/ @( F- n. }, Ss[idx.dayofweek.isin([5,6]).values].head(). c) ^, Z$ l* l& J
Out[51]: 5 a2 S: `% Q. f( z
2020-01-04 1
! g( y, A" ?' l, c; h q2020-01-05 0
* S2 W" W1 Q7 t# l2020-01-11 0
$ B/ Y* L; A- K1 c" k% f2020-01-12 1. C: w! R$ z/ C% M) t
2020-01-18 1. L- I: j/ Y+ p$ o) T. \4 U
dtype: int32! H. h7 f& d ]+ S! P+ f& t, r; k
13 ?; t0 X* L% y P
25 P% t' U6 A# J$ P( ]
3" Q" m/ z& B4 x# U
4
) T; N8 R0 B- h) L: S5
{) d5 B1 v s4 ?6/ c7 f' X+ m5 _
7
; t8 D0 p4 _2 D5 y- e; q8
5 C/ ?' C! ^% e' ^7 g$ HExample3:取出单日值
* E' {; |1 I- Q+ o5 i7 [* V, U: [3 D: l/ \ u
s['2020-01-01']
" W) B9 Z: h5 S, b9 eOut[52]: 1
2 b% r7 L2 a/ t# p# V7 s- k
( l' J/ E2 r$ J2 q5 t0 Ws['20200101'] # 自动转换标准格式) c1 [2 {% q& {; }8 _0 w W
Out[53]: 1$ m) k! N4 A; `* S' h5 E
1
2 U6 V! u n5 e% X2+ Q& N6 |1 y+ v. Y
3( q% C9 B7 X: N7 H; | a
4
1 @3 P7 p7 F! E0 ~" o- p5; C! l: @3 k$ Z! l; G( i
Example4:取出七月
! w& j: i" P! F2 C# {. U# ^
$ j# q% \" L u/ T3 g A$ Y6 zs['2020-07'].head() \7 y+ f3 V: f& C9 s
Out[54]: / Z7 F" v3 H1 k- `. j. e0 H
2020-07-01 0
9 K! f0 P- d7 M/ w: _1 B* @0 v2020-07-02 1' O; w# v) z. D/ j* h `( m- m7 D
2020-07-03 0
% g0 z3 ]/ x M1 ^: {$ z2020-07-04 0
. J6 ^ U$ g3 r7 n: T% x- g& j8 B2020-07-05 03 S6 P1 @4 e; m+ ~! V! C
Freq: D, dtype: int32
2 e& C3 K7 R! j/ E: N" g1( C) P/ ]. D p* ~- v
2# t9 X% f! O" u% a8 K0 O+ ~
34 n/ i; F. J, i1 v$ H. V
4
e" a( C8 Y5 X% V/ y1 Q51 T/ S0 ^7 K- l6 j$ h
6
# s0 k* v% b% y* e3 b1 Z74 C+ Z" Z0 ^ u+ _- c' I) L
8# k& H! O! ~, j Z2 l# m7 l
Example5:取出5月初至7月15日
: U* u8 e# V5 I$ T. w: M) |% O8 s0 p1 z9 I
s['2020-05':'2020-7-15'].head()# u7 R, P0 X1 I) V# D# u3 `' M
Out[55]:
( y' U* _* q1 `' B- [) ~7 q! v2020-05-01 0/ l# h. S4 H" N, M8 N ]
2020-05-02 1
7 N2 I3 r7 [+ I2020-05-03 09 |9 p: h6 n6 o) }* g
2020-05-04 11 ^: P( C& U* j2 b& l9 V5 ~
2020-05-05 1
( `% V0 K( Y0 B8 bFreq: D, dtype: int32% |9 x: \+ o7 c9 S' R: e
, }% W( P1 L% z: ~% R
s['2020-05':'2020-7-15'].tail()4 F ?; t2 y2 R6 [
Out[56]: 5 v9 |, q7 ^* n$ \
2020-07-11 0. r+ e* n; Q& g/ I& {
2020-07-12 0
; \8 K4 _4 [7 M5 o1 B5 S" F2020-07-13 1" U8 I8 ]: \, D& v9 r! t7 ?8 [
2020-07-14 0( r, r# k5 }0 n/ W
2020-07-15 1
8 E* d, e! O! w9 `/ Y6 FFreq: D, dtype: int32
0 Y" K5 n+ ^; g, V# c& a) Q
7 \) \* y$ p, g/ C0 j1! |! ^/ l6 r* j i% }; `6 N
2
9 n- J v) V# ]5 C& T6 s& c3( j8 L/ _* ?" v1 T/ [6 \1 n0 M& I
4$ C9 {, x' k+ d. U
5& B( G- `. k& ?& k0 b. Z
6
. D; j- X2 C1 Y" v" O7+ t; n+ k4 X, j- ?$ \) Y
8
! _+ G% W" P) s2 s% {9! [' I) q# [2 n7 h t! f
10: n7 F( I' Z4 a( R8 i2 l0 T% l
11
; @& n* D: f- o7 ]$ a `3 a: B12& [( U! ?: { M$ V
13. D* }0 d6 E% L0 P; H3 t
14
8 q. _$ c: u$ _% w( V$ y9 s( W15
7 H3 N# p: r3 }. h3 C0 B167 e( k, ]" v* r
17: p' j0 T3 A- k% l! O6 A! e
10.3 时间差
" Q8 _) Y/ H% P- d10.3.1 Timedelta的生成
* O7 K# Y( d+ ?pandas.Timedelta(value=<object object>, unit=None, **kwargs), y2 v7 h5 q0 ~
unit:字符串格式,默认 ‘ns’。如果输入是整数,则表示输入的单位。
4 X1 |; H( f# X) e8 k 可能的值有:
: v: E0 x m! I6 Q0 p) S, y
( n& P! d7 s: o5 u‘W’, ‘D’, ‘T’, ‘S’, ‘L’, ‘U’, or ‘N’
6 C. Z b! D1 c2 ^‘days’ or ‘day’% b- `8 P P" I2 [' L2 e$ E0 s
‘hours’, ‘hour’, ‘hr’, or ‘h’
0 z; }) ?2 J6 B) A7 J9 d‘minutes’, ‘minute’, ‘min’, or ‘m’
v% Z6 Q: }+ ~8 A: I7 u‘seconds’, ‘second’, or ‘sec’) Y m/ z4 b! n3 l
毫秒‘milliseconds’, ‘millisecond’, ‘millis’, or ‘milli’4 ?$ d4 y0 n' F* `: M
微秒‘microseconds’, ‘microsecond’, ‘micros’, or ‘micro’
* V8 F Y! v0 E纳秒 ‘nanoseconds’, ‘nanosecond’, ‘nanos’, ‘nano’, or ‘ns’.
6 _( {" H0 d* a时间差可以理解为两个时间戳的差,可以通过pd.Timedelta来构造:
, k4 I' i. A3 }pd.Timestamp('20200102 08:00:00')-pd.Timestamp('20200101 07:35:00')$ H4 | ^$ k9 W( n
Out[57]: Timedelta('1 days 00:25:00')# Q* C- F& u: a
6 r1 m! L# D; g5 U% \ Tpd.Timedelta(days=1, minutes=25) # 需要注意加s' Q0 X. R2 [ b6 c8 b
Out[58]: Timedelta('1 days 00:25:00')
( k' K# h& e1 s' ?* `
1 s' Q& \6 K! H# @pd.Timedelta('1 days 25 minutes') # 字符串生成
% f6 ?1 O/ \3 V5 _Out[59]: Timedelta('1 days 00:25:00')
# q6 ^8 x9 e r0 u3 f# t0 r3 x8 ]- S8 G
pd.Timedelta(1, "d")" b' O( C8 ]" r S" l
Out[58]: Timedelta('1 days 00:00:00')
" n' [2 v: t: x0 W- l1
- [7 I& h, |) T8 k% {: m2
; s: x! D6 r0 r6 ~# K% Z& u7 U- o8 ]- m3
, W' J; a ?# `5 l/ ]! v2 L$ b4
/ w0 D6 i: ~, d6 `- a% K5
/ J8 s1 K" J6 h' {0 a9 P6
3 A! m: B) p$ b& d9 T, X1 A! }7* h% E" `" b! _# ]1 t; x4 z
8
4 M' s4 x. B5 v9
0 t5 x/ s: q* n, H6 B t d10
7 Y; e% V& X- E/ G11# K6 I( Y2 b' k( k
生成时间差序列的主要方式是 pd.to_timedelta ,其类型为 timedelta64[ns] :. s& a5 X6 {! A1 N2 O
s = pd.to_timedelta(df.Time_Record)
) c7 |6 K% i6 @ ~! |4 A' a6 A }+ S+ C4 Z* [
s.head()
3 A2 J* G+ L$ g- c3 |# GOut[61]:
& m0 v6 `# y3 w& e0 0 days 00:04:34" [+ A4 j8 C; M9 |3 G- S
1 0 days 00:04:20
1 ]. k! R! h$ F) e, m2 0 days 00:05:22! }! }8 R. N# K% U0 S
3 0 days 00:04:089 R- m4 M T' `% B* d: ^' F
4 0 days 00:05:22& Q8 D7 u8 {4 [* A5 u) ?0 J9 `
Name: Time_Record, dtype: timedelta64[ns]; T9 D2 R# T( [! a. `- R
1; }/ N2 P! \, |! r' r
2
3 [, ~) L) L% T) h8 D2 S: `3
0 c2 }$ M5 u' x+ V1 ?% u4. y( D9 {: |- A( v
5
" s9 \+ k) |! x( [/ F' ^6
; F3 U6 j2 i, L& O6 h# i, e+ M71 }+ e: T; V$ Q% [
8
% l! g( v! {- y9 H5 B% k! Y; ^9& }( m* h( J: g* j/ k1 Y
10
$ b2 p" ~9 b/ X5 [/ t& r与date_range一样,时间差序列也可以用timedelta_range来生成,它们两者具有一致的参数: x6 ^& f, }" R- h' p( C/ t
pd.timedelta_range('0s', '1000s', freq='6min')% y* f; ~: k. T
Out[62]: TimedeltaIndex(['0 days 00:00:00', '0 days 00:06:00', '0 days 00:12:00'], dtype='timedelta64[ns]', freq='6T')
; t3 C) C6 b$ T; q+ @" M( S( N1 A, H: G' H6 P& w* l/ m; s9 C* s4 i
pd.timedelta_range('0s', '1000s', periods=3)* ?, ~; {$ h4 k9 k- n0 _9 k
Out[63]: TimedeltaIndex(['0 days 00:00:00', '0 days 00:08:20', '0 days 00:16:40'], dtype='timedelta64[ns]', freq=None)
& O- i! p7 j5 `: C9 D1
7 |7 G( W; ^! ?4 q" v! p: u0 M27 R" I. f, d6 y! q
37 `% a/ f" Q9 r( @
47 A8 _7 X1 \6 M3 {, B; L y
5
h, h1 K6 z4 s$ k对于Timedelta序列,同样也定义了dt对象,上面主要定义了的属性包括days, seconds, mircroseconds(毫秒), nanoseconds(纳秒),它们分别返回了对应的时间差特征。需要注意的是,这里的seconds不是指单纯的秒,而是对天数取余后剩余的秒数:
! H! `( {2 i" n% @' m% M6 c. b# cs.dt.seconds.head()6 P: ]) c" j" I4 L; k/ c
Out[64]:
2 w! ?0 p2 ]3 j! w3 k0 274+ N3 A( x6 ^ r5 M1 q+ k/ v; ~
1 260
- }, a3 P6 t8 \8 l$ b! p I: g4 o" \2 322; Z) Y4 @- @/ v) \) {
3 248
, s7 k ~9 w" s8 d' m4 3225 Y7 W# j7 p2 ^ f7 O
Name: Time_Record, dtype: int64
2 }9 N9 n3 n5 R2 b( I1
+ k9 |! b. }6 H" ~4 s- o1 A8 w2, E5 |+ J! c; K. @
3. A$ d2 o3 i- R |- k i
4
/ [% K0 E% W0 g- U: e9 Z! @! g5
( t; w, W: F& S! G- I" T; q) F6
" k* U1 i+ {& n7
% G5 ~# N& U# R: L: B) g B8
h/ u- V" v0 e& u9 {* L8 Q2 @; w如果不想对天数取余而直接对应秒数,可以使用total_seconds# |) Q. N7 n, Z/ t
# B5 o; ~+ m* m7 v" q
s.dt.total_seconds().head()
4 x3 k9 t" O. \5 xOut[65]:
8 f% T, G a4 g' U. D0 274.0
9 h* b/ T, y( r% E9 B3 C1 260.0
4 ^0 z* M; y( w% I, i2 322.0
; s) s% _9 [% Z3 Y! P3 248.0
* j( E1 l& w" a' [3 w4 {4 322.0! L3 W% r. t8 P; k. ? v. V
Name: Time_Record, dtype: float646 t% q; j/ g0 _! E4 L2 N& w
1: g X- C3 Y4 T3 b n) G
2* W3 z: ~* i+ [9 C$ m. N
30 h3 D9 _6 p5 m- A' M
48 I3 ~% `9 }* ]( p$ h# x0 B; k0 f
5
* l6 t4 G! E U( b* ^9 d66 `# C0 }0 E* y* P" D8 W
7
9 h! L+ N1 t3 L6 b/ S8 ?. X8
/ U: o: e& K& u8 u$ @: b' E与时间戳序列类似,取整函数也是可以在dt对象上使用的:
7 r" e- s2 O& l* n, k; V$ m
1 `+ j% M$ W9 q7 F2 y/ b! X9 m& Tpd.to_timedelta(df.Time_Record).dt.round('min').head()- ]6 E5 y+ U: l/ Z, R \) h
Out[66]:
7 W! Y7 o! f6 y5 S. X2 t5 ~7 }) q; n4 b0 0 days 00:05:00
/ w; X) I3 j9 }# l: ^8 v1 0 days 00:04:00* l8 g0 m5 k6 c4 L" p, E
2 0 days 00:05:00' J; x' K$ I' \2 u& x$ J( B
3 0 days 00:04:00 ?1 b. B3 X9 `2 Z" s5 y6 }
4 0 days 00:05:007 H9 |- }9 L! Y/ B j
Name: Time_Record, dtype: timedelta64[ns]
8 `8 L2 ]' {- \0 [- ^5 @% |1, P h' A- x# X& w
2
! M3 {/ R' Q, G+ y3: g; g! e4 G+ Q
4, u2 R1 I- ]0 k+ |9 S) y; O
58 p! c J1 ^6 N
6
, C3 x* ]$ M; v5 @* c7! a; w1 z ]! E( W
8) r" w- d5 @* Q" `) T; [
10.2.2 Timedelta的运算% O% S7 L3 v0 j' p7 B4 o
单个时间差的常用运算,有三类:与标量的乘法运算、与时间戳的加减法运算、与时间差的加减法与除法运算:
+ S7 M6 I* S/ ~$ _9 l2 {/ U$ Ktd1 = pd.Timedelta(days=1)
( r: y+ U. K" b& }. Ztd2 = pd.Timedelta(days=3)
3 `: E. N6 s# Dts = pd.Timestamp('20200101')$ X% q% y8 v2 C3 J
# P! j4 X# r1 ^+ K- \td1 * 2
5 ?, i! {4 _$ z: L1 R3 hOut[70]: Timedelta('2 days 00:00:00') E& x' R5 R. }% b( M8 h$ k/ {
2 j1 T4 J: `& Z `; c) Ntd2 - td1
1 E1 q4 ?% s% F: }6 r8 XOut[71]: Timedelta('2 days 00:00:00')) z) t0 d* U2 ] N* y# b' C
% c4 u1 g9 G# n/ Fts + td1
& l$ Z: I' s, [; h0 G" xOut[72]: Timestamp('2020-01-02 00:00:00')5 U1 X) P* y4 S5 C" f; S; \
/ ]0 Z) @8 {/ S. q0 ^ts - td1
0 E7 i, V/ c- AOut[73]: Timestamp('2019-12-31 00:00:00'): k" O5 P* V0 j* ?/ R
1
) e6 x9 P( i# Z8 }5 j2
1 B' K; H% D+ F. @3 i8 e3
- S1 S( G- F) q/ B; J4
$ g7 J& m/ K, @0 R5' z4 j7 l1 Y7 J" w. O4 K, a9 d
6% f% p# z8 \ t/ k5 J( G
7. \7 L8 E8 e ~0 t4 D( w" ^
8
) _* l h2 d4 s9 O" M. P6 r0 V9; b- T, b% Z* u% H
10& c" O- t. r& W3 g3 I) A: Q
11
% J7 E4 M1 `! H* A6 M2 z7 \# f2 x12' k) J U( O9 f) [; A# p
13- h, F: M) X5 e3 x8 ^ t
14$ |! S' c0 a! U! ] b
153 G! |: _5 Y* I8 @" W$ p' _3 ^4 ]2 t
时间差的序列的运算,和上面方法相同:
3 ^( v; q; }3 N, Vtd1 = pd.timedelta_range(start='1 days', periods=5)
' D7 A; Z/ }4 d0 ^# utd2 = pd.timedelta_range(start='12 hours',
) m9 U# y2 G+ d& c8 s freq='2H',
1 V" s' {: k; M* } periods=5)2 G% P# k2 s! z, ]; e/ u' P$ y9 i
ts = pd.date_range('20200101', '20200105')
3 ^# p. O* Z& A p9 S* ]& R' Ltd1,td2,ts) E! G z/ @0 W2 N0 R& C
+ v7 A& Y0 o X/ x( J a
TimedeltaIndex(['1 days', '2 days', '3 days', '4 days', '5 days'], dtype='timedelta64[ns]', freq='D')& q, G# F5 Z3 ]2 O+ L+ @8 b
TimedeltaIndex(['0 days 12:00:00', '0 days 14:00:00', '0 days 16:00:00',3 c8 O9 [) f* i0 w y
'0 days 18:00:00', '0 days 20:00:00'], dtype='timedelta64[ns]', freq='2H')4 x! a* w$ Y5 h) G0 Q& V1 ]$ i
DatetimeIndex(['2020-01-01', '2020-01-02', '2020-01-03', '2020-01-04',) J/ L) q0 w8 d. b. q% U- c' z
'2020-01-05'],
7 f3 G, J" H6 c* ~# U) p' T* O dtype='datetime64[ns]', freq='D')" w2 R, v, K( T5 n9 A4 E
1
9 C. K* ?* [, A+ e: D I27 H/ n4 O! B/ f: g
3
7 M1 x6 z' q; e( l) F! A* z3 i4
- |* O: a8 ?0 {% @* ?5
: d( p: x) e) y! Z# l6
3 }# `1 K& @ W! N2 t7
( }5 x7 W" H" K; l5 p& b87 W# ^$ ^, y( x4 I2 H
9
$ _+ h U# H: |* `) D10
: L5 \5 @5 i. j11
6 P3 C7 g, }7 ]% F* u12
: D u/ |) j5 Z13
2 {5 N8 C1 P; Ztd1 * 5
9 U g+ y# L" d- L$ lOut[77]: TimedeltaIndex(['5 days', '10 days', '15 days', '20 days', '25 days'], dtype='timedelta64[ns]', freq='5D'), q& x, `, F% A: `4 v- g
3 ?1 _# {& v2 t2 Q( C
td1 * pd.Series(list(range(5))) # 逐个相乘
9 i: C/ [$ K' z* O' R! i1 KOut[78]:
) j; k5 s# E5 r; a5 `2 x: b) h0 0 days
7 x- o* |6 d- b# y; V5 K1 2 days3 c, U* b2 D7 H% W! O
2 6 days, V, d2 p7 v* c: b7 G* E: I5 T) [
3 12 days
) V! O9 g! k7 K6 a" Z* Z4 20 days8 @8 _( ? [9 c# r( K ^9 I
dtype: timedelta64[ns]9 s2 C& V+ Q$ S# `5 `$ R
: j- n1 h9 f' M [! u9 O% c3 Atd1 - td2
0 B% h' l7 Z i* E' zOut[79]:
" }8 C! j4 W5 A2 S* RTimedeltaIndex(['0 days 12:00:00', '1 days 10:00:00', '2 days 08:00:00',$ g- M+ g0 A$ r6 k2 x9 S6 N- N
'3 days 06:00:00', '4 days 04:00:00'],2 c0 { r5 A: Z6 V
dtype='timedelta64[ns]', freq=None)
: j& l2 X0 u! ?2 Y
0 a. u: a+ s2 p) D: qtd1 + pd.Timestamp('20200101')) y- W/ ^: }- N( U4 o+ q; ]7 ^: w
Out[80]: V7 n4 Z8 R2 {6 H7 y5 M
DatetimeIndex(['2020-01-02', '2020-01-03', '2020-01-04', '2020-01-05', d$ ^- k, K. G! ^- b$ @6 r9 W1 q+ F
'2020-01-06'],dtype='datetime64[ns]', freq='D')" k9 u4 y! s: Q* n1 t1 G, d4 e
$ P- h& O7 M8 |9 q
td1 + ts # 逐个相加
) m$ k, ^3 W: c+ h0 VOut[81]:
0 q" X0 r- k; Q( j; B" o1 o" [DatetimeIndex(['2020-01-02', '2020-01-04', '2020-01-06', '2020-01-08',
# x+ p$ h) x9 P. X '2020-01-10'],- k0 U+ G& m3 t- G
dtype='datetime64[ns]', freq=None)
. }& k9 d3 C0 J( {, b/ z! u, R: G
9 V" x# D- d6 M, ~, y* W# v, Y1
( g1 o8 r) L* Q( T' {1 v9 ?- V2
3 J& G. t1 z. E4 [) {33 t$ u6 w* a! n/ h9 @
4
2 ^4 p4 V J: S, _$ a1 K1 D% l, G50 o# _+ @! i$ @: H$ ^
60 J% f& _1 o* \0 h4 U3 o# c- z# l
7
4 m, J) v1 B3 p/ y80 U/ W% I( b' r9 u4 v( k
9* d! D. G0 l( e$ O } E& E
10
3 F9 [( ~8 U; y4 q) }11) K3 h- l1 B7 X z
12- K* M0 N1 G. t7 w- ?8 l) {$ O
13& G$ l& m; x: U1 T# o( b0 ~% e
14' E8 d- w; `& H: ~3 S$ M: ^
15
1 u0 z9 L' s) T8 E. p) }5 I16! d# o$ t4 g8 h1 s
17
3 N! ]$ P' A0 `, C+ z a187 e2 I7 G# H( q+ [
19( `0 c O* k- c& W* Q R
20
: V# v- P3 h8 n: j) k214 k# {9 \, l3 S# U6 J
22$ r4 G: d! }5 j
23
' j; G/ E$ I0 H( j0 J+ I24
6 I9 E0 ]/ {5 E: m( z. H" h25: a9 z8 C* `0 l
261 H. A8 X' U) }3 L# z# x0 w5 L! e
27 |! U h4 i' Z8 F
28
1 o. D5 u; [- u7 g% g10.4 日期偏置4 ~6 U( L5 k7 h0 g2 n; @
10.4.1 Offset对象! S6 ]6 ~5 W) h" {; Z
日期偏置是一种和日历相关的特殊时间差,例如回到第一节中的两个问题:如何求2020年9月第一个周一的日期,以及如何求2020年9月7日后的第30个工作日是哪一天。
8 g7 B# D/ q( P+ @5 q8 z. Q6 [9 A2 z& {& E2 R; M
DateOffset 类有10个属性,假设s=pd.offsets.WeekOfMonth(week=0,weekday=0),则:
) ?7 u. L& g1 b$ R& m& s$ D* }- W! ^0 p6 Z3 ~
s.base:<WeekOfMonth: week=0, weekday=0>,返回 n=1 且所有其他属性一样的副本
1 }' e. E( t- H) `s.kwds:{‘week’: 0, ‘weekday’: 0}; c& Z1 I1 D, S/ p; @
s.wek/s.weekday:顾名思义. C% X# P3 @' O' c4 T' O
有14个方法,包括:
" P1 a+ c8 W" n/ H& ]$ W
" b, B! ]# p% ?. C# U. T2 WDateOffset.is_month_start、DateOffset.is_month_end、DateOffset.is_quarter_start、DateOffset.is_quarter_end、DateOffset.is_year_start、DateOffset.is_year_end等等。* G9 }6 b5 m- ], S3 c
pandas.tseries.offsets.WeekOfMonth(week,weekday):描述每月的日期,例如“每月第二周的星期二”。
; V) G, A0 p) X7 v2 K7 ^
0 j# C' g0 ?& B4 p有两个参数:
# A b7 F+ y+ S' ?% Y7 W5 w) Vweek:整型,表示一个月的第几周。例如 0 是一个月的第 1 周,1 是第 2 周,以此类推。1 R! ~; e5 C; J1 }, S% R( w
weekday:整型,取值为[0,1,…6],表示周一到周日,默认取值为0(星期一)
7 ?- s% {2 j, s- r6 \) Opandas.tseries.offsets.BusinessDay(n):相当于pd.offsets.BDay(n),DateOffset 子类,表示可能的 n 个工作日。
+ K" |( w. D8 M7 b
- C# D1 b/ h2 F4 o) W5 W( Spd.Timestamp('20200831') + pd.offsets.WeekOfMonth(week=0,weekday=0)" E. Y! e! n8 o9 v L+ F: o
Out[82]: Timestamp('2020-09-07 00:00:00')- q# F5 ]" A5 P$ Z
( S: q9 @/ w9 q; @+ Z
pd.Timestamp('20200907') + pd.offsets.BDay(30)( H$ p" n/ O6 O. g- }
Out[83]: Timestamp('2020-10-19 00:00:00')- a0 u- u- d7 _
1. {7 Q4 I# k* ?, H
2
9 n0 E# x7 m3 F1 W3' }! m* ]1 {+ _8 D
4
8 L Q& q: B4 g. {' M; n5
- N1 a* |( g$ d4 _+ g! P 从上面的例子中可以看到,Offset对象在pd.offsets中被定义。当使用+时获取离其最近的下一个日期,当使用-时获取离其最近的上一个日期:2 r1 V t# I* l c
8 z6 t" a* ~0 d7 N' |
pd.Timestamp('20200831') - pd.offsets.WeekOfMonth(week=0,weekday=0)
9 [( F0 _+ v, ]- J9 mOut[84]: Timestamp('2020-08-03 00:00:00')
! ] Y( z8 Y! X6 T4 M U' C* ?6 J) y) o, B. i
pd.Timestamp('20200907') - pd.offsets.BDay(30). c, i' ^! O/ I/ O
Out[85]: Timestamp('2020-07-27 00:00:00')2 `: v. M! A6 E: }7 j2 p, p
6 G- t9 g. |. B2 o
pd.Timestamp('20200907') + pd.offsets.MonthEnd()
! F' Q/ L3 C8 f3 hOut[86]: Timestamp('2020-09-30 00:00:00')& K9 g3 Z, t% A, X3 J
1
# G% b: N" i _" F$ W8 T0 _# m2+ e4 y4 @. V6 L/ s; R
3% m Z @ j; T6 S* [7 h2 u' ]" F
48 d b- A, W1 P5 p" @; b
5
+ D4 K, b, l" y$ ^) o( w6
. ?! Y( g8 o; N( A70 k9 V4 o" o& W1 z
8
: I0 U2 M" L' D6 C' j 常用的日期偏置如下可以查阅这里的DateOffset 文档描述。在文档罗列的Offset中,需要介绍一个特殊的Offset对象CDay。CDay 或 CustomBusinessDay 类提供了一个参数化的 BusinessDay 类,可用于创建自定义的工作日日历,该日历说明当地假期和当地周末惯例。* A8 ]/ t/ l9 H x2 Z
其中的holidays, weekmask参数能够分别对自定义的日期和星期进行过滤,前者传入了需要过滤的日期列表,后者传入的是三个字母的星期缩写构成的星期字符串,其作用是只保留字符串中出现的星期:
0 o. R9 k/ f' S0 V5 }( ~! \) b3 i, J4 s; u: @, q
my_filter = pd.offsets.CDay(n=1,weekmask='Wed Fri',holidays=['20200109'])
0 A) L: A+ c/ Z' u1 b' |dr = pd.date_range('20200108', '20200111')) g4 [; u* }; @/ Q
& w% ]: G! m' v4 v7 G$ ~7 y! y
dr.to_series().dt.dayofweek
% ~& E$ G* Q5 N) l* X* ]Out[89]: ' a& v: k5 ?$ f6 [ i: S
2020-01-08 28 f. H6 j9 K/ D/ @4 E
2020-01-09 3
5 {% h% O9 J1 {9 Y) i5 f& }2020-01-10 45 `0 K! K, X. ?9 I8 I
2020-01-11 5( \3 [) Q T- d1 Y0 h
Freq: D, dtype: int64
; L# P. q. G9 M9 q5 R1 k. T) Q( Z
1 |0 ]' F1 f- g$ R1 j/ d[i + my_filter for i in dr]; C- f* r# H# j
Out[90]:
# C2 J5 z. ?1 `; ]. z/ u$ N. k[Timestamp('2020-01-10 00:00:00'),
) f4 a6 v7 s' g1 \0 t9 l& r O Timestamp('2020-01-10 00:00:00'),
$ H) F' e$ y! n0 h) s4 }& f/ R Timestamp('2020-01-15 00:00:00'),
& B- d: V" ?2 }. S3 d- U Timestamp('2020-01-15 00:00:00')]5 |' S+ R0 t0 p+ I: Z
$ W8 Z3 o" F7 y/ j" F( R" B+ d1$ S. Z2 x- S+ i4 e i+ j8 ?
2
5 ?+ d0 A( a# m# z! T" s3
8 F( P, l2 L# Y) X; L1 t; u4$ n; l6 `* S8 ^- q$ n
5
4 _2 Y% ]( _4 P# w9 E6! ~+ f( `+ l4 y# l$ u: i9 B
7
' U6 U6 ^5 Y& T7 l( J" @3 S1 j80 f# Q+ d# n+ q+ S
9, s$ L2 }- C" i" s
10
- c$ B: t, B% }0 X. ^3 \11, x3 @/ _4 |# o6 B0 z; z: V$ R
12
) Z' ^0 M- j8 ?13
7 A4 e1 ~# r! |2 Y14
i" G- C8 b! ^6 [* h15
5 G# v9 D- z9 q: A16
" w% O! i, J. |0 P3 @/ d/ t178 c! O8 e, K& Q/ N) Q! U x
上面的例子中,n表示增加一天CDay,dr中的第一天为20200108,但由于下一天20200109被排除了,并且20200110是合法的周五,因此转为20200110,其他后面的日期处理类似。
2 X a/ w' g7 X8 x8 t' o" P' d( E# K5 _5 V; M. n: W
【CAUTION】不要使用部分Offset
9 q/ ?( y3 k1 w; c* R在当前版本下由于一些 bug ,不要使用 Day 级别以下的 Offset 对象,比如 Hour, Second 等,请使用对应的 Timedelta 对象来代替。' I0 X& N* X% D- t( m
0 _' S# C' f, M10.4.2 偏置字符串
! w! T. f( Z+ @$ e/ j7 p 前面提到了关于date_range的freq取值可用Offset对象,同时在pandas中几乎每一个Offset对象绑定了日期偏置字符串(frequencies strings/offset aliases),可以指定Offset对应的字符串来替代使用。下面举一些常见的例子。+ g7 \0 D3 h. Q) l W
+ U" G1 [- I2 \4 Z+ K# k
Offset aliases:pd.date_range函数中的freq参数,为常见时间序列频率提供了许多字符串别名。 也称为偏移别名Offset aliases。偏移别名列表点此参看(大概27个)。
. {- N& L# S" e- S" h) g
' o R& V( t+ S6 ^9 T& [) Fpd.date_range('20200101','20200331', freq='MS') # 月初 R) w6 W3 @+ B7 |% K3 t& N
Out[91]: DatetimeIndex(['2020-01-01', '2020-02-01', '2020-03-01'], dtype='datetime64[ns]', freq='MS')
1 b$ u0 @, `2 W w7 J% B
* a# a# y' O) vpd.date_range('20200101','20200331', freq='M') # 月末
- x; I; W+ v9 @+ @% l) rOut[92]: DatetimeIndex(['2020-01-31', '2020-02-29', '2020-03-31'], dtype='datetime64[ns]', freq='M')
- o2 o0 \( f ^+ _6 W. D5 p) v' r: X h5 F. O8 O, o
pd.date_range('20200101','20200110', freq='B') # 工作日
3 u4 o( p2 ]( c5 vOut[93]:
; ^1 {9 g; V8 q0 ?! V* ], S4 ?DatetimeIndex(['2020-01-01', '2020-01-02', '2020-01-03', '2020-01-06',
9 H. A! O s% W% a+ W' ]$ Y '2020-01-07', '2020-01-08', '2020-01-09', '2020-01-10'],+ U& @' v. x1 ~8 V" z8 {, z* L6 l0 j
dtype='datetime64[ns]', freq='B')
1 e+ Z. j( p u+ _; T3 e3 r
+ }" g0 D2 e" ?pd.date_range('20200101','20200201', freq='W-MON') # 周一+ I3 o: r* w7 E& k$ }5 ^
Out[94]: DatetimeIndex(['2020-01-06', '2020-01-13', '2020-01-20', '2020-01-27'], dtype='datetime64[ns]', freq='W-MON')" K3 h! n9 d1 v; O/ ~* O& J0 j! I
6 o8 K; h! c6 X9 | Npd.date_range('20200101','20200201',& b9 W, {3 F# W; s2 h* e5 N, C$ `
freq='WOM-1MON') # 每月第一个周一' ^( O, y& m, t) [& Q4 B C
& j; R, f0 k, K+ U* e/ Y5 M. p) J$ cOut[95]: DatetimeIndex(['2020-01-06'], dtype='datetime64[ns]', freq='WOM-1MON')
) u- v# n$ M+ L4 w
- j/ e6 ]9 Q9 `" F1/ \% P! O0 P3 _2 ?9 o" m3 X
2
# o3 s$ B8 e" d& z7 ?3
% Q0 F" o6 B" I* G, @3 g4
! T/ W6 P. x+ k$ ?$ `3 I& m5: b: B5 V2 J7 I9 P5 O) I
67 f1 W+ f1 _! W5 R" c
7
) b- h2 S7 _. h) s6 i$ b8; O+ ~/ O% M# H1 L8 a
9. G" H, t( p4 C/ _* i
10
& @. O' g0 E- i# z, @% R* P8 q9 ?11
- b- R/ M4 ]* Z# ]/ G: L1 | l4 I12; @0 h$ M7 A- E. ]4 G
13
0 u/ P1 w* C! c3 N1 x* V14
, I( n2 o3 Y& H9 q) u2 p1 g15
6 W7 D; @1 Q/ |16
) A, f) r: k' x2 p' w% z17& x5 [& _+ `$ v
188 B, t6 p7 g) o* s: h8 E0 A
19
& \9 C( _( k( `上面的这些字符串,等价于使用如下的 Offset 对象:! k8 h, w8 m2 K* N5 Q! b! k; k @
5 y/ V8 j. W R3 a& {& Z- F
pd.date_range('20200101','20200331',& P5 |8 r9 ^) K' A' C
freq=pd.offsets.MonthBegin())
: K, N T( h+ P ?0 P
$ |# R3 ^/ Z$ R5 Y$ ^Out[96]: DatetimeIndex(['2020-01-01', '2020-02-01', '2020-03-01'], dtype='datetime64[ns]', freq='MS')
1 k5 d' h: a/ o4 w- b6 v
3 E8 K; t$ a4 q( q7 J/ b3 dpd.date_range('20200101','20200331',
" R2 ?6 y. G0 g* o freq=pd.offsets.MonthEnd())0 [2 ^ j) ~5 u7 B
# I# [: V: `* A3 q5 |% Q" @- h
Out[97]: DatetimeIndex(['2020-01-31', '2020-02-29', '2020-03-31'], dtype='datetime64[ns]', freq='M')
! c+ u6 u: z- } ~8 o9 Q7 C6 }% z
2 ?& Y, {$ \9 s/ {0 vpd.date_range('20200101','20200110', freq=pd.offsets.BDay())
- w p1 S. V0 J8 @ x, ?) P4 WOut[98]:
( l A7 s2 L2 `DatetimeIndex(['2020-01-01', '2020-01-02', '2020-01-03', '2020-01-06',9 v: z5 w. m! {( w6 x& w1 o; f* l+ p8 p
'2020-01-07', '2020-01-08', '2020-01-09', '2020-01-10'],
) ^& {! v1 b! L" t9 F @ dtype='datetime64[ns]', freq='B')
$ \' o1 Z! N: A$ R6 Z/ M) _# a1 I" H
# }* @6 m. K9 N1 k2 p% T1 dpd.date_range('20200101','20200201',
) P* @$ H/ L" t" o' B freq=pd.offsets.CDay(weekmask='Mon'))# }0 Z7 y# M- E
7 s3 q& ~% x: e4 _) TOut[99]: DatetimeIndex(['2020-01-06', '2020-01-13', '2020-01-20', '2020-01-27'], dtype='datetime64[ns]', freq='C'). R6 V9 Q0 G5 A7 Y. N* y' N- r5 L
& r5 `8 V& p" A+ {# Gpd.date_range('20200101','20200201', ^8 q0 e) j" L
freq=pd.offsets.WeekOfMonth(week=0,weekday=0))
7 c3 |1 S' P$ P8 p9 J+ \
7 n( N0 q- b/ A5 J5 w7 s! FOut[100]: DatetimeIndex(['2020-01-06'], dtype='datetime64[ns]', freq='WOM-1MON')
$ T3 {- t, p, {# x: C$ F' W: w5 B: U# p- F
1% X4 u! P; \: {
20 b+ z$ j% q; X9 c3 Y
3
, v9 ^! K, L. \; o4
5 n v5 s( t7 [2 J. \ X9 B5
0 a& ^. F5 _7 L4 e% L, |* E) x6
8 R7 b4 @. p$ I9 b7& A& _+ o) G, [9 K
8
3 O! Z3 i8 E( G9
. ~+ ]8 e( Y0 ~/ u: c- z10
u! d# y2 [4 ?. w11$ P* M& c* u( a8 O. }
12
: W/ T) e" ^3 K2 P/ w( C$ X3 H13
+ m8 f; [9 k" j: }14. ~) `9 R/ T7 R6 p
151 b2 g' E8 s; L0 L& C
16
5 [( w ~% {3 h+ d- _/ s& m$ v17
5 \" ^$ o9 q0 i) N2 w18
) R- Y4 C( a4 Q" Y; ]( [& x19
$ h3 Y! N8 k: L: D1 e4 c20
' A9 q$ f5 ?" X+ X+ t @21
) C& f* U# S1 d" E' T2 a* r* P22- q( Q0 g3 h+ V7 p/ a$ F$ v
23) t- V2 q- G0 C
245 l9 a) h6 o O
25
4 V7 Y, ^: y0 W5 Y5 c/ O【CAUTION】关于时区问题的说明' s g; @8 e5 q* Y3 |9 O% W' r
各类时间对象的开发,除了使用python内置的datetime模块,pandas还利用了dateutil模块,很大一部分是为了处理时区问题。总所周知,我国是没有夏令时调整时间一说的,但有些国家会有这种做法,导致了相对而言一天里可能会有23/24/25个小时,也就是relativedelta,这使得Offset对象和Timedelta对象有了对同一问题处理产生不同结果的现象,其中的规则也较为复杂,官方文档的写法存在部分描述错误,并且难以对描述做出统一修正,因为牵涉到了Offset相关的很多组件。因此,本教程完全不考虑时区处理,如果对时区处理的时间偏置有兴趣了解讨论,可以联系我或者参见这里的讨论。' p. \. e) D# g' p
, V; g* a' h* P' K3 O6 @) O, z8 _' i
10.5、时序中的滑窗与分组
6 {( S3 A. u' c( C* N10.5.1 滑动窗口
' @1 ?' j' t$ L# G) D8 V 所谓时序的滑窗函数,即把滑动窗口windows用freq关键词代替,下面给出一个具体的应用案例:在股票市场中有一个指标为BOLL指标,它由中轨线、上轨线、下轨线这三根线构成,具体的计算方法分别是N日均值线、N日均值加两倍N日标准差线、N日均值减两倍N日标准差线。利用rolling对象计算N=30的BOLL指标可以如下写出:7 w% Y5 t& i9 ~
0 I2 e* h& W0 K0 M" c, ~import matplotlib.pyplot as plt
+ J8 o7 o/ `) G$ Nidx = pd.date_range('20200101', '20201231', freq='B')
4 e s ^/ E. o) Ynp.random.seed(2020)! o& b7 c! @3 f. V( R: m+ v( ?
# v- r5 r, S- A' m; }
data = np.random.randint(-1,2,len(idx)).cumsum() # 随机游动构造模拟序列,cumsum表示累加 \" B- T W& y) o$ P) F
s = pd.Series(data,index=idx)5 y& }7 C1 T& l
s.head()0 p) i/ J) u3 D" r2 A' e
Out[106]: / t0 L" p/ t! j# j1 d
2020-01-01 -1
7 L0 l. o2 X% C4 S& l* ?2020-01-02 -2* L: V$ m/ S1 ~% A1 T, h" p
2020-01-03 -1& j# C9 ~/ L7 c- v
2020-01-06 -1/ k- e0 T$ y9 o3 S5 {
2020-01-07 -21 a/ Y" M' l$ C8 W' C
Freq: B, dtype: int32! L' d1 x. a, X) P( |, v7 B
r = s.rolling('30D')# rolling可以指定freq或者offset对象
' T- F; S8 |. }& m) I- L$ D" [! C8 @0 ~: ~3 y. k0 s; [
plt.plot(s) # 蓝色线
+ E2 j+ ]3 X8 p, {( g, o9 GOut[108]: [<matplotlib.lines.Line2D at 0x2116d887eb0>]
2 ]1 a3 |+ \" w1 K0 ]plt.title('BOLL LINES')
3 n+ o6 q9 q/ e% UOut[109]: Text(0.5, 1.0, 'BOLL LINES')
- a2 }: {7 }$ _* W" p2 d. M+ G: `
0 S. ~9 Y5 r* l- |6 oplt.plot(r.mean()) #橙色线
: A+ F9 J: } H' v4 P) [Out[110]: [<matplotlib.lines.Line2D at 0x2116d8eeb80>]
+ x6 ?, y6 f% B) V- _/ |: T5 i& p& E R4 B1 Z
plt.plot(r.mean()+r.std()*2) # 绿色线
1 h( V: [1 f0 I4 i3 @Out[111]: [<matplotlib.lines.Line2D at 0x2116d87efa0>]) Z, y1 g' h+ y
/ q" }4 u" [# a1 c. P9 |& P6 \( X: d# _) ]
plt.plot(r.mean()-r.std()*2) # 红色线5 l3 V2 s# t. o
Out[112]: [<matplotlib.lines.Line2D at 0x2116d90d2e0>]( x2 X) N; `) u2 V+ ?( k
: z; m; i- Y \1+ H5 ?6 G# L: c2 Z0 F4 V6 I
2
; t5 F' P+ w$ g& R; m! G0 O3 D39 ?8 H+ E; h) ^
43 J5 `! S, N3 D, ~$ X- C
5' s8 A+ o7 x3 j/ O B
6
6 Q( B% t$ q8 n7
2 R9 \. j; G# |4 p, K, L: s86 u' K+ V! W, M9 p
90 ]- V8 u# B0 D2 f$ M) j, T$ u
10$ I+ W: O+ l: c* F C
11
7 C* V& a; y- F. D- @$ Y12' L) S' p6 D$ y
13
6 ~/ y: ?1 Z) k, |" L- U* k+ g& f4 ]14
7 ^+ K2 O; f; G$ Z0 q5 s( G15: @8 K# z5 h) L& |: P2 M n
162 h" ^, j5 [1 `2 m
17
: u8 f# o Y- a5 K Y7 g( c/ p18% T6 Z" A. f* W+ p+ i: v+ g
19+ P- b% Y; Z& S5 U# H
20; {' s6 l# k2 C G4 r" @
21% O- ` t# K7 n
22
w: z, B7 j4 I/ C* T/ U9 z$ P23
( K. y% s/ [$ O24
R+ A7 T# T. ?/ J4 ^" R255 q- t0 a2 J+ p
26
9 L) x+ l% S$ W- T2 p! Y27
* N6 h% k9 b/ ?3 ?: v% A! t& X28
& S' H, M8 Z4 R2 R8 b29
, W& k' e: l9 e5 q8 n5 L
/ w% K0 [4 l# i9 `1 P* a( C/ w7 R1 S7 D 这里需要注意的是,pandas没有实现非固定采样频率的时间序列滑窗,及此时无法通过传入freq字段来得到滑窗结果。例如统计近7个工作日的交易总额。此时可以通过传入多个函数的组合来实现此功能。4 I* I2 s) m) z I
首先选出所有工作日,接着用普通滑窗进行7日滑窗加和,最后用reindex()恢复索引,对于双休日使用前一个工作日的结果进行填充。
5 y$ l& \* o; g& |8 |
e8 |% B3 T. D1 D) i' @8 V. Kselect_bday=s[~s.index.to_series().dt.dayofweek.isin([5,6])]0 ?3 w% x! H% }5 {! z
bday_sum=select_bday.rolling(7,min_periods=1).sum()
' A9 O3 x7 ?9 h/ N( D7 n: bresult=bday_sum.reindex().ffill()2 R& w% ?4 u& v/ j
result# ^" {. x/ U8 F
" N, a p z3 [6 { p
2020-01-01 -1.09 t, z* |) M) a; p
2020-01-02 -3.0
- O ]0 ?7 J* N1 p8 `# S2020-01-03 -4.0' a: o$ }. k1 B0 c5 s
2020-01-06 -5.0
( g, I) I, ^3 U5 _/ A2020-01-07 -7.0
4 z- G- B4 l! S$ r" X/ J ...
. A( O% I! x9 _5 L* F0 n( I2020-12-25 136.08 h9 T4 F! F* Z8 {
2020-12-28 133.0
1 N L+ l- G' N5 A8 d( m2020-12-29 131.03 J' b, A6 Q: L1 \- d
2020-12-30 130.05 ]( A$ i* s: c2 ~; I- m# J# z
2020-12-31 128.07 t" H* @ @! ^9 r, d3 \
Freq: B, Length: 262, dtype: float64
6 c$ A/ Q7 y; ]& K+ D2 i8 z0 r5 o8 J' u
1* i. G7 ^4 E5 P
28 R! H1 W+ Y* _; z' u
38 r: L4 P% f* V
4
& y, {" i3 }& e8 B4 X3 z8 _) B5* C. n, U7 u- R! A' S8 b
6
8 B6 v P5 B& p8 f' y. T7, |0 r3 i: k5 C3 Q0 `, {! n3 V
80 S% _1 `% H6 Z G
9
( ^) R z; y8 {/ N10
. E! y) B% a: X11* k% k3 r& u4 k) K! x& v8 W
12
9 @. \5 Y. r2 X/ A* s& P U13
' F: z- t1 L Z7 g( T+ j14+ c* g8 L s3 ~6 E' Y2 P2 n, u
15& l9 o1 V) C2 u
16
6 b4 X" P" Y4 ?. D175 f7 Q$ W& d3 H9 x
shift, diff, pct_change 是一组类滑窗函数,它们的公共参数为 periods=n ,默认为1,分别表示取向前第 n 个元素的值、与向前第 n 个元素做差(与 Numpy 中不同,后者表示 n 阶差分)、与向前第 n 个元素相比计算增长率。这里的 n 可以为负,表示反方向的类似操作。
5 Q& o6 H5 |! R- l/ s/ \. u
, R+ O% u* x: { P4 g" H+ y 对于shift函数而言,作用在datetime64为索引(不是value)的序列上时,可以指定freq单位进行滑动:
/ O7 L; X$ k8 v% ~* M4 J I/ I9 t) q$ z+ W
s.shift(freq='50D').head()( P# w/ V1 @: X
Out[113]: 8 E [3 ?/ O; `( z( }
2020-02-20 -16 H; P- [( y8 O$ H/ y
2020-02-21 -2
" V, F; c. }9 Z2020-02-22 -17 q& t6 a# i9 f' ^( p: M
2020-02-25 -1
1 x: c& [* _/ `" M9 Y2 l* C1 r" D2020-02-26 -2
+ A, o, ] d9 w: Ndtype: int328 i4 |9 f% e# i
1
& x6 ?$ b( ^7 v2
/ r% M1 K) m& i1 f9 o9 ?3
4 F5 g$ k Q$ }( N1 g" x0 j. A4' n! z# g* R& j2 A# i
5
4 S, o% t8 I; Y# P& G3 V% V69 G- D+ m9 D- H! c7 Q
78 Y5 n' b8 M2 V$ x& Q6 M g
89 {- A( Q, B- y/ Z
另外,datetime64[ns]的序列进行diff(前后做差)后就能够得到timedelta64[ns]的序列,这能够使用户方便地观察有序时间序列的间隔:
) ?& r [9 a, T5 [$ Q
. d {* o) M, w! M, emy_series = pd.Series(s.index)( ~+ l3 V* z1 R0 r Q$ S
my_series.head()+ e3 ~3 s3 v5 W8 ^
Out[115]: * g: M! c# ^; U$ r
0 2020-01-01. J6 E' Q6 F2 u, E
1 2020-01-02
3 j, }/ U6 H& w3 y6 [$ D2 2020-01-03* g) |. F& m$ s8 T# S+ L
3 2020-01-06
# h$ x+ M: h1 O4 2020-01-07
) L, f6 W/ y0 ?: g' D6 H2 f, Zdtype: datetime64[ns] w8 d. p6 J% F. @) @
' c1 R" m9 E) U3 r# u+ Y) H" `
my_series.diff(1).head()
! x k6 J% [! q" GOut[116]: ! @% I, r8 \' h. D6 s
0 NaT
7 U |9 A- ~: }* k1 1 days" _* i0 m# `. G7 _
2 1 days, M% ]& Z- A3 l8 T! M& `5 f6 l
3 3 days8 g' k* n5 A/ I/ E I
4 1 days$ W0 e8 P7 |# U6 h v
dtype: timedelta64[ns]( g: ^$ H( J9 o @9 L% ]9 s; \ M
# _/ y% t ^6 Y+ z
1
2 _2 D8 k: e- n. u23 o! t! k( @( J. U: ^
39 J. W/ H( C% w1 h! ^
4) g3 g- S; }9 e, u$ H- [3 E/ L
59 k, c% \9 f \0 F
6. I% M6 |% e& C7 z
7
- t8 w) n( f7 Q: |# ~% F85 H( Y1 y# {$ l5 L9 F# N% p
9
; m' ~; J- _# o109 g( l' l+ n% v; ]8 ^! p
114 Y i: g5 e) Z9 ~
12
4 \- S" I. `9 m137 Z: {) r8 C+ x1 n3 i: `
14
; k) R+ U( ?; G1 J0 t; [, T& e, B' ]8 H150 w2 h x; z) q0 ]
16
4 w* V7 |$ j6 D6 @- N8 q- H17
" x+ c; W" o2 e l& H& @187 Q$ x* X9 a8 @& o3 W: O$ ^
10.5.2 重采样
* D( ?2 A# a2 X$ e 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)
% b" |" e6 N# a( K+ y常用参数有:# i5 ^: n: O$ \4 c
1 c* Q" ?7 K+ ^9 v/ s: Urule:DateOffset, Timedelta or str类型。表示偏移量字符串或对象
; l; ^! x* K1 xaxis:{0 or ‘index’, 1 or ‘columns’}, default 0。使用哪个轴进行上采样或下采样- f) N0 H+ k, @
closed:{‘right’, ‘left’},默认None。表示bin 区间的哪一侧是闭合的。所有offsets的默认值为“left”,但“M”、“A”、“Q”、“BM”、“BA”、“BQ”和“W”均默认为“right”。0 r$ R( [; a& E7 \& z
label:{‘right’, ‘left’}, 默认 None。hich bin edge label to label bucket with,所有offsets的默认值为“left”,但“M”、“A”、“Q”、“BM”、“BA”、“BQ”和“W”均默认为“right”。
. ]: N2 }; b$ s+ h( n6 h. Hconvention{:‘start’, ‘end’, ‘s’, ‘e’}, default ‘start’。仅针对 PeriodIndex,控制是使用rule的开始还是结尾。2 \$ e+ x! j5 m
on:字符串类型,可选。对于 DataFrame,使用列而不是索引进行重采样。列必须类似于日期时间。2 B7 G. M* S/ b s
level:str 或 int,可选表示多重索引MultiIndex的级别,这个级别的索引必须类似于日期时间。4 r5 `# i0 I" K9 o5 i# ]" E
origin参数有5种取值:/ Q* f# X; B+ `4 O/ m- B+ C3 Z8 m
‘epoch’:从 1970-01-01开始算起
* y% u' D' t- E- W8 {1 H‘start’:原点是时间序列的第一个值
\1 B$ m/ W4 h3 D( _: }9 E‘start_day’:默认值,表示原点是时间序列第一天的午夜。
3 d% r% F9 \$ }" U'end':原点是时间序列的最后一个值(1.3.0版本才有)3 t9 s- d+ |* n$ {9 r r# N+ Q
‘end_day’:原点是序列最后一天的午夜(1.3.0版本才有)
; s3 }# u" X4 j3 z1 W2 Moffset:Timedelta 或 str,默认为 None,表示对时间原点的偏移量,很有用。
3 Z* V& c/ v( A- i: Y, j closed和计算有关,label和显示有关,closed才有开闭。& z- n- v+ v: h4 X- B/ Y
label指这个区间值算出来了,索引放区间的左端点还是右端点,closed是指算的时候左端点或右端点是不是包含。 V- C$ P0 c5 T N- U. l' \4 A
4 C6 T( p; ^. \
重采样对象resample和第四章中分组对象groupby的用法类似,resample是针对时间序列的分组计算而设计的分组对象。例如,对上面的序列计算每10天的均值:( d9 \6 f* i' Y3 l$ o
s.resample('10D').mean().head()2 h! {8 A3 c- c& Q3 C4 L" Z
Out[117]: ' L h/ u+ i2 T0 f
2020-01-01 -2.0000000 J2 \6 O% n2 ], p$ V/ i
2020-01-11 -3.1666676 S9 Z" Y. z* t; I6 u: ?
2020-01-21 -3.6250007 T3 [! B/ _0 f
2020-01-31 -4.0000005 k- K; z- S, Z5 Y" Y
2020-02-10 -0.375000
( Z: s; ]) U, E! E! `Freq: 10D, dtype: float64
$ d$ U/ p' \2 K1( K3 l9 V. _1 `) Z
2
5 h+ h% r( n0 n% {( e+ S3! g- L* w9 t5 n: O2 y2 u) e
43 q" j4 O$ `, r) f: }9 Y6 v$ W
5
5 p8 y+ j' a4 \+ f6
: D; N1 ^* H9 \7" L1 l- n- X$ Y- R- m
89 I w8 @ D) t. R, B
可以通过apply方法自定义处理函数:( H4 @1 r1 g: j
s.resample('10D').apply(lambda x:x.max()-x.min()).head() # 极差
% [* c* D% L( \+ x$ _$ \* v6 |3 H2 Z0 H) N' v
Out[118]: ( P. F; q3 c0 |
2020-01-01 3
8 V: d' V; R9 i+ s5 z M2020-01-11 4% b0 D% B" D7 F3 k( n/ o4 |
2020-01-21 4
% L8 O4 F, Q$ \) R5 o- O' x" _2020-01-31 2
' t5 N) @& Z: a: x C8 l7 x- C2020-02-10 4
* U& R5 v+ E% @1 x, s, x" X; EFreq: 10D, dtype: int322 E; f, a7 A m' Z' \( z+ ~! f0 T
1
! @0 q, c6 D8 T- N- q6 @, M' K2
+ ]' b: P/ r8 n% V30 k# T" D6 L w9 @" Y0 ?. i; [
4( S! k, p$ J& a- ]8 g. u
5
" T+ W$ H: C4 A9 g* Y* [6+ m! r7 K2 t. u# U/ e" a* o# q
7" e4 m* U% o1 x7 o O* t
8
7 {8 m* i& x# u4 a p' |9; @0 n& [; O Q, G+ h, J
在resample中要特别注意组边界值的处理情况,默认情况下起始值的计算方法是从最小值时间戳对应日期的午夜00:00:00开始增加freq,直到不超过该最小时间戳的最大时间戳,由此对应的时间戳为起始值,然后每次累加freq参数作为分割结点进行分组,区间情况为左闭右开。下面构造一个不均匀的例子:
, K8 v+ @+ e' M3 h
3 g: x$ Q+ G; p6 {( cidx = pd.date_range('20200101 8:26:35', '20200101 9:31:58', freq='77s')6 S5 k, Y! a: ~
data = np.random.randint(-1,2,len(idx)).cumsum()
8 n+ L4 _3 t0 Cs = pd.Series(data,index=idx)) k2 R3 R) |( T' k
s.head()
8 Y# o/ V2 M4 H# g9 A8 U$ N; ` O. j$ E' h0 h( u
Out[122]:
( p6 U. N' j8 n% k. V! J0 ^2020-01-01 08:26:35 -1
9 |1 u) a, p3 K5 B/ t2020-01-01 08:27:52 -1/ j3 H, l4 S) | j7 D! ^
2020-01-01 08:29:09 -25 Q4 f/ Q- k0 f8 q. t1 o
2020-01-01 08:30:26 -36 C5 o0 e$ W4 t4 @. C9 `1 I" R4 D) O( @/ M
2020-01-01 08:31:43 -4
/ |8 T8 S7 Y( `/ Q+ u# H' AFreq: 77S, dtype: int32
4 g5 r' F6 q5 Y: ^! }# g1
" X1 U+ J' m4 W, O- O2+ E0 j# r- D) h: N
3
# Q4 _9 k( E/ t4
2 Z4 d. x+ ?. [- q4 i F5( S/ S) g7 J9 L$ ?# g
6( V: O% L; }3 T' U0 t7 O9 K# h1 k
70 f' v6 E4 x. N! h! O
8
1 X9 N! g8 |+ V6 Z/ V. D9
; S1 U$ f% B+ ~. j# J4 @. K& O6 G) Q& z10
2 Q8 r9 X8 Y# y# \, @$ p11
. ~" S9 y" j1 W5 R6 O( h$ e12" ]6 r2 |5 F& e( M
下面对应的第一个组起始值为08:24:00,其是从当天0点增加72个freq=7 min得到的,如果再增加一个freq则超出了序列的最小时间戳08:26:35:
+ z* I5 b4 D' R4 g( b/ e# ~# ?. o) m; v; c9 p8 Y
s.resample('7min').mean().head()- M# d" |6 f( N9 l7 s
Out[123]: 7 H6 R9 v8 N; D+ H
2020-01-01 08:24:00 -1.750000 # 起始值,终点值包含最后一个值
4 R% ^7 P$ E0 d2020-01-01 08:31:00 -2.600000
) h0 D8 p( ^. B& t7 S$ E& W$ d2020-01-01 08:38:00 -2.1666676 ~# v' K, e7 [9 C3 k" p- ?% w" e3 ]
2020-01-01 08:45:00 0.200000/ T" m( m% ?1 R* j) i
2020-01-01 08:52:00 2.833333$ F5 w- O% x' G, N$ ]1 H
Freq: 7T, dtype: float646 A2 B8 t. ~/ c G8 D
16 ^2 O) Z- Q* ~
28 n6 I- D* L1 Q- v) [: {( e* g( k
3* Q' O7 x, T9 i+ h! p$ D! H1 l/ p
43 t5 G3 x' P" P# F; A
5
- V8 ?' j2 n# ^, C5 T# N- Z; @6
2 P- m9 E9 V+ H" M, _5 c7
1 o' o8 [" o ^" H* l# L" I8
2 _& N! ~, z/ ]( L* z/ O9 f7 ^; y* n 有时候,用户希望从序列的最小时间戳开始依次增加freq进行分组,此时可以指定origin参数为start:
9 j, D7 _7 U$ U! u) K d/ S$ _( G: ?2 H' i5 g3 p
s.resample('7min', origin='start').mean().head()- v( J8 r$ w. a6 A6 B w( H
Out[124]: M o8 d+ _5 |# V3 }/ G& L9 p1 k3 [
2020-01-01 08:26:35 -2.333333: z+ \4 }0 P9 [' \4 X: s
2020-01-01 08:33:35 -2.400000
% W: P$ f7 e" O$ I2020-01-01 08:40:35 -1.3333332 D! q. r" P8 [& J0 y( \" P
2020-01-01 08:47:35 1.200000
3 H; O3 u( {' [9 W( ?2020-01-01 08:54:35 3.1666672 p9 b& M3 w& X( {' x( B
Freq: 7T, dtype: float64, A3 x8 G; y8 A) ]( m) B
1
# ?. I5 G9 ~! A) _" M, ~5 b) G! S25 I, z. \2 p. m( ` J S& r3 N
38 \6 m9 R& x( o
4
% j3 G' [% y4 w. \5
. d* G9 f+ E2 L& l i6) g5 \) V/ M9 x! m1 D
7- \0 ]( r4 Q; ~0 f4 j
8# a" G: @2 {& U+ i0 c; n+ w u9 l) u
在返回值中,要注意索引一般是取组的第一个时间戳,但M, A, Q, BM, BA, BQ, W这七个是取对应区间的最后一个时间戳。如果想要得到正常索引,用’MS’就行。
6 h: O X' m# b+ [0 C0 R
1 f$ m. Q. n. r( b) M3 ~s = pd.Series(np.random.randint(2,size=366),% t c& h1 q8 ~6 U* f0 r: J: e
index=pd.date_range('2020-01-01',
: L3 w* w7 e" O/ e$ Q% @5 I '2020-12-31'))
/ A) k& T. Q! {8 t# F! r
3 ~5 A" Z* `% G3 S- W: g
# ]+ x* R: ~: _' x: X+ `2 Ms.resample('M').mean().head()
3 I% I. L# v B) Y: M3 o1 w6 ^Out[126]: 1 E7 { M# i# ], F3 M
2020-01-31 0.451613& L+ k; t# ~ m n, \
2020-02-29 0.448276
" H; p5 h; }& o. {0 @2020-03-31 0.5161298 G1 J" T& \3 R$ v! G9 [
2020-04-30 0.566667
( z. H F2 |0 V2020-05-31 0.451613
4 z/ U; q4 v, c6 }# Q) Z- ?& l7 Z) mFreq: M, dtype: float64. e( t: c+ d8 K: x( J* m
# [' w- `% N0 i5 s' m, H
s.resample('MS').mean().head() # 结果一样,但索引是跟正常一样5 N; r( s4 B' Y) F9 v' ^+ f0 I# b
Out[127]: $ B7 i3 @7 }8 {; Y) x' Y9 B1 m
2020-01-01 0.451613
( {- F5 }2 |9 _2020-02-01 0.448276 P* x r4 p; _# W0 O# U, l
2020-03-01 0.516129
% V+ k! _3 A: G2020-04-01 0.566667
7 _% L( O' i( G% ^" c% Z2020-05-01 0.451613
) G+ _" r% [) qFreq: MS, dtype: float64* t1 ]8 K2 z. l3 e. M7 K& S
8 a& Y c; R/ x$ |! Q
16 e O2 x7 N. q- h
2
- g4 v5 z: W8 E( B9 a2 T. M3- V* f/ [2 x8 v) b6 k8 @8 M
46 n4 K) W" @) M! E
5
! a. M% [5 \0 `1 L; I) |/ `6" T" d& h. P1 Q+ U
7
! I1 s) K* r: [8
& Q3 Q: Z8 S4 k4 q J9' b. _/ M1 S7 U$ [: ^
10
0 C! c( i4 Z! ]: c/ o116 D/ Q% y8 m) ~/ V; [
12, o+ `6 e* K- W7 j* P W/ m& J- b1 y8 G
13
( Q6 s* @ m8 U) a( P14
$ x5 Y0 r! v# G6 a! _- e% \* _15! A$ i9 R, d4 p! b$ C5 s" B; \
16
* ?7 N7 a: F- c5 C. p! r+ Z176 Y, W/ t( Q0 M1 Q
182 P; L+ x; r$ _) p, i. s3 s3 ^8 H: N
19! Y% `6 _7 O6 J7 C$ E$ c9 X1 x
20
d9 l- P% w# V& Q6 X8 a# ?& M21" J( W. {3 L" R* x; }
229 m# `: U- M: z. \9 x
对于 DataFrame 对象,关键字 on 可用于指定列而不是索引以进行重采样:+ G8 J" y$ b. J
d = {'price': [10, 11, 9, 13, 14, 18, 17, 19],
1 F. B4 @( E0 j h 'volume': [50, 60, 40, 100, 50, 100, 40, 50]}
M7 E7 Z3 P/ N1 H9 d7 J( {: \$ Udf = pd.DataFrame(d)1 e2 |# |$ e* J( Q& _" }1 `
df['week_starting'] = pd.date_range('01/01/2018',
- {6 M% A' j9 E' ?. m3 m' U periods=8,
* i+ O$ ]0 Y$ `* k freq='W')/ [3 e6 l0 k7 O
df
0 }7 _: _+ m$ B2 p5 g l price volume week_starting# }$ }/ M) U/ ~
0 10 50 2018-01-07
( ?1 m: d* M" j+ o& T' x1 11 60 2018-01-140 c( W6 k" v8 D$ j5 Z0 e+ ?# L) j
2 9 40 2018-01-21
. P) V ?# A% S# H: [0 v" A3 13 100 2018-01-28' D, z- A% _; O& _% Z
4 14 50 2018-02-04
+ g7 _# @1 H' x! g2 O5 18 100 2018-02-11
! ^5 X6 k2 B. j& Q6 17 40 2018-02-18
* t, [5 x+ L- z0 i# U% m7 19 50 2018-02-255 |6 f o7 }6 V! Z# B4 l
df.resample('M', on='week_starting').mean()
6 B0 Q V, u# n4 b, P1 A, {7 }% z: } price volume9 |& h- c! {6 M9 j# j/ s; z) @2 f4 g
week_starting' B. @, I; D. o3 c
2018-01-31 10.75 62.57 k5 X. o% u% d1 p; ?% |/ N" L
2018-02-28 17.00 60.0
! U2 W6 z- I$ {% p' f! @5 @; {8 z/ k% d8 N* c- n4 g1 j4 Y+ A
1
: v1 `: K, c9 R0 |* r. x2
! H) a5 p3 g9 V9 z$ y# }3" p8 Y5 S4 `" O9 h; {7 q% Q
4+ W" u8 E" d+ W/ {
5
6 ]) T: \3 j' D8 f- f6
) J5 x; V5 [2 s7& C" k2 Y' M6 S% v
8
+ Y9 U) ?! ^1 Z/ S2 Q% N9
* S8 h" _5 t+ N; H4 B6 T10
3 P5 y5 N9 h" K3 u. q11# L0 L, r$ e# g- c: a
12: Q5 L- h6 K( U
13! U( w/ K8 m3 [
145 u$ R' J% V9 o9 B9 r
15- M9 q7 ^' z, `9 h' L$ Z6 x! X9 z
163 H: N# K' n l1 W( ?3 C/ p
17, ]; J0 f, E$ H) M
18- o- C& I/ n# u x
19
7 @: z6 L" C6 Y20+ j7 j) e4 z# E& i( j1 M; {1 }9 i
21+ B/ u% f! a5 U/ k* H7 f; f, i
对于具有 MultiIndex 的 DataFrame,关键字 level 可用于指定需要在哪个级别进行重采样。
) ~ z* M( k7 o7 g3 S$ k) s0 odays = pd.date_range('1/1/2000', periods=4, freq='D')
* N: p1 y( x) y( D- kd2 = {'price': [10, 11, 9, 13, 14, 18, 17, 19],
8 w, H: g$ T4 X$ W& k' |( L/ K) H 'volume': [50, 60, 40, 100, 50, 100, 40, 50]} s- \( A6 n8 O5 Z H k6 ~) f
df2 = pd.DataFrame(
' b3 S3 U$ Q: ` d2,5 b/ B! Z: \$ v: V' v0 W2 l7 v5 ~9 O
index=pd.MultiIndex.from_product(/ L( r. c5 I! z. _* d/ f5 @
[days, ['morning', 'afternoon']]
0 n3 l7 R% |; z* x7 j1 B8 r ) \ @( ~. v6 U7 b! r6 n5 N4 {/ G
)
9 j! W/ K4 v+ y2 c, N0 k0 }- Ydf2
" V$ V5 z+ @2 o Q. E) e price volume% J! P( \7 r9 I
2000-01-01 morning 10 509 C% W: D% A7 j" f/ }3 A$ H8 o$ E$ q
afternoon 11 609 v: y$ ^! P6 X P' n
2000-01-02 morning 9 40/ q+ f. p. j9 z% ~) u+ n R
afternoon 13 100
% \* ~8 o" [$ k) V2000-01-03 morning 14 50
8 p# y s$ l* A5 B8 b afternoon 18 100$ Q" t1 P. g' G: W+ F$ V Y
2000-01-04 morning 17 40: N4 T% N3 L* n- O( j% w7 d9 K& e
afternoon 19 50; h" A& a6 ?( D
df2.resample('D', level=0).sum()
- J- Y$ S- u6 M price volume8 l3 p- l$ ]1 _/ `
2000-01-01 21 1102 l2 o5 E' x3 ^- \0 g2 S/ ]
2000-01-02 22 140( l1 r$ O0 a, h: o! P
2000-01-03 32 150
/ y: \5 C: U' e2 z/ G3 c6 ^/ u2000-01-04 36 90# W2 @+ E9 {) v/ i, L
8 t4 s& t2 ?) k* @1 g. [1: l# _, e$ V% s* K
2
# q" c. B# J. b* W9 s35 [9 E, t7 U# O2 y3 I4 X+ [
4
7 B% M, d! T0 {) c/ e3 G6 w5
- w" N$ }# \1 w6 d( Y6
/ h. Z$ x1 A6 y" V2 U76 X6 V6 e, G1 e& s" w% |" f( z# a
8
4 V$ `3 X" i9 {; O- e90 k: @7 P0 A* b" D& `6 @0 O. ^
10+ i" O: @( R% U2 j
11
4 i0 o5 H5 |0 x" M k0 w3 n- D126 U$ a: q2 t I9 s% I- M- D4 g4 V
131 @$ Q8 M7 Y8 O( ?( F
14
& D3 N8 e t5 X& q; w15
$ G+ F2 I- T- m: S' Q162 v7 t. n& O8 P' u, @. N! Z
17
* @9 {2 s4 }) D: {18' g1 I( b- Z7 I! h) U. a+ Z" a
19
' u# j. Q4 @4 h; f2 h20
5 p4 Y0 H. h' ^% M( |2 v' N* _' ~21
/ J! v# V2 o, h+ i( v22
: O7 Z& J( [3 n6 t- ~1 p; D23
; Y2 k% M. s- S1 X% w24
3 U* j% _1 t0 C' m; M2 i25' X) c/ Y, q) ]; ^# g
根据固定时间戳调整 bin 的开始:
% X+ j3 W: d; R5 o! o$ e" Ostart, end = '2000-10-01 23:30:00', '2000-10-02 00:30:00'
$ N9 ?- t- A4 \ c6 }& z1 C4 y) }rng = pd.date_range(start, end, freq='7min')
* ~8 |# g! V' }0 i* \ ~/ B+ e* i* ats = pd.Series(np.arange(len(rng)) * 3, index=rng)" s8 d) s/ C @5 `" S
ts8 c$ r! K- S) q7 N
2000-10-01 23:30:00 09 c* C) ^8 b" Y) g' j, U$ O
2000-10-01 23:37:00 3- ]) K4 Z( |! X7 H9 k8 G9 X
2000-10-01 23:44:00 6
, `4 m# [' B! D: w6 Y2 I4 T2000-10-01 23:51:00 9
" [3 s( h& U6 q( Q2 ^5 r. b- Z3 f2000-10-01 23:58:00 124 s7 d* t C& @- ?! B, Z! ]9 P
2000-10-02 00:05:00 150 v4 r# U4 I* G9 ~4 Z: R
2000-10-02 00:12:00 18
2 R. z% S# Y/ U7 E% u3 e2000-10-02 00:19:00 21( |4 C! X$ z6 R. t( M) L. H
2000-10-02 00:26:00 249 z( Y1 H: Y1 {) B* L; F# |8 |
Freq: 7T, dtype: int64
$ a8 j4 D# g3 \: l
5 T( \5 ?7 V$ Fts.resample('17min').sum()
7 l, z4 k1 h; ]: @' E' m2000-10-01 23:14:00 0
! `' \( m7 T' b. ?' P7 }/ E2000-10-01 23:31:00 9
R% c1 O- x. g* w5 w Q% b5 ?0 N2000-10-01 23:48:00 21
, K7 L- `1 n1 l; U2000-10-02 00:05:00 54
( a ^* Y9 M( h, n, D; C& r s; f! S2000-10-02 00:22:00 24
( h C5 b# z: L' }6 V/ u4 v" m, TFreq: 17T, dtype: int64
6 V" ~7 c. g" L+ z* q4 u* v" i I0 Q% Z( A9 G+ A( P6 ?% a
ts.resample('17min', origin='epoch').sum()
y3 x9 T( {9 t2000-10-01 23:18:00 0) d* }+ b( H$ u
2000-10-01 23:35:00 18
' N/ L& J6 c+ T Y2000-10-01 23:52:00 279 `7 |7 S9 B" S% [
2000-10-02 00:09:00 39
. I1 j; e' n7 s& r1 i0 \. b2000-10-02 00:26:00 24
. e/ t9 m& ]( f2 [ g5 B; C K6 n. }Freq: 17T, dtype: int64
4 }6 z0 s% J P
# ?0 K# p0 W# a& a: m% S% P, Vts.resample('17min', origin='2000-01-01').sum()
/ B* g" D, J- o$ |2000-10-01 23:24:00 3
4 y6 d. z, w3 m) ?) p. J6 _, u9 W3 o2000-10-01 23:41:00 157 z' D$ _1 G5 v4 v$ m4 Z
2000-10-01 23:58:00 45+ X9 A) n- A7 y2 G7 k; B* z$ d: R: i
2000-10-02 00:15:00 45
3 B4 p4 |6 j- X3 N, |Freq: 17T, dtype: int641 @ w y& Z' x
& d8 f0 I9 h6 b) A# X
1/ F7 I& p% e5 k/ ~
22 a) C4 t: |* C/ P* X
3+ U8 j& u3 \% s. w9 N
4: E% n! _0 a7 g. n: g/ z
5
0 t Q$ s) Q' ~/ x8 Y2 t6- G8 M" J4 }# r& l4 |
7
- P7 a. f$ B( @- N" H81 G4 \( d: U* ^
9
: g7 y2 V( N2 M0 v8 p2 x+ R3 A: M10
3 Z/ d1 t1 _ d1 C11
7 o2 ]- s! D: R0 Q9 W y5 t" V* K122 l0 m, F U% h: h' e3 m
13
4 i; E! X1 ^ [% b4 t14
* k. ~. e, A, C3 W. J, u15$ ~6 e2 b$ {6 P9 W1 B
16
$ T) w7 _. A- f C17
% h; {8 I+ p9 ^, ~181 _7 G& U2 Y! E# T2 N
195 X& c( e& Z$ S
202 V8 |7 n) `% U
21' M% ^7 p. A: T; d
22& f4 a( o, f9 c4 U7 B6 d3 L7 M3 ]
23- N3 b8 @' x; L+ E
24
3 Z: [& ]! u m3 u6 F# H25
9 l5 g3 o3 ^ a26& `' l& }# x! h( P( j `3 P: y4 B% }
27- g$ \: K. r, I, F
28; D' D* @( U; j, K; e
29" ^% Y9 T/ k" F
30: r8 {9 A6 D4 H ?9 o
31
6 }& O' G. e% Y# g- G3 v6 S4 I. ~. ?32, m6 p E# T+ z' o4 o9 J9 }
33+ w4 r/ w$ w/ E' z8 `* S
345 {# `% O: I; o& N9 p
35
# R! w2 }1 d# P& m' Q+ c369 P$ A Z& P& q
37
9 H( A- @# w( G1 A2 R如果要使用偏移 Timedelta 调整 bin 的开始,则以下两行是等效的:
5 l1 M6 S* S. |: F! H) L' v+ N. _ts.resample('17min', origin='start').sum()8 s$ @0 U5 h& B" N
ts.resample('17min', offset='23h30min').sum()
' }0 x( K' F! \2000-10-01 23:30:00 98 N! B! r4 }# x9 _1 N- ~, Q% h7 F
2000-10-01 23:47:00 21) j/ i" A. P0 O- @+ p
2000-10-02 00:04:00 54
/ t+ w! n) w; H0 w3 `' R2000-10-02 00:21:00 24; h0 ?7 h$ S+ e- ~1 H5 U, S
Freq: 17T, dtype: int64
, a1 U9 a5 N1 {8 a/ d& C1 q1
( \- d; Q- m% j# p2 R2
$ q0 F, C( S5 \& r' D5 _3
& V" u/ o: ?" c' h8 k0 {/ y v8 [4
% |7 t9 T' I3 K9 a5& r3 r1 A% w) D" j
6
& b {1 l/ q; o4 i, _* y7( H ]' r3 [% _' T/ T' T
10.6 练习
8 [* a, `! r, cEx1:太阳辐射数据集1 W& B. q: M9 L6 j
现有一份关于太阳辐射的数据集:
3 g) W4 X; P7 w! F/ G! l7 R& O* E! a
df = pd.read_csv('../data/solar.csv', usecols=['Data','Time','Radiation','Temperature'])
. M# [$ V6 }3 Rdf.head(3)
2 S1 c" b# L9 d1 y2 o& Z
3 ^" b9 K+ _. E" C' lOut[129]:
3 f& }/ K( n6 T$ `2 _8 s Data Time Radiation Temperature% D& G4 V6 L/ q" i5 R: v: m7 L* i' j5 ]
0 9/29/2016 12:00:00 AM 23:55:26 1.21 48
3 V* T v. ?) J( T" B1 9/29/2016 12:00:00 AM 23:50:23 1.21 48
9 O0 Z" Q; ]# a9 [1 N# e |+ ^2 9/29/2016 12:00:00 AM 23:45:26 1.23 48
2 X0 u/ J) x& v( I7 C1
& O& t# ^, Y0 P1 D& C( O3 M8 k2, r) c& l5 T" b, q: _4 g
3; ?" T' v: @' V$ J1 ^
4$ L8 q, s' a; O h
5
- b8 N5 H5 ^- y6
$ D/ `" e' I H% `# C; J9 S7
& C) G8 q `! p' Z$ o% L+ M- B8
0 i [! c C. s j将Datetime, Time合并为一个时间列Datetime,同时把它作为索引后排序。
! B& L; c2 C1 F每条记录时间的间隔显然并不一致,请解决如下问题:, l9 I% c, n3 e6 I' q8 ^
找出间隔时间的前三个最大值所对应的三组时间戳。
" ^. m. s: b& P是否存在一个大致的范围,使得绝大多数的间隔时间都落在这个区间中?如果存在,请对此范围内的样本间隔秒数画出柱状图,设置bins=50。
6 c6 z' H) E" I! Y% J2 F求如下指标对应的Series:+ e, d7 r( F4 R1 i8 u
温度与辐射量的6小时滑动相关系数2 R' @1 H. c: W' G3 n
以三点、九点、十五点、二十一点为分割,该观测所在时间区间的温度均值序列
! }& E! c, G& F! ]5 s. F, J每个观测6小时前的辐射量(一般而言不会恰好取到,此时取最近时间戳对应的辐射量)
/ w$ u' W) C, M \2 K4 _" Iimport numpy as np
; `% m9 [* ]4 Y. {; e& uimport pandas as pd! ]: D5 r1 J; x- N1 R0 y( t
17 p* q3 m3 T* D# x; A) Y: G" _! d
2
. {, K: R u( ]9 E将Datetime, Time合并为一个时间列Datetime,同时把它作为索引后排序。
$ M! m6 B7 ~( `# Xdata=pd.to_datetime(df.Data) # 本身是object对象,要先转为时间序列
& f% p% q# C' o. }times=pd.to_timedelta(df.Time)
# A8 {2 O. z# Y; W. a9 [2 t% C, Xdf.Data=data+times1 d' S7 H( k' M; [* q
del df['Time']
" i) Z. z5 P% W/ Pdf=df.set_index('Data').sort_index() # 如果写的是set_index(df.Data),那么Data作为索引之外,这个列还另外保留
$ R3 V7 j |0 }3 Q6 G5 t0 Rdf
1 K0 C k) I u( K Radiation Temperature; g% U) V5 F3 M/ }- }2 _" W1 r
Data ! a1 t3 e. q# V" e f4 t4 ^# [
2016-09-01 00:00:08 2.58 51
6 R* F5 g) p( a* o2016-09-01 00:05:10 2.83 51& k/ u! {4 j0 G' y& v9 G
2016-09-01 00:20:06 2.16 51. x, h6 l L# j: L' l
2016-09-01 00:25:05 2.21 51
A) U, y/ K* {+ P6 r2 g2 U2016-09-01 00:30:09 2.25 516 S8 w# Y. c9 B2 V( ]+ F
... ... ...
7 }1 H$ }$ V% u" {2 H3 J2016-12-31 23:35:02 1.22 41# v9 m* I7 R- j8 z7 `
2016-12-31 23:40:01 1.21 417 R F p7 c. G9 z4 k- v
2016-12-31 23:45:04 1.21 42
' d0 k' L' a- p* \: z2016-12-31 23:50:03 1.19 41
5 C4 s, |* ] L' V( e+ G- _2016-12-31 23:55:01 1.21 41" I! r) T* ]3 s' y o
) ~4 H- u' T4 k, g5 c* R* }1
$ L! z( p8 H5 R. K g* B2& b1 l6 a& A$ q9 |
3/ J. A6 h5 m5 V# t
4
% i1 H, U6 ~& e5. K7 [# v6 Z( a0 b% T( {
6
3 ^/ c1 i: q4 e0 v9 x7! j+ r8 V D, O* D% \! p
8
. N: J7 M' @/ i9
) B3 `4 g4 W& Y j10
2 B: _, i Q$ ?8 _- @ x) F11; J; X3 v. O* z+ p* }
12) m/ c, K" ?) j
138 z2 y5 m( D% @0 g k/ _/ G
14" L( f& o$ q1 |3 t; L+ w7 ]! ?
15$ f5 J, w1 z* }1 \
167 ~) H1 V; t5 P2 m# F* B Z
17) k% ?$ m4 T+ ?5 e, q' e% ^
18; \& i7 V$ K* e4 }9 |7 @+ ?
19! p" u d; R$ l5 F. I% b" H( q
每条记录时间的间隔显然并不一致,请解决如下问题:
1 {, B' s, J' b4 G L找出间隔时间的前三个最大值所对应的三组时间戳。
) Y) B1 q+ T0 c# 第一次做错了,不是找三组时间戳# E, O- Q5 {4 f, |: d
idxmax3=pd.Series(df.index).diff(1).sort_values(ascending=False).index[:3]. I3 k/ ~! t4 o+ c
df.reset_index().Data[idxmax3,idxmax3-1], w! `) L2 f! |! X! D/ T6 w
. r) x6 s3 F# P
25923 2016-12-08 11:10:42
" s( `% J3 \3 z! {1 t5 m# J2 A$ E( w24522 2016-12-01 00:00:028 H5 j# @8 s/ c9 K u& W
7417 2016-10-01 00:00:19
$ }( H& ?4 i* D% F. V. Q& OName: Data, dtype: datetime64[ns]
, ]! E8 U( Y, ^* |1
: m$ I, z% J8 Y# q* K22 B- D9 m& D$ f* J( g& G( U# C
39 Y+ j0 P1 p0 V/ S! f% A6 h+ _
4
% e& q, D% F* ?) i5
* B+ \1 X9 X/ \8 X+ N+ C67 ~$ ?3 J' d7 d" F8 I- O. n9 i
7) s8 ~1 Y) _% o+ u/ ]( Q- S1 a7 [
81 ?' h' U/ U2 Q! B4 x
idxmax3=pd.Series(df.index).diff(1).sort_values(ascending=False).index[:3]7 j/ S; r' O6 S }. ^) v
list(zip(df.reset_index().Data[idxmax3],df.reset_index().Data[idxmax3-1]))
B; w h j# O# {
! O- J. e7 d9 L. ^[(Timestamp('2016-12-08 11:10:42'), Timestamp('2016-12-05 20:45:53')),& M8 G# g3 }4 }( D% d5 ^, }
(Timestamp('2016-12-01 00:00:02'), Timestamp('2016-11-29 19:05:02')),4 O' R, u) E. h H1 n
(Timestamp('2016-10-01 00:00:19'), Timestamp('2016-09-29 23:55:26'))]9 p+ B* N4 Q* g; X9 m' l2 u. Z
1& m* T, ]& T% Z6 {: a4 x9 _2 s g# Z
2
: {6 h# o d: J! g l l3, c7 o5 r. l- x( n( T7 l. b j
4 T0 ^5 Z: m2 N; d/ Y2 X* J
5
. o6 n4 h2 S; ^6
8 W% \- V% ?8 D& g+ ^! o参考答案:$ p' @2 C* S2 |; g9 h
( E9 {/ i* F. E5 f, }8 Ls = df.index.to_series().reset_index(drop=True).diff().dt.total_seconds()
9 x; P& x; D: y9 _max_3 = s.nlargest(3).index
4 D4 ] i |' C& }& ~- s3 Y' [df.index[max_3.union(max_3-1)]5 _- @, D1 }1 I: s' k% {( o. r
+ V+ }* c. l" F4 @ B
Out[215]: * Q+ N1 P5 @( b1 D
DatetimeIndex(['2016-09-29 23:55:26', '2016-10-01 00:00:19',: O+ q: ~+ [& g' V
'2016-11-29 19:05:02', '2016-12-01 00:00:02'," }6 Z: s7 o {* [
'2016-12-05 20:45:53', '2016-12-08 11:10:42'],8 `$ w |! g- X2 S: l7 y7 R3 L/ a
dtype='datetime64[ns]', name='Datetime', freq=None)
' b* T, T) Z# \6 g, _1
y( G1 X$ L9 B4 N2 g0 ]; Z2; r2 A; O, ^- B8 O' s& ^. @4 q
3# X4 N- o% l5 U7 r
4
/ g0 b/ O* K# [" ]. u5
: m: _0 H: e( s% a- E& a, H1 E66 i, I, O2 ~) B: Q
7/ K- v: Q& s7 G/ ^: L% r
83 m4 q5 G2 J7 \5 [$ v8 |; R. J6 {6 @
9$ x0 u: H! D- M: ~( f
是否存在一个大致的范围,使得绝大多数的间隔时间都落在这个区间中?如果存在,请对此范围内的样本间隔秒数画出柱状图,设置bins=50。
5 G0 i, S" M0 D) U, }) @# 将df的indexydiff做差,转为秒数后排序。再求几个分位数确定取值区间
. N t6 p3 ?8 Ws=pd.Series(df.index).diff(1).dt.total_seconds().sort_values(ascending=False)
e/ x. e. K2 n3 r, R3 @8 Os.quantile(0.9),s.quantile(0.95),s.quantile(0.99),s.quantile(0.01),s.quantile(0.03),s.quantile(0.05)3 [7 @: g; c. h- @, h3 Y, L
5 U/ N" X9 J/ t+ d1 R(304.0, 309.0, 337.15999999999985, 285.0, 290.0, 292.0)
1 V5 |3 C" I$ V# s" U2 b1
5 @- v( k. ]0 }! j; L1 C- [: t6 s( |2$ P2 N+ x# y' m( |
30 y% g; W6 u. w$ ~' S* c! N
4; s1 D6 @' _( w: ~ m
5
( b3 U9 J, _6 J. n%pylab inline [9 { x6 t# f1 W+ M Y& g
_ = plt.hist(ss[(s.values<337)&(s.values>285)],bins=50)2 C1 o7 v( L3 p( i! @
plt.xlabel(' Timedelta'): m6 A; ~: Y6 F! E1 O( ?
plt.title(" Timedelta of solar")
( C6 s, _) C8 v& z8 R6 a q12 _% _* Q5 X' ]* U
2, s( \- w6 ?' B# s" t5 b
3
' n9 q3 D3 _8 v# c5 l4/ {# n* P! y% j' }) d# }( Y
- y% s, B: p, C. y
1 X9 e R) T L: R5 C求如下指标对应的Series:
. B9 A) t; j/ A: C6 R3 D温度与辐射量的6小时滑动相关系数
$ r1 t% l% v$ p# d+ N以三点、九点、十五点、二十一点为分割,该观测所在时间区间的温度均值序列
' m& H3 B# T; \; v每个观测6小时前的辐射量(一般而言不会恰好取到,此时取最近时间戳对应的辐射量)
# C i# k9 w( r" `# [: y h1 |3 ^df.Radiation.rolling('6H').corr(df.Temperature).tail()
) m. o1 ]8 m5 m' Z6 c5 K& e( G- {: i
Data
3 M1 @: _: {2 r1 L- u2016-12-31 23:35:02 0.4161875 U, w* T9 t7 _% b; |
2016-12-31 23:40:01 0.4165650 l& }8 `- N* k: W( `$ ?
2016-12-31 23:45:04 0.328574
9 f$ F, s% T$ `' Q/ V: H2016-12-31 23:50:03 0.261883
4 H, i7 x6 w5 i5 U+ Q& O2016-12-31 23:55:01 0.2624068 w1 t( U1 j* Y* r( V$ @/ D
dtype: float64
8 Z6 i& C6 v$ U( B' O$ w1$ E+ Q2 w: W5 m2 ~6 e
2
* v5 |+ q0 j" U/ f3' `/ u2 O- d$ I! x1 U0 d+ m
4; k& w0 o$ z( ^3 H; m% j, k
52 a" l1 N% a& I) ]
6. N7 v* s1 x7 m, ]3 \, J6 |+ w
7& V( p2 @3 H3 k/ M
8
1 f9 x6 M0 Z Z o$ I94 y g4 U# E- ~1 ^
df['Temperature'].resample('6H',offset='3H').mean().head()
. r0 b: J5 A' w0 c# k/ E, \
' l, v% W9 z/ zData$ ~# w) T% ?/ ~8 U" I
2016-08-31 21:00:00 51.218750! l7 w3 V& g, [- Z" C( P
2016-09-01 03:00:00 50.033333
6 W, L# X6 J' }& M0 C: o- e7 Y6 J2016-09-01 09:00:00 59.379310' D1 Z6 }# J0 w+ ]& X
2016-09-01 15:00:00 57.984375
0 E8 x; I6 M- I4 B2016-09-01 21:00:00 51.393939. l1 e1 m; o- z' P7 l
Freq: 6H, Name: Temperature, dtype: float64
: _' q2 l' @0 Q+ G- d z1
' |# o$ r- ]( J8 r. G8 _9 H2) [# H, ?+ ^$ X' d
3
* }* [; o2 @( ]4
% K$ G. b5 G: x7 @50 f0 a& w+ Z8 I, [4 |
6
7 K9 z* H9 u9 y1 }' b* n7
3 u8 L2 k# R6 y0 x; `8) |! Z) P3 D' V/ W \/ T& s2 R v! N
9
/ \8 {8 ]& k/ `1 R; {. D7 z$ X最后一题参考答案:
* W" \/ v2 ?- X
: F6 O$ v8 u; w# x& q' E, c# 非常慢. A/ o8 N; S6 m) ~7 Y5 \
my_dt = df.index.shift(freq='-6H')( I( N- ?5 I4 F' B2 b: ^. J: W0 F
int_loc = [df.index.get_indexer([i], method='nearest') for i in my_dt]
- d0 x$ ]% o/ q O$ ~ g; z- Rint_loc = np.array(int_loc).reshape(-1)9 d% H3 B9 t+ M: B/ p, u
res = df.Radiation.iloc[int_loc]
$ C$ B) z5 R* j$ s) Jres.index = df.index
: N/ H! O% q: R. v8 f u/ zres.tail(3)+ T/ C p4 i" a% P; X7 \, a# D% k
1
) a, c1 p% x4 N+ z# D! `) ^9 N2
O* T r6 L5 M/ N; K38 E( V5 G3 O! d( r! o& T$ l3 m9 ]
4
5 E x& a* u5 L3 ~5
: L( \7 x' H0 u60 J1 r" b3 c5 O2 r' t$ K7 v; }
7
/ a9 J* b- J$ t# 纸质版上介绍了merge_asof,性能差距可以达到3-4个数量级
/ W6 b1 m+ C9 ?2 X+ P- vtarget = pd.DataFrame( ]3 g, y8 T) @" q3 H' h- h
{2 s8 h! `6 V/ g* S+ r& _4 }
"Time": df.index.shift(freq='-6H'),2 B3 P" _! E, G* g5 Y; l: v
"Datetime": df.index,/ z0 ^, y! ^3 i5 W
}
; Y0 M" k5 T* V4 h/ a; T5 _)
; ]4 q3 q4 [7 j$ D( i$ ?4 D# `+ i
res = pd.merge_asof(
y8 {% ?( E, @' Q' h target,
/ {' G: q: w7 e0 f% t1 i df.reset_index().rename(columns={"Datetime": "Time"}),0 u) @# I' X- [$ S2 D9 N* W
left_on="Time",0 a$ _4 ?6 D7 |, W- w0 d
right_on="Time",
1 S1 F( O) l" n& y- g# \ direction="nearest"
8 k1 E& r4 W. i, M* ]4 Q- V9 U).set_index("Datetime").Radiation" H* {7 F- C) [7 \% D
% u; `2 r% d1 ]! v
res.tail(3)5 k+ `/ @- O! N2 [6 Y1 U) }- l
Out[224]:
9 d$ p1 l1 a4 A. _Datetime
# V) O% x2 G7 I6 v% Z. G2016-12-31 23:45:04 9.33: z# J2 K7 C5 r1 A$ y. I# [
2016-12-31 23:50:03 8.49: B% P* {- [3 J$ S# I
2016-12-31 23:55:01 5.84. j0 [' C* k, W# D( D; k, X
Name: Radiation, dtype: float64
! H+ o& g2 A8 ^7 f0 |) b: n1 P5 b+ a* G
1
: a' H$ z1 H! o- l2
0 [& \' q; U$ d- |3 a1 e/ s# B3
S: g8 V* Z7 o: s& ]* V4 }4
6 F- V: q/ i9 E/ ]+ @/ }8 Y9 x5
5 Y# o+ Q1 n6 ]8 y$ `" G6
: w/ \& ^8 E0 b0 ]: W D73 Y/ O* x. [ `) i, D/ g$ Q
8( c# Q' M1 i# s( [3 g! E
9
* N t$ d) j4 L& X4 M+ s& T10# }; g2 E9 Q6 ~. v% P7 Z
11; ^# m2 x, A: t7 N. u6 j; f; z5 H
12
- f. p' _& f, {13
9 ^/ P' ~$ _& f* i) Y14% C. _" k4 o! k0 A, N2 H
15
0 R' c/ w2 O/ }* F( W0 R0 Y) r16
( M3 \. o- v' ^ c/ y! K17
9 w! {# t* @' v0 H$ t18
. o% B6 q7 N# O19* I. m3 Z; t% N- m0 y9 G- Z X$ w
20
2 l5 j, g0 @* |+ l9 S0 H2 F; u212 @ D0 _% R: R+ H+ [
22( Q( L1 {( _4 u" e+ ?% D4 g5 n
23# P" {+ T! b J0 [
Ex2:水果销量数据集. `, l3 a. {" y- W) f
现有一份2019年每日水果销量记录表:- [4 ^7 T- J9 Q1 z( E3 }% k
1 v. [6 ]' l" ] t+ a
df = pd.read_csv('../data/fruit.csv'): @' X% O" V+ k, M& E
df.head(3)
* @+ w `- e9 s b; u$ @7 f( R0 c3 V& M& | G# V n- M6 s% C& P- v9 A
Out[131]:
) c' |" J. M* t# V Date Fruit Sale
: u. H$ M& G1 V0 2019-04-18 Peach 15
; _* n* K' t& ~2 M# P% d' |1 2019-12-29 Peach 15: f% z8 Q% e, l- p; C3 A
2 2019-06-05 Peach 19! {6 h" `3 J/ x& l! T
1! o" y( u. x% g, v6 ]6 l
20 l$ M) h7 R# A9 u# P6 l e
38 I8 R! V- {" C0 c/ u) k
4! g) V- Q, q4 y% l
5( @! D, d4 B$ W- f
60 K) ]; X8 }& g8 I/ W0 H8 |3 l( D
7: z$ \) H+ a/ F5 f( Q( r
81 B( ?) T1 g% d; Y9 o
统计如下指标:
& h1 u6 t. @8 [ r6 m6 {. T每月上半月(15号及之前)与下半月葡萄销量的比值
* ?' e8 m7 v" _4 Y; H) i& h每月最后一天的生梨销量总和
: E. ]' [9 X2 b* }, H" n4 c每月最后一天工作日的生梨销量总和* O2 x, G1 N2 g0 W
每月最后五天的苹果销量均值# B' u+ S; j2 G* {% w% V
按月计算周一至周日各品种水果的平均记录条数,行索引外层为水果名称,内层为月份,列索引为星期。
: R/ ~: ^: \# t5 o/ P& D按天计算向前10个工作日窗口的苹果销量均值序列,非工作日的值用上一个工作日的结果填充。
( }. {/ U1 H& N4 W: H) b1 S% limport numpy as np$ P. E) W7 D& G3 T( U) n. K
import pandas as pd* G8 S% X! k( V+ b3 ~% r
15 p1 z- s4 W' \0 \
2' w) [, n, y8 R: l& S, ^5 ]/ p/ i4 B
统计如下指标:
0 Z& X# [9 {9 x; I) F每月上半月(15号及之前)与下半月葡萄销量的比值5 n" ~3 b6 ^. X2 u( Z" {
每月最后一天的生梨销量总和4 @" M/ H$ A4 h& f) V t
每月最后一天工作日的生梨销量总和
0 `" A6 l4 X* D' O q每月最后五天的苹果销量均值1 p' ^$ Q- O3 ^& N' t: O0 Z
# 每月上半月(15号及之前)与下半月葡萄销量的比值
, U! }5 U8 j4 [. i: Y* M7 tdf.Date=pd.to_datetime(df.Date) `0 b3 \) m7 V& q6 w/ M1 L/ G
sale=df.query('Fruit == "Grape"').groupby([df.Date.dt.month,df.Date.dt.day<=15])['Sale'].sum()
" ]9 S: o) A1 Csale.columns=['Month','15Dayes','Sale'] # 为啥这么改没用啊
3 e9 I1 o1 C8 {& C* F$ Psale=pd.DataFrame(sale)
: F6 ~) z6 B, W. [" jsale=sale.unstack(1).rename_axis(index={'Date':'Month'},
+ G S- e% T6 d7 L4 W columns={'Date':'15Days'}).stack(1).reset_index() # unstack主要是两个索引都是Date无法直接重命名4 U0 R( C9 x( o) n- ]
sale.head() # 每个月上下半月的销量
/ s3 }% h3 J! m% `+ b$ w! Z, R
Month 15Days Sale" e$ Z) f! D) F7 P) D
0 1 False 105032 o8 F% z* }; y: }( Y
1 1 True 12341, q1 p- k7 _& Y; [. i
2 2 False 10001
4 m3 B& `3 V% v* |5 I' r3 C; X3 2 True 101060 i7 ]7 {: k- J) k- L/ `/ H
4 3 False 128143 ?* v K7 R" Y* h3 f
* x, K* h7 x% N% Z8 R3 J
# 使用自定义聚合函数,分组后每组就上半月和下半月两个值,根据索引位置判断求比值时的分子分母顺序
& N6 l, y' T) g; B6 N, [sale.groupby(sale['Month'])['Sale'].agg(
4 f+ \; W' N2 t- t7 A# v6 ^4 ? lambda x: x.max()/x.min() if x.idxmax()>x.idxmin() else x.min()/x.max())
9 T$ h9 y7 D% v* |3 W1 j9 p9 P+ ?9 E; H* u
Month
/ g8 C5 g+ D: x1 1.174998
' W% U% R7 O4 s1 {: s- R2 1.010499
: C: W! m) v$ t* P `3 0.776338
- O# x, r$ z6 D( {4 1.0263453 |& G( S3 q2 x4 u* l
5 0.900534
% D( ^- A, V# Z- X y, J6 0.980136 s. V7 @3 z$ B7 x; T( x
7 1.350960
+ Q6 M2 R% T& ~* F8 1.091584
# G* R9 a4 a* P! o3 F ?. r9 1.116508* \3 b% Q: [) S5 z, P# B& i
10 1.020784
) F" H- [, G% R7 v1 u8 e11 1.275911
- I4 _) a0 x" t" ?12 0.989662; Q* t4 S8 p4 z
Name: Sale, dtype: float64
8 r" f6 U" u% f* a5 N0 F" j l" [# R
1
8 v8 U# g! t9 _: ~8 I2) D: b* f7 Z5 _! ?4 c0 j
3
' q% p3 c& B( y7 \1 k3 p: c4
2 C- T3 Q% Z1 P' T# K5' Y( j# }, E6 P! P- e% f8 \
6
5 x: A9 Y% H% ~5 f/ U2 [9 a/ Z: W3 C, u76 O0 p e4 X" @ ~; |
85 b$ ? M7 I! @+ o
9- M2 U/ l, f5 t. b; e9 P! R+ O
10( C1 _9 [* N/ {
11% C; F: \4 G7 M* R
12
* d) H8 u$ C5 t) M# ? j13# X" P7 x4 h+ d$ |; ]
14
# [3 x* f& W1 x/ l! O15
9 `/ b- h5 G, O9 |0 ?. Q16
6 P2 K+ K7 L2 \+ f175 Q, O' p2 f. t2 C
18
" w- P K6 m" N' P7 C' B) H) x S19
; u3 b! u; V0 C, C4 @* S Q8 P20
v% K$ t4 ^0 S, c7 M% o# b7 N212 b; M! |" E2 X6 g: P- y
22+ G0 {1 l+ ]1 W; p. N! A
23
% V9 j( D+ k4 h24- ]% j# m" j8 A7 U$ s# D9 J
25
+ h5 ]* O; }/ \2 l26
, X" J9 x, p" o9 }- U. Q4 e! n27 b. k9 f6 D+ D3 {; k) a
28
& A; [+ J& T( z. c" R290 B6 b3 K* _2 t5 M7 H6 ~
30
N5 Q8 [! ~2 a# ~8 L% G313 d) _, y( `- h% @! Z' o
32* W0 ^# h# K/ ^" X* Q) H
33
" Q; s) b1 s; Q" b2 @34
; q1 h) @) w. B# 每月最后一天的生梨销量总和0 ]6 F5 s m/ W4 `
df[df.Date.dt.is_month_end].loc[df.Fruit=='Pear'].groupby('Date')['Sale'].sum()
" O( e& ?' u" }' J3 X9 b J9 O" T# {; C5 w: W
Date: s$ U# `, d: H* }' B) @# b8 H
2019-01-31 847
& o; M% f! B. o! [3 e; {1 h& U* q2019-02-28 7746 o5 @" [: j. e' ]# ] \8 I
2019-03-31 761" z3 y/ B( U' h6 c p; X
2019-04-30 648% n6 _9 Q. D! r. y! f6 d
2019-05-31 616
?; Z u) I0 O, Z9 {. H1
' o3 l2 s4 E* d* _2
! z" y: u: ~6 \1 Y( Z3
5 v5 i2 X# |; J3 \6 @/ ~4
0 U; A& h' [. f. y4 D, h5
- v" E9 c' T3 U3 `6
% [: k: r* @+ B) t3 H i7! g# ^; o* ]. q% F# {6 j
8$ L" _ _8 }5 d3 ]' }
93 d6 A, Y0 g: ]! n( l. `9 r. A+ f
# 每月最后一天工作日的生梨销量总和' ?6 {! B5 v% o( k. ?# S" ]
ls=df.Date+pd.offsets.BMonthEnd()
8 L; G7 [. r( |my_filter=pd.to_datetime(ls.unique())6 d' B" s2 K% W7 @1 ~
df[df.Date.isin(my_filter)].loc[df.Fruit=='Pear'].groupby('Date')['Sale'].sum()8 x% i3 F5 l/ v1 l
3 J! ?9 q. t. s' l- J& f$ U% o
Date. l0 V0 s+ \; [
2019-01-31 8478 Y5 h' n4 [8 F1 }, U
2019-02-28 774
R0 S9 \1 e# o" c) @/ u. c; ~2019-03-29 510& `/ p- g7 ~1 g0 n9 c8 U
2019-04-30 648! a7 I. C+ ]( z, t0 x
2019-05-31 6163 U5 H6 T4 P% f _9 X' r$ E0 ?
1
/ [2 O) W5 @; h9 A4 b2
( Z" g* W1 z5 ~/ {3
. _" R2 t# n; K8 `$ k- @4
. @" `% F% E$ \$ }5
8 I( H! g2 S, ]# g3 i0 u6) R4 x2 u% A3 l+ l$ h
7
8 V0 D, |: g4 T" v9 y4 H# g( N89 o, ^# R0 F1 G
98 t7 w; a: z; g( C
10
% D& L7 d- A7 `& H5 O7 m" `11+ d3 r5 ?* E, _9 ~6 D
# 每月最后五天的苹果销量均值
/ B( y( D V, i7 Sstart, end = '2019-01-01', '2019-12-31'
( Y/ y+ h% G) v/ u$ U* Q7 Iend = pd.date_range(start, end, freq='M')
; m$ D* ~# \ |* q" Rend=end.repeat(5) # 每月最后一天的日期列表,重复5次方便做差
' H8 W4 i/ k6 O0 K E- Q$ D+ [
8 g$ C3 O% \, g4 x9 k) }td= pd.Series(pd.timedelta_range(start='0 days', periods=5),)& Y+ k+ W( F( p- r
td=pd.concat([td]*12) # 日期偏置,最后一天减去0-4天
7 g& c u# {+ {" |1 b+ nend5=(end-td).reset_index(drop=True) # 每个月最后5天的列表. o" j- D; M% t# d* Y
* R. \- K k) W' d3 {' N4 `4 C
apple5=df[df.Date.isin(end5)].query("Fruit == 'Apple'") # 每月最后五天苹果销量
0 ?4 C- w. v' p1 m4 Y/ o3 yapple5.groupby(apple5.Date.dt.month)['Sale'].mean().head()1 n& Y" [% ]; n) ?* {
# ]" K) U) d2 Y$ T8 Y1 B
Date
d7 Y8 s5 P5 ^2 D* G1 65.313725" d! n; O+ j6 P3 l2 l
2 54.061538( y$ N- I* }8 ^, [' i9 [
3 59.325581
) B7 t3 U. x: g, ~% f" w' `4 65.7954558 l5 ~, t# e/ f0 \( k- U4 l3 I
5 57.465116+ i9 k$ b: O( J, u) T/ e) b
. R: b8 w. Z8 b" m7 N3 W1& i$ p7 b+ ~! h
29 u* |- u; L% W# U
3
1 V9 H; s5 a; F8 p+ D/ ~43 c' l. }. o2 y9 |4 {* N
5
8 S$ O/ ~8 Z$ F c2 S& A: Q: S6$ Q- \8 }) v! a. ~( y0 O
7) R2 P4 g) n* I* l
8- j8 [/ K, ?! t
94 a( i. g4 x/ ^* I/ e4 s: }1 X
10* |% C1 b% {: E) o# X6 O
11/ w( D" y3 Y I) u
12
$ L( f! E b5 ?/ B7 g131 _6 ^5 _( p3 D2 b% @
14
8 @% D" R' |' ]6 I& u156 X" ]) \) w4 |8 i8 }6 X
16' i+ S1 w: Y* U
171 K' u; \% s$ m
18. i6 Z* ~" x. K" v
# 参考答案:3 `: q5 A% t4 W) }1 z% v, q
target_dt = df.drop_duplicates().groupby(df.Date.drop_duplicates(
8 D# j2 n3 V4 R+ p0 J3 W) X ).dt.month)['Date'].nlargest(5).reset_index(drop=True)& O+ T& {/ e. o* B% O+ l9 v
; _9 i0 m5 l: W* `5 t
res = df.set_index('Date').loc[target_dt].reset_index(
' A6 K* Z4 @$ e: L5 n ).query("Fruit == 'Apple'")# a" \- `' l _/ [7 k8 G
( c2 N1 x- s! i' k8 l7 bres = res.groupby(res.Date.dt.month)['Sale'].mean(
; D$ E! V+ \! h4 k ).rename_axis('Month')8 `- ?" ?" ?8 t, N- n6 u, k
' ~; E1 f* _1 J* ?* o
; n1 n0 l* _) _0 }- m
res.head()
# a3 ?+ i% F- D, n) ZOut[236]: / D. T* R0 M9 {% } L
Month" U. b3 F" N. }5 k. y
1 65.313725
( S7 l) }; @/ X6 U) Q% N8 B2 54.061538" L4 F& W$ E& b+ n, V9 P9 O3 }0 ?
3 59.3255817 W+ |' D8 t% d& x M5 O& u4 F: c$ F
4 65.795455
3 W( H% l$ m9 B& E8 z% k r. t5 57.465116
s- ^/ |6 I* o2 yName: Sale, dtype: float64# V- A. D% G1 b* p' Y
" o. Y5 R, e& b. Y4 N
1
0 U {; w% e4 P) x n7 M- n2
5 p2 l( U3 N, M' ~ [2 k3" ^, P3 c2 g/ n1 T0 n8 K
4
- S4 j) D) t* Q3 G" \55 ?9 _1 c3 I; Q- J+ h
6
+ j7 \. m3 D" c; ~) |3 u76 v% \8 |7 f$ d& k
8' q* r' ~) h! ?5 V; W. E5 {
9 n+ o7 C) N5 ~& ~0 I- `
10
4 {) B$ O3 p8 \" G11
5 t! [; s# z" T9 J120 X4 M1 \1 C2 F
13# } |3 d- I" Q5 L
14
/ L1 D3 `: d$ c3 h' E: w15
1 u! B% f7 n/ G# I7 D16
( J. T" J+ _$ p0 l0 {, d17
7 m6 A) A; [' E+ x18( y% |( U2 r f' ]+ e3 |# `
19
9 ~ N' @: o0 B! \# u! F* s207 v. a6 G& T( B
按月计算周一至周日各品种水果的平均记录条数,行索引外层为水果名称,内层为月份,列索引为星期。
9 g0 H# |: L( J" P# sresult=pd.DataFrame(df.groupby([df.Date.dt.month,df.Date.6 [& r& t& ]* q: I; J
dt.dayofweek,df.Fruit])['Sale'].count()) # 分组统计 # ]6 U' ^* L+ R
. _) ?9 B% a4 b' r- Fresult=result.unstack(1).rename_axis(index={'Date':'Month'},
6 f, l6 I0 Y. E3 \+ i8 r, U+ q8 f, m columns={'Date':'Week'}) # 两个index名字都是Date,只能转一个到列,分开来改名字.
% H$ \' @( e, Mresult=result.swaplevel(0,1,axis=0).droplevel(0,axis=1)" M3 _: Q0 H3 S8 M: L# g+ U l
result.head() # 索引名有空再改吧' X5 R3 Z% D- ~0 l. A# ^" ~. |8 w4 \
5 M9 U( T7 e5 r Week 0 1 2 3 4 5 6# P/ f4 r! W* H3 |- _0 l/ Z
Fruit Month
# ]. \2 M2 i% h lApple 1 46 50 50 45 32 42 237 z' M, ]$ {. X6 r
Banana 1 27 29 24 42 36 24 35
9 x: J6 t/ u6 LGrape 1 42 75 53 63 36 57 46
) Q. M; U5 Q- Z# t' e9 fPeach 1 67 78 73 88 59 49 72
8 N2 G5 ]. W9 f0 H1 \( XPear 1 39 69 51 54 48 36 40
4 F0 i0 ~) Q. O+ K1/ R/ Q3 ~, L- H% v
2$ m' |% \% g6 g0 s) d
3
1 @" L: H5 v; w. e43 O/ H3 {) ~ T6 Q, E
5. ?0 |8 @9 D3 L$ o( i5 G* W0 F
6$ I% E! \- v1 n$ ^6 \, b
7
2 |! k8 |1 x5 ^6 Y7 t( e8
! k: m, i, m! W! \1 y1 o9
$ a. V; s* y- S4 E) x$ D$ H( |( W! D10
4 L" l+ Q$ _: J& Y I11
' O/ w$ {1 _. |, b12
( V: K* P# t0 f' F& E+ S' q136 K, |9 P4 ^0 t D
14
2 v# T( p. x9 e/ i15
. b5 @" r. S$ ^/ i+ a按天计算向前10个工作日窗口的苹果销量均值序列,非工作日的值用上一个工作日的结果填充。5 U: F# R0 B3 }
# 工作日苹果销量按日期排序- Z: T+ s4 p5 O' H, [3 A
select_bday=df[~df.Date.dt.dayofweek.isin([5,6])].query('Fruit=="Apple"').set_index('Date').sort_index()
: j& @. ~- C1 y2 A0 \select_bday=select_bday.groupby(select_bday.index)['Sale'].sum() # 每天的销量汇总: P9 A% X0 ^7 v3 _, J: Z3 a
select_bday.head()
! y, w7 j6 ^+ j; l2 k E4 @& O5 ?2 y5 L
Date
! |" f. Y' k+ ]+ }$ f; |2019-01-01 189" P" S& Y1 k3 X6 w
2019-01-02 4826 `' r3 {) D6 o+ L2 {. b7 N: H
2019-01-03 890
5 r; ~1 J1 p& X- Z" W: f( c+ o7 m2019-01-04 5505 t \; ~+ Q- ]3 V
2019-01-07 494
# z. i1 l& c; J ?4 u7 f2 z0 k# e/ H* J: K$ o7 L/ L( N( ]6 q
# 此时已经是工作日,正常滑窗。结果重设索引,对周末进行向后填充。" h9 D9 I- d5 {2 C
select_bday.rolling('10D').mean().reindex(df.Date.unique()).sort_index().ffill().head()0 M" f" d7 h4 L* ~! Z4 L
" w% B' B2 l$ y2 vDate, O0 q6 _+ O& V2 B" J- e+ g
2019-01-01 189.0000009 b4 C7 W; [3 Y' [6 K
2019-01-02 335.500000! J% ^+ V/ C4 }# e
2019-01-03 520.333333
2 t @4 e( k" q' T2019-01-04 527.750000# c/ i1 M# P. J) q( c: ]4 {, n4 t
2019-01-05 527.7500009 E! c$ L. m( C$ |
( P6 [0 o5 I) ^- m R W
————————————————# u2 V7 h, w& S
版权声明:本文为CSDN博主「神洛华」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。" x1 u* _( v6 `% A
原文链接:https://blog.csdn.net/qq_56591814/article/details/126633913; Q j- f0 X- `3 L p2 X8 |
( v# n* b \ |9 t3 ^: n7 K
/ t0 e6 c' H' {( b% r |
zan
|