- 在线时间
- 1630 小时
- 最后登录
- 2024-1-29
- 注册时间
- 2017-5-16
- 听众数
- 82
- 收听数
- 1
- 能力
- 120 分
- 体力
- 564679 点
- 威望
- 12 点
- 阅读权限
- 255
- 积分
- 174627
- 相册
- 1
- 日志
- 0
- 记录
- 0
- 帖子
- 5313
- 主题
- 5273
- 精华
- 3
- 分享
- 0
- 好友
- 163
TA的每日心情 | 开心 2021-8-11 17:59 |
|---|
签到天数: 17 天 [LV.4]偶尔看看III 网络挑战赛参赛者 网络挑战赛参赛者 - 自我介绍
- 本人女,毕业于内蒙古科技大学,担任文职专业,毕业专业英语。
 群组: 2018美赛大象算法课程 群组: 2018美赛护航培训课程 群组: 2019年 数学中国站长建 群组: 2019年数据分析师课程 群组: 2018年大象老师国赛优 |
6 t+ u5 A3 ?0 j# |
' N W7 d) B) Y7 E
6 e% K( g v9 R+ O3 \+ @6 u文章目录
( \4 r# Z" _+ `4 v9 \第八章 文本数据, M% [7 B. E' q/ _; K: Y
8.1 str对象
$ ~" r4 X% R4 `/ G, V( G' H8.1.1 str对象的设计意图
* V' X" C" j, d" R# K* S8.1.3 string类型
8 T. G4 [# O+ i$ M% e& h8.2 正则表达式基础8 r( e3 \- s: g" ?* P+ R- o
8.2.1 . 一般字符的匹配% t+ t L2 p+ y" O1 L
8.2.2 元字符基础+ k" d# S" Q% A
8.2.3 简写字符集1 D; v/ F# D* f' i: o5 w
8.3 文本处理的五类操作
' i7 c+ C" l1 G% h5 C: s# i8.3.1 `str.split `拆分
- G O0 A4 O8 }% b \2 K8.3.2 `str.join` 或 `str.cat `合并
L0 E5 b3 t! y' ~" B/ K `. S8.3.3 匹配: x, g7 g5 I3 X X0 V ^& ~2 h
8.3.5 提取
9 J9 @+ v3 B. q8.4、常用字符串函数! e4 K% i2 I# ^1 L3 F3 @
8.4.1 字母型函数
" m. H' B3 ?( E h# H2 U8.4.2 数值型函数$ X& W( B$ Q6 P# I* R6 d$ ?' R8 e
8.4.3 统计型函数8 ~ B3 M: }; d! z
8.4.4 格式型函数
! i/ k3 l5 q! r3 z1 d8.5 练习
; u" D9 Q3 f/ c; K8 _Ex1:房屋信息数据集9 g& c. m/ y9 |! \% j1 ~3 Z7 |
Ex2:《权力的游戏》剧本数据集/ @1 y/ [& `5 J
第九章 分类数据
2 H3 @! Q0 @7 ^* p. x: H9 J1 f+ ~# ]9.1 cat对象, X! d1 S8 Z) z% @6 u2 K
9.1.1 cat对象的属性6 U' D U9 c1 T8 L2 B2 M: S
9.1.2 类别的增加、删除和修改: Z6 s! Z* d8 B: `, m( o+ S* B
9.2 有序分类
' m7 M* @+ m! p9.2.1 序的建立7 g0 |; U! r" Z# p
9.2.2 排序和比较. K5 J; V" F L; Q& o6 j1 [
9.3 区间类别! F8 W2 S- w8 n$ z1 ^1 y- ~: x
9.3.1 利用cut和qcut进行区间构造" x1 I) p" R; R+ U8 X5 Z
9.3.2 一般区间的构造
; }- Z0 ~( p+ t: P% A9.3.3 区间的属性与方法
$ ?: x; p! W9 M0 m1 X9.4 练习
8 S5 c8 b3 A! w1 pEx1: 统计未出现的类别
6 S2 X. c- F) [# h% x9 YEx2: 钻石数据集* U, g6 i5 \' i8 m% T0 c
第十章 时序数据 s2 d: v' w! }5 J C
10.1 时序中的基本对象
+ [2 d& {* u; I1 p10.2 时间戳. G+ e! S5 R" H# i# ^) l
10.2.1 Timestamp的构造与属性
5 Y. R7 O6 Y. k$ s; H9 @6 N10.2.2 Datetime序列的生成
- l d. T( ]( S10.2.3 dt对象
& \, V3 ^; m7 g7 Q3 M, w10.2.4 时间戳的切片与索引8 ?: ^8 K4 f7 T3 `+ a( h: ^
10.3 时间差
. Y% b, o5 [6 o# R9 u10.3.1 Timedelta的生成
6 E/ \; d7 k( b10.2.2 Timedelta的运算
. C) x( d/ u2 ^4 C10.4 日期偏置( e0 }% ]8 W3 M. y. Y
10.4.1 Offset对象
, t2 y3 U- X. I9 h- i: c10.4.2 偏置字符串
% W; b; ~6 [9 q+ |10.5、时序中的滑窗与分组
( \- Y" O- @1 S6 Q, M2 H10.5.1 滑动窗口# f$ L' \8 j) j) s
10.5.2 重采样
7 q6 ^8 \& y6 R& ^10.6 练习$ R- ?( U, j! {1 a
Ex1:太阳辐射数据集
: x; |/ E h1 b# k) ^Ex2:水果销量数据集
1 f. f0 b& Q- u8 e 课程资料《pandas数据处理与分析》、github地址、讲解视频、习题参考答案 、pandas官网6 m# E& V/ d% u& ? e/ _
传送门:
; K( I4 L( E F) ~9 i5 ?! T5 ~+ _/ J2 p1 j9 L+ Z
datawhale8月组队学习《pandas数据处理与分析》(上)(基础、索引、分组)/ n# D+ C+ s+ }
datawhale8月组队学习《pandas数据处理与分析》(中)(变形、连接、缺失数据)1 A d# D; y( G* P7 r' ~
第八章 文本数据
5 l/ r6 a; A& J& f, O* @8.1 str对象
8 ^0 u6 J& \( G4 \% ^: j8.1.1 str对象的设计意图5 {9 T5 X$ M2 x
str 对象是定义在 Index 或 Series上的属性,专门用于处理每个元素的文本内容,其内部定义了大量方法,因此对一个序列进行文本处理,首先需要获取其 str 对象。在Python标准库中也有 str 模块,为了使用上的便利,在 pandas 的50个 str 对象方法中,有31个是和标准库中的 str 模块方法同名且功能一致,例如字母转为大写的操作:
/ K2 L, [$ L4 L) X6 m7 w5 U- q {9 E3 h( f7 g
var = 'abcd', Y# j& k# S3 t; S& D
str.upper(var) # Python内置str模块1 H; W& G3 H8 u3 p
Out[4]: 'ABCD'1 W7 J8 C, G: N3 @5 I( J
3 Y; `3 R6 ~5 n c% O% t0 z( i& ?s = pd.Series(['abcd', 'efg', 'hi'])
' H& f' h4 B0 S# s9 }
6 O, R) C# Z+ R3 Bs.str
, K$ o1 G5 I# _, o3 cOut[6]: <pandas.core.strings.accessor.StringMethods at 0x2b796892d60>
+ B1 G: g1 E) T
. e0 W6 i! z: Y. Zs.str.upper() # pandas中str对象上的upper方法; Z( g- p/ D* ]) q, r& c
Out[7]:
" B4 o& f4 @5 [9 x5 \) R* ^$ f0 ABCD2 j! ~! R. k/ N5 _0 B
1 EFG
K) W4 z) ~. a' o: W7 n n2 HI
% E, l4 c0 a3 g8 c* W/ ]dtype: object! R& K: B7 o# h& l/ E( s- e5 Q9 T
1! k3 B. F# Q1 z# L* j
2
) B* R/ J9 f# l6 L3
* x2 M) f4 Y9 o9 r8 C49 k" w! Q* C% s) Z( t! O
5- W/ S8 S: P/ ?; s2 Y2 p% c* {
6
7 h( }% I, _9 F) J7, \( L2 A' T7 R
8+ S# B4 j2 B# o6 c
98 R7 l, w4 B7 `) `
10
; P6 f! U8 }, O! g8 Z8 R11
8 k! ]- t9 @7 C- y+ p, M$ m12
. r) [5 _6 c- \4 r- j+ \& }! z13
2 l* Y; K: e; q14& F. q2 M' f. [1 H9 N6 l
159 P5 _9 \7 L& L2 W' B+ [; @2 i) P4 p
8.1.2 []索引器
. O7 ~. g T$ I/ b* f# H 对于 str 对象而言,可理解为其对字符串进行了序列化的操作,例如在一般的字符串中,通过 [] 可以取出某个位置的元素,同时也能通过切片得到子串。' ^3 ]: h; C6 F9 Z3 I0 Z1 Q! A
pandas中过对 str 对象使用 [] 索引器,可以完成完全一致的功能,并且如果超出范围则返回缺失值:
! V! Y4 _$ P+ v3 C+ G9 t) @6 y* p3 R' z9 t: S2 f5 f
s.str[0]
( E* W& h9 y8 B. D; BOut[10]:
* K ~9 n# ]2 y6 m- |# B8 r) x0 a" N( S, m% [5 q7 ^7 Q y
1 e+ O. w& b- X. `: \3 T
2 h* @2 t* d" |0 O; Z) L
dtype: object, @0 V, L; R8 d" L* `% C( } {2 T
8 c' { v8 d4 O: I5 Cs.str[-1: 0: -2]$ z8 N' f7 U8 Q4 Y
Out[11]: 7 @3 A$ w; X# l
0 db1 c- |. p8 d/ T+ z/ m3 p" C
1 g
7 k7 u0 \8 u& `# g/ l4 q2 i& P1 J8 {- t# s6 e7 s
dtype: object
( W, I4 K. D/ l' e
' ?6 l3 G+ X1 Vs.str[2]
) o% ^; E1 r2 i! ^6 yOut[12]: # t! W5 a9 {" @9 U; @
0 c$ o7 n0 |3 ?: ~+ ?6 ]- i3 B
1 g
+ |% N2 {* G! y2 NaN
, z( m+ |. v& a7 W5 v. sdtype: object y" `1 N1 ]% E, d
]% Y6 o0 ~+ D9 C1& t" p4 l2 o" X1 ?4 o3 r
2% m; l, U4 o3 n- p
3
" J; b8 G1 c% u! h% g4
: g/ P. w- |& V2 P; `5
6 ]( q! i6 g) s2 r% D! \8 B6
% z* l! |; k% V) D2 e$ `1 s4 c! i78 A& j! O3 M2 f8 v* Y% V( u
86 X/ z$ Q3 t7 a/ [) @$ w) J
9
! ~( E% G0 ?$ |% g# Q10, Y# g! o/ H' _7 z, P9 X
11
9 I' U" g4 C W7 H12' l- _- j6 d3 w/ d, X9 r7 K
137 R g8 \0 R* b y8 J) x$ O8 J5 v
149 v4 \6 l: T% q; W1 v! i6 b, | ?7 u
15
. }% B; x! V4 t& {! J' r* v% y16; J& n3 p1 `5 i3 B X0 X
17* x1 ~$ t$ a/ k7 H/ |' H9 Y, }
18
7 ^2 k+ P# Q d S# `7 O19" u, K, D: e1 \
20( ^$ v. q% P& g) a, L1 a
import numpy as np6 B4 j- G2 W! }' J' B! ^
import pandas as pd
/ e; ^$ n+ g; c, E2 I2 ?* T- H9 I" D7 d2 z- e( Z8 ^ P# x
s = pd.Series(['abcd', 'efg', 'hi'])0 a5 @( @* Z3 j4 \5 J
s.str[0]# ?8 T3 f V7 i/ Y
1
# p$ `9 P+ d) L0 b3 \2
/ T' h* ?& S: k: Z35 v7 ~. Z$ O, O$ p( Y* L. e" F# i: S
4. L+ V( _! w1 E& J! x" o) S
5
4 F9 Q2 n7 i7 Q i3 [+ K* O$ U0 G1 q; h+ E0 a/ s. P# t3 T; N5 y. N
1 e R4 s( T2 B4 A( ~- O! e/ v2 i: q
2 h
' C# g, H; R4 Z2 J1 ~dtype: object; Q7 {! q, p7 |/ R' r
1
3 p' c, }% ^6 [27 H6 f$ A5 a. e% j3 x; r# [6 e' u5 z
36 Y& _% T$ V7 E
4
7 s; s$ j6 g, q* j8.1.3 string类型$ w# W7 S. w( K: x* B# j/ t! q* u
在上一章提到,从 pandas 的 1.0.0 版本开始,引入了 string 类型,其引入的动机在于:原来所有的字符串类型都会以 object 类型的 Series 进行存储,但 object 类型只应当存储混合类型,例如同时存储浮点、字符串、字典、列表、自定义类型等,因此字符串有必要同数值型或 category 一样,具有自己的数据存储类型,从而引入了 string 类型。& M1 V- K1 f2 G1 J8 o6 ?$ K
总体上说,绝大多数对于 object 和 string 类型的序列使用 str 对象方法产生的结果是一致,但是在下面提到的两点上有较大差异:
& W' Y6 D* `, c; T/ |& s) T. d9 ?8 |0 k6 s2 ^5 T
二者对于某些对象的 str 序列化方法不同。5 a4 P' Z" h& s
可迭代(Iterable)对象包括但不限于字符串、字典、列表。对于一个可迭代对象, string 类型和 object 类型对它们的序列化方式不同,序列化后str对象返回结果也可能不同。例如:% ?( a$ U8 ~- t8 e5 V q8 l
s = pd.Series([{1: 'temp_1', 2: 'temp_2'}, ['a', 'b'], 0.5, 'my_string'])
- z: Z; P0 T6 _+ P2 m% hs. a! n. _0 [9 p) \
1% Y0 k3 J; @$ b! Q
2
& l& k' S! }2 M7 B0 {1: 'temp_1', 2: 'temp_2'}
' D1 W2 E+ c2 y, B0 j9 N# P5 r& E# u1 [a, b]
; e' }2 P/ _$ O1 y1 ^% m2 0.5, c- r+ x; Z8 u+ ^) \# i1 H& R
3 my_string
- }1 U1 w4 d5 t- Kdtype: object
$ V; s; l5 T1 ~: _1
4 m# S! f+ ~4 s8 F* t3 W1 u, m2
8 J: S4 Q% r: i5 u6 |7 Q! W/ c4 R30 g$ g" p' {3 M/ y
4/ \! E) ]7 K+ n, k! p- l
5
% h) }- K9 a6 J( o7 `. i1 R2 \+ bs.str[1] # 对每个元素取[1]的操作
. d$ L. A: M' K& T/ ~6 L8 s8 ?$ C. Z# o11 z- Y P/ K! {* s$ w
0 temp_1
* G- s' m4 x, L/ Q; W; n1 b
" {! g- o! W: p7 c* Z5 M' R2 NaN. ^, e6 ^' J# d. }6 m/ E
3 y+ ~" ]. t3 u$ Z
dtype: object) G: l. K0 {) D; A/ x
13 g$ `# `0 {* f- x
2; [; x$ F$ J, `' f& ~
3
& J1 w# l& o2 y1 P48 {9 j' z/ i! O+ W9 x$ g) I- }" e/ F
5
/ _# H9 O8 B7 e5 ns.astype('string').str[1]% P' q0 n7 r2 W" B9 ~
1
. K6 W9 f' d9 Z& G# Z0 1
! N' m6 ^3 ^- [1 '* j( [. T8 {& ?2 g& G
2 .. C4 ?' g9 H# X! B
3 y
' O2 C3 R: e$ j% H7 \( vdtype: string" N" k( }5 c# @- j
1
8 F: Z" ^2 J: O2
+ r& p2 o. z+ p3 l9 A$ K% T3 G& M/ [! ^, I& d0 C; s' F
48 ^8 o# U& r. S) S
53 C8 D/ N. D+ G
除了最后一个字符串元素,前三个元素返回的值都不同,其原因在于:
P; l( f9 {- ^+ _7 l5 N2 m! G' N! D/ K3 N/ h Z4 Z5 L+ h2 n
当序列类型为 object 时,是对于每一个元素进行 [] 索引,因此对于字典而言,返回temp_1字符串,对于列表则返回第二个值,而第三个为不可迭代对象,返回缺失值,第四个是对字符串进行 [] 索引。
7 m/ W. ^3 r: h9 _, z; [. F& ystring 类型的 str 对象先把整个元素转为字面意义的字符串,例如对于列表而言,第一个元素即 “{”,而对于最后一个字符串元素而言,恰好转化前后的表示方法一致,因此结果和 object 类型一致。
9 H) t! j% F6 q8 Mstring 类型是 Nullable 类型,但 object 不是
1 ]6 r w# D% l7 q! Z$ J7 N 这意味着 string 类型的序列,如果调用的 str 方法返回值为整数 Series 和布尔 Series 时,其分别对应的 dtype 是 Int 和 boolean 的 Nullable 类型,而 object 类型则会分别返回 int/float 和 bool/object ,不过这取决于缺失值的存在与否。% p+ s* D: A2 F4 k4 f: i; |; O2 A
同时,字符串的比较操作,也具有相似的特性, string 返回 Nullable 类型,但 object 不会。
# ]) m* {) ?$ b$ M& `: Es = pd.Series(['a'])1 _5 J. _! T! F; @
7 Z( ]3 g6 ^- N/ y# ~
s.str.len(): ^; V$ w+ x! B$ V' z+ I
Out[17]:
: `: E4 _/ h2 o2 H2 n0 1 p3 L. l' f/ d0 u' i. M% q* a
dtype: int643 d& V3 |+ }8 |6 N; Y& `4 `
9 N0 Z1 M( w7 \( o
s.astype('string').str.len()2 }% F8 v" s! O! D, J* X' I8 U
Out[18]: 4 h* M* F0 h& Y8 d
0 1
0 t# J: z4 q6 adtype: Int64
5 V" y) G& [& H, I- a6 ^2 L9 {' ^/ r1 i% A0 ^9 X- ^. E {
s == 'a'
" ~0 O; G6 C% D6 D4 yOut[19]:
' p; @- D& K/ S3 Q0 True6 Z. V. z4 O* `3 j; r' N
dtype: bool" k) U! ?2 b* t% r& A% o
0 x5 `4 I" j; J8 zs.astype('string') == 'a'7 a/ L( @6 a) d2 w6 H4 C
Out[20]:
3 M. \- Z" p1 ^ \) b$ ]+ |0 True4 b! k1 P0 b5 N8 C" K6 Q2 N
dtype: boolean7 ~( U( X5 v a( t/ w+ L
: y6 y' J) ?) S: D' f* ys = pd.Series(['a', np.nan]) # 带有缺失值# M, j6 l! D2 B# t
! p$ a) \' Z0 S3 y8 ds.str.len()! ?( ]! B3 E& R( k" f
Out[22]: ' I* i' |/ B5 M6 ~- V, M
0 1.0
+ i6 o9 J! k1 h! s4 }' {1 NaN5 N' q4 n, x2 }5 s
dtype: float64
% ?7 L, e) [" z# r+ P8 J7 D. `! ~$ t" P6 o, z" L8 W; Q
s.astype('string').str.len()0 G+ R2 \: i2 `4 I$ L# ~9 M s2 n3 ?
Out[23]:
% ]9 v$ w* Y. x* [0 1! o6 l# c9 m% @ D4 g$ d
1 <NA>
# ~' L7 k2 O7 f% V; `dtype: Int64
! n6 q$ u' V O: N* b, k+ N# U- \ @1 A( Y
s == 'a'
. M% O3 U M" p& I" K0 VOut[24]: ) V O z4 u* _+ B! q
0 True
9 h% w/ w4 e( R' U8 [6 f& m3 o1 False, w% Q) V& d/ k& [* @: S h
dtype: bool
' z m% h' o1 ~: J) o2 D% j6 F/ E; p$ P. W! c# {
s.astype('string') == 'a'- X4 V' ~* Q" e6 y. Y
Out[25]:
( K" I4 f3 N( J0 True
+ i- U9 G" y; k K7 d1 <NA>" P0 H& {" E1 k0 R$ u9 \8 X
dtype: boolean
' e( ~/ r3 e! I3 F8 e
2 `& \; k6 V, K n2 z19 i. a; s: C" V3 e0 N* z0 D& k+ e* l
29 ~- Q4 f' j( s6 c" J7 M9 M" @ ]
3- T4 t3 P: w2 y
4
. @4 d' x' w' d( S5+ x3 z g/ t/ R N" o7 g1 m
6
) D9 I/ E9 r. ?* I% R7( c2 J t. i7 D; l5 ]0 m A$ |
8; i+ x% u) o: w& @ D% K
9
% h# Z1 T; X6 h# X0 {10" G9 \5 K+ U5 d# I# r) W
11
# ?5 \7 s) n' D; U; @3 {' s& ?8 x12
$ V$ ?$ ^" w+ ?9 T13
, e+ \1 z: g* }' b14
! B$ X- K7 l _153 P. y) ]1 W, v' F5 G' d( J
16
8 t8 ?- z7 S2 o; \170 |; n- T l& L A& [( I* \
18" v, u! m6 n: h- b/ M) I
19* U; X4 W2 r( N1 A+ {, w
20
7 |1 d6 w% C: O0 Y21
; { o: t5 ~8 R6 c, Z22
/ p9 s! R1 ?& g2 `/ J23
1 v$ x$ V) Y3 d' I' a$ _9 f240 Y) I0 T0 O* U/ o4 A( `
25
! u) `" w% p4 m8 \6 ]8 \26
7 J3 b5 _ b% r) f$ g$ b$ e27
" ]- [/ E5 x" ]! P9 _6 X9 J28
$ ]$ ^& s1 ^; q% h' U% P29! M9 w* k. F5 @8 X4 B0 I4 H
30
& q+ M+ R. U. o" |2 Z% k31
9 e/ }' F9 _2 I; j# L32
7 d0 F8 C+ l; u5 Y33
! m! |" I3 Z: X6 m34
* ^6 I0 r X9 W0 w8 j35
4 Y# G2 h& o& b" g36
8 L& y' A G6 S1 J37
! b( P' E. W I I( c# G38! U: e3 y. e( V' @
39) z E3 l- P: p" Q$ F7 g
400 S- n6 R4 Z* o* ^3 l
41
( H( r% d+ M1 V- h( c. X8 R' O Z42( v6 \# I( E1 `
43# ^" d0 H, U, y @
44
" ^# u" I2 c7 X! S( R" ~) M45
, w$ f8 y' P2 k, C7 j. S) u46/ M. X! z ]2 L% g$ m0 X
471 Y* z% d4 U' s6 _/ h
对于全体元素为数值类型的序列,即使其类型为 object 或者 category 也不允许直接使用 str 属性。如果需要把数字当成 string 类型处理,可以使用 astype 强制转换为 string 类型的 Series :. m8 |0 C8 o9 @$ K
- [% }6 R* N& V/ `s = pd.Series([12, 345, 6789])
0 ^/ h$ {' Y7 u& I7 Q- ~3 s3 s$ ^4 A% I. h# @) h4 a" B0 T+ S" L V" [
s.astype('string').str[1]
+ g6 \3 x9 K1 o6 L0 h* EOut[27]: ( y# s5 q5 n. m4 n8 F$ I
0 2
) Z0 z$ }* I: {; i1 42 d# v* r. c/ L; \
2 7) ?* G* p; Q: j! q3 I! Z
dtype: string
% i2 h8 r2 C6 E0 h19 g5 j$ f2 U' @* e( } `3 K/ Z
22 G/ u- H2 ?* Q. t: l- k
3
; Q5 R t5 W+ c: x3 D- P4
/ U) e3 N0 s3 j+ U3 ]9 u5! J0 R8 A' I b
6. s. Y! }, q$ o: z) y/ J
7
5 ?4 x y9 ]7 M$ T84 E# ~% U0 u/ x$ L
8.2 正则表达式基础
8 H1 M, i2 E' g, @7 I8 N3 t! R* B# }这一节的两个表格来自于 learn-regex-zh 这个关于正则表达式项目,其使用 MIT 开源许可协议。这里只是介绍正则表达式的基本用法,需要系统学习的读者可参考《Python3 正则表达式》,或者《 正则表达式必知必会 》这本书1 b$ }' k/ c; Z5 q0 `
' `+ G5 N- F6 w0 _ w3 E$ t7 f, O9 f, l
8.2.1 . 一般字符的匹配$ z" a& d2 E A" U4 b/ s
正则表达式是一种按照某种正则模式,从左到右匹配字符串中内容的一种工具。对于一般的字符而言,它可以找到其所在的位置,这里为了演示便利,使用了 python 中 re 模块的 findall 函数来匹配所有出现过但不重叠的模式,第一个参数是正则表达式,第二个参数是待匹配的字符串。例如,在下面的字符串中找出 apple :
, _( D. z& R9 ~% r: M; B# s
4 X" P- y! w8 O8 C4 x, P. fimport re
6 P! }" F- b2 l# r; ^% p' }& J
+ D& |, a! B5 O3 f' T6 Bre.findall(r'Apple', 'Apple! This Is an Apple!') # 字符串从左到右依次匹配
1 J( }) _( M! F; Y6 B3 R4 xOut[29]: ['Apple', 'Apple']& A7 E: R0 ]; I. E7 B7 O# j8 W+ p! c
1, ~' m$ n5 G" K4 R
2
3 R6 [. A# u- H' {; [1 C; @31 { @$ {# a7 \: M
4
$ A0 @8 `3 Y, H0 F/ ]: D6 Z5 I8.2.2 元字符基础
7 e& f% B, M( H1 x# Q: D2 w元字符 描述
3 Q* k- f0 l- p# y1 M2 ?, d. 匹配除换行符以外的任意字符
7 ~% w7 N: {1 m0 H[ ] 字符类,匹配方括号中包含的任意字符
, F! ]3 p) }) q: t0 K: [[^ ] 否定字符类,匹配方括号中不包含的任意字符
' h4 N; S4 M7 C9 v- a+ h; T* 匹配前面的子表达式零次或多次
2 W; `8 b+ s1 Y& T( _; p" G: w2 {+ 匹配前面的子表达式一次或多次。比如r’d+'就是匹配数字串,r’d’就是匹配单个数字7 Y0 w. w7 r( B# r( ?! g
? 匹配前面的子表达式零次或一次,非贪婪方式* p1 E1 d C& F: p5 j; V3 r, M4 U; X
{n,m} 花括号,匹配 n 到 m 次由前面的正则表达式定义的片段,贪婪方式5 e" ?, i+ X; m4 E
(xyz) 字符组,按照确切的顺序匹配字符xyz0 Q; K& K% V( \. F) A9 S; I: _- T
| 分支结构,匹配符号之前的字符或后面的字符
2 y) n/ T7 \+ b0 z- u4 V* p; z\ 转义符,它可以还原元字符原来的含义
1 n4 s1 m! M7 P* q( T7 _^ 匹配行的开始
4 U- k5 z6 P3 a/ ?! s5 N$ 匹配行的结束& A; ^$ J( z5 H
import re' A+ Q: X2 s' U
re.findall(r'.', 'abc') F [: h3 t* K7 [( Q
Out[30]: ['a', 'b', 'c']* Z$ G" w$ [3 w% I9 D
" F, O( w: u% H( ]1 Q4 hre.findall(r'[ac]', 'abc') # []中有的子串都匹配' {4 Y+ d- s$ N6 d* d- d1 _
Out[31]: ['a', 'c']
7 ? P$ Z& c" q, |( V s- f6 L
7 X# H. x X. E2 U/ D6 N) S4 J+ Dre.findall(r'[^ac]', 'abc')
4 h2 u- t( O8 @4 n; Y- v2 w) }Out[32]: ['b']
) j- [, J- C" q1 i0 d7 d% a2 |
4 L5 F* Z0 J$ p7 @! F; M3 zre.findall(r'[ab]{2}', 'aaaabbbb') # {n}指匹配n次* \: Y E O6 M8 i/ A, c
Out[33]: ['aa', 'aa', 'bb', 'bb']
5 }% }3 e1 |! }( i) F W, ^5 S/ W1 x
re.findall(r'aaa|bbc|ca', 'aacabbcbbc') # 匹配前面的或者后面的字符串
1 i8 [) e- P$ a: kOut[34]: ['ca', 'bbc', 'bbc']5 k' l; ]5 j) f, h; z5 c' {/ a
; M) k8 Y- V3 ?$ I# 上面的元字符都有特殊含义,要匹配其本来的意思就得用\进行转义。
" R( d( X/ T+ ^/ M"""
8 ^8 M0 L- c# A# ~* }1. ?匹配的是前一个字符,即被转义的\,所以|前面的内容就是匹配a\或者a,但是结果里面没有a\,相当于只能匹配a。8 j1 _1 ?7 n+ K$ Q
2. |右边是a\*,转义之后匹配a*,对于竖线而言左边优先级高于右边" E) B% u: j2 L+ M4 `
3. 然后看目标字符串aa?a*a,第一个a匹配左边,第二个a匹配左边,第三个a虽然后面有*,
) o- y+ F' f1 m: o但是左边优先级高, 还是匹配左边,剩下一个a还是左边,所以结果是四个a
: W1 X" M4 X7 @: Q5 W* V"""9 ^! P9 V5 b" W* y" t0 y- c
& g. Z0 k, ]* Q1 U% r+ G- @8 a& Dre.findall(r'a\\?|a\*', 'aa?a*a') # 第二次先匹配到a,就不会匹配a?。a*同理。
0 C! c1 |3 Q+ |Out[35]: ['a', 'a', 'a', 'a']7 m8 ?$ g* J# O+ X
- L. W- x# o) O9 l# 这里匹配不到是因为目标串'aa\a*a'中,\a是python的转义字符(\a\b\t\n等),所以匹配不到。
- c' _7 r4 x" n, D% ^# 如果是'aa\s*a'之内非python的转义字符,或者'aa\\s*a',或者r'aa\\s*a'就可以匹配到\字符。- U, l) P! s% @% v6 T* e
re.findall(r'\\', 'aa\a*a')
% j& |: t9 Q% l& a/ {- {& d( j, m[]$ L3 q0 X' v2 q* R. r
1 A( }2 |+ {7 W3 l8 a5 i- |( o1 g
re.findall(r'a?.', 'abaacadaae')
0 T0 `3 A( `0 q, h1 Z Q* OOut[36]: ['ab', 'aa', 'c', 'ad', 'aa', 'e']5 [/ V; u: F5 ~3 H7 W' g1 p8 d$ W
6 L+ i* m2 q* tre.findall(r'(\w+)=(\d+)', 'set width=20 and height=10') # 多个匹配模式,返回元组列表& r+ k' L) D4 z# Y0 \
[('width', '20'), ('height', '10')]
$ A. L& h/ d8 ]* v2 [# \$ x. H) ^/ ~; t# S8 @* L+ G* Y% V/ m
1; w8 l5 @3 h. |. `5 f
2" o0 h8 v; `0 P3 e& n
3
/ U( f, [- V# M41 ~& B- N, N8 v, M! C3 c
5
7 F. u d% I! b+ \6- o% D5 r" {0 u% u3 ?9 P4 O
7
- b) f: p4 i6 W8
! {" m2 W6 ]: a& G. K {: L9. {, Y: N5 F1 D1 ~
10
/ U( ]# s% M1 ?( ]# D11
& S8 [+ H/ b z12
% g, m; q. k2 \) \6 A: @3 O! [136 c, L6 Y7 O7 W* R# R& Z( a5 B4 H- b
14
2 l5 c" |) b9 N* I. L: v, u153 V! z! |* w9 X+ |3 s9 s
163 C w8 D8 k$ a6 F. T8 R
17
; O1 L# Z+ j, P: @2 a# Q& X2 p8 M18
, v7 s, m* ^6 e: d% C19* ?( A( A \9 C4 |* o9 ~5 c$ A
20
/ ~. Y7 Y; T% |21% n7 {! ^2 K8 n/ h B( e0 Z
22. G5 _( s0 e/ W) y) L$ T9 g% z
23& l7 u8 {0 ~$ k: M9 W, h$ N
24% g4 A+ v$ A5 ]; q! o) o. B$ {" _
25' z* \0 g0 J: }
263 D0 ?# g# ]! U1 j8 B
27( U8 ]7 s6 s3 |+ j% e
28. r7 e b- ]0 G- P2 U
29
: l1 z n" T; Y$ p) p/ ]% C30
. W/ R9 \5 v( u: G' O' v: g. e* k& l31
) P" C: q& j5 n: k; o/ f( {3 C32
" ~6 P6 G' z; O. t33$ E# H- p+ m# o' W m: u; T% j/ ~
34
# ~9 S7 f2 Y( Y) l9 T( Y4 d35
% y% s% D7 U" d3 N' z# P) v9 E367 v4 |5 M1 z- W J9 r9 h) F, x
376 D# C# O$ r9 X+ I% G
8.2.3 简写字符集+ T1 x( M' o% H' O0 S# J! G& x" H: k
则表达式中还有一类简写字符集,其等价于一组字符的集合:
. W, s7 m U/ C/ g) [' G- @4 o9 r) s# k/ ~2 S
简写 描述1 y( ~5 ^- o! f% B( W9 U
\w 匹配所有字母、数字、下划线: [a-zA-Z0-9_]
4 o6 `3 z' a y1 n' O0 M. d\W 匹配非字母和数字的字符: [^\w]
+ \7 x4 ?. s$ Q\d 匹配数字: [0-9]. s* D$ `! g! v
\D 匹配非数字: [^\d]
/ R' H2 Z2 W( N) _! S4 ]" w" }+ z\s 匹配空格符: [\t\n\f\r\p{Z}]
* W2 W: l5 W" z8 I: M. ~1 y\S 匹配非空格符: [^\s]
6 a$ O" H$ k/ H, e, M0 }\B 匹配非单词边界。‘er\B’ 能匹配 “verb” 中的 ‘er’,但不能匹配 “never” 中的 ‘er’。8 B# a( `! o9 i5 j5 T% B
re.findall(r'.s', 'Apple! This Is an Apple!')
* h# h+ R" e7 @Out[37]: ['is', 'Is']
3 B, X) @9 h. W" W
! H- U8 S# u8 s0 c$ Z! v. |! `re.findall(r'\w{2}', '09 8? 7w c_ 9q p@') # 匹配任意数字字母下划线的组合,但必须是两次
2 h4 j0 W' @8 g! ~' `: Z8 AOut[38]: ['09', '7w', 'c_', '9q']7 b: ], o$ `9 i X/ T
* A7 q" k3 q9 R& _7 R& r3 F
re.findall(r'\w\W\B', '09 8? 7w c_ 9q p@') # 匹配的是两个字符串,前一个是任意数字字母下划线(\W),后一个不是(\W)" w8 f5 Z8 }' `1 g! G
Out[39]: ['8?', 'p@']
( h) t5 h2 \4 t+ |
$ K0 ^# R$ U% V# W$ _re.findall(r'.\s.', 'Constant dropping wears the stone.')+ ~8 i) H* P3 G# h3 m( j+ ^6 v5 u' t
Out[40]: ['t d', 'g w', 's t', 'e s']7 i4 u3 H. x( J/ ?! S5 v+ m
7 v; G9 m$ b/ ]: Y4 kre.findall(r'上海市(.{2,3}区)(.{2,3}路)(\d+号)',9 @7 } r- Z. s, \2 R0 @7 e* I9 X6 Z
'上海市黄浦区方浜中路249号 上海市宝山区密山路5号')# `, f% j0 j( F( C2 D( m
0 f& A$ ~8 r7 b$ R3 P/ MOut[41]: [('黄浦区', '方浜中路', '249号'), ('宝山区', '密山路', '5号')]# M9 N* L# h3 k* K% H8 Y" G
+ Q# W2 c% i0 ?/ U
1
; \" ? {$ h E2
: l: Z7 A- G9 v' f7 g+ }3
+ |# h) R% {& B$ n+ F, @* S9 G4' r7 q. B* F/ S" c/ `
5% D; g# M4 w6 A. B# I# D
6* j/ C4 a. q! {* a2 y5 \
7
# a* J8 i3 Q5 B7 ^: H8
8 s& K! Y* g8 O w* {6 Y# X9 ~9
3 L* I: G0 l4 X8 d( z10
6 R2 R% {8 k* t) A7 M" M5 @- q11- f6 L P4 y" C x# w2 v
12
5 w4 H" d/ P. y- ^7 V5 R1 Z Z13
3 T& ^* i5 p. Z2 _14( t$ f& F L: i" x
15
* I# N- ^3 r5 k6 ?7 X7 ]16
% Z- D2 @3 z, ~# ~- e- Z! t+ P8.3 文本处理的五类操作
( r9 h$ g$ U4 F" K4 n8.3.1 str.split 拆分# o4 R! F' w4 R
str.split 能够把字符串的列进行拆分,其中第一个参数为正则表达式,可选参数包括从左到右的最大拆分次数 n ,是否展开为多个列 expand 。
/ z/ N6 }+ \0 R1 O* j% i, W+ h9 {" N
s = pd.Series(['上海市黄浦区方浜中路249号',
. `6 a4 `' f5 O: L! w/ x '上海市宝山区密山路5号']) I- B8 [+ J( \$ v8 v$ U8 i: }
$ S" [4 `5 a5 o" \7 ~2 ^; I
! e% Q3 E: D4 C. }* k
s.str.split('[市区路]') # 每条结果为一行,相当于Series
' `( p4 u1 ], x" eOut[43]: 7 M7 j2 A* @3 x$ F" N7 a) ^5 h
0 [上海, 黄浦, 方浜中, 249号]
; s% k; ~9 O- E& z1 [上海, 宝山, 密山, 5号]
: r0 v4 Y) g y1 Ndtype: object
: u5 z2 \2 p6 m- p+ p/ n T a, ]# |; t1 \
s.str.split('[市区路]', n=2, expand=True) # 结果分成多个列展示,结果相当于DataFrame
, R/ j, n: D$ iOut[44]: # A: l( o; V& r. ?
0 1 2- p" T+ w2 _2 i- C( K+ S6 J! B5 i0 n
0 上海 黄浦 方浜中路249号
" q0 b. g0 f0 @) u& k5 f1 上海 宝山 密山路5号( Q3 |7 J/ ]0 x( N0 ^
1
& n. Z2 W4 p: {) m+ ?, s" w3 ^6 C26 K6 T8 b/ ^2 W+ d8 n- z
3
" ~$ O' H( s$ x0 h46 R4 W/ u' W' |
5; Z7 x; \( Y6 l' R$ D! L& r
6
+ D) U- o0 F3 {2 M6 C& p7! N& s7 u" _. J! b0 U5 D( w; J# q
8! U: ^( ]* V4 L! o% c
9
% X! n! d' u4 B ^4 S9 Z$ u/ o10( V/ U% ]9 N- g$ J9 M
11
: o: H9 O7 j) j7 b% x3 S12; a' ]5 `6 }- P0 E# n
130 v* N8 M- o; |2 \
14
2 Y+ k% a' s: _' n- |15
0 K% I4 j* k% c9 j 类似的函数是 str.rsplit ,其区别在于使用 n 参数的时候是从右到左限制最大拆分次数。但是当前版本下 rsplit 因为 bug 而无法使用正则表达式进行分割:
& G) P O* E$ A4 |7 r1 {! F9 _3 M1 ?0 n N: b7 u" P
s.str.rsplit('[市区路]', n=2, expand=True), ^" c& q7 b# V' R2 p( s/ r8 y$ B
Out[45]: 6 L7 m5 T$ f4 ?- Y4 z# j4 V2 G
0
7 D1 ]1 y9 Z* [7 K% q6 P# y0 上海市黄浦区方浜中路249号
9 }2 \" T: C* H6 e: E7 r/ s1 `( P) d1 上海市宝山区密山路5号! }. j/ C6 d/ o* S& K$ P8 w
12 P9 \2 c% \; V0 P/ e0 D# e
22 l2 w* I) P& {/ Q
3
|+ h9 c* f5 X& O" y4' J' Y; P' [+ B2 b* z$ s& _
59 }9 g0 k8 _0 K+ Q% i5 s8 H
8.3.2 str.join 或 str.cat 合并
- l/ M, k/ I% Wstr.join 表示用某个连接符把 Series 中的字符串列表连接起来,如果列表中出现了非字符串元素则返回缺失值。" }- m( [" p/ \ t! [
str.cat 用于合并两个序列,主要参数为:
0 M U+ j8 c, ?9 fsep:连接符、
/ j% ^! H. x# X) e4 O! s! F+ ojoin:连接形式默认为以索引为键的左连接
/ G s% l( n. t( m$ @0 Q8 B6 \na_rep:缺失值替代符号
8 V% ?. r( t: N* Ws = pd.Series([['a','b'], [1, 'a'], [['a', 'b'], 'c']])
5 ]6 N* ~+ ?6 m) As.str.join('-')/ g% t0 u- N+ X, R# E* I
Out[47]:
0 P) z1 z. h X8 D H/ B, p/ Z0 a-b9 `' N5 H; Z# ^4 u" c/ e: J
1 NaN5 W; O$ ]# q5 D8 J# r+ ^ C) i
2 NaN0 _4 J4 Z( k, E: f/ _7 u1 i
dtype: object
; f5 @" V+ l( q1. r- k: n- Q/ B1 d7 V, |+ ^
2
* n* D7 w1 o/ \2 z; p. J$ l& I* d3# h' D: \& Y* O8 y3 }. @3 e, p
4, b9 H( M8 N9 t
5 n4 J b7 X. E0 d
6) s8 ~3 F# i/ x/ |5 j' Y( l
7
9 o* p. E$ J9 f. X2 M+ K, Js1 = pd.Series(['a','b'])
; S; Y% }( o3 j1 o$ @s2 = pd.Series(['cat','dog'])% k) v! Z, k& E
s1.str.cat(s2,sep='-')2 [9 f8 s5 q% m3 r C9 h Q( M6 I
Out[50]: s9 E% \4 E- C* i1 v
0 a-cat
1 h. n, B C/ G+ Q7 b: }; C8 {1 b-dog
0 F) q2 e4 {' s* J. a* ydtype: object
7 w# Y; Z4 ]! V& X
! g+ I% ~7 \/ ks2.index = [1, 2]3 W( Y% I0 R a, M2 O0 K" X
s1.str.cat(s2, sep='-', na_rep='?', join='outer')
7 Y8 J8 t8 p% N( z6 yOut[52]:
' T$ K. d \7 u0 a-?
7 r# U$ j S! N, d! d$ J7 |1 S1 b-cat
+ G( R$ O9 d& _8 \& V _7 e2 ?-dog* {6 c5 E4 f2 v' ^
dtype: object0 i9 Z6 {: G! u s: v4 ~
1
5 I# s0 e9 K+ J& {28 g' w- y0 m( D5 B. Z3 w
38 z) f9 l7 a# q
4
+ N1 B4 B( `& j5, h/ A* y" {8 S$ h# ]9 F
6# U6 K, N$ k3 \# W
7( n. y+ w6 T5 y+ F
8
6 b( r" K$ a* d x9
3 z8 N, h7 f8 c! m! ]& V# p) e; _1 h10
2 E+ `0 N) F3 j11
# ~* R* `6 ^' d }8 o( z8 E120 s, y& u6 M+ ?& q
13" b& l3 k: U; j( O+ ~( z
14
. G& `7 @' [2 R ~2 k4 Y/ n) s) ^15
, _4 F. F- k6 N% D5 v& W: G8.3.3 匹配
; ?' N% g2 e. Rstr.contains返回了每个字符串是否包含正则模式的布尔序列:
. L& Y) Y8 i/ j7 |s = pd.Series(['my cat', 'he is fat', 'railway station'])) E2 X7 p+ U: U4 u. {& [. V3 o
s.str.contains('\s\wat')
8 r* l! K: a2 D6 b" G3 h: E
$ i1 D. C) O9 }+ {2 i. D7 q4 A0 True. [( U1 r6 `; C" u' M) p6 B' x
1 True
, z, Q/ ]) g. b5 F4 Q' I2 False
* t; f8 }* E! ~9 A/ e) e7 R- Cdtype: bool
$ `/ e6 ~7 N; R1
* ~" y+ z& J- C2
8 i5 @0 T5 h8 ^, V6 e' G, b3 ~$ W1 d; u) V* ?
4' C" h$ h. V2 x$ S
5
$ D& A2 D" w+ X8 v6
2 N: Y9 T8 r) ~ ~- h. [70 J8 C v) f7 A* Z5 o$ D
str.startswith和str.endswith返回了每个字符串以给定模式为开始和结束的布尔序列,它们都不支持正则表达式:5 f% Y, [: j, ? r2 h; ?6 z
s.str.startswith('my')6 ^% a! |" i3 Q8 O3 M7 o: G7 r; J
) K- T8 Q. t) y/ n. e# d
0 True8 h: A4 b* I; D( g
1 False
$ c K( V9 ]* R4 A6 l( W( u' h2 False9 _8 ^# C$ O0 ?
dtype: bool: W' i1 Y5 R+ ?: w% `" z
11 h8 ]! a6 o3 \2 M# x5 u$ ~
2& a) n9 n j% {& t
3
0 w- f) @+ @% w U: L9 v4
) ?' }1 L7 u( b2 E5
c5 d9 l+ c2 a8 ]8 f' [" B* m( n" A63 N) M* B& a0 i1 ?8 w8 v
s.str.endswith('t')# Z3 D* c% R- J, Q
) I1 o& W5 w7 t0 True: { D7 z) F/ I! P! m! y5 @
1 True
) b/ J5 b2 S& D& a* x$ p8 ^5 f1 K2 False
. K t& f' D; {. g% L2 _dtype: bool/ q. q% Z( D# k9 S+ K1 q& P/ |5 i
1
9 e& r7 L- V* Z2) |- A& ^7 S; F) L; @
3
- F& M; ^- B1 @% @4
0 E3 W, ? \6 @5
" l H; P+ P1 ?! q7 [62 I- D/ l$ W4 _3 [5 i. l# F
str.match可以用正则表达式来检测开始或结束字符串的模式,其返回了每个字符串起始处是否符合给定正则模式的布尔序列。当然,这些也能通过在str.contains的正则中使用^和$来实现。(貌似没有python里的search方法)
% {( I4 n3 x% b! Z( }" Y) Fs.str.match('m|h')( r# X; _8 b' o6 r4 K7 {: p3 w
s.str.contains('^[m|h]') # 二者等价
. R1 c2 ]+ l; t/ a+ M: q8 D& w$ _& K4 d8 y3 n- n0 h+ ], V
0 True
; W# P# @& V+ U6 Q% {/ g2 t# m1 True
4 `4 F$ D) \7 \* _2 False; b5 G7 q! |2 N+ c
dtype: bool
" }: o7 S" `- v0 D, }2 P7 ?/ L1$ o" f1 f' M" t" {
26 v$ j2 i" N8 x. j% ?1 }3 X( o
3" @, D- Q% Y7 w4 N- {1 U% x
4
4 w5 w! O% A2 R8 L9 F* {( G. M5
1 {3 y1 I/ @7 z. x6
! ^( D Z W9 K8 k0 x/ v) x71 b1 F( j$ O3 q$ ]
s.str[::-1].str.match('ta[f|g]|n') # 反转后匹配" v. |% B6 i3 n0 m
s.str.contains('[f|g]at|n$') # 二者等价
8 ]) m# C* v0 @2 d; T+ y' q
; ]) w0 o+ M3 ~$ O4 e4 p0 False, S6 B6 `1 W. e' y3 D& o
1 True( l" v! B9 L' s( d$ r
2 True
( [; t. C e3 u+ W% Rdtype: bool+ A \: V! W% O' Q7 O
1
- L& u/ e) {; q4 m2
/ e$ Z8 F- X6 P2 Q$ h3
% r8 c; n* f9 X }4
; V2 s2 Z- J7 L9 a9 r53 _: K/ U' Z I' i4 ]* J
6. p* X, R5 g- \- }0 W3 a/ ]
7/ U! B" I, [$ h m. P2 [% k
str.find与str.rfind返回索引的匹配函数,其分别返回从左到右和从右到左第一次匹配的位置的索引,未找到则返回-1。需要注意的是这两个函数不支持正则匹配,只能用于字符子串的匹配:
1 o6 J4 e$ }, w" L) j0 U- Qs = pd.Series(['This is an apple. That is not an apple.'])5 w6 ~6 |/ w; R9 Z$ o4 f$ J1 k
) R r. i4 i( I
s.str.find('apple')7 v; Z- b- q$ e! ?' e& w) A
Out[62]:
) u3 ~- \+ t* y8 V2 l! n9 S, Z& n0 116 [9 P& R* j- ?# v6 ]5 U, ~ M# y
dtype: int64
8 w5 @; C) ^; W3 A( x4 H7 K1 Q' X" Z8 R" S, k% b' y% e8 D+ y
s.str.rfind('apple'): U7 {0 U) `0 D! w' F8 s8 ^
Out[63]:
# o% \; `! b3 W: F3 A2 N0 33( ]$ j$ i- C# D5 C2 Q5 ?0 Q
dtype: int641 v3 p- C5 d' U6 D. u
1
0 O4 a7 G9 C- K/ K- s1 V% i) t2 f1 N6 H2
4 x7 R0 R, J; S! |( o& N% [0 a" L6 W3
5 Y7 ^: i! d; W" R' Z. }" X4
1 \* x+ w# y. y& I+ w5
( A x; `5 k- N" \ [6
- f2 J7 N9 i0 O D6 j2 Z75 H* Y( p1 i5 w4 N8 P; @; \
85 a( ^8 x/ \$ p# h' l
9
- m9 ^9 _7 m5 _" K( V) u9 k- w$ n10
9 B, }) ~: m2 J' R# y4 `% ]11
5 G8 v2 `, L, [# @替换
+ K! q; n* W( Pstr.replace和replace并不是一个函数,在使用字符串替换时应当使用前者。
" C3 ^" H& g$ n6 Os = pd.Series(['a_1_b','c_?'])
! f* a$ ?% L0 c2 a9 O) @# regex默认为True,表示是正则模式,否则第一个参数内容表示是单纯的字符串,也就是匹配字符串\d|\?: e% q* d: K3 a: |
s.str.replace('\d|\?', 'new', regex=True) 8 {0 u& Z9 N' Q; N- K; S* E
7 S$ |8 ^: D. |0 o5 P$ }, R
0 a_new_b- U8 ]0 W8 a3 Y9 P& y
1 c_new( a! q' c( [( j6 _% M; |& X
dtype: object; X* i M: n. N9 H" U' Z9 ^
1
$ Q' C4 ?; G o6 h5 E2
- i. Q0 H. Z! R9 o" h3 g% Z; S4 T4 o4 C Z! I8 I h* u
4
3 X1 N( m3 `% Q! z& R- u52 b0 g8 z- v: g4 N2 f
6) f3 O* p. U9 F4 h, T
7
3 X8 ~9 D, h4 U: y5 ^0 }- H3 e 当需要对不同部分进行有差别的替换时,可以利用子组的方法,并且此时可以通过传入自定义的替换函数来分别进行处理,注意group(k)代表匹配到的第k个子组(圆括号之间的内容):1 P9 u& {/ a8 w/ P j
& U" Q! U& s; ~6 U! k
s = pd.Series(['上海市黄浦区方浜中路249号',5 n: ^7 g9 N7 B: H# H! l% C) Z
'上海市宝山区密山路5号',
( [& D; K& W* J" E '北京市昌平区北农路2号'])
3 v' `4 k! k& ]& l+ t( Vpat = '(\w+市)(\w+区)(\w+路)(\d+号)'9 y- ]3 j l% E2 F! E
city = {'上海市': 'Shanghai', '北京市': 'Beijing'}
1 n& T" f( z2 _; O6 n, C/ P3 ldistrict = {'昌平区': 'CP District',6 @* i0 |# O6 @8 R' G2 |
'黄浦区': 'HP District', s* O% v4 ]" I1 m2 J
'宝山区': 'BS District'}8 c" M I: E# ~. i, e
road = {'方浜中路': 'Mid Fangbin Road',
& e" Y5 Z/ s5 x4 s '密山路': 'Mishan Road',
+ q) T; t$ N. \" f& O4 ^* M '北农路': 'Beinong Road'}6 g0 Y# G. x8 b# u! S3 a
def my_func(m):
7 l" r7 ^$ a7 }$ Z1 q! r str_city = city[m.group(1)]# P9 }; p# A- M* y, n/ }% d
str_district = district[m.group(2)]# }3 u; P# x$ |2 A+ q& Q7 J$ O
str_road = road[m.group(3)]; R M+ i5 n; r7 O* c/ w
str_no = 'No. ' + m.group(4)[:-1]
3 ]3 z" N; _! ^2 s1 P4 t2 j return ' '.join([str_city,/ p$ l/ ]1 b7 c& X9 A* Q& I
str_district,
( X V% o3 X8 B8 p1 A str_road,# i9 @+ |$ m/ m r6 {- A2 ^% j% Y5 D
str_no])
2 h2 z* ?1 U7 n5 G( k& Ls.str.replace(pat, my_func, regex=True)7 P( l& t* ?) B4 j$ E+ c
! J( e' v6 Z9 Z1
5 j M" _" T! b( ?+ R1 K2
8 k! `1 D" `7 J# b# H3
/ B4 O. w" t7 X M4
. ]4 l& E6 h" i5
% L& Z' p0 t& H; P6
0 Q6 c: {. O6 D& Q7
+ u' y8 p( C% [8
: M3 E! c4 V& M2 m& h& E9
5 Q; a) ~' H9 f4 \10
- j q5 n# M0 G4 g4 d1 {! K119 Y5 Z5 u% t7 w% L
127 _5 Q) L+ o( s% m7 u
13" q$ P6 _% E/ y7 ^# S
14% f, d+ Z5 k! v* N) q3 S
15
+ u2 Q* N& T8 j0 s# F16" C p4 k9 `) [) \$ j- V
178 G4 h/ f+ k1 o7 [' n& t
18# ]/ T! n% ~" ~, W
196 ?; I% P. [4 C
206 @3 j6 H# r- W0 e& h# H N
21
" N. Z' R6 m8 Z) V0 Shanghai HP District Mid Fangbin Road No. 249
) X8 l* B+ \1 L) n O; R7 g* \/ X8 _1 Shanghai BS District Mishan Road No. 5
1 r$ ]3 ]5 w5 i2 Beijing CP District Beinong Road No. 28 e/ e+ B' Z% D4 e0 ^
dtype: object; X# C, E U. Q t
1
) b- k% G2 E n% q6 g' m2
+ S( t7 o7 P5 z2 d9 a7 \38 M- U* T7 f" d. }0 @" L* \
4
6 `8 o8 a4 [, _这里的数字标识并不直观,可以使用命名子组更加清晰地写出子组代表的含义:
; w: k, Y h* B9 K9 u+ h0 I# |1 \% Y7 P0 E& z& _# B* |
# 将各个子组进行命名! [7 C! H/ A: L& u
pat = '(?P<市名>\w+市)(?P<区名>\w+区)(?P<路名>\w+路)(?P<编号>\d+号)'
" ^1 {/ E9 r' Q$ u7 Mdef my_func(m):) J: n% @/ R2 c3 `, _
str_city = city[m.group('市名')]
1 i |; @9 p K- w; B str_district = district[m.group('区名')]
/ K; M5 Q* g% x% C str_road = road[m.group('路名')]
6 v0 h$ ?3 t i9 U L" H4 I str_no = 'No. ' + m.group('编号')[:-1]
+ c+ T, U' n) M9 J% \3 J return ' '.join([str_city,
- v& o6 b6 I) I( e. r str_district,
. [5 K8 M" L" j* N( A- l8 ? str_road,
; a; F- V2 w5 l' w str_no]): w5 |% {2 t% M( u9 m
s.str.replace(pat, my_func, regex=True)
$ b0 `& E, F$ c1$ Y( O4 I" G# X1 _! d8 k! m
2
6 d4 ^$ m w @) U0 T+ d3
/ J! x+ E& K* \4
0 ^" q9 Z- a9 ~, B8 |4 F5
: a6 q4 \, F; t0 y" T2 r. t8 a68 o' X6 \6 j( t. n
7
2 p5 f Y# Z: O. Q6 T9 J8
1 P3 @( v% T' d5 C- I92 f. X6 r9 B; E; [& x5 R
10
6 R! r* ]. m% `( t( j8 W. G% Y11
* Y7 Y1 U% m' c2 l7 S% b h: {" |12
: c4 A9 |( ?0 r0 Shanghai HP District Mid Fangbin Road No. 249
9 U) h: v v- s) X/ I: n( `6 ~3 B1 Shanghai BS District Mishan Road No. 57 M/ M! w) u0 j1 w3 f! k+ v- m
2 Beijing CP District Beinong Road No. 2
' l* @& Y; [: l) Idtype: object
3 g Z$ \, k5 ^1" ]+ ^: p4 q7 v; h
2
$ L9 c' |* R7 s I0 u3
/ W7 j- N% ?! E6 Q6 V4 E( ^- q1 j& ?3 s" C7 t1 T, H; D
这里虽然看起来有些繁杂,但是实际数据处理中对应的替换,一般都会通过代码来获取数据从而构造字典映射,在具体写法上会简洁的多。% g0 i7 p7 I" x6 Y. q) Q
' t! t: z8 n* U7 F& a' P* V8 W
8.3.5 提取
& C% D. k, k" w( Gstr.extract进行提取:提取既可以认为是一种返回具体元素值(而不是布尔值或元素对应的索引位置)的匹配操作,也可以认为是一种特殊的拆分操作。前面提到的str.split例子中会把分隔符去除,这并不是用户想要的效果,这时候就可以用str.extract进行提取:4 @+ K' n' {9 k; G1 {# D" e4 |2 X' f
s.str.split('[市区路]')+ } G% M! r7 R4 c& v* O" s
Out[43]: : k. i" f% I1 c" l' e! e! h
0 [上海, 黄浦, 方浜中, 249号]
9 p) ^1 q8 F* E! j! m& `# J1 [上海, 宝山, 密山, 5号]/ h* ^, [$ x1 `. j
dtype: object
, Z# `9 _* x5 z4 I" a$ I$ g- e- v, |2 S
pat = '(\w+市)(\w+区)(\w+路)(\d+号)'
5 m5 x) o+ Z2 s' v. U! `s.str.extract(pat)
8 F) A$ s E* ~2 k" z7 T* f9 sOut[78]:$ F _) d6 E# Z# _
0 1 2 3: z3 x+ i4 a1 L" \# D) @$ H9 ^
0 上海市 黄浦区 方浜中路 249号+ r+ l5 S2 v _ @3 S4 \) v" f- k
1 上海市 宝山区 密山路 5号
" F: N6 D5 m3 ?2 北京市 昌平区 北农路 2号
- ]2 {8 O) V" r# k4 i9 b1
( e- w. r! s9 {* e+ L2
, S: K# P. X* Y& M- Q3
& B5 D0 W/ o6 _1 i+ T5 {- W9 M4; T' y1 F ~! q9 `* h
51 y ]1 q! A5 l
6
H$ Q1 { K- J7
+ ?/ J$ k; n" F/ y( h3 ?8
0 c4 f. L2 \6 w& x# f5 \. S: ~6 o' |9; |1 _8 L9 w$ H
10
9 j3 Y. p! }; ]* d* }% D H11
8 z$ X. ?% Y2 k A12
9 T7 N" O4 f- Y7 ^# Z13
* ]8 C8 H- _/ \ ~& o通过子组的命名,可以直接对新生成DataFrame的列命名:
1 |4 t5 V9 c5 k" d/ C' V5 E5 z: |% Q" |* D6 B! w' N. A
pat = '(?P<市名>\w+市)(?P<区名>\w+区)(?P<路名>\w+路)(?P<编号>\d+号)'/ ^- R* Q1 _9 r8 u) \1 V& }; @8 B
s.str.extract(pat)
6 X0 z) s* M$ v+ OOut[79]: & K* G+ h8 p& Z& ~' e. Y! e
市名 区名 路名 编号
! C9 o, x, z2 z# J; U. @" T0 上海市 黄浦区 方浜中路 249号7 l$ G5 z( ]8 W! h6 G9 h. C
1 上海市 宝山区 密山路 5号
9 m( C; h c1 h: a; t* `. M; \2 北京市 昌平区 北农路 2号 q+ e: R" v; S" O& S7 _
1
]0 [( ]8 V6 n2 d1 l- }* s0 E6 _4 J* t2
" E% ?2 g$ ~8 i) I5 Z, t3
7 E0 Y2 e* _! O( S: E4
1 G" g& O" a9 N5
6 q6 g% O( w z8 W* r, T. L6
# |; R+ B2 V& h! N7
6 w& Q% F2 D/ `- ~$ Sstr.extractall:不同于str.extract只匹配一次,它会把所有符合条件的模式全部匹配出来,如果存在多个结果,则以多级索引的方式存储:; ?, z' X( Q' s: z, n
s = pd.Series(['A135T15,A26S5','B674S2,B25T6'], index = ['my_A','my_B'])
5 N; Q6 ]8 z" d& F. qpat = '[A|B](\d+)[T|S](\d+)'6 P% X3 V' A- ~3 b
s.str.extractall(pat)
) b8 f, M) O. s6 O8 YOut[83]:, v7 q* P( t# R: A! L/ M3 \% V
0 1
. e3 f$ y3 `' M- Q3 b; U) m' n match
0 q3 W: r7 q6 A2 w; pmy_A 0 135 15
$ b( R+ g0 \( }* b 1 26 5
# K1 v: `9 H' r# S- o9 ]5 v7 J2 Dmy_B 0 674 2/ ^' r8 Z7 c$ b4 r2 u, b5 W8 z" I
1 25 6/ A; d' q% q5 ^; N3 k
1& ~% z4 o8 q# J+ {8 B( K) C$ B
2
6 i# j9 J7 }* m7 o1 {" K9 l! ?3, _5 N. X0 p/ C7 H: [% A
4
1 \5 o$ U0 ?) [- @5
) R4 ?4 W4 n# ]1 @& T1 y$ ?62 f% S3 a3 \1 l$ F0 S4 R% Y t* a
78 d |+ R$ L' Z& ~0 k
8
: y& b. R; S2 g: H9
3 B3 I1 ]* m& n4 {8 R/ W8 c10
! q6 _3 ^# ]% y: upat_with_name = '[A|B](?P<name1>\d+)[T|S](?P<name2>\d+)'
5 x3 l8 c' B L# f/ }s.str.extractall(pat_with_name)
. Z$ i; [9 ?8 w7 ^5 ]: NOut[84]:
2 E- }# X6 J o# e. M name1 name2
4 ` E5 [" p3 x5 ]! u8 } match
/ j6 Y* U3 ] t: _, {% e9 |my_A 0 135 15" n; F, G( x7 \& _# i1 B& i
1 26 51 q# x3 |/ M8 Q8 s8 X& q
my_B 0 674 2
j! Z9 P: ^3 T+ o9 } n" I 1 25 6% l1 e) I6 e1 K: U$ C& C2 [& C
1
+ h1 J% q1 |2 G' T2* R; `0 i$ J6 z! T
37 ^6 g. P; } t, p& p
4
: K& i5 P2 d6 d( _6 ]) G# V5
; o5 ?. b4 @" I3 l+ z6
6 Y7 c9 R* Q6 ~5 K( f78 C1 \9 c" Y) p! n
8
0 W; D" D2 V2 G; M1 c9' E- H* {5 ~* M7 G
str.findall:功能类似于str.extractall,区别在于前者把结果存入列表中,而后者处理为多级索引,每个行只对应一组匹配,而不是把所有匹配组合构成列表。; x% p* o* U9 e1 G, _& s
s.str.findall(pat)" h1 \1 |% I0 N
1* g2 j! A4 _1 L) ^% \7 [
my_A [(135, 15), (26, 5)]
' z0 x# Y2 Q r0 L$ B l5 Q5 K2 |my_B [(674, 2), (25, 6)]+ w" z- V$ @) E1 t+ v% p2 b% g
dtype: object
4 J' ?. Z7 j0 |; b' C% r n1/ c) a) F, ~3 Q! ]! r; N
2. f3 q! r) _+ |9 q
3% s8 O$ b7 @' X# U0 |, W2 H/ I
8.4、常用字符串函数
; \* p. ~2 n9 ]; I: B 除了上述介绍的五类字符串操作有关的函数之外,str对象上还定义了一些实用的其他方法,在此进行介绍。
( F& e! g. U. |: j
( b, ^8 A' l4 g, y8.4.1 字母型函数3 b: R* \0 L O/ G
upper, lower, title, capitalize, swapcase这五个函数主要用于字母的大小写转化,从下面的例子中就容易领会其功能:" H9 x. C0 N$ r" s7 l" S/ K# S
' s* a0 N$ s# d# x( T
s = pd.Series(['lower', 'CAPITALS', 'this is a sentence', 'SwApCaSe'])) y+ ~8 E E/ Z, ^
) Z. B j/ l2 a. C0 U% o: U) v0 [; \s.str.upper()5 _. L; M1 ?" [7 k6 d9 ]0 }/ ?4 K
Out[87]:
8 j" v6 y& h/ R9 _. F5 B, m A$ I0 W0 LOWER: y5 n0 I& ~2 n& q! F
1 CAPITALS: f3 G' t6 E" W* \6 ~( ?
2 THIS IS A SENTENCE3 q. f- G; |6 ?6 g" K. l
3 SWAPCASE1 Q1 _: |( K5 S
dtype: object! `7 o0 B9 F7 y( A6 q! Z/ ?& r
$ S/ @$ J) u( k! a" ?
s.str.lower(), \% g, _; l, F) X. x! Q
Out[88]:
( o1 [- ~* }' d0 U0 lower8 R K4 {8 `, ^7 F
1 capitals/ [4 ~. S( x; X m. H" ]
2 this is a sentence, Y+ K. Q6 A: x5 h& Z+ d: H
3 swapcase. Y, r8 Q" [# `; C/ q6 }' P7 v/ z
dtype: object+ J' @- d" w+ { ]$ j1 r
2 ]) {! i9 t/ z, t8 x* h# ^s.str.title() # 首字母大写7 C0 u+ d3 I) X) N# J6 e
Out[89]: % q3 S8 r5 b/ f9 c2 v4 P
0 Lower
3 e6 s3 [& h1 ^! r) [0 K9 `: m) D1 Capitals5 u. h" q4 `$ V+ X1 }# P6 E3 o
2 This Is A Sentence
! M [* _0 X2 w/ a: J* q) _3 Swapcase
. {0 q0 g' o' ^) Fdtype: object9 @, X- V' Q1 H4 g4 r0 J
/ o% T; E8 T1 i2 I7 Ts.str.capitalize() # 句首大写! ?2 C. ]8 B) A/ ?. d
Out[90]:
) G9 d1 e) Y, z( K. P1 E0 Lower, l I1 F: j7 Q. ]- r/ t
1 Capitals
4 d3 V$ E1 ?# L6 @/ {% D2 This is a sentence
: k. r3 Q- L7 }2 O3 Swapcase$ |) X8 @. Q; M4 m4 O5 ]
dtype: object: i6 T% b; e1 k: O" p7 [' f9 Z
, i6 E4 P( A8 _9 C/ G
s.str.swapcase() # 将大写转换为小写,将小写转换为大写。' T* G* i1 A @$ H! n% V
Out[91]: ' z6 _: n k6 U( [; _ ?& Y& t ]
0 LOWER9 d# a' t2 _( ?/ ^- K7 N8 s: E! j
1 capitals
1 x$ M7 |3 `; w2 THIS IS A SENTENCE
# r3 [1 V" n* c& E. C3 sWaPcAsE& Y) B& T" S( i" s' r3 O
dtype: object4 h) Y3 Y. T: y7 h' ~7 R! {
% q0 p0 x% z5 w5 `8 as.str.casefold() # 去除字符串中所有大小写区别
/ r7 f9 i9 Y7 _3 s0 {' h" l) O" j* l4 y3 U( L8 O4 [% H6 D/ P
0 lower9 ^3 @6 y# J: S, |
1 capitals
, l# P; K3 ^& Y9 c6 U2 this is a sentence
( s7 g) }7 a) b: b" T3 swapcase- X/ B5 f- W1 x
- a0 p9 ~7 u4 ~ l7 ^1
& O7 N" q7 M' P" p2
. Y% C1 c% ~ l6 S p3
4 O" k0 U$ ^' f" b% y9 U b' A4 U4
, C, h$ W! M$ c( J! o% }( n6 ?5 h5- T- L8 B+ b+ o9 M, c4 ^
6
$ z1 v4 d F9 g! L7
; d( A( n% Z5 I/ [1 ? h4 _80 u& q: x7 u4 _! w, o
9
7 K2 }# P. [" \/ v/ H& n10" _( `7 o( q7 j
11
) N4 y) _2 g9 U+ G1 V& }, O129 H- {- Z" Z6 V% U, N5 \
13& R F/ P7 ^: |
14, x' k; A F8 G& K& ~
15
, [6 Z' [7 q2 k7 R! z169 `0 X# |0 Z4 {
17
/ v1 |2 `# n" g' \) A/ L9 N6 h18
! y: {4 \+ a- t s19- L; J& _1 t c& g2 K
20" n2 v* T8 x$ {. D% D7 H0 S
21- f8 H' ^# w; Q: |& j2 K& f
22
( y! R/ }8 Q2 ^+ G" e23
- ]) X' C. K0 n* z' L) U. A' d3 P247 ^/ }: w8 B! W- `4 K; i
25% e2 a. W! |: T# m. G; X, \
26, ~) ?7 Q0 u& j; \% Z! J
27
7 |5 g8 s7 T+ c7 @# W8 {28
9 y5 |$ t1 B3 q" z1 Q/ W; [: U% q29" F4 i; K4 ]; T2 C, }1 `% e( X2 ]! f
304 a1 M' D$ l& X1 u0 |
312 w0 F7 p! v2 A2 j7 }! ]! z
32
) N: d, M# K2 ^+ `% P# a+ ]1 V7 r( `- F33
2 }$ i" C* R/ C: _" @( _342 n4 H+ ?0 ?8 W0 d4 x) a
35$ g4 h6 O6 w! d3 H/ f
367 k9 a' E! g4 D3 @9 c
372 Y9 M3 K) R: r* c; C$ P0 J
38) S6 D% b, S) m* T
39
! C0 ]! r3 }( ^; F+ l7 e) |# ?40
# |' M, r0 i- U$ O, }( l+ L7 @3 ~; h0 g41
, g0 {7 ?2 T( ^42
{% f1 e' ]6 o8 U: i1 M43
& Z% v- o) u7 e# ]/ L44* w" F5 A1 }0 t7 Z% ?9 m
450 _8 X( q7 @. {. z6 M
46
7 x, v) I# h' G8 U, U* ^) G47
- |, C- j1 T9 @# C' R48
6 ?8 l9 k* p* z, ~, g8.4.2 数值型函数
' s7 {9 [( O7 w5 n/ y: A 这里着重需要介绍的是pd.to_numeric方法,它虽然不是str对象上的方法,但是能够对字符格式的数值进行快速转换和筛选。其主要参数包括:0 x3 J" c0 ?7 u1 ^) c$ F
7 U1 F+ R7 I8 N; {7 |
errors:非数值的处理模式。对于不能转换为数值的有三种errors选项:
; d0 G3 X/ L0 j* s0 ^raise:直接报错,默认选项, z+ a- j+ O& A
coerce:设为缺失值
: ?3 N7 c+ [. x9 jignore:保持原来的字符串。
* p8 D, h! j! k7 b. n, G# \- ?4 ]downcast:转换类型,转成 ‘integer’, ‘signed’, ‘unsigned’, 或 ‘float’的最小dtype。比如可以转成float32就不会转成float64。- \$ k5 P8 p5 ^+ ~
s = pd.Series(['1', '2.2', '2e', '??', '-2.1', '0']): W( `: w, K- L- D8 m3 o
) C' e$ p! J: j% F( \$ E
pd.to_numeric(s, errors='ignore')* e8 l8 F1 X9 q& K% u+ M
Out[93]: / Y( y7 V4 l! `8 N& q1 G& [; E$ ?( c
0 1
, s1 D* o7 z9 g5 y" z4 n1 2.2* `& T& A! h# j) M; W4 M* d
2 2e
" ^# h. c/ m, D6 o# z) b8 K3 ??6 F2 P( S; n1 H2 ]0 r: ?/ Q4 X+ d" ^
4 -2.1
6 p J* b: `/ ?5 0
1 H% ~- o1 W& \# W0 _dtype: object
6 }# F. t. G) \
- t8 w; P9 [* u k$ qpd.to_numeric(s, errors='coerce')
/ } E/ C" d* H" vOut[94]:
0 T& y# V5 d+ u1 i' j) @0 1.0
" t+ E: z5 C2 K- u' t Z1 2.2 T2 Z6 c; t& q2 h
2 NaN; T) W# w k, W5 Q
3 NaN
; N$ [; E K$ N" N1 ?6 Y) `: `; Q4 -2.1
, x& s4 n8 i, p7 X8 V2 k5 0.0. r8 {' S. t5 t- i& F
dtype: float64
* D8 _# t+ V' C+ Y/ P! N4 R! q- I( r
1" Q+ m! b5 }2 c& }' h; u: P# I
2
- n. [2 n a- s5 w3
2 n- F: D& v+ j0 V/ i4% K$ q+ A. U& h1 o
5, f' x# n% t, A d/ Z
6: c( {" b4 @& G9 E5 ^
7
4 A& a5 \# v2 ?' t, \1 T. ^8
. Y+ d& c: E, k; m: d9, l% a. N5 v2 F, N& U. z5 Z# N' `3 R
10
7 c9 s/ f5 E; h11 }8 T+ U: }) O
12; D {# e7 j: T5 A6 [
13) e2 h& G+ ?1 z# w" {( N8 M) ?3 T
14
" P$ o( \7 s" q5 w& {2 d- y15
5 \* p: b" O9 b, B6 W& ~166 L% [( d, t0 C
17$ [% `* Y1 m9 y' N! _
18# Q ]: R' }9 `6 q
19
) f! {0 n$ d* B% m20
1 J8 b& D: x6 J, f21
* h; d4 t+ m; k8 R6 a v0 A" t0 | 在数据清洗时,可以利用coerce的设定,快速查看非数值型的行:
4 F% G, b; M% m5 ?9 g8 j
8 e$ Q2 w2 H$ i* H. ?* q' Q6 j+ [4 Ws[pd.to_numeric(s, errors='coerce').isna()]
, Z( ], N. h: i8 ] ]Out[95]:
+ I) X! K5 W& K& J8 j. }2 2e" o' ^4 z. ~& F9 C9 W4 n
3 ??0 j# X4 G) g3 [& m% s C
dtype: object
/ V1 {; V, v: w( b6 D9 f1- | }' i4 A4 h) d0 S
2% s4 g# h. O5 N+ a4 |$ M0 n
3
+ p- ?$ s( h" b& ?1 I6 e, H* J3 s4
5 c- @8 t. S( C: e& p5 d, D6 F+ r! b5
# W9 A/ \/ V4 d0 D* \8 ]$ g3 q8.4.3 统计型函数; |$ p, P2 m! i# s6 z3 k$ j& l: [
count和len的作用分别是返回出现正则模式的次数和字符串的长度:* V+ a6 X* K4 e. A' B3 K5 p' J/ e
( T3 x* T0 v, Y- o3 b7 [s = pd.Series(['cat rat fat at', 'get feed sheet heat'])
, t# k* ~. O4 t) y; P4 Z2 @* M6 C7 B! P3 m' a0 x/ E( u3 E
s.str.count('[r|f]at|ee') # |左右两种子串都匹配了两次
: p$ u4 ~" k+ e$ k- v+ G POut[97]:
- S+ H8 L F# g. M0 2
* }# Z1 V. n3 l, P/ w1 2% A+ q/ N5 p% ]! k4 C$ f+ m8 E
dtype: int64( M1 \; p w9 x. q
7 V. ~5 R- b# P& v4 @ d1 X5 e
s.str.len(), I% J8 j5 d) y/ S- d% R
Out[98]: - F S: a6 V0 Z$ G3 X# v
0 14' [8 _0 y3 Z0 e L. \4 @* m" D, @ m
1 19. j/ m" y9 \* F
dtype: int64; G# I! p+ m7 v$ X: z. I8 s
1+ n1 Z2 V$ c& p0 O
2+ k" t2 b5 E: ^
3
n, Y; g0 Q% b4
. a) C2 Z' C9 ^" P2 V4 [5
2 F* s/ v) b# ~( ^9 }# K% K6
# O6 D) x/ D3 N% [7- _: k6 I6 g. d' {* c2 P1 z$ A* y
8
' b5 u# K1 f2 g$ ?, ]( P7 `# t9# D, x. S' ?. c2 F) r% C
108 c2 g/ i G5 X; F
11 T1 Z7 C; i' `8 R" H1 k( V
12
4 {" n& i3 f: Z" @! L! k13
. q* f- v: Q$ {. w& t8.4.4 格式型函数3 p5 l6 G i4 n
格式型函数主要分为两类,第一种是除空型,第二种是填充型。其中,第一类函数一共有三种,它们分别是strip, rstrip, lstrip,分别代表去除两侧空格、右侧空格和左侧空格。这些函数在数据清洗时是有用的,特别是列名含有非法空格的时候。- ^; S: I$ A1 ~& G4 }# @
3 R! D Q6 \: {+ Q- S3 u: g
my_index = pd.Index([' col1', 'col2 ', ' col3 ']): W% d8 I. @" q" Y# y0 G
$ m# G3 }2 |5 i
my_index.str.strip().str.len()/ i$ K1 H" N8 V$ r2 A6 p
Out[100]: Int64Index([4, 4, 4], dtype='int64')
1 ?* j7 ~0 g: h, W* M
i$ o9 @9 }0 z0 bmy_index.str.rstrip().str.len()
) j$ r- y8 `; Z' }; R- C* NOut[101]: Int64Index([5, 4, 5], dtype='int64')
/ W: q: J9 A2 C4 |, N2 I0 M: y# K- k$ ?, _/ w7 A
my_index.str.lstrip().str.len()
. B2 R5 q2 e+ C/ [# l( POut[102]: Int64Index([4, 5, 5], dtype='int64')* j7 h6 d( z, W' d3 X
1
+ U$ F. Y+ m5 s/ ^2 K' C A, c' n23 a( D; j: u* B( u
3# e( S. h B+ q: [
47 f0 n8 ?1 F4 A( T
5
, C1 H/ d) M: v2 \' I1 s/ Y6
6 p3 K" ~$ u1 S5 C) p' R3 Y T7, d5 M% U0 U5 q8 U j
8" O- d) s0 p: ^! ?' x
9
7 G5 n0 O6 }0 \5 _- U10% A" ~- ?2 e+ `/ _! j
对于填充型函数而言,pad是最灵活的,它可以选定字符串长度、填充的方向和填充内容:! g: ^) R" e5 M) ?
3 J+ ^* ~$ P! [- `
s = pd.Series(['a','b','c']) {8 i' {% \$ R$ p% m
. a( _( h: T" @* `& t' L+ s: k+ Ts.str.pad(5,'left','*') C1 z0 U: w: S
Out[104]:
8 C" E/ i9 N' K& A7 _0 ****a/ H8 ?3 Y" I6 Y7 s* ?0 \( C7 L
1 ****b
7 P* k) i, u" N: l6 d+ t( v1 |2 ****c) d: ^: G1 {) [* M- A
dtype: object
( S: x( R" Y; q7 p2 S. Q6 i0 [- H% N! t# V
s.str.pad(5,'right','*')
8 M/ ?! ?1 d( T1 X, oOut[105]: s( T; v0 y7 \: Z; T
0 a****7 A0 s* ~5 Y, A
1 b****
0 _. m$ Y' ?8 b6 u2 c****
4 ^6 t7 i/ V2 r- F! f* K7 d0 d; [* gdtype: object
! _& N! z0 q4 k- q0 l5 z, m
( B% U2 U9 G7 [" Cs.str.pad(5,'both','*')4 ^: g1 j1 I; m* u
Out[106]:
1 Q9 s7 @1 L8 A; q5 O6 |0 **a**" J& |4 e2 d; M$ X
1 **b**+ P/ k/ K' w/ [+ p
2 **c**) s! B- N/ S7 f4 s% p
dtype: object
1 ?+ e, y7 t2 i+ [ v
/ d* N. u( D2 W1 s5 y10 S# f5 L' v4 t$ y! d7 k+ z
2. L) G" v' y7 I/ w# h3 w' C
3
0 |! ~8 V( K8 g4
$ }, M( h3 [* Q% a ^50 F& ]# X2 p3 K5 W( @
6
5 U5 }6 x! [; E8 b3 R) A4 n74 R$ [+ f$ W) h9 x
8
$ ]4 c0 x" I, z! ?0 n0 M9
3 A% M4 [, f2 J7 d102 `' K: l2 E5 n1 c* b0 Z E
11! C" t8 l3 c/ P5 P3 [& t* n
12
4 J+ v% i2 F* O1 Z6 {& F: l, ?13
& @+ j; W& N- d! K) I147 l1 U. h* S9 l, Z7 ~% f, n7 E
15
/ ?& p) \" m. H/ I! `16
' o9 }7 n" b L, W# Q7 E17
! H% r7 s) J/ C N+ y# S. v18% t0 a* `7 P1 c& e
19
3 m3 x/ _; z9 v2 i& G20( }4 S% M' G' C/ Y0 p
21
; N W- r0 g: n* N1 W5 v22
* q- g8 Y# C) F' d/ b; c7 |. A 上述的三种情况可以分别用rjust, ljust, center来等效完成,需要注意ljust是指右侧填充而不是左侧填充:
3 r4 Y! _& g* T0 n# M7 |6 ]7 Q3 ^( I+ {: e0 W) Z' s5 x
s.str.rjust(5, '*'): V4 i4 @4 @9 ^' o. o( A) B0 g
Out[107]: $ F3 b. m' o2 o# A
0 ****a- l+ [ i8 E6 a' R; v/ Y
1 ****b
- Y( V$ h8 o. T2 d) @ b3 ]+ R2 J& t1 T. v2 ****c" B" R, b. C; I) J* S' {
dtype: object8 ^0 c+ l0 N' m/ b9 B
! C3 M$ F! x! } f' ~5 T: f) s+ As.str.ljust(5, '*')+ U1 W7 W& V+ A, k" g
Out[108]:
$ a% H3 k, [$ `+ c+ Q+ \0 a****6 V2 b6 K c: ]5 u( D7 a
1 b****
8 i/ x. g" f3 C$ b7 f ]; w2 c****/ e a. U7 v2 g& ]2 C7 R
dtype: object8 }3 d6 D% [/ a8 _( O! J: W
$ A8 N, V' ~; H( H6 rs.str.center(5, '*')
; y% V! X) R: l; l; w. J6 fOut[109]: / F/ S+ T5 x6 x5 |$ p
0 **a**
. I! r/ U. @0 {1 **b**
4 N: F+ z. ?. \9 K# p: b2 **c**0 O! N* d* C$ b( H4 [( D. v
dtype: object
2 `* D1 C0 t: D' g, g# q
% I# L6 i8 p% H V0 h( S5 v1( V5 x' G( `( J. Z( n# J
2
+ F, r3 n' K5 Y/ B* z t39 K) O, o% J' ~/ l
43 C$ A1 s% S! `# w; i
5& C% e8 {: X8 y& K
69 _# U' d' [5 s
77 X" [" ~6 r6 Z0 B3 m! e! g
8
4 ?# i( F& L+ q2 B97 f' B: `( `6 T+ u8 I9 h8 C
10& t' O ?( ?( G! ?# j. {
11& J. N* n6 S* G( O9 y
12" [9 a& I- g' q0 {
13
- w" `* Z+ W; d& b3 I, `14; e+ J9 q* [+ y' d3 i
15
( D- z$ t+ l" }$ T5 w& `16! ? A% B' v0 E0 r; _9 i
17
5 b, S! ]3 p$ X18
& }/ F [( L! d# R19
, y% |; f4 M n20
3 D$ J! Q# a9 B- N* \ 在读取excel文件时,经常会出现数字前补0的需求,例如证券代码读入的时候会把"000007"作为数值7来处理,pandas中除了可以使用上面的左侧填充函数进行操作之外,还可用zfill来实现。
$ {, B! U$ [6 P. s1 f* T
; ^) {8 P$ r3 f- A# L! ^, D( ws = pd.Series([7, 155, 303000]).astype('string')" s3 U$ B& E; U* T2 O+ R
1 \1 j9 `6 C b& F' Ps.str.pad(6,'left','0')
1 N* s1 m4 \+ i( `& {! d3 mOut[111]:
% t4 E4 G& I6 E6 o* @0 000007
" C% K& T2 g v6 p3 T: G$ b0 @. m+ Y1 0001557 o8 A# ]" L0 t5 c
2 303000* J: u3 W+ e/ x& C9 _
dtype: string% i$ A6 w+ ]9 b* X9 ~/ r9 b
8 T' F4 a9 g+ D& {. E d! A
s.str.rjust(6,'0'); Q; u3 m/ p' B I7 x$ N" r$ G3 h
Out[112]:
3 l2 z/ y5 _& k* G/ z0 000007
% n3 w/ F4 I' X! d1 000155
- T% Z$ U1 {" s7 ]2 303000
: u$ B% ^* t. e; L9 ?dtype: string
9 k5 a% B% {, L+ @! @/ e4 p% U
" D2 e' s6 \8 N- \2 Gs.str.zfill(6)
5 L- g1 h% a' y. v7 r0 |$ j% MOut[113]: & V8 u) c+ x; Q; @0 D, `$ Q
0 000007
2 [6 J1 ] {* y+ `7 l1 000155% l- R3 X- X! K2 D
2 303000- h: c# A# ^4 T, A6 {+ |' C
dtype: string6 U3 z$ M9 K: O5 i4 x+ {
: x) {/ \& ~/ M9 L$ w2 X
1
/ `, ?& X( l8 E' R5 y23 y2 ?- `1 N6 E. F% a! e
3
7 p/ `+ w( S0 u0 R' N: c- n9 `) H47 W H( X2 W" }, m6 z5 z
5
' D; x# Y' G& A {6
7 @. [) C% v V8 v. V7
% P* j3 v. g$ ?% x3 N7 I& t- I3 |9 k4 \8 ^& R& X4 A4 v( C! f5 d
9
/ \' r* P; I9 d0 F. `% R10
3 C1 ~$ v/ a, T* }11
2 U) N$ }; c6 |) p12& ]1 I4 t. P, }
138 E# R& |9 D5 }- J7 A% M
14, \! |9 l) ^4 y2 y5 i
15
% X) [4 a8 e# T2 a8 I& |! @16
! `+ F- u6 z! U- z; N: ?9 q& c! K17# ?2 [0 V5 k4 ^/ A% j1 _) j7 \
18
) Q3 t O- W7 N/ ]% z/ k191 S- P6 P. z( z3 U! j/ Z
20
/ P1 Q1 T3 v8 f21
! ], I& A( s9 _0 U$ m22
' _7 y" G- A4 l7 d- v- f1 A8.5 练习
% Z9 R7 [( d4 ~; ?Ex1:房屋信息数据集
" c: M: y& N* g! E1 J现有一份房屋信息数据集如下: X' d( O7 O+ L- r' i7 y- v0 f6 l
; Z2 f( v: L+ e) I7 q( n4 x
df = pd.read_excel('../data/house_info.xls', usecols=['floor','year','area','price'])
/ C! W1 M2 |, udf.head(3)
/ i: m; {4 B: z) ?- \ tOut[115]: 1 [4 M! Z1 p" u f8 ^7 _
floor year area price
$ k( |. C; C( K' w( \0 高层(共6层) 1986年建 58.23㎡ 155万9 R" {" T6 [; J, y! d4 t( G' D
1 中层(共20层) 2020年建 88㎡ 155万
W+ r; U- G$ h3 H$ k. N2 低层(共28层) 2010年建 89.33㎡ 365万
1 M& f% ?: m% N; Y% w3 v1
! L7 `8 Z0 G3 Q# M' a$ W2
, E3 ~- x8 s6 @5 i z6 g$ m3& q5 k) O3 F j
4# X' q n$ ^. [1 X1 x
5' \6 U$ a- d+ h9 i! m" B- _
6
% Z9 H; e* f. _6 t' M7: s3 |8 h8 ~! X) `' E# S
将year列改为整数年份存储。
+ d9 O8 G1 d% V" U将floor列替换为Level, Highest两列,其中的元素分别为string类型的层类别(高层、中层、低层)与整数类型的最高层数。& |* g& M$ Z: n- U/ T
计算房屋每平米的均价avg_price,以***元/平米的格式存储到表中,其中***为整数( z3 s9 P |* o
将year列改为整数年份存储。
8 o2 K6 x4 k$ @7 t2 J# ]"""* Y. y+ T! E3 a, ]! |8 S) C
整个序列需要先转成Nullable类型的String类型,取出年份,再将年份转为Int64类型。% @" h9 Y3 M) H1 A+ H- a2 H% S
注意,转换的类型是Int64不是int,否则报错。即使astype加参数errors='ignore'跳过缺失值,
' L: J8 B8 q+ F8 J8 i# R9 @转成int后,序列还有缺失值所以,还是变成了object。
" H8 R- x& g$ {. a) Q而整个序列转为Int,就还是Int类型,缺失值变成了 pd.NA 。
" \( o2 n0 p0 E- z3 _"""
, v5 t! Y! Y# y% Gdf = df.convert_dtypes()* @+ M: S# c* D8 f9 p5 t& t' ?9 ~
df['year']=df['year'].str.replace('\D','',regex=True).astype('Int64')
% I! z) P8 e i0 l3 h, t7 Xdf.loc[df.year.notna()]['year'].head()/ i- O; P, R. s. _% W8 h9 Y9 H
* Z% Z; r0 D; f
0 1986
6 U& w, ]: h2 }* N. [: [9 w1 2020
' p: p+ x& Y4 B4 Q$ V# f. S* T2 2010
2 I( ^( ?- A( h- K7 L3 2014
; ]- g5 o; E9 a2 W! I4 20153 s+ X4 e' m8 R& B0 C
Name: year, Length: 12850, dtype: Int643 `8 ?8 N) T+ g- Z5 r: |
v# c5 r; J1 A, C1 n) x1 ~1
# m4 t! X" h- T2 v1 C. t9 h7 e2
& h# }. Z, Y Z: O7 n( x3
t# ~! R3 s/ E3 y+ } W1 g6 y4' H4 T) f7 e4 R Y" b
57 n4 L$ L8 B! L9 ]& s# f" U
60 \ U' x: z' T0 t" D) W: W
7
7 F$ e% W. _& [ V8, r- E, r& S# S9 D( b8 Z
9% J. l, G, _' z$ z
10
* S: |. e7 o7 D5 D# Y11% [8 _1 I* W ?7 }+ c
12
9 \0 U' @( Y' F, s9 f9 m134 e x9 @( k0 i8 ]+ F0 t) `
14( }5 |% V, e5 z3 ?2 k# Z
15" V; s) v: E! n/ W, ~9 M
16( ~9 r6 N$ S# C2 G3 l
参考答案:, s# y K' v( J7 W9 K
) `8 Y# {7 ^& f$ o4 z8 q不知道为啥pd.to_numeric(df.year.str[:-2],downcast="integer")类型为float32,不应该是整型么
7 Z4 M5 `3 z$ B8 D7 ]8 n! L1 u& c/ [/ p! Z8 I0 \& F0 b3 Z
df.year = pd.to_numeric(df.year.str[:-2]).astype('Int64') ) z& f: ?, h' X0 v3 k0 ^( f
df.loc[df.year.notna()]['year']
3 t4 k9 n0 a# {: U- _- s+ H# r& J1
2 s% [: S6 ~8 V% \% w2' d% T) \. y$ z
将floor列替换为Level, Highest两列,其中的元素分别为string类型的层类别(高层、中层、低层)与整数类型的最高层数。
" n7 M% l& d. Qpat = '(?P<Level>\w+层)(?P<Highest>\(\w+层)'+ f' e ^' K) H+ b& W
df2=df['floor'].str.extract(pat) # 拆分成两列,第二列还是(共6层得形式,所以还的替换一次/ \% z. Q9 j* X1 n
df=pd.concat([df,df2],axis=1).convert_dtypes() # 新增列拼接在后面,再次转为Nullable类型
b1 K3 _( u1 T6 V% [& edf['Highest']=df['Highest'].str.replace('\D+','',regex=True).astype('Int64')
5 k/ Z$ A; L* u2 \1 Rdf=df[['Level','Highest','year','area','price']]5 }7 ?3 u9 b, o. O$ U! ^$ u
df.head()
8 a. n: d3 D M3 V. B
" s% N- q% `- r' t% S Level Highest year area price9 r; t1 ]2 O8 s# K! ~; E* }4 X
0 高层 6 1986 58.23㎡ 155万
) t- c9 c* N5 X; Y; P1 中层 20 2020 88㎡ 155万2 s5 q J2 \2 G4 [
2 低层 28 2010 89.33㎡ 365万& J* e A' n' l+ W u9 q, d+ j
3 低层 20 2014 82㎡ 308万
( o: |4 f+ V+ s$ c8 g4 高层 1 2015 98㎡ 117万3 |! d/ [6 q! C2 h
1" P: K0 I9 u+ V2 G4 W! S
2
0 Q- B$ `- T+ ]3
: v3 m; R6 X. f6 }1 m4
+ e( y. X/ l W+ V$ B% e2 j9 G5+ D: | U9 s# n4 X1 R# Y
60 ~: ? y9 Q' v3 Z: K
7
# L5 U+ y/ v* [* q, ]" Z3 z8
- c, _; L- T/ S/ O2 ^( {9$ W3 I7 ^# g' n }! z
10. N9 h8 i9 O- n! a- u
11 h* S) W- q# j- t$ ]
12
( ^/ s- L& s1 z5 g, B! \13. @6 z( x3 h: y5 k k6 z- v
# 参考答案。感觉是第二个字段加了中文的()可以准备匹配出数字,但是不好直接命令子组了
$ }6 @! L: Y! L; @+ O. dpat = '(\w层)(共(\d+)层)'
" d7 A4 C5 h7 w! W& }4 Gnew_cols = df.floor.str.extract(pat).rename(3 A% D, f' G2 \6 R+ s% q
columns={0:'Level', 1:'Highest'})
! k9 f7 z A( P0 S: A; ?; J1 Z0 B) N) ]3 _" {& a. u
df = pd.concat([df.drop(columns=['floor']), new_cols], 1)
/ }7 x' P Y0 Y1 o0 O: M$ Ddf.head(3)4 ?0 z0 u3 {2 b; e$ L3 M) | A
t, g' n7 O( \3 q9 Z
Out[163]: : `; U1 s% B* m6 d2 g6 C7 h% I
year area price Level Highest9 G, z; z+ }. s8 b2 g4 L
0 1986 58.23㎡ 155万 高层 6
: W8 m- W D" e. c' ~1 2020 88㎡ 155万 中层 20
( T7 J3 M" y- C5 h+ j2 2010 89.33㎡ 365万 低层 28* {6 D( c- m; s
1
8 [/ M3 h( _7 v/ V3 j* I" V27 P1 L$ C# k# r y: K$ A
3
7 z' P! n4 f- Y4
6 {% h0 x6 p7 d0 }58 f6 G C- Z) o2 ~/ K
6
: _0 U' s9 F; b% n. O7
# Q+ `3 s1 H5 M1 |; m* g8
5 B X% d9 w/ j* k9% B2 N" Y( ?2 C! H0 ^
10" z6 y; o; v* a
110 c' o! x' |/ l2 w* T% {
126 v' y; W7 V9 G; D9 w/ |
13' {/ T5 ]0 i1 U
计算房屋每平米的均价avg_price,以***元/平米的格式存储到表中,其中***为整数。& _8 F- N5 n, C7 s
"""
: ]7 R0 V) V4 F/ _( w2 T- q9 L* Istr.findall返回的结果都是列表,只能用apply取值去掉列表形式6 U+ \* Z' f" c/ G* S4 g/ M! I) A
参考答案用pd.to_numeric(df.area.str[:-1])更简洁8 d5 {8 P% }- s2 R. E# M
由于area和price都没有缺失值,所以可以直接转类型! t2 B; B S3 ?- Y
"""
( }5 c( M5 |1 B# e0 c1 Kdf['new_area']=df['area'].str.findall(r'\d+.\d+|\d+').apply(lambda x:float(x[0]))5 `# ~$ w, s, H7 _ c
df['new_price']=df['price'].str.replace('\D+','',regex=True).astype('int64')
5 @7 u& ?9 X) N$ h: xdf.eval('avg_price=10000*new_price/new_area',inplace=True)
, I; d% E: U7 X% ^: \2 h# 最后均价这一列小数转整型直接用.astype('int')就行,我还准备.apply(lambda x:int(round(x,0)))
/ ^% f, ?- o! m2 E# 最后数字+元/平米写法更简单7 P$ G7 {1 q- W2 |/ x% t
df['avg_price']=df['avg_price'].astype('int').astype('string')+'元/平米'
$ W) j8 s R4 G9 y( Xdel df['new_area'],df['new_price']* \1 I' ~% }! q8 H @
df.head(), A P) r3 M6 s
5 ], D+ I, S3 |. |: m7 w# D- d/ q
Level Highest year area price avg_price! y0 c1 }& U3 w* J1 W7 J; p
0 高层 6 1986 58.23㎡ 155万 26618元/平米% A* z! g! H+ A3 m
1 中层 20 2020 88㎡ 155万 17613元/平米
+ k* i% x1 V, }5 F9 ]# E; E2 低层 28 2010 89.33㎡ 365万 40859元/平米
" D# E6 ?. h' ^1 h( m3 低层 20 2014 82㎡ 308万 37560元/平米
2 \0 d( v$ K2 u: }4 高层 1 2015 98㎡ 117万 11938元/平米
6 M$ G8 o4 b R+ g& |1 @7 X- R3 \
2 F! [7 e" O5 I$ B! g7 h5 I11 G! Y7 v6 `$ p- O! M, m
2' n9 {* ~5 d: B" {- H" K5 V
30 Z+ H0 A" K/ [- T: v) `3 j
4
9 m% A7 O* t4 q! z50 N* o' q$ a; g
6
9 @ Y) }- V2 M2 s0 [7* b( O+ L8 d& l' S
81 T, \6 _7 ?/ e6 n0 v5 T# v- T3 f$ V
9
, m6 ?) M8 [. U6 T- D% E10
9 f7 |! O) P' B: Z- n11
, ?. E6 ]8 N2 i9 W7 w12
6 t, Z# I$ J' |( e- s; [' h13; Y. a. \: t4 ~3 j: Z! i( _
14
8 o }- G: Q3 B" x# j; z15
1 G4 L6 ~0 l& k3 i- m4 N& x16; V m7 y" `0 `3 [# h8 h
17
& [4 i/ R7 B4 F4 {- {- E+ l18% I! A* l9 |- G2 y( W6 i q1 K/ q
19
# a& f! a! t0 t; ]# I1 A4 E/ r2 o. }203 f1 M0 _4 W+ T0 }2 k8 L/ o* N# y
# 参考答案
( g6 M: d0 y- J% d+ \s_area = pd.to_numeric(df.area.str[:-1])& u* n, ^/ e6 a: ~/ j6 _ a% U
s_price = pd.to_numeric(df.price.str[:-1])# _7 n! r( I2 E
df['avg_price'] = ((s_price/s_area)*10000).astype(7 @3 j7 `/ |) S& w
'int').astype('string') + '元/平米') w0 ?' `8 p; V a0 V" \- e d
/ j; E3 p4 O: [$ L9 y2 _$ H5 `1 b' t: F
df.head(3): W; z# t7 q9 @ {* k
Out[167]:
, n2 g0 B T* d: Z( P m/ ` year area price Level Highest avg_price
( M! h& ~% ]+ ]0 1986 58.23㎡ 155万 高层 6 26618元/平米
! u5 B% w! r. w; V1 2020 88㎡ 155万 中层 20 17613元/平米
^7 M0 b2 s2 ?' l, s+ y2 2010 89.33㎡ 365万 低层 28 40859元/平米
7 Q8 H8 B5 f& j% y1 @$ C1
. S* \" `0 K) v9 A$ J7 _: s' R! A2% ~" M% S' L2 j* \
3% w1 d/ e+ S0 G% z! {3 F5 b
4* F" M7 [6 Q0 a2 w7 {+ z0 e
5
- _. q' a+ B; M% b w67 P' n0 U, A8 m1 s& P5 m+ Z
7+ h2 m$ Y* c2 u- y, c( s$ ]" p8 @& B+ R
86 _0 Y. T$ L" {+ y
9
# ]& S. |5 U8 B4 u( ?' h10
% ~ @+ A# M7 |. x11
$ D, ~( ]/ e F6 D2 u' O12( T) W1 E ]& m: ^
Ex2:《权力的游戏》剧本数据集2 ^7 o1 V. x5 J G9 M
现有一份权力的游戏剧本数据集如下:
9 p) Q: p+ e3 y$ x* i
3 ?7 f" H5 Q" Idf = pd.read_csv('../data/script.csv')
2 C; r1 o; i; i8 R- h+ pdf.head(3)
) Y: W: y( A2 n, ~6 v( D0 ?& f7 g9 O3 \. g
Out[115]: 8 {- S& T. v6 X
Out[117]: ! f0 d, S3 y8 F+ c/ ]" T0 M" S2 Z
Release Date Season Episode Episode Title Name Sentence
# U8 z& ^8 ~& ]* k) e" |0 2011-04-17 Season 1 Episode 1 Winter is Coming waymar royce What do you expect? They're savages. One lot s...: i' ]+ y5 l2 t" @6 q3 M
1 2011-04-17 Season 1 Episode 1 Winter is Coming will I've never seen wildlings do a thing like this...9 u1 b/ S& }: ^! |8 Y! x
2 2011-04-17 Season 1 Episode 1 Winter is Coming waymar royce 7 n% d6 d( o# F D* ^4 Y
1& r) g; v8 s. b$ K& [3 g" @
2
( O4 u; Z( L& q$ N3: W/ i# Y0 H3 p+ U; v( U
48 \- d6 A _. W: b; S2 m: o" M
5
6 E& x/ J* l1 O$ D' S' n6' u9 M* E1 j# b, h) b* ?1 k
7
8 L" G& M. y# r% Y8& v/ S, L( D$ }0 ^ w
9
8 v% `) g; |9 m/ r! f f计算每一个Episode的台词条数。0 O" i h3 i# z# E
以空格为单词的分割符号,请求出单句台词平均单词量最多的前五个人。
9 J9 r3 ?7 W' J% m若某人的台词中含有问号,那么下一个说台词的人即为回答者。若上一人台词中含有 𝑛 个问号,则认为回答者回答了 𝑛 个问题,请求出回答最多问题的前五个人。
4 @6 |+ g8 y7 h7 m( x* Y8 `计算每一个Episode的台词条数。
. ?$ W$ G2 N7 F" s3 w8 _! M, M# C7 Z8 Idf.columns =df.columns.str.strip() # 列名中有空格9 m) S* ^( [$ ^4 I
df.groupby(['Season','Episode'])['Sentence'].count().sort_values(ascending=False).head(). \' r1 ]. m9 Q# n5 B
1 d) R# B+ Z7 Q" _) n
season Episode 8 C9 B o: b8 r/ V7 t2 ^
Season 7 Episode 5 5056 I, p- N2 {, Z0 g2 F2 g6 X* g
Season 3 Episode 2 480
4 z) m" c$ `$ gSeason 4 Episode 1 475
) c, @/ I, j% n/ `: |+ ]! NSeason 3 Episode 5 440) K- v- r: p0 R2 H# K5 I9 ^
Season 2 Episode 2 432
% U! o& n5 Q/ ~' n6 x: @5 P1% ~9 g {* w S( K
2
- X- i) N( y6 V& f! c3
: G' t0 B3 i! D- H6 _% H3 J0 s% Y4
6 N# |& t6 _( a5
8 {5 a$ a+ ~0 Z2 j& `0 c6
" Z* @% m1 q2 d) g8 }" \) d9 N, U1 i8 b7( G% \6 M" B& i F3 W/ A" v+ D. \3 \
8
( h. j9 G9 j0 b4 l% u- w* p/ ^9
9 |7 P3 n% c& ]' u. R4 {, t4 B以空格为单词的分割符号,请求出单句台词平均单词量最多的前五个人。
1 p/ `% A3 F. l& J3 L# str.count是可以计算每个字符串被正则匹配了多少次,+1就是单词数) x! o i6 [* d5 M& Q2 ^/ v; M
df['len_words']=df['Sentence'].str.count(r' ')+1
+ X; E! ~/ }3 R2 Rdf.groupby(['Name'])['len_words'].mean().sort_values(ascending=False).head()
% s" D' Z/ Y8 \/ _" s# q6 x* ^* O) B$ {
* V. `0 V! D% R- A. J2 o; e" JName
& v$ R- p0 O: U9 dmale singer 109.000000
9 `1 t( b5 |" t& I9 hslave owner 77.000000
0 g" n( P8 N, H- {, R! T# h& Lmanderly 62.0000003 J* w& F7 F0 j0 M. b$ l* p
lollys stokeworth 62.000000
6 v7 K2 [- W. N7 P" Y5 e* D3 Gdothraki matron 56.666667% m! z5 h: A4 ?
Name: len_words, dtype: float64' @: A' z, N' E# E* X% z4 W
1
5 P' p& \6 h% k0 N6 \2
) R. X+ z# J: x3
, m, u z) d* r0 D4
3 L6 \& Q- D* V; Y5
- V7 N8 y; C. D6
4 S. H9 ^, i' ]3 p2 h7
2 g0 s! ^0 t" A! m. Z82 P9 N: M: j9 P( a: Y
91 y- D: F3 p6 ?) W
10
6 ^" w7 r' ~3 X% q! B" x1 a9 |# w11
1 T7 j+ t0 ~' E$ T$ Q7 c S* L若某人的台词中含有问号,那么下一个说台词的人即为回答者。若上一人台词中含有n nn个问号,则认为回答者回答了n nn个问题,请求出回答最多问题的前五个人。
/ [( P9 r: K3 n& c+ y% Adf['Sentence'].str.count(r'\?') # 计算每人提问数
+ \+ ~7 f/ I! r: Pls=pd.concat([pd.Series(0),ls]).reset_index(drop=True)# 首行填04 G9 H' o% e7 O% L- z
del ls[23911] # 末行删去3 l4 {) I/ W/ b+ J7 _" o
df['len_questions']=ls
1 H, D/ H$ N; A& w! mdf.groupby(['Name'])['len_questions'].sum().sort_values(ascending=False).head()
0 F5 C( I+ e, h1 m" O0 `$ O. x0 g/ B/ i Y, V
Name" D" V: C- s& U7 U9 H9 \9 a1 o
tyrion lannister 527
2 U) `9 e) F% q' f# M2 Tjon snow 374
6 v8 L) Y+ ` v% O& r0 |$ G7 Djaime lannister 283
# a2 v, U3 I( F Y Carya stark 265
, H4 ~- ]' D1 A6 ~cersei lannister 2467 I# s8 W. w+ @$ q5 ]) n. ?
Name: len_questions, dtype: int648 ~, p& K5 ^/ Z
- X: G f- \; i4 Y( T+ X: u# 参考答案
3 x) t0 O8 D& U6 T$ L7 gs = pd.Series(df.Sentence.values, index=df.Name.shift(-1))* R* b* e; z0 U2 A* v8 B) d
s.str.count('\?').groupby('Name').sum().sort_values(ascending=False).head()+ Q3 F2 z$ e: ?, J7 E# K
1 D5 B8 b/ p: @+ s5 F
1$ ?3 S0 ^, A5 c; t o
2
' y1 v# z+ O2 |9 I3
/ ~8 |. B2 x+ D% `2 S4
' p) Z3 I3 N2 u! q5 ] o b5
# _7 Y. h5 S" R% n/ o7 p. O6/ e- G. m6 N) Q2 v6 y
71 V. t0 u5 }# _+ ~
8% E, ]: V4 T8 P. D
9
3 r! L. X+ p$ c* n3 n* D ^( W10( U) W4 @- K. j ~. g7 o
11
" _# ]. M) q O" r( o: z1 a" l# M* @' r- D12+ |% c% v* q4 x. Q' a1 N+ i
134 {* S! \. ?. D) k
14
" o. d' B1 [4 w) f1 z5 ?* Q" T/ F15
6 s2 M+ n8 G. K0 ~2 r9 S16# {' M" }. |0 g4 Q9 L
17
9 m& z: x) ^( v. U" J% V v9 Z第九章 分类数据
! T/ J( h2 O) j; v6 O ]: r( Limport numpy as np
7 W5 T# T, U* Aimport pandas as pd
2 \ X. R. P( t) j$ V& e7 V1
6 F& q1 ~3 Q$ i# W" d24 x. ]+ R! e3 L! Y
9.1 cat对象% Q, h1 l4 X% e" ?1 Q& R5 K$ m
9.1.1 cat对象的属性
! N% D: u* O3 k2 p1 A2 p 在pandas中提供了category类型,使用户能够处理分类类型的变量,将一个普通序列转换成分类变量可以使用astype方法。
$ B. i$ l* T$ B( k7 R* w, @! C( {4 b
df = pd.read_csv('data/learn_pandas.csv',
/ \9 M6 j* E/ U' O. v( b3 U/ @; z usecols = ['Grade', 'Name', 'Gender', 'Height', 'Weight'])" W( |# W3 E K/ k0 s2 V! A. U
s = df.Grade.astype('category')
F1 {! c$ ^' {. ^. u) W7 p
9 P$ v' m' `4 s( js.head()
3 s6 D9 K' q/ [6 Y4 J2 P m; a) u. WOut[5]: $ a# t' t- q0 m
0 Freshman4 L' O$ x2 b, g
1 Freshman; o0 Z3 F& d( j& y
2 Senior( d( S! _+ ^: Z& e5 l# @
3 Sophomore
6 P& V& l+ x: @$ \5 H& A& D$ l4 Sophomore
- d) W+ w" g* bName: Grade, dtype: category
& A2 R7 E4 b! _$ O+ \% j% fCategories (4, object): ['Freshman', 'Junior', 'Senior', 'Sophomore']2 s ~ v: A7 A& o' I
1, k5 z1 F9 e& W! J/ a
2
9 a* G9 f+ a2 o0 H3% Q9 A! u' N' @* w- m/ j3 X
4
6 t0 P9 x* ^/ P) ~7 a! ^( g. W5
# o/ ?6 T) n4 l) E" J; k7 ~7 V6, t2 X) F2 r, `# b1 X4 y
71 t. P" I* C2 C5 q
8
& t+ `; R. |3 A0 l! q6 y9
* P# I8 g. w3 v# j3 ]3 B0 Z: |10
+ R' U1 a7 D0 G$ a7 h! X$ R2 O% l11/ Q$ {, d8 g& Y0 e; C9 z+ M, p
12+ T% X0 \, ]3 i% ]9 _
13
+ g+ w' {! p$ }/ f/ ?4 y0 X 在一个分类类型的Series中定义了cat对象,它和上一章中介绍的str对象类似,定义了一些属性和方法来进行分类类别的操作。
; W; p8 D; J3 a E
) \9 R9 _# Q' t) ?- M& u* c4 l5 u- ?6 w9 Os.cat! y |9 b, A: @/ _/ M4 y
Out[6]: <pandas.core.arrays.categorical.CategoricalAccessor object at 0x000002B7974C20A0>& @0 l( E+ p3 G7 }2 O9 h( }1 u0 q- c. P. T
11 v2 `2 l4 [4 A/ K8 o
2
" C2 Z. Y8 i4 Bcat的属性:
4 T( o4 t0 M& e8 c1 G" d
) Y* I& p2 S! @; v# Z6 W: ~9 Scat.categories:查看类别的本身,它以Index类型存储6 B. B2 y4 Q( L/ g) h% ^7 b
cat.ordered:类别是否有序
3 A, I- V4 I$ z o0 X j( B5 jcat.codes:访问类别编号。每一个序列的类别会被赋予唯一的整数编号,它们的编号取决于cat.categories中的顺序0 h% p0 o% S: e N# \6 Q
s.cat.categories
, F. k1 m* J2 q eOut[7]: Index(['Freshman', 'Junior', 'Senior', 'Sophomore'], dtype='object')
9 `9 U" A3 M( B1 M' X/ J
2 h0 j# P# ]* Ns.cat.ordered
5 z4 W0 P4 R, I2 Q0 OOut[8]: False
3 @: G/ S" T" v3 K! m. m% n2 S5 c3 {
s.cat.codes.head()$ b$ B' O/ i5 i- p' S9 F
Out[9]: 0 A( o4 H" y: g) }
0 00 D" v+ B" o2 R7 Z9 h
1 0
5 |# g2 q( u- m2 2% i0 ]8 w* T6 Y$ |8 Q
3 3
$ h# t& O; Z& F4 C7 }+ M4 3$ } u$ p, z; K6 i- ~0 i
dtype: int86 p9 h. t/ i) p* t. u/ ]4 u
1
$ }2 S5 V9 {: @# q% C2 q* r* u$ ?: d2
/ E; v+ K/ b$ ^/ E5 D5 S" I& p: e% r3 M v' u- G& ]: `1 @* Y
40 U+ J5 n# _: M4 Y+ k P6 ~5 z/ h
5
: w6 |) I" h- y6$ X% n9 d! K* v) G) N" t; l
7. F. K' }/ c2 o' n& V$ w
88 u9 _, ]$ p0 I$ s, X1 t
9
9 p+ l6 m8 _( K( R4 U101 J1 P5 Z* W4 q7 J
11
2 q: }) @. ^0 Y( C& }12
( n5 ]5 U- \% O4 T0 i2 _13( K. R+ r4 ~! r3 u3 X
14+ p4 @* O* m+ Q9 h+ l9 d
9.1.2 类别的增加、删除和修改
+ V, ?% }. k& h8 P# E" [2 n 通过cat对象的categories属性能够完成对类别的查询,那么应该如何进行“增改查删”的其他三个操作呢?3 d- m: J9 \0 A! V
% v& o! r* B$ { |【NOTE】类别不得直接修改
6 H) G& N8 w3 Z3 }* `在第三章中曾提到,索引 Index 类型是无法用 index_obj[0] = item 来修改的,而 categories 被存储在 Index 中,因此 pandas 在 cat 属性上定义了若干方法来达到相同的目的。 N" X. S& }* [+ E, u' Y) y
1 ~/ L$ O) h* b. a, Kadd_categories:增加类别* y) o i% g5 Q" m4 c
s = s.cat.add_categories('Graduate') # 增加一个毕业生类别
# ^, B: @& a& t, K. O* Rs.cat.categories
& d( n" Y5 b: q! R y* j; o) G- E% N P8 L1 R! `" z7 A
Index(['Freshman', 'Junior', 'Senior', 'Sophomore', 'Graduate'], dtype='object')
8 P4 ~) ] w# X+ a1
- ~- r V% k9 i( t D, R) u28 w0 D+ J: u+ E k9 ]5 J) b
31 n, ?: {! R' c7 b8 a$ l
4
+ T: d9 D/ \8 @9 dremove_categories:删除类别。同时所有原来序列中的该类会被设置为缺失。
9 i& s; Y' c* O$ S6 os = s.cat.remove_categories('Freshman')
# ]1 |; Y- s; [9 A" d& x2 `
1 y; C5 B5 }1 T6 m e6 y1 Us.cat.categories. n8 |3 M. C. h5 R5 e# {8 U
Out[13]: Index(['Junior', 'Senior', 'Sophomore', 'Graduate'], dtype='object')
+ g; ]: e7 D9 x9 T! \$ [5 g
& v# [/ L1 J; W1 Ss.head()& A% K& L! P9 J2 h) Z! c8 |, ]
Out[14]:
1 T/ S7 Q4 _) X3 }7 W0 NaN" O! F' X' Y" \ V/ {# ?. d
1 NaN
* A" X4 V1 ]0 @% C, [2 Senior
9 [+ @ D; |( t8 i0 b/ Z3 Sophomore
- i- p# g" P$ b. P4 Sophomore
+ B; S% i" A' u' j- W) m* K. I) SName: Grade, dtype: category
' K" V- ?% D- e, Z, r' S" Z( rCategories (4, object): ['Junior', 'Senior', 'Sophomore', 'Graduate']" C2 w' [+ b; w8 e' C& C: f8 T
1
9 f7 `' V6 I: f; H. A5 B, P# U2
4 U8 i+ `" D5 n- W3 }% ]2 V: u7 j3- G, p: Y& }! g# w6 D$ J E, J
4' e r q2 b5 E% d' U
5
6 D7 ` @8 I( S( m; G6
7 Y9 a8 U7 f" l4 ^6 C- D! M% P7
. b- b/ c; R7 V8( t/ f# y7 B U) ?! x
9/ }& s" D1 B: \9 r; e! e
10
! X$ [2 r5 Y! f7 H( X+ X5 p11/ Z n e, L& z l0 V$ W5 ^
12
( A2 w) E" ^+ d$ c# H13
5 @; N) ?4 Y) ~" T14
5 H% S( P! I1 x" A8 E+ W2 hset_categories:直接设置序列的新类别,原来的类别中如果存在元素不属于新类别,那么会被设置为缺失。相当于索引重设。9 \. E$ m4 c/ }) N) g/ c, U6 F% w
s = s.cat.set_categories(['Sophomore','PhD']) # 新类别为大二学生和博士
# E2 r6 ]5 y& W) j& G; f& h. ~) L8 os.cat.categories
* v: o1 r2 Q, Y1 i) oOut[16]: Index(['Sophomore', 'PhD'], dtype='object')
/ J( o# {! f- _) U3 ]
/ T% V) O2 P% Ts.head()
) e* m- x& Q1 o2 Z' { UOut[17]:
2 w% i' D) L6 H- ]. n" w0 NaN
3 L, k* _* \/ u) Z5 J1 NaN' d5 o! j7 o) ^( {7 A' S7 J6 V
2 NaN
( U( `% J& I2 }3 i1 W' n" B" |3 Sophomore: u, C: W4 h+ M. ?
4 Sophomore1 a% z1 ~' H$ c" ]) k2 K5 \
Name: Grade, dtype: category. i X( V3 w2 B u. a2 X i5 R
Categories (2, object): ['Sophomore', 'PhD']
0 m/ m0 U+ G1 h9 |( ~1
* a+ C0 o) f7 {& o# K* y; k2
?7 S5 H0 I) f5 @ e3 L, _+ w35 O7 [( s( Z0 M; @2 B% S0 K( O
4
6 n- p9 F! ^2 A& _5
* F& {( i$ ~ c6 X6
0 _: M( z8 M( o4 g7
( L/ i% Z- h5 j1 t8
4 E" l% R1 S9 `9 N) q9
2 c; Z8 ^1 K# U" y10& `( ^1 k2 v' r7 ?/ [
11' T# \4 ?. A( _ X6 l4 C; i2 E: g0 A& l
12. U: H2 y L: \5 k
13$ r2 J' y. m9 F$ z2 ~6 a
remove_unused_categories:删除未出现在序列中的类别
4 G" E7 O+ ]/ M' w1 F5 T+ n' `4 \s = s.cat.remove_unused_categories() # 移除了未出现的博士生类别# X) u2 m0 A0 c* X! A* z
s.cat.categories
. y6 e0 f2 W2 a2 s9 Z4 _- y* r7 _4 {9 J0 C: l0 X
Index(['Sophomore'], dtype='object')
( y# u) F G; a. [# Y; @% D1
# q1 f+ f7 D7 v3 C- l23 ~0 n7 |/ e+ c& o$ `3 G
3
) f4 d q" i! H2 N) d! o( a4, m) h1 O! O& h v. g4 g2 K
rename_categories:修改序列的类别。注意,这个方法会对原序列的对应值也进行相应修改。例如,现在把Sophomore改成中文的本科二年级学生:
! g8 W% ~* O& ]6 g5 Q9 C$ Z5 cs = s.cat.rename_categories({'Sophomore':'本科二年级学生'})) z5 s% H n5 c S% x
s.head()/ y6 [8 ^; b. w7 X- T6 e
' _! ~: }8 c" }2 @$ Q3 Y% c0 NaN, @! d1 F- ?: V6 Z$ ]/ Z
1 NaN) ? d7 k, h$ \, u' I
2 NaN
, B. p M1 K3 x3 本科二年级学生
- [8 z- e$ K6 J" j; W4 本科二年级学生
$ z) G/ x8 ?& [2 m' a7 c. p5 Y( TName: Grade, dtype: category8 q) f- N5 J0 D0 l. l
Categories (1, object): ['本科二年级学生']; ]& g3 z$ C+ l
1+ K5 j* }2 V8 I( d: n
2
" d) f( v* I* _1 x3 F2 T/ ?. Z; p2 L) s34 B0 }4 I8 F& ^# g
4
8 i3 }6 s5 M! Q J# f( R; Z5" c6 o! G( q/ e% D- z
6
~# \! `6 }. O: @6 r7
( M8 c) E' y! @0 c% X& e8
2 x& s4 M. U: M9 Y, [4 ~4 s I9
7 `% l0 h3 S! n, H/ g- e10
8 _5 O) t4 Q+ B; a2 Z- J1 r/ w' r9.2 有序分类
% V5 m# L( j/ `; W9.2.1 序的建立
Q0 z+ N7 d) l9 T% Y 有序类别和无序类别可以通过as_unordered和reorder_categories互相转化。reorder_categories传入的参数必须是由当前序列的无序类别构成的列表,不能够新增或减少原先的类别,且必须指定参数ordered=True,否则方法无效。例如,对年级高低进行相对大小的类别划分,然后再恢复无序状态:
/ i9 h8 P$ {% `+ F% y1 L4 }/ U7 O& F8 x2 J5 d2 s! Q# U; _7 f
s = df.Grade.astype('category')( n4 t5 F& E3 \( R1 Y2 R1 B r- i
s = s.cat.reorder_categories(['Freshman', 'Sophomore',
6 f$ W- t+ o0 K) A5 C 'Junior', 'Senior'],ordered=True)
: L2 S; I9 }6 Rs.head()
4 V$ L2 ]( w2 ?% [0 gOut[24]:
\, z! U/ U8 Z% O2 G& m$ W( a0 Freshman
$ N% E+ {2 i$ s6 s! \1 Freshman. W1 v$ K: U5 G( e& {" Y
2 Senior$ u# m+ ?4 c5 x: Y% t4 Z8 q
3 Sophomore
. l8 |- j9 \3 S6 ^: f4 Sophomore9 z5 Q7 R" Y7 ^! X1 \" B) n
Name: Grade, dtype: category, n3 Y/ ` j. @6 x
Categories (4, object): ['Freshman' < 'Sophomore' < 'Junior' < 'Senior']
( ? A0 U% X7 X' ^' R
% M% s. u- J6 ~1 |# Y; ps.cat.as_unordered().head()" q( b, {3 _: s/ d& g
Out[25]:
5 j) E! i4 o' P4 S" S0 Freshman! g' C7 Z( M: L+ o
1 Freshman
! M4 Q; C+ [, K' P2 Senior
2 U( y" P9 a+ T! a3 Sophomore2 q9 w) i7 s) d9 R
4 Sophomore% _6 f S: P* p4 P: b/ l3 a
Name: Grade, dtype: category
+ s1 |* c3 q- i8 z- |Categories (4, object): ['Freshman', 'Sophomore', 'Junior', 'Senior'] X5 }0 e1 e9 K
) |5 e$ h8 g/ f5 P. t+ u
1% p$ t2 c: |% p( p( L
2
. M' f! B' N9 W/ G- f3
5 _6 f6 o6 Z9 p0 ^% {! f6 E# X4
% e( N/ g: r2 x' p& H& | i8 y5; O5 A0 H+ j; E' v
62 n" V* M, a, n G
76 d1 T' Z" r- }7 S
8
0 A* v8 e+ n3 l8 W6 C7 |, t9: t& l+ i3 ~0 [/ e/ S9 k# t h
10
' g0 B% L3 i- V) D" W7 ^112 l0 p$ o6 x/ Y/ S* q9 R! {
12, y+ N: g0 G8 I7 }4 V
13 s3 v; ?5 e. U$ K/ c# }/ n0 W
146 b8 a2 r. r$ @' G* \, a2 s
15) q# x0 d1 P# B* L9 T0 d
16
/ C! ]+ C* [* D# x. }17! b2 V5 v; J$ `
18
& N4 L* y" Z3 a) n$ J3 W198 t% d1 V0 ^ q, y. ^% S6 q4 j
20: I$ ^/ T$ h2 M3 W1 ~6 ?2 h
21
2 j3 J2 @% G" v+ ]& L# X22& l; H+ R7 h u0 N+ M. W$ I8 ~
如果不想指定ordered=True参数,那么可以先用s.cat.as_ordered()转化为有序类别,再利用reorder_categories进行具体的相对大小调整。
5 Y" N/ j+ u- d' ?) P/ A5 L$ x6 N6 y# Z
9.2.2 排序和比较: h+ P( f! ?* u/ T
在第二章中,曾提到了字符串和数值类型序列的排序。前者按照字母顺序排序,后者按照数值大小排序。7 I7 J6 V6 K/ ]) W0 n
( @9 h( ]$ M$ j 分类变量排序,只需把列的类型修改为category后,再赋予相应的大小关系,就能正常地使用sort_index和sort_values。例如,对年级进行排序:1 W& l, M, x' M) z& q
# }# Z; }% {$ e( i0 I% N9 c% Idf.Grade = df.Grade.astype('category')
1 V \1 q: Y1 [. m D, V) t0 @df.Grade = df.Grade.cat.reorder_categories(['Freshman', 'Sophomore', 'Junior', 'Senior'],ordered=True)
6 t6 Y$ U& d/ C) a. Xdf.sort_values('Grade').head() # 值排序
3 n& Y- O. v3 x7 W- t' V# r1 ^4 LOut[28]:
0 y- V$ ~8 O# d% }% \: ]7 r; p1 F Grade Name Gender Height Weight1 a0 f2 l' Q+ w7 q' |( Q! l+ ]" |0 o
0 Freshman Gaopeng Yang Female 158.9 46.01 y; P* n9 g' m) `. ?' [8 s
105 Freshman Qiang Shi Female 164.5 52.03 l6 ?( ` ?0 E& @+ Y
96 Freshman Changmei Feng Female 163.8 56.06 B! C1 W" r, P+ d) r2 P3 ~8 w2 d
88 Freshman Xiaopeng Han Female 164.1 53.0
6 F9 [0 f8 l# b. p81 Freshman Yanli Zhang Female 165.1 52.0
4 B2 j P3 l. I( B3 w! f. M8 r: `, C! Q$ }! v) L
df.set_index('Grade').sort_index().head() # 索引排序
1 `7 |8 D. ^& M: [, z" COut[29]:
9 |5 t R- h+ Q: Y; u7 z Name Gender Height Weight8 K# G! S6 {. s# B8 q
Grade : D! q2 ~% x( B. |% w+ j
Freshman Gaopeng Yang Female 158.9 46.0+ A" w [9 Q' l; [2 y
Freshman Qiang Shi Female 164.5 52.0+ \8 w1 m' P" c$ u4 n& H
Freshman Changmei Feng Female 163.8 56.0- R9 I' Z" N5 I
Freshman Xiaopeng Han Female 164.1 53.0
8 j# d& M5 G Z/ H; W6 uFreshman Yanli Zhang Female 165.1 52.0
8 q. O" a C: f$ {1 U! ]( `
; F: N4 J, h1 Q, y1 J g, X( f& w/ W/ ^- h% T
2* s$ C5 @* s- o0 _% ?5 V
35 M" F0 q, o, Y( J0 }0 T
4
7 v8 }. ~8 L* Z4 h H, ]9 P/ G+ T/ c6 i5
( ` _! j& \# ^ w0 \6 ? ]" C* z t) l5 Z _# @1 @
7
% D; @8 ^8 l4 H" L8
! r6 E' s: w0 W0 p" y9# u( R1 K6 w# w9 T+ Z
10
! ?4 Y" Y# X l1 \$ T/ }" S11& R; {' Y) T$ F$ `( P
12
" @$ \+ O5 m+ R, ?5 _13% x3 R$ C2 F, s
146 Y$ \# }* L9 t' H* t3 f
159 p3 _9 J- Q: l9 A$ r. k
16/ Y; t5 `! m, }& ~- a; d T
17% B K8 i) _& Y) ?' y
18- p; {7 C# d& r7 p f8 s: ^
19$ q9 e5 X8 L- E! g/ [
20* d* @+ E7 q$ l0 q7 Q; |
由于序的建立,因此就可以进行比较操作,方便后续索引操作。分类变量的比较操作分为两类:
w9 }% @+ Q* _! O0 b# e4 D$ Y; S' l, u* I6 I( ^
==或!=关系的比较,比较的对象可以是标量或者同长度的Series(或list)。(无序时也可以比较)5 a* M8 Q" N5 H$ A, G
>,>=,<,<=四类大小关系的比较,比较的对象和第一种类似,但是所有参与比较的元素必须属于原序列的categories,同时要和原序列具有相同的索引。
) [) X3 ^6 V' j& L/ yres1 = df.Grade == 'Sophomore'# M' j1 B' c/ c: j6 |7 G" i
. ?# a7 ]# x5 a8 Z6 U1 Q9 Y
res1.head()
! M4 R8 t3 W3 o7 fOut[31]: 2 a. l& `& F& w5 E: L2 c
0 False
( ^! R- T& q F1 False
" v/ u- f; I/ v" s1 F: ]2 False
1 i. e+ A9 _2 @3 }; D3 True# U' ?0 v( d6 y1 n! _7 O ~
4 True
; _) J+ i1 S( t9 `9 V- XName: Grade, dtype: bool# W& `) M' A6 h
# J/ K9 X( c/ O6 X- pres2 = df.Grade == ['PhD']*df.shape[0]
/ V- B* Y7 d* M% R: O# b: b9 d
res2.head()
5 O, Z% |; v# S+ P: K5 o3 t; xOut[33]:
8 |$ K7 `9 F5 {& }) O6 s0 False
8 x* U/ x/ f4 x5 U% G1 f1 False' Z5 Y! Q; B3 W. c! r0 I6 h X+ m9 p
2 False8 V+ w, \; \5 q+ X) x
3 False
2 L( T% G+ M* V' b1 v0 r4 False
; a# q, |/ `. ]4 _ xName: Grade, dtype: bool
+ v8 F% T4 n2 F, v# u! |$ Z& e0 E; t
res3 = df.Grade <= 'Sophomore'
. w3 c" b3 t+ j6 |8 y, U( Z9 Q+ T/ t ]# x; M
res3.head(); o; V/ a5 |5 {
Out[35]:
2 `4 P& d7 ? [, ]; ~ o2 t# ~# }0 True2 v; K, A9 C. }- m& r
1 True2 u) @+ o# A2 s- Y. p1 k7 M/ L
2 False4 p. t- k2 y( b" ^/ [9 S2 T
3 True
$ Z( Y5 n0 w2 {5 u1 M4 I4 True/ O s& e9 L Q# l# n9 ^
Name: Grade, dtype: bool
. @2 ?1 y) X9 e5 O3 s5 A n! S- o) f- P" m* \& d0 P* O
# sample(frac=1)表示将序列随机打乱。打乱之后索引也是乱序的,直接比较会出错,必须重置索引。- [& n0 g! q1 A' d- u7 K
res4 = df.Grade <= df.Grade.sample(frac=1).reset_index(drop=True) , _3 f) G! _, l, n" k
6 f" X9 E5 w7 k! f" I, S5 M
res4.head(); w2 E; {6 L6 U' r+ a
Out[37]: ! @% H( A4 o1 I
0 True
" p! I. o0 _6 v3 _* j" u* @; T0 a1 True5 M' t" k. I; U }2 X& C
2 False
/ P7 U* U* {+ y, s4 u2 v3 True
4 u) P f2 u# E7 d( a& x7 B4 True0 S3 p8 c/ e* W E
Name: Grade, dtype: bool
2 @* b# C4 U. p) ~3 q* W+ [- D. x
! x* T9 Z6 F; n$ W. [1) H! b% e) G' R( d
2: d1 o, o i, L: n- M- a
3
D- ^$ E9 k5 n ]4
% ~1 L0 ~% U# d* }! X' I' U1 O5+ }* S7 W, p' ~0 ^8 B
6
9 C0 E* { T2 w74 i, |; D/ e" I3 W
8
+ G ~9 l' ^# b& ]: H# b1 u9 p93 Q' V+ P4 |5 ~7 v# I3 Y) \
10, I! T" y& h! I+ @1 C
11
% _% I8 q' Y! h/ U: H12, e8 x- N# z, z! H+ d- F& C
132 r' c' A0 Y0 W* W. ?* E9 [
141 F H. p6 P+ @- U4 E; L
15
; a, Z" k+ k- O8 l' \. H3 I( X16
2 Y& k1 Z8 M! j' S17
# i! u8 J" R+ O- j18% W4 _6 x" C/ j, X, U0 {- C+ \
19
/ b% c0 J% o' u9 q0 G W* X20
5 T# B( [ ?# m$ w: Y0 m/ a S214 N' @" L, y5 W9 j' C+ _
22
, T2 x: `1 ?$ e7 z, r" l, R23
/ \' @7 h' w* V5 P4 P0 D24" G0 N% w% p0 u+ L* l& ~
256 v( K( c4 n. I& w( N$ O$ H0 B7 d
264 A, v* I$ Q5 ]) e; ?) Y
27/ k8 u$ R- x* G' @- t$ {' I) v
28; v1 Q$ O. ?5 D/ P0 Y5 S: _7 s
290 ^4 K+ w# U s8 d. D3 X3 W3 P" N
306 c0 h* O/ W3 E+ t/ L
315 a; E2 x4 ?9 c- `4 X6 a3 V8 U
32. A3 R1 u1 D* A
33. d% B6 z2 [, T1 e) j; f( c
34
* |! u# B$ f* x# o35
# z @) d9 y7 ^6 `- L l# y7 `360 S4 a0 B, M2 Y8 ^
37
" n. u) K5 t3 N9 l! ?38
' [ F4 [ c- S" e397 Z/ o7 t, B2 C0 X' I; W) b2 `
40+ Y* s M& c" a. B4 ]/ F+ z( x& Q
41
5 O1 Z# T3 [2 E0 q: t3 x' P42 H/ l7 y% v1 T' d0 i; E/ t& @
43
! n9 s4 H) Q2 P( T, N8 w; F! m& u44
2 s0 L. u6 Q% R2 N9 y9.3 区间类别& N+ S# v/ o% I
9.3.1 利用cut和qcut进行区间构造6 ] E: L7 ]/ s! o3 J. W
区间是一种特殊的类别,在实际数据分析中,区间序列往往是通过cut和qcut方法进行构造的,这两个函数能够把原序列的数值特征进行装箱,即用区间位置来代替原来的具体数值。( Y: g, H( q5 T, }, f& ^
+ V8 u. Z* }, U- @* T
cut函数常用参数有:
3 S; E/ E1 k# L& n# Dbins:最重要的参数。+ N8 ~/ _' H( p4 u
如果传入整数n,则表示把整个传入数组按照最大和最小值等间距地分为n段。默认right=True,即区间是左开右闭,需要在调整时把最小值包含进去。(在pandas中的解决方案是在值最小的区间左端点再减去0.001*(max-min)。)9 \8 K( p4 T$ ~1 _& R/ K
也可以传入列表,表示按指定区间分割点分割。% N* ~+ j! `4 [9 o. p
如果对序列[1,2]划分为2个箱子时,第一个箱子的范围(0.999,1.5],第二个箱子的范围是(1.5,2]。
+ E' d/ T U% @. {. h" u! B 如果需要指定区间为左闭右开,需要把right参数设置为False,相应的区间调整方法是在值最大的区间右端点再加上0.001*(max-min)。
" o+ k! L9 ^3 C. j2 ]8 j% g: N, W: P: f& C6 d9 k: P
s = pd.Series([1,2]) ^8 y5 ~0 M b% J2 j* a% d
# bin传入整数
) r% o, ?& I! \- W! t
+ k- w) j3 e. W; E: Z2 c A0 u1 E3 K7 Xpd.cut(s, bins=2)
" z$ Y' z, n. p+ qOut[39]: / {0 q0 F1 H1 r/ V0 T( d) C! r
0 (0.999, 1.5]
4 O7 {' ~- b+ ] l4 C9 Y: X. m1 (1.5, 2.0], {3 y5 V6 I2 Y$ Y) S
dtype: category q4 {1 R% L7 N
Categories (2, interval[float64]): [(0.999, 1.5] < (1.5, 2.0]]
9 T/ z$ J8 s& W% }6 j3 j+ T1 K e" \- D4 O- Q9 b1 w
pd.cut(s, bins=2, right=False)- c0 q9 L% [# U0 t
Out[40]:
8 l! s3 m% e6 |8 |7 ~ [0 [1.0, 1.5)
8 G2 Y7 f2 h: }, S5 s$ b" F( b1 [1.5, 2.001)* K% h) S% J" |: D C
dtype: category9 C- I+ X& N; |+ @ S$ H
Categories (2, interval[float64]): [[1.0, 1.5) < [1.5, 2.001)]
9 v; e8 O: X1 z! h N) J
+ T5 a6 R! D/ r5 x; i" J' a$ Q4 T
9 K0 I/ V, y R" P" q; Y; }# bin传入分割点列表(使用`np.infty`可以表示无穷大):
7 \& M" W3 \' R" Zpd.cut(s, bins=[-np.infty, 1.2, 1.8, 2.2, np.infty])
1 t! T/ v/ [) AOut[41]:
; e8 i9 {; q: u0 (-inf, 1.2]. @2 R' w0 I5 H4 b( S
1 (1.8, 2.2]3 \& _1 R# U! t' Z/ `& f3 R
dtype: category
6 _* h# M- U8 Q+ l. v: ]; o% o; DCategories (4, interval[float64]): [(-inf, 1.2] < (1.2, 1.8] < (1.8, 2.2] < (2.2, inf]]# \4 g5 h6 L4 P ` l, m4 o
/ ^) P K' H8 X1; e3 v4 z8 u0 h, H/ P% V
2
" x0 n( ?/ E, i1 q; U3
/ F- R1 U+ f" P T, {4
5 z/ y- ?: m- v$ V8 u, R! c50 G8 P* ^' D) `0 N2 J Z) _5 S
6
N/ T# E# c# _( ^7: P" M, F1 X; F# H% C/ ?
8
8 l+ D* X* C6 o; G$ c9, _& K. E5 J6 D7 G4 e5 B+ W9 M- h) q
10: ~1 T' f! u7 v" }0 I0 b- b
11
0 M* z9 C# P0 ~5 O3 d# x( l12
& b! ?4 J5 Q. ~132 A F+ H* C P W( @ X M
14
" `+ G9 ^% u+ W15
5 A1 E7 p# U7 u' e! D. e- i/ Z16
+ N" M# d7 s, O+ V$ v% Z2 M176 A2 v4 ^( M' Q8 M+ Y8 w8 @
18/ |$ V6 @* x. D" d! ?
19
4 o/ O" z2 c8 J0 l9 Q, H208 r6 {: V3 U2 k0 [8 J
21; \, q: }$ W1 `' @4 q3 ]
22, t/ l$ _$ J" }2 p
23
" @: ~7 E! o* w: f1 ~! _: v& [24
" J5 J; t" K7 E; r/ O; P25
( i' \4 q/ k$ b, R4 n8 E; Tlabels:区间的名字- L' Q# l3 s4 c% U t) h
retbins:是否返回分割点(默认不返回)1 R. g0 b8 ]+ O, p" {, Y8 C7 [
默认retbins=Flase时,返回每个元素所属区间的列表
. g. h" o) l, N- B0 I2 K. Cretbins=True时,返回的是元组,两个元素分别是元素所属区间和分割点。所属区间可再次用索引取值+ \9 Z+ K4 P" e+ z
$ h: L- |+ A7 Ms = df.Weight1 r) O% s2 a3 F" a' v, T) N2 t
res = pd.cut(s, bins=3, labels=['small', 'mid','big'],retbins=True)6 _$ j5 f, w, c
res[0][:2]
/ U/ M% e' r. L
' {% d4 U, u3 qOut[44]:
& R& [& @2 N) ?; Q9 N& [; J0 small# o& ~+ F }+ U' P. A, b& \) `
1 big. f# R, M2 C( [! G
dtype: category
2 a* d9 b U$ D& KCategories (2, object): ['small' < 'big']# f$ I4 y! J" H$ o j$ A' I" x
# }8 a9 J1 O# \/ x
res[1] # 该元素为返回的分割点
( A, W j2 _* b F* cOut[45]: array([0.999, 1.5 , 2. ])
4 x- y' E, ? ]* n1
, ^6 }- Z! V- i2
1 _1 q& ^# }* I' P5 v0 i3/ q& B& _' C& s/ c( I `0 h
43 ]& ?1 T7 z. R, J
5
( J# d# r* f# v- M* G* Y6 t64 Q4 f2 f( I* U7 x! [7 \0 F) u
7
6 W8 T9 _: Q+ ^6 m. o8 H' ^1 [8
' Y" J9 E, ]. l& I! a8 P94 N" \* s: h a' H8 \2 f! r2 q
10
4 O9 c! `6 D5 H4 B% K- Q: W11$ B% q5 A1 ]1 i2 {% u* T+ L8 S$ E
12
1 m! {* O' j) _1 s" d A o/ ~- M# Gqcut函数。其用法cut几乎没有差别,只是把bins参数变成q参数(quantile)。
`9 p1 j' i" X& B/ h" iq为整数n时,指按照n等分位数把数据分箱# D8 P# n7 R! X. J# M" @
q为浮点列表时,表示相应的分位数分割点。( N+ b. v; u! T- x' X( }9 \1 u P
s = df.Weight! @, e$ Y7 e: H1 P$ |
% s: R2 u7 |* Q; J
pd.qcut(s, q=3).head()
9 k4 C1 L1 V5 c: L" qOut[47]:
8 d, E0 r' a' @# n( B- R0 `/ Z0 (33.999, 48.0]
6 i; b! o0 X \/ q2 R+ [0 _1 (55.0, 89.0]
' d( R, U" q+ }" x, S& U2 (55.0, 89.0]5 f# }* B6 a8 d
3 (33.999, 48.0]
- c% T9 ?, h( r( n4 (55.0, 89.0]9 Z4 U. e1 E: ?9 k) J0 H3 W
Name: Weight, dtype: category5 A- x+ m- r9 @$ k5 J" {
Categories (3, interval[float64]): [(33.999, 48.0] < (48.0, 55.0] < (55.0, 89.0]]
# D4 h; l5 W3 m/ R, @ J( H! e7 \: E1 r! S# M" }
pd.qcut(s, q=[0,0.2,0.8,1]).head()
! R9 @' N' y `5 ~# Q- U0 ^Out[48]:
+ O) ^) [% K7 U6 n+ U& U) B ]! [0 (44.0, 69.4]
% p% R. |" ^% f; }$ P. ~1 (69.4, 89.0]5 Z& l% i$ {# l* `" p
2 (69.4, 89.0]) v) ?" {9 h- Y2 z" q: l- F7 h
3 (33.999, 44.0] P+ q6 n4 A1 y! z" o
4 (69.4, 89.0]: B2 R( V) J; v& @6 m! C
Name: Weight, dtype: category4 b" r4 J- s- x' P, I- k
Categories (3, interval[float64]): [(33.999, 44.0] < (44.0, 69.4] < (69.4, 89.0]]5 n7 [* M# `/ @! N. C; V
1 i! l% b& N A0 O5 c, V
17 }9 u0 s* z8 j; J
2
8 h# y- D! Q' q0 b35 t) O+ Q x/ f B% R. B+ q
44 l V- I" e+ U# W* P" S3 j
5
' e3 ]+ J7 D3 u$ t" k# R) t6- A( u0 z4 \( h+ z$ A
75 f1 z/ m, ~' L3 b" V
8
T' F* t8 w+ u/ }* |2 Q7 ]9
% R6 m8 y% d! F- V* n10/ b* p2 w/ m5 ?2 Y
11
8 V8 G: S a7 s' \, {129 ~2 Y& g# t; Z$ I2 w. U
13
" x3 m$ M4 y0 a+ \$ a' s8 q: f' W14
* W- l Q* u1 k$ _' T6 L: j! ?6 G6 x154 ^+ `+ d5 R. v0 T
16" Q/ B. X% |9 M; l5 J
17
" M/ c; X0 Z h6 z18% r$ I, ^7 ^+ O& e) |. h) i
19
8 U5 m4 g* d1 ] h8 U9 L9 a% `$ C20
U k: E" R W4 C8 y5 e# C) R" B21
0 v9 B% p6 J2 v5 g4 j9.3.2 一般区间的构造
% {6 A$ L/ P2 ~# \! s! u$ C pandas的单个区间用Interval表示,对于某一个具体的区间而言,其具备三个要素,即左端点、右端点和端点的开闭状态。/ Y0 a8 Z/ S: |# Z% f$ A4 y
|( S% J7 a2 V c8 h! i* E* @$ J开闭状态:包含四种,即right(左开右闭), left(左闭右开), both(两边都闭), neither(两边都开)。; ^2 u* z: l4 Q3 S: D2 _) z
my_interval = pd.Interval(0, 1, 'right')" q6 ]' S H, x3 m
v* B5 E) s3 \: fmy_interval# e, G# l! Z+ a2 a# K6 Y' Z# G
Out[50]: Interval(0, 1, closed='right')
/ @7 |; V9 {0 i$ l12 i% X, }0 ^' G2 i3 D0 G: w% Z. [7 a
2
: B/ K' _! B) o* ~* G+ J8 T ^% y$ t3. z' y; g8 v) q; x3 A
4
% a& F/ e) ^6 Y, h9 k- a区间属性:包含left,mid,right,length,closed,,分别表示左中右端点、长度和开闭状态。8 `: N) U& e1 f0 w- s
使用in可以判断元素是否属于区间
! {6 n7 b! S8 H- Y用overlaps可以判断两个区间是否有交集:
7 Q0 \, A: J1 A. x/ P; F0.5 in my_interval4 |' h; U/ d2 t+ ?" \
: n7 e% | `, y4 MTrue5 G }. p, C, G5 Z
1% w' U/ c9 i) d: J
2) i( O! H7 C, z9 |" m
35 d3 s$ ^5 F1 m2 }5 A# P( F; c
my_interval_2 = pd.Interval(0.5, 1.5, 'left')0 y6 y$ F6 W8 p& T2 x: i
my_interval.overlaps(my_interval_2)4 R4 I" x O0 s& A2 \2 [& K1 K' O/ O
# G- L: B9 i" I( k/ s
True- E* i0 u0 N+ ~# e9 B6 V5 M" d& C* r* W
1! E8 g% l' B% z. Q" M/ x% e
2+ c! D5 Y( p5 S5 L
3
/ h5 Q2 `" [4 D; P+ U45 ]# Q- @% K0 V# \" e) `+ h
pd.IntervalIndex对象有四类方法生成,分别是from_breaks, from_arrays, from_tuples, interval_range,它们分别应用于不同的情况:
/ l5 I8 e: b: X0 ] c
+ v% l, B7 J8 N9 W1 A- ~from_breaks:类似于cut或qcut函数,只不过后两个是通过计算得到的分割点,而前者是直接传入自定义的分割点:4 G$ O1 y# m C# W( Q3 i5 @1 v, b" p
pd.IntervalIndex.from_breaks([1,3,6,10], closed='both')/ \$ ~: q1 t" _* w: }( E# E2 \
. k% u, \ `# I% c2 [# k4 D! S
IntervalIndex([[1, 3], [3, 6], [6, 10]],
2 I) c8 E7 S5 q0 N, U5 ` closed='both',
) v3 V3 u9 a& ?: ~- g/ i$ R. `+ i4 r* Z dtype='interval[int64]')
9 d% G1 d; f! l: n; j5 A* r, ^1
9 N7 @) @# S# h8 f* z0 o2/ p) Z$ j3 x4 y5 J" O& n, b3 L
3
2 u- _* c% J7 G. a# W9 g$ t, r- p# u4& b4 }2 k c$ f5 I0 M
5
$ D, q$ `- S3 L8 Efrom_arrays:分别传入左端点和右端点的列表,适用于有交集并且知道起点和终点的情况:
4 i" p& l/ Q* m- `pd.IntervalIndex.from_arrays(left = [1,3,6,10], right = [5,4,9,11], closed = 'neither')$ ?+ t! [, U: L2 n5 K' q
6 D3 P7 m8 U8 h5 A5 w* G, L5 x0 M
IntervalIndex([(1, 5), (3, 4), (6, 9), (10, 11)],0 ?) v- j6 c0 ~3 Q) t
closed='neither',
8 V5 ~5 M$ A4 F4 b U: i- _/ R dtype='interval[int64]')
) ?$ t; R% K$ N8 z8 C1
+ E# |! s7 i; C" D. N2
! A1 t( \- s- y9 e; G2 m3
: q* \: q: u9 b* ?8 z40 E# c& i, v( Y% H3 a8 \
54 o. x# U! q' U1 \# K
from_tuples:传入起点和终点元组构成的列表:2 e6 ~3 H) ]$ \* t
pd.IntervalIndex.from_tuples([(1,5),(3,4),(6,9),(10,11)], closed='neither')
6 m/ d. n- \" l% N3 C
/ k6 n. R" R5 J! G4 y# t4 GIntervalIndex([(1, 5), (3, 4), (6, 9), (10, 11)],
8 m" n; _$ r* ]- }3 E closed='neither',
) o# @' k- o0 x* I5 [ dtype='interval[int64]')
" l$ A9 e1 }# c& x0 g10 w( Z9 ~- }) o+ R6 ^5 a
29 R6 D8 T! L& J1 m, E
3
1 _5 e6 N/ V4 N: U6 I& q4" `' e* P2 P: s' `/ s! v3 q0 ?! W
5
. X; R4 g! [& V2 j" F2 kinterval_range:生成等差区间。其参数有四个:start, end, periods, freq。分别表示等差区间的起点、终点、区间个数和区间长度。其中三个量确定的情况下,剩下一个量就确定了,从而就能构造出相应的区间:
( S' Y- Q7 p1 g3 U! ]1 L' `' Kpd.interval_range(start=1,end=5,periods=8) # 启起点终点和区间个数
/ H6 D- G3 s$ }3 ^! W2 N) eOut[57]: C- h+ h; |3 v
IntervalIndex([(1.0, 1.5], (1.5, 2.0], (2.0, 2.5], (2.5, 3.0], (3.0, 3.5], (3.5, 4.0], (4.0, 4.5], (4.5, 5.0]],
# K' f8 v: {* P9 }$ e# _2 `8 s closed='right',& ^. r* k k9 Z. L# ^6 T
dtype='interval[float64]')
+ E4 `, j+ d; |& x8 ]4 F# V: j9 w3 f9 n% e
pd.interval_range(end=5,periods=8,freq=0.5) # 启起点终点和区间长度
! ~0 u, t% T; COut[58]: 5 ~8 g; P. i' C9 s" 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]],
( q% |+ }- u" A/ l2 r0 a$ t$ m: z closed='right',2 T% @4 u3 a$ t5 |! W W
dtype='interval[float64]')3 R( F& a; A( m/ V4 J
1
5 J9 n5 b! `6 l7 H6 x+ D9 T% f/ w28 N- Y0 u; \* R) K- n5 k1 X9 G
3# I! J8 r+ [' g7 j
44 C$ ?6 k9 l7 K& n0 J {) s
5
6 u2 w9 z W& L2 R& q5 u# ^2 M69 _: m# ~; h9 I0 S$ `
78 R$ a1 |# [4 \* ?: I' v+ o- Y# \
88 U' V" U! k7 v# ^4 n) R
98 ^. d% e, k# A1 r w3 e: M
10; L, t; r8 p! f) L6 ?
11
# b6 d2 ?8 a. r* j* p2 m- y【练一练】
8 @2 y' H ^' y5 W" x) Q 无论是interval_range还是下一章时间序列中的date_range都是给定了等差序列中四要素中的三个,从而确定整个序列。请回顾等差数列中的首项、末项、项数和公差的联系,写出interval_range中四个参数之间的恒等关系。
5 o4 A+ k: \2 {% b' e+ z$ i1 n( p
除此之外,如果直接使用pd.IntervalIndex([...], closed=...),把Interval类型的列表组成传入其中转为区间索引,那么所有的区间会被强制转为指定的closed类型,因为pd.IntervalIndex只允许存放同一种开闭区间的Interval对象。6 y* i8 c5 g: O5 s: t# G5 S
" [+ C' h; t# \4 S. rmy_interval
% e% R0 T: U; P. G8 M% r! |, a! `: iOut[59]: Interval(0, 1, closed='right')! d. ^0 s" E+ z9 ]1 O& C3 r3 K# b
1 J4 P+ Y8 O0 O* Y* k, ^ Z
my_interval_2
6 `$ G4 I0 C/ ]* P$ KOut[60]: Interval(0.5, 1.5, closed='left')5 W! d% z$ o7 Z9 [" ^0 q
% y8 O7 O+ b$ jpd.IntervalIndex([my_interval, my_interval_2], closed='left')
- b0 K) Z; z# W" {9 EOut[61]: . N. S/ l* g$ R! P. k2 U) E# Z
IntervalIndex([[0.0, 1.0), [0.5, 1.5)],
r( P; O& g* _5 _8 A2 C! n- f closed='left',
s6 h5 f7 Q7 m1 R dtype='interval[float64]')
$ f |. d5 N3 U0 Y/ S1& M, `0 \: s. a0 g' B9 `' X
21 ]* ]1 l' Y: v& @6 E4 n6 v( W
3
4 _& E# t0 L$ |/ i/ i4
% W, Q; F' M& R' N59 z1 {. C9 Z9 f% C8 o! R; n
6
; K' v% |/ |9 ~7: I* I4 J8 U" [+ @0 Q5 ~2 \$ U! n8 |
8
0 c6 ~2 b; ?; M0 i9 _& |; m' T9* v* x7 b9 l/ v" N7 p6 n( j
10
9 r" R' r7 {7 U11
) T3 C8 O6 l; N2 H, Y3 t9.3.3 区间的属性与方法
% d8 }0 n( ~9 a IntervalIndex上也定义了一些有用的属性和方法。同时,如果想要具体利用cut或者qcut的结果进行分析,那么需要先将其转为该种索引类型:
% y9 N8 s. `% k
7 Y) y' w- h3 r7 e' Rs=df.Weight, w6 Z6 `+ r* ~) [: V. k
id_interval = pd.IntervalIndex(pd.cut(s, 3)) # 返回的是每个元素所属区间,用具体数值(x,y]表示
% l/ A" y4 \$ _& V, ~8 ?id_interval[:3]
) K7 j8 L6 i( s" o' s p; n& z; _1 L J
IntervalIndex([(33.945, 52.333], (52.333, 70.667], (70.667, 89.0]],
$ ?. U1 t3 g. q closed='right',
0 ~: Q+ d7 z% y/ N o: p name='Weight',' Y5 s' ]! i; ~! b+ M; }
dtype='interval[float64]')9 s7 J/ F3 c2 T- J5 I2 d( ~
1. N. T- s- R7 J7 f
2, L' O' u5 t3 S$ |; c
3
$ r! ]1 H. q( z' C4 K4
/ H3 t: d4 x. }6 C5
/ R1 I1 c2 Q' w, @# v6
* u5 P# R- |8 R. k+ ~- o73 |( l' p! X& S. U V5 \6 U2 d
8
5 ^' ~# Y4 S) k5 F' r与单个Interval类型相似,IntervalIndex有若干常用属性:left, right, mid, length,分别表示左右端点、两 点均值和区间长度。
( A6 }' e0 D3 H$ ^) kid_demo = id_interval[:5] # 选出前5个展示
3 B) A& s4 F, P' x, I Q7 P
0 y5 \0 W- P6 Uid_demo0 j$ g. l* {; c, P* J) M
Out[64]:
7 G$ g! ?2 Z( B6 i" e cIntervalIndex([(33.945, 52.333], (52.333, 70.667], (70.667, 89.0], (33.945, 52.333], (70.667, 89.0]],
: s4 y }. h9 ^* u6 ^4 A closed='right',
& f8 q2 U* B! N+ a% U name='Weight',
+ X; I; \; F$ K1 i9 V0 ~, U dtype='interval[float64]')6 J. O7 F9 s3 I3 m& t* _
" p2 V; B3 D0 t2 Y/ F! ]. @id_demo.left # 获取这五个区间的左端点1 U+ f5 A+ Y: q/ e
Out[65]: Float64Index([33.945, 52.333, 70.667, 33.945, 70.667], dtype='float64')
8 A+ M7 L' U' }! ]8 f m+ F, f! s& V- [+ X
id_demo.right # 获取这五个区间的右端点; L. u3 a- ?: P4 e' [3 Q0 e
Out[66]: Float64Index([52.333, 70.667, 89.0, 52.333, 89.0], dtype='float64')
- R, e; [3 w* i# L. `% H+ L2 w7 _( e; ?+ y8 @
id_demo.mid
5 H8 R' V: k. T1 Y% m& I! EOut[67]: Float64Index([43.138999999999996, 61.5, 79.8335, 43.138999999999996, 79.8335], dtype='float64')
|1 |" b* T& l4 a) |( i
4 i5 ^7 T/ t7 q2 q' e. g3 x# R6 j0 mid_demo.length" \: X! g0 w% {7 U7 S* J
Out[68]:
8 ^: P* k0 f9 _1 kFloat64Index([18.387999999999998, 18.334000000000003, 18.333,
5 ]4 {+ B6 X+ Q8 L 18.387999999999998, 18.333],
6 D% c- p+ m r& s dtype='float64'): v+ v o1 e4 ?. K
) {! m% m' {$ `" C4 I& t0 a4 z$ [1
: a# O5 x( U3 h$ P. H20 g7 O1 X# @9 m7 D7 { \- N
3
& K( c( b0 a* D+ ]8 j! z' c: P5 w8 ]; G4
: F; F( W: a: {/ c1 F9 v8 N4 Q% e5 a" z$ ^2 z4 d5 K& s! k) O
6; d- r- t3 u* s2 A2 O- R+ K0 ~, S
7* Y( z7 O+ W" ?, D( n: H
87 p ^8 V& j' o5 Q# W2 U# A( A
9
, b* @! M) h/ ?; l( s0 Y" u2 J10/ Q( h& l- W! |+ s
11
4 }7 q/ a! C# g# W& \# r) h12# Q! ~: N( Z) |, p, {( E+ I6 i7 R8 b: Y r
13
/ ?- Z, m2 @. ^' K' n% j14& c+ j: \3 R7 W b B# Y
15% F7 J7 T9 ^8 k+ r. }. x
163 c$ ^+ Z3 t* W( S- z' D
179 q4 i! z! ^5 b% N& r! H& O
18
" ~9 w1 c0 ?" s0 Y& m8 _7 O* P$ g19
9 Z( p8 R$ \2 q6 j20
9 c0 c; `2 N; T, c$ @' g1 w! G21; t" m* [3 c. m5 M; _6 |9 t+ m
22
- f- _- x1 Y; z% X23
# _! w" t6 G s' C7 ?IntervalIndex还有两个常用方法:
9 w S! g+ t6 f5 ?" I$ B9 m5 D, Pcontains:逐个判断每个区间是否包含某元素. i' t& c, D. D, ~
overlaps:是否和一个pd.Interval对象有交集。
B* F+ [! C# b* E) [5 `) Jid_demo.contains(50)
9 ]! N$ ?8 o7 j* Y- j6 `; UOut[69]: array([ True, False, False, True, False])# s( k6 w3 N5 ~" w9 \+ A
9 M( u9 L% _. J4 ?" |' g
id_demo.overlaps(pd.Interval(40,60)); Z& g8 O! e2 D0 D' {" T
Out[70]: array([ True, True, False, True, False])
7 k0 W' H- I* |% N12 D5 d8 \+ [+ I) E8 V7 n5 \, B
2. [8 G$ i5 X2 P" ?8 g6 a+ F
3
( {$ w# d0 u' [; i3 C) d" g4 F" f- z8 m2 v, }' T; ^
5
2 k, `" D5 Q- p |2 q+ r0 K9.4 练习
k* u1 K* Q4 q1 B1 OEx1: 统计未出现的类别
1 X9 t/ Y7 C: O! [- {3 V' @ 在第五章中介绍了crosstab函数,在默认参数下它能够对两个列的组合出现的频数进行统计汇总:
+ M s( U5 Y6 V2 f z: b
0 w( c* U) _( U$ T) Wdf = pd.DataFrame({'A':['a','b','c','a'], 'B':['cat','cat','dog','cat']})
0 g3 g0 p5 a3 V& D9 E5 ~pd.crosstab(df.A, df.B)! A- y4 E) I4 a8 b
7 B$ z/ }2 e5 m7 m, q
Out[72]: ' ?( L I! q1 M. o4 ^
B cat dog
. y; V6 U& w* ~" K) FA
; Q7 R3 K7 y( F% U7 w& O; x" @a 2 0; S$ D9 d' L) F7 }3 m7 r
b 1 0
, U6 v7 k/ a9 uc 0 1+ j& q' a- @! t o- i2 A' Y7 U" Y
1
' m ?$ m; o" t) f1 `+ ~( X2: k6 C8 c' z4 T) L7 e" U
37 p. w$ \# C! s) c5 h! H
4% v, _0 C+ c6 V/ J- ^' b
5& t6 {! G; y. j5 `1 k' d
6
' Q$ H1 T8 p, x6 V5 f7
, O3 d F$ F2 y2 `5 ?$ Q9 v8
5 U% |( K. G0 q+ l2 V9( [ f, @. j6 A3 |, @
但事实上有些列存储的是分类变量,列中并不一定包含所有的类别,此时如果想要对这些未出现的类别在crosstab结果中也进行汇总,则可以指定dropna参数为False:" m: V* x# D( L/ ]# x- C2 L
' B; b/ V3 T1 K8 E: {6 ~
df.B = df.B.astype('category').cat.add_categories('sheep')
4 O% J+ N5 m- n7 f) Q/ Qpd.crosstab(df.A, df.B, dropna=False)( Q4 P( A% Z& X9 B/ Q% n8 p4 M' V7 r
# g9 p: c, ~2 ]8 _
Out[74]:
! ~9 ]' j9 a- g; O1 p6 mB cat dog sheep
, {# A! L' ~( I( | I, S# aA 4 G1 [6 |/ P8 o
a 2 0 0
7 _4 J6 k) C0 C/ b$ `$ ]b 1 0 06 O( F4 K2 }$ G7 j1 U$ i% r3 }; X
c 0 1 0- S" Y& z0 }" I0 N$ ~
1
) s6 B( J. x! v* x" q. Z/ Z2
6 |* r- ^7 E! t) ]% r: g3
* u3 P6 i- B6 F# T2 \4 J# D9 [# S4 L; A4
; z* ?' x/ I% ~5
" Z8 F" {4 l; Y. l6( T2 ~5 v( [) O+ t
7
- l, s( `5 w, w3 m6 d- ~7 N81 T% s' f9 y4 H; Y- f0 k( k. J
9& R b; c/ F1 H; }9 [4 u
请实现一个带有dropna参数的my_crosstab函数来完成上面的功能。7 R- _1 q0 G8 R) A8 Z7 Z* N' v
, N5 ]8 I- T8 Z. FEx2: 钻石数据集2 @4 m E2 u9 T3 g
现有一份关于钻石的数据集,其中carat, cut, clarity, price分别表示克拉重量、切割质量、纯净度和价格,样例如下:
9 n& h8 O" E; i2 t: y8 B' \& K+ X% F8 T' ?4 B2 p8 X# ^
df = pd.read_csv('../data/diamonds.csv') ' I/ e9 B7 x% w @0 L$ d
df.head(3)6 k7 T) l& }+ L; ]: Y
9 T, R/ _4 [5 Q N, N
Out[76]: P5 G1 q9 ?3 o" w+ S/ j& K+ U
carat cut clarity price& e! f. D) I; g4 A9 y2 d# w+ R3 b
0 0.23 Ideal SI2 326% P" y u& H' v z
1 0.21 Premium SI1 3260 h: p" Q, _9 i2 U
2 0.23 Good VS1 327 o/ M' i; d, O8 r
1$ e& {2 N! E, ?$ }0 r6 |* i3 Y
2
" X" _/ |- d7 v* ^3
1 p( E, i/ f" U, Y/ w* n4
% q) m) i1 z. P/ K$ U1 e7 |5
" `# R) _! c( h. d h% X' J( e6
: r, ]* b8 K) i0 S3 h7
- i* E0 w) v. A0 D8 G( i/ }! q ^3 s7 G8 i0 {
分别对df.cut在object类型和category类型下使用nunique函数,并比较它们的性能。
2 x% t/ _ ]( K/ n' P# i钻石的切割质量可以分为五个等级,由次到好分别是Fair, Good, Very Good, Premium, Ideal,纯净度有八个等级,由次到好分别是I1, SI2, SI1, VS2, VS1, VVS2, VVS1, IF,请对切割质量按照由好到次的顺序排序,相同切割质量的钻石,按照纯净度进行由次到好的排序。
& Z7 C" M8 s% A! z3 I; R* p' E分别采用两种不同的方法,把cut, clarity这两列按照由好到次的顺序,映射到从0到n-1的整数,其中n表示类别的个数。
6 B- J# X, u, H对每克拉的价格分别按照分位数(q=[0.2, 0.4, 0.6, 0.8])与[1000, 3500, 5500, 18000]割点进行分箱得到五个类别Very Low, Low, Mid, High, Very High,并把按这两种分箱方法得到的category序列依次添加到原表中。9 g/ z1 O( h* u: `+ b. @8 Y4 L
第4问中按照整数分箱得到的序列中,是否出现了所有的类别?如果存在没有出现的类别请把该类别删除。
* u+ o5 t9 q2 K% y- K9 W/ a; ?对第4问中按照分位数分箱得到的序列,求每个样本对应所在区间的左右端点值和长度。( o, ]& Q4 X% {: V. L5 d$ t
先看看数据结构:
/ ]: A6 k/ D/ T* h; q: `2 S; B9 x$ o; I1 H) ^0 x. Q
df.info()# o& c! h" m3 j$ G: l) k* N6 i$ U
Data columns (total 4 columns):
( ^0 ]$ A6 ^, o2 b! W& h% U8 w: f # Column Non-Null Count Dtype " }3 t2 X0 J' z! [- u
--- ------ -------------- -----
, T: b2 I/ N. y% ?7 T" y 0 carat 53940 non-null float64 `5 G0 J7 m' k: f. |4 B: ]
1 cut 53940 non-null object ; f! N+ q7 @3 d7 g
2 clarity 53940 non-null object " K0 ~; x$ v" j. s* S
3 price 53940 non-null int64 ( r7 U s2 y% H0 R- _, _( U
dtypes: float64(1), int64(1), object(2)
$ A' H8 v; C+ N) S& a* I13 v3 S+ S* w) L* ~2 U
2 S5 A. c( l! U- e
3
$ o$ O* j4 k5 X1 o1 e+ p) K2 Z y4* R/ B. l6 q/ v' x
52 Y. Z. G" L4 ?8 M" F9 Y
6
" m" p' s% q$ c* b77 u% v. a- V/ A/ R2 y u
8
9 t2 K$ k, l7 _$ I3 o! O+ ? w9
5 G8 S$ `* k2 D- p7 A x比较两种操作的性能2 F# K B. j0 X0 v6 U& @" V
%time df.cut.unique()
4 T/ B& p# q1 y
$ B/ C0 X p6 A, j' z& ^# r d& aWall time: 5.98 ms
; j8 z/ f" d2 Jarray(['Ideal', 'Premium', 'Good', 'Very Good', 'Fair'], dtype=object)
3 k2 b( Q7 x5 Y3 a6 k" x s6 o1; p! C- f2 [) l! E2 N+ N
2' K: t( d- [) n& H
3* @1 k" ~! D" g- S4 Z
4
# H% ^! M, N4 G2 Y+ f- r%time df.cut.astype('category').unique()
. g7 R+ j0 q& g" L) ^+ {
2 ^! q3 D# g4 z# y( d' C) NWall time: 8.01 ms # 转换类型加统计类别,一共8ms- N4 `5 V8 M3 n x. U' Y- O
['Ideal', 'Premium', 'Good', 'Very Good', 'Fair']
6 A" _* `& U _Categories (5, object): ['Ideal', 'Premium', 'Good', 'Very Good', 'Fair']
1 o _3 J6 Z' u! E% ?1/ J, Z/ m0 _0 c
2
; Q7 n/ r6 p9 q) n( {) A3 E3
, o7 ^; Z8 O/ d' h45 p1 O8 E7 _6 L$ b7 m d. q. H
5
S4 Q4 B( I" F% S4 F! |df.cut=df.cut.astype('category')# p% @5 g9 Y8 u+ {0 h8 f# y
%time df.cut.unique() # 类别属性统计,2ms
. Y5 D+ m/ _1 n) P' Z6 ~
% B- Z* B8 Z' k4 u" V. Q2 KWall time: 2 ms* s' @4 E2 ]; N
['Ideal', 'Premium', 'Good', 'Very Good', 'Fair']( |& S0 Q, t* W
Categories (5, object): ['Ideal', 'Premium', 'Good', 'Very Good', 'Fair']; j, P+ D" Z+ N) y
1 A! z3 O) } ^( j' L/ @# ^
2
* L; o& s% Z& @8 ? i: o3
* O! j7 V8 H, `% ~42 F. S: T: q2 X( r% f/ m' W" E
5" R8 [; X1 J+ p
64 ~- t! @* X" `' ~4 g
对切割质量按照由好到次的顺序排序,相同切割质量的钻石,按照纯净度进行由次到好的排序。
+ i+ f& d+ O4 T6 F/ r# P# Qls_cut=['Fair', 'Good', 'Very Good', 'Premium', 'Ideal']
4 x2 L6 G L2 ols_clarity=['I1','SI2', 'SI1', 'VS2', 'VS1', 'VVS2', 'VVS1', 'IF']
4 Z& \* M' ?5 S- ~df.cut=df.cut.astype('category').cat.reorder_categories(ls_cut,ordered=True) # 转换后还是得进行替换
4 w& L+ B) b0 {' Ddf.clarity=df.clarity.astype('category').cat.reorder_categories(ls_clarity,ordered=True)
1 D/ O0 @ }' W7 F
+ _, ?8 k: U7 h, ^df.sort_values(['cut','clarity'],ascending=[False,True]).head(3)' S5 q) c& e7 R( a" Z/ Q
; Q" Y1 i1 F5 d/ |. d4 i carat cut clarity price3 {. G, S7 p/ Z- `$ ]6 l
315 0.96 Ideal I1 2801
7 P" c! L8 @4 S( n) S535 0.96 Ideal I1 28266 X2 R8 D8 N# Z" Y5 K7 N+ n# t$ `
551 0.97 Ideal I1 2830
2 g: X' l Q: A4 @$ p6 b* Y |9 e1# g# g4 C0 i- L4 r) K) F! k
29 _/ V w: i4 q# Y" _
3
3 t: s& R- t6 M. I3 y4
3 z; X# {) E. ^8 T! Q5
. J7 H$ H( F1 Y6. p( z/ r/ w7 o0 e# { }
7' g; o1 C% D$ S# L
8- X: X- B! h# \( R0 N
95 J" |9 b! H! o( `1 w( x4 N
10
8 t4 B4 c/ Y/ K, M11
, B7 \3 R" v( ?8 N0 A分别采用两种不同的方法,把 cut, clarity 这两列按照 由好到次 的顺序,映射到从0到n-1的整数,其中n表示类别的个数。
8 ?, l2 I1 Z2 m# 第一种是将类别重命名为整数9 h& X3 D" C5 _; J9 D% v- H* a
dict1=dict(zip(ls_cut,[x for x in range (4,-1,-1)]))
" d+ I/ ^9 e1 ldict2=dict(zip(ls_clarity,[x for x in range (7,-1,-1)]))6 F+ [8 k6 J/ p7 c- |- z
# s J$ n6 ~# v: Rdf.cut=df.cut.cat.rename_categories(dict1)
8 f7 R. b2 L& ^4 Qdf.clarity=df.clarity.cat.rename_categories(dict2)
5 q/ s( q% \* t7 @' F4 g' f' Q7 }' Qdf.head(3)
4 D; _% ~, q _. K( t% F2 s" r6 [% i) ]. R
carat cut clarity price1 l2 o- @3 W4 R
0 0.23 0 6 326
- N5 x: s" P5 V) z2 E) ?1 0.21 1 5 326! X2 s) w; ]& V4 r3 `; x
2 0.23 3 3 3276 R! t# o0 y' g9 d7 E0 W2 S0 l
1
; f" p4 a) p& P9 ^% B4 B" {2& j& ^# E0 h1 G/ J2 W
3
! `7 ?! |; ? P5 _48 y) R: y8 K% a- p0 b8 J" i6 e# |
5
, x* b: f% s) O9 r- X! g7 s66 D& {/ I* [' L6 {
7
, K% v3 I `1 K# q7 A. n+ H8
# f) v/ K9 ^1 Q/ F1 z6 R9
9 U" ~. Q* r* j; h10& D3 R m, ?: J
11
! d' ]. x$ f( K+ |- r. I- J% H125 p- a/ P3 y* d/ B6 C& ]1 f
# 第二种应该是报错object属性,然后直接进行替换
) {1 _6 z: S* Y2 s; | Gdf = pd.read_csv('data/diamonds.csv')
3 ^7 ?- w9 d- n5 m5 zfor i,j in enumerate(ls_cut[::-1]):
B, j; R' T( W) R& ~ df.loc[df.cut==j,'cut']=i
' @2 l' [7 z* N% I! M& W7 k+ M
3 @# S% {, _# B! hfor k,l in enumerate(ls_clarity[::-1]):
2 V( U# z5 @: ^( b df.loc[df.clarity==l,'clarity']=k
8 ~6 T& R6 s! t7 N. Z! Ldf.head(3)* ` f& D. P$ x+ Z" Q
$ |9 T$ z4 O* Y( b( |1 H
carat cut clarity price
4 r2 \& Q* ^3 `' ?5 ?$ b0 0.23 0 6 326
6 f6 y" T$ I, |6 i+ j) O$ e' m1 0.21 1 5 326
1 D' I" v, R9 Y7 |2 0.23 3 3 327. Z" r% [* s1 P5 y% x! n
1
0 \% P0 W& u$ F2
) U1 ]0 }9 w& d3 R9 c A k! X3- o6 d- f) S5 W9 X2 |, n
48 w5 y6 n; T# B' k( h" y
5
, a: I6 ^: g* E. \; h6
: l' z4 K$ P) [2 c2 R# b7
5 X8 D1 o5 r5 z8 U8 L. u5 ?4 ~- z" S
9$ m& H7 Y2 |! w' K( i% n. y
100 {' r' o) ^8 n
11
, l1 ]+ m# B) r, I5 E12
: I. G# M& r( Z. x& r: }- Z. E13
+ V( q2 }* h0 J3 ]* n对每克拉的价格分别按照分位数(q=[0.2, 0.4, 0.6, 0.8])与[1000, 3500, 5500, 18000]割点进行分箱得到五个类别Very Low, Low, Mid, High, Very High,并把按这两种分箱方法得到的category序列依次添加到原表中。
9 [" C0 }+ W# @4 Q- w# retbins=True返回的是元组,第一个才是要的序列,第二个元素是分割点
3 Y7 t0 O7 Q5 Q9 R/ y5 ~' @3 i3 ^! \avg=df.price/df.carat
4 `- g* ~, d2 A* C
6 \9 r# ~" e+ tdf['price_quantile']=pd.qcut(avg, q=[0,0.2,0.4,0.6,0.8,1],
% w/ d9 v ?, s& y labels=['Very Low', 'Low', 'Mid', 'High', 'Very High'],retbins=True)[0]$ n( t- T2 X4 X; M* f- U# V( F$ j
, C* P& l+ f5 k, P0 }
df['price_list']=pd.cut(avg, bins=[-np.infty,1000, 3500, 5500, 18000,np.infty],1 H; l* g6 Y! R: f9 }
labels=['Very Low', 'Low', 'Mid', 'High', 'Very High'],retbins=True)[0]- R- d# R+ s: M7 |& k) L
df.head()
5 a% l2 n7 P* L) a8 q4 c; M# _+ s3 M* _% f
carat cut clarity price price_quantile price_list- a$ \3 x9 h) X) y
0 0.23 0 6 326 Very Low Low* z% K3 j# I, a* _8 p( P
1 0.21 1 5 326 Very Low Low% x7 V" K8 D$ N- A5 t: U3 H
2 0.23 3 3 327 Very Low Low- l6 ~+ v" }. u
3 0.29 1 4 334 Very Low Low& H! u# R1 Z/ q4 `
4 0.31 3 6 335 Very Low Low
2 N7 m0 |' z% H, K% h$ G/ d8 c% ^% w6 F+ y) }! d$ B; }
18 B. Y) w b! w9 R- {: }3 Q& p1 Y
2
. q2 j% y3 X' W; O3
0 n5 W) u( W3 X4
O6 q9 t$ _* z& y5 P5* h) L5 [: S% {( U6 _# n" h5 h5 d+ K
6
5 Z0 B3 U1 A6 g& ` Q7' u8 N" x( a, n
8
# C/ o+ S6 z9 Z- n/ d8 k9) Z- J0 _6 y; ^% f9 p' q
10% c. P( c" [* y- ]1 G( |9 r
11
0 b) m L0 {- R& {) m; L12
4 ]8 C- m% _- c" y13
# t# U$ Y0 r$ y% m$ s8 B# e14. ^1 N8 ?9 y: _, g! T) ]5 T
15
# E: O4 Z0 @% Q6 z4 V: D5 [! b( P7 C16
$ i5 C3 K# J9 R8 S! E6 \分割点分别是:
. P$ H3 n* ]' r* X7 Z1 B D
5 L5 U5 k! `7 d) `) |, I& harray([ 1051.16 , 2295. , 3073.29, 4031.68, 5456.34, 17828.84])3 W# n! V& j# P3 _9 t5 M: y
array([ -inf, 1000., 3500., 5500., 18000., inf])
6 }! b! a: B# c S1 f. n! ^. S+ B C1( S. U% F5 B3 Q: e( L9 l4 v! w
2& R9 I4 x. ?* R% \ P% {9 \. h
第4问中按照整数分箱得到的序列中,是否出现了所有的类别?如果存在没有出现的类别请把该类别删除。- Z: m0 y6 E4 ?8 r
df['price_list'].cat.categories # 原先设定的类别数! r* ?" ~- i, y, h3 ^- c; e
Index(['Very Low', 'Low', 'Mid', 'High', 'Very High'], dtype='object')4 x4 I( S8 z$ S* g9 o! I4 F
5 P% H# B5 M- U* U% ~0 t! V
df['price_list'].cat.remove_unused_categories().cat.categories # 移除未出现的类别
+ ]+ x) L# I! P: fIndex(['Low', 'Mid', 'High'], dtype='object') # 首尾两个类别未出现, m, u0 m4 j/ _' O8 u
1) T2 h, d% |5 }3 Q2 f. V
2* ? h" c# L3 V* Z! v& A. B
35 s* e2 h. p) y+ c' B
4) u0 c1 h2 m! H* ~
54 ]7 c" I9 `+ _" U" n
avg.sort_values() # 可见首尾区间确实是没有的0 \# Z/ u' Y. Y$ {$ I. M/ k
31962 1051.162791" D0 ~' _& e6 a) W! l: x% C
15 1078.125000" n; J+ ?/ L. [! C( L: O5 N2 `
4 1080.645161
6 @7 a" L; Q0 `2 y- f28285 1109.090909
& f3 r# N+ u" \. a$ r13 1109.677419) R p1 E; X' w6 k) b
... 2 A1 L7 ^7 e$ {% A
26998 16764.705882
9 `/ V& D1 n3 L: ~) j27457 16928.971963. Y3 O! _) E- y' t% h
27226 17077.669903
: w, V- D) m" H1 v0 } q27530 17083.177570
' T2 ^9 _& p8 Z+ g# V27635 17828.846154- k7 y" h: Q8 H+ Q% c
1
5 }+ j! B2 H( X; U0 _* r! ~2( e6 |2 t+ @# `$ E) @# F9 y
34 h0 Y# ?* G- [/ E
4
7 J. d; L( [( S- _53 \ }5 _) o3 R7 W; q | J. {9 p
6% ?7 s; K( u, N8 t: B1 Q
79 p0 n$ h' |0 L( w' h
8
, Q2 H1 ~; |$ f) K% T4 A/ k9
5 {, f5 L" L) m+ `9 R10
8 R. j; a+ _$ [' w2 F118 f. e+ v, \) e% Z# ?9 y2 O- I) b
12
% n$ b' X" I$ e: i" u3 {) q对第4问中按照分位数分箱得到的序列,求每个样本对应所在区间的左右端点值和长度。; q6 t( k" V( ]5 J" ^; t$ k
# 分割时区间不能有命名,否则字符串传入错误。
3 R. M3 ~4 N2 V1 q' N; u6 Did_interval=pd.IntervalIndex(
0 V8 Q* T# s [5 N: L" t pd.qcut(avg, q=[0,0.2,0.4,0.6,0.8,1],retbins=True)[0]) Z2 f3 R t! `& o' ^
)
/ t8 W6 i/ M# {# _id_interval.left
7 O5 |$ L( ]4 R, y! Bid_interval.right4 m& y2 U7 S/ h; H. D
id_interval.length
9 i/ W# J' _8 N1% h! t% ?$ J, G0 E
2
Q& O i: \' S! ?& F" Y33 U! a0 u% C/ y2 b4 S6 @2 X
4
. G/ y6 h0 G1 C4 n9 N: C5- p+ y+ b: y8 Y, F
6
) c7 B; P! @0 l; b! X$ Q6 u7
`* `& |5 Y" o0 O! M第十章 时序数据+ D/ k. a4 O9 e! X8 w
import numpy as np
" X% W: s$ e P- Y" a/ V& t& dimport pandas as pd
" s }; p0 f0 X" j; G9 X7 B2 p* r7 S14 Q. ~0 W( p1 N' O1 ]) K# Q
2
! F. @% Y5 a! i" ]6 v
; {' l1 S' Z6 [* Y- @' ]+ R/ b0 B& ?
10.1 时序中的基本对象
. B% M- L8 r0 q7 ] 时间序列的概念在日常生活中十分常见,但对于一个具体的时序事件而言,可以从多个时间对象的角度来描述。例如2020年9月7日周一早上8点整需要到教室上课,这个课会在当天早上10点结束,其中包含了哪些时间概念?
2 X+ H2 p# p _
. g# x2 D, e+ ?9 I6 J( [2 D会出现时间戳(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的简写。/ ^9 E; Q& R2 }6 U; n8 r
! [0 |# n7 h; X* E会出现时间差(Time deltas)的概念,即上课需要的时间,两个Timestamp做差就得到了时间差,pandas中利用Timedelta来表示。类似的,一系列的时间差就组成了TimedeltaIndex, 而将它放到Series中后,Series的类型就变为了timedelta64[ns]。
9 H Z) O6 ~- W" s0 e( |8 [. C: v: l$ z, K5 u( a; p4 z; a
会出现时间段(Time spans)的概念,即在8点到10点这个区间都会持续地在上课,在pandas利用Period来表示。类似的,一系列的时间段就组成了PeriodIndex, 而将它放到Series中后,Series的类型就变为了Period。
. b/ g7 _. }) A, r. {# w! d% b7 f6 _0 f
会出现日期偏置(Date offsets)的概念,假设你只知道9月的第一个周一早上8点要去上课,但不知道具体的日期,那么就需要一个类型来处理此类需求。再例如,想要知道2020年9月7日后的第30个工作日是哪一天,那么时间差就解决不了你的问题,从而pandas中的DateOffset就出现了。同时,pandas中没有为一列时间偏置专门设计存储类型,理由也很简单,因为需求比较奇怪,一般来说我们只需要对一批时间特征做一个统一的特殊日期偏置。! G' H8 P4 ]: W6 X0 M+ M1 H% y
" @: B( E" a: A2 }$ S 通过这个简单的例子,就能够容易地总结出官方文档中的这个表格:$ ^! J$ C& p: d& g' S) s
& [% k }6 N7 d% C; y1 w* @ d
概念 单元素类型 数组类型 pandas数据类型
/ s* k: k# W6 B- u* i/ [0 bDate times Timestamp DatetimeIndex datetime64[ns]5 ^ a j3 t6 G9 ^, W
Time deltas Timedelta TimedeltaIndex timedelta64[ns]
: q3 _7 l+ X- U( K4 e$ X9 gTime spans Period PeriodIndex period[freq]( j% P$ _2 D( c5 D: [% \" |+ z
Date offsets DateOffset None None
. ~2 c; E- \8 a* `" v 由于时间段对象Period/PeriodIndex的使用频率并不高,因此将不进行讲解,而只涉及时间戳序列、时间差序列和日期偏置的相关内容。
# {- K" b& D8 w0 u/ o" }. \
% R; p9 k' [' [ w10.2 时间戳
6 E* Q+ i6 D/ b10.2.1 Timestamp的构造与属性& L n) _1 ~3 g) M
单个时间戳的生成利用pd.Timestamp实现,一般而言的常见日期格式都能被成功地转换:
' @6 u$ E% x/ F. g6 R7 }" w& k& l+ u9 G- ~5 H
ts = pd.Timestamp('2020/1/1')
0 S9 n! {' P" p1 k5 L/ ?4 _ l) I5 E$ o/ D0 x8 r* r$ h
ts
% h: W2 D1 w# U) \& c; |, B7 T' j% aOut[4]: Timestamp('2020-01-01 00:00:00')& G' @9 [ Z3 u( k4 X5 K* l
( D9 [8 v! c4 w$ q7 Z2 i0 X5 Sts = pd.Timestamp('2020-1-1 08:10:30')
: m2 t; g" J F" i2 e9 n6 P4 ~: J& s9 f& Y2 \
ts. K# t, q. i# X2 ~& w
Out[6]: Timestamp('2020-01-01 08:10:30')
- p" N( }5 Y! {9 r/ x1# h+ S/ N# ^! O
2
: l5 n+ x- X1 i* [$ W) u8 m3) U. J+ w/ ?, D. u. C3 V3 r
4$ j, n0 C$ F- ?3 l j4 R5 B
5. C, h# z0 z) U4 c1 }- k# Z( K
6; O0 S, g ^% y, I# \
7
/ r/ a( b6 ~9 e L5 }6 r8/ x* ^% [2 |0 x7 f# n
9
$ U1 I2 J' Z5 m0 P8 [& J/ A9 e通过year, month, day, hour, min, second可以获取具体的数值:
; o* ~5 R& P9 V6 X+ {* B3 Z1 I
2 m7 B8 ?( q. D9 M8 x/ sts.year
: H( L/ {4 Q, e4 @. wOut[7]: 2020
Q! S0 M2 M r" R- a' c& f/ v4 s6 X" \
ts.month* @) @( p7 [# g3 m- b- Q
Out[8]: 1
+ i. d/ q3 m$ k- _; G4 V/ e3 ^# E' |7 b
) B9 A( `' ^8 B# h; Hts.day
+ E/ `& y$ E& n- I4 hOut[9]: 1
4 @9 Y0 x: q3 R Y
! g& d9 a8 `% pts.hour- C( |; l" t% j/ I0 z
Out[10]: 8. m, T' K* N: ~. b
2 y* o; [, ]1 M7 N3 |0 [0 ?/ Dts.minute
6 Q9 R( z8 i( D P% EOut[11]: 10
5 B# m' V' E- d, j% L) D2 ?( t' j# R, ?
ts.second
$ t% f8 {& d9 h$ F$ WOut[12]: 302 o% D# `( ^3 r
' E' \ d4 R1 X+ F0 Z; o" z1, @. P/ O! d2 v2 m& |4 ?
2
! ?# B: I4 K9 ?! G3
( k6 B K0 L3 I/ G5 ^3 [2 g4
& u8 Q* A$ I+ w2 ?: O4 m5
, H% K' Q B( o7 n9 n5 z6( t s4 x; v; k& J
7
3 F3 T5 ]1 b: F; B- Z% D8 a" X4 X: D, ?& K
95 R- k- y' N" E" T
10
_" W& b3 X! V6 Y& V% D; e11. w+ O5 d! J% t& o
12
7 J3 _+ R4 ^7 }1 c6 b$ Q13" L9 Q3 n4 y# U# X7 @" H
14' h) W, E8 s+ T0 K9 _
15
) k- o2 l9 i3 Z# ?, G7 y! t* p: L163 j0 f6 v0 U* H3 }( z' ^4 {* U
17
T5 |4 \: V! f$ l6 u9 ]+ v( Z# 获取当前时间, e# B" i% f# Q- L
now=pd.Timestamp.now()7 M: C. h# d% s! k
1$ h: C7 a N- _( I8 S- h
24 c, {8 h0 ^! g" M1 b L
在pandas中,时间戳的最小精度为纳秒ns,由于使用了64位存储,可以表示的时间范围大约可以如下计算:7 v: b3 O1 X! z+ g- y! B* E
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)
B1 [- }; A$ m" [3 D: YTimeRange=
, x1 Y4 a* E' o, i) J# m- _10
' ~4 q$ O q/ p8 H1 O9 Z) t! s& |9' {; G3 u. X0 ]( G& u2 e$ o6 ?9 G
×60×60×24×365
. n# G8 p8 k; N# |; t2
1 ~4 T1 w& Z/ |* H) M9 ^" r( \) v" G64
$ m9 P) T' U. {+ X+ q" c1 H% Y* m# z- a0 b0 E8 d
& F: V1 d; ^: p# b6 C s4 p: S& x' G ≈585(Years)
& [4 B ]& {1 b- p8 |- s' Y) Y- _( g
通过pd.Timestamp.max和pd.Timestamp.min可以获取时间戳表示的范围,可以看到确实表示的区间年数大小正如上述计算结果:
/ Y( P$ T: }" j* M2 v* D
$ i3 K& Y5 p6 U# v6 N7 Z bpd.Timestamp.max
# y( b! t1 e. q/ Q" z- v& ~8 ?Out[13]: Timestamp('2262-04-11 23:47:16.854775807')( T- j# {5 B& |& t
: j6 f V- L1 Z+ t5 v5 P# A1 {pd.Timestamp.min
5 Y6 v8 W: E% l7 Y6 s9 y2 Q) y. i1 eOut[14]: Timestamp('1677-09-21 00:12:43.145225')3 f, C- o$ `8 B8 g: A0 [
# P! w u# \" F; Xpd.Timestamp.max.year - pd.Timestamp.min.year
8 n& T# c& J, T; X6 {( Q* ^Out[15]: 5859 Z3 R, k# S% r- U" D; y+ Y1 B; K ^
1
5 W( O, p0 F. r5 X2
( D! [; `7 p4 h, Y3( v3 z7 l- Y5 a8 [' X
4' j T7 n$ _, C2 D. U
5
y$ {6 v! k2 v$ k6" G4 t8 b: T0 l, J5 U9 Q' {) K I4 C
7/ h, U) p+ f8 g8 L; r0 `
8) a: F) d6 ?, @/ H; a4 W. P) v
10.2.2 Datetime序列的生成4 d, x9 i+ M8 `% R( F6 M, W1 T
pandas.to_datetime(arg, errors='raise', dayfirst=False, yearfirst=False, utc=None, format=None,6 n, [& C. X6 i5 d- Q: S
exact=True, unit=None, infer_datetime_format=False, origin='unix', cache=True)
( p. k# c) F$ V6 @# W1
, k7 X+ e" w9 {" H( y2
; W7 S3 y; E! Y9 b& `pandas.to_datetime将arg转换为日期时间。' y; k! E: S, A* I( c1 _
1 e* E% y$ T( P, V
arg:可以是argint、float、str、datetime、list、tuple、一维数组、Series、DataFrame/dict-like等要转换为日期时间的对象。如果提供了 DataFrame,则该方法至少需要以下列:“年”、“月”、“日”。
* ^( d" S8 j# T9 K0 s8 {errors:
( l2 K4 }3 m! U& J* ]2 Y5 t- ‘raise’:默认值,无效解析将引发异常
4 A& Z2 X" o8 C- ‘raise’:无效解析将返回输入5 H6 ?) D* z/ v6 t& r ]
- ‘coerce’:无效解析将被设置为NaT
, o% u+ F; D3 s" H7 o; F' Hdayfirst:如果 arg 是 str 或类似列表时使用,表示日期解析顺序,默认为 False。如果为 True,则首先解析日期,例如“10/11/12”被解析为 2012-11-10。如果无法根据给定的 dayfirst 选项解析分隔日期字符串,会显示警告。
# e" ^) k% F- Y. n8 Gyearfirst:如果 arg 是 str 或类似列表时使用,表示日期解析顺序,默认为 False。如果为 True,则首先解析年份,例如“10/11/12”被解析为2010-11-12。无法正确解析时会显示警告。(如果 dayfirst 和 yearfirst 都为 True,则 yearfirst 优先(与 dateutil 相同)。)
. z3 N' g$ `$ E/ Hutcbool:默认None,控制时区相关的解析、本地化和转换。请参阅:pandas 有关时区转换和本地化的一般文档
3 r! U' f/ M3 B, T( Eformat:str格式,默认None。时间戳的格式不满足转换时,可以强制使用format进行匹配。
0 a; B* J; U9 X' aunitstr:默认“ns”。它是arg (D,s,ms,us,ns) 的表示单位,可以是整数或浮点数。这将基于原点。例如,使用 unit=‘ms’ 和 origin=‘unix’ (默认值),这将计算到 unix 开始的毫秒数。) g% K4 _" J$ W# J
to_datetime能够把一列时间戳格式的对象转换成为datetime64[ns]类型的时间序列:5 \8 n* }8 r) s) P3 j2 L, U
pd.to_datetime(['2020-1-1', '2020-1-3', '2020-1-6'])4 h0 m J7 \6 F/ G/ r( y' {5 u5 T
/ \% {" v" Y- {% Z8 @
DatetimeIndex(['2020-01-01', '2020-01-03', '2020-01-06'], dtype='datetime64[ns]', freq=None)* T2 u) Y8 N- K; ?0 \& H
11 q3 A; j$ ~! _# P, H
2
) p0 B$ |# f2 N' o( O; V3
( ?+ D/ W* V q, Q在极少数情况,时间戳的格式不满足转换时,可以强制使用format进行匹配:
. A/ y7 r* K1 p4 D/ P( H
$ D$ E8 b3 L! A) X' a1 rtemp = pd.to_datetime(['2020\\1\\1','2020\\1\\3'],format='%Y\\%m\\%d')/ y2 v- a2 p5 q) [& N
temp; I4 n; L1 F- I% S% @
9 {* u4 F3 B) g1 O4 `# c% XDatetimeIndex(['2020-01-01', '2020-01-03'], dtype='datetime64[ns]', freq=None)
0 d8 p- G5 ~! V: d# C6 M1
" {" c/ M5 N& o# p7 S2 ]4 m' M2
% O: a; `5 @: k/ V& L3
: C6 [4 N8 |$ v8 Z- x: v4
5 ~% H0 V }0 s: C$ @ 注意上面由于传入的是列表,而非pandas内部的Series,因此返回的是DatetimeIndex,如果想要转为datetime64[ns]的序列,需要显式用Series转化:4 A# G+ ^! u. Q: ~$ W
* S: Q) I+ g* g; |/ T! l! bpd.Series(temp).head()
; S @" F8 }& A* W5 F8 L0 r
B/ t( N. E1 Y( i0 U0 2020-01-01
& B( R8 i; A2 [) G0 j1 2020-01-03
1 {0 T. Q, M0 vdtype: datetime64[ns]
- h% X' s0 ]) @# y1- p, \# F) {% G7 c
28 Y: w6 I8 L( \' h {5 _- y3 R6 H2 G
3) C, \ e5 a! T$ U1 Y
49 `. r' e7 p5 E0 o: Y" k# \
5
1 q* V% V) \9 D; o- z$ m. _1 M下面的序列本身就是Series,所以不需要再转化。' q9 C0 e4 f' ^/ ]: X( k: T' t
. A0 d- v0 J8 Q+ _2 P7 ]. n
df = pd.read_csv('../data/learn_pandas.csv')
* d: @3 x0 T" `4 a( us = pd.to_datetime(df.Test_Date)1 m' {3 |8 x& `2 k: s
s.head()
% |5 c7 n/ M3 u& \0 y5 Q, _* D2 X* H# B: D+ n y- g
0 2019-10-058 G/ Q1 K; y: R7 X9 K+ Q
1 2019-09-04" ^/ p2 q4 E( D# M) D/ e
2 2019-09-126 ?3 T4 X6 F8 r H" I/ t! \7 [( Y
3 2020-01-03
# Y+ B9 m* @. B% ~+ W4 2019-11-06
1 d8 ?8 e6 s; O9 v# n8 u# D; z# z' lName: Test_Date, dtype: datetime64[ns]. K/ m& d% ^$ m a, c
1
j$ k$ y+ O2 Z9 R( U0 g0 |# \25 _. j6 h- J0 I: m9 ]
37 F1 M" e, J# R( c- ?8 t/ e
4; [) s7 d" Z# E( A3 s; E
5
( J+ a9 @' {, b6
3 z0 w5 n/ k1 k/ u+ S& z7
$ z2 [+ {1 k/ ?! p0 g1 `& y8
5 @! c: p( e& e5 A8 |6 G3 b& B2 o9
* o( ~5 Y. `1 |: \4 J: _10
: C( I5 t# I7 G& s2 D. o把表的多列时间属性拼接转为时间序列的to_datetime,此时的列名必须和以下给定的时间关键词列名一致:# L& C2 |: p5 I- i7 c0 [5 b. m
df_date_cols = pd.DataFrame({'year': [2020, 2020],
0 y+ u- }+ X; i7 I 'month': [1, 1],* I0 j& R- m, j
'day': [1, 2],: m# d4 `/ b3 W8 _0 k3 ~! @0 G
'hour': [10, 20],
( A+ a/ T) l* ~! [ 'minute': [30, 50],
8 h% A1 I& L! u9 J 'second': [20, 40]})
; r w6 [% N5 O9 O: ]' r% c, spd.to_datetime(df_date_cols)' m" f' P7 ^& \5 ]5 k% R/ {
5 }3 h* z1 o; `
0 2020-01-01 10:30:20
% f- g/ p& ~5 h, a6 x7 u9 Q1 2020-01-02 20:50:40
6 ?6 m, I" F% R$ Z6 `dtype: datetime64[ns]
+ c1 k) }+ u; I# ~: C# c1
5 m' G6 ?- n% T; @: v3 ?: X2
, z3 \# ^; h+ \6 _" x) M; z& P3
; e+ @ H& t# i. Z4' t2 x. A" L; [" @* ^' g
5+ G5 n: q# K$ I9 Y( u) d. j
6
" ~* b6 B7 Q( Y$ w( }6 k7
. \; J; s2 Q1 W% S, W8
6 v6 ?, f+ e: s; i9
7 F2 x% E6 Y' z/ {0 ?10
, |7 v( m; [2 R11
6 d3 q3 y8 y8 X8 o8 {# adate_range是一种生成连续间隔时间的一种方法,其重要的参数为start, end, freq, periods,它们分别表示开始时间,结束时间,时间间隔,时间戳个数。其中,四个中的三个参数决定了,那么剩下的一个就随之确定了。这里要注意,开始或结束日期如果作为端点则它会被包含:/ ]: z4 @% r7 D
pd.date_range('2020-1-1','2020-1-21', freq='10D') # 包含
) n0 [3 m9 f% Q% p1 q! g/ ]' M4 xOut[25]: DatetimeIndex(['2020-01-01', '2020-01-11', '2020-01-21'], dtype='datetime64[ns]', freq='10D')' e1 V" g4 x0 t- ?) _' ^+ [
$ f( j; Y1 @+ d9 q+ Q% M
pd.date_range('2020-1-1','2020-2-28', freq='10D')
/ A8 p% g0 @. KOut[26]: 5 }$ l- z! N* f3 K, r0 Q+ L
DatetimeIndex(['2020-01-01', '2020-01-11', '2020-01-21', '2020-01-31',
2 [9 o- v, b1 ^+ e) T" B '2020-02-10', '2020-02-20'],: R( w- F6 A( r5 P0 t5 p
dtype='datetime64[ns]', freq='10D'); [; s$ G* m" I3 [$ e, ]" z4 p' e
g3 p& I5 [( y$ b' {( p) L+ Zpd.date_range('2020-1-1',
+ r; }0 o5 h7 m S! }; S '2020-2-28', periods=6) # 由于结束日期无法取到,freq不为10天
" {& l; I0 b$ B" i+ k
z0 K8 L9 W, d5 n2 A* Q; D }( uOut[27]:
3 I. D& U N, p; P( G2 A+ VDatetimeIndex(['2020-01-01 00:00:00', '2020-01-12 14:24:00',! A! u: K8 {* _
'2020-01-24 04:48:00', '2020-02-04 19:12:00',5 _7 F& Q6 B! r+ ?# M# P* Z, t
'2020-02-16 09:36:00', '2020-02-28 00:00:00'],' G% C4 e2 ^: P$ |9 G
dtype='datetime64[ns]', freq=None), o6 o( T( W1 `
( q( C+ a1 \1 I! e' n8 ~' i1/ d" B8 D- l3 R. v% @; g8 {
2
4 e4 K) M0 Z( k9 F5 ?' J" I9 P8 b5 U% o3! `8 v& F4 q8 M
4
# I, }- g5 N1 `8 {3 J1 |8 ]3 s50 }; x! U$ b, f* Y! K6 g
6" @ T$ L) E+ @- M
7
8 W8 l( \6 j3 y" T8. P6 ~% x. @6 t8 ~2 y- S
9! N' u& ~! V( w8 C# b0 O
10) N+ ~7 E$ P9 A) d, |
11 I, K+ G. U: m
12. o4 Q+ P0 k/ v/ Z
13
6 W ]2 H6 u6 s( Z4 H. B. r8 V14) t& ~( i8 s& y/ L
15
8 [+ b+ G- x2 _$ x+ E16
) j7 E) d4 E. t; {: I# L m; u2 y, z17( Q% j4 Z l& e0 ~! v) b
这里的freq参数与DateOffset对象紧密相关,将在第四节介绍其具体的用法。
% e' V; _. ^& S! P8 O. f+ m# Y/ k. }& L
X+ a' O# f8 u【练一练】) i" ?) U9 s- j, K9 a3 ~( ^
Timestamp上定义了一个value属性,其返回的整数值代表了从1970年1月1日零点到给定时间戳相差的纳秒数,请利用这个属性构造一个随机生成给定日期区间内日期序列的函数。 S, U7 Q6 o' m2 e
2 w* B g S( }) E v* ~ls=['2020-01-01','2020-02-20']5 r9 r6 J- a' L# B( ?& L2 I3 k
def dates(ls,n):
6 ]" S/ r9 f. ~" J) D min=pd.Timestamp(ls[0]).value/10**9
. x& l+ A w6 B) F; ^ max=pd.Timestamp(ls[1]).value/10**97 ~1 i) H* g9 C
times=np.random.randint(min,max+1,n)2 J l0 C& {3 B& B. a! P& |
return pd.to_datetime(times,unit='s')
, u1 v/ g7 f0 i- D1 Ndates(ls,10)
1 i) ]$ @9 J/ P K, Q8 s
) y4 Y5 R- @6 W2 P2 p xDatetimeIndex(['2020-02-16 09:25:30', '2020-01-29 07:00:04',
1 c) p' h* I' Y4 z2 S" r% } '2020-01-21 12:26:02', '2020-02-08 20:34:08',
. q2 r) I4 h% f '2020-02-15 00:18:33', '2020-02-11 02:18:07',
5 |* Y$ D+ E% y) I0 |8 Y '2020-01-12 21:48:59', '2020-01-12 00:39:24',# I( n! m/ y/ k3 P! V
'2020-02-14 20:55:20', '2020-01-26 15:44:13'],' |1 S0 d3 N) s
dtype='datetime64[ns]', freq=None); A' j) {& p' B ~" ]2 s
1
2 h8 m7 |2 v, k" p2
: h: H" C+ s+ t& Q) K. U3! M5 N U" Q% y& Y% J6 S' ^1 w1 G+ _
4
% L* \/ D5 Q8 x: Y; W53 b) A% X6 L* S$ j h* S M# K
6, o2 z5 q0 {! u7 W3 s
7
; \" @3 u2 ~7 ~: U& B$ W1 ]8' k5 o& a. j& @8 }+ k$ G
9& b6 t% ~- m% T
10" b- Q) _+ w* }/ J) w
119 W7 e2 b' }3 \! k- z5 }
12
J) Y- O! g4 U+ Q' s8 S6 S6 V13
0 k3 f4 i0 U3 R% Q+ t: S14
- T! r1 c& Z. L# q+ P' E. Oasfreq:改变序列采样频率的方法,能够根据给定的freq对序列进行类似于reindex的操作:
" J; C$ w6 X& @- b8 zs = pd.Series(np.random.rand(5),
- |1 }# S1 A2 k$ J. b& m index=pd.to_datetime([( U1 |4 n6 X" w8 f4 C
'2020-1-%d'%i for i in range(1,10,2)]))
9 @' `) K5 x: n% A& f6 B5 U+ N, @* K
: J3 }0 G: j5 i# ~; R6 Q3 Q
s.head()
2 A% X& A L9 `, J8 ~1 mOut[29]: - _ n* a- x9 m9 S/ E
2020-01-01 0.836578
8 E" u7 A. a- {( }9 S- ?8 t7 y3 y M- j2020-01-03 0.6784195 b+ W2 f* C- N; E" U
2020-01-05 0.711897
4 A% a. O7 P( d5 Z, ?2020-01-07 0.487429$ \0 c' b9 s* b
2020-01-09 0.604705
6 X7 q9 |; T, {7 i4 @* K* ldtype: float64
$ P+ O" V; k- f( l9 b! L; Z6 Z; Q
s.asfreq('D').head(); c( x$ G3 ^3 J9 m' h- s
Out[30]:
* V4 j( p, p4 b$ N: b2020-01-01 0.8365781 n8 R" l5 t4 L
2020-01-02 NaN
# q% G. t, l2 K% m& q% W2020-01-03 0.6784197 l! P* X/ e% F& p/ ]2 p9 I
2020-01-04 NaN% r3 W5 U: F) F9 D; L
2020-01-05 0.711897
; a; d( Z( _- z, F1 a" sFreq: D, dtype: float64 q8 t) ~. Y, C
; G3 P$ B/ U; L5 L$ ks.asfreq('12H').head()
/ u$ C# S: E; _9 s! d" hOut[31]: $ f9 W4 ^4 m7 C' z( Z
2020-01-01 00:00:00 0.836578; B5 m0 l# l2 n7 @( F; B
2020-01-01 12:00:00 NaN
! M4 d, U1 m" L# p" v2020-01-02 00:00:00 NaN l6 W# X8 M; P& Q
2020-01-02 12:00:00 NaN7 r6 p$ d. C3 B/ l
2020-01-03 00:00:00 0.678419
+ x3 [0 T1 B; Z7 n' e, e0 MFreq: 12H, dtype: float64& n* R# i1 L& a1 A5 R4 i4 d
& q- n- h5 @+ n: o9 M0 v4 B7 L
1( J6 m( I) b, m) n7 c& }
2
# X' z1 m- P. p5 p& C5 b, p3% {. L w8 ~. Y4 l, p
4$ ^( Y& [( j7 ?: D, ~# @1 X( M
5
: S: M) {6 h2 t6& k4 X% z1 y r3 S- `+ G
7) M* u2 N: ?8 `9 x7 W0 ?
8
6 m U4 _" y1 V' }. e* ~3 r9
0 K' Q( O- R# Y; s3 i10
5 {4 A& J0 T6 {6 Y* [11
$ r1 p+ T, G+ y/ C/ ]- [12
* A9 F* k) V/ Y. l/ N+ p13
& Z. e) k& q& f) p2 E" _14
- V1 y' h! B L) V+ a15
1 [, g3 v: f$ B, p3 V169 [% [& q- [8 {! K I$ [ {
17
4 [! J0 a1 \" y" ~18/ N2 e6 d* d: v8 y7 s
19
- I& C6 u" }( i$ y: C8 a20
( K* z* W9 V' W21
9 d% ?* u2 \. `" Y% r# o22
1 O% Y' D: i5 l$ h23& V. r# n P) x0 k' N. L
24
6 | z H6 u. n: x255 P: ~- h- H8 [
26
& t4 J% T w0 U6 T+ M27
6 ?" N, z* \3 a* ?& O8 a284 g) K4 D# }. q+ p+ U3 H
29- A- e) A: y& j/ H
306 ^, A8 N$ t+ p# T2 Y
31/ d3 C! q, I. ~
【NOTE】datetime64[ns] 序列的极值与均值1 F: S: d2 O! ?! t
前面提到了datetime64[ns]本质上可以理解为一个整数,即从1970年1月1日零点到给定时间戳相差的纳秒数。所以对于一个datetime64[ns]序列,可以使用max, min, mean,来取得最大时间戳、最小时间戳和“平均”时间戳。9 P @* ^2 V O' k
& q2 x! a9 |* v, s; ?: M C
10.2.3 dt对象2 N( v% A5 W; _8 W- G" ^
如同category, string的序列上定义了cat, str来完成分类数据和文本数据的操作,在时序类型的序列上定义了dt对象来完成许多时间序列的相关操作。这里对于datetime64[ns]类型而言,可以大致分为三类操作:取出时间相关的属性、判断时间戳是否满足条件、取整操作。
, ]8 n T/ R" A/ r8 a% }' d$ v# J8 S0 S3 p% r( t
第一类操作的常用属性包括:date, time, year, month, day, hour, minute, second, microsecond, nanosecond, dayofweek, dayofyear, weekofyear, daysinmonth, quarter,其中daysinmonth, quarter分别表示该月一共有几天和季度。# }1 J# ~# t2 e8 d' s
s = pd.Series(pd.date_range('2020-1-1','2020-1-3', freq='D'))
! b& N% G# Z& Z' r9 u
( ]8 @( N- `& W8 {- E$ ^$ S; ~7 Ss.dt.date
& r u: _7 _. R( |Out[33]: - A6 q# o7 `9 p' ? B5 v8 o4 b
0 2020-01-01
0 u4 C9 T. W/ x) q& L" R2 |1 2020-01-02
! k- f% Y0 n: T2 d M2 2020-01-03: ~; T$ f4 l. Y c2 u9 `4 w# D
dtype: object; ?4 I/ ^, n- X; L4 }
# F; H& o+ [6 h0 }8 ?
s.dt.time1 A) I* I+ T( k7 C5 p1 V4 K
Out[34]: ^3 }8 S: M' D
0 00:00:009 c& ]. a9 M' G; D" z9 b
1 00:00:008 Q$ C2 ~" H! Z9 V- t
2 00:00:006 |7 |* ~. u% a' g1 Z2 h
dtype: object, _9 }1 ]6 ]8 [- K1 w
/ @0 S/ g% F9 y! }0 F3 _9 m5 }s.dt.day5 `. z- q" I* B' e# U( ~, m
Out[35]:
0 a$ S1 q- w- l3 d0 1/ E. T: N- q; D0 _. [% x
1 2
0 w( y4 y( p0 ?2 H9 n$ ]% N2 3( g! ]) O% h3 n3 h4 J- V
dtype: int64
) W( y5 ]: _. k6 t# Y4 I
5 h n! O1 b0 F2 Qs.dt.daysinmonth
; O( t b& `. V2 r- k4 [Out[36]: - A( C* \, o1 ^: O
0 31
2 a( G+ {4 {+ l+ U& o0 @1 31: M2 f6 ?# F0 A& K- v
2 31
; [4 }- g" L1 v1 w8 Q- mdtype: int64
; ]1 N) d$ h% ^1 i4 f
2 d: u5 n+ i7 _6 K; n1
l! |! `$ m' X9 S: G, w3 d2
3 M2 ^/ U" ^- l3
- y! z" g. D- g4 u% e/ c9 r4) v0 B, h* ~! y8 C
51 S+ r9 A# V2 C* \& |' A3 n
6
- C( |' U9 T/ P4 p* J- x$ H70 d! {% _# A0 ?! l8 J$ a
8
. P `2 z" O1 h93 O* D7 E {1 V' ~! p
10
' ?, C0 A3 ?; d116 ~% H, L/ p" P- V. R' m( Y3 U! h
12( i" n- x( a5 Y1 \3 e
13
5 U: P+ r9 _" |! n14
( B! l" K, S2 P* i" y5 M. R15
% G$ Q4 B! P: L E16
1 z( g. N# A* O17% @( w# ]$ s% |) l& ^7 J1 N% P0 e
18
$ v# x: L3 T, X" g% k7 _1 L* Q19
/ g+ E- }/ Y6 m8 Z, ~, X20
8 {" g V* @' F7 D8 \2 X. q9 z( l21
6 d8 E$ o: B# r2 D22% |: W% _9 k$ g
230 m) \$ F7 ], I- q Z& C+ V# m1 B4 n
24, Z9 g, e* K/ m3 u* w1 R p/ f
25* T a9 y% g" ?% @2 Z% \
26
' {7 U3 _) X) ^- {$ n; A+ e27
' K" F# }+ }5 l7 h28
' Y3 o" w. M6 G0 ~: N y29
: m( x& n9 h5 Y 在这些属性中,经常使用的是dayofweek,它返回了周中的星期情况,周一为0、周二为1,以此类推。此外,还可以通过month_name, day_name返回英文的月名和星期名,注意它们是方法而不是属性:; ?) I$ X' x7 E% a4 w9 v. E
0 V/ O. q% }- P9 m1 J/ ?
s.dt.dayofweek
& c* v, D" k6 L/ [" {. H$ LOut[37]:
: A- M+ N7 W* P: L0 21 [6 h( A) B: f4 P6 L0 O
1 3 g4 G, x: w8 }# h0 C. F! Q
2 40 J% H0 d: f% p" Q5 \+ m
dtype: int64" ]# q- w; F- j- v- m* l
; b: O( R' X% o- i2 h1 ?" Fs.dt.month_name()
. I: U5 p5 L' wOut[38]: ' \: R/ r e# F3 \( B- A x# B( y
0 January' }* q: }; ~, D( [& B
1 January1 N/ O' A- K! C
2 January
# i6 z& C3 ]2 T; N6 Adtype: object! ~( N: s! k- B2 K
8 S% W; h- A3 C6 o
s.dt.day_name()
, h+ c4 [: j$ kOut[39]:
% J5 @: f7 d: B3 P0 Wednesday
2 X4 ~: O( c; X$ \1 q8 |% m1 Thursday- e% M$ l( z: Y7 E9 p0 T* P
2 Friday( T* P- @- t: I9 T: J$ e
dtype: object
; A3 o+ O; S: C# Y9 v
: G/ j0 j m7 ^; t+ f1% {0 P9 p4 X$ X4 a% l9 Q
2
& ^- J2 l. B1 U4 D3: D/ p: i9 p2 H. P/ ^# ^% Q
45 u) d" X3 [2 }. ]- T* [( c
5
?+ c4 _9 L1 z' U- n/ e: U) @$ _6& ^* F# n7 I: d1 X
76 d7 n/ Q5 M% K3 B) Y" W) C) v
8
7 _- \4 G5 u+ B" d2 r; Z/ @9
! a4 j; }% B( v# r2 f1 _- m10
6 I8 |. c, ~8 I* b" x11/ a0 I& k# U/ S1 W
129 E6 ]( u! n% f v5 Z1 u
13
/ u4 H8 t4 h5 w& R/ Y14
2 ^" Q8 w" R+ ^/ t3 f15
1 d5 U& @; o# P4 H8 `16( L7 o2 }: n, x' \
17
: w) }' b9 u7 C* S+ G18! Q/ g8 m9 }6 b8 Y3 c' N) @
19
; o* p( T' L$ ~# ~' C20
2 ]2 i! P; Y, P6 K C第二类判断操作主要用于测试是否为月/季/年的第一天或者最后一天:
6 g- x# |1 X# A( y I: r1 os.dt.is_year_start # 还可选 is_quarter/month_start0 t& J) h4 t' p0 i
Out[40]:
1 ~- V4 S- L% C2 w0 True
7 [+ x2 }" j* _1 z1 False1 \9 k' f3 z: p+ t1 ]5 I+ l7 d3 ^. O0 K
2 False* ~# ~2 a8 j' t( ?
dtype: bool( \' `4 T$ w: A! B+ B% u
' M% D, e/ T3 i# s& S$ T
s.dt.is_year_end # 还可选 is_quarter/month_end8 `$ g2 i1 b8 Y/ ?/ E' \
Out[41]: ; d X; G" h1 [ |( w/ L
0 False2 G$ b1 g$ Z; b! V6 m
1 False) C# q: {& y% H# _; r- k
2 False
5 n: R1 t' y1 _4 ^5 F# s6 q! `1 ?9 Mdtype: bool
; o: J+ D& _# _3 f! d9 J1
9 k; I5 A! U: M+ \2
, I+ ^. J! b1 C2 t0 w! x/ {% H3
$ e* ?# c/ V; d, N% b4
$ I* s4 s& \' l a; g1 b5+ s" n5 k D" V0 k" Z1 X# k. B
6) z1 P0 b. W1 ~0 z! F- D, j! ^
7
! g+ A3 `+ @2 o% A& I: m- _5 y7 G; N8
' c0 O' u" P2 a; e- j) [9# {8 @' \* ~, R0 j2 ~1 }" _
105 l5 P$ x8 w6 v' ^' v& @! I) M# p
11
) X- p! F, `3 w& v5 G* n: c12
" I; N# r0 Z5 q( s3 S& ?2 ~13% @' L" i) S6 v. s( ^0 O! Y* L h
第三类的取整操作包含round, ceil, floor,它们的公共参数为freq,常用的包括H, min, S(小时、分钟、秒),所有可选的freq可参考此处。
- l. j. n: w5 _) r7 Hs = pd.Series(pd.date_range('2020-1-1 20:35:00',0 W' m7 a1 q$ @5 C( U0 t* k( N- z
'2020-1-1 22:35:00',
5 b* z8 D5 I' ]5 M* Z: w& D$ l/ F freq='45min'))
# [+ c, }8 X) p- q( i6 i0 _7 H$ g4 C& ^) v/ X& P
/ Y }& Q7 M! h/ K, j* h7 j0 [8 Y; R
s. K0 F, o! S2 {/ r, w" f% n
Out[43]:
5 }, B" L5 X2 m6 C$ Y$ A% E0 2020-01-01 20:35:00
! Q, K! t. F& k+ I1 2020-01-01 21:20:00; B# L8 l ^5 q; _
2 2020-01-01 22:05:00
$ y/ t' S, l# ^dtype: datetime64[ns]
6 r! o5 }( e9 U4 X7 J0 l( ^) i
& S2 z+ v' r) E2 Ms.dt.round('1H')% T. G0 Q: t$ C% R* f h6 r) f% h: m
Out[44]: & {* l6 ?: M$ J" A" G4 U
0 2020-01-01 21:00:00
2 R1 A# Z+ p/ U2 L: {8 B1 2020-01-01 21:00:00+ V3 O, s% v5 e; G- {) M* E
2 2020-01-01 22:00:00
1 K$ R. U, S6 u! h, {dtype: datetime64[ns]! {/ l/ i& r$ Y1 ^. o6 X
2 w1 i; ^* b* h R) \4 [8 j' Ls.dt.ceil('1H'); b3 P5 d) ]: Z' |+ [+ {2 z
Out[45]: 9 f4 v. _6 b1 H2 o0 e
0 2020-01-01 21:00:00& _; m% c* o5 A% j# P7 `; B* l
1 2020-01-01 22:00:00
# K% g8 u+ u( |2 2020-01-01 23:00:00
! |: e) b; k3 m9 C$ Ldtype: datetime64[ns]
- w, k$ F& ^4 w4 N
- ~5 s5 x; z9 ?( ]2 p; M. [s.dt.floor('1H')
+ S2 ]+ W" R8 k; W% XOut[46]: 9 O4 c; N5 {( `6 m
0 2020-01-01 20:00:00
& J1 ^' o/ ~# H9 {8 a- C. ^1 2020-01-01 21:00:00
; n( p& u' n& F/ J2 2020-01-01 22:00:00% ~0 v3 n; r& l8 m0 N/ j
dtype: datetime64[ns]
# b9 ?+ T1 T$ ?8 _4 G7 W0 {8 m. k) s% U
1+ ?) @4 \% s Q
2( C" |: B& T, }, c* g/ P) k" C
3
\) T0 H8 p5 q4 v4 D: H% Y4* w9 \4 W2 w/ X' w9 O! S" ~
5
) F9 a: Y6 T9 f0 S. c+ D! n; d4 R6/ X; h, i. R$ ?3 \7 p2 L9 V
7
3 A& x; N- Q* j2 p' ~( {& e5 X88 Z, h7 u1 |0 ]1 q' O
9$ i& O$ v* g# [+ s
10
7 x5 H! F# r$ \" u11! ^( |5 z* X2 A9 ^
12% ?* w1 V2 e2 R
13& k% f/ z8 `8 g, H5 S- A+ G
14- V( t2 R, H, J+ K( q
15/ M. X4 m$ e# j# e; L) K
16
! B! F2 u7 Q9 w. i& U* r% g) v# g8 J17
# y- {. f9 a, z& h; [% p18! \) N* g' M7 j( W' [ \" W+ i. F
19) v) l' l' S: U1 }/ a
20
0 E6 k8 y: \, [9 b2 g) z" |4 D2 e216 g7 j& g; s Q* J9 M
226 y9 C: n/ s, q( E1 F2 E
23
; s6 M( a# V& s' s, V24
) e/ ~& G$ r, l/ P* Z/ @% M) a25
6 ?. ?0 Y* H8 h/ M& E: f264 m2 ^/ u$ c( X. v8 V
273 [: p& r& y" l3 G" K8 B* |2 j
28
0 M z0 i7 y' Z* i- E( R6 t, {1 H29! k2 [2 u1 |8 T
301 E3 a9 i* |: `* a3 I
311 i0 M, Y4 Q8 }9 X% w3 f
32
6 r4 J3 o7 H! n0 q6 }- R10.2.4 时间戳的切片与索引
: c& ]7 Y, m$ {6 D7 k) r) Z 一般而言,时间戳序列作为索引使用。如果想要选出某个子时间戳序列,有两种方法:
h/ Q: H' |2 {" G. {- x4 R
- f$ x K; ?' U0 { j利用dt对象和布尔条件联合使用
: f! C" Z# b! l- B9 Y: Y( V利用切片,后者常用于连续时间戳。
; l3 N/ O1 l' X' [ i7 a7 z0 Es = pd.Series(np.random.randint(2,size=366), index=pd.date_range('2020-01-01','2020-12-31'))4 F& @0 i7 M- E2 I+ D0 T* d4 f
idx = pd.Series(s.index).dt: V" v$ i* I6 g; X" Q& O' V
s.head()0 [0 w! @) D) `; T2 P( m% c' z
2 Z5 S. h$ E7 r6 t- Y5 n
2020-01-01 0' H: L8 B+ }7 U, h( r- V3 |, K$ S2 ?
2020-01-02 18 l d, Y0 R& g+ K1 I$ i
2020-01-03 12 l f' R6 W) Q% C7 L
2020-01-04 0
. c& f. O# ^* e9 A2020-01-05 0
9 z' X @9 C4 v% fFreq: D, dtype: int32
0 M! ?) H4 @% a1- A% k7 ]$ o8 B( Q& s, P
2
2 V" x9 R! n; T! V3
! s4 ]% a5 S6 ~$ T' r8 l4
6 F6 `4 V u; t50 m0 r+ [6 W: I! O; m9 y
6
7 m y+ s3 o( n2 o6 m4 C) P72 v% }: z7 F5 ?3 k7 q6 }& s
8
6 Y7 e; u" m- i6 e7 m; z9/ ?: |! x0 i( n
10
* X+ U* T# Y2 B2 |Example1:每月的第一天或者最后一天
# ]2 C V" I8 ? o' \3 b! Y; F6 v
9 q# [" K* c# Y0 w% O! h8 _s[(idx.is_month_start|idx.is_month_end).values].head() # 必须要写.values2 m' n6 P6 q( |" V
Out[50]:
) v) `$ i" R6 y$ q2 S2020-01-01 1/ J+ v5 w( P$ ?! B2 p& b
2020-01-31 0
1 S2 |( [/ `% a9 i9 p2020-02-01 1
8 E+ P% J$ f! j9 E- H$ E7 ?- O2020-02-29 16 a% ^9 Y1 F3 J c Z/ {
2020-03-01 0
& T+ P. C6 S- `+ @' I7 d0 ?1 Adtype: int32
$ W) x! U# i+ d2 Q0 ~1- S V, s4 z$ r! h/ M
2
- J, j! }% [( a! A# I9 c37 w) w$ r) P1 K! ]0 ~
4
1 P" P$ E$ w- ]% L0 d* I2 y$ a53 D D4 I4 |3 S3 x0 ]
62 D9 b" o* U1 {2 ]% v9 ^
7& {9 X- d& r! \. E4 V
8( r3 m* N# D, E+ z
Example2:双休日
/ D& E. _& u; A" }7 R8 n& [% z( I! M: J# T2 ]/ b4 B
s[idx.dayofweek.isin([5,6]).values].head()
/ _) }* ^+ O+ I+ R. _Out[51]: ; T& w) ~% f9 _ {8 A
2020-01-04 13 y/ _* [! u# H# _( T2 U9 P9 d
2020-01-05 0
/ G, @4 a# U/ p! d6 B& @2020-01-11 02 C* t8 G# v7 U: Z6 c
2020-01-12 1
! F' c. [# x/ ]$ `6 \( l2020-01-18 1
: k( w4 D9 G9 x- i: _. `4 e/ `dtype: int32. \: w: ]$ _& t( |, a
1
2 Q" v! ^2 i+ h! @& V2
# v. ]# O4 p! j1 c; l) r3, V4 f6 J8 m" ~4 f' @2 j$ {! X# p
4" A( W. I K0 q, X5 p
5
' f7 s2 z3 g" K6( G( X2 T4 f5 M7 }
7
2 l. J6 Y o( C1 ?2 O! U2 j8+ L* `# r# H. ?( \6 m
Example3:取出单日值
0 F# C% O5 J2 E7 N. [) `& n$ m" x) P
s['2020-01-01']
3 C/ n9 D! V$ W: _' Q( C! bOut[52]: 1! j* |; S( i e. H9 y3 Y+ U/ ^
0 i+ ?: E: b0 X9 D) F# P5 v \2 v
s['20200101'] # 自动转换标准格式
+ L: }: \# [1 Y, z4 e" JOut[53]: 15 R. @9 L5 E* C6 W( Q" K+ h; Y& G
13 |& e; b8 |8 Z8 Y- p4 ?
22 ]/ Z2 i( I: c" Y1 e+ i! H- }5 A# a
3
* n2 ]8 o$ A( A& D4
/ p; j, p7 q* O- e9 }5& `% a5 N" c5 }$ j3 Z/ x' G: g# |
Example4:取出七月
$ D5 n, P& z: c1 [* ] x; ?+ g/ a" y7 r2 n' Q
s['2020-07'].head()
' i* [3 ^, O. ?Out[54]: ) y3 X. I0 K& @, ] r. T- _
2020-07-01 0/ `, ]7 k k4 n- [0 |8 v% s! }
2020-07-02 1
6 n# _' y) R/ I' M4 X2 J5 j9 U2020-07-03 00 G! c8 R; L8 }1 p& v% L
2020-07-04 0+ Q0 i! Y; M0 F1 }
2020-07-05 07 A1 f3 X+ ^6 ^% |8 o- `" B
Freq: D, dtype: int32
. I/ N, u3 B) k2 q4 L+ I, k/ ^1 H% O" h4 N! p6 ~, g4 l
2: w2 {( P; O% B
32 D/ a9 ^% O) `4 P% ~
4
% {4 @* t0 i; E0 t5
* Z" O) m5 L! Y, ~' k9 k9 m6
, w9 ?; _4 J, f, {8 V! C- v; u. O7, m$ c% G- `0 g% E- {
8! J* f- i+ |: H2 x' m# F2 x
Example5:取出5月初至7月15日; ~* T& r3 G# o$ c# I9 N3 J% {. [
* S0 x4 \6 _; B; P" P
s['2020-05':'2020-7-15'].head()
- A1 l0 t0 ^- X. S' VOut[55]:
5 {, S, s7 i5 w! T% d( P2020-05-01 0
' P2 n j/ X- S2020-05-02 1
+ q/ F3 @: f" {+ ]$ q7 q2020-05-03 0
. T, a" W; G: l2 U1 u9 z2020-05-04 1
: R- L- _- J$ q; t/ v& p& C8 X I2020-05-05 1# w! `* ] }5 i& n; e
Freq: D, dtype: int32
0 F' k' n2 x0 Z/ T' U& w' D" u, v* c2 `" | h% Z+ Y$ {# }
s['2020-05':'2020-7-15'].tail()
( q( ~( Y: M0 Y& KOut[56]:
& b, C) z3 f' ?0 |0 Y1 v2020-07-11 00 c0 s) X. l" v) M3 U1 Q* n+ x' N) S
2020-07-12 07 c }* ]! B! t, C: o# s0 i# r
2020-07-13 1 Z9 M# `- b5 K) [# |
2020-07-14 0* I2 B. w" l; ^/ ^1 x2 }. q2 ^
2020-07-15 1
# y! U Z/ G* L0 \ JFreq: D, dtype: int32
% K) q1 }0 d* t2 b) y5 W
) q6 b7 F o f* I% \1
$ _2 y! A0 {6 q* b/ |9 E2' R. O4 R& j* k" a$ q
3. @' h1 v( s: v+ x, J
48 y. w" K! l! W7 q# h
5
# P g) D0 K L1 h& W9 b# Z/ d62 i. m6 Y" K" Q* v0 O* y; x( i: T, X
7
$ f1 u! B- H2 g1 Z. |80 } O0 m7 A- W" y
95 P# @- m4 ]/ @
101 B9 h$ f# x4 k
11$ p3 R1 M6 a* x" f( `/ e
124 c7 A2 ]" |1 B7 U& O6 {4 q9 l
13
# C! A. h& d+ o7 q. [/ R14
, R5 v$ D$ C- V y% Y5 V# K+ @15- ]0 x: K4 ]7 }, H- Z
16
7 z$ R1 r9 l( W% ?6 T/ M17
2 z' g5 X/ Z0 c8 K. H$ U10.3 时间差
& \( P! F3 t r& h- W4 q9 B10.3.1 Timedelta的生成6 A+ a! l& K" k8 r: E, \
pandas.Timedelta(value=<object object>, unit=None, **kwargs)& I. b# V) o1 m0 A" B) I
unit:字符串格式,默认 ‘ns’。如果输入是整数,则表示输入的单位。
: ^( l0 u- b. o/ U1 X) | 可能的值有:- H! h) D% j7 f$ j& t3 x) c y4 o
& m( }. O9 G V‘W’, ‘D’, ‘T’, ‘S’, ‘L’, ‘U’, or ‘N’
1 X2 o+ d2 \* m6 d9 E‘days’ or ‘day’
# f- N% ^+ I& l) ]% U) C‘hours’, ‘hour’, ‘hr’, or ‘h’
6 H+ h' \" N! j7 {9 j3 p‘minutes’, ‘minute’, ‘min’, or ‘m’
) r& w h m& J0 b& y6 V‘seconds’, ‘second’, or ‘sec’
5 D e# n& f. z. P/ e) B毫秒‘milliseconds’, ‘millisecond’, ‘millis’, or ‘milli’
1 O9 N: f. } ?微秒‘microseconds’, ‘microsecond’, ‘micros’, or ‘micro’
4 t$ p& P1 `6 c( @, @! H纳秒 ‘nanoseconds’, ‘nanosecond’, ‘nanos’, ‘nano’, or ‘ns’.
, H9 X; P4 I5 V4 i2 |9 ?时间差可以理解为两个时间戳的差,可以通过pd.Timedelta来构造:+ F- i8 [' q+ ]3 v
pd.Timestamp('20200102 08:00:00')-pd.Timestamp('20200101 07:35:00')1 b( M. z. M+ J# U) m
Out[57]: Timedelta('1 days 00:25:00')
8 f3 K) C, K' M i) z
8 k; @/ J" d- \( |3 u: }' Jpd.Timedelta(days=1, minutes=25) # 需要注意加s$ ~+ L( s( r+ J& J# i( f
Out[58]: Timedelta('1 days 00:25:00')
9 E& Y: d' ?& c( B
& h& T3 T" T, @$ M1 kpd.Timedelta('1 days 25 minutes') # 字符串生成
) R9 j$ {9 _- p* f9 S& VOut[59]: Timedelta('1 days 00:25:00')
`9 L' n1 x5 t' U( w# x/ f7 T% }# o/ t8 }2 C
pd.Timedelta(1, "d") r8 B& @ V) Y2 y$ R, o3 {- s
Out[58]: Timedelta('1 days 00:00:00')
8 {3 l/ Y+ W/ k1
. k5 q1 i' ^/ [* @; x26 r W) m+ W, a! w( E f! }5 A
3$ M; [; e7 L' U6 J
40 _, s. M6 [) ~* l% h: V
5
1 X( z: E2 y: t+ @. u6
, n. Z5 Z. h) a0 S: b7
' L4 s, N. B5 [ J/ p83 \- t( d, j. q
97 B: t# t( B) d1 ]; B
10# k0 @; R, u; F" v$ \4 h; f
11
2 y: N( I. [8 y8 F- T1 `7 J( r生成时间差序列的主要方式是 pd.to_timedelta ,其类型为 timedelta64[ns] :
' k' Y* M. ?. h+ J/ Gs = pd.to_timedelta(df.Time_Record)2 G9 ?- L$ K8 x t' `
& b' @/ a8 W: w$ t% Y
s.head()
' X8 h L% M3 @9 ~9 fOut[61]: " ~) |1 w Z- t# N/ R$ Z
0 0 days 00:04:34
1 o. X7 l" l. o: `1 0 days 00:04:20
: v5 e# ^& M9 y3 q2 0 days 00:05:224 ?) J- v0 r$ o1 ~0 w
3 0 days 00:04:08
% s# v: M4 Z ~+ G) ?4 0 days 00:05:22
% f" s( U' O' R8 ~4 E& LName: Time_Record, dtype: timedelta64[ns]5 \8 |" u* y" t- F. U5 }4 d# T/ X
12 V+ l( L: \ _7 \% {
2' z/ _, ^: w) Y' K6 Y% `
3
# V* B2 I5 h+ r. h4/ E' w) l. U+ y3 Z$ Q- g/ @
5
' f P+ g e- \* W) c6
+ x R3 X, `# R/ ^0 C7
9 A( D, N# T: W" d8. W# F0 j7 E* I: @
9' c# Q' y5 F+ _# V7 H0 e) ]
10
. a n. B2 Y" e; T2 ^ N与date_range一样,时间差序列也可以用timedelta_range来生成,它们两者具有一致的参数:/ r2 F5 [9 P z: G8 _
pd.timedelta_range('0s', '1000s', freq='6min')3 H% R& P, C* j
Out[62]: TimedeltaIndex(['0 days 00:00:00', '0 days 00:06:00', '0 days 00:12:00'], dtype='timedelta64[ns]', freq='6T')& ]! R. S8 D0 K" `6 r, I
3 t/ t: e" c2 g% i& y+ e, |
pd.timedelta_range('0s', '1000s', periods=3)
6 E$ r! {( e2 T6 `, P/ GOut[63]: TimedeltaIndex(['0 days 00:00:00', '0 days 00:08:20', '0 days 00:16:40'], dtype='timedelta64[ns]', freq=None)
% Q. H$ w7 N4 ~9 \1
) U8 G) D/ J/ `' H/ O; H" r2( k6 u$ I8 u) ^2 a9 o/ V
3# o/ m1 H: s- A0 L3 P3 A5 r
4& N, @% X8 I5 h+ d5 a6 r
55 f" {6 Z. [' z* L( y) w
对于Timedelta序列,同样也定义了dt对象,上面主要定义了的属性包括days, seconds, mircroseconds(毫秒), nanoseconds(纳秒),它们分别返回了对应的时间差特征。需要注意的是,这里的seconds不是指单纯的秒,而是对天数取余后剩余的秒数:
* k$ P! I' j! @2 }* @8 `s.dt.seconds.head() @7 U+ b+ f8 A0 G5 Y' x% z$ v* B7 Z
Out[64]:
: j7 p2 r/ \3 n+ J9 Z) k# f9 T6 i0 274
# M$ q. z/ P B1 260+ @) Y! I0 c! @# `, G, w
2 322; _9 [, W( Q, s' D4 s8 ]9 _
3 248
( o H& P. m7 C) _4 322$ i% C8 P2 @2 u4 C1 w+ R1 W) A
Name: Time_Record, dtype: int64
5 U9 |, ]% i3 X8 q1
* t6 M, a$ J/ T9 u$ v6 I) r6 {0 j2
3 w" f. N- l* z& q3; J5 i6 x: |: ?5 x0 w
4
/ q$ ]$ s6 K1 H1 h" B51 U3 _+ d+ D2 h
6* p$ z |4 @. h, Q. |5 X# l" @6 A: _
7
3 R' `6 u( U) X, A8
2 q4 | N: t' Z- e0 R如果不想对天数取余而直接对应秒数,可以使用total_seconds
! ]8 _8 v" `# g" S* ^/ }
2 Q7 A! H. D1 rs.dt.total_seconds().head()
& q$ }: }, U6 J' POut[65]: 0 E5 H; ?2 w4 M9 F: {
0 274.0
P5 z8 V! @! `2 w. Z) H/ S1 260.0- Z4 D" O& ]8 I( B
2 322.0; j5 g2 x5 i$ S+ U( x* V$ u
3 248.0
, _, h2 Z! ^" G3 i4 322.0
2 F9 W( v4 |$ d: Q6 {1 d. a( v UName: Time_Record, dtype: float64
% a7 _2 S# ?7 l" z1
0 \1 E# n/ E/ ^3 F/ U7 l4 a: j9 ?2
9 y: F; r8 Z9 n) g3, ~6 A6 _2 q+ Q- i
45 R2 m+ N; [; _8 U
5" {* i4 L/ t0 l' Z6 p
63 C, S: C: [5 R( i! K- D
70 J+ S q: c& j, G1 N2 s( ?
8 ]; y5 U" ~( v- H( y3 [& W" A
与时间戳序列类似,取整函数也是可以在dt对象上使用的:5 I9 T: d2 \9 _+ ^% t
0 y* S( C/ R" l" t. ?& l9 Y
pd.to_timedelta(df.Time_Record).dt.round('min').head() w3 \8 r; R* G9 E( p
Out[66]:
9 w' u# ~" ~& Z+ c5 |( B: y0 0 days 00:05:00
* Z# Y7 G6 J C( G2 e1 0 days 00:04:00
& _" e* v q+ |% g% u7 [2 0 days 00:05:00; d7 K+ G# o( h# a& ^4 Z& v$ [
3 0 days 00:04:00
- S; }5 ?1 V3 j9 c: D4 0 days 00:05:00
* d! v }0 T v0 D3 h- TName: Time_Record, dtype: timedelta64[ns]0 V( f! l* s' K/ @+ l9 T2 S
1' c3 [; Z: n/ o3 c9 S
2
, |3 t! h4 N- m# ^- V3& j' D" l2 U3 J1 y3 S! Y9 i
4
' s0 E3 |6 p8 }! S; {+ F5
8 }9 \/ g. o2 r& E64 w0 ]$ r* d4 @" T9 D- O& Q4 V
7
! @: k* O3 `7 u; u8
3 K* a" h2 o% u2 J: U# Q( D4 U$ v10.2.2 Timedelta的运算
5 F3 d! z! O7 Q/ |: q; T( x8 @单个时间差的常用运算,有三类:与标量的乘法运算、与时间戳的加减法运算、与时间差的加减法与除法运算:
Y* u/ A( B, e# u! Std1 = pd.Timedelta(days=1)
$ y) A* h+ s* N$ L$ s: T; y; Dtd2 = pd.Timedelta(days=3)' N) L. y* Z! t% J% \
ts = pd.Timestamp('20200101')! [3 M6 t( e( @. F
, G) ~4 ]& `9 S6 q) ftd1 * 22 d: ]$ c& z$ X" j: X5 ?
Out[70]: Timedelta('2 days 00:00:00')4 |0 H5 |" k; }3 M
' ?1 ^: S2 g' S. E7 u; W
td2 - td1& h$ ?3 t) E' X R3 G5 m7 D/ F. V, P
Out[71]: Timedelta('2 days 00:00:00')# w/ W4 [! f& f9 e/ }2 H; @
2 }3 }# ^0 L+ n$ I# S. T
ts + td1
4 N& Y0 ~4 W' z$ q2 A ROut[72]: Timestamp('2020-01-02 00:00:00')! g0 v) a8 C3 U2 Q; Z
v5 S8 q7 i3 z its - td14 b8 V/ Y4 \8 y$ b$ r6 ~3 o
Out[73]: Timestamp('2019-12-31 00:00:00')
' ]9 m2 z3 L" U. W( c1, E! c6 e: @$ Q! ~# d1 `
2
% W) t' c Y' ]% l$ r8 g, ~3
9 z/ y I+ U" `6 s3 D! Z, O4" d7 x( B) U, l
5' k, \# s0 {9 {2 V. y* Y$ y( p
6
* \6 L* i# C7 y! H7
0 Y' b% d: p8 `- Y+ ^8
6 C. p- J. Z3 C% Z/ [90 j$ J. V+ [& i) _
10
; [' E* _! [: b) c' z3 H1 d11" ~* e @8 J9 \$ A8 z
12
4 J; H6 W" e, d8 }8 R" f13- [7 E- y. ^' B: Y7 I
14
6 \* U. L+ H" q! b15
# O$ ^4 R( Q8 G时间差的序列的运算,和上面方法相同:
/ z7 z: f* h- A; g* etd1 = pd.timedelta_range(start='1 days', periods=5)
( l1 A6 R& h( T ^+ Ytd2 = pd.timedelta_range(start='12 hours',
/ n. S- |/ r" @3 f freq='2H',
6 z8 V8 y4 c C% I8 {7 ~ periods=5): w8 n% K1 M/ w# K+ `
ts = pd.date_range('20200101', '20200105')
) C. Z& H: X- }td1,td2,ts) q. M G& r8 p% J% E" O9 T3 _( S0 S
! D# r9 b' E1 _TimedeltaIndex(['1 days', '2 days', '3 days', '4 days', '5 days'], dtype='timedelta64[ns]', freq='D')& s. ^0 C* x Q) n
TimedeltaIndex(['0 days 12:00:00', '0 days 14:00:00', '0 days 16:00:00',
1 B% j! E$ m1 z7 Q; D3 v, s '0 days 18:00:00', '0 days 20:00:00'], dtype='timedelta64[ns]', freq='2H')6 U8 V7 P- b2 ~ N3 X, {! U. e
DatetimeIndex(['2020-01-01', '2020-01-02', '2020-01-03', '2020-01-04',
P5 `: }* [* o! f '2020-01-05'],6 s3 Q! _$ E4 r9 Z! _
dtype='datetime64[ns]', freq='D')% t _+ t& j" L8 n$ u1 E# {
1
* p7 X/ e& p* g6 p7 y& ~- c) l20 _5 O! Y3 J- ^3 e! |/ O( M; V
32 c; I. t$ M) d9 d. I# b% O" |8 n5 w
4
$ D+ _5 n$ j% P5& ]; I" |1 D8 k: v
65 b+ Q, H6 ?7 }* U @( }! |' w
7
, A6 x$ l% `- m/ I7 }1 D* n8. @5 y {7 N$ F5 A( B# b/ t+ p
95 x: Z: ^0 g" U9 J
10
2 R* Z0 \6 x0 z3 A1 D' E11! |. w: n( ~. s6 S7 H7 ]
12
( R& i6 Y% e! {13: d& R* H# f; Y* K
td1 * 5
3 m& g& h" n- s/ @4 O# mOut[77]: TimedeltaIndex(['5 days', '10 days', '15 days', '20 days', '25 days'], dtype='timedelta64[ns]', freq='5D'); f: m v8 w6 R% I: K9 n0 S& |
6 l+ e8 e+ _# Z
td1 * pd.Series(list(range(5))) # 逐个相乘
: H* L" F# o& C& k+ b: eOut[78]: " ?$ f- P$ e; l/ }6 a
0 0 days
5 ?4 O" q! g3 ^- ?# o1 2 days4 I! o! `# F: m0 }1 B4 g
2 6 days
m) I: g% L% F1 T$ F3 12 days
@$ M x% c5 N% K2 D+ H7 ?4 20 days. p3 {5 E! s1 n, R7 d3 U/ g4 d& H4 l {
dtype: timedelta64[ns]
; J" t5 G- ?# Y$ P
( W) w# p$ Q, @7 n5 Y3 J& T# \2 qtd1 - td2
8 d% R9 f; K% ^ uOut[79]:
1 q1 d U& B$ x; eTimedeltaIndex(['0 days 12:00:00', '1 days 10:00:00', '2 days 08:00:00',
$ S7 z! f) x1 C; S/ `0 L! H+ s# j '3 days 06:00:00', '4 days 04:00:00'],
/ A4 k2 a( a( G* m7 [5 c' y+ Z dtype='timedelta64[ns]', freq=None)
2 O' ]: J% Y1 w% B- `( @1 w. ]2 S2 Q Y/ X: p" T+ N
td1 + pd.Timestamp('20200101'), H8 E. q: S. H
Out[80]:
: E8 `5 [! S( T$ l |# _/ dDatetimeIndex(['2020-01-02', '2020-01-03', '2020-01-04', '2020-01-05',
$ B, G3 ?" Y8 J9 z5 A0 d" D '2020-01-06'],dtype='datetime64[ns]', freq='D') I! H' U* ?' J
1 S b. a0 R/ O* c( s
td1 + ts # 逐个相加
' Y( H3 v* {3 K% X' O% w+ tOut[81]: , r: B/ ?! U/ S' ^& J1 H, \
DatetimeIndex(['2020-01-02', '2020-01-04', '2020-01-06', '2020-01-08',7 M! ^ `; Q+ D% e& n9 j! I q
'2020-01-10'],
- T( r6 T. @6 C4 k dtype='datetime64[ns]', freq=None)
0 t, K7 i6 ~* ~
" d* K7 y" |$ W! H6 D: ?- U1& O- k, I+ Y2 `* v' r; x
2, Q9 ]+ q3 p4 n5 F! x) s
3
U* g8 t6 K. {; t0 R% G/ S4 ^4
- {1 T* E. P9 H6 b5
+ @* ]: m$ w. ]" ?9 ^6 N8 |5 \2 V) ]# n9 |9 z! O& Y& g
7
* x5 e- f7 h3 W, N- P' m# A; T80 W& v/ _; R0 \1 ?2 z& O
9
W4 N2 L5 O" m3 n% `10
8 G# @ W# z# G$ G8 ~. S$ T8 L c" q0 c11) x0 Z# ?+ W; N
12
2 a" e9 V. G9 s! B; E' t8 |6 A2 o13/ e" I; x4 f3 u' }1 r3 }& [* H/ _
14) K; {+ A# ~; `& S+ ~! t
152 c1 d" M% k! b& G1 y [% e$ q$ u
16
9 J( H4 n. Z# _+ R4 [17! z# W! U" S2 r9 o% Q' l h: ^
18: l0 V) [* `- Q5 D& S. z
19
7 p$ X$ j1 E" w6 V7 g20
0 a: b' Q7 f9 N$ Q5 R* G! N4 j21
2 r/ O! f, U% ^" G, U6 E* n4 w8 R22
2 ?3 \8 k0 M; y; s1 e; U3 n23. G# j! b* v* m- Q+ U. r3 c
24* _; b* {( R* P" M8 L
25
" U2 h* E% `6 [: }26
$ J( G( W3 u) s# A( C4 J271 J1 I+ c/ x3 @/ O( p) o7 U2 ^& _
28% S4 w$ ]; j8 o" ?5 X' p/ ]) P
10.4 日期偏置
1 C) `8 d, a, [* u10.4.1 Offset对象
5 B% G6 A& \2 {& h7 q% R( t 日期偏置是一种和日历相关的特殊时间差,例如回到第一节中的两个问题:如何求2020年9月第一个周一的日期,以及如何求2020年9月7日后的第30个工作日是哪一天。6 b0 T* [+ X7 P, p$ h6 k
6 L3 V% j/ P$ A5 r. Y/ C4 u, B$ g
DateOffset 类有10个属性,假设s=pd.offsets.WeekOfMonth(week=0,weekday=0),则:
4 G5 ~% s) W8 C& r& a+ @' }5 P9 v0 k, _2 n# _# S
s.base:<WeekOfMonth: week=0, weekday=0>,返回 n=1 且所有其他属性一样的副本
: S, W k; q7 t8 J, b% fs.kwds:{‘week’: 0, ‘weekday’: 0}6 i5 G; {# J' Q4 R z( A+ z
s.wek/s.weekday:顾名思义
+ N# k- @* D0 s( ?1 |, [- _, m有14个方法,包括:
A$ |5 y! s- K, h d1 N# ~9 M: S" y" O# c6 |4 M3 k3 s
DateOffset.is_month_start、DateOffset.is_month_end、DateOffset.is_quarter_start、DateOffset.is_quarter_end、DateOffset.is_year_start、DateOffset.is_year_end等等。
W/ g4 P Y$ g j4 v" Z q5 Mpandas.tseries.offsets.WeekOfMonth(week,weekday):描述每月的日期,例如“每月第二周的星期二”。7 X0 t* I; I- I" m6 Y
! k5 l) I0 J, ?- z$ m2 j
有两个参数:( @+ p: q H% o0 u. J
week:整型,表示一个月的第几周。例如 0 是一个月的第 1 周,1 是第 2 周,以此类推。
8 t2 I7 v3 Y3 t* F' c3 I6 zweekday:整型,取值为[0,1,…6],表示周一到周日,默认取值为0(星期一)2 T V6 w% q7 P+ p
pandas.tseries.offsets.BusinessDay(n):相当于pd.offsets.BDay(n),DateOffset 子类,表示可能的 n 个工作日。3 M; t4 K: C6 O+ h! Z8 k/ Q! ?+ G) ~2 w
4 l2 }0 K6 z! `) Z( V4 H" ~/ J
pd.Timestamp('20200831') + pd.offsets.WeekOfMonth(week=0,weekday=0)
9 j+ K! q4 R* a& [7 V7 U% R+ dOut[82]: Timestamp('2020-09-07 00:00:00')
; _9 W. c5 C! }9 o+ w+ H
2 g: ?2 q% L9 g+ W. `, |( Rpd.Timestamp('20200907') + pd.offsets.BDay(30): P7 j( F0 Q9 ^4 b! i" z
Out[83]: Timestamp('2020-10-19 00:00:00')
, ]8 T+ e) b: s( z% m1% O, D! _7 Y8 Q- ?* O& q
2
: X8 b$ k! Y. K8 [& R6 }3 E3$ K' {3 W, k* h' j8 p
4
( [! X3 i) B# ]( r4 Y53 ]" {* ~ H. U
从上面的例子中可以看到,Offset对象在pd.offsets中被定义。当使用+时获取离其最近的下一个日期,当使用-时获取离其最近的上一个日期:
* Z% Z+ m: B3 C# j( w# h6 i+ J
0 b5 u/ m) U& i- W8 e8 y* xpd.Timestamp('20200831') - pd.offsets.WeekOfMonth(week=0,weekday=0)
3 o, Q3 }# a" BOut[84]: Timestamp('2020-08-03 00:00:00')
+ v4 a m% y4 H' E$ w: J$ u' |. c& C# N0 I/ \, \* X& X
pd.Timestamp('20200907') - pd.offsets.BDay(30) J, n3 s1 R/ e4 t" b, r3 l: Q k
Out[85]: Timestamp('2020-07-27 00:00:00')( ~5 T, p( I) Z' e0 x
5 W/ K" ] J5 d4 i2 b7 b' S+ h
pd.Timestamp('20200907') + pd.offsets.MonthEnd()
e! ]4 j/ ]. z+ f5 T5 ^4 ROut[86]: Timestamp('2020-09-30 00:00:00'); y5 U$ U7 E. ?/ k9 R7 N: ?8 z4 f
1: F, d9 A( c8 w. z" y7 Y
2
" N4 A' e$ X+ M. A: f7 a* o! A9 A3. m+ E7 v6 o7 }4 z: q' C) y* u- W
4
! @, x. n% d/ \3 W/ v( g: E9 k50 B; E5 i$ S: V4 t- {
6
) @2 l) @# T4 \7
6 ~9 {1 t7 Q* V8 R: ]4 d! H/ i85 d, H& j' q& z5 g0 V+ I
常用的日期偏置如下可以查阅这里的DateOffset 文档描述。在文档罗列的Offset中,需要介绍一个特殊的Offset对象CDay。CDay 或 CustomBusinessDay 类提供了一个参数化的 BusinessDay 类,可用于创建自定义的工作日日历,该日历说明当地假期和当地周末惯例。
! `1 X d# K8 f# I. T 其中的holidays, weekmask参数能够分别对自定义的日期和星期进行过滤,前者传入了需要过滤的日期列表,后者传入的是三个字母的星期缩写构成的星期字符串,其作用是只保留字符串中出现的星期:, ]4 T% G3 u3 t/ T9 q! ?3 G
2 I/ g- ~3 \: N- z4 b3 U2 `) _/ C' T
my_filter = pd.offsets.CDay(n=1,weekmask='Wed Fri',holidays=['20200109'])! U# X, @6 \/ T! e3 K7 f8 p
dr = pd.date_range('20200108', '20200111')2 ^5 u0 m" p! U& Y! t
3 W7 Z4 {7 v5 {+ [5 b, y& e4 ndr.to_series().dt.dayofweek8 w" U5 I! x9 s3 i0 W4 ^
Out[89]: , G$ g1 s/ b6 n/ H
2020-01-08 2
+ k! }" S W* E2020-01-09 38 h Y4 K+ M! } \. {, i {9 s
2020-01-10 4' ?( D# f5 x/ b7 E7 b7 V
2020-01-11 5
* b3 P7 ?) a. a; q' qFreq: D, dtype: int64
* d; I( j- Y" U, c! R
) _2 I7 U* q9 O; K8 @! d9 R[i + my_filter for i in dr]+ _% _* g1 Y, ]$ r; F$ _( X/ N' b
Out[90]:
' s. X, \4 x* L[Timestamp('2020-01-10 00:00:00'),
- e& C) Y9 Z7 N4 U) t Timestamp('2020-01-10 00:00:00'),
8 M6 a' l5 h1 E5 B Timestamp('2020-01-15 00:00:00'),5 U( [* A: T1 N h6 P: I* ~
Timestamp('2020-01-15 00:00:00')]) U2 y' o m: K: Y7 h3 o. z
* l9 |6 x6 E1 f13 a6 y. e! e5 r) F3 j, {6 J! v
2
; R% f5 }0 [; M D4 S" v; I3
6 K2 K$ `; I: {8 t4
: @* B+ y, P9 V" T! \' X7 x5
4 B) L3 ?8 S8 d68 ]9 N; r) S, L- p9 v: s) C
7
( q8 @: F! D9 I0 W8
/ u( Y4 B( e/ P! S9
" n* X' f3 s# Z) \; g7 h10
& C5 q1 B% U# ]5 T- y) Q% M/ c11
; \; Z9 [) A- y# j# W, T12 e% C; F- _5 q1 h. V
130 X1 }( M. W9 y3 s' _; t+ h2 s
14
) h: i2 J) F0 W+ ^15
: M g: D2 P. w' b* b. B3 p165 a' |& Q' Y# D( z
174 W# H6 S% W9 \( E( k$ s9 v& O
上面的例子中,n表示增加一天CDay,dr中的第一天为20200108,但由于下一天20200109被排除了,并且20200110是合法的周五,因此转为20200110,其他后面的日期处理类似。, r3 \" [ @ }7 v8 }4 l+ n
( n+ U9 \2 ~- S' C* o" ?【CAUTION】不要使用部分Offset# _( n0 [8 \0 u- H$ V! D
在当前版本下由于一些 bug ,不要使用 Day 级别以下的 Offset 对象,比如 Hour, Second 等,请使用对应的 Timedelta 对象来代替。
+ Z: X; `: C5 q! l5 }0 K
7 D* f$ Q9 A: D6 y5 @4 _ e10.4.2 偏置字符串
3 p$ w1 a" _( U 前面提到了关于date_range的freq取值可用Offset对象,同时在pandas中几乎每一个Offset对象绑定了日期偏置字符串(frequencies strings/offset aliases),可以指定Offset对应的字符串来替代使用。下面举一些常见的例子。, v! n' n7 } w$ b0 e4 I
# b9 J$ a! a6 ?0 k- K# r
Offset aliases:pd.date_range函数中的freq参数,为常见时间序列频率提供了许多字符串别名。 也称为偏移别名Offset aliases。偏移别名列表点此参看(大概27个)。# }. W% J) e# S9 `
; G! l/ H( t! y) R8 u; x% H
pd.date_range('20200101','20200331', freq='MS') # 月初: T1 S) ~& }3 a0 ?" n) Z; T) x8 o
Out[91]: DatetimeIndex(['2020-01-01', '2020-02-01', '2020-03-01'], dtype='datetime64[ns]', freq='MS')' L$ Z3 Y( A6 Z' k/ z& O9 |: C
! e3 K# T2 `" J1 ?
pd.date_range('20200101','20200331', freq='M') # 月末
8 M6 }( _9 ?) J( rOut[92]: DatetimeIndex(['2020-01-31', '2020-02-29', '2020-03-31'], dtype='datetime64[ns]', freq='M')
- }) c0 r6 F; K/ c
, s. S# V8 `9 l2 r# {pd.date_range('20200101','20200110', freq='B') # 工作日
3 ~8 `0 n8 A2 t$ u, P, S( T4 e0 bOut[93]:
; r! l/ L" B4 j. Y$ m' @. e' RDatetimeIndex(['2020-01-01', '2020-01-02', '2020-01-03', '2020-01-06',
2 `, L+ V/ ~' p) i' }. h/ P8 G '2020-01-07', '2020-01-08', '2020-01-09', '2020-01-10'],. |# Z& d" l( g/ h
dtype='datetime64[ns]', freq='B')
9 M8 V1 @0 x' D* r3 I( P
3 G/ f! A. h! Ppd.date_range('20200101','20200201', freq='W-MON') # 周一, |. }" [" i6 C( x c
Out[94]: DatetimeIndex(['2020-01-06', '2020-01-13', '2020-01-20', '2020-01-27'], dtype='datetime64[ns]', freq='W-MON')
+ ^6 \4 W. \3 l: ]) {1 w y0 F' j. h9 g( Z6 X3 r* P" [
pd.date_range('20200101','20200201',( @- L$ g6 ]0 ~( e1 r6 ?7 J
freq='WOM-1MON') # 每月第一个周一
' T' j" n4 V( ~; T" u4 b5 ~+ t% K4 M& ^% h; O0 w, G* Z
Out[95]: DatetimeIndex(['2020-01-06'], dtype='datetime64[ns]', freq='WOM-1MON')/ ~' c0 t* u9 W+ H7 [
* v1 N. A: Z- G5 a6 ]11 ~) s9 O& ]. c
2& c& q9 ^) V; ]3 Y; e! j( l. p
3
% Q% S0 q {! Z0 y7 u B" ?4
; z& S; x$ P" K2 `* U" @5
! Z" L8 q. b. B( \5 c3 l6
/ J- }# L3 \- {8 z. I, \7& f# a% |; F7 k. J9 ~# s# u
8# G/ J& L- f) f P" M
97 k- V9 `! Z' D2 k9 \
10- m3 \' }- k; {) j5 }! X
11
' w$ g' ]. `4 w+ o" u12' x5 l5 {% L9 a G
13% [3 D6 v# H2 ?( H8 g
14
; A% d0 U; y- o" I: w6 \) e3 L2 n154 q5 Y6 P; \7 s1 v5 `# E
16! W5 v7 x4 f# M5 M, s8 i
17
- T4 p! W/ H! N, k9 \, y0 ?18) S3 e- f- {" i" S3 ?" B6 U7 [' V% L
198 o* O4 {$ f4 C7 k
上面的这些字符串,等价于使用如下的 Offset 对象:& p; e* u) d6 q: X" T2 F5 C+ |
% L- M, [3 ]; O: W! @" N
pd.date_range('20200101','20200331',
6 u* w9 p0 J8 U! |5 a freq=pd.offsets.MonthBegin())/ j! X- n) F: L( _" K W
; X" E7 l9 O- q: }5 UOut[96]: DatetimeIndex(['2020-01-01', '2020-02-01', '2020-03-01'], dtype='datetime64[ns]', freq='MS')* f; g3 s; U* Q& s- V9 i; I& S
$ a, r6 A9 { x$ F- i' c7 j# Jpd.date_range('20200101','20200331',
n* Z V, k9 i" k4 e( T0 m6 e freq=pd.offsets.MonthEnd())1 T6 T1 Y" }$ o% W# ]- b
& y Y) a3 C! ~* U
Out[97]: DatetimeIndex(['2020-01-31', '2020-02-29', '2020-03-31'], dtype='datetime64[ns]', freq='M')* I) X, |- k0 v( ?; t
5 Z' F8 u6 K& ppd.date_range('20200101','20200110', freq=pd.offsets.BDay())
% Z+ |( \2 s7 S7 C% D, c; [Out[98]:
( \# t; s# e" f* M3 T, cDatetimeIndex(['2020-01-01', '2020-01-02', '2020-01-03', '2020-01-06',4 C' q$ F. J' r1 }& M, V
'2020-01-07', '2020-01-08', '2020-01-09', '2020-01-10'],. s, r- [2 D/ c2 K# B: Z, U
dtype='datetime64[ns]', freq='B')! U$ S0 B( m$ o6 S* F3 ~0 q
% Z5 I2 g$ I& Z9 g0 Z4 _pd.date_range('20200101','20200201',. U( m" `; o" ]: @: g! P; Y7 A
freq=pd.offsets.CDay(weekmask='Mon'))
; n5 [* [8 S a& U+ g( s3 H' Z8 V+ ^
Out[99]: DatetimeIndex(['2020-01-06', '2020-01-13', '2020-01-20', '2020-01-27'], dtype='datetime64[ns]', freq='C')
6 k9 u3 T: _8 i& z, c
d8 a9 x0 @& dpd.date_range('20200101','20200201',
2 F& K. D' ?" O0 w! }. }0 P freq=pd.offsets.WeekOfMonth(week=0,weekday=0))
: U1 U i& `3 j$ z) N5 a$ t$ o; o* `3 M1 ~5 S" d
Out[100]: DatetimeIndex(['2020-01-06'], dtype='datetime64[ns]', freq='WOM-1MON')( \( ]% a, \2 Y
0 X/ Y) u! Z- e13 p/ j' |* x8 k( e2 z1 g! s |
2$ A1 R( ^) |, W$ ^% ]
33 z g0 n2 s- Q$ W+ {
4" j& [& {) G' \5 U5 y2 G4 O
58 ], J- p7 q# ^( }
66 ^7 L/ k' G) V
7" R. f3 A g x+ J
8
' w" ]$ ^; N7 n5 S9
/ S9 ]; V0 n2 N/ ]# K) q10
6 P4 X; c" K3 E2 o( U0 z# W11
" C8 k- O& Z* |% @4 @9 _( J# ~12: @+ V. y$ G1 K
133 g) w5 M& j3 n/ e1 \( a& k
14
6 W9 S% D8 D1 Z) F$ N154 \& [7 R% n+ f& L6 ^( @
164 |1 J' J5 M. Y ]5 R
17$ \& @2 B1 M* A1 k
18' _; q! Y V6 y8 g- z: e$ M
19
1 k2 e" }# L+ A Q20
( Y4 c& i; G' y7 o0 ], z21
. w2 l9 |' j0 r8 p' k22$ t- }- t0 ` J, R$ X3 w- y& k+ X
232 ]3 q, S0 @" X* U
24
3 s+ i! q2 [& U k2 e- Y2 P \( @1 \& h25
$ c& D4 A# h' [; \8 b# p1 u3 e【CAUTION】关于时区问题的说明1 f' b* P/ N0 j3 J
各类时间对象的开发,除了使用python内置的datetime模块,pandas还利用了dateutil模块,很大一部分是为了处理时区问题。总所周知,我国是没有夏令时调整时间一说的,但有些国家会有这种做法,导致了相对而言一天里可能会有23/24/25个小时,也就是relativedelta,这使得Offset对象和Timedelta对象有了对同一问题处理产生不同结果的现象,其中的规则也较为复杂,官方文档的写法存在部分描述错误,并且难以对描述做出统一修正,因为牵涉到了Offset相关的很多组件。因此,本教程完全不考虑时区处理,如果对时区处理的时间偏置有兴趣了解讨论,可以联系我或者参见这里的讨论。. N& u( g, A. T9 N& i1 m
! d( i( Y7 _/ X+ K, Q& o10.5、时序中的滑窗与分组
) o C8 z. j9 K4 I8 y9 J1 Q0 W) i10.5.1 滑动窗口
8 S7 H6 d: z. v, e 所谓时序的滑窗函数,即把滑动窗口windows用freq关键词代替,下面给出一个具体的应用案例:在股票市场中有一个指标为BOLL指标,它由中轨线、上轨线、下轨线这三根线构成,具体的计算方法分别是N日均值线、N日均值加两倍N日标准差线、N日均值减两倍N日标准差线。利用rolling对象计算N=30的BOLL指标可以如下写出:) F1 t. ?) W1 o3 p5 J" Y
5 Q! E$ n1 x7 b! |# @
import matplotlib.pyplot as plt- J1 I9 a; _( D4 m5 A
idx = pd.date_range('20200101', '20201231', freq='B')% x& A3 v# S1 T# S( h
np.random.seed(2020)
5 H5 \, s2 J; Q, x& k& b% g; O4 D. U; |8 Y
data = np.random.randint(-1,2,len(idx)).cumsum() # 随机游动构造模拟序列,cumsum表示累加0 g) A/ B. ~* u! ]# m
s = pd.Series(data,index=idx)# e0 R, h- B/ F' G2 \' p5 M- O5 N
s.head()* A. U5 m' N# @) D* q0 y
Out[106]: ; s* z+ t: K, n- \
2020-01-01 -1; u" V* t3 ], `) l* e
2020-01-02 -2
" d7 `3 M' t1 [3 I8 M0 k: c2020-01-03 -1
& m, Z) A5 Y. }* t1 c# i# @2020-01-06 -17 g4 x" a% y. Y8 W
2020-01-07 -2$ W4 b4 N; P6 l" {6 S0 |
Freq: B, dtype: int32$ t3 L% Y4 l8 |! }7 W
r = s.rolling('30D')# rolling可以指定freq或者offset对象9 s2 Z) |5 x4 I$ h# t
2 p; U o0 K! H/ R5 @, K6 Fplt.plot(s) # 蓝色线
* ~) _# w! ]- u" X2 mOut[108]: [<matplotlib.lines.Line2D at 0x2116d887eb0>]
W1 d! @, ~. N+ Oplt.title('BOLL LINES')0 g# }: ~9 v& N8 ^# U1 e8 W5 B) t+ {
Out[109]: Text(0.5, 1.0, 'BOLL LINES')9 J( a Q3 ~( Z7 A. n v) |+ @2 u
k- m& q, k# M' w; b
plt.plot(r.mean()) #橙色线
5 ]/ [9 z' y" [' sOut[110]: [<matplotlib.lines.Line2D at 0x2116d8eeb80>]
+ A( O- t8 ]4 \% C8 i2 J0 N
4 G1 L: G4 }4 u9 i! P! S( P8 Eplt.plot(r.mean()+r.std()*2) # 绿色线7 G. @( s) l3 O4 ^. H4 T3 p
Out[111]: [<matplotlib.lines.Line2D at 0x2116d87efa0>]
" \& n0 S' y7 w! B. T* |8 p& P1 H+ p* Z
plt.plot(r.mean()-r.std()*2) # 红色线, u2 t& o4 Q' Y$ T
Out[112]: [<matplotlib.lines.Line2D at 0x2116d90d2e0>]9 `9 u# k0 F G3 j0 S+ c& L
/ f9 l5 N$ O' m8 T) {1
2 Q) s8 F& p4 O% y; P/ q g2
$ J! u& m3 A6 {" H( ?- ?) l3; [/ R9 V- i0 O+ x$ d" R/ d/ h) Y
44 Y! G; P, X5 V$ W
5
9 e$ y3 V) t5 x6
* d4 b ~9 b, o( Y7% {- R0 @ L( u/ c. {, E/ `3 Q8 O
8
$ o0 H0 m0 t( a! E4 E* N( h8 n( P/ q9 c$ ]% x$ s. p, Z0 _
10: [7 K* g$ ~& t. C* r
112 \/ t, z) \9 [( s& E
12% s, |) f' {2 j- G
13 G6 T% o* f5 I& r/ y
14
' K( A& t5 n8 \15
; X2 h! h' |8 k+ R* ~16
! q! e1 U$ P' h; j17
, z: x3 ~1 G# \) G1 T' [$ P18
' h$ N. _4 h" |/ l! J( W: [19
# [; `" D O, }+ O8 [8 P) I' U209 c1 S& n4 l: e5 A
211 L" Q0 I) X4 F" H
22* h) \* o- B. N$ {7 j9 @' n
23, Y0 f$ R" N- ]* C4 N
244 q3 p" v$ j) A5 f6 D
25
1 P0 k8 y: Q$ ~6 }. z26
4 L1 x4 U: j2 F6 ?1 Q9 g27
6 P6 U: Z. z9 o# M/ R6 ~9 ?28
# p- |0 M3 C K) Y292 D; Z6 o- Q# K/ z m- ?7 [
2 H0 C$ |+ H( H& y$ r. s8 N 这里需要注意的是,pandas没有实现非固定采样频率的时间序列滑窗,及此时无法通过传入freq字段来得到滑窗结果。例如统计近7个工作日的交易总额。此时可以通过传入多个函数的组合来实现此功能。
6 m: A, P& k- p% m: s4 T: e 首先选出所有工作日,接着用普通滑窗进行7日滑窗加和,最后用reindex()恢复索引,对于双休日使用前一个工作日的结果进行填充。1 h, p" @6 A6 S: F& J% [
0 w4 Z3 e! p& vselect_bday=s[~s.index.to_series().dt.dayofweek.isin([5,6])]/ E* u; ~. j! @3 Z6 Z0 O0 ?4 s
bday_sum=select_bday.rolling(7,min_periods=1).sum()
; Q2 |- O/ ?, J5 V) ]* D: {8 bresult=bday_sum.reindex().ffill()1 {% m/ }+ P. K1 x3 i* G% Q
result0 J2 w2 E7 j* W% W
* O0 p! ~+ Q/ P! m
2020-01-01 -1.0
% {# ]& l6 ~" g, b" w- x2020-01-02 -3.0
) {+ Y( L9 ^( ~0 [+ Y! I/ h* N2020-01-03 -4.0
7 f1 _% Y3 h! E! Q3 X0 p; f1 m2020-01-06 -5.0 R& Q6 W! `( }/ Q. ~0 H* t5 d
2020-01-07 -7.0, r2 Y$ I" T/ k% R# P& T6 I
... ( Z- D9 Y4 u4 K0 e1 T
2020-12-25 136.0
+ v( [; |( l9 o4 g, Y9 B0 t2020-12-28 133.0! D+ c+ m5 z5 j
2020-12-29 131.0
* }& N2 N8 Y2 L# w- Z- P- w- t4 ~2020-12-30 130.0* n) Y* D, n+ D
2020-12-31 128.0
. _% j+ t/ r2 @. u; R, eFreq: B, Length: 262, dtype: float64! |" y9 R8 V1 f) M1 |$ {
' ~0 F; l! a5 k) K) r$ A% n7 s
1
( J# V4 x3 I# [2
& Y7 C/ F7 s# p) W2 C3
' Y! F8 X& s% ^* m4
- } U: H) n( }& d5
% z/ }7 e8 n3 ]6 r1 _% m, V6
- I! }% \3 w; ^9 f' h7
7 `8 W+ J- C( T5 U9 ]% ?85 i, w1 O: b9 t
9
. b+ N4 Z/ p h7 D0 A10
* A3 i5 r# `/ `/ e* S11) N, p6 T1 M3 G! `: R
12
) I" F/ N C9 B" m8 j13/ h1 P2 z9 ]$ `+ x
14
9 i, C4 l$ ^/ S! p15
/ k- H2 T8 `4 t! {# U4 [& G; @168 l: ^$ @8 ` |% l3 e. @) R
17
1 b3 C3 N& n4 t# k shift, diff, pct_change 是一组类滑窗函数,它们的公共参数为 periods=n ,默认为1,分别表示取向前第 n 个元素的值、与向前第 n 个元素做差(与 Numpy 中不同,后者表示 n 阶差分)、与向前第 n 个元素相比计算增长率。这里的 n 可以为负,表示反方向的类似操作。
3 Y! p- @ S3 \* X, k% I8 n9 Q0 y$ z+ }
对于shift函数而言,作用在datetime64为索引(不是value)的序列上时,可以指定freq单位进行滑动:1 _) _; |+ w" l0 C
- g. Z# M: e1 W) w* @# Ts.shift(freq='50D').head()
: r! `' A% B0 p0 X- m4 w8 w- KOut[113]:
3 I* @3 A+ q- S. O2 a/ ]2020-02-20 -1
# g3 Y( {1 U, c5 v2020-02-21 -29 d; ~! r& X9 W, G2 s/ S+ N0 ]9 `- r
2020-02-22 -1% a* O( J+ f* p6 K5 l, h
2020-02-25 -1
; J3 Q% R5 w) \, C# _4 _2020-02-26 -2
: u, _! y( _8 w2 B, Ydtype: int321 |" u; M1 b/ ^, O' r
14 k; K/ B' k* [$ A7 L1 o
2
; X* }8 C, k0 i( e3. P f# o' ]- \& R3 y- S3 b
4* M$ K$ t- n! J" ?
5
3 X5 r$ w: \, H/ a% S* s6
* U% I/ {( t) w8 ?7# H( ]1 x2 @% _0 d$ o$ }
8
7 X' g! t2 ?! ~ M4 Y 另外,datetime64[ns]的序列进行diff(前后做差)后就能够得到timedelta64[ns]的序列,这能够使用户方便地观察有序时间序列的间隔:
/ w/ O1 u6 X b' i1 a& c+ `5 C5 v V+ j* ^1 b6 B8 P' H
my_series = pd.Series(s.index)
+ x: ?3 D6 g4 O/ k+ j5 Y& @6 G# Mmy_series.head()" o. r7 _! o* o3 |% E% j/ T! w
Out[115]: 7 }, Z& x8 v5 Q& a" }
0 2020-01-01
9 `. t, z6 w+ x$ l0 P. [& e1 ], U1 2020-01-02. q* f- q; y+ m& Z4 ~5 N
2 2020-01-03
+ a* x4 F, D' D5 o1 m3 2020-01-06
0 g2 c/ a2 r! X5 [; D# W8 s4 2020-01-07
& X( ^6 I& N/ B. Cdtype: datetime64[ns]5 _6 [9 O$ B6 x+ {) n
/ Q% e# N' X( B6 \my_series.diff(1).head()
* Y6 _9 X. \1 ?0 {. B6 p; \' J4 DOut[116]: & e" c" Z5 X/ t$ T
0 NaT
9 T1 b2 n6 z, j5 ~1 1 days
! e6 Q! I T9 F, q1 v2 1 days$ L. f; \4 @/ q
3 3 days
6 x5 j K' p+ z% J4 1 days
4 n. J. u0 F3 B$ Wdtype: timedelta64[ns]5 Q7 o( i* T9 x( U1 f
. d& H0 t# ~5 g3 {. G& `, e
17 ~& d+ ?6 a" Z* `3 `6 R
2
- N$ D! a1 @% F' r: M( t' k33 P! F8 k3 x6 \- \$ s" X3 U
4
0 K& O' [' a5 `4 A$ l, a5
' S, J# D3 s0 y" {" h6
/ B' m y N% @) R B7) S# B' m+ I# n* h. v/ A$ u+ c, f8 b) f
81 Z9 E5 G! I& ?) Q! z# d) y( i! Z1 f
9/ w L! T% F* R' o- i; U
103 p( d$ E3 Y0 a/ U& F, T/ F
11- A6 O0 c* Q% Q# _/ s7 t
12
0 M4 r( a* Y8 a9 S13
2 ~% Q) j; P3 z14
6 Z: p3 N: v) n; `157 `4 h' Y9 X1 H& c
165 {) l. T$ I' k6 b' W
17
6 N/ \/ h& ?% G5 X0 p18) Q6 Q; k, U5 O1 ? C1 a! r) ~+ ]1 v
10.5.2 重采样
! A' h3 e: i* \5 h" n( C 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)2 V. U0 f! {/ ~
常用参数有:, r1 F. j4 v) i( D' ]
- o1 u4 M- O+ x, a$ S3 D) j2 q: Trule:DateOffset, Timedelta or str类型。表示偏移量字符串或对象
& c0 }$ k n& `axis:{0 or ‘index’, 1 or ‘columns’}, default 0。使用哪个轴进行上采样或下采样
7 x+ N2 e: i$ }5 Y- A# M5 f: oclosed:{‘right’, ‘left’},默认None。表示bin 区间的哪一侧是闭合的。所有offsets的默认值为“left”,但“M”、“A”、“Q”、“BM”、“BA”、“BQ”和“W”均默认为“right”。; s0 r, m# ^2 h! \* Z# B% E6 f+ F
label:{‘right’, ‘left’}, 默认 None。hich bin edge label to label bucket with,所有offsets的默认值为“left”,但“M”、“A”、“Q”、“BM”、“BA”、“BQ”和“W”均默认为“right”。- L0 |: p) d# c/ x7 q
convention{:‘start’, ‘end’, ‘s’, ‘e’}, default ‘start’。仅针对 PeriodIndex,控制是使用rule的开始还是结尾。
. ~0 n8 e) e( m" h9 y1 e0 pon:字符串类型,可选。对于 DataFrame,使用列而不是索引进行重采样。列必须类似于日期时间。$ C6 K7 h7 l p9 A$ Q
level:str 或 int,可选表示多重索引MultiIndex的级别,这个级别的索引必须类似于日期时间。
6 }9 z: L' H2 l5 B% m, jorigin参数有5种取值:9 f6 Z& C9 F1 t( V( C
‘epoch’:从 1970-01-01开始算起
5 C5 @; T' v: V! R" t. C5 Z, A8 W. s‘start’:原点是时间序列的第一个值
3 s) E$ z% Q3 y5 }0 H. p‘start_day’:默认值,表示原点是时间序列第一天的午夜。8 u: S1 I+ U0 q! r
'end':原点是时间序列的最后一个值(1.3.0版本才有)
& n9 H$ p2 ]: v5 l# | f‘end_day’:原点是序列最后一天的午夜(1.3.0版本才有)
3 m, N: t; O) x& Ioffset:Timedelta 或 str,默认为 None,表示对时间原点的偏移量,很有用。
) j9 H9 g1 o% Q! z closed和计算有关,label和显示有关,closed才有开闭。
" d* b+ ^7 J# P* R& l label指这个区间值算出来了,索引放区间的左端点还是右端点,closed是指算的时候左端点或右端点是不是包含。
' _" y; W& C' @- u6 L- P% N! f* P5 s) C
重采样对象resample和第四章中分组对象groupby的用法类似,resample是针对时间序列的分组计算而设计的分组对象。例如,对上面的序列计算每10天的均值:
% L n/ J; g/ ws.resample('10D').mean().head()3 P) u3 |9 x9 E" r6 l
Out[117]:
( g7 |+ G, \" ]* k) N3 s; L2020-01-01 -2.000000
! I% E7 T) _- M% B2020-01-11 -3.166667
1 o& `7 b2 z1 P, E& K2020-01-21 -3.625000/ E `, c$ @6 y# a1 n& V1 d8 y' m5 O9 s
2020-01-31 -4.000000
1 [4 \9 N0 A& Q6 G2020-02-10 -0.375000
0 \/ q! f. F3 ]Freq: 10D, dtype: float642 C- d8 `& ^3 ~6 g2 X: F. ]1 I) _
1
0 D; e: o2 C$ M2 P2' }% b# Y5 `7 m* n
3
$ t) {+ c( W1 V( o; K+ H) }4$ M; z& q. O a8 a6 \4 Z# l
5
) V( ?2 x/ ^5 V8 t* W( M& G+ P6
* X1 d7 f7 }9 {# }; _' N7, Q. o. ?" D7 W/ ~4 a
8
; B2 v" P+ E7 z% k7 S* a% a! @" F! q4 J可以通过apply方法自定义处理函数:% D& i% T/ H8 w" g) @
s.resample('10D').apply(lambda x:x.max()-x.min()).head() # 极差
0 X) {9 n8 k, {2 H6 S5 ?8 o n% u' T+ g% X# O
Out[118]:
% Q3 _7 t W# z+ e. E! ]2 G! c2020-01-01 3
) w- `* U$ k& m2020-01-11 4' h q2 u# \' I! {. K
2020-01-21 4
% o N4 w6 u; ]2020-01-31 2
3 U1 `# f: y. V8 f1 ]2020-02-10 43 a6 o. w \3 O e, [
Freq: 10D, dtype: int32
2 y0 c) P8 d/ b# M' |2 m: Z1! V+ k3 t w. E/ K' J9 v
2! P9 @8 D* \4 w( G
3
7 E g/ {3 ^; S2 B% z4( i/ `; q+ @3 w- B) [& [6 z
5
5 f7 l4 g) |( E: Q c6
; K* j# `3 ?& y A+ K: ^8 b6 {7! E% o1 ^$ S8 q4 {% j
8 y% Q9 i+ e9 }' a
9$ x" k4 u7 W# [' {/ u& U# B }
在resample中要特别注意组边界值的处理情况,默认情况下起始值的计算方法是从最小值时间戳对应日期的午夜00:00:00开始增加freq,直到不超过该最小时间戳的最大时间戳,由此对应的时间戳为起始值,然后每次累加freq参数作为分割结点进行分组,区间情况为左闭右开。下面构造一个不均匀的例子:
, }. X. v9 {7 M1 I& o2 ?0 f/ C. k( s
8 t' ]6 A3 H! eidx = pd.date_range('20200101 8:26:35', '20200101 9:31:58', freq='77s')
" L( q4 i- g$ Xdata = np.random.randint(-1,2,len(idx)).cumsum()$ Z& }" f3 H4 N
s = pd.Series(data,index=idx)
% Y# M s$ b/ Y Is.head()/ h& ~0 B) V0 o; F4 r
g) O* Q! m+ b3 ^# IOut[122]:
. X8 A1 n; m! y- K% }' l; [2020-01-01 08:26:35 -1( C* {* t! N5 a q0 b7 z& @
2020-01-01 08:27:52 -1
% _: j6 t, O$ a) w+ K' r, V/ j2020-01-01 08:29:09 -2 t3 C6 G7 [3 c+ l
2020-01-01 08:30:26 -3( Q$ Q& d2 h4 j2 S3 ~9 H$ E
2020-01-01 08:31:43 -4
- h9 t5 x: \4 g* | ]; p7 g( ]Freq: 77S, dtype: int32# c$ ^4 ~2 G# u5 t
1
1 U( v5 c% D. O2; o' I, u# ]2 S9 m( c [
3
4 P, l5 l, \* i. u- H- s- B7 u! F4
& k" r6 {) B* u1 Z50 M" y/ O' {7 b6 r6 R# E
67 t" j- B# Y5 Y+ `
7
7 n6 ]) X9 b# h v88 x8 L4 r6 ?0 c- s+ X1 t' z5 x( L
9# j* ~. ]7 z7 M0 b4 T
10
+ L/ I/ S% U. E( J) A7 L11
- ?& g8 @: r# ^7 O12
* `* Y1 ?3 G. R) ~- ?# g$ M& X 下面对应的第一个组起始值为08:24:00,其是从当天0点增加72个freq=7 min得到的,如果再增加一个freq则超出了序列的最小时间戳08:26:35:. W9 I9 i# o" \
1 U# R; @# o9 V& q
s.resample('7min').mean().head(). g$ H% k: r! [. t+ Y2 u' K3 e# Y
Out[123]:
5 ?) V+ s' b8 v0 E* Y" Q2020-01-01 08:24:00 -1.750000 # 起始值,终点值包含最后一个值
' R" A& v$ w2 |& H R2020-01-01 08:31:00 -2.6000006 S. L5 A, D/ m' }4 B
2020-01-01 08:38:00 -2.166667
& D2 \; Z, N% f' e: Y* f& K; A2020-01-01 08:45:00 0.200000
' V; M/ v* M9 v2020-01-01 08:52:00 2.833333; _. u) x2 s' S. I4 _1 A
Freq: 7T, dtype: float64
% Y, P3 I; k* D! F, {: h$ a17 p9 p. `# s: R& ?7 U
24 f. o/ {% ]+ U* f: ~- O+ w7 S; g
3
# z) R! P$ B r e @ b! I: k4
) ^" p, _1 b9 Q4 {( j& A& s" A& W+ x5& n7 f2 v; \+ d
6
?+ g8 @4 B/ }7 Z: Y7
0 N/ I$ X U2 B9 |% @8
/ c: u) u5 W% Q0 n7 z+ D4 W 有时候,用户希望从序列的最小时间戳开始依次增加freq进行分组,此时可以指定origin参数为start:
4 @: Z5 `5 y2 K0 x: j" p. u
8 v# F; c1 [& b1 Os.resample('7min', origin='start').mean().head()
9 `0 i- N9 G. U; o* vOut[124]:
8 ]+ W4 L/ @% c9 O6 {! s2020-01-01 08:26:35 -2.333333! r/ Z0 }: e6 J1 O) ~/ ]+ m
2020-01-01 08:33:35 -2.400000) I/ p' p9 @/ ?: R u5 S3 A+ R7 f* D
2020-01-01 08:40:35 -1.333333; }% v; Q* q: u5 H9 e6 `
2020-01-01 08:47:35 1.200000" n- r" }( B- d: p; V. y
2020-01-01 08:54:35 3.166667+ z- h& y4 [- @+ s) u: |
Freq: 7T, dtype: float64
1 G& n, r* q6 @1 q5 W4 G2 V1
; h: d4 V% I9 b7 h$ o2
5 Y0 Y( i: o7 C. n) I3
* g( D X2 z: y$ A6 ]4
( w0 w5 }2 t1 ^, B" X: v5' b5 ~# m1 [4 c
6
: H7 J- W# L: O( `( _' q7
( W4 t: B; K7 W8 Z84 ]! G+ a T! ` |% P& N x8 y3 l$ \
在返回值中,要注意索引一般是取组的第一个时间戳,但M, A, Q, BM, BA, BQ, W这七个是取对应区间的最后一个时间戳。如果想要得到正常索引,用’MS’就行。
F5 j9 W9 I* n% g% z2 j4 t7 g
- t1 C- X1 V. b4 r3 Cs = pd.Series(np.random.randint(2,size=366),
* I2 [) \8 ~' Q R: v! @. d index=pd.date_range('2020-01-01',
- e# \' F3 V2 T. `7 Y '2020-12-31'))
H! j+ y, a, x( e2 ^; J- z) H' |3 p$ L: B; m/ c U
3 I" w* M. P% S' {# ?# W! Y
s.resample('M').mean().head()" X. T* M3 U5 [, s! M, \* U3 n8 Y
Out[126]:
3 E% i; M/ t, C; _, a8 L8 a/ G* d$ V' V2020-01-31 0.451613
& s- ?" V z( y" L3 i2020-02-29 0.448276
$ q1 @: X2 h/ {+ m2020-03-31 0.516129/ L6 G% _% @( J) t8 Y' E
2020-04-30 0.566667
1 G: }* r3 r3 P' R. W! f, S5 X2020-05-31 0.451613- z2 |' c! h8 G: D- p& X
Freq: M, dtype: float648 I8 o6 Z) y9 l) M1 b4 Q! y
: U" H( A, }" S1 P7 ~" G2 J
s.resample('MS').mean().head() # 结果一样,但索引是跟正常一样
* {* W- i( ?7 b7 q! lOut[127]:
* ^ x3 J& @* c) E; Q' h# {2020-01-01 0.451613 d1 ^) X g. f2 l2 a' N: k/ n
2020-02-01 0.448276
9 p7 k1 I0 ~/ h3 z7 \2020-03-01 0.516129
4 M) w: b! A* j. d2020-04-01 0.566667% O" W8 Z# X2 L F) T
2020-05-01 0.451613
3 X. Q! o- x" l+ p! L( Y$ NFreq: MS, dtype: float64
* O2 O; z( L% |0 t! M/ E- `* }; @/ _! Y3 e7 N0 @5 B( v7 f) D7 X
1
0 b1 b$ r! |& n2 ]+ a! y2& P) {9 B8 z9 C6 k2 U9 Z
3
, D2 t$ `7 B( d; ?4: m: A! e$ U7 p' c/ _, T
5
5 W8 a- c- U3 h( g6
4 J- s4 ? X) j6 S7
/ d- h- Z/ H2 Q5 B( u; s1 V0 c8; O" K7 O9 {! R, B) |2 B& M5 n6 ~
9
$ K1 A6 u, j; B# W10
1 \. x4 m6 V7 }5 ~/ r) n11
* D' T0 L- p. i J12
3 K4 z% l" E7 Q3 o G, R" ?& o13- r! ^, n" q B6 L: b9 Q
14
0 j2 M; f- W# q4 x3 O, k, z6 H0 B3 f0 j15
. R% A9 ^* b" Y16' }7 Y( j& T1 |
17
! Y7 ?" \3 {* y$ H18
3 o% c8 W# |" z* h3 w$ m5 X19! s N& n/ ]4 P/ l
20" B' o1 E$ K7 A6 Z) u& I; l: O& X
21
& r s- [- q/ r% y, D22
2 f1 R% w; g2 g对于 DataFrame 对象,关键字 on 可用于指定列而不是索引以进行重采样:
1 |' ~) F3 d* Z# Ld = {'price': [10, 11, 9, 13, 14, 18, 17, 19],
, e2 Z, X: u9 Y# q4 [ 'volume': [50, 60, 40, 100, 50, 100, 40, 50]}+ ^5 v+ h7 {0 ]4 f$ y, O
df = pd.DataFrame(d)0 P, U: }, B7 t1 b A+ C
df['week_starting'] = pd.date_range('01/01/2018',
) o4 j2 L' ^! G0 | periods=8,
* G4 R4 i' T# k$ G! A4 T, [ freq='W')& N0 O1 q& ]: `% ]! ~
df$ j6 A) q5 H' V9 g1 Y; s1 F
price volume week_starting1 k1 r* o5 Y& G0 s1 T% v
0 10 50 2018-01-07
2 B) q8 I& a3 {5 O$ Q# O1 11 60 2018-01-14
* y9 F `6 n @ c2 9 40 2018-01-21$ t4 } x! T1 o
3 13 100 2018-01-28
$ A5 J/ `% J) s4 14 50 2018-02-04' h( q& m% A, U; Z
5 18 100 2018-02-119 O$ N7 t$ y2 d; [
6 17 40 2018-02-188 \$ v1 D' I9 |' G. S, \# x
7 19 50 2018-02-25
: E4 G, q8 e+ {df.resample('M', on='week_starting').mean(): ]' v$ @% f; N5 D4 F
price volume" E) W* t, t$ Z' p$ D% q; N& ^5 C
week_starting
3 [* k x% E# t0 F+ w2018-01-31 10.75 62.5
0 R0 o5 g( M$ J# P u) h2018-02-28 17.00 60.0! l. |4 y h/ E$ [" a
5 H& x0 V/ M7 k2 F) y/ w' U$ U5 A! V1
) q: _; w& n/ B" Q S1 Z2# h8 A- ^# _ a3 v& ^0 k' l5 J: l
3, C; s* A6 U3 O4 ~3 J! h
4% ?5 Z; [+ U) {( |
5
' Q: x" E6 r$ d1 j' Q6% F6 j" u& `% ~8 `0 c
7
1 j9 U q( H! R, r* i; O3 j0 }8 N! k9 I7 L9 M/ D3 U
9/ c( _% G8 |' T
10$ F: }) T' n p% C" c
119 S- x* F& t6 `+ A; {1 x
122 T" z0 L. u+ O7 O# g1 j
13# K0 ]# l( W& K3 u* l
14
# s5 a' L" F7 G& T5 x! b15: U/ K! ~2 a( s( o7 B
16, N9 i) p6 w0 j
17
" B6 u! j3 x5 |+ s7 e; a18
; q# L/ S1 c+ U' z# n! T' a19
. k5 p& R# O9 v2 i20* v ?2 _0 Z" Z0 G* g$ h
21
% @3 }- e! w' c# E |对于具有 MultiIndex 的 DataFrame,关键字 level 可用于指定需要在哪个级别进行重采样。
7 A) U# C# M7 H. z3 S2 m2 zdays = pd.date_range('1/1/2000', periods=4, freq='D'), O: H6 U, ^6 g9 S8 j
d2 = {'price': [10, 11, 9, 13, 14, 18, 17, 19],
; e" m; [' K" Z/ U; M/ H 'volume': [50, 60, 40, 100, 50, 100, 40, 50]}
2 M2 B. V# ?; @) Edf2 = pd.DataFrame(
2 R9 h' B2 X! f' P( t d2,
" ~$ G3 G3 z. t4 O( C index=pd.MultiIndex.from_product(8 j6 @3 T1 A- E$ w( X' H
[days, ['morning', 'afternoon']]
8 z* {( t. z$ W+ d( N+ b )
8 V4 |& S3 G- n1 x# y)1 V0 |* U5 c/ V2 K: l
df2
4 u- ^ r/ e& a/ ^3 ?& S1 p price volume7 M: R V! Y: ]4 a& I
2000-01-01 morning 10 50& A4 h" b/ k5 L- w
afternoon 11 60
& I0 e: q" P) i; u" {2000-01-02 morning 9 40
, `- S0 E* n8 Z( R afternoon 13 100& p4 m5 H3 c' F% F1 x
2000-01-03 morning 14 50* G$ h' c+ w7 b! B1 O
afternoon 18 100
' R: U& N) b |# H5 Z2000-01-04 morning 17 40
* r ~& u: h. L/ M+ I afternoon 19 50: Y+ M. N) a2 {# {/ [
df2.resample('D', level=0).sum()
* b. D* ^) D' b/ c7 x price volume
# U4 \/ n# j5 R% E" G% \, ]2000-01-01 21 110 e V! F5 v# ~( T) M% m8 {1 L0 x
2000-01-02 22 140- L u9 `% i7 }3 k8 q' \+ v
2000-01-03 32 1508 G8 v7 l9 O; n s' |% t
2000-01-04 36 90
* [5 r1 e- w7 w& K) e! e7 X+ D$ I* m3 D0 i4 r: a7 M3 \5 c
1
4 I5 r4 Z( p( E( I2
( q- \/ X3 I5 c6 a6 y3/ T8 C9 C5 D4 ^, O, c
4
0 @% U) g2 ~( V! y/ S) G ~53 b: S9 Y; A: O, A" |
63 c, V1 c$ s3 r8 \
7/ u8 ]: e6 J7 \% T
84 K; Z2 B9 u" Y! i, C
9, J( F+ a5 M# }, \) Q( q
10, z8 p3 S( |. \' o
11
6 P4 `' m# e8 r2 y12
2 }, ~3 t9 F, p5 D7 g: |13
& T+ [0 n8 R0 }5 q" K14
6 G5 S/ T4 L7 V! L( I, V) G15
% L: d! V9 N% s: N8 \. ]164 L% K @5 u( f' N! T) F# r
17 O" n) i" n7 X( Q# M
18' M( }0 h, ?( _/ j0 u% F& Q
190 Y( U; _. J, A5 P: O9 T
20, D9 o+ O4 m0 n/ S3 K0 ^
21& k6 N' E9 c% F+ j4 }0 @9 D1 L
22: G0 {" x- T- B6 _4 z6 \! n, o
23
$ q/ ?- |3 `$ l' N. h2 _24
# ^1 L4 e* \& ~4 _5 N25
& ]# E& ~8 U# e$ e& N根据固定时间戳调整 bin 的开始:/ q0 E% Q8 E6 h/ q7 W' x) n
start, end = '2000-10-01 23:30:00', '2000-10-02 00:30:00'! p* n/ z' ?7 J( U8 |* f. c7 L
rng = pd.date_range(start, end, freq='7min')
+ I1 Z( x5 Y# {9 cts = pd.Series(np.arange(len(rng)) * 3, index=rng)
7 S* f- {# ?2 D9 ` f0 W& _( Jts
) b! f5 ~. A; t# o8 L: S2000-10-01 23:30:00 04 m# o4 p& z0 E* a& Q
2000-10-01 23:37:00 3
7 |+ C1 h; ?6 J0 ]2000-10-01 23:44:00 6( `# o$ J5 O" B9 J* D; Z
2000-10-01 23:51:00 9! e% y$ [- `' [' B$ H4 `1 [
2000-10-01 23:58:00 12
" A/ h3 r# s8 |- Y( n2000-10-02 00:05:00 15+ [' y& x- N) W+ C6 {
2000-10-02 00:12:00 18
3 r' D/ l- d; z/ g2000-10-02 00:19:00 21, q+ L8 i2 a/ m1 N
2000-10-02 00:26:00 24
m' g& P* Z2 r) m7 ]Freq: 7T, dtype: int64
0 V# B# c) l6 z* l! K) q3 W' ]* t5 L. F/ N* _: [) c1 U1 F$ _
ts.resample('17min').sum()
$ I$ r& a- Y6 D6 c* d/ u+ a2 n2000-10-01 23:14:00 0
! ]1 b- s4 i7 \* t: _! v# P2000-10-01 23:31:00 9
- a* X& a9 K1 {9 s2000-10-01 23:48:00 217 [, O3 v$ X# }9 E; p2 v9 Y
2000-10-02 00:05:00 54! ~7 F% V% H" U" ^
2000-10-02 00:22:00 24
; h5 P; V# V- N& D- R! wFreq: 17T, dtype: int64: \9 a5 |" z) D8 }
% |- d6 c% r" v0 M4 Vts.resample('17min', origin='epoch').sum()
3 q7 U' k0 M9 g0 r: z. a; E2000-10-01 23:18:00 0, u Q% F5 R, {& n! \& I+ M" d
2000-10-01 23:35:00 18
: ?& A# H5 z$ f( a( {2000-10-01 23:52:00 27& b& m6 s/ k0 J5 e6 C3 i
2000-10-02 00:09:00 39
% V: J! v- v* o" m9 U2000-10-02 00:26:00 24
6 i$ H( M) p0 Y) f/ KFreq: 17T, dtype: int64
3 \/ W7 a3 D: y4 o% m# ^
# ~4 G' o: B4 V7 r7 e7 A& U' N8 |ts.resample('17min', origin='2000-01-01').sum()
; d) N3 w# b; {9 h9 ]9 p7 }2000-10-01 23:24:00 3
' o4 @+ [! i, j8 H2000-10-01 23:41:00 156 P# E% Y* B+ x" B
2000-10-01 23:58:00 45
. ^7 F) X. V( @2000-10-02 00:15:00 45
* D' v7 x; |9 b, T; tFreq: 17T, dtype: int645 V7 b, G5 T* X2 E0 N( a! k
9 }2 c. B) g- ~1 ` H2 w ^1
5 Q# r: J( C. b9 m' u6 ~/ ~3 s2
8 c! Q4 l6 V6 l! X- m7 P; z3) e1 P0 s, Y3 x9 X/ }9 Y1 P8 {1 q# l
49 h1 T8 ?- s9 |, q/ E5 U i; o: X
5
& F3 m; E: u* u y6
/ {) B2 R) h, u2 w3 O7& A/ r9 E- i# k8 f
8
) T7 A6 \1 i: b2 Y# r9
( d) I. F% a5 x# H9 F2 c% ?10
! b6 _* `% Z5 H" R9 v: ^11
6 u9 J, a# N2 F3 P8 z# D! y12
4 @, v! V0 [- s8 c1 ]& c13
7 {% O) d4 W6 |* d" z- D; p14. [- q/ |) _6 O4 @9 q
15
6 S* k3 Y2 J5 P16( N# ]8 `: P* K2 g! _" x$ ^: v
174 B# ?9 `/ [9 L- ?1 Z7 I
18/ o: a/ m j `/ I
19
' L E9 j0 v5 r9 U. @: Z9 A" i& a8 b207 L6 y2 i4 g* p; }) v
21
1 O5 G" t6 O. h- K221 v2 v# L# K' A
23
- I6 K T2 G9 R$ P" I" J24$ X# {/ T; c1 d; T
254 R3 k. @" q2 T1 F, D) }- P
26 A' y5 b# f Y) ^9 J, P2 Z E
27
; H" w+ u6 M8 V! q28
5 W5 X7 }8 l0 ~29: N( q7 l# _# d! l2 Y
30
, f. q- f% F( g6 F! q31
& W+ I$ O. O: f1 C5 t/ t32; h6 ~! i; y# e8 L6 {
33" A Y" X7 m9 |( }& O. ?- f0 A( N
34# `. Y. t# a5 A- j3 [# H" V
35
F8 M' k1 c7 ^8 ~* ? ~+ x: _36( C6 e5 O* f# ~7 y; {! X9 I
37. n L5 g) v" x
如果要使用偏移 Timedelta 调整 bin 的开始,则以下两行是等效的:
1 g- K2 p1 L5 fts.resample('17min', origin='start').sum(), N7 t% P+ F, G' N1 H' n* z
ts.resample('17min', offset='23h30min').sum()5 [: z7 z6 h$ L
2000-10-01 23:30:00 9# x/ p) L: Y3 f C6 G) J
2000-10-01 23:47:00 21
, V( O6 g! D. t/ t C2000-10-02 00:04:00 54
3 \1 v- I2 y/ j! x8 U2000-10-02 00:21:00 24) Y/ c9 s5 n9 s% H9 n
Freq: 17T, dtype: int647 M' | G# `- h" J' @% h9 ?
1$ h( x5 T3 T! j
25 K7 x j" n/ U
3
, W# b, d, R0 m8 L1 c8 k% |2 t4& o. C% H! x4 [3 g: ~" D
5: g. Y) X/ W+ \; Q L* i. U4 W
6
* T0 N) Q' H* X1 S$ V$ |7
9 P! \( v# v2 v7 S5 ~7 i10.6 练习
( ]5 }( ?5 x8 g( y3 ZEx1:太阳辐射数据集0 g8 O+ g6 A' ^( @8 w. X
现有一份关于太阳辐射的数据集:% o$ B' V, {0 l0 R8 S
" @. @3 y. V C+ pdf = pd.read_csv('../data/solar.csv', usecols=['Data','Time','Radiation','Temperature'])
( M8 }9 ~9 s( I9 Rdf.head(3)
) Z/ Q$ c2 e W: P
- K. N/ B4 Y3 y2 G7 f6 N6 J. J) NOut[129]: 0 U: B d- n9 o2 g
Data Time Radiation Temperature' s/ q5 S3 ?# g$ z/ {6 ?$ W6 h3 i4 v
0 9/29/2016 12:00:00 AM 23:55:26 1.21 484 x* z/ h0 `' p2 @/ }& |
1 9/29/2016 12:00:00 AM 23:50:23 1.21 48
: s6 C! B3 H7 I2 9/29/2016 12:00:00 AM 23:45:26 1.23 48/ \; S; j4 e1 t0 g* {- q f
1' L& L z7 F& U, e1 K
29 i. g) L- |) G% E/ j- F4 Q2 G
3
; _/ ~' L" W/ ^, t0 R4
# f n# ^/ I* G; d54 ]: l1 o3 F* O) I
6
( h& f4 Y7 d# |1 U7 r1 n" C* g+ ?2 Q( v! l$ W
8
5 g" y7 z4 v: o2 h+ y% t0 _: k% C将Datetime, Time合并为一个时间列Datetime,同时把它作为索引后排序。
% Y4 f% l4 c: h$ V# V每条记录时间的间隔显然并不一致,请解决如下问题:
9 h3 T5 G$ c! z6 C' m找出间隔时间的前三个最大值所对应的三组时间戳。
( U3 [* m9 [$ B是否存在一个大致的范围,使得绝大多数的间隔时间都落在这个区间中?如果存在,请对此范围内的样本间隔秒数画出柱状图,设置bins=50。
" B- O9 ]# y& ^: w) `( P求如下指标对应的Series:
7 N2 K+ m5 z) [% _温度与辐射量的6小时滑动相关系数) s* P% `5 G& [' r
以三点、九点、十五点、二十一点为分割,该观测所在时间区间的温度均值序列
, l. }# d! j% ` u每个观测6小时前的辐射量(一般而言不会恰好取到,此时取最近时间戳对应的辐射量)% L/ R- Z$ Q7 B& _; m7 o
import numpy as np
0 Q2 \& t! m1 _" s* Mimport pandas as pd
2 T X7 f5 O% A( P( I1
( L$ r3 Q/ t1 L6 A; O& W2
5 {/ {; ~# h: _* t6 N7 o将Datetime, Time合并为一个时间列Datetime,同时把它作为索引后排序。
( s- o& l9 O) n7 X8 a1 b6 zdata=pd.to_datetime(df.Data) # 本身是object对象,要先转为时间序列
! A' k+ u; W" }/ {times=pd.to_timedelta(df.Time)
6 m2 O% k! b. hdf.Data=data+times2 n$ d6 c0 i3 _9 |( Q
del df['Time']
6 s$ {4 K( ~7 Ndf=df.set_index('Data').sort_index() # 如果写的是set_index(df.Data),那么Data作为索引之外,这个列还另外保留7 ]9 o4 W0 s5 C. \* N5 |
df9 T0 Q; h1 E: V! f
Radiation Temperature
) o) y* V7 P1 [1 f9 j4 J$ cData 5 ]5 r, e5 \0 I! u' f
2016-09-01 00:00:08 2.58 51. E, A5 K" J7 u3 {8 ?; \
2016-09-01 00:05:10 2.83 51
$ u& y" u6 ?6 P" x2016-09-01 00:20:06 2.16 51/ ]& s$ E. }2 E5 s1 B
2016-09-01 00:25:05 2.21 517 `; m1 G O3 N* s# i1 y$ @6 J) o
2016-09-01 00:30:09 2.25 51
. m% k! n# T- M9 U/ N3 v/ l... ... ...
" @6 T4 R! u' \# ?* a6 G3 N2016-12-31 23:35:02 1.22 41
% k: ?. \% h! n: x2016-12-31 23:40:01 1.21 41
, v0 Y5 Q0 U) E3 @4 p3 K7 r! n9 ^& |2016-12-31 23:45:04 1.21 428 O1 C7 I R- y& H2 a7 L7 e! r
2016-12-31 23:50:03 1.19 41, v% K, A+ U; O% k
2016-12-31 23:55:01 1.21 41
& r, `1 v' t6 N1 h, {( U. X
( f* o: C3 i# t1) i7 S+ |1 U+ t/ V1 j) i3 t
2% N+ c% S- w8 v$ N# i
33 @& H: u- Q5 Z1 q4 \
4" \) w: y6 {6 [+ `! M4 S" Z6 y
5
9 j$ t: C* n( Z+ C* q# O1 s) l9 }60 R# t; r6 d# a. o% h; j3 L
7/ C. v' i% K o. u- [+ |
8
5 ]0 a+ ^6 E* f( O# F( ~& f5 E9
8 u! i% F F5 r0 W10
- G. ]8 d% v8 ^11) [; z. D1 T: z
12
0 `# E8 i- w; h+ I$ V3 J% |13
0 N8 i v* P$ R* e14
7 v* J. t* ~! G15/ n3 D. p4 ?$ [- q
16
& q6 w8 m9 T7 W+ h/ N% E17; [" F2 E3 B' W7 [( {. _% z
18
) l5 M7 k5 I% E" [( O$ g19
! D/ S0 ^/ E; A3 t+ G每条记录时间的间隔显然并不一致,请解决如下问题:
& X3 p+ }/ B/ L+ W$ m$ h z找出间隔时间的前三个最大值所对应的三组时间戳。
* l' ]+ W9 p1 a! ^# 第一次做错了,不是找三组时间戳
0 }$ j Y2 b6 `2 nidxmax3=pd.Series(df.index).diff(1).sort_values(ascending=False).index[:3]0 N- ?. ]% B* L1 b9 D
df.reset_index().Data[idxmax3,idxmax3-1]
& m( P9 q1 u" h0 [$ o" P! C) k1 ~, r, V3 L
25923 2016-12-08 11:10:42
' `: J1 L( ^ Q4 [; c) M1 W5 r9 K, X24522 2016-12-01 00:00:023 N6 K. ~* c- h, [+ r- Y5 q
7417 2016-10-01 00:00:19
0 e1 w5 m) X( D W _, VName: Data, dtype: datetime64[ns]; g4 Q5 t7 q1 _: Z% M
1
5 q# {2 h6 D" j2
3 Z) U0 j- P" ^( k0 K# V) N! Z3
3 Q* _" U# P' v; I/ o4
2 W: \) W! s7 I! a5* l1 Z6 A* n- A* q F
6
' \) G- k' ?9 n6 J/ g+ @* R1 M9 e7
; c. T+ p6 O* o. R$ `& W+ |3 V8
( g; _1 P3 k$ w2 A7 Jidxmax3=pd.Series(df.index).diff(1).sort_values(ascending=False).index[:3]! @. G3 s) x$ E/ O! X# ?
list(zip(df.reset_index().Data[idxmax3],df.reset_index().Data[idxmax3-1]))6 H# R1 v( @9 g
" N' g: S3 M8 E) @* X, \
[(Timestamp('2016-12-08 11:10:42'), Timestamp('2016-12-05 20:45:53')),
+ W4 v% q4 p- P$ p) o) _# O# V (Timestamp('2016-12-01 00:00:02'), Timestamp('2016-11-29 19:05:02')),$ Y3 _4 g, ?/ s' l7 u' |/ r0 m! C7 P- m' z% q
(Timestamp('2016-10-01 00:00:19'), Timestamp('2016-09-29 23:55:26'))]
7 \) e1 _$ X* b& ~; S1
8 c" r: F/ C% N P2, S! k6 q+ f3 B; s
3
. ~0 a7 d& E* w4 z4
) z1 W% n0 B% J( q$ U/ | f: l0 ~5( K% ^. Z; {" E# G
6# a# c6 J1 B0 t3 k9 E9 @" B7 ^: v
参考答案:. d4 V+ {. a; y. n
3 g3 G, K( G' U
s = df.index.to_series().reset_index(drop=True).diff().dt.total_seconds()9 K, e, z# e2 i! s7 @
max_3 = s.nlargest(3).index
q- R. D3 E) j0 R9 r& |2 pdf.index[max_3.union(max_3-1)]
4 B! S9 o9 H3 T. ]) J3 G, W5 ^. [7 i+ T$ g( Y
Out[215]:
2 x6 R' ^/ A* S' b+ VDatetimeIndex(['2016-09-29 23:55:26', '2016-10-01 00:00:19',% m2 N. W4 }2 _% K7 [, h
'2016-11-29 19:05:02', '2016-12-01 00:00:02',4 i% X6 Q. I# e# @6 @* V" Q
'2016-12-05 20:45:53', '2016-12-08 11:10:42'],* x1 h1 P4 s; J
dtype='datetime64[ns]', name='Datetime', freq=None) e5 g( b; ]9 l$ B1 C2 X4 l! F5 V
1# o/ O- P; N1 z! N4 Y3 s; v
2/ v$ O1 v+ _3 N/ C
3
; v7 @" |0 r6 P$ T) s4
/ t+ C& |# }" M! ]- p5% Q) v; c/ l1 R+ c$ a2 N0 @# l
67 u! u+ }1 P$ ~ E2 c
7+ n$ n0 H' I7 z: X$ p' A& ]# k8 f, r8 |
8
& }& z: i$ F# L) T9
9 S/ A8 _0 b2 ]$ x$ q' ?' v是否存在一个大致的范围,使得绝大多数的间隔时间都落在这个区间中?如果存在,请对此范围内的样本间隔秒数画出柱状图,设置bins=50。. W$ {+ n; @0 c3 S: t; K
# 将df的indexydiff做差,转为秒数后排序。再求几个分位数确定取值区间
5 c6 b) q" c. i* e9 {s=pd.Series(df.index).diff(1).dt.total_seconds().sort_values(ascending=False)
: |' a" n" i- ?4 V( K. [* Us.quantile(0.9),s.quantile(0.95),s.quantile(0.99),s.quantile(0.01),s.quantile(0.03),s.quantile(0.05)
1 ]" L( _) `* d0 z3 r& h
' b# r% u! c- i! g& k(304.0, 309.0, 337.15999999999985, 285.0, 290.0, 292.0)
3 K8 A& ~/ X$ E3 z1 V1! s( B9 F: X/ _4 \
2
' y% X1 C1 `5 z; u# S$ z8 s& H3
) i: s0 E$ v8 A1 W4
, c$ ?8 z4 a& h2 u56 x3 [( O$ Y; c
%pylab inline% b2 O2 F5 }: S9 m7 ?; l X
_ = plt.hist(ss[(s.values<337)&(s.values>285)],bins=50)( Q$ b2 G* Z0 o0 c ?9 {- k
plt.xlabel(' Timedelta')
9 I% ?3 l9 l$ q! Lplt.title(" Timedelta of solar")
; f3 w1 ? w" d2 h4 O( Z1; k' y0 B+ O6 O* m
2
( t/ s, ~1 O9 n$ e& p3% x9 v0 V9 k( E8 M6 w' h
4
/ ?/ \' p0 l+ r! k# |9 b/ [: C% k( R& p; A; u5 ?$ g
! @. S- o6 p" K$ D
求如下指标对应的Series:0 ?$ R( R" a6 h. Q ^
温度与辐射量的6小时滑动相关系数
: r, K, w' P0 ^以三点、九点、十五点、二十一点为分割,该观测所在时间区间的温度均值序列
" \! A+ u4 l+ s' N% p" }2 g每个观测6小时前的辐射量(一般而言不会恰好取到,此时取最近时间戳对应的辐射量)) M( h& y! e2 l& w5 R$ q4 Q- @
df.Radiation.rolling('6H').corr(df.Temperature).tail()
3 H1 L6 ]) P+ j% q- D) R: J8 [# M( ]/ j' v
Data
8 f" p3 p) u. A! K: u/ L% q2016-12-31 23:35:02 0.4161876 u0 J( R- U% s' G. y/ `
2016-12-31 23:40:01 0.416565$ ?7 \+ U$ `/ Y/ L6 J1 M
2016-12-31 23:45:04 0.328574$ a4 d( w9 Z8 D% J4 F
2016-12-31 23:50:03 0.261883
$ z6 ?6 N3 H4 n0 `2016-12-31 23:55:01 0.262406
; i: ?" @8 }- f5 @dtype: float645 Q# g! }: h) K4 D6 V; k# p; n
1" V" M$ ^/ H/ l/ N) B& r
2& u: C& N0 C% ^6 @
3
1 o/ ~% j7 L6 f# t6 ^( a4
- s, ~+ ?" c8 {/ F5/ _, b- Q1 u' D- Q: s! F
6
% ~8 F# F; V( T- R74 _( o8 p4 \6 i
8
, c. m0 I& N1 X$ f0 @; `% m9
& E3 i9 N! g) @' Q3 g! I3 b* ~$ S; I7 ydf['Temperature'].resample('6H',offset='3H').mean().head()+ p! i4 E! ?. }
: F- d @% u5 D& j* s
Data" Q! k7 D4 A: t! h" _0 F
2016-08-31 21:00:00 51.218750) Y/ Q8 e, m8 S2 K2 O$ W; p) u
2016-09-01 03:00:00 50.033333
1 W& w- x2 b9 Y7 V2016-09-01 09:00:00 59.379310
/ k- ]" x# _' V, S* h8 A- V2016-09-01 15:00:00 57.984375
0 |+ H+ f( G! C" D9 b2016-09-01 21:00:00 51.393939
( t, U {1 g1 l6 J- _/ SFreq: 6H, Name: Temperature, dtype: float64
. u R& S) C1 M/ j% i1
: Z/ W% q; W9 l* Q! T. p" D3 C v2
7 x8 @3 }# ~2 W: Y: z/ o+ F- `( ?3
2 L4 t( s9 O" a; N3 q, k48 x) r6 i- Y E/ H9 a
5+ \8 [% k$ m" z: G- I6 }' T2 H _
6) g; B0 F b) q! ~, _
7
# ~, D: G4 d9 a* c7 f# t6 N* q81 x# P, w" N- Y- L
9- J- r. t6 X9 f4 v! T
最后一题参考答案:( {5 B4 J& b( \9 @5 t- @- T' x
2 c% `. x. D, z9 W/ L0 f* Z
# 非常慢
- { o4 _/ m; S: U$ Vmy_dt = df.index.shift(freq='-6H')
8 k- K, V# @. B. j4 }7 Fint_loc = [df.index.get_indexer([i], method='nearest') for i in my_dt]
& _& ]0 J9 H, ^6 Y$ S- R d- fint_loc = np.array(int_loc).reshape(-1)
. V/ z9 `4 h$ Gres = df.Radiation.iloc[int_loc]$ _9 \! U2 T! S3 v% H7 H$ T5 d
res.index = df.index
' }! p/ C! S' _+ C7 yres.tail(3)
& @2 y; K4 i% A' J1
& p7 O! ?! t! x# @6 C7 i4 d% K2
( @, i* | e% X+ e3# h9 u# k' a, N/ C. t
4
4 f3 j# o# x/ c2 X5: V9 `8 f1 `: G4 N# d! O$ n
6& a- M! N3 c2 w
7: C. I2 L1 c5 K z+ p
# 纸质版上介绍了merge_asof,性能差距可以达到3-4个数量级" C/ T' h( p" @
target = pd.DataFrame(
) P* q: Z* W6 N( C {. u( W! ?( r2 G& Z" k
"Time": df.index.shift(freq='-6H'),: ^- ?2 k8 Z$ _! c$ h8 r9 w' ~+ E
"Datetime": df.index,
! Q) K- S; u! i8 o% l }
* H; y$ G, Y7 g2 s5 t0 C Y)) g1 z, T' i3 A8 L; x8 `
7 q3 A) ?# S; ~; |7 S+ _res = pd.merge_asof(7 j6 ?: N7 H+ p$ i5 g& p
target,( ^- C! Q7 {+ [
df.reset_index().rename(columns={"Datetime": "Time"}),3 _ \9 x: i+ k) ]! M
left_on="Time",
" o! ?- ~2 P* j" s* h right_on="Time",
+ B& U. W; O) y, w direction="nearest"% l+ l6 b& e' c; w
).set_index("Datetime").Radiation D4 e7 v5 m# N( Q
0 z+ t, ^1 Q# H6 O0 Tres.tail(3)6 N; X9 S* Z# z" Z7 f) V$ G8 j* V
Out[224]:
8 z4 a* |: Q4 c7 g. KDatetime
; K9 W5 j$ Z: r" p6 V3 G1 [/ v9 p2016-12-31 23:45:04 9.33
% [ j* p0 K' c8 ]; S6 R2016-12-31 23:50:03 8.49# }' I* }" o. a* d
2016-12-31 23:55:01 5.84
! ?) ^- C) p+ k" U7 FName: Radiation, dtype: float64
3 B X& t k) F" L9 x: w c5 i# `- O! A: V6 i" v1 w6 L/ |
1
0 t/ [! B e5 F' s# z& s2- D7 R" \. y3 V9 Z2 U+ v: I
3
4 b( q. \) l9 l1 |& @4
4 J8 }& W5 s0 h5
: h- d# I* Q2 u8 T$ D9 Y6; G6 A) S$ Q" ~* _" O) Q
7
& v. I. k1 H. k, K) F: H s* P8- |, Y* A; ~) J, T) |! g$ Q6 f
9
% J3 G% J5 E( W& b. ]4 ]10
0 X, V0 V& ~ M- j/ @$ _9 W11
& `, ?8 s! t# R( |4 s9 e120 W2 \( G4 A( V+ X
13
5 U! w/ C/ n7 z1 I14
6 E q/ u; g) ~( }$ Z15
/ n9 [3 @7 w+ z4 D* E16
3 a* |* q2 M# ?2 t& A17
; L* q4 m4 ?+ z6 u; `7 F& k18* A+ M1 e5 G( M, Y4 ]: B
19
! k5 g/ N. {' ]- \20' H0 F; K1 E' T& k+ p+ t% w ]
211 ]% N9 A* F" H7 x8 U+ w1 ^
22! Q1 R; r: D9 |, Z- p' r4 o
23
, A. Y! u U) A. cEx2:水果销量数据集9 E6 s8 [/ B# D9 X
现有一份2019年每日水果销量记录表:6 a: C' K9 _# |+ l4 ~
% O( T$ }1 q7 f; _1 R5 Udf = pd.read_csv('../data/fruit.csv')
0 k; s5 K+ k Vdf.head(3)( q7 S) w* W4 c7 a; U
2 E0 G" _8 q8 }7 q
Out[131]:
D+ P% i0 b5 p' A( w Date Fruit Sale( R8 ~- R. `1 e( _& b6 \
0 2019-04-18 Peach 15( Z( k) ]+ Q( o
1 2019-12-29 Peach 15
& t* ]& i$ }5 q) f& n3 P3 d2 2019-06-05 Peach 19
9 m. \- }/ ?" c' k1* {- N) d/ l( k% ~
2
' V# p4 P. }6 ?5 f2 d31 G; }6 L* J/ g1 K: S8 D/ H
44 |. Y& n" ?# T1 \0 Z
5& z/ f* ~* U4 v- m, V0 i( Y
6
3 q9 t( {9 K3 q0 t74 a( o" U1 r" v5 P) t
88 a& P; v& y. E. d: f, o
统计如下指标:* a* L1 X: U, A X
每月上半月(15号及之前)与下半月葡萄销量的比值
2 |8 S Z2 |6 X每月最后一天的生梨销量总和: V4 p, [2 k8 t/ D
每月最后一天工作日的生梨销量总和) A# l0 y. W0 h* d! P, K) t5 G
每月最后五天的苹果销量均值5 }, ^/ u7 t v$ f9 J
按月计算周一至周日各品种水果的平均记录条数,行索引外层为水果名称,内层为月份,列索引为星期。; u/ _; y4 n/ W9 q; e2 g) Q7 k- C
按天计算向前10个工作日窗口的苹果销量均值序列,非工作日的值用上一个工作日的结果填充。# `" q! Q* p C$ }
import numpy as np
, X. c: J0 q* {* j" i0 W! _1 Limport pandas as pd9 O% H5 ?0 t$ h6 q- f/ Y$ g, G9 }
1
, x) F- r( S: z6 R' f8 b; h1 x2' F8 H. i* n, @
统计如下指标:) n+ t+ r9 E- J- K I9 A; h7 l
每月上半月(15号及之前)与下半月葡萄销量的比值
$ O/ A) Z% o3 F4 T每月最后一天的生梨销量总和) s8 D# x5 ]! l( R6 o' V7 l( v2 b
每月最后一天工作日的生梨销量总和& l# h2 y6 x" X6 Q: _6 _
每月最后五天的苹果销量均值* q2 A- f$ E7 S4 Y( v
# 每月上半月(15号及之前)与下半月葡萄销量的比值 {$ X7 g5 D1 x. h
df.Date=pd.to_datetime(df.Date)1 H" c9 N+ z4 t# ]# I
sale=df.query('Fruit == "Grape"').groupby([df.Date.dt.month,df.Date.dt.day<=15])['Sale'].sum()9 l* b* K% s4 v; w0 q
sale.columns=['Month','15Dayes','Sale'] # 为啥这么改没用啊0 a: ^- w$ P9 _& [* y, t: v% t- C% D
sale=pd.DataFrame(sale)% Z+ d T, I3 C5 G# s+ O( X
sale=sale.unstack(1).rename_axis(index={'Date':'Month'},
1 Q! }8 E1 v4 ` r/ m- B columns={'Date':'15Days'}).stack(1).reset_index() # unstack主要是两个索引都是Date无法直接重命名
X; v+ Q4 V/ ?4 U3 M# xsale.head() # 每个月上下半月的销量( O* i3 x' S, N* L: g
; E( w3 m$ f$ M2 j. o$ X0 B% x
Month 15Days Sale
n" d: a7 }; Z9 j% V0 1 False 10503
3 K9 D& K& ]/ I( c3 x# _% V Z; `1 1 True 123417 g. L# q. T" B# e
2 2 False 100018 d2 Z- T" W. L! U; k3 B' e
3 2 True 10106
- F# b$ @. s, s k4 u9 o. ?; i1 B4 3 False 12814
3 Q8 l' J0 P, ]1 `' y
: f: E8 ^ z; b6 J# 使用自定义聚合函数,分组后每组就上半月和下半月两个值,根据索引位置判断求比值时的分子分母顺序2 R6 N! x7 u2 \ w/ j, k' v$ V: M
sale.groupby(sale['Month'])['Sale'].agg(
: H1 K0 t8 z. c8 | lambda x: x.max()/x.min() if x.idxmax()>x.idxmin() else x.min()/x.max()) z4 i f/ k8 T3 c; s! R9 `+ ?
5 i, }" s& x, v7 t* i0 y( |Month
4 {6 n5 Y4 F2 I" k( y( G1 1.174998/ r) B8 i! f4 _8 z( g5 v
2 1.010499' b3 H2 D8 p/ N/ D7 l
3 0.776338
: T, X) l: T6 w& R* s4 1.026345( V& T( y% X. _2 W7 [
5 0.900534
\3 q: I' b0 Z t! L3 k8 L# C6 0.980136
3 }# n5 y8 O) l6 D1 @" }. Y7 1.350960
: P% T* B& j* W4 R7 v9 a8 1.091584) j9 z3 U0 B4 q$ P, z+ v
9 1.116508
) m+ z& _0 [7 ^10 1.020784
$ p5 v( x8 [) r8 M11 1.275911' B5 i; F/ V" f* d; M( h
12 0.989662
, O) r* C- B) V9 H/ n$ \Name: Sale, dtype: float64) @+ R4 K2 A- U7 _, b6 z
7 s6 G' X5 X T" a0 u1
+ {4 x7 c4 Y! H4 ^. d3 {8 \2
: y% j) f( ]) W0 L1 |33 x+ L; v0 _+ l6 ?
4, E2 L+ w* g ^/ E
5
5 [6 `! K4 ?, X2 j8 a! e6! ~- c, m. D5 q
7
" c2 V6 h2 I5 X. C2 t4 N# H8
% ]' }& A" U1 P9+ B) Y" k4 H; w* O* b8 [8 Q2 P
10
" H4 P( b9 H- H& H3 b11
7 A- y; C( x- ]6 }! |5 J12, W5 O( X1 F! s" B6 l, [
133 F; \6 @0 Y: h: `
14 z3 D1 {/ {; Z; t; e7 z
15
( z( G0 M2 z. F1 n7 G a16
$ D$ n6 ^" O9 {; w1 `8 J17% c$ ^2 l7 x2 ^
18
N# l+ k/ ^8 T# U4 s+ n5 }# t19
7 |$ I1 O8 `+ t* L: L+ E0 d20& Q# G" G( n0 ?& d" \( e* ^7 ?
21+ m3 B/ b2 M) @! x0 J% B
22
0 g0 j% D1 e2 p6 s* @232 `/ g3 _+ x' L1 a1 V) T* Q- P$ Q
246 S/ l2 o' S8 K$ G' M
25
% Y& x. s7 p3 D2 u; W: E. t26
/ ~, w K x7 L27
/ m) \5 [, C8 F% q28
& M I R. z- S1 W" f6 v299 @. K& R# B: ]
307 k! d: o2 V9 P7 ^+ a
31
! n1 {, a, L8 g A, c/ O# F; J" v32
* n+ }& {6 j' p& u3 V. M33
) j! E: V) w: n34
; P5 g: A- v( ^+ ^7 {! q# 每月最后一天的生梨销量总和* o3 |- T+ J1 ~" P" u
df[df.Date.dt.is_month_end].loc[df.Fruit=='Pear'].groupby('Date')['Sale'].sum()* t1 z- b) R! Y8 k- T# @# G' O
, [ Z: J% I) n$ c' KDate
A1 W. o0 U' \1 R" }8 L7 R7 V) o2019-01-31 847( D" H2 @% v n* c
2019-02-28 774: k) {# P$ k, u+ h
2019-03-31 7619 ^# h# O2 I( M/ M3 ]# E I+ X
2019-04-30 6489 g: M6 u3 w* F& M$ E
2019-05-31 616
) n8 N; X3 X% a- l1
0 u' U3 r+ b+ K, t. K2
' I. t# S$ O# _30 y' Y n6 a5 T: f: R: M# i
4! ~/ L8 V2 b% ~) T& {, [& d2 S; ^! `
5) j% k/ d0 V# o2 E% C& H. W
6# {# n S+ U: K
7
6 a: I, O5 G1 R+ y$ r7 @8, c6 D+ M% D0 _7 Q# p
9
, ^' q) y+ Y1 H- M7 o2 N% e# 每月最后一天工作日的生梨销量总和
) u# f, A$ e+ w' zls=df.Date+pd.offsets.BMonthEnd()
2 k0 H% O t" a; L5 Cmy_filter=pd.to_datetime(ls.unique())
, d8 b6 ^$ O( {5 m! c9 Pdf[df.Date.isin(my_filter)].loc[df.Fruit=='Pear'].groupby('Date')['Sale'].sum() t: ]; W% Y" I3 @# ^
d# A0 R! u. p! Z3 d; _Date/ ^" j: U" s) a' L6 _0 A4 x% ]
2019-01-31 847, t0 P( W& H% t
2019-02-28 774/ S+ v1 k0 V9 a( m* m
2019-03-29 510- a' ^& Q/ k# P Z. v. g! Z" W
2019-04-30 648
- \$ F8 ]1 W& A; A, J! O2019-05-31 616
. n t, a! T/ E) @1
9 t! p. c4 _4 _2 g( {20 m* |( ?" u! a/ H% z6 c
3
% @: A" ]# H7 ]6 ?0 T+ b4
, K( A6 B$ O6 R8 Y; ^51 Y# e4 g4 y; c8 C) ~2 w0 |* @
61 k: Y6 d, m! s+ e" Y# K2 V& w
7+ h/ j% ^' _8 Z j" @5 p
8
; i/ K, K$ m" g' d! J' ~9
6 }4 t8 w, k1 @+ _10) p# U$ P4 E( y
11* ~8 l& z2 k; |6 ?% a
# 每月最后五天的苹果销量均值
+ g* k+ N! ~3 \start, end = '2019-01-01', '2019-12-31') P( r( d3 S+ T6 M& z* k
end = pd.date_range(start, end, freq='M')( E3 V1 ?6 u# ?1 R( s
end=end.repeat(5) # 每月最后一天的日期列表,重复5次方便做差
9 t+ R4 C" n6 ?8 `+ n8 _0 b$ K9 h) p0 r3 J$ W5 T5 a
td= pd.Series(pd.timedelta_range(start='0 days', periods=5),)
# t: ?' G: R6 n' Z1 m1 [td=pd.concat([td]*12) # 日期偏置,最后一天减去0-4天
, h1 R# C3 n" Q' }* pend5=(end-td).reset_index(drop=True) # 每个月最后5天的列表1 f/ s9 P* U7 p; {& d
3 k6 E9 P4 @5 j( d2 @) u( }apple5=df[df.Date.isin(end5)].query("Fruit == 'Apple'") # 每月最后五天苹果销量8 M. ]5 }: \- `+ P6 l9 x2 c; l! o" \
apple5.groupby(apple5.Date.dt.month)['Sale'].mean().head()4 m) Y; a0 _6 I( d0 h5 r$ l
: I" S9 T" J3 \5 {
Date
7 l, A% A M' Y, ?( H* Z1 65.313725 h0 A& t: p' O3 {4 E F- m" F* g/ T
2 54.061538! j+ I5 J, C6 p. f; ^( F- u0 O
3 59.3255814 F5 e; m5 a# T- I6 X' d- m, @
4 65.795455" s+ R+ d; q- ~& v; X( I
5 57.465116% a% Z* y* I# ]: e& m; d4 [' H1 H2 C
5 X( L9 G! i& x) h& u1 u$ m) M1$ e. g- H8 n& q& N$ p' |8 ^
2
X. ~4 U; ^1 C+ s5 j4 v) \3. q! g* c6 e& F' z
4& `7 a* `- Q' `5 L
5/ ^3 g& j W. S4 k$ S' \& ^
6
% N6 H- e% i. |0 q8 U76 Q; p( R9 P, _* m! w# e) O# `
8, O4 a7 L+ t5 H) i, N, U
9 w A c* b# U7 F$ v
10% H0 Q9 ]5 v& O# U" a Y4 M3 ?0 Z
11
6 B1 B U7 K2 i2 m4 G12
, V" v* E0 ~+ G13
) T: k1 k$ C9 G2 ~8 B4 D) m; d144 |$ @/ h0 m5 j9 n# |2 F
15- z% A9 ?6 ] B: ^
16
/ ?' t5 |& U1 z( N+ a8 e17. Y1 w5 u( e. ^3 k. P+ B4 R
18/ A, B! j+ U* J- x6 `, |
# 参考答案:& j* D; E4 `! l% ^. [
target_dt = df.drop_duplicates().groupby(df.Date.drop_duplicates(# b5 W0 G* c; m; J. o4 l) d/ n% K
).dt.month)['Date'].nlargest(5).reset_index(drop=True)4 V* K w8 N4 p4 N! ]7 s
& Z O/ z$ z9 _2 ^( ~res = df.set_index('Date').loc[target_dt].reset_index(
# k M) W' |; l5 H ).query("Fruit == 'Apple'")
6 i, \9 F6 r( o0 |! b( o- o$ K" A$ F \* |1 k- y/ M
res = res.groupby(res.Date.dt.month)['Sale'].mean(
, [- ]' A+ g# J& u3 F6 |/ P ).rename_axis('Month')- N' f& y9 U0 B8 z4 Q2 o
; ?2 E. a" s( w/ T7 h9 Z" m
: w- E% Q3 f7 t4 }2 [9 p' gres.head()
7 ]# z, C. [( n- ^Out[236]: 1 J8 H9 U; q2 z% q+ u, H+ g5 w
Month
1 {3 w# ^ M, k, q1 65.313725
! q! Y" o/ E' h* |8 {, s2 54.061538 G$ l- y. q8 }6 R$ h* V
3 59.3255814 J9 o2 j9 ?& Z
4 65.795455
" l2 d( ^& U7 t, `5 57.465116# k! i5 k. y/ l0 r8 ~" f$ A3 [
Name: Sale, dtype: float64: x- [( A: T; u; w; W
$ _/ ~8 }9 @5 y# u" R1 ?1
]( |- C* V$ k, `: d$ x28 e0 |, n* f+ @8 q0 k' ?1 ?
3" [2 Y! L7 f1 l# v
4
. U/ C3 z B5 @, \5% A7 r+ d& E A% ^
6
3 K8 f. O- v' ^+ g, d7
7 ~3 Z# ^3 S ~' ] Z- ]8 u# P* j( i- b6 m! {5 |" }
9
0 V9 S0 a9 h0 \10# ~% P6 i( ~" } P, l# s8 j
11
5 x R7 k7 X" Q1 Y9 [4 n124 k) ~6 l) h6 I2 I/ v
13
c6 x Z% _. A- U1 w14# q( k- H. _2 i9 Q6 k9 [' H' s
152 [* X3 c; W& Z4 t, b6 x" }" ?+ L
16$ l0 v' v% a: A$ T/ X, l8 }8 ^, X
17
% q" p8 z# G. X4 J: i* _18' y" }3 J: N1 c1 }$ K
19/ v3 e4 s' u. H$ l" o
206 G# m8 E3 \8 D& e
按月计算周一至周日各品种水果的平均记录条数,行索引外层为水果名称,内层为月份,列索引为星期。
8 [; U( k7 O0 W$ K$ [result=pd.DataFrame(df.groupby([df.Date.dt.month,df.Date. u, s$ W4 u" ^7 P) j- W, d& y
dt.dayofweek,df.Fruit])['Sale'].count()) # 分组统计 ) F) ~; n* }/ R' \% N2 ^2 B! `
) ?& z/ |8 J f0 t, n9 d# w* w: m2 `
result=result.unstack(1).rename_axis(index={'Date':'Month'},$ I! t( D2 y3 J. H. Q8 D% S
columns={'Date':'Week'}) # 两个index名字都是Date,只能转一个到列,分开来改名字.
$ ^* i( M6 _8 p9 ]result=result.swaplevel(0,1,axis=0).droplevel(0,axis=1)* @8 G1 s4 D& j
result.head() # 索引名有空再改吧 E: ^) C3 R: {
' p( a) P B3 o Week 0 1 2 3 4 5 62 b- V! v6 L( i+ V: s/ N) x; P8 a
Fruit Month
8 y i, f$ W9 H8 X* {1 A" wApple 1 46 50 50 45 32 42 236 |, D( ~! Q8 |# j
Banana 1 27 29 24 42 36 24 35
& h) ?! u* D+ {3 ~Grape 1 42 75 53 63 36 57 46; B; `5 Z [% S
Peach 1 67 78 73 88 59 49 720 |0 n( I" j. n: c9 }
Pear 1 39 69 51 54 48 36 40# Q v; |7 F0 U' L2 c" N) c1 \
1
L! j% T2 l; S2
. G j4 B* H% X3: ^' @9 @4 D# C: J8 x
49 J, O) s5 ?' g( E; e: G# @# k% M
5
8 A; Y- R$ B/ d# A( U) M6
' g! {( e8 f' y) i: w ]5 B G70 v3 K/ ]1 ~: r. z/ c( j. T( u2 l
8- m1 F. ~& M1 ~7 K4 O8 ?
9. w! T7 P9 [, W* I
108 J: m! d$ [% w; L
11/ B5 S& u, X7 W8 C
12
* h3 r! V& P$ E' v7 u13
8 u7 t$ v" m$ p14. ]9 ?( s$ N7 M% d9 A* H# h! `
15* s5 M( E- X! |" J' F% v
按天计算向前10个工作日窗口的苹果销量均值序列,非工作日的值用上一个工作日的结果填充。" `+ g1 B& Z. g+ d8 u7 J) C
# 工作日苹果销量按日期排序# ^- C& H% U0 B. Z% ~
select_bday=df[~df.Date.dt.dayofweek.isin([5,6])].query('Fruit=="Apple"').set_index('Date').sort_index()
7 X9 U& P. W5 Z8 R. h* P" c1 bselect_bday=select_bday.groupby(select_bday.index)['Sale'].sum() # 每天的销量汇总
3 E, k* h& U+ M* F5 s, D- Qselect_bday.head()" f. d, u/ c1 n
T; ]: L/ P" d* P+ rDate
3 D& q4 Z1 ^: T7 i9 A/ `2019-01-01 189 l' a# G: a7 Y1 R' Z5 t
2019-01-02 482$ N; k4 c# { {9 W
2019-01-03 890
) |( `# U2 \7 j0 G" e: j+ c2019-01-04 550" p4 X+ S' B( G% ^6 |+ H p
2019-01-07 494
( ~! c4 O2 Z6 R1 P1 l3 A5 A( m4 B8 H5 `0 d9 O
# 此时已经是工作日,正常滑窗。结果重设索引,对周末进行向后填充。# H2 [' t+ J8 J& k' k9 _ u
select_bday.rolling('10D').mean().reindex(df.Date.unique()).sort_index().ffill().head()
, ^+ Z. r2 g0 g; _) S0 {# U+ p! a- C. X7 X* J
Date" o z! r1 P4 y8 X
2019-01-01 189.000000
. W8 I$ e7 S! [2019-01-02 335.500000
! B' T; j9 ^: z# k( Z2019-01-03 520.333333
' S6 }8 |9 r. L( t( z2019-01-04 527.750000: V+ g& G. S' O4 s6 A; z
2019-01-05 527.750000. c" y* p) q1 m T
4 O1 l4 ?/ X \1 n4 S3 P, V9 K' X
————————————————
% [1 `' r. F/ B4 C版权声明:本文为CSDN博主「神洛华」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。* q: M/ Q$ t$ D
原文链接:https://blog.csdn.net/qq_56591814/article/details/126633913" h8 q- h5 {+ _+ T+ w+ v9 M
+ `/ Y" U, P# A* a: F
7 f. F% {" q7 N' A: ?1 t |
zan
|