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