- 在线时间
- 1630 小时
- 最后登录
- 2024-1-29
- 注册时间
- 2017-5-16
- 听众数
- 82
- 收听数
- 1
- 能力
- 120 分
- 体力
- 565654 点
- 威望
- 12 点
- 阅读权限
- 255
- 积分
- 174919
- 相册
- 1
- 日志
- 0
- 记录
- 0
- 帖子
- 5313
- 主题
- 5273
- 精华
- 3
- 分享
- 0
- 好友
- 163
TA的每日心情 | 开心 2021-8-11 17:59 |
|---|
签到天数: 17 天 [LV.4]偶尔看看III 网络挑战赛参赛者 网络挑战赛参赛者 - 自我介绍
- 本人女,毕业于内蒙古科技大学,担任文职专业,毕业专业英语。
 群组: 2018美赛大象算法课程 群组: 2018美赛护航培训课程 群组: 2019年 数学中国站长建 群组: 2019年数据分析师课程 群组: 2018年大象老师国赛优 |
A7 P# G" _7 M9 U
; b$ w) Y# k6 _, u: L
3 Y" w6 G" s! ^+ Q, W& h0 S文章目录$ F* X' m7 c' Q+ \
第八章 文本数据
$ I9 r) h# `# Q: Y. C4 p" n! c8.1 str对象! x. F# D4 a" v. R2 \/ W
8.1.1 str对象的设计意图; A% G# V- T( ^2 M# f4 b6 z
8.1.3 string类型
# A {$ c& I- h! S8.2 正则表达式基础
' H3 U# U5 C% n- h, ]8.2.1 . 一般字符的匹配) H1 h+ j" @* j: U2 [* w
8.2.2 元字符基础
6 P% b' m& \8 @) i+ \% [+ }8.2.3 简写字符集
/ v/ i& M/ S& d+ }4 E: h: H; s! E6 ~8.3 文本处理的五类操作+ U: D: s" @ s1 T1 i8 E% B+ x
8.3.1 `str.split `拆分
1 E( A( J+ q3 g9 O8.3.2 `str.join` 或 `str.cat `合并
) [( k8 d3 W- i& K9 q: _8.3.3 匹配
$ m; ]( m2 m) I9 I2 s2 O; O p8.3.5 提取
. g+ s: o( | R7 n3 W g& ]8 N5 H8.4、常用字符串函数* z; Z% {+ |- y9 [2 z/ s5 v
8.4.1 字母型函数6 V: A: y$ c$ |$ u, D/ j
8.4.2 数值型函数
. V6 d- u, ?5 i7 U8.4.3 统计型函数
( s* r9 s8 l. _0 K8.4.4 格式型函数
- K: n h* ]% x8.5 练习
# }) \% n N) ^3 }7 p/ oEx1:房屋信息数据集* O6 q i' o- m8 @/ J3 i" P
Ex2:《权力的游戏》剧本数据集
0 e6 @6 H2 [) {, b8 x2 ^% J' a u第九章 分类数据
+ f+ c/ U8 ^) n8 M( o8 x) X9.1 cat对象
" ]5 K& R& k8 H& {/ G9.1.1 cat对象的属性
: U0 U6 @2 R5 H4 j9.1.2 类别的增加、删除和修改& u( Y9 s6 `5 X
9.2 有序分类, k5 ~5 T) o3 q( g4 l4 z* v8 W
9.2.1 序的建立4 i1 l f, Q& g2 F9 j
9.2.2 排序和比较; x8 v ^ U j: t: u
9.3 区间类别
% F' s0 q Z9 \' |+ S( G5 ^9.3.1 利用cut和qcut进行区间构造3 ~( H3 w+ l: \0 i, M
9.3.2 一般区间的构造
6 ?9 H. X* I0 V$ M6 l$ @9.3.3 区间的属性与方法
6 G% H7 i: K& t9.4 练习
5 b. O1 H# t; V/ V g" K* d$ l6 zEx1: 统计未出现的类别! o$ a" `4 e- @
Ex2: 钻石数据集
& P* N1 G- r: D6 ^4 l第十章 时序数据
. W) m/ x/ ?4 j10.1 时序中的基本对象
8 `: A5 s3 n0 B7 R& w3 P; H10.2 时间戳% e4 q' f. s+ ?% Y
10.2.1 Timestamp的构造与属性" r) M5 M2 }0 a3 _- f' M- T
10.2.2 Datetime序列的生成
% h- a- i+ ?2 N( t l% z8 ?10.2.3 dt对象
2 f$ [0 c N) M1 u10.2.4 时间戳的切片与索引" g, t5 _) c/ v# Z
10.3 时间差, V( n* U1 j& L9 y' d
10.3.1 Timedelta的生成! j% ?* ]( V6 {: M' H
10.2.2 Timedelta的运算
9 y. E! i+ c0 ?5 w2 D+ g10.4 日期偏置$ Z: M, M5 g: A
10.4.1 Offset对象2 O/ x& X( t: h8 h# i$ Q
10.4.2 偏置字符串
6 L ?5 ^& X( L( Z ?10.5、时序中的滑窗与分组
& x/ o$ w- N; h+ `4 O& s3 y5 c10.5.1 滑动窗口
0 Q5 C, m1 ^! ~! [( Y; x. m10.5.2 重采样 ?" b4 ^' D+ ]) c
10.6 练习
% K7 j+ \ I1 W5 B: yEx1:太阳辐射数据集% O- s0 E, z T6 f8 ~" _
Ex2:水果销量数据集
- ^! u/ P/ H* o$ M7 R7 y 课程资料《pandas数据处理与分析》、github地址、讲解视频、习题参考答案 、pandas官网- S& q* f7 {- m
传送门:8 c$ g' E+ q; z0 K- a, ?2 A/ B3 v
) M2 e% N& z( H d2 F4 a7 ?datawhale8月组队学习《pandas数据处理与分析》(上)(基础、索引、分组)
- g/ o- M g m" l" mdatawhale8月组队学习《pandas数据处理与分析》(中)(变形、连接、缺失数据)
& T* \! |+ I; s. B6 p$ W3 t第八章 文本数据
; s A4 O" W2 f3 y! r8 d2 p8.1 str对象
7 w4 v6 H! l$ S+ a" v( |' a6 n. y& A P8.1.1 str对象的设计意图2 \, Z3 w2 Z* T& c$ S: P# ~
str 对象是定义在 Index 或 Series上的属性,专门用于处理每个元素的文本内容,其内部定义了大量方法,因此对一个序列进行文本处理,首先需要获取其 str 对象。在Python标准库中也有 str 模块,为了使用上的便利,在 pandas 的50个 str 对象方法中,有31个是和标准库中的 str 模块方法同名且功能一致,例如字母转为大写的操作:
: r" b* [9 U2 | I6 F7 w1 O; f. X) k
var = 'abcd'8 W% m9 c4 \7 h' m, M6 L
str.upper(var) # Python内置str模块: I+ d* J" x/ U3 O1 D
Out[4]: 'ABCD') W) L2 q: D& }7 A) O7 _) z
- A: F7 b9 X( P7 c" n- N6 |6 E
s = pd.Series(['abcd', 'efg', 'hi'])6 k% h; V/ j7 [. `. m
0 \ d- `' p! s4 C. As.str
7 A( i& N# ]9 s4 K9 l: q5 g8 lOut[6]: <pandas.core.strings.accessor.StringMethods at 0x2b796892d60>
% e+ n: O0 ~- o$ U- C2 @3 f' X
$ I% T" \- X2 W6 [! b% q3 Rs.str.upper() # pandas中str对象上的upper方法1 _ C- j `' _! s& d
Out[7]:
/ [. p/ C7 a4 B- E3 n0 ABCD1 ~. F- l$ a7 s' {( a5 E H
1 EFG
. a. u: b4 N" Q9 r7 T2 HI1 R7 s7 V9 G, U$ G: P3 O. D" N
dtype: object* c4 W1 d& k* _: X. i
1
2 L, g2 {8 i# o2
# B, Z: x3 V# k- l5 S" T1 }3
+ N; Y! [- J* K7 H# G8 \- y4. q9 G4 X% M2 ]- M' O
5) n. E8 d: Y! _/ z7 I
6: X6 X+ G# L1 H
78 C8 y6 s ?& Z) \
8
4 Y* M3 |0 ?. Y7 F* B95 J+ X+ u5 }) I" z; h
107 X) y0 p( @" I1 g
11
+ n8 T, c# o! }12
9 L( ?7 }7 y+ N/ u13( S2 v7 r# e- m! u) M/ S9 `5 m
148 z8 z1 V% K$ f
15$ ?& Q; ^! `5 ?5 [! v+ g
8.1.2 []索引器7 h. k( k+ {7 C! D8 E( a
对于 str 对象而言,可理解为其对字符串进行了序列化的操作,例如在一般的字符串中,通过 [] 可以取出某个位置的元素,同时也能通过切片得到子串。) e0 X8 B! r$ x* m) P
pandas中过对 str 对象使用 [] 索引器,可以完成完全一致的功能,并且如果超出范围则返回缺失值:5 e" l' x' W+ |: x9 x
* P6 m6 k7 B! b
s.str[0]
+ o% T5 ^- c; JOut[10]:
, i# R8 [5 Q V0 d) g! j. C/ F0 a) W# @: U% \& n3 ^' u/ [' s
1 e! G$ A; C$ T0 D s& o* ?) Q+ |, h
2 h
7 Z9 t$ q+ |6 u# zdtype: object
+ K& n- o. g3 N0 o* @1 H4 D0 x; W" p* I3 ]1 l: ]/ W
s.str[-1: 0: -2]7 D7 K% C2 \. x& ~3 g" ^3 G
Out[11]:
, N6 g# q: d ]% N0 db
6 u0 a# T) Z0 ~/ d6 }1 g
0 t+ i6 C u1 A7 P2 i* J) w0 U0 S* n1 Y
dtype: object
8 B( ^0 z$ G8 k- V) u- R8 t5 D0 \) _( l( E& {% Y( F3 b
s.str[2]1 z( E# D4 w) y! a5 X7 g
Out[12]: 8 a) ~$ a: o4 i2 E
0 c M; |% k, x) Z1 ~* O: K
1 g( X b: R& a$ Q
2 NaN
5 ^, i- N# I1 _% f8 I5 E1 t# G5 v3 edtype: object
. @. j7 K9 N3 r6 G5 |; g. x7 Z: {$ t: x/ b
1
y) M8 V! k j8 Y4 i2
. Z1 Q1 k- z9 Y1 ^3
! l) F( C0 H( c- _& c4 a( s, {4
" k7 {: E4 d- ?( c56 m' ]3 K0 j+ f8 i' L1 C: ]0 n! Z2 q
6 G; @+ `0 `5 \5 o
7
, m$ p1 f1 ~8 ]8
; _# W$ P! y. B5 ^4 h9 i1 K97 L( J; n8 G% w3 M
106 }9 _+ E+ Q8 Y. K; u
11# {6 T3 U+ s* B) M/ \
12. i ~# {5 R8 v9 a
133 F2 R1 a" T7 P/ E
14% v" Y1 e4 w5 P9 q
15
# V) q8 r! c- \8 [163 A& @/ E$ \- A; m6 U! \# [; \
17
' `& V8 U' x0 \; P5 N8 {184 Y: W7 j8 M. f( I* ?. ~. ^
19
5 p w$ d# v2 B9 M3 E" r20$ Q' |: S4 A5 c2 T [
import numpy as np
# l2 ^7 x P( i) ^2 I( |! Mimport pandas as pd% h1 X% a* o3 c8 O$ \/ [- b% ^5 D
1 N) {! }0 ]8 Xs = pd.Series(['abcd', 'efg', 'hi'])2 A9 n0 X. e9 Y) m P
s.str[0]
+ ?7 N9 u" |( S2 g, a& q3 U. L7 j1
& ~+ a2 \2 t3 I q I& E! \7 }6 |2
/ {( \4 u' L9 x" i0 G! C3
. g7 E8 Y0 d$ F, f1 {. j& x' D8 [4. L2 x0 {5 I- Y! |+ V
5
6 `, `( g5 D7 I t: [0 a
8 }2 T. {" J) {1 e6 T6 T( p0 y& a& V9 j7 g$ G! E
2 h! ]4 O& j7 @2 Z* X. `! F
dtype: object
8 V5 h. n# d2 _* o& i1, F, x$ ~- a. `+ H. Y6 ^, n
2
! r2 z* ~( [3 T) y- o3 X3
8 o" r$ U' V4 b2 ~/ ]4
! B) X) g7 q# e( H! N8.1.3 string类型( |5 ?- r! d3 N, j( f
在上一章提到,从 pandas 的 1.0.0 版本开始,引入了 string 类型,其引入的动机在于:原来所有的字符串类型都会以 object 类型的 Series 进行存储,但 object 类型只应当存储混合类型,例如同时存储浮点、字符串、字典、列表、自定义类型等,因此字符串有必要同数值型或 category 一样,具有自己的数据存储类型,从而引入了 string 类型。
8 m6 @2 H) B( i 总体上说,绝大多数对于 object 和 string 类型的序列使用 str 对象方法产生的结果是一致,但是在下面提到的两点上有较大差异:
1 r( B* l& o, s, m9 T( H- Z3 v# o4 S0 N! _
二者对于某些对象的 str 序列化方法不同。
7 o' F9 F3 k% t2 i% G可迭代(Iterable)对象包括但不限于字符串、字典、列表。对于一个可迭代对象, string 类型和 object 类型对它们的序列化方式不同,序列化后str对象返回结果也可能不同。例如:
8 N: F, N3 h9 N7 es = pd.Series([{1: 'temp_1', 2: 'temp_2'}, ['a', 'b'], 0.5, 'my_string'])
/ O- W( I4 _7 p$ s/ P2 _; es' N- K+ A7 L6 G: t( W- O2 s
17 Z5 b0 z! B( C7 T2 N
2
- E; ^ u9 }8 k k' c0 {1: 'temp_1', 2: 'temp_2'}
" j; `# ~/ }2 B. B6 C1 [a, b] m) N+ [2 f! N5 ^" ^8 M
2 0.55 G1 p) {4 p1 h% j* Y2 `
3 my_string
, @! P/ K! |6 C! v- p9 j7 hdtype: object
2 h, }7 W" a: e7 K1( ~0 e5 `5 f. C7 t- k4 X
2
: q9 H+ S" v# W1 a; R# K31 ? i4 J* ^5 C5 X5 P2 \# q0 E
4
* e: N) p* Y% J. V* H! Z4 v$ Q* o' C5
9 ?) r" x- c0 Q- u$ j$ ]/ Es.str[1] # 对每个元素取[1]的操作- t+ ]! ]$ x: l% ?8 f. `
1
- C" E9 P& T2 c# t5 ^1 |0 temp_1
+ [) h2 j: a* w5 e0 o; L. q1 b: `( \9 b! b: o" m
2 NaN; O1 G& I% i) M0 ]' I9 K7 R
3 y
2 _2 f: K {1 A5 y! ~dtype: object6 e0 I8 U) V' o9 \) ~3 a4 |# @5 j* w/ U
12 {3 q( M8 J$ B
29 k, J) J- W' v( [
3
8 t- R$ M& {% B+ N! Q F2 R0 ?, k43 D- k' A1 {" j, L$ O9 C8 \0 S: P* z
59 v6 V8 ^1 Q0 \0 s4 a; v
s.astype('string').str[1]
4 h+ p( T" s9 s" y" Z% n16 V: z) B) A8 P( P$ S
0 1) ~- Q. t' T( Y+ @4 P/ n V6 y
1 '6 {. t" Y6 X3 T* }6 ~
2 .
& }9 M/ r& r5 o8 z/ i9 h: r7 i3 y' S2 {* Y6 a: C, N0 t6 O, E" z I
dtype: string
$ b* i Q- j/ \& X+ M6 }1 m% S3 }1 e* I6 Y1 F- c' u
28 b, r5 l9 [2 U
3
3 A S! @' _. c6 f4. ^. W2 U# k: X% M
5
- W3 L8 @! k- G, b t0 `) \除了最后一个字符串元素,前三个元素返回的值都不同,其原因在于:9 b* X3 K7 T. F6 m' \% ?! q
9 H j" S) m! u. S. J; i当序列类型为 object 时,是对于每一个元素进行 [] 索引,因此对于字典而言,返回temp_1字符串,对于列表则返回第二个值,而第三个为不可迭代对象,返回缺失值,第四个是对字符串进行 [] 索引。
! }% M9 M4 d6 R; vstring 类型的 str 对象先把整个元素转为字面意义的字符串,例如对于列表而言,第一个元素即 “{”,而对于最后一个字符串元素而言,恰好转化前后的表示方法一致,因此结果和 object 类型一致。( X" A- I$ P' \
string 类型是 Nullable 类型,但 object 不是6 j; V& E0 E. Q# O6 g
这意味着 string 类型的序列,如果调用的 str 方法返回值为整数 Series 和布尔 Series 时,其分别对应的 dtype 是 Int 和 boolean 的 Nullable 类型,而 object 类型则会分别返回 int/float 和 bool/object ,不过这取决于缺失值的存在与否。
! D/ |7 r& u3 _" x 同时,字符串的比较操作,也具有相似的特性, string 返回 Nullable 类型,但 object 不会。5 O: H- g8 `' R, b
s = pd.Series(['a'])
# a2 J+ l7 }- U+ y0 E+ _7 _" W& Y8 [' J5 E2 V g4 p
s.str.len()
( {. C+ ~: A* g/ DOut[17]: ( C1 m$ @( Q. e2 y
0 14 ~ t- L L+ Y
dtype: int64. }3 I/ t$ A c5 U4 D# T- Z
# z2 `6 I5 g8 ~$ l4 [
s.astype('string').str.len()6 k, P* b* s- z9 y" n
Out[18]:
9 V' j7 A1 S& X. W' j; g0 1: }0 N4 O! X3 d/ b. @
dtype: Int64' M# Y3 e. j' m9 w+ c+ H# i
) k9 F. x, Q, r5 Z3 H0 B( ^
s == 'a'
# V4 U6 d4 \/ WOut[19]: ; E4 m6 E6 p, h; P/ m; N% w5 m
0 True
7 G1 y$ ]8 n# K( Ydtype: bool) b* z! f7 l: K: G* y; A. M& a7 n
9 _2 g5 g$ B; }s.astype('string') == 'a'
( N6 @1 c$ m: Q; g9 x1 y& l6 HOut[20]:
I4 Z B+ D5 M) l4 u4 ]0 True+ ^* F, O( X w5 F( m
dtype: boolean% j- S( X4 f" |
' b( `, r( l( @s = pd.Series(['a', np.nan]) # 带有缺失值
/ B0 c% o: a# t; K9 \" C$ v$ `$ ~& U C/ J! C7 a
s.str.len()1 ]+ C; q5 q e2 d
Out[22]:
- s p% Z; ^' e0 1.0
& ]) t8 |7 j9 y( D1 NaN
! O$ g5 D0 ]- u" b) u0 [dtype: float644 F9 @# b% T. y
0 }% _5 ]: ]7 U5 [. Ys.astype('string').str.len()
- t: r: A2 m) a8 B2 C% }9 R* D fOut[23]: ) ?/ R# t6 { w8 d8 O
0 1+ v* N8 \: y, Q8 l3 G A( s
1 <NA>: O4 l0 U. n* U; A/ x; n* f
dtype: Int64
/ ?* M& }1 @6 @% Y
( w% y7 ? u2 J) Js == 'a'0 |' h# b! c5 D2 [
Out[24]: ; A5 d/ D& O; @# q0 q; s1 X& T$ }, t
0 True
. _3 U N1 E3 t( T5 L/ x1 False
% z3 C, K0 U9 Y, f; g/ B" Idtype: bool
( X5 h& X6 m3 ~! q! w$ g/ h9 V2 L$ y/ K/ Y
s.astype('string') == 'a'
+ J5 v2 g: d, K% u5 Y) YOut[25]: % X* @0 s. X* T# g7 }+ t4 M! `0 m
0 True
- j3 a4 L+ l8 V7 x: `1 <NA>
: K! _" C( c1 ?dtype: boolean
& H; S1 S4 }2 V& ]- E8 o& E' _6 A3 l$ ~9 A8 N
1
: g% B# N3 f; R, N$ v f( v: u% Y2* } f% u, _8 N4 }
3' r- z2 V8 E+ j6 a8 j& E H: V
43 W8 H4 O+ m* s3 g9 ~
5
5 _5 `4 z) Z3 j# O. u69 g+ N3 ~6 ~' M0 V6 ]
7& P* H! n$ D G. f
8* x0 G. R, w. Q6 s7 {" \5 x
9
4 L ?, |( a: \5 [, w( K1 E10
* Q% \# e, N5 f4 r; f6 _4 t112 c, o/ b9 q- b8 [. Z; ]
12
7 r0 |! @. N/ P1 x% @" G13
- ?7 K" c, W5 o7 l! i14
! }9 e o- z, O) g! K. I15. Y5 g* B* m6 N, n, m. T* w
16
5 S8 ]- I1 Y5 W% \3 w9 Z" H' e$ S$ |9 T17/ [' Y- }1 C) G1 H6 j/ Q$ p+ N
18! a8 Z$ J8 p/ w# p; }
19
+ e: }% v+ }" k/ x/ X8 c20
$ S2 w$ `8 X- c9 t, `+ q21, A/ |1 o: d% n W; ~5 r
22
# P( m7 [: t' J0 p3 \23* l5 d \' c4 s6 e) O; e: z
24
/ Y- v( \5 k9 ], D# P6 w* p5 y" X3 a25
" I: d4 P; m6 ?( h263 _9 \% K$ y1 V- `1 c
27# c* c/ w, R0 x# E1 K
28
( u" n; T0 T' P8 \2 t4 N6 _# w29- x- N4 F# K- a
309 E, `' v/ f& L& ^
31- }2 u$ w. Y$ P+ X
32
. W! W* |) d. n) M+ {% m6 }33
0 Y0 U4 m) V9 ?3 H34
3 O$ J% x" c C35: i, E5 g. O. d/ J* t2 C8 q
36% }8 h+ l5 ~& |! U2 P
37
' K& X7 n0 N: E; I38' N& k! P, c1 p* y& w5 [
39
# |4 M9 h# E( H" a" Q40
) j$ r, T h: s/ o8 [& M41/ a, E$ @- C3 }, U* A
424 j/ `6 [8 y7 P
43
* r. C( f' l7 K- [44
2 F* L5 S1 D9 B# Z7 \( `45% D8 f7 }1 o3 O6 o! l p
46
- `5 m+ }6 w+ J/ |* b9 E47
* O' {- ] N$ C, I/ `* ^ 对于全体元素为数值类型的序列,即使其类型为 object 或者 category 也不允许直接使用 str 属性。如果需要把数字当成 string 类型处理,可以使用 astype 强制转换为 string 类型的 Series :; S2 `$ P9 i' G/ O7 o) z& M
. _8 T7 @+ b: T2 Cs = pd.Series([12, 345, 6789])4 _: Y8 U, C. \( f
! K1 L: i" F4 `" J; P4 M
s.astype('string').str[1]; n7 T- i* M* m; g
Out[27]:
7 w$ I a$ U- }8 e: w; J* t) h0 2) n! K# q1 M, N Q
1 4
1 e$ b4 @9 S0 ^' ]% _* T/ N7 C6 z2 7' \9 n1 _3 _ z% r& H( `
dtype: string7 v& N3 \& a0 ? j% y
14 ^8 v. p' P7 b: [" D0 r* G
2
" {/ @5 n( _5 X/ ?% ^0 e+ Q3
4 d. [! Y4 e+ f9 Q, h4 M0 }( F' ?4$ ]# p2 C1 i8 t, _5 }6 v; l5 j+ f
55 e, O' U: N+ u
6
1 V' K* B# N5 B2 J; |( U7' {2 w1 f! I2 E8 J
8
% x3 b9 c+ L. O8.2 正则表达式基础
- b6 d8 y9 n; J3 N/ i1 i& [* ^这一节的两个表格来自于 learn-regex-zh 这个关于正则表达式项目,其使用 MIT 开源许可协议。这里只是介绍正则表达式的基本用法,需要系统学习的读者可参考《Python3 正则表达式》,或者《 正则表达式必知必会 》这本书8 s! @4 P& f, `. f
D' i- @" }. ^; Q; @8.2.1 . 一般字符的匹配3 a$ Q, @. s6 |* n: t
正则表达式是一种按照某种正则模式,从左到右匹配字符串中内容的一种工具。对于一般的字符而言,它可以找到其所在的位置,这里为了演示便利,使用了 python 中 re 模块的 findall 函数来匹配所有出现过但不重叠的模式,第一个参数是正则表达式,第二个参数是待匹配的字符串。例如,在下面的字符串中找出 apple :
' c- [3 N# h1 h# G* h: F: _8 L% A: q, ~+ ?5 i: \
import re7 a1 V# D) {( v& f
0 B: ~% M+ u! X; V9 `re.findall(r'Apple', 'Apple! This Is an Apple!') # 字符串从左到右依次匹配- }! i2 ^. O4 E
Out[29]: ['Apple', 'Apple']8 \3 V9 Z1 e+ q9 a Y t/ s
1
# J! M2 ^( x# R21 [1 e) L1 s1 g; a
3
) R6 s# G+ T# X6 S4
8 a% x( V \! c4 `; n7 t) j8.2.2 元字符基础
; V6 W% l% i5 j4 p! m9 g元字符 描述
* ~4 m# P9 [0 ~2 m. s/ O# [: @. 匹配除换行符以外的任意字符! k9 ?6 v4 w9 v8 S, B6 w) W. x6 _+ X, v
[ ] 字符类,匹配方括号中包含的任意字符1 w: q( }- _8 I
[^ ] 否定字符类,匹配方括号中不包含的任意字符# g: M3 D5 `/ n
* 匹配前面的子表达式零次或多次2 p# r. \6 q3 b* o& B. D% B! V. X
+ 匹配前面的子表达式一次或多次。比如r’d+'就是匹配数字串,r’d’就是匹配单个数字
* g1 M: N1 j* ?! x? 匹配前面的子表达式零次或一次,非贪婪方式
3 D9 m; x8 v9 Q! w! x" E. E{n,m} 花括号,匹配 n 到 m 次由前面的正则表达式定义的片段,贪婪方式
- g4 b/ n) V3 \9 z% y3 _$ F/ g(xyz) 字符组,按照确切的顺序匹配字符xyz8 e% s; k' \. H# ^- W9 m# w
| 分支结构,匹配符号之前的字符或后面的字符: t2 l8 P I5 ~% ^
\ 转义符,它可以还原元字符原来的含义
7 B, |- ~7 g# \( w; Y. U8 s^ 匹配行的开始. d' [) S: x* M3 Z( h" \* A# q
$ 匹配行的结束
" ~9 ?* T- w: d8 L6 {import re
5 Q! Q- w+ \+ N; M, D" G6 Wre.findall(r'.', 'abc')
9 z# K+ s! x* ?& n" oOut[30]: ['a', 'b', 'c']
- B$ d# J) F- ]; A% i5 _1 j5 |- C7 d$ S
$ L \) B* s) `. Vre.findall(r'[ac]', 'abc') # []中有的子串都匹配/ {$ m' q8 d9 |5 r0 Q& l& l
Out[31]: ['a', 'c']1 P/ t- ^7 ], W. J S C
1 v+ i3 i/ T" {1 D3 g2 i+ b1 \5 @, S/ ?re.findall(r'[^ac]', 'abc') & {. ]" v) @6 c4 h
Out[32]: ['b']
% u: E; H' T6 [! D* H5 M) i! \4 ]: F2 m; O; L
re.findall(r'[ab]{2}', 'aaaabbbb') # {n}指匹配n次
0 x3 r; q5 i l5 ]0 cOut[33]: ['aa', 'aa', 'bb', 'bb']
" G! m. h; L$ I: k3 z
4 ^$ ^' K2 W8 qre.findall(r'aaa|bbc|ca', 'aacabbcbbc') # 匹配前面的或者后面的字符串
6 F) q8 e$ E/ R' l4 c) \; UOut[34]: ['ca', 'bbc', 'bbc']
0 e* {+ c- u! F; D3 S" L G# ^
7 \+ s; e0 N% n( I9 ^" Z7 F# 上面的元字符都有特殊含义,要匹配其本来的意思就得用\进行转义。
" V' l% G4 o8 J+ F# w, E( M% r5 b""" k x+ w, b+ D9 F( `: \; E
1. ?匹配的是前一个字符,即被转义的\,所以|前面的内容就是匹配a\或者a,但是结果里面没有a\,相当于只能匹配a。) F# I1 \& j# V6 A2 v
2. |右边是a\*,转义之后匹配a*,对于竖线而言左边优先级高于右边
' w) c5 f; e8 n; p4 X: Y2 Q9 e3. 然后看目标字符串aa?a*a,第一个a匹配左边,第二个a匹配左边,第三个a虽然后面有*,
* S# l# F* @4 U+ G但是左边优先级高, 还是匹配左边,剩下一个a还是左边,所以结果是四个a
7 N- Y: Y0 @! d6 M6 a' g"""& I! r8 B) Q- r/ o+ n% e# G
( g! n0 _8 S0 l3 C# r0 J
re.findall(r'a\\?|a\*', 'aa?a*a') # 第二次先匹配到a,就不会匹配a?。a*同理。8 f' q- ]4 @ O" _5 y0 @
Out[35]: ['a', 'a', 'a', 'a']
9 k8 D( r" X c- f5 S
# I5 U1 g# j% s# 这里匹配不到是因为目标串'aa\a*a'中,\a是python的转义字符(\a\b\t\n等),所以匹配不到。2 }8 d: W. X' [, \0 ~7 Q) X! L" b
# 如果是'aa\s*a'之内非python的转义字符,或者'aa\\s*a',或者r'aa\\s*a'就可以匹配到\字符。
" @& R+ ~ a0 Q$ N: r! Mre.findall(r'\\', 'aa\a*a') q) G# [8 R/ b" O7 R, i
[]
9 G) O. T$ J& S
8 ?' t& a3 U+ c+ Rre.findall(r'a?.', 'abaacadaae')
6 @8 h5 h& y/ x4 ^" mOut[36]: ['ab', 'aa', 'c', 'ad', 'aa', 'e']
$ O6 h3 g+ j4 s+ A# d% X7 _0 x n" y! ~/ P) w' \* y
re.findall(r'(\w+)=(\d+)', 'set width=20 and height=10') # 多个匹配模式,返回元组列表
7 a# ?9 ^/ A" S* K, f9 I[('width', '20'), ('height', '10')]# X# N1 W; z# D& f; h
! z. Y P, h2 ]% | J4 z1
# f. A! [8 k0 G7 n2
8 c7 x1 r2 I( K/ i' t36 R1 v1 R- k/ [3 M" m
4
' @( i& s! p6 a9 @" @+ P5 F9 L' r. j' W! f
69 m, j3 U V- ]7 u
7
+ g9 n, `4 I, b0 D" p' {; L* Q: N. @8
1 M# x0 k6 x2 \& ?9
Z1 e/ F% |, C3 N10' G! m, H8 }2 R2 ?, q9 @
11
; ~. R ]) t$ Q8 v- t12
: k9 R% C6 a8 U5 Q% r' y13
) x( G( L* k5 Z3 }+ `14! W. ~- i; I3 _4 U* J: h
15
- U% p- R! w5 m4 g1 Y8 L7 Q% \5 v16
# _8 y/ K5 l( `: f9 S171 O1 ~' C/ p. d. E
18
s# K5 [' Q3 `# n195 _0 X% O) v7 X* C ?6 e
20
/ i0 `3 w! x7 O l* u21# y7 B) z* R: n$ w$ i
22" [; L" g1 a7 m7 D. O9 L3 V
23+ r; c+ I: N1 Q2 T. l1 q2 a P
246 `2 A5 E( H2 K: u2 u' }# P" h5 C
25
* H- R; n3 u5 Q3 N0 k6 w26/ B u7 g$ V, |4 M- n! Q
27+ S0 |; a1 e& I% ^5 N
28
e5 ~1 P2 V8 a6 @" U299 f4 }4 R7 ^6 F/ R" Q1 A
30
( N2 I1 b+ b& [; S& ^31
- x, Q$ O7 V7 K6 F! r% s* |32, r! v" c: \0 U( ^. o- w% q7 t
33
' H5 k! P9 K+ i s6 U/ I7 I34/ H: C; B& r/ m2 D& C
35
+ ?3 g5 E3 Y/ i1 J) E360 ~0 s5 z2 D# k3 z0 E
37+ k' t4 m& j3 `. C1 ]7 P
8.2.3 简写字符集
& m: m; ~( J; |! Q! ~) g则表达式中还有一类简写字符集,其等价于一组字符的集合:& r6 R9 V4 n- v) t" H
" w* B/ D+ G0 R: u
简写 描述# [$ D! r" x( D+ _- x5 r3 L
\w 匹配所有字母、数字、下划线: [a-zA-Z0-9_]
/ R2 q8 |. n% v) p, I3 `0 m9 n\W 匹配非字母和数字的字符: [^\w]3 g1 x" h) I" n1 i
\d 匹配数字: [0-9]
; |' ^0 l- U |" R, E\D 匹配非数字: [^\d]: R3 e. C4 H! P* K# J' u
\s 匹配空格符: [\t\n\f\r\p{Z}]
, Q3 ], G+ Z4 \1 I, M+ X C\S 匹配非空格符: [^\s]5 J' w+ S6 b) z. F* i
\B 匹配非单词边界。‘er\B’ 能匹配 “verb” 中的 ‘er’,但不能匹配 “never” 中的 ‘er’。
* e) N4 F6 _, y# p" b$ ?re.findall(r'.s', 'Apple! This Is an Apple!')# v3 [; M4 V9 H4 n! g
Out[37]: ['is', 'Is']
2 k9 L( r! q6 D) F1 N( t2 Z0 x2 Z1 Z T
re.findall(r'\w{2}', '09 8? 7w c_ 9q p@') # 匹配任意数字字母下划线的组合,但必须是两次' x9 u$ h) p! H9 X5 Z
Out[38]: ['09', '7w', 'c_', '9q']* R! S @3 n1 O/ r6 F
" J$ t' v x, ? k9 ?, \
re.findall(r'\w\W\B', '09 8? 7w c_ 9q p@') # 匹配的是两个字符串,前一个是任意数字字母下划线(\W),后一个不是(\W)
8 Q$ y- A: O" Q" N0 TOut[39]: ['8?', 'p@']7 y; B6 k, X ^, I; t* l) k! \
% N& h* \7 R" }5 Kre.findall(r'.\s.', 'Constant dropping wears the stone.')
: [0 F- @; X( ?9 X0 [, QOut[40]: ['t d', 'g w', 's t', 'e s']6 }7 X! t2 m) F# y4 v( O, G
9 K& a+ K. h2 O b8 b
re.findall(r'上海市(.{2,3}区)(.{2,3}路)(\d+号)',
" J) B) U( [# D '上海市黄浦区方浜中路249号 上海市宝山区密山路5号')5 v1 v/ V0 L# G; U; k
+ G- H8 g% e9 i- aOut[41]: [('黄浦区', '方浜中路', '249号'), ('宝山区', '密山路', '5号')]
: x: X, X, }: s8 D; p8 B
, H3 J/ N6 a& ^. M17 t- w2 J: a6 m- |0 |
2- b4 }8 c9 d& R Y8 z
3% P( M" ?/ Y9 X$ _ D0 b; k/ d
4
8 P7 J) \2 l9 K9 N5' o% A& \& c, S {7 |
6
# s+ I1 l0 u. q' \6 K+ }7
, v- O) m" r4 Z) u8
: R/ Y. B0 ?0 h9 p+ g4 G- j* x9
* v" w# g. B$ A0 _* A& U10
/ Z; @8 c% x/ R$ w. r; s. [11
7 }7 Y3 k S# |; U8 {9 r% G12" x' g' I& s0 T. [' w5 W
13
+ q+ c( z; l" `6 S14
4 q9 g N- C% B% F: ^15
2 n8 y+ N- Z3 f7 i0 x! E3 b$ O16
. n0 S9 u6 h" c" k+ C8.3 文本处理的五类操作. L) |3 V ~# \' Q9 R
8.3.1 str.split 拆分
' L2 k" A' L" @2 ~: I str.split 能够把字符串的列进行拆分,其中第一个参数为正则表达式,可选参数包括从左到右的最大拆分次数 n ,是否展开为多个列 expand 。% V, V* T, [. F7 P7 ^& }
& W6 B* @9 T* R' g2 w, p5 q5 h
s = pd.Series(['上海市黄浦区方浜中路249号',) r1 m$ q, e$ ~
'上海市宝山区密山路5号']): w$ o1 I& A& C. H& X
+ d* q* Z" ~3 A( R( M4 B+ q \6 E1 h! {# ]3 f
s.str.split('[市区路]') # 每条结果为一行,相当于Series
' g- i- V5 A$ `( L$ W$ Q; yOut[43]:
# E, d) C: g+ y0 [上海, 黄浦, 方浜中, 249号]
. L( X- V7 g. i! b4 u/ |1 [上海, 宝山, 密山, 5号]
# x8 i- |5 K! t) N" a) R: qdtype: object
) r7 C) ]& Z; ]3 V
& f7 u* _. n0 q* u, r& v0 ]2 js.str.split('[市区路]', n=2, expand=True) # 结果分成多个列展示,结果相当于DataFrame
2 D: _; w& ~; _% KOut[44]: : w0 u9 b1 v& e& m+ N
0 1 2, c6 T$ O( \5 }7 j1 h7 n. w
0 上海 黄浦 方浜中路249号* `2 } C1 u1 K% n
1 上海 宝山 密山路5号
% w" E8 [# t! J3 _+ g- a/ T1 t9 o1
8 v' N" }. s/ E- V) N7 z' T2% w7 v+ W7 h) ?
3, ?% y: R4 c$ c" ]. w* q
4
3 t9 C( A5 k( t Q; M F2 L5, o* i- C! @& d4 Z- Z
67 u6 v8 H O- A z
7
- c. S* S! Y( H0 x9 F- d5 o$ u8
" g! E' o; d- n3 S, W# E1 b9
4 N% U8 V; A& U+ I, O10
; q8 Q3 z W, u% t: s8 p2 x. J11
' j! f) U0 c1 i; T% B12
2 h, d: A- _1 t; |13" ] a4 |, ^' j5 A# p
148 k: S, h5 e9 J1 T$ W) Z
15' U; A$ M' p4 `2 g
类似的函数是 str.rsplit ,其区别在于使用 n 参数的时候是从右到左限制最大拆分次数。但是当前版本下 rsplit 因为 bug 而无法使用正则表达式进行分割:
/ E% L9 w, e: u6 T% K( I1 q/ S5 }, ]6 O5 N6 r2 ~4 v7 z5 k
s.str.rsplit('[市区路]', n=2, expand=True)/ y0 [: M9 L7 |. {2 c) p: u! I
Out[45]:
2 l0 ?' L+ z' V) r& } 0
8 h# @; D& b9 D. H5 H1 ]0 上海市黄浦区方浜中路249号8 F/ U' A. T. {* j
1 上海市宝山区密山路5号, H0 d2 _# z; B8 Y
1
f1 [5 G5 a" @24 u9 B% h. {1 h0 {
3
2 x. @ L% i/ F. B- i4
! m* G, N! q2 z( p+ I50 i2 j& w6 h8 d* l4 x) H
8.3.2 str.join 或 str.cat 合并
r5 a7 B0 p% R9 x6 ~str.join 表示用某个连接符把 Series 中的字符串列表连接起来,如果列表中出现了非字符串元素则返回缺失值。
. C/ m7 w8 \$ O; n4 P3 X6 U5 }str.cat 用于合并两个序列,主要参数为:5 F" n& X" r, Z0 v# k/ g8 w4 p g
sep:连接符、
7 x, D" u. t& }3 ?1 f' {) M0 [join:连接形式默认为以索引为键的左连接: M& b3 Q& J/ Z+ w( w6 e: |
na_rep:缺失值替代符号6 J3 i+ Z3 H! @4 H) d5 ~
s = pd.Series([['a','b'], [1, 'a'], [['a', 'b'], 'c']]): W- [5 a$ b$ X
s.str.join('-')* }, e; F w) A: _8 V# w, q" p0 m4 o( f
Out[47]:
& Z6 J7 Y$ E" ]+ U) p0 a-b: D9 h2 w+ `; U. x8 m
1 NaN( l; B/ Q: p+ N
2 NaN
~( g+ t6 g0 q# Y4 V# d% F$ G; m \dtype: object2 X- s. X9 C4 C/ M
14 S, n7 c: T0 A; F
2" y0 u) Q m4 J
31 W" S5 b$ ?! h
42 H& c+ _3 t' s- i: d( W
5
J: ]5 W) C+ C5 V; n3 V$ ]6
& u- m# }3 w9 U$ }; o79 B8 y+ B, q1 o4 Z
s1 = pd.Series(['a','b'])* g# }8 d% a0 F/ ]8 m# ^
s2 = pd.Series(['cat','dog'])! `* j) ^( k! z
s1.str.cat(s2,sep='-')
: Z8 A9 O4 Y: o% ], Q# z+ bOut[50]: * l% h1 l) p( c
0 a-cat% P& \: U5 Q; G6 K% o# j
1 b-dog
% F4 q+ }# |- d7 ydtype: object' Z/ b, I4 P( W9 \
$ W8 Q" w' M' {
s2.index = [1, 2] n3 V/ M8 s: j0 X+ E
s1.str.cat(s2, sep='-', na_rep='?', join='outer')
. l) h. V' W! pOut[52]:
( I% r& w0 S" u( A0 a-?
( n2 D( ?' R5 D" G6 X1 b-cat
+ M0 ?) I3 ]2 T2 ?-dog0 N# X; P) ^6 I0 O. n- g
dtype: object, W/ g9 d4 k, I3 z
1
' N [6 ?' f0 e: @2: q+ g5 x8 U) [( M. D
3
# Q% i. z1 ? o# k/ x( M+ A4 h! C7 N4) W8 T/ X- j$ Z: J
54 S% f( R/ Z/ Z1 n. {. B1 z
67 \& V+ q$ p1 Y$ u& h, Z" u4 Q2 }/ M
7 {& a( u9 q& p% S7 P
8
4 B) Y5 c6 ~ ^0 W- V1 Y4 R9
) }& v) L, o: |; d10
2 J1 h( y b! R. N# {# a1 L11 D: f% ~( G1 f
12
D& h1 f: \# J5 x* \( N0 ^2 o( ]13
% l2 z% u: E) G/ \ ]2 n! }1 M140 U* V; n/ S: @( j' w
15
# C8 y( u7 K/ G2 N& w8.3.3 匹配
; f9 g) }; R3 Z8 H, dstr.contains返回了每个字符串是否包含正则模式的布尔序列:
4 L; Q- {: B/ z* V9 I) l4 `* C$ Ls = pd.Series(['my cat', 'he is fat', 'railway station'])) t& T$ X. z" ~! l0 f: @0 h8 R3 {
s.str.contains('\s\wat'); J! J4 R: }* M \& Z
- i6 I! m' r& Q4 J+ c) M6 g& c+ C6 x0 True
+ R6 |) W* ~& [3 w' E! Q0 f6 g1 True
2 u2 P8 C7 G; |" D, @2 False) c: E* X8 @, J* |- @! y
dtype: bool0 e0 |4 r9 z6 _: T
1
& I+ _/ @( H7 e1 B5 t! z) q2' k, i) r6 x. i6 W7 D
3
5 \$ h; V, g0 x* Y, P* }6 s4
, y& c7 ]- Y0 {/ P5
) X! e- I+ Y7 N" m8 ^6
. R; \7 _ L3 r. y0 T3 \" N9 d- x4 K7& s) r7 l3 Z x0 a
str.startswith和str.endswith返回了每个字符串以给定模式为开始和结束的布尔序列,它们都不支持正则表达式:! O5 l9 R$ R# t4 W" B7 l* ?' N# _
s.str.startswith('my')
! U: A6 c. Q. w8 i. @3 w. Z0 g3 H9 j1 o% p( h
0 True
, U1 c& x q( c/ {2 u7 B1 False
7 R6 h2 V$ B& X5 `: D2 False
) b9 J) @0 f& odtype: bool1 E1 ^, t9 Z$ ^% g) h& i$ r
1 r9 h: W% Y1 F) q6 x x w
2 w' c8 H- ^- y$ K
3
/ V% g. M$ X: s4' ~( C8 s( m0 W
5
: a6 G- O6 n) m" ^1 Y6* o; I) W8 Z! h9 C. J- U# ^$ F
s.str.endswith('t')0 j- O3 [& V* t7 `/ v' Y. K
+ x# ]7 m) H3 e1 {. q
0 True, Q# ~+ {* x; `- Y7 E
1 True
% ~% ^3 P4 k) R, ?& ?7 u2 False2 \9 s8 D& S K2 O4 Z
dtype: bool! C/ I4 e* V! r( m7 z
1
2 I3 e; g$ d: h$ l* k8 j2
x* y3 r4 W/ @0 h. v- E3
0 G& b9 e$ ^ F X4
( O K6 C$ C4 W3 n8 Q$ R/ G* `8 C5
, X0 P9 l) s3 r+ q6- ]% v4 r, } O
str.match可以用正则表达式来检测开始或结束字符串的模式,其返回了每个字符串起始处是否符合给定正则模式的布尔序列。当然,这些也能通过在str.contains的正则中使用^和$来实现。(貌似没有python里的search方法)" _) K M. e. X7 |
s.str.match('m|h')! w* m6 Q/ t) x9 V( t: A! q' ?
s.str.contains('^[m|h]') # 二者等价
/ m+ Q$ A9 u. ]- X4 L/ b
, I& m6 @* q" L0 True
& C Y% l+ j3 @1 True
9 e$ F/ }2 a7 u/ y/ O0 E2 False
5 ]' c4 n" R' }dtype: bool$ @* s, c0 R, {) u
1
" t! w J- E$ J0 ^1 R- n" {2" P1 {/ r0 E) r9 W# [: c, O9 n
3
8 Q$ S8 M# |) ]( ~40 [4 [# Y$ X. P% P2 ~
5
; J( p6 ?; x! z- [" p' i6
' A, q4 ]8 s: Y8 Q% J: P+ N7; Y$ A4 N! X) b+ |0 e, c
s.str[::-1].str.match('ta[f|g]|n') # 反转后匹配
2 D( R4 t+ J. S3 L$ gs.str.contains('[f|g]at|n$') # 二者等价+ e' P9 w! l6 n4 B' O( J5 z
8 X0 W0 B4 ~% j% T
0 False
1 m5 ~& u i7 e0 t9 a1 True u4 P; x# `* d1 G0 b! }
2 True8 W2 L! M# N* Y
dtype: bool
; A2 B1 @6 w% X7 I- C1
1 X- q: c" @# r8 E7 t( N24 n( K6 |8 X9 u. ~0 f4 K( I8 q
3
8 `9 C: |) x x+ H G5 o4
[# X; n- j1 n3 |2 v- B g5
6 q& @, g5 f: U. W, c6
; l1 M% U9 J) a! m7
5 Q7 r$ ]+ R6 m! T' N1 ?str.find与str.rfind返回索引的匹配函数,其分别返回从左到右和从右到左第一次匹配的位置的索引,未找到则返回-1。需要注意的是这两个函数不支持正则匹配,只能用于字符子串的匹配:- O7 I/ n2 K. }# D
s = pd.Series(['This is an apple. That is not an apple.'])
( m# B* h. r1 h$ O0 y+ x! @$ u! g7 V2 @6 r
s.str.find('apple')
" f/ m, p5 c4 \5 f, v" H9 W- TOut[62]: $ ]4 Q z7 N# L4 r0 X
0 11
u: R' ?7 ?6 J" P) ?1 N* T& J& b1 O4 [dtype: int64
6 B9 m: y4 u' m8 [7 c
3 z! h! o) _# es.str.rfind('apple')
# p4 F4 f7 ?- h# A4 |$ ^ AOut[63]: ; ]4 F* r5 l+ E4 z. B
0 33
' A& b' D* x& o' @# vdtype: int64
0 ^( v/ u) ^2 O/ W# E1
5 x5 Y) J7 l+ Q* \; k$ ]$ V2
% E0 K" z% B& o- U3 r" M4 f: B3
# s* Z% Q, o# X46 R1 A* H8 g: n) ?5 B
5 F# w; o L: s( u$ H1 v
68 Y. T& ~% B) M/ _) x: m3 G# C
79 K h0 C" i) N: K. D: X
81 a( y& T3 R; R: d/ Y) b+ d
9
8 w" r9 s6 b! b10
' y5 w. U' _" O11: u L$ ?6 }9 I# t5 o
替换1 v) x6 A% c. d) [6 Q3 o
str.replace和replace并不是一个函数,在使用字符串替换时应当使用前者。6 U) V: L* ~; _ U' `& @
s = pd.Series(['a_1_b','c_?']). p. {4 N; H. F7 ~7 I* o
# regex默认为True,表示是正则模式,否则第一个参数内容表示是单纯的字符串,也就是匹配字符串\d|\?- w4 P, C& \ I) L. K. i
s.str.replace('\d|\?', 'new', regex=True)
% G0 @4 U% v; b5 P; a. H2 G$ L; [3 x
0 a_new_b8 [( Q8 q/ T4 _' G& d/ a
1 c_new
+ w. e& w) [1 \) s+ xdtype: object
! v5 J, k" } w/ n1
" z9 M1 M6 V( O! v28 F8 _" P$ b9 \
3
3 o- }9 H3 E5 Z+ x5 E* X$ Q- d4, b, S1 B: t* L
5! M9 K7 b8 J% F# C' w% j3 W( o
6
' C# w* q1 x. e78 p8 G9 J& a$ Q: w" p
当需要对不同部分进行有差别的替换时,可以利用子组的方法,并且此时可以通过传入自定义的替换函数来分别进行处理,注意group(k)代表匹配到的第k个子组(圆括号之间的内容):
% S8 Z. _) y. T1 _5 a j: j: e$ w- ` ?' G
s = pd.Series(['上海市黄浦区方浜中路249号',
; D7 a' ^4 ^% [3 V" c" L, t '上海市宝山区密山路5号',: n+ U+ D( E) [* k1 c
'北京市昌平区北农路2号'])! g; ^6 E; d$ b8 F6 H3 ~- K
pat = '(\w+市)(\w+区)(\w+路)(\d+号)'
* G+ q# @8 u+ L# I3 j1 g+ B- n+ U. _city = {'上海市': 'Shanghai', '北京市': 'Beijing'}0 @, V$ G+ e( _7 q
district = {'昌平区': 'CP District',1 Z) `$ ?! u2 k+ O# @5 L# _, ?
'黄浦区': 'HP District',/ v( Y/ r- y7 `5 P. }
'宝山区': 'BS District'}% A! U" a" V/ c4 Q* @! q
road = {'方浜中路': 'Mid Fangbin Road',$ v- a! B, q' Z$ V% K/ Y% ~& [
'密山路': 'Mishan Road',
' j( `. p( N; c I5 r' Y4 T A '北农路': 'Beinong Road'}
2 n) O% i/ a' |2 _def my_func(m):
- M( A4 I( l8 L6 l1 Y* r5 P str_city = city[m.group(1)]
3 o u( |* m$ w6 G/ s* ]6 Y str_district = district[m.group(2)]
" T& ?9 P* G' [ str_road = road[m.group(3)]5 l5 s7 ^, @; T- ~: U4 q
str_no = 'No. ' + m.group(4)[:-1]
% B7 _+ E; g/ a6 X return ' '.join([str_city,
' M1 L, {. F2 _1 h# L* |5 r str_district,
$ D! D& m7 } K/ M9 i# C str_road,+ q0 J$ [/ i. x/ t0 \; o/ O7 Z7 Z* r
str_no])
7 e7 i) _" `+ s4 _s.str.replace(pat, my_func, regex=True)
$ G5 [( S0 ]) e7 u3 s8 x) t5 x4 B L+ ~" E% m; j% a0 f, r" }2 N
18 f4 _6 x4 [0 Z# [
2
# r& J) \' W; K$ [0 p2 r, I5 H& G34 {6 F" q& f( o+ ]( J- m
4# n: b8 h$ c p$ N4 I/ U
5% u% f0 D4 L! @' a9 {; s6 y* \
6
) [0 j' u8 D; W. \7 e- b7" b+ S. ]) O# W
8
6 {% s2 Y* E& o4 S; G$ ^9
/ Y. D6 f, W; ]. t2 `0 J6 r, y105 n, M- @8 b9 |, o: P
11
9 h9 G3 V! ~+ P& g) E6 F12
" P p/ a5 f& T: z, x3 C; g' c13& e2 j4 f0 _ x* h6 [! W+ h' ~4 n
14
, g T3 @4 |- V6 j$ ^/ {15 x5 U9 l2 Z, ]6 W
16, g$ U t( g! \) z2 U6 @3 G2 _
17
& ^* j$ g u3 q( d! R180 m. T1 |& n2 l
197 Z9 |) u0 R+ F: z
20
$ H6 l. [& C; ]3 P21
: n& E* Z( c, f4 j& `3 i; P# y# w0 Shanghai HP District Mid Fangbin Road No. 249; ^+ E! ]' P0 |% _, ~$ S& `9 ?, ?9 N. `
1 Shanghai BS District Mishan Road No. 5
8 d5 x6 c; E& C! C. J4 J) W+ _- c- ^2 Beijing CP District Beinong Road No. 2: c. E+ s$ e D6 Q7 O0 Y2 a
dtype: object' J4 ~' a5 _/ a& r; {1 c+ O$ H
1. b) H& |( a% v: D4 F
28 `3 h" d) u, z! i
3- q6 h6 W4 j: S6 V
46 @! C' r9 j8 t! |
这里的数字标识并不直观,可以使用命名子组更加清晰地写出子组代表的含义:# D& V& j. A! v0 Q8 u) Z
; h$ x9 p7 R/ `. g; I# 将各个子组进行命名1 E U( k- Z) Q7 R7 O4 y
pat = '(?P<市名>\w+市)(?P<区名>\w+区)(?P<路名>\w+路)(?P<编号>\d+号)'
3 @) n8 y L/ R- `% h5 N( Ldef my_func(m):
: H& A% ]$ g4 o9 g+ n str_city = city[m.group('市名')], \* N( F3 ]' N+ h( W( s
str_district = district[m.group('区名')]
8 o2 W" e! P m- \* m; Q str_road = road[m.group('路名')]
8 s4 z) t, m3 I str_no = 'No. ' + m.group('编号')[:-1]# G+ _* x* C: T5 F
return ' '.join([str_city,8 k$ h4 ?' S! u Y) H
str_district,
+ h8 E2 V# o R str_road,6 M: F5 o9 S) [$ }# \
str_no])
4 L/ Q. U& g8 v8 y0 fs.str.replace(pat, my_func, regex=True)/ {0 D9 @, P5 f1 N
16 n6 p5 {8 O" q& e6 ?3 i
2
$ W- q9 z0 ~: t. b2 \3 p3+ B; @$ o( r* f+ ^. e) _" `; S
4& X& r2 m9 t# V% g2 b
5
2 }+ q9 p# E7 a" o4 V5 L6% g+ U0 s1 I3 {4 h
7
8 {% v) K/ I# Q1 K4 O5 }, G/ T8
9 r- f" ~; m, |9
% G4 d- f# i6 ^: ?! ^10
2 x9 H4 g# s8 V5 H& A/ K( X8 d. K11
) \8 e2 }8 p( O( R& p2 k8 g12
- F7 c" @/ I% i9 P0 b1 w" b0 Shanghai HP District Mid Fangbin Road No. 249
; [0 M4 {8 @8 s4 S0 C1 Shanghai BS District Mishan Road No. 5! z/ R, x. z9 s/ U6 |
2 Beijing CP District Beinong Road No. 2$ p. V$ Y: b# i9 n3 |& f: Y
dtype: object
! v3 t. X: I5 [: `+ C1" x4 p" C( x5 H- a
2
6 t7 ]4 j' b4 _" V3
A6 t4 A4 C% u! A% ^! P3 r! I H4
- h& s# I; c' P) V" W 这里虽然看起来有些繁杂,但是实际数据处理中对应的替换,一般都会通过代码来获取数据从而构造字典映射,在具体写法上会简洁的多。
, v% |4 T$ W: ]4 I! O' X; o2 u1 o% S+ J1 X
8.3.5 提取6 o0 q; O$ z9 L7 M. w
str.extract进行提取:提取既可以认为是一种返回具体元素值(而不是布尔值或元素对应的索引位置)的匹配操作,也可以认为是一种特殊的拆分操作。前面提到的str.split例子中会把分隔符去除,这并不是用户想要的效果,这时候就可以用str.extract进行提取:
+ U) j& T6 q. P+ j5 w% S$ Ws.str.split('[市区路]')
+ T5 n4 o9 B0 h! ~Out[43]: 7 a- D# L# }' ^) t. F
0 [上海, 黄浦, 方浜中, 249号]
. r" \( Z) R h! L5 }% g1 [上海, 宝山, 密山, 5号]! P- l R( V! | h8 p; r
dtype: object, m) z& b" ^, n, f- o
8 a& }- }% J0 {$ I6 h6 N) {pat = '(\w+市)(\w+区)(\w+路)(\d+号)'
) i, r# j, P; ^4 ys.str.extract(pat)
! h( {7 k9 B' Z7 A" bOut[78]:) r6 _* D! | M8 _4 Q2 b; u" y6 ?
0 1 2 3; N W- z; P$ _$ U' r
0 上海市 黄浦区 方浜中路 249号: r, @2 G; @8 A* d5 D2 d
1 上海市 宝山区 密山路 5号* c6 M0 ?" U7 X, H* x
2 北京市 昌平区 北农路 2号7 L- ^' q* \0 b. @
1
8 z5 G# ?6 c% y7 D" [' z2
% R g; F& t# \5 b+ k! [3
( P& r5 l- `; i2 v8 Z4
. y% \# b6 H& O: J52 z+ w9 U) @9 `; a( L+ B3 w
6
3 y. h+ z, Y) `! P( x- W7
6 ~. t$ P. e# O5 z/ F; d/ a6 U8
, r3 r4 S0 z5 w: B: |8 }* X9
" E. i) r/ g9 J8 m0 F7 x( R10
; ^3 A0 U. H' a& m11
h: A6 [1 t. ?: x8 ^9 u12
I1 P& G+ o `; [6 O q13% ?2 P. V- d) c7 f- u9 x
通过子组的命名,可以直接对新生成DataFrame的列命名:
0 d# t; W8 s% b
$ j7 v; d$ `4 c- W F( ~pat = '(?P<市名>\w+市)(?P<区名>\w+区)(?P<路名>\w+路)(?P<编号>\d+号)'
5 E" u( }* v, o! Ws.str.extract(pat)
' M& U. I* ~2 m2 E QOut[79]:
) u& [* a7 z+ H+ ?* Y: u# `8 t: A+ I 市名 区名 路名 编号. f7 E% c% C/ b. ]8 G4 k8 L
0 上海市 黄浦区 方浜中路 249号
4 x$ I$ q- ~4 n2 Z( T6 Q1 上海市 宝山区 密山路 5号# A/ j& ~3 D. b9 @
2 北京市 昌平区 北农路 2号* d: {* p& E9 N3 V+ @
1
! o+ }8 i1 G, I0 D2
7 N# j' _$ d) l+ x3
( d" X Z6 T2 P2 r46 b$ P3 x/ H- F- w) Y7 {+ Q" P
57 B" E( ?, Q4 q$ p
66 g3 x e) b9 P0 R" P7 d
79 E5 y1 Y) j/ G
str.extractall:不同于str.extract只匹配一次,它会把所有符合条件的模式全部匹配出来,如果存在多个结果,则以多级索引的方式存储:. e4 Z8 v$ |6 m# A: w5 G# ^4 Y; I* D
s = pd.Series(['A135T15,A26S5','B674S2,B25T6'], index = ['my_A','my_B']); W$ Z6 }& V- r5 M
pat = '[A|B](\d+)[T|S](\d+)'- f) L& c, w4 b) t( ?0 {7 ~0 P
s.str.extractall(pat)
& l' z* p9 Z2 Q ]Out[83]:
6 Z" T& g. V* H 0 14 l3 t' K! @! i" H) p6 J: c
match & q5 U8 _. m. x$ _& h
my_A 0 135 15! W( T+ b# K8 T: n' |9 a
1 26 5
. G5 e# d: f" T/ jmy_B 0 674 2
) L( g$ @8 v" M+ C5 j6 G* U 1 25 6' g* r: A" g6 b( x, O2 k
16 O: O4 f/ V& ]: d i9 C0 J
24 j, z6 ?/ B. U0 J% o
3+ |! x! u, R( V! M+ g8 s
4' W" r# a8 B# E0 V
5: K6 X+ F9 x- N: d6 ?5 @
6: H: N& J4 a% I# N) A$ L2 s
7
5 J5 ^1 j$ Q1 l2 Q, Q+ [+ f3 ]8$ s0 c4 |1 K7 {+ G. s
95 _& U- c0 b) B3 T0 O: e1 z. d
10. e0 c# U: g) o5 V
pat_with_name = '[A|B](?P<name1>\d+)[T|S](?P<name2>\d+)'8 w* s" M/ y' `4 e) Z
s.str.extractall(pat_with_name)
- m8 @8 Z. W: X8 h1 f+ N3 ^Out[84]: # V) O& @5 o! c/ ^$ l* N. [
name1 name2
; M; ]* f, y6 t: B9 E$ T match ; _. G$ w# \! F; d
my_A 0 135 15+ s/ _9 F% M. n7 u" S
1 26 5
2 r! z2 z; d2 r( {0 v/ c( smy_B 0 674 2
. B+ X7 W3 O$ ~$ B' R 1 25 6
$ I0 I3 @5 S# C/ J2 w7 F1# U* |" v0 P X; z, E+ K
2
4 `1 N o. W& Z3" Z. m, a; ]# U
4
/ F2 s0 y1 {3 S k4 e$ U51 ?/ Q' ?; p" g6 d; g
6
- G* X; z V# a+ b* Q2 D+ D7
- c/ x3 p7 \7 H- I1 m1 |9 V( k2 R( j" ^8
/ e6 @3 p! s: h+ `# ~% W3 M9
- A# K h$ W4 r) \7 {* b9 hstr.findall:功能类似于str.extractall,区别在于前者把结果存入列表中,而后者处理为多级索引,每个行只对应一组匹配,而不是把所有匹配组合构成列表。
+ ^! S. a1 [ @7 n" Vs.str.findall(pat)
+ U4 K0 m/ V. J- I1
, u- n a) q4 A( r3 s2 f+ imy_A [(135, 15), (26, 5)]* V& D' G7 h6 P! r& @
my_B [(674, 2), (25, 6)]
W: A$ z* z) f1 P' gdtype: object7 W7 Q( \* q3 K- v4 X
1
! f; q6 W, Z$ K: s8 K( O: @2 x2 m" }( u6 ]
34 t$ |) E+ C7 m: |. C c5 `( h
8.4、常用字符串函数/ b3 m0 X% a+ l1 l9 ~
除了上述介绍的五类字符串操作有关的函数之外,str对象上还定义了一些实用的其他方法,在此进行介绍。
' f7 W; x2 r& |
4 w1 B W1 g X! Z) r7 C+ [8.4.1 字母型函数- m3 D- @6 g! z, S; q$ U, g! D; @& J
upper, lower, title, capitalize, swapcase这五个函数主要用于字母的大小写转化,从下面的例子中就容易领会其功能:
% F/ m6 E: h% H9 m9 j$ X5 a, `+ v2 v% n5 I
s = pd.Series(['lower', 'CAPITALS', 'this is a sentence', 'SwApCaSe'])
6 a& | P5 g4 Z! v) q. I
8 X8 \/ ]; K" ^- [7 Y! {) N1 Bs.str.upper() I% J4 m2 r0 q- S l S
Out[87]: / l# x6 o! k8 p0 _* [- Z5 |# k! l
0 LOWER
9 ?6 l) A; J: d( [! |+ J: H1 CAPITALS9 ~7 Z( f9 R5 j! g& h) J% p
2 THIS IS A SENTENCE
% } M2 a! ` w, C: E" v" K5 A3 SWAPCASE" T2 {1 e4 ]8 c
dtype: object# b% ]. u8 y! l8 ~. I
2 B1 n5 F6 ^/ [0 q& d' e3 Y$ [; ks.str.lower()
; |& Z, m! }7 IOut[88]:
6 a" ~# n* H9 d: b& n0 lower
3 a" T+ G- F3 U N$ x x1 capitals
4 X, |9 N3 e1 i. R2 this is a sentence
5 R) p% a6 |( {! U+ X3 swapcase
, M7 x6 @9 }% G, ?dtype: object- Z8 ?' `2 r/ j O. C( h
* i) N! O; L4 q( h# h+ u# @8 z+ Qs.str.title() # 首字母大写
% L2 N U0 b6 i! hOut[89]: * C+ n$ L* ~3 m* r4 K& w
0 Lower+ d) q) C; K; q( P
1 Capitals
3 j. ?" \7 e* K5 _2 This Is A Sentence x. F: N; O. o9 F
3 Swapcase7 e4 Y+ X/ ^3 L1 z8 k
dtype: object
. ?( @ t) b; s" X! ]. ?/ ~# e4 g. R) D: X& r
s.str.capitalize() # 句首大写$ q; }$ J! _3 x. B4 V4 H- ^9 y
Out[90]:
( C% ], L5 W$ {# i0 Lower: ?2 c- L' S9 H. X M4 ~
1 Capitals9 q% Y. U2 M/ [5 x
2 This is a sentence
: {4 l' L1 ]7 x* @: {3 Swapcase
$ Z$ \4 L* {* t; n S$ c7 Ldtype: object
& k6 Q. ~$ k# f
. o& B; K! B5 _; H+ Rs.str.swapcase() # 将大写转换为小写,将小写转换为大写。
: t0 m( M: n8 e# M- BOut[91]: ( \" [9 z0 I F" t# n
0 LOWER; G. } M: [- {, A& t) v6 x) i7 B
1 capitals3 s( ?2 d3 J& g
2 THIS IS A SENTENCE
" J% I+ a% u$ z9 P/ G3 sWaPcAsE
) J% M& B9 Q3 o# S7 i. ]' a, V' p+ `2 d6 {dtype: object j& A5 {+ [; p' T# p
1 ^* Y! y1 l0 O2 ~$ }: D) Qs.str.casefold() # 去除字符串中所有大小写区别
; x0 W+ c$ h* Q9 B( ^, k( u. _; z' E) c
0 lower) c; ]0 j! m% ]1 P$ V
1 capitals s+ p2 Q$ {, p! f/ t. O! X$ t3 z
2 this is a sentence* F9 K( L" t, O$ s. @0 E. K' ^( I0 H
3 swapcase
( l. C& c* s! h1 ]7 k8 w; {
( v/ C) \7 r$ j0 K; M9 k1
/ H& o6 H/ ^2 C# L6 p2
, f) }& _) i" `" W" s" c$ Z" F3
" }) D/ G) u3 X! b4
, U( S" S/ a" M8 ~7 ~, f4 A5, x' r3 B0 d1 A4 [' p
6
7 O9 P1 c( ?6 o- I& B72 T9 V: C; {3 J1 N$ c, |; `9 y
82 r5 I% f" J6 k$ `) C7 m
96 Z& }- H) ]! U" o' [1 F$ l
10
. A, K, [0 J- ^* i& {1 a( V11
1 w. X1 K% D0 J, n! b0 P" N4 f12
, ?; ]3 w7 {; R" `7 x3 c13
+ b; e/ e2 z" i5 O# C+ c7 T/ C14
, b7 M- ?! \+ f, g' X! b' r, o157 x% P2 Z9 x5 f6 L
16
% J9 u! e5 f4 w* e* i7 F17
}5 J) y: u; _+ @3 Q18
* B% T9 n8 C/ b3 @* \7 n19$ d$ Z+ w7 z6 ^7 T: r1 W
204 @5 D$ h1 L3 k) r( Y" O- h! X
21. `4 x2 l: k: b1 k4 A. U+ ?
228 f- N( A9 k# F. L8 j
23
7 E7 B( S3 }9 {. L24+ l- e" }0 e. R7 F
254 J6 C. l1 ~. T. D4 X) n
26
6 Y: K5 S2 _8 H) B% _- O V8 u: _27
, s W. ?* x, v+ m287 B$ d' D% k( W4 }1 k; ` ~9 q
29) ]) D) y$ p+ B
30) R1 ?$ a! e6 f5 U+ X& q! _: P
31+ O' s1 @3 L7 O- j! O8 F/ r* Q
32
: g8 \2 e6 R. w, v4 ^33
! Q( C: T; | Z34
9 k- ~$ @# ]2 g, y N" M0 \! v5 D356 g/ j! n% W! }" l
36) k7 a9 b$ h N" }! E. V
378 c7 Q" r# U$ _' H2 \
38; q0 V0 a$ P8 P7 P! A
39
. P% r" F) o( F: l& c40
- E/ f: ], z3 Y* X# A41. S, V+ Z- c% _1 J
42+ Z0 l8 z, r+ i( `4 ]7 u7 k4 o
43
& ]) N0 p2 o+ R! O44
% j& S' F0 V( A8 l% R45
! k7 ~' ?5 d- r4 [6 ^461 \# _: S; Y) X7 N2 z9 P
47. G$ x( N+ e# ^
485 f" ]. x$ k0 f
8.4.2 数值型函数( ?8 i2 U' Y9 O5 ^2 ]- T
这里着重需要介绍的是pd.to_numeric方法,它虽然不是str对象上的方法,但是能够对字符格式的数值进行快速转换和筛选。其主要参数包括:
; j& N: S- ]1 |4 Q5 }7 l2 L5 v% n6 {) T N4 H
errors:非数值的处理模式。对于不能转换为数值的有三种errors选项:
- [/ O% P: Q i) J# ~/ m$ Kraise:直接报错,默认选项( m- \1 C+ r; Z. c
coerce:设为缺失值8 @9 v$ E. g$ }! |$ ~9 l
ignore:保持原来的字符串。0 y" l2 [8 D2 L8 u' Z4 w
downcast:转换类型,转成 ‘integer’, ‘signed’, ‘unsigned’, 或 ‘float’的最小dtype。比如可以转成float32就不会转成float64。! U5 i9 Z# i/ w5 u. T5 q0 L& E7 t
s = pd.Series(['1', '2.2', '2e', '??', '-2.1', '0']): A0 e% d2 ~7 ~+ f8 ?3 `& R1 ]
5 u9 Q6 C H3 K: D2 e: wpd.to_numeric(s, errors='ignore')
0 @& M4 @' u( ~- L. d) Z: ^# }Out[93]: 3 L3 A* _( ?% [4 _7 V
0 1
) g0 A8 z2 I# t6 j1 2.2* f/ ]5 X2 q. u1 H
2 2e* z' a2 N( a" y7 F* [ t2 j0 v, |
3 ??
% X: V" r: `0 Z+ _4 J5 ~4 -2.1 E( z+ o; ^# Q5 E4 t* B! ]% T
5 03 I6 w/ U+ Q$ S$ R: M
dtype: object
& I: |4 x0 a! B/ ~5 G- i; e3 O8 P" ^8 T F6 L* p T; d0 o- Z
pd.to_numeric(s, errors='coerce'), m3 K& A) N9 t1 }8 O* y
Out[94]: : K) a- f' y# P5 h
0 1.0+ k0 s: O: W; s% e) V6 c" J( C
1 2.2
1 |6 Z) Q/ i9 K2 NaN* ~+ M+ d6 P) D: {
3 NaN) B& A1 y8 n$ P
4 -2.1
* d) g% l+ U0 @9 a2 X: |3 }, ]5 0.0
9 O( Y4 s( W& |$ A' b: zdtype: float64
7 S" U+ T0 Y& c+ m* k6 ?) i7 p ]! {# k% `% g' p
16 y( X/ m' d h5 D$ p' d
2
* w U- G1 O& p$ U( ~36 f. S0 M2 m$ F
4
( o5 m8 F) c' b4 X& J y; Z5
6 D* h6 m, \, m* ~8 A7 |2 B6
8 L" f# e5 s l$ L: B- ?7
* _( W& j& y9 p4 [- f8$ T+ g- {* l3 r' u7 n( T e1 D5 \
9/ Z; l& m- U4 y$ L+ Q9 ]- w
10
$ F U4 \' Z" D; v; |+ L11
/ D; ^$ X4 v# q7 _+ |+ ` s9 B12: F* N D8 ?1 Y$ j. a4 @( N
13
3 k( u% R& G) }( |) N7 f14
) d# Y7 |& L! j' l* K) P9 `156 l" W) A6 G3 M* i5 O0 ^, N) `
16
# R/ I( n0 e7 w6 ^2 L; ~17
' ]% F1 F6 M5 D; R {) ~# j( c182 Q) t1 C! F4 R5 Z
19
# ]7 P2 C/ X/ F9 I4 r. w20
0 X; y- Q6 c6 Z2 O& D) p: Q* p21
( s' e% H! P; k& n. e- ] 在数据清洗时,可以利用coerce的设定,快速查看非数值型的行:& Q7 ^/ i/ R& O& _
- G6 _; o5 |: c0 j4 v/ J: [s[pd.to_numeric(s, errors='coerce').isna()]
( p4 q. b! ~ A! q$ B+ _! WOut[95]: * ?( C7 a& j6 q) E j; a
2 2e
* D: h1 M2 |2 {8 r k/ J3 ??
* A2 x- a* Z5 kdtype: object2 {' x7 v2 |$ u d
1
1 [ E6 G, |0 F0 t Y8 T2
$ g( s. Y% s4 p& y p% V6 {5 [ {3
- i7 F2 q* B! r2 b3 D4
* S4 `: h+ P4 ^* }8 b n h" M: Z7 c5
. x1 ^$ _/ j1 |: O6 ~- j) B5 B8.4.3 统计型函数
; ?0 w( t$ U) j1 H- u( A count和len的作用分别是返回出现正则模式的次数和字符串的长度:$ H8 M. J8 k- O9 V- T8 z2 N
6 \4 i" U+ _8 g( @5 r( X
s = pd.Series(['cat rat fat at', 'get feed sheet heat'])2 d( W. o( ~# M/ e
0 w8 s+ D+ g2 N! g
s.str.count('[r|f]at|ee') # |左右两种子串都匹配了两次/ e5 ?9 k0 s3 u0 R, @2 ~+ _
Out[97]: ! r% w! U5 z. P
0 2
# P8 G$ ]6 `6 h9 h* T; z1 2) P3 c7 \) e) h' Y2 t$ i
dtype: int642 Q! O2 V# s$ H8 T- Z
* w8 O. W) i9 Q+ is.str.len()
1 T- O& ]- f L' G8 A/ YOut[98]:
& {$ H; X* x8 M$ S) r# c3 B0 145 [, l' r: y* W/ [0 q; a
1 191 N/ J2 w; s! e
dtype: int642 v e/ |5 ^1 D0 Y4 e
1* Y5 n3 `2 d0 C
2: c4 K6 \3 h: W2 e7 [- Q
3* w$ g" v( ` T1 w- x. @3 C
4
# S( S8 H7 E9 `- B6 b7 B, i& o5
0 Z- n6 O( o2 h; T& O F6# g8 b, M. [' k
7
7 M$ \* W9 X1 e8
) C4 J& ?$ o+ E* H B! ~4 d97 M! S4 @( E) E( i% i$ V9 m
10
& A/ W4 ^8 o6 ]9 A' m2 P11
& d! f- T+ u& M12; _% F" `, l& S q- ~1 @( \
13
4 n# j4 q% m' A& S8 s) o8.4.4 格式型函数
& k2 u, u6 S1 h; m$ Q 格式型函数主要分为两类,第一种是除空型,第二种是填充型。其中,第一类函数一共有三种,它们分别是strip, rstrip, lstrip,分别代表去除两侧空格、右侧空格和左侧空格。这些函数在数据清洗时是有用的,特别是列名含有非法空格的时候。
9 e! p' s2 X. T- W% e
1 y3 p( _6 T( S2 t0 c! z/ h( jmy_index = pd.Index([' col1', 'col2 ', ' col3 '])
$ J+ Y7 I5 c* G$ c$ y
* N, C4 Y" t/ M1 L+ |4 _8 g9 `, d) Wmy_index.str.strip().str.len()
. i0 M8 {$ l' u1 q q" `3 YOut[100]: Int64Index([4, 4, 4], dtype='int64')' F1 k) L. a. j' ^" ~' H; W
8 ^- r/ x5 T- D
my_index.str.rstrip().str.len()
2 N2 C$ ^; ~# R- k: F* K4 e7 dOut[101]: Int64Index([5, 4, 5], dtype='int64')7 |1 n6 C4 q. J0 M+ V
, m* w4 `# h. L N7 {my_index.str.lstrip().str.len()5 s" p9 N: [' [( g+ ^& x5 \ v
Out[102]: Int64Index([4, 5, 5], dtype='int64')
2 e/ p! z1 h$ L8 ?- s, ^( x1* E9 ]* n& r9 H7 y1 J
2
) d8 p- V# D1 _9 ?3
) }- `; O5 w, a! `, K$ T' Y4; v- ^ }3 ^' o4 P1 i1 s( g# Y
5% J5 ~0 D) l3 v4 Q( \5 j* F# ]
6/ n/ E0 Z- U) S) Y
7
+ }& O( A/ k* P5 G/ ]0 | I8
- X8 B- I9 N7 s4 @! J: x0 }9& w5 q5 K$ t. w' j9 z; }( g
10
$ j8 u c4 b( H' R* a9 I 对于填充型函数而言,pad是最灵活的,它可以选定字符串长度、填充的方向和填充内容:
! H9 d- E9 @9 n Z5 h, y% H& a$ B7 C, A$ `
s = pd.Series(['a','b','c'])/ ]1 O( Z! {% E9 D2 ^+ G
m! r. D0 |1 z" F) K5 p1 [8 Ys.str.pad(5,'left','*')
( c1 f1 [# o4 z. y7 J/ N) Q `- BOut[104]: % d0 z3 G# i8 Q% X1 @2 k1 M |
0 ****a
7 n3 z# o; K$ |! q7 N7 ]1 ****b' Y! X* a- W; B4 ~! n
2 ****c! k% c! R5 ^8 u
dtype: object; S9 Q; Y2 o- Q0 b3 e& b$ Y% j
4 [! P: o; s! o
s.str.pad(5,'right','*')
7 I w4 V. K- hOut[105]: / T' v9 j- G6 n. w
0 a****
# C5 J: e# L" S0 c1 b****7 A k3 w! O7 p. _4 p) v+ `" V4 a
2 c****- g- w0 t7 k" y; l: r) \6 _
dtype: object
- x! z1 f2 C: c! g7 a0 V/ a
; J! J! `: l8 @/ C+ ps.str.pad(5,'both','*')7 `: |3 R3 @( @' G
Out[106]: 9 }2 y1 O/ d# i2 y/ I$ T
0 **a**
1 }* L B# x/ g3 E" B. @5 |1 **b**
4 ]8 d) E" {5 @% S2 **c**
' V; L6 B" u- L7 F6 W- ~! zdtype: object
# w; F/ u. Q* `9 s4 r) L' b# c+ T W8 E! p2 y1 b
1
# k, H* Q/ T& Y0 K- U! e- i* M+ r20 U- Y. ?; [0 M3 K+ Y
3
, k' k7 B$ p0 n% o4
3 e3 m5 d. j- l" r5
. N6 v( T6 o" V3 h; T68 r5 a- e' q( ]' U
7$ G7 m; C3 T: b& |9 I7 Q
88 ~: ]+ b" a4 c% l9 a5 f% a
9
. C. r& F1 C, |4 F% p6 r10
9 x. ^) g9 p$ O" a11
. ~ ~ `: B" V7 L0 n12$ K7 G, U# a I! R
132 x" h& y8 [! i4 c2 P9 S1 z4 W8 }
14
/ }2 J y* K6 W# z2 a5 r157 P/ l; ~( X3 B1 g1 R
16
3 R/ _( r+ z) o# ?2 G+ }) C171 w! ^& q( f% B2 ~" z5 Z* @* |
18: |: Q$ ?6 a& s5 J+ w9 |7 I
19" R W" u1 O$ V- i
20
; d! @% T' L' i& |) e2 d212 \- k& x4 k8 H/ p$ E' D% ^6 O- d" e
22' m, F3 F8 }9 z# e+ ?* P
上述的三种情况可以分别用rjust, ljust, center来等效完成,需要注意ljust是指右侧填充而不是左侧填充:5 |0 @3 |# b# M+ P
' }+ G/ a' P& v: O. {9 s+ G$ S7 Xs.str.rjust(5, '*')" O3 f& _' R% l" Z% n2 w8 M0 _# T& h U
Out[107]:
( n2 r9 U- F: [" Z+ L0 ****a' `( D' m4 D) ^. k
1 ****b6 C, F& D- Z' C8 R. J
2 ****c" m( ?9 k8 N5 }% [$ W
dtype: object3 ?( {$ [9 r" t8 l( i: d
3 h, G9 N( L2 L- N9 o# ?% m3 ?s.str.ljust(5, '*')
- {' q; V4 g& yOut[108]: w& g: j; T3 e1 `4 z4 K
0 a****
) H! S( n. H& M9 U1 b****
0 p2 w) R/ }* e0 z" }) W4 \2 c****
0 A* M7 b1 Q( adtype: object
/ [" U# q# ]; ]# Y' {/ S( {! {9 w$ ]4 o; f0 V. Q
s.str.center(5, '*')
" [' u' W+ N3 @* G& R5 j7 {Out[109]: 1 F, g* j# M2 c
0 **a**
: E! y! u: f4 A+ s4 _6 G1 **b**
3 _* a2 m' C+ H. J5 S" q, P" U O2 **c**) p( s* ?: e J+ o4 P
dtype: object& P, `5 d' K4 @9 W" |
8 ]3 H4 u: V3 V9 o s1
8 v$ y# O- h* F) I. r2
: k; y1 B7 P8 t: B* `; w; x' W3
6 Y- A! M: j' z( G$ X R8 n47 M, m+ h: @% Z$ B- X" w( B3 [+ D
5 }8 |& e! {2 U" H
6
+ C7 ]- i: O' X7
( ~# H5 W* v1 I% |6 P8
) a2 c! x r9 {! @' ]9" `9 J3 B5 i7 ~ A9 j4 D
10
8 q9 H$ a8 U" O$ G% r9 A! t11
6 J) L$ f- c: \$ K7 N7 e9 L! n128 U# C: |" w% I
138 X: R; ]+ X" ?; j$ D) T6 d" n
14, e8 X# c: u9 g& z9 W- m
15
4 @# r* i; H# [9 v0 m1 [- K* b16
, v: c3 g/ K- ^ [" K# y* v& i: n4 U/ r17' w6 D9 K, @4 g1 v: B, o& ~
18
Z8 d: }/ |; r1 f, C J19
$ A$ f# w* i3 q4 k* Z0 ?& f$ v20
5 h# ^( V0 _9 Z( F 在读取excel文件时,经常会出现数字前补0的需求,例如证券代码读入的时候会把"000007"作为数值7来处理,pandas中除了可以使用上面的左侧填充函数进行操作之外,还可用zfill来实现。' t$ r( i( B" |4 I z
/ ]' E8 x8 @1 V! D# b) `, F& W ?
s = pd.Series([7, 155, 303000]).astype('string')
! c7 q+ I+ l, ~
6 R2 \+ i( v+ X1 Ss.str.pad(6,'left','0')
& b7 u) b( I5 ~: c, h8 T$ k/ zOut[111]:
! l. w+ [7 S3 L( E0 000007% Q! J/ D) a5 Q8 w+ X# f
1 000155! O7 U7 x9 a0 L* C" q
2 303000
4 K' I: R! {1 A; ^( p0 wdtype: string- r. V1 |( _7 B- G# K8 d
* z' t4 v4 r7 K2 a. Z( ys.str.rjust(6,'0')/ Z2 q5 @/ q9 \2 [+ f/ _- V8 g
Out[112]:
6 m1 w0 q5 y8 D6 J0 000007
, R& k3 |2 G2 u1 000155
' v/ d! U+ V0 ?1 l1 j% \( W$ u2 3030000 a: T# e* G7 m' t/ u
dtype: string
' g. Z. \, n6 ~* T
- u( U" d0 V2 Y" n: Xs.str.zfill(6)7 q7 s/ L8 }1 U5 V; [& K
Out[113]:
* T' ?5 l. J. p0 000007
+ N1 N; i4 z8 Y. n& n2 @. c [$ g" X1 000155
5 d) z& @% B, p+ E- V2 303000
3 P2 B6 x+ l& v& Y; Y( m$ Wdtype: string
( z" S" \0 K2 V: R8 s# t: o! t, M' \" A& Q0 p9 E9 }
1
6 A# O, ~* m! G7 q2' G% U! @- L! n K6 ?5 b% ^
31 {1 \6 H( H! g; Y' _
42 Q- Y+ I9 w" J
5
4 r' g9 W# W1 X6
. d% i/ t. x, y9 f! _- b0 j! R7
. @# \" C: A) O. `: s( Q85 L/ x. [& s6 F6 H+ {( N: x4 {) x
9. B& n4 I" v5 ~* P1 h7 ]) i5 ^( {; O
10
% j: ?$ W, J* ^11
( V3 ~. Q1 N7 M* ^& o12
& y' Q) c1 ~0 y5 ?- O& W13
& a' S# Q8 c. K6 N& B, i141 u1 p! ~6 u. J7 _1 K
15% q3 G7 O# I9 z/ W" K, E, ?
16
5 p5 |+ M: ]7 h8 m5 c17. A. h& S1 w: \9 g8 ^4 q- m
183 X, u2 w( `. V! G6 W* E. r0 l
19: r* a2 p3 w' P q: `
205 [& b4 y1 |( }3 ]# l
21
. L8 e3 w3 B1 r: [) v229 j4 g$ s6 v" w) H, W# ]$ L
8.5 练习) C; Y$ X6 J! K) b& B
Ex1:房屋信息数据集
& R1 }3 M2 d- p" V现有一份房屋信息数据集如下:) u, k3 ^4 ~' n0 ]
" o, y. ?- {7 c9 R- x
df = pd.read_excel('../data/house_info.xls', usecols=['floor','year','area','price'])6 \' y* S) G; l4 R( u
df.head(3)( r2 Y, \: Z* P: Y! P
Out[115]: 7 p) X3 m; Q E% ]' ?0 ?. q
floor year area price
$ j# T! t. Y$ [3 F) R0 高层(共6层) 1986年建 58.23㎡ 155万
0 L) [( l( L0 }5 C# _4 h1 中层(共20层) 2020年建 88㎡ 155万
) ]' }6 N" G: k& W: v/ ?% y& R7 T2 低层(共28层) 2010年建 89.33㎡ 365万
5 ?) {: d, f: z6 T# d3 `, O7 W1
) _/ Q0 j- f* z% D2
. j: T% }- m$ I) X# ]8 _3 y4 m3
# N+ N$ k. J, W6 J# I: C4
& T1 |' N E. p3 q( B0 i0 n5
- E' k8 h/ h. @# ~6% P& Y8 H% S" }" V- }% w* C6 p
7
- f$ G' t- Y D% i- g将year列改为整数年份存储。
" _0 q0 B, u( o2 p6 B1 j将floor列替换为Level, Highest两列,其中的元素分别为string类型的层类别(高层、中层、低层)与整数类型的最高层数。 r: S& |7 r: {& ^2 `
计算房屋每平米的均价avg_price,以***元/平米的格式存储到表中,其中***为整数6 v R9 T1 z$ _3 g# }' N; d: ?
将year列改为整数年份存储。
' S* Q/ v) \' d! O4 ?5 t8 d"""5 P9 Y0 t- Y& K7 o( L
整个序列需要先转成Nullable类型的String类型,取出年份,再将年份转为Int64类型。! P! A! T# d4 }) y V' z
注意,转换的类型是Int64不是int,否则报错。即使astype加参数errors='ignore'跳过缺失值,- d8 ]) P: I7 p, N, S6 }% B
转成int后,序列还有缺失值所以,还是变成了object。8 W7 V" z" N" I0 R# K) G
而整个序列转为Int,就还是Int类型,缺失值变成了 pd.NA 。
" L+ x m1 |5 L& R% @6 |$ b0 F- X. G+ f7 j"""* e, N0 o1 _- ~; N+ q9 i! c1 P T
df = df.convert_dtypes()
7 B: Y, U: X# B) E7 Ddf['year']=df['year'].str.replace('\D','',regex=True).astype('Int64')3 H1 x; M7 T3 q& p5 x( Q' p
df.loc[df.year.notna()]['year'].head()
9 B& W6 l* B. d
2 |9 k% y5 W# A/ \- ^0 1986
+ A) n0 f2 D& t2 C# U* [7 s) g" X1 2020$ E6 N3 M7 ?% b" E) B" G
2 2010* f* T# D8 ?( G" B0 q
3 2014
3 Y/ B5 J% W2 w, `! o4 20156 M' `+ W D6 \
Name: year, Length: 12850, dtype: Int64
0 P$ h! d3 F4 X" f5 ] a* y
5 ^! w0 p. u# j1
; U4 ^. y% R+ j6 E3 ~22 N! N* X$ {4 P/ L/ w6 L' v ~; Q
3( ^- L3 ]3 d8 l; Q3 r
45 A. v3 o6 h, j" ^
5
0 B7 J6 L$ j7 O8 n) `6& @9 T1 ~$ ^6 k. q* o, @
7+ u, s) W) {/ Y, d3 |
8! D3 n9 p) l; K, N& L( u9 Z- ^) g
9
/ n8 }0 I5 A/ @107 R/ l: Q2 @6 X& {
11
3 c$ m) Z& S4 H12
5 ?* J4 s/ Z _5 ^13! N$ G3 ]( N; f& H- ]
14
( k" T" O. R/ f: Y. M0 `15
9 K" X! P$ s% C: ~$ R$ M$ e16
5 U7 b7 o, Q6 X+ Q参考答案:
7 M* A$ D; E% _. |. u/ O% t5 W
: |# X8 a& u3 ]不知道为啥pd.to_numeric(df.year.str[:-2],downcast="integer")类型为float32,不应该是整型么8 ]4 B, i# Z3 z' N
5 |6 e* w/ i2 ~0 ~df.year = pd.to_numeric(df.year.str[:-2]).astype('Int64') d# l3 x2 X; N9 E3 t
df.loc[df.year.notna()]['year']( d6 s# E6 q v- E
1! T1 a& B! L5 a2 u h$ w! C: U
2( N0 s7 v) }3 _" r' P
将floor列替换为Level, Highest两列,其中的元素分别为string类型的层类别(高层、中层、低层)与整数类型的最高层数。/ n8 A1 d W3 A1 r- h
pat = '(?P<Level>\w+层)(?P<Highest>\(\w+层)'
! T5 L# j. E& p4 Y% mdf2=df['floor'].str.extract(pat) # 拆分成两列,第二列还是(共6层得形式,所以还的替换一次: i2 x7 i; T; f, R! C' @2 x
df=pd.concat([df,df2],axis=1).convert_dtypes() # 新增列拼接在后面,再次转为Nullable类型
$ a, n) e: l5 T' |# n: D' fdf['Highest']=df['Highest'].str.replace('\D+','',regex=True).astype('Int64')
$ z+ |3 Z2 t$ U9 Y u& e: Ddf=df[['Level','Highest','year','area','price']]# K8 M; e) r0 |1 p- F
df.head(); l+ F3 `! i& r! l7 R5 L: L
0 c" J& q# } V! k! r, e# X1 x: T
Level Highest year area price
8 k2 r( h1 ]2 f; ^0 高层 6 1986 58.23㎡ 155万
5 i% }: t! Z- J+ v: Q5 G1 中层 20 2020 88㎡ 155万
! Q8 E& @. G4 j2 低层 28 2010 89.33㎡ 365万) x y+ [' Z& G
3 低层 20 2014 82㎡ 308万- Y) R( B9 C1 d- j( B7 R( Z
4 高层 1 2015 98㎡ 117万
% M# D3 P9 Z, r$ `6 d$ J! t3 B1
- K1 \4 v2 V7 s7 M& q$ T# e8 s. z2
7 w6 e2 @7 I9 Z8 p* a4 J: k3: N$ s- a3 d2 q/ L, J; M
4. h |& H( E/ w% O" K9 ~ a6 O2 Z
5
9 @. {6 X& ^4 Z, X @; S6
" P* j! P; `; E) {& ]5 U6 b7
6 t6 H- X6 G( A. h, W+ i9 c89 q0 L' ]+ G5 V ?2 b7 D: N
96 d ^1 P2 ]- X0 p* Y- q
10
; L$ [5 W* H _0 R5 b+ D3 z11! m0 K, x) E" K
125 `' O0 m& F+ ]5 }- c" ^
13
. G. x- r4 }$ i0 M* x: X# 参考答案。感觉是第二个字段加了中文的()可以准备匹配出数字,但是不好直接命令子组了0 ]% e: f2 Z @. s7 D
pat = '(\w层)(共(\d+)层)'
1 U" o! I, i& {2 \1 Unew_cols = df.floor.str.extract(pat).rename(0 f3 @* l0 Q( ]9 _1 r( a" a2 q
columns={0:'Level', 1:'Highest'})
' a) [! D' f: u) s: B* J) ?+ V: k5 d5 V2 z2 c! H
df = pd.concat([df.drop(columns=['floor']), new_cols], 1)
1 w* C/ Q) A% C- b) B9 }6 m( Wdf.head(3)
0 ^, k2 ]2 R# t4 r f' Q
; N$ ^) p, w3 ^6 u, C' m6 rOut[163]:
8 s# d$ c8 Z6 s* f6 Y4 P year area price Level Highest
8 g( n% R2 _1 o# ]0 1986 58.23㎡ 155万 高层 60 P o) [4 W) h4 d
1 2020 88㎡ 155万 中层 20
" }: F/ a: o8 D) u# H1 ]2 2010 89.33㎡ 365万 低层 28
! A0 J' i; {/ M0 I9 Q15 Z% G0 I. M' s% N- u9 f, _* x
2* Q0 `+ J, H* ^+ e2 Z/ r
3% C U- }) p/ }1 L t0 i
4
% o0 Q$ G* f6 z6 c( x) y5
2 W% t* E# l" X W. N9 p& E3 Q6, @6 D" [* |% S2 z" N8 l$ F: B
7
1 _9 @# N) z$ Z2 r$ `. \1 e( t, ]89 K7 t' a3 ?- X: Z
98 Q& n% W) E8 a4 U+ P" \+ I2 L2 n
10
& S/ M6 K# \* d( s11" j. y+ G! U. r) h
12; o! U5 m3 X E, t/ Y$ T3 q
13
7 U& R, _( ] l% p$ p计算房屋每平米的均价avg_price,以***元/平米的格式存储到表中,其中***为整数。
1 c+ e" k) j9 x0 x, V" _"""# e5 y- A9 ^. N8 K
str.findall返回的结果都是列表,只能用apply取值去掉列表形式5 D' D! H( ^$ J% u3 _! d
参考答案用pd.to_numeric(df.area.str[:-1])更简洁4 m) X3 }5 g5 l2 \) H, m
由于area和price都没有缺失值,所以可以直接转类型
/ ~ Z( D( U" g7 Y+ Y s"""
% q/ s) s1 r# s% Rdf['new_area']=df['area'].str.findall(r'\d+.\d+|\d+').apply(lambda x:float(x[0]))8 z& y8 K+ ^" X0 J
df['new_price']=df['price'].str.replace('\D+','',regex=True).astype('int64') W8 q: g- _- v' z& ?& c( B9 ?& l4 q$ j
df.eval('avg_price=10000*new_price/new_area',inplace=True)5 w# H5 _, ~9 R7 B9 I& ~% t
# 最后均价这一列小数转整型直接用.astype('int')就行,我还准备.apply(lambda x:int(round(x,0)))
( x1 Z- s- c" L: J+ ] m% n# 最后数字+元/平米写法更简单
: M/ r. w# q: ^! T( D* z+ Wdf['avg_price']=df['avg_price'].astype('int').astype('string')+'元/平米'8 c7 p c6 d* w% D
del df['new_area'],df['new_price']! W4 t! l) N5 a) H+ o8 l# U
df.head() p/ ^5 _, X7 ^: x2 D: b6 T
. S8 m) u# s' v2 O9 U3 v- | Level Highest year area price avg_price
+ I9 F, I; }5 z7 r; x0 高层 6 1986 58.23㎡ 155万 26618元/平米5 u- o9 n) {9 _0 x3 V) M/ V' \
1 中层 20 2020 88㎡ 155万 17613元/平米; \: k3 c% o# a; J: D
2 低层 28 2010 89.33㎡ 365万 40859元/平米% N$ K8 S$ S( e( M" x! t2 Y
3 低层 20 2014 82㎡ 308万 37560元/平米6 c# o% ~ `% j* p: ~- g; j3 ^
4 高层 1 2015 98㎡ 117万 11938元/平米8 T2 ^1 m$ b$ Y% b" C- b5 k) i
( A" R1 V0 ]7 _- y" O! h
1
+ Z' I7 x! K' p; M2
8 d4 {6 c2 d6 m% S* P3) q- }. T1 I9 T
4
! F) p2 C5 g$ A! [/ R' X `) o9 z/ \3 u57 R$ I- F2 c. S6 K" v
6
0 W- p# ^9 Z" J$ L+ e$ [5 b78 C1 L _* N/ N k+ L' Q
8
" v3 K+ y9 q1 k. T" |( V8 D9
3 M5 J9 `! O& O: c# _! X1 R10) Z( x+ m& D- ?% d
11- ]# G$ s6 p1 }, x) g7 d, |" x
12
A: Z3 }: m1 c. ~- K13, n. l: M% G3 K9 W
14
d- _0 I2 f- X0 j# B" q4 U3 z15' x+ @. j7 i/ Y" O
16
, V7 [- V& f' C+ ?2 r17
X) o( Z! x& W/ C' [18
. r& F0 y. `& A191 u) U; Q; x' I6 {
20: l; S) p- D% {" y' \
# 参考答案
, u4 r! C2 ?( B |5 T( B7 Ws_area = pd.to_numeric(df.area.str[:-1])
2 E% _( L) z/ a7 w( ?# xs_price = pd.to_numeric(df.price.str[:-1])$ T3 b% T+ h8 U9 r0 X9 g0 v
df['avg_price'] = ((s_price/s_area)*10000).astype(! r9 U: V0 I% u3 R6 m
'int').astype('string') + '元/平米'
7 X1 W; D& i0 I9 p! C
0 ^. d1 V9 ^: Q1 ?4 z* r; C" Tdf.head(3)
+ E( E/ t, G$ ^8 Z. qOut[167]: * l" x8 X) j3 T- h
year area price Level Highest avg_price
# V4 m" q- x# c1 O5 t4 G0 1986 58.23㎡ 155万 高层 6 26618元/平米 C( I! I' t9 U1 P" [8 F0 r8 R
1 2020 88㎡ 155万 中层 20 17613元/平米' j$ k) J+ y: R, |9 B
2 2010 89.33㎡ 365万 低层 28 40859元/平米
8 {, E: { ]& s4 E! c( G1. c/ X5 p3 K% F$ j+ u4 p
2
$ D3 V$ [* f& L; i$ H( ~( T, s3
* p5 s$ f/ m1 r9 S- p8 l8 C43 t _; f+ d B3 B, e
5& I- L. I. w% J
61 f" y R% Z+ V+ j$ U+ i
7
$ }( l, k) X8 M2 ]8
7 F5 x* N2 z7 h7 n/ }- W j9
3 b9 z2 [3 t- N% p9 I10
% z9 W+ `/ |( M1 m; q114 Z! c* l( U: S* ]* }& p$ J) q9 N
12
`, Q) ?9 ^2 d2 R4 _5 sEx2:《权力的游戏》剧本数据集
5 d, V! W4 }+ Z* C2 U现有一份权力的游戏剧本数据集如下:
( L2 ]3 b7 Q8 w% V1 M3 N9 ?! i: }# z
df = pd.read_csv('../data/script.csv')
; I. o2 ~/ G; }( x8 g: } S" mdf.head(3) J) I* G7 N }8 R
+ a f. u, c, C6 V
Out[115]:
1 L0 R: [0 `7 _ E) L( s9 x3 lOut[117]: + A! O* L4 l: E+ z. @
Release Date Season Episode Episode Title Name Sentence" O1 H! z. U" n
0 2011-04-17 Season 1 Episode 1 Winter is Coming waymar royce What do you expect? They're savages. One lot s...
. B: R. L0 S8 k5 P& V o1 2011-04-17 Season 1 Episode 1 Winter is Coming will I've never seen wildlings do a thing like this...
' Z7 S8 G3 w* b% l2 2011-04-17 Season 1 Episode 1 Winter is Coming waymar royce
& B; {) z# X, j2 W- X/ F1
, s. K# A! u, Q8 e2
7 N5 G" R6 C+ }/ s* F) W3
! a5 V5 U, O- a49 y+ J/ z+ O0 U8 d4 }* V; O4 y
57 d$ e$ T! l+ o6 M2 n: V
6( R& Q# [& S" s+ \3 ^
7
* _* V: }) f& g/ I7 {, X8
2 |/ u( z, ^5 \. S5 T/ z* X ~9% E/ y4 X" G6 U- F7 v
计算每一个Episode的台词条数。
$ _6 G5 S* `; ?- k6 B以空格为单词的分割符号,请求出单句台词平均单词量最多的前五个人。
7 G& c& G1 [# r/ f/ Y$ S1 E若某人的台词中含有问号,那么下一个说台词的人即为回答者。若上一人台词中含有 𝑛 个问号,则认为回答者回答了 𝑛 个问题,请求出回答最多问题的前五个人。. S4 K5 u% _4 ~# T% C' O( o/ a- R) {
计算每一个Episode的台词条数。
. E: ?: f, l( }& y% d9 ?df.columns =df.columns.str.strip() # 列名中有空格
1 D* q B3 a+ L" e/ sdf.groupby(['Season','Episode'])['Sentence'].count().sort_values(ascending=False).head(), W2 e) q+ l8 B3 \: ^+ T+ m
P8 J. a! i! N# K# r0 zseason Episode * S6 h8 `0 I4 z7 g* v
Season 7 Episode 5 505
! l. C. r# E8 B3 t4 k4 L/ S' ]' uSeason 3 Episode 2 4808 E! N% S% y( d( c% @
Season 4 Episode 1 475. ?; N8 [+ V' {4 N& v% g
Season 3 Episode 5 440* T" r: e. x( h" b5 d
Season 2 Episode 2 432
+ r+ l9 m+ |5 a# S14 f& B1 y J, w
2+ o, U- _( T1 n& X
3$ N. }- n9 _" g* c G! |& q
4" G* G0 K4 q) u% w" P( B* N6 g t
51 M& J4 t3 Q- r; {! Z7 e: l
6
3 J/ w% s- V2 s* X# c76 |6 h4 k/ v" ?! _( }( r" ~9 h
8, w2 F7 ?6 s0 c. r: i0 D
96 n2 j% N$ c' i# n! k/ _7 E
以空格为单词的分割符号,请求出单句台词平均单词量最多的前五个人。 Y8 |1 [& J( K% s- X& t
# str.count是可以计算每个字符串被正则匹配了多少次,+1就是单词数. [- e( B6 }+ `# N
df['len_words']=df['Sentence'].str.count(r' ')+1+ H6 i2 G* j4 Y& z) T
df.groupby(['Name'])['len_words'].mean().sort_values(ascending=False).head(). L5 A' B+ A* Y! z! B
4 K" ]+ a' {# iName- s9 B4 u3 F1 D( p7 d6 z
male singer 109.0000007 X; b& w- X$ h
slave owner 77.000000
8 G; U, G' E( v$ dmanderly 62.000000
$ s, u, F/ E: X1 ^( ]1 y# W: n7 `lollys stokeworth 62.000000
) n8 L/ ]5 o" idothraki matron 56.666667. V$ h; E& w) z3 K8 i1 F% ?. d
Name: len_words, dtype: float64% W2 L" `) L3 H3 H# p8 J
14 t; M8 J' s e
2: b6 l* u" U( T% F" o* N- @; `: k
3
$ H/ P3 X5 I" c) p9 J0 p- U0 O( k4
6 ]2 a9 _. X: ^' }1 ^* x# u5
9 n9 U, z9 g, v6 G4 M6
+ d. U R' O: ]- n7- l1 \& k( a* c. T1 t) {4 ~% R. d
8/ m7 [, x4 w: b7 _
9& k, S5 I: ~. l9 k# P
10
& m5 ~0 l5 [" X; K N11
$ m; q3 L4 I5 S: ^! F若某人的台词中含有问号,那么下一个说台词的人即为回答者。若上一人台词中含有n nn个问号,则认为回答者回答了n nn个问题,请求出回答最多问题的前五个人。
! V7 B" P, w; I1 M: v- Sdf['Sentence'].str.count(r'\?') # 计算每人提问数' R7 [5 K: C$ N6 c
ls=pd.concat([pd.Series(0),ls]).reset_index(drop=True)# 首行填0, b2 j5 o7 K5 Q/ c: V
del ls[23911] # 末行删去
# i% F0 |" {& [: f$ X; r8 q2 y7 }df['len_questions']=ls6 E! p, q, y3 W9 G
df.groupby(['Name'])['len_questions'].sum().sort_values(ascending=False).head()
: U* U9 l+ k4 I8 e0 F6 y3 M: U
5 T% S. Z* r8 B1 Y0 R6 E6 WName
; @6 X2 u% i$ ~' Ltyrion lannister 527
, _1 M4 E U* r/ Yjon snow 374
% F. _. ~9 v) B, p, h. a) Pjaime lannister 283
- a0 n, ?9 J7 z/ S; |* `( Farya stark 265
1 c b* d' Q9 f( N! Qcersei lannister 246. w0 |+ S1 u6 R' y/ A7 Z7 R
Name: len_questions, dtype: int64
0 s8 n9 o% E* H
; I. |" g9 ?9 P# E- x$ m7 n# 参考答案+ F+ D% V s1 {4 m8 s
s = pd.Series(df.Sentence.values, index=df.Name.shift(-1))/ _8 V1 }4 Y5 V; K0 A! G
s.str.count('\?').groupby('Name').sum().sort_values(ascending=False).head()
) F6 q6 e$ w3 t- u m4 a7 S# W5 y( C$ @( o; H" i! _( ~
1! p& K4 m2 v! c5 C
2) L0 P8 D& F* J/ g9 e" f
3: h7 N7 d& x A) V0 d
4/ i8 v. Z2 p" B( J+ r1 r4 L; ?% f
5
/ e# A2 v( I# Y: t z6
; F4 o. y6 g0 H: c' D7
- V/ \! _5 n2 g1 g8
t9 |! x- A' h- w. J9
5 L: X& ]/ R; J4 c4 |& w103 M% G) g% b, H* m! u' j {) L
11
6 Z' `" ?- t3 g K2 ^12
8 S) T1 p6 ^/ q) Q13
" l5 S! T' [( `14
+ G; D1 N( K; n0 T; U2 \15# D' @; H0 |" F' S+ C- I7 c
16
9 B0 Z) a) O0 V% m17& j! J; }# }2 ~4 p$ n( G
第九章 分类数据7 X7 f7 M% w# A& n: P7 ], f
import numpy as np
6 k/ P; j4 g* s3 }4 p% _. g* W limport pandas as pd
* ?" D [! |$ }+ } [& s1
& F' K) D$ B/ Q5 ~1 S2
; a; |/ I' a2 `4 d" ~) w7 `9.1 cat对象# o4 b! o2 K) |* c0 Q
9.1.1 cat对象的属性, C! W& g3 T7 v9 n4 ?' t: a
在pandas中提供了category类型,使用户能够处理分类类型的变量,将一个普通序列转换成分类变量可以使用astype方法。3 Y8 b; u L' i
8 }8 Z1 }' |% @# f1 l8 b$ M
df = pd.read_csv('data/learn_pandas.csv',3 y! k+ \! }( t0 x
usecols = ['Grade', 'Name', 'Gender', 'Height', 'Weight']), U8 J% s! q3 D5 z& |- L$ f+ }$ Q
s = df.Grade.astype('category')
5 x1 d" P( u5 }* f9 q4 S# o5 p' s$ C( A1 q+ Y* `/ D$ U
s.head()+ Q- a& w% h7 Y4 r ~; |4 `1 v9 ?
Out[5]:
8 r- J/ w/ w% I; @# y- i7 b0 Freshman5 t2 g& y" P6 E' O: M+ q1 r( ^
1 Freshman
! ~7 H1 r6 z- g' H j1 b2 Senior0 [1 f, Z' S5 w) i; f, q% C
3 Sophomore
2 _* Q% k$ I+ V7 R% M+ |7 M4 Sophomore
9 \. D8 |3 X% Y8 jName: Grade, dtype: category' ` H9 E: M2 U( j! O" U* T
Categories (4, object): ['Freshman', 'Junior', 'Senior', 'Sophomore']
& [* w! U- g1 m: h1 F2 i1 H1, `, Q! \( V( l5 I' S" }1 `
2/ f' N1 x# u0 o7 z: `' o
3: B8 v9 E! Z) r" r" j
4
1 b1 \2 V! v5 f" V' K }" T0 L5
7 m0 E. {7 c3 h1 H6' ^& {7 B) u! R+ @* Q6 p5 q
7# Y9 Y" z3 Q9 S3 |
8
3 i z& P4 J& k& X. [7 r97 O5 a b5 {1 I/ L
10
% ?8 N3 |4 ?. K0 S11* `2 Q% }) d0 w, J$ \
12: E( x! _1 l; I9 G" d7 v2 m
13
- C1 y" N1 v1 Q4 Q3 U2 ^. f' N- Q 在一个分类类型的Series中定义了cat对象,它和上一章中介绍的str对象类似,定义了一些属性和方法来进行分类类别的操作。
7 U; c) _/ T7 g) r
. Q6 X: m7 b5 x2 B6 [. Ns.cat2 L, u: e9 J5 n/ U: G* V& ]( w
Out[6]: <pandas.core.arrays.categorical.CategoricalAccessor object at 0x000002B7974C20A0>5 U9 o, D! \' l6 A# r& L# q
1
3 ?; h5 C; Y% r4 s; M26 ?. c4 B' T' I) G' _# q5 \9 X3 J
cat的属性:9 \! \9 _' j/ ^7 |" P* N) E: D/ L
) T0 ~! f1 d7 I' y2 `
cat.categories:查看类别的本身,它以Index类型存储
! k" K* q, W6 D/ _! Zcat.ordered:类别是否有序
) C& W( z, B" K4 ~) g, `/ acat.codes:访问类别编号。每一个序列的类别会被赋予唯一的整数编号,它们的编号取决于cat.categories中的顺序
$ X6 m) z9 l- C9 u+ Zs.cat.categories1 ]( U g2 a( ^3 T9 G% p
Out[7]: Index(['Freshman', 'Junior', 'Senior', 'Sophomore'], dtype='object')& f& D; n [( f' @) m8 A! \
) q5 Y6 c: V. O! Q ps.cat.ordered# l. \' U- b1 a3 c4 l5 z- s& K
Out[8]: False, x0 L' w# d; i+ a; r$ w4 L* ~
. i' I' y" s; Y$ |7 ~ I
s.cat.codes.head()
4 Z! Z0 d3 F- l6 j- U6 POut[9]: $ {! s6 U" N) i6 b8 T6 a( S" L
0 0
* ~1 t$ R$ M% P, @1 0
6 E/ H. q3 B3 k2 2 k3 [6 t- u/ Z. \( n8 b
3 3
5 [9 o5 G/ v) Y* Z! v y4 30 Z' [$ n2 k& b5 }$ o0 Z
dtype: int8
# w2 `" u% {6 d7 o3 i17 J, m6 O* F9 L' N# {
2; c8 J) Z; v9 q/ l5 v+ L, D% H5 @- | Q
3
- N( c; p2 V( F' D$ ?1 n" [4
( g8 ]. @( r' ^7 s5
! X$ `8 F I S) M1 T6
, v. J- Y. A4 A/ r. L( j" P7
A) L+ f% Y# ] H4 m, j85 W3 Y; N3 m/ V% [$ U
94 L7 g7 U z+ t6 B# [! G2 o
100 K+ N7 y# q! O. z7 h
11
9 B: s0 m5 r( X5 G7 }9 M) C+ \12 `+ G" N" { J: d4 R/ i q7 J
134 D0 t" n3 `* U3 B5 F! ?
141 _- l' J" v2 ^- _! J: f
9.1.2 类别的增加、删除和修改 Q! G& D% t& t8 i0 x
通过cat对象的categories属性能够完成对类别的查询,那么应该如何进行“增改查删”的其他三个操作呢?
/ K6 x9 O, e& d2 P' N' C
( T& C# l) ^3 F' L( N, \7 W【NOTE】类别不得直接修改8 F1 N( R2 t8 `8 A t7 u
在第三章中曾提到,索引 Index 类型是无法用 index_obj[0] = item 来修改的,而 categories 被存储在 Index 中,因此 pandas 在 cat 属性上定义了若干方法来达到相同的目的。
+ O. H$ ?& ~$ j- y2 Q0 d$ X& R, o* t$ O# @; a- @8 D2 c, l
add_categories:增加类别
4 p% K0 I$ {5 M+ k2 y+ @s = s.cat.add_categories('Graduate') # 增加一个毕业生类别, ?, J. b4 a' U" Y' D
s.cat.categories9 A! g" s) v2 {0 s5 ^# r
/ K! l+ J0 C* _3 M _& E
Index(['Freshman', 'Junior', 'Senior', 'Sophomore', 'Graduate'], dtype='object')5 K' p# A" u( z) |" M4 h
1
; x7 z) X: z; |! r! H/ ]$ R# [2
|9 n3 {6 B$ m* G. M3
4 i+ Q( X; L: F% x# _4# t* _8 w3 M) L' J) s2 V" I0 _
remove_categories:删除类别。同时所有原来序列中的该类会被设置为缺失。# e7 @; Y) m. y: x- }
s = s.cat.remove_categories('Freshman')2 X/ Y' K; ?! F t( W4 z
3 I1 a9 ?- K1 B2 {
s.cat.categories) t5 e; X/ z o! k8 D8 V4 x
Out[13]: Index(['Junior', 'Senior', 'Sophomore', 'Graduate'], dtype='object')4 I" x. n% M' B3 [# r; Q- p. a
1 Z$ K8 }8 ~. w5 {6 h! fs.head()& j9 V2 v3 L+ q' Q0 |
Out[14]: % W- U5 S) N4 Q8 ]$ F# O
0 NaN
; f# ?) Z" N+ G7 f1 NaN( s+ C9 g, S$ B# l& A
2 Senior
7 m5 G9 z+ g- H' i" K$ l3 Sophomore
; W" I2 g6 a( j, \. e3 L4 Sophomore
, h1 {( c5 }2 D, s) \Name: Grade, dtype: category. U4 E$ G- [/ o
Categories (4, object): ['Junior', 'Senior', 'Sophomore', 'Graduate']
0 P: E2 v7 |" G% G. X/ u e' y( }% a1
' G0 d9 F5 z3 }' O8 R2. S2 e0 t- V g' P" C
3% {2 J4 I. v# B! I% S$ F
4
3 j. e( O0 T' N1 \( P9 A/ ]' j5, ~! V1 L7 }% u7 E+ M: D8 W$ g3 g
6
. x1 I! \; i6 ^- b) G/ P# x7+ G: G$ @; V7 q% X/ g! l
8$ [( }0 _1 Z) @- ~1 w1 k8 ~4 r
9
% `4 q% H5 h0 w1 p109 b( S5 }! W- u' P$ m. Z; V
11
7 j) i+ z T7 d' | Y7 F12$ f1 B& u1 j( y% H) N: \2 ^4 N
137 X7 B, O4 V6 p. v7 w0 @; |" I
14
* Q3 Z- X" i) }. Y% oset_categories:直接设置序列的新类别,原来的类别中如果存在元素不属于新类别,那么会被设置为缺失。相当于索引重设。
4 u; @0 m& T/ |) q2 \7 os = s.cat.set_categories(['Sophomore','PhD']) # 新类别为大二学生和博士, B% t7 z. c6 u- Z: c- M) X
s.cat.categories' K8 N0 [0 V1 ?5 F r9 \
Out[16]: Index(['Sophomore', 'PhD'], dtype='object')9 L; p" F" [! ?- |; J3 ^6 h" ]
2 W7 v4 _. h1 S* ~& S+ ns.head()4 R! \ M S1 ^) y
Out[17]: $ F2 ]% t0 q7 F( O
0 NaN
6 Y0 f+ r2 ~, y A0 ^1 NaN
6 N0 c; \! F3 b+ B/ p- O, ~2 NaN& a" E2 W; X; `% }5 e- J
3 Sophomore
A/ w2 o4 ~8 X0 P( p- `, V9 a4 Sophomore
2 s$ t# n; D7 rName: Grade, dtype: category
' E: G! w( r7 b& e$ u7 Y* \+ l+ v% PCategories (2, object): ['Sophomore', 'PhD']( z% [2 `* {5 ~5 o6 ]
16 ]4 _+ y$ @& M# _: E
2
# v% P* N$ V5 w, H* p/ Z- p, E* g38 U6 h0 ~1 } J% q; P7 A
4
; b7 w0 _! G7 z8 {* g3 H5
: W2 ~ F K. }# X+ ^ v61 ~& ^& s$ l/ q7 F+ k, ` d
7
2 u9 P5 d, V5 D1 _* B! l4 t0 {. L8
% x9 S1 i0 ]2 t! N8 n3 D9
2 k! r' [- j! B! y9 l$ T* @" S10
6 D" q+ o6 Q. y11" r7 W; `! @* h0 D! {
125 G0 {$ l8 a9 Y3 I" m
132 V9 @3 @! n6 R! [# d
remove_unused_categories:删除未出现在序列中的类别. O2 i0 p5 N2 \) ]. g
s = s.cat.remove_unused_categories() # 移除了未出现的博士生类别# l- ~; o) J# M4 ?" R- H; D
s.cat.categories
6 X" r9 U: p, {% s% P" B# G1 ]" L( Y# [% s" @( a4 i2 s) ^# n9 R
Index(['Sophomore'], dtype='object')
7 s: P& R! K6 @+ v: p8 G z1
9 k6 T; J0 J* N& }+ m/ d& p) A2
6 h8 }; s9 l; } A3
( [( v5 g: U; M2 y7 s8 u4- X% o! \' j$ j$ z% K- |, e! G4 M
rename_categories:修改序列的类别。注意,这个方法会对原序列的对应值也进行相应修改。例如,现在把Sophomore改成中文的本科二年级学生:
. M& I8 B# @. l- h- G- es = s.cat.rename_categories({'Sophomore':'本科二年级学生'})
4 i! t2 E# h3 Cs.head(), _9 k, e: Y% _8 Y9 \
+ W6 d; ^) t. k7 _4 U3 Y0 Q0 NaN, M% S3 D7 g0 E( r+ z
1 NaN
1 d+ \- E9 G; u2 NaN
! {. E/ Q% w6 H' j( r8 ?3 本科二年级学生/ n* N6 j; i- ~0 j p7 H" t
4 本科二年级学生
$ I- X! O/ d2 ]Name: Grade, dtype: category
2 `% J4 ~- G- R; oCategories (1, object): ['本科二年级学生']
, n: X8 H( U* b, B# M1
2 f, I- T3 z) i, H2
. S7 S0 ^0 w$ o* C& f3 X& B3
1 x6 Z5 F! S" \; y8 t2 P4" T- E6 Q8 p+ X+ O
5. _8 t/ ~* u; v8 Q
6& Z& _. n; N0 i) l7 a
7& Q$ a# K. O# O
8% }; y# K* o' m1 `% h3 c) p: Y
9; c% `" M6 m" Q6 w1 c
10! |# a# H- m$ Y; |6 Y) ]4 P
9.2 有序分类
7 k9 m) b1 X3 e* g9.2.1 序的建立
% U6 ~& Q- b/ L& [" q% B$ i 有序类别和无序类别可以通过as_unordered和reorder_categories互相转化。reorder_categories传入的参数必须是由当前序列的无序类别构成的列表,不能够新增或减少原先的类别,且必须指定参数ordered=True,否则方法无效。例如,对年级高低进行相对大小的类别划分,然后再恢复无序状态:
9 U8 k! @1 I+ n' }; `+ Z7 z6 _. {- Q9 ~' ?- F2 R' I/ N4 |
s = df.Grade.astype('category')
0 n' Q. y" }8 h/ O4 c. w. e) p3 a7 Ss = s.cat.reorder_categories(['Freshman', 'Sophomore',
, m8 ]# H) M, x) J 'Junior', 'Senior'],ordered=True)3 `* W3 @( ~: z0 ~: a
s.head()
0 h) b" s! \$ S5 d- |* X+ E: f& N5 wOut[24]:
0 b2 x- q, o. J: m3 U& ~/ Y0 Freshman
0 K% l. t0 T, l% A1 Freshman- p a- e* A a4 b7 V& c0 A. L
2 Senior. [- b) J, o( @* g2 m* F
3 Sophomore
# g [. ~4 k3 F1 u/ }" w4 Sophomore
" @* Q3 X5 G: d0 {$ t! T& e- t# AName: Grade, dtype: category
4 f/ C9 t3 y4 K' H; @ \5 VCategories (4, object): ['Freshman' < 'Sophomore' < 'Junior' < 'Senior']
, M& ~" U+ W4 w
/ F9 A% w0 O' v8 R/ u: V! _s.cat.as_unordered().head()3 [, D- E2 N6 w W9 P
Out[25]: N4 i0 q. R# U( p) L) P* O8 z
0 Freshman, |1 z, Z. \; c1 H: j
1 Freshman
* h5 X6 F( i% w' Q" y2 Senior
; f3 T4 q# ?+ X% p, A( j ^, O* S3 Sophomore3 v7 M/ O8 d2 q3 z( o) r* a* Y1 u; H
4 Sophomore9 x0 y' I. a3 W6 U% c. c# _
Name: Grade, dtype: category4 h5 ?* i5 j$ Q4 s# }! M2 G
Categories (4, object): ['Freshman', 'Sophomore', 'Junior', 'Senior']2 l, h/ y9 H; a* A
. d# X' m! p; t( x" i0 ]
1
' F8 L& V1 z1 R* ^2$ v+ N# ^% J4 R, x! a+ J
3, z# E2 e; y$ w! v/ b8 |* s, p
4
1 W% E W$ ?1 P6 s5- r- D! ~5 }. u4 n
6
, z$ n9 O" R% [% T3 D7
5 i" [7 j6 b# ~9 q# Q4 e' R$ T8
7 f" @) J/ X3 \* A% X& d+ @9
( R, i$ i& C, a9 J7 U% t10# Z2 S0 \0 B4 ]* H1 k
11
: h7 d G2 Z8 ?12
( g" v9 b$ w6 @: N8 |; c13
" o u: i# I# v4 i14
7 q0 j \. E3 o. W' i3 k159 |' i: W" j4 T) M, O
16( v; A( a% A8 k. P
17: n( r9 R* K; E
185 G5 E' [4 h2 D9 Q$ Z* T3 p4 O
19
& U7 w- o# F* U+ b$ t7 B20
7 i: \7 }, a8 h4 S( A* Z21
) A! |" t: l" f8 F. V22
8 p0 w/ D5 Q; E- l! @ 如果不想指定ordered=True参数,那么可以先用s.cat.as_ordered()转化为有序类别,再利用reorder_categories进行具体的相对大小调整。
% u5 I0 X5 `. | Y7 t$ y* c. b! i" g- V8 L1 {, k8 T
9.2.2 排序和比较
; O* C% L/ ?0 h8 \# ^* ]在第二章中,曾提到了字符串和数值类型序列的排序。前者按照字母顺序排序,后者按照数值大小排序。
, T) ]6 V; t. l. M: k, \
9 t! l; ^8 s7 Y2 O: `9 o9 X 分类变量排序,只需把列的类型修改为category后,再赋予相应的大小关系,就能正常地使用sort_index和sort_values。例如,对年级进行排序:% o( f: S2 Z* X. }9 u& ]
: ~$ A1 c( W# Y; Odf.Grade = df.Grade.astype('category')
+ _. P3 e" P6 X% |; g* Pdf.Grade = df.Grade.cat.reorder_categories(['Freshman', 'Sophomore', 'Junior', 'Senior'],ordered=True)
" a6 u* f, O0 H9 h" S& P) ydf.sort_values('Grade').head() # 值排序
. T% }+ E0 Y" c2 dOut[28]: 6 _' F0 D m: ?$ p+ J% P
Grade Name Gender Height Weight5 ?7 L+ y' s" O# P! R" K' Q
0 Freshman Gaopeng Yang Female 158.9 46.0. t7 I6 y0 W" M* Z; Y9 {
105 Freshman Qiang Shi Female 164.5 52.0
0 ~3 B# J* p$ B' \7 v) u7 o96 Freshman Changmei Feng Female 163.8 56.0
, G2 {8 P2 d3 H# v88 Freshman Xiaopeng Han Female 164.1 53.0; m G8 y1 c9 s4 | F/ D1 p( j
81 Freshman Yanli Zhang Female 165.1 52.00 d, Q+ ]* e2 M6 U6 p& A! v6 l. [
# @$ }6 S! j. t& K, p1 t* R, Wdf.set_index('Grade').sort_index().head() # 索引排序
) i+ h5 |) j* j7 F5 v! G7 xOut[29]: 5 h. r ~8 O& G, X8 J# u
Name Gender Height Weight9 ~6 P+ A) F7 x: W
Grade
; U: S. }& w) T# P$ I6 V- }! eFreshman Gaopeng Yang Female 158.9 46.0
. m* I; m$ f* `$ U {Freshman Qiang Shi Female 164.5 52.0* O. \ ]3 N! h R) p
Freshman Changmei Feng Female 163.8 56.0) y* i8 |& {/ X j, R% e) Z
Freshman Xiaopeng Han Female 164.1 53.0
# r$ {6 z: n3 i& R) g5 VFreshman Yanli Zhang Female 165.1 52.0, ?: w" T6 h/ x
# A# x- R5 y) s o1 r1
% C8 h6 f) E$ i* f, ~; _3 S26 ?8 e+ D* T4 F, m
3
% @$ i; M3 b# y3 f2 H4 j49 E$ H* e# }/ _! c/ X/ b* m: G4 j
5
~; {: x4 W4 G5 e- K/ |6
: P4 O) t7 l/ r7 v7) c6 c# l/ p* L( u H$ V o
85 F8 g6 f. E' m2 j+ ?2 t8 F
9# h4 o2 U' k& e" p% i# V$ r$ N8 g
10- q; _' V2 X- J7 z5 l$ N5 c- u
11
& t8 `7 I1 ^# Y* d3 v12
: R0 M5 ]+ `! p13
6 `" K; @1 Q1 ^" R; V; r14
$ C9 _. O" X5 t/ B. |! e3 ^. b15
$ E# Q t. z' @8 G9 o. ^16
5 U7 U/ E3 _9 |& L! {17
4 u- b+ \; m' D$ G' n. L18
( U4 G1 e1 G5 g/ A+ R/ z! e7 f D. T19
2 Q7 p7 W9 I6 g% i20
h' ^& B: z$ m8 D 由于序的建立,因此就可以进行比较操作,方便后续索引操作。分类变量的比较操作分为两类:
( A' i* Q, f) l1 ` a# e3 Z2 J" b K7 f c, ]
==或!=关系的比较,比较的对象可以是标量或者同长度的Series(或list)。(无序时也可以比较)
- x4 k" S, Y- R2 O3 V>,>=,<,<=四类大小关系的比较,比较的对象和第一种类似,但是所有参与比较的元素必须属于原序列的categories,同时要和原序列具有相同的索引。
+ v6 P3 r# I/ N9 `- Pres1 = df.Grade == 'Sophomore'8 A2 ^, R) b) x
" l* I% z2 }2 ^- \ b9 u
res1.head()
! R7 y! l$ C( f0 COut[31]:
3 f% o, M" W. ~7 a0 False+ |& U5 Y+ {( S9 b h1 D% Y2 C
1 False
6 t. U! ~! D: j. [6 B2 False% x# N- P4 L4 [' d4 G0 e: G1 D
3 True3 k- m, e7 [, f: F* y
4 True8 V k! A* S X+ Q, B7 L, P6 ]
Name: Grade, dtype: bool
( A5 M7 E7 P) m3 w! ]$ J' t3 q" P# x, z- p/ M& }
res2 = df.Grade == ['PhD']*df.shape[0]
: n8 T* L4 U$ h- W6 y% M3 t6 O _7 @+ c: _2 u6 J8 b, K
res2.head()" V( I7 w' C4 g3 f; {
Out[33]: ; l2 |8 ?$ v8 W' y1 ~: x
0 False
; E$ }' r1 M. Q" |5 w* J! B% x1 False0 u6 r2 t. X. V7 y
2 False
2 Z) f% p; X7 u% m, P3 False/ r# [6 W, a& ^9 v0 ]! A! m8 {7 N
4 False% R7 H) j/ B; z
Name: Grade, dtype: bool
1 D% R/ e% N( N v( O) U; h! {
8 d3 u* m' c8 [res3 = df.Grade <= 'Sophomore'
" C3 M% Z+ n9 M
6 u& v) w" l4 z8 M$ Cres3.head()
% \" b2 |/ @. Q$ dOut[35]: ' T- I2 x0 Q! t) `! e* f6 c
0 True
% M3 T) n# o* y* m" |1 b, _1 True& f9 L0 j8 A6 i3 ^) |' G
2 False
) I2 ]9 q& o, ]9 P# I: P3 True
# j# A) F* E$ u% q4 True
, q6 ]- i# F8 j! N: F! yName: Grade, dtype: bool
( t. Z8 Q6 Z$ i8 @$ a- P! o7 s1 A
! w* C. H7 n) M% W# sample(frac=1)表示将序列随机打乱。打乱之后索引也是乱序的,直接比较会出错,必须重置索引。* v0 c8 Q. r) b! R4 ^& p# N2 J# e
res4 = df.Grade <= df.Grade.sample(frac=1).reset_index(drop=True)
7 j" Q6 |9 h0 y0 E2 S; N3 k6 i: o! `9 ]4 X3 B! t* @
res4.head()- e8 l/ L6 d# `9 M0 K
Out[37]:
2 n2 l5 P. e4 z/ G5 Y: ?! w0 True9 [- o8 V+ h4 t5 W/ U5 O" C1 V2 j
1 True7 H; m9 L3 v* e6 I
2 False$ z5 u2 K* N- c* V& E( V7 D
3 True& H8 t; `! C4 s, \
4 True
8 ~ R1 ~) W! l" [- sName: Grade, dtype: bool) p6 v* }$ v, N! b
. h8 V4 p+ L: v$ G6 A) t' p4 \3 e1
( K' V$ a. M) F$ ?" F6 x2$ F* q' L6 l7 T, K- U, {! b
3; _3 [% E- x, E7 q( q" j
4' [ C3 U5 R& ?6 |+ ~' X
5
: f& i$ k9 H! X9 ~( a9 z6
6 d5 K2 z3 E& t1 E: n5 A7
9 [- A7 H) M" W( K7 X! D8
+ u7 }- J& u. N d1 x9
: N! U* `+ O9 B: g6 G1 {10- `% v x" ^# b3 D6 Z) [
11% D+ r3 @, T" _7 |
121 Y+ o/ j6 v* @% Z4 T G7 ^/ Y$ x3 u9 j
13
. k z' N* Q4 ]# E; | _6 w14
6 R! O- o6 `, _) s1 X# h$ o# U15
# T( ?2 M& L* Z% G. l16
. b; v& j" W' v2 j+ o& T2 J$ ~" {176 F6 ~- l' c! d
18. M2 U0 E* ^4 D& w( ]6 H
19
7 L" F% E. j( x) _9 e. j% @: J20; {9 {( k. R) `6 x
21
5 ]5 `& o" E! T22% N) B. O6 D0 p( p- {" _
23
6 P9 W2 Y/ F4 }9 w$ d7 D243 k' Q2 Y8 F+ r2 D+ j
25
$ o7 z5 k* V- \1 M' a R+ ]! y26& O# p2 {0 I6 @' ?
27
: Z2 a2 j* h$ ~1 H6 i. h+ y8 O& ~28
- y/ }) I# P( o29/ C: B6 ~9 `8 Y2 B
30
- x! q! \% W6 J: a& V319 ]7 a; n4 [6 z7 T/ K$ o6 P( S
32" d# y4 v& _' s$ H" i$ {
33
( H: ?- O+ z9 V) K$ Q8 R% R! b9 p342 ^* @% A8 W1 W& _$ h
35 g( T8 E9 p) W D/ X4 Q$ F: e
36- ]) k N N1 I: k- Y6 J
37
8 t* R: g! [( a7 R% p- ?38
8 b5 [5 @2 t% s8 }395 J; x: \" U* Y" m) N; Q$ s3 k
40: ^7 [2 F/ E. H" Q6 v' F. ]
414 p6 e& e2 M3 }3 f5 r! N' i& P
42
3 K; k6 J/ l1 x43
k' ?% g: _9 f C44/ W3 b; @4 y5 }
9.3 区间类别
' U8 S; ?& U7 B7 c3 o9.3.1 利用cut和qcut进行区间构造4 q/ a2 L. u% n |; r3 r) @
区间是一种特殊的类别,在实际数据分析中,区间序列往往是通过cut和qcut方法进行构造的,这两个函数能够把原序列的数值特征进行装箱,即用区间位置来代替原来的具体数值。
1 ^9 R2 i$ x' [- z( M4 p1 n7 A6 a7 ?0 r8 ~( t! N2 G, n0 V1 s
cut函数常用参数有:: S6 E5 ?* U/ ?& d: s
bins:最重要的参数。
- q1 I0 N' U6 b9 Q( I& c. }- D' a( X如果传入整数n,则表示把整个传入数组按照最大和最小值等间距地分为n段。默认right=True,即区间是左开右闭,需要在调整时把最小值包含进去。(在pandas中的解决方案是在值最小的区间左端点再减去0.001*(max-min)。)
2 V5 l. D, `4 l: x S" h也可以传入列表,表示按指定区间分割点分割。; S" c w9 d2 I c, I: D
如果对序列[1,2]划分为2个箱子时,第一个箱子的范围(0.999,1.5],第二个箱子的范围是(1.5,2]。3 ~. K; w, k3 Y1 J" O9 T5 ^8 o/ R
如果需要指定区间为左闭右开,需要把right参数设置为False,相应的区间调整方法是在值最大的区间右端点再加上0.001*(max-min)。" C* j$ m' Z$ d w; B. a
8 t# R& S% F% A# ^9 M
s = pd.Series([1,2])2 m4 I f7 K& x+ I1 U0 X
# bin传入整数
! n. o) K# e" T2 X& L; g, W
* i7 e6 k! g3 Z3 B3 P! Q1 dpd.cut(s, bins=2)
! h0 p2 D* C: o& v/ B: nOut[39]: & S8 Q+ `4 z1 [. {. _
0 (0.999, 1.5]) w8 W7 i: ~- p: o, q( W
1 (1.5, 2.0] x( G9 Q9 [- ]3 N7 R
dtype: category5 t: k& l& K/ e. |7 h8 Z1 l6 e
Categories (2, interval[float64]): [(0.999, 1.5] < (1.5, 2.0]]
- r% I7 U5 f' W; ~( e6 V3 I+ e. Q, |3 z) t8 V
pd.cut(s, bins=2, right=False)/ Z1 T! e8 p( f% ~
Out[40]:
$ q3 i+ G) K) ^! L, X6 X0 [1.0, 1.5)( k) _- X( R- O
1 [1.5, 2.001) Q; l7 I5 B! w/ {8 i0 I* f
dtype: category
h9 b2 ~/ z8 q: ECategories (2, interval[float64]): [[1.0, 1.5) < [1.5, 2.001)]
6 G- @- g. z: Q* K O. N$ J3 L$ }- a1 d. j
- {# S5 N) w8 j; T# bin传入分割点列表(使用`np.infty`可以表示无穷大):4 L- E4 K- F6 g
pd.cut(s, bins=[-np.infty, 1.2, 1.8, 2.2, np.infty])
7 U# O7 f. z8 V2 \: J0 g- s6 gOut[41]: ( H4 x5 ~- L- l F
0 (-inf, 1.2]
/ c) @% X {+ O' C/ L: F: _2 l; p1 (1.8, 2.2], Z: ]2 |# E% Y& m# O
dtype: category+ I! n3 P$ u8 y D( l3 u. d
Categories (4, interval[float64]): [(-inf, 1.2] < (1.2, 1.8] < (1.8, 2.2] < (2.2, inf]]) i& X! R1 H( F8 c6 H# m4 K
/ `8 y6 C: g4 S- b. |1: P P0 U" o( n6 u. Q r
2* E9 x1 {& T( i6 t
36 u B7 Q$ L8 ?6 k0 U' ?
4" R% C1 W# a8 F) a; s
5
& p$ c" w2 u/ V5 H* C/ J65 k% H" q& x: |% U/ K
70 y$ d% }, V& a8 j, X- t& p
8- l# K% X- K% P9 v" U3 E+ K. D
9
$ ]- `' |% d/ g, O+ n9 y9 G10; m4 h# q) F8 M! n* x
112 b6 e7 w7 F6 b5 p( U4 o
12
+ m3 g- q( H+ {" ]0 p13; c* U7 |" p) a7 s8 O" N2 W
14* `4 X5 {9 i1 V7 g- b
15
0 R# |0 f! n$ t+ `2 c' L16
! [. g% X# c# e6 Z17, p3 t6 ^" z0 t- s
18
) z3 h/ j! @, w19
; I/ s7 `3 z& C; {6 M5 x5 c2 A20
' l5 L' a( S( P9 o# ]6 k0 v6 V$ l21
$ w* ~5 `# i9 q22
; P% a" K! T1 w( N( E23
+ Y6 N# i! H+ t; f2 P% v3 O24
# ^) l8 ^/ a4 {* w3 Y2 \$ ~. Q25
. J4 l! w+ u4 X0 dlabels:区间的名字
! ~5 D8 I! B& r- Oretbins:是否返回分割点(默认不返回)2 z# ?' N1 L X9 o
默认retbins=Flase时,返回每个元素所属区间的列表
M( y3 A% A. P- H7 I# Dretbins=True时,返回的是元组,两个元素分别是元素所属区间和分割点。所属区间可再次用索引取值0 T$ _% O+ _2 j6 h6 O! \' G: S+ S9 z
- P: f& `* b! I$ i
s = df.Weight4 S3 v; V9 T& {
res = pd.cut(s, bins=3, labels=['small', 'mid','big'],retbins=True)
. r0 ~% s" B5 i/ k/ t; Eres[0][:2]: i) a6 ]6 n. H0 f, t+ m$ q
/ ~8 n' W7 m' F i0 lOut[44]:
7 p: M( V; g9 J" g' ^6 w( R0 small
8 J8 J7 L& Y& c1 big4 V3 i0 T$ @: o9 K4 o
dtype: category+ y& q! U# j/ M m* N. W% O
Categories (2, object): ['small' < 'big']+ J+ v. o% Q" T; w8 y; O
2 e+ c0 z) o0 m. P- N; ^
res[1] # 该元素为返回的分割点9 m) u% p6 V4 `& P
Out[45]: array([0.999, 1.5 , 2. ]). S4 J3 x& m' F, n. \3 q( e
1# z$ |8 D8 F! P3 s- e1 f8 _
29 U: n+ K4 M0 _" j
3
. v+ I$ a" A8 S# `4
, {9 p! E: R# b$ r9 z" g5 p! ^5. L9 N. P- C$ Y; m/ |! Y
6! k5 K" F5 P- Z' x! s: w
79 J5 H1 c' O& b* `% b
8
0 c3 B, ]) o# I/ q& @4 I8 {. W9! _9 g. K6 d) t0 |, E) \
104 q/ a V( S/ R& ~# v0 @1 M
11
1 z' ?. W& `$ @! Z+ N; h( c12
{+ |& b4 z' H' J" {qcut函数。其用法cut几乎没有差别,只是把bins参数变成q参数(quantile)。& \' C" \+ r$ c; C4 {
q为整数n时,指按照n等分位数把数据分箱; e( C/ ^- b* P$ z" A
q为浮点列表时,表示相应的分位数分割点。
# ~* ?6 W* r5 `s = df.Weight5 I- s* D' Y8 X+ J2 I
/ P# l$ T2 T" [" B- \
pd.qcut(s, q=3).head()8 R) ?* z4 @( X1 l# N9 I8 J
Out[47]:
8 H9 m) i+ ? a0 s6 f0 (33.999, 48.0]5 |- h; L8 A1 }4 |' O* A
1 (55.0, 89.0]
3 Q* p& m' j6 g: n: `0 d2 (55.0, 89.0]
3 v' Z! A( t q+ J' G" Z: e3 (33.999, 48.0]
B( N" V+ F' d2 r4 (55.0, 89.0]2 z% D D. x+ m( ?- `# I
Name: Weight, dtype: category
7 i/ ^! [* p: u Z6 g* s3 R JCategories (3, interval[float64]): [(33.999, 48.0] < (48.0, 55.0] < (55.0, 89.0]]
E7 Z. v; k) l9 F: ?7 H8 \" x, ?. O# T
pd.qcut(s, q=[0,0.2,0.8,1]).head()
, t* z" g: u, UOut[48]:
, Y9 s1 ~# z5 z3 ]8 x0 (44.0, 69.4]* b5 f- q B* B6 Y: B3 l4 P c! J
1 (69.4, 89.0]
+ M& A7 f: J9 L4 H9 w/ x. F9 z* |9 \5 K2 (69.4, 89.0]
; l U0 \' s# x6 ]$ a3 W3 (33.999, 44.0]
0 j1 e2 b5 G9 A: w$ D8 C4 (69.4, 89.0]
5 V' b3 z* R$ d/ KName: Weight, dtype: category
% G' Y6 V& G( |' _+ M8 OCategories (3, interval[float64]): [(33.999, 44.0] < (44.0, 69.4] < (69.4, 89.0]]
& L6 \# `! a i3 S
9 U& {5 K) v4 ]$ i2 r) `1% X$ ?% y, y7 I: P" B( T
23 [: x! e" A5 G9 l8 |2 t
3
0 j8 Y% T, ~, u4( l, R! t( r$ l2 y8 E
5. u0 s1 e1 F* X* z1 T9 H
6
: B- w4 u2 @8 n# k, Z7
5 m: |! s, ~! n o8 \6 U$ j8. g( r& @, x% x: L
9
7 m6 n. O6 R& p( |10- `9 X2 M6 a& L
113 F M3 E3 `% M* @
123 W d7 ~/ P% }; X. u+ v& p: j# w0 G
132 A# a+ {! a% \
141 q/ k' ^& `* a8 {+ m% a
15: O! P6 N0 Q" m
16. @7 `# e1 e& J; r
17# d: _, g, N9 c. w. Q/ r, P
18
0 E; L& k; {# u( k; X" A19( ~3 F3 x& M+ P. } ]- y8 N
20: U; S. ?+ i! r! {: s# c* E
21
# G$ w A# t3 f5 O2 E* o6 F9.3.2 一般区间的构造
1 {& L E; A( H0 k) J4 i0 j pandas的单个区间用Interval表示,对于某一个具体的区间而言,其具备三个要素,即左端点、右端点和端点的开闭状态。9 C- v6 T* D+ {4 \5 v4 y; j
" O0 O$ X0 c! |1 \% Z8 }) s2 K }! q开闭状态:包含四种,即right(左开右闭), left(左闭右开), both(两边都闭), neither(两边都开)。
2 @) l5 d1 i9 e; o2 {& [my_interval = pd.Interval(0, 1, 'right')
: D L B4 a# P; w" A: n/ e! y# S3 t Z0 J
my_interval
9 b- t' o9 A* [: v+ K: nOut[50]: Interval(0, 1, closed='right')
% H$ @" l0 v% U. Y4 i' G+ P1
. [0 p, n* y4 j22 D' u5 c/ Q4 P+ x( S! l' B3 i7 k
31 H) A9 S( W: W/ }
4
8 S1 ^: Q% p+ h( t7 W1 i H区间属性:包含left,mid,right,length,closed,,分别表示左中右端点、长度和开闭状态。2 v# a. }5 |, q6 i6 K# ^- Z& f. K
使用in可以判断元素是否属于区间5 k# ^1 d8 k+ {1 r. d
用overlaps可以判断两个区间是否有交集:
0 ?$ n/ N/ s& v1 P0.5 in my_interval
: {+ o: S/ R' L8 \
! b3 {5 L) O" ?4 b: F( @True, c- A" ^6 U: h% [% Y
13 B% n, U& }+ K, ~
2
2 z; \0 v; E9 u3
$ e$ @! C* t; Imy_interval_2 = pd.Interval(0.5, 1.5, 'left')
9 }$ P3 I5 L+ K, d7 a$ xmy_interval.overlaps(my_interval_2)
2 D0 z6 s* G6 n# h: M8 H
/ H( `- [" w b4 R. C ZTrue
& W* s) c1 F3 I# ]* J1
8 \+ ]* T4 I2 d6 E1 `2
3 P' `; }& w: l3 d3
3 k9 W" d* `5 k0 ?& }1 G5 t5 y+ ^* b4 D4- R; K! D. A$ z+ D2 U$ a& N$ ^7 t3 J
pd.IntervalIndex对象有四类方法生成,分别是from_breaks, from_arrays, from_tuples, interval_range,它们分别应用于不同的情况:
1 q8 R; |5 b# ]4 q
: w3 |$ z! i4 }0 efrom_breaks:类似于cut或qcut函数,只不过后两个是通过计算得到的分割点,而前者是直接传入自定义的分割点:
. C' ~7 Z! a- K$ k7 L7 X) Fpd.IntervalIndex.from_breaks([1,3,6,10], closed='both')
5 Y% f5 V, F3 t4 W
$ y8 }; o9 w8 r+ ?IntervalIndex([[1, 3], [3, 6], [6, 10]],/ f2 y: r0 m& V
closed='both',2 h; W) n: ~6 ?2 e6 V
dtype='interval[int64]'), Q) ?( i/ ?9 {# `( X1 T
16 }2 _8 |7 [3 Z3 |* e8 W' L7 v
2% k. |. B1 i/ j; T
35 C: X1 ?" w- X! Y2 U4 t6 h$ R
4
& o8 ^2 D. N3 n: H5 M, f! m5
6 `. M x, P4 {' Nfrom_arrays:分别传入左端点和右端点的列表,适用于有交集并且知道起点和终点的情况:. @+ w) L' i& p/ l; L) F
pd.IntervalIndex.from_arrays(left = [1,3,6,10], right = [5,4,9,11], closed = 'neither')
K9 }# G- P5 ~. x k
; l( T1 `. \. c) d! A _7 ?8 ]IntervalIndex([(1, 5), (3, 4), (6, 9), (10, 11)],9 Q- |+ n+ `$ Q& e' l
closed='neither',
4 J `' d+ g' q1 h4 h3 P. l2 ] dtype='interval[int64]')0 P' y3 n, C7 a& a1 J a6 Y
1
6 x- b# G0 l) B- q2 L; X- ]9 @! D7 D s/ _2 N( @3 p
3
; b$ |! X% J6 ^# M# t4
& I% f5 \9 A$ T5" c% _. M! E5 M3 H" u0 ^8 H7 {2 W
from_tuples:传入起点和终点元组构成的列表:- f8 @! S1 D4 b" c9 M$ i
pd.IntervalIndex.from_tuples([(1,5),(3,4),(6,9),(10,11)], closed='neither')
, ]" X" h$ O7 p8 z& {! g% x6 @+ X# q! e! k
IntervalIndex([(1, 5), (3, 4), (6, 9), (10, 11)],
, s+ d' R) U% |( C! ?* y9 _2 b# T closed='neither',
0 e8 c3 F, U" [3 Z$ L7 B# x j* k! S5 q dtype='interval[int64]')
7 n+ q0 k4 \# D- w% G. {16 Z8 c: T) N& f! O" M6 N5 u* W
21 d+ v% s- A6 W$ P8 ]7 R
3
0 i7 k Z( Y3 u0 \: T4) x$ n7 B9 ^* i# x( V8 a; |
5) G& y- F* m8 g4 D8 A3 K7 l7 X
interval_range:生成等差区间。其参数有四个:start, end, periods, freq。分别表示等差区间的起点、终点、区间个数和区间长度。其中三个量确定的情况下,剩下一个量就确定了,从而就能构造出相应的区间:% k# B/ f. G2 m/ a" [& P
pd.interval_range(start=1,end=5,periods=8) # 启起点终点和区间个数7 l$ D6 b; W7 k* C6 V
Out[57]:
8 u( w; H1 e2 x BIntervalIndex([(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]],
+ m [0 b* |" c! J: A closed='right',
4 l0 l; r: a1 F! @6 J, A/ f dtype='interval[float64]')
" v/ @" P3 b0 a7 P
) m2 g0 ~, b7 n7 `4 Kpd.interval_range(end=5,periods=8,freq=0.5) # 启起点终点和区间长度$ v) a6 Q" k. w G) H# w2 a5 |
Out[58]: 6 n: M: `. E6 a) v2 S% q
IntervalIndex([(1.0, 1.5], (1.5, 2.0], (2.0, 2.5], (2.5, 3.0], (3.0, 3.5], (3.5, 4.0], (4.0, 4.5], (4.5, 5.0]],
% [' w \+ d$ {, R3 w( v! P closed='right',+ j1 J2 w* N# W- f
dtype='interval[float64]')
3 N6 U% R) o" @3 b+ ]- I1* \ y/ l% o1 H% B( i8 S
2! Y/ Z' y% ^$ s y
3
+ _% w+ o0 E) ~( s ~47 O2 C( S9 c0 n
5. i3 r+ T. C- o
68 J7 Z- H3 g. z9 j' ?) _& F5 S# y2 c' V
73 P; a, y' ^4 h% g e& Q+ X2 o
8
- t/ ~) q6 e/ ?1 M9
$ a; U1 F- Y# _/ G3 M5 u105 w) p$ J6 B* B6 }
11; A" M6 e; k; |7 [$ L6 t# Y* Y) l
【练一练】
: U9 V# Q+ G6 _, ^# N, R- e/ ]- Y 无论是interval_range还是下一章时间序列中的date_range都是给定了等差序列中四要素中的三个,从而确定整个序列。请回顾等差数列中的首项、末项、项数和公差的联系,写出interval_range中四个参数之间的恒等关系。+ z/ P' w* k$ j: \% [/ v7 t
* I! J8 t7 Y1 f2 B
除此之外,如果直接使用pd.IntervalIndex([...], closed=...),把Interval类型的列表组成传入其中转为区间索引,那么所有的区间会被强制转为指定的closed类型,因为pd.IntervalIndex只允许存放同一种开闭区间的Interval对象。. t+ B L/ i+ \9 r- S. o
8 h+ q2 i9 E/ o) m
my_interval3 g6 _: ]1 {$ n% @" w. I
Out[59]: Interval(0, 1, closed='right')
$ b% |1 r4 r/ [* I: z! ?. q- Z3 A8 r- I1 ^+ [8 A# d( A! I/ s9 Y f
my_interval_2
: {$ S* e Z! \& Z! `8 f# yOut[60]: Interval(0.5, 1.5, closed='left')1 b2 t& t+ G! m. x3 g
0 {) \5 K! K3 u. d3 a y3 P
pd.IntervalIndex([my_interval, my_interval_2], closed='left')
5 [0 C5 w1 l$ ^% c8 t7 Z" `Out[61]:
3 {+ Q1 C) E/ \* O% l' hIntervalIndex([[0.0, 1.0), [0.5, 1.5)],( @5 O: c6 Y/ |4 V; v
closed='left',
$ n7 \0 m/ ~7 T, ^4 P# V dtype='interval[float64]')% E7 @4 h# O3 X2 X: Y- F5 f$ G& J
1, ?+ O: T; \# p9 a& [2 |2 Q: W
2
! g. j3 J* i. r& b/ `32 R; _2 S2 T* F( w7 A
4
V; L: l! u/ _; l5
. o7 S5 \8 x7 m4 t# j* x6
1 [7 k0 w+ S! C) w2 ^9 S1 j7
& J( q- u3 ?) D% O. @8
& p/ V& M3 k: X; ^. k$ g9
7 _9 D2 \5 O5 ?6 ^& E2 s4 V) e105 j- b& c, c- Y
116 c' j0 z, _ }% V& k/ M0 E
9.3.3 区间的属性与方法! B w( o* Q$ q) s$ X$ D. C% b- |/ c7 x
IntervalIndex上也定义了一些有用的属性和方法。同时,如果想要具体利用cut或者qcut的结果进行分析,那么需要先将其转为该种索引类型:
) s' n+ e( r) X+ ~$ a. C. ~6 R$ ?! a3 G; Z
s=df.Weight; A z4 J7 m: c! @1 i
id_interval = pd.IntervalIndex(pd.cut(s, 3)) # 返回的是每个元素所属区间,用具体数值(x,y]表示) c! C4 U+ ^2 H( j- g c* J
id_interval[:3]4 M4 c1 w, R& b d$ f
6 ^/ w. M3 I- G$ P! P: x" j
IntervalIndex([(33.945, 52.333], (52.333, 70.667], (70.667, 89.0]],) a+ P% x$ u- v5 U3 |! T
closed='right',
5 E6 K2 i/ e- e3 b- O name='Weight',: m1 M# {, ]5 Y8 y$ F
dtype='interval[float64]')
6 Q+ P8 p, ]& I! H6 p15 b8 W+ A2 A! {* H1 O
2
n$ r( ^) O8 L# @3
2 y: q( s) u1 Z s6 L$ a/ ?$ n4
; g+ R- W6 k: I3 U5
8 j+ i% V! O0 e6/ p: |1 z$ W3 X; W! R
7
3 z( a. V- Z" O) {! {0 \! w81 B5 ^$ t* W4 l" i) k
与单个Interval类型相似,IntervalIndex有若干常用属性:left, right, mid, length,分别表示左右端点、两 点均值和区间长度。
! M$ o# J6 i5 M* \ F: g/ Aid_demo = id_interval[:5] # 选出前5个展示9 n! f9 d/ h2 g' B% F* \
$ ^$ _# |: m* lid_demo
; P, ^" y9 d2 aOut[64]:
( N4 X6 D- W4 h6 KIntervalIndex([(33.945, 52.333], (52.333, 70.667], (70.667, 89.0], (33.945, 52.333], (70.667, 89.0]],
* ?8 L$ I3 |. Q closed='right',
: x+ K* g( c& W0 x) O name='Weight',
; q O2 u. q U: k3 O dtype='interval[float64]')+ Z5 k3 y- G6 c" f H% K ^8 Z8 h
1 n0 x6 F7 N: W
id_demo.left # 获取这五个区间的左端点7 P0 f$ k6 \# p! M( @" ]) G
Out[65]: Float64Index([33.945, 52.333, 70.667, 33.945, 70.667], dtype='float64')
( g& V' g% \3 b# S$ y
% P, @* F2 L: ]7 b5 }id_demo.right # 获取这五个区间的右端点
+ A6 @# D7 Y7 R1 iOut[66]: Float64Index([52.333, 70.667, 89.0, 52.333, 89.0], dtype='float64')
' l* {: t4 C4 p3 m
% k. U6 Y L; `1 b2 v0 c9 Bid_demo.mid) [; S" j+ E1 v/ r1 G# O4 S4 a# `
Out[67]: Float64Index([43.138999999999996, 61.5, 79.8335, 43.138999999999996, 79.8335], dtype='float64')
8 ~# Y0 l. D0 S
P0 M2 r: v* p) C/ ^id_demo.length6 {8 i- `2 }; }7 d1 g
Out[68]:
8 a% L: I9 W$ Q3 y2 ~# x# h( e* B4 {Float64Index([18.387999999999998, 18.334000000000003, 18.333,
! t/ A# S: k' ^2 l: {4 t 18.387999999999998, 18.333],
/ q+ m8 S* s8 ] d* E1 j# n dtype='float64')! `$ S [4 S) _5 \8 N
% A# r2 M5 M/ Q2 d2 Y( G0 U
1& S2 P' s8 ?) [' L/ J
2
6 z/ f- y" e% E" M, g3
6 t- l5 M9 V$ B' U4
* C7 j- m" ?$ f2 p7 x( |1 ^55 _7 d' L z- W4 B% a
6
4 U# q6 s1 x6 o. ~& j7
: C; h7 N4 V5 }7 u. Z" K: l. V; j8
& }+ r/ I4 v( U9 V! X9
& L. {! ~; V6 [6 a" q( ]: C/ G/ m104 \$ k" s% b9 A9 i
11+ l8 h9 w! r( m% Y5 r
126 k7 A) U0 E( N9 G8 n' P6 `
13
6 r3 a5 O& I' J3 J+ G% g14
; c8 G7 c& X( c9 J6 N( L15
Q' {! ]( v% j16
' w' C( `0 X7 O0 D* l17
2 @' J' v' m% S' j6 z( H! W! M18- T! E6 G, N( l' K I" j+ R: A
19% t# s' ~- Z; O$ \/ M# A4 O; V& }
20
4 j# d, ^* ^1 z213 R, |% S: h% T! s6 r
22% r ~; A# c" W% o$ N
23
# f( v @- Y; ?# d2 q7 Q1 y# R( SIntervalIndex还有两个常用方法:
8 B# R( D' P- v1 \' H! U. ccontains:逐个判断每个区间是否包含某元素
8 j& \; H) S' B3 ~, S9 Z% u' r0 K( xoverlaps:是否和一个pd.Interval对象有交集。
+ e; d5 I5 ^, C6 K9 S* N( E. p2 Eid_demo.contains(50)
3 F2 ]9 L1 J0 o0 c/ m. W4 yOut[69]: array([ True, False, False, True, False])! F5 j% [1 K" Z7 Q1 W! f
1 K+ t4 r8 d* H; T) P9 T
id_demo.overlaps(pd.Interval(40,60))1 M2 Q0 j: p2 w$ x) N6 S
Out[70]: array([ True, True, False, True, False])" f1 k' a, b/ ~ H$ `
1
6 U: J2 W* c# S$ L! t, S( E. Q+ {, M2
" g; r) w& P+ {' @' [3, `0 v1 s4 A, Z: u$ {) |
4
8 f1 s* J6 M% E$ Y5
6 s7 x+ W5 E' G$ o# v9 n: P9.4 练习* f: p$ }8 F- w( M t
Ex1: 统计未出现的类别
9 p ^3 }' k. ^, ^% {7 f* b: h 在第五章中介绍了crosstab函数,在默认参数下它能够对两个列的组合出现的频数进行统计汇总:' ?4 | [, c; z; z3 k9 {
6 V- }3 x4 _! n/ P: V
df = pd.DataFrame({'A':['a','b','c','a'], 'B':['cat','cat','dog','cat']})) x7 s, P+ B5 w6 |
pd.crosstab(df.A, df.B): b* x: u1 k7 G4 ?1 p: `' |% O
0 l: R1 n0 W: x" l+ I2 _
Out[72]:
1 o- Y7 i0 Q; h5 [9 V+ RB cat dog+ N( x# l! S! S5 M" O* X1 ]
A
$ ]7 r' h; L# X/ Y$ q/ Ya 2 0
) ` H8 z8 X$ q0 o' f; lb 1 0
& O" |0 Y7 s2 ~* E$ {c 0 16 }6 U) N O) G* h
1& y& |- B h; S7 \
25 I& p% F, p0 K5 z5 S* f
3
# k, L8 S( m; B3 z( W& j* Y, ~4
/ ^( f4 z3 A! }9 W, p! t5
9 M( Z) e& U+ _9 Z+ B) p) u4 D( o6
" G/ J9 z8 r( b! Z& i* Q7
+ \/ ~* \" m5 a8
: k; |" ^4 J5 _$ ?9' [4 D0 v: a3 G2 m' q) b
但事实上有些列存储的是分类变量,列中并不一定包含所有的类别,此时如果想要对这些未出现的类别在crosstab结果中也进行汇总,则可以指定dropna参数为False:1 C `; Z- I* v
! X- B# w4 v/ l8 F) f
df.B = df.B.astype('category').cat.add_categories('sheep')9 m* o* T1 J' T3 G; I
pd.crosstab(df.A, df.B, dropna=False)% y+ u% X4 h, s; Z
1 m6 P4 x, R0 H' Z2 gOut[74]:
" b5 ]5 {' S( E' R! X: J j4 ?B cat dog sheep
, U, S" y4 Z6 i% M+ f9 u8 IA
% i! u$ b+ K8 H2 U& J+ f1 ra 2 0 0, Z, g6 F$ R( E- @# Q* k3 k) L/ I! y
b 1 0 0
( X6 E; }' Z4 _' xc 0 1 0: @$ `+ m( w9 r! ^* l
1
9 [6 L) S+ j0 N2
7 }' i5 e2 b+ N. C( e7 F3 B, q- P! \: ^0 D
4) [ q" F" e6 x( |
54 e( a7 e3 E- @0 R
6/ \& V5 i3 k- G/ G; O- a
7: Y3 q" o3 q- z
8* ]8 x) m1 D' Y$ h: o
98 M2 ?( F ^6 k: X, `2 k
请实现一个带有dropna参数的my_crosstab函数来完成上面的功能。
! i4 c. h! a6 {8 B/ r4 U6 X
' m- m4 M. Q1 S5 H/ F* GEx2: 钻石数据集; U5 s1 ^% S# f
现有一份关于钻石的数据集,其中carat, cut, clarity, price分别表示克拉重量、切割质量、纯净度和价格,样例如下:) M% h/ X0 O3 ~/ }4 z% b8 D
2 H% j8 t0 R3 q' g* ?" xdf = pd.read_csv('../data/diamonds.csv')
& }/ e) o8 Q; o' A) X5 Bdf.head(3); {$ K& g5 i, f3 H, h
* K" Q! j7 B' w1 s& L: N
Out[76]:
6 F3 m- O8 y$ K, H carat cut clarity price
5 M, M4 L0 J/ W5 ^6 R0 0.23 Ideal SI2 326# g' e$ a' I/ _& r
1 0.21 Premium SI1 3268 _1 _' A# ?% U7 K
2 0.23 Good VS1 327
( r2 ~# v+ P" M. S1( f8 m0 t. ]. r) l9 c' O; }
25 i. I, _* Z" X, L2 |
3+ j$ ^3 @: D0 ~" G
4# n# V7 o4 c3 [3 I# m& i$ \% N
5
) y" {9 `9 x. U& V2 a6/ c2 U* {/ X, K0 D4 m) C- r
74 x7 I z3 G- N
8" A) o7 r! `0 O5 @$ w
分别对df.cut在object类型和category类型下使用nunique函数,并比较它们的性能。2 |1 R! c A, \( h
钻石的切割质量可以分为五个等级,由次到好分别是Fair, Good, Very Good, Premium, Ideal,纯净度有八个等级,由次到好分别是I1, SI2, SI1, VS2, VS1, VVS2, VVS1, IF,请对切割质量按照由好到次的顺序排序,相同切割质量的钻石,按照纯净度进行由次到好的排序。; a* P( \% @! D" w
分别采用两种不同的方法,把cut, clarity这两列按照由好到次的顺序,映射到从0到n-1的整数,其中n表示类别的个数。
- y7 m6 b3 X/ O对每克拉的价格分别按照分位数(q=[0.2, 0.4, 0.6, 0.8])与[1000, 3500, 5500, 18000]割点进行分箱得到五个类别Very Low, Low, Mid, High, Very High,并把按这两种分箱方法得到的category序列依次添加到原表中。
: x/ }: u( o3 S9 n8 k第4问中按照整数分箱得到的序列中,是否出现了所有的类别?如果存在没有出现的类别请把该类别删除。9 m) r0 H4 F3 k8 m% j/ c! U
对第4问中按照分位数分箱得到的序列,求每个样本对应所在区间的左右端点值和长度。
4 L, v3 O1 y' i# Z先看看数据结构:
+ M7 U; b" \, M( Y2 c4 ]
$ [) {" @( J, R; \$ ?df.info()
1 F5 g" o8 P. Q" d# l% R eData columns (total 4 columns): }7 g# z+ `' Q6 H$ z' @
# Column Non-Null Count Dtype . \+ B* {# L9 }- ]$ c8 p
--- ------ -------------- -----
) k7 D8 h7 s4 K- D1 L0 N 0 carat 53940 non-null float64& `; k' ?) ]- F
1 cut 53940 non-null object / G8 K6 g* ^& J$ A4 Z% z
2 clarity 53940 non-null object
F" \; o1 X* s( o/ u4 K 3 price 53940 non-null int64 , p7 c5 U. N# p' T: _1 {8 C, F1 K: B
dtypes: float64(1), int64(1), object(2)
. T2 ~6 {! ]( _" }: [& U1/ D1 C9 K$ w; B
2' A# M/ W( R. Z0 n! E3 q/ ]
31 q/ w: F. S2 `( @4 e0 Q8 Q% A
4* v2 {/ K- H9 [( @
5
+ j% G4 D* G% m- x# Y; F q6) ?1 Z# {) t, F. v0 s
7% w, ]5 G* v4 o; ]& }
8
# ~* X7 m$ M+ p9
- y' E, b6 H; \* _, d4 Y比较两种操作的性能
$ G& D4 i+ Q; u%time df.cut.unique()& G( S D; A5 {
4 S: t1 R- X# M, p$ ]9 O- [' u
Wall time: 5.98 ms/ x E! D4 O9 A
array(['Ideal', 'Premium', 'Good', 'Very Good', 'Fair'], dtype=object)5 L+ Q) c8 a& X0 }3 r
1
( C# z6 ?$ T5 H+ F6 B. R j: n2
6 b0 I, H* e8 q2 D. k% H1 \! y1 W4 v" X' F3
. t* h3 c+ o, e/ r4
" D- y( A: e; F) F3 t: J%time df.cut.astype('category').unique()0 U# d" w( `% j4 v& A, g
3 `# \5 n$ e. u6 h4 B0 \Wall time: 8.01 ms # 转换类型加统计类别,一共8ms$ ~: A# y* x" _$ U( i' z
['Ideal', 'Premium', 'Good', 'Very Good', 'Fair']& e! {1 p7 ?% w9 D( P/ ~1 A
Categories (5, object): ['Ideal', 'Premium', 'Good', 'Very Good', 'Fair']6 w# M! I2 K/ C& V* y
1
6 F, \( N* J5 G2( T# m* \. H. H, B
3. _* T$ S) Y$ U) l' ?/ F% L+ u0 j
4
# m" e$ c5 E% k% T7 B5
+ S% E9 M+ i8 C d6 l( ldf.cut=df.cut.astype('category')
! ~& g% S( k T6 | }; a+ u%time df.cut.unique() # 类别属性统计,2ms
N$ K- }4 ]) H! R7 C. _" L$ P9 X+ V; y3 H) ^* }
Wall time: 2 ms
9 E$ D+ z8 f$ h r6 h; O['Ideal', 'Premium', 'Good', 'Very Good', 'Fair']0 R' F: T# ^! X$ N% I k, `
Categories (5, object): ['Ideal', 'Premium', 'Good', 'Very Good', 'Fair']
& q/ B5 I9 J. Z# b. w1
! X) K2 O! ^- Q$ y: H2% b: n) O4 E! @6 ]& E- \9 j, |# @
3
8 c$ @+ G+ J- u6 a; b( @# b4
8 c: z- l5 a. N |! U* W3 B5
) W. X7 G$ Z- W8 l9 ]( L# W" B6
# N2 M5 G! t. ]. j3 a对切割质量按照由好到次的顺序排序,相同切割质量的钻石,按照纯净度进行由次到好的排序。
1 D' C3 W9 Q3 F2 E9 u/ n+ zls_cut=['Fair', 'Good', 'Very Good', 'Premium', 'Ideal']
) \1 ^9 K! I3 q/ sls_clarity=['I1','SI2', 'SI1', 'VS2', 'VS1', 'VVS2', 'VVS1', 'IF']
# R5 K" P& [5 C; V! ddf.cut=df.cut.astype('category').cat.reorder_categories(ls_cut,ordered=True) # 转换后还是得进行替换
' G A/ l d' \& a# V( |df.clarity=df.clarity.astype('category').cat.reorder_categories(ls_clarity,ordered=True); ?( s0 B( s! \7 u% w4 ~- w+ A
1 Y5 F9 b. O- m, w0 X3 c7 i! [
df.sort_values(['cut','clarity'],ascending=[False,True]).head(3)
: I! I8 `* o7 J7 w5 F7 f5 S
; T! {3 M$ o$ T, R7 d Q _# A3 X carat cut clarity price
8 u3 s; E3 i. H9 ]8 v315 0.96 Ideal I1 2801+ e) h" A* E' r6 k' v9 d
535 0.96 Ideal I1 2826& V% S6 X: L/ D8 g
551 0.97 Ideal I1 28300 `& _7 g/ M. z& l
1
% {/ B1 u7 a7 ~. ^2% l) l- r, m3 D6 |2 S; b1 N' Z
3" q8 ?% f/ X; i) a# |
4
3 y; f+ M0 j1 n2 f; a1 O7 _5! ?& ?- C8 K7 ?2 Y9 U
6. n. B2 J4 R4 W& `4 B9 m# M- v" T
73 n( ], F& }4 C5 \
8) [1 ~) P: T; F# F7 V) _
9
1 K) W# j$ w; M; f4 R10, T$ U; v# w6 i% Z& w
116 w. Y( w# |4 E) D6 L3 H# N+ t% e
分别采用两种不同的方法,把 cut, clarity 这两列按照 由好到次 的顺序,映射到从0到n-1的整数,其中n表示类别的个数。
4 I! Z7 V* O5 V) U$ Y8 e. P# 第一种是将类别重命名为整数8 f0 d. H5 P5 K/ F- i$ c1 R- j
dict1=dict(zip(ls_cut,[x for x in range (4,-1,-1)]))9 S- Z& f" w' |* D8 j
dict2=dict(zip(ls_clarity,[x for x in range (7,-1,-1)]))
! Q- v2 J1 p# b7 H9 U- v0 X& Y3 _' ~2 y: \& l/ W
df.cut=df.cut.cat.rename_categories(dict1)4 y, L5 N% u' E- n( f
df.clarity=df.clarity.cat.rename_categories(dict2)* B# ]. ^9 K. k% x0 j6 p7 u
df.head(3)
: f* ~! o& @# f+ l7 \8 x4 K, A" u8 f4 N( I4 d
carat cut clarity price1 A7 D5 q b! _6 b
0 0.23 0 6 326. U/ X- l5 O* H. m" R' @
1 0.21 1 5 326
( w0 N; c* t# c6 K3 n8 A2 0.23 3 3 327
7 K9 G) P# w. F4 l: q7 Y" I1
% p9 \4 b$ Z# z! z7 M! ~3 O8 M2# ^7 T3 H4 W# m# X; m
3
) B- U! m( i: ~3 t2 H( i; b4
1 u( D [; M; L- Z& q5
/ `0 {; }+ W) F6
7 u, s$ z' w( d" C" \; E. c% v7
) y4 Q, \9 Z0 ?* i- Y! O; E8
0 p2 q* `) U/ H* o: d! R/ j$ ~( K1 }9
" P/ `/ g2 A7 G' P10
% q. `" L' D7 @; b11) Y ~0 B, }1 F" R& i* t2 s( ^
12
; |, M4 v' ~7 R' C* t# 第二种应该是报错object属性,然后直接进行替换/ y9 q3 ]: o9 ?% ^( U" y
df = pd.read_csv('data/diamonds.csv')5 Z( S' i7 h Q4 A' R
for i,j in enumerate(ls_cut[::-1]):% r! V8 y9 o7 y: A! K' k3 u
df.loc[df.cut==j,'cut']=i " [6 g+ b5 c9 h0 F# _5 q2 Q4 E
0 V, L+ Q; b, Q, K: q) Rfor k,l in enumerate(ls_clarity[::-1]): h. s$ t/ C3 _8 K
df.loc[df.clarity==l,'clarity']=k6 ^! ~. J& o( v7 m1 K2 n
df.head(3)4 I! j+ b Q" i1 T
& Y* m3 l6 @1 F& p: R
carat cut clarity price
! z+ m+ O' Z8 T0 m+ D1 v2 G) Z0 0.23 0 6 326
% @2 w. \* S: i3 _1 0.21 1 5 3268 E' k: p% F* ^6 F4 E9 @. d
2 0.23 3 3 3279 ?& U4 |% g$ P% `
1
, z/ A) p) H4 ~& M+ k7 H# s2' m, g& l0 W6 \3 o8 f
3
3 u* l& N' @2 c- c+ M0 k% H5 m4
9 y6 w+ Z$ M, ?: A' Z' d* p5$ D, R8 |$ }; F) i6 @
6
2 s* ~% L" I3 C/ d: ~& s7
+ {: v- r- t8 n" Z. P8/ ?5 f% s3 D) Q5 q3 m& p. `
9, l+ U) l) q" q7 P6 x
108 {1 T# I# [( r: @% T
11# H! D/ ~% K" r [ J
12
/ p4 V2 l# A- z$ U, A- q. ^* |, ]13" r6 a* ?7 O, V: Z! U9 u" z9 u
对每克拉的价格分别按照分位数(q=[0.2, 0.4, 0.6, 0.8])与[1000, 3500, 5500, 18000]割点进行分箱得到五个类别Very Low, Low, Mid, High, Very High,并把按这两种分箱方法得到的category序列依次添加到原表中。3 k' q9 o& K$ v: {9 t8 j* ^
# retbins=True返回的是元组,第一个才是要的序列,第二个元素是分割点
' n- a0 ]3 X- `: Navg=df.price/df.carat* F1 _! O; ?' j8 m' q+ K
2 E9 K) w3 w* u- m2 k. W
df['price_quantile']=pd.qcut(avg, q=[0,0.2,0.4,0.6,0.8,1],
7 U! ^& J& F, U( X8 D) i; X labels=['Very Low', 'Low', 'Mid', 'High', 'Very High'],retbins=True)[0]
; W6 a/ G' }; k3 t# I. Y+ _! V7 j
0 p, v3 \( p1 v* D- Ldf['price_list']=pd.cut(avg, bins=[-np.infty,1000, 3500, 5500, 18000,np.infty],
^+ C& Y7 t$ A) u labels=['Very Low', 'Low', 'Mid', 'High', 'Very High'],retbins=True)[0]2 g) U; Y* J3 R6 k: I# y
df.head()' s( {% z% T4 N, j6 i8 O3 ?9 d
. t* h% T8 s! O6 G( B carat cut clarity price price_quantile price_list
4 A$ D4 H6 x0 A3 \* j0 0.23 0 6 326 Very Low Low6 g9 i {. f4 g5 J# z$ T" L
1 0.21 1 5 326 Very Low Low
3 e' [- m j9 _/ f: u2 0.23 3 3 327 Very Low Low
, K5 @) e) ^9 `' b' P3 0.29 1 4 334 Very Low Low8 N$ c8 s6 c6 s, [
4 0.31 3 6 335 Very Low Low
) k& o9 Q. h/ |% c: R9 p, X% q2 |' N* j
14 R( v. y$ {. ]0 x3 q! A
2) q8 o" Z l& C7 w- [, ~
3
& G2 @& k2 Y' @0 Y3 D j4
8 `5 |. J2 j8 B( }( b57 z8 Q6 W3 J5 j9 _1 y* `' E
66 y6 p( |( T b* a. A3 h
7% S( U( {, {0 K/ A8 c5 J
8" P# _: T/ X1 b: u& s2 G
9
) r! O8 `$ B+ M10; }* Y* d$ j" h/ C
11
' b0 K3 C$ i0 F7 j123 C2 k: N8 y0 |, J
13
$ `5 }! N( O1 K9 [0 X14
; B. e$ F6 z8 k1 f15
4 q" |" N+ b0 |" ^4 C165 z- n7 W5 m$ ?) L. [0 X6 @
分割点分别是:1 `( S$ R/ |! g: W5 q3 j; s
) F% B# T- u4 F, y" V1 B8 Barray([ 1051.16 , 2295. , 3073.29, 4031.68, 5456.34, 17828.84]): X9 Z( y; Y' a3 Q) l
array([ -inf, 1000., 3500., 5500., 18000., inf])
4 {( K6 v& W8 V# ^4 K' R% b/ n1
; n+ C& Y1 G9 T4 ]" L2; D1 N" P+ F8 h8 n7 o; N/ H
第4问中按照整数分箱得到的序列中,是否出现了所有的类别?如果存在没有出现的类别请把该类别删除。; y' |# ~' U! ^) y& {/ n( Y
df['price_list'].cat.categories # 原先设定的类别数' Y) N: ]% T m E6 B
Index(['Very Low', 'Low', 'Mid', 'High', 'Very High'], dtype='object')( S& l0 Z# m5 z; l. U; b
. Z8 ]1 @5 O* m w
df['price_list'].cat.remove_unused_categories().cat.categories # 移除未出现的类别. R$ M) {5 D4 ]1 z3 m8 N" h
Index(['Low', 'Mid', 'High'], dtype='object') # 首尾两个类别未出现
1 ^- f' f P( J. H1
; K0 _) Y5 k2 l+ ], B7 T" Z& B. Z0 X2/ o. G% c. q$ t$ ^/ C
34 q% i8 {" y, [ A& s7 K
4
5 r S$ \# E2 j/ D5
& L3 m7 B. y) f3 j& bavg.sort_values() # 可见首尾区间确实是没有的
' n$ L3 i; j. c+ N: X1 w31962 1051.162791
% Z* q9 y% {5 ^; b7 U* V" k15 1078.125000. p1 Q* W9 I. h5 i/ [
4 1080.645161
* j% K8 ?/ m* h- q28285 1109.0909090 v# h. G& Z$ ?' ~) a# u( e* n) c6 J
13 1109.677419
- o" O5 U. E! v9 e5 P ...
+ {+ G1 U* r; I; p. P( {5 l+ r26998 16764.705882+ e$ G5 @# U6 E' u
27457 16928.971963
. i# [' ^7 E' J' _0 K7 U27226 17077.6699035 p+ c9 l. E# f! d1 d' R$ a- i4 b+ W
27530 17083.177570; B/ e/ F' O( b/ o
27635 17828.846154
$ l ?! N2 D& J" i) \6 W9 c. |1
$ u. Y, ]$ M c- H9 w1 t, d2
( _! ?$ Y4 B0 _/ y3
* t9 I1 s$ U) W! y5 A4
7 S. s" \. V: B- w$ }5 `4 [* D; `. V' k9 A- m) v' [
6
$ S4 G! u+ e2 s! Y6 q# k7+ l8 ?6 y/ U% f( k0 T( U6 D
8
9 C8 F) {( v+ B3 c$ e9: K8 M! G3 a, f/ u
10( C( i/ O( F0 ?9 z1 {
11. y2 q! U* E+ q1 D* ~
12* g& L% t& R& B) A
对第4问中按照分位数分箱得到的序列,求每个样本对应所在区间的左右端点值和长度。
1 N4 V4 @7 w3 ]& ^% b$ N6 I# 分割时区间不能有命名,否则字符串传入错误。
2 e$ ]9 ^6 d1 |6 g! l x0 Hid_interval=pd.IntervalIndex(6 \5 {0 w( N+ f
pd.qcut(avg, q=[0,0.2,0.4,0.6,0.8,1],retbins=True)[0]4 J$ S8 A/ U9 |1 ^2 y
)' Z/ Q3 n( @# A" G6 Y& F9 l7 A) h6 B
id_interval.left
0 W& u* [8 D: }5 ]6 U8 G* {$ z: Kid_interval.right
* B0 K$ ~* {% Pid_interval.length - L) M9 A7 Z4 N* @ J+ y
1
- Q0 G8 e( t" k7 ^" G2" d3 q8 X3 n$ M% L1 y9 B
3
; J: [! w- ?* {4: q) k) v( [2 r7 G' K! i
52 f9 C$ ?. i( b% c0 y- e6 i
6
# e e; Y& U7 C" \' H, ]: I0 s% t7/ `& ^- n4 }- N) [& g7 ?! a/ D
第十章 时序数据
}& y9 w* T* `' K$ Yimport numpy as np
/ Q: v/ e& c; B& Rimport pandas as pd$ D+ i) a9 C* C& {4 R' D
1! U, u4 ~+ `- i% E3 D, z
2, N2 j; K2 W4 [/ U; i1 J
/ O& j! H2 ~4 o1 r2 @& N" M# [# G! y1 k8 ^3 F# b
10.1 时序中的基本对象2 s7 R9 {$ z1 I9 a
时间序列的概念在日常生活中十分常见,但对于一个具体的时序事件而言,可以从多个时间对象的角度来描述。例如2020年9月7日周一早上8点整需要到教室上课,这个课会在当天早上10点结束,其中包含了哪些时间概念?
( d y- }5 P! F0 z% ^' D: X$ e! m% i' p, I0 |5 k3 G
会出现时间戳(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的简写。
$ T: m+ x* k! N" j- e( E) Z; e( `: r. x# X, n1 N( {
会出现时间差(Time deltas)的概念,即上课需要的时间,两个Timestamp做差就得到了时间差,pandas中利用Timedelta来表示。类似的,一系列的时间差就组成了TimedeltaIndex, 而将它放到Series中后,Series的类型就变为了timedelta64[ns]。
) L4 P: |: D6 L( I, r: k3 ]+ B
/ F$ u. x( Z3 C. I4 A$ M& l$ y, w: i会出现时间段(Time spans)的概念,即在8点到10点这个区间都会持续地在上课,在pandas利用Period来表示。类似的,一系列的时间段就组成了PeriodIndex, 而将它放到Series中后,Series的类型就变为了Period。
* e8 k7 O6 K8 Z- h2 P! r2 n0 D
o2 k# P, k( l2 {/ L; f& V9 U' [会出现日期偏置(Date offsets)的概念,假设你只知道9月的第一个周一早上8点要去上课,但不知道具体的日期,那么就需要一个类型来处理此类需求。再例如,想要知道2020年9月7日后的第30个工作日是哪一天,那么时间差就解决不了你的问题,从而pandas中的DateOffset就出现了。同时,pandas中没有为一列时间偏置专门设计存储类型,理由也很简单,因为需求比较奇怪,一般来说我们只需要对一批时间特征做一个统一的特殊日期偏置。% a0 ]! x( L/ d' |" M" {
- x5 u$ J5 M8 I5 o; c2 U5 { 通过这个简单的例子,就能够容易地总结出官方文档中的这个表格:
( S! m2 n5 @ ~0 f1 q9 g: v7 B0 j9 D2 ]
概念 单元素类型 数组类型 pandas数据类型
7 ?- Y( y3 ^* Z+ t# n4 ^+ W' N* sDate times Timestamp DatetimeIndex datetime64[ns]
* J: N( p: c" m, w/ o& yTime deltas Timedelta TimedeltaIndex timedelta64[ns]
2 `2 ]( e0 y9 C* A9 @9 q& K K+ jTime spans Period PeriodIndex period[freq]9 r, ^: {" P" ]- a f0 |% o
Date offsets DateOffset None None- K% n0 V4 {3 C; A
由于时间段对象Period/PeriodIndex的使用频率并不高,因此将不进行讲解,而只涉及时间戳序列、时间差序列和日期偏置的相关内容。
0 y1 d. J; e8 S* }6 \4 U' Z$ Y6 M2 a4 e
10.2 时间戳
: I( u. s' R5 X6 k' [10.2.1 Timestamp的构造与属性' J2 d& c' J0 a9 ]! M+ B
单个时间戳的生成利用pd.Timestamp实现,一般而言的常见日期格式都能被成功地转换:
) A9 f# P# b3 l' u# X( n
0 B! F/ O" k) {) R' |# uts = pd.Timestamp('2020/1/1')+ U2 E( T& X! u% z$ R
! M: }" W) h7 I3 x& {5 ?
ts9 j3 W& [! _4 r, \
Out[4]: Timestamp('2020-01-01 00:00:00')
! Z1 X- s& Q4 w4 G/ a9 R2 p2 D7 f) e+ G6 B5 |+ u- f8 c5 o
ts = pd.Timestamp('2020-1-1 08:10:30')
0 k) R; s3 P. O ?) C9 A( Y+ B6 e' y
ts8 {; M. L* T) @
Out[6]: Timestamp('2020-01-01 08:10:30')
5 E! r- S8 v3 B! u, b# y7 f18 _+ x8 I& p% x4 q% [4 a
2
5 v6 H! f/ {3 X9 M3
6 P% x( }' k$ a; o46 H7 j7 D: V' ?( S( Q$ u
5
- z0 g1 J" F: i0 \& w6& z$ y7 u+ A( s$ d- T
7
' b3 u8 U/ `" \+ A7 @ l# i O8
6 @$ L" b/ |+ L; d9 e4 ]9$ o% A' |& |( ?
通过year, month, day, hour, min, second可以获取具体的数值:
% Z2 Y+ d! k8 X, ?$ |
) Y: c2 N" y Ets.year
" M e8 h9 H9 ?0 |8 B( AOut[7]: 20201 B1 S- U U) m" F8 M6 H
6 l1 f0 O2 j4 c+ m X
ts.month
' I% p/ {. O$ Q+ v/ ^" OOut[8]: 1
S# W- l5 m; q9 }2 b, E# O4 [& o1 j, v% a9 ]# U
ts.day
: ^0 f% j K" v# l3 Y0 c4 pOut[9]: 1
: i+ C; n6 s5 B( y) r# R" i
3 d+ `1 H+ S6 C" u; M. Uts.hour
/ t2 x& t8 _3 O# HOut[10]: 87 \ y! y9 W |" {9 w5 T# o% M
2 n5 j5 d8 G5 r5 D
ts.minute! E2 D) u: P: @* y+ R1 E" o
Out[11]: 10
# t) ], m; k* r/ g0 R
3 @% ?7 A8 Q' m. Q* a" w+ C* zts.second
. |& x" s- Y5 d; ZOut[12]: 304 T" o6 o7 |. ?4 t3 P# @( T
" L& O. ~5 `& V7 r8 I1
6 O# t7 p; \2 O25 q; [9 l* c) Q! `! V
3: X X% Z! D% R) ^) G$ y
47 ?) ]9 ]5 y( H4 b5 m
5
& `. W8 D; H0 Y$ M }5 C6
' f# `5 [& D' G( A7, N0 b$ o! z* y
8
2 A2 |6 r% y- l: c9 J9+ J7 `7 J! _; Q! x& k8 P! r3 v
10" e; ]& E% F0 ^( [% n1 W
11
: o1 @2 \$ D3 X6 g129 w$ s' p: B# v
13
: v4 \, C" t% j$ m) G" I, P14
* t' a2 b/ `1 ]( Q15
3 _) V' m7 f! D4 [2 a6 W" Z" ?160 S6 ^6 G' L0 P3 f
17
, Q( v$ o1 m4 {9 e0 y8 l3 O) g# 获取当前时间
E1 r8 H/ u7 J" f5 nnow=pd.Timestamp.now()- O/ h* s/ l8 R- V. i. F
1
3 Z' H9 |, W$ F, }% @2
% E, V+ X+ e" W8 S在pandas中,时间戳的最小精度为纳秒ns,由于使用了64位存储,可以表示的时间范围大约可以如下计算:
! ~% U1 p+ q4 UT 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)/ _) j5 x; p/ R! y' x K
TimeRange=
2 e6 S9 `" x1 q; t$ J/ u$ h10
' s& s1 v0 n) N) O, f9
: }0 i, d0 F1 e( }! G" m ×60×60×24×365
8 A# J1 k: A' K8 o6 P& D: H6 [2
1 b- G$ L& N7 O648 X* N* Z0 Z# Q( n3 ]
' }' W4 C. w1 C7 f9 N: u. M% \ w& B; ?3 e4 X- P+ U. e
≈585(Years): d9 e; Z( j6 n9 t
0 z3 r4 p) ~# j) J$ Y. u/ n通过pd.Timestamp.max和pd.Timestamp.min可以获取时间戳表示的范围,可以看到确实表示的区间年数大小正如上述计算结果:
/ m2 a+ o# I- E {/ O% F
5 m# } z2 ~8 k- E) Fpd.Timestamp.max
. [- X/ B7 J8 f0 g {9 {Out[13]: Timestamp('2262-04-11 23:47:16.854775807')
+ D' f$ d) q( m0 d0 Q2 k; X
5 G @ O0 ^+ }/ H: X+ h) {pd.Timestamp.min5 U ` x* u l
Out[14]: Timestamp('1677-09-21 00:12:43.145225')
! f. A. P' e* [9 U& l% A# a+ f' Y$ K- w" n
pd.Timestamp.max.year - pd.Timestamp.min.year8 a' y* Q% s2 b0 F4 W7 T% E0 I
Out[15]: 585' L6 Z1 ^0 @ r# i
1+ L; |5 Z# L0 u4 T5 ]
2
, e6 I# t! y7 d) ~: R! c3
+ i5 D( d& V$ ^9 G* [2 a4
7 K+ Y) O* _4 B- t; T1 h5( i% a6 b" J/ L( Z
6- Y' \* F0 Z5 Q3 v7 ~* k- t
7
3 o* o. S5 G) D, s- V) b. V$ m82 w7 s- g: ^0 m* m
10.2.2 Datetime序列的生成! f3 l6 E9 k4 J0 ]: Y
pandas.to_datetime(arg, errors='raise', dayfirst=False, yearfirst=False, utc=None, format=None,
& b1 z" l9 h& u exact=True, unit=None, infer_datetime_format=False, origin='unix', cache=True)0 g( Y$ q9 o( B: f/ e# J5 |- W4 v# G
1- K0 P' o1 c' |5 ?' j! z4 J
2# r! C, S! k/ e f3 h2 w+ J1 `- W
pandas.to_datetime将arg转换为日期时间。
1 h% ^4 M" H- {/ I9 k& ^ G: ^
% a# j4 V4 A& L+ Rarg:可以是argint、float、str、datetime、list、tuple、一维数组、Series、DataFrame/dict-like等要转换为日期时间的对象。如果提供了 DataFrame,则该方法至少需要以下列:“年”、“月”、“日”。
, y' g$ o2 D3 terrors:6 Y& S; x: {9 S
- ‘raise’:默认值,无效解析将引发异常
5 }! U& y! b* J6 d$ t3 y9 z9 h" T- ‘raise’:无效解析将返回输入
% o" a. q; c+ P3 {- ‘coerce’:无效解析将被设置为NaT9 n% V3 N8 E$ x6 y+ N
dayfirst:如果 arg 是 str 或类似列表时使用,表示日期解析顺序,默认为 False。如果为 True,则首先解析日期,例如“10/11/12”被解析为 2012-11-10。如果无法根据给定的 dayfirst 选项解析分隔日期字符串,会显示警告。
% |# o8 ^+ \5 Z; Z8 ryearfirst:如果 arg 是 str 或类似列表时使用,表示日期解析顺序,默认为 False。如果为 True,则首先解析年份,例如“10/11/12”被解析为2010-11-12。无法正确解析时会显示警告。(如果 dayfirst 和 yearfirst 都为 True,则 yearfirst 优先(与 dateutil 相同)。)! ~! d7 z5 O2 u- H0 z: q( }
utcbool:默认None,控制时区相关的解析、本地化和转换。请参阅:pandas 有关时区转换和本地化的一般文档
3 f, L' d) N2 C% S* H" d7 P$ [* Eformat:str格式,默认None。时间戳的格式不满足转换时,可以强制使用format进行匹配。; x$ r+ g; }9 X- M' q& O
unitstr:默认“ns”。它是arg (D,s,ms,us,ns) 的表示单位,可以是整数或浮点数。这将基于原点。例如,使用 unit=‘ms’ 和 origin=‘unix’ (默认值),这将计算到 unix 开始的毫秒数。) `) X. T; L: R( [8 \
to_datetime能够把一列时间戳格式的对象转换成为datetime64[ns]类型的时间序列:
2 ?6 j$ {: x4 v+ F- opd.to_datetime(['2020-1-1', '2020-1-3', '2020-1-6'])
. ^. y& m) X5 |1 c/ f$ \9 }4 C- V7 Q! e
DatetimeIndex(['2020-01-01', '2020-01-03', '2020-01-06'], dtype='datetime64[ns]', freq=None)4 O' N! [$ q" o6 H1 s- \
1
0 x4 u9 n( Y& ?& a+ b! k3 L; t: Z2
w# P1 c L8 p* G8 }3+ O' {/ u6 N9 X- X) _& t
在极少数情况,时间戳的格式不满足转换时,可以强制使用format进行匹配:
/ U8 q, a2 J( g9 F% k# P0 |
$ S0 w$ ~) I3 {& Z% Dtemp = pd.to_datetime(['2020\\1\\1','2020\\1\\3'],format='%Y\\%m\\%d'), \# k, A' f3 u6 z6 \7 h
temp$ x+ Z% M6 F `
+ i, m4 u/ ?& h8 M6 z( w" S& l
DatetimeIndex(['2020-01-01', '2020-01-03'], dtype='datetime64[ns]', freq=None)! } b2 L% p: O& k8 W7 m4 \8 k
1+ Y/ `8 u: [! T
2
# x/ q' f, n4 i2 z32 J5 `; A) Y! r3 n& N/ t5 \
4& G3 N& w6 E( D; a' v6 d
注意上面由于传入的是列表,而非pandas内部的Series,因此返回的是DatetimeIndex,如果想要转为datetime64[ns]的序列,需要显式用Series转化:
2 h1 J V6 e$ g- w1 w& j0 @! h+ a' J3 z, l3 V
pd.Series(temp).head()* w' y) c" ]% n% H# E7 z* I" t7 P
. N' N Q$ l% V' \* J0 2020-01-01
; W J h& ~5 ?9 B6 c1 2020-01-03
! W3 m: |! r* N& v6 Ydtype: datetime64[ns]
$ |6 S& }9 W6 [) e6 S' y) F1
- D+ b+ c- b7 f2 o2) W3 W8 D$ h! x% X/ e3 T
3
! ^0 g F3 \# n9 w4
$ Y: @' b: o' c5 o. r2 [- h5
1 \* X W( K5 G' O2 ]5 N/ e7 k' }下面的序列本身就是Series,所以不需要再转化。
# J% e7 L# I, {( `8 p* [" h5 y4 r$ h' Q2 l* b
df = pd.read_csv('../data/learn_pandas.csv')
+ F O+ O( O$ Cs = pd.to_datetime(df.Test_Date)
5 z+ T( s3 c5 R! hs.head()
; ` @7 ^( ~6 r& m: ~5 |5 l/ N& o* v7 y$ y; ?( W7 b
0 2019-10-05
' C1 p+ z( z( x1 2019-09-04
; v' N2 a: h: C, e ^2 2019-09-12
; D; b' G/ {( H3 2020-01-03
4 P9 r- Z8 ~5 k6 d; s0 S) [4 2019-11-06, l9 Y" Z' Y i( P& S' [) M
Name: Test_Date, dtype: datetime64[ns]- D$ z0 o/ g. K8 C4 |+ N
1
0 U+ a6 Y6 k: Z2
+ H3 t9 a/ I% f N; c3 @: }35 a0 ~1 v. y( o3 e# a2 |$ w
43 q" [# r' x, G8 Y( _
5
" f1 x0 _7 c* o" B6 Q6: y! g9 C1 F1 E3 ]: _. w. W, _3 {
7
$ a$ M7 w6 K( T( v0 E8
" E& i; o! X" n9
$ n# J5 o {" o2 j$ b/ j101 y! R& \: a+ F6 Q6 q
把表的多列时间属性拼接转为时间序列的to_datetime,此时的列名必须和以下给定的时间关键词列名一致:
P7 n) y; P' U7 ]/ ddf_date_cols = pd.DataFrame({'year': [2020, 2020],
) j. E1 a. }1 p1 W T& C r* Y 'month': [1, 1],
6 }% R% C) [9 V2 r 'day': [1, 2],
' X) J+ x9 q( |* G8 h' V 'hour': [10, 20],- u& t5 R8 O0 H4 N( y
'minute': [30, 50],: A6 r# _* V0 u( E; }8 S4 T
'second': [20, 40]}), S2 r! M( D; v& P$ Q% N
pd.to_datetime(df_date_cols)
! F' P( U# a$ ?( u! I6 G2 _9 t0 v1 ^* C) w& P$ ~5 ~" ~
0 2020-01-01 10:30:20; i( C$ R8 v* l1 u
1 2020-01-02 20:50:40
' V( O/ W. o( R4 X0 }dtype: datetime64[ns]$ k( P" w/ i. } d3 J C
18 |1 L2 F7 o1 H* M" J0 N
2
6 d/ L7 r: m9 k/ R+ N3& ~. ^0 n& A, h0 F3 V/ {+ i
4
. B. a& S, A" k; E4 D5
" D, l0 E6 V3 c9 O& V) a& {* l6/ M. z; i1 t$ r! N! R) j# J4 [+ I- E; R
7
# l# g3 T: ~# T+ I+ T% ^8
% }8 r0 `; c8 ~6 [9
: e0 N9 V# s, ]' C5 @) o10
# e& R6 J6 z) E0 v7 A2 B7 ?4 v11
e' P/ s* p0 j/ Zdate_range是一种生成连续间隔时间的一种方法,其重要的参数为start, end, freq, periods,它们分别表示开始时间,结束时间,时间间隔,时间戳个数。其中,四个中的三个参数决定了,那么剩下的一个就随之确定了。这里要注意,开始或结束日期如果作为端点则它会被包含:; n- u: b. h, g: I' I% ?: Z
pd.date_range('2020-1-1','2020-1-21', freq='10D') # 包含
. F; m; W9 @5 [Out[25]: DatetimeIndex(['2020-01-01', '2020-01-11', '2020-01-21'], dtype='datetime64[ns]', freq='10D')/ x! I6 o5 U/ S5 `( r+ ^
3 _4 L# b+ B, t/ U9 o; U* Z5 }pd.date_range('2020-1-1','2020-2-28', freq='10D')
; Z4 R% P Q- L) S8 i F, e6 vOut[26]: # j& ^" u) f0 }* T5 @3 i3 g/ i
DatetimeIndex(['2020-01-01', '2020-01-11', '2020-01-21', '2020-01-31',6 M! a7 y1 `: H4 ]9 K) J
'2020-02-10', '2020-02-20'],
& @- d; M, I. E0 H( N2 h dtype='datetime64[ns]', freq='10D')
3 o% S9 P/ ~0 A! m6 S; s1 z4 M
1 r1 e# E; Y+ q+ |pd.date_range('2020-1-1',' ?& D% U6 A3 E( r5 J: y8 @9 T
'2020-2-28', periods=6) # 由于结束日期无法取到,freq不为10天
4 n/ p3 s3 ]! h9 V) _8 ]& r8 J/ K+ @$ R. ~; b4 t3 ^. s- Q
Out[27]:
% k) S; s, c7 D. xDatetimeIndex(['2020-01-01 00:00:00', '2020-01-12 14:24:00',: {: e2 O, G; I* M9 K6 C( \' S
'2020-01-24 04:48:00', '2020-02-04 19:12:00',% e: H& ~$ P8 O1 P/ `
'2020-02-16 09:36:00', '2020-02-28 00:00:00'],
) L' V: |& G/ D dtype='datetime64[ns]', freq=None)- o" d. a' p/ I+ n K
4 v" z. q) |2 O, M" ~1
; d0 |6 N6 ?, z. {25 p# s' z1 [' t/ {0 \; [8 q
3; X4 H7 w) {) U [
4
3 }- y6 |. _1 y54 @8 @2 y) V/ G) V; u+ t
6( w) Q: ~. [: B' h! |, q; z' h
7
+ H; _/ k5 G9 H7 Y6 e2 x3 [" g8& x# c. d- Q- M& k+ J$ k
9
/ ]1 A* E- @7 |5 B) t101 D3 X# ^0 X" L, e. o* U! _2 x/ L
11
1 ]9 K. x% h$ J% a12
; q: I" P8 I: _7 x% z" `13: H" C$ w# t% B* j c
14
3 r9 ^2 p2 n& p. y" f9 }6 Q, n+ m15# T7 B) n' G* M# S
16
7 j0 a) l8 p. l" M! o171 G9 @ k3 P$ G8 Z" {
这里的freq参数与DateOffset对象紧密相关,将在第四节介绍其具体的用法。: w9 k. ~& U8 P
$ Y9 X6 G( t$ v4 W: x
【练一练】# x, m: F* r5 R8 [6 a# e( E* I( Z& s, p
Timestamp上定义了一个value属性,其返回的整数值代表了从1970年1月1日零点到给定时间戳相差的纳秒数,请利用这个属性构造一个随机生成给定日期区间内日期序列的函数。4 s+ V, \5 j, t
/ N+ u( t7 w4 X4 `# m* \ls=['2020-01-01','2020-02-20']
- a' X9 H' f) L! P, z) adef dates(ls,n):
- p. u# ?' N# K6 Q! Z/ c min=pd.Timestamp(ls[0]).value/10**9/ Z$ R( Y: R/ V% C
max=pd.Timestamp(ls[1]).value/10**9
8 B! Y) [) ~( t# C times=np.random.randint(min,max+1,n)5 B6 h% `. |: r# i5 |* u9 M! i; n
return pd.to_datetime(times,unit='s')
S) _+ b( C B4 I) l( J9 cdates(ls,10) % z/ L: a P8 }( z E9 b
! y8 B+ ~1 Y' H: m9 T9 A; X
DatetimeIndex(['2020-02-16 09:25:30', '2020-01-29 07:00:04',' F* z. A- q6 _% v$ ]" S8 Q% W6 P" ]
'2020-01-21 12:26:02', '2020-02-08 20:34:08',
3 x7 \3 r5 V: p- S& ` '2020-02-15 00:18:33', '2020-02-11 02:18:07',* d6 L( J8 n4 X' U* U5 T
'2020-01-12 21:48:59', '2020-01-12 00:39:24',, l$ w1 Y" L' V
'2020-02-14 20:55:20', '2020-01-26 15:44:13'],
) e8 V0 z6 k Q dtype='datetime64[ns]', freq=None)
; Y' d# J6 E' U8 z% K! o2 ?1
, j; m4 A/ k6 u# z( s( i6 u26 j8 x) S; Y0 f: @! l5 g
3- M' M+ @$ D2 L% P: Z- t' V
4
" a2 R/ J* G. n. `5
" @/ q) f, g B: N, L5 N6
# M8 K+ j* Q6 _/ O7( q0 E1 O4 u6 n0 u7 b
8
1 w+ @2 e0 \) r& c9
. g/ n% e7 W1 d" h10! y+ u x# O3 M# j4 ]
11( F3 N$ h7 d4 _8 c8 w1 P0 q
125 u1 R- \* ^7 ^5 e: Y5 s
139 l: g4 D5 U" N l D
147 | x: v- w% z" K; D
asfreq:改变序列采样频率的方法,能够根据给定的freq对序列进行类似于reindex的操作:
; x/ N" c/ H3 W0 p+ u* ~, U: A# \s = pd.Series(np.random.rand(5),
' h# }1 @. j# B6 H2 L index=pd.to_datetime([. Z6 {" u- x ] o, z
'2020-1-%d'%i for i in range(1,10,2)]))- F1 v1 J* o* D2 @8 {4 Y# s
, }4 a4 Y. W! W: o3 b5 |. _% m+ S# Y3 j! X8 q% `+ v
s.head()
: l! U) Z0 n/ D& \$ ]% OOut[29]: - |# Q3 v5 G v$ s, ^
2020-01-01 0.8365787 D8 ~+ s g1 p' A& R' ^, W
2020-01-03 0.678419$ H& Z G! o: ]; Y; {
2020-01-05 0.711897; Y/ A5 }- }, A7 @. r
2020-01-07 0.4874291 a- C8 P9 Y, n; V# z# k
2020-01-09 0.604705
. c! w1 s0 L, Xdtype: float64
& J; {- ?# e+ ~, i7 P
7 P! ^. `6 y$ ^* Bs.asfreq('D').head()
! g' d; v. ]7 w/ a- EOut[30]: 2 n, m3 X. z2 _% ]- Q' z2 B4 f6 O4 F9 ~
2020-01-01 0.836578& @6 k6 y |/ y9 m$ o
2020-01-02 NaN
& A7 p2 V; ]) f, r- ^2020-01-03 0.678419" u8 l' ?' d! V0 k$ D* b
2020-01-04 NaN# r2 c- a v! o* ~$ [6 ?. F/ D7 Y% l
2020-01-05 0.7118977 a3 K$ Q. m+ ^9 a6 l# U2 r
Freq: D, dtype: float64
: i3 J- Y1 n( l
. p) |0 ]' ^' ^s.asfreq('12H').head()+ Y- H1 Z( l, N
Out[31]: 3 f9 b a' `- T+ s' T e/ O
2020-01-01 00:00:00 0.836578
; G0 T; G ]) H( O( c1 r2020-01-01 12:00:00 NaN
7 k9 `0 W3 ~3 A9 e$ J; X9 [8 \2020-01-02 00:00:00 NaN9 i& M/ X+ ?0 m. P" v
2020-01-02 12:00:00 NaN D8 _9 q7 n, ~. I; J
2020-01-03 00:00:00 0.678419
: S+ N+ O- D! M2 X) n) OFreq: 12H, dtype: float64
1 G% N1 }/ v/ F3 C, J, h/ k9 `
# m5 _6 e3 n/ n( J2 t17 Q7 r- G, m# Q
2
. o3 d+ q; U+ q, ~& B! S8 s0 U35 u) R1 C6 K' Q" u6 S. J
44 H# G0 ^. ~3 T. E5 l
5
( m6 I( L" j0 _6
7 Y/ l5 W# u; e1 r1 w7
" c+ S6 N% V# D1 `6 p8
; K+ t1 m/ `2 d* U3 _9$ w2 V! \: w( V6 x
10( G- E4 y% A. |" g; [& o
116 p2 a2 D1 i2 T/ B
12. O" V7 v9 u; W/ i: q6 U* |- F
13
# ]) X- l2 U7 e8 ]9 B14) _; h2 p6 ~6 c
15! O* R( H& T* V0 a3 y
16
- o% R2 n6 G; u) {, \17
% U! v( _3 P& V# ^3 u# P18
( Y( u k, J' g9 K19
% A2 \/ O0 p) p; c* V. m, M2 t20
. u; K1 k/ b; Y6 V2 G8 q# F21
% h0 P7 ]- D7 J @; s2 Z: ?# b22
9 W) M ]6 ^# M( {. \; B5 }; k @23) U5 F6 Z& D$ g1 y
24/ T* W* d" V* v
25
$ P0 ^2 [$ x+ G A2 R; O" G26
7 [5 J. q4 }/ S27
5 s) g* {1 |& o3 _, x, b28& h4 a0 I8 d- x* O7 o& Y
29) O, B7 T, K/ x4 f; d/ V- m
30- h5 Q% T- z; H# r
318 d( m& \6 E- p
【NOTE】datetime64[ns] 序列的极值与均值, ^3 C/ D% Y) `3 ?; x
前面提到了datetime64[ns]本质上可以理解为一个整数,即从1970年1月1日零点到给定时间戳相差的纳秒数。所以对于一个datetime64[ns]序列,可以使用max, min, mean,来取得最大时间戳、最小时间戳和“平均”时间戳。( O+ d& C+ m( w
2 \" m% l. m7 Q: | L/ Q' W
10.2.3 dt对象" s, m4 A: f, S3 E3 m+ q- w
如同category, string的序列上定义了cat, str来完成分类数据和文本数据的操作,在时序类型的序列上定义了dt对象来完成许多时间序列的相关操作。这里对于datetime64[ns]类型而言,可以大致分为三类操作:取出时间相关的属性、判断时间戳是否满足条件、取整操作。
# X( ]/ V6 b! X/ \4 u, b! p
) \& ?! w( [- s h% M第一类操作的常用属性包括:date, time, year, month, day, hour, minute, second, microsecond, nanosecond, dayofweek, dayofyear, weekofyear, daysinmonth, quarter,其中daysinmonth, quarter分别表示该月一共有几天和季度。" @- O7 ?# ~2 s
s = pd.Series(pd.date_range('2020-1-1','2020-1-3', freq='D'))
2 U, @) q* c _0 o6 L y) i! \% V6 m7 i8 Y
s.dt.date2 J. n7 u& i% {- s/ I* Q( A
Out[33]: ) _- V6 y0 U5 Y S
0 2020-01-01
( m8 Q3 l; M( n5 F+ H5 y# _1 2020-01-02
# D! P0 @ Y6 Y8 I# @7 A" z2 2020-01-038 r2 D4 p J5 J' r8 V q
dtype: object+ s( X% {& @3 M) m7 N2 @% j
5 d c* Z$ O V8 z9 y: O8 h
s.dt.time6 X) [- b" K* D! l# J0 B5 D
Out[34]: 2 D9 A* P+ x/ C
0 00:00:00
" Z; p$ ] Y& d( R1 00:00:00. p- k2 d% ]1 a/ T0 _( H, q7 a
2 00:00:00& m) m8 e. u: U* [5 f6 P
dtype: object
" x* a& ^# t3 Q3 @4 r5 C
6 [6 {4 S% ~0 f% W" y. [/ Y) ]* ls.dt.day, w8 G& P( O7 H& |
Out[35]:
5 L0 d$ c5 U' F3 h" \1 P6 O I0 13 c- V4 {6 @7 \+ M; F
1 23 ]. b- V& z' m
2 35 v/ q% y) d# u- M* Q" Z
dtype: int64
% s' d9 J* [* |0 z# ]+ V; ]% i3 e; l- U5 {, a, o
s.dt.daysinmonth+ H+ {3 u: [% f3 O. M
Out[36]: : L. y& I q; h- Z: O
0 31
1 S8 U8 `! m" Q0 }' y1 31
9 _5 a: v0 o( k; k+ v) o' Z% S2 315 y- Z& _ [% b2 s2 a5 }
dtype: int64$ i% p& e5 Q* l4 x, O. S( D) _% |
% N3 T" i+ r7 W) w% q+ B4 g) F
1
, [* D* F. [' }2
# |7 v1 w& z/ I# u3
" @5 u' ?! I, M/ D4
) B L* v. `( G3 W5
2 c/ |3 O r4 C5 D66 }8 e! P; l$ w
7
1 z% Q* n/ K# z2 ^8' j7 O' @' `7 n5 b- d
95 {" f- T$ L0 [% a
105 R1 H% ~3 p4 V, B$ L0 c" G
11& |# q5 x, l, a' q! d* r0 |
12
3 q+ L8 Y& R, D, R" A13* S% _' R) ^1 m( e
14; s6 |4 w# f8 v, W: k P. p# n
15
* Z8 P: ~7 X7 R/ l6 k& z16
9 E! d$ @5 f+ g2 ?7 O* y [* W17
! H0 `/ v6 `, ?9 z9 P18
6 L. P* E# }4 ^1 Y+ B) r7 ?19
" P* ?& p( h. `% a: e; m- f203 M1 Q; R- H( x' }+ L K
21+ F# R( t4 M% ^
22
( z0 u" B5 G) S% o23
2 B/ S& p5 K8 d& y" F) i, }' }24
3 |% y& \4 a2 o. y- N; m9 ~25( ]2 M# q% J' m! L2 p) x7 r2 {6 t) }
26
% t' x6 j" P Z& O* ~272 P: f5 _) H/ g: ]+ x
28
$ V9 J0 G1 z: u9 }# o& m8 j- H29
* `$ N, N, y5 p5 z5 y' @; V 在这些属性中,经常使用的是dayofweek,它返回了周中的星期情况,周一为0、周二为1,以此类推。此外,还可以通过month_name, day_name返回英文的月名和星期名,注意它们是方法而不是属性:# r) `# b) e6 N7 {2 U4 v
; o3 p8 e! x) P' c3 x2 h( q$ Q/ k. W9 G
s.dt.dayofweek* w! r, U* N' n q, I
Out[37]:
8 e1 Q) b, u4 q' a- d" Z0 2' q& x6 s3 P# t2 l& N
1 3. n u" Q# {5 O6 h
2 4, a8 T9 [4 s1 o/ x" o( t
dtype: int64
0 v- n+ i _# Z/ U7 n$ p
$ m" m/ Z4 e( e- p% \s.dt.month_name()+ l8 O8 a( E& L
Out[38]: & q' c2 i( B& C# I; d
0 January; h3 H, E0 J) Z! |' }4 `( a
1 January
: J* D0 i* I* Z) D& _5 u1 _7 G2 January6 _0 T8 r; k6 n4 K+ O( f+ p
dtype: object/ w1 o; g, i+ C' L" v0 t- m
- f$ I7 E+ |5 J- Z4 B& D, @. Q$ W
s.dt.day_name()) V$ [) x4 e% V
Out[39]: / o$ B; f8 t! I/ Q9 M5 J8 t9 c
0 Wednesday
- ]- G' b' Z6 y' j6 O1 |) l( Y2 P1 Thursday2 t7 c3 t( x( ^! [
2 Friday
8 J& r( V' |" h, N6 o( V$ Z; Rdtype: object
4 Z" H/ B: d" z9 w% {( i6 k4 s8 N* d- s
1
$ Z$ U, \! P3 [! a6 f4 K0 n22 Q# O- B% \! W/ k. C4 Z
3" p5 d8 h' G4 X9 @* K& |
4
3 ~( Y; X* z7 b! z, |1 F5- k2 {, y& e: f) }
6
4 Z; P. f& P# Y" b4 s( Y i5 w7; Q7 P: s" H, X- H: \* z1 l8 u
8% O( p$ d* v1 q+ ?0 B
9" s6 ]% q$ ]6 f+ S0 d4 f4 K/ g
10$ \" N! J' C: X
11
% F5 h9 B2 X; V$ _* Z; Q5 }12% D9 C- F' a% t d, T7 r& ~, t+ B
13( W- f8 r' z0 L) h
14- w3 ^2 M: f$ _* g. U2 J
15
4 e: Z( S Q4 N5 P0 E+ t; [# O16
- K4 I, `& e! w; }/ u, n- j17- e/ i# A3 u3 N& F
18) P% F- A" @% N( i
19
1 d; D. ? ]+ L20
5 Y, b1 x) e$ j: U" W第二类判断操作主要用于测试是否为月/季/年的第一天或者最后一天:5 C$ b& S& I& L" @, q) C
s.dt.is_year_start # 还可选 is_quarter/month_start
' y8 R$ T9 `# g2 R; @Out[40]: # `: j; ]7 l i' G n3 ]* R9 ~; v
0 True
) w9 ~. r( O9 c4 X `1 False
3 f+ I: t i8 L& K w8 V1 t! [% n2 False: H3 n$ H# I' U/ b1 A; U' U
dtype: bool
, \% b# ]( \2 L1 v
# Y$ O8 A4 t# _! W# w( V/ |s.dt.is_year_end # 还可选 is_quarter/month_end. h( W' Q) u( x' `1 W( u
Out[41]:
( Q$ l: N; _# I2 S% q7 [) ^0 False
8 ? E' j1 S8 ?5 N3 M% k6 x1 False0 r4 {. _0 h/ d C! |; |
2 False% [1 i- A j$ ^2 j/ v5 o
dtype: bool6 Y7 |7 e0 r ?
1
( J0 T4 |& n% s" ^2
: E9 H7 ^% Z: `! L# b" G/ I& H% C& |3
8 _ c* J$ O4 I4
! L, N. ~+ `+ @. }9 V/ ?6 r4 s N/ P5
' p0 e' w, T- N* @0 z' h4 M5 U9 v6* ]* j* P( v# N+ Q5 |3 S8 h
73 h: h9 n/ c I8 e1 o
8( E$ m- u a' u- O, I# B; z
9
9 [2 S8 t& L" _10
9 z- m" w7 J2 ?% x! v5 E119 A3 H5 g1 [5 [7 @
12
. v+ ]& e! c" P) {; o+ ~132 J% n8 J# P; l9 Z% S
第三类的取整操作包含round, ceil, floor,它们的公共参数为freq,常用的包括H, min, S(小时、分钟、秒),所有可选的freq可参考此处。5 a+ d" z) h1 @9 ~2 `( o
s = pd.Series(pd.date_range('2020-1-1 20:35:00',2 G! C" x! z9 r; ~! h+ P7 f
'2020-1-1 22:35:00',1 s# l/ v5 c5 g1 [0 B% ]8 A
freq='45min'))
; u3 A7 f9 e2 C) t% N
; K; L! `5 R/ u' x9 l0 _2 d4 U# P/ ]
* N( X7 T" j; Y; M" ~ fs) Y9 O1 t# P3 h" R; N _9 T
Out[43]:
% m A6 |. u: {# n0 2020-01-01 20:35:00
* X9 q# N3 ]) h0 B! I7 L1 2020-01-01 21:20:00
6 d2 F K+ @/ P M# \2 2020-01-01 22:05:00
) i: J2 ~7 L' I# ^dtype: datetime64[ns]
! S+ L- x& k2 x1 T; {9 l9 d. a3 l/ Q# `
s.dt.round('1H')8 s( g" H5 W+ r! X) O, S
Out[44]:
' ^2 z; h2 d" }# p0 Z; p1 e0 2020-01-01 21:00:00) H1 T" v+ k4 N
1 2020-01-01 21:00:00
4 U+ W% u: G: L$ q* `" g" L) U$ ?/ ]( E2 2020-01-01 22:00:00
! C$ t- Y: ~5 ?5 A; D/ K" Z, zdtype: datetime64[ns]" U6 O! m- I, A
- `4 ^% g. W; {s.dt.ceil('1H')& L P$ q( Z) a7 u( W3 |: X( C
Out[45]: 7 w0 l7 `* x0 G7 V2 v
0 2020-01-01 21:00:00
+ p( i' H3 ?: {' ?/ H4 e1 2020-01-01 22:00:002 ~ y0 [3 x: b* l- k
2 2020-01-01 23:00:006 p5 T5 ]/ i; ^# [1 U1 Y5 N4 \
dtype: datetime64[ns]
( k3 B; p' b% S$ U- D2 n z z+ t2 p
s.dt.floor('1H')
- d/ ~: t# q1 d6 N& v0 y- [' ^1 yOut[46]:
/ _* b1 {+ i+ F; U0 2020-01-01 20:00:00
* l5 {+ t: w) S$ n- M1 2020-01-01 21:00:00
, \! D. N& o d2 2020-01-01 22:00:00- f1 r+ P+ m+ G' a$ ?. @
dtype: datetime64[ns]
# x7 v# _ y$ d C/ J
3 l& e& Q6 g9 v" Q4 z8 W2 W1
- u- l# I, I: t& |: I) U; O3 q2& ^; [ h: M l$ J9 B, W
3" x/ q" @! E1 U/ ` M
4
/ Y! o) `0 K: n7 G! Z7 }5
D* ], _9 X; b4 l) Z6
1 {3 A# S* C" V7 S+ D) d4 Z7 y* A8 y7
7 a6 g( g4 x+ R5 t% t. a$ A8
8 T: {; w' N0 p/ Q' ~" ?9" t/ i0 C; G ^
10
8 f0 `* J; n' J6 Z+ ~0 G11
: \" q6 c- w' Y12% O6 Q: \4 t: g% C) e
13" v M( p- R: ]9 ]; t
14/ t- K, Z" B% u; l
152 k- B x8 t& r2 \
16
8 N, W; H6 c- X, ~17
7 |; x% S: }8 m* K189 X5 N& A2 f6 N5 y% Y; s' P- z6 G
19( g6 B* Z3 w+ |, r0 ` ^" ~1 Z( ?. m
20
$ E# L# N# G$ ~21
9 _2 b' K2 D4 ^+ b22
9 k3 S+ R R7 E. U) |: T" m3 q23( {& K4 q1 A8 W; q, K
24
4 }2 M' v7 t# z5 R( b, ~8 O" Q25
# z u0 Q8 S1 e! K! v" s. W1 f261 L4 F2 x- K. E
27' K" F4 S9 [1 L: [
28
' P5 {# F6 a- p, S$ m" |4 H1 k( a290 m1 @4 m1 I4 m
30. q d! w- n6 V, E
318 j( F0 R( ? J
323 ~' i' J: h2 ?$ c) [
10.2.4 时间戳的切片与索引
/ G9 M( t' J% l 一般而言,时间戳序列作为索引使用。如果想要选出某个子时间戳序列,有两种方法:
/ Z1 @! w! @$ s- Y; }9 z( ?+ n' z. c% X0 T% t
利用dt对象和布尔条件联合使用
$ a) ?) _4 S1 S Y3 y' a/ v; j" }9 a利用切片,后者常用于连续时间戳。
j; D Y! I9 t! H, ks = pd.Series(np.random.randint(2,size=366), index=pd.date_range('2020-01-01','2020-12-31'))4 T% ?* ]( i7 p
idx = pd.Series(s.index).dt( d! ~7 ]5 X0 P
s.head(); u+ a: r i( x- g. n
4 H, n; T5 F* h2020-01-01 0
6 l- k3 [/ f- t2 y( u; @* R2020-01-02 16 x; l' F9 c7 q; B- Q( _
2020-01-03 1
4 G8 k) @+ Y# h" g, {2020-01-04 04 W3 u( m+ O) v7 Q/ w
2020-01-05 0
; V ^9 B3 A! \; F/ I! ^Freq: D, dtype: int326 v7 C: c1 r, ^# Q) t8 c5 k
1) a1 h2 n" f. h$ A6 f
22 D9 L2 z: Z5 m. h& e; Q( \( p' P3 ^
30 a$ A/ x) d* B8 q3 a: J+ r( R
4+ [) R+ Y8 v- y( U0 q7 X
51 Z* F& a3 @& ~" N; J& ]5 }
6
/ f: r/ N; W& `: Y7- I) D% K' ~9 A& ^6 [) S
8% P6 g7 h. g$ M# Q
9
/ O! ]4 ?9 _" }$ p3 t7 ]& P104 ?, R/ a6 G: A2 U
Example1:每月的第一天或者最后一天( }6 z/ |* `' O6 `
' Z6 {' M0 d/ W5 M z: v5 is[(idx.is_month_start|idx.is_month_end).values].head() # 必须要写.values
7 q' Y" l1 q1 e4 I: _Out[50]: " }9 _9 l. r) _5 H- N7 E) m9 D
2020-01-01 1
$ a, l! P( z. b' @ w4 o0 e& q2020-01-31 0
1 G4 f: l* U+ U: O' A8 {2020-02-01 1
* G) P" m: i5 h0 R* y2020-02-29 1
3 `* s/ `3 ]: l. n" E( r/ `4 Y2020-03-01 0
- l9 q- ]+ V4 m* j3 {dtype: int32
6 b: z! o. B8 A l5 y% `2 V+ n0 I% {1
P" a0 [0 D# z9 {2
( C+ B+ ?) B# H30 c# a& s' s" r* O( ]1 ]
4
2 I' k F( `% h6 [1 X53 l8 O3 d" n- C$ w% Z
64 |, ] K6 Z7 \# [
7
8 a* Q w9 d# P5 l8
$ z+ t- i# i. z5 v, k8 \Example2:双休日
' _& U: Z* L' M8 T( G" c/ P. G* f8 L& I: h
s[idx.dayofweek.isin([5,6]).values].head()/ ~# M% U/ M9 d: Z9 M3 {: w2 ]
Out[51]:
2 M9 U' t8 `& K$ ?& @) V6 G2020-01-04 16 r3 D" T9 D. b- ? z) ]
2020-01-05 0
) I: d1 p. @# O8 ^* S& H. }2020-01-11 08 G1 n& c x) I' B4 m* }+ K
2020-01-12 1
* Q( [! Q9 h, L6 k {, ]2 P. k2020-01-18 1. @: k. @: }. h) l$ D; K) D. J
dtype: int322 I9 D2 X. Z0 e7 h$ c5 ~
1
; K) Y+ m. K' B h26 b. |+ R- {* w% J* j( m
3
% F: t2 W* W: d3 d. c1 t4
3 A, e# p' }' p7 C& i2 ~- m5$ L: p; l, a2 j" E$ y8 ^# q
6# }7 c3 ^* q. q' o; \1 h
7/ [/ ]1 n* Z2 ]7 }% h
8
+ N% ?0 E/ c) c0 }: b# }Example3:取出单日值
- [; x4 K8 Q- q0 M
( U& @% p0 @6 zs['2020-01-01']8 ^$ ]8 S: b4 }- b3 \
Out[52]: 1
' C( b) j/ y9 T6 k' E6 _+ G( m7 f6 q+ N" t
s['20200101'] # 自动转换标准格式! N0 P4 `) ` }
Out[53]: 1$ K# s5 D1 \2 d( P/ t) X
15 U# |- K4 x3 E' a. y+ t: f
2! ^7 I4 e- A5 D Z C
3
9 M" U' f6 A8 j) w5 H8 o4
$ S1 t4 k, ]) K% L57 a# e# B+ @( N: q8 j( D+ P* h1 L# j
Example4:取出七月+ }: b8 q% l5 r7 G7 U* @# N
3 ]* ?" @8 }$ S. w7 ws['2020-07'].head()
6 y1 F9 U0 Q/ g& H8 w, e4 \Out[54]: ' v& Y: d1 k! Z% j8 I5 S2 W3 [# c9 X
2020-07-01 0 t! _% M. S: }
2020-07-02 1 e' y( e: u) S
2020-07-03 0
/ a r% c* H: W$ y$ y4 w% _) f2020-07-04 0
0 {7 _# ]9 H+ `; N6 [6 o2020-07-05 0
' \" ]7 |; \- [4 hFreq: D, dtype: int32
" I C3 n; v! Y1
7 [- S2 ]: m" X' ~- L3 o. i24 U4 Y0 f) e2 z! Z$ n7 P. z
3) g4 q5 \6 N( N8 f7 C6 ]/ ?, }
4
' C+ X) M# u; n' h5" |3 N T& s& U# H |3 ^3 D
6' F: r0 s# H- M% |2 G
76 |* ^1 E6 w& r0 w- p" O
8
& j/ I4 k9 N6 u0 U8 K6 q) ]Example5:取出5月初至7月15日# B+ w/ s7 h E* x% E4 I* L
2 @% M# A8 Y1 x9 Y6 y' V& ] b ss['2020-05':'2020-7-15'].head()4 M. j. K2 ?( `% G3 G$ H
Out[55]:
7 z. \5 {- m6 y4 i2020-05-01 0" g* V' S7 F1 }# w! f, Q+ I
2020-05-02 1
- F' T4 ]# Q, I1 `2020-05-03 0( ^1 L* o3 G! V, N d
2020-05-04 1
& a' K" t! R0 x& v/ s: F. Y5 \ {0 L2020-05-05 1
' T$ p+ E2 [/ s. _4 zFreq: D, dtype: int32
- i. `) z& u7 \1 w/ R* m3 F2 v1 P' u" A( N0 i4 \
s['2020-05':'2020-7-15'].tail()
$ F+ ]/ ?: F+ p% W! K' q! b( {Out[56]: " R s) G4 E9 R- s' c+ x
2020-07-11 0
. }/ f) a" F% f" m* M2020-07-12 0
4 _8 e( J' l4 a2020-07-13 1
% l! [* k2 Z/ ]7 L3 h( l2020-07-14 0
" i7 D8 I; R. R3 k2020-07-15 1) G$ W2 y! j* @7 F9 X
Freq: D, dtype: int32
) `, u# G# Y/ D+ B) D( G' q$ o- f: c0 e
19 y' a5 n& M. H7 y H) ~' Q$ Y% [1 r
2
/ L: W8 X' r8 I8 h5 Z3
9 ]* X1 x- F0 N6 B, @) a4
: z* o9 `* j( d, D$ K5 G: E5( h& V7 N" P- S3 K8 j
61 {* K3 t1 L( X7 b
7
! p% r. M) A! {8 h8
! m- u; \! [% K$ ]+ n9
- B; h W* R) l$ b( a3 f10( N1 ]# ?& t5 l* D( @. {3 h
11
6 |+ X5 F5 u+ n. [+ J3 q( E) J9 Y# Q12
5 K8 v: z3 Z4 ]6 ]/ y1 D7 ?13
; d! Y2 ~/ I3 N% F; m14
: Y. W" d8 ^8 v' p* t5 U4 Q/ W% G154 F1 S) f- n# G3 ?
16) Q% o Y3 v1 n' C% W1 V
17* K. S; ?( p6 F# u: b( q
10.3 时间差
$ D: A+ `8 U) w0 C7 J* x1 K' m10.3.1 Timedelta的生成
2 `5 L# @3 g2 _+ Qpandas.Timedelta(value=<object object>, unit=None, **kwargs)* l+ N% [2 O" X' I5 x' l. k/ V, j
unit:字符串格式,默认 ‘ns’。如果输入是整数,则表示输入的单位。# G4 H) h* P; [, _
可能的值有:
% e* z1 h0 o. ` P! U
) t1 ?0 r# v& N7 q) P% N‘W’, ‘D’, ‘T’, ‘S’, ‘L’, ‘U’, or ‘N’
9 X' z, v% P) x‘days’ or ‘day’' v0 K" x$ U$ \1 L
‘hours’, ‘hour’, ‘hr’, or ‘h’7 {7 `% o/ x9 P: m/ r, I7 @+ a
‘minutes’, ‘minute’, ‘min’, or ‘m’- z1 Z% N/ o* q6 F2 i4 H/ K. ?
‘seconds’, ‘second’, or ‘sec’
1 _3 [5 x. x% d; L6 P7 Y# W9 u毫秒‘milliseconds’, ‘millisecond’, ‘millis’, or ‘milli’
% i! \' X: Y+ h微秒‘microseconds’, ‘microsecond’, ‘micros’, or ‘micro’
9 A5 b" W! D3 P/ ?2 c& S8 M* _纳秒 ‘nanoseconds’, ‘nanosecond’, ‘nanos’, ‘nano’, or ‘ns’.1 {& w3 |0 M9 n
时间差可以理解为两个时间戳的差,可以通过pd.Timedelta来构造:
- N/ l/ D. X/ n, Dpd.Timestamp('20200102 08:00:00')-pd.Timestamp('20200101 07:35:00')
2 N% V" s, q% Y/ O9 YOut[57]: Timedelta('1 days 00:25:00')4 B o( ?3 Y+ O2 }/ h
# L$ D6 m5 G) Y! e$ P1 R! z# Mpd.Timedelta(days=1, minutes=25) # 需要注意加s! I/ H" J" W* Y% M" B
Out[58]: Timedelta('1 days 00:25:00')& G1 Y5 q' W4 ?1 v* n! l
4 c, @5 G% R( O0 U* [
pd.Timedelta('1 days 25 minutes') # 字符串生成6 N3 X4 X2 ?- K8 j
Out[59]: Timedelta('1 days 00:25:00')
4 q/ e! X. W ?* a" _
, J( X8 \4 C$ ^8 H: e, Tpd.Timedelta(1, "d")2 U2 r$ e2 L. q/ G- N g5 ]
Out[58]: Timedelta('1 days 00:00:00')
* D0 K# D6 Q A6 ?# n, I8 G19 [- F0 a/ V1 B* I& ~4 ^- e
2" Z! E; e* @, A) m( y8 X2 e G
32 {7 C1 M& w$ J# o/ @! }
4
) |9 u y7 A* k5
& Y3 W I$ y* n0 ~$ s! D Y6, r( \' j1 n% W! s* ^
7( I, J. [/ @ l- v% A
82 Y, `, U3 f. N
9
- y" c. \0 S/ X* P* W) h108 G6 l* x: j+ _: c' t6 |0 V
11
& ~/ k6 }4 N1 a# M7 C2 v生成时间差序列的主要方式是 pd.to_timedelta ,其类型为 timedelta64[ns] :" b/ o6 Q1 e. |# m
s = pd.to_timedelta(df.Time_Record)) n5 M0 O6 D) `9 ^" x& n4 g
& _, {8 P" |& G8 F4 Xs.head()
$ p. i& T. h! t9 S ]- d5 R3 H1 ^Out[61]:
$ d! ^- y3 N \: A7 y5 `6 p0 0 days 00:04:34
3 H) Z# `2 o: D* r" e# }1 0 days 00:04:20
. U. f+ ? y' I5 I$ q2 0 days 00:05:22
6 m. B. `" {$ ^3 0 days 00:04:08
2 Q$ ~7 W) Z0 I+ J( `3 a( P4 0 days 00:05:22
d' J b" X& { R6 G8 A& }- t$ IName: Time_Record, dtype: timedelta64[ns]. y8 @7 W6 v: E8 }( X& L3 n+ A
1
2 [# c* ]6 m# b% |9 E2
: b2 Y. J9 [2 i% X% Z+ O, S3
6 _" G" X$ ?5 D$ n9 w4# C% \8 V9 h' n! E# W0 X
5
/ x- ?+ l$ z4 @- R% P( v x6' @& E' t& a' w* b
7
* i0 F% M' B6 ~8 I: _8
* s* U* Z, d: m- G, w# y9- K5 [6 g; Y! N: D( R
10
( D7 P3 v# n; G& n5 e% i6 }/ ]7 W# A与date_range一样,时间差序列也可以用timedelta_range来生成,它们两者具有一致的参数:6 e; V4 J2 ]- A0 E' w; C6 q
pd.timedelta_range('0s', '1000s', freq='6min')- `% G3 A; X! `8 `1 b9 _
Out[62]: TimedeltaIndex(['0 days 00:00:00', '0 days 00:06:00', '0 days 00:12:00'], dtype='timedelta64[ns]', freq='6T')( Q6 N8 w: A# P' W. }* A; n
2 ?6 R( ?$ D8 G- Tpd.timedelta_range('0s', '1000s', periods=3)# X! G9 g/ W& W
Out[63]: TimedeltaIndex(['0 days 00:00:00', '0 days 00:08:20', '0 days 00:16:40'], dtype='timedelta64[ns]', freq=None)
' x, P7 B& O/ j4 f T% w1" V/ F ?! e9 U
2
; ^8 w/ \; g3 R' {& ^! _8 N3/ H2 u4 ~) D; n$ L4 A
4! j% j! u( j8 k; \- N! O) R) F
5, v7 a: J+ g% c D+ F6 i' s
对于Timedelta序列,同样也定义了dt对象,上面主要定义了的属性包括days, seconds, mircroseconds(毫秒), nanoseconds(纳秒),它们分别返回了对应的时间差特征。需要注意的是,这里的seconds不是指单纯的秒,而是对天数取余后剩余的秒数:
( z5 @. [# ~5 X5 Ds.dt.seconds.head()3 ^2 M& t6 ]; p4 I- c% @7 m/ @) \, C
Out[64]:
% J2 l' T3 t0 S8 |0 274
3 u: u' y# V; f+ d( e# k. _1 260
. j6 d6 [0 I" a' \/ R% v7 T5 @2 3223 Q& M* m2 q3 @/ C) H8 u0 h
3 248
/ u N6 Z- M3 I, @8 i3 i- [& F3 s& ?4 3229 t8 U3 k: ^# G T1 z5 |9 z
Name: Time_Record, dtype: int64
7 c% E# Z5 x4 @& n& ^1
% v" Q. ^/ o& Z. ]% v, A2* c1 L+ j5 z; `% I6 J
3+ \9 g5 e0 [' K, I
4
: W/ V6 q, [3 J ]$ h% v$ H& k5: z* ]' L7 `4 _2 I0 z
6
2 Z7 y6 V5 r' O* j0 j$ v, L# n# a73 F# s6 s* }. B
8
" j! X5 m+ n8 [7 e如果不想对天数取余而直接对应秒数,可以使用total_seconds. f. k' N. O. t; T7 O
! B2 v/ J! _# P1 F* l d a/ Hs.dt.total_seconds().head()
% E/ a5 _# i* aOut[65]: 2 a) u, e2 n+ ^, E2 N# k2 v5 K
0 274.0
( d! Z6 d' f5 {$ |' p1 260.0
# V: ]2 N! E! @1 S9 `2 322.0
- s& h* u3 F$ E1 V2 f7 o+ |3 248.0
5 o% T) m" W# \: V4 322.0
& N2 Q: r$ j4 YName: Time_Record, dtype: float643 o( H# E# M- G
17 j) U* A) e- d
2- h5 M' ]0 w$ T p/ G* ?4 @8 t+ w# h* u/ X
3
Z& \' Y0 c, [( o: u1 i( Q5 ?4
/ z! H y8 v8 [5
' d2 D k+ ]$ x; E4 R; g6
4 i! f! m2 p5 J' a8 ]2 S. b7. {# i% A) c8 h' R7 w5 D
8/ t5 m( l1 k1 x+ `; h, [" i
与时间戳序列类似,取整函数也是可以在dt对象上使用的:
$ \. e6 P r3 r+ L- ?- \; N( o; R" V" I% j( I$ x
pd.to_timedelta(df.Time_Record).dt.round('min').head()
8 c+ l4 V4 _) V. b. z) F5 U; JOut[66]:
d; a+ M6 _& G" ?1 e: N3 w0 0 days 00:05:00+ W. f' d- \3 T( S! w( x
1 0 days 00:04:00
% k7 R/ i8 b6 u7 J8 ]) W Q2 0 days 00:05:00
' f6 z) X) k! b3 0 days 00:04:001 x& ?- }( u7 ?/ [* g1 a
4 0 days 00:05:00
5 L) p: T# N% l7 B& E/ YName: Time_Record, dtype: timedelta64[ns]
4 b4 H3 _; o) i" s6 e* r- q! ^; h6 L h1
- s7 ]' a$ ]8 a9 L. T: ~2! q& ]# A$ {0 ~' L& c
33 j& D' b2 J# C, t) m+ x/ j* s
4
4 U7 M* A, z5 }& L5) M* D# h, d$ |4 r9 Q, V
6
( n* }: a' D; M5 y# f- W7# O; b( g8 j* i2 U4 H5 w6 a% u
8
8 u& [9 L. ?5 s: p2 |10.2.2 Timedelta的运算+ c/ A0 p \9 p8 _0 P1 I. }
单个时间差的常用运算,有三类:与标量的乘法运算、与时间戳的加减法运算、与时间差的加减法与除法运算:
`0 ?9 [, U% { J* a6 t8 A4 qtd1 = pd.Timedelta(days=1), Y8 v6 f( m1 {- [
td2 = pd.Timedelta(days=3)) |+ ?" S1 W u/ G# y6 a; l2 l
ts = pd.Timestamp('20200101')
: C$ y' r( r1 Q4 W2 O* V3 [) b1 }7 o3 B9 r' s+ s5 I1 Z2 a3 \+ R
td1 * 2. |: h! O6 @: p4 J2 Q% M+ D6 j& n
Out[70]: Timedelta('2 days 00:00:00')% _# C1 \/ D- i# ?7 F5 `
; X* l3 d; y- C" n% L( G' Y, Gtd2 - td1
& ]" |/ n' X9 F, @3 ]: T8 I# FOut[71]: Timedelta('2 days 00:00:00'); ^; p2 z% W2 Z! |) O' X o) U
* i- n u/ K, C$ b
ts + td1
3 D ^" \& s% f7 h% @2 rOut[72]: Timestamp('2020-01-02 00:00:00')$ U+ T8 v8 u2 q1 M5 S4 H1 J7 Q
. q4 T2 h; M! L3 ]3 g+ j
ts - td1
& ~! X0 S' d* h/ w, [Out[73]: Timestamp('2019-12-31 00:00:00')
0 R% |& e9 a. D9 n1
1 e- E; q1 S- x9 z& @, k3 n23 P; q) R' O- I. G$ k
3) ? b) `. P8 o; t
4. l3 t2 B, U+ b h c1 c
52 P/ Y& ]% ]# B
6
( s2 t! r% c' Y! R% u( D7
1 u u, J; b' w! e85 ` |# s/ _1 ?4 @% h) V7 D, s
9
, g+ z( [2 V& Z) {" ?10
. P, N9 j+ Z& D- P' P' c [11
; [2 r* ]9 \ a7 b) l5 R12
4 f7 z7 r. u8 Z" X' M139 s% F! A; G9 f( Z' X- X" r
14
7 A7 y$ [* d% t* }6 z15% k5 @) i) I, W3 }" e5 {
时间差的序列的运算,和上面方法相同:
% v$ A6 o$ c: i" r, A4 w! S- @td1 = pd.timedelta_range(start='1 days', periods=5)
% J L; d) R1 R1 I. |# Itd2 = pd.timedelta_range(start='12 hours',
+ B1 i d! k$ h2 ]! { freq='2H',
4 U2 q6 A! m! ?9 Q8 X9 @ periods=5)
* O3 j: A" [; I9 u6 H. R0 nts = pd.date_range('20200101', '20200105')
+ k2 v% ~, c5 I: Ftd1,td2,ts5 ~; I9 o1 \7 M7 i e8 L
5 }( a) F0 r+ g& B& B2 J- b& hTimedeltaIndex(['1 days', '2 days', '3 days', '4 days', '5 days'], dtype='timedelta64[ns]', freq='D')& S, d9 }8 g/ ?& l
TimedeltaIndex(['0 days 12:00:00', '0 days 14:00:00', '0 days 16:00:00',- k5 K3 @1 G# l" j: l4 v$ h* j5 ^
'0 days 18:00:00', '0 days 20:00:00'], dtype='timedelta64[ns]', freq='2H')
]) ^- E9 T F( {DatetimeIndex(['2020-01-01', '2020-01-02', '2020-01-03', '2020-01-04',
0 M/ L! H- i% c5 ^& L9 ` '2020-01-05'],1 N: O* v5 J1 y9 c: W: [
dtype='datetime64[ns]', freq='D')7 F% V) L+ J" h& |: a1 l
1+ q: |* ?: G! T/ a% W: \# Z' e4 V
26 k2 s; l! |+ j& z# T& H: E+ @
3
9 K2 `+ u; O. Z. X- C+ D6 Y4
& Q+ z1 H: \# t' |56 V7 G/ D" C6 c
6" n% O1 V2 t9 ~" a4 X, A9 K$ z0 X
7
8 n, N$ R+ ^( z, K8
/ ^+ U5 Z/ l/ s4 C9
+ s/ L& Y$ h, e- S$ `10
# } ^' f+ I6 `11
3 Y/ h) T, ~# [( s! b+ \3 J12$ `) \- L& H Y! n- Y
13
6 X( r8 y1 D2 z# D/ Ctd1 * 5, ^- J7 S' Q3 u! N8 N9 D% e
Out[77]: TimedeltaIndex(['5 days', '10 days', '15 days', '20 days', '25 days'], dtype='timedelta64[ns]', freq='5D')2 R# d$ ?9 A& L; ]# C9 n
' c: e; T7 f" B& htd1 * pd.Series(list(range(5))) # 逐个相乘
. S) Q! q. b5 K0 yOut[78]:
2 B. t0 h) B3 x. C2 K0 0 days" y4 n7 R) M( D
1 2 days
/ j- Z: L$ u# z' L$ \6 O0 c+ f: c2 6 days
( O% E- |- U* b7 ?3 12 days
+ P) ]; x1 o$ {, x4 20 days% h" J/ M# J9 {
dtype: timedelta64[ns]1 ?2 R: k1 E" \8 I
o& q0 m0 X6 ?" P6 U
td1 - td2
, \- M/ d- _2 b+ jOut[79]:
! f& U I l5 {' s1 y. f3 V, ^, k0 ZTimedeltaIndex(['0 days 12:00:00', '1 days 10:00:00', '2 days 08:00:00',. J$ w' B# ^" N) ]2 t I
'3 days 06:00:00', '4 days 04:00:00'],
, x1 t* k) X; D% `' I" x dtype='timedelta64[ns]', freq=None)
' ~8 q8 f) s9 j. q; I+ e/ ~0 D2 {" ]) q: C) i6 U2 a q
td1 + pd.Timestamp('20200101')+ g$ \- h) w5 |' _% l( b
Out[80]: % }: S. ]: T# u+ E4 K
DatetimeIndex(['2020-01-02', '2020-01-03', '2020-01-04', '2020-01-05',
4 x# U; f( p/ q- J- g/ `0 c5 g '2020-01-06'],dtype='datetime64[ns]', freq='D')0 z5 z" \9 m/ S% s
* |9 V& H9 p9 H- htd1 + ts # 逐个相加
! \6 D9 [7 y. E& j; j! A. A5 Y4 nOut[81]:
7 w3 K9 x, F9 v# E0 P1 X& T( bDatetimeIndex(['2020-01-02', '2020-01-04', '2020-01-06', '2020-01-08'," \ B% _/ }# ~6 ^: m
'2020-01-10'],
' d& _* `8 y0 N- z6 F K+ R X9 l dtype='datetime64[ns]', freq=None)
9 j) m2 A( K' w- E* a! q6 Z) h0 [( ]4 v: D4 s `
1& W3 V% `3 d3 g
2
; Y- C, P, ~) A3 s& d+ L, G$ H3
8 r' o4 x& P2 M: ^2 W& y6 t* G6 V4
( j" ]# Y7 B" P7 n# R, S5
: D, O) M6 f# S+ F6
6 z6 C2 L, I- C8 B3 F7
1 E2 U) m; y5 u" [, u3 p8
6 f) {1 G* ?9 {! \9
3 y, P4 g a2 x& x( ^* W3 N10
. ^( e* X5 i5 q! S4 U/ m! ~11/ f+ D8 N; G$ S2 j. R4 ?3 {8 t5 P
12
( x+ A0 F4 n v13
, z I+ B3 i: L& }14
' s# }4 x" u* W( g# `. Z2 {15
/ q x0 S/ t3 x$ j x4 o16 N7 K6 q' F) U
17) l& E: H2 r0 R3 L# c5 y
18
- F+ G% P: B7 q _192 @. Q' w# f& w
20% d& E8 R. t3 y& Y
215 _9 ?0 K' O- C+ y/ G
22/ ]; ?$ C1 f2 p. X6 X5 [" J$ o
235 u u1 Z! b1 M9 H3 j7 V
24# N( z2 e% Z) `, E, i+ ~
25
7 T3 f6 i6 z5 t+ \7 ^ n26" }2 M8 ]. S1 Q# p! Q
271 }6 L1 Y+ z1 a6 Z
28
0 B+ q, B% k3 _3 r* R1 S10.4 日期偏置
3 x ]/ N% B9 `7 w10.4.1 Offset对象# z5 q# Q/ }7 f9 e3 @
日期偏置是一种和日历相关的特殊时间差,例如回到第一节中的两个问题:如何求2020年9月第一个周一的日期,以及如何求2020年9月7日后的第30个工作日是哪一天。, r* ^0 y( a3 n+ z7 Q+ X
3 i% m% T3 V. t. ?! IDateOffset 类有10个属性,假设s=pd.offsets.WeekOfMonth(week=0,weekday=0),则:, A4 J- V% @( U& ~! R
/ O* ~' T0 J. j8 Ds.base:<WeekOfMonth: week=0, weekday=0>,返回 n=1 且所有其他属性一样的副本
4 r; J# T* T# q* |" Ms.kwds:{‘week’: 0, ‘weekday’: 0}9 a% N r7 w/ k Z; D( F2 b5 `
s.wek/s.weekday:顾名思义8 M5 D% U1 b. @* k/ ]
有14个方法,包括:: ~- G; W# b- Y8 }: N# V$ r4 \; q
) ^* ?4 @0 z; Y' f/ JDateOffset.is_month_start、DateOffset.is_month_end、DateOffset.is_quarter_start、DateOffset.is_quarter_end、DateOffset.is_year_start、DateOffset.is_year_end等等。0 m' L- t# i4 H" }8 a6 C
pandas.tseries.offsets.WeekOfMonth(week,weekday):描述每月的日期,例如“每月第二周的星期二”。
9 c8 t+ ~- i% E3 Z% h" N2 K. O
" F: Y. c X3 B* L- v: H有两个参数:/ U8 m2 r6 |$ j& A
week:整型,表示一个月的第几周。例如 0 是一个月的第 1 周,1 是第 2 周,以此类推。* I2 r' J, ^, G/ H9 ~; C
weekday:整型,取值为[0,1,…6],表示周一到周日,默认取值为0(星期一)
2 ?$ o+ j$ c p' L. _2 I" mpandas.tseries.offsets.BusinessDay(n):相当于pd.offsets.BDay(n),DateOffset 子类,表示可能的 n 个工作日。$ N* P- ?" p; ?7 b$ j
% U3 O% G0 L0 @4 T" c, w
pd.Timestamp('20200831') + pd.offsets.WeekOfMonth(week=0,weekday=0)
$ Z! l; j Y: x/ M% q: Q, hOut[82]: Timestamp('2020-09-07 00:00:00'). C. I( {+ L; w2 G6 ~2 j8 q- b
, p# J& g" q% K) i6 M. Q2 M
pd.Timestamp('20200907') + pd.offsets.BDay(30)
; e% ~/ w+ O! y3 MOut[83]: Timestamp('2020-10-19 00:00:00')
' a9 Q) m& p, B H& E% y* C; ?1& s6 {& ^! ], ]5 ^ [# d& ^
22 h+ w- W' L+ H; |4 k1 Z' \
3, \' j1 w( M% s. T4 a4 f3 J
4( \ i& M* k& {' t7 n9 x! s
5, c+ F- }+ \; g& e0 k/ w+ ~3 `
从上面的例子中可以看到,Offset对象在pd.offsets中被定义。当使用+时获取离其最近的下一个日期,当使用-时获取离其最近的上一个日期:* E& n' q6 h7 l# m0 L& K4 q" Q& i
2 F5 s4 v# C( Q5 b
pd.Timestamp('20200831') - pd.offsets.WeekOfMonth(week=0,weekday=0); y1 g4 |& q* h1 F, E
Out[84]: Timestamp('2020-08-03 00:00:00')
0 d- k/ s! p0 Q3 y
# b4 Y7 q E" H$ g- U9 S2 p7 Xpd.Timestamp('20200907') - pd.offsets.BDay(30)6 U( Z7 z( t8 J$ X
Out[85]: Timestamp('2020-07-27 00:00:00')8 b" l: P/ k6 i; ` X
y# O! j0 x y
pd.Timestamp('20200907') + pd.offsets.MonthEnd()
: l E, f- O h+ G4 ^2 HOut[86]: Timestamp('2020-09-30 00:00:00')
$ k& j* l4 x( V; N- Q1* P. U( e& l% V$ K6 q( Y/ N
2- D. U: r7 \) J9 M- X
3
+ l, J- I1 N7 ^; C! m t) k4
/ x/ _3 N- `9 M) s# }5
/ ~, }! n x& Q. Z# n6
1 f, r8 J w, l6 R( s7' v. _' p) S* `3 `% [. N
88 S! h7 ]) M& j4 z2 F% P% o
常用的日期偏置如下可以查阅这里的DateOffset 文档描述。在文档罗列的Offset中,需要介绍一个特殊的Offset对象CDay。CDay 或 CustomBusinessDay 类提供了一个参数化的 BusinessDay 类,可用于创建自定义的工作日日历,该日历说明当地假期和当地周末惯例。) y" \/ a0 K: f$ F8 \) ^' A( z
其中的holidays, weekmask参数能够分别对自定义的日期和星期进行过滤,前者传入了需要过滤的日期列表,后者传入的是三个字母的星期缩写构成的星期字符串,其作用是只保留字符串中出现的星期:1 y# a* J" R5 ^8 y4 X: V. P
9 A; X1 J3 u% F
my_filter = pd.offsets.CDay(n=1,weekmask='Wed Fri',holidays=['20200109'])5 ?' i' E3 X/ h9 O6 C" r
dr = pd.date_range('20200108', '20200111'): Z9 V& d& l& H0 g/ @0 n
. N) [7 @& i+ |3 A# U1 ~! w" Q
dr.to_series().dt.dayofweek8 v% s/ r+ G+ {/ ]1 K# v3 H& \1 @
Out[89]: # I3 M8 C8 e# {1 L, m$ | }
2020-01-08 2
1 Q: ~# M" i+ r+ C5 v2020-01-09 37 U( k0 a5 D1 I" W E0 q1 s* m0 K
2020-01-10 4
5 `" c% Y/ Y' i; L7 S" o4 z3 u2020-01-11 5
: Y4 a6 T1 V2 `0 y2 ?; DFreq: D, dtype: int64# _: I u+ x/ Q1 Y4 z9 |) p) a
- c! _+ P. ^, F) G( q, Q7 l[i + my_filter for i in dr]: C9 u+ E# B1 |9 @ P# z O
Out[90]: ! l$ N* V5 Y2 T$ Y" b
[Timestamp('2020-01-10 00:00:00'),
9 c7 r9 {% \/ p) J+ i" p3 G Timestamp('2020-01-10 00:00:00'),. a/ K8 m- G Z' S2 S- c% D
Timestamp('2020-01-15 00:00:00'),( D0 p* j9 M# L$ D
Timestamp('2020-01-15 00:00:00')]
+ W F( I2 |2 c G2 x
7 J" z( p4 [8 s2 S* L# k1
+ z/ [" i2 q: I6 g8 ~3 v% x, \* f2
9 B {. c3 [' Q3$ |5 [# f- M" ?& Q
4- J1 ]& d& L* s- O2 b
5
+ w+ n7 H: n6 E9 C6
0 Z7 B: Z9 i& W0 _* M77 S2 I- [/ b& k& f3 ~
8
# a, b5 G+ R ]& ~9
" b# Y, w7 U% {10
) r7 R0 U; U6 ]. S+ n; M% U11 Z$ X5 h5 s4 a9 q' p
12
" m7 F, C0 c9 Y3 p' M7 U- o/ F4 R" R131 l0 F" Y$ n, {: @* W3 G. y* C
14! d: e! ~5 b$ \7 x% H
15
$ h/ \4 P( c. {$ }( F; d) C) h160 B- b( u3 L! k9 i- |% m& c
17
2 r9 O/ ?- }& F$ V7 ^ 上面的例子中,n表示增加一天CDay,dr中的第一天为20200108,但由于下一天20200109被排除了,并且20200110是合法的周五,因此转为20200110,其他后面的日期处理类似。
' a+ h7 k3 ?: G) Z- T% Z3 E- {# @9 Z; v: ] o
【CAUTION】不要使用部分Offset0 S8 ~/ w' Y- [# g+ h& j3 ?' R+ r4 A
在当前版本下由于一些 bug ,不要使用 Day 级别以下的 Offset 对象,比如 Hour, Second 等,请使用对应的 Timedelta 对象来代替。
, E* w7 [& p H. k5 l8 C% ?3 d6 C9 {4 ]6 @( \
10.4.2 偏置字符串) j8 A$ @* _/ S$ T
前面提到了关于date_range的freq取值可用Offset对象,同时在pandas中几乎每一个Offset对象绑定了日期偏置字符串(frequencies strings/offset aliases),可以指定Offset对应的字符串来替代使用。下面举一些常见的例子。1 C6 G: \: B0 r7 n) f
, E1 L0 Y" h0 }$ Y; M' G Offset aliases:pd.date_range函数中的freq参数,为常见时间序列频率提供了许多字符串别名。 也称为偏移别名Offset aliases。偏移别名列表点此参看(大概27个)。" b) W3 e1 E) W0 ]+ x% c
& G% x |! J1 Y. ]% epd.date_range('20200101','20200331', freq='MS') # 月初- O$ k4 ?' m8 c8 A! i; ^1 ]
Out[91]: DatetimeIndex(['2020-01-01', '2020-02-01', '2020-03-01'], dtype='datetime64[ns]', freq='MS'). O* p5 T& K6 ?1 c! C
4 l( \2 \3 f0 G* X4 X3 Y6 V
pd.date_range('20200101','20200331', freq='M') # 月末3 Z7 i- L) S7 a% o6 u0 W5 D
Out[92]: DatetimeIndex(['2020-01-31', '2020-02-29', '2020-03-31'], dtype='datetime64[ns]', freq='M')
7 J( M# }7 K2 }5 x9 E+ k( ^8 t; t7 ^! O* T+ W! ~! Q
pd.date_range('20200101','20200110', freq='B') # 工作日$ g7 f3 R+ d0 d4 e
Out[93]: 9 G4 Z$ \% x+ I- ^) G
DatetimeIndex(['2020-01-01', '2020-01-02', '2020-01-03', '2020-01-06',9 T" j* _3 D @3 T `5 E9 a/ P
'2020-01-07', '2020-01-08', '2020-01-09', '2020-01-10'],
; Z, E; D0 O1 `) n dtype='datetime64[ns]', freq='B')
7 n9 ?7 b: h: d
$ I1 h2 x$ B1 a" y/ {7 m# `+ F1 zpd.date_range('20200101','20200201', freq='W-MON') # 周一
0 x1 B& Z" @. R! tOut[94]: DatetimeIndex(['2020-01-06', '2020-01-13', '2020-01-20', '2020-01-27'], dtype='datetime64[ns]', freq='W-MON'): {% ^2 y/ C6 l- k- {
+ h# y" d( u6 Q1 d6 R* i
pd.date_range('20200101','20200201',; n, \. b) X# {% W+ \
freq='WOM-1MON') # 每月第一个周一
/ Y+ K! x2 A0 Z
/ `" B) `/ N4 h% e$ B8 u8 R5 OOut[95]: DatetimeIndex(['2020-01-06'], dtype='datetime64[ns]', freq='WOM-1MON')
" y( {, {2 \: w! v: l" h1 U- {% t, e) B" b) @" L+ M$ V
1: U" @# E5 ]: Q5 f" g
2
" h( p3 v. H# G7 c- I3
T# v% _! m: \4! H! E1 ?! @ M# T7 ^7 Z0 _
5' b2 i0 D" K" |5 Q* i/ P
6
6 S4 i. G# `8 X [3 [7) L" a8 t" Y9 {* M: F+ k0 ]
8# m; s, r) f$ ^: _' q* D, F% I$ t! p
9
7 D3 d, s* ~. E8 H; B. v% m10
g/ L4 M2 k) O/ ~( R* q4 J! O! H2 p11
2 U W, J8 R6 J- z12% f+ y) |6 Q# S& Q* {8 J
133 s! ^% n) t% \0 N* U7 O: `, w8 r! H9 W
14
2 R1 a5 s1 Z) u6 _& d6 n15
1 L' K. R+ A3 ?16
. X$ B2 ^2 Q8 r2 n- @/ M j17
1 v8 Y, z& s, ~+ m18. Q9 H" p) I3 W) I
19
2 _, A G; \# b$ P上面的这些字符串,等价于使用如下的 Offset 对象:
% d) R. ^8 t& k% m+ k8 D4 F. q+ o! c+ K' D7 n- E' U, B+ B" B
pd.date_range('20200101','20200331',
# x& C% @: {, K, e- E- b5 o freq=pd.offsets.MonthBegin())
* |* ], Y7 O3 P. s# C, S8 \
& k- g: z/ j$ \1 M1 {Out[96]: DatetimeIndex(['2020-01-01', '2020-02-01', '2020-03-01'], dtype='datetime64[ns]', freq='MS'), O6 @ I8 E: l$ d# E* i
) L: Z0 \+ Z Y" t& E
pd.date_range('20200101','20200331',3 q+ q$ ]3 h/ p2 K7 Z
freq=pd.offsets.MonthEnd())9 l3 D( p. ?* H. p
' t2 O: p* k- I4 i6 x$ bOut[97]: DatetimeIndex(['2020-01-31', '2020-02-29', '2020-03-31'], dtype='datetime64[ns]', freq='M')6 h4 P {) u( C: h' `: w
% O- [" O `$ A4 n/ C, xpd.date_range('20200101','20200110', freq=pd.offsets.BDay())
- y H# a% Z- U1 ^Out[98]:
3 a# }( z$ j& ^1 K: u( q; a# B: bDatetimeIndex(['2020-01-01', '2020-01-02', '2020-01-03', '2020-01-06',7 ^% p, E% l1 C3 U
'2020-01-07', '2020-01-08', '2020-01-09', '2020-01-10'],
1 u# r/ `/ N/ ]+ @% l3 P/ s- C dtype='datetime64[ns]', freq='B')9 l3 H' n' G# w3 P# j
9 B$ W# V e% R. m( `9 Lpd.date_range('20200101','20200201',
( h" s3 l" U9 Q b# P$ j+ B freq=pd.offsets.CDay(weekmask='Mon')) @/ J9 h! g8 P! ~
5 Q: x) N0 z! }: ]( e5 B9 Q% L
Out[99]: DatetimeIndex(['2020-01-06', '2020-01-13', '2020-01-20', '2020-01-27'], dtype='datetime64[ns]', freq='C')
; T0 Y* f, C" t) z( q c& Y* i, k
pd.date_range('20200101','20200201',7 R& k9 D6 Q! w8 r: Q' l) e4 I) j
freq=pd.offsets.WeekOfMonth(week=0,weekday=0))
6 u* o* F9 U% y9 h) k. Y% B7 a# H, _2 Z1 x$ y5 u
Out[100]: DatetimeIndex(['2020-01-06'], dtype='datetime64[ns]', freq='WOM-1MON')
3 e0 _# v& l2 o! H) V2 F; V1 v! d+ q4 k2 Q' U1 e; S1 |6 k
1
" B4 Q8 [0 b: P2- D- H7 f/ P) N8 `* Q
3. T4 ]: Q$ a! d' \5 R& U) x/ j: a1 M
4
% q- O# ~4 n4 {3 ~' p5
: O6 |3 ^3 n: Q# A1 A+ B9 d3 F, Y5 _6
' \" D& a) D' Q! v9 l/ N% X7
% C/ x" R2 G" U6 ^8 j/ [9 [8+ y0 U/ [- N, Y$ I+ k7 `
9
. _- C& Y \- z x$ j. i10
: r' I3 S( E# ~' \1 H. ^% D11
2 s7 {" g, ?7 w2 B. ?+ U' q2 N12# `1 P* }) T$ x4 Y( \
13 b" N3 w4 Y. S- D* h5 N4 n
14) l( E2 X3 x( O& m0 m% o
15) z" g0 S( g$ ~# E0 P, E) j- L% R
16
- J8 I# b5 y2 k! I e177 a+ R' C, W n9 O9 y
18
4 N) A' V# T/ i% q19
: ^9 V! A) c5 O% D& l M20& L. `7 E( ?3 p0 D3 ^2 G8 U v
21
, b3 q5 o5 S$ `4 _6 o221 W1 D0 \- g9 g) q4 b0 n" U* T9 F# b; X! E
23
; Z6 ~5 `" N( C1 f6 s! B" I8 R24
$ d5 C7 m2 g2 {- V0 ^254 I) J/ ~( _1 x. B" Q! N$ K
【CAUTION】关于时区问题的说明
6 v6 g- `7 k L, X* a) Q 各类时间对象的开发,除了使用python内置的datetime模块,pandas还利用了dateutil模块,很大一部分是为了处理时区问题。总所周知,我国是没有夏令时调整时间一说的,但有些国家会有这种做法,导致了相对而言一天里可能会有23/24/25个小时,也就是relativedelta,这使得Offset对象和Timedelta对象有了对同一问题处理产生不同结果的现象,其中的规则也较为复杂,官方文档的写法存在部分描述错误,并且难以对描述做出统一修正,因为牵涉到了Offset相关的很多组件。因此,本教程完全不考虑时区处理,如果对时区处理的时间偏置有兴趣了解讨论,可以联系我或者参见这里的讨论。' C6 l& W# I: `& p
6 u( Z- f4 M5 |7 N5 V% q
10.5、时序中的滑窗与分组0 [+ \, J9 \) a! n/ _9 O
10.5.1 滑动窗口' q' @3 j" a' S, k9 V( J% V' N
所谓时序的滑窗函数,即把滑动窗口windows用freq关键词代替,下面给出一个具体的应用案例:在股票市场中有一个指标为BOLL指标,它由中轨线、上轨线、下轨线这三根线构成,具体的计算方法分别是N日均值线、N日均值加两倍N日标准差线、N日均值减两倍N日标准差线。利用rolling对象计算N=30的BOLL指标可以如下写出:
5 E; H+ \7 T$ o, W0 r4 ~" o% H4 z% R' o) |! T+ x) k |2 p
import matplotlib.pyplot as plt. Q# g. s" @0 [" L q
idx = pd.date_range('20200101', '20201231', freq='B'). s; o( _4 h/ d% S3 {6 {6 a" p
np.random.seed(2020)2 o& K( W) \3 D
$ G2 ?3 B$ @" Q, [& L5 q, K
data = np.random.randint(-1,2,len(idx)).cumsum() # 随机游动构造模拟序列,cumsum表示累加+ _3 {1 L6 e3 g- U$ M2 k
s = pd.Series(data,index=idx), j) g1 F$ M5 ~7 w8 m
s.head()
1 }+ R( n. X$ f) zOut[106]:
; ], {- x( Y6 Q0 H8 B. x3 ?2020-01-01 -11 B0 b3 [: a3 t( n6 d
2020-01-02 -2: @0 _% O9 K- N5 T. j0 \
2020-01-03 -1$ r& p6 V* z1 V' x) ^7 {) o
2020-01-06 -19 R; U$ v" m6 a; w: N% |
2020-01-07 -2
/ g. V( _2 W/ A( v# P( |$ O% o3 IFreq: B, dtype: int32
1 u# ]& r0 K" @+ ]" ?+ z; br = s.rolling('30D')# rolling可以指定freq或者offset对象; ^5 O! z4 ?6 Y+ p" v% q
* `% P _7 _ I
plt.plot(s) # 蓝色线
3 x9 q* ]2 R7 w+ c+ \Out[108]: [<matplotlib.lines.Line2D at 0x2116d887eb0>]' O9 T7 |& v$ W7 @6 q7 J
plt.title('BOLL LINES')# w" e, i, @- }. [4 J. T7 L& g
Out[109]: Text(0.5, 1.0, 'BOLL LINES')
9 u3 l4 o7 `* F O) ], [
4 x- ~) y# j; c& ]plt.plot(r.mean()) #橙色线6 T& X/ O8 k% F- x* ~
Out[110]: [<matplotlib.lines.Line2D at 0x2116d8eeb80>]
0 _0 |. c, Q7 x( K m
" ?, G% N. p: V6 Hplt.plot(r.mean()+r.std()*2) # 绿色线
; [, ~5 A F: W9 l: S" HOut[111]: [<matplotlib.lines.Line2D at 0x2116d87efa0>]
2 g/ h# H9 A0 ?5 J2 P& H2 @! n# _2 g( D- r
plt.plot(r.mean()-r.std()*2) # 红色线' A' w: z/ O) P! S3 j
Out[112]: [<matplotlib.lines.Line2D at 0x2116d90d2e0>]
# F0 x+ T* ~5 L. N& b7 f: \
- {' W; {9 h* ~9 _, O6 K1
4 ~9 m' i0 [8 `; k. n: C2
& _/ Z& q& X8 c) J2 g6 r- W% ]# N3) R; v. R: ?: [- \) q+ }/ a- X
4
9 T3 u3 ~/ g z4 o2 U" y' X54 V N7 x7 z c
61 r8 L* v+ c' O- I/ \
7
' ^! x2 _1 ]3 D$ X0 @8
Z5 L. V0 F7 {/ U+ A0 b9. X$ M9 q/ w, i6 x& d
103 A" I( ^- E" W) K" a! I: U3 S- G- q
11
" x% @$ {# [8 ^# f q( N' n12) ], k, ~" O6 f6 \8 q& ~
13( N$ d% u1 s: E1 f/ Y
14
[, ?1 z( \9 y, t15
' Z8 g: q& a X; E16+ b9 ?, v9 W1 m, l. r
17
! }8 i! u* s' X2 v$ t& v18
. r$ I9 U6 r; R19
4 A! P) G" V0 a. u; g1 x5 k0 v20
1 {! F; i# p& Y1 N! J21
: A* i& I% Q( w22
0 {' [# I1 X) ?; _# x, e# U23
]! k- C x5 s; Q, h24; a7 I4 x& }. ]3 y, Q/ b
25- E c) i$ U0 M1 o9 o+ H! p) g
263 Z% w/ ~" }2 a/ a7 A
27
- W4 m* M7 f# s3 }. v28
$ [& B7 X' X. M% U, b+ S291 Z+ {% q2 x4 U9 {" u! q
8 q5 I$ v2 Q2 T
这里需要注意的是,pandas没有实现非固定采样频率的时间序列滑窗,及此时无法通过传入freq字段来得到滑窗结果。例如统计近7个工作日的交易总额。此时可以通过传入多个函数的组合来实现此功能。; s; Q; a4 |1 e- x
首先选出所有工作日,接着用普通滑窗进行7日滑窗加和,最后用reindex()恢复索引,对于双休日使用前一个工作日的结果进行填充。5 F, T( H& ?. c8 C( K" ~
& C; w5 J9 w: x- q: }9 Kselect_bday=s[~s.index.to_series().dt.dayofweek.isin([5,6])]1 s% o3 x+ H* h$ k( C6 x6 G
bday_sum=select_bday.rolling(7,min_periods=1).sum()% o) N7 D: ]/ o8 l' d9 F
result=bday_sum.reindex().ffill()# G/ s$ W1 I6 o4 u- D
result
4 O. p8 X5 M: N
+ ^) k( r$ j. L2020-01-01 -1.0
0 d g. g% L5 ?9 _/ \2020-01-02 -3.0
' I4 o: N# e% k/ z2020-01-03 -4.0
, P% s# D9 t6 d& V% S. h9 f2020-01-06 -5.03 R: J3 W" `& `4 {
2020-01-07 -7.0! s) n3 z6 l" f6 v9 @# ^
...
- i# R2 e% k& J1 g3 G/ F2020-12-25 136.04 `0 A- Q$ O6 E5 c9 F1 S
2020-12-28 133.09 v2 n; \8 q( L& E4 Y
2020-12-29 131.0- h d7 K- b% u$ ^
2020-12-30 130.0# t$ P3 E- v# d' W
2020-12-31 128.0# _0 ?+ ~# [/ a6 j% i) {
Freq: B, Length: 262, dtype: float64
3 N0 w; w1 R w! B$ l. }3 m! G* N& X# A& S: V9 D! O
1! O D3 u1 H4 f$ _: C/ ~6 \
2: Q; b. Z4 z/ Z+ r5 C/ i% a& }
3. o, o* w S$ K/ z4 x7 z: E
4
, L4 G) `' h$ ~) H# ]1 e5
* Q& w. e9 l. a" I8 [% q- U67 q: M; t% G9 g4 l
79 a0 q6 z5 \, p, C
8
' t4 `; b# O6 x3 w9 A+ C: `9% ^' I- r$ H$ A7 A f& i- @& O
10( D9 ^0 \0 m/ M6 ~9 q
11* I: o0 m8 H" a
12- a) ?/ ?1 p7 _' O/ e" r$ {
13" ?$ f( W) i4 r$ H$ _# Z+ W
14
7 c& Q7 H+ H: |+ ~ B15
' Z- i5 i/ |- O, E0 M+ G/ M% Y16
; t3 u; g! j/ D( Q2 J* [17
4 x& W) {, c' U shift, diff, pct_change 是一组类滑窗函数,它们的公共参数为 periods=n ,默认为1,分别表示取向前第 n 个元素的值、与向前第 n 个元素做差(与 Numpy 中不同,后者表示 n 阶差分)、与向前第 n 个元素相比计算增长率。这里的 n 可以为负,表示反方向的类似操作。
]/ O# s! @+ a! w! r: b' }' G: Y& p Y% ~1 _
对于shift函数而言,作用在datetime64为索引(不是value)的序列上时,可以指定freq单位进行滑动:' W; G8 z4 U. B1 v! j
1 S+ K" t3 u4 L5 [0 g0 A9 hs.shift(freq='50D').head()" v1 e1 G" q, _/ ^+ v8 r0 M6 p
Out[113]: 6 d p! H/ Z! ^- D* `
2020-02-20 -10 q. Y. ~$ {3 g$ ^" T' r& K
2020-02-21 -2( L2 A2 t w6 E c) y* Y, g( @% o
2020-02-22 -1
3 |4 T! I5 B3 F$ k2020-02-25 -1" l# j6 b- \) j0 H+ E
2020-02-26 -2
$ d' ^3 g, u. \dtype: int32& K; g! x( [3 L! k
11 P& l/ J- w6 u7 b. i* N9 X3 O$ J
2( x4 D# y8 x$ n
3
7 O/ M$ s4 }% M- ? d* ?1 S4
$ j- }5 |1 c( Z" `) v/ k5' t3 K8 `& d2 s8 O, { n
6* w! J R3 R9 l }
7% U7 j* p" \& f0 z1 R% }+ d) t
8# ?' O6 C8 O) B# M* M% O8 L0 f! o9 }; U
另外,datetime64[ns]的序列进行diff(前后做差)后就能够得到timedelta64[ns]的序列,这能够使用户方便地观察有序时间序列的间隔:
# ~! N' n5 P' l2 f. S5 A" N* I- d/ B7 O- T* P1 ^
my_series = pd.Series(s.index)
2 N& u: K+ z; S) hmy_series.head(). k, [* ?) B* p
Out[115]: . T3 z. t1 u5 f% E2 w v
0 2020-01-016 J+ f( f0 E. v( S
1 2020-01-02+ n4 B6 j; N% `
2 2020-01-03$ K- U9 w4 \8 B
3 2020-01-06' k" T9 s; o" `
4 2020-01-07
' P1 ~! ?5 o3 e$ I% h1 R6 xdtype: datetime64[ns]
1 i: P1 ]7 i1 ^. l. D+ f, `" c, R, r& Z, P
my_series.diff(1).head()8 m& [2 C9 ?# Z0 z) M
Out[116]:
% l& e" G1 @& a1 d5 Z+ [9 H0 NaT R" W4 A8 ]! L- u' T7 k
1 1 days Z# j: x5 v* X
2 1 days
. m! Q7 L$ \& P8 c( J0 w3 3 days
1 W- D* O" B' `4 1 days% d! G6 c+ r3 E/ m8 _& c1 c4 ?& P2 a
dtype: timedelta64[ns]
) x' I( Q' f c. Z5 o% S8 B
2 O" U) a. `& N- `1( f$ J& e9 Y% B5 H
2
) `! F; q0 D% G$ G3- d7 c7 {6 t$ j+ l& i
4) u3 |: M, _% M& z6 S
5( o* \$ H% V* ]9 p
6
* j8 m& f7 s) O7
" j( K. i2 ^% e: Z4 b/ J) @8' D4 |+ ], S7 M" Z: c$ E
9
3 s6 W2 p( i' {+ ]103 x* K. A" `1 ~" |7 F
11& X5 }& b H, ^" `
12
M8 K$ E" o+ f0 J13
' V, b4 W+ X1 h& Y& N14
7 i& L9 u4 _$ Y( s8 v! b159 s" K# V! D% Y# s' u! I& n
16+ D0 m6 @2 u Z- |* k
178 c) q$ F. k% y; B2 f; o y' p
18& h6 o4 L! ]8 ~9 e* V
10.5.2 重采样
6 a' Q9 N- }2 C8 o" p/ y+ U 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)
) w7 D) M& |6 C常用参数有:$ {3 k, ?. H& ^2 Y1 v; T& S
% M& H2 j- z& B2 `. \, u0 I- ]% |rule:DateOffset, Timedelta or str类型。表示偏移量字符串或对象5 a5 N( j* g3 S8 L
axis:{0 or ‘index’, 1 or ‘columns’}, default 0。使用哪个轴进行上采样或下采样
# A! b9 u- d7 ]5 C; Fclosed:{‘right’, ‘left’},默认None。表示bin 区间的哪一侧是闭合的。所有offsets的默认值为“left”,但“M”、“A”、“Q”、“BM”、“BA”、“BQ”和“W”均默认为“right”。
+ a; i! z; a; K# b: u/ vlabel:{‘right’, ‘left’}, 默认 None。hich bin edge label to label bucket with,所有offsets的默认值为“left”,但“M”、“A”、“Q”、“BM”、“BA”、“BQ”和“W”均默认为“right”。- {: L4 @* W; J+ J( G) v5 `& y
convention{:‘start’, ‘end’, ‘s’, ‘e’}, default ‘start’。仅针对 PeriodIndex,控制是使用rule的开始还是结尾。
" j+ Q! p7 |/ f* W; b$ Ron:字符串类型,可选。对于 DataFrame,使用列而不是索引进行重采样。列必须类似于日期时间。+ H2 a# B5 Q o6 ?3 B8 d1 f. b
level:str 或 int,可选表示多重索引MultiIndex的级别,这个级别的索引必须类似于日期时间。7 G. w( m5 K: o& @4 e
origin参数有5种取值:
]5 c* v# f' J7 `% i/ }‘epoch’:从 1970-01-01开始算起, f( B9 i4 d R( Q- Q
‘start’:原点是时间序列的第一个值8 U, W# @' V( H/ U u9 V9 r4 R
‘start_day’:默认值,表示原点是时间序列第一天的午夜。& ~3 _/ }' C) t! v6 P2 q0 o y( z
'end':原点是时间序列的最后一个值(1.3.0版本才有)
/ J. m' @) f P‘end_day’:原点是序列最后一天的午夜(1.3.0版本才有)
/ L# v7 U$ b7 E+ |# `offset:Timedelta 或 str,默认为 None,表示对时间原点的偏移量,很有用。, G. m- V; }2 a. U8 S' x
closed和计算有关,label和显示有关,closed才有开闭。
, i$ r. p) R4 m% Z1 C# n0 p& P label指这个区间值算出来了,索引放区间的左端点还是右端点,closed是指算的时候左端点或右端点是不是包含。! M% \" |$ Y9 {- E& K9 x% _1 V+ ?2 ^
$ k2 f' T4 C+ ~" B: y7 T重采样对象resample和第四章中分组对象groupby的用法类似,resample是针对时间序列的分组计算而设计的分组对象。例如,对上面的序列计算每10天的均值:
" M, b4 [ y% B: i* Y4 xs.resample('10D').mean().head()
- L( w6 k5 x4 m0 I2 d4 |; Q* @! QOut[117]:
$ N( J5 g+ d* |' v1 x2 K2020-01-01 -2.000000 q" F2 ^/ H6 C3 D7 j2 |- j/ n
2020-01-11 -3.166667
: e+ Z% y& k# T w2020-01-21 -3.625000
' l* x9 k F2 ]: t& p% N2020-01-31 -4.000000
- g4 ]+ ]+ W' T3 Q# c2020-02-10 -0.375000
- F; M6 C0 m3 L4 v' NFreq: 10D, dtype: float64
q3 k( X! z+ `! [. U4 g, Z1
8 X- g9 n7 n4 W h26 M/ ?% [9 `1 Z
30 y' N) S6 T# K4 ~& k X: N
4
2 @/ L: n, Z' ?& k# U8 d' U3 F5
4 I) d/ ]: F1 c- f+ G$ X6 {6
6 h; y, w% {! r% P7; K3 g, ?. _4 U4 X [, m
8
* I/ p" ]& r: r4 X, V) q. h可以通过apply方法自定义处理函数:
! P- _+ M9 a$ ~4 T0 E' O7 e; Ws.resample('10D').apply(lambda x:x.max()-x.min()).head() # 极差" R% h! x! [, ~# i
0 ^6 \' [/ V0 P5 Z7 JOut[118]: - j7 o2 B1 b! i7 _6 S% b( Z7 m; }
2020-01-01 30 W% ], }- K, A2 U
2020-01-11 4
' Y$ N! Z+ f" p6 d1 S9 Z2020-01-21 4
) I% M4 A; r* F2 N6 B9 j2020-01-31 2$ w' Z6 y* Q, O3 ~+ k
2020-02-10 4, Y* d( p3 l e; ~8 k6 B+ L
Freq: 10D, dtype: int32
, v2 m, B/ T& b. e, S/ b/ |1
! O0 z3 J9 `4 J% G2 O" w& A! ^; E" ~2! o$ f9 @" P2 V( a$ a
3% O( R8 M5 M: T7 W+ N: P8 V
4
; J( I" n8 ?% M; d5
5 Y+ o; ?" Q, D. D# e6
- M4 X" X3 H% ?' C3 q, q7
, O; a, ]7 s( T+ {8! Z! @, [- U+ E% ~; M- L
9
* g3 U9 q( a# o/ F& _1 Q 在resample中要特别注意组边界值的处理情况,默认情况下起始值的计算方法是从最小值时间戳对应日期的午夜00:00:00开始增加freq,直到不超过该最小时间戳的最大时间戳,由此对应的时间戳为起始值,然后每次累加freq参数作为分割结点进行分组,区间情况为左闭右开。下面构造一个不均匀的例子:
4 H2 v) D1 V& [! T" [' T6 a7 B) ~: Q2 x# {
idx = pd.date_range('20200101 8:26:35', '20200101 9:31:58', freq='77s')4 k6 m/ w0 O7 |
data = np.random.randint(-1,2,len(idx)).cumsum()
; m0 E! Z% _2 l+ J) m: Ms = pd.Series(data,index=idx)
* @: _2 K3 T8 D9 is.head(): |' o+ q6 P& b0 E% a0 U$ c
+ v6 @/ A7 @5 E2 ~# U3 nOut[122]:
: [% Q8 J, h# o" ~) j2020-01-01 08:26:35 -1" y( m. s. I: s
2020-01-01 08:27:52 -1! p' j% d, m4 ?$ s: o# D( k
2020-01-01 08:29:09 -2 C- {, G% q0 T+ z" H1 P) |
2020-01-01 08:30:26 -30 @* J5 ~* g% m: V3 m. h
2020-01-01 08:31:43 -4+ M0 L; _1 `5 l' T
Freq: 77S, dtype: int32/ O: U% |! A- p9 H$ `7 P" V
1
" t/ `" \+ Q4 _/ s, @" A4 @) Q2
( ?, u) f3 {. S( h3
1 C* E! d6 e$ {2 ?: D42 a/ q- |- ]) ~# |7 D0 {
5
3 c% e3 { d D9 M4 |8 F: k6
1 u4 E8 c2 l" [& L6 _3 m7 s4 `# `' q7
+ D' v! t9 M- |6 c! E8
1 K1 i8 i5 g! o9 J- |9 n; B1 z2 h% j, c' c3 Q
10
! z: M" i. x( T) @5 ]4 u% T5 D' |11/ ]$ ~# P8 m0 i$ \! O
121 h' d/ e+ Y9 f7 p, Z0 h6 X" d9 X1 n8 {
下面对应的第一个组起始值为08:24:00,其是从当天0点增加72个freq=7 min得到的,如果再增加一个freq则超出了序列的最小时间戳08:26:35:4 q# @( g6 ~# J
. V# W0 v! K% m* }* i
s.resample('7min').mean().head()6 F! R }6 p- j( X. _* d4 u
Out[123]: ) G, Y% `3 E- S6 ]' @
2020-01-01 08:24:00 -1.750000 # 起始值,终点值包含最后一个值: V @- y! e' w. W9 l9 G
2020-01-01 08:31:00 -2.6000001 L H# X9 L' Z) B7 c
2020-01-01 08:38:00 -2.166667
}1 ^1 a: r: I% N- T! x2020-01-01 08:45:00 0.200000
% H3 A! _) c, g1 N2 S& T$ W2020-01-01 08:52:00 2.833333- z* t7 l; Y/ K) @% {- u4 q
Freq: 7T, dtype: float648 ?5 I2 e/ q7 Z* M( \
12 ]# U! k) d4 T. T/ g. D
2
4 |9 R6 f) o% E' Z$ K. I0 k3
8 L5 j2 ~+ z# T4
` r# A7 Q( Z g" H. }" }5
* @5 l6 X6 M& @/ G65 m3 e3 |5 S k5 D4 {& Z" [
70 C/ J9 n, S+ I5 N* k, s3 s
8
7 t8 L6 p$ G5 }5 X 有时候,用户希望从序列的最小时间戳开始依次增加freq进行分组,此时可以指定origin参数为start:# H+ S- o% S& i5 G, G) j2 X
6 A4 m0 L& c; h+ j- ns.resample('7min', origin='start').mean().head()
% ~! O' m" f& D! h# _Out[124]:
3 C9 V4 [; |1 r8 _6 r2020-01-01 08:26:35 -2.333333
; d0 H) N& X* y I: y/ V: Y( x2020-01-01 08:33:35 -2.400000
Y6 I: x5 p+ W3 Z; n0 v; F2020-01-01 08:40:35 -1.3333330 \' m7 r2 R5 O9 N+ k- U9 B2 X& e! |( V
2020-01-01 08:47:35 1.200000
6 c6 i( [8 t6 c2020-01-01 08:54:35 3.166667
' ]- K4 r, G0 o4 a0 i( x* [' N1 T' TFreq: 7T, dtype: float649 `8 Z/ m$ N) U8 D1 d9 A
1; u. @! P) `3 J* k" i2 N8 S
2& n* z- o2 Q; G. i+ u$ l: f# d* _) Z
3
2 l6 x5 W8 z2 [8 E) k4& k* w) d7 ^: s& Q
5
0 [' k9 @. v4 {. o) U' D6
+ [- _3 _$ s6 ~$ k8 d7
) \+ X: v+ _$ B6 {% X" ^2 s8
! \7 s1 ?* p w/ W, k 在返回值中,要注意索引一般是取组的第一个时间戳,但M, A, Q, BM, BA, BQ, W这七个是取对应区间的最后一个时间戳。如果想要得到正常索引,用’MS’就行。
: `5 H0 }9 d( A1 P/ A! E$ L d* h0 G( Z0 ~( v- Q* r% M
s = pd.Series(np.random.randint(2,size=366),% k7 _* ^; l6 u/ P+ _" \
index=pd.date_range('2020-01-01',
- n! ^3 K) @0 d* w; j '2020-12-31'))
0 V5 Y; h, v1 W0 |% A) ?( v$ Z6 z( x F) \* `1 m! |
' J1 p5 Q* P0 Ps.resample('M').mean().head()/ b/ v3 v6 @( s7 K
Out[126]:
( V( K2 w5 O5 j3 a2020-01-31 0.4516133 R9 u( A: R. u
2020-02-29 0.448276! l' ~; P+ }' {4 l8 [( [% y) z) x
2020-03-31 0.5161297 g/ G$ i- e$ Z& A
2020-04-30 0.566667
& H/ N* O" g, C; ?& X& w# ^% |& C2020-05-31 0.4516131 q0 f! {$ T/ n( e# b, f, U
Freq: M, dtype: float64
1 v: @" y: G# d9 O; m8 d6 u; R L* X" f% A9 w9 J1 t' ~
s.resample('MS').mean().head() # 结果一样,但索引是跟正常一样2 `: r2 R2 Y. J O z8 {
Out[127]:
' U# X: j0 }. b& ^9 z2020-01-01 0.451613
" T* @7 K5 B+ p3 }3 U% v! _2020-02-01 0.448276
, O* {: ]( e1 L a- X2020-03-01 0.5161296 t# D0 r" |: P" h4 }
2020-04-01 0.566667
9 r/ R! v0 r7 H: D% X' T! J/ k2020-05-01 0.451613
! ?& R: P1 x! Y/ [6 L3 {Freq: MS, dtype: float64% o \% b0 I1 a$ M$ w$ g
5 J- z; p/ D4 d
1
( q" _4 F/ f2 H2) F7 j5 ]+ h* t/ t. \
3
5 ~3 G% e% a, f3 }0 {& {4
. V. a+ ], V* c1 x5( }( R2 s- V/ G9 G" ^, u
62 \) a9 i& U! u9 J# L: U
7
3 R4 w* T' Q. I3 ]; T8. y1 Z$ h, s/ X% K7 T
95 h8 X: r* m' ^- l4 D+ H
10
: ~1 P3 o3 g- t11. m- G. P3 c3 D9 G# g$ n
12
- Q( d5 \$ G) z( u# [4 A" m- Z4 f5 a13
* _9 \3 Y; h: p+ W. }14
& h i6 |. } R0 a0 u& i15
8 F/ u: g( z0 u0 D& u h2 N16' t. [# \0 V" Q, o8 g% s2 r1 i% ]: l
17! x" d' I" V C* C( i/ @! p
18
X6 }- B2 @; J7 I19
0 ]6 M7 z; `6 t3 T% A20
+ I2 f9 U/ {* H8 L& X0 b& t21
( g" G( D: c8 e& {0 }. [6 }226 b, L! d- F& i& @! g- c l
对于 DataFrame 对象,关键字 on 可用于指定列而不是索引以进行重采样:
) T( d" q4 O' x! \d = {'price': [10, 11, 9, 13, 14, 18, 17, 19],
+ e" `+ j3 V _: w v# Y 'volume': [50, 60, 40, 100, 50, 100, 40, 50]}. x1 T. {$ P! I% k3 i7 |* D7 {* P* ^
df = pd.DataFrame(d)% u$ c$ M9 i; ^: ^$ u6 y2 j# s$ D x
df['week_starting'] = pd.date_range('01/01/2018',: N+ i' v h/ n& H; r( r; j
periods=8,
( C+ N* {5 r U: ]* p2 E* v freq='W')
: P; m2 C( w, Mdf/ k+ H" p$ z4 e, R/ U
price volume week_starting
4 H. l* F" A D2 @2 _! _% B0 10 50 2018-01-07 c+ X& Q+ e1 T7 R$ w& s" B& h
1 11 60 2018-01-143 b/ Z* M0 H. V2 ?
2 9 40 2018-01-21
. @& }4 H/ n' G( M. t3 13 100 2018-01-28
`( j E+ X d$ t8 W2 P. s4 14 50 2018-02-04
' s4 ~# z+ u1 O0 K* I5 18 100 2018-02-11- x! f) b2 K5 d( }
6 17 40 2018-02-18. f0 P; q) x0 m1 X' R0 o& o
7 19 50 2018-02-25
( a' j' _( ]! E$ ]# T' Qdf.resample('M', on='week_starting').mean()! ^5 z, k! f9 V) k9 C
price volume% m8 e1 K* b# l$ a/ }
week_starting
6 f, B/ `4 d5 Y5 h' w) e/ R- M$ ]2018-01-31 10.75 62.5
$ D) V9 h: m& Q- _0 p9 y, y2018-02-28 17.00 60.0
. H5 i, |* c; t
! E% [( `* g3 k+ \1
* ^% x7 g. A P' c1 Q/ i2
V) Z% Z( }; k# T3 |2 P# ^7 M- \' s! x3
' G( H" ~ X8 r" E4
) e+ P* J% n) a# Q' w ^2 K5/ s5 P/ m$ v& A. a; Z9 t
6
# w0 F% Z3 K; l+ w, r7
/ @, z. U5 h- V. V; p8
& F; c, G8 Y0 }9" R, l: B& H# D- h
104 O. f3 `# W4 }9 d7 x5 k6 v
11
/ P' Q6 V0 {0 t% t) A, w! x12
6 G0 Z3 z. r; m. }6 M13
. B N: M. o, C; ^# @2 o14
$ s& O$ r3 e7 S! n8 [7 }: M15
8 y+ t/ U0 l( Z% m9 K' y16
& | F7 u2 S' c0 r7 ]. F5 a+ }17
, N/ S& v2 t6 u, d18
1 ^" j4 z8 s/ W' Y, B9 i19
3 |) A# M- z% L. z- {0 b* v201 v# t$ d" [6 L% M1 X7 ^
21 m. e5 n' n/ ?. D# M5 {2 K
对于具有 MultiIndex 的 DataFrame,关键字 level 可用于指定需要在哪个级别进行重采样。
6 V* }1 x% A% ^, ]days = pd.date_range('1/1/2000', periods=4, freq='D')
2 I& j1 |+ Z3 x+ v. Z7 j" Qd2 = {'price': [10, 11, 9, 13, 14, 18, 17, 19],# o; f( |. b# P- E% H; [5 T
'volume': [50, 60, 40, 100, 50, 100, 40, 50]}) L. w6 j0 @3 j; H) D7 K9 J
df2 = pd.DataFrame(
S `4 g0 l( ]1 y {( @% | d2,
* i9 T" K' }9 _' A index=pd.MultiIndex.from_product(/ F% @6 j2 U" d$ Y! N
[days, ['morning', 'afternoon']]4 L1 }& J# T/ }8 I& O
)8 z2 B! A9 N ?9 T& b
)
1 T0 K* R: ~1 E/ ~df22 ] ^* ^' Y- q# g4 A U2 g' e
price volume
) }0 k$ K; C/ _$ \9 O( o) o' R' {; l2000-01-01 morning 10 50( L& K; v4 L6 Q: C3 ~% ^: A
afternoon 11 60
. `+ L6 h3 W% m# C4 X# ]9 S2000-01-02 morning 9 40
: L2 W$ z7 B# a afternoon 13 100: A- e# f4 F, O- H* o1 _; m2 `
2000-01-03 morning 14 50
6 u; x. q. i5 |5 j afternoon 18 100" Y, J* a/ G7 Q! M8 y
2000-01-04 morning 17 40
( @" d1 s; G1 B" r afternoon 19 506 q# X& \; H) V7 o
df2.resample('D', level=0).sum()
; @: R. u, f" m6 x price volume
# D0 ?! @: z3 W# P- E V2000-01-01 21 110$ S; F- Z8 L. U
2000-01-02 22 1404 F+ P- q: E2 R' z* I3 t6 r
2000-01-03 32 150
$ M# p2 w: Y. A: @: p2000-01-04 36 90' w( w5 E" \% f1 B4 S4 |4 [- W
' O. N" @' t' @6 k1 n1& u0 L$ C5 i0 @9 W- ? ?
2
/ p( G; i4 w6 [3+ N2 V+ H. p: S1 {4 Y
4- ~4 C2 X+ \$ S' {4 G) ~
5/ E1 [- H4 z @/ [0 a! Z3 W R0 u
6
+ P, `: B. }- z0 B5 w9 R7
$ `& x5 I' F3 J4 F* h k1 }8- {* W2 K5 {$ W0 s2 V$ t5 X: @+ V; ]
9
9 [# C4 B; a( w' a# W8 _' d10
1 O& t9 n# X& Q3 }11: K9 V1 }# {9 W9 \( m& K, q
127 H2 s8 Z; X8 N! R& P
13
2 V7 H1 |6 `7 C1 T# f+ c14% s/ z, I' A+ |. S5 P& c6 ]
15 j9 a$ g+ z3 X) H
162 Y6 P: ]& f% f5 ^% L W1 E* C
17+ t ^. x( h( D+ ~! O9 J6 ?+ q8 y
18
8 E4 E) j2 Y: q! e+ I* X3 k) e19
& W$ _4 N% \' w2 R, U20) U& L/ i" Y6 {2 y L! I
21
( {" W: R, _' ] d& A7 P2 C& i222 S5 B) a3 M+ V9 i! L0 K
23" H6 n: U2 r: c+ Z; p
246 ^* S! ?% @8 a( I6 f; V! L
255 S3 s+ F' B7 s! e0 ^
根据固定时间戳调整 bin 的开始:
5 l3 D. ^. [& _0 L! C5 c1 gstart, end = '2000-10-01 23:30:00', '2000-10-02 00:30:00'
% d* l: J+ g' U9 krng = pd.date_range(start, end, freq='7min')2 q* I1 D+ m$ a1 p, _
ts = pd.Series(np.arange(len(rng)) * 3, index=rng)$ F! \7 d$ T2 q) A& Z- ~: M2 V6 P
ts, N" O# r1 T" \, j% O- q
2000-10-01 23:30:00 0
M( X9 w, X" f* x+ C) I. O2000-10-01 23:37:00 3: F( N) O% n, ? c. V* b- ]7 Z
2000-10-01 23:44:00 6( n$ X# D. x: |& X9 F h
2000-10-01 23:51:00 95 m' |3 V6 a; H! I* S
2000-10-01 23:58:00 12) l; O i2 ?* S- B! G. n* R" V
2000-10-02 00:05:00 155 }* o) d+ Q& K/ A0 [
2000-10-02 00:12:00 189 V* S/ ?4 `! x P* X9 {7 Z
2000-10-02 00:19:00 216 B9 w& t$ }4 x, c6 ^ u& g8 M
2000-10-02 00:26:00 24
5 n9 B, _" ]* |1 aFreq: 7T, dtype: int64) m2 F* G1 W0 ?& L9 h% g
" U; D( G- t; R: y
ts.resample('17min').sum()
0 _6 ]; ` Z, F n8 O" M2 Y2000-10-01 23:14:00 0" o9 o/ [5 e+ X# q5 ]7 G' ~" G+ ^
2000-10-01 23:31:00 9 z+ _6 y! ` e9 [0 F! D3 R: s4 G7 R" g
2000-10-01 23:48:00 21
* Q) ?; I3 z" V' P2000-10-02 00:05:00 54
# l+ p) `! q9 N& }8 G$ N2000-10-02 00:22:00 24
% f% `8 G9 G6 z- n! q! }2 IFreq: 17T, dtype: int642 L5 [( `4 ` c! u
, p9 v# L! C. h7 [) S, |- Q1 Lts.resample('17min', origin='epoch').sum()+ a# l, l) R- q# _* u; @9 s& x; d
2000-10-01 23:18:00 0" ]- d/ |3 y6 s3 @
2000-10-01 23:35:00 18
9 f: ?/ r8 _; Y0 H2000-10-01 23:52:00 27
L, Y, |7 p+ e2000-10-02 00:09:00 39
6 x3 w. v: `. E$ j; R& j$ l2000-10-02 00:26:00 24
; v# E; k2 z/ z1 jFreq: 17T, dtype: int64
: T0 R3 |2 V: p: I3 g% c9 ~7 k- x9 n7 S% h* x
ts.resample('17min', origin='2000-01-01').sum()/ z7 t$ b9 B' C3 c; @7 `# s0 A! q) L/ r' {
2000-10-01 23:24:00 3+ E; j: A: R5 x o
2000-10-01 23:41:00 155 K! }6 B, z2 r4 s. v6 V
2000-10-01 23:58:00 45) i4 y& U& n% w1 X
2000-10-02 00:15:00 45
" x. ]6 [7 Q: g5 e- VFreq: 17T, dtype: int64
2 a$ ~, f) r1 K+ |% R, q/ h. x. Z+ J. C1 B2 |. n8 q
19 C/ \2 q+ a+ [4 F( h; M
2/ P. m0 M1 ~3 V1 ?
3; h( t. O. m5 |2 u
40 r! t9 K0 X9 F
5
9 v( G6 d' r& H8 `; W; F6
3 C& F L9 O4 ^+ E7
8 L) t% Q! u1 W81 l' r- n/ y4 e6 d6 T
9
& ^; e5 L3 G/ K109 ?+ j1 p9 }) Y% V6 \
11
3 y( B( N2 z8 s' L; M12) ?2 O( ~3 Z0 Q" `% U
135 s: N4 \, g9 e5 i* z* c
14; X' Z3 k! v$ z7 p
15+ t$ u! g+ T: j5 s4 @6 q
161 r$ l) L: Q4 U
17
* }4 C' ^* t( o( p. y18
2 s \0 [$ E. Z9 J$ _5 x7 [) g9 z6 M19
9 J7 T4 q, ~# t20. q. N P% [( Y- O/ i' k
21
3 ~) e9 n# |& d2 z5 s* ?22
8 q' h0 ^! h2 |, @% N& B4 m9 h23
% w; K, Z: T" }0 w0 R24+ d" A$ D: ^8 }5 I0 u4 M
25
, H0 b4 ^2 v% k% S2 n+ p. a26) F# q" r! ? [- {4 p9 C8 L) p9 s
27" B# U6 c; a* N/ k7 }4 H6 w8 X
28' h* i4 I8 Q6 M$ u
29 t$ t3 A5 [8 G5 p
30, V# K# @! i! U3 k& \
31
2 Z' f7 r7 D& y5 f" m1 B. H, g9 _! k32
2 J+ Y; q4 c5 t+ n2 C9 Y33" F; r# }. g' J) Y
340 _# l4 O9 S1 X% d
352 e$ f9 K& n. E: v( R
36
/ \. l9 F/ J" B/ g37* j1 \/ J& Q$ B' B0 J8 {# \
如果要使用偏移 Timedelta 调整 bin 的开始,则以下两行是等效的:
' b9 b: l6 K* B' zts.resample('17min', origin='start').sum()
, o+ b- F l. s8 c+ X3 Ats.resample('17min', offset='23h30min').sum()
. j* F8 g8 R& l$ ]% |2000-10-01 23:30:00 9
, g2 {& y: X0 D; c; m7 E& m; Q0 {2000-10-01 23:47:00 211 l' }1 w! I( N! H6 f) o0 a( Z
2000-10-02 00:04:00 54
5 X3 e- z: Q& U# L2000-10-02 00:21:00 24% { A# F- T9 l0 Z" n8 ]
Freq: 17T, dtype: int64
% E: R" L3 ^: |- h; t# q7 l10 E) _" T1 c1 h/ e0 T! T
28 g8 R8 n* a! c* ~
3% s8 z# w- K( S; W1 N5 N
4
7 r/ n$ o) }# C5
% g7 _1 F( g& _* P4 l6( v0 I. D+ ]* L
73 t L$ |. o8 u
10.6 练习! \1 i* L# Q" Y2 ?3 B8 w& L! n
Ex1:太阳辐射数据集
4 v* s7 y# g \ o/ I# _5 T现有一份关于太阳辐射的数据集:& F* X4 _2 L1 e6 |
! r c4 I+ ]& Y. S: s5 v; N
df = pd.read_csv('../data/solar.csv', usecols=['Data','Time','Radiation','Temperature'])0 d# v/ ]& l; ]# x+ A+ @, r2 c4 K
df.head(3)
( g% G: y$ s, O0 l7 k, `5 t
4 I/ u1 G# E8 d! tOut[129]: # ?' ~' B8 a9 \. e! w0 y' M" i9 \
Data Time Radiation Temperature2 A/ H' ?/ ^8 _8 i3 v
0 9/29/2016 12:00:00 AM 23:55:26 1.21 48+ W o3 v! L! j* D l% A0 Z1 G& y8 g
1 9/29/2016 12:00:00 AM 23:50:23 1.21 48. B0 ]+ p. n7 A& v) Z
2 9/29/2016 12:00:00 AM 23:45:26 1.23 485 r7 K3 q& ^! V7 w* X+ m3 V; n
16 M9 X. E% ?+ k x
2$ h2 g) H9 C. F' x: f0 P* p0 K
3
' w4 c0 x* w( x3 l; v- c4 [+ Z4
8 Y# t6 a" j+ _% F5 V4 c5: x5 s K2 D6 Z U9 E' k; y8 E
6/ i1 _8 h( E1 A3 A" f( S0 ?3 M
7" N2 t! {# B( G2 `9 ], g
8
! T* x; L: E6 ]; [将Datetime, Time合并为一个时间列Datetime,同时把它作为索引后排序。% ~4 L2 G8 e' r0 e8 W1 H
每条记录时间的间隔显然并不一致,请解决如下问题:( Q. ^3 d2 {1 `0 X& F/ @
找出间隔时间的前三个最大值所对应的三组时间戳。8 i9 b+ q; o) ~7 W3 r b1 J
是否存在一个大致的范围,使得绝大多数的间隔时间都落在这个区间中?如果存在,请对此范围内的样本间隔秒数画出柱状图,设置bins=50。
6 ]0 z3 a3 V5 }: C求如下指标对应的Series:
; C- _7 w" v0 F! C1 g! C" l温度与辐射量的6小时滑动相关系数: ^' v' a, G8 M
以三点、九点、十五点、二十一点为分割,该观测所在时间区间的温度均值序列
' c% l Q" p+ G7 ^ J& n$ P( p! v每个观测6小时前的辐射量(一般而言不会恰好取到,此时取最近时间戳对应的辐射量)
5 B: w7 L# f% limport numpy as np
6 H* m& q& n' z; k+ _# yimport pandas as pd
/ [2 q- |( j9 b9 X* K# o$ ^1( @! Y! ]% z: q# E
2
/ L& Y+ j U1 A将Datetime, Time合并为一个时间列Datetime,同时把它作为索引后排序。( y/ i" y- o9 `0 _; d" |/ E
data=pd.to_datetime(df.Data) # 本身是object对象,要先转为时间序列
7 H- O: t; G# L/ ? e# A) V4 x" ntimes=pd.to_timedelta(df.Time)6 B9 G! s( G9 \
df.Data=data+times
$ f; }0 j8 ~! _* v* d; G" l( ?del df['Time']
) w4 Y6 ~+ E0 S! q% _df=df.set_index('Data').sort_index() # 如果写的是set_index(df.Data),那么Data作为索引之外,这个列还另外保留 h% W- {$ O- i9 v
df
6 k- C0 J2 D- O+ } Radiation Temperature
( b) i1 |: `3 GData - J; }* d( t/ S9 K% R! W
2016-09-01 00:00:08 2.58 515 d% @8 O2 ?8 A# W& M7 v
2016-09-01 00:05:10 2.83 51( E3 P" K: p9 M" C1 T3 @" M
2016-09-01 00:20:06 2.16 51: H, M" q5 m2 e$ m g4 n
2016-09-01 00:25:05 2.21 51
@8 [' `" f! d6 w: j2 @7 L2016-09-01 00:30:09 2.25 51, a0 r4 j& s$ G* J1 G" @
... ... ...
! S7 P$ l7 r4 ?, I4 \8 C P2016-12-31 23:35:02 1.22 41
3 C; W2 m: L3 w, O- j& X2016-12-31 23:40:01 1.21 41
$ S8 S4 b+ d6 z3 R/ O2016-12-31 23:45:04 1.21 42
. N0 l- i0 K4 |# _6 X. V2016-12-31 23:50:03 1.19 41
7 `$ i# I/ j, i" ?. m! T3 g0 Z2016-12-31 23:55:01 1.21 41
0 [' ]% K1 M( ]5 B$ t( y; U& [5 z# J
1) W( o9 Y8 u" Z- M# |; u9 [3 r
2
& \6 Q4 ]0 l6 z* X3 s! w" I3
# O: P) |8 m2 S0 C; G# N. V% B4
A. _: t) u. B! Y1 W( o+ G5! p F7 W- |, L$ g% U
6. y5 G) p/ ?% t( u7 L1 Z
7
/ c2 r! P% x7 ^8$ r; w5 l" e6 b0 b8 [% Z
9
9 S6 F7 \/ O- c1 e) w10; q) c6 A6 `- `+ w% A
11
x3 r9 d3 d6 T- \# A12
9 T$ |+ {* x: G/ g133 T" A7 i( m. M. ~
14
6 [5 Q5 J1 R: L- |! p% |9 D15
5 c! }: |' l# }5 x168 s+ k# D, B# U; r
17, X$ P2 T+ e; v2 G: n
18' A8 |, g7 v* S" Z3 _$ U9 _
19+ M6 L* ^6 N* j2 s/ r& O3 v
每条记录时间的间隔显然并不一致,请解决如下问题:
( |( x) U9 t; }0 j$ \3 i找出间隔时间的前三个最大值所对应的三组时间戳。
5 p5 a- c4 X1 a# 第一次做错了,不是找三组时间戳
5 h- R7 `0 N$ e: }/ o. kidxmax3=pd.Series(df.index).diff(1).sort_values(ascending=False).index[:3]
: U+ v3 h! \- [df.reset_index().Data[idxmax3,idxmax3-1]) S u5 B9 X( T8 e6 ~( u1 a' z
* ~2 [: ?7 h8 B) b+ d25923 2016-12-08 11:10:42
# u+ M) Y- d) V9 m0 T24522 2016-12-01 00:00:026 O! e& } B: z! K$ o: t
7417 2016-10-01 00:00:19
( H& a- i, T- Z W( }Name: Data, dtype: datetime64[ns]. ^/ \5 v# _" c! V: m# w
1 G5 d( `1 m0 h3 ^3 H$ p! t
2- [. P% ~# A. d1 ]& l+ Z* G2 e
32 P( L2 v3 _1 ?/ }) u
4
) u9 Q& c; H. ]4 c5 F51 P" M; f8 ]/ J
6
' R; f5 \) C9 X; l7 l4 m: R/ s$ R7; {9 L4 D4 v( P& |4 Z5 b o T0 K
8
/ t5 O8 B; l t, a: Nidxmax3=pd.Series(df.index).diff(1).sort_values(ascending=False).index[:3]
. [9 e0 Q" n- |& h% i3 L& Flist(zip(df.reset_index().Data[idxmax3],df.reset_index().Data[idxmax3-1]))( P4 C3 n9 A3 ?3 ~: X
* ?& p1 b6 Z' z' K8 m( T4 c[(Timestamp('2016-12-08 11:10:42'), Timestamp('2016-12-05 20:45:53')),) X# |. @6 k% F- W
(Timestamp('2016-12-01 00:00:02'), Timestamp('2016-11-29 19:05:02')),
" u W9 P0 @* H- E1 c G! B% ` (Timestamp('2016-10-01 00:00:19'), Timestamp('2016-09-29 23:55:26'))]2 D& } A( P; P) F
1
. p3 J" {) ], g$ g5 t; a# X. Z/ j2+ m# I$ Y0 i8 _) a& ^1 F* B
3
/ Y: B/ i' K3 t# |& M2 s+ l3 z7 V: a4
' P: I/ X. }+ H2 O1 {7 K8 l0 X5
. Z0 m/ e: Q; u4 r1 t3 _ e( k+ H6( O( M2 z: b. I- V
参考答案:( }- d9 R. w$ ^5 A+ [# l* [
. @4 S5 j7 R& a9 p) y
s = df.index.to_series().reset_index(drop=True).diff().dt.total_seconds()1 Q9 x& A! X( N+ Z3 M& E1 E+ ?
max_3 = s.nlargest(3).index
# q: G3 `" R/ f/ S. \df.index[max_3.union(max_3-1)]
4 R- O% H' ~ R7 `/ F0 K: {: b' c5 y) ~- d3 E9 H( h4 L. q( z- T% g
Out[215]: # ]/ U L5 l r/ z [
DatetimeIndex(['2016-09-29 23:55:26', '2016-10-01 00:00:19',% b% B; X$ G( Q3 g+ h
'2016-11-29 19:05:02', '2016-12-01 00:00:02',
9 U' A2 x+ s9 P '2016-12-05 20:45:53', '2016-12-08 11:10:42'],
! f" i, R9 [0 D dtype='datetime64[ns]', name='Datetime', freq=None)
' Z% y2 y, B, z) f7 w1 Y1
' D+ o4 s3 z0 i/ @7 R- L, F& P2
! N. ?4 `2 K/ g* w7 }3
. Q* k; C1 p& ^& p3 r; r7 Y4, K" F w* `9 C( b* @
5
~, v) _" V! `: q- R: Z- L! h# v: s. X6 ? J% P' V1 D) p: m5 z" }
79 F- m) E) k: h4 w" w( F
8! a( x! u7 L: \9 }; e# z7 V
9! y$ K1 Y) F* b) a; ]9 l' L* M: p' E
是否存在一个大致的范围,使得绝大多数的间隔时间都落在这个区间中?如果存在,请对此范围内的样本间隔秒数画出柱状图,设置bins=50。
' J" |3 k, `9 N: d) M4 v; J# 将df的indexydiff做差,转为秒数后排序。再求几个分位数确定取值区间' g# E2 }$ J7 }9 Q
s=pd.Series(df.index).diff(1).dt.total_seconds().sort_values(ascending=False)
2 H* Y' W a# m1 I/ z8 Y: zs.quantile(0.9),s.quantile(0.95),s.quantile(0.99),s.quantile(0.01),s.quantile(0.03),s.quantile(0.05)
5 F6 d9 I+ V6 o4 T4 f! n/ n+ H
, t; G% B* q4 v) `3 @& H(304.0, 309.0, 337.15999999999985, 285.0, 290.0, 292.0)
; W: V$ e8 G! i3 ~18 ]; k) y+ V" }* G# y" \3 }
2 O/ b( ?; O- p J* P5 d
3) G4 C0 P G) @) }# \( I& o" m
4
# f* C/ L- q8 ?- h5
7 E7 g: k L7 ^/ S; ?+ ^%pylab inline1 u8 ^4 l9 ^7 |3 h
_ = plt.hist(ss[(s.values<337)&(s.values>285)],bins=50)
# {) ` E8 J6 p/ [" t/ f! Aplt.xlabel(' Timedelta')
9 Y9 C( A4 z5 O0 ?plt.title(" Timedelta of solar")# ~3 q; y7 e' L+ @
1( ], W3 a* Z# V0 [( j5 U
2
2 C7 h' Q$ I+ P: a0 {" R4 x. t+ e3
8 ~0 ^/ Z g" @. h% }4 d% [: Y9 p4' J0 j; | Z ?' C! \; t
) u9 X% _% Q& i6 ], r
6 [/ S9 p4 [5 K6 d求如下指标对应的Series: o; {) Q x" N: W3 a& k8 e) B
温度与辐射量的6小时滑动相关系数2 F0 V! @- m! X
以三点、九点、十五点、二十一点为分割,该观测所在时间区间的温度均值序列
2 h' e3 t: {* L) m/ T9 ^每个观测6小时前的辐射量(一般而言不会恰好取到,此时取最近时间戳对应的辐射量)9 M9 f! \3 D: D7 R! E) t$ S
df.Radiation.rolling('6H').corr(df.Temperature).tail()
/ O- W m3 U5 G: D2 S5 X+ i/ q
4 Z; _4 b6 b* EData
( C1 j3 e& U2 ~% H, E. k2016-12-31 23:35:02 0.416187
4 E" \% d+ m, i4 b q P+ ~2016-12-31 23:40:01 0.416565
$ | ~4 o4 r+ P2016-12-31 23:45:04 0.328574 I R8 C. U3 g
2016-12-31 23:50:03 0.261883
0 q6 G% }- S0 ?% e. L2016-12-31 23:55:01 0.262406
+ O7 F4 t: |5 J0 X- ~ e4 r) I* @dtype: float64
1 o3 w6 P- Q U9 F1$ K* O5 K4 k" O" z1 ]
2
+ ~ w n+ ]' W1 F. E3
- `, m' d6 O9 z! y6 f- ]4
4 I+ ?" [9 [4 [, n" {4 i55 Q+ ~- o& i0 a5 U* Y
6
: `2 E! a0 A% m! j* v7
$ f; H5 l$ ^: u2 v0 Z8
9 ~4 M# p* I$ r) d5 A97 D( W+ F& ~2 L8 k
df['Temperature'].resample('6H',offset='3H').mean().head()$ w5 l0 e& t2 v" [' p
' W) H! s9 X A1 q8 _Data/ R% R6 p8 V! t: N9 w" j5 v- j: z
2016-08-31 21:00:00 51.218750" q/ a6 j. P- R& W$ O1 M0 l7 h
2016-09-01 03:00:00 50.033333
4 y' ~& m7 ?* u% l1 M) ]2016-09-01 09:00:00 59.379310
& L' _, @) T2 e* E2016-09-01 15:00:00 57.9843759 h2 Z" C0 N/ O3 C+ a
2016-09-01 21:00:00 51.393939" _3 |9 I9 y% _
Freq: 6H, Name: Temperature, dtype: float640 Y( v g7 y! z* q$ W) @0 y
1* [# D2 V6 y8 C- v8 ~% L
2
% t( S; f, w: u, Q3
: }9 k) Y u) k* ]- u' x4
$ N" k/ O1 l# t7 S' h( \ J55 } {6 ^& a0 J% o, Z! @" ~- Z
6
) Y% C9 p. E# R2 ?4 l) f- f# z7' k: J1 S; x# ?) N) Y9 l
8
7 L4 z+ r5 ?2 G1 T8 J92 z' H+ N! X* W( N- } [1 i6 W
最后一题参考答案:
8 M, C8 Z' @9 O9 Y+ Y7 y" e% p/ b3 t! B4 L
# 非常慢& G& y" y! u6 @0 H! z& }- \. \* Z
my_dt = df.index.shift(freq='-6H')
2 b* a( v; T9 ~/ |$ {" {3 j; gint_loc = [df.index.get_indexer([i], method='nearest') for i in my_dt]
7 E2 Y3 c0 i; M6 }; oint_loc = np.array(int_loc).reshape(-1)7 P2 @ Z( ~8 B: x! q
res = df.Radiation.iloc[int_loc]
* E& `% ?1 j+ A4 }! h7 hres.index = df.index
% f/ u0 A) D% Z: x8 ^res.tail(3)
& G, ~- L5 _3 c0 g! c1, L1 m( Q# l# r- K; q
2' s# [% _" X( U6 S
3
) ^% h. o3 T8 ^6 _& r. i40 n5 a) I9 s+ i; @
5
6 p; [$ ^4 \/ Q7 q9 S6
; ^! Z7 A4 T# H, V7 h6 t# D7
( O$ P1 C% o: x8 j0 T# 纸质版上介绍了merge_asof,性能差距可以达到3-4个数量级
& O9 Q; c0 v' @/ wtarget = pd.DataFrame(
9 f" o, s# P- s8 P {! N' j. L# |- Q
"Time": df.index.shift(freq='-6H'),2 Y9 p5 }( V7 `; O
"Datetime": df.index,
- U& d3 {6 _# B( f" K6 f9 t }
! V/ ?6 M5 C5 S; v* b; N; Z* e), Q% l3 |3 |: p# N8 T" A1 j
. K2 w x/ F" u% O6 ^' Y7 I
res = pd.merge_asof(
9 f* Z4 E, E6 T7 Y! I$ T; G8 f target,
! D) v; s$ y7 J1 R! z3 x df.reset_index().rename(columns={"Datetime": "Time"}),, c3 m# ^; a' \+ |# j7 f' J3 |0 K. r
left_on="Time",
2 [. K$ }; P# `) m1 H# T right_on="Time",/ Q! }$ W* {# P9 [8 m# @$ W
direction="nearest"1 a1 O* N e& M7 i4 y1 s
).set_index("Datetime").Radiation/ e S* x. [! m. ]$ I
6 l! w. ~6 m b% u( J& ?$ mres.tail(3)8 e# N/ B4 [( N0 d
Out[224]:
& u5 @# I" F a3 [" j! MDatetime1 V# N! I) `0 M0 c; W& S0 p
2016-12-31 23:45:04 9.33* g/ L" R$ N# }
2016-12-31 23:50:03 8.49+ U4 f/ X5 p8 Z* j0 K4 P
2016-12-31 23:55:01 5.849 E; n# D2 s' `% ?! m) A/ \
Name: Radiation, dtype: float64
: X7 @* ]4 e M! K S3 ^8 L
- E6 ~/ N( T5 _5 V6 ~- q1
# f4 H1 `' F2 X& L8 T2, l P9 r6 @* t; @
3
- c( P5 y+ ^8 d0 j4
4 p6 m9 n. m1 l1 }- a T1 X- e5
' f" S! d. ?, ?# i9 o3 I$ [2 c6+ u' ]3 B' U$ f- o1 c& {
7
# \' O, y& Q( Z; W$ k6 @1 j' j8
/ _6 W4 a" o1 v d94 t& g( v- j' y# _
10
) B0 C$ i/ H3 M" ~. F/ [9 y3 n* C11# ?* B9 S: [; E3 z- I9 `
12
+ @/ G7 O. d( R3 V13, G& Q5 k- `0 E& }# s- a
14
1 u, C; \( l f# f6 o% q* H15
% S1 n; I: @8 b% e3 K- q, G16
! m/ [1 f! p0 g% E$ N2 Q1 N17
+ a ?: P, i3 v1 @18
, x' t# h! ?; f19
2 A% {5 ]3 a1 r* F8 A20
! M8 z7 @1 v E/ x0 ]7 v* M21
, ]5 [* j% ]/ h- L' e0 ]22
# o" `; K8 C7 P230 C& @8 X& W; g( W% v7 Z) N# w5 o
Ex2:水果销量数据集
: ]. r7 D* H# `, }+ E+ W) z' S8 @现有一份2019年每日水果销量记录表:- N) e# O, ^0 ^3 ]6 x, n, B6 I4 E
. l& Y1 U' V9 k% Y; \df = pd.read_csv('../data/fruit.csv')
+ Z9 k2 @) ~8 k& {0 zdf.head(3)
% V- q S; V: Z x1 C Q. G. ~% K! Y( y+ I! e3 ~& W
Out[131]: $ b9 N2 e3 A6 C9 y
Date Fruit Sale1 v) T, |9 e! C
0 2019-04-18 Peach 15' u$ d( c8 z V
1 2019-12-29 Peach 15
* ^3 e0 @. s: R& I, v9 J# R, R2 2019-06-05 Peach 19
! Q X) u- c" i* O3 ?% _1
6 K1 s" c( Z) P2 {28 `+ @7 Z! E- Y
3
) L- p+ [- {5 z d5 B6 O7 z42 {9 L% Y" ]% @" n1 k
56 G9 z# z# Y! S+ o1 b/ }7 W4 \
6* v, r `( v1 H' c3 ]0 o; u
7' F7 c: C: [7 |1 p0 @0 H
8
( {6 P$ i, u) C& U$ D3 `3 w统计如下指标:! z1 c; h2 w. w' d- |. {
每月上半月(15号及之前)与下半月葡萄销量的比值! h$ W7 S$ c- M% Y/ N$ ^5 k# F
每月最后一天的生梨销量总和
" M& Z O! s0 j( B& _, t' K. t每月最后一天工作日的生梨销量总和 p8 C, a! d1 W
每月最后五天的苹果销量均值
4 R' h" n2 |. ~6 o5 n& h按月计算周一至周日各品种水果的平均记录条数,行索引外层为水果名称,内层为月份,列索引为星期。
, Z3 o _2 l6 h按天计算向前10个工作日窗口的苹果销量均值序列,非工作日的值用上一个工作日的结果填充。; L2 @' a7 U T i
import numpy as np
3 w' n. j! t- m( q5 p! Himport pandas as pd, k9 u2 f. |& L3 m* ]
1
: ~' e' Y y6 l: y2
6 T: W8 L( _! _2 ^统计如下指标:, M- q2 o) V& _5 l& `; F
每月上半月(15号及之前)与下半月葡萄销量的比值
% o/ C* h. H0 M, z- e0 r) }/ g每月最后一天的生梨销量总和
. A' l: I; P2 t6 a0 W; i5 f; l每月最后一天工作日的生梨销量总和
: j7 ^8 s! {' K1 X2 [; V, C8 E每月最后五天的苹果销量均值2 n/ a( ^5 m7 Y$ x/ K" q2 n, T+ E
# 每月上半月(15号及之前)与下半月葡萄销量的比值
( @, g/ S' v+ ~8 M, ^df.Date=pd.to_datetime(df.Date)' x" z7 ^. G: i+ f
sale=df.query('Fruit == "Grape"').groupby([df.Date.dt.month,df.Date.dt.day<=15])['Sale'].sum() D4 g: a6 S6 f8 n# [
sale.columns=['Month','15Dayes','Sale'] # 为啥这么改没用啊
1 Y. f) x* m5 V. o2 |sale=pd.DataFrame(sale)& c; `+ k$ O/ X$ x& O
sale=sale.unstack(1).rename_axis(index={'Date':'Month'},
0 w: @$ R% c( Z% i7 A' e }' P columns={'Date':'15Days'}).stack(1).reset_index() # unstack主要是两个索引都是Date无法直接重命名
9 i2 M$ o* t; ^% O, dsale.head() # 每个月上下半月的销量0 G2 w# L( q! Z5 v1 }! ? L
+ G8 n2 G h( Z+ L Month 15Days Sale
! c- C# e1 E* e% s* I( | I0 1 False 10503
7 g- d% h2 U3 M* Z9 r+ K5 t1 1 True 123418 p- r, `: r k: D+ I! d
2 2 False 10001% @/ g3 X5 g! H7 g- J5 x0 T
3 2 True 101064 s8 a% I" ?# W: H2 E
4 3 False 12814! O" K# t0 y7 F* n' c$ c [
* G4 D6 l7 x& v9 ]0 \, z+ o3 r) L( d$ |# 使用自定义聚合函数,分组后每组就上半月和下半月两个值,根据索引位置判断求比值时的分子分母顺序6 n- y6 a. _- T+ e& o$ X* {
sale.groupby(sale['Month'])['Sale'].agg(
# P) B, ^: L! i X- u. } lambda x: x.max()/x.min() if x.idxmax()>x.idxmin() else x.min()/x.max())! b, x6 h+ |% z
, Z( W$ M3 r1 Z" E, SMonth
% h9 T, C; G, a# Q1 K: Y s1 1.174998
}. `% q% J0 j0 A* |5 p9 {1 r2 1.010499$ j8 b( b: P+ v* D n" K
3 0.776338- }! |; W( L( ?2 _8 h0 ^6 ^
4 1.026345
+ ], ]3 w( X+ z0 x/ f5 0.900534
, R3 y# G1 G7 J0 t" D6 0.980136
( Z: `. ?3 v/ H/ x7 1.350960
4 Y1 {* m6 z" ^6 o! |8 1.0915841 a( k' G$ F- ?8 ^1 u& m
9 1.116508. w9 M1 t- a9 w+ Q1 @! s
10 1.020784& _; d' d; t' U; S- B* q$ ?8 M
11 1.275911
, R5 s$ K+ z# h, f* P$ @5 a12 0.989662
+ z7 R6 ~! N/ ^8 j$ `Name: Sale, dtype: float64, R4 @9 m! v' ~' W
( _( @5 D" i* x! T1+ P6 O4 `: x9 a/ d& x
2! D# ]+ y) X+ E/ v9 }$ R, z( ?* H5 P4 H% _
3
- c, B! t, C( }0 {0 V4
' X# ^ T! z2 G2 G+ U O6 W- p51 F3 V+ q# ~6 P# t" P% z
6' h/ b; _; \. i1 N& D% b
73 T2 f/ q# c: Q9 g7 p0 F$ P
8
& L+ y3 M' s- Y: X) S9 u9
+ k r3 t6 r9 K( z& I; J/ v9 L104 L0 I+ L, \ n+ j( I
11: Y/ N3 w4 l9 B
12
( ]' V; B G$ i* v, }- a4 b13' e, u+ C) O9 O' P5 i. g
14
* r4 I, f5 {8 R' J+ U8 D8 C15, Y, r/ E& [+ N& g
16
( v% O0 h$ T7 k" C+ F8 `176 g8 V1 H% C" a( r
18! L9 ?% T- | V, O0 ~) S
19. Q* S" F* e* M
20
' T7 Z. g. K2 c. g/ S. h/ E21
/ f5 c6 w$ O& H! p* h$ c+ C22+ N4 B% r; g2 g- G
23
[/ Z/ ~9 z9 p8 T, _7 {9 ^$ K$ d6 m24; S8 K1 D( Q9 E" T3 K
25
% i# c. [$ R2 Q26
$ R% S2 J$ }' s; |( f+ d27* H. C0 V1 a2 w* }6 P
28
3 i. ]! w0 g# A& N6 I. q29; q0 G# ]7 _5 Y" @0 G
30
?$ a6 n, ~9 ~5 h1 j312 t! m( x# I$ F, c0 {. C4 N
32
& k4 Y1 a1 }! M) E- t/ o; \& R334 b! Q4 A. i! f+ A: F
341 M! n) \. R! A' i
# 每月最后一天的生梨销量总和
, e4 m# ^0 T$ z9 V5 t9 e; m# w/ odf[df.Date.dt.is_month_end].loc[df.Fruit=='Pear'].groupby('Date')['Sale'].sum()' z* W4 z4 m& V
" ]7 E( j$ b2 _" t) p% fDate8 Q# e" v( g4 x6 \4 d2 y
2019-01-31 847( F: M5 y0 }3 B$ f
2019-02-28 774
" t9 ]1 l, H4 x! d0 s4 M4 s2019-03-31 761
5 r2 L1 H) W9 ^; ~2 T2019-04-30 648! a+ U) w' h+ } q9 A7 D
2019-05-31 616- W& k7 N E" K2 f8 C
1
) u8 y* G5 m$ H ^2 o/ O25 S2 F* W: m8 d6 R' |+ {. ]
32 C, O) h2 _. Z! b# _
42 h3 X) ~1 y( C7 j$ ]( W: P
5( A% N& B- a! [! I& I: u
6+ w, l( j: W% Y- X
79 D6 R( X$ b& S$ [! w% [/ c" q
84 T2 }, t' U4 o. @* k
9
8 Z1 z/ e" t4 }+ {* E" i+ K2 K2 U4 \# 每月最后一天工作日的生梨销量总和
+ |1 M2 f; K! v- E9 Dls=df.Date+pd.offsets.BMonthEnd()
5 E5 R1 X! x; @1 ]9 C- H7 q: y2 G+ Qmy_filter=pd.to_datetime(ls.unique())+ F) `' W# X1 u" X- u& k
df[df.Date.isin(my_filter)].loc[df.Fruit=='Pear'].groupby('Date')['Sale'].sum()! P2 v: C* @3 {, V0 e
; l, J5 ?/ F0 \4 a* H% YDate
5 ~! m+ O: A# U3 n1 I2019-01-31 847
' x4 D" e3 ]( x; s8 \' `+ n; d2019-02-28 774 F: v2 H9 G, r4 z- v6 M; x& w$ k
2019-03-29 510
6 L8 C: _5 o& w0 s: Z, @, T/ r2019-04-30 648
D9 W, K5 D7 k* n0 q2019-05-31 616
* p1 [1 E: M9 R, O1
1 {# K7 [0 I- a2
8 p+ R+ b. O( `" X. b3
" `9 `! y9 i+ F1 h6 f) z' j8 [. D4 m+ h0 X0 R. o" K0 _
5/ P0 b+ o+ H! d8 ^
6$ B8 D* |1 \5 V& D+ f) l6 r
7
4 ?& |* h( c/ t9 _3 v0 V2 [8. ~* w8 { ~! N2 q! @% `
9, z" P: R( b. s
10
% F7 e3 ]! e4 ^5 e3 F+ z# J8 u; g118 |) @7 M6 ^* ^ d% f1 j; {
# 每月最后五天的苹果销量均值
: Q: T8 o c( P5 E* s, U u/ astart, end = '2019-01-01', '2019-12-31'9 x2 l# d, p5 s M( s/ X. M
end = pd.date_range(start, end, freq='M')2 G" }; Z4 s0 G3 }2 F
end=end.repeat(5) # 每月最后一天的日期列表,重复5次方便做差
' x6 v. g5 E ]- X7 c6 V; U S& Y- r7 U* ^( @) J
td= pd.Series(pd.timedelta_range(start='0 days', periods=5),)
, U: O7 h0 P& j' ntd=pd.concat([td]*12) # 日期偏置,最后一天减去0-4天6 y( L! t" O, B6 R% o. [
end5=(end-td).reset_index(drop=True) # 每个月最后5天的列表7 e3 Y4 z$ P' l; T6 X+ `- ]
3 M7 l: C3 P( G) j& { f- O
apple5=df[df.Date.isin(end5)].query("Fruit == 'Apple'") # 每月最后五天苹果销量
2 [( T3 \ W& ^' f3 oapple5.groupby(apple5.Date.dt.month)['Sale'].mean().head()
; Z9 [, J5 z7 E! B; b- T$ |2 P0 c! z% e7 Q) F5 X
Date9 r7 T$ x+ S; J& Z. {3 X* h
1 65.3137256 O; z! G3 y0 G, I$ E( e9 |
2 54.061538
) t; N. z4 ^5 W. z3 59.325581
' ?/ ?( E3 C$ p/ T8 j" [ B4 65.795455
7 g' i g/ @- D- j5 57.465116
( e2 O( f1 P$ {$ p
$ i6 c! d6 m% a5 |( I9 a) h$ N. H1
1 D! o, V* d. Q- v U3 v2
4 C; `) r- R" ~# O. o3
, w- ~8 W" D+ a& ]! V$ U4 Y8 u4
! w' g5 O% G: e58 V6 W3 a/ b9 \
6
a7 }, x% J( T7
* K. w7 b3 O2 h* e$ `+ e5 Y88 L/ q! F, y$ M% {: e7 J
9; m* @* `% w; h9 O k
10- n B: R" b8 v' N' A2 E
11& C, f6 e$ j4 ~: R [, A. q# w9 P' I
12
2 ~8 [: } N" |. r# R) }, S13
# e9 V- I& z6 k) B$ t z14% b" V3 c# L, i
15; m3 w3 x+ `9 ~; t
165 M' m) K$ G: X" I# I& u4 H
17
3 Y: V3 }. X' p) p18
/ @; g3 o% k5 ?6 C7 B' l# 参考答案: K R9 ^" [5 ~- d7 V8 U" ]
target_dt = df.drop_duplicates().groupby(df.Date.drop_duplicates(
5 K0 Y8 Z4 R- \; n ).dt.month)['Date'].nlargest(5).reset_index(drop=True); x9 T* Y' D" ? G) c
( ]5 M) o& S+ c" [- w5 T" C: U/ Pres = df.set_index('Date').loc[target_dt].reset_index(
m/ k% j* \6 B z" N5 f ).query("Fruit == 'Apple'")( j! t l c4 R2 h2 Z8 u1 l
2 J: s: b+ m6 ~! r, [4 J$ Z
res = res.groupby(res.Date.dt.month)['Sale'].mean(# I* H* K) T" k0 [8 d7 t
).rename_axis('Month')( ~7 u3 k8 I: c& p4 j9 j
4 A0 t0 _1 `; ?7 O# C
. C$ ~$ O! E; k: ~) K. O* E4 t! cres.head() }9 h9 J# O* p% W; A/ T' U
Out[236]:
3 m" U0 V5 u+ K- tMonth6 ?/ G) h+ V7 Y* L% H3 j
1 65.313725
4 V* t, T# X+ u. E. Z) e% X/ O0 P2 54.061538, m' F7 E8 r9 w4 k( C' k9 C4 q3 _
3 59.325581
) k9 G9 M, F. J4 E! m' y- d4 65.795455) a3 ~2 C5 |- {2 F* K
5 57.465116
" _" [. w: @' y/ nName: Sale, dtype: float64
6 O6 P9 j1 \' H- ~. N' B; @9 {% N9 m: |. j/ f, o
17 M$ b4 W+ J% z! W4 J
2
8 J: S( J& D$ ^0 W. @+ |3$ p) \3 V8 ]2 G3 E, I, o, N
4
( \1 |6 r4 r* u# X5
0 i/ K6 j* O1 L& Z6
7 ^# q( [$ g- p& e7- l; q- s }& z' r2 X+ t
8
- ~4 M: {; N* J% T; e+ A9+ O2 ]5 [& o1 S3 M! f
100 @; T9 O+ W4 I+ I
11# Y7 N( K4 R/ `* ]5 v
12
- F. ?* \4 O0 Q6 ~7 m1 u13
* d; f3 K7 y* Y7 H+ |: A% [3 A14
% G, Y1 Z2 z% ]" Z" p3 a153 g H& w0 w& H9 c& N
16
# t7 o" s" Y) `179 r4 P& b6 ^4 d7 L$ y$ m8 [( g
188 P2 o6 V7 R/ Y& k5 r
19
* P7 U0 V1 m$ `/ k! ~202 Q2 S8 H6 |" y `5 e3 `# w! I0 i
按月计算周一至周日各品种水果的平均记录条数,行索引外层为水果名称,内层为月份,列索引为星期。; O% N' j6 i3 v. [; Q- \
result=pd.DataFrame(df.groupby([df.Date.dt.month,df.Date.5 d/ o7 h. E+ I. \$ w9 [0 c% s
dt.dayofweek,df.Fruit])['Sale'].count()) # 分组统计 ! o6 Q2 j& w" Q7 z; W2 o
O" a+ o; e" v, N- B5 Presult=result.unstack(1).rename_axis(index={'Date':'Month'},* k! w- R9 ]) M6 l8 u1 ^4 D
columns={'Date':'Week'}) # 两个index名字都是Date,只能转一个到列,分开来改名字.
! g9 |6 m" {: t6 Q3 m, w6 X. [7 jresult=result.swaplevel(0,1,axis=0).droplevel(0,axis=1)
2 h# {0 `7 q" e6 d0 Oresult.head() # 索引名有空再改吧
2 K4 E v/ Z: v0 p& i B# ~# P
+ Y* j0 W5 k0 f7 `/ ^ Week 0 1 2 3 4 5 6
& T* n3 A. u; L: Y; hFruit Month
% D" y5 I3 m# I0 O1 F) x6 j$ }: QApple 1 46 50 50 45 32 42 23
3 z, n2 I r5 m- g" qBanana 1 27 29 24 42 36 24 352 A y5 u5 Q) r; E
Grape 1 42 75 53 63 36 57 46: n9 ?; x0 r5 u
Peach 1 67 78 73 88 59 49 72
; S* z T6 m0 C. R5 O9 t! t' BPear 1 39 69 51 54 48 36 40
8 _/ O5 R& o0 i e4 R. D3 o4 p1
6 z. N# E$ u; y% n2
7 m8 \& R# {# X7 J" Y+ B3 N3 C3
8 C1 V/ A* Z2 j0 | H# j7 q4
% O# |6 p; A* F( ]' }/ A5
! n/ q7 d% u' C* t62 X, {( E, o, Z( \. G$ L1 r p
7" A4 `( m8 N% \0 F. E6 \
85 L7 l- t# |/ t; I+ P
9# ?) ]* }, w! f" ]4 ^% [3 h$ U' P
10+ h( Y! c* s2 B( e7 I6 P
11: i: O- P# u6 `& i
12
3 G5 D0 [4 f/ _! I4 t13
8 q( d: e1 n* }* Z+ p) H) a14
, f8 C: P5 a9 b1 s Y0 G; i* t15# e# i. P8 ^" m" }/ r; M4 |" J5 f
按天计算向前10个工作日窗口的苹果销量均值序列,非工作日的值用上一个工作日的结果填充。
( I6 q$ {0 H7 \: N0 Y# 工作日苹果销量按日期排序8 |, v) f1 R& C4 c$ Y- I
select_bday=df[~df.Date.dt.dayofweek.isin([5,6])].query('Fruit=="Apple"').set_index('Date').sort_index()7 |# l6 q; U' }! f' y2 r0 U' u4 I
select_bday=select_bday.groupby(select_bday.index)['Sale'].sum() # 每天的销量汇总) V( u6 z" T3 u6 z
select_bday.head()
; `# j0 N+ f' s$ f3 ~# Y. I' ?2 `8 y
Date* A& `, V" {9 @' q: j. g
2019-01-01 189% ]9 J _6 i9 K3 F
2019-01-02 482% e3 F [( h% U
2019-01-03 890
# _& ^; s1 s& x( z; J, W$ o w( @2019-01-04 550
% p$ t, R. w- o2019-01-07 494
q* X& o. Q& o4 s2 d: m
3 s: h: ]* y% j" l' G# 此时已经是工作日,正常滑窗。结果重设索引,对周末进行向后填充。
' P% l$ P+ f: Y* H& Eselect_bday.rolling('10D').mean().reindex(df.Date.unique()).sort_index().ffill().head()
7 v* \6 [5 A( z+ t2 l, q$ i& Q% v% V) U) r" R
Date
+ w$ X+ L& D0 y2019-01-01 189.000000
- {7 n- K# O5 p7 h( _2019-01-02 335.500000
; g- [! ^2 k% b9 I. V+ t2019-01-03 520.333333! _$ ?8 [: o; j
2019-01-04 527.750000
" B+ a2 G8 N y- S' ~3 Q3 U2019-01-05 527.750000. B6 q q! Q2 f1 G
, R0 ~ g( d$ h- w! a- S( |! d————————————————' q" p% g! d$ J: f" d+ z9 R. Y. ~. U
版权声明:本文为CSDN博主「神洛华」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。' L" f8 @( X& S: x i
原文链接:https://blog.csdn.net/qq_56591814/article/details/1266339132 e0 R5 e( L/ N; g) ]% W' P) X/ o
+ ?/ |# Q! q( {. m
% i' E- z3 z4 H e7 X) Y |
zan
|