- 在线时间
- 1630 小时
- 最后登录
- 2024-1-29
- 注册时间
- 2017-5-16
- 听众数
- 82
- 收听数
- 1
- 能力
- 120 分
- 体力
- 566253 点
- 威望
- 12 点
- 阅读权限
- 255
- 积分
- 175099
- 相册
- 1
- 日志
- 0
- 记录
- 0
- 帖子
- 5313
- 主题
- 5273
- 精华
- 3
- 分享
- 0
- 好友
- 163
TA的每日心情 | 开心 2021-8-11 17:59 |
|---|
签到天数: 17 天 [LV.4]偶尔看看III 网络挑战赛参赛者 网络挑战赛参赛者 - 自我介绍
- 本人女,毕业于内蒙古科技大学,担任文职专业,毕业专业英语。
 群组: 2018美赛大象算法课程 群组: 2018美赛护航培训课程 群组: 2019年 数学中国站长建 群组: 2019年数据分析师课程 群组: 2018年大象老师国赛优 |
W- N# N s$ y1 P- d8 X7 x
4 R; G! e3 Y3 O+ O# L! O2 h0 _ V. j: z; z/ m2 @5 r
文章目录! `# z2 M3 y8 c
第八章 文本数据
/ N+ D- F a, l- _+ ]1 q8.1 str对象
) W9 M e; T9 E. }4 e: o5 F" e1 |% W8.1.1 str对象的设计意图
9 G+ h( Z" ^& C1 B8.1.3 string类型
) |. g! _$ E' @& G) U8.2 正则表达式基础5 S* P. w5 K" n7 i+ p" G( H6 }8 C
8.2.1 . 一般字符的匹配+ I4 b) _4 S% h# |7 e$ y' j `
8.2.2 元字符基础' G7 _6 `* T0 E! F- X0 O5 l
8.2.3 简写字符集8 V5 h9 x' _5 W/ Q3 ]9 _# K
8.3 文本处理的五类操作
* N$ O* l: B- Q1 @% i8.3.1 `str.split `拆分
9 c9 w/ s! r) P1 L0 [ b4 q9 a) J8.3.2 `str.join` 或 `str.cat `合并
& L. r7 s3 f5 a. L9 @& A6 ]8.3.3 匹配
* G1 \) b) a) V9 X7 C9 e8.3.5 提取0 y: V! {+ P5 e( ?6 z
8.4、常用字符串函数
; U3 _ g2 ], y8.4.1 字母型函数
# ]. @% z$ j( E% a3 K: k# g: l- g8.4.2 数值型函数! y: r: C7 g, y& u/ ]
8.4.3 统计型函数, j* M1 a" k4 k. ^9 O: B
8.4.4 格式型函数/ L \" @1 t% @' j4 u) u! B1 L1 x. S
8.5 练习
7 T+ p) q8 ` U: ?3 Y9 S! n" w0 UEx1:房屋信息数据集8 d, C1 g, n+ ?+ k) A: q" C" w2 B
Ex2:《权力的游戏》剧本数据集! q2 d; V( J- E7 g% p
第九章 分类数据
- Q# e3 e/ P0 D6 [( h7 s( f' R9.1 cat对象# `3 L$ T! N! M+ o% A
9.1.1 cat对象的属性+ g1 s8 |3 Q ?+ k6 z
9.1.2 类别的增加、删除和修改" i' ^. l. a8 X+ r1 r- t
9.2 有序分类
; x& T, W% @) X) f! c4 p) j9.2.1 序的建立
% c! O8 ?! q. T) Z9.2.2 排序和比较' l% k. }, @1 M+ o2 c+ b0 h
9.3 区间类别2 L$ X6 f0 b# J C% v
9.3.1 利用cut和qcut进行区间构造3 `, r/ V9 i; }1 U' _& v
9.3.2 一般区间的构造' d+ L, C& Y% i8 u0 }! ]' S
9.3.3 区间的属性与方法$ y* o- W; U, f2 a" h
9.4 练习
% m, m1 ?* M8 i: r0 Y3 h- X8 S: WEx1: 统计未出现的类别
, H5 f& X5 K* I6 k5 gEx2: 钻石数据集# K9 L- d" p# l% J, R1 w
第十章 时序数据
8 {* [) h4 o1 x9 f0 |10.1 时序中的基本对象
; M- P% [6 i5 X* R/ f10.2 时间戳5 ^+ z: L' [: B; J: w0 h
10.2.1 Timestamp的构造与属性% `9 L1 @1 u' h) j3 k2 a
10.2.2 Datetime序列的生成
' c* |' u, G5 b7 e10.2.3 dt对象3 e' E7 c p2 Z) b8 k+ X
10.2.4 时间戳的切片与索引8 ?$ A* h! ?7 S* I
10.3 时间差" j' z9 j: u8 w1 U- f% ]
10.3.1 Timedelta的生成
+ T8 x! m% e% ^- ^1 b% [2 e10.2.2 Timedelta的运算( E l: t" A5 P4 e8 g" F B
10.4 日期偏置
! ?. X9 K+ Z1 y9 l5 x6 `10.4.1 Offset对象
1 H, P$ n' S/ A2 S0 {2 L10.4.2 偏置字符串
* h' ^: ^4 m, b$ L10.5、时序中的滑窗与分组
# g, s: ~8 s/ n' {9 i$ p! s; R10.5.1 滑动窗口$ t, j3 j# w. ?3 o* C4 I
10.5.2 重采样
" u2 j5 a" }; I5 q3 }) Y( T10.6 练习
# z( J) G7 c! tEx1:太阳辐射数据集5 B$ ]/ M1 N* z% F
Ex2:水果销量数据集, d% r/ i- E' _9 Z1 L
课程资料《pandas数据处理与分析》、github地址、讲解视频、习题参考答案 、pandas官网
$ L5 s. m+ A e" G4 h' r传送门:
5 ?& ]" y% m! z2 a/ L& Q) y V9 H! T3 m( R/ }8 L" N( H
datawhale8月组队学习《pandas数据处理与分析》(上)(基础、索引、分组)4 \5 M3 V; g. f. e# ?5 @* B* B0 f' P
datawhale8月组队学习《pandas数据处理与分析》(中)(变形、连接、缺失数据)
! M* T5 l7 L; V第八章 文本数据
- s T; H$ I' b2 o8.1 str对象8 G2 J, D5 @& T+ O0 R2 k2 a
8.1.1 str对象的设计意图& y$ K: F* x" k5 l. r. y {
str 对象是定义在 Index 或 Series上的属性,专门用于处理每个元素的文本内容,其内部定义了大量方法,因此对一个序列进行文本处理,首先需要获取其 str 对象。在Python标准库中也有 str 模块,为了使用上的便利,在 pandas 的50个 str 对象方法中,有31个是和标准库中的 str 模块方法同名且功能一致,例如字母转为大写的操作:- v' T3 l5 ^9 \6 o" V
, I& N3 ^$ z W* u7 D$ g
var = 'abcd'8 w% A( n6 D5 J/ s0 z
str.upper(var) # Python内置str模块, a; \& r: {+ y
Out[4]: 'ABCD'- x, J' c/ c) n( C
8 n' T/ p7 b. H7 Fs = pd.Series(['abcd', 'efg', 'hi'])
0 P5 e; C1 \9 D/ Q# a
& N( j+ T# O) `% w5 ?# d) Gs.str
* a, a) y- n3 ~; zOut[6]: <pandas.core.strings.accessor.StringMethods at 0x2b796892d60>
/ l- k3 z- h2 l% V/ x, z8 r* E+ R2 e2 w: N+ V' o) C
s.str.upper() # pandas中str对象上的upper方法9 e0 E/ m9 w) t
Out[7]:
( X" f# g/ [+ w% g8 C0 ABCD
+ h! Z) ]3 G7 Z4 Q3 h; G1 EFG
/ W Y% s1 K; P' A" l. h9 l; K, e2 HI$ Y! b; l8 h: G
dtype: object
0 O6 N: M9 M8 L* p* [1, I$ |; g# g. E Q6 e6 ^& l& r- u
2, X8 m3 G( J/ x# H. W1 X1 R3 p
3
7 k1 H& c, l5 O+ N# N1 b$ t4+ K3 _9 d' J4 a
5
8 M( q8 G9 o; u1 B7 N N6* B# ^; W1 I- Y& J
7! ^. B3 w& I# [* n) Z5 v
80 ^7 b% U9 A" ^- c* Y
9
4 |3 B# O9 Z0 F5 C7 x# W8 ~10
7 V c5 F* z" M; j11' a. a8 T/ y- c
123 J& D; n& T* t0 r. K4 S
13
4 G6 Y {1 `& \; |) P/ n14
5 [2 ~ V0 O3 X0 @6 Y; k$ K159 X4 H3 S7 S* Y1 W" c1 r6 C# A
8.1.2 []索引器
1 {( l8 [! i, d* S- R 对于 str 对象而言,可理解为其对字符串进行了序列化的操作,例如在一般的字符串中,通过 [] 可以取出某个位置的元素,同时也能通过切片得到子串。
( b. \' [) Q9 S2 ~9 U: g8 v3 S* W pandas中过对 str 对象使用 [] 索引器,可以完成完全一致的功能,并且如果超出范围则返回缺失值:
3 g' j" B0 g0 q/ }! z! R7 ~. i$ ~
! J B+ {3 O: S% H8 X; {$ ms.str[0]
, |" `( c8 ^" o: J4 q9 Q. x5 T% C, d5 iOut[10]: 2 L4 a3 _% J. A: h3 G& G' m% g0 s& C
0 a
. K5 i1 V" u, V1 e
3 R% ?: f1 J% ~ X9 p, M9 z2 h. _( e& m/ b3 h; O. S
dtype: object
! |. S, }5 Y0 W
& i% U9 B! |, Y1 B. |s.str[-1: 0: -2]
. S' X" H; }7 ~, B/ mOut[11]: 3 b0 E$ d5 v% T4 s2 {
0 db0 U U; G, P2 T0 E' u3 V
1 g& @+ l0 {& d9 s. X Y5 W: g
2 i2 q- |# A) Y7 ?
dtype: object0 {2 c" e% y# k. f" _4 w! y
- N& C! d! h8 h+ z7 G8 o os.str[2]
) a6 w# V _* x5 r, J0 nOut[12]: $ F+ Y7 R! a/ ^& s; @3 P; D* W& b# }
0 c9 p# a* d& c, H3 \
1 g
' ?3 ?; O9 o- G& F7 S" f3 [2 W0 S2 ?2 NaN1 w ]2 o: t1 R- E, W+ v
dtype: object
/ R( c& C) V! P0 W& n- R( A0 D& c [
17 J# e1 p, g. W; u( R# z
27 d4 w% L3 `+ O& q
3
1 M' C' B1 w6 K' _! R4 K6 k4
: ?7 V/ o$ E6 I% ?3 r* E5/ f1 x0 P- l/ h& {' m5 O
6
5 J1 o0 C' `! H* f. t* |6 k( z7
5 t: S6 b) L6 h) Q$ X; A( H7 W85 G+ s6 q# `- Q
9
" c; ]% P' r- P5 H/ X, ]10
( H& B& L# o8 B- B1 k11
5 b! P6 ^1 q1 l; _ s12
) Z% o& M9 J( N13
- Z5 ?% k1 r8 U; @) R14; D' c8 j7 ^3 U H$ M
15
. p" W9 K5 E( ^; i* O' ^# `16! U0 X' U, v3 Y. I" I) S. o
17
9 i7 `5 S) X% X+ F9 w# P18
/ T- J( k' E0 b4 |' S9 C19& G3 A9 y, m* T. I+ J/ R
20% M0 E5 N1 f m$ L7 ~# V( ~
import numpy as np$ g7 V6 e* s7 {' @; K
import pandas as pd
" c0 H/ ^, `5 R" T
! E+ V* J# d/ i# ~7 y# U7 ps = pd.Series(['abcd', 'efg', 'hi'])/ J: n2 L% T) W! ^& w
s.str[0]
6 ?7 u: f7 K* Z) T- w/ j" P f1
5 s+ j/ Y5 O9 a2 y% a2
. w# W X$ T0 y0 H3+ j" Y* X9 I# A6 @
47 Z3 @" O# V6 J& z7 F
5
9 O( B3 B- `3 ^" `0 d' c0 a* y) N5 i) H5 A" H* ~3 b
1 e
- `% R" J2 K1 H+ A* H$ H' y V2 h
# S7 @9 T: {- M7 S! q( n" ~dtype: object- V0 E8 c0 _0 e! [: z
1
1 z- `3 E# v1 V! t V; M2
/ t$ W% A) D# K, u0 L( I37 \& K% e' O3 @/ m6 a
4
" ~/ [) g9 x: W* u6 C# P9 u5 @8.1.3 string类型1 c( U. T& J. U* K5 G) \5 w
在上一章提到,从 pandas 的 1.0.0 版本开始,引入了 string 类型,其引入的动机在于:原来所有的字符串类型都会以 object 类型的 Series 进行存储,但 object 类型只应当存储混合类型,例如同时存储浮点、字符串、字典、列表、自定义类型等,因此字符串有必要同数值型或 category 一样,具有自己的数据存储类型,从而引入了 string 类型。 I, G( @" Q2 O( H( j6 Y5 |
总体上说,绝大多数对于 object 和 string 类型的序列使用 str 对象方法产生的结果是一致,但是在下面提到的两点上有较大差异:
5 P" O" W; D: ]! D7 W" Y6 o
, z y1 r, x# _9 o二者对于某些对象的 str 序列化方法不同。4 i a6 Y6 n' P' N: u2 l
可迭代(Iterable)对象包括但不限于字符串、字典、列表。对于一个可迭代对象, string 类型和 object 类型对它们的序列化方式不同,序列化后str对象返回结果也可能不同。例如:5 x D, c# }* S+ J7 x2 Q6 N
s = pd.Series([{1: 'temp_1', 2: 'temp_2'}, ['a', 'b'], 0.5, 'my_string'])
4 Z2 h: r }2 c+ d' I# H6 cs
3 j5 v7 y- @& Z& z9 |( g- \1; }5 |1 S9 N2 n* Z$ M3 N$ z4 S
2" v8 V8 l! ? E5 k9 n( j1 b, j( C
0 {1: 'temp_1', 2: 'temp_2'}! I3 ]3 r/ ?/ n9 K/ N
1 [a, b]! h& o. e. a, Z3 e4 i% |+ j: M. d
2 0.5
7 F9 B# B) T% m3 b; [- I- `3 my_string
% T6 X2 o$ {4 @3 T5 `0 Ydtype: object
6 G7 _, _: F$ I8 {0 n9 l; V1' w, `2 ~8 t1 J
2
' A. M+ ?+ L4 P( Q4 s33 _8 |3 j0 Y9 }1 R b2 q
47 z% T3 W' H2 F6 X9 Q; w) A
5
$ D$ f9 ~8 Y. ]s.str[1] # 对每个元素取[1]的操作
: A; [& k* W+ i0 d: x1
' D+ u% x/ a; i3 E0 temp_1 H6 a6 I' ^1 W( ~ X0 `, t. q8 l% F9 y
1 b
~+ X" v+ n+ c2 NaN/ X2 Q! o9 w' S9 L5 H! x
3 y
. J( c( y' v7 t) w& u7 K9 x7 jdtype: object9 m0 A1 h5 f+ G7 {+ H7 ?
1 C8 B6 w+ W& E
2
2 i! g7 S& ] ^! ?; X2 u) A3
7 @- E/ G4 ^( s* z$ E$ d7 W4
$ k3 w% ^2 b) Z) F% b- s5$ G# f, D+ b8 s
s.astype('string').str[1]
2 X* U8 {7 W, \1& Z: }4 y& [ V1 c/ E+ m
0 1
( M' ]; N* k }1 '$ A2 R' s: O3 Q3 F, A+ z
2 ., q* x1 M- h' v3 C# B( S
3 y
9 [3 m) P: R z. j# [; N# zdtype: string
8 t" y! Q8 n1 [2 F0 F1( d v: e3 P w; b0 r
2! J6 ^9 `0 g# F& d
3- h( M6 P$ U+ E- Z$ t3 j
4
( a5 p e: a* u0 E5: C* T6 W& N# [4 d2 p8 L: S7 `
除了最后一个字符串元素,前三个元素返回的值都不同,其原因在于:
0 ~3 W, X# G7 [! `2 E. X0 m; L! B9 [% E# U* ?, L) f D6 ^
当序列类型为 object 时,是对于每一个元素进行 [] 索引,因此对于字典而言,返回temp_1字符串,对于列表则返回第二个值,而第三个为不可迭代对象,返回缺失值,第四个是对字符串进行 [] 索引。/ A3 |. A$ m; W' N
string 类型的 str 对象先把整个元素转为字面意义的字符串,例如对于列表而言,第一个元素即 “{”,而对于最后一个字符串元素而言,恰好转化前后的表示方法一致,因此结果和 object 类型一致。3 ~( T$ n L9 }2 t3 t" G
string 类型是 Nullable 类型,但 object 不是$ ~, {2 a5 G" g" q2 P
这意味着 string 类型的序列,如果调用的 str 方法返回值为整数 Series 和布尔 Series 时,其分别对应的 dtype 是 Int 和 boolean 的 Nullable 类型,而 object 类型则会分别返回 int/float 和 bool/object ,不过这取决于缺失值的存在与否。$ \9 X6 T) d9 v
同时,字符串的比较操作,也具有相似的特性, string 返回 Nullable 类型,但 object 不会。; l* B: ~: ^6 Y. g
s = pd.Series(['a'])
! I) ^8 v+ _% d z' c2 f* S& @8 u$ E; s1 f/ [& N
s.str.len()
" C% C! D2 \( D/ bOut[17]:
+ S, A/ c& V3 y9 ^4 |0 13 u8 U1 K& e4 V
dtype: int645 {, Z( `, v5 A( [. \$ M* S0 m2 I
7 n; K) j. |" h9 Ts.astype('string').str.len()- p$ V4 X1 d2 ^
Out[18]: 9 R# [" Y+ N" |2 l+ [1 G; W n
0 1
0 K, U- [2 U, _8 a( C: m2 j' Zdtype: Int64
1 ~# S, [) z) P. V9 t; e3 w
+ ]- f) r+ a' P7 ^# ys == 'a'- c* _! R) d3 y9 |7 H
Out[19]:
0 L! C- m7 j" R6 k; R0 True
7 h+ ?, a- X. Y7 B, k7 a2 y; Edtype: bool
6 J" d# H7 T& I; \% g% D4 Y f4 _ w- V: t. K5 Y
s.astype('string') == 'a'( [; d. Q6 d) C+ j0 C
Out[20]: % R* @& ]! k& E
0 True m+ L" H+ C7 A! G2 N! h
dtype: boolean% P: j' |7 a/ _4 k- l2 V; F
7 q2 r6 E3 F1 k- U% Xs = pd.Series(['a', np.nan]) # 带有缺失值, i1 e4 n6 b6 W8 @6 v0 n: w6 v. H
) n/ k( b6 F5 F3 Y7 F4 `1 c
s.str.len()$ N$ {; O$ M, j/ n- |3 A0 @3 T
Out[22]: ; H8 {% }' e7 x
0 1.0% d. A* t( G# J4 ^: s& a* } ~# U
1 NaN+ Q! {/ {4 H7 J# K5 O
dtype: float64
7 s6 n2 e5 W: s V' E3 @+ ]/ v$ {: Y/ G' t: l: [8 Q9 s& T* h `
s.astype('string').str.len()
: }# x8 O8 X+ ]8 l( }) O+ JOut[23]: 8 }5 V3 h! R+ A3 g! m4 N
0 1
0 }5 l3 F% G$ A9 W; p1 <NA>9 P4 p. e$ @1 K- T# x2 }
dtype: Int64# g$ P1 b% T! B4 I
) N0 W% M+ T7 i9 ks == 'a' p7 \7 Q4 k! r7 A
Out[24]: / ~6 ]' F5 K3 d: J; k
0 True$ o. E+ C9 i+ Y+ a9 Y; z
1 False9 q/ I. X6 v, A" q' S% i
dtype: bool
. F. P- D+ g* q' V$ L& ]9 \
6 R2 i; R3 B1 V W' u0 _s.astype('string') == 'a'
/ O- V& _( L! R: |Out[25]:
0 f8 {; S" y# Y* v! @0 True* }4 D: t3 ^" H6 j# e6 Z$ z
1 <NA>
- @" ^* I9 d! J# tdtype: boolean0 R7 G) e" B0 c
5 l; N: |$ r, h* I/ t13 h# o" L# o8 T$ v% t/ f7 v/ h
2( D4 Q2 O9 |9 v1 [( z8 A
3* T1 z% J7 U& k3 U. X" X
40 G k0 M/ a* R" W, ^4 V( b; ]
5( u& i0 f0 _: x* a- b* G5 P
6
- r5 g F, h6 x* q7. f- u# S/ L6 l6 @$ L. K
8
3 A+ t7 n3 M7 _4 _ `9- x A+ a. p& g) x! n& M
10
6 n! I7 U7 P0 C2 Q1 ~11% ~' { q3 O: o1 `7 }
12
5 X. Z* }" ]( B1 Z- O9 D13/ c. P& n) ]$ J' r( l
14
- v' ]% e" X k2 b15- j O2 T* [$ s9 Q2 Q
16
" T- C( t3 I/ O/ z+ U1 i% V& ]$ S17
" o1 y- x8 S& c" g) P18
& S! q, y9 F/ l19
; p: J4 R) m; ]2 w20
1 U% v" q& H: u21. D+ `1 c6 Y, u& ^: J; _
224 J5 k. {3 e1 A( J; L
23# h7 ~. q+ o0 r
249 w; h( r S8 S0 M6 `/ s {
25
# z! Z7 Z3 J+ {26
' G2 q' _1 w# T' f: P8 `- w, R2 }27
9 W# ^5 k- E& F$ g9 N8 Z28/ T+ a- ~# O) R
29
! T7 J: L& b# w2 d& |: c309 ] c$ \) L1 F0 N# @# m9 q# Z
31/ F1 q9 W. c K
322 w1 _& t& k# s- O9 y" `' \
33
* e/ @' y; {# ]' _* m& g; E342 ^; l3 q- W& S; U, Z2 w- ]
35
9 [/ e% X) ^2 D$ j ?* R( }- K36
6 u7 y4 }% j( Q S% z37
; Y+ T; q0 j+ V. ^4 E6 s2 M. {, R38. F3 j/ g. Y, ~
39) c4 I: u0 [% x3 Z7 ^5 w( W' G- T" X
40
9 b# H" H/ T2 I5 ~41
% q7 [8 H8 [0 V42* G% R2 E4 H6 a- f6 |
43
, h' W! V+ Y. ~ D44# K0 O+ b$ [8 C) h
454 Z7 n; S. N0 l( x0 K
465 m6 m4 K2 c, Y- b: }9 E
47
( D& z/ F; M! a4 y# X" }, |8 X 对于全体元素为数值类型的序列,即使其类型为 object 或者 category 也不允许直接使用 str 属性。如果需要把数字当成 string 类型处理,可以使用 astype 强制转换为 string 类型的 Series :2 s1 ]$ m$ q9 K3 y
1 p# {/ h# L! f) m1 S) c' W% Hs = pd.Series([12, 345, 6789])" {% s% {" @5 h% V4 u
; q n& N4 V8 j6 z# l
s.astype('string').str[1], W3 F' D6 Z. l' f4 H% L: O0 ^
Out[27]: 3 n. S" D# I) b* p4 P$ c0 }
0 2
# ~2 c& o4 b7 M7 ^( C$ r1 41 {! H% @0 Q6 `& T4 X* `
2 7
$ Z9 J. C0 `) J! W, P. N) f& s% |/ jdtype: string
0 v% j( v& r, r1
$ x- |/ o( c! z# u: l* V; F8 q( J1 y3 o2% G( Q: D- J+ c' _' a6 y& ^& g
3
6 t5 P& K7 k5 I0 M0 l% e6 ~+ @4$ M- R: W7 h% ?) u# F; ~
56 I+ [- Q8 {* e( R ?
6. f; W9 j4 i& N; E% s7 C5 K* x
79 E; [, K3 Q) d8 T2 j
82 \' m* t' h+ m, e+ ~6 c
8.2 正则表达式基础
( W4 B0 I |6 B! ? k这一节的两个表格来自于 learn-regex-zh 这个关于正则表达式项目,其使用 MIT 开源许可协议。这里只是介绍正则表达式的基本用法,需要系统学习的读者可参考《Python3 正则表达式》,或者《 正则表达式必知必会 》这本书9 V& u$ X0 `7 @! `7 s1 F9 v# ?
# R5 J" X1 K5 f
8.2.1 . 一般字符的匹配$ \3 @& B }; @# P( J& q
正则表达式是一种按照某种正则模式,从左到右匹配字符串中内容的一种工具。对于一般的字符而言,它可以找到其所在的位置,这里为了演示便利,使用了 python 中 re 模块的 findall 函数来匹配所有出现过但不重叠的模式,第一个参数是正则表达式,第二个参数是待匹配的字符串。例如,在下面的字符串中找出 apple :
% B* o, f2 u C0 N3 Y! R0 R" z
' l( v* _0 e$ k1 A, l6 b, oimport re% a8 j) a3 v. q+ o! d0 z$ ]4 S! @8 a+ P
5 [6 _, B3 F+ Q
re.findall(r'Apple', 'Apple! This Is an Apple!') # 字符串从左到右依次匹配! w7 e& i# n, ^* B' \6 J/ _+ \
Out[29]: ['Apple', 'Apple']# P8 H! p+ _5 u2 U
1
3 j( b5 d/ M0 z8 }- l# k2' J }; o% |& j* O: ^
3) |4 p7 z+ x N) w
44 v0 a% Z( V5 ]" D
8.2.2 元字符基础8 t$ g6 L/ X8 |$ F; V
元字符 描述
2 L" d' p; M0 B# Y- f. 匹配除换行符以外的任意字符* ]- U5 u) b3 v# N9 i) Q
[ ] 字符类,匹配方括号中包含的任意字符) ^# q4 e: }: P
[^ ] 否定字符类,匹配方括号中不包含的任意字符2 y g) v \$ _; d4 _6 C8 J
* 匹配前面的子表达式零次或多次( P3 |: y5 r- R; {! ~2 y
+ 匹配前面的子表达式一次或多次。比如r’d+'就是匹配数字串,r’d’就是匹配单个数字1 O. p# C! u* l' Z' \
? 匹配前面的子表达式零次或一次,非贪婪方式* U( X; u& _4 `/ t
{n,m} 花括号,匹配 n 到 m 次由前面的正则表达式定义的片段,贪婪方式1 l- N9 m+ t S# t, F3 e* s. [
(xyz) 字符组,按照确切的顺序匹配字符xyz
K) }. T6 q/ y+ t| 分支结构,匹配符号之前的字符或后面的字符: t5 h2 I% [* T
\ 转义符,它可以还原元字符原来的含义4 J; h$ t! \1 c
^ 匹配行的开始
+ P9 i4 j) ?/ B) o t$ 匹配行的结束
* k/ k O" ~ m3 _1 w6 a4 |import re) K& o4 R9 H+ b
re.findall(r'.', 'abc')1 U0 g3 T. C5 |0 n
Out[30]: ['a', 'b', 'c']( K$ x3 [* c0 d7 u" H- E; ^
3 u& Z) _8 ?) }- `8 y3 b, M$ E
re.findall(r'[ac]', 'abc') # []中有的子串都匹配
+ |- @1 e- N/ d; u: l/ Y7 W9 xOut[31]: ['a', 'c']
, s/ T7 y# c6 ]
/ \8 M# R# d P: z: Zre.findall(r'[^ac]', 'abc') % b+ a8 h7 K) H/ i
Out[32]: ['b']
/ O! ?. Z) d. k# N9 G
- T; t" t0 m8 l: }! i2 [' K1 T6 Fre.findall(r'[ab]{2}', 'aaaabbbb') # {n}指匹配n次
" R# p, U3 T( K: Z1 U: \Out[33]: ['aa', 'aa', 'bb', 'bb']
9 k7 p" R& n; @$ g' n
! ]' w& S2 Q0 o5 [2 dre.findall(r'aaa|bbc|ca', 'aacabbcbbc') # 匹配前面的或者后面的字符串1 \+ b% _) G; L+ R/ K/ ]
Out[34]: ['ca', 'bbc', 'bbc']; J# G# H+ E3 d. ? ]) C
; e' N8 B9 m; r+ W/ @+ Y9 D* y
# 上面的元字符都有特殊含义,要匹配其本来的意思就得用\进行转义。2 r2 f& S! V% @( [+ {4 F
"""! q* P! V) c' V, W$ v1 N. F
1. ?匹配的是前一个字符,即被转义的\,所以|前面的内容就是匹配a\或者a,但是结果里面没有a\,相当于只能匹配a。$ f) p: K* h+ t- k! b
2. |右边是a\*,转义之后匹配a*,对于竖线而言左边优先级高于右边9 u- B1 a: R) i; |4 a1 i: [' ^
3. 然后看目标字符串aa?a*a,第一个a匹配左边,第二个a匹配左边,第三个a虽然后面有*,
! K: V6 ^! ]7 c% c7 W3 i但是左边优先级高, 还是匹配左边,剩下一个a还是左边,所以结果是四个a4 E4 @" |# }# l J. ^- H
"""; B ^' u# H0 M: ^; s: t
$ `, i! L' I: N9 k7 Hre.findall(r'a\\?|a\*', 'aa?a*a') # 第二次先匹配到a,就不会匹配a?。a*同理。) Q k. {% Z, E7 e
Out[35]: ['a', 'a', 'a', 'a']
5 ^% O W) p" d6 ~
6 T. ]9 G# d0 ]0 ]* ^+ w l# 这里匹配不到是因为目标串'aa\a*a'中,\a是python的转义字符(\a\b\t\n等),所以匹配不到。
: h$ E5 @; W( i, S- g1 ?2 c# 如果是'aa\s*a'之内非python的转义字符,或者'aa\\s*a',或者r'aa\\s*a'就可以匹配到\字符。
& x" \" v/ I3 \6 A* L2 ~0 h( rre.findall(r'\\', 'aa\a*a') 1 p4 @/ @! P( J+ X; q) d7 Q
[]3 ^ d$ }. \! |# D) z% R$ k2 ~! f6 d
2 J' l& b1 Z; Z' y( Q
re.findall(r'a?.', 'abaacadaae')
* v4 Z3 L# X2 {Out[36]: ['ab', 'aa', 'c', 'ad', 'aa', 'e']
0 d8 Q3 r6 g$ C/ ~) O" ]6 h& @* i% x( n; z. T: s# w
re.findall(r'(\w+)=(\d+)', 'set width=20 and height=10') # 多个匹配模式,返回元组列表
5 c2 i T7 p0 u2 s% i4 n8 M[('width', '20'), ('height', '10')]: b* [# ]: L: y2 ~" l% T
- \8 K( h8 B" h0 J1 j8 F' ]1
F6 n) }5 X; P; Q1 d. r1 N24 Y6 P6 m, D3 |8 h+ F( Y% {6 o: b' s6 q+ k
3& _7 k' ~$ C! a+ M
4* k$ Q! B6 x$ s8 z
55 w2 d% s, A: V
6
8 A) G% u' |1 M2 M73 ?8 }$ m1 X0 O) F% m0 w3 K
8
8 O% Z/ w3 N |& `9; P- s" Q* o9 |
10/ h O* M0 L4 c% T6 t) {
11
# b* p! G" D3 B R; S12$ M3 P0 ~; P! J9 f" o; D
13$ n5 v, r) ]$ W
14
+ f+ n! l0 q+ I) q1 h( d; w2 V1 X154 A/ J9 u0 ^$ r; b& d0 j
165 u; T2 B& A$ f
17
( V# F/ l" `4 W. e, r180 J5 s0 v& }0 F8 u
19
! p; {) N3 y: V/ q9 s! H1 {4 A206 \+ Q e7 K6 w4 h; {
21# M* k5 O, w# n1 D( }* ?
22/ n/ N6 u. Z% \, ~# z# a& t2 E
23
) M) ?+ D3 Y4 f2 ~ F7 C# m24 b* _ y( R) w3 ?7 i$ ]/ Z
25! H" @7 V- F( @5 o6 {2 P6 ^8 r
26
; {+ j5 h* c" f! ]0 B) I2 j, ?27( A# ]% v+ N. i N3 [" L Q
28- y( W+ J/ C/ \) w T2 u
29: l' h5 e; X- k/ V% m" t( `. P
30
, o- B; L! x: l1 T+ g5 t2 b31
; U& P; n, |) k1 K t! E$ o32: W& ]/ v6 Q8 n! d! A8 R
33
6 H+ y8 v6 ^' X$ C4 Q! s34
, F( Q% q5 l, D* q9 p* [0 F) U+ P354 p( Z0 ^7 q" G9 j4 A
36: h* \$ W, u* q0 w7 c) J' o8 V
37
/ e7 i5 i' ?. P8.2.3 简写字符集
0 a; t5 ^, N0 R k: ]则表达式中还有一类简写字符集,其等价于一组字符的集合:2 Q) j. z/ Q8 X% w! V
4 {; f0 L9 c. \ U' p) S7 N6 T简写 描述" c+ @& o" N! S, W
\w 匹配所有字母、数字、下划线: [a-zA-Z0-9_]
& m" m; }$ _8 p/ V/ b! e2 Z\W 匹配非字母和数字的字符: [^\w]
% L) z0 n8 E. y5 I6 C0 B\d 匹配数字: [0-9]
, F% i; {3 B0 @\D 匹配非数字: [^\d]
5 [ v- j( G1 A$ v( u" i+ }/ ~\s 匹配空格符: [\t\n\f\r\p{Z}]1 |2 X3 x; i5 P
\S 匹配非空格符: [^\s]4 `" m* Z' v5 Y9 v0 p) A5 k
\B 匹配非单词边界。‘er\B’ 能匹配 “verb” 中的 ‘er’,但不能匹配 “never” 中的 ‘er’。6 W: d1 |1 I# @" R
re.findall(r'.s', 'Apple! This Is an Apple!')
7 Z% i9 x; r6 A* O! rOut[37]: ['is', 'Is']
" {6 Y$ h2 w5 ~; D% y: L0 @/ @" m/ _: J; J' }
re.findall(r'\w{2}', '09 8? 7w c_ 9q p@') # 匹配任意数字字母下划线的组合,但必须是两次/ y1 V, A) h. @8 @. C
Out[38]: ['09', '7w', 'c_', '9q']4 S# o1 E3 K& E7 e- Z
$ A1 P {9 V. h0 a
re.findall(r'\w\W\B', '09 8? 7w c_ 9q p@') # 匹配的是两个字符串,前一个是任意数字字母下划线(\W),后一个不是(\W)6 L- c7 w* ?' T! U( V9 U0 @
Out[39]: ['8?', 'p@']5 Q( C# h8 T8 j- Y; s2 }
W3 n$ F6 t t7 n& L
re.findall(r'.\s.', 'Constant dropping wears the stone.')+ k2 q0 R1 L1 a) }7 [$ C6 z
Out[40]: ['t d', 'g w', 's t', 'e s'], l$ O9 X; M' K! \; m: p
: M, |- O# E' p- h
re.findall(r'上海市(.{2,3}区)(.{2,3}路)(\d+号)',% ~5 X* J: W( V2 X$ C) Y
'上海市黄浦区方浜中路249号 上海市宝山区密山路5号')
% r( s5 Z2 D; E. A' v; J$ U& ^. D M! F4 B: j. b% {! n; ?
Out[41]: [('黄浦区', '方浜中路', '249号'), ('宝山区', '密山路', '5号')]! S4 T( Q) q9 t- x9 q, E
$ B4 v( g( k7 p( {3 G3 R1
1 B6 n5 X' c6 @+ K1 h2
( O) R- y0 f* d3 _: w0 A3
/ O- q, }4 v* D1 e+ @* ?5 z4
0 w8 g+ ~" F" w* r5
; d( V( d" V- N' u9 Z; @! A67 }$ @ y( F8 v
7
5 n7 z+ S; ^5 T. H86 H) H4 K8 K1 p- e6 ^
91 g( R/ N! z9 m9 x* S+ [
107 k# m( Q+ ?0 w6 c7 [. ?4 N( H( z
11: X; A; x7 S* x% o
12
3 D& c+ n& w+ D8 {8 v4 T13
- N# t: ?8 ]" \ W14
! {2 Q& M3 S2 _+ b7 k15
6 j4 d: h- m; A) f16
$ Y p4 k$ V$ D, S+ B8.3 文本处理的五类操作
8 S5 {) S4 i0 z+ R1 J2 u& N- d8.3.1 str.split 拆分- c! b# G' j) H+ `8 I! W+ n
str.split 能够把字符串的列进行拆分,其中第一个参数为正则表达式,可选参数包括从左到右的最大拆分次数 n ,是否展开为多个列 expand 。1 Q, R! C' l6 @* L& C z" |
' c$ @3 X4 e ~s = pd.Series(['上海市黄浦区方浜中路249号',
" n2 v6 k2 f. } [' l' B0 h; ?. _" w '上海市宝山区密山路5号'])
0 O* d. {6 B' P0 m' h
4 S; J, m7 `+ S6 ^7 Z, J: \9 D8 _/ G
s.str.split('[市区路]') # 每条结果为一行,相当于Series
- `9 s3 ]& M4 @; _# \/ e8 O, kOut[43]:
[- i `1 t( U7 m" ^9 ]0 [上海, 黄浦, 方浜中, 249号]: z+ {: K( z6 F8 p9 x
1 [上海, 宝山, 密山, 5号]% Y! K7 j& F6 {; m/ N
dtype: object1 v3 N, j" I L, \& A0 E
) H- i/ w. |6 K$ V$ w1 Ws.str.split('[市区路]', n=2, expand=True) # 结果分成多个列展示,结果相当于DataFrame' y+ `! O! }8 `& H
Out[44]:
; e4 c5 l3 O1 u 0 1 27 _! _% q/ e7 S8 U, L
0 上海 黄浦 方浜中路249号' W; J0 ^3 p& K+ s8 H
1 上海 宝山 密山路5号0 `0 b; h6 s+ X+ j4 Y; @; R
1
/ m* h; v7 h) }; s+ b2% K- g: n8 ]' H/ d
3" a& h9 o& X$ a9 A% B
4
0 V- M( h; v4 r, a& e5
* t5 j' d% O( t$ ^- `0 C( ~6
7 O* l/ u& H2 c2 k7' Y: w k9 M. o# D
8( Z+ i$ K! g1 p" C; Q! l
9
- R( B6 R3 W) i* A100 N/ {& p6 t* d! K
11
/ t2 |6 x$ p# n% A9 ~12
1 A3 _* e( D2 q* G13 C% G8 S1 z/ [2 Y/ q2 y
140 {9 y) z1 o! {+ c% `. |
155 G, u2 n3 W1 [$ \/ A
类似的函数是 str.rsplit ,其区别在于使用 n 参数的时候是从右到左限制最大拆分次数。但是当前版本下 rsplit 因为 bug 而无法使用正则表达式进行分割:! c+ O9 Z% i( ~* Z& A G/ ?
: N' b$ ~ ?0 K. z6 e) p; l
s.str.rsplit('[市区路]', n=2, expand=True)9 C% \( W1 D, H6 ~
Out[45]:
# h% g4 v0 k5 }3 ?% d5 f 06 m" z, V8 V# H5 S2 K4 X2 _
0 上海市黄浦区方浜中路249号2 L& W3 r8 S y; ?0 i: `0 g- Y1 f
1 上海市宝山区密山路5号* D- r! s6 A' w; b; n) R2 r
1$ L/ g4 W6 H: o# A3 l) X" b# e
2' ~, n; N/ ^/ }( C
3
- K: H2 e! @, v, S: e0 p4
4 Z o( L9 B; f) k5
2 g/ ^1 ]2 k- u* x2 y8.3.2 str.join 或 str.cat 合并! n7 K5 x; p8 D0 F* j
str.join 表示用某个连接符把 Series 中的字符串列表连接起来,如果列表中出现了非字符串元素则返回缺失值。6 q( B- q* `4 V l1 B; W
str.cat 用于合并两个序列,主要参数为:
" j; ^. a) r8 b* w: s4 e4 gsep:连接符、
, T5 t$ @6 M$ n3 K% V" b0 z- Y8 ~join:连接形式默认为以索引为键的左连接
* d5 h; C, K D1 O8 j. [na_rep:缺失值替代符号7 m9 q9 {( }* C* F# E5 ]' V2 j
s = pd.Series([['a','b'], [1, 'a'], [['a', 'b'], 'c']])
4 r! }) U0 L+ r% x" H8 T: t: u& u3 ^s.str.join('-')
' Z' p9 M5 c* n ~Out[47]:
: {1 b% N+ H) ~- L1 x1 D. Q* A0 a-b+ k& u" q2 L7 \4 Y- P
1 NaN
: q! A& j" o8 z) Z/ ]2 NaN
0 ]2 z; d7 |- d$ H. kdtype: object) ?5 t* o8 Q F" x! I
1* \5 M& o0 @2 U2 b7 w6 E% w9 L
2; ?* j r; }( h7 s- A: W; v
3 ~. X* x0 ~, y
46 l: L& G5 w8 a% k7 i3 A2 P% P& a! e
5
; L6 g$ T, u8 j64 F7 b' y, ~6 v8 m
7
& `& ^4 x5 x6 ]s1 = pd.Series(['a','b'])$ _2 v, }7 s, e4 M% q/ U% m
s2 = pd.Series(['cat','dog'])- B- i% N+ c% x! s1 s' C2 D
s1.str.cat(s2,sep='-'): ?9 d* I a" R, Q& }: x/ E
Out[50]:
Z9 r7 e) n6 k0 a-cat+ l+ v2 k L9 I2 ^9 @) t' c S
1 b-dog5 d4 p, y! m- `" d6 W# n
dtype: object
3 }: b0 w {. O, X, N
; b. [9 W! \$ f# c- }2 }1 @s2.index = [1, 2]/ f/ F& ?2 T; o3 k$ [0 _
s1.str.cat(s2, sep='-', na_rep='?', join='outer')* z+ {+ }9 g4 E# t% s3 h, e
Out[52]:
. M$ ^) K; j4 p- n0 a-?0 B+ `0 T% J/ p+ S7 k( p
1 b-cat
- x* T' k( ^, [% p2 Q! O+ K; f2 ?-dog5 o$ _" f# e- {7 B
dtype: object2 Z$ f- n0 K" S/ z3 r
1" o2 P. @: h# C2 r' M/ d
2
. ?% `9 ]/ Q- ? _: Z3& `; x8 a7 W5 E2 _) p+ E6 F5 [
44 ?; n4 r- T$ u; g4 s
5
6 G& z: \- ? D* h6; Y( U; y& K1 C/ k, _" W
7
o3 l. D! g5 x' z# p8
+ C2 U1 r# ?6 p+ Z97 k6 p# b! d; E$ I s7 d
10( X# ^1 n; n- y& _1 t
111 D5 r. Q3 ^! d }- z
12# o( G N! T' p& F% P' s$ j# H7 k
13
$ D( j8 T6 p5 V/ G ?8 Y14: a6 Y% p3 g/ E
15, Y: P8 z1 d# a5 @" o8 C. ~9 \
8.3.3 匹配
7 q- ]2 r; b/ [str.contains返回了每个字符串是否包含正则模式的布尔序列:/ Q0 z. E! ~5 r3 Q* H
s = pd.Series(['my cat', 'he is fat', 'railway station'])
/ H- I! h, m) T$ m9 j( u5 w6 Qs.str.contains('\s\wat')
" g' e- l e* r
; P! K4 G* Y$ Y! v* N0 True
) E3 R: b$ E, e. g; G* A1 True
. C* H; F3 x, r' K* s* w2 False
4 _, L8 ^3 ]! ?7 rdtype: bool
% z q1 K$ D: Y ]8 w15 Z1 e' [; J' G" O
2+ j# R2 U- U3 d6 p
3 a' x' o5 ~) ?- @
4
, M) \1 j+ {5 c! }59 G1 _4 N4 ~% r$ J7 W. J
6
2 l* |; b. L& T1 {) S+ k& h6 w78 u+ D; a1 A. P$ t, b2 R9 i8 j5 ~
str.startswith和str.endswith返回了每个字符串以给定模式为开始和结束的布尔序列,它们都不支持正则表达式:
$ q( o" v5 K- b! A3 |& q0 V% N* v$ e6 Hs.str.startswith('my')
: \5 S. k, P" t& M4 ?! {4 h4 Z2 ]) Q) \
0 True
9 J7 {7 |( v s/ v1 False
( L- ]% m% G! D6 H- X2 False! d7 `" i7 H- u9 K" n4 ]
dtype: bool; q: A1 y) t' K7 [% h f
1
' s8 \: ^& m( N. J. i$ f2 Q9 n2( V5 m! d/ l! {1 W5 M7 a
3' D0 M' A7 r! c6 t0 C
40 O4 X6 T9 j! B' d" j" z
54 w0 b. _1 ?' e
6% K2 U- {7 L7 }& I, s% }5 W
s.str.endswith('t')
7 [ x U% k3 `0 d% S
. K% j. i) Q3 Y; P2 M0 True g" h. c, N& T9 I
1 True
# a6 ]: |* U% l: h2 False1 C+ D% t9 D5 r* Z3 C/ j; O, b
dtype: bool& [ n7 @" k) r4 z5 o2 D
1( ] Y. M- A5 Q B& r( \" ]
26 c8 X0 ^2 @7 x$ u) `+ o& Q
3$ v& \9 c8 X6 [' f
40 R! C t3 R5 E$ x) I- n
5
' `8 x3 O$ R1 m* w( ^6
1 W: ]2 V" s2 E! ?) Ostr.match可以用正则表达式来检测开始或结束字符串的模式,其返回了每个字符串起始处是否符合给定正则模式的布尔序列。当然,这些也能通过在str.contains的正则中使用^和$来实现。(貌似没有python里的search方法)
, T& }! j F4 vs.str.match('m|h')4 b, |/ t2 t1 e7 n1 h
s.str.contains('^[m|h]') # 二者等价
/ Z: |, m( `. Q, e/ Y; @; G4 F; ?" u# g
0 True
( r* ]; A. Y) g j1 True' Q6 T' `4 f/ k4 K+ M6 i
2 False- @# y+ i; P# x0 G
dtype: bool2 V& o1 p0 U) J; b
12 A6 R7 p7 [& S: o7 ?: S
2# _- N+ W4 e, M* j
33 q" E; h! h+ O* m( ^ w3 N6 k
4
# T- L' X; P; i' r1 I5
1 b: q- E4 Q+ j- b7 l+ Y- P8 S61 Y2 {/ v! E+ W' v8 b
7
" t7 @, O. J# V4 ^s.str[::-1].str.match('ta[f|g]|n') # 反转后匹配. _% _6 k% w& Y ?: m
s.str.contains('[f|g]at|n$') # 二者等价
" [; }- F( Y$ S
4 L, C! c* S, a: n0 t; P0 False
/ [' [) U- f' _1 True
* ]2 i" F H# J; l2 True; g, C# |3 _- c5 f
dtype: bool4 G" o) g9 m9 x+ e3 ?" u
1
; G" f9 t' l2 E8 e, r2
, r% J5 U/ L- s0 B. U; r# H3, y9 _( E8 t' u+ {: f
4& A% y2 k2 Z7 _+ f+ |5 M* }
5; u7 Q( u4 R' }3 }( s
6
2 p# u2 D1 i& Z$ K; Z8 K) \7
; x; f' f. q% e* o5 Bstr.find与str.rfind返回索引的匹配函数,其分别返回从左到右和从右到左第一次匹配的位置的索引,未找到则返回-1。需要注意的是这两个函数不支持正则匹配,只能用于字符子串的匹配:
# k! w; U+ R1 J0 t. ws = pd.Series(['This is an apple. That is not an apple.'])# p+ |/ k7 L7 R) _- h
2 L" O- [% P: Y' Z+ r* ms.str.find('apple')
! n Z+ C. Y( ?/ M# v9 q5 x3 IOut[62]: ) E/ X& b; v9 h: ^0 m9 ?7 ^
0 11# R7 O: G5 e8 Z* i, _/ I0 N3 s' f4 `
dtype: int64) G1 G5 |6 g, `! H( @
. e! h0 w' K' Q5 s) M
s.str.rfind('apple')
- p" ?( C, q& ]! p4 nOut[63]:
- L2 w7 d) X; {0 33
1 @3 O; S+ o" Mdtype: int64
* J1 Y# l7 s5 ?. H! v; ~1
5 h! V. _* i- S8 h, v2
% z- u9 S- t) d+ j2 @3
6 G9 V6 w# x, B% |, [7 q: V4
/ l, A$ _6 G% f" y& Z2 M5 m5+ b2 |. h# I5 m0 K7 J' h5 f8 Q
6, m. M$ ^$ v* \' X7 c" T
7
% U4 q! N% F- g& K$ O7 V$ }8
]$ Z, S G. x) L4 X6 J3 h. F9
# {: d, }- a B7 o! G10) q {3 e t$ S/ L
11 t; W) p( w _4 H6 g1 b% h
替换. Y7 z z& s, K. |/ D( X9 C8 G# M! {
str.replace和replace并不是一个函数,在使用字符串替换时应当使用前者。/ Y2 Y% W+ Y; i* G& L E& R
s = pd.Series(['a_1_b','c_?'])
' `. [$ j d) H# regex默认为True,表示是正则模式,否则第一个参数内容表示是单纯的字符串,也就是匹配字符串\d|\?9 a& Q; g0 A2 B) [! K
s.str.replace('\d|\?', 'new', regex=True) / ~3 S! | x& Y# j5 B2 S% E2 N
+ ]5 B3 y' o; Z% h0 a_new_b/ {0 L8 D* E( o, E: H. u4 d" h
1 c_new5 b$ l p2 [ t0 U; }) t) ]) o. R
dtype: object; O" g1 u6 T; V* D( w
1+ J. T; O6 y' S+ H% s" i7 K
20 q) g% y! j* k* v' d
30 j) Z& u4 t) @; S
4; j9 H$ x0 R# N- n* E
55 D; M. }2 |/ b$ s
6, \+ M/ w4 g$ y( i2 a
7
" t2 W5 j* S: I- } 当需要对不同部分进行有差别的替换时,可以利用子组的方法,并且此时可以通过传入自定义的替换函数来分别进行处理,注意group(k)代表匹配到的第k个子组(圆括号之间的内容):
@5 l9 q6 U y/ C. ~" O6 G$ }. G% b; C3 j* u+ v& A' E7 x5 h
s = pd.Series(['上海市黄浦区方浜中路249号',. W3 O3 e0 p5 t" H4 k: v# S: {3 J# U
'上海市宝山区密山路5号',! e3 ]/ M4 M! X/ n, W- K, M* E3 N9 u
'北京市昌平区北农路2号'])
# ?- l" n: I3 w# i V- lpat = '(\w+市)(\w+区)(\w+路)(\d+号)' p6 B8 A) X$ \' ]8 | F/ S
city = {'上海市': 'Shanghai', '北京市': 'Beijing'}
* J4 ]# l5 }% S: udistrict = {'昌平区': 'CP District',, I$ C, y2 m- c. m
'黄浦区': 'HP District',; g p( f" Y+ O& j7 ^+ F
'宝山区': 'BS District'}
! }: [9 ], ]! Y' c% W2 U/ v. p+ e9 Wroad = {'方浜中路': 'Mid Fangbin Road',/ v, t- l4 B4 A, c/ C1 _
'密山路': 'Mishan Road',
# D! n: T1 S0 n# f, i '北农路': 'Beinong Road'}
% a& t2 c+ t/ w" J8 Zdef my_func(m):& r# \( {* L2 G& @
str_city = city[m.group(1)]
0 a' D: d# s$ H! T# E: T8 G str_district = district[m.group(2)]
' Y' H# U; @" j5 r& i T# i str_road = road[m.group(3)]
* g8 m* G7 H& P9 }8 X str_no = 'No. ' + m.group(4)[:-1]" {! Z1 x8 u; @4 T$ Q6 O
return ' '.join([str_city,. k9 |: I i0 j3 A% ?, p+ S- ^: K, e
str_district,
: L) t) a u3 j& K2 d# g: c- l str_road,) l& v, S4 W" P3 }
str_no])% l7 x4 A0 d0 ?$ C! V
s.str.replace(pat, my_func, regex=True)2 }& @2 p) S- Q
, [5 J, G9 D/ X% v( M1$ |1 [" T4 p: E$ ^ {! q8 S
2
U1 S6 y' b; C( ~3 b; R' Z3
' t N1 p" N% D1 i" X6 I4& S- Y) c+ S, }: ~ y. b
5( J6 z( p. e4 o+ V
6 F5 \+ \- f' _5 j1 s
7
r: l/ S) n1 F* y f) i8
6 y4 f8 w& K. T. ^. A6 v; p& b% G& e9
( M3 H: D: i7 P9 I10
V; K* w9 f3 o2 v- Z8 y0 E11- T1 D, W* U0 M1 n
12
# w& E' S/ n+ X. F4 r7 l13* B( k4 r o: t5 d9 K m3 v
14
# n7 G: [* m0 V+ d3 ^4 L; h) c15
0 t8 P# p# k C; h [3 u2 h4 S16+ L' X8 o* `0 M# C; Q8 q. c3 {- P
17
5 D. _$ u- \3 P/ B. g18
9 q. L, {" V _" r; M" i( t19/ }, ^ `. X# z) b
20
* |3 I2 h' ?0 V" a. J' ~. z$ C217 }9 b' _* p: _% k( _
0 Shanghai HP District Mid Fangbin Road No. 2493 K7 k# A2 o# r& l9 `% L0 k: Y
1 Shanghai BS District Mishan Road No. 5! w; }/ @ z" {( ~
2 Beijing CP District Beinong Road No. 2/ E& |! _' O2 ]6 Z( I" W$ @, h
dtype: object
' I- g' T; Z3 w2 L9 q5 O8 N1
! J( b, l Y7 a* M2
& r9 u W( [3 t1 T6 r' h! L: n+ g3
0 U6 ]7 V, s, h9 X' m4
1 ?- B/ @/ S' q这里的数字标识并不直观,可以使用命名子组更加清晰地写出子组代表的含义:3 {1 G* c% N; @: r5 S1 k
; x; {2 O4 b( f/ S) W: M7 f( i: w# 将各个子组进行命名& ~1 j) A' ?) _" C+ k" a
pat = '(?P<市名>\w+市)(?P<区名>\w+区)(?P<路名>\w+路)(?P<编号>\d+号)'1 f7 _- x; Y9 x; w$ y9 i: Z& V
def my_func(m):
( Q1 t# B4 o5 [& T str_city = city[m.group('市名')]
' O2 m. f. \' n* w str_district = district[m.group('区名')]
& c7 F7 q" A8 M& C8 Y( l7 v. l, K str_road = road[m.group('路名')]
% Z C" [3 L( ^# D: d) j str_no = 'No. ' + m.group('编号')[:-1]$ q8 z2 N* @+ z5 A6 [
return ' '.join([str_city,
; }/ S2 G1 i1 A8 K+ o4 g str_district,
, y) D/ ]" S( K# k% {5 N str_road,7 }& g6 O7 n! m5 |: r- @
str_no])- q( U# c/ L7 r* n& G4 b' @! \
s.str.replace(pat, my_func, regex=True)
. Z; E6 t9 J" `1
p# v2 x0 Y$ u! Q! B2 r6 `2
4 m1 h4 F- p" ?+ i! M5 Y38 ` W2 X8 x' x8 P5 J9 H2 B
4# _; E4 {3 V7 p% _: `) w
5
% O* Y1 }0 e. N' ?" n6) G3 F7 s4 O, u5 a* P
7
$ _; J! m# m2 {% d8( ^' ?( L" v( d# ~1 w
9+ v( ^) O4 {) I# a- P, p4 h4 G
101 I' o w4 w' |2 h" j3 T
111 W$ s+ ^. Y/ L. O# X! K
12
; l6 R' j+ a' K3 c) Q0 Shanghai HP District Mid Fangbin Road No. 249
2 Z4 c0 _0 c5 V. k& l0 A* {/ \5 c1 Shanghai BS District Mishan Road No. 5% k2 A, ~/ h$ i9 Q5 ]
2 Beijing CP District Beinong Road No. 2$ h8 C. J: K- d% W( ]# v/ h5 o5 j
dtype: object; Y8 s6 ?7 D0 Q( r9 K4 M
11 ]" ^( S4 c5 N: w. S
2' R" c* B' }: D
3
8 O5 g- G" r% I3 T: c' S' n5 Z45 Z/ z5 q0 s P7 a: {- C
这里虽然看起来有些繁杂,但是实际数据处理中对应的替换,一般都会通过代码来获取数据从而构造字典映射,在具体写法上会简洁的多。
7 L0 a: ~' U! k) s5 i! A$ G8 n( F% r: o: ?# B
8.3.5 提取5 Z h8 g8 ?3 S
str.extract进行提取:提取既可以认为是一种返回具体元素值(而不是布尔值或元素对应的索引位置)的匹配操作,也可以认为是一种特殊的拆分操作。前面提到的str.split例子中会把分隔符去除,这并不是用户想要的效果,这时候就可以用str.extract进行提取:
& `. J2 b! ?- g( Vs.str.split('[市区路]')
4 g1 z3 U9 H7 l' NOut[43]:
: L) I) e5 w. ~3 a5 d' u0 [上海, 黄浦, 方浜中, 249号]
& u" U, Z K9 V% m& x1 [上海, 宝山, 密山, 5号]
/ V3 X$ c- H2 B1 \dtype: object
/ V' Q8 o- V% ~' {
# I5 c+ j1 c/ m. D0 S, spat = '(\w+市)(\w+区)(\w+路)(\d+号)'& S1 f0 Z% y1 [( }: W
s.str.extract(pat) q( \/ I' W7 l/ ~# `
Out[78]:
5 [8 s# h- D- q 0 1 2 3' q$ P9 x7 W" s8 ?0 k
0 上海市 黄浦区 方浜中路 249号
$ j6 O! w* A) u( U& ~2 Y' X1 上海市 宝山区 密山路 5号$ D" i. F# ~: Y+ b5 g7 b
2 北京市 昌平区 北农路 2号8 N: a# {* ]/ r$ a0 E9 \' c3 z
1
! R. x/ ^ s* [( t1 v+ _28 c8 v4 ^$ t8 A% W4 e' J
3
, o# I6 A' _$ ?$ g4 c+ F. u) t2 `+ e" @0 z% b
5" G" M/ ~2 [; P! C, d
6
* L' t% ]1 F- Y6 R3 u7
+ j4 j! C& D: ?7 U3 Y8# ]% r6 C: e' e8 w1 w) }, ?
9
+ e" _- O$ O& @$ _101 b! m# o2 h" z8 ?4 A' L
11( t8 c+ R* n+ r; i* S0 R$ \2 J: G2 y/ K
12: V& [: K9 i9 E! R% p1 C
131 W; K! A% q5 q# ]6 c+ t
通过子组的命名,可以直接对新生成DataFrame的列命名:! a! H/ I y6 w2 k
+ o# X$ ^' V/ v" u- C. tpat = '(?P<市名>\w+市)(?P<区名>\w+区)(?P<路名>\w+路)(?P<编号>\d+号)'! c6 d4 R( q% L( x0 `# }( B
s.str.extract(pat)! M) w, n' X" Q6 }1 J
Out[79]: / r/ A6 s" a4 x! E' E
市名 区名 路名 编号
& I6 j0 X3 Z! ~0 上海市 黄浦区 方浜中路 249号: B' Y( ~* A9 j& v$ J
1 上海市 宝山区 密山路 5号
; F1 `# N( X: r. @# M: T6 Y2 北京市 昌平区 北农路 2号) N2 b, j% ~5 P) M- G
1
. ]+ ] @7 t/ l, M4 v: \9 J2
! F& D5 P# x7 y5 q. ~) O' N" z3
$ R+ e" w2 ?& g2 L4: A: r; I: \5 ^; j! p7 j; \7 F% S& q
5
8 V8 p* a- F* ?% [8 ^3 l' B6
4 @* q6 s' y7 U5 @7
: i' `+ O a6 M% Tstr.extractall:不同于str.extract只匹配一次,它会把所有符合条件的模式全部匹配出来,如果存在多个结果,则以多级索引的方式存储:
4 o) T) T% Q0 l( U6 Ls = pd.Series(['A135T15,A26S5','B674S2,B25T6'], index = ['my_A','my_B'])7 W3 }, k! `. w, I0 d& v2 T: O
pat = '[A|B](\d+)[T|S](\d+)'% S: D! [) e8 W3 h2 @
s.str.extractall(pat)4 u. H# V1 i, A# @- |+ r" J% h/ @
Out[83]:* e+ Y. f# |( g% V, Z1 M& _
0 1
0 M! ~* T! ]2 W C& X) O! K match g8 P! Z5 ~- \) n) S1 [
my_A 0 135 154 y" Q1 ? v" U5 F0 n' S
1 26 57 Y& T! S4 Z. m+ t
my_B 0 674 28 O4 `& x% D8 W3 d5 I: W
1 25 6
! \1 z, H5 g5 \# F( L1! @1 K: S( L1 @1 w) l5 R) L% Y
2
8 Z0 ?+ ~$ l" W& K( G3 H3 ?: ~- p3( h6 P) `! F% @* h$ B+ [
4
$ c, M7 [' w n8 v6 \6 P5
! k, Y8 e; r d% l+ P9 ~6
5 ]1 `% e, Z2 M) p5 u5 E7
) S, |$ s. ?& e6 M# ]8- K% Z( w7 E) F; V( w- T2 J, _
9# K7 j- z+ R @7 N2 k
10
5 h* U: X% n5 }pat_with_name = '[A|B](?P<name1>\d+)[T|S](?P<name2>\d+)'7 S9 c: A2 P% ?& y
s.str.extractall(pat_with_name)
% V/ D1 J7 ^9 ~; \. YOut[84]:
8 H" `8 y: s0 U1 \/ j name1 name20 D2 P) {# q( M- X6 W$ }
match " t( v: g3 F8 p$ ^; {. Y3 j
my_A 0 135 15
5 y+ p6 l' x- z# G 1 26 55 t5 N$ p h, s y- o! w9 b8 m2 ]
my_B 0 674 29 d! o- Z4 k8 b. M/ b* ^. A6 i7 I
1 25 61 F3 V, h" R# m2 O" {" f) z1 c6 K
1
" A' E6 \: { y/ i% |) p; K4 }* x29 [' N7 k! \! f p
32 c2 r7 F( Z ^) x. b
4! @' @ n# R" ~1 b
5( f* \6 ]( n3 x- _3 s
6
! W' x7 E1 D) N3 n% C* L6 A1 R71 v( r" l8 L! z( C* H1 p5 y7 a
8
( c6 I2 z; A- v97 V. G+ E; }9 F1 [
str.findall:功能类似于str.extractall,区别在于前者把结果存入列表中,而后者处理为多级索引,每个行只对应一组匹配,而不是把所有匹配组合构成列表。, {: _' g% [. y
s.str.findall(pat)
, f& @9 r/ a4 r. @1$ k0 R) b6 b6 }
my_A [(135, 15), (26, 5)]
4 t5 q( M4 a# ~+ hmy_B [(674, 2), (25, 6)]
0 R& v. X$ S! a, l! v* ^" cdtype: object
" _ |. U& ^8 }0 N9 u; a1
# I# ~( F' `7 K3 R- D24 J0 t5 ?6 M1 r6 [' z
3
1 N B5 W' m8 r8.4、常用字符串函数
& j0 [2 B5 T2 S3 N# p4 }/ z" K' ? 除了上述介绍的五类字符串操作有关的函数之外,str对象上还定义了一些实用的其他方法,在此进行介绍。
\, ]. ~ y3 @: K
2 t! t- H' f9 } [7 `8.4.1 字母型函数2 s3 w% u- B! ^+ \0 ^! D
upper, lower, title, capitalize, swapcase这五个函数主要用于字母的大小写转化,从下面的例子中就容易领会其功能:
& G2 W0 p& f. A2 H; f
y& g( [6 H' H- ~6 _9 n' q7 @3 Ts = pd.Series(['lower', 'CAPITALS', 'this is a sentence', 'SwApCaSe'])
1 c3 y% m# w. ]0 i$ B% ?+ ?- r
8 O C( ~0 E2 a1 v* s$ as.str.upper()" {: g6 p" u6 ~7 Z& V4 ]$ \! [1 T0 ~
Out[87]: * \: }" P5 d2 ~. i9 c- t" f3 K. ]
0 LOWER7 n$ `0 m8 v' U! x
1 CAPITALS2 J- ?& M+ K+ @6 T F0 }- @
2 THIS IS A SENTENCE% Q$ y% t) F3 J& M' M e6 F; F
3 SWAPCASE0 }+ k% L b! e* x( g
dtype: object
$ I6 `. F" q1 O9 h
& P$ J; q# j q# b; B# c" Ks.str.lower(): _9 V( W) ]% c2 @, E$ z/ l
Out[88]:
+ C8 l& u1 I- G; t* m, e, u0 lower
- f0 b. S" U. I" l: D6 O1 capitals
) ?3 V: x2 _- a* _2 this is a sentence
2 R& l* z7 B; p- O+ x+ t- T' a3 swapcase
, f0 v6 _8 E% `) fdtype: object
7 a; M" _3 q3 `3 r* n# @1 I" B2 [0 |! t% _5 t2 G4 c
s.str.title() # 首字母大写
V- h2 u7 G/ o& M* b6 m4 @: V9 x% COut[89]:
+ U* _9 _& M. e1 T) `$ {2 D0 Lower7 b4 @; H" d: d
1 Capitals
5 R3 X j N0 w2 This Is A Sentence
/ |/ z E' ~9 M9 s8 u% z: l. |3 Swapcase
" P: E9 A5 W, }) u4 ^4 k! J+ z! ~dtype: object
4 S- _/ Z. W( |- F; G9 u( Y
2 v3 I5 h1 P5 D6 o. K+ ?* v$ b8 b. xs.str.capitalize() # 句首大写
8 ~6 L- {1 W, J- v6 \Out[90]:
& h+ ~) }" [& i3 `2 K# t' h* V/ Y0 Lower
2 O, I. j, r9 ?; s1 Capitals
' J9 D7 m+ }# |- l! w5 z2 This is a sentence
+ p3 k/ L* }! A$ n4 o6 E% N/ i3 Swapcase
! U% r7 `/ W% J% \5 s9 Vdtype: object
7 A5 V2 b: j- U% h: P( j7 A' Y7 U& O9 r( U# l; Q& E
s.str.swapcase() # 将大写转换为小写,将小写转换为大写。
) B6 s2 D0 m: l* P8 Q" [Out[91]: " b2 o( X. T. _/ w2 f& R
0 LOWER
z- U5 ~5 h- H) i$ O; P2 D1 capitals* u& U( S/ `4 l) {$ ~
2 THIS IS A SENTENCE
9 T/ t; n( R3 J) C: e3 sWaPcAsE
$ P. |0 G3 C6 b& U& r4 r Ndtype: object% `2 z! V9 `9 e% @% z
) ~6 F1 C1 g+ \9 d3 t
s.str.casefold() # 去除字符串中所有大小写区别
4 m* U- \' A& W9 k9 g7 G( s$ R* {" {/ o6 D2 z7 p8 m% j( A: V
0 lower
9 r6 n' x% p, S- F& s% q1 capitals3 ~: \& b7 Q, A- \4 j0 U0 M
2 this is a sentence1 W, ]0 E* m1 d- N! W
3 swapcase9 K7 ]3 ~, M- E/ ~9 Z3 L
2 X5 \! Y" k2 z# t! z
1
5 {4 `5 \/ a6 J) h; L& C9 M% Y1 E% U7 i2
) A8 D+ }9 d! o" c* ]; g4 v) R3
& i1 o1 t' n! o+ Q( B4/ G+ V& v" i- U0 n
5
9 ~$ v0 o3 U1 q) t8 W64 t, y4 i B: g% {8 F6 Q v
76 ~2 f; I9 g ^) r
87 x- M, }& x2 D3 v+ @6 l
9- D p/ `, Q9 g
10$ l. w N2 `0 d7 e/ y3 T
112 C- z: X$ q& R* ?; D6 L2 T# E8 I
12: Y+ k: I* j2 ]4 C" M
13
& }9 Q3 S9 N6 n7 p$ h+ a14- m$ O& n; u6 V$ ]6 u) z
15
4 t v3 w$ d) g. v16& k; Q4 a1 E. f0 Z+ T
17" s# L! D" i) N6 ?6 L9 |0 _9 a
185 ^, C5 E) y, [: e
19
5 F) S W$ F2 I9 U20
, {# Q( `1 c5 G' f/ N1 L6 z+ v3 t4 `21) V0 P7 o# W3 W' S# f; @
22
' A4 V7 u( v1 ^# n! S% r$ n23
2 E2 c, B) u; s5 p' r) K24
5 ]5 m* g+ Q% S! ^( Z25" k o2 M$ U- Q7 Q6 K. k% u
268 K5 @, p7 } o1 I) A! `7 w" H
27
+ t* Z! e r1 u4 d: z' M, T28. L3 y# S$ k L
29
) t& g$ l' `) m- c9 q305 c9 o# Y- x) Z
31- m# m- c! l7 N: c5 U8 U1 B! w. T
329 B7 p3 j C+ l/ E- V) M$ b
33/ y- g5 C5 |% o3 @
34
$ M% B$ ^3 d" e2 {9 [, I35
$ Q7 x# B( Z8 g7 ~36
' z5 h9 q0 w% }( P+ }9 ]373 E1 k. O1 a/ _& L3 {' _
38
8 Q4 n+ c5 a# y, c- C39+ t/ r+ p# F% c4 }
40
) V8 y' V) N: M5 m5 o: A; U! X410 j0 ~2 q. b8 M7 o
425 c8 I4 h9 t7 k! F$ W ]4 K
43
3 w/ s- k, I0 R3 g44
- {. ?3 l+ i! A1 a45
* X v: X7 h2 t R5 P5 F46
$ p- H8 J+ t- T% V6 B# G477 X3 e @5 ?8 q' q" E+ f: l3 k9 q
48- L" U6 Q) x& b: i( ~, ]+ n
8.4.2 数值型函数7 j+ w* N( ^+ i* O
这里着重需要介绍的是pd.to_numeric方法,它虽然不是str对象上的方法,但是能够对字符格式的数值进行快速转换和筛选。其主要参数包括:! k, A3 g$ K4 Y0 I
$ l5 E# m2 {( {/ [. |errors:非数值的处理模式。对于不能转换为数值的有三种errors选项:4 e) U$ f# c% D6 G: [5 s
raise:直接报错,默认选项
`& C( b7 v2 D2 j0 u# Gcoerce:设为缺失值4 g" X9 t+ N! {, I' j* S
ignore:保持原来的字符串。9 _( e' f0 a) U8 E9 _ O' z
downcast:转换类型,转成 ‘integer’, ‘signed’, ‘unsigned’, 或 ‘float’的最小dtype。比如可以转成float32就不会转成float64。
( G' r( m7 C' g i3 qs = pd.Series(['1', '2.2', '2e', '??', '-2.1', '0'])' P, S8 j4 B2 _/ }9 `
0 w! x1 B5 s) Y
pd.to_numeric(s, errors='ignore')
# j) B0 e1 e1 N7 nOut[93]: 3 y8 P. T8 G/ }
0 1- R6 _0 X. w W
1 2.2/ y1 `) x2 \& R. ? P- d1 _
2 2e
. S7 q8 p* a" r7 R+ T2 _3 ??
. C7 l4 t1 f" u# c4 -2.1& w: o% j" D7 ?
5 02 @; r. E( F/ v/ Z
dtype: object s# G* x& P1 s/ o' m; z7 ]+ Y5 D
* k q+ x6 l/ l: p# P
pd.to_numeric(s, errors='coerce')
& H1 R2 k; s3 [- N4 G& E/ ROut[94]: & S2 D! c# O' p2 n) z
0 1.0
5 L. q+ N/ L1 w1 [, Z1 2.25 d- i. J3 K& t F8 X# P+ ]5 n
2 NaN
2 Z3 Z, r5 W% v4 m3 O9 {% Z3 NaN
" Y/ K. }# n+ `& o4 -2.11 e( j& X6 Y& w$ t
5 0.0" N. W# z# f5 j
dtype: float64* d2 n3 N6 _) x0 s0 ^
1 `+ B2 h! a+ v; {" a1
" S1 i3 n% K# @% b) ]6 t2
M! T3 F$ A3 C; T39 x8 a* i* ]* Q0 n
4
' S2 G j$ n. G+ y1 ]' z( C4 K. K5
+ ~) ^! r+ f: s) m: w: l K8 }6! s- r% q5 j/ W7 @* P" E* c. w3 \# j
7
& t. }) F( ]: G9 |3 f8
: I' t/ _. a( I9 A+ Q9
) C: }5 t! b; w+ c102 I! c+ f8 N3 w
11
; w, h9 |0 m# N' ], {+ |# d, y12
) @" v: C; [- F# s! a( P13
$ N/ X# V5 |2 g( z( V14# L% d6 d0 v" y7 i5 A
15
. V' r( i3 v! m- G0 c5 P- h16
" A/ V# S/ _9 ~" t) @) `17
1 ]( ]. c5 `3 m; x3 `! A3 r2 B18
% G! D4 N; e& g8 \! y19" c( q; _( o/ p
204 m h- _, F- e( b% k4 P$ P
21* A' L9 c* S4 [, u
在数据清洗时,可以利用coerce的设定,快速查看非数值型的行:
! F, U+ r' W5 h7 L0 }4 s. h% u% k7 b5 Q, r) _
s[pd.to_numeric(s, errors='coerce').isna()]
! j- v4 r& `/ v4 K; g7 N1 D0 R; rOut[95]: , g( q& ]7 m% Y& l, K' j/ ?
2 2e7 N7 W; F. o& j
3 ??" w6 T$ |9 q3 [0 P/ m
dtype: object
6 Q/ N1 H4 m4 c: }: L9 E1& E5 I7 t6 _+ `
2, Z% Y+ m5 L; w$ N) P3 _
3
" N+ Y/ H2 [0 |. \! Z# x45 L% b# q% R8 r: r! y- B7 F! E
5/ T- `. `0 {& p% b3 N* l! Q8 V
8.4.3 统计型函数) { F- x+ C8 s
count和len的作用分别是返回出现正则模式的次数和字符串的长度:
1 U( m7 R0 y# I
D2 C7 g, K Q+ R# I2 Bs = pd.Series(['cat rat fat at', 'get feed sheet heat'])
+ q) d4 c) a3 R0 Q+ L) h
* D( d1 g- `) w+ @/ p4 as.str.count('[r|f]at|ee') # |左右两种子串都匹配了两次, f2 s' l* H! w0 _+ h' {- U" s+ L
Out[97]: 1 W( i/ M# D# {) f1 O/ A
0 2
0 v9 {5 Y* u" n1 2! m8 N) B( b5 z# Q7 p
dtype: int64
4 o4 s9 }) P. T
5 \/ ^2 C* Q+ zs.str.len()
( S( X# D- f5 e1 n$ e# @4 h1 q( |Out[98]:
" T9 i; m- b& g# ?3 O0 14
$ `' m' Q6 p" H& @4 E7 d# A, W1 19
: u3 f4 N2 {' q- v4 N. Kdtype: int64
4 x( E ~# D5 J' t* U$ _& O17 c% h; @/ b/ i0 O
25 w+ U2 p& O1 a( s
3
4 U9 f) A3 G9 [% T# x4
/ f. O' x7 L! f+ J1 i5 ^1 t5 i5
0 F) ?2 p& V$ f2 K6
" Z" |. \' N$ }, M" v1 v7
# m3 b7 B; V1 ^1 R) o87 E8 }) q2 x9 R5 U0 V
9
' |& t; j5 z% e4 I, [8 P1 x108 M5 G4 U. w) f. C6 D4 J& O
11
/ I: [) z* @3 u' f0 Z3 c12
) ~- M$ i$ f. G" w, r131 f" H- M( w/ I
8.4.4 格式型函数
. R, d# a1 P4 q. a 格式型函数主要分为两类,第一种是除空型,第二种是填充型。其中,第一类函数一共有三种,它们分别是strip, rstrip, lstrip,分别代表去除两侧空格、右侧空格和左侧空格。这些函数在数据清洗时是有用的,特别是列名含有非法空格的时候。' t- p! W, K# S
9 _# j7 x6 A" f: ^: i$ C* E
my_index = pd.Index([' col1', 'col2 ', ' col3 '])
( k2 J# d8 L4 h2 w0 x/ p
0 K% r9 [7 e' O, `6 X5 o( kmy_index.str.strip().str.len()
" Y$ k; k. M/ Z5 X7 ?8 `6 aOut[100]: Int64Index([4, 4, 4], dtype='int64')
& y: I' A, @$ C0 |! | j; k
9 L7 l Q0 `" S- O! bmy_index.str.rstrip().str.len()" x, @' i/ S: H9 m
Out[101]: Int64Index([5, 4, 5], dtype='int64')
% e$ w: q; ?( B0 A) A. t) P: `+ d! R
my_index.str.lstrip().str.len()
0 p6 f2 q# @9 P: H: u* TOut[102]: Int64Index([4, 5, 5], dtype='int64')! r3 h# b3 L' N7 k
1
, L) C d [' M( W2/ W' j( }; V) b9 T* M
3
$ B7 U M+ F# \4 W4# H* P) C; Z1 c4 _
5
1 h& a% n# s# s& u" T" T! [. p65 \- i c2 r5 C
7
- a9 u" _) H! ^9 o; D3 n) ^% r* U8
4 O! p: Z( o8 ?: Y. k' k5 F# `96 `8 @. S* T& f1 j: [1 w7 s5 R
10
/ A7 ^1 Y; ^2 r( x4 y7 w) {8 B9 Z 对于填充型函数而言,pad是最灵活的,它可以选定字符串长度、填充的方向和填充内容:" h* Z5 @: }, L, ]
$ x4 i' Q. _0 X1 ?$ c% qs = pd.Series(['a','b','c'])
+ X; ]' y1 X% r( f# X( ~- |4 i' r- R" T) F8 d; C
s.str.pad(5,'left','*')1 m2 |5 e8 |0 m0 q4 W7 `& ?/ p
Out[104]: ) B5 {: ?$ `2 |5 c" [& i
0 ****a$ E7 X0 s. }$ a9 b! x/ o) S
1 ****b: p8 V% w8 T0 r9 x
2 ****c
1 u7 }! ~9 k( B5 Q4 Q9 S1 m" Sdtype: object
4 N# p: p9 _. H1 c5 P4 l4 q- N; I& ]6 U, L7 i( E+ D2 s2 I
s.str.pad(5,'right','*')9 \9 @7 d2 }0 e" N
Out[105]: % d3 F; W1 x" b
0 a****
2 ` h1 H8 G8 b2 U5 f( q B9 \1 b****; q5 t* s- |6 m: n5 Q2 Q
2 c****
8 D1 z1 h: X- C# `dtype: object
- k7 S" l% {1 `) }' Q1 h- v* Y9 \( {$ k' G F" i8 Y: I
s.str.pad(5,'both','*')
! a) D4 i7 k& ?8 oOut[106]: / t1 i! X7 Q1 P2 H1 S4 P
0 **a**
4 e: B1 e D# o& y4 {7 t1 **b**2 ]6 u2 q) b9 O7 ?0 K: E
2 **c**5 z+ s% }: N' s, \& d& ~
dtype: object& @% [0 i( y; m5 a9 B
% ?, q8 y! t6 X! | }# l1
8 A3 r8 r }8 a1 a2
* [% }- q! G2 p, \! u7 U4 V3 e2 F34 U2 j4 P& m5 Y4 e
4& p; c+ H4 m* m9 H
5
8 c6 F2 Q* Q) [; R66 {& e" P c+ H- g$ \) Z) Z+ Z
7
9 D% G5 u+ B+ i, S- T" M( [' z8
# k8 o2 ^/ P; o S. l9" F2 ^$ g+ q- U* y8 a2 Q+ Q
100 S% Z3 R9 Z0 y- z& [- }
11' N9 P* T4 ~5 J) _
12
) F5 _" U5 g) e- j+ s, [; a! G13
# h6 t- M: [$ S14/ n# F$ Z, h6 c$ |( s) \/ }
15
* I9 j7 Y3 Q! B) ]3 K16$ V) W! m; c" ~# W
17
+ t* B3 G# I2 t O3 M! b18
$ e% G/ L1 h; ]19
' f6 L* \# `" C7 j. N- i/ u20
* |4 i! v M9 w) r7 g21
% N" F; s4 @" c- G22
/ `5 U v( g; _7 | 上述的三种情况可以分别用rjust, ljust, center来等效完成,需要注意ljust是指右侧填充而不是左侧填充:4 |5 o( c W+ T6 j4 J( r d0 H8 U" \; F
8 C) l# W4 S1 I4 F/ ss.str.rjust(5, '*')7 ^4 b" g3 ?, K: e3 Z% P; X0 K$ A
Out[107]: ( K. _* V2 {) t& R" d
0 ****a) W+ s0 b/ \0 z3 X$ H0 y
1 ****b
$ K9 G6 f$ ?* c6 X- Y0 C- ?5 `0 O5 A2 ****c# a; J( \# \" S/ \
dtype: object1 J" l8 U& g# H+ i# t* M
, t& t4 H4 L( E4 Q; {+ a1 h2 m8 F
s.str.ljust(5, '*')* s3 h$ @ A5 c) a. N1 c4 U
Out[108]: 9 Z% j+ p' ?+ ]# l% y+ m
0 a****/ Z1 ~( u6 f7 |- L' w: i) @) g& ^
1 b****# X% h1 \, X9 a" w/ E5 V: t
2 c****
$ {+ R. _3 d& e# T& z; Z- [dtype: object
. h- ?6 B/ \( B. p$ x- X E f t: C- F6 O6 O7 a7 J. ~' B
s.str.center(5, '*')
( ~& r9 z! F. ]Out[109]:
% n- g3 W' U3 W1 l- M0 **a**
o; O+ ^6 C0 p) }9 H1 **b**
7 O6 j5 o' n1 l& @2 [: ], v2 **c**$ \. W- E9 ?# s8 O$ a q M
dtype: object
8 c# P$ ` L* i p S$ D0 a6 }" j0 P: h, w
15 f0 G `7 r/ N! G( U5 r; m
22 ^4 P/ [9 b! q) M
34 F- n8 }6 u; K, P0 f: t
4* p" J7 \% F0 N4 ?& e% H
50 q$ `$ l4 E0 e8 X5 `
68 t2 r, M; m+ q! J
73 J' N+ b& N! u4 E
8* p/ @2 L o4 ^3 x- F
9
+ [$ e) i8 d5 h6 b10% | z* K8 s |+ z3 m2 S2 E
11
' H2 J" s f( J8 e124 n6 x! n. n6 n8 Z7 v, |2 X1 ^: i
135 u" E, D% W. \. K) b
14! p' c! I" u5 ^6 k6 b
15$ z3 s' Q6 A% B, L( G- x6 k
16
0 Q5 k5 X2 V2 u2 o17) U7 x% J! C) m; y: r* A& K
18
( Q5 t1 O* f2 D1 s* O19
; d! k+ ?# c: d' q B8 x* t/ h202 [1 Y7 P, X) j! o
在读取excel文件时,经常会出现数字前补0的需求,例如证券代码读入的时候会把"000007"作为数值7来处理,pandas中除了可以使用上面的左侧填充函数进行操作之外,还可用zfill来实现。
5 s# r: @' z: @4 R3 n/ U; i7 P% y
& ]3 a* w, ^9 R+ A2 Ms = pd.Series([7, 155, 303000]).astype('string')
* J# ^# M+ `4 ?' D4 v4 P4 s: ^: X/ ~) o, `0 X
s.str.pad(6,'left','0')
+ H1 R7 B& d$ U! h- QOut[111]: 2 B8 Z. f/ h& i, F- L
0 000007
% `5 `1 g% R- T- e( U+ i) o1 000155
1 i2 M, Q, }4 _" c2 3030004 {4 m. x& `8 R6 s9 q
dtype: string
" E2 w" T0 }6 [: s7 }
. i6 L' i- m! B: Is.str.rjust(6,'0')/ V" P7 D/ Q& x+ u _
Out[112]: + ]* v, s8 U) E0 o. P) b
0 000007+ q, }, Q/ L: z& U% K8 g# T
1 000155
0 a9 p2 _- L/ t+ V$ I, N2 303000
% Z: k$ Z& z& E% h2 h: idtype: string ?0 [9 W$ U2 _3 o3 v, y- h
2 }, I& U: m0 J+ U% w4 q- a' hs.str.zfill(6)/ v R- Q. k' ~% P! }7 I8 H
Out[113]:
7 `& f) y9 Q' \* G5 E0 000007
2 F- P9 m' |% g4 Q3 d x% z1 000155- p& n# u7 b# v& d# O
2 303000
9 R0 i& |. X' y: _, Q. Vdtype: string7 w6 X9 c/ C4 A( |7 x/ Q! U
4 \5 h: W! Q6 d! e3 ` V
1, k9 k/ ?' f! f9 H7 j7 v
2! O; |' z% \. z3 `8 T
3% t. m2 w; n: D, g! t
4
" E6 N! ?. D* Q, s4 O5
+ A0 c/ O8 h- G [% ?% ~6
# z% j3 f5 Q4 x4 W1 z) [8 m ]7; B6 t' v; G0 C$ b( F
84 K1 \; M A7 }, Q( v# v
9
7 r6 @. u$ m3 {( [* N Q1 }. Y10
6 [8 G( g9 k# b w11
* \% V3 P' p7 |+ |- [ X5 B12
4 p. q" y+ H, F1 l2 ~13( t6 P/ O0 [( E$ Y7 M: B4 W
149 ^) G' g6 W2 f+ q0 m# K
15
0 x" v* `% b% l5 x16
0 u; a. n7 X3 N$ M. t2 ?17
% P$ k' V& M& ~ r) }3 m4 q18% E/ g$ ]5 _$ N
19; N% Z/ I9 x6 w! O/ |! v
20
9 F7 c& G( A+ y2 G21( X5 ^7 W# y3 g2 W; ?) Z- C
22
, R4 _. a' n, u& Q8.5 练习
% q. A, T- u$ H; j2 e% cEx1:房屋信息数据集
8 z% G# {* r' E* f9 r5 K7 C现有一份房屋信息数据集如下:& T% p4 {1 B/ k& d7 q# Q
0 I0 w0 O2 M9 u; E% t$ B3 S/ qdf = pd.read_excel('../data/house_info.xls', usecols=['floor','year','area','price'])
4 d% }( G( X* H! }: O( G" ~df.head(3)
{7 n4 q8 b) R* G0 G7 KOut[115]: , ~% h8 E$ z9 U* c: |
floor year area price
& [ }9 S. t& x' Q0 高层(共6层) 1986年建 58.23㎡ 155万 x+ [5 k: y5 U5 G
1 中层(共20层) 2020年建 88㎡ 155万
8 J: h) D ^% x1 z. z3 ^3 a2 低层(共28层) 2010年建 89.33㎡ 365万
6 v6 B2 g% J" m4 Q& ?1" G; v; }, [4 x( [8 ]; n! m1 K
2: X% b6 k/ S" L% }2 \8 g( T
34 n3 ?& Q- Z6 Y. g: y& J
4
. C6 j) R. [4 m5 T+ A2 u57 T! E0 n8 Q0 j1 ?1 x$ t$ k0 d
6* j' ~/ }' A% |8 F, b/ [( O
7+ u' K8 C z* Q- W
将year列改为整数年份存储。
: K7 v7 t, m8 t1 i: P( D将floor列替换为Level, Highest两列,其中的元素分别为string类型的层类别(高层、中层、低层)与整数类型的最高层数。
3 ]% \; w; J4 z: g+ T计算房屋每平米的均价avg_price,以***元/平米的格式存储到表中,其中***为整数
5 o+ _( j& B+ r( p将year列改为整数年份存储。$ [# \' a9 H4 y- [, V! d0 i6 ]
"""
Y7 F& ] {+ J% E+ T v& ]* E整个序列需要先转成Nullable类型的String类型,取出年份,再将年份转为Int64类型。; W0 Z* B0 u% T( O
注意,转换的类型是Int64不是int,否则报错。即使astype加参数errors='ignore'跳过缺失值,! [: q5 @2 m2 C& T- w4 T( F
转成int后,序列还有缺失值所以,还是变成了object。8 Z. [# W) o. n" H j; u! s/ z
而整个序列转为Int,就还是Int类型,缺失值变成了 pd.NA 。
3 [! U w8 _8 w* v. C% B# k"""
) d: X* w, v7 \& v( x7 ndf = df.convert_dtypes()2 I9 x& r4 f1 a8 ?9 O: ?1 T% P
df['year']=df['year'].str.replace('\D','',regex=True).astype('Int64')
. V3 x$ V- T; N, F- V# S sdf.loc[df.year.notna()]['year'].head()
" C/ W. V/ B4 d# a
& P! Y8 ^' ^( v' i, \# U0 1986
' o3 D' b. U4 ~* ~1 F; W$ ~# U1 20209 C3 o. |" {% i" r$ ?
2 2010* Q7 Z' S" l! z N; S# f
3 2014
2 D7 a2 V( b; G4 2015
/ m- G! I# k; Q# W) Q6 eName: year, Length: 12850, dtype: Int64 d5 d" @, j2 @' }; _; }
2 |# `! @3 @( M D4 S. b4 E
1! F6 \& c6 x* X* Z5 t( l/ }( W7 r
2
1 |5 N/ Y) j% s2 j8 X+ J3
8 j2 v; d" \2 o! } ]2 h: a ~& {4 A) G* w) I8 [0 [. e
5
' d0 D8 } `0 n# Q. l! p( C60 g5 g$ Q0 [3 V& j
7
( \% G# U. S( G, w% D h81 X, G/ p1 z3 a/ ~) h
9
! V F: M' a( I- Y10! s3 a1 G5 {6 {- a/ d; Z; p
11
# L- o5 R) S3 C; T0 O- W, p: r12* l6 Y0 b1 b1 N( I
13
8 i' x* L. Q& G X) K# M: @! K14
: X- F d4 g& J: J, B9 n4 n153 g8 @; j1 _" w& B# k! q1 I
16
7 l- v, a- ^4 y, _# T. X参考答案:) p' N, n3 Y& A9 M3 @
) ]/ A: M+ i/ M F b$ R( n4 ]不知道为啥pd.to_numeric(df.year.str[:-2],downcast="integer")类型为float32,不应该是整型么( ~4 c5 q/ }; e' X3 |
8 f( h/ [$ Z& a- L( I# G* U& G6 Kdf.year = pd.to_numeric(df.year.str[:-2]).astype('Int64') ) C" ?; A, a9 ^& Q z
df.loc[df.year.notna()]['year']* U0 g* i( U, p
1
+ C" C/ @9 A' z! `/ y2
$ Q% Q- Y' @. M) [5 q+ f# F. V将floor列替换为Level, Highest两列,其中的元素分别为string类型的层类别(高层、中层、低层)与整数类型的最高层数。3 S4 N* D8 V; I, u m) g% K3 _4 K) }
pat = '(?P<Level>\w+层)(?P<Highest>\(\w+层)': ~+ s' ]" O! `8 N: N. v
df2=df['floor'].str.extract(pat) # 拆分成两列,第二列还是(共6层得形式,所以还的替换一次2 |/ T7 O3 G- x) ]/ x* `7 m" j/ u
df=pd.concat([df,df2],axis=1).convert_dtypes() # 新增列拼接在后面,再次转为Nullable类型. u* a9 x. b" R) [) ~" o
df['Highest']=df['Highest'].str.replace('\D+','',regex=True).astype('Int64') . N# o2 F: n6 D9 f
df=df[['Level','Highest','year','area','price']]
8 A; R7 h9 r3 R/ ~df.head()/ Q2 [8 {; ]4 t" `8 R
4 a! `# G" x6 s) |3 n
Level Highest year area price" t+ O) }' A- F6 }' K/ u! A
0 高层 6 1986 58.23㎡ 155万6 N" }+ Q m6 {% n5 ~" H: l
1 中层 20 2020 88㎡ 155万5 X& K. R# X6 w. e6 F' E3 m( P
2 低层 28 2010 89.33㎡ 365万
6 L3 o- J# {3 S1 v9 U3 低层 20 2014 82㎡ 308万, `. p; n- X" `) E
4 高层 1 2015 98㎡ 117万
2 r( M6 W' J0 @$ r) W) f/ I1
8 p; b' E! P0 c* U% x' L2) y& `" }. K4 p. S4 i4 I: K& q, |" @
3- b4 _; `1 X; Z% H
4
( R4 K, W% ]3 {% y5
; h! M! L$ q3 ~6# B+ `2 c7 m% u$ e
75 s; m6 {: r+ a& i: F$ h$ S$ x
8
# _9 e# U6 }6 \, T- }# Z9
% s& E( Z2 k) q7 U103 a7 {0 O+ d: H- J& l
11 m' L! }$ ?' y8 x8 f
12
! W2 f4 {5 p8 z9 M0 C" n4 U13: b5 m# `8 w9 e$ l0 t4 o) f+ t0 v0 i
# 参考答案。感觉是第二个字段加了中文的()可以准备匹配出数字,但是不好直接命令子组了
0 b! O$ `2 J# Y0 _; D. f3 jpat = '(\w层)(共(\d+)层)'* K# d* b3 z: ?' H+ l- h6 h1 Z
new_cols = df.floor.str.extract(pat).rename(& o: l2 K. n) z J' u7 a
columns={0:'Level', 1:'Highest'})
, a8 ]* N" s% Z4 P3 Q. x) u, q' C, q' X: g* ^& u+ _9 G* o3 T
df = pd.concat([df.drop(columns=['floor']), new_cols], 1)1 U# A* v0 Z j! m2 _' v' _
df.head(3)
+ E/ X1 I* D4 e+ Y8 j0 M) K1 [& G; o, r) M, m( _$ B
Out[163]: / p2 i: l" o, h2 m) ]% P
year area price Level Highest
6 J( f7 h+ h0 ?% W) D3 `0 1986 58.23㎡ 155万 高层 6! B0 i& G& Q- p! u! { p4 e
1 2020 88㎡ 155万 中层 20/ c: M1 K! z8 c% H6 ^
2 2010 89.33㎡ 365万 低层 28
A2 t2 L: p) u E8 V- G' j1" |' E* m5 |) d- e( [. g
2( t9 A4 y. H+ P, W3 V
3
5 }0 j5 G: |) K4, j0 ^# C c: \4 Z# e$ B
5
) k1 _# V$ d' K5 z9 x* a6
: P p c9 r0 P: D: x) g7
! P+ Z# k. l0 v3 V1 y, H8 [( ?3 Z7 h' L" R3 v0 [
96 h$ { W3 b" ]! B+ }" _
10- m0 A% Y2 ^, G% A8 S- m
11
# ?+ F" g! `; V0 C4 I12$ w9 F T. q7 @: J. H! x3 G# M# ]
13: D& ^$ O( Z( U5 Z: F) M
计算房屋每平米的均价avg_price,以***元/平米的格式存储到表中,其中***为整数。
* n, h0 b4 U. ^ Z"""# W2 m+ D. p3 {7 d
str.findall返回的结果都是列表,只能用apply取值去掉列表形式3 ~& d+ K! Q7 c; G* t3 N
参考答案用pd.to_numeric(df.area.str[:-1])更简洁7 O- g& v' h* i) v H% R8 y
由于area和price都没有缺失值,所以可以直接转类型$ S3 I/ c6 O' C& ^0 C
"""! ^0 ~6 Z5 B( z* ?
df['new_area']=df['area'].str.findall(r'\d+.\d+|\d+').apply(lambda x:float(x[0]))0 v9 H0 f5 w! h+ w+ j- C4 `7 `# p5 ]
df['new_price']=df['price'].str.replace('\D+','',regex=True).astype('int64')5 b3 J$ O7 o1 Z; I2 S
df.eval('avg_price=10000*new_price/new_area',inplace=True)
% w F+ F& [, F3 Z" K- y7 ^! z+ c) F# 最后均价这一列小数转整型直接用.astype('int')就行,我还准备.apply(lambda x:int(round(x,0)))
$ L. @; U# {% p7 L5 \: g, N# 最后数字+元/平米写法更简单
) K z2 y" s, v0 S9 v! sdf['avg_price']=df['avg_price'].astype('int').astype('string')+'元/平米') m- o0 O7 n1 g9 N
del df['new_area'],df['new_price']
" H% B+ u, j" J" p# `df.head()
* z3 L0 }' Q+ N4 G- L
, O0 Q* v; w1 g/ B Level Highest year area price avg_price
/ j3 t8 ?5 F: p+ t0 高层 6 1986 58.23㎡ 155万 26618元/平米
! V* |. U4 j' l( v1 中层 20 2020 88㎡ 155万 17613元/平米) h) _) A9 _" `/ d8 p! W
2 低层 28 2010 89.33㎡ 365万 40859元/平米
8 O# w K. v" ^7 D3 低层 20 2014 82㎡ 308万 37560元/平米* k! e: t4 V. R2 Z/ _- {
4 高层 1 2015 98㎡ 117万 11938元/平米7 w3 C9 [# p- `" {( E: ^
/ e: L+ \! m, V% V3 }' T+ z( l
16 ~0 ?" \7 M9 M( R1 j# K1 U: d
2
. H! z! u {2 ~4 Y1 J3+ j3 ^% {! P- ~/ [5 d2 i3 o
4
6 j1 ?& M* ?7 w, d$ A( \) u/ j5
& b4 X/ C6 K. e1 \" [6 d$ h6
: q* M4 h, k" c7 G( l; Q" E1 i7
$ p1 |' t- f5 e! y2 ~$ t, A7 z84 k$ d+ I: Z0 S q
9
& K" h5 y6 }8 Z7 @- p& [( V! H9 O: ~10
- s% ? W4 {) J5 f$ K# v I% ]11
9 s; E; S( f4 p& S12
2 k8 a* A' O& k5 E p S/ @13" y. _# h/ Z% ^, N: D/ {- Z7 @$ E
14- B1 \; I! R& `1 t* G$ }/ d
153 U) o/ q5 ~/ B
163 p$ v4 y4 g4 L& S4 x& `4 Z# _9 Q
17, S9 u3 h+ Q* ~8 P( ^7 m
18
5 c* @. }7 Q; ], \* @19* _. _) y& w! O0 D* X# z2 S
20( M7 w8 ~1 ]3 f" R% v9 W3 H9 U
# 参考答案( \* z5 Z9 J! h; l; }- Q9 j8 R
s_area = pd.to_numeric(df.area.str[:-1])2 O; l" r7 x3 c& [1 x
s_price = pd.to_numeric(df.price.str[:-1])
' j& o- ?) h$ T9 edf['avg_price'] = ((s_price/s_area)*10000).astype(# V6 v( ]8 d9 k& z* n
'int').astype('string') + '元/平米'
: |! C& R3 E9 O( @! u' U
( X% A6 r! a f1 P5 U# I- ?" |9 [df.head(3)
3 D- b9 L" k6 A4 b* J# w! E' ZOut[167]:
+ {3 X9 X$ x1 V3 O4 o& K year area price Level Highest avg_price
' K2 t" k e% J! J0 1986 58.23㎡ 155万 高层 6 26618元/平米9 \0 o: M! {3 P# ^9 ]! {& \
1 2020 88㎡ 155万 中层 20 17613元/平米
9 E. ~, z7 @: U' T( h0 G g, A7 ^8 B2 2010 89.33㎡ 365万 低层 28 40859元/平米+ _8 D* V# a/ M& g0 m" |: \
1" X0 ~/ l* u" s. q, ~
2
0 f. w3 N$ ~3 `$ Z& v31 O$ {) K* f' H& }2 ?: [6 T& L# B7 h0 S
4
5 v9 W+ |: r4 z4 U& E" o: U8 L5 X2 b5: m& F( B: m& l9 n/ z$ e' I4 f
6
- o* t5 E: I* \' w; L) A7% k* h5 P5 g$ P9 `% x+ |" r5 ^) l
8
9 d# [; j: R; _" s' R" Z9# q! J9 ~" V3 W5 X7 S0 b# o; u
10' ~, I: _6 F: L$ M
11$ e2 D9 S( R1 V2 K2 N! h7 ^
12
- s* b% h* T" s( X, P1 eEx2:《权力的游戏》剧本数据集
* H* b5 Q# n; |8 w现有一份权力的游戏剧本数据集如下:% A0 C, ~2 P; B, ?/ A
1 L; v# G* ^- q5 {df = pd.read_csv('../data/script.csv'); l' |9 I4 r& _, D+ k
df.head(3): I! Q5 a- v& {! m8 x7 C
; \: \: y9 d6 I4 j+ x/ c
Out[115]: ~; P" H L2 K- ~; a1 v
Out[117]: . j0 N1 G3 {& q" T4 H2 _$ |: f
Release Date Season Episode Episode Title Name Sentence
1 m' |& }6 n+ ^7 O2 Q0 2011-04-17 Season 1 Episode 1 Winter is Coming waymar royce What do you expect? They're savages. One lot s...
7 R& M5 Q7 G! p- d8 ?+ `4 ~: i% v4 h1 2011-04-17 Season 1 Episode 1 Winter is Coming will I've never seen wildlings do a thing like this...2 B) H! p# g5 t4 Z2 @9 q! ]
2 2011-04-17 Season 1 Episode 1 Winter is Coming waymar royce . V, O& V% C" q, ?% [" U8 b
1
5 p+ H' i5 u0 q- H2; e% H% U% Y$ U# w: b" R
3
6 R( Q! J$ r0 i" O4, \" q) q8 E' n
58 U; ]6 M o q, c% U& K- U$ V2 N% e
66 d2 I* R8 o$ z! }
7) g5 a2 x v+ e- t" y) |* y
8: W3 j# S8 D* U( o3 @) D. C
9, u+ c0 M% F- F
计算每一个Episode的台词条数。
5 L, N, i& e8 Z. @2 _' ^, ?以空格为单词的分割符号,请求出单句台词平均单词量最多的前五个人。+ o4 b$ \8 ]) ~" ^0 \* w
若某人的台词中含有问号,那么下一个说台词的人即为回答者。若上一人台词中含有 𝑛 个问号,则认为回答者回答了 𝑛 个问题,请求出回答最多问题的前五个人。
; M4 H" E* o* P7 m& u计算每一个Episode的台词条数。4 t) M3 v. b1 n5 o w5 Y
df.columns =df.columns.str.strip() # 列名中有空格: M2 B% ^9 ]) L& }3 p( G; l
df.groupby(['Season','Episode'])['Sentence'].count().sort_values(ascending=False).head()+ N" Y+ q1 P9 J& e- F- i
% n6 s2 N* f7 G2 }/ l1 u. R% rseason Episode O: S6 G3 B; n: r* w( v( j7 `
Season 7 Episode 5 505+ b9 m1 R8 ]& w" s5 V2 Z
Season 3 Episode 2 480 T" A, o5 s" P1 y
Season 4 Episode 1 475 q* O4 H& U" |+ k
Season 3 Episode 5 440, `5 `4 ] z: a. _+ c( R
Season 2 Episode 2 432/ s9 o- K, M' l) a( o% H) v
10 T8 Z. j; _/ ~3 B
2
8 }8 ^ a, D5 I* j: C2 z$ Z, W3$ S, S; n9 g# V9 _/ I# M, [
4
$ A8 [) q \6 h2 o* P, l5, A# R8 S' b( s5 F2 u
6
& y6 O) k1 q7 }: C7. }9 I8 J( R @0 D; f: q
8
/ f, S* ]! \ w4 B9
+ r x E* o0 k: @以空格为单词的分割符号,请求出单句台词平均单词量最多的前五个人。. o P8 D, ^; _3 N( R) k
# str.count是可以计算每个字符串被正则匹配了多少次,+1就是单词数
% E+ g& X/ s0 Adf['len_words']=df['Sentence'].str.count(r' ')+14 o0 O G, m' k# k3 D0 C
df.groupby(['Name'])['len_words'].mean().sort_values(ascending=False).head()
! A9 W& {& G- Q$ A
9 D- A/ D5 p% E9 Y: XName# Q( Y" Y$ J$ Y4 P9 k
male singer 109.000000
, [& E) s2 I2 W7 M% v+ bslave owner 77.000000+ _8 C) ?, C* v9 l
manderly 62.000000
/ c- n( p, ^4 slollys stokeworth 62.000000
5 d$ a$ a6 T% C f# Ldothraki matron 56.666667
8 j/ n3 e7 o, J$ y$ J d( J$ pName: len_words, dtype: float64
* \6 K7 s5 J( U4 E- A7 Q S8 a9 N1- |, J* B3 ]( z0 q E. k. ?1 g
2* B( W' X1 u- k. z# i _0 T: d, P t/ e
3
C: g* Q! I J% E42 {- _: X7 @4 \5 q; Q' T
5' W' j- n& ?' f: o! g, M$ ?
6
$ n H8 E/ q3 A7 x7
* Y7 Y/ i- K9 \8 _- ]8
) d" j4 d' v% y+ R( @; P. b9
4 }& ^( H I: t, x10
. Y+ V* m3 M& w) \11
9 Q+ h( f2 c! [( v8 T4 n% J若某人的台词中含有问号,那么下一个说台词的人即为回答者。若上一人台词中含有n nn个问号,则认为回答者回答了n nn个问题,请求出回答最多问题的前五个人。
3 G) p" R7 e7 H% x0 K; J- z* Qdf['Sentence'].str.count(r'\?') # 计算每人提问数
1 G/ D! c) _6 u1 }ls=pd.concat([pd.Series(0),ls]).reset_index(drop=True)# 首行填0
, R2 e9 u: v& Z* d2 I4 Odel ls[23911] # 末行删去
3 \5 D: g8 N6 Z$ c$ idf['len_questions']=ls8 F7 n2 f1 r6 t1 [. _( K0 _9 R( h
df.groupby(['Name'])['len_questions'].sum().sort_values(ascending=False).head()
8 t5 R, m$ C3 C
, g2 c! D$ l$ k: `8 L( w0 `Name$ Q* S. a" K3 G$ @/ @! V! a
tyrion lannister 527
! {4 B' d% r3 l/ e% f* Z7 ]& z7 H) hjon snow 374
% @& a5 J: V7 |jaime lannister 2836 `" w+ r. e& e2 r/ @, h5 f: ^
arya stark 265/ p$ N" }8 V3 w1 v- h
cersei lannister 246/ _' G _; a5 A+ ?
Name: len_questions, dtype: int647 w5 z- @0 p) `8 j2 P1 [) \
% {+ H; q% a: {2 Z h# 参考答案7 X0 G0 Z5 l; Y" I" `; Y6 [
s = pd.Series(df.Sentence.values, index=df.Name.shift(-1))
9 G( x; E4 m. p$ _1 n* Q/ j/ Ys.str.count('\?').groupby('Name').sum().sort_values(ascending=False).head()+ U2 e& J" T7 ^9 D9 g! U
2 ]; S9 a5 S: }- [+ f! J7 s1
3 ~ @: f w: L& h% R2
$ C. w, y! ?* p3 y* S3
X- u/ C5 ?* e5 t4 }, S( I1 l9 l! O) M
5
5 P8 S9 U9 i) ]6# i/ D9 ]: j- H8 \$ ]8 q! j0 B
7
7 y( v- N# n6 u84 k: a2 N0 \: S& p) E
9& H2 Q! g3 N3 B
10
) d5 J$ a* h: U r. i3 t11# \6 _; p+ C/ R8 F! u
12: d# r6 c1 Y' I! W
13. u1 T3 c9 Q) `+ T: R6 u
14/ h# B) d4 C0 x2 w/ y6 S
15' L# d1 }, s e R/ l& k1 { r+ g: {
16
7 f7 o& S# O P8 F+ o17
/ A% e- @/ Q4 D3 H& H3 j% f9 x第九章 分类数据
7 _$ N9 u, w+ J) iimport numpy as np! x* V: a, } z! u& r' g5 A7 \- P$ g
import pandas as pd
3 X* `5 a7 L# I6 Q L B# I1. R+ x' I8 K: j/ Y* `( y9 Y9 x+ g
2( X8 b: p3 l4 _% J# f
9.1 cat对象- B8 `* k. e6 `
9.1.1 cat对象的属性
; \9 ]4 e: [7 m+ b8 } 在pandas中提供了category类型,使用户能够处理分类类型的变量,将一个普通序列转换成分类变量可以使用astype方法。8 } T; r: N& N9 Y; f
9 {/ l& d4 Z$ \5 l
df = pd.read_csv('data/learn_pandas.csv',
. K5 P+ v, E5 I- C+ q usecols = ['Grade', 'Name', 'Gender', 'Height', 'Weight'])2 W+ r) d5 w" n
s = df.Grade.astype('category')
' h' O) [9 g. {" |" }/ t1 m/ N/ B& `) k' u2 a+ K2 C
s.head()
: k3 H: a. `% D' w3 k( NOut[5]: 5 s" V8 x( d/ J5 T X
0 Freshman
! c, f7 ?& z7 o r0 [! I1 Freshman
4 x- r: Q& k& r3 C& L: ]2 Senior4 p- e z2 x6 z0 T3 K$ |
3 Sophomore
& H1 x v w x; X f4 Sophomore
4 \0 R, t" s, _7 B4 ?Name: Grade, dtype: category
/ J; D+ v. E* rCategories (4, object): ['Freshman', 'Junior', 'Senior', 'Sophomore']9 ~8 W( \+ }$ z( [& k8 y
1/ R! i$ I& d8 \
2
* y" f0 V) `+ |- Z+ C39 Q1 O8 i& L4 y) ?
4
: Y- Q' Q: [1 b0 E57 `, Q& ~# R. R2 x' R
6
! ~: G. w% t% d. {& F |7
% W' b: D- X9 @+ |5 p8 h3 @: b4 E- D* u
9; C$ F% R1 y# h2 p. J3 Y3 N
105 O% u7 b( W" r7 x
11
! Q& D' m$ L. |0 Q12! S+ M# P+ O' t9 _6 R- x$ `
13
1 T, S2 q- Y0 { 在一个分类类型的Series中定义了cat对象,它和上一章中介绍的str对象类似,定义了一些属性和方法来进行分类类别的操作。1 i: I7 t" u0 U0 }
2 t; k7 m6 }/ z+ Z5 as.cat
. y5 m4 Y; i) l! uOut[6]: <pandas.core.arrays.categorical.CategoricalAccessor object at 0x000002B7974C20A0>
5 s8 B" A R- d3 Y& {1
$ x. |- |3 q3 u. q' x V7 w2
, p& v( H$ K. k7 O( m# h) Z; _% L( Vcat的属性:
4 a: X) n T! f+ a
) J5 N9 c5 _7 K9 p( ~: Q( Y+ L! ?, Xcat.categories:查看类别的本身,它以Index类型存储$ g2 r7 @" J. Z' n
cat.ordered:类别是否有序0 O' H; B* p. V
cat.codes:访问类别编号。每一个序列的类别会被赋予唯一的整数编号,它们的编号取决于cat.categories中的顺序
0 L p& | }% P: S Qs.cat.categories
# R. {4 |! C i+ ^+ kOut[7]: Index(['Freshman', 'Junior', 'Senior', 'Sophomore'], dtype='object')
5 @% I b- w% c" b# `2 p4 n. n7 K5 N( I9 y: |$ d9 O- n. Q
s.cat.ordered: C- d6 Q E$ b3 b& g
Out[8]: False
3 U- w. H! _0 A* y& a. V, Y3 ?, T8 J3 T( P) `' `
s.cat.codes.head()3 r. M5 ]3 h; t. M$ ^7 u* C" @
Out[9]: . D# `) O, F* I6 W
0 06 \0 w7 J$ o1 F0 T, I. J
1 01 M! S% l: u, y, h
2 2 l, [& s7 K) Z/ F+ l% W
3 3: R/ u7 V1 A! }" A3 V5 i% V
4 3, K$ N# o* E" O9 l2 A
dtype: int8
- Q, u& K+ f0 i- q$ E# D1 w5 {# O/ h# A/ v
2
1 M* W/ v# r1 Q5 B. n! p3
/ p# x1 s5 |; r& x$ @! Z4 p3 }4
7 R# j# X$ f7 E/ Q, D- \55 |7 Z4 V- ^8 U) [7 b* Z1 L
6( v+ `$ R) A9 h3 w$ t4 r1 L) h
7
( D' h4 n' {$ A5 {0 Z( r8
D5 J" X7 x5 }. H Z9" ]9 `: g4 p' M9 @
10
+ u i1 v) K/ U G# F11
$ P; b5 T$ y. N. E7 o% s& `, g1 R12
0 R" ^. d6 Z1 e7 x: e/ s! o13! R+ t' e9 Y/ s7 ]; p& B b
14
# d" e- [" u5 a9.1.2 类别的增加、删除和修改
# G' p" V$ F" h; _$ m 通过cat对象的categories属性能够完成对类别的查询,那么应该如何进行“增改查删”的其他三个操作呢?
3 A5 [6 N( }7 z3 i* a9 m9 u. N; {0 [4 \* K. K
【NOTE】类别不得直接修改
. l! W1 u. s* t+ H9 N" G5 U( {在第三章中曾提到,索引 Index 类型是无法用 index_obj[0] = item 来修改的,而 categories 被存储在 Index 中,因此 pandas 在 cat 属性上定义了若干方法来达到相同的目的。2 g3 P7 k+ K- p4 ?' `3 r' F
2 K/ k- ^9 r; q) ]# `0 P7 N
add_categories:增加类别8 j0 w: G9 ?8 j- c+ v" I
s = s.cat.add_categories('Graduate') # 增加一个毕业生类别
$ |0 v8 k6 d s$ L- k/ os.cat.categories
" j' V: f. U- A: L6 q9 X* w% N2 J
Index(['Freshman', 'Junior', 'Senior', 'Sophomore', 'Graduate'], dtype='object')- N* h0 F4 i7 M1 X# H+ t& U
1, X) T/ _1 e% k8 @4 |
2
2 z; R+ ?. n9 }% O0 j( O37 G/ g7 d# e9 M7 p6 Y: G9 @
44 ~" X, `7 _( @. Y3 p
remove_categories:删除类别。同时所有原来序列中的该类会被设置为缺失。
; P; n; [2 W( qs = s.cat.remove_categories('Freshman')+ S6 ^. c( U& h! s& ~: M% F3 n7 A! h3 R
& x6 {' U! w& L* ~; N6 ~
s.cat.categories
! _) S$ C6 P! l$ y5 sOut[13]: Index(['Junior', 'Senior', 'Sophomore', 'Graduate'], dtype='object')+ z0 V* m- A9 C. G$ }: h) r
+ W6 U. s2 p! p/ e
s.head(); [! Z! W# u. e F
Out[14]: 3 F% I R+ d% x, Y
0 NaN
2 X1 ^! w% p! r' S) F( R/ Q1 NaN
1 K* q3 u/ G4 n7 B5 p3 W; i6 a* J2 Senior
, P" n5 o! v3 b3 n6 C3 Sophomore
! z0 A. S& K/ `4 |4 Sophomore, j, _" T: Y1 S% }% P3 O
Name: Grade, dtype: category" G9 N* v2 Y' x% W
Categories (4, object): ['Junior', 'Senior', 'Sophomore', 'Graduate']
) }- M% S% _. e) n, C1& g* a1 S" {' {: r0 M
2
: Z8 ]: a& o% G/ a/ P38 e0 `/ q( v) q
4
9 ]- L" b- |+ o: b( ~5
7 @, t( e& t6 t; v64 g. ^9 y! Q: ]
7/ t/ h) L- a. P
8) w$ U& r7 _: n5 V
90 h. [$ f' P0 T6 ?. T
10
, S4 s9 G8 U1 t6 V7 p4 ]1 v4 V11
% x5 A" h6 O; I6 U7 F1 J& E& J7 n( ~12
/ B9 r6 h R( s, \) w13" k" h- O2 S7 V, U& b
14
& O) ]! v: p# g" U; ~set_categories:直接设置序列的新类别,原来的类别中如果存在元素不属于新类别,那么会被设置为缺失。相当于索引重设。
1 r' ?. p0 B' x8 O* b9 `8 ]7 n3 ys = s.cat.set_categories(['Sophomore','PhD']) # 新类别为大二学生和博士
8 p! G# c6 \5 B6 {s.cat.categories
: }2 K4 T$ s" {8 N3 m: t# A g& `Out[16]: Index(['Sophomore', 'PhD'], dtype='object')
8 b& t0 G' f# }, P' P' \7 s' t
7 U/ j1 _, z( p' hs.head(). G/ J$ R% D W& i2 _
Out[17]: 6 Q/ B) Y/ D( u& J) Y8 A. w
0 NaN
% ?1 A* ? G2 ]9 G1 NaN
h! V2 n Z$ c, E' M# s- y- r2 NaN
: a q" V6 Z5 p3 Sophomore
& c. u$ k& _8 D8 R3 ~- ?" T4 i9 a' S4 Sophomore
! k, \# `5 @5 @Name: Grade, dtype: category
, e$ z2 e) a7 I) t& jCategories (2, object): ['Sophomore', 'PhD']
# |# c4 N& E6 w+ Y! {15 k5 M/ H. n0 m% r
27 H. n N' d+ w0 y
3
. g6 Y& b1 L H$ q" L) I6 D4) z" u/ N3 ?' O, J W
5 W/ ?1 {- D. w* x" `
6
$ i1 i3 }2 S/ n. c; H- C7 Y$ x: F! X* P7
) ]: f, k% {' s8
* h) R6 j3 s5 G# t& q$ _& s3 W9 R9( v; v: g5 X# G; {( p3 L n
10
+ F7 Y" v6 o" _( z% k5 n5 T/ }11
8 f6 D5 P# A$ r, }! H* \$ g" }12) q$ I) p. |# ^7 }2 _( F
13
6 F. H- D7 O1 C3 L G$ Oremove_unused_categories:删除未出现在序列中的类别* p$ i0 W: q" ?
s = s.cat.remove_unused_categories() # 移除了未出现的博士生类别
. u( s0 f) z9 s! S, ns.cat.categories
- D" c' R7 j/ k/ R* \" H# |9 _" y) z+ o: Y
Index(['Sophomore'], dtype='object')+ [+ _5 Z3 ]5 q/ g7 ^2 l# \1 B0 D
1
' G/ h: v4 x' B+ `5 @ O7 l4 Q2
+ E, ?" ]. G( G3% W5 O$ r. k+ K2 \
4
" g ?- M9 v7 m7 trename_categories:修改序列的类别。注意,这个方法会对原序列的对应值也进行相应修改。例如,现在把Sophomore改成中文的本科二年级学生:+ J' _: Y' X5 x" D6 L3 ?
s = s.cat.rename_categories({'Sophomore':'本科二年级学生'})1 @7 s [3 G3 f8 ?
s.head(). F4 Y* l. P. c% J- B s- \3 I
) I) [( v! m }
0 NaN
0 [2 Y% F- A* d' A3 K1 NaN& y! I2 f) ~: V$ A
2 NaN
: f' D- ^- l! p/ C/ C; X: H3 本科二年级学生. d+ A: |+ ~7 d* Q7 S/ J
4 本科二年级学生4 x8 ~" u) I6 t3 U$ p
Name: Grade, dtype: category% B- b& _4 n" V) Z* Q
Categories (1, object): ['本科二年级学生']5 j7 _; O! [' a; j0 o
10 g2 ~4 | g1 @$ ^+ Q
2! }2 _% I( p: p7 ~9 Z
36 F/ t/ p: H' F( S+ n' p# g0 b" C
40 p% W7 m* d% L9 O! x1 V
5
+ I9 [% u, ^, @ W7 F" J6
4 y: N) R8 [$ U! O: ^78 M( }+ K( ^6 ?1 g: n" x
8: w% L/ m& K% V* Y0 I( C0 v( P8 l! F; j
94 {5 z- j. r0 P6 C8 R
10
# \& g3 K4 m- {+ t3 x. |" v4 k9.2 有序分类
5 e" w: q' J3 V/ ~) [9.2.1 序的建立- d- r: K# y! T% y4 u9 A
有序类别和无序类别可以通过as_unordered和reorder_categories互相转化。reorder_categories传入的参数必须是由当前序列的无序类别构成的列表,不能够新增或减少原先的类别,且必须指定参数ordered=True,否则方法无效。例如,对年级高低进行相对大小的类别划分,然后再恢复无序状态:
/ x: X1 o5 u" a0 R5 O8 {8 ~+ W1 w- h
1 Z7 M+ ^! E* ]. c& \1 ls = df.Grade.astype('category')
" }4 `4 M/ S2 u3 P7 m' Fs = s.cat.reorder_categories(['Freshman', 'Sophomore',
+ d" t$ L2 P# W* { 'Junior', 'Senior'],ordered=True)- ]6 J. s0 m' @7 N& M
s.head()
5 q( D0 }7 Q* Q# Q9 R2 n1 E: `- K0 QOut[24]: . p! s6 _! m# f" k) ~. I% Q: d f
0 Freshman
3 |2 s% \; q. ?' ^" q2 Z& z, Z1 Freshman
, d4 c: J+ v- u0 i* M* u6 M2 Senior, e1 n, p' k" `1 ]( m
3 Sophomore
( P" f3 v; w! j- c; t/ ~/ s4 Sophomore
7 P$ N8 O7 x1 d+ n' E7 \ \" [6 bName: Grade, dtype: category
; E* w$ W. U( e) O+ cCategories (4, object): ['Freshman' < 'Sophomore' < 'Junior' < 'Senior']
" N! m6 O( c3 ]* Y' u$ x: y5 i* @9 x& |* J4 {5 W0 O
s.cat.as_unordered().head()1 s W: v: A7 q L' L& q! ]
Out[25]: ; j, q6 p( H, g7 E2 V$ N$ c
0 Freshman, t- @; {" }' G
1 Freshman
4 z: }6 R J. S: @/ }+ W$ {& ?2 Senior
* V. x8 c+ S% w5 O# Y& f3 Sophomore3 f& m' u8 O) f! n; {* v3 r
4 Sophomore1 v! F, y0 B" g) F
Name: Grade, dtype: category6 j) i6 i) k( D
Categories (4, object): ['Freshman', 'Sophomore', 'Junior', 'Senior']
_% P# P! m# P: Y0 ^% g; e% M. m$ N( I! {9 P
1
8 ?; n8 R4 X; o- y28 |; k2 W Y1 p6 W# p1 x$ U1 | i
3
) v7 `; H& C& R, b, F0 D }43 v8 i1 t. k- `
50 @5 `/ j$ O# ]& m. J
62 r& i; D L# X+ i
7
6 s; c6 \- x) z' h B8. A5 f: |8 T. S/ {) ~
9, f6 J/ c2 w0 X0 l; R5 i; j
10
7 J# ]: b' ], S6 i3 U6 Z: C% R116 W7 w* k, A& Y+ s
12
" A: N& ?" c7 Y7 \ k2 |1 E5 Y13! [1 R1 G5 p4 h
14
3 O8 }2 t( f& b. p G5 W0 J15- d! m. l/ F/ @; L2 w: M* x% ~
16
" h$ _/ c' A% n) T17
3 W) q0 F! Q+ j% z1 o3 u5 J18
h4 u# |" d& z- i8 k19
, g, r( @5 ~$ A20
0 t# \) W; T: u( w21
* o- g7 X, L+ Q22- c8 q) G3 n9 ]9 a! g
如果不想指定ordered=True参数,那么可以先用s.cat.as_ordered()转化为有序类别,再利用reorder_categories进行具体的相对大小调整。" M% I" h0 A% ?. Q+ N
( p$ K0 v5 I' C9.2.2 排序和比较
. i3 d2 K. p4 l1 T6 X: w6 ~在第二章中,曾提到了字符串和数值类型序列的排序。前者按照字母顺序排序,后者按照数值大小排序。
( L5 {+ H2 M; N& E) f
( _( F9 K# G5 F! [/ n 分类变量排序,只需把列的类型修改为category后,再赋予相应的大小关系,就能正常地使用sort_index和sort_values。例如,对年级进行排序:. T2 G T1 O7 ~8 G9 Z6 j
+ e! B9 [# |8 Z, K7 w' g1 Ddf.Grade = df.Grade.astype('category')
3 G" L1 |' i# n) E2 _/ Jdf.Grade = df.Grade.cat.reorder_categories(['Freshman', 'Sophomore', 'Junior', 'Senior'],ordered=True)0 o4 c/ v4 U G# L* }- s
df.sort_values('Grade').head() # 值排序/ f9 M6 S* Q1 c+ R
Out[28]: 0 O0 U# ~- q( M
Grade Name Gender Height Weight
2 x. M1 E5 z. U/ u0 X3 a* f) j0 Freshman Gaopeng Yang Female 158.9 46.0- y* c5 i0 s- d$ g# p7 C0 s
105 Freshman Qiang Shi Female 164.5 52.0" {" c- A4 S; m" f
96 Freshman Changmei Feng Female 163.8 56.0
6 ]4 `$ |0 H# u# b- E$ I88 Freshman Xiaopeng Han Female 164.1 53.0( m+ v. V/ w6 X
81 Freshman Yanli Zhang Female 165.1 52.0; K7 [5 D0 L! I, y# m( v2 [
' g* X k9 P* w, \4 a& ddf.set_index('Grade').sort_index().head() # 索引排序
; g& d2 t: b( F4 U9 a% POut[29]: 2 j4 o$ z+ B2 K
Name Gender Height Weight
5 \8 I9 X$ }$ QGrade
- U1 S3 F4 `2 B/ CFreshman Gaopeng Yang Female 158.9 46.0
5 S7 Z, g/ x+ G5 jFreshman Qiang Shi Female 164.5 52.0
; s9 r% e6 C. c/ g& R8 s6 bFreshman Changmei Feng Female 163.8 56.0( {2 U5 k! ~) W) R& a
Freshman Xiaopeng Han Female 164.1 53.0' M, S4 v& X3 `% y4 U* e2 I$ K$ q
Freshman Yanli Zhang Female 165.1 52.0
% ~! D. G& a& q- ?6 d
^( e! q: U: ^7 ]* c" E1
6 y( h8 b6 l, }2 w2
1 ]" O6 }5 ] N39 @% }) Z, o- s' V% `
4. H- r+ \% u1 ], R
5
/ c6 z9 D9 ]# j) A D) ]6
! d' i' q. v0 i4 a) q' [' Y7 _7
2 |6 a$ W6 m2 H2 s& E83 K6 J, u1 O) h+ \# [
9
6 E$ [* G' w! V2 e7 x10 G0 C" [4 f l5 @1 R1 s! x
11: {$ Z) A9 n3 P) q
12
+ Z/ B0 h' e6 B" ?9 L13* v3 t' T7 C0 j1 G
14
% |. l. e+ S, K- w8 S1 g. j: E155 q- C W5 h& v* a
166 j7 ?6 F- Q/ G1 P, d
17
. \* k/ L' T5 e# {! C2 U18" g( e# K+ U; h) [
19 _- L* S* A1 b
207 N- {$ ?9 p8 J
由于序的建立,因此就可以进行比较操作,方便后续索引操作。分类变量的比较操作分为两类:/ k% Y! H- _! H( f6 `! I" K
- y; n/ U8 ^. ^! Y0 Q/ F==或!=关系的比较,比较的对象可以是标量或者同长度的Series(或list)。(无序时也可以比较)
; t) w$ v/ z& H5 m; K7 Q>,>=,<,<=四类大小关系的比较,比较的对象和第一种类似,但是所有参与比较的元素必须属于原序列的categories,同时要和原序列具有相同的索引。' d" C3 `1 `) C) F4 X
res1 = df.Grade == 'Sophomore'! K- b3 j2 t Q4 [6 T) c9 h: t
! d2 t' C6 y4 ]5 q9 }2 e
res1.head()
0 K1 l0 i# i& m& }) m4 ~Out[31]:
0 ^+ T) r& _; J$ Q0 False
! d+ x& v6 u. E+ X1 False5 M3 n5 p- R/ i' _2 s
2 False7 o4 [# X l* `$ y) k) u) H
3 True( ?' h% k8 M% C F
4 True/ @7 T0 h5 _/ N; R1 |) n" t
Name: Grade, dtype: bool" R M: l% V$ I8 `! ~2 p, q
& ]# i6 R0 |3 K
res2 = df.Grade == ['PhD']*df.shape[0]
) W: I/ l: U( \7 p; N* {# o1 {/ E2 |9 \9 U# x; [# G
res2.head()$ ~: @' F f, j" Z" Z" g
Out[33]:
; E0 V; ^) B% `0 False
# z( w5 }/ [- U' I% b+ _( J1 False
4 f2 K( p5 d9 N: y/ s( {8 L9 V2 False6 e. P" T0 C% S# j
3 False" Z: p3 W X" `* _/ x8 t
4 False( H, Q7 i' x7 o$ w+ i1 {
Name: Grade, dtype: bool
! R+ J, I: v( m! O8 Y1 M
5 Y4 b* s% Z) c% S% l4 Ares3 = df.Grade <= 'Sophomore'
2 [- |( ?( ^9 B1 A( t" y( F% I2 N y3 H Z3 W- C
res3.head(): O1 {" E2 d* i0 k, u* i' K
Out[35]: ' v' b5 @( f" u1 D% ^: K1 p! x
0 True
5 l d* N" }* Z* N7 m8 H9 Y7 S1 True
) g0 _3 i4 E4 l) w: `) j+ [2 False
# g1 a9 e& S( a7 o" u' f- v) X8 @2 y3 True
6 S: O G6 J- T9 {+ G3 f4 True% H& j2 P; C! T( I' e% r. w" Z
Name: Grade, dtype: bool. u4 f7 ^$ m3 I2 d" ]: \: x
5 M5 @9 Q% [1 ]( E& R+ h# sample(frac=1)表示将序列随机打乱。打乱之后索引也是乱序的,直接比较会出错,必须重置索引。
# K9 ?0 y4 R! P3 D3 X! B! m- N ~res4 = df.Grade <= df.Grade.sample(frac=1).reset_index(drop=True) 7 r2 `+ }% S. g
- M" ?- w' c# {* c
res4.head(). L% k, g! z; e; _) j
Out[37]: & W% }5 N9 }) ]% ~8 J5 B
0 True
4 _" q% G# O# ^+ o1 True4 ?8 \& }3 _! V- m
2 False
8 M" U- ]- z7 W: ^& Q D3 True7 Y1 B( o+ c: x# o7 O
4 True) N$ q/ C% a* H" m5 l. q( D |
Name: Grade, dtype: bool
0 }. o" q( [) b, n, g! N, |$ n( K! e! K' J" H! ]( n
1
- P. @2 R2 j+ z0 X: } U+ }2
0 |$ s7 o2 g5 ]3" H2 Z( V2 q* o
4 v+ V8 T4 P3 \! e+ q4 \
56 ^/ C, c, z1 h3 \
6( D& Z! g# |+ T+ u
7
# [/ N1 w! _7 R h0 q89 I& d' }8 J% g
99 a$ a, t1 i) ?* \) t; i: i2 z
10
) q( D% E8 r! Y* c* N11
5 g, T: g, ] y8 Q12
9 c! ]/ S: P# ^13
9 c2 `& L) v( l7 J9 h144 L4 K- e; q. F* g7 e- N
15% _4 r4 C; y, s' w8 h
16( @2 K; S# A3 i C9 ^/ g
17
; H9 S$ m* J% a$ W8 W/ E! ?7 }- A18
- ]# m9 B/ U* s3 x19
: c! @' e, B+ P20
1 j% P! J- H) k) B+ p- z4 n ^' L0 V21
: u& x. |: w9 {228 }. q% T/ S& P, R
23
" L+ I/ l3 w, w9 a24
! q- @* u" q! o, G7 _" ^5 B25
7 C6 e, V* ]* d' G" `262 W! I _ s6 V& p' Z/ i, {3 o, C
27
! m" _9 O. r1 J, Q1 ^2 [3 p28
: u3 W Z/ g c2 ?$ d29
. e- M; f( @& W+ \30
5 }6 N7 i3 z U6 H- D7 j1 i311 s( b: l, f* O. P
32
* ]! [/ p) M% v) S" Q5 ~3 g4 n33( M# |! S. @1 C5 H' E
349 n6 J5 R! X1 @0 x1 D5 ~
35
1 x) m9 W' p G8 i# y- V4 v A36
; u, h, i& b7 u0 N" _37
) N, r/ H$ w) |& d8 [38
4 L1 [' i- j7 w. |& a2 i0 A) G39+ N. p/ E; y% P1 `* |& {
40
9 i8 D/ S( N- M+ g7 \414 J9 x, d6 _0 \. x3 h
42+ C2 E" P, v' f. h3 ]" @4 L
43) ~/ H# m$ }2 N$ H: B
44
( C! a. `# }% ?/ D9.3 区间类别( ]# [* S0 h9 l" n" V1 W$ Y1 m
9.3.1 利用cut和qcut进行区间构造
. p& q7 f. L1 B* u0 U) ^ 区间是一种特殊的类别,在实际数据分析中,区间序列往往是通过cut和qcut方法进行构造的,这两个函数能够把原序列的数值特征进行装箱,即用区间位置来代替原来的具体数值。4 V* o" Q& V5 i5 j0 F
9 z P! B( b7 |7 K3 ~5 ?' b
cut函数常用参数有:+ |5 _5 S# J9 C3 j$ h
bins:最重要的参数。
& G- N/ W& S1 ?. u8 |如果传入整数n,则表示把整个传入数组按照最大和最小值等间距地分为n段。默认right=True,即区间是左开右闭,需要在调整时把最小值包含进去。(在pandas中的解决方案是在值最小的区间左端点再减去0.001*(max-min)。)
( U8 [$ |% {3 l5 h' D2 E$ k也可以传入列表,表示按指定区间分割点分割。) F- E& G, e P; ^4 \& o
如果对序列[1,2]划分为2个箱子时,第一个箱子的范围(0.999,1.5],第二个箱子的范围是(1.5,2]。$ j) T9 t( D, e0 ~
如果需要指定区间为左闭右开,需要把right参数设置为False,相应的区间调整方法是在值最大的区间右端点再加上0.001*(max-min)。4 y# K5 n+ }, ^9 j* p( t x$ \
) X3 ^! p+ b: L5 \
s = pd.Series([1,2])
' ]: q+ c9 e# }: e0 G) b: u# bin传入整数; m j' u7 Q8 a5 q7 S
+ ^ [3 ?0 ~' y6 r
pd.cut(s, bins=2); U( _8 c# N& ?$ h( H# O
Out[39]:
, g# `. \, ]( `% ?! \# h2 l0 (0.999, 1.5]
' [/ Y: y" Z% n! l1 (1.5, 2.0]
9 W0 Q& M9 a0 Udtype: category' v# f7 K# D j
Categories (2, interval[float64]): [(0.999, 1.5] < (1.5, 2.0]]
3 {6 Q2 [8 z$ b( J c; k
( I# {) z: r$ Ipd.cut(s, bins=2, right=False)" F6 v# y) w6 B8 w
Out[40]:
- N) m3 |& ]0 h0 B+ f, O+ i( z0 [1.0, 1.5)
7 ~) g$ E: B: F0 R1 [1.5, 2.001)
8 I, L/ ~2 o, d' |" U& a8 U/ Gdtype: category, O' D4 a7 \6 D- m5 A
Categories (2, interval[float64]): [[1.0, 1.5) < [1.5, 2.001)]/ V2 B e, a7 l% m+ u& Q: }) B
2 P, C1 Q* I$ ^9 F5 B z
" |) o/ m7 }" E
# bin传入分割点列表(使用`np.infty`可以表示无穷大):
2 h$ ~! ?- J( ]$ s/ P6 lpd.cut(s, bins=[-np.infty, 1.2, 1.8, 2.2, np.infty])" n9 ~ p* k/ g/ R8 T& W% [' M
Out[41]:
( z- R0 Z2 D3 b0 V# _* W6 u0 (-inf, 1.2]4 M8 _/ V! s1 k$ q9 Z; L
1 (1.8, 2.2]
" @0 y+ j B9 g( Wdtype: category
& v, X$ Y( w# T7 ?! j3 jCategories (4, interval[float64]): [(-inf, 1.2] < (1.2, 1.8] < (1.8, 2.2] < (2.2, inf]]
$ t) [" P! \& D' V2 ?4 [1 ^4 i4 N- S5 m8 ~( X1 P
11 h# M4 Q, q3 |. g! X
2. [8 ^) w; Z/ X3 `
3
( f1 y X5 F; \9 v4
: i& g3 d; T$ J6 H# U8 ~" ?6 e5! I2 o/ t7 t2 i6 R+ b# H
6
+ P" v5 h& x% `% I' q p) U7
5 E- c5 m* o1 _. X% Q: d/ H8
" g. l) w0 ~; r- b9' s7 }% k/ l; t
10
+ S1 e8 [7 Q, O) p11: h% J; w! n. a' Z5 V
12
1 f6 p' d' r4 `- Z139 ^5 f$ D# \' e; G% o6 r8 G
14
9 J. @+ u: ?! b) j- u8 \151 y; F" L" P. f, j& p4 z- f
164 C9 `& g$ R3 ?# x X6 `! J3 @6 k
17
1 B8 `4 q! j3 {3 g5 `, B, M" S18
4 e; S( w# p5 Q! Y+ R4 N8 ~19& H# Y6 E4 s v( j) ?$ E
20
l; D, {2 T. T5 J! W9 c21
g/ V; T0 \. Y' L. Z R22
. ?$ ?. T. k1 C23
% a8 k) {9 s* I! l24* Y9 B! {$ o; K9 K. m8 t6 E
25
5 J& W; J6 n2 q: x, I, ~labels:区间的名字
5 p; g1 A7 k7 \) ~9 u' }/ ?retbins:是否返回分割点(默认不返回)$ p& T5 q8 O" O* z$ t; n: l
默认retbins=Flase时,返回每个元素所属区间的列表( @3 u7 [- }) o
retbins=True时,返回的是元组,两个元素分别是元素所属区间和分割点。所属区间可再次用索引取值
$ a3 w+ s0 K5 L7 C( z% q
* M2 Z; N) ^- x) }- Ps = df.Weight
1 E9 @6 S# z) Vres = pd.cut(s, bins=3, labels=['small', 'mid','big'],retbins=True)
% R! e r: q6 D0 ?/ zres[0][:2]8 i# d/ h+ M4 ]8 C3 F
1 F2 O/ }6 C6 T" X# f TOut[44]: $ Y# x+ w6 i, Z" b
0 small2 w/ A: D: f+ h4 u, g4 U0 H
1 big
% C* U6 _: i. Q) Z/ L. S4 }dtype: category
1 o( E$ }3 i* D$ i; ^5 V# ~Categories (2, object): ['small' < 'big']- `% m& I6 y7 ?7 M+ f# ~1 N
- [) S, D2 }$ Z4 R0 Hres[1] # 该元素为返回的分割点* B) F3 L8 D* U; |$ i# [" n8 |
Out[45]: array([0.999, 1.5 , 2. ])4 N( E4 D4 j' m) L* ?) g& J
1
* A% {0 [0 p9 ^6 V8 C$ w6 b2: y: _) B* z' K0 F
3
- X; P8 }' I+ m; i1 V4' W6 X6 I1 C( W; B; H
52 t. a s. H9 Y$ a- s1 @% C0 M6 @
6
, ^0 m7 E- X- F+ w! g7- x. L: F7 _ d/ e: G+ O/ _4 {4 \
8
0 B* r% d1 d7 F, o: r98 @0 j# e7 `! a+ M
10
" n* R$ l" X% {9 C$ U5 Y2 h* j11
( N5 B" C5 K. h! t12
2 D1 l( H, J; Lqcut函数。其用法cut几乎没有差别,只是把bins参数变成q参数(quantile)。4 p( k; u* w K7 K7 x
q为整数n时,指按照n等分位数把数据分箱
2 f) s0 `$ } X+ Yq为浮点列表时,表示相应的分位数分割点。+ a1 ^) |8 a4 r- d' h
s = df.Weight
7 n+ a6 t; M6 O3 ~# }
) N9 Q! I2 M/ L" Spd.qcut(s, q=3).head()
" {' S4 n4 o. q5 ? jOut[47]: " F2 ]% {" P1 ~1 d' U( x
0 (33.999, 48.0]/ D" R" S. T8 p. K5 ^
1 (55.0, 89.0]
2 E% G/ ~, M9 k' I, A2 (55.0, 89.0]6 N, y5 U3 E {( ]9 g2 l/ J
3 (33.999, 48.0]2 y/ Q8 z, K, w" U
4 (55.0, 89.0]
3 e; K9 d7 u/ Q [0 hName: Weight, dtype: category+ h& b. o, _ [9 E% Z2 n" ]$ z7 O7 }
Categories (3, interval[float64]): [(33.999, 48.0] < (48.0, 55.0] < (55.0, 89.0]]
9 g) M( X* `0 H0 M5 P9 v7 u# s5 M/ V) N$ y6 g
pd.qcut(s, q=[0,0.2,0.8,1]).head()
* }/ g0 ^/ ^$ o U8 R7 xOut[48]:
# H3 E/ F# ^) G8 S$ J* d7 L1 P: _0 v0 (44.0, 69.4]
& }- X" S) R1 K- W: q1 (69.4, 89.0]
& X! u. E( u& @2 (69.4, 89.0]) m- M- n: Q' j& f7 |2 ~
3 (33.999, 44.0] Y7 `; I% v! i$ Q' r
4 (69.4, 89.0]* s2 K% ?( u% O8 x1 B; {3 m2 R; n
Name: Weight, dtype: category
/ m: M. a5 x9 a0 a8 hCategories (3, interval[float64]): [(33.999, 44.0] < (44.0, 69.4] < (69.4, 89.0]]# X0 h) B4 D+ t: f6 e; m7 r
& B4 U$ A' E. X
17 l# T2 u y- @) A/ i2 I
2
8 g4 u! O w* e' }- S4 l3
4 o5 ^ [! i. W7 }8 U& E+ f( F+ }1 T4' J% f+ @3 O1 w+ r
5
' z/ S7 v/ l- M) ?4 A& E. H60 _' f2 a( o# C
7. Y6 r7 w( o: Y" U
82 d- U! I/ n# v+ B
9
9 G, a# i f& E! g/ S0 g0 t9 |10: z* H, {- n. ~* w3 t7 D5 N
11
8 B+ E: J( T+ l1 Q8 Z12
! ~' [% O; {: P! h139 ?* H/ f; b# f6 U, U
14' W7 _8 `9 ?' G7 Z1 `) B/ V+ L
15
( |7 j$ D& S3 \' H3 _6 Y163 P( X a9 n' j G, ^1 K+ D
170 _7 ~! I8 s/ |/ Z4 l' _" a2 N
18# k7 d# G# V8 r" v# [ h/ O
19
7 Q2 @" e- m+ ~) J. P20
% E# {( M8 r" A5 E21
& A3 n9 @% Z* e& y' I9.3.2 一般区间的构造
# e% p8 A! R; T J pandas的单个区间用Interval表示,对于某一个具体的区间而言,其具备三个要素,即左端点、右端点和端点的开闭状态。& \. G! j1 ?- S
9 e# t5 Y* ?- g* K) Q开闭状态:包含四种,即right(左开右闭), left(左闭右开), both(两边都闭), neither(两边都开)。/ b( Z+ M) q" E
my_interval = pd.Interval(0, 1, 'right')
/ ~" \3 B# @- T7 \+ r/ z* S( _4 y ^4 T3 a- V
my_interval
' W1 t2 U: F8 s8 J! ?& o) \Out[50]: Interval(0, 1, closed='right'). F$ R/ O4 A# O* K! D" x
1
) W9 r& x f; }* O6 {4 C2
5 D7 E4 [; r8 ]6 l, R( N. J38 O6 v- t/ Z* }' L7 k- y8 U: J$ s
4
, d c8 e) S# [" y. r6 |& b区间属性:包含left,mid,right,length,closed,,分别表示左中右端点、长度和开闭状态。4 q2 y4 M3 P2 ?, D3 G5 o8 `
使用in可以判断元素是否属于区间: }/ E# D. I& F
用overlaps可以判断两个区间是否有交集:
/ d' d2 J q7 g1 u7 v. S& A% ]0 t ]0.5 in my_interval
- x) C u( v" X/ t ?: J
a: x% t5 W9 A3 _. V! w/ UTrue. Z; ~7 _! `! A
17 d# v- \: k/ {* \
24 i' i- ] Y: s( N' w9 K
3
# D9 @' M, s4 H j% Zmy_interval_2 = pd.Interval(0.5, 1.5, 'left')
( _/ h0 ^/ x% omy_interval.overlaps(my_interval_2)
8 i# l2 E; J% ]( \' }, |3 ^! I$ V5 C' x6 ]" ?
True, y+ B( U/ y; [- g. a/ v2 U
1% \. t0 _' {7 d% X. H
2
$ K D) O( p) h: H3* C" |2 c4 x6 M6 I5 f m0 j5 ]0 u" @" X
4
; @7 y) F- S: S$ p! n% t1 t pd.IntervalIndex对象有四类方法生成,分别是from_breaks, from_arrays, from_tuples, interval_range,它们分别应用于不同的情况:3 I5 j% [& I( }! i# e
4 o8 X2 s' |, q) p( u8 qfrom_breaks:类似于cut或qcut函数,只不过后两个是通过计算得到的分割点,而前者是直接传入自定义的分割点:
" k8 _" a$ u" K( A& _+ F4 ~pd.IntervalIndex.from_breaks([1,3,6,10], closed='both'), P+ \5 v0 v/ [& \' c
# O2 A# j5 ~1 i$ j- r7 `5 Y4 l6 rIntervalIndex([[1, 3], [3, 6], [6, 10]],1 O K! J( w) p. i
closed='both',
; Z* k% ^8 e: b7 I, x! s& M: L dtype='interval[int64]')
4 @: o6 C8 k4 U2 R' u( m. f" j1
7 x- w F& l5 d2
, R e, M% R5 e32 y; }0 Q8 l: k
4
: J; r+ _+ ?$ E( g S7 {5
& I/ k0 s1 H- r" E2 [0 N# tfrom_arrays:分别传入左端点和右端点的列表,适用于有交集并且知道起点和终点的情况:
( C* Z& u; D( H( P8 a; x+ ppd.IntervalIndex.from_arrays(left = [1,3,6,10], right = [5,4,9,11], closed = 'neither')6 C" s( e. {/ w1 Z- K9 P# S
; Q3 u9 F7 x: w* v( Y$ K' E! o# XIntervalIndex([(1, 5), (3, 4), (6, 9), (10, 11)],
6 g5 B9 b9 E( c. s3 W) @ closed='neither',5 P. A ]1 Z5 E0 \$ n2 p
dtype='interval[int64]'); h! D2 W( {& k2 T
16 f# \0 V# a$ a
2
( a5 v2 L6 _, b1 Y: |3; |. }* M9 y" f$ k! F' l
43 K5 J2 p! i7 ^( p' Y) i' @1 @2 Z
56 }/ |' r& H1 \/ R2 c- Y' Y" Z
from_tuples:传入起点和终点元组构成的列表:& w$ }5 [2 v9 L r: B: S# U
pd.IntervalIndex.from_tuples([(1,5),(3,4),(6,9),(10,11)], closed='neither')
# [2 m: Z5 ]. g$ @& [: R: M8 E* n9 Z# L& k
IntervalIndex([(1, 5), (3, 4), (6, 9), (10, 11)],: x1 H$ z" l" W+ ^2 Y5 F5 [
closed='neither',+ d& C4 [$ ~+ b9 [7 S
dtype='interval[int64]')
0 c- L7 _+ A2 G, U1 ~2 B3 c1
0 r% j* m2 s4 H8 R* r2
( i( Z( X6 Z" g# [; c& V3
+ r* p7 t, |/ d% {* h2 C- [48 A7 O: j _0 b6 n0 {+ R
5
7 x/ N8 b Q, Qinterval_range:生成等差区间。其参数有四个:start, end, periods, freq。分别表示等差区间的起点、终点、区间个数和区间长度。其中三个量确定的情况下,剩下一个量就确定了,从而就能构造出相应的区间:" N$ o8 z0 V% g J8 S0 `
pd.interval_range(start=1,end=5,periods=8) # 启起点终点和区间个数
+ C; |; _5 U8 D- c" V& LOut[57]:
) ]% A( {( _; @. C2 uIntervalIndex([(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]],, G2 b% O/ E% ^8 s/ Y/ j
closed='right',
( w# r/ C' a$ f9 k" M# c dtype='interval[float64]')2 g. R* p& P6 K5 f
5 t9 l0 |7 C; j6 B: n3 k1 R' G1 K3 n9 Ypd.interval_range(end=5,periods=8,freq=0.5) # 启起点终点和区间长度! c/ R7 A- p, q: {" q3 s
Out[58]:
5 B( ` F; A: T2 b8 kIntervalIndex([(1.0, 1.5], (1.5, 2.0], (2.0, 2.5], (2.5, 3.0], (3.0, 3.5], (3.5, 4.0], (4.0, 4.5], (4.5, 5.0]],/ }( q) P( N- ]% c6 T$ l
closed='right',! s$ R U2 O- \% N2 k2 G
dtype='interval[float64]')9 m& \* f! B# d5 ~
1
3 k n$ J- ^# Q/ [; G2. w0 V- l: v3 e& E1 c) {
3
3 a, v8 o$ { W4$ G! n1 B; _+ s) z) W
53 A; I; j: o2 E7 f
6
9 r) R0 T) D0 h% X5 c7
3 Q# G0 l+ ` X2 } Y8 x8
* k5 q* d. D% E; A' Y9% E; v# n) [. U: {
10) E: I- c2 h: D0 ?
11
6 o: l% T( J9 v+ U( p) I【练一练】% a" p; d6 E+ X4 ]# {+ i7 w. b
无论是interval_range还是下一章时间序列中的date_range都是给定了等差序列中四要素中的三个,从而确定整个序列。请回顾等差数列中的首项、末项、项数和公差的联系,写出interval_range中四个参数之间的恒等关系。
) ]" N# }# {& H5 J7 P1 K0 d v3 r, K( C2 ?5 G. ?& W1 b
除此之外,如果直接使用pd.IntervalIndex([...], closed=...),把Interval类型的列表组成传入其中转为区间索引,那么所有的区间会被强制转为指定的closed类型,因为pd.IntervalIndex只允许存放同一种开闭区间的Interval对象。
/ A2 n: V/ ^9 }4 M& M8 k# L+ l
my_interval
$ p S; `' N k0 QOut[59]: Interval(0, 1, closed='right')
1 V: l3 G% a: i( [
2 s# T. O9 d* {* a1 } j& M9 Qmy_interval_2
1 ~' n: i* Q8 jOut[60]: Interval(0.5, 1.5, closed='left')4 S K" ~( P; D: q3 L6 H7 a
& H5 A! ?2 H! F l. m# y, B
pd.IntervalIndex([my_interval, my_interval_2], closed='left')8 ]: z* Z* }& A. C2 q; @0 O
Out[61]: 4 P/ E/ \( ]& E! `5 x* K0 I9 {
IntervalIndex([[0.0, 1.0), [0.5, 1.5)],. W7 x. Q$ d" I! x8 {& a
closed='left',* N* |, c" Y. c& T" y
dtype='interval[float64]')
- M/ J- Y% G5 I8 u6 C" M; h6 h18 o" ~/ @# {) j; V
2
6 O* J T f; U2 u3" N1 o" x! ]6 D. D1 q" ~9 k+ N
4
' u, w4 O& w( a6 R5
, ~' c) X. t: [& i; w6
- F7 h* r4 d x" ^% W5 v7
6 W# V' _8 a0 Z& q9 r/ Z( w8+ k# m# W1 ?' c# W9 Y2 D
9
( W0 U# ~" ~) n1 g. V10, N: Y0 L/ z. I3 j; B
11" l: S$ v; v6 g: ?* m3 p& v
9.3.3 区间的属性与方法
3 J7 C) q2 h1 n, O r5 Q6 ] IntervalIndex上也定义了一些有用的属性和方法。同时,如果想要具体利用cut或者qcut的结果进行分析,那么需要先将其转为该种索引类型:* W# g5 H9 i A: z
% ]2 O' h6 Q+ Ys=df.Weight
. ^. K7 Q& t2 f6 V: o }# q- [/ |id_interval = pd.IntervalIndex(pd.cut(s, 3)) # 返回的是每个元素所属区间,用具体数值(x,y]表示
8 p$ R7 `: p) {6 O/ Hid_interval[:3]7 o$ C0 o i: g' a( {- y+ `
3 G" R# Y% b1 zIntervalIndex([(33.945, 52.333], (52.333, 70.667], (70.667, 89.0]],$ o/ s0 Y( U$ J8 Q
closed='right',* S! S4 k, Z" G1 G% ^
name='Weight',
) ^0 f0 d/ e6 d0 I e dtype='interval[float64]')5 S. z) y, f, i5 f- Z9 {
1
) |8 {; e$ ` H2 `( C# Y: M3 n# z: y2
! ^* S- B+ c8 }1 R3
7 {0 R+ E1 M" w* M7 o8 W4
6 E, ^% a4 d4 C* N7 Q" B5
, y5 [& H& g! w5 n6
7 d$ a8 Q/ g4 s3 X7
$ j" L' G2 W8 L+ R' y, _) j8
3 n5 D/ z" s0 f' ?" ^) m6 J( C! _与单个Interval类型相似,IntervalIndex有若干常用属性:left, right, mid, length,分别表示左右端点、两 点均值和区间长度。$ F# L: E* T j& e. b
id_demo = id_interval[:5] # 选出前5个展示0 \2 O1 P5 {/ [0 q3 C V! W F
% c, X6 F* F: f$ {3 B( {id_demo( A" r3 J/ ]/ O7 u+ U; j
Out[64]:
* z; e% f+ t% S9 Q7 g. mIntervalIndex([(33.945, 52.333], (52.333, 70.667], (70.667, 89.0], (33.945, 52.333], (70.667, 89.0]],
6 p3 E& a+ l: a. _ closed='right',
' A1 P7 U [8 O' ^& z6 S$ }7 f# L name='Weight',
+ e& R, a) h- e4 H dtype='interval[float64]')0 P K/ E7 ]( F( V O- i& U
) u6 N. V: i0 R8 \# f0 i2 e+ Oid_demo.left # 获取这五个区间的左端点
$ W9 |& f# v; W8 t8 H j3 N" T. I1 ~Out[65]: Float64Index([33.945, 52.333, 70.667, 33.945, 70.667], dtype='float64')
+ u m6 b- \; M& C0 u3 f7 o: L. \) Q
id_demo.right # 获取这五个区间的右端点
" n- l- E* ]4 ]9 H" iOut[66]: Float64Index([52.333, 70.667, 89.0, 52.333, 89.0], dtype='float64')% r g0 ~8 d7 o! U/ u$ X" `
+ @" X. w3 _- P3 U3 j/ b& ^id_demo.mid
+ j) P8 ?9 V% Y- O! A/ o3 TOut[67]: Float64Index([43.138999999999996, 61.5, 79.8335, 43.138999999999996, 79.8335], dtype='float64')
5 o; q \2 d5 p& v
% o$ v* i) g! B3 D# I( c7 kid_demo.length. r- o r* Y0 m' j+ P& o
Out[68]: / E Q' k. p s0 l5 s) N* m
Float64Index([18.387999999999998, 18.334000000000003, 18.333,6 Q, z, b0 {4 G7 U
18.387999999999998, 18.333],
* m% J6 ?$ W4 W, b# C dtype='float64')
$ [8 p4 `: U6 r
/ r5 y4 V$ P9 t1
1 h3 J! r* {- d% y5 i( v2
8 P5 X/ Q" h# L3 D) [0 Z3* j: O! r. D2 U* o6 g9 b8 b
48 y# s& u# h2 r# H
57 v& d( W! h W$ z# O9 S5 {
6
/ J# [2 d' F3 Z9 f5 N; e w7
/ [: V7 o7 Q# R8 H8" E. P k! }/ l' C" O% m) w
9
3 c# _. C: O; G! M. c4 _ k10
& u; J7 m1 Y- `3 E2 S( y11
0 [. J$ P6 S ]12# R, E2 j' _& E) {4 J$ a ]
13) p/ v& I1 o% H3 }% u/ F2 }0 M
145 n2 |, _- R. k+ o: H
15
" c, ]6 l0 t2 d8 [6 ?( p9 O16. z; [* x# I+ p, S* |5 ^
177 y& p( l5 q9 J
18# o2 f: Y/ f- }% Q5 y2 o
19
! \0 Q$ Z' e/ |+ f8 p" j% F1 `201 l5 p* V+ S0 f0 j
21& N G5 Y2 y# m4 `- X) F9 o
221 O2 e" v8 J* J& V. c8 j; I
23( g! l2 b. `5 B( i$ Q( E4 d1 W8 V
IntervalIndex还有两个常用方法:
5 j5 A" r$ w; e4 bcontains:逐个判断每个区间是否包含某元素$ I2 {, n1 Z r- V+ e, ~1 H5 |' G
overlaps:是否和一个pd.Interval对象有交集。, Z1 [; q. ?# M7 x7 O9 c
id_demo.contains(50)% V# B f! o$ }8 y3 ^ T
Out[69]: array([ True, False, False, True, False])
& O( W8 n4 O ^: {. ?1 N
7 S! t8 b0 \6 \) s2 mid_demo.overlaps(pd.Interval(40,60)), ~6 Y* I$ C2 P1 q' @
Out[70]: array([ True, True, False, True, False]); ]" T8 W* A0 `2 V4 V' H, b
19 L2 o, R$ P9 o3 j! s/ W! O% w
2& ^; T& ]0 f1 l' d
38 A5 |) d2 u \% h
4
$ r7 u8 H: z& {. T5% I$ M% S8 }9 O, ]
9.4 练习 M: K& F$ X0 t/ ]
Ex1: 统计未出现的类别& W3 Q9 o) O3 L2 v% J7 G, g
在第五章中介绍了crosstab函数,在默认参数下它能够对两个列的组合出现的频数进行统计汇总:6 k/ Y/ b9 Z' {7 \% Y! H
8 N8 e9 {/ A* y5 a! H
df = pd.DataFrame({'A':['a','b','c','a'], 'B':['cat','cat','dog','cat']}). m. n* _8 n1 ]* A2 F
pd.crosstab(df.A, df.B)
; ]0 g# }- y# d+ D$ g, S2 Q" c" {( e: I" g/ ?2 W
Out[72]: 7 l2 L6 t1 i# J, @% `; s
B cat dog
1 h" b a: ]# _& OA
, A1 L* T% z: fa 2 0
7 ^+ S. _* w% X8 Pb 1 02 m$ w& K' f4 {% c3 y* o R
c 0 1( n# i( T6 Z7 [; V1 m
1% R# F: A* a1 g& I; e0 v" t6 ^3 I
2
9 v0 z* w7 X* s1 @0 ^" M+ K, \3
) ?2 k' a3 L @9 `$ o# N. i( d48 G$ G+ M3 R; U) X- Y
51 ]4 |2 `4 Z0 ]/ y+ J; f- J) X
6
; O$ {; I( m( Y- z; W7
; ?+ [ w4 Y5 q; r80 Y& p5 `! b, l/ F& h
9
5 p% w, I4 s" K+ q6 P3 ? 但事实上有些列存储的是分类变量,列中并不一定包含所有的类别,此时如果想要对这些未出现的类别在crosstab结果中也进行汇总,则可以指定dropna参数为False:$ @7 f9 Z2 E7 L' F1 \! b
6 R `, K, W4 O8 Q- w9 g, y4 Fdf.B = df.B.astype('category').cat.add_categories('sheep')
6 S e) m$ C$ N0 H* wpd.crosstab(df.A, df.B, dropna=False)1 H& x5 g) i$ P! ?# L G3 Z1 W
4 ~# o3 }9 w0 } I% \, r
Out[74]: 5 b. K8 y9 s9 Q# M$ o
B cat dog sheep
- S9 D; `2 v8 W. i3 M5 V) vA 8 p9 `. _3 c% V0 h; n8 f. ^
a 2 0 0. j* y* _- T- V- n* O$ s4 |* e
b 1 0 0
( O4 [' a K% H7 |* X: f# Sc 0 1 0
2 w7 L8 u- o* R0 W1
( o, E9 [2 ?+ S/ a& A; r5 }; t2, S: Z ?0 r6 q4 P
3- A8 o \: ]# J' O4 I
4# E; F& Z4 u3 w! l( ]) n
5
0 r; ^. v4 F. n7 s6( V* J( U8 v+ t
7
# j3 B6 S& m7 A2 x8 t8
* G% g( _# C4 Q) J9
% ? s* K7 z+ M) M2 m( G& J, U8 M请实现一个带有dropna参数的my_crosstab函数来完成上面的功能。* J# C1 K- T0 G* q; Z+ I
3 J2 h+ e) t4 D D( rEx2: 钻石数据集
8 ^ X t. {6 F( m! P# E; T( C 现有一份关于钻石的数据集,其中carat, cut, clarity, price分别表示克拉重量、切割质量、纯净度和价格,样例如下:0 X* w1 p$ O+ K, ^) n& M( ~% e
n1 i) u0 e8 e+ r3 _% ]df = pd.read_csv('../data/diamonds.csv') " _# O+ P# a* O/ e; p$ M0 N! C. S/ A
df.head(3)
' U8 y+ \* _9 H* b# a
' G8 a( y" Q, W0 [8 _" h$ nOut[76]: 6 {" O3 ?5 A1 X$ E0 W/ v0 P3 h
carat cut clarity price
) v9 m. p4 `* j4 \3 R5 }, G0 0.23 Ideal SI2 326
" P' T5 R+ o+ G1 0.21 Premium SI1 326
# D0 x" ~) Q. D* J2 0.23 Good VS1 327
! Q8 e% A- S7 j) p7 z* @# d. n) G14 i# T, \; j* E$ s* F7 m
23 W. ?: U" m0 ?6 w
3
/ U1 j3 q9 [4 N" y, P4
# B" H# o9 y J1 A$ Y/ _" M8 r9 D& P5
: j8 R6 ?. n+ y- F6 ]* }, B6% O: J% g7 q0 _ O {' {( F* U
70 ]* m9 Q" }0 g
86 A3 ~) W0 Q1 b
分别对df.cut在object类型和category类型下使用nunique函数,并比较它们的性能。
" U' U1 G% U! q; v5 ]; {钻石的切割质量可以分为五个等级,由次到好分别是Fair, Good, Very Good, Premium, Ideal,纯净度有八个等级,由次到好分别是I1, SI2, SI1, VS2, VS1, VVS2, VVS1, IF,请对切割质量按照由好到次的顺序排序,相同切割质量的钻石,按照纯净度进行由次到好的排序。- P: F3 P4 A2 O+ A4 V* e9 {6 e5 w8 ?2 `
分别采用两种不同的方法,把cut, clarity这两列按照由好到次的顺序,映射到从0到n-1的整数,其中n表示类别的个数。8 I) d. v6 G* h7 }1 a P( i# c w
对每克拉的价格分别按照分位数(q=[0.2, 0.4, 0.6, 0.8])与[1000, 3500, 5500, 18000]割点进行分箱得到五个类别Very Low, Low, Mid, High, Very High,并把按这两种分箱方法得到的category序列依次添加到原表中。$ E6 x, Q8 r" }& p f# n% K. N3 T
第4问中按照整数分箱得到的序列中,是否出现了所有的类别?如果存在没有出现的类别请把该类别删除。
" e. u+ t2 I. S对第4问中按照分位数分箱得到的序列,求每个样本对应所在区间的左右端点值和长度。
! f0 I% A7 f" P" T1 [6 |) i0 }先看看数据结构:
7 w- u9 I- N: w4 g4 j, ?* t2 [- p7 r; k; |1 ]% X. K
df.info()/ F* L/ m2 u+ m. j. A7 _# @2 ^
Data columns (total 4 columns):" ^6 ]' W. k& o) }
# Column Non-Null Count Dtype
" q; w1 q: G5 h. o% {; i--- ------ -------------- ----- , D9 [- J( I: x8 m+ f, t; p; E
0 carat 53940 non-null float64
* k; W+ E) `* p 1 cut 53940 non-null object 1 h' \$ f0 u& P8 ^) u% E, [! t8 y
2 clarity 53940 non-null object
" Y4 V7 ]4 }' ], f 3 price 53940 non-null int64 3 W3 p+ x9 I! i- B# K1 f
dtypes: float64(1), int64(1), object(2)
; i% Q: ^+ L. @+ M9 U9 O1
6 i! R% O* ~; i2 b3 b8 d7 @27 V$ l0 M" m! X: |
3
. M9 e2 Q8 A, X0 v# P! u7 p( f* h4
8 s. u8 E- j0 O0 ]) x9 ~5' ] B) }. c1 b* {
6
* Q; @( y6 D3 N/ |74 L1 M( Y- Q4 a8 B$ e. A
8
! a3 h' k# S, o, x' G* f9 [. d( C98 W5 O# r- P1 g% O; u& |3 k
比较两种操作的性能
! b- A: Z- f) {! S8 ^! _' v3 u! G0 M%time df.cut.unique()/ G( S0 K" D D" I
& e4 m0 p+ l( b0 G' H9 X" [Wall time: 5.98 ms/ V! ?9 d- ^+ _9 v/ B
array(['Ideal', 'Premium', 'Good', 'Very Good', 'Fair'], dtype=object)
3 ~* j5 T# y: I1: q$ Q) |+ W9 w% f0 q
2
/ T8 F! U& J, A$ _# i3
4 k2 J+ y6 v6 s+ y( L' T4
, S7 Y" }7 D, c( s! c%time df.cut.astype('category').unique()* Z& H3 w6 s7 B# U ]
. l. z2 E9 g, \) ^) t
Wall time: 8.01 ms # 转换类型加统计类别,一共8ms
7 c# X+ P" j. R0 |1 u['Ideal', 'Premium', 'Good', 'Very Good', 'Fair']1 D2 [& j7 v- O. p+ a
Categories (5, object): ['Ideal', 'Premium', 'Good', 'Very Good', 'Fair']( r* e6 L( W4 `: r* A+ X8 U- }- u
1' T7 y) l# d3 R$ R. y8 Z) v" Q
26 U+ Q, g% R- \: Q+ Q) Q
3
) Y5 V9 m2 s% ^# U/ W; {' V4
1 i3 ^, b; _# _) Q) C: I5
3 m- g+ X* |* a, Wdf.cut=df.cut.astype('category')
: m+ M g/ x$ ?9 l6 R j%time df.cut.unique() # 类别属性统计,2ms8 @3 V: O9 B3 U! g
( _& a' i; w g# a) S3 NWall time: 2 ms
$ ]6 U& }! p3 \( a+ B. s- I2 t& j['Ideal', 'Premium', 'Good', 'Very Good', 'Fair']
* C& U f3 ]3 i2 MCategories (5, object): ['Ideal', 'Premium', 'Good', 'Very Good', 'Fair']
$ Q5 N9 | O6 G8 X# d1
. e5 f) V( [8 }+ O$ ^2
; e( u/ F% k" a+ d5 h7 ] d, z3; Z+ S+ h. E0 y- Y5 V0 o: w
4/ k2 c! b6 L7 H6 l3 t
5- N- t' E5 f- c* {" j5 }( W6 a
6
+ ]: I( ]2 i, _ }对切割质量按照由好到次的顺序排序,相同切割质量的钻石,按照纯净度进行由次到好的排序。
8 H/ S3 F% N: i; |' Lls_cut=['Fair', 'Good', 'Very Good', 'Premium', 'Ideal']
; g, v1 b1 @8 els_clarity=['I1','SI2', 'SI1', 'VS2', 'VS1', 'VVS2', 'VVS1', 'IF']
4 ]$ D# N6 D( k e: tdf.cut=df.cut.astype('category').cat.reorder_categories(ls_cut,ordered=True) # 转换后还是得进行替换6 o1 h4 P( h0 s2 T0 I1 F0 C
df.clarity=df.clarity.astype('category').cat.reorder_categories(ls_clarity,ordered=True): ~! H2 z' m- K1 v( A( x
0 x g1 c; f0 \# \df.sort_values(['cut','clarity'],ascending=[False,True]).head(3)
5 T/ q; }1 u( ]+ W- J' e: W% J& ?" ^; |% s8 F
carat cut clarity price
: @6 g" f8 J7 v315 0.96 Ideal I1 2801, T+ Y% V0 W; t. _
535 0.96 Ideal I1 2826
" N3 V# t: N/ ?7 c2 U$ S+ i9 m551 0.97 Ideal I1 2830
, k4 M* P8 o: n0 @ Z8 ^/ s) t( P19 ~% w9 t7 V' R9 _0 H# ~
28 K9 j4 b; L$ S C4 X( L
3
/ H g% |, t! c" t0 m' W4
" e5 m8 l4 \, H/ v/ N5; f, w& p* f' _1 [) q# d$ ?
6
9 Q- K6 y. e! }+ } r9 e1 q$ ~7
: ]- C. n1 n6 Z- p1 \& l2 o, w4 N8$ b9 ^% i$ S$ D( P0 V
9
: g/ Z s% z5 M) r7 D4 ?105 _; U5 g% c6 V8 R- ^
11* E6 I! l7 z8 m7 g
分别采用两种不同的方法,把 cut, clarity 这两列按照 由好到次 的顺序,映射到从0到n-1的整数,其中n表示类别的个数。2 p# f* V* K$ x. S# \! S
# 第一种是将类别重命名为整数
. H5 W; l9 O5 x* ]* y. \- Mdict1=dict(zip(ls_cut,[x for x in range (4,-1,-1)]))
% \2 N$ ~2 a% k3 T7 sdict2=dict(zip(ls_clarity,[x for x in range (7,-1,-1)]))9 T5 q2 w! J! k: r% f
! x+ S1 j+ {% @0 y3 Hdf.cut=df.cut.cat.rename_categories(dict1)
0 T# K, y5 _2 gdf.clarity=df.clarity.cat.rename_categories(dict2)
h1 m" l% m/ I6 rdf.head(3)
* j- J; f, i" ?. t' ^8 i
. a4 ~( f8 [) h carat cut clarity price* n6 f, s, p8 z4 p& e6 k
0 0.23 0 6 3260 i s4 M; j5 _8 x9 ]% E6 M0 J, _
1 0.21 1 5 3262 ^0 ~% ^# d/ }9 v$ ]+ a5 w0 t
2 0.23 3 3 327' k' x! i: i( ^
1
) j5 q7 Y( _/ T2
: `% o ?( i" h. c/ G: p3/ y2 H7 f& _( ^/ k |4 E- G
48 o! d; {) o A
57 F& ]2 J- m: L( b) u9 b M
69 v* ]1 N2 }1 s
7
7 r- }5 w5 w9 P2 Z, u. u! {8
% I8 c4 P3 M# V4 r9
2 u5 T3 y8 i! D10 G( M r( _) s M0 E2 T I
118 B7 f* l3 R& x
12
- K: P$ B- g- k# 第二种应该是报错object属性,然后直接进行替换
, C* A% E& `' D2 ^. q' Vdf = pd.read_csv('data/diamonds.csv')( L4 `3 j* ^; g0 s5 ~
for i,j in enumerate(ls_cut[::-1]):
4 A9 p" x* T! Y% U6 | df.loc[df.cut==j,'cut']=i ( r8 y2 [3 o9 P5 _, D ^: x
" _$ [5 m7 t" m/ }2 u4 f" `
for k,l in enumerate(ls_clarity[::-1]):
6 g& b7 @) H- s, v3 H df.loc[df.clarity==l,'clarity']=k2 k3 _' U: ]" Y P. J5 H3 C
df.head(3)
2 c* T# t6 X2 Z9 n |
; Y3 u4 J/ U! y$ i* Q carat cut clarity price
6 j* U7 V- g, |: ]2 r( v1 J0 0.23 0 6 3266 P$ X4 J7 b0 y# M; o* A! b
1 0.21 1 5 326- P) w1 _( v \& b9 f- T$ U7 ]3 U* S
2 0.23 3 3 327
' P8 [! N9 u$ f z$ L1. s' ]3 d3 g9 Y* x6 r
2
5 o ]8 ]# b$ V# g# b- ~) n9 t3 m3: v0 I" J% i9 t( _ W" S: g
4: O' n( q5 Y" I: [3 b
5
5 l3 D4 N- k% E9 r6
4 P; F& e) ]/ N6 Q, d3 ]76 n" E# f0 T, [$ R5 n
8# t1 L, ^$ [8 w
9
" N- E$ [6 L. e; K3 @10; Z' {0 G4 N* e% d
11
4 \' o' w" v: A6 B4 [# ~, m! ~12! P, G1 P, c; I1 V7 s5 P& J* C
13% d! f' k0 P% k
对每克拉的价格分别按照分位数(q=[0.2, 0.4, 0.6, 0.8])与[1000, 3500, 5500, 18000]割点进行分箱得到五个类别Very Low, Low, Mid, High, Very High,并把按这两种分箱方法得到的category序列依次添加到原表中。! G, n+ ]. B( a4 s4 M
# retbins=True返回的是元组,第一个才是要的序列,第二个元素是分割点
1 [3 c% [' @" P; `( |avg=df.price/df.carat; }; [2 U) @+ e, ?
) V2 `+ L9 n8 w& J, w- idf['price_quantile']=pd.qcut(avg, q=[0,0.2,0.4,0.6,0.8,1],
. R3 r0 F9 u2 C6 |9 s; n) L* l labels=['Very Low', 'Low', 'Mid', 'High', 'Very High'],retbins=True)[0]. h7 `: g- M8 b e
: n2 |1 }# [+ p1 X
df['price_list']=pd.cut(avg, bins=[-np.infty,1000, 3500, 5500, 18000,np.infty],6 ~# y e. A; l9 {: ~" F$ B* P$ R5 H5 ?
labels=['Very Low', 'Low', 'Mid', 'High', 'Very High'],retbins=True)[0]1 [# X6 K; I' X5 L1 R
df.head()
0 U3 r5 e' r# G+ S9 U; Q
+ L {4 t8 C$ {. i2 o1 t* y8 E carat cut clarity price price_quantile price_list
) l, t; U8 f/ I" B! e0 0.23 0 6 326 Very Low Low
1 G- u2 S9 O0 ~8 b, X1 0.21 1 5 326 Very Low Low
; [" b1 Z8 p! S; f2 R$ C2 0.23 3 3 327 Very Low Low0 V8 I, Z- Q/ j. Z
3 0.29 1 4 334 Very Low Low2 w, O$ ^, w- i* [$ h3 K. i
4 0.31 3 6 335 Very Low Low : m8 H) C7 ]4 s" s q7 y6 ?
5 R% r% Z. o: ~. n& a& P
15 J7 E+ Z2 I$ ]' X& A# X1 {2 ]" p/ P1 z3 n
2
* ]5 A, W+ c7 B( \2 j34 F# l9 `7 @& ^, v! d6 e( a
4
1 v! U Q( m7 |1 U6 z% S5* x' ?" n& S9 z' o7 u
6
* u4 ~( C% [ F2 P! B4 N7! R6 a+ Z' Z2 h* o" b
8+ @2 t3 N P" I" A2 [% y
9
) ^" }& Y- s% `' v5 r10) s6 p8 v( v% {4 K
116 }; r( J1 W! p' o8 ^, R
12, u0 ~' K6 B( D9 p9 a% x4 N" c
139 Y% Y' {/ G- Y) s4 H
14
( u0 D$ S* J9 X" I% }15( [& H2 M+ h! F. s$ T7 L2 U
16
, U1 N# J4 E8 z% R9 G: n分割点分别是:2 h9 V8 \& f# J+ _5 U9 |& E
# ]. Q% I' o/ o# T( `# Iarray([ 1051.16 , 2295. , 3073.29, 4031.68, 5456.34, 17828.84])+ o D7 o; Q1 z9 o
array([ -inf, 1000., 3500., 5500., 18000., inf])
1 X( g1 V/ F! \7 a3 a. w18 T8 [5 a1 N( l% g: f: h, J8 a4 D5 f( [
2* P. m& Z! `) Y" S5 M. \7 N
第4问中按照整数分箱得到的序列中,是否出现了所有的类别?如果存在没有出现的类别请把该类别删除。
* ^& V9 z, u; l6 i7 H6 _df['price_list'].cat.categories # 原先设定的类别数
: L8 H" i( Z) n. b2 `6 HIndex(['Very Low', 'Low', 'Mid', 'High', 'Very High'], dtype='object')2 z8 U$ \" h. c; j( P; J$ i5 O
, j# N2 e$ l1 t7 {' p2 L0 y& Q. ^df['price_list'].cat.remove_unused_categories().cat.categories # 移除未出现的类别
6 i! ?2 x! z; d' B& FIndex(['Low', 'Mid', 'High'], dtype='object') # 首尾两个类别未出现$ h5 S* G4 {* J6 V" _5 S
1
# S# F% E) z' a# O* Y1 H' T2$ q5 D2 m! D9 G# q9 h; M
3/ k9 r7 F, t( ]2 Y
4/ e% z1 w' p2 I2 b' k s" J0 V9 |' `
5+ P' F# @, Z6 o! C" f, K
avg.sort_values() # 可见首尾区间确实是没有的
: ~7 k' n% Z5 [! Q) U) x' s3 m31962 1051.1627919 ]/ P; A* r7 j) n) j/ K5 G
15 1078.125000# @" E! }, x) z4 D$ h
4 1080.645161
/ p3 L$ _; x/ m28285 1109.090909
9 K) b& B. N Q8 p$ v0 H13 1109.677419
3 K6 Q, O, L7 Y/ m# b$ H% I ... $ q+ y, o6 ]& c9 ]; P) Z2 J
26998 16764.705882/ W% Z6 Q: P# F
27457 16928.971963
3 R, ^* q; g% b! o5 ]27226 17077.6699037 N7 f% p% `# [; T
27530 17083.177570+ H3 S8 d5 ~' J' J6 |0 m
27635 17828.846154
9 {$ I1 b- n- F7 R" u4 M" r1
. I3 M0 G) b$ i( O2
! w+ F; F \, q1 P35 k" Y5 `0 a% W9 z" R
4! x) Q7 x& C5 y; S$ B1 x7 A6 t! V
5' T2 v3 X% e* [4 p8 U
6
/ F! [( i' P' L5 t8 `" Q0 a" a' G79 X5 a% R [: \7 P2 B
8
; @& C k- P( M o) l9! f5 M6 o: Z* ?1 a6 ]. \
10
2 U9 F" F1 ^ g5 A' m11
9 l j3 X" r9 v# i125 g& |$ v! L! N
对第4问中按照分位数分箱得到的序列,求每个样本对应所在区间的左右端点值和长度。
0 H! n- ^& o+ `6 b# A# 分割时区间不能有命名,否则字符串传入错误。3 V9 m5 P1 |" Z. Q
id_interval=pd.IntervalIndex(
$ i5 i4 f$ g3 h- W9 W pd.qcut(avg, q=[0,0.2,0.4,0.6,0.8,1],retbins=True)[0]# j" L8 h2 d. q1 E9 _
)4 s9 \; F+ e ?6 ]6 b
id_interval.left
7 H+ N: a2 |' Lid_interval.right3 j4 M! b( H( N9 f; Q# O/ h
id_interval.length
8 q) S4 V- h' j/ A% l" `4 R1
* @+ e; C' S3 U. S( Y/ I2! B, ~% P7 L& N H3 _: w1 x- O
3
& }' M* H. O+ f' q0 o4& M8 S: S% @4 d: ^8 W
5) }/ @# O! G K& g$ E
6
7 H# j7 m9 |' Q- Z+ k" T5 }4 ~7
( J% |* _2 a0 |" ?% i第十章 时序数据
8 E; O; [$ N8 _import numpy as np A) U5 V+ j/ [+ b: l- A
import pandas as pd
9 r9 A1 |1 X% G# B1 a, \19 g# \( X8 S0 ^' g
2
# r: g6 {; r! u+ Y; H3 V. k, k; M' v$ ~7 a
2 D- _- v c) f; ? A) D$ e
10.1 时序中的基本对象; E8 o1 i% z: H* E
时间序列的概念在日常生活中十分常见,但对于一个具体的时序事件而言,可以从多个时间对象的角度来描述。例如2020年9月7日周一早上8点整需要到教室上课,这个课会在当天早上10点结束,其中包含了哪些时间概念?1 s6 D) s4 V# M, _4 L
3 p; [# d$ ^' N1 `会出现时间戳(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的简写。
. I4 a% F: ?% f+ J- M7 a
. ~$ r7 r3 R) \ L/ P6 O" _/ t! c会出现时间差(Time deltas)的概念,即上课需要的时间,两个Timestamp做差就得到了时间差,pandas中利用Timedelta来表示。类似的,一系列的时间差就组成了TimedeltaIndex, 而将它放到Series中后,Series的类型就变为了timedelta64[ns]。' z3 M8 W' z6 L- m/ T
4 Z* q: X5 X& a( P! m会出现时间段(Time spans)的概念,即在8点到10点这个区间都会持续地在上课,在pandas利用Period来表示。类似的,一系列的时间段就组成了PeriodIndex, 而将它放到Series中后,Series的类型就变为了Period。9 `/ G1 I. E- k( m
5 _. R) A" r( C% b9 z: \) h' k
会出现日期偏置(Date offsets)的概念,假设你只知道9月的第一个周一早上8点要去上课,但不知道具体的日期,那么就需要一个类型来处理此类需求。再例如,想要知道2020年9月7日后的第30个工作日是哪一天,那么时间差就解决不了你的问题,从而pandas中的DateOffset就出现了。同时,pandas中没有为一列时间偏置专门设计存储类型,理由也很简单,因为需求比较奇怪,一般来说我们只需要对一批时间特征做一个统一的特殊日期偏置。
+ ?" ]2 ?$ Q! O2 I1 E6 x
4 E! A9 o7 P' _! q% j 通过这个简单的例子,就能够容易地总结出官方文档中的这个表格:5 _+ \. P( n* m8 w, U
i5 ?4 }8 T' X5 D概念 单元素类型 数组类型 pandas数据类型
6 l. N; q8 J6 w1 I$ D7 c7 t+ gDate times Timestamp DatetimeIndex datetime64[ns]
& i, Y W) h, K* T6 J( c/ T: {Time deltas Timedelta TimedeltaIndex timedelta64[ns]
3 ^% A2 m! Z! X. n* a0 A2 nTime spans Period PeriodIndex period[freq]
- h, ~. M$ }$ x3 e8 `4 e& tDate offsets DateOffset None None
" y; d4 A/ N" ]; p 由于时间段对象Period/PeriodIndex的使用频率并不高,因此将不进行讲解,而只涉及时间戳序列、时间差序列和日期偏置的相关内容。
& M' f" M! t( {# ]# `5 X: H& \/ g. ?. z. Y: o/ V
10.2 时间戳
+ V, j7 b V, i7 J10.2.1 Timestamp的构造与属性% c, W, i- I b
单个时间戳的生成利用pd.Timestamp实现,一般而言的常见日期格式都能被成功地转换:( e, m$ A5 X b9 u
# p* m* b0 o ?4 D
ts = pd.Timestamp('2020/1/1')" S' p$ V" d1 ]6 C+ z
, Y9 H% K$ H. z/ r8 T; ^+ i
ts, g5 S' S, N# V8 L- ?. @; ^
Out[4]: Timestamp('2020-01-01 00:00:00'). }- g0 u% ^" ~! H1 `9 B+ C
, d+ d, Q9 C7 l( F# bts = pd.Timestamp('2020-1-1 08:10:30')
+ a* R# ]5 n* _4 S; ?; b/ {) ~( t J8 J' X
ts
/ _, k4 e1 s O- F4 }Out[6]: Timestamp('2020-01-01 08:10:30')
/ x* F2 |( |: e" L1" G C8 B+ Q2 n: P# M
2
. U, E7 y7 [! ~& |3
) g f5 j. T1 R4
1 n" l* B1 k- } P8 e- n" W* N4 ?2 r7 q5
: U( Q% Z2 T. y& G: B6! S( y# ~( @" B1 W1 S0 k
7. w8 G6 n8 v, N) T; G
8
. w4 \/ l+ C0 u$ L% m) G" r& B9
% e" e$ n8 v4 d1 X7 m通过year, month, day, hour, min, second可以获取具体的数值:$ D- q# s3 `0 q D" o
/ y7 ?& S9 U7 @; y: v0 _! Ots.year
9 [/ Z2 D4 o; }% ZOut[7]: 2020
/ k6 q' W; n, c- @& i* g8 Y% G4 @- e/ V$ ~. b4 V w, z
ts.month( o8 { e0 V0 `1 C+ |) n
Out[8]: 15 N: F8 b! \/ _+ s9 F2 {4 I0 J
1 \% a* k8 V" ]ts.day
$ i1 B+ }7 j2 b! o0 d: G8 @Out[9]: 1/ z" K) y, W; {7 B
. O' i3 L8 q5 V9 T J5 S0 ~" ~ts.hour
# M u" R: D# q5 \5 IOut[10]: 8
# G, t, s) h5 L8 h# v( e, F1 S) i
0 \- C) h5 d' ?$ pts.minute, U3 O C; s7 M0 ?: z/ I
Out[11]: 104 J- V8 R: f; a5 x% }- g
( l+ z4 e' ?$ j7 R5 A5 N0 J+ l. _ts.second
6 E( D5 E8 t+ j5 t% LOut[12]: 30* y/ }( h. x1 ^6 h- x
2 U1 U+ B3 m, K2 U2 T5 z7 W' R |
1 d6 p3 K7 m, `% @$ z" x
2$ l: ?0 Y p s! t+ |( H! T
3
/ R6 q \% M7 C# L U, g+ ~47 i! x1 Y/ h' o/ B" Z7 ^
5& q( |1 p2 o. d. _. v" J7 v! `
6( N, [6 }' E. M& _ R4 v
7
1 b; Q6 g2 j4 s' y0 q1 H+ ~8
! f8 ?( b; v. U9& D8 S0 q1 X; r/ F
10
7 L3 N% }- w9 P& E1 \119 m2 s% {( C# O$ `0 B" @# n$ k S3 c, |
12% S8 a! K6 t9 S3 W
131 M8 q. S6 s. Y/ \3 N7 Y2 f
14 z) U. W% j- P% ]8 }/ z
15
9 Q' f: X( M* T16
3 b& i6 b# J( F179 B3 _) G# F/ r* R7 K, _* j5 G
# 获取当前时间5 ]' j0 h# X' r/ q) ?' I
now=pd.Timestamp.now()3 T) D& R: H# T, e- E) C
1
/ Z- B& C1 v8 }, [5 u% _. h: }2, k, Y2 S( D+ D
在pandas中,时间戳的最小精度为纳秒ns,由于使用了64位存储,可以表示的时间范围大约可以如下计算:
1 s3 g+ ~: c/ mT 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)
4 j# N; W8 ]* S5 DTimeRange= 5 j0 e, M7 c) g# a# E6 e' Z" ?
10 4 G- [8 ~/ ~5 n' z+ ?- K" B# y
9! j1 h# ^, k& a9 R( q
×60×60×24×3650 i$ U% N/ o7 E
2 " x0 b7 F' h* c, q# L
64
1 J$ s' [8 c/ m6 _4 K: W8 N/ U3 F @9 @0 V% L8 {) z) R
) p- L7 P3 P8 ]
≈585(Years)0 w+ @3 E. E! t
, k# n& e3 f: t: t n4 I v通过pd.Timestamp.max和pd.Timestamp.min可以获取时间戳表示的范围,可以看到确实表示的区间年数大小正如上述计算结果:
4 J% @7 Q. F5 |: |% m. r# P& D/ T) \; B; W/ ~: P( a
pd.Timestamp.max
2 Q$ A2 g! j8 Y. v# \% O2 f, cOut[13]: Timestamp('2262-04-11 23:47:16.854775807'): O# g E& O" M/ G; C! r
: \+ T. R7 ]0 g" W. a* b
pd.Timestamp.min* \' F( R& ~8 h+ l6 o
Out[14]: Timestamp('1677-09-21 00:12:43.145225')
9 t: e) l1 E; |4 P; G3 U
2 ~8 c% _7 r0 Npd.Timestamp.max.year - pd.Timestamp.min.year; C( B3 A' w; Y( U. k7 M8 z8 c
Out[15]: 585
9 D9 F9 J; `( ?- O- j3 R* @; f1
4 k( p/ f2 n E" [; N) \3 {; q29 x2 V u; X, x l6 A1 ?+ b
3
! P- v4 q# C# }/ g4
+ B+ n8 {$ s+ \% v5 d5
! S a* u% s) Z9 Q6
* Y1 a. S! ~2 ?, S& s/ R+ V7
, q' n9 u1 X4 M- m- v8+ `% V6 Q8 c: Y
10.2.2 Datetime序列的生成
. @* ?0 U) v3 J. ?- K% L. M9 u0 W Vpandas.to_datetime(arg, errors='raise', dayfirst=False, yearfirst=False, utc=None, format=None," r# [* R1 M5 \/ l7 ~9 U7 I, z
exact=True, unit=None, infer_datetime_format=False, origin='unix', cache=True)3 O+ z" ~( W$ N0 Q9 H
1
- P9 d( F4 q \; L+ A( S2
7 L1 j+ F2 q \, N! N; n1 Y% epandas.to_datetime将arg转换为日期时间。! \5 ?. t% w6 O; i# b6 R8 P
# K. H& _+ _+ E8 D2 Warg:可以是argint、float、str、datetime、list、tuple、一维数组、Series、DataFrame/dict-like等要转换为日期时间的对象。如果提供了 DataFrame,则该方法至少需要以下列:“年”、“月”、“日”。
& F2 x1 {" |7 m# l W* _0 ^errors:
8 N& \5 F5 h8 h4 a: f7 L8 a! f- ‘raise’:默认值,无效解析将引发异常
" d! @1 Q4 n) ]6 l+ B7 Z' s0 F. `- ‘raise’:无效解析将返回输入/ S1 s7 R D5 {4 Z" o
- ‘coerce’:无效解析将被设置为NaT' z+ `- }% ^6 b9 l( I! ?# E
dayfirst:如果 arg 是 str 或类似列表时使用,表示日期解析顺序,默认为 False。如果为 True,则首先解析日期,例如“10/11/12”被解析为 2012-11-10。如果无法根据给定的 dayfirst 选项解析分隔日期字符串,会显示警告。, q5 E1 E4 w, l. w$ O7 l% y
yearfirst:如果 arg 是 str 或类似列表时使用,表示日期解析顺序,默认为 False。如果为 True,则首先解析年份,例如“10/11/12”被解析为2010-11-12。无法正确解析时会显示警告。(如果 dayfirst 和 yearfirst 都为 True,则 yearfirst 优先(与 dateutil 相同)。)
8 {6 m: q3 r. O9 uutcbool:默认None,控制时区相关的解析、本地化和转换。请参阅:pandas 有关时区转换和本地化的一般文档4 J' @# A0 c3 x
format:str格式,默认None。时间戳的格式不满足转换时,可以强制使用format进行匹配。
% u+ o6 [8 ?2 m9 u$ g% Q; Ounitstr:默认“ns”。它是arg (D,s,ms,us,ns) 的表示单位,可以是整数或浮点数。这将基于原点。例如,使用 unit=‘ms’ 和 origin=‘unix’ (默认值),这将计算到 unix 开始的毫秒数。2 `' [ M& c; Z; @* T
to_datetime能够把一列时间戳格式的对象转换成为datetime64[ns]类型的时间序列:
9 h6 y# x, H; _6 E' U+ N2 [# `* Tpd.to_datetime(['2020-1-1', '2020-1-3', '2020-1-6']), S' m" g. V9 A' T8 N# d9 M, |
( e: W. n0 |' Y9 S
DatetimeIndex(['2020-01-01', '2020-01-03', '2020-01-06'], dtype='datetime64[ns]', freq=None)# w8 _3 v& R- y
1 u' F! J$ d0 Z1 |! T7 a5 w) p
2( }+ l# e1 b8 D4 L2 j- {9 `' `' _7 g0 c
3# a: b4 M/ v! M$ w
在极少数情况,时间戳的格式不满足转换时,可以强制使用format进行匹配:" B' g' w' Z+ G- O) X
n& ~3 y4 @3 |% M
temp = pd.to_datetime(['2020\\1\\1','2020\\1\\3'],format='%Y\\%m\\%d')& a: N8 v4 j8 O! T; l
temp- V8 A8 ^# t$ |! t9 |2 p
3 y3 X8 e/ I, t: G
DatetimeIndex(['2020-01-01', '2020-01-03'], dtype='datetime64[ns]', freq=None)8 |4 @ `" R1 o- g/ Q5 _9 r
1/ X/ ^% x6 F$ B3 y5 x; z+ H
28 c+ V: ^6 A6 Q) d
3- g' a2 u2 T1 z3 ^5 l; h" d
4% v( g0 ~' W$ c5 S' l/ O- s0 }
注意上面由于传入的是列表,而非pandas内部的Series,因此返回的是DatetimeIndex,如果想要转为datetime64[ns]的序列,需要显式用Series转化:
1 \, ~2 b0 A' D: R Z
! n+ @* V; @4 wpd.Series(temp).head()
7 |! s/ l% S5 u3 X% s8 Z9 K' q; |0 h7 Z# Q
0 2020-01-019 n8 v0 I8 _, F$ D
1 2020-01-03. m% ]6 s. K c* V7 S( Q! ?. k
dtype: datetime64[ns]1 K. e& Q* I9 z$ }
13 B2 r* R/ _9 J8 c- ^6 P
2
( a- D% X" Z2 T# w s4 Y3
3 N, o8 v8 {: b! N2 `41 u5 c6 o$ b4 K
5" E% a- I0 o8 Q. H
下面的序列本身就是Series,所以不需要再转化。
' |, ?, m0 t. y" l; e( A
e1 C5 N2 C' f2 z# L odf = pd.read_csv('../data/learn_pandas.csv')2 g$ E$ x4 e( ^& C9 V& K
s = pd.to_datetime(df.Test_Date)+ O. y8 y' l: Q, I! B
s.head()
7 p6 T2 ]/ a/ Q8 X2 y
6 g& f3 k1 f6 q, J2 x1 y$ b3 Z0 2019-10-059 g# O! C/ U# d: M( a! _+ z/ X* r
1 2019-09-04
/ Z" I: B! O: T( c2 2019-09-12" u- f; v3 o; j1 A8 ^ S! ^! p
3 2020-01-03
; e3 L& i I0 L4 2019-11-06; @ m1 Q4 F9 _. E
Name: Test_Date, dtype: datetime64[ns]
/ }* A0 c9 V# X; W1
6 s% w5 ?1 C4 `/ Y; A+ y2
$ X. G6 G/ N0 k4 r( D3' U6 \, t% b, U- j/ D! Z. w$ c; B8 v" M' ~
4( K7 o9 C) }# m. {2 ~
53 z) y1 D+ d) e2 @0 D$ |$ N) c
65 m& g) o; Z7 r# N1 D
7+ ]4 D1 k. t( j" S* S) J
8" `* {6 Y. B3 k2 ~
9& `" D# Q M, d& X# T3 w
10
V' }- o7 A) a2 d: T4 l把表的多列时间属性拼接转为时间序列的to_datetime,此时的列名必须和以下给定的时间关键词列名一致:7 y- x3 s/ `; Y+ y8 V
df_date_cols = pd.DataFrame({'year': [2020, 2020],
% A x" T1 P8 I' w 'month': [1, 1],
( O: g* C/ i1 }& I8 N6 X 'day': [1, 2],& w0 E' G* ~' \; w0 u' D
'hour': [10, 20],
) U; f7 m" ]- E: H 'minute': [30, 50],
( L w; ]! G$ }% `# ^ 'second': [20, 40]})$ W" y1 o. F% I2 I
pd.to_datetime(df_date_cols)
+ ^$ p- l5 t- W5 u, R2 d. u* n! G
$ }& |& j: [$ l2 r: e" R0 2020-01-01 10:30:20& i% G# ?" L/ c. m6 B5 @6 I
1 2020-01-02 20:50:40
`; t- v1 B1 A4 o9 bdtype: datetime64[ns]9 G$ U' K! Q5 t3 S6 q6 V, B t
12 z( H! L: n, f6 g
2% L" `# Z$ @3 F7 v0 m7 F9 v4 e( M8 }
3, g* t8 D4 X2 n/ F }
4
% w' K& G: u/ H# {, F8 c9 H! F. |( l0 m5
/ |3 e2 ~+ Y: T# Q; }6$ K) {8 Z& v1 w8 l- O' J
7" U, a$ M7 E% J$ u
8
/ T( J' L0 j- P6 r9
) y; T; e1 M0 P. p" G# A8 B' H10
* u3 U5 z$ s% v7 p6 _1 j$ u118 C$ S P# i$ t" F Z: I Z8 ^
date_range是一种生成连续间隔时间的一种方法,其重要的参数为start, end, freq, periods,它们分别表示开始时间,结束时间,时间间隔,时间戳个数。其中,四个中的三个参数决定了,那么剩下的一个就随之确定了。这里要注意,开始或结束日期如果作为端点则它会被包含:
6 @. B" \+ F7 Z) gpd.date_range('2020-1-1','2020-1-21', freq='10D') # 包含$ U* `8 I. \( Y; O) m
Out[25]: DatetimeIndex(['2020-01-01', '2020-01-11', '2020-01-21'], dtype='datetime64[ns]', freq='10D')
9 t0 M( X( W/ C9 g, U3 o9 }! a3 Q3 a/ r3 f& ~. j5 j( i* }: n5 @
pd.date_range('2020-1-1','2020-2-28', freq='10D')4 O8 v" I5 b! F! _+ O* M' J
Out[26]:
5 {; S/ B) X: Z) QDatetimeIndex(['2020-01-01', '2020-01-11', '2020-01-21', '2020-01-31',
- M( E; Z5 j+ C* P '2020-02-10', '2020-02-20'],
% @$ c! @6 t: ^: `% ^& V a/ P! n dtype='datetime64[ns]', freq='10D')
* E" L- U+ |3 q) h3 z1 K# R% [' R. Y1 q/ F& I: ` P2 K
pd.date_range('2020-1-1',
( r# l F# I9 j: }* i0 }$ n '2020-2-28', periods=6) # 由于结束日期无法取到,freq不为10天
1 g# k( D7 K" U' s J, A4 |% o {4 g) N1 ^- d3 V2 N9 A" g! Z' `
Out[27]: & a( u, h* _. q2 @7 E
DatetimeIndex(['2020-01-01 00:00:00', '2020-01-12 14:24:00',- ~, M3 k' f9 o2 q& E+ V, L
'2020-01-24 04:48:00', '2020-02-04 19:12:00',
- R# s4 C; w8 d/ I$ H& E '2020-02-16 09:36:00', '2020-02-28 00:00:00'],0 z1 b1 l/ X/ ~* V. P
dtype='datetime64[ns]', freq=None)
" M+ I; }2 ?6 {9 Q1 q; v1 w ^4 h; C! l
5 M! q; ` k+ ^1
* x0 w7 q8 b7 r6 q9 b) Z2
& p4 e4 u; v9 t, y# g) ^3
" v- m( G) x6 ?% Z% j( {$ r4
) |; s& S" l5 Q+ l, d5
1 Y' x0 j, Y2 O69 J( }8 N) Z5 B) D# D* f
7, u- H" U6 k2 V$ m, Y3 R+ F
8' f. s4 D7 f" g H! J% H
9, ^; |' u v7 n; v) O
10) O$ t& K0 Y7 r3 V4 X) ?4 J; m+ q1 O
119 u5 Z0 y7 A: l
12- D2 F: \* v6 L* u
13
! r" Q6 p. M. {2 o3 t% e% z l14
1 w Y! M" _& z# r; S15
! w) Y+ G* c( v+ l# w+ \- R161 ?6 d6 F% X( z. O% \! c& r
17& S! p, [% u& I7 e2 X/ f- p3 N
这里的freq参数与DateOffset对象紧密相关,将在第四节介绍其具体的用法。
) P9 G9 Q1 n8 n5 Y9 p- L1 ?: G3 i3 x6 g
. ]& \; C6 k0 {0 `【练一练】6 y" @, I, Q9 |$ E% c7 B! s
Timestamp上定义了一个value属性,其返回的整数值代表了从1970年1月1日零点到给定时间戳相差的纳秒数,请利用这个属性构造一个随机生成给定日期区间内日期序列的函数。 K- s0 }8 Z3 E, c/ Z# U/ q/ ~
4 t7 X2 r- W7 c) v; M& u
ls=['2020-01-01','2020-02-20']
. D' c- `; c$ [- H. J y2 b* Ddef dates(ls,n):
" e0 e! Q' r2 w- I. ?+ A) O min=pd.Timestamp(ls[0]).value/10**9
& z R; Z% [# Y M3 r9 ]# ~ max=pd.Timestamp(ls[1]).value/10**9
4 M% ]$ b( }' u+ u5 y. {& O$ B Y4 F, z times=np.random.randint(min,max+1,n), j+ ?6 B* t0 X6 y
return pd.to_datetime(times,unit='s')# R3 y/ w; q; B' _- r. y
dates(ls,10) 2 {& j3 s$ U v4 D; A8 I* X8 ^
4 j- G6 G* [5 {" k
DatetimeIndex(['2020-02-16 09:25:30', '2020-01-29 07:00:04',
+ F( P( @0 ]: Q8 _7 {0 Y '2020-01-21 12:26:02', '2020-02-08 20:34:08',
% n& B; S: m- X! U; H6 }' g '2020-02-15 00:18:33', '2020-02-11 02:18:07',
. }2 w9 Z% R' r/ i '2020-01-12 21:48:59', '2020-01-12 00:39:24',
9 \' a* | ?& _ '2020-02-14 20:55:20', '2020-01-26 15:44:13'],4 V+ Y7 T1 E7 ` y6 N& H5 l7 C' w
dtype='datetime64[ns]', freq=None). Z/ O7 C N( h+ F& k) X3 s
1
) \+ N9 T; R0 e/ R9 O# H2
" ]1 u! |& w% q; ~3
5 m# B K, n$ B- G8 A4
/ a, n' X/ [0 y' H' \. q" d5# g2 [# F9 O+ b; _. z0 H
60 @# R' c, Y) Z
7& ~; ]5 N. i0 u4 i8 F
8
W. @! {3 K- m; Z9% a3 J1 d) |% a0 V7 M* {
10, b, ?, p, c! @' X6 d F
11. Q+ d& m/ q0 c
124 Q) N6 i: G+ P- I6 \
13' G0 l3 z+ e9 ^7 e, Z& j& b2 g( f$ I
14
' F8 D) F3 ]. S( p2 `$ k; G$ Yasfreq:改变序列采样频率的方法,能够根据给定的freq对序列进行类似于reindex的操作:! R* M0 t" [, `8 q2 g; Z
s = pd.Series(np.random.rand(5),
: H* c( v7 \, d6 j. O5 b7 v index=pd.to_datetime([
. b/ ^* @0 e' b) U' O' J '2020-1-%d'%i for i in range(1,10,2)])), x2 I, r0 q( J$ z+ i
. T: O; x2 `( M. F" q9 S3 X0 {5 r; i- S; W) i7 {
s.head()" q8 m* N k- U' `
Out[29]: 8 X7 R& b& j5 z5 d' O: }
2020-01-01 0.836578
! J- z$ Q5 Y& e2020-01-03 0.678419
% U* ~+ q* \1 I3 @7 D2020-01-05 0.711897
4 G+ ~; G- c0 u5 K! p9 t4 n& \2020-01-07 0.487429
; a8 X$ b0 M+ k% \2 U2020-01-09 0.604705
/ K* R. n O2 ^& i" p Mdtype: float64( f( r! [' x* n
1 z# k1 `$ ]! {
s.asfreq('D').head()& i& N$ m6 P& f6 p7 Z; O
Out[30]: 2 H% H% D2 M) ~7 ]" Q
2020-01-01 0.836578" W$ b6 c2 V9 c- y( w/ f. y
2020-01-02 NaN, ] A1 h. U, }( ~' w3 b. R2 J2 i- d
2020-01-03 0.678419, T* \) w; f& C0 j: M; v; C
2020-01-04 NaN! C) o0 o4 _ R% b4 y
2020-01-05 0.7118974 b- R2 V2 P( j& R5 a
Freq: D, dtype: float64, J# E' R' w; l- P
3 X1 q" L1 ^! }; Ts.asfreq('12H').head()
; ?. r; G' I& B7 M7 y' w4 L1 @Out[31]:
$ e4 F! C. k8 I# S2020-01-01 00:00:00 0.836578 Q( M$ n5 X( w7 _5 V, T" P
2020-01-01 12:00:00 NaN
; q/ @1 @2 D ]4 A: @5 u2020-01-02 00:00:00 NaN5 _7 ]) P% _; `/ v8 z# y5 w, R8 |$ e
2020-01-02 12:00:00 NaN
' b8 I5 s9 @: [5 J7 I2020-01-03 00:00:00 0.678419( e8 u1 G$ T* y) `9 P- B l
Freq: 12H, dtype: float64 f, D% {( a+ C) T
0 w9 I1 b! H9 L5 g4 e' o3 n1! Q. L' F! Q6 {# {% d
2! b: ]- n: E' _! M2 k
3. _3 @" k" {6 U; F
4+ h+ [) I7 E1 _' ?
5
8 Y x& J; I4 l. e8 o6
+ u: b9 V8 }8 `6 C) _7& Z, v# ]/ U. J1 `2 h( u" ~
8$ d9 \0 h) I, l4 s
9
( |5 k- M" E( V8 ^# D; l( w10
+ `$ h$ T( I$ t5 `3 Z' l* D' G11" O, z3 \9 k+ j" r' j* _, F
12
7 {0 v& N% }' M1 K3 J+ D+ o6 q# j134 C0 Z+ Z k- T. o
142 x) d* j/ a4 [# V
15 d# K7 G" P2 S! `; y
16 e# }, B8 d: {8 h% i; h
17
2 {; d3 D! y: _+ k2 v! c- j1 W18
' m; x1 ]+ l9 S3 \- N19" f; B5 k" h* q- c& n" `
207 A- k$ F/ l( W* ~& k: M" ?
21
W% R6 { l" f% C, e22
4 E' s* S" A4 a I23) t+ e) Y" Q1 c$ l# ~ v
24
: p/ e o* t' W25' B0 ~# g* v( M
26! I# p5 b7 H9 Y
27
( k, E6 \" V/ y2 ?# ^28( R5 X. |" H( T, R5 o! n
29
$ l6 C$ b5 K! f# _/ Q# S5 u30
3 c# s0 y' k0 F6 O& Z, _" r3 j; }31
: D3 c6 {5 [ x( p* |/ H: K【NOTE】datetime64[ns] 序列的极值与均值
$ @* H; e6 s L 前面提到了datetime64[ns]本质上可以理解为一个整数,即从1970年1月1日零点到给定时间戳相差的纳秒数。所以对于一个datetime64[ns]序列,可以使用max, min, mean,来取得最大时间戳、最小时间戳和“平均”时间戳。
+ R# J+ e1 e7 t+ K
* @/ }# N, U1 Z5 H& ~# C10.2.3 dt对象
8 G' Z7 R7 M7 S" m' R 如同category, string的序列上定义了cat, str来完成分类数据和文本数据的操作,在时序类型的序列上定义了dt对象来完成许多时间序列的相关操作。这里对于datetime64[ns]类型而言,可以大致分为三类操作:取出时间相关的属性、判断时间戳是否满足条件、取整操作。5 _% {3 z6 I1 u- N8 J% M
. K0 U+ L4 c2 x# }% w5 @# y }第一类操作的常用属性包括:date, time, year, month, day, hour, minute, second, microsecond, nanosecond, dayofweek, dayofyear, weekofyear, daysinmonth, quarter,其中daysinmonth, quarter分别表示该月一共有几天和季度。8 m8 C3 }- E! {6 B& F, J9 ]; d
s = pd.Series(pd.date_range('2020-1-1','2020-1-3', freq='D'))
- }" s' u# W( g2 Q) y2 u5 J9 G: I
! U6 ^ L( A$ H0 l/ ` Vs.dt.date8 A. n" D; W! I5 d
Out[33]:
- l4 T. X* w. y# d& `/ M0 2020-01-01; y6 @" C) a( Z$ n, ?
1 2020-01-02
3 Z* p- v# O9 s2 2020-01-03. Z! y6 ~+ }4 R+ g; t1 _/ P( v
dtype: object( K e3 U8 h, V8 k! n1 R4 L4 n# s
0 h. n# ]0 H9 P3 _* g8 r* ss.dt.time$ N" }* y6 O$ @" `1 M# ]& m+ j' H
Out[34]:
8 v }9 k: ?$ E& I3 A5 p t0 00:00:008 j0 O3 d7 [; @* k. w. K: N! O2 s
1 00:00:00" b. \6 ]- w1 |7 s
2 00:00:00
q! K8 R1 S3 E- P1 ^! ?, J8 w7 ]( {dtype: object
1 N. X4 Y3 \6 K% j0 I* ^) b
# n$ w F! I/ C1 {s.dt.day. g* e' C4 y p" f' M
Out[35]: # k0 g/ s5 z7 b: M6 S" Q
0 1# j. V2 q0 m; b4 R& a
1 29 ^, \- n- X0 s7 Q7 L3 N! v( r
2 3
" b0 d5 G+ E# Q9 a$ o% F: }" B5 rdtype: int64* M z; |/ N, o4 r% \3 B1 I8 d
% d5 e I1 f8 G7 R* U2 H& ~& @
s.dt.daysinmonth
1 q; M, u9 U: J2 ]% e5 H$ g" ?2 {Out[36]: 9 V+ o# ?( a# _5 W* Z2 A; i/ v
0 31
' v/ f, z7 H8 W7 M3 k1 31
( `* D" ?5 r5 w1 B4 o6 k3 A2 31( ?- M" Y' p! K
dtype: int64
0 ^/ [3 R6 s" Q/ k5 t5 t9 x/ g5 [) C5 m+ O
1& z4 {2 c9 N* f
26 m) b9 ~3 q) Q& x* ]6 ]' r
3
( S; H6 D2 Y. r$ q; U40 N( L0 h( ]# f$ W* }- r( |
5+ J+ z& X' L- j) x5 b4 _
6
/ s4 J% S0 v. C# }* I7
! g- e- T6 h$ H6 L) e/ S+ I8
: E! T5 j1 Y/ M- ~6 V" m9
8 O# @- m6 o: c108 p9 h! b+ o: x7 v4 Z1 G
11
# f" Q" B7 o' T* D) J( ?/ S2 E12& l ]$ H5 K5 y j- I- K! w
13
. i" S. o9 i! O8 X, F, J14
" l/ {: q4 I+ v9 e: Z& x15
' u5 G( H5 K* x% m* o% s16& ]. s. h$ e5 r7 j1 _
17
a, S3 ^3 X+ T" ]% Z- b18
+ E3 w* T" q1 m8 L19
0 _# t7 n- H; t* `+ x. z20
7 x' j. o; e! I% ?" c21
% Q8 e2 q2 w# B4 w- U. G22$ t2 G$ U8 z4 l+ s7 r1 b2 B
23# `, q& p) ]6 [* m- `# u, x* I
24
* i9 H& z8 e1 x \25
7 i2 M3 u$ H4 V( B* t3 F+ g" ]+ a26/ V! k3 |1 x2 P2 l. S" I: Y
27% A3 l, x$ I5 l% b* x: ~& G; D
283 o4 C2 f; j3 n, a: w: |9 h- r
29
- H7 Z8 g# D- j- v+ k' a+ `" ^/ h 在这些属性中,经常使用的是dayofweek,它返回了周中的星期情况,周一为0、周二为1,以此类推。此外,还可以通过month_name, day_name返回英文的月名和星期名,注意它们是方法而不是属性:
; L4 X0 L# o# [# G2 p( [
3 f( s- L* q; t' L m4 V+ ~$ _0 Qs.dt.dayofweek
2 S! i5 u* k( G6 LOut[37]: : G2 ?; O$ Q l. _: W, n& R3 o
0 2
6 u. }* h: ?" a; z# t1 38 z$ r* Y! C5 x
2 4
+ P4 o: f8 ^* t5 d; ]dtype: int646 y( A/ D' q# ^4 m. V
7 m- I# H( G3 V" F% S7 E7 X
s.dt.month_name()
7 K: Q2 ^% L; a& W) _Out[38]: z0 g+ j4 p; w- R
0 January4 v4 k3 @1 a; M, l/ y
1 January
1 p, s% R( `" ?+ f. m) q Q) x5 i2 January4 T3 q$ }* J: n
dtype: object9 \ m0 O' d3 y( f
+ y/ u- U% c1 [
s.dt.day_name()
7 d6 S, v8 ^ w& B/ t0 G* p) @* {Out[39]:
, B* H- l( K# Q" n0 Wednesday( k* q9 O( I, P! ]8 `6 I8 u
1 Thursday
# ~3 M* U! @6 J* m4 {' J* t+ K! \2 Friday# t) a3 M W5 _0 g( I- d
dtype: object$ l8 ~* V6 A1 a" s3 x5 x2 a
4 z6 {9 D! E' V/ B9 k15 J) W" l4 s. Q1 r/ q! ^" s
2
& [' n9 U7 M4 W$ m6 |6 X' W( w: G3; \. X Z; V4 ^% V! q9 i0 X
4
: Y: w0 g: M0 {- w- }) }1 A0 k. X5/ J) \5 d# a I
6
" \9 F% [* f2 z& f V: R- F( ~( a7- q2 P' Q$ w2 d
8
" q& v& k6 d/ b' q2 N& h9
, c9 ?3 @* t/ {' H+ o% l: T10' T0 T6 v. e$ r" l& y* u
11
8 A7 f: ?' }3 x12" u8 e* d% |# p d$ y/ _: u9 \
13
" A# H, t. a2 f5 N+ M3 L0 L14
( Y+ t1 L8 ^$ |154 J Z9 t9 l/ {: s5 i" Y
16
% W% } Y. ?+ w8 ^. m- }9 I173 A- a# z; t) l/ j5 L i8 c
18
8 T9 l/ h3 t6 y) R19; ~/ a& e& Z& R. Y0 |) S; A/ T
20" I$ M/ S c' Z% E: ~4 I, e, R7 D1 ?/ ~
第二类判断操作主要用于测试是否为月/季/年的第一天或者最后一天:
) e# C! z5 z/ w5 u3 r7 Ts.dt.is_year_start # 还可选 is_quarter/month_start) o! k, I$ X, f9 f
Out[40]: - W* x# v, H$ K4 t
0 True
/ X% i' @7 U& m& p7 V/ T1 False' L( G ?2 \; y5 Y* D- I1 V
2 False. X; U& \( S' E1 |. ?( ?4 s
dtype: bool5 l3 j" R' I& e3 H& i
' l4 Q" G* {5 A0 h6 N
s.dt.is_year_end # 还可选 is_quarter/month_end
: a4 n% r: B1 p! A6 x, tOut[41]: 1 }4 u# g5 X Y F
0 False! r N6 F/ D9 `0 Y0 B/ g6 p2 D# l
1 False+ e. U3 @& [! M) F9 e
2 False, J: V, k. N6 v$ l( N6 w
dtype: bool
1 A7 s% M* j+ M0 }* h1
7 a4 }$ T) \5 t: f- F2. E; b9 G/ @% ~5 `' u
3/ X! n: ], s" c; c ~4 _
46 x/ x) b' i4 ~. s
5: \8 i4 ~9 |& X# ~: j$ u; W
6) q6 r' `4 ~( U1 [/ G' ~8 q- C: B7 O
7$ H0 D1 I B% L' P2 P, d1 r' Y
8
: w( ^3 i: t, t- ]' z- ]2 g# `! X$ v9
( Z, J. b- w) f" |" z10
R/ w5 ]0 M, P" T1 ]/ W+ F8 J11
3 g: ?% R2 e- n. V12
) q9 y) N1 `" s13
) I& s1 \) [! p! k: a/ [第三类的取整操作包含round, ceil, floor,它们的公共参数为freq,常用的包括H, min, S(小时、分钟、秒),所有可选的freq可参考此处。0 n3 e. y; |- d6 g
s = pd.Series(pd.date_range('2020-1-1 20:35:00',& E ]/ `8 W8 j
'2020-1-1 22:35:00'," v* Y. b: z( ^( s
freq='45min'))
7 X3 M, _4 c7 F8 X0 t6 K8 h( j( P
2 `2 V# p+ O5 p5 q8 h1 l" g2 Js
; o% E Y; b. U3 J: fOut[43]:
* | O, f8 p. z0 2020-01-01 20:35:00# d }9 U) w2 {9 A0 p/ G
1 2020-01-01 21:20:00
/ ^2 R- i& `, d! q3 o5 C8 U; Q" t! m2 2020-01-01 22:05:00
4 q' \0 c6 r" s0 ]2 p1 ]" a( V7 ^dtype: datetime64[ns]
5 m0 y3 C! r+ ]1 z1 c& n3 Y5 m! E/ W! R) z* l1 {3 ?
s.dt.round('1H')
Z c( e# x* K0 TOut[44]: 8 y' z5 y8 Q% O3 | D2 ~
0 2020-01-01 21:00:001 i4 Y$ E& n: n
1 2020-01-01 21:00:008 ~2 c6 {( [) Q
2 2020-01-01 22:00:004 w- u3 o9 `8 N1 U! b# K3 ]* [6 J7 u c
dtype: datetime64[ns]
9 A7 y( Z, N- o
9 `. I8 T5 N+ x' d0 |s.dt.ceil('1H')
1 \5 _; c5 J; a- _& ^+ ZOut[45]:
1 j7 q+ c- K7 ~: A" H* R0 2020-01-01 21:00:00
- K3 P3 M2 o0 w& `' B1 u1 2020-01-01 22:00:004 w+ N( i" X3 l$ z& Y
2 2020-01-01 23:00:00
H/ g( @% N. ~. K! Vdtype: datetime64[ns]
% ]. o' R2 ]* b0 j3 L2 [& u1 H
$ _7 _, T0 c, ^s.dt.floor('1H')
0 D; W0 G# X9 t4 EOut[46]: ' F6 U" Z( m! f/ t5 O9 m! \
0 2020-01-01 20:00:00
8 Y& O1 ^$ k% E! D+ n3 l- D1 2020-01-01 21:00:000 `! a% n q; q
2 2020-01-01 22:00:00
: C. t& Y" S1 i- k4 Hdtype: datetime64[ns]+ m- V9 ~ N, s$ P
- F8 m! I) X& f9 z. L1; m9 ?2 Y1 u$ r9 Y/ }
2, l6 B2 Y& L+ r; B+ d, U
3: M, ?6 i$ q# o! n5 f+ ^, q- Z* f( m
4
) d8 f/ z7 {8 Q" d/ u% U5
^" [8 [1 i6 |6
% y9 P8 _: w: G; W% z. H7
& M9 f. a7 _) @: e' {' r8 p- J; J U) ~7 M( f1 W; z
9
, g. s+ r2 c0 R/ e8 y8 A' U) I. K3 `10/ I6 v; C0 K) H3 a) ^6 {
11
6 s! {# y, q S, g9 o12
& I9 N0 S8 P/ P13, }9 Q4 G1 l1 E& @" {# x$ g: M
14- ?* L! R. |0 ~! Y
15% [1 ^: O; f1 p6 C: y' R' O* |
16
1 s7 X2 i* f7 ~+ Y7 N% m, `9 l* f, w* \17: k- K; p7 F; ?% m7 k
18
# U$ C7 P- { W+ ^" h' U19
& B; X' @4 s; ^8 N4 }5 A: ~% y& r) `( Q20; X1 q+ q5 c' a+ x, F0 c5 u
210 [7 z' N; E7 k* f9 Y5 k4 J2 _7 G F5 k
22( C- L0 ]* }/ O8 ]
23
, Y5 X% R2 \2 u6 _24. s8 Q/ c& Q/ `: G# `
25 H: h3 M5 h) g
265 |: G+ O5 ]) d3 L8 g* d4 `/ V
27+ D" i( s- r) E9 [4 I/ d
28
- o6 t' v; W# M* \4 b29
+ u$ f/ x6 x2 \5 H# V" i& w$ w30/ B1 J. Y6 i! s( F7 }% m; @, X7 {) }
31
- f" r7 @; `$ M4 P% S8 Y; _325 T, J) W" p4 U: v5 P C% W
10.2.4 时间戳的切片与索引
6 P( d0 _1 j+ d" x 一般而言,时间戳序列作为索引使用。如果想要选出某个子时间戳序列,有两种方法:! f8 p I9 b9 ]
5 X$ E: x; E7 X利用dt对象和布尔条件联合使用
: M/ L9 T6 I2 \, Q) Q5 r. @2 V% N: D利用切片,后者常用于连续时间戳。
) E# @; `# u3 T: R% es = pd.Series(np.random.randint(2,size=366), index=pd.date_range('2020-01-01','2020-12-31')); e# u8 V6 s7 _: N+ U
idx = pd.Series(s.index).dt
4 Y& G0 R# W5 p @s.head()
0 G) b2 `6 F; B& l: {" N7 }
, J3 m7 N) ?- ~4 |5 X- D+ _2020-01-01 0; L r0 k0 k/ ?/ c: m: b
2020-01-02 1
) v$ t% \1 \# p8 {: o0 M2020-01-03 1
+ }, W* P+ r$ f6 q* [$ B+ D8 a2020-01-04 0
( W3 { o; T b$ m+ i2 y% N; n- G2020-01-05 0
8 J( l3 H$ z# b, @# w5 kFreq: D, dtype: int32
9 X* X/ @/ m$ `% J" J' W( A/ o16 \+ M: {: Z' @( ^; _- w
2
% B9 J# w! ?, ~3. Y. L! X+ ^9 @! B: Q' J5 }
4. ^' m$ b0 l; ]$ h
5
; `; E. I2 N: e9 }3 E4 |6
7 Z, s$ L2 D, V, }! d' w5 E7, u& X6 c# i+ S0 g9 }
8
0 Z3 ]& o' [ c- t, S9
! h/ ~' r: c o q) ^10
( r$ a3 I! V) h- WExample1:每月的第一天或者最后一天' J/ K& Z" d! c$ Y4 Y; i3 a) u
9 p; ?; I0 K7 H; \# V* I) |: ss[(idx.is_month_start|idx.is_month_end).values].head() # 必须要写.values, r" d) ]: o. a
Out[50]: : n+ B0 k: y1 I6 a
2020-01-01 1
# K; ~+ w8 B7 I% j2020-01-31 00 V( o( ~+ H' `" C+ E* d
2020-02-01 1
; e* m6 O+ Z/ X# E6 B( q2020-02-29 1) G0 h4 h5 }* o: S
2020-03-01 0
8 b6 p9 K! z" q9 Gdtype: int32! x9 l: H! F! u1 j w
13 l |6 X+ [$ p, i% o" a2 V2 x
28 D5 ?, [. J3 c r: N$ ]
3
, M. z7 C, `2 D& \" B0 {4" K- J- P1 N/ Q
5: W1 y0 f# a4 k4 G
6
. M9 a) v8 s9 r0 u7
7 c6 k; q; A! W. d8
r! C2 a# C6 Y; D9 n2 YExample2:双休日
1 y; [+ @( L% _$ T4 S3 q' L- F0 s& X4 F( G$ S) V3 y
s[idx.dayofweek.isin([5,6]).values].head()/ p ]0 A7 P! [+ Z
Out[51]:
2 _: Z; j& A) P9 b2020-01-04 1
0 B0 Y2 B- ~& x2 W9 b8 Y$ S2020-01-05 0 g! ^2 V7 i* J* O1 F. w
2020-01-11 0, ]$ b' @& c* N% n- q/ [9 D
2020-01-12 1
" D4 M9 X8 j' @% x3 m2020-01-18 15 x8 @( i' b4 K9 H6 F( h- L
dtype: int32
! o3 m1 |: S& t% K1
" ~. v4 k; O& ~: \9 O2& J; q7 q# G- _
3 q1 ]' z# h- v8 \8 F7 k
4
% B [3 D; t [& t g5* ^; T8 w1 h2 ^# c3 y9 H/ Y$ A
6
2 P! ?# x1 c8 c/ S7* ], [2 h/ W/ p: S# ~. P P0 I
8
# H" P D$ b" K0 xExample3:取出单日值
& [+ T* R7 I8 |) b8 W% w
( A4 j, F1 k* j) x# c# Os['2020-01-01']
* p% O: A8 `. b. z" EOut[52]: 1* ^0 ]% h0 h/ O
! z# F) Y3 c5 |: z
s['20200101'] # 自动转换标准格式
3 e7 {& Y" H1 _& ROut[53]: 1$ A" f9 k. t$ g- m! X! U6 ], d! A
14 O' R' O3 T) j& V& ^
2
- ^, d* p) E9 S4 T3 i3
% v" X4 x/ x" i* Q9 G4
+ L4 k# e+ y7 F0 R! f5
{0 H& r" ?* G8 b) bExample4:取出七月
7 u4 Y: x. n8 a8 S |
" g( b" u9 z5 h$ g4 |+ Z. A$ j9 Is['2020-07'].head()% j# F( o3 X# t8 o6 n A2 ?2 F
Out[54]: 4 [/ B# k$ H5 e* S( H' w
2020-07-01 0
- K8 y0 N, \& {2020-07-02 11 h- I; K) g$ t: {
2020-07-03 0
, z6 W* e& D2 c2020-07-04 0
& X* M i+ Q; g+ F2020-07-05 0% ~) z1 f" }3 W2 r8 T9 P6 h2 L
Freq: D, dtype: int32
7 _" q, k* \- @ N1( G! Y7 p8 q3 W' o
2
& ^: L" M2 [4 m, W- \: N5 I3
- m$ f6 X. I+ X& v1 S' W% a0 q48 y' J3 T! x( T" \6 W/ h5 }
58 \- c7 p' ]! M7 R1 B) [/ [: n! B
6
% h, z( Y8 g; z4 H6 {7
3 n# k( V8 [6 f. I8. q1 S5 |/ x0 o8 c9 r7 G; U
Example5:取出5月初至7月15日
! n" I- x; O) a# M* I( D5 Q1 F$ i1 _0 m) K
s['2020-05':'2020-7-15'].head()+ [6 W6 C! p G2 R( R! b1 n2 v
Out[55]:
! |9 h/ D i. @3 X5 U8 j& x" D H$ q2020-05-01 0
3 ]! L" y$ x0 `$ [2020-05-02 1% P2 }) e: J/ n* P; I1 P
2020-05-03 0
) G4 p4 J" O) W2020-05-04 1
+ N6 A( ]3 h# Z2 P) d5 i, ~* ?3 a2020-05-05 1# ]2 l/ h/ F5 y0 I \, _% G' S b
Freq: D, dtype: int328 ]9 k3 B* j1 N) n+ B3 i# P
7 e1 k8 X6 W, d5 Qs['2020-05':'2020-7-15'].tail()
% d) Q* r$ M( x% MOut[56]:
5 ^/ q" w: D' d, x0 P2020-07-11 02 k: z! k7 F) |
2020-07-12 0
! O& O0 ]/ v1 I! ^2020-07-13 14 C. Z4 g: P! C7 J1 d) S4 F \- S0 i/ J
2020-07-14 0
! l+ J' C8 A3 M! O x0 W$ i2020-07-15 1
; Y* j1 C4 ~- t' z) g I" l/ TFreq: D, dtype: int325 P+ e- }9 C6 q) a: U: v
* S R3 c. U4 ?
1/ T# l( X# t$ c7 C+ S7 Z& g; O
20 Z* Y5 \( Y' H: V' \0 \' e
3' E' f1 Z# n$ Y+ ?
40 R" q8 z, e. m( S3 z/ {& G
5# x2 W6 L: Y2 U' I& x
6# ]: M& M1 P. e# T" i' D8 e9 f
77 \; C) G0 j, a3 G a
8+ J, K4 ?/ F0 Z- E
9
) c; C5 ~" N$ o3 h2 t; \5 K# s10/ o8 h5 t8 s; ~
11
+ Z2 \& o1 |4 W12
9 c6 K6 G3 k( a E( j5 T) k13
% s' k4 Q& b! w+ [5 S- N14" w( ?& l% f) u/ N) ]* f! Q
15% j# v, q: s, z4 V
16
+ K' i8 n# \- d. ^2 c) ]6 }17
! x. I! H/ a$ d+ e. f- e6 u10.3 时间差* i% D* W+ S) X: h
10.3.1 Timedelta的生成5 I. \* l4 P3 E- }! ?) v) _4 x
pandas.Timedelta(value=<object object>, unit=None, **kwargs)
% z8 f( K% k- a: e% y+ ] unit:字符串格式,默认 ‘ns’。如果输入是整数,则表示输入的单位。
. S# p% h! I# V& G% S 可能的值有:! G# V+ q4 z5 c& |0 o2 a$ S" ^
) A ~+ \( s( f0 X _
‘W’, ‘D’, ‘T’, ‘S’, ‘L’, ‘U’, or ‘N’5 H9 m. J! ]7 r: }* e& M# w
‘days’ or ‘day’5 I8 t6 t! j ^6 U, I/ b% M1 p& Q' E
‘hours’, ‘hour’, ‘hr’, or ‘h’: J7 g/ {* y3 a! ^
‘minutes’, ‘minute’, ‘min’, or ‘m’
5 Z5 i4 ]. Y5 B% K6 B8 [9 g‘seconds’, ‘second’, or ‘sec’( C7 }4 M% g& f8 B- \6 Y0 W5 _2 t6 P& Z
毫秒‘milliseconds’, ‘millisecond’, ‘millis’, or ‘milli’
5 g( K: r& q3 ?1 h& Q$ }4 B$ S微秒‘microseconds’, ‘microsecond’, ‘micros’, or ‘micro’
5 A& r3 m8 f% C纳秒 ‘nanoseconds’, ‘nanosecond’, ‘nanos’, ‘nano’, or ‘ns’.$ l6 n- O( B* t7 w$ G! C9 X
时间差可以理解为两个时间戳的差,可以通过pd.Timedelta来构造:/ P4 T2 L% a* b+ F3 @ z+ g- _* ]- q
pd.Timestamp('20200102 08:00:00')-pd.Timestamp('20200101 07:35:00')
! r5 j+ B$ R5 G( NOut[57]: Timedelta('1 days 00:25:00')
( R. v6 [9 t. u' ?% b, `; ~+ ]) ]# l/ r1 K* S
pd.Timedelta(days=1, minutes=25) # 需要注意加s! k( ^% z. n5 o& ` A
Out[58]: Timedelta('1 days 00:25:00')
, J+ y' Q; y3 Z! O1 \* L- Z6 i, ?0 U: A/ O6 Z; y: t
pd.Timedelta('1 days 25 minutes') # 字符串生成
- b" u8 m% x; q6 \$ NOut[59]: Timedelta('1 days 00:25:00')
6 c- X# \1 O K; @+ Q! [" e
- a4 J& w j0 W4 c; M' [pd.Timedelta(1, "d")$ O3 ]% k& T5 w; B9 Y# |# ^+ u
Out[58]: Timedelta('1 days 00:00:00')8 H0 i, m0 N. E8 B* t4 a
1 X2 c$ J9 J( P$ q2 b
2
6 q2 |. q# T* ^ t4 n) J1 {8 Q3 l3
2 P! a9 E' f% @- |( b4 ~4
+ Z9 G: L x% U$ ^3 X# P5- v% ?4 ] Z) x$ y: d- s
67 p$ X7 x- ~- j2 a+ r4 Y' ]
7$ w7 x+ O' `' t3 r1 \# x
8 ? l7 y* C8 }1 T" ~/ I$ ^! s
9" x5 I, O; I4 J% {7 l$ M
10
' g0 C' B3 Z9 ^ L4 a' O11
) n- F# m4 p G* a B/ `生成时间差序列的主要方式是 pd.to_timedelta ,其类型为 timedelta64[ns] :2 _6 {2 F( |8 K! y7 l
s = pd.to_timedelta(df.Time_Record)
( p0 M% J' O2 D5 [; E. m. H1 f- a/ `* u- x
s.head()
. j, s9 U9 f/ {* }4 w/ I3 JOut[61]: 3 i" D5 k% d3 M$ L# |1 v* a1 k" F) V" R
0 0 days 00:04:343 Y' F8 A; V8 K& O/ ]) x* H
1 0 days 00:04:20
' A$ Q9 X& A: ~3 i2 0 days 00:05:22! H8 }/ v ~5 M$ T" y1 c: D9 y' h
3 0 days 00:04:08" W, L5 Q7 x5 N8 ^$ ^- [
4 0 days 00:05:229 `3 B4 y. G$ |/ |8 [' `0 j
Name: Time_Record, dtype: timedelta64[ns]
A5 Z; w$ Z8 [- I' O1
4 m5 N8 l7 ~5 n20 |0 y6 U! I" n$ o
3
- }3 D5 C( f e- P4+ _5 ^5 n* a+ c+ j2 r2 {2 n0 H
5, D0 v T, I. d- Z( }
6+ e# `$ k6 O" S5 T6 j1 _
7
+ S* U5 E. A2 |/ l9 ~- d8; U8 G; }5 C0 W& y0 I4 X
9- J7 n8 E4 F* |' p: M* E
10" r7 _0 f5 ^" H- K3 Y
与date_range一样,时间差序列也可以用timedelta_range来生成,它们两者具有一致的参数:* I" O* B8 {5 p9 s$ z
pd.timedelta_range('0s', '1000s', freq='6min')
7 f* n* r5 b7 [& g- X. S! q, }Out[62]: TimedeltaIndex(['0 days 00:00:00', '0 days 00:06:00', '0 days 00:12:00'], dtype='timedelta64[ns]', freq='6T')- {9 [! Z0 e" [' s: N; O4 [: Y
) J& Z: U# Q+ K8 n2 p, c6 h; `4 X
pd.timedelta_range('0s', '1000s', periods=3)' L' e% `- O" y/ S) F& b- m4 j
Out[63]: TimedeltaIndex(['0 days 00:00:00', '0 days 00:08:20', '0 days 00:16:40'], dtype='timedelta64[ns]', freq=None)
0 {0 v" A: W/ v6 N3 x+ @9 I( t" V( e1
$ ?# B6 V! X* p& A29 j, b: B" v' A+ I, _
3
; L% L! o/ B: n4
! F+ e: _ U- @( d; }# o' E& x0 X56 n$ f( e4 r3 Y4 A. f# I
对于Timedelta序列,同样也定义了dt对象,上面主要定义了的属性包括days, seconds, mircroseconds(毫秒), nanoseconds(纳秒),它们分别返回了对应的时间差特征。需要注意的是,这里的seconds不是指单纯的秒,而是对天数取余后剩余的秒数:
/ ` m! ?8 W% N+ h, i ]' x$ fs.dt.seconds.head()0 Q8 Q' b3 m+ j- h3 q3 z2 k
Out[64]:
, s, E; x1 _9 F0 274: A+ a. K/ x$ Y S6 ~# N- i n! B4 h
1 260
* n& b1 L0 a$ E8 @% o1 L+ ?% N2 322# a) a2 d2 M9 j7 ]
3 248
( c7 J* Q. q$ z" h4 322
. k& j' ?; e+ v4 F- XName: Time_Record, dtype: int64; ]. `( d9 g* d/ ~( ?4 k2 G+ S6 P
1; w4 ]( v+ k/ D0 f" L) F Q
2$ j6 X7 g1 e1 g; k" p: t- R# Z4 S
3
: q7 r+ _8 p. u- e8 j' J4 h4. d) N# n9 V, D
5
, H+ k# H9 F% \5 L0 N6
: q. K6 J0 L/ _ x3 S) h: q75 A7 d6 h* r$ s( @3 u
8# Z/ O1 U8 @0 X% b
如果不想对天数取余而直接对应秒数,可以使用total_seconds6 M G7 G: T6 Y8 j) o
! E5 M$ D8 `! x# G/ w
s.dt.total_seconds().head()
9 m2 w5 ]# D* p. c! b5 YOut[65]: / ~; o# Q, V$ }# E. t& ~4 o1 U* l
0 274.0
6 n5 S; F$ N) O$ g6 P. {1 H1 260.0* `0 O$ F3 |/ E0 y# k
2 322.0
6 N2 x+ b& B" j6 n0 j, A; ^3 |4 e3 248.0
* L& G: v4 [5 r5 l+ D4 322.0
6 u) C$ X5 O: Z- W7 lName: Time_Record, dtype: float648 t$ R0 M# k! Z, u8 W8 E* h3 l
1: D* n `, [) Z4 O7 d
27 X8 p$ @3 b0 w# X
3
7 V' H/ Z; t8 c" m) |4 i, T4# K7 ?( L5 i j: q
5: l2 }- d( X# u5 r5 P8 j1 K
6
' ^ L& t" o: c3 k& ?73 i) I5 l$ b4 l3 d* w
8
) \7 V; O+ @8 B: _0 r8 }( Q: c% ?与时间戳序列类似,取整函数也是可以在dt对象上使用的:
, r$ e( P) G0 D; D8 H, m+ r# T' J2 X; z
pd.to_timedelta(df.Time_Record).dt.round('min').head()
+ C1 B' S2 M2 G- SOut[66]: ; A! i, m$ p" d( U; Q) Z9 Y
0 0 days 00:05:00
, R0 E. P i8 U9 |) _* F1 0 days 00:04:007 G3 l0 r- d$ ^' n) C
2 0 days 00:05:00
5 `, @! a1 ^, d, n3 A+ ^% `3 y5 R" b3 0 days 00:04:00
7 }& N3 w7 S& I" I7 A$ }# H2 }# |4 0 days 00:05:00
' ~2 t3 j; s8 l2 f' XName: Time_Record, dtype: timedelta64[ns]5 ?6 r, M5 c/ V
1
- Y. D- c, l& H4 J: h; T5 r2
m9 W: @6 j& D+ G+ Y' k y) y$ O* j3
A9 s4 w4 `' W+ E+ r4! p3 \% ?2 [! K, L3 a/ Z( I
58 P2 a4 R, F2 |9 C! U* N
6
5 U8 _8 T! \8 K- l; ^7+ n* o" `3 b- A+ F4 Y3 X x. w
8; P. g# c' i' J. r$ D
10.2.2 Timedelta的运算
, v% s- q& M) t% }1 K8 p% l单个时间差的常用运算,有三类:与标量的乘法运算、与时间戳的加减法运算、与时间差的加减法与除法运算:9 ~' U# o i5 k1 J# g8 ?
td1 = pd.Timedelta(days=1)( K7 z& j4 h0 G
td2 = pd.Timedelta(days=3)
. q% o0 F" ~4 c& Pts = pd.Timestamp('20200101')
; b" S$ v. M5 o1 q. ^4 y5 [7 d* F; A+ W# p
td1 * 2( d$ o2 j- g5 i/ D Z; B
Out[70]: Timedelta('2 days 00:00:00')) `/ X+ d" s; S3 k9 k6 Z" J
! M& C ?, N0 D. i4 Utd2 - td1
7 q, O. X& n+ }) ~) A* ` TOut[71]: Timedelta('2 days 00:00:00') r+ J) @% T. r% e9 K2 d4 l
% R9 N- s7 O- i6 K1 q9 Bts + td19 K2 W. @+ _4 W5 {% C& M4 q2 r* E
Out[72]: Timestamp('2020-01-02 00:00:00')
X5 b! W* V% u9 H4 {' y: q* e v! y! ^; W# F
ts - td13 [* Y" Z5 }; S5 F$ F$ {8 B' o2 r
Out[73]: Timestamp('2019-12-31 00:00:00')
% U" ^3 ^3 u4 o; E1# X8 B7 L- T: ^$ W- a
2
" d5 S. ]; ]5 N+ e; Q) X( n3
" k2 e" Z7 L' L9 n( P4
% O8 s' u8 l7 h. O+ U5) k/ C- m& i7 L6 c v. ]% Y$ d
63 N' K7 i, q$ a0 D7 ]
7! T; v, }8 ?( |; L
8; e* e4 M$ i' a7 p- t5 n
9
, l2 J) X+ ]! F- [5 N108 X9 Z+ A4 f. K3 }! A4 Y
11( V' ]: p8 t1 z! h
12
$ u! O* ^3 L# @3 k13
% E& O8 e2 X+ y* f# T14
: y2 x7 c2 Y* V l( K3 }7 U& }15
+ r; W2 `7 R" [' G9 w& s( m时间差的序列的运算,和上面方法相同:
* i/ j" s' `3 ^% a4 d% `2 g/ Utd1 = pd.timedelta_range(start='1 days', periods=5), Q" q* _. X3 r: Z: O4 j
td2 = pd.timedelta_range(start='12 hours',3 _! F9 m' w+ _
freq='2H', C. p, h6 F( a8 J0 `/ c
periods=5)9 O3 i9 f4 w- k6 B
ts = pd.date_range('20200101', '20200105')
$ C7 i' G3 `6 h# N4 K1 w& atd1,td2,ts: d! u: _) {! u0 Y* ]
- V7 L1 h0 U* [1 h( I+ M" g1 p3 yTimedeltaIndex(['1 days', '2 days', '3 days', '4 days', '5 days'], dtype='timedelta64[ns]', freq='D')) q# A1 C1 N: W; ?/ z7 F! `
TimedeltaIndex(['0 days 12:00:00', '0 days 14:00:00', '0 days 16:00:00',
5 T; N. b0 N5 G$ f; a9 a5 X+ O '0 days 18:00:00', '0 days 20:00:00'], dtype='timedelta64[ns]', freq='2H')) ]! G0 m i/ O# q; I! D
DatetimeIndex(['2020-01-01', '2020-01-02', '2020-01-03', '2020-01-04',
8 J# s8 z& J$ E; u6 l8 h '2020-01-05'],
4 L3 b9 b; L, v+ \7 u4 r" R dtype='datetime64[ns]', freq='D')
5 B N# J6 D: b- y1, S3 P1 X5 v# P8 w1 q' f: [
2
! B7 i) `& w% K. q; J3 K1 I9 f$ K0 ]. p" d: B
4
2 {3 u& j+ C: V% T4 Z; {5
+ ]5 R% d0 I3 j6
# a. Y Y- X% M' \' S3 p2 r0 l7
( V% U1 z1 U- f- v8 |; O6 v# _8
D/ R% B7 g9 T+ @9 y1 j& v9
* M) c$ ]2 |9 P+ x# n; R" D10
$ @4 M! V! @$ T; t1 i' }0 W s11: j2 d( _( o* ?# H
12
! L4 C4 G* N5 b+ J1 m9 b/ [; I1 n13
# L k$ r5 D" U' X, j/ Std1 * 5; M) {" I# ~0 B
Out[77]: TimedeltaIndex(['5 days', '10 days', '15 days', '20 days', '25 days'], dtype='timedelta64[ns]', freq='5D'); b7 { s5 g' \" v, O3 V) n
- Q, f9 d }& gtd1 * pd.Series(list(range(5))) # 逐个相乘
0 D# Z# V7 k; pOut[78]:
/ n1 i* Y* m( s& |0 0 days
: t4 C0 l8 e6 U; |8 J X1 2 days
5 J* Y# x* p4 y3 [- |2 K( ~2 6 days& U8 V" i* z6 u) W! h% j
3 12 days
5 L4 t D1 G; q, S" K3 ^3 t4 20 days
2 U: X& t5 h) Z& j' c6 Y( ~1 j* Ldtype: timedelta64[ns]
* t0 F, I( W: @$ i* ~( h4 F
6 [" f! O: N5 e* Jtd1 - td2& U9 B) i$ Q9 R) S
Out[79]:
0 Z9 g; U6 h5 v! c7 a; ~0 }' ?- TTimedeltaIndex(['0 days 12:00:00', '1 days 10:00:00', '2 days 08:00:00',
. Z) F- n( l6 z I/ l '3 days 06:00:00', '4 days 04:00:00'],
7 }; t* u1 R) F dtype='timedelta64[ns]', freq=None)8 y9 @& L$ h& Z7 l& B& l
8 x Q( j( `9 T& K ~$ H3 T
td1 + pd.Timestamp('20200101')8 P- Y3 b8 o& O/ X' g+ `; {
Out[80]:
- O/ s7 q# a9 m5 t. Z7 g7 }& _2 nDatetimeIndex(['2020-01-02', '2020-01-03', '2020-01-04', '2020-01-05',+ w& ]+ \$ q, |7 z
'2020-01-06'],dtype='datetime64[ns]', freq='D')8 g& ]7 ~/ ]3 z2 m+ x' O
& m, d I' M$ N* X6 _. Z
td1 + ts # 逐个相加
( E& z8 K$ L3 a% uOut[81]: 3 \4 x% {& g4 J, G% v
DatetimeIndex(['2020-01-02', '2020-01-04', '2020-01-06', '2020-01-08',
3 {- D* m8 g6 {% F+ e, k3 Y8 S '2020-01-10'],
7 s) l% ~8 b- i: A5 p$ S dtype='datetime64[ns]', freq=None)
w* r; r( Q, ]8 [) R8 t C8 L; \' i1 |' z" q9 t
1* \0 a) q; Y j% E7 j# x4 p/ I
2/ M" D, U2 K/ b E9 Z* h
35 s u6 G9 z+ i L
4' J0 A, i- h1 l3 X
5% L& C8 R: k: b! _5 Y" {
6
1 [+ f; c+ X* q$ g79 y/ E4 g7 ?" ~" ]
8
& b: ~& ^4 P0 m: Z9
1 I) S& O! k* J6 i8 ~10
3 H+ p: ^8 f6 L8 O11
& Y' b! w( T7 k: M+ L: j% ~12
7 ?7 m: _ u9 k2 C9 T" @4 z( {% a13( Z4 f) @8 X/ ^8 c9 T* l3 |
145 g) ^4 A5 b/ Z$ i
15
6 p) k. ^- I$ X9 p166 ]: b6 n8 [4 l- T
17
8 t% o; C4 @2 S# R18
) q: l. X" [2 N195 p9 t$ j: I7 W" I2 c3 \
20
& H! e0 e* b0 q5 K, W! A21
/ `! @0 q: X2 i8 ]( Z22
' m5 c+ [ p- ~; W0 e9 Y! M6 u23
1 m- O5 e, w# e1 ^2 S24
" p1 B7 x B7 _% N1 t; y3 `9 n25- O8 [' X8 {& o
26
5 ^+ c0 V! U5 P. t+ [1 B9 Z; i275 j1 g* x7 x+ ]1 ^' c; H
28* R* F& E& q0 \, j! i' I
10.4 日期偏置
3 {0 X* A, ]0 g- E- ~: Y10.4.1 Offset对象6 l: c" S( e1 {% [1 T4 h0 M1 r
日期偏置是一种和日历相关的特殊时间差,例如回到第一节中的两个问题:如何求2020年9月第一个周一的日期,以及如何求2020年9月7日后的第30个工作日是哪一天。
6 F' [( l8 [% O5 U* X8 X
- ]- ~$ y( h( h$ B) WDateOffset 类有10个属性,假设s=pd.offsets.WeekOfMonth(week=0,weekday=0),则:! _. {' Z3 u" u( w4 C
+ v6 Q3 t$ {: F% K( t
s.base:<WeekOfMonth: week=0, weekday=0>,返回 n=1 且所有其他属性一样的副本1 u' d% q, s% T1 q' Y' M) C
s.kwds:{‘week’: 0, ‘weekday’: 0}+ b8 i# H: @. T; \3 o! @9 W7 u) U
s.wek/s.weekday:顾名思义
8 z2 ~, h- [) M有14个方法,包括:
. F9 D, n* S# t2 p4 B7 i: x
- M: y+ Q6 G/ |! I% M' W" M, S! ADateOffset.is_month_start、DateOffset.is_month_end、DateOffset.is_quarter_start、DateOffset.is_quarter_end、DateOffset.is_year_start、DateOffset.is_year_end等等。
4 o' o0 g- L8 h3 wpandas.tseries.offsets.WeekOfMonth(week,weekday):描述每月的日期,例如“每月第二周的星期二”。
' }% p" q+ p" F( w& S1 P) {
9 \4 \1 i' `4 e- y- W$ a( L: U9 _有两个参数:
5 J4 |$ c" u- b- o* m! m0 G$ wweek:整型,表示一个月的第几周。例如 0 是一个月的第 1 周,1 是第 2 周,以此类推。 c8 ]5 k5 M- l; @8 q/ S. M
weekday:整型,取值为[0,1,…6],表示周一到周日,默认取值为0(星期一); e3 v& i. e5 w' f+ d
pandas.tseries.offsets.BusinessDay(n):相当于pd.offsets.BDay(n),DateOffset 子类,表示可能的 n 个工作日。' }2 h9 g' q& s' q: |7 y+ `
+ U) N5 _& G5 x1 rpd.Timestamp('20200831') + pd.offsets.WeekOfMonth(week=0,weekday=0)' n4 m+ }/ z; P7 h# m8 @
Out[82]: Timestamp('2020-09-07 00:00:00')) n6 p' t( S" L: P4 y
6 x* K. X. o# P0 h" T; [
pd.Timestamp('20200907') + pd.offsets.BDay(30)1 Z1 e! o, y, u+ X6 O( o
Out[83]: Timestamp('2020-10-19 00:00:00')
9 Q" @! J) w! r0 a9 Y" h' K1% u3 f5 i2 e% _0 R
2
. Q' R2 U: p9 `, o: e3
+ z( m; f8 c; e: W1 D/ o4
1 Q! y' J/ w3 i/ `- ^- B- k5
5 |7 p, R+ \% o 从上面的例子中可以看到,Offset对象在pd.offsets中被定义。当使用+时获取离其最近的下一个日期,当使用-时获取离其最近的上一个日期:
! D! z: T6 Y w3 `. I3 A+ D- ]4 [4 H. ~# ~" Y
pd.Timestamp('20200831') - pd.offsets.WeekOfMonth(week=0,weekday=0)
: \6 J9 G% R( v# WOut[84]: Timestamp('2020-08-03 00:00:00')
4 ?0 Q0 A* A- ^) e8 x1 v" ] ]6 o
$ K( v1 O; j$ l5 X6 o* Qpd.Timestamp('20200907') - pd.offsets.BDay(30): T# W% b7 H I+ Y5 a
Out[85]: Timestamp('2020-07-27 00:00:00')
@' q) I) N& B# Q2 H* ? C5 ?9 d# n9 `; K7 m7 [
pd.Timestamp('20200907') + pd.offsets.MonthEnd()
; q3 E$ J3 a3 A+ rOut[86]: Timestamp('2020-09-30 00:00:00')
$ x) F& _, J t2 R( q: j$ k2 |1; s% }4 Q; F7 r: t/ M2 f
2
5 Z( `, n1 w3 ^) m. ^3. i" M4 |6 V4 |# e
4) B; W7 n! U7 t: \) ?+ k
5
3 [; {0 s1 W- h! t1 g: B8 @& v6
% s: @* w+ e" N; a: v5 e7) N8 r; T. h7 _' ^) f" w
8. a+ r; {1 ?+ Y- g! ?' s
常用的日期偏置如下可以查阅这里的DateOffset 文档描述。在文档罗列的Offset中,需要介绍一个特殊的Offset对象CDay。CDay 或 CustomBusinessDay 类提供了一个参数化的 BusinessDay 类,可用于创建自定义的工作日日历,该日历说明当地假期和当地周末惯例。7 R9 G8 v2 M; F* {- f* a
其中的holidays, weekmask参数能够分别对自定义的日期和星期进行过滤,前者传入了需要过滤的日期列表,后者传入的是三个字母的星期缩写构成的星期字符串,其作用是只保留字符串中出现的星期:3 w2 M- q# y( K9 E
, [9 ~4 i9 n2 Y9 S0 \7 Omy_filter = pd.offsets.CDay(n=1,weekmask='Wed Fri',holidays=['20200109'])+ W1 S& a; U# u! M) r& n4 q+ n
dr = pd.date_range('20200108', '20200111')
, r) T9 M+ c; C8 l% l7 ~% W+ M
dr.to_series().dt.dayofweek
, U/ L1 W8 r; d4 \- `8 o: f8 IOut[89]: % O- F3 D0 P" A4 Y( d, b# ~% A
2020-01-08 2+ Z- n4 U* j( P2 R$ J# m8 j9 c
2020-01-09 3
8 b$ f% A; Z! z0 E2020-01-10 4/ G/ E/ X r. u7 O8 H
2020-01-11 5
0 ^# U% C% ]3 `8 r7 T8 aFreq: D, dtype: int64
$ W& g1 X0 v1 p- V1 O0 X: U
# X4 ], A7 o0 V x3 m9 K[i + my_filter for i in dr]
e# y% ~, i# e& h$ ~' TOut[90]:
; J/ | E7 l) d& H% O) L6 R[Timestamp('2020-01-10 00:00:00'),9 C7 ~1 j" x, m% {, N5 Q
Timestamp('2020-01-10 00:00:00'),1 _0 A5 f6 M+ \6 N" E4 h S/ z8 @3 e
Timestamp('2020-01-15 00:00:00'),
( N- s" A0 ?3 ^1 W4 _4 m4 ` Timestamp('2020-01-15 00:00:00')]' b& ^% V8 B" c6 t3 f
* z" v6 `: |/ v6 \5 }) e+ |
1
& [# ?! m, _- s' [2
3 M4 ]: T8 V. X# \3! H* M0 v3 L/ r! m
4
+ ?) ~9 e2 O" n9 d* i( t3 o, Y* X5
( A$ u0 H1 H% j5 d% [/ L! z68 K; @1 s/ b8 _# U
7
6 t; J5 u) w8 F+ Z& I9 c8 D; s, N2 f7 w
9/ R f& B t, }( d
100 c; J; F# E$ v2 s
11
+ a4 R) x3 D# [6 Q3 ~12# d7 d; K! v+ M* }0 l
13
) D! x# C! v" M. P1 P14
1 z" E5 J& x* }- p8 P15
3 A$ E* p% |% }8 L. x5 D+ T( d16
4 d) i! C' |3 `17
% I5 u0 X0 B" v 上面的例子中,n表示增加一天CDay,dr中的第一天为20200108,但由于下一天20200109被排除了,并且20200110是合法的周五,因此转为20200110,其他后面的日期处理类似。6 D b2 E3 }& ]# ]/ m, n& a
3 I! X: X- c8 Q' y
【CAUTION】不要使用部分Offset
: h6 ?( U5 W" [0 P' [ d4 O% _ r在当前版本下由于一些 bug ,不要使用 Day 级别以下的 Offset 对象,比如 Hour, Second 等,请使用对应的 Timedelta 对象来代替。1 ]6 Y" ^& s2 J/ ]
2 j, y5 ~! @9 R* y$ q$ |10.4.2 偏置字符串
9 |: J" I- e, H4 \' _9 e 前面提到了关于date_range的freq取值可用Offset对象,同时在pandas中几乎每一个Offset对象绑定了日期偏置字符串(frequencies strings/offset aliases),可以指定Offset对应的字符串来替代使用。下面举一些常见的例子。2 H7 \) F r6 {8 Y" ^
0 ^" C- d( g# j( N+ O/ u+ Z% V5 ` Offset aliases:pd.date_range函数中的freq参数,为常见时间序列频率提供了许多字符串别名。 也称为偏移别名Offset aliases。偏移别名列表点此参看(大概27个)。
3 c& ?: |- y3 z5 l& U9 ]: V; C$ W/ G' x) B" E
pd.date_range('20200101','20200331', freq='MS') # 月初! W+ g& i' _8 _8 E* O& w+ [0 N6 v
Out[91]: DatetimeIndex(['2020-01-01', '2020-02-01', '2020-03-01'], dtype='datetime64[ns]', freq='MS'). {. ^" F# Y& ^5 P- ^
* r) Z% k( D. l2 I5 k$ ^; c
pd.date_range('20200101','20200331', freq='M') # 月末
- \7 s2 S2 K7 V6 P9 {Out[92]: DatetimeIndex(['2020-01-31', '2020-02-29', '2020-03-31'], dtype='datetime64[ns]', freq='M'), W7 M+ q K% @8 P
0 Z/ f. y5 `! m, Apd.date_range('20200101','20200110', freq='B') # 工作日. X8 A( \5 v2 o; I- z
Out[93]:
3 e# q. _9 |& lDatetimeIndex(['2020-01-01', '2020-01-02', '2020-01-03', '2020-01-06',
3 H: U! I+ R6 n8 ^4 X" d# z '2020-01-07', '2020-01-08', '2020-01-09', '2020-01-10'],
/ q8 S% V* U0 F* C! E) O+ X2 { dtype='datetime64[ns]', freq='B')
; Q5 @' \0 q; q4 e, q4 j8 L5 O5 Q( w0 \5 l! ~0 q1 s* g
pd.date_range('20200101','20200201', freq='W-MON') # 周一( M S: j+ B3 i# `9 a* g
Out[94]: DatetimeIndex(['2020-01-06', '2020-01-13', '2020-01-20', '2020-01-27'], dtype='datetime64[ns]', freq='W-MON')3 }8 V6 N" O6 g {1 {' ]- C5 W% k# _& K
9 ~0 a$ s R) R7 e$ P
pd.date_range('20200101','20200201',+ u) [) F9 \# T) v9 j
freq='WOM-1MON') # 每月第一个周一* I- T0 W7 x" r1 F" t' d
7 X$ H- N+ B/ _9 x, |
Out[95]: DatetimeIndex(['2020-01-06'], dtype='datetime64[ns]', freq='WOM-1MON'): w/ i3 l2 v4 L* B& n
/ V" v. y9 D- y9 A; U& n13 L3 a. `$ t0 f0 N7 i: ?
2
/ E; N) J6 U% ~# r7 F; c30 [( M- T; j# B; l9 Q4 W
43 p; D1 d J. z4 f# a" @ D
5: d. A) G( Y" V
6
: d1 f+ r$ J# B: `! s+ |, S77 T. J! P4 S0 |+ u7 }3 a
89 T& e6 s# s% Y1 Z
9
9 L& U9 i2 L, v! Q! [10
; t9 ?4 W0 Y0 o* V; }11
/ l4 [8 n- ~9 X/ h. v- b128 a" W8 O/ Q' R$ n% S! R
136 H8 I7 U: K j3 l2 X
14
8 [- s, q+ O0 q15' W5 w d5 O! ]8 I
16# \$ H9 R7 t; V6 b$ {9 D4 W
17$ t$ z) @# }9 Z, m* I$ e4 }/ S
18
" U' Z) m+ e E# p% z19
) y- G9 f! @" P上面的这些字符串,等价于使用如下的 Offset 对象:4 G$ o4 r, z* Y+ ^" j, s& e
+ H1 p$ ^8 ?& `3 B
pd.date_range('20200101','20200331',6 B( K( s2 L4 }. D3 X8 w
freq=pd.offsets.MonthBegin())
4 P# x; E2 P8 w& o7 e
* c4 Y* t4 f6 L& T% }3 kOut[96]: DatetimeIndex(['2020-01-01', '2020-02-01', '2020-03-01'], dtype='datetime64[ns]', freq='MS')
: m z# b3 d# h; `& f, v) r! U9 v6 N- k$ i
pd.date_range('20200101','20200331',5 e2 I% u3 r" ] ^2 P
freq=pd.offsets.MonthEnd())- g0 C* H) w0 s0 l
( w6 W: a+ ?! R! }Out[97]: DatetimeIndex(['2020-01-31', '2020-02-29', '2020-03-31'], dtype='datetime64[ns]', freq='M')
}# v7 B( R, w! M) W( ] w9 U3 C: k- o1 e2 k0 c4 f/ Y
pd.date_range('20200101','20200110', freq=pd.offsets.BDay())
3 y6 ^; d# P& y* D, i6 tOut[98]: " ^( Z# G. S2 Q. D0 [0 F) u
DatetimeIndex(['2020-01-01', '2020-01-02', '2020-01-03', '2020-01-06',
/ D2 |, p4 `' z '2020-01-07', '2020-01-08', '2020-01-09', '2020-01-10'],
3 V% ?# I8 s8 e5 |1 o dtype='datetime64[ns]', freq='B')+ Y# k, q% F z! m0 Q
% n* p& w8 s: W4 b7 epd.date_range('20200101','20200201',
7 n4 n9 w+ L. c; I' } B freq=pd.offsets.CDay(weekmask='Mon'))
2 n: L3 F5 ?, e1 e/ K, {3 X' s- o, q; z6 h1 }* z
Out[99]: DatetimeIndex(['2020-01-06', '2020-01-13', '2020-01-20', '2020-01-27'], dtype='datetime64[ns]', freq='C')+ A' ?# K- J2 U" h! Q
& m- R" I& g+ G( l. N+ j, u6 X
pd.date_range('20200101','20200201',. O! ^& k& e# {0 x j' V% b$ u
freq=pd.offsets.WeekOfMonth(week=0,weekday=0))4 m( M3 ?) {+ Y
! A3 t6 }0 I( j2 T: Z% I/ [2 D/ mOut[100]: DatetimeIndex(['2020-01-06'], dtype='datetime64[ns]', freq='WOM-1MON')
/ `7 _( B0 W* q7 I( v2 n: C' s5 U: G' t Z+ ?' Q
12 j1 g5 b! E+ O
2
+ n o, |+ Q% |7 D8 f" S37 V0 \$ `* A1 K& k& z7 w: j' G
4
5 m, ~- Z R+ u8 v: M( z5
4 F6 |. z& t' m5 V8 n6# K) t0 P4 O2 X2 f6 z* K9 F
7
! s9 z2 `6 T, r: D8
$ e0 K6 E& I& X7 @4 Z! f4 Z9
* Y' D8 u( O5 q$ z T: e10
8 f- f7 f2 U! B/ _11- e2 v# x1 U+ C0 N( z
12
6 ]7 C8 l- d# M3 M! t7 J1 e, T% y13
$ V0 Y& q( Y( B" @/ j147 W5 |0 U4 `1 E. k
15
% e; a- T8 J/ E: G( J' \. I% ^16
9 R. D3 w* G1 H( J1 O17. K3 j. U z: y' Z/ ?
18
l* c! ?8 A) ^0 E% J6 W ~6 @9 Y19# U3 z Y2 O; f( a) Z* }3 g- ~
20
d4 d/ E0 c+ V) I21
" l/ A2 Y& i" ^ }5 C22
; K- D& Q/ Q4 C+ H23- g" ?' P! h, ~( ~+ V# N( a
24
# P8 H$ _1 n! D+ _3 U; M$ f25 _- ]9 n* ~0 `! M. ]
【CAUTION】关于时区问题的说明
* p% p- _, V% r) [; ] 各类时间对象的开发,除了使用python内置的datetime模块,pandas还利用了dateutil模块,很大一部分是为了处理时区问题。总所周知,我国是没有夏令时调整时间一说的,但有些国家会有这种做法,导致了相对而言一天里可能会有23/24/25个小时,也就是relativedelta,这使得Offset对象和Timedelta对象有了对同一问题处理产生不同结果的现象,其中的规则也较为复杂,官方文档的写法存在部分描述错误,并且难以对描述做出统一修正,因为牵涉到了Offset相关的很多组件。因此,本教程完全不考虑时区处理,如果对时区处理的时间偏置有兴趣了解讨论,可以联系我或者参见这里的讨论。* t5 v& e6 A7 `
% O( P; D* b" r! E0 j0 E. |$ W10.5、时序中的滑窗与分组! [9 A+ | F; g
10.5.1 滑动窗口
/ b% j) [1 j$ D; C$ F9 O 所谓时序的滑窗函数,即把滑动窗口windows用freq关键词代替,下面给出一个具体的应用案例:在股票市场中有一个指标为BOLL指标,它由中轨线、上轨线、下轨线这三根线构成,具体的计算方法分别是N日均值线、N日均值加两倍N日标准差线、N日均值减两倍N日标准差线。利用rolling对象计算N=30的BOLL指标可以如下写出:
4 ^; B6 K" g* C
0 Q2 F3 r7 d; w( wimport matplotlib.pyplot as plt
1 j' D: T/ s% sidx = pd.date_range('20200101', '20201231', freq='B')
# _2 S" a" ?+ f3 T0 a0 nnp.random.seed(2020)
. ~) c1 b E5 L3 ~4 I
: R% v$ V i) t2 vdata = np.random.randint(-1,2,len(idx)).cumsum() # 随机游动构造模拟序列,cumsum表示累加7 H( B% A6 a0 k6 ]0 v5 ], j- }
s = pd.Series(data,index=idx)1 k; N* S& |. ^ Y5 j8 K4 J
s.head()
7 ?2 ]: c0 J2 ?% V2 l/ SOut[106]: / M( Y% ?/ H7 n
2020-01-01 -1# g/ |9 J! L6 V: G
2020-01-02 -22 c* |; Y% ~! B& r( c( q. f
2020-01-03 -1
: s5 L+ ]+ |6 u5 @5 g/ A4 D! C2020-01-06 -1! [4 F/ y9 @, \: Q n
2020-01-07 -2- h9 @2 ?3 Y+ m) Z/ A0 q
Freq: B, dtype: int32- i& s. X3 b- j! O1 U7 M" z @( l
r = s.rolling('30D')# rolling可以指定freq或者offset对象
! Y! @2 @* G$ O8 P1 l& i
9 B! h% P9 e& a2 a8 P) N Tplt.plot(s) # 蓝色线
$ V( Y% ?3 }9 {# o* G8 a+ s3 qOut[108]: [<matplotlib.lines.Line2D at 0x2116d887eb0>]% |. @- D) P* I% a7 A8 n
plt.title('BOLL LINES')
' x* x! f2 d7 r) x9 o7 Q" Z2 jOut[109]: Text(0.5, 1.0, 'BOLL LINES')
2 C q$ N+ @; e! D, _# n1 z- f3 G' g3 x8 Q* K! U& g
plt.plot(r.mean()) #橙色线
, s5 q/ [, {' H1 a, {Out[110]: [<matplotlib.lines.Line2D at 0x2116d8eeb80>]! \4 o( u; F: V- u
- A4 G( T+ a: ~& Splt.plot(r.mean()+r.std()*2) # 绿色线
* d3 i0 h1 v4 \5 IOut[111]: [<matplotlib.lines.Line2D at 0x2116d87efa0>]
6 y6 m& U- } m9 r) f6 J, Q3 t* J0 s2 ~" e+ J
plt.plot(r.mean()-r.std()*2) # 红色线5 g: W4 b% J, J' m$ z* \$ x6 l3 a6 m
Out[112]: [<matplotlib.lines.Line2D at 0x2116d90d2e0>]
6 b2 P, C' ]5 o
- P1 P' G8 k; q. s1
6 q; M% E0 y$ Q- P! d: w2% `+ {5 Z M; K l0 ?
34 }* {- L1 S4 g! U, E$ ]* j; F
4, P3 i( S/ I3 \5 c v
5) B q& X) l M% _) H
6; j x' a. G8 d$ B
7/ s1 D" t4 H# P( t) v. d/ g
89 R5 C$ |5 q7 P0 ?
9
2 e1 |; U* e3 } h$ t10: ?& t3 V+ K! } f! X! |
11& J7 I/ @. k% A1 K
127 d5 q: |5 ~- H
13
7 y0 N; i) ? o# x4 ]9 e- N/ ~14, I2 u ]" d" `& C0 l7 W5 C
15! `0 f; T% Q0 ]% I
160 H0 f5 R, b5 ^# B6 a
17
L6 n: l; N* ` U9 i+ G18
, m& B+ b% G7 ]19
7 N6 \# I# `3 |1 _20/ h+ _7 r6 y' v! D$ Z
21& E0 ^- ^9 d4 n7 {6 B* O
22
' ?* K5 q2 a4 p$ o- s q; s23( ]9 r; w# U3 A7 t% t! I; g
24
4 E) E3 B6 G, b5 Q* }$ A6 {3 F250 K+ o" Z. I6 s7 i. K- o2 v7 C0 Y1 h
26, X, a9 O9 t2 i! k
27: f" n* ^+ W% w" n4 }9 x
28' v/ |* \6 O. a$ M" U& N6 K, \- c% B
29
. D4 m9 J: N# [% s- l/ v0 V! g, G# g
+ m6 j% N) E# Y1 q. h8 ? L. n% L 这里需要注意的是,pandas没有实现非固定采样频率的时间序列滑窗,及此时无法通过传入freq字段来得到滑窗结果。例如统计近7个工作日的交易总额。此时可以通过传入多个函数的组合来实现此功能。2 c! w7 m i3 A" s$ |, n3 O
首先选出所有工作日,接着用普通滑窗进行7日滑窗加和,最后用reindex()恢复索引,对于双休日使用前一个工作日的结果进行填充。
7 \3 G- {7 `( ` Q1 P' j+ [- t3 I$ D8 v; y; D; V
select_bday=s[~s.index.to_series().dt.dayofweek.isin([5,6])]: K/ p$ X; }, J. Q- @
bday_sum=select_bday.rolling(7,min_periods=1).sum()
. O7 i* A7 d1 L2 a% Hresult=bday_sum.reindex().ffill()6 f/ o( h3 K6 s0 j4 \$ P3 z) ?
result% C* O1 g( v/ c" b7 h" s) R/ L0 k; [( @
4 V% ^; G5 `) i3 c. ^2020-01-01 -1.08 a8 R# _1 J6 b3 x9 L
2020-01-02 -3.0, `( {: {3 u- i a- ~* `
2020-01-03 -4.01 q, |) p" Q; j5 J
2020-01-06 -5.0# s5 \. Q* g1 V; y: H
2020-01-07 -7.05 U. X; f; Y& J: @, A
...
2 c6 C. e0 L- R; l, E5 r4 L2020-12-25 136.0
8 h8 y! L8 \! [/ [8 H/ s8 i2020-12-28 133.00 J( \# F: z7 V0 V
2020-12-29 131.0
: H ]# r% U" n7 I) U; M* g, j. u2020-12-30 130.0
1 O, S2 \3 Z, \/ X j5 X$ r$ C2020-12-31 128.0
* t% T8 t7 g. g6 D0 q* i& q5 E2 RFreq: B, Length: 262, dtype: float64
% E. U ?) y, U' H3 D4 }, D, Q6 C- w8 x& q
17 S# l/ G" l! k" U" o8 F
2
. O5 [0 q2 T& b9 B$ h3& J: T! f, h. K* I
4
" K! c3 @8 {- }# @3 I5& `# l- G& C9 z4 v# g
6 r! D* U9 a; }) i6 ^
7
. v- o ~: y! L0 f8 [8, H6 N; }: }8 R- m* `7 A- s* C
9% C/ a5 }2 z" ~) ]3 Y0 v( Y
10
R3 |+ i# a( N6 F j11+ ], @$ K2 W& E+ w8 p2 b
122 C0 ~2 l- w" _ f/ e
135 J* \( X; A- t3 K" \, c
14
; f& i) u- x; G4 U157 g f& q4 O6 m: W1 C2 E
16" s- A! G% p/ ~) S- z5 u4 v# J
179 l4 g* [/ E0 X1 S: s
shift, diff, pct_change 是一组类滑窗函数,它们的公共参数为 periods=n ,默认为1,分别表示取向前第 n 个元素的值、与向前第 n 个元素做差(与 Numpy 中不同,后者表示 n 阶差分)、与向前第 n 个元素相比计算增长率。这里的 n 可以为负,表示反方向的类似操作。& V8 N$ u( g( l# ^
7 F5 y! @1 |5 [: f8 P
对于shift函数而言,作用在datetime64为索引(不是value)的序列上时,可以指定freq单位进行滑动:
- T7 _' f& `- v* T8 k/ K" H8 E/ G* m) I" m7 I. j u
s.shift(freq='50D').head()7 s' q: k/ T$ h8 R4 t: d
Out[113]:
) u* _1 j" r0 y% S! T. T2020-02-20 -1: J& W+ T1 q0 Z. g, N, F/ _5 [; `
2020-02-21 -2
) q5 r a8 S6 s; R8 C6 f9 P9 R2020-02-22 -1. B8 D& R+ q8 T% m! w* ^. o9 ~
2020-02-25 -1
8 W+ e5 |9 q. ^% T( S9 ]$ f2020-02-26 -2
6 L; O6 r! \& _6 e# vdtype: int32
! h, e8 q F) x. M6 z1 N, y1
& J7 S9 t H: J8 }4 d* l2
. U" s. G' T- O, ]. S% O2 H3
: q5 u4 b4 F# s& D5 X. U8 Y) B8 x4" L' V! t. U+ _, }
5' K! I: y* O: p! e3 y- m. K
6
" B' ]6 W5 x: i7/ U* Z2 B0 m% d; U& K/ ]
84 J% L* r/ @, i9 s; H/ x# f+ b! ~" n. w
另外,datetime64[ns]的序列进行diff(前后做差)后就能够得到timedelta64[ns]的序列,这能够使用户方便地观察有序时间序列的间隔:
[" d5 I! {% V$ U9 B
' z+ V" b% \; t9 m1 ~. @3 \) Fmy_series = pd.Series(s.index)% g0 `& R: r& F3 u! t
my_series.head()
: h5 ?# g: f+ B' r# [ tOut[115]: 3 M. Y! t$ K! a+ J1 u6 A
0 2020-01-01
3 Q( a t6 e. K. W: e+ V' d* ^1 2020-01-02
1 n. \. Y9 V" Z& Y Q1 g2 2020-01-03
/ @* h. _0 \/ N5 z2 A1 P! V3 2020-01-063 L3 E& `" A: @
4 2020-01-07
" Z8 Z; d3 h% u8 x8 p8 \dtype: datetime64[ns]& r- m# A0 p& m8 i2 p+ ^
3 O! g5 N4 A, N, E- qmy_series.diff(1).head()
; R" @9 t% T5 @7 [Out[116]: 8 ?, J y/ p+ b: N3 o6 C: k* y! b: G9 W
0 NaT
. p& R: v( {4 t1 1 days7 f) u U6 t5 p& T2 H9 q3 e
2 1 days; T, k4 x0 ]" h: ^6 j5 X
3 3 days+ ^, b; o2 j, a, i2 b2 c6 h
4 1 days
9 B* ~8 ]4 ` U0 g1 c- r( U" R0 h, ydtype: timedelta64[ns]) t3 j. q$ O. d8 o" N# j9 z
! P# o8 B& J9 C
1- k9 h8 e* Z2 e4 k0 M
2% O; U: t2 n! t2 a
3
# r. z* t. |% F6 F1 D" j4
# F' a; Z* x& _, S2 f: C5! M9 L) j$ y* Z8 ^
6$ W, b. {& u! q2 \3 k E- {) t! H
7
4 L) c4 ^5 M0 {$ U8 n8! c- Y2 e, w& p( w* @9 D; `- `
91 [# w9 R: `% P* u3 q* f+ M
10
d. x' L; j# j% t$ T3 b11
' i) D' V3 S B7 h, X6 G' v12
% [4 }6 g/ l4 U8 n6 h13
9 A- Y% B& X/ m+ O$ v0 l$ g/ d2 `7 {0 K& F149 ~: Z( F) u& Z+ `$ ?* G
151 \4 I% ^0 b4 |* h" u! q3 K$ \
16
S0 h' J+ {" x! x1 n j7 N1 J17
0 m0 \: R6 ~1 s18
& b( k b U1 V \5 ~1 c10.5.2 重采样
, Z2 T0 s9 d% B7 r5 A 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)
L7 K0 o* Z* c6 C) @, S9 B9 n常用参数有:
6 p+ x) f% V1 _9 o) Y4 R" A: B6 p5 c4 y4 ]
rule:DateOffset, Timedelta or str类型。表示偏移量字符串或对象
) @ w: E) H& A( L) Aaxis:{0 or ‘index’, 1 or ‘columns’}, default 0。使用哪个轴进行上采样或下采样
, Q% _4 \: [8 y5 ?' l' Vclosed:{‘right’, ‘left’},默认None。表示bin 区间的哪一侧是闭合的。所有offsets的默认值为“left”,但“M”、“A”、“Q”、“BM”、“BA”、“BQ”和“W”均默认为“right”。
; t6 o$ z) [; K# klabel:{‘right’, ‘left’}, 默认 None。hich bin edge label to label bucket with,所有offsets的默认值为“left”,但“M”、“A”、“Q”、“BM”、“BA”、“BQ”和“W”均默认为“right”。! ^ T% w7 E7 v1 M' g }0 o
convention{:‘start’, ‘end’, ‘s’, ‘e’}, default ‘start’。仅针对 PeriodIndex,控制是使用rule的开始还是结尾。 e% s F3 O, w7 T! \ v
on:字符串类型,可选。对于 DataFrame,使用列而不是索引进行重采样。列必须类似于日期时间。
4 D8 |2 l9 H- Plevel:str 或 int,可选表示多重索引MultiIndex的级别,这个级别的索引必须类似于日期时间。
' V, H' r I! R' M" q1 ?6 gorigin参数有5种取值:
( `. `- t: i( F5 e% T# w* C/ y‘epoch’:从 1970-01-01开始算起
/ E, } A& j1 X6 S" w, H‘start’:原点是时间序列的第一个值
6 F! A* }0 I1 M. K) Z‘start_day’:默认值,表示原点是时间序列第一天的午夜。) u2 p+ y0 c/ b) f) @
'end':原点是时间序列的最后一个值(1.3.0版本才有), c/ ]- P' K9 @$ n7 l0 U$ ?$ P6 W
‘end_day’:原点是序列最后一天的午夜(1.3.0版本才有)
+ J6 B7 i) A5 qoffset:Timedelta 或 str,默认为 None,表示对时间原点的偏移量,很有用。
% k1 S5 X6 A! Z& w( a9 g closed和计算有关,label和显示有关,closed才有开闭。
) `% R& [3 p) o& ] label指这个区间值算出来了,索引放区间的左端点还是右端点,closed是指算的时候左端点或右端点是不是包含。
! ]! d+ q/ g; D% e" ~7 o8 c
3 S( v: N& p/ I) K) [重采样对象resample和第四章中分组对象groupby的用法类似,resample是针对时间序列的分组计算而设计的分组对象。例如,对上面的序列计算每10天的均值:
- f* d) O1 ^- G7 G, Vs.resample('10D').mean().head()
( y" S$ s1 y: U% n! K- B+ b6 dOut[117]: 9 b0 j( f ^2 e% Y3 s
2020-01-01 -2.000000, a6 j& i0 e* f" k* _# b# _
2020-01-11 -3.166667
- r5 Y- R: k) S2020-01-21 -3.625000
+ t1 m5 Y: m( u( D2020-01-31 -4.0000003 [$ F& @5 o( u4 d9 c
2020-02-10 -0.375000
/ ^7 E1 ]/ l7 }* e2 `" jFreq: 10D, dtype: float64
( {1 C6 Z' [. u% z2 p; a! f4 n1. j5 e6 J7 B6 h4 m
2 ^& v5 V, Z0 `
3
0 g" j* k+ g! k/ `4" ?+ Y! I' A2 _- f
55 Q, Z3 b$ A% B: F) s% }
6
/ ^9 A+ o5 w- |% a! L7
- ^# e% I \& g# X8 K: w87 U$ X( m3 i1 F0 I* |! i
可以通过apply方法自定义处理函数:; z: ~3 n9 |+ w2 Q( V/ W3 F* D
s.resample('10D').apply(lambda x:x.max()-x.min()).head() # 极差
( ]2 D9 ~4 |$ ^- z6 _' h N
% y% X* [5 l+ `7 \- A% L2 M- YOut[118]:
/ ^. J% b0 ^& D: q' p$ L' R% |; v4 C2020-01-01 3 F. r- [9 n7 m4 Q% a6 Z+ C7 j) c* Q
2020-01-11 4
f6 |" ~4 E$ K) W$ t2020-01-21 47 z$ C' e5 ^1 N* s: [
2020-01-31 28 ?+ }4 G' S5 z; B+ R: M
2020-02-10 49 u5 a, s! Z' F* t
Freq: 10D, dtype: int324 q" k, \0 N, s6 x
1
) A, D/ c7 B, m4 G! j ~4 i2
# K0 G7 D$ g" `! S9 d3$ J7 B! }% c4 O1 B1 G" z
4, J; r' G: d: L
5
0 v' d5 X! @" f6
Y* F) W) x# s+ ^" d# s) g( O7
7 l- T. o: f/ S; Z N3 C# ~8
" `( H& x% T3 s! c9
4 p# ?' L6 A/ k 在resample中要特别注意组边界值的处理情况,默认情况下起始值的计算方法是从最小值时间戳对应日期的午夜00:00:00开始增加freq,直到不超过该最小时间戳的最大时间戳,由此对应的时间戳为起始值,然后每次累加freq参数作为分割结点进行分组,区间情况为左闭右开。下面构造一个不均匀的例子:7 e- ]+ l: o" K! K
/ |+ p; [9 [5 }$ zidx = pd.date_range('20200101 8:26:35', '20200101 9:31:58', freq='77s')
! w- o. O; |. `' ^8 udata = np.random.randint(-1,2,len(idx)).cumsum()+ u, ` w2 T! Y2 I3 Y2 p( l
s = pd.Series(data,index=idx)
) Z- n1 l7 w' B" ]- s( q/ I+ vs.head()8 T2 S' r W# I1 t- i
! p% l! \3 m4 L% b
Out[122]:
% ?! E# J( O. T6 x, J% m" c2020-01-01 08:26:35 -1/ N# X' p/ }$ l0 z' s- ^; P. Q
2020-01-01 08:27:52 -1: F8 p0 `3 Z" R% \1 v- n9 s
2020-01-01 08:29:09 -24 I/ N& {( D7 @$ q
2020-01-01 08:30:26 -3# {& v6 j% G1 s1 {, g6 f' V1 F
2020-01-01 08:31:43 -4
* s0 u9 O. ~* JFreq: 77S, dtype: int32$ I; `2 Q+ R; P5 c, Q
14 B+ H* C/ @# m/ \, Z. R3 B
2
0 E5 d, [4 L3 v8 @( U0 D; E6 ]35 g+ l" T4 T2 o) ~# @$ ]
4$ X% Q2 N# K7 U( \: _1 B
5
: t* s# e3 z" Z1 }: s6
4 K( F& x! g5 d" b; `( f7
, R$ }; M) P5 l- L# Q j4 H6 g- K8% T* {1 d0 C) R9 B+ r" I6 N6 U( W
99 D) U4 k9 O. V8 @0 W; ^2 n: d# o6 X
10
! |% T+ z& ^. h! g! {5 ~11# r9 A* I3 i7 L$ l: b% a7 R# G; e
12
$ d2 V' {: c s9 R0 h# X/ u 下面对应的第一个组起始值为08:24:00,其是从当天0点增加72个freq=7 min得到的,如果再增加一个freq则超出了序列的最小时间戳08:26:35: k! k/ [2 h7 W, T
0 H7 E' |+ Y" i" b
s.resample('7min').mean().head()7 S+ z. P4 [0 n8 E1 ?* k3 f
Out[123]:
' N& F9 V+ |, j* Q( |( _1 \2020-01-01 08:24:00 -1.750000 # 起始值,终点值包含最后一个值
: H" G2 G+ K% O2 i4 d9 ?2020-01-01 08:31:00 -2.600000
1 G" Y; T' U) Y$ d2020-01-01 08:38:00 -2.166667
* n: l* x2 `$ _6 F- H2020-01-01 08:45:00 0.200000
2 X3 f/ n+ h; U* v# }' F5 Y2020-01-01 08:52:00 2.833333" O1 [6 ]4 o! G9 G0 k7 U
Freq: 7T, dtype: float64( v; Q; R6 a9 n+ S2 |
15 O" U, |* a7 y- J, M! z5 A
2# _) E: Y( n( L; c3 P
33 C( o! Y: l$ q% F
4
2 w7 q& u! G. I. k9 |8 W5% y7 y. V0 E4 s8 W. t' y+ b
62 E- |! c: u5 Y3 Q9 k1 f
7
5 x; \# _$ f" `( Y& n8$ @% \1 e. \0 N( k" C% \
有时候,用户希望从序列的最小时间戳开始依次增加freq进行分组,此时可以指定origin参数为start:+ z* a1 k2 B3 f9 ]7 Y3 i
' h3 j5 R" v& us.resample('7min', origin='start').mean().head()
- E7 Z7 Z" s& o: U1 Z5 MOut[124]: , U8 o# x5 L( T5 r; R/ |! x P* g3 L
2020-01-01 08:26:35 -2.3333331 w- a8 E" b) [( y! e; t, j
2020-01-01 08:33:35 -2.400000" }2 v x y; }- ^
2020-01-01 08:40:35 -1.333333
' t( i& f$ ~" c" v2020-01-01 08:47:35 1.200000
& C5 D# S9 Y! B! o. Z2020-01-01 08:54:35 3.166667
6 q4 Q% r: s% r5 U% c4 _Freq: 7T, dtype: float64
1 X# g7 f- x1 i7 [0 X; L7 w4 x( H3 @2 A14 g T" D( u7 G9 f# k% c9 i( _
2- G4 @+ {6 A$ o* P* A
3! f# l( d( D( M* s" `5 W6 c3 u
43 j; x- L4 M2 W @1 A/ F- H
5
/ K2 I; [* y( X0 B9 \! _, J6- H B" u' b' Z% m$ a
7; m5 r$ T$ `% C% V" Y! l; e
8& ? w0 ?- P y8 `1 _& c
在返回值中,要注意索引一般是取组的第一个时间戳,但M, A, Q, BM, BA, BQ, W这七个是取对应区间的最后一个时间戳。如果想要得到正常索引,用’MS’就行。/ S7 i8 S' H2 H
- Z7 ~) a# Z* u8 C; w' `3 F
s = pd.Series(np.random.randint(2,size=366),# O8 q# B5 o; i! X4 J4 @
index=pd.date_range('2020-01-01',
, V9 O) h! A( r: M '2020-12-31'))
% R7 I1 k! ^+ ]7 i/ Q, Y, Y1 O( g8 X# |) O$ a8 Y- t
9 ^& A, N, M( Y" w- Z! Hs.resample('M').mean().head()
! y2 b, H( C" _. ?/ e; G, XOut[126]:
! H7 S, d! a% ^/ v7 w. v2020-01-31 0.451613
' r& n1 ~0 G# ~) U( U v2020-02-29 0.448276$ Q8 V5 H, L) ]6 w* I$ @' Y* Q
2020-03-31 0.516129
G/ v. Z$ j% \: C2020-04-30 0.5666675 _2 V6 h$ c+ q! A4 B# ]
2020-05-31 0.451613
2 j) D1 I: G+ g$ [% x/ ?. CFreq: M, dtype: float64
# @9 e( k# H7 C- |: ]" D' Z3 ]7 v f
+ C+ s& g* o1 l' Os.resample('MS').mean().head() # 结果一样,但索引是跟正常一样; m4 i& X& ]9 G( i9 ~6 @* F
Out[127]:
' A0 O& H' t3 w. q2020-01-01 0.451613
" }8 X) ^# A- G; K2020-02-01 0.448276" X$ L, D1 @. U& ]" b! K" ^8 D. {
2020-03-01 0.516129: c. r9 g0 n/ e2 G) W7 N
2020-04-01 0.566667- y9 f+ L5 f& U& O9 e, @/ G
2020-05-01 0.451613
: M+ r# g! R8 p0 k& b4 mFreq: MS, dtype: float64
4 D( Z0 K" m/ z* O4 C- ]/ p' ?; |% j/ f' d* w
1
. _+ k' b' Y( c9 g& @5 @. s# P2! G# [) Q1 @9 r6 [. J: w7 D
3
+ l9 l' ]- V' A* i% s% D% X4
q2 H) L9 o) |5
7 T/ T# I( s' A6 Q3 ~& n62 q7 C8 E3 X5 J5 C* p, F# `0 u
71 A4 G. b( A) j8 ~ b. d
8' {9 a0 K P/ H
9
* Q9 I8 T- A$ d2 T0 \/ E/ T10+ M1 l3 }% d- n; s$ D" y
11
9 x# O% T7 x9 d/ u$ y, m: J6 O& N7 X12
) s' G5 I) x( E$ l7 L3 a: C137 {2 |+ M# q; }! t2 ]5 ?
14! y4 {4 s% ]7 S C
15
5 X# m% L; Q9 l. {7 F! M1 `16
3 C. `8 k+ z& G5 _5 ~! O" Q+ i. i17$ J i+ q; F- z& y+ q8 z# E
18' H) _0 t! F, D" C' ]" ~
19
, f; l9 h2 Q9 W! w/ ~3 o k0 C+ Y20
# L8 R ?) C7 |& Y217 R n- y7 E9 U. K* ^" {
22: \6 E" S& G3 z4 Y5 l! g
对于 DataFrame 对象,关键字 on 可用于指定列而不是索引以进行重采样:
* q3 L, ~5 s3 wd = {'price': [10, 11, 9, 13, 14, 18, 17, 19],: @% l9 o: F! M# H
'volume': [50, 60, 40, 100, 50, 100, 40, 50]}. W9 F/ I, c( X+ F: k
df = pd.DataFrame(d)
2 l: K6 m0 {6 h' {, ~df['week_starting'] = pd.date_range('01/01/2018',
& {! a7 d0 w3 p7 L7 E periods=8,
- c, D2 C. u$ C. C8 L: Y freq='W')
1 ?% [$ N' h& V% C/ Mdf
/ f" }4 C/ M6 w1 h, c D price volume week_starting
; A3 |% P0 ?' ~* C3 N+ N0 10 50 2018-01-07
9 y0 O! X& S( Q. R. j1 11 60 2018-01-14 }: g5 n% e* Q. s
2 9 40 2018-01-21) z3 j5 q9 v0 n' l0 \1 n
3 13 100 2018-01-28
, @6 B# \0 b& T" a0 j5 o2 P4 C4 14 50 2018-02-04+ i0 Q' Z& g o% R. s& j9 G
5 18 100 2018-02-11
+ E [3 N* N T4 L b6 17 40 2018-02-18
7 |8 c$ t/ Q3 h8 J. j) z7 19 50 2018-02-255 t+ }; f' z5 M! z
df.resample('M', on='week_starting').mean()
; | @$ D3 b0 D9 Q price volume
. j& z* H+ o pweek_starting
" N! P% x2 @ q6 z$ t( U2018-01-31 10.75 62.5
6 \' o; |, u7 h2018-02-28 17.00 60.07 z# {8 I4 y$ F* q( I9 L8 `+ \
9 G+ I' {7 m$ E* U8 N3 U1/ |; b) e/ q( ~, p% P
2 ]& J( k) S3 E* L6 U+ h
3
3 i; l4 l$ d3 g% z2 E4
/ Q. I5 j% L; F" g) k$ O0 Z5 T5
x/ c' p1 L8 G6$ Q6 L% r1 M- `8 Y7 m% W5 W( T) Z8 T6 h u
76 v) W; b$ o( r' [1 b7 f- P& F
80 Y: Y6 M: n, ^2 ]
9
1 }; c( Z6 v1 j8 K* e- |: g102 O7 m6 {& g5 ?& i }4 h
114 C% M3 B$ ^0 N5 l( B3 U! f
12: g6 i3 X. z1 G! A `6 \
13
8 P, ]1 a) g v% d' M5 Y7 s1 `14" U, j7 k2 E8 E
15
8 E" D3 d p+ R( H& w% I16& E1 |$ o5 ]% y0 W
17
3 ]+ g6 i3 `5 b E& U! l182 W0 i8 w* _0 ~0 _3 g
192 t J; _0 N2 s o3 M/ \
20! ?4 \. ?( O# O& }3 F+ P
217 t3 {6 z& G. y: i7 I; A: e7 T, s
对于具有 MultiIndex 的 DataFrame,关键字 level 可用于指定需要在哪个级别进行重采样。
0 i1 i$ y7 Y2 fdays = pd.date_range('1/1/2000', periods=4, freq='D')' Q, o- d6 F1 g
d2 = {'price': [10, 11, 9, 13, 14, 18, 17, 19],
% h+ ~3 @3 y- [9 K: ~3 w 'volume': [50, 60, 40, 100, 50, 100, 40, 50]}
5 N0 M3 X; }0 \; Ndf2 = pd.DataFrame(" w$ T% ^6 J. `9 S& e q
d2,
7 ]2 |1 N7 m& c! [4 W9 m4 }. T index=pd.MultiIndex.from_product(. w+ d- U3 F" u- V! u: f" v E7 Z
[days, ['morning', 'afternoon']]
; z% G0 B7 h! x* d- g% @ ) d- x1 ~7 u" o
)$ w1 z$ c1 Y2 A, g7 O
df2
4 Y5 q2 `; V1 r' e) j5 c price volume9 O8 W, c% G: K S( y
2000-01-01 morning 10 50
% y5 ~ v) h- ^# C afternoon 11 60 T) i8 \/ f0 p8 k( G5 D- Q
2000-01-02 morning 9 409 u9 o! a @/ B" y
afternoon 13 100
6 @7 l& f4 k0 J5 s8 P$ i1 m1 b2000-01-03 morning 14 50
# D+ e; ?1 h1 I$ A6 `4 A, o4 Z afternoon 18 100
( I( a" D& |- R' p+ D2000-01-04 morning 17 40" k) ]1 G1 Z2 |: V7 g2 m8 |
afternoon 19 50, g6 t7 }$ K$ x$ }% A
df2.resample('D', level=0).sum()
* v) K8 u7 N1 `6 n D price volume
, b' q4 \+ m% r D' f( ?; H p2000-01-01 21 110- [% Y; v+ s8 Z" T- [
2000-01-02 22 140
! ~2 ^6 a5 G% y6 h! a2000-01-03 32 150
' H' ^( B b, F; {2000-01-04 36 90
$ U% p- P/ f3 @) J+ ~+ k8 P# ^
' [1 X2 y3 c/ v7 k% |0 S; w1& X9 P( T; x) I/ [# ?
2" Y) ?1 g2 D7 ^& t+ _) v( C3 o( c8 f
3! _5 k3 F$ u" R3 b' V
4
0 r/ h% V/ f1 ~57 ~, y4 `* Z$ c, J7 ]3 K5 U) F
6; I0 s+ ^# ~* ^% X8 Q$ ~, f
7
9 X! t r R0 }# b. A84 s6 h) L% r& n% r
9/ w4 |4 v. ^1 `0 }; C+ I$ y
10' x# S' c' k9 g, {
11' w, n; Z3 W) P# S7 a
123 x& R6 D( u# O) i4 ~2 ~
137 o9 [9 H1 h8 J4 _- }
143 c* v4 A7 U% |! u1 t& Z
15* g! q3 T; y; [; A, N6 i9 b2 M3 U
16
N2 l2 _3 g/ }# Y/ M) ?17
; `* R4 _) } M9 F18
* i% `: W1 C- R! l19
+ E8 E, h8 \1 t; M20
6 V) y/ o/ f: d( A; D0 ?3 l21
3 T4 A/ j# a0 {( f2 I22
) L. J' w- A; R9 z. a7 i23" h3 X6 P1 Y( u' h4 [) ^7 z9 k0 C
24! F2 ~* V1 J4 {" k6 j0 e2 v. J
253 v% G* r. G5 i/ V; v2 i
根据固定时间戳调整 bin 的开始:
3 |0 h R" U' F' }$ d* \7 ostart, end = '2000-10-01 23:30:00', '2000-10-02 00:30:00'4 `; S5 O+ J: T- v O
rng = pd.date_range(start, end, freq='7min') d A! X) J( R" r5 v& I
ts = pd.Series(np.arange(len(rng)) * 3, index=rng)3 g! J% S( S% n/ X
ts# v* B1 i# d. g% G
2000-10-01 23:30:00 01 q' p* { ]: U7 [5 Y0 I
2000-10-01 23:37:00 3
$ Q5 X4 i8 P1 H2000-10-01 23:44:00 61 @' o" b3 V5 |4 c2 m1 `
2000-10-01 23:51:00 9. \- R. t1 O; F' D) e
2000-10-01 23:58:00 12
a: w6 K7 C( s) I- P+ y2000-10-02 00:05:00 15! D1 Q: {: C6 _* U7 u
2000-10-02 00:12:00 18% Q4 W7 ~4 i3 G" L6 C
2000-10-02 00:19:00 210 A1 Q5 o( L- U A
2000-10-02 00:26:00 24" ~- R1 b/ E% Y& E
Freq: 7T, dtype: int64/ t% b3 \, l6 W! L% u
7 s, _3 t2 u2 `ts.resample('17min').sum()
" U' y9 ]7 c# z4 O/ L( ]9 h2000-10-01 23:14:00 0
0 Q5 Z1 ]5 |3 L, j& W+ E- J' ]2000-10-01 23:31:00 9
7 t, y9 j% }; S( S/ G. `' p" G2000-10-01 23:48:00 21
/ j1 Y" r( W: j/ ?% ?2000-10-02 00:05:00 54$ i* ]3 V7 ?. {! P1 S2 ^
2000-10-02 00:22:00 24
5 K- R7 g! P" I) M1 j- {Freq: 17T, dtype: int64
% a0 g. v9 ~, A( O: y+ D, J g7 e" v8 o3 \- A1 G, n s; g
ts.resample('17min', origin='epoch').sum()8 q% P f7 U9 c/ a7 E6 y
2000-10-01 23:18:00 0
9 U- M+ J; P& }2000-10-01 23:35:00 18! D& r7 N. P( G0 L
2000-10-01 23:52:00 27
% X: k& t: F- [1 J8 A' [# O2000-10-02 00:09:00 39% ` n& @. m( c/ x: {
2000-10-02 00:26:00 24
6 `$ l- D+ b% U; z/ ~+ F( FFreq: 17T, dtype: int64
) {! ]" l; d9 l- L/ C7 \# ^" V
/ y4 \% @4 Q5 e. g; hts.resample('17min', origin='2000-01-01').sum()
3 C5 w; U) X: s9 P2000-10-01 23:24:00 3; E' k Q2 ]4 b! N) o
2000-10-01 23:41:00 154 X8 _. p! p7 z1 Y, Z% ?3 v, E
2000-10-01 23:58:00 45
5 W5 S4 t- f, T: m- D: n, b2000-10-02 00:15:00 45' D: K4 p7 J6 e. y/ i: ]+ a& w6 n
Freq: 17T, dtype: int64
q5 I6 I2 G& F- y
" e* u4 a" X" C+ m" h4 Z. O- P2 P1
) ] k4 h& f2 Y- t) G9 L' R0 s3 ^2# ?- i0 H" G; Z
3& q1 C) q9 ^6 ?; U [6 _
43 a z; y$ t$ q, x% Z0 B; L
57 G; L, g9 b1 E+ T" ]5 E9 q) o
6
! g, I5 S+ w0 Y- \7
2 C8 N) h7 y/ A" f; O8# f% j/ P: c; O, D( s; T
9, }! ?( N5 [; H5 B* u
10
* g7 A- [2 m, K& w) E3 J4 _11- \* R n3 O5 ?6 f
12
6 ~2 L0 c3 Q+ p/ E+ V3 u13) y; M3 _+ e7 l8 S' u. F
14
H1 c- {7 c! r4 `0 ], ^15' a# l" u3 {$ Y1 `, W4 w" k
16
7 u7 e Y. r% L1 j. p( V+ Z173 Q& p: L l2 E7 `4 `4 Z
18, m; ]; U3 v4 y2 f1 U" \7 [
19- F6 A5 b( P2 C; E, a9 S
20
4 g; ] r( i1 a3 [0 {4 r) {21# t( B6 D) B$ |6 S. \( d
22
( O5 d/ f2 J% b/ A239 k' s7 D& H7 k
24' n7 y+ L, X4 y# i) m9 R" {, s
25* k0 o, P8 [1 T
26
" z c e/ h C' q) h27; ^3 z* A1 W% c% H, `
28' D3 h5 U! U* |* y
297 w, `8 C$ O! O/ S+ }
30
+ j5 ]/ Q! ^& s& S3 j, e* Y: K31; `% C+ u! n, r0 t
322 {0 y( x' a( ^9 _
33. k$ x7 m9 ?. S7 s
34) ~2 o6 t+ s& z; R1 v3 v$ a- ~9 V3 G
35' [4 Y3 y1 G) R- Z( `, r5 h" T. u
36
) {% `2 O) q% H% h7 w8 g H- ?2 y. K37; {4 n! f c( [- ?$ F
如果要使用偏移 Timedelta 调整 bin 的开始,则以下两行是等效的:
% J' _# K- v; s9 H# ets.resample('17min', origin='start').sum()0 C2 Z$ V& V6 p7 b& `, M e
ts.resample('17min', offset='23h30min').sum()
+ Z. l$ r2 ^ N) T% d- H9 g2000-10-01 23:30:00 9" @) v) D/ a! b7 O+ Z+ w
2000-10-01 23:47:00 21- d, x+ k6 I8 m( }
2000-10-02 00:04:00 54$ r# e& U( p9 a3 l6 M% A9 c% t5 ?
2000-10-02 00:21:00 24
5 ^% m9 S! q: }8 l, A5 A6 MFreq: 17T, dtype: int64
6 Z" ^3 |% n/ `7 W' s1
+ w& x- b9 x% H3 \' \2
$ k$ w- M/ B2 a6 d' G! D: D4 z$ `3& T) ]! I! |1 J x' s0 z* J* }
4
" q6 t; Q" M5 }/ b: e5
p6 z' i! w$ w4 U% d- a! O7 E6
# n) B% b. p/ q7
' d3 Z Y! K' z6 J+ Q% E2 p10.6 练习
& L! D; D: ^+ t/ @Ex1:太阳辐射数据集
) W% m2 S+ Z4 R' |现有一份关于太阳辐射的数据集:. z' [& {, l/ C" ~2 T& i9 e
* i% [& A( }) y, X$ \2 b& Y7 vdf = pd.read_csv('../data/solar.csv', usecols=['Data','Time','Radiation','Temperature'])
2 e; K4 @) g' [0 L- sdf.head(3)* C3 S6 M7 O' j8 h3 C& x; F9 _
4 Y/ E) M4 s+ V. a, r( `
Out[129]: ; @! \' W: {0 {7 g2 ?7 ~/ I
Data Time Radiation Temperature8 V' J6 s9 ~/ m5 p8 x* t' P d3 j
0 9/29/2016 12:00:00 AM 23:55:26 1.21 48, G& f, t8 f2 \* T. R
1 9/29/2016 12:00:00 AM 23:50:23 1.21 48
! V6 C' h/ z5 X$ G$ W2 9/29/2016 12:00:00 AM 23:45:26 1.23 487 D+ q1 b& j5 N. I/ q/ o
1
$ r% s! t+ v$ D* X) N! ?( i21 n4 A: E$ K1 |# T8 k/ O1 u. Y
3+ H: h n! i, m- `" W
4' O L, w+ z3 N9 Q
5: }6 I& L: v) a+ y/ j! C3 {
67 \. v) Q: I" m' h
79 W# q" O4 ^8 a3 i4 M. @1 A
8' Z8 r/ _" }) M4 B0 H) s* x
将Datetime, Time合并为一个时间列Datetime,同时把它作为索引后排序。
$ r; d' K5 U% N( w1 \每条记录时间的间隔显然并不一致,请解决如下问题:" U$ i: h! x# E( y' [! H# ~6 D
找出间隔时间的前三个最大值所对应的三组时间戳。& o- Q/ I3 _( h
是否存在一个大致的范围,使得绝大多数的间隔时间都落在这个区间中?如果存在,请对此范围内的样本间隔秒数画出柱状图,设置bins=50。, \# d% p+ p1 X
求如下指标对应的Series:. m5 H7 _7 d' X* H; A- X
温度与辐射量的6小时滑动相关系数
. p3 J1 |2 X7 k }9 C" U1 h以三点、九点、十五点、二十一点为分割,该观测所在时间区间的温度均值序列
% f: _8 ]: |" e每个观测6小时前的辐射量(一般而言不会恰好取到,此时取最近时间戳对应的辐射量)
) j u& o4 s' I4 simport numpy as np+ F4 p( q) k* h, e
import pandas as pd) M e$ Z$ y+ B! F$ P( Q
1
8 `$ R! i: o$ M2; i0 n9 s: Z3 M
将Datetime, Time合并为一个时间列Datetime,同时把它作为索引后排序。
0 k5 i4 m$ B& ]* ~+ m& v4 Ddata=pd.to_datetime(df.Data) # 本身是object对象,要先转为时间序列; i1 L* h0 `0 w# I7 q7 O1 e
times=pd.to_timedelta(df.Time)9 F$ t3 y+ {: {' Q a
df.Data=data+times, S% x$ B9 d7 @, X# p
del df['Time']: w Q2 ] o- u. B! T
df=df.set_index('Data').sort_index() # 如果写的是set_index(df.Data),那么Data作为索引之外,这个列还另外保留
, t. Y) n9 h! G6 |8 pdf2 d4 s) h0 S' ?) }* M, f0 I* Y
Radiation Temperature9 _: O2 A( h# ~5 x* P- N
Data
: D" r9 ]! Q' v, ]' Z/ }: n V2016-09-01 00:00:08 2.58 51" D/ @5 ]- o6 ^) q
2016-09-01 00:05:10 2.83 51
4 s) M, e2 u) n2016-09-01 00:20:06 2.16 51
+ m& w3 ]2 M% Z* N& R+ \, @2 [- x2016-09-01 00:25:05 2.21 51# `0 @- R- c1 P5 g4 ~$ [
2016-09-01 00:30:09 2.25 51
6 ]+ Q9 F. A* L" [... ... ... y' W: u5 R3 t/ N
2016-12-31 23:35:02 1.22 41
6 ?" t F0 _8 Z V1 ]& |6 ]2016-12-31 23:40:01 1.21 41, h8 \' E% |3 ~- X* O
2016-12-31 23:45:04 1.21 42
/ ^8 }; `3 Q; Y4 M2016-12-31 23:50:03 1.19 41
# O* u. V* p; E2016-12-31 23:55:01 1.21 41
6 q: N9 q* u( m/ T, U1 k/ Y# M; q; h# ^' j( B
1
& Y" c# e; T" \. ?) j2+ E8 d9 D- \3 K
3
7 t; |0 o+ w. b, F! c( {44 u0 O1 D5 ~& X/ }( o
5
+ V( E5 ~+ @8 z6 s0 |. }9 h6
' X2 h4 O5 X* Y E# X7 V H( \7' K _6 }, g/ [3 X$ L0 T m
89 d8 p4 M. p( t
9$ u' O; p1 f. p3 c+ K
10
. ^& R o0 b8 C: k8 _11
; ]6 e, G' g, k l( ?12* F9 A- T& _1 x
13. L+ c; Q+ [1 {8 l* F
14
; K! u( `) C( Y; {15
: N/ M0 e6 X5 ]( e% S16; N, ~& w5 ^# T3 P
171 i( h0 D4 j3 Z3 J0 X. r# U- w; X
18/ D4 W/ b; C8 P0 e: p
19
2 G+ C4 N b' L+ H/ }: [5 m: s) i7 z1 o每条记录时间的间隔显然并不一致,请解决如下问题:
7 X, d: j2 b& d. T1 Z( x* b& `找出间隔时间的前三个最大值所对应的三组时间戳。
. ~6 \9 l( i/ ^4 O# 第一次做错了,不是找三组时间戳! @6 M! n7 o. d8 V9 z
idxmax3=pd.Series(df.index).diff(1).sort_values(ascending=False).index[:3]! z5 w" j# o8 m R, s! L/ o
df.reset_index().Data[idxmax3,idxmax3-1]
+ H) c2 c+ J o; ~/ K# J) Y% @. Y4 Z' T$ c: @) N' T
25923 2016-12-08 11:10:42- B8 k$ B- M! L4 s Y7 `
24522 2016-12-01 00:00:02
' z p* U7 r7 J+ q0 U7417 2016-10-01 00:00:19 a0 A8 }. m$ r0 x0 k' i8 n
Name: Data, dtype: datetime64[ns]
5 o {" C/ }3 l `1/ o9 J& O# D* N$ l7 l3 b
2
) U0 J2 `2 W8 O/ c' z" ^: ^3
3 ?+ A+ }; Q, q' X: ]# C" e4
5 w/ o, Q5 [+ c: [' A" _0 c5
% C/ j" A3 O6 o/ Q/ A! G% M2 ~3 F6
+ w; \9 v1 o. M' i# b7
4 p) L7 Z* J, `3 q! C8
6 W/ w. M3 {; r, s# Yidxmax3=pd.Series(df.index).diff(1).sort_values(ascending=False).index[:3]
: ~6 E% T! Y/ C2 o5 B; wlist(zip(df.reset_index().Data[idxmax3],df.reset_index().Data[idxmax3-1]))
4 N4 k( s4 Z7 a6 Z! ]! ^ W$ S8 x/ s# P/ Q8 L% j1 t0 m, Q3 J) L$ [. H
[(Timestamp('2016-12-08 11:10:42'), Timestamp('2016-12-05 20:45:53')),& G+ d9 g" u2 G! x( h
(Timestamp('2016-12-01 00:00:02'), Timestamp('2016-11-29 19:05:02')),
( Y( ?' E$ Q1 v! h4 o& N (Timestamp('2016-10-01 00:00:19'), Timestamp('2016-09-29 23:55:26'))] X- I" ^( I, ?! m' E% a
1: i z7 O- E$ Q7 x: ~
2
- C9 l' W8 b& l1 S" r3/ x- k4 }3 [+ f8 p1 J' i9 n
4" t$ ]- z! m, T+ E, c& L
5
* ^+ |7 b: r' L) Y& A7 B8 A8 c61 v, D; ~- _" a, z5 P9 c6 D0 {
参考答案:. C+ P6 v. C+ E+ _1 x4 n
% }8 B6 X. x5 q! ^1 hs = df.index.to_series().reset_index(drop=True).diff().dt.total_seconds(). J4 n4 j, c1 C6 p, w
max_3 = s.nlargest(3).index4 r- G1 O [" R7 ^ d5 i% K* r
df.index[max_3.union(max_3-1)]( s% o. ^2 Y3 Q$ o
% _. M7 t3 l1 }3 y/ a
Out[215]: ) c; Q4 B$ H! C. D' a P5 ]
DatetimeIndex(['2016-09-29 23:55:26', '2016-10-01 00:00:19',
5 r* m6 S$ @5 k5 y& T '2016-11-29 19:05:02', '2016-12-01 00:00:02',( x7 w2 L* s/ ]1 k* }4 e7 u
'2016-12-05 20:45:53', '2016-12-08 11:10:42'],
, s% R* r8 f0 }! C, p dtype='datetime64[ns]', name='Datetime', freq=None)
( C8 O6 z, P o0 C' k+ [1+ e, J& Q% ~, L) D
2' X# @. m$ Z: t9 X4 k4 ~: @
3
D( l: ?3 d& A) a7 E) z X- w4+ D" J. q2 h" L# m
5% `; c" ~ N! i" P- B8 P* W7 p
63 ~& z4 i1 Q$ O. E# L7 `
7
4 m# E% P6 K+ o2 m! J% }1 J) S0 m4 |8 T) _ V- Z7 F4 [: L8 Q4 Q' }8 t
9
4 ~' p- ?6 Q/ y! z1 d$ z是否存在一个大致的范围,使得绝大多数的间隔时间都落在这个区间中?如果存在,请对此范围内的样本间隔秒数画出柱状图,设置bins=50。: f4 t. O* p: _
# 将df的indexydiff做差,转为秒数后排序。再求几个分位数确定取值区间
5 u+ @ M1 D+ S. cs=pd.Series(df.index).diff(1).dt.total_seconds().sort_values(ascending=False)& t2 ^, E+ V) ~1 j L
s.quantile(0.9),s.quantile(0.95),s.quantile(0.99),s.quantile(0.01),s.quantile(0.03),s.quantile(0.05), V9 |5 S7 }4 Y6 [2 ]
$ M) }# q7 p8 i+ r4 R# d9 J(304.0, 309.0, 337.15999999999985, 285.0, 290.0, 292.0)7 \ K: \0 p$ \) ?7 @! S2 V' x7 k8 z
1
- o& p* e% L/ t, T X4 U' m+ p2
5 k0 ?' H& H+ `9 r0 ^( f: {' ?1 J7 Q3. z8 E% Q) ^4 Q# c- i- _
4% y6 H2 G8 v& ~" @6 c
5
4 @- o+ w" P* B2 z' O%pylab inline/ N4 h# e4 N8 j, @ t
_ = plt.hist(ss[(s.values<337)&(s.values>285)],bins=50)
; j5 o" M0 h* a; N" oplt.xlabel(' Timedelta')8 J. R/ V2 \1 i7 X& H8 a- |& P
plt.title(" Timedelta of solar")
3 L- V3 U5 `; F1 g1 g4 d16 F- H" `. }) p$ Z" ?# q+ g, ?1 y
2. c. n4 M* N6 O2 f" u
3) _3 W9 @* Z4 C2 Z7 W, ^
4
" _$ d2 C3 j9 U( {- u' F! M, Q
: u1 c9 u4 I! ~) p- Z7 }* z0 `& q# O; e) R: K- k2 n
求如下指标对应的Series:
# Y6 w; \. m( |: V+ D3 v$ Q温度与辐射量的6小时滑动相关系数
- S! d+ y" E7 C# O) [' b& V以三点、九点、十五点、二十一点为分割,该观测所在时间区间的温度均值序列, D) T8 J* g) ^$ Y
每个观测6小时前的辐射量(一般而言不会恰好取到,此时取最近时间戳对应的辐射量)
" [) b3 v( }1 \0 [8 f/ mdf.Radiation.rolling('6H').corr(df.Temperature).tail()
3 A! O6 }1 F& T( ]6 X5 w. D
. T9 t! `; ]- `# c6 [Data6 z& c" J# I4 }, c0 d
2016-12-31 23:35:02 0.416187 L) M0 W7 r) y0 ~" R. B: k
2016-12-31 23:40:01 0.416565
) D2 r6 e( ?: ~+ \2 I/ v2016-12-31 23:45:04 0.328574 i( i/ ^8 O: d. f5 F) ~* ^
2016-12-31 23:50:03 0.261883
% ~; [3 j3 [) B i0 b% ]( P2016-12-31 23:55:01 0.2624061 p2 ?9 E( N$ A& N9 ^+ h! v
dtype: float64
, v6 y% r3 g; j3 [: U1
( Z4 D* m3 E. \5 m' V0 c2
5 c. F* r+ _4 W* f' C5 y6 K3& A+ R$ g& I; w; c( j. R
4- o7 k0 C; w3 h8 M) G( W# [6 |
5
7 L; O( X9 [& g+ H60 F. n j7 d& q+ _ s
7/ f7 C n+ _2 z" \7 N! {
8
( }5 a0 M! e9 `8 E2 i* _4 U9( k6 G! }" h# b' L
df['Temperature'].resample('6H',offset='3H').mean().head()
) e1 T s& N4 P& U- |# C8 l% Q0 a- D; r$ M: u0 j5 i
Data5 Z v/ p* c8 I- S) ^9 Z; o
2016-08-31 21:00:00 51.218750
3 H( p: i4 L, c/ U2016-09-01 03:00:00 50.033333
; @4 E9 t! A9 V }- n+ e- f2 m2016-09-01 09:00:00 59.3793108 }5 ~; p, ^; ^; ]- A
2016-09-01 15:00:00 57.984375( O' B* P; v& U: D3 @7 Z
2016-09-01 21:00:00 51.393939& }" T$ a9 w% A
Freq: 6H, Name: Temperature, dtype: float64
8 C" U" z+ }( e16 Y( I }- v" ?' g3 m8 k8 ~
2
3 w8 l7 M3 h3 A) @( s# D# `) l3
5 B" o m9 l, d46 j; e5 a2 G; p. s
58 q, u! D x- M4 W# Y
6
) U0 }7 r {) v5 R5 V7
; p" l% z6 ?& m- {0 S8
; w+ O6 {$ {! r9/ o" o' {* j( r6 Y8 ?
最后一题参考答案:
6 v' H2 R A. l1 k+ k( u+ a# A, g0 v; t! p
# 非常慢
$ N s3 c& d2 _) b) ~! O( imy_dt = df.index.shift(freq='-6H')* u' Q: `+ V# n0 S+ X5 t- @) r
int_loc = [df.index.get_indexer([i], method='nearest') for i in my_dt]% ^. ?8 m! n! H2 K8 s, [
int_loc = np.array(int_loc).reshape(-1)
0 V, \8 E5 [0 N8 s5 F4 tres = df.Radiation.iloc[int_loc]
4 E; i: Q0 n/ D4 \res.index = df.index
x; @' M* {8 e* \) }0 cres.tail(3)
& P9 h8 u1 J) F7 Z( a3 l& ]1
% k n% `. @6 ], d" E3 B+ p& b2
S" K- N! d* L$ P+ Q3
2 \3 L* ~ ?# b/ z g47 G& h% `5 o3 g' l
5
; h9 ]0 r8 K2 i s3 Y3 f [% G# x5 w6
& |' D4 X9 N2 O; F( j" m! t7" l. t! A: F3 O3 U* h, k3 x
# 纸质版上介绍了merge_asof,性能差距可以达到3-4个数量级
" S7 J+ _# V! q$ K E# T' t$ k# itarget = pd.DataFrame(
( y0 c3 @4 C* j {
, `' P7 }$ g: S2 S/ q( ^% j/ ?3 S2 I, n "Time": df.index.shift(freq='-6H'),- V; F# P, w2 h$ I) D
"Datetime": df.index,
1 E6 P4 \# w7 G. R& [ }0 Z6 D4 t/ j& ^% L
)
0 _- r* @1 Q' l% |' y8 t6 L# m- q! Z
res = pd.merge_asof(& b% Z1 I: S. k8 M
target,
8 t" X; l2 c, w4 r! @, h- ] df.reset_index().rename(columns={"Datetime": "Time"}),
7 _# p0 O8 {! p' j* [: m6 v left_on="Time",( Q' d/ f3 F. j9 k# b
right_on="Time",
4 K U! m7 r, ^3 F8 R1 u4 E. Q direction="nearest"! U; V1 {/ q% K' X. e% s
).set_index("Datetime").Radiation3 }! \) [8 y9 \8 Z" H( v- x
" b# G" {1 P1 W9 r0 M) p. O. vres.tail(3)% R1 _+ w4 W) z/ A# k+ y' f. e
Out[224]: " E% \3 w. C( b) C! }+ D
Datetime$ `: k2 D, v8 q9 g* Y7 E" B
2016-12-31 23:45:04 9.33 [" \" l4 x. |: M3 U* N
2016-12-31 23:50:03 8.49
% O, T \5 R( h; b) ?2 Q2016-12-31 23:55:01 5.84
) Q0 k; g6 E6 q& U# i. P/ VName: Radiation, dtype: float64
% U9 e/ W# Y. y9 ^) C; E! e$ F: a! j3 [
1: s j3 s5 G! w, I1 u7 U
2; }1 S) Y3 i Z% G# |
3- h, F6 ]9 v* O4 t+ t8 W
4 F4 u) `. H) V
5
/ Z2 h( W: b1 \# q- e; \66 S) a: Y- J p. F4 e2 `0 i
7
2 ^$ X6 i$ g( J3 b+ b8+ O. g( ]0 T$ t
9" T; H# v5 f: t' t7 J
10
& G1 B [6 w8 g. i111 P# Z% n9 k: y8 h
12
. Q# B q* q# L- w& ]" ]13; C+ c9 B. l$ b% {& V
14
, y7 E* i- S' M u$ z7 g155 v: j. Z$ a1 n' y
16
* g4 L- q4 I$ I" F' R6 z7 p17
5 B/ `( v- a x! B18, U2 H" ?: f8 X8 B8 S: o) k" h
19
; H$ v0 J; D6 f# M8 {) H20- S7 X' K# {5 O b7 ]
214 t9 A; s) {- s1 @( @6 c1 u
22. O. R5 l% O) ]5 @: \; D/ V! W0 _
231 U( R+ c2 g6 x k5 V+ l
Ex2:水果销量数据集! o; ]! j* V$ b- a' A: @3 o
现有一份2019年每日水果销量记录表:
9 X: {/ t. G& d" q# y, D; m4 p& g, D& l
df = pd.read_csv('../data/fruit.csv')/ H) L2 k) O7 P Q" K3 f- O$ m
df.head(3)
! N6 H- a! K* {! F9 a0 Q, J/ S" E" Z9 {4 i7 x6 i6 N; {# n" t
Out[131]: , Q* ?: i7 _) ?% p# K1 T
Date Fruit Sale
/ }* {* [" ?# w( g) t# }0 2019-04-18 Peach 15
) f4 D8 u+ l" z# ` M( Z) ?- B1 2019-12-29 Peach 15: E/ ], X5 U* C
2 2019-06-05 Peach 19
. z& `9 S* c \& o$ x" |0 f3 Q1( Y+ m& W' m9 ], |5 u
2
' z+ B# h' F: r$ K1 O. |" g; h3
6 W( ~% i d$ q' r8 \1 F4/ L; d5 B! l4 @ p; e3 H/ c+ w( N
5
6 s1 p8 i i" i% }6
M% Z$ S3 Y ?% } M- a5 {7' V, _ A- X1 C6 d! I0 g
80 U7 e* J. ~$ o6 f+ {, A
统计如下指标:% Z7 i% D- c# J: R& @9 B
每月上半月(15号及之前)与下半月葡萄销量的比值/ L1 _) G2 b q& v- h% @
每月最后一天的生梨销量总和
1 M% G/ a7 U. E, f每月最后一天工作日的生梨销量总和* R7 H- K& v" v* o' r
每月最后五天的苹果销量均值+ i1 [- S# ]4 ^4 g P
按月计算周一至周日各品种水果的平均记录条数,行索引外层为水果名称,内层为月份,列索引为星期。
5 l, J% j1 D+ E) M" Y按天计算向前10个工作日窗口的苹果销量均值序列,非工作日的值用上一个工作日的结果填充。
1 b: b4 R/ h. @$ v. N* k: Gimport numpy as np
. P" t' [' i2 O6 X- Nimport pandas as pd$ l6 e% `! h- @0 f. B4 Z, D: F
1- n: j O* x( c! U4 v/ J
2
$ Y i1 j6 X. ~' r统计如下指标:
- ` l- m' O/ ` w每月上半月(15号及之前)与下半月葡萄销量的比值
; z! O/ r* n+ w每月最后一天的生梨销量总和
, @! N B6 B' n: _" }3 l每月最后一天工作日的生梨销量总和' {- A) ]$ `1 C f
每月最后五天的苹果销量均值
* f4 r }& F5 S7 s9 s# 每月上半月(15号及之前)与下半月葡萄销量的比值
3 d( F2 a9 l: J) z( ?' W$ s4 j! fdf.Date=pd.to_datetime(df.Date)
8 I, B+ h2 J! ?3 z/ Ksale=df.query('Fruit == "Grape"').groupby([df.Date.dt.month,df.Date.dt.day<=15])['Sale'].sum()
% o E4 ~; q+ X5 Zsale.columns=['Month','15Dayes','Sale'] # 为啥这么改没用啊
. V/ j8 a% q* i* `6 }6 T- asale=pd.DataFrame(sale)/ a5 J$ D4 N7 a0 o9 [# `; n
sale=sale.unstack(1).rename_axis(index={'Date':'Month'},
+ x1 E5 }3 d! a3 n& N columns={'Date':'15Days'}).stack(1).reset_index() # unstack主要是两个索引都是Date无法直接重命名
; K% s$ T [* `# j% s+ M( csale.head() # 每个月上下半月的销量
3 j+ `8 `' i) Z, c# u$ N, e, Q( K0 T+ ^! q2 ]6 g( z
Month 15Days Sale
+ M" Y) N6 _' h0 1 False 10503
& ?' `' P9 C2 d1 1 True 12341
7 r$ g4 G* [8 a8 p% M- n6 q" c2 2 False 10001
) u2 J& |% p0 o% ?- g3 2 True 10106
% b7 E- i: T( i0 e9 q. M4 3 False 12814, u/ e1 e3 v* y
# L* F. D' ?" ?, n7 P
# 使用自定义聚合函数,分组后每组就上半月和下半月两个值,根据索引位置判断求比值时的分子分母顺序: q/ l, g& @; Q3 G- E3 `1 ?
sale.groupby(sale['Month'])['Sale'].agg(5 ]$ q; }2 G4 | a
lambda x: x.max()/x.min() if x.idxmax()>x.idxmin() else x.min()/x.max())
* M2 ]' a9 }: W- X% r! s' x' O7 O# n& x+ e6 T* K
Month6 q( B; T+ I+ i% h1 `6 `" E n7 @
1 1.174998! W3 H- b8 ~" Z, o3 h$ B/ V4 ~
2 1.010499$ b, w9 k* M, P
3 0.776338
$ }* t7 Y. V$ Q7 K; k1 d- d4 1.026345$ ~9 t( c* B$ w% f) s5 }+ r4 K, T
5 0.900534
+ y! p0 {# c o- o6 0.9801367 b+ n) f1 V9 f, x/ e
7 1.350960 N: Y, W8 V/ `& o P+ Y# F2 p& ?
8 1.0915849 E3 A& Q" \: v* t1 T
9 1.1165083 M( N" a3 i* b" N# R4 l$ \
10 1.020784, _" H/ \: b) W+ T
11 1.275911% A( j6 U( B" \+ ^7 n- }
12 0.989662
/ I. s3 E$ H1 D4 a7 C fName: Sale, dtype: float642 P/ l) ^5 e0 E6 k
: k" L# r0 j- b2 Y+ ]1
) k9 J6 f" b9 P- g8 A4 {, L2
4 J# X/ F0 K- v |5 m2 }3 x, q! c0 q, u5 @
4
6 C' ^9 r! | U8 e5
9 W4 X( D( q8 Z" P% W& g& y6
' t9 L- r5 V: K8 \! o6 ]$ w7; L* Q6 @2 b* v1 B! J% W
8( l* a4 M# y( P. q1 s' t. m
96 K' V# @5 F# D
10
$ H f2 ~: u4 s& I1 _11% P4 T4 b# S5 W$ [
122 J8 N+ B9 H8 W6 h
13
+ `& M, w( |& G. U6 K6 W$ F14! v* Z+ m& f4 U1 [
15
) H1 z; G& h$ k6 x; z5 Q0 E9 I16
$ t! q3 o* |$ E T17
- [4 g- m, j9 X" Z18
$ `7 h1 T: V. K6 Z S+ ?19
8 E# e) W3 C& D" A3 X$ p$ b! I20& @/ R, q( |5 W: x/ `! @7 R# ^
21+ r1 i8 w2 m: j6 F) Z3 u8 H% O& u
22+ H) k6 e, Q, d" c) L
23
% R* j0 c. k" f3 b- g G24
/ U: C) v# S$ z25( h/ c* F3 P1 ?* j# D/ [/ M0 r
26
) _: @7 l. Q) ]/ d8 |) i, \27 j, h. Q. Q( i; w
28
~# M0 _0 V: c293 R# Y$ g4 [6 O* Q$ j3 i1 N8 [
30
6 L) ^+ ?' ]: ?! U& d31$ o2 Q5 e. ~( Z: }* ^) j% o) Q9 `7 A
32
! i3 j' w: _8 ~6 [331 J7 l% z0 o9 T' k
34
. d7 N) l2 u* W# b( B6 j5 N1 s6 W# 每月最后一天的生梨销量总和. H- ^0 H4 G2 @* E
df[df.Date.dt.is_month_end].loc[df.Fruit=='Pear'].groupby('Date')['Sale'].sum()
" @' D2 p% Q/ o @4 W$ W! r% ]- S( n. H) L/ k4 z! R- m% G
Date" O0 T2 x" J: H) u/ V
2019-01-31 847
; N6 c7 U' X) v! A8 M2019-02-28 774
+ B* k3 g. Y9 O2019-03-31 761
* d* G. L7 F8 Q5 U$ n9 E2019-04-30 648
% W: s* {5 ~! Q0 ]: h2 _1 [% x2019-05-31 616; m% S0 ~' n( B2 A
1
! d- m5 Z7 ?6 X7 h/ ?( ]0 ?2
+ j p" ^2 E! }# S0 \( M- f3
( Z5 U. U$ O! X) J& R. B# b4
0 y; y) f! f1 ]* C, q0 B5: f7 n- Z2 _) l; \, d
6
! T" Z+ D# i6 Z% N, q4 A1 u3 X J6 \7, A" ]1 o$ a' @. v7 ^
8" \2 w! d1 C/ o6 v, @3 i- ]
9
$ ?1 |8 ~: D! @" k; c# 每月最后一天工作日的生梨销量总和
* y8 U/ _+ w# @; T/ @( R! ols=df.Date+pd.offsets.BMonthEnd()0 s" V- N% Z/ E) U( G s$ R
my_filter=pd.to_datetime(ls.unique())* O3 B% |5 `. P/ R
df[df.Date.isin(my_filter)].loc[df.Fruit=='Pear'].groupby('Date')['Sale'].sum()
/ m# d) W8 B+ @7 n. N6 D
$ K" |* Y) h8 h; z0 k8 ?Date% `/ ?; p3 X6 s4 {- w1 n5 T
2019-01-31 847
% P6 k, M/ m8 e% m( G2019-02-28 774: g* d" m ~' c9 [, D# e
2019-03-29 5109 Y* T) Q, }; M4 f. T, q! ]
2019-04-30 648
# v) [# ^/ {' K& h! `. n: q2019-05-31 616
! I# b5 h- @6 p. j$ B' F v" W1# J& Q: Q U) T8 c w' S' o9 c
2
. Q! j/ I. f/ I9 `1 [3# N$ { T' P& A; F
4/ H) C1 L9 h& a7 ^8 B
5/ ]7 a) _0 n3 l- y
67 s2 N* M" @/ v3 i3 u6 u% v
7
5 d" n& n. c: x/ }8 C% y! Y86 q3 I( ^# E7 X% ?; f
9
4 J* J! s8 K6 {) }105 a1 d. U" `" H% Y- E8 S
11/ b% W: q$ z2 L+ c6 _
# 每月最后五天的苹果销量均值% r8 C- u3 c' k
start, end = '2019-01-01', '2019-12-31'
/ S' z3 t: }5 x8 ]3 c9 eend = pd.date_range(start, end, freq='M')" V- q2 u% Q+ n- b, z3 D$ V4 o
end=end.repeat(5) # 每月最后一天的日期列表,重复5次方便做差# J# R( @1 ]! e% M) t+ m8 z
% f6 g7 C5 m+ c$ a
td= pd.Series(pd.timedelta_range(start='0 days', periods=5),)+ v# Y* ] r/ y+ S# H4 A
td=pd.concat([td]*12) # 日期偏置,最后一天减去0-4天
; c2 b) h v0 [( r+ ]( g7 \& Wend5=(end-td).reset_index(drop=True) # 每个月最后5天的列表
3 b0 O4 K8 f2 m. j$ K% b6 l5 ?4 M0 D2 g d) J6 r- Y5 Y7 h
apple5=df[df.Date.isin(end5)].query("Fruit == 'Apple'") # 每月最后五天苹果销量; c8 a9 n" _# q: l* `" X
apple5.groupby(apple5.Date.dt.month)['Sale'].mean().head(): U1 b% B! Y2 I1 V' ~7 G
4 U6 ? @- O/ N. D) P: cDate
. K* l- `' b1 l# H: ^% k, G5 g1 65.313725
$ T8 C, W1 ~2 G; @1 I2 54.061538# `4 B( O6 O" G% C* p
3 59.325581. X9 H# M5 c2 ^7 |) ?6 D% y
4 65.795455; h) u' r& c; ]' [7 |* ?
5 57.4651161 p+ G( p1 _/ B7 ]- S8 r1 r
2 x t: v' d; ]+ u+ w" A6 [7 [4 Z
1 F% a! r( m# S/ l% w$ L% k
2% k Z8 L' P ~! Q9 Y
3
/ O* ]( E. ^8 t0 q( j4
* z) W8 o( F3 j# g3 _$ G5 @58 I0 f! t- F; k! ?/ o! p
63 c$ K9 y% k6 F# w) O, r% {
7
5 u) k4 P$ G( v, ^7 j$ B8- j' Q! D2 k# {. W2 U( Y; U
9- ^4 |) `. z6 _+ U# f$ n7 o4 R' D
10
' v W6 o( {. b+ b, ^' b% O/ u119 k1 p+ Y% a" W
12. B: e1 U6 ^: n. k" n9 r# m
13
" T9 F% x* g, U, Z14
( H$ [ U' p$ M% s9 _* v6 m; p15' ^2 V" E) H& p6 w% q/ v* o j
16
7 w% C" V/ }' \# |- V174 Q( J- r; T2 t" w8 T; A
18
* h7 p( O) Z! \) q# 参考答案:% \/ s: u& o6 O) E- V
target_dt = df.drop_duplicates().groupby(df.Date.drop_duplicates(: c2 X9 b& b) v8 T: F5 y u. Y4 ~
).dt.month)['Date'].nlargest(5).reset_index(drop=True)' L( p* D9 }$ }2 w0 F& p
( K: H* [3 k$ U% `5 O2 _res = df.set_index('Date').loc[target_dt].reset_index(
7 l0 w& U3 M8 v1 N' J. M ).query("Fruit == 'Apple'")
- t d1 {4 ]( q2 T7 X0 g) U4 \# V. [4 \
res = res.groupby(res.Date.dt.month)['Sale'].mean(
$ |- G4 a6 ?7 z8 V4 {+ L( A3 z( j ).rename_axis('Month')/ I1 @; ]' E9 D! @
' Y) [# d) n% N; Y) |/ E x
' [+ E9 C6 s( gres.head()
: k1 _- l. r$ J, D5 { N9 KOut[236]:
* l* `' r) k, P1 B0 oMonth4 D2 X9 I) H! Q. |
1 65.3137250 z7 i8 t- w8 ?7 l1 f3 ^
2 54.0615387 [ |; L. K! _- z7 ]
3 59.325581
; q, m& Z' B% [ R) p2 W! u4 65.795455* S0 x7 P$ X; z6 e/ _
5 57.465116$ d, C8 W/ [* k9 a* Q1 N5 z: E5 C. i
Name: Sale, dtype: float64
- V3 K' g9 t u8 X/ k; X$ F1 p3 |" l. I- r
1; i/ {9 T, c4 L% X5 m
20 l. b( Q$ Q3 Y1 j5 B2 \
3 F: ^0 y# {. J _6 k r2 S
4
+ H- ~8 y* q4 W* v X5
6 n+ ]* f; L! Z6
/ M0 x" v$ \3 d7 \7 k" ] [7
3 o$ W ^) r) I8
( ]" h& q# ~0 D/ A4 t9, H2 g0 d& H' J. x
10' w* B h; Y: Y! b% {
11
5 X, L A7 u5 s1 _1 r129 @$ c. N/ R7 d
132 {4 l# D3 g+ ]+ j
140 ^9 c* b2 l7 I$ ?/ H$ v V( d
152 A6 D% _( _0 g# R! j( n
165 w4 A/ J5 G) U
17# H2 i' G0 B0 R' O$ L
18
5 S; |- Y5 b& i19
" m" d& q/ z$ [$ X% x20
/ [3 k' ^ T7 z( g+ O按月计算周一至周日各品种水果的平均记录条数,行索引外层为水果名称,内层为月份,列索引为星期。( y) d4 F. W: B" L8 i
result=pd.DataFrame(df.groupby([df.Date.dt.month,df.Date. s( e; j5 M3 }8 N6 o. m
dt.dayofweek,df.Fruit])['Sale'].count()) # 分组统计
# c# m2 _5 G5 ?9 |$ h4 z
; \& j @& H4 C2 N% i% u3 M/ Zresult=result.unstack(1).rename_axis(index={'Date':'Month'},
* h7 X- e2 c8 V' N+ c columns={'Date':'Week'}) # 两个index名字都是Date,只能转一个到列,分开来改名字.5 E" K& E% R7 H% G$ X* L
result=result.swaplevel(0,1,axis=0).droplevel(0,axis=1)# _/ F* Y# h% E$ I+ W( X( B2 c
result.head() # 索引名有空再改吧
8 x% v4 O1 f+ x) _
, Y9 e' H5 k) z: w, J Week 0 1 2 3 4 5 6
$ S$ \4 p' e( M8 a2 n6 l- U. q/ KFruit Month % R( d0 H# a9 p6 h0 C# \
Apple 1 46 50 50 45 32 42 23
8 j4 L$ R! `! s2 aBanana 1 27 29 24 42 36 24 35
8 z8 Z( x# o7 n4 s: e: `Grape 1 42 75 53 63 36 57 46# s9 X" J2 Z, k
Peach 1 67 78 73 88 59 49 72
$ d( O0 S! M$ T( XPear 1 39 69 51 54 48 36 406 N2 E/ F! v& [
15 k' r$ Y, k# @$ C" a4 ]
2
& @. n9 a7 X% k- |% H+ k& R3: C2 ?2 \7 P: O1 i; j0 ^, ~) p2 n
49 o1 }7 ], `& o
5
: e+ g( m6 m9 b3 Z1 e6
* i( Z9 M4 d) Q7 M# f! X7
$ l) \$ s7 E4 T( Q8
5 J5 e S1 P3 R. Z& E; I: d9
H8 H* C! B' H, y1 r5 I10: M- Z: `2 I6 B/ W# d. D
11
$ c4 A& y! J. i$ B x# I12
9 ^; `9 p2 |) [; n$ n139 |" Y) Q; K$ C; W+ D# _+ v
14
) W: [, D* `6 F" b15
$ v) W; k; u5 D: ?& b1 K- i! F) Q按天计算向前10个工作日窗口的苹果销量均值序列,非工作日的值用上一个工作日的结果填充。
/ M E! b$ q$ |0 X# [# 工作日苹果销量按日期排序
; J4 h/ e8 F' O; A s5 L( U lselect_bday=df[~df.Date.dt.dayofweek.isin([5,6])].query('Fruit=="Apple"').set_index('Date').sort_index()
) k( H( p9 |$ [ e% jselect_bday=select_bday.groupby(select_bday.index)['Sale'].sum() # 每天的销量汇总. [) n6 m8 t$ v! U; B
select_bday.head()
0 G9 Z* J8 f- f# y+ [
: O- m; a3 _$ {Date$ J7 F" r" @' [# B
2019-01-01 189
* i# E& Y+ ? w5 f; _( _6 G2019-01-02 482
/ L1 `+ X) ~1 D- c2019-01-03 8903 I7 ^) K3 L' Q
2019-01-04 550- _; W& ^: f7 |+ V
2019-01-07 494
/ X! x. f# j# ~. U6 j6 |; M' F
, i. c5 i& f: B& {# 此时已经是工作日,正常滑窗。结果重设索引,对周末进行向后填充。0 j2 Z2 Z3 Z% I1 k7 P
select_bday.rolling('10D').mean().reindex(df.Date.unique()).sort_index().ffill().head()
. h# I) k- Q5 O9 {) P- j- H/ B, x: L8 s
Date
% f5 b5 n+ W8 k0 F5 t2019-01-01 189.000000& W: o0 s! F9 W
2019-01-02 335.500000/ g( V' t- R1 H. z
2019-01-03 520.333333( M6 k7 K0 z C9 c
2019-01-04 527.750000
7 p3 B4 @' Z" [2019-01-05 527.750000
2 k/ b% S5 C v4 _2 [& s( n) B9 a
) O" T# X* p7 T G+ q; q! G————————————————
, y) [' [9 y& @/ z版权声明:本文为CSDN博主「神洛华」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
2 ?& g @* y9 h# a原文链接:https://blog.csdn.net/qq_56591814/article/details/126633913
* E. K* K$ ^1 L4 J8 X; d8 ` |% A/ ^' h( i, D) R% h+ t( ]4 l* w( J5 Y8 D
6 ?: U2 v7 Q3 l# a
|
zan
|