- 在线时间
- 1630 小时
- 最后登录
- 2024-1-29
- 注册时间
- 2017-5-16
- 听众数
- 82
- 收听数
- 1
- 能力
- 120 分
- 体力
- 563306 点
- 威望
- 12 点
- 阅读权限
- 255
- 积分
- 174215
- 相册
- 1
- 日志
- 0
- 记录
- 0
- 帖子
- 5313
- 主题
- 5273
- 精华
- 3
- 分享
- 0
- 好友
- 163
TA的每日心情 | 开心 2021-8-11 17:59 |
|---|
签到天数: 17 天 [LV.4]偶尔看看III 网络挑战赛参赛者 网络挑战赛参赛者 - 自我介绍
- 本人女,毕业于内蒙古科技大学,担任文职专业,毕业专业英语。
 群组: 2018美赛大象算法课程 群组: 2018美赛护航培训课程 群组: 2019年 数学中国站长建 群组: 2019年数据分析师课程 群组: 2018年大象老师国赛优 |
. `% k$ Y% p5 Y" X3 S+ l: Y7 G! }$ Y6 i
, N4 r* {! H5 r7 G! o
文章目录
# P7 n, E% P3 P' ^8 ]第八章 文本数据
" D- C m% N- V( W, L' M' D' E9 [8.1 str对象
: Z+ I% ], a q8 ]& S8.1.1 str对象的设计意图' ^# ~9 D/ J. ?
8.1.3 string类型2 I' r$ K6 l, y! Y
8.2 正则表达式基础" o$ f$ @, D( _3 j0 o
8.2.1 . 一般字符的匹配" ^( u2 O% |2 b6 z4 P
8.2.2 元字符基础
- @4 e/ g+ C* S& b+ Y8.2.3 简写字符集
0 w* I# k- z7 k0 p8.3 文本处理的五类操作
7 E) c( C8 S) {5 H" T7 x. K8.3.1 `str.split `拆分' O2 W# D2 N* d" {
8.3.2 `str.join` 或 `str.cat `合并4 g8 W1 m/ S1 U: w' x# d
8.3.3 匹配: h$ p) V5 ^/ r* h
8.3.5 提取* {9 P3 ?& a" s1 `3 D* R- r1 ]( K) S
8.4、常用字符串函数
7 ^; C, \$ N# f8 M. T" n. X' r0 N8.4.1 字母型函数 K5 ]( @/ Z+ s1 q+ o
8.4.2 数值型函数+ `) ]) z! J1 A) w @
8.4.3 统计型函数. `9 p1 T* L* e( D
8.4.4 格式型函数
" H8 M9 B' ~3 j. p5 l b' X2 C8.5 练习# b& p0 r5 j( V2 @% B+ ?
Ex1:房屋信息数据集- f0 ]9 q( m5 n* v z
Ex2:《权力的游戏》剧本数据集; P6 g: X, C( t0 F" ~2 u
第九章 分类数据8 f3 ]0 u; X/ e
9.1 cat对象
& \2 G. u( T ]4 D9.1.1 cat对象的属性. E# L: P% T [/ S# r5 \
9.1.2 类别的增加、删除和修改* V/ w% e6 @+ c! `0 \ [' q
9.2 有序分类) \3 N) C# X" @$ }) {. `
9.2.1 序的建立* v3 A6 H& P3 d! P" B
9.2.2 排序和比较
0 K6 @3 R4 Q) _2 k9 m9.3 区间类别, i& |6 v T" |( k
9.3.1 利用cut和qcut进行区间构造! H3 M" Q9 @' w6 p# i2 `) S) M
9.3.2 一般区间的构造5 k" o- \! x6 y# x. ^/ N/ i) e' Y
9.3.3 区间的属性与方法
9 I$ Z7 m4 F1 v7 _* d e9.4 练习& X% e7 v4 {2 z9 e: X4 u1 o
Ex1: 统计未出现的类别
' W4 o% O1 E& {! t' h' a. V! wEx2: 钻石数据集, _: f4 [( [& W" ?+ C
第十章 时序数据
4 M8 ~3 I/ I/ q6 D# k1 E* b10.1 时序中的基本对象( I" f/ p, Y# U4 Q j# \. u
10.2 时间戳! j& l+ X7 l1 I$ U
10.2.1 Timestamp的构造与属性* H- o0 }! E: i+ G' {
10.2.2 Datetime序列的生成
# Y! G# f& G% u10.2.3 dt对象( N! V7 o; q2 S; Y% t3 ~
10.2.4 时间戳的切片与索引+ p, R, Q5 N- D
10.3 时间差
2 [/ [9 g5 G3 u0 M" @5 }3 p0 M. c4 V10.3.1 Timedelta的生成
8 a1 A- u! G9 W0 x6 J10.2.2 Timedelta的运算
. N4 V) U$ Q9 {) n. Q! S0 e; ?$ @4 Z10.4 日期偏置
7 W6 r7 V3 h. E' o/ r, d10.4.1 Offset对象
1 A4 w7 P; V4 m2 G10.4.2 偏置字符串
- d, E6 G; z; e* X' f' ?10.5、时序中的滑窗与分组
# j/ E- i% x' V3 l( R+ v$ A+ F+ b; h10.5.1 滑动窗口* k2 C6 M/ T4 Q; j+ h R6 F
10.5.2 重采样" W) z* |& N4 R) `+ a4 D* G8 m( s7 x0 l
10.6 练习
) M- s& I. q, b3 DEx1:太阳辐射数据集
) s5 q1 P$ @# q6 S% K& v% pEx2:水果销量数据集5 j# Y9 Y" j) P3 G9 P
课程资料《pandas数据处理与分析》、github地址、讲解视频、习题参考答案 、pandas官网
# @) U) m$ P- f2 T" j" [传送门:
7 s U F- z2 y0 Z: ?
, w: {) b" d& Y* q8 c* Fdatawhale8月组队学习《pandas数据处理与分析》(上)(基础、索引、分组)) o, ]2 e7 q. E: v( L
datawhale8月组队学习《pandas数据处理与分析》(中)(变形、连接、缺失数据)
0 a) S! \ ^, ^1 q$ X第八章 文本数据
* I+ j6 y* l9 j _# ^1 _- l5 u* I8.1 str对象) ?# s- p' U t" u- p
8.1.1 str对象的设计意图
) H! k* R, v$ ? str 对象是定义在 Index 或 Series上的属性,专门用于处理每个元素的文本内容,其内部定义了大量方法,因此对一个序列进行文本处理,首先需要获取其 str 对象。在Python标准库中也有 str 模块,为了使用上的便利,在 pandas 的50个 str 对象方法中,有31个是和标准库中的 str 模块方法同名且功能一致,例如字母转为大写的操作:
8 q7 k; K/ @; F( i
' r1 y5 E% t }3 }6 N: E$ nvar = 'abcd'
& H: f3 l! j$ n, X/ \6 l; istr.upper(var) # Python内置str模块
# r) I4 N; C8 d8 c) M$ U$ zOut[4]: 'ABCD'& l( H( ~( k4 a/ `# f1 ^
" S& n$ m! x# h9 }; Fs = pd.Series(['abcd', 'efg', 'hi'])
. E# [6 n8 n u7 T) L$ S! |: ]5 f+ U/ H( |+ ]; l. o5 H3 u
s.str7 A' x! \, N* O- o" T. u( o
Out[6]: <pandas.core.strings.accessor.StringMethods at 0x2b796892d60>
( N6 M. R+ |) n# e$ A7 ~6 ]0 A) g# ~ c7 `4 O6 s) @
s.str.upper() # pandas中str对象上的upper方法
. L; S8 v. n' ^5 i6 t- TOut[7]:
! }/ A, U5 a5 r0 ABCD
6 d9 Y1 S3 A' E3 N" F2 ~4 n# U1 EFG
: D/ U# ?# }' Y) `2 HI: B7 A3 \, ~. N. e
dtype: object
; ~3 S% `( a6 e1 \1
! u( R; m! n% u. F4 F M$ w2
3 _8 W1 ]& j8 m5 y# \3, R8 E2 d7 Y/ B0 G: m! M! h
4
5 z1 g! f3 E- F/ E3 r5 g: Z2 V5
/ x7 Z% N3 i& d) y# B" \66 d, {0 p. q! W1 Y9 E" T
7- A- Y% d( e5 ]& a" U7 V
8
I2 }3 T7 V) ?2 a1 R# E3 ^9
! l) @% M3 n0 ~+ I/ c10! l* B1 {, ]8 q# f2 Q+ Z
11
9 F- R) b4 N3 R1 o12
; g d0 { R$ y2 T$ f13
4 H {" c+ J4 f* `! S0 B) D14 M! } j8 F" o% }; m% Z$ _7 ?
15. M- q5 V0 O" \5 S6 `
8.1.2 []索引器) m( @# y9 [3 E3 H+ q `
对于 str 对象而言,可理解为其对字符串进行了序列化的操作,例如在一般的字符串中,通过 [] 可以取出某个位置的元素,同时也能通过切片得到子串。
' U/ M" e& t% \4 n6 k. R pandas中过对 str 对象使用 [] 索引器,可以完成完全一致的功能,并且如果超出范围则返回缺失值:' |$ z+ x& V* o" e3 k+ Q
! K3 h6 S; A, r P5 F9 Ts.str[0]
5 Q4 @; g8 j! m1 @, ]9 R5 MOut[10]: ( \) W) W: L9 Z4 e: |9 g
0 a( h; U* y# V- H- [( }: b
1 e& `' ?( F' b7 B0 A- v w7 u/ j
2 h0 V6 z& q* Q# P; [6 Q% Z
dtype: object, ~+ j1 @6 T/ y: K" n$ Z* \5 Y) b
?, ^ R/ s7 F9 F8 L
s.str[-1: 0: -2]
@. C- j; F% ^* x, f- U" e" ^( [8 lOut[11]: - X" Z$ f$ D9 `) K
0 db
7 c/ e# W# y% F; }: W# t7 `: u1 g* Q& ]0 i3 q& Z. ^' k& U
2 i
% \# p$ P4 ^0 b; E' Ndtype: object
. X7 c3 e$ ]) L& Y3 R' w* w! i- {; M6 W7 n/ u* g0 H
s.str[2]
6 q+ v7 S0 s9 f: i; KOut[12]:
5 V- R& x/ x; z5 {* {% P) W r0 c- ]5 R* B- q X* |( L# x" W
1 g: d G# N" T) X( M3 B5 d
2 NaN/ q# H3 d1 L$ v, F
dtype: object3 L4 `9 M* U3 M" B* G4 n
1 \- s4 B) O, r: A# z5 a
1
8 a- o7 `/ Q7 V( e/ [: K" E2
2 T8 L, N Y) T* f- }3
; @2 h/ R, C7 W( M7 Z3 a4
* M$ j- m5 l) K2 y6 n5. G0 m2 n# b4 b* i5 U
6
; c9 b" t0 y( _. q( w/ ]0 g7 ^% x7
- a- a: p0 u8 i: L4 Q8 }- N8, h% f' Y5 L. N/ O: S
9( E; t& U( G* K" z# j
10
6 {2 r: p# t4 D" e# s; `8 a ~11
8 A5 {' y! Y; r" W, e3 r128 ^, a3 v( W/ j
13
* {, }. Z2 P- c" ]( }14
R/ l4 r" l! U9 X% p15) A/ ~# b% ?' F* C
16
2 ]3 x; L7 e K# O4 O17
/ v, G0 |7 w. `9 y9 G* G18
& {5 \' R' y% \: w9 L7 b# Z' l- D19
: W# P- h3 g' a+ }& k1 ]# f( U20' `6 ^8 ?$ r( O% ~, g* d
import numpy as np! S# j$ R7 n8 @0 F( W
import pandas as pd
1 W( F1 W. w& i7 ]
* }6 H8 i0 k3 `" b& `& Cs = pd.Series(['abcd', 'efg', 'hi']); s/ f$ h2 ^/ w* P. a' @. J( u1 n$ h
s.str[0]8 n, e0 I1 X5 z4 W' P7 [
1& Z6 g3 B+ U; X, H8 C, y$ V& ?. U
2
% T, ~! ]! E4 e, p38 g8 `" z7 ~& `. _& f
4
0 W& j6 ^# T# o" E6 b/ V Q6 G* v2 i58 C( R( f, k5 g! K+ |+ |# [( T
0 a6 y3 `: H0 @5 {
1 e; L& o- Y6 |7 g
2 h8 k* F( x; c/ i" h8 s/ k
dtype: object
; W; e0 J0 [2 Z7 P; R) R1
- @7 ]3 Y! W6 h& D4 f/ I2
3 _: c2 \7 w3 e4 E; V' i3
! P7 i+ T/ v- i( k! S( G4! \* l1 t7 q. [: x5 V
8.1.3 string类型
+ C# D- q% a1 v+ J7 J 在上一章提到,从 pandas 的 1.0.0 版本开始,引入了 string 类型,其引入的动机在于:原来所有的字符串类型都会以 object 类型的 Series 进行存储,但 object 类型只应当存储混合类型,例如同时存储浮点、字符串、字典、列表、自定义类型等,因此字符串有必要同数值型或 category 一样,具有自己的数据存储类型,从而引入了 string 类型。
' N$ G' }' _2 O/ ?; d 总体上说,绝大多数对于 object 和 string 类型的序列使用 str 对象方法产生的结果是一致,但是在下面提到的两点上有较大差异:
# a# d2 i* [# ?6 a# y/ }3 g8 }2 t1 y8 b0 d- {; A- d; ~9 S. y8 J
二者对于某些对象的 str 序列化方法不同。
. ?) u8 l; {; m- w |$ l9 i- ]: [可迭代(Iterable)对象包括但不限于字符串、字典、列表。对于一个可迭代对象, string 类型和 object 类型对它们的序列化方式不同,序列化后str对象返回结果也可能不同。例如:
* c9 r9 v) f2 W& m7 {0 B; a0 [% z9 Gs = pd.Series([{1: 'temp_1', 2: 'temp_2'}, ['a', 'b'], 0.5, 'my_string']) t% Q I# J, Y9 E) e# S, K
s+ U8 o5 O9 D, d) D
1
+ w) F/ u7 ?! K/ {8 Y3 a5 b2
! `5 I2 F% W. ^3 G0 y0 {1: 'temp_1', 2: 'temp_2'}# D5 J% v. ~2 W
1 [a, b]) n8 V% E) v! v% Y% [* t
2 0.5. j0 N* q0 T. M8 f5 D) \
3 my_string& W- M" B( z! V3 r
dtype: object
+ \ d u p! U0 _) ^4 S19 R4 ^* W8 C1 O" S
2
7 o" Z: e8 J3 r- I) F3
# d1 p- w5 N, X( d( f4. z1 U7 a" N1 D; F' e- S
59 I" j1 l' c& n( E
s.str[1] # 对每个元素取[1]的操作5 ~# }9 \7 T1 h3 z! `
1
0 E# Q7 O6 p) U& i- Y3 e: h0 temp_1$ e$ D7 k% b2 |3 G
1 b
- }! Z) I) V7 r' Z: y2 NaN0 B3 s- {4 q8 ~0 P% Z$ B
3 y$ [2 X4 K2 Y9 ~
dtype: object
2 k, X* g0 R1 I, W* e6 G1: D$ [7 }2 a" \8 V% h
28 g0 I. j/ W- c1 E4 g7 ^9 k
3& T& k6 M$ T1 ]& K, F) T3 A9 c
4
# t$ H7 N; G* T9 {50 N, u* \* `/ [7 S$ h( [5 S: |% V
s.astype('string').str[1]
5 ?, M$ I f, e: A& @ L ^16 E+ i: o5 m7 a. S( B( T/ u) Z
0 1
* V( l; t! `0 d9 H5 R4 V4 F1 '
9 K. f$ Q$ W$ R. ?2 .5 w, j, ^0 w* B7 r" q
3 y
( f' K4 g L" h$ qdtype: string
) v, x1 }0 @- @, s0 f& }1/ [! e* A2 H( }/ L# u9 Y9 F3 S7 [
2# H- v* j" r @& p$ Y( _2 G% @1 F
3
9 \& b+ ^* @) y/ R- |4
2 d% g- k" G) Y3 X5
: ~+ n2 H4 W, C9 x, {. |2 P5 C5 r5 S除了最后一个字符串元素,前三个元素返回的值都不同,其原因在于:7 Z7 c+ b' [" n7 K" y9 z9 Z
/ w; }. p& q0 d当序列类型为 object 时,是对于每一个元素进行 [] 索引,因此对于字典而言,返回temp_1字符串,对于列表则返回第二个值,而第三个为不可迭代对象,返回缺失值,第四个是对字符串进行 [] 索引。
2 G8 @6 W% R! Q) ~$ w0 qstring 类型的 str 对象先把整个元素转为字面意义的字符串,例如对于列表而言,第一个元素即 “{”,而对于最后一个字符串元素而言,恰好转化前后的表示方法一致,因此结果和 object 类型一致。/ R3 r& q: n7 F r4 u$ T+ `
string 类型是 Nullable 类型,但 object 不是3 W+ e% L2 @# q/ J* N; e+ r: U
这意味着 string 类型的序列,如果调用的 str 方法返回值为整数 Series 和布尔 Series 时,其分别对应的 dtype 是 Int 和 boolean 的 Nullable 类型,而 object 类型则会分别返回 int/float 和 bool/object ,不过这取决于缺失值的存在与否。- q( b0 V" M: t0 A1 p. o6 F5 v
同时,字符串的比较操作,也具有相似的特性, string 返回 Nullable 类型,但 object 不会。' A- C1 ^9 K, F- U4 r% z
s = pd.Series(['a'])
6 ]% D7 h! Z* O* z+ u7 b/ {) J2 b/ O( s3 w' \9 ^1 u) P; J
s.str.len()
% o6 }" W# B4 t, b1 JOut[17]:
6 `8 [ u2 i5 K2 x! F. C& A5 }6 P0 1
r' ~6 H1 h; ^* J6 ?5 C% rdtype: int64
% R2 l9 ]) g$ \/ X" |7 E
9 l/ j4 I) R7 ^3 u$ z. Y0 y4 Ds.astype('string').str.len(); e2 I; @9 s; p; A& I1 ~
Out[18]:
& u# L) t% d$ ]0 A. s1 H0 19 x/ `6 L4 w3 q* |3 t
dtype: Int640 _4 D: l/ j& {0 m J) _
# L4 p0 Y3 e! k' S5 l
s == 'a'
" E3 x1 U5 C/ _: B' _2 F% FOut[19]: $ H* o4 J2 y1 L0 K) O* B3 P
0 True
4 R! S4 i9 Z% l6 u6 _8 c3 U% B sdtype: bool9 y& J2 N. O0 O; O
0 w/ n- t, Y$ G M/ Ms.astype('string') == 'a'/ h* w. `8 l x: A7 K; h
Out[20]:
3 q* F1 ], v1 q* j- g0 c" X/ W0 True
1 x" I% Q% z# b6 X S6 |; u7 {dtype: boolean
% K- [" C5 K5 B
) `4 D/ k( ~ s/ g8 N1 Y8 bs = pd.Series(['a', np.nan]) # 带有缺失值- ^' L+ o/ o; L- W# ?+ l q* T4 N8 `
* w7 T+ W! p E5 ~s.str.len()! ^2 k9 {3 [, u% x
Out[22]: : V* l3 i3 S1 ?3 K* h# I
0 1.0
$ w' R2 s- }' Y3 B9 G/ ]1 NaN3 h; D, F8 j) _% A \( K
dtype: float64" P5 B3 T) L; f2 _/ T0 A
- {1 k1 U3 l, S! X1 Cs.astype('string').str.len()9 m4 w. w1 q" H7 H5 P
Out[23]: 3 T" f1 v. e2 Y+ C0 V' M
0 1; ^" [" r; X$ c5 R
1 <NA>: j2 t2 m1 z* R8 y, i) R
dtype: Int64
' n, L2 ?3 P9 x% V+ i8 h
0 @: r; D+ `( ?+ X$ ^s == 'a'
Y: Q& x" O) H$ oOut[24]:
: p1 R. j0 i8 p0 L/ B0 True
) P# i2 A* z+ d3 R Y9 K1 False
$ u7 D# L' ?9 b" }. X) b9 Xdtype: bool2 \+ I. ~8 I( z1 T+ J) L$ }' M
S R/ U; r4 B) X& P
s.astype('string') == 'a') p$ Y0 n# _* w- M. d9 k6 B
Out[25]:
; |* @( C# B0 r0 True
( P4 @( G4 b: Z7 G! z1 <NA>% t) I6 L" M% h0 b1 Z$ X6 M
dtype: boolean, }; \$ \. [) Y& u* ^
* q# c' A5 g7 e# x1. ]" g) y0 E O' j- w3 L( J8 n
2; E2 U/ N& z7 P1 w, Q+ ?7 U" b
3
# V, k- [5 c, {& K' E) n3 w4
) L# G3 ?/ N- c5
6 f" A# v8 F" L! R6" x9 R% ?: f( \7 B1 x1 _: W6 k
7
* v- d7 b* T9 w8
d0 o- H2 y' A9+ v B# b+ f3 w* P8 t
10- k3 M* ?% K0 r: }" V. Q- B. b$ P
112 R/ Q! |% Y" u4 w* R
12
; U8 | v" Y7 T4 W I139 k1 I7 j+ Z5 X# J
14
. l ]1 s8 S6 V5 ~8 r15+ L0 C1 d% j8 H' H5 S% {
164 ^. I6 I3 U0 A+ F" T# `' q9 C7 S
17* x' w4 F% @& V: Q9 a+ }+ [. B+ V
18
- C& r/ K, m9 |19
* d- R$ J* J c8 C! Z. n- t6 X20
& V6 G- j @9 l/ F" q" M21
* T+ Y. M! Z) A% J22
: y2 E0 T0 b$ \7 `, E6 O23
) C$ b2 P' o6 k4 W* S; O% z24
7 u5 q5 z4 E/ z8 N6 \' i- s25
6 s* T% l# x; \. M% |/ f8 ~0 B26
7 T5 E: r* O0 X, a3 E5 B2 G27
: T# Q$ O! r( V' \/ X( y28
" M, m; x" Y2 g& j290 @9 @4 i) k% g0 Q& T: G
30' i& ^2 Y j, K
31& r4 Z4 W' W+ M1 d3 g9 w2 j; y4 X
32, \3 D- R. ]! |8 A8 H1 ]
33
# `: o* i/ Q2 x. T3 U34
3 d0 D- L B p0 A n7 C* K35
/ K. E" D1 z+ k) @% H% O: h36
* J, q4 g+ G* @37
. s* ^, g) K1 M7 d% a38
( d- ?- X* K5 j' e39
$ @3 n+ Z- _: }2 C7 G40& N/ D# T7 u4 m3 I' Q; K. ]
41
' V! t# ~/ @2 |: u42; B4 C ]. T7 g( J! t1 _3 c5 s% E
430 Y( D0 C& B" K& C
44
8 `; F p- Q- T3 ]& f# ?/ q) n+ O45) A; T5 Z" X. ?1 ^3 o1 v9 Y0 k& U
46
, F$ X+ o! A0 M# C+ Q' q47# C2 b- D0 t; o; ^+ E( o' g
对于全体元素为数值类型的序列,即使其类型为 object 或者 category 也不允许直接使用 str 属性。如果需要把数字当成 string 类型处理,可以使用 astype 强制转换为 string 类型的 Series :
E! X! _. \: B3 H) J) c2 s4 J& c( t0 E2 I
s = pd.Series([12, 345, 6789])
1 q8 h; |+ q8 X8 l3 k: q, C( [, p4 j6 w0 ]/ u$ Q& K% u1 @. D
s.astype('string').str[1]3 A& g* L O5 t: o+ |& H# o8 P
Out[27]: $ t- K6 X' b7 ?; H8 X8 C( h/ @
0 2
$ b( O3 [% _4 p1 4: u( l& G# ^+ ^! W# R, ]: S$ a
2 7
& U L" v! S. l# N2 Ddtype: string
6 ]$ R9 J( |# R, k17 m* J* |8 @( l9 C" d: r: U, @
2
; Y% o7 u8 \" o [/ L8 o# Q3
5 m5 e+ J4 O) l, ]7 T2 t4
. m* k' I; i8 w3 d Y5
+ f3 q' u# N4 N5 l6
4 q; N8 d4 a* a! l7
' W9 f! V. H0 i% l8$ V& N6 J" \! W" N
8.2 正则表达式基础2 t7 F; r1 T2 O, }" X- G) K: f! v
这一节的两个表格来自于 learn-regex-zh 这个关于正则表达式项目,其使用 MIT 开源许可协议。这里只是介绍正则表达式的基本用法,需要系统学习的读者可参考《Python3 正则表达式》,或者《 正则表达式必知必会 》这本书+ V) k* |8 L+ q$ H! C& I
/ b- S s1 f! C+ p: d+ W4 z8.2.1 . 一般字符的匹配
! \2 V6 T4 l9 J/ Q正则表达式是一种按照某种正则模式,从左到右匹配字符串中内容的一种工具。对于一般的字符而言,它可以找到其所在的位置,这里为了演示便利,使用了 python 中 re 模块的 findall 函数来匹配所有出现过但不重叠的模式,第一个参数是正则表达式,第二个参数是待匹配的字符串。例如,在下面的字符串中找出 apple :
7 q; s6 }' t. \/ a4 A2 a- X$ [' r+ |! w I8 N$ T6 K) f. }, l
import re
& }* g2 P5 H3 C& o5 K U0 F+ d; F- {, ~; G9 P( d% v
re.findall(r'Apple', 'Apple! This Is an Apple!') # 字符串从左到右依次匹配8 W" ?, l1 O' k: L p# N
Out[29]: ['Apple', 'Apple']
1 }' i6 h6 y% `# [) m1
& s$ {% _1 Z S& h0 R2
( m$ i8 K8 P' L! a3& f2 a2 h8 a: N* J4 H" k
48 w% {1 J1 z0 G. V+ _2 I
8.2.2 元字符基础5 m6 C! I" ?: p6 o8 D' C/ ^# }
元字符 描述
' T+ }" g& I6 p' c. 匹配除换行符以外的任意字符
% y6 c! v: b' w[ ] 字符类,匹配方括号中包含的任意字符
, J$ ?- N3 w+ P[^ ] 否定字符类,匹配方括号中不包含的任意字符: `% x" X# k" W+ U1 C
* 匹配前面的子表达式零次或多次% K" P5 Q' r* M& ]/ ^- V
+ 匹配前面的子表达式一次或多次。比如r’d+'就是匹配数字串,r’d’就是匹配单个数字1 k; d4 K2 k8 |$ ?# |$ t
? 匹配前面的子表达式零次或一次,非贪婪方式
5 x8 R _2 D/ R; r( G{n,m} 花括号,匹配 n 到 m 次由前面的正则表达式定义的片段,贪婪方式7 c1 ]& M# w, ~% n2 c- s
(xyz) 字符组,按照确切的顺序匹配字符xyz
0 J' C: H6 B* _| 分支结构,匹配符号之前的字符或后面的字符
/ b# G5 k% I# t' b( V ?\ 转义符,它可以还原元字符原来的含义
0 d# B7 ]. ?9 ]. R1 D! t& l^ 匹配行的开始
' _8 j9 a9 w& O7 p/ h; A$ 匹配行的结束# M- m+ g' X- @
import re5 u3 x( B: J1 \% ^" |1 R
re.findall(r'.', 'abc')
' k9 r5 |5 I+ Y( H: `& TOut[30]: ['a', 'b', 'c']
* l4 B- z. Y4 y& D4 P/ ~- ]# p3 L) v' o6 V7 A5 }0 h0 h1 a2 d
re.findall(r'[ac]', 'abc') # []中有的子串都匹配9 f2 y( X% a8 {- a4 U$ i
Out[31]: ['a', 'c']" u# C/ r% _7 n! @% k6 b
# Z0 q. @+ B' t; W2 {; [
re.findall(r'[^ac]', 'abc') 2 g5 K( s6 y6 n" Y% `
Out[32]: ['b']9 T1 L& l, M0 N& L9 Y
. p: M! u# y+ k! o0 F( {' i& S/ Bre.findall(r'[ab]{2}', 'aaaabbbb') # {n}指匹配n次- o' ~% w3 a2 ^# |6 M% X# W
Out[33]: ['aa', 'aa', 'bb', 'bb']7 Y3 }, I- j/ c* y4 u y4 I o+ B
0 U Q# X4 h& ~% j% W8 `- k5 ]3 _
re.findall(r'aaa|bbc|ca', 'aacabbcbbc') # 匹配前面的或者后面的字符串
* q* x# c" G+ ~7 R. x9 |6 H, B8 B* LOut[34]: ['ca', 'bbc', 'bbc'], l1 d; v2 X+ b
3 b; _1 B7 j4 k/ \7 }! [1 A
# 上面的元字符都有特殊含义,要匹配其本来的意思就得用\进行转义。2 X$ y$ q2 Z# C0 s" S3 V, s) W
"""8 K) i5 t$ n2 Y. E5 ?$ C; B
1. ?匹配的是前一个字符,即被转义的\,所以|前面的内容就是匹配a\或者a,但是结果里面没有a\,相当于只能匹配a。) x1 Y0 F8 s; g- S6 D
2. |右边是a\*,转义之后匹配a*,对于竖线而言左边优先级高于右边
+ n0 G8 @5 W9 f, F+ J/ g9 s8 @5 ?0 M3. 然后看目标字符串aa?a*a,第一个a匹配左边,第二个a匹配左边,第三个a虽然后面有*,
$ f- t) X; _3 |但是左边优先级高, 还是匹配左边,剩下一个a还是左边,所以结果是四个a
2 k' ~) F& F. ]8 u0 W, c"""
' G( T) {) j! M; W( H# d% M9 p# l- U/ X2 @
re.findall(r'a\\?|a\*', 'aa?a*a') # 第二次先匹配到a,就不会匹配a?。a*同理。9 u; k; x. D t9 M# X* N, v/ K/ t
Out[35]: ['a', 'a', 'a', 'a']: k# k1 a4 z0 W) S; H+ b1 p$ y" _9 c
) O* T# b3 V2 [2 f
# 这里匹配不到是因为目标串'aa\a*a'中,\a是python的转义字符(\a\b\t\n等),所以匹配不到。
9 t* f0 c3 t1 s) m# K: Y3 c* c a# 如果是'aa\s*a'之内非python的转义字符,或者'aa\\s*a',或者r'aa\\s*a'就可以匹配到\字符。+ A0 Z+ e: }9 }2 I7 W
re.findall(r'\\', 'aa\a*a') 5 C# E$ B: a# b
[]. R4 Y! |! R) b9 h+ \
; u8 X, y8 g( l( q" C
re.findall(r'a?.', 'abaacadaae')
$ ~* h0 t) A( p1 z" ], }Out[36]: ['ab', 'aa', 'c', 'ad', 'aa', 'e']
4 i& @2 W% n1 z: a9 Z) }5 V6 ?4 P4 H$ Q' U8 N9 P) O& j
re.findall(r'(\w+)=(\d+)', 'set width=20 and height=10') # 多个匹配模式,返回元组列表
8 B- d+ ?, j: n; B[('width', '20'), ('height', '10')] Z' J) ~. M8 r: e
3 Q9 \% i4 k) E# q* p% L. E1
# _/ v7 {, }0 O8 Y ?( r* @. C2 d- n2
# Q0 q& @" H, p. B9 I3* d- \% C0 f) _/ I) N; Y5 y4 l" J
4
1 L8 b$ n7 t0 ~5/ E% Z, a% t1 ?. u8 p
68 `- V; |, g- O8 }
72 e) [9 e, k% _7 O+ `0 R
8 L1 w$ d9 [, a
9
" q; }+ \: f: ]101 U8 P! s1 Y" p# D" }
11 y. `: b7 Q/ l- E2 V, V. H
12% y7 B1 B' {1 h0 l- a4 F* T/ Y ~
13
& ^0 ]# U( P1 [4 C& X; ]8 S3 P6 z14
3 @9 B6 r' `% P15& e% ^0 u# i6 a9 g2 G! f4 c( {
16
2 `& p, v6 o9 x( J$ y8 Z! u' `17% d; J0 w1 [4 R x6 T8 J
18
$ V- X' {6 X; r2 I" J# r4 i19 ^' c( r+ \) {$ K
20
1 S/ o1 G# V }4 w21
2 S8 s" v. k5 R! K/ G226 }" H" E9 S% h, r3 e
23
! F% y J1 ~3 {+ o' F# s24: x0 e) p, V8 ~9 L+ C4 N0 w
25' l8 h: @5 z( x2 K) m
26
: t8 u; e. V9 E* X27
6 U, x1 c# D. N) C; _8 _6 e288 N% O, Z |4 {) Z3 n1 [
29
& X0 A- I9 ?* N, @30) c2 l1 F B0 O3 M
31
5 t1 N$ r, v; r; ~328 _# R3 @! Y B1 A \! r$ V7 E5 i
33, s/ c0 s. r5 X8 P4 ]
347 j) L! M. M, B* L
355 ]& H; w! d& v( F& J( R# | {
364 M, }4 y4 X* ~, C
37
7 d! w B% k1 q6 [- G1 |* Q. b4 Z* |8.2.3 简写字符集6 d: C; z. e! S; Q
则表达式中还有一类简写字符集,其等价于一组字符的集合:! [% Y8 J. U) q3 R
8 M# r H" o( g, Z" E! L简写 描述
% @: o/ p1 {( G9 S$ j\w 匹配所有字母、数字、下划线: [a-zA-Z0-9_]
: W" W" M; x7 H' \! R/ m\W 匹配非字母和数字的字符: [^\w]
' g+ Y( Q( B1 n; i9 p d1 R\d 匹配数字: [0-9]% h+ y6 k( M9 W0 k. b$ }, ~
\D 匹配非数字: [^\d]% _8 G' o) v6 R# ^ }
\s 匹配空格符: [\t\n\f\r\p{Z}]
1 D4 u0 s- Z& @$ D\S 匹配非空格符: [^\s]
9 S9 m) Y- y, V5 s6 F1 v\B 匹配非单词边界。‘er\B’ 能匹配 “verb” 中的 ‘er’,但不能匹配 “never” 中的 ‘er’。! k4 B4 G: y2 y5 G' ~& p/ T! V
re.findall(r'.s', 'Apple! This Is an Apple!')1 F/ V. n$ ]" m
Out[37]: ['is', 'Is']2 m) F" ~$ `/ Y2 b. o& _
. v/ \1 }# u! L7 w4 r
re.findall(r'\w{2}', '09 8? 7w c_ 9q p@') # 匹配任意数字字母下划线的组合,但必须是两次% l/ E; J4 f& K- Q w% q* ?' ~: G& \: d5 X
Out[38]: ['09', '7w', 'c_', '9q']
8 t# F# a' w% j7 p) k
7 \0 |7 ^) U$ w# |re.findall(r'\w\W\B', '09 8? 7w c_ 9q p@') # 匹配的是两个字符串,前一个是任意数字字母下划线(\W),后一个不是(\W)
2 _' J& }( ?9 W( _Out[39]: ['8?', 'p@']
% I" e2 @( ?* E" L) {. e L. |! h
re.findall(r'.\s.', 'Constant dropping wears the stone.')0 K- ~$ V6 l- p
Out[40]: ['t d', 'g w', 's t', 'e s']% @7 e, k. u7 D p+ n- J8 L, \0 i7 Z
- B0 `- W: C8 u' [re.findall(r'上海市(.{2,3}区)(.{2,3}路)(\d+号)',
+ @- z9 d( F* `$ v" ~ '上海市黄浦区方浜中路249号 上海市宝山区密山路5号')
" k* h. d2 i1 b- k# I4 U4 J& T
3 @, Y2 K4 A& Q) F0 R& i1 EOut[41]: [('黄浦区', '方浜中路', '249号'), ('宝山区', '密山路', '5号')]
3 K" \3 E) ]9 C h5 d6 o( e% q% f" O; H0 \- [/ O2 p
17 s9 r6 W9 r2 f; V5 A9 g
2
0 o, _ ]/ a: O& |32 _" f. s: _$ X
4% \3 I6 S2 m: V1 {
51 O/ a. U. w" T! M5 F
65 V3 F+ ~5 h% ]: }% l
7; e9 \) F5 \" Y3 k6 R5 L1 r
8
( D# E& E4 ^/ q0 }6 V- i. g9: L% r l. M' I" I- |+ E/ ^
10
+ q4 j3 k9 b' H# k; x8 l111 J; H+ p# C3 c7 X, @; y' w
12
7 U, p, V( B7 S7 l: N13" p- _- A; B" |( R* N4 u5 f$ d1 b
144 b' l( V$ ^/ R; m. C
155 z) [+ g4 @5 q8 [* U1 p7 ]+ {
16
1 C* \% o, [# D6 X! V1 H! |; D8.3 文本处理的五类操作
. g8 b: a& l( a0 l: o8.3.1 str.split 拆分, J$ R0 O* \2 I1 h( {
str.split 能够把字符串的列进行拆分,其中第一个参数为正则表达式,可选参数包括从左到右的最大拆分次数 n ,是否展开为多个列 expand 。$ n# l9 ^( V8 v# s
K7 x L" t V9 _$ d6 p3 \s = pd.Series(['上海市黄浦区方浜中路249号',
. x3 D- W7 u3 p# Q/ R '上海市宝山区密山路5号'])
$ e" @- j3 O4 N: U/ k. g* A. g, ?! q# ?
2 v* f+ _; H {; H( B0 f/ vs.str.split('[市区路]') # 每条结果为一行,相当于Series
4 O: X$ B% R# J# E ^( \' r0 F# `Out[43]: " M6 Y* f1 @" s# J( N" Y
0 [上海, 黄浦, 方浜中, 249号]: l/ F# E6 C/ l V7 S
1 [上海, 宝山, 密山, 5号]
# U. l! E3 }( d% N0 Edtype: object
) H- S" H; o( q* P- C
2 _' B- @& o4 m8 W9 qs.str.split('[市区路]', n=2, expand=True) # 结果分成多个列展示,结果相当于DataFrame
5 d+ Q- {+ h& i$ |+ K cOut[44]: ; p% K0 ~8 G3 v: V2 c" k7 S2 E
0 1 2" i7 z; u9 u( b- Z1 ^+ W
0 上海 黄浦 方浜中路249号
; p0 K" i0 @5 n A# _6 _5 v1 上海 宝山 密山路5号
6 u" x7 e% T+ l, C5 ?2 {: @2 v1
9 @" `- \' Y1 r6 O7 f2 K( ^2
& M8 i b3 \* [# |3% N* v3 o: V0 l1 Q2 z# g1 u
4
0 V% N' U9 R6 f8 ?2 L$ a* v5
: p6 |" G2 }# x1 p: O& H6
& j# y" `' N& z8 t8 A7
& }3 S% Q. Q* ?: ^0 V9 a8' Z; Q8 ~( w, M5 H- [
9
$ N, e, z; D. c B% V7 X10
3 X3 F! `0 P! C$ G. a1 H: s9 H5 [0 G11
5 L8 h8 ? S9 y1 R2 W, b& \121 K& x7 A/ `3 d0 Y' `! O( a+ `
13
* g1 h* k4 W7 c. Y145 f/ K1 O* m( x. b4 ~0 K
15
; f/ p+ [8 |4 { 类似的函数是 str.rsplit ,其区别在于使用 n 参数的时候是从右到左限制最大拆分次数。但是当前版本下 rsplit 因为 bug 而无法使用正则表达式进行分割:4 w `: a5 Y# i
5 X3 c% o& A, |s.str.rsplit('[市区路]', n=2, expand=True); w2 b! K( A/ U D; P2 B! Q
Out[45]: 0 b2 q4 P8 ^7 L
0" n# u9 c% ?5 M, z
0 上海市黄浦区方浜中路249号
8 C5 o/ {/ n8 z* n0 t3 r' l1 上海市宝山区密山路5号% W- |; a& d- m$ E( w$ F
1
, L/ d) `- b+ S+ q) S f* B" K4 j) ?- W2
& H4 e* D: o: l3 b; L3
% p3 P- f' ~; O! y! o+ j4! m, H5 D" V- K/ `) b* ~
5* K* U9 }% I J, ]* D1 H- ^
8.3.2 str.join 或 str.cat 合并 k6 k! M: Z# Y: \; Z/ a( {
str.join 表示用某个连接符把 Series 中的字符串列表连接起来,如果列表中出现了非字符串元素则返回缺失值。! F% A7 N( H( J
str.cat 用于合并两个序列,主要参数为:2 v( T* [: w" Q7 E, F
sep:连接符、" b0 K. O6 n% o/ c/ j
join:连接形式默认为以索引为键的左连接, X0 N3 C# X; ~& L7 o2 M
na_rep:缺失值替代符号) u0 P! p' i1 Q: s7 C' |
s = pd.Series([['a','b'], [1, 'a'], [['a', 'b'], 'c']])
$ M. k; M$ h6 X% o" j9 A3 u6 Ks.str.join('-')
& E1 H; w- ^/ Y) o; m; [Out[47]: : Z6 S0 r6 O+ y% [
0 a-b! \% t: L& @: q2 d6 Y
1 NaN/ k2 x6 |- ?: E; r
2 NaN6 g, O5 p4 c8 n3 K3 l, H
dtype: object: f; `" e5 }. r! C
1/ Q- |. C' x2 k& D, _
2
/ b. U- }+ Q8 W/ |3 T- G6 q' N/ R" ]# k
4' p {* F; r) _" ?( ^1 [
5
- g# y6 }" E2 }) O6, f2 E7 I8 Q5 W" {
7
0 P6 F' H# T# m* ls1 = pd.Series(['a','b']). h! m* x- c- s& X2 |
s2 = pd.Series(['cat','dog'])/ i9 k3 L8 D, t3 ?% O; B7 X
s1.str.cat(s2,sep='-')7 }! |6 b) S: K; d7 a
Out[50]: ) G$ @5 Y" w5 v N( Y
0 a-cat
# V u+ j5 I- |% P# I* h `1 k" X1 b-dog
* d2 A1 q$ C& f2 Tdtype: object
8 O9 h+ d( @/ z1 }! P& |9 @8 m; A, s& v" h n7 m! l/ M
s2.index = [1, 2]2 |( j3 C; S" d- v
s1.str.cat(s2, sep='-', na_rep='?', join='outer')$ G! Q }5 c6 ]
Out[52]: # G) j, j+ @# s1 p
0 a-?
, i5 d/ x) K2 U+ j! e1 b-cat
: L# k+ Y" G7 o( d$ K. d2 ?-dog
" X" O U, h& l; v# edtype: object, u4 \$ u! g, Q+ e+ p
1# O" p) y$ }' p: Q8 K8 k# e$ F9 U
2
% x; ^ p- l |1 s9 I3 `2 V6 r31 O6 \# F) ]0 q! c0 l( D
4# Z- B4 Q0 ^& N1 m' e6 r _6 i0 F4 }
56 _* y" Q+ M: p g
6
: a/ q) ^* d4 W* X/ x71 S8 H) E7 [7 \
8. P' ?6 T4 f" u) }0 _- e
9' j. G/ H3 B2 n" `% r
103 P5 w8 t; O+ a1 B% N1 _( m6 ]
11; v ]( R, a$ [( Y' |
12# a1 u5 i& ?+ C
13- T3 L; n( G' g% Q
141 f" R- V5 u6 w" c; S# q" _1 V8 ]
15
' u0 d$ x, n% t* G/ e0 \8.3.3 匹配
9 t; T# L3 n; T3 l7 Lstr.contains返回了每个字符串是否包含正则模式的布尔序列:2 c/ I% d/ }6 F
s = pd.Series(['my cat', 'he is fat', 'railway station'])9 m9 f1 O0 Y7 r- q/ v
s.str.contains('\s\wat')
& @, f: q& j' W5 J! K4 M' ^& } K* j3 f: v3 e6 f$ |" t: a' N
0 True
+ u8 T, s7 p5 @1 y* P$ z1 True4 t# b' K. y6 H+ `
2 False
& V( w L' M! X( s8 W! ]8 y8 Gdtype: bool7 y7 S: l8 i& m0 E/ F6 ^
1
5 @& D( Y2 B# S, t7 X8 ~; g R, l- W2
; z, x* o i7 f7 t+ H; O4 `# P3, r! H% }% g7 Y- H- ]
49 `+ c$ b5 ]. o# Q/ C. O' P R. a
5 g }1 z- {8 ~) K4 L% A
61 B$ l2 b6 J& Y7 ~& o
7% G$ U4 R- A$ [% a: D$ W3 ? M
str.startswith和str.endswith返回了每个字符串以给定模式为开始和结束的布尔序列,它们都不支持正则表达式:
, S# D7 y+ L7 ^6 a8 k* h/ Ts.str.startswith('my'), l. _, D; B& M- m; B
) Z4 X# a/ [# b* N6 I2 E& G
0 True( F/ _- ^7 ?. k
1 False! \9 y; g7 }( [- K/ O+ M- R5 C
2 False
! ~: t! T- r# W9 S; M5 `: Cdtype: bool
$ P6 b* {. W& B p1
2 Y% [' g, o* l2
% p) w! @. D' D$ [3+ ^6 y& ]1 g) ~ z2 q L
4
3 T; P a9 K" n6 G5) ]6 ^ k" o% c6 X
6
: w: {5 K# [) t3 L2 T+ d. F& \s.str.endswith('t')7 }) [ N5 X a
# |* G) _" W5 ~6 x x K& J0 ^2 k8 J
0 True
* {6 H: E. q. t0 r1 True' V1 _1 S4 T& `& W% E6 q# N2 _
2 False
. U2 x/ ?' Q/ C3 a7 A gdtype: bool
8 m: y' G0 J# N" w1
$ t5 o0 m3 h v- c3 i& U; [. q! W9 ^2# u! b L! h; B1 I7 B* V. U
36 N6 o4 S4 W: ^* p" N# ^
4
& h$ f a5 S+ g2 f7 E( x$ V5
; }4 f1 ~- R, [; P6
9 D% i+ ?+ ~5 r% m% \1 o0 qstr.match可以用正则表达式来检测开始或结束字符串的模式,其返回了每个字符串起始处是否符合给定正则模式的布尔序列。当然,这些也能通过在str.contains的正则中使用^和$来实现。(貌似没有python里的search方法): V Y ^3 n- w
s.str.match('m|h')9 Y$ L) n: F& Y4 x- b7 o# a# Q
s.str.contains('^[m|h]') # 二者等价
$ o( Q' j s/ k- s0 s5 J
+ b+ w' j6 y$ q7 f' f0 True
% r0 T9 l: b: i% E3 Y" w# t1 True
7 y) z7 u8 l5 F6 D) ^5 W2 |! x+ I3 y2 False
% A% l+ k% Z G& gdtype: bool7 j$ ?8 ^* o# h+ H0 N0 R; T
1
. p% A. Z, L" f/ N8 j, ~2& h- N/ R; j! F& q! y+ {
3
9 C P2 V' ~: w* F5 o) [3 K4
; f6 ~- z. v. n5# k) i2 e+ \/ z, c/ t, F3 u* N
6$ J7 x4 [( E5 X* I( Q; a8 |) B" b
7) J* ^* P. u( l o( h* Z; Y
s.str[::-1].str.match('ta[f|g]|n') # 反转后匹配
8 P$ D& `: R% m4 d S$ W- xs.str.contains('[f|g]at|n$') # 二者等价2 p: e- b: h) r0 J8 x2 h1 J3 m5 j7 r
% g* a$ D4 Q3 A7 S0 False6 ~! A* G$ l' v9 t
1 True d( u' h# h1 ~* i& y3 F
2 True/ {/ e" X8 l- b. D
dtype: bool
1 W) d* y' k( I* V2 T" ?1
6 H w* J5 Y' N# W2
$ s. F9 a: u( \/ m+ E37 b) S! \. [4 T4 P
4
; e6 P- D0 U' f. T5
- E1 ?' A) r8 T4 r; J63 E+ u# F+ P7 `: M
7+ @7 }0 D4 O0 N) Y
str.find与str.rfind返回索引的匹配函数,其分别返回从左到右和从右到左第一次匹配的位置的索引,未找到则返回-1。需要注意的是这两个函数不支持正则匹配,只能用于字符子串的匹配:
6 r6 F0 }( O7 v4 ]. t6 a+ F% @s = pd.Series(['This is an apple. That is not an apple.'])
% y" T: B4 {2 g7 W% d$ A1 V, s' N% k$ I" L& n6 y5 l" r
s.str.find('apple')
& a( v! c% l% vOut[62]:
+ q8 T% N5 }: R' X1 k2 w! I* w5 K0 11
) C, P0 q E* D" e' w5 adtype: int64
$ C: H6 l$ c X" l, {" U& H H2 \1 s" i9 P: Z: r
s.str.rfind('apple')
; `, d( y$ D- B: s5 U+ |Out[63]: " Y0 F }% ?) m m+ x2 A& b
0 331 O. e1 f4 m/ a1 r; L
dtype: int64, h9 M5 y; B5 k+ i
1+ a+ I& S( H& l; Y9 o% Z: e3 w
2# f) |4 a! v6 u4 b. a
3
- U. _* j/ I+ o; c& i7 y5 a8 z: X49 h3 L1 v( b1 \$ p+ {2 e
5
. b+ |- o* V' C* d1 f# R6 y! l _' S& \( j6
, [% u; f- A( H6 j) [' |7
2 r; ?' l% V5 ^& I80 B- e4 P% p0 r0 i5 a6 b
9: C& o/ T; T" _8 |' j7 Q
10
2 L8 O0 H4 R% n8 f$ z, I5 N11
; w3 ?- |! I4 A, T- C1 j" ?) _替换" v2 _% k( S+ W5 d5 V5 g; P
str.replace和replace并不是一个函数,在使用字符串替换时应当使用前者。+ E6 k& R/ X5 y
s = pd.Series(['a_1_b','c_?'])
- \+ S( A) Z9 q, k' u% ^7 Q1 _# regex默认为True,表示是正则模式,否则第一个参数内容表示是单纯的字符串,也就是匹配字符串\d|\?
! P$ z3 m- A5 c* {8 d/ f6 n' C/ ps.str.replace('\d|\?', 'new', regex=True) 4 F3 j( i4 \3 @
0 N4 h! ]0 u; ?* x* j: B0 a_new_b
! f* Q( i, F* [1 @1 c_new
6 J8 P- o9 R- R! z" Ndtype: object
1 v) Y! O& Z5 |1% j- X2 B* m9 F
2" d0 s% B7 ~* u6 y9 m7 ^
3
7 J3 @! z& F# J: u0 t4
% W. n( A+ J# f& l. H4 ~: v2 A59 o/ g) p0 P7 ]# e7 F$ P0 X9 G6 P
6
% b/ C# i# \) x5 J6 B: K* {2 n3 _7$ @( @- s3 d" Z5 Y
当需要对不同部分进行有差别的替换时,可以利用子组的方法,并且此时可以通过传入自定义的替换函数来分别进行处理,注意group(k)代表匹配到的第k个子组(圆括号之间的内容):
2 J; W5 [) i) l* ?; B1 y+ M- q" t4 ~! ]+ X
s = pd.Series(['上海市黄浦区方浜中路249号',
3 K8 e( J0 I8 n G: R' N '上海市宝山区密山路5号',, |. f; A. B' z. y: d
'北京市昌平区北农路2号'])* q7 ~$ M/ j5 A& z) U
pat = '(\w+市)(\w+区)(\w+路)(\d+号)'7 @: z5 M+ `) X% V y3 P5 u
city = {'上海市': 'Shanghai', '北京市': 'Beijing'}! n/ i5 J' Z9 K$ ?% C* \
district = {'昌平区': 'CP District',% |' Y. E0 o0 O' w/ w1 K
'黄浦区': 'HP District',
* J7 |! _7 a" Q '宝山区': 'BS District'}6 x% f$ M3 N8 }+ x* S+ G# O
road = {'方浜中路': 'Mid Fangbin Road',
. l6 l, `* j! q2 l! v4 ? '密山路': 'Mishan Road',
) K4 E2 S1 ^! U, t9 h3 g: F: t '北农路': 'Beinong Road'}
6 I9 I1 q k# Kdef my_func(m):
, G: |0 }, C+ J( S* O/ q$ S str_city = city[m.group(1)]9 @) ?6 Q* z7 f( H% E
str_district = district[m.group(2)]) W* p( W! l: E1 [8 p
str_road = road[m.group(3)]
* s' } j& F2 Z9 w' g str_no = 'No. ' + m.group(4)[:-1] R( l: d, u- T% P9 W# ~( R9 `
return ' '.join([str_city, [5 p" |# y/ k2 b/ C# \0 E3 E
str_district,
" |4 p, T7 j* ?. H8 }# q. ? str_road,
0 D0 Y: x( e. V6 { t" _ str_no])
9 k0 F* t& v( p2 Rs.str.replace(pat, my_func, regex=True)
: o; h0 C1 R8 B# x9 y
! v4 ]* C- z6 {5 ]6 p1
) Q1 D7 |8 |( {+ s9 x: @2
. X9 W1 e0 g& i( K2 N3 b" d3- k, F6 B2 v% J! c6 g6 ~4 b9 o
4
k# g) h' S, D0 }& F u5/ }) f8 y& u7 C; \7 a
65 r# w/ S5 O6 x
7
& m; R1 U- h: a7 @! U. W1 x8
) d) j, G& Q9 ] c9& U9 _. S) J3 c3 t: q9 D3 m- j
10* Y B" B2 H& x
11
5 u8 P! D5 y) b% q8 [: @12
5 X, r/ z2 c" C* C13
8 b- i/ G8 ?/ A14, w6 N- c4 D3 A2 @
15
* ]2 y4 i: `5 L- x- [166 z! ]4 U2 }' X0 w3 O
17- M: l& p8 X' M& U) W0 J
18
9 k2 V+ x3 V8 I1 p19 ^% B. `3 V8 \! }( s3 ^& W8 o1 r/ G
204 Z9 p( H2 S( X( [
21" d7 D- D$ k/ j
0 Shanghai HP District Mid Fangbin Road No. 249
b( w% T0 w7 J4 f8 M9 P1 Shanghai BS District Mishan Road No. 5) @& z& L5 y, y2 K) r: l# ]9 S: U( d
2 Beijing CP District Beinong Road No. 2
5 z! C/ ?8 x2 k* F, h( t( j, ydtype: object B( u1 @- Y7 N, G4 ?
1' H5 {( n* K4 J+ x& M/ d. x
2
8 U4 {% {' s, H; x7 f# M& n9 T3( G# D" s) r; h- R F
4
$ q+ |( o$ {3 P& V- [% ~这里的数字标识并不直观,可以使用命名子组更加清晰地写出子组代表的含义:3 t' w4 S0 ~7 o/ i
) X8 ]4 U% C5 ?8 f
# 将各个子组进行命名5 A. j& V' Z, K$ i- z- x, T4 ?
pat = '(?P<市名>\w+市)(?P<区名>\w+区)(?P<路名>\w+路)(?P<编号>\d+号)'& Q, A7 x. D/ X- O7 _! ]. d7 c: \
def my_func(m):* U/ V; w" ~$ L6 \' Y4 I" M% u" q
str_city = city[m.group('市名')]
+ a$ [+ U/ y: u. c str_district = district[m.group('区名')]: @& x! E/ A( V3 ?1 x. H8 r
str_road = road[m.group('路名')]
2 g% F# D" [ f; j3 C4 L; Y) J$ P4 [# t str_no = 'No. ' + m.group('编号')[:-1]
0 E$ u% w6 S# Y1 ^! Q# w) c V0 ^: v; m9 Q return ' '.join([str_city,# K: o$ X( u; y) w K
str_district,7 f6 F2 N7 |7 z8 J1 z9 L% g
str_road,
9 T- W7 w+ S. b str_no])
/ S/ G" H$ ]5 [# I& {8 Ks.str.replace(pat, my_func, regex=True)
2 K1 Q' ]/ y' M# @# u6 S$ z: d* `1$ s5 N1 \' B( k
2& O% M5 [5 L2 g9 O% ^
33 ]. ^$ ~9 l) E" `
45 k8 v$ m2 g: \, I+ A( H
5- A$ f W4 _# y& p7 }6 h- A% l
6
! }" h P8 @, H' |7
$ z7 i. x" p% H# u; H8; @8 q8 r+ t, h: Q2 g! X. Y
98 E* o S/ F/ ]
10& q0 l; P8 I. [) V8 t; a5 q* U$ D# _
111 j" Z B8 ^0 K/ s
12! f/ N; L& k1 i
0 Shanghai HP District Mid Fangbin Road No. 249
, ^$ c- ], E8 A9 w& C+ j1 Shanghai BS District Mishan Road No. 5
$ T% `; s: Q) G/ m# S* p2 Beijing CP District Beinong Road No. 2
/ @1 y; k( ], Ddtype: object) p0 N7 b1 \- j5 o0 p) j; t8 d
15 K+ S0 E7 C4 u" V! V
2
1 O- Y8 F* N& u0 `2 d* p3
, R4 y. j9 f! C1 m' }- q/ \# @$ m4
1 P& d, k9 d6 ~: r2 a5 o# i" Z 这里虽然看起来有些繁杂,但是实际数据处理中对应的替换,一般都会通过代码来获取数据从而构造字典映射,在具体写法上会简洁的多。) [- W% O: n+ B9 G& E# j) @
1 C0 X+ W6 p3 F7 ] H8.3.5 提取1 v( T: f' P. W3 Z$ ^% Q$ _- }8 o
str.extract进行提取:提取既可以认为是一种返回具体元素值(而不是布尔值或元素对应的索引位置)的匹配操作,也可以认为是一种特殊的拆分操作。前面提到的str.split例子中会把分隔符去除,这并不是用户想要的效果,这时候就可以用str.extract进行提取:
5 t- f' Q+ T4 h$ K9 Z$ Fs.str.split('[市区路]')
+ i* a0 J: G `- U% lOut[43]: 8 l- L; k: B: n. L) r
0 [上海, 黄浦, 方浜中, 249号]
8 c" f; _) J+ Y( O# @" r1 [上海, 宝山, 密山, 5号]
! C" ?( W6 o% M" R2 N' v# Cdtype: object
- t2 }3 E: r1 _/ n' s1 x) B; _! g( ~7 ?% q) U
pat = '(\w+市)(\w+区)(\w+路)(\d+号)'
7 w4 I1 [8 E/ ^& D* Q- Ts.str.extract(pat)
7 h6 b' Z8 a' x6 W+ I1 LOut[78]:
. \: J9 R! z% d; R/ t2 z# S 0 1 2 3# h! x% Z6 d1 M4 {2 e- }9 k8 ^
0 上海市 黄浦区 方浜中路 249号& w& R+ e- C0 [: V4 ~
1 上海市 宝山区 密山路 5号
0 k( i6 N T( m# _/ q& W. e2 北京市 昌平区 北农路 2号
8 u% Q$ X- C* ?6 n) i, P# \. s0 W7 z1/ C& R) e! {* j9 O
28 ~$ }& d6 I/ a* o4 S0 M
38 A, ^# I# Z0 j, \9 c( I Q
4
& A4 @- p/ e: Y/ P. ^! `+ ] A- N8 {5
2 f' |/ s. v2 [% `5 K0 j2 d6 a6
. s8 k- U# P3 U5 h1 o% |! X7# K$ B. d& @' {4 A$ o) F5 g
8
3 v* R9 R! _6 Q2 @% p* q, }9 s; x. z0 g+ n; j
10
3 `& r, d8 a( X, O113 |5 Z6 y. T1 _: C D
12
1 t) X u0 V1 L- m1 Q13
! K; |4 K6 A3 c( E' ~通过子组的命名,可以直接对新生成DataFrame的列命名:
- W: b) \0 f4 B" Q! f
9 M9 J" B% j$ t9 w ` W0 X1 Dpat = '(?P<市名>\w+市)(?P<区名>\w+区)(?P<路名>\w+路)(?P<编号>\d+号)'7 o0 W8 G9 y1 k4 P
s.str.extract(pat)9 \% {5 \6 T6 S1 a
Out[79]:
! q6 H3 {9 A0 d2 a9 T 市名 区名 路名 编号/ O4 A" [- y3 Q3 q: i( Y# i
0 上海市 黄浦区 方浜中路 249号. ~* D8 Y- \. K! _' \$ \, O+ Z# O
1 上海市 宝山区 密山路 5号7 V' M' J* j, S0 d, L$ r. C; P( E
2 北京市 昌平区 北农路 2号1 y. w+ o5 a% ]7 R7 h- d
1
/ ?# M0 d* `! S) x' a: ]$ f22 u% x0 `6 N2 f" X/ |2 V: A
3! T/ ]; t/ Q+ O
45 N+ t) B& d$ [* U2 K& Y' L" V: W* v
5
+ U, J; d5 V C8 G) d2 c1 y6" z; x6 E J) ]' S) Y4 o- k
7
! E/ C: L q D) n1 pstr.extractall:不同于str.extract只匹配一次,它会把所有符合条件的模式全部匹配出来,如果存在多个结果,则以多级索引的方式存储:, }$ n, c3 P+ ?( Z: ]8 x
s = pd.Series(['A135T15,A26S5','B674S2,B25T6'], index = ['my_A','my_B']). v! ^/ U3 n+ I! q+ X' q* S
pat = '[A|B](\d+)[T|S](\d+)'0 k1 r+ M& O6 |( j5 m7 K
s.str.extractall(pat)
' d' I: u1 t$ A6 a' O1 nOut[83]:
7 `! R& M( r3 i6 _ j* W 0 1% L$ B4 D8 l4 i$ j h5 f' N
match * _% ]5 k! U5 Y- J) f4 l# a
my_A 0 135 154 k' \$ H, J+ w% e, e% Q
1 26 52 i# `" y z( A
my_B 0 674 2, q$ q; s+ I# d* u. I, l- O% w
1 25 6
" l! q2 ^( C( E9 v" {; u" N1
4 G0 U- Y+ b7 O+ a7 a4 P; t9 W2
6 R7 l8 s7 e' t' F- I' ^3. L% |" ]. f9 `) |
4
8 @! o2 |! n" j5
1 t3 w* H2 z$ J, j$ X, \6
$ b) ]( T! x! P* `, `7
' f# J$ \1 ?5 n" C9 X0 m8% Q7 Z& [' @2 i2 ]5 k1 f/ J' r
97 w8 w; W! ^7 [) k. l
10% D- y) K6 l; ^- ?0 w
pat_with_name = '[A|B](?P<name1>\d+)[T|S](?P<name2>\d+)'6 L4 j. |" u4 L; M' U3 e: B
s.str.extractall(pat_with_name)+ ]5 S( ?4 X4 C4 B3 ]
Out[84]:
" i) t; f* U0 a% i name1 name21 r7 S$ e/ C, G+ _
match - Y- }% A' i: o7 R. [6 I
my_A 0 135 15
, J9 L! [; k4 J/ W, ?8 a: w 1 26 5$ s0 X1 L$ q4 X
my_B 0 674 2
) E& ^, l2 s; } H- s$ _ 1 25 67 b, O# H6 z7 b; O7 x
1
( Q$ ^$ @; u0 }2
# C# V% E0 X% F y6 W! E3# a" x% K: b* `3 `! `* Z, h2 [' H
4$ u+ t9 d: a+ H" A! \
5; J; e) d; h* L2 |* H; N
6
' Z9 e N/ D D( D70 L4 N! L) |1 }" ^' L7 A$ J8 b. ~( h7 a
8# q' p0 c5 ~' m- \; { s
9* `9 j0 T F* T% }7 Y
str.findall:功能类似于str.extractall,区别在于前者把结果存入列表中,而后者处理为多级索引,每个行只对应一组匹配,而不是把所有匹配组合构成列表。
# Q0 R4 j+ l B; [1 S( qs.str.findall(pat)
7 ?! h! ?5 D$ Q8 f1
- F7 q! ^( s0 N+ g7 _4 Mmy_A [(135, 15), (26, 5)]
2 J8 V9 a% @+ @my_B [(674, 2), (25, 6)]
# ]+ f# K4 a! U$ ?7 sdtype: object
! x" g$ N. |9 N4 w B( Q/ P15 u4 j. Y6 U v
2
1 `2 g# ^) ~1 T; y3
& n/ F& _& M5 N6 |, Y5 h1 |8.4、常用字符串函数
, @& b( W- u9 t 除了上述介绍的五类字符串操作有关的函数之外,str对象上还定义了一些实用的其他方法,在此进行介绍。
; I3 Y8 t F$ `0 p3 J' I) t" a' n9 f' z* L& N' I; Y
8.4.1 字母型函数
9 a; i2 z7 E8 \& ] upper, lower, title, capitalize, swapcase这五个函数主要用于字母的大小写转化,从下面的例子中就容易领会其功能:, |" ]2 e1 b7 C
6 X4 J& ~/ }9 W2 ws = pd.Series(['lower', 'CAPITALS', 'this is a sentence', 'SwApCaSe'])
9 E& k. G# N0 ]
4 f T+ B/ ]( E& Q! ^s.str.upper()
; H6 F4 B5 b7 UOut[87]: 7 e. P j* ^( l* W5 M D% U
0 LOWER
8 x% N) \( a8 @# }1 CAPITALS
% t1 j5 }0 C- l9 z% s2 THIS IS A SENTENCE( N4 F7 a ]8 r+ v% c. U
3 SWAPCASE. q* D1 }* [& k1 W# o
dtype: object
% |7 l' s0 F& P- |, p3 g5 Q/ N% a* h+ ?- d3 e, I
s.str.lower(). P; ?+ E8 H) G [5 O/ V
Out[88]: : C; h) f+ o/ o
0 lower6 Y6 z( o5 v$ C* J
1 capitals& M3 G1 ], Z; M; ]1 M3 x
2 this is a sentence/ |8 y+ A: H j2 A3 f- E
3 swapcase
% O7 D# i9 o" \& ddtype: object' F$ A7 ]- H' ]
2 S* B3 I% P% y! N1 M( _
s.str.title() # 首字母大写) ^7 e+ H1 O S4 c
Out[89]: : o& W: {% a! i6 C: B: a& a
0 Lower
. y, W0 k* z: p$ Q# \! J1 w0 j1 Capitals
! |* f" {# H# Z3 E1 l' Q. V2 This Is A Sentence
7 ]1 O5 b3 X J7 g3 j" W, y3 Swapcase# K1 k6 ~3 _+ N& e
dtype: object$ X. X4 i6 z: k
& {- O3 ^! w5 d0 }) e4 Xs.str.capitalize() # 句首大写
' ?1 E" B% W. n3 d$ s5 T f% QOut[90]: 7 a& J) o8 [! E Y
0 Lower
+ W, ]) c I: L& |2 a c1 Capitals
- D) z$ M' U; f. I% S2 This is a sentence
& K/ @5 h( Z+ [, n: I3 Swapcase) Z+ S. a$ Y% a
dtype: object
- r3 m+ o& T5 Y$ T0 l7 u; R- \% Y: I' I! u- ~1 a' F
s.str.swapcase() # 将大写转换为小写,将小写转换为大写。
5 W9 E7 b# D' f. u6 b$ VOut[91]: @/ W P- |! J
0 LOWER3 m- c y h4 T+ @! J; s# `
1 capitals
: E9 D6 V* Q, b7 ?" x5 s" {3 z2 THIS IS A SENTENCE# e1 O1 e2 R2 `+ o4 S
3 sWaPcAsE. d3 ^1 [ z- u
dtype: object# C8 w* w [5 z* e
: L# F" n' x, `5 z. w2 Y' ms.str.casefold() # 去除字符串中所有大小写区别
* C: `% e4 K2 U
! v% h) {9 J0 r% _0 lower% O+ [, `, P0 ]0 v4 ~" C# a( I w
1 capitals
% }2 P# H6 `1 }8 L4 C# w2 this is a sentence
3 S. D0 o$ W5 _. I6 g o o+ L3 swapcase+ r- a. L4 U+ Q# f, a
; ?3 a0 k1 |' o8 A1
0 s' R) N- m/ Q# _2 _, `6 Q2
& J" y6 `& K3 j, U4 Q) L0 b' r3$ p' J/ U/ {* [4 k1 x3 t B, f
4
5 R; W- `# U; G2 r( V, V5
$ O; T: Z& }" g# R4 X3 t) a5 t8 R/ b6
0 ^* A' y; J8 z) j, b72 | [0 _9 t! L9 Y0 @
8
7 f$ c4 u, U+ ^9) W, u+ @+ G& h8 K b
10
8 i4 G: c+ r9 I$ k11# B. D" A$ q1 ~# Q+ I% `" L$ w1 f8 ]
12 A2 i. `. t3 \9 h- J- `% r" n
13
9 I8 v. W- M' ]- w14
: y+ N6 s3 E( R, Y1 f& B( H2 l h& u15
& L( ?4 ~5 }" H$ ] \' q6 D16
5 |5 x: V5 P8 ^17' P) A% D! F8 [! z7 |3 B
18
( ^, C8 u7 b% q0 ?" T6 |+ e' l/ o/ U19
1 |) G5 \ m0 _& l& W# c) C20 {" T; d. L7 h3 B+ Z/ \
21: ~% k4 Z8 f. f0 O$ D1 B! v
223 f- I, P n9 x" Z$ S9 }
23( H b+ \/ y e5 ^, n, L
24
9 m! e; h4 |- S/ u5 Y& ~25 E, I& R" C d5 ~8 ]1 T2 |" A, e
26
+ ^: j( ^3 d% Y3 f6 w- t- [, N27/ t7 W8 @# j1 Q. H" n3 {: p" I. i
28: R( m6 P6 e" _$ q# `$ Z
29
9 y0 D9 l, \# a/ q8 o30, w" B% |/ R# j, x1 Y
31
% w. N5 z. n9 e* f32
, W% G. L0 x$ {* ?2 g$ E33$ n5 h9 o- b: T$ Y8 P& i
34
^1 V& D8 n7 P350 v/ z0 b; `/ S8 ^+ _" ~
36' o) [. h* V' s$ c4 n6 {" R
37
! f5 B" p1 k% Y- A, }0 q% [( v38& c0 P+ f; t4 j" `
39
& F7 Y0 C- ]" S! y6 Y: M7 ?7 j* w3 e40
; _3 d( I+ m ]& Q+ l- a2 h- O41+ S5 F3 k* |; E
42
2 I% i9 Q0 M( d# F, n435 ^7 b. k. O; G( k
447 w/ Q* @( {# x9 ?1 C- I
45% B1 O/ b4 z1 |/ d3 ]" W6 L9 u/ D
46+ i/ }# n4 s9 n' { t( G
47# E9 t. P5 w) W' a4 N
48
) s4 G# s) F2 c7 i- Y$ d8.4.2 数值型函数# W4 ]* e+ m/ {: K& I+ {
这里着重需要介绍的是pd.to_numeric方法,它虽然不是str对象上的方法,但是能够对字符格式的数值进行快速转换和筛选。其主要参数包括:6 O8 n3 ^8 ?) \! e, L+ P
% |" h( G d e. M) Z H0 H7 oerrors:非数值的处理模式。对于不能转换为数值的有三种errors选项:
9 K+ x# o' d* Y2 nraise:直接报错,默认选项, S& @( G: r3 d% ~3 B% N
coerce:设为缺失值
2 X3 o- E& w7 P4 R% w) J7 mignore:保持原来的字符串。
8 V- \# [# ^4 i Z+ Pdowncast:转换类型,转成 ‘integer’, ‘signed’, ‘unsigned’, 或 ‘float’的最小dtype。比如可以转成float32就不会转成float64。2 [" i7 m ^: b% \" X' K% y
s = pd.Series(['1', '2.2', '2e', '??', '-2.1', '0'])
! x; Z; T }9 l6 ?
6 }. B) r& P, H' B0 `+ Upd.to_numeric(s, errors='ignore')8 d* C- Z, {2 {) T' H& ~4 |
Out[93]: j7 q, a5 W. N0 h8 p& x1 |* {
0 1) G& q; j P2 D8 R% J
1 2.2
; ~) M( s b' E" E2 2e" c: e2 Y$ u/ r3 h! ~ f
3 ??& C% a/ R& Y0 d0 ?" r7 d# o8 F
4 -2.1
! q2 Z( X* k' v5 0
) s& U# ]4 e5 ldtype: object
8 s: r& _+ |; i: ~: E- L$ `
$ U7 n: Y3 _( c' v/ xpd.to_numeric(s, errors='coerce')2 l* N# m0 f5 h5 R
Out[94]:
1 B! k0 y1 X0 O/ }0 1.0; T: y6 k; E* z4 k2 D2 p5 l! @; n
1 2.2# f2 r( m( A0 X* ?" I
2 NaN
% p+ d% X3 g1 i# F& e& ~3 NaN
1 t! N. }$ Y# M# d7 W1 o4 -2.1$ x ~% O1 o" y& T2 \" i2 n/ z6 `6 K8 G
5 0.07 a& g$ z1 }4 W% X1 K
dtype: float64/ {3 A4 K7 n/ u6 b
5 r+ y) _0 H5 u4 r1 O2 i. Q1( ^0 N" ]1 P1 J
2
' N; C1 c/ ^8 R7 H) h3
: g* p2 y% `) D# ^1 |4
$ Z& k/ D$ c6 `) o! K' W. W o3 d5( Q' b8 q& W9 D; Z
6
! ^8 p$ Z4 w% L3 ]6 h1 a& k& i7. `$ w. q- g H( U( e3 t- W
8. m, c4 k+ P' H4 ?7 u L. R4 A
9+ `' ~" d( r7 k7 B$ q& q4 H8 }/ D
10
& T$ ^ s9 Z8 d( U2 f% u$ c0 |11
* S1 r6 b1 b0 |0 |& D125 U1 ^* x y+ I4 C
13
5 p6 {. q* F# o, V. X* x* ^: A14
, v- I: I4 Y) I15
& ?. s k! `: q; o3 V3 A+ J4 z6 [16
! h7 m; j( V0 j. x0 Q17
0 L8 |: ^: d+ Q8 D; {18, R K: S8 @ z
19& t9 d( n& R* C
20' z D4 G m" s4 A
21" C* J* ~, c8 I6 w: Y$ ?
在数据清洗时,可以利用coerce的设定,快速查看非数值型的行:
" h7 k9 G" Z+ `) T+ S! t# Z# s K/ z) W) @
s[pd.to_numeric(s, errors='coerce').isna()]
8 J; Q$ E. O4 N5 r9 rOut[95]:
1 p( l* h4 X1 d" T7 }' b2 2e
4 v$ z" u+ l; e6 n; j3 ??
# ^- ~" U9 v0 Zdtype: object
. r4 q+ S6 ?: z- S4 H# y1
9 H" A6 V9 L& p2/ f6 r- ]$ d. Z- s
3
# A4 y8 L1 o) G6 }2 ]- h46 t" j+ L+ J8 F+ g# T
5; Y1 X" Q( b& V5 d( [/ |3 {3 P
8.4.3 统计型函数
2 d) c2 m6 K+ |) d, N! ] count和len的作用分别是返回出现正则模式的次数和字符串的长度:8 i9 E0 P. [/ H2 D/ G
! [2 s" P& f4 C8 `: N# G: y
s = pd.Series(['cat rat fat at', 'get feed sheet heat'])
4 g1 C# q# v2 S7 x
$ `+ Z8 Z2 K+ m: v5 ^9 g- P4 ss.str.count('[r|f]at|ee') # |左右两种子串都匹配了两次
+ G- r4 ]4 `. UOut[97]:
& V; |; C1 i7 X- k7 l0 2
, x) s$ R5 D% _, B1 2
- ^$ @0 ^( I8 S" `, T Udtype: int64
: Y) B2 ]+ N2 E7 U6 [% K$ n6 H; }& H! u+ A4 c7 \% L
s.str.len()9 _% g: }/ O3 t" \5 ?
Out[98]: - E# r* F. H* i& N; A8 h9 K% ?
0 14
C: N8 f' d2 }8 ~! B+ `7 n1 19" M: \5 l5 ]5 o5 h
dtype: int64# z5 y6 ^& b3 X! |0 |3 d3 u8 E1 D
1. r n9 s. p4 W) h! V# |; T8 a
24 t* P0 r: |, R. D y) c9 j, O
3
4 \8 T. c. W/ m& C1 P' y4
' F" N" N( ]2 G' O% L0 m" ]2 g5 _5( h/ O4 x6 l1 ]4 J( _9 y
6! l& L* }3 m* F2 m! I1 [) r% m
7+ p$ _1 C- I1 W* z/ [
8/ t+ _% @% [! B
9
7 f1 q, F2 `5 a, ~7 f0 x: k10* L' ?! W& i3 Y
11% G4 w& y2 B; C; I$ x
12
8 R, e# g7 j3 `) D* E13
# g \8 Z' [, y( H* o% U- j' r8.4.4 格式型函数
0 t0 s5 y( V9 E 格式型函数主要分为两类,第一种是除空型,第二种是填充型。其中,第一类函数一共有三种,它们分别是strip, rstrip, lstrip,分别代表去除两侧空格、右侧空格和左侧空格。这些函数在数据清洗时是有用的,特别是列名含有非法空格的时候。# d3 V# O. c7 U
" q! \8 x/ {, ]" ~
my_index = pd.Index([' col1', 'col2 ', ' col3 '])
/ n% U7 |/ V* l2 Q6 ~2 i7 m' c) }5 `" Y5 L8 p. V3 `
my_index.str.strip().str.len()3 F2 {1 t! o u6 k" g/ I7 U2 e6 K S [# c
Out[100]: Int64Index([4, 4, 4], dtype='int64')
; \: n! y, F7 D0 V" _
5 i$ P) i" N9 t$ y8 Wmy_index.str.rstrip().str.len()
6 [& Q; s% [4 l3 f2 m+ YOut[101]: Int64Index([5, 4, 5], dtype='int64'). M6 O5 ?$ z8 A5 i
6 e3 w \7 J3 s2 a" Z% Kmy_index.str.lstrip().str.len()
) G3 E+ I& v. I4 M: O+ ~& JOut[102]: Int64Index([4, 5, 5], dtype='int64')
9 |0 Q x3 F" V13 I6 n V8 t' u5 n
2
1 U( E: t0 Q* q [# Y8 p3
3 X4 `% w& ?% g, ^- [0 E8 p8 p" ]" ~4
' u! n* R. N4 a3 K( a5, @! k: C, _) G5 s0 |
6
5 S2 u* \9 r1 H% y/ s( r# W7
0 Z3 W1 `$ w7 Q4 q) `8
" e9 K* T, d- `7 x9
, N6 x7 \; `$ J1 \10
4 W- B; n! R: V5 Z, u" T( a6 g1 U. E+ B 对于填充型函数而言,pad是最灵活的,它可以选定字符串长度、填充的方向和填充内容:
6 |' c5 y6 V) x" Z- W) h
) f& U+ x' ]3 f+ A0 z; c( xs = pd.Series(['a','b','c'])
4 }' [; C! D8 B% C& w* S+ r: m6 R+ G& x4 a- `- o: A
s.str.pad(5,'left','*')
' [3 Y, ^3 Z3 _- U2 u& LOut[104]: ) @" F1 {3 ]1 q1 K& P
0 ****a, j: v7 T5 N) X
1 ****b' C8 e) {" P9 Y, G
2 ****c- m1 z0 I D1 m" k9 U! O: _
dtype: object
" i+ h7 f0 b2 J6 ?: k
% ^; ~. O: P- l6 xs.str.pad(5,'right','*')7 B% w% D C7 X* w! ]2 H, J
Out[105]:
/ w( q- L* O* W Y4 H0 a****
1 T, ]& ~8 L8 ^ c: d$ S. B1 b****/ d( o! A; q/ t$ B- i" Z; J
2 c****: p' n5 j7 D8 T* q i$ e. W$ Q
dtype: object6 e2 t5 W5 I, V7 V! E% V0 ?) d, n
' a" A) v3 R0 X9 a3 s" J
s.str.pad(5,'both','*')! H1 U3 _; \) M6 \+ Z/ K
Out[106]: ! {( v- @5 w/ U0 |. n3 r" ?8 |4 P2 n
0 **a**
& E1 D- |4 e! l% s7 R1 **b**7 b" {4 U0 F! o% Y5 {
2 **c**$ L3 g' A l7 ~3 D" j- P1 A, D3 T
dtype: object
# Z, Z' T" y% H8 S/ m8 B g3 J& O. }( F% a- \. A
1
5 c2 t7 u' Z( E4 w' Q5 S1 s2
e$ ?5 s( U0 L3* U0 o5 c; }, b0 t$ B I* X
43 i5 x! R2 x$ O6 x" B
53 B, E' b; Z8 p1 C j, j
6
7 _: C. D9 `9 V7 T; \7 U7 t5 f5 |7
+ G; d1 h" Z( s3 F4 n8! @* X5 D3 g P3 t
9
+ T9 R- Y" N* M7 x" x$ S8 |10
( I5 D0 J' N1 F/ |7 h. S$ Z+ c, u11
$ f3 F! J. }/ k2 T7 `12# Y+ P' k8 L0 X6 Z6 L$ @" @
131 M+ G+ F4 N% @% B# A( @. H$ Y
14) D7 u2 f! a# \# c- {
15
& `, V% W8 S/ K! f9 Z16
* q0 Z* b7 J- c+ [; O: U( t$ K$ Z0 G17
* m- M ~* w, J" p* b( D. e2 x18
( H! S5 u1 {$ x6 [19( p5 z) A7 G) k. O$ F& Y* }. S
20
( ^. k5 w/ Q$ z0 g21
. ]. m- u, L4 U- G+ I22
: _1 S, ~( n [5 \; n# Q* V 上述的三种情况可以分别用rjust, ljust, center来等效完成,需要注意ljust是指右侧填充而不是左侧填充:; m6 w" u2 v5 A( i5 p
) P4 w6 P, ^9 r" \# z6 Js.str.rjust(5, '*'); {/ f( I/ ^" r0 f8 A: z
Out[107]:
! x" b6 |, M- m1 o) q( b! ?8 L0 ****a
8 o, B+ @9 @' b8 x1 C: F' g! G* m1 ****b3 P$ R$ x8 r4 [% Y; w8 ]0 h7 z. d
2 ****c- J3 x5 C6 t" v# J3 k9 x8 L+ H
dtype: object
- b% ?1 R; J! v; r/ c% S) i! ~" [: z( Y; @3 V" g
s.str.ljust(5, '*')
9 a) B3 c% Z: q) V- p! ~$ WOut[108]: " ]* B% M" {& F% d8 d7 \$ [3 ~
0 a****# i( S7 B* B& O2 q
1 b****/ a6 \: f1 a ~* S( Q
2 c****
, R' Q F) j$ Z& O i) y5 ]1 z# Udtype: object
/ v& ]7 i# w/ _, c U) i( T* |4 A: k5 m& Q: Y& Q
s.str.center(5, '*'), ~- q# O. A0 f! j
Out[109]: * f$ X+ N/ \/ H+ F& g2 u1 z
0 **a**
8 i7 I- i6 j& e; }1 **b**0 M8 q3 F& m, O$ e
2 **c**/ [+ S$ _7 ~2 ^1 s6 l, S. j5 L
dtype: object
9 @9 ]4 Y' ]* U5 S4 R9 q. k$ H* x) f4 V6 l( S8 `) a# c
1* X" y8 W+ B* h i( x# ^2 S. w* w
2
4 F+ Y+ X0 z o4 x5 E3! Q7 X! @. B6 D
46 @3 E8 Y6 j. |4 K2 y$ {( R' B" n
5% ]+ F- d8 N8 z# x" e" K
62 |) R3 V8 w7 C; {
7
3 G0 \- I& A- c9 W8/ H# r3 F2 D) w' [! K U
9# ~1 M$ D3 k) N
10
& K! E7 P8 ], T7 D$ p11
9 Q% k. O' E& _( `6 Z% p& G12, D3 B [4 H9 a
13; B+ x5 z2 T3 A+ e2 U
14# D6 _: a8 W* r( {8 K3 U1 l
15
) O- D: \9 G" c- E: G5 {16
& K# q6 r9 D! `4 g) v17" c2 D% U3 B$ E8 \
18
1 }% V; G; C$ c" {8 t+ x19, I- ]4 K6 C( ~( _& u9 P
20
0 |/ ~* ^5 S$ l2 K 在读取excel文件时,经常会出现数字前补0的需求,例如证券代码读入的时候会把"000007"作为数值7来处理,pandas中除了可以使用上面的左侧填充函数进行操作之外,还可用zfill来实现。
) h$ k& C7 j4 [
$ F7 j1 I' S' k+ Q: i* Hs = pd.Series([7, 155, 303000]).astype('string')
! d" j+ u) o2 o% T- u. T' o; {0 s; n1 ?6 L% S
s.str.pad(6,'left','0')
5 C- V1 L S, N* AOut[111]:
2 {( S7 S8 R, N0 000007
3 I$ q! i$ a- N6 h( x1 000155
/ ^" L5 b" b) b! ^; |; l# a4 X* F3 i2 303000
6 N% p; B0 i$ s* G$ Cdtype: string
6 w: s3 y2 R- ^8 l9 p* N- d% s2 g
s.str.rjust(6,'0')7 S1 z6 z. Y. G; i
Out[112]: # l1 R, q8 u4 ]; ^& x
0 000007
) ~8 p8 H4 m; ^2 H# s1 000155
& U v8 B2 q5 n- J2 3030005 c1 m6 y' i+ [! M# e: [$ y4 [
dtype: string0 r- _+ F2 ?+ k4 d: x9 W2 r
7 D3 h3 n- Y. j1 w% B& W$ zs.str.zfill(6)
3 ]: V9 [; ?3 F7 g2 R8 J3 ^Out[113]: 9 h2 `+ R. C* g$ L
0 000007
5 P- M: p; n; I1 I3 E& c0 t7 G1 000155
7 \; t8 @3 c0 b$ v7 m2 303000
8 [/ _& K0 ^3 J Wdtype: string
; m9 a' G1 ?% W7 ?1 W) P+ V0 ~/ g5 T2 a2 g- t0 b. ~0 i
19 E9 Z9 Z! S, k+ u+ Q5 O5 J+ Y$ n& R
22 S! G& J! w# ?$ `
3; m/ O- J5 u3 A. U S
4
- C n9 ?! x# t* d' ^% J5! o* O5 q8 E: l0 m1 G9 Y' V9 q# U
6
7 h% ^9 n3 B7 q$ T0 e& t1 n" c5 s0 W. Q79 r: U7 e0 L- ]
8
6 d; d) a' S# ^) @2 V9! w0 h3 Z; B$ O
106 f6 [) o o2 j
11* x2 x% E7 R) x; l
12' l2 d' a# D+ V8 t
133 A/ L6 }- g) g& Y
145 Q8 c8 ~! q4 n" {5 t+ \. I! E
15; f9 q( j3 |) f7 a
16
) b' @* N. U% u+ `. x* S17+ ]6 B" B, t7 P$ B, K. j: h; O
18
- i( n) w- Z G$ s1 C19* O1 |+ J* K+ m$ }7 @5 |
20
Y- q- Z0 Z( h! h' `21
2 v8 c/ q" ~" Q- ~22
2 O8 o4 H# z1 H% L/ B1 e- J W) W" r6 T8.5 练习
! I8 A7 ^) u$ {8 x( EEx1:房屋信息数据集4 B; f2 E ]. j* k7 o' K _ E% K
现有一份房屋信息数据集如下:# e4 e/ s* ~! m
' v6 t7 d" [( r& W; z% a7 Sdf = pd.read_excel('../data/house_info.xls', usecols=['floor','year','area','price'])4 c3 z$ u; j% U ]! ]+ U6 |. Z
df.head(3)
H/ V& l/ s i' I+ P; dOut[115]:
* Z( J- |( y7 p8 f% R floor year area price
* q- [* i8 g4 A* X1 t0 高层(共6层) 1986年建 58.23㎡ 155万 S' B0 \! H( U! W% K
1 中层(共20层) 2020年建 88㎡ 155万% f7 I. q1 [$ [. j( }4 t) d
2 低层(共28层) 2010年建 89.33㎡ 365万1 a/ C, I- B+ X0 Y- g/ O% b
1
( O6 `$ }. Y0 e8 X C: A0 m26 ]0 b! `9 S0 T6 x
3
, p8 K1 x+ ~! S& {2 }4- j9 g. L4 e( P! Q9 ~: O/ p" }4 J
5: K. G9 a$ o) M* n" V% e
6
0 U- r, T5 Y& x) c. ?* Z' N4 I5 \7$ _' G3 b g! A$ K7 C: P7 t5 Q% [
将year列改为整数年份存储。
" o' g7 k' a/ K2 G- D# h) I1 k2 s将floor列替换为Level, Highest两列,其中的元素分别为string类型的层类别(高层、中层、低层)与整数类型的最高层数。
* {, n1 A/ P% _" n, [: X. I5 O计算房屋每平米的均价avg_price,以***元/平米的格式存储到表中,其中***为整数0 f9 H4 E- u3 v/ @
将year列改为整数年份存储。: A) L O4 u' R' q( p) S6 G8 o4 P* k1 `
"""3 |8 D( Q! C+ \5 M
整个序列需要先转成Nullable类型的String类型,取出年份,再将年份转为Int64类型。
6 M& Y3 o' q1 {' }4 C* \注意,转换的类型是Int64不是int,否则报错。即使astype加参数errors='ignore'跳过缺失值,+ p) y$ M3 P5 n3 Z
转成int后,序列还有缺失值所以,还是变成了object。: e3 e4 @6 [. U+ S
而整个序列转为Int,就还是Int类型,缺失值变成了 pd.NA 。( Z* @% {+ g0 j
"""
9 _% V9 X) J: W; E2 x! Ydf = df.convert_dtypes()
( O1 J4 x. r: ~- u7 `( r# x5 x" adf['year']=df['year'].str.replace('\D','',regex=True).astype('Int64')) p+ X8 c8 p p9 A5 z, @4 i
df.loc[df.year.notna()]['year'].head()
' L! _2 n" s' c7 ?/ O: P* L* g# Q" q" s$ Y
0 1986- e$ F" J$ ]+ [ Y$ W/ h
1 20208 I7 C7 Q0 H* b O
2 20108 I$ W. h3 c4 d2 N
3 2014
! g0 n# n% U. z' v# c) V" z/ J5 ]4 20154 H6 ~" D* v1 O' h6 x
Name: year, Length: 12850, dtype: Int64( [6 P# w/ y {8 D
, u4 _0 K5 G3 j3 ]. `19 ^6 H* y! K: R. P1 a7 o4 V
2
9 G4 i. w) q- ~8 f# @5 o3
6 ~2 X* q; \$ Z3 l+ d, i( J& c5 o8 j6 |4
! C K5 n) Q+ X! M4 i+ `5* [* B; R: C" A2 P
6
5 @' m( G8 z1 H! `73 t- J0 s$ A" D8 {5 y7 ]/ l7 _
8) z! X* k) S3 `6 F2 D3 X" U0 x. T
9
3 f7 F/ i- I1 `. D) Q10. @7 z z# [0 _7 _
119 r' H1 U7 T1 |9 r s
12
1 u' E# D4 {8 C! |- G8 r/ x13
0 B$ V% u/ h) y; H$ J4 m143 E; r4 X4 M0 L1 {0 _
15; {! L* `4 d# [ I' @# [7 W, Z. _
168 l: {6 W+ N/ C1 x! _
参考答案:& d2 F+ y, ~- Z" Q1 i# S" j8 r
* c0 |4 ]3 Q5 x
不知道为啥pd.to_numeric(df.year.str[:-2],downcast="integer")类型为float32,不应该是整型么& H+ A1 i# i4 }4 j+ v9 k2 t
, b/ ]! t6 O4 Z3 X* \
df.year = pd.to_numeric(df.year.str[:-2]).astype('Int64')
- b1 T# Y& `/ s8 h3 V5 J7 N. Adf.loc[df.year.notna()]['year']
/ y- f8 |) b) ^% l3 s1
' B1 A& W) n, O0 A& F2
: k c4 D5 B3 R6 l: G将floor列替换为Level, Highest两列,其中的元素分别为string类型的层类别(高层、中层、低层)与整数类型的最高层数。
2 o( `" K( C/ l7 Lpat = '(?P<Level>\w+层)(?P<Highest>\(\w+层)'
0 t6 F" @: Z+ E1 j, K% r" V* S# ~: sdf2=df['floor'].str.extract(pat) # 拆分成两列,第二列还是(共6层得形式,所以还的替换一次
7 l9 g& e& T3 q9 }0 G7 x* ddf=pd.concat([df,df2],axis=1).convert_dtypes() # 新增列拼接在后面,再次转为Nullable类型2 U' a& y/ i. R- }
df['Highest']=df['Highest'].str.replace('\D+','',regex=True).astype('Int64')
$ }3 ~- O+ W3 \7 M0 T C3 _9 ndf=df[['Level','Highest','year','area','price']]
9 P, u; R6 [8 W2 B6 Q& B# t% Qdf.head()1 p5 ]- e! ~2 y# e. w( k! G8 H1 I
/ W3 w- l( a/ J9 T Level Highest year area price/ P$ t/ W" j( C, Q: J/ _& q
0 高层 6 1986 58.23㎡ 155万9 m4 Z4 N; E7 K6 X! }
1 中层 20 2020 88㎡ 155万+ [- P8 r$ a) c: d% Q
2 低层 28 2010 89.33㎡ 365万1 u/ d; h' o6 D. K M u9 F5 k
3 低层 20 2014 82㎡ 308万
0 H4 h2 x: F4 V" w4 高层 1 2015 98㎡ 117万
+ M5 W8 b0 e$ m) z9 q f1
& f: }/ J# E9 X* p, A+ O4 K2% _' Q8 q! R9 \2 ?
3
, d1 I; W [' T& ~* W* \' H4
; `8 U, Q, ~1 H3 z# b' g# `+ q5
/ @5 Y* j5 [( |/ T6
! v+ |3 Z6 D7 O" Z74 v, R) x" z# l0 M E
85 ~2 U; S0 u J! |* t
9
+ f6 |/ k# L; U" D P$ ]( G/ l* O10$ Q! _7 d/ B1 o: d) s6 H
11
( k6 y7 i# n9 ^7 n" B# i, U+ F2 l4 W122 c, I4 P$ |9 o: w: I* [% @
13, v5 x# R! N* E, l2 ?% i( @
# 参考答案。感觉是第二个字段加了中文的()可以准备匹配出数字,但是不好直接命令子组了
V8 ~0 v v' X/ ]* K- tpat = '(\w层)(共(\d+)层)'3 a' Q& l4 D/ E& l, }3 e* U& \* Y
new_cols = df.floor.str.extract(pat).rename(
6 l/ Y4 I3 H2 i% @- z columns={0:'Level', 1:'Highest'})
$ f8 k# Y3 P/ c1 Q' o3 ?2 Z! l8 ~& a. ^( O/ [" D* W1 E- Z& u) P
df = pd.concat([df.drop(columns=['floor']), new_cols], 1)
7 D9 f' O8 n/ v2 G2 r; xdf.head(3)
. u( p/ K4 f- X9 v. C0 D3 k: F1 ]8 a8 O! a( Z; a; ^5 K$ x2 }8 @
Out[163]: 9 [% W5 u; e( N7 `
year area price Level Highest
, A7 @/ D0 J/ \, Z% f- i3 H9 f0 1986 58.23㎡ 155万 高层 6
# P( L* F" M& t' k! A- Q ]7 E1 2020 88㎡ 155万 中层 20
2 U6 `, f" d$ V5 O# S1 h, q2 2010 89.33㎡ 365万 低层 28- i/ w4 M- z# t
1
/ O2 M N5 j( D( [25 x' ]1 F+ l' w: a3 R
3
4 o: q O1 |. [% J' Y* X) l/ z4
; K6 [' m' s: D! ^" _" ^ N3 L5
; _+ _5 ? @% M/ K0 S% J6
& M) V r2 {% ~7
2 E g; q, y. @. G% ~7 ^9 [" E8
6 `- I6 {" U: l2 M9
" Z" R" N; g8 |! ~8 v10( V: w( y5 G }8 }' x) z8 @
11$ Q- }, `/ q3 f( u& W( B
12 H5 n: z2 @2 v5 t" X
13 A- C9 f% w& S- [+ _6 b: I* o' k2 ]
计算房屋每平米的均价avg_price,以***元/平米的格式存储到表中,其中***为整数。
3 i1 @6 q5 V, Q n"""* I: b0 F5 Z) W' _
str.findall返回的结果都是列表,只能用apply取值去掉列表形式
. k% ` Y( p+ Y8 ~2 I: z z参考答案用pd.to_numeric(df.area.str[:-1])更简洁& m5 X; k: d( ~" W( i: @
由于area和price都没有缺失值,所以可以直接转类型 t4 C1 f% ?6 e# _7 G
"""8 |' h% j3 u, M) m1 K
df['new_area']=df['area'].str.findall(r'\d+.\d+|\d+').apply(lambda x:float(x[0]))
- [! I5 z7 H: s4 v2 f, l6 }1 v$ Gdf['new_price']=df['price'].str.replace('\D+','',regex=True).astype('int64')
5 p7 h; V5 E# X* sdf.eval('avg_price=10000*new_price/new_area',inplace=True)0 z" c2 p, j: A g* w4 t+ J" D
# 最后均价这一列小数转整型直接用.astype('int')就行,我还准备.apply(lambda x:int(round(x,0)))& c/ H9 B: W' _7 r5 w) Q% b3 k
# 最后数字+元/平米写法更简单# w5 y3 u, @" `
df['avg_price']=df['avg_price'].astype('int').astype('string')+'元/平米'% b9 ]; J: B3 ?3 {; ]+ H7 X+ d
del df['new_area'],df['new_price']+ g- w: u' q6 W( u9 R# _# a
df.head()2 Z$ |7 _2 x% {# U* S
+ R/ C5 I2 ]) [/ o! C6 k Level Highest year area price avg_price2 K: S7 q" l E8 @' f( m4 Z1 ~
0 高层 6 1986 58.23㎡ 155万 26618元/平米! h' z! {$ h% q5 y$ e
1 中层 20 2020 88㎡ 155万 17613元/平米7 ]# K5 `# o# m* }% B& `
2 低层 28 2010 89.33㎡ 365万 40859元/平米
: X' m: m. R. K& r7 [$ q3 低层 20 2014 82㎡ 308万 37560元/平米7 z6 N' Y; ]6 a: p. c
4 高层 1 2015 98㎡ 117万 11938元/平米0 a- t/ ?. L6 Y4 v
- W% P) S7 Y" c( p, k; ^0 _# b
17 N. v. K a+ d! g8 W
2; ^% _4 {7 L- j2 H; E# K3 B
3
- {7 {& f( H9 ^; d$ K+ N; V# A" ?. g4
! M! {- j. @- y5 P$ J5
& K# i& O, Z/ a9 J9 V8 G: ~6+ _; @' b& h) Y; u
7
" O: c6 v" m) M% b" j8 M4 \' _8! ]' @7 ]" P; F- G3 t; O4 O
9
4 z+ {, {. r5 ^8 m& \10
/ ?3 U# s6 S4 D: Q11 l) [7 |( L4 ^5 e1 X* P) ?
12
0 u+ Y, D4 S: O% d) i13
, f) K) ~: z R7 d* C146 R& n3 d0 V& g5 @+ [
15
0 L: ~4 _: o# {0 M) ], D# [7 R16
# ]5 c5 J% k; @17
- r* q7 O$ L( Z4 o187 _9 H% c. _' k/ q: I p" Q- G$ S [
199 G: ]9 o2 G4 P# X A6 k1 \
20
2 b5 Y- o# E- _9 K, q! d& H' w3 H# 参考答案( j% \5 g! [0 v+ j
s_area = pd.to_numeric(df.area.str[:-1])
, s0 k. v, s& M2 z; o2 b9 ys_price = pd.to_numeric(df.price.str[:-1]) N* |8 _5 x/ y8 e& i% |- h
df['avg_price'] = ((s_price/s_area)*10000).astype(- ^- N/ l- w7 M; B) ^
'int').astype('string') + '元/平米'* e6 E5 b1 h3 B! ^3 @+ c
- m" _0 G8 b0 o9 b5 z0 O% gdf.head(3)! N8 I' a, ?, s* L
Out[167]: ( @4 |- O+ [2 E8 j7 S: |; c
year area price Level Highest avg_price9 G5 u. A' c' H% E( i
0 1986 58.23㎡ 155万 高层 6 26618元/平米2 l1 e! N, ?% k( _
1 2020 88㎡ 155万 中层 20 17613元/平米
. B* \3 E3 w7 o `' K! } {- w0 H, R$ |2 2010 89.33㎡ 365万 低层 28 40859元/平米: ^ Y! D& z9 f, L2 p
1
- f3 K3 b" V; o$ M7 f$ w3 ^26 \5 l* p8 @7 h) h% ?+ n
33 S r4 }9 F* @0 p) \: Y! y" {- o' \- p
4
+ p7 e, V2 H, f7 G/ r% V4 Y5( K; [- V( I; j) {: h
6! j+ X4 D: P! y- H: d
7
9 l b" Q3 C4 F+ ^82 r- q! J. w1 l! N( x+ Q R
9
1 N5 C( K: g6 U2 K l/ d10
0 q# f$ _" D' L) F; {. Q. h( C8 J11& Y9 z* f/ `# a2 y3 C7 A
12
6 R) n! e0 Q# I. |" H+ ~ y7 W; v4 TEx2:《权力的游戏》剧本数据集
6 n' k5 }5 ~- U; C/ ?现有一份权力的游戏剧本数据集如下:
* {* [% @5 K8 A; O
8 s/ a7 P# ~7 f+ l! y3 t5 e7 Gdf = pd.read_csv('../data/script.csv')" e" ]( D* c& W
df.head(3)- a q) J2 _! R- T$ f; s
8 h) K5 Y; A5 J( w+ N8 {
Out[115]: " l/ ^5 Y9 N/ D4 B3 b# _- B* o
Out[117]: : s' O- |; P0 f
Release Date Season Episode Episode Title Name Sentence
8 ?6 `! K( |1 i: C; o% I) L0 2011-04-17 Season 1 Episode 1 Winter is Coming waymar royce What do you expect? They're savages. One lot s...: c4 \) ^, S# d6 F
1 2011-04-17 Season 1 Episode 1 Winter is Coming will I've never seen wildlings do a thing like this...
' y- G% T/ L- }9 u2 2011-04-17 Season 1 Episode 1 Winter is Coming waymar royce
- ^$ u9 D# @+ E5 L2 ]: I. K1
' R, N( O3 c- @2& {# T1 L4 j; Y' V0 E
3
+ X8 c4 v6 d/ ^4
( _! b5 c A, T& z' h5
3 Q* T. S/ H6 K+ F2 ]% @- C+ K6
, u L2 v/ S Z1 X8 p- i7
# E A; P# n- i" o O& G+ Y8
& q* J& v2 _! ?- B9; G3 q t; F0 @3 e3 T& ~* e2 L
计算每一个Episode的台词条数。- K6 E! |; X, p. E5 B3 W7 B
以空格为单词的分割符号,请求出单句台词平均单词量最多的前五个人。
0 w3 A* ?6 Q' ]& g若某人的台词中含有问号,那么下一个说台词的人即为回答者。若上一人台词中含有 𝑛 个问号,则认为回答者回答了 𝑛 个问题,请求出回答最多问题的前五个人。% S9 R. t, A9 Z0 X) j# ?4 v
计算每一个Episode的台词条数。9 U* C5 q+ {( ? c6 \4 q) u* g
df.columns =df.columns.str.strip() # 列名中有空格
5 I7 q0 U1 V8 m$ _# f+ W# odf.groupby(['Season','Episode'])['Sentence'].count().sort_values(ascending=False).head()$ I' M# z, J, T8 I; I4 f2 r
. X' M' Y+ B6 Y b* e
season Episode " x2 _) K# U4 D* T0 x" t- w
Season 7 Episode 5 5058 Q5 f4 p& l) w. x' a
Season 3 Episode 2 480
' W/ R) z2 p# U Q4 i* N: qSeason 4 Episode 1 475% _: r) t I4 N' p2 d2 U! G
Season 3 Episode 5 440
. t5 k9 t6 `: a8 Z$ ^5 H, ~Season 2 Episode 2 432
" ]+ \/ l7 ?. |% Q# P _! W1& o& s v2 }; {
2
& [ y$ g# G, v! w3) `8 C' O# h m! H
4
' T& {3 o: G Z& D9 b0 @9 ?5
3 d/ P4 v0 o$ G0 t: a. R6- C/ Y1 n" x" P* E; i. a
7
# n1 M$ H0 g. z( B1 M; }" {/ ?8
; D9 [4 H4 n4 B. B2 m* U; z* u9+ s8 l* L% p0 s- R) a, A: t6 W
以空格为单词的分割符号,请求出单句台词平均单词量最多的前五个人。8 W. u( J# \' n+ @: ?( d" V+ w5 m
# str.count是可以计算每个字符串被正则匹配了多少次,+1就是单词数
# @4 g6 n* M* g7 [* \, F; f4 `df['len_words']=df['Sentence'].str.count(r' ')+1
( e' q. V# J* n) h8 \df.groupby(['Name'])['len_words'].mean().sort_values(ascending=False).head()2 n9 U& l5 d, i2 _* h
+ \) F6 R4 M/ y4 N3 s6 A5 l
Name
8 o {& a! m, w- H* Omale singer 109.000000- l w% f& w( [4 k" d- z& z
slave owner 77.0000001 D2 T! F# @7 y- D
manderly 62.000000
) u: O5 m- A5 C; f9 w9 V H5 A( Qlollys stokeworth 62.000000" J* h; \* Y/ S; S2 u
dothraki matron 56.666667
7 ]3 r1 H8 r) Z* xName: len_words, dtype: float64
' g/ t! L7 p2 r% P* e! e% \, E1 P: }1( u; Z/ l) D+ K& _8 j- d- K! h
2
6 p& s/ f" r, O" ]3( r K& w4 P' ]! ?& q- y% J# b/ x
4
( [( q: [( [3 k* Y' P5
) B$ ^7 L2 Q/ D+ R$ @5 k8 z6# n) M6 N1 K* k: @0 m
7
- N9 e+ e t: Z# E( L8
# N# E+ }$ f. h+ d7 u: q6 c0 e93 u: B% c4 A3 h
10, `; l# j) ?$ y+ y0 i: [; r. _9 {
11
+ X1 C! C1 K+ E% k1 R& B若某人的台词中含有问号,那么下一个说台词的人即为回答者。若上一人台词中含有n nn个问号,则认为回答者回答了n nn个问题,请求出回答最多问题的前五个人。
+ y) y8 \( ~1 A U2 |$ Tdf['Sentence'].str.count(r'\?') # 计算每人提问数
2 m3 V0 ?) z3 z& pls=pd.concat([pd.Series(0),ls]).reset_index(drop=True)# 首行填0% I* B* }( h1 ^+ ~+ I+ e
del ls[23911] # 末行删去
" T! o9 H' |& {* ?2 [$ I% qdf['len_questions']=ls' e, }. T' Q3 S8 v q5 T( _
df.groupby(['Name'])['len_questions'].sum().sort_values(ascending=False).head()% o. k7 @* X) h/ t* r4 U* C
. O1 _* J! }5 G$ j0 K7 gName$ r. W& G: u2 L4 Z D7 ^0 D
tyrion lannister 527( b. Q9 V; J9 |; `3 ], A2 o) @8 W
jon snow 374
2 [; [8 D9 R& ]6 X1 \) W: Jjaime lannister 283
2 t. _ s( P; M: p3 q% Tarya stark 265
) y' P& h4 m9 J8 Y+ @cersei lannister 2465 j; d& [& ^) C5 ~ C: x: ~0 u9 K' N
Name: len_questions, dtype: int64/ ]9 ^6 ]. |9 H! Q' W8 k
# t& O+ m* Q! X4 t% x
# 参考答案
; ^7 V2 W( @! b5 u+ G. Ts = pd.Series(df.Sentence.values, index=df.Name.shift(-1))/ Q/ b5 O9 Y# U- B3 R9 N0 r. T
s.str.count('\?').groupby('Name').sum().sort_values(ascending=False).head()
% H* V7 P3 U, o) u6 n8 b3 p* @$ v2 K' Z* v$ T) w+ {
1
8 }( R q9 I6 ?# k+ t2
4 e$ w- D2 ?, w+ n# I4 |% I3
6 N% o- q$ b& K' Z4 B4 v4 t( V4/ Y& n+ U8 K/ A5 c# x/ I0 P
5
- ~5 O3 u& U. \/ V2 q s68 i3 U6 Y/ S1 h& `/ D; A& R6 E
7
, g4 i. u) d! m0 E4 `2 h% h8
9 C( t! z7 R3 V, O- Y9
9 g) G U" v3 ~10% Q4 F' _0 y3 y- W2 e
11# n, u0 V, A4 y% c% a& n/ N! q
126 k# g, I8 Y% v9 L! p+ M9 @. N
13
( c. Y" }; g2 y0 t2 ^4 x* X2 G14 f1 L/ T& } w3 n, E
15
a/ ^& n7 y& A* t) S) [16
. u( r4 d5 |6 o# {' v9 Z4 }" l179 b/ |. w/ k. F o% c9 Y
第九章 分类数据$ |; r: c/ ?6 ~6 X% K# b
import numpy as np7 L1 C% R1 ?( I0 a
import pandas as pd
4 l, \ }# F3 X# ^" Q0 U, `1
T0 N8 v' O( R8 u( N8 _4 {0 T2/ L# b! Q3 ~ k8 ^9 Q
9.1 cat对象. v3 x+ M" U' s0 x; h4 T
9.1.1 cat对象的属性4 I% Y: U9 ~6 |; z, J$ B. l
在pandas中提供了category类型,使用户能够处理分类类型的变量,将一个普通序列转换成分类变量可以使用astype方法。, a, w6 C0 g% F \/ s6 q
~$ R- N* G1 K Tdf = pd.read_csv('data/learn_pandas.csv',; K5 s6 `* l, {- ~
usecols = ['Grade', 'Name', 'Gender', 'Height', 'Weight']), _' v, m% v) R$ [& P+ g+ `
s = df.Grade.astype('category')1 f" h+ Z2 H9 j5 l4 C- r3 [
! c+ j" ^, X( f& h& r; Z
s.head()
5 b; l6 d W0 O( b- hOut[5]:
1 J; z a6 q% _$ Z, k( X0 Freshman" V5 t% Y* S, [, d- a+ a
1 Freshman# b; e" j0 H7 x2 M$ c& \. [5 Q" s
2 Senior+ T* x% f7 J: l" j# o# n- D
3 Sophomore& m6 g7 M" n% b* b* K5 @
4 Sophomore) {: s; O0 D, f% x8 L# ^ }
Name: Grade, dtype: category
]7 [, ]- b4 x$ V4 [( c" mCategories (4, object): ['Freshman', 'Junior', 'Senior', 'Sophomore']
5 L0 ~# S* i K5 N r1, _/ M& L; c+ V/ W
2
$ ?. P) }2 ~4 T. D) u32 z3 c3 ~3 @3 V
44 w' k) j. [! I) E/ J d
5+ V; _$ r- L3 m; B5 S9 y5 q0 d
68 O1 d6 Z: W. P: S0 A
7 K# H, v7 E+ N7 K
8# N' a5 T) L; ?% u' {
9
2 t6 h1 y/ q! D" r* v+ M- n; Z10
# N8 A. E3 U( O( j U110 p5 ~. C" L" @. X8 h2 c
12( `; f R; I& R, A' ^
13( h8 b. X1 r1 J# X
在一个分类类型的Series中定义了cat对象,它和上一章中介绍的str对象类似,定义了一些属性和方法来进行分类类别的操作。
4 x. J7 l' P% {+ l( ]
# K% y2 G0 J1 R1 F4 |7 V* \" gs.cat
. g0 ?9 l' N- F, S# E2 \/ I- ZOut[6]: <pandas.core.arrays.categorical.CategoricalAccessor object at 0x000002B7974C20A0>6 n! m* f! G8 d! I" H
1* h8 @. u3 Q" u7 W
2
4 [* g2 m- ]* z; J$ f, N. ]1 ^cat的属性:, u* t+ `1 ~/ M$ C: n6 J* x
# a; k0 F: z/ j' u' e& D) s+ N! jcat.categories:查看类别的本身,它以Index类型存储: V0 T: a, Z( ?. _
cat.ordered:类别是否有序, W1 E! I) E) `1 C) d3 }9 n! h, C4 `
cat.codes:访问类别编号。每一个序列的类别会被赋予唯一的整数编号,它们的编号取决于cat.categories中的顺序5 D' m/ {7 U2 h" P) C. x9 i
s.cat.categories, j" i' ?% o: X( s% g
Out[7]: Index(['Freshman', 'Junior', 'Senior', 'Sophomore'], dtype='object')* x1 [9 H9 w( L1 u1 G( I/ w
6 U* ?6 p' \, v6 ss.cat.ordered" A' o' g7 c4 ^% g' X
Out[8]: False- e9 h. R$ ?. m
7 H6 M2 p0 C, W
s.cat.codes.head()+ b# \( B& V0 m- q: f
Out[9]:
3 b$ ^! E8 W1 L+ W. {$ l0 0
# t& b. X; W! K& N% k1 00 P* a o# U- A( T9 I/ }. |
2 2
) ^' ?! G' q; C3 F3 3
( b( c! g5 v+ d4 [( Z+ V. C4 3
2 W: Y" E( F" M! k4 f# Jdtype: int8
% _( q o9 C" O7 D! u1
9 f/ H T4 n1 h c! t22 C' p d- V N
37 b) _- v2 M0 w1 L5 I* P, ~
4
' ~9 [8 _ Q4 ]0 z, J9 J% {/ n9 k: r2 u5+ G& w' M5 j) V% M! u1 _
6; {5 v9 f, c" @8 j- J
7* ~- l* C; i# x* G7 [
8
& c; r/ `# t6 r, O/ V- K/ l% t; F' d98 M. o. g; O1 X
10
& |6 F$ Y% B; v9 H* `+ [11
/ j) v9 g8 S. r) ?8 h% y12. B4 A- }4 y5 |& M
137 M, ^& y; M- T0 U6 n
14/ p3 ?0 [8 G7 b ]9 e5 G' F# o4 Q
9.1.2 类别的增加、删除和修改( j* k3 e6 `4 B
通过cat对象的categories属性能够完成对类别的查询,那么应该如何进行“增改查删”的其他三个操作呢?; r! ^5 a; O8 P0 U7 _; O) }0 A
) R2 C/ ^$ e9 p7 O
【NOTE】类别不得直接修改" h- @% s: i; U8 l' A2 F; p' h
在第三章中曾提到,索引 Index 类型是无法用 index_obj[0] = item 来修改的,而 categories 被存储在 Index 中,因此 pandas 在 cat 属性上定义了若干方法来达到相同的目的。! P( f7 E1 {( T
7 L( R7 }4 _. y9 a1 O' S! p e/ O. H. Gadd_categories:增加类别6 j. m' ]+ D, T& v* ^
s = s.cat.add_categories('Graduate') # 增加一个毕业生类别+ L- F F0 v/ E% r* l
s.cat.categories
0 C" U J v6 \+ J: c( K. W
9 p; K$ R) l6 R, n0 N( LIndex(['Freshman', 'Junior', 'Senior', 'Sophomore', 'Graduate'], dtype='object')0 g* w7 m/ i: M" j& l3 p
1
3 d: ?8 @' @0 r9 \4 l2
! ?$ |2 w x: Y1 X7 R9 s; a9 \3! X4 H% k% `' X% w, c! `: T
42 ]( ~" v. S2 x: h; [8 c) ~. x
remove_categories:删除类别。同时所有原来序列中的该类会被设置为缺失。
( K z2 m9 ^8 {/ w( \$ ?9 x9 Os = s.cat.remove_categories('Freshman')
- [! O* {) j; m0 o, Y2 z1 r, u9 a) k1 E, T! A4 F5 S1 q+ Q _
s.cat.categories
7 i& @2 \$ x0 ]5 QOut[13]: Index(['Junior', 'Senior', 'Sophomore', 'Graduate'], dtype='object') ?' Z$ F9 G) m; N
" D8 H3 x8 ~- ~3 b* Q& es.head()) ^6 t8 G7 k3 e2 [1 w) |5 W
Out[14]: : \. e# j: a3 S; N7 D9 C2 H
0 NaN3 e+ P5 @7 d: O% R" }
1 NaN
* \) ?' `* a4 S4 j7 h2 Senior+ f) b3 G/ k) d& E6 G
3 Sophomore& n0 O# e- S1 l
4 Sophomore1 S/ b7 J5 K8 A# _; Z
Name: Grade, dtype: category: s8 @+ u' Y! @1 T1 Z! _
Categories (4, object): ['Junior', 'Senior', 'Sophomore', 'Graduate']
/ D1 k/ n/ s1 ?$ z) n; p+ L1 ?1
3 x5 [/ h5 |, b1 T2
9 O+ I: b; D' r6 s, r32 O# @6 F, q) B1 `0 l
46 C- N4 l9 d' U$ \1 d
5( }9 ?5 G1 R. X" [# V
6. V# F+ Y4 m7 c% }
7
/ @; z% C. M" l; p4 {0 R80 j7 K4 \% i' Q7 `: F) ]
9: e! k0 {( \6 @, r) T7 ^
10
- ^ Z" C) \; p/ |11& u9 ^6 u1 A' f1 N
12# z8 x9 z& x, D; ]3 L0 i1 i2 ^
13' B# t6 @+ u6 W3 \) N: p
14( l' ~7 p9 }' P/ q& U! t3 A$ i
set_categories:直接设置序列的新类别,原来的类别中如果存在元素不属于新类别,那么会被设置为缺失。相当于索引重设。
# q5 P$ b* ]& y. q4 Fs = s.cat.set_categories(['Sophomore','PhD']) # 新类别为大二学生和博士5 w+ t; Z2 _# H) g7 @
s.cat.categories
( {9 u# G$ d; W- n5 _# |+ OOut[16]: Index(['Sophomore', 'PhD'], dtype='object')& c# Y1 @/ w" X5 z1 j: G% }
- a. l" ^' L- N. Ps.head()
8 ?6 _0 S5 M mOut[17]: " |- V1 _3 Z4 r' q$ y, w9 `7 a
0 NaN& \& ]. ^5 C& j# L" ?1 ^2 s
1 NaN5 c3 [4 ~6 e3 p! V+ Y) [
2 NaN
1 p7 K. t- t* ?3 Sophomore% j- L) N5 b: H, C+ d; ? w
4 Sophomore& q& w% p7 g6 C' x8 `' }
Name: Grade, dtype: category3 I$ g* b! R5 P% b, i1 ]5 R
Categories (2, object): ['Sophomore', 'PhD']
# l% ]2 f; d) k$ X) M: D/ M( s- _1& N u/ g# D( K, E
2) S2 ^) Y5 h$ q9 w% |
3
: h l1 t, d( f3 E4. S5 b( P4 D( {. d/ J& I+ t4 T
54 z4 D6 i0 k& w
6& k" f- _5 y2 h( {$ V4 |
7
4 i$ X; X+ _. X' Z y! q3 A8
% j g9 y- ^( s; I0 D9& \9 z7 K& Q$ w7 w+ A7 q+ N
10" T! @; W9 G: P) F5 \5 c
11
2 h3 f" }# s0 l! ]( G3 U- e12& k0 J7 [& y$ I( _0 f h
13* a( t8 ^: U1 J1 ~
remove_unused_categories:删除未出现在序列中的类别8 e7 P P' Q# n. B, y
s = s.cat.remove_unused_categories() # 移除了未出现的博士生类别
) H) g3 j0 |' F6 |! n/ ?s.cat.categories8 I0 \; u* _. X$ F% l' N D
8 K. S7 ]2 h4 \! R1 c7 F
Index(['Sophomore'], dtype='object')0 B& Y) \. U. Z! a0 X+ l
1
& E2 O9 y8 \+ _/ Y- O- Z2# s% p* r: r0 r, b& \& V
36 @6 P& M( G' `# z
4
! L. S2 F/ N* J9 P. arename_categories:修改序列的类别。注意,这个方法会对原序列的对应值也进行相应修改。例如,现在把Sophomore改成中文的本科二年级学生:
) k5 [7 |$ \- [5 z2 ts = s.cat.rename_categories({'Sophomore':'本科二年级学生'})
3 E/ x. f0 s ~. Rs.head()/ W# ]2 `) X- N: e: b) ?8 w
9 f ~9 D/ x* q0 |9 e7 G4 L# F
0 NaN
; C$ `+ D) c) u# n; \( c" O1 NaN
$ j8 d' L w' X' H2 NaN% H [, [( a$ g: m3 y+ h) o
3 本科二年级学生
4 U7 b0 e* T& s8 s4 p4 本科二年级学生
: a) ]' G. O% r4 {* Y/ Z4 TName: Grade, dtype: category4 d9 p/ w/ I# r! T& l
Categories (1, object): ['本科二年级学生'] v2 }+ l/ d8 u: k) e7 L/ N
1
4 |: F& i8 E( I2/ P" E+ N5 p3 w+ A$ D, j3 Q/ P
3' J# m+ p3 i' f, i) v W9 V* Z
4
4 `/ C6 T4 a$ K. {& J* I5! ~7 E; I3 g. Y7 p9 Z
6& j# X2 ]6 O# K0 l7 C; b" g
7- a9 e1 G' G4 c9 w% m4 G6 v
8& |0 |* i5 o3 J9 ^
9
* p8 F& c8 P" M) n) I/ S/ ~10& l9 J, e. V' ]7 I7 r
9.2 有序分类
8 y7 i2 [, k% m' C. c9.2.1 序的建立
4 h9 L& O4 f% B! M 有序类别和无序类别可以通过as_unordered和reorder_categories互相转化。reorder_categories传入的参数必须是由当前序列的无序类别构成的列表,不能够新增或减少原先的类别,且必须指定参数ordered=True,否则方法无效。例如,对年级高低进行相对大小的类别划分,然后再恢复无序状态:* ]6 n& \3 E+ V) F& {5 b
$ K8 u" w4 X ^! E4 s
s = df.Grade.astype('category')2 d# L* C" V, n$ C7 K, \
s = s.cat.reorder_categories(['Freshman', 'Sophomore',
1 H6 T1 _" ^7 i& b 'Junior', 'Senior'],ordered=True)0 @$ r, g) j+ T* v. O3 }' U
s.head()7 k1 j' J; Q: w( Q
Out[24]:
8 @/ E. E" f- a V' q; x( B& ^) s0 Freshman
! L. H' p: s9 w1 Freshman2 O. C. D, G0 C" \) N
2 Senior
3 M4 W% G: J0 z% B! _3 Sophomore
" k8 e1 c+ t5 q1 l, x# {6 B4 Sophomore
* Q& t% r( u. {Name: Grade, dtype: category
! t0 H) R6 C; z" ?' rCategories (4, object): ['Freshman' < 'Sophomore' < 'Junior' < 'Senior']
# p% ?( ~0 L# @$ R3 z# q I9 o w- d: S" p! W5 Z
s.cat.as_unordered().head()
5 X5 m7 A; \; o, \Out[25]:
, K$ I+ A$ \: `& S0 Freshman
- X9 N' i9 ]" J9 D1 Freshman) o) V/ Y1 M7 R5 B! d. M* G! r
2 Senior
8 h+ ~* e' t5 Q5 B' L. C1 _. g3 Sophomore8 U5 w9 H" B; G/ l' p
4 Sophomore
9 I6 p5 Y( k& U8 c% e4 lName: Grade, dtype: category" y6 w( s4 |$ S1 r( b
Categories (4, object): ['Freshman', 'Sophomore', 'Junior', 'Senior']
) Y9 ^6 v7 R* T4 u- K9 l% c+ e% D+ O
17 s. u+ y& V4 ^6 A+ I9 P
2
6 A- l; n% N. I35 x" W6 M! T% L q
4
3 f7 [: d* V. w1 e' K5' ~$ [5 Y6 r! C& |
62 b* O* ~% u( C2 c1 o! g9 F
7' M2 m& l; K; ^! L, N
8
0 ~& b. {2 q# v5 i! u+ o# h7 R91 f2 {. S' {5 s4 Y* S5 }
10/ c Q! r% `4 z5 c$ B
11. [, g/ V3 x7 ~& B$ u
12
# j( i$ j+ D! H13
# D- U2 x& ~* B0 T$ S, m14
; c& Q3 M. r+ p) G# b155 ]9 m* r. U, C# y/ i) t/ Y
16
$ ? n2 f" g' x" N3 H; g2 _8 y; a171 j( v7 K, H' _7 `* A" B
18
6 k3 Q5 l4 K- D. p6 l19( K7 L: f! a! |2 Q
20; S. {7 A% k, J3 V8 [
21$ z9 f/ D, q6 v
22$ Z! d% y8 \- } i7 C
如果不想指定ordered=True参数,那么可以先用s.cat.as_ordered()转化为有序类别,再利用reorder_categories进行具体的相对大小调整。 _+ V+ y4 x4 Q* F8 ^: o
7 S/ i# L9 B+ B A0 E. j: x9.2.2 排序和比较3 P8 }. q _6 d( I# w
在第二章中,曾提到了字符串和数值类型序列的排序。前者按照字母顺序排序,后者按照数值大小排序。
" L$ X2 r, c, ]* c$ f7 `& |/ M9 @7 f" x s
分类变量排序,只需把列的类型修改为category后,再赋予相应的大小关系,就能正常地使用sort_index和sort_values。例如,对年级进行排序:
2 C) _% g; v6 M3 w9 F3 O$ Z8 l
i% }$ r+ {6 ]+ q7 g+ y1 l, j8 ddf.Grade = df.Grade.astype('category')( y" c6 y/ r' J; V+ u
df.Grade = df.Grade.cat.reorder_categories(['Freshman', 'Sophomore', 'Junior', 'Senior'],ordered=True)7 F- D, g0 Z: J1 r2 v' M0 W
df.sort_values('Grade').head() # 值排序, c0 h* p) q) }+ n# n
Out[28]: # }8 J1 A* p' C* r5 O
Grade Name Gender Height Weight8 l1 |$ n" d9 U9 ^' e8 [) q, Z
0 Freshman Gaopeng Yang Female 158.9 46.09 ?- Y$ k/ `# u5 O$ C' o8 L. }# ]
105 Freshman Qiang Shi Female 164.5 52.0: K$ D1 V+ C8 k
96 Freshman Changmei Feng Female 163.8 56.0
. g" _% d0 I. t m88 Freshman Xiaopeng Han Female 164.1 53.0
# A0 v& t: t5 g: Z2 j4 T6 y1 z* n81 Freshman Yanli Zhang Female 165.1 52.02 G* S5 G# ]9 j7 w0 i: Q3 T- L& |
; ?4 K2 U% C( v2 k! a! P* K( xdf.set_index('Grade').sort_index().head() # 索引排序
' C# g/ w0 z) H$ Q( o1 YOut[29]:
' t) R4 H# {: z) { Name Gender Height Weight# O9 w1 B' S2 k. S
Grade ( M% ^. `$ v- O3 S
Freshman Gaopeng Yang Female 158.9 46.0) y8 }. s& ]% U ^5 g6 i
Freshman Qiang Shi Female 164.5 52.0
# E2 [# [% |* k6 P' ~' f6 s- vFreshman Changmei Feng Female 163.8 56.0
- k" `% F8 L) \: W& a, cFreshman Xiaopeng Han Female 164.1 53.0
) z' Q v9 D# r* D. ?Freshman Yanli Zhang Female 165.1 52.0- c& ]3 w8 [; b: j5 M# ^
/ T" w/ _5 N/ Z
1
1 G; D( p9 T3 w3 U/ ~3 K2
! D: d; X& `4 H f/ j) M39 B& j) m( R# k6 F
4
0 [! w4 h/ ^9 s% x. O! r5; B; t D) D) J) I1 \
6
0 C. y, Z; ^" s/ K' G/ R1 R7
$ }1 P4 a& [1 ^) G84 G8 w' g( C! |: p ^8 t) `& E
9; }1 d) @$ f8 X
100 M) J4 Q! @# M" ?1 g5 [. ?
11' Y5 A4 e! J. r' P
120 S2 S- U; i5 R! _' h
13
) ?8 V5 x, E# K! f: V14- S( _6 ?; ?5 A9 V1 |
15
. I4 z2 t% w. P3 C- y0 B" x16
8 A0 P7 l; s+ A% o171 l* [7 ` ~* G; _ O1 U
18! K* e- X* h r1 D
19% O4 [4 ~2 T! ?. n
20( Y" F0 q3 F; ?2 `3 [
由于序的建立,因此就可以进行比较操作,方便后续索引操作。分类变量的比较操作分为两类:5 r9 a" `7 z% l4 C% M0 A
( `: Y7 o9 @" ?8 x+ i; ?
==或!=关系的比较,比较的对象可以是标量或者同长度的Series(或list)。(无序时也可以比较)
& x' s( \! Q+ \; V6 q A% V6 h. c>,>=,<,<=四类大小关系的比较,比较的对象和第一种类似,但是所有参与比较的元素必须属于原序列的categories,同时要和原序列具有相同的索引。
$ O, h8 t$ F5 u& x& i# n+ p3 xres1 = df.Grade == 'Sophomore'+ V" t- V! b, [
7 b4 @- D- \' y W/ r, d; s0 f
res1.head()
* N3 a( l2 g, H* @Out[31]: + o8 r' Y$ P0 C" S& z, R. Z/ _
0 False2 O7 K, O0 a c# v
1 False. C# ]. w/ B6 f! [* {; o
2 False- ?6 y/ o0 I4 T$ o! E* z3 z! X" V7 |
3 True
" |' g1 i; @. D4 True
9 e% Z( P4 H& p) [3 yName: Grade, dtype: bool
: u% C' |9 E/ S4 P! d {6 k0 E7 G1 \9 j& ?1 F3 p" t, N
res2 = df.Grade == ['PhD']*df.shape[0]5 n$ I2 k; k8 @! j2 H9 L7 Z# d
y- s% Q0 h; O- s! ^1 y3 ?( Sres2.head()
# A! T5 B6 U* oOut[33]: / O( ~* P8 k' j4 M- {. `. k1 @2 ^
0 False6 B( @$ v6 `( j5 o" q5 b
1 False
2 X; |; l9 e" s; F! w7 O: S3 a2 False9 ?$ Z9 N* E, h8 V
3 False
4 E* p) m- p% q# a2 G4 False& m7 \2 v2 j* O* B) [3 `5 h, b
Name: Grade, dtype: bool
9 s) p D' j ]* z, h7 ?
$ [+ }6 P8 D( H: y$ G Q; G8 Q' @res3 = df.Grade <= 'Sophomore'
' i8 o0 q: J( j# @# h3 e
! @: y& B! M1 ures3.head()
- ?3 p0 x3 B: B$ C$ hOut[35]: 4 n! r+ J' e' B, l( c. w
0 True
& N0 l" l' o/ d* v, ?* T3 ^8 l1 True" x0 |. K2 n& e
2 False
) ]0 h5 l* |( c( m/ }# x( ~3 True
1 q1 {8 V1 P' T3 Y1 ~. o1 Y# P4 True
5 S! E+ }7 o$ E% V8 U- J! m' W0 sName: Grade, dtype: bool" Y7 o! a; X# H) Z% _
: ~; h5 ~# P8 ]: b# sample(frac=1)表示将序列随机打乱。打乱之后索引也是乱序的,直接比较会出错,必须重置索引。# y0 c% m' `2 a' F0 e) c
res4 = df.Grade <= df.Grade.sample(frac=1).reset_index(drop=True)
0 Y _* k6 [9 m, J# h/ {9 }
6 R+ d' D! M% j' l7 h) ^% M9 \res4.head()
; [5 k9 u" E9 |; R8 Y8 p( FOut[37]: : L8 R2 Q" u0 K H
0 True
) F7 e& c5 N" r$ a/ |1 True
8 Q7 M) l( s; \% N* u$ }2 False& b9 ?& p; j' p ?
3 True
8 w8 S- R5 d: A6 \4 True5 z* g2 d4 _* c7 C: F7 j
Name: Grade, dtype: bool8 m4 H$ F D- W6 w: W8 q0 w
8 O+ M8 M; F2 F, [' m
1) Q% `" w! N) U: r) }/ ]0 O- r
23 \9 @. s. S6 A! Z3 \
3
( [" t1 [' p N% d3 Z4
2 T! [2 V( q, E0 b4 F B' W59 g( y- a5 @: V: e
6
' e9 [) s$ D) T7 ^7
3 C, I: U0 c' u; m) q/ \ Z8- B2 T, J! j8 v& ]: r% U
9) `: Z, P% k& }/ u
10
+ [5 U+ H2 f/ K11
- J0 J$ F! R0 t. d12
# l7 n6 D) {, Y; g, p6 B- D! Z13
5 A, f7 N1 }0 A- w! |7 o: s14
q! {6 q& y& m156 S3 G' D# i7 G6 |/ Y& X
166 c% D& O7 J/ P& ~
17
; O5 w8 t6 U& T% }+ m, m18
& F \. _1 `; r( e7 }8 Z# i191 q2 d0 l; f% |, e
20( b0 X; n: ~0 i! c
21. t+ b* ]' {" \; v9 g
22
+ P% A) }7 b; Q230 g" r: {* ^. u5 B
24
. \; t" _( l2 W+ E4 `/ n- m( i( s$ W25
. v! D" E5 u( R2 Z26
% ?5 U, U/ @5 B A' c6 T273 F: p& N( T6 T) w$ ^# k
28( `% t7 g6 |: Y% W: v2 E
29$ n+ r' t8 \$ m% e* z4 `9 ~
30
1 S3 l8 |+ C- Z* y6 c2 X319 b: H: C- G. H1 X6 e. h
32
1 R0 ^- P# O( o( i/ f3 x9 _: Q33
, k6 S% D" R. [& X34: Q" b* ?" Y9 T( |: C1 ^
35
8 O+ `$ ~! {' s% b3 Y1 U36
: y, [ x# P6 Y' J6 M( z) I37
# K: _3 M4 A4 g& U: E384 g/ G% O! v, N, U+ p! [4 i
39
; Z/ e! j' [- t- j3 y40
$ V& W x3 B" U8 D6 M, e$ G41
3 }9 b% ?& @4 P$ m& j6 M42
! O" r* ?! m' O( y h( \& H7 ~1 Z43" L; l* B; Q" P' W
44
/ B9 y( O7 a- a8 e9.3 区间类别
" J4 ~2 P3 q# S: V$ Q1 B9.3.1 利用cut和qcut进行区间构造
% V' E2 g: Q% E9 d 区间是一种特殊的类别,在实际数据分析中,区间序列往往是通过cut和qcut方法进行构造的,这两个函数能够把原序列的数值特征进行装箱,即用区间位置来代替原来的具体数值。$ {4 f, x$ ]. C/ x1 Q
: `( O7 u1 ^9 L
cut函数常用参数有:
- J9 Y4 O% u7 p) N) ebins:最重要的参数。
4 L2 K, ~/ [. H$ |如果传入整数n,则表示把整个传入数组按照最大和最小值等间距地分为n段。默认right=True,即区间是左开右闭,需要在调整时把最小值包含进去。(在pandas中的解决方案是在值最小的区间左端点再减去0.001*(max-min)。)! m, j! M( G: m1 W. L; y4 L
也可以传入列表,表示按指定区间分割点分割。 B. Q" Y0 [$ J' S. b9 h6 W
如果对序列[1,2]划分为2个箱子时,第一个箱子的范围(0.999,1.5],第二个箱子的范围是(1.5,2]。
* y) {" b: x) g J8 a+ i 如果需要指定区间为左闭右开,需要把right参数设置为False,相应的区间调整方法是在值最大的区间右端点再加上0.001*(max-min)。
$ @( s; t7 X3 q; g/ P
& M, Q& s, W% t! W" us = pd.Series([1,2])
# }4 n, r7 U, `# bin传入整数* Q6 r6 ?/ b' g' ]) n7 H
' ?% |% o1 y+ E
pd.cut(s, bins=2)
- I# V$ O9 S1 A l( } MOut[39]: 1 X" N/ ^+ l& v/ ^, T
0 (0.999, 1.5]3 D" X7 j+ w! L x- T0 r0 a
1 (1.5, 2.0]
$ C- ~( q8 H8 t$ Idtype: category+ x# m9 {5 B1 v- F
Categories (2, interval[float64]): [(0.999, 1.5] < (1.5, 2.0]]
3 j p# _7 K' G# e) ^) a8 r7 R8 U5 h0 R1 R& b
pd.cut(s, bins=2, right=False)% M/ s9 q% i+ E
Out[40]: ( v9 Z8 _5 |1 E3 }8 V6 \; y
0 [1.0, 1.5)
; ?1 t! n/ U, G0 f) I: ^1 [1.5, 2.001)
% K J4 g' a- w& Tdtype: category
* Y. s& L, v1 c/ b' nCategories (2, interval[float64]): [[1.0, 1.5) < [1.5, 2.001)]
% o5 d4 E! b! P7 p6 X0 p% o; J
" m! G; e) q* O; @1 x$ D" `9 Q g8 t, Y$ j
# bin传入分割点列表(使用`np.infty`可以表示无穷大):- o$ V2 z* f* s* l5 y) d
pd.cut(s, bins=[-np.infty, 1.2, 1.8, 2.2, np.infty])
; _' N$ w& Y; t2 p# f5 y5 |+ ^( gOut[41]: # u6 V0 Q& i6 X7 R% p7 t
0 (-inf, 1.2]
5 E7 X& O) ]" w6 m) ]1 {1 (1.8, 2.2]* D* o6 k1 Q2 v; O
dtype: category3 V% R: i0 x( L$ O# `
Categories (4, interval[float64]): [(-inf, 1.2] < (1.2, 1.8] < (1.8, 2.2] < (2.2, inf]]1 X- s+ Z5 f, b$ Y4 g) d& S' |
0 a" [4 n% i$ t9 d' H: O8 j
1& B8 z5 c, c% L$ u% j6 s3 j1 g7 |
2
; {) N7 h9 L9 C% ` Q/ j O. H6 _" z3
6 @, x) k% N, R8 M7 l4* b6 S7 ]; _% D
5$ f; B4 Z. J' L/ B
6
6 P. g6 s B6 _8 P5 `8 M7! X* E1 T$ G; G* H# `
8
1 O) ^* A' j; s# _1 ~) S9
x3 Z0 [6 `. N( ], E4 A10. Q$ l4 z' `7 }
11& z; X% X0 z) K
12
' b. |9 Q3 f/ |# c13
' f0 v$ n- d' l. ^14
4 d# y4 ~& |! \* V7 J15
3 M# h" t! b( q: L! t; W* U" r' @) p v16
0 s4 o# v+ H w# a `+ C17; |: l- j0 b& _% [, n" L7 p
18
i* W! |+ Q9 H5 e8 T* J197 _4 ^( L$ _" O' h+ Z7 F
20% v2 F2 K/ @* g. g o1 y
21
2 ? f8 g# \( h Y; \ G: Y+ d! u225 F1 S/ P0 x& }/ P0 P
23% {" H9 a' a* a2 t) n
246 |* |9 A- o2 F4 U/ k1 ~
25; [6 r8 r9 z' F! j- b7 B* q
labels:区间的名字/ L+ {! U. v/ [: x' B3 c
retbins:是否返回分割点(默认不返回)
& j& Y1 f( @& s* {9 w2 \& g) i5 S默认retbins=Flase时,返回每个元素所属区间的列表, u% L4 s$ W% a; A2 Z) ~% p/ S6 ^
retbins=True时,返回的是元组,两个元素分别是元素所属区间和分割点。所属区间可再次用索引取值+ ]- D, G5 K+ Y$ {2 F
7 @# O L0 T% M3 v* P" Os = df.Weight$ Z( l* ?1 L' c" r' n# @
res = pd.cut(s, bins=3, labels=['small', 'mid','big'],retbins=True)# @8 h# B' V" Z
res[0][:2]3 n2 \# g) ^ [& {
; k4 p9 i5 t; A. w5 `Out[44]: 1 o$ q" k* T# P7 F! ?
0 small
* T4 d6 V1 L+ l( w1 big* J/ q" }" x; l. v
dtype: category- g- D, R8 g Y3 s
Categories (2, object): ['small' < 'big']7 Q$ n. k2 @( s9 g4 L
) H0 N% t g8 Sres[1] # 该元素为返回的分割点
$ n* w* L" L" s! W g6 `! W" ~Out[45]: array([0.999, 1.5 , 2. ])
- Z" z- v# }4 k# U2 J1
' [& W+ f6 n) l: k% Y: S' F0 m" J25 `# b. g( E1 h) t+ w8 h
3
- I1 X3 v7 ]' i) E/ p; ^) T# G4
; ^/ `9 G9 d9 s! s5
" Y& B/ J4 E D6 t. } d6
7 F+ p, E6 s( @. N9 x" k7% I6 H! g1 J; c
8% D! ^: |, @: p3 o) u/ y- p
9% G3 n; Y( g4 _0 H) @
10
& I+ b" p, U3 E- a11
$ O. k& `, `) h c) O+ O126 V: h0 E7 e+ D3 O/ u. q
qcut函数。其用法cut几乎没有差别,只是把bins参数变成q参数(quantile)。9 Y( Q) t2 r/ o' q
q为整数n时,指按照n等分位数把数据分箱
8 b \& q: y; n. a" Vq为浮点列表时,表示相应的分位数分割点。 b! l% U! |- `$ v5 R
s = df.Weight
: V" W& x9 I5 o' v; ^' N" Q
1 X* C; W6 v! v) F- q/ r- ]pd.qcut(s, q=3).head()
1 ?/ n4 }9 T- `. @% DOut[47]: + E- f3 k0 L% y' V# E( M
0 (33.999, 48.0]2 J. d) t8 @& b5 J3 B; p) U2 v
1 (55.0, 89.0]
. ]# z5 E' S" }( v8 N1 E* P5 D- x6 Y2 (55.0, 89.0]: C( B9 W; n/ t3 i" v
3 (33.999, 48.0]
% p( F( q$ F" B# Y2 a4 (55.0, 89.0], y% k' q2 s4 ]! P/ L
Name: Weight, dtype: category& e( q* `6 k+ ]
Categories (3, interval[float64]): [(33.999, 48.0] < (48.0, 55.0] < (55.0, 89.0]]
9 y: X \8 h+ `3 s! B$ q8 l" E! T3 G+ g" s9 V, \
pd.qcut(s, q=[0,0.2,0.8,1]).head()9 U/ }# z* `: y' F0 n$ B( ~
Out[48]:
4 v& b, i# ~* I' q2 i0 (44.0, 69.4]* q0 i8 g6 F9 f' ]
1 (69.4, 89.0]
% K7 L5 Q6 n7 A: q, y' {2 (69.4, 89.0]1 x- R) V5 R! X( K
3 (33.999, 44.0]! G8 I( T' a, L0 F
4 (69.4, 89.0]: s) b* d( k/ k% r ^# P4 Z4 @
Name: Weight, dtype: category: Y3 P. W0 B. t6 N0 l
Categories (3, interval[float64]): [(33.999, 44.0] < (44.0, 69.4] < (69.4, 89.0]]: X9 A0 c1 c; z/ `! V+ k
" z3 {7 _1 u9 U1
5 K n$ h9 R7 r) ]; m0 C9 P! p2: W) T5 S4 R6 c
3& V/ M% n0 ~7 Z$ R: x2 S* Q1 `
46 W4 n/ v! @1 q+ z
5- |5 j$ |% j8 O" _' \
6# i0 h$ |& U7 E: j) X1 V
7( C5 q9 `! d2 ?" L
8. [% U4 y: K. v
9
0 n+ D' P' S* m# k10
3 _. j3 R0 R5 {- }- u, Z11/ T2 Z4 L( g. h2 J
124 w/ p7 T$ ?8 d$ C* @7 D4 X
13) U0 U! n2 |, A+ c, X, b& ?; U
14" w0 P7 M; Y1 \$ Q
15" A- p& Q3 @5 ^: X; A
16
6 I9 U. Z: i7 g" P17
5 f. M) N& r; F8 `1 m" e/ ^3 v% T184 R, R: ]8 t4 q" h* s
19
! v* s2 I+ [9 _1 B- w20* N- M- Z3 H5 H$ A9 g, k6 E) Y5 E! d
21" |* ^$ ?: l- v# ?$ O* _
9.3.2 一般区间的构造
) y: A4 h$ f" H7 n$ S7 U pandas的单个区间用Interval表示,对于某一个具体的区间而言,其具备三个要素,即左端点、右端点和端点的开闭状态。
( H8 [. A5 P: U' {/ L9 Y [# d" e" K/ _7 v1 l8 J
开闭状态:包含四种,即right(左开右闭), left(左闭右开), both(两边都闭), neither(两边都开)。( N4 }; C9 H" a5 X- Z( F
my_interval = pd.Interval(0, 1, 'right')- a( e2 F- o$ B6 L3 k( e
) U+ N, `. w" B" J7 Amy_interval
D$ \2 t4 |. ]1 D2 D% fOut[50]: Interval(0, 1, closed='right')
1 T3 \+ j2 R+ w. _1/ ~6 M8 l$ L( ~5 I# l; |6 V/ r6 G
2: g, B2 E" B1 B7 V
3* B m* V9 @0 P c% ~: I+ J
4& d9 |, y4 A# B0 s# u- _0 W2 D
区间属性:包含left,mid,right,length,closed,,分别表示左中右端点、长度和开闭状态。
1 J, J3 ~( n+ r使用in可以判断元素是否属于区间
- O$ W$ G$ b4 S. H2 _# a( r用overlaps可以判断两个区间是否有交集:
, A- ?& h" B3 s' H# W& Y) Y0 i0.5 in my_interval' K6 D* Y; }# k1 g z7 L
# W; l" Q8 \) l& ^1 J
True
1 \: [! K' f- q9 y4 W- v; s+ h9 @1
# {4 c) l! e+ V4 N' M, u. t+ E) [: \# K2
0 q: h% G, k1 ]0 {# d4 r, e# {3* H5 _: f. M" w9 |8 Y
my_interval_2 = pd.Interval(0.5, 1.5, 'left')
6 Q5 Q; d$ k% r' c7 z. Emy_interval.overlaps(my_interval_2)% G2 i' ~) Y) S8 W! T6 q/ ]
$ w, q: H- h, V. [4 gTrue
0 e$ o+ V1 `' C1. b" f1 B8 L4 f, J5 X5 w. O1 O1 [; w
2
4 a. }' A! K& C2 Z3/ }' D% `% t" t1 {8 R
4
4 j' D t1 z$ L" f, p1 `) N pd.IntervalIndex对象有四类方法生成,分别是from_breaks, from_arrays, from_tuples, interval_range,它们分别应用于不同的情况:& @# y3 B8 l& Y( }2 x* K4 N H
l# Y3 p' L# K& x0 _from_breaks:类似于cut或qcut函数,只不过后两个是通过计算得到的分割点,而前者是直接传入自定义的分割点:
! ]' H) A1 _ n, Q' r" ^7 ypd.IntervalIndex.from_breaks([1,3,6,10], closed='both') u7 p$ j, A" u* p6 F
% l3 d4 a3 w: S6 wIntervalIndex([[1, 3], [3, 6], [6, 10]],
% `/ t; ]% H/ e: P/ t& R8 i8 d closed='both',
0 J6 }- y! C3 `0 ^- d dtype='interval[int64]')
X- I% v9 d( H1
% v& _3 C* E3 ]* ?2, ^0 |5 i( {% X% o5 y
37 n( v: R3 r9 Z1 c2 i
4# M& A- r# _6 D8 e: J$ }, p$ g8 G
5* Z! R) x/ x$ }% Y( n
from_arrays:分别传入左端点和右端点的列表,适用于有交集并且知道起点和终点的情况:
' U: ~& @! m" g8 }' j! Npd.IntervalIndex.from_arrays(left = [1,3,6,10], right = [5,4,9,11], closed = 'neither')0 A) Z3 e* a7 q, Y5 l2 V
4 W: R; D& }# \$ r$ {. m
IntervalIndex([(1, 5), (3, 4), (6, 9), (10, 11)],# u+ l, z2 J' j* ?; |
closed='neither',( K* b5 |/ J$ y
dtype='interval[int64]')
7 Z, X+ `) Y; ^% m: x4 a8 ^4 S1
3 z3 ?% O: d$ o1 g3 I/ R! r2- B! `, C' K- V. r5 R! c: \
3# w$ o7 R6 V1 B5 `, J' [3 p
4* T; k& E& |% I, C, v8 s- u9 g
5
Z( F4 I, q% S% r" pfrom_tuples:传入起点和终点元组构成的列表: x7 J* c, _: _( Y- W: P" X
pd.IntervalIndex.from_tuples([(1,5),(3,4),(6,9),(10,11)], closed='neither'), V0 I) Z$ `8 i' U
3 O/ {+ I2 o! P1 d# c+ AIntervalIndex([(1, 5), (3, 4), (6, 9), (10, 11)],- g) }9 X3 _+ r/ ` X! F
closed='neither',
y4 p# h3 q8 }7 ^# G2 q- z8 l dtype='interval[int64]')
; m6 [4 }. K+ P15 V! x, e! q) T( g* S
28 G: C! z: B3 c. w" a! _2 D
3
! f/ I- l% \& j) A) S% T- A3 Z4
) D: v0 I7 W+ \% W/ `. c5
0 S& {5 j. X( B- @$ E, S E6 D7 K) {interval_range:生成等差区间。其参数有四个:start, end, periods, freq。分别表示等差区间的起点、终点、区间个数和区间长度。其中三个量确定的情况下,剩下一个量就确定了,从而就能构造出相应的区间:
9 e% U0 y! a( u: u' y) \3 U8 kpd.interval_range(start=1,end=5,periods=8) # 启起点终点和区间个数4 L( G2 U( |3 n8 F, R- N+ ?
Out[57]: " I6 [) e3 H, S. J- J7 W- X! N
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]],
9 w* d4 g% \8 Z( m0 k closed='right',# }$ N" v. K% a k: ?3 _
dtype='interval[float64]')
0 a0 b% S, V# a% J& n. ?1 |$ Y- O' t9 ?9 B6 Q! a1 u& ?% M
pd.interval_range(end=5,periods=8,freq=0.5) # 启起点终点和区间长度5 p$ n4 R/ q. n' e5 l2 Z6 d0 _/ g# J
Out[58]: 4 O! r9 b$ G4 ]' [0 M$ D
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]],1 g' U, A. f; y) Y; ^ }
closed='right',+ Z( j2 N1 L9 z: E- [" |1 R: ~
dtype='interval[float64]')
$ g' \1 t5 b! Z( d7 o$ a1! d* }( l3 Q: m* z( |
29 x3 l4 {3 g+ q- F
3
8 |, ~1 y/ e' E0 X4( m1 {6 C; \/ s7 E% t/ G
5
: W/ q, Z. Y7 ] E2 u. `' f65 d3 {2 P; ]# a0 p2 d9 e& O3 P
7
/ @" B( u6 J. `: [& | O8
! R4 S4 w3 B0 O9 Y$ E9
5 z2 _7 i/ l& \10
+ Y: m7 i- a+ U) @119 W# X) G, v' ^3 s; X, C; B7 V
【练一练】- V7 T" |1 H4 Y0 y _
无论是interval_range还是下一章时间序列中的date_range都是给定了等差序列中四要素中的三个,从而确定整个序列。请回顾等差数列中的首项、末项、项数和公差的联系,写出interval_range中四个参数之间的恒等关系。9 X) Y3 ]9 H% A j) C4 ^
7 y# f' {8 o |/ E( g Q
除此之外,如果直接使用pd.IntervalIndex([...], closed=...),把Interval类型的列表组成传入其中转为区间索引,那么所有的区间会被强制转为指定的closed类型,因为pd.IntervalIndex只允许存放同一种开闭区间的Interval对象。; W& |2 z7 s! W* `% ^, N" l
, \# K( b, S# l, j% C' ]/ R# F a; B6 u
my_interval
/ }6 n1 P! j9 D: ^7 tOut[59]: Interval(0, 1, closed='right')
' S$ E+ t! c3 O6 U; K+ U. X
p7 [. J* o1 N1 U x* {my_interval_23 B, X# k, P+ O. o
Out[60]: Interval(0.5, 1.5, closed='left')) g( D; c( l7 f/ x! h
D% i- D+ | `1 n& C& Zpd.IntervalIndex([my_interval, my_interval_2], closed='left')
* U8 [$ V) u4 zOut[61]: 1 J/ n+ X, U: m
IntervalIndex([[0.0, 1.0), [0.5, 1.5)],
7 @0 f9 Q' v2 ]& r closed='left',
- u' T. F7 }. B4 V2 B& H9 g$ F dtype='interval[float64]')
" L' W5 B2 h( j# q! o$ |: R1
, Z8 F0 S% b* X8 L7 |, V" ~2
4 P2 y. I+ X+ {$ t( t: T( O6 |3+ K* @4 O V# I6 t
48 V4 W5 x) @7 Y! X9 \$ ^
5
% ]1 g0 F& o6 C' ^ M$ O2 K- I& I65 A. w) N: Y0 C# l5 c9 u
7
# D# c4 z1 Y, |+ b8
, I" k1 G) P6 ]& @* h- W9; @/ B2 w' v. g4 n, G7 e6 Z
10' o: Q, N' R5 D* m
11
% [ b7 |! C0 C9 E8 _9 V. B9.3.3 区间的属性与方法
& A( _0 c5 l- |; k' c$ t IntervalIndex上也定义了一些有用的属性和方法。同时,如果想要具体利用cut或者qcut的结果进行分析,那么需要先将其转为该种索引类型:
b# D: X& J2 e6 i5 a; d9 i% @! `
3 y+ _, v3 F' W" u$ Ds=df.Weight
" m0 ]" K# M1 s1 j: F/ R/ Mid_interval = pd.IntervalIndex(pd.cut(s, 3)) # 返回的是每个元素所属区间,用具体数值(x,y]表示3 y& H/ t1 f: K& h: G5 }0 \
id_interval[:3]1 w& ~) P8 I! G7 K @
/ R8 }# ~1 O7 N; j7 b% I, M! r7 t/ CIntervalIndex([(33.945, 52.333], (52.333, 70.667], (70.667, 89.0]],
% y" q b2 a( d: F0 e, X closed='right',
' _. ~: {0 b) `1 o name='Weight',0 n6 [$ `( @! w2 L0 i% r$ S% D$ P
dtype='interval[float64]')
8 |! z' \3 _0 Z- X/ G6 }' N! I1
0 F( d- b- q" {$ g' E6 ~4 S28 ?% x( s5 v, N, w+ y! O
3
. o$ f t- a3 l9 [" I( \4
# }! K$ F, R+ k, K4 E {! \5& M9 N. h8 R! J" J7 `% G9 K
69 s! \: \4 M1 p7 d
7
/ U; m: B( ]' c- I1 v8' A% s u% o/ g/ r6 t
与单个Interval类型相似,IntervalIndex有若干常用属性:left, right, mid, length,分别表示左右端点、两 点均值和区间长度。) D* A3 n) e% _
id_demo = id_interval[:5] # 选出前5个展示* I% \: r! z3 C, i
1 K- T" Y& L8 m. C* H
id_demo
0 }! h5 p" T/ B9 b8 a/ W- z- EOut[64]:
a+ }1 ^4 H' c' z" i# M4 rIntervalIndex([(33.945, 52.333], (52.333, 70.667], (70.667, 89.0], (33.945, 52.333], (70.667, 89.0]],
1 M1 I+ ^/ d% H% j( |6 W( Z closed='right',' }7 E+ j7 R2 K- d# q3 A
name='Weight',
$ @! @: Z2 o9 K* k- u( R3 w dtype='interval[float64]')
+ l( i3 B% x7 l1 H8 N) w1 w5 s. g
id_demo.left # 获取这五个区间的左端点3 |# A' H) @& f8 Y1 T& S2 y$ A
Out[65]: Float64Index([33.945, 52.333, 70.667, 33.945, 70.667], dtype='float64')
, M/ @/ j; |8 p6 n8 h, V4 p! ]8 y, d# B2 {( E B3 r7 r6 E
id_demo.right # 获取这五个区间的右端点 o$ u- D+ T! _3 _" Z
Out[66]: Float64Index([52.333, 70.667, 89.0, 52.333, 89.0], dtype='float64')
3 t3 K4 Y1 a n( I+ v, T
! F7 D$ Z( \7 z' @# y) vid_demo.mid' X* t+ |9 A+ i& \/ {8 S B
Out[67]: Float64Index([43.138999999999996, 61.5, 79.8335, 43.138999999999996, 79.8335], dtype='float64')
0 `4 Z: U% [# L+ A# t; L# o0 F [! E) c2 ~! ?. E
id_demo.length
6 T8 H V$ D; o3 _& L8 e+ _ O4 o; N9 rOut[68]: 5 g7 o: _, j: e3 f6 M& y3 e
Float64Index([18.387999999999998, 18.334000000000003, 18.333,
Q1 z& B6 G. Y( |+ s* v. n, u 18.387999999999998, 18.333]," M: _2 M: ?; i! |7 q) F9 C
dtype='float64')
+ |! K- t# ^$ `3 }9 `, j, o8 N4 c. r# l. `7 E3 m# M0 D
10 ?) b) F2 ^3 X) c, |) O* e- g
2
4 s" ^7 \6 L5 h* W! U8 m. ]: w5 Y3
( N6 ^# x- b. _- N+ i6 k6 u) e3 n4
) `/ M$ e3 b$ J5 s5 n5 g5
0 D! h# f9 V4 {6 Q6 D' l7 e* w4 Q C* A
7" h1 {- R% W% f/ {2 l# m
8
$ G' J2 \4 V8 P) J6 b( q! h8 I% ~9
5 V7 C) G- Q6 P10
( T, ^1 Z1 U8 u4 m; X6 I: o11/ z2 J6 ^4 v6 j" p; ?" x5 Y6 i( E
12( c+ j; V' c. }: o
13
$ o6 @- b! X6 B% C& J& a. |) p14& `+ N; u7 Z2 L9 G7 v: \3 g
15
5 [+ N/ [4 \( p' v% K9 T6 n16
0 M0 I. y u& b4 B) x17! F* v% M/ W! L. a
18
1 G5 {. m9 B. M% Q* x: L0 Y& F192 O- k) R/ @! J, J- ~& J
20
" p; s+ |, V4 u21( q4 K, }0 @; B% V! @9 H
22
- y% K# i9 T( }! w( `. U5 t23
7 h }$ v0 {8 D8 KIntervalIndex还有两个常用方法:, a/ q+ j' X* y7 i9 [+ R! \7 g, V
contains:逐个判断每个区间是否包含某元素& C! G7 s: ^) Q! I
overlaps:是否和一个pd.Interval对象有交集。( T8 P. ]' |$ u. H
id_demo.contains(50), \+ s( t: I( ^
Out[69]: array([ True, False, False, True, False])
/ s6 d( j$ G; t4 V" o( J" ^7 ^0 A. ^) y
$ ?1 ^& }/ L" G5 }5 ^/ ~id_demo.overlaps(pd.Interval(40,60))
+ a& n$ o% g1 }6 }Out[70]: array([ True, True, False, True, False])" p% G7 x" O, R* a7 q; _9 E0 `
1- Z7 f8 S. g7 E/ S# j# I
2# Q* R& ?" ^# T6 V
3
' n7 h' J- \) N47 [* K2 g [- I
5
4 T* J! C- B8 l% J# n% D9.4 练习( e, V6 `* S9 i! u% \) M& k
Ex1: 统计未出现的类别
# H$ u3 J. Y* r 在第五章中介绍了crosstab函数,在默认参数下它能够对两个列的组合出现的频数进行统计汇总:4 t, H2 Y+ P% G% n$ y7 A/ I
: ]0 {: `1 k; x
df = pd.DataFrame({'A':['a','b','c','a'], 'B':['cat','cat','dog','cat']}). E4 F1 B9 U0 X: p4 N5 \
pd.crosstab(df.A, df.B)
. J: Y7 g5 u: J- X) e5 h
0 O% {# \& A2 n% ^Out[72]: 2 S' @; M, s) ?& r A9 u
B cat dog/ _" c0 `# [# Z/ s+ i
A
$ u# B" ` J' Z0 e! s+ P3 @0 Qa 2 0
9 j6 g" g/ V' E: ]b 1 0
m. c! }9 H c! _c 0 1
0 Y# [: S% k- A2 n! V: m n1 W1; J$ W! O g* W+ n1 d; d: @
24 R2 y f; \5 k& p2 r. _7 |
3) R+ E- V. E1 m8 ?% d
4
: P2 b" l% e9 u% S. o5
4 Y/ {# r: X9 X* j @ K. D62 \( t1 q, C- d) ` P- s- g0 N
7, F2 ]9 F& Q& m/ K
8
5 j0 l2 K) t$ W r$ I' V# N9% l* `) A4 W, W: L* q+ U; V
但事实上有些列存储的是分类变量,列中并不一定包含所有的类别,此时如果想要对这些未出现的类别在crosstab结果中也进行汇总,则可以指定dropna参数为False:
% _' n) E+ u' \6 K
* n% v' R7 S9 L1 S+ Ydf.B = df.B.astype('category').cat.add_categories('sheep')
/ l9 g5 i* d0 }4 vpd.crosstab(df.A, df.B, dropna=False)
7 @9 H& C+ C% X: F1 w& }7 t, j& O
A0 I* n) |6 Z3 m" GOut[74]: / w' D5 F8 Y5 t
B cat dog sheep
. Y8 U/ m- C8 z2 \% m# f+ C gA + l5 X- N0 C9 h
a 2 0 0
( o3 k: a0 J" B& U pb 1 0 0+ d# z9 O+ w. G# @; @3 E
c 0 1 08 C% z/ Y/ G% o( Q# l6 M6 Y
1) h- W% ~0 g0 B) v+ @2 {$ m
2
# o! A) N& N- u: R) X3- W7 x; \; W4 j X9 Z
4
* ?% z. G, t# p9 c# e. o- t54 s3 U* v6 o1 X5 A: W
6- z' r1 A4 R; o5 s# J" X
7
' K6 Y# y5 r, K T2 Y8, t% h* W" ~ @- s/ t+ @* B/ Y
9# ]( k' u$ h* \0 G) k( U
请实现一个带有dropna参数的my_crosstab函数来完成上面的功能。6 C+ k, W6 R7 V; v
+ K8 Z* U& I$ K1 w
Ex2: 钻石数据集! b, d8 P5 a, c: p# G
现有一份关于钻石的数据集,其中carat, cut, clarity, price分别表示克拉重量、切割质量、纯净度和价格,样例如下:! R u4 b$ k+ g& O1 I
n* V/ V0 N* W k1 M }
df = pd.read_csv('../data/diamonds.csv')
9 t- {. c4 a8 ?# Pdf.head(3)
% Q; c! j% T7 K( K* [" M: p- Z7 G' _3 |( @, L
Out[76]:
- S6 R( Y) A0 K carat cut clarity price4 n- a' z; {( `/ `7 u5 a4 A+ D
0 0.23 Ideal SI2 326
: W5 G" k C" d' X7 h. }' J1 h1 0.21 Premium SI1 326
, z1 K- |. K4 a; ~' n: V; O+ y2 0.23 Good VS1 327" L9 Z+ Q2 \7 S- L( ?+ Q5 d, P+ F% O- Q
1" k3 P" ^( V2 _& G
24 e0 B) X8 g$ V2 i; U0 L
3* @4 k0 O8 F8 \' T9 g
4
$ e3 V" d. q. x$ ]& N, [. @9 `5
1 P/ G2 c8 f( D- A+ f$ V" E60 l* r7 _( g" Q ~
79 o$ A( [. I5 V: W0 Y6 Y; w/ c! U' T6 v
8( Y5 f' H9 q7 w4 C- H
分别对df.cut在object类型和category类型下使用nunique函数,并比较它们的性能。
5 U8 K0 I0 j2 f; b5 F钻石的切割质量可以分为五个等级,由次到好分别是Fair, Good, Very Good, Premium, Ideal,纯净度有八个等级,由次到好分别是I1, SI2, SI1, VS2, VS1, VVS2, VVS1, IF,请对切割质量按照由好到次的顺序排序,相同切割质量的钻石,按照纯净度进行由次到好的排序。, y7 Z, |: ~+ m# l) l* q! B# E
分别采用两种不同的方法,把cut, clarity这两列按照由好到次的顺序,映射到从0到n-1的整数,其中n表示类别的个数。
) t4 R2 W( Z7 M9 z. N ? O- B6 {对每克拉的价格分别按照分位数(q=[0.2, 0.4, 0.6, 0.8])与[1000, 3500, 5500, 18000]割点进行分箱得到五个类别Very Low, Low, Mid, High, Very High,并把按这两种分箱方法得到的category序列依次添加到原表中。( ?/ [" {/ G$ \5 D I& R
第4问中按照整数分箱得到的序列中,是否出现了所有的类别?如果存在没有出现的类别请把该类别删除。! y8 K6 T/ P+ D8 T( G
对第4问中按照分位数分箱得到的序列,求每个样本对应所在区间的左右端点值和长度。
) x9 C( _6 p9 @+ |先看看数据结构:+ @6 S" C* ~# j; H0 ~5 V! B3 s
7 \# x- @0 a1 P; v* F4 C
df.info(): A2 d2 y% F7 X
Data columns (total 4 columns):* t! t& A6 K0 D+ \7 L
# Column Non-Null Count Dtype
: T* b/ z# k; |% F) n--- ------ -------------- ----- ! n% d1 ^9 {" K" b
0 carat 53940 non-null float64
# I- ]3 f& ^* \* @+ s0 h- S 1 cut 53940 non-null object . l; V. l: H9 z
2 clarity 53940 non-null object
3 t# z% T/ G% S: U 3 price 53940 non-null int64
0 q6 p) t, X6 edtypes: float64(1), int64(1), object(2)
4 [+ s& E+ Z M5 m1 I" r' O1
! L8 u" D& F; W7 U5 p$ X22 A2 ?% v2 e. M% W6 s9 f+ r# c* ]
3* q6 c5 T2 d- W6 u& G6 o M
4
0 @1 k* A# G( w! y5
+ V$ n1 i) D. E# m6- o. V! Z W4 v3 P5 e% H
7
Z! X1 r* M* _) j3 r p' P8/ F1 w7 M/ w# h8 H" Z
9+ l& G' T# e& _5 g; l
比较两种操作的性能
: ?9 d4 d/ Z( a3 t+ P; A$ f%time df.cut.unique()
+ e5 C; ^# X0 k. @7 }: H" D# {" x, M7 Z1 \% N/ `) K" }9 I* t" I5 p
Wall time: 5.98 ms
3 p, ~; A# |& u' r" W0 harray(['Ideal', 'Premium', 'Good', 'Very Good', 'Fair'], dtype=object)
/ A" _: ^& B1 r1
! h/ O4 `. `; s& a& ^' N9 V2
6 x4 {6 O, b9 z- h, @, F0 k1 M' K3 l/ F3
% E t- {% E* U6 {$ `4. v8 q+ y: m2 @" K9 f
%time df.cut.astype('category').unique()
4 H/ F. t( ]" W' a3 \3 C3 N: q
6 [2 I9 [+ |0 q+ y$ MWall time: 8.01 ms # 转换类型加统计类别,一共8ms
& p4 i5 M% J( i' a& ?['Ideal', 'Premium', 'Good', 'Very Good', 'Fair']9 G& G- n& x) J6 k+ {# E
Categories (5, object): ['Ideal', 'Premium', 'Good', 'Very Good', 'Fair']' Y6 S6 A. o5 d( e# B- F. P) [5 q
12 Y; X. V, e$ l8 B0 w* U
2
/ j4 h% d* T5 K( g% W3; C& U" H+ n) O r S/ z
4
0 ?; y8 p+ k% k1 u5# s3 I) m3 q1 {& i
df.cut=df.cut.astype('category')# ?+ V; z' Z( e: G5 G$ h1 I3 \* Y
%time df.cut.unique() # 类别属性统计,2ms
% h+ C- F0 t# e$ S8 ?
+ y" r9 Z4 Q) h+ r( [/ LWall time: 2 ms
6 b$ t V- J! }+ Z# M['Ideal', 'Premium', 'Good', 'Very Good', 'Fair']
' W7 c' r. p: ?( z: Y0 jCategories (5, object): ['Ideal', 'Premium', 'Good', 'Very Good', 'Fair']
4 n( n' _9 a1 o1( @/ |: U `5 n Q0 s3 O
2/ o# X' I. L6 E4 r7 ~
3+ z& Q ~7 |; B, r2 t- D0 u
4
# {* X; G- h- W; l' z5
, m; Y8 y0 k; Q3 J l60 D2 A" e- {! Q5 k' _/ w
对切割质量按照由好到次的顺序排序,相同切割质量的钻石,按照纯净度进行由次到好的排序。
+ a: T! a+ v; p* c1 X% d& f) Z0 F, Xls_cut=['Fair', 'Good', 'Very Good', 'Premium', 'Ideal']
# ]: `. `' h$ ^! y8 A1 ols_clarity=['I1','SI2', 'SI1', 'VS2', 'VS1', 'VVS2', 'VVS1', 'IF']
& E- W4 j. j2 }7 v1 y8 x) P# n, Mdf.cut=df.cut.astype('category').cat.reorder_categories(ls_cut,ordered=True) # 转换后还是得进行替换. ]( B) r) L6 G! A s
df.clarity=df.clarity.astype('category').cat.reorder_categories(ls_clarity,ordered=True)5 \ v3 c5 k% ^+ T
* \7 g. B3 U1 c; l1 E) R/ {) H e) r
df.sort_values(['cut','clarity'],ascending=[False,True]).head(3)0 R$ p9 {8 x8 o: `: I
6 O" L. g3 j; M1 c$ `" u N
carat cut clarity price/ s; ~7 N8 M( o
315 0.96 Ideal I1 28017 X& c9 ~2 L- C9 ]/ e' _
535 0.96 Ideal I1 28263 g5 U& h2 X) a
551 0.97 Ideal I1 2830& b: ?6 k6 H* a& l, b
1
$ z1 f* @; C0 `' _2& S) m+ h9 T, g, B+ k* |
38 [ x$ ]* w' a* U1 [* ?# j; J
4) a) a5 l6 J) c6 p/ ^& E
5 l) J C* i, y3 v
6
0 O1 x; ^5 m5 X0 k9 o7% s8 Z4 _- L# q
8
3 L$ T4 K* J8 n/ W" u6 I9
* n' K5 a+ }( X10
2 {5 h; |$ w3 h K/ M- u11 ?, }& r' z3 Z$ `$ n9 |4 O
分别采用两种不同的方法,把 cut, clarity 这两列按照 由好到次 的顺序,映射到从0到n-1的整数,其中n表示类别的个数。! b& \3 ^- x/ B8 G
# 第一种是将类别重命名为整数$ X, Q( W' I8 K8 t h
dict1=dict(zip(ls_cut,[x for x in range (4,-1,-1)]))" J1 K- A' n! s1 ]6 T
dict2=dict(zip(ls_clarity,[x for x in range (7,-1,-1)]))
& P, Z* g. o% h& Z' ~. Q" z; `1 r) }
df.cut=df.cut.cat.rename_categories(dict1)! w! V3 O8 \* J" t; C1 r
df.clarity=df.clarity.cat.rename_categories(dict2)
5 n! |% Y& c& a5 odf.head(3)
l% H( _0 `) _( V3 T9 P+ g
4 q! |; H4 s& t( ~, V carat cut clarity price7 t k4 Z5 x% Y' m6 ~
0 0.23 0 6 326
; M- B% C# x& ~4 ^: r1 0.21 1 5 326
% z4 X* n1 E( W: a; e" b2 0.23 3 3 327! v7 @# E; b3 r& I% w) ^+ e" ~
1 m, G2 T) k' P- M1 d
2
& v1 i2 _: F6 a2 T9 s3 ]3 Y* R/ X3
B/ O) N- P( O. w( f+ X5 l4, x: K6 f- B1 C7 D9 }6 Y
5, {% c1 P* _" @- v
6
! o! N2 ~0 G& {4 }) p7, n* B0 L8 U: ~! n3 U; Y! H9 M
8( O) i/ u6 @& l
9
/ l/ Y# p% v" g+ F w10
$ l! a/ \% O1 x) M1 F @" L110 l+ E6 \" l) V. y7 X `
12
L0 B7 s" L" V! ]# 第二种应该是报错object属性,然后直接进行替换% y: [# ]8 z6 b# @4 R/ H
df = pd.read_csv('data/diamonds.csv')
# M# [* }, ?4 P' }& [for i,j in enumerate(ls_cut[::-1]):
0 l* K4 E5 M. E& `5 c: F df.loc[df.cut==j,'cut']=i
! o2 D7 l& W/ z( _8 U) J$ M1 N2 {, g( h" P
for k,l in enumerate(ls_clarity[::-1]):
1 O+ I% \" h! x* c. U% Z; s/ \6 W df.loc[df.clarity==l,'clarity']=k& g" W, j' ]1 G1 ]. W2 z5 M% Q
df.head(3)8 p1 ]& Q2 V2 Y# ~
3 \7 J/ m1 y( |/ z
carat cut clarity price
/ }3 ?# y& E4 N0 0.23 0 6 326
0 O+ H, [2 _; k+ B) z) |& J( `+ z/ x1 0.21 1 5 326& j7 A* o* i0 |, a W2 |. B4 u
2 0.23 3 3 327: \0 q# R- c+ f% j5 @) l
1
* D7 W4 g% p, ^# q/ M8 v( B: L2
" e# g* m1 X, M; Z3
9 J1 x* A: C$ n' G' J5 l, v) }4
2 `0 |/ `( ^9 p. _0 x. r! ~55 a9 Y- {; B( N; }- [
6
2 C$ o2 {5 z, |7 P6 Z( e: J& c7
* w0 u# _) i$ a9 w0 P p# [8
5 ^: d% U4 D1 R& \8 q9+ N4 o; J- l) [* K+ v; r) m
10
, P7 m/ K, N( i5 `. k' Y11
* S+ C9 c$ n5 S; w/ i- M2 t12" U# ` j" |2 y0 m
13# N( V" ^3 H' a
对每克拉的价格分别按照分位数(q=[0.2, 0.4, 0.6, 0.8])与[1000, 3500, 5500, 18000]割点进行分箱得到五个类别Very Low, Low, Mid, High, Very High,并把按这两种分箱方法得到的category序列依次添加到原表中。8 Q4 S5 t1 a$ I
# retbins=True返回的是元组,第一个才是要的序列,第二个元素是分割点
8 J1 l5 t' L g% ?( j; \9 _avg=df.price/df.carat
" [6 v7 d7 f/ P% K7 L% u( b
' s5 w" R( \7 Y3 Q! O* R7 bdf['price_quantile']=pd.qcut(avg, q=[0,0.2,0.4,0.6,0.8,1],
( F" f5 f! L7 q" E" z2 Z$ U labels=['Very Low', 'Low', 'Mid', 'High', 'Very High'],retbins=True)[0]
; R) R, I2 `8 o, ~( r& i0 n
! W0 m' Z& G* ?df['price_list']=pd.cut(avg, bins=[-np.infty,1000, 3500, 5500, 18000,np.infty],( a0 y# U, z/ a9 T( o! u
labels=['Very Low', 'Low', 'Mid', 'High', 'Very High'],retbins=True)[0]
% ]4 [5 S: o0 w A `; u0 ~( \$ ldf.head()0 }4 s& }6 D6 O7 s
, d! A) L$ E& t/ j* u4 ?1 G9 p
carat cut clarity price price_quantile price_list0 j) z" a, d( {0 y I1 l3 U
0 0.23 0 6 326 Very Low Low
2 ^) J5 A2 w+ l: U% B1 0.21 1 5 326 Very Low Low
: L- \2 C. \& v0 |4 B( N- F2 0.23 3 3 327 Very Low Low P- L" D* L; Y3 k& |
3 0.29 1 4 334 Very Low Low
9 m$ \8 ?4 _- | n- M4 0.31 3 6 335 Very Low Low / _1 ?8 F5 v: C( I2 c- ?
& n- v2 B# ?1 W2 l; L! |: i* z
1: m; p/ f5 ~2 Y! N$ e3 y j
2( D8 t% ]$ \$ h% Z
3
! j) @% W# ~# O& N! z+ |4
3 c5 U k" P# J5
. K: F* m4 \9 d' w; e6 z! }6
; s" r# k* H5 a- I7
7 {) A+ r q+ {0 K, W3 u8
9 X# n' j' t* c/ [5 d1 S' V- p9
& F% ?, C3 n* y+ m. r/ }! |10
3 V0 c, Q$ C* w; ]$ s2 }* X11# [7 l$ w2 u% x7 B/ W3 S" j
12
) k; j) w, g2 Q9 e13
2 e3 v) L9 z% s* O$ Z14
0 \9 _' b. |) S( s15- u V4 D7 T! e" U+ Y2 q4 X* W
16
: e( l$ M' [5 [; O5 b0 ?分割点分别是:5 o3 F! l- T `4 O% m4 Y
- ]3 m* E4 Y/ n Darray([ 1051.16 , 2295. , 3073.29, 4031.68, 5456.34, 17828.84])+ m% ~0 P" U |/ ?0 y
array([ -inf, 1000., 3500., 5500., 18000., inf])+ O) d6 k3 I, c! ]: O+ C3 d& a
15 f: G( v5 m* J
2% Z" L; {, A0 B" V: i: T& E+ \
第4问中按照整数分箱得到的序列中,是否出现了所有的类别?如果存在没有出现的类别请把该类别删除。1 C" e2 V' b; b$ J5 n
df['price_list'].cat.categories # 原先设定的类别数
; ^+ F4 \. p/ A+ {Index(['Very Low', 'Low', 'Mid', 'High', 'Very High'], dtype='object')7 C' b" k, R- _+ W! A6 d1 X& V5 w
7 H) p9 o) f( E* h6 M0 W( }
df['price_list'].cat.remove_unused_categories().cat.categories # 移除未出现的类别
$ l$ B' G! i: D+ s6 [ dIndex(['Low', 'Mid', 'High'], dtype='object') # 首尾两个类别未出现
% q' U) n* J+ N7 b1
% \' N1 ^1 i+ m8 Y2. l' l% `: P9 f
31 m' h3 p" e# U$ X( \4 G% h
4
$ ?1 i" }1 G; f1 k- c5 g5
+ m- }. m* l1 b' X) Savg.sort_values() # 可见首尾区间确实是没有的5 m) M& H( m, j3 ?: h( t
31962 1051.162791
! G- N; ~2 g+ _2 h6 T( E( U& `( F15 1078.125000
8 \+ N* K, u" E" ]. }4 1080.645161/ w9 L+ }* O B( ~$ H$ x" i! w7 B7 `
28285 1109.090909
- F9 |4 w$ n W* t13 1109.677419, H6 N5 ?& o$ A8 \0 H
... ; H$ o+ P/ M9 A6 `5 [
26998 16764.7058824 [( j; T# K6 T2 M" Y1 U( c
27457 16928.971963% F/ K! W) H" \( l
27226 17077.6699037 q! \* s! ]/ c& {& L4 e
27530 17083.1775709 J- C3 F B/ e* L
27635 17828.8461547 I8 W) n2 Q+ b! Y! [3 \
1' ~4 t+ ~/ c$ n1 ~7 o8 T5 U
2
0 ]& [: E! d' M% c& P e3 N3
8 Z6 \% u3 A7 C5 |5 R$ |+ M5 M+ [4
8 k3 K( r8 V4 ~) V" G5
# X; k% `1 g4 z( w$ M Z+ `; E& f6
. ]/ v9 z7 c, z, e% l( h6 B5 h7
# a/ D2 D0 ~, ?! Z' _6 [# }88 d6 K& h8 _; u( [" {+ Z, S0 g
9
! j+ q! |+ Y2 j' Y+ k+ q' ]( H10
S0 k3 |* L; X; P( T! W11
& V3 Z9 H) s0 b' ^! I6 R }7 P/ L127 L0 b/ \8 ?1 W; f% s5 b+ j2 o
对第4问中按照分位数分箱得到的序列,求每个样本对应所在区间的左右端点值和长度。
9 N% W, ~5 M! p! S# 分割时区间不能有命名,否则字符串传入错误。/ K( ^' R+ ]" F. W& V) G- H* X
id_interval=pd.IntervalIndex(& l- ^( Z; O6 f+ C6 s$ {: N
pd.qcut(avg, q=[0,0.2,0.4,0.6,0.8,1],retbins=True)[0]2 Z! [, B, d' l7 E& \$ q
)
' s& f7 {/ W+ h! b0 i" F" o% C l+ ~) iid_interval.left
' m: K( ?' L6 p) K1 @id_interval.right
1 [1 l/ G |7 D6 v6 L1 k% F4 ^id_interval.length
6 H3 _+ p) w, `1 F L1
3 V4 s, @1 n+ s* S( N7 n1 b' M5 o- S2
5 `3 m p9 {+ i) |35 G' c+ A% K( I4 o: b& R- _
41 o& l% @9 x9 J# d0 V
5
N, `2 ~" p5 }6" L; B2 @+ x+ M7 C: L# C
7
4 L z# ?7 }# f( H# y, c$ w第十章 时序数据$ [- L' C% J% r
import numpy as np
* r( Z: ~2 o# H Z0 c: Gimport pandas as pd+ p" |/ @" P4 t* `0 _) H( A
1
( }, Z- V) R8 ?2 J2( q0 X7 ?: r# }7 X
0 i" l w2 }" \* B* J! L4 f4 S4 c$ w; Q6 u
10.1 时序中的基本对象9 m8 G2 H* u* B$ S2 l
时间序列的概念在日常生活中十分常见,但对于一个具体的时序事件而言,可以从多个时间对象的角度来描述。例如2020年9月7日周一早上8点整需要到教室上课,这个课会在当天早上10点结束,其中包含了哪些时间概念?
& @! Y5 h9 l( c
+ @/ T% T8 p6 i. h; T会出现时间戳(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的简写。. a* V9 ?# j4 I7 L
: [ s, e3 j7 [. G会出现时间差(Time deltas)的概念,即上课需要的时间,两个Timestamp做差就得到了时间差,pandas中利用Timedelta来表示。类似的,一系列的时间差就组成了TimedeltaIndex, 而将它放到Series中后,Series的类型就变为了timedelta64[ns]。
# ^) R* N$ J. s8 b! n
/ Y/ ^( E) s' H会出现时间段(Time spans)的概念,即在8点到10点这个区间都会持续地在上课,在pandas利用Period来表示。类似的,一系列的时间段就组成了PeriodIndex, 而将它放到Series中后,Series的类型就变为了Period。
# r* K" e" s7 k6 L6 Q5 G2 e3 F: I2 y% r5 p$ ]
会出现日期偏置(Date offsets)的概念,假设你只知道9月的第一个周一早上8点要去上课,但不知道具体的日期,那么就需要一个类型来处理此类需求。再例如,想要知道2020年9月7日后的第30个工作日是哪一天,那么时间差就解决不了你的问题,从而pandas中的DateOffset就出现了。同时,pandas中没有为一列时间偏置专门设计存储类型,理由也很简单,因为需求比较奇怪,一般来说我们只需要对一批时间特征做一个统一的特殊日期偏置。
; X2 A, u, j3 N3 z3 `8 g1 o: L, S: \
) S7 M3 i# w& {! J5 P# `7 ~ 通过这个简单的例子,就能够容易地总结出官方文档中的这个表格:& b2 K2 t+ D$ S: l5 j5 k+ u5 T
) u% a9 M8 v/ F6 c2 L) c8 z
概念 单元素类型 数组类型 pandas数据类型7 v. {, Q Q7 ?) [# P1 z/ y' B9 I
Date times Timestamp DatetimeIndex datetime64[ns]& `6 h2 y6 T, p; n& Q9 A
Time deltas Timedelta TimedeltaIndex timedelta64[ns]
8 X7 ~# r6 X& |' b# N0 Z* bTime spans Period PeriodIndex period[freq]4 e: F8 A, j* m; A# U1 C
Date offsets DateOffset None None% M+ L& _ V' ~( W6 ]* f6 {* A
由于时间段对象Period/PeriodIndex的使用频率并不高,因此将不进行讲解,而只涉及时间戳序列、时间差序列和日期偏置的相关内容。" x0 s: L2 l0 x4 m
% I: s" }- W) X5 n: C9 b10.2 时间戳
9 s7 f* Y- [" b10.2.1 Timestamp的构造与属性
# c% u2 }0 H0 L单个时间戳的生成利用pd.Timestamp实现,一般而言的常见日期格式都能被成功地转换:& c6 S9 d( ~% |2 R4 u3 F
) H) R5 a8 s& N: S! V4 n) X
ts = pd.Timestamp('2020/1/1'); Y8 O; F5 A3 l4 J1 O
/ s6 ^; @( O( L5 d. xts8 `8 t- V3 W" q+ ~' m. F
Out[4]: Timestamp('2020-01-01 00:00:00')# [( a; A4 C# ~! s
3 j/ ~' w5 {( }2 K# P9 J) dts = pd.Timestamp('2020-1-1 08:10:30')
. a2 L) k! G. z8 `( M9 d4 B+ Z
% F& }. u9 l0 ?7 k. p1 s# V2 f9 U8 h4 Xts
# [& e( |6 O4 J7 _Out[6]: Timestamp('2020-01-01 08:10:30')+ n4 `0 ^5 ?. c- w4 F$ Z3 @ B5 N! c
1* G! _: Q+ X. N+ P$ x
2! g% r7 s7 j, T m* K, f6 S8 G2 H
30 m4 C, c# J( w2 {) c: ?7 C
4
1 m2 ^8 c2 v U$ i5
4 c& k, t/ |. n) h62 Z' L) X6 j* Y$ x. o. t! o T
7
4 P$ i8 V6 o( H; a8$ M" Q0 I8 X3 k2 k3 o* `/ _
90 I; O1 ]! f3 x) f d* p2 `
通过year, month, day, hour, min, second可以获取具体的数值:
( y7 u$ T3 k; B) d. f; ^8 ~# L
2 `+ P; t) P! S- X: {, @ts.year
2 Y4 L6 h" J/ J% H( W+ J" tOut[7]: 2020
- b0 V" K+ @8 X# ^" n4 A6 i8 B$ t! p3 B
ts.month' }3 o8 _; Z- R" Y( n
Out[8]: 1) A; y+ V$ v! _3 Z. _$ L% ^/ e
Y6 C5 C0 k6 r7 h0 ?- U* z
ts.day" q* o, @. h% \0 t
Out[9]: 1, U8 } {7 E* n$ h# b
8 y6 H7 c( K5 l/ w# `7 H; u* r
ts.hour
4 ~. D# r4 v% q8 Y, }& v) gOut[10]: 8( a% x: l6 u) g
; n! _! [+ Q+ D( e( }, K
ts.minute
; f1 r' `# _5 b8 O4 f ?8 FOut[11]: 10
- l* i. O9 O& g* P, r, [( ^. i6 k! |! i
ts.second
* E0 F) W* a+ Y4 E5 ZOut[12]: 30
9 e3 O' v% ?: h8 S2 i8 ^2 d+ Q& J$ L7 a% \0 A( j! N, D+ o' l
1, j0 B$ _7 Y5 p5 F# d
2! o! q" f% D4 i3 {5 T3 a
3- W4 v# z- ]9 B/ k/ ?1 R# d
4
: ~: m: R) e+ }+ _% i& y: o/ A, [2 R5; x' {( s: O5 f4 N+ p$ u/ Z K7 b
6
2 J8 K$ |# i6 e+ U7
3 M' m8 E2 s9 b. D1 R3 j8( t L" b- l. Y; g
9
# Q3 \/ \- J* K( Q% [# t4 O10- @: Q3 e6 {9 a; ?: O) B
11
+ o( p7 L7 i C& d( Z12
0 ^1 c) { v+ \# T139 E' J {! _5 O) I3 r
14" _' Z* L$ e) ?! u. k) R
15
. z# E7 U: n% i8 @16
) G1 \! x0 F1 V: q# Z17
9 q( O1 }! [4 K* Z+ ~+ M# 获取当前时间- u* q# k, ?3 h
now=pd.Timestamp.now()
+ \8 n: j! ]. h" ^: A1 G( s& Q1' b% F; b) E( A
2+ X/ c2 S" l! f6 Y" `5 k
在pandas中,时间戳的最小精度为纳秒ns,由于使用了64位存储,可以表示的时间范围大约可以如下计算:
3 \ c1 z7 i, h* x7 pT 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)2 X# d" j3 x; I, W9 \
TimeRange=
+ {" `; E# l. Y- J3 o10
1 I- {5 s; O2 C- v! @95 a' W, f; H- h9 T% _% j7 I
×60×60×24×365& g0 x& e( ?2 H, H9 O* i- {
2
" ~! j/ s# q: H3 p. h' z! o641 s& ~2 j! s6 d0 K0 \6 A4 H
- P5 H( x9 y7 c* Q; q& O
' U$ X; ~ R- _ ≈585(Years)
( r$ Q9 {# @% Z1 L$ X! h( N* f6 Q% n1 R# \1 b6 [" d2 ~ V0 ^, ^' S
通过pd.Timestamp.max和pd.Timestamp.min可以获取时间戳表示的范围,可以看到确实表示的区间年数大小正如上述计算结果:. l6 W( Z6 z5 ]: K
& e: }" ]0 N) w8 C4 B o9 _
pd.Timestamp.max& i* |9 f7 b4 J; f: a7 G1 K9 y
Out[13]: Timestamp('2262-04-11 23:47:16.854775807')
& d+ o2 }8 \- m( Y5 r) h# N
& R4 E5 f- g1 q. X, H3 Gpd.Timestamp.min
4 o+ g. a: Q6 AOut[14]: Timestamp('1677-09-21 00:12:43.145225')
' ]; N9 b- n7 u' w1 j% `, C5 F
5 S! _( K1 d0 Dpd.Timestamp.max.year - pd.Timestamp.min.year: [/ t5 M* I0 x/ K
Out[15]: 585
# g- F1 Z2 H* ?8 v0 L1 j1 X% w) Y% l' ~5 ^5 K# C
2: s" v8 u+ T5 E5 U: v' P
35 k; \) A% C7 x
4
2 w1 d: L+ Q6 S7 o& U9 p50 J" x3 S( a: A
6
" E( c5 W) \6 B8 E) h _# j3 |7, I, x" u S' T% E
8
4 ?: s# v2 s+ @% u# S: U10.2.2 Datetime序列的生成
8 W% Z: m1 Z5 B3 v% m% }% a5 ppandas.to_datetime(arg, errors='raise', dayfirst=False, yearfirst=False, utc=None, format=None,& g, W2 X' L( t3 i( M
exact=True, unit=None, infer_datetime_format=False, origin='unix', cache=True)# W3 s) `7 j5 o0 V
1
+ I# O9 x% v7 f% r. r4 Y8 r. }$ u2
2 \. R5 |4 q6 }( _pandas.to_datetime将arg转换为日期时间。
2 A9 }9 a; D+ v6 v+ X
2 g0 M) w1 c& O8 f) Farg:可以是argint、float、str、datetime、list、tuple、一维数组、Series、DataFrame/dict-like等要转换为日期时间的对象。如果提供了 DataFrame,则该方法至少需要以下列:“年”、“月”、“日”。
Y) j! a* u- X2 ]) V3 X+ Ierrors:
, V, t& b% C9 n [- ‘raise’:默认值,无效解析将引发异常3 p% n/ J* `$ T- d9 t( r
- ‘raise’:无效解析将返回输入
+ M# h8 R; @" v5 e Y6 B2 I- ‘coerce’:无效解析将被设置为NaT* W6 Q; l! m: A: X' h3 ^
dayfirst:如果 arg 是 str 或类似列表时使用,表示日期解析顺序,默认为 False。如果为 True,则首先解析日期,例如“10/11/12”被解析为 2012-11-10。如果无法根据给定的 dayfirst 选项解析分隔日期字符串,会显示警告。
, N; z `5 L# E% K/ @yearfirst:如果 arg 是 str 或类似列表时使用,表示日期解析顺序,默认为 False。如果为 True,则首先解析年份,例如“10/11/12”被解析为2010-11-12。无法正确解析时会显示警告。(如果 dayfirst 和 yearfirst 都为 True,则 yearfirst 优先(与 dateutil 相同)。)
8 v/ w A# J5 _! ]7 Yutcbool:默认None,控制时区相关的解析、本地化和转换。请参阅:pandas 有关时区转换和本地化的一般文档
9 `$ @, A7 p' n: ]. I' n6 Kformat:str格式,默认None。时间戳的格式不满足转换时,可以强制使用format进行匹配。0 w1 c" g' v( r% P7 Z" h( T! Q
unitstr:默认“ns”。它是arg (D,s,ms,us,ns) 的表示单位,可以是整数或浮点数。这将基于原点。例如,使用 unit=‘ms’ 和 origin=‘unix’ (默认值),这将计算到 unix 开始的毫秒数。
& Y) D9 U. U5 I1 x1 N. M2 Kto_datetime能够把一列时间戳格式的对象转换成为datetime64[ns]类型的时间序列:
/ R9 z4 e+ G/ F; \; Dpd.to_datetime(['2020-1-1', '2020-1-3', '2020-1-6'])1 N1 q" D: G4 ?9 ]& z" S
: M d6 B1 R6 `: {. t5 IDatetimeIndex(['2020-01-01', '2020-01-03', '2020-01-06'], dtype='datetime64[ns]', freq=None)
9 ?6 V# r& }; u: ^; {9 F8 c, g1
/ j3 ]; j0 f$ s4 p2
' d$ c& x& V9 ?' f+ g [3
: V& p( i) D( i3 ^在极少数情况,时间戳的格式不满足转换时,可以强制使用format进行匹配:5 J4 `4 p. P( D# \4 w4 t1 ]" ~5 U. C7 a
1 J% o% z5 w& z* }temp = pd.to_datetime(['2020\\1\\1','2020\\1\\3'],format='%Y\\%m\\%d')- H7 J8 L9 ?( e
temp* R! M, `( T! \' ` {6 c( n) Z8 n$ O
/ O6 h. }7 E# x; [% h- BDatetimeIndex(['2020-01-01', '2020-01-03'], dtype='datetime64[ns]', freq=None)
' g/ U9 ?$ D. g2 I# _" I9 s6 {: G11 M7 y" o; F& \9 v- F- ~& m
2! m+ v: v8 V: {3 A# v
3
' I) E8 M9 X/ M4) Q2 T+ g! {! X+ s" f4 `
注意上面由于传入的是列表,而非pandas内部的Series,因此返回的是DatetimeIndex,如果想要转为datetime64[ns]的序列,需要显式用Series转化:% F+ O& Y8 I& x3 l) y* m/ t/ H
2 Z6 i$ k- f- y# `; c4 Vpd.Series(temp).head()5 s7 \9 K9 X$ \) u0 T4 B
$ H) r; {# C* e: Q* J# A0 2020-01-01
/ @' Q0 U3 u2 B+ c1 2020-01-03# J& n! L p9 [% ?4 ?2 L- K
dtype: datetime64[ns]
0 C7 t2 b+ H& i. q% j1
4 i3 }* e i/ t& s2* l. }; \! a; b9 U1 j& G; D
36 B/ ]6 K [0 E' ?: |) j
4
8 `/ l' Y* I; H6 P5
' A% i9 z! H# T9 l* t下面的序列本身就是Series,所以不需要再转化。) K# G- @* b+ K2 m. [
2 M( i3 {+ |! ^# X6 |df = pd.read_csv('../data/learn_pandas.csv')* E2 h W& _7 d1 U. d1 X
s = pd.to_datetime(df.Test_Date): s, L! w) a' v$ p- b9 y
s.head()
1 P7 V2 ~! k$ M
" g+ `' j6 F; a' s6 M0 J$ D0 2019-10-05
; @3 G7 S: d$ p1 O6 g) S1 2019-09-04/ q. a. `' S O
2 2019-09-12
' M4 ?$ D& @( Y3 2020-01-03, O+ v9 N1 M4 ~. U$ G
4 2019-11-06
! d0 e6 m9 [6 ~+ F5 i0 A, D8 F& e, TName: Test_Date, dtype: datetime64[ns]
) i$ X9 m. q; n/ Y& X; |2 p( M11 F( L. w8 N2 G; R
2
& }* F8 {3 ?/ Q& P2 v; x+ j7 u/ T3
) b7 w. [! B" j. d/ Y, U9 }4
, g+ w9 q$ ?0 o) u5
2 `3 H D# i% {$ W6 Y61 O5 s5 Y% h8 J5 V, Q O4 u
73 V& }9 j% I" X
8
& A; E# h# T' s+ C* q# O9' F" K7 ^0 q# x# E7 t" N
10; r) P7 D5 f3 E8 B; U' u
把表的多列时间属性拼接转为时间序列的to_datetime,此时的列名必须和以下给定的时间关键词列名一致:
- ^* z) D" d# S; Odf_date_cols = pd.DataFrame({'year': [2020, 2020],) W9 D1 P+ {# m3 c! N
'month': [1, 1],
1 |4 V# j2 Y7 K4 u8 `1 q% C 'day': [1, 2],0 n) g! S0 T1 i g k i% Z
'hour': [10, 20],
( O$ _% k5 X. Z9 c4 U$ K 'minute': [30, 50],* f& N3 s0 W! M5 p* A! Z+ D& |
'second': [20, 40]})) M4 O# @) \. ]4 p
pd.to_datetime(df_date_cols)
( i4 }& E N- g: F8 Y
$ C( q4 X# R$ e ^; ]0 2020-01-01 10:30:20
1 a: w* b+ h$ I6 i1 O1 2020-01-02 20:50:40: f5 q# k/ {5 t L% X. e
dtype: datetime64[ns]' k* H/ o. K1 k! M# `. M
1( k+ |+ D, I& q% ~. l; U8 _
2
7 `- ~8 q. f8 s' n1 h; q7 B3
" N4 B. ^! F1 Z* E4* D- m" i9 K: G5 Z6 `) u; h
5
$ ^- c+ D7 t- N0 \6$ j/ n- t' a# v* d3 @% R
7" m* @, A' Z9 F3 a8 A
8
) B) q& F1 F/ b: b6 _* |9% [3 R* B8 m# i; c$ E/ A: \3 c* A
108 e! e9 M- H, d$ B" u) g
11
% I* t/ q+ o; x: l! ?date_range是一种生成连续间隔时间的一种方法,其重要的参数为start, end, freq, periods,它们分别表示开始时间,结束时间,时间间隔,时间戳个数。其中,四个中的三个参数决定了,那么剩下的一个就随之确定了。这里要注意,开始或结束日期如果作为端点则它会被包含:
; `/ j' w6 t, p6 {. ypd.date_range('2020-1-1','2020-1-21', freq='10D') # 包含
9 B* e' M" `1 P3 @( N, QOut[25]: DatetimeIndex(['2020-01-01', '2020-01-11', '2020-01-21'], dtype='datetime64[ns]', freq='10D')
! F/ n8 u, [9 Z/ b/ ]% }2 C/ W. m0 G1 K. V, `
pd.date_range('2020-1-1','2020-2-28', freq='10D'), c; a7 g) T& Q: t( \& W
Out[26]:
|+ y Q8 e) C. p- K/ C0 cDatetimeIndex(['2020-01-01', '2020-01-11', '2020-01-21', '2020-01-31',5 h% z9 m- v. P
'2020-02-10', '2020-02-20'],6 ]; v% O2 e$ y/ q
dtype='datetime64[ns]', freq='10D')
- H5 U+ ^) I) T: d9 ?2 E, m3 K
6 |2 M5 i! z) t' y5 p. [. q$ V- v+ bpd.date_range('2020-1-1', _7 |; C; y" B1 s% W/ e
'2020-2-28', periods=6) # 由于结束日期无法取到,freq不为10天! a4 r, ]; Z, C/ [
! k" J% z1 l' k- m. ~- mOut[27]: s- w( [$ c5 O/ _/ G& V
DatetimeIndex(['2020-01-01 00:00:00', '2020-01-12 14:24:00',
1 [% z3 x4 q1 J9 ]5 f4 b '2020-01-24 04:48:00', '2020-02-04 19:12:00',
% |, Q/ ^0 S6 ~# v) K- G* d- E '2020-02-16 09:36:00', '2020-02-28 00:00:00'],5 }, Z# Z8 R8 R/ S
dtype='datetime64[ns]', freq=None)
5 L+ w, |2 K+ V5 V* ~# ^( V- z1 O9 R; A; w7 a# r7 F
14 B: Y; D Z, x
2
7 u) M2 d- J! [& A4 X* M/ k3# O) Y$ P# `* I/ f T. N* ?; `) |* d M
4: [4 W, B5 B! T0 c- l6 c
5
0 E6 N# v" W) v8 J" m/ {8 r9 q61 \- ]- r4 y. R+ ?4 P: i% W" Z
7# w" @+ d: X: I. C
8
9 ~6 C% }/ k7 j- G9
+ z' Y7 o! E* F/ {+ Z$ m102 `+ P3 m0 X6 T
11) R# A7 f. H F$ n# d4 m
12* t! y. `4 @/ _. r% i
13& B9 [7 M( z& f7 q# @8 t1 ~
14+ D2 j/ T; P$ ^+ b6 o' y( K6 c
15
& [3 Z( b& V/ F& e6 c4 A3 C166 x" M6 m3 d8 v1 n4 ]9 b
17
, B$ `7 [8 P8 ]! I" n) a这里的freq参数与DateOffset对象紧密相关,将在第四节介绍其具体的用法。! R7 U7 P, a+ d X1 V
. \+ s! F# {/ K
【练一练】9 D/ N% Y& h# \9 `4 y: D* b
Timestamp上定义了一个value属性,其返回的整数值代表了从1970年1月1日零点到给定时间戳相差的纳秒数,请利用这个属性构造一个随机生成给定日期区间内日期序列的函数。
2 Q2 Y+ g0 M' h8 y& C% w$ y. L1 p0 U( b, A: T a
ls=['2020-01-01','2020-02-20']% `0 s7 P- V# r2 M: ~0 h
def dates(ls,n):6 S+ D: C; V7 {. ^ w& ?
min=pd.Timestamp(ls[0]).value/10**9' Z* |, y' h: B, d }2 ?
max=pd.Timestamp(ls[1]).value/10**96 M$ J) R% g* c% o% H. G- o
times=np.random.randint(min,max+1,n)9 J0 T4 p9 W W6 h4 g Q
return pd.to_datetime(times,unit='s')
% C5 P p0 u$ I# N: |$ o2 k7 H8 qdates(ls,10) 6 x: U* a& M8 {+ ?' ^
- R& C- Z! w/ n" h4 r2 Z3 mDatetimeIndex(['2020-02-16 09:25:30', '2020-01-29 07:00:04',
& o! l; W) }2 y '2020-01-21 12:26:02', '2020-02-08 20:34:08',
( [/ w/ K0 a t0 x '2020-02-15 00:18:33', '2020-02-11 02:18:07',
, o5 N" B1 E1 T6 ~5 G4 C- l '2020-01-12 21:48:59', '2020-01-12 00:39:24',$ e: g9 i" E3 e
'2020-02-14 20:55:20', '2020-01-26 15:44:13'],/ v& Z. p3 [) ^9 b
dtype='datetime64[ns]', freq=None)
+ v4 l b- q1 ?2 p$ V+ U, D4 V1
9 a) S, ^, l+ ?3 @3 g' S: d7 N6 ?2" K5 d8 W+ M& W: F
3
& }' v8 ~" c: o7 ], f: P4
! B" J" C5 O+ A$ o( H: ^5) [+ C4 s1 |2 Q' v$ y
6
+ p# a& c% @9 P8 _74 s9 [- h% \) P& x& n
80 O, @; w" K2 G1 `8 n% Z7 E0 @1 i
9
, ?; Z2 a5 ~8 j0 T' Y) Z5 h10
' i6 J& D) i6 }+ t g8 V1 ^3 c11
9 i0 z7 I; [% F( v) [; s12) `, G& ^6 x; S0 x, N
13
7 J: K3 l, x% j7 z1 n; U14) W4 ~5 c Q0 y- j9 x
asfreq:改变序列采样频率的方法,能够根据给定的freq对序列进行类似于reindex的操作:$ ?, X6 r/ J5 R& D% H0 |
s = pd.Series(np.random.rand(5),9 O: f: H4 y0 z! y1 Z% I
index=pd.to_datetime([
- u% u0 [; T5 u! @. F) l! T '2020-1-%d'%i for i in range(1,10,2)]))
7 L0 r. U0 h+ }% J; f. r, s. R+ R2 a
5 F% n4 J$ D% x8 J( i0 _* M: h' A2 d& @* r" P5 q
s.head()( E1 \7 v( A( ?# u- D7 h
Out[29]:
+ j# T& T' I: v E+ ^3 ]+ t) V2020-01-01 0.836578
* v* O+ ~6 e% I6 R" ?6 t, b2020-01-03 0.6784192 E. y/ n. H; i
2020-01-05 0.7118979 y& ]3 K: z; K+ j( w
2020-01-07 0.487429; _" `* ]' |6 ]% \5 c* @
2020-01-09 0.604705$ Y l( z; ^4 o; c6 l
dtype: float642 a: Q) z. ~, C; J
; G! z, ^. x, \% d1 K K/ h5 ~' Bs.asfreq('D').head()# {- q2 T O! L, D0 P8 {: d
Out[30]: ' J8 Z/ Z$ U+ u j' ^
2020-01-01 0.836578
9 r' I, m. j8 W' |2020-01-02 NaN- N+ O! i l' ?7 d! K, I" z
2020-01-03 0.6784194 v) p- a) m& b+ \
2020-01-04 NaN! |5 z) R: o7 K' W- t6 j: j$ `( u/ ~
2020-01-05 0.7118972 k4 u3 {$ }7 L: e3 ]' \( x
Freq: D, dtype: float645 K% ~# G- q. R& F l- j# o
* O9 }/ ^9 {' Y, _& a3 K( R$ ?
s.asfreq('12H').head()
, p2 Y1 H' G' QOut[31]: 6 \3 e# b7 W+ \7 [
2020-01-01 00:00:00 0.8365786 `- k# J6 L$ a0 {0 T3 h! \# [+ ^* b
2020-01-01 12:00:00 NaN
% p5 D6 S) _! b# q! T7 x3 f) Q( s2020-01-02 00:00:00 NaN
) Y/ B A4 b2 \$ i( |3 j! O2020-01-02 12:00:00 NaN
0 f. F! x( O9 v2 Z, a9 S5 s1 e* d2020-01-03 00:00:00 0.678419
/ W9 Z! @7 ^% {3 G8 sFreq: 12H, dtype: float64
2 J* a* k2 o1 Y {1 k; `$ ~1 e! G
: o3 S4 {! s) H: ~5 D- |19 H3 Y K+ U+ A b
2
( Y) z8 d: f8 Z( s30 Z/ \' r9 \. Y7 G) E* q. l
4
( B8 ^4 Q2 F0 z( k8 Z. J5
7 q& B( M/ J& Y0 f. y6 T! O. S- A% k) Z2 O% L
7
( C. g* d1 S) ?! Y7 i+ p1 T8
* P2 k3 w) {2 K$ m0 X6 @9
5 p Y+ n8 J# \10
9 q* n! g& T6 r, L# r% h11* V7 t7 Z' d, \- \
123 v( ^# O' [. q0 {: s
13& c# b3 l! i8 T: J
142 h5 ?5 T. R& R1 \; o
15( `" n' W8 P S+ h
16$ X: O5 W1 m6 H- @9 e2 ?
17( ?3 S% v5 L4 o! g3 n3 S
18
/ k+ M8 s& r. p% C# \19
6 h& E3 r& ^$ T; G) E7 t9 y; i1 W# z20
& r* E: b7 u: p) o21
* |$ N0 m1 w5 @- `1 T2 e) |22, [4 M9 V0 J: d# J" C! h
23
3 Y0 X n- s/ t0 L) H; ~' ^% X24
2 o6 F8 r' T* f2 H25
# ~6 g( S0 H7 H' M* D26
" u5 L3 {# L4 ~9 b* ?6 L270 `4 v/ {# p" s; E3 y; g
28
" |8 s& r" B1 @( V9 v291 J) k8 G' m7 @! V- v. i" c! i
30& a" n, D3 M4 }+ H
31" b6 M0 F0 d* u
【NOTE】datetime64[ns] 序列的极值与均值( C% }7 y" P- y# m" g
前面提到了datetime64[ns]本质上可以理解为一个整数,即从1970年1月1日零点到给定时间戳相差的纳秒数。所以对于一个datetime64[ns]序列,可以使用max, min, mean,来取得最大时间戳、最小时间戳和“平均”时间戳。
$ o* u, ^0 N/ r* r- Y
( ]3 Y. I7 T4 z% l& p10.2.3 dt对象, N: y$ x! ^; |
如同category, string的序列上定义了cat, str来完成分类数据和文本数据的操作,在时序类型的序列上定义了dt对象来完成许多时间序列的相关操作。这里对于datetime64[ns]类型而言,可以大致分为三类操作:取出时间相关的属性、判断时间戳是否满足条件、取整操作。) ~( n: K& W. e* f% Z& ^, l( I
6 {0 D3 l9 O* R4 b6 Q$ x' z) N% x
第一类操作的常用属性包括:date, time, year, month, day, hour, minute, second, microsecond, nanosecond, dayofweek, dayofyear, weekofyear, daysinmonth, quarter,其中daysinmonth, quarter分别表示该月一共有几天和季度。; e! d2 \4 o4 m1 M, f
s = pd.Series(pd.date_range('2020-1-1','2020-1-3', freq='D')), F% A7 J2 v. c
) |# F& }' R; d7 N2 C* ]% o9 c: Rs.dt.date, i5 J* b7 ~# s2 R# n: B$ P
Out[33]: 3 |/ B3 I3 a) R$ v& X
0 2020-01-01
% d# @2 l9 F$ `6 @ P1 2020-01-02
5 m; a, F, [8 n& {% ~% J2 2020-01-03
0 b, }% o4 |4 [% }( ydtype: object5 j7 K. ^# F) [; {
6 ]7 ^% ?5 I# k8 k; J( o# @s.dt.time
3 y9 L* \( \% [$ {* r% e6 m$ kOut[34]:
. a/ G- n" U6 P7 `4 M0 00:00:002 }/ X2 O' S; X+ |
1 00:00:00
, z, S- h' {; l3 s. w8 U6 r2 00:00:00
& q, O Q/ i' a: p1 Gdtype: object+ ]5 m% P+ l8 O. D
. x/ ^- A. D2 A! i
s.dt.day
) _+ x8 H; A7 l* JOut[35]:
' B9 b* `8 k& ~/ E0 1* l) ?4 m) h& e% P) I' s$ F+ G
1 2
8 n* x! _" }( m+ }# h2 31 l& I; A* k2 J
dtype: int64
* w$ P% O6 a7 O! {6 _: m1 {5 L ^$ p P5 R' U9 A6 ]$ U8 s
s.dt.daysinmonth+ v9 k# s3 n2 H9 A& E7 P& p' A4 l
Out[36]: + ]/ W+ [" u# v) x
0 31
/ ^1 ~1 B9 I! D5 s& d1 b) E: X1 31
2 J8 k2 e$ G9 L0 P' P2 314 E+ z3 [# s( P, d, Z
dtype: int64
" O5 Q5 f6 w( n% x* c" n+ c8 z" T3 u3 r
1
+ a+ a( y4 T Q; W+ R, @2
& x, L% z0 E: Y3 l3! x: b3 K1 t! N1 M' a: y$ S
4
9 t+ r l: j+ B7 G0 [+ R% h, ~5
7 }6 Q$ T) z: c4 Y, X6: l) U d m; o9 q- V# Y& x
75 p# _/ {8 W1 Z3 q. u
8
7 b' Q H; q. M2 G0 P6 `9; m3 Y0 i$ T6 G7 n* B# S* w5 e% f
10
& w$ d0 C; S: i3 r3 l4 S115 N( _/ l* p3 U* H( k& _1 ^
12
2 Y2 G) l; o5 a: ^139 j f- g x- h6 Q
14/ T; i( Q3 P2 [! s- a
15
! }# J0 p2 B& u* Z4 O" `16
% R. A, L2 O" E( I1 _. k9 y- e173 L! Y% D U) C; y9 i
185 n) D6 i$ v4 Q4 ~
19
- `6 ^7 x/ }, v9 I: s2 _ U20
* ~/ ?6 \* B: |* P8 x4 Q21
& c& _4 b3 o5 x, F+ }0 N$ [& X226 G3 C, A/ h) {% r
23
6 M! n$ r0 @ J6 j2 v5 P* L1 H5 W; Q% u24
* U6 f+ x( z0 _25
+ [5 b% q9 b& }7 s26
$ S7 _( C% h7 g. H1 T27
4 \4 O4 s4 r. L0 b0 W286 c/ O; u+ o2 d4 f& u: H
292 n5 q6 i. n" K" }& Y8 o1 h
在这些属性中,经常使用的是dayofweek,它返回了周中的星期情况,周一为0、周二为1,以此类推。此外,还可以通过month_name, day_name返回英文的月名和星期名,注意它们是方法而不是属性:
! T9 g9 e' h b5 z7 G: _% k+ y' j" Y7 Y% g$ P$ N
s.dt.dayofweek
; T% ^0 j4 F, lOut[37]:
+ d4 |. x, @* b- x; S' h0 2
: T4 K' U8 J/ b8 E1 3
! G( w. {# a2 b( z2 4
! P3 Y0 H) a8 j9 ~9 ^4 Gdtype: int64
m0 L0 v! ~ T: E& ~8 ]( ?( M6 |
6 r! b9 |+ o. |; h( W% H& Ns.dt.month_name(), H/ ^8 h* q$ w9 |8 h
Out[38]: . _. y0 F; {6 |4 N) A+ |" ?
0 January& S( E( v. u% [% i- e+ O
1 January
1 }" ^9 z3 _$ `3 e# G2 January5 s$ k- R1 }, M$ b [4 c
dtype: object. d* g: A" n: ?. e
7 n) |+ T7 C. B! X. C, E: p2 Y
s.dt.day_name()
% c6 ~+ h* W8 Y5 tOut[39]: ' {& U& ?3 l$ p
0 Wednesday
) G4 S9 Y& F8 s: B5 T- N' d4 Q6 K1 Thursday
% M o( \3 y! J! g% {1 `* J" l2 Friday
2 ]/ t' p# @9 U% U7 wdtype: object
. X# n$ v$ {( [* Y. j
" }# d: ^5 d5 Z, U; |$ \1
, a5 J9 n) W/ g$ t- o/ d- g3 V2 @& G2# M6 F) w. z. w2 h
31 D8 [. J, ~, e% e; u9 Y
4, e7 C( z; Z2 X! E
5( S7 C4 i1 I. W0 U$ m: }( }
6
+ Y. F/ a/ O3 `. F. M74 [$ b" O2 Q1 h" e
8! {& T; Z9 M3 }
9
) i4 l& f. V# U) {# n+ x109 b/ \, K0 ~1 m6 W X5 I V/ Y$ B
11& Z2 b! O, {8 W6 v- b+ K
12
9 x' T, s! p- z A z$ B13
, _4 [2 L: V0 C' V% I14
* F. ?; p. o; r& g0 \* M) O15
/ d8 Z! X! s. d0 n. o167 v$ ^1 T! l% f4 f
17" V/ l3 O5 q, E/ ?2 X |5 u: \
18% L6 q. Z& n: v! e# S
19
0 ]5 R" u7 ~) d" G; T20
1 ~! H* i- Z4 l: ?2 t第二类判断操作主要用于测试是否为月/季/年的第一天或者最后一天:
& x' G, |7 t! u. e ]$ vs.dt.is_year_start # 还可选 is_quarter/month_start
4 }/ c- a7 [6 U5 dOut[40]:
. V0 R: f5 M' n0 True
. Y( A& m" M+ T2 t1 False5 e2 p3 s) S' f
2 False
: ~, e' q1 N- a- ]dtype: bool( R5 D! H: g D0 D% I8 K
' y0 D: y4 `# C: d
s.dt.is_year_end # 还可选 is_quarter/month_end
f+ G# z' G6 J& G/ F W) WOut[41]:
' I8 [, y4 q5 L& F. Y) w o0 False
5 G5 N" f. q' e1 R1 False
' L# K; k4 L% B" R- D& X6 G2 False
) A5 O# T0 `- g" g! `) Y1 R7 j: Z0 ydtype: bool5 h- f. w7 a+ @8 \# K# f6 S( R
1
+ g9 a- ?3 I! U2% g0 v" \6 \0 q
3
4 q) }6 c5 U- g; }41 g0 n& Y4 Y/ V) y9 X& z
5& u: x9 S' ?1 C/ o' `5 k
6& }9 H, C. f1 n0 I! P" f1 F
7+ S& b2 r4 n/ F) T7 o
8
# H: F% U1 Y: i# [9) j; @3 ]" K' F' |+ j- _0 v
10
& h+ I/ U' ~# {+ [9 o% x6 Y1 U9 [ D11
; |, `( g6 x2 l12, \! T( t- b' h
131 W5 o F9 A# G, s$ V6 h
第三类的取整操作包含round, ceil, floor,它们的公共参数为freq,常用的包括H, min, S(小时、分钟、秒),所有可选的freq可参考此处。
$ Y% d1 [3 V6 o( Ns = pd.Series(pd.date_range('2020-1-1 20:35:00',; w2 y T! j: ~6 K% l
'2020-1-1 22:35:00',+ `! M: b) R% @2 s( D# P; g6 r6 x
freq='45min'))1 Y3 S' C2 [- ?1 c% V# L: d) x9 D
* G- d1 [8 k0 j1 {4 L* K7 s
- B6 t1 g0 M( W/ `' cs
/ z9 O U" D7 r. M$ FOut[43]:
# f4 f2 p7 v2 F" @8 p0 2020-01-01 20:35:00' }* T& u. B F1 u& @
1 2020-01-01 21:20:00
3 z' h/ E ^8 T* @- A7 {& y& [2 2020-01-01 22:05:00
# H- k$ o% {: Y" F# C7 I% Qdtype: datetime64[ns]6 k) L; W$ b8 h- L" E' E
4 K/ Y7 G% F5 f! Vs.dt.round('1H')
1 ^2 R5 Q9 t# w7 ?Out[44]:
% V* d# b, l( T; {1 e0 2020-01-01 21:00:00% x# \! ]$ V5 e; A i4 F; K
1 2020-01-01 21:00:006 n' n# V6 R9 N1 d) H: y* U/ R& ?
2 2020-01-01 22:00:009 f: a( [. Z4 A& @1 R7 A
dtype: datetime64[ns]
, L; m0 U, L9 O2 q. G! @& e
, l/ j6 i0 W; as.dt.ceil('1H')3 I4 h& s8 [0 x( r5 _+ F5 c- |8 `
Out[45]: & r) W3 T; z: |' U
0 2020-01-01 21:00:00
% ~6 b1 C. b7 l) f' b1 2020-01-01 22:00:00. w* N/ ^9 B0 i3 D+ G
2 2020-01-01 23:00:006 v& c" @, s" A. @* C1 [* y( G
dtype: datetime64[ns]
& w6 _" B9 D' r" t# c+ o7 D
9 _; |. Y% Z/ Ns.dt.floor('1H')& ?# N. s* B! g7 R
Out[46]:
* Z5 W X/ u' \# K1 Q$ R0 2020-01-01 20:00:00
' l2 m" v/ k5 l8 g) |/ A1 2020-01-01 21:00:003 j. h7 _$ T3 f3 p- ?2 D2 C! Y- ^* g5 [
2 2020-01-01 22:00:00% L- c& @* I- E% c* K) P4 w; s! J
dtype: datetime64[ns]2 d8 b% ^3 G, O- ]( ?# b
; f5 ?- ~$ f! a5 h
1
# [' d* }: T) X4 ]! C2 M2
: j9 r; O$ P# _ T3
: u& N, I& G2 V4# |, O0 c+ i2 y4 I9 y. R- [0 U1 d
5
% M- w! N; h. s% j. ~9 i6
: }2 P7 G+ Z$ b; i. U7
. ]8 P4 T- p) j: B) f( z8: s* t# h1 P; A2 S0 f& R& K
9
- C: a; d2 I. {6 O3 P4 L2 J10+ P) @7 y D5 s& Z# P2 k
11- f, x8 @; R8 K8 W, A" I% J2 X
12
7 d& Y* I: x# V9 C13& k( |3 e. U( c A! }
14- m4 B i( o/ s/ c, P+ t$ ~
15
5 ~# G% B9 b: U2 _$ g16
: i- h1 y3 A: @17
) {) p5 c' N3 f18# N' I* e5 ~4 N d7 s/ M; X/ c0 q, K
19
8 W( V% e \9 y h; B5 ]20
) g) U( F7 o3 F$ \0 h5 C. E21/ H# w! k& ]9 |1 t8 b% C1 |5 b
222 d" O/ ~" F. |+ j8 X$ O6 n2 g
232 f5 X0 B# e. D& v, ]1 ]7 I2 m
24
- S5 {+ N7 _2 I: ?- g: h25
2 L9 ~; C- ?- I! K* i, u; P26
" H q) i. @) k7 X27
' G5 c& V9 `# N" G4 d28/ U3 R7 n8 X" H% s
293 a: A* V- s1 D
30
8 ^+ @, [: E4 [# ]0 u) u$ U& j31
" o8 }9 l) L* e* _' ^. {4 T% L. G32' Y% b; N- `9 n
10.2.4 时间戳的切片与索引; B2 A! W Z, u; Y( _
一般而言,时间戳序列作为索引使用。如果想要选出某个子时间戳序列,有两种方法:
. Y6 @) f: L7 M- X: U9 O. }( q5 K: k" o) C
利用dt对象和布尔条件联合使用' W9 s% Q4 |2 F
利用切片,后者常用于连续时间戳。6 C G5 T3 T1 y1 R/ G
s = pd.Series(np.random.randint(2,size=366), index=pd.date_range('2020-01-01','2020-12-31'))
5 T8 w/ w: g" ~idx = pd.Series(s.index).dt: I0 E9 @$ E. n k5 J
s.head()
# N i! `8 R e- l# G2 s1 j2 N. @( G1 I$ L3 m+ G
2020-01-01 0- k' o1 `5 [: Q$ O2 i9 x9 Z
2020-01-02 1/ d( Y3 ^# e' `0 T0 H1 d
2020-01-03 1
- L0 w3 Z9 W0 w2020-01-04 05 f6 y8 f2 W; X+ z3 J
2020-01-05 00 v) Y/ ?+ |( S4 F' v; v
Freq: D, dtype: int32
) ~- D( V- G0 Z5 j* F) S1
* _7 | {+ \' I8 R2 \9 l7 k28 x. z, J; b# a* @- y3 Y9 W7 S
34 U' ^( V9 {' L
4
! [# Q9 ^. B6 p# P6 a- z! Q- F5
) X: C0 H' S' D# H% g1 D6! z' Z$ ?8 B! m
7
, Z; J6 F ~& d- [7 d+ H8
+ l: f. H- s- p* y3 L+ ?% k+ ~# L4 B9
8 _2 K l% ]" c# P" W( w4 W$ t! F10
- @( @3 B+ {2 y p1 K- Q0 eExample1:每月的第一天或者最后一天
: \. T. u3 H2 C7 |# M2 y* q! W) x& R' t$ V
s[(idx.is_month_start|idx.is_month_end).values].head() # 必须要写.values
3 I: }+ X; h4 d) ~0 a" S7 @# A2 SOut[50]:
% Y) A- U; r8 M* F: [2020-01-01 1
( R/ O( [6 z6 i& Z% }) W3 |1 z2020-01-31 0$ J, D$ ^$ O/ A) M
2020-02-01 1" y$ w4 B. ~4 [( S7 k4 A* i- W
2020-02-29 1' P f U$ E: r6 i) k1 r/ d
2020-03-01 08 ~% `" L1 K9 {
dtype: int32
" J! l& k9 O8 c+ ?+ g% n# g1 X$ @; v- m' \4 m
2
7 i+ j# @: \0 \9 S3
( r- i- K0 G2 s( r4 e' P9 I l1 \& F2 G5 ?/ L, r6 A
5
: D& J* ?3 {2 H* p1 q& h! k" z69 a$ {/ W. l0 H
7. ]7 d% r7 V: A. x0 b* m, z$ u
82 f P+ t5 ~: n( ]) S
Example2:双休日
0 s1 I' {8 k9 Q; o9 d6 J4 E' p6 _+ E" J5 q5 N E
s[idx.dayofweek.isin([5,6]).values].head()
/ A/ M1 m* Z: p! [( `" F$ yOut[51]: # S8 Z$ g. w& J' D& @2 Q
2020-01-04 1
/ n) t$ K& c% X4 z; W- T3 I2020-01-05 0. T0 S$ B# O4 C7 h$ i' J# u
2020-01-11 0( b. u' \2 W4 |6 f6 p
2020-01-12 1
6 x& ?' |8 S- I) L' [2020-01-18 1
* j1 s: L8 C# @2 I0 o) ~2 Cdtype: int32+ b8 N. S+ f, c& b
1( C2 {2 @+ q' n" D# ]# v! b' R' }
22 e5 F2 O! L, f- ^" t- U
36 h7 P5 H* ]* K$ j# U2 G7 d) i
40 u( [7 J0 P2 H( s* U) A
5
e: R ?% B3 U+ b/ u$ i+ N7 n6! V; x( |7 f8 J/ L1 m
70 @; g9 z- J/ R4 |" w
8
* p. M$ u4 b( y( Z5 | [- eExample3:取出单日值
8 H% w' p; x. n& K
# p7 ^6 W, R! @0 Es['2020-01-01']
( `, d! _9 p$ L( B$ [7 A! WOut[52]: 1
: M( c; W) E2 ]( q" W. F. k# {$ T6 U% c4 N5 T' b
s['20200101'] # 自动转换标准格式1 l& R" I _+ ^
Out[53]: 1
# P4 Q" \4 G8 S- T+ e1: A# x9 l7 r, }5 x N/ `& z
2
7 h7 i& x6 [# A* W. f35 J/ n3 O* E, O$ s% L _* F
4. A# _ M) b- n
5
. P2 d6 g" h* XExample4:取出七月
i. @1 |' K+ W4 R, [" C( r+ L7 N" G& G- c
s['2020-07'].head()+ U6 ~, P7 K# G+ B* i
Out[54]:
+ r# W! S( F4 o( X) y0 l2020-07-01 06 g7 _* t. u, P/ b7 O
2020-07-02 1
2 K B9 o0 [( _4 R) o2 B3 K2020-07-03 01 r0 C/ A9 q' M& K
2020-07-04 0
- g3 X, a# y9 b) ~, I2020-07-05 0
& e H6 N% t. J7 E% I) |. {7 dFreq: D, dtype: int32
. K: H; E8 n1 R# B+ n5 m1 {" R1
. S% A7 U0 C6 g. i/ R23 \/ X+ t! v8 {# Q
3
4 u9 P, E# R1 x+ g. f) n' a41 C% u+ t, U7 ^, e1 Q @" D& B- ^
54 B: i1 H# b9 I7 W! n
6
% J& a# @( s1 Y; Z N: a7 C73 U ]3 {! d: j6 X, l% Q) E `
8
5 w5 _1 I) A/ W7 q/ E# t$ F" XExample5:取出5月初至7月15日
8 q& P$ M# t) Y4 i( @" ^+ J( _3 C+ y0 B# h- o! s, f* B
s['2020-05':'2020-7-15'].head()
! x% X6 V( [1 R/ y+ T; _Out[55]: 7 \& U" h! N% s# K7 v7 W8 @
2020-05-01 0& }$ O( O- o% w, A1 d: m
2020-05-02 1
+ B4 \4 I/ [ R# s$ _2020-05-03 0& k- m; H6 o# z( p
2020-05-04 1
8 X, u1 R3 C3 H w: Z* s2020-05-05 1
; n) S" I& e l- kFreq: D, dtype: int32
2 k3 L$ }0 p) u; e' M R d7 D1 ]1 }3 w% o I; @
s['2020-05':'2020-7-15'].tail()5 t7 u7 ]: G: q7 @) ~0 ], Z
Out[56]:
2 A8 }0 b( f$ J) C8 ]) c2020-07-11 0; J& O# d" K/ O! N Z" k
2020-07-12 08 Q; u6 U$ E! R0 l- f8 W
2020-07-13 1
: o: b# m) z9 H1 y. ]3 J* j1 P2020-07-14 00 N" y7 W3 N5 t7 ]" |
2020-07-15 1
2 g1 Y5 O( y6 z( b0 j9 U$ mFreq: D, dtype: int32& i( \/ X# v8 m) b) W" f$ ~6 H
3 t P9 z4 b7 E6 ]$ ?, B1 L1$ Z2 Y+ v& o2 \7 `; M$ n* F+ d
2
" E) q5 E( L3 m1 Z3
1 j6 q s/ A- d5 x$ T0 m" v0 f41 x3 n) p1 ?$ E
5
$ O! ] {1 Y6 v0 M+ z6, T( t% \: \) L/ G. o8 @4 F
7
2 ?/ o4 n% a- P8
' }# @# G! J, |4 P/ x3 Y9
- F7 x w* @+ v! ^9 x$ P- s10
) _0 g, `3 H" i" N5 F2 y11% a0 \7 x8 W$ A' z4 a6 `9 f! m6 s
126 K" y/ x3 C( a
13( e |* k0 q7 s) {' A
14
7 j+ `2 n C. w/ L$ S7 J+ Q) G8 ~0 ~( h15
6 N! N0 i6 o3 p( I9 d' G' @& C* M% f16) t9 u; D" W! _7 l3 F8 R' N3 C: J2 j
170 ^' n$ s. h8 C* H; C7 k# k3 B
10.3 时间差0 e- [2 H1 s: H" u
10.3.1 Timedelta的生成
! X+ X9 v% ?. @pandas.Timedelta(value=<object object>, unit=None, **kwargs)8 j9 L& Q8 l5 w! H
unit:字符串格式,默认 ‘ns’。如果输入是整数,则表示输入的单位。4 T' u4 p+ h, F+ Z& V
可能的值有:" b/ o0 T2 c, N' E; t
) A5 u/ D5 j9 [) I% K4 ~' i
‘W’, ‘D’, ‘T’, ‘S’, ‘L’, ‘U’, or ‘N’4 Z* R# J$ Y% Z
‘days’ or ‘day’% Z, ?2 j6 o& g! X4 V! \
‘hours’, ‘hour’, ‘hr’, or ‘h’
& {' X7 I4 w6 X3 N# A3 K‘minutes’, ‘minute’, ‘min’, or ‘m’
# S2 X0 N/ ?1 y, V# |‘seconds’, ‘second’, or ‘sec’+ L1 J1 v' s# \" i$ s
毫秒‘milliseconds’, ‘millisecond’, ‘millis’, or ‘milli’
: u# e) {# ?1 O/ Y微秒‘microseconds’, ‘microsecond’, ‘micros’, or ‘micro’3 |, ^ o% b- S( a% ]# ]. g5 n
纳秒 ‘nanoseconds’, ‘nanosecond’, ‘nanos’, ‘nano’, or ‘ns’.
: L: k8 e- Z! w3 v时间差可以理解为两个时间戳的差,可以通过pd.Timedelta来构造:( k6 o* p: q ~9 C1 E+ p' f8 W
pd.Timestamp('20200102 08:00:00')-pd.Timestamp('20200101 07:35:00')
! ], P4 n4 H3 U+ J) b8 P8 }# eOut[57]: Timedelta('1 days 00:25:00')
! X5 G7 _# V1 o$ L8 `2 O, a: ]" X) f* b
pd.Timedelta(days=1, minutes=25) # 需要注意加s
8 B+ [9 x4 _6 w$ f$ KOut[58]: Timedelta('1 days 00:25:00')5 C# A: x9 Z% ~: A* M
# G' Y7 [6 N7 g( r6 Spd.Timedelta('1 days 25 minutes') # 字符串生成. |3 V Z/ O2 L# \) H4 P
Out[59]: Timedelta('1 days 00:25:00')9 ^; Q! S" @5 D5 x9 A+ B
8 b3 I. D: u( A3 ^+ Apd.Timedelta(1, "d")* m2 n7 J9 P* O7 ]
Out[58]: Timedelta('1 days 00:00:00')
& |5 G5 l( H6 r7 I0 Y$ L4 o1; {9 c# |6 B- n4 c- ]! {
29 D5 }4 \: O1 d/ X9 _( m
31 u' [ b- i9 S/ H( S4 R, E: ^
4
! U( G1 [( C/ k9 v, x- y2 X0 c9 r c5; o6 d( d) y7 Q+ O. [; j, r
6
5 T- a' q# D* I( R i( `5 ~/ B7 V5 N7. \% i/ E/ n, B" H% U) K. e
8
; f( r: P+ P9 p* L& G4 E9
. e8 g( \# w. Z$ E5 m( {100 }: T5 h, U: d0 e* H
11
2 `! N0 k9 `$ a生成时间差序列的主要方式是 pd.to_timedelta ,其类型为 timedelta64[ns] :
6 x( _1 t0 @7 G& [/ X8 p2 u) S8 W3 gs = pd.to_timedelta(df.Time_Record)9 S) g! m2 Q; C- w/ d1 N& ^
$ i" G$ M7 m9 I9 b. l+ Y8 ds.head()2 {. s2 \4 o% M: w5 ~
Out[61]:
3 n$ g6 {3 G& C% @: J0 0 days 00:04:34. W' w. j7 e2 \3 P4 e! a
1 0 days 00:04:20' v3 T6 y- ^ `; U c) z! t+ R
2 0 days 00:05:22/ u: g- X) e3 P: ?4 V' V" E) p
3 0 days 00:04:08
: V5 d0 L1 ^: u; p1 R4 0 days 00:05:22! E. N1 A. D. T5 ~" z4 l: h
Name: Time_Record, dtype: timedelta64[ns]
6 ~- s- j" L+ ?* K, X1
0 l/ l0 b, m6 b) x0 i25 [4 {( L2 c# m& C
3
7 m8 V: k. _; y0 K" \2 I" E# d4
4 Q+ r$ p1 _" ^' b6 p57 }& V; Y7 K0 D. a9 m9 D& J# R
68 \& U; H- \2 ~: V
7# H* A- I, z; P/ T/ _1 p6 c
8
/ `0 A8 }8 o& }4 V7 |9* f" F$ z* X) t% M& Y8 P5 R, O$ n
109 |1 ~% a! \9 Z
与date_range一样,时间差序列也可以用timedelta_range来生成,它们两者具有一致的参数:
+ P! N5 c% e. `& Vpd.timedelta_range('0s', '1000s', freq='6min')9 v$ A5 F6 H. s q8 ] J$ q6 D5 v
Out[62]: TimedeltaIndex(['0 days 00:00:00', '0 days 00:06:00', '0 days 00:12:00'], dtype='timedelta64[ns]', freq='6T')
5 [+ C. h Y4 p/ H8 C9 {( R$ `/ C [* u# O- k2 G- L! D5 f3 E# S
pd.timedelta_range('0s', '1000s', periods=3)
0 v3 f) A7 y7 s; B& LOut[63]: TimedeltaIndex(['0 days 00:00:00', '0 days 00:08:20', '0 days 00:16:40'], dtype='timedelta64[ns]', freq=None)
% Y S# ^! J/ q. X: ^: W7 D# ]1/ @2 T ^( H& F! _
2
6 D6 E; `5 Z( R( o3
% v# n2 s. x1 Q4
2 \6 A& r6 g2 ?& m( ] H$ e% s50 G \# L; Z/ \- Z) a' P$ P
对于Timedelta序列,同样也定义了dt对象,上面主要定义了的属性包括days, seconds, mircroseconds(毫秒), nanoseconds(纳秒),它们分别返回了对应的时间差特征。需要注意的是,这里的seconds不是指单纯的秒,而是对天数取余后剩余的秒数:2 m* i* k/ t2 }# M# N
s.dt.seconds.head()' h* P* Y. r& a8 f9 Y4 B1 G4 n
Out[64]:
% r) [! m* Y8 K% P$ L7 ]. \$ E0 274
% f j, t% o$ @# O: I1 260
5 D. f+ @# c7 R' @2 322
% U, C: l5 _ s& t, F/ A4 I3 248+ S: Q# X \. {4 ]* x7 q4 i
4 322
`2 X$ ~+ M! O- Q pName: Time_Record, dtype: int64. J) `, ?0 Z# K {) v* V" ]% ~
1
# U3 G |9 f5 p8 [6 P2 z21 ?4 Z2 Y8 ]2 i6 s% v- h4 Z- D
3
6 A3 B6 V5 Z- Z: m( Z: T5 H+ v4
2 Z) F0 l' c4 T+ S9 x0 e5
$ p; _2 t. x0 q6
* @" R& S: z; y6 }, d7
; t4 R1 E* L- _4 \85 L) m7 p+ H' [) e9 T n
如果不想对天数取余而直接对应秒数,可以使用total_seconds# _( j' T% t5 a
- A% ]: g7 f1 J' \4 f3 T; ds.dt.total_seconds().head()
1 K! n, ^; Y( yOut[65]: 2 l6 X; ]: f# }& B
0 274.0. K% a, _/ V z3 E
1 260.0
& K/ v5 ?7 F v+ ~6 y2 322.0
9 o, ~3 n9 o2 q1 Y7 G. t) ^6 Z3 248.0
4 z' h4 q9 H( B* t5 m5 q4 322.0
, h- U5 B& m, F1 KName: Time_Record, dtype: float64
, ~3 u* L4 Q+ l! M% l1
( u% `/ T4 ? Z7 T" O$ u2' G4 T P* ?+ z" P1 z
3
5 ~2 e: {7 n n5 [) p4
+ j" r% m! j, q5- T& x) @; D5 Y1 {6 k
6
' W3 b0 h6 t @) j7 b* ?+ y& \7/ b1 b3 |! }" P* D3 s8 @. J: m" }
8
; _3 d9 h# s6 ?; Q2 ?2 w9 H与时间戳序列类似,取整函数也是可以在dt对象上使用的:
3 p C& R4 ~ \
0 Y R n. H Mpd.to_timedelta(df.Time_Record).dt.round('min').head()
% P- l6 f Z* @; v+ F2 E0 XOut[66]:
! \2 L" J$ g9 l* s/ m E; g0 0 days 00:05:00
" E7 t- H: L! a# L+ \1 0 days 00:04:00
3 m: _1 q5 o! A8 L: W* F0 _2 0 days 00:05:00
! p, B' K9 K9 h4 F( \) m, @- |3 0 days 00:04:003 a9 Y- |; D. @$ ]1 M( M% W" T' I9 u
4 0 days 00:05:00. a! n' W% q; l$ M8 h2 @
Name: Time_Record, dtype: timedelta64[ns]
/ j9 t) m% K8 f1 b8 s# a- Q0 j( D1
& k9 y* l. T& F2
! F$ C0 L4 n- M3
, a7 A& H: `% ?: w4
! ] i4 g h: U. `5 ^5
; D6 d* {" g$ o1 R6 ^% w6
8 F, Y- |& P# q# ^" R0 z7( R3 \6 I& D1 B$ {7 m* G0 g
8! h4 y; a3 [& Q+ j6 e1 w
10.2.2 Timedelta的运算5 k+ i2 F& C& @3 M( s
单个时间差的常用运算,有三类:与标量的乘法运算、与时间戳的加减法运算、与时间差的加减法与除法运算:
+ P# i L" {+ S H* wtd1 = pd.Timedelta(days=1)3 K! L7 g, `; s* d: Q
td2 = pd.Timedelta(days=3)
5 N. x* \) M Y+ o! \ts = pd.Timestamp('20200101')
$ r6 P! y* I8 ]: }5 C
4 z' F* ~7 Y \& B% J# gtd1 * 2
5 V) a' q# c% ]& S5 [- oOut[70]: Timedelta('2 days 00:00:00')
, A7 B8 O$ ^$ n9 S6 ^' t: v" Y. [- h* X. K) p0 T8 l
td2 - td11 d4 k( d: Y# _
Out[71]: Timedelta('2 days 00:00:00')% Y5 k8 [3 f/ O
* ^: I* H! @ C1 w9 d' Z" K
ts + td1& ]1 u7 ^; s" X( U3 h
Out[72]: Timestamp('2020-01-02 00:00:00')
. I4 N& _ F: G7 S, B
; S. a" n# r, r* Bts - td1, ~" S' c5 I% Q$ I: n; s! b
Out[73]: Timestamp('2019-12-31 00:00:00')
$ m5 E* i. a, Q& Y4 x19 @ X/ c: l3 @- G+ D
2+ l; F) F& v! z( v; h) q
3$ C& |5 G0 e, V3 k) r
49 {: n, _! _% w* N
5
- ^5 H1 u& A. u6 T, U62 A% P3 r( ]+ ^& E
7
6 @; v( ?/ t C0 ? X, q, A0 S$ K8
" R4 V' {5 x% H" N/ B8 L/ A6 {9: m0 @, J/ i) \ q2 \' |
100 o3 Z2 i! W) y3 Q( ]% M8 f- b
11
# B* A8 C8 H" X7 B2 n12
1 K) D' p: F" ^' l5 `! T13+ j; U$ s9 x/ K. y4 q: K
14* p* E; j1 B$ [
15
% r- X9 d' J0 | e时间差的序列的运算,和上面方法相同:$ p- m$ L) Z; m( {8 \$ U
td1 = pd.timedelta_range(start='1 days', periods=5)5 g% b8 k& O& @! z6 L) P! b h
td2 = pd.timedelta_range(start='12 hours',3 h$ _7 s. H8 F
freq='2H',
# T( i3 P- o2 [# N periods=5) D4 Y5 \+ v) O5 U; b: \% Q
ts = pd.date_range('20200101', '20200105')
; ]6 {4 B8 J5 C/ V3 Ltd1,td2,ts
) t6 N4 O0 t u+ I* ^8 _6 a
1 U+ W4 A/ v# H0 @9 s1 WTimedeltaIndex(['1 days', '2 days', '3 days', '4 days', '5 days'], dtype='timedelta64[ns]', freq='D')
2 H8 z0 {* V# _9 i5 H) JTimedeltaIndex(['0 days 12:00:00', '0 days 14:00:00', '0 days 16:00:00',
! P" ^1 y' w" C) r, G6 ? '0 days 18:00:00', '0 days 20:00:00'], dtype='timedelta64[ns]', freq='2H')
5 S/ f" G0 M: X3 D' nDatetimeIndex(['2020-01-01', '2020-01-02', '2020-01-03', '2020-01-04',+ T# z$ l+ U7 l$ ]; N( V$ |
'2020-01-05'],* U' C8 [) i6 q
dtype='datetime64[ns]', freq='D')
4 `4 w/ `( E' u1
5 v9 q7 N) ]9 O" V' M2
( z" B2 C8 d3 m' p7 l3
' ~1 b2 v4 d, W4$ p3 C0 N* @9 q; A/ Y" U, k9 w
5
- g0 U2 v [4 |6 @6
A( W% t( j; X/ I; v7
9 I6 I0 F/ H [7 [3 Z- R3 Y8
9 F- N$ q* e: Y; ~92 j$ e# L( N# S! m0 l
10# Z t! E1 Q( u1 W8 i
11' z5 [ F# o! s& `
12
) \' j; o2 O( ~0 t; x) Q! k: i- c13
2 _4 s$ Y) W. W! B. I2 ?8 v2 N# Xtd1 * 5
3 H0 o# h) D7 C5 q! ZOut[77]: TimedeltaIndex(['5 days', '10 days', '15 days', '20 days', '25 days'], dtype='timedelta64[ns]', freq='5D')! U% W- c+ l- k; |3 S2 j
; e5 d* i+ y& [. b9 ltd1 * pd.Series(list(range(5))) # 逐个相乘5 x9 B8 C' e( F# Z1 R
Out[78]: 3 ^/ r' u8 \, u0 ` [' T
0 0 days; F) v0 G$ _# e& A1 |
1 2 days1 j# h: ^' s& e( j
2 6 days
. y7 ]: b" t$ i: c3 12 days8 j1 ]9 r' e$ P9 Z: y/ i* x0 G: d
4 20 days! n. \- r4 x, q& e* p# t3 @
dtype: timedelta64[ns]5 U/ r0 U" ~ T( r0 u( a
, Y6 M6 ^: b6 c/ ktd1 - td2" d3 Y3 X1 K' T0 r f
Out[79]: / n4 c5 h) y, m, l9 M
TimedeltaIndex(['0 days 12:00:00', '1 days 10:00:00', '2 days 08:00:00',
* l& @; N2 u" I! m. H$ l '3 days 06:00:00', '4 days 04:00:00'], t- }: r+ ^0 h3 |3 m3 a
dtype='timedelta64[ns]', freq=None)
7 d. ~* y# ]; ^0 H9 ?! w8 _/ z( n5 E# e
td1 + pd.Timestamp('20200101')
0 W5 G! F' }* L' r, T G3 }$ {2 UOut[80]: / y1 X/ V: w) g. D) g
DatetimeIndex(['2020-01-02', '2020-01-03', '2020-01-04', '2020-01-05',! t1 f' X0 j, _6 i7 ^9 k s( Y- \
'2020-01-06'],dtype='datetime64[ns]', freq='D')
. ?7 Z3 D! ]0 L9 h2 J/ K F$ `
' Z9 G, F% ~! t' `7 Ftd1 + ts # 逐个相加
* F. w9 x0 X, m. gOut[81]:
. S& ^/ s! {. L: aDatetimeIndex(['2020-01-02', '2020-01-04', '2020-01-06', '2020-01-08',( ]4 F. A8 K4 w% f l# x
'2020-01-10'],
# W% k% n* f* k9 a+ m8 {7 Q; H dtype='datetime64[ns]', freq=None)8 f6 t" h& O, p
+ O9 E& g. A. n* w
1
& Z4 a2 u; ]8 K2 J: d2# |# G+ ]6 [8 S0 @
3# f+ K! ?/ i0 h; a
48 }3 v. h) S: P8 `
5% y" m* O5 x ?7 g
6- ^: @% `9 o: u7 E8 Y, [
75 j0 f1 C- e/ v0 g6 j
8
' D0 b( U- Q; p V3 U9- f) T( D% D- [4 ~' b
107 m; V$ y& r$ K7 i( p
11
2 z! m" c2 |* R: J1 Z0 d12
: X5 e0 W3 E/ y3 w3 _+ u0 p( r13
# r9 \8 X. `' B" }14( v; o2 _6 z& N9 o8 j- F
15; u5 V/ t/ v: o% f# Z; z
16# |( l1 O: T2 Y: e. @6 J
17
+ i& B4 Y, Q9 G. D5 [* Q181 `+ C9 f" A) z& i @
19
7 H v0 d7 `- y5 L! |- }203 u8 z% L% Q# C/ u1 j
21
* F; e2 w9 @* f/ o22
5 t1 y5 @; _" F1 a23
: b0 B6 r& Z% w" ~: B: X24
) G4 x6 {* z: `1 d2 V25
/ r8 d: n9 c! s( G26
' e1 V6 ?9 P1 H' H" e: s27. ~) q1 S& r% x/ k4 w" G
28/ L6 F" L2 u( s- D
10.4 日期偏置
' }1 O7 p- A$ f: {$ M$ N( B10.4.1 Offset对象/ ~+ E5 K$ p5 [
日期偏置是一种和日历相关的特殊时间差,例如回到第一节中的两个问题:如何求2020年9月第一个周一的日期,以及如何求2020年9月7日后的第30个工作日是哪一天。+ K# A6 i/ t8 W
1 h/ J8 v. p& I4 T# g& ?) n9 o
DateOffset 类有10个属性,假设s=pd.offsets.WeekOfMonth(week=0,weekday=0),则:: {% T1 K+ V. [9 I3 W+ U. m
9 y- g' D E" h# f& bs.base:<WeekOfMonth: week=0, weekday=0>,返回 n=1 且所有其他属性一样的副本
- t9 M8 K2 _& T/ F0 [( Fs.kwds:{‘week’: 0, ‘weekday’: 0}
7 k0 z8 u. B) P2 Os.wek/s.weekday:顾名思义
/ ^8 e4 ]; V* {7 j有14个方法,包括:
5 ]' w: x- P$ L; \" z- N1 ?, N3 ?5 W! Z" v: H& J6 a* H
DateOffset.is_month_start、DateOffset.is_month_end、DateOffset.is_quarter_start、DateOffset.is_quarter_end、DateOffset.is_year_start、DateOffset.is_year_end等等。
- ?+ S4 V# W* M+ C |+ i; X+ mpandas.tseries.offsets.WeekOfMonth(week,weekday):描述每月的日期,例如“每月第二周的星期二”。% S& ~) e2 G, ^0 [
' h2 ]- l/ [; p. `
有两个参数:4 V# J) Z6 R7 x1 K, S. T
week:整型,表示一个月的第几周。例如 0 是一个月的第 1 周,1 是第 2 周,以此类推。
; f6 X i q2 H% p1 w6 b! E8 Sweekday:整型,取值为[0,1,…6],表示周一到周日,默认取值为0(星期一): O1 _! P# X! B+ r# w
pandas.tseries.offsets.BusinessDay(n):相当于pd.offsets.BDay(n),DateOffset 子类,表示可能的 n 个工作日。
' {) n, [' F# j& i I) Q+ k" _
5 X1 _5 y! H N% S3 D. H0 bpd.Timestamp('20200831') + pd.offsets.WeekOfMonth(week=0,weekday=0)
) T$ r% p6 w7 y' f& l+ fOut[82]: Timestamp('2020-09-07 00:00:00')) S# I' e+ \1 _ I2 h
r8 J9 L9 z* S/ t% L- Epd.Timestamp('20200907') + pd.offsets.BDay(30)
9 `) C9 o$ y- X- P, Y2 `9 S, `6 a) dOut[83]: Timestamp('2020-10-19 00:00:00'), L7 u6 |+ F" d; _# Y
1
: ~, }2 T+ m( ]+ s5 s22 F( x. H% r& T4 ]5 K4 L- t# R
3% t/ f: ^& C K+ P2 m
42 S* K4 x- c6 ^% z: g- \/ V
5, s2 |- M& f2 x& ]5 D& z
从上面的例子中可以看到,Offset对象在pd.offsets中被定义。当使用+时获取离其最近的下一个日期,当使用-时获取离其最近的上一个日期:# I, q- `6 U" L' R+ W
9 q( ?# |6 x" U5 L& m
pd.Timestamp('20200831') - pd.offsets.WeekOfMonth(week=0,weekday=0)8 R X4 T& x3 r! D
Out[84]: Timestamp('2020-08-03 00:00:00')
% l0 n8 a' U* G7 H; r! X9 I
3 C# L; ?: T5 T5 |8 s) ?pd.Timestamp('20200907') - pd.offsets.BDay(30)7 `. L( v8 j: ?( {( V0 r
Out[85]: Timestamp('2020-07-27 00:00:00')# j" R1 r. T6 v8 Z# @9 G: p
6 G2 i$ L. f4 Q
pd.Timestamp('20200907') + pd.offsets.MonthEnd()6 j$ |7 g9 Y6 ^0 z( Y) s4 c' T! s$ f( ]6 D
Out[86]: Timestamp('2020-09-30 00:00:00')! |0 A4 V7 q; j4 ]' _
18 L6 g8 d/ V+ U& c7 A* p
2
/ ^( s* A8 n& ^* A4 y% @+ ~3
3 j4 O- r9 R$ F+ V5 f2 {/ C% G$ c4
- e9 c$ ^5 j: r5; I% c) f. `% R, T- G' d) v# {
6$ j+ _( @8 [# `
7
3 B/ T0 A( u$ g" v8
0 X; }8 J6 b( C# t/ |4 p 常用的日期偏置如下可以查阅这里的DateOffset 文档描述。在文档罗列的Offset中,需要介绍一个特殊的Offset对象CDay。CDay 或 CustomBusinessDay 类提供了一个参数化的 BusinessDay 类,可用于创建自定义的工作日日历,该日历说明当地假期和当地周末惯例。
& Q4 z9 H+ ]1 y2 K 其中的holidays, weekmask参数能够分别对自定义的日期和星期进行过滤,前者传入了需要过滤的日期列表,后者传入的是三个字母的星期缩写构成的星期字符串,其作用是只保留字符串中出现的星期: [* B$ H+ } R) e0 Q8 X) V7 o
4 ^& l2 g! ~) |8 _my_filter = pd.offsets.CDay(n=1,weekmask='Wed Fri',holidays=['20200109'])
6 _: K2 M* L: F( X2 @% pdr = pd.date_range('20200108', '20200111')
) @ Q# h# B) @3 i8 H6 d1 e' ]/ C( Z3 W. {8 d
dr.to_series().dt.dayofweek# s3 }8 j/ ]7 @, i, C6 R# M* i# m
Out[89]: & W7 v$ q+ p5 G: l+ m, V% s% Q* u
2020-01-08 2
& k2 U* F8 k' S& I' O$ n8 S+ M9 |2020-01-09 35 j E/ j1 s. P, V7 t8 f: N
2020-01-10 4
5 }2 }' n' C% r( I& m& Z5 P& k2020-01-11 59 w: P) h0 ], G$ S4 B
Freq: D, dtype: int640 F7 ~" `2 r+ `! @( c) ?. D
) d- S& W) [0 _1 k" s
[i + my_filter for i in dr]. _# I# W, o0 a% n, h
Out[90]: 4 w4 T, `$ U; r) L# s& t8 M
[Timestamp('2020-01-10 00:00:00'),! i& E) F* `5 _2 }0 w5 G
Timestamp('2020-01-10 00:00:00')," D& h4 `- \( M. y# [
Timestamp('2020-01-15 00:00:00'),, E* L2 I% E4 i
Timestamp('2020-01-15 00:00:00')]
6 \" z& [$ V$ e# W! V
& S1 _) N, v& v) U" U9 u4 Y18 E" P( K9 W; `; J
2% T) q1 ]% D" r: m1 |4 X
35 t4 K# B5 P0 R9 r! [2 N
4
! H+ v6 }: N8 ~; V5& Q! c" s6 a! O0 V3 @
6
% _! W( ]$ D5 t4 P( J7
1 y6 Y" r* {' T* L( n! `, @0 z1 M8, C4 k0 u! i+ |% `0 Q
9
% I# U0 w- F* A& V106 G; S+ h8 Z$ _! U- [% G
11
6 H6 t7 T+ m$ J12) B1 P X: h' E6 p$ n0 J7 a% }
13
$ m; n! }# s9 r1 b8 Y* U14
0 j; W% d! `; j: @15
/ C' ?0 p0 P. v0 d16
$ ]2 e: e7 Z+ z, u+ F170 g+ Y7 A8 D; u
上面的例子中,n表示增加一天CDay,dr中的第一天为20200108,但由于下一天20200109被排除了,并且20200110是合法的周五,因此转为20200110,其他后面的日期处理类似。
, N7 y) B+ i; [0 o$ z1 ~; @& ^) [$ m5 Y
【CAUTION】不要使用部分Offset
, n3 ^1 x1 k1 p在当前版本下由于一些 bug ,不要使用 Day 级别以下的 Offset 对象,比如 Hour, Second 等,请使用对应的 Timedelta 对象来代替。8 s; Q+ [9 @9 ?7 C. q
7 k |7 B- F* ~+ p* t2 R
10.4.2 偏置字符串
! c! x( }5 o/ e4 i! X5 ~ 前面提到了关于date_range的freq取值可用Offset对象,同时在pandas中几乎每一个Offset对象绑定了日期偏置字符串(frequencies strings/offset aliases),可以指定Offset对应的字符串来替代使用。下面举一些常见的例子。6 m7 y9 o7 w, C) i* U
+ b% {) r" S: x' ^ Offset aliases:pd.date_range函数中的freq参数,为常见时间序列频率提供了许多字符串别名。 也称为偏移别名Offset aliases。偏移别名列表点此参看(大概27个)。
; _1 w/ C3 I9 U2 h) u/ t. P# h! o! P7 i" l/ {9 B1 ]4 A: P
pd.date_range('20200101','20200331', freq='MS') # 月初, F: E' X9 h. h; M; j
Out[91]: DatetimeIndex(['2020-01-01', '2020-02-01', '2020-03-01'], dtype='datetime64[ns]', freq='MS')
7 l% r; s2 ^9 ^; }6 F8 B
7 h/ u3 L; h9 r& C7 hpd.date_range('20200101','20200331', freq='M') # 月末/ {9 V/ E" e) G) _' ^5 \6 E& V/ C
Out[92]: DatetimeIndex(['2020-01-31', '2020-02-29', '2020-03-31'], dtype='datetime64[ns]', freq='M')
7 ]- F3 J) w2 G9 f9 y
4 ?4 M8 M! Z! r& z+ X) u* cpd.date_range('20200101','20200110', freq='B') # 工作日
& G- O$ {- x. f, f5 `' j- `Out[93]:
h& n) P$ C& A$ ODatetimeIndex(['2020-01-01', '2020-01-02', '2020-01-03', '2020-01-06',& k; R$ b8 m9 J/ `$ d+ d5 f
'2020-01-07', '2020-01-08', '2020-01-09', '2020-01-10'],
) s3 U2 C; V0 A; G2 U& z dtype='datetime64[ns]', freq='B')
- ?1 O, ]$ G7 z7 |0 y& I" ^ t% A* T. C8 U+ T+ Y
pd.date_range('20200101','20200201', freq='W-MON') # 周一
# k l$ p8 e3 m3 t( u$ ~Out[94]: DatetimeIndex(['2020-01-06', '2020-01-13', '2020-01-20', '2020-01-27'], dtype='datetime64[ns]', freq='W-MON')( G" T- }" k9 p2 C6 o, K
- M; e4 p9 ]7 Z) ?
pd.date_range('20200101','20200201',' J2 A l0 {3 p. U0 E
freq='WOM-1MON') # 每月第一个周一
& V% E5 }" @: V* S* O: T+ T- f! z
Out[95]: DatetimeIndex(['2020-01-06'], dtype='datetime64[ns]', freq='WOM-1MON') p0 c' u# h2 Z8 W$ ^
* f0 r0 H6 ^3 D8 q2 h% p. R
11 y- i' ^ H1 Z
2
7 {. w7 ~4 G. e" y. H( L. q3
9 f% X. A& v8 e u& j+ y9 M4
7 q+ Z* a% K( Z+ S1 g0 a" X, U5
) b) v+ m3 m, ?63 |# t9 c8 Y3 C
76 A# l' X v! O# Q# x3 e" y, e
8
/ g4 N/ ~% G2 Z97 ]* |! ?* s: m5 _7 b. P
10
, l, v# d6 T* p) D8 @! S( G11
W5 N7 S, g; M9 d3 p12
3 m0 V" d' G6 Y o2 G3 d% [7 f139 D8 `' P0 }0 [' `0 W6 h
14
/ q! u1 X/ t% z5 |1 g+ j' P158 i0 z3 O$ @' a7 I5 J
16
8 X+ A) L L7 K; F2 x, O* ^( R9 f; q17
& F, m& l# c% A4 m18
+ q" o% c+ X6 U19& u: X1 Z5 f9 }" z! A. a- o
上面的这些字符串,等价于使用如下的 Offset 对象:% ]" S; C: q4 v9 L6 b4 h
$ O7 c' s& a6 I0 P# G( Zpd.date_range('20200101','20200331',
* F& d( Y+ O' [0 ^, L; L freq=pd.offsets.MonthBegin())
& R U7 ], o. y% P% X0 _& r: x7 a v2 D9 [
Out[96]: DatetimeIndex(['2020-01-01', '2020-02-01', '2020-03-01'], dtype='datetime64[ns]', freq='MS')/ E; I1 j+ H- u1 z9 P1 V
# J5 h% }2 @# }. N+ ]+ Y& e: i% ?pd.date_range('20200101','20200331',* _3 J4 u3 s+ T$ f5 s) y
freq=pd.offsets.MonthEnd())( _9 n% r" B, Q1 F# {8 |3 M
/ j$ A7 w! s& f- m& H+ {Out[97]: DatetimeIndex(['2020-01-31', '2020-02-29', '2020-03-31'], dtype='datetime64[ns]', freq='M')6 {- R" G4 t" w1 z, ^" W
" D- c- j: f: N
pd.date_range('20200101','20200110', freq=pd.offsets.BDay()); N; L9 |( B% P' J6 w1 T
Out[98]: 0 S( z' L, u( }4 J; |- \+ H9 F- w
DatetimeIndex(['2020-01-01', '2020-01-02', '2020-01-03', '2020-01-06',( U/ `: _1 \4 j! F! h7 l& i# t, u
'2020-01-07', '2020-01-08', '2020-01-09', '2020-01-10'],
6 X! \- s" T- j8 M6 x. ~! `/ V/ ` dtype='datetime64[ns]', freq='B')
. d: A: N5 z! p8 W8 A& A7 k
; ~# l$ |/ u. Z3 F% P) Npd.date_range('20200101','20200201'," p! T- n& s& [$ p
freq=pd.offsets.CDay(weekmask='Mon'))
9 k+ Z" @! c4 E9 Y. c8 Q. e+ ~0 H5 q: H+ W1 Y, H
Out[99]: DatetimeIndex(['2020-01-06', '2020-01-13', '2020-01-20', '2020-01-27'], dtype='datetime64[ns]', freq='C')
% `: Q5 k1 b- Z% X4 o! S& A. C' e" h" l* N7 P& B5 N% T; e# w* D
pd.date_range('20200101','20200201',' ]; L' C# j3 Z! r
freq=pd.offsets.WeekOfMonth(week=0,weekday=0))
8 M( X! W6 Z: Q1 \$ k0 w- i! m0 U" e5 ^ h$ S( R" y
Out[100]: DatetimeIndex(['2020-01-06'], dtype='datetime64[ns]', freq='WOM-1MON')
* R. h- F/ A8 _1 i1 N
" ?+ T% R# K% |" e0 f1
$ f+ Q* y0 H. a$ {; o2
+ h/ y- w$ j( G8 D8 P3
! A2 o! R0 Y" j3 Y3 \4
- a, C9 c/ H# v) T3 I5
6 @6 ?/ R3 w5 _( X* b63 m$ @' |8 k; @6 v
7* u! D4 l; M R5 ~3 v+ ^5 a) B+ U% Y
8 [, D- m0 ]; ~
9
$ O6 L$ H6 p# K2 K6 p( ?10
( M7 S( m% G- \3 w11; Z/ o5 i1 A" [3 Y$ C" ~$ h. ~
12, v- Y$ x' d: T/ f( t" t
13: ^( v+ b/ q1 N! S' r
14
9 Q* v) v# ] L- A6 J8 T15# \, v- c$ [9 c6 K3 ^ F
16& x/ x j! W- k7 ?4 j- `
17
/ Y0 r; Y X0 e4 j18# Y* n; E% T$ u4 v, e: Y" U. m
193 b& g+ w4 |% E; F
20
) Q E0 Y1 y3 R* k& Q+ R7 C. [21& ?9 x! B1 G1 [; K0 V7 @% s
22
2 |; P0 j8 M% a C( b- n/ _23
8 R4 m/ c" @2 ^0 B; }5 `& p( E24# Z7 i# ?$ w" @, d) l
25
/ V+ ]& ?( u' k; x# w. o【CAUTION】关于时区问题的说明
; ^% m8 ^4 z8 w* u. W 各类时间对象的开发,除了使用python内置的datetime模块,pandas还利用了dateutil模块,很大一部分是为了处理时区问题。总所周知,我国是没有夏令时调整时间一说的,但有些国家会有这种做法,导致了相对而言一天里可能会有23/24/25个小时,也就是relativedelta,这使得Offset对象和Timedelta对象有了对同一问题处理产生不同结果的现象,其中的规则也较为复杂,官方文档的写法存在部分描述错误,并且难以对描述做出统一修正,因为牵涉到了Offset相关的很多组件。因此,本教程完全不考虑时区处理,如果对时区处理的时间偏置有兴趣了解讨论,可以联系我或者参见这里的讨论。4 w! _9 k4 X# u' |6 E) y! [0 v
4 k- X: [; o u& C- ^. F* C1 `10.5、时序中的滑窗与分组; b' @3 k# j( k( x6 u s- b( s
10.5.1 滑动窗口1 \2 T" c7 P' Z% U& _2 T
所谓时序的滑窗函数,即把滑动窗口windows用freq关键词代替,下面给出一个具体的应用案例:在股票市场中有一个指标为BOLL指标,它由中轨线、上轨线、下轨线这三根线构成,具体的计算方法分别是N日均值线、N日均值加两倍N日标准差线、N日均值减两倍N日标准差线。利用rolling对象计算N=30的BOLL指标可以如下写出:! m1 s) R1 d! `8 W; G6 H
: h! i9 S1 B2 C" {import matplotlib.pyplot as plt) |1 V3 y; j8 H/ j. A% j) T4 @
idx = pd.date_range('20200101', '20201231', freq='B')
/ i& Q9 t4 M2 s* Hnp.random.seed(2020); w" x& e4 \) K; K1 f% z0 K2 [4 p
4 W" g! ^; w5 [, [" }data = np.random.randint(-1,2,len(idx)).cumsum() # 随机游动构造模拟序列,cumsum表示累加
1 u" a- A% u: o: N* cs = pd.Series(data,index=idx)& e0 \/ c6 K( _- h5 ]9 N
s.head()& L- I3 k2 w r* l) f+ j9 y
Out[106]: ! M2 M% k& S, e, @: H. |& m
2020-01-01 -1# J$ o+ r+ J/ i6 v% C
2020-01-02 -25 |6 l2 r& C. G; |: H9 X0 Q5 w& l3 k
2020-01-03 -16 w) Q/ h& s" D3 @; N/ l
2020-01-06 -1- P0 u& j- E4 \; q, G0 u
2020-01-07 -25 K- r4 ]# x: {
Freq: B, dtype: int32& s$ k/ C. U3 J- m3 z) U. W& A
r = s.rolling('30D')# rolling可以指定freq或者offset对象3 x- N+ q" J- S# ~0 G
( l! \0 s8 ?4 x$ d. E
plt.plot(s) # 蓝色线7 \% w" }* _7 C. a3 h
Out[108]: [<matplotlib.lines.Line2D at 0x2116d887eb0>]7 E4 a0 C7 ?( c" V
plt.title('BOLL LINES')
# F# o# t$ K! F/ R" yOut[109]: Text(0.5, 1.0, 'BOLL LINES')" g: O8 w8 q/ z& ?' v% r
: A( U: s/ V* \4 t: ~( E4 U4 d! A& ]% mplt.plot(r.mean()) #橙色线$ \" p- M% N: k v3 }' U2 o
Out[110]: [<matplotlib.lines.Line2D at 0x2116d8eeb80>]
5 h: ~2 A2 A: n. D& v: q: K
* {4 n- v9 F6 e1 G: @plt.plot(r.mean()+r.std()*2) # 绿色线1 n8 T0 D- y2 M% y s2 x% S
Out[111]: [<matplotlib.lines.Line2D at 0x2116d87efa0>], u1 o1 I7 T& y* \9 p1 b
9 K4 G, m6 _9 E! L+ Pplt.plot(r.mean()-r.std()*2) # 红色线
7 k% a6 e0 f( q% V/ i# b, POut[112]: [<matplotlib.lines.Line2D at 0x2116d90d2e0>]
# W, E6 T2 {' Z6 \% S
h- {6 x7 g9 |1 a9 d6 [: o+ s( ]1+ m) @7 ^" S. u, ?/ v7 l# }
2- m! w4 J2 m( b
3
$ q2 [1 U( k6 w4
/ w! u5 B+ b' u# r9 F! ^* Y5& K( Y& B+ J1 b& L! L3 \; C
6
L/ u1 T7 s( L7 o. ?6 p70 a6 j# E9 h0 m' G
8# k! i+ V; q# x" ?( Y( q
9$ l" e7 h3 `7 Q! ^( W
10
* @. m) f2 v1 r j$ R118 Y! |) r7 j) D
12
) F' e T) f, f: L13# Z; l. W0 L& W4 f, k3 t
14- K! f1 V6 [. u3 ~# s" r
15
$ L+ U* H" U6 p9 y- E7 x16
! w) R0 G, \/ Y b17& ~6 [ _' [* _5 i3 p" r5 z5 ?9 N
18: k5 L0 y/ Q( A1 V- a: C2 E: w% _4 w
19
' X5 z* _' a3 P( A7 N* i20
* q1 X6 p! \( H+ d215 b1 h$ t9 E" ^$ ^6 M. C7 @6 @# n
22
], e3 Q. o) v' r1 y9 C4 w23
, l& F! L, M9 ^/ j* H, J% ]6 e24 R" g! s0 M2 g5 l+ I% ?" F0 R3 m
25
+ k2 [" h# u5 F+ J4 s9 S4 L4 _26- v5 K7 n% X: H; l: l
279 c3 M, ?1 H' T6 c- v
28
5 Y ]$ b0 o2 N& c29" m0 @; H1 Z" c' b- B+ [4 R
4 Q9 q+ [" T" a# [ 这里需要注意的是,pandas没有实现非固定采样频率的时间序列滑窗,及此时无法通过传入freq字段来得到滑窗结果。例如统计近7个工作日的交易总额。此时可以通过传入多个函数的组合来实现此功能。
8 }& |1 l# N8 n 首先选出所有工作日,接着用普通滑窗进行7日滑窗加和,最后用reindex()恢复索引,对于双休日使用前一个工作日的结果进行填充。
W5 y- Z1 {2 u9 `/ H
3 `) @+ a% M! |1 Fselect_bday=s[~s.index.to_series().dt.dayofweek.isin([5,6])]! D# @3 P, H |- l( h9 `
bday_sum=select_bday.rolling(7,min_periods=1).sum()
2 n! D: J( R( v2 D& z! zresult=bday_sum.reindex().ffill()! H$ j) _' r/ u1 }! C' S
result, k# v" m* k# W/ A
) l6 y$ x+ A' K. W3 p: n; ]8 S8 D; }
2020-01-01 -1.05 j% d- X0 Q* {& g- H6 |$ v1 |
2020-01-02 -3.0
% N1 W8 C9 F' L/ G z4 |2020-01-03 -4.0
( K% |/ t8 o7 [$ s6 b2020-01-06 -5.0
3 X7 [, J3 q1 a5 J2020-01-07 -7.0
- u0 [* R" l/ u% w/ d% W1 a ... 4 H9 k1 Z+ U- x h% I. ]; K B
2020-12-25 136.0. n1 V. t! r* p# }# H+ W
2020-12-28 133.0
Y f( H6 x4 [2 E4 `4 ]- H7 |2020-12-29 131.0
( t9 S+ M* m- j4 `) L, h4 j. p% @2020-12-30 130.0( A3 m3 J+ Q }$ K
2020-12-31 128.09 S, P5 X @3 m' E+ W3 {
Freq: B, Length: 262, dtype: float64
: _) e2 E: U: `
8 V% X; i/ {9 u1) q, M9 u( q, D( t
27 i+ S2 P, z* v. d. Y# @% P# ?6 b# B
3
M# W2 A( B4 ^5 Y5 o3 l4( S. q, d$ k" g& U5 Z* X6 @+ w
5
# W& U8 A$ }5 f6
, C: B! Y. s8 i6 ?& p7& ~5 z3 I# _: `9 G9 R
8' D3 p5 ~! E5 X% W8 ?; Z# h
9
& b4 f. O; A2 ?1 {: J V. |10$ W9 o) |% f2 ]
115 \0 G$ @! e- h2 j9 h
12
& p: V0 J/ c' w( ]& d139 R9 x1 L1 Z/ e. v
148 l/ l* m5 a& G6 \1 e' D( U
15
u5 J. { L9 y, \. X16
- i. Z+ q6 y# W, s, L17* i* T# r) {5 z0 t- I
shift, diff, pct_change 是一组类滑窗函数,它们的公共参数为 periods=n ,默认为1,分别表示取向前第 n 个元素的值、与向前第 n 个元素做差(与 Numpy 中不同,后者表示 n 阶差分)、与向前第 n 个元素相比计算增长率。这里的 n 可以为负,表示反方向的类似操作。
% n+ V, n. J0 J# ?& q5 L4 Q6 i: e8 I
8 g4 k5 K. h" w$ ?5 O9 Q+ V 对于shift函数而言,作用在datetime64为索引(不是value)的序列上时,可以指定freq单位进行滑动:
0 g3 @( `) {4 P0 W8 v
- h& e/ R7 m! f, Zs.shift(freq='50D').head()' }' L- b# c5 G2 t: s1 P
Out[113]:
! B' }- p( m" G2 P' @8 K4 g1 x* ~2020-02-20 -1& d8 R0 _$ U9 h @
2020-02-21 -2
; }6 K) [; \2 H. r" N2020-02-22 -14 Z9 t. G/ t, R
2020-02-25 -13 h& h# O) X% P" P
2020-02-26 -2
# G& w3 N0 A- C# ldtype: int329 y/ l/ W; j, ~
1
7 i" s1 q g: Y2 ?. f% ^2
. a3 j& W6 a. Y7 {3
: E, n7 x2 O$ \0 H4
2 x% R' f% I% T0 K3 F' l5
. \* p0 Y' ~, k6
. i: L7 E3 D: R5 u7
2 c/ _. q% S5 ~3 g2 A( D82 c2 {) j) u s9 V$ I y: `
另外,datetime64[ns]的序列进行diff(前后做差)后就能够得到timedelta64[ns]的序列,这能够使用户方便地观察有序时间序列的间隔:
' z. w, d1 y* p
0 } l4 m- b& @* @* jmy_series = pd.Series(s.index)7 k/ }. g6 h: [* @
my_series.head()
* O% N3 B( S6 l+ {' vOut[115]: % e+ s. B( V7 Q8 x
0 2020-01-018 v2 {, a+ H! m
1 2020-01-02: |$ V6 {; e/ G: l- G3 z
2 2020-01-03
' P6 B* o5 G6 v4 ]1 F1 F3 2020-01-06
8 `) ^2 G$ E5 J2 }! V; n5 G4 2020-01-07
& @0 p0 L; i2 wdtype: datetime64[ns]
( F7 p7 C% W& f6 P$ h; B" u0 f% z: n+ E d2 g, h2 c; L
my_series.diff(1).head(). |! c1 x* [: H. Q+ Y. x' w
Out[116]:
/ s! Z6 S) `: w. n0 NaT
0 w, n% p. _, A) K- Q- b0 ^1 1 days! m9 U! ?) a2 ~: m% d
2 1 days0 c# t8 j- [( J4 O' u' [
3 3 days8 J; I9 m" Y. G9 I- g6 g: ?; {
4 1 days
2 j# `; d; P4 X( T4 Jdtype: timedelta64[ns]
, @& e8 _/ Q2 Y, o3 ?7 c! e, L: m( o8 U/ ?) @
15 K- O8 S2 m( K
2
5 p" ]3 S. l- x* A7 D3- \% N8 C" y5 a
4
: E1 \, v. A- E5 l- Q5
$ }' ^# W. \' R# F G( r% a6
7 @1 s0 n5 a+ \0 Z* b1 c, K7
( v% p! V- e- M; a5 L$ t1 B9 Z- |8
9 e8 ?8 u* X/ g5 v. j9
- ?9 K2 D6 E& ?" N- }10
0 q7 J2 V4 S4 F( [4 y$ d7 q11
$ I5 `" `# E0 {, h129 y! ]/ g: r: C% j4 b
137 C. O+ H, D; D8 n
14& h9 y z! }: ^ c2 F( H
15& H: p3 m3 W, T/ z$ M* G p8 v
16
0 r) k1 m! f/ H9 `. O' J3 q17
, @ f8 M) y/ w9 W0 L" v18
9 a; x6 M! d% F: I3 N- u5 x; S10.5.2 重采样
V. f$ {. e8 M1 T' [ 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)- ~' J! i8 O2 b
常用参数有:
$ ~( v' T E: s8 o. s% h: B1 Q" t" h+ j( t& F
rule:DateOffset, Timedelta or str类型。表示偏移量字符串或对象/ ? U3 }8 K: o7 b# _6 A
axis:{0 or ‘index’, 1 or ‘columns’}, default 0。使用哪个轴进行上采样或下采样6 W5 c5 t) L$ |% s
closed:{‘right’, ‘left’},默认None。表示bin 区间的哪一侧是闭合的。所有offsets的默认值为“left”,但“M”、“A”、“Q”、“BM”、“BA”、“BQ”和“W”均默认为“right”。
, ?# O! Z2 @$ Mlabel:{‘right’, ‘left’}, 默认 None。hich bin edge label to label bucket with,所有offsets的默认值为“left”,但“M”、“A”、“Q”、“BM”、“BA”、“BQ”和“W”均默认为“right”。( [- J i0 c' q+ V
convention{:‘start’, ‘end’, ‘s’, ‘e’}, default ‘start’。仅针对 PeriodIndex,控制是使用rule的开始还是结尾。
8 b1 V8 F, |9 _6 ]on:字符串类型,可选。对于 DataFrame,使用列而不是索引进行重采样。列必须类似于日期时间。
/ d3 R# t3 B/ D- Plevel:str 或 int,可选表示多重索引MultiIndex的级别,这个级别的索引必须类似于日期时间。" O4 O" y7 T z) s1 C! Z
origin参数有5种取值: ^+ n5 r, _% @# H2 o
‘epoch’:从 1970-01-01开始算起# `9 {4 E; B5 r+ Y t
‘start’:原点是时间序列的第一个值
* m- h" Y6 W) d‘start_day’:默认值,表示原点是时间序列第一天的午夜。7 r0 b% X+ q6 t \; n- d* _
'end':原点是时间序列的最后一个值(1.3.0版本才有)( z4 W, Q2 o- I3 H
‘end_day’:原点是序列最后一天的午夜(1.3.0版本才有)
4 M! f) y1 U' Z0 |3 foffset:Timedelta 或 str,默认为 None,表示对时间原点的偏移量,很有用。
% c8 z l9 U9 r closed和计算有关,label和显示有关,closed才有开闭。4 N3 T! x" M0 @1 A
label指这个区间值算出来了,索引放区间的左端点还是右端点,closed是指算的时候左端点或右端点是不是包含。* C0 Y# }1 Q k& p% u
9 a4 l7 @+ w# F ~
重采样对象resample和第四章中分组对象groupby的用法类似,resample是针对时间序列的分组计算而设计的分组对象。例如,对上面的序列计算每10天的均值:9 ?& t: s2 ^$ {6 z% Z
s.resample('10D').mean().head()
; S5 R( P$ I& q9 C( TOut[117]: * L: T, i! P5 y6 U7 X! y
2020-01-01 -2.000000
% M; U4 l: i% F0 \( q2020-01-11 -3.166667
7 v, y! m" y: G* x2 \, R2020-01-21 -3.625000
) X) L2 S$ S$ h; \' ] k2020-01-31 -4.0000002 p% G7 e/ v' w6 X4 ~& E3 H1 H
2020-02-10 -0.375000
8 w) ?" z" v* U* u2 JFreq: 10D, dtype: float64
) B9 A3 C4 A* p5 v- ~, @/ L1# z; N- P7 u% C6 C- i: u" s( ~
20 c. k5 R! Q& N Y( |3 {
3/ x* s S( l9 s5 [0 v
44 o; Q' Q7 f! \* X* i2 @
5, L' K" f7 y' X8 X- J# t
6
* y. T2 M5 s3 {9 I7
) |" o- E7 |" @. D5 L& _8
9 w4 D3 p, ^% \6 q) _' x可以通过apply方法自定义处理函数:3 v9 d8 h9 `- H4 J7 L5 K$ s& n& x
s.resample('10D').apply(lambda x:x.max()-x.min()).head() # 极差
. K9 p4 t6 i) t+ Q( h/ a) c4 |
3 c; D5 |+ Q# cOut[118]: 9 u, u W* N$ U. l8 I5 c. v2 x
2020-01-01 3" c0 p+ A* D; i8 f6 D. j
2020-01-11 4
' B+ M8 O$ t/ q2 y2020-01-21 41 E' ]1 o; t( L4 N7 ?
2020-01-31 2" H: J; t1 v4 }; `. y
2020-02-10 4
0 i( G% {1 r/ X7 VFreq: 10D, dtype: int32
3 p* x' w1 [. h( x6 K/ a0 |0 [2 p' h& P1
7 o6 Z6 w5 R0 S: d8 ~) H7 _) Z2
0 x& ^: `/ m4 Y9 ]3# f% h0 }" Z6 r* a8 ^: Q8 e
4
' N" k1 r3 D6 O; e5
5 z8 Q0 R! A9 r8 o' U/ }6
0 e0 R( `9 N6 V/ e0 U( n& ?* H9 _7
$ m; K4 j' v6 j. ?8
% ]7 a9 s @$ U5 d+ K% d, z98 q& `- v; C* E( O: x
在resample中要特别注意组边界值的处理情况,默认情况下起始值的计算方法是从最小值时间戳对应日期的午夜00:00:00开始增加freq,直到不超过该最小时间戳的最大时间戳,由此对应的时间戳为起始值,然后每次累加freq参数作为分割结点进行分组,区间情况为左闭右开。下面构造一个不均匀的例子:
+ u' u* E2 |2 ]2 j5 m/ o9 @
; c7 |; V" p- Fidx = pd.date_range('20200101 8:26:35', '20200101 9:31:58', freq='77s')4 M! G9 n: Y$ r5 v
data = np.random.randint(-1,2,len(idx)).cumsum()
. i: }' Q. C9 x4 L f }7 d& Ns = pd.Series(data,index=idx)2 A. \& n8 ~* j% ]
s.head()
* F; z* C ^8 A
1 J/ z9 `( E y0 iOut[122]: & F6 Y6 {1 F K. K
2020-01-01 08:26:35 -1. ?5 |& c" e Y
2020-01-01 08:27:52 -1
3 M1 q0 n2 I* H: r+ p9 J2020-01-01 08:29:09 -2# [9 b" p+ e, B$ W& K* {- x" E
2020-01-01 08:30:26 -3& o- A( _4 H; K+ u" f
2020-01-01 08:31:43 -40 {6 a, c7 s, G; v
Freq: 77S, dtype: int326 O4 {9 R" m0 @0 m, N7 m
13 G' y5 ^2 o& i/ h
2
% z w, w2 q% m- b! A6 G& O6 G3 f0 D5 b3' x$ h' d; N" b0 n# b$ A; x
4' D$ @2 Z, l% R% @+ i
5; \4 l3 a" O: l" P* V7 S- |9 W, J
6
) {- n" O! j4 M, d7
: U) ?7 f' k# _% |/ A8
% f9 Q$ A* {2 Z) f0 a4 R9
) n p z0 E+ J- M; t* P10
9 C! Y0 ?( X9 J5 n11
8 }1 c8 @8 h. q8 D$ @' r9 i120 v: J1 z" s9 J0 `+ I! ?
下面对应的第一个组起始值为08:24:00,其是从当天0点增加72个freq=7 min得到的,如果再增加一个freq则超出了序列的最小时间戳08:26:35:2 ^- u) p; u2 p
" i9 R7 f- {4 o: S* \s.resample('7min').mean().head()1 r: T" K1 h2 ]' ~2 s9 x" }
Out[123]: / s; v1 \8 q" t- U& S% V' D6 d$ b
2020-01-01 08:24:00 -1.750000 # 起始值,终点值包含最后一个值/ V+ t. ~$ \5 f5 Q S; e1 R* O
2020-01-01 08:31:00 -2.600000
0 Y6 w0 ?- b0 Y0 X7 ]+ E2020-01-01 08:38:00 -2.166667, T& N% N1 N" N% R) m+ I
2020-01-01 08:45:00 0.200000
% }: I4 T2 t1 R' `+ e2020-01-01 08:52:00 2.833333
& @7 Z- q- Q. t% uFreq: 7T, dtype: float649 F5 y" ?/ d3 E; g! Z3 p
1
: a8 r! H7 ?$ L2 J( i6 n: y, ?4 y4 H' d0 s& M1 w- M! h
3! L# M( d/ v ?3 l# Q0 p
4
3 x) g$ C4 d M9 X- y0 O" ?52 v2 X& `% J8 B
6 x9 s0 l- [7 @% N. t& w# S
7
) A5 _7 ]! A% ^1 ^% i8. z4 {$ r) m$ O" w; q
有时候,用户希望从序列的最小时间戳开始依次增加freq进行分组,此时可以指定origin参数为start:
( C' ~: K& }" w
; @0 ^3 ?! a* `8 B3 |s.resample('7min', origin='start').mean().head()
4 p2 r9 P/ D; K( yOut[124]:
+ [% z" j7 D- V, V3 o3 ]% @) Y2020-01-01 08:26:35 -2.333333
% d+ k- @: g& H. ~0 Q2020-01-01 08:33:35 -2.400000
9 }) ?, y, K3 A2 q2020-01-01 08:40:35 -1.333333
8 e r, {0 N: k% u2020-01-01 08:47:35 1.2000001 Y* n& \6 X! i9 j- @0 O" K6 Z
2020-01-01 08:54:35 3.1666673 z# Y) x( H+ I* F1 H% x6 _
Freq: 7T, dtype: float64
& \* F+ m: ?9 e& `# f. v! Z# P+ {0 R& D1
1 x8 h: J8 ]2 F5 H' s2) ^" W# r h/ `
30 m; |7 U+ j H# G
4
4 Y( [) L# }# n2 b4 W) D( R9 e5
$ \2 l, _/ l9 R0 y9 m1 S1 _. ^6
! i9 X8 t( `8 M1 @/ G/ ]7
2 M$ C) a2 m \7 Z% I, _8
) m; g2 n, y! B% r 在返回值中,要注意索引一般是取组的第一个时间戳,但M, A, Q, BM, BA, BQ, W这七个是取对应区间的最后一个时间戳。如果想要得到正常索引,用’MS’就行。
- r; ~* i& v4 F5 P* D/ R, T: C! h0 P* |$ C: i
s = pd.Series(np.random.randint(2,size=366), D$ Z+ h7 A& R+ `
index=pd.date_range('2020-01-01',
. g! ?7 T0 g& L# U# I/ X( [ '2020-12-31')) x3 {0 y, k6 {1 D6 f
$ b3 D* P* f$ m
* B! o( ]: ^# J( c) e2 z# X. P; n- zs.resample('M').mean().head()
' G6 ^- d* T8 \ J! c1 V# I" lOut[126]: & `+ O2 \/ y9 J
2020-01-31 0.451613
7 o% b7 I& l9 c7 X2020-02-29 0.448276
5 p; @ g* k+ i7 {2 Q2020-03-31 0.5161290 u) v$ P1 ?$ Y1 ~% Q% n6 R
2020-04-30 0.5666673 ?) T O# v) n+ D) o
2020-05-31 0.4516132 J# o: j h! |2 \3 k {
Freq: M, dtype: float64- o6 R7 d: U H% m% E- ^( u7 b
. n4 R8 H! l% k7 \( ps.resample('MS').mean().head() # 结果一样,但索引是跟正常一样; a, i) L' q& }: d4 ]' s
Out[127]: , H2 c/ x3 k, H) } `' M. V
2020-01-01 0.451613/ {+ G7 \+ h; s8 V- R
2020-02-01 0.4482763 z' \7 f+ ]2 e' l* u
2020-03-01 0.5161297 p+ U, n; l7 K* p
2020-04-01 0.566667/ T5 E: D$ Y( Y
2020-05-01 0.451613
: t0 j$ Q. `8 w) x' ]( [, B QFreq: MS, dtype: float646 ^7 {6 J0 j7 X' ^8 E' S& m0 N
4 s4 _+ N/ U9 `% h
1' k7 q* Q( C( `9 H
2' {% `" `( t2 i# U
3
& E2 J' `" `, T# c) q& }. A+ L5 S42 u7 \6 G- z: ?1 u [* G4 T, {
5
# A! ~1 S3 b, g0 e: Q6 ?2 \0 X! M5 c6
) B7 Z3 v- [* _6 W/ j& a7! ]( L' |% `; Y, E
8
0 Y/ c u% d" Z; S4 L7 n7 Z4 ~9
: v8 a; C" E6 A; Z10
. {. A/ H& t3 k$ j/ t114 ?5 D( ~& a( h9 p! g' ?3 k6 \; `; W
12$ e, C, f, X$ @8 ?! E- g- W+ X
138 N! e& X2 v" T
14
4 e& B" ~0 H( L; I; {' ?15
6 x( D4 w9 @+ K3 {$ J: O' i( r16
$ {1 f+ c3 |' m# K2 Y8 W7 b17
" A# H' b4 _7 s: x2 C Q1 W7 @18
1 f9 s# I3 s- e/ Z3 a Z19; v' [; @- O. B, o
20
8 F9 G/ t3 c4 f% B" R21
$ P% W3 D- Z! N22+ V# S4 g0 j; t# o
对于 DataFrame 对象,关键字 on 可用于指定列而不是索引以进行重采样:. D- o, \7 d0 [
d = {'price': [10, 11, 9, 13, 14, 18, 17, 19],
# }. n# g" X4 r 'volume': [50, 60, 40, 100, 50, 100, 40, 50]}
# L& i" n2 z" A5 [1 u1 Tdf = pd.DataFrame(d)
! L3 A* O w' i# R& B6 udf['week_starting'] = pd.date_range('01/01/2018',
! X& B+ r/ a; {/ g/ v" A N3 [; n periods=8, V8 R. g! n) y# t# K
freq='W')( t' H( m$ j; o' O; M! t
df
( F3 @" _7 _ X! p price volume week_starting4 J7 Z+ l( N4 U) v6 w. R5 g3 ~1 ]
0 10 50 2018-01-07
) c1 x# H; I5 I$ _1 11 60 2018-01-14
9 f) W w) C2 y g" D4 [2 9 40 2018-01-21
+ K9 O, E9 Q8 C5 I% z3 13 100 2018-01-28
# a) B. }3 q) j# o, f4 14 50 2018-02-04
3 y# K+ c# B, x% c5 l, {$ s5 18 100 2018-02-11: G3 N, E1 r" v" a; u# [
6 17 40 2018-02-18) i( m- u- o5 P! J
7 19 50 2018-02-25
( P$ l1 U3 \. j3 n( xdf.resample('M', on='week_starting').mean()
+ F& L# W2 D% K& w" o' f price volume
: N8 f: M4 F/ g2 V7 K. p: \" \week_starting
& w- R3 @( x) N& h5 m4 x2018-01-31 10.75 62.5/ c* P7 V9 r* Z% W# W' G
2018-02-28 17.00 60.0* g% G) \8 m9 s) j; w, d
* z8 n! L* h# r/ a6 ?19 @* s. ] e( ~
2
) V0 u( X* N8 d( Q H39 r9 T5 l3 E' l" r
4
7 i+ h% w0 W: ?1 L/ Q7 z, x) A# g5: [, M" D8 D3 P
6
7 N- u# i$ H6 ~. j0 l74 Z! `' A& Q3 i/ d* l# J
8
/ {: y' A) `4 f( X9 P C7 d9
; X4 T8 c+ v+ k( u' z10
( ~! s$ [( M! E" K11
& W- e, w6 p; s8 J* F12
" `9 ~, f( h/ v% G" u. d/ A9 ]2 a' C13
* ^% m: ]) k0 ?3 R! N6 j3 Y14! i/ t6 ?; B& `$ }+ U
15
! ]" F9 ~, v9 X1 \' f. _6 J16
0 a- a7 O6 t3 L6 \+ `5 D17' j @& V$ z/ [9 ~5 \9 }4 ?
18* Y' ?& B6 f: s- o, D) N6 l/ {! X
19
9 V% y+ z4 U! [$ b L8 e# S. m% R20
9 L: c* ~8 g; F8 K$ n7 h. u21
% ~4 Y7 M. `2 u' |# K: I对于具有 MultiIndex 的 DataFrame,关键字 level 可用于指定需要在哪个级别进行重采样。! { m( v5 d; I' e( N# @, N
days = pd.date_range('1/1/2000', periods=4, freq='D')4 R1 Y- \- w3 M# O: a
d2 = {'price': [10, 11, 9, 13, 14, 18, 17, 19],8 j6 T- N5 W$ d3 P
'volume': [50, 60, 40, 100, 50, 100, 40, 50]}
, K1 a, w$ q+ u$ Q9 p- R% T. tdf2 = pd.DataFrame(
2 T& g6 B+ I7 P3 R! v* q, P7 R d2,
: |' O `8 b% W index=pd.MultiIndex.from_product(
/ P# r4 ?$ ]1 l# N7 z1 n7 b [days, ['morning', 'afternoon']]( |8 a3 l4 h& ~( [7 ]; ^; F
)
# Z3 P: C' k/ b! ]# I: M) s)2 b& F8 g0 m$ m" |0 c* o
df2+ d) @ f6 N/ s; c6 L9 z/ J* e
price volume
' q1 {, K- \! A/ K1 \2000-01-01 morning 10 50
$ A& g0 U- {, s$ A6 F1 o5 s afternoon 11 602 z0 {1 m* \0 D' Q b4 [) C
2000-01-02 morning 9 40: T: K8 i; p7 ]/ ~
afternoon 13 100
6 r- T& F) ]& C& P2000-01-03 morning 14 502 u {8 [. w. h9 l
afternoon 18 100
* L1 `* V! y0 L: Z" u2000-01-04 morning 17 40
- V( u1 n! Y. I, u afternoon 19 504 ^+ `: g- ?0 O( r. x
df2.resample('D', level=0).sum()! H3 o8 N, C* h2 f
price volume3 }% H+ U8 y7 J- B- e0 g8 u- w
2000-01-01 21 110
% A9 p3 @" G: Z, |% h2000-01-02 22 1404 x) P/ G" S K; b" c' D
2000-01-03 32 150
( k' |4 ~7 Z h2000-01-04 36 90
3 a% s- w) x! m
# a9 h% |# e% T5 A! S6 C7 D1 m1
5 v. M$ X& @8 n+ u& u9 d2 I2# q( R: a7 k% R$ | g
3
1 Q5 Q/ m& i2 J, b2 ]5 g! [4
; o9 Z3 n! ]2 L6 C, p! U56 |5 Y/ p% w- U- A7 s3 U6 p( @
65 l) ]: v) i. W1 p' `( u7 g
74 n1 S0 c) v8 c4 [8 z
80 `4 N% w9 [0 X
9% {0 C U# o; T8 K! L
10
2 Z/ P/ @7 K7 X4 S: q: F11; N+ z$ D! \0 G& H6 e6 D$ |2 o
128 p6 b2 Z2 \5 b" S- q2 v9 \% z& ]; [
13
/ g' G9 V2 z& e; x* f4 j$ V; s149 E* A, m. o: }) L- P
15
U. B1 o/ K6 |7 k16
! V1 A. N: A! _6 B17
% v5 D! K. \* H8 A) K" t7 {5 B4 A181 j; @2 L, L& z& Z0 ^
19
; e4 \. R, X$ L, G20, P5 p6 B. }9 S: L& n
218 K. L% d: B5 |7 S
22! a+ I3 A& I* W3 @" G$ n! L
23
( q) M; B; p! H' N9 S0 G$ u24
% h+ T6 b2 D$ Z+ A4 F25
" a# {/ {, y3 {- d: W根据固定时间戳调整 bin 的开始:
5 Y" W' d3 {. O! R, l6 ^4 Xstart, end = '2000-10-01 23:30:00', '2000-10-02 00:30:00'
: }% j2 S L# M, F1 j+ Orng = pd.date_range(start, end, freq='7min')
8 N& I) A) m* z, P9 j6 M6 O u3 Fts = pd.Series(np.arange(len(rng)) * 3, index=rng)2 b* e& [) N/ W' q o8 J
ts
4 O& ~, J6 N0 l% [% i; A( q2000-10-01 23:30:00 0
) y9 j+ _& i8 D( w2000-10-01 23:37:00 38 }$ [! \: K- T3 b, @& E
2000-10-01 23:44:00 6" d) F! R {/ }; r. N! j2 K
2000-10-01 23:51:00 93 I. Z& K: p( p& b0 z1 M
2000-10-01 23:58:00 12
" O* w1 A) r% g2000-10-02 00:05:00 15
; ~, ~9 T( }' l3 [% M$ w/ e2000-10-02 00:12:00 18
$ V& R2 Z, l* @- L9 \2000-10-02 00:19:00 21
/ h0 W6 o4 V0 j1 g% ~& P) i2000-10-02 00:26:00 24) |! N; Y& L: T" I( p
Freq: 7T, dtype: int64
. W3 q/ d Q% I; v6 j3 u5 y2 t# o; ?: b% J, Z; r# `+ c6 S4 ]
ts.resample('17min').sum()
- _. s& a7 X6 ^* x! J" K) @2000-10-01 23:14:00 0# b8 _# F1 G& a1 H5 P$ G+ ]4 f
2000-10-01 23:31:00 92 d" w2 x8 M7 _' ?
2000-10-01 23:48:00 21% K- I' U: F" `9 {( @/ D Q% Q O
2000-10-02 00:05:00 54' ], B: D& T0 R! q m+ y& u
2000-10-02 00:22:00 242 ~+ }6 p" K' K/ i
Freq: 17T, dtype: int64, j3 i6 R( r- p$ e+ }# E
5 k- t) s: D% Hts.resample('17min', origin='epoch').sum()
: u# [( N/ j* O3 d# w$ G( Y2000-10-01 23:18:00 00 z& z2 P) w3 O( g' n* r
2000-10-01 23:35:00 18
2 {& L! _ d1 u2000-10-01 23:52:00 27
9 a8 Z) Z. ~- q2000-10-02 00:09:00 39$ u8 [) l" V* Y# X" e
2000-10-02 00:26:00 24
& o5 Z1 d8 {3 `1 `, |- M% JFreq: 17T, dtype: int641 m3 }( W5 O) v$ v# V& M
5 R6 W6 `% Q0 [' @; O& Y ]
ts.resample('17min', origin='2000-01-01').sum() L! I+ |' L9 g' b- K
2000-10-01 23:24:00 3
4 N O9 c2 _' ]/ Y: R; s4 Z+ W7 G2000-10-01 23:41:00 154 @/ e) d9 }5 V- C _! U! _, D
2000-10-01 23:58:00 454 u; _3 ]! \6 o& ~- o
2000-10-02 00:15:00 459 E7 b3 m6 H: d; g; B, d- R+ ]
Freq: 17T, dtype: int64
1 q5 K' T) S( i% {( | O' E% n) g
1 l6 v& { `9 L- g4 ]6 ^/ p6 }' b1# i. m% V7 o$ s6 J- t- |( M# h0 a
2* {% F7 C! P( ~/ y+ d( Q
3" F! x/ p, {: E5 i8 }3 j
42 N7 W9 @- @3 v+ D, a7 n( @
52 {3 o& f( s4 s
6: j3 a+ G3 X4 s% G! ?0 H6 W) b
79 I) n2 o1 C: ~4 I3 p$ C. ]
8
1 j: z' ~0 K$ C, N& Y; H% ]# a. ~9/ f, K5 N- O' i/ L1 Y
103 l: c! Z' G$ o# O
11# q8 r7 G8 q3 [
12& b; M+ ~, ~9 d) Y2 g9 g
13
% ~( P- i2 D% f0 `% ^14. X4 A4 t; T( Y$ n: [4 R/ O
159 D7 I$ M% Y# t2 y; i F4 S! n, V; `8 S- L
16
" v- R1 j7 \! r9 [17 l) c- L5 `+ v( _% @1 k
18
6 Z0 l- O! H! l* r7 N( t0 m19
$ m# `+ @6 E, A2 i0 J20' F9 B! J4 S4 p W$ C& E
21' g5 S& T5 ^* r0 R( {: O& n D5 d6 H
22
9 D C$ R$ ]1 P* l U23! T s f( N4 X, b
24
/ v# A$ b n6 v5 z25
6 M L. E& M/ W6 {2 l: v26
& d& T; ~. d/ ]# K2 t( G& z27
0 J( }. S o8 N3 o- e28. ^$ W) L/ g; o! B. l4 h: f
29- k, u; f2 M6 G# A3 i8 `2 o
30& d- j( v2 `" Q' R& x/ v2 I! A
31
5 T: O( g6 B2 Q% h! ]32
8 e) W, q' c, q336 e. x6 P( w5 u. X& g) y
34. v2 [ Q0 F0 q1 |
35. q$ ^* p# u: p8 ]6 j0 z
36
& P; ]+ H" A6 U1 P- ~; O0 K* h, {37
. I/ @; W" `2 O8 a0 w; \如果要使用偏移 Timedelta 调整 bin 的开始,则以下两行是等效的:
8 k3 h' T1 q& N" w; V0 K: J9 ets.resample('17min', origin='start').sum()3 g+ \3 a: ]. ?" g6 a3 q
ts.resample('17min', offset='23h30min').sum()
& B; d) g1 I) X3 m# `/ }2000-10-01 23:30:00 9 R1 i) R/ Y, H. V% |/ A* h
2000-10-01 23:47:00 21; [+ ^( N. Y; }: l, z- B1 K
2000-10-02 00:04:00 542 \, ^. w5 Z6 i+ w6 c
2000-10-02 00:21:00 240 Z( T. f/ h5 G9 [
Freq: 17T, dtype: int64
: F: f( F9 n% D: L$ ]! P# W: z$ S17 V m' y- V1 g2 r7 m
2
- D$ D; I5 u" f7 Z& G$ i3
1 `5 _; U6 m$ K. M" V4
) _/ T8 T4 @) u0 c- _5 K5
" }$ }0 B, Z0 ?: B: X6
: L& {" C" L. T! j" l7" n2 v) U7 \; {, _% _
10.6 练习1 a" P4 `9 o, G8 U+ v: B
Ex1:太阳辐射数据集$ Q. C2 ^- v& ^6 ?* o
现有一份关于太阳辐射的数据集:5 u2 l$ r! h L. Q
8 R2 R9 X4 l/ e( G7 x4 N% i( e
df = pd.read_csv('../data/solar.csv', usecols=['Data','Time','Radiation','Temperature'])
! n, ?) }6 m* J. W& Y* Mdf.head(3)/ f8 i I/ f7 O
# n$ W9 r! B1 w$ [" E1 m( mOut[129]:
3 ?! U$ M% E7 Y( D6 E# d Data Time Radiation Temperature
. | H: l0 a1 \7 w2 D0 9/29/2016 12:00:00 AM 23:55:26 1.21 48
* a. u' {, z& i* w' }1 9/29/2016 12:00:00 AM 23:50:23 1.21 48
2 t) F/ T* {# j" ^2 {4 ^2 9/29/2016 12:00:00 AM 23:45:26 1.23 48
- Y% D7 z( b; d9 }: y+ k; |18 ]0 f) e2 w+ s3 f2 Z
29 p! E! Q8 d3 a
3
) j6 V$ k4 M, l- _6 r6 \% K `4
) @: F$ j0 M, }6 z4 X: z3 o5
* C' I! i9 g5 |9 I. m6 ^& `5 e4 Y/ s+ x# r4 X
79 c% ]# [# T& Z/ l4 [0 k+ [
8
% N+ M, I1 ~/ h+ |4 s将Datetime, Time合并为一个时间列Datetime,同时把它作为索引后排序。
- o/ o' M/ q$ y. f* S6 |. T每条记录时间的间隔显然并不一致,请解决如下问题:$ C) y" ^- E" Q
找出间隔时间的前三个最大值所对应的三组时间戳。7 ~$ m& s7 R0 `1 T: |* F
是否存在一个大致的范围,使得绝大多数的间隔时间都落在这个区间中?如果存在,请对此范围内的样本间隔秒数画出柱状图,设置bins=50。
8 { t+ S4 I! o$ N3 {, \) p求如下指标对应的Series:
3 o$ M' C T! G) r$ y; [& T) I温度与辐射量的6小时滑动相关系数
9 i ~, g, s; b; j9 u( {4 |以三点、九点、十五点、二十一点为分割,该观测所在时间区间的温度均值序列
- C% |* t* i3 b! v0 w& T' @每个观测6小时前的辐射量(一般而言不会恰好取到,此时取最近时间戳对应的辐射量)8 [& M( x' X7 C: y- l
import numpy as np) S) a. Y# i5 l, N1 p* a
import pandas as pd
- L6 b' e- N! R1
8 c+ N, W' l& ~- A" @" j2. I1 Q7 G) O- s
将Datetime, Time合并为一个时间列Datetime,同时把它作为索引后排序。
: g/ h2 G* C: Z; w d- \data=pd.to_datetime(df.Data) # 本身是object对象,要先转为时间序列" q: \9 b" O% R
times=pd.to_timedelta(df.Time)
4 T0 ?4 {( b9 E' H4 g6 R# h& Udf.Data=data+times. ]- ?' X7 X# h; v4 D0 n3 ~
del df['Time']; F4 W, D1 a* y R+ q- w( e( z6 }/ T
df=df.set_index('Data').sort_index() # 如果写的是set_index(df.Data),那么Data作为索引之外,这个列还另外保留
0 ]! k+ o1 z: w2 ]; Ndf% G9 }. b4 T* G O
Radiation Temperature3 @" U2 `7 `, u U* ^
Data
- l( Z0 k9 {8 f! Q& m# }) k1 K2016-09-01 00:00:08 2.58 51
# D0 ?- V; {8 N2016-09-01 00:05:10 2.83 51
8 e( h$ y" w+ z" N2016-09-01 00:20:06 2.16 51
; y7 G( i" ]) S6 A2 M0 v2016-09-01 00:25:05 2.21 51
& C; J' ]3 ^' |+ f2016-09-01 00:30:09 2.25 51& ^$ \: U6 c2 k# ?" E+ J
... ... ..." q' I+ a, h5 t" c; q. e8 t
2016-12-31 23:35:02 1.22 416 K6 o( h% c+ g
2016-12-31 23:40:01 1.21 41' W& N2 {( Z0 I
2016-12-31 23:45:04 1.21 428 w& n% P% L6 _2 m
2016-12-31 23:50:03 1.19 41
5 y3 F& b0 z9 d4 x2016-12-31 23:55:01 1.21 41
' k0 S9 P$ i- `8 i
# q7 Z) n. B0 V f) u+ t0 j18 k1 Q& g% l7 L
2
' q& U" f- [# j" h# w9 |4 d6 N3 J' f3& X; T5 w% o& Z- z5 x# I4 z
4
/ e) c: ^8 q2 p& I6 `58 B6 P( c' _2 H) V5 ?+ ?
6% [" O C0 f; a6 N J8 L9 }
7
: B4 `5 f8 q+ b) _3 c8! B: }2 [+ B5 r' L1 X P. U: l
93 A: l J+ a7 @9 [/ Q. Z
10; g9 F- f/ T, o2 N9 A) ~5 v
11
" q2 I; `4 m- x6 b; z* X1 A; E0 `12
4 t' u, q5 K$ F" }+ i13
* N- C- @9 J. C14! v& c1 [+ O( k! ^
15
/ T: F& p2 m/ w) F' V1 A162 g/ _6 j' m; |8 t) f; A4 p
17
. M7 w% A8 S% Z. B. O( }( y- \18
! E1 U2 V/ h, J19# a# ]5 a/ S' }4 Q! i4 B
每条记录时间的间隔显然并不一致,请解决如下问题:1 Z( x) L5 c+ e1 c7 d$ D
找出间隔时间的前三个最大值所对应的三组时间戳。
* g7 f% y' ?4 P1 O( _2 q; P9 F# 第一次做错了,不是找三组时间戳
. s& v. S7 A$ I$ Didxmax3=pd.Series(df.index).diff(1).sort_values(ascending=False).index[:3]( g: ^4 O( C3 n% k2 }: W1 S
df.reset_index().Data[idxmax3,idxmax3-1]
3 w) M! T# O$ i6 k" i" L0 y3 }; S( F1 B0 k
25923 2016-12-08 11:10:42+ i0 }* k- q( q8 P e5 D9 ]
24522 2016-12-01 00:00:022 w8 H3 K. B9 a# `) x. R1 Q
7417 2016-10-01 00:00:193 t: T+ ?4 Z/ `3 B
Name: Data, dtype: datetime64[ns]3 i$ C/ H2 |5 R8 T
14 `" X1 h1 [0 v& ~6 |' Y
21 e: f+ { W/ T3 Z9 X, E6 y
36 H6 y7 i: G8 v ?
4- G& L9 H0 @" U+ c7 P* c- I6 C
5
6 [8 f$ i! l* J' [9 H' T) k$ f! W% J6 q3 F' R1 h+ m3 j2 H6 h/ }
7
8 Z2 e+ S! ^2 t+ W8 R! a8: y! F( ~$ o$ p; F- Z r" S2 z( r; ~
idxmax3=pd.Series(df.index).diff(1).sort_values(ascending=False).index[:3]
, E, V" m' O0 A8 ^, [list(zip(df.reset_index().Data[idxmax3],df.reset_index().Data[idxmax3-1]))
9 i- ?4 j2 l+ Y: S+ U7 t+ \, q, R2 \( ~) A0 a* P% a n
[(Timestamp('2016-12-08 11:10:42'), Timestamp('2016-12-05 20:45:53')),
. _) U" C0 X" n) {* G$ f5 ^ (Timestamp('2016-12-01 00:00:02'), Timestamp('2016-11-29 19:05:02')),2 S5 X% A. Z% q; r' N
(Timestamp('2016-10-01 00:00:19'), Timestamp('2016-09-29 23:55:26'))]. |1 E) }0 q+ _1 E R( r
1
$ P/ c# p: e' ?8 ?2
. _+ l, h% j; U5 u7 C3
) ^9 G: C& H J3 Y6 W4
- {' D, G. o) _/ ?2 K2 K5
3 _+ F+ c! V% M% v# {& A# g" l1 i4 K8 M60 \* V- B8 A0 i/ i l1 Q6 T: P; z
参考答案:
: r: k9 u: O' Y, A5 P
! e& ?( e6 U9 f$ }- t, Us = df.index.to_series().reset_index(drop=True).diff().dt.total_seconds(); I6 h' h: B+ h; b4 `' A
max_3 = s.nlargest(3).index
5 r' c7 X7 C; ~ hdf.index[max_3.union(max_3-1)]
( S% P! ` _( v% ~- U# I* U n8 C0 t
Out[215]: * m3 l9 c1 ^, u6 D
DatetimeIndex(['2016-09-29 23:55:26', '2016-10-01 00:00:19',
/ f. j0 R3 W D& J! m; p '2016-11-29 19:05:02', '2016-12-01 00:00:02',4 m' p1 b' K% A! M0 o
'2016-12-05 20:45:53', '2016-12-08 11:10:42'],! ?3 M* n4 k$ |- z3 n
dtype='datetime64[ns]', name='Datetime', freq=None)
6 k5 ~, p- E" Y9 o9 o1 B* W1
/ O5 r' N% N$ d: w; Y( v2+ d) m1 ?/ a! `( ~/ b4 i
3
" D' j5 b- h% s5 c, `44 } M: ~6 f" w& Y0 P
5
9 t5 E9 s5 o3 h4 c- J u% g) g61 q: j1 S3 z h" E, E7 S
7
4 A9 G+ r$ X2 |0 X" Z8$ X' H. w% }: [6 W* M7 t" }
98 Y7 C% ]) a+ E4 \
是否存在一个大致的范围,使得绝大多数的间隔时间都落在这个区间中?如果存在,请对此范围内的样本间隔秒数画出柱状图,设置bins=50。
$ P" ~9 A8 e* p' q4 m: b* I7 G# 将df的indexydiff做差,转为秒数后排序。再求几个分位数确定取值区间$ q6 s4 F9 K( p( `3 @
s=pd.Series(df.index).diff(1).dt.total_seconds().sort_values(ascending=False)
4 Z( r7 `% x% g/ a: h6 ns.quantile(0.9),s.quantile(0.95),s.quantile(0.99),s.quantile(0.01),s.quantile(0.03),s.quantile(0.05)
: e8 c1 j; l0 K; o3 e
5 b/ u: Z! ~4 L+ J9 G t- x(304.0, 309.0, 337.15999999999985, 285.0, 290.0, 292.0)1 U3 \, o, u- @
1! R1 z1 t! I' ]# o+ ]0 a9 |( F" w3 a
2; R+ Y; t0 e9 _! u! j0 q
3
/ M+ H, N; v, I2 l- m4
7 W1 i$ {$ u2 R% _, x2 L4 g, {) F1 _3 h5
( t! D( p2 A0 W9 `1 P%pylab inline/ w; L0 c6 i# r7 W: v6 r
_ = plt.hist(ss[(s.values<337)&(s.values>285)],bins=50)
. R& Z* b9 l& e" g5 x- vplt.xlabel(' Timedelta'); [# G) ^7 X# W1 `) p
plt.title(" Timedelta of solar")
- ?0 d4 Y: {7 a, ?0 p) R8 m; b1+ S' E' _- q* H; a4 }
2" K" E6 k, h2 @) d! N) F3 W
3' W# k: H* I# {$ Y6 i& [6 A
4
6 u1 c8 ]$ _% |# E+ H) _$ R# e( p1 i* C
9 W* |5 ^ O# o( [( O2 O$ M( T求如下指标对应的Series:
- I( _" ]2 {3 B4 Y8 E. B温度与辐射量的6小时滑动相关系数
2 O7 I3 T& U0 g( E, L以三点、九点、十五点、二十一点为分割,该观测所在时间区间的温度均值序列 R# q& j/ | H" h$ c/ c
每个观测6小时前的辐射量(一般而言不会恰好取到,此时取最近时间戳对应的辐射量)
+ H7 g" w/ `. a0 N6 x7 C" Ydf.Radiation.rolling('6H').corr(df.Temperature).tail()/ m9 ?) _$ f6 d8 ~- N
u& g8 {5 Y/ y D% GData
6 k! Y( F0 Z3 |$ X& `& p0 B8 N' T2016-12-31 23:35:02 0.416187' A4 }+ n/ Y- E5 `
2016-12-31 23:40:01 0.416565& k, |6 a5 d2 O1 E" I' F
2016-12-31 23:45:04 0.328574
+ }- T9 \5 R9 S4 J x# k' Y2016-12-31 23:50:03 0.261883
5 L+ g3 z: R0 X; `$ x5 Y T, D2016-12-31 23:55:01 0.262406
+ ~/ g8 }' a% r; odtype: float64
' `/ ]7 a2 j, i5 G6 `' p4 Y1" V# a v* v$ x% h8 [
24 l% Y5 C8 \' x+ V* F* k
37 m+ d- f9 e0 n& X
4+ i$ M3 ]& r" X- o9 ], R+ ]' s
5
( R x( O/ f9 L/ f6
0 g2 C( Z! y9 ?; }$ I2 z7
1 X: i- o$ e. ?9 h5 J% A8
! N. _. Y4 S- ]0 h9* v, X* u& W7 C& T: d& z
df['Temperature'].resample('6H',offset='3H').mean().head()- b, `( t4 n' b9 ^7 X
* M% t8 p% x( m: q0 @, q- v, s
Data0 _' E$ h3 n! ^6 U, q
2016-08-31 21:00:00 51.2187502 }' {8 S" O$ E: G& |1 _) Q0 b: m8 S
2016-09-01 03:00:00 50.033333$ U# u5 s+ `$ b5 l& ^- j
2016-09-01 09:00:00 59.3793103 K3 @- ?& k" h- ~% x
2016-09-01 15:00:00 57.984375& |7 Y a3 P( k' a4 t
2016-09-01 21:00:00 51.393939- G7 n) _% E( {0 l' w. j+ w
Freq: 6H, Name: Temperature, dtype: float647 m2 @1 u9 T, J+ \
15 r( y2 I# U1 Z8 I" l t) s
2/ R, e1 T6 q" j9 b8 l& ], n
35 J' z1 e6 ~1 b* p! @7 B' ^
4
5 W& W$ I/ ?6 n) Y: X, j5. ?. p/ T' G- h9 _7 [3 y
6
' b8 i# {1 f9 Y. ^# p& I7
1 z1 E; \( |% E5 U8$ b, F9 z/ c- _0 S7 @9 R
9 D: _5 ~, o5 l9 L' X: s" o" [0 w
最后一题参考答案:3 o# o; D5 @, d, w' r" ?
3 Q3 u$ Z8 w) Q) W# 非常慢
( \4 W/ i/ X# ymy_dt = df.index.shift(freq='-6H')5 L6 S4 `6 g% [: \# W& u
int_loc = [df.index.get_indexer([i], method='nearest') for i in my_dt]! v5 a6 q; F6 i- E! ^1 V. p6 |* Q+ B
int_loc = np.array(int_loc).reshape(-1)
: H, M2 N2 Q3 U7 v+ Eres = df.Radiation.iloc[int_loc]. t3 o! p$ n, E/ O
res.index = df.index1 ^1 w0 w- {1 y n- y7 t
res.tail(3)' r' z2 j0 i8 A/ Y( W
1
2 B6 b ^% t( s2
* r1 J; a, F6 h* Y% y3% i K' w* z G1 b
4
9 S& ~2 q% l+ K1 h% }& h59 N8 ~$ \# _8 c$ J! J" A* V
6
; d# r I6 e! D% \7# T! d; C% K5 W
# 纸质版上介绍了merge_asof,性能差距可以达到3-4个数量级
: h( L3 F7 d, M* I& w) H; Ytarget = pd.DataFrame(4 H# k, P' L/ X* K5 n% y5 a
{
8 U1 K: N# {! z4 ~5 h& [ "Time": df.index.shift(freq='-6H'),& Z5 ~0 w5 p0 j' W8 S% n
"Datetime": df.index,5 V, E" X- |) |# i0 u8 K
}% n' m+ {& @& q/ U; {
)
7 P3 h6 k7 Q+ b4 P/ y5 n3 Q- C! M; U7 l+ q
res = pd.merge_asof(; |" W1 ^8 ]4 _
target,' t+ d: v3 a2 F% I/ I3 v2 a" r5 O+ ^! k
df.reset_index().rename(columns={"Datetime": "Time"}),
; r$ }' w4 y) D" o left_on="Time",( i9 y' b9 f3 |- Z! X, s. y
right_on="Time",
& a" }- S) n f2 b# {$ P; ^% X% I# I" P direction="nearest"* I- F U: x0 b! I
).set_index("Datetime").Radiation" k' w6 Z( b& m/ Z* T
) F5 Z2 i. D v* U% K4 ~- Tres.tail(3)
/ s; h* \. S' R" Z; b0 O2 i2 M7 \6 a: fOut[224]: / b7 y3 r' c+ g6 @& l' D
Datetime
# k) N# v" L% m6 |- i* x5 q' ]" Z2016-12-31 23:45:04 9.33
' G1 J+ P9 H" ^. q2016-12-31 23:50:03 8.49
+ {' B$ Z$ V) n7 W" ~) D2016-12-31 23:55:01 5.84
9 ?# K& h: \8 w3 zName: Radiation, dtype: float64
0 S2 Y7 @) q7 Y
6 n% P4 i7 K# }2 N' {1
# X( e& z7 U0 k" u( S- d9 Z2
5 F" d$ k' _6 j4 v. W8 a! Q5 f3
. ^9 D- X, S* F! c4, P7 Z" o7 {: O) w3 g
5
( F7 M- s s* d' M8 s; ]+ [9 q6- y" J3 K; A) y4 {1 l: ~6 g, @0 _. [
7
& ]+ r& t$ b0 A8$ {$ m4 D4 F: z/ G* [, a- ]# c; x
9
; p, K7 I' D. ?8 W% Q# j1 q0 \; j10
. d' {' o3 }; Y3 A" j11" ~+ `' t, }5 ?2 A" `$ ?* o
12
7 s8 ?& m7 d8 r13" [7 m5 Z% H4 O; c# l
14: ]0 k& ]6 R+ e/ y, Q4 N; R0 |
15
6 {2 @( e5 S$ M0 }; k% D% ]% H16( ~7 K1 A4 T) A% Q6 u
174 {( }/ O0 P% k0 R
18
0 W u% O3 V4 i6 e" J6 n' ?/ B19
) ?8 _* Q' l0 ~; h* f# }2 D6 I, Y- `20% _) ?8 a" X R. \
21 i$ ]! I/ S9 i3 D. |$ o
22
- Q0 F( D, e3 S- }232 m( G5 J8 F- H, A: j
Ex2:水果销量数据集
8 R* k [* L& m) w* H; u现有一份2019年每日水果销量记录表:
8 k: }5 [+ W$ y$ y5 |
% Y. y( Z; i: F$ @# [/ S5 ?1 [( |df = pd.read_csv('../data/fruit.csv')' |7 i# T W. s1 g! z
df.head(3)- G0 _' E0 s7 q1 M
$ g% }& `7 L* ^2 tOut[131]: ( A4 V4 o7 A7 j9 t S$ t
Date Fruit Sale
/ \- ?, J" q% ]. q: u- K0 2019-04-18 Peach 15
7 u) @5 z% C" ~. p, Y: X$ m1 2019-12-29 Peach 15
2 Z! H# K: H9 F* j; T+ ]7 m2 2019-06-05 Peach 196 I- X! Q- {9 k- ]3 ?$ c
1
) J' {4 R3 G N1 {$ Y" ]2
' }0 _; Q4 T# v5 k% r& G: |3
' W$ Y. N3 ~2 n" r g% W+ x- Z7 l" h5 ^$ G4
5 [% h6 ?8 Z' g5 V& m% ]5
; w& ~5 p' ?' C2 y) V, s/ K( `+ u) J6
- Z2 ~$ C8 Q4 U1 k4 [. C2 ?% Y! f7. K- k8 o- n* n' V% p
8: j, j, D; D0 o$ W; y4 l' l" U
统计如下指标:! ^# G% n: j2 ?" g
每月上半月(15号及之前)与下半月葡萄销量的比值
) W0 g' C( o1 y每月最后一天的生梨销量总和$ g7 P' h% m9 H. F
每月最后一天工作日的生梨销量总和- \: X" I& q8 G4 o* g* D
每月最后五天的苹果销量均值
' o/ c) d5 U. i( a1 G按月计算周一至周日各品种水果的平均记录条数,行索引外层为水果名称,内层为月份,列索引为星期。& b1 }' |0 m9 ^) \7 P) @! O$ g
按天计算向前10个工作日窗口的苹果销量均值序列,非工作日的值用上一个工作日的结果填充。
" |; L8 Y1 V# ]import numpy as np3 h6 b% Q5 y9 L
import pandas as pd
3 f7 f, T: b: u% Q% I. M1
5 q% S' w4 A0 J1 Y- w) w9 G2% y# F5 {, j9 H6 k
统计如下指标:
+ c+ x. d: Z, S- m6 R% d每月上半月(15号及之前)与下半月葡萄销量的比值
+ o l/ i- Y! x, v4 K每月最后一天的生梨销量总和
# v4 V; [+ ~8 \ b( G* c7 f3 P每月最后一天工作日的生梨销量总和
" p$ v# j* j2 a2 G5 }7 A& ^ c- q; g每月最后五天的苹果销量均值( u1 x, o4 ^* {+ Q/ [
# 每月上半月(15号及之前)与下半月葡萄销量的比值1 Y. d' G) \3 C/ | w! C
df.Date=pd.to_datetime(df.Date)/ T1 @# z; R9 K/ F; d$ x& ^
sale=df.query('Fruit == "Grape"').groupby([df.Date.dt.month,df.Date.dt.day<=15])['Sale'].sum(). O2 k" |3 G* L- x4 f0 B+ Y8 h( i
sale.columns=['Month','15Dayes','Sale'] # 为啥这么改没用啊
) r2 s, O4 ^. V' Z, Vsale=pd.DataFrame(sale)
% I! N2 X" [3 ]sale=sale.unstack(1).rename_axis(index={'Date':'Month'},
3 W/ L& y) |. _1 x8 Z* ]: H columns={'Date':'15Days'}).stack(1).reset_index() # unstack主要是两个索引都是Date无法直接重命名
9 K9 @9 `( A/ csale.head() # 每个月上下半月的销量
, u5 ]- {7 v( U2 h5 P: Y
( h* h+ H1 k. ?6 b8 e) f, K1 l/ ` Month 15Days Sale1 `& W/ M! {5 X7 V5 w5 o* |! w$ a
0 1 False 105032 m) F4 M$ I [6 v0 w
1 1 True 12341" \+ ?7 C6 y7 n2 ^' K$ m) m
2 2 False 10001
0 ~1 l8 h4 ?4 N. Q3 2 True 101060 c V) i8 } O5 P' X
4 3 False 12814
" ^/ l7 Q7 V7 I& N% ^( |
! J: T* ?. P) g, d# 使用自定义聚合函数,分组后每组就上半月和下半月两个值,根据索引位置判断求比值时的分子分母顺序2 P9 y) [: Z- n9 A4 v
sale.groupby(sale['Month'])['Sale'].agg(, x9 u8 O p3 f6 l
lambda x: x.max()/x.min() if x.idxmax()>x.idxmin() else x.min()/x.max())
4 D B' e( t+ i* W1 e# d6 w+ h# E0 Q _1 B; a1 A
Month; G4 h$ `3 ] K5 O
1 1.174998
% l& |! z( m% X! o2 1.010499
7 i1 N0 ?+ r2 d5 B" Z3 0.776338& S, A2 _, v: p% E# O
4 1.0263450 l7 M+ ?; e# w, o
5 0.900534
0 P. `6 v' \3 D6 0.980136- a m. @2 X$ h
7 1.350960
3 t9 w, O% L+ r7 f1 Y8 1.091584, {, o5 d! [' l2 J; M/ a
9 1.116508
1 T S+ B+ W3 [$ d8 V8 X; z10 1.020784! U6 E" w. ^/ I& l! O2 W1 Z& l
11 1.275911
1 U3 ^9 k6 [% B( `8 J1 A12 0.989662
' n. c+ E6 `- {/ \* {( L! NName: Sale, dtype: float64
: o- d6 g' D3 o& c2 q# B8 c9 |4 S
12 c% y. p$ J$ o
2
" D- Z& M" B3 `& ?37 w/ f8 F9 F$ U; A
4* q8 y }* Q S, D; f6 V" a, s& v
5' K$ A+ T8 M+ A7 {+ D4 H
62 P; |' u" Z! o/ @4 d
77 B+ k7 L; D* s% e- \, M
86 O$ U5 G7 q) q' I4 g% u' s
93 M6 a6 x. d ]4 r8 ~
10
2 |+ |' j9 X6 `2 A7 ]/ s2 V117 V" ]7 I% P7 y, t' ]5 d
122 R6 A: A/ Y0 S/ L! b
13
0 y# S* A. z! h; h3 }5 B14& f4 x; |( _) `5 t/ ?: { v$ R
15
: h- a+ ~* x: i16# Y8 P" E3 m2 W4 E: }5 h
17
% ]( j3 }, s# S0 O' _2 c182 x. g! X$ m, \# t, _! A2 j
19- h. |/ y8 E# O; W2 w/ ]
20
2 h) t+ l: l, a$ G! p21
6 o8 J' r0 |: Z K; W" c22
4 ]* ]: s7 H7 }& p+ a234 h* A9 c# ]0 P
24
) R+ r$ x. e1 B9 @$ {: O25
9 ]# K* u$ Q- O26, b: v D" W, C% Y
27
9 k1 Z) T* \3 z5 O2 S28
3 C' R1 G4 `. {/ B/ G3 f29* d% q5 @. f: {& v! u
30( ?9 `# [( o9 w5 z3 h, J2 o
31, R q( j4 c4 T# Z% [
32
1 F0 [( Z, I9 l33
/ s! {% C' T: S" H34' F: K G" |% s' D6 E
# 每月最后一天的生梨销量总和
% k1 @! d: B* q p$ Q4 ?) W. ?2 gdf[df.Date.dt.is_month_end].loc[df.Fruit=='Pear'].groupby('Date')['Sale'].sum()
( M9 m: c7 i1 ~# J! x/ M5 V" m& V5 k& {) F9 k. H: L
Date
$ h% V+ N ]! _8 G3 _3 Y" @2019-01-31 847
l. ?% D5 [2 B- s" C2019-02-28 774+ F# |( l0 ^8 A) H% f
2019-03-31 761
4 L; @) _8 G4 C/ _! F4 t$ p6 H2 k2019-04-30 648% i$ f- @# ^5 p/ o- z* |, k4 e
2019-05-31 616
! B4 `+ F- J- {2 L) V1
! W' S* O) O" _5 e: F0 ]) R. w2$ `& [% g0 e+ e
3
$ @ e. o' d) G0 M/ J2 N6 k, d' L4
% ~* _7 t, `: c" I* x5
( l4 J3 h6 K! H) B- s8 p) e' M62 L$ h/ C9 e' x: i% f! w/ y
7! A6 f3 u/ q- M) Z, T5 h+ y: ]7 f
8; U" T1 a7 M0 u8 c; D: P0 p; H
9
D* i3 Y" _% D% Q3 W$ l/ {# 每月最后一天工作日的生梨销量总和3 [0 a% m6 V8 H" v2 ^
ls=df.Date+pd.offsets.BMonthEnd()& L. a) J5 U" G$ k# P
my_filter=pd.to_datetime(ls.unique())
- h# [& [; K6 j" o- ?+ q. r6 d% tdf[df.Date.isin(my_filter)].loc[df.Fruit=='Pear'].groupby('Date')['Sale'].sum()0 e- b& z, o( F' e. V' j: F2 |
* O& ]' d5 X* C# ~% ^% w
Date
- d2 V- Y' a8 o& v. V8 e2019-01-31 847
! P) T) r2 p% A: ~- B2019-02-28 774
3 g# J0 l. A! ~, x$ w. D2019-03-29 510
. U0 x- E; Z( C% y. {! ]+ ?2019-04-30 648( x* f% z' {7 r; s, { L
2019-05-31 616
7 x) u7 W! i; i5 {. U' S1 I1
) O/ F, P$ W3 l9 I" B/ P2 g, d2
/ H5 L# h( ?: u" I5 [31 l. C8 J: i2 Q8 v* _
4+ P% e$ ~( E0 T+ X, U8 k) ~$ ^
57 @" n D! s. h% A( q: W, I
6
( S! f: V6 ]) Z% q5 g2 Z. e8 _7, a0 C* m' u0 }. b5 Y+ w1 y6 ?* z! b1 Z
8
7 r: X; D3 }! l! s6 r5 ]9- d4 q- w# k- q% j3 s f/ v& B
10, ?8 T" u. z f2 i
11
4 G& P# |& D: [( X# Z0 o R( ?# 每月最后五天的苹果销量均值. s# |" v8 T# x+ o' m* ]
start, end = '2019-01-01', '2019-12-31', u( W- m; B3 S% r- _/ M
end = pd.date_range(start, end, freq='M')
/ e4 H% w$ C' t7 _: ]end=end.repeat(5) # 每月最后一天的日期列表,重复5次方便做差* b0 v3 H4 l9 C6 ^, Q+ ~; ?& z
, n* I6 l* @9 x4 P
td= pd.Series(pd.timedelta_range(start='0 days', periods=5),)/ e9 k# q% j: ^6 e8 \
td=pd.concat([td]*12) # 日期偏置,最后一天减去0-4天
* J) F( L$ i% h/ c7 Eend5=(end-td).reset_index(drop=True) # 每个月最后5天的列表: z9 [ n& C' O2 ^$ { F- J
$ \6 L7 n, J+ V0 m; m# S8 Capple5=df[df.Date.isin(end5)].query("Fruit == 'Apple'") # 每月最后五天苹果销量
$ t/ p" L, X2 K; `7 xapple5.groupby(apple5.Date.dt.month)['Sale'].mean().head()
% y: e# b3 |3 \( B" {# n& V6 b) r k) C/ h
Date
5 L$ q+ ~) }' y/ h; b1 65.3137251 E+ i4 L G. ]; w# I% E8 K
2 54.061538, G6 ~2 s9 i0 z9 W) ~9 l
3 59.325581
5 Z/ K; C* F E4 B5 E3 e4 65.795455
6 w7 s2 L- U- c" h0 K; F5 57.465116& `4 C D) H1 D! D
5 s( G: x7 a% H1' _9 U0 j) \" e) j
2+ m ~* W' G+ T; M! I& x, ?! Z/ r
39 K& o8 F# L) }/ `/ B3 ^
4
$ k& z2 U2 Z, i9 u }8 y# ?, Z5
! t$ A5 d, B3 R69 p Z; w; H: F2 O6 @6 p1 G: {- o
7& F' p/ y$ u- Y3 a6 ]. C: c
8" M5 L' a3 F/ m& W' w- f5 ?) ~
9
4 I1 g8 G1 Q8 O2 T: Y1 E10; ]5 R& G3 h- ]4 D4 |" r, u6 w
117 z7 Z% r- u# u9 q$ }4 @ v5 o
12
$ R Q7 C* f0 I/ Q13
% K) A% g5 f! S8 w& r; O14% s; \2 u8 @, z* c7 S' \, U
153 q8 q% D! }3 L
16
. a3 g& ~1 n' e4 |0 l9 y a+ _17# n6 l9 k; N, `- ?# q6 P1 f3 T
18- L7 a6 `: I" b; O# T# [( P6 V
# 参考答案:9 Z) R2 n3 j$ E& ^* z7 y; D
target_dt = df.drop_duplicates().groupby(df.Date.drop_duplicates(1 V3 z/ x& d1 W0 ^" k# d c4 E, h
).dt.month)['Date'].nlargest(5).reset_index(drop=True)# G0 x' n5 f: [2 q
9 Y) J L b1 o9 t" m
res = df.set_index('Date').loc[target_dt].reset_index(
# }! a* b3 [9 l* ^4 X- D& r ).query("Fruit == 'Apple'")
8 K# `$ M6 X! R# ^1 B! G$ t5 |7 Q5 s5 m7 L" E: E
res = res.groupby(res.Date.dt.month)['Sale'].mean(
% {2 \! h9 q1 P$ a x ).rename_axis('Month')3 c" S& p) R0 D
# _9 V9 I9 A3 Z( \0 A5 x$ p
7 N- O+ s$ o) x& W+ I. r
res.head()' e2 N1 Q/ X2 P5 ^- r
Out[236]: 7 F3 d8 X* L8 `, ]
Month" P3 v0 U I1 i7 D! @* p
1 65.313725
0 @9 S& ^$ Z1 S# ~& M2 54.061538
" R+ v$ b: \- C. v3 59.325581
8 o1 A- @/ G) v& O: [( Z4 65.795455( G2 S$ ~0 w- W1 { z; o) ]
5 57.465116; J$ `1 N( H- s9 L4 S4 q, x
Name: Sale, dtype: float64. ^5 S) i, k t: r* a' `! g! W
' \, S8 `9 X3 p, _8 s( n$ Y, _
1; j4 j+ J7 h- w- Q2 B+ M6 a1 W
2* L4 k5 @6 R7 j" w6 F
3
& I4 r9 h" W4 z! v$ q4
5 }: ^8 U% j3 M* d5
; ~. D t8 a( a/ b% V& c7 E* ~" S$ c6' j) D E5 @5 \: P) {8 X; y8 L
7
1 P9 ?6 o, T) C( D8 V8
0 q9 K- q9 j. J9+ Q1 V' w2 E- f4 v3 M" [ Q: Z$ e
10! v& H2 E# e3 a+ U
11
( B( i7 x8 A* O. D0 R& Q12
" X. r, l: Q S13
- U% A, [7 r0 ]4 p( h5 p' c/ {145 @/ F( t: k4 R c: {
15- \% |4 `4 U( X7 k2 a+ F* ?6 b1 G
16
+ n% L/ B- D7 y/ u' ~17+ `* a. D) b/ f. ^1 ~" e; d
18
+ d$ N- i L" }9 c3 D' y19" n$ L0 J1 l9 h* l: p
20
H$ n4 j/ p+ C1 [按月计算周一至周日各品种水果的平均记录条数,行索引外层为水果名称,内层为月份,列索引为星期。
/ P- |9 M4 i0 oresult=pd.DataFrame(df.groupby([df.Date.dt.month,df.Date.) X1 }4 ]0 f# n, X1 J: \
dt.dayofweek,df.Fruit])['Sale'].count()) # 分组统计 + |, Z/ c, u& v1 V; `' M6 ^
5 b Q- H9 D- I/ e/ X a
result=result.unstack(1).rename_axis(index={'Date':'Month'},
8 V0 W' o3 I* p5 P+ U# a" J- @ columns={'Date':'Week'}) # 两个index名字都是Date,只能转一个到列,分开来改名字.
U$ j6 L6 V+ C; _' s) S( {7 dresult=result.swaplevel(0,1,axis=0).droplevel(0,axis=1)
5 K, ]3 x: Z3 Vresult.head() # 索引名有空再改吧
- X Q r; |5 j9 R" B9 R# ?- L5 g5 Q3 l) E, y% u; T
Week 0 1 2 3 4 5 6, w' ?# J- @; w2 \! v
Fruit Month ; n4 }3 O Y- x' `. t$ b& m5 U' S
Apple 1 46 50 50 45 32 42 23# p% T) b9 v7 @) v2 T1 W
Banana 1 27 29 24 42 36 24 35
0 L( v' l Y' {' U1 Q RGrape 1 42 75 53 63 36 57 46
! U! R- D. v0 O9 ~1 h7 [Peach 1 67 78 73 88 59 49 72
$ r" I2 T3 `2 ~! uPear 1 39 69 51 54 48 36 40/ D1 M f( S! x9 d& i
1
, a) A" w t0 r24 W; K! G3 H8 {% O/ r8 k
33 X# z- X) [- F/ o# p& b
4
3 S) w5 X: u1 Y: G: {7 I. b5
' a' Z; B4 \2 q6' `8 o3 w0 Y6 `* b0 [5 L5 z: t, O
78 f" G0 _' B Y
8
7 r. W$ E0 c' H( j9+ h3 J+ z% Z. Y$ I, t( n! H8 u
10% c1 ?1 H2 p/ G' c$ J
119 B& Z$ \8 }1 s& B: s
12: m1 s4 I2 @" \7 p
13
2 } y% o- q8 u7 [1 @, K8 f14
. B) h0 m2 `6 E6 a: j ~! W15
% ?- g3 L; [: j4 F2 u按天计算向前10个工作日窗口的苹果销量均值序列,非工作日的值用上一个工作日的结果填充。. U/ G0 n b, ]4 |' \
# 工作日苹果销量按日期排序% r Y5 N% j9 N) V' d) H5 z
select_bday=df[~df.Date.dt.dayofweek.isin([5,6])].query('Fruit=="Apple"').set_index('Date').sort_index()$ Q8 l: X6 P7 q8 h0 Y7 G
select_bday=select_bday.groupby(select_bday.index)['Sale'].sum() # 每天的销量汇总2 }+ p$ b" I7 { V$ N4 n% ~1 W
select_bday.head()4 j3 q/ E+ a: J& ~2 m0 k
# \7 X7 u' {" k* W/ ]4 }# _Date
6 l2 S0 Y( Q) ?5 B2 K: y7 q2019-01-01 189
& i5 o: R6 \/ z% h, j5 X2019-01-02 482 L: Y2 [5 U8 D$ e, f
2019-01-03 890
; |1 p* W8 {+ M! K: Z2019-01-04 5506 V, U* a& K f
2019-01-07 494/ u& Y! Z2 Q% v$ i
5 A! C/ _. P1 |& F6 R
# 此时已经是工作日,正常滑窗。结果重设索引,对周末进行向后填充。6 b& m; {3 h( D) X9 Y2 q6 s! F }
select_bday.rolling('10D').mean().reindex(df.Date.unique()).sort_index().ffill().head()/ h7 q% ?8 i( a7 r
% K6 @1 c3 ?4 b9 U- n. Y; k" i4 MDate7 w. b7 O" a' X! }3 H9 ?
2019-01-01 189.000000
8 w$ ]' w; b& ^8 u2019-01-02 335.500000 W8 L) ^6 {/ S8 ~8 p g8 V- ` l: B6 Q
2019-01-03 520.3333336 g4 e3 c/ H5 P
2019-01-04 527.750000. i, Y' M% O, |- u
2019-01-05 527.750000
% ` a$ s# [: Q3 m
* X0 r8 k$ c" i4 Y4 M# T————————————————. q+ Y: M& k" Q
版权声明:本文为CSDN博主「神洛华」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
) U, [( ^) x9 v% [原文链接:https://blog.csdn.net/qq_56591814/article/details/126633913* ], q, `( j9 J( A% O" K" ?$ R
1 D% E& v, P0 T" i6 h, _+ r
' {9 Z# a3 X; x. t6 S2 F
|
zan
|