- 在线时间
- 1630 小时
- 最后登录
- 2024-1-29
- 注册时间
- 2017-5-16
- 听众数
- 82
- 收听数
- 1
- 能力
- 120 分
- 体力
- 565659 点
- 威望
- 12 点
- 阅读权限
- 255
- 积分
- 174921
- 相册
- 1
- 日志
- 0
- 记录
- 0
- 帖子
- 5313
- 主题
- 5273
- 精华
- 3
- 分享
- 0
- 好友
- 163
TA的每日心情 | 开心 2021-8-11 17:59 |
|---|
签到天数: 17 天 [LV.4]偶尔看看III 网络挑战赛参赛者 网络挑战赛参赛者 - 自我介绍
- 本人女,毕业于内蒙古科技大学,担任文职专业,毕业专业英语。
 群组: 2018美赛大象算法课程 群组: 2018美赛护航培训课程 群组: 2019年 数学中国站长建 群组: 2019年数据分析师课程 群组: 2018年大象老师国赛优 |
/ f1 A. o G, G/ |0 O4 @; b( I: }5 |( ]( M7 [8 \0 h: P. g d
1 Q' z3 h$ {! X. K' i; [4 n
文章目录; [2 W+ k6 i V# Q( v, V/ b8 B
第八章 文本数据 M1 B5 z ?6 z
8.1 str对象; U" E& M9 e E1 N( f! g/ A2 Q* U: T3 D
8.1.1 str对象的设计意图* a r0 W# J$ x8 d6 I! `
8.1.3 string类型0 a3 X% u( p. l
8.2 正则表达式基础2 U5 Q/ j8 K h# P7 l' T+ c0 x
8.2.1 . 一般字符的匹配
6 Q! Q. e+ W \( y" Z3 c, r8.2.2 元字符基础! }; V0 Q! z( A' N
8.2.3 简写字符集, i/ Z& P: W1 H- b; O1 i
8.3 文本处理的五类操作
" ~! C& s. r0 D& i! ~" r- e1 ]7 ?8.3.1 `str.split `拆分
, l# `, {, ]2 f; K8.3.2 `str.join` 或 `str.cat `合并( n3 A5 Q3 c3 ^* w1 U: e# x
8.3.3 匹配
0 L: ?7 s2 C% V$ Y8.3.5 提取0 F9 z" ^# A+ Q! K
8.4、常用字符串函数; i" z- Q, h0 g8 A- u; c
8.4.1 字母型函数
' E) x5 G) A; {8.4.2 数值型函数
* i u+ ~. J: q1 s) V8.4.3 统计型函数( c) `" Q! }- R$ c
8.4.4 格式型函数
0 m( v) `0 c" ^/ m- m8.5 练习( {9 Z/ q. u$ I' b
Ex1:房屋信息数据集
) u |* ~2 v k) s- O* F4 YEx2:《权力的游戏》剧本数据集3 S6 H. _, ^# C" ~4 X% u
第九章 分类数据
# W+ t9 r! y/ `9.1 cat对象
" y% B4 N* |5 e, n1 L ]9.1.1 cat对象的属性
, U: ]+ [3 k+ S9.1.2 类别的增加、删除和修改, U& {8 V- M3 W
9.2 有序分类
% h$ r r* q" W7 J7 V9.2.1 序的建立
2 z: V( ?2 ]& L5 m* x( D/ A0 {9.2.2 排序和比较( B3 j$ V) J! ~: P: y; P
9.3 区间类别
0 r A8 [ Z, \+ S9.3.1 利用cut和qcut进行区间构造
9 L3 r, a4 ?- Q9.3.2 一般区间的构造) J5 Z; ]0 Q+ l' x0 e/ w
9.3.3 区间的属性与方法
7 ?* A8 T7 K2 y9.4 练习* d6 l" t. {' \8 C& F) p8 j
Ex1: 统计未出现的类别
( I2 D, _: M" oEx2: 钻石数据集
" N: I a6 Q1 V ?- M) {第十章 时序数据# U4 R. |) P; Z8 [3 d
10.1 时序中的基本对象+ ^& _2 Q. a# ^9 Z
10.2 时间戳7 B7 P/ t& z t5 m! x7 G+ Z- K
10.2.1 Timestamp的构造与属性
% X9 b- W* O* u: S9 \10.2.2 Datetime序列的生成, w( H% x- @6 C
10.2.3 dt对象2 R4 \! h$ y2 i9 i4 `5 A
10.2.4 时间戳的切片与索引
6 c1 H/ w2 X# E3 y5 X# N% T3 _10.3 时间差* a/ l: K" S* ~" S
10.3.1 Timedelta的生成
& K* U$ ^7 s+ ?8 |10.2.2 Timedelta的运算
, Y$ l+ K9 W( V8 Z. D10.4 日期偏置
" S* E( H; O8 T10.4.1 Offset对象! u3 m7 a' u0 V1 I
10.4.2 偏置字符串* I* Y; B: Z* S! O1 _; M1 @+ o y# e
10.5、时序中的滑窗与分组5 \& v3 K+ T+ S
10.5.1 滑动窗口% D/ h/ [' Q# z' G5 b* }; e
10.5.2 重采样
6 ^ M, O0 L$ b' ]& U4 Q) R10.6 练习
" ^: s, [# H% _* m- [Ex1:太阳辐射数据集
2 _1 W! w# r) n, h; x6 b5 UEx2:水果销量数据集( u# ] g# ^) U
课程资料《pandas数据处理与分析》、github地址、讲解视频、习题参考答案 、pandas官网9 x5 M+ {% A6 n& g+ R- |8 @
传送门:
4 j/ q* f( t3 p. J) b' q
5 t% Q/ K: S; J! m3 a9 I, r& F# A. edatawhale8月组队学习《pandas数据处理与分析》(上)(基础、索引、分组)
- u7 H% W/ n/ E2 ^# wdatawhale8月组队学习《pandas数据处理与分析》(中)(变形、连接、缺失数据)1 ^8 h8 |; h0 l4 w( y2 a& V6 \, i
第八章 文本数据
; j6 }' o: W: {8 c8.1 str对象& @2 E* z$ H( J# s* G7 k/ h- O
8.1.1 str对象的设计意图& J$ W7 Z3 d& \* F! E: p
str 对象是定义在 Index 或 Series上的属性,专门用于处理每个元素的文本内容,其内部定义了大量方法,因此对一个序列进行文本处理,首先需要获取其 str 对象。在Python标准库中也有 str 模块,为了使用上的便利,在 pandas 的50个 str 对象方法中,有31个是和标准库中的 str 模块方法同名且功能一致,例如字母转为大写的操作:6 [8 `; E" u+ G2 X2 T* S
& `" b+ v1 G e% Z$ O
var = 'abcd'
/ _% k/ _7 l' lstr.upper(var) # Python内置str模块
+ R! g* L% x/ S! dOut[4]: 'ABCD'
0 l3 P8 v0 A) i* j6 D8 w5 {# U& R) f
s = pd.Series(['abcd', 'efg', 'hi'])
1 C+ m& g2 |7 B6 O6 T: G
! W6 s6 {; `+ G- C8 Fs.str
: i' e8 q! P/ i; ^8 NOut[6]: <pandas.core.strings.accessor.StringMethods at 0x2b796892d60>
( u7 {# [3 i# Y
& r' P% c) V8 Y# d7 As.str.upper() # pandas中str对象上的upper方法
9 l1 T% p5 v/ R5 z; l9 ~% [/ u+ c! ]Out[7]:
^) b2 c. |3 j% }+ R0 ABCD& a7 r0 W/ g; w: {+ o% h! i
1 EFG" h2 f) o8 Z; V
2 HI; J" i2 ]6 k0 r" ?3 n/ G
dtype: object
+ Q2 o- B2 ?! x/ A1 |) B1
: n. y1 L( t, G% V! O2" p( s4 c" Y: h. ^( x
3: K* h! S7 e4 M; V* O! B
4- _6 c: O6 n; @# b' R. x9 r
5* N6 y) w6 @+ U' m/ g$ Q, |
6
2 w" w+ c$ J( @4 R" A, d7
. M) _& y( Y4 [0 B5 u89 H. j) y4 d: R4 m
9" F O1 [9 K# F0 l1 N8 }* q
107 B4 f; M2 {( E: Z7 ^: i
11
6 q: X5 G. L( R6 l2 e. @0 O126 y( Q5 J8 J; R
13
3 {( N3 c% F& J$ J1 E: P0 v9 @14. N$ |4 _7 ^) D, h, q/ Y5 s
15
8 L; X1 V8 U3 G! ^* D5 X: {8.1.2 []索引器
# H. F+ P* P8 F4 g! v 对于 str 对象而言,可理解为其对字符串进行了序列化的操作,例如在一般的字符串中,通过 [] 可以取出某个位置的元素,同时也能通过切片得到子串。
' U" `& g1 W, [1 Y pandas中过对 str 对象使用 [] 索引器,可以完成完全一致的功能,并且如果超出范围则返回缺失值:# i+ M$ G- z, {; [$ ~, d; w' ]9 ~
. W4 ^ i+ q+ G" v1 rs.str[0]
8 Y7 i% k2 @7 g) _Out[10]:
! R6 w+ q, S6 F- u( W$ D) C0 a
# ?) `& Z: W. l6 f& e) K1 e) I. I( t# V( J- D( |3 v
2 h
1 j6 L0 R' D7 @$ Ldtype: object+ J& w' X: T5 j/ j! h: z$ ]" H5 O) m
" b" d }3 z% k, D% b7 A
s.str[-1: 0: -2]8 E7 L3 G4 y9 Z7 z( \* O( c( `1 _
Out[11]:
[1 _& {0 J! j: s) R$ L0 db
4 O+ v5 }/ S' w; C* h( v1 e0 I D1 g8 _1 G! t/ h% ^: H
2 i
" s5 ?+ @: E, _dtype: object+ s% H' m& `# J% R, J- P$ A2 h. o9 C& ~
7 g- _& k# }% m3 ]3 v# _! }s.str[2]
4 S$ t4 E; o& e# OOut[12]:
0 { _/ j. M- b) S& v$ J* u0 c j* `, v% d p/ ]6 s7 e- ^
1 g7 F, }! P/ a4 w0 X' R
2 NaN
+ _/ c! ~6 I+ vdtype: object
" ^+ X" m, `4 n2 `" O) M' f3 ]: s: x- s6 @; C" O: q l2 D
1/ Z7 a& k6 Y) n: j* ]+ h
2; ~ N# r# A: e$ q
3. e( u* l3 ]+ ^0 X# @, o' X: x- A
4
9 ?) @1 O5 `2 U* h. R, Y5% x2 b; `" B8 E, ?
6
: Y2 ]1 ?" L4 }1 o2 s( Y7" @$ G# X' }7 l
8
6 [- `+ s ^0 F7 b95 Q- _2 {: i, q6 a2 \& i
10- S+ \, s9 n1 d
11
1 Y r& S2 I) ^6 p _12. ~4 I2 k2 [+ i# o
13: b. g! v+ k8 M5 X ~ j
14$ ^7 s* K6 S' K: V- e1 v: n
15
5 p6 V' H% I$ L16
) z; \2 G) b0 s) F4 b17
9 Y$ R1 N) |4 o( {& `18
|$ K2 ?& ^ G, W192 k/ K8 ]- j% T6 `$ p. k# Y
20
1 R+ s% T; W. Aimport numpy as np/ U# u2 G E* V
import pandas as pd) l( }' T% b0 _3 ]
% X8 V: `+ A2 e# x$ t
s = pd.Series(['abcd', 'efg', 'hi'])5 V4 a- X+ S/ G/ P- q7 t& o. F
s.str[0]# L4 @5 K8 t6 G
11 D9 n2 k+ T) q, E5 h
20 d3 a6 N9 Z' L
31 G! b% r9 G% T* q0 L
4
0 z) S$ o' _* {5: X F" c9 K. {. Q: Y0 x! H# H
0 a
y/ {2 f8 Z# W9 t7 ]2 }, c4 T1 e
; c: s8 [* ?0 d2 h
8 p8 `. k. A- L) `# ?9 b' J" V0 [0 {dtype: object1 l+ o w' F: r) Z ^0 E. w8 A
1 Z& U4 V" G9 d- U% q U
2/ r5 G' [( T' }
3
& G6 j6 t! S2 X A9 q7 l4, \/ o, ]4 L3 s' D% @) p
8.1.3 string类型3 K- T! A$ M t3 b' ]' U2 H m
在上一章提到,从 pandas 的 1.0.0 版本开始,引入了 string 类型,其引入的动机在于:原来所有的字符串类型都会以 object 类型的 Series 进行存储,但 object 类型只应当存储混合类型,例如同时存储浮点、字符串、字典、列表、自定义类型等,因此字符串有必要同数值型或 category 一样,具有自己的数据存储类型,从而引入了 string 类型。
% b% } t" r* ~8 r! q 总体上说,绝大多数对于 object 和 string 类型的序列使用 str 对象方法产生的结果是一致,但是在下面提到的两点上有较大差异:
$ V) z7 s1 n* O6 l4 R
0 o, {" ~6 S& f% F5 r二者对于某些对象的 str 序列化方法不同。/ X+ n! |, [) A4 a6 Y9 t3 W
可迭代(Iterable)对象包括但不限于字符串、字典、列表。对于一个可迭代对象, string 类型和 object 类型对它们的序列化方式不同,序列化后str对象返回结果也可能不同。例如:
# e3 c& E' O' B5 h, p; P) \s = pd.Series([{1: 'temp_1', 2: 'temp_2'}, ['a', 'b'], 0.5, 'my_string'])* Y% J2 R/ \5 D
s+ D1 I2 {5 J4 }# C* k9 k
1
F$ O3 I2 J P6 t' N H5 [9 g2$ |5 j( R3 }* s
0 {1: 'temp_1', 2: 'temp_2'}
" b% e' W+ ~5 f. ` { t5 Q( ]" I$ q1 [a, b]8 }2 n; c1 Z0 p
2 0.5
2 G) O" j7 x) {. j4 C0 C3 my_string$ [. }- m ]/ b* r3 ]" l2 x
dtype: object: P/ f- p( }2 Q/ }1 C
1: D, J8 C. h# c
22 T; b, M; ^" z1 u4 k
3& G1 @! n! c0 f$ K
4- l; Y# o' u1 ~" c+ Q( a7 h0 y
5
! h+ W( Y/ X% g) \2 q1 Gs.str[1] # 对每个元素取[1]的操作
% z9 q& ^( I0 ]8 N8 T* j% ]& H1
) h$ M1 {3 c( _ F/ l4 p, s0 temp_1
1 @2 y# Y) c6 @7 K" }1 b3 r3 h' S* C9 N# ]8 U! n) V
2 NaN
# h% ~2 h# r% F5 e3 y
5 Q: s! M( B5 X) t4 ]dtype: object% T6 P5 Y! q& L; Y- R3 \
1
& i% r9 K `) e. ^7 U7 @' Y4 O2* D, u: \" R5 j) v% q
3
3 G2 v+ X8 h# X; m5 T) T8 H2 \45 S2 a k& ?$ V+ i, O8 @& W' S
5 m, J+ _0 o; M1 ]0 i9 F2 J
s.astype('string').str[1]7 z* |- `, [, A6 N# H: d+ J
1
( M2 \6 u6 Y' O$ l0 1
3 K* g" `% p" q2 y6 Y1 '
+ e. H( q4 ?1 s. e; v2 .
A% j# I! x l% v8 T: S- N3 y9 [4 X" ~& \7 J V1 b
dtype: string! H% g: \. U* T' l/ B
1
" C- O3 s C1 F2 c! D s2 r0 e+ ^% i4 L1 e
3
2 D2 b8 @, G! p4& E8 u& w- b8 a) e/ S; \: Y
5
' b1 Z, n7 T8 e) l除了最后一个字符串元素,前三个元素返回的值都不同,其原因在于:) v7 V& i5 a- n) E" x% r; t
2 f% a- f- E9 r6 r0 r
当序列类型为 object 时,是对于每一个元素进行 [] 索引,因此对于字典而言,返回temp_1字符串,对于列表则返回第二个值,而第三个为不可迭代对象,返回缺失值,第四个是对字符串进行 [] 索引。( i ~- Q6 X- @9 E' { t" \( t0 l
string 类型的 str 对象先把整个元素转为字面意义的字符串,例如对于列表而言,第一个元素即 “{”,而对于最后一个字符串元素而言,恰好转化前后的表示方法一致,因此结果和 object 类型一致。+ ^0 y/ M! Z+ n: Z# G- H
string 类型是 Nullable 类型,但 object 不是
% o1 U" R2 `8 s' `4 j" O1 f 这意味着 string 类型的序列,如果调用的 str 方法返回值为整数 Series 和布尔 Series 时,其分别对应的 dtype 是 Int 和 boolean 的 Nullable 类型,而 object 类型则会分别返回 int/float 和 bool/object ,不过这取决于缺失值的存在与否。$ C, g+ X$ q/ p8 t9 d8 ~
同时,字符串的比较操作,也具有相似的特性, string 返回 Nullable 类型,但 object 不会。' f7 s: Y5 Z( S; |& `" O- K* R
s = pd.Series(['a'])4 V7 c0 q5 N+ m3 f
0 W2 l0 d; f1 @4 `- E
s.str.len()
5 K; a J+ A6 W8 R4 IOut[17]:
( M% h& Z' i. ^+ h+ N0 1
7 i# Q2 w& ]) @- p* x& a, ldtype: int64# H) E0 S, u2 y1 J
1 F; A( i" h5 @1 T6 As.astype('string').str.len()
+ b3 e; ]' ]& \" \Out[18]:
' X; v7 g+ ]4 R; c; c$ R0 1
f2 V, w8 G7 p$ D, F3 Cdtype: Int640 L# w6 W6 Q, v& ^+ N/ K
: K$ X5 m$ O# r2 {; D
s == 'a'
1 t. V* A, `/ n3 R' X8 IOut[19]: 3 W( d+ Y" s( l' Y4 v. j$ X _
0 True
, B! x8 J: A6 T; F2 xdtype: bool
9 b6 ]3 \. B4 A3 T8 P7 B2 p
8 ]. M) p' E6 f T& Qs.astype('string') == 'a'
+ H4 b6 }8 z5 V! H( j$ `Out[20]:
$ l/ l+ F. k! |' u. }0 True
' P4 v5 D$ H0 H8 R4 s+ _dtype: boolean
9 O0 | f# B$ D ~- A. V% |
x6 i3 n# }$ ~! i$ ]/ t/ Y/ A% S6 Ls = pd.Series(['a', np.nan]) # 带有缺失值) N+ U H8 L4 y# B S
: [4 L- D# ?" N7 K0 W/ ~s.str.len()9 W" f) C1 M" u9 K) T- b
Out[22]:
E4 L7 @& Q8 x$ }0 1.0# `3 r: Q9 P6 @7 P. R
1 NaN/ m9 M5 ?" M: _
dtype: float64; J7 z" R# X) L* B1 {" r
/ ?+ U6 |6 u1 x3 K: X6 @: V) Z
s.astype('string').str.len()
1 c2 p/ Q7 S1 n# s4 d$ Q* X- c9 `Out[23]: ! t* c% K B9 m5 x! r$ L, n
0 1
& @. k& ^! y* k2 w" m: P6 s1 c1 <NA>4 m1 M2 H# q7 Z$ h6 V
dtype: Int647 k5 B# @) f T; H* C
. f: X9 U0 D7 ?s == 'a'5 n( z F4 ?4 }" Z
Out[24]: , M7 r& x! a; M: ^' O
0 True
6 n4 X" \9 j, E6 r) W0 d& ?: _, {1 False
3 \2 N8 D9 |+ D4 o7 |# Fdtype: bool
$ U- S5 K( { F9 b: C5 G0 V$ ?4 l( C; B
s.astype('string') == 'a', g! r5 j; R$ D$ i
Out[25]: - v s$ \. {5 g
0 True
: j7 x' E. T4 |% F% o6 G' a1 <NA>* D! F* O. j1 `" c8 \$ q( {
dtype: boolean( M( q' \% ^! a2 k6 d) D
5 c# P) d7 n, I/ U1
0 P) B8 @+ y" k3 `* b2
9 z, n* K8 X1 m3* j9 k& [) s% ~+ t! {% c
4
* ^3 _2 v2 h% W! J4 g8 ~5
Z5 K' }& R& R$ ~6
! T+ O; l) {+ y7
3 Y% `$ x+ g2 b9 E8 n* o4 A2 o84 [# p \# `% U8 T: M! y
9
( ^; Z5 Q! i- Z: r) O10
, x6 W1 G0 S$ T2 [11
4 }6 q5 m' @0 y- p) A' D12
2 A7 G( r+ K& ]; ~- {! G13
@& u0 y8 [: n' I% ?14) ]& C0 V2 j" q/ B/ ]) v- v
15
2 _- E; ^; J s0 ]$ a* ]( c) G16" B% J& B! W& }$ a5 r0 k7 J. e' p
17; J% |# I: |5 H
185 ^& A; t8 V% ]; }, Z D) c
19/ W# p6 d* q: H5 F( C; B% w
20! O$ @0 J. X8 B) Y6 w* O
21
+ O1 N ~7 E5 m) ^& z& t- b228 ?6 N( j% [9 Q3 F
23
& q' d- y; H/ g. n% x! Q24
1 N: i3 S5 N4 \6 R# r1 V/ I. q25# v# F9 H8 y" `3 j
26
2 K3 g& e# A+ i$ c# s1 U) n, W+ w27
- i' ?/ f) A8 ^+ X! X- S8 R, s- t28
, ]7 i' p" t3 Z2 Y29
8 r9 I6 `+ L; g# q301 s) d* s# {( S% D
31; P4 h1 m1 }1 h7 ?# K% k/ g
329 p8 c" y) g1 j
33, d5 g% H5 ?; }* ?! B
34
" [8 L. [# l4 W |8 i$ _. F35
1 R% [" I; X8 }) f36% r, J! y9 V1 U, T' A: T' L+ @
37
4 \7 {5 T' M* v38
3 } y0 q8 m% n" `1 F9 E$ S39
4 i+ O+ J* T2 `. ]3 T403 n/ G. i, f- ~" @' a7 i3 `4 D& L
41
5 d# l2 r8 f! e3 f% W42
: p' X. N# k! D7 ^0 u7 n43
5 W, T h- k# \# w! N: Y( t$ P44$ o5 ?+ a# h" W: _/ a+ \* [
454 X, u1 K/ f1 b/ d$ n
46& t/ K- a; p; T' B- u: E- X) D
47# U- T) I* a/ g4 j- a
对于全体元素为数值类型的序列,即使其类型为 object 或者 category 也不允许直接使用 str 属性。如果需要把数字当成 string 类型处理,可以使用 astype 强制转换为 string 类型的 Series :% F3 Y& v. ^6 ~) v x
0 C, x+ s3 ?0 ]s = pd.Series([12, 345, 6789])
: K v. Q. h4 C3 ^) X* A8 l
7 k) _: i$ R+ r: P+ {s.astype('string').str[1]
9 f+ E& b1 w# p) l4 tOut[27]: % K1 h5 s5 j+ _$ I6 ?
0 2: y, W) c4 v6 F+ N! R5 t5 H, o: m
1 4
& ~; d1 a6 u% G& \2 73 C7 O3 \9 y. Y/ o
dtype: string
* u& Z, S, ]- J+ G6 r0 K1
, l( G* O& P" X0 W" u7 Z6 J( a/ V2
; G+ u+ ]6 Z/ @+ ]3
5 M5 H, a' u: F( C3 K4, H) \. X( ?8 \/ [2 s# Y' c
57 a* i+ T f; J1 h* I% J& D
64 H$ @- O" G- g! `+ |
7
" h7 `0 I& M+ u1 a1 X! I" i8" \8 D- K0 A/ F2 W! T+ a7 l
8.2 正则表达式基础
8 n2 Y2 ?& d- u0 L7 I0 f这一节的两个表格来自于 learn-regex-zh 这个关于正则表达式项目,其使用 MIT 开源许可协议。这里只是介绍正则表达式的基本用法,需要系统学习的读者可参考《Python3 正则表达式》,或者《 正则表达式必知必会 》这本书
6 z* I& z6 } Y, f/ L6 |5 [
+ e' w' F( j: h$ w- f5 m; e5 x8.2.1 . 一般字符的匹配1 z. @' ]" r k4 m
正则表达式是一种按照某种正则模式,从左到右匹配字符串中内容的一种工具。对于一般的字符而言,它可以找到其所在的位置,这里为了演示便利,使用了 python 中 re 模块的 findall 函数来匹配所有出现过但不重叠的模式,第一个参数是正则表达式,第二个参数是待匹配的字符串。例如,在下面的字符串中找出 apple :
3 D% `4 A K, p( F6 j
& t7 D- V0 t$ r" j) _import re, x0 J2 w4 i9 ?4 t/ \1 u! L4 m6 z
- o! n& X* W3 k+ g; ?, a0 k1 s
re.findall(r'Apple', 'Apple! This Is an Apple!') # 字符串从左到右依次匹配. ]+ _$ @' K& ?& |* @2 k, |9 r1 q, \
Out[29]: ['Apple', 'Apple']5 y: _; k$ q7 T9 m
1 A3 i1 H7 f0 Z+ a
2; e! j5 ~$ s2 f. ]# }' s
35 g V) H1 W. y, ]# e2 J: @6 L) k
4
, L* U3 J4 F2 u6 v3 B3 _% f8.2.2 元字符基础
! F7 }7 _ e' O, R: z元字符 描述5 w8 ?! h6 v; @1 { ], b
. 匹配除换行符以外的任意字符$ T8 A# d* e5 z! `0 d: j# [
[ ] 字符类,匹配方括号中包含的任意字符( n+ j: a, J6 Y7 W/ ~
[^ ] 否定字符类,匹配方括号中不包含的任意字符
$ o. ]9 C3 |# ?5 ^* 匹配前面的子表达式零次或多次
T# T8 k, h6 ^( c+ 匹配前面的子表达式一次或多次。比如r’d+'就是匹配数字串,r’d’就是匹配单个数字! m. H4 e( u+ H
? 匹配前面的子表达式零次或一次,非贪婪方式, T5 \: K; \0 e- e7 `% v. @8 Y: P+ I
{n,m} 花括号,匹配 n 到 m 次由前面的正则表达式定义的片段,贪婪方式( X$ }. \! i% f2 Z2 q& R
(xyz) 字符组,按照确切的顺序匹配字符xyz
8 ?; X: ~* y$ }0 G6 A| 分支结构,匹配符号之前的字符或后面的字符
4 ^. l0 y; m' E\ 转义符,它可以还原元字符原来的含义 d6 T, g2 i1 Q$ q5 b# q
^ 匹配行的开始
. Z+ y! r G. A* E) S9 q" t" A$ 匹配行的结束. X6 z! O1 O+ |; i, T
import re0 s3 S, h1 q/ J) s: Z0 w6 g& W2 A
re.findall(r'.', 'abc')) i4 j# Y8 r% Q0 j$ |- {
Out[30]: ['a', 'b', 'c']4 n! H1 A r( X0 Q0 y' {
" z. o* w) K. Y! m! m2 W9 M, X Gre.findall(r'[ac]', 'abc') # []中有的子串都匹配
, r3 M n# M/ E: m8 OOut[31]: ['a', 'c']
' l% L% O+ ^3 f4 u/ O' i( ~, p, D6 ?; y: H
re.findall(r'[^ac]', 'abc')
; }9 t& U7 p% n# j) C5 `& f5 a/ `Out[32]: ['b']. h1 Z; g! k2 W% u7 S
4 z3 N0 a! y" R- H* d8 n7 ure.findall(r'[ab]{2}', 'aaaabbbb') # {n}指匹配n次% U0 L( ?6 m0 G& R. {# T
Out[33]: ['aa', 'aa', 'bb', 'bb']
2 p. s% ~& _9 Y' Y
5 a6 T! M( x- R7 R" Q) U: pre.findall(r'aaa|bbc|ca', 'aacabbcbbc') # 匹配前面的或者后面的字符串/ Z+ c1 o3 J2 P4 w- O
Out[34]: ['ca', 'bbc', 'bbc']
+ a' R) U& a- d1 ?! c/ G. m3 w* ]- T( j5 p" Y
# 上面的元字符都有特殊含义,要匹配其本来的意思就得用\进行转义。. l: O8 e% ^0 r/ z& f8 V: E
"""
( O+ L* y2 K/ r% f3 h8 k1. ?匹配的是前一个字符,即被转义的\,所以|前面的内容就是匹配a\或者a,但是结果里面没有a\,相当于只能匹配a。; j/ ~( P8 h3 v7 t; `: R% }0 O
2. |右边是a\*,转义之后匹配a*,对于竖线而言左边优先级高于右边. n6 s6 X5 ~- j- Z2 b# r
3. 然后看目标字符串aa?a*a,第一个a匹配左边,第二个a匹配左边,第三个a虽然后面有*,
# t- q6 a, n l/ x! x但是左边优先级高, 还是匹配左边,剩下一个a还是左边,所以结果是四个a
0 a0 `9 ^9 C1 ~, l0 I. x9 V"""2 X2 i0 v# d% I+ S3 w+ M& M2 V5 M
# U7 h" }* W7 Y7 Nre.findall(r'a\\?|a\*', 'aa?a*a') # 第二次先匹配到a,就不会匹配a?。a*同理。
5 `; e3 i+ z8 a. C3 [( I# DOut[35]: ['a', 'a', 'a', 'a'] @/ D- |3 m% P, ?- M. T4 u3 c
! n; M a4 S3 A# 这里匹配不到是因为目标串'aa\a*a'中,\a是python的转义字符(\a\b\t\n等),所以匹配不到。3 z8 V( s& B' [
# 如果是'aa\s*a'之内非python的转义字符,或者'aa\\s*a',或者r'aa\\s*a'就可以匹配到\字符。
2 [( b9 X+ S X2 Qre.findall(r'\\', 'aa\a*a') , S( L, H d3 V2 k% l6 v! A
[]
3 u, i# n0 g- `3 C( K4 ]0 D6 Q0 }+ d
re.findall(r'a?.', 'abaacadaae')! }+ [+ ^) A+ m2 Q/ |6 \4 L
Out[36]: ['ab', 'aa', 'c', 'ad', 'aa', 'e']
: {# }: x+ r( V. `( r& b* t, I' T
6 X; d: Z. M3 {% e& d; N( C4 ]re.findall(r'(\w+)=(\d+)', 'set width=20 and height=10') # 多个匹配模式,返回元组列表! s1 g3 H; e7 ~, @% ]
[('width', '20'), ('height', '10')]- P% y+ `4 i; J
' B2 x& @; f/ I% t& i
1
. d$ L. p( u& h$ p" o$ I2
$ w; U6 c; \2 L; s) F3
9 ]+ F% Y; V; `2 W7 }4 D# g4: M) G* O- Q+ z& \
5/ h! s8 ^, f4 l6 D* a0 O
6: G5 R- H1 m x/ e! P ]/ x. O; H
7; m% H3 i0 }4 V! N# W' z3 C4 r
8
+ s/ s3 Z, m2 j9: \" e s0 ^! J* J$ R3 Z
10% @2 H1 F2 e ~: K1 R2 `8 T
11
2 b. I6 s- k I12# @" X8 r n7 h; x
13
6 a& F9 V5 U) y$ G! w14# n" y' P5 m/ O4 m( h
15
4 P* S* n" E/ W- D9 {% J162 @$ `7 m) e, G2 t
17
6 [) ^+ _4 X* y. s2 O18
4 C8 L3 P+ W' y, _# ~194 h8 |$ l- ]2 q1 ]& G9 S* @; D
20
4 p% D0 r U3 n" N21
% a. }$ [9 o( i$ s0 Q# k22
6 U+ q, u+ V4 E" i23
8 i2 E9 T+ j: R$ A( G4 x( R24
" Z3 J, O4 T8 @8 h( R254 S# A$ a: H q: o- @( t2 N3 i7 d
26, F% h! U; W" K5 A5 [) I" M+ p
27
( q2 Z% A% \1 i28
) I' M' t6 \" d+ r29
' s. Q' @; s" C$ p/ ^306 C5 x" r; u- c6 s9 u2 Z+ o
31
x& A3 U, j' b- h; c" I% P321 h3 z$ S: V) V" e. I0 D
33
; V7 V* \4 ~4 {( W0 x$ }& b' E347 r& v9 \/ M5 K% J& w
35
' Z9 ]. \3 _ n: S) ?& V36
% n2 z( g s# Z) j* P% B378 h; P% y" s$ b* \
8.2.3 简写字符集6 |, g, s3 \$ V0 H2 P, {( {' I/ {- n
则表达式中还有一类简写字符集,其等价于一组字符的集合:
1 ~9 P/ h# S$ A9 S# M) S5 l4 g% R4 \) z' Q3 O6 D4 T
简写 描述
. D, i, n, [0 Q& p\w 匹配所有字母、数字、下划线: [a-zA-Z0-9_]
6 U: b# F& L5 c5 Z4 Q& r\W 匹配非字母和数字的字符: [^\w]
( v2 U6 F- B u( _8 m% {\d 匹配数字: [0-9]
# I: v0 S7 n) p5 r\D 匹配非数字: [^\d]6 M1 T7 t$ T# M
\s 匹配空格符: [\t\n\f\r\p{Z}]( [% x, k+ k9 o, d9 e" n
\S 匹配非空格符: [^\s]
3 ?$ ^/ j6 E6 R9 W0 P\B 匹配非单词边界。‘er\B’ 能匹配 “verb” 中的 ‘er’,但不能匹配 “never” 中的 ‘er’。- E* p: p1 K4 ]: Y! ]
re.findall(r'.s', 'Apple! This Is an Apple!')# W: r' Y, d$ S( t8 o/ s/ s3 s) h
Out[37]: ['is', 'Is']
* k( t I- M w; `' z; \8 w7 p: d, c
re.findall(r'\w{2}', '09 8? 7w c_ 9q p@') # 匹配任意数字字母下划线的组合,但必须是两次* J" i/ R4 q/ {7 O
Out[38]: ['09', '7w', 'c_', '9q']
8 B3 n, b! C( L- D! {" @/ H1 b0 P, i1 `& ]0 g t- t: B
re.findall(r'\w\W\B', '09 8? 7w c_ 9q p@') # 匹配的是两个字符串,前一个是任意数字字母下划线(\W),后一个不是(\W)/ ?9 d( h4 C/ G( m5 @9 S0 _7 J
Out[39]: ['8?', 'p@']
- U6 {; E- U m" o6 T4 \7 v
/ r! T+ p3 w/ r: Y* R$ ]: v. s7 |, Kre.findall(r'.\s.', 'Constant dropping wears the stone.')& {/ n- t, u$ K& D* C
Out[40]: ['t d', 'g w', 's t', 'e s']1 ^7 X0 n3 z- ~8 B6 l1 e6 N/ [" i
% _( @1 ~0 u R$ r, D+ V
re.findall(r'上海市(.{2,3}区)(.{2,3}路)(\d+号)',0 Y5 d- r" ?5 v$ H" D6 @7 Q; f% R$ c
'上海市黄浦区方浜中路249号 上海市宝山区密山路5号')4 o: Z, t/ j/ o% m7 }7 q
2 s$ g* r o8 `+ ^7 O* c
Out[41]: [('黄浦区', '方浜中路', '249号'), ('宝山区', '密山路', '5号')]
! u, `: Z' P$ y; M: P( I
0 z3 J3 N6 P' P" r+ \1
1 Y) t0 f2 \% O0 Q3 N# K2. D" @0 {; U# O }, v' m6 \# @: e
3& g5 W8 ^0 m" A; ]2 K4 w9 _
4) X- Z0 S* z- p$ `: X, z
5" f/ r5 S( r( R' t0 G
62 Y5 R8 R, ~) |4 R/ ?9 ?
70 j" Y6 i( u( Y! ]2 T# u' F
8& R8 Q' I+ e* Q- K% M# k
9
/ K. \9 C% K. u10+ @. x' w [9 V6 {- j; _; g
11
4 X4 K2 H5 n2 c/ X- \12
9 w/ O1 D, J6 O! T. a3 L13
% I$ f1 a) F. L, }; B( G14
& K6 s+ W0 B- Q! U15
/ b- n+ J7 d+ p16: f# B6 H0 K W0 E1 n" v
8.3 文本处理的五类操作
, b. A2 y5 [0 ?+ ?( A8 y8.3.1 str.split 拆分
O. K- M# O' r( [ str.split 能够把字符串的列进行拆分,其中第一个参数为正则表达式,可选参数包括从左到右的最大拆分次数 n ,是否展开为多个列 expand 。
6 q+ l$ w2 K4 Y/ @( j" Y4 a# w- b( C4 R) X3 n
s = pd.Series(['上海市黄浦区方浜中路249号',
' c' Y7 C! z/ W8 N" g+ A* L& b9 i '上海市宝山区密山路5号'])$ L9 j5 Z3 R* j$ ]
* ~9 _' F- w) }! w9 b# e6 a; ^# }
, {( ]+ o Z" `1 L4 `; es.str.split('[市区路]') # 每条结果为一行,相当于Series
, \9 A3 z( J1 _ D kOut[43]: ' T1 O0 o1 W: m5 P2 a8 e5 ^
0 [上海, 黄浦, 方浜中, 249号]
/ W% @ W/ L6 }6 ]+ m8 n% I3 Z1 [上海, 宝山, 密山, 5号]3 t/ V; W' o/ S* p8 L$ U
dtype: object. h" l& i) d2 \# l8 Q' p1 N
( W" z. l# L& Ns.str.split('[市区路]', n=2, expand=True) # 结果分成多个列展示,结果相当于DataFrame9 a' w# j$ M; C6 w
Out[44]: 3 C \: ]/ H& W/ K. q
0 1 25 g1 ~/ P K' \( ^5 H3 ^
0 上海 黄浦 方浜中路249号
) b; p3 ~' O2 `+ M" M1 上海 宝山 密山路5号7 d4 y( C% x( U1 {* E+ e8 X: q
1
% s! A3 ^" |, O) c9 B2
' h9 X# w0 R) L$ S9 I5 _" e, t3
! E9 O6 i" N6 f j4
( c. t1 W! @$ P- o6 j. c9 ]3 G- n4 U5
% V& E0 k/ A& l1 o! M; @* Y64 P% A0 ?5 o; U
7
( e9 Z- n7 Y5 q1 W84 }9 P5 e* Y0 N2 w" R v/ p) }# r# I' ^
9
7 m% n+ E2 |5 S M$ b- C) h# P4 i10
2 V8 T3 t9 V4 h8 A( B7 ^- h+ x11
" h+ u6 @# K" E/ x12
; r' a( X1 Z, J) p8 ] {13! l: M' C7 @6 j/ ^& N& ]
14
0 R& l# d, Y {3 |, Y; R2 R1 B15/ W0 A$ G" n0 n" i
类似的函数是 str.rsplit ,其区别在于使用 n 参数的时候是从右到左限制最大拆分次数。但是当前版本下 rsplit 因为 bug 而无法使用正则表达式进行分割:
* M9 j) e/ M# O& C# v1 v- _9 G* Z" y) V5 M* k T0 v. ^9 ~. N
s.str.rsplit('[市区路]', n=2, expand=True)& o# a/ |* |5 ]. N
Out[45]:
8 O0 V! C6 e, J* C 0: V2 b% c& N; d
0 上海市黄浦区方浜中路249号
$ l# v7 _2 s: w: _: ~ {5 l& c1 上海市宝山区密山路5号9 ?( d W, H, Q& V) n M* r. K; t
1
# Z' ]8 Y# g. k& s2/ X7 i! e0 m9 r% {6 G, |
3, Q8 b, X9 V1 o; J0 t7 q
4
) G% u1 j( a: _. f: Q9 j1 L5
6 {9 k H- i' d7 b y% u8.3.2 str.join 或 str.cat 合并# z9 a; Q7 _+ N
str.join 表示用某个连接符把 Series 中的字符串列表连接起来,如果列表中出现了非字符串元素则返回缺失值。( s) S2 \% h; m
str.cat 用于合并两个序列,主要参数为:
: Y+ h. D- B/ b" x9 Nsep:连接符、 ~% R( M# f! J0 R& J/ H: A7 f$ R/ \7 U
join:连接形式默认为以索引为键的左连接$ i; h( V3 r# f
na_rep:缺失值替代符号2 i- b- D+ d' X- T8 {, G9 p8 L4 y0 ~
s = pd.Series([['a','b'], [1, 'a'], [['a', 'b'], 'c']])3 x9 g1 f5 \- w' p2 o4 o; Y
s.str.join('-')8 d4 r+ n. G5 Z# g
Out[47]: ' W% J8 z' C4 H9 f
0 a-b6 V+ l% T4 ^( I3 m. M& e
1 NaN, Q5 e8 D2 J8 L* n: D) [/ D+ g) j
2 NaN; {% A$ o4 Z0 p/ X
dtype: object
+ L' K: }, `( O) X4 N, B/ e% J; M1+ h5 S2 W2 p+ h5 C' o7 n
2
' E% ?4 [& a* i1 g; w32 J/ v* V+ I/ {9 [) g8 ^$ c4 f) k$ Q
42 x4 h$ i* G) E0 \
54 J/ y# L; M7 p3 _7 H& X
6, f- z0 T9 g9 v' A
70 N W7 O: |5 o/ y
s1 = pd.Series(['a','b'])
1 `, B* F7 Z( U/ c" ts2 = pd.Series(['cat','dog'])# g3 z6 s& k1 x
s1.str.cat(s2,sep='-')1 T+ p# w. G% |+ F8 D' I( a8 C1 T
Out[50]: 4 j6 x2 _: N) [) L6 `0 }% G
0 a-cat
$ z7 {: O D8 I4 ^3 I b2 T1 b-dog
+ r; b' A, ^1 a3 v( t" Pdtype: object$ q* ?! B( V* ~) t, |/ F9 z9 i
; }: w. s. f9 g% J- a! y) @6 bs2.index = [1, 2]2 `# H9 {% W6 v" `) }! L
s1.str.cat(s2, sep='-', na_rep='?', join='outer')
- m/ P0 ~9 r/ c% A6 @ d+ zOut[52]:
N+ e: D; Q5 l0 a-?
; d% x2 ~0 g( ]- T: J1 b-cat
' p/ ?2 d+ o- d+ |, e" Y2 ?-dog
$ ^4 c# s5 e6 r |dtype: object
7 n( l0 z+ |- v( h1+ e* X, W3 a7 r3 T
2
A! H$ \# R. ?4 O7 I* V3* c* @7 `" ~0 l. E% b2 T
4
, A8 w0 D1 j* w! w z5/ O1 Z& N" h* D" S" J
6) W! W4 {% J$ j, P) D. ]. }
7$ y; j+ O5 l9 y" U9 \
8
+ x" J1 {1 {; q8 K95 A6 m& o9 c! h1 F
10( Z, G9 H8 ^0 e, }, e- |2 Y5 \
11, k u5 ~- x3 w& W
12% h q9 a r b6 D1 S4 L5 i
13
8 [( V6 e$ J! w! {! K14
& L9 R" l9 G8 f3 X0 n$ @, f: g15: }6 X0 ]2 q6 m9 s' m3 c
8.3.3 匹配" Q4 s5 a! n$ M2 [( R( q" k
str.contains返回了每个字符串是否包含正则模式的布尔序列:
3 q# [1 ?; g0 Q" e' O/ P) g% ]s = pd.Series(['my cat', 'he is fat', 'railway station'])
; J `- d: h3 c+ Q/ K+ [s.str.contains('\s\wat')! w# `. C; U/ t3 H
5 r# e* D5 a: c2 L
0 True
1 Z5 s5 v9 O" |5 F9 I6 v9 Y1 True) `$ \, k0 \3 F- M2 j
2 False
" n( l9 t" J- G T3 o) bdtype: bool3 w. N& x) M% y, h C) X( g' H
14 z( w4 I& V+ ?8 c* y
2; @* w+ s. ~ A7 a/ r" [3 r
3
/ x. S' X3 Z# e, ?: W4 O5 `: T4
3 D1 P& Y# |* M3 {" H5
: A- K' H r5 k4 {6) J1 f! P' m+ ~6 G0 N- i
7
& c) r* H5 m9 X0 e7 p2 J7 Estr.startswith和str.endswith返回了每个字符串以给定模式为开始和结束的布尔序列,它们都不支持正则表达式:
/ h& K# A' ^- F0 d/ W9 _" ms.str.startswith('my')+ i X) x% U+ l, b6 ^. |
- u5 e1 j7 {4 B+ |$ m. i. `) {
0 True
( I& D1 }" S( w4 t1 False
3 g2 p- r1 q& @+ [2 \% E2 False6 g" h1 T8 s' z4 F# M2 q
dtype: bool
8 j) ~) n" U; u8 K% O1 M8 J1
3 ]& ^) c: M( Q! k- G5 J% ^2
1 Y- q- O, }3 V/ Y: n$ [$ _! w% E3
3 X, T, ]4 i" z( U2 {$ J$ o4' b& t. `4 M; i& t. b5 d& C
5
' F3 x. z/ z5 }1 N; a6' ^* R7 N; r f4 l6 h3 [+ W9 m
s.str.endswith('t')
1 g$ }7 N2 T# v: d$ E
: P0 X/ i% H6 {; r+ K0 True
( V2 G' j( D2 l' g4 V4 {1 True; X! n2 ?+ }6 f5 Z; g
2 False9 ?. H& i" q6 U8 v5 q) a4 v
dtype: bool
# G! g" f/ C% n( {" S15 h5 k8 U0 R7 c
22 S2 c6 \& M2 J
3
7 v; t" Z& X9 c7 |# B4 r( `- Z8 M3 t4 j. W( d6 g& ]0 O
5
6 s# F5 n; }, R/ r/ l: }& C69 V5 j6 \$ R3 V3 b
str.match可以用正则表达式来检测开始或结束字符串的模式,其返回了每个字符串起始处是否符合给定正则模式的布尔序列。当然,这些也能通过在str.contains的正则中使用^和$来实现。(貌似没有python里的search方法)
0 V3 e* g4 B) f1 t' Ds.str.match('m|h')" r% K) R. V0 N0 ]2 e
s.str.contains('^[m|h]') # 二者等价
* x# c% r( k8 h- W" W0 j' f/ m; z% l; e
0 True( ]( @. U. ? ]" _! E9 j2 Q. e) A
1 True- R! X- F# b$ B1 R" {3 n0 Y
2 False
# t# X% H m6 G5 }# x3 w% mdtype: bool/ _# P) l; R. s `
15 v- T, H5 u/ P5 r% G) Q
2' B9 }3 w3 t4 C; A
3
* t! {# r- e' v& b9 Q) E4# B5 P6 y% E* ]0 ~4 K% x
5
' ~7 `5 ^8 f9 q, a; t9 Z. D6
; f1 _4 Y/ y8 L% c3 @4 W7 l8 n2 l; I$ `# ^
s.str[::-1].str.match('ta[f|g]|n') # 反转后匹配
* k0 h, `3 l5 Q l8 H, x6 Z" \* Ks.str.contains('[f|g]at|n$') # 二者等价
9 N8 e0 ^4 X9 c! r9 {- C6 S% N* P- I* M9 k) R
0 False
z% e/ |2 r! d( k4 ?1 True" X1 U0 X' `6 `& b$ ]& w# f0 F$ Q
2 True+ Q0 {/ E5 ^- m* W7 Q
dtype: bool
) Z8 R6 p S! J6 ]. M; F1
0 ]" t; z9 J; [, D9 @2 Y# _0 l/ l3 G) P# d
35 ~' s2 D% l s5 U# F4 W# w
4
: j* }$ t' L- A0 Q9 o: c7 {52 X/ ^9 c) x; U. |
65 ]% L J: Y. T) ~0 h8 L' `8 p9 b
7& Y; {; m6 {1 C0 p4 O
str.find与str.rfind返回索引的匹配函数,其分别返回从左到右和从右到左第一次匹配的位置的索引,未找到则返回-1。需要注意的是这两个函数不支持正则匹配,只能用于字符子串的匹配:
1 G6 n; _6 @. \, ^- ^( Rs = pd.Series(['This is an apple. That is not an apple.'])
1 c+ ^/ T- }0 P1 s6 M1 e* i- ~
0 J* M' u1 b+ k1 \1 b8 z6 g( Qs.str.find('apple')
3 L0 M* q) ~. d( `! a9 HOut[62]:
5 j3 D; ], ^. e: v. R y0 11) ~" E2 N* j5 y0 S" F | Z" K
dtype: int64
8 o8 f N8 d9 ~! v# x8 @/ H; [- @# t9 \5 b1 H" W
s.str.rfind('apple')
' B0 l0 B! H2 ]0 j' H: c9 ?' n0 c p$ KOut[63]: # a; Z5 J' ~0 i7 V
0 33
; ?: M' _9 m8 |dtype: int64
$ q$ ~- s0 t$ v: i: j, h1
4 t- }, {: C! }) |7 Q23 M' }' J. U6 `1 I) u0 ^1 c
3
3 e; U7 E- Y9 t$ H3 U" q# e0 c' P* R7 z4' C) B1 r# J* F8 a+ _2 m5 \
5$ h/ D0 a; X# k- R0 G
6
p6 C" ~, r7 _5 {% G0 e6 b78 F; `2 e: y5 X6 G# d/ h- Y
8
# k0 Q* B; L7 H, t- F96 i) s. q, A9 b1 Y! U# {
10. p8 b u3 L, U# V# E
11" M( z9 D3 k6 G# I+ a
替换
9 w' z0 E. d6 D- v: cstr.replace和replace并不是一个函数,在使用字符串替换时应当使用前者。
( W7 C' u4 P4 h- fs = pd.Series(['a_1_b','c_?'])1 x) Z3 \; S, b& E I
# regex默认为True,表示是正则模式,否则第一个参数内容表示是单纯的字符串,也就是匹配字符串\d|\?0 A7 ]! l, y! ?: r* W+ V: C: L* F
s.str.replace('\d|\?', 'new', regex=True)
9 c) _* Z+ c# @+ |: f0 K* i8 `: X$ S1 E1 E" p# V
0 a_new_b+ _( Z/ @, x4 C9 T
1 c_new# D" c* A* v4 C" j
dtype: object4 d @% u, [+ B* A# x
1' o$ P8 p1 j) e& \/ c
2
0 A: f9 b" k' b) R3. O; U3 A/ c9 n7 T9 o6 h! O
4. B& j. O8 y) w. h. U& k
5
3 A8 i, C; j4 O+ K! m6
3 w2 t8 S, W M" v# Y2 L& {7
7 O; y6 o9 Y' j- x! m+ k 当需要对不同部分进行有差别的替换时,可以利用子组的方法,并且此时可以通过传入自定义的替换函数来分别进行处理,注意group(k)代表匹配到的第k个子组(圆括号之间的内容):& D+ M! O e6 s5 a3 g
) n# Z0 {1 x- g! c3 H# |5 @
s = pd.Series(['上海市黄浦区方浜中路249号',5 x4 v6 o( j, s) `
'上海市宝山区密山路5号',
# Q0 k) Q3 J* x) h, w* u '北京市昌平区北农路2号'])8 W% C- W1 b8 o' f( Z
pat = '(\w+市)(\w+区)(\w+路)(\d+号)'
; ]0 | n* E' y1 j* ucity = {'上海市': 'Shanghai', '北京市': 'Beijing'}, n; Y# k+ f6 u# ^5 R, `- f( ]
district = {'昌平区': 'CP District',8 J. b1 @( ]) }- L. X- R. H
'黄浦区': 'HP District',
' M9 x; E$ k0 N/ W4 ^ '宝山区': 'BS District'}7 L' e! B& P/ n6 j
road = {'方浜中路': 'Mid Fangbin Road',
0 y- e" M4 V; ?: G$ h* S* c '密山路': 'Mishan Road',! b$ M* F2 d8 Y0 L
'北农路': 'Beinong Road'}( [1 P# ?3 o! j5 z5 Q
def my_func(m):% @# ? a }0 g* V
str_city = city[m.group(1)]
; }( h+ m. g! k str_district = district[m.group(2)]0 f/ N" O5 E: A. _+ e! j0 z O2 A
str_road = road[m.group(3)]2 S K, x& d. {' k- S+ h6 N
str_no = 'No. ' + m.group(4)[:-1]
6 u C2 t1 X p1 f return ' '.join([str_city,
M D" z6 u" |5 ^3 H str_district,/ @7 R& ]- V. q- e2 g
str_road,+ v" M- p' A' p- ]2 I# ~
str_no])' A9 i0 n5 `7 H: x. P# F; \
s.str.replace(pat, my_func, regex=True)+ ^! S ]: u) r6 y9 p& I
) `3 |7 @$ m$ c0 Q' @
1
: h1 v1 a0 K- y. {, L2
7 {. O' q6 e! U35 o2 ]! K9 N$ Y7 n
4
6 ]+ f6 }8 P9 L2 C5 \9 @0 w0 P* M, p* I% R
6
8 d( \* a! G; `0 f' e; w) Q1 q7
/ W6 G7 a; h. l) C8 D& `8
# p) |2 p! a5 y& f3 x9" R, k: e" F* b
104 z C- t. G2 W, s/ W
11
# ]. l2 |+ T5 r, H12" K* I( V3 C3 z6 c* F
13
( v4 [. b6 `8 ~8 a6 c146 [$ e# i5 Q( a+ B/ T
159 |6 Z( P5 f/ _& M; b5 i+ @
16
3 m2 Y* V/ Z3 S; C17
) [9 Y4 [3 q' ~8 F- \- `18
: R, b; U6 S; J& z8 [4 s199 m$ w& i0 B! | O2 u9 l
20, P% E9 Y. ~( m- K0 ?1 f$ y. s0 m
21
1 _$ [8 N5 k; f% a0 Shanghai HP District Mid Fangbin Road No. 2493 c- I: ], B& ~: F8 _7 z
1 Shanghai BS District Mishan Road No. 55 P2 M2 j* K: ^' s" }7 Y+ r$ f
2 Beijing CP District Beinong Road No. 2
$ c1 [9 q8 |# idtype: object" ~4 R, \% e7 E% }2 [
1
' ?% ]. y6 p# _9 H# G2 v2
4 e9 D( s2 [- L! P9 B2 y34 J& _8 b( q0 a4 r
47 K7 {4 s$ g1 K% U; I/ I) N
这里的数字标识并不直观,可以使用命名子组更加清晰地写出子组代表的含义:, E6 I2 n# \2 O- l% P! J" [ I, B+ ]
. g r4 E4 Y) H" U' B7 S, w# 将各个子组进行命名
) W s$ `5 l* D* B4 Apat = '(?P<市名>\w+市)(?P<区名>\w+区)(?P<路名>\w+路)(?P<编号>\d+号)'
: Q' R& F8 X9 {2 U6 @% H/ Qdef my_func(m):& k: s0 j1 }% R! }, m
str_city = city[m.group('市名')]
# F# @4 z9 @* A. b6 {4 G str_district = district[m.group('区名')]
% G0 D5 m$ G4 d+ G5 N, H str_road = road[m.group('路名')]
. `9 r5 c) o; ] str_no = 'No. ' + m.group('编号')[:-1]
9 U- g m, K! _5 X5 v( |; ]2 t6 g return ' '.join([str_city,
. r3 W E7 [/ ` str_district,
T( k" S& _" v+ b1 P; e+ L str_road,
8 V: v K, A% D+ [2 l str_no]); G+ q" N ?0 g6 E' V: [! i
s.str.replace(pat, my_func, regex=True)( `9 H5 m5 f( y
1
6 E: [* X S, e/ L3 \; R8 L1 D2
( w( r$ l% H, r- `( z# a. B' u3: a& ^5 D% |' n/ O' P
4
( }! {* [8 {" F; N, b: L58 Y% J/ l1 u" s; E l Y* Y# g
6+ v w9 O+ r" N, G1 H& Y9 a% x
7
7 `" ^1 y3 L: k. w/ v% _" f! v8
) ]5 @. W) d$ [$ M1 }$ N9
1 Q/ S/ C2 P, t! v. ?! @10
1 \/ E) U& b4 I1 n8 A0 e, ]11- d' w' e( W% B! t4 A
12
+ ~: C/ Q5 v( @/ {0 Shanghai HP District Mid Fangbin Road No. 249
( C' E1 Y# k6 `3 @+ m7 f1 G1 Shanghai BS District Mishan Road No. 5
5 h1 s0 W5 V2 ?" y, p2 Beijing CP District Beinong Road No. 21 \& v: o) E% Z( T
dtype: object
; t, I0 b+ s! X. t1# f! H# V+ C( z1 `+ `( a9 `+ ^& {- T
2
8 F9 s: G* S. N4 v9 U8 f" M+ D8 u7 ?3( Z. ]3 f6 L6 ]/ s1 v) l
4- q" x3 E6 H& ^
这里虽然看起来有些繁杂,但是实际数据处理中对应的替换,一般都会通过代码来获取数据从而构造字典映射,在具体写法上会简洁的多。" d2 E% E7 S I) N, @. c' C: h6 M- N" ~
/ R% w" r" I! o" P5 T+ O
8.3.5 提取
! |& d# U0 l2 Istr.extract进行提取:提取既可以认为是一种返回具体元素值(而不是布尔值或元素对应的索引位置)的匹配操作,也可以认为是一种特殊的拆分操作。前面提到的str.split例子中会把分隔符去除,这并不是用户想要的效果,这时候就可以用str.extract进行提取:$ r! ^8 n% f& V! d
s.str.split('[市区路]')
, i. X0 E- u& K* m: QOut[43]: * N+ U" H! c$ B5 f
0 [上海, 黄浦, 方浜中, 249号]
% f; F0 P& {! ^0 l% @1 [上海, 宝山, 密山, 5号]
) Z/ |- ^& {- G2 R" g. y; `1 ?dtype: object
0 V+ ^/ s+ {8 m% t2 `4 ~, j
# o+ w, g+ }7 l5 D+ ^pat = '(\w+市)(\w+区)(\w+路)(\d+号)'4 A0 k3 S. h4 l4 M1 g# Y9 J
s.str.extract(pat)
$ X# m7 J7 ^2 q; }5 COut[78]:
4 ?) m0 U( D7 P. }% |1 d 0 1 2 3
" _% S) I o8 v1 v8 d0 上海市 黄浦区 方浜中路 249号
. Y! C6 U t, v/ r, z" n1 上海市 宝山区 密山路 5号& [6 d8 S4 @- d) y
2 北京市 昌平区 北农路 2号
- g9 B4 ^5 V9 a& z% i- A' x1
6 w- _# @( Y. k. Z" M1 ~! z: Z- ]2$ u* N% P& }) i- v. o8 f7 {# h& K
3
; C0 C/ A( F B' B/ P- r4& C) C, ?: s4 e
52 M, @7 U& e6 T9 v
6
' ^5 V$ ~ w A7
: B' |7 A! N. S% @+ g* n8
. p8 S2 }, W7 }4 C" \/ ?: `- B94 t o' N+ V1 ?$ y: U% C
10
% V: h; L& J4 G115 x2 a* D; R) @6 l
12+ w& w4 I/ ^) r3 ~1 K7 g
139 {9 R, E R7 C' N5 f, d: W6 D D' b6 }
通过子组的命名,可以直接对新生成DataFrame的列命名: T m. v' V3 Y( U7 _% u
9 O8 }5 n& }* b: b$ X7 C# J& n
pat = '(?P<市名>\w+市)(?P<区名>\w+区)(?P<路名>\w+路)(?P<编号>\d+号)'
" G, c- i, \6 d- u s' Zs.str.extract(pat)
( C% `4 F! { }3 X4 b8 SOut[79]: - Q3 o$ b' T) Q
市名 区名 路名 编号! n) {% L& a! o- D1 j
0 上海市 黄浦区 方浜中路 249号5 S9 t) Q' O" p2 d0 U+ E; r1 ^5 I9 o! N% t
1 上海市 宝山区 密山路 5号
% d- S6 x7 S2 D! i+ l+ s6 e4 V. Y# t2 北京市 昌平区 北农路 2号
& k, e% l! B) M6 r3 P! W& X1# D9 q. {. N( Y- V
28 R( T# x! R; w+ J0 |
37 a! V" p w- U4 E1 l
4
) m. P, ^6 _: m5
# L1 C9 Z6 x* d. b4 m" _! d6( r5 Q& d1 P7 D9 T K$ m# \0 R
76 G. S& P$ `4 V: p" Y
str.extractall:不同于str.extract只匹配一次,它会把所有符合条件的模式全部匹配出来,如果存在多个结果,则以多级索引的方式存储:
/ [9 m F# n/ `s = pd.Series(['A135T15,A26S5','B674S2,B25T6'], index = ['my_A','my_B'])
# Y9 o1 }) M4 G3 z. Npat = '[A|B](\d+)[T|S](\d+)'
3 y' @& D. q' L. i- |( a* Y0 ^s.str.extractall(pat)
" @- R6 _* y, g; {Out[83]:
, Q. I* }3 q! c5 @) z 0 1
]0 x: e0 X$ @8 } match & j7 f1 \! |/ n, }$ [9 X( i
my_A 0 135 15
2 Y) k$ x- @8 w 1 26 5
) @( R) I7 s( D4 |. }, u5 @) qmy_B 0 674 2
4 i. s8 q+ f3 {$ V( v8 W7 p 1 25 6
- ^3 C# ^: x S4 u; O! U% p10 d2 ^, W: P# q( R
24 R3 R2 f8 K# e
35 y7 V8 w! B5 R" L! O0 l' T0 c
4* B3 n. c. |3 ~( N+ Y; v9 N9 B
5
7 R1 c! F. o4 b" p( f2 {2 V* V) i6
- E o: x( [( w: [7) T- j) R) G' H. ~8 n
8' o u3 P& b( ?5 |
9
: F0 V, H5 f9 @1 m" o* ^! Z10
$ L @9 {; {2 L. o/ tpat_with_name = '[A|B](?P<name1>\d+)[T|S](?P<name2>\d+)'1 _& z: x% Z6 h4 ~ R, y7 E# ^
s.str.extractall(pat_with_name); ?8 C, k5 j t# [, ^' y
Out[84]: 7 W1 S: V' E) ^( ~! a
name1 name2
/ ]7 `3 D. F; Y* H) ^2 s match
# u" |/ K. r, i1 Bmy_A 0 135 150 g; \5 ^. x" E
1 26 5
' f& p* C& y- N% N+ P1 q. amy_B 0 674 2
% X$ ~& c+ j* @ 1 25 6
% n2 s' }/ `/ D( ^7 k, x0 V1( \2 x% t @' t. ~
2 h( C: `! |# r$ O. c- Y7 v
3
$ a8 R8 b# P6 @7 c0 p9 t/ K9 D4
# d2 B6 _- D" W% m# L5 ~2 J55 e/ D5 g/ A+ u( W3 i
65 ~8 y/ P8 C$ K; c
7
3 I( z( ]- t8 T0 V' @88 u7 o0 r( p7 @6 J- N/ A- B! [
9
& t; q2 [/ n7 n2 p1 x/ Ystr.findall:功能类似于str.extractall,区别在于前者把结果存入列表中,而后者处理为多级索引,每个行只对应一组匹配,而不是把所有匹配组合构成列表。4 M4 e# _ q& b& [4 r( G, X) P
s.str.findall(pat)! G& @* A1 h$ {9 ^( y9 a0 {; B3 v
1
4 F/ C: L+ e- mmy_A [(135, 15), (26, 5)]
# ]" Y+ K5 a- a- d* b! D! Jmy_B [(674, 2), (25, 6)]
/ }) a7 s H9 s) y; j2 @dtype: object6 _! X9 b" N, R7 ~! b
1/ Y5 u0 W( w% L
2$ i# @1 z' N( O, q0 V4 x
3
, g! R$ |" {! R- C& z6 I6 D8.4、常用字符串函数
! ~) c d# n" p6 u; d) | 除了上述介绍的五类字符串操作有关的函数之外,str对象上还定义了一些实用的其他方法,在此进行介绍。
2 N& q. g+ f# b# f3 r- Q
5 e# p% Y% M* u5 n8 Z, m5 E7 b+ `2 X8.4.1 字母型函数
$ r, N8 @* s! d1 C8 @ S/ o- i upper, lower, title, capitalize, swapcase这五个函数主要用于字母的大小写转化,从下面的例子中就容易领会其功能:+ D7 H9 r5 o2 n
5 D0 `2 }! ]9 ks = pd.Series(['lower', 'CAPITALS', 'this is a sentence', 'SwApCaSe'])( A/ D- g( d7 H; d6 m' O* c
. V% {1 W: _" A2 J& f: {, C
s.str.upper()5 ~% N$ H/ e! R3 i/ c0 H
Out[87]: , A1 ^# @" c- j% v5 s; T
0 LOWER
! q) J6 D* h8 P; k. D( i1 CAPITALS" _% q! O8 X1 B* q
2 THIS IS A SENTENCE
9 s9 _) e3 y1 [% _) s# U: H3 SWAPCASE
4 z4 d0 C" U* D9 K$ idtype: object
9 f1 Z/ d. o" [7 C/ q! }+ |% |4 X* S" n$ S( ?! v( Y7 D a+ d
s.str.lower()
7 ?4 p9 j! a) g$ ^Out[88]:
5 V/ R8 Q: r& v, d$ t8 n8 m0 lower
1 F6 `3 H) l( L1 capitals$ n$ {& x' Z( j' b) C
2 this is a sentence# {3 G* Z5 r G! X
3 swapcase0 ~2 @1 n0 Y! A) G
dtype: object& @. w' r1 i |* g$ O# O' X' V2 ~
- f) l. V3 a$ g. H, \
s.str.title() # 首字母大写! G8 U, `) c) Q
Out[89]: ) C" O. D6 D- t1 t- r; o8 l3 H/ ~: {
0 Lower
- k7 I3 j. M$ c) z1 Capitals5 k$ w s e. W' k# b8 h
2 This Is A Sentence
! ~4 }* w1 a1 u- O3 Swapcase
' e* P" R: }/ Y! y& {! C+ ~dtype: object8 [ d ]& l$ X! b$ c( N+ ~
& n3 ^) [+ o& E7 F' is.str.capitalize() # 句首大写
8 i* \# K5 C ^; U& x. rOut[90]: + P7 D. r# K/ U" s+ W- V" N4 t' d
0 Lower
% e! h7 f1 j7 u" s8 s% F z- B! N J1 Capitals
- `8 E5 _( ^6 }5 \1 O2 This is a sentence
3 j7 G& _6 L$ y5 Z, o; Q3 Swapcase+ f% ^+ u5 O3 }1 j6 v* s
dtype: object
$ Z7 h. Y1 A4 a
" n% V1 D( \' T: V" s8 L: \% `s.str.swapcase() # 将大写转换为小写,将小写转换为大写。
7 }7 a- z8 h. b* Z; `2 c' g% ]Out[91]: ( m) w9 a( w0 k) `& `, c0 p3 i
0 LOWER
8 X+ w, l$ p) W+ k! l3 d$ o9 H1 capitals( ]1 V# ?2 N1 N! @0 B1 i
2 THIS IS A SENTENCE
/ T# |+ c5 d( q3 sWaPcAsE9 P; o' `: S- |
dtype: object
& {! q3 r8 [# v( v) m+ ~# h" l0 m' c
s.str.casefold() # 去除字符串中所有大小写区别% z) z% L, t8 Q) \& k) F! v
' H" S% k/ \5 f/ p" N
0 lower
: U* K2 y5 u. U- ]- {1 capitals; l7 ~+ o2 o( i& d7 E: f
2 this is a sentence
4 B3 |0 g" u& ]3 swapcase7 S; X) p( f4 `- A, N& N/ {
- x; P9 p! i s* G$ L1- a7 Y/ S- s/ [3 h P8 ~3 N
2% X7 C- W2 {% b! r
3
- n6 Q+ T& V( X$ v, _4
& w/ r0 I0 u$ X5 b5
& ~# S0 s+ }5 _' V- e, m& S6( L7 O5 q0 A$ L. e, ?4 F( a
73 |% G2 M- B% `
8
" A |% U8 o1 D+ D# P9* u5 |2 f" I( m9 O! K
100 V/ i' ~8 m. ~' @# [ @
11
! q6 B$ I( }8 C. ]& I' L9 Y12) V$ u' U: q: M7 T
13
* w4 b8 ~$ N$ }2 ]14
0 b6 F# U0 f0 P: w* s15' m/ V f: K" X8 Z( s
16* _+ A7 b" m9 P0 b* Y/ E
17
" t* G+ N, o- z) c18
! O1 ~$ x* D7 R& O S" z1 D+ \" d198 k( e" k/ {* D9 v, N+ h8 ]
20
" C0 y& j6 E$ e/ r: @21; ~# Z2 f* u$ `+ j% h5 }
22
9 |. T h7 g$ p5 h: O3 f23( c% e- F1 J3 w' n7 n: A
24) H3 C% E. U+ b( q
25" ]) W" y; c* m5 q' D8 V$ Y" G
26" s6 X3 Q$ G4 O/ J5 a2 L
275 G; S) \- m: s7 m. `6 ?
28
3 _8 e: T( f+ c+ S29
) k# o+ I4 v- ~* o; q; t$ r30+ [& M2 q8 r) O/ B
31' O" o- w% \0 b9 r
32
; }3 `) `. u# X7 z33
+ W9 U8 _1 |$ i C e Z34$ S( i5 E+ X4 F2 V2 r2 c0 A8 }
354 W+ b/ {: n2 e7 e
361 `9 W4 z8 U& Y, @" k
37
$ j4 t7 H' E6 M+ P( G% B38- l2 g/ o/ F5 Q
39
" I; b5 g) V7 e3 ^% S40
8 r$ B: D" Z1 I41
1 W& B& L( C. p- i42
; P3 { B3 ]9 `43
; {' f* T' O; \1 k44
! R: f* i' `$ E; Q1 ^8 I: X45) i0 f" C# @1 W) W% N( L$ A. V- o
46
: [6 ~! {( J1 ]$ I z: w47
3 ^' `% x. W5 | H' A, m48
0 e$ v# x* a R6 o! ]" R J8 N+ X8.4.2 数值型函数, N S. {$ w; \7 [8 s
这里着重需要介绍的是pd.to_numeric方法,它虽然不是str对象上的方法,但是能够对字符格式的数值进行快速转换和筛选。其主要参数包括:
( S4 Q6 o+ h) d# c% \, _
) S8 r2 z; H4 i2 Cerrors:非数值的处理模式。对于不能转换为数值的有三种errors选项:
d" t) k9 q: W- v& C2 Z7 Fraise:直接报错,默认选项* Z( e* q8 ? @* o& S( i2 M6 y
coerce:设为缺失值
5 s* g3 m+ B9 H5 h$ Kignore:保持原来的字符串。
, G; O) X7 Z( N/ cdowncast:转换类型,转成 ‘integer’, ‘signed’, ‘unsigned’, 或 ‘float’的最小dtype。比如可以转成float32就不会转成float64。% `6 {+ ~+ m1 Y7 ^' q
s = pd.Series(['1', '2.2', '2e', '??', '-2.1', '0'])8 S$ M: p5 Y1 A
# x6 q! J; ~6 d H
pd.to_numeric(s, errors='ignore') o) |( A/ @) q1 r3 D5 I
Out[93]:
" ]* s5 N6 n" H0 1
0 _5 N5 d! [3 {% l B- ?6 @1 2.28 S7 f0 H- k0 L( K0 o# r3 f
2 2e
& B+ h5 t0 w9 M1 J M' R3 ??) I9 `5 a& \ H! b
4 -2.1
: F8 F' N2 v F5 05 z5 |+ Z* ~ Q5 g. R; Q/ I6 w
dtype: object
* [2 A9 T2 R; T+ ~. v- ^% F! v: Q3 W* b. F. K, T/ X
pd.to_numeric(s, errors='coerce')
, j2 o2 y4 g3 O: ^ D& I" ^# AOut[94]: 2 `2 t& P5 W4 W( v
0 1.02 P0 p: {+ E. k6 c/ s/ d* v2 _
1 2.2$ `) g1 \8 k1 E$ k
2 NaN G( t& Y7 t/ k) ?
3 NaN9 G! F+ V, F/ z% }
4 -2.1
. C+ T7 x" T# m5 0.0
9 j _8 P7 ^3 Q* M+ h% zdtype: float64
$ r. x. u- k5 G" G9 D* a
3 a0 A: W% l0 E, \1& e# }4 `4 j0 u9 Z* S6 b7 h: V% L4 ~" H
2
- |& v) }" H: B2 B5 O9 v- f0 y3
; j: W0 H( V. R: A" f7 B. D45 i0 H. x6 e$ [. I+ o
5
% e# x3 c a" e6
' ~) r; v6 Q) b$ J75 a s+ x+ i) \; H4 ^. ]# t
82 Y$ k9 m/ @6 ]; R; K8 M3 V5 P
9
9 k# h8 Y9 U( D6 Q7 B; F' r10$ _7 Z# E, k4 _6 H
11
; o0 v1 W: z- w% }12
8 a7 [/ t4 f* m; J1 F* d4 Y13
. f% o+ H$ ^% X: v14+ D7 V2 `5 a& b; E* b7 C# ~
15
2 \" o. Y: X6 Z1 h- r2 \16
5 Q6 `; R" [1 R6 O) w17* f5 ?: z: ~% `! v: ?
186 K" f& q' i1 ]3 a2 _+ w
19
3 Y$ A# A9 n1 {" `' s20$ L1 Y9 r `* _ q- i: z3 s
21
7 @9 Z, ~# `4 v: m) ?. h 在数据清洗时,可以利用coerce的设定,快速查看非数值型的行:
2 G. T+ v: j P6 h. [+ t+ e4 T. e2 u/ a. f' p7 n1 `5 ~# Y7 Y
s[pd.to_numeric(s, errors='coerce').isna()]
6 S- ^- u- s7 ^1 w( [0 MOut[95]:
Z9 X: k; L, X) Q4 G: O, N5 t2 2e; Z# a2 i/ D) I# a: S8 F( c
3 ??: o* N3 s* k) @- m2 l
dtype: object
' z. K3 E# O1 V T8 d! m. x1 x1
m l0 Y: w0 G2, z$ h2 `8 V( Z" U- U1 O& `# P# M
3
2 N: j/ i$ @# N8 O4
6 r- h6 R& d1 _6 {5# j$ [2 |5 P* g; v
8.4.3 统计型函数# l% i0 }" a m8 j3 h
count和len的作用分别是返回出现正则模式的次数和字符串的长度:
! l8 T2 c+ `( |8 e3 {0 Q
6 Z$ R ?- N: S% S5 n0 P( ^s = pd.Series(['cat rat fat at', 'get feed sheet heat'])
8 G7 E; b5 C0 S" p0 v
: c: |8 f3 r8 p; Bs.str.count('[r|f]at|ee') # |左右两种子串都匹配了两次
- u* |6 G! Y+ fOut[97]:
8 F- V# R" i# d% Y; n2 F0 2
0 ]: Z* Q* r8 E8 S1 2
, p; f9 ?6 o1 d- ~4 |7 w% }dtype: int64
8 M* J* K( F6 e& g0 Z
9 S5 D. B- i1 ~( ns.str.len()
: p+ c2 x* S' @3 c& \Out[98]:
, D" A# s1 W: C0 D x! @! A0 14
& {/ k& Z3 F3 ]9 m$ B$ v7 {1 194 K5 N6 a- }- U9 H. Z1 z& L* H
dtype: int64( w( C) D! [: l$ v F
1
]' } i; |% ]2
. S0 Z j* ?2 \, W$ h9 A0 n* _ U33 U5 ]5 L0 V' b* L/ j" |
4& W* s2 e& x8 i# y& V
5" k5 E6 k, s; g7 O" ?
6
% f. K2 k5 q( {6 ]" [& A7* A0 F+ m3 d5 ^( N' B. V v& T
85 b2 V- s8 C# C. A" ]* r
9, ^ [6 r& T! L+ i, y. s
10
/ [ p9 J* x& v N/ E114 ]! B$ l! A; P5 ^* b
12
" Y7 c$ b) I- N9 ]$ q0 m0 `13
2 W7 x* W! x: T2 D: s. Y$ p; a8.4.4 格式型函数- h4 r3 u- d* K" T& t
格式型函数主要分为两类,第一种是除空型,第二种是填充型。其中,第一类函数一共有三种,它们分别是strip, rstrip, lstrip,分别代表去除两侧空格、右侧空格和左侧空格。这些函数在数据清洗时是有用的,特别是列名含有非法空格的时候。4 x8 F1 D$ ^# [; g8 Z; v% W9 _
4 U& P! M) J) r* u) v* I! `# q( G
my_index = pd.Index([' col1', 'col2 ', ' col3 '])
) O: J* n5 y8 g7 j! {+ k2 |9 y, Y7 ~2 M" f2 |) i: {
my_index.str.strip().str.len()
1 U: B. K0 L. y5 V b3 K5 {Out[100]: Int64Index([4, 4, 4], dtype='int64')
/ E, f+ G% t) R o F+ s* z# i! j$ H' c0 K! n
my_index.str.rstrip().str.len()7 d- g, K/ }6 g. d9 i e
Out[101]: Int64Index([5, 4, 5], dtype='int64')/ \5 I1 D3 O+ Y9 _2 m3 Y# F
5 J+ {$ |# @) ?) C8 H& W9 f! n; rmy_index.str.lstrip().str.len()
1 B, I/ z; V# M3 h' c: KOut[102]: Int64Index([4, 5, 5], dtype='int64')
" s6 \* E& f; L' D( H" |1# @1 k; l" K" f: b" U
2
" C D6 K0 e- w5 W* ^7 I O2 F38 a5 |* d% S7 B/ Q, o
42 S3 D; m9 ~" O
5
( r7 c" ~$ N/ w69 i# c' N: ?( ^2 ]! D. f" Z. H( f
7
a0 e* z& ~* Z- q- T% i c8! G$ A' w$ p! ]5 [7 F, ?/ D
9
k: o6 A2 f4 u; t5 u10, I0 g( a8 V3 C5 F
对于填充型函数而言,pad是最灵活的,它可以选定字符串长度、填充的方向和填充内容:4 w( f6 i: [$ x6 M- D6 J
9 f* I, L+ j) q2 m* A; G
s = pd.Series(['a','b','c'])
' ?2 E! i; n' @6 |: z% }
7 J& J; u0 E; m5 W9 S' es.str.pad(5,'left','*')3 O7 I, A: L" m d
Out[104]: 1 n" \6 p* y) w, M
0 ****a4 {/ z* t4 I* Q# }+ C
1 ****b& [4 G$ t4 c: \( z
2 ****c3 ]3 k P' R8 z0 [0 u! a
dtype: object5 ?5 n. e7 ]0 a& Q% s& \
F3 @" v/ v6 P+ ~) p9 C! x ?
s.str.pad(5,'right','*')
0 _$ P0 K; n8 K Z8 Y* U) c0 pOut[105]:
- o7 h1 {1 p. k* q0 a****9 ^# F+ g2 M1 j( R
1 b****/ A. e$ i' |% w0 d1 H8 w( i
2 c****
9 L, ^4 X' Y! b6 @& X# ~dtype: object
: T+ ]( u& ^3 V& B' A+ k, X5 H
s.str.pad(5,'both','*')
9 E, I+ O3 o, @% o! {7 nOut[106]: * O# x" b/ E. S# X6 D1 T3 M
0 **a**
4 N+ L. h2 ^0 T7 w8 k1 **b**: j0 R2 l! S# f, H
2 **c**
. D0 s R+ W9 U3 D4 Xdtype: object
" r& c$ b% k/ L1 o( O. `/ q* r/ _( b1 Y5 B
1
6 l6 F( R! i4 r! L25 b& U# h0 ], f3 e2 R! w
3: D# f/ a1 \1 h- O% u
4
2 Z* D8 b8 P$ i# N" J3 X4 e57 L i) @0 j2 E" D4 Z/ g. i
6
0 T: b. S: F( a3 K2 h7* }5 S) A2 X% v& Z8 e' G( I
8
( ?+ O2 r- d4 |5 f( I9 ]6 z' n$ ]9. k$ {. m. a5 M3 f+ _
10
# \9 C# M9 G2 g) @+ R11
" b0 r) v: s2 b/ O4 H: a12( V4 T1 o9 ?/ b" Y" E
138 Z& r- i! E$ r2 X) E' P
14
- j5 f* D: A, \# J15" K; X/ U1 ~$ A, Z/ L1 }( Q
160 h1 n( I6 ]8 T" i
17
9 T7 B: a, E: F- {5 A% h18' _' y9 d+ [& g% }8 F4 _
19
; k/ |& A s! |20$ d2 x& b$ }8 Q$ Y$ j
212 u2 ~. B5 O9 o( X
227 [1 E' T8 U1 c1 G# G7 e2 `
上述的三种情况可以分别用rjust, ljust, center来等效完成,需要注意ljust是指右侧填充而不是左侧填充:* k$ \! a9 ] B% h) t: C1 V
) A. P' p! s1 d1 L9 X9 H
s.str.rjust(5, '*')2 j: z6 F2 ]2 [4 H, G
Out[107]:
% u. R Z5 N5 c- l0 ****a
8 T- q$ v' c H$ g1 h1 ****b9 L% y5 M* V' y! z
2 ****c
$ k, c$ O1 S9 M. b7 Adtype: object7 ]0 g+ c1 n; c" [( H3 f9 Q3 N4 ^
) K# z1 f& f+ A! }$ m1 R3 P
s.str.ljust(5, '*')4 M, G. i& ]! K0 v1 ?
Out[108]: 6 ]: O( L8 G. S; H; r0 G' b" a4 v
0 a****
' c! o' d# o. M3 {1 b****$ X- B5 f( ^5 l4 B9 x5 g# E
2 c****7 y' R. |: c$ k8 q6 w
dtype: object$ ~9 b& l( [* {$ G
/ n! J7 U$ v" I' N) D4 U/ i* ds.str.center(5, '*') f3 l; _' H6 U
Out[109]: 9 d8 `3 @3 U8 f& \) F
0 **a**
5 h7 G6 D h _1 R: c7 |3 n1 **b**
2 n; f2 V, J t& K# {2 g$ k' a$ ^. A2 **c**
2 X' C9 s. m* U" }$ E! \/ Pdtype: object
3 i/ P! y) |# K' o6 W0 y0 O* j( }0 e0 @ N! Y2 S3 E6 ]
16 E/ B1 B! S( D" N! {# n' ?
23 Y( r" t. d& W% z$ C
35 J9 _# A' C7 W- L: X3 ^- r' D0 E5 L
45 h' x9 A0 n! a- ]' ^( `( ^, W7 F
5& R, k5 T4 p7 K5 K. K6 A
69 h9 R. ^( k2 H6 }8 }' A
7
5 \9 R$ K' ]3 l- T6 Y8
& f ?6 p' M0 c( B7 M! N9
2 }& z: J! [: y) j10
0 n+ m% V7 M8 O: D& z11$ D3 O3 D h- p- g0 q% q1 Q+ Q% q
12
/ f. \+ [" g- I5 n# [. q \7 f; o: g131 M5 d+ p1 l7 I4 i
14
6 H0 ^; x! G; f) y159 U/ Z0 x! k2 u; y
16
$ z' c8 K* S, i4 n17
1 S( ?8 e }/ c1 w: h. B18
% P% m6 I. A7 u; u" T; |19+ ]/ o9 u' h+ s6 i6 R1 s" F; q7 N
20
, `# `) {- m' r5 W 在读取excel文件时,经常会出现数字前补0的需求,例如证券代码读入的时候会把"000007"作为数值7来处理,pandas中除了可以使用上面的左侧填充函数进行操作之外,还可用zfill来实现。
2 w8 H" c) B' b1 l) P6 Z( x& C3 `' J& X
s = pd.Series([7, 155, 303000]).astype('string')+ U2 z8 w0 ?+ i, Q
. y1 ?6 O: w7 o, l0 U: z# i& p& Ds.str.pad(6,'left','0')
# J) Z8 H9 J% k6 J$ MOut[111]:
7 m8 J9 D- p s' S& L0 {0 000007
R9 o( D! R, ^; \# G( n2 T6 C2 m8 |1 000155
- z! A+ K( i+ B2 3030009 t* X. c2 g, [6 ?+ J x5 R x& s
dtype: string! Y3 t2 R. g+ [' R+ n
% {4 k3 E$ A! |% I
s.str.rjust(6,'0')
! T, |3 y! B0 s3 X- zOut[112]:
" d. N; L9 X7 G0 000007
. q9 O! c+ c* S: M, |% t1 000155
. e0 X' @$ Y0 V+ F2 303000! u% D' T& o6 j) F
dtype: string/ N4 L- k) _* ^/ _. w
" a6 w2 j0 U. m* G3 f/ j) ?" c: J% d! [4 s
s.str.zfill(6)" Y3 s2 z/ r% F2 M; P
Out[113]:
* r9 M8 K3 l2 L: f: z- j0 000007# f5 _7 ]; T" d& B4 I7 {
1 0001559 d1 b+ y* w/ ^% ]6 I
2 303000, v6 R6 z6 E# {) @; g- N
dtype: string
7 h+ S: F$ r* S
. v0 J* h2 w5 Y! W0 k10 ?0 F5 r' x4 Q5 c% m0 @
2
! n* ?) Y5 P- n" D8 ^8 R3 ?3
1 B/ l% v, ?/ q5 w' K46 `4 W' {; E q8 R- `! [$ I
5
' n8 D6 u( r3 d& E; V6 l62 w, X$ [8 B7 q* z7 ~
7
" Z! _# O2 D8 {8
7 t4 s* I2 j3 A! o5 o. ?3 C9
/ X6 [0 @3 q* \5 ^( U5 ]: K, s102 [) Q/ \* A" t2 ^+ F% G
112 F3 d# s: g1 q& x9 ?
12. `) u9 x" l3 F/ M( U( \4 F. S
13
0 Y* J; A5 G$ B+ S14
' _! C3 k. f2 M# B0 u+ \15
8 [( ~; k3 w/ |+ e" S, B16: j- U# T8 q' w- H- m
170 X' T) q) r% z! P- X- ^
18! F: I* t# |! f b" e) s
19
/ Y( v9 C* C: x/ Z# H20
; D* P$ A5 ^6 g$ I2 t, C211 S/ E D7 N+ K4 J& {
222 P0 P8 y( D0 `4 \
8.5 练习; G( Y9 K3 T8 V s
Ex1:房屋信息数据集! C& |/ w! ]! Q; ~7 ]. k5 t4 T+ E
现有一份房屋信息数据集如下:
7 m: V% w9 I7 L$ |! y* @/ e |" e( N
4 w4 V3 x, b. pdf = pd.read_excel('../data/house_info.xls', usecols=['floor','year','area','price'])
. m5 Q2 Q$ c5 q, {4 m8 x6 Xdf.head(3) m$ k/ x) N( a& ~/ a
Out[115]: % P# K; R; k$ h8 S* s
floor year area price# E1 ~9 [4 C) c4 z7 w
0 高层(共6层) 1986年建 58.23㎡ 155万" [1 o" r8 h$ D4 z6 S, n$ B
1 中层(共20层) 2020年建 88㎡ 155万* `6 g9 j+ x4 p# ]1 A) _9 Y8 ~
2 低层(共28层) 2010年建 89.33㎡ 365万
5 R2 V4 J4 y* {( D0 }- I1
; }/ S W$ F2 u& m3 p22 b$ |! u- y8 I( u
3, w/ D6 s$ H" G6 u6 d% Q2 I: x
4
1 b2 q# ~: U) P! j! F51 Q( ^3 d( p/ Q, [
6
' z6 @- _/ v. U3 `' \8 w% U. Q7$ x$ f) r; l9 d) v* z: w8 v0 j
将year列改为整数年份存储。
# T9 ~) p& p8 t \: w& v1 B6 R将floor列替换为Level, Highest两列,其中的元素分别为string类型的层类别(高层、中层、低层)与整数类型的最高层数。, o$ J5 p* u$ B; h4 w
计算房屋每平米的均价avg_price,以***元/平米的格式存储到表中,其中***为整数: e" D2 D; b( U. _& ^ f' W
将year列改为整数年份存储。2 w7 @" |0 j' p( o: r
"""# q5 ~4 u) C f# Z. Q+ B# P
整个序列需要先转成Nullable类型的String类型,取出年份,再将年份转为Int64类型。
1 ]: p5 g1 {0 A: A注意,转换的类型是Int64不是int,否则报错。即使astype加参数errors='ignore'跳过缺失值,) T+ N6 c* @3 w/ e
转成int后,序列还有缺失值所以,还是变成了object。+ \( d S4 L. T: u/ E' a
而整个序列转为Int,就还是Int类型,缺失值变成了 pd.NA 。
- f# @& K( u6 Q" S4 `- N0 F" u$ H"""
: l `( ]; m6 P, [' P$ mdf = df.convert_dtypes()
- T) r# t0 M" x8 O: C4 Gdf['year']=df['year'].str.replace('\D','',regex=True).astype('Int64')4 Q5 b6 x, Y; e& S* ~: ]
df.loc[df.year.notna()]['year'].head()4 t3 m4 P) t5 \' A% P( @8 T
9 ?: T% n6 Z- ?1 Y! W* q2 w
0 1986
9 @5 l8 k8 x& S1 2020
) ]& M- `1 I' k0 x1 ]2 2010
6 [( Q- i! k: b3 2014
" W' L( ^" v; ~4 2015$ S1 o4 w1 k8 c" M2 p
Name: year, Length: 12850, dtype: Int64
' }$ H! N9 \; ^+ M7 c! n0 o, \+ q& L3 o
1. [2 Q7 Y1 N" h/ p
2
. i3 h" T/ z1 z0 j8 C* W, E3. i, q& S1 V' s- Z
4
6 i+ D# x9 K8 \/ `' M58 Q' @5 I1 _' I& X4 [% b
6
8 U. c1 w8 m1 f, q8 e" a7- h7 r$ a1 X. }" t1 {( A
8
- ^' _# }& ^* H- @ B# @: O9
9 a: i" k1 N! L4 z( b+ `4 U10
7 {* \% `; o% z$ V( f11 w( M9 r3 n% C+ v. u5 G4 L
120 K* e7 h4 G+ N; X1 w3 Q& F
13
; }9 z' d! [5 _) u7 c' e14
/ ^6 ?7 s4 S0 A! R150 ^6 E) h7 N, m1 N* L& j
16
: e3 y3 }2 G! V9 E' |, ~# \- O! E' x参考答案:
' r: B& \4 u& h, v' q- D$ [
+ @9 O& d" G* V# w不知道为啥pd.to_numeric(df.year.str[:-2],downcast="integer")类型为float32,不应该是整型么
* N2 K. \1 z2 q$ p3 j
+ J d# t% [. w- T2 E% e* d% Tdf.year = pd.to_numeric(df.year.str[:-2]).astype('Int64')
! ^$ w& R" O; c( Y/ Ldf.loc[df.year.notna()]['year']
( l9 x5 g! S3 R7 E# v# e0 u0 a; X4 O1
% K' \( q3 X) s- \$ K: ~6 o2
1 \- t$ D. h0 e. K- O: ~6 Q3 C将floor列替换为Level, Highest两列,其中的元素分别为string类型的层类别(高层、中层、低层)与整数类型的最高层数。9 ~& e% }9 b, e5 Y! \1 V
pat = '(?P<Level>\w+层)(?P<Highest>\(\w+层)'
/ V: c, a$ K( f/ ~0 s( q/ Gdf2=df['floor'].str.extract(pat) # 拆分成两列,第二列还是(共6层得形式,所以还的替换一次
* n6 C+ ?1 E. m' x e Qdf=pd.concat([df,df2],axis=1).convert_dtypes() # 新增列拼接在后面,再次转为Nullable类型8 y6 S. [6 u6 \* Y# k
df['Highest']=df['Highest'].str.replace('\D+','',regex=True).astype('Int64')
" |+ Z" V* _4 @1 F* x5 G/ adf=df[['Level','Highest','year','area','price']]
5 a! [8 c0 \! k, H" T ]6 ?) udf.head()2 D* n( f8 S, v. ]& q% J1 V4 c
M7 N2 v" j; `/ h+ P/ i Level Highest year area price
1 @4 U0 ~6 \2 j0 高层 6 1986 58.23㎡ 155万1 b6 i7 d. W6 T: m
1 中层 20 2020 88㎡ 155万 M& r. ~3 u( a8 T: f7 Z" M9 P9 j
2 低层 28 2010 89.33㎡ 365万
7 U8 d M6 \4 c. ?7 A3 低层 20 2014 82㎡ 308万
) o) @* @5 F+ S9 D3 G9 u4 高层 1 2015 98㎡ 117万
( u& y' ^7 X' N. d; U7 m1
5 g; Y T& P4 F& ~, B$ n# M1 K- C" j2
7 k4 i& G9 Q, S2 n5 V% d' Q36 l8 w Q/ X- j F/ X' d' L. l# c
4+ R& \. U3 U% N% e" b3 @
50 m3 w6 }6 H, j& B6 q
62 Q6 c9 ~: o- X( R6 |- |" r
7+ l" Z& _# U, S
86 U& A1 ]( d9 d
98 i* ~6 j/ v6 f9 n" U8 x* C
10" r* c( l1 N% [4 g. j
11
$ A Z$ T0 c9 Q' m/ d0 L12
" e% q' W) T. J1 x: s2 x/ D* T13& ?& [* Y" f$ N4 t- W" {& r0 l
# 参考答案。感觉是第二个字段加了中文的()可以准备匹配出数字,但是不好直接命令子组了2 N$ M- x1 |5 ]" Y
pat = '(\w层)(共(\d+)层)'
) @! k8 T0 l7 m9 e9 ~new_cols = df.floor.str.extract(pat).rename(4 H) [6 N: a% Q, d) Y8 R7 n( r3 Q
columns={0:'Level', 1:'Highest'})
1 [+ F/ u R4 d8 y% A/ z3 T+ k" y8 i0 [
df = pd.concat([df.drop(columns=['floor']), new_cols], 1)- S9 L. y$ x$ z: ~6 A. b: o+ _
df.head(3)
: |% D) ]6 J2 B" T. @
+ h, U. P/ W+ Q R4 d/ jOut[163]: / L& k" w. v4 G7 L, D \2 g% k
year area price Level Highest
; X* n9 T) w: s c0 1986 58.23㎡ 155万 高层 6! V/ F3 \. t, b3 p0 p+ }
1 2020 88㎡ 155万 中层 20$ e( N& w7 `3 d7 }+ \
2 2010 89.33㎡ 365万 低层 281 r7 H" q4 L+ `- J+ U* x
11 g) u, b. G4 n. E% `6 V
26 j+ ]$ L+ R& C$ O6 u) t
33 H; M! ^! U8 T" d+ X- \
4
* a" ?* U$ [# N; j! c9 o5) @; H+ `! d/ w
6
5 G1 ]$ H2 n+ Q& T4 Y) w75 O, R$ r, q G7 s' x
84 J+ u. A; v( P; r0 E9 h
9* E9 p3 C# _/ G' b4 i2 j
10% e) u2 f( t- ~: |% q1 L; e9 m
11+ E+ ~1 D4 c2 }& u9 k2 |
12
4 J+ a$ s4 [* B( p2 }. M13
3 w+ R3 \. b" z$ u) B' U/ `计算房屋每平米的均价avg_price,以***元/平米的格式存储到表中,其中***为整数。1 S A8 a% a: M
"""
4 {. Z6 ^& G" f; F. j0 P3 Nstr.findall返回的结果都是列表,只能用apply取值去掉列表形式$ }3 U4 v6 v4 _ P; d
参考答案用pd.to_numeric(df.area.str[:-1])更简洁# S9 O. A; D$ P+ h, |; L8 A
由于area和price都没有缺失值,所以可以直接转类型+ O( J7 L0 d4 v5 P7 y' L- _" o
"""8 q# ]* ]) M* Q- Q
df['new_area']=df['area'].str.findall(r'\d+.\d+|\d+').apply(lambda x:float(x[0]))
N: _ D6 F2 g4 j' bdf['new_price']=df['price'].str.replace('\D+','',regex=True).astype('int64')% A. o, e$ a$ n6 J( S+ L, ~
df.eval('avg_price=10000*new_price/new_area',inplace=True)
1 n1 ]. j; m% C" j$ T$ Z% q. S+ F# 最后均价这一列小数转整型直接用.astype('int')就行,我还准备.apply(lambda x:int(round(x,0)))0 S; c: v+ o" \' O
# 最后数字+元/平米写法更简单
* d+ ?' o! T5 ~8 f' u N! M$ Zdf['avg_price']=df['avg_price'].astype('int').astype('string')+'元/平米'
, o& X5 w# u( ?: v; hdel df['new_area'],df['new_price']
+ }' u# J* [. q2 S( tdf.head()
N. K( ]7 K# W7 q0 z- B5 y
9 N, ~+ E9 c' E6 i Level Highest year area price avg_price v+ p/ D) b% R1 h1 D* U
0 高层 6 1986 58.23㎡ 155万 26618元/平米( m, O6 }8 R1 ?* ?! b
1 中层 20 2020 88㎡ 155万 17613元/平米
& \6 H0 R0 [1 D7 p2 _/ l2 低层 28 2010 89.33㎡ 365万 40859元/平米4 R$ p' F( U- |% j! \6 r
3 低层 20 2014 82㎡ 308万 37560元/平米
3 s. \7 X4 W, y4 O( O. l& b7 r7 Z4 高层 1 2015 98㎡ 117万 11938元/平米8 }! @" K! ?' _" T) A1 `1 `3 _
' f8 @- `6 _: F5 O5 y* j0 T+ ?
1
7 z4 D+ ~6 b" f7 K( f* b8 ?2
, ?! T9 ]+ l4 q2 a) |3$ M. h2 ]$ B7 n
4/ U8 b6 P: `, G8 H3 @
59 p; |) n0 n% Z+ d
6
( C- b) Y5 X; P% J% k7
! v3 v; O2 h2 a9 }( M6 {8 V8
' [5 x3 `) N8 |; o* Z, I91 [. v* k& q! |( \
10
# e( @0 v {! A* j114 p6 e9 y! w) H6 F/ p$ z1 H: h/ m# ~0 T
126 r* ?! p8 `* o6 D8 \8 `
13% {0 T- ~" V1 N
145 P+ @$ p( s2 f
15& m8 L# c" l& ] u# D7 u) E4 E
16! q' j) i: c: O% O/ ~9 i
17- r7 y0 g7 x" z8 j N0 A% f
18
5 j' X/ r) O1 O19) s2 O; S+ u7 D& h
20
9 L3 s7 ?. h' v- \# x E# 参考答案
7 k( @( r% ?! `+ Rs_area = pd.to_numeric(df.area.str[:-1])+ G& v/ S, k% e2 D& h- ~2 z3 I
s_price = pd.to_numeric(df.price.str[:-1])
( `, s' i% r4 E6 S, \df['avg_price'] = ((s_price/s_area)*10000).astype(
5 h% S7 h8 y4 q7 l4 @# t 'int').astype('string') + '元/平米'4 y: p0 h' A) X
( [- E$ h: V8 b# G1 ^
df.head(3)1 ]$ U1 i) l) b1 A6 X7 w7 A
Out[167]:
& T9 s5 w0 [4 s' h4 U0 w p- Q" i+ J7 I year area price Level Highest avg_price
# O5 h4 K8 ?1 h- g- {$ K0 1986 58.23㎡ 155万 高层 6 26618元/平米
) [) E& M% n) J# n- ~1 2020 88㎡ 155万 中层 20 17613元/平米
9 T9 u2 R0 s" D, u/ I' y2 2010 89.33㎡ 365万 低层 28 40859元/平米" S. u: \: O# ~5 \% C, O
15 z9 Q! c4 f, M
2
* I9 O' v3 L3 e& \; C+ e8 m31 w \& y- E* h2 s
4
$ S* u& }' R- a6 k- z5
. u9 B7 r- o; |% l4 }1 Z6) ~! ]4 T0 u: y& V3 a6 R' J# c# a
7
2 [( p2 ^- p0 R) S5 v; l* F! S8
" {" H- g; c+ e/ U, @0 N9# j, T$ K- B9 j' G, ~3 r8 R
105 V2 y. p. u; T( X
118 V7 F) v: I/ L. z7 A
12
7 y* o3 X x, c& r/ ?Ex2:《权力的游戏》剧本数据集. J! Y7 ^! t, h3 a
现有一份权力的游戏剧本数据集如下:
8 S e" _( E( q& M0 I c5 e6 c' R8 H, ~# g/ \" i7 f' u- ~
df = pd.read_csv('../data/script.csv')
1 P$ ^+ u, Q: ]9 p6 r7 o1 gdf.head(3)! Y$ v2 P" F9 J- B7 {7 J% |& d
& l9 l! ~+ i5 Y( e; A. @. dOut[115]:
" F/ [9 N6 c/ ^8 P COut[117]: ' o$ j- e3 h; u# o
Release Date Season Episode Episode Title Name Sentence7 b. P$ ?( w5 _
0 2011-04-17 Season 1 Episode 1 Winter is Coming waymar royce What do you expect? They're savages. One lot s...
2 n ]' o/ p, D! H1 2011-04-17 Season 1 Episode 1 Winter is Coming will I've never seen wildlings do a thing like this...
5 @8 S! X/ j( n3 E. Z! l- ^; O. x2 2011-04-17 Season 1 Episode 1 Winter is Coming waymar royce 9 X$ \; {: A, Y9 M
1
\* h( W Q5 b, v! W! {27 Q5 h+ @2 P" T D6 }
3
5 L- U6 h$ l% U4# A4 R2 z6 h( L: x( o* ?
5
& c/ m% z A! C5 o62 ?4 S, \7 m+ f9 |
7+ x9 E- v& F' z. g- j
84 {! O% z# H4 O& A# i
9
$ C# \5 X" ^/ O% T1 q计算每一个Episode的台词条数。
E2 X0 F; t! z& J( B2 a# Y- A7 @8 r以空格为单词的分割符号,请求出单句台词平均单词量最多的前五个人。! e- R/ Z# `2 w3 s4 k8 u. I/ Y
若某人的台词中含有问号,那么下一个说台词的人即为回答者。若上一人台词中含有 𝑛 个问号,则认为回答者回答了 𝑛 个问题,请求出回答最多问题的前五个人。
4 A* \! K, f5 ?6 z2 ^7 J' u计算每一个Episode的台词条数。+ ~! ` n1 z$ }6 z2 g, |6 z; `3 u
df.columns =df.columns.str.strip() # 列名中有空格! _! u( _. w1 ^% }( C; ]! ]1 J: d7 \
df.groupby(['Season','Episode'])['Sentence'].count().sort_values(ascending=False).head()
7 s# P" ]8 S5 o+ Q, k) a
9 X, c b6 b$ L; |8 D( w. ^- ?season Episode
# V8 J" ]+ n% C2 a; eSeason 7 Episode 5 505* A: a2 ]) Z1 P% w9 w1 |2 N
Season 3 Episode 2 480& H: H4 C( y1 a7 {" E
Season 4 Episode 1 475
0 e+ A3 O" g3 ]5 x' l1 vSeason 3 Episode 5 440
5 ~' l4 B+ S7 G" W( CSeason 2 Episode 2 432
) |7 n' }# b4 ~1 l5 E/ `" n1
& A$ p8 y% W# [' ~2% w3 \1 ^3 K- W4 R
3
2 X0 X4 ?+ C- U( Y% H% N( R4 L4
" ~" u+ B! @, |9 ]57 {# b; }3 M( C4 H. Z
67 Z, q) U2 Q J1 l: z0 {: \( w( k
7
0 w V# o. j9 h' G' f7 X6 W2 [8
! _* }5 P% K# E n! z* R9
% L i9 {% h# E7 o以空格为单词的分割符号,请求出单句台词平均单词量最多的前五个人。% Z1 [) T+ `6 u4 w' d0 }2 _1 {
# str.count是可以计算每个字符串被正则匹配了多少次,+1就是单词数
) [5 w, l% ^2 w# j! edf['len_words']=df['Sentence'].str.count(r' ')+11 I" k( c8 }& _" e
df.groupby(['Name'])['len_words'].mean().sort_values(ascending=False).head(); g% G/ o. `# K$ S/ } u, a8 m
$ B8 n0 ]- T0 |& Q) g7 x3 s# ~Name
, g$ K6 j3 i% ~6 {8 Nmale singer 109.0000007 ]9 N j! {- }6 T4 t8 \
slave owner 77.000000$ `7 o# d5 \( v; O* @
manderly 62.000000( @; f0 d4 B; |: K% c+ Q
lollys stokeworth 62.000000
, N# F" i$ D1 jdothraki matron 56.666667
1 Y8 Z1 r7 R2 E# D6 J2 A& u mName: len_words, dtype: float64
, d/ E7 Z8 \4 I, C1 I4 S1( I9 P4 x: G k
29 l/ J6 z i& A k
3
3 |+ l' K, @: d) _3 J# [6 s3 a4, z) L. ?# v0 b5 t6 ?5 [8 ]
5
5 Z- G* N; A$ l/ X7 I+ t: |5 ?6
) l* t1 `5 O, s: A, ]5 ^ j# n7+ |4 R9 m; Q6 f8 }& ~; U# n' W2 x/ w
8
2 i. n- E/ U! Y8 T6 k/ F9/ n) R* b8 R* e3 h5 n: o Z# p" | \
10
# }9 x4 ?. h! ^3 N11
4 Z9 a/ j0 M, h- l2 A# V若某人的台词中含有问号,那么下一个说台词的人即为回答者。若上一人台词中含有n nn个问号,则认为回答者回答了n nn个问题,请求出回答最多问题的前五个人。0 i1 y( c4 o s) b
df['Sentence'].str.count(r'\?') # 计算每人提问数$ b- ?: o7 S% R7 Z: `7 z7 `* k& y
ls=pd.concat([pd.Series(0),ls]).reset_index(drop=True)# 首行填0, C; Q$ F! y9 M$ M; Z
del ls[23911] # 末行删去
; ?0 Z. M5 m1 h6 U# X9 Ddf['len_questions']=ls" t3 C% S3 n( h: x1 F) H% v
df.groupby(['Name'])['len_questions'].sum().sort_values(ascending=False).head()# z4 v: f9 }6 A$ D" C: e
4 R# C* K$ }. o) s
Name
6 f7 j! a" g/ {tyrion lannister 527
3 n! C3 V5 z/ X) a$ b9 L# l4 rjon snow 374
: Q2 ]% v1 x& Q2 E/ w$ \# Y- ^3 ^jaime lannister 283
2 Z2 t2 L& p7 y! ]( Xarya stark 265
* {0 ]" i; [. N) E7 G1 X- e2 Jcersei lannister 246
7 k6 m' ~/ w# }0 K$ ^- pName: len_questions, dtype: int64* X: Q1 H. J4 O; u) U9 u, q0 }$ g
9 }4 [: ~ x- z; q8 [3 q7 C+ j+ b3 k9 [# 参考答案
+ ?4 |6 O9 f4 U C2 {3 fs = pd.Series(df.Sentence.values, index=df.Name.shift(-1))
1 G# W' t: i# e/ J4 R" P. a* Ss.str.count('\?').groupby('Name').sum().sort_values(ascending=False).head()
6 b: e9 F) d3 R. \+ T+ x1 X1 M4 M2 `! H
10 _* Q& u2 A0 E! N/ g2 h: d
2
1 e3 T" i4 E/ j1 R" K: e32 a7 X; q. A$ s! ~, t# ?6 Q* R
4
) h6 ?% u: }+ M; |5
3 M) E' z1 { o0 n: u$ w9 y6& m- F; h0 b2 j1 P7 `/ X' x
7: d4 I3 x# r7 w9 I# U& p# u- H
8
% v' c8 Z3 f {1 c9
+ f! k" V0 p# z& J2 a _4 H108 z5 }4 r r( i C, w1 Z* w: E
11
5 ~0 s- C# [9 k+ m5 o12& n, `+ \, H7 } L
13
* i+ N- \4 k9 o2 D14
* n( V1 Z8 o& a" l0 C# K15+ k8 l0 f9 A* ^+ w& e
16
" t0 K- s8 n: Z4 `) F17' C v0 N8 ?. Q9 o2 F( j% U3 }
第九章 分类数据
4 |+ Y* @: p8 W" \, t; fimport numpy as np, V' J( \1 v5 J: L5 g. r. Z
import pandas as pd
/ N7 E* ]( _4 f6 F1" U" ?& Y6 E, h" @! y7 r& w
2, C& ?: f' r9 S
9.1 cat对象
5 M( C& r& v% R% i ]; ~ s2 S. U9.1.1 cat对象的属性
+ o* R+ v2 z' d$ C+ f 在pandas中提供了category类型,使用户能够处理分类类型的变量,将一个普通序列转换成分类变量可以使用astype方法。( c5 G; U+ n; Y1 [6 W' x
4 D# j4 I3 ?4 E, Odf = pd.read_csv('data/learn_pandas.csv',1 u2 C# Q; H* T6 Q' A# j
usecols = ['Grade', 'Name', 'Gender', 'Height', 'Weight'])
, J" c/ G1 q4 H2 N U2 Ts = df.Grade.astype('category'), M& L8 q0 c+ z0 E9 c2 v# X5 w
' L" P3 x) |) [) Y7 P' U/ R$ p
s.head()4 H& f+ X4 e) U! s k' v* V
Out[5]:
/ B1 Z" C a2 q, F) m0 Freshman8 j6 b6 |- K, X7 n) n( f
1 Freshman
, [9 l1 `+ K+ W, x! ? _4 h _* n( _2 Senior2 |! ?1 ?9 V# T; i4 `9 v6 D
3 Sophomore
) N5 q* l& s g [) Y* Q# g- l4 Sophomore: X# j" ~. R) s
Name: Grade, dtype: category
. _8 z1 e2 C4 t) d8 L7 Q! a; @Categories (4, object): ['Freshman', 'Junior', 'Senior', 'Sophomore']; V; i) U; n" O! i
1
; ~; B9 |" S' M8 }) O2
! d1 m! _2 i* |. N; c& I1 N3
2 w- F+ @6 t& C* Y7 a40 Y! n: I6 I/ x( S# U7 j3 ]/ W& t
5
! ^/ @. |, L2 s8 \: D! v0 _! T6. I h7 K! E* f' S, Z, E
7& O/ U6 `! O' s6 z( f9 a, D3 i
8
4 s0 R/ L( T( C; }4 o1 b H, o; [97 P; G" Q: ?) @
108 X. h+ a* q" T" ?0 z/ C9 j2 w
11
) k4 D) a @# N, C0 j12
2 L0 J# D4 }( P+ E# G9 z1 ]' r! ?* U, l134 C- k$ H$ |) a/ X* p/ p
在一个分类类型的Series中定义了cat对象,它和上一章中介绍的str对象类似,定义了一些属性和方法来进行分类类别的操作。
! H% S3 g- y3 J
4 A0 g- Q6 k. O8 ss.cat
/ S" [/ v: t6 S) G6 E! g. oOut[6]: <pandas.core.arrays.categorical.CategoricalAccessor object at 0x000002B7974C20A0>) z' n, y* m: F/ Z
1* ^1 h# s6 R a: T+ n9 R
2' h) F4 M* h+ y& a9 F
cat的属性:
5 Z/ D: ~* n. |9 V
4 O' c2 r" _. i I$ ecat.categories:查看类别的本身,它以Index类型存储
6 f" d9 s, X0 ]5 G* _- Ccat.ordered:类别是否有序" f, t3 Z }' s, r
cat.codes:访问类别编号。每一个序列的类别会被赋予唯一的整数编号,它们的编号取决于cat.categories中的顺序% K& G/ F4 K9 Y- f+ n
s.cat.categories
v/ B* m& y: `, MOut[7]: Index(['Freshman', 'Junior', 'Senior', 'Sophomore'], dtype='object')
2 D' e) W+ \( i: X8 s# ]7 u- O" s" t& p! J, L$ u) m6 Y/ p7 O' Q1 ]
s.cat.ordered
' S3 _# F' ]/ n5 r; `1 `Out[8]: False
% Z# t" D8 O1 l" ^" b1 g0 ]# x* t
s.cat.codes.head()/ q2 B! q2 |+ [! C% K
Out[9]:
: o1 d9 j+ z3 |6 H. X0 0
3 |$ q8 T/ w3 z/ P/ `. e1 0
o y+ T" I* r8 w/ ~0 Q' b4 l2 2
2 p. e, r" A' o; b' d% L. F3 3- W, e8 N) [3 |, K) O2 U: H5 I
4 3" [6 _7 n* ^4 H) s
dtype: int80 T( Q, A7 V* i0 q- M" ?8 @; G
1
9 |6 {& w7 o3 n2
# Q) h! Y# S& W: \/ C3
Q+ Y# V8 `0 V6 ]1 @5 M9 @0 I* q4; m ]( g( }8 Q% ?* g
5
: h5 F9 u: B5 u6
1 `& P0 ?2 h$ t0 Q3 f3 j4 d76 h& p/ h9 V# K# s5 g8 O9 V6 {# H
8 ~" Q5 I$ m5 P
97 B8 Z/ j( d# d- u& @6 C, Q$ o
10
7 t) K5 [$ d; a; S11
+ P# Z4 u H( K12
. |% |6 V: h1 |! z% z. c9 q13
& ?5 r# w/ j# x14
3 H- P7 h2 c* ~" r. d- f+ h9.1.2 类别的增加、删除和修改. p* m" h0 W; [7 g" ~
通过cat对象的categories属性能够完成对类别的查询,那么应该如何进行“增改查删”的其他三个操作呢?
0 c4 r, L7 w' `+ Z# L1 l; u4 u- B; a* W
【NOTE】类别不得直接修改
: O u) ~6 f3 ^2 [在第三章中曾提到,索引 Index 类型是无法用 index_obj[0] = item 来修改的,而 categories 被存储在 Index 中,因此 pandas 在 cat 属性上定义了若干方法来达到相同的目的。
+ ~* d* Q- z2 [1 l- L( v" p, k$ J, U7 G
add_categories:增加类别% Y4 c' R6 K; i6 F8 }7 c7 v
s = s.cat.add_categories('Graduate') # 增加一个毕业生类别
( K% x/ {6 j& Y/ Js.cat.categories. Y; C# F. {; }- `
r9 R9 F ` E3 l' A hIndex(['Freshman', 'Junior', 'Senior', 'Sophomore', 'Graduate'], dtype='object')
* T, k( B7 f- q6 F! M1
# W. s' p% |" ]4 j2 a; T2
% r# y! j+ _: Z( ?6 l; D4 ~" p3" b( X0 ~* F% t$ v
4
X8 ]8 d6 e9 o3 \) [, h8 ^remove_categories:删除类别。同时所有原来序列中的该类会被设置为缺失。
) ^. Z: H' v% j; A. ]% [s = s.cat.remove_categories('Freshman')7 ^) p0 l7 S* _9 P
8 E: d0 i- P ~% Ps.cat.categories$ p2 }- H/ M- W4 w# n+ B
Out[13]: Index(['Junior', 'Senior', 'Sophomore', 'Graduate'], dtype='object')( j: f. p4 [8 m" q( n Y4 [, {1 m
5 t; q$ j/ V1 H& M) U0 [9 Qs.head()
: _# t# z% C1 w. POut[14]:
9 Y% ^3 s8 r6 g) J# ]0 NaN4 A% t3 {, d/ E' ]- e4 d" c
1 NaN
' u( i+ O+ I1 z0 ^- z2 Senior; ]! d7 K* K! U
3 Sophomore
2 J% ^+ [, {* `$ C4 Sophomore
* l) }9 j1 A0 IName: Grade, dtype: category ]+ y) x2 M+ V7 N# j2 L6 u
Categories (4, object): ['Junior', 'Senior', 'Sophomore', 'Graduate']+ L/ [. N; N6 z6 `& ]7 J
1! m/ C- s$ E* E# y+ ]
2+ Y: ^! w7 _3 S' _5 Y" d- \
3
9 z) q) A$ i2 y9 i' p7 Q5 x3 |! }4 h4
1 a1 t5 ]! l3 m G5
3 M$ F) w- k/ ^/ f/ w64 `9 I- D% ]2 m) f: E' G5 y4 s& |+ R
7$ O. i# ?5 X5 _3 \' Y+ x; W
8
, I) C8 W2 Z+ `% M; m7 o9
: G( f7 s: j% P9 o5 g- g108 K5 ?' a7 G$ ^& v+ [& h; w
11
& R! M3 |- |$ y4 E5 B& j1 B126 k. v# g9 Q$ H
133 o1 u# }7 @/ {2 M/ b
14: s* }5 Y, F0 n3 z
set_categories:直接设置序列的新类别,原来的类别中如果存在元素不属于新类别,那么会被设置为缺失。相当于索引重设。
% ?% D) K* i+ R2 P2 J4 E( F6 `. Es = s.cat.set_categories(['Sophomore','PhD']) # 新类别为大二学生和博士
* `4 w7 W8 f* h c: fs.cat.categories) f6 s) I( j8 b0 w4 _! t
Out[16]: Index(['Sophomore', 'PhD'], dtype='object')
2 z1 J; [- h# C' K0 a. w. u& j8 n- k# x: K; U; {2 r+ ?
s.head()3 U/ R4 w @3 g9 b
Out[17]:
" X% @+ _+ J& r0 NaN6 Q- |. T4 U2 Y. U5 D* ?) J
1 NaN
4 D: R/ z# a5 {0 w- {2 NaN. _7 g$ W: I' B
3 Sophomore
+ Z5 V2 p$ d( H0 X4 Sophomore5 y- ^! v7 `, F* f- }
Name: Grade, dtype: category
" s( r+ E( T! s" h6 w( fCategories (2, object): ['Sophomore', 'PhD']( h# |9 E; f, T! y" g: C# t, |
16 q7 T$ e3 i3 _0 E( J1 \
2% x0 U& T$ C/ z5 ^ d
3
4 h O5 [9 n/ j% z0 }3 v42 x& s; L9 i0 t! [# e
53 h! K7 r; G0 D
6
9 b y# X3 C; Y* {4 I76 j" U9 y$ P, [# q: w# |
87 R- z$ P1 ~9 j
96 f2 v: c7 I/ ^ o; }, c* N7 n9 ^' r
108 F$ ?: D- c7 N% A; C
11- R5 h' {: N- P! d7 ~0 K
12
- j/ |! B0 h6 S2 y. ?) O13: P8 _0 F: [! S; m% l1 c: X
remove_unused_categories:删除未出现在序列中的类别
; B1 n: ^2 T; n* B/ u0 P6 w _s = s.cat.remove_unused_categories() # 移除了未出现的博士生类别2 X, [& U% A' o) |
s.cat.categories
; |9 C& t; G% t
0 N" e; g, l1 f5 A3 b' R* DIndex(['Sophomore'], dtype='object')& Q: a1 \2 \1 f; \) k& ]
1
v1 A% T# b' S: P) A& A& g* a. B, T2
; A, t" d& ~7 P- s& z0 G3 e36 ]# Q: v. U; ?4 _: C( d
4( f9 j! ]0 |$ y9 U( t9 ~
rename_categories:修改序列的类别。注意,这个方法会对原序列的对应值也进行相应修改。例如,现在把Sophomore改成中文的本科二年级学生:
/ ?- _3 o) ?1 X! T/ N! Is = s.cat.rename_categories({'Sophomore':'本科二年级学生'})
$ U3 I* ~9 ?% o8 {s.head()4 ~( R Q$ Q% t% G/ O5 s5 d
' W# @# Z% V" L9 `( j& W0 NaN
7 E9 e1 W% I! @0 @' X1 NaN/ m- \1 j- U1 n" @. p) t% x
2 NaN
( Z9 C3 u- J2 g" F0 F+ T! T- }1 o& C4 R3 本科二年级学生
4 Y' U/ a5 _2 b+ _' F4 M% \4 本科二年级学生) i; ? ?. d c
Name: Grade, dtype: category1 E, X/ _1 }! D
Categories (1, object): ['本科二年级学生']" l3 Z7 v3 ^/ j+ x: v2 H3 Q
1
5 N. t( g. S' o; ?4 m' U; s9 Y2- U4 [4 ]9 L5 g* [1 ^ y9 l* p
3
8 y6 p0 j0 o( Q) V% s' C49 W& h4 p% f+ D6 B1 p1 a
5$ M$ Z6 C4 f+ M- C3 j
6
- R+ G- T9 J; m, d0 w/ M" `7$ k% z0 ^8 e' j- u
8* Z1 P$ ~. Z3 U% r
9( T' C: v4 y: v8 ]. \
10
2 p( E% I8 |3 Q+ n, v* m& A9.2 有序分类
: c/ F* I/ M& q c9 m9.2.1 序的建立
5 X0 T$ @) k0 F; V' h 有序类别和无序类别可以通过as_unordered和reorder_categories互相转化。reorder_categories传入的参数必须是由当前序列的无序类别构成的列表,不能够新增或减少原先的类别,且必须指定参数ordered=True,否则方法无效。例如,对年级高低进行相对大小的类别划分,然后再恢复无序状态:# @* z$ Y! Q" y8 e
2 Y% Q; `. n, Zs = df.Grade.astype('category')& I/ E/ `4 W, [$ r, J( d, W8 w2 J
s = s.cat.reorder_categories(['Freshman', 'Sophomore',2 e; h- K* c0 R" y
'Junior', 'Senior'],ordered=True)
' r1 x* q, y, o& I/ j8 m$ Cs.head()0 C4 _. P% f& V3 k7 F1 [# B
Out[24]:
; u$ `' X( _9 G1 ]0 Freshman9 W; J0 j" t" {
1 Freshman0 B c4 `. Z& ~4 b3 z' h
2 Senior8 q% W* W+ @2 S7 H @: W0 N
3 Sophomore1 n6 Q0 }# V' v& k5 J
4 Sophomore( x! @* r, w8 [) f" U+ W7 p. Y
Name: Grade, dtype: category
2 e7 z B9 X+ h; z; e& vCategories (4, object): ['Freshman' < 'Sophomore' < 'Junior' < 'Senior']; y% q! q1 \! T4 N3 L5 T! c: M
, L, ~( P0 W/ Z$ \ J& Ls.cat.as_unordered().head()( g9 E# u; h0 Y6 ^1 z
Out[25]: # z7 x. P, s+ R* y9 U' C4 W
0 Freshman
; y0 W! a4 k8 a i8 w1 Freshman2 O, ^0 I; R" V9 k
2 Senior8 }5 c1 c9 a0 w* O
3 Sophomore. w3 j Q# K/ S% `
4 Sophomore/ q& i; J0 e W: F' t
Name: Grade, dtype: category
$ R0 k. ?0 W* ?7 V& g; J. C! L: vCategories (4, object): ['Freshman', 'Sophomore', 'Junior', 'Senior']& i) l7 F3 R. Y5 ~
) S N) i. G$ X: v1
& n+ P; E: i$ B# }1 k2
: o& t4 F5 t9 I0 H/ ~' m2 {" T3
- D9 M4 @ i; Q2 [& h6 V4. {) H! A) g! Q$ W/ c
53 m7 w8 C* Y8 R; L) k8 n4 |) b1 p! R
6
) l) j; K+ B. l) C2 q7
' M3 O5 c9 G: F9 d5 {8/ [" Y( @/ ~# y/ D: [) P
98 M4 b9 ~+ T$ T, Y
107 u6 a/ W/ v: L( z) B4 i
114 e: X: d, Y; O4 A9 b! u, @$ x! n
12. @; o5 b6 F: r! ?1 Q
13
O2 o6 e6 ?' W: M3 h14
9 ^5 R& r# X5 K' @0 T15
' t- p8 P" k, k) q$ I" o, h16, @: z; l- |3 I! G( R2 w
178 z) a$ e( O* \% X2 R% Z: u
18
2 X- ~( g8 H: O- T1 k9 C( _, x$ r19/ P% I G, p% ?! u& N* i
20; r2 ]; D L T, I! H1 i& H
21
) r, \' `9 V) v# L% y22
, g0 \, Y/ c. L3 i4 s1 y% X0 P 如果不想指定ordered=True参数,那么可以先用s.cat.as_ordered()转化为有序类别,再利用reorder_categories进行具体的相对大小调整。. a/ y3 w' k( S
. N. ^5 v9 |% B4 ~! g& }$ j
9.2.2 排序和比较
/ c) K1 ]7 K h1 I7 Q在第二章中,曾提到了字符串和数值类型序列的排序。前者按照字母顺序排序,后者按照数值大小排序。
: s" c- y i# a
' L' V. {. [' B3 z 分类变量排序,只需把列的类型修改为category后,再赋予相应的大小关系,就能正常地使用sort_index和sort_values。例如,对年级进行排序:$ n( }. E5 ^% V, H. a
4 r* n4 V: Q& p+ d8 w' @
df.Grade = df.Grade.astype('category')
) f1 R; X/ \9 f8 J/ Mdf.Grade = df.Grade.cat.reorder_categories(['Freshman', 'Sophomore', 'Junior', 'Senior'],ordered=True)
+ |; A1 W E+ \ y7 H$ Rdf.sort_values('Grade').head() # 值排序 \, f/ @- f7 |5 b4 Y- b, N
Out[28]:
2 _% {2 D0 a' I. `$ E Grade Name Gender Height Weight
( x2 O1 ^5 \" P3 @# s, j% e0 Freshman Gaopeng Yang Female 158.9 46.0
0 H+ w6 i! S: V1 q- w$ q/ [105 Freshman Qiang Shi Female 164.5 52.07 m5 t5 c+ K h0 n0 z5 }) d
96 Freshman Changmei Feng Female 163.8 56.08 X5 _% z2 s# R3 s5 M6 ?0 U/ \
88 Freshman Xiaopeng Han Female 164.1 53.0
( O& K, E- T# ], o8 G% f* C! l81 Freshman Yanli Zhang Female 165.1 52.0
# s9 |, Z+ j% R" W% A6 t" H4 a! i/ {7 c# d
df.set_index('Grade').sort_index().head() # 索引排序) D$ e4 {3 J- \7 S
Out[29]:
* k: e, ?1 ^+ i# n Name Gender Height Weight d0 D7 D I5 M+ N9 B( V) g n
Grade $ T0 S2 e( A( o* M% Y
Freshman Gaopeng Yang Female 158.9 46.0
% T. y( y$ M- t% b1 [1 rFreshman Qiang Shi Female 164.5 52.09 x# q' f6 N" @( Q
Freshman Changmei Feng Female 163.8 56.0
7 p2 }" L) j# _Freshman Xiaopeng Han Female 164.1 53.02 j% f6 v+ r H2 s6 h3 y0 x5 k* B5 ], d
Freshman Yanli Zhang Female 165.1 52.0
" R* |* \& f/ P' g4 G0 z& O# h5 j+ a- f0 k0 T' S5 `5 W
15 q# z+ N- }8 F% ^- z
2
! `. C2 w1 K0 l3- T4 u1 E) ~. [+ @6 j
4
4 N; d) j5 e3 `0 F5
; t" I" D$ D, g: J/ \3 ?6) \ M. x/ O( a% J* K1 N& {
7
0 Q7 V3 b. h7 T& y) F' D8 B- r/ Q8
% g4 n2 v! ?7 Q9
& G6 G o' c' t$ T8 X10
8 s/ i+ ^9 z; l* }5 m11
. a! V& a i& I( o' p, G \129 \) o2 }- g: E2 V2 {3 M
13
5 z5 b' q! m z$ d& i/ I! A- N, ^7 b% v14# W1 |4 P G/ n, `, Y
15
6 X& f8 [7 ]$ v. l" k& j161 m1 n0 b- \* |, Z8 F; l
17) f$ O: O, j( T4 B
18
0 c9 C- Z) X6 S+ W$ B( e4 ~+ V196 h6 E0 \% Z0 k: [( n
20
4 ^# c& A$ d# a6 F. b 由于序的建立,因此就可以进行比较操作,方便后续索引操作。分类变量的比较操作分为两类:. O) K |, U! M$ A6 E% e
& _6 E3 W- K; D( ]2 h- F==或!=关系的比较,比较的对象可以是标量或者同长度的Series(或list)。(无序时也可以比较)/ ~ a& X# f2 z V+ }8 t
>,>=,<,<=四类大小关系的比较,比较的对象和第一种类似,但是所有参与比较的元素必须属于原序列的categories,同时要和原序列具有相同的索引。
6 v+ X% N4 q R a, X) vres1 = df.Grade == 'Sophomore'( h0 m, m: n4 R$ h2 I
1 o# b6 Z: `+ I8 u5 |. Rres1.head()
1 r2 K$ K5 k1 x" x, R8 f5 IOut[31]:
0 c- p. m6 E$ P4 n; k; L$ U0 False0 _$ J8 Q$ j: O* i5 f: W
1 False
( Z, @& r; |6 @) @) q2 False+ W1 W8 J) R3 S
3 True
- c! y. m. n" Z# d* S# u5 B4 True% J/ @7 o% l1 f* L) A
Name: Grade, dtype: bool
; J H" t& Z1 _2 b: d
w* p+ O- d7 qres2 = df.Grade == ['PhD']*df.shape[0]7 E, F! f f9 F$ u5 _
6 a- I3 R9 v/ O! T) ]res2.head()2 K- I+ \) o. s9 P. U
Out[33]: & m* ?4 Q. u. B7 { Q
0 False k* a i0 }- m# ?" f5 b" p5 G
1 False. q! ?6 g/ l( L4 `
2 False
3 E1 X+ D1 v F) D" S) Q7 g0 a3 False& ^; B* J+ ~% i
4 False
* E% C8 Z) L9 h* W; E4 K2 c! eName: Grade, dtype: bool/ p0 F: d. e$ L/ f5 F
+ O8 _: T) W$ g$ E' b6 }
res3 = df.Grade <= 'Sophomore'
q2 P ^1 t* K5 V0 D) o: u, r& h% @5 m
res3.head()
8 T; b' t6 o: N$ r( S$ cOut[35]:
4 z% O p$ ^1 m4 Q7 W1 m p0 True
" C* b" E$ y M0 s8 z7 ~1 True) y" `/ Y& P+ b- I$ H, {
2 False" C4 N7 F( T9 z- J0 m: H, R" H4 i
3 True
8 C" C L. d2 v! [3 i& S4 True+ b7 K5 [* k$ |, y; N) E1 f% \' _* X
Name: Grade, dtype: bool
7 N7 q1 H0 a1 a. y/ }, f$ r( j7 |; L1 m5 y5 `7 ?1 @
# sample(frac=1)表示将序列随机打乱。打乱之后索引也是乱序的,直接比较会出错,必须重置索引。
+ E' \6 x' G% } \' j" z9 hres4 = df.Grade <= df.Grade.sample(frac=1).reset_index(drop=True)
$ X. m( ?3 B: i9 }
z7 U; ~1 p/ T4 q1 F9 jres4.head()3 z* C! n$ \0 h" X% H
Out[37]:
: Y4 M: N2 c: \1 a9 t4 }) G* Y0 True0 y) o4 x) h8 V5 O
1 True
; R' `$ C; |: V( `! x( Y* k# h2 False
9 o: u3 h+ }. s# m; r5 K3 True# W4 \; p+ A0 a
4 True1 M; ~8 w4 j- |+ J5 e
Name: Grade, dtype: bool
- }' d2 b1 Y% z) H0 x
3 w: q# S- j& h& O3 z5 n1; t# i9 H, r7 `$ F1 i2 B9 b
2
1 [7 l9 R' R( x* g+ W2 k' e3
. i: l1 Y. | c3 I. J( t. l4/ Z2 S c/ o5 |; G, W: r
5
2 v! k& J; t( l1 v4 k2 J3 K5 R) J6, S+ i$ \/ ~9 f) h; h
7
/ y& y' M1 @5 N# A6 Z8 s8% S' X7 s$ k k4 G, X. G) X
9) E/ H, s0 _# z( X& t$ F
10
2 o) c9 h! l5 D11: m5 v7 ~1 W6 E2 t
12. d! {: T7 n$ Q# g. ~' K$ v$ ^
13
0 R' y( z+ I& O* c14
; f7 n) m% R+ m0 C/ h15' L/ Q! S% ?7 @! P# F5 k/ ^. F
16
: r& e! u& t; ~/ B, E17; S/ z. e; U/ R8 E4 R
18( V2 g4 |6 A0 }
19
) \1 S J) @5 X! S) w0 N206 F% D! z: s9 M# n* X8 _
21
; j- A" I3 {8 q5 G/ W22
4 D. m: w6 ]* i7 K) b23
' _& ?, b5 |( y8 e) |245 K9 k9 d, V3 x. H- s6 {
254 T8 k. q o" x1 U8 Z: o: h
26 m1 e U$ K( W' S7 \
27! S2 d( n; T, w4 e; n$ @8 X
28
: k% W3 C1 f- i6 N) H- m/ ]' o29' O. S" t2 h0 ]& ?9 M
30 t3 @* I A3 g! F# ~
31
- O. p6 H2 {# Q32. V6 ]. L1 d4 M' [6 b! f
33+ P; r* s% d. s9 Y: y! i! ?
34
. F0 j) Y, D3 z1 j1 U35# R( p7 e. l5 S& ~ U
36% \+ u0 U/ @0 r4 m x
37' A6 Z. h6 [$ U B
38
! |- B0 l" H, o& X' [4 L% K39! [4 k5 s6 j R, g2 r1 l
408 N1 y2 W9 j q8 h6 c
41% O) i, B4 z- X+ M) C) Z
42% v, B7 h( j; T# g5 w/ K
43
* Y( g6 \7 D+ E) M8 P8 s44
& [7 E" S$ G/ ^4 Z' n7 t/ r9.3 区间类别
& V: S0 D+ X% O" I9.3.1 利用cut和qcut进行区间构造( }8 J5 ^- Y0 O {7 I
区间是一种特殊的类别,在实际数据分析中,区间序列往往是通过cut和qcut方法进行构造的,这两个函数能够把原序列的数值特征进行装箱,即用区间位置来代替原来的具体数值。
) w, A" \3 w) J, c" ?
" P5 {5 `9 n, _' f) l0 K4 N# Kcut函数常用参数有:
0 r/ n Z6 g' F$ T" xbins:最重要的参数。' F( N7 J Y. ]/ {- O1 @
如果传入整数n,则表示把整个传入数组按照最大和最小值等间距地分为n段。默认right=True,即区间是左开右闭,需要在调整时把最小值包含进去。(在pandas中的解决方案是在值最小的区间左端点再减去0.001*(max-min)。)
+ l2 F( Y2 _# z }( [! j2 x5 _* Q也可以传入列表,表示按指定区间分割点分割。
0 I# p; ?! C% U8 D2 b 如果对序列[1,2]划分为2个箱子时,第一个箱子的范围(0.999,1.5],第二个箱子的范围是(1.5,2]。7 w G8 Z( f+ c! ] o
如果需要指定区间为左闭右开,需要把right参数设置为False,相应的区间调整方法是在值最大的区间右端点再加上0.001*(max-min)。* o# [: U( w0 p; H
: D* `/ c0 S$ _! H2 us = pd.Series([1,2])
3 @( }, k# |9 x% X4 \# bin传入整数
- Q% L) L' k9 f/ H2 b
! E# I9 ]& o1 L3 {pd.cut(s, bins=2)
; B7 s, X2 W% T: Y% I* x( l1 eOut[39]:
% i# _% N( |. D+ F. v6 b0 (0.999, 1.5]
% p7 p0 [. m! V& T$ p1 (1.5, 2.0]! H0 U& n: \- u) |' ?
dtype: category" Z3 t) m( N& K2 x
Categories (2, interval[float64]): [(0.999, 1.5] < (1.5, 2.0]]7 Z6 r) ]# g1 a
) e8 e! y& c- x' v" x: s) _' ~pd.cut(s, bins=2, right=False)
$ g9 m5 ~' {2 a2 ? pOut[40]:
+ i3 P/ s3 E7 ~ X0 [1.0, 1.5)
1 k) L" |! \2 C1 [1.5, 2.001)! j. {. i( v; E
dtype: category+ v1 E/ O6 e6 J; o) ~& c, ?- z$ s
Categories (2, interval[float64]): [[1.0, 1.5) < [1.5, 2.001)]0 x$ }; B. N3 Q A8 H
0 ?+ Y" r* N9 A
* F+ b- N* d7 ]
# bin传入分割点列表(使用`np.infty`可以表示无穷大):. O0 o# a | {8 X! T
pd.cut(s, bins=[-np.infty, 1.2, 1.8, 2.2, np.infty]). ~$ g3 [6 |6 e2 K5 l+ q
Out[41]: R2 X& b6 |1 u1 i0 H. t7 y0 @
0 (-inf, 1.2]/ A2 r/ t+ H# F9 f8 F! I
1 (1.8, 2.2]
+ e2 `& K, d1 zdtype: category6 P% J) x+ |5 N/ l6 e/ |2 q
Categories (4, interval[float64]): [(-inf, 1.2] < (1.2, 1.8] < (1.8, 2.2] < (2.2, inf]]& B. w" ]' q$ T8 a9 _* o
) q: I9 X, L, N [
1
* m/ \# u8 Z, D0 a; \$ X+ n2
2 u1 j/ t0 ?- {3& d0 z+ N' D! A% f6 {& Y! `
4
' u2 {5 [: E' d' L: E' z$ a0 c3 H2 c0 o54 ?8 s+ H5 d3 {, B" {
6
0 O3 J; n/ R: }: g& N: }3 @' i$ N7" m- e0 W: T4 Y
81 | {, C$ x: r
9
# x4 O% I; M8 y! f+ Z2 k. |10( u: \) u* a1 D, b% J% X8 ^
11
: `3 [+ T# |. s) t/ ?12
8 F1 I, P, Q( u; O* t* s133 Z( G: _) u: N5 s
14$ [: i$ A3 ^2 Z9 r: s {7 H- P2 A
15* K1 P2 X8 c" U1 |9 U0 G) X. r
164 U" t4 c2 x+ \5 {
171 r3 y! E4 b2 e2 X' Z" B
185 f L3 f9 o! O5 j5 I& H9 O
19' a ^7 N4 Z8 |9 W5 n- `* Z; _6 t
20! p4 I7 J5 r v& `. h" {
21
% \ E9 \2 E3 J, g; x: r' ?22
6 H C5 J A9 Y* b1 h- b23
3 m2 T* l% X ]6 H24
+ k ^) I# s6 E Y25/ z6 u' [8 a: s: R7 a7 |" {
labels:区间的名字
/ k- ^. @ b) [3 xretbins:是否返回分割点(默认不返回)
5 h3 a7 h# g+ p默认retbins=Flase时,返回每个元素所属区间的列表
* c6 L8 p* Y: u" v; M. x# kretbins=True时,返回的是元组,两个元素分别是元素所属区间和分割点。所属区间可再次用索引取值
" x+ N& t/ G: R0 ~; s
3 m, M L; i2 _' T$ R+ k0 @s = df.Weight- E2 u9 ~2 R' ~ {& V+ y$ _. f
res = pd.cut(s, bins=3, labels=['small', 'mid','big'],retbins=True) R+ ]9 X3 E% k. ^7 \+ Y: M
res[0][:2]. n/ Y! o' r4 l8 ~% g
Y$ T c$ s5 q; L- X. R6 W# E
Out[44]: 7 A* H' V" m8 ?5 l" v' t6 P4 r a% B$ c
0 small0 K, ~1 ]( p0 b' P& x
1 big. C/ K( c% v( M8 Z; p
dtype: category
0 B, ^6 \ }/ _Categories (2, object): ['small' < 'big']
# U' R/ \9 @- I& c9 e# ~# ]
8 q4 n7 R [- }' T' N, `( |res[1] # 该元素为返回的分割点' v1 \! @+ v; B' ]
Out[45]: array([0.999, 1.5 , 2. ])- V* M( Y# X x, a
19 ~+ A% ]1 h, U' p T' Z3 ~
29 M$ a# \/ @1 v/ U1 [
3
! n& H( i: g$ D% {1 u8 P8 w4' b9 k4 Z. q3 D; T; n5 Y
5
, x& B! E+ V B, Z5 w5 x6$ j- T" s" v; x: c- `
7
( m" M$ i3 a* B u$ ]8/ k6 r1 z) p3 F3 M i+ Y/ F
9% M) u4 l1 s7 m" U5 W
10. @2 [ ]3 A, T1 t" @. f
11" T7 i; }7 P6 l0 I1 t
12 G: L/ ?' h: {* ^
qcut函数。其用法cut几乎没有差别,只是把bins参数变成q参数(quantile)。% p( A4 s* \: D8 o$ L! z
q为整数n时,指按照n等分位数把数据分箱
* M4 \, o5 X+ S- s" jq为浮点列表时,表示相应的分位数分割点。$ L- c; } }& c4 y" R' S8 W
s = df.Weight1 S/ N% j" ^4 q+ l4 L/ @8 @; n0 D5 w
+ V5 U. E# W$ q* ^5 {
pd.qcut(s, q=3).head()" P" t! H; D6 m, t" N6 Y+ u0 U3 v
Out[47]:
$ _# b% k! G9 M& Q$ B" W8 m0 (33.999, 48.0]
0 ^1 E5 I3 B' h" R# a/ |! Q5 i- q1 (55.0, 89.0]+ Y9 ?& y: D4 s1 K- w) G% M+ N
2 (55.0, 89.0]
- {0 t* W7 C7 _' e3 (33.999, 48.0]! `& T m* x9 i
4 (55.0, 89.0]
' ~7 d; n9 K& lName: Weight, dtype: category9 o4 {9 o- I5 R! r% _9 p
Categories (3, interval[float64]): [(33.999, 48.0] < (48.0, 55.0] < (55.0, 89.0]]- ?! L3 v2 S; f6 ]7 j6 s
2 A- |# ?" B9 b3 I" `
pd.qcut(s, q=[0,0.2,0.8,1]).head()0 k9 M; P1 R7 R7 o1 b. ^
Out[48]: ; C; Y2 m9 V7 g1 K
0 (44.0, 69.4]# d. r, |/ K; [- e
1 (69.4, 89.0]
! T9 l. o) ]- F2 (69.4, 89.0]
5 B m9 p% |3 f7 `+ f! e3 (33.999, 44.0]9 k) S* P3 X$ h' Z
4 (69.4, 89.0]
( u& ?7 a( y; b8 N2 I/ N$ vName: Weight, dtype: category
% Y: b( Z! O5 Q% N" ACategories (3, interval[float64]): [(33.999, 44.0] < (44.0, 69.4] < (69.4, 89.0]]. ]& ?8 {# w- k* R3 T# n6 T
0 h5 \- [# k. W7 s7 n6 L i- F& q1$ R4 I) x1 q( i- ?( l0 e1 M& c8 |
2
8 \/ g/ L) K8 k# }; l3& U$ H4 M* e" T
4
# Q; q& {* L5 F5/ }5 Q% x& X" j! l# |; _
64 h9 b @ z. m$ @7 Y; h& Q
7' W* N0 ~4 W4 O. u/ o6 C
8/ r8 j2 W }: P" v1 d5 \# L+ t. O
9
0 g6 M: u' o" S$ v, d4 [& d) l10% @9 E% ]; D- M' y
110 m" U: @. z. m* X+ p' N
12+ @7 |$ s& x' g: f9 v O
13* C4 ^* r7 F/ ]( u3 r
143 v- A4 y. @- y
15
: i" |- P4 q9 [2 _16& [" e/ R6 s x) D
17
6 D2 d* N3 K u z; ^3 L8 q7 b18
4 G3 I& M5 X5 @) x& ] k* A3 J19: R, t5 q5 [" N4 o
20
" H# T+ {# u, L4 M; q21" k6 P- f" D- w1 P( D, P
9.3.2 一般区间的构造$ y7 U+ R. H9 S$ g. x
pandas的单个区间用Interval表示,对于某一个具体的区间而言,其具备三个要素,即左端点、右端点和端点的开闭状态。6 y! G6 g& Y) G# h5 k7 A2 r
% v9 j, B4 u9 r8 \& |1 W开闭状态:包含四种,即right(左开右闭), left(左闭右开), both(两边都闭), neither(两边都开)。( _0 z( F9 ^9 J& ?2 {+ d1 u
my_interval = pd.Interval(0, 1, 'right'): I d3 H: T ?* Z
/ s/ l+ V- Y9 A# ^6 amy_interval& j# |/ w- f+ l5 n1 T, A
Out[50]: Interval(0, 1, closed='right')
, I+ v% v' h$ A* @+ M' R! w1
# x$ M! \! d6 c! }% T$ n& V& Z22 M, y+ S! h3 c3 b- K
3% d, p; Z A: h9 X+ U6 z+ L
4
5 N9 ?5 ], L2 P" e8 V8 L区间属性:包含left,mid,right,length,closed,,分别表示左中右端点、长度和开闭状态。" f1 [* Z. I8 B
使用in可以判断元素是否属于区间
2 |3 K3 j: i) z1 @$ S6 Q用overlaps可以判断两个区间是否有交集:
! k' O4 }( x; s- E/ J0.5 in my_interval
: v/ v- k$ j. X: q# z' p$ \4 R& K( F7 `2 }
True
# n8 r+ K! j r$ e12 ~0 H' F% N5 v0 b( J
2
6 _# `1 ^% ~6 L2 B* L3
. p# F/ D$ H) M1 x9 r4 `; j% E" nmy_interval_2 = pd.Interval(0.5, 1.5, 'left')- f" ^/ P* T0 p, Y, T
my_interval.overlaps(my_interval_2)
- W3 k* j/ {) V: q' H9 {& o+ q+ f. }% p
True
$ ~) s9 L# ~9 [1 O& l1
% {& ~( Q i/ K4 m28 C0 ], T" T# H& c' `
3
5 S" S, d# d6 p6 p `. F! n) u. Z6 W4
! b0 r; A2 ?( X pd.IntervalIndex对象有四类方法生成,分别是from_breaks, from_arrays, from_tuples, interval_range,它们分别应用于不同的情况:9 z2 S1 C3 j+ M$ ]- g! j7 S% z) x
5 f$ A* H; j8 N" s5 l9 ?from_breaks:类似于cut或qcut函数,只不过后两个是通过计算得到的分割点,而前者是直接传入自定义的分割点:
$ ~4 D6 c, z+ X. ^pd.IntervalIndex.from_breaks([1,3,6,10], closed='both')8 j/ M# \1 T2 L- T4 X) v
8 O& A2 n3 g. j
IntervalIndex([[1, 3], [3, 6], [6, 10]],
5 P- h" X$ k3 r( x9 D/ b closed='both',
( Z: N$ g" g8 ~1 l) V5 W dtype='interval[int64]')
# U; k/ [+ N: K: {10 W9 H/ C F- r/ v# H6 Q, M" f
26 X, }0 X5 @- k. R0 P
3- T; D8 F! L+ n" ?( d |
4
% v( x6 u" s# t) [; g( x5# C m. J/ m( S- v# u9 t2 Q. Q9 \
from_arrays:分别传入左端点和右端点的列表,适用于有交集并且知道起点和终点的情况:
& y9 _2 C5 C( R! B apd.IntervalIndex.from_arrays(left = [1,3,6,10], right = [5,4,9,11], closed = 'neither')
: L1 J+ k' q$ \5 b! `3 R; t' d* l/ B0 T6 ?/ G8 R# B
IntervalIndex([(1, 5), (3, 4), (6, 9), (10, 11)], c' |* w- s0 I& `0 s( P
closed='neither',5 _. y; u; X9 a8 d* I
dtype='interval[int64]')
c- i( M# V( Z) R/ A1
( N8 v$ S7 A7 r+ d# j6 G7 ^! D2/ ^; A5 m, p" f9 a
34 c1 G, z) T) C- h: R
4
t- ~ A+ l6 q1 b& n8 R' n4 M5
0 `; C8 Q/ v& |from_tuples:传入起点和终点元组构成的列表:
: [0 |+ r$ m( L- Vpd.IntervalIndex.from_tuples([(1,5),(3,4),(6,9),(10,11)], closed='neither')! e- K \8 \# w* F) q0 `0 L
8 p+ Q5 ~$ s0 k, P% j( u( y: u4 mIntervalIndex([(1, 5), (3, 4), (6, 9), (10, 11)],( m+ K8 j( k0 y; v
closed='neither',
' `3 @6 C0 o, d" O8 B% r dtype='interval[int64]')
5 E& j& J' C9 h# }, {1
0 [0 K- L- `; ]2( @8 j3 ~! \ t/ J/ [" }% B
3
0 f# ~0 S( J. P( C; j4; Y ~4 O- ] I; u% o
5
$ j/ R/ u4 G0 n1 k/ {interval_range:生成等差区间。其参数有四个:start, end, periods, freq。分别表示等差区间的起点、终点、区间个数和区间长度。其中三个量确定的情况下,剩下一个量就确定了,从而就能构造出相应的区间:& g. w0 ?, X0 S- C
pd.interval_range(start=1,end=5,periods=8) # 启起点终点和区间个数
) d* w* j9 c2 f" l0 Z+ pOut[57]: 2 q8 Y2 f; K% A! R8 C% Q
IntervalIndex([(1.0, 1.5], (1.5, 2.0], (2.0, 2.5], (2.5, 3.0], (3.0, 3.5], (3.5, 4.0], (4.0, 4.5], (4.5, 5.0]],- U) p+ ^: a1 G8 T
closed='right',9 W- d6 d3 s8 A! p& X+ @
dtype='interval[float64]')
|: x- a( l# U, @) A3 K; R. d- }3 ~
( {$ j0 g! g1 {. o9 w L6 epd.interval_range(end=5,periods=8,freq=0.5) # 启起点终点和区间长度/ R6 e1 `. U2 D. L* Q. I
Out[58]: 9 D8 r) q* Y. ?! w
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]],- {% N! J, {& w( [4 v; s# q
closed='right',
! O. c9 P1 k: x4 W dtype='interval[float64]')2 w. b$ G t/ }
1# y Q. v9 ?3 _
2
1 W% e+ g! ?7 ]3% ]4 B- P% |% {* d4 ^6 R
4
+ K# P/ m3 F( ^& A Z$ `5# ~6 x z7 h9 i! w( a- w, j
6 O/ l }6 d4 O' J
7
* k; {" ^) w: \% c2 ]5 U: m, n4 o8
% Z( Z. A3 x1 f# V; u. u9
+ m% u2 d/ l w1 z) a" T100 X! Z& O8 t) ^# r8 \
11
9 q. }9 x. p- f$ A【练一练】
0 {5 H9 B. w, s1 Q 无论是interval_range还是下一章时间序列中的date_range都是给定了等差序列中四要素中的三个,从而确定整个序列。请回顾等差数列中的首项、末项、项数和公差的联系,写出interval_range中四个参数之间的恒等关系。7 z4 j Q$ ^ Q% j/ q4 m
% `) u$ u0 P% w2 n8 i 除此之外,如果直接使用pd.IntervalIndex([...], closed=...),把Interval类型的列表组成传入其中转为区间索引,那么所有的区间会被强制转为指定的closed类型,因为pd.IntervalIndex只允许存放同一种开闭区间的Interval对象。" n: w/ b3 l1 D- ?
/ f8 W8 u$ v7 D, E& Z
my_interval
1 m) J' X: @4 [4 sOut[59]: Interval(0, 1, closed='right')
1 E) u3 {- n# r/ z9 l7 Z# ?7 L) p! @
my_interval_25 h# r5 n0 ?$ I% r9 d/ g
Out[60]: Interval(0.5, 1.5, closed='left')
$ J) N% x% ]* b7 D8 p5 z4 u: ~/ v+ A- X+ Z# A; c
pd.IntervalIndex([my_interval, my_interval_2], closed='left')$ p+ t0 Y: S+ w- f# D' ^3 W
Out[61]:
+ Q. p& D% k- X6 G% pIntervalIndex([[0.0, 1.0), [0.5, 1.5)],
" ]+ ^- J- @: G A closed='left',# X0 I9 U5 V' H9 _
dtype='interval[float64]')
$ W" I; i6 z5 l1
5 D# z/ [4 F3 ?$ n24 }; @* ?7 n7 ^. O7 b* h( ?+ L# B
3 P# Y% C. i/ b4 b
4. ]5 s: b) m+ L# P' o. A
5
5 J+ S n$ a0 ?6 m" G6
0 v* o7 ~6 U! d$ H. `0 a7* P# U% K/ g% _- M! q% |9 ?; E# F0 V' Y
8
( N' X) T0 Q8 K& Y3 E K9
' {: e3 G6 `) U- o8 H1 \10) U B8 n% x( x0 u6 x2 _' o
11
1 g( z V9 b5 B9.3.3 区间的属性与方法
" ~1 n% e( F% J$ d. c) X; I IntervalIndex上也定义了一些有用的属性和方法。同时,如果想要具体利用cut或者qcut的结果进行分析,那么需要先将其转为该种索引类型:; P' T' l8 J8 ~5 o. x, g" i J
8 W0 r" y- b! v. ~7 e$ Us=df.Weight0 z1 @* I# }5 y+ u* N ^$ @
id_interval = pd.IntervalIndex(pd.cut(s, 3)) # 返回的是每个元素所属区间,用具体数值(x,y]表示
2 F8 g5 l% q& \0 }* k- jid_interval[:3]
$ J9 i) D7 s0 Z7 @# C
+ y0 z7 H$ ~3 _1 `IntervalIndex([(33.945, 52.333], (52.333, 70.667], (70.667, 89.0]],
/ [0 |9 d* {& Y/ y! n closed='right',1 c2 i% k$ ^+ i- C1 k
name='Weight',
3 M( t5 b S( t6 C4 ~" R1 P dtype='interval[float64]')0 T* E: I$ U: y* o8 Z0 `
1
3 _9 G e+ S) w9 T/ v; I2+ P# F0 i8 J$ ~. S' H. v
3
+ w: s* h2 @2 k, w1 R" x3 B4
. W; K& B1 B7 M5. N% W9 I" a9 \2 r9 ~% w0 [
6
( q4 |8 ?1 C. x: q( `" B5 |7
% \: }' S( l6 K: Q3 C8& ]; g9 g% d+ c6 v( ]
与单个Interval类型相似,IntervalIndex有若干常用属性:left, right, mid, length,分别表示左右端点、两 点均值和区间长度。- _# W0 Q; G9 ^" R [* u; E
id_demo = id_interval[:5] # 选出前5个展示' ?- t9 U) Z. \" Z
: e; E" N- | f$ aid_demo. q" {7 r% u7 _/ A" s, v
Out[64]:
2 e4 N. N6 ^/ Y7 i4 r+ [IntervalIndex([(33.945, 52.333], (52.333, 70.667], (70.667, 89.0], (33.945, 52.333], (70.667, 89.0]],
9 [: @% P) M: _' W2 a% l4 G. | closed='right',
6 j% ~! J# V( G8 z7 P; K* | name='Weight',0 y) F2 A# W5 L- I3 D' r
dtype='interval[float64]')
' {* Y4 _$ Y( z t5 b6 l0 w: w7 U2 b+ \( s2 a5 \
id_demo.left # 获取这五个区间的左端点
[. z3 I. B: E1 E& N% |" r0 @) ?Out[65]: Float64Index([33.945, 52.333, 70.667, 33.945, 70.667], dtype='float64')
$ G; c* c8 t9 I4 N9 P
! Q+ N5 \# Q. Mid_demo.right # 获取这五个区间的右端点
) I1 V8 r. K( q: V, F2 mOut[66]: Float64Index([52.333, 70.667, 89.0, 52.333, 89.0], dtype='float64')
& ?" b/ i6 G. a/ m5 y6 S) F8 O, f. K- E' O* Y% Y
id_demo.mid8 J* s) p. n# l1 l! }2 E
Out[67]: Float64Index([43.138999999999996, 61.5, 79.8335, 43.138999999999996, 79.8335], dtype='float64')
2 R$ e* p9 y: n' w7 V$ h
: D4 z, O+ S$ }id_demo.length, b6 t+ F; L6 K" S% ^
Out[68]:
: Z" B, {7 O: I- i- }8 Q0 PFloat64Index([18.387999999999998, 18.334000000000003, 18.333,6 k) U- `2 ?8 O& F6 f! G" T6 L, W
18.387999999999998, 18.333],
3 ?2 v" i. J8 \ dtype='float64')$ ?5 {2 x9 h+ L
# D0 l! |# u* Y0 r/ t" _3 g) s
10 y4 C; p, c+ _, o$ U4 a# E& B# P
2
& E# U% |3 D8 L5 I0 L3/ D1 \5 Z8 X7 Z3 L$ l' n
4
& i. |* f8 d! n2 \' A+ t: t* A53 Z1 `% M- \6 K6 h! A
64 s1 D/ ]" j+ i- k4 W/ `
74 d( Z4 p4 h% K/ S$ f9 W. x
8- H8 C; L6 S2 T) `$ @* Z/ V8 c/ B
9
8 e2 \. R3 A$ q0 f$ e- q10( l* K, l5 ]" _
11
/ z( G# I8 v9 |' X1 ^ ]12& R$ T2 Q+ P3 `% `6 Y; [
13
! t8 Z( s' v4 E% T( }7 z- k+ m14
( V/ ] M/ c. @! q15
( c$ @2 G2 ?0 F16
8 e# ?" G: _1 {" B: e17: R/ m& D7 t8 N- C
185 H1 D6 i' L) }0 x8 m+ h
19
2 M0 n" @$ W2 f( t4 T. {- l20
0 o) Y/ [1 Q( f4 C. A/ y! M21! o6 ^. c* E* }8 r
22" k" t, s) |# V
23
1 M7 \' I: K5 a' P! W! vIntervalIndex还有两个常用方法:: b. e4 K! ~0 _: y0 l4 ]
contains:逐个判断每个区间是否包含某元素
' U& t& Y0 p; _8 K4 coverlaps:是否和一个pd.Interval对象有交集。
, K& M% M7 Y: Gid_demo.contains(50)/ _! O* v' n0 ?( j- R* {+ Z; e
Out[69]: array([ True, False, False, True, False])4 R8 ?! s5 d( ^* C; s
/ G4 U' z0 S* ?# y& M; `
id_demo.overlaps(pd.Interval(40,60)); S6 d S# `: R2 L; {
Out[70]: array([ True, True, False, True, False])5 D$ B, X+ H* M$ \$ l' k
1
1 R. Z1 w6 v% d& x* Z7 R# s2
0 Q; ]$ Q- s. C5 l39 m; ~3 x: p7 C5 u, g8 M* D
4# T! c+ z8 n d0 x3 c' k
5
5 B" ?) O9 d8 _9.4 练习& C' a o# q# ]; O8 q
Ex1: 统计未出现的类别
* |, J- A% n; P) P# ] 在第五章中介绍了crosstab函数,在默认参数下它能够对两个列的组合出现的频数进行统计汇总:
1 b7 A$ S; ^! S7 C) ]6 R+ I+ n
df = pd.DataFrame({'A':['a','b','c','a'], 'B':['cat','cat','dog','cat']})" d% g' g6 @* J
pd.crosstab(df.A, df.B)- V" d3 A. r n8 e; [
5 J p) j" I. G1 `' W7 u! K2 W
Out[72]: : m0 q1 h. d- \% W* O' \
B cat dog$ I; ~8 F. {) C
A - }: ^! s5 ^: K
a 2 0; z0 }$ l+ n* G4 T
b 1 0
/ L6 D4 ?# j+ _% Hc 0 1( v- \9 F9 L: I2 g+ L T
1 ]/ i: N4 n( `! v4 n+ g v! a
2
" }- M% w+ Q. y' w3
3 s9 E" k' q2 s" |4
4 }( T8 ~ F% l& h8 x% `# f5* ^ w' H& \: u. ~
6
' u2 U: @1 k7 B l$ y. B1 \0 B70 n/ B! T7 A r7 h1 Q
8
$ J, L' ?/ ~7 R& w9
3 r9 e6 [' h. D" P+ O, d. c2 U 但事实上有些列存储的是分类变量,列中并不一定包含所有的类别,此时如果想要对这些未出现的类别在crosstab结果中也进行汇总,则可以指定dropna参数为False: Y' @0 V4 F1 {4 r: H# U) e
; B+ X/ I5 x; |9 L' J# k: \) C0 X( e0 v/ a
df.B = df.B.astype('category').cat.add_categories('sheep')
; j3 g3 d: `2 G7 l ^% U3 z0 Ppd.crosstab(df.A, df.B, dropna=False)
$ L& t. u3 g I2 c# Z
5 E) }; ?2 \6 r5 z* C' w; bOut[74]: ' T/ N! j) g& X- D
B cat dog sheep
( |( T& S2 G, P" }( {A
7 u5 [ s2 M+ |3 v; p: ~ t9 sa 2 0 0
- ^6 u: b( f+ B& R1 sb 1 0 0" \; { A& f. B ]1 z) X2 p- e
c 0 1 0
; G- [) I+ Q9 R! ^1
# ^* O2 u3 o9 ^/ m* C2
, o% S+ w' i: R/ A- L3
# h! E4 B, x, B3 x49 n* i. Y. Z) F
5 v& _: |; h/ N* X
6' K8 H- Q7 A# {4 J/ |1 g; @5 S2 m' E
7
" R" B; Q7 A' N4 H0 B$ d/ j. N8 g8- G& J# B- }" i3 T2 C8 l8 g
9' X" c) a4 I; N! A( @# o8 h6 J: J+ K) D
请实现一个带有dropna参数的my_crosstab函数来完成上面的功能。8 B: u3 s4 |. V) u* G: m# @
5 s- `* T% Z5 \7 N7 N. UEx2: 钻石数据集
; c8 W& x9 z+ l( B. K7 ? 现有一份关于钻石的数据集,其中carat, cut, clarity, price分别表示克拉重量、切割质量、纯净度和价格,样例如下:
! Y- e/ Q% R& H# U7 y H6 q0 `; n, n) y% E2 i! v
df = pd.read_csv('../data/diamonds.csv') 6 B) B, n* V; u. G* ~" M: B
df.head(3)
6 e9 I n0 ]; ]8 x' l" o. A: ~
4 h. @6 Q0 @* }# _- y1 lOut[76]: # }2 B. q" i! b% q) V9 q
carat cut clarity price
/ ~0 S1 f {* r6 |, R6 S5 H5 d0 0.23 Ideal SI2 326
; S' D7 ]( E0 j1 j [, `+ U1 0.21 Premium SI1 326
! h, j9 @ j: G2 0.23 Good VS1 327* Q6 Y0 v+ J" b- p' m" U3 P7 T
1
' _" k; } j# H; R1 b- {1 t2, t; t3 N! i( b
3
& [9 A" [5 w9 X4 X% J48 H7 J# H! D2 d/ u' W& O
5
+ ]4 q6 ~ S+ p6
* J2 N7 h _$ V9 E8 u8 V7
9 _. F( z/ c6 [, Y$ N7 J8: D: }) C5 D0 N+ l3 Y% B3 U
分别对df.cut在object类型和category类型下使用nunique函数,并比较它们的性能。/ X/ }( |; @9 f5 N
钻石的切割质量可以分为五个等级,由次到好分别是Fair, Good, Very Good, Premium, Ideal,纯净度有八个等级,由次到好分别是I1, SI2, SI1, VS2, VS1, VVS2, VVS1, IF,请对切割质量按照由好到次的顺序排序,相同切割质量的钻石,按照纯净度进行由次到好的排序。0 C2 A( p: A! u- f, m6 k
分别采用两种不同的方法,把cut, clarity这两列按照由好到次的顺序,映射到从0到n-1的整数,其中n表示类别的个数。
3 M+ V" Y6 G6 S) ~对每克拉的价格分别按照分位数(q=[0.2, 0.4, 0.6, 0.8])与[1000, 3500, 5500, 18000]割点进行分箱得到五个类别Very Low, Low, Mid, High, Very High,并把按这两种分箱方法得到的category序列依次添加到原表中。
+ x9 S) [6 K+ z8 { w第4问中按照整数分箱得到的序列中,是否出现了所有的类别?如果存在没有出现的类别请把该类别删除。
3 K$ r ^, A4 _" p对第4问中按照分位数分箱得到的序列,求每个样本对应所在区间的左右端点值和长度。5 G( P5 X j7 n+ j: {* o
先看看数据结构:
$ u2 Z$ p: m8 G: C+ g/ A Z. o1 j' [5 B4 a' @3 ?5 A! F0 J3 [
df.info()
0 P/ Q' u1 \: xData columns (total 4 columns):
5 g5 H, _, o& y( i3 T! S$ N # Column Non-Null Count Dtype - c; O, d4 v! E& Z' n
--- ------ -------------- -----
# F5 @' ~3 n$ J6 v* ?9 S 0 carat 53940 non-null float648 d$ O d8 G4 q/ M% m; s
1 cut 53940 non-null object 6 q9 r& r0 P2 I, a C: o2 e1 B7 e
2 clarity 53940 non-null object
6 \# j5 }0 `. a( ^2 W# B" T) g 3 price 53940 non-null int64 8 ?# p( U% u5 B: g
dtypes: float64(1), int64(1), object(2)# r: l( c" B3 n& d/ `7 s9 B
1
# ^) i+ J9 g' Z) H2
: l4 w' `) f4 v! Z9 }, p3
$ F& k& M i$ \% t! i1 n$ X4% h% }1 \) B9 {7 L
5: V, n& s" ]' p# v; I3 @! V; ~
6, F0 [! o$ c+ w% Q& V# r, S5 c
7
+ B% ]6 O- w) o- o* w* H3 i, x8
, _( M! }1 r& R, @9& \1 _$ c6 c; A6 j' |* }* J
比较两种操作的性能
" w- ~+ Z) w6 G" C%time df.cut.unique()0 [; n- e% w% Z0 K+ `0 m2 O
8 Z" S6 u" |+ @# T, d! S" |Wall time: 5.98 ms3 B. f# F9 _+ V. H
array(['Ideal', 'Premium', 'Good', 'Very Good', 'Fair'], dtype=object)
/ T* \. f9 V5 v8 k" u Q1
1 c" z* C! Z( I$ Q# o; i$ S2* C( p, Y) L) v+ z
3
( c: |' G7 p4 E- `' _4
9 t' r1 V' k+ `5 h8 J% U" w%time df.cut.astype('category').unique()
5 D& Y }7 y* P9 C, B- a3 @! c$ v! M, F
Wall time: 8.01 ms # 转换类型加统计类别,一共8ms0 r% _9 x% j/ x& y' j- m" r6 {
['Ideal', 'Premium', 'Good', 'Very Good', 'Fair']9 o7 h2 X& F( n* q; @; p, t1 H
Categories (5, object): ['Ideal', 'Premium', 'Good', 'Very Good', 'Fair']
6 p$ p9 b% J1 C9 R8 ?. e7 e3 K1' t, M6 B2 S# s& ?
2
$ k% X9 T* }# j$ U% W0 }) w7 i% U- H3# L( e* L9 g! `! \1 X
4
/ @* B7 j2 D& P5 C$ Z5
" h2 [! q- A4 j, cdf.cut=df.cut.astype('category')
" e5 t, @5 Z2 z* i. N) a% `%time df.cut.unique() # 类别属性统计,2ms' d+ Z, w' q2 @3 u# U# O y
; B5 X, e3 l* O" r# o0 S
Wall time: 2 ms- U$ Q2 y" |. [: q: B- D- n% C6 j
['Ideal', 'Premium', 'Good', 'Very Good', 'Fair']0 D5 G0 Y" B. S' c0 u
Categories (5, object): ['Ideal', 'Premium', 'Good', 'Very Good', 'Fair']
* w9 J! k0 \- ]( p; H1
+ D- x+ z" o# j$ D7 i* w9 I0 `2
8 I/ R1 p t+ @+ i/ S: b: Z3
' n6 A9 w! A" f3 r- L- G6 l9 ?3 B4
9 _' [) h9 l7 q; V+ @5
5 L6 v) j& F+ d0 l7 O' E6
4 }( U4 ^- {& k% u( A. v对切割质量按照由好到次的顺序排序,相同切割质量的钻石,按照纯净度进行由次到好的排序。, p5 f' c0 a$ ^6 O' |( f
ls_cut=['Fair', 'Good', 'Very Good', 'Premium', 'Ideal']
! |5 m, N( o9 _# s! w$ q, Vls_clarity=['I1','SI2', 'SI1', 'VS2', 'VS1', 'VVS2', 'VVS1', 'IF']& I# `( Z+ n- [5 Y' c* E9 Y( \/ g
df.cut=df.cut.astype('category').cat.reorder_categories(ls_cut,ordered=True) # 转换后还是得进行替换
" b9 C# Y& ~- ]9 B* i8 m6 S$ J- ?% vdf.clarity=df.clarity.astype('category').cat.reorder_categories(ls_clarity,ordered=True)
+ k; u* e. B4 k" T4 w" A1 \ ^" u, P! y: z5 D2 q
df.sort_values(['cut','clarity'],ascending=[False,True]).head(3)6 O. C. a/ \+ Z$ `2 V
2 ]6 Q% X! i2 f7 j. }
carat cut clarity price9 l6 ~, V' W& W+ d' _
315 0.96 Ideal I1 2801
/ E. E. u( }( k. N8 o. x/ @535 0.96 Ideal I1 2826
/ g% h7 d# X" h& M8 E. m' `551 0.97 Ideal I1 2830
8 K' F3 N6 `% \- Q. X1
- w( `$ u/ l' _8 f2
1 M" J4 t, `* m% _# Y7 m k3; c4 B1 l2 k2 n2 h2 |# ~- @7 q
4- v/ ?$ Q% ?/ E3 l# Q' t- Z. U, W
5
( V+ ^- [/ c8 ^9 f6" z: n& r7 Q! [$ {8 q
7' p% M/ q( X# U3 m( i0 Y A
8% Z! r1 k$ R0 c% F5 F8 Y
9
* U4 D9 u1 P, Y r }* J" v, J& C10: k3 n0 ~" I, _/ T
11' J1 O7 m- B: _ \" X
分别采用两种不同的方法,把 cut, clarity 这两列按照 由好到次 的顺序,映射到从0到n-1的整数,其中n表示类别的个数。2 e$ R7 `; }' y# k
# 第一种是将类别重命名为整数
2 ]7 z( |" Y3 idict1=dict(zip(ls_cut,[x for x in range (4,-1,-1)]))
# Y7 a# X8 A4 ^) ndict2=dict(zip(ls_clarity,[x for x in range (7,-1,-1)]))
/ h; ~# _, B3 F! c1 ?% p/ i6 u2 k) b& k$ n& t9 j$ ?
df.cut=df.cut.cat.rename_categories(dict1)$ Z% [! p7 z) w5 A8 ?
df.clarity=df.clarity.cat.rename_categories(dict2)( S/ o+ u6 f& w/ e. G
df.head(3)' I5 B* J( l' Y
# Q% j4 D% Y* F
carat cut clarity price [2 Q" Q& t! n
0 0.23 0 6 326! C) W5 @- b* e Y$ i
1 0.21 1 5 326
3 X6 Z& w+ K1 c' I7 }2 0.23 3 3 327: u$ |9 V! }+ Y& B+ B9 E
10 B) |! \. _( W" A
2
! z; m* A9 R5 d0 G! L4 f3
6 F# f x; P: b t( C0 X: l3 ^4) b: ^0 K3 j* @5 B
5
k5 N6 |9 _* I6" z5 D) u5 }. X1 }- c6 g' X3 E
7
; a5 }0 V) x7 B( G- I# F. B2 G! O8! H7 n& ?) d! U- ?
9) D" p6 Q. n8 W" D
10$ F# t/ |5 P+ `$ a
11
7 s+ }" ?1 Z" E, [$ h; `) ] f12 B7 R% c0 O$ G* i7 A
# 第二种应该是报错object属性,然后直接进行替换3 c1 N4 X3 {$ U" s" l: j. _
df = pd.read_csv('data/diamonds.csv')0 R5 N! m! `7 a: W1 j+ e
for i,j in enumerate(ls_cut[::-1]):
% {; h1 F# Q. m) p5 v# D df.loc[df.cut==j,'cut']=i ) `7 l+ K$ }; h$ {' j
! S, _ j7 T" h7 P5 j
for k,l in enumerate(ls_clarity[::-1]):4 T1 |5 Q( U" Z. ]
df.loc[df.clarity==l,'clarity']=k5 D( w* p# D$ R) C
df.head(3)! [4 Z. m3 @$ L6 z+ a
. M7 v$ _" ^& k carat cut clarity price
H3 E7 Q: s( D. m0 P* D0 0.23 0 6 3266 z7 @2 l/ H7 J/ V
1 0.21 1 5 326' U' c3 Q$ I( c% a% W4 i
2 0.23 3 3 327
3 f2 w4 Q3 {9 p z1
|& B- u1 ?! f4 d2
1 ~7 s* D9 z: y& P- Z3) j! Q. v$ B# z3 F" V6 Z7 e. o
4
9 j4 R# M1 Y6 G5 ?. r( |- w/ ]
67 D( g( k. l' V7 l3 }1 B4 [3 k
73 `, z0 C7 h" h4 L3 s
8
5 _# q ?& }0 h! e9/ O2 Z3 n" G6 H1 W! Z. W
104 ]5 x! [8 w3 B, J
119 w+ @6 ^' f- o8 O2 ^0 B, E. g' f
12: ?/ x2 `( w. u
133 n1 F7 y- m8 C: A) o2 d6 K) f
对每克拉的价格分别按照分位数(q=[0.2, 0.4, 0.6, 0.8])与[1000, 3500, 5500, 18000]割点进行分箱得到五个类别Very Low, Low, Mid, High, Very High,并把按这两种分箱方法得到的category序列依次添加到原表中。
& {- W) |& Z4 k w1 c# retbins=True返回的是元组,第一个才是要的序列,第二个元素是分割点
9 I0 G# Y i: mavg=df.price/df.carat
# K2 z! S. J+ b e6 Y
! m& d( j: \# Q' ^df['price_quantile']=pd.qcut(avg, q=[0,0.2,0.4,0.6,0.8,1],
2 Y$ ]* H; F- m3 z8 ^; ]* V labels=['Very Low', 'Low', 'Mid', 'High', 'Very High'],retbins=True)[0]5 H! ^; X: J( @! x4 K$ ^
* }# H. e# Q* s! X; E/ j! Ndf['price_list']=pd.cut(avg, bins=[-np.infty,1000, 3500, 5500, 18000,np.infty],
/ P7 e$ ?5 Y" ~/ b' ^) P0 L% X; L labels=['Very Low', 'Low', 'Mid', 'High', 'Very High'],retbins=True)[0]
; [5 B* W$ }4 Cdf.head()
" T3 W2 L+ V4 C1 n: l0 x, ^* }9 I+ } Z8 J
carat cut clarity price price_quantile price_list
+ h1 F& L' M! B4 U% b- }; _' T& l8 h8 F0 0.23 0 6 326 Very Low Low
5 ] v" C! L. H7 L1 0.21 1 5 326 Very Low Low! l5 Y; p; H, d/ v- Z. d
2 0.23 3 3 327 Very Low Low
" ]! d0 q; Y9 L' i) g3 0.29 1 4 334 Very Low Low
( u5 \- l" w) J4 0.31 3 6 335 Very Low Low 3 I3 E& E) S+ b$ Z6 z( l
9 q, `( \" J( Z* Z+ p
1( p) @5 ?& S N/ W" f; [" I
2
% ]: m. M& y( y: J B, u4 u* B4 d3/ D+ X% l0 v( \
46 ~" `# _9 ^* O1 k
58 i& o2 D! g: j K
6
" i( Y3 u n) i7" o7 e* y" G3 o& x
8- A/ ]) Y6 A& L5 W/ c( k: l' I
9
7 j @ ?0 Y; M9 l10/ s. H2 ^3 j/ D0 y
11: W( L) V1 T, z+ D6 C3 E; ^, n
12% J- a& s4 w& w: r3 h
13; C3 L1 o& d* r6 V: `
14% t/ t$ a; x( |
15- w# H9 `8 S$ N* ?4 y
16$ h. i5 r6 s6 w1 c
分割点分别是:
1 ~, ~, J8 N. p/ f6 C) C! I: f
0 I. m( c% S2 X, d8 ?# ]array([ 1051.16 , 2295. , 3073.29, 4031.68, 5456.34, 17828.84])+ G( @- C7 c- m) \- |7 U& R
array([ -inf, 1000., 3500., 5500., 18000., inf])
2 p1 i5 {1 F9 e8 k( k1
* i q; x9 l5 o2
@) ]) u) C( m5 U+ u5 P7 V# ?/ u, M第4问中按照整数分箱得到的序列中,是否出现了所有的类别?如果存在没有出现的类别请把该类别删除。- r) W# k- R7 C f4 Z1 t P5 ~
df['price_list'].cat.categories # 原先设定的类别数
. I! I5 V& F9 r* ~& H3 |Index(['Very Low', 'Low', 'Mid', 'High', 'Very High'], dtype='object')
! R. J7 R, Y$ c$ Z3 b' S3 R
4 V( _3 P5 T4 p* b; k0 Rdf['price_list'].cat.remove_unused_categories().cat.categories # 移除未出现的类别8 v# ]. [; n$ V% g' W; d
Index(['Low', 'Mid', 'High'], dtype='object') # 首尾两个类别未出现
1 F! Y A3 M) X1 p1, y: n$ M2 B$ I& i; `
2" a; i% q# q: L$ f
3
) _' k2 x( C& r8 h- X4 Z41 N9 |: x# o' y- [
52 l- i9 {+ G/ k, f& k. o8 ?% k
avg.sort_values() # 可见首尾区间确实是没有的0 F" s5 E" }$ ~* U0 O, J- F
31962 1051.162791: S/ O$ `6 N: ?
15 1078.125000
$ H \( L8 j1 m$ [* K4 1080.645161: S3 B. L) @) b% o) p# ^/ j
28285 1109.090909
* ~! c3 Q" ^8 ]: D+ G1 c13 1109.677419
9 O; y0 G* z6 y! y ...
2 y* |3 y2 R, l( a26998 16764.705882) y! I0 r9 Z2 r) J
27457 16928.971963
2 d" z6 h) Q- F0 O2 M/ A2 d) |/ o0 I3 n27226 17077.669903
3 l1 b6 ^6 c/ {4 R27530 17083.1775708 K8 }3 ^1 {: @' N3 I' h
27635 17828.846154
2 {9 U9 p+ T) z2 K+ q8 [5 j# X( B5 f1
& Z1 d, e2 a( m2 k: q2
+ u3 X# j! Q0 k. k5 u3
5 D1 P0 D6 J Q( B0 F$ }4
3 j+ Z( F/ ?0 [; h0 G) O5
& f. L3 i) N9 N: g0 F1 A1 ]/ Z* b6
7 N5 d8 a0 v a+ a7
6 c5 h. Q+ ^6 v t2 ]& Z# l& G( O: M8
9 H( u3 S# |/ b7 M1 u! q9
0 ?) X2 F+ z, f) Q' Y$ ]: G4 E10, O- T6 {* k# @8 B3 e$ M+ @$ L4 l. p
11
4 q# s0 G* h: x; q# H- X6 S12
" N* X% ~+ J, w: f% |9 s, P8 o对第4问中按照分位数分箱得到的序列,求每个样本对应所在区间的左右端点值和长度。
; R6 w4 ]( q+ O( D# 分割时区间不能有命名,否则字符串传入错误。
) Q; l8 N7 u/ Iid_interval=pd.IntervalIndex(" J. p( O$ f& }5 P, [
pd.qcut(avg, q=[0,0.2,0.4,0.6,0.8,1],retbins=True)[0]1 P# B; D! G. }" u% Y: @/ t
)
4 n* j. W8 ^$ D1 G, Y2 s% }: j: z3 ]id_interval.left+ b) z' s) J0 P7 u/ h6 B
id_interval.right
* b9 P/ B5 v0 s' F" Xid_interval.length : C: B% P a) Y: _4 k; f! s* Q
1
- O. m7 ` I% F, P! c5 o2
* o/ U, S7 t3 v: `+ X3 ?3
0 t" Q, k# t; W3 H/ x: c4$ p( U" e5 \2 F3 \7 p6 {* @5 o
50 w5 k: z6 V9 |( x( R( ]
6; N# m. L, |4 u& j
7
; W5 y. F# f$ l) k! D% j# b8 w第十章 时序数据
6 C) X) s$ \+ Fimport numpy as np
' T' [+ l# c! J% N1 J7 A) simport pandas as pd
+ l5 C& n* U) D7 @" C1 D( w d19 b& y$ M7 Z- z. J% d+ F
2
! l7 \8 ~8 ]8 x5 b0 }3 O
2 U* x- `, c- V) M6 d. o, Z$ s3 `: x: p( V$ [ V2 P9 S
10.1 时序中的基本对象
+ u# x# d/ y( } 时间序列的概念在日常生活中十分常见,但对于一个具体的时序事件而言,可以从多个时间对象的角度来描述。例如2020年9月7日周一早上8点整需要到教室上课,这个课会在当天早上10点结束,其中包含了哪些时间概念?
" _, M; b. O- J+ A4 S `+ v) d7 O/ v, `9 ?. l9 F- s8 |: k
会出现时间戳(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的简写。
# w2 U( y* z, G/ A' G' N
" l4 o$ Y0 c4 }# B& M会出现时间差(Time deltas)的概念,即上课需要的时间,两个Timestamp做差就得到了时间差,pandas中利用Timedelta来表示。类似的,一系列的时间差就组成了TimedeltaIndex, 而将它放到Series中后,Series的类型就变为了timedelta64[ns]。5 V$ u$ R( N: ?0 b( m1 R) A1 a
) M1 y. v! _- W( d7 I; i; A
会出现时间段(Time spans)的概念,即在8点到10点这个区间都会持续地在上课,在pandas利用Period来表示。类似的,一系列的时间段就组成了PeriodIndex, 而将它放到Series中后,Series的类型就变为了Period。' V! [2 o# V1 a" q4 u% l3 K
7 o6 ]# a* p5 F. M会出现日期偏置(Date offsets)的概念,假设你只知道9月的第一个周一早上8点要去上课,但不知道具体的日期,那么就需要一个类型来处理此类需求。再例如,想要知道2020年9月7日后的第30个工作日是哪一天,那么时间差就解决不了你的问题,从而pandas中的DateOffset就出现了。同时,pandas中没有为一列时间偏置专门设计存储类型,理由也很简单,因为需求比较奇怪,一般来说我们只需要对一批时间特征做一个统一的特殊日期偏置。
2 U# r' R# _) f4 g% ^1 t0 `
8 q2 r9 C- U& U9 i* G }/ o3 ]! { 通过这个简单的例子,就能够容易地总结出官方文档中的这个表格:( ?) x; ?! P- Y* g0 e! h
/ F- n9 h4 u" |0 \$ j p
概念 单元素类型 数组类型 pandas数据类型( E) e5 z: q: v+ s, m8 X, A
Date times Timestamp DatetimeIndex datetime64[ns]3 c g( i( O% _* Q: {; o; ?$ T7 {
Time deltas Timedelta TimedeltaIndex timedelta64[ns]
& w" Z, F# y' {& vTime spans Period PeriodIndex period[freq]: _% g: s+ X1 s3 {$ Z2 ^- `; ^. m
Date offsets DateOffset None None
# y0 y- v4 j# z: a( ]' r8 l$ a 由于时间段对象Period/PeriodIndex的使用频率并不高,因此将不进行讲解,而只涉及时间戳序列、时间差序列和日期偏置的相关内容。
3 Y, y' V' Q7 [) I) {+ n- r% }/ f s* z) T8 L0 r1 H+ {
10.2 时间戳
4 g$ w, o% g8 j10.2.1 Timestamp的构造与属性 U) T _. M; ?* _
单个时间戳的生成利用pd.Timestamp实现,一般而言的常见日期格式都能被成功地转换:
' b, Q! r1 L+ C9 ]: |* k; h+ ~ D5 h
ts = pd.Timestamp('2020/1/1'), C# X5 E4 o; y1 m
( }( k% U4 {! d" J* R4 `3 c
ts* a$ G# n7 t# J8 D- a4 j# B
Out[4]: Timestamp('2020-01-01 00:00:00'); B1 O5 @# ~6 L/ @& G
2 y( Z/ s4 d2 @7 {* Ots = pd.Timestamp('2020-1-1 08:10:30')5 _3 _; f( m; f# v8 F- ]
- C- v* P) r2 O3 Z, @' G; Dts
) U- y! D9 l# Y: n& ?/ vOut[6]: Timestamp('2020-01-01 08:10:30')
" B5 N0 _, z6 f. }- v18 S: j" K5 b' M9 ?' C1 Q& Q. H, i
28 v9 z/ e8 N1 `) ]
3
* t* M# K/ w5 l0 p' A6 n4) G# ^* C1 N9 `6 p
56 f$ l, ?; S. u7 v: i
6
8 v1 Q k: g" O7* ^( h; g$ r# d( M9 [6 e; c+ V( i
86 V k7 Q6 o& q, Y2 S- H3 O* g
91 q; ]/ |2 Y' J4 ]. k
通过year, month, day, hour, min, second可以获取具体的数值:7 b8 ]' Z6 w3 Y: b; l4 _
3 x3 x6 m9 X! Q5 d: W- Fts.year
' d3 {! ]3 A0 U# U$ eOut[7]: 2020
3 b% h! _5 N% [- ~: h
0 M# f+ l! [4 y, x9 L4 Ats.month
j7 f" k: O/ l5 Q5 G( lOut[8]: 1: R: |* v5 s# I; w
3 e ^' F% o2 F3 [ts.day
2 t; o Q! W) e4 h) s# hOut[9]: 1% u, u. r$ A5 F7 @( K
% P$ z& q/ b# e" dts.hour- n1 Y' F; `9 m( B) H3 t- e
Out[10]: 8) q) a$ E N( R2 o) h1 A
- ^* B( P+ q/ ^( d+ L! r$ [9 L
ts.minute2 t1 f& K% Q0 j0 a: B
Out[11]: 10, |; J% D; j! M Y9 q
! I5 F5 K9 B$ N2 q* u/ \- H9 {ts.second7 D8 n* y1 r, N( \' q
Out[12]: 30
5 k9 [4 {+ @* d& P0 r2 E$ v" q+ n7 n
1
2 A5 o2 P' C) B N! D- L2 s8 ~2$ v G* a( S) Q. N
3
, |* p, `9 v9 Q4
- p2 i) ?: M& a* h8 |/ \' i5
- X/ P& c, T4 ~9 ~" E, E6. @/ e# E( x8 e7 S& d7 r% V' D2 X
78 y! x# e2 G& O7 y$ L$ J- O
8
3 F4 o6 d4 k" B0 y3 M: \' {8 A9 S$ D9
# q7 F4 H& s$ u! D# G# z10
( J* S& d) e$ `! d6 S/ g V4 f) A11
9 J: N, a; T6 A12
9 g* v1 [1 N# ~0 }7 L. i. M13# T- i$ v% }2 ^* ~4 N1 C
14
& t3 H Q3 P2 J! }: s; J15
~5 n6 T" \- S16
: c, ^# \& G* y7 S( l! A17
3 X h' [, Y" {- v# 获取当前时间$ k7 Z3 S) g( w$ K1 c/ w
now=pd.Timestamp.now() x1 z$ y- b" L, ~+ E
1
" l8 n2 Y9 p% ~5 Y- L; V$ I2
- Y! \( c7 a' g0 y5 A T7 {0 b* G在pandas中,时间戳的最小精度为纳秒ns,由于使用了64位存储,可以表示的时间范围大约可以如下计算:
! Y6 {- y9 u! m: FT 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)
- A3 n# d' D0 Z! X3 m+ @. bTimeRange=
Z; ?. K1 l$ u10
3 e: c' U# c) Z3 H9
7 V1 W( k7 ^: z j) a8 f+ F, T' N ×60×60×24×3654 [# }. B/ s5 k7 s* v
2 : Q9 a3 S, M/ l' ]" H$ w# U) d7 M W
64
2 |0 ~9 A+ h2 M8 B0 T6 W) V1 h" h+ u* n# [- V4 T" o: S' l1 M
8 }6 r6 N- J) z( N9 F; l. e: y% K ≈585(Years)
. T& V' j, K. i( I& x3 \* g+ u7 g' k$ L* [2 z1 p' T# o9 i- o3 J/ I
通过pd.Timestamp.max和pd.Timestamp.min可以获取时间戳表示的范围,可以看到确实表示的区间年数大小正如上述计算结果:6 J4 o; A7 {" l( d
3 |& k+ s% i1 c; P7 Zpd.Timestamp.max2 s0 ?9 q) r- Y: p% M/ R& e
Out[13]: Timestamp('2262-04-11 23:47:16.854775807')
& b7 [% J% F \* s* z9 l! G* z3 r$ _" z$ O
pd.Timestamp.min% p0 H2 ]+ x' ]: l, o/ z* ^! A
Out[14]: Timestamp('1677-09-21 00:12:43.145225')
7 @ e$ s7 ?# ~6 O3 F: c7 b5 u* k& H6 [5 z( V8 j, T/ @1 _5 N
pd.Timestamp.max.year - pd.Timestamp.min.year6 x1 G# U% h4 p! s7 ~. `% h8 g
Out[15]: 585; m7 P; t9 Z4 w1 Q& h$ B- C4 {
1, l$ d$ \3 A$ w0 v# [% q1 }7 |
2
( L6 X6 y) r7 e- q0 o3
6 p; Z1 h! r8 _9 o. F& t4
5 U' [! q7 K! M: t50 v5 M, c: K% V7 v' }8 U
6
( K% D! j7 N* c. k7( [' v2 X% a Z# D6 r, w
8
; j2 J/ j4 w# ]10.2.2 Datetime序列的生成' H" j2 T2 P8 ]% e. s J3 q
pandas.to_datetime(arg, errors='raise', dayfirst=False, yearfirst=False, utc=None, format=None,; Q; Z$ O. |9 [( F1 k/ K
exact=True, unit=None, infer_datetime_format=False, origin='unix', cache=True)
4 N" K9 [0 k1 G) G9 E1
; b6 m1 h/ Q1 A: J) u) p& d, R. ?- d2
: E7 E. n9 P% p# [9 g# ppandas.to_datetime将arg转换为日期时间。 D6 X' A4 }* x. V0 g: _7 V
6 r/ y9 L9 D5 R
arg:可以是argint、float、str、datetime、list、tuple、一维数组、Series、DataFrame/dict-like等要转换为日期时间的对象。如果提供了 DataFrame,则该方法至少需要以下列:“年”、“月”、“日”。
% a2 m! p2 P+ K" o9 Qerrors:- s6 G7 i4 `* t9 K$ n U
- ‘raise’:默认值,无效解析将引发异常( U1 M" ~: T5 h5 V1 g/ N
- ‘raise’:无效解析将返回输入
' L5 e7 |# Z& |( c8 G. T- ‘coerce’:无效解析将被设置为NaT
5 n% L j7 Q* a+ m. L) A9 g1 ?2 g+ zdayfirst:如果 arg 是 str 或类似列表时使用,表示日期解析顺序,默认为 False。如果为 True,则首先解析日期,例如“10/11/12”被解析为 2012-11-10。如果无法根据给定的 dayfirst 选项解析分隔日期字符串,会显示警告。
! c0 F8 M- s7 `0 z3 ]: syearfirst:如果 arg 是 str 或类似列表时使用,表示日期解析顺序,默认为 False。如果为 True,则首先解析年份,例如“10/11/12”被解析为2010-11-12。无法正确解析时会显示警告。(如果 dayfirst 和 yearfirst 都为 True,则 yearfirst 优先(与 dateutil 相同)。)* [8 W; i/ q5 X. u# X
utcbool:默认None,控制时区相关的解析、本地化和转换。请参阅:pandas 有关时区转换和本地化的一般文档
8 j1 h5 T: _, Q$ fformat:str格式,默认None。时间戳的格式不满足转换时,可以强制使用format进行匹配。
6 f( _ c$ n6 {4 c) U. i. i7 J# W9 Funitstr:默认“ns”。它是arg (D,s,ms,us,ns) 的表示单位,可以是整数或浮点数。这将基于原点。例如,使用 unit=‘ms’ 和 origin=‘unix’ (默认值),这将计算到 unix 开始的毫秒数。
1 R8 i! q) h9 a3 z) I$ hto_datetime能够把一列时间戳格式的对象转换成为datetime64[ns]类型的时间序列:" t( y# Q* g! I' y) D6 e8 \+ l. P
pd.to_datetime(['2020-1-1', '2020-1-3', '2020-1-6'])) k7 B3 r& H; Z. N1 t# y5 l
8 K. N$ j3 Z' l+ P$ w" o QDatetimeIndex(['2020-01-01', '2020-01-03', '2020-01-06'], dtype='datetime64[ns]', freq=None)
) _! V* v4 i' D1
" t% y0 r1 O" E% L. x+ l* ^21 g7 M( u7 j+ b3 Q$ ]
37 M1 d1 k# ]4 j* {6 m3 D) s. e
在极少数情况,时间戳的格式不满足转换时,可以强制使用format进行匹配:
# w( j7 E6 W4 T2 p; J- G& @2 n. } A2 |9 M& c+ H
temp = pd.to_datetime(['2020\\1\\1','2020\\1\\3'],format='%Y\\%m\\%d')2 e Q# y& K. G( s
temp
- @* n2 ]0 S5 [, v2 _8 o( c1 ^( T$ `4 n/ [. Q! s
DatetimeIndex(['2020-01-01', '2020-01-03'], dtype='datetime64[ns]', freq=None)# ^% V* @4 v9 g9 l, x' @) j
1
/ N! Z- D H& B% V3 t9 q" D; ^2
- a2 h. e6 N& B- t6 q3
9 d* T2 f% H, U {4
% L9 Y* \, Y8 s" E6 S* M% I 注意上面由于传入的是列表,而非pandas内部的Series,因此返回的是DatetimeIndex,如果想要转为datetime64[ns]的序列,需要显式用Series转化:8 I4 I" [2 q2 g0 ]9 a
' M0 ~9 b: _5 \# t' ^4 B# v
pd.Series(temp).head()) x, y0 z, c: s, l; [
* g. i7 `" [- O# v5 b) T0 2020-01-01" }; N& m/ H |
1 2020-01-03, I% f+ k2 q% G4 w& F
dtype: datetime64[ns]
1 d b& c" A) x3 a' W$ y1: |8 @' G* P/ j8 u7 K+ k) ~
23 |- Z( }3 t% Z! u, E# z4 |& d U
3
{5 M3 m# \( P R+ P44 O- R" |9 }, L G [9 p4 h3 a
5
4 G4 k- [! d) p0 y( |% r下面的序列本身就是Series,所以不需要再转化。
% y7 c+ Y* P1 q6 i* g0 W! F# n( {% o8 w) A* g, P8 H( _+ D& l
df = pd.read_csv('../data/learn_pandas.csv')
# R" V. ]2 E( A' a+ hs = pd.to_datetime(df.Test_Date)
" G9 {% j- c2 ]# t; s) O7 Ds.head(): d9 t2 k5 n+ f( i
0 T9 H, s) ^0 M* x
0 2019-10-056 V2 B D* i8 [- I* x
1 2019-09-04
?! H- [' S; G9 C7 g2 2019-09-12
8 ^% Y3 l) V1 h3 2020-01-03
& [! A; J# I4 x2 }" R# Y, y: j4 2019-11-06
! D- `; i$ D" m0 q( i9 qName: Test_Date, dtype: datetime64[ns]) R8 K2 E% _: p2 e& Q3 c# q
1
: ]+ M& ?2 R5 Y20 R( I Q9 _. u
3! |+ s# {* }7 T1 \
4- o8 g$ N- X% p! ^
5% T* l3 c: K( `! v& Z
6& D: L, G3 O) |/ T; H
70 v4 F* t8 |# c8 y* x
8
; Q2 n1 F( Z7 j2 F7 [9
9 s8 e! r' i" W8 T. x d10# Z4 I4 Q+ O2 ]. o$ r7 T
把表的多列时间属性拼接转为时间序列的to_datetime,此时的列名必须和以下给定的时间关键词列名一致:1 E/ j. \6 V9 x( I1 F
df_date_cols = pd.DataFrame({'year': [2020, 2020],
6 j: L1 A$ C y4 n, ?4 y1 p 'month': [1, 1],) H% k3 \ j3 i" C1 Z Y
'day': [1, 2],
8 O+ m' G# B6 |8 r \% c 'hour': [10, 20],8 K* J/ V: ]* b, X' j. v- ^
'minute': [30, 50],
8 Q9 T" Q9 ~ _- m Y 'second': [20, 40]})! N4 q$ p8 x9 y4 B! M
pd.to_datetime(df_date_cols)
& X2 b3 `3 l. [1 q. O
9 {- {2 Q0 Z8 u0 2020-01-01 10:30:20
" ^% b8 F0 ]* i6 {9 L- g4 i1 2020-01-02 20:50:40
) g- k' k) K: _2 gdtype: datetime64[ns]
+ o- Z6 B( X' E1 s( S1 R/ v" H; o1 V) T0 M. c
21 k7 C' I6 o* d8 d3 K
3
6 ^8 a6 X9 }' e" J8 S/ l) J4
' t# G+ k$ |# w5 i* S3 b5
$ G; W$ d$ ~/ ~; D1 M6
0 i- [ P( S; n$ ^& k3 a7: }( b. ]. Q4 M) U! n
88 L1 v4 b6 B; a. i
9
# k" j1 U$ {! K; Y; j! a- k0 `$ f. f10, R) N8 T& V) l- D. X# ]
11; D1 ]' f' ^2 O2 X+ a, C
date_range是一种生成连续间隔时间的一种方法,其重要的参数为start, end, freq, periods,它们分别表示开始时间,结束时间,时间间隔,时间戳个数。其中,四个中的三个参数决定了,那么剩下的一个就随之确定了。这里要注意,开始或结束日期如果作为端点则它会被包含:" V- ~& s, Z4 M- O5 h% a
pd.date_range('2020-1-1','2020-1-21', freq='10D') # 包含5 J7 T$ y. g6 Z3 z) }
Out[25]: DatetimeIndex(['2020-01-01', '2020-01-11', '2020-01-21'], dtype='datetime64[ns]', freq='10D')
/ `$ L+ J! k/ n4 o
8 _) y% t" w( w7 @$ d; Apd.date_range('2020-1-1','2020-2-28', freq='10D')& @6 U& o" y9 e' Q3 u8 l/ R0 m C0 M# C
Out[26]:
' J+ O6 f1 V5 I. I9 S2 A% tDatetimeIndex(['2020-01-01', '2020-01-11', '2020-01-21', '2020-01-31',
$ K6 V* K! B( n! v5 F- [9 d% F# M '2020-02-10', '2020-02-20'],
% S. V9 f: q2 B# N& w, | dtype='datetime64[ns]', freq='10D')
) H0 \1 H0 I3 @0 w) T( r/ |5 `) q1 N
pd.date_range('2020-1-1',
5 D. L1 D4 ?# w* i) Q( b '2020-2-28', periods=6) # 由于结束日期无法取到,freq不为10天
w3 h. o: I; [2 ~! H- ]/ d# Y1 C0 ]- D7 a
Out[27]:
& j7 S3 m5 x9 ]! U. ZDatetimeIndex(['2020-01-01 00:00:00', '2020-01-12 14:24:00',
& y+ n# c3 a2 D- r! K7 _ '2020-01-24 04:48:00', '2020-02-04 19:12:00',2 f% b' t- P: N( M
'2020-02-16 09:36:00', '2020-02-28 00:00:00'],; x& O+ h0 ]8 P: h) T* K
dtype='datetime64[ns]', freq=None); X! X' Z# _" M1 x4 I8 _2 S: E
, |; |; N* A6 c. G0 Z _, m6 ]
1 ?3 S u5 M1 ~9 J1 z# p( M0 P$ q
2/ S' |* l8 Y4 F0 `( D- `& X! O
3
8 m: m" G/ [/ |' H5 l0 I4" R& q/ d# X1 _) \
5
! y4 g" p5 m5 D% A60 C- Q2 a j. \* H2 l) w" D6 j y
7
" {# {3 y* q, c+ O8 M8
# @- r4 b1 B) q) |" y! ^- {# ]- Z9
: \9 k2 G) F7 o0 z10& Z* x2 N6 p, y/ B8 @6 ?
11
4 d& D$ S' n& i) X% B128 U. T2 }* U5 N" Y( i" E
13/ P- C: G, n& ]
14
+ g" @; V' ]( S0 E* X. \* j159 k1 p/ t7 w/ U# p$ C$ d* q$ H/ _2 D3 u
16. q) [6 L9 H, p4 z4 {; `
17; k3 i7 g& E9 z! Y
这里的freq参数与DateOffset对象紧密相关,将在第四节介绍其具体的用法。
. p1 K! K: O5 b' T1 A8 c" c
& x M( H) n/ A2 [; a【练一练】. d0 |* U4 O* d# o
Timestamp上定义了一个value属性,其返回的整数值代表了从1970年1月1日零点到给定时间戳相差的纳秒数,请利用这个属性构造一个随机生成给定日期区间内日期序列的函数。
$ B; Z( Y1 e2 t: k. Z
; s0 ^2 }7 ^+ s# Zls=['2020-01-01','2020-02-20']
a9 P4 Q" P, M# q6 Ldef dates(ls,n):
& x* D* E* `- Y, X min=pd.Timestamp(ls[0]).value/10**9* C" _% w1 [5 a1 M8 b% J
max=pd.Timestamp(ls[1]).value/10**9! d) t. U* c: y2 t
times=np.random.randint(min,max+1,n)% n8 }5 `+ G( P) Q d, _# }( E
return pd.to_datetime(times,unit='s')8 d3 X. P0 |# O6 {2 @1 n: r$ J
dates(ls,10)
1 z2 ~; o$ w2 K/ ~: j" d
3 j$ ?" W/ H/ U; g1 ?DatetimeIndex(['2020-02-16 09:25:30', '2020-01-29 07:00:04',
; m& C' W! d4 P5 E6 S4 e7 ?3 C& I '2020-01-21 12:26:02', '2020-02-08 20:34:08',
4 O- I& a# y; a '2020-02-15 00:18:33', '2020-02-11 02:18:07',& x9 y7 E2 X' |; M
'2020-01-12 21:48:59', '2020-01-12 00:39:24',
" [& }1 n9 ~* C X. |+ \ _4 T '2020-02-14 20:55:20', '2020-01-26 15:44:13'],0 e0 L, n1 F# _2 { k$ C
dtype='datetime64[ns]', freq=None)/ w/ C& z: _' r3 @
16 \5 W7 O/ H* z; t5 r' i, f
2
0 G2 J2 Y' O; x6 T$ L0 |+ u. F3
& j' ?5 K) a$ C" P& c4
8 {. k/ [+ R2 Q! C1 _5
+ z9 e" e) [) J6
8 O5 V! e7 y) `7
# P# L) V7 ?! w @' Q" ^8: c0 L) C% C& K N/ }4 ?
9! P. I& E0 n4 w! A6 v* G
10$ S m7 ~8 ^5 ]2 |0 E& e6 P
11
6 i' \& |5 q# F129 f/ a, S) _( \% h1 s! t& |
13
( k$ z+ x0 U. v0 Q141 z, Z% t' {+ I; U! W
asfreq:改变序列采样频率的方法,能够根据给定的freq对序列进行类似于reindex的操作:. V+ \3 }$ x" I1 R+ G
s = pd.Series(np.random.rand(5),1 h8 v4 O! V) ?
index=pd.to_datetime([' j4 s2 }6 X! K) t
'2020-1-%d'%i for i in range(1,10,2)]))" E3 K- w, _6 x2 C+ y4 h! v- c& P
5 U% `$ _! K6 r9 O& \$ K% p
2 V1 h- A4 t7 s: W& T5 }* a* Z4 is.head()
9 i4 j$ [1 R+ }2 @( [" FOut[29]: * Q0 r6 c! z) j" P( x$ ^
2020-01-01 0.836578! l) t3 i$ n. t# X
2020-01-03 0.678419
7 }1 y& O8 S6 U2020-01-05 0.711897
! u! M- L( ~2 |. M7 [9 ^2020-01-07 0.487429
`. g$ Q0 m7 a& L5 x5 l2020-01-09 0.604705
]1 I2 B" Z* @3 q7 pdtype: float640 G/ g' G. q7 X# }3 l- \9 u: ?
( m4 A2 M) L5 R2 w* [, K7 [9 ]( r2 @
s.asfreq('D').head()/ g! _5 m- [; W$ q- d7 r
Out[30]: 7 }: F: a; x. K: D
2020-01-01 0.836578" f9 B; v. |8 O) L& Q8 ^
2020-01-02 NaN+ a+ U* O& ~. ~$ J' g% T( x
2020-01-03 0.678419
# V% m9 k6 Y* ~2020-01-04 NaN. @/ Q/ e$ N& b/ ] B
2020-01-05 0.711897
4 ^" C* }7 d' M; wFreq: D, dtype: float64
8 {8 {! R0 q6 X" s& L- _& K% M; K8 u) M7 p" P( [5 C5 W
s.asfreq('12H').head()
& A" `3 K0 k- p- _0 @! j2 U5 LOut[31]:
) m5 x6 C4 [4 k2020-01-01 00:00:00 0.836578
7 w9 V6 B% T; x: B" i) J3 k2020-01-01 12:00:00 NaN" A: Y! P1 N& u& L, a
2020-01-02 00:00:00 NaN$ k1 x$ a0 ]8 U( V5 V
2020-01-02 12:00:00 NaN
, H5 s! D( W" F* I2020-01-03 00:00:00 0.678419* K2 D0 v* D5 u
Freq: 12H, dtype: float64: r( N- u1 S' [ @
& l) f N: q% w% ?
1
2 `9 ], C7 A- a2
3 }- F5 O4 F9 _" R" ^) _9 U3$ b/ u1 S# y0 v) s3 k# k: H
4
; w7 N7 z& O: c8 @5
: X0 ^0 J, Z9 W1 k$ \. v* q5 ]6, p/ _0 C# m' E; l
7
: F t0 {: D% u, S! e9 F0 V3 q8: F0 p! K! m4 B+ R8 [+ ~
99 L+ k: h4 o) y4 j. w5 z
10
+ s3 C9 t3 X8 l4 ]; h9 u11; C3 E s: @) D/ |; w
12% o6 b: N) k q: C6 M, w
13
" ^1 {, [8 q' _3 y( I( [7 O9 z% L. d14- V5 w- t9 t* D1 ]9 c7 D e
15
0 u. X2 ^0 Z9 X) {16
9 q8 W/ c0 C% `" }7 ?17
/ W$ n V. r" m2 m' X18
# T y/ z- S. u( Y0 @191 v9 f4 L3 J. b7 u, u( L- h6 a( j* ]
20/ y/ a5 @+ V1 L- e
21- }0 v) z2 Z/ N" B/ y2 S
228 }' [% A9 {: G0 D$ U# i6 Z
237 O# g% g9 B( z' `& l/ O
24
* q+ X! k# A( d! `. |+ w* M/ G, L25
8 ]) Q( s; o4 s26
2 t ]1 q S0 z* j27
( | w/ ~4 U6 n+ |( t6 n4 }28" b' F# }9 t x+ o
29
% `8 r2 v: w g% C4 S8 E' Y30
4 Y, _) O, Z. i; n315 \( D$ o5 f( A/ z/ N$ k
【NOTE】datetime64[ns] 序列的极值与均值
$ n1 `' [! [/ i' i7 Z0 A' i4 q 前面提到了datetime64[ns]本质上可以理解为一个整数,即从1970年1月1日零点到给定时间戳相差的纳秒数。所以对于一个datetime64[ns]序列,可以使用max, min, mean,来取得最大时间戳、最小时间戳和“平均”时间戳。
+ Q; m4 N+ H L8 j
" t8 {! r: @% F* w; o* k% c9 G10.2.3 dt对象
/ U$ d# U; G; ^# i9 n 如同category, string的序列上定义了cat, str来完成分类数据和文本数据的操作,在时序类型的序列上定义了dt对象来完成许多时间序列的相关操作。这里对于datetime64[ns]类型而言,可以大致分为三类操作:取出时间相关的属性、判断时间戳是否满足条件、取整操作。
( v# N3 r# A6 {& k/ [: i" Q8 I0 Q9 Z" j0 j! H: C; }6 E
第一类操作的常用属性包括:date, time, year, month, day, hour, minute, second, microsecond, nanosecond, dayofweek, dayofyear, weekofyear, daysinmonth, quarter,其中daysinmonth, quarter分别表示该月一共有几天和季度。+ s1 X6 ?9 u% W4 |6 e: n- @, x# j
s = pd.Series(pd.date_range('2020-1-1','2020-1-3', freq='D'))
! Y& A0 ]6 `9 K- ?, X
/ m( _# N8 Z$ Bs.dt.date) R1 O, ]2 ~( o0 b" h0 y& T
Out[33]:
0 Q; c$ Q6 i! t) Q. t1 C/ E0 2020-01-01" }) ^. E7 T: {
1 2020-01-021 t0 ^6 L1 l; V" U: w" I& Z
2 2020-01-03
5 f c% j9 h, G' tdtype: object1 p7 v7 N0 b5 ]7 d- J# }0 f
4 p- w6 G6 l2 e" l+ y4 Q6 v7 S/ @3 D6 T9 {
s.dt.time
& D2 E8 _/ A+ ]) p4 TOut[34]:
B! [7 d- i9 [5 _( ^1 T7 r0 00:00:00: ]; T1 E& t$ E2 o# h) r9 N
1 00:00:00- ?. c, z# O$ D/ V
2 00:00:004 @, c6 }5 S9 ]& m% x
dtype: object
& |2 q0 k0 M# K0 o0 {
) B E3 b4 q1 g, C6 V3 ps.dt.day$ s/ y- x7 j- L
Out[35]: ; Y) a9 T$ k& I5 e( X
0 1" g6 M! E- W W( c* N
1 2
1 ]5 f) Y3 [' g( Q8 b2 3
. {0 z3 E- E& G- G qdtype: int64 D4 x! m$ D3 L. V Y) R$ ^6 f
6 m) F5 [! S" E
s.dt.daysinmonth* Q; p0 f# a% @2 M+ X! s& x& U6 C
Out[36]:
' t2 d' u( Z* }6 T& j0 311 \$ E7 q8 ^# Z9 h% M$ e: h# c+ k
1 31
) ]. G5 \" ], S3 |2 31
: x- N4 Y6 q/ \" i7 C4 udtype: int647 M3 ^, `2 k' _7 Z2 X7 f7 U
; H1 q4 V2 s+ u4 c3 q7 t# n* L7 m) f1
7 p. J9 p- X" a; Y5 A2# s. j1 K8 y' I9 N
3
$ P, t; [* ~6 }4 y9 ?41 j+ |- d% [4 ^7 y/ D
5
$ y: Q$ P& `$ z9 W) v6. K7 z) d" A2 J! U5 D
72 Y1 O! ], _3 O7 f+ w
81 ]6 N+ o4 ~) _+ ~. C# }; `
9
5 A6 L, F# D t) ?7 r. N) s10
. l. ?& V4 I3 N3 g) ]" o, I11
& n; Z+ w) C& H) j6 _12& T) [3 ?4 A6 E4 h, p
13& k! t( c1 r: T* | I5 A
14
5 [- e, _1 ^3 X* L8 R" c15" H5 G+ S& _" R- e
16; r: E5 ~ ?' [" x! `8 L* x
17
% B& Y8 ?/ I5 l: c9 ^7 [* }; i18
' e J, y" w* z/ Q- e( ? [9 |# Q& a19
+ A+ o5 r- x5 ]4 X( o207 ]$ C$ d8 w6 g( U o( v* ^+ h: V* M
21( r: T# W; U$ x, t% @
22
' D# W. x. n& B( ~# B5 a( h23, P( A/ C C, J; z" ~- m
24) C1 U- J/ i' P
25
* {. I2 ?0 m" y# d7 H; g$ t26
' }) @( } L7 h. C: Q27
, A4 H- B+ V8 N* g) g( y28
1 m1 c! T! r) X' g" Q5 `: V! E29
+ ?4 M0 x* ?( w 在这些属性中,经常使用的是dayofweek,它返回了周中的星期情况,周一为0、周二为1,以此类推。此外,还可以通过month_name, day_name返回英文的月名和星期名,注意它们是方法而不是属性:- d& r' y& `# k6 `% _
" h. b% C) Z! k
s.dt.dayofweek I Z9 a0 t) o- Q
Out[37]:
% j) ]1 a; }& S: x) R" w' l0 2
3 b3 T; V. I$ _$ f3 {1 30 I! g4 @" Z4 [+ ]( _ S: w
2 46 w5 W8 U+ J5 U7 K) R f" Z' _+ U
dtype: int64# t; ?7 `; U6 L7 q g/ p
# w; R! B. S. I5 d
s.dt.month_name(): d9 a- }6 z. @+ v6 ^7 M6 g
Out[38]:
, Z$ G# B, x, @1 r% y6 k0 January2 V$ D; M# A% x. W" O
1 January2 U0 |. t# z k1 ^
2 January: }' n1 W% ]0 s; D, x p
dtype: object
4 p P4 e6 M: C/ ^+ j' g% _' V. }8 U0 u# b! R( l m( W0 ]) x
s.dt.day_name()
9 P% D4 l% `( {: L. t$ ?Out[39]:
8 ^$ G3 b; E8 R$ Q i0 Wednesday
* z# R/ n5 g9 g4 J% d: \2 ^& y1 Thursday
2 c' g- }+ W- z1 v) Q" d2 Friday* d4 c. m: _) v! k3 t. S& X3 p
dtype: object. m4 _1 x. Y: P2 D; S( k
( s2 _; I$ B0 {1
+ v: s: S. a; G/ W# ~5 ~0 S: u2# i8 A+ [/ i1 w" `7 {( J
33 ~, S1 U S; x+ K; Y* C
4 {7 M$ ?, _2 [$ i8 m
5
3 k, R) t: C# h! ]2 p6) h5 \# e8 [# z
73 o0 k# y1 ?$ L |
8) Q, }% ~7 A: X2 z5 \
9
9 h$ B, F [. Y! X$ z/ Y" A% i& x2 g10
, E$ ^+ s! M }: o4 o11
/ u. G8 q* N$ P" @* p. Q0 r% f12
; ]/ s1 d; B1 r+ |7 H3 V& V/ L13
" b6 P7 N' Z7 Z- R6 O4 J& U) k) W14- Y; W; y X$ V- y; |0 s
15
4 s* X i" R6 q& }7 G16
7 k- S0 c4 T( q* m) j% }17 L9 r& w7 j& Z
18' i- [3 I K; c, ^* H \
19
# ? A+ E+ `9 ]20
$ H/ _' D+ q. f6 W0 y( P第二类判断操作主要用于测试是否为月/季/年的第一天或者最后一天:
+ S8 f1 y2 F: [* ?! cs.dt.is_year_start # 还可选 is_quarter/month_start
$ J& H$ G! z( y5 v. COut[40]:
2 d( B3 y7 q6 p( O; f1 O G0 True2 s1 X1 r8 g8 l$ _
1 False
; p+ a& `2 B4 _! q0 a2 False# q4 M" _2 ^7 [. v: ~
dtype: bool& O a7 Y& ?* K$ T
5 f* [3 `3 f& }3 H. xs.dt.is_year_end # 还可选 is_quarter/month_end" A$ {9 w# E3 w3 P
Out[41]:
+ \" w7 V1 F- u6 I0 False
- Q+ P: _0 O" L& c9 V- n0 O1 False
: z# u) H3 u! L: j' W% ^2 False
$ B+ } ~# w3 r, Cdtype: bool- d; F5 y) j" ?2 ]
1
" |6 d q9 i0 v2 i2 g8 i- ^9 K7 R2
1 Z2 }. K$ C( c- B, J0 e; O% p3
8 J$ Z+ ]8 n$ |. C4
6 s$ a+ W6 y" \# `59 Q6 ^% H1 P; y$ r8 X
6' D8 [: e9 |! @9 N8 O
7( C, P7 R4 D) s' y' I
8
: W! R7 l6 H* y: Q( Y9
" v' Y# b0 _' U103 L" {! \. i. b- I$ G
119 W# [1 F" J* u3 |2 k8 M3 B5 @
12' [% G, K- Y0 D: L% e6 u+ J, m* v- W
13
- \" Y* h) t) S* U+ H2 L第三类的取整操作包含round, ceil, floor,它们的公共参数为freq,常用的包括H, min, S(小时、分钟、秒),所有可选的freq可参考此处。0 d- ^2 m3 u) L! ~9 r& H
s = pd.Series(pd.date_range('2020-1-1 20:35:00'," L2 f }. }, a
'2020-1-1 22:35:00',% x$ Y/ f2 z+ N6 r @0 V
freq='45min'))4 g! {, n7 E* m) x
" `4 q# z' C& c7 {* i
, N3 T/ Y; r( us
2 k+ M1 [3 n6 V7 q& p/ iOut[43]: ! l1 x" M7 f4 b9 O5 {3 U
0 2020-01-01 20:35:00% L, a; [ v& \; I
1 2020-01-01 21:20:00) z* z7 g. A+ K N) m7 Y, ?; r
2 2020-01-01 22:05:00: P3 {/ j# n3 X t, G
dtype: datetime64[ns]
" | G; u D2 }4 R u9 \. @5 _$ M& i9 g( ~# Y
s.dt.round('1H')
* a7 @* b7 P; Q sOut[44]:
1 i+ y% l( H. W3 f) ]' _+ R6 E2 S0 2020-01-01 21:00:00
/ q- H- n8 p4 u& H- ?/ ^1 2020-01-01 21:00:00
* `" i/ E: Y5 ~/ \" \+ k9 E0 M2 2020-01-01 22:00:00- o- I: k' q& M
dtype: datetime64[ns]
7 n' N/ Z6 i7 v! ~3 z. @
9 d% I1 I- h$ H: y. qs.dt.ceil('1H')
: s& M9 R4 Z5 g) DOut[45]: ! a; L5 C; u% k4 A; h
0 2020-01-01 21:00:00
! H$ D2 K+ `# _3 T: \1 2020-01-01 22:00:00: \) L: b, G0 H+ _8 Z+ }7 e
2 2020-01-01 23:00:00
& V" T/ G) m' vdtype: datetime64[ns]" B _. G; k2 P3 ]4 E! ~
0 u0 a* l- k. G3 H+ W* R3 \, Y7 h
s.dt.floor('1H'): P) D; o3 Y, [$ ^8 e
Out[46]:
$ W4 S; W" X# e. M0 v0 2020-01-01 20:00:00; _+ a$ `$ |+ B" K8 {
1 2020-01-01 21:00:00! ]5 Z% \4 z4 o
2 2020-01-01 22:00:00
! a/ [8 x N. [9 Ldtype: datetime64[ns]
* f+ R- |3 Y- \! j9 F: u9 g. q7 I+ }' ]
1
" [* Y, Z1 b W- \$ B) T2 m- M) J, o& D1 o
3
' {' L/ O; s. F5 o! h7 G4
- U$ ~5 x' p$ i& i. Q$ k; C5 C }% C1 V5 q, J5 n
6
9 X+ o; s! v2 U" `" ~$ x0 G3 s71 u$ p" }' d! {& b
8' F# f8 f% {& x2 C! c
9; w/ }! `; M# b0 @0 f' e S
10
2 k L. D; O F9 p: z11. u+ ?$ I! B4 M! n0 h5 h
12" b2 u$ p) k# n, i8 e2 ~
13
+ Z$ m) f9 j1 r14$ a; I' c3 Z9 {5 r+ g! i+ H
15
9 H9 G* s1 G; I \16
" M3 d) _% Y, F+ w( O& {17
8 H1 J- J& L/ M( h! V9 \( L9 q183 Z7 H, z* Z8 x: h! d
19 y. @! u# S. T( n, }5 S
20
" x; ~- @) Z' W$ r2 N21( q# s4 V+ k3 H7 k& q6 W% J
22) [1 ?- ], p' o+ y! h! i+ x/ j
23, ^" k4 l) \! @; D2 B7 ]4 j
24
) Q8 J" a8 `1 ~ r& ]25
/ g6 w3 U8 w/ v# a8 W) i" n26' c _ }8 V/ W( R
27
7 o! g/ u: T0 a" d, K `28
- s8 c; Q. U8 n1 i( ?29
8 i, T. y% I: O& p30
+ S5 A9 Q4 U/ ]2 w% i+ A31
0 o. [9 m/ q* R6 Z32$ B/ f8 X- J+ s; h" W# C' g
10.2.4 时间戳的切片与索引
* w2 p4 s2 K- m6 h, }0 c 一般而言,时间戳序列作为索引使用。如果想要选出某个子时间戳序列,有两种方法:5 T$ |9 m9 n d7 M# Q/ O8 |
- t; Q. w# g5 y* T+ ?; |# i; S9 ^
利用dt对象和布尔条件联合使用
9 y% N) b2 h. I8 f$ d1 A K利用切片,后者常用于连续时间戳。
1 p+ p" h. V% M6 d1 Hs = pd.Series(np.random.randint(2,size=366), index=pd.date_range('2020-01-01','2020-12-31'))& E9 R* v+ }9 v9 q
idx = pd.Series(s.index).dt
+ o5 x+ e4 y; C) L. J$ Qs.head()* X) P+ l1 e- h2 M3 ]/ l1 ]5 M
+ Z5 N) n1 p& F9 J4 R. a! _. E7 z
2020-01-01 0$ s" o" c7 Y/ r4 W0 ~* h: S
2020-01-02 1; f; L- u! _( Z4 F! q) [/ ^" p7 t& N
2020-01-03 1
: ^% j( m8 k( F9 n4 b; X9 b! w1 q2020-01-04 03 Y! v: n0 m6 I( d# b. `# x( Z4 D
2020-01-05 0
* }: r7 d% s- c: FFreq: D, dtype: int325 D, B' m; }- f l
1* S$ O& G7 r3 N( l, S% R
2/ I! P9 T5 i {
3% X0 d1 \" ?8 d. e# w1 C/ A
4, }- q1 _$ C( [3 {
5' q, V4 t3 g, ]7 @% y! J
6
# N3 c4 t; R1 H7 M/ `, R: C7
% C' [7 ]( Z9 c0 ?8# a8 q7 U/ c* ~' j9 W: o F r
9
. h2 F" n0 g4 b* y$ S1 O10
' E! A7 a/ S% b) A" XExample1:每月的第一天或者最后一天; z# \1 a# U6 T. k. F
9 X3 u, I; M! c; y$ O( b2 W9 Ks[(idx.is_month_start|idx.is_month_end).values].head() # 必须要写.values
1 g& Q0 N3 k% j- y" y- c( ?# hOut[50]:
2 I5 g! I G9 l0 n# J2020-01-01 17 K9 x% L! m! F; @9 D) o, G
2020-01-31 0& t: U+ j; r" ?/ [
2020-02-01 1- h+ c# \5 E% v! _: C
2020-02-29 1
' u1 O8 ]3 N5 Y$ @+ N2020-03-01 05 \! Q. O1 K+ n4 h2 V0 Q
dtype: int32
8 p8 L: A5 F6 p, k13 H1 |7 c0 ]3 w2 d! ]
2
$ d2 ^' f6 m6 N3
9 W8 c, W/ V: ^! B: X. \- H; o4) \! O t! c, u o8 B. x
5
; ^8 T2 p* F% I4 C68 A5 w* Q! f3 U0 A% q* T5 z
7! `5 ^0 G# E6 O- @% z/ g ~
8% z3 L) w7 b( F- B" o& i+ d1 u. {0 ~
Example2:双休日
5 Q. s# L2 }& g8 D7 W
4 y: Y, t6 h; {: us[idx.dayofweek.isin([5,6]).values].head()
4 \) M4 s1 o' R' Q5 C( R' }Out[51]: ! c4 y7 N8 s- w+ L- t2 a7 P
2020-01-04 1
( R# m/ a# ?2 x% ~# d+ r* W6 s4 {2020-01-05 08 O& L7 q" X9 E! Q4 X: a- t
2020-01-11 0" @+ j* d/ r5 h
2020-01-12 1
: d8 @! V. {+ L. \9 r2020-01-18 1
; w- v/ P" s5 U1 D( G# R$ l4 Fdtype: int32- q4 E6 P. |; t; e' N
15 E- z& u, R. r4 L9 K& A( O1 p- m
25 N2 |& H+ A) [2 c
39 Q* ?$ ~5 n, J
4' V0 X0 C0 ?4 H
5
& e" p( p1 ^9 h6 w1 ?/ T67 b- d* T% }) |( f9 w
7
% F2 k- O: B" t1 h" Z. p8
. c4 ^4 W0 w# I* u5 {4 Q$ ~Example3:取出单日值' o- b% M4 t: I* k- j
. d7 q0 v& L( E/ o: ^5 I
s['2020-01-01']
. {. L$ b0 r) R' p9 cOut[52]: 1. p4 h8 Y9 ~# U- ?
4 k1 ?% C0 T! B+ f
s['20200101'] # 自动转换标准格式
) }3 q8 b1 x; Y4 `4 e. @2 _Out[53]: 1, [" c1 k; E7 _2 z2 k( s
1
; n9 k6 v/ G; n# x }8 f" W2
4 z) B2 `, U/ J) a" H; b3/ Z, n( ^% f# u1 v7 @- e
40 R. J) i) v7 C2 Y1 J" U4 i
5
5 c# e% x8 H% t/ C' z' ?8 e; sExample4:取出七月2 n" \& M: C8 e9 v1 X
- D+ ^. }* y( y O2 Ws['2020-07'].head()8 s! L# l8 B% Q3 X4 W
Out[54]: * C- v$ A: s. u; Y0 q1 [
2020-07-01 0* Y+ H, W9 g+ l5 |
2020-07-02 19 N Y4 g& q; ~$ N
2020-07-03 0
7 _6 ]- e( e% d5 y/ x, Q8 x2020-07-04 0
( i* U T: ?- w! r2 K2020-07-05 0
) `2 L! v5 \3 ~$ } J# O$ ]/ `0 P( [Freq: D, dtype: int32
$ Y1 |7 ?/ q+ u4 x12 @ y6 X! I, J$ R2 b9 M# u5 S
2* ^/ _" n V- J3 z4 Z9 r& @" T
30 M4 {2 ]" P0 w8 J/ `) d& F
4; c) r* w! O! g8 W4 S
5
/ q: j! T p3 A( x- h! s6( a$ _6 x' G* y$ A# |2 a
75 C( j( U; q9 y' T( e- b% G! p
8
8 `9 R; N- d: N* o' XExample5:取出5月初至7月15日" R& |& E5 A. M9 P. C- C
% l7 \- t0 G9 B* \
s['2020-05':'2020-7-15'].head()
% ]% {- R3 Y7 ^$ K! W5 n9 [Out[55]:
. l, s2 h' f: ]1 I0 ]2 W2020-05-01 0/ f: f4 X s! L% U. g
2020-05-02 1
$ F- N* W6 @/ A: Z* _9 s2020-05-03 0" q' s5 E) k H6 S2 O- L3 k1 _
2020-05-04 1
3 M9 w, c J- k2020-05-05 1" Z0 a2 U( z( U5 ~4 t; T+ q% y
Freq: D, dtype: int32
2 ]. k/ A, L& V4 F0 R* w5 P
$ ^/ X$ K; i7 Y6 _) l0 q1 ^s['2020-05':'2020-7-15'].tail()5 G0 |7 N) H" e9 y' C
Out[56]:
0 |* L9 w/ f& k$ ?: X( O- [3 g2020-07-11 0% o2 s0 w) G% e& P
2020-07-12 0
. m; `2 t8 X* ], L+ ^. M2020-07-13 1
+ o3 `3 h; t3 ` S8 Z2020-07-14 0
J& O! W( |7 E0 g2020-07-15 1
7 U% m3 }: A2 \5 O. _! {Freq: D, dtype: int32
* ?5 q! C4 s+ |0 z
; m2 x3 s) ` G. c8 t2 @: e1; w( A: c* G: J; B
2
* v5 f- f; z* x7 _0 O8 U3# z! y4 Y- U [" ^- a' p
4
. O4 J2 e8 E7 J. [( Z8 t* L9 j, p5
- m; I5 U" S2 ~+ L! T$ ?6% o+ n9 e3 {& Q1 [! U2 B
7
6 C6 c4 r9 ?& K: O u8
. _7 [' X8 y. X3 z9 ^ c0 O: R99 x4 V4 }& a5 O2 {% _/ w( [
10" O$ u7 u! K4 G
11# q! g4 A F8 w
12- Q' n4 B/ O3 P, J
13. R1 y3 c8 @3 T l/ ]9 K
145 ~* | Q# C, B1 ?& C6 [9 W
15 X/ p' P. {" k) Q& w
16
) ?+ A7 ]7 H4 p/ [17
* K$ B2 U- i) B2 x10.3 时间差
) x/ H* f4 W {$ t( y& t10.3.1 Timedelta的生成! V7 Y+ M0 {) {) G& x9 h
pandas.Timedelta(value=<object object>, unit=None, **kwargs)
# G1 e ?0 J$ \9 g/ ` unit:字符串格式,默认 ‘ns’。如果输入是整数,则表示输入的单位。
0 \) n+ T% f/ H+ Z- h& i 可能的值有:
8 z! T$ ]* s3 S! X2 \% J( y: D3 `+ ^
‘W’, ‘D’, ‘T’, ‘S’, ‘L’, ‘U’, or ‘N’4 a6 ?' Z4 l( x! S' [% M
‘days’ or ‘day’ W5 \$ I G; ]6 x) A: ^# V
‘hours’, ‘hour’, ‘hr’, or ‘h’5 V8 d7 Z' i+ a" n- E
‘minutes’, ‘minute’, ‘min’, or ‘m’- ~* N( L5 O! [, J
‘seconds’, ‘second’, or ‘sec’' Z. @3 q$ U0 A' Y, L
毫秒‘milliseconds’, ‘millisecond’, ‘millis’, or ‘milli’- `' U+ e, ]4 N8 j0 m- B& @1 N/ m
微秒‘microseconds’, ‘microsecond’, ‘micros’, or ‘micro’
, ~ a2 J8 H/ v# k- j1 Z8 B纳秒 ‘nanoseconds’, ‘nanosecond’, ‘nanos’, ‘nano’, or ‘ns’.
! |' X; u1 f' q8 s% M, i" M时间差可以理解为两个时间戳的差,可以通过pd.Timedelta来构造:
5 [' h2 q; c/ M5 x* @pd.Timestamp('20200102 08:00:00')-pd.Timestamp('20200101 07:35:00') K+ I8 o% @1 s
Out[57]: Timedelta('1 days 00:25:00')
% l0 J/ n) t, K1 O: r. b
4 L& J7 o/ A3 h0 Wpd.Timedelta(days=1, minutes=25) # 需要注意加s
1 Z% d6 a8 t; P: d. _) VOut[58]: Timedelta('1 days 00:25:00')
* ^$ K% k- Z+ N: N$ U% T
8 Z0 _" U1 f- G9 u/ u8 J) H% d3 Lpd.Timedelta('1 days 25 minutes') # 字符串生成
. B% j2 ^" m6 X/ e3 POut[59]: Timedelta('1 days 00:25:00')
7 r8 J, Z [5 G! `$ @* e% ~% O# F& x7 q& M
pd.Timedelta(1, "d")
' q* r' K5 @9 L! W [5 a: aOut[58]: Timedelta('1 days 00:00:00'), Z |7 b. G# e, D; B* u
1
: ^; d2 N* w, ]6 @/ d7 Z! z2
! s3 S; }6 L' X5 J- ~0 I' T6 U3
/ o9 f$ B, o. \) }48 B& F' S" a! z* U2 H
5, q ^/ ^: A( Z3 @9 U( I6 A6 U* {
6! L( i1 y% P/ i% }* s, |- T1 E
7
! O, ]" w( l' F# F* g9 Z8 q9 i: Q, x" c% [/ N
90 ~) A u% N: n1 C
106 d/ U) ?' I6 p# j: O$ u# t
11: Y5 w% B$ g+ [9 b0 W* v. u
生成时间差序列的主要方式是 pd.to_timedelta ,其类型为 timedelta64[ns] :
8 [9 K1 m6 M4 y; X- ?5 }) X. _s = pd.to_timedelta(df.Time_Record)% m8 ?/ H6 Y8 Y$ K- v2 V4 V
: M% Z3 `5 R, V* k, q r
s.head()8 J6 z( T/ H3 E
Out[61]: $ ?5 O7 E; j2 o5 V
0 0 days 00:04:34
( ?* U$ A% x" }, B8 c% h$ h- `1 0 days 00:04:20
& _" _0 s, o# a6 w+ Y2 0 days 00:05:22
" {1 X/ c1 v! G3 C8 b' H3 0 days 00:04:08
5 B& K/ m l+ [4 0 days 00:05:22
9 k' S9 Y. L1 n7 Q7 kName: Time_Record, dtype: timedelta64[ns]5 i( N4 }* O1 Z! b$ a/ X# Y7 `- Y
1, Y1 p$ E4 [3 K; v5 K, {+ K( x
2# }6 O' z3 E0 I! g+ n S
3; I1 i. g+ n1 E2 _ M
4
/ E' t3 A3 `4 e7 ^. b; u- Y" A51 s9 N$ Z# f- H7 f9 B
6, u# n& f6 B8 j+ v
7! x- o3 e3 o7 I& G& y; y
8
3 c- b, u- p( U6 h/ w/ l9" V7 p6 o1 G+ A7 T
10
0 q1 i2 Q: { F S- L" H) O与date_range一样,时间差序列也可以用timedelta_range来生成,它们两者具有一致的参数:/ _! T9 @2 ?% {
pd.timedelta_range('0s', '1000s', freq='6min')& K& Y7 B* f/ v8 W( g# o8 a* h
Out[62]: TimedeltaIndex(['0 days 00:00:00', '0 days 00:06:00', '0 days 00:12:00'], dtype='timedelta64[ns]', freq='6T')7 n" T5 r( ^6 v4 w3 b
( ` z6 O9 U! X% J4 K5 p
pd.timedelta_range('0s', '1000s', periods=3)
6 w- Z* @& K( ^! c: uOut[63]: TimedeltaIndex(['0 days 00:00:00', '0 days 00:08:20', '0 days 00:16:40'], dtype='timedelta64[ns]', freq=None); |! w3 V, I' H5 F
18 d% N9 f8 c' G# \7 J
2' v5 E, F$ {; Z, M9 c+ C! ^7 l, I
39 C7 F8 u. ~5 Y" i8 j% y& ~7 l5 ?
4
`8 k' Y3 p0 ^* ?5
' ^5 S) v6 }6 u+ N1 p对于Timedelta序列,同样也定义了dt对象,上面主要定义了的属性包括days, seconds, mircroseconds(毫秒), nanoseconds(纳秒),它们分别返回了对应的时间差特征。需要注意的是,这里的seconds不是指单纯的秒,而是对天数取余后剩余的秒数:" {. ?/ s4 D2 z% {! r) u1 e
s.dt.seconds.head()
. q" ~: \+ z. y5 cOut[64]:
' \; Q( |* ^& q/ J: |$ M0 274, \$ Z6 P/ h( a1 G
1 260
7 z4 [2 ]' [* G5 V `2 322* F8 x' W' X0 V |2 ~
3 248
! D2 k3 w, X3 ]: e4 322
: v1 j4 [ P+ I# A0 HName: Time_Record, dtype: int649 e- {* m% \( y& U7 [8 v/ e
1
# ]5 T1 N* M; J2: D) v8 m! H2 v5 V5 D {3 J8 \
3
; `5 i! |8 K6 R+ L2 _9 H4 X6 E4% r; {2 e3 O: A8 l* M/ `
5& e- h" m" l# D. Y) ^
6! g' T& o3 a6 m: I; K
7
5 E6 }& o, R }% r8$ U- O7 A8 Y* Z* V( Y, |3 s% G! O! q
如果不想对天数取余而直接对应秒数,可以使用total_seconds
$ g4 C5 y+ u' ? K: H6 S9 e/ y9 A% U( i* }8 X/ r% F3 H+ D
s.dt.total_seconds().head()! I) G0 h4 v9 U
Out[65]: # K# L+ \$ [. Z! ]6 v
0 274.0
0 u( H' x) v/ a$ ^6 L, Y9 q1 260.0
- ?4 F) L3 v/ \% K7 D4 m2 322.0
! ~% N% i" |9 C4 `3 248.04 v* M9 ?# F! J; G
4 322.0
/ p1 \. z7 W& AName: Time_Record, dtype: float643 T" ], F' z1 w. @, E, _5 b
1 f6 H/ D+ p' r3 M2 z3 J
2$ j0 t- I5 W7 _4 Y5 @' H
3. Z5 \" @( m) K" Y- _) S! q
4/ b# {+ {8 _$ x/ b% n+ W
5 C* h6 ^/ x4 l5 N- m
6
9 R/ Z; [% T6 s2 y4 P7
. F& x3 t# r: X3 W' q84 \& v/ r% X5 ^+ o: ?! T( g: e
与时间戳序列类似,取整函数也是可以在dt对象上使用的:- M8 N3 Q0 ]0 f7 V! ^
# L% }" t2 }& {6 v
pd.to_timedelta(df.Time_Record).dt.round('min').head()5 W' y t( P& |3 G1 k( F
Out[66]:
3 G% Q5 x9 J4 E! d- T( J( ~0 0 days 00:05:00
) E) Y) x3 V, K/ l1 0 days 00:04:00
2 I) ~+ y' M6 [/ \3 ]4 u# D2 0 days 00:05:00# D, W M# Z/ h
3 0 days 00:04:005 C" S' i( Y* ?0 t4 g6 }
4 0 days 00:05:00
9 b2 \3 w, t* E J" n8 _" GName: Time_Record, dtype: timedelta64[ns]( B: z- s6 ~- a1 B
1
1 B3 W( g1 x% {# Q4 v% C2
+ Y; E5 X2 t8 d- x0 h5 ?; j" Y. x3& I$ F8 `$ Y$ @
4( C- D# b" H2 t$ Q2 X" x& l
5; q3 H% G2 H# [2 M% Z& [& Z
6
9 p9 G' y5 L' h2 ?* G2 Q$ ^: Y" s7% V q, I. c7 a k9 u
89 h, y- P5 a$ d" P" D2 _+ U
10.2.2 Timedelta的运算
2 u% R% E0 P2 A# j单个时间差的常用运算,有三类:与标量的乘法运算、与时间戳的加减法运算、与时间差的加减法与除法运算:
/ e: h& u, Z1 V [& }+ utd1 = pd.Timedelta(days=1)
, x* b* v% S2 Gtd2 = pd.Timedelta(days=3)
* P/ j$ L" P0 z6 P3 z( q( c2 I6 z0 Yts = pd.Timestamp('20200101')5 I% c; J8 L Z& \# Z& W
$ G3 E! s5 s1 w; i, Rtd1 * 27 q7 X7 `/ g7 B" g! [
Out[70]: Timedelta('2 days 00:00:00')1 q* Z( u! g3 B) o& B
4 m6 N8 H ~0 p" @+ Ftd2 - td1
5 r' Y( e0 ~; }Out[71]: Timedelta('2 days 00:00:00')' G/ ?* }% E/ B: F7 }/ s
5 Y! M1 E) X) [" I' N5 ots + td1
+ `# {; L6 [2 [( H6 {5 NOut[72]: Timestamp('2020-01-02 00:00:00')
4 w0 L( b' ?' o9 c: L& e p
( C) W. d5 v( B% E2 G* K/ dts - td1
! y2 q+ |$ e9 g+ _ ~Out[73]: Timestamp('2019-12-31 00:00:00')
* W% \. u5 }1 W) W* \+ D1
* w/ L8 N" i; X& n- f1 J2
( A% f+ G& y& U# Y, |1 M3
+ i4 T0 V v4 M! x: S: v4
3 n3 @, r6 [3 s5
* [" w# N+ g8 K* s6 U$ _+ L' Y6
1 Q# h' `) F2 _, L% d( y; V7
5 ~+ @7 z9 n3 w" ?! S' U) }' j8
9 W! k# p) p6 `. M0 b" W91 ]6 X# j. D* f% \0 ~6 w8 c
10: Z* A b0 y% h8 N& t8 Q
11% W+ ~' w M# {5 Z- y
12
w: @' N4 i8 p1 ]1 Z+ v$ h0 x% O13, \. B; x: k, v$ c9 @
14
- ?- U4 P; j$ A" ]3 m159 _; Z+ H" S( s
时间差的序列的运算,和上面方法相同:6 @) V, v. l4 O ~" Y
td1 = pd.timedelta_range(start='1 days', periods=5)
7 L; z' c, V a; I! u; @) ]td2 = pd.timedelta_range(start='12 hours',
: D* Z, {/ G8 q2 ^; \% {6 b freq='2H',* D2 y% I- h- b
periods=5) N8 }" T/ W( z# F% X9 [1 y7 F4 H
ts = pd.date_range('20200101', '20200105')
* L( o9 Q# d# [$ Gtd1,td2,ts
0 B% _" G& Z/ h6 I2 W3 k# N: X
! k8 X( V# e4 l I O: M3 ^* n( k5 BTimedeltaIndex(['1 days', '2 days', '3 days', '4 days', '5 days'], dtype='timedelta64[ns]', freq='D')1 _7 k, ~* C4 b/ K# {
TimedeltaIndex(['0 days 12:00:00', '0 days 14:00:00', '0 days 16:00:00', X4 t# U/ O; b; p/ z+ C1 x) \
'0 days 18:00:00', '0 days 20:00:00'], dtype='timedelta64[ns]', freq='2H')
- C; f# ~; a+ k; \/ O o+ E+ kDatetimeIndex(['2020-01-01', '2020-01-02', '2020-01-03', '2020-01-04',
; l, \2 V2 x( v4 x$ q '2020-01-05'],/ S- i" ^8 a6 G [* R
dtype='datetime64[ns]', freq='D')9 W! T! c1 o/ T. P" _7 F3 H" `* [+ V
1
% }6 [/ @4 d/ G r( x; u2) a, ~" c# X8 c3 |# j2 U4 i
3$ W- p! |6 ?( A- S1 v
4* n# E ^& S. F4 T e
57 _* T& n$ r( Y, q
6* T8 J' z O! r# q# H7 @
7 X' J0 r8 v9 h9 P+ b. c
8: O/ D- }, r6 k) O; ^- I& M
9' i! C N& B: ]& g3 v
109 k1 G" k. z1 V9 Z2 ~" `! t/ g, n
11
0 D" h d* c+ t- I( @3 t) h12
2 }2 D3 f( }- N# f0 w13
1 P& A+ S- Q7 ]* vtd1 * 51 o: w) w ]* c! v3 P. f
Out[77]: TimedeltaIndex(['5 days', '10 days', '15 days', '20 days', '25 days'], dtype='timedelta64[ns]', freq='5D')
% Y: n5 A. s& h* R/ k; c3 k" l6 {; o, D; {1 ~
td1 * pd.Series(list(range(5))) # 逐个相乘3 K" y z7 e; H& e6 s
Out[78]:
6 q4 o' f3 v2 @0 0 days j+ X5 x+ }7 ]+ A, g
1 2 days
% _) J4 z. a( G2 P1 I t2 6 days
1 T# v7 l6 q! _& c/ A3 @( k3 I' f3 12 days
! @" O4 D/ T) K+ j _0 z6 Q4 20 days
) `! b8 ]$ n& B0 Jdtype: timedelta64[ns]+ a0 b4 o. C7 y0 n) ~2 P: G ]: \
& N; W C0 p- E
td1 - td2
6 b+ h& X% X" n( _; M* W" HOut[79]:
- \6 D- S' e3 E8 y S1 P) l' k* ~4 h0 C6 sTimedeltaIndex(['0 days 12:00:00', '1 days 10:00:00', '2 days 08:00:00',9 _, U' d5 h# H9 P' j! _; `
'3 days 06:00:00', '4 days 04:00:00'],- u8 m) }+ @ y& b. `
dtype='timedelta64[ns]', freq=None)2 C8 @9 N0 j8 t. u% ^
: d9 ?; I2 {1 I% j) b
td1 + pd.Timestamp('20200101')6 R$ O/ H+ X& q; i/ q
Out[80]:
$ t0 q* @. @6 b- o. i- [$ i$ \DatetimeIndex(['2020-01-02', '2020-01-03', '2020-01-04', '2020-01-05',
5 |3 x! b/ X. |, |) K) i& c% C '2020-01-06'],dtype='datetime64[ns]', freq='D')5 q$ b5 T: V5 R5 }1 Q1 h: W; s
3 s. A; ~9 A5 y, r" X& T) R
td1 + ts # 逐个相加7 s: `: x' ^/ p; p9 m
Out[81]:
) K+ U/ @; o6 w7 C2 E [3 mDatetimeIndex(['2020-01-02', '2020-01-04', '2020-01-06', '2020-01-08',
5 N, w2 O( P. }$ [' ? '2020-01-10'],
! B) l: Y, m' i, V% L dtype='datetime64[ns]', freq=None)9 r: O- t' ?: I1 k' `1 `; ~- F8 G
5 f0 A+ P: e7 e y1
7 V: C2 V$ J v* }8 T4 b2
: m& _8 T, {# f( k( L3
( Z. K2 r2 S6 }- U5 }1 m6 V( k- c4
, x; G! |7 A3 L5
, b6 r( `0 A' ?7 d3 n6& l% ~4 C6 p$ k* w- Z& s
70 L& G; W* L/ U
85 E+ l0 P! J6 v2 L( d2 B
9
! a I- N# C( Y8 g7 E10
0 I% J. w u2 m! P11; a- l8 y% V O4 y* i
121 H% v% W* q! G6 T6 S
13
% |! d, l _9 q2 S& e7 T146 `4 u2 n- Q/ I7 R0 {% g
159 H, h7 U# a5 O2 M. }
16
: _2 A5 Y% x3 f9 Y17
" P- L# @: m& [+ S; U2 o% W18
- N# L. E& L) _+ T& s$ d( X19; \( U: x7 g; B4 W
20; j+ h4 k( w. J; k
21; l# |( m! B+ `8 a5 Q9 }. |. Y
22
* d: t: a4 X. p+ j* i; a235 \& j# h' m6 ]5 w3 l. A
24
0 G* Q. f5 O4 N6 u3 E25$ p* N$ G6 P% f- W2 l# ]+ @; `
26
9 n% |5 \) R: w% G27( ]7 p8 l+ K4 ^, p% u
28
/ q# G9 N- g$ W4 h/ E10.4 日期偏置
* b; I, {. m) i% f7 C9 i10.4.1 Offset对象
G3 F, ?. n6 x$ D! C2 b 日期偏置是一种和日历相关的特殊时间差,例如回到第一节中的两个问题:如何求2020年9月第一个周一的日期,以及如何求2020年9月7日后的第30个工作日是哪一天。8 s) L+ u7 F4 l5 S8 O; F- `
$ a1 U: ?7 _# S; MDateOffset 类有10个属性,假设s=pd.offsets.WeekOfMonth(week=0,weekday=0),则:2 M, e) G% N7 Q3 Y: K- h
/ A1 F; Z" Z# D( P/ ]s.base:<WeekOfMonth: week=0, weekday=0>,返回 n=1 且所有其他属性一样的副本
# x; Y" o" G/ t' m, o8 H s- j" @$ Js.kwds:{‘week’: 0, ‘weekday’: 0}# J* e: q* A1 ?7 q
s.wek/s.weekday:顾名思义7 h. L& \, n1 A9 Y3 N2 c9 p
有14个方法,包括:" ]9 J+ ]' |' L9 r c2 }' W
5 q1 ?" l! e9 D1 g
DateOffset.is_month_start、DateOffset.is_month_end、DateOffset.is_quarter_start、DateOffset.is_quarter_end、DateOffset.is_year_start、DateOffset.is_year_end等等。
/ Q- c1 p6 |# F4 q# I npandas.tseries.offsets.WeekOfMonth(week,weekday):描述每月的日期,例如“每月第二周的星期二”。! J: R" f, f! k5 o7 j
; R& y( O0 ]1 a
有两个参数:
; J/ W Q! {' R. _- wweek:整型,表示一个月的第几周。例如 0 是一个月的第 1 周,1 是第 2 周,以此类推。
0 r# H0 N5 c0 ^4 ^& J) V2 \! j: X* Mweekday:整型,取值为[0,1,…6],表示周一到周日,默认取值为0(星期一)
- F2 q7 q7 T# l( C! ]7 T% f9 R! }pandas.tseries.offsets.BusinessDay(n):相当于pd.offsets.BDay(n),DateOffset 子类,表示可能的 n 个工作日。
# M3 f% H; j: @2 I: Y9 d4 ?0 V/ c2 ?9 w4 x9 Y; t
pd.Timestamp('20200831') + pd.offsets.WeekOfMonth(week=0,weekday=0)9 ^$ _2 L" ?0 M7 t3 I" Y/ h
Out[82]: Timestamp('2020-09-07 00:00:00')
8 g I+ K! p* W4 e$ X* e4 U
# @- H3 w* c4 e0 Q/ {6 P; }pd.Timestamp('20200907') + pd.offsets.BDay(30)
! q- H$ `4 S1 X' ~! W: o0 COut[83]: Timestamp('2020-10-19 00:00:00')8 w5 i+ I0 w- `+ r7 i8 T& K
14 V9 X8 y- T+ B# ]
2
# A' A1 B& ^! G t# ]) n3* Y7 {) k$ t U4 ?9 l8 S: K; ~
4& S2 _/ X$ T( _5 I, `2 }2 q
54 I* i& w/ z" G
从上面的例子中可以看到,Offset对象在pd.offsets中被定义。当使用+时获取离其最近的下一个日期,当使用-时获取离其最近的上一个日期:; l, Z. m# o' j) c8 y8 Q
! V5 n t/ x1 }9 f ?2 b
pd.Timestamp('20200831') - pd.offsets.WeekOfMonth(week=0,weekday=0)
/ Z. {0 U; O; A# R9 g0 YOut[84]: Timestamp('2020-08-03 00:00:00')0 A$ _* d# {; u& ^
" b3 H; }6 l3 h* }+ _
pd.Timestamp('20200907') - pd.offsets.BDay(30)& u7 ]' t2 ^, B6 J9 ?" p2 f
Out[85]: Timestamp('2020-07-27 00:00:00')) N- {: }% w. Q7 V8 U. A) F
0 \( j J' O$ N, ^9 I& K! Epd.Timestamp('20200907') + pd.offsets.MonthEnd()
8 Z5 E5 B$ K5 m( aOut[86]: Timestamp('2020-09-30 00:00:00')
1 k- n3 ~8 B% l" f* q9 B' g3 U1 P13 n1 j* D1 b3 F0 G5 u- G/ r
25 G5 J4 i: F/ Q8 w+ v V" R. Z
3
m) B$ n3 g K" j4# g: x2 Y) m! r8 |3 I* b
5
6 Y8 P+ p; K3 _6( b/ W( I9 m1 h* X
7
: u; s$ l6 l% n) w8; J# b& O7 I9 n/ c
常用的日期偏置如下可以查阅这里的DateOffset 文档描述。在文档罗列的Offset中,需要介绍一个特殊的Offset对象CDay。CDay 或 CustomBusinessDay 类提供了一个参数化的 BusinessDay 类,可用于创建自定义的工作日日历,该日历说明当地假期和当地周末惯例。- x0 J0 u6 B. S2 ^. h
其中的holidays, weekmask参数能够分别对自定义的日期和星期进行过滤,前者传入了需要过滤的日期列表,后者传入的是三个字母的星期缩写构成的星期字符串,其作用是只保留字符串中出现的星期:
2 L4 S6 ]1 _* Y& b2 [" l' n T/ r# z0 S6 R! ~6 n9 g
my_filter = pd.offsets.CDay(n=1,weekmask='Wed Fri',holidays=['20200109'])% t( O! V# S9 J+ f7 Y
dr = pd.date_range('20200108', '20200111')0 g, f: B4 D) |$ c9 u( D9 R- t; J3 q, y/ e
) }8 {: | J' F
dr.to_series().dt.dayofweek: S! c [/ x3 K3 E" h: {
Out[89]:
( x+ M0 p: H9 x# k2 Q, Y# G. W2020-01-08 29 a; w( E# V% Y$ E0 G
2020-01-09 3
0 {; z* U( q) s. ~+ q* G2020-01-10 4
0 O% _ i: {: g. f4 q. Y' ~0 R5 I2020-01-11 5- k% D7 n0 P$ q( J
Freq: D, dtype: int64# O- q, S0 V0 x; M3 Q+ f
# R( _4 {6 G9 l9 G1 n$ T+ n[i + my_filter for i in dr], g* D- ?$ R) I
Out[90]: & z2 g6 ]) y: u M* r& X
[Timestamp('2020-01-10 00:00:00'),2 B" \* @, i+ I$ \' g
Timestamp('2020-01-10 00:00:00'),4 w7 S# A) p( |) |
Timestamp('2020-01-15 00:00:00'),$ @8 ^5 _; g+ F, Z
Timestamp('2020-01-15 00:00:00')]
( U# c: W4 B( T( v+ o
( S- n0 G' c- g. G$ Y9 @$ Y1: o, C. U, y1 y6 l3 n
2
0 B; E4 t7 `6 o- [3
) F8 U; r2 k; V) ^/ ]/ [4
5 s2 q1 U0 p- {( L5
' i' E p5 D+ g8 ]9 Q ]! T6
3 o6 c. K2 |, _! h' \9 L9 W/ W2 | i7 u7
6 d* W; [4 m b& f0 ?. j" h8
/ N; I! K. W1 I9 b. [99 i& _( @, x, r4 R7 H; i ~
10
, s. L1 i: ]3 H0 d h6 i11) Z @$ c" ?9 @1 [( i2 o+ I
123 W4 C& _% `! a3 V3 \6 K5 O
13
+ ^, Y6 i) ?) ^: m' ?9 `14
: X5 `# A& J7 `+ o7 b15
, p, P9 |1 h# v4 L% ?0 R$ W16
B% G9 s& X. s" S; D: ?17
4 J V% ?# E5 d1 u: X+ e8 \ 上面的例子中,n表示增加一天CDay,dr中的第一天为20200108,但由于下一天20200109被排除了,并且20200110是合法的周五,因此转为20200110,其他后面的日期处理类似。, n3 Y( P4 k+ @) [) F) `9 n' s/ I
+ H! H3 `2 c/ T: V9 G7 j: a; v% y
【CAUTION】不要使用部分Offset* q5 u; W, ?- s7 E1 v" z/ m) c
在当前版本下由于一些 bug ,不要使用 Day 级别以下的 Offset 对象,比如 Hour, Second 等,请使用对应的 Timedelta 对象来代替。
% K# [# y& _- G: @" ]0 i5 ?( U: V- o/ M8 h0 Y/ Y* I |; M" B6 i3 W
10.4.2 偏置字符串9 m& W+ H* I7 m& K( J
前面提到了关于date_range的freq取值可用Offset对象,同时在pandas中几乎每一个Offset对象绑定了日期偏置字符串(frequencies strings/offset aliases),可以指定Offset对应的字符串来替代使用。下面举一些常见的例子。
8 t$ b5 @ [. `& J7 W/ @) J5 t. q# G! B& D' r# M" J6 s
Offset aliases:pd.date_range函数中的freq参数,为常见时间序列频率提供了许多字符串别名。 也称为偏移别名Offset aliases。偏移别名列表点此参看(大概27个)。" N2 X$ X2 o" H, M; C; n. t% [
$ R, Y& s2 G# O9 V6 R
pd.date_range('20200101','20200331', freq='MS') # 月初
! x; {$ }; C( t( C! S" ?6 lOut[91]: DatetimeIndex(['2020-01-01', '2020-02-01', '2020-03-01'], dtype='datetime64[ns]', freq='MS')
; ]9 ~0 z0 }+ y! H' l) G/ ~, `" G
6 C2 e3 f H7 Y, Ppd.date_range('20200101','20200331', freq='M') # 月末* o7 P' J' t. ]% m5 Z0 ^9 W- ~5 w3 C! Y
Out[92]: DatetimeIndex(['2020-01-31', '2020-02-29', '2020-03-31'], dtype='datetime64[ns]', freq='M')0 I# S/ q5 n! k* h. [
- R1 F5 N- t4 [4 G* M
pd.date_range('20200101','20200110', freq='B') # 工作日 T$ G5 Q9 Z2 i: x
Out[93]:
/ t% r2 B# U% g% dDatetimeIndex(['2020-01-01', '2020-01-02', '2020-01-03', '2020-01-06',& s/ }5 v: p2 f. k5 x/ z
'2020-01-07', '2020-01-08', '2020-01-09', '2020-01-10'],9 z# F4 y* q( C
dtype='datetime64[ns]', freq='B')( r% g) g( {3 |" x. r) u
4 D ]& t& e- S' t! w" P
pd.date_range('20200101','20200201', freq='W-MON') # 周一* K# k7 f% {- y% r$ D1 r
Out[94]: DatetimeIndex(['2020-01-06', '2020-01-13', '2020-01-20', '2020-01-27'], dtype='datetime64[ns]', freq='W-MON')$ M+ n; Z; W u z1 G- t" w! Z
9 U3 \' P/ z+ d+ Tpd.date_range('20200101','20200201',
7 C) x' h3 e0 x8 ^5 Q freq='WOM-1MON') # 每月第一个周一
) A' |; n( j- Z0 w3 H' A) m' N8 Q
9 v) W, M6 X: \& X$ M6 WOut[95]: DatetimeIndex(['2020-01-06'], dtype='datetime64[ns]', freq='WOM-1MON'). z6 J" y6 {7 p B: m& V
3 D2 D m" I$ S" D3 x9 ^1: C& W" h) p# R0 @5 _& q1 v/ a# t
26 M* w' i* z T0 C% \$ j
3- B4 ^( H8 G) o! I
4
* D: W- W7 _ ?( [59 G3 G" \) s( P. s
6; A% S$ l7 y& h) w% \' F' x V
77 Y3 c) v+ ^, N- a: [9 s
8
) N8 Z) D7 ^! B0 ?- M* T6 J/ I94 V! |! l, _" x7 a: \+ ?0 \
105 P3 }( ?( y" o
115 Y& j0 J, N$ a. L" \+ o! v
12# D- r0 y$ U1 n' ]3 s# f. S+ D
13
+ h( a% x" C$ P" e% Z14) F7 E" ^5 }7 t/ U# v
15
) s+ a. P# t. C$ j( ^4 U: x' V3 ?16& A; k( o7 `2 T& p( K T
17
; n' E/ N6 R8 s6 ~. P- r1 Y: G18, T4 h$ ~% K/ G* c
19& _9 ~- d- T& \* i2 K: ?
上面的这些字符串,等价于使用如下的 Offset 对象:
. `8 ~8 o8 \. ^, F
! U0 d: Z. _/ w: b7 u, g ipd.date_range('20200101','20200331',; z5 }7 i3 o- X" s; T: [$ k7 g9 f
freq=pd.offsets.MonthBegin())
$ h5 o. @0 q2 n0 z3 M7 Z$ r7 U6 Y2 D, c! q d
Out[96]: DatetimeIndex(['2020-01-01', '2020-02-01', '2020-03-01'], dtype='datetime64[ns]', freq='MS')/ A0 ?2 e) P U. n; z
( w% g$ {% [+ l; Xpd.date_range('20200101','20200331',
5 S5 B9 @7 P& X/ W+ H) w* k freq=pd.offsets.MonthEnd())& f1 Q5 E( S2 |% ?, ^
G) e6 I! V+ l- `( a) K
Out[97]: DatetimeIndex(['2020-01-31', '2020-02-29', '2020-03-31'], dtype='datetime64[ns]', freq='M')* N" q0 a' W2 _
$ F& v; s$ w, y4 ~pd.date_range('20200101','20200110', freq=pd.offsets.BDay())% C7 _- Q. d; k, k G3 j
Out[98]: . I& Z$ {& ?- O7 a; w0 k% [' H
DatetimeIndex(['2020-01-01', '2020-01-02', '2020-01-03', '2020-01-06',
% a; _# j- }) [ '2020-01-07', '2020-01-08', '2020-01-09', '2020-01-10'],, C, b6 ]% L8 Z: L7 L; d7 j/ }
dtype='datetime64[ns]', freq='B'): m' i+ } Z3 X" A8 J* z( K
* n4 g6 G6 i4 n# _* @- w. J
pd.date_range('20200101','20200201',
7 y ^' Y1 ?7 ?) g freq=pd.offsets.CDay(weekmask='Mon'))
( q/ a w: h" G( r$ H6 q- }( m! j, v% D! v' }( \$ ]1 l
Out[99]: DatetimeIndex(['2020-01-06', '2020-01-13', '2020-01-20', '2020-01-27'], dtype='datetime64[ns]', freq='C')
1 O/ \$ @5 k( j+ @+ d" \ u9 d
" i7 G, }* z5 K/ opd.date_range('20200101','20200201',
7 i. z* Q8 p- t freq=pd.offsets.WeekOfMonth(week=0,weekday=0)), Y5 A1 N5 y! V5 e
) a: _/ b, _3 h0 \6 JOut[100]: DatetimeIndex(['2020-01-06'], dtype='datetime64[ns]', freq='WOM-1MON')
* q' G. C0 c: X; ]# y9 ?
/ |% {+ ~& L( k" L: y18 r( v3 C, D6 u( N |# P7 Q$ ?+ e: M
2
! I z2 ^6 P! d) K- G3
i5 U4 F' [ b8 p. {6 x, U2 a44 {9 M6 z8 w3 _0 Y
5
/ X# j* i& m0 Y& e6% J$ {( v8 i2 Y# S# _: q
7" `) T' X. s0 [/ J ]& \6 \
83 j4 X" | T; N- t# s
9
* K" {. y9 H; O& I* Y10
5 C0 r! e1 P! v- X6 Y- X11, |! t7 O9 f& }; Z1 U
12
2 z4 f; u: o3 A Q; Z: f/ o. f: _13
6 R0 S; H0 _* r5 T! _6 f3 q14/ X/ I. H! a# s2 j9 Q& t$ c, i
15
, O& n* K7 D) X+ ^+ X16* C4 H' u) x y' U
17% U( A( d; F" E& U) n& i# J- J
18 j# i% e3 t7 m! y+ T6 |; i
19
% U9 M; ~% ]- e5 c! _20
8 j' y- {- u$ V$ p' v, L21
" b0 \: s2 b0 O) |. o4 a* J22
/ Z4 H& T" m" ]' I$ g* k23# B; B8 P4 D: F; Y/ a+ h" j
244 W* @9 F' M* t& H1 a+ X1 ? w
25" X: y- g, B. z
【CAUTION】关于时区问题的说明
4 ~! m- b4 m0 T7 b 各类时间对象的开发,除了使用python内置的datetime模块,pandas还利用了dateutil模块,很大一部分是为了处理时区问题。总所周知,我国是没有夏令时调整时间一说的,但有些国家会有这种做法,导致了相对而言一天里可能会有23/24/25个小时,也就是relativedelta,这使得Offset对象和Timedelta对象有了对同一问题处理产生不同结果的现象,其中的规则也较为复杂,官方文档的写法存在部分描述错误,并且难以对描述做出统一修正,因为牵涉到了Offset相关的很多组件。因此,本教程完全不考虑时区处理,如果对时区处理的时间偏置有兴趣了解讨论,可以联系我或者参见这里的讨论。
3 R7 |& N5 n. t2 F: F/ z. T5 E/ J6 H5 u- }' ?0 D1 Z5 u! q5 ]; D
10.5、时序中的滑窗与分组
# B% l. P7 l @$ B* j3 p10.5.1 滑动窗口5 q3 n3 n, Q" s, H: f( Y$ [# y
所谓时序的滑窗函数,即把滑动窗口windows用freq关键词代替,下面给出一个具体的应用案例:在股票市场中有一个指标为BOLL指标,它由中轨线、上轨线、下轨线这三根线构成,具体的计算方法分别是N日均值线、N日均值加两倍N日标准差线、N日均值减两倍N日标准差线。利用rolling对象计算N=30的BOLL指标可以如下写出:
6 v# a2 ^5 r# l: ^2 j! i. h/ Z) X
7 x1 t6 V3 Y q$ z6 ^, himport matplotlib.pyplot as plt
3 B! H0 Z& c/ C( C1 fidx = pd.date_range('20200101', '20201231', freq='B')
/ Z% z9 m% ]' l! t, Wnp.random.seed(2020)
& n, T6 I+ j; P# _
# N1 s/ J2 h2 D6 J' \+ Qdata = np.random.randint(-1,2,len(idx)).cumsum() # 随机游动构造模拟序列,cumsum表示累加
5 X5 x/ {9 J. V5 R9 s9 Ns = pd.Series(data,index=idx)6 {4 r1 {7 T8 \9 X& u$ s
s.head()# z- P5 h u' L, W0 `% k/ ^) T8 n0 N2 ^
Out[106]:
1 J$ _. @% S$ |! V2020-01-01 -13 D- p' {- h5 @( y# h
2020-01-02 -21 H& k# [' \6 X, i+ b9 B
2020-01-03 -1
, k6 v5 T0 U; s% d( m8 m; [! c+ X2020-01-06 -1; J0 D% Y" I* Q1 s2 @
2020-01-07 -2$ ~* J, @6 C7 X; v- G- u" |) ?
Freq: B, dtype: int32
4 k9 L* [! p) H9 c7 \r = s.rolling('30D')# rolling可以指定freq或者offset对象
) V/ Q( p: k% i7 d. W/ R
( ~: Y- Q' Z" l( i5 z& Dplt.plot(s) # 蓝色线
" |1 y* s1 t" K. XOut[108]: [<matplotlib.lines.Line2D at 0x2116d887eb0>]
* R3 y% d: y% e+ q0 f! Qplt.title('BOLL LINES')
( Z: e$ i: ]6 b. AOut[109]: Text(0.5, 1.0, 'BOLL LINES')
: Q+ i) A4 @" c! m1 }7 F
; J* s y9 R4 V* [5 v' v' v$ yplt.plot(r.mean()) #橙色线 }, Y0 }, v) p0 \3 m, ?
Out[110]: [<matplotlib.lines.Line2D at 0x2116d8eeb80>]
' r" p9 Q$ Z( h: W
7 D% t% \* B5 R7 kplt.plot(r.mean()+r.std()*2) # 绿色线
) t% O `# Z) r, c( c; FOut[111]: [<matplotlib.lines.Line2D at 0x2116d87efa0>]
0 o0 V" R7 d* q& F1 p) S0 m* H9 o% ]
plt.plot(r.mean()-r.std()*2) # 红色线
$ Y4 `+ K0 A3 L# [1 V! ^Out[112]: [<matplotlib.lines.Line2D at 0x2116d90d2e0>]. y8 B2 S1 w' r7 H
- q1 [% P) m* D+ E. a; O* E
1* t: R! f( Z# ^" h
29 a: Z! v# e6 Q, G' m. S+ I
3
3 w ~4 I* V z% p2 P4
. B% [: j' P' e* U7 r, o( ~4 ]1 Y5& A6 x2 I5 t$ m' n5 B
6
/ i7 G; q3 W2 @: M' h% W5 x1 T! V7
% W, J# |2 b9 q) `8
9 ~& m* m0 v B! r+ R+ Y9% J( a; J# Q3 M2 w* P: J4 T
10
3 F4 v9 |, |: q' S& I0 |11: `* v" P/ C, L+ H! w' b
12" i: L( Z. }. x
139 O( O, i5 E3 b3 Y
14
* @/ c! I2 r W" |% a$ v, `& x15
! p( e/ O4 h- ~. J. o% N16- m7 t( l' R' y
17
! D6 V- h2 a; ^* }# s18
: l9 X! I) ~# q% T0 X8 X# f4 [ a$ j19
$ k, S M8 ^/ k/ G. ~205 h1 C/ v, S6 G% }
21
8 x( q7 N6 N, I1 i( W1 W22
! i2 G: N$ s# ?7 j239 J$ @+ Q% H$ K- ?. x
24/ o( Q1 ?9 J5 k9 P8 q
25" j T) O N5 B! P {: v. b' r1 z
266 W( V5 N- `* @! l
27
+ L& h" Q1 p) F, f7 p( [28. `- p& {8 v) [. y
29
C+ B. l# {0 t2 _% z7 o- V; D
. S' `" T, D) a8 s$ o! c 这里需要注意的是,pandas没有实现非固定采样频率的时间序列滑窗,及此时无法通过传入freq字段来得到滑窗结果。例如统计近7个工作日的交易总额。此时可以通过传入多个函数的组合来实现此功能。
' O$ h& r$ t, H/ ~ 首先选出所有工作日,接着用普通滑窗进行7日滑窗加和,最后用reindex()恢复索引,对于双休日使用前一个工作日的结果进行填充。
& B6 g" `$ s5 R: I* `$ @# A! M* I- c7 g7 G6 K# B7 f
select_bday=s[~s.index.to_series().dt.dayofweek.isin([5,6])]
% ~* ]+ s, L0 q2 A2 Jbday_sum=select_bday.rolling(7,min_periods=1).sum()9 L/ r) S @2 {, e; q3 u) X* n( C
result=bday_sum.reindex().ffill()3 b' F# e9 H7 E1 K c) i1 v
result/ h0 N; r: C& r' ~
z, U0 b6 S+ o, v6 H1 l/ f' V
2020-01-01 -1.0 v- U* O7 S1 v4 F2 z
2020-01-02 -3.00 z7 b6 _& _7 u+ c( } d% \; ]9 ~+ L' @
2020-01-03 -4.0
- H9 S+ m" S. k4 v% |# T; G2020-01-06 -5.0; D6 o9 d8 _+ o3 C4 ~7 o
2020-01-07 -7.0. `$ g# D, n( W) K, I2 g6 w( y( w T# W
... q& B1 F5 m+ t) {4 G& h
2020-12-25 136.0" T( p3 T, l0 ^
2020-12-28 133.0
- e0 V! H" B9 H' c2020-12-29 131.0
" {, q% A* a2 u( P7 x- k2020-12-30 130.0
4 q8 R/ v6 E5 x# ^/ O/ X$ G5 \2020-12-31 128.0
: X& {& F2 C# C! H/ I) @Freq: B, Length: 262, dtype: float64 |: I. Z3 t- o4 A4 M- m0 w
4 s& }4 ^6 b0 S3 m7 ?- {1* d8 l" O& J3 n/ v5 l% a+ e4 A6 T
2
" a$ N& g0 L0 v31 m' `& z, L& h5 Q
4( k8 t! b8 a5 j8 Z
5
5 _; l. z: Y* j, d3 `8 d& M/ M6; k- S8 n5 m( p! d0 q
7
7 P6 g8 D: X+ V/ q1 e1 K8
7 e0 g7 h6 C- l9
" d( H" m/ X* n+ G1 O10
$ D7 ~ E1 `4 w5 D11# E; j+ F- W5 B3 z* D8 v( O. h: W% e
129 z: {( W9 K# l) I
133 q5 O$ t, H' o; \
14) _: {; Y4 f* |1 n; s
15) Q; N/ h2 v7 ?( K+ j7 ~
16
! H1 o9 S* F. i4 b17$ ^% q4 w: _* U+ I7 o w9 g
shift, diff, pct_change 是一组类滑窗函数,它们的公共参数为 periods=n ,默认为1,分别表示取向前第 n 个元素的值、与向前第 n 个元素做差(与 Numpy 中不同,后者表示 n 阶差分)、与向前第 n 个元素相比计算增长率。这里的 n 可以为负,表示反方向的类似操作。
! P% l1 x/ k* g1 K8 _
0 G3 b- Q0 b, p4 K 对于shift函数而言,作用在datetime64为索引(不是value)的序列上时,可以指定freq单位进行滑动:* i+ F1 c- e4 Y6 u& ^- N5 C
: y8 P, A) x0 ]$ ~0 @) i8 D$ R8 hs.shift(freq='50D').head()7 k" b0 M3 _; O9 }( r- }+ H
Out[113]: ' P! Q. B9 N' q! u
2020-02-20 -1
1 i1 E. O3 q% p, x# O% h6 { q8 O2020-02-21 -2) N9 l5 X( k' j/ B
2020-02-22 -1$ S% h" X' I5 |1 ~, C: L0 T
2020-02-25 -1
0 [+ z" G. I" J }2020-02-26 -2
8 U3 B" y. F. o3 c3 z- |dtype: int328 Y r2 Q) G4 |
1( b# H2 ?% l$ H% }3 q+ k
2' e2 }6 H* h' }8 c
3
! d6 k" v6 @2 P6 P4* D4 E, t) W0 ]; D4 q J
5
2 Z& g- j0 ^) ?( W6, b% U( Z- b8 [1 g) [
70 S% C2 B+ u- T; e" m2 Y* v$ {
8
: e6 R3 E7 J/ ?* k. h, `$ i 另外,datetime64[ns]的序列进行diff(前后做差)后就能够得到timedelta64[ns]的序列,这能够使用户方便地观察有序时间序列的间隔:
6 I! p6 R4 u7 I5 O9 W' I( N% _; ^! f: y8 \9 i. x- B% @
my_series = pd.Series(s.index)
( R1 Z r0 ~$ o$ m8 V9 v, umy_series.head()
4 o; Q1 [( ^* SOut[115]:
1 q; J2 k% e. E) a5 R1 R$ d0 N; m0 2020-01-01/ K T0 B; F/ t0 H7 [
1 2020-01-02
2 ^0 n' j& w$ e# X9 Q% w2 2020-01-03
$ O! [8 f* H4 n. ]$ S3 2020-01-06
: ^0 B" P* c' A0 M! q4 2020-01-07! E: I/ }/ @% [$ D
dtype: datetime64[ns]
0 e6 W; o4 O6 k. [3 M/ Q% K$ z. V# \+ Z$ ^1 m7 U- c: S
my_series.diff(1).head()- q e2 z2 f1 j1 f: `: ~( D
Out[116]:
G" c) ~& h ~: t$ ]3 x0 NaT- G# M; o+ H& [* P
1 1 days8 z1 Q# K6 p& r/ i
2 1 days
0 {+ J0 G7 ?* b& v# X" J g, G9 L3 3 days
* t- F% L+ Q" f6 m" ?7 x4 m$ o: y( I4 1 days
# L/ {3 j2 G) Ydtype: timedelta64[ns]& d0 ~: [# c% x, [ r- {' K
; ` I C$ p. W5 N1
y& c& ~: j. Y- }2
5 |$ @# a! k% h K% A: `* d1 N$ A34 G7 i% `# n5 S3 a
4
7 A7 R, v: V. c5/ x5 A. b' r4 E* j. R' k# \
6
& x4 e% X4 u' |3 W) |3 I: E$ o74 r8 o6 y `. q, _
85 E: U& R. O- L
91 W4 G( \. J8 k2 L* h/ v/ C
10
$ I* T5 Q p2 D7 F" H" t11
$ R0 a+ j% r+ F) a+ e12
- K; a/ X0 }/ u13. N, r( Y! ]% R/ c4 b! z. ^, x: A
14
9 N0 h7 s. d8 p$ ]* x15
* \" x, A7 v9 G2 i4 Z3 k168 N% K/ R! P1 v" J% s# T: w$ t( b
17
7 G! N# s: D$ \2 j6 {( M. k18
( V2 z: Y9 m% X( r6 I10.5.2 重采样
4 @4 p# v$ m' W# g( S' q 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)$ ^- s9 [6 ^$ i( i+ {
常用参数有:( O L6 u0 \5 `' @- T
3 \7 p B3 G2 F2 m, L0 F( srule:DateOffset, Timedelta or str类型。表示偏移量字符串或对象
! J* _2 m3 g# U7 Uaxis:{0 or ‘index’, 1 or ‘columns’}, default 0。使用哪个轴进行上采样或下采样
9 q. O- B: s, o/ Dclosed:{‘right’, ‘left’},默认None。表示bin 区间的哪一侧是闭合的。所有offsets的默认值为“left”,但“M”、“A”、“Q”、“BM”、“BA”、“BQ”和“W”均默认为“right”。
# \. f6 F2 k7 ?4 @$ vlabel:{‘right’, ‘left’}, 默认 None。hich bin edge label to label bucket with,所有offsets的默认值为“left”,但“M”、“A”、“Q”、“BM”、“BA”、“BQ”和“W”均默认为“right”。+ G- \. A3 s N# f: s
convention{:‘start’, ‘end’, ‘s’, ‘e’}, default ‘start’。仅针对 PeriodIndex,控制是使用rule的开始还是结尾。
3 H' k' |+ \% y" ~! _on:字符串类型,可选。对于 DataFrame,使用列而不是索引进行重采样。列必须类似于日期时间。
. j& J" _! o+ ?( J! m$ Tlevel:str 或 int,可选表示多重索引MultiIndex的级别,这个级别的索引必须类似于日期时间。2 K+ U/ |5 O* r
origin参数有5种取值:$ ?& M b9 x& w( }6 Y
‘epoch’:从 1970-01-01开始算起
: a5 A6 f& o6 r0 N0 V7 A‘start’:原点是时间序列的第一个值
. |1 v! n# ] I0 _6 h‘start_day’:默认值,表示原点是时间序列第一天的午夜。* {) q+ n$ k1 |# N0 ^8 c4 F, \
'end':原点是时间序列的最后一个值(1.3.0版本才有)
' y$ U- @6 M% [; u9 b) B‘end_day’:原点是序列最后一天的午夜(1.3.0版本才有)
8 |2 x3 X6 m/ o1 C. q# [offset:Timedelta 或 str,默认为 None,表示对时间原点的偏移量,很有用。" \: ~0 T5 R% c
closed和计算有关,label和显示有关,closed才有开闭。 b0 Y! }7 `+ o4 I D, y% i
label指这个区间值算出来了,索引放区间的左端点还是右端点,closed是指算的时候左端点或右端点是不是包含。; s& f2 s: o& k) s# W
" x4 R$ E* |* I: B9 Z
重采样对象resample和第四章中分组对象groupby的用法类似,resample是针对时间序列的分组计算而设计的分组对象。例如,对上面的序列计算每10天的均值:
P' t: Z3 Y) G$ }- D/ D# ]s.resample('10D').mean().head()2 e0 c: j. d$ J3 x, | q1 V% _
Out[117]:
' \' Y' T1 Y) X6 [2020-01-01 -2.0000008 [9 z' v# F; P3 E7 s: U
2020-01-11 -3.166667' m4 `; i4 Z3 Q2 y) R w) }4 ]
2020-01-21 -3.625000% o. R# T# x2 C" P* ?) J
2020-01-31 -4.000000
& J4 a$ T3 F7 X3 a* y2020-02-10 -0.375000
2 d, a5 J4 J/ N( H4 EFreq: 10D, dtype: float640 a& F( I8 ?) c9 ?' }' X- \
1" F+ {0 F6 _; c) X5 ?
2
0 ?/ Y2 b& Y& ^) F o33 x: R. S, L/ n0 p/ c1 _& F" U
4& G0 ~# \1 g6 X5 B# g! i# `5 ^# u
5, j4 M# l Q* n/ A# ?
6
# |( p" S1 T" m5 x77 g4 Z: R9 x! q" T: s, V! N
80 h8 Z A8 M, Q! O6 b0 n; h/ w
可以通过apply方法自定义处理函数:
2 S8 V6 ~" b& F& d6 s0 F3 cs.resample('10D').apply(lambda x:x.max()-x.min()).head() # 极差/ }4 R! Z3 Z, l4 f9 F5 f
+ H& {' f( X, Z. o
Out[118]:
5 s$ j0 o" |9 s9 d$ F2020-01-01 3
; P" O5 d! F9 p" D2 i; P0 o& _1 G2020-01-11 4
: Y2 v: J1 Y7 z$ r) }2020-01-21 48 j8 U& ~+ z7 _" ]- x$ U8 z
2020-01-31 2
2 V$ N3 Z7 \" M" W+ N' ^" h2020-02-10 4! _$ T$ F" ~2 ~! F% F+ ^
Freq: 10D, dtype: int32
3 N0 _: C, P% d# ?8 R3 }1
, {9 V% F8 P8 T' @2
. g/ N, L* `! ^7 S3
9 n% e* U, `) Y0 [* q4
* Z% F) P7 q# m( t4 |6 ~2 H2 X5 F5
) e# m G; j+ k6* a9 M; Y1 Q! p& u/ u8 {" d
7" j! g/ O) t2 b9 w- k
8" w- |9 c/ X! ?7 h
9
7 C3 Z$ d+ ^" t5 R6 T, s 在resample中要特别注意组边界值的处理情况,默认情况下起始值的计算方法是从最小值时间戳对应日期的午夜00:00:00开始增加freq,直到不超过该最小时间戳的最大时间戳,由此对应的时间戳为起始值,然后每次累加freq参数作为分割结点进行分组,区间情况为左闭右开。下面构造一个不均匀的例子:
! X/ E C' O2 a5 p ]
6 @( G0 I, ]- @; m8 didx = pd.date_range('20200101 8:26:35', '20200101 9:31:58', freq='77s')2 ]7 T' l; Y) F
data = np.random.randint(-1,2,len(idx)).cumsum()1 U* H0 K( v& u4 l. s# ~
s = pd.Series(data,index=idx)
/ d% e j# ^ I$ Es.head()
. G' O2 g. W3 z5 T2 ]. K5 q) d- y5 h/ d' U! T8 I
Out[122]: g; Q- {4 U- E5 y
2020-01-01 08:26:35 -1
S: H# _! n. U$ r2020-01-01 08:27:52 -1
( E3 h# K0 p5 p: c9 b; o2020-01-01 08:29:09 -20 `6 F* L& x8 \& G5 A! N
2020-01-01 08:30:26 -3* d: K4 g1 W3 G: A4 L
2020-01-01 08:31:43 -4
7 j: a- `# h1 j$ ]: C' O$ A$ p4 dFreq: 77S, dtype: int32& s! T# i2 m) c; ^4 {1 T
1
( ?) Q; x4 @, m) H2' H) z2 z" V; H; d) l9 o# m: V
3
* [4 O: k6 K4 I1 W, x1 t! E$ J, o4
1 z2 J: [' {1 f- ]5 w, F5+ q6 |: e& G* \; S
6
: u8 e$ N8 i: Q/ b% o7/ v b5 g, C, z, r) b5 }8 z) y" c7 n
8. c U4 |$ h" O6 S9 m
9
- L" e6 D4 P3 Z$ a' l5 O10 l% n8 n( A# C. Z; q
11- D% d* G# c0 m' p8 u8 G7 k
12
# `. ]9 ~) O3 Z# W: l4 p% K/ y 下面对应的第一个组起始值为08:24:00,其是从当天0点增加72个freq=7 min得到的,如果再增加一个freq则超出了序列的最小时间戳08:26:35:
' P$ G. h: }' l8 ]* ?+ R
5 U1 R; ^/ L3 Ts.resample('7min').mean().head()
; a8 q3 b8 a! p0 y2 POut[123]: 0 `5 j$ f. C" E' n' U v
2020-01-01 08:24:00 -1.750000 # 起始值,终点值包含最后一个值
7 E" b- F* w( Y$ e5 S( r. e8 `( }2020-01-01 08:31:00 -2.600000
4 X( W- ?& D1 Z: ?" x& q K2020-01-01 08:38:00 -2.1666671 z7 b' }7 ~- q7 n2 z" ]9 r
2020-01-01 08:45:00 0.200000; f% I5 R# H8 P
2020-01-01 08:52:00 2.833333$ R, f% E: e4 ^4 Y$ M. O: r4 x
Freq: 7T, dtype: float64) u+ {/ h7 V2 ~4 v0 S
18 f& H+ w- j) D# D9 E
2
# t5 H6 Q. `4 y5 [3
7 w: T; t5 l! _/ G5 k+ U4 p4
3 l( E+ H. ?7 `' T5
! M8 Q" Y2 x4 _% v. F- a t6
" E/ A. C/ p6 Y# |" O0 S0 J9 ^7' o4 Z6 [1 Z6 ~7 w
8. k* x: E# A+ C. g
有时候,用户希望从序列的最小时间戳开始依次增加freq进行分组,此时可以指定origin参数为start:9 Q/ w B& [, ~5 f( D
3 H: k7 m: G) }. N, V! u7 L
s.resample('7min', origin='start').mean().head(), G7 C* T' N) j7 c0 g( ~% t: }
Out[124]:
/ N$ D6 U/ i8 z2020-01-01 08:26:35 -2.333333
2 Q& Y0 x; t; S( U+ Y. v3 V2020-01-01 08:33:35 -2.400000
# D# M& a+ P* t2 u) n+ W y2020-01-01 08:40:35 -1.333333
& h& n5 v4 E7 q$ o+ Q5 ? g$ L2020-01-01 08:47:35 1.200000& s3 J2 p( ^7 k& z. N: k$ Y/ ~" ?1 @
2020-01-01 08:54:35 3.166667
, O9 k: v4 U& |6 v1 C0 b3 {Freq: 7T, dtype: float64. G$ a- m% ^! H$ p% B
1
B9 | ^" a4 U3 Y4 G) A2: {& C0 j; y! K
3
- \8 Z1 i9 \" P% E) E4) c' h0 p3 Q7 Q! X/ T
5
+ Z8 f* ^$ ]; ~0 \* s61 q& B9 C+ y% D# g
72 P' S! M2 v, Z5 p
8
7 u* D+ Y6 q: }4 ] J 在返回值中,要注意索引一般是取组的第一个时间戳,但M, A, Q, BM, BA, BQ, W这七个是取对应区间的最后一个时间戳。如果想要得到正常索引,用’MS’就行。
$ {& y/ m, z( N6 u8 }5 Y# N0 c$ {' R9 H2 V
s = pd.Series(np.random.randint(2,size=366),
7 o' }1 \5 y/ e/ F: R! l index=pd.date_range('2020-01-01',$ Q6 z. L0 h( |6 K; l
'2020-12-31'))+ D9 w/ S5 ^9 ^* O$ }% l2 W, n
9 o0 p; r7 B* |0 H& P* r) [
+ F3 J' G, `6 M* n' Js.resample('M').mean().head()
% P- T: e; K3 y& n# W/ Z7 ~1 dOut[126]: " m4 G" p( y6 I- o/ }
2020-01-31 0.451613% L: H7 M7 ~+ ?. H& c2 `* e( G9 v
2020-02-29 0.448276
! p, X V7 ^, I4 O0 I2020-03-31 0.516129
0 O9 R/ M2 P: Q8 L- Q, k2020-04-30 0.566667
' p* V5 }) ?7 C7 L8 d2020-05-31 0.451613
! e7 {% h6 R6 s- P+ Z& e# {/ ~Freq: M, dtype: float64
9 y7 \* B! l: r2 l5 p( f% q8 T) c# g5 z
' k* Q R; ^) o# i9 c7 o9 b' |s.resample('MS').mean().head() # 结果一样,但索引是跟正常一样
, q% G. ?2 k: O! O1 u) G* a; P! V+ SOut[127]: ; V" D: w% Y: _5 k' W: n* s
2020-01-01 0.451613
9 N: k3 v6 D! |% E2020-02-01 0.448276" }5 |6 C% `6 T6 t% |- b: R
2020-03-01 0.5161292 l# \+ R$ o! m! [1 R. _
2020-04-01 0.566667
: W2 y4 {0 y% ?3 k/ r2 k2020-05-01 0.451613
0 q2 h z; K5 i/ rFreq: MS, dtype: float64
$ O9 H7 p/ {6 W6 k
: f |+ `/ ?( s% Z% o, X4 ^9 E1
/ r- |4 X, h. Z C. b$ h2
; N. [% {0 O' Q; O2 n; E& P4 }3 j# R3
) ~: a) C! ]' L7 J& N' i( S4, S1 A, l: Q0 d
5, Z0 k4 H: h0 W! z
6! M0 Y, J, u! A' U
7
w% ~7 J/ t4 P! v+ ?8% _( U) x* T: P3 t: S
9
8 k* f A/ q# ^# `& `109 N4 P$ p0 G" U0 J- n
11
0 u3 ?/ { A* y12
/ q7 u7 E x" C( I( M9 ?13
- D% ~& z6 p4 W146 S `3 R5 r6 i* ^( J
15
3 s; I7 n% F+ L& A16" ]! p" \. r6 O
17
/ ^+ V' r, K2 k3 x7 |3 ^18
! L& t; M/ G5 Q7 B19
% H2 i1 g+ `- s. J) i+ a, ]5 S9 ^% ]20& K9 V( j; n0 K% Y) e @8 y; b
21. \# `/ ~+ c r) L/ B; u; Q
220 ?: S- P; [. v
对于 DataFrame 对象,关键字 on 可用于指定列而不是索引以进行重采样:
8 O: e; W, O* @6 Fd = {'price': [10, 11, 9, 13, 14, 18, 17, 19],5 r5 ]: O( l, ^& F9 }5 _) W
'volume': [50, 60, 40, 100, 50, 100, 40, 50]}
4 R# x5 j1 N% p$ V0 }! p, cdf = pd.DataFrame(d)
2 v& ?: ~2 h+ Y0 o7 L; W8 K" A5 ~2 odf['week_starting'] = pd.date_range('01/01/2018',
1 P2 ?/ a4 P% h3 K7 Y3 X periods=8,3 s. `6 @8 g. L
freq='W')
- ?& K# H0 j! |; v' K5 p/ Ndf
& R; N" c$ E m$ ?! D price volume week_starting
2 T+ Y: J1 | M- f/ W7 }: Q0 10 50 2018-01-07* X. e% |) |1 P1 V& A! x
1 11 60 2018-01-14' }0 C( P- ^2 r' {9 x+ u" O" ^' \
2 9 40 2018-01-21. c6 I/ `& F3 p+ R
3 13 100 2018-01-28
, G, K# E& K+ P: U7 @) q4 14 50 2018-02-04
& x2 k+ K$ D) @1 ]5 18 100 2018-02-11; Q6 D2 o- o3 F3 Z! x, X
6 17 40 2018-02-18 Z: D4 Y6 b/ l2 v
7 19 50 2018-02-250 r2 ]2 P0 H6 U8 ?
df.resample('M', on='week_starting').mean()
' Z% Q' C4 g* f/ } price volume5 V; R' |/ t1 U2 d. O; X8 ^1 t, v
week_starting
! i9 ?% k5 F/ v: b2018-01-31 10.75 62.56 n/ P/ g4 A, F9 X
2018-02-28 17.00 60.09 g: u1 @8 K* _, r$ a
4 S: {+ K5 u/ r: {3 W7 n1
|" |4 ^$ \: F2
: G! ?2 x1 W( c4 q1 P: E3
+ p2 o' |8 f9 q' K43 e* H0 F3 l2 \4 \- T! C
5
* D: e% _ S2 Y3 [% K6
O- S6 K' R/ F' ~" J74 p/ P. a1 i" T$ C. R
85 V- c1 }4 G6 A1 p4 r
9
) I7 X" t0 t/ V7 p8 l$ f10
4 c0 g! g2 x0 v, I( J; B11, L# i u% X" ?9 K: ~
12
- |6 }( a* X& x0 v8 H9 T7 }2 M2 I5 l" O1 j. O130 H2 X/ K# ^' w, w. z* ^# e
146 A3 ]; {7 l$ m# \/ @3 I% I
15
" E- L" ^$ ^: S16) N2 b. B# S1 R2 L
171 Y( C) g2 C3 w# J
18' P" F/ I1 L% t; i0 W
19
7 a8 v: \5 ^3 r# k$ |3 P3 C, a7 q20" ^) E; X1 X$ b" X/ S
21
) l% _9 \0 s# @# m. d1 p; d$ u对于具有 MultiIndex 的 DataFrame,关键字 level 可用于指定需要在哪个级别进行重采样。
. M# p3 }7 V# \days = pd.date_range('1/1/2000', periods=4, freq='D')
* c# I& `' \ Xd2 = {'price': [10, 11, 9, 13, 14, 18, 17, 19],
4 R) G7 ^2 |! P7 {) ~ 'volume': [50, 60, 40, 100, 50, 100, 40, 50]}4 \* c( d( \: o0 B
df2 = pd.DataFrame(
$ r4 R% a$ @: F8 ^8 N( J d2,
3 [- B& v4 t6 O' @4 [ n s% C% v index=pd.MultiIndex.from_product(
# G$ c6 w5 T8 n& Z- g [days, ['morning', 'afternoon']]
" _3 ?/ y9 K2 D6 A. C8 k) r )
1 g1 N/ G; [4 @)3 h4 ~3 i' p" _3 O$ n
df2* \+ x- Z' o; O& J' f* X6 \) S; X8 U9 \
price volume2 G" i3 n1 h8 M6 z5 n) E
2000-01-01 morning 10 50
# t4 d1 R5 _; m+ [$ B. O" _. ^$ W afternoon 11 60
1 }' O* r) ]1 D# j8 O9 m2000-01-02 morning 9 40
& w0 d" O. k5 p! P, ]8 |/ t6 N afternoon 13 100! t# }1 _ N# E
2000-01-03 morning 14 50' r2 R. o* O4 I4 P; D" Q
afternoon 18 1001 u9 b) Q# r- t9 D) P; H& `
2000-01-04 morning 17 40
( v; Y1 W- Y& A afternoon 19 50 b2 v1 o6 M; o* r9 n0 \# h2 m3 E
df2.resample('D', level=0).sum(): ^# Y! k0 L& @- x! _
price volume r. i) z1 |! ~$ _+ }8 i% W
2000-01-01 21 110
6 `1 U8 T( c0 s/ l; a+ g0 V2000-01-02 22 140- t. w5 O; K3 B# R3 u7 d
2000-01-03 32 150
3 l1 J8 t3 v1 }2000-01-04 36 90: M( I6 B% }; Y2 f; {2 y- t- F
1 H, L$ M$ @, ]% p1 S1 D9 O1 @
1
, {( e3 F. z& N0 O2 ~9 B, C7 f2
8 I' F5 C D2 Q# n$ X3
# i! c0 l/ @. J) h5 a- o/ ~- H n4
; O8 l9 O# |. b+ q53 U& i9 ~. H5 w" Q6 W2 E6 B7 a
6
$ X8 W9 u8 ~8 E0 E# l w- p7
' g1 l; r9 E, F/ E# y89 A% S4 Z' B% W% }& D
9
/ k7 K: y: N& x, q1 F* A' n3 p10, Z1 B0 B* U; X8 j k+ A: h ^
11* }4 Z( R( T1 _ ]3 @% v' H% H
12
$ g- J: d6 Z) Y6 p13
# [1 w" t2 V( E7 w14
- K1 W4 B4 a1 T' q9 I5 `15
7 f9 r4 S& E4 r% V+ C( L! b169 m, X7 H' |+ }- t
17
2 o+ q \2 b! j5 x6 l# ?: Y18' ~; s# P4 e a8 N6 `3 L" m
19
; m5 W/ r$ d H/ g3 ]! i" D' Z20
+ ^- w0 S0 F! c, \$ {4 \, F- ?21# N8 L; }; @: G- Z+ N' [+ w7 ?
22
* C# B7 C2 @4 p. S23
7 H, ]+ @( H& c246 i4 h8 D' I: t6 ?: @$ b
25
% f Q6 |! ?, \# R- T1 H- f根据固定时间戳调整 bin 的开始:
' \) t3 h4 U9 }* }& X* xstart, end = '2000-10-01 23:30:00', '2000-10-02 00:30:00'
0 I3 l! J' t' D) ~: `0 {0 lrng = pd.date_range(start, end, freq='7min')/ \* ^9 Z0 d9 \: X4 s. [' x6 u/ M
ts = pd.Series(np.arange(len(rng)) * 3, index=rng)
a/ z8 D3 J: ?# f( D5 B8 z; W; k" _ts- I6 h! h+ |8 Y! r
2000-10-01 23:30:00 0
0 ~$ D& v/ L7 q0 d. I: H2000-10-01 23:37:00 3
7 ?1 l0 M1 J9 d5 ]$ m# ]. U2 R$ V% B2000-10-01 23:44:00 6
9 K/ v2 s$ V+ d+ ~" B2000-10-01 23:51:00 9
! ~8 a# v1 }2 T0 o0 Y0 b+ k* Y2000-10-01 23:58:00 120 N; p- o( g4 t5 Z
2000-10-02 00:05:00 15
3 N$ j# g2 }; F$ U l5 v% H. |( A2000-10-02 00:12:00 18
, N" `; e6 I2 h2 r/ i2000-10-02 00:19:00 21" L8 r, l; b9 J1 C- l* z9 X7 o
2000-10-02 00:26:00 24& ^+ \7 h4 r7 V N4 { j& S. D
Freq: 7T, dtype: int641 k7 h4 a0 N! w+ u3 k$ d4 @
/ o& ~% j4 V# ?
ts.resample('17min').sum()
8 M# L1 o2 @! y. m, |2000-10-01 23:14:00 0! q5 t k. q6 t. P j2 D$ A: }
2000-10-01 23:31:00 9
" f* y" `8 M6 N4 {2000-10-01 23:48:00 21
0 E' V8 d! i! U2000-10-02 00:05:00 54
- d1 A' x# R" y; `2000-10-02 00:22:00 24 p! a2 t6 ] h1 D
Freq: 17T, dtype: int642 D3 C* c' l; U+ b4 c. D9 M. R
' Q. W7 L0 @$ ?& g& v
ts.resample('17min', origin='epoch').sum()
* W$ ~8 n A% h2 G) ~( v, M! l2000-10-01 23:18:00 0
! P% U- Q! A' ?- w2000-10-01 23:35:00 189 N- b8 T4 T# a' H- k2 A9 s
2000-10-01 23:52:00 27
$ ^* a* _7 x' v( X- y" P- g2000-10-02 00:09:00 399 h5 K% @7 K. e5 S- |
2000-10-02 00:26:00 24/ {& D, s0 h9 o J
Freq: 17T, dtype: int640 V2 Z3 I3 l9 C
- }4 e* l* N$ f& M7 I7 I+ p9 ~ts.resample('17min', origin='2000-01-01').sum()
: j7 v* q! \2 r. ?9 G2000-10-01 23:24:00 30 v" B$ y2 {# m/ q7 q9 K" U* g
2000-10-01 23:41:00 15
% T, ~# e- Y) F8 v" j& g2000-10-01 23:58:00 45
. R4 G6 Q; }' S6 Y( I* e: ~8 M2000-10-02 00:15:00 453 t6 c! R3 i, W5 N, B6 a; P
Freq: 17T, dtype: int64' w5 r6 S% `: T: V! F, v; d
: K7 H$ c! `% m6 K' {4 @9 b18 L+ D ]$ _1 g# i7 V- P
2
o0 l% C0 u2 _" ^31 U5 R1 \* W8 K/ @. p3 S
4* I" d! S# g' L
5
8 Z7 Y9 z. s0 t" |8 B% {3 J6" ]5 c8 p- x5 B2 J
7
9 T% b) }" P' U$ O- W, O- j86 ^6 p4 ?0 j" p* t7 }4 s+ M
9
: |7 I6 @' |# J1 Q Q) [; [, y10
" C/ G5 I0 r- S7 I- h4 ?11. o+ s- g ?+ u! }
12 M- X1 ^& u- w6 D
131 n# g0 H# j' H* m' B
14/ O( p* g5 H& V- m7 l& N* r2 N9 k
15
# _: d0 ?5 T% f; V5 d1 Q4 n$ o16
A% L1 B4 @$ ]( D4 y3 _! o17
4 f. c: Q0 f$ p4 f3 w. o18: F/ ?4 U3 p6 s; ^. L# I% Q
19
" S. v! Y* g, X1 Z5 Y5 O$ l, l20
1 W0 l! W: s! u9 x9 M" _6 o4 O5 K21
) @6 S2 R* i3 i' F/ k22
& H1 ~. l- D1 I" h [0 b1 i23
9 z7 S8 S) u" y3 Y) R! B245 g7 P1 G( d; k8 l* k( D) f8 g
25' [+ v; j$ {/ p5 ]8 ?7 w
26
# t6 Q4 W1 X, v8 S27 c( s, S+ C0 n" n4 k# W
28* ]# _) |# D) s2 W
295 |- q8 J8 [) D2 v, r
30
5 H& c7 R/ w5 W7 M5 ^2 C315 i1 s+ L+ Q) m+ m( h
32
% X' F7 R3 Y/ r33
% u6 ?! Q" U* w$ L' g+ v" ^) r34
0 B( C6 }$ w5 N354 U9 n! u/ l. m/ O
36# U6 A7 X) R, Q1 L) _) s3 L
37! @' i2 L, P# t+ U
如果要使用偏移 Timedelta 调整 bin 的开始,则以下两行是等效的:4 | N+ |5 |0 g& \
ts.resample('17min', origin='start').sum()" q2 L6 e: [* S& x" w# C( f6 X0 R( ^
ts.resample('17min', offset='23h30min').sum()9 N; F h5 U! V/ \
2000-10-01 23:30:00 96 s3 H( A. N4 ~5 w4 T
2000-10-01 23:47:00 212 A/ J- b. F" Z* Y0 y
2000-10-02 00:04:00 54
/ i* \5 R5 r9 v; L$ y2000-10-02 00:21:00 24
, v0 B+ p% N& k. A! ^6 h) z9 j r- aFreq: 17T, dtype: int64
/ A% Y' C9 g0 M7 } f8 E2 ]8 |# j \- A1
* U/ Q5 I) |7 H i H9 M: [ T2( y3 ^3 i6 a. f# P
3: G6 m% }2 o& ?; |
4
5 N. E+ Z5 W# L# i5, t( M4 b& T$ d7 w! u; e
6 S' w3 i/ c6 ~6 M. z" c1 V7 ?
7( ~* g5 t* m: _
10.6 练习
# Z0 ^7 R) P8 g K4 t! V" \: PEx1:太阳辐射数据集
# J* P. r4 n _现有一份关于太阳辐射的数据集:9 ?& a0 t) `0 D1 X3 T; O" H3 `
9 C5 u5 s# w/ ^
df = pd.read_csv('../data/solar.csv', usecols=['Data','Time','Radiation','Temperature'])
' {6 |+ v1 M" h; l, _8 h \df.head(3)
# P4 n) U3 b, e/ g( H9 _' Q
- d3 c- w- b6 q8 b% {Out[129]: 2 B- k. S2 R( [( B: F
Data Time Radiation Temperature- O1 Q- X) Z. k9 x; ]* v% R
0 9/29/2016 12:00:00 AM 23:55:26 1.21 48# r) t9 W7 [# o- o. y
1 9/29/2016 12:00:00 AM 23:50:23 1.21 48
) W6 {3 U" H8 w8 ~) j9 P2 9/29/2016 12:00:00 AM 23:45:26 1.23 48
4 h3 I* \- g' [# `1, m# v" ~! [% W; t$ k. B
2' e$ x2 ]" l: i0 E5 L4 U; X' E7 T: R
3+ E5 v1 U# T0 s
4# U t2 `6 S& |6 r- k7 h; G
5
^; ~, `6 J' Y: v( E* w6
: [$ E! i# _$ C9 k; E+ M0 r1 q70 }9 z8 Z3 e2 T/ _; h7 W1 p( n& b
8
( t* @9 v; b0 |& g4 p将Datetime, Time合并为一个时间列Datetime,同时把它作为索引后排序。; G* L6 |0 W% g# O0 n6 @) x
每条记录时间的间隔显然并不一致,请解决如下问题:- _9 R, T, V, `$ A0 K9 z' u
找出间隔时间的前三个最大值所对应的三组时间戳。
8 f0 J* N# R5 C! b- _* j5 q! q是否存在一个大致的范围,使得绝大多数的间隔时间都落在这个区间中?如果存在,请对此范围内的样本间隔秒数画出柱状图,设置bins=50。* W: ^' `, l) T+ `( O6 M3 z
求如下指标对应的Series:- M; \2 `0 D! F$ w6 C L
温度与辐射量的6小时滑动相关系数
8 o/ d. T' \' s# o! Q以三点、九点、十五点、二十一点为分割,该观测所在时间区间的温度均值序列
( |+ a3 J9 W6 R4 z每个观测6小时前的辐射量(一般而言不会恰好取到,此时取最近时间戳对应的辐射量)
7 X1 n$ I! T5 e0 b5 jimport numpy as np: A% `$ W4 E1 Q' l# M% k
import pandas as pd# C3 E" O4 [) \6 J: n
1/ ?1 f* Y9 V. \. @
2& o2 e1 `# \- v! o1 G- h8 f
将Datetime, Time合并为一个时间列Datetime,同时把它作为索引后排序。* T. s7 N( n4 T) r- r' U) }
data=pd.to_datetime(df.Data) # 本身是object对象,要先转为时间序列
' Q0 z7 h( j- R6 Btimes=pd.to_timedelta(df.Time)
1 u! P' H% D/ E) m: N3 H1 W4 Tdf.Data=data+times0 L, l( x0 C) C5 p
del df['Time']
. x: H* S9 I; [/ xdf=df.set_index('Data').sort_index() # 如果写的是set_index(df.Data),那么Data作为索引之外,这个列还另外保留
% K- a; C3 c8 L/ xdf* C, f5 L9 {# W0 W& C' u% L1 I8 B
Radiation Temperature. R; e: d( q, o
Data
! |3 {: m7 \" v( s, \8 }2016-09-01 00:00:08 2.58 51& F. E' Y# P+ f4 ?0 Z5 ^8 ~
2016-09-01 00:05:10 2.83 51
2 Y& b2 j- S! X) p3 f) R" Z2016-09-01 00:20:06 2.16 51% {/ {0 p% G6 D9 B. _+ y7 G8 A9 a0 |8 y
2016-09-01 00:25:05 2.21 51
' f9 F! T& Z; H# N$ I2016-09-01 00:30:09 2.25 51. ?" o% a% M N' }/ M. t4 q# c
... ... ...
' o9 z M) B! N v# g2 U+ N' B( E2016-12-31 23:35:02 1.22 417 N U$ p2 l1 R. o8 v; Z+ _
2016-12-31 23:40:01 1.21 41
- @& z0 I; T$ U2 Y7 i2016-12-31 23:45:04 1.21 426 b; F: R3 }& s+ r' Z
2016-12-31 23:50:03 1.19 413 q& i6 v5 F7 @% V2 P
2016-12-31 23:55:01 1.21 41
: \+ p& V- c" m: x- F% O5 a3 t, e, {1 s) A3 j. F
1
H4 Z' c$ B5 a! Z* H. X5 |3 O* i2
3 m' t4 q; k. F) t8 B! T& U3
6 F3 U1 C/ ?( u C: I, q4+ d- h7 X6 H, h% Z
5' U- l) v" C) _4 s. N+ S
66 g" ~" j, l6 ~% C0 N) {
7& D$ Q3 r9 [, J# Y
8
8 U+ {4 w$ u$ p/ V8 \90 F$ A# L( t2 v" @
10
% i$ A+ w e: I) W' N$ D11; @* `7 z; @6 p/ t, ]9 i1 f" z
12, ^* z! D9 N* q7 h! N
13
" v) c {. m: K% d3 d146 H& E' u- Y n2 f
15) M; c9 l& G$ p$ _. v' J
16
6 L* b+ Z! x3 b3 ~6 O B/ g. f% @& {17
# t' k& c7 z$ v0 F6 S- a# }' ^18
! N+ k" C8 ^0 _/ {, ^19
# B `( c: j0 n7 @9 x% h7 P每条记录时间的间隔显然并不一致,请解决如下问题:
\0 ?" H- I, h' f7 _$ u找出间隔时间的前三个最大值所对应的三组时间戳。. t$ R) b, T$ ]; K z
# 第一次做错了,不是找三组时间戳
- j) o$ k2 _; a) M- Ridxmax3=pd.Series(df.index).diff(1).sort_values(ascending=False).index[:3]
" w2 |- ^% u% I9 D6 C @df.reset_index().Data[idxmax3,idxmax3-1]
; L" V9 ~, z+ V, `% k1 w& L; O6 v4 \
25923 2016-12-08 11:10:42& i: V! Y& H. i7 q8 A) J/ `
24522 2016-12-01 00:00:02, f$ h y$ g) ^
7417 2016-10-01 00:00:19
0 Y" f; `9 E, J! j; ~Name: Data, dtype: datetime64[ns]/ L: m' a7 `9 p" p4 G0 F, R/ r
1 h h! q) B8 |( y2 p! F' R7 W" R ~3 M
20 d2 x4 h7 X/ R
3
+ B/ E% X( I" ~% w' ?( ~. A4
3 p: u* R" ?! D" S& X# J55 P/ \; `* U* L0 c/ W4 g. S6 u( a
65 X7 r. Y8 [7 l7 O* |
7
0 S" e% s- W1 s: B* ?8
% d0 W. }, L7 ]idxmax3=pd.Series(df.index).diff(1).sort_values(ascending=False).index[:3]5 r; l, G x/ _; U( t& l
list(zip(df.reset_index().Data[idxmax3],df.reset_index().Data[idxmax3-1]))0 X; A. S, S/ Y' z9 F6 a6 S2 S
. I3 ?* P0 O" x( ~& A, h[(Timestamp('2016-12-08 11:10:42'), Timestamp('2016-12-05 20:45:53')),6 S6 Z; U; Q, Q9 Z$ R
(Timestamp('2016-12-01 00:00:02'), Timestamp('2016-11-29 19:05:02')),( @, u. n+ |7 S7 m: D4 }0 C+ |+ U
(Timestamp('2016-10-01 00:00:19'), Timestamp('2016-09-29 23:55:26'))]
0 U( P" N1 u8 t; _' b1
( |9 _! m) W+ `- f23 x. O( H! U+ S
3% o( A5 q) H4 q6 N% w
4
- G% y3 G. ?/ n4 m5, Q' Y$ P& W/ {0 j
6* m0 N( r$ d4 Y- j; J- |
参考答案:: ~& E% P! \" P9 n/ t
' q0 n. z4 L, Z; ^& i# Y' B f# v8 b( x
s = df.index.to_series().reset_index(drop=True).diff().dt.total_seconds()" K! [2 }9 Z1 T7 j
max_3 = s.nlargest(3).index
, G' m p- M& S$ ^df.index[max_3.union(max_3-1)]
) Q* ]* h" ? [3 R4 F7 ]" x$ p( h+ n, z/ R5 ?( K8 H
Out[215]: ! Y5 a9 \3 v8 i- R+ Q9 Q
DatetimeIndex(['2016-09-29 23:55:26', '2016-10-01 00:00:19',: C8 I5 M" Y7 q2 w5 l7 Q
'2016-11-29 19:05:02', '2016-12-01 00:00:02',* O: c6 x! Y* k' g& ?
'2016-12-05 20:45:53', '2016-12-08 11:10:42'],
9 f& o" y7 v$ _& Z, ^ dtype='datetime64[ns]', name='Datetime', freq=None)
, ^; e. B2 Z! G8 J6 u" L4 X1
: W' X( h4 S) E; V* L- r2
7 D2 m+ G$ v& V I4 D3
: N( T. N" D- m* g( q4; y% ^ X6 R+ v6 u O4 T4 }5 d" {
5$ q% R; ^5 `" Q A, B5 E
60 Z8 b$ e$ v- V
7
% m; n9 p9 Q) E0 P4 |/ |, V8) _5 H/ K) v8 h f3 f( f# Y
9: }6 {% J0 n5 f: J/ O- w
是否存在一个大致的范围,使得绝大多数的间隔时间都落在这个区间中?如果存在,请对此范围内的样本间隔秒数画出柱状图,设置bins=50。
/ _* N; X/ j0 s( h# {' ~# 将df的indexydiff做差,转为秒数后排序。再求几个分位数确定取值区间% y2 E3 G/ D# \. Z* R% x
s=pd.Series(df.index).diff(1).dt.total_seconds().sort_values(ascending=False), I, e0 b+ u2 y- t: V J
s.quantile(0.9),s.quantile(0.95),s.quantile(0.99),s.quantile(0.01),s.quantile(0.03),s.quantile(0.05)
3 `) V6 e$ O( n( _' O- V
$ d7 f- l" i0 z" @+ l(304.0, 309.0, 337.15999999999985, 285.0, 290.0, 292.0)" f$ M6 S1 E9 I
1- B. ?2 E$ v# ^1 E* q% k& m1 B5 p
2
/ m* y6 D! n0 Z3 L/ t3
) Y7 w3 P. |4 j. T4
7 X, R3 P. g4 F8 B: Z3 w5. v7 y9 E- r: o. l4 y) \
%pylab inline
* g. B1 }# `" o7 U: r! E_ = plt.hist(ss[(s.values<337)&(s.values>285)],bins=50)" w* q0 L! |! q, x+ f: E
plt.xlabel(' Timedelta')" e: L: P5 k- H
plt.title(" Timedelta of solar")
. K( R Y( L+ V( _1: y$ s" |7 ?9 h# ~ ]9 L
2
1 R' k9 E9 |8 Z3
9 ]) T4 C( J. ?2 J* |9 H b4 s0 S" a' ^4 X( m4 q5 m T
p3 U6 R3 j; A
2 _) J9 B0 _6 c+ L) W. Q5 ?# B求如下指标对应的Series:
7 n# t+ T" R0 @9 f$ D温度与辐射量的6小时滑动相关系数' U9 d n" T0 J
以三点、九点、十五点、二十一点为分割,该观测所在时间区间的温度均值序列! ]- u2 \8 R/ \2 V8 a y% D
每个观测6小时前的辐射量(一般而言不会恰好取到,此时取最近时间戳对应的辐射量)
/ W' }- X$ @, `' ~, Ldf.Radiation.rolling('6H').corr(df.Temperature).tail()
$ D' o" t" }$ e7 {# @ x0 q! T% C4 V B8 Y4 D1 ^0 [- G
Data
7 j o2 M# f/ s+ y; J* _2016-12-31 23:35:02 0.416187( m# x0 _/ D6 ^4 C5 i
2016-12-31 23:40:01 0.416565 B6 o, }. E3 m5 [% o0 `# A5 L
2016-12-31 23:45:04 0.328574- r# B6 A+ m, G' N
2016-12-31 23:50:03 0.261883* J7 C/ b u' O2 i/ Y/ Q( o
2016-12-31 23:55:01 0.262406- u: c" ~1 \8 U$ \: m
dtype: float645 p2 Q- P5 b- M3 `
1
- c2 ?1 _& ^3 w5 `: I9 L- K2: j$ u/ w9 E- E% y- Y# k
3
" ?! A) l* R9 G1 G4
\1 m8 f" q% n; x5 [& \) Q9 u5
7 A+ J, N! j1 z3 M1 v6
' L m/ D2 l7 U/ |79 f1 q2 b$ W/ C8 m' ~. z, |
86 i& I0 I/ z6 \9 h+ }/ P
9+ N5 m; g# g# O
df['Temperature'].resample('6H',offset='3H').mean().head()
- j* A5 C0 p, Z9 l+ E8 N
# U4 ~' P% d) H- l! y2 x3 b& n" bData; }& x9 n a; h' g
2016-08-31 21:00:00 51.218750" U$ a* J8 H! Z8 |0 o) o" s
2016-09-01 03:00:00 50.033333
' G, K; H/ Y( z1 J1 E ?( O2016-09-01 09:00:00 59.379310
3 W6 r* Q( S( ?2 X4 R2016-09-01 15:00:00 57.984375; }0 l2 I! `, m( b
2016-09-01 21:00:00 51.393939
/ e- Y$ _# x7 l9 ]Freq: 6H, Name: Temperature, dtype: float64( V2 n5 a, S: c5 ~; b
1
C u1 ?0 u# s1 R( I- R22 \* E& m* c4 z7 T0 j; s1 a
3& j4 C: [$ d/ D2 L) @, H) j
4
$ l# w: s+ H* K* M( K1 l8 e5
# ]* ^. j0 F6 G3 x6
, q6 n: A. V8 W3 c! l. I7
+ J$ g8 q: U. L2 h* t5 X$ O/ t4 t8
^- V0 b" c+ M0 B3 S' @' s9- l: k S: w8 B
最后一题参考答案:
, X$ N3 i2 J3 X. t" k8 K6 l: ^
3 ]) `3 n* V* R& |6 C# 非常慢
9 T4 w: g( T0 c/ ]) g, `. @6 smy_dt = df.index.shift(freq='-6H')
- }' [) L5 P) fint_loc = [df.index.get_indexer([i], method='nearest') for i in my_dt]
/ j5 L/ Y, f6 O9 A2 @) Sint_loc = np.array(int_loc).reshape(-1)" d2 l% ^6 x6 p5 o* _3 @& `7 a9 S
res = df.Radiation.iloc[int_loc]4 o+ G0 |- P+ P
res.index = df.index
2 m5 k. l+ D4 i# Lres.tail(3)4 t$ q% O" D K+ N
1
S$ B+ W! z2 B: n# ?2
, r: a _: I) X. A; M: f3* h- Q7 ~. h1 ~; ^2 n. ?
40 O$ {0 `/ `% M) B1 t& r, R/ d
5
2 v- a0 z) C& p, U6
' a6 G% j9 k5 u( @7
/ h4 b! h5 ~9 |' ^# 纸质版上介绍了merge_asof,性能差距可以达到3-4个数量级& ~6 S0 r3 c5 O8 b4 q) j' u
target = pd.DataFrame( ~8 O$ ^" t" ` Y Y- J
{
/ c# g4 c E- ] G# K "Time": df.index.shift(freq='-6H'),
: Y: a% Y3 s4 ^" { "Datetime": df.index,
' E5 E; e7 q( Q }, F1 d& k( i0 a" ], }+ l: ^ p1 t4 S
): F2 ~* c/ B4 ?# b3 U+ C9 M( ]. n$ [
' K9 ~$ @; I6 ]2 ures = pd.merge_asof(, D4 G) P; L, d2 x$ h8 c
target,
2 H2 E2 \7 q" r! E% o. D( l df.reset_index().rename(columns={"Datetime": "Time"}),
' Y4 J) E1 M- T! o0 ]. @/ [! f left_on="Time",
$ r0 H; d/ O5 o$ k S* W) z right_on="Time",
( b& E' O Q' c: C direction="nearest"" ^3 G7 d: B# a
).set_index("Datetime").Radiation) @( T6 d& }' |
! S% w5 L6 }% R6 D
res.tail(3)" H/ Y# @ t! T. y) J8 M
Out[224]:
- v0 P7 w: K. B. |+ iDatetime
* L) }: [5 g; f( f; A) r2016-12-31 23:45:04 9.33
: @5 M( a% Y) E7 k5 h2016-12-31 23:50:03 8.49. G* z) h- w& r) K: L
2016-12-31 23:55:01 5.84
2 i i1 V- P9 d3 g N' M$ V2 hName: Radiation, dtype: float64# g- m" w6 k, F% |3 ?6 N
+ K9 E% R) v! \! x5 c* v
1
$ b3 E% r" G5 n5 p6 \2
2 g( G+ q6 Q7 t6 T# {3
6 j8 \ ~( k4 `" G! L47 q; z; a5 c5 s
53 C, S7 d5 _- S* j; e% d4 D
6
7 L2 O) ^4 Y8 b: y( e7
. o+ i# O6 h9 x8
2 f; K' K, D( s3 m8 d' z9! h6 ?7 f& |/ V2 T- N: k
10
/ l% G3 z B# O, m11
9 v9 K, p+ x. [12$ X. f. f1 V- r) N, Q
13
8 ]9 K( m# Q4 s* B( T8 r: y14! r& m3 _5 c5 Y" F
157 J" B5 J: I- M+ ^, @( {; q: G
16
8 b5 k% M# \3 W i' c' K* r" d17
' v) z5 ]: ?! F0 i L8 N18" L' I2 |. v! x2 P$ C: }# f
199 x- t7 Y2 j* W( @1 |2 z& K G
20
+ B; _2 f$ y4 P7 H2 [1 j216 U7 q9 ~5 F; P/ [# x$ H+ n O
22
5 @; l8 Q/ F/ C2 e% s8 y* E: f23) @) H0 j. V) w0 L0 m* Z, k
Ex2:水果销量数据集
4 x" Y: u' I6 y' J- v m现有一份2019年每日水果销量记录表:0 F2 L- }8 ~! U$ O9 ^
/ Y4 G& K5 k% ~7 R0 M! A1 s g9 _df = pd.read_csv('../data/fruit.csv')- c2 D% a, y0 B% g
df.head(3). f# K4 ~1 ]8 c8 p4 ^
! l$ q4 ~/ Q7 N; C+ G
Out[131]: 8 b" V W* \4 e
Date Fruit Sale4 H5 c, V5 m, m" j x9 o7 ~
0 2019-04-18 Peach 15( C4 n2 I6 Z1 _" N/ h
1 2019-12-29 Peach 15
/ {; t1 e; _* H! ^- u, q2 2019-06-05 Peach 19; d* N$ X* a7 |8 B
17 ?0 A$ a3 }7 ~7 S( L
2
: ?3 p" |; H: v7 ]: H3+ ~* [- x* q+ k1 S1 s+ W+ `
4
$ P, L# m* Z b: R% S5* m; d3 r+ U; `; ?
6
5 e+ e0 X" B# y2 \7
6 B+ \$ |5 u6 [5 q# h8
8 q" R& }1 n( N, a. O统计如下指标:
9 z( O8 k& L+ o: ^/ V ^每月上半月(15号及之前)与下半月葡萄销量的比值
. b7 u6 c5 z( _* j6 d( Q, E# \每月最后一天的生梨销量总和1 o3 M! w" q& |( z. I% q
每月最后一天工作日的生梨销量总和; Z: D& u. K, Y- W
每月最后五天的苹果销量均值
) W& D" x3 A8 R: Y按月计算周一至周日各品种水果的平均记录条数,行索引外层为水果名称,内层为月份,列索引为星期。4 B! l; ]5 {% s5 g9 g" n
按天计算向前10个工作日窗口的苹果销量均值序列,非工作日的值用上一个工作日的结果填充。
" ~6 \. v U7 n1 _" Limport numpy as np
# ]+ u1 [( Z# t, w1 P2 ^: Zimport pandas as pd. |1 S5 M s* g" _6 n/ `+ I
1$ W. A0 Q$ ]6 Y4 Q
2
# N: t7 y- S5 N4 W$ t: G; [4 z统计如下指标:
9 ~- l2 k' ]6 p3 }: K每月上半月(15号及之前)与下半月葡萄销量的比值
" X- m6 e9 E7 W8 p, v每月最后一天的生梨销量总和
, G8 I4 Y! w7 V9 ]" }每月最后一天工作日的生梨销量总和6 J* T: o7 V2 z" p
每月最后五天的苹果销量均值
! @0 F6 F5 y0 x# 每月上半月(15号及之前)与下半月葡萄销量的比值0 A* L4 w; ?2 L1 @
df.Date=pd.to_datetime(df.Date)1 J* y: ~ M0 L5 V; U
sale=df.query('Fruit == "Grape"').groupby([df.Date.dt.month,df.Date.dt.day<=15])['Sale'].sum()
3 a- G& Q* q; N# a$ E" Hsale.columns=['Month','15Dayes','Sale'] # 为啥这么改没用啊" A! {" T4 `: b& ^ S) R7 \& \
sale=pd.DataFrame(sale); ]% ` v* r# B @# e; ]# _/ K+ A
sale=sale.unstack(1).rename_axis(index={'Date':'Month'},
/ V! T1 B: P- l7 \) H% A columns={'Date':'15Days'}).stack(1).reset_index() # unstack主要是两个索引都是Date无法直接重命名
- E. ^! t: w! q8 T' E2 E$ w: esale.head() # 每个月上下半月的销量
- }9 V# ^2 ?5 R
/ U1 ?8 E5 d. g5 F8 {( h8 ^! W Month 15Days Sale; h' ]. {( T( F6 W8 o3 |3 F9 k! a b
0 1 False 10503* u! a6 q6 V2 E, m
1 1 True 12341 `; C2 U0 e* D# M+ p
2 2 False 10001
9 T5 J6 P. d- A) r3 2 True 10106
; _+ j" {4 @, `; o( b4 3 False 12814 t' i8 w; `3 M C3 h0 R: t; H( z' x
1 B# h& B; ?. ~* i5 Y
# 使用自定义聚合函数,分组后每组就上半月和下半月两个值,根据索引位置判断求比值时的分子分母顺序
$ _4 f- j% D) j- z8 O( dsale.groupby(sale['Month'])['Sale'].agg(
5 g5 B; a3 h3 S( E* F/ Z* i lambda x: x.max()/x.min() if x.idxmax()>x.idxmin() else x.min()/x.max())
$ P7 s. R& w C# Q" a" n. b" i* E; G% e
Month: e+ |# L. C: B" O; }: Y
1 1.174998+ c$ m$ g& g& H0 H
2 1.010499, k, z- Z! J J. J" T3 h- A
3 0.776338/ F6 |6 }+ l+ ~8 C& t0 M
4 1.026345
. @/ S/ v' g0 W4 b1 B: t. Y( N Y5 0.900534
1 o& D- f, n$ Q7 p- n4 ]# @- F6 0.980136
/ A. Y/ Y6 O! x7 1.350960
- U$ r/ F! l) I8 1.091584
J$ `- |: o6 `" S2 p7 }, y9 1.1165082 D" U# i; Y8 ?$ x3 W" O, P {: L
10 1.020784
: M0 C, y/ c" ^; W/ I- k. h# h11 1.275911
! Q. I" J# D. `( s9 \: P12 0.989662
6 g" u) ^" q3 w; f1 n `5 {: y" YName: Sale, dtype: float644 ]4 {0 j3 I0 ~" t
0 s7 P; ~- U" C0 a
1/ }3 Z6 z7 R4 D) s: K
2# \3 g% n' w$ n+ ~, \5 B( M* R: C
3
% e3 K- {2 t% E$ ]5 P0 l4& D8 m9 W$ L& j+ G
5
) ^- M2 J3 D" i7 T" {5 V" d6
1 R! M2 I! q8 N) B73 W% z1 J n- P5 n4 u# H1 ^6 Z1 Y
8
7 q7 T6 ?# h9 Q& T% Q9; s# t0 P4 Q2 R
109 M6 s& Z$ n9 S; R% _2 e6 H
11
: h$ @6 f9 V6 L Y7 L12
4 m0 S4 s* D" B: Q3 T( j% r# e2 }13
# S- ]3 d2 K _ }# j8 i14+ T. f- e# `( M/ D
15) h" T" `4 H4 h4 n$ k8 p
164 C! }& K3 G8 h6 r$ J; O
171 |0 V7 u7 v: r& {1 H+ m; g
18
5 P4 I1 K2 V- ^5 Q19
) j. Q4 y. n `+ W3 K( F* h20
, C4 Y" S" P- }21
/ x* Y& l' q. I% E22
1 S+ J, _/ Q; ?$ A23* p/ h! R4 O9 x- h& x- J
24: E8 w* F; \1 \
252 _! w, x/ T, }. L1 J2 Z
26
% \+ ^- @9 Z6 n27( V1 J4 Q! T' g! }
284 v4 Q! X0 r( J& M4 h
29
# d' y4 v# L1 V& g: t+ s! G& }30% G6 m% @5 o ?/ ~( |* w! _, N
31
. f5 i" l# L9 s0 W' M32
" ]" f2 I; }- s( l \33: d. k* x$ M4 R! w! N o: i( Z
34
; U: K9 l4 Y/ s4 f1 P' Q# X# 每月最后一天的生梨销量总和
, t8 c' g; h, @! [3 x# g& E2 adf[df.Date.dt.is_month_end].loc[df.Fruit=='Pear'].groupby('Date')['Sale'].sum()
; H% i& s0 B9 H! }/ V6 c/ `! D0 r8 n
Date9 {( g# Y2 \7 `
2019-01-31 847
6 v7 k: p' V. x3 i' n, F- {2019-02-28 774& w k) r$ U1 P4 Z1 m
2019-03-31 7615 E( _4 c4 c: Y" m
2019-04-30 6487 S4 ~, C# m% @7 X4 i1 t
2019-05-31 616
. V. n K w; W0 P' H) o0 A4 M1
# V% }6 f+ m- c# K2
5 D8 c' A: J: c$ F2 c5 J1 ~/ v3. Q5 h- Y- _+ a k
40 V) s- \3 _/ [
5
$ l7 q9 k$ B6 T4 C6 h69 J1 Y; ~1 H/ G# ~9 h% [5 c
73 B5 _# e/ O# N( ^0 `
8; s5 J+ o0 D9 w; I
90 x. D& M$ m5 U0 Y# ]
# 每月最后一天工作日的生梨销量总和( j/ r( Y* k! T+ R9 U. E' ]
ls=df.Date+pd.offsets.BMonthEnd()- v/ U* B: ]9 ?$ C
my_filter=pd.to_datetime(ls.unique())
0 H2 D* P+ d5 d1 B, Jdf[df.Date.isin(my_filter)].loc[df.Fruit=='Pear'].groupby('Date')['Sale'].sum()
$ a. X$ U6 y/ G N/ o8 a, ]6 z9 g) y# ^" J: ~8 i
Date
0 q' _% u2 r9 B0 }3 h4 f+ k2019-01-31 847
/ R/ n6 z5 ~9 q# y2019-02-28 774. B; Q2 L4 J+ B2 J/ {( K" m
2019-03-29 510
( n: t& h/ n7 m9 h6 N0 U, L2019-04-30 648* q8 d, w3 H6 `: O+ L0 ?
2019-05-31 616
+ `% i$ K; ?; H1
. n* f- { u. f T4 G21 p1 e0 L% C, m# i7 l G9 d
3' X' V2 A& p2 |- a0 ^4 ]0 Q
4
2 r3 K4 K% J4 e1 Q5
( N% T1 M/ z5 k$ p6
! g g) ^2 D# ^, Z" _1 i/ t7 d+ ]7
* N0 {) `, o$ ]/ {! u2 o; n; a8
! |9 p1 O+ O1 k6 m" w# e% T9# ]- E( F1 B7 V' h& r7 l9 P6 ^1 n m n5 U e
10
5 |& F# N- D x+ q+ s9 A11! |$ p( @3 r3 s3 S& S
# 每月最后五天的苹果销量均值
* e0 j- k1 d0 y1 y: |, e( zstart, end = '2019-01-01', '2019-12-31': Q$ G+ J6 n4 X9 A+ u* ~9 t
end = pd.date_range(start, end, freq='M')
' E1 r. f7 J+ e# Xend=end.repeat(5) # 每月最后一天的日期列表,重复5次方便做差, D- \; s$ Q, Y5 N. D0 A. Y
4 W$ t: _3 l, G {2 dtd= pd.Series(pd.timedelta_range(start='0 days', periods=5),)- N2 e( D! T9 c' q& O
td=pd.concat([td]*12) # 日期偏置,最后一天减去0-4天
' ~( Q+ c1 O; pend5=(end-td).reset_index(drop=True) # 每个月最后5天的列表% G/ i" l' x# C* m
6 X' a) d; [9 t7 f
apple5=df[df.Date.isin(end5)].query("Fruit == 'Apple'") # 每月最后五天苹果销量
& I) F E3 n# D. v1 j, y3 I Bapple5.groupby(apple5.Date.dt.month)['Sale'].mean().head()" i5 ^! d; l' u m3 z0 }' \
1 g: @) @# B5 D y
Date) t4 |# o8 U( ]. j& R. X! X4 n
1 65.313725
, H8 C4 x6 Y/ [9 a. s2 54.061538% t0 R) F& X# y% y# `$ |7 s* W
3 59.325581/ l4 x, g; X' Y6 c
4 65.795455
5 {) i! p) \% E Q5 57.465116* d$ j/ A1 h3 U/ P0 i9 m
4 [2 s% A& w3 w* X
1
1 v2 o8 {; A, T, a/ t/ V+ s2" f$ C4 t# ] e, W# l4 ^
30 e1 e3 W9 i6 x! J4 n! p8 \
4
{3 R2 q9 G4 u4 m. c/ h# w. q51 f+ w# H @ n, ?; Y0 u* w8 H
66 E/ Z: l9 D! t( I" L% c3 v
7" G' P0 s4 H, W
8' X6 k& @5 B' j" i9 Z9 J
9/ J* F7 i0 F6 f% k2 @! V2 V
10: I8 I0 t4 Z. E
110 l! v5 H1 v! [
122 T r$ S3 s6 T+ v4 U) ^2 a
13
; R+ F3 [9 k/ a% k147 S3 p) ?2 ^8 V. K
158 k) B: h5 \5 I! ?7 G
16% ^. O, }- h* b, Q9 A
176 X( n. |% e8 c9 |/ J ?* S- v- Q
18* a1 a$ c' S' R
# 参考答案:
P1 O+ C+ |+ o/ k4 itarget_dt = df.drop_duplicates().groupby(df.Date.drop_duplicates(; r) q% X5 y1 A
).dt.month)['Date'].nlargest(5).reset_index(drop=True)
- v3 H" j; n) F, I4 I' E2 I" j
* X4 N0 U) |9 b o, T7 Q3 j3 X$ J Z+ Mres = df.set_index('Date').loc[target_dt].reset_index(
6 F9 G" D4 i( X; a5 u N( l) O ).query("Fruit == 'Apple'")
6 `; [3 [" y9 C) n; N0 D) ]. ?' n# u% ~7 L! J" `
res = res.groupby(res.Date.dt.month)['Sale'].mean(
& t8 }$ {( X- S, P0 A/ i0 [' m2 c% s ).rename_axis('Month')7 l5 h" t- n& R) o- f
8 R6 T% s9 Z" s. u
( \) H) E. U* p2 E+ |) m2 d
res.head()
, C. H/ N* N/ h8 W/ \Out[236]: ; ~% R- k! l+ o% @. g
Month
' |) i; O4 z, `& S \/ |1 65.313725+ H' G4 H H5 ^- R. g
2 54.061538
- K9 R5 K% T$ ]' w5 Q3 59.3255818 K) H O0 U4 B% r1 o
4 65.7954551 W ~* |$ [/ b! v) X
5 57.465116/ V4 H& p3 L) ? G" a9 z
Name: Sale, dtype: float642 r; C N" l2 }" ^' E* G
% g: j! R) ^3 ~) c- I8 J( }! V4 y1
, `6 f3 l" p) c1 x( J, Y1 [2! ~6 L: [! {& {8 l6 @
3" `1 a* O6 B m9 q) M
4
( w, [& K4 F7 Y' k; F7 v5
% ?9 ], {1 h1 B0 n8 ]6$ E3 i0 s/ W3 U$ R; |( h' l
7
8 B( U) Y4 T _" P3 y2 n8
, J7 a! \2 b5 `1 |+ u4 B9
# I# \+ n) t. E0 b+ f103 T; ]5 C2 n5 f* C
11; h- ^2 R4 m7 C$ m2 b6 e
12( ^6 l. d+ L' C9 C* J2 ]
13; s( e* z. j7 W2 k8 x) Z
14
' z3 L: U6 K- K* E1 B; q15
3 S* E% c- w @7 o9 N3 E16
6 D% d$ e8 o) c6 B& P) G17
* B, i. h$ a8 h: l182 ^& m( I6 T3 @
196 I, z: q) t9 S ?* K, @. q
20( h2 f1 w/ V8 O! l9 P: f' a
按月计算周一至周日各品种水果的平均记录条数,行索引外层为水果名称,内层为月份,列索引为星期。7 S, w! P6 ^) I9 E J" ~6 L, q
result=pd.DataFrame(df.groupby([df.Date.dt.month,df.Date.
' M1 ?- T7 w, ?. a dt.dayofweek,df.Fruit])['Sale'].count()) # 分组统计
m2 d- D* \9 K- J
$ T- [# ~2 h$ |* Vresult=result.unstack(1).rename_axis(index={'Date':'Month'},
. A& J4 h8 K# K+ N1 @- I columns={'Date':'Week'}) # 两个index名字都是Date,只能转一个到列,分开来改名字.% q$ a# m8 p& G' v
result=result.swaplevel(0,1,axis=0).droplevel(0,axis=1)* C3 K. |5 I2 z- z% R
result.head() # 索引名有空再改吧
, O8 _' D+ d* B4 s0 a# X
% A* n+ H7 |- h( C' k Week 0 1 2 3 4 5 6+ L2 ^; X) b8 R4 f
Fruit Month 1 @- z. u) P0 {' l' C
Apple 1 46 50 50 45 32 42 23- c& C! v/ w# I6 G1 {/ C& e
Banana 1 27 29 24 42 36 24 35+ W# Q# ^2 I# V. M
Grape 1 42 75 53 63 36 57 46
( N, s+ w0 k, y& QPeach 1 67 78 73 88 59 49 72
: v( w" Z9 ]" N/ N. f' z `Pear 1 39 69 51 54 48 36 40
. `1 j' _/ V/ S; ?1
# k5 O$ \6 ]" B4 J) z* ^2# E* e# ], s7 p' x6 S# u
3/ F1 n) N- n3 M* p+ i! @
4
$ c. Z8 G# \0 e& \+ P6 [* N' n5( L/ y" E' v+ n y+ ?( `
6
: ?. Q# m4 j0 c3 \( \/ a7- m+ \% K6 T" P- `5 G& ~/ `
8
2 }8 a6 z# [0 b9
/ k5 Q% _( I$ b9 f5 L$ W10& G, \7 m5 T1 R
11
! o7 i; ]2 ~( w12
5 k5 v1 ^1 a" Z m2 u6 n13
' x3 N% d$ Z; f- n14
% o1 S$ F' x1 }3 W15
6 C4 \, F- m+ |3 Z6 }7 A1 g按天计算向前10个工作日窗口的苹果销量均值序列,非工作日的值用上一个工作日的结果填充。/ Q! B9 R: a% c9 t: T8 H9 I
# 工作日苹果销量按日期排序0 g7 [8 e' c6 ]7 ]0 S* G+ z
select_bday=df[~df.Date.dt.dayofweek.isin([5,6])].query('Fruit=="Apple"').set_index('Date').sort_index()
. N1 ~6 |. x3 I" ^select_bday=select_bday.groupby(select_bday.index)['Sale'].sum() # 每天的销量汇总
5 k4 H0 n/ i" X4 k Wselect_bday.head()
" N' z! k5 i2 M0 s0 {
- J$ ]- J4 w* O9 t( o7 C* ZDate6 N( k/ D9 K) a6 u
2019-01-01 189
+ V" m5 `1 Z5 K. q8 D2019-01-02 482) a; p- p2 g# L+ T0 \9 k
2019-01-03 890: z1 @9 j3 ]) n: c% a3 n0 m0 e
2019-01-04 5503 I) j( M" {$ j8 d, R3 e6 |
2019-01-07 494
; ^9 ?$ `# W4 W
" Y1 q( h$ k7 J$ B) m c9 H# 此时已经是工作日,正常滑窗。结果重设索引,对周末进行向后填充。 ?( V( L* D# }/ [) D( s& |
select_bday.rolling('10D').mean().reindex(df.Date.unique()).sort_index().ffill().head()
6 ?" s6 v8 G; \) }
3 d' `: ]/ U8 v* DDate
; C9 B% C0 d: v; C; E2019-01-01 189.000000 e& ^4 p% w$ n# W7 O/ c
2019-01-02 335.500000( s5 \! G( _7 ]4 Y5 r* m6 z% ~
2019-01-03 520.333333: n) J: ?6 M: _( Z6 Y7 B
2019-01-04 527.750000
; C3 U! t& w0 { r" k2019-01-05 527.750000
3 \6 }, x$ f6 w- n2 }
# N6 A$ c% y8 T———————————————— h2 p% b- a( b# Y' x: G2 o
版权声明:本文为CSDN博主「神洛华」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
- d( ^/ j# G3 ^# q7 A原文链接:https://blog.csdn.net/qq_56591814/article/details/126633913" T1 U2 S+ S& V. t/ M+ w, X
$ |- J: o9 w8 m( T- J! x' t2 s* H- j. G4 b4 |8 A% j8 } A
|
zan
|