- 在线时间
- 1630 小时
- 最后登录
- 2024-1-29
- 注册时间
- 2017-5-16
- 听众数
- 82
- 收听数
- 1
- 能力
- 120 分
- 体力
- 565607 点
- 威望
- 12 点
- 阅读权限
- 255
- 积分
- 174905
- 相册
- 1
- 日志
- 0
- 记录
- 0
- 帖子
- 5313
- 主题
- 5273
- 精华
- 3
- 分享
- 0
- 好友
- 163
TA的每日心情 | 开心 2021-8-11 17:59 |
|---|
签到天数: 17 天 [LV.4]偶尔看看III 网络挑战赛参赛者 网络挑战赛参赛者 - 自我介绍
- 本人女,毕业于内蒙古科技大学,担任文职专业,毕业专业英语。
 群组: 2018美赛大象算法课程 群组: 2018美赛护航培训课程 群组: 2019年 数学中国站长建 群组: 2019年数据分析师课程 群组: 2018年大象老师国赛优 |
& w8 g% m% K0 Y
2 {- O8 K7 |1 Y
1 Z7 m2 ^ |) l; @
文章目录# }; Q( G# U3 Z/ }$ E M! _
第八章 文本数据- ?7 a0 K7 i) |3 W( O' t
8.1 str对象
! x% D D! N1 \# `" m8.1.1 str对象的设计意图% v' f' |+ V; F( m
8.1.3 string类型- b6 A( _$ C P ?4 _8 N
8.2 正则表达式基础
+ a/ W R& |8 S1 `4 c8.2.1 . 一般字符的匹配& B: `6 m& ^& X& a4 ~
8.2.2 元字符基础
+ D" w7 h6 s3 K, @8.2.3 简写字符集, |5 Z! j: G( v
8.3 文本处理的五类操作: x" l/ X* [: ^6 G, E5 u
8.3.1 `str.split `拆分
" z9 R/ K+ O) A8 {9 s8.3.2 `str.join` 或 `str.cat `合并2 W s% N% F0 c
8.3.3 匹配
5 ^7 [# n( y8 d- I8.3.5 提取
. z# c5 f7 H% j6 Y8.4、常用字符串函数9 r4 Y# M+ o( M# h
8.4.1 字母型函数: O# `& G, \$ W
8.4.2 数值型函数& U* ^( \3 k2 ]0 }0 n
8.4.3 统计型函数
) a4 N$ g0 e0 p9 ^; W8.4.4 格式型函数! I3 B' E) O8 ~8 [: Y& b+ P
8.5 练习
4 D* |) h) y! n$ ^7 TEx1:房屋信息数据集3 r( C: w: V6 w& D) H( r
Ex2:《权力的游戏》剧本数据集
( o8 z. I4 n1 A6 }' `第九章 分类数据
6 f. }$ g. e, |( e$ ?9.1 cat对象2 A) I4 v m0 N* g* e& y
9.1.1 cat对象的属性# V& O# P G- i: w& O
9.1.2 类别的增加、删除和修改! U4 f2 _$ Q+ r8 u: t7 d, Q
9.2 有序分类
k+ y/ v0 u2 |. i9.2.1 序的建立
: d i) d- `7 p" P- {4 Q# Y9.2.2 排序和比较4 f! k3 U) _/ U- J
9.3 区间类别/ j1 @# }' H1 a7 s
9.3.1 利用cut和qcut进行区间构造- Y5 o- O0 W) l, h7 b3 R
9.3.2 一般区间的构造
: q' T3 j4 p7 A0 f4 d" ]( V9.3.3 区间的属性与方法
! N! o1 ~* O6 k/ Q6 b9.4 练习4 o4 D7 u; i: k# |9 L( o! q; k
Ex1: 统计未出现的类别
. L; c& s, R: l' rEx2: 钻石数据集
" J0 R8 J5 z# O, |+ e1 d( \1 s2 _1 c第十章 时序数据
2 \2 `& x8 C0 O6 W. z. I. w10.1 时序中的基本对象7 f: O' Y) w- N& v0 y
10.2 时间戳
& a% Q4 ?% Z! ?; b: X* E1 p10.2.1 Timestamp的构造与属性
) f. X5 U/ y; ]! ]10.2.2 Datetime序列的生成1 q7 O! v% _0 M, r$ m- y
10.2.3 dt对象8 n; O0 H$ g7 V% @
10.2.4 时间戳的切片与索引+ x4 H+ |& \% p' ^8 d/ i3 F, ]
10.3 时间差
( N8 z, X. z/ {$ `) Q) F10.3.1 Timedelta的生成0 B9 y2 w/ }5 c- \2 h* j
10.2.2 Timedelta的运算4 w: E/ {7 Z, N
10.4 日期偏置7 v1 g+ j& ^+ ]' R
10.4.1 Offset对象* R1 d$ s. ? S; _- c8 Y. d
10.4.2 偏置字符串: ?2 C- Q- P5 B4 c9 ?" w" F
10.5、时序中的滑窗与分组
) f! m* p( v, Z% u% u6 d, z10.5.1 滑动窗口
3 g y1 {8 D2 \$ ]1 J' f* }10.5.2 重采样
7 O4 K! Y2 k& _% j! j8 P8 {10.6 练习2 r2 y4 f7 R* {. t; w A& z
Ex1:太阳辐射数据集
% f x9 F/ k: Z: J EEx2:水果销量数据集
* F! f* h& O# J' [1 H# H' c& _ 课程资料《pandas数据处理与分析》、github地址、讲解视频、习题参考答案 、pandas官网5 J* y1 [% i! S/ x" \ N
传送门:& f* m8 H+ |7 p
4 P! n( X! q1 E0 x; C: w3 {; vdatawhale8月组队学习《pandas数据处理与分析》(上)(基础、索引、分组)4 s2 Q; C, z5 T) Y
datawhale8月组队学习《pandas数据处理与分析》(中)(变形、连接、缺失数据)' @% ?/ u3 D0 y: H+ F
第八章 文本数据 q9 Q! V! o- J5 c
8.1 str对象) w# |* v/ `1 V" h! {
8.1.1 str对象的设计意图8 q# r8 p. k: ~& }1 V- p, Q
str 对象是定义在 Index 或 Series上的属性,专门用于处理每个元素的文本内容,其内部定义了大量方法,因此对一个序列进行文本处理,首先需要获取其 str 对象。在Python标准库中也有 str 模块,为了使用上的便利,在 pandas 的50个 str 对象方法中,有31个是和标准库中的 str 模块方法同名且功能一致,例如字母转为大写的操作:$ r1 l' A; ?/ D; g$ d, ~
8 U( Y- T5 @, D1 o7 g& ?var = 'abcd'
) ]! k# X# H v1 [: ?8 ?str.upper(var) # Python内置str模块/ G9 T, S2 G) m! t3 c
Out[4]: 'ABCD'
) m4 {2 |- E$ k- j6 k+ h$ i' u8 Q+ p# [6 |5 I7 w
s = pd.Series(['abcd', 'efg', 'hi'])3 v+ P3 U5 @4 S
. r5 f* \/ s9 g
s.str
: }# J# O- q; j, dOut[6]: <pandas.core.strings.accessor.StringMethods at 0x2b796892d60>: k- A c4 @% i' ?) @3 m$ o. c
% x" ?8 i- S8 j- h4 `) {! _! f, r
s.str.upper() # pandas中str对象上的upper方法/ q- g! U7 B9 m( |; y1 V1 {+ _7 l
Out[7]: / {% L' T- h9 e! v; @# y$ I- ~
0 ABCD
/ ~4 ]9 U& P% ?: G2 R. I1 EFG) H: V$ b* S, [9 Z
2 HI% R5 ~* x" _0 h+ |8 Z
dtype: object
" N X4 C& G/ b+ }2 E12 B! k, ^2 T0 [% ^2 g+ J
2
# b' a: R# R8 k4 X3 S$ X36 `$ S; Y) f1 f; T% |
4, P- T9 S3 U% D3 @2 ], T' b
5
( y2 V5 ~( }1 F9 s, D3 C Z5 b# X6
3 F, \- }! }! j' S$ A+ A* A74 V- }. K& `" q5 j/ K) t& v
8
% M8 h5 M9 l8 [# ?/ t91 c* s" D1 R& `. K- P0 q. j& c
10
4 ? e: y- X/ [* z' W: @11
t$ `$ h& r* H, Q; B2 z& y124 e% `1 @6 d4 t2 L( @6 Y
13
3 g2 d" g4 l" l5 J0 l140 M @2 O/ w$ k' n% F1 Z- A" P2 i# v
15
6 ]* c) V- |* m7 O3 E7 M% H8.1.2 []索引器
. i% J+ {5 n' h& E 对于 str 对象而言,可理解为其对字符串进行了序列化的操作,例如在一般的字符串中,通过 [] 可以取出某个位置的元素,同时也能通过切片得到子串。
6 s7 n' [* M# p P+ A) K pandas中过对 str 对象使用 [] 索引器,可以完成完全一致的功能,并且如果超出范围则返回缺失值:
4 O5 {$ ]/ J) d7 [0 z( R
, [! D/ U# ^2 A$ A# e3 j) v8 Ds.str[0]
- _+ _/ \. R4 V3 ?Out[10]: % [* o. m* l+ H3 o" D9 u
0 a
6 g" L# t0 k$ `" y5 g1 e
& o0 S" ^- J1 n& Y1 j, W$ c2 h, O. r% Z& P$ b) \, k( f$ V" W! ?9 A! \
dtype: object
! r4 n, w7 Y9 Q( v0 n* p; S3 j* ~
s.str[-1: 0: -2]0 \4 K6 _4 c' I$ t6 j4 T8 t% B4 G' l! H
Out[11]: 1 I; J- ?4 p( [
0 db7 m5 \* V' w5 O/ S7 p$ |1 X
1 g$ B4 Y7 m3 F, Y7 y0 }3 c
2 i/ k/ n6 H; S) N, f% q
dtype: object: P+ _; L( L- d7 |1 W& r
$ c6 L1 a G+ b( r2 N2 R4 a
s.str[2]
0 }5 ]0 X+ u! B) `, NOut[12]: ! I2 V/ m3 U- T* W% p7 z! L4 f5 o- d
0 c% X2 _6 }- z5 p3 p4 C6 ~2 K
1 g$ p: k$ S; z) d/ ~2 T
2 NaN; h9 h1 X& N: b9 ?* M5 g) H" M
dtype: object
8 }& P: O5 V7 z- y/ p
; m+ L0 u5 E; {& ]1/ A- Q& L" J5 P! X/ s8 S. j& {
2
4 o% G T/ C4 g) c' }3% ?9 j, H( |! A* U. p7 H
4 a+ S1 e' X7 p9 J* i
5
' H% B+ q" @6 j9 _7 L4 o% _/ s+ V1 N9 h60 U6 s0 f6 h4 W9 K, C
7
+ l* Y0 e* o3 L% `9 h8
) p- i; W, k, s2 m- c8 u9
# L& \: q8 M! N+ h; P1 F7 L10! ]3 E) d4 r, J ?5 [9 b+ J$ ]
11
: K2 ?5 u7 n. W12
0 A5 @- b7 M4 k8 q/ ]2 d13
* {$ t) r' C% w$ R* k14
8 ]' U @/ ?3 F) k* W15
+ S5 j+ O6 {* ^$ I. ]) x* B2 O16
2 s! ^1 {2 r! C3 R1 b: m& C170 a: N# K$ d( I1 h0 C7 i8 C
18
! z% [( z, R" y: d19
/ s1 j% Y4 l# z7 a O9 b3 c) y203 h% b6 B, j" P j. |7 w
import numpy as np! n u; e0 e+ P2 ?
import pandas as pd. H5 l0 S9 a! a% U9 [' D$ R
9 b- T5 q9 f; v7 Es = pd.Series(['abcd', 'efg', 'hi'])0 F+ x9 ?, }3 w
s.str[0]
, J+ s' n- _# s; R" H1
0 n) |" X. H( Z; p' u$ ^$ I2, }$ W; k/ n% _( o0 o
3
( x) c1 L; K8 N8 e4
6 q/ I I/ M* F50 h/ C6 Q2 ]# L
0 a
+ h) i& N; Z( a1 e
5 P! ?- y0 L5 J5 c, r2 ^2 h- C4 Z- Y# Y* I
dtype: object; P5 p8 J2 G+ I) T; {; O, M
1
0 w8 i1 M, `) i5 Q2
/ H% K/ U! M2 B32 U1 u; k% \8 N8 ?9 W8 h
4
& p) t8 Q- T" B; G8.1.3 string类型( ^9 d$ H1 t E( N" x0 ^
在上一章提到,从 pandas 的 1.0.0 版本开始,引入了 string 类型,其引入的动机在于:原来所有的字符串类型都会以 object 类型的 Series 进行存储,但 object 类型只应当存储混合类型,例如同时存储浮点、字符串、字典、列表、自定义类型等,因此字符串有必要同数值型或 category 一样,具有自己的数据存储类型,从而引入了 string 类型。
/ A5 D, E; n( i- o 总体上说,绝大多数对于 object 和 string 类型的序列使用 str 对象方法产生的结果是一致,但是在下面提到的两点上有较大差异:' G. G7 @2 Z8 u. f8 H0 h! `
5 \. A/ P" E' C- T* m# s
二者对于某些对象的 str 序列化方法不同。
6 }5 ` P" q" j; {可迭代(Iterable)对象包括但不限于字符串、字典、列表。对于一个可迭代对象, string 类型和 object 类型对它们的序列化方式不同,序列化后str对象返回结果也可能不同。例如:( G5 z1 o* G+ J2 ?
s = pd.Series([{1: 'temp_1', 2: 'temp_2'}, ['a', 'b'], 0.5, 'my_string'])) |) {$ b2 Y2 H0 x( ^& Y0 ?$ D
s; e$ q' Q- |4 c% y9 X7 @8 S: @: ~" D4 z
1
( n- d1 o2 |( I0 A* {! Y2& H0 l6 f- l8 O% p/ w+ e
0 {1: 'temp_1', 2: 'temp_2'}7 X- X5 I0 N1 X* S9 F! J' e* v
1 [a, b] u) h$ ~3 T S' Y
2 0.5: A5 a. p+ y3 Z$ S2 ?6 Z4 D0 P8 J
3 my_string. f9 k6 U2 j# `# n/ [
dtype: object% G% ^& A' W7 Q. z
1
4 C/ Q8 V+ K( t" Z1 j27 D9 x- V. O. Z# x- W
3
4 k2 `/ L) S6 J8 t) }. i- g45 c: H( G& n8 Q2 O
5
- I' L# {3 R) D+ T7 x' E4 ?s.str[1] # 对每个元素取[1]的操作; @$ J% o- u- x# c
1
& m* Y- {# a8 \6 k0 temp_15 J8 {. b/ [8 t6 F$ Y* y
1 b
3 P& }/ ], S- e1 F6 {: M5 Q2 NaN8 _# P) R8 K4 h1 @+ B
3 y x! E1 W% G' W/ _/ o
dtype: object
7 w# m% `& p, l$ r1$ Z! a! i. A, m- y5 R5 i5 j
28 _1 A7 \; w& M0 O2 B9 m- q l
3
" i1 L9 p( ~. z& L. W4+ o; R1 H& G$ t" [) }/ y }2 E) l. G
5
% K# F- ~4 _7 r4 r/ x* us.astype('string').str[1]$ w3 ?0 x' R6 G# b2 z) Z& r) r" r
1
) k# O1 [: S: J0 1# ?# L8 o4 \ E9 N7 s+ @2 _8 `
1 '
6 R4 p4 ?; ?& L% R. \* E6 y( v2 .
6 F X# J( A- U- \# A9 P* s3 y$ s4 A: v/ d8 r7 J) w
dtype: string
- r2 I8 m( E! Z1 u! ?1 b1
8 z1 A; t7 g9 v& B+ X2
" I% Y. Y4 q5 v2 w* V3 D3
! r1 P: H& U: \. i2 x4( Y, i1 N6 e3 ?
5
3 L* C W6 y, n) x8 Z0 X, m, j除了最后一个字符串元素,前三个元素返回的值都不同,其原因在于:; ]4 V* g+ D1 H+ Q6 r P3 z& ^
& c$ L: K2 H, c" I* k" o& @) v
当序列类型为 object 时,是对于每一个元素进行 [] 索引,因此对于字典而言,返回temp_1字符串,对于列表则返回第二个值,而第三个为不可迭代对象,返回缺失值,第四个是对字符串进行 [] 索引。
- j+ [7 r) {2 Q7 Mstring 类型的 str 对象先把整个元素转为字面意义的字符串,例如对于列表而言,第一个元素即 “{”,而对于最后一个字符串元素而言,恰好转化前后的表示方法一致,因此结果和 object 类型一致。8 H+ G+ B5 ^4 z$ U3 C |6 z
string 类型是 Nullable 类型,但 object 不是$ h$ f, A9 v8 J" E1 J7 j, k" p
这意味着 string 类型的序列,如果调用的 str 方法返回值为整数 Series 和布尔 Series 时,其分别对应的 dtype 是 Int 和 boolean 的 Nullable 类型,而 object 类型则会分别返回 int/float 和 bool/object ,不过这取决于缺失值的存在与否。
& K: g; j0 g% K 同时,字符串的比较操作,也具有相似的特性, string 返回 Nullable 类型,但 object 不会。
/ t. f. Y$ V4 ^: o$ L, ds = pd.Series(['a'])$ Q; ]; \1 \; V9 s( V! W
% l/ w9 y, t* j: `0 \1 G& J$ A
s.str.len()
& Y6 } I7 E( C% SOut[17]: 0 o4 S) ~" Z3 n4 y& R
0 1
% T! f- \; F( l' q- L+ tdtype: int645 `. y9 ^5 w& W2 C& ~( Z, y
6 q$ {/ Q, A8 C; I- c& Gs.astype('string').str.len()% m- c' O- R, _/ @4 x
Out[18]: z" w2 w% `8 H Z& e5 L+ k/ L
0 16 ~1 n7 ?/ A: r) H$ b
dtype: Int648 F2 g9 J2 m1 _7 r, t7 a) x0 r
5 S, w, C; I1 d7 i/ n' Y6 `
s == 'a'
4 B1 q7 d* H% @3 ^% x# f& kOut[19]: 9 \! M9 C0 M" S4 @: b9 J7 k
0 True
! x+ x1 Q1 ~3 q; M) Z2 d) Qdtype: bool5 L, y2 s; S2 J( U( X
" F& L1 k! j/ t" y
s.astype('string') == 'a' b7 ?1 R+ E2 ~# o9 ~
Out[20]:
' U6 x8 q) {3 W& t0 True
6 S' M, X; V* S2 W; a2 Q9 [/ @dtype: boolean1 \. V; r9 {+ R* Q" |& t
+ \ q* T3 u* O2 n+ w: K6 g
s = pd.Series(['a', np.nan]) # 带有缺失值3 J& _6 P) R# c) E v# z' A! Z
# {* Z. T+ H* N9 F& l
s.str.len()- m5 J" D% `/ D
Out[22]:
" a0 i# A9 C8 I R2 K0 1.0" F$ R( M" l# i( f2 |( k
1 NaN
0 k1 x2 J8 F$ sdtype: float64
! ^- U5 G" z v0 N. g4 q6 `! W
7 J4 V0 v( o& r% vs.astype('string').str.len()4 m+ a) o* j/ o
Out[23]: ( F/ f s9 v; \% H0 }- [
0 1 E" x; m! T8 T) f3 z1 ^
1 <NA>
u- B- `2 n$ \dtype: Int64
" A. @& |; r- \# e0 S5 u
) O9 ~9 m6 j) Y# \2 t es == 'a'
( k1 G+ x/ M; W. DOut[24]: # @2 e2 x# q3 k4 \
0 True: x, w4 u) h& u, t- {/ k) g% A
1 False+ F$ y* n, D7 Q# E/ t: `4 M
dtype: bool/ i, m* y: ^' a. u9 S; d! u
9 _! y+ D+ L$ n( l' c/ t
s.astype('string') == 'a'
! \5 f- t" k+ L* I( E2 E' iOut[25]:
0 p5 {1 w/ w1 k% F: s7 W0 True
4 F1 ^9 R, o" U6 j4 m' I, [2 U1 <NA>
& j; ] e9 k. j7 q$ v, b2 Udtype: boolean
2 S2 {2 `+ t1 `6 F) U1 w2 Q+ X, B; s+ m
1
1 h0 k1 G) {# q, ]4 C29 b! X4 I: x9 J" C4 ?# Y T! ^
3
7 {& }: W5 C- s49 D' E6 [0 t; B4 |
5
* E7 ?, V( n% I2 Z6
! S# q5 |5 O9 F8 n7
2 F+ U# ^; L; ~! l1 b2 g& p8
9 @# R9 n# i# {7 i: v' X9
' b. {2 y0 B/ s8 T. q10
' E8 L# w5 U( Q h8 i/ t( k' `112 ^$ _0 b. g" ^( E# j" |) H# R. i
125 F2 f1 m3 D6 U% U$ h' [& q4 |
13
* S4 r. L) O& J14( [& J' N/ ^& R) n7 }# E
15
3 ^: {, V9 ]6 L1 v167 Q; D) @% m" H0 O5 a4 r9 W
17
+ y6 |. A, M# W/ ^18
) C: `) J+ E' q& g19; X! R. v3 B, A" w( G
20
B9 w2 D# _2 f5 |. [21
5 n7 T8 M8 W" r: s22* h2 H& g3 L' F3 e! J. O
23
4 G0 ]' ~. P% a( ^- f24
" m) ~- J. _! N# N# o& e25
- u8 x1 x5 g8 S3 M0 S1 M$ t5 K265 ~6 Y: i/ o! ^0 ^. c: W
27; l( J- A! t3 N! O4 u
28 d6 ~; m# G2 [* f
29
7 x- s% |' r m! j* J30
7 ?( f4 |1 t7 O, Q2 M. i6 A31
. u, ~% ~. P0 R" D32
$ ^ n0 Q% p1 l9 c$ L33% c- K) J2 G, q& |, c& c+ ?+ f9 m
34' P! o# l! I* e/ j
35
- l& c6 l/ _& [& U( f& Y364 Z. b# e7 Z; p/ r' I
37. S, R( k2 o9 z2 i
38& |0 ~5 C, q; E- O) q/ k3 S
398 j. P9 H% F* W2 M& h' T
40
! j* }' W* h3 j% t1 M L415 a( p- @4 p! [7 k4 c) Q/ `% _% W
425 n6 X" _5 B3 ]* r0 y
436 p- y. \' U% Y3 l
44
+ h0 T8 n3 P! w' s8 v% |8 V5 \45- J! m, [) b5 I, \
46
! T# O( |* ~2 Q0 k47
4 J- w4 x6 l/ S 对于全体元素为数值类型的序列,即使其类型为 object 或者 category 也不允许直接使用 str 属性。如果需要把数字当成 string 类型处理,可以使用 astype 强制转换为 string 类型的 Series :7 Z" d2 m% e5 w2 t
$ h+ [9 B; r) R# q9 us = pd.Series([12, 345, 6789])
2 @3 W( c' p3 }
9 w) T' ^8 i, a9 Ks.astype('string').str[1]. y: |, _4 S1 s& h! K/ w1 k+ ]& M7 u6 q
Out[27]:
' L: e* d( l% J. a/ g }7 _. ]2 j1 }0 2 E$ A9 O9 X; C
1 4" Q4 ]/ a4 X/ v
2 71 j6 _. c, [, b" e; }6 Q
dtype: string
8 L9 j/ U" K: m) F- K+ R14 u2 w1 b% P* a. A X. h& z( o
2
6 X9 J! ~ u! o; T7 j3" L( q E/ `$ _8 z5 t
4
) I1 G* R3 a& n0 [, ^5
# `9 p! ?: s- C/ k6
. G6 W0 v3 Q; \4 Z7
+ O0 W0 i9 F, N0 V8
7 Z: j1 _8 l5 ?' {. {: |4 ^$ m8.2 正则表达式基础, ?. A* w0 @0 Z( {4 ~; R
这一节的两个表格来自于 learn-regex-zh 这个关于正则表达式项目,其使用 MIT 开源许可协议。这里只是介绍正则表达式的基本用法,需要系统学习的读者可参考《Python3 正则表达式》,或者《 正则表达式必知必会 》这本书( T- A b4 b& E7 `4 q7 |% }
8 A9 r" ?+ ^( C: I# X* R8.2.1 . 一般字符的匹配
) D+ r* s5 k. m" B/ R6 v) @! F0 [正则表达式是一种按照某种正则模式,从左到右匹配字符串中内容的一种工具。对于一般的字符而言,它可以找到其所在的位置,这里为了演示便利,使用了 python 中 re 模块的 findall 函数来匹配所有出现过但不重叠的模式,第一个参数是正则表达式,第二个参数是待匹配的字符串。例如,在下面的字符串中找出 apple :
7 a, i Z5 e) f) b$ ^0 n1 F9 k! A. d6 v, i+ t5 f
import re
8 x6 v) r! \" C0 x/ s0 P# O7 x4 S( a; _7 o3 g/ Y# m
re.findall(r'Apple', 'Apple! This Is an Apple!') # 字符串从左到右依次匹配
9 V$ U$ q7 R" g/ C8 x4 h# j9 @Out[29]: ['Apple', 'Apple']
# O0 o: _3 L1 L6 O' k" b1
3 ~. R6 W, C/ T; l, M3 `3 s: M! K2; M, ^7 P4 w$ z. a& {; I' B3 l7 C
31 X; d- e7 w% G& r1 j
4) ~4 k$ W9 l4 R3 M
8.2.2 元字符基础
" l3 b/ H* l& S5 A2 @$ U: ]+ w6 j元字符 描述+ e% ^8 G/ d% @: a9 p, |
. 匹配除换行符以外的任意字符
) e8 U6 g: j* z2 T. B3 k[ ] 字符类,匹配方括号中包含的任意字符/ Y% {. K0 [" l- A" i' s
[^ ] 否定字符类,匹配方括号中不包含的任意字符
# E, B, K5 H# ?' x3 K9 [; @ ~* 匹配前面的子表达式零次或多次
* i* _0 c1 B% r5 G+ 匹配前面的子表达式一次或多次。比如r’d+'就是匹配数字串,r’d’就是匹配单个数字
7 ~4 K# z# ~4 h' u$ T! v" A? 匹配前面的子表达式零次或一次,非贪婪方式: s" Q$ z' _# H& P1 x& S) F
{n,m} 花括号,匹配 n 到 m 次由前面的正则表达式定义的片段,贪婪方式% X4 n* Z x- k9 P9 `
(xyz) 字符组,按照确切的顺序匹配字符xyz
/ p; e' x: x2 P| 分支结构,匹配符号之前的字符或后面的字符, m' M' A. l$ r7 M8 J# d# \9 |' A1 W
\ 转义符,它可以还原元字符原来的含义
% b% G3 r5 p" B) s. s+ G( x# F/ Q^ 匹配行的开始
) Z, t) r" @0 J$ 匹配行的结束5 @% U5 L2 d: f+ G
import re
& Z9 w9 Q) V; H, F1 N! `! x) O: ^re.findall(r'.', 'abc')
: q3 ?( y$ c5 C/ IOut[30]: ['a', 'b', 'c']6 O, m/ e/ E0 P: _9 Y
1 p& |& R' u ?' K& V8 b% l
re.findall(r'[ac]', 'abc') # []中有的子串都匹配5 p1 B2 F. y* m( |' v7 i& r! h
Out[31]: ['a', 'c']* X7 L0 s0 ?. }
( I2 v$ _9 h2 }( n6 ore.findall(r'[^ac]', 'abc')
. Z) M% H7 w" o0 k7 {8 e* a" x- W6 BOut[32]: ['b']2 j6 V& {7 f2 v- J4 q" I
- {% n& W, Q3 @re.findall(r'[ab]{2}', 'aaaabbbb') # {n}指匹配n次
. C3 y; I0 X0 P! C; A1 tOut[33]: ['aa', 'aa', 'bb', 'bb']) f, i3 Z l5 @/ |, D
' L2 J# `( s2 V+ Y- h" R; s% d
re.findall(r'aaa|bbc|ca', 'aacabbcbbc') # 匹配前面的或者后面的字符串
( ~" N3 n0 i e K5 W/ I9 ^0 @* HOut[34]: ['ca', 'bbc', 'bbc']6 o9 J5 z9 H/ X
9 v: ~! x0 h' s
# 上面的元字符都有特殊含义,要匹配其本来的意思就得用\进行转义。
7 d! [/ U6 h- @. p"""
4 Y: }- q9 U2 b8 Z6 o5 o1. ?匹配的是前一个字符,即被转义的\,所以|前面的内容就是匹配a\或者a,但是结果里面没有a\,相当于只能匹配a。# e" ?$ c3 `# P( N& q) q, _
2. |右边是a\*,转义之后匹配a*,对于竖线而言左边优先级高于右边& t) j% W' d* U9 G" j: B) @
3. 然后看目标字符串aa?a*a,第一个a匹配左边,第二个a匹配左边,第三个a虽然后面有*,1 N# j7 N* B1 O9 D
但是左边优先级高, 还是匹配左边,剩下一个a还是左边,所以结果是四个a
8 E/ M& S+ u! D+ ]"""
7 P z/ j B' j; T4 g0 {, r8 Y6 t$ c, [5 C
re.findall(r'a\\?|a\*', 'aa?a*a') # 第二次先匹配到a,就不会匹配a?。a*同理。& Q0 p8 m) t2 ~
Out[35]: ['a', 'a', 'a', 'a']
" l7 C5 F7 B) Y$ z2 c
& }- W6 j" A+ u; b8 k# \6 i# 这里匹配不到是因为目标串'aa\a*a'中,\a是python的转义字符(\a\b\t\n等),所以匹配不到。5 C& L2 U9 X* U& C
# 如果是'aa\s*a'之内非python的转义字符,或者'aa\\s*a',或者r'aa\\s*a'就可以匹配到\字符。/ p- W! N0 x5 w+ J( G/ z. q
re.findall(r'\\', 'aa\a*a') 3 |4 ?6 g. z; B9 k" J0 Q
[]/ q3 ]3 U# K V* N
+ q$ a% Z# E9 L+ T7 D2 R1 T# Sre.findall(r'a?.', 'abaacadaae')
. j; s1 o' C7 `" M4 t0 \$ kOut[36]: ['ab', 'aa', 'c', 'ad', 'aa', 'e']+ V) H: E1 L8 E+ `) |: h' J
% v) u4 o3 r+ d2 S A* I4 W$ p A
re.findall(r'(\w+)=(\d+)', 'set width=20 and height=10') # 多个匹配模式,返回元组列表
2 X: A$ [) s, X& ]' `[('width', '20'), ('height', '10')]7 T1 Y% Q( ^" M" u2 @- b0 F
6 S% f1 h' \1 }0 t; n
1
, n6 \* Y! T1 f4 ^. O1 g: X; W2
. J7 m5 \% C; o1 z9 G37 S$ B8 a3 r9 V2 e
40 i T* Q+ x7 ^9 s
5. ], `" A5 I$ s H3 f7 G
60 I+ |0 p! Y# b' ^- p; O
7
1 U, X- C c1 `! ~2 c& @* `8
; p$ b: m) n. z' Z, A9
/ r& ]/ C" B1 Z" V& D10& ?( o% y" n" _5 E. ^* l4 Z
11+ `5 N. a8 e3 R3 M" N; S
12
! d3 g8 v3 V1 n; L* n13$ O1 Y q: i8 I
14
4 F2 ?, x! \( p% d8 }/ w, \15
+ ^: `7 {0 s* p8 u0 z$ {16
5 [1 Q" F: \* G; ?17& t1 x( ^7 w7 E9 r. Y
18. L' e/ y- Z6 L9 S) v; Y% Y6 l* f" w
19
* l- O, L! e- U0 `" D6 v* d, {- f20+ ]4 l0 N0 a) j. q% |
21
8 U) H: L s, P+ R22
7 ~0 a1 a1 {2 i6 ^2 |7 n23 d6 l) b' o6 v2 C b6 w3 p/ x: V' n; h
24. L1 }' M8 h3 F& C1 c! C; a
25: {" H4 o# L8 k8 b, W Q
26
5 Y" t3 w$ ^6 p1 U* H8 c7 u270 q" `. E, f3 r: O' b: r
28
) e" P# g: l1 P" I# c" a( u29* D2 c: [- n' M0 ]$ n8 Z. ]8 }
302 j6 d' R; g& k/ p1 u* f2 F* y) n
313 F; B% L3 w7 x2 Y. |! {
32
/ y/ b6 T0 d6 T4 `33
5 Q; j. Y$ @% F3 h2 w34
8 T9 ~2 l+ O- Y" \35
# O2 t3 f- j1 q36
' }: {/ k$ K$ Y% r' N4 M9 c378 ^! A$ M# J& b- X
8.2.3 简写字符集
0 U( v& A! j& N4 X/ m* u" \# G则表达式中还有一类简写字符集,其等价于一组字符的集合:
# I2 Q c) B) x4 _! T7 d9 C" S6 N- v5 s! W: n! O3 Q2 T/ y
简写 描述( [! G; C: O: f- i. L+ ]- [/ w
\w 匹配所有字母、数字、下划线: [a-zA-Z0-9_]
, w: _ P: {( H/ w3 _8 f\W 匹配非字母和数字的字符: [^\w]
7 b8 O; W7 h) h% p\d 匹配数字: [0-9]
3 M' r1 w+ M; A( x5 `\D 匹配非数字: [^\d] Z3 H2 @3 k0 Q9 p: [6 Y8 j1 k8 a
\s 匹配空格符: [\t\n\f\r\p{Z}]* L8 S' J! J! r! C% X
\S 匹配非空格符: [^\s]
/ @& E1 b3 c& }\B 匹配非单词边界。‘er\B’ 能匹配 “verb” 中的 ‘er’,但不能匹配 “never” 中的 ‘er’。- Y# x K# t, b" i3 f0 K
re.findall(r'.s', 'Apple! This Is an Apple!'). P i9 g9 y' m ?9 e5 D+ y2 f
Out[37]: ['is', 'Is']
7 G w" B" j h# j
' X( T* G" ]$ y* U9 q8 Jre.findall(r'\w{2}', '09 8? 7w c_ 9q p@') # 匹配任意数字字母下划线的组合,但必须是两次
8 _7 k5 h$ j1 U# U u2 R# }Out[38]: ['09', '7w', 'c_', '9q']+ @* n( I$ W ~3 \! I- P2 K, Z& a
; c4 ]* y* u. U
re.findall(r'\w\W\B', '09 8? 7w c_ 9q p@') # 匹配的是两个字符串,前一个是任意数字字母下划线(\W),后一个不是(\W)
5 o+ i% I. j! M/ t8 F! }Out[39]: ['8?', 'p@']/ b$ l' X* P6 D v0 C' T8 o
! j9 \1 T' `; n
re.findall(r'.\s.', 'Constant dropping wears the stone.')
# y y+ G: u# S# ?/ }4 aOut[40]: ['t d', 'g w', 's t', 'e s']
- y6 B$ R; `9 n5 G9 v" ?, F* S
0 ?# ?+ Q$ Y0 |re.findall(r'上海市(.{2,3}区)(.{2,3}路)(\d+号)',3 u! T# z8 V- W* t' G X
'上海市黄浦区方浜中路249号 上海市宝山区密山路5号')
3 d/ b7 E8 P% H! \# ~6 z) W b5 _ J9 U( q4 {$ k) p$ n8 g
Out[41]: [('黄浦区', '方浜中路', '249号'), ('宝山区', '密山路', '5号')]
D& R+ o7 B( N
% U& V- `5 s% p9 t3 r1- c' Y& Q! _2 F( h5 D, _
2& j7 B: o" N4 W- ^" S
3
( T4 n ~' r7 P: ~ Z# l6 K% {4
1 P; x3 P0 _' v4 D$ v# @5
8 T! G1 }* _: C4 b" b1 R6 U6% N8 e; g) I9 l% @* ]
7
2 l/ h y# }1 e% t8
) z! L4 F5 d+ z! [3 s/ a% ?9
6 f. ~* J% F* r2 f' H10! f! ^7 m1 [" S
11
u& q5 N: N( `6 i) t2 d2 v12
3 N' R: [& n) s) U) N9 e* l13
2 U0 `& P6 U% W, j: `14
3 O x# Z! ?* j' S7 B5 q. h15/ D& Q7 L/ ]1 [2 y3 [4 p
16
7 b9 O0 u) c6 b5 h/ \( N" F8.3 文本处理的五类操作
7 ~( q5 q/ a, W) ]% h- T8.3.1 str.split 拆分, b n$ u4 B. d* ?1 \* Q: C
str.split 能够把字符串的列进行拆分,其中第一个参数为正则表达式,可选参数包括从左到右的最大拆分次数 n ,是否展开为多个列 expand 。
/ W" J9 _" y# r$ F9 k2 U: E3 j, w$ j" w; q# z/ {
s = pd.Series(['上海市黄浦区方浜中路249号',
4 N6 ~1 |0 z* Y8 K '上海市宝山区密山路5号']), o( ~& L; B9 ?" U3 |& J: m
- j7 J8 l2 Q/ I4 y& Y( h4 w
% `5 r- ~8 b7 ^- J
s.str.split('[市区路]') # 每条结果为一行,相当于Series, h/ m% B. \( U. E9 [
Out[43]:
+ T! E n1 ~ a' X* C0 [上海, 黄浦, 方浜中, 249号]( e( J; y$ ], A! ?& l9 A+ ~5 }
1 [上海, 宝山, 密山, 5号]
% C+ {# U2 A% X+ rdtype: object; u y0 ~9 L5 n
* W5 h+ z+ b+ e$ h+ n& Ks.str.split('[市区路]', n=2, expand=True) # 结果分成多个列展示,结果相当于DataFrame
9 B. A$ S h! ?5 L4 P" ?" _Out[44]: 4 }+ _! F# n1 u4 C4 _, \5 q, v
0 1 22 I- j5 d* P+ l
0 上海 黄浦 方浜中路249号
: _- F' g8 |+ t+ D, L3 _0 T$ B1 上海 宝山 密山路5号3 l8 x* [& l% ?5 m
1
+ O8 W7 u; l2 c ~3 H# P0 V27 a+ a' t* F4 S Q1 b% v
3
3 b; F9 k3 \8 @. O' b2 u4. J, V1 m3 ^; u9 g8 R
5& |) f# _, g: v7 y9 N* w
6
. A" T. ^! p2 I8 x7
" v/ o, w$ {! ^! i9 R8' m' G2 b" C) M- H
9
! {4 D- C( \8 z5 v/ y z5 q1 _9 X104 Y, Q" L6 K9 e6 _1 `, @
119 A$ t0 d& Z9 s, U& P7 O
12
9 b! E3 e: d; |( `) N13
9 H3 F( d. {: k0 p14' [3 p2 A' W8 _1 w/ U: D
15# E0 Z5 ~/ | u, {. D3 |) \
类似的函数是 str.rsplit ,其区别在于使用 n 参数的时候是从右到左限制最大拆分次数。但是当前版本下 rsplit 因为 bug 而无法使用正则表达式进行分割:
^4 l; P: }+ h# w# @$ {" ?' |2 S; P' c4 f0 [
s.str.rsplit('[市区路]', n=2, expand=True)8 M _* P0 w& ?: ~
Out[45]: . h) c! s% D( G# T9 Q( g
0
; c' ]; |) M5 M$ I# X8 ~0 E0 上海市黄浦区方浜中路249号4 j1 Z. [: `/ L$ L9 S7 [, p
1 上海市宝山区密山路5号! J$ K) i# v w, R" ?9 i9 O
1
+ u! n2 ]) @7 I7 J/ X/ ]0 d. p2
/ l2 r0 M: k+ k7 q3 }" B' `30 Z* G7 z' {! [
4! B+ ?) L/ _6 |4 Y
5
& M1 c% h" `' t8 Y; k8.3.2 str.join 或 str.cat 合并0 ?9 o# Y) T" \& Z
str.join 表示用某个连接符把 Series 中的字符串列表连接起来,如果列表中出现了非字符串元素则返回缺失值。
& R, v1 q: H/ P' n, Xstr.cat 用于合并两个序列,主要参数为:5 d) U% F3 m0 j# G) u) h* ~& I
sep:连接符、6 F. d) ~3 ]$ @$ A- N7 g* k
join:连接形式默认为以索引为键的左连接: A' `6 \- D' i9 j/ | @0 ^
na_rep:缺失值替代符号$ ~0 N( G @3 x. u
s = pd.Series([['a','b'], [1, 'a'], [['a', 'b'], 'c']])
L& |! V5 i) J' G) \s.str.join('-')
! K: _- ^7 ]3 H; T) I& i$ t7 m/ qOut[47]:
9 Z, S% B# r3 _# S" G, L. P0 a-b
6 K; W! Z" h7 q' B4 ?( v1 NaN( a( N, M1 C1 |& b! a; N8 ^
2 NaN
2 K# X5 Z9 U0 y4 {dtype: object8 k; N4 E; x# Q# P0 ~
1
! ]3 b, |) D7 c2' J3 m9 T% M, y/ G5 j
3
& s; o, z6 w1 U7 e$ H6 g4; ~3 G9 X6 c1 z' K Z* |# s: t
5
, U4 g, y5 @4 s7 @& \: a63 E0 V( D" `5 @ n5 e
7
8 D# x+ Y5 A" k8 S9 F& ]; X, C& @s1 = pd.Series(['a','b'])& h& I) s0 C$ E# Z, O
s2 = pd.Series(['cat','dog'])
0 q7 G% V: n6 u) t2 @3 J; Ns1.str.cat(s2,sep='-')
* g7 [7 j$ v7 h1 C! ]" eOut[50]: 3 U7 i% ?; W$ [. S* O) \" Z0 U9 z0 N
0 a-cat
- ]9 j. ]# I: e1 b-dog* X2 ]: O7 d1 Z1 a
dtype: object, @+ ]6 `6 n7 h8 d7 ^/ H, M
* V. Q, h* Q N ls2.index = [1, 2]& g5 Z: i+ I8 H3 H' h, `6 i
s1.str.cat(s2, sep='-', na_rep='?', join='outer')
- C- v; k( p) p7 z0 X; ^1 u. L fOut[52]: 3 k, k. ^( C9 c% C& z% P
0 a-?
# D: k4 J1 a& w6 A3 K" ]1 b-cat
" L9 c/ h9 }. e" w. l) D3 x9 z2 ?-dog
: I/ w# Q. _4 v6 x. V( fdtype: object
& Z0 _- L8 X- h1" z5 e+ t; h/ O9 k
28 j% H# ]9 ?& x/ @# c8 ]
3
. B% U, v2 e @6 L4
5 G0 h# H6 n7 h ~3 X4 _5& x4 W' o P3 t1 U: `# d
6
4 k9 J/ G) U. L; f) O' \' z7+ u' A# b7 S( R' x5 i
8
+ C+ p2 H0 F+ X7 F: T9
5 |: c, ]/ f! a+ ^* T10# x1 l. k( d3 E' I3 z
11* n' V; G9 A* {$ e% d/ H
12. l' u8 n& N% [; t3 P
135 U4 ^9 ^: [) u$ R2 _/ Y z. ]
14
# y% N! Q0 v% o1 J% x9 J5 N$ G158 @3 v, Y/ \3 a! R
8.3.3 匹配
% K) P9 p" P9 m6 vstr.contains返回了每个字符串是否包含正则模式的布尔序列:
: Q! V* p& P. M s9 O0 L9 Gs = pd.Series(['my cat', 'he is fat', 'railway station'])# x. @! g$ ~: P# ~7 I. N, _" n
s.str.contains('\s\wat')
+ M1 S/ X0 a- o# q1 e
* D8 k1 B1 |* a( l4 q' `0 True x2 g& o( H. i
1 True+ H% r! X1 e$ c
2 False8 r( H5 l2 B4 O3 t2 E
dtype: bool
7 ~5 d9 I# C: I3 M% W1+ q' u$ d% l5 `( J+ @
2' y& W9 g& A& n! V; V
3: a8 q# v+ ~' K! z% e" T* f* k
4
$ A) s7 Z4 P; X; C. G |6 Z5
/ o: q( B! k* p6
( N+ b3 W% J# L, |& x% y74 M+ Z7 a( I5 }$ }
str.startswith和str.endswith返回了每个字符串以给定模式为开始和结束的布尔序列,它们都不支持正则表达式:
l0 F- m. P! c# \/ Hs.str.startswith('my')" U/ T* i/ [. D4 b' H, a
$ E: c: t8 s: K7 F: N% ~( q
0 True3 K& A6 X: O# N( W- x
1 False0 j( ~& L3 Y1 W; L
2 False
0 u4 _8 z( h( F2 [* x2 ydtype: bool
& Q" X* @5 n/ j, Q+ h l% c5 I1
5 k0 F- ?5 N) `; v! m8 c2
9 m" L$ v a# E' S3$ v. Q, T8 M7 \$ S
4
+ \& W1 U# v8 X0 ^5. V8 e% q7 p* q$ k3 T. E6 l
62 _: E3 _) b1 @# n
s.str.endswith('t')
$ r& D8 P! |- l/ a) W0 m* a- V6 }; L) f J6 X# t" O. ?
0 True* o o4 Y0 ?* K* G0 \
1 True
" j0 s# B' y$ U% P1 N. W2 False
( @* o" V4 S6 G# N) [dtype: bool! A' h8 f8 S( |4 r+ a" b
1
2 d5 X2 }6 E4 @4 I- |' x' ?! K7 A25 M8 c8 N( g2 Q' b8 K$ @' ?
3# ^3 R) b2 T' O9 F1 a& I
4# a, c- ]' U+ B: r) X) H8 R
5
6 A. ]7 B$ ^9 H5 D( W5 F! n/ Z6! z" z, w9 y0 i' A) I+ Z
str.match可以用正则表达式来检测开始或结束字符串的模式,其返回了每个字符串起始处是否符合给定正则模式的布尔序列。当然,这些也能通过在str.contains的正则中使用^和$来实现。(貌似没有python里的search方法)% e' k5 |' l6 v. `
s.str.match('m|h'); }, r( [! S* q% u4 X" |
s.str.contains('^[m|h]') # 二者等价
/ {: ]! M! m4 T; s
) _3 F" W/ Z; S0 G# ~0 True
l$ j# f/ B3 @4 O- ?! [1 True
7 P* [0 M4 Y8 R% t2 False" c1 {5 \5 e6 A3 l& `
dtype: bool/ g; L2 _# F+ ]1 u6 |) X6 f
1$ l) Q! J& C' U/ ~* W' l+ D
2* z/ z& L/ I6 v( W3 F$ A4 ?
3
. h9 S" f0 u3 x4 y8 R4
* r- {0 `" i% S' Z( y" `/ A58 W4 x" r% w& W. m0 T$ n
6
3 ^' \7 z I7 s7
9 T0 m! u1 v% D: v& |, u# J' C5 C8 Qs.str[::-1].str.match('ta[f|g]|n') # 反转后匹配
! z1 c7 H# s; m- d) t" Ws.str.contains('[f|g]at|n$') # 二者等价( |6 z, K/ b j+ y9 l& Z/ D
4 F1 E( t3 e: g c; W" w$ u0 False
4 g. J1 J- f6 T# ]! u1 s }1 True0 O$ o% G H6 @1 B2 G) i# C5 s
2 True
5 W* C) p( S: Gdtype: bool( Y* E5 K0 J3 F/ V. `
18 K- i# r# f7 ^9 Z
27 f6 g+ C3 z6 d
3/ I e; |" O7 G( R0 r( N8 d
4: m, D2 E7 s/ g9 E6 i" c% {1 ]
5
; t3 }8 R( f/ F9 R6* K4 w' ?* M' |2 b) \
7& f% w B* q% }& Y
str.find与str.rfind返回索引的匹配函数,其分别返回从左到右和从右到左第一次匹配的位置的索引,未找到则返回-1。需要注意的是这两个函数不支持正则匹配,只能用于字符子串的匹配:
$ D% j( J% ~9 c1 a6 V0 P/ Ls = pd.Series(['This is an apple. That is not an apple.'])
2 C. [0 G" t9 r% g6 B$ m; j; p: T n+ F; T* Y0 y' T7 T* E
s.str.find('apple')5 c- ?2 p; d% F' ?; M6 ?* o
Out[62]:
( Q* l9 N" f: i, g9 |: J. e0 11
+ a: @% W4 N' p1 F: [- a! {dtype: int644 F: [. y& T7 ^. x2 \
3 r# ^. e+ ]9 Hs.str.rfind('apple'). i/ }+ X! B1 z
Out[63]: . Y& S# n$ Z7 P) s% I5 O+ P
0 33
: w( P5 \8 X, t0 J6 e; L4 Odtype: int64! \, F" x3 T! z0 u$ {" ]
1 F; q& @* {8 i3 c" h
2
# K$ I. Z$ y, g' `% S- V" f3
4 |5 [9 [1 q1 S49 I0 T. B- f" E% F: r# W$ E! B
5
+ O5 s. C( a$ X# p5 V6 o; V6
( Z9 n/ f1 \7 S* a4 O7
$ n1 G7 w! k ~4 ]' N8
4 Z( y& B0 X5 \* q8 C: F. y9
- ?" C6 V G1 b6 F, L8 N8 {, m; Q: y10: }+ b, R( y* A0 s+ ~7 j
110 v% _1 k; b' o
替换5 N* Q2 [ P& C5 O( {. }( T
str.replace和replace并不是一个函数,在使用字符串替换时应当使用前者。) w7 O) [. m0 J! w& o X8 a3 r
s = pd.Series(['a_1_b','c_?'])6 h: c' o! D$ A0 c5 a
# regex默认为True,表示是正则模式,否则第一个参数内容表示是单纯的字符串,也就是匹配字符串\d|\?
; A5 m+ S( R: t3 e# j5 Vs.str.replace('\d|\?', 'new', regex=True)
& [0 g G+ t' V4 i3 B o5 z! v
" U0 A' V( p- G. t* a0 a_new_b
$ B, Y% @7 I7 x6 {1 c_new+ v, ~2 k5 J S$ k* @' f
dtype: object: U; q6 h D! T4 O# j! M! F& L
1# {- ?1 F# R @$ q# m; b/ W4 s
2
$ Z P; D. E& |+ j7 H, R) s3/ f. V7 _9 Q9 `1 O+ G
4
3 H5 H+ U4 t2 q w( \) I8 C! q5
, J- t' m' C: J6 w/ q) }% P6
/ l0 O* B# y+ r1 |& m72 r o9 ?6 I/ _" D2 c( Z5 k
当需要对不同部分进行有差别的替换时,可以利用子组的方法,并且此时可以通过传入自定义的替换函数来分别进行处理,注意group(k)代表匹配到的第k个子组(圆括号之间的内容):
' [/ T, H9 Q i0 c/ _& }) J0 G0 s) h: y- N* a6 ?" g
s = pd.Series(['上海市黄浦区方浜中路249号',# D2 O% {# _: c/ M' P+ [
'上海市宝山区密山路5号',* [! L2 d: i# V( \5 Q7 `
'北京市昌平区北农路2号'])
3 @2 `: N. t& }6 V* w% tpat = '(\w+市)(\w+区)(\w+路)(\d+号)'4 Q' `( u9 ~; s' p# Y- R: w
city = {'上海市': 'Shanghai', '北京市': 'Beijing'}0 n7 i3 t9 |; V# c4 A7 u5 ~. j
district = {'昌平区': 'CP District',
" W3 |: G$ N+ @, L& D '黄浦区': 'HP District',
# f- H" j3 d1 L '宝山区': 'BS District'}
/ l5 Y! L1 N- Eroad = {'方浜中路': 'Mid Fangbin Road',
' K7 g" j1 {/ `& i9 n# y# d4 A& K '密山路': 'Mishan Road',
% c' O7 H, E- y- p0 W '北农路': 'Beinong Road'}. E) ~! ^) L* s+ d% z
def my_func(m):
- A6 d9 q- K* P$ k# f str_city = city[m.group(1)]
/ q$ @/ D) R6 ^" q* w str_district = district[m.group(2)]+ I z! J6 a( b5 J8 t! |8 _3 E
str_road = road[m.group(3)]! |9 H h' n9 l+ y+ Q" Y4 a, T& G9 |
str_no = 'No. ' + m.group(4)[:-1]
; O2 i2 d6 `& c+ ? return ' '.join([str_city,
" H* l, W2 l& R3 o, H str_district,
/ M) k3 f7 P6 I A str_road,# y6 f2 K* L u. |. i" n1 a- G t
str_no])0 i; s% r% M* k$ [& f
s.str.replace(pat, my_func, regex=True)4 }' K5 F9 w1 b1 W$ @! s
5 _3 q7 j2 v( d% {1
0 N3 i/ V& E/ D" j* |2
& f7 @3 X, ~2 _9 s' \1 g# S2 ]4 r3 o' U$ v: U* w7 t: d3 O
4
6 V$ t8 U& _" v3 Z9 x: |5
* O) {2 }* M0 j. u7 ^% z$ o6
( O; z* D2 d$ b, g* {7- Q. _' l& C% u/ I5 N S3 {4 V- B2 G
8
4 T. G6 s& Q/ i6 n9
1 n- J3 C* B) z103 y$ ~' d' g2 H: g
11/ t4 F: g* l5 F/ q2 \ B
12
9 ]( G/ |8 u7 q) m& O1 m% E' h* i13
8 @9 P5 m& R6 L, Y14
. ^! E; Z+ ^1 B# @8 i, ^' N! {15, h+ M* C9 x7 E
16+ {! k+ n. U" h, P/ r
17
' z6 \3 P/ f4 H1 i18, B' i/ X4 c0 M
19
# N0 k1 M9 B1 V9 Y. n/ h20
. H; N8 V. A. J0 h219 t; k! G4 G& |
0 Shanghai HP District Mid Fangbin Road No. 249
' Q3 P# q# S4 q* ^0 w$ Z) [1 Shanghai BS District Mishan Road No. 5% g: U1 p% S% F. }4 B
2 Beijing CP District Beinong Road No. 2
) j( W# x( y9 f. C' ]0 [8 Tdtype: object
0 N/ F( S$ T* l4 ?, v- F19 p) e& N# p( Q# @' i ]
2
. c( l4 _- H2 x/ U8 n) ~" r35 f! G7 B$ P* s, [
4: P; S. B2 M/ ^) L) ]! w0 w
这里的数字标识并不直观,可以使用命名子组更加清晰地写出子组代表的含义:5 u/ u& d5 j1 q) {# b- v5 ]
* W3 Y, ^4 b" E G" g# @, U
# 将各个子组进行命名: S- ^+ f) ~1 ^ A8 V5 m
pat = '(?P<市名>\w+市)(?P<区名>\w+区)(?P<路名>\w+路)(?P<编号>\d+号)'
! A8 K( E& x- u; N1 Vdef my_func(m):
7 g; Y$ o) }' X/ i str_city = city[m.group('市名')]/ O S# x, P* C/ Q7 O4 w6 Q
str_district = district[m.group('区名')]
, b4 U! z+ J& q) R1 ^7 C& u str_road = road[m.group('路名')]
/ [) J0 K. [1 P r9 ?* P) N' v str_no = 'No. ' + m.group('编号')[:-1]
5 _7 Q$ D/ N& _, P m- V$ U; L return ' '.join([str_city,& ~* x- e; z9 D, B) B6 [5 Q. L
str_district,/ c+ G5 A6 P3 w
str_road,
/ ^6 a0 a5 f, s9 [2 J1 g0 y! d5 L1 @ str_no])
; S$ O% _! q# Ks.str.replace(pat, my_func, regex=True)
' J- d6 k5 Y) X1
! d0 B! d* k& T. ?6 c; v2
9 F, f9 z' `/ _9 j31 V+ T& q+ f, c* a8 G* L. g
4
+ C! ^' j2 ]3 |1 F$ T% l; [5
6 Y) @- F, L- F Q; T; a6 V$ j+ p2 t; R
7& X, C3 k$ l0 |; x2 Y2 S
8% [* d# N0 p: h
9
% g' T: [0 D3 U$ f% P c10. `1 k! i! G2 v4 \: n% Z+ D% m2 ]: D
111 B! \* r b2 S; {
12
- K1 Y* \; _' d' N& l0 Shanghai HP District Mid Fangbin Road No. 249( O6 U# u$ z+ c5 U! X4 y, }2 i# o- L
1 Shanghai BS District Mishan Road No. 5& B; S ~' h3 X2 [) h
2 Beijing CP District Beinong Road No. 2; x4 @# b; a8 u: O
dtype: object
+ U$ D& K7 I6 k1
' o! N$ |. E I" S+ Q0 h: u. Q21 m' \3 N& @6 C
3
; \/ B+ b& q4 ?- _6 O5 E" P49 ?3 T9 k/ f4 {* V# X& f
这里虽然看起来有些繁杂,但是实际数据处理中对应的替换,一般都会通过代码来获取数据从而构造字典映射,在具体写法上会简洁的多。2 ]: M! g5 T2 l; i4 H' j$ w& J3 z2 \
G a( _ N# F8.3.5 提取8 l' J% I& `/ n E
str.extract进行提取:提取既可以认为是一种返回具体元素值(而不是布尔值或元素对应的索引位置)的匹配操作,也可以认为是一种特殊的拆分操作。前面提到的str.split例子中会把分隔符去除,这并不是用户想要的效果,这时候就可以用str.extract进行提取:) f/ w' v! ^8 k9 k0 ~
s.str.split('[市区路]'), l9 A' P* @$ B; K d, ~
Out[43]:
+ s! c9 g6 c7 M* k7 d0 [上海, 黄浦, 方浜中, 249号]
9 k$ J& c! O# D/ o/ g8 j1 [上海, 宝山, 密山, 5号]
" R5 A8 ], Z3 Jdtype: object' ^, w6 O, ?3 W; G8 p. |
: s& L' k9 X. F0 ~5 N
pat = '(\w+市)(\w+区)(\w+路)(\d+号)'
" r" R7 L% w/ l. K2 Cs.str.extract(pat)
$ y# P! \4 \ [6 Q; `2 YOut[78]:. _8 o$ Z9 Y: b
0 1 2 3) u0 F$ G$ q7 V3 H+ S8 U
0 上海市 黄浦区 方浜中路 249号: p) v# m5 q( V. _- a, i# L0 J
1 上海市 宝山区 密山路 5号
. X8 _- T7 r6 C2 北京市 昌平区 北农路 2号
4 H- }5 H! `3 X ~5 d1
x0 `' Z9 w3 ? M22 s8 R5 O$ @, b, j' [
3
$ K; f0 t3 t7 ^, m4 ~7 Q1 C t9 N F4
/ M8 s: M/ u+ h; t3 P0 O( ]: P5
% T, r* {. @6 R, F/ n" F' g68 c1 v; w' r- x0 @$ M) a% j5 d, ?
7
0 _6 o0 c/ q4 O: c/ V8
& O# E; ?2 Z% A9
$ H$ G3 a5 P$ X3 J/ w10
" X- Q) U' Z+ Y2 G3 \" a) ?: s! B11
4 b6 T- N+ C. {" w. C12
8 O! ~! O4 w" J7 X @$ g- _2 M13$ X }/ p& y8 L* w& x; e; M8 j
通过子组的命名,可以直接对新生成DataFrame的列命名:
9 ?0 M( N# y( F3 o9 r
- e% N. e9 G3 f Epat = '(?P<市名>\w+市)(?P<区名>\w+区)(?P<路名>\w+路)(?P<编号>\d+号)'7 L$ V8 E3 T0 ?9 L! |8 k7 }1 s
s.str.extract(pat)* k, j2 Y, P' Q' H3 K" g
Out[79]:
& ^# x. h4 b; d& ~' k) U) X# f: W 市名 区名 路名 编号
; O. z, ]' p# M' k1 t) z* ^- _0 上海市 黄浦区 方浜中路 249号
+ j9 E: ?0 D9 Y5 }2 ?* l, `1 上海市 宝山区 密山路 5号: q/ }- s$ U6 p
2 北京市 昌平区 北农路 2号7 E- g W) Z! M( B! }% j' {9 u0 D
1/ u# l7 d& o/ b! L) h: x9 c% F
2* M% q; A5 `% c* ~6 c: Q7 Y/ q
3
9 g# Y/ q- q g7 w4
& ~1 k1 M1 ~, }5
& C, I& a s) z2 w( p- S6$ I2 y$ L6 O" e6 ?6 b$ [+ o
7/ p8 \; d% _! i5 p# Y& m0 M) X
str.extractall:不同于str.extract只匹配一次,它会把所有符合条件的模式全部匹配出来,如果存在多个结果,则以多级索引的方式存储:
1 p8 K/ y4 w6 `. m: N" X8 ys = pd.Series(['A135T15,A26S5','B674S2,B25T6'], index = ['my_A','my_B'])
6 G3 k7 r) o% z4 Hpat = '[A|B](\d+)[T|S](\d+)'
& K0 M3 |2 z6 }+ B }s.str.extractall(pat)
6 M- L: E6 Y2 |. fOut[83]:
$ n# P/ T0 S6 M& \3 b3 C) f 0 1
( K0 c6 A( W9 D( D# G+ ]( g' S* O# a match
" S% K/ E+ x2 p) y1 F3 A' Emy_A 0 135 154 M5 a" L! b; l+ |5 v" c
1 26 5* P- K; t; b& {( { |5 v
my_B 0 674 2- x8 X- F" \* o( k& l
1 25 6$ k$ k: I3 X$ R- R
1& z8 z. i, U* @' f* e, J c8 n
2" P( T" b ?/ @8 j; N1 R8 S
3$ g8 U. R- d/ p, [0 p! V5 a& l
4
9 E+ ]5 t, V, _+ s5, K& A6 }! J7 l2 i
6
; e w0 f7 v8 i- n: u+ C* D7
" X e6 L3 q/ t8 c p! ?84 ~' z3 I4 e* o0 k0 F! `" r: d( r
9
5 n" t6 \: d7 T+ d" |10. |1 S" Y8 v" j% h
pat_with_name = '[A|B](?P<name1>\d+)[T|S](?P<name2>\d+)'
3 k5 _; m$ g/ d( w/ Ks.str.extractall(pat_with_name)
7 A3 B: D9 o! UOut[84]:
2 k2 U' P" ]1 R; t name1 name27 x; _# K( l. }+ e
match
6 `) r* T6 c! g7 e$ b% X8 Y) gmy_A 0 135 151 S0 h9 W& [$ h8 {4 e+ }& ^: P; _
1 26 5. r5 d L% s1 D7 i8 F: F9 j2 N
my_B 0 674 2
7 B7 j. H. n( C 1 25 6
0 h- V/ E6 P! H# C' k$ T1
8 [2 v; r( @$ K# p2
. Y$ H6 |: D* i8 K6 S39 S9 {$ |7 N! }
4& Y: A+ ?3 n% g# p/ A0 k
5
2 }7 U/ [9 R. S, i8 N6
8 r% p0 K y( m! N7
. G; o6 B9 _- f9 r7 z: Z8
& P; ~& I' E+ _' [, M# \9
% A1 z5 P* \* Estr.findall:功能类似于str.extractall,区别在于前者把结果存入列表中,而后者处理为多级索引,每个行只对应一组匹配,而不是把所有匹配组合构成列表。4 `+ V. u- q, M
s.str.findall(pat)
# D; Y: s& U! N1$ i+ }, [ s; D) \' b
my_A [(135, 15), (26, 5)]
" v& e, f- @% X* H/ H( kmy_B [(674, 2), (25, 6)]5 M4 i+ K% R* Y
dtype: object
8 L3 e. T+ X8 Y, P Y1
- ]0 [6 `, E! P2 B; O7 c3 Z0 W2
% q, R: e0 _! _$ z! y0 ?+ L' p3$ h k3 j5 ~/ j# W; [2 }
8.4、常用字符串函数
* B+ `1 P3 n4 u 除了上述介绍的五类字符串操作有关的函数之外,str对象上还定义了一些实用的其他方法,在此进行介绍。
$ z/ O$ G) Y( [& W l p
: h5 v) u' e G8.4.1 字母型函数
P! c. z' [0 @3 h upper, lower, title, capitalize, swapcase这五个函数主要用于字母的大小写转化,从下面的例子中就容易领会其功能:, c5 ^- ^& S1 X5 F6 T4 w
, s0 B8 S4 c$ k: c0 p1 D9 l! V% ss = pd.Series(['lower', 'CAPITALS', 'this is a sentence', 'SwApCaSe'])5 q6 T, I5 n$ \! Q1 z# H/ \5 e: N% E
4 y+ a! P+ y* U3 K( f3 [
s.str.upper()
. w. N G; a. [9 hOut[87]: ( V& z7 z( I: d7 F( C2 c
0 LOWER+ ` j# q: X ?$ k/ O3 g/ ^# A
1 CAPITALS0 F p) v; v& X. ]3 a) s& m e* `
2 THIS IS A SENTENCE
' g8 C' r, n! @: c3 SWAPCASE3 F$ o$ H! t! m. Y P& g
dtype: object; f9 Z3 x$ w2 |2 z5 H
+ x) x! j: a- o$ g* @: V! `
s.str.lower(), t1 }% a! e0 u! H
Out[88]:
6 u! W8 ]( D$ u" h1 W1 H0 lower, }( t2 o$ O# n' g3 @: c2 k8 M
1 capitals9 u* e1 @' @) k" W( t f) ?. b M
2 this is a sentence
" P4 B0 Y) H q2 J3 F0 q* ^% J3 swapcase# H+ n; H1 B& ~- T, k; C; E
dtype: object0 V! {" i4 f6 p2 u) ^
# C4 S0 d- u4 H: {- O! L8 H- T! Es.str.title() # 首字母大写# V% N- C; }9 Y
Out[89]: . f( T9 W+ n; d" d9 v8 T
0 Lower q: H% o' d2 E+ |2 m8 |
1 Capitals
/ n# J2 f Y, n9 @& M2 This Is A Sentence
5 H$ ~) G) i4 o- J. f4 i2 v3 Swapcase2 H2 C+ w9 x" q" A7 }
dtype: object- M( W, ~# y7 w0 V0 J
. y {* D2 e& }
s.str.capitalize() # 句首大写- @# q: H# ]/ n
Out[90]:
% A x( ^0 |9 j0 D0 Lower
3 W- C5 _' B/ Q4 c4 b1 Capitals1 @& F. v% _4 D
2 This is a sentence
: s: U+ G: ~& L/ J/ ^" y! W3 Swapcase* X4 R+ [. S6 j2 S0 V0 d
dtype: object, @* ], H5 M p% p$ U5 c( F
8 B a' X9 B% Y/ I; n3 xs.str.swapcase() # 将大写转换为小写,将小写转换为大写。
7 j1 Y0 c0 w/ dOut[91]: + H, g* `- K# n2 g/ }" P
0 LOWER
$ L! |: I9 Y& R/ a3 X0 p1 capitals
' L* i9 ]$ i' n/ t3 \2 THIS IS A SENTENCE
9 @+ c# Q$ V1 w* Y% M! d/ Q; {4 ~3 sWaPcAsE
; \( q3 B+ t( d7 ddtype: object6 N6 i u& w2 S7 R* Y
$ S, M2 [5 s6 K! Ks.str.casefold() # 去除字符串中所有大小写区别0 l- A+ x9 O; d1 h5 k
/ b+ s6 g( h0 y) l! e9 E/ ?
0 lower
! B2 I( D" n& J% H1 capitals
. y9 L5 V" x2 a, J2 this is a sentence! {6 _( `7 Y& ~
3 swapcase- |( k4 E+ V5 v9 |# h
( a- J9 T5 g( y3 R( N8 O$ K7 R _
1) g6 g l7 I. O/ R% D% z4 c
2
- \3 {! d# s& y \( P6 E3; O* [6 I w5 n, M4 U
42 k! p+ T& v! K2 x4 ?# D: K
5
& o1 T. H: `; D6; s. F, H' G* z/ j: s( @& F9 x: x
7
! b) @ n* m. m4 z ?, D8 N. t% y6 |+ C" ]% `$ f
90 m2 P5 l* \# a- u7 f) v
10& D7 t; U; P" y' ~& \0 j
11
$ b$ x+ r3 e+ w* O% |12
/ o6 i' Q/ t5 z! \: j) |$ ^/ }* R$ d5 f13, L% `7 ` R8 C: V4 M6 o& e4 E
14
3 b* D0 l+ c; C4 r15
+ Q: C; n9 A' D16
9 J2 ?/ e: m: P. k: [17: v% n8 \7 b* X* @# A, g! e- |5 E
188 w6 l& M% @* @. s9 s" t
193 c! P( p, |$ x
20
' E8 Q C: |# |' M21
6 Q( B; `/ u! i/ W3 N22
) ?0 U6 q& o" }1 f* T+ D2 _23
+ a: F! Z+ ?' o4 s9 D! N24. `$ b% ?; Q. @! P3 A3 n- Y
25
6 W2 Z: v1 v& u5 u- \7 H26 b. q3 ?( Y# b6 a! D' w
27
4 X c- W( p4 p, q& n6 {) b287 V* T6 _2 _5 ?' n' v) F$ J
29, r$ u# b8 L% u. G. w
30/ o. y8 v9 i1 }
310 x' U( z1 m; G8 V; X" f( I
32
+ M* k$ D7 }% T/ u33
5 K( B' c' a. K) c34" D; W4 O6 J* B( a. E- \7 i- X
35
- U" O- U Q8 s, k! ~36
5 p) G, v) B& q* v37
& k' _3 d8 P1 }8 K6 G6 u38
+ Z- p* M- O4 W; S, Q% r9 T4 u39) \, r1 U8 Z/ @# \/ q- u8 V
40
- A: r0 ?+ m8 J6 z41
$ |- O( `9 c3 _- |420 o7 a, {3 B! x/ O2 b" f
43
* ] ^& S/ M2 R2 S3 O. \44
~! N' z4 I* X1 E: u0 @4 C% |45
; ]* j6 B* a, Y& M q# O6 S46
/ U' i4 u& Q. p* F1 m47
# [% M$ l9 j% |* |1 ?1 M! g48
# F9 W& ^2 O4 B% j7 p7 `# `8.4.2 数值型函数; A) r+ D4 i1 [, U( ~
这里着重需要介绍的是pd.to_numeric方法,它虽然不是str对象上的方法,但是能够对字符格式的数值进行快速转换和筛选。其主要参数包括:" o( U6 w, `: u8 e9 a* e1 P1 L
9 U" \0 S' j. H+ v# N- M B
errors:非数值的处理模式。对于不能转换为数值的有三种errors选项:
7 Y2 M+ K- G8 g; z5 lraise:直接报错,默认选项: R, K" E7 e2 _. r/ C6 t" O7 I
coerce:设为缺失值
0 Z) {8 h+ f* {! Q4 q, m( w, nignore:保持原来的字符串。
3 F" J# F* f- t3 l3 vdowncast:转换类型,转成 ‘integer’, ‘signed’, ‘unsigned’, 或 ‘float’的最小dtype。比如可以转成float32就不会转成float64。
: S# r& Q% D" p: |3 M; Ws = pd.Series(['1', '2.2', '2e', '??', '-2.1', '0'])' K+ w/ k6 k [8 D; T9 f+ o. l% L
+ h, i% `4 ~" I" [
pd.to_numeric(s, errors='ignore')
. v: O' `) ^1 H0 POut[93]: - j) K" b( ^; G8 n
0 16 k1 G! z3 C$ s+ c$ \7 Z+ i
1 2.2
9 O& V: `% L: b) K; V1 P }: K2 2e
/ L: x' o3 u9 i3 ??6 t+ z, f6 S: D- ^, W
4 -2.1
! W m: _. h0 P% ]5 0
- p* A6 c7 }$ j! y" xdtype: object
) c: g, C7 w, F( L9 l" ?5 t2 {. `$ V. b4 K: Z- E
pd.to_numeric(s, errors='coerce')
4 K6 K. O: [# t1 P' ~Out[94]:
# O7 R0 @' p, D0 1.0: u# N2 V* {! d$ L
1 2.2
% F- r p T; e% ]6 s3 p6 d) J2 NaN3 }0 G3 c l! P8 J& U& C
3 NaN0 y: s2 Z7 A7 z- D( G1 e4 C3 q0 r
4 -2.1
6 h* a, I9 Y0 i1 \' v9 r! w5 0.0
$ V0 t+ S- \9 e8 h$ ]dtype: float647 M( j9 W! T- K; L, r0 D/ U
4 H" b* S" g- n& p. f3 B1
- u" J$ M$ o0 ]) J9 g7 @2
3 u! e5 k8 y! Q. y% U3/ y/ J8 z- p9 n, v2 R4 e
4" S: p! N" d$ X" l. E) a
5
- o' m+ S6 D5 z9 r/ b6 i6* s9 ]4 ?, U: o
76 v5 c( C: _- T( e5 O8 e
8
8 b% z/ h5 r0 W3 ?/ ?( W+ Y5 l6 Z2 u9 h8 F* L7 n* f1 h7 c0 Z+ ]
10
; b8 Y% q* c- i, c# ^+ Y' {0 w11
% G& O) J; R$ E9 ^2 s9 r7 {7 _12
* L8 C9 I+ J& K13
" c# d: f7 |" [0 [, [* k14+ ?8 k4 m- p( \
15( `9 {3 w/ K7 p% ~" M
16
% O9 f" U2 C" l# e0 t17
u6 C1 u6 G. \* ?" c" A Y18/ L. R' t* P" t7 L3 q0 m
19
2 `. j" v n3 D4 F7 N, ~0 F20
1 i: t' \4 v# R" N( s$ i0 \& j8 r21
, l1 P" p5 n. ~3 z# @; c! T 在数据清洗时,可以利用coerce的设定,快速查看非数值型的行:: t2 u9 K- N8 i% T5 l
& {7 R( }2 ]+ E C$ l7 q
s[pd.to_numeric(s, errors='coerce').isna()] x% s( c' |: n& P- [" ^
Out[95]: + g% S# Q3 K Q: U4 v
2 2e, A! o& D9 b/ M6 r; j" r
3 ??
# s. K% P7 {2 O }2 ^7 idtype: object
; |9 J3 R% C3 O" r2 B# o1; {; B0 l& u" O/ t/ T
2. \( ^3 h' e/ A6 q. e
3% e) R+ Q2 Y) U6 W# I2 K" ~& `
4. k' h) l/ V& Q- C7 B
56 j. O8 ?4 R; W1 Q* p
8.4.3 统计型函数
7 w+ U6 B$ S' V& T; z count和len的作用分别是返回出现正则模式的次数和字符串的长度: W+ T% N2 y' ~
/ S( R5 a3 v& E/ o3 C/ q8 ^
s = pd.Series(['cat rat fat at', 'get feed sheet heat']), [9 l0 Y6 s }( `" A9 ~
3 _/ A; z+ B, y6 a* ws.str.count('[r|f]at|ee') # |左右两种子串都匹配了两次
) W5 Y$ H- N" F8 F" ^Out[97]: * @5 o, ?& G3 R/ ]
0 2
% b/ i% e+ a# O, [9 L2 C8 O& _" h1 2# C. w( X3 s$ a4 Y) Q8 Z' A
dtype: int64. }. G- K) \: k/ |5 t5 \6 x! N
5 `5 @- Y5 d4 V# p+ e3 b# ?9 hs.str.len()
8 E+ ?+ n$ J8 R2 M0 I. j+ _5 vOut[98]: 3 W) s) ]" t# W7 R$ F, l3 S
0 146 X" \# u: `( u$ T. r
1 19
/ \ i/ | V0 y* I& J$ Bdtype: int64, m2 I y1 F8 f! s ^- E, t
11 X- w) p, v& r# H6 u
2
5 ^( {/ u$ y" K. p5 ~5 S# s! U5 [31 F% u/ g6 L) X* q* z+ j% y8 f
4
- X+ L- H. P* m" b$ d, U5
- I% q* `' V+ v% k: d) H2 L6
* H$ s: a I/ U- c' t3 B7 T7
: |9 j* K( @, q2 L6 w; l) s. ]8# \5 \! ] L* j
9
' l7 x. l3 F/ q2 O4 C+ |4 k10/ P9 J* V7 D% [) E; d- U
119 \( X, f! y$ W
12# s0 u+ r; c6 O8 r2 `- u
13* G) j: f( d' G& Y% b" e& }0 }& {$ f
8.4.4 格式型函数: L* e3 L8 T3 l7 \* U2 w
格式型函数主要分为两类,第一种是除空型,第二种是填充型。其中,第一类函数一共有三种,它们分别是strip, rstrip, lstrip,分别代表去除两侧空格、右侧空格和左侧空格。这些函数在数据清洗时是有用的,特别是列名含有非法空格的时候。" g9 W, |6 g/ h& P) {
( k2 w3 I h }. O$ ^my_index = pd.Index([' col1', 'col2 ', ' col3 '])6 Q- s1 }0 k& R3 b: h v9 m8 J
2 V6 C. ^' T: X+ R
my_index.str.strip().str.len()! _4 H3 W2 U, L# r/ c7 |
Out[100]: Int64Index([4, 4, 4], dtype='int64')' k3 e: g1 ] F* f" Y |; p0 R
3 a( P; W- j% n$ omy_index.str.rstrip().str.len()3 w/ h$ M' p, r
Out[101]: Int64Index([5, 4, 5], dtype='int64')
% w. z: N. `. l& x. I0 f+ M) U
( Y x8 i! r" P# [9 I; Q2 smy_index.str.lstrip().str.len()( B$ x( D& m9 Y5 P- k8 P3 g
Out[102]: Int64Index([4, 5, 5], dtype='int64')8 i) g* Z8 q! h- G
1$ n( K$ `4 Z7 j( m, B& m
21 C, M! B# s# s
3
) B6 d- ?; ]7 @" ]4
8 W2 ~$ u' n) \- y y58 K) i1 e' P1 h( U) J! q1 `
6& x7 l# z9 u) W: X% [* [6 q& w% }
7
" r" ^6 c" y Q4 z4 {2 j8
8 R9 |, y- a1 u; U* z/ `2 l9
# B+ o8 ]% U7 Q& Y. F0 ~* x10
' M" G: z, [. ?3 p, l! E 对于填充型函数而言,pad是最灵活的,它可以选定字符串长度、填充的方向和填充内容:
% A( b- j; }7 g
+ d5 E, f7 [+ O1 as = pd.Series(['a','b','c'])
7 L* N/ M. l+ W6 w# }
+ O U" @0 J. W" V4 Z4 is.str.pad(5,'left','*')! O- V. R( J5 G$ J+ e
Out[104]: * t% Q4 B- J' M# h" a* e* e! I
0 ****a/ D. A/ O$ j2 ~, O
1 ****b
8 y* Q5 s( l$ E/ C: a6 ?8 s% L2 ****c7 T. a% t1 v$ h) N* [" W- x! _0 Y
dtype: object
- U) `, `$ j% Z% ?; j% q9 R" K! P3 l$ U
s.str.pad(5,'right','*')
3 U( Z# Z, w5 j8 t6 pOut[105]:
$ O% E8 ?8 X6 ^ I0 a****
# {7 Q8 F' g, Y- F/ b# G1 b****
5 k& S9 y, a8 F) F0 H2 c****$ n& l$ O5 `' m }
dtype: object
7 L! W6 _" |) H9 a' l% @( _" L6 ]6 o2 ?: q/ P
s.str.pad(5,'both','*')
& G3 u w# L3 a+ |) `( |; | y( A4 ~Out[106]: & [# X5 \( L% S
0 **a**4 v- E8 \+ z' i! I/ t e$ u
1 **b** J l: p0 Z! ^
2 **c**& c5 W; `) q9 }! S6 n
dtype: object2 ~+ N& ~. ~( x; ]7 v1 W' w& A! |
8 m1 u) y9 u" [( w$ s
1( f! J6 F2 _7 l J9 i1 `
2
8 ^9 ^) g' c! S7 X3" E: l: @) j% ?3 E/ X6 [' _0 N
4. J3 Q* \/ b5 Y4 r, ~+ y
55 ]5 C5 K+ x# d
6" q2 K% Q. F+ X! @' m8 {
77 J( L3 z! J" U
8
) H" ]7 A( Y) k3 T" k! y/ K9+ w8 i4 l2 i! j0 {4 n5 i0 K
105 M2 G$ }4 ?% V- d" i* V; I
11
2 e b6 p& U1 v& o12
* j4 ^* C* Q9 ~- g13$ t9 Z; {8 `' o" E6 @' C$ N. B# R
14
7 r- G7 ^( s5 q1 @+ l$ k15' m* o( K [, c+ Q/ ?( \) T* J
16
5 f0 K, C/ E2 o# w17
1 D$ j! ~$ F5 X I/ N/ K18
( g- k \9 G/ g! ]( H* A: A19: K1 `) C5 J" v% |) `0 J' U
20
* j) w7 c; b$ _/ z8 u6 r% ^217 {/ m9 p" C& Z& {7 N
22
6 ~2 [* a3 t' R, k6 z3 U 上述的三种情况可以分别用rjust, ljust, center来等效完成,需要注意ljust是指右侧填充而不是左侧填充:
! x6 K9 }( M: \& a+ t1 H5 u
8 [ a! W J7 _0 @9 {& Bs.str.rjust(5, '*')
; A& P5 e8 D8 m- EOut[107]:
5 m) J, |% Y9 `. a- a0 ****a9 Q. U; Q" O, j
1 ****b9 a' l1 P* m& O& u8 j! r
2 ****c; k( k7 `0 z. x5 _0 R
dtype: object
$ C% n1 O f( h/ [& y" S$ x+ B2 k0 V j" ]5 K4 x
s.str.ljust(5, '*')
* h. N% L" ]! hOut[108]:
' X; }2 z, @3 V% o/ `& O0 a****
: E$ u2 W. [3 V* W- A5 n1 b****% P$ o1 h( v1 V% l
2 c****2 _7 s* }4 e3 g9 Z+ B2 s5 t/ }
dtype: object
( _# ]2 d# P, ^* P3 \9 ?
+ _! p( r0 m( Js.str.center(5, '*')
6 Z3 @" D8 k$ E, o5 I5 C" D [Out[109]:
2 O1 o, _* b( M3 w* x# }0 **a**
/ B: _ Y4 l* i0 d1 **b**
/ `" H" v: G8 `/ u0 t6 x2 **c**7 t* i3 m9 a; {7 b5 w
dtype: object3 Y. H. s1 Z4 i$ P: \) X
. F& S' Y" A" U" ?/ T |) G4 ?
1
3 {; E2 t& t: v9 v3 @; D2
1 r1 I- a" Q" L% G( a |( z3
7 z0 I5 f$ ` z+ R- ~2 Z4: Q; Q* t7 }2 d9 J' Z
5( f3 y% \' B$ ^! `5 R
66 g9 O4 f' c7 k0 H+ y
7
9 N3 [% J$ M: H a81 Q X& l5 ]* v+ n; F
9/ d6 L& M2 o3 b* w$ j
10# {5 T1 v o3 G; |) n: c! y9 S, g
11" \7 V( P, D L5 J
12/ m0 X+ M3 |/ D5 t
13
+ W* e; f0 S. o% K14
* ]8 G% ]8 V) Z6 B# h B& l/ ~2 ~15
9 X \! R8 y) B165 A' Q+ g2 ~* J$ y' F9 h
17
( } x+ x; C2 f8 c' {3 e189 H$ A5 a6 \: u
194 M i3 r. x4 [
20
6 n9 v+ R- S% G2 F7 y) q 在读取excel文件时,经常会出现数字前补0的需求,例如证券代码读入的时候会把"000007"作为数值7来处理,pandas中除了可以使用上面的左侧填充函数进行操作之外,还可用zfill来实现。: F0 f1 c8 b- b7 c* k
2 ]& M, E1 ^9 F/ v+ s, ps = pd.Series([7, 155, 303000]).astype('string')
7 {3 u x! K+ R% H, q: ]
, x0 g9 b" n, ~0 x2 Ss.str.pad(6,'left','0')
* e% @( k6 O0 D. u6 r* rOut[111]:
- S, s( T8 u6 R0 000007& Q& C1 L6 F& p' e
1 000155
% h$ {) ~, _% j! j8 h2 303000
* V) D& M7 o7 d3 k5 r hdtype: string* x% h* B' Y! d1 Z) a' a6 y
- b( K7 E: R6 q* S) o K
s.str.rjust(6,'0')
4 g: @3 r' Z" EOut[112]:
Z$ C. j0 D& Y q- x0 000007
# C* \* M" c+ r8 ]2 b. e1 000155
* ?9 v; K# Z7 m, t m$ S2 303000
# X3 j9 A' E4 ]" c! Gdtype: string
$ _. j- u6 ~: U' c1 p# P. U; U' x( q. L& x
s.str.zfill(6)
1 j S. @9 X) D4 c8 p' r/ dOut[113]:
) w, X, E! [) ?! E7 n% I) j; u0 000007
5 j; t6 ]$ M, p( k1 000155
2 a9 Y" }/ W1 R2 3030002 ?5 j5 S' W$ I2 d1 H
dtype: string
% B! D% o0 c3 _: j& C5 [, d! |4 _0 `! P, M* k. G1 S
1
( W" H3 g3 @ K' @' {2
( E1 |0 u2 r! J3
. _& C- R" h- X& Y43 ~( v, A3 P" f, r( ^
5
% [* n1 S$ }5 Z/ b# o6
3 }" u2 o6 K, J7 l6 F7
' K; f' R4 b u' B7 f84 y- w" R% |9 S8 J4 M- V( H
9) ~+ s* x" P7 A1 j+ G
10" t) I3 n: m8 d9 v. u7 V
11
% ?% L; {1 S& c5 E12
/ k4 S8 U5 @& e" g# L* ^13
& [) u9 m2 T! q& ~14
* d" o8 ~% H: _6 V9 }0 ?150 D. E* i- b. x' r7 o+ p
16
& I# [; ]' C0 S# x17
$ @. N* l9 j$ ]18
6 H/ d- m( Z$ T* A19, c# }+ }& d' x" p- H' ^2 [2 H7 v2 D
20
! s# j4 x5 O1 b' i- T0 w/ U& {21
0 w" w5 [( t' l/ V# h+ |1 S22
. G2 _+ z. T* k# v/ h, d. e8.5 练习$ S: t% D; M% _( z$ U' Z
Ex1:房屋信息数据集- m' o$ i6 g3 a2 Q
现有一份房屋信息数据集如下:9 V; L" b7 o8 L
: d% d" p! E7 q7 S! sdf = pd.read_excel('../data/house_info.xls', usecols=['floor','year','area','price'])9 K1 e8 ^+ J. i; H
df.head(3)
! u/ O* ^* `4 W6 JOut[115]: ; E3 B( T/ B8 ^, l& r7 V4 ~: G6 E/ C V
floor year area price
/ `& ~' D1 a9 u+ p) T0 高层(共6层) 1986年建 58.23㎡ 155万4 L+ N3 f% Q( ?2 m$ @! g- U
1 中层(共20层) 2020年建 88㎡ 155万& y1 U$ D% K' r- ~
2 低层(共28层) 2010年建 89.33㎡ 365万- N! Z% s$ R- _: f
1
" t1 {! r s, n( n# W3 g+ I- Y8 O24 \$ m" F6 J, w. x, i9 I y
3( S# j0 S# _6 f$ N# b. M: s
49 r% c7 b' }/ t# f
5
2 ` v% h5 c, h" U6
3 K' ]4 B; c0 u7 V2 e" n( b- c70 i0 f4 o/ J2 Z' `3 B# y
将year列改为整数年份存储。
8 s, _3 E# M, G将floor列替换为Level, Highest两列,其中的元素分别为string类型的层类别(高层、中层、低层)与整数类型的最高层数。1 P8 `0 f* U: V3 [) @
计算房屋每平米的均价avg_price,以***元/平米的格式存储到表中,其中***为整数: ~4 Z/ R' |, g. N T
将year列改为整数年份存储。
4 K4 o+ ], ^2 ^; L) q3 r( S2 A* ^9 f"""- D0 h% e! R& K8 l3 C6 ~
整个序列需要先转成Nullable类型的String类型,取出年份,再将年份转为Int64类型。
- n/ q: {5 u- P3 o/ `% [% M注意,转换的类型是Int64不是int,否则报错。即使astype加参数errors='ignore'跳过缺失值,4 ^8 ~" u- I7 L( e/ B" _# y
转成int后,序列还有缺失值所以,还是变成了object。+ l, y$ x# h' g6 k2 Z+ C
而整个序列转为Int,就还是Int类型,缺失值变成了 pd.NA 。7 z1 X9 Z& ]; s* N
"""
) T. h* n& B) a; @9 @4 r) Edf = df.convert_dtypes()& Z+ o3 ~- Z* J
df['year']=df['year'].str.replace('\D','',regex=True).astype('Int64'). x; b5 |0 \' w+ S8 z
df.loc[df.year.notna()]['year'].head()
* O2 m" K# a, i5 f9 n; {; o
9 K I% ]) d o% F. p: k0 1986
* {! \+ W$ O4 y( q: A7 K b* m1 2020
& u- d a7 u" @8 f2 2010" M+ P) ~( M/ v4 P
3 2014
$ l/ N6 A7 e1 B$ ]' K) h4 2015
$ M: `* U/ ]4 |7 z$ sName: year, Length: 12850, dtype: Int642 X" G3 Y0 b' T3 c [
% s2 X% K+ y$ \ o9 X7 f; y( }
1
- ]" _2 t r I% K/ k6 b6 X2
2 @4 T1 G' P, J6 D# ]2 k39 p; W5 e, h' |, Q; W
4
9 u% K+ f4 Q) H5 ?3 Z55 N. s2 m, i9 l
6" q* h+ l* \* y Y" h( h4 _: H
7
# E* n" Q% Y! p% v# S8+ M+ v3 Q* c9 w% h g
9
( W+ ~, _+ z" f' s- v103 W' H# l1 b8 u5 D# U
11
. }( s& q6 S+ i! E* s12 q) @7 C8 l8 o+ H9 k
13- Z* I$ `$ T1 R/ _9 h' m- w+ P
14
V+ u6 l) ?0 f0 u% }, G154 R2 V0 I, F4 I& C/ a! v" P
16
" i. f! U: K4 r2 }9 O! I1 q$ m参考答案:3 h( b3 J) W/ H; q1 V
) A9 n4 j6 h9 V/ W6 p' ^5 U; P2 Z6 f不知道为啥pd.to_numeric(df.year.str[:-2],downcast="integer")类型为float32,不应该是整型么) ]& B3 N3 y) M7 F; y: y
7 o7 g5 Z1 I/ P) F# N0 J
df.year = pd.to_numeric(df.year.str[:-2]).astype('Int64')
( @& ]) p! i+ N1 U" O) D% _df.loc[df.year.notna()]['year']
, C8 A' [: Q( g: ^$ }: d# }6 P13 `( b1 |1 b7 T( o4 Y2 q4 G/ G
2* N' `" |7 C7 x% O
将floor列替换为Level, Highest两列,其中的元素分别为string类型的层类别(高层、中层、低层)与整数类型的最高层数。
: i+ h: G# j! @& \, z$ h4 D6 |pat = '(?P<Level>\w+层)(?P<Highest>\(\w+层)'9 ^8 x# Y5 ?# s& b
df2=df['floor'].str.extract(pat) # 拆分成两列,第二列还是(共6层得形式,所以还的替换一次
9 h H7 ~2 k2 r9 R& _df=pd.concat([df,df2],axis=1).convert_dtypes() # 新增列拼接在后面,再次转为Nullable类型% Q2 X/ |( X% z# J7 x+ n
df['Highest']=df['Highest'].str.replace('\D+','',regex=True).astype('Int64') . H1 x- t5 ~ S' T% U& v# Z2 \
df=df[['Level','Highest','year','area','price']]" ~3 @0 u! K8 E1 m" j, S
df.head()
3 H7 r+ s0 W# e; a" v/ {6 y* k8 I# c! o+ \/ m% S0 i: z4 r* |
Level Highest year area price
; A: w: y9 z q, q4 u& H; [- k0 高层 6 1986 58.23㎡ 155万+ w! m" C4 Z1 i/ a) M3 k
1 中层 20 2020 88㎡ 155万
( u+ x% }8 t- o- ]( `7 h2 低层 28 2010 89.33㎡ 365万* ^1 I3 Q0 Y; @! J7 \; z+ Y
3 低层 20 2014 82㎡ 308万4 H% t) l3 p5 R: B% A+ G- x
4 高层 1 2015 98㎡ 117万; T5 s$ ^- S- k
1
, h, s, Q% a* u3 w" }: U n0 D/ {9 I2
" ]0 `5 L+ I; m) B( J2 ?0 _7 |" h3* ]2 A8 Y. ]9 V, O
4
. l3 V: b. e& p$ X57 v; _ f- u% t1 k8 {" L
6- V1 h. j0 T, @! z- E7 I
7
: x3 |# B' D7 A6 j: x! d( f. c8
. _$ r Y: d" y& S, [' o9# p* m2 d* H9 }7 ~- {! ]
10
6 q6 T* K$ L9 @" i; B11( ?9 e( \) l2 d. w/ N
12! y3 u% s, z9 e) D; F; N
134 ^. B0 i, l9 c* |: p6 S1 T
# 参考答案。感觉是第二个字段加了中文的()可以准备匹配出数字,但是不好直接命令子组了$ J l( B: w$ m+ i5 U- Q. K
pat = '(\w层)(共(\d+)层)'& \- t" A( ~" a: N9 q4 M4 n
new_cols = df.floor.str.extract(pat).rename(
0 H: x. [) W3 B+ a' O columns={0:'Level', 1:'Highest'})
1 N4 Z, o w) g, S% q
) g+ T% a) E- M6 w8 B5 p! f% @4 a0 `df = pd.concat([df.drop(columns=['floor']), new_cols], 1)
8 C2 M" V( i" m# O; L6 \4 C4 M! cdf.head(3); K. l! l6 c' {. h7 t
5 y% y+ u% _) k9 p9 `2 UOut[163]: # U( Q0 j. Y8 ^. P
year area price Level Highest
# k# Z6 T/ Y' g4 Y: h; V( m) w0 1986 58.23㎡ 155万 高层 6
$ ~( j, s$ x' H; W* C! N1 2020 88㎡ 155万 中层 20
$ `* D2 R/ F1 w8 `, H8 B. l2 2010 89.33㎡ 365万 低层 28
7 i% T$ C) s( L6 s: U1
8 r0 K$ y; T% }2 x2% o" n3 ?2 w7 J( l! x
34 h( s* U' L' p, t
4- P9 E: K# o3 L* C% o, q8 b4 U
52 E2 u. P. }; |
6 a! a& x6 p) O' S9 W B
72 Z% @0 _6 H {
8# g. x5 q4 j, n8 Y( J8 ~
9
8 m) J8 j7 h2 y7 M; _2 h! _10
2 C2 q; ~6 |1 _. O7 v+ a* I2 ^11: c& A2 X% _% p
12
7 F" l0 M2 p0 `, [; U; o13" Q/ q* R( ]: \; |$ `: O
计算房屋每平米的均价avg_price,以***元/平米的格式存储到表中,其中***为整数。
5 P+ s+ z; Q3 j7 U: G"""
; {' N: X0 L( D3 L8 ustr.findall返回的结果都是列表,只能用apply取值去掉列表形式9 y1 L8 S# p1 v7 k
参考答案用pd.to_numeric(df.area.str[:-1])更简洁" _+ C1 p2 z6 K8 ?- C( M! b
由于area和price都没有缺失值,所以可以直接转类型5 `/ ]! t0 F6 L7 i% v" _0 Z) H
"""; v6 O p1 _ }- X
df['new_area']=df['area'].str.findall(r'\d+.\d+|\d+').apply(lambda x:float(x[0]))
$ t7 E: j1 F7 Ydf['new_price']=df['price'].str.replace('\D+','',regex=True).astype('int64'), @) v' g* `1 U7 f8 m' e
df.eval('avg_price=10000*new_price/new_area',inplace=True)- ^. C( v" t, ^3 N
# 最后均价这一列小数转整型直接用.astype('int')就行,我还准备.apply(lambda x:int(round(x,0)))5 I. z( I) F2 B, K; w
# 最后数字+元/平米写法更简单" t' s9 L. d! r" o1 t4 }' ^
df['avg_price']=df['avg_price'].astype('int').astype('string')+'元/平米'
9 Z0 ^* |; ^8 `! E% d: {, J2 j# Qdel df['new_area'],df['new_price']
6 h; g0 b; ^! n$ I( w+ Mdf.head()
: `- p& Q: H$ I- G/ n; h) z
1 A0 g' c( [' o2 t- B3 T Level Highest year area price avg_price
2 I* w" w% h4 {0 高层 6 1986 58.23㎡ 155万 26618元/平米( h; b: r4 W( ~: }% O
1 中层 20 2020 88㎡ 155万 17613元/平米5 v" \; C4 e+ ] T y
2 低层 28 2010 89.33㎡ 365万 40859元/平米3 m$ b. l: V5 d
3 低层 20 2014 82㎡ 308万 37560元/平米
. [ l( a6 B' U4 高层 1 2015 98㎡ 117万 11938元/平米; }9 B3 H0 g0 R: l [
: c8 f F, Y2 r5 ?& x
1
! a4 k4 `1 Z# C* O% p; z" A2" V& j5 `7 ?4 D$ z4 E8 p2 _
31 Q& V, q6 d1 _5 U& H; K$ d, @8 `
4
7 C2 B. ?* N' {4 _2 n& P5* K- M1 F+ B8 v$ F/ p$ C
6- i; Q, m/ [. x
73 u6 K+ m% {: ]9 Q3 f
8
8 ^* u* s6 N0 V; i# m m; _9
& D. J! b/ D) E0 f5 L1 o' x, f4 {" w10
# R+ @8 U( Q2 I" W4 j6 r110 a7 G7 t; [) L5 n% T& e2 R* h
12
: [, g7 I+ f; P k! f+ M8 S13; |1 M3 S. d& |: K: A+ x
140 D1 R9 {0 v/ G! p" h
15
* i" Y8 B! Q$ W+ P- ]$ G ^3 f16
( q7 J2 r! L- ^& |0 H7 M" d17. `) `! y) j7 R
18
5 z! v, ^, ?- s19
: r% e: h. q* H/ Q! y* d& f* o20 V/ @( p9 r7 w; Y" X5 ~
# 参考答案
/ @" @) w/ [6 N8 A3 ~s_area = pd.to_numeric(df.area.str[:-1])
- c$ M! M: a6 h4 p) G5 N/ o; zs_price = pd.to_numeric(df.price.str[:-1])
/ o. C4 N: Z7 M p* H" |+ n2 wdf['avg_price'] = ((s_price/s_area)*10000).astype(
: N# ~: ?8 ~& e4 S1 i 'int').astype('string') + '元/平米'
1 T! S% l M8 ^% M: j5 n6 Z4 V
. P! r0 W4 U% Q5 Y" Jdf.head(3)% \2 G; r6 p" t0 b6 ?
Out[167]:
# o h8 i8 E( j: U# P- Z1 H( f4 b year area price Level Highest avg_price& u8 u( j2 w% P( Y0 t
0 1986 58.23㎡ 155万 高层 6 26618元/平米
- L0 \ w7 c* @0 ?9 q- t7 n6 P- A1 2020 88㎡ 155万 中层 20 17613元/平米, q- K- ~' P6 N* v
2 2010 89.33㎡ 365万 低层 28 40859元/平米# J0 I5 H$ p# j) o7 ]
1
. ~. t. a+ ?( k% v3 m1 G) ?2
- w1 _4 O2 {# R& m& n) F32 M6 A3 W9 |+ h
4
m c/ |- X. ]% J7 ?5 h5' B# r8 F4 M# S2 b+ A- P
6
# {8 E6 ^' C# l& U: Z( G. ]* d7
; e0 J' P1 [+ W! z2 Q1 Q8$ L. C, n; Z( B+ Z" T& \# G4 `
93 Q! n+ y# u1 L4 Z g: S
10
! O3 `3 y' W0 _, ~" o$ Q11
/ d" G2 s) H+ Y v12+ C9 H' k7 T3 O4 Y9 ?2 l8 o! f; G% j
Ex2:《权力的游戏》剧本数据集
7 h: y7 M! m: ]8 @/ {现有一份权力的游戏剧本数据集如下:
) e! e, C: E$ i& W! d" h
9 I0 l: F, f, G2 kdf = pd.read_csv('../data/script.csv')3 j( x. f7 ?! i/ N+ q% s
df.head(3)
/ R# ~! A4 ^5 ~) S/ f) c6 z: G0 r+ f/ c( E
Out[115]:
/ t; n# Z( N t, p6 mOut[117]: ( S6 f$ a" U3 C6 E# j# x2 k
Release Date Season Episode Episode Title Name Sentence
* j+ H( G) `$ b, A: z# N# G0 2011-04-17 Season 1 Episode 1 Winter is Coming waymar royce What do you expect? They're savages. One lot s...
' x+ P+ N5 l; K# v1 2011-04-17 Season 1 Episode 1 Winter is Coming will I've never seen wildlings do a thing like this...2 z+ o3 h! D* N9 l1 j; u |+ K
2 2011-04-17 Season 1 Episode 1 Winter is Coming waymar royce 6 ?7 k$ B/ V y6 [/ j) z: ^; G1 ^
13 r! b3 ?7 }' {$ `
2
# f& C4 A: g1 o8 R0 n1 u38 R" T% J* T; [5 F: [
4( @1 {7 F+ E& b
5, Y! E# M; V/ Y B! `. Q
6
) V9 b% z7 Y# j4 O! W6 h7 s, n/ [7
& H. J& z% I: r3 T2 q$ A82 f }9 k. c! ~ X% D+ B
9
9 q& D0 d: v% q: y2 T6 P计算每一个Episode的台词条数。
7 u* }6 C- p% P ]以空格为单词的分割符号,请求出单句台词平均单词量最多的前五个人。+ E- Z8 O0 A6 ] n
若某人的台词中含有问号,那么下一个说台词的人即为回答者。若上一人台词中含有 𝑛 个问号,则认为回答者回答了 𝑛 个问题,请求出回答最多问题的前五个人。
# t) y! V& ]% T- e# w计算每一个Episode的台词条数。6 A; j+ J( p) `8 n. i4 i, x
df.columns =df.columns.str.strip() # 列名中有空格2 B, @3 Y4 G- k+ o9 O
df.groupby(['Season','Episode'])['Sentence'].count().sort_values(ascending=False).head()- _% K) I8 N: X+ E
! Y- h$ ]0 ^1 Y9 B1 r& Fseason Episode
4 {% [6 }- j: y/ Q. jSeason 7 Episode 5 505, h& @7 Y# T( R( v: i9 B- D
Season 3 Episode 2 480
3 j, j. v/ h2 ]# NSeason 4 Episode 1 4751 y9 X/ L$ f) m+ Q' D
Season 3 Episode 5 440, k9 w+ B5 M: J& {
Season 2 Episode 2 432
( d4 @. J% i# X, u- M( j5 _: N1
' u; @4 d* q- n* { v' [9 j2 W29 `9 y; I u. d: W
3
0 |7 T& t9 W: x/ j$ ^8 \ s7 c* Z0 s43 m1 Y% Q# {) o2 k/ }+ _% ?. F
5
0 C0 K) s+ H" `! I- b9 ^6
$ _' u" Z! O# R. L7
$ w9 Q$ r8 F, L) b! K# T5 i8
& Z- W9 z2 U7 }% k4 ^7 B& l. i0 O95 ~9 D5 `, q4 n+ a
以空格为单词的分割符号,请求出单句台词平均单词量最多的前五个人。
* p2 D l9 S' {. s: [+ k# str.count是可以计算每个字符串被正则匹配了多少次,+1就是单词数
& d- q2 @8 a8 n' D& n2 F+ m3 ~df['len_words']=df['Sentence'].str.count(r' ')+14 |+ r. N- K) O3 L) ]% X# J9 v
df.groupby(['Name'])['len_words'].mean().sort_values(ascending=False).head()
+ a t7 F& U+ a
( k/ X" h# f* h" t: W7 n; T7 hName4 {6 y8 L2 {+ n4 b3 g
male singer 109.000000
# [9 B6 r+ M# N6 eslave owner 77.0000006 q$ J) s1 U" h$ P
manderly 62.000000; o O+ H6 m8 N, `$ s$ w1 v
lollys stokeworth 62.0000001 F6 `7 B" [+ W R6 y
dothraki matron 56.666667
8 s4 K. [0 O4 [2 O4 G/ z9 GName: len_words, dtype: float64
7 a# A1 C) o: w5 K( {, R$ u" u1
1 a. c5 k: h; x; J24 Y" x7 h, v' v$ j
3
( T9 v( ^! Z2 N8 d' Q4
1 l7 S, p0 h, t9 Q5 v5% ~0 p& g# d A& [. k* U
6
5 ]- V( h- [# ~5 ?7/ Q+ A# ]9 j4 s& m& x, f9 Y% N
8
+ l. R& r4 n2 ]2 }: T# F: B+ y9+ _0 n3 h% f8 T3 t+ F
10
" o! C, n( B% K# c" f, B11$ F' P: M' I8 s% K6 u
若某人的台词中含有问号,那么下一个说台词的人即为回答者。若上一人台词中含有n nn个问号,则认为回答者回答了n nn个问题,请求出回答最多问题的前五个人。
. u8 v/ ?9 W0 f/ q3 l8 k: ^9 ] pdf['Sentence'].str.count(r'\?') # 计算每人提问数3 H% K2 |. P5 L ]: ]8 |
ls=pd.concat([pd.Series(0),ls]).reset_index(drop=True)# 首行填0
& y/ T6 C3 F, xdel ls[23911] # 末行删去
( t0 j3 }$ l5 C+ U, `df['len_questions']=ls
' B* F2 `5 y" u' p5 m. Sdf.groupby(['Name'])['len_questions'].sum().sort_values(ascending=False).head()2 _2 ] c, t. k! ~, G( W( l
2 Z( U/ Y" l8 ]# @; X
Name9 c6 Z% t( Y# q
tyrion lannister 5279 N1 _# f" O3 B3 Y1 U+ s/ N) c
jon snow 374
& V7 U( E) B6 O3 T0 @3 \jaime lannister 283
+ L8 O+ n# V$ parya stark 265* Q& G& z, X. y( K2 i, p
cersei lannister 246$ I% K# }/ `0 P; V) m$ I7 ]# w
Name: len_questions, dtype: int642 C) V* H. E% E# V. ^0 A
& ~( q' d5 j: j' h1 G
# 参考答案
2 ], W) O1 R3 {0 a$ h( \; vs = pd.Series(df.Sentence.values, index=df.Name.shift(-1))
9 ~" F2 p* Z% X1 r: k- |- w* \s.str.count('\?').groupby('Name').sum().sort_values(ascending=False).head()# v2 p1 w# d- Q+ m' a9 v @
* ~5 N# K6 |) p; g1. ^6 @& J; ^( ]2 o4 V
2
+ \3 @% U% \% J$ ]. g L& |3# E" J# E2 l* T5 n
45 @/ M! L: l$ V" V! S/ d" K
5
8 a- E& `" V2 c. D. b& x6$ B1 a- J" ~' [" Z) O# q+ D1 t
7
6 V% `4 ?. R0 G/ T" _- n s9 @6 K# @' A8
$ w) m, `" _3 Z/ O! P9; D t3 w& b! [* a, k
107 i+ B% t2 F% @
11
) r* u) r% p5 z* |8 |120 w/ ]7 c! b9 v3 y) P7 r
135 X6 q; q( R& S _
14
P' v+ X0 n" D' q% q! K* u150 j& R5 R7 b' p' T) G* l3 n
16
$ P4 j& ?; H+ u' C8 f; f17
$ {1 R; w5 n8 M! a) p" `( _第九章 分类数据
- z( E+ D+ q! b3 [, cimport numpy as np$ {- Q' A. x D ?4 A1 b
import pandas as pd
* ` y' Y, X+ R; E5 a; u# ^( F1
0 q x) d' a% J. v+ T/ X: n# Q" u2
. `* o7 C! l7 {9.1 cat对象( I2 u3 z) U3 @0 z$ U
9.1.1 cat对象的属性
6 J6 U2 d7 @! c) {5 B 在pandas中提供了category类型,使用户能够处理分类类型的变量,将一个普通序列转换成分类变量可以使用astype方法。
0 u9 C1 ^( l, x, V7 g9 \9 \; O* m. L; Z$ r0 O% z6 h3 O I" m
df = pd.read_csv('data/learn_pandas.csv'," Q( C/ p' b/ K/ K
usecols = ['Grade', 'Name', 'Gender', 'Height', 'Weight']). C% O; `) J$ ]3 O% N5 i! }
s = df.Grade.astype('category')
+ {. u! y8 n7 G' d! G+ O) S# }4 ?# l# j4 H
s.head()- l0 r7 |: }! M( R+ q
Out[5]:
0 M( ?; q. [# }1 P2 f7 S2 F0 Freshman4 U. f, R0 y& J p
1 Freshman
& W, }& V, q: n# ^2 Senior
6 G, V8 y8 N+ v2 r3 Sophomore
- i; @* X/ Z: U$ [! T/ J4 Sophomore
8 i$ d |9 p4 o+ FName: Grade, dtype: category
1 M) J4 N7 }) v) B4 _! a0 XCategories (4, object): ['Freshman', 'Junior', 'Senior', 'Sophomore']0 z/ x. I# y* i9 p+ { m
1
% q) t. X1 W2 l; x" e2 r }# S- f+ y; _$ c+ g3 ?0 z0 S6 q( y
37 l* q4 ]$ h& \) [( J: \$ P: K
4
) c; \2 v% r# r0 b5 ]5& q! {9 N& O# u+ w
6
/ v/ V. F3 k1 O% D7! ~ @' d- D# [4 N6 r! F
8
3 r; Z" Q3 a1 `& K9 O4 m* |9
8 g7 o& N2 x0 Q+ F+ q10" D6 F) c5 F1 j) U2 k3 F
11$ ?7 Y7 v D$ o% m# C4 T7 |7 V
12& J: @) ~2 Q4 c
139 p% [: k8 |( D! D5 l+ Q0 O
在一个分类类型的Series中定义了cat对象,它和上一章中介绍的str对象类似,定义了一些属性和方法来进行分类类别的操作。
+ M" Y* j% g6 W
; e2 i5 r4 ~* J9 u9 U- `s.cat& A) P7 x* N! `
Out[6]: <pandas.core.arrays.categorical.CategoricalAccessor object at 0x000002B7974C20A0>
+ q1 @. ~/ `4 T, n% U0 G% E1
+ n, k7 C! m3 ^( Q$ o8 l2 ]# ^, ?& l1 c2
7 d* v2 ?% \: X" scat的属性:4 o! {; I2 E8 T7 j
8 K. k$ [& V( V3 Z- p
cat.categories:查看类别的本身,它以Index类型存储: Z' d) U: I$ G( v
cat.ordered:类别是否有序
& r0 m# h2 V( m- J8 _cat.codes:访问类别编号。每一个序列的类别会被赋予唯一的整数编号,它们的编号取决于cat.categories中的顺序. Z; }7 n2 ^( a/ k: ?
s.cat.categories
. [- f) Z; [1 F+ d( U4 t' r7 P2 gOut[7]: Index(['Freshman', 'Junior', 'Senior', 'Sophomore'], dtype='object')
; ~' r; p" U5 g- Z: P) L. Z6 O/ z2 k, }) y c; U7 `- T' Q
s.cat.ordered
4 |( K4 I" j) G' @( t" u+ AOut[8]: False
% Y8 Q4 S t9 u
& j S3 C, H |, ?. f. ts.cat.codes.head()
* P4 C J A& ]; }' Y/ C) \. M& AOut[9]: 8 N! R& f% F" H- B$ E4 h5 W
0 0
3 u9 T* f% k: L) C1 H( v' {1 0* x- n1 `. r. m0 o) X
2 2
# x! _6 q# J$ c) }6 E1 v1 M( ]3 3
! G1 j% {; m: e4 3
4 J% p8 Y. u( c8 Cdtype: int8
4 `- d- E! o+ [5 N0 x3 e1
1 Z/ G( V0 b% K3 ^4 u3 y( p- Y( r2- \7 \- l9 G, z& ^
3
v: e! v4 S" A4 }; L6 g4- N3 q% C: r: \0 b6 G
5
" |! m* f3 S" f* u# w6+ b7 r: x7 j" `- y
7
, x) }) q7 [) X/ I% @8
7 u+ i5 }% Q, `9
7 t* g; O6 O& k" `" V8 a10% O+ S8 H$ ~' u$ v4 b& Q
11
8 M y- I8 N/ y8 e) R0 S122 k! c* S1 \* V2 A
13: r6 r5 I3 ~/ u* K
14
7 g5 u: M, C7 v& c9.1.2 类别的增加、删除和修改* y; }& ~+ o( ]5 T4 R& P, s( G
通过cat对象的categories属性能够完成对类别的查询,那么应该如何进行“增改查删”的其他三个操作呢?( X% B; E: |9 v0 x- H" L! `* l* T
- N4 Y, v- G& V0 z% r4 L【NOTE】类别不得直接修改1 C% } ~0 M& l% Q5 Y, N# x' F
在第三章中曾提到,索引 Index 类型是无法用 index_obj[0] = item 来修改的,而 categories 被存储在 Index 中,因此 pandas 在 cat 属性上定义了若干方法来达到相同的目的。+ P/ k; q9 h" [2 i# U9 ^( M
% X% G1 y# ?# R! y3 Xadd_categories:增加类别
: d( G2 ?9 g2 d$ f: I( Bs = s.cat.add_categories('Graduate') # 增加一个毕业生类别
* J1 G/ O' e, B* q+ q# P- q# Es.cat.categories
3 M: R# B1 B7 h3 @0 b% E* a
8 h9 f# X t- N7 V' c2 o$ oIndex(['Freshman', 'Junior', 'Senior', 'Sophomore', 'Graduate'], dtype='object')
+ R# o# L; X% y1
# Y4 n4 [9 U |- w/ C2
, K% S$ Y9 B9 {2 h- k9 U; r3' J, h' Z) }; H/ z( u- Y& X% A
4
) W/ ?3 T7 q8 a1 o. W. _remove_categories:删除类别。同时所有原来序列中的该类会被设置为缺失。. Q0 a3 P9 {9 V5 n* H$ r8 ?
s = s.cat.remove_categories('Freshman')
. \/ p. I" _6 p% p! ?) A. T' o9 t& W" x1 I
s.cat.categories
4 k, Q8 z% ^2 c4 k [- b5 b" F$ n+ UOut[13]: Index(['Junior', 'Senior', 'Sophomore', 'Graduate'], dtype='object')
1 Z8 ~+ H: d; I; A
1 H6 r3 e" m# {8 H# \$ gs.head()4 Y2 i5 g! d0 _; N7 m5 D5 [
Out[14]: : z4 k$ k/ @ N- E* r
0 NaN Z" V& J6 f- {4 d
1 NaN& C* H5 |1 Q; H6 U5 @% w
2 Senior# W4 A9 d- ~1 M1 Z' f; ]
3 Sophomore* N1 A. q+ X u R: g
4 Sophomore
, m+ G- N# d; M1 O/ h+ gName: Grade, dtype: category
0 E) f' X. u' z8 L2 KCategories (4, object): ['Junior', 'Senior', 'Sophomore', 'Graduate']2 [8 d+ x" Y6 y9 k1 I5 S: c
17 z' [$ B v. W: m, x3 Q7 p
21 X1 {& D$ K5 B* v: m
3
1 o R, m0 K4 {: h+ N7 w3 j3 d4; X: j! y, |0 }1 A
5
" m6 Q8 W) ^1 s n1 _8 j3 N66 ]$ \7 V& ?1 o
7
/ I; B P' J3 T3 V1 J, @. g4 ~84 ?/ x# p1 E& B b
9- \6 m5 {4 h5 a& w A$ w' D
10
' j0 O) y% W# n$ W/ T11
, l( ]7 q v! V6 Y( G) B2 E127 o1 h, V" x; I* L3 E9 z0 Q! C& k" @
13
. | A1 _1 h+ H% p4 u/ \14
' n5 H' H2 V) ~' Xset_categories:直接设置序列的新类别,原来的类别中如果存在元素不属于新类别,那么会被设置为缺失。相当于索引重设。9 c5 Q. M" d. v
s = s.cat.set_categories(['Sophomore','PhD']) # 新类别为大二学生和博士
% Q7 d; [4 F) l, p4 L% js.cat.categories3 j0 @. |1 X' ~6 c: l
Out[16]: Index(['Sophomore', 'PhD'], dtype='object')8 \" s. m3 Q* c1 ]4 C- S2 ~
+ F9 [7 d2 W" P* ?
s.head()* D7 f3 T2 D0 O* f+ ^( D
Out[17]: " | d& L8 I1 @3 \3 n
0 NaN
I3 T7 _. \: i( S/ v7 b1 NaN- A+ ~; d$ B! ~
2 NaN
7 U* G/ S9 C" O% e) k/ B3 Sophomore, e) v# Q6 [; T, P* H5 ~
4 Sophomore
z+ B! r+ M0 L% VName: Grade, dtype: category3 L; W7 `1 @7 Z- z
Categories (2, object): ['Sophomore', 'PhD']3 \! u H5 b7 J5 v2 K# F
1
g- o; _! i0 k0 @3 s' o# q* |2 H5 d2 i; S1 g$ X) ]
3
A5 A* M1 p" H, y42 P. s: T! L" I/ X( B
55 J# g) w& S+ u9 a0 |& k
6
2 V( l' E* q) s8 u+ S7% [1 c6 |9 u2 q
8* j: A1 Q4 |1 t4 B/ c( r, V8 [) }
9
" U& \8 @0 C" ]' h& L10
7 k# L, X! P* O) V7 E& _7 p112 _) i, ?' v% k: Q2 q$ M. q! P
12
5 K/ e7 ?9 R: k0 R# v13
6 m y* }9 [" G. v' vremove_unused_categories:删除未出现在序列中的类别6 @# O9 r. O L3 u5 [
s = s.cat.remove_unused_categories() # 移除了未出现的博士生类别1 X: }; ?1 y _2 z' k# }! b0 H
s.cat.categories
: O. K3 G3 a8 z! c2 q2 E7 @7 K
3 C: Q$ l5 ~; o9 Y7 G9 hIndex(['Sophomore'], dtype='object')2 m8 T {) A6 @. d+ T* y- U3 y' ]5 p
1
6 x/ _7 b0 v( L8 m7 M2
% i Y- c" e# ~+ ]8 [! h3
. Y/ j2 y& w! O Y( C4
6 \3 S6 t+ j2 F) Krename_categories:修改序列的类别。注意,这个方法会对原序列的对应值也进行相应修改。例如,现在把Sophomore改成中文的本科二年级学生:$ ^; u0 a. W6 D" d& w; D
s = s.cat.rename_categories({'Sophomore':'本科二年级学生'})+ e t4 R& M; M1 R' t1 C+ x+ L( g4 Z
s.head() l" q0 Q" A6 T. _* X
# N# E2 Z" ?2 `& H* H5 Z
0 NaN
" C4 g! E% s9 W( |9 D( {# a1 NaN
' ?$ O+ g+ p" C7 a2 NaN" `) Y/ D$ F8 B* T& K7 H
3 本科二年级学生
0 y5 x3 H+ x, B4 本科二年级学生! L6 y& H# @! U4 D
Name: Grade, dtype: category
8 _ \! e- Z' Q- n c6 B7 zCategories (1, object): ['本科二年级学生']( e, ]+ {9 b/ H8 ]
1
+ {) Y T* }& R27 {/ f$ e& }+ z+ E( m
3
: v z9 X& ?( \6 t) t4
: C8 S. |+ D& c9 n9 k7 P0 t3 f5
0 ]8 `8 D; _4 s& Q! j. ]. C( q6- o F- x6 f0 k( V1 Y! t9 {
70 Z* v5 d1 J9 T
8! I8 }& p6 Z7 \# }
9% _! r$ t" ~/ s2 B7 B
10
) |6 |- t3 w' D$ Q/ h! }* D* k9.2 有序分类* i. J/ A" `" ^ A" L' V
9.2.1 序的建立
6 V/ o$ P1 f; N4 C- u) p) d9 u 有序类别和无序类别可以通过as_unordered和reorder_categories互相转化。reorder_categories传入的参数必须是由当前序列的无序类别构成的列表,不能够新增或减少原先的类别,且必须指定参数ordered=True,否则方法无效。例如,对年级高低进行相对大小的类别划分,然后再恢复无序状态:
; Q9 ~# P& ^- s }+ |
! u; W7 W7 N3 ~& F$ Ls = df.Grade.astype('category')
0 v; z+ D2 ^: @& _& ~7 Ps = s.cat.reorder_categories(['Freshman', 'Sophomore',
1 W k# C8 S/ ~& O) W7 R' k0 A$ z 'Junior', 'Senior'],ordered=True)* Q. E# p# `, [$ w" ^5 g. Z* z
s.head()
/ b# ?5 q, H3 w$ `/ ^6 \0 HOut[24]:
+ {% G j) p9 q" P8 g; o0 Freshman
' K+ _# r% {8 c8 N' j6 o% @1 Freshman
/ o& Z8 q6 f& F$ O$ I$ d- h6 ?2 Senior: v, j. _9 s9 n" t. n
3 Sophomore
! `! [+ t/ y- y4 t. Z: A4 Sophomore! L4 V+ n" h) o7 m# ~' x
Name: Grade, dtype: category
) z8 g: G* ^% ^: d5 V" K: ]Categories (4, object): ['Freshman' < 'Sophomore' < 'Junior' < 'Senior']( _: P1 y7 W! m3 {
& D; `# a; ^/ U4 e
s.cat.as_unordered().head()4 p: O3 W- j! e- ^ w
Out[25]: 7 v% w: S% Y! S ]& N
0 Freshman! ~( ?$ @8 ?8 X
1 Freshman
/ u8 ~7 f+ ^& p+ n. k2 Senior- B! p6 X, m# s! K4 W4 D
3 Sophomore5 N8 @( c, J9 ^1 Q, G& P9 C
4 Sophomore
% f/ Q% ?, A+ k/ J' RName: Grade, dtype: category* H) ^7 e ?4 `/ V j4 i: J
Categories (4, object): ['Freshman', 'Sophomore', 'Junior', 'Senior']
# v% Z5 ]# M" y! N
# K! |% \2 M! c! D1
+ L; M+ p, f; ^; s6 G2
+ _' O+ [+ {* P9 l4 N% W/ f( d3$ }7 ?7 W; E$ |/ b) I- C. S
4
5 S1 _8 y/ t- C N0 |* [53 c3 e( B, O Q: }
69 Q6 d8 v" e$ Z- O9 R
74 p) a7 @+ [5 h' v- `" s
8
9 y. ?) m! b" J+ \. f9
. Z" d7 p9 Z: h8 M# @10
' q; z- v' ?3 D( ]118 g. A5 q1 h# j- o/ R( B
12
5 H# H. f/ \: g0 w! y/ ?" \" `13
# |$ M3 D j) f; \2 Q, C' N14
% a6 g3 i6 t/ H+ x; V) ? O15
& h- N) b+ V- u, `9 ]16+ P9 i2 t* z* l- j" Y) r5 L
17; B, e$ R4 _6 W
18$ I' d9 w+ V) r3 @
19
$ s, J* W+ X$ l& o0 i, G Y20& f8 t. E0 [9 @0 B
211 P5 F8 d, e; S! l! @' n
22
. `/ B0 \7 Q9 f! [3 `9 ~% W 如果不想指定ordered=True参数,那么可以先用s.cat.as_ordered()转化为有序类别,再利用reorder_categories进行具体的相对大小调整。9 _! `1 P; S' p/ n4 L
* O9 ?* @6 u) r" {- _- g9.2.2 排序和比较6 e& {' Y( i# l: q
在第二章中,曾提到了字符串和数值类型序列的排序。前者按照字母顺序排序,后者按照数值大小排序。& z. m6 T$ @7 {# a: ~% i8 @
- ]4 q8 V7 l- L& q& ?2 ?2 J/ S; L 分类变量排序,只需把列的类型修改为category后,再赋予相应的大小关系,就能正常地使用sort_index和sort_values。例如,对年级进行排序:
! d8 t+ B& K. T$ |" R4 ~
) K0 J0 ~+ I' G6 q; [: {df.Grade = df.Grade.astype('category')
1 n4 e+ Z. F9 i* C5 Z2 w8 sdf.Grade = df.Grade.cat.reorder_categories(['Freshman', 'Sophomore', 'Junior', 'Senior'],ordered=True)
' { \5 u+ [7 V8 y& mdf.sort_values('Grade').head() # 值排序
# a& V, N3 N- s- G$ t! E ]" dOut[28]: % b4 y9 U6 {2 j
Grade Name Gender Height Weight+ K. N1 p W3 ^
0 Freshman Gaopeng Yang Female 158.9 46.0 e" l1 q: \2 f0 k
105 Freshman Qiang Shi Female 164.5 52.0
+ Y3 V+ Z9 A2 Z4 x/ @96 Freshman Changmei Feng Female 163.8 56.0
# ~, R, o7 q) D e9 O5 i88 Freshman Xiaopeng Han Female 164.1 53.0
8 z2 u& K$ m- }" e: Y& D6 L0 n81 Freshman Yanli Zhang Female 165.1 52.00 B) `* o8 E% i& W
, y; k, q, @1 |5 L2 h: W
df.set_index('Grade').sort_index().head() # 索引排序( L! ~- {' V3 c* s
Out[29]: . B3 k& A8 L0 h1 F
Name Gender Height Weight9 }, {* `/ A' e- g! w
Grade + s+ Q' A, e/ Y! t- V# V3 _
Freshman Gaopeng Yang Female 158.9 46.0
" h: x- W! X) M* Q/ z+ @' q% m4 VFreshman Qiang Shi Female 164.5 52.08 T H6 p* i( R. M5 L
Freshman Changmei Feng Female 163.8 56.08 y/ B' O5 k( x- y
Freshman Xiaopeng Han Female 164.1 53.0
[. h/ J2 x1 e+ A/ l/ FFreshman Yanli Zhang Female 165.1 52.06 v5 G1 r) r* P: X7 K
; l# T" U( ]9 c9 y
1$ [5 [' ^; L; u2 K. h
2/ K, [5 h$ B: q/ _! ^6 B' d
39 Z2 E6 z$ N* I8 R. q" k2 P
4' Q8 I/ B, l l$ p% y
5/ O& ^6 r0 s" w: {# r
67 O2 o/ ~2 k) J3 [, S
7- `9 T% i$ b. V& E) W
8
/ P9 `1 ~7 j G7 ]9# C5 u/ S7 `* r w3 w
10
& l. |0 Q- N# f11
% }' V1 a2 W! ^- V4 k, M" u12. R( `' c# B; j) u3 @
13 I8 v" ?) m2 q* [7 i- Q
14 Z) p* O# b _8 P6 G
15: `8 R( n% D+ {, _, c
16" b3 U3 T9 q' J
17
. }3 L w* o) F6 p0 [18
2 `! n. j" t' N1 t, @1 [! a/ k19
: g( I8 A% k& k$ ]' L, A# h20% {& b" F6 j. ~6 p+ m/ w
由于序的建立,因此就可以进行比较操作,方便后续索引操作。分类变量的比较操作分为两类:' L! i$ o4 p+ o* w# d% y
* ~/ g% _) D" F8 M6 R
==或!=关系的比较,比较的对象可以是标量或者同长度的Series(或list)。(无序时也可以比较)0 C2 j$ P% T) N( u" A4 @
>,>=,<,<=四类大小关系的比较,比较的对象和第一种类似,但是所有参与比较的元素必须属于原序列的categories,同时要和原序列具有相同的索引。
5 o4 j0 g! g7 ~1 e1 xres1 = df.Grade == 'Sophomore'! l, T) I3 I% g5 y* x( c9 ?+ M, n
1 L f% s8 Z$ w/ v( x, t U
res1.head()
% u7 N! i& ]/ m/ I1 K y9 KOut[31]:
* ^4 R% z$ o* S3 V0 False% [ L* V0 s& h3 N) D0 b. I6 L% S
1 False8 ^! O" ~5 ~6 P6 f
2 False8 O6 G5 d$ L! b7 m5 ^# f' O
3 True5 f0 g% M' l' d0 L( N$ U. b
4 True8 @0 L ]# Z# h8 n5 J; [; E% n
Name: Grade, dtype: bool
5 p4 w" t/ m3 K/ k q# X! F
2 o( B" t* h7 mres2 = df.Grade == ['PhD']*df.shape[0]
. B6 I9 p- h* F: y$ s6 X. R
6 e! { S) o( _- q7 U% o% Lres2.head()4 }& [5 Y7 S& ]
Out[33]: & _1 |' Z/ C, b1 Q' C
0 False: @* q2 X& u7 x
1 False# R {$ J) j- P0 p4 f
2 False- z/ i8 u4 W8 `1 r: I3 F E- @
3 False
! F" X; y- f/ r- I y( }( j7 D4 False
# N, Z F- O( t Z) D! LName: Grade, dtype: bool! w% q) q+ |: l' v( S# `2 ^
! |0 j7 v: |- o3 g0 ~/ t8 ~: Kres3 = df.Grade <= 'Sophomore'% c, q* W7 `- l1 Q3 G- G) i
; l5 e, X5 P! _: {res3.head()
2 P9 E. n: Z9 M# k6 g! W5 zOut[35]: 8 u. M+ A* s1 W$ q( J, B
0 True
: H1 a3 f, W& {+ ^% U1 True
8 r" j* X9 A1 }) }( i" R* w: n2 False
" h3 e- ^/ }) I2 u6 o3 True
# O& L7 K1 {8 M' z( G. E4 True6 b6 A5 E) t9 ~6 ^$ \
Name: Grade, dtype: bool
8 d$ z5 |# q7 b8 b9 ~( t; I- u6 Q' j$ N. V, X1 V6 N; P
# sample(frac=1)表示将序列随机打乱。打乱之后索引也是乱序的,直接比较会出错,必须重置索引。) m) Q$ ~ N: p% O
res4 = df.Grade <= df.Grade.sample(frac=1).reset_index(drop=True)
$ o1 i6 a, ?9 Z# K- I, C2 Z) U
res4.head()$ O$ I: x" s; V3 _
Out[37]: # F" r! t3 p. i
0 True! a4 D+ Z. X! t. y8 C
1 True- y1 @' Y# f* I% R7 B
2 False, P+ n: a4 e6 [0 g. [6 H
3 True
, ^9 ?7 k# } s& b6 g4 True& \3 q2 D. f, G+ u0 Y& B
Name: Grade, dtype: bool
5 a b: O5 V" u7 E N5 [4 k4 J2 M# W3 L. ? w
1
/ w6 C+ N) b. I( @2
. F) s4 [3 {" D30 k) L% _- _- H4 Z3 ?; b
47 C# H- U, ^7 T& H
50 K: S# G# N! n- v( K
60 Q- u+ z Q1 b2 W C' J. N
7 ^; @4 ^" N" j6 C! s4 T
8; Y2 d8 \; o2 I
9
( q' [4 Y+ D2 ^7 M% O0 R$ Q103 o6 P- a2 W' y; j- B4 l( D
11. K6 [# N' H t; ^3 t4 M
12, Q4 P# }* I$ g+ b7 P8 o
139 P# c# l7 y) C5 f
14& T% a7 X9 b5 i$ r+ B
15
3 @6 l4 r0 L" a. W! J0 Z16
; ^* L+ W N7 Q17/ n0 a5 e4 {0 g) O; g4 S
18) P0 _8 {* r: y6 y' `
19
3 e, Z7 d7 S1 x0 f7 H$ i- ^20
6 R/ g5 C E5 K+ p% \8 P$ g8 D/ P21
1 J6 L+ Y: l7 W2 E: _22
( G$ W) `( G1 A6 n6 H: u c/ P0 l234 Y& g7 Z9 t1 {" r( l- e7 p" U
24
8 k( f+ C3 P# [8 {- s25: n# Y/ t5 _/ u' L+ C
263 d* U: B5 r2 z( D
27
0 u, l7 {/ ^. D8 F" Q6 W28! m9 Q1 ?. C9 g3 t9 x' {8 {
29
) Y5 z( ^3 ^. o, w2 \- I30
8 q' i4 [: j# y( E31
# n' e& H/ ^! ^( g32
7 z* i/ D) [6 _$ {33
3 s) I. y+ ] W, p. i34" X: [. N- ^2 u' b1 G# o% S
35$ M4 R# s0 ~6 u- D5 ?7 M
36) a# {/ }& v9 A! s: J
37! M! {+ n7 [8 i
38$ g% ^3 r* `: R
39
9 X8 P( h5 T3 {4 }* l' L6 U P40" w1 D5 ]+ g+ Y; P A# e$ X/ |
41
N( @1 O6 ^& H+ k& v* n( D42. q" f3 D$ f% a$ g
43* K$ m( [! X/ V! l
44* i8 q( S, S9 H- H- `4 }( T
9.3 区间类别0 B9 {! P- U6 O. y' e1 f: i; U$ ?
9.3.1 利用cut和qcut进行区间构造
4 b' r/ G/ H0 W/ z1 S 区间是一种特殊的类别,在实际数据分析中,区间序列往往是通过cut和qcut方法进行构造的,这两个函数能够把原序列的数值特征进行装箱,即用区间位置来代替原来的具体数值。# Y2 C+ @2 n2 T, S" ~
4 H4 L7 t) A$ m
cut函数常用参数有:7 r$ o7 U* E2 \. j
bins:最重要的参数。. H8 t- j [ p
如果传入整数n,则表示把整个传入数组按照最大和最小值等间距地分为n段。默认right=True,即区间是左开右闭,需要在调整时把最小值包含进去。(在pandas中的解决方案是在值最小的区间左端点再减去0.001*(max-min)。)0 |' z9 O3 ]( y) ^
也可以传入列表,表示按指定区间分割点分割。+ X: A" `. `7 |' |
如果对序列[1,2]划分为2个箱子时,第一个箱子的范围(0.999,1.5],第二个箱子的范围是(1.5,2]。
% k( q. x; F# |: b4 H 如果需要指定区间为左闭右开,需要把right参数设置为False,相应的区间调整方法是在值最大的区间右端点再加上0.001*(max-min)。7 I, u5 h2 s) m& u
7 ]" h& m3 ~: @# [
s = pd.Series([1,2])
( \0 ~) N6 t; x P# bin传入整数& W ]% o! z* | s
! x( U7 t T" \5 k& `9 `: L9 b6 M
pd.cut(s, bins=2)
7 a" F% c& u D! W0 n6 J6 x5 x; yOut[39]:
9 ^4 M# z# w) w3 c( L0 (0.999, 1.5]( K4 I! N( w0 N* v7 D B8 g$ d3 [
1 (1.5, 2.0]* V1 ]; N' O- W) L& S
dtype: category. }. T6 S- V6 j
Categories (2, interval[float64]): [(0.999, 1.5] < (1.5, 2.0]]- v3 ^+ E* A: {% n2 l/ S; \/ Q
9 d0 |- n) r- b$ J& ipd.cut(s, bins=2, right=False)
: f, Z. n: |( x# o8 `Out[40]: ' H6 v' M, t" p( q8 q1 L
0 [1.0, 1.5)
! X9 m' E. @- q* J1 [1.5, 2.001)
! c3 w3 Q1 p! T n; U0 V# Xdtype: category$ B9 f0 x- D. m' C* w" z
Categories (2, interval[float64]): [[1.0, 1.5) < [1.5, 2.001)]2 C. z7 Y7 Z( m$ q
) f+ A$ y1 \2 S- o: t
1 |: m# u) i9 T' K
# bin传入分割点列表(使用`np.infty`可以表示无穷大):
6 j: H- ]5 a0 \4 J$ Npd.cut(s, bins=[-np.infty, 1.2, 1.8, 2.2, np.infty])
* K1 R2 D2 T( ~$ zOut[41]:
1 _3 p" S, J6 g7 T, h0 (-inf, 1.2]
7 {- `( r% B2 H7 R( t( G3 }1 (1.8, 2.2]
: x' n+ P4 i3 M2 T7 P: _dtype: category
, [ a0 }# n# ?2 D, s/ A- ]Categories (4, interval[float64]): [(-inf, 1.2] < (1.2, 1.8] < (1.8, 2.2] < (2.2, inf]]
4 `8 E( k+ V3 m( X* s- O3 z2 v0 \! t$ F# [5 X
1
* n4 k) ~6 x, E0 f4 i21 L2 g+ T9 j9 u+ C1 ^
3* }0 H2 G+ q5 f8 F/ n1 F
4
Z. N- n, K: S7 }5
: E- q L# c/ B, V8 ]* `6. k" P6 w, w1 h" B7 m: _" K
7
( h* D/ P' h/ r* [; w! R' q; N8/ A N1 D, x& r! B
9
. z; l# q7 V9 n3 |5 t& h10
$ I' w' z& i% b# P110 v3 _9 m" j* M7 I% _6 |# x
12. j2 h5 g3 D2 C% R& T% q
13$ M7 m4 M2 E4 c; ~/ y
14
% \, O" y% \: K# N/ c* l15
8 |/ k- A* K* R) N8 Q8 k9 M16, m d* X$ @: f8 s
17
$ `& P2 v& ?; U6 ^* o+ X18
2 B: Z5 K! V, g5 J, q; ]19( z) B' q. {; J! M# Z4 D$ e
20$ Z' [" f4 f/ S) s/ P
21
# Z, o) J- N5 j- y22
* t3 o; }( p* I23
+ G+ ^4 q& i' n# a' f24' q! h2 |* b# c; \9 w: \
25
1 V" j' y# ]! q3 blabels:区间的名字
# i' ~8 s, r: A- t5 w k0 X5 [' v7 K; Bretbins:是否返回分割点(默认不返回)& d, l# n) o/ U8 [3 K# l, e# s
默认retbins=Flase时,返回每个元素所属区间的列表: J2 c5 m5 v( `
retbins=True时,返回的是元组,两个元素分别是元素所属区间和分割点。所属区间可再次用索引取值
+ M0 k* j. A0 F* G
- [; [8 J2 d) n; Y3 g$ x6 Q6 r; Hs = df.Weight6 y% U- v) k% ]) m
res = pd.cut(s, bins=3, labels=['small', 'mid','big'],retbins=True)& n6 q5 B/ }, U9 l/ W) r( P
res[0][:2]0 K* p! {: |8 `1 Y2 e( I3 I/ B. W5 [
+ J4 U( w3 {! c- X. h; P u
Out[44]: * K& j2 \, x: U" z8 R2 S
0 small) v: x& v" l7 r" S
1 big
) V5 s. N; u; |" p9 d! W' Adtype: category
! B1 t5 u7 w$ o; |& l& UCategories (2, object): ['small' < 'big']
1 l% A1 {3 E+ U9 H/ p7 l; o6 g2 {8 ?
9 i; S5 R3 E, x+ I. b, G9 y0 G$ ]res[1] # 该元素为返回的分割点+ r, {/ G# u+ m( \ Z2 z" l
Out[45]: array([0.999, 1.5 , 2. ])
! k8 Q. p* C: p" F! }) w; i# |! k1
! U$ z: q" e" `8 _2
4 g8 o% b5 s/ K3
6 N l% t: e( {4
3 E3 H- B+ v6 X/ u/ }- [5
' x- e4 Q7 A% p! y6/ z# ~( M* v' a
7
: _* n1 ]$ }7 ^( U& E4 \; R8( I ?1 y. p4 s5 t3 q6 K
93 \7 _& D& R' C
10
) N) J/ z# B" j6 t! Q11( G! Q( l# x0 |% @$ C
12
9 \ ]+ M" U2 x [qcut函数。其用法cut几乎没有差别,只是把bins参数变成q参数(quantile)。, a- b4 v+ L; S* S+ i% w+ [! S
q为整数n时,指按照n等分位数把数据分箱
3 W; ^! y+ m# Q( S$ x/ Bq为浮点列表时,表示相应的分位数分割点。
0 p; q9 Q$ p9 g3 ?, Ts = df.Weight
& y% G/ W& Y" h; `9 d2 Q2 k
1 B2 Y- B1 Q, r) R, X# Ypd.qcut(s, q=3).head()
1 u _; U, q" D; _# YOut[47]:
% c+ ?, l) }; N- U- `( z( P0 (33.999, 48.0]
" [! l6 f; a' E e: C# s1 (55.0, 89.0]* D" Z; O9 g. ]/ J% V& S
2 (55.0, 89.0] b, G( u6 z5 H" x% |' Z
3 (33.999, 48.0]
k4 w9 Z0 t$ F4 (55.0, 89.0]
# _4 a; _& v% }0 |9 A9 C3 f0 KName: Weight, dtype: category2 L# |+ o# [6 B
Categories (3, interval[float64]): [(33.999, 48.0] < (48.0, 55.0] < (55.0, 89.0]]
8 C/ I: K: w2 e% n. `: O
/ B M3 f3 n' Kpd.qcut(s, q=[0,0.2,0.8,1]).head()
# J! F! e3 U" Y3 m0 WOut[48]: - H6 M! t* c! c/ \8 U+ G
0 (44.0, 69.4]
4 I4 B1 ~4 Z }" \1 (69.4, 89.0]: O0 S* `) ^/ B5 a; t! v2 }' M
2 (69.4, 89.0]
5 d3 C8 G# H9 Q t) ^5 f# V. I3 (33.999, 44.0]
8 ~$ X* f E# o0 j s) o- D4 (69.4, 89.0]9 ^$ u* b# M. E& r# V" w, p0 o/ H
Name: Weight, dtype: category
, V u" ~. f) e4 b9 RCategories (3, interval[float64]): [(33.999, 44.0] < (44.0, 69.4] < (69.4, 89.0]]: n! q* ?0 M9 D5 r
8 l+ P1 e% B$ ~- O/ X1( m9 \. R$ L, s6 j8 O1 a R
2
5 o H+ B8 Q$ n1 V39 X) a$ \: X6 Q8 g
4' d: q: H- @5 b5 d/ P" [
5, h# F# T+ ], ]& ^3 d* V
6% u, B) L# N- A/ x6 d, }9 v
70 M2 l! j2 @) O) y# K ^
8
3 K- ?: ?5 X7 {* ]9
; ?1 @! r1 |1 `, N F+ d10
) U1 ]: G5 q r3 G11
: `5 j, K; h% K* p' o1 s8 [120 s6 H5 h9 y1 e8 d! I1 I1 H& O
13
* w( i' M9 C+ r# P( F3 f1 o14
* }* V! u( s- |( q: c15
, p y% q4 @6 S0 M7 }7 S16
# k+ U- {. V, P* G173 ^9 N/ O- l, L/ I8 E5 t
185 P* C# f! j) Y
194 F5 F+ v* B8 S# C) j7 W1 T
20
/ |* R* d( B3 v, w5 S21
% d3 {7 i! e, x5 ^3 h- d9.3.2 一般区间的构造
4 B; ^7 ^! M6 O y! Z pandas的单个区间用Interval表示,对于某一个具体的区间而言,其具备三个要素,即左端点、右端点和端点的开闭状态。3 L8 v% J# ^$ g f9 T
1 U- a; o) \1 |
开闭状态:包含四种,即right(左开右闭), left(左闭右开), both(两边都闭), neither(两边都开)。2 E& f5 h, W) r3 s% ?, c6 A" U9 C' e
my_interval = pd.Interval(0, 1, 'right')1 v2 H- L6 l+ c" Q' M! R# z
8 n1 U' U5 T/ i6 z0 z2 I% K
my_interval9 c" p G+ F$ g* X: x
Out[50]: Interval(0, 1, closed='right')! F( L. \) V! p: M% i
17 E: |, V* @+ d! I) Q f( q6 G
2$ |2 a+ J$ l/ z9 H9 s0 P' p1 W
39 ~8 h4 N; o" P
4
. N5 d R( @5 d7 C区间属性:包含left,mid,right,length,closed,,分别表示左中右端点、长度和开闭状态。! H* W7 [" W- V, w9 G% l( X& X
使用in可以判断元素是否属于区间; j) s% U7 A- ?: l, N
用overlaps可以判断两个区间是否有交集:: L; R3 U; e2 c' m4 Z. k
0.5 in my_interval
7 i5 A# _/ @( X& f% l5 T1 f4 ]7 q3 e H. C* x4 Y9 i
True; q' S1 K# w8 b2 d
1
# ~" ^& X& z& G1 J# V2 E2! R* I5 I9 Z1 D8 U5 {4 ?% v4 [
30 V( i, @4 ~9 t y* Q; M
my_interval_2 = pd.Interval(0.5, 1.5, 'left')
8 D: a2 s7 \0 g2 {7 P: u( K0 r6 S9 Umy_interval.overlaps(my_interval_2): A5 J1 A! F5 N
& b- j% U F+ o/ a0 I+ zTrue5 @/ ~ A( F- u5 l* _
1$ b! K" p$ A X' N4 W: [+ T
28 s& B; ~1 f4 N1 w8 Z
3. ~( g# R7 J* A9 J9 l
4
& v3 @% y% g b1 ~8 e" k pd.IntervalIndex对象有四类方法生成,分别是from_breaks, from_arrays, from_tuples, interval_range,它们分别应用于不同的情况:
' [- s3 O/ Y; K @( t
9 Z/ J# q% e1 H* P, R9 e! Dfrom_breaks:类似于cut或qcut函数,只不过后两个是通过计算得到的分割点,而前者是直接传入自定义的分割点:! m0 R% [; F8 n1 @
pd.IntervalIndex.from_breaks([1,3,6,10], closed='both')
. P P% Z) S7 ^2 ~/ M1 V; D; P o: ^
IntervalIndex([[1, 3], [3, 6], [6, 10]],
1 }) _3 x% v7 Y7 W7 _ closed='both',
5 J& Y9 t: P! `+ w- k2 @9 W- U dtype='interval[int64]')
/ n; r0 ~: r G: q1
b2 H* `# }; c6 \2 u9 T9 @28 J4 G- {2 P% }: `0 I
3
! s9 R" s0 K- t4 M5 V40 u# m9 Q' R+ j2 K3 e2 T: @' H
5" t. A/ P6 r6 }2 z/ l6 v
from_arrays:分别传入左端点和右端点的列表,适用于有交集并且知道起点和终点的情况:
8 ^0 G7 g/ o; }4 r5 wpd.IntervalIndex.from_arrays(left = [1,3,6,10], right = [5,4,9,11], closed = 'neither')
0 g$ t, ^. `, Y0 a4 }+ c& {, @. i- p
9 _$ [' B: l1 p. ?7 d. s+ {IntervalIndex([(1, 5), (3, 4), (6, 9), (10, 11)],' V5 z0 M( [# l: I& t' s2 a
closed='neither',% l$ D3 n, T+ g
dtype='interval[int64]')
. y' _! P4 K7 A8 {% m1
9 o1 a; n: h: {- n" f* y& G1 Q) d# J2; P9 l* N' r2 ~; x
35 l, b! r8 h+ x/ z% L* X7 s0 d: L2 U
4
4 ~4 k$ L$ _0 x5: R* v2 ?4 b' H' `2 H2 q
from_tuples:传入起点和终点元组构成的列表:, \& m+ _9 \+ f8 R5 N, s' [9 [+ a
pd.IntervalIndex.from_tuples([(1,5),(3,4),(6,9),(10,11)], closed='neither')) w/ d+ U& D/ y$ T7 d
( G6 O$ R9 T4 _IntervalIndex([(1, 5), (3, 4), (6, 9), (10, 11)],
/ m1 M7 H8 Z0 G% l5 g3 x0 @: h closed='neither',) @& S8 |* n# f8 T; m, M$ V
dtype='interval[int64]')5 x% R7 u$ J p2 h% A
14 a( O0 n s M* G5 e, e+ D2 m
2
/ W* h u3 u9 ~+ D" a9 ~38 }) u( T1 m2 w, f
43 d# I: M7 G3 g+ ^
5
6 x6 ?( c) q% y7 S0 w0 G- D/ S& k1 finterval_range:生成等差区间。其参数有四个:start, end, periods, freq。分别表示等差区间的起点、终点、区间个数和区间长度。其中三个量确定的情况下,剩下一个量就确定了,从而就能构造出相应的区间:: E) s# B/ t) B; j% S
pd.interval_range(start=1,end=5,periods=8) # 启起点终点和区间个数
0 H1 _, V+ r3 J2 QOut[57]:
/ j4 l+ V5 _4 }% G7 xIntervalIndex([(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]],* Y5 a: ?1 H% _3 P3 x0 c) b- P
closed='right',
L- G$ t' |7 Z2 y. W dtype='interval[float64]')
" F! X% F8 \* j- ]4 e
. X5 b" ~/ p4 Y$ s0 W: Dpd.interval_range(end=5,periods=8,freq=0.5) # 启起点终点和区间长度( Q5 O! Z* r8 G9 m- }' v( k3 s7 a
Out[58]: , f+ \! Z0 d% o" Q8 r U! P
IntervalIndex([(1.0, 1.5], (1.5, 2.0], (2.0, 2.5], (2.5, 3.0], (3.0, 3.5], (3.5, 4.0], (4.0, 4.5], (4.5, 5.0]],3 M! F5 g9 G) q
closed='right',1 i: L: _3 v7 t- }! j
dtype='interval[float64]')' F: Z# a) W8 a o& Z
1
: |4 }! a) @$ A0 J! I a/ S+ Y2; d* @' p- e0 B7 [* n5 ]
3
, d4 Y6 _0 q4 b# \% E4. R/ {9 [: v3 X+ X" c% @6 {
54 }; w) @( o( |4 r( K
6
]1 c0 o2 ]$ a9 d5 S7 y$ z d7& V9 A/ g/ I" Z' B- [5 r" R& j' H
89 W3 g: g. H. c% u$ x4 [' o
9) D6 H; ^* r) @! i, W ~
10
# Y( t, W& x" E0 S; n: s11$ @& G2 j2 F' H' k. D# `( h
【练一练】' X+ F4 T' s" W8 j
无论是interval_range还是下一章时间序列中的date_range都是给定了等差序列中四要素中的三个,从而确定整个序列。请回顾等差数列中的首项、末项、项数和公差的联系,写出interval_range中四个参数之间的恒等关系。
4 K! V2 X8 X* o; A; k: \& c- D- M. L$ `# K2 A2 Y
除此之外,如果直接使用pd.IntervalIndex([...], closed=...),把Interval类型的列表组成传入其中转为区间索引,那么所有的区间会被强制转为指定的closed类型,因为pd.IntervalIndex只允许存放同一种开闭区间的Interval对象。
/ x4 a" b5 K* Y9 }9 d9 b* m `
@- T7 V, _5 o {( A7 }5 Omy_interval& Z' A+ G4 V3 S6 \" ]) |
Out[59]: Interval(0, 1, closed='right')- N2 u; s ]5 {3 @5 h/ s6 l
$ ^/ R |1 S5 a `
my_interval_2
4 z$ o( d' ^9 a8 V, J5 o! M: |+ kOut[60]: Interval(0.5, 1.5, closed='left')2 ]1 l. C' R- j# x" l; K6 x* w
, a8 w7 n( r2 R! jpd.IntervalIndex([my_interval, my_interval_2], closed='left')
# q* C. l1 G6 f4 X5 iOut[61]: - T0 ] d2 v6 X( S6 O2 E
IntervalIndex([[0.0, 1.0), [0.5, 1.5)],
5 n! x: Z" p$ ~, \ f closed='left',3 A" u, D0 H$ \
dtype='interval[float64]')
/ e) U+ W5 g( U4 U: @1
2 @3 ~8 n$ r0 p- [+ B2
) G8 C5 `2 q' w3
" A& G" j" D$ G4; N& @9 o) K- ]5 f
5# I z7 v3 r6 s5 R; R$ L& D
6
5 \3 Z( t1 A; I5 m5 ?. b7
f$ O% m1 E* C, y- @) S8* e0 Y9 l0 e7 C9 M
9; F; p6 l3 I" k; ~$ i
10$ a7 a$ ^. q9 ^4 u8 S$ k5 l5 N
11& O' h, h& n/ e. m6 |
9.3.3 区间的属性与方法 \) h- p6 k1 m/ j, Y0 q2 J/ r
IntervalIndex上也定义了一些有用的属性和方法。同时,如果想要具体利用cut或者qcut的结果进行分析,那么需要先将其转为该种索引类型:
. g/ N4 V6 N( Y0 O, S) @' N: N+ L( _: y: o/ ?4 E4 b( l" ?) F/ w6 L
s=df.Weight
: K1 N2 F0 l. Z# @* fid_interval = pd.IntervalIndex(pd.cut(s, 3)) # 返回的是每个元素所属区间,用具体数值(x,y]表示" }8 `$ }* d1 N" \5 \
id_interval[:3]
$ Q+ A4 b% g, Z7 ?* g3 P, U* h& h
) l2 i5 M: U* QIntervalIndex([(33.945, 52.333], (52.333, 70.667], (70.667, 89.0]],1 K6 a8 V$ m6 |& n$ k
closed='right',
+ d9 N0 r4 l$ z9 U* k: c name='Weight',
$ W2 N. y' C/ D; Y dtype='interval[float64]')) f& a s1 U) [, s, n' J) E
1
9 R* c d7 c3 `6 y2, y0 {$ h( q, I9 E @$ g
3
8 @1 m' m m% d* l8 q4 g2 v4# a- k3 t! S0 ]* D
5
- T; z- m1 v, R& U6. C7 @% c3 _: ]6 q! b1 K6 A
7
; I* _, i- A/ c' l% i, }: ~8
2 J, S; @3 T/ E4 E) \ H4 i, x与单个Interval类型相似,IntervalIndex有若干常用属性:left, right, mid, length,分别表示左右端点、两 点均值和区间长度。/ H) \0 w" z3 ~) Q# b1 V4 b2 W* d
id_demo = id_interval[:5] # 选出前5个展示( ?: l( l6 L7 H6 p
; S6 y/ I3 L7 j2 H, H2 P$ J- \id_demo# m, k9 x& J! N- P* l, U
Out[64]:
% \ ?* L2 }+ _- J) z$ _IntervalIndex([(33.945, 52.333], (52.333, 70.667], (70.667, 89.0], (33.945, 52.333], (70.667, 89.0]],
5 \, h1 Z0 Y j, @5 @ closed='right',! N3 G ~9 e3 h4 t7 J
name='Weight',2 N+ s" [7 B9 g: ~' s& B+ b, [
dtype='interval[float64]')
( J/ e1 e" ?) ^1 V( a9 U+ T( }* G x, V& i" I
id_demo.left # 获取这五个区间的左端点7 d l6 ~8 U( \' p1 a
Out[65]: Float64Index([33.945, 52.333, 70.667, 33.945, 70.667], dtype='float64')3 g' W( _- F# x+ D) J: V
/ J: N; Z+ w6 m: n ~id_demo.right # 获取这五个区间的右端点3 w- u# ?0 `0 T0 f
Out[66]: Float64Index([52.333, 70.667, 89.0, 52.333, 89.0], dtype='float64')- v, U: I5 ?( R B9 v& B. F8 l& H
2 M) P9 h. i2 L; f5 B$ |' Q- |) ^
id_demo.mid
' J3 E7 M0 Z t' ROut[67]: Float64Index([43.138999999999996, 61.5, 79.8335, 43.138999999999996, 79.8335], dtype='float64')+ ~8 `# |! n% [3 \* v/ \+ w1 u
+ V2 \- ^* u0 K. H+ gid_demo.length
) \# D4 R0 `! J, ROut[68]: ) G9 k0 a9 N$ R4 s! ^: z9 i
Float64Index([18.387999999999998, 18.334000000000003, 18.333,& s: d) A2 \% p; U6 M2 k
18.387999999999998, 18.333],0 O0 |- W1 S# }/ p/ M
dtype='float64')
; a# ^0 @4 j2 _! Y0 W- }5 J2 r. L! k! G9 h& g
1% Q; V# f6 G* ^
2
( o% X0 u* a0 J) I- ~: y( S4 E3
" M5 \6 w5 p& e0 p4* N, u& [7 [) y$ r5 l
5
& Z' ^: Q0 ]5 d$ S0 J6
! E9 m, R: y- u' c2 b70 R" i3 j( q" H+ V) ~" ~( Z- [
8
$ X! @" O% j7 a, H9
5 E! m/ l" H( g4 X' \; o4 t* }10
4 Q9 G* @4 P. B3 N11
& S/ _$ B+ t2 ~- _ P% ]' t I: T12
9 t% {, F7 U4 H) W' S136 P- U3 W. S# G( }1 a
14
. f# f( p: R/ i$ c; ]2 c158 p* Z7 F& v2 X3 U4 b; B, Y0 q
16
5 f$ N4 m3 y! A' s3 w. u5 F4 v7 o17
; Q+ V: g$ ]; J- C18
' m. g, ^: x0 G' L4 A* I# a19
% x" h( p+ K5 D7 |2 h20; a" b. q0 E3 z8 ?6 a3 o
21
) v" T9 k7 G: a! `22# R1 z7 Z5 O% n0 [
23' I) M1 z6 W# S2 C+ h- Q* P
IntervalIndex还有两个常用方法:
: ?+ O7 {& }, m1 T0 vcontains:逐个判断每个区间是否包含某元素! Q, q; d* L# l# o! v
overlaps:是否和一个pd.Interval对象有交集。
/ d \9 y5 `7 |" hid_demo.contains(50)
2 ]9 s" K2 w5 W1 U; ^( Q6 rOut[69]: array([ True, False, False, True, False])
) j5 V3 y( Z: H4 h3 n0 v. E8 i7 @( w* p0 U
id_demo.overlaps(pd.Interval(40,60))
+ T6 L. T/ C: cOut[70]: array([ True, True, False, True, False])
2 d1 _$ o( y, ?) L5 X2 R. d1
% g5 k' e: r c- k8 y/ o2 c% {# V5 M: K% w; W) \
3
" p* Y+ N, u* i2 a. t5 y c% y4. J( f5 M0 b0 w, A6 o. j5 ?( v& F
53 j4 [0 J1 G) I1 w7 e- w! U: Y
9.4 练习- Y( R$ F. U# x' O& o
Ex1: 统计未出现的类别) q) J3 k2 Y: s) S7 B
在第五章中介绍了crosstab函数,在默认参数下它能够对两个列的组合出现的频数进行统计汇总:
! [ j, W0 `3 |! v9 h) k# I d0 j. G) ^- ^, _ k
df = pd.DataFrame({'A':['a','b','c','a'], 'B':['cat','cat','dog','cat']}) q# a" ]# [" @8 X2 o! m' ~
pd.crosstab(df.A, df.B)* H9 X2 d. [3 J$ k& r4 b" S4 I% S
* j9 H5 u" i7 Z9 Z7 d" hOut[72]:
+ z* \5 p0 C- Y4 HB cat dog6 \9 b- p7 h: ~* _% } P- m+ u9 p- u
A
' g# E$ H" {5 l8 na 2 09 m. j" t7 V) J& u5 n) Y* p
b 1 0
2 ?. q) J% j+ i9 _% i1 y8 Y- Bc 0 1
+ t( _" [% C: b( f10 K8 z" ]( A$ } e. X5 @% [
2- R" ?4 ~& F! [# Q4 M
32 c* j$ m" Z( T- J! \/ ]
4
* I' s( E2 O" D7 _7 O6 _5
& ~! g5 `* \( z' D" ?! y6. g$ G; W( D! }# Z
7, u9 A0 J; U0 E7 A. M. f' b7 X
8& {7 F2 S$ Q: [5 x: M
9
5 y, e, t. M" `" E& H 但事实上有些列存储的是分类变量,列中并不一定包含所有的类别,此时如果想要对这些未出现的类别在crosstab结果中也进行汇总,则可以指定dropna参数为False:
9 f+ } n, a Z2 r7 i8 c% o1 R$ M$ _" _8 k
df.B = df.B.astype('category').cat.add_categories('sheep')
" b" G7 Y. M1 n/ D. M W2 f% tpd.crosstab(df.A, df.B, dropna=False)
u# @# t8 R, I+ ~: \! `$ a7 ]& ]0 A/ i
Out[74]: , Z5 f- B2 D0 m& y( s( I% |& E
B cat dog sheep
+ H: \8 {$ W% h4 D* X* a& ?* MA 6 Z8 M0 x7 W2 F+ ^$ `' ^
a 2 0 0- K) j) X3 N& y4 c$ c) |
b 1 0 05 a/ `, G9 j, Q
c 0 1 0
6 q0 D6 |9 P3 `2 I% Q& T# S1- p$ e$ c" E& {
2
6 }/ E' ^3 s7 P* L2 b R$ G39 N& d9 Q, o! w7 P
4
& ]* [- Q% Z: x6 S1 N5
# o) R. @. c5 j8 S: I6 k6+ j1 `5 T* g5 w3 [/ e: \
7
* V' k6 D$ P, N- ~5 @# i1 c' a; B( h8
5 F4 k$ B& p: e* W8 ]6 f9
6 @ P+ b* @- ?, q请实现一个带有dropna参数的my_crosstab函数来完成上面的功能。/ y5 l, S. N# p' ^# ~. Q
6 }! V1 C+ \: y* |. Q' A0 ?/ d- m
Ex2: 钻石数据集
6 T* s( z: Y: I 现有一份关于钻石的数据集,其中carat, cut, clarity, price分别表示克拉重量、切割质量、纯净度和价格,样例如下:
# \. E8 b+ w1 B7 d, s" ?3 c0 M1 Y& K+ w6 s2 i
df = pd.read_csv('../data/diamonds.csv')
/ A. x7 Y$ M3 ?+ \8 U7 y* J( d- x/ \df.head(3)4 Y( Q! f; j/ r- k4 a
/ C7 @( d% V/ K1 L8 I: H* a
Out[76]:
+ u Y% u7 g, Q3 W T carat cut clarity price) w/ H1 ]5 m* s, y
0 0.23 Ideal SI2 3266 D: e" D% m' `/ v) v0 D- h
1 0.21 Premium SI1 326
- Y" G+ v& i* f/ Z5 {* @/ W5 F2 0.23 Good VS1 327
* Z% p. ^5 ]2 x* k$ B" Z" g# ]; C8 ?& w% a16 e8 f4 n& x- R
2
~4 M" x+ O: {. l3 w' U33 h& G; a* M: p- y
46 c! K% e2 J, w$ L3 Y3 X6 O3 u3 L
5
i3 S% |6 X- x, E: [6
+ U. w. l/ B! r% m7
, L8 @; c5 m4 {; X* a0 ?% a8
9 M/ W1 a/ }. {分别对df.cut在object类型和category类型下使用nunique函数,并比较它们的性能。3 y0 y, S8 j8 a/ O
钻石的切割质量可以分为五个等级,由次到好分别是Fair, Good, Very Good, Premium, Ideal,纯净度有八个等级,由次到好分别是I1, SI2, SI1, VS2, VS1, VVS2, VVS1, IF,请对切割质量按照由好到次的顺序排序,相同切割质量的钻石,按照纯净度进行由次到好的排序。7 C) }7 V0 Y: l
分别采用两种不同的方法,把cut, clarity这两列按照由好到次的顺序,映射到从0到n-1的整数,其中n表示类别的个数。
) v- g) S; @. y4 [对每克拉的价格分别按照分位数(q=[0.2, 0.4, 0.6, 0.8])与[1000, 3500, 5500, 18000]割点进行分箱得到五个类别Very Low, Low, Mid, High, Very High,并把按这两种分箱方法得到的category序列依次添加到原表中。1 S* T/ ?' g8 l: E# F
第4问中按照整数分箱得到的序列中,是否出现了所有的类别?如果存在没有出现的类别请把该类别删除。
* B' o; N3 y8 @5 E对第4问中按照分位数分箱得到的序列,求每个样本对应所在区间的左右端点值和长度。
+ t9 H$ Z5 M& C4 g7 ^9 L8 p$ ~先看看数据结构:
7 d+ ?( w r o$ ?" n7 k/ \6 a5 S: O+ Y, w a' F* ? H+ L) y
df.info()
& ~# B0 E* ^ T( [Data columns (total 4 columns):
! b! ^$ Y/ V; V9 s6 l# _1 l # Column Non-Null Count Dtype * a( y. p, E2 n9 f' T: e2 a; `) H
--- ------ -------------- ----- , ^; D5 R# L3 A4 E: g9 C% @0 S
0 carat 53940 non-null float64$ N, T4 s! r: I* E
1 cut 53940 non-null object 3 x& z8 O( Y3 r5 r k: m/ X
2 clarity 53940 non-null object 3 y! P: ]( i0 B! i+ U, s5 s$ J2 j
3 price 53940 non-null int64 5 }1 S( V' V/ }, E& P6 p" z( N& m% g2 Q- X
dtypes: float64(1), int64(1), object(2)
5 q8 G& a$ |$ d; | d1
0 v& K, W& S0 H5 ]+ w2
+ K! x _% m" S6 K. t( p3+ [1 j d' Y- N7 z; b
49 x$ d; ?3 ^4 I; V1 |6 U
53 m5 u; Y0 w e
6% t& {! S! P8 w# I3 U4 b
7
5 s- e0 C9 }. R8
8 W4 V: ]0 G# Z( D9
% M% o. x7 [ R: D& Y* ]- Y6 Z比较两种操作的性能- n& Z! n) y1 r$ C& Y1 h: W: `; P
%time df.cut.unique()7 p2 W0 S) d% a1 y3 D4 p3 W
! ^! o- D7 M; U9 S7 y9 GWall time: 5.98 ms
8 T+ q0 f) z& a2 g3 |3 q& |array(['Ideal', 'Premium', 'Good', 'Very Good', 'Fair'], dtype=object)
5 q. X8 \6 s7 T6 N' ?* V/ L1
4 P7 y& K S C/ l9 k2
# t- \1 Q- W. A& i3& _- H v5 W$ n( M
4
0 i, q- e8 V' _$ H%time df.cut.astype('category').unique()
+ v% \( u6 U. l) U+ j
) U" J+ l6 g. v- yWall time: 8.01 ms # 转换类型加统计类别,一共8ms
7 I- M! n1 u% w7 z0 \# M1 z7 V7 S['Ideal', 'Premium', 'Good', 'Very Good', 'Fair']
+ o* w- N% B* f! e* c7 T- DCategories (5, object): ['Ideal', 'Premium', 'Good', 'Very Good', 'Fair']
% M8 p0 C6 |" k. o( i1
% N* J3 L" t9 {! {+ G' m& }25 g: g; x4 C+ e: ]+ y
3
& M7 U2 t: h3 i4
. ^! p' F* `$ r# P, I4 E( O M+ v5
$ }+ L. H, j* B' E' h2 @df.cut=df.cut.astype('category')
+ t3 I9 g# d6 h3 a%time df.cut.unique() # 类别属性统计,2ms q0 ~, d, ]9 o V4 j" A
1 n8 L' H# A8 Y1 F* ?, sWall time: 2 ms- a" r+ g: ~7 ~+ _- p6 z
['Ideal', 'Premium', 'Good', 'Very Good', 'Fair']
- S3 s* G$ H* L, m1 [9 a* jCategories (5, object): ['Ideal', 'Premium', 'Good', 'Very Good', 'Fair']! p9 O2 J$ T5 z6 r# O2 D; K
1
( w& i' }; n7 V) b2
; m6 }, H2 G9 x, [& I+ B: P33 x! E. Z( ^7 a7 g, s
4) T$ n, D* {1 H* [- n+ [6 v) P
51 Z( K/ D9 j; c8 g$ R& d" P
6
5 e. @2 o5 O- M- e4 Y/ O对切割质量按照由好到次的顺序排序,相同切割质量的钻石,按照纯净度进行由次到好的排序。
5 B$ J `, e i/ h5 d. q/ fls_cut=['Fair', 'Good', 'Very Good', 'Premium', 'Ideal']3 y% _. I! C0 F( C W4 Y8 F; A
ls_clarity=['I1','SI2', 'SI1', 'VS2', 'VS1', 'VVS2', 'VVS1', 'IF']
( x# ^- H+ A8 f) i7 j5 C9 kdf.cut=df.cut.astype('category').cat.reorder_categories(ls_cut,ordered=True) # 转换后还是得进行替换
! e0 [' Z& ~! ?% V" L) Adf.clarity=df.clarity.astype('category').cat.reorder_categories(ls_clarity,ordered=True)
& ^$ }/ @/ J' U
& M' h$ f1 d3 X: Y7 Ldf.sort_values(['cut','clarity'],ascending=[False,True]).head(3)6 }5 ?2 h1 L, x' v: |
& j; h# U% L; S' Q- Q6 ]+ \ carat cut clarity price" c, A3 o6 i- w5 V
315 0.96 Ideal I1 2801
P' u7 v6 O% O4 Z: T& Z535 0.96 Ideal I1 2826
. W- G& V6 w6 A: N" t8 N551 0.97 Ideal I1 2830
& |! w1 w0 I: p2 V! r& T- H1# T* N+ h1 m! B) m
26 i+ v* S% h3 O# o. C- W8 k3 R% \2 a
3
! V Y; _ o5 N; m; V, f4
( H9 J6 }9 I. I/ I5 J5. I& w3 F. `5 V! ^
6# H. b* W5 j! ?; Z* i, u
7
+ p3 @$ V$ u1 Y( p83 _( ~/ X& a5 C% p
94 {& n7 t1 v/ [# p) x. i, O7 }
102 G+ J8 j& [9 J. I3 {2 X
11- a' K" ^: I, R4 g# |, S! u1 H# }
分别采用两种不同的方法,把 cut, clarity 这两列按照 由好到次 的顺序,映射到从0到n-1的整数,其中n表示类别的个数。) Z+ o8 P5 E0 D& y& f
# 第一种是将类别重命名为整数1 S- B5 s, `, ]1 n6 G1 h
dict1=dict(zip(ls_cut,[x for x in range (4,-1,-1)]))+ s, n G7 [' b3 b0 t
dict2=dict(zip(ls_clarity,[x for x in range (7,-1,-1)]))
: _* n/ \2 `& h' \* Z5 U3 ^
7 S3 D/ S% r& p' q# y- Q6 kdf.cut=df.cut.cat.rename_categories(dict1)
0 F( X# r# \. v0 g ] v! L& Wdf.clarity=df.clarity.cat.rename_categories(dict2)
' i* k$ q" N9 q8 ?, P5 M1 r$ J- edf.head(3)
5 z9 K& v, q5 X
1 k9 E, P/ h( \7 ^6 j carat cut clarity price
- r- q4 X. r3 j* `* g# a/ R$ i0 0.23 0 6 326
2 e9 C* V/ a, V" [- m4 x* {# y( \1 0.21 1 5 326& b9 e) y' }2 o% u, i- w
2 0.23 3 3 327- S/ y* Q- {9 t9 T4 t- W0 E7 i" p
1
0 B% v0 ]9 P2 [3 m+ m2
1 h! T0 U, n/ X34 Y" ~" l5 O' h3 X
4, ?3 k+ Q8 K8 e" H" _1 U
5, s, O) Y6 b5 H+ y
63 O( ~$ y- l% s0 Q, y6 x
7
% e* u+ D4 Y3 m4 I# d8( x, Z9 Q& S( O1 _' U- I) U
9! @2 d, W! Z6 K8 F
10 k' D5 N6 \ `5 L7 Z( v
11
0 F: U# O [2 g$ B- ]12
" o7 ~7 X$ e6 w7 E$ {: _7 J9 [" S% M# 第二种应该是报错object属性,然后直接进行替换* C2 _$ s0 Q4 w; N7 h8 a& J, R
df = pd.read_csv('data/diamonds.csv')$ U1 s# j2 k+ [, G6 i
for i,j in enumerate(ls_cut[::-1]):
8 D% `0 \8 z+ @- v df.loc[df.cut==j,'cut']=i
! o5 k2 G- }# T) K" o6 K6 J" s, q5 F! ~- _& v$ o& J" ~/ m) Y
for k,l in enumerate(ls_clarity[::-1]):
: x" q% A4 S! K! R! ]$ W df.loc[df.clarity==l,'clarity']=k
3 ^+ `% a B9 C# R( T5 a2 U3 vdf.head(3)
( a' H" b4 n, O% c( ?1 ]8 [; g d! Y. l8 |% [$ |; M g' R2 E2 Q' ~
carat cut clarity price! O+ r6 P9 w( f/ A
0 0.23 0 6 326
* f& W& C# A* I W0 ~: h1 0.21 1 5 3267 p- Z0 J# U+ p# ~* U1 z5 G
2 0.23 3 3 327
: U7 ^9 U& Z: K$ j1
. u u5 A' }- A2
8 r2 j- I/ Y" |7 }- b, _3
2 W/ G( Y8 m& Z' B1 u43 F% [3 f( n3 d) @+ ?& b2 ~
5$ i$ N( Y8 B, w; n
6
; j$ v0 J( P; k% F+ J+ q: s7
1 ?3 A5 P- u0 K' g7 `6 k8
q5 c+ h2 k0 G8 G9' P. P4 B; {( `% C' k2 W% I/ k& K
100 J' w$ a/ G# \- |/ o' ]
11
2 J4 m; {7 x' i% t2 f& r' h12
* {( m" f. T+ L5 O13, G9 d0 R& p! b
对每克拉的价格分别按照分位数(q=[0.2, 0.4, 0.6, 0.8])与[1000, 3500, 5500, 18000]割点进行分箱得到五个类别Very Low, Low, Mid, High, Very High,并把按这两种分箱方法得到的category序列依次添加到原表中。' s1 J( a' e/ J
# retbins=True返回的是元组,第一个才是要的序列,第二个元素是分割点% x8 x* t9 D+ M9 Z, k9 V: ?5 Q) Y
avg=df.price/df.carat
8 {8 K/ C. r" n6 O) m9 v/ y5 \; w9 ^; |
2 F. y4 p" ]" M8 qdf['price_quantile']=pd.qcut(avg, q=[0,0.2,0.4,0.6,0.8,1],
* {1 h9 T- o! z- y labels=['Very Low', 'Low', 'Mid', 'High', 'Very High'],retbins=True)[0]1 X/ {2 L% Z/ v8 [' ~; a
$ A$ I9 f/ k2 P6 m/ U4 e- v
df['price_list']=pd.cut(avg, bins=[-np.infty,1000, 3500, 5500, 18000,np.infty],' G$ k l' R% c" p, D2 k
labels=['Very Low', 'Low', 'Mid', 'High', 'Very High'],retbins=True)[0]
( f# H3 z2 p( ?0 P ^! X/ kdf.head()
' h$ v! K2 N4 N2 R1 B
# {9 F' [5 k% x7 d; T* u3 p carat cut clarity price price_quantile price_list
6 q7 Z6 @- Q, Q2 a0 U3 `$ g5 f, f0 0.23 0 6 326 Very Low Low1 X: c! H' J F- H L
1 0.21 1 5 326 Very Low Low
6 {3 E5 m3 n: }4 m: [0 S2 0.23 3 3 327 Very Low Low# s$ t' s- |( ] ]
3 0.29 1 4 334 Very Low Low- G& f1 `* X. N& ]2 H1 Z& P
4 0.31 3 6 335 Very Low Low
" E# D. i+ n2 [1 l; {6 ]- |% B
8 t6 I9 v, C1 A* b! N13 d* Q1 I; f0 K0 L1 e
24 N# T' K+ i6 a& Q$ i, A
3# f& e& Z9 t5 d/ N) U, I4 ]
42 v, ^) a) x$ \6 C- ]# o/ M& P
5( y; M# U& @. Q& |1 a) k
6
D; }: J2 I' A! T; `* g: x7
: g# R3 p8 w) U; E+ a3 M! w. A8- n5 l8 }8 G0 r) z7 ^
9
# `. U0 G2 c& ]10
, E, i& `% H+ M! A! t% R11
8 j; y) a. n% h" }$ M0 ~% ~12) O& X3 Y: A3 e2 V5 l
13/ X! N' S8 B) S5 t* ~, D
14
" p* H, l# @3 y1 }15
+ ]7 g8 a7 n! l) _6 i% C8 M16
$ ^ e$ k: ~) H T分割点分别是:
u9 ?- K2 f3 H" B/ X6 v1 B9 ~" p% ]- g8 W1 F. h
array([ 1051.16 , 2295. , 3073.29, 4031.68, 5456.34, 17828.84])
4 _% B8 C8 Z4 ^. ?array([ -inf, 1000., 3500., 5500., 18000., inf])
8 C9 L4 m. E0 ?+ W5 q9 Z8 o1
/ U [3 ~( Y# I9 |/ I1 N8 I2: Z Z3 x' t2 j( e- j
第4问中按照整数分箱得到的序列中,是否出现了所有的类别?如果存在没有出现的类别请把该类别删除。
2 [( K" ~( p/ R( U0 r0 \6 s3 ]8 mdf['price_list'].cat.categories # 原先设定的类别数
1 K- k8 Y" X7 n) R2 rIndex(['Very Low', 'Low', 'Mid', 'High', 'Very High'], dtype='object')
' s1 w; W9 S, g" G
9 T- ^+ Z! h/ h! ~8 Q7 g; Hdf['price_list'].cat.remove_unused_categories().cat.categories # 移除未出现的类别
- Z& @7 m5 h9 q/ ZIndex(['Low', 'Mid', 'High'], dtype='object') # 首尾两个类别未出现; a0 e" B. ^: {' h
1
# o$ k+ \) T) Z; `" l* ?( Q6 o2
- T3 |7 M5 }8 \+ T$ l3: B* h W% f/ O" O2 g5 j$ h
4; s L6 z Q+ Y6 B0 o- R
5/ ]8 y. u6 h3 |9 n8 e
avg.sort_values() # 可见首尾区间确实是没有的6 M( A+ h9 ?" |. }5 `; \, U; F
31962 1051.1627912 u1 N% F! t. Q0 I g
15 1078.125000' F8 s, Q1 X) ^
4 1080.6451610 d) B: i, v! O8 u$ R' W
28285 1109.090909
j# ?7 m/ ]% E4 ]" D8 ?13 1109.677419
6 n7 \" P4 U1 o- ] T. ` ...
3 K N: a+ G$ h1 Q1 e/ \; M+ Y& W26998 16764.705882
; N6 U$ x6 {( `# [9 y. Z27457 16928.9719632 @7 f2 {: z9 k3 Q& a+ X# x- Y
27226 17077.6699032 I" P& Q4 y4 P- M L) a5 M
27530 17083.177570
1 i- R, R/ W$ G/ l. r9 o) M27635 17828.846154
' ?0 U: o: K& Z( L1- S I8 h2 q- K' G8 n
2) d/ M8 Q2 [: E( d
3
0 V, n- P9 B% P2 E41 @4 h2 R" u$ n- z2 r
5
0 O# q9 A& M7 t) K$ }6
& s6 i& `3 J2 \( {7
" \# b8 F% z q8
0 x5 p' n6 V2 A! c9, P$ S1 |! R* h& p: T5 h
10
3 k, Y" c9 a# S4 |5 C11
- E# T% d+ `- Q% z7 O% T) O( U- x12" m: `9 I: z, i' u: R/ Q; X
对第4问中按照分位数分箱得到的序列,求每个样本对应所在区间的左右端点值和长度。& T9 w( A6 B$ O
# 分割时区间不能有命名,否则字符串传入错误。 a8 s0 W" [6 n7 N5 D0 V; d
id_interval=pd.IntervalIndex(
8 Z. u4 \+ B1 \+ | pd.qcut(avg, q=[0,0.2,0.4,0.6,0.8,1],retbins=True)[0]
( V( y# _- N- J0 r' f3 [; { )
1 i& ~; p+ `; _5 _3 _+ U' [5 ~; ~id_interval.left
: Y5 |& ~" e a ]& N, Q; |id_interval.right
/ d4 y# ^# p6 Bid_interval.length
: y; I$ s+ `4 Q5 n( {1
8 ~$ ^" a! I7 y20 N6 f! t5 g: W4 w
3
% ~' N) G- H4 v+ }4 q' N4
/ m+ u) w( I5 ^ p1 G/ `5
9 ~7 ~: {2 X5 a& Q* }/ F6
5 s0 n; [* B, L% K3 ^% W78 h F5 o7 p! \' m
第十章 时序数据# ?6 W) d; V( L2 V2 i. ?
import numpy as np
2 }6 w! Y5 z* }9 Z6 u! jimport pandas as pd2 g" O5 L9 `4 D. T, ^+ z
1+ ?$ @2 Z& Y% j) v
2
+ y1 {5 K1 S* n, }7 {% I! G( ~* V2 a: \/ i+ R
9 _& D. m" x9 V* h. N10.1 时序中的基本对象% k& S: ^( M/ L9 c8 W& w n0 s
时间序列的概念在日常生活中十分常见,但对于一个具体的时序事件而言,可以从多个时间对象的角度来描述。例如2020年9月7日周一早上8点整需要到教室上课,这个课会在当天早上10点结束,其中包含了哪些时间概念?+ Y2 \* W7 w# H
0 Y2 B7 f: T, P3 U6 {' l! I% O
会出现时间戳(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的简写。+ s- g/ x1 V1 y: @% a7 I: U5 C
8 G& f9 _& T9 D3 H- t/ d
会出现时间差(Time deltas)的概念,即上课需要的时间,两个Timestamp做差就得到了时间差,pandas中利用Timedelta来表示。类似的,一系列的时间差就组成了TimedeltaIndex, 而将它放到Series中后,Series的类型就变为了timedelta64[ns]。, c4 y& W0 M, }6 _: {) y0 A4 V/ \
% D* ~ x5 S' u# s/ e y0 G$ q* M
会出现时间段(Time spans)的概念,即在8点到10点这个区间都会持续地在上课,在pandas利用Period来表示。类似的,一系列的时间段就组成了PeriodIndex, 而将它放到Series中后,Series的类型就变为了Period。! ]* ~" C# ~/ w/ o. U9 v* i
. @% o" Y; r/ W: o i8 k& Q
会出现日期偏置(Date offsets)的概念,假设你只知道9月的第一个周一早上8点要去上课,但不知道具体的日期,那么就需要一个类型来处理此类需求。再例如,想要知道2020年9月7日后的第30个工作日是哪一天,那么时间差就解决不了你的问题,从而pandas中的DateOffset就出现了。同时,pandas中没有为一列时间偏置专门设计存储类型,理由也很简单,因为需求比较奇怪,一般来说我们只需要对一批时间特征做一个统一的特殊日期偏置。
( {2 P4 [6 ]) d' `: r# I; g! ` ~( d( H9 D1 _' Y/ g! E0 Y
通过这个简单的例子,就能够容易地总结出官方文档中的这个表格:( s9 X5 Y& G: T% u
, i/ S, O( S1 M1 c概念 单元素类型 数组类型 pandas数据类型3 b& h9 M0 f* C' o
Date times Timestamp DatetimeIndex datetime64[ns]% k F, R; Q, G/ W6 P# @; _
Time deltas Timedelta TimedeltaIndex timedelta64[ns]; r" ?5 @ J' |7 Q5 d
Time spans Period PeriodIndex period[freq]5 c1 u) |( p( `: u8 z! F$ C
Date offsets DateOffset None None" u) p+ }$ }9 u2 f$ }6 |4 ~
由于时间段对象Period/PeriodIndex的使用频率并不高,因此将不进行讲解,而只涉及时间戳序列、时间差序列和日期偏置的相关内容。# P( Q1 u, x4 ^7 ]8 v
0 [ w* F" e' c- J: Z! q10.2 时间戳: N2 i m- E* M) t! @
10.2.1 Timestamp的构造与属性 _8 W3 s! v8 M) N% a
单个时间戳的生成利用pd.Timestamp实现,一般而言的常见日期格式都能被成功地转换:6 x7 ~% {. ~* F
8 K6 L. _8 l. s3 l/ tts = pd.Timestamp('2020/1/1')
+ F7 I9 i1 E0 Q& b8 a+ q0 Z* }, F K0 j3 W) y% ]/ t
ts
0 P$ b; Q0 C* Q* {Out[4]: Timestamp('2020-01-01 00:00:00')2 q! a5 i1 r" b
/ }9 p. a, L# U! r' j; F3 v
ts = pd.Timestamp('2020-1-1 08:10:30')# D0 v; o t. v
; z8 A( }- y: c# [4 ~3 ]2 {ts
1 j9 X* Q( Q' ]6 N: dOut[6]: Timestamp('2020-01-01 08:10:30')
4 _' u2 A: L6 e) X6 t1 ]$ a1$ f( v: ]( R; Z8 ^
2
. G2 f% m* p. C" j( G& Z3
, B c2 O& J. d4 ^" Q2 {1 n- n* l$ j4 _+ ?. d) m$ f B* n/ t& z
5
8 \ S5 I" S, e( s6
Y; i) N: i& k! S7
! W6 P T% U" I/ F9 U8/ M, R# ]' x6 [7 ^
94 w" T- o' ~1 U
通过year, month, day, hour, min, second可以获取具体的数值:
% S! H4 F5 B- S# s* R0 U3 }4 k( X
ts.year
% @( W8 n; T0 Z9 a, B; eOut[7]: 2020" y; a& u' ?) \: I$ P1 L P# ]
: v7 Q( N6 P6 E* h/ E
ts.month* I; g1 C: W* m/ L! \
Out[8]: 1' U, p. u4 j" i. C: O9 @( L% V8 Q
3 ^7 [, j* ^ |# }7 e: W, Y
ts.day$ n( n/ D( Q, G2 v
Out[9]: 18 s% S N8 F( n! y/ Z
1 Y6 k7 i- x) Z6 ]% zts.hour
1 o* V& j0 W. c$ LOut[10]: 8$ R+ O$ l; P% `8 }: K
, [( T; q0 h9 v a" C( X6 n( h
ts.minute' Z' X; `6 c+ q4 L5 U3 B/ ^" J) T# B
Out[11]: 102 U6 `: w' s! b% K+ F
9 ~# ^/ e+ C6 qts.second
" p7 z2 ?- F% u: zOut[12]: 30
: r0 J& x# [- h" `) {* A, O6 H+ [
d1 j- J! o0 v1+ ? n$ R+ d: i* j+ d! D
2
; ]; n- k7 R0 ]) F+ K3
4 P5 R7 j' Y0 {4* J: @% L$ W+ K+ m5 n/ ]& e
5
, X' _! W; }" |& {68 v6 g* ^6 _2 i8 r% M
7
. o- c# J6 j# B- p: i. \85 t6 m, T& p2 n; b
9
( g9 _# @( b& O' s: s10
6 B+ h- u6 ]" Z) h8 I; _( M; F3 L11$ f' F2 R9 r9 A! e4 R
12
# ]5 \' w/ T3 T5 k! S8 l1 d$ I. U13
( @# w R9 r' _2 c' a- t t14* ]6 t6 ?' u% f
15
6 }/ v, }% A! o j3 R; W% S' M7 O16
. p2 P- `4 h( L1 f7 Y: G6 V9 n2 g+ @17
( B+ j. \+ g# x2 O& C- ?" j2 t# 获取当前时间
! g* r2 Z/ N* A. q2 Bnow=pd.Timestamp.now()
# `* ?6 z' R1 N$ F17 ~0 |$ ~% N) g6 L
21 g1 f9 p* o. o0 H3 n3 }' f4 F
在pandas中,时间戳的最小精度为纳秒ns,由于使用了64位存储,可以表示的时间范围大约可以如下计算:
: Q' f- \$ P& p0 q WT 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)1 _( k' l/ _3 @, L
TimeRange= 6 p% P, j1 C! b5 v
10 - u t- X. b% R2 k- t& [
94 \; e" d( z7 L7 z
×60×60×24×365
- F' `% G* j" }2 g3 a1 O( {* s2 `! j5 e2
$ d+ K, p! U0 q& M' ?3 H64 J( v* b8 |; m. F* D. z# i1 N
. D# n+ K' Y& [; w! C6 B' m8 d+ B! e5 w8 ?
≈585(Years)
- l1 y4 n0 {- x) _% o
2 \6 { h& p, k3 X5 b通过pd.Timestamp.max和pd.Timestamp.min可以获取时间戳表示的范围,可以看到确实表示的区间年数大小正如上述计算结果: ?9 `5 O$ \% b6 C; E! y0 \9 j' U! p
M$ H% L. ]1 b" j' w
pd.Timestamp.max
8 ~: m" L! H5 P9 z1 ~/ w- l7 ROut[13]: Timestamp('2262-04-11 23:47:16.854775807')# K) L. \% J* w9 z% ]
" Y$ b' [" k& d2 y9 ^
pd.Timestamp.min. c' {: \" b# z6 @' u
Out[14]: Timestamp('1677-09-21 00:12:43.145225')
0 X2 G3 Q4 x, G( _: ?: [- U; N( g5 m6 e2 S+ @8 c# i
pd.Timestamp.max.year - pd.Timestamp.min.year
% p$ }* o2 U' K5 W7 ~3 M9 y, oOut[15]: 5856 p7 m8 v9 f" M+ o3 a
1
' O$ w5 e/ P" Y, i% I3 Z2
* I4 T* i7 R/ i& K+ Q- e3
* ^% [7 z# z8 o. C4 t$ i4
5 ~8 K! F/ c1 [- n1 V G5
9 S: Q; q* v" [7 W# A( ~# R6/ F) x/ L2 |$ f) a3 x
76 `2 l' u8 s- f% }
87 w5 U5 c, A) P/ i, z' ]
10.2.2 Datetime序列的生成5 k6 a- U( i1 S" i
pandas.to_datetime(arg, errors='raise', dayfirst=False, yearfirst=False, utc=None, format=None,
" o% K( \* p0 ]( J# r exact=True, unit=None, infer_datetime_format=False, origin='unix', cache=True)
% t- a7 |! l, o' H17 B/ f/ S# @( t2 Z
2
3 I, O& Q- e4 b, dpandas.to_datetime将arg转换为日期时间。
1 l% M5 n7 W1 k* |
/ f8 s1 a9 f3 j1 H7 A) Y! rarg:可以是argint、float、str、datetime、list、tuple、一维数组、Series、DataFrame/dict-like等要转换为日期时间的对象。如果提供了 DataFrame,则该方法至少需要以下列:“年”、“月”、“日”。 I6 g9 ~4 g- ~7 L! ^
errors:
3 Y8 i1 b8 f# Y( c- ‘raise’:默认值,无效解析将引发异常
+ X) F, z i1 r7 x- ?- ‘raise’:无效解析将返回输入
3 Z; r5 u5 ~% A9 v6 A- U- ‘coerce’:无效解析将被设置为NaT
! ]; j6 `5 }4 p S6 kdayfirst:如果 arg 是 str 或类似列表时使用,表示日期解析顺序,默认为 False。如果为 True,则首先解析日期,例如“10/11/12”被解析为 2012-11-10。如果无法根据给定的 dayfirst 选项解析分隔日期字符串,会显示警告。2 f) ^6 U- o7 E! z9 s% X1 f
yearfirst:如果 arg 是 str 或类似列表时使用,表示日期解析顺序,默认为 False。如果为 True,则首先解析年份,例如“10/11/12”被解析为2010-11-12。无法正确解析时会显示警告。(如果 dayfirst 和 yearfirst 都为 True,则 yearfirst 优先(与 dateutil 相同)。)
3 L9 O- L% d( a& sutcbool:默认None,控制时区相关的解析、本地化和转换。请参阅:pandas 有关时区转换和本地化的一般文档
: v- Q- s# A+ J; L, S; J& n# eformat:str格式,默认None。时间戳的格式不满足转换时,可以强制使用format进行匹配。+ ^0 g5 p! z% e: [
unitstr:默认“ns”。它是arg (D,s,ms,us,ns) 的表示单位,可以是整数或浮点数。这将基于原点。例如,使用 unit=‘ms’ 和 origin=‘unix’ (默认值),这将计算到 unix 开始的毫秒数。
: n2 F' p1 P# b9 Pto_datetime能够把一列时间戳格式的对象转换成为datetime64[ns]类型的时间序列:
: A% y3 W( e, s% \pd.to_datetime(['2020-1-1', '2020-1-3', '2020-1-6'])# e3 O$ ?& d& [ E0 d: b1 l0 g
+ D: A# m, q8 \7 ~! J" pDatetimeIndex(['2020-01-01', '2020-01-03', '2020-01-06'], dtype='datetime64[ns]', freq=None)
o. g. o: }' w9 Q1
8 U0 R+ v7 b; j24 j' `, c+ `2 l8 l
37 T' V9 P, M: X2 H% k( [
在极少数情况,时间戳的格式不满足转换时,可以强制使用format进行匹配:
! b) o) T: B& B8 l5 W, b9 a8 C% L2 E0 _& L, U5 i; |
temp = pd.to_datetime(['2020\\1\\1','2020\\1\\3'],format='%Y\\%m\\%d')* [: O; n; J+ N- c8 Y
temp
) o ]" a- V t5 G( Y: {4 J6 x) o9 P1 F B! j: ~
DatetimeIndex(['2020-01-01', '2020-01-03'], dtype='datetime64[ns]', freq=None)
8 @8 F/ b- _: v$ W6 {# E1
' z, P% M p0 S; d3 m2
5 H2 N# H4 I1 q36 U& {* M( C& X& x, [
4
0 U/ y8 v1 y/ Z# m8 r 注意上面由于传入的是列表,而非pandas内部的Series,因此返回的是DatetimeIndex,如果想要转为datetime64[ns]的序列,需要显式用Series转化:+ j* i. x; L; [- V7 l
7 Q1 Y5 @0 ?2 I; |0 K$ m. J8 r* Jpd.Series(temp).head()7 x7 u) e% \% {2 i# W" `) H( ]9 I
5 i8 p6 I; h/ a0 2020-01-01
/ J& Z" @5 y. N2 h1 2020-01-03
# T8 n7 ?, f5 P* ?2 Wdtype: datetime64[ns]* G/ N7 t. z- M* t- M
14 d; _$ V' a" ^4 ~- k
21 }, c- m( q; q W1 I
34 f. u# Z, I R" q( {; I
4
+ [! e: T) z+ Q6 k; s" M6 Y7 m53 p( ]: s1 v$ ?9 o8 M( x
下面的序列本身就是Series,所以不需要再转化。" T$ L8 F- s7 ]. i
5 `# Q& T8 Q. Y, \
df = pd.read_csv('../data/learn_pandas.csv')$ } [% I% K9 `: D0 X" |" B6 h/ q
s = pd.to_datetime(df.Test_Date)0 W) G( r& Y. y1 Z! {
s.head()/ A3 i3 V6 G( [( s- g6 H( G+ K
( L- \2 r, Z: k9 L8 @9 y2 ~7 S+ r
0 2019-10-05
/ \1 Y$ ]; a1 B1 2019-09-04' b5 Q, T& X/ e, H* r
2 2019-09-12. r+ }3 Z. d/ a! A
3 2020-01-03
2 d0 [3 o! ~( F0 p9 ]4 2019-11-06% h! s- w: @- A$ h4 [2 F
Name: Test_Date, dtype: datetime64[ns]/ m/ C* O8 H7 ^; L4 ?
1- k4 c$ T" J( U* }6 l
2" x$ c+ u4 Z. D) ^; X
3# y- A6 A; }% Z8 ?% f
4
6 Y2 T) j* p* |0 Y2 U$ L5
* L Z; I- m; C3 o6$ v; j# L9 H4 u4 |- t
7
; [$ A# {' z$ R- f8* |' f) j: T6 `# b1 U) r; @
9
( {* s1 | }+ ^& o7 W% K9 J; X10
; n* f# ]. I9 Z, F把表的多列时间属性拼接转为时间序列的to_datetime,此时的列名必须和以下给定的时间关键词列名一致:0 n0 E. g3 X* V9 Q x9 |
df_date_cols = pd.DataFrame({'year': [2020, 2020],. H% J4 ^: ~4 V5 V! q! t
'month': [1, 1],, q# T* l6 G6 P6 W! [* z. I0 F7 v
'day': [1, 2],' u/ S# j6 F2 p- U# E
'hour': [10, 20],
0 F' _$ h( x- ^# j. A 'minute': [30, 50],
) d' d/ Y1 \8 R+ F 'second': [20, 40]})0 J9 v4 I0 W3 T% O' U
pd.to_datetime(df_date_cols)
9 A/ c( _- x7 z$ B, x2 ^/ D+ T# Z; I& |7 c6 k' n
0 2020-01-01 10:30:20
0 [, Y1 i* B( j2 ]% z1 2020-01-02 20:50:40 \6 j7 E$ J! n" j. _2 u" `3 _5 S, `
dtype: datetime64[ns]6 A) y! ], X6 v3 T3 t
1 O% D. s) D- N
2
' t5 @5 s9 ?( Q4 x& J3( _# s' ]3 ^& v- |0 G2 @
4
0 h. U; v2 f: e, ^ O m, K53 J( \6 p9 a) @ \( R( o6 `1 S& U( C
6
# X: Y1 ?" k, ]$ F& _8 V$ |7: ?0 s1 @% X3 ^4 {& _3 R% {
8
' A; d- q$ ^7 U- @9
7 s( L! `! `" {8 ]1 t3 |( o10! V9 X% X! y* @+ {, A6 l$ c: h" y0 c
11
& E/ {* |$ V) w. M* pdate_range是一种生成连续间隔时间的一种方法,其重要的参数为start, end, freq, periods,它们分别表示开始时间,结束时间,时间间隔,时间戳个数。其中,四个中的三个参数决定了,那么剩下的一个就随之确定了。这里要注意,开始或结束日期如果作为端点则它会被包含:% [5 T9 e) h2 x2 E; |6 Y- R
pd.date_range('2020-1-1','2020-1-21', freq='10D') # 包含
U; q' T4 L- p" C) h: AOut[25]: DatetimeIndex(['2020-01-01', '2020-01-11', '2020-01-21'], dtype='datetime64[ns]', freq='10D')5 t. [: b3 H# h$ r; A
5 ?% v% n: Y9 F( h( K g0 r5 C* v
pd.date_range('2020-1-1','2020-2-28', freq='10D')# s7 K" j; x6 I' O
Out[26]: / F1 [) b. P! g# I( `+ n
DatetimeIndex(['2020-01-01', '2020-01-11', '2020-01-21', '2020-01-31',
# z+ y" T3 L2 |' @ '2020-02-10', '2020-02-20'],$ Y- J0 G% ^1 A
dtype='datetime64[ns]', freq='10D')
2 u: S3 `* Y! A3 k( l7 P$ Q6 [2 U- d) n$ f I
pd.date_range('2020-1-1',$ \8 ?6 T( \. h0 T- V
'2020-2-28', periods=6) # 由于结束日期无法取到,freq不为10天
. w& h! s) _5 }5 Z3 u2 b7 M
% t2 {4 e% s9 X/ i( }' i( C' qOut[27]: & p5 w% V' }2 z# H V5 w
DatetimeIndex(['2020-01-01 00:00:00', '2020-01-12 14:24:00',
9 h- B. X3 m8 m# | '2020-01-24 04:48:00', '2020-02-04 19:12:00',, \/ k, g: o2 r3 \8 k
'2020-02-16 09:36:00', '2020-02-28 00:00:00'],
, n. E% O! Y7 `2 O dtype='datetime64[ns]', freq=None)& w2 O2 c a0 Y8 r
2 D6 B$ Y- I$ P( ?# c' R# Y
1$ M4 k1 {4 e' F) o" k1 ]( _+ a
2
& X# V7 D5 z& ?8 Q3
, q0 [3 \: D. V, K# k4; B" y. I( U! X% R
5- a& g: y, C) ]( o, h, C8 j
6# C7 \% Z |# z
7( e; h" g3 b- D ^8 \8 k
8
4 w+ Z$ t$ Q4 @* p% w9
1 u. f" v9 T1 H$ s3 M9 }$ ?10
; [" H2 S* ? e2 n& t5 X0 ]11$ w& d2 x) h- t! R0 _% S, s0 y
122 Z2 |2 ?0 O( h) N" j" d6 J
13% \4 w+ A+ @' T; E+ P
145 j: Y8 H7 l$ ]( M; l# A& P# l
15
3 h3 F1 W. p( r( k16; F* U% s6 }: Q/ B6 s2 D( P
17( `! B. \0 P: K" ]
这里的freq参数与DateOffset对象紧密相关,将在第四节介绍其具体的用法。
7 o( m1 @5 B! r9 Q. d) _
* l: F& f* ~, b3 \【练一练】+ G+ W! k. h; X% N& e1 [5 j
Timestamp上定义了一个value属性,其返回的整数值代表了从1970年1月1日零点到给定时间戳相差的纳秒数,请利用这个属性构造一个随机生成给定日期区间内日期序列的函数。
2 {& u9 W0 N" K3 x: V, z4 P, s% _6 O2 e: {- u) ^
ls=['2020-01-01','2020-02-20']$ Y# { k y/ ?! j# G" o
def dates(ls,n):
: m6 I5 G+ z+ c+ C min=pd.Timestamp(ls[0]).value/10**9
' v ~0 i9 o% [! b max=pd.Timestamp(ls[1]).value/10**9
/ {2 V% m# W3 H2 T0 p times=np.random.randint(min,max+1,n)$ V1 U: b& K" \4 o4 q
return pd.to_datetime(times,unit='s')
T: ~7 N% l2 _; P. Udates(ls,10)
6 `! r' v, }4 o& H, G5 u5 m4 q1 H* w# t! Z, ^% G0 p$ l
DatetimeIndex(['2020-02-16 09:25:30', '2020-01-29 07:00:04',. F0 v9 y& p$ O( `5 _
'2020-01-21 12:26:02', '2020-02-08 20:34:08',
4 e+ i# g V! v9 e: e '2020-02-15 00:18:33', '2020-02-11 02:18:07',
" c' H, B X6 T9 W: r2 Z, Y* v '2020-01-12 21:48:59', '2020-01-12 00:39:24',
i. R. I1 \5 g '2020-02-14 20:55:20', '2020-01-26 15:44:13'],
/ |; [/ z% f( Y+ ^0 [. | dtype='datetime64[ns]', freq=None), @ |9 I# S+ }" V" R7 p- t
1& w+ V m) N- y+ I/ L1 q6 V
2
0 J( B* T4 w" I$ \+ t, M3
2 g, J8 u6 K9 n* y4
q9 d, N0 D( o% @! M0 [0 g/ b5# K6 ?- r! a" ?+ N/ K
6% q; a. M# A# R# @
7
$ ]; c+ f& t4 ]8 s: w8
, x* ]( [+ [& s$ N& T95 R0 N9 d; C) Z& v. U+ W
104 s* z1 P9 K# }3 d& T6 j# I- P" \
11+ S7 g8 k: d; o' d0 G6 g4 a' F
124 J. ~) q7 V* w$ l7 \& I
13( v- A! H' {8 s
14! [1 C! h& c% G2 E* J
asfreq:改变序列采样频率的方法,能够根据给定的freq对序列进行类似于reindex的操作:
9 t) |) i) d3 _s = pd.Series(np.random.rand(5),( l3 e$ l. c I/ J0 p
index=pd.to_datetime([ z' Q! D0 Q- v1 ^
'2020-1-%d'%i for i in range(1,10,2)]))
5 p9 G Z% X. k
2 A8 B' m9 P% b7 o. z2 d9 q7 H$ e. i0 D7 z3 U$ ~; }) V
s.head()
- @1 o2 L/ v$ H2 I; b: {Out[29]:
2 d+ e, G7 c7 z2020-01-01 0.836578
" z9 p6 Z7 k( s4 O3 e! s& q2020-01-03 0.678419
6 ]$ ^! [" k' z9 E8 Q3 J R2020-01-05 0.7118977 J7 _0 ~; u+ P( { y0 |
2020-01-07 0.487429# l7 ~2 A) T6 V$ d2 v
2020-01-09 0.604705( `. t. @1 B5 @, Q- A& v
dtype: float64
, r' O5 @! t6 ~" ~* L' L
% W: A( F' j) bs.asfreq('D').head()
2 @- O- w/ C5 P' \& XOut[30]: & i+ [+ u- H2 U7 m& D6 [" {2 Z1 L |
2020-01-01 0.836578
4 ?2 X( _$ ^, \# ] j+ a2020-01-02 NaN
( a' R% g- d& U/ S2020-01-03 0.6784192 A: L, i h1 h9 [
2020-01-04 NaN" v. S" f4 w' p' Y J! W6 f
2020-01-05 0.711897: M( F- `1 u1 C) ]9 i
Freq: D, dtype: float64/ @1 I) r; a2 r& x7 t
/ [5 A+ S( f6 O& t& is.asfreq('12H').head()
( g! \$ j6 p. QOut[31]: * u. L9 L( O. f# y" j, C/ F4 a% w
2020-01-01 00:00:00 0.8365787 M; E+ e8 u+ u! x! a
2020-01-01 12:00:00 NaN
3 u; R! G6 t* { M% V) y2020-01-02 00:00:00 NaN
9 [# M3 {. o' D9 I. g" a9 B2020-01-02 12:00:00 NaN
1 [5 A, M9 x4 A. u' e2020-01-03 00:00:00 0.678419$ Q: z4 W& k$ T' _- f; O
Freq: 12H, dtype: float64* `% P& q R A7 G& j
: k- D* k9 A5 Q9 b# r2 ]4 c; `2 A; X" A1
e1 w2 y& z8 x$ ]0 o2
4 L; |) c' m8 |7 r; C34 v) m( J7 Z: T$ b
4
/ V5 N6 k t5 s" h( A3 e5
5 ~4 U' ]! _9 H. p7 s6
+ l' s5 I& r/ v+ m* X77 R, o, y( s1 N: s1 G0 z. w" r" `8 C
8
& H- f0 D3 q6 m9 O9& s: t. c1 V) @/ m( Z9 e
10# L4 _# {0 W1 ~4 e1 c0 G
11
6 h+ |% C& i2 c! f+ r12' o" f! E2 y6 o6 H+ Q$ o' o* m/ |4 O
130 r5 G, D/ G! m
14
3 }5 Q& R5 ^ e; l15- B% i9 E% ]! ?
16
y0 J) x8 T7 i179 Q8 d, b& J+ B& Y+ \
18
X; k5 ?0 T, A* g! L193 a, X: p- f5 ~
20+ A) {7 }& ^( ` i2 o$ x# R, c% k
21
: A# v0 v$ S3 j$ b/ A3 R22) a6 U9 ^: p+ a' a. v- C* K& p' G* ]
23( m. _, m; Y" c" k" F& B. n
24+ z3 _' O$ K! a0 M
25
8 [$ b; R) f" s( {$ ~! K, j26
1 |. L$ b2 p4 N& l$ Z" V27
Z$ D- B9 B, I! s$ x28
, H7 x) S- `: `4 @, L+ s29
4 u3 k+ \$ d; {2 d30
3 b/ O1 o) W( t8 a3 W6 N' E& ]31
& }$ G& |7 F( s8 O' v4 e% H【NOTE】datetime64[ns] 序列的极值与均值6 e/ K) L7 N9 |" [* J& o
前面提到了datetime64[ns]本质上可以理解为一个整数,即从1970年1月1日零点到给定时间戳相差的纳秒数。所以对于一个datetime64[ns]序列,可以使用max, min, mean,来取得最大时间戳、最小时间戳和“平均”时间戳。
* P9 B- t5 X& V h4 n. \$ r' l
10.2.3 dt对象/ I; Y+ Z$ X3 ^1 T
如同category, string的序列上定义了cat, str来完成分类数据和文本数据的操作,在时序类型的序列上定义了dt对象来完成许多时间序列的相关操作。这里对于datetime64[ns]类型而言,可以大致分为三类操作:取出时间相关的属性、判断时间戳是否满足条件、取整操作。3 `; w& ^" S1 T/ ~# J1 G8 A4 L0 B" Y- k
5 f" B4 g/ n1 z
第一类操作的常用属性包括:date, time, year, month, day, hour, minute, second, microsecond, nanosecond, dayofweek, dayofyear, weekofyear, daysinmonth, quarter,其中daysinmonth, quarter分别表示该月一共有几天和季度。, l6 F+ Y+ A ~- ?
s = pd.Series(pd.date_range('2020-1-1','2020-1-3', freq='D'))" z# f5 A' {/ o# J
2 p: W# t& u, C* w/ k6 o+ R( j
s.dt.date
1 O) y9 J3 L" ^2 M. i, ROut[33]: ! O* U1 ^0 o8 j
0 2020-01-015 A9 I" G" u N# L9 p8 ?$ u- T8 ]
1 2020-01-022 n I4 j2 E- @$ r9 l
2 2020-01-038 \1 ]$ z' P; w+ |; x6 r
dtype: object
8 b: K, ~- m7 _" q
) o, a7 z, b' A5 q# G. z8 j1 P$ Os.dt.time
+ E r# u7 ?9 c3 D& ^/ _Out[34]:
, C0 u8 q' M) ]& m) j; A0 00:00:00
/ z: w) L! ]( @$ Z& t1 00:00:00
+ I; e& ^2 ~; V1 n3 g2 00:00:00+ @6 k% u6 l8 Y7 P7 I
dtype: object
# Z0 R+ t8 U8 g+ N$ H9 i
- j" d: N! T0 x. I3 o# o! zs.dt.day
0 X4 ~% q1 o& WOut[35]: 6 ?2 g; H& z4 P& a
0 1
) h1 r" k A9 B1 2
0 E7 r9 P$ G( O( l2 ^/ D2 3; H) M* i' x, b ~: ?4 V
dtype: int64
2 _+ W0 u% J$ P; d' j7 e" [4 k# L3 ]% N0 {: i: Q$ ^2 J6 v
s.dt.daysinmonth3 o/ t: ]: I+ W# |9 {5 y: J
Out[36]: + ^ j$ i" y$ X8 O- E
0 315 u3 I- ]- X8 t3 p# e
1 31
8 t3 N; y& o; a t) z$ d+ |: N2 31% w3 Y% B K9 U) _4 E5 J
dtype: int64( O; Q) N% R. ^( j
3 M) c8 ~$ M7 j$ L# f5 N8 R& W- g1
2 {/ e. {/ c* r7 q7 U! ^; z2
* O5 S E' G0 i _5 q6 M0 `/ j8 t9 b3
# i. L3 |; b8 ~3 L/ ^4
- w, [/ i& _4 [2 u' {* F" w5
9 ^3 m3 V" x2 g1 @' ?. ?$ x6
& ~7 i3 a$ N% N7 u a7% e3 S% H# c; d4 V
8
; @9 Y3 r' q5 b! M6 c/ j/ n9; Q% W$ `$ ^7 a6 \, E3 \
10( Z- _, C# ~' K. c" b9 E6 O6 N6 E
11
y$ y, U7 U8 h% o; q+ ^4 L$ L12( X; W5 g" e2 i0 u" @
13+ O! Z }% U4 M* Z* v+ [+ |8 I
14
* G# |7 W8 U, {( O8 l157 e+ G2 m* l1 F" P) r
16
- E8 F6 k" _0 k5 m17
V6 F$ _0 A+ q9 [( K5 Q18: v2 N! e" X. ?
19- q! ]* o6 S4 D! W$ d
20' ?, w8 ?8 V! O7 T; y. l+ N
21$ x( {, W9 p% r. p$ N5 ~
22) v5 [8 o9 @: O$ @# @* `0 h
23
) R+ V% j$ G% J, o( @( b* V6 J24* K& g! ~+ ` a, ^: N% N6 @6 `
25$ P5 c$ b; O% ]
26
. x' q* i8 C( C! b1 t27 f' |6 [' e! D% N. n' o
28$ X8 Y3 F) o& ~4 p. |2 Q
29: U0 N) a2 S! T% S5 Y. @
在这些属性中,经常使用的是dayofweek,它返回了周中的星期情况,周一为0、周二为1,以此类推。此外,还可以通过month_name, day_name返回英文的月名和星期名,注意它们是方法而不是属性:3 L1 O% l( ^8 l) I0 W4 g; P7 q9 @
# D1 J6 H& i9 w7 N. x1 _) vs.dt.dayofweek
( U" b8 ?7 E2 I4 S( r! _/ uOut[37]: 8 | l9 E# [- K7 Q2 i; b
0 2
4 y7 r9 c( p1 r1 3
& @. V, c* \1 n( c3 R2 4/ @+ Y! S7 M1 v- F6 h
dtype: int64
( ~2 q C3 T7 Q) n9 N: |, t4 v4 d3 H& {; N
s.dt.month_name()2 l# g& Q8 |9 e" m0 c
Out[38]:
3 F! G1 L- u- x0 January
" d3 g' N1 |; q, I1 January2 R, B5 i- s# Z0 q E
2 January( |! Q3 V0 b8 G* e
dtype: object
+ [0 J9 v0 r5 A* c9 H: [+ b
( J! c% y& H$ Q' A9 ns.dt.day_name()
+ F$ V) I( d1 f2 B" s: d* b3 @ \Out[39]: 4 m/ ^0 E2 [0 Z
0 Wednesday
! o0 C9 ?& k, A3 m: f: \, S1 Thursday1 ^3 V$ v( E0 z Q
2 Friday
( e6 P& g- I' i+ Q* G# S# c9 H& L6 ~% f/ udtype: object
) ?+ r: \1 l1 k: w2 _" S* n0 G! i: V- e* p) _6 P. M1 C8 n- F
1
9 ~6 l& A& j( M1 {7 {) a2( `6 p' [6 ~; Z
3. }( \& o% c9 I$ ?: j0 q( h
4; k6 U W! }0 j" S6 T5 T; N
50 i+ S9 W$ i; w% @+ `
6
% X5 q% w3 X1 r s2 N9 d7
* t( m3 E" A1 ?& j8. ~! U. g: g' C
9 u$ _5 I4 a( z9 a/ E
10
# A3 `% y" _' ^. P) v7 U" Y+ B11
3 `. c* n) F3 w U( F' _125 E4 X; J( _3 ]6 P& Q- O
13
% F# M" F' {8 z" q7 o0 }0 { u14
5 |( W; C# \) s9 X9 p8 O158 }$ B0 j3 f2 b! k4 t" d
16
- o1 h5 H: `' i% Z0 J! l" s178 \) d) b1 }8 f% u Q' j
18" y$ q; ^0 y1 x) k% F8 ^! K" b$ h
19) b2 {$ D2 m+ T+ a9 O' i( C$ B
20
* W. k% |" P& a. Y. \第二类判断操作主要用于测试是否为月/季/年的第一天或者最后一天:
5 k( p% l* Y5 X, i7 R7 Q1 os.dt.is_year_start # 还可选 is_quarter/month_start
* N( Q" ]& d0 ]' V- ~( d7 S! [Out[40]:
& a) L" L& a* {9 J! j, R. R* i0 True
) D% V/ f$ ~( e3 u6 A# H$ |1 False; }' F1 v6 c8 i4 _
2 False8 W( X3 d+ V! y1 w6 E& i
dtype: bool
" V! f, r6 \( i8 C9 }4 d: M5 \* z
6 C" q" a; O! V" Hs.dt.is_year_end # 还可选 is_quarter/month_end
# D: T& Q6 @/ D( y9 J9 P& zOut[41]: - ~" C+ G0 w( D- W* `' j
0 False
k5 p( ~ J7 L* A+ i) A, t1 False
9 z7 o+ c( M& n2 y9 M2 False
$ N2 _7 j. V9 T9 W2 qdtype: bool
, T* [6 U- u: A2 D$ B. _1
2 U4 i: ], J s3 G2 N* ?! H2
. E& f8 A- a4 [+ F) G3" {3 n P7 H$ f- W- p4 {
4 U& G) I9 U2 C# X# u; E
5
& d/ p+ o" A+ E0 I) M6
+ |( `7 W) E% i: h& ?7
3 L# B- U( O0 e* u5 N1 H' M8
8 k8 k7 ?' X' x0 l0 _9 |. J9
8 m/ _$ N0 [( k( L9 z7 Y10
& N% i0 _+ D5 G" T11
" x8 L& y% F" j12
, C' A X1 X& o0 X X6 }! Z13 H4 y6 Y& J/ W# w) N
第三类的取整操作包含round, ceil, floor,它们的公共参数为freq,常用的包括H, min, S(小时、分钟、秒),所有可选的freq可参考此处。
, Q0 o* V. r9 V& z% D: ~s = pd.Series(pd.date_range('2020-1-1 20:35:00',
0 N4 W. C. `& B: }4 K) W ^- U '2020-1-1 22:35:00',5 H8 [# u) V5 c0 B( B3 g) U: n* {
freq='45min'))
# F# @# Z: z1 b; F( c: }5 b W3 q; \" C4 l
' A7 |. H5 \+ @
s/ b1 T) r! v N' ~8 _1 N2 d& L6 C# _
Out[43]: - V9 h, t. e0 z+ t2 y- R
0 2020-01-01 20:35:00; i2 ^2 K. k# E7 i. u m
1 2020-01-01 21:20:00; n* o% G0 {0 v" v: J
2 2020-01-01 22:05:00+ j( J$ N$ v# S$ A6 ]
dtype: datetime64[ns]
' Y L" k7 z- I& C& U! [0 T' ^
s.dt.round('1H')
& w" I- {& z3 wOut[44]:
0 o; T! b' a5 B2 Z7 W0 2020-01-01 21:00:00/ C3 U) X' Z5 s
1 2020-01-01 21:00:00' _+ U r: q; D# E
2 2020-01-01 22:00:00
2 `# N( y/ L' P& D& u! I; ldtype: datetime64[ns]
5 f5 i# h1 d. ~% a s2 Q' {
2 k% u! D: i5 d q3 c* {s.dt.ceil('1H')
6 F/ s3 g# G9 Y/ Z# U; c8 ^Out[45]:
- \3 r9 o4 ]* b4 B& A# N0 2020-01-01 21:00:00' V: o" b4 Q: C: ]' X4 R1 [) `/ Y
1 2020-01-01 22:00:004 U; v6 O$ {* h# H4 }
2 2020-01-01 23:00:00; J: w) M! \2 x ^8 B+ F
dtype: datetime64[ns]
" O0 G/ b: U! T! c W6 u$ {
+ d( A6 R Q" w' y( P _s.dt.floor('1H')
) x) r! S: E/ GOut[46]:
. J8 t4 C; Q* z0 i* T! n* x: u, }0 P7 u0 L0 2020-01-01 20:00:00" _/ o {/ h* `0 B4 U
1 2020-01-01 21:00:00
# A. ?' z0 s+ O! Z$ e) Y& [2 2020-01-01 22:00:00 h$ Z6 \2 ]& h& a& {( m
dtype: datetime64[ns]/ w. n5 _5 M- v5 V
/ P: V( U% D! n+ z) s. f p# n
1
/ f( L$ j5 E+ O6 Z+ C# I* J2
: R: K8 l- H, Z8 `2 O+ N$ K3
/ [ D7 K" B, l6 }4$ p$ |3 T: ?8 } ^! ?
5
% w7 f* O% t( F7 t4 v6
8 j, h! B. f5 ^. h+ u- l# F4 N7( D" `( x2 e0 h1 I
8
! Q/ u8 n+ Z! R' k9
/ L) u4 w8 A, y5 A% z/ `108 l+ W9 O [* Y% y" V' F' a! U z# c; f
11
, w# N" s# c) \: C12
- n6 D* L6 M1 m J5 W) g134 c' R J4 E2 i" b2 m2 G8 M
14' e- t6 x+ e7 C; r, N7 j7 ^& K
15" W! I+ X# @) T
16
, s% \: |# x# o% ~/ R1 H17$ U# N }# x3 j
182 ]7 V4 Z; K/ p
19( A+ B) Q& B0 D4 t
20
: F5 ^7 Y$ ~4 p/ T21
* F( p0 ~* f, Y5 l* D+ C( q2 k22
( d2 S5 D" g/ N23
* g/ o8 D/ n3 a+ T1 b5 |24
+ a3 X% Z( u8 [" I3 N25
: b" P* s7 [; Z# V26
; R' f0 p+ B: j* t# Q9 L9 Y27( m B' d* W% h6 U3 G$ y9 w3 r/ f
28( H4 k% r# t5 y4 y* O
29$ y( i0 P" C& S9 P/ y& U6 h, u
301 f1 O) d- N) x% w( m/ K
31/ c2 l5 ^& [0 g9 x# Q+ L X
32
4 i9 n/ {( t3 |6 Q10.2.4 时间戳的切片与索引, j/ B' ]. k* O/ i
一般而言,时间戳序列作为索引使用。如果想要选出某个子时间戳序列,有两种方法:: r% ]# T" L0 [0 O/ }0 ?, r* b
& F2 [5 u/ F; h3 _, t* j
利用dt对象和布尔条件联合使用
* m' Z" }. ~7 Z利用切片,后者常用于连续时间戳。6 C& n$ m6 t3 M: `% B, U
s = pd.Series(np.random.randint(2,size=366), index=pd.date_range('2020-01-01','2020-12-31'))
' s/ b. t0 j( ~$ tidx = pd.Series(s.index).dt
H7 Y. ]8 I. M' C7 C; I4 h4 x. Fs.head()( A, {/ k& R* n1 @* P$ \
% q3 d9 K' q( \- n5 ]" k1 Z
2020-01-01 0& w. e+ n7 }( t
2020-01-02 10 s0 L s* y: P8 I, U9 b
2020-01-03 1
7 V- q2 P6 X& n# Y/ H" v$ O- \2 f2020-01-04 0
0 s7 d i/ S; u; @7 Y' ^2020-01-05 0/ Y% c5 j/ E/ q& F: T3 n# x/ Z
Freq: D, dtype: int32 {) r, p7 K8 ]* M2 f
13 j1 l2 j: q1 R2 P- I; Q( j
2
3 d9 F2 ^' @9 B% K3: Y2 R0 }* _$ ~" {: z
4* [' o" R2 o9 l" i% m1 k% z
5
) ~3 V# y8 i! p) C- U6
9 n5 v. P4 x/ r. H7
( J; t( v1 S# P3 @87 o, f5 p6 J3 i
9, [# n) c% U1 N( T! g6 n
10
! k* d0 q2 B. \Example1:每月的第一天或者最后一天' r1 N+ e f3 L: T% `8 Q
& I$ @& f" b% Y3 {s[(idx.is_month_start|idx.is_month_end).values].head() # 必须要写.values
$ i& L9 f% u& @7 EOut[50]: e; }, s- x! X* j F1 v
2020-01-01 1
: t( {7 ]) |6 U% s# w4 x2020-01-31 0
9 I3 \( f/ F& g, [! [# s2020-02-01 1
! C8 F1 L) i( I& Z2020-02-29 1
% E' C8 x8 x, R, S6 Z2020-03-01 09 a5 n8 F) T/ Y# _
dtype: int32 Z! f2 S9 c) Q8 o: v% s
1
/ y1 a' a, P8 n, d% _2
& J% S0 F3 w: ~! t3% ?) c. r$ ~- A% Q# Q" C
43 Z3 a9 z7 M0 J% P+ T# T8 P
5; f: W* n# c- P( ]& a; r+ ?% l# E) p! h
6( T# I2 j4 T) k% U. k
77 [# A# M# z6 {) Q, Z& R
8
2 z3 ^4 c" \- {Example2:双休日6 _2 [* c/ C1 P
) k0 n7 z3 }9 W; a9 [. Y
s[idx.dayofweek.isin([5,6]).values].head()% I3 u5 X) ]1 @4 h
Out[51]:
$ s, [7 Z' O7 o) S0 E- {2020-01-04 1
9 L! j9 ^! ?+ o2020-01-05 0
. v+ Q: E3 @( u o2020-01-11 01 w0 ~; D! P! E9 G
2020-01-12 1: ]2 a6 k& ?) K8 W) _5 v
2020-01-18 1+ L& y# T* _" J, S
dtype: int32
' n4 f3 b3 n8 m6 |2 B% {1
; U, m; Z+ i. a9 M! l! j! [& c, Z, Z2) a, S R' b; f R
3
9 U+ O+ a( o# @# g1 w& L4
4 e1 H$ F, T' |" N2 M, {# _: r5
& \- N. J& [7 C/ I: b6- {8 }) `; ]1 W1 }, Z% J" a; U
7' j4 L- _+ X2 {6 m2 X
8' W4 e& A7 u0 F) v
Example3:取出单日值
' p# @% b; E4 x5 ?4 @1 x5 a; o* r
s['2020-01-01']$ p7 N) g7 l/ Y" q8 E
Out[52]: 1' [; ?* {; V0 e1 F
4 d3 c Y# P! V7 D
s['20200101'] # 自动转换标准格式
7 {- N: B N9 y9 o! lOut[53]: 1
+ a8 W: j8 m0 B1
- G$ \' ?7 w% E' m/ Q4 d: k2
4 E( o, a7 ^7 @3 H3
8 b& j q. f1 _' X! e47 @: Y, Z3 C) t6 W* A1 \3 M- r( Z
5
9 M0 ?9 {7 i/ t+ vExample4:取出七月 s4 f( A) c# U# {, ^
8 D- y5 N4 {& d+ @' ?* g' W: Js['2020-07'].head()
1 @2 Z" i0 w9 G" j3 n5 c- a' ZOut[54]: # Q; D; t8 M( x* |$ Q
2020-07-01 0- U1 d3 f# v# Q% N, N# D% [) l; C
2020-07-02 12 b9 D) \ f* a1 I/ H" O/ ], g9 h6 i) F
2020-07-03 0
/ B8 z9 Y. a- l6 c2020-07-04 00 d8 B0 S; M* D, J6 W; l
2020-07-05 0
9 ]+ ~5 y' v) u% v7 c: A0 w* K2 CFreq: D, dtype: int32
! ] b" x5 D$ |1
! u+ C5 i( e+ k1 \" Y0 D6 E; t+ d26 _! ^/ d" e3 @8 j; h
36 B1 z9 n7 X4 ]( M
4( |0 w3 w) j0 W1 d/ m2 ?$ `
5) X% \/ n6 ?- ]4 i! \
6
1 h4 D6 s4 N0 `. l2 D. e1 j79 V8 \; _) C" q# f
8
; u! d' k% C, H) q3 S6 j3 SExample5:取出5月初至7月15日
& w4 p& L* s/ s8 T/ V) c6 i" r
) D* Q( N6 a; H' o$ S, V) v$ Cs['2020-05':'2020-7-15'].head()$ r+ Z W6 L6 J0 J
Out[55]: 3 Y% c$ S5 a I' E
2020-05-01 0
2 m7 q+ f7 G+ h5 d4 r/ R7 V2020-05-02 11 q3 w2 e ?3 ^+ E" X( M
2020-05-03 0* P) w6 H3 N6 Y7 ?* D* s( o6 U
2020-05-04 1/ T9 a N s. D5 F. T7 L
2020-05-05 1; n$ o/ C- ~; ]* |' k/ J5 K" n) { `
Freq: D, dtype: int329 f4 k. X* h6 o" w
+ X: Y2 m- @7 D' S- X$ o2 \s['2020-05':'2020-7-15'].tail() w; D4 |" ]* Q& Z& s' E
Out[56]: / y. K0 T2 s, z D' ^
2020-07-11 0
2 G1 ~- k) [' g \0 @/ s2 N2020-07-12 0
4 P& q0 a: P7 G$ o6 A; O. @( D9 X' ]2020-07-13 1
8 p: J! G' ]$ q6 i" `5 V# M2020-07-14 0
% _3 J! z3 I8 n# v( c2020-07-15 1
& v% Y, p, X9 P$ aFreq: D, dtype: int32
# ^' d2 l1 w2 s# q. s- r; i! L
( T! R# c: U' d* ~. I8 d' Y% f4 Z18 d1 i; X8 K6 _2 x; K9 h( x$ n
2
8 H! _) Z: T1 P) x' w: w% ~3 L; |7 g4 O8 ^- k! q2 [$ J0 ?
48 b; |0 z x+ V8 P/ w _
5% j% p( e( }) L9 d/ R
6, B3 [, z! m7 s8 _
7
' j' {2 n2 u5 Y80 ? A# H; z Y- d* _: A+ L
9
3 E" T. o( K* L) Z10
+ M ~4 a7 N, c! @11* U* \% w' W3 {, O7 C1 M2 C9 D9 C1 o
12
" t7 [ M2 b! {% t& ^13
9 m! y9 J6 v3 x( [* N2 V) i/ X/ y14
5 r k4 X+ W2 y15
7 t) \6 Q) q+ @& S( ]. l16
0 l3 e& I! P4 l17
8 _ A3 { g J4 @' M6 z ]10.3 时间差, t$ m/ ?5 o8 I
10.3.1 Timedelta的生成, P+ ^& b6 C' H
pandas.Timedelta(value=<object object>, unit=None, **kwargs)
; p$ U4 W+ k/ v% Z3 f( @ unit:字符串格式,默认 ‘ns’。如果输入是整数,则表示输入的单位。
8 T! ]+ k0 V, i1 m5 B, r 可能的值有:* o# A$ F, `9 n% g. @9 L4 A
* J$ L Q# q) x x- H% [7 l- H8 @1 B‘W’, ‘D’, ‘T’, ‘S’, ‘L’, ‘U’, or ‘N’
8 S4 I7 ]. E. I; W1 ?‘days’ or ‘day’" a! h( v0 D; y: n! x2 v: Q' B
‘hours’, ‘hour’, ‘hr’, or ‘h’9 x7 ~* d! r$ N! K! ^5 b; _' K
‘minutes’, ‘minute’, ‘min’, or ‘m’% X+ e% e _4 e8 e; R6 m: E
‘seconds’, ‘second’, or ‘sec’+ x$ Q1 @) e! K4 m' s- F
毫秒‘milliseconds’, ‘millisecond’, ‘millis’, or ‘milli’5 p) b2 G/ K5 E
微秒‘microseconds’, ‘microsecond’, ‘micros’, or ‘micro’. ~4 d0 Z* M1 r" W! K/ H6 v. J
纳秒 ‘nanoseconds’, ‘nanosecond’, ‘nanos’, ‘nano’, or ‘ns’.
6 p% d0 W6 S8 `, H9 }" f时间差可以理解为两个时间戳的差,可以通过pd.Timedelta来构造:
# ~' L+ C/ i! t3 c: T ~pd.Timestamp('20200102 08:00:00')-pd.Timestamp('20200101 07:35:00')4 q1 u( k5 w- x8 x
Out[57]: Timedelta('1 days 00:25:00')- L O' @. Z; g) k* P" ~
% o( a; V R, E- m% Y! qpd.Timedelta(days=1, minutes=25) # 需要注意加s: B5 u4 Z4 R3 `4 C* _! S
Out[58]: Timedelta('1 days 00:25:00')
. y8 e3 @+ X1 Z( K- [1 L
/ m9 w2 ?1 w2 Xpd.Timedelta('1 days 25 minutes') # 字符串生成, j: z+ ~* K1 H5 X# m* A+ X
Out[59]: Timedelta('1 days 00:25:00')+ s( W9 }9 w! V! M0 b7 K" r
8 o: ?9 @/ [3 R& p S+ e7 Y) ^. F
pd.Timedelta(1, "d")
# f# x; l# W% ~$ H I" lOut[58]: Timedelta('1 days 00:00:00')& X& G& _* W* `6 J! I
1
6 `# X3 _ h6 S7 _2
* D* m0 e8 t1 d! i1 w9 V( p1 U) g3
9 B/ |% Y3 h, K4. e8 p8 d; q3 `# @; A/ z: j
5
- R: ^& M6 ^) c6
0 S2 `7 \ m( U% ^% s& U7
\' ^2 B- x$ \% p* ^0 \8
$ y, z' X9 F& ^. K, d9
2 A* _/ p# w/ a7 s+ c/ G3 T10
& e- K' {8 P z' a2 ^117 f J; x: x! i! A4 b
生成时间差序列的主要方式是 pd.to_timedelta ,其类型为 timedelta64[ns] :! Z2 j, U1 A0 y6 _. k8 {5 [: M
s = pd.to_timedelta(df.Time_Record)! y' z. M! ~- e6 m3 g6 Y
7 d! W5 B. q" L9 G9 Y" K. L( m/ a* i: Os.head()2 m& ]1 q4 u5 F, ]
Out[61]:
$ L6 [* @ t% J' n0 0 days 00:04:34
* `! l0 H# Q$ J* c( l! w6 m; p1 0 days 00:04:20- Y% i6 v1 I7 ~, ~$ e8 s% k0 O
2 0 days 00:05:22- t* m; Y- k! m! h" E
3 0 days 00:04:083 A* @: n# X! d1 O
4 0 days 00:05:22
4 ~% d; w' o! f b# O* x: RName: Time_Record, dtype: timedelta64[ns]9 U! y* Y, A2 o$ M3 s
1
: r$ S" N2 s5 j! G3 P" u/ P20 o4 i6 m2 v) c) q5 f! y9 S& Z
3& U5 |$ Z1 B1 S3 N; M k& O
4$ m5 p! }! |: H" B. a% l: Q
5
@ Y6 ?% ~" N( d( E% v& @% @6" x, I; f: E8 c) x3 A, B0 e9 q
76 Z. d! G( |$ e
87 w% `& u1 S% t
9
( {+ M( h p$ X" r( K1 y10
3 Y+ t) D* ?, J ^0 \7 J与date_range一样,时间差序列也可以用timedelta_range来生成,它们两者具有一致的参数:2 ~" _$ Q$ d) o$ r. d
pd.timedelta_range('0s', '1000s', freq='6min')
3 \1 N/ U! ]1 @2 V1 rOut[62]: TimedeltaIndex(['0 days 00:00:00', '0 days 00:06:00', '0 days 00:12:00'], dtype='timedelta64[ns]', freq='6T')
# y' M1 L3 r3 R4 O' o
& `4 h4 ~" j, z% r+ Rpd.timedelta_range('0s', '1000s', periods=3)
; s, i, \) f3 _/ U( aOut[63]: TimedeltaIndex(['0 days 00:00:00', '0 days 00:08:20', '0 days 00:16:40'], dtype='timedelta64[ns]', freq=None)
# m0 E2 {1 ~. U# O+ K8 c2 ~13 O" [/ P* W- k8 K4 i
2) e5 @# q$ q" @) k# \0 h
3: r: _7 B1 r- j! [% R% U
41 c9 j. w' a2 z1 n
5
' V! _4 Q T; R对于Timedelta序列,同样也定义了dt对象,上面主要定义了的属性包括days, seconds, mircroseconds(毫秒), nanoseconds(纳秒),它们分别返回了对应的时间差特征。需要注意的是,这里的seconds不是指单纯的秒,而是对天数取余后剩余的秒数:; K ~) f5 e, `" Z0 B! x
s.dt.seconds.head()
6 u* S; v' Y: Z; UOut[64]:
& J& o9 n D/ T" P0 274. _3 H. S% }3 D( R; z
1 260. W5 ]! E8 i% t# Y0 F I! @- A
2 322+ F: u: ~- e! n
3 248 }" `. e/ m: z, v
4 322; r: {4 d8 y5 o1 S5 c: M2 o
Name: Time_Record, dtype: int641 y7 p! c% e* B- j. D$ f" ?
1
! M4 H( T; `" q; h26 S/ T9 z9 D: B# ^
3% r' `4 g; n+ e) G, _6 P
4. x2 H) p3 r5 U* L9 b6 Q
56 I8 G) G5 K1 r# x& s; m# C
6: I3 {: H, c. G. E% x
7
4 w" C8 w5 u' |+ z% b7 s: N9 y84 t! X+ I: E+ ~
如果不想对天数取余而直接对应秒数,可以使用total_seconds% _0 W3 s. F1 M1 f
7 Z8 _4 F9 ? _" _' hs.dt.total_seconds().head()
* \# t- ^, j7 [3 D) wOut[65]: & l& P$ ~9 c! m9 D3 `9 m) s9 M2 a2 t
0 274.08 E, j9 a, v4 h* d% R
1 260.0& r7 t6 b# R7 _* s
2 322.0
1 K& |6 T; m- [4 ~' r3 248.0" }% }9 x$ Z) V! X* g& Z, L
4 322.0' T$ a" r t; y- x. x) [
Name: Time_Record, dtype: float64
7 E; c* @9 B7 m. k$ b6 X) B1& p' Y2 p6 c: X7 J" W: s
2- G& V2 [& g: b) r5 q, U2 l
33 a$ |7 }; L$ ~
4
1 ?" K, D/ J& v4 Q `& |9 S5( g8 x) C) B* j$ d# q) m' Q
6
% |: x* b( O& |3 c6 J$ r7
6 X& L- M8 }3 b! m: t4 l3 B( Z8
% b# @4 {" C5 P2 C与时间戳序列类似,取整函数也是可以在dt对象上使用的:
% w9 _/ i8 X2 ^7 z: ?
7 |/ K, _, g9 ^# `pd.to_timedelta(df.Time_Record).dt.round('min').head()
1 @6 u F' Z6 UOut[66]:
% z' b* }2 M8 k- O s7 j. N, H0 0 days 00:05:00
% l& K/ ~ b9 K# |1 0 days 00:04:004 V d1 X! X! L" Q( ^
2 0 days 00:05:00
0 T8 M: j9 ~% p) ]5 U9 [0 {3 0 days 00:04:00( G: C4 S7 ?* T- j6 r
4 0 days 00:05:005 @* l' p5 \$ I9 r J
Name: Time_Record, dtype: timedelta64[ns]" K) O' X$ g2 l, n9 d+ s
1/ u9 }4 D+ n2 s: S) |
29 N* Q* q9 U) K; c
3- B8 p x, g( p. B0 O$ t2 Q
42 {4 @# ]3 p) D6 o5 B& R* u, {" O0 ]
5: O! r1 v& v7 u5 V& u
6
6 L) h8 }& }! r4 p X& c. j3 }7+ Z, K8 c1 o% Z! m9 r' Q+ `
8# R* @/ _& `" O) Z0 m
10.2.2 Timedelta的运算
6 L( }) h% a$ G3 f单个时间差的常用运算,有三类:与标量的乘法运算、与时间戳的加减法运算、与时间差的加减法与除法运算: v0 u, |; ?" t
td1 = pd.Timedelta(days=1)
5 i% E( B3 b0 I6 X3 M1 t0 E+ ~" K) itd2 = pd.Timedelta(days=3); U4 E$ x- ?5 k9 E0 u
ts = pd.Timestamp('20200101')+ _* D6 g, ?7 r0 d; U: ]# c- D
$ |4 w. V: j+ h; c7 itd1 * 2( E0 Z+ J! L2 A" D- _
Out[70]: Timedelta('2 days 00:00:00')
2 X( |8 Y3 p' P! n6 z
5 X9 @( g+ y% }0 F; xtd2 - td1
/ U0 M5 C2 g) `1 n4 L$ ?Out[71]: Timedelta('2 days 00:00:00')7 F% {8 v& p% Z8 x8 u, x
# j" m3 ~ U. u% D
ts + td1
! B& t* u' B+ KOut[72]: Timestamp('2020-01-02 00:00:00')" q' |& U9 ~5 Z: ?9 R2 _
% S) Z" ^. [1 U+ D& ]8 ?7 D- ]8 F
ts - td1, t' o$ f9 i6 Z% d) i# A
Out[73]: Timestamp('2019-12-31 00:00:00')6 @, o6 L. s3 J. K- X8 T" c6 @
14 K# @6 [5 f/ ~
2
. N' g! ^( G8 G, T+ Q4 t5 L- j3: t8 N& x7 ?% d0 \: ^1 h
4
0 v/ ^* o4 x) v+ y2 d4 Y5
6 V+ j; c6 z, Y- Q5 V. o6
% C% P4 Y7 ^6 b% p# F& z+ v7
$ l v, M$ D8 B1 e; R* |& w8
5 a8 T4 B1 p9 C92 u( J4 ?# m( A d$ E0 a
106 ~1 v' h# j7 ~4 u
11
^! p* f* C2 G# F1 |3 `4 {4 ~1 O' B- m12
H, }6 k8 A* E, E( Y' O13
' i Q4 c/ c& Z% B1 K14
+ B! b# N( ?' k, [15
+ y! i/ V2 z- _' z时间差的序列的运算,和上面方法相同:
D& I: a7 H$ x# y3 b; R( c& f3 }, {0 Ltd1 = pd.timedelta_range(start='1 days', periods=5)
* I8 _+ H7 G( K- C/ [% q/ R& {4 Ntd2 = pd.timedelta_range(start='12 hours',1 w. ?8 X7 X, Y& g
freq='2H',, M% E4 h' H3 b4 s
periods=5)
5 U. x7 D) ^4 m5 D. s9 qts = pd.date_range('20200101', '20200105')1 ?' }4 [8 z/ |: d
td1,td2,ts
+ z3 [) x. M5 L% Q8 c$ e3 S
* `+ k6 k8 u) |+ d* A7 wTimedeltaIndex(['1 days', '2 days', '3 days', '4 days', '5 days'], dtype='timedelta64[ns]', freq='D')8 @1 D: H- A, C g5 O9 s$ R+ o
TimedeltaIndex(['0 days 12:00:00', '0 days 14:00:00', '0 days 16:00:00',
) m& W3 Y" b$ W5 g '0 days 18:00:00', '0 days 20:00:00'], dtype='timedelta64[ns]', freq='2H')
4 s% a; v, l/ I( b$ k8 HDatetimeIndex(['2020-01-01', '2020-01-02', '2020-01-03', '2020-01-04',
' u; A- z U3 j0 | F% x; |# m '2020-01-05'],
( ?5 k/ R; H) Q( N( V dtype='datetime64[ns]', freq='D')' i' R7 W3 q' E1 I
1
9 x2 e, Y' p; {0 b8 {/ c: ^2
) O9 c" ^' ~, E1 ^3, f* t; g, ^) G; i4 y$ O
4
3 I' ^9 `$ b* i. ^ \5
, e) j2 h" R* T6, ?: M8 W' n- Y3 ^6 C+ M( d
77 P/ b, t/ W) P( \
8
4 k/ L3 o% k, d+ x6 ?# s7 U3 u9# a' Y0 n( c& N q% v
101 R; n9 ~' m6 D' C* l% j
11; }: j% q5 M9 n( }
12
' w8 w/ t1 R7 \2 S% s; ^13" K2 M& J. ~% i5 N
td1 * 5# ?3 N. V( I: b
Out[77]: TimedeltaIndex(['5 days', '10 days', '15 days', '20 days', '25 days'], dtype='timedelta64[ns]', freq='5D')
; p& d# q; e0 H! ?7 C# a- s" v& e8 f% U6 H! ]+ `& p6 T
td1 * pd.Series(list(range(5))) # 逐个相乘
0 s& S2 ~8 w; F2 y$ e! z; [Out[78]:
6 {8 J' O( ~7 J% n0 @( p% w0 0 days
% A7 }; Y. K$ Q% Z+ k+ `1 ]7 D1 2 days' \( g' ~7 N5 c) G. S+ B' Q9 k9 k
2 6 days
; y; a: i8 s* ^2 j; |3 12 days
2 x# Q& ?0 v. Q( o) I4 20 days6 l8 O( |& E: [
dtype: timedelta64[ns], s" w& N/ k; O5 T: D5 w U! `
; E3 G- H- G4 u) ?9 C2 X
td1 - td2
1 f- T/ f7 Q; c8 eOut[79]: ! ^7 T' H( E) t- ~* r5 s$ k
TimedeltaIndex(['0 days 12:00:00', '1 days 10:00:00', '2 days 08:00:00',
* h2 N1 M/ j, G) U" W '3 days 06:00:00', '4 days 04:00:00'],6 p* i/ n& ~" ^: t% z
dtype='timedelta64[ns]', freq=None)
5 D4 O! j' v3 f' H
w) v5 T3 T, s& ^ xtd1 + pd.Timestamp('20200101')
) x! k. B4 X" Y7 \/ i1 ~Out[80]: 9 m3 e8 B8 I+ w( d8 N7 o, C) U
DatetimeIndex(['2020-01-02', '2020-01-03', '2020-01-04', '2020-01-05',
* D$ [$ l( Q' u0 x '2020-01-06'],dtype='datetime64[ns]', freq='D')! E5 J2 T7 R# X8 B
; ~3 d I/ @2 a9 A7 U' jtd1 + ts # 逐个相加
2 S. y4 t) O) n+ z& MOut[81]:
& w4 B% q6 c+ lDatetimeIndex(['2020-01-02', '2020-01-04', '2020-01-06', '2020-01-08',
3 v3 i4 `" L9 z7 z, f0 R- |) Q '2020-01-10'],
* ]7 e4 \. k! }+ m* _ dtype='datetime64[ns]', freq=None)+ |) ^0 {; \ U6 @. S: G( G/ ?1 b
# k+ ?5 V9 i$ c# i) e1- U" C* F3 h6 m( t- |" ?* ^* r
2
, ~2 K2 Y. p E+ l0 j" c8 M3
% k" p6 x& t$ j. P42 `7 }: r+ S" e3 Z% i
5
# ^; ^" R3 F; }! |6% y# u4 d- y' T4 M6 u
7
. \5 R, A! t% ^: C3 g- A- `8. ]/ m) {- v: P/ C& t" B
9: s% o) ?; U% E) H) Z+ \; ^
10
* P' ]7 e) u$ I# {7 S( D11
! J2 s1 U: ~# k" |12; A. c, V2 v' _
13
' v1 E! h* T' z+ V14 k& _8 F: Z* [" u& n6 |6 Z* @* e4 E
15
- k( E' [3 e( @& {5 c2 |16
. ?/ N7 f/ x! `6 z5 `- e/ }17- J+ S; H) t! u, E5 C: |
18
% e" ~, y1 ~6 d9 K19- O$ k7 ]. s; \# ^, Y7 `8 ~2 e
209 w/ @0 X% \* i3 i% V J
21% q2 Y7 U( F( @9 @1 e
22
$ K. M4 ~* ~' w. g$ h23# g' n& ?5 O, a$ Q, N
245 p" z* k& g* J" i' j" }
258 U# D+ ^' a/ S9 x
26
$ P5 r" T7 u" T, o27
! Y& P/ F0 g0 ?4 i! i" Z28
5 b9 |, C5 ^5 S# N+ K' t$ e( N" _10.4 日期偏置
/ \( M9 H8 [* F+ d0 ?9 Z+ e2 N10.4.1 Offset对象. l0 G4 Q5 t- g% x4 g% d
日期偏置是一种和日历相关的特殊时间差,例如回到第一节中的两个问题:如何求2020年9月第一个周一的日期,以及如何求2020年9月7日后的第30个工作日是哪一天。
, W/ Y! @' P+ u, l( k
% \ E+ Y9 t: T" m1 l4 ^DateOffset 类有10个属性,假设s=pd.offsets.WeekOfMonth(week=0,weekday=0),则:
6 J, p. m( Y& f: T: Z; S! b x |* @3 ~: V
s.base:<WeekOfMonth: week=0, weekday=0>,返回 n=1 且所有其他属性一样的副本: q& r, w. e1 P# S
s.kwds:{‘week’: 0, ‘weekday’: 0}& } H1 n3 v* g* L y
s.wek/s.weekday:顾名思义
2 {9 V0 K8 N2 K' B7 L有14个方法,包括:2 w( Q/ d9 X J, w7 h! b
- r8 @ s4 G, u5 |- N- B* G6 D
DateOffset.is_month_start、DateOffset.is_month_end、DateOffset.is_quarter_start、DateOffset.is_quarter_end、DateOffset.is_year_start、DateOffset.is_year_end等等。$ o3 |( F. ?" a/ u
pandas.tseries.offsets.WeekOfMonth(week,weekday):描述每月的日期,例如“每月第二周的星期二”。
7 k! ?4 `! ^( Y" k* c ~$ [6 r
% v( H( Y3 {# g4 }# |有两个参数:
, S* _0 l, n0 t; M/ yweek:整型,表示一个月的第几周。例如 0 是一个月的第 1 周,1 是第 2 周,以此类推。: H" Y* n* @# O# T8 n/ j" V
weekday:整型,取值为[0,1,…6],表示周一到周日,默认取值为0(星期一)% l o* Z1 {, n D, h" k9 i7 J
pandas.tseries.offsets.BusinessDay(n):相当于pd.offsets.BDay(n),DateOffset 子类,表示可能的 n 个工作日。
) K7 ^2 C' L6 @; R( G
* j& k% i9 s4 B8 Apd.Timestamp('20200831') + pd.offsets.WeekOfMonth(week=0,weekday=0)9 l: ~( ^3 T, ]
Out[82]: Timestamp('2020-09-07 00:00:00')& f2 G6 @ m' H9 Z7 k- S
, ?' t: y* K, E( g0 ^) L
pd.Timestamp('20200907') + pd.offsets.BDay(30)
* f* f' G) |7 M L/ }/ i9 }Out[83]: Timestamp('2020-10-19 00:00:00')
- e6 U# T+ W2 {+ P# P% g1 Z5 Q1
6 _. ?( ?4 T+ g( ]! Q t2
: n" G* U9 A0 L9 T+ f3
: G2 n7 Q6 k+ C4
* {1 A4 J, |/ l% E4 w. t8 [5
8 }: O# p1 n ^" {" } 从上面的例子中可以看到,Offset对象在pd.offsets中被定义。当使用+时获取离其最近的下一个日期,当使用-时获取离其最近的上一个日期:
: N* d- R: o/ @1 D
" H& m1 q- s tpd.Timestamp('20200831') - pd.offsets.WeekOfMonth(week=0,weekday=0)) M) _0 p! N; [2 r2 I
Out[84]: Timestamp('2020-08-03 00:00:00')$ h% y3 Z: f% A
6 \; I& z) K3 Rpd.Timestamp('20200907') - pd.offsets.BDay(30)
( {5 m n0 R0 [/ f9 E, |Out[85]: Timestamp('2020-07-27 00:00:00')" p; X$ p, S4 l& P6 U* e
6 |, S) w2 \) a- i0 z6 dpd.Timestamp('20200907') + pd.offsets.MonthEnd()& z2 ^6 J' `( `
Out[86]: Timestamp('2020-09-30 00:00:00')% x y4 l* t, v( g( p# E
1
' {2 K6 x, B- G |/ F2
1 j) h0 {) S* B! Q! r' H8 J3
0 Z4 k" q9 |- _. l4: l+ p" O! {& b2 s" F" ^2 E! N
5: I# o, E) E5 U3 X8 T
6' K7 O$ ~8 h) h3 A5 ~7 n$ `
7
$ f$ V1 I9 I. S! V* H8: p; i/ r* m2 u
常用的日期偏置如下可以查阅这里的DateOffset 文档描述。在文档罗列的Offset中,需要介绍一个特殊的Offset对象CDay。CDay 或 CustomBusinessDay 类提供了一个参数化的 BusinessDay 类,可用于创建自定义的工作日日历,该日历说明当地假期和当地周末惯例。
4 r( F" }% z" k 其中的holidays, weekmask参数能够分别对自定义的日期和星期进行过滤,前者传入了需要过滤的日期列表,后者传入的是三个字母的星期缩写构成的星期字符串,其作用是只保留字符串中出现的星期:) m4 r* k7 a F6 f, r0 A) J5 c& b
1 S) ~5 e& K" r% r! D/ _' d9 lmy_filter = pd.offsets.CDay(n=1,weekmask='Wed Fri',holidays=['20200109'])
$ X" b# ? ^6 Q, u8 Y: [1 ~% Kdr = pd.date_range('20200108', '20200111')! r6 c( F7 a' f+ |$ V
# }% Y, S, M$ I. ^ ydr.to_series().dt.dayofweek
: Q4 l3 X2 f z5 v8 w& uOut[89]:
8 T0 T& c+ p8 c/ |/ Z2020-01-08 2/ {9 P: [3 j$ B3 r, h7 I$ E
2020-01-09 3
9 W6 S& C$ \) V6 D9 j2020-01-10 44 G5 }" ?$ u( V1 X* f0 c/ r) @( T% b
2020-01-11 57 c& m# Z7 o! |: Y4 _' M6 ]
Freq: D, dtype: int64
( N6 X* G2 a* \+ }* p0 f+ Z& j4 B) o/ ]. Q9 Y
[i + my_filter for i in dr]
# E$ ~: U% Z6 X, gOut[90]: 9 }0 p. i0 u$ t" z8 k
[Timestamp('2020-01-10 00:00:00'),
9 K, Q7 A9 v( V0 J+ @ Timestamp('2020-01-10 00:00:00'),
\: S0 [5 |2 i4 v4 A$ w( m Timestamp('2020-01-15 00:00:00'),6 A- O; L5 j2 z4 q# u
Timestamp('2020-01-15 00:00:00')]# M0 x$ R* _2 K4 g; X
4 M" z+ @7 R4 |0 ?/ k% [: q
1
3 v! I, X2 v. R9 |+ O- ^' I2; V2 r3 O7 o) v* Z: ]
3
$ q7 o/ J( z0 N4
, v0 `& S. A/ ~, o1 h( A3 `6 C51 h7 c2 l. L( O* F
6
: N. h. K/ J& j/ L4 x7
' Y$ _5 _8 N9 f4 J- V8
- A; ~! k. p$ m3 N9
" V; c# C0 ?% y2 @$ c* W10- O" n$ k7 i( x* D( {, c0 |! D6 B
11
# J' k! Y; v9 ~- \+ `12
' S' d. N5 G( Y+ C; \, {7 o. |13
, c, o! }. W& p; e/ Q/ A6 X14* F( R6 b$ C7 }" h
15; C# K9 f, Q# W
16
- ~2 Y( l. A8 G9 [178 j+ @! q4 g+ O4 U
上面的例子中,n表示增加一天CDay,dr中的第一天为20200108,但由于下一天20200109被排除了,并且20200110是合法的周五,因此转为20200110,其他后面的日期处理类似。# z3 z4 y; t, }8 ?3 r" y
# {: W" f. ? d# @0 I: V【CAUTION】不要使用部分Offset
( n( f* E& S( j- e( Y) q' w在当前版本下由于一些 bug ,不要使用 Day 级别以下的 Offset 对象,比如 Hour, Second 等,请使用对应的 Timedelta 对象来代替。7 _- C! H4 T4 U0 l2 R
8 c2 h* o: O3 w
10.4.2 偏置字符串
6 Y0 s4 r; ?+ {: l 前面提到了关于date_range的freq取值可用Offset对象,同时在pandas中几乎每一个Offset对象绑定了日期偏置字符串(frequencies strings/offset aliases),可以指定Offset对应的字符串来替代使用。下面举一些常见的例子。: ?2 d; O* r3 c- A
. _6 f9 X. x0 V' Y& \8 j. ~ Offset aliases:pd.date_range函数中的freq参数,为常见时间序列频率提供了许多字符串别名。 也称为偏移别名Offset aliases。偏移别名列表点此参看(大概27个)。, }; E! }! { S' U) ~6 _, J
) x7 w& W1 T- _# h
pd.date_range('20200101','20200331', freq='MS') # 月初4 h$ `! u; i: _# h+ o8 X
Out[91]: DatetimeIndex(['2020-01-01', '2020-02-01', '2020-03-01'], dtype='datetime64[ns]', freq='MS')
2 @! O" n# w5 t4 x0 S4 r' o, k* I" r) F
pd.date_range('20200101','20200331', freq='M') # 月末0 {( H. _9 v+ w* o( P4 Y6 `
Out[92]: DatetimeIndex(['2020-01-31', '2020-02-29', '2020-03-31'], dtype='datetime64[ns]', freq='M')5 {. _, ~: x$ R" L
0 K+ v8 r3 ^) W% R$ G
pd.date_range('20200101','20200110', freq='B') # 工作日
, ~. B! J1 `7 d+ V1 d' I5 LOut[93]: 4 P n. Z: A% L
DatetimeIndex(['2020-01-01', '2020-01-02', '2020-01-03', '2020-01-06',
! ]! G3 a/ [0 y, i3 ?$ g '2020-01-07', '2020-01-08', '2020-01-09', '2020-01-10'],
/ T% ^0 D( e+ B4 }% J dtype='datetime64[ns]', freq='B')
8 F% N: }6 Q x" W" O8 {: d
O' [/ m; U0 ]3 D. C( O4 d, fpd.date_range('20200101','20200201', freq='W-MON') # 周一
- u) f8 ]! ]) T% jOut[94]: DatetimeIndex(['2020-01-06', '2020-01-13', '2020-01-20', '2020-01-27'], dtype='datetime64[ns]', freq='W-MON')
1 C+ Y0 S% N! Z; x' j) t
) {5 D- I% Q" J$ E" L( l5 ipd.date_range('20200101','20200201',
8 A( L, x$ ` I freq='WOM-1MON') # 每月第一个周一
& e; d& D) O3 z+ G3 M* z5 X7 Q; H$ P
Out[95]: DatetimeIndex(['2020-01-06'], dtype='datetime64[ns]', freq='WOM-1MON')
/ F7 C* d2 W3 R& j d2 e( m: a9 ] K9 C: z
15 t' R3 s, X/ y0 e8 u* h
2$ c* L; ?# u0 m9 j! y9 c: q1 E
3
/ `; q6 U' t( V3 _5 `4' b- B3 n y- Y7 u& I" `
5
# |7 ~# k1 a0 H1 A6
" Z4 F$ }& Y; f- }4 v' q72 P3 u. O# X8 F' y
8& m5 q8 ?) z- D& g7 N) J! w
9
" x. z+ F+ _% L% G% d% ]4 _10& \( ~# p6 r: V( F5 W
112 s* I' r/ N2 ] q! S2 Z& [$ ^
12
6 S# }8 y! Z0 s( F13
" f$ V5 L% Y, |7 X& t148 y7 _1 j! | Q5 o7 Z3 ^2 D0 ~: J# S
158 e0 L. L- Z3 k/ b8 r
16
. H% n8 {3 O! J' w170 b& d8 g6 P- j5 j# Y. x8 v
187 T! l) S6 A+ d
19
$ S# L. s$ J3 a5 J7 Z( W上面的这些字符串,等价于使用如下的 Offset 对象:
* O0 s$ w& r5 m5 b" ?2 k3 H1 A8 ~) w1 t
4 f [' n U9 c7 rpd.date_range('20200101','20200331',! {7 C' ]5 [ K5 @
freq=pd.offsets.MonthBegin())
# Z1 x& w" m: ]9 d0 w" Q& {: T1 d2 p% E$ U+ q' u1 g' B1 t. J. ?
Out[96]: DatetimeIndex(['2020-01-01', '2020-02-01', '2020-03-01'], dtype='datetime64[ns]', freq='MS')
& Q! V4 E: P4 O) g- ]' Y/ Z/ M$ g" d: @2 x" t
pd.date_range('20200101','20200331',; {9 m8 \/ R! e. h$ T
freq=pd.offsets.MonthEnd())
) c# t$ _/ t; k4 Y" E6 r. m4 Q0 o; a( T t9 X( s' t4 Y
Out[97]: DatetimeIndex(['2020-01-31', '2020-02-29', '2020-03-31'], dtype='datetime64[ns]', freq='M')9 E4 I1 `# Q% H4 R7 Z p
3 l8 Y9 S8 T3 @2 P6 Opd.date_range('20200101','20200110', freq=pd.offsets.BDay())
* L; q% ]2 L9 pOut[98]: ; h7 M& R. R% y2 m
DatetimeIndex(['2020-01-01', '2020-01-02', '2020-01-03', '2020-01-06',
- G- g7 j2 y' Y0 I J '2020-01-07', '2020-01-08', '2020-01-09', '2020-01-10'],
+ m+ V, \8 |4 T- m; g dtype='datetime64[ns]', freq='B')2 s' x, y; T, p7 j
" H5 B% G O7 x4 N% e$ o9 m; \8 Cpd.date_range('20200101','20200201',/ u) Q5 e- q' P3 v4 f
freq=pd.offsets.CDay(weekmask='Mon'))5 o$ O! p$ _( X& ^0 z
/ d( [* E# y$ g
Out[99]: DatetimeIndex(['2020-01-06', '2020-01-13', '2020-01-20', '2020-01-27'], dtype='datetime64[ns]', freq='C')
$ C9 \1 j0 {" @; c0 _+ D
& x& ]7 W" G. X/ Spd.date_range('20200101','20200201',5 [9 h3 {0 G5 s5 l+ q
freq=pd.offsets.WeekOfMonth(week=0,weekday=0))
5 Z7 M$ [% X) M- u/ q2 x- @" |6 b& ]* W5 d3 v v
Out[100]: DatetimeIndex(['2020-01-06'], dtype='datetime64[ns]', freq='WOM-1MON'). q) m" W& U. |3 _1 R+ R
& W- v: T" o! N& j2 {0 G10 t3 m! z! S; ^' c" }2 l$ `
20 X* r! O! h1 X" B9 l1 D- I$ f
3/ o$ @3 c1 @* z( m- L# C$ s9 ^
4
: Q& N1 x2 @$ G% ]" z5
/ C# v( u' o2 ~6
2 F$ K% p8 \) p: A; r; Y7
! O2 K. j9 }* p6 w1 S% E8+ j# e" a0 i5 g; p/ _
97 q- g# l& j. p: \6 n5 N6 z
10
* b. |# v" P; J7 F4 A; Z' y( O11
0 v9 D" Q* J0 P12* V$ G4 F5 t6 {; R6 S
13: @9 V: {+ w8 O
14
) i0 v9 ]6 ]( u, G15+ h( [/ j0 j) s) V4 K4 o
16
9 |1 s' W8 o2 y: m6 ^+ v8 T172 ?# s* X8 A& g, l# o
18, ]& N V- e: `2 k! B; }
19
: o" @9 C+ y2 ?$ J- y205 w$ P" Y" a8 W5 Y
219 ]3 R" w; F: n4 e% K# p3 ^( I
22
5 ?' f* e. \% q7 y0 b2 e# K230 c4 s L6 T& ?' [/ s. t
24
7 C" ^3 J) H4 H' \/ Y1 q. `( v25
0 s- E p" F' C: i3 J1 b0 s5 Q$ ]【CAUTION】关于时区问题的说明
$ O8 x% W$ P3 Y: L9 l 各类时间对象的开发,除了使用python内置的datetime模块,pandas还利用了dateutil模块,很大一部分是为了处理时区问题。总所周知,我国是没有夏令时调整时间一说的,但有些国家会有这种做法,导致了相对而言一天里可能会有23/24/25个小时,也就是relativedelta,这使得Offset对象和Timedelta对象有了对同一问题处理产生不同结果的现象,其中的规则也较为复杂,官方文档的写法存在部分描述错误,并且难以对描述做出统一修正,因为牵涉到了Offset相关的很多组件。因此,本教程完全不考虑时区处理,如果对时区处理的时间偏置有兴趣了解讨论,可以联系我或者参见这里的讨论。# Y5 t9 ]3 G3 h; O C6 V8 q
6 ~9 q: a8 L0 i/ o% r/ ^10.5、时序中的滑窗与分组2 L* Q& F8 V) S1 Y$ C* ^
10.5.1 滑动窗口& H0 N; U: d7 L6 j. K
所谓时序的滑窗函数,即把滑动窗口windows用freq关键词代替,下面给出一个具体的应用案例:在股票市场中有一个指标为BOLL指标,它由中轨线、上轨线、下轨线这三根线构成,具体的计算方法分别是N日均值线、N日均值加两倍N日标准差线、N日均值减两倍N日标准差线。利用rolling对象计算N=30的BOLL指标可以如下写出:
m" j% ?* E. v H, Q
* U- `& g" [3 M* J+ \import matplotlib.pyplot as plt$ p- f4 p, M$ S
idx = pd.date_range('20200101', '20201231', freq='B')
; r$ N0 b! J& M1 J. Fnp.random.seed(2020)9 K. d5 P* m8 o/ y1 H) y
& r4 G- o6 d7 _
data = np.random.randint(-1,2,len(idx)).cumsum() # 随机游动构造模拟序列,cumsum表示累加' f$ E) K9 M a7 N8 {
s = pd.Series(data,index=idx)
1 {- P+ ^6 \4 M7 S7 q# |2 fs.head()
1 L( q/ u- i) N5 ?9 I" H$ q! ?Out[106]:
( l& R D I( i4 u2020-01-01 -1
5 w8 ]6 }, k' @2020-01-02 -2
' L; W2 r" n- \! i- I2020-01-03 -1
6 W3 P% `" V' S/ S0 M' r2020-01-06 -1
: r# `6 |0 h- I0 l" w2020-01-07 -2& f5 d9 I8 x; @& j4 o
Freq: B, dtype: int32
8 c. M. ]7 p' H& E6 zr = s.rolling('30D')# rolling可以指定freq或者offset对象! i1 [1 f2 }; d3 O! [
6 x! N7 a6 e: @" d1 g! q; b% p
plt.plot(s) # 蓝色线
% C* O: R; I- ?( Y: ]4 sOut[108]: [<matplotlib.lines.Line2D at 0x2116d887eb0>]1 i0 R- J) V$ G3 J ~& y4 h6 x8 L
plt.title('BOLL LINES'), q+ Q* P6 k7 K. ]
Out[109]: Text(0.5, 1.0, 'BOLL LINES')
% b+ V4 t# M! u$ Y. R/ y; B/ R/ Z+ d" S3 W* N7 W4 P
plt.plot(r.mean()) #橙色线6 C4 u3 j' Q# O" J- {2 i# O* a
Out[110]: [<matplotlib.lines.Line2D at 0x2116d8eeb80>]3 R- l) Y/ m" _; K$ m
7 l) G6 b& _ C, M: x: ]5 X
plt.plot(r.mean()+r.std()*2) # 绿色线
/ H8 j c# C' a* X [- \, gOut[111]: [<matplotlib.lines.Line2D at 0x2116d87efa0>]
( w" [) _+ F9 }9 l: Y% I; x# ]' J: K6 a2 s; T
plt.plot(r.mean()-r.std()*2) # 红色线
3 V! X! V& @# s9 Q9 jOut[112]: [<matplotlib.lines.Line2D at 0x2116d90d2e0>]
# z3 V$ L& U2 D: R- E& U- w% c* ?
7 b8 e0 \$ [- K& g1
! Q) X* Y/ j* P s5 _0 ]/ u2- _4 u# k( h* g# C( _
3
2 B9 b5 |( f8 V6 ^1 ]$ Q44 t+ C! N' j% m [. E+ @+ I3 ?+ @9 d
5, s! f. w: A3 O5 Y+ ]6 w" s l
62 P) b4 _, m4 n' h0 j' Y
73 i( `8 v* h; z0 A8 @
8
# w: p- a" G! q: T6 U9
6 S: b' g% q1 l, L* `10
8 R' @3 d1 m$ A* y115 s/ H: C3 B* l9 H) B
12
3 P& l; `+ t8 S7 Z13
; o5 W5 g: P c% Q* N% l14
; S* C8 k) ^) K2 F7 `15
0 V7 |" v( K/ a- Y& C- s3 i16, _. G* _ n9 i! s4 K& p0 Q
17
5 M4 P/ p E, s p6 k& S18! S1 R0 L" X( y" A- w1 L
19
! Z( T# Q; F r1 _1 h& r20) ?. \' ~- I, E" u9 W0 ]4 G
21: z( q3 l6 ]2 H% [9 e; G9 a
22" d3 t( I1 q2 R' g6 N( J+ P% D7 H9 y
23
6 k8 S0 |4 E3 j' X$ a24
, l# r! o0 Z- F, c) G, b7 t25
o) t3 j9 I+ C( O8 r* }9 t26
; c2 Y$ V, o# A ~5 C27
1 y ?3 z( A7 }7 h6 |1 Y: p28
& o' e! O* c! x* @9 D29; C- e6 x( f! l0 S
' k& E/ J1 Y' L1 c. J- @2 o2 p0 X
这里需要注意的是,pandas没有实现非固定采样频率的时间序列滑窗,及此时无法通过传入freq字段来得到滑窗结果。例如统计近7个工作日的交易总额。此时可以通过传入多个函数的组合来实现此功能。
5 }2 F$ q l$ J 首先选出所有工作日,接着用普通滑窗进行7日滑窗加和,最后用reindex()恢复索引,对于双休日使用前一个工作日的结果进行填充。
$ I; N: |+ s! P, m/ R
/ I' o! @! R( Z" iselect_bday=s[~s.index.to_series().dt.dayofweek.isin([5,6])]
' I1 _" R# E2 `8 t/ s& zbday_sum=select_bday.rolling(7,min_periods=1).sum()
# B% ~6 X O* v# A+ d8 V$ }result=bday_sum.reindex().ffill()
% q O5 x1 w' t7 L- tresult
@" O' K$ _; H) E4 P1 g/ G; _4 N6 \$ a
2020-01-01 -1.0
1 r) ~* Z$ f) g4 R( T! o2 S! V" O2020-01-02 -3.0: U' m! o) \0 O' `/ c
2020-01-03 -4.0
g- k) _* ?# _$ f4 }3 I2020-01-06 -5.0- u# t. j/ R# r* k3 v+ q0 p
2020-01-07 -7.0: I( H* C: A5 q3 s
... ( F! o! r% B. c ?& S0 P0 C8 w9 c
2020-12-25 136.0! k7 o6 S5 x8 p6 w
2020-12-28 133.0
8 V0 \+ k, T3 x+ t8 E% P! K1 X) f2020-12-29 131.0' L Q2 @4 Y' d1 C7 S9 x
2020-12-30 130.0/ u9 Q- f' ^4 y' X {. ^
2020-12-31 128.0" f( K. p& ~9 ^9 j, }7 `
Freq: B, Length: 262, dtype: float647 l- V$ n1 U; ]: C) Z" a u
+ {- x0 C3 R' d, I- V
1
$ D& ?- ~7 r. @! K2: S; y3 Q8 b8 e" `. ^, n; W
3
0 [, K, R6 X+ j: m45 c9 L9 b1 \1 l* G" i
5
% Y1 {- N1 J- \' x6
8 _7 M" o5 S7 P) n( h7
4 u8 V$ e2 g( q6 i6 R8! b' | M6 B& l9 w
9
* F2 u! W, C1 ?10
0 k/ V+ n5 j# `6 Q11
$ F& N6 g8 d' R7 {12& _) n' y2 R! B$ ]! N
138 r) {; r! w- c# [
145 K5 ]% U: S1 J5 J
15
- S/ U" \0 r# W16
6 D% j# ~6 R- Q& L! s/ }+ w M+ z17
. t# i; o, j9 q# j% \- W shift, diff, pct_change 是一组类滑窗函数,它们的公共参数为 periods=n ,默认为1,分别表示取向前第 n 个元素的值、与向前第 n 个元素做差(与 Numpy 中不同,后者表示 n 阶差分)、与向前第 n 个元素相比计算增长率。这里的 n 可以为负,表示反方向的类似操作。
7 Y- I# |: W+ d2 X( W4 z: f" p- h. ]& m- q' A
对于shift函数而言,作用在datetime64为索引(不是value)的序列上时,可以指定freq单位进行滑动:
& i0 N+ U2 r% U
3 U* K3 ?1 F& x/ j& Z8 is.shift(freq='50D').head()3 e; |) T# a E" G
Out[113]: ! H' c& ` R: U& t+ t1 |
2020-02-20 -13 N; h' R# e! P" E% L$ d
2020-02-21 -25 f& R1 ~$ ?; E4 N% ^* \
2020-02-22 -15 F& l0 d5 |4 o* P& }; S, P
2020-02-25 -17 p* Q4 n, ?) T3 m; x8 ]
2020-02-26 -26 i" g% e6 `+ J9 o) q* L+ @
dtype: int32/ w3 G3 n% x# x6 W: k* M: k( z
1* w6 u" q8 O( G7 o+ R
2: _2 [# F/ z/ \9 q
37 A0 R8 v/ R- o
4$ @3 I4 E: h' g
5& b% n: V) E( P. Z1 y! W( D
6
6 F; S* d$ W: |& Z Y4 Q% `7
* T2 K5 W- F& [+ u8
/ D/ S; B( e2 f; q9 i, B 另外,datetime64[ns]的序列进行diff(前后做差)后就能够得到timedelta64[ns]的序列,这能够使用户方便地观察有序时间序列的间隔:/ @9 s% _5 i5 s& R+ R
+ n8 o# U; W/ t; o+ h0 V* J
my_series = pd.Series(s.index)
5 J7 i7 f) \4 x% Rmy_series.head(): h. Y0 v: a, F7 w3 r& n" D
Out[115]:
$ L2 w* s% s8 C8 ~0 2020-01-01. S; y3 \8 b4 g8 c5 A5 `$ a
1 2020-01-02: g9 I, h; p* H; ?
2 2020-01-03( H C- g, \2 g
3 2020-01-06$ [, n0 j6 E1 g5 L
4 2020-01-07
! `# b B$ E9 k [; idtype: datetime64[ns]
k) `% T [5 ^. t
1 W" t& T P* Q& Smy_series.diff(1).head()4 `+ }) m2 I3 x
Out[116]:
* L. D1 I5 C8 |" |: B0 NaT! @/ C7 a3 l8 N T) i- \
1 1 days7 r1 r6 H- Y8 \8 H: m3 Y& C* C
2 1 days/ n5 m3 {$ J2 t* B
3 3 days
$ C0 v& K5 i) H4 1 days
/ y& }! x! W$ |/ n9 Adtype: timedelta64[ns]
+ ~, o; X. E+ B8 F( h
. g& D3 x& I) g$ o. z/ D+ I! W11 @" \. s* f! \+ N' l
21 b. k# k8 f4 ?2 [4 ]; i. Y
31 }7 u* W) \9 ?& Z0 a. b, f
4
- S1 t4 l8 ^% L5. P9 q5 _1 P! ^, g' j
6
2 ~& }2 U# ?& k8 a5 C7 U7
! `, F U H* v$ ]: |& ^9 t82 K0 [2 s- z/ I# E) t
90 u' f" ^; L" O& H# @9 I2 p" q9 H
10
! K% s: r; \6 a0 L3 @$ E2 I9 q6 p11
3 |: _( w) o2 I2 @6 L! T12
& b8 a# f( ~7 j1 H$ i. b& g# v3 |; p8 ~138 E' t3 W, A+ u6 D' D5 S
144 |3 a. t/ M8 |+ s
153 W- Z/ r' F j5 C2 R
16
/ R# B& @( N5 R, Y2 h3 m17
) M! x# s8 ~. h& ]" t" d$ a18
; U+ t9 H9 Y- e' Y+ z10.5.2 重采样2 y! M% W5 b! `8 B( n& S# s
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)
# U/ s6 Y# s& d& x/ p& E P( |" _常用参数有:
' b( M6 k8 j, h4 p
# w% e# w0 D, H* ?' R7 \5 Krule:DateOffset, Timedelta or str类型。表示偏移量字符串或对象
7 T* D+ a! V8 n( c( }( Saxis:{0 or ‘index’, 1 or ‘columns’}, default 0。使用哪个轴进行上采样或下采样! r, W& W* t. o7 B0 J" Z
closed:{‘right’, ‘left’},默认None。表示bin 区间的哪一侧是闭合的。所有offsets的默认值为“left”,但“M”、“A”、“Q”、“BM”、“BA”、“BQ”和“W”均默认为“right”。
, H5 H. n+ i/ l W7 A N7 c; {! E5 Vlabel:{‘right’, ‘left’}, 默认 None。hich bin edge label to label bucket with,所有offsets的默认值为“left”,但“M”、“A”、“Q”、“BM”、“BA”、“BQ”和“W”均默认为“right”。
. e0 r" n8 H& gconvention{:‘start’, ‘end’, ‘s’, ‘e’}, default ‘start’。仅针对 PeriodIndex,控制是使用rule的开始还是结尾。
7 e+ d: t$ n9 a/ z- X# i: X" r1 Eon:字符串类型,可选。对于 DataFrame,使用列而不是索引进行重采样。列必须类似于日期时间。
2 K8 p& [ }: m# f7 h' \+ H2 ]level:str 或 int,可选表示多重索引MultiIndex的级别,这个级别的索引必须类似于日期时间。5 [, y( a5 D0 s* q
origin参数有5种取值:
( h. ?# B& i7 V& ?- _2 P) @) Q‘epoch’:从 1970-01-01开始算起
; [0 f* b; r5 A& p; Q! v, f9 B‘start’:原点是时间序列的第一个值
$ D) j# T& {9 l2 q, j! y3 H6 J‘start_day’:默认值,表示原点是时间序列第一天的午夜。
. M9 [: J. l. {# g. u'end':原点是时间序列的最后一个值(1.3.0版本才有)7 N ]0 _. F. p, {8 t4 p- j
‘end_day’:原点是序列最后一天的午夜(1.3.0版本才有)
# [2 G( s' k: V4 Woffset:Timedelta 或 str,默认为 None,表示对时间原点的偏移量,很有用。
$ z* b. e+ @8 J% D) \8 t5 C closed和计算有关,label和显示有关,closed才有开闭。
: N: i, M7 i" }+ Y2 y label指这个区间值算出来了,索引放区间的左端点还是右端点,closed是指算的时候左端点或右端点是不是包含。
3 |0 c# C' ]/ Q6 q( Z6 s/ I% ^2 J+ q+ B4 g4 _6 O0 p" Y
重采样对象resample和第四章中分组对象groupby的用法类似,resample是针对时间序列的分组计算而设计的分组对象。例如,对上面的序列计算每10天的均值:9 C( H5 v& }% D1 ^: Y
s.resample('10D').mean().head()8 N# r. U2 U% J' U
Out[117]:
' f5 U0 J' H8 t# K% @* L D2020-01-01 -2.0000009 f$ _& `7 \& j3 |2 |% E" \
2020-01-11 -3.166667
( A) P, q/ P/ Q% i2020-01-21 -3.625000
5 ^ ]2 g$ i' e1 [( l* r# g2020-01-31 -4.000000
9 \, q. a/ ] C2020-02-10 -0.375000
) q, T( f+ C8 G5 mFreq: 10D, dtype: float64
" w* @" k8 i+ q7 X* U$ J1' b* s: d# n5 Q" r0 M4 y' j
2& \1 @- [# X6 L: i. Q; Z* G0 M
3% G4 v" N4 r2 m# |! t8 u
4
1 S! f; y5 ?4 N) _6 e1 D! b( y5
) U- S9 V; m% ]' c0 J% Q0 |. X. G& ^6" \7 G# u+ g1 e3 k& V4 M g1 h
7
) H+ W. F& y$ w M5 Q8
3 L$ W3 L8 U- d- ]" m1 [! s n可以通过apply方法自定义处理函数:- k& P: S U: y9 y" c
s.resample('10D').apply(lambda x:x.max()-x.min()).head() # 极差; p# c }! W* _/ `
2 \6 o! \ I' [2 c$ SOut[118]: / s! K& _3 ~0 e8 _- q
2020-01-01 3* W3 W* K% X- b3 f
2020-01-11 4
: J* }+ h) A) |- ^' i3 b2020-01-21 4; z1 i1 S" H$ D9 T( `
2020-01-31 2; q5 I6 W! H8 X' ]% D
2020-02-10 4
6 I0 `, T+ C2 B, MFreq: 10D, dtype: int32
: U; q- c1 W4 E$ V d, N0 B" {1) s+ D' u. a4 U6 q3 c9 i
2
1 C7 m6 Y3 f* q9 B6 z: L3. I# C/ L( p ? n7 f
4/ p7 p9 R- G) F0 C! Z
5
7 K$ W. K0 d) W" j61 f! j$ R+ G# u' @/ k
7
3 H6 U+ {* J. q& v7 `8/ J& ? C u& ^, \
9
0 B4 t+ E0 z: Q" V3 I) ?& v. H8 t1 `7 ~ 在resample中要特别注意组边界值的处理情况,默认情况下起始值的计算方法是从最小值时间戳对应日期的午夜00:00:00开始增加freq,直到不超过该最小时间戳的最大时间戳,由此对应的时间戳为起始值,然后每次累加freq参数作为分割结点进行分组,区间情况为左闭右开。下面构造一个不均匀的例子:
. x3 x. [ l+ \2 D9 ^: O, i
3 j- o2 O+ ~7 z8 h) I8 F) |! gidx = pd.date_range('20200101 8:26:35', '20200101 9:31:58', freq='77s')
7 ~$ O( u2 ?' T- K. ^data = np.random.randint(-1,2,len(idx)).cumsum()) A7 s# j5 L E, D& v
s = pd.Series(data,index=idx)
! O; y3 o( M( h) C$ Ys.head()$ j1 A- v* ]+ I# U3 k6 y5 W
( D4 R/ K3 Y" H# r& G" k8 [% nOut[122]:
% i3 s+ b- H. D+ `( P1 l: A2020-01-01 08:26:35 -11 y. J) g. k. t: d6 T; g6 H
2020-01-01 08:27:52 -1$ f. b% d1 i4 s, G% \ A. f- j
2020-01-01 08:29:09 -23 a% [: H( a4 M* B
2020-01-01 08:30:26 -3
, x* ~5 [- i& j0 Z, h& _2020-01-01 08:31:43 -40 | T+ e N; i6 j
Freq: 77S, dtype: int32
6 @2 S1 h8 m q8 T! l1
5 j4 t' x; R9 I0 `7 f27 n; c0 [; V( Q
3
; v! U4 x; G1 H) Z" f' s4( X0 m, G, r' S# f$ Y
59 R' ]; _, Q$ n2 K
66 J0 \9 \ X0 ?( w, }- V) k) f
7: E9 H8 z+ a& \. x' l5 c, j
8
& q6 D7 a+ d# u) M/ G9 v9
5 K% z9 a1 m+ H& O& y4 f j10- B) r, x: z2 v/ E4 J
11
* C7 Y. L' g( l0 } G12
' Y5 Z" @- o) B- u 下面对应的第一个组起始值为08:24:00,其是从当天0点增加72个freq=7 min得到的,如果再增加一个freq则超出了序列的最小时间戳08:26:35:
2 @ ^) f) V% w4 `
& U4 @, h: Z% Ds.resample('7min').mean().head()5 Z% B; \$ ?0 \' O) ]2 U2 R
Out[123]:
* E' h( x( z e* ^9 H% ^) H2020-01-01 08:24:00 -1.750000 # 起始值,终点值包含最后一个值. w6 T# `. B1 [: ^2 d/ @4 z
2020-01-01 08:31:00 -2.600000
# u, R# O% P; h8 }+ E2020-01-01 08:38:00 -2.166667
4 a& n9 p4 C! F$ V4 D M2020-01-01 08:45:00 0.200000; O8 [# e! n# X4 O3 V
2020-01-01 08:52:00 2.8333337 v3 s3 K+ R9 W6 f
Freq: 7T, dtype: float64& j9 w3 H+ r# P1 F0 z+ j' i
1, g8 m2 V9 s( y: W: F
2, S. |. U' _, T, p* ?
3
) _7 U8 P6 J' N, H& i42 S- o' c) F+ r# W
5
6 L% Z ?3 k! H' e5 j' }6
; ^+ M5 P' P1 K1 A T7# g0 I ] ?4 g# ^) S
8! e- { c4 `2 A0 E% T2 {
有时候,用户希望从序列的最小时间戳开始依次增加freq进行分组,此时可以指定origin参数为start:
' h" V, c/ ^( i8 b$ ]7 }! I
1 ]% r2 O, x3 {s.resample('7min', origin='start').mean().head()4 N/ v. }& b) s: @) X
Out[124]: ) a0 X3 V `: p
2020-01-01 08:26:35 -2.3333335 M. N2 i! X6 I/ w4 a
2020-01-01 08:33:35 -2.4000000 t9 H" ?0 m! Z ^$ e: u
2020-01-01 08:40:35 -1.333333/ D* K `: F* E1 e# w2 @
2020-01-01 08:47:35 1.2000003 S) Y, R0 f8 \/ ~1 o( R
2020-01-01 08:54:35 3.166667
% g+ s. s" X; S3 u" H" W0 U* UFreq: 7T, dtype: float64
' H( ]1 S0 V1 @* q( x; _ _1$ \6 O: x* S6 h Z
2# c! e" E* z2 G$ ]: E
3
4 Z* h/ o2 \. W8 I' Q9 g44 g4 |2 L& `7 v r& _/ U
5; B; d6 x E; E! D
6
0 j% E& Z2 K6 I2 h- h& T# ?; k7
1 r! X" U# z' P. }9 f& C% O8
9 R! U. ~4 z( }9 L$ q; x 在返回值中,要注意索引一般是取组的第一个时间戳,但M, A, Q, BM, BA, BQ, W这七个是取对应区间的最后一个时间戳。如果想要得到正常索引,用’MS’就行。" O, D! r% `+ }' {6 [# h
V' W/ _. a3 c: P @s = pd.Series(np.random.randint(2,size=366),( w/ m& H# O" s4 M
index=pd.date_range('2020-01-01',. i/ G3 l% `0 ~* n: o( d
'2020-12-31'))6 { Y8 J- @4 B
+ ?$ }; r" a- ~3 I w' Z& e7 S8 c' |
s.resample('M').mean().head()2 {& h4 P& A- y S3 k
Out[126]:
# l3 }* j4 F& T, C0 x2 g i' q2020-01-31 0.451613
9 P- h; C S0 e2020-02-29 0.448276& g" r% A- o+ t8 b. S0 k
2020-03-31 0.516129' b/ U. Z' z8 X
2020-04-30 0.566667
( H, J( a5 N8 C0 ~7 ^2020-05-31 0.4516134 E: B7 m3 e9 U: A( L u
Freq: M, dtype: float64: ^3 Z: h# }$ @( D2 u7 E
2 m* S/ t% u: M) Bs.resample('MS').mean().head() # 结果一样,但索引是跟正常一样" y$ H( i3 h0 R# y3 n
Out[127]:
1 g5 H+ L) a" l! T/ B2020-01-01 0.451613: D- X! |+ {& L0 b9 k r; H
2020-02-01 0.448276
2 F4 ^, h% H- W4 f3 `1 z/ _; l2020-03-01 0.516129& N! R) \1 m$ L9 d* [; x6 g
2020-04-01 0.566667
% y" ]4 l0 q, B) P0 l/ |/ b7 f2020-05-01 0.451613
# b% u9 t* a2 C: Y" t UFreq: MS, dtype: float64
; B1 i4 _' H5 x# ^: Z- p% R! z d' D! g% U3 i; @' q
1$ Y0 @" L! ?+ U- j5 [( O
2" V4 J( Z$ ]: Q2 T6 @) z
3
# u2 A* ~0 i9 e3 }" @- |+ T0 N4
e2 z* ]& M& }9 l! ?" a5
4 b3 }- L+ U/ {7 t$ P62 {9 M \2 M+ h
7( V- _$ x# p9 V' K! _
8
4 F$ b( x" s& i% ^, _# ]2 I% ?9
) ]3 l& c8 O& p0 b- O6 V9 n/ ^+ g10( Z+ H; s: B( \! g* z+ o
11
- m, R9 x; Z# @. f, y/ |12
( e6 n! a9 e$ _6 b13
, O2 [9 l$ A5 V ^14
8 S6 s# j. U' i) L15
: i4 a c9 L. w+ Y. _16
5 u) N0 S- G- F q& o2 P! m" k17
7 ~" k) B5 ]4 [+ }. A186 c/ @% G+ E' f w
19* g0 u" x( j1 O: ~4 S+ g* \* e& d3 s
205 H% R. W- x5 p. r8 V8 p! c
21* U+ q8 \3 W6 e) ~
22
& q' Q, n* ]5 }对于 DataFrame 对象,关键字 on 可用于指定列而不是索引以进行重采样:& J! {$ y, e/ G: v. L- E2 ~1 ]
d = {'price': [10, 11, 9, 13, 14, 18, 17, 19],. H& v0 E4 [5 N& C# w+ W2 r$ m7 s
'volume': [50, 60, 40, 100, 50, 100, 40, 50]}
: b! r. X) o* v3 E/ xdf = pd.DataFrame(d)
1 u: n, J# {7 Z+ U5 hdf['week_starting'] = pd.date_range('01/01/2018',: q! @7 |$ X, _$ ~! ~9 U
periods=8,7 O% Q* F% |" ?9 @0 j
freq='W')
( Q0 H/ V2 i. D: f$ c2 Vdf4 K) j; y1 e' }5 V2 }, U
price volume week_starting
" y3 S. d) e: G$ H" X0 10 50 2018-01-07
$ Y7 v' o& a9 ]2 E* a1 11 60 2018-01-14
% [$ C" c. o% O! U# q+ E% y# d7 I2 9 40 2018-01-21* {. Q! j3 J+ _+ x' I9 R7 E
3 13 100 2018-01-28
) I6 ?2 ?1 _' }8 V% h4 14 50 2018-02-04& @9 B" c) O) L
5 18 100 2018-02-11' d/ y5 S% H7 @
6 17 40 2018-02-18
: w& e L! X6 \+ I5 W/ t* Y* b7 19 50 2018-02-25, O3 s; Y9 r" j" |( Y
df.resample('M', on='week_starting').mean()/ ]* t$ g" }* r- W- Z) @ U4 {
price volume- k3 o/ A$ p& G2 v$ X( f' p& g8 b
week_starting K# w0 q0 u1 H5 n
2018-01-31 10.75 62.5. R* e) d' A. y
2018-02-28 17.00 60.0" s# c1 P, p9 k* Z6 p) w8 r
' o z( p) c7 ^# m
1
0 Z6 I8 k" p# T( l% m" }) s8 ]2
2 y6 y" R+ ?5 A( q3
+ B2 o$ p! r& f/ U" U$ r4
- ^6 A9 ^7 ~5 ^: B5
& i& ]/ v" f# J6
0 }. T5 |8 p: A, E6 b2 U" s5 D7
8 {1 O, L7 U1 e: r; z8
- T( X E3 y4 T& [; R9) l/ m( P2 \ W
10
6 B! B, m8 Q; a1 g0 ]; e11- {3 _$ q/ C4 i
12
+ \& ` Q* s! ?) m, n, Z7 U/ u" b$ l13
2 B+ i9 T |5 m4 @1 z14% C7 B. Z0 s" B0 I
15% O6 p3 _9 d! C8 W3 ]2 O
16
& y ]- a; D2 s, O17
9 J( C- b. B$ M0 D1 s! i18& L) \! `; W2 p* n8 L- @1 ^
19& k1 E) o9 f, d9 N& _3 y' X: n
20
! I. s0 S! G; @; n21# n8 |7 k( R. W+ T2 r* e$ f
对于具有 MultiIndex 的 DataFrame,关键字 level 可用于指定需要在哪个级别进行重采样。
7 K# ]# N) S1 x+ T/ E* B6 j, }( edays = pd.date_range('1/1/2000', periods=4, freq='D')
* a4 G$ [ a+ r5 Od2 = {'price': [10, 11, 9, 13, 14, 18, 17, 19],
2 k$ F- g* T: Y5 P; q 'volume': [50, 60, 40, 100, 50, 100, 40, 50]}0 q& J0 w2 X, A
df2 = pd.DataFrame(8 K8 T1 B$ E9 f1 d
d2,5 N/ ?4 q6 l7 | ^) X( f. v1 Z
index=pd.MultiIndex.from_product(+ s# e, n# i: U3 ^ q9 w. m
[days, ['morning', 'afternoon']]
, Y+ U0 \+ [ w9 V4 L1 b+ m0 o )
! o) j( X! P& D% a) j* @)* \. X0 T4 o* s4 U2 m
df2# \% L }9 c/ p& f" o! u4 E
price volume
( z% h& q* H. u( r. v' Q2000-01-01 morning 10 50
4 v0 }! C7 Q+ J( }( s# Y afternoon 11 60
( Q4 a5 I8 p# E& n% S8 `2000-01-02 morning 9 40
+ `% x3 x8 o) k afternoon 13 100
5 A9 ]+ B _8 w6 {% M; k) A) X2000-01-03 morning 14 50" h) w% W( a# Y) E
afternoon 18 1007 [8 O/ ]8 k5 L" I b! W% @2 L6 B
2000-01-04 morning 17 40" }: @5 l% J, ~
afternoon 19 50
9 _) ^, I$ Z' q& P$ R% j) Idf2.resample('D', level=0).sum()- e8 I8 R& r% |
price volume
9 s z4 h( d5 c2000-01-01 21 110$ Q4 R" o+ e4 O) ]
2000-01-02 22 1400 i8 n8 } Y! c% K6 B8 [$ a
2000-01-03 32 1502 N+ v, \: K, s" H* i* r+ K/ G2 S
2000-01-04 36 90! T: L! w5 _; h
. H7 i) z$ T7 B" s ~2 w& I7 ]/ e1
, A/ p3 D+ J2 V& x' j: b( w$ s25 ^" F& S1 { M# m. l$ F0 \: k
3
* C0 _2 m' R3 u9 D+ ?44 q: x Z3 P7 d
5
7 S& E; t' x7 D8 j ^" {) x8 Z7 d9 o$ P1 ]6
# y" G- k8 K% S7% T+ P5 N3 H& M. N: E, u0 H7 P! E
8
' }! y$ X" Q' T4 j0 q5 ?9$ f( W: v* u6 c7 U% w0 r% f
10* ~8 V* ~% l/ v8 n
11
* V: a: x: h: H9 F12
9 c0 i7 y8 b* Q/ d13
9 A. \) |: e8 N0 R140 B. q9 [. g+ a- o9 ^ n: W b
15/ ?* {: f! a' |0 L- D( b9 m
169 K" Y% o5 s7 u0 |5 u8 R
17
~3 [3 \) ]5 ^4 A, g$ R18 }# F7 `7 c/ G
19
) z. Y8 Z/ q+ M& I& C203 Q6 R5 X4 a1 Y" b2 Y
21
) V( E: e5 \2 B8 G, g222 @. I7 J' h+ O* D' P
23. G I3 U! n; M* K
24
& e" `2 C: R N J25. L6 o9 B/ y3 i9 y
根据固定时间戳调整 bin 的开始:% e" ?; ^' Q0 c: L
start, end = '2000-10-01 23:30:00', '2000-10-02 00:30:00'
V H$ G: G3 t- s5 M* arng = pd.date_range(start, end, freq='7min') z/ ?, R3 U0 z! [8 ^
ts = pd.Series(np.arange(len(rng)) * 3, index=rng)
G, ]" v: Y+ U8 L) D2 a# ats6 S7 r g" a _- M5 c
2000-10-01 23:30:00 09 z) ]1 b8 d1 J& V/ W N: b
2000-10-01 23:37:00 3- R. G$ R9 q: |0 B2 e
2000-10-01 23:44:00 68 u, ^$ u- \. m, B8 L
2000-10-01 23:51:00 9! M+ r) t2 ]& I
2000-10-01 23:58:00 12# t3 O0 k: W8 D
2000-10-02 00:05:00 156 z7 Q$ ^$ e B D# F9 i
2000-10-02 00:12:00 18) y! T' o# J. e6 G& i) \, A0 B5 A1 `
2000-10-02 00:19:00 21
/ d# g" t7 r, j/ v' \4 X2000-10-02 00:26:00 24* G" k' D9 Z( t
Freq: 7T, dtype: int644 q- U/ }6 T( b8 T& u
. z# T3 \5 a. i) L4 zts.resample('17min').sum()
: A# U( ] m, s+ g6 E- m0 w7 F2000-10-01 23:14:00 0
: b6 r, } Z( X/ t2000-10-01 23:31:00 93 @ p) J3 P3 \$ ^9 z5 a
2000-10-01 23:48:00 21
! G! K' D- {# @( r2000-10-02 00:05:00 54
5 k& O7 n) e& I# i2000-10-02 00:22:00 24
+ U$ }; Z. b Q$ e+ JFreq: 17T, dtype: int64
1 ~3 D/ }" [' }& I4 K4 {; G, D w# N" {
ts.resample('17min', origin='epoch').sum()5 Y% R' j" M4 a- X
2000-10-01 23:18:00 0
4 a4 ~" g1 U6 o5 h/ I2000-10-01 23:35:00 18
7 B7 i% y0 X5 `# K: z* {: A, Q2000-10-01 23:52:00 27
0 V0 y) Y: m) U3 B4 k" ?2000-10-02 00:09:00 39
9 q9 a, B% f# ~" T$ Z z/ y: K U2000-10-02 00:26:00 243 Y' f$ S. D. _& A$ I J
Freq: 17T, dtype: int64
$ _8 `% X) q/ o! Q5 z% Q% F2 M* ?) J) ^& w4 S& G! a0 @( c
ts.resample('17min', origin='2000-01-01').sum()
3 g' \. p2 O- i* F2 F2000-10-01 23:24:00 3
# {1 h! H8 j% T; | v. e) j2000-10-01 23:41:00 15
$ ~" r! l4 d2 N9 A& r2000-10-01 23:58:00 454 u2 k& z! d* U; f
2000-10-02 00:15:00 45
- X9 z6 _* \1 JFreq: 17T, dtype: int64+ ^$ Q; N+ A* k% n5 O/ G& L
6 z" l* h, d9 m Z: k; f
1, o* G2 z3 f3 S, D; T7 u
2
* g) _' g7 E0 E2 L31 w8 T! I! b2 V( N, `
4 m# n( ~( k- x+ Q5 r8 } p2 i
5
) g$ E- G- n7 P& m6
8 s+ k$ B2 l U' c" Y9 Q! T7
0 P" p4 t: R0 f. \5 V1 A, G/ R84 R/ v+ I, b+ u, M$ O: H
9
0 o' N3 ?# K# G( l3 S10
# B3 E4 D; ~# U3 s) K8 j6 E# [7 I- w11
6 E- ^- @; n$ ~" A X5 U# d0 S12
: X; T. y7 q/ y; n+ q13
Q2 i. r$ \0 q; D/ v1 |14
( [$ n" y) l( g( z1 A' V157 i- h4 d4 z+ d# ~1 `/ L
16
; ?6 w d; I9 c4 i* J- A170 O% X6 v& T8 R; m" O( r! x6 i
18 F. L! t/ d0 J: d) P% E7 ?; |& @5 A
19$ J3 L) ~1 a! ~4 R
207 p7 b, r6 N2 a5 E
21
) Z0 L" Z0 Q, |, T1 b# }0 w/ N22
" ?1 z2 B& d3 l: ?% J, M5 p23
% C8 J A7 K% U! o( M* i; W244 {) r) D# x/ ]% r) S' }3 u
25
/ Z! T* F" _4 [264 Q4 x( u; A. E/ l
27
! V- D. m6 }6 h. s28
, [! [- |) V* ]) W ^. T( O296 j+ u' L" M, U7 d
30' d/ i( b( N( i+ A7 a. V
31 W) T6 T: F3 f2 M, h
32
% E3 N! Z9 P7 M33
; m n' I& X' P: ?# d34, X5 S5 G" L- J! S* f# G; U
35
% _# [2 Q% u5 W& w36 R, I' T5 j/ z1 ?$ S
37
6 R* r! R% G" J( q/ ~如果要使用偏移 Timedelta 调整 bin 的开始,则以下两行是等效的:) c% D% e1 ~( L$ N" ^$ A/ Q
ts.resample('17min', origin='start').sum()
! n2 w F3 [6 y6 z" ?2 Sts.resample('17min', offset='23h30min').sum()1 e. x4 S% S( y0 w- U
2000-10-01 23:30:00 9
& w; p2 W5 a5 o# L4 J; w! x2000-10-01 23:47:00 21
2 `5 g# ]$ A) q; U2000-10-02 00:04:00 549 {$ p. ?5 X+ v! S' q& b6 {+ C
2000-10-02 00:21:00 24
& B9 W# K* f6 ?6 W a8 rFreq: 17T, dtype: int64
) r4 y5 a% ?& v( [- b( o# j1" i( a7 }, Z% d& F: s
27 C( J5 E$ F9 b$ d2 g6 B
3
8 R" e2 N9 q( U. s# E6 M4
9 D+ b1 U, a ^# d/ e9 k5, v: q6 ?3 n% @/ B, [
6
4 R$ C4 N; n/ s1 Y+ A+ h6 r7; s/ r% X; [- Q- h7 L2 H
10.6 练习
$ h- y R+ _ B9 z# F0 nEx1:太阳辐射数据集; r$ `) A7 ~1 D2 }/ V8 Z7 e% E
现有一份关于太阳辐射的数据集:# w* {) `; Y. h3 a" Z) @8 z" r
( g0 X/ C) Z5 h5 Vdf = pd.read_csv('../data/solar.csv', usecols=['Data','Time','Radiation','Temperature'])% T$ s! o- d: s+ V
df.head(3)( P9 h4 V& t. t; S
1 M( u6 l7 N# ]* kOut[129]: " ` L! A2 U8 A8 e- L% F- W& }
Data Time Radiation Temperature+ t Y9 l: ^# A5 \4 a4 C: g. | c; V: l2 Q
0 9/29/2016 12:00:00 AM 23:55:26 1.21 484 R4 M& ]: [* a4 }
1 9/29/2016 12:00:00 AM 23:50:23 1.21 48
7 L/ N4 x5 |6 S7 x4 d! r2 9/29/2016 12:00:00 AM 23:45:26 1.23 48: W4 q2 m2 z w! C. b9 h/ S* j- S. o! D
10 C- R6 R q/ @& J' ~
2% ?3 x( h$ _6 n4 ~4 W
35 L+ A6 w$ o, d0 ~3 I
4# l7 s( U' J+ s
5) z! F# S1 D% O9 r
63 K8 M5 R( ~1 T3 Q5 _
7, Z. Y r2 l2 R$ \6 l
8! I0 c& X8 O* t5 M" p
将Datetime, Time合并为一个时间列Datetime,同时把它作为索引后排序。; K+ b5 N) ~& h1 U
每条记录时间的间隔显然并不一致,请解决如下问题:
2 O+ l) L; W. H5 Z( B4 G0 o: F找出间隔时间的前三个最大值所对应的三组时间戳。8 E$ j+ s6 k d( Z# p( t# W2 N8 i& m
是否存在一个大致的范围,使得绝大多数的间隔时间都落在这个区间中?如果存在,请对此范围内的样本间隔秒数画出柱状图,设置bins=50。3 k; V, ?6 ?" L
求如下指标对应的Series:
) ?/ W- U! Q9 { N8 i温度与辐射量的6小时滑动相关系数
2 l8 K6 W* E/ F* b" v以三点、九点、十五点、二十一点为分割,该观测所在时间区间的温度均值序列
" v+ z: b8 m0 J1 p1 J每个观测6小时前的辐射量(一般而言不会恰好取到,此时取最近时间戳对应的辐射量)3 T' M6 T- r) @ I
import numpy as np
# N. n% k) U: Y$ X$ T* Gimport pandas as pd( _( H# X* s6 Z4 q( s
1
+ ^( c' h) Y; M4 Z. e9 `1 c2. @8 ]8 e) K" U( I8 O
将Datetime, Time合并为一个时间列Datetime,同时把它作为索引后排序。" o% o& |6 J% x* a( p8 M) }6 M! ~$ O
data=pd.to_datetime(df.Data) # 本身是object对象,要先转为时间序列
' g% ^4 L& M9 ytimes=pd.to_timedelta(df.Time)( p2 a/ S) o( F3 I8 a3 n
df.Data=data+times& L! @7 ~9 {. w6 x% h
del df['Time']
. G- z0 O0 L# q; Xdf=df.set_index('Data').sort_index() # 如果写的是set_index(df.Data),那么Data作为索引之外,这个列还另外保留4 A9 D/ b7 p# m* ]
df- K4 @% c' y1 u$ t9 b7 _! n, X* ^
Radiation Temperature
$ G( n2 i' I S5 WData
3 y+ ^$ Z2 x/ {! R9 O, y9 x0 b4 y2016-09-01 00:00:08 2.58 51
! y3 S+ U$ v* t: p+ J8 p( J" @2016-09-01 00:05:10 2.83 51
7 Q0 g p" T, w2016-09-01 00:20:06 2.16 51
8 t+ G9 O% R- Y: K+ ]* M* m2016-09-01 00:25:05 2.21 51
1 |2 S- T: g! K- D! E2016-09-01 00:30:09 2.25 51! `$ y" C3 N8 F1 _" }1 n1 s" `- Q
... ... ...3 r" w& E2 f. V2 r
2016-12-31 23:35:02 1.22 41
/ I: M; K" O4 c2 O1 I2016-12-31 23:40:01 1.21 41( Q$ i) @$ o" Y8 z1 c
2016-12-31 23:45:04 1.21 42
. w# a4 | L" v+ d' H2016-12-31 23:50:03 1.19 41/ @5 K( Y; x# q: E
2016-12-31 23:55:01 1.21 41/ R4 Y( V3 v) _- o, i
' h+ N2 j5 v D5 b# @1- h% X }6 |+ `8 C$ [ v$ H
2+ E( T& ^$ N% z3 R; l: Q$ _ C
3. l! A& S/ N8 U# e7 h+ ~0 Y" u
4
; U$ H; r7 R3 x7 H3 |5
) N' x2 e5 b1 |( r# C6$ ]; V: F1 L8 u/ m1 _0 P3 \
7
5 l( I# N( w6 |8 f5 u8) ~2 Z* w i1 e# |3 A. o$ S
9
* r: l0 [* Q% v( N8 e% g: E10
& G* b+ R, c7 t$ L2 n, i119 a7 }/ T( V+ V G) B+ }
12
6 G6 U* h& i, Y0 ^+ W+ j13, [5 X* G$ p4 w. W9 ^
143 D+ f1 P5 `/ K, v/ R
15
# Q* x' R2 }: X- I. R167 r9 n; `8 Z3 l2 S1 i9 t8 v( f
17; K# D& K: r9 z2 e5 Y$ J
189 a: N0 a+ g8 @& G$ F. u1 I
19
+ x. a9 `6 T a# L: O每条记录时间的间隔显然并不一致,请解决如下问题:
?. l' `+ h) r* n找出间隔时间的前三个最大值所对应的三组时间戳。( L5 H2 ]+ ~5 r9 t4 b% d
# 第一次做错了,不是找三组时间戳
/ W- H9 R/ E& E7 g1 @: w+ @. h, pidxmax3=pd.Series(df.index).diff(1).sort_values(ascending=False).index[:3]0 v4 v, b H* [( r0 e
df.reset_index().Data[idxmax3,idxmax3-1]7 J6 W0 g5 c: z4 L6 l: i8 K# o3 u/ h
; v& f# B m7 T3 J1 x+ @: K, ]
25923 2016-12-08 11:10:42. W2 s% N! ~0 k6 r
24522 2016-12-01 00:00:02
% h% Z- C G# c- a$ ~7 v8 v7417 2016-10-01 00:00:192 y+ c- } r" m' m( \
Name: Data, dtype: datetime64[ns]
1 ?6 B' ~3 S" S( h9 d: w+ u: z1
: v% G, V/ L1 v! v) U2
0 m% D: z! V6 h( J' B" ~( R$ y3
$ R; e. E' o( H4; G' b0 @2 |$ h# h' r- ?6 |
5, h) P4 r' g( N4 i
6' E# j- h$ ]% x& k
7
3 N6 F( I+ j* N0 M( C+ M8) P" q! K6 W% u9 x, w
idxmax3=pd.Series(df.index).diff(1).sort_values(ascending=False).index[:3]+ t! ]9 \7 H- X9 `* r
list(zip(df.reset_index().Data[idxmax3],df.reset_index().Data[idxmax3-1]))
4 _; v/ V7 I2 h# U$ `% Z' l- t* W* A) B o2 }
[(Timestamp('2016-12-08 11:10:42'), Timestamp('2016-12-05 20:45:53')),
1 a6 a* y; B# @& l. T- D! X% J (Timestamp('2016-12-01 00:00:02'), Timestamp('2016-11-29 19:05:02')),
/ o G2 e# R4 T) |" Q" s (Timestamp('2016-10-01 00:00:19'), Timestamp('2016-09-29 23:55:26'))]6 Z: l% I! Y$ T O; D( }
1; u j7 D2 @% M; S% j
2
1 q: q; S( h9 h# b$ {3, @; ^: h7 b8 m; m* u2 I
4
/ R1 r+ G( F9 L+ a4 Q$ R9 B7 U. N5. f. C% M* P# x' B
6
4 U1 O( p) C- H# H* g参考答案:
7 U' |/ ^. \5 z* i4 X/ s
' s% a$ k, q, w# f8 j& zs = df.index.to_series().reset_index(drop=True).diff().dt.total_seconds()- ?0 z( w" d% U _5 c
max_3 = s.nlargest(3).index
$ Y- [4 m- _" G/ |; vdf.index[max_3.union(max_3-1)]
, Y) h) k7 y; J! A2 h
8 x5 J$ d" A4 Q: iOut[215]:
8 k5 y0 W: v6 h: rDatetimeIndex(['2016-09-29 23:55:26', '2016-10-01 00:00:19',
. a: p) t" J5 T. \5 U+ E7 Z# o '2016-11-29 19:05:02', '2016-12-01 00:00:02',* S6 q" c* u: N/ n4 z' f" H
'2016-12-05 20:45:53', '2016-12-08 11:10:42'],
! B- G9 f3 b; ^+ w dtype='datetime64[ns]', name='Datetime', freq=None)6 Q, J7 u5 J6 n o6 J
12 ^7 F9 _$ t: m, L1 W7 V0 e1 ^
2
) L" W, H0 p# k8 p3
& ^. n& @8 Q I: n. h; @2 o' N% Z4: D' E' ?4 g/ S& q& D4 q
5
1 [: X a2 s, L2 Y3 K6
% {* V) i4 [8 G7 `" m+ X7- z# N+ a9 m' Q6 P
84 J8 r1 s: k7 H7 _) ~
9: R* x; X: O# b
是否存在一个大致的范围,使得绝大多数的间隔时间都落在这个区间中?如果存在,请对此范围内的样本间隔秒数画出柱状图,设置bins=50。: M, N; R2 ?( b
# 将df的indexydiff做差,转为秒数后排序。再求几个分位数确定取值区间
" g, J" R7 d6 L, P z- F" Hs=pd.Series(df.index).diff(1).dt.total_seconds().sort_values(ascending=False)0 `& E; ?4 ]4 m, K
s.quantile(0.9),s.quantile(0.95),s.quantile(0.99),s.quantile(0.01),s.quantile(0.03),s.quantile(0.05)
1 |$ f. n# O8 B# ~5 w) g. W; J/ n' n( S( U8 ~
(304.0, 309.0, 337.15999999999985, 285.0, 290.0, 292.0)9 x4 J0 _4 F- }! M0 N" Y
1! F0 T2 y! z" |& g! S
2
5 E7 K* l' y$ b7 \! |4 Q36 a% Z7 b- s. v
4
, X( n: v/ v) b0 {& g6 r. f5
" ~/ d9 f; w$ i$ ^%pylab inline( ]! i0 a# z2 H
_ = plt.hist(ss[(s.values<337)&(s.values>285)],bins=50)
8 k% L- M! U9 Z% W' L, kplt.xlabel(' Timedelta')+ Z4 v, H9 e( W: S
plt.title(" Timedelta of solar")
7 F2 X9 s, q( o5 n0 d1 A% m- b, b1
- ]8 n4 F* {$ K- K# }1 A6 O2
B3 O W0 I. q- O0 T& U3* J3 \- p+ O- h1 ?8 j, b* Q
4
! u5 y3 [4 s. B2 P6 Y) l% A% z$ m; {! C" h3 x7 ]& ~
; N& B# s2 C- B% S求如下指标对应的Series:7 ~7 B; p9 Y+ W
温度与辐射量的6小时滑动相关系数
, ?3 C0 |# k4 s以三点、九点、十五点、二十一点为分割,该观测所在时间区间的温度均值序列 C6 f: }; j1 G# H6 y5 A. R
每个观测6小时前的辐射量(一般而言不会恰好取到,此时取最近时间戳对应的辐射量)9 q' Z5 a8 K1 P% x6 I. L8 u
df.Radiation.rolling('6H').corr(df.Temperature).tail()4 }# f8 k- G( p" [9 o8 O' r% j
s! u2 O" e- ~9 k" q
Data
% P9 j' Z) ?6 S2016-12-31 23:35:02 0.416187
0 E6 L$ _. [5 L3 S% G8 D2016-12-31 23:40:01 0.416565
4 c0 |- e& S3 x; N4 R/ E3 Y) n2016-12-31 23:45:04 0.328574
6 x; U' @' x8 {, \, o# w2016-12-31 23:50:03 0.261883
) d2 d/ Y9 M6 _# P- z+ v8 a2 L2016-12-31 23:55:01 0.2624060 Q8 a: \+ S- V: [
dtype: float64! a. ^- i, j4 [: H
16 Y; b& e' T6 w) R, r) I
2 T- j' _+ @& K: A
3
+ h8 s- ^& t/ F2 S" ?( V+ W+ t4
4 o& w k4 H- l# d: \1 m3 x1 p5
) x; y' I+ S7 G8 f8 ?) h65 ~2 w" y: F4 y$ ?. u
7% @, ~% G8 K# }- a @3 u1 ~
8# j2 g2 f7 n4 }! |* \
97 }/ z3 \' ]$ W3 X
df['Temperature'].resample('6H',offset='3H').mean().head()
+ Y1 A/ _3 S% W7 N# M& s
2 R* G J2 i0 mData
0 r3 u" y. U. K$ p# m2016-08-31 21:00:00 51.218750
' b, Z% O) D2 s2016-09-01 03:00:00 50.033333! R) {6 H" p* c& K7 x
2016-09-01 09:00:00 59.379310
! y! Q8 J8 m% T& l1 r2016-09-01 15:00:00 57.9843759 K# E1 e1 P& i, q1 Z4 C% c
2016-09-01 21:00:00 51.393939
: K# z8 d; p' T# ?( AFreq: 6H, Name: Temperature, dtype: float64( Z9 @- Y N/ G! E
1
6 c' b& ^5 g& z) X v5 |" L1 _# x9 i2) J' z; W) x. [" V& E
3
: q$ L9 u: K2 {4
5 [& x* Y" `; N' A- {) Q7 |6 I5
6 X' ~8 M: d6 X' n6 N: R6- u- b6 K ?8 i/ P7 t
7! ]5 k0 `% j$ b0 H7 P; l: ^3 I
87 d5 l1 u0 }* e {
99 D; }, b$ o9 a, B8 C, F& y7 h
最后一题参考答案:* K+ S, @0 r) t# _: X
0 M& v. L- W) @$ W8 y( A# 非常慢5 r% X! D2 A5 J) n
my_dt = df.index.shift(freq='-6H')/ X1 H. D" W! Y( ?+ g0 U
int_loc = [df.index.get_indexer([i], method='nearest') for i in my_dt]
: f0 ~+ C* z* mint_loc = np.array(int_loc).reshape(-1)
: v: x& M& [! U& y. V& Gres = df.Radiation.iloc[int_loc]1 e0 {( }1 P) m0 k5 t" C' }
res.index = df.index7 q0 [2 {0 p, Q% T! G: r
res.tail(3)
5 A8 o1 x" u$ T6 v# e1 b P1
) _6 C' s Y; q, z9 v5 j* Q2
! k( K( B$ R* G) c# L7 a3
3 W- v$ k$ U8 L8 S4
/ m) k0 g) F1 K: Q5
( _: n# k4 a7 Y9 `: U6
3 c% b, i- `1 G* }; W8 {72 L' ^' h4 ~+ w2 [) c
# 纸质版上介绍了merge_asof,性能差距可以达到3-4个数量级! Y; g3 v% F. ?/ Q" u
target = pd.DataFrame(3 d- s) \! k. L% b
{0 M) k X* _" |% N
"Time": df.index.shift(freq='-6H'),
+ I3 a; E+ E% U1 D1 ?, r* o# t "Datetime": df.index,! G/ O! Z/ ^+ r- v; e5 w+ {
}
) y1 s+ N1 s$ I% E5 T5 @9 z)' P/ h1 W4 n" [* ~3 o e2 I) z$ e
/ y K, H, }( n- S& b3 t S, dres = pd.merge_asof(& p" ^( Z) f3 `
target,7 G. D( ~" I! x' X8 P- t
df.reset_index().rename(columns={"Datetime": "Time"}),. y3 |- ^" [5 @, r9 o, H
left_on="Time",
7 F H+ d2 p' S" U+ O) {' Q right_on="Time",4 d" u8 t2 H2 c; I# g0 A& _
direction="nearest"
+ w1 Z+ y8 n: c, C7 s7 M).set_index("Datetime").Radiation& i7 B8 @4 K1 X# Y
0 ? N3 ^& w7 c& @res.tail(3)4 ~/ h9 y0 S% |3 [
Out[224]:
; f- q1 R" | }8 H5 } j$ vDatetime
& @1 R6 Q+ N9 t: \2016-12-31 23:45:04 9.33
, ]% Y @7 M G: f5 e* B2016-12-31 23:50:03 8.492 P$ B" ~/ e! a& f1 o% X! a% L S
2016-12-31 23:55:01 5.84: D$ O) W7 z* |3 I1 u
Name: Radiation, dtype: float64! Q2 L8 J' I3 K) v3 r, ?
& C; k/ L9 F2 M: G R
1
4 R* o1 w' N. Z7 \5 P" _3 G2$ g# X; k4 X& I" W/ w
3: B H1 a z) O6 g+ m* M; i8 f
4- @6 \* k7 |. J9 |* `
5. O+ b, r- B8 }6 R% a% V! V
6' Z4 O9 T7 K) [$ ^1 B+ E
7/ n: `! u! l; t8 q2 G
8$ c0 R( N( S+ c% _: n+ e% \9 W
9. B9 u6 R9 @# x9 o% E7 k
10
1 G# t% X4 [ I* }" O8 a11
0 x6 V; T( Q' e8 r12
) v+ {" i! T' ~& t u1 }5 y13
@) t' ]% b& I& Z7 k" X6 M5 v14
1 P8 o% ]; W6 D6 E: a) \% z15
/ ~- L( D: b( I1 K16: W2 q1 [0 N( H; F, \
176 t b% ?2 Y, U! k3 \
18
+ i% e- D9 |. H" [19( u8 }2 K3 u' a1 P/ s% X( q9 q# {
20
2 U3 D" m9 k+ h, C* |, {21& C0 c5 ^! @( ~' p+ V
22/ b- u' M9 A2 J8 z7 R
23
# S0 Q* w" Y' \+ d, O. PEx2:水果销量数据集
0 F/ z; @1 G9 L6 d2 T现有一份2019年每日水果销量记录表:- |) @! v; ~/ o, Q& L
; p; E7 t# E2 K7 E" E
df = pd.read_csv('../data/fruit.csv')
w/ P3 q( N0 i7 Jdf.head(3)% y- Z: Y( R( s3 c/ `. H5 z1 ]# d
3 ^2 w6 ^* W7 ^0 S1 OOut[131]: ( @% C7 N, h# E& L7 E- m
Date Fruit Sale
) D9 O: X2 A: a$ @7 H0 2019-04-18 Peach 153 W0 l) W2 y, C: T. k
1 2019-12-29 Peach 15
) f$ Y' ^; f+ l* e- l% w& T. R2 2019-06-05 Peach 19) x4 @5 u# y3 }/ v o
1. @& |, P. L2 C$ E( k! m! c+ T
2" D' p* C0 k& I" U
3, P: M" s; d" K; U
4; c# E# O( |: X+ n; D
5( a2 ^2 l% A' v0 z* u, W9 H
6
9 L, h/ s/ P/ n( F75 R, Q8 s2 w( ?# i4 a+ E% W3 Y
8
% k# ?# [* m4 Q2 ~3 X0 p: w统计如下指标:$ H% j, A, e3 W/ k$ x+ j9 v
每月上半月(15号及之前)与下半月葡萄销量的比值
. r. T( i3 t4 K( h每月最后一天的生梨销量总和! G$ [5 U( }' Y/ F0 D& a
每月最后一天工作日的生梨销量总和" Z7 q( K4 F( y
每月最后五天的苹果销量均值
- b$ m) {3 L. ~1 \/ c8 Q按月计算周一至周日各品种水果的平均记录条数,行索引外层为水果名称,内层为月份,列索引为星期。
6 m8 N+ D! |' r4 E- L; _按天计算向前10个工作日窗口的苹果销量均值序列,非工作日的值用上一个工作日的结果填充。; g* _1 l3 |% M
import numpy as np" E5 B2 I9 i( A: }
import pandas as pd4 v( z- e( M/ V8 ^# ?* i/ Y1 W
17 A+ \7 r, Y ?9 V W' q1 g4 P' n( h1 L
24 X; {' B' r2 `! e
统计如下指标:/ N2 F. Y, K7 M8 `7 ]
每月上半月(15号及之前)与下半月葡萄销量的比值
) _1 |$ A9 F, P/ _' B每月最后一天的生梨销量总和
( S5 o9 U5 q, a! [每月最后一天工作日的生梨销量总和* u: O0 B3 q4 o
每月最后五天的苹果销量均值
, {( Y. n# x- x# N W# 每月上半月(15号及之前)与下半月葡萄销量的比值
4 B8 b, Y2 ~" Z* J" l6 O- Rdf.Date=pd.to_datetime(df.Date)$ o2 h+ i7 Q6 s' Q' h3 D, r
sale=df.query('Fruit == "Grape"').groupby([df.Date.dt.month,df.Date.dt.day<=15])['Sale'].sum()+ W) s6 b. Q) U
sale.columns=['Month','15Dayes','Sale'] # 为啥这么改没用啊
( ^. @) w: p- [, y dsale=pd.DataFrame(sale)
9 r) m3 q% M6 H, [sale=sale.unstack(1).rename_axis(index={'Date':'Month'},5 d+ V' a& F; v" m
columns={'Date':'15Days'}).stack(1).reset_index() # unstack主要是两个索引都是Date无法直接重命名
" s, u3 I8 h4 n |; c- gsale.head() # 每个月上下半月的销量9 j- Y$ C8 p/ \8 `( e) {
, I" d) Y1 `, ~0 V6 Y Month 15Days Sale8 N" ?- d m2 V/ G" K5 R
0 1 False 105030 J# @3 Z- m" x
1 1 True 123419 m) P! E8 ]! e* A5 x$ X5 ^
2 2 False 10001* n* w& N* t. O( p+ R# A& q0 D
3 2 True 10106
; i+ B# U7 O' @0 v. S6 C; d. }4 3 False 12814
3 Z" v0 C, ?0 O: [ d9 U% R& N( m# X1 t1 t# U1 S* ]7 y0 [5 `: w
# 使用自定义聚合函数,分组后每组就上半月和下半月两个值,根据索引位置判断求比值时的分子分母顺序
: w/ c. u O' n1 t# msale.groupby(sale['Month'])['Sale'].agg(
! m# e; ?2 S( x) x: J lambda x: x.max()/x.min() if x.idxmax()>x.idxmin() else x.min()/x.max())
5 }" T s) T8 n2 E5 d& D; h9 ]' k% y9 H0 `' Q
Month0 K2 b' `$ a0 C/ o0 ?
1 1.174998
8 f6 u8 m& |2 P! j$ k; }+ ~2 1.010499- W4 c* p( Z) }8 ]
3 0.776338
& k) J& A! J& u* D: x4 1.0263457 ~" H0 O9 @) b2 Q
5 0.900534
8 g- G7 {2 U& X) {# W6 0.980136
6 N. f; Y$ G- t6 ]% j7 1.3509601 A/ P( q" Q6 ^! }# l
8 1.091584$ g- H5 S0 L4 x5 A) ], \$ _1 p
9 1.116508
5 S# R" D4 N3 n0 [10 1.020784" c4 | j* o9 c1 N4 i5 Y0 r) B7 P
11 1.275911
& x( g0 p; E1 i: J12 0.9896624 T \, V9 v$ L% J0 c/ w4 P
Name: Sale, dtype: float64! {. }2 D3 v9 U; F: \
6 }( l& s, I/ {$ k4 \5 F
1
0 A8 b8 c W% g0 K2
- x6 L0 G" b; |9 O7 i3
* |9 Z3 {; u0 J2 }( T3 W+ ]0 z4
4 f6 A- B8 e, M: L \& V5
: ~7 z# O2 ~- Y" T7 p3 m0 H% h. @6
1 }5 T4 ?( l$ M! z4 Q! g7' u/ L6 n# c; l' q; s; S. w
86 I0 R& R7 l2 X+ y
9
& a! y7 R6 ?7 A10$ y( B0 ^* m5 f
11
3 w( e8 Y( X; [3 l' y4 e12
& k" c5 ?4 p3 a3 `139 a, g; T) x6 f- O1 C
14
! R* z' A5 D1 Z% i157 h- j+ R6 C* A! }, Y' G
16+ w: T0 Z$ U% L1 [$ X6 O) Z: P
17
" h# e' R3 o: Q; d/ n i18# S9 D9 D* T0 {3 y5 N
195 b7 V) n( y' c4 |( e9 A9 r& H- X
20( O- \0 `. a' ~2 e7 |$ @0 t- _
21/ R/ Z( K0 n1 o
227 J9 \5 F J `) {7 B
23 T h" d* I+ A3 b8 o
240 ~3 S3 m+ ~% L W/ P2 C
25+ @& _+ \! I* v8 L- S! a' m
26
7 b0 I1 ?" C# ?& b: [- s27. s% o* d; `( J6 \4 l+ [
28( \6 Q. D l% }1 k1 u
29
6 H4 f2 W4 S. ?30/ X5 m Y% d$ J3 ~, s
31. n- u$ a" J$ A% r- R, i' I; h$ q
32
4 ?* F/ W: L# C. |6 m1 m339 C& m/ u3 K; ^" Z6 o, E
34/ F2 f8 m* e& Y! O' x
# 每月最后一天的生梨销量总和
" ?! h2 e& c2 U$ v: e1 \df[df.Date.dt.is_month_end].loc[df.Fruit=='Pear'].groupby('Date')['Sale'].sum()
7 ]; C& W# p3 f9 ~4 d! w9 J6 d
/ _3 A3 H: O" nDate
" m7 O- U) ?# b8 w2019-01-31 847
- b: B( E4 V* |0 X: _. j' q2019-02-28 7748 M7 c+ `& r6 v7 [! f7 U$ O& }
2019-03-31 761* J9 p/ z7 g- ]7 V" B/ L
2019-04-30 648' E" l1 k) |2 E0 h# b
2019-05-31 616. v, d4 s# u" j+ y4 d2 W
1
% m$ @' y" @* O9 X0 J! V* X' Y2' [% |0 Q+ w7 N. t( a# ~0 e
3
7 J$ H7 Y# k( B43 O* u3 u$ @# B2 \/ d
5
" Y& J1 N+ ]! U3 ~. @' E6 y* i" ~) C6 D+ l4 o% p
7
9 V" g' t8 o, \' r% o8
0 k( j. r& {) G9
: F1 v1 s! d8 ]1 @# 每月最后一天工作日的生梨销量总和
) J% b# L7 j( j2 s# c" xls=df.Date+pd.offsets.BMonthEnd()4 Q! [& q! T; C
my_filter=pd.to_datetime(ls.unique())& Q! K8 d3 G/ h, t& k
df[df.Date.isin(my_filter)].loc[df.Fruit=='Pear'].groupby('Date')['Sale'].sum()
4 I4 j$ O6 F; z$ ?5 s; s+ j1 F2 [8 i6 C# x$ S( ~0 H
Date" r" H$ @; |8 x8 ~# l3 T
2019-01-31 847
, p/ ]( R& T/ \4 P! ^2019-02-28 774
/ v: q/ V }3 r! O R, \2019-03-29 5107 W6 j* h" j) b7 I! I; g* K* Q8 N
2019-04-30 648
0 e' G" ]. k' a Z( a w' x2019-05-31 616
: l! S& C* ?) U1 P' w! w' O1/ f( q8 h( [, R
2
- j9 [. N0 a Y2 f3
: }, y1 {2 C1 d ^# Z; T {4) t. e& f1 y( t
5
0 n+ W) u0 f s0 V/ }1 S6& m5 j' ?+ H! D; x
7) P* F1 y' ]2 B1 ]* @
8$ b2 ?4 g5 b1 P! ^* @$ ?
9
9 h% s6 v- E; @* b; _! }6 b2 L10
4 B! N$ a m) @11
0 R3 R( g2 i3 j/ y: R A; [# 每月最后五天的苹果销量均值: g& g3 B. C& E' w# k6 s& L
start, end = '2019-01-01', '2019-12-31'
* Y( m1 V( V7 @& V9 Q1 ~1 N! H$ c/ h" Uend = pd.date_range(start, end, freq='M')8 \! }5 p+ Z6 G8 p+ b: |
end=end.repeat(5) # 每月最后一天的日期列表,重复5次方便做差
q" ?& z+ y( z- f; g8 K$ V+ Y6 m7 I7 T
# P8 K2 s ]" v3 k+ ]& W( T+ |td= pd.Series(pd.timedelta_range(start='0 days', periods=5),)
3 E9 A1 l. N, z$ R; ttd=pd.concat([td]*12) # 日期偏置,最后一天减去0-4天
' ]/ E$ y \' ~) [# g0 {end5=(end-td).reset_index(drop=True) # 每个月最后5天的列表
. m; V/ V( c$ J6 h( ~; D5 s( C: X- A* W* g/ ~: f/ d
apple5=df[df.Date.isin(end5)].query("Fruit == 'Apple'") # 每月最后五天苹果销量# _* j% j% F& _0 M0 C
apple5.groupby(apple5.Date.dt.month)['Sale'].mean().head()3 w- s, K! E" ~" I6 ~+ G
, d0 A, p" W! x G9 J$ d
Date
7 A+ Y: u6 z0 u1 65.313725+ h/ E$ y: V4 e" H% Y: ~
2 54.061538
. p S( D4 c; `" r- ~3 59.3255819 Q1 n& @+ T8 r
4 65.7954557 Y9 g$ e/ c0 g7 S" S/ O' i% H
5 57.465116( k! G- o6 W% v9 @$ i3 b& {. ~
+ @! ?, p# J C, @! q' I$ O/ q, f1
. U5 }- B# M; R7 m" W$ k20 p' J, M( j: }% ~& R& N* q0 t+ J9 H
3
- ?6 C, Y# n( B" _* \+ A" r" u4 l4! P$ T: J0 T9 \
5
) _; n! V' L# F: l6 e' A* o( R6. {! d" _2 o$ X5 ]
7
5 [6 U9 E" T ~; x* h0 I8
5 c. f, n& H: @$ A6 {7 q95 J) C0 D, Q q: F
10* g6 t) r" e$ v1 u! {
117 J7 y! [' u+ A) e
12
3 b/ }# m1 ^1 ?, T1 x; C13( K; d c+ M( t1 O# W/ i
14
' W' B! w5 Y, o7 |+ ]0 b15
' j* W8 C* T: S, B/ A16
6 c% M! {8 J% t9 P! {$ I. O179 ?0 `; z; B* z+ p( H* n& g
18; l4 @- J8 m4 p2 m2 k4 v) n8 Z. x
# 参考答案:" t! X6 e6 b# x; U
target_dt = df.drop_duplicates().groupby(df.Date.drop_duplicates(
' O- r9 ]6 G3 _0 B, x ).dt.month)['Date'].nlargest(5).reset_index(drop=True)* b/ \# L. w4 u+ I0 H2 w% i% ~
: ~" X' ]" l6 s! ires = df.set_index('Date').loc[target_dt].reset_index( q$ H, M7 t) ^' v% R S M% x) g
).query("Fruit == 'Apple'")- Y' ]4 J2 `8 k, C
$ A% M. {3 A6 i0 }& ?* Z
res = res.groupby(res.Date.dt.month)['Sale'].mean(
4 E! _ s% J) h4 j/ ]' g( p ).rename_axis('Month')
5 G/ X# a& d( \1 y' n+ \
- R$ t1 L" c: Y
0 ^5 Y! P5 k A. Wres.head()
6 i, o8 W0 x/ x% l) D! kOut[236]:
/ v2 P) \% t; vMonth
! g( H3 R) n4 e8 y1 65.313725& b7 d: h( W# d2 S
2 54.061538
2 e& N# Z0 X( p6 N* I3 59.325581
0 E5 q& ?6 m$ p4 p1 ]2 T4 q4 65.795455
L/ g$ Z0 t" R% g! ~, N2 X$ r& \5 57.4651164 `( B, i1 e, K* x3 l) N
Name: Sale, dtype: float64/ e3 S1 A* a/ @* E% R
0 K/ Q0 A, A% ^$ S8 t7 n7 {
1
+ L% d I& y% l) t& B% A( D2
9 M" ^+ B9 A( N6 O' ^: Y* j8 F0 S$ Q5 i34 }+ a9 w0 A" |1 T' Q$ M
4
3 I8 e# z6 ^' x% J( i53 }0 c5 i0 M1 O) |# G
6
" E8 i6 R! M/ Y( F0 j [* Y7
' @, I3 D. t; ]" i, J% z8) X$ h1 w4 J! w
9& o9 u/ p( F; x! r
10
% [8 r- n" T# F- m8 ^8 `& B11
- m d6 F4 Y( b/ b g: J2 U126 B* F+ u$ R* m) C6 ~/ O$ J, d
13
5 C' U: N1 _6 e14
7 ^& n8 I H2 r4 Y" A- I5 i; L/ y15
6 P+ f3 }7 Y) D+ c8 x, ]/ e16% k+ {! b7 z" Q# g
171 z4 f* K" `1 Z- D5 I
182 T8 s/ x2 W7 D
191 i6 ~8 S# G* M5 G5 F' b1 _
20, h- y8 w% r1 a1 `9 H+ }
按月计算周一至周日各品种水果的平均记录条数,行索引外层为水果名称,内层为月份,列索引为星期。# z8 r5 O6 B0 d- ]4 `, V
result=pd.DataFrame(df.groupby([df.Date.dt.month,df.Date.
! s! Y8 g1 X8 E; l J7 S dt.dayofweek,df.Fruit])['Sale'].count()) # 分组统计
7 ]. f8 C$ ^3 u3 u- O) o . w- m- [$ X6 i8 f
result=result.unstack(1).rename_axis(index={'Date':'Month'},
& n, Q. a+ `1 U% a5 Z columns={'Date':'Week'}) # 两个index名字都是Date,只能转一个到列,分开来改名字.5 D7 Y4 H/ `; O
result=result.swaplevel(0,1,axis=0).droplevel(0,axis=1)
$ E! @8 J/ A' D, `result.head() # 索引名有空再改吧& d7 H% Z* A' ?# ]/ n% v3 {
7 D+ o( H8 e0 l Week 0 1 2 3 4 5 61 Z8 u, h/ y0 e* X V$ u% S
Fruit Month
- m+ V y) t) r* B, O WApple 1 46 50 50 45 32 42 23
2 r& J% P N# |5 X' e# `Banana 1 27 29 24 42 36 24 355 l0 ~" H* i: _9 ]* E9 p/ \
Grape 1 42 75 53 63 36 57 46" [6 I: D( Y7 e% _7 D' t+ ^) P1 R! |7 F
Peach 1 67 78 73 88 59 49 72
7 G; o: w' y% J$ qPear 1 39 69 51 54 48 36 40
$ N- \! g# f1 W7 n& h) I5 D; O1
1 N5 [6 g4 m- W0 f$ U% C. h2/ n5 O5 m& Q! u6 ^. _$ V
3& r$ T% A) i5 y) n) B
4
! Y- @/ a/ R; y5* [# l) Y5 i+ b* _3 ]
6
* @; G- ], C! M" c0 u1 s7& b. a* B$ _4 i5 R! e3 X
8
" c. i5 M3 D0 D9
9 e$ v. y" ?/ h0 i104 y; O* [5 I* {: g, P. P5 }
11
6 q. H# w: c( X: x! B& A3 e12
0 L/ P& a7 ]1 F: Z; x5 [13
7 D) X3 f; y* A( M14
) `! W5 B' O) W$ ~& d3 ^15
% F x- l# V, U5 Y/ }: N4 j( k按天计算向前10个工作日窗口的苹果销量均值序列,非工作日的值用上一个工作日的结果填充。
. y* D& `; L# c3 r# 工作日苹果销量按日期排序 c4 j' t0 Y+ {6 V* E; [
select_bday=df[~df.Date.dt.dayofweek.isin([5,6])].query('Fruit=="Apple"').set_index('Date').sort_index()
! m: K+ G/ {& J$ u; bselect_bday=select_bday.groupby(select_bday.index)['Sale'].sum() # 每天的销量汇总2 t5 a2 N3 s K0 e* C
select_bday.head()
. g0 [# g/ M5 E" g% O4 \8 f% c `8 K1 P0 q4 T: m4 c' ]
Date
5 U4 X' Q* F2 k4 e9 W) A) g8 x2019-01-01 189" |* b- E2 H% q* D
2019-01-02 482
4 _7 A; T8 d+ ]& I" S8 x& q2019-01-03 8908 o- j, N, F- B! |" f# D8 e& R
2019-01-04 5500 M1 ~8 H9 _9 {9 M. Q8 N- D
2019-01-07 494
$ o# J7 u6 @4 k+ r. N1 t, L& K* F6 t& L1 T' h
# 此时已经是工作日,正常滑窗。结果重设索引,对周末进行向后填充。6 V& p4 ?: @2 ]5 j' L
select_bday.rolling('10D').mean().reindex(df.Date.unique()).sort_index().ffill().head()9 E9 G& E7 W& g& n! X d
; V; v6 c c0 P: Q5 ~Date
+ J0 }( g6 k& u( p2019-01-01 189.000000
3 u' ]6 R! D) q7 \3 X- C" Q2019-01-02 335.500000
4 F3 H, T) @: ?8 t2019-01-03 520.333333
, S9 j. O4 T9 x- u' E2019-01-04 527.750000- ]5 v4 m! j% r5 b- t9 v4 l" |
2019-01-05 527.750000
; p3 F/ k z" I" ?. c
. p* S5 ?6 C$ w! }————————————————9 C B) \" N8 @. [1 }5 c2 ? F( k; Q; X
版权声明:本文为CSDN博主「神洛华」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。! {$ Z! q4 D( f
原文链接:https://blog.csdn.net/qq_56591814/article/details/1266339138 g; j6 e" C" n
: P7 u( u' P, f4 x* I5 w# @" ` U% n0 ]5 r. J, F% y
|
zan
|