- 在线时间
- 1630 小时
- 最后登录
- 2024-1-29
- 注册时间
- 2017-5-16
- 听众数
- 82
- 收听数
- 1
- 能力
- 120 分
- 体力
- 569832 点
- 威望
- 12 点
- 阅读权限
- 255
- 积分
- 176172
- 相册
- 1
- 日志
- 0
- 记录
- 0
- 帖子
- 5313
- 主题
- 5273
- 精华
- 3
- 分享
- 0
- 好友
- 163
TA的每日心情 | 开心 2021-8-11 17:59 |
|---|
签到天数: 17 天 [LV.4]偶尔看看III 网络挑战赛参赛者 网络挑战赛参赛者 - 自我介绍
- 本人女,毕业于内蒙古科技大学,担任文职专业,毕业专业英语。
 群组: 2018美赛大象算法课程 群组: 2018美赛护航培训课程 群组: 2019年 数学中国站长建 群组: 2019年数据分析师课程 群组: 2018年大象老师国赛优 |
# R# _3 \7 j" S/ y P! |6 L. \
$ ?. L1 H* i1 Q' [* f1 O* h
+ [' C. I" t S- C5 q) L( b1 l) J文章目录
+ d: Q7 v! ]- Y% d! q7 |& ^第八章 文本数据
/ P% f V F O2 s8.1 str对象! y9 B' Q- J: A9 d/ j& R- m2 q
8.1.1 str对象的设计意图; z: A# r# N" s8 U0 x9 r) F
8.1.3 string类型
/ G3 v+ `! k& C+ g. [1 @8.2 正则表达式基础
8 }9 m `, H \- L% v3 h- a, U5 s$ S8.2.1 . 一般字符的匹配
( i' H4 D7 P- ?% |3 e8.2.2 元字符基础; ]" b4 H7 s. y* r+ i% d8 G
8.2.3 简写字符集
9 c& A7 Z. j( {% s9 @. n8.3 文本处理的五类操作
6 U( x7 Q; F- @' K' i$ E8.3.1 `str.split `拆分
" X; A" C- L9 B8.3.2 `str.join` 或 `str.cat `合并* r+ a+ H R0 D
8.3.3 匹配9 D. ?8 y6 S3 S- i. c( ?! g, U" O
8.3.5 提取: A7 ^4 o( T! @2 h
8.4、常用字符串函数
6 R1 t8 r7 c1 u# [8.4.1 字母型函数1 ?+ V% ]7 j; J
8.4.2 数值型函数
) H% V8 U+ f P. x2 O8.4.3 统计型函数5 V* Y/ Z% Q( ], z
8.4.4 格式型函数6 {& T& h! v ^) G& v* T
8.5 练习
% _+ H& R) L8 o2 e9 g8 z$ ]Ex1:房屋信息数据集
0 R' Y# v1 P6 X/ MEx2:《权力的游戏》剧本数据集
1 X& }( n; L+ C9 T第九章 分类数据
9 |7 v" [; w" m! r$ j% h" B9.1 cat对象
7 i; F/ v- G/ Y9.1.1 cat对象的属性
% e0 b+ G0 f/ [9.1.2 类别的增加、删除和修改
+ U. J) j7 U3 y2 w5 N' ?0 [9.2 有序分类
: u |/ U! o2 \1 s9.2.1 序的建立" ]; |8 P$ r9 M# q
9.2.2 排序和比较
4 W# H( O3 {% Q1 ]" Z/ |, l5 B9.3 区间类别
8 h. l' J& |. H. Z+ |# ^3 O" x }9.3.1 利用cut和qcut进行区间构造: \5 o3 n. ?& A6 [4 [* Y1 A2 b
9.3.2 一般区间的构造
- h% Q: ~# Y6 q; g- Y3 A% ^; p9.3.3 区间的属性与方法: }9 d7 K2 g7 f1 H8 w3 M. B1 |
9.4 练习
, H6 k7 }) A$ A+ ]* G I7 bEx1: 统计未出现的类别
' m& W$ _/ ~, E5 B6 Y/ SEx2: 钻石数据集# z; C# S* ]4 d- q$ c
第十章 时序数据
0 _3 S5 ] k* {" t& R& w* z6 g) G10.1 时序中的基本对象4 t( m2 c" h) \* F! ^- H- w2 m
10.2 时间戳4 a9 |8 U$ W3 ?0 W8 Z3 c, a
10.2.1 Timestamp的构造与属性
5 T m8 ]" @5 W3 F' o; z% V10.2.2 Datetime序列的生成; `3 O! Z9 F% @8 A
10.2.3 dt对象
) E e0 E8 h" l) N/ n8 B! Z10.2.4 时间戳的切片与索引* }: f) h6 ]3 P$ }
10.3 时间差# d( D2 i7 \7 b$ r) J
10.3.1 Timedelta的生成0 P% x1 U" M Y4 \
10.2.2 Timedelta的运算" P: E6 J" i2 N- U2 K! a" S- w3 F
10.4 日期偏置
5 ^# i: T. a: G' y* l1 B10.4.1 Offset对象7 p" D3 A; u; z# y
10.4.2 偏置字符串
2 E( H0 h, O$ v( k10.5、时序中的滑窗与分组( f3 \" m) L5 r1 O$ i
10.5.1 滑动窗口/ L) I; Q+ }, ], D7 ]
10.5.2 重采样 D/ a" I, f+ ^5 G: ^
10.6 练习
2 H T% T$ `6 |/ F1 i4 pEx1:太阳辐射数据集
3 I; a8 H3 [0 o. W: S/ O2 F; `Ex2:水果销量数据集8 {% F, E0 o+ a& z8 F
课程资料《pandas数据处理与分析》、github地址、讲解视频、习题参考答案 、pandas官网
# ^4 ?1 s z% X" q传送门:' q$ a, E2 S0 S: w6 a; n# p
/ B( I# B) c( c k( zdatawhale8月组队学习《pandas数据处理与分析》(上)(基础、索引、分组) W, w. `: P' N% x8 D
datawhale8月组队学习《pandas数据处理与分析》(中)(变形、连接、缺失数据)
9 b4 k Y3 H. F% F' V( o第八章 文本数据/ j4 Y$ \& e+ g; Z# Y
8.1 str对象$ q( W r7 X5 z6 M7 e$ B" K9 y
8.1.1 str对象的设计意图
' s9 t: J3 F. T' b0 U/ g& A$ c str 对象是定义在 Index 或 Series上的属性,专门用于处理每个元素的文本内容,其内部定义了大量方法,因此对一个序列进行文本处理,首先需要获取其 str 对象。在Python标准库中也有 str 模块,为了使用上的便利,在 pandas 的50个 str 对象方法中,有31个是和标准库中的 str 模块方法同名且功能一致,例如字母转为大写的操作:
. P. c# |5 h# `
# u" T2 P: z9 R$ U% o1 a" D( Qvar = 'abcd'
1 h' }5 _ g/ n: Ystr.upper(var) # Python内置str模块6 L9 u1 Z( G8 ~. p# Z
Out[4]: 'ABCD'
8 q$ W. b9 X+ l: X+ \9 c1 Q$ l+ N: Z+ h
s = pd.Series(['abcd', 'efg', 'hi']) t& G0 L/ U0 ^
+ ~" l: x" E, g5 y4 a% ws.str$ z! L/ ~# M" K6 I( ~( n3 e
Out[6]: <pandas.core.strings.accessor.StringMethods at 0x2b796892d60>
. u5 i* n% M/ z9 ?5 ~3 d% p+ \4 O* z' e8 B, V# {
s.str.upper() # pandas中str对象上的upper方法; r% Y# v( ^" [* i2 L: {
Out[7]: ) K8 L( R1 ~- a5 T0 p c, e% ?' M( W
0 ABCD
3 p) q) g8 b& g4 B8 `1 EFG1 L/ X% R! H( U
2 HI8 S9 i1 b8 I0 U* a# B
dtype: object
( M& [6 z9 @/ h# ]+ \4 c+ u( j1
* M0 C9 j0 l1 v% j* F+ v6 ~2
( z x* P0 M$ j; e' d3
2 i3 y3 m6 i# z4
; x9 y3 x: n" h- w- x' Z" H6 X5
' T5 k0 u8 e) y5 }7 w$ R0 c6
; [9 {& j9 u- K& L3 y7
/ l/ A9 h& [. H" _8
2 r: \1 o- |: Q9 n* R" ^, o9- J% L+ I( R! N
10
* X( j; k7 H+ R. E; z) o' p11" V5 A- e) Z7 W" m& i( K1 E
12
% @7 v( T& w# k13
/ m' N3 T- Z! |& ?# L4 h14
7 B" s4 v7 T0 `2 f15: ]) A6 y4 N$ _
8.1.2 []索引器7 P5 G3 T. X& U( G) `9 t* r
对于 str 对象而言,可理解为其对字符串进行了序列化的操作,例如在一般的字符串中,通过 [] 可以取出某个位置的元素,同时也能通过切片得到子串。
8 M/ H) o+ n. t# h pandas中过对 str 对象使用 [] 索引器,可以完成完全一致的功能,并且如果超出范围则返回缺失值: o6 c5 l* y' f: {
5 _! u$ I" m' g6 Hs.str[0]
+ X4 l6 j+ |7 m, lOut[10]: % H: M! ^5 V0 J6 `1 U8 ^) A* q
0 a; m( I; Y. F1 e( q1 E( U( T
1 e ~7 H. D$ U6 _# k
2 h
3 A* }1 H' l4 z, odtype: object
$ y0 I; k8 M9 q" p, [/ ]' N. S* ~4 ~0 C# ?. i1 Y/ j6 }
s.str[-1: 0: -2]
! m( X5 P$ K" l! h8 H& ^: H' nOut[11]: * c: Z. }5 f& J9 y
0 db
, v- \$ i( r: Z. G, r' |1 g
# |$ ?; X. d. N r4 `7 m2 i/ u& d3 G6 l7 W+ C2 L' N/ p
dtype: object
6 v4 U# R6 X( F8 \, E) ?
& {6 Z: M; Y: cs.str[2]
: u% r6 g8 R( zOut[12]: , I8 m/ l/ I& s" T4 V3 E
0 c
3 m9 r( O/ Q/ H1 g& H+ S% _( B$ `4 e" W3 ~ h
2 NaN9 @2 Q+ }( a2 c, H, {, @
dtype: object
9 u- y/ _; Y9 t* [) n
) E2 x* o; F$ u4 Z14 |4 L- C5 { h8 U
22 y. l! S) p* {8 S' P6 x9 a+ ?
36 F" g" _. y, L% ?* X: x! g( i0 P
45 @: D! x9 I7 s) @
58 @4 M; o$ k3 B; u- o- E# _' G; q
6
* M% R5 t8 u( }2 [* {! \) }6 u7
$ Z' ^% ]( @" A$ P, {; N2 Y8
6 I3 h6 t, O7 o" e9) r, E% b2 {, a, u& i2 x: s5 G
10
' f- d9 m1 G4 L11' }* ~! ?! y1 h4 ^' \# Y7 e6 m
128 G) D- J( ~( G8 f
13
" Y+ ]1 f8 r; U# U4 a. V2 \1 _, B; H14
0 M+ W4 p2 c6 y1 M; |/ ?15
2 [* C0 a, ^- Y5 j' U16. m! ~- h, z1 F6 g
17# d) P) n" v: a; O4 e" q o
18
1 t$ l. S8 H9 m! D19
$ P, U3 Z. ^; _9 I- c20* g/ M" }8 t+ q X: K( ], ]
import numpy as np
7 A- V' S9 N6 Y; V5 |8 Qimport pandas as pd
5 E; F6 V+ w6 W2 R4 |: T; N
9 D# G. u% a, Z0 J6 is = pd.Series(['abcd', 'efg', 'hi'])
: h( L, o7 B M1 }/ cs.str[0]
# e6 c) K3 e1 i4 t( h% ~+ Z, ?1
, c% o* M! C1 X- D2
. H9 j- @3 f$ W7 A9 H( D3
- A7 l; x& o4 i$ y3 C3 }: F8 v4
0 a5 T7 L" ^& E9 g9 a0 t5/ ]; v6 u- x+ \! {1 u; {
0 a
4 D( k* Z% H3 i4 q1 e) T5 d6 `$ S+ M; H
2 h7 }( F5 \" M& }- x9 l+ G4 {/ X. R
dtype: object
u0 b2 v1 ?2 }0 f# [1! X2 s7 G; t# E* N9 Z. |
2: j0 J h$ N5 k6 ?) H! S& V
3
: V" \# z0 x9 D4
# E/ |$ s4 P, s- L8.1.3 string类型
( s2 P" t/ ~( J1 V/ ?( f. ] 在上一章提到,从 pandas 的 1.0.0 版本开始,引入了 string 类型,其引入的动机在于:原来所有的字符串类型都会以 object 类型的 Series 进行存储,但 object 类型只应当存储混合类型,例如同时存储浮点、字符串、字典、列表、自定义类型等,因此字符串有必要同数值型或 category 一样,具有自己的数据存储类型,从而引入了 string 类型。
! j* T6 i4 Z8 `* w+ _4 s! a5 C" c 总体上说,绝大多数对于 object 和 string 类型的序列使用 str 对象方法产生的结果是一致,但是在下面提到的两点上有较大差异:0 _# ~5 I1 f7 z5 G9 K2 v- s
$ R9 [+ L2 p1 H1 _5 k$ H
二者对于某些对象的 str 序列化方法不同。3 j5 V6 X8 J& t- Q8 j b' J
可迭代(Iterable)对象包括但不限于字符串、字典、列表。对于一个可迭代对象, string 类型和 object 类型对它们的序列化方式不同,序列化后str对象返回结果也可能不同。例如:
. g3 O+ U9 k7 o, d' |! T! ^' |s = pd.Series([{1: 'temp_1', 2: 'temp_2'}, ['a', 'b'], 0.5, 'my_string'])
$ m K$ `7 ?1 O1 S, g! @% I' ^+ }s' g7 ^% F7 B: V [
13 M- @- K0 r5 E( G( ~( G w4 _
23 R9 c* z' A3 p
0 {1: 'temp_1', 2: 'temp_2'}! I5 `0 G- C( Y" v
1 [a, b]
* j2 }' B7 B3 c4 J3 ]. m) T2 0.56 h- \3 R) z: U" s
3 my_string
/ V4 e7 C6 C) I& Bdtype: object
& A- H" o& F$ [- c1
: Z$ N: _. O5 o+ f1 O4 G2
[( p$ ]! R7 @+ K8 w- B! ?7 ?3) `5 d4 I$ g$ T3 T1 r, ?; w
44 f% @! t) P7 u4 E
5
: D; [3 k% u/ ^: A3 p: s% T1 ?" C( vs.str[1] # 对每个元素取[1]的操作5 W5 \7 B0 Z' V, b
1
5 v, i) f5 Q0 ?0 `. F4 W M0 temp_1! h% Q/ M) q9 `
1 b) ~; h. m1 d2 ^( V9 U
2 NaN
3 C8 R3 Q' e. n% S3 y( E7 R _. r' m- l- V/ g) A( {
dtype: object( R( |( R$ @1 y! G, G0 E6 [8 a
1* [! u6 C# l# y4 V h- e* R7 s
23 K, Y* P4 }: z$ B
33 P$ W9 B6 c7 z( Z4 p1 Y
4
2 S6 A6 ~! ?4 M* _7 Y54 E) G! {! L' Y$ \0 d" a3 J
s.astype('string').str[1]
3 C0 Q& C- P0 F; n1
* }( v$ P$ _1 O: q0 1
' t2 {$ s1 @" s1 '
+ x, Q. V: w( h. g2 .7 n. s3 s& ?# H2 _$ C( G8 o
3 y2 x7 {. O& ~) A: I
dtype: string3 ]* Q# U6 C5 M. z( y0 Y' ] f% b0 K4 ?
1
7 @& z9 L9 _' I+ D) `9 V29 D Z) f# E Y/ i( K( [% S
33 }5 N7 g$ A- W2 h5 d: e
4
_* _7 d [. r5& ?. ^) i1 q8 e- S! n
除了最后一个字符串元素,前三个元素返回的值都不同,其原因在于:3 S5 a3 s0 a$ r
& [% q* ^- E6 ^& J
当序列类型为 object 时,是对于每一个元素进行 [] 索引,因此对于字典而言,返回temp_1字符串,对于列表则返回第二个值,而第三个为不可迭代对象,返回缺失值,第四个是对字符串进行 [] 索引。4 Y6 N7 w' t. I# z8 C- e
string 类型的 str 对象先把整个元素转为字面意义的字符串,例如对于列表而言,第一个元素即 “{”,而对于最后一个字符串元素而言,恰好转化前后的表示方法一致,因此结果和 object 类型一致。
# O0 M6 R' |+ P) b( ^7 t4 L8 xstring 类型是 Nullable 类型,但 object 不是" e+ N4 H8 W/ N( o0 w( e" ^
这意味着 string 类型的序列,如果调用的 str 方法返回值为整数 Series 和布尔 Series 时,其分别对应的 dtype 是 Int 和 boolean 的 Nullable 类型,而 object 类型则会分别返回 int/float 和 bool/object ,不过这取决于缺失值的存在与否。
8 b: c3 f- v: K2 M* X 同时,字符串的比较操作,也具有相似的特性, string 返回 Nullable 类型,但 object 不会。 n' g# [5 Z$ ]- q, E' V! l
s = pd.Series(['a'])2 Q6 _; e1 ]. x# |1 R9 U7 C
* P; N# ~4 k! t1 g+ q8 x
s.str.len()) a: h) R! G" ~* @
Out[17]:
: K, K+ C5 I- c: K' E0 1
0 q! C- f: T6 G odtype: int64
& e: I9 i7 w- _) R
0 e; j) v7 B. v/ L% Ls.astype('string').str.len()
1 w& Y, E9 [4 FOut[18]:
' ~2 B- {& U7 I& |# R0 1
: _7 ~5 W: K) _/ qdtype: Int64
+ c! x$ T5 v9 y, |* _. z' Q4 \
s == 'a'
: M$ {8 J' T+ aOut[19]:
4 q& N/ g2 x2 ]' }0 True
, m5 A9 H% T/ d3 Ddtype: bool; ]; i- V7 @" ^! U) P1 w
# S# s+ o0 C Y; y1 V1 z. y- l& }9 }+ O
s.astype('string') == 'a'
, P* j) u9 D7 i" pOut[20]:
; ^9 [; j8 M, ]8 o% H- C0 True# W$ B8 g+ v2 ~$ _! T6 u
dtype: boolean
& n: K) ]% I# \
: j$ l) Z) J/ C/ a. P5 \4 as = pd.Series(['a', np.nan]) # 带有缺失值5 V2 N+ O2 h# Z$ \( @: W
/ z5 e6 K3 H% ]' ]4 }
s.str.len()9 {! G1 _0 t3 ?4 l' p% }
Out[22]: ! e* k4 b" g) K: N8 z" f
0 1.0
& K6 G4 g8 u( u6 J6 ~( e1 NaN" k# C0 r1 l e1 w
dtype: float64, X" z. N6 P# Z
( N' j3 y7 T. C+ @s.astype('string').str.len()
! T& D* x5 m; p5 {5 iOut[23]:
: ~5 W( S3 ~1 @, i0 1
: F1 {/ r% `% P, j* ?- U! z+ C1 <NA>; C5 M7 }4 s6 m* C- O
dtype: Int64
0 u; |* q% B' U# [
7 E& ?3 X) V1 U. y6 Z% S3 Xs == 'a'% u" Z. m' M. z4 G* r) c% [2 z
Out[24]:
. b, ~& }: [7 w/ ^% I: O" n0 True1 f6 z5 b. ]( d w/ q2 o; r
1 False7 G+ L6 a, v4 J' k
dtype: bool( b0 [, Y- }! Z9 J ?1 |
, J4 G* S7 p9 Z/ Q
s.astype('string') == 'a'- O2 w, [ x/ j- j+ ~7 v
Out[25]:
$ u9 H: @, z9 F& P( ~5 i/ m0 True
' v9 H$ W# E6 o5 b6 \0 U' Z1 <NA>
1 I7 p& ~# g& c5 }3 Ydtype: boolean
]2 l& m0 E6 q% b0 v: C! n% Z% j# A8 E" z- V# o
1# ~7 f5 u, b/ Y% r
2
/ S- X0 ?( { Q7 u3
6 M% F+ C! K6 l5 I4) s0 m+ ]: C. C6 M* q) {( Y
5
3 d* F, z+ z; A1 r- A6# O) T8 k3 b! i0 i9 j
7! d& ?+ x+ a2 y5 b1 Q9 Z
8
# y( b* H" E* n- N9/ b4 E8 B9 N% ]3 p% Q- |
10: D5 l0 l8 s3 d
11' ~6 c3 p \6 ~. `4 j
12; E( F6 z6 C# A) A3 f0 N
13# Y+ b4 j) a# X. ]# B" U
141 p2 J7 @5 a6 C7 H
15
' Q" `. e) p0 t! f8 }16+ S! d3 y9 a( T2 _ }
17
+ }9 z. i0 V- u! R, ~; w7 v18
" Z+ u W* t0 {# |4 I* ?5 g19
$ |6 U, T; }9 u3 D6 Z20
S+ \6 A. Y4 ~- M: g, K21$ O# ` [5 E/ X1 w% v9 T" t! S$ d
22' f5 p6 J/ ]) ^: n A+ m" q
23
" E8 `. X8 U1 E: a p6 h6 Y T! m24
1 Q2 B: V0 t4 z5 j! n2 J2 D25) G' P* N' ?: P& r, e# Z
26
' u$ y6 Z) U6 ~27
6 L. ^3 C a _& m% B: G' V. Z. b28+ F ^6 _# R; [: c
290 V9 y9 S# J3 T* E
30
! q+ I7 W% ^6 A+ D31
* F- c; s4 [7 d& x; {32$ w$ }7 j/ {& E J7 ^
33
) \ ]+ I; [! e' l1 ?7 ~0 n34
+ t; ?: C6 _! t# u35. J3 [( X) \# s& q! L
36
+ x) [ z6 v4 ?6 \- o37) u% _. J3 c7 _* S, v7 N. j* [8 N
38
* [" p1 W% V" L/ [7 {7 V% F/ O39
! ^* \& x7 n# U% Q8 m40
6 [% ]" P [3 k41) @) A; X# R. E! k" Z( N2 r
42
( b0 ?, p8 n& G' ?1 f9 Q/ T430 m8 e/ a7 O& ~8 j. U+ a! P# z
44
7 N4 T" a, X& U/ B* L& _! G, `' I45( w& s0 v) t; @) h: t4 N2 d! c0 u% ]
46
0 U! v) B, H& l) F& K# t" F478 w# T0 Y, C: L( r
对于全体元素为数值类型的序列,即使其类型为 object 或者 category 也不允许直接使用 str 属性。如果需要把数字当成 string 类型处理,可以使用 astype 强制转换为 string 类型的 Series :
, S2 S& P- v, `; i; n- d6 a, O2 d; r2 ^/ \) h
s = pd.Series([12, 345, 6789])$ m0 ]* ?4 N3 X
. C: g$ P* x0 B* js.astype('string').str[1]
, f' H. J/ L" t2 QOut[27]: , K& _' u5 q4 u* l
0 2
7 X8 Y' l3 ^" y1 I" O3 `1 4
' B5 p9 }6 P& C* M* a$ y2 7( p% m2 L' z, t+ P& ^1 K" A
dtype: string
% G2 v, H3 c9 S+ l- G$ X- h1( V* z/ v9 G. C) p$ E
2
, a( M9 n# f; b+ G# N- B4 t3( |' }2 v' j7 b& p8 @7 z
4
4 t( a) Z# g, u2 e! C2 b' P5 C5
$ E( c: V. v0 v. l63 x8 {4 j1 F$ J9 v
7
) W! y6 L, o6 R1 G$ q* ?8
* Q1 p( K, N% l' ]8.2 正则表达式基础
2 y2 s1 |; h% L) s+ O, U+ V这一节的两个表格来自于 learn-regex-zh 这个关于正则表达式项目,其使用 MIT 开源许可协议。这里只是介绍正则表达式的基本用法,需要系统学习的读者可参考《Python3 正则表达式》,或者《 正则表达式必知必会 》这本书
. H, F! @( d- l x x% j7 x4 l# C
8.2.1 . 一般字符的匹配
6 L3 F' j! b3 K" b1 G& h: j. j9 K2 p正则表达式是一种按照某种正则模式,从左到右匹配字符串中内容的一种工具。对于一般的字符而言,它可以找到其所在的位置,这里为了演示便利,使用了 python 中 re 模块的 findall 函数来匹配所有出现过但不重叠的模式,第一个参数是正则表达式,第二个参数是待匹配的字符串。例如,在下面的字符串中找出 apple :
@# ?3 i( W8 Y. ^8 I( U; B( O* j0 V! H- z9 w h$ v
import re
: w1 t" P0 D" [) L( d; @
8 C) y& \* N: I4 [; l5 n6 o) e' Are.findall(r'Apple', 'Apple! This Is an Apple!') # 字符串从左到右依次匹配+ e1 X# z$ T5 I2 Y/ r3 S
Out[29]: ['Apple', 'Apple']
& m1 t: ~1 x, C3 m% R* V5 k18 Y( @( D+ o* e/ O, n M0 r' i
2
$ `5 u( A+ G! {% i# o0 @3
. [; A. U5 S* b8 E/ I4
& ?0 w1 ?" l4 B% ^# T' g; Q8.2.2 元字符基础! }3 Q; r3 \/ e5 H$ [
元字符 描述* L# `- q( t; s W. ^0 [1 w$ u
. 匹配除换行符以外的任意字符; M7 x( c& C$ ^. U
[ ] 字符类,匹配方括号中包含的任意字符, A' W$ [8 v j8 Z
[^ ] 否定字符类,匹配方括号中不包含的任意字符9 ?: d0 J5 ]- h# Z0 a. M
* 匹配前面的子表达式零次或多次! [& [# |1 F( @
+ 匹配前面的子表达式一次或多次。比如r’d+'就是匹配数字串,r’d’就是匹配单个数字
+ W: c1 d8 n# T6 ~) m* P# P' i, k? 匹配前面的子表达式零次或一次,非贪婪方式
: I- ~0 f# N7 e& h: B7 J{n,m} 花括号,匹配 n 到 m 次由前面的正则表达式定义的片段,贪婪方式
& `5 j8 d. S. `0 f3 N(xyz) 字符组,按照确切的顺序匹配字符xyz
3 _! u* M* A# i p| 分支结构,匹配符号之前的字符或后面的字符5 \& O# z7 K% d% Z
\ 转义符,它可以还原元字符原来的含义0 g, R! X. ~$ U; O# ~6 D
^ 匹配行的开始, e9 ^4 D& Y* W; a E& j' Z
$ 匹配行的结束
3 a- h4 w& O, H8 c/ eimport re
% e; U7 s2 X: ~- Z" V ]" t1 H n$ xre.findall(r'.', 'abc')
9 v8 ]4 t9 I- q1 LOut[30]: ['a', 'b', 'c']7 k/ y4 b8 t: R
1 R1 |2 a* C0 ~- ~8 nre.findall(r'[ac]', 'abc') # []中有的子串都匹配
) S4 n+ W+ L( D" x6 ?Out[31]: ['a', 'c']( k r* U1 K/ q, _5 L
; O9 `" `' K3 f/ [' ?" f9 T) |2 U+ a
re.findall(r'[^ac]', 'abc')
& F/ M) a6 C8 A; a! Q: B! H: t# }Out[32]: ['b']
7 Q) f. R: z, a V" T& \( ?3 P3 ?# L: ]) ^0 Y+ p. c. Y. ?( ?
re.findall(r'[ab]{2}', 'aaaabbbb') # {n}指匹配n次
1 Q! f7 L9 p/ Q- g& D9 D) o, k6 ROut[33]: ['aa', 'aa', 'bb', 'bb']
# X) h3 |6 W# L9 `" k
5 a+ e, F% n! T$ Q/ g. ]; J Z9 N7 jre.findall(r'aaa|bbc|ca', 'aacabbcbbc') # 匹配前面的或者后面的字符串5 T0 |1 z r( a: s# l& d6 P
Out[34]: ['ca', 'bbc', 'bbc']
5 g/ N! l+ S* u& V5 x+ A# \4 G, R7 }5 R4 t
# 上面的元字符都有特殊含义,要匹配其本来的意思就得用\进行转义。9 K \1 Z! Y9 k
"""
0 ^$ B1 [, z5 c( @1. ?匹配的是前一个字符,即被转义的\,所以|前面的内容就是匹配a\或者a,但是结果里面没有a\,相当于只能匹配a。) G0 C/ D# f: q) Y7 W P, R
2. |右边是a\*,转义之后匹配a*,对于竖线而言左边优先级高于右边& K p. i$ S1 {# A# S, |* |% ] P
3. 然后看目标字符串aa?a*a,第一个a匹配左边,第二个a匹配左边,第三个a虽然后面有*,8 c& e) L% Q6 d: F; S6 x( s
但是左边优先级高, 还是匹配左边,剩下一个a还是左边,所以结果是四个a3 |3 H. I- }8 e
"""
& [& U% Q& m7 g
8 u+ r+ t) {' `4 a4 _3 p8 `% W7 ^re.findall(r'a\\?|a\*', 'aa?a*a') # 第二次先匹配到a,就不会匹配a?。a*同理。
$ I$ I. K- B& t @; C1 G4 n& QOut[35]: ['a', 'a', 'a', 'a']% A1 o/ p( G% [# E* L; U) z/ d+ I* |
! u9 A `8 K$ C. d. ?
# 这里匹配不到是因为目标串'aa\a*a'中,\a是python的转义字符(\a\b\t\n等),所以匹配不到。- }* _4 Q& k9 r7 B0 k/ g# ~
# 如果是'aa\s*a'之内非python的转义字符,或者'aa\\s*a',或者r'aa\\s*a'就可以匹配到\字符。 H" `% s" ^4 _
re.findall(r'\\', 'aa\a*a') 6 B+ k/ T$ k6 v. x5 i6 t
[]
, ?- _9 K. n! X) c
/ m- V. T& H# z1 H$ H1 z9 Dre.findall(r'a?.', 'abaacadaae')
( w# _7 ?2 ^# I# V9 D. J4 z! N1 g1 ^Out[36]: ['ab', 'aa', 'c', 'ad', 'aa', 'e']
4 s0 v; @* m9 U! S( r7 P) u% X$ v6 h
4 c1 S }/ \/ [- u. G. O1 X9 [re.findall(r'(\w+)=(\d+)', 'set width=20 and height=10') # 多个匹配模式,返回元组列表4 ~& ]' E) }3 k% W. K
[('width', '20'), ('height', '10')]0 _1 D: X& p* H+ j) v$ M
8 A) ?/ Q& h6 T2 f' b
1
" l; p: Z: N# J# a+ O2
+ y' B6 ?6 ]! [' k7 k3
* I2 Z8 d! N% G: I& {+ a! U4- [$ W) c" K4 H* J7 {; V7 A! m
5
' T0 ]5 \ m* W# v; Q66 c8 m/ `( p5 G4 q, |
7
5 s0 g- I# e8 N$ q4 f9 P8
/ X9 |9 E( t7 ?9
9 n. w8 E) [- ~. j10 J. O2 N% \- ^+ ?9 C& L. W
116 u- w# x( s) i
12
5 ?3 K( [" ?4 {: N2 e136 B% E2 @9 d* x+ w' h
14 j: s. }# A E8 U9 G
15
5 A; }+ u# I6 n8 z( ]% r0 k16; b% O5 Z' ]8 s2 a3 |4 V
17
. B+ _) T* T4 {4 C/ U18# g' |8 f% {5 W9 q
19. G6 S# o+ P' l! R% w2 @
20
* [5 f8 c8 D1 T0 ?21
% f6 u5 l( _( C2 B% e5 W22- y- f+ y6 w- W$ K+ H, J& I
231 F. Q2 [( q! {5 t) j8 W* z* H* U
24! _0 [; y2 s: @+ ?9 k) e
25
( |; A+ f( U, N: u26$ ]5 y9 T* X# f9 V
27( {& t) C6 p" i- V* o2 L
28
; N: a% d+ h3 \29
7 t2 {9 W0 Z2 C8 v4 z" U30
0 ?8 G- D! `& D' [% \/ X2 E31* o0 ?9 X3 _$ r% s
32
' r) m3 v! N% H/ j" n M338 I. Q+ m" H) g# b
34" M! g3 ]9 m, D- v) t6 P* @$ z
35
, D* F q# B8 T2 j36
# Z! m# b. [- `% B: Z3 ]3 C37
% n, L% Y$ J0 j3 ~ g$ d8.2.3 简写字符集, [; G& R8 T2 w9 A9 Q! o9 M+ E% j
则表达式中还有一类简写字符集,其等价于一组字符的集合:
7 ?; V( U; W: i5 j; w1 l6 v1 v* ?& ~5 F: U& \7 v) b
简写 描述 m) d6 K8 ~$ i) [" @
\w 匹配所有字母、数字、下划线: [a-zA-Z0-9_]0 g' d8 Q6 x, u' i' M% u
\W 匹配非字母和数字的字符: [^\w]/ o2 d/ i! ^7 k7 L% j! Y0 W$ l
\d 匹配数字: [0-9]6 R0 n+ A" j9 y! @) q6 K
\D 匹配非数字: [^\d]% F! c; L* k6 O8 p* t$ i
\s 匹配空格符: [\t\n\f\r\p{Z}]8 c' W. i! j# W1 o5 ~% h
\S 匹配非空格符: [^\s]
; o" G" b/ X0 N$ b( `0 i5 F3 o M% t\B 匹配非单词边界。‘er\B’ 能匹配 “verb” 中的 ‘er’,但不能匹配 “never” 中的 ‘er’。
* J0 d; y9 C y, ~7 m4 \- v! Pre.findall(r'.s', 'Apple! This Is an Apple!')
. q N- r* E& h9 q9 G" gOut[37]: ['is', 'Is']
, Q2 G! X% d e+ q: s n* w( R# v8 L$ T$ ^" X% c/ y
re.findall(r'\w{2}', '09 8? 7w c_ 9q p@') # 匹配任意数字字母下划线的组合,但必须是两次1 A' A7 d& H/ M- i3 ?: a1 s$ R+ d
Out[38]: ['09', '7w', 'c_', '9q']
+ `" n5 S% H# o" @0 L8 J* n9 Q4 E+ ?0 b( K. G
re.findall(r'\w\W\B', '09 8? 7w c_ 9q p@') # 匹配的是两个字符串,前一个是任意数字字母下划线(\W),后一个不是(\W)
& `1 G# I% p/ u! oOut[39]: ['8?', 'p@']- N% f6 {7 R1 ?% g3 W
( q/ P1 Z, g4 Y& u5 Z9 }
re.findall(r'.\s.', 'Constant dropping wears the stone.')
5 I- y( o9 F$ e1 b8 \Out[40]: ['t d', 'g w', 's t', 'e s']# r1 u3 d, m( e) r. A" ?
5 n1 O) h# @: D: Z; E ~) M) yre.findall(r'上海市(.{2,3}区)(.{2,3}路)(\d+号)',1 x; M+ S3 f+ e# e( k" q
'上海市黄浦区方浜中路249号 上海市宝山区密山路5号')5 H$ t/ i3 o( T2 c* D
/ R1 t/ {3 J/ POut[41]: [('黄浦区', '方浜中路', '249号'), ('宝山区', '密山路', '5号')]; I# d/ y0 J* j$ p. a! i; |
) [# @3 {; X6 O/ Y0 m! G4 l; |14 }0 s( R2 c1 A9 v$ E
2 r( Y2 J+ `. a- r, T7 B
31 n! m% z8 k" ]
49 t4 V8 t' | ^- v& c
5
7 [8 n5 H, w* t9 A' d: {5 f6 X* C6% x+ G) m- M) n$ z* K* _0 q1 R
77 m# w }8 d4 X' _
8& ]& ^: ]& K$ [: A. G
9, m4 F/ b- H. |( r
10
7 w+ D+ c' w3 x5 q! w0 k+ S6 M% q9 B" Q11
6 `/ \+ e. c O6 u/ d% ^7 x12
8 I9 {! n/ G; T& C+ U0 ]( P13* \: x" u5 ^% ?6 J% Z N. G! \
14% e' i7 D9 v) L: j/ N
15
6 d+ y9 s0 D: [. v* E* M# ], E16
2 Y* l7 @* f" d4 Q) m( f8 P+ O8.3 文本处理的五类操作* c; H* C6 @5 g0 \9 m6 E7 w' k# @! \
8.3.1 str.split 拆分
* W/ s3 u* Y( x+ C str.split 能够把字符串的列进行拆分,其中第一个参数为正则表达式,可选参数包括从左到右的最大拆分次数 n ,是否展开为多个列 expand 。
4 Y ?& P, Q0 R+ g9 ~, E
. J, k& r0 V) S0 E2 j: ]s = pd.Series(['上海市黄浦区方浜中路249号',$ U8 l' y0 I2 s3 t" p2 R$ N
'上海市宝山区密山路5号'])! F9 D7 f! `, p E+ ?. \
8 z' `; T, D1 q9 {& ]( f2 `
, y$ j* u# p5 y8 D1 @7 n9 as.str.split('[市区路]') # 每条结果为一行,相当于Series) _2 l( ~+ N( ^
Out[43]: . w4 A4 s9 A# G6 }, d4 v8 d8 G
0 [上海, 黄浦, 方浜中, 249号]1 D5 C1 n$ ?1 {3 s
1 [上海, 宝山, 密山, 5号] e. A% S2 {! q, D6 I, @6 x4 ]. n
dtype: object
3 \+ t( p2 S7 \7 ~, I
5 G% t! G3 a7 C2 W; _0 G* ?s.str.split('[市区路]', n=2, expand=True) # 结果分成多个列展示,结果相当于DataFrame
( P+ I4 L6 q" V' Z" MOut[44]:
+ |2 I- Z( b* W- j! m% q/ f 0 1 26 a4 M0 k: B& o
0 上海 黄浦 方浜中路249号7 q: `; C1 {. P) {- S: S
1 上海 宝山 密山路5号
; A5 s, q, k" k& T& v5 N1
{. }- }0 P0 R29 U" A2 N) m: v( W# r
3
& y6 J1 x$ l6 Q* y4; ]8 r6 l) t0 O+ q5 d: q
5' | T; @/ m; ?# d9 V
6
& U8 n" b$ m# d& {4 l) x( h7
0 }5 U7 ]" Z# Q0 [) D0 o% [) ^81 S7 K" {' F2 {2 {/ Z$ H
9
: K& O" L. E, X, v [10 o' S5 U' C7 X0 y
11
( v$ `/ _2 Z; E& d3 f12# _4 @( g; T. ?; u" ?4 q, ?
136 U) M y Q7 {! R) t
14
* J* w# @& T( t9 G. g {5 z15
% q3 [6 J! J( V 类似的函数是 str.rsplit ,其区别在于使用 n 参数的时候是从右到左限制最大拆分次数。但是当前版本下 rsplit 因为 bug 而无法使用正则表达式进行分割:- d9 I% O7 `. u' N5 [8 ^
; i/ c: i3 I+ f
s.str.rsplit('[市区路]', n=2, expand=True)
0 [% b/ A) K( M0 o7 L' FOut[45]:
' A7 I6 y3 ?/ d( |) E% q 0
7 j. [7 H( {4 ~& e0 上海市黄浦区方浜中路249号
! u9 a$ a3 u2 T- U3 B1 上海市宝山区密山路5号
6 \- c7 }* a" \0 z& `; a1
+ V( C5 H- p7 F5 W2
8 O8 c4 @2 L' Z; {5 {* w3' ~) \* y3 q3 x8 N
4
: m3 k8 q6 \% ]; m1 Y5
. A/ n6 s3 z$ E4 D ]) S% @7 P8.3.2 str.join 或 str.cat 合并& f) ~. @+ P' R
str.join 表示用某个连接符把 Series 中的字符串列表连接起来,如果列表中出现了非字符串元素则返回缺失值。
- ]( ` d2 T" v; N& Y! U( Zstr.cat 用于合并两个序列,主要参数为:
) v4 k4 C5 w/ ysep:连接符、
0 h9 V) B4 U4 o7 tjoin:连接形式默认为以索引为键的左连接2 u' A0 ^# O M, Q$ A* A: B
na_rep:缺失值替代符号- R7 y: i3 c. g2 w
s = pd.Series([['a','b'], [1, 'a'], [['a', 'b'], 'c']])
+ U O8 I- |4 Q9 |/ X5 E. Y( cs.str.join('-'); Q" F2 E8 v. [5 B% D" H: O
Out[47]:
6 Y$ N. X* h' e; P" W3 G4 m, Q, O0 |0 a-b1 w1 y0 u5 }9 ]) s: v/ B( G) T( x) v
1 NaN
: w6 g) x4 _3 C O) E1 g7 i2 NaN4 B5 Y3 j; b! L& M, A
dtype: object* n4 U5 V. H, m+ D) y+ r! k
1
3 o! r, N9 @5 ^4 ?) S" L8 P5 ?4 @2 J% H# {' Q$ W
3
9 P& ^& L4 W6 y0 r4
& `, ^; [; B9 z/ d7 R5
; U* O; c, k Y* ]( q$ S0 N6" _/ s6 }9 u& x; l
72 ~) Y: j$ @ p( ]% q& |: I
s1 = pd.Series(['a','b']): W9 Y) f( J8 x' C
s2 = pd.Series(['cat','dog'])1 v2 O+ R) V7 q
s1.str.cat(s2,sep='-')' o3 x% ~2 |6 S) y
Out[50]:
( i! D! h; k1 @1 ^: }1 W0 a-cat6 |% ~' \6 E# i2 q
1 b-dog
& Y" m- K: K0 G; `; fdtype: object
' ~+ H+ E1 |/ E. w# `4 W6 c- b' U
W* y8 K3 E( P1 ts2.index = [1, 2]
9 f' P" |1 O5 o: h js1.str.cat(s2, sep='-', na_rep='?', join='outer')6 }0 m9 I3 K1 v: b3 @! Z- A0 k
Out[52]:
3 O1 P+ a2 [' L: K$ { Y$ L0 a-?
) v& \! Q$ B5 f) K x1 b-cat. z2 _- x2 G1 v, C
2 ?-dog1 F0 w5 u& i; [
dtype: object
* a' u. f0 V0 y1
8 T+ Q4 }1 N; H" M/ O1 ^2
% E" n. X% d* P" {3
! c! b6 N; |) c; n6 G2 O* U+ a9 P4
+ B( j4 N7 _8 D6 \5
& {* Y7 I6 a% `2 J* {6 U& o$ h" E2 H1 P6
: { V# s. c8 B! W" S2 |9 _5 L7" L8 W: k4 ~1 `2 q
8
- g7 T/ Z/ m% z5 y9
- y5 h& ^: t7 m4 \5 W: ~' _# ]10
" i& \+ [ w2 U1 {: |9 p11& P) v1 U: y5 o; d* a5 E7 d4 ]
124 P( w* b& X+ [$ r3 m
13
. B# ?: Y! e% S14
8 M' ? q) ]7 N- k+ f, i15/ z# H2 q6 R4 k2 o
8.3.3 匹配
1 y: v k1 ^: D9 C) W4 I6 Zstr.contains返回了每个字符串是否包含正则模式的布尔序列:
0 J7 L2 x( J5 k' s3 hs = pd.Series(['my cat', 'he is fat', 'railway station'])
8 ]9 z# w0 M/ s/ L* hs.str.contains('\s\wat')& ?0 x* G$ @1 I& d0 Y& h' @
$ u3 r3 ?/ D7 s7 ~1 P! r4 n0 True2 u& X- V4 ?9 z I- G' {
1 True
0 _& b; b4 w7 y! c2 W/ T# { a. B2 False
& X4 d, K- u0 n; t; g! adtype: bool
+ q9 J, m- _ A9 Z: W- O1& L/ j3 G2 T) Y( g5 i" b
22 p+ Q$ K! L1 T& i4 z; @
3
* E, T+ ?$ k: E7 o* K' T& {% q7 h4) n" x& s; ?0 @. b$ K! K* U- @# y
5: R6 c8 O) R! W- f
6
; x) N k4 v; ]3 N9 X g7( m; ]' t% x* R4 i* W" e
str.startswith和str.endswith返回了每个字符串以给定模式为开始和结束的布尔序列,它们都不支持正则表达式:
- y& S6 h; B- x M. T' x6 |9 z; w/ Ds.str.startswith('my')/ j' a3 | F/ l1 i7 F) P
* J6 I7 H3 {/ S2 K, z) J# }0 True0 }0 ^+ \/ {. w- P8 G3 t
1 False
; |. I6 H, t4 h; y' T2 False
$ ^ R) ]/ ?- z/ _+ h3 Q8 f# G$ ?dtype: bool4 {$ l" Y, [! ^& A G
1
$ n8 j# ]9 p- [# x7 V9 N1 y- ?( v2
/ A; B) e- ^, A5 ?3 c/ `3' u* h7 o5 G& J
4
4 _- A# A1 C8 h7 J$ D5
/ K2 ^# C# H7 l# ]4 |$ f6
* C9 s" z4 o0 Zs.str.endswith('t')
6 Y& {, W# ~8 `8 w8 H7 L
) `0 h2 z. x) E" W4 \. i: u0 True% t& B5 A" ^) |/ k+ I/ |7 z( K' a3 d
1 True* `- A* h) T3 `) [5 O# P
2 False, w' o8 a8 F0 e" U; W
dtype: bool k( q) R" s' J
1
" I" f/ J5 p+ l+ X3 f, W5 c4 F2
1 E, Y" h# `! ~6 t* E3& Z# g, o; S6 A2 F" a
4
; U1 j) q: V4 |' `5, g3 n# [: k3 d6 B9 R
6
5 u5 i, x! b+ Z2 P1 E$ J5 V+ G& I9 bstr.match可以用正则表达式来检测开始或结束字符串的模式,其返回了每个字符串起始处是否符合给定正则模式的布尔序列。当然,这些也能通过在str.contains的正则中使用^和$来实现。(貌似没有python里的search方法)
! c1 P2 A8 c" U) }. X5 e l4 y) Js.str.match('m|h')
. b! t% n8 C: X8 ps.str.contains('^[m|h]') # 二者等价% C# b% I& s5 g3 x; @( Z; H
/ K; \& a7 B) `# H$ X0 True' O& ?! J; u. ], }
1 True
* \+ n+ n) H/ C% b3 Y3 K6 ^6 _2 False) e. A4 J; d8 ?8 F; q
dtype: bool( n, z. c3 {9 ]8 B
1
- o1 U! @6 ^# x9 p* x' q2% h% `# _* l- R* e1 i! @9 H1 D
3
: @/ K& X/ }- I& E* D" @4 Z' _4- o8 w& Y% U$ A2 U! K4 P; }- B
5
w* w# M$ R$ o6 s6
( D6 V8 B5 }4 [+ m* ^- h6 _% G- T7
6 u% I4 O1 e3 [3 }! }; W7 c; [s.str[::-1].str.match('ta[f|g]|n') # 反转后匹配; e7 \( p; I- R- Y1 X. u- D# v9 e
s.str.contains('[f|g]at|n$') # 二者等价
# V! E! [% a+ K/ e% C& q1 O8 A5 o1 i1 w+ c* c
0 False
3 w9 z7 X/ w8 d0 N+ H* W0 m1 True
- G ?+ e0 P3 p K: E2 True6 {0 o1 f5 B9 l) o$ T
dtype: bool- ?% \" Z, \; ]# x" O, w
1
4 {0 @. k J% F2
/ @6 I7 k9 X. w C, J3
1 r7 \, N/ ~' r7 {1 k. C6 {4% x9 s& N% G2 Y2 z- |: }% u
5( r) y! R& m. J" z
6# o f G6 {7 @7 @ U8 p
7
/ \/ R! g9 _: ?. ystr.find与str.rfind返回索引的匹配函数,其分别返回从左到右和从右到左第一次匹配的位置的索引,未找到则返回-1。需要注意的是这两个函数不支持正则匹配,只能用于字符子串的匹配:" g1 a9 r( {; \; K, h; V* _
s = pd.Series(['This is an apple. That is not an apple.'])+ S% Z- _ f+ s1 w% `! a8 c) f
1 }) R" ]$ E7 a4 a" v; m+ G
s.str.find('apple'); ~* ^2 J8 v* d+ n" S3 g" I
Out[62]: ) i+ B* w" }/ e4 D1 ~4 F
0 11/ s3 y2 H* w2 y6 f! A
dtype: int64
+ }9 s" {4 ?+ ~& l% N
1 Y2 }$ W5 ] u( @s.str.rfind('apple'). [6 T4 C7 z. \( i" J: G! U
Out[63]: + h. X! D. b4 v, D# w% M
0 339 H6 c& ?' U8 ^( O1 o
dtype: int64
3 b4 V1 s3 W! f* E1
! k$ J9 g; `5 ]: L9 x2" _1 R! O1 {4 ]( Y' B
32 g! S. t, w) v0 p* K
4, j; ^$ a4 f4 A/ R" q+ l1 p3 @( I8 M
5
0 z b2 T( r& E& E! C- d/ @62 M. u* f9 s) ~/ o& x
7. c; X* o/ @+ n: J7 I+ Y q
8* N" M& Y# X5 T7 g
99 z7 \* o4 b! u# L% A4 a
10. `. r8 Y: B1 I. E
11
* L! J2 `' R6 `, k3 g替换
8 E9 @6 K# |& H- g* {& A* t* Pstr.replace和replace并不是一个函数,在使用字符串替换时应当使用前者。 |2 y3 H) e0 t0 h* i w" n$ ^
s = pd.Series(['a_1_b','c_?'])
0 ~' ?2 ]% o8 Z9 Q+ T/ q# regex默认为True,表示是正则模式,否则第一个参数内容表示是单纯的字符串,也就是匹配字符串\d|\?; C" o, E7 ~3 v4 f
s.str.replace('\d|\?', 'new', regex=True) 1 g) S" C) V8 l9 D
/ n9 |3 o) o P2 J1 y" p0 a_new_b: u# n0 E c) a
1 c_new
& A" q1 _9 t# E' ddtype: object
2 \2 I6 K2 `. c" T4 W7 I/ E. Z, G* v1
8 U% P" Q' A- {; ?9 h9 b3 u/ |2
6 H+ e- J6 N$ N q3 I/ \9 `3/ u' C7 o' Q$ `. Z8 N
4+ {! f8 e9 c6 l/ `( S; }
5
9 A) \! U4 S% a! }66 ^, p' N' u# k
75 D% h; Y0 T4 P; F0 A [% b
当需要对不同部分进行有差别的替换时,可以利用子组的方法,并且此时可以通过传入自定义的替换函数来分别进行处理,注意group(k)代表匹配到的第k个子组(圆括号之间的内容):
1 N4 s( K: W7 s) J
' v: g5 `: B' J- o7 u, H1 Cs = pd.Series(['上海市黄浦区方浜中路249号',
& B- p- T7 V, X# R '上海市宝山区密山路5号',
3 y3 v5 g1 U: m3 F) i '北京市昌平区北农路2号'])9 i: o+ X* }9 `' |
pat = '(\w+市)(\w+区)(\w+路)(\d+号)'* y) V/ G7 R, e: V( b6 i- f
city = {'上海市': 'Shanghai', '北京市': 'Beijing'}3 G6 t- k' `3 q8 A
district = {'昌平区': 'CP District',
2 z8 b1 O" r$ J8 n( T '黄浦区': 'HP District',- z" u. U# a; a
'宝山区': 'BS District'}) [3 T5 [8 k( J# j4 X
road = {'方浜中路': 'Mid Fangbin Road',* W1 d- g1 z1 k0 h& P
'密山路': 'Mishan Road',
, B) N3 a9 E+ f '北农路': 'Beinong Road'}
* T1 q5 j( y; H; z) a, i& M: adef my_func(m):& H$ n8 f( V' \# K
str_city = city[m.group(1)]! g3 A8 K) x* Y6 B1 h
str_district = district[m.group(2)]
5 g- h, ~0 Q0 I* Y ]) n str_road = road[m.group(3)]$ M! \ y8 s. g
str_no = 'No. ' + m.group(4)[:-1]+ N' t9 N- c, B3 F" I& i
return ' '.join([str_city,
, _7 r4 g, {) ? str_district,7 K/ i0 A/ `% }8 _
str_road,
) o/ z1 W& T$ g+ _! l) d str_no])# I. Y7 q6 E. z! R, v0 k# _+ G5 f$ R
s.str.replace(pat, my_func, regex=True)
K/ j8 M; H) e# p! |- F4 I$ v9 z, I, q5 b! l& Z1 S
1
2 z: A7 P: V8 r# a0 k8 y* Q6 W$ K2& b5 Z4 v, \- Z+ D
3: z" p# \, l0 b0 x
46 ]- p e) m9 ^% O4 @5 n7 T/ [( @
5
9 ?8 R2 P1 A+ }$ V/ r( z b. |6& M7 x8 q: G1 \8 }$ h5 r' f: N
7
/ y0 N2 f. z1 _8
/ Q7 S- h r- B5 S" p d9
7 x! q T, ^7 P* \2 d6 H10+ v& ~6 [* I4 I0 Q" _/ q5 x8 m5 [
11! Q. E# Z& P5 b! M7 k8 p
122 K* K( Z$ T; h# w) \& u% a
13* t7 R: |" S2 }# A0 p' f1 K
14
& ^- d t4 q3 r' y" M15
A. J0 A2 |; ?, @0 l; g16. n/ Q+ c+ p% c/ f3 a
17
; x# T5 `" t$ s( ?# ] x18
$ |* M# |% t0 P- E2 x19
3 j# O- z$ ^7 r# Q$ ~& Y20) K ^1 A, j: p! m. C* S& L. z
21
. h! y/ p2 v* t/ g0 Shanghai HP District Mid Fangbin Road No. 2497 R/ a% D. w O& ]% e7 F
1 Shanghai BS District Mishan Road No. 5
1 j% i ?, q' J( x8 n0 N/ N2 Beijing CP District Beinong Road No. 20 p' E5 b+ T& r k
dtype: object" Q! o9 i4 u& G/ r
1' h2 L* [$ B- U. U, }
2
" v5 r' o" j0 W5 t. [% R31 o* Q3 o1 ^! ]! s% F% F* K
4
& L% S& T+ A9 [/ O8 V& z这里的数字标识并不直观,可以使用命名子组更加清晰地写出子组代表的含义:
6 o6 v' l; w" L4 I$ B2 Q e/ `9 i+ R! S
# 将各个子组进行命名
) o; K/ h+ @, n7 c9 Cpat = '(?P<市名>\w+市)(?P<区名>\w+区)(?P<路名>\w+路)(?P<编号>\d+号)'
' W3 q, p2 }6 U( J4 ~def my_func(m):
. I- u& H$ ?3 H0 n6 g str_city = city[m.group('市名')]
' k/ V# p9 o" e5 X0 E" {. k1 | str_district = district[m.group('区名')]
9 U: |9 ~$ w2 H str_road = road[m.group('路名')]
3 b6 j7 n" Q# O0 @ str_no = 'No. ' + m.group('编号')[:-1]
/ y+ g( x% {& E return ' '.join([str_city,
, C3 z* p# s- s9 V. R5 i0 l) \ str_district,* S. U9 P: N+ Y* K1 H9 E! M2 Z
str_road,% J h6 u" N% L7 J
str_no])
g6 K9 y1 `: |s.str.replace(pat, my_func, regex=True)
0 f9 l5 g `- }. T+ U1# D% ~9 B$ h1 D# m1 R1 [
2# m( O0 _% [: S/ z. P/ W
3* e' O: I. P; L6 a' b
4
% v7 \+ J: x# q# a# E' |5
, w2 F# a1 c8 u: N4 A, W. W2 v6
9 L1 m7 \9 l4 z- b- r: d0 M7
% t7 N# K" ~+ b$ b2 V4 {1 B8 v+ k4 q* r$ F$ n& i* h/ l
9
% C) Z" j; P$ @5 B10
8 N: u' g6 d" V5 I11
5 `6 X% N& e. u, c12
, Z7 T" w7 U3 d2 a0 t6 q7 ]9 w) ^0 Shanghai HP District Mid Fangbin Road No. 249 l$ k2 m. G$ g5 Q) h2 v& w8 g
1 Shanghai BS District Mishan Road No. 5
& W" }2 @: k$ K: Z3 j; L7 a2 Beijing CP District Beinong Road No. 2
( u. W8 o7 m( ~dtype: object
: G J F) A5 Y! u' l14 M% D/ Y+ a7 @$ C6 ~7 c3 L/ R5 r3 J" v
2( h8 F" l' ?' p& _
3 k$ P) V" A- W5 l" M9 F% S
4) R2 U: N4 T% ]' l
这里虽然看起来有些繁杂,但是实际数据处理中对应的替换,一般都会通过代码来获取数据从而构造字典映射,在具体写法上会简洁的多。" Q1 M; g0 Y. ?$ F
$ k5 O8 b* y7 r: { H8 C, u# q4 S4 Z8.3.5 提取' J3 z4 x* B d- j
str.extract进行提取:提取既可以认为是一种返回具体元素值(而不是布尔值或元素对应的索引位置)的匹配操作,也可以认为是一种特殊的拆分操作。前面提到的str.split例子中会把分隔符去除,这并不是用户想要的效果,这时候就可以用str.extract进行提取:/ X0 n& P- ?/ s/ {
s.str.split('[市区路]')' Y% `/ p9 f7 ]; I) w
Out[43]:
: `8 u! i+ @1 G+ T6 Y" j0 [上海, 黄浦, 方浜中, 249号]4 |; H y$ o1 z8 m" t' v$ o) i
1 [上海, 宝山, 密山, 5号], Z3 f; x$ |& U2 G: ^: y0 b0 J
dtype: object+ o& e) |) K0 w% v: z7 P
% T( H7 V2 o$ W7 b& K! t
pat = '(\w+市)(\w+区)(\w+路)(\d+号)'
& b& f0 |3 D) y, Z3 }8 Us.str.extract(pat)
& |6 o8 O" i4 K5 y4 cOut[78]:! X, v+ ^9 L! ]
0 1 2 38 C f9 m/ s5 H6 b& f2 @/ B) G7 t
0 上海市 黄浦区 方浜中路 249号3 R* ?+ t( X3 W- H
1 上海市 宝山区 密山路 5号
! z0 `" ^7 B: e0 \4 r3 E2 北京市 昌平区 北农路 2号, Z9 S% j& u: P y; ], j4 s3 b
1. h: I0 a6 G" w' x. M
2
! X5 ]. ^5 W& Y" X! i31 q' k8 [( X# t1 B- u
4" r g/ F) l; |4 M- M
56 T! j1 c: h! ]. T( D: w- J% B
64 v# E/ r! \( k. m; A' X: T$ P
76 f; ]: ^4 k' v0 G6 h
8( L4 r0 F( J! R, l- C) f
9
5 l) l: N) s" G109 s0 _7 L* I& }/ o7 u1 x& i
11
/ R: a7 @6 i# [. [. d0 p12
2 Q3 n( y4 U& n5 k13: x6 G) }( Y2 I) f6 }
通过子组的命名,可以直接对新生成DataFrame的列命名:% ?7 n5 ^( I! v4 {3 N: x! w+ S
2 o) N( {" h. S5 [6 B
pat = '(?P<市名>\w+市)(?P<区名>\w+区)(?P<路名>\w+路)(?P<编号>\d+号)'% X# Y: Q7 m/ G8 P+ h1 _
s.str.extract(pat)7 |+ g8 _) J" o3 {3 Y1 @; X$ ]
Out[79]: + q- H% s# A$ a
市名 区名 路名 编号; i# ]* j9 f. I1 [5 Z/ d9 w& T
0 上海市 黄浦区 方浜中路 249号6 c* P7 ~3 w( V/ M$ Q3 C3 V, Z$ T
1 上海市 宝山区 密山路 5号, v7 }) G$ j8 p2 M; q
2 北京市 昌平区 北农路 2号 r0 `. t6 ?: {9 i. g
1
9 Y* J! y6 o0 P2 n* o+ I4 F; ~# {, u4 ^2+ y$ X) ?2 S2 G
3
: o7 R p3 o6 Q& b" E4
# \# E( j2 f0 n! D8 \/ L- U' z+ O5( ?3 B9 z$ M7 V! b+ D
6$ ]9 t( Q3 I% s W+ b) `
79 A" b0 u' _, Z7 V& g' ~0 c
str.extractall:不同于str.extract只匹配一次,它会把所有符合条件的模式全部匹配出来,如果存在多个结果,则以多级索引的方式存储:
1 |9 h3 r, d$ f" S& \: s" N- ~s = pd.Series(['A135T15,A26S5','B674S2,B25T6'], index = ['my_A','my_B']): a3 p& j# ]5 `
pat = '[A|B](\d+)[T|S](\d+)'
7 Y0 u2 `& g( U! W0 m. Bs.str.extractall(pat) S" C p J. m5 x$ u
Out[83]:* Y2 N! c5 n h/ [6 ], B* G( p" ?
0 14 n( ]0 K8 y k y+ [* K" F
match
. v) b' B: d) R+ V% m/ Imy_A 0 135 15+ A* ^; X: P& u. b8 Z
1 26 51 C+ D1 X2 w# A9 V
my_B 0 674 2
$ u f- I P9 d R7 t 1 25 64 {5 Y7 e3 A2 S; j6 S4 t
17 w, V8 e+ u Z- a' [
2
3 N4 h! _% O" ~5 M0 X3/ q: G+ T. q2 q% G: |3 A1 Q5 `
4
1 v" m: P7 g- D3 S* A( \5
d5 V; i$ u" i% U) s6- o" K) P/ {# }* F
7
- z' x+ I- e' y82 b6 j Y2 ^5 J3 A( |$ M. o. w
9
* H Y- C4 j8 E4 e# A0 }10+ O4 N& D- t. h* i
pat_with_name = '[A|B](?P<name1>\d+)[T|S](?P<name2>\d+)'
+ ?! I* I" b/ ]$ Ds.str.extractall(pat_with_name)/ g8 S; k6 X* B5 ], D
Out[84]:
/ @& M8 h, d0 c/ G name1 name2
: r; M1 b6 Z6 `* W match 8 V b, n- e7 v' s( q+ _
my_A 0 135 15
1 n, E" P6 J# O 1 26 5
0 k) D1 Y1 L" U8 L4 X( tmy_B 0 674 2
7 ?( T4 @" j1 \, w 1 25 6
6 M" I! [0 f9 u9 L! h- B. C! V) y+ P1 a* D4 a1 ^+ }
2
; `4 {# p) j, R8 Z4 m3
' I$ i$ Q0 f5 a; x8 O5 H7 P3 T5 b: x/ {4- Q7 c# l% R$ t. n
5
5 Y4 t ` j* j# ]7 x' M( \6# B* j* _% `3 a% ^
7: |% ~6 K4 [! {
8" y B+ F$ C C# U2 o
9
! g8 U& d o: Q$ o% U- W2 U* o4 `str.findall:功能类似于str.extractall,区别在于前者把结果存入列表中,而后者处理为多级索引,每个行只对应一组匹配,而不是把所有匹配组合构成列表。
2 m/ K6 E8 G( \/ P" v" n! Fs.str.findall(pat): { ?8 I# Y4 |6 X0 h
16 @ ^/ a5 t. E& D
my_A [(135, 15), (26, 5)]& B- e' j0 b0 s# P% { Z
my_B [(674, 2), (25, 6)]
. Q- u; b! b" |* q+ odtype: object0 y) C. u+ E) T$ c: l) W
1
7 p4 V# |: X: K$ }. J2
, I% X" ` ]+ o( d# U# @; E x5 j31 Z% N7 [, y+ L$ a
8.4、常用字符串函数
+ o) q1 R. L# Y7 X9 O3 n 除了上述介绍的五类字符串操作有关的函数之外,str对象上还定义了一些实用的其他方法,在此进行介绍。5 K7 j, C! v* s
" U3 T+ ?! k3 \# k0 S6 N y8.4.1 字母型函数! R4 Q. y9 x* t7 z& d
upper, lower, title, capitalize, swapcase这五个函数主要用于字母的大小写转化,从下面的例子中就容易领会其功能:0 z# V" C( F2 H9 }. u
9 ~* {8 p. I. A. w% P; L5 d( [1 g
s = pd.Series(['lower', 'CAPITALS', 'this is a sentence', 'SwApCaSe'])
R$ D/ I4 a+ q( e
6 ]3 {- E0 [# y' b+ Y) ]0 {s.str.upper()1 f* ] b7 ~3 Q, v/ v( T$ F3 F" M
Out[87]: % x/ Z! I1 c \* K2 W) w3 n
0 LOWER1 p. V8 z5 o6 k$ I
1 CAPITALS5 ~2 b4 W3 B) O- _2 M* O
2 THIS IS A SENTENCE
, B" p7 f' ~- ?% I. b9 C7 A8 w3 SWAPCASE
0 \4 `- v5 t) _! L$ I" I. v# z2 Xdtype: object
7 b$ O8 S8 ?1 z% e" r' O
& o# v( B+ z- }' P4 x, I4 x% es.str.lower()
2 |: n) B) O! K( s& d q1 L/ W( }Out[88]: & W. H4 |/ Y$ @" K7 \+ ~
0 lower
3 G. G# n# }- J! L% d) {* Q1 capitals
3 p. G$ [) w q# h) u- @2 this is a sentence1 \% s* j4 k* a; I8 V F. u p
3 swapcase) h# N9 C- B' v5 B
dtype: object" P* @* M2 N3 J0 A, V
+ p1 ~/ I4 V( [, V$ l
s.str.title() # 首字母大写
% G' U9 O3 X5 g6 J, C7 aOut[89]: ! O1 v& L u N7 C9 J. x" ` A1 J
0 Lower0 ^7 C8 J' r+ m- s* g; r; i) u5 F3 p
1 Capitals
& q+ t" X* h# ~% G: u2 This Is A Sentence# v! j* N" i ~0 @5 E$ z
3 Swapcase
2 A5 |0 M7 I% k& m& x( j" J2 V$ K- zdtype: object
2 ~$ l7 ]7 N2 f# g5 J
2 M1 q/ h6 ~# C3 A9 i) F" [( T7 Rs.str.capitalize() # 句首大写: b i; }) q; P
Out[90]:
9 |9 i# ^# H# w1 p. V& j/ Z0 Lower% ~+ _: n6 }# A
1 Capitals
" ]; P! `5 u7 u+ n3 l# |2 This is a sentence6 ^; D# Y1 l7 ~' H! d
3 Swapcase
L' H5 q' O, w# _dtype: object
" N$ Q# ~ d7 d' W2 O: l/ i7 h; N& M' Q9 }' v) r
s.str.swapcase() # 将大写转换为小写,将小写转换为大写。2 @5 P0 o: { |9 J) D; |) D
Out[91]: 2 _: ^0 o. X7 R* u
0 LOWER# A% M2 [3 M! n1 {+ c. q2 l
1 capitals
v4 G5 k5 Q' j7 w- T2 THIS IS A SENTENCE- T. \4 f! V1 o5 m9 r
3 sWaPcAsE
0 S3 v/ k4 f: f1 Qdtype: object
! ^) L& R/ e1 @& f4 F" A. h0 A+ p4 N- Y" S; q2 f
s.str.casefold() # 去除字符串中所有大小写区别/ @- p2 _, v$ x0 f2 R' \( E5 k
- I: R& N! N/ R% v% Y+ ~0 lower
- ?7 _% P1 A: b, G9 c% v' H1 capitals& i; h; p4 F i( l! f4 I. j2 U
2 this is a sentence
1 o/ q* q s0 r3 swapcase, {* T& s- l3 Z4 ^' Y; t/ g
' y& P( I# P6 X8 H! c
1! i1 B# z" r$ f6 A
27 H4 s) ?2 k! O9 d( v4 [
3
: z+ H& [, b, h b, \& W4. ~2 O w( W" B' o
53 W$ u& l$ \! M& ^
6+ ^2 O9 o/ Y4 b: C
7% H( S0 ~' o9 d' W) x
8
5 {' E9 i1 x: K: u' Z1 V E8 [( U% N97 {+ l! w2 @" z; K/ j1 p
10
! l2 \1 Q& ~8 ?4 h11! U* _ V, a. ^5 t: J
12# L. d, G+ ` g6 }6 J
13& n3 N, X( @# e( b
14
) R# @% n6 Q1 \15
- {5 H1 X0 `3 R- C0 W16/ \! V m! d; g
17
% P8 ^. J1 v3 k2 m5 h18
- O+ K7 U3 m" o X2 Z19* p% q( A% ]4 l$ z4 @
20
. H1 \7 Y" t q4 T9 }& N( Z21
. R: ~& J k8 M6 C22
% z4 n' n/ y+ I9 `: \! e& f23. a4 L6 I5 a6 W! W3 C
24( h! ?. m- U Y/ E" y* K
25: A2 Q% I' M5 H' \
268 m8 {5 Q1 T" f2 w/ b
27- m, w' d/ E, ]. s. K; Z* p
28
5 V+ A; W' G, ?) h" q+ V1 Q29+ H1 g J( H( U3 C* ^0 U
30 ~0 }& @0 R6 q( P! _! A
31
: b$ |2 }" H! C. n! O32- p& a5 M1 ] ~# a3 m+ {$ O2 T
33
9 n; W! G- w( h3 `. Y. n l34# h+ Y9 E4 t) l# z" P! n+ ]
35
: e2 A; t! b7 `; V) P' Y2 G0 M361 g `5 ^! S# M7 \. F8 M
37/ V" H3 z1 ^9 |0 h
38# Z' B a; L* C3 d. Y
39
) K) i0 ~* K t$ j# f1 U% j& o40
- `4 Y9 V) m! }! W+ w7 [41
. `5 h: _9 w4 b/ s" @7 ~42
4 C# f% p7 g. ~8 W; u6 |43 @. H. P% r* H1 q
444 `! Z" m9 K% U! H' O0 s
45* W/ `7 @# {+ U! x. U8 ]6 z% A
46. v2 k$ v) U: s& W, T
47
; w1 J) Y; G8 D/ z) z48
( P9 [1 l e( S( i, b0 L# t+ |8.4.2 数值型函数
, h. A9 B1 q6 `! ~) a: P& J 这里着重需要介绍的是pd.to_numeric方法,它虽然不是str对象上的方法,但是能够对字符格式的数值进行快速转换和筛选。其主要参数包括:5 B) t( } S+ Z) u
: G' c" p) S2 g/ K! R0 P! T, yerrors:非数值的处理模式。对于不能转换为数值的有三种errors选项:+ z, B& h: F8 C$ O) e$ \
raise:直接报错,默认选项! c5 H, a7 Z% L2 ^4 i5 y+ a5 `5 N) k
coerce:设为缺失值
9 d' B: u$ m5 r+ h# A. Hignore:保持原来的字符串。3 P$ s' [6 M# \; X8 z& O
downcast:转换类型,转成 ‘integer’, ‘signed’, ‘unsigned’, 或 ‘float’的最小dtype。比如可以转成float32就不会转成float64。
& Q& r; c2 @" b" Y& Q9 O2 ws = pd.Series(['1', '2.2', '2e', '??', '-2.1', '0'])
8 v: z7 _. T, ]$ k
: b3 a6 Z4 L/ \! Xpd.to_numeric(s, errors='ignore')
, e, W' J5 T" s2 YOut[93]:
( T4 k3 D1 S$ ?0 G0 1: o4 X6 R$ z, _! r8 z3 j. `0 F3 N
1 2.2
0 d* d" a$ U0 M& o+ D7 B; @2 2e% T k/ R d/ b5 |, q" W( U% p
3 ??
4 {2 q- R1 t/ z7 g4 q d4 -2.1
/ _* \* i0 Y) Q' N5 0' H( [: H+ I1 R9 f# {
dtype: object
$ R5 H+ n( S4 l W+ d9 H
* p: n7 r& @7 ~- s$ p7 D: k; Hpd.to_numeric(s, errors='coerce')5 z4 x/ q/ P) n0 w2 Z) w
Out[94]: $ i, H8 g5 Z( _% U$ b
0 1.0
3 M2 P* p! r% t g# |( u. x( p1 2.2
4 I. j) A: {9 `2 NaN" z5 D$ `2 \& Z) L; @: y
3 NaN+ _" i: b0 y g. F, W& ?2 [# C
4 -2.1' i$ @" k2 g3 g
5 0.0( [8 E* O) I. k! \
dtype: float64
9 A$ S; ?! b- ^/ u# m
7 a* _3 U3 O# o# P: F% I11 T, }& g" [. c# K: M
2& R9 G' B5 M* J, l+ q3 ]
3
$ Q% _# B" x+ c9 c; z2 ~. z& G7 H4
5 [$ O/ s( S1 t5 ?( j2 m5 s5" L( R, ?0 T; x: e
6
/ f% ^; w+ k$ {0 n74 S/ X& s& Z. D
8
3 B: f7 M2 K ?6 ~& V9- M+ R% l8 I( }& M
101 f# i6 t/ V0 I( H& w) \
11
3 L9 b+ N8 q! l1 Y& p1 _! ^% I12" s, c: E% e/ _5 B+ \3 r7 [
13' g3 }0 M1 Z* R" i+ d
14: L6 t+ X# p8 M; h; H4 m- g
15
7 ~' k4 Q% b( _3 W16
{, C: Q( b/ _8 |3 E" E17
/ \* x7 e2 f. g& c18+ j+ f l( \$ B
19; I0 O- h% f* {
20
: @' {5 j$ M* r4 p: G2 n21- Y7 P% _% e8 D& }" R8 H
在数据清洗时,可以利用coerce的设定,快速查看非数值型的行:
z- t* g1 N+ V3 g. x) s# j7 I0 ?
! t8 g! x* Y! ?s[pd.to_numeric(s, errors='coerce').isna()]. l* D( c" Y! ^! n c
Out[95]: ; `- \! I4 d8 N% a/ c9 b
2 2e
2 r5 l( ?" ]( T! N9 g1 R" k3 ??$ s2 R6 A- q x& Y) o6 [* P5 q9 {
dtype: object; G B! l5 P, G, E) @' a
1( d& |7 L5 Z6 N0 ?3 ~
2$ c3 T8 \( G. W# z' e
3
+ h9 D' K- |" y. a4
d3 `! m- H! f5
+ \6 h/ a7 u( e6 F5 G8.4.3 统计型函数
- r9 a- X! d( [9 v$ X) O6 ~ ? count和len的作用分别是返回出现正则模式的次数和字符串的长度:
( D/ C0 n3 Z3 Q7 g# X
4 l# I8 V5 \4 w7 Q) {) `s = pd.Series(['cat rat fat at', 'get feed sheet heat'])
- w# B) v2 [+ o0 S; ]1 g+ s6 ]7 h" M) ~
s.str.count('[r|f]at|ee') # |左右两种子串都匹配了两次& S8 t, e$ b6 o+ Z2 F
Out[97]: 0 \) S1 ]2 O: {4 p3 E/ V6 b+ R
0 2% D g, G5 F! J0 E0 _1 w0 C
1 2$ ~$ x6 A) R6 q" r; J
dtype: int64
+ \. c$ R" Y3 ?; \1 I7 P5 k3 _' L' c# c
s.str.len()
5 e8 N8 y6 J) _4 Y) Z' F! O, }Out[98]:
4 ]: R9 z, E' m) L0 14
1 z, T9 C5 o( j( ^7 B' e+ y( t1 19! f4 b( Z, I& ~' S
dtype: int64
5 O0 }; f- d( r4 e1( {) c3 H4 k. I' Y
25 t5 Z4 W1 ?2 u6 d$ y% x! [
3# E" z7 o% ^( ^% D8 y7 U
4
6 y" M' j' ^ f0 C. R4 R5
% h) X+ S+ h, c2 G67 v$ ?9 }+ r2 V9 F0 g$ u
7+ C8 w" J3 D4 J
87 }3 E! L+ y7 ^% Y' e5 J6 `
9
% u* X" [. @" q9 c1 Z% W( L10+ W7 k7 ~5 n+ v6 T( O3 A0 [6 d
112 h! m2 |4 H `( O% m% w) L
12, ]" p- p5 d3 d. @8 ^, H0 f
13
9 H8 j0 X: R8 n% ^3 ^8.4.4 格式型函数
9 O* K* c! ^- A, h3 V3 j0 z 格式型函数主要分为两类,第一种是除空型,第二种是填充型。其中,第一类函数一共有三种,它们分别是strip, rstrip, lstrip,分别代表去除两侧空格、右侧空格和左侧空格。这些函数在数据清洗时是有用的,特别是列名含有非法空格的时候。
$ p4 K2 W( W/ o8 P
3 A: |: l4 f! ?my_index = pd.Index([' col1', 'col2 ', ' col3 '])
2 M- |# k( Z1 y& v4 M* p: L' j5 h& h& k* p" @4 x5 r, S$ B
my_index.str.strip().str.len()
! f5 [+ e3 i! Y2 j& r; jOut[100]: Int64Index([4, 4, 4], dtype='int64')6 m# {0 I. }+ i; s; N% L
$ ?) p) @1 E8 [7 \my_index.str.rstrip().str.len()# U5 m1 H1 V% h- p
Out[101]: Int64Index([5, 4, 5], dtype='int64')6 H: b' y9 b( I. x6 |9 [
! ^9 ]$ M& K5 L3 _. b4 G3 ymy_index.str.lstrip().str.len()0 m/ l. z2 P' f4 r8 E" W
Out[102]: Int64Index([4, 5, 5], dtype='int64')! P6 Q" f: S3 a, S H
1
: R8 V7 Q. Y9 P' }% ^2+ h: y0 x8 y4 n
3; _( l% m1 Z( R" [) u
4
0 ^8 Y9 B4 T* F1 h1 ?( w) ~: w/ T# b0 k55 d& E; f$ p" Z3 Z, ^
6
; i9 i! k# R' W* j% C7+ C5 ?7 ~; t8 ~! @) Q6 P
87 w4 x' w) h r$ T
9( k7 `: [" P. r: j B5 F7 X3 T1 r
10
6 X" _6 Y& r$ \ 对于填充型函数而言,pad是最灵活的,它可以选定字符串长度、填充的方向和填充内容:
6 ?) }# P1 U& L" F. X& \: a; g. z: J( w$ h% W; V6 ^) f2 w/ s! s
s = pd.Series(['a','b','c'])5 ] J* p! u ^1 X; h4 G- |
4 Y( B5 H2 o$ \4 Y: b2 Us.str.pad(5,'left','*')& e0 @1 v5 E% v, a8 B* B
Out[104]:
- h' o" N: \$ z& p; R" O0 ****a
9 x' d" ^) z+ z- X1 ****b; G: k0 s& y" q( W, ^# N; a
2 ****c
% C R" o" T3 \8 Vdtype: object
8 [( j9 D) ?. J% e8 \) }8 f! Y7 ?: R
s.str.pad(5,'right','*')' j% G+ E7 w& V x8 X# ^+ @3 E# q
Out[105]: : d, }4 `; B- |
0 a****
* P( V& ]- k9 h' d# M- z1 b****
' V) C* J- Q2 S" S, l! ?2 c****
Q+ ]0 c7 U0 V/ t% _7 L# b vdtype: object
% A' t: g7 y' h" h! \, C9 B. \
9 I3 k- F; N$ k2 }. `s.str.pad(5,'both','*') f' _/ N9 u" w- g/ r* K. l3 L
Out[106]: 4 p+ ]: m! j, d" X
0 **a**
6 D( f+ x0 u/ y1 j( w1 **b**9 t1 z' c# u! ]; B
2 **c**) d; g* H# q# y1 o! T" N; W
dtype: object. m) I2 R6 e% t$ L% t
: R9 r/ r, _, T3 i/ b/ l17 o- u2 e) e! H I* }
2" o" }" {% {1 x. g
3( I# f/ o+ |( c- ]1 F/ l
4- \* v. u) W& r% n& D; O/ E+ ?
5
2 k, Q& T$ j0 s6
0 z' G' K% N% K% q2 z7
$ D! Z/ A% D/ [- ~- J8) x. P& o$ E; @) u
9
! [" P2 A7 z9 {/ t! k10
* Z8 `/ d7 k4 P, B11
# r2 }; O9 T/ x# G12
) _# g L- @+ h" u$ ~" r7 |13/ t4 j* {$ W1 ?! K9 `
14$ g/ K7 I4 b6 C2 e% ?
15
- H4 Y: a! n: _/ @9 @, d6 p16: I/ T7 E/ v' C/ b: P
174 H& |) W$ Z0 q; d4 c0 k/ g
18
0 e. d( \1 g+ ^0 b/ |" R; D$ [19
# t: k' n4 S" s- V$ s. R+ p) B20( x5 o7 {9 W, Q3 }- B
21; ?+ K6 v+ g1 v, }, U/ D8 a, X3 T3 {
22
' g" {4 A c) D B0 X' { 上述的三种情况可以分别用rjust, ljust, center来等效完成,需要注意ljust是指右侧填充而不是左侧填充:
. U# m; t( r( M% ]' ^; f/ r# j- l {! D: \
s.str.rjust(5, '*')
# K! j& K; X6 R% t3 K1 S& nOut[107]: % E) J5 |6 p3 r% U2 b* B8 z$ x
0 ****a! e& g! ?0 t+ `6 m/ B4 H
1 ****b
8 \- S5 H) k6 {1 o; h! x2 ****c* k, O( Y2 i% }. X* ]2 j- a+ r
dtype: object; t0 R. F8 k# g) D, K0 p
" W/ D% g: K" c q( O5 I3 Z" l
s.str.ljust(5, '*')
& B% Y# ^6 r! j4 q; ]; bOut[108]: " `8 B$ Z/ ]9 u8 k
0 a****
], }4 B4 [% ^+ W R( @$ y1 b****
% k5 f9 d4 a. U! U& n Q2 c****3 L v+ ?; R% ^4 ]4 y- z
dtype: object
6 Q: R g9 G) i# m; R$ x# |+ g' }0 w: Z9 i; j# Z% d
s.str.center(5, '*')1 y/ N% m" e1 }
Out[109]: ! `) o; I z p( }4 a/ p
0 **a**8 W0 S J4 @- e9 @& z8 p) b
1 **b**6 L5 Q, E F1 V) Y0 \( \0 l+ a
2 **c**
4 e _ {1 b! l8 P) b7 wdtype: object0 d/ I. C/ X4 ?
; q" W @& f4 W& T, f1 Z* G* e3 U; |8 W- v3 E- s
28 k, O l( D- k+ X A& {0 O. I
3
8 P% K: \, ?, a0 P+ J7 u4& ^0 [! k. N6 l Y9 n# p
5
& O3 [; { U; ?1 u3 i6
7 D9 f: z" o/ A; X: h6 G70 g& \ ?4 D7 W' ^! e/ D/ \. H
8) @6 z. H& a8 G V& w+ \
9
% a9 L$ V& h1 n% F10
" j5 W1 O6 p; P# j! N11# h+ H- y: S% t2 |, t6 i
12
- \1 Y9 @3 R, K/ e0 V! u2 _ w- I. [130 m3 }$ X% {6 H# K9 \
14
" r9 |$ n, n! I# R7 G- Q# j15* T; K7 W8 f9 z: M: N
16
. R' \5 P; J7 y. W170 B8 T7 J$ K7 h0 @
18
$ Y" h7 i5 C7 _1 K) z19. U# }, R' [3 t1 K- r& V
20
1 s x& \- X$ m/ v% e 在读取excel文件时,经常会出现数字前补0的需求,例如证券代码读入的时候会把"000007"作为数值7来处理,pandas中除了可以使用上面的左侧填充函数进行操作之外,还可用zfill来实现。: [1 F% ]2 c* Q
% a' k. n% q* O9 n
s = pd.Series([7, 155, 303000]).astype('string')/ o. J& I, w- r5 Z8 H( G
1 H: b2 u9 X1 |, o, M6 Es.str.pad(6,'left','0')& a+ O) T" N% q2 h5 ? o1 P
Out[111]: ' h4 f B! K% e) W; p
0 000007
7 m' d' I& J. h6 Q' n1 0001550 Y( h) b& R8 a. j
2 3030005 L7 w5 Y/ S \* C( K
dtype: string
! C# B8 ~( L( t8 f6 n9 B2 d' P9 A0 D6 U9 Z, m7 z: e% N
s.str.rjust(6,'0')6 Z$ C b$ P5 p: ?! h
Out[112]: . `1 N8 m& f% b" l( }- Q8 I1 z
0 000007
; y5 W2 ^$ l2 y) W0 W3 Y7 d: F1 000155
; D8 I" H C) Q1 V' @3 ]) q" V7 Q2 303000
3 m6 V( P$ }( Ddtype: string
5 Y3 C* P8 P, G( H' x8 e" T
, T8 B7 w; C, u" c: As.str.zfill(6)5 A+ s. X, g- u0 K) |( s
Out[113]: ; V9 q% |- m& ^7 ? }0 J$ Y6 ~9 {
0 000007: D, v% S5 e& A7 u+ ]
1 000155
1 {1 D% R4 Q% W% R7 z8 v* c1 M2 303000- d, O) N+ w5 u, _5 _% s) ^
dtype: string
5 R8 S% Z6 ~; d2 v% {7 V6 m, l% ?1 F) }' }8 F( e
1
& X- Q. U# ^+ d) [. U* K/ r9 m. `2 ^! m28 x# v8 w% Z2 R: M# v
3
7 ]; b% M4 v8 Y4 [4$ |+ F' @# N C5 M$ {
5. e) L+ l6 Q- i! m6 U8 m3 {) t3 c
6* @5 S9 k; v- w: r5 k* M
7" F! E, O: Y z+ Z. [/ c
8# |4 b8 x4 d5 J- d3 }. o
92 e/ j2 P/ T4 Z. \. z" i
10
. G& @/ g3 @% t; w0 ~4 t11! D2 [3 l) {2 P4 `$ T
12
m# e; |- T7 o$ ~5 N; J4 N% }- p13
' J Z* p4 C* A" D/ w14
5 C, \# w; m) @" F3 F+ y" s; W; z15: d" M" h; ^0 T- {
16
! W# [* v: F6 E% N# E! c179 w# f) @9 [6 j. B T& x% G" i7 g5 m
18! p( j: |4 A |, a( _
19
1 `9 j2 C$ p' e. E1 W4 W20. w2 Z* N/ J& O4 g% q/ v! `
21
% S! C9 O+ |# K" I( g1 Y4 W8 I229 ]+ h1 k8 o4 n% S4 O- j& Y
8.5 练习
& K5 l/ w* \' fEx1:房屋信息数据集: |& k# b- W/ }+ N+ }9 K! H
现有一份房屋信息数据集如下:
4 u& |# i3 P# l2 v, R( R/ S9 B
6 e/ ^% C8 x$ y" M: Y5 F! {& ~% fdf = pd.read_excel('../data/house_info.xls', usecols=['floor','year','area','price'])8 i/ A+ G$ v7 I1 {
df.head(3)/ K1 r5 p z. x" l* i4 L
Out[115]: + Y0 m! Z, X2 E
floor year area price& w6 p! j- |; `* d' T
0 高层(共6层) 1986年建 58.23㎡ 155万; N4 E a: O" }0 m+ Y8 Z' K
1 中层(共20层) 2020年建 88㎡ 155万) }4 o6 { i6 k# X
2 低层(共28层) 2010年建 89.33㎡ 365万0 T- o* V, z7 o; _, v
16 s& v. A! A4 K
2# ^( ?7 ~. K1 z y
3
1 s5 N( B! h8 ]. j4. T8 w1 G) Q( Y+ ~/ R
5. k0 I- F0 T. n1 Q1 k: k+ e2 @
6
9 W% G1 n$ B9 s5 ?7" e6 k+ H! P+ D" s7 k
将year列改为整数年份存储。' n/ S$ F) H. o0 s2 V
将floor列替换为Level, Highest两列,其中的元素分别为string类型的层类别(高层、中层、低层)与整数类型的最高层数。
5 Y1 a$ D8 T' ~2 |9 n计算房屋每平米的均价avg_price,以***元/平米的格式存储到表中,其中***为整数
/ x! N8 G$ u' A+ T F将year列改为整数年份存储。1 |- p/ c# A, ~2 i3 Z' [
""", |- b" g# r$ ?; `: r/ c5 Q3 O
整个序列需要先转成Nullable类型的String类型,取出年份,再将年份转为Int64类型。
/ f! U* a/ c: _, P2 P+ C注意,转换的类型是Int64不是int,否则报错。即使astype加参数errors='ignore'跳过缺失值,
, I3 m+ x. K- s3 V& e$ ]+ I6 U转成int后,序列还有缺失值所以,还是变成了object。6 t% D4 ^# H3 o8 { s
而整个序列转为Int,就还是Int类型,缺失值变成了 pd.NA 。3 `. f6 v) ~( K5 |
"""
+ Q2 j0 J( w" @% z! Sdf = df.convert_dtypes()
$ K2 g& W. }/ L$ Fdf['year']=df['year'].str.replace('\D','',regex=True).astype('Int64')
- q6 j+ I8 [: W0 c5 odf.loc[df.year.notna()]['year'].head()$ ]; a( i, F+ {4 t9 I( h6 ^% k' j
1 ?* C( s4 K: B; l' i& F
0 1986
7 @+ ~! f4 v6 z& \1 2020
1 l, W9 R5 S0 w% I3 k2 2010
: Q2 \& I) \) o3 2014
% P* ~4 O1 m% p) A! O9 E/ P' t4 2015
/ u. Z' r3 ?5 ?' e; z: ^9 qName: year, Length: 12850, dtype: Int64
* d+ D4 E( j3 D7 _: C
/ p# M% S0 y2 t. @& p1
* l' T& @) n6 N: F- F( K; |* _" E2 a2
; M7 O* a f# H: D3
+ }- k, H5 D! u* O: Q" B) N$ n4& \$ |) D7 o6 J+ l4 ]& p
5
) u+ F* }' r7 E. G( A6
7 q# u6 A" Y7 o! R" C d8 D, l7
$ T( u+ N% U1 D. ]* r- _8
( C3 u5 h _( _0 Y9
. p' H7 b |+ Y6 ?1 `104 s, ^; n- j: e6 ^; K
11
- Q8 K! x o- T* |4 L! W; t }0 v128 n0 n/ f. s, o; n# Z
136 I6 G7 Q* B& j" M
14 g8 o% ~' U/ s9 U
15
7 A( t# `6 q- ]6 D; ?( I6 ^& {16
3 \8 w( M" `( z/ s; Q. }. c: b) ~9 ^参考答案:
4 q @- S3 r# y+ s6 H8 Y, g
7 {2 r# [$ M2 N: D0 w( y0 c不知道为啥pd.to_numeric(df.year.str[:-2],downcast="integer")类型为float32,不应该是整型么. E9 |+ |' j1 R, h2 x! I% }2 \
! E# B! a U8 A1 J* x, V3 f. s; Q
df.year = pd.to_numeric(df.year.str[:-2]).astype('Int64')
6 X% h1 b' }0 Odf.loc[df.year.notna()]['year'], v k7 J2 A8 C/ K+ H* A0 w2 }3 ~ _( j
1
# I5 H9 u6 @& [4 b, I20 }+ Y+ z% X4 a3 M' b
将floor列替换为Level, Highest两列,其中的元素分别为string类型的层类别(高层、中层、低层)与整数类型的最高层数。
/ A, l; f( E% M1 t9 O+ [ J: zpat = '(?P<Level>\w+层)(?P<Highest>\(\w+层)'3 c3 @$ h) {$ P4 l. Z
df2=df['floor'].str.extract(pat) # 拆分成两列,第二列还是(共6层得形式,所以还的替换一次
! K# r6 g; D5 _. o% H% odf=pd.concat([df,df2],axis=1).convert_dtypes() # 新增列拼接在后面,再次转为Nullable类型# X- T# E3 @% n J6 [* l- ?
df['Highest']=df['Highest'].str.replace('\D+','',regex=True).astype('Int64')
, c2 w4 ~7 j) e& V( Vdf=df[['Level','Highest','year','area','price']]7 `+ a+ Y# W/ L8 d. @9 F
df.head()
" h8 @1 [5 C6 b! F4 o
9 v8 R: Y8 @1 f4 W5 Y/ y- h4 N Level Highest year area price
, |" L7 c) J- D0 高层 6 1986 58.23㎡ 155万5 v2 L+ Y; [# y9 X
1 中层 20 2020 88㎡ 155万
' y8 V7 Z7 t/ U. |) \' m% o2 低层 28 2010 89.33㎡ 365万
5 J' F5 w' f7 F8 B! k- A3 ]" [$ D3 低层 20 2014 82㎡ 308万% k* W% H* Y. O4 H9 Y7 N) v( Q+ A4 L# ^
4 高层 1 2015 98㎡ 117万5 l/ y' Q& I/ m, o9 P$ X
1
& I, Y3 V! D @2
" ?2 X+ w2 O, B3 s% m3+ E9 g( t+ |5 l! v8 W2 P2 _' D1 U
4
! n$ a: |3 c2 G2 s a5
: B8 Q" s0 {0 l8 d7 d1 n3 s6 L61 S8 g% {0 z# Q4 E# R
7
2 k% ^, C) L9 B8: j+ q) f) ~6 o. u( r
9# T& C3 g6 t% T( s x7 T
10
' Z* ?1 t! t& |6 L* D1 T' H) M11
& n' D# q0 @" A0 X: m12
3 o6 g w# C: }. o' a13& m8 e$ c5 B k- a$ v" d6 G
# 参考答案。感觉是第二个字段加了中文的()可以准备匹配出数字,但是不好直接命令子组了/ L* ]% W' A9 {. y
pat = '(\w层)(共(\d+)层)'2 Z6 B1 y5 Y" ~1 D1 G4 g
new_cols = df.floor.str.extract(pat).rename(
! R! v3 C) [3 y* Q columns={0:'Level', 1:'Highest'})
* C7 p0 ?& }0 M9 C9 ^4 L0 E5 n1 n) r# E
df = pd.concat([df.drop(columns=['floor']), new_cols], 1)' P& l$ @5 |+ `) e( p) t; n
df.head(3)) v: e/ H& q0 d: e6 [9 `7 W/ N
1 V. ?; N1 A4 q6 o$ y: G
Out[163]:
5 `8 |* e2 r9 x! x( r- d year area price Level Highest2 t0 m' s9 ]( y# B+ D$ B
0 1986 58.23㎡ 155万 高层 6; |% H5 t2 C4 R/ E7 B% p3 B Y
1 2020 88㎡ 155万 中层 20
4 T( j, C: f8 v! k; h$ |8 z |2 2010 89.33㎡ 365万 低层 288 n$ `) t X! R+ v$ l7 V- [
1
; |( Y) g! i! W' b2
1 c$ n4 |2 Y8 P7 o0 K3& q: _3 G6 s5 H# }5 K0 d. T
4% h! E: f6 h5 Y# T7 W1 U
5
- ]+ h3 [; D) {0 R/ L* G6
0 R6 L( t8 z. g4 P# f2 ^71 @, [ ~0 E. V3 z" T: Y: ? z& ^
8' w" @; V3 K# a) p6 }
9
4 t* g! ^* \0 p" r7 c- t- K10% g% ~; D: P0 u$ H H6 P
11
! ?/ H" f8 Y" z# W: l/ x! s; B126 s8 G& I, l$ k2 p9 o) W) k
13
) Q# {! o* m7 j计算房屋每平米的均价avg_price,以***元/平米的格式存储到表中,其中***为整数。
& o& Q. A$ l u"""
8 Y0 K8 C5 f- [5 tstr.findall返回的结果都是列表,只能用apply取值去掉列表形式 ^0 b3 }1 g9 x' H! N) ^- N
参考答案用pd.to_numeric(df.area.str[:-1])更简洁8 _1 Z9 s4 N% a7 g" S/ _4 _
由于area和price都没有缺失值,所以可以直接转类型! x! }+ C) I1 S$ e% g
"""4 Y* r" Q; `; H j P6 P' v. s
df['new_area']=df['area'].str.findall(r'\d+.\d+|\d+').apply(lambda x:float(x[0]))/ m# r% ]; S# E+ L
df['new_price']=df['price'].str.replace('\D+','',regex=True).astype('int64')% U# P3 F: f; a! z% i& ^
df.eval('avg_price=10000*new_price/new_area',inplace=True)/ n8 Z* D# ?: N3 O6 t) x7 t7 r2 w t' ^
# 最后均价这一列小数转整型直接用.astype('int')就行,我还准备.apply(lambda x:int(round(x,0)))$ X5 \ E ~0 W' w, _
# 最后数字+元/平米写法更简单* ^ d1 i4 |, j
df['avg_price']=df['avg_price'].astype('int').astype('string')+'元/平米'4 U8 n! z ~' O4 Y: a, g+ M( w6 `/ M
del df['new_area'],df['new_price']
* U; _/ C+ {: X! `3 K% l$ @df.head()
; F) V, m8 ^9 s6 N9 Y' g4 N
! O" T$ A. r0 b Level Highest year area price avg_price; E+ K: v# t+ \# z$ j9 l& |3 U
0 高层 6 1986 58.23㎡ 155万 26618元/平米
& v5 m# d9 ?% f! p$ a1 中层 20 2020 88㎡ 155万 17613元/平米
+ C, V9 |! C3 b3 T* ^2 Z4 P4 x- |2 低层 28 2010 89.33㎡ 365万 40859元/平米1 p; _. j d) K3 d: i2 a' K l( S
3 低层 20 2014 82㎡ 308万 37560元/平米
1 m) e5 }* c& u5 b: Z4 高层 1 2015 98㎡ 117万 11938元/平米
+ R' a/ W7 l9 o# q# P2 ~
* F- ]4 b, w, r( [ x$ _1
1 ^& c$ m( ^* J, M' x+ a22 c; r: D; T. k% {2 |# ~: M
3, k2 ?) A1 u. l" p1 Y/ I3 L
48 D# c" a4 o! l% S5 A
5
( v+ m$ G; R# a; I! F2 E( l6 x6) h" V' I4 _$ P: z* J* f2 j5 ]
7& F/ `% ^+ D2 m
8* o- ^; [# o; H( E4 b$ ~3 S: e" ^
9) u& m7 m6 F4 r; v5 Q0 D
10
) b# P- B5 {! H8 k11
- O5 U: ] w% H& v* g Z* ?* j12. }2 |3 K' i/ K9 e/ N( t
13
& g* H3 b( Q3 Z14( V% k9 o% o- w4 H
15
* ~6 ^9 ?9 l+ z% x8 r3 j; ~16' H& z' W& ^6 ?8 ?+ P
17
+ T- I5 c/ \! f3 S+ m18
) S4 U' G. c/ G' w ^191 w) r* Q: |- p. Q6 O. |6 O
20
% O% S8 l5 ]8 J1 g6 R5 Y! x" [8 {" s4 Z# 参考答案
( N! S' ]: a( c# @8 u# F9 Z+ Ns_area = pd.to_numeric(df.area.str[:-1])
% B7 L* d5 \& A! \0 H3 L( t, t- M! Ts_price = pd.to_numeric(df.price.str[:-1])% Y" x9 _: y7 I
df['avg_price'] = ((s_price/s_area)*10000).astype(
0 V) i8 j! N. K; U+ B3 j 'int').astype('string') + '元/平米'
% Q0 D+ |8 w" E# Y2 X9 o5 H( S7 D9 R
df.head(3): f* p8 h8 A* H o) G& S' S
Out[167]: ! L) q& ^1 v( O9 S
year area price Level Highest avg_price
0 G& v6 \' B2 \( w- T5 n0 n0 1986 58.23㎡ 155万 高层 6 26618元/平米
1 L+ ]! R5 ~$ C3 G% ]' V( A1 2020 88㎡ 155万 中层 20 17613元/平米
: _. e" {# D( W! Q) I% {# x2 2010 89.33㎡ 365万 低层 28 40859元/平米
6 s, |$ a* |8 u- x1& J. p4 E' A+ w% C1 A
2
$ @( x9 u& r1 g1 v3" D. G8 q' n8 C4 z
4
* f/ E2 g+ Y( W% W m5
, D1 ?- T: a. P7 B; k2 R) V6
% G0 z& X2 C* A4 M7% E, q" @, T8 k: \& {# X f
8
1 q. P2 N/ @1 z2 E, A& U _9' L+ \! w1 y2 Q2 w. O9 `9 @8 t
10
! |! T* `3 I8 f; o: v- O/ D/ ?115 v2 d: E; v$ |' ]
12$ S* I* F5 I) N3 n, _) `
Ex2:《权力的游戏》剧本数据集# i9 }& T* Y" u7 b6 @$ i2 b
现有一份权力的游戏剧本数据集如下:
" s& W( @# V- F) X1 x5 _# h! O j4 v2 ]: U, M# w
df = pd.read_csv('../data/script.csv')
' [5 |; f# j$ U- p# Jdf.head(3)
5 O; M$ Z- d; G1 Y. z& Q
; v6 _: p" n9 u& w6 B. G0 C: i" {% L# ~Out[115]:
4 r# `. K8 F5 ]# t% q+ h9 y; uOut[117]: 7 H/ O1 N5 d! K! [# I- {
Release Date Season Episode Episode Title Name Sentence
1 f2 q0 N5 U0 A0 2011-04-17 Season 1 Episode 1 Winter is Coming waymar royce What do you expect? They're savages. One lot s...
& Q6 z j$ _' Y z2 d1 2011-04-17 Season 1 Episode 1 Winter is Coming will I've never seen wildlings do a thing like this...
2 ]# r- G1 w( u) A3 U+ c2 2011-04-17 Season 1 Episode 1 Winter is Coming waymar royce
2 h, A! y8 o5 F6 p% O& f4 S! t1
4 I9 \; R, t' M4 [ [( o) X27 b6 v' D* l9 H+ T
3
+ |% O" i7 q2 P! y4 g3 S' w# o+ Z; C ?7 V% Z
5& t9 e1 y' x+ g3 @% ~
6
6 q3 S& t+ ^- Y) j9 w& Y7' T5 O+ O9 p& L/ w
8' K$ q6 o8 W1 `9 y
9) ^5 P! O5 b( @4 c
计算每一个Episode的台词条数。/ M* n0 A' i. C# h, t
以空格为单词的分割符号,请求出单句台词平均单词量最多的前五个人。! } @8 T& u8 F! W8 K( D- t' t
若某人的台词中含有问号,那么下一个说台词的人即为回答者。若上一人台词中含有 𝑛 个问号,则认为回答者回答了 𝑛 个问题,请求出回答最多问题的前五个人。
$ s, F2 _4 S: C4 X/ V计算每一个Episode的台词条数。
, {8 F# |! R3 p- pdf.columns =df.columns.str.strip() # 列名中有空格
2 t- H- C/ _4 N% o- c R/ [. I% c7 udf.groupby(['Season','Episode'])['Sentence'].count().sort_values(ascending=False).head()
D! F; M; e& |. O6 N& L3 P$ {
% w3 g: ^4 y$ G# z, Cseason Episode ( C: |3 u9 z9 a$ d# K
Season 7 Episode 5 5056 w& Y; j2 X" h6 k- Z
Season 3 Episode 2 4803 Y, j4 ~& a( b% j
Season 4 Episode 1 475! C& }$ E: L0 L9 o8 V
Season 3 Episode 5 440
" A3 ^& b! `0 e! L/ o0 ]Season 2 Episode 2 432+ r! R* ^2 n& E/ h
1
9 L1 `# ?; C( H# G/ R, Z c4 l29 h+ T, c$ G `/ b7 w
3
2 e# Z, p) _0 o: w. G7 s4) G7 ~! g, n- w# t3 A! l* l5 J/ B
5( J* D4 [( W" J) D2 |
6
: m {, j# ?4 w- L" a, a71 p* k2 V7 F8 M( F& y
8- t; ?1 |+ u0 p( a
9
! T2 r3 d( _- a以空格为单词的分割符号,请求出单句台词平均单词量最多的前五个人。
4 } ~( O$ ]5 l: _) @) ~* {+ E# str.count是可以计算每个字符串被正则匹配了多少次,+1就是单词数/ Z6 b m- P Y1 }: d: Q$ Q, U
df['len_words']=df['Sentence'].str.count(r' ')+1
* ]: C% k; L+ w1 U8 b+ Cdf.groupby(['Name'])['len_words'].mean().sort_values(ascending=False).head()
/ [4 p8 q+ Y7 M% C F% y
4 _% @' k# F; V: sName
, }) H) l0 q0 g6 [3 qmale singer 109.000000
% Y/ ]( x! u( pslave owner 77.000000 G% `+ p2 \0 q, P
manderly 62.000000
/ r7 P( n o: U. jlollys stokeworth 62.000000- e# F2 U) K3 `
dothraki matron 56.666667/ h2 v$ E6 f. f' I
Name: len_words, dtype: float64$ f% [" q! E9 F. ^
1
1 r7 M0 M/ S: J# N7 p6 z2. Z4 x5 W# o0 a
3, [. M4 ?8 |& P5 u( W
4 u. z: h, h1 m; t8 }. Y0 ]
5
R0 ?; g' _" o/ J6
, n4 e C, @! {+ o, l7
! b0 z( E: B, w+ t80 W! K! U9 @0 h7 {. d$ F$ B
9
/ K6 p. Q' n+ M10
2 x4 P# y& \ `# u11; H$ J: k2 t) L+ Z3 K
若某人的台词中含有问号,那么下一个说台词的人即为回答者。若上一人台词中含有n nn个问号,则认为回答者回答了n nn个问题,请求出回答最多问题的前五个人。
+ V) p# d: k( ?$ l1 J3 h7 Udf['Sentence'].str.count(r'\?') # 计算每人提问数
+ c2 z7 [& K. B$ E2 zls=pd.concat([pd.Series(0),ls]).reset_index(drop=True)# 首行填0
$ K8 A* g1 Y6 V) ?7 Z vdel ls[23911] # 末行删去
9 O- K: q6 R. H- U P; h4 a9 r, Tdf['len_questions']=ls. c9 e6 k; P" h% T* _
df.groupby(['Name'])['len_questions'].sum().sort_values(ascending=False).head()6 S, D. T" r- P& [4 H' C1 N' t/ b
7 {/ R9 f# ]4 d# p; n. l FName
+ p; v( m. I4 w2 m3 a4 N+ ]tyrion lannister 527
4 R% a6 H9 j: l9 j9 `# yjon snow 374
( O. a0 w; A7 m! M5 m4 ^jaime lannister 283, v* K& X+ \$ T& T
arya stark 265
% {) d9 z% j7 u) h3 F& m# ~cersei lannister 246
# p7 A, Z4 o% o/ }9 i/ pName: len_questions, dtype: int64
2 }" V: S1 F' Q4 Q* _1 i/ E
2 `: o! e( P4 ?. J1 L2 d# |& O+ s' k# 参考答案' ?) M4 F$ k P' F* A8 \8 `/ D
s = pd.Series(df.Sentence.values, index=df.Name.shift(-1))
$ N2 ~0 w$ {$ J0 Ms.str.count('\?').groupby('Name').sum().sort_values(ascending=False).head()
( F5 y4 [. @( l E, f) c( A* A. z
) s2 h/ c* G9 Y. Y. x19 }) Z+ d% v& X( z% o1 Q$ w
2% B4 w1 E; a2 U" P4 V- O9 Y! `
3
' U% E# ~3 E$ ~) C7 N4
/ o4 W c5 Z( W* z5 m57 L2 r% E9 Q- ~; O
6* Y$ N5 W$ \' Q. |' u
7. Q3 u9 \6 U' M
8
0 n' R* A! h/ H8 n9
) T! {, ?) x- A, p6 v# U% D/ y10/ d- a8 N8 b3 [
11
: d5 X4 h1 x1 }. v4 p2 S12
% j7 T# Z" X& b7 I& {9 b138 S( @9 ]) C8 k4 ?
145 x" W8 S# I7 X* k
15
- Q3 P; ~- V0 O165 w2 W( g, T7 f2 H
178 s* }' d) S) r2 j' v# l
第九章 分类数据/ u& X5 K) P, a% ^9 A& G
import numpy as np4 G, c6 l; A5 {2 ]
import pandas as pd
; L3 Z4 h- j v+ y8 Y4 ]$ W$ d1
/ s% K% }$ S1 v8 K7 H26 F5 \ P. C* N q/ _# F0 B
9.1 cat对象
7 L6 e& G1 W! U9.1.1 cat对象的属性
/ w* X( l6 s! q& p: ^# W6 ~6 E 在pandas中提供了category类型,使用户能够处理分类类型的变量,将一个普通序列转换成分类变量可以使用astype方法。4 q( I% n+ X2 P7 f
4 |, Y0 J7 o5 f& D
df = pd.read_csv('data/learn_pandas.csv',
- U& j/ n% K; P! V+ d- o usecols = ['Grade', 'Name', 'Gender', 'Height', 'Weight'])3 L( ?4 a: |/ ^) ~! |5 P" i
s = df.Grade.astype('category'). a% m3 F! A4 m8 m' f
( T7 l; @: {3 U9 X/ g% C
s.head()
) ]2 b5 x4 e$ i: a& {# K- N [. qOut[5]:
3 ^" L9 z7 ~- s' T8 x O4 \0 y! V. U0 Freshman
# J; X) s* X3 E; j5 |1 Freshman
1 t# ?5 D- G: @* U2 Senior
0 x, n- c, ~5 Y& z+ Y8 ~8 r3 Sophomore
+ |" W+ q; D; ^4 F4 Sophomore. O" s6 k. w& Z1 I
Name: Grade, dtype: category
( e% z$ |4 f5 g/ b3 k: Z- ]Categories (4, object): ['Freshman', 'Junior', 'Senior', 'Sophomore']
) K' \6 V0 m: }' ^$ W1; H9 J1 @0 x. i- b1 _
2/ `, U9 p8 W' A' Y
3
; r4 y* q5 V$ U6 M5 y% V6 T' V4! z/ F8 ^4 k- m- E
5
! w! k6 K6 p0 ~0 G% w/ I6) s) R/ @1 C) |# X8 s! L, ?2 |- ^
76 r( J* Y* q% c8 y' @1 U( P
8, N- T" Q+ |+ v
9# f! C+ L1 A w2 s: z! B' p1 G
10$ C* l8 J1 M/ h( U
11
) [$ C: j. n2 g+ y) t/ @% p12
3 M) ]+ D! u, E6 m6 o( ~6 D5 U13
# o9 C J- r1 N/ I d5 E# m 在一个分类类型的Series中定义了cat对象,它和上一章中介绍的str对象类似,定义了一些属性和方法来进行分类类别的操作。' ^, `# M, ?7 d
' n$ V, a+ J# P5 V4 as.cat! O( x: z: ?1 N& c
Out[6]: <pandas.core.arrays.categorical.CategoricalAccessor object at 0x000002B7974C20A0>; L1 R# E6 J: s+ U d6 M8 H
1
* E8 X, N. v t, p2" B+ {0 Q5 D9 s: s+ k1 {
cat的属性:$ j! N8 h/ X" @! d# f `
- ~ @, J: M' |+ T( N0 `cat.categories:查看类别的本身,它以Index类型存储
~) A. w3 r6 I1 w1 {# Ccat.ordered:类别是否有序
4 P# w3 C3 V) v+ {9 gcat.codes:访问类别编号。每一个序列的类别会被赋予唯一的整数编号,它们的编号取决于cat.categories中的顺序, { g0 D+ B) R- [9 Z8 h) e, U
s.cat.categories* l' x8 @2 ~/ E; L9 ~2 x) [
Out[7]: Index(['Freshman', 'Junior', 'Senior', 'Sophomore'], dtype='object')
! h6 x6 z) q8 G& a
/ ]7 A& Z9 T; a e$ s& P" E% Ks.cat.ordered+ C* J4 X2 V9 V. Q/ ?
Out[8]: False
# H6 T" v! m1 P( i, `3 K% n1 i7 [/ @; `9 j5 l9 z) E0 {/ D4 X
s.cat.codes.head()
; D$ E) A" ^) _3 T+ S/ C I: m& Q0 HOut[9]:
$ I0 y! |% N* \0 0
% @$ c1 h. a! C1 M% t; c. T1 0: `" @! _/ ?* i( k5 E6 w4 A% m
2 2
0 S1 ^3 q* E& l3 V0 J( l3 3
9 V6 U9 b8 ~# s9 Z# H& C2 S4 30 w% @( ?9 _- Q0 _2 B1 v( b
dtype: int8
5 X5 k9 ^7 j# Z6 \! {1
$ ?( c7 [; d P0 {2 c E: }* y. f+ M2% }9 B1 X. t, q" w% x, B4 f
3
t5 f/ f6 h2 i2 p `4
7 r$ p, |2 `/ K* b% C: j* h" {5( s( E( I1 T3 a5 X) m
6
+ G% q& I U' e7 F) S7
7 _3 R% y' ~6 b8 W! T7 W82 b6 Z2 q, }( Q8 z: c1 \& Z
9
8 E5 r: [% a' u: r$ e102 ^1 @2 S; y# B& O
11
9 ^+ d( D3 F% B0 ?8 O+ T12
/ x* p# c. w7 [! q2 f13
% f- k/ s I8 i147 }2 \' Y6 o4 ~/ |: L
9.1.2 类别的增加、删除和修改( M$ \ N! W* P& {
通过cat对象的categories属性能够完成对类别的查询,那么应该如何进行“增改查删”的其他三个操作呢?
7 m5 W7 m% g6 {7 k2 _4 [( H1 \4 B* p$ G( w4 W. C
【NOTE】类别不得直接修改
5 o, @# R Z0 f, H i2 k6 ?在第三章中曾提到,索引 Index 类型是无法用 index_obj[0] = item 来修改的,而 categories 被存储在 Index 中,因此 pandas 在 cat 属性上定义了若干方法来达到相同的目的。9 z( M1 ^' l; a6 s; ]
0 ^' l) U4 U( H: j" m. ]- [
add_categories:增加类别
: J( s, B$ M9 y5 |# y: c" ys = s.cat.add_categories('Graduate') # 增加一个毕业生类别" G7 y4 o! L( [, `+ P* u( g/ {
s.cat.categories& ]! G/ t1 U F; |( R
) P$ {% Q. e* r& V" EIndex(['Freshman', 'Junior', 'Senior', 'Sophomore', 'Graduate'], dtype='object')
& L* w4 }( q% [4 ~1 @' Q, a8 g& ]7 f8 @7 t: c# [
2/ ?! _5 F# l) t9 O
3; O, O8 _9 N- K5 d& G( Q: M
4
+ D6 @) |; C [- x! `' t% h5 J4 Yremove_categories:删除类别。同时所有原来序列中的该类会被设置为缺失。# f- T4 V/ z% I9 |# B+ w* D+ ^
s = s.cat.remove_categories('Freshman')
0 E3 w; g2 W- P4 ?$ v; x3 k h' W8 t, G; N& m* L m
s.cat.categories) e( _/ }. Q# D9 x C" O
Out[13]: Index(['Junior', 'Senior', 'Sophomore', 'Graduate'], dtype='object')
, o6 F. D& P+ \. A3 p0 z+ f% j5 D/ s, a4 I: U6 B# L
s.head()
9 D2 d- E' N7 |! yOut[14]: 5 W. f i1 P4 r0 R2 b
0 NaN
* j. N6 Q7 n# \' ?1 NaN+ I: Q& R' j( w6 K a" i
2 Senior
& m- q+ \& H( ]0 U. ]1 @' Q3 Sophomore
8 y( ?$ i( s* M- c) C* v4 Sophomore
. F0 I0 ~) C& l" U A; u+ IName: Grade, dtype: category
7 V& d- j$ S; ?: k0 KCategories (4, object): ['Junior', 'Senior', 'Sophomore', 'Graduate']
; U% y& b: ~8 L5 W1
, }3 ^/ c! U* a5 z5 F2' Q1 S9 { N# W$ Y' n* \
37 H+ o4 E: q% Q! p" p E3 o _
4
+ l7 i q9 J0 f0 m5 I( ~$ g* `+ }5% ~1 a- V# a) w1 j( g; _3 j, i
6
: _3 A1 e& x4 Y* e$ U8 I2 v79 j% `/ \4 e' c) [& P
8
& }- {. |/ R) T& Q9- G2 @6 O% x; [9 z# x
10
# L7 v4 ]4 {8 ~5 y112 {. b$ ^7 N7 ]6 H2 `6 ?
12
" j, }2 }- O$ `, u( R- \, g/ E13
, i C9 W; N: l7 K8 b& U' {14
- H" A+ m; L; o5 U* aset_categories:直接设置序列的新类别,原来的类别中如果存在元素不属于新类别,那么会被设置为缺失。相当于索引重设。
* R2 n; Y" ]* b' d5 a) s) a Ts = s.cat.set_categories(['Sophomore','PhD']) # 新类别为大二学生和博士
9 ? W! Y4 r7 g+ D- v5 \s.cat.categories1 v5 H- W9 t$ x' }9 W" h, h
Out[16]: Index(['Sophomore', 'PhD'], dtype='object')8 r6 G, a/ q: a/ n
) |8 y2 h+ f* h9 F5 Y4 is.head()8 e% P h* U2 f$ i
Out[17]: ; n7 Y+ i8 u. _+ L m$ d; x
0 NaN
# G; d1 h, ]( e2 W, g1 NaN
4 _, y* p) E a/ \$ R2 NaN
6 z( {6 p. n! A" ] U3 Sophomore& x# k8 w0 F0 y
4 Sophomore' ~+ {' M$ D, _8 Z& _- @# S' k' g
Name: Grade, dtype: category) G, }4 k* [, d1 b: Y8 V
Categories (2, object): ['Sophomore', 'PhD']% l* j0 J! u; ]: `5 e8 J' G" Q
1! k/ v: n; ?) W1 b0 w. I" U4 K2 n
2
2 ]; w' L2 r( M X% G* Y$ D3/ Y! l N( W* ?. M# R/ H0 g, a
4
7 P, e) i% t2 r1 z' p% e5
; R3 J1 E' h3 w5 h( l. d6
. L6 q+ ~' {% Q7 Z2 O) n7
; L& m- ]& {& C- a82 `/ V& f& f) Y2 q$ u
9
, A3 C7 q$ p8 J- \10
4 A% o. @4 F4 A* Z11
$ W Y. `5 ~& W; M) |) v12& U8 s2 ]" y9 p$ X1 a+ Q/ v
13
2 d1 l$ K* ?2 @& Uremove_unused_categories:删除未出现在序列中的类别
7 _' l, t8 Q0 m7 Us = s.cat.remove_unused_categories() # 移除了未出现的博士生类别1 y2 u$ z2 }8 V) S% Y: q$ Q4 B V4 u
s.cat.categories
9 y0 B, p# K6 M: c: D
" u0 A" J; e9 sIndex(['Sophomore'], dtype='object')5 D, G' O# Z& s; c# x; c. H$ w
1
1 ~4 w2 \' R3 E7 b* s9 u2 K1 u7 y2
2 i/ p6 r* R" ~# X36 l( T4 y: t- r: g3 v4 s
4 k9 [, C) t8 B. B8 O% h
rename_categories:修改序列的类别。注意,这个方法会对原序列的对应值也进行相应修改。例如,现在把Sophomore改成中文的本科二年级学生:
" v/ V/ o! x. N1 D/ a/ _3 y" ^s = s.cat.rename_categories({'Sophomore':'本科二年级学生'})1 L2 n; F' P" s4 {
s.head()
& j% d7 h6 y0 d) s) M
7 _: x" T# D* N' w9 q0 M0 NaN
6 N, L, V; }( P1 NaN
R3 D* } \$ z6 W- ^2 NaN
1 o/ b. {1 j$ _1 g3 本科二年级学生6 q! D- t! i5 @) L& v6 _) N
4 本科二年级学生
. ?! G3 m* X( }: N& ^Name: Grade, dtype: category: a5 D/ X( m$ m o: h6 F
Categories (1, object): ['本科二年级学生']
- ~% D4 K4 l7 U. c' J; P9 Q1, f" V6 J* X% C! W$ E+ p
2
7 I# f& g9 e& }* V. k( E8 p3
+ R6 ?9 B4 m! F49 w8 D) c" o) ^3 a) g9 M
5
& O) {0 Q# v$ c69 L" l5 i8 N% I
7 m5 O# T" N8 M8 G
80 B2 ?8 a4 f* [$ z
93 R( i! g6 ?( S. s! D/ i
10
. C3 B) q: U# D9.2 有序分类 `) C/ N }4 u' h8 d D
9.2.1 序的建立. F8 p' B( N: `9 S- |
有序类别和无序类别可以通过as_unordered和reorder_categories互相转化。reorder_categories传入的参数必须是由当前序列的无序类别构成的列表,不能够新增或减少原先的类别,且必须指定参数ordered=True,否则方法无效。例如,对年级高低进行相对大小的类别划分,然后再恢复无序状态:4 ?; g/ t1 g+ K. h$ x& t1 O2 b
9 w4 u1 ^# e; M6 Q" ps = df.Grade.astype('category')+ y2 @! z* ?% \/ t5 b7 v, c
s = s.cat.reorder_categories(['Freshman', 'Sophomore',
. d0 V4 m3 _5 c \8 L8 b 'Junior', 'Senior'],ordered=True)
1 ]' K) b9 w; n1 b: g7 L6 zs.head()/ R! u% A: `" t
Out[24]: ) d" o" s- g% o; s
0 Freshman7 \/ Q# Y) r6 b6 K# m
1 Freshman$ k: ]- P$ s: L# R( b4 A$ ?! Y
2 Senior1 H/ k7 y% c5 C% B5 \0 h8 i
3 Sophomore3 y$ F1 z+ D. R3 o2 {
4 Sophomore
; \1 Y9 @2 H1 y2 s% {Name: Grade, dtype: category. c: e, H& ^! Z7 i8 D
Categories (4, object): ['Freshman' < 'Sophomore' < 'Junior' < 'Senior']' g( v( B7 F( U# J2 {; i! r
' [; f c! M5 L
s.cat.as_unordered().head()
) Q/ Q V: |# @6 n* M+ YOut[25]:
- b- Q8 J/ L- Q5 |! z3 \' j# k0 Freshman
- j- r; D, C4 e1 Freshman
6 A: D7 g) x; T" x+ }/ A2 Senior t; x! @5 V4 B4 o: |
3 Sophomore
5 o5 b2 v4 g* m9 h; \( |7 H4 Sophomore
/ R4 p( I- i. \Name: Grade, dtype: category a- H8 C% m9 o! y, e
Categories (4, object): ['Freshman', 'Sophomore', 'Junior', 'Senior']3 f L5 b9 C `) d8 [% n
) C$ G' Y1 h' q c% q7 E1
* p8 g0 S3 d9 D6 ]& Y2
/ O" y- h f, |5 c) t4 H3
+ |( T- T; M# e' g" O! b4! n5 }6 }2 y/ f1 `/ q/ b
5
7 c, Y& O0 q9 @& x* W6! x' P, t$ |: {) n4 v. P: |- ^
7
9 X% H$ Q4 H7 m8
' o! X, U9 U& a7 O9: n2 V8 m. B* B8 t5 V* h
105 ] f0 J, X& U0 m* b
11
) q* K3 [* D4 @9 Z127 {5 | b, E3 `5 o, k) r6 a
13. r! a9 n0 E; k( n
14/ P' _. K4 l/ o2 A& ~# Q
15
8 A7 v+ T9 _% n# [: Z7 C* ~16: Z8 I( L- f: k2 [2 W9 p! C/ i
177 J; t2 ~; L, _. U) n3 M
18
/ z1 I6 I* u& E" {# o) Y19
8 ]& m- ?; f& r, S% E* `2 G20
0 s: w9 e$ Z9 Z6 e217 ~% i- h, j! N5 I( J
220 I8 p, Z* ?' v! h. ?
如果不想指定ordered=True参数,那么可以先用s.cat.as_ordered()转化为有序类别,再利用reorder_categories进行具体的相对大小调整。5 G, G8 T q; s% d4 D7 l
2 o0 y M4 w4 m( h" t# {( [. Y# g% C
9.2.2 排序和比较 p3 ?* r3 `' Q6 \( l7 A! b
在第二章中,曾提到了字符串和数值类型序列的排序。前者按照字母顺序排序,后者按照数值大小排序。
* ?4 v# h) L/ K9 f7 M
, n: z4 [: } V/ N+ v 分类变量排序,只需把列的类型修改为category后,再赋予相应的大小关系,就能正常地使用sort_index和sort_values。例如,对年级进行排序:4 t5 f& T/ W$ J: T2 J& e
3 x) R1 ^6 ]% ?% F" Y
df.Grade = df.Grade.astype('category')
/ q M) j5 y% {: I% vdf.Grade = df.Grade.cat.reorder_categories(['Freshman', 'Sophomore', 'Junior', 'Senior'],ordered=True)
: _ w6 D$ N; e. \8 W3 T( gdf.sort_values('Grade').head() # 值排序
8 r3 ^0 B! U9 B- |Out[28]: ; L; o& y8 u$ K) l* _' c9 W6 O, Z' Z
Grade Name Gender Height Weight
; F- N5 V+ S" v; m9 P2 H1 ^! q0 Freshman Gaopeng Yang Female 158.9 46.0
$ q- ?5 l, K: H* y7 }/ x. H105 Freshman Qiang Shi Female 164.5 52.0
& J5 W5 X+ x1 v. }6 N; ], o/ i7 g96 Freshman Changmei Feng Female 163.8 56.0 | _% _. N: {. t \) ^4 f3 t' l
88 Freshman Xiaopeng Han Female 164.1 53.05 r* L* e+ Q0 D2 m
81 Freshman Yanli Zhang Female 165.1 52.0
2 j' F- S+ F( d5 i9 j/ L; u
! r; Q* j) }/ s) k a& wdf.set_index('Grade').sort_index().head() # 索引排序
8 E. s! d1 ?4 o+ F7 U6 Q$ a1 XOut[29]:
A& b" p0 l- D0 T Name Gender Height Weight
' A. i" c. F0 K: i& P# HGrade
, u8 {( A9 D6 ?( C1 U; {Freshman Gaopeng Yang Female 158.9 46.0' U% J. Q$ X( A6 ^% f5 U: o0 D
Freshman Qiang Shi Female 164.5 52.0
1 m# B t9 r) K) Q4 |( j* l% aFreshman Changmei Feng Female 163.8 56.0
' m B% Y( o7 j5 T5 y. C4 OFreshman Xiaopeng Han Female 164.1 53.0
3 w6 I$ b& ~/ b( H6 ^+ ]5 mFreshman Yanli Zhang Female 165.1 52.0
- r, \' V, W) q3 G A+ q5 C4 z5 U# U) V4 @( y# v
1+ T$ ] _; l1 l1 t8 i( t3 Z. y
2
: Z: |! F* _0 i$ p: t3$ M* X+ v2 Q/ `- p" T* Z" h' M C
41 i! h4 g: v- d
5
* p0 p @$ V- m6 k3 c" s6
3 R* F$ p8 @: H/ K1 N" ^7 x/ F2 H$ C8 P7, T+ w# ? ?' e$ i4 r0 A3 M
8: ^8 I1 W$ k! @) A# B6 A! Y2 \9 L/ P
95 U: h3 i! A6 a. M, V; F) k3 a
10
9 b- d! F5 S* W118 s2 ~$ ^/ ?* r- W1 m+ u/ e
12, O N3 |4 r: ?6 U6 J+ i# m7 r2 }
13
: `& g. g8 k) n' s140 q6 D' {1 w8 x9 y- r1 C
15
4 s$ T0 N4 g* { y16
, S1 G+ n4 E( E1 k/ v17# I P0 o( [2 r
18" B3 N/ {3 ]6 u! ~4 I' m- J7 T
19
( m, @6 c2 c+ P. ?. C20% N" c5 b* e+ N0 I/ f3 b( g6 t
由于序的建立,因此就可以进行比较操作,方便后续索引操作。分类变量的比较操作分为两类:
' ^2 R3 O$ Y6 x) Y1 ^8 O5 I, b& a6 _3 o4 I2 k, G
==或!=关系的比较,比较的对象可以是标量或者同长度的Series(或list)。(无序时也可以比较). _+ D1 ^7 m9 I7 l
>,>=,<,<=四类大小关系的比较,比较的对象和第一种类似,但是所有参与比较的元素必须属于原序列的categories,同时要和原序列具有相同的索引。# i2 L' A- i/ R- H/ t) }
res1 = df.Grade == 'Sophomore'5 M2 t3 u6 U# C2 ^+ R/ D
& I* I3 j: W. X) A P% e4 r0 ]
res1.head()3 S$ k6 F. l5 B% |' t; \; B+ R" @
Out[31]: % H9 f/ V5 u* o& D
0 False, U- r* v4 a8 \& b7 j9 \: G: d
1 False
* [$ g$ L$ {; G2 False
8 G t* T8 K# Y3 True# Y- g. o a6 ~# c$ N* c% Z+ T
4 True
6 ]- x L+ y. S* Z8 @! VName: Grade, dtype: bool
7 ^; l( o% s, \" _) L& n" V2 l+ p9 {# l" w9 l
res2 = df.Grade == ['PhD']*df.shape[0]" V5 T( s" J3 d3 w
5 E; [4 y6 ~1 s+ Y3 {1 K' i: P
res2.head()1 O6 c. |8 R# ^
Out[33]:
) P+ b4 O9 d7 z- e& t5 }( F* y0 False( r( T- V8 J% X* h3 A; c3 W
1 False
* n6 N) F2 y) }8 H4 i' s9 N2 False. {1 ]& x( i6 x3 e1 n6 U3 h
3 False, i, T+ ?, v$ J( J
4 False7 j9 w- n g0 H8 H+ K g
Name: Grade, dtype: bool
( a, O" R% q: e) ~' ^5 I% j$ ~4 u1 i( ~" R& l
res3 = df.Grade <= 'Sophomore'/ L* Z H: D* W; x! [, K8 i
" }4 M/ c- B K3 v, C: t. R
res3.head()
6 o- j& g* @ f$ @7 P, `Out[35]:
$ {+ Y5 j( O( O& X3 g+ U0 True1 a: d4 c8 L4 D! C9 W3 C
1 True
" f/ Z* X% |; G( M2 }; l6 P2 False
6 X f0 \. Y) I a) P3 True
+ ^ J/ g) L3 w0 M7 J/ l4 D4 True" f: |9 u; j8 H$ V a. _ B
Name: Grade, dtype: bool& e% z0 }: q8 S
; G4 N6 [7 D4 V1 c# S B/ E# sample(frac=1)表示将序列随机打乱。打乱之后索引也是乱序的,直接比较会出错,必须重置索引。" _% W9 r3 N4 E- V+ E5 O
res4 = df.Grade <= df.Grade.sample(frac=1).reset_index(drop=True)
& J W \5 j- I6 d* I4 g3 \
7 M7 I( S7 R& b; U( W: lres4.head()
& e& _: C3 y1 W- N+ t Z! vOut[37]: - |! [3 P% D l& ]" f. e' j
0 True* A j) Y4 |) i! T
1 True
! z; F( E! g+ Z% T1 a2 False* V6 W0 ^6 E# p/ L- D6 H0 H
3 True
6 ]' x5 g( B6 o' e5 j4 True3 d! S: |% s1 q9 h
Name: Grade, dtype: bool s$ f. S* u0 @- m) a4 R% c- J
3 ]5 R5 c) y4 f0 f# y. N0 C( k1
0 d8 h& e0 m# v6 _ }- |26 z% _. J' v% i( \- W6 |5 q% }# c
3, {2 o+ S( `, ~5 J
4' _2 R" x; h0 R7 ^0 S7 Q! c
5
5 H q3 T3 T# G V; q3 I- E2 H6
3 ~1 W+ n! c5 p) n4 I& O! s7
5 V( ~6 h' T0 S! V7 c+ u8. c E( G8 y' M7 ]
9
% O w8 t3 c- g2 E10
) E+ C7 e/ f. Y$ j% H113 |; Y9 r0 v0 |+ \; ]
12- h! Z& C; g7 v$ v: W, \1 T
135 O+ ^' ?$ c" Z
14; y+ u7 ?( h. r9 j/ ?
155 O/ D5 h! C6 V0 W9 l
16
1 V6 K+ v2 x; E" f7 V2 K9 m17! F- [/ j. R: k/ H
181 ]/ r! Q' E# O M# I/ p+ l6 \$ J
19
4 F0 W G0 b! h$ l- \. q1 y7 h6 t209 p" s1 n' @3 l& x6 O
21
6 f F7 F$ T; D3 J. @4 e R22
4 ^1 |3 I. [! x% \6 {23
4 i& e# }1 t4 m* p2 x+ ?24- _3 n7 M$ R# i4 u' J1 m
25) c# U( G7 s+ L# ~+ Q0 z- I) `+ W: B
262 z: u* f; ^4 l( j7 G) D
27
% [6 d) k) ^" |3 @. `. y28! c' g3 g- c/ V; K' I7 Z5 m
29, j2 ^. y7 T1 W1 e% ]' g$ v+ C
307 a* m' ?4 ^5 x& x7 z3 o
31" g( y, w! g* L: R
32& c4 Q2 |. l! E9 D5 L, V: I, i0 z9 G
33; d3 h9 J; u2 @
34
0 C4 z5 c* v# Z' v) z1 u! p- O35
+ v" Y3 o V x5 m/ G" n36
6 C9 q8 J; J7 v+ \37+ ]# o( I; d6 B2 Q
38
3 O/ [- |$ M, S z9 j9 }7 g39; z& a( O G8 I) b: l" U- ?; |
40
! s, n) \3 B% B4 }8 F9 {2 }6 b9 z41
5 i( [3 p* n E0 A' M4 w" V42( U0 q |3 A$ I8 u
43
8 h& |1 F L. P; t% J0 {+ W44
% \9 S0 y( W+ Z4 [6 a9.3 区间类别
; T1 f, I$ s# e, E3 g. f9.3.1 利用cut和qcut进行区间构造
R+ v- n# F& G$ }) f( Y" V. _; @ 区间是一种特殊的类别,在实际数据分析中,区间序列往往是通过cut和qcut方法进行构造的,这两个函数能够把原序列的数值特征进行装箱,即用区间位置来代替原来的具体数值。
4 [. _+ L1 d" U7 A. ^5 B& [
2 l# F9 j' H( K3 d) I! |: Q0 d }cut函数常用参数有:
: a5 N% F7 W+ p- fbins:最重要的参数。
9 x) z+ V0 g7 E: |6 G' E# F如果传入整数n,则表示把整个传入数组按照最大和最小值等间距地分为n段。默认right=True,即区间是左开右闭,需要在调整时把最小值包含进去。(在pandas中的解决方案是在值最小的区间左端点再减去0.001*(max-min)。)
# w7 u/ r% d" ~; G也可以传入列表,表示按指定区间分割点分割。! `' _# H1 Z% E F4 r0 O- v
如果对序列[1,2]划分为2个箱子时,第一个箱子的范围(0.999,1.5],第二个箱子的范围是(1.5,2]。
6 S% S% M$ ^3 U& E$ b1 t( V W 如果需要指定区间为左闭右开,需要把right参数设置为False,相应的区间调整方法是在值最大的区间右端点再加上0.001*(max-min)。# w9 l# E- }) z! @
3 r4 b. R6 S P% h% H( ws = pd.Series([1,2])
0 f3 b$ q0 r. H* ]( U2 ]# bin传入整数
1 @+ j5 [# j B4 t n" S
; g5 f* a9 }( H; `; O% | W) ]pd.cut(s, bins=2)
4 S# |; o3 Y) K E" V: bOut[39]: 9 q* t% \7 x! D+ l" V/ I( k% M
0 (0.999, 1.5]7 x+ ?# |$ a, ?+ `2 |6 J
1 (1.5, 2.0]
3 w9 X+ f5 C, O+ Q9 Z7 j5 Fdtype: category
) F. i3 R1 s" E3 \0 HCategories (2, interval[float64]): [(0.999, 1.5] < (1.5, 2.0]]
; b, `& y* ]6 \6 w( L! b9 _* N
0 e7 n+ G/ H( K0 x& Y' b8 tpd.cut(s, bins=2, right=False)& k" b( w9 r, r+ d
Out[40]:
) N. O4 l& l0 |; ^. n0 [1.0, 1.5)/ P- z3 j% J" [% h, N F) [
1 [1.5, 2.001)
1 G: n8 N) I" \- w! o; g( Pdtype: category
6 W, q U$ Z$ n$ qCategories (2, interval[float64]): [[1.0, 1.5) < [1.5, 2.001)]# t$ z( G# F% {. h! {
' M4 F9 |; Y: g$ Y1 z, ?$ ~, B- G2 j) S2 G9 _
# bin传入分割点列表(使用`np.infty`可以表示无穷大):
5 C$ A8 F1 i8 ?. Y+ T* }. `) xpd.cut(s, bins=[-np.infty, 1.2, 1.8, 2.2, np.infty])4 R" G2 _! _0 b4 H/ h
Out[41]:
D' `& v3 W1 c. d+ i; Q, y0 (-inf, 1.2]" C# L5 b6 \9 T0 [- u) ^0 M; |+ \
1 (1.8, 2.2]! C# E" w- [) S) D
dtype: category9 f. C4 F& v1 c4 n9 y( V
Categories (4, interval[float64]): [(-inf, 1.2] < (1.2, 1.8] < (1.8, 2.2] < (2.2, inf]]" J! A( F: o- a1 J, A2 J
% }4 g4 b9 z$ X
1' e% @- f, N% s- N
2
2 v' H2 J. G* W3: I" V( w2 i' H$ g8 q+ t
4/ C7 {( I ~/ K) e4 h
5
1 V$ W9 v, ^4 O. R) K: Y6% q' k! Q8 e d; t" Y& [3 ~
7- K! S5 Q- G. u9 Q: _- d2 c
8$ s) H, }" n d* C2 t. X
9) P7 p3 t: E8 ?
10. b; p O( K' _, T
11# ^& r3 A" N% d" {: a1 U' u( T: f
12) ^6 C; \4 r9 t" N
13
. S: d8 r5 w' E145 E, h% w( K4 u' A
15
6 j8 y* {* _4 W- k7 S t16
/ u% a& u% o! \' `. ]178 g- h% m: m+ `6 I
18
& U5 F( M' a- H9 O; K) [19
1 P% y; {2 n: Y3 e5 ^ g! I( a/ G20. i/ Z C+ n( ]: {/ E1 I
21$ L# W; p) ~3 j! d; B3 _( _! y
22
8 `: Q) L5 B H W$ \23
- V! J% A/ D: g2 U; Y+ e24* J- m5 B O2 J3 s! l
25
* j _# f6 R0 @ H3 X3 q7 qlabels:区间的名字7 G, c% K9 \4 s# P+ D/ U1 L$ F6 F- g
retbins:是否返回分割点(默认不返回)+ I. T! g- @: y. m- E% x/ n) e
默认retbins=Flase时,返回每个元素所属区间的列表
0 a* W# U& K9 ?( i! x7 gretbins=True时,返回的是元组,两个元素分别是元素所属区间和分割点。所属区间可再次用索引取值$ E' b, f; T6 N
' a2 x5 |0 B& _ K& s M Ms = df.Weight
9 R7 q; j! c& w P: Ares = pd.cut(s, bins=3, labels=['small', 'mid','big'],retbins=True)
. n: e, \ L6 Z& E Mres[0][:2]$ k ]7 v' B. t& x" S5 j
, z1 O/ g+ p2 X' J+ q9 f# L x
Out[44]:
: y, k+ |& b+ N( u0 small" U2 d ~; w6 ^1 v% V- \' C
1 big9 x; W) E3 z1 P5 l% F
dtype: category& h0 ^: ~ R8 J. V
Categories (2, object): ['small' < 'big']2 f6 h5 _9 |* B
! m$ r0 h& B. Qres[1] # 该元素为返回的分割点
: E7 i; c) T/ f) dOut[45]: array([0.999, 1.5 , 2. ])0 y+ n& R+ @: K3 V$ @6 p
1
% _$ \# p: Q) I. n0 H( U2
8 [! v: } F7 v: c3# G! \9 V6 [/ R# n- J
4+ `! C1 y) _" k2 Y/ T1 p) ~
5; E" o9 l+ q# F8 u0 `6 [1 H% ^
6
5 j; L) E* Q) Z! }) h7/ l# o- h. d, l; w) j5 h$ ]4 e# g
8" |$ ^% g4 K* D% Y( t
96 u2 ?0 L# N. f* b" v
10
% n' V. e+ @# S4 t+ N, i114 a. r3 a( e2 t3 b2 [
12& | o V1 f) U; ]" A
qcut函数。其用法cut几乎没有差别,只是把bins参数变成q参数(quantile)。
9 k: b- I; Y9 t: q9 h5 @q为整数n时,指按照n等分位数把数据分箱- g, i! L/ B# j/ n
q为浮点列表时,表示相应的分位数分割点。
8 [9 N& W) T9 F1 r( s8 as = df.Weight
6 j6 O+ ?5 f4 ?% R: z" i) Z
d( x& ]' ^/ _! z8 O/ M: o4 S! ipd.qcut(s, q=3).head()
$ O1 d' S" g7 G# HOut[47]: 3 `8 p, I; }/ B
0 (33.999, 48.0]
( H- Y- _$ m/ ?$ x/ }9 x1 (55.0, 89.0]
' H' M2 Q# d$ o2 (55.0, 89.0]
6 n" `1 {4 G: [) i/ G0 [3 (33.999, 48.0]
8 O5 x, Z g: H* R! J/ ^& o# d4 (55.0, 89.0]
. C7 h- i2 P/ y1 |Name: Weight, dtype: category5 M4 d" Q" y h( {5 s! n
Categories (3, interval[float64]): [(33.999, 48.0] < (48.0, 55.0] < (55.0, 89.0]]; ~! X1 v1 r# \8 \9 X0 Q/ }/ }, \- |& ^
6 O" _) A2 P: f' w8 Kpd.qcut(s, q=[0,0.2,0.8,1]).head()3 v1 n- x2 e/ o. |
Out[48]: ' n- i! m! ]! T: r3 @
0 (44.0, 69.4]
# P4 @! d0 F) {$ U; `; N' W1 (69.4, 89.0]
# J8 m- N4 d S$ Y& b/ s2 (69.4, 89.0]' |8 X8 q: E M
3 (33.999, 44.0]8 S' v; }0 g6 R0 Q' e# p
4 (69.4, 89.0]
& c$ u' g" @/ ~Name: Weight, dtype: category. @' {9 o* T3 d* n! d3 Y l& C
Categories (3, interval[float64]): [(33.999, 44.0] < (44.0, 69.4] < (69.4, 89.0]]$ ^/ p6 f/ e( L; m7 k6 j2 N& X
. y3 G. `5 ~% P* H j) ^1
8 B3 s# p( c) J) A2
! G- u! k( o) U/ G2 Y! f; f F3; Z1 f e: x) u7 d! p* F) `5 N
46 Q* h5 F% O5 c$ e+ T
5
$ i D# Y0 U! }; U! R61 `; D3 E3 }+ J3 M8 Z
71 T; I- v0 d9 f" Y" l
82 C3 a- {! l7 X: ]) R X
9
7 m7 O l$ q& h1 j0 S10
, E- r( |; y: D7 ]- }5 S/ q/ M11
! W: o9 k3 a; K, y( A3 y! g6 R12
5 ?: J! k2 D2 Y13
# H/ R: q: K* Y) o f. _14; M; ]7 ?/ _0 [( J& y L
15
3 |! x+ V; F9 }16& F4 H! {1 s0 q$ @# G+ n
17
. M; B ^, [! x( U+ ]- d182 C" {! j& P* ]/ y, Q4 i
192 y# G5 e8 o2 C8 E6 D
20
1 I. e1 K- B9 g" i: c1 T21
$ J' w2 Q/ @3 n1 ]9.3.2 一般区间的构造
@! M9 a0 @7 ?( [: i) p5 C4 K9 A pandas的单个区间用Interval表示,对于某一个具体的区间而言,其具备三个要素,即左端点、右端点和端点的开闭状态。- G8 z# x6 H( y6 h' q
1 O" z: o4 N4 W f) J U0 `7 [开闭状态:包含四种,即right(左开右闭), left(左闭右开), both(两边都闭), neither(两边都开)。
7 Y$ ], L6 v" a! s' n8 Y7 ?my_interval = pd.Interval(0, 1, 'right')+ a/ z% B2 c3 S' c2 O" T
# m ]7 k0 n$ B
my_interval* r4 |& G- |: K+ d* h
Out[50]: Interval(0, 1, closed='right'), e8 _0 J% J2 `# V1 b
1 |: P6 E, @7 H$ ^ k2 y
2# o6 `) h S2 c1 @
3% O7 `9 {4 a- H- N9 e, o! ]
4+ ?+ D. H: `8 y5 n# |
区间属性:包含left,mid,right,length,closed,,分别表示左中右端点、长度和开闭状态。3 {# a0 E! E2 n, ~! L) c. S/ }' v& t
使用in可以判断元素是否属于区间! `: c' `* D* h# N
用overlaps可以判断两个区间是否有交集:
( V+ R. I* B! F2 M$ k, d( C. ~7 ]8 h0.5 in my_interval
% o# E8 e1 u2 m- L; b/ t' i# \! s j, I) X" u. _ `
True
# `; w$ Z7 u7 C! M' s; F1
i+ J2 |: @. y2
' G: M( W% m* P! b36 w8 w J9 V) M5 {, n$ ]( |
my_interval_2 = pd.Interval(0.5, 1.5, 'left')/ i) f7 a, N Z
my_interval.overlaps(my_interval_2)
' i! O5 @( ]; v8 Q* @2 M! f( d- n$ n3 e% O
True
" @. w6 A5 {: z1" H2 S) A6 }- u; P6 C5 b, S
29 ?6 q4 P- Q5 x5 b2 x' i) l
36 h8 ]+ l0 q, V
4
* s) ~1 q9 c+ L1 e! H pd.IntervalIndex对象有四类方法生成,分别是from_breaks, from_arrays, from_tuples, interval_range,它们分别应用于不同的情况:1 {9 J) c; J/ _
& l! `9 d9 U4 l$ q. X/ qfrom_breaks:类似于cut或qcut函数,只不过后两个是通过计算得到的分割点,而前者是直接传入自定义的分割点:" O' {$ Z' R3 L$ K
pd.IntervalIndex.from_breaks([1,3,6,10], closed='both')
: ]& b+ x+ J0 c' U( n" |! p1 _) ~# L9 H- I% a3 A! R
IntervalIndex([[1, 3], [3, 6], [6, 10]],
7 M7 V0 f% ^/ a6 v1 ^) u2 M9 L$ H closed='both',
* J; I% A: I, \( j* i dtype='interval[int64]')
# V6 k3 \: X, ?5 a* q; Z1
' Y) X7 W H% I2 {( }" R2
8 f3 b3 b7 ^4 }+ U6 b. [1 P3
7 C/ L$ O! r6 `$ r: @8 Q' ?; O5 M# @! u44 `, y0 ?) }- C* `! h1 N- ^% F- I
5
9 r) S8 h- Y0 W% ^from_arrays:分别传入左端点和右端点的列表,适用于有交集并且知道起点和终点的情况:4 X( K% g# |4 i3 A; p: H$ c/ K
pd.IntervalIndex.from_arrays(left = [1,3,6,10], right = [5,4,9,11], closed = 'neither')
# z1 A; a; @# f7 [# N, W/ ^9 P2 S6 e! H, d( R
IntervalIndex([(1, 5), (3, 4), (6, 9), (10, 11)],
: w* K' _ O$ P0 W* P T closed='neither',2 N D! J2 k# P1 V( O+ ^. A) V; b
dtype='interval[int64]')
# C" s! q5 ~ S6 J1) ~0 h1 G2 Z- x
2: f; ?: m0 p( o i( g) g4 V
3
; z% y# _5 J9 D! m0 g7 z4 e. p9 K. }; h) t
5
: O0 l, |* L8 t4 P2 Z2 ^8 }from_tuples:传入起点和终点元组构成的列表:& x, o& t7 ^1 Y, v4 d% ^/ Q
pd.IntervalIndex.from_tuples([(1,5),(3,4),(6,9),(10,11)], closed='neither')
3 \; L5 I; q4 f- U! X: W8 ]( R& T6 J7 i$ E6 p0 Z
IntervalIndex([(1, 5), (3, 4), (6, 9), (10, 11)],: o, a7 M' g4 t) @( t4 s- ^" C+ z' w
closed='neither',
, K q! X1 Y. L, N5 \; t) c dtype='interval[int64]')( y! ~( a1 N$ }" ~# }5 ]+ N
1
: u3 s1 v& q& }% w0 i2& b4 ~1 w5 b! |8 Y5 z, a& g* O
3; q/ _1 M. B3 ]" y `" M
4
# B$ u5 P, x$ N& Q) ?5 C( l" g J2 s) L5 d- C- E7 q' i
interval_range:生成等差区间。其参数有四个:start, end, periods, freq。分别表示等差区间的起点、终点、区间个数和区间长度。其中三个量确定的情况下,剩下一个量就确定了,从而就能构造出相应的区间:. v9 _2 j3 m! f: V
pd.interval_range(start=1,end=5,periods=8) # 启起点终点和区间个数
- ^' G3 w) P% g' BOut[57]: - |. U6 y# h8 ?6 J% U$ n9 E$ B
IntervalIndex([(1.0, 1.5], (1.5, 2.0], (2.0, 2.5], (2.5, 3.0], (3.0, 3.5], (3.5, 4.0], (4.0, 4.5], (4.5, 5.0]],
6 V$ d! }# t5 [+ b4 x3 A closed='right',
8 c$ A4 Y% {+ ~ dtype='interval[float64]')
9 n. c/ ^; Y5 g! S( S' q
6 P3 F: S/ P1 \pd.interval_range(end=5,periods=8,freq=0.5) # 启起点终点和区间长度
- q; n& P6 \# N0 l pOut[58]: ; [, U( U6 c- C
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]],
7 M5 H" q7 E5 S% L7 W# ?2 m closed='right',
( ~& G- v% p$ Q v" k dtype='interval[float64]')0 @0 C" H% a: }: K+ R, s2 o2 J
1/ |+ G: Q4 D4 N. ?0 E1 Z
2/ k, Q4 Q' R m
3
# Y" N6 s+ o7 i5 {, O- }3 d4
! \# U! N/ E8 U/ d/ y1 `5
3 l* J, }) s0 w7 [5 L) p65 M( [8 p7 @6 y
7
& H* x$ U. g+ V; Z( E! t" ^2 x/ j8
% F3 G0 K# w; I6 s; f5 n9
! e+ k8 m7 ^' P! L10' H6 ]6 d" e: T4 |8 a
11" X% k+ Z& q% J
【练一练】
A; N/ u; j2 R/ _6 b5 g 无论是interval_range还是下一章时间序列中的date_range都是给定了等差序列中四要素中的三个,从而确定整个序列。请回顾等差数列中的首项、末项、项数和公差的联系,写出interval_range中四个参数之间的恒等关系。$ A# b) ]" q( U1 v% [1 q
, S- f9 e) }- o) s, V" W/ f2 M
除此之外,如果直接使用pd.IntervalIndex([...], closed=...),把Interval类型的列表组成传入其中转为区间索引,那么所有的区间会被强制转为指定的closed类型,因为pd.IntervalIndex只允许存放同一种开闭区间的Interval对象。2 J$ r" M5 j0 q- l
+ D/ \( j+ d1 H$ s3 J; k
my_interval, ?: e& @% {$ f4 P- u3 i
Out[59]: Interval(0, 1, closed='right')9 r q& a; p D# R
5 `" Z$ N4 v9 T' b" a2 g
my_interval_25 m& P- v4 d. c' i- H2 |5 J9 G* F
Out[60]: Interval(0.5, 1.5, closed='left')
) U7 H- L# V' S, C& d4 P" R- ?1 f. S& b4 x
pd.IntervalIndex([my_interval, my_interval_2], closed='left')
( [3 K f8 k/ u: W$ j; jOut[61]:
" U) n5 Z7 k3 F4 O' G# G% kIntervalIndex([[0.0, 1.0), [0.5, 1.5)],! n+ s# j/ u1 q: b
closed='left',6 \& x% e8 ]( Q1 x1 J7 h
dtype='interval[float64]')
0 ]% g4 f' [' l, y5 z8 m: `$ S1- ^+ r# H4 A) S- ^. A
28 n$ ` W5 A9 p% Q' R
3- s8 a- u& q8 [2 A# j3 Y
4
# L. E3 N8 ]+ B8 O! l5. z/ h' b" D, D% M6 }8 N
6$ j3 k- N2 l. _+ c, E
7% w- l3 Y4 I4 `" N6 }. a* Q
8& z4 A0 z2 O" Z1 ^1 d
94 I. E1 ~3 r6 y& K n1 {# {
102 t6 g5 _. o% `2 \8 p
11
& w" Y& O( Z) E7 w; b+ T9.3.3 区间的属性与方法
4 A! K' {- F$ X2 k! _% | IntervalIndex上也定义了一些有用的属性和方法。同时,如果想要具体利用cut或者qcut的结果进行分析,那么需要先将其转为该种索引类型:
: K, G' |8 T" |1 w. P0 W( Y% L2 ]8 P2 R5 t
s=df.Weight. \; I1 l9 M3 H4 U3 O
id_interval = pd.IntervalIndex(pd.cut(s, 3)) # 返回的是每个元素所属区间,用具体数值(x,y]表示
p8 X( L: Y g, c! e4 x$ Xid_interval[:3]
1 g$ P) J1 i3 T& x7 `& r+ U0 t8 L' J- W0 I; ^
IntervalIndex([(33.945, 52.333], (52.333, 70.667], (70.667, 89.0]],- D' V4 A5 r9 w* j
closed='right',
! W+ w% y4 d4 R3 g name='Weight',8 n+ q2 T* `0 X& q
dtype='interval[float64]')5 C6 M$ @6 {$ \+ ]+ z/ `
1' {' a- |$ j8 Z5 ~# n" h- [
28 T! ~/ C4 U5 J
34 z& ]: ~$ N6 l% w2 {1 t
4
c- V# r3 y! F6 z* E" y4 e$ n# _5
& K- y; K3 r; a/ b) x6
+ ]+ ]$ @4 M9 y: e8 l6 K7
' g, R# F8 X9 G/ i1 ]8
( [: l6 v/ _! Z- E- B5 C与单个Interval类型相似,IntervalIndex有若干常用属性:left, right, mid, length,分别表示左右端点、两 点均值和区间长度。 f8 o9 v! a) e9 F; c
id_demo = id_interval[:5] # 选出前5个展示
' `$ o, J" C3 c% N7 v* ^- @; \, c" |. |
id_demo3 @5 O+ q) N: {" L5 `) H* i
Out[64]:
. K, V* ^' u2 T% c1 r% v( eIntervalIndex([(33.945, 52.333], (52.333, 70.667], (70.667, 89.0], (33.945, 52.333], (70.667, 89.0]],
; v5 O# d* L" I; Y3 J2 P( W$ S closed='right',$ H {7 }$ {/ w+ m# i
name='Weight',
' \) b: Y3 k, p9 d dtype='interval[float64]')/ t! M' [* l8 [" _- _! d9 e
( m7 @, _- f! Z5 j0 \7 R
id_demo.left # 获取这五个区间的左端点6 y d' e" [% B( T
Out[65]: Float64Index([33.945, 52.333, 70.667, 33.945, 70.667], dtype='float64')
' e0 ^4 S0 _/ s x0 P8 H% {/ y2 z& \1 j* N# T# O
id_demo.right # 获取这五个区间的右端点
* I3 V# b4 O) y$ u" ZOut[66]: Float64Index([52.333, 70.667, 89.0, 52.333, 89.0], dtype='float64'): H$ [1 T1 Y0 s3 g6 r/ J$ F
& o% K. |9 d; E |1 w/ a" O @7 L8 i
id_demo.mid4 Y" y" T# j, C4 p- X, B
Out[67]: Float64Index([43.138999999999996, 61.5, 79.8335, 43.138999999999996, 79.8335], dtype='float64')
6 W0 a* F$ O' t! F# L
+ F+ f. C$ k1 E0 a2 m- sid_demo.length
: [8 f* {. Q2 @3 _+ rOut[68]: ; @# k6 B) J1 `1 K. |
Float64Index([18.387999999999998, 18.334000000000003, 18.333,. Z: I) q0 |. |+ I+ I- U
18.387999999999998, 18.333],
" G' E/ @; t T dtype='float64'). C( f, l4 Q/ l- ^1 G2 } S O* }
' u5 K( h4 F" K: M1 W b. C' {
1- k3 O! f {0 Z/ O2 j
2+ E/ O0 `4 B5 L) `/ L
32 G+ D5 S0 w( A% k. R3 P( i
4
6 c* j4 b/ |6 a) f; t; n: c* E* v5
/ E2 R6 U" s3 {+ H7 v4 f6
/ o5 l* e3 |$ h6 r9 t/ W' N5 v$ g7
$ h) K) S9 E- @: R8
e$ a8 K* Y, ?9# z" I# P' R7 o' F- P% Y( o
10
# o2 O, v0 d4 Y9 X' ]: K116 g& _1 O! W/ _0 T# B
12
* x1 R/ i, T! C+ R! U: c" O2 M. I13
2 \. `7 k+ f, {- {14
* |4 G( B }& X$ x* n Y5 p' m15
& s5 h% X6 w1 p% A8 e16
! {" e8 M% }- l; G17
* ^% a1 M% ]! o2 b+ c, e188 v5 a, I7 }# Z
19
% }) S0 v" C# \2 q20
" x% a' C9 l# q6 J210 Z& a* x- l8 }! `9 N
225 e6 n7 p+ T9 z- [
23
" V8 S& c* q& ?8 j/ s; aIntervalIndex还有两个常用方法:
! Y9 F8 e( C6 g& H7 _" kcontains:逐个判断每个区间是否包含某元素
. S% @6 n8 L, n3 ?" g; y; Doverlaps:是否和一个pd.Interval对象有交集。
' l- d0 _* d) H! i! ~9 t" Xid_demo.contains(50)
& c* q0 |0 Q1 W# c2 J# V: GOut[69]: array([ True, False, False, True, False])
' \# |) K+ ?1 y$ Y& K* P! P T0 {3 g. X' k2 |
id_demo.overlaps(pd.Interval(40,60))0 n. z1 ~! u/ ?+ y- f7 m
Out[70]: array([ True, True, False, True, False])5 w+ o* r3 o/ z# d0 V3 {% @" x% O
1% \4 f; i1 L% ?4 ^
2- Y8 d! ?/ t" w5 n
3
4 S0 p% _! Z; r0 d: n; E& @9 |2 t) y- D: k4, O' O$ e( d9 P) `7 F( B0 }' `' V
55 w5 n: w- T3 @& k% b T' n/ g
9.4 练习8 e1 _, B) `' @8 R- K1 o
Ex1: 统计未出现的类别
- K5 a8 l1 Z% \) l) N 在第五章中介绍了crosstab函数,在默认参数下它能够对两个列的组合出现的频数进行统计汇总:
3 i4 T2 U7 t, F- A6 _' q
4 d4 V) E# P* R2 ?% tdf = pd.DataFrame({'A':['a','b','c','a'], 'B':['cat','cat','dog','cat']})5 B" b: e7 n( B0 G9 X
pd.crosstab(df.A, df.B)2 e/ s- u! \- b! c& t
) `: r6 `$ c: H: C0 m: [
Out[72]: + z- z6 C6 }9 X1 O h
B cat dog% ^2 x: k2 P( h$ v' F+ [* K# H0 {
A 3 s/ Y( `) j" H( D. E
a 2 05 B* e( V( N: J$ r- H
b 1 0- o3 i( w8 ]& K6 i
c 0 1
1 e+ G* y# a% A$ f! G1# \: R+ G! q0 Q& E( f! I: D; j
2
; |( B2 R4 u1 Y" {8 j' T9 W7 Q+ F3
2 T1 p7 n6 S; }' t4 C( y4
; K: Q: z: [0 M7 f5 k* M# Y5
: v9 Y7 P$ c0 B6
3 d, Z5 @) `8 r' T8 v/ D7
! ?5 N7 O8 ^5 A5 A8 D$ h# Z1 p$ X- K6 s% f
9, M. t3 x1 C0 w7 r
但事实上有些列存储的是分类变量,列中并不一定包含所有的类别,此时如果想要对这些未出现的类别在crosstab结果中也进行汇总,则可以指定dropna参数为False:5 G8 T; T3 F- y" S
' U I$ u, m. D
df.B = df.B.astype('category').cat.add_categories('sheep'): b& N/ O, L$ ~* B1 O/ m
pd.crosstab(df.A, df.B, dropna=False)
- y6 R0 Q6 L1 u7 L8 K7 h% n/ y$ z" D0 F; J4 C+ H
Out[74]:
( c& y9 |7 T6 {: _- C/ [B cat dog sheep3 g; h* i. A1 ]3 p( H- Y; e
A
5 ]2 b! Y, w- J! c) Fa 2 0 0
7 K8 [6 _2 g1 Sb 1 0 0
/ m1 |; U- O1 f: }% T6 Jc 0 1 0
% M& y" f) a6 b0 [/ l6 E5 V1& |. [; V1 x8 N
2" f. L, f5 B2 Z
3
; I( U" W" s, z- ~4
+ ` C, J0 U" I9 y: D5
9 o4 z: m& _: l$ h% E6
$ T4 Y: W, S* j5 P) ~: R# p7 ]7
& q) h! u3 r) u. l8 l( m8& u. Z# j B& |1 |
91 D4 G8 }# u1 R# e; z' p
请实现一个带有dropna参数的my_crosstab函数来完成上面的功能。
% @, e; y, s. p6 b) D! ~. V
4 L# o6 j8 I( T2 ~' vEx2: 钻石数据集
6 G" g1 g: c; j2 F 现有一份关于钻石的数据集,其中carat, cut, clarity, price分别表示克拉重量、切割质量、纯净度和价格,样例如下:
! x+ q, t* f# L! Q$ O0 H& N) [ ]4 u# W
df = pd.read_csv('../data/diamonds.csv') + Q& v: e& r) R8 T+ b+ x- C
df.head(3) n+ _& y0 }% t% i
! E6 A! i5 Z# C% B c
Out[76]:
) Y5 ?! D, m4 [3 {5 c carat cut clarity price
# Y& U* J( m8 g8 ? I0 0.23 Ideal SI2 326& h! z6 m5 U4 |5 q5 C. ~
1 0.21 Premium SI1 3269 G) P/ v9 i/ t+ [9 n+ V
2 0.23 Good VS1 327
; P$ o2 u; @6 U$ {- f4 [10 D9 _ s3 \9 x
2. j7 h/ O6 E1 g3 o
3
( T+ t0 b5 ?9 ]3 f# ?; _/ c R4# r* D0 J( p4 g2 m
5' c) Y- u& f4 }; Z* _7 ?" w/ E
6
0 a0 n5 Z: ^$ J# y5 R71 v- z% Y% [8 y! x3 I) [5 E
8" w' W$ E) r# `4 U Y: s5 y! T
分别对df.cut在object类型和category类型下使用nunique函数,并比较它们的性能。4 a( l; N+ L# | Z
钻石的切割质量可以分为五个等级,由次到好分别是Fair, Good, Very Good, Premium, Ideal,纯净度有八个等级,由次到好分别是I1, SI2, SI1, VS2, VS1, VVS2, VVS1, IF,请对切割质量按照由好到次的顺序排序,相同切割质量的钻石,按照纯净度进行由次到好的排序。
; T1 E5 Z9 Z; g+ [0 Y, U8 Q. M- j+ R分别采用两种不同的方法,把cut, clarity这两列按照由好到次的顺序,映射到从0到n-1的整数,其中n表示类别的个数。
~7 ] @+ A: O) M* `对每克拉的价格分别按照分位数(q=[0.2, 0.4, 0.6, 0.8])与[1000, 3500, 5500, 18000]割点进行分箱得到五个类别Very Low, Low, Mid, High, Very High,并把按这两种分箱方法得到的category序列依次添加到原表中。; y+ s" y: g. X7 E
第4问中按照整数分箱得到的序列中,是否出现了所有的类别?如果存在没有出现的类别请把该类别删除。
4 l+ V# s" {/ z& z2 U8 K' L对第4问中按照分位数分箱得到的序列,求每个样本对应所在区间的左右端点值和长度。
' {; D9 ], G0 i( q& {! @先看看数据结构:2 P! b$ q: D; J4 a& }: ? O: w$ w1 u
7 U3 R% x* H5 E" \% X/ g { Tdf.info()
/ }7 S$ ~5 o$ Y6 f) n. ]! z% u( ~* [Data columns (total 4 columns):. }) T! b' |1 `8 z. s- k4 A& \+ ~% R
# Column Non-Null Count Dtype
5 a( X- x' H; \+ u# \; {--- ------ -------------- ----- ; W9 k# C3 b" |* \2 \9 F
0 carat 53940 non-null float64% x( T1 C! O) Z: O u. H
1 cut 53940 non-null object , w- \9 z. E+ H
2 clarity 53940 non-null object ! a+ K) u& E0 Q6 A/ d; O
3 price 53940 non-null int64
" U7 j1 Q9 V+ M% g$ p0 l! @dtypes: float64(1), int64(1), object(2)6 s7 T4 D0 \: Y$ ^: R) s) C
1! h$ H( V/ ]; h! [9 R, _9 H6 P0 s
24 C" r* v( t: `
34 F4 L2 ]3 \; N f
4
7 ~ E0 j& K. F- E8 r5% `. i0 K7 r" z4 C" z1 y5 g
6
; N. `4 w4 B q, h) S78 p* j1 O2 a5 H+ {6 H0 |& G- g
8
- y+ N( b* r {# g0 Q( Y8 ^; W9
/ {2 l: _6 ^6 S( v& w y比较两种操作的性能" V. D5 V. w3 B; `8 x9 i
%time df.cut.unique()1 q8 v( Z7 Y$ S7 e4 @7 b
. I; s9 G4 p( m
Wall time: 5.98 ms
- F# `, L( Z; zarray(['Ideal', 'Premium', 'Good', 'Very Good', 'Fair'], dtype=object)1 k z/ D# }. A1 X5 x
1
' p. p# }% V5 L) o) e2
. d% o" c2 b% ^" i! Z3
- C I' }% r y( s4
8 w; o6 k5 z* y) N3 L" ?; t%time df.cut.astype('category').unique()
& K+ a7 ~4 A/ I0 ~3 K& S ^! ] k, f' }& C9 Q6 T# c
Wall time: 8.01 ms # 转换类型加统计类别,一共8ms
- H; k' X! p* t! R['Ideal', 'Premium', 'Good', 'Very Good', 'Fair']& R% s0 [2 h0 n. X% p# H7 R
Categories (5, object): ['Ideal', 'Premium', 'Good', 'Very Good', 'Fair']( H3 e) F5 N4 T! b% b. p6 o
1
1 Y. s& O e6 W5 h" v2
2 R0 ?% l7 ]+ x7 b; Y# U3% F% `) g8 a$ _; n( I
4
1 r, ]5 c' e+ X( e, L/ Z0 w7 _5
+ O6 Y9 A1 h* R$ Mdf.cut=df.cut.astype('category')
3 j5 }; v) Y8 u- r- t# a1 p%time df.cut.unique() # 类别属性统计,2ms4 ]. j1 J$ G# u) C
* A2 s$ @* s/ } Q8 q% {% bWall time: 2 ms" k! n! I8 J0 Y3 m! {9 V
['Ideal', 'Premium', 'Good', 'Very Good', 'Fair']
& L7 {2 N$ t0 r3 C) jCategories (5, object): ['Ideal', 'Premium', 'Good', 'Very Good', 'Fair']
& {; H u1 ?$ K0 Y1
' {7 J' F& H$ t+ R% d" O2
! ?) h4 f; Z6 X" D! P3
J3 O- [9 ?8 h8 X4* `: m2 D: P# C8 b; I
52 b. P& ^5 g7 _ [7 @9 b
68 l4 v0 i5 @7 i* K N# I7 b
对切割质量按照由好到次的顺序排序,相同切割质量的钻石,按照纯净度进行由次到好的排序。
. p6 T' e* o+ Q' f4 X( bls_cut=['Fair', 'Good', 'Very Good', 'Premium', 'Ideal']" @1 B/ k" p0 P/ q
ls_clarity=['I1','SI2', 'SI1', 'VS2', 'VS1', 'VVS2', 'VVS1', 'IF']( S" a7 t- J' H$ v7 C( \
df.cut=df.cut.astype('category').cat.reorder_categories(ls_cut,ordered=True) # 转换后还是得进行替换
2 O5 x! j' `1 y" l6 U* h9 p( Q* cdf.clarity=df.clarity.astype('category').cat.reorder_categories(ls_clarity,ordered=True)6 e7 R/ J, H% K) a$ y1 G1 h0 E
2 J' ` g+ B0 [df.sort_values(['cut','clarity'],ascending=[False,True]).head(3)
/ b" T* b6 _( w3 H/ v- g! k9 g5 j5 k0 |' f4 x% f0 g' T9 v9 e
carat cut clarity price
7 O- V$ R2 K- R1 j3 X6 _315 0.96 Ideal I1 2801
s& _$ J. B1 l; h8 `$ j535 0.96 Ideal I1 28264 }% D/ V d* f
551 0.97 Ideal I1 2830
/ {$ Q% \& b8 O5 S; c" I7 v1+ m4 T" H+ |- K+ u$ h5 q
2; y( J/ P3 m- m0 J( b) [
3
Z" h% @; ?- D6 X* ^ T4
9 T% U' J. e+ O! {, b& |% G5 L8 |5
7 _* {' ]+ N" h( E% U2 }2 m" l6 ~' b6- n$ Z6 F; T: {
7
. |2 e: H7 @0 P2 K& ^8
* t" H9 z, _6 f3 w3 n6 E0 j9
2 Z; v" C/ ^& }5 c8 |" ~10
5 S% p/ m6 u) I& l% G' _% ~11
8 i/ }! N+ B4 b+ _ H' U7 _6 q; [分别采用两种不同的方法,把 cut, clarity 这两列按照 由好到次 的顺序,映射到从0到n-1的整数,其中n表示类别的个数。0 E& j9 P; R. D m" o9 }! R/ L
# 第一种是将类别重命名为整数
9 s* h- s% p! i8 _3 {% zdict1=dict(zip(ls_cut,[x for x in range (4,-1,-1)]))
) W! P9 p: ^" v$ l L, ]8 |9 jdict2=dict(zip(ls_clarity,[x for x in range (7,-1,-1)]))
4 v7 I: N* B" O9 U+ n7 u+ `/ E" D1 O `% o
df.cut=df.cut.cat.rename_categories(dict1)
3 D$ e" } ~- t# G1 ~- tdf.clarity=df.clarity.cat.rename_categories(dict2)) R7 h4 W% u% r9 @8 ?
df.head(3)
% ]& E1 p5 U2 {- q9 d1 A
) B0 o. O" T0 C" e' H, ?* P carat cut clarity price$ ^5 T+ T* k5 R: [$ _0 {
0 0.23 0 6 326
9 M: t3 ~1 m+ w3 k1 0.21 1 5 326
7 s# P7 t( [ G8 [, ~- X2 0.23 3 3 327
5 P4 e7 W- u3 x" H1
& W8 t. l, ?- |" S& E1 x/ F6 J2
4 v! @' H1 Z# ]% H l0 x- @3
9 B0 p/ e3 ^; l: C: M4
9 V& y1 L9 } u3 c5
2 D( n/ x+ r2 r! g( F6. [( O4 \% C( D) b2 ~* ?
7
0 Y2 m9 R8 [! a/ C8
- T, L4 W$ ^; { g90 I4 J0 F& Z/ v* q
10
5 Y. j2 y% |: Q1 a( e/ N115 \( e+ b5 B- O' _3 N
12
" ?: ]+ I8 X1 }' R& \4 E# j4 Y# 第二种应该是报错object属性,然后直接进行替换
: c% m7 R8 }9 m5 d! k; n4 ldf = pd.read_csv('data/diamonds.csv')
; w. Z( ^* L# h5 [4 ?. l' |for i,j in enumerate(ls_cut[::-1]):; X" g8 X, C2 O' w
df.loc[df.cut==j,'cut']=i $ \- i; A) {4 r% k' T, r F( b, |
. q# Z( Q' h' ]* R* s1 y% e# {' Ufor k,l in enumerate(ls_clarity[::-1]):
. `7 O* J+ q8 j2 P9 w6 l) Y df.loc[df.clarity==l,'clarity']=k9 `5 h; D' J P. \4 A" p8 u/ {- v
df.head(3)
8 K+ ~6 S9 P: Y' N6 B5 T) V( m' Z) N. o
carat cut clarity price0 c% Q& C4 B; `+ I1 h+ Q& a
0 0.23 0 6 326
8 s6 z2 }" c6 t- D( |& D k1 0.21 1 5 326
6 R+ j2 E- P9 S/ A2 0.23 3 3 327
* L7 N* Y* {1 z1 z* W6 q/ A, [16 N0 y, v5 u3 I4 D5 e8 y$ t
2
9 z0 Z7 u; Y7 ?5 g+ }3# ?) }: L! ^6 U: D+ f) w* ]- u/ g
4
1 x2 j$ A1 T, b4 ]5 d5. f* |- _7 f5 t3 C
6
: M( _2 N) p# ~6 M( Z8 H7
; {; }# W# z" Z3 T i9 |8
- k" X; h/ b8 T# u93 Z3 H( g/ V$ g) r! Y
10' | `7 O, [: W& u/ n; K7 C: |7 S1 v
11
! h! G. Z" b" P) g, J% {12
3 v5 I+ U$ N+ ^5 [$ Q6 |, C$ ~136 B% b( p! \2 m6 a* D2 V5 `
对每克拉的价格分别按照分位数(q=[0.2, 0.4, 0.6, 0.8])与[1000, 3500, 5500, 18000]割点进行分箱得到五个类别Very Low, Low, Mid, High, Very High,并把按这两种分箱方法得到的category序列依次添加到原表中。' m2 A; n, x! Q1 A$ I, k
# retbins=True返回的是元组,第一个才是要的序列,第二个元素是分割点
$ n0 Q- Q3 h% s4 ]/ ^/ s3 B& iavg=df.price/df.carat
9 r; \9 [, [" b6 q* [0 l
! f$ ?* C4 F4 ldf['price_quantile']=pd.qcut(avg, q=[0,0.2,0.4,0.6,0.8,1],
) v' L, P# f7 h# p) U6 i& s labels=['Very Low', 'Low', 'Mid', 'High', 'Very High'],retbins=True)[0]4 S$ j8 M$ ` X
: R2 B$ R) h: [. E4 {5 w
df['price_list']=pd.cut(avg, bins=[-np.infty,1000, 3500, 5500, 18000,np.infty]," m0 O8 w0 o5 e6 B# \! p
labels=['Very Low', 'Low', 'Mid', 'High', 'Very High'],retbins=True)[0]9 I1 y" _5 [) N/ S2 N
df.head(): A4 t3 Z1 n: t- N! {
+ G6 m1 N7 Q( m" E& D
carat cut clarity price price_quantile price_list; m+ R1 a( F$ n4 j
0 0.23 0 6 326 Very Low Low0 S: l- d: ?: c, O
1 0.21 1 5 326 Very Low Low4 k- y' L" N1 E7 L0 p- j$ M
2 0.23 3 3 327 Very Low Low
3 G4 w, ~5 O9 ]3 0.29 1 4 334 Very Low Low
/ i& Z& Q* I# c) \/ {6 j1 i6 y) f! @4 0.31 3 6 335 Very Low Low
2 E* t7 ]# K3 j" M' M9 I5 W2 L: o' z3 b+ U
1' _; h$ w/ r& z9 j% X
2; a* o% w" R" _" n. V( p' d; W
3
6 Y+ o: `( z4 G* y4* K, |' `* i3 |
5( P3 N; C, z( r4 N) v" N
6' z5 ?/ B" O) A, L! v
73 t! l7 h1 f: x$ o- m; j
8% `" y. z+ |$ T% W- ^ i+ n
9
/ U8 N9 z6 i$ N- k101 Q) S6 I, p0 ]6 r [! O$ G
112 _" \0 b5 W6 i% V& ~
12
A5 [. _' N$ B# p ]13
8 G B: }% j' n! b8 ]" y141 B6 h( Z. |8 _
15
, U4 h4 m0 \2 l9 T6 a16/ f" `3 c F7 _- {7 M
分割点分别是:
, c; A8 E2 r* ?* a! a
, i# v* x1 @% Y/ }9 Uarray([ 1051.16 , 2295. , 3073.29, 4031.68, 5456.34, 17828.84])
) _) C0 I5 T' P4 b" t0 Z1 f5 Harray([ -inf, 1000., 3500., 5500., 18000., inf])
, y1 ^/ `4 m4 C; J' L: `# b R17 S& m# t- Z- a! H
2
l( e- v# G7 z2 I7 y第4问中按照整数分箱得到的序列中,是否出现了所有的类别?如果存在没有出现的类别请把该类别删除。9 H: z& f0 {* h1 X k
df['price_list'].cat.categories # 原先设定的类别数 K0 \+ T5 I, d/ [3 T2 F
Index(['Very Low', 'Low', 'Mid', 'High', 'Very High'], dtype='object')
8 M, n n: g: w2 G! y9 `
3 q a5 i. p, d# qdf['price_list'].cat.remove_unused_categories().cat.categories # 移除未出现的类别! ^/ o+ H& Z5 {; E
Index(['Low', 'Mid', 'High'], dtype='object') # 首尾两个类别未出现
# z- G! N; M0 U1
/ K. Q. v$ a, w) `$ M+ ]5 B" ^21 ^- X7 y) T3 Q+ F8 R
3
/ y% F) R" N8 V6 P( ^8 D. P; m/ G4
! J, k+ s8 w/ o; |$ j5
" ~! S8 g) p& R- l+ Uavg.sort_values() # 可见首尾区间确实是没有的3 h9 H, g9 C( w: \% n
31962 1051.162791
: H$ G D7 r/ z" a15 1078.1250009 L- g1 n/ m3 g! n! n% {# s
4 1080.645161$ x1 ]2 r% _/ b" O
28285 1109.0909098 P& I _5 n/ T% ?5 w; h
13 1109.677419; z6 D* H" j6 J1 r; |+ g {$ _% D
... * H& t9 g5 j; X h3 W
26998 16764.705882# L9 \# P8 l+ {" a: j. _( W
27457 16928.971963
9 ~7 \1 M5 \+ {, T27226 17077.669903; u3 Y4 v- g w& j0 H
27530 17083.177570( q( [$ H* w0 D9 E" Z
27635 17828.846154" S/ o( U" c+ f+ o4 |, m- W! P
1) `# h, h8 Y- O s( \. {* c
2" N% b o4 T$ u
38 {& ]# e6 m8 q
4& _ \* C1 h5 N
57 m' o" q' c W1 [
6
/ n M O8 l3 c: J2 f$ p7$ H& {3 u- }: b4 l
8
1 z7 g7 Q6 M: g% m# l# Z( S9
3 Q9 r2 O# r2 N- r10
) K0 Y' h. Y- s w* y% T D- x2 P2 q11% w6 N- J5 C2 v0 _9 s
12
7 L# V6 d& h4 D, Q0 h6 g对第4问中按照分位数分箱得到的序列,求每个样本对应所在区间的左右端点值和长度。
3 J+ K# A/ E, r, f; ~# 分割时区间不能有命名,否则字符串传入错误。
0 n) G6 h8 i% g. qid_interval=pd.IntervalIndex(7 r4 j, H. l% H; M+ @
pd.qcut(avg, q=[0,0.2,0.4,0.6,0.8,1],retbins=True)[0]& q D% l$ o. ~' [
)
3 r9 w$ A& K& ]# g, u9 Sid_interval.left
- z8 Z% | I: w U& t) y9 |! Nid_interval.right
0 @+ i: V4 x9 n% W$ Pid_interval.length 1 [/ j, R k7 k4 I$ ^
1- T# J* u: j2 R! W( m0 o
2, j# b# N3 U3 O6 a
3: W9 G6 i( l: O; N0 K$ i
4; p4 F. Z. A; ~$ X4 a! [- {
5
5 x1 d+ C+ l# D) f6
( u1 t& E+ y4 T& b1 K# I+ {77 B4 N7 b. C' n/ I; }
第十章 时序数据. y* Z3 w4 Q8 n
import numpy as np8 k. h# u Q. Z9 P
import pandas as pd! W3 a+ a3 S/ P0 n
12 e# e0 n- m2 L$ c/ I
2/ `' N k$ S. W5 V7 V( ~
) k& o" T1 ~: ?
& P9 f# a3 Q! a' S' a10.1 时序中的基本对象- q6 y! ~1 ?3 n
时间序列的概念在日常生活中十分常见,但对于一个具体的时序事件而言,可以从多个时间对象的角度来描述。例如2020年9月7日周一早上8点整需要到教室上课,这个课会在当天早上10点结束,其中包含了哪些时间概念?! r2 d/ U8 G# j* l1 Y& W
* w% S6 [& z0 a5 h
会出现时间戳(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的简写。
$ P# a k C( G, Q# r+ G
# t8 u, i Z$ C# c/ d会出现时间差(Time deltas)的概念,即上课需要的时间,两个Timestamp做差就得到了时间差,pandas中利用Timedelta来表示。类似的,一系列的时间差就组成了TimedeltaIndex, 而将它放到Series中后,Series的类型就变为了timedelta64[ns]。
! B9 q) M. _' D, c9 `% w& ]) G; U, n
* H( p& k6 a9 J5 T4 u7 k会出现时间段(Time spans)的概念,即在8点到10点这个区间都会持续地在上课,在pandas利用Period来表示。类似的,一系列的时间段就组成了PeriodIndex, 而将它放到Series中后,Series的类型就变为了Period。/ Y4 ?. S% R! s5 {2 g, g) G9 r9 a
s q+ d0 v B, o; _& K) ^
会出现日期偏置(Date offsets)的概念,假设你只知道9月的第一个周一早上8点要去上课,但不知道具体的日期,那么就需要一个类型来处理此类需求。再例如,想要知道2020年9月7日后的第30个工作日是哪一天,那么时间差就解决不了你的问题,从而pandas中的DateOffset就出现了。同时,pandas中没有为一列时间偏置专门设计存储类型,理由也很简单,因为需求比较奇怪,一般来说我们只需要对一批时间特征做一个统一的特殊日期偏置。
. ?: @3 d' h! F8 b# ~
% u; y( Y0 m, [: y3 \ 通过这个简单的例子,就能够容易地总结出官方文档中的这个表格:
4 {! q( `5 @- j. K6 j& W" p3 R$ x$ J
概念 单元素类型 数组类型 pandas数据类型/ t) m6 c' o5 X+ ?
Date times Timestamp DatetimeIndex datetime64[ns]- h; ^; K) Q/ w/ C+ `: f! B! ^
Time deltas Timedelta TimedeltaIndex timedelta64[ns]
K! C0 d. x# g! LTime spans Period PeriodIndex period[freq]
$ l3 q/ v; q1 i; V! y! YDate offsets DateOffset None None0 ~& C+ O' @6 U5 M
由于时间段对象Period/PeriodIndex的使用频率并不高,因此将不进行讲解,而只涉及时间戳序列、时间差序列和日期偏置的相关内容。
" H! z5 V2 Q7 B0 y9 s1 d! L( Q: j' f# k5 N; |
10.2 时间戳
1 y) K% l8 E/ r$ j f z10.2.1 Timestamp的构造与属性1 ^3 D5 N; l# p {
单个时间戳的生成利用pd.Timestamp实现,一般而言的常见日期格式都能被成功地转换:. m Z r" y' e2 m
+ s9 u+ ~3 y& x: w! p* P
ts = pd.Timestamp('2020/1/1')0 k: F) i; a" K# X& F
7 S( m# F4 L7 x" K
ts
2 N2 |. t( a% y U- W& oOut[4]: Timestamp('2020-01-01 00:00:00')6 S% J/ L/ @! R
' L0 I6 b* X \1 Wts = pd.Timestamp('2020-1-1 08:10:30')
! T+ B) O2 N# _- W
1 k6 I! ^# v0 V- Yts- i, r m) L- x C+ \, _
Out[6]: Timestamp('2020-01-01 08:10:30')
; p" w5 d5 y! F% A6 E1
/ O4 L3 {5 D+ |. U2
% X+ c- N8 W, y4 C7 T0 _34 ]1 n# Z) B1 A+ v$ M$ ~; S, B
4
. y- e: G+ y* g, z# D7 j5* Z+ I# l$ \( a: `( u
64 B' l7 D* a F- a; k& ~
7
x- V6 ?" @& b8, G" z9 G, P" ?, d6 J7 r9 D* u$ e
92 D0 y; m9 o: g, P6 V
通过year, month, day, hour, min, second可以获取具体的数值:- Z3 e3 @) \* J/ D
3 x2 v) X' f) A( ?- k4 ~ts.year
+ L2 x9 d9 O' @* [Out[7]: 2020
9 E4 U$ ~; V, l/ Y6 @# c0 F3 |9 B( x% O! M5 V
ts.month) o( p5 x6 y# l- V" N. Y+ k9 d, M
Out[8]: 1
0 ~$ y/ u1 ~9 j$ {0 T8 t) l# W5 z! \0 t W2 k7 x1 F* d2 I/ L
ts.day
3 [, O }' q g; t& i. u; z1 ZOut[9]: 1 Z: p/ i. c. ~
. f- l/ z9 _1 c6 W
ts.hour
$ ` T$ M4 b/ |Out[10]: 82 n/ L n/ }* ]
. h- t/ F. {; e6 I' K( ?% z
ts.minute
! A4 }& I" y2 K$ k2 \5 IOut[11]: 10, U& `! C% v: L/ p1 [
" @8 p' l- N4 u; m
ts.second" Q6 c) F/ u$ N5 }2 h7 j1 {8 W
Out[12]: 30# E5 G4 N0 a# @
3 l, e$ e; ?' q3 O' l12 `( `, N/ S* h4 g" ^
2
8 o4 m; H* G7 l32 x# E9 X7 e8 f
4
- g9 }* Q, i6 s3 W- K5: }, u% b! P' n* B X
6
" ]( v! Q1 \* R' ^7
5 l) q# \! o9 n8# R5 A' _3 f- p9 t: w
96 `/ ]# ^/ Y$ z9 R& D
10( y+ Z1 g9 E1 h, m
11
" e6 G3 B9 K4 c# K- e12" D$ N( x7 P+ v c8 T$ w! ] B
13- j) Z! X3 S# E, I h3 e
14
3 r6 }5 t& N: [- K15
1 _# G5 h: M+ Z9 O! }: O16
$ }# M O' z, X5 i! F17! x5 B8 M6 R ]+ C p
# 获取当前时间
$ u# I4 j! A' M" ]now=pd.Timestamp.now()) A/ e9 b z7 @$ @ D; u" W, U! S
1* ~2 d" i" i X; v6 m4 m" N4 U/ T: K6 Q
2
+ _0 Z+ r6 D. {: s+ X7 s$ i在pandas中,时间戳的最小精度为纳秒ns,由于使用了64位存储,可以表示的时间范围大约可以如下计算:
0 v8 \: Y) Q$ j1 \T i m e R a n g e = 2 64 1 0 9 × 60 × 60 × 24 × 365 ≈ 585 ( Y e a r s ) \rm Time\,Range = \frac{2^{64}}{10^9\times 60\times 60\times 24\times 365} \approx 585 (Years)
+ ^ p8 X* L7 ]8 Q9 o& vTimeRange=
- l6 h8 M) {0 E# Y! L! R10
% l; P- T5 e$ X8 H9
4 p8 e0 O! p- a ×60×60×24×365
6 s9 s/ ^8 f# W }- [2
d0 E2 w0 w- ~$ o64
A2 H. i8 } e' ?4 F5 x+ m/ I, \% m, m7 M1 q+ F7 d( U; \
1 G7 q) ~8 n% M R- q) w& ~
≈585(Years)6 f2 c0 E/ l: v2 l
: Y/ Z3 f/ F+ X& ]: t! P
通过pd.Timestamp.max和pd.Timestamp.min可以获取时间戳表示的范围,可以看到确实表示的区间年数大小正如上述计算结果:
% @- r* m, R7 a; w3 r" `
* z& T# j6 z- S' Q6 T/ Mpd.Timestamp.max, e0 ]* |' N& x% u+ a0 z
Out[13]: Timestamp('2262-04-11 23:47:16.854775807') n: l9 y# @# n0 z( e5 ?$ k, Y: Z
$ D r& W, v1 [, K& ?6 n
pd.Timestamp.min
1 W- L' U0 {1 D% r O# [Out[14]: Timestamp('1677-09-21 00:12:43.145225')
8 z5 G2 S+ X4 M9 Z
$ y7 ~1 |6 i+ z/ d5 Q( d7 E2 Kpd.Timestamp.max.year - pd.Timestamp.min.year+ Z$ @0 \; W4 ?" C" G1 v) c& {
Out[15]: 585
J: O) \0 Z) k, W$ T5 V5 D1
- ]$ [( C q% Q! u- k27 B! A/ |* @( ?, `7 x. h6 C( L( ~
38 G7 m. B2 `) {
4
- A' H9 @( Q* v. l/ Y( o53 K) Z6 Z% ~/ p# e2 F9 `
6& _* C, k! f0 h7 L
7( u9 k/ J3 a) b) l" T
8
; n; V9 k9 t( z* j8 {$ ?) V9 ^10.2.2 Datetime序列的生成" H" U6 ?7 T) o0 L+ ~
pandas.to_datetime(arg, errors='raise', dayfirst=False, yearfirst=False, utc=None, format=None,
; M; a1 |9 K. { exact=True, unit=None, infer_datetime_format=False, origin='unix', cache=True)4 f5 Y- D. d* B, Y
1
t1 A- P/ b0 z6 a) B/ {5 `2
/ v+ J; i4 p( W) S! @9 p% U0 F5 Y* ^pandas.to_datetime将arg转换为日期时间。
% ~3 ^! T. ?) N2 Y
; G4 j' ^" I# Zarg:可以是argint、float、str、datetime、list、tuple、一维数组、Series、DataFrame/dict-like等要转换为日期时间的对象。如果提供了 DataFrame,则该方法至少需要以下列:“年”、“月”、“日”。 H5 b( ]& o* M' e9 b
errors:
; U# S% G) z* d/ f- ‘raise’:默认值,无效解析将引发异常
: E1 `& {/ Z4 p% A1 u1 l; R6 K2 X- ‘raise’:无效解析将返回输入 v9 [+ |4 l! b) k; G
- ‘coerce’:无效解析将被设置为NaT
) Q5 @& L8 n& }9 A. pdayfirst:如果 arg 是 str 或类似列表时使用,表示日期解析顺序,默认为 False。如果为 True,则首先解析日期,例如“10/11/12”被解析为 2012-11-10。如果无法根据给定的 dayfirst 选项解析分隔日期字符串,会显示警告。: c% x' T0 d; p6 v% M5 Y! G. {4 p
yearfirst:如果 arg 是 str 或类似列表时使用,表示日期解析顺序,默认为 False。如果为 True,则首先解析年份,例如“10/11/12”被解析为2010-11-12。无法正确解析时会显示警告。(如果 dayfirst 和 yearfirst 都为 True,则 yearfirst 优先(与 dateutil 相同)。)
4 G9 K1 z2 |1 O' Cutcbool:默认None,控制时区相关的解析、本地化和转换。请参阅:pandas 有关时区转换和本地化的一般文档
Z! |5 F7 `; yformat:str格式,默认None。时间戳的格式不满足转换时,可以强制使用format进行匹配。
0 `$ U' d2 b% e4 W# {9 B+ Ounitstr:默认“ns”。它是arg (D,s,ms,us,ns) 的表示单位,可以是整数或浮点数。这将基于原点。例如,使用 unit=‘ms’ 和 origin=‘unix’ (默认值),这将计算到 unix 开始的毫秒数。
, {5 ~" l8 F5 l O( ~3 i* zto_datetime能够把一列时间戳格式的对象转换成为datetime64[ns]类型的时间序列:/ n' v" V+ X7 w- m% Y
pd.to_datetime(['2020-1-1', '2020-1-3', '2020-1-6'])
. s6 J9 R8 w: t9 d( z o/ D( m& Y9 V* L6 S. h1 z
DatetimeIndex(['2020-01-01', '2020-01-03', '2020-01-06'], dtype='datetime64[ns]', freq=None)
& h U3 Y8 T1 K17 E, B/ P2 x! u2 x' J' J
2
% }! y: w n1 H/ y% g3, L" y7 H2 n) e- r& N2 y
在极少数情况,时间戳的格式不满足转换时,可以强制使用format进行匹配:
$ R+ z5 Z& _4 h+ i( y! ~5 s" x/ g4 m* o
temp = pd.to_datetime(['2020\\1\\1','2020\\1\\3'],format='%Y\\%m\\%d')
$ `1 Y \$ A+ ]% ?temp7 o) L ~3 D" m) z$ ]' M3 Z, ^
' j, }4 f; n; ]
DatetimeIndex(['2020-01-01', '2020-01-03'], dtype='datetime64[ns]', freq=None)& t( ?- S; z0 P3 S
1
) y3 z! ?7 j: I4 w29 S$ B) o9 I9 ~ ~
3
, w( p" ]: m: v4
* r4 L" b( v3 E% J W 注意上面由于传入的是列表,而非pandas内部的Series,因此返回的是DatetimeIndex,如果想要转为datetime64[ns]的序列,需要显式用Series转化:
1 O3 X1 ^. ?6 i5 \. ~1 d& x/ g
9 b! j5 V+ I0 Tpd.Series(temp).head()
B, q% m9 S6 j( I1 G7 I4 c, D/ _9 M S% n) W- a5 {0 k" B( `
0 2020-01-01- N3 r# F0 Y- Z% `$ _
1 2020-01-03; m4 a; k9 @6 D$ P4 T: P
dtype: datetime64[ns]
2 o! h6 }1 l2 U. F |1
3 E, [/ y3 N! G* d5 n8 `9 g2
! U, [* G: d) t# I+ T/ y+ v3
! j8 Q: E3 P# A U, J1 g) y42 X0 C S( I( f8 w% C! a2 o0 ?
5 b, Q) W! b6 v( P
下面的序列本身就是Series,所以不需要再转化。: U" O5 Z' L/ Z5 w, u& s
; _. s' h# @1 u1 A- a( t+ ~! A
df = pd.read_csv('../data/learn_pandas.csv')9 o; f* a! _; H
s = pd.to_datetime(df.Test_Date)
( a: V& m5 X }/ ys.head()
! B, W8 P" H4 t- ]* p, }
: W0 k4 R& q3 F7 N9 x% `+ q0 2019-10-05
4 K& }6 T T3 d, Y1 2019-09-04
6 D3 C6 `4 X+ t$ m5 t3 P8 W2 2019-09-12
6 v% N# |6 u, N$ K- r1 v3 2020-01-03. u6 I8 n0 R' I9 r- l5 h
4 2019-11-06
+ w) O% e5 }2 V& jName: Test_Date, dtype: datetime64[ns]
1 h+ m1 E4 E: @5 O, k# V1
o" j" m5 X2 f) ?29 B! Y/ o K: e$ u4 x0 |
3" F0 S! e1 H& J" n4 k9 J- ^8 ?* k! g) l
4
+ S( d6 n0 t* o1 _1 i6 {- V55 q# A4 `1 L/ {; M9 E
6
4 k: V/ ]2 }/ p9 r7# d$ m" S- @7 h! W% ?8 q3 \
8
- S5 I7 z# H7 k2 j0 }; p9& S, ]9 Y |" N0 b1 k5 [" q* R
101 k0 t/ Y: t8 k5 X/ r8 \1 a5 G
把表的多列时间属性拼接转为时间序列的to_datetime,此时的列名必须和以下给定的时间关键词列名一致:
$ D& q( C9 n* A1 Kdf_date_cols = pd.DataFrame({'year': [2020, 2020],' m) Z; l% B! c5 s
'month': [1, 1], N" Q) i8 g/ X; e1 I' y
'day': [1, 2],: [2 @2 o* c4 J: F
'hour': [10, 20],
; e7 x& o+ f) z. F 'minute': [30, 50],/ | h! n% b. m5 x
'second': [20, 40]})+ V# `# I) d" F* y
pd.to_datetime(df_date_cols)
& s( Z' `; t* S5 U6 E# z9 [2 |6 p w8 j% Y e+ f
0 2020-01-01 10:30:20) W+ n& i4 _% r8 y$ b& y
1 2020-01-02 20:50:403 n- s2 x+ q% I: Y$ ]: J6 m
dtype: datetime64[ns]
- ^( U, x8 H+ `18 d* v! e; e+ z( v2 m" s
26 A! I, j$ i. W5 h* i' F. I8 ?
3
- X: V' M$ J: I4" |# f* t$ l _/ ?; j
5* {, }7 N4 S. C z
6
% _8 K) M4 q- x7
' H7 l7 r0 A' P) m% |8
! ~3 M8 y- T' _" Y* d90 d$ ~, {% I1 {
10( o6 S& M9 ?& l2 [* J
11
. Z" U y C+ ]. i( F# y! S) L$ ^; Jdate_range是一种生成连续间隔时间的一种方法,其重要的参数为start, end, freq, periods,它们分别表示开始时间,结束时间,时间间隔,时间戳个数。其中,四个中的三个参数决定了,那么剩下的一个就随之确定了。这里要注意,开始或结束日期如果作为端点则它会被包含:) f7 k3 A0 ]! H C+ g, `' N
pd.date_range('2020-1-1','2020-1-21', freq='10D') # 包含) ]9 v5 I! o5 k4 G9 ?
Out[25]: DatetimeIndex(['2020-01-01', '2020-01-11', '2020-01-21'], dtype='datetime64[ns]', freq='10D')' q; \* b9 Y7 _% ]
# O( B5 x- |6 S3 r2 ~' R ~pd.date_range('2020-1-1','2020-2-28', freq='10D')' v# E. o" z2 p/ C+ d5 G9 T
Out[26]: 6 K( T/ k. q8 D/ v h6 i
DatetimeIndex(['2020-01-01', '2020-01-11', '2020-01-21', '2020-01-31',
' _0 N: F; @; Q* n '2020-02-10', '2020-02-20'],$ `- M* m& M' J: S. D E% z. c
dtype='datetime64[ns]', freq='10D') F6 N$ }$ }) K) B0 ~) v
7 p; W% |* r- J' d0 |4 T5 r' {. q
pd.date_range('2020-1-1',
3 k& l. U7 ~3 g; b; [$ F! t '2020-2-28', periods=6) # 由于结束日期无法取到,freq不为10天& F7 f, K' a# @% A: V' _& G- S' p/ x
+ j& u7 |8 O8 g( y3 p$ xOut[27]:
5 e: h9 v4 P f; a$ B8 x9 qDatetimeIndex(['2020-01-01 00:00:00', '2020-01-12 14:24:00',. S3 l% _8 ?2 R* x: F
'2020-01-24 04:48:00', '2020-02-04 19:12:00',! R4 \. Q3 O" C8 D2 B
'2020-02-16 09:36:00', '2020-02-28 00:00:00'],
& W; w+ M& `, j0 V; b7 a dtype='datetime64[ns]', freq=None)
( r* Y% w! U7 `1 t5 A
2 Q! M8 _! |9 g/ t$ B1
7 w/ C4 l6 r ?% n# C& s' A2
% o+ u1 z! i. j) v J30 F' K; T' A% O: F S0 s
4
. Y4 x- Z$ c2 R# R& o' Y. ~) N+ B- W5, c+ ?& r5 f5 E8 A7 Y) _# ~0 _
6" D/ W' [$ N: b# K2 H
74 b/ ^1 u2 X7 f/ _
8! O# N4 b# F2 N0 c; u- J
9+ A9 L& _' r# ]! u3 ~- a0 V9 t
101 C- ]$ c& p" r. E" g
11. I6 F. o; q% M9 D, V$ ]! w$ u* ~
12/ s" o0 G% ^. O
132 e* G6 d, j6 \8 q4 E8 {* n
14
7 O% b/ ^) E* p3 j0 ]15- f/ K5 z) o( {
16
$ k: A0 r2 n& i7 ?3 S! B174 k1 n+ @6 J, C
这里的freq参数与DateOffset对象紧密相关,将在第四节介绍其具体的用法。0 d* j! N; X: u
, C3 V! [, Y2 F' }
【练一练】
L! C" g0 V) M9 f' {% ~) }6 [5 ]Timestamp上定义了一个value属性,其返回的整数值代表了从1970年1月1日零点到给定时间戳相差的纳秒数,请利用这个属性构造一个随机生成给定日期区间内日期序列的函数。
/ B% _. ^' g- R$ O
$ g/ _- Q* q( M% `. Z% Xls=['2020-01-01','2020-02-20']
5 V" R7 S" K" X+ P& s( @5 Gdef dates(ls,n):
2 i. z* P a0 |9 { min=pd.Timestamp(ls[0]).value/10**9# b9 @" s2 h" r' ^4 h
max=pd.Timestamp(ls[1]).value/10**9+ [; r! p7 g& d$ }' K5 ^% }
times=np.random.randint(min,max+1,n)
3 _& g7 \" N7 V# O return pd.to_datetime(times,unit='s'), H' ?: T' ~- M- j
dates(ls,10)
" ~/ d$ t7 ]% K; j, q8 Z* o3 J* n- c- }4 j
DatetimeIndex(['2020-02-16 09:25:30', '2020-01-29 07:00:04',$ U4 s) { h. v1 U. F# T7 W
'2020-01-21 12:26:02', '2020-02-08 20:34:08',; p |/ F1 V" v6 b
'2020-02-15 00:18:33', '2020-02-11 02:18:07',
8 `6 N# k, X& T' i '2020-01-12 21:48:59', '2020-01-12 00:39:24',
+ z6 S% z) m) w4 I. S '2020-02-14 20:55:20', '2020-01-26 15:44:13'],
" c) Y0 Q: n* W0 l$ h5 u6 c dtype='datetime64[ns]', freq=None)
; A6 ~: b1 o% j" |( ~* o/ [' {1
" S# `7 N; v3 h- O- _' I2
1 s {8 ?1 ^6 \; a$ U3
4 g) G0 Q9 \, C4
0 n O) D2 K9 w# D& m3 v4 J5! O+ s% C: E) t d
62 x- l. V3 ^/ E
7" ?% c) H$ A" c3 D% ^
8
7 \: G% s/ K" v* \9
) q% r- N) W( Z j* t- |! N3 C10
3 Z( m4 h4 W! f5 [! n# ~: t11
% p- a! t" e, T8 g12
+ s# k# e5 V( M0 @8 N13
: C# M9 X' L9 V- I, o; H149 {) F8 O5 ~) |# L% O( u
asfreq:改变序列采样频率的方法,能够根据给定的freq对序列进行类似于reindex的操作:
3 f1 T7 ^" \8 J, t7 v- d* Js = pd.Series(np.random.rand(5),% {" A0 M& ^: c$ B; u0 O
index=pd.to_datetime([' R9 J0 V% E7 `- a" D- c7 y6 M
'2020-1-%d'%i for i in range(1,10,2)]))
8 b) m$ @) T* k! E- F q2 p, K
! a9 _7 U" ?9 u9 T; N
s.head()# v- Q' P; N1 e; s; h; G/ D1 b
Out[29]: : L' u* |3 {5 v; B
2020-01-01 0.836578
/ @ D9 h( w: x3 M! m0 S6 a5 o2020-01-03 0.678419
! x7 H0 B1 }. O# S- f7 z2020-01-05 0.711897/ M' U' E2 T$ W3 o! V
2020-01-07 0.487429
) X" M) e y# q, p2020-01-09 0.604705# v( c% z5 a B2 W# B$ p6 U
dtype: float643 J+ E& P/ H7 v2 v5 D
0 S- v% B+ `% ks.asfreq('D').head()
8 f& Y. P1 ^/ ]5 uOut[30]:
/ \) u/ w6 D8 W9 U% q2020-01-01 0.836578
( U' h/ O7 A/ H2 d2020-01-02 NaN8 {9 }) b8 N) k. y) @
2020-01-03 0.678419
1 j6 M' V5 x5 D! y$ Y1 e7 j2020-01-04 NaN# D, y0 i5 L" C9 e! F
2020-01-05 0.711897
6 q( \# E' z1 D- ^. @- B3 GFreq: D, dtype: float641 G$ _7 \9 H D& ~
2 F! ^% [2 O+ T- i- `" t
s.asfreq('12H').head()
* P7 i2 a, I! B4 n/ `$ Y# g) sOut[31]: 8 p" x9 Q! q2 z, ]
2020-01-01 00:00:00 0.836578/ V. P, j7 p3 P7 f
2020-01-01 12:00:00 NaN4 M1 g4 C2 A. ]9 d1 I
2020-01-02 00:00:00 NaN
G6 V! F$ q* F2020-01-02 12:00:00 NaN) w5 f3 i' ~: L! i, g/ o/ [: d
2020-01-03 00:00:00 0.678419/ a$ A& W2 m2 U8 [
Freq: 12H, dtype: float64
- Y# I7 y3 ]+ G5 l8 A2 P3 [* R2 x5 B- j' `8 j* _5 d9 l2 A# O
1$ m! I2 {9 q6 t% m0 y
2
" n9 S' s0 D$ l% J% ^- w1 U/ M3
. d! M, J% X) R z2 v: g4
9 D& g- o4 g! f0 y- N4 v59 I w6 O5 b) T; C9 A
6
' A* N' X# ~) f) d. q3 T: z7! @( C3 J) x$ C- s. @! W
8
6 u1 y3 G V: L- m) G+ O9
. K; c& U) ~ T @. u1 _; n, H10
6 p! Y% ]) p4 O ^9 J+ W11, B/ a0 n0 F7 V- v
12# W( ^/ ]0 W/ W& V0 A3 H( M5 H
13
- Q; X5 A9 J3 t' s: K1 f14
. H: ]: a+ ^5 t, a" a8 n9 X15
9 l+ E9 m! }, Q |3 M2 T6 G+ u) E16
! L; u* Z0 _* z6 a& t17
) [0 z4 W, Y( d$ g, N r4 ~18: L' g! g- O* I, G
19
. q+ \ E. j2 b" i4 b# _6 B$ b/ V20
# D8 I, b' t" J21& K1 @ S8 G N: r
22
. m0 u7 K- H' ?& R" S23# G9 H: A. g/ q/ Y7 C
24: g% g4 C1 N6 C
25
# @/ U+ ?" C7 |/ f3 z26
8 Z9 D2 z/ J$ `0 O27
/ e) j) }- p( B- { u$ D H* I282 R/ U* P V7 [6 ^
298 n9 f& H! @, Y9 v# z
30% O+ `- r9 \1 `# ~; X" o5 {; J+ J
310 ]5 A/ B& _2 T) ]2 H5 q# m9 v
【NOTE】datetime64[ns] 序列的极值与均值& z1 A' c, n! H4 J ?
前面提到了datetime64[ns]本质上可以理解为一个整数,即从1970年1月1日零点到给定时间戳相差的纳秒数。所以对于一个datetime64[ns]序列,可以使用max, min, mean,来取得最大时间戳、最小时间戳和“平均”时间戳。) L8 s( i2 f! z) Q
7 J: f# z8 L: r, ~& I
10.2.3 dt对象
% D0 F7 q$ w# o) n! @% ^ 如同category, string的序列上定义了cat, str来完成分类数据和文本数据的操作,在时序类型的序列上定义了dt对象来完成许多时间序列的相关操作。这里对于datetime64[ns]类型而言,可以大致分为三类操作:取出时间相关的属性、判断时间戳是否满足条件、取整操作。
- r7 m- O7 M% f* e; K4 l' @, T! P8 }+ y9 r3 k1 W# V
第一类操作的常用属性包括:date, time, year, month, day, hour, minute, second, microsecond, nanosecond, dayofweek, dayofyear, weekofyear, daysinmonth, quarter,其中daysinmonth, quarter分别表示该月一共有几天和季度。. W7 x( `! z# q
s = pd.Series(pd.date_range('2020-1-1','2020-1-3', freq='D'))5 Q/ N w6 R1 D& z" h; ?
) ]& ]. x ? G% ~7 q# t3 f
s.dt.date" d2 R& r+ Q* W, P. d" ]% C
Out[33]: : w& V% d$ v* a
0 2020-01-01
9 d* I2 `% ~ e: A1 2020-01-02' C0 H7 o ~( X6 b" W5 s
2 2020-01-03/ b8 y5 w6 |( P3 m" b3 u: p! [; S
dtype: object
& [: W e# m3 z) Y+ W7 C2 j' g& M2 t& d7 U a" S5 A' s
s.dt.time, i5 f) a h( F" }
Out[34]: . v* |6 ?- t8 y% V6 u Y
0 00:00:00
! i s6 I% q; J, M- g1 00:00:00
# E$ g4 s! e, q& z2 00:00:00
0 b5 K3 t- w; T( f+ m0 ^dtype: object
. u/ |/ e: X8 i. q2 e( O/ h& e1 \; q4 S
s.dt.day
8 n0 S. @7 A- @( U1 i& C% _. cOut[35]: ) B3 h$ ^. |1 E! C2 R
0 18 c. ~9 p$ V! C/ u2 b
1 2
) S* u# H" Z! r% A/ T( h. `2 3
, y/ I. {! f J" Jdtype: int64& o1 q/ P6 z L6 R% ?
5 O' @& E0 _! [
s.dt.daysinmonth& Y3 n# X2 l: _' Q3 l. z
Out[36]: w6 w, a- ?- T) B! J( Z& v$ U9 [
0 31# }$ D/ D3 n1 c
1 31: W4 ^* ]% Y% W. H1 |5 B) y, f: t
2 31' I, m+ P/ m! r' T
dtype: int64
2 B: W3 `: ?) \' y- n% J5 k5 M9 Q/ J9 _
1
3 ^: P# u& n9 `& {% k1 {2
% ?# E" S* ~9 k" W; O# o3+ S0 @9 A9 ]+ w9 f1 G8 k# r z
4- X6 X5 F6 B6 X2 M7 Y3 O- \
5: L3 v2 y; D0 U
69 U/ H3 A: D2 f8 ?7 o/ ^
72 q3 R* v$ t, ]0 P+ t) U$ U) D& n6 ~& R
8+ P! _8 g2 _/ H1 J: ]
9
, I; N1 D; x6 G( `9 m0 [6 L10
: W) H! X4 [( z1 s11; S' U# \( H: Y8 M
12
2 p, r* A) C* s6 f( M2 O8 v; v; ?13$ m. l, _# q, L5 _% I2 R
14# N) O- C; V7 o" _' g; L
15
9 D& I, P! V* N7 O16
1 P) l6 w* R, t* g# ^: ^17
. n, }) E$ Q! a% W( }- t2 q+ C189 G+ c$ p' M- }: P, d
19
3 K3 a3 q' g! [6 {20: `1 j: }& |% {6 A5 Y S1 O. Z- E
21
$ |5 ]6 p, y4 n1 [7 D6 V22% c& o* c( `; |" c5 {
23
! p5 B% e# f& f24
% M: I/ u& b5 d; T4 C! K" @) ?" W25$ y$ U* q; m+ Z. M$ C
26' V2 B L, k& x, O& ?7 R5 S
27# ^; ` i O5 ?' D: F
28: V8 q( j4 d( |4 H
29. X. S1 B( Z2 P. U& \
在这些属性中,经常使用的是dayofweek,它返回了周中的星期情况,周一为0、周二为1,以此类推。此外,还可以通过month_name, day_name返回英文的月名和星期名,注意它们是方法而不是属性:( r# A. J: F* Z
7 Q& Q8 u8 E8 @6 q% M8 _1 Q! `
s.dt.dayofweek
. ?% c# Q9 N' t) e7 D" B4 ~Out[37]:
) t b' X% Z1 I0 2
& T" V+ t @; R9 v; N1 3. q# ^2 v* L1 E' f+ f/ C- x
2 4
# ~( n5 l6 o) w. [8 t8 S2 a) F* ~- Ndtype: int64
: d! \; h. h9 z( y8 ?& M' S9 N! G- F: L5 {( B7 |! }3 T
s.dt.month_name()6 e. x0 }; T* w3 T
Out[38]:
9 l" |* V) J# a* b: M# ?0 January: i: T$ C$ v+ k
1 January
/ j: |6 ]* v5 h. }5 ^+ H2 January5 x5 b. G! ?. k
dtype: object3 u& O, i2 D( H0 m0 p8 m9 U
2 d) t Z" s2 x5 d* e c% H4 @. L
s.dt.day_name()# V! j4 s2 O, ?0 ~; X' z- ^
Out[39]: ' G4 x, N5 n% H5 P' {# Z' G
0 Wednesday
1 y: k9 L0 W+ j2 M! }# h9 m- W1 Thursday7 I, g2 U! x! c& k/ C
2 Friday
% X9 ~+ {: a) Jdtype: object. g+ F$ f3 k8 Z3 T
6 f7 U1 S& X! v. S
1
- G/ A+ ~- R# E* p+ ?9 E5 ^2 h6 x/ v2
9 ~2 ]6 O0 c9 v3
) K6 \, d, R/ ^, y/ P; Z$ _/ e4
1 a) {% Z0 A8 D: a2 T& a. U5& X) @( [( t/ P, k$ K' `
6
% p" f; E5 P7 C, L7 b+ V) P7
# F( f6 @9 J3 ]' G7 Z3 d0 C3 b8: \9 ~# y7 Q/ j7 p
9
; v5 _* U! l. \5 z10
- Z6 `1 {8 c9 m! D1 W" n d$ q% L11- X/ a( z" a5 Q3 a7 @. g
125 _' T. s1 j# }. i3 }
13, F! K* L, E$ ~$ @+ x% c% X a# D
141 e- O: C! A4 F) K
15& Q0 W7 F, {! T- R
16
: [' |2 Y8 [( G5 }, I171 Q* W0 F6 n/ a. v9 t2 @# \/ u P
18) `( k3 M9 [9 l" D$ ?4 z
19% H; X, v `. `7 \* J
20; l7 s4 B j+ \: c& _4 ~
第二类判断操作主要用于测试是否为月/季/年的第一天或者最后一天:
& c* q/ O3 t' ]0 P8 e' y: i1 @s.dt.is_year_start # 还可选 is_quarter/month_start: M) {0 P+ e+ p4 d! Q! |
Out[40]: 6 e2 q$ U. H1 s
0 True
! B* T/ Z2 e5 Z1 _- |3 I! u1 False6 r! U; C2 h# U% b1 u2 M) i
2 False! B% Y4 Z4 A9 `. s
dtype: bool
/ ]( j! h3 `; g( w: V
/ ]1 `( k9 N) v9 Ls.dt.is_year_end # 还可选 is_quarter/month_end! L3 a( |4 k5 b$ e( H0 s- ~
Out[41]: 8 d3 r9 k1 K, s z* |6 g3 a
0 False% [! M5 K8 q8 s/ `9 C; m
1 False/ s% g+ o! Y- ^
2 False
/ c3 ]/ u& ^" r, ~! l- ?dtype: bool
6 Y8 I8 C& c D5 U( q" t2 T8 z7 [- a1
# t9 ? E; |4 J" y: @2
$ }" y. W( z, I' w6 H9 L3- c& V5 A- U+ ]5 _/ u/ }
4. f( m9 O$ b7 j4 L
5+ e0 [+ u( s3 G: f
6& I) P0 |3 K! I
7. ]9 f. g {4 h7 ]1 I: K9 J
8
: i: p; W+ A& U, U5 \) t2 e9; e. ]' ]3 N* i2 _4 w9 @& @
10
9 W3 w2 I3 k2 D% n' j5 @. w8 b) p11+ z. Z8 O& b* _' E% W2 X" s
12
1 ~! C5 X3 u1 X2 X0 e2 V13
- ^, c7 `/ r" Z2 J) X% O6 T# }第三类的取整操作包含round, ceil, floor,它们的公共参数为freq,常用的包括H, min, S(小时、分钟、秒),所有可选的freq可参考此处。0 u; q) A& m2 n6 Y+ c
s = pd.Series(pd.date_range('2020-1-1 20:35:00',, X% s% t7 u: R" `, v$ p9 m6 C
'2020-1-1 22:35:00',
2 C% |4 k$ T0 S* N* L( Q* D1 D freq='45min'))
- m( Z; V' Q% U
p4 o2 a6 F; g+ e1 C3 A- R: g# y/ X: T3 d+ |$ K
s
F4 M# e0 i6 g D0 f9 L) ~. LOut[43]:
2 G9 v4 F' s; F* n0 2020-01-01 20:35:00' k9 I r4 _8 G- |- x7 E
1 2020-01-01 21:20:003 d0 M4 a% G8 T ^5 f3 m, \
2 2020-01-01 22:05:00
/ Q* Y+ h. v/ l4 ndtype: datetime64[ns]( ?1 T2 L8 K6 |( C
1 b& E$ |2 f& N1 a6 S" T( Ms.dt.round('1H')6 Q, e+ }, O3 S* X. n( ~
Out[44]:
: ]5 y3 w) H! z# U9 ^0 2020-01-01 21:00:00
) l+ c* f; }% U E# `! R) B. s1 2020-01-01 21:00:00$ _# r5 p6 b* B7 C9 {* F+ m
2 2020-01-01 22:00:00
2 q2 L$ L, G. ^4 k) U( g5 ]/ p- cdtype: datetime64[ns]4 @( I) ], ?+ `. g! H9 g
, M# ~! e5 z+ A( _3 ms.dt.ceil('1H')! k* Z) M) Q$ O* g6 F/ E, v
Out[45]:
# M# _" w C/ q. N3 T! y0 2020-01-01 21:00:00
- K. }3 `5 u" z) ?; f3 T" T1 2020-01-01 22:00:00* l- @6 A) i( A4 D5 F. r) ?1 E! n: t
2 2020-01-01 23:00:00( ^& ^4 ~9 R, F& R
dtype: datetime64[ns]) b! s, e; ]# s+ Z& Z
' u! X2 A& v! Z! j1 A+ k5 I. H2 Bs.dt.floor('1H')1 _6 H$ v7 w3 b; U5 U
Out[46]: ( Y [% o: J- U6 w. {
0 2020-01-01 20:00:00
+ L: y* j4 j3 f3 u/ w9 X1 2020-01-01 21:00:00% e& w0 @) p* w6 \! `+ Z
2 2020-01-01 22:00:00
9 a: k( o8 l- H1 @# s- mdtype: datetime64[ns]! D/ m. p& f% H B) `0 E! T3 z
! c6 f4 @6 o. }. E16 U9 Y, P/ c& S/ a [2 k
2- A3 i& N8 z$ e; Q$ B- Z. L
3
6 k; U4 K7 E: Y1 M2 q8 r4, y3 z! P5 X" d6 ~
5
9 g& @7 l! V' q# G- G6 _: Q6! L: {% [' H1 n
7% s1 q7 K! M, {0 @' }$ V1 Q9 z
8
3 m" n$ b# h' M4 x& r3 ?9. Q, @" P; p8 a) s
10) ^& L9 ~8 e2 ?( w- Y( c, W3 G
11
* t [, Y8 d1 b4 i1 t12
) c! ^- D' m) H6 B13 D- l5 R& Q" `" ?4 v% F
14
4 Q5 [/ [' u3 h# N6 ?% q4 }; ]2 D15
$ _3 A" w0 K7 a! ?1 r' s16
/ b u; K! J; A; s0 q173 k! R, W3 i) r/ P6 }, y& N
187 N( f" X) L+ ^) _
191 G5 Q: T; }: q5 h
20
: ]5 F" ]6 q4 i/ K+ ]8 n2 B21
; o6 w* I- |+ [, Y22
8 h& H* m' X8 e0 J: E. z8 u% N, }23
1 n/ y( L* |' ^# X240 x3 Y6 H% @0 @ C
255 x y+ P% N$ O. t* M- s
26
: S/ P7 G( }0 S+ H+ C9 _27
* k% m: j, O2 H3 q& Y6 t& e28( x% ~" k4 G) i* ]
29
8 z( f6 L2 z; Y0 ^; R30
s' X; t6 e+ ?2 R31
/ V* v+ x7 V( }6 h32
8 W3 O0 W" @* k0 u10.2.4 时间戳的切片与索引/ y) O/ s; J, A$ g
一般而言,时间戳序列作为索引使用。如果想要选出某个子时间戳序列,有两种方法:$ n8 D4 A3 U+ G5 c O2 \
' @% h! V0 Y$ Z& r" m利用dt对象和布尔条件联合使用7 v+ j- ~: f" X2 {1 F
利用切片,后者常用于连续时间戳。( T% c+ M3 |3 H% u/ I
s = pd.Series(np.random.randint(2,size=366), index=pd.date_range('2020-01-01','2020-12-31'))
/ f( s: G+ s% y0 M zidx = pd.Series(s.index).dt8 T! \; f. p: v) V* B/ \
s.head(). B5 \2 g8 P# }1 k6 L! w: m6 i
$ R5 Y! Z& c# s g) c) a( q" t4 m2020-01-01 0+ J( l5 ^6 D0 W s
2020-01-02 19 f6 o1 m7 T ` H+ z( y3 Z2 d, ]+ [) Y4 Q5 h
2020-01-03 1
5 Y& b0 F; O& l/ T* z2020-01-04 09 H1 ^+ v# i H! q: c
2020-01-05 0
( r: d+ |( l9 C9 n9 \; _5 }; MFreq: D, dtype: int32
; {& e& a( H, {% Q& Y1
. A" U3 p; B2 O+ V \ n2/ j6 O$ ^9 g9 U
3
7 P# _2 y/ b5 d4& s! ^ z, p7 `4 ]% A3 ?
5% Z' m5 x7 P# `, k- _4 U* z
6
! H# r; A, l# g7 k/ P7' w' C# p5 C1 H. f% S$ Q
8
1 a( H# ~7 H8 N9. L/ M0 [; X, d. m
10
( l; p7 v& R8 q' f& ]Example1:每月的第一天或者最后一天
: B) s5 s) _; G: h4 v
/ W, n! `4 d1 q7 `s[(idx.is_month_start|idx.is_month_end).values].head() # 必须要写.values! W* I$ B' a. B' I( S b
Out[50]: 0 C% ], F9 m) Z! }- s% j: z/ ?. o
2020-01-01 1: x% G9 j% F' ]3 k
2020-01-31 0
9 r/ @8 g4 t6 \# k) B) {9 g2 ~2020-02-01 1. b' B7 K6 ]8 `
2020-02-29 1
, h( g, O& C2 N2020-03-01 0" m7 |4 f! E. T
dtype: int32! |9 @6 w4 o8 C! J% H: y
10 F- V. R% e/ ^) H8 T" f1 g0 |& @6 O
2
: _0 C& X, I1 y6 M3 x/ f. P( x3
# q$ h& `& |+ M8 Q% T8 v4
4 |- }- q, z6 ~" ?: U5- z5 z/ ]/ m# h# k. q( x" V
6
8 ~" V( ]& S: b- f" ]70 C ^3 T( A, j2 |
8
$ S) u3 Y5 i1 @" j% z3 v$ r( dExample2:双休日9 D: [ Z* |; T" x
& ^5 R: _! l7 A* V) `) G
s[idx.dayofweek.isin([5,6]).values].head() F: t% ?+ s+ `* y6 M
Out[51]:
' E6 H" f1 D6 s) `' e1 U2020-01-04 1
1 e* {( Q( j5 l% ?5 Q2020-01-05 0
2 }! H! L X. i7 Z X2020-01-11 0+ H' s$ X& q1 q. ?$ R9 D$ b
2020-01-12 1
z: \8 U0 [7 Q m: w& D2 F2020-01-18 1- i. g% y2 Y( Z3 A
dtype: int32( f5 L# W \. `& t+ {
18 U! d/ @) \% ?
2- u* c8 ~0 l! t6 k, I
3' ^ ?) w* P$ C/ \4 u7 y2 {& E
4
" B! y. |: X8 z1 e5
h, [9 p" q) k/ {, [65 r2 S" d. P% F4 X6 F) { R w5 }
7
; L6 o* D/ N& a0 U/ C$ e% f$ ]8
3 @+ i" {. N& U; x+ K) t! m# PExample3:取出单日值
7 F; W: x8 V) f J. D" w4 _. b) _9 M6 ~- X% E/ o# e) v) t- _
s['2020-01-01']! ^1 p8 |* u0 C
Out[52]: 1
7 y4 {" r5 r4 _- W
; G1 B R9 ]0 p# k4 f3 ^) A- k& V3 As['20200101'] # 自动转换标准格式7 a; S2 _6 y) M* i9 U2 R% E
Out[53]: 12 {5 m4 n* A' V% S7 F' J& ?0 H4 w' l# Y
1
0 x1 [ |9 O9 S. P$ o2/ u9 s. u" j8 I# x2 v$ H
3
/ p5 H5 e+ U1 A* j% W( ~4
8 ^) l, C; W2 R: j% z, n0 [5
0 Y+ W6 k" y* r9 yExample4:取出七月
]% [6 q" t e |
$ [5 l4 K, c; q: Hs['2020-07'].head()5 B5 T: G3 u" X* k: m
Out[54]:
9 V e0 L! y* |- P2020-07-01 0. j8 y/ H/ c& F4 F3 v$ {2 N) X
2020-07-02 10 v' \/ v" f7 Z) t/ U- Q
2020-07-03 08 n/ J/ Z+ \4 t* Z- V$ [7 z
2020-07-04 0% Q& F c# u3 U7 n* }1 B
2020-07-05 0
4 E5 r1 C# Q/ D( K% HFreq: D, dtype: int32
1 B1 K: c5 I7 i$ ^. ~* q' C& Z* I12 B4 _% ?% D6 o; E
2, |( h$ N( S+ g; X( e- M! z5 D$ p& H
3
7 M6 c8 X+ b5 e& h. o: C44 Y) M- n( h( f8 [( F4 h N
5, ]* b( H4 X3 k* W. s: L i
6. ?5 c6 ]6 S- g1 T3 L: x
70 h+ o! P6 u+ A1 {. i0 K1 h
86 b) t! N, G5 W3 l& `# |
Example5:取出5月初至7月15日
4 g+ ^9 z7 }& H$ h' J- Z
+ D' j. }* j r( M) j6 m# d$ fs['2020-05':'2020-7-15'].head()$ c6 h3 L* z- a+ a9 u0 C
Out[55]:
4 A! ?- ]& G7 g: [& X! e2020-05-01 0
, z5 S. E0 i1 H8 e/ p2020-05-02 1- _! q& R1 T6 G* t6 H9 J
2020-05-03 0
O, p0 f- w8 {( \$ t9 U2020-05-04 11 \- n5 w+ q; J% P& N7 Q& S
2020-05-05 1
" F' M, ?( v" l; fFreq: D, dtype: int32) ?' z# J0 l) e9 j0 K1 W- Y7 a
3 v8 e: x3 h: \7 ` X0 Y k
s['2020-05':'2020-7-15'].tail()9 F2 z) q9 l4 f& h4 R; `& e
Out[56]: & A9 d) V1 _% J2 z' {# A0 y
2020-07-11 0
) x3 O5 I$ E& n" }3 i' T- D+ F5 y. E2020-07-12 0
% d$ j7 S% ~) r x0 x2020-07-13 1/ F4 |5 U* p5 e* @4 v
2020-07-14 07 ~$ y& x1 f- G8 Q- Q" W
2020-07-15 15 y9 J( I4 m9 I: }: D
Freq: D, dtype: int32% f8 C' c' N, s3 D
9 {! a1 _' r) U. }% D0 J; ?7 }
1: R1 o. s _! [ y1 L& B# s% l9 d
2, q% B% Z" \ c5 c+ `
3
6 P' l) n+ E* H6 _% n/ }' c4
: p4 N8 s2 h/ Z% K/ o5. H' x6 T, O& W% w
6
, @% ^& a8 \! v% I7* A* h. a" z/ s( q6 t
85 b% |0 N+ ~9 \' w4 Q6 r& `
9
8 v) S9 ?6 \4 R' |4 e1 c2 S" b10) k+ C' }' H* f5 d: Z
112 f% u9 Z' h& x( g% R2 ]
12! H% b* {, z2 U% B
136 l0 X6 X* d6 D
14
5 J/ N# R. L# o# o/ f h( C1 a3 H15
' J* }) e- F$ K4 b! k: G16
/ j! N6 z0 v/ S& g7 a17
3 K+ k7 ~0 W$ J! y3 J ~6 n10.3 时间差
+ g5 R. l5 p& ^$ c) x10.3.1 Timedelta的生成+ f- a, H! K8 A! E( E
pandas.Timedelta(value=<object object>, unit=None, **kwargs)$ O& g) |: V$ s6 j/ x. ~7 l
unit:字符串格式,默认 ‘ns’。如果输入是整数,则表示输入的单位。. |& _7 v9 g# z
可能的值有:
4 \, a" q$ i+ J) @4 g8 X
4 n6 H- t; H- _/ g' P! e% ~# p‘W’, ‘D’, ‘T’, ‘S’, ‘L’, ‘U’, or ‘N’: Q. f* E2 R6 e& t
‘days’ or ‘day’
; s: S; _* T6 {( }( H) i( o5 \2 [‘hours’, ‘hour’, ‘hr’, or ‘h’
! m9 T9 \, v7 |/ V2 r$ |% B4 W9 k‘minutes’, ‘minute’, ‘min’, or ‘m’
# _7 W" y; c* q. j‘seconds’, ‘second’, or ‘sec’
7 \4 Q1 E( U2 f, C' k3 B毫秒‘milliseconds’, ‘millisecond’, ‘millis’, or ‘milli’( j4 P1 \$ Z+ m! A& I3 V
微秒‘microseconds’, ‘microsecond’, ‘micros’, or ‘micro’4 w- ~* V( H5 b0 w7 R' { J
纳秒 ‘nanoseconds’, ‘nanosecond’, ‘nanos’, ‘nano’, or ‘ns’.
8 m& a# K. x" }* h; ]4 W& U" X, Z时间差可以理解为两个时间戳的差,可以通过pd.Timedelta来构造:
. s' H" p. u+ v, Q3 h2 d% `4 F' Xpd.Timestamp('20200102 08:00:00')-pd.Timestamp('20200101 07:35:00')
% ] _2 V/ K( Y$ cOut[57]: Timedelta('1 days 00:25:00')$ u$ X- }# h' G% `5 p1 V
* E0 V; z* y$ I2 G/ K7 e! q! n
pd.Timedelta(days=1, minutes=25) # 需要注意加s+ C* `5 d; E5 J/ X3 J
Out[58]: Timedelta('1 days 00:25:00')6 A4 C1 E/ G' s! @
1 k" M8 w$ d# o [- ^* J9 lpd.Timedelta('1 days 25 minutes') # 字符串生成
; W6 ~8 q/ {' f# f6 @% K7 ?8 JOut[59]: Timedelta('1 days 00:25:00')& t# D$ ?( V( i% G) ^
' {& u0 b5 E B9 q$ X: e- L
pd.Timedelta(1, "d")1 A2 D3 X. z, v5 u8 e- d
Out[58]: Timedelta('1 days 00:00:00')
: Q4 C- x$ z4 A/ J18 r. I7 i2 t7 Y; m
2' k: d+ \7 q' e, e7 B4 G
35 T9 |$ \% v1 q) k& t! f* B& U
4
8 [: |5 ^; [, K: n' `7 n50 f: `# v% T N+ N! r, W, ]
6
) l1 q/ d2 e$ m& |5 O7
( b% e" S3 A) R; h% E87 D, _0 c6 x$ ?' @ B, k
9; a. N6 ?% ]/ I$ }5 ]" p2 ]) t
10
, Z2 i! g" c6 z7 V- z; @11; L" v' k. f5 M; Q- m9 N
生成时间差序列的主要方式是 pd.to_timedelta ,其类型为 timedelta64[ns] :
# @+ M7 t9 v( R# Ss = pd.to_timedelta(df.Time_Record)
$ H! h+ r6 q# S/ E' v+ z: {% z, e6 {4 g" o5 V" \
s.head()
+ U, }; t) Y$ D. F9 ]$ j0 ~% hOut[61]: . s( Z6 E2 v- m+ W
0 0 days 00:04:34
6 `% O6 o2 `& `6 k& T' B1 0 days 00:04:207 \/ c+ T# _: U/ |* t
2 0 days 00:05:22
: P1 m5 P1 x! L7 E( H! f3 O3 0 days 00:04:08
5 O% ^& ^/ F4 B6 P- o/ S4 y% r4 0 days 00:05:22) A: @) b0 u9 E) P* l; H
Name: Time_Record, dtype: timedelta64[ns]
# Z' D5 A1 U' X w4 @. Y1& q6 Q4 H, y- l5 Z# C
2
7 z+ C& L, y% \# L$ ~+ g, k3
) Z! l3 h$ e, x4 w, }4
7 W3 Y/ K7 m+ _& ]5
9 @" p' e+ x8 v8 c& ]5 F6& {; M, x2 [; z) t, ?, Y% i
7
7 l; ~$ _8 t3 p$ _4 a8
( p1 C+ x& y. O {+ k$ A) b96 d3 W! |, G$ V8 d* b6 S
10$ E; q1 |( E2 V& m. M! x/ d
与date_range一样,时间差序列也可以用timedelta_range来生成,它们两者具有一致的参数:
% d$ t3 T, ]$ _- K' n1 npd.timedelta_range('0s', '1000s', freq='6min')
" J% @9 v# Z9 l. r) zOut[62]: TimedeltaIndex(['0 days 00:00:00', '0 days 00:06:00', '0 days 00:12:00'], dtype='timedelta64[ns]', freq='6T')/ g6 i7 f/ W; P# @
. G. y0 } {* E) I" K0 w( J& Bpd.timedelta_range('0s', '1000s', periods=3)8 W- u$ P0 ?% l' M
Out[63]: TimedeltaIndex(['0 days 00:00:00', '0 days 00:08:20', '0 days 00:16:40'], dtype='timedelta64[ns]', freq=None)
- Q6 @% Q8 h4 r9 j6 b1
' t( F. S. S( L9 b$ m2; ?3 A9 O$ D$ D/ ?4 N
3
* h& @+ r. S4 I3 G4 R4# o2 Z ^0 K1 G0 a- f
5+ Q* l+ F. v$ a2 d- p3 ]+ t
对于Timedelta序列,同样也定义了dt对象,上面主要定义了的属性包括days, seconds, mircroseconds(毫秒), nanoseconds(纳秒),它们分别返回了对应的时间差特征。需要注意的是,这里的seconds不是指单纯的秒,而是对天数取余后剩余的秒数:
6 R- @8 t, ]3 k; x+ Hs.dt.seconds.head()
& k# j9 j, y3 aOut[64]: : P4 w/ G' ~% F5 u% M1 C
0 274$ O, P# S; L; E! ^4 W
1 2605 t+ u. c' t8 m0 Y$ h) `7 ~; t9 \
2 322
) Y V# e, S0 D0 K- |3 2482 i/ j. M$ W" I2 l. [" k) S2 L
4 322
8 G s, Y% {9 FName: Time_Record, dtype: int649 c2 c- L; Y+ k, L
1
. r. | \7 Z: C% T( s/ x2
4 W8 r- U" P$ z% N1 h( @ x35 ?% t* L+ U; ~% Z4 z1 E
4
: x% |% n' X' b! {7 g53 ^' g8 h$ l3 g* x9 Z5 L! ?
6
5 X" ?6 j, k5 l$ g. F. u7, p' G% n) |, T U8 x
8
7 F3 p" z( j' e2 R1 Y如果不想对天数取余而直接对应秒数,可以使用total_seconds
- L2 o0 S# r; P K# W# U; }
4 S* V3 B' e1 H S$ Hs.dt.total_seconds().head(): O I! t% o0 u5 Z2 s
Out[65]: : l. `& B8 O! m0 ^8 j" |
0 274.0
& r5 F9 |7 k4 L. c/ C8 G" P: _9 Q# o1 260.0
- o2 V' E+ r9 T# H! ]2 322.08 g- u/ k J6 N
3 248.0
" N% ?: L4 b7 f5 m& u4 322.08 Q+ {2 N6 v& V
Name: Time_Record, dtype: float640 X' l8 M; J& v* R; v
1) C# P$ {5 I1 o' e5 A
25 o( J3 X; A" M: F& S; n
3& s) l7 f; E$ i" u* z
4* `7 t! o4 M: O' s% \6 @" M8 m4 ?
5" S6 ]6 L1 a# C5 z, V$ y: \3 R) x
6
1 @6 U! F1 V9 z1 p6 K- h7
7 |) H3 W; j" }1 g5 \( S( l86 s! w( d. r$ u
与时间戳序列类似,取整函数也是可以在dt对象上使用的:
$ y2 ?* b' S$ Z/ f6 L3 @% A2 f. A
6 l ~6 `8 \% fpd.to_timedelta(df.Time_Record).dt.round('min').head()+ X/ e; O- H# ~0 m
Out[66]: / Q m r+ g; n0 ?0 U, @3 Q- F n* h4 ?
0 0 days 00:05:00
- v% g) |. F; I6 W$ ~0 U- [* _" E1 0 days 00:04:00, |/ ^: z# ~- Y, Q3 N3 x7 X
2 0 days 00:05:00
' K& x) J- E- t: ~% ^* |% }3 0 days 00:04:00: G g* m0 V8 Y/ E9 _
4 0 days 00:05:00
4 L" I8 ]7 f! p' UName: Time_Record, dtype: timedelta64[ns]4 ]( t3 e4 ^$ |5 R5 f O
1
0 [4 H& R1 c/ a+ y2
# y ]( D. z4 U3
8 N7 M# p) y7 ]5 F5 J" u/ \: }4, R, I6 {# x% ~: n5 u
5
' a* b6 v6 w* Z; |& ^# ?# Q6 X$ [, G' ]+ ]3 C" L( ]
7, X+ N3 R* E" X# l& j
8
' \7 n& H* Q7 _8 v, a2 g10.2.2 Timedelta的运算
W% S$ U6 p# r. F1 A单个时间差的常用运算,有三类:与标量的乘法运算、与时间戳的加减法运算、与时间差的加减法与除法运算:
& q: u' h+ i/ Y- V4 Otd1 = pd.Timedelta(days=1)
2 Z: ^2 ^4 ~$ F. A- v# ftd2 = pd.Timedelta(days=3)
- d5 u. F3 {, W9 G! R" m5 Wts = pd.Timestamp('20200101')
) X1 j. W# Q+ W) Z, c9 {( a1 y4 w
td1 * 2. y5 o( d* W0 y- z! E; M1 x
Out[70]: Timedelta('2 days 00:00:00')9 P, k l( w# I
9 B$ n& l& {6 J- H9 `9 {
td2 - td1$ w4 b$ |9 d v/ S9 |
Out[71]: Timedelta('2 days 00:00:00')
, c- h+ e2 C& e5 v9 I# |
# U9 E0 u8 `! q# Xts + td1
' `7 w6 d: Y% |! I1 W+ i. {( o9 uOut[72]: Timestamp('2020-01-02 00:00:00')
' R6 F6 B8 |* h* V2 [& e& W9 X( W8 K
ts - td1, w p: q* L8 {
Out[73]: Timestamp('2019-12-31 00:00:00')
. Z5 D4 r' X$ X' X) O( L+ }4 E1' L- h! V, G2 d) m# F& n
23 o, ~( _) O3 H2 Q" ^
3; [1 P( G! H, l& i1 i& f$ _
4
# H& z! {$ _2 I! w: S5
& y5 F% u# t! X7 D/ U0 _( y62 R: e$ T( A- Y) r+ d8 O
7+ @1 ~2 u& j0 i$ X8 t
8, R. U' G+ J$ n h5 P' \- Z: n# z
9* d# ]$ ?2 S/ D& s
10
, }) k Z9 k+ D w11
( x g/ G6 h1 K3 j12
) N7 F E8 p& d5 {3 h i13
2 H6 h( Y5 { b/ z# ^* a14
/ R: U( Q6 f7 t9 w15% Y3 H5 ^! L. H
时间差的序列的运算,和上面方法相同:5 P- g* ^; \6 l: o- I
td1 = pd.timedelta_range(start='1 days', periods=5)
( H0 h+ w7 n9 o5 O7 h1 G- {6 ntd2 = pd.timedelta_range(start='12 hours',
* S! \( r7 P4 B9 v freq='2H',% ^! F+ F/ U8 h1 F) \
periods=5)* I0 N1 i" j, E4 b
ts = pd.date_range('20200101', '20200105')
9 s% X9 ?: c3 Y& i0 k0 Z7 _, Atd1,td2,ts% Q! s/ z: F' d2 {0 {/ n7 M }0 K
( j0 a$ t6 T( w" ?TimedeltaIndex(['1 days', '2 days', '3 days', '4 days', '5 days'], dtype='timedelta64[ns]', freq='D'): s' }7 c5 Y# e0 r, B" V+ I6 f3 v
TimedeltaIndex(['0 days 12:00:00', '0 days 14:00:00', '0 days 16:00:00',* }8 R: u4 |! f" }/ @: \+ @
'0 days 18:00:00', '0 days 20:00:00'], dtype='timedelta64[ns]', freq='2H')) y1 E, i2 ?; s" `5 `& K& u
DatetimeIndex(['2020-01-01', '2020-01-02', '2020-01-03', '2020-01-04',8 w# t+ t( C* L& d, P g i# F
'2020-01-05'],# t- g4 g% T( |# q5 T6 r% D; J1 c
dtype='datetime64[ns]', freq='D'). H: U6 u/ ^0 K! H8 T+ g% |
1& q+ r' |0 C1 @
2
7 Q# ]9 I6 v" c) Z3
+ ?9 z0 E E& h! k, l$ Z" o+ y4
: ?! P, P: a% g$ D2 [- k5# d1 e1 y3 L$ }* V: U) u
6* x X8 s- j$ m. q9 E/ ~, _
73 C! ?! r' N! P+ W
8; j0 g' l+ m( O0 d p' u5 q
94 ~) G5 q" X7 Q" D& ^& R7 Q
10
; m. V: {- N& g2 u7 w1 M+ G1 V11# ]" S* t/ I6 }4 C' X
12# T; B! G% w- O2 ~" G @3 A: X7 q* M
134 s4 N# {# c, @1 H" b* W
td1 * 5% D. M- q9 V$ u5 g/ _) F) j
Out[77]: TimedeltaIndex(['5 days', '10 days', '15 days', '20 days', '25 days'], dtype='timedelta64[ns]', freq='5D')
- Q6 W& B2 M" b, ]0 K. S# Y* s
[& j0 b# |0 a$ H1 Etd1 * pd.Series(list(range(5))) # 逐个相乘8 O1 T5 Y7 J- ]6 \
Out[78]:
- E1 C" x* O( n" C7 l7 p" q, l0 0 days
8 G& o- O4 Z. X& r' _1 2 days7 }" s# ?$ L4 W6 l' c/ S8 H$ }
2 6 days
/ Q5 o7 D3 O9 O% @, s# d8 F3 Z; K3 12 days2 e# f0 o" X4 Y. k8 k, l6 w* t
4 20 days7 X6 X3 i8 e* g2 m" I. H7 @0 x
dtype: timedelta64[ns]9 I/ W E" V2 q& y1 ]4 p
: U" P' m% x% `td1 - td2) G- c( r. V' K9 d# g# _2 H% O
Out[79]:
1 a$ P9 ?8 E* U8 GTimedeltaIndex(['0 days 12:00:00', '1 days 10:00:00', '2 days 08:00:00',+ z# K7 p' ]% l* F( [
'3 days 06:00:00', '4 days 04:00:00'],1 ]0 Q+ e" b0 d B$ q/ E2 m! k& t
dtype='timedelta64[ns]', freq=None)& G9 H# [5 |7 t) k; n: \' k
' o: {; {3 ?) ]' X7 Wtd1 + pd.Timestamp('20200101')- e: @% Z/ z" n/ h9 w8 R
Out[80]:
: l- }# a- T4 nDatetimeIndex(['2020-01-02', '2020-01-03', '2020-01-04', '2020-01-05',# K3 L3 u6 j$ ~8 k
'2020-01-06'],dtype='datetime64[ns]', freq='D')2 \: X$ G/ c' t+ L1 B8 U4 O- c, J: w' H
; V* `! Z# P5 H
td1 + ts # 逐个相加
- I: q; m u, n5 p! U4 O0 {* `Out[81]:
( s3 Z) o9 p5 o8 d# w5 wDatetimeIndex(['2020-01-02', '2020-01-04', '2020-01-06', '2020-01-08',( S6 A+ E/ u3 `# p# V1 |
'2020-01-10'],& B5 z3 x+ r. q; S1 O
dtype='datetime64[ns]', freq=None)
' `$ K3 |, R' ~2 y4 |# z! V! q# a0 ?
1- P0 g3 d. I5 x+ \! G
2
/ K8 O* P* l$ E0 U S) M3
3 e Y5 x+ ~ F2 p9 Y+ P, S' Z4
9 Q2 J2 _1 |: Q59 y3 }" L z$ q
6
2 t1 j/ y3 P) ^7
! [6 c" u1 L1 A1 X; h9 I8
( Z1 n9 L' z, Y7 f$ o1 w9
& f4 \2 ?# N" u/ Y3 r; {% J10
$ \7 ?7 N' Y$ R# l- v11
" E# \- @5 q4 q; S' \( }12
$ G/ M3 w! @7 ?7 T( ~3 c2 r13
( e0 {: r9 S# b0 i* m9 O, s14; K1 d- S- ]% c: b+ v
15$ o: ?! Q( p [3 j( b9 P
16
& `0 g% U, _! Y i2 J/ }2 ]- N17
& Y4 ?5 d' m. H0 W18
. V% ^! I) U% t' c; S& g19
3 L, i2 J' h! B# `6 }2 I206 U" [& t# e$ G5 n7 }9 P
212 z, P z; O. U
22; A! I I% i. N' x+ i
23
- E# X' Q$ c5 @. q. u! \24' {# |8 J9 [) d$ ?
253 r R/ I- i4 m: I
268 _8 j+ \; l6 @2 K# L8 ^1 @; E
27+ V3 H( |8 q- U1 C8 {
28
- H3 T( g7 e* }& Z8 X) f, Z& B10.4 日期偏置
% \4 b7 n# c: x10.4.1 Offset对象+ g; t6 \6 I! o( p3 p; s: f( _% P
日期偏置是一种和日历相关的特殊时间差,例如回到第一节中的两个问题:如何求2020年9月第一个周一的日期,以及如何求2020年9月7日后的第30个工作日是哪一天。
! L9 y1 |! N- \' C6 y
8 }# n7 i; n. S8 ?4 V2 }3 EDateOffset 类有10个属性,假设s=pd.offsets.WeekOfMonth(week=0,weekday=0),则:% [& D7 e7 M4 c: m/ T
6 J7 J% Z1 [2 @" S' L9 ?1 {s.base:<WeekOfMonth: week=0, weekday=0>,返回 n=1 且所有其他属性一样的副本7 K% [2 L) {6 A3 C) j- y1 y
s.kwds:{‘week’: 0, ‘weekday’: 0}# N4 U+ c0 k/ _ S9 s7 B
s.wek/s.weekday:顾名思义
4 c8 V: A5 M9 q* D X. O0 ~9 ]: m有14个方法,包括:! i4 n0 N; t! P0 _/ \0 z& l( K% ]
7 I) g, c, R; S+ a J
DateOffset.is_month_start、DateOffset.is_month_end、DateOffset.is_quarter_start、DateOffset.is_quarter_end、DateOffset.is_year_start、DateOffset.is_year_end等等。
+ B O( e/ x: w9 c N* Wpandas.tseries.offsets.WeekOfMonth(week,weekday):描述每月的日期,例如“每月第二周的星期二”。
+ t: c+ m6 m, i% L* N" I3 M; V/ {. Q) n4 m' o8 ~ e
有两个参数:( V9 T, f' v' |7 y# p% b. \
week:整型,表示一个月的第几周。例如 0 是一个月的第 1 周,1 是第 2 周,以此类推。- P/ b" j; M/ `6 n7 K
weekday:整型,取值为[0,1,…6],表示周一到周日,默认取值为0(星期一)
, A$ j! O: w: o3 Ppandas.tseries.offsets.BusinessDay(n):相当于pd.offsets.BDay(n),DateOffset 子类,表示可能的 n 个工作日。
" c: [, d! M# F' C
7 D. n4 Z7 ^. _; ]4 dpd.Timestamp('20200831') + pd.offsets.WeekOfMonth(week=0,weekday=0)
1 ~2 F4 B' g1 ~/ ROut[82]: Timestamp('2020-09-07 00:00:00')
l$ x/ y) t! u% Z+ Q1 ]2 O" m( A% Q0 F% ~* {7 x
pd.Timestamp('20200907') + pd.offsets.BDay(30)) l- j: ~$ ?. o' ~/ o v
Out[83]: Timestamp('2020-10-19 00:00:00')! ~0 I9 d- g6 L% Z
1) V' N: n2 I$ T! e7 R6 k- g
20 i' {5 n9 d- z2 h% M
3
8 }& \0 P6 u. z3 o E4 f& F2 R4
( n+ j- k5 m* c. N6 ~+ p5 Y5
6 F( H1 O1 j I4 T B7 G 从上面的例子中可以看到,Offset对象在pd.offsets中被定义。当使用+时获取离其最近的下一个日期,当使用-时获取离其最近的上一个日期:0 f# v) L4 I3 ], e; i. |5 h K. Q
- ]6 j5 f9 T# d4 @' R* o
pd.Timestamp('20200831') - pd.offsets.WeekOfMonth(week=0,weekday=0)
. Y) k& x. [3 f5 [1 a& q/ T7 sOut[84]: Timestamp('2020-08-03 00:00:00')
% v/ Z4 a* Z6 a
2 M1 m; X( y* ? t% H: d+ Gpd.Timestamp('20200907') - pd.offsets.BDay(30)5 k' X |! Y- q! ^2 J" d8 H; A3 Z
Out[85]: Timestamp('2020-07-27 00:00:00')
$ O9 q/ S, _ Q2 H6 ?, I7 `% N" K$ w: [" ^4 x
pd.Timestamp('20200907') + pd.offsets.MonthEnd()
7 `1 J5 N; l; m, M. p O3 OOut[86]: Timestamp('2020-09-30 00:00:00')0 q; q) ^! w% J, J$ R/ W
1
: y9 a. P4 z3 t3 f0 ]0 ]2
_) k. P5 k. t3 j( {$ }3 A) g3 p8 Y8 g) h
42 J+ s! T4 l# {$ M
55 ^* E: B6 U+ g, g9 [# V/ z9 e: D# m
6
! g) C2 Z% j" K. i8 C1 O% |7/ R3 r% R$ l. ~' o
8
% J b0 ?: g/ P$ o0 j- f$ Q& D 常用的日期偏置如下可以查阅这里的DateOffset 文档描述。在文档罗列的Offset中,需要介绍一个特殊的Offset对象CDay。CDay 或 CustomBusinessDay 类提供了一个参数化的 BusinessDay 类,可用于创建自定义的工作日日历,该日历说明当地假期和当地周末惯例。
# G8 P" ? C4 D8 V2 \& X* @ 其中的holidays, weekmask参数能够分别对自定义的日期和星期进行过滤,前者传入了需要过滤的日期列表,后者传入的是三个字母的星期缩写构成的星期字符串,其作用是只保留字符串中出现的星期:7 M; l O7 a# z5 v) n6 E3 R" L
3 g5 `5 t s4 s/ Q s9 U
my_filter = pd.offsets.CDay(n=1,weekmask='Wed Fri',holidays=['20200109'])
; i& Q3 O- W6 d5 G5 P" _dr = pd.date_range('20200108', '20200111')
# g( c T( g) \. ~- b. N& `# F( z6 q1 h+ v* q% c' G
dr.to_series().dt.dayofweek
3 ^3 {1 J7 \6 QOut[89]:
! w7 G( {7 N- u6 ` M3 P: b2 D+ K2020-01-08 2
3 _7 H% G$ c3 J- D# b7 A; f: b2020-01-09 3% h3 \( a( c( y8 [
2020-01-10 4
% m# C8 d1 n2 U. K4 [& F2020-01-11 55 c V0 V- A' z/ R+ _ J ?
Freq: D, dtype: int64, s3 @( X/ m' J4 O" `" \$ a
" h: R4 l5 x5 M9 R/ K4 L3 F. w[i + my_filter for i in dr]9 t' |2 h1 ^. O+ F2 B: C6 {. a
Out[90]: % S2 L" t% X7 n+ C
[Timestamp('2020-01-10 00:00:00'),, {# S7 K% u3 ]
Timestamp('2020-01-10 00:00:00'),3 a8 V4 Z; h, c( A5 p
Timestamp('2020-01-15 00:00:00'),
7 l/ A( F2 D+ f8 d# Q! G _2 ~ Timestamp('2020-01-15 00:00:00')]: e( G' i7 l+ C
4 x2 Q4 i% i6 }! m7 ^% W1( X$ e5 B9 ~) K8 _0 E, K) Z* |4 w
2
* J% X! u3 d# ^9 {, E33 s8 W3 u8 B: d- G$ s+ }& }
4
1 E, C7 O/ K5 i% i* |' F5& {/ \; X0 v2 B# F
6
5 V7 {. d# A. V6 U! a `* k4 X71 e$ f8 |) ~7 I
88 Z! d7 _2 b1 F$ }9 y7 ^/ C, q
9( d7 w A! |3 Z/ Q) E
109 m$ p" _3 J$ X9 t F
11' o# ^+ w% ~* K) {1 a1 a
12
6 u$ p8 A) I/ t$ z3 T+ t1 L13+ e- x% A. `( a
14
5 L* q9 n& D" F& f1 A! ~ s- S# T152 H) a+ j" Q4 @* k* j% I& h( z
16
! H7 [- V6 `; N! C% D5 [17
3 ?, U& h" |% J2 i' G& u \ 上面的例子中,n表示增加一天CDay,dr中的第一天为20200108,但由于下一天20200109被排除了,并且20200110是合法的周五,因此转为20200110,其他后面的日期处理类似。
4 W# u1 W+ l5 u; N* @: K S3 {) R1 p8 B3 q" b
【CAUTION】不要使用部分Offset
$ r! c+ A# O, K" G在当前版本下由于一些 bug ,不要使用 Day 级别以下的 Offset 对象,比如 Hour, Second 等,请使用对应的 Timedelta 对象来代替。/ T+ E* _! J* p7 a7 `; I
3 q2 ^, z) [& K* a10.4.2 偏置字符串
2 X6 B' h6 k6 W) h0 ?0 t) { 前面提到了关于date_range的freq取值可用Offset对象,同时在pandas中几乎每一个Offset对象绑定了日期偏置字符串(frequencies strings/offset aliases),可以指定Offset对应的字符串来替代使用。下面举一些常见的例子。
! z3 O! h2 M2 g( C, ~% b/ W2 m( H3 {$ y- ]# Z0 K
Offset aliases:pd.date_range函数中的freq参数,为常见时间序列频率提供了许多字符串别名。 也称为偏移别名Offset aliases。偏移别名列表点此参看(大概27个)。
0 _ A0 G& |4 N# Y0 d. y0 r3 q. ?* n6 ~- g" [' l, X
pd.date_range('20200101','20200331', freq='MS') # 月初
5 r' J* M6 S2 n# X& T1 S& yOut[91]: DatetimeIndex(['2020-01-01', '2020-02-01', '2020-03-01'], dtype='datetime64[ns]', freq='MS')9 U9 e# T! Q1 p% _' H
8 ?2 x# B5 L! Qpd.date_range('20200101','20200331', freq='M') # 月末
?/ M4 N9 W. K6 I6 d) ~2 `! z( kOut[92]: DatetimeIndex(['2020-01-31', '2020-02-29', '2020-03-31'], dtype='datetime64[ns]', freq='M')
5 ]8 [! Y0 |( n# U% ^, T9 l8 C
# n" ]$ x2 q1 dpd.date_range('20200101','20200110', freq='B') # 工作日+ }/ H$ n" p* ~1 k
Out[93]:
# g2 B- M; ` I1 Y+ I& K4 q$ NDatetimeIndex(['2020-01-01', '2020-01-02', '2020-01-03', '2020-01-06',% d* C2 O' A/ q g
'2020-01-07', '2020-01-08', '2020-01-09', '2020-01-10'],, k; E/ X" X/ [ {3 Q) F8 u
dtype='datetime64[ns]', freq='B')
/ Z) k, U5 j4 K
! H/ W- x/ \4 D3 g; V3 f3 L3 Tpd.date_range('20200101','20200201', freq='W-MON') # 周一
$ R, r( K- @2 HOut[94]: DatetimeIndex(['2020-01-06', '2020-01-13', '2020-01-20', '2020-01-27'], dtype='datetime64[ns]', freq='W-MON')
9 q" d, y6 w$ N" s$ z" N" G0 y/ T; ~* M) i
pd.date_range('20200101','20200201',
, L }4 }* `1 H# i$ g9 D+ c+ f freq='WOM-1MON') # 每月第一个周一# G* T* K% ?( T" R
1 U. s1 r' p; p/ c7 L" L( `
Out[95]: DatetimeIndex(['2020-01-06'], dtype='datetime64[ns]', freq='WOM-1MON'), m5 @5 k/ P, n9 h2 I
! v8 C- a( N1 ~( R+ Z' f% k' [1+ B5 `% \$ ~/ ?
2# _! S4 S: M: Q7 y9 A1 t5 a7 h
3
0 g# E" r' [! j& E4
a9 M* s0 N* H/ k+ B5
) M7 u5 ~; z$ ~. C' ]6
* e7 L3 ?9 E" c# p' Q. F6 H# e9 y8 h7
- W& i* {, {+ O( H4 `) U4 [8% r) V( K' r V
9: ^- q% O3 G: }1 @, }+ B/ `
10$ R; H5 P# H: \) l! i# s
11
+ g" ^8 N: Y" G. a- k12
- r5 \5 Z3 t1 b1 _13; r, t& L. B& a/ M& k% O- K
14( E1 n# Q0 w! L0 z: M- {; H9 d) K* m
15
8 I- S' R) n; Z- [) f162 q5 ~- @# B$ H; y" h+ Y
17' S7 D. ^0 ~: \8 x5 v& {4 F+ a
18
0 A5 L. ^2 `7 ~5 h- n2 V19
- v L& j* x0 W4 B, ?) U上面的这些字符串,等价于使用如下的 Offset 对象:
* G- Z% ~+ c; C8 L6 h9 w# b4 o) q6 \/ y2 n8 f# R* ^
pd.date_range('20200101','20200331',6 C2 D+ n8 Q& {2 Y
freq=pd.offsets.MonthBegin())
4 O4 R$ ?: E( ?$ j/ }% H/ A1 b
! z. B, ~. o D8 a) vOut[96]: DatetimeIndex(['2020-01-01', '2020-02-01', '2020-03-01'], dtype='datetime64[ns]', freq='MS'), X3 { g z. l" R8 j. [
$ \" L. E2 |4 d& _ Upd.date_range('20200101','20200331',
8 G) Z! p1 x, I. p freq=pd.offsets.MonthEnd()), {+ v$ ~0 E8 H9 P9 C2 N$ e
+ y& y4 I% ]$ [+ ]
Out[97]: DatetimeIndex(['2020-01-31', '2020-02-29', '2020-03-31'], dtype='datetime64[ns]', freq='M')4 m3 E1 d% {+ d' @' t, r$ b
) K- C) R- _/ H
pd.date_range('20200101','20200110', freq=pd.offsets.BDay())
2 M+ E; c5 m3 R* E) \$ [0 D5 TOut[98]: 5 ?- K7 d) n: A) L5 f, Z3 V
DatetimeIndex(['2020-01-01', '2020-01-02', '2020-01-03', '2020-01-06',
: m2 S4 d8 ~5 G. ~, {* T '2020-01-07', '2020-01-08', '2020-01-09', '2020-01-10'],
^# m. t0 Y* W7 d* N! I dtype='datetime64[ns]', freq='B')
9 z3 {9 P, W0 k) g1 W9 D' ~2 ` v* C% W# z
pd.date_range('20200101','20200201',
0 b0 M! w- O* G2 k1 w8 s% d* H freq=pd.offsets.CDay(weekmask='Mon'))* ^/ e" T# V( @) [5 h
9 j. h" K, J8 }* y! GOut[99]: DatetimeIndex(['2020-01-06', '2020-01-13', '2020-01-20', '2020-01-27'], dtype='datetime64[ns]', freq='C')+ k$ R ~/ }; p' ^, p9 F, n! n/ V
5 Y+ [& ~, j# Z3 T- h1 `
pd.date_range('20200101','20200201',
7 x" S$ l4 V- a, u2 _$ D freq=pd.offsets.WeekOfMonth(week=0,weekday=0))
0 L- _% @" ] {, r- {
/ n/ [( x* C, G: ^Out[100]: DatetimeIndex(['2020-01-06'], dtype='datetime64[ns]', freq='WOM-1MON')
& f @$ m6 [1 [
% \& B# W9 |6 I- q12 a- B* c* O4 o7 d1 [ i
2
+ ^/ m& c0 `* u) V/ C' d3
2 L; G# Z% I$ I4 S/ r% s9 `4
/ T8 L+ `4 `: l( a/ U) p M5
0 R! `/ l& q% V) R+ N( a6
4 ]5 Q0 n# Y. T, G7 D. `& l+ r& P1 S) W- T# }: G* W
8) t7 M9 \* i M7 {5 O
9
# h/ i3 _+ q' e10+ a% s V9 l( N6 |, T' c/ M
11
( V% |7 Z2 t- P% b1 w3 @4 }7 x0 M12+ d1 O8 M( l5 t( }
13
5 R1 |" Z4 u0 \. @14
0 ]! t* ^ y7 _! ^- \154 f% O, o# `/ K
16
+ {/ f, {; `; @* `17
. H+ h( x" Q6 L. y; D& r18
* G2 s! k$ K4 |19
8 N3 m( O! b% Q3 P! ~20
! |1 \; [( g0 V; [21
2 H( @9 Y; }2 `22
# n+ _- g0 h# `230 d, U. ~: Y4 N7 }1 w- j
24
/ e8 m& O+ Y; Q250 l7 h, V& C# [* e
【CAUTION】关于时区问题的说明4 K J" \3 p" `2 [2 [; ]
各类时间对象的开发,除了使用python内置的datetime模块,pandas还利用了dateutil模块,很大一部分是为了处理时区问题。总所周知,我国是没有夏令时调整时间一说的,但有些国家会有这种做法,导致了相对而言一天里可能会有23/24/25个小时,也就是relativedelta,这使得Offset对象和Timedelta对象有了对同一问题处理产生不同结果的现象,其中的规则也较为复杂,官方文档的写法存在部分描述错误,并且难以对描述做出统一修正,因为牵涉到了Offset相关的很多组件。因此,本教程完全不考虑时区处理,如果对时区处理的时间偏置有兴趣了解讨论,可以联系我或者参见这里的讨论。& O( o1 `# S4 ~) o
7 m6 v0 B6 V O. N7 V. P0 y6 y, F10.5、时序中的滑窗与分组
8 a$ M E. E' L/ g! ^10.5.1 滑动窗口* z z% @# Z5 r- ]
所谓时序的滑窗函数,即把滑动窗口windows用freq关键词代替,下面给出一个具体的应用案例:在股票市场中有一个指标为BOLL指标,它由中轨线、上轨线、下轨线这三根线构成,具体的计算方法分别是N日均值线、N日均值加两倍N日标准差线、N日均值减两倍N日标准差线。利用rolling对象计算N=30的BOLL指标可以如下写出:
8 o5 P' H$ S0 Z. V( W' I& r! S X; g- T" z
import matplotlib.pyplot as plt# U) i" ]; C% C a, F
idx = pd.date_range('20200101', '20201231', freq='B')
; H( K4 { a6 J5 t7 K3 Inp.random.seed(2020)
6 I$ \8 w* k/ Z. g/ D' b# P. n T, l" L0 @5 t, w
data = np.random.randint(-1,2,len(idx)).cumsum() # 随机游动构造模拟序列,cumsum表示累加: u: Z0 F% r) l1 N& E# d" h1 n5 y7 G0 O
s = pd.Series(data,index=idx)
r: e; {* u3 C: c9 }" k1 Ms.head()
0 q# M) `* G/ |8 l& N9 C& r MOut[106]:
2 i/ J6 f0 Z2 U) l2020-01-01 -16 M8 ] Z! j5 G6 m5 s
2020-01-02 -2
0 F" o5 w5 c$ l, ]2020-01-03 -1. S `& r8 t {. a3 o
2020-01-06 -1
+ V7 i U) B! c4 d: V( p3 e4 B2020-01-07 -2
1 j {( N/ z; ]5 [' ~( D2 JFreq: B, dtype: int32# Y& ?% x5 f6 q; T* f
r = s.rolling('30D')# rolling可以指定freq或者offset对象( x, S; }! y9 C$ V) o
; \ u7 `0 `) _; j1 {
plt.plot(s) # 蓝色线
# _3 N, m2 z/ V% DOut[108]: [<matplotlib.lines.Line2D at 0x2116d887eb0>]
) [: D& o& m7 q7 ~ U! Rplt.title('BOLL LINES')
" Q) k: D8 w; oOut[109]: Text(0.5, 1.0, 'BOLL LINES')
* F' O, d( [5 ^+ H0 c+ M
$ P% I$ S" z, [, e+ [plt.plot(r.mean()) #橙色线6 v2 u O0 O( D' ?5 z0 H) p
Out[110]: [<matplotlib.lines.Line2D at 0x2116d8eeb80>]
9 B- o9 Z1 k9 b; R8 A7 o: c, R6 |2 x, c1 J- @- W1 z& N3 E
plt.plot(r.mean()+r.std()*2) # 绿色线3 X' _) ^ r' x% ]/ |4 b w
Out[111]: [<matplotlib.lines.Line2D at 0x2116d87efa0>]
& F# N/ v# B$ _1 N$ X9 I' p; h! [5 `' f4 N4 x
plt.plot(r.mean()-r.std()*2) # 红色线
0 w7 [8 L) T6 v% K/ x$ @# |Out[112]: [<matplotlib.lines.Line2D at 0x2116d90d2e0>]
; ?% _: j( A1 C3 `! i' F/ Q) J! v: w" x, a0 q# @4 q
1
' d& E0 ]# h' p' Y; [* L2 l' v27 \8 d: e: ]6 R- r2 Q2 ^! O6 ^
3% H; E" U- z3 _! d+ V( T' w% D
4' t* N( B) W6 _. H& ?* i
5
& W' _3 n- K$ ]2 `2 g7 N6( r$ W; f K# t$ ]
74 a% s6 r8 _; p, Z, Z& s
8) T! N6 g7 D# j5 t, U& A
98 s' M# L6 o# I9 q i3 \
10 f y1 s" N& j& x
11
, {9 F3 H) e, j: @* w. r" `12. p! F7 P, N% L
13' |' b( T( N: y- n: C
14
- A$ ^- ~+ T# t157 i! d) T/ Z7 |6 a R8 h6 S
160 Z) K( Y/ V+ t0 l
171 x; F5 s3 q" }$ m
18: O9 r. R3 I$ |; H
19
F/ X5 C" h7 G6 C20
/ p& H {2 ~: [2 |" d3 _21
. O/ m& G" x. s0 O22
' p' T. w/ S( w8 y6 Z237 |! x6 K. w0 p6 y
24
- e$ g* L$ }" A( S1 P( y1 L5 m25$ ~+ U2 }2 [+ P6 n3 J: N
26) Z; p1 }9 B7 q( q1 i) G& s& ~
275 `9 E. g1 h( C6 S. n5 H
28
( ~ n! Z& F4 ~" B: i7 m/ x; H9 k$ ~29+ x; X$ w3 I3 h4 |) t% _
) z$ [0 } l4 E, X! y 这里需要注意的是,pandas没有实现非固定采样频率的时间序列滑窗,及此时无法通过传入freq字段来得到滑窗结果。例如统计近7个工作日的交易总额。此时可以通过传入多个函数的组合来实现此功能。
# u3 J6 I% o* q* @ 首先选出所有工作日,接着用普通滑窗进行7日滑窗加和,最后用reindex()恢复索引,对于双休日使用前一个工作日的结果进行填充。
: P+ p5 ^8 g2 j) M& U, N u$ p, [. D! R. z9 e
select_bday=s[~s.index.to_series().dt.dayofweek.isin([5,6])]- g5 p# G5 h5 c- E
bday_sum=select_bday.rolling(7,min_periods=1).sum()
& k( _2 z2 z4 ?, gresult=bday_sum.reindex().ffill()) X( p# ^5 w& X9 @9 d
result
) z1 i8 q8 N/ X1 Q1 a f
% ]0 _7 s n, i3 g3 J3 E2020-01-01 -1.0
/ z- I5 O+ t: M) h! p% Y2020-01-02 -3.0$ y3 t" X# M" a: e! c( |1 B
2020-01-03 -4.0
3 \/ B9 o/ i* w7 H& ^$ g& |2020-01-06 -5.0
( s. S- W/ b: G8 A% D2020-01-07 -7.0+ I1 j5 P$ [; b4 {& r5 y
...
$ f; p1 s% C* ]# D5 K" X2020-12-25 136.0( t, w0 U6 c! `5 e$ F" |7 L; b' z
2020-12-28 133.04 Y$ r8 A7 ~8 f, P) {6 H
2020-12-29 131.0! M1 e$ |% v( U2 Q; y( E# h
2020-12-30 130.0
; [% y- a7 L- P. I3 F2020-12-31 128.0
2 B% W/ C, E5 |) M; ^& qFreq: B, Length: 262, dtype: float645 k% b9 q9 U7 O0 M0 ?, G
' e; X- M& @/ F2 s, Y3 z5 ]1" J+ l$ ~; o: b7 B
2
- Y- P% I j* [! ~/ q) g3& W& ?3 Y* [4 `/ G- q
4
F+ v/ a2 W5 ^" o5
4 I9 ^) c1 a5 E: \, X64 j& g# I! r# o8 s9 P5 _
7
3 R4 }/ x! X7 U# J, f/ U8/ S/ R* E' @$ N( |& `& U, x
9
3 l* X1 u( g C" G3 c10
" g0 L0 k& I: S11( ^) K5 o* A! m1 I" i6 }& Y4 P
12% ^ V- L0 S/ V# y
13
! H$ M' k' l" b& q0 d; T) [149 ^, z# R! \; A: {/ A
15
' z, [& n) u2 ]3 d% P# ]4 Z( t) s& x1 R16
- ?3 C+ H3 c O6 F5 K174 j, D7 f, M: h
shift, diff, pct_change 是一组类滑窗函数,它们的公共参数为 periods=n ,默认为1,分别表示取向前第 n 个元素的值、与向前第 n 个元素做差(与 Numpy 中不同,后者表示 n 阶差分)、与向前第 n 个元素相比计算增长率。这里的 n 可以为负,表示反方向的类似操作。8 b! n; w! K9 M! H
3 ]* Z2 g O+ ], w7 I: _. g- n 对于shift函数而言,作用在datetime64为索引(不是value)的序列上时,可以指定freq单位进行滑动:
: F: [( K' j+ f4 U2 p. @# F8 c: |2 A( P4 ]
s.shift(freq='50D').head()1 d5 m2 ~* ]/ k7 E7 o
Out[113]: . K$ h k+ G0 O
2020-02-20 -1
* C: G5 s1 B% C+ |! I2 z2020-02-21 -23 K6 M! u7 u$ f; H. h! V
2020-02-22 -1' ]( e4 E/ q7 C" x4 y% b
2020-02-25 -1 ^/ f4 N u. D2 a( j* T! w% m
2020-02-26 -2# L3 ~! m7 H: l g( `! u1 K5 ?
dtype: int32
6 b$ p9 |' |7 B9 ~$ z/ e8 |2 }1
; |: k2 D5 R% j5 e: h6 D& x6 N" ^2
3 C: p+ m: P2 ^3
+ \( I5 o' R' Y# b4( b& X7 v/ L7 B- V5 Q" s
5
( N3 _: ?7 Y- l& K" A# _6
2 B) T' \& D3 o1 \7
2 f' M* g* w5 \- R0 g( {0 d81 \8 S3 Y' D$ L& ?
另外,datetime64[ns]的序列进行diff(前后做差)后就能够得到timedelta64[ns]的序列,这能够使用户方便地观察有序时间序列的间隔:8 B2 F' y1 ^0 ^: a
6 \& \. ^' w' ymy_series = pd.Series(s.index). y6 H0 n. x8 A2 T1 p5 T
my_series.head()
B' `# i8 W) z& o& G$ G* @2 Q6 TOut[115]:
, _* }5 `$ L- j3 L0 2020-01-01
/ ?/ ]* z0 L( o# T, \7 X% N1 2020-01-02- h: l3 M% \" P' A
2 2020-01-03
) e3 a4 P$ d$ r# H+ m0 U+ [3 2020-01-06. H. F# e' |* r5 w4 v
4 2020-01-07
4 |# I/ l0 X+ U" ^0 v, A# |dtype: datetime64[ns]
- H" D/ Y7 {) t) B! V( P# D7 F6 ^" ~( I7 B* X& C6 E& B
my_series.diff(1).head()" Q, d. O$ W7 P* A( k( E! W; c8 n
Out[116]:
5 u% T l, @1 i g, J+ c0 NaT) Z" {7 ?2 O5 L8 {% {
1 1 days
a. U) a2 v7 P2 1 days( L7 a9 |" j) Z ^' ~
3 3 days
2 y( b' c8 a4 }+ d m6 v; g |4 1 days* i9 J6 s. _: u% B6 Q0 x
dtype: timedelta64[ns]
: e( E3 h2 |& S2 Z2 ^! {8 P
: k- i' a0 `* `6 h' h: A" P1
8 W8 X. _- d8 B% s2: d9 d+ K2 ^) V/ d' ^
3
2 t6 V( V$ [, }4 N" U) p, {; w4
* d& t( L0 Z' i, M5. _5 I3 E* A6 s* f3 Z
6- h( c/ Y$ f+ \+ ~& d& p H0 P
78 j: V9 X# R" n/ [0 v) T" U. g
8# Q: J+ A! v' V3 r& v8 `" z, p, [
9
4 \$ A, z% H: H3 {4 G10( o! E6 ]. T' x" T6 t0 D
113 H1 @0 T- H+ L, F9 v% t
12" T0 p6 J1 @3 m5 s
13
, l4 B* \- ?. x/ T9 V, n- J" `14 D# t+ r4 R8 J& g0 {1 H
15
# o: w% o0 O; a: ^8 z) T* y16; Q1 H+ \7 N& D# O' ?$ E
17: y/ ]1 ~' v4 }9 C$ Y
18
2 X/ X7 A3 I$ F10.5.2 重采样$ r, a+ }- r; [: }9 a& X
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)
( P. I8 P1 C& I, d; I! t- P常用参数有:" g- s5 O" p# |5 x3 j, o2 B
- @; I v- ~: o; B* b" X7 w" z
rule:DateOffset, Timedelta or str类型。表示偏移量字符串或对象
) x/ s! n8 S! J- caxis:{0 or ‘index’, 1 or ‘columns’}, default 0。使用哪个轴进行上采样或下采样7 Z% y/ N2 z/ `9 z) ]) l2 r+ B0 f
closed:{‘right’, ‘left’},默认None。表示bin 区间的哪一侧是闭合的。所有offsets的默认值为“left”,但“M”、“A”、“Q”、“BM”、“BA”、“BQ”和“W”均默认为“right”。5 d3 l8 }% I" C( X/ ^
label:{‘right’, ‘left’}, 默认 None。hich bin edge label to label bucket with,所有offsets的默认值为“left”,但“M”、“A”、“Q”、“BM”、“BA”、“BQ”和“W”均默认为“right”。 l- r' {. w- z
convention{:‘start’, ‘end’, ‘s’, ‘e’}, default ‘start’。仅针对 PeriodIndex,控制是使用rule的开始还是结尾。) v8 j# }0 d; r1 O. j
on:字符串类型,可选。对于 DataFrame,使用列而不是索引进行重采样。列必须类似于日期时间。& u; [; \4 B* ^
level:str 或 int,可选表示多重索引MultiIndex的级别,这个级别的索引必须类似于日期时间。) l' A5 K b$ J- v/ g9 h- X5 a
origin参数有5种取值:9 K0 S/ P7 e; \0 i2 J* A
‘epoch’:从 1970-01-01开始算起: t2 \4 u* Z+ x+ s" z% w; w
‘start’:原点是时间序列的第一个值
# n0 o7 l3 Y" ^ \7 M‘start_day’:默认值,表示原点是时间序列第一天的午夜。& ~8 J8 T' ~$ e3 \
'end':原点是时间序列的最后一个值(1.3.0版本才有)
2 j% @" x. d' g: s' A# @+ p‘end_day’:原点是序列最后一天的午夜(1.3.0版本才有)
2 K# V. p" P: ~; R! b: p) K" I2 @2 aoffset:Timedelta 或 str,默认为 None,表示对时间原点的偏移量,很有用。
, l1 L# c/ y% {* {; L closed和计算有关,label和显示有关,closed才有开闭。+ S" C2 R0 j2 q2 Z9 e5 A
label指这个区间值算出来了,索引放区间的左端点还是右端点,closed是指算的时候左端点或右端点是不是包含。9 r1 z. {& J l# M
5 u" {4 C# U! f1 {: C+ z
重采样对象resample和第四章中分组对象groupby的用法类似,resample是针对时间序列的分组计算而设计的分组对象。例如,对上面的序列计算每10天的均值:0 O1 d' z6 s+ _% J+ O: h: {5 c# u
s.resample('10D').mean().head()
& L7 N( y8 x& j( DOut[117]:
" F* I Z z& x9 [2 w3 \5 r0 L8 X2020-01-01 -2.000000* ~6 t* c" \& r( V
2020-01-11 -3.166667# K: _9 F8 W9 U* n
2020-01-21 -3.625000
4 h- H7 U9 K+ f2020-01-31 -4.000000
9 o0 f$ d" ?) R' H8 \$ G) j% L1 ]" L2020-02-10 -0.375000
2 } C+ t0 b; x3 G1 u' r% F* sFreq: 10D, dtype: float64' v2 Z8 [$ w5 D* T) }$ _
13 L8 ~ n8 Q7 i" V
2
2 @7 p$ ~7 C4 t( T3) |7 j4 @3 c* R
4' R6 H' y0 \. x4 Q1 x$ E. ?% k
5
! L, h/ D, a1 S# I* E: [6
. T6 |9 e' Q; f7- p) O' m0 K3 |
8
: r Z: t" M+ T可以通过apply方法自定义处理函数:9 ]1 ~. U' `6 W* n# \
s.resample('10D').apply(lambda x:x.max()-x.min()).head() # 极差 d# H/ Y7 s% H2 {9 S# R- ~
+ D" \7 D! A- f% ?9 M8 TOut[118]:
/ t Z# t0 a5 k/ j9 [+ ^2020-01-01 3
# B, g' u4 F Y4 [2020-01-11 4 ?6 U" L) Q5 |# I
2020-01-21 4
; b: R2 d! F9 A0 b' K8 r2020-01-31 2
M+ C- m4 x; x2 I o |2020-02-10 4( g$ y, }6 @/ C4 m+ l7 d7 K9 W
Freq: 10D, dtype: int32
2 @$ F# g( K6 @' F; q( x1( r6 Z6 z2 @# \4 _% W
2
( N1 T0 W/ j. Y4 A$ c3
' I. c: y3 E0 _: p7 h6 J47 v2 U/ Q! m' Y
5
& N% y/ z4 c6 Q. s/ L/ z4 ~- ~6
( w3 F! Q _; [7
6 N4 m% m0 g1 J2 L7 P: i8# N4 ?% r! `7 c
93 c ?7 V3 r6 Z1 M) W3 Z2 Y& a
在resample中要特别注意组边界值的处理情况,默认情况下起始值的计算方法是从最小值时间戳对应日期的午夜00:00:00开始增加freq,直到不超过该最小时间戳的最大时间戳,由此对应的时间戳为起始值,然后每次累加freq参数作为分割结点进行分组,区间情况为左闭右开。下面构造一个不均匀的例子:
1 p0 l8 X% R9 |* w
. L+ v0 R5 r8 v/ f5 \, R' c) Iidx = pd.date_range('20200101 8:26:35', '20200101 9:31:58', freq='77s')+ z1 S% J7 ^- u! C2 P; w
data = np.random.randint(-1,2,len(idx)).cumsum()- X- Y% |- d7 c* a2 o: ^3 O4 j* q) K
s = pd.Series(data,index=idx)
8 v9 S+ \# ~& |" U( r, y9 fs.head()3 N; p, O: Q, u3 r% n
$ o; l8 {+ x) E2 A/ d2 DOut[122]:
* T7 K( n, |) u/ B2020-01-01 08:26:35 -1
. b- U7 c; D0 b4 Y/ g* x8 T2020-01-01 08:27:52 -1
. I5 V% p3 U% e/ C. w: b5 N4 G2020-01-01 08:29:09 -2
5 y& k# X! `( p) g! j2020-01-01 08:30:26 -3; D% i+ m" U8 N& T
2020-01-01 08:31:43 -48 ?* V% Q; M* D" n5 \! G/ u
Freq: 77S, dtype: int32' J5 o7 \; ?; H( W6 P6 O- \) V
1
! {$ O2 B) t: W! t' d) a3 j0 e/ U+ B2
/ g! G7 Z8 F9 }* o3 l* G/ o7 @; Z" f
4
1 U, I2 L! E, p8 ^6 H+ ~6 w5
; o' D/ v* C* I$ s6
( A, y, |4 h( r' j+ L5 Y5 v* D7
. `! Y0 u+ d- {& j6 [8+ _' B1 q. }$ b. [, H2 n- k1 p
9
' G. @7 a% C% u9 X8 M1 ]. ]1 {10
) t. V) l [7 w119 \) ~; Z. ~' X6 {
126 a q& e$ |/ k2 W" Z4 @
下面对应的第一个组起始值为08:24:00,其是从当天0点增加72个freq=7 min得到的,如果再增加一个freq则超出了序列的最小时间戳08:26:35:
5 T7 `3 C* V0 Z( U, N# \$ R9 J4 Q$ l, l! c, a" p# h+ b
s.resample('7min').mean().head()
7 _! R+ F/ e; y; [% I8 Z, i6 uOut[123]: + Q: o" n' K/ f( J5 V7 X
2020-01-01 08:24:00 -1.750000 # 起始值,终点值包含最后一个值
& m/ \/ [) t2 [- [% Y0 Z2020-01-01 08:31:00 -2.6000002 x$ e1 B5 K$ D; _1 T" b5 H/ ^! ]
2020-01-01 08:38:00 -2.166667
7 f% L: I* f, S7 o2020-01-01 08:45:00 0.2000007 G; T/ C' p+ u/ q9 h T9 p
2020-01-01 08:52:00 2.8333335 F+ u3 g3 \( e
Freq: 7T, dtype: float64. e0 P+ s! M+ T$ I6 J
1
- E3 d% R/ K3 X2
+ k) _+ [; a5 S- K7 l- w0 i3. R5 A" X" w8 a" t2 l" D/ h3 W
4
1 c+ o% v; l/ S' W' c& |9 k0 m' J5
/ c9 b3 X9 ^# P7 b9 V+ o6
, ^* p$ ?: B1 b5 A7
3 v5 w. _3 r0 G0 W) v/ }2 y8
$ Z) E) j4 m3 f c 有时候,用户希望从序列的最小时间戳开始依次增加freq进行分组,此时可以指定origin参数为start:
5 m$ P) P3 v5 h6 x1 Z
1 G. {+ R4 y' o7 {% |3 V: W+ Ws.resample('7min', origin='start').mean().head()
$ P0 v9 A: ?* l7 _0 J+ F$ A; lOut[124]:
) ]6 X0 k2 s: g* k/ ^. F U8 L# j% r2020-01-01 08:26:35 -2.333333! o- n9 t) G+ Q4 b( N2 N5 {6 B
2020-01-01 08:33:35 -2.400000; t3 l! U& q( O6 B' A
2020-01-01 08:40:35 -1.333333
" S4 a7 Z" j7 S6 A8 d4 D9 t4 @2020-01-01 08:47:35 1.200000
2 Y, [5 @6 P6 }. v2020-01-01 08:54:35 3.166667
' Q x" H+ A5 F2 X1 qFreq: 7T, dtype: float64# d; R7 z4 o& Y
16 y/ O5 [; S$ b6 H. t/ S
2
! G' ]& Y- {, B% R& H3; \: Q$ ^% \5 b& h2 X
4
+ z. [; [- L$ Q5" S2 Y- S+ o, c2 F; j8 y% ^
6
/ j. k/ D# H# O9 Q9 o7 `$ j1 ]0 ^74 N1 c, Z% a& H" Q. x% I
8( ~7 L- o6 T' ]% }
在返回值中,要注意索引一般是取组的第一个时间戳,但M, A, Q, BM, BA, BQ, W这七个是取对应区间的最后一个时间戳。如果想要得到正常索引,用’MS’就行。$ m* s3 R5 o3 \9 R2 k' F
9 [5 W' r: R/ N
s = pd.Series(np.random.randint(2,size=366),
^+ y, |" ~; p0 C- ^8 q o index=pd.date_range('2020-01-01',
' b# a4 s" T/ O x5 h '2020-12-31'))% J& I: w* T/ E+ P) Z* C H, f$ L! g
+ T- j5 i# T0 B6 I) Q
* ?7 m7 Z9 f4 xs.resample('M').mean().head()5 `1 v0 ]8 W2 v
Out[126]:
, z8 Z3 |/ j! E% U/ r2020-01-31 0.451613+ r3 u9 P% Z! G
2020-02-29 0.448276
$ W# f" H `5 ?/ o+ z8 ?9 \2020-03-31 0.516129+ P- r: K. l3 x+ `! Q. x& `+ k
2020-04-30 0.566667; y/ v! O- i) ~3 f/ @$ f
2020-05-31 0.451613
: H$ M- S6 L: u+ z7 ~5 R }0 q% g' N1 PFreq: M, dtype: float64
3 s- Z0 Z. |) q H0 D. f- b: ]
/ T, Y' R' W. R$ X7 L+ [s.resample('MS').mean().head() # 结果一样,但索引是跟正常一样
/ [8 X- b; N' e# N2 gOut[127]:
" F: q$ W6 I6 `, }: B2020-01-01 0.4516133 U/ ?! g; `, n: q. O5 x, G
2020-02-01 0.448276
- d5 a0 T0 d, t, L# ^, z2020-03-01 0.516129& O5 c- Q% I/ K6 U
2020-04-01 0.5666672 B C; L) W9 p9 n
2020-05-01 0.4516138 n$ X# ^9 b" ~1 M3 _# y
Freq: MS, dtype: float64
' u/ E7 G% R$ l3 [0 g7 J1 h ?0 t6 }$ l* `7 V9 Q2 V
1
9 n$ H3 b) g1 l, U# ~! `2
/ \4 t' }, V+ @3$ p2 V# V' J& o$ g) t5 Z
46 W: [' S* I+ b3 d
5
& T$ Y ?+ S" x1 L2 g! ]( P- g6
; ?! D. x6 x0 O% t5 H' y7 ^% F7
$ Z0 \( p9 C7 r/ X6 B8( c- ~, X' C" l0 q) p0 P) y
9
' n9 ]9 K8 k7 [) w& H10
; {! W9 d5 Q$ v/ n& J* I$ w6 @11! A% N# G+ M! j3 S+ _
12
8 h# V) L2 P& R% I, P136 b- K" `% G2 q2 q+ b s; |
14' Q3 g6 K E ^9 d3 J/ Q1 R0 g
15. ]# k& E2 U5 u. w
16
, Y* M$ B( }$ h U& F17) n5 i$ c/ W. G/ Y
18$ K8 G" m, |# G
19
- w0 j0 R5 A1 G% ~* s203 b! Z# q2 G' g( b. [" M
21
9 }5 X- V* L& z9 d; q22
6 D0 Q! f9 D" ~4 M4 l对于 DataFrame 对象,关键字 on 可用于指定列而不是索引以进行重采样:
7 Y$ f" P6 B# ?* c) d- ^d = {'price': [10, 11, 9, 13, 14, 18, 17, 19],% j' K8 ?1 I$ W( I S" Y! w; \) V
'volume': [50, 60, 40, 100, 50, 100, 40, 50]}
7 P& M/ I0 _ udf = pd.DataFrame(d)2 w3 h2 G; `( |: J( u8 ^- `
df['week_starting'] = pd.date_range('01/01/2018',7 I. [% t" U) Y2 B) U/ W
periods=8,
/ `/ F0 N, N4 b freq='W'), t7 @: Z0 I. O) C* s f7 ~
df9 H- @6 u' S" t. N$ X5 v
price volume week_starting" m/ f, h. @9 b$ H; h
0 10 50 2018-01-07
; `* _# }$ _! V+ s1 11 60 2018-01-14% \% U: [8 @) T: X/ f
2 9 40 2018-01-219 q# ~& |4 @9 V( X& E
3 13 100 2018-01-288 K; @7 B8 k- F4 `* v
4 14 50 2018-02-04
' K! E' E# Z, K N$ M5 18 100 2018-02-11
1 q9 z6 ~+ K: p1 H6 u6 17 40 2018-02-18/ L1 [8 c( d. e, k
7 19 50 2018-02-25
# V! _, `% d" g4 ?/ q: Q/ [df.resample('M', on='week_starting').mean()
1 }4 Z; ?9 h L price volume
& d* }( z( T3 U ~week_starting
, @, ^/ Z' h/ B- [4 T2018-01-31 10.75 62.5
g3 u* f# S( L! ?1 B. i% ^2018-02-28 17.00 60.0% f% Q; v/ }/ G9 W% ^- w: }7 `
' u7 l. T6 Q, [% J3 K& t* E* f9 L8 z5 r
1
% Z6 H3 U0 ? C" w2+ u0 b5 j& K) m: h; j
3
& d& n- {. z1 m3 |' M4
4 X1 i5 p5 N. K6 y% T5, E( [$ G! v, j4 s. k
6: b; ]& O5 ]1 w Z6 w1 R0 f
7, Z( i6 j4 T* K+ u; m5 @! R' R
8
- d- Q2 R- V) P% U& z W/ S9
3 {. B% @! W, I5 K10. E" ]$ @2 i# S# ^4 n
11
( Z s* q2 J) p6 t: }+ }12 V# }* E: o# {+ u
13
: m# A: f1 `& w, X142 G7 a' T3 f% _8 [2 m
15
" W9 }0 ?3 l1 k) s1 l6 _7 h+ F) q) K166 c2 Q3 h# j, Z2 U$ S7 l" P# f- o
17
! h5 C2 E+ d& Q) b18; T% _1 a' K4 z% x c5 A5 t% Q
19# {5 w( a5 n! w0 @" J3 d# W
20$ D3 m0 u$ j; ]" q$ U" E
21. c4 B0 V. p0 {+ E' v7 i* B
对于具有 MultiIndex 的 DataFrame,关键字 level 可用于指定需要在哪个级别进行重采样。7 Q5 Q" y6 \4 x$ Y
days = pd.date_range('1/1/2000', periods=4, freq='D')
: Q. y% o8 ]2 [" x2 l6 W8 @3 i3 o" {d2 = {'price': [10, 11, 9, 13, 14, 18, 17, 19],
# T3 B `( F$ X6 B. F 'volume': [50, 60, 40, 100, 50, 100, 40, 50]}, u6 Q2 d8 k9 {+ {6 ]
df2 = pd.DataFrame(# t* a$ N+ F( e9 w1 h3 {
d2,
9 X9 s. C( o) r" T3 [ index=pd.MultiIndex.from_product(
, i% [; a+ `/ k2 c' N [days, ['morning', 'afternoon']]
q* D! V/ w1 O& N )( d$ `1 K6 D# @* V x. y/ X; T
)3 k" `; e, Q7 J9 H9 O0 h& j% c
df2( |. a6 h5 a: q2 o \( P1 W
price volume2 @1 H" t5 Y* b
2000-01-01 morning 10 50/ h9 |1 M5 O9 t: I
afternoon 11 606 r3 G5 n" g' {9 V
2000-01-02 morning 9 40
" }1 x( j5 [ l afternoon 13 100
' J+ [5 K: a8 v" T/ @% Q2000-01-03 morning 14 50
/ U: v4 H) \- D# ~ afternoon 18 100& P U' B8 e! j2 a9 k9 P) C5 {
2000-01-04 morning 17 401 C# K; o# g; a& e9 `- B! @9 S Z9 \
afternoon 19 50- P5 s5 i6 f' P( P* h5 P0 r8 g
df2.resample('D', level=0).sum()4 ^: S/ d9 E) a$ ]
price volume
! i( W9 T+ a1 k% O8 D2000-01-01 21 110
3 g$ a H2 G8 u2000-01-02 22 140
$ C3 a9 I4 l h* s9 E' H2000-01-03 32 150
5 ?0 \# d! u. z# s R9 r2000-01-04 36 90
8 o m$ p. E4 i8 m' C7 W# c# N) `1 V, z) T0 D
1
& C) I, _5 @" Y- Y( X1 n+ Y2
) L8 T# N, c. }/ D; x% I1 y5 v* ~3
! N0 M% J, j1 ^# @! R; f, n! H4* M6 P1 u! d' C' y& \6 u
5
* @, K2 c9 U0 [/ S, `; \6& A1 V0 Y, v. J1 g7 f
7) W- ^$ p' z2 S2 C
83 M( v: e% V8 r) j$ U, M% X
9
4 @0 C5 H" i0 E" x) T10
3 s1 M! ?$ i# A112 m) L @: i) U
12& J- E! S1 P1 O2 T1 f
130 A# Q: h; P6 M: o( H2 v* H5 B
141 x$ e; A2 [$ f c
15% i$ V4 _& T! t: u) O; A; m+ |# L
16
7 r! |% C/ K* v8 e0 ?/ S0 V17* q% w1 O. ?* T" d0 D4 j; }/ ?
182 x2 ^& }# k4 s+ T" N; h
19 X7 ]& N) V6 ^; e- }% ^
206 A* \; ~ K, w# Z
21
$ @& D) C& X. R* B/ B22
1 G- |' b8 U$ X6 |+ H3 \/ H/ _8 @" Q23
6 H3 A! N$ P! I24' E/ B# T0 N& O" |+ g
25 k; F8 f% \7 E
根据固定时间戳调整 bin 的开始:
9 j, S& o* U8 J) T& ustart, end = '2000-10-01 23:30:00', '2000-10-02 00:30:00'1 i: Q+ D1 D, m, ~
rng = pd.date_range(start, end, freq='7min')7 t/ S' X9 X- o [: y0 Y$ [
ts = pd.Series(np.arange(len(rng)) * 3, index=rng)1 z/ G" B2 Q- ~" D d& A! E
ts- ]9 @ L M) k. V8 U- |
2000-10-01 23:30:00 0; k3 c9 B6 J' P- [, E# k& z$ V
2000-10-01 23:37:00 3" X" {( d$ w' E7 m# |9 Q" S' s) {
2000-10-01 23:44:00 62 ~, w V. P. f
2000-10-01 23:51:00 91 o5 m1 Z& h- k) H& P: |# X" ~$ o: X
2000-10-01 23:58:00 12
" S2 g) b' ?$ \" _- o5 _9 Q% [9 q2000-10-02 00:05:00 152 r, ~' d4 c( K9 d
2000-10-02 00:12:00 18
& ^- ~) F5 W% z, [' ]3 T" h4 j2000-10-02 00:19:00 21 B1 D7 r$ V. e8 ~' i# @
2000-10-02 00:26:00 24
! U D3 m0 y$ X1 _# N, ^Freq: 7T, dtype: int64) m3 W1 L- n. X/ k
$ w' ^9 A* a. J; s, r4 @
ts.resample('17min').sum()
) s% ^' R8 x+ a* v2000-10-01 23:14:00 0
5 ~* H+ m% U* X9 T2000-10-01 23:31:00 9
+ p% I, B" P5 t6 @( @9 k2000-10-01 23:48:00 217 [4 P* e5 f. ~9 [4 f
2000-10-02 00:05:00 54
2 Y3 U ^; B! t7 f9 |2000-10-02 00:22:00 243 n; j; c. F& D# {9 l- a
Freq: 17T, dtype: int64
/ _! m8 R1 w0 o+ n
1 g) }. T$ R! {& rts.resample('17min', origin='epoch').sum()
9 m+ ?7 L1 v. v% R. P _- C2000-10-01 23:18:00 0
2 S; h% u" G+ F3 `* N2000-10-01 23:35:00 18
# b6 i3 C# a3 ^8 V7 n, m6 H2000-10-01 23:52:00 27
. C7 s3 {) a6 q5 n2000-10-02 00:09:00 39$ t0 w, J- K @0 z5 I. |- [; {
2000-10-02 00:26:00 24$ O0 g2 |+ I) q& P- |, ^8 r2 x" `
Freq: 17T, dtype: int64- `8 o1 _# S' U2 x7 _/ Y
: H7 }; m& R6 T# `4 gts.resample('17min', origin='2000-01-01').sum()8 j( d7 ]' d3 x. v; [
2000-10-01 23:24:00 3
' ?6 s# h0 f7 t2 _, R! R- v! D2 }2000-10-01 23:41:00 15
. D# w; ~2 [% \% ?! O. _2000-10-01 23:58:00 45
/ c6 ^9 x2 Z3 x/ X2 J2000-10-02 00:15:00 45( t- I( ?3 v6 @( U" ]3 h
Freq: 17T, dtype: int64
' T v! d9 T m* X2 Q
?, n2 [: ?1 } B1: a) z" I6 S, k- E4 } u0 E
2
1 f- v% r6 [9 V9 g* \3
% ~. P! g% Y; h; ~4, v- A( E( E8 o3 U# w. P5 t' F! G
53 _$ l. h8 e( w+ t) o: p+ V6 w
6
; ?# |. d+ K+ e. \6 J+ w/ E7- S3 s$ @% x3 l- @* i; {- o2 |
8
% T8 q& ~. C( @: O4 h! T96 e1 S: F. v) m
10
* y3 A% e. j0 ~; F" P11* M0 ?: k. C, c+ }: I
12
2 S1 c+ U4 ]: C/ E13: }7 C% e+ o; r" {
14, V7 W- P# ?" f% W
15
! G0 y4 R9 o2 F3 _# @5 B16" G6 S, t r, ?4 V* T1 Y
17
, n4 V$ }0 }7 W2 e& |; P5 y18
; ]; W! L: i- z# \ D7 [* Q19
, w0 s" T1 G2 J/ c1 g1 ]; {206 W" ] H6 P/ E0 a. h- x
21
* x" P& `/ I- `2 w; F/ c7 b* w22
# F4 ]" F5 b8 }/ M7 u23
7 S! |0 z5 @- v$ Z24: I5 c: Q- d6 h' M9 a( j+ @
25
( v' V6 b( k9 f; l264 @) A1 b& B( _+ p0 j
27+ Q% b; `- l( d; H
28
- K1 y. A! r- y29
* k( }( `8 j$ p& @% T30
8 ]* M8 S) B- P/ P- j31
! r4 m8 @$ `( {( f; U# g2 A32" T8 @. ^3 N, o
33& _0 a% G7 L( u3 ^3 H
34
9 V* ] p7 h% c+ |; b" ^35
2 ?& A# C. a. e36$ |# g5 G5 d( F! _9 r
37: A5 F5 V z+ z R O2 n
如果要使用偏移 Timedelta 调整 bin 的开始,则以下两行是等效的:' S( H& m# T( b/ U3 O% F$ u3 ?
ts.resample('17min', origin='start').sum()
1 C$ i7 k) d4 P7 Wts.resample('17min', offset='23h30min').sum()
- E. S0 a; F7 b" R- X2000-10-01 23:30:00 9
5 m! }. l% }4 L! i% d( w2000-10-01 23:47:00 21# _, ?+ c/ y* K6 y% r
2000-10-02 00:04:00 54) X' b- g0 N+ p7 V8 A- \
2000-10-02 00:21:00 24
8 b7 i! R3 t" [Freq: 17T, dtype: int64
4 N' o; i9 [& p a& y1
8 X( O8 Q- X1 L, T1 z2
% _ |3 v# h4 b; z. s1 I# J* S# n8 X/ U3
0 I2 }" A' {0 x5 r, Z. B, T4
2 e2 I2 u: i# ^5# v" I7 |4 Z! G/ y
6
/ h; d6 c7 w4 C- R% W8 [& K$ H5 ]7
& L* r% q6 ~: [8 N* J% |10.6 练习
6 S" b2 ? h9 L$ n" P; SEx1:太阳辐射数据集
/ N7 [1 \) u% G现有一份关于太阳辐射的数据集:+ {7 V5 [/ K4 E% k9 M
4 a X) _$ y* L, Q: r7 }df = pd.read_csv('../data/solar.csv', usecols=['Data','Time','Radiation','Temperature'])- W; D" Z- ^- x1 O
df.head(3)
# V. U1 M- n% }- h- h: K8 I% z2 I: w& b! O
Out[129]: . V ?& M/ T4 d4 ~( T
Data Time Radiation Temperature
; P7 q5 D7 k: }( Q# U0 T0 9/29/2016 12:00:00 AM 23:55:26 1.21 48 l, ^% N; n2 @6 ?4 u) S
1 9/29/2016 12:00:00 AM 23:50:23 1.21 48
9 u" g( Q% T9 f' v S) J2 9/29/2016 12:00:00 AM 23:45:26 1.23 48; W9 S- `0 Q. y U; s
11 x$ e* Z$ D& C
2
r" O1 _7 ?; \/ H1 b3
: A/ @3 B9 c' Z' c3 n7 t4
2 a; P* j0 T) z; { p5
2 ^1 {* C7 f5 g2 s% @& O: i5 y6
6 u8 K! n$ a$ ]4 h+ D+ ^7
: g+ V) c5 `5 @3 x# t, B0 _8
3 y! f1 M0 c0 T C" Z( ~将Datetime, Time合并为一个时间列Datetime,同时把它作为索引后排序。
; }% p* L; K+ d* A( z) t6 p每条记录时间的间隔显然并不一致,请解决如下问题:
% o5 i1 Q0 A/ b$ i) O, a" h7 f找出间隔时间的前三个最大值所对应的三组时间戳。+ E5 x7 C( q* e _# V$ S. E) O
是否存在一个大致的范围,使得绝大多数的间隔时间都落在这个区间中?如果存在,请对此范围内的样本间隔秒数画出柱状图,设置bins=50。5 o s! @: a8 l- O
求如下指标对应的Series:2 t8 l$ p5 ]% S7 E/ I: |- |' S
温度与辐射量的6小时滑动相关系数
2 y$ y& E& X: t0 `2 D! r以三点、九点、十五点、二十一点为分割,该观测所在时间区间的温度均值序列
1 n( w- p, f! ] j7 F每个观测6小时前的辐射量(一般而言不会恰好取到,此时取最近时间戳对应的辐射量)
' a% A; D% b# ^% W& Bimport numpy as np: {$ c% O% V3 z; D0 F
import pandas as pd
# N/ l8 K- a7 m/ }1
. i% {8 K- n* f' v- w6 O1 D- t6 g1 q2
% S4 s: U0 p9 t( Q+ H1 \0 D将Datetime, Time合并为一个时间列Datetime,同时把它作为索引后排序。
) m$ g* Y* L- Idata=pd.to_datetime(df.Data) # 本身是object对象,要先转为时间序列& ]/ Q+ q6 ?3 @6 G7 Z. Z0 K+ E
times=pd.to_timedelta(df.Time)4 Q" e; F3 s- V5 g c, {
df.Data=data+times/ V( V5 V+ r7 s9 L
del df['Time']% p" V `! x: M2 p# ^
df=df.set_index('Data').sort_index() # 如果写的是set_index(df.Data),那么Data作为索引之外,这个列还另外保留- e4 X( H- I+ N1 N
df
H7 N' G' {# ?7 Y" ~. w Radiation Temperature
& N& I7 R! A( K4 [( s% ^Data
6 |& j: B& g4 M7 J; F& x4 Z# _2016-09-01 00:00:08 2.58 51# u. M9 d9 O. w Z1 g- z5 O
2016-09-01 00:05:10 2.83 51' s& @8 G% e) u3 d4 E' ~$ B+ f
2016-09-01 00:20:06 2.16 51
( s9 j4 ^* |9 v1 y2 i2016-09-01 00:25:05 2.21 51
" U& J M2 F( U/ c8 r7 M; I8 x% J2016-09-01 00:30:09 2.25 516 G& g( x" N$ _6 g7 D
... ... ...
! |( V( O5 q4 n2016-12-31 23:35:02 1.22 41
$ T- x/ e. Q. Y: s5 z+ z2016-12-31 23:40:01 1.21 41% O& p. v1 u K9 e/ k. F! D t
2016-12-31 23:45:04 1.21 42! d" W) T1 B! T# [1 _9 e1 J" `
2016-12-31 23:50:03 1.19 41, E) ]: L' I1 D5 W
2016-12-31 23:55:01 1.21 41
! Z# _9 t; ^$ g/ T
$ t7 H r) d4 c$ R1# G$ g6 i* O3 B/ c0 A
2" {# M& ~+ Y2 J1 J
36 D+ r% u7 f8 p6 {" z
4
1 s! G: a4 l! |5
4 ~0 I( V& j/ X+ f6& U( I# ^. X" z% @6 m: e v
7) s8 J u. n1 s- Y
8
4 m1 u, Y5 t& t2 d, r: a% |8 W9
( G. d" r" e+ ]2 p' C! Z10
/ J: M) d; C$ D, N7 j11/ o& u1 [% s" j. q- T+ h/ j b
12 E. _, R. U3 F
13
2 c @& N5 f+ ^2 ?14
1 z: M) J7 _1 p) |8 b5 V15, X9 v6 [( `7 }- d4 G3 {8 C( ]1 w
169 p; ^ M: ]: d& x, V
17
8 t; w3 {, }7 [! K6 r4 A+ Q+ G18
( w, b8 V. Y' b8 K19. |) z5 ?2 j& j0 _+ L& s4 }
每条记录时间的间隔显然并不一致,请解决如下问题:
9 }% _$ l" H! A8 S找出间隔时间的前三个最大值所对应的三组时间戳。1 g z. o7 h1 c
# 第一次做错了,不是找三组时间戳
/ Q b1 `) t8 J7 w3 t; qidxmax3=pd.Series(df.index).diff(1).sort_values(ascending=False).index[:3]
8 w9 c3 e. \5 Y+ P. I9 A7 E, }2 qdf.reset_index().Data[idxmax3,idxmax3-1]/ M1 R) z6 @! E$ T ?- i4 W! k1 D2 Y
7 @/ B+ Q. ]* w
25923 2016-12-08 11:10:42& O* m7 h$ p& M o- G$ N+ l
24522 2016-12-01 00:00:02 Q" b {) f0 A0 a
7417 2016-10-01 00:00:19
# P0 o5 y* D; e1 P# U' M" ~$ e/ KName: Data, dtype: datetime64[ns]9 |# g$ k$ R8 F# H6 `
15 B2 `# d% l6 A0 x
2
# X! I* i6 O. E( r: T4 h3( N" ]1 W" }, E# K9 K% Z& V' i. @
4! ^. S' D! w: }# k
5- ~. n% p5 z# z
6/ R; J0 d; O- v
7 p2 J1 v9 B, V* W9 b1 }
8% j* Y) x3 b; e
idxmax3=pd.Series(df.index).diff(1).sort_values(ascending=False).index[:3]
& B9 d/ H- x8 R& _8 t% z; s- Ilist(zip(df.reset_index().Data[idxmax3],df.reset_index().Data[idxmax3-1]))
: R0 i2 y( S p! {& y6 ~
4 [ d) k3 @+ c" K[(Timestamp('2016-12-08 11:10:42'), Timestamp('2016-12-05 20:45:53')),
! F% i r% W) E# L0 c# Z" _% |0 A/ z (Timestamp('2016-12-01 00:00:02'), Timestamp('2016-11-29 19:05:02')),* p0 y, l. ]* W5 K
(Timestamp('2016-10-01 00:00:19'), Timestamp('2016-09-29 23:55:26'))]4 i7 D+ p. B: @
1
, X) Y; | S8 b! M9 T6 l2; F5 _. E! z& a) ^. M
3) S4 d1 Y4 G1 |7 x/ e6 B& B# A6 ]
4
% D6 [% `0 x$ Y55 ] ^9 g8 f4 J8 Z f
6% Y I( f2 n! G6 z5 f7 `
参考答案:7 L/ \0 n: Q. t# u
2 q+ c1 R- ]1 t0 D' y, Z: _
s = df.index.to_series().reset_index(drop=True).diff().dt.total_seconds()
& k+ y4 U* H7 S; imax_3 = s.nlargest(3).index
* w0 j- `/ c. S! G& L, K2 mdf.index[max_3.union(max_3-1)]
# d2 @$ H7 T$ R- ]( l
' x' A2 m7 d9 C1 e+ h( g4 mOut[215]: ( X9 X; H/ t" \* N
DatetimeIndex(['2016-09-29 23:55:26', '2016-10-01 00:00:19',
3 ^1 r& `( `/ q9 M# D3 s '2016-11-29 19:05:02', '2016-12-01 00:00:02',4 H& F* ?8 S+ q0 k6 ]/ q3 }- \# A
'2016-12-05 20:45:53', '2016-12-08 11:10:42'],% x- p- L7 [1 R9 n! Z v; f( S
dtype='datetime64[ns]', name='Datetime', freq=None)0 v: n- e3 S2 f& r6 x+ g
13 h' r! m' `- k1 U" X6 L
2: g3 J9 R+ @5 N: K
3
$ M; k: [6 _. ~5 k! C, E4
! W/ ?. M7 P; b5
3 Z9 o5 R5 r6 U6
& G6 k3 m5 N5 ^$ {; o$ V+ U( B7, e! T! L& {. o2 j: k
8
" y6 X3 F+ n. H n- G, I9# l* y b+ z$ _. |
是否存在一个大致的范围,使得绝大多数的间隔时间都落在这个区间中?如果存在,请对此范围内的样本间隔秒数画出柱状图,设置bins=50。6 m( |5 j# L. ?7 t# R
# 将df的indexydiff做差,转为秒数后排序。再求几个分位数确定取值区间
4 W) s% _( d ws=pd.Series(df.index).diff(1).dt.total_seconds().sort_values(ascending=False)
) g9 I/ g5 B' Hs.quantile(0.9),s.quantile(0.95),s.quantile(0.99),s.quantile(0.01),s.quantile(0.03),s.quantile(0.05). o& n; Q# N M4 K6 g
7 @( L( a3 h. P7 U
(304.0, 309.0, 337.15999999999985, 285.0, 290.0, 292.0)# }. }: s5 z4 @& y& Z
1- [+ C. B* x; S3 r) k( ^ k& m' T
2" j3 P% }* J9 o$ Q0 K
3) v8 O0 G' R; T) m
42 ]& u1 E1 L& X0 f- W$ g$ v8 _* u
5
. R6 X7 L! m0 r P5 J6 P9 N7 T%pylab inline
0 P1 P7 x" m; Z5 x# b& q& F2 R_ = plt.hist(ss[(s.values<337)&(s.values>285)],bins=50)
$ z$ a7 B5 C' ^5 w0 kplt.xlabel(' Timedelta')
: I1 x% W1 b- F5 F5 z) y$ J dplt.title(" Timedelta of solar")
) N: B$ M0 @- J F6 [! b0 J1
5 M9 L/ F: f0 i2' w2 l( c( t$ s5 A
3
2 c' L4 a* ~: F; D3 ^2 q' g! H% F4
8 H7 Z- X W0 v/ y
5 c* T X8 R& e8 L3 K$ W7 F; I5 k0 A9 K% G$ U5 `- W
求如下指标对应的Series:! ~3 q- _1 d2 F2 Z. T1 G; V
温度与辐射量的6小时滑动相关系数0 x9 W! [, l4 Q6 I( p) P, c
以三点、九点、十五点、二十一点为分割,该观测所在时间区间的温度均值序列7 ]1 ]7 R( h; m2 q1 h1 N4 @4 u9 e
每个观测6小时前的辐射量(一般而言不会恰好取到,此时取最近时间戳对应的辐射量)( Z- j+ f' O( o# o, T4 n
df.Radiation.rolling('6H').corr(df.Temperature).tail()
( ^$ O, s2 n! j& Z1 |
% o$ n3 [: v: H Y4 QData
6 ]# n+ I" c7 H) @7 v7 @0 N2016-12-31 23:35:02 0.416187
|. g9 k5 F' s/ j9 U8 j& L" t1 \: K2016-12-31 23:40:01 0.416565- `: l3 [: D( m& P" ^0 F
2016-12-31 23:45:04 0.328574& T B! n$ @- v) `* H7 v% K1 c8 x
2016-12-31 23:50:03 0.261883
( `( A' o* ]+ V8 H2016-12-31 23:55:01 0.262406! e d! H% ?5 C; Y3 r
dtype: float64
1 O- q1 t. x2 v" E* l* [1
# R' r! ~+ q ?+ \, V; b8 T23 \. I- `* f7 g$ x
37 Q! [. E+ E7 D0 i# P' {
4* v9 k2 b8 F% N y" l x
5/ y) t8 `' Q$ ?4 T; e$ x0 k6 ^
6 l2 N6 l3 P0 O9 u. V+ y$ q/ Z' r4 n
7
( u) i. R- v, ]8 ^7 u81 o' t) ?$ _9 N6 s6 \
9
/ w8 c4 E" r `: Q% j t6 e( F8 gdf['Temperature'].resample('6H',offset='3H').mean().head() v( q4 ]: z5 u0 K) M
' R2 r# q5 ^7 @3 I0 o8 w/ D' `* HData
" P' F, ]/ M8 ?$ n7 P0 k+ V; Q2016-08-31 21:00:00 51.2187505 B# o# B+ h ?) M, q- f" u/ L
2016-09-01 03:00:00 50.033333
4 Y/ \) {& K! a& Z5 n/ b( ^2016-09-01 09:00:00 59.379310
! O" S9 U% k. Z( a/ ]9 K" V% E2016-09-01 15:00:00 57.9843753 Q% A3 a8 ]2 V) U" b; D' M
2016-09-01 21:00:00 51.393939
% h) N% M: c3 y( M$ m1 s# uFreq: 6H, Name: Temperature, dtype: float64
( `( O8 R9 @+ x( s& P7 _1
d+ q/ J0 ~. s2. o' B: v5 Z3 m/ i# B6 c2 z) I
3, S8 D! ?5 f( _. Z5 @' V
4
8 r) D# o1 k- a3 U' v2 N5
/ R+ I# O$ a% w1 Q' }, T0 i+ G6
, O5 W3 D( _- i* b: |$ e6 P1 j. A3 k7
" ~6 V$ p6 X4 o3 Z( ^8
+ I0 L) m% v- G9 a9
+ N; S( v) o4 ^8 y+ y最后一题参考答案:) S# M0 W( |' ^
" R) g# U, h( t8 T. m8 A# 非常慢' r, i1 B4 [; {
my_dt = df.index.shift(freq='-6H')4 v6 R9 p5 z* l* u/ p
int_loc = [df.index.get_indexer([i], method='nearest') for i in my_dt]/ g, ~/ {+ Y# w4 X
int_loc = np.array(int_loc).reshape(-1)( Q9 T/ I! w4 d. B* h4 ^+ T
res = df.Radiation.iloc[int_loc]
5 N ~7 v- B/ O6 yres.index = df.index
/ \8 ~% K* S, s9 L- z8 u% m9 jres.tail(3)
& J, A! |3 D4 b. x) H- i1$ H; L% V6 g8 Q0 v4 g
2
9 M; f- w$ t0 Y2 N, \3: ?' F1 D, ] ]/ y" r2 \4 p
4
3 F3 g f1 G" e, c3 r5! u, Z# g7 g2 E; h5 Z' a
6
2 G- O6 ^3 l N, @7
0 e1 f6 {! b9 f. H+ B, b; u# 纸质版上介绍了merge_asof,性能差距可以达到3-4个数量级
, j+ v8 c8 n% f: G8 _" k; dtarget = pd.DataFrame(( z* W# W* h) W/ x; `: C
{6 ` k, D# Q5 n- c: z' d
"Time": df.index.shift(freq='-6H'),4 r9 W9 A. U1 L& G
"Datetime": df.index,
* X7 f, }$ d" ?; u+ Y* g+ h5 ] }
: d7 I7 X8 P+ u& x# U3 g7 j)& Y$ l8 h$ y0 e
0 `2 e8 o9 d( s+ U m- xres = pd.merge_asof(7 w: D- D5 U: x8 w
target,+ ^* e$ B3 M, k* B. s
df.reset_index().rename(columns={"Datetime": "Time"}),
8 l" J$ ~+ {+ q, s4 M left_on="Time",
! {- W1 _' n/ Z- r B right_on="Time",1 W4 J h ]2 ?
direction="nearest"
; v* c9 s' ] h3 n+ l).set_index("Datetime").Radiation
" p( m# {" ]# u) o0 @- Y/ r) }
& B# q8 i9 A" ?res.tail(3)" @0 a* a/ n: l' S8 F9 A3 t
Out[224]:
) x. ^/ }. u" `4 W' S( gDatetime
, P/ g Y/ v, L3 S$ q2 p2016-12-31 23:45:04 9.33+ s. {, i% p R7 R
2016-12-31 23:50:03 8.49
4 ?3 q& e' y9 e3 q* ]% Z2016-12-31 23:55:01 5.84! y2 S0 l- {' @3 S0 p$ l& s& P- \" t- J
Name: Radiation, dtype: float64) q3 r) |( a0 u, @$ A, H7 ~
2 L5 |' M/ M; u7 Y* p v! U% Z1
6 N! t& @5 F0 F: t+ @; k2+ U7 S8 B* K/ e f/ M
3, M9 N/ @' q4 I! h
4/ k- q$ h4 M/ D5 w# q7 S9 p
5- ^2 S1 u$ _1 ~3 H
6- {+ [ h; ?4 t
7! L3 |" T4 W w h; P" H
8, @/ [& i. n+ I" \7 @; } P
9
4 L- K" c0 b8 y, I' f10
# Q" [8 S' ~# E1 _7 Z! b2 u118 J. n4 F% r6 S& w& R9 [% _
12: C, D6 d( W( r% A, i
13( }( X( @. `# M) C
14
4 r+ i' |, p/ ^$ x) C15
8 r6 {/ G7 I2 g- l" s( N" T+ @' M16
- m6 W* Q: N6 z* w% V17
+ [: |5 K6 b" x9 t! J18
4 ?) V4 v& e; ~; ^& k6 L19
9 x' v) ]0 M/ j7 I20+ i* j+ h; L$ o- r- d7 m
21
& Z8 Y) h8 r \* x4 J0 q22
9 H1 c9 t) |9 V23+ D' Q. _ T- X9 B' k
Ex2:水果销量数据集
* I$ \/ i' u6 r$ z) Y: |现有一份2019年每日水果销量记录表:
1 Z q' c2 @: p; [/ L+ |; R X: e7 ~# y. d9 X4 l
df = pd.read_csv('../data/fruit.csv')% {6 y8 u4 ^, e5 r& Z: A5 }" q
df.head(3)9 P$ y; Y( J2 s
- R/ Z! E5 n9 h$ x4 \Out[131]:
: p( X& f! S4 N Date Fruit Sale
$ k! [0 \/ O$ } G4 }- ^0 2019-04-18 Peach 15+ ^, J$ ^ _, O8 W5 b
1 2019-12-29 Peach 15
+ ]9 U; u5 P3 [8 E0 r2 2019-06-05 Peach 19
0 |" {: L' Z" K/ y4 n( @+ }1$ ^1 h9 C( m: t! G1 j& L
2
; a z* ^/ r) u4 }3
7 l e6 Z; i! v; R9 {; a41 ~0 P8 O! A% Z! u: ?. x
5- X2 A+ H" S! M9 z0 J
6/ Y5 C% |! I( V6 _0 x, X; N
72 S0 G) D( n D# L q
8& D' C& `- H m, c p; ]. W: P/ R! _1 j
统计如下指标:
4 }5 S$ E$ z; t6 U, b每月上半月(15号及之前)与下半月葡萄销量的比值
. _# o# E! D) ]7 q每月最后一天的生梨销量总和
( _) p0 d' v D _4 ?9 c# s7 o每月最后一天工作日的生梨销量总和0 a/ T1 N; e6 _) O+ Q
每月最后五天的苹果销量均值' I& j. u% i; v2 V1 u
按月计算周一至周日各品种水果的平均记录条数,行索引外层为水果名称,内层为月份,列索引为星期。" L+ o0 c+ y, K/ {7 t
按天计算向前10个工作日窗口的苹果销量均值序列,非工作日的值用上一个工作日的结果填充。% l6 m( I3 w* G9 B1 v% Z
import numpy as np; X+ i. x* E/ P' n# ]' N6 [5 o( `
import pandas as pd
2 u% Q7 S9 O u: s0 V19 R# s9 K( i$ q- Y- p
2
9 X: Z" C- |$ \# [: l统计如下指标:
2 P/ W! E5 K0 {) Y& a: R每月上半月(15号及之前)与下半月葡萄销量的比值0 |; g# f9 I6 X. _) C% V4 F; L
每月最后一天的生梨销量总和. @; d0 @, ?" I1 i' D4 I6 s$ x7 O+ e
每月最后一天工作日的生梨销量总和- b' U8 T9 ]7 Y& f6 h3 w, i
每月最后五天的苹果销量均值, ]/ d2 |) T4 `: D7 u/ U( H
# 每月上半月(15号及之前)与下半月葡萄销量的比值
2 v2 f) u6 b4 f$ F% Y- c# Ndf.Date=pd.to_datetime(df.Date)2 |' f9 L7 t3 H! G
sale=df.query('Fruit == "Grape"').groupby([df.Date.dt.month,df.Date.dt.day<=15])['Sale'].sum()
u# x2 N% B/ U9 B z* G' C6 i* Ysale.columns=['Month','15Dayes','Sale'] # 为啥这么改没用啊
( @( \) [- r J, c% m+ Z# xsale=pd.DataFrame(sale)9 U" d7 n' R, T" P; ]7 y: K
sale=sale.unstack(1).rename_axis(index={'Date':'Month'},1 L$ k# ^4 P/ e
columns={'Date':'15Days'}).stack(1).reset_index() # unstack主要是两个索引都是Date无法直接重命名
$ _4 d! H; e% l8 S" V" x3 z- Fsale.head() # 每个月上下半月的销量
! t! F- m$ a# ^! q% P& f0 X
& A, [- R9 n& t7 H. C+ F9 Q Month 15Days Sale
1 k. Q2 I- i9 A5 u- t0 1 False 10503% G$ p1 E4 i# n2 M$ [
1 1 True 12341
) h. C( Z" b7 M* V2 2 False 10001
( i4 J- q/ |) U6 t2 ?) F c/ H3 2 True 101067 a% {0 t8 ~% Q d+ b: |
4 3 False 12814
+ A! ?; L% K7 c3 ^. K& `1 \
Z. ?6 l2 |- \* J" s6 u) X% s# 使用自定义聚合函数,分组后每组就上半月和下半月两个值,根据索引位置判断求比值时的分子分母顺序1 c8 @9 ?3 v/ d- O9 y$ b) G
sale.groupby(sale['Month'])['Sale'].agg(
n5 t( K7 M9 Q: p3 |( M( o5 P lambda x: x.max()/x.min() if x.idxmax()>x.idxmin() else x.min()/x.max())
. w ]" p' q9 I6 l( h
9 ?' N- I1 b- J4 ]Month
& d- v# ?9 g- p" g- c1 1.174998" [3 ` d4 C7 y8 X3 j
2 1.010499
' T4 E2 N4 l0 N2 q% G+ q- F3 0.776338
3 X& B+ D T8 O; H$ W. F, k# L4 1.026345
6 C& R0 ^0 u, W1 H% M8 y+ Y5 0.900534( m3 R9 x# l3 @9 B
6 0.980136+ l# N. Y) ~" W0 V, H
7 1.350960
; C" d) Y3 G6 p% ~% l9 a# S0 d8 c `0 t8 1.091584
# B8 p) w5 i* P3 @4 s \9 1.1165083 s/ o8 s% ?) c' M' n! ^: z
10 1.020784' _3 I8 v B$ R& H
11 1.275911
2 G3 i# d+ H4 G1 b12 0.989662
: ^' C- ` S+ _* }Name: Sale, dtype: float64
/ {0 k; c9 T. Z0 A+ `
F% u: C5 d2 {& |7 @6 f- l$ I1
6 w7 E' X- e* B6 g' Q2
8 N2 g6 M3 l# T" ~3 @& j, Z3
6 W$ A1 x$ ?# q( [5 ~% z4. Z8 ]: G+ e7 C5 F- X# C8 ^
5# \, e( W0 W$ v4 a: \+ J" ?9 X
6" l. J0 x9 B* i/ m
7
]. D0 C. h5 O# Y( m$ E) H8
6 G9 |3 q$ r; A9 S3 ^1 u9
' ~. z' Q& z* J7 @10
$ U4 F; a7 a3 b! ]7 l3 r) w, v11; G" F/ r+ G5 ^6 U6 O4 ^
12
" H5 F" w! ~( o13; L8 Q7 L# v, z# Q; m3 @8 n
14
* G% }+ Y8 A( n% A15
( F$ q x3 A, R( M16
# H' w* E2 E2 x, i5 c17. g: E- ]3 }4 E2 f( ^4 c& f' H
18
% z2 m1 y3 F e( r4 U+ X. z$ U190 x+ g( g* z2 w! ~
20
4 d+ ^6 X+ ]# y2 Q4 `6 `21
+ @( n$ v, {1 w+ S: c4 h( g220 y, D% Y4 Y2 G# _% I9 l
23
; i8 s+ W7 |$ y. _8 S0 p' y24
" { s. d8 ^4 ?/ z25
& l! v8 J) ]. m' L4 p. m26
7 N0 R# H% z; C27
" c2 U) k8 _4 c2 l28- O$ h, I/ f9 r5 @( N' }
29$ L0 b* K% V7 Q0 t' V
30' T/ n' R. w3 a5 V- E; D
31 p/ @$ L2 c0 A& R( F- g1 t% j- u
32
1 ^1 p+ x) A1 f# ^) Y33
* B" e: }5 M6 L6 {+ o2 y- J34
1 n: h+ G- L* E: N7 }# 每月最后一天的生梨销量总和 J- P2 q, y: b C3 x
df[df.Date.dt.is_month_end].loc[df.Fruit=='Pear'].groupby('Date')['Sale'].sum(); G' s. U( s# A# v' D+ o7 \) T
2 T: ^' e+ i- u. o1 E1 W$ JDate
4 L3 z4 x$ h2 @9 H+ E7 J2019-01-31 847
3 \8 K2 B( I; {; I+ b+ j# K2019-02-28 774" t4 U6 \4 C" F6 g5 j2 K0 E
2019-03-31 761# v% {" w$ ~, q Z. j+ D
2019-04-30 648
* b% _! {3 |! ~9 P" W! a2019-05-31 616. S0 i" @1 q8 f; i
1( i( d( k; G( n
2* e; d5 b* U0 c/ ~7 @
3: D7 S+ b: T5 _& _' ~
4/ a: n, W& c1 \6 V
5' y, U8 x" x: e. {2 e' C' p1 e/ _
6+ n2 P0 i0 R$ u6 G' I/ J
7' I. N, R/ `+ K9 J5 z/ H
8; G B: d1 D2 I! @5 b
9
. T/ _- c1 F! K) K# 每月最后一天工作日的生梨销量总和6 O: Z ]1 z% u1 Z
ls=df.Date+pd.offsets.BMonthEnd()
! E1 v0 m5 X3 Hmy_filter=pd.to_datetime(ls.unique()); W5 h% _6 O; Z) T0 j R% [
df[df.Date.isin(my_filter)].loc[df.Fruit=='Pear'].groupby('Date')['Sale'].sum()5 X v- e& \$ X! n3 `
3 w- B9 j" {% w4 N3 a
Date
4 k' Q8 m: F. N3 E% `0 P2019-01-31 847
! \9 ~, {2 z. q \1 Y3 a/ J6 M [2019-02-28 7749 ~- ]0 T3 w9 E$ b" Y4 U6 e% ~
2019-03-29 5100 n4 z; D7 _9 n+ a
2019-04-30 648
! {: ^- D% t- N2019-05-31 6167 ^5 \' x1 [- W3 F3 m9 F
12 |- {, ]# O0 u
2( q7 y0 c; n8 I* E/ W
34 }3 h: s- g' s- c( T/ ]* p# T
4
7 E+ W: O& V8 g/ [0 j5
4 ~! S6 ?, W S8 m% ~3 F5 _6
5 c4 }( D. g5 t! x7 E77 _) Z, o9 P9 s7 k5 h$ l1 x
8$ z- b$ \" C% C
9# ^! H6 R2 s" X3 ?* \; Z
10' v) B3 M6 J2 U4 ]
11
: c" K8 G! f) `9 O: Q2 s* C# 每月最后五天的苹果销量均值
M: C9 ^* H+ M( Estart, end = '2019-01-01', '2019-12-31'
# p+ n P, Q7 |0 Z" Lend = pd.date_range(start, end, freq='M')! \0 G. @5 L p& n: e
end=end.repeat(5) # 每月最后一天的日期列表,重复5次方便做差! Q2 }8 n* n7 M
, a2 Z- z. c, N; D1 @/ k9 r7 f
td= pd.Series(pd.timedelta_range(start='0 days', periods=5),)
# d$ o8 {( H8 J* a( Y( Ftd=pd.concat([td]*12) # 日期偏置,最后一天减去0-4天
0 K0 i2 _, Q5 p& C, gend5=(end-td).reset_index(drop=True) # 每个月最后5天的列表/ {' F9 P. O) ]1 E9 p) p& s/ p
9 `* b" z/ K4 |1 D" b
apple5=df[df.Date.isin(end5)].query("Fruit == 'Apple'") # 每月最后五天苹果销量1 a9 k5 H5 ^, C: e% ^% w
apple5.groupby(apple5.Date.dt.month)['Sale'].mean().head(), k7 i0 K( l, a3 S, ]* [. ^& O
6 W9 ?3 A& g4 }& Y0 m! T* w8 J! G8 I
Date' M$ g: T) v W/ [) }( c& P6 L6 j
1 65.3137258 s. }- [) ]! d, A) d p
2 54.061538
, d% M/ o0 _% R3 l+ `- ^1 [, z; D3 59.325581/ w, }0 _) M* V
4 65.7954551 U$ V2 a4 R" d4 ~
5 57.465116: W' i: i: y4 u0 o9 }+ ^+ k' ~
1 ~5 w2 f" S- s7 \; U1+ J; @# L$ W' u: r, a8 m; E5 x
2- s% c( y+ r P( v: U
33 o6 G* a# Y9 m9 C/ k
45 M/ F! h m) U, Q2 Y
5
! T; H& D* _+ J( K$ V4 v6
2 z# o9 J# e1 n% ~: ~75 H# k& y# T4 [) p' W9 n
8
4 P- J" s0 f7 ~7 ^: Z! c1 k92 k1 J0 S0 k$ T/ b. l1 @
10
# H$ ]$ v$ W% @11' n) M7 @$ Z% p$ i
12
" m" y3 J" s# R3 C* `: t13* S7 Q& ] R3 V, m- F4 P- m
14
$ k6 E( ? Q8 ?7 s3 A3 L6 ?15
1 k# e R8 _4 H9 C L' U16
3 }& U3 ?& n: x! U* J176 H3 C: K4 o7 P& M! v; B7 _( z8 `
187 d; _1 }. |& m' S6 _
# 参考答案:
4 M s6 e2 `1 A9 X5 x8 V1 Itarget_dt = df.drop_duplicates().groupby(df.Date.drop_duplicates(
5 Q: d5 N* f5 A5 l ).dt.month)['Date'].nlargest(5).reset_index(drop=True)
: }) q2 W. `% E. h
! I4 `4 v1 K Tres = df.set_index('Date').loc[target_dt].reset_index(9 G* q: \# F% T) ]8 b: l" A
).query("Fruit == 'Apple'")
( h F1 a9 R, C3 O" ~: |
! ?2 Z% m/ \1 s# X; Ares = res.groupby(res.Date.dt.month)['Sale'].mean(' u/ ]6 b* p0 b1 s/ u* C
).rename_axis('Month')( Z$ K/ Y' I2 v0 \
6 c& [; t4 ~ t- C: S. S9 S% q9 }
* |2 H" G3 M. d- x* K* k4 Q9 v2 Q
res.head()
" o( N3 K" E# COut[236]: & m4 Z9 [0 t! Z5 _+ H! E9 w
Month
! W6 N5 r W0 b9 e2 k1 65.3137259 g) e, ?% J1 q) m
2 54.061538( ? I+ f! \2 U/ q1 n0 q
3 59.325581
) P- H$ ?: O4 j; ?/ O4 65.795455# e4 z. S n j* @
5 57.4651166 Z" G9 U2 i4 u% X/ ~* N0 r
Name: Sale, dtype: float64
. h0 i; x+ ?" U# f$ A% F/ w% N) Z
) T0 I! b# \3 a8 L7 Z1
' _( e; c9 w: U1 a, r& m7 [26 N3 K, z! P: l# f! d* B
30 o( n7 @# y& @6 I6 i0 S& W8 R6 K o
4/ A2 v/ T4 V: x' O2 k; \) r$ f
5
# d0 z3 B/ t( A' G5 S7 h+ B" W6
' n) N* d& d8 o3 k76 }$ a5 _& N" i# M# a+ \: I) W
8
- n& U# L' W. n9 V( I8 J9( i6 S3 {$ }+ f. p" P* c8 S3 r
10
5 [. B9 x3 n2 ]8 u+ g11: Z2 n- s. y" M, B; }1 m
124 z7 K% I9 S' U0 N( r
133 j3 ?3 M6 B; ~) s
14# M" o6 {0 n, p) e
15
0 U* T: W& e1 G, h0 e! U16
$ k2 H4 ?# y2 X% G17( \! U! e* H- z# v
18, R0 \1 m7 n' r
195 ? |2 j3 E o. o1 {
20
; I S3 N) B w8 Z按月计算周一至周日各品种水果的平均记录条数,行索引外层为水果名称,内层为月份,列索引为星期。
+ D8 `/ e2 b( r. F# Bresult=pd.DataFrame(df.groupby([df.Date.dt.month,df.Date. P; p* d- y' D
dt.dayofweek,df.Fruit])['Sale'].count()) # 分组统计 ) Y9 m0 B' {: v: ?" E1 @
1 h' x. A2 G. [' f# }
result=result.unstack(1).rename_axis(index={'Date':'Month'},
1 V% m$ ~+ n" N7 I( d columns={'Date':'Week'}) # 两个index名字都是Date,只能转一个到列,分开来改名字.* S M1 `( r# n0 n
result=result.swaplevel(0,1,axis=0).droplevel(0,axis=1)
, ?" l7 w( f9 U9 lresult.head() # 索引名有空再改吧* A: r$ [. P! Y. b% f
. N' o* v* r3 m5 F5 H2 _) I" _ I
Week 0 1 2 3 4 5 6
& w: L2 R1 t$ f6 T5 T4 U0 C3 K! ZFruit Month 1 {, A' |8 A& M1 R: ^' Z3 ]
Apple 1 46 50 50 45 32 42 23
4 s1 Y! p2 n( d5 @0 I* uBanana 1 27 29 24 42 36 24 35
2 d' N8 r' _7 T; y5 e/ v/ iGrape 1 42 75 53 63 36 57 463 j4 i, `1 @8 h0 F9 M
Peach 1 67 78 73 88 59 49 729 a, {$ W. J. z/ O
Pear 1 39 69 51 54 48 36 407 \4 N! w9 F% H
1
) m8 F" [8 p/ t& b2
, P0 b& m# x7 `6 I0 h3
) S6 Y; n0 g7 W& W% O$ n4% H* ^5 c! }4 a1 S
5
" }, h2 N) D( _6 O5 [62 P9 }* m, m7 E
7% r+ z! I: X6 V
88 x6 Q! S% w8 |. U) T' s
9& G! B% O- E! [- k6 z! p, ~
10* h: F1 z3 T @ y& o
11+ C y* B$ w" Y2 w! n
12
; ^, _* S/ R& w13
# e; E3 r5 k# s7 F1 O& r/ g14
) b% G& ~8 s/ V5 J9 ] \' [153 y4 H# J, k8 H5 u3 b2 w1 ~
按天计算向前10个工作日窗口的苹果销量均值序列,非工作日的值用上一个工作日的结果填充。
* @6 `( B$ d' R5 Y$ r# 工作日苹果销量按日期排序
' V3 x* r0 Z6 N$ s, qselect_bday=df[~df.Date.dt.dayofweek.isin([5,6])].query('Fruit=="Apple"').set_index('Date').sort_index() W' K5 h3 d8 T& |
select_bday=select_bday.groupby(select_bday.index)['Sale'].sum() # 每天的销量汇总
# Q/ s- i6 u/ g1 Z* A9 ], {" wselect_bday.head()- q) ^! q# v2 w- D
% {4 B; v3 z% U4 BDate
% ` A! ?( T9 B) u6 B2019-01-01 189
% @) k, L, ^" \( |2019-01-02 482+ \" Z" \9 G1 C; `8 E
2019-01-03 890
) ~ E1 |8 }/ L2019-01-04 550
9 H7 `9 C V( m2019-01-07 494
: [- W$ ]7 ^7 C/ h f
6 G' G' O/ m0 K3 W2 N1 L2 ?# 此时已经是工作日,正常滑窗。结果重设索引,对周末进行向后填充。7 q1 }- p; P: N4 ~* |: `
select_bday.rolling('10D').mean().reindex(df.Date.unique()).sort_index().ffill().head()/ j' ^* Z. k: O1 [/ v
: j+ b2 a! C: k7 R8 l
Date7 q/ v0 n* e( _( j; ]8 S+ @
2019-01-01 189.000000
7 d; M" ]* ?, M4 e/ o, M, Z4 f2019-01-02 335.500000
$ h; D1 y! R7 t; |( M" [2019-01-03 520.333333 [& A8 t% ^ k( e" s8 v
2019-01-04 527.750000
$ i- j1 q, h+ k T. b! K+ N2019-01-05 527.750000
$ r0 T, x3 B/ N
5 d# Y6 E: p5 u3 N* Q! }————————————————
7 F% z5 n. V; v版权声明:本文为CSDN博主「神洛华」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
/ f, @* Y. G- F2 r% e9 v7 Y原文链接:https://blog.csdn.net/qq_56591814/article/details/126633913' Y6 E1 S% }5 g7 v
; i- ]# A. y- o- x
& U, |/ d8 ^' U |
zan
|