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