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