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