- 在线时间
- 1630 小时
- 最后登录
- 2024-1-29
- 注册时间
- 2017-5-16
- 听众数
- 82
- 收听数
- 1
- 能力
- 120 分
- 体力
- 563328 点
- 威望
- 12 点
- 阅读权限
- 255
- 积分
- 174221
- 相册
- 1
- 日志
- 0
- 记录
- 0
- 帖子
- 5313
- 主题
- 5273
- 精华
- 3
- 分享
- 0
- 好友
- 163
TA的每日心情 | 开心 2021-8-11 17:59 |
|---|
签到天数: 17 天 [LV.4]偶尔看看III 网络挑战赛参赛者 网络挑战赛参赛者 - 自我介绍
- 本人女,毕业于内蒙古科技大学,担任文职专业,毕业专业英语。
 群组: 2018美赛大象算法课程 群组: 2018美赛护航培训课程 群组: 2019年 数学中国站长建 群组: 2019年数据分析师课程 群组: 2018年大象老师国赛优 |
! |9 R, `7 G( `9 j, d0 o+ E( H! c
0 d2 a, }" g# j E/ X$ p0 Y3 l5 t B7 Z, N$ ?
文章目录6 p* `- w8 N3 c# v# y5 ~* A4 M
第八章 文本数据
/ f$ Y; x9 f+ i3 \1 |% n8.1 str对象: F( V) Q' `9 U) Z
8.1.1 str对象的设计意图! ~ B" {0 ?0 e% O4 j1 c
8.1.3 string类型
" A" z' M2 k' H# y8.2 正则表达式基础
/ E* d2 Z" A8 x% b0 R8.2.1 . 一般字符的匹配. ~1 n- S* W+ U6 j8 \
8.2.2 元字符基础
7 }" l4 q1 U& J8.2.3 简写字符集
1 b6 F U* T- S; I- z8.3 文本处理的五类操作
8 |- q7 T$ K+ R: D5 [1 S0 Q7 w* d8.3.1 `str.split `拆分
' S f7 p' P; r- b; Q* A% e$ p8.3.2 `str.join` 或 `str.cat `合并7 a+ ?% I" `' z5 j6 N! U5 {' p
8.3.3 匹配5 `1 j2 ~1 e% b
8.3.5 提取, }# s1 Y* ]2 ?. a6 }4 x
8.4、常用字符串函数
; o! k# ?3 f4 F; |* B% `8.4.1 字母型函数+ l0 a+ ~' F& U5 u3 `: ^
8.4.2 数值型函数
( M; t r) O, b6 N8.4.3 统计型函数
( f; x7 H* T4 {8.4.4 格式型函数' i, U- C0 E1 a/ [+ [( }
8.5 练习- U- _( m# X5 m+ ?$ W
Ex1:房屋信息数据集' G$ }. }6 [7 [+ O# ]/ T' r8 Z$ X
Ex2:《权力的游戏》剧本数据集# X5 ^0 q. q2 ]/ D
第九章 分类数据+ f3 T0 |8 W2 v& C' N
9.1 cat对象' u7 }5 u* r6 w7 t
9.1.1 cat对象的属性
% l0 B7 _8 x& E f5 U: L2 D9.1.2 类别的增加、删除和修改) V" d2 {! \ B! [7 G8 ?
9.2 有序分类
% `( y# |6 f5 p; Y+ P9.2.1 序的建立1 n& [$ |; ]% D$ h
9.2.2 排序和比较
9 w9 O9 j, l* T9.3 区间类别# w u) [! A9 ?4 e' B- i- Z9 W1 U
9.3.1 利用cut和qcut进行区间构造; s! `% F G; h! l0 T8 u
9.3.2 一般区间的构造. k+ v4 }9 t; o
9.3.3 区间的属性与方法
: t4 `- M1 k4 r9.4 练习
' A0 u/ I6 P/ ^' e" x% p# dEx1: 统计未出现的类别$ t0 W# |( l" r% `% ]: H$ s
Ex2: 钻石数据集
: b: ~2 `' J# w, n1 S! I0 p( t第十章 时序数据
6 K3 n5 Y! i2 b9 L: K3 ^' ^10.1 时序中的基本对象
' @' U- |- ^, \- d10.2 时间戳9 e Z7 v; C1 u
10.2.1 Timestamp的构造与属性
9 z. p- ]; Z; d7 y( u3 l' D' T/ [10.2.2 Datetime序列的生成# T$ Y7 X0 Y8 K+ k' }; r8 w
10.2.3 dt对象0 K& W( r* ] |8 a4 ?
10.2.4 时间戳的切片与索引7 |( X, o' \! H0 O8 A3 X2 G
10.3 时间差8 {' F" s+ o! I0 _1 u
10.3.1 Timedelta的生成# @# b2 v! r0 m( i6 ~3 a
10.2.2 Timedelta的运算
+ `" @8 j; i5 o10.4 日期偏置6 Q9 C' i+ W* f( w3 [% J
10.4.1 Offset对象
, u& `8 z: w( ^2 k" T+ U8 w( w* j10.4.2 偏置字符串
6 \1 B1 x# P9 k) k x0 k0 I" _1 B10.5、时序中的滑窗与分组/ X; b) G/ u/ M. P8 H
10.5.1 滑动窗口8 v% \3 k& v5 b8 K
10.5.2 重采样% z8 Q( F C3 B% e+ M$ w
10.6 练习
3 M* B3 R6 @" q' v8 u8 {" cEx1:太阳辐射数据集& `, g+ w6 u6 ?
Ex2:水果销量数据集
) c: s* k6 e9 m 课程资料《pandas数据处理与分析》、github地址、讲解视频、习题参考答案 、pandas官网
2 z5 X" s% g4 ~8 @传送门:
9 l- z9 o6 j- @% |$ }) Y, p5 `" \$ r0 @& ^
datawhale8月组队学习《pandas数据处理与分析》(上)(基础、索引、分组): Q3 l: x+ _7 @, ?9 h
datawhale8月组队学习《pandas数据处理与分析》(中)(变形、连接、缺失数据)5 ^. {; e& t, n/ {1 K0 I4 I
第八章 文本数据
: ~" ] ~2 S% `0 Q7 P: w( [8.1 str对象8 s5 L: b6 K% z1 k! f8 U
8.1.1 str对象的设计意图
4 X2 ?: T2 U9 ]- ~- J7 S str 对象是定义在 Index 或 Series上的属性,专门用于处理每个元素的文本内容,其内部定义了大量方法,因此对一个序列进行文本处理,首先需要获取其 str 对象。在Python标准库中也有 str 模块,为了使用上的便利,在 pandas 的50个 str 对象方法中,有31个是和标准库中的 str 模块方法同名且功能一致,例如字母转为大写的操作:
9 ?9 k! L) @! w7 w, p( y; [# b$ C; i) o- `6 _
var = 'abcd'% p( q" V2 K, q7 \. s* Q
str.upper(var) # Python内置str模块
5 V6 k& L& N6 O: x& FOut[4]: 'ABCD'6 ^( z) T7 }$ h
& ?0 H% U$ }, ^" _! ps = pd.Series(['abcd', 'efg', 'hi']); n4 ]0 o" M3 o9 A
) R6 q; b$ F/ v/ X @9 R
s.str' N) [0 s; G* n5 }
Out[6]: <pandas.core.strings.accessor.StringMethods at 0x2b796892d60>
: O# [9 O4 Z: x9 A2 Q
) y8 X# D( p$ Y! G- ?s.str.upper() # pandas中str对象上的upper方法
3 A; I! Q2 ]8 HOut[7]:
3 \) v" B& _) {7 P0 ABCD
$ M$ J; L/ ^/ b5 X1 EFG/ e: \: t7 `+ w" C( R
2 HI
+ z9 [) @- d. Y- t$ fdtype: object0 Y, _& C6 @$ n/ J
1* p$ T* i" n# ^* H8 {
2! W3 ?$ C1 l& l; o5 d* c) m
3
- {7 | p2 e; B0 N4! P; x, _" n% Y( Q L# S
5
# i( ^5 @- {& `6
# g7 f* l. u5 z7
% R3 l4 [' J# Q1 \$ Y. {* {8; d8 b ^( \0 T3 B. ^
9
) j0 Q. b7 v6 V10
( k: I4 l9 j/ d, U11; {4 C! f Y7 O/ m" a) T$ q
12
$ i1 O0 X- ?+ e13
+ ]0 J1 f8 ]& P4 V7 F5 |14
( x1 W, y1 w ~& @5 }3 N( z# w15& g8 e8 ^% K: Z/ I
8.1.2 []索引器
* H) G) z) c" a" o7 B C( [/ I8 a 对于 str 对象而言,可理解为其对字符串进行了序列化的操作,例如在一般的字符串中,通过 [] 可以取出某个位置的元素,同时也能通过切片得到子串。& E" g; Q+ ?* m. X: n) d; R6 K
pandas中过对 str 对象使用 [] 索引器,可以完成完全一致的功能,并且如果超出范围则返回缺失值:
5 U. |8 b* Q' ]; o/ P2 X
5 k/ a' J1 `2 }4 N) d, Js.str[0]
! R% R* }& x2 T. WOut[10]: / a, i$ u( @8 Y6 w4 ^
0 a
6 ? R' r' _8 e1 e6 T0 ? f2 H8 K/ ?- M9 _% y* h0 Z
2 h
' b! Q+ T5 \8 X# W1 k1 v; z2 Qdtype: object9 M* G6 g( @9 G' n
5 G0 j9 ?* r" {# ds.str[-1: 0: -2]
# N. s- O) O( F7 Z1 _Out[11]:
& C! M" \5 x( B7 C$ \$ o0 db
9 P$ H! C. l: o7 r# }: Y" ?3 y' G1 g! ]4 @# m* A" C2 z8 g1 J
2 i
0 A$ K3 x* n0 m( D" ]5 l+ adtype: object- }" e# z$ ?- J& \; r4 i
; w9 y, [9 [) i: C( x" O1 O
s.str[2]( j0 ~9 D& b$ I) |: \
Out[12]: & b5 F4 h4 K8 B- H% V
0 c! ~: `" y( J) G! I
1 g
( v7 C" G) @+ ]4 W" |5 j" S3 ]7 n2 NaN
. R; X6 y$ A+ ^. f7 T& F) odtype: object6 ?& R" I( _) K$ s+ V
, g* Q% _8 U1 D/ M1 F7 U, B1
0 W' F+ M) v' a- G# S9 h& H) l2( w5 L1 `+ k5 C; {( c
35 {) p F; b1 N, |% Z' u0 {
4
' V V) E3 o; {$ e6 N% H56 w# A% I8 C# c4 Z4 a3 k
6. N$ c! Q1 o6 M5 I& t& d) x2 V
7
$ ?$ z+ [& L: z, |* r83 G8 V" l5 x+ S3 F
9
2 E# p d4 {8 r0 s' j( C10
0 a& R1 {* w4 H11
1 r2 n# ?% D2 Y+ x8 f! J126 q+ G8 Z. @& H1 E
13
. ~% V5 K b8 y8 t' X147 i% D* U# i$ v! O0 A
154 m2 }+ b+ Z$ R: ]. i9 @
16( n' F! p. X: _" {6 |9 }
17: q- ^/ H; a6 s/ H: k) _
180 F2 Z# y6 C* C2 T/ Z9 k+ m
19
( v! i3 F3 _! ]' b. d20
# F, S5 T+ [* w4 {1 n) rimport numpy as np
, ^* F7 x/ g% d& Yimport pandas as pd
9 N/ t% t0 P; f& b* ?
1 G& L! Y& Q# l! k' ^$ y' ls = pd.Series(['abcd', 'efg', 'hi'])
6 `3 S1 Q/ {4 K; O% J+ S0 P9 as.str[0] ~; ~- Y$ C% f: v3 N
13 ^1 b7 X5 z7 I$ r3 k6 `% h
2
) Y, U, \! X2 T3
* E1 ^' a9 Y! E' q+ u4
7 l6 s$ x( S2 O, {8 b7 Y4 K9 L5% a% H6 X% e" m- D7 v
0 a
& p+ L' O* R; _) b1 _ F1 e
" c- ?+ Y3 g4 |8 N5 e# G3 x2 h
. r5 M8 {& Y$ i z ^dtype: object
. S8 Y1 o5 V0 |( [" Z9 a+ S1 f9 w10 o% {- _( w' j4 b6 X7 j
2
5 c) ?1 B7 ~6 f4 w6 z35 b1 _6 t+ N3 s" Y# `! ?& [
4; E0 B. g$ ^: s1 t v# U- M% e* S
8.1.3 string类型
7 }! Z; ]. c7 T( u/ h' n 在上一章提到,从 pandas 的 1.0.0 版本开始,引入了 string 类型,其引入的动机在于:原来所有的字符串类型都会以 object 类型的 Series 进行存储,但 object 类型只应当存储混合类型,例如同时存储浮点、字符串、字典、列表、自定义类型等,因此字符串有必要同数值型或 category 一样,具有自己的数据存储类型,从而引入了 string 类型。4 O" E/ y5 [7 S6 g" N
总体上说,绝大多数对于 object 和 string 类型的序列使用 str 对象方法产生的结果是一致,但是在下面提到的两点上有较大差异:) m" L3 C1 C [' @: J
; l% ?; w8 M! K) a+ ]7 h二者对于某些对象的 str 序列化方法不同。
/ N! Q- I: o; o' q, L+ N可迭代(Iterable)对象包括但不限于字符串、字典、列表。对于一个可迭代对象, string 类型和 object 类型对它们的序列化方式不同,序列化后str对象返回结果也可能不同。例如:
# F" S- D( f8 |8 ^$ vs = pd.Series([{1: 'temp_1', 2: 'temp_2'}, ['a', 'b'], 0.5, 'my_string'])
# V( J( b1 v& {! |# N' s$ L: zs/ }. F9 g% s4 e9 Z, C7 _1 Z: q/ K
1
; ]5 B& D& g5 J2' N2 e* o, a/ u7 M8 O0 ] e
0 {1: 'temp_1', 2: 'temp_2'}" b! X; ~/ L* U4 [% v+ t% {
1 [a, b]. P( n7 d$ j1 s( _6 f( ?
2 0.5$ C% ~5 t+ h7 t* ~1 ^
3 my_string
4 |8 J0 J$ d9 u0 I" Y8 ?% R; v Cdtype: object; A" J, t; U j2 g, M
1
& u( Y: v) o1 |7 m. g2
. j( J0 i4 p; r; w) d9 `3 R38 S4 q: [; W/ e0 K0 j; Q
4- ?/ W3 R. w9 R$ B2 N k& r
5
+ r' r+ n) s8 Q; as.str[1] # 对每个元素取[1]的操作/ \: [0 u; V4 ^5 g8 s7 O
1- W1 N- m) r2 S5 N! K8 U) o
0 temp_14 n: S" Z" I5 E7 v
1 b
8 r) F: _0 c+ x1 D2 NaN: O7 C" J( f$ d: x! ?5 D* z
3 y
+ l! s& G& E. U H2 @, p6 h Bdtype: object: c6 }4 ~& B( r6 y! h
18 z( g% m- R* D* a. T
2: f2 A3 O! `6 d: l i
36 @+ G6 ^* N8 B
41 O- X4 z, a/ t" D- {: R$ y) A
5. k5 R& g) n, J; L) R6 O W& M/ }% a* L
s.astype('string').str[1]
2 Q7 Z) m! s T1 e2 K1
' r8 P5 n! L6 G3 b8 C9 D0 1
0 @" t* _. f& x, J1 @& _6 j1 ', y2 H+ d+ i, Q. I$ P
2 .
_6 x- u+ {1 q: g3 y
6 u |6 |; k5 x3 e2 L" |dtype: string
# I6 ^4 {' w, o1
% I- u! g+ K/ Y- k; d23 j$ N( ]! A! r6 c. U, D
3
' P. N; S/ }2 s4
5 E# Q5 f4 Z- {2 b- T" @5
) C# i. [, ]8 b6 A" \除了最后一个字符串元素,前三个元素返回的值都不同,其原因在于:- n6 B9 }% L5 s+ `- C2 L
4 E2 Q- T, r6 H& V% \, k
当序列类型为 object 时,是对于每一个元素进行 [] 索引,因此对于字典而言,返回temp_1字符串,对于列表则返回第二个值,而第三个为不可迭代对象,返回缺失值,第四个是对字符串进行 [] 索引。
9 y0 Z9 h9 f+ I$ Cstring 类型的 str 对象先把整个元素转为字面意义的字符串,例如对于列表而言,第一个元素即 “{”,而对于最后一个字符串元素而言,恰好转化前后的表示方法一致,因此结果和 object 类型一致。
9 Z3 Z. ?& A+ R8 qstring 类型是 Nullable 类型,但 object 不是
x/ _/ E! c4 [2 v( d0 x Q% z 这意味着 string 类型的序列,如果调用的 str 方法返回值为整数 Series 和布尔 Series 时,其分别对应的 dtype 是 Int 和 boolean 的 Nullable 类型,而 object 类型则会分别返回 int/float 和 bool/object ,不过这取决于缺失值的存在与否。
% u! `; g- F' N4 E' m$ r 同时,字符串的比较操作,也具有相似的特性, string 返回 Nullable 类型,但 object 不会。3 k' Z0 O6 ^0 u: D
s = pd.Series(['a'])
6 h ^6 g( a# r; _
+ \$ C- ^% _4 `s.str.len()
: Q/ i' }+ p jOut[17]:
: m0 Y! O2 Q0 v1 _% \0 1
: L8 R @1 \% H D. x/ ^/ Hdtype: int64( q1 q" x8 e' a j6 `' O# }
& {7 z/ ]' d4 As.astype('string').str.len()
5 h+ }9 V% o' {6 F% r. qOut[18]: 6 p6 T7 B2 R% S5 A* I5 g3 j* a
0 11 E0 I* ]7 j$ z: s
dtype: Int64
; Z f! o+ H% h: H! v, Q9 _! Z! J* Z% c% d7 b& {# `
s == 'a'4 P' U; E! d/ P( p, ^, \
Out[19]: 8 j. F8 x& u; h8 k b" b7 c; }0 y
0 True
" c! i" k% W7 K) ?; Adtype: bool3 w- L0 \1 Q7 O8 M; t0 t
: j) A- Q3 m ]6 D% c1 F3 P% fs.astype('string') == 'a'
3 I# O; Q$ g3 x- o1 A$ U/ q0 P; c( yOut[20]: 6 }: ]3 D8 d8 Q, i
0 True
+ x" s& }4 ], u4 J* ?dtype: boolean. I. d$ ^( F4 I6 b
, h3 E' } A7 x* u) Z" {
s = pd.Series(['a', np.nan]) # 带有缺失值
4 \" P$ O- O2 k; V! ~, a: g
3 x# s3 f, O( ks.str.len()
9 z% y; J, ^% T1 z- B4 ?9 i/ kOut[22]:
u: G3 n! G3 E( x2 X4 w. k0 1.0
' R. A( i, X. J4 p1 NaN$ }/ d9 D! p: T+ A) N( _* ]
dtype: float64% B3 U [( q2 D
& e/ ~% C/ ?0 ?% @8 ]8 ~2 `s.astype('string').str.len()! p2 a$ ?. c5 m; y
Out[23]:
" Q k& }3 d) _6 _" X0 16 |- \! j: A: C9 ^6 B0 V. G2 r
1 <NA>
! X" W0 m" W8 A7 O; `- z6 U/ `dtype: Int64; { V6 Y3 R8 d: i4 w e) c
4 o$ L _5 w+ ?$ V: Y) b, Z9 c& \
s == 'a'
' l' Z: Z( O8 {Out[24]:
5 R" L% v7 x# s- C! l- i4 \$ |" m7 L0 True
& k; c$ Q4 L* Z) C9 r) r: w% J1 False3 S5 G; c0 G; Z, O
dtype: bool7 V" ]" S% B/ c1 x+ B8 F5 X
& \* p% n6 h$ I& @( x7 |5 _: I
s.astype('string') == 'a') y; b# Y p5 d- k. L
Out[25]:
1 @( E) s, t' S' `0 True
# Q! d) Q7 h* z" V9 B2 c w1 <NA>
; Q) @2 x( ^7 C B) X: Cdtype: boolean4 U* _6 x& J" R4 l# I
" F; s6 P+ ?7 a1 z8 w
1" p# Q* u. w$ }& y
2# ~- r2 w* O4 R/ P
3" A. C' e# D, y
4
d" f, \! m! l3 R$ E. a4 e5
7 |/ L9 g6 i! D- O- @6
8 v% @/ ?$ G1 i' r) G; `3 z+ {% i7
2 z# N4 o/ P8 g2 J9 z0 P8& U" T; ^: D' k5 ^9 `3 s( T ?
97 K5 G/ S( F$ a* S! G6 Z
10" k6 U* Y% L+ m# M5 f4 w1 T
11
5 n1 H8 L. l" u4 p. d) u12
6 @# A9 l0 c& J u$ u V, H137 J) ~% J0 A- X* W) W
14
3 e# A+ ~ s) I, V2 T15
( G7 R$ u) \6 a" k }: ^- |4 k a16 U( o' E. I4 x: @
175 Q3 x2 P! E" z0 ~8 q. X0 V/ J
18; I$ n, t0 U6 L& Y: y4 i5 m
190 s0 q3 w( i, d, I
20
4 |6 r# L) b6 u* k% b21. ^7 s% }) d. i6 Z
22
. V R6 V# h a23
- v6 S$ X! a' l% q24
" O9 U$ ~' D7 H Y6 @& D253 D0 P( U" }' d5 B2 B0 z
26
& Z0 s: Z7 X. v0 v+ o! ~27' R8 o6 `' |* `5 y& @2 w" ] d
28
( ^# |% F" Y* Q3 W0 w4 q29
/ n s. \/ u" h* M4 H) F' x8 B0 _30
( d) m/ k4 O7 d& M6 D1 _31
& S- P8 d( L, }32
) B6 j0 e, \& ]) p1 o) I33/ M: ^) b0 h% W! ~; w
34* |. s0 a# m' e. s# ]8 H
35$ ^( b. x; k: {5 e- ]
36. t4 L& M0 T8 O- }+ p8 G/ l
37- f( n5 S) }# C* z' y5 t! u
380 ]; }: |& i4 b% ^. Z: q! n- D
39
# V- C! \# H" x4 i) [8 [8 u400 j; @: Y7 j5 n- Y
416 i0 O& J9 j' @: k
424 n. k* G1 T, k5 e8 n; k
43
4 e% K6 L2 J* g; `444 S$ K- m N# n
45
* g% c; N7 n/ d: ^46, W) t4 R& i- d) @- G. j9 k
47# z7 ]. y8 y" D: X6 Z
对于全体元素为数值类型的序列,即使其类型为 object 或者 category 也不允许直接使用 str 属性。如果需要把数字当成 string 类型处理,可以使用 astype 强制转换为 string 类型的 Series :7 s) d6 V, ]6 C3 S4 T- W t
. V# l6 K6 l$ o0 l7 j$ J
s = pd.Series([12, 345, 6789])+ H1 H' H, v+ Z0 C" j
7 l6 O- s/ i7 c$ C% as.astype('string').str[1]
P4 Z8 V! X9 O) J& v+ |( NOut[27]: ; ?: D6 h% [3 y0 B1 N4 b
0 2* L" u7 H3 P# A1 j& z8 }
1 4 v. c7 h8 j8 o# i* v( n% ?3 Y5 n
2 7
( P2 `- o9 k. p) I' }) l p4 gdtype: string/ \8 G- ]6 r( z% G
1
1 D# G: n$ u" ~+ @2 ~5 {) ~7 X2
O8 Z$ V0 s. f7 t+ j B: `$ t( C' j3
- L0 c3 a" \8 I9 v% y' h _4
& `2 m* _ F/ s5, r2 R1 v. e+ Z* Y5 j& T
6
$ {1 A M! ?2 h- p% m7
: i- ?+ Y5 G/ Y8 z! ~* y8
+ ?, R0 G5 v4 k6 _, N$ ]9 B. F: ^5 P8.2 正则表达式基础% F0 J1 Z* V! ^) C( u9 ~
这一节的两个表格来自于 learn-regex-zh 这个关于正则表达式项目,其使用 MIT 开源许可协议。这里只是介绍正则表达式的基本用法,需要系统学习的读者可参考《Python3 正则表达式》,或者《 正则表达式必知必会 》这本书
' f! t; ?! f% f% L& O6 k6 d- s L8 \1 }% x
8.2.1 . 一般字符的匹配
2 h. v# L) }$ t& [+ ~1 }正则表达式是一种按照某种正则模式,从左到右匹配字符串中内容的一种工具。对于一般的字符而言,它可以找到其所在的位置,这里为了演示便利,使用了 python 中 re 模块的 findall 函数来匹配所有出现过但不重叠的模式,第一个参数是正则表达式,第二个参数是待匹配的字符串。例如,在下面的字符串中找出 apple :
( D8 \4 A) O* H' W/ @( W$ b! K3 J5 b) y" n
import re
$ F; F S2 f# U; h" i9 |% F0 D9 d
, Z, M4 y+ l# j9 o- B& g5 jre.findall(r'Apple', 'Apple! This Is an Apple!') # 字符串从左到右依次匹配, ~& Y- A) N; C: b0 F% u, m
Out[29]: ['Apple', 'Apple']
( O3 Y2 P2 w' p) S r5 u6 b0 ^1 Q) e% w* H% c- {+ y1 Z
2. f( K7 e& K+ c4 T q1 E. V6 C
3
+ q! X q8 A) f K48 v+ {8 G! W$ [6 b- A: C
8.2.2 元字符基础
5 v/ ?4 i, |2 f) W7 F( d元字符 描述
* d0 r$ h! o) s5 J, P# p" A. 匹配除换行符以外的任意字符2 z, F5 x) r* z" K8 ^0 g
[ ] 字符类,匹配方括号中包含的任意字符& W3 q% i% ?. y, u5 a
[^ ] 否定字符类,匹配方括号中不包含的任意字符
" A- v1 U' }) ^% C* 匹配前面的子表达式零次或多次
8 Z' e- G7 n4 l6 b' h1 Y/ \+ 匹配前面的子表达式一次或多次。比如r’d+'就是匹配数字串,r’d’就是匹配单个数字% S8 |+ a# E0 D* f8 D( m
? 匹配前面的子表达式零次或一次,非贪婪方式8 z, h: a$ _8 t( m
{n,m} 花括号,匹配 n 到 m 次由前面的正则表达式定义的片段,贪婪方式9 g% T$ t# L" U& I h
(xyz) 字符组,按照确切的顺序匹配字符xyz
3 T4 P$ k& \7 c* || 分支结构,匹配符号之前的字符或后面的字符$ V/ ^! \4 V$ Y
\ 转义符,它可以还原元字符原来的含义
5 O8 o9 A& g2 G" M Q6 T; D7 v^ 匹配行的开始; @2 r" t! `9 c: _1 p% n& q" p. c
$ 匹配行的结束
* ]) U7 y: N/ [: ~% x% R" simport re
3 R7 F5 Y6 r4 K6 Sre.findall(r'.', 'abc')4 D! @* j) T1 S0 G2 s$ d
Out[30]: ['a', 'b', 'c']! L! {- y, `3 ]7 n# [6 |
# @( @" s( ?0 N* U+ m6 ]re.findall(r'[ac]', 'abc') # []中有的子串都匹配
$ b; ?9 {" k* uOut[31]: ['a', 'c']( X6 V( E! Z% L1 u3 e: v
7 p" J% p* W7 M
re.findall(r'[^ac]', 'abc')
1 B! v+ ?! \; Z* COut[32]: ['b']
$ m/ x2 |! u) q% H% a3 M5 w! z( L0 b" V
re.findall(r'[ab]{2}', 'aaaabbbb') # {n}指匹配n次
7 D( s# o2 G) C; LOut[33]: ['aa', 'aa', 'bb', 'bb']. q8 s( o- E( v, m8 E
o( p1 B e! t
re.findall(r'aaa|bbc|ca', 'aacabbcbbc') # 匹配前面的或者后面的字符串
2 ^; @0 R# h, ^7 X0 ^3 F& bOut[34]: ['ca', 'bbc', 'bbc']( R) n" J; e0 X2 r- {
: P5 s, {$ f2 G3 ?! D- {
# 上面的元字符都有特殊含义,要匹配其本来的意思就得用\进行转义。
. u% }) W |/ `; T"""
. _) g* B, Y% G/ c( q1. ?匹配的是前一个字符,即被转义的\,所以|前面的内容就是匹配a\或者a,但是结果里面没有a\,相当于只能匹配a。' O" G# A1 c7 F/ Z% M0 s
2. |右边是a\*,转义之后匹配a*,对于竖线而言左边优先级高于右边+ o4 J/ E" M& y' F
3. 然后看目标字符串aa?a*a,第一个a匹配左边,第二个a匹配左边,第三个a虽然后面有*,) f, b, J5 n6 \) R1 _
但是左边优先级高, 还是匹配左边,剩下一个a还是左边,所以结果是四个a: t2 V# `$ x" X! y k
"""3 \7 ]1 Q9 t1 n+ a9 Q
5 L1 N6 @) X; F6 n7 H4 h% N4 t2 D6 T& T
re.findall(r'a\\?|a\*', 'aa?a*a') # 第二次先匹配到a,就不会匹配a?。a*同理。' Z, c8 M/ q* q! V9 {$ z& s4 B0 |
Out[35]: ['a', 'a', 'a', 'a']
7 f+ q( _1 I$ M
. x& v7 x0 I& F# 这里匹配不到是因为目标串'aa\a*a'中,\a是python的转义字符(\a\b\t\n等),所以匹配不到。
3 K9 ?0 `# Z) `1 B: r$ A( P. ~# 如果是'aa\s*a'之内非python的转义字符,或者'aa\\s*a',或者r'aa\\s*a'就可以匹配到\字符。# ]( Q0 D5 f3 {7 W; _2 w
re.findall(r'\\', 'aa\a*a')
: A9 M1 J! n4 b- ^" e; Y3 Y[], M! i4 n4 N( q+ O5 M# B
6 G2 j6 Q O+ ere.findall(r'a?.', 'abaacadaae')! |& F) h# ?) I2 s( k4 p
Out[36]: ['ab', 'aa', 'c', 'ad', 'aa', 'e']4 @2 c4 b5 ^9 t" Z
% z }8 t. [" e' B! q9 Rre.findall(r'(\w+)=(\d+)', 'set width=20 and height=10') # 多个匹配模式,返回元组列表, j, |& M* H7 H" {4 V2 t3 ^
[('width', '20'), ('height', '10')]) C9 Q- I9 @+ S$ t0 p: z j
0 h! |5 S& `( @. C" o& i) x
13 A8 g1 L) D1 n* ]. [+ j" l T
21 d2 E: h) a! t1 W t4 w
34 C S" U# y6 B$ \$ _! H% r
4$ r+ C6 M4 G" ^5 P0 ]" H- I a/ |
5( Y% W# o1 m( Q8 \# v
62 [1 l$ q# W; I! T
7+ j0 T( l% C/ d: y1 A
8. p0 [8 ^8 ~8 a
9
) e( ~; V& O$ |4 m109 Q1 b4 ^$ k8 W! C
11
; J* K/ O. J# Q12; t, ^; I+ `% [- m: ?1 O. X) d7 r
13& R* [2 Y9 n6 M
14
6 c( X" T- T" k, o0 @9 k T15
. T4 f3 N, o6 [" H! Q3 \- ]16
4 J# u# k; E6 o0 ^4 _1 d17 t& o9 s' d' ~5 x! q% A
18
4 E2 j3 ~: A! i- H7 v O E& ]' O199 C9 M$ A( e% V' D$ a$ D# B( J$ R
20; z+ `2 q* E4 U3 O% g/ V
213 z5 I( Z& d7 e7 c4 u, p
22/ U1 q- A5 s5 D' V! f/ r* S5 [
230 d* j e: x3 o9 j( H+ C
24
) K4 V( B+ I A/ H25" N2 C; A& q0 ]. _* ~
269 T. ]/ B M/ A3 i) B2 m2 o% E3 z
279 W, ?& s; f# \
28
0 F6 f. S# C$ e9 O0 r, w& g, C29( k7 a6 W' P, `0 _. U) v3 T* k
30* }1 W/ Q6 s# L6 V: P
31
# b( @0 C8 M' j; {! h1 B32
6 v8 E: Q3 c/ j; \6 b338 | [' L& y7 o. y r
34
. Y6 a2 K& ?' z4 z9 y35) ]/ P% ?/ l p" I Z" Z9 Z3 J; _
36; ]$ ]. h: v6 @7 T, R" K
37 J' J7 n F& V
8.2.3 简写字符集, ` H- O/ f( E1 u2 Q
则表达式中还有一类简写字符集,其等价于一组字符的集合:3 X; p0 [- G9 g3 o
S: N* B/ {: ~2 ~! I2 M, C$ D$ f" ]3 ^
简写 描述8 I5 b: M; ?" @8 O$ q
\w 匹配所有字母、数字、下划线: [a-zA-Z0-9_]. ?1 }3 c$ c5 j! j
\W 匹配非字母和数字的字符: [^\w]
8 `# e7 o4 C; S4 M; ^: R# O\d 匹配数字: [0-9]
7 {, B& _9 a5 a- O/ [6 n1 N h3 [+ \- Z\D 匹配非数字: [^\d]
4 @9 C3 I0 ?, Z% S* p4 p7 _1 s\s 匹配空格符: [\t\n\f\r\p{Z}]
( B/ \) G' f0 C' m. T+ n\S 匹配非空格符: [^\s]
2 p, o" t7 }( w. p E" O\B 匹配非单词边界。‘er\B’ 能匹配 “verb” 中的 ‘er’,但不能匹配 “never” 中的 ‘er’。
8 |# f4 r/ }( \9 V9 V( ~re.findall(r'.s', 'Apple! This Is an Apple!')# l4 c# f. d9 X" {& D- C3 f
Out[37]: ['is', 'Is']1 k0 W/ e& D# Y6 ]5 k/ s, q1 b
! Y( e+ R" V1 n) T0 Y+ J ^; D
re.findall(r'\w{2}', '09 8? 7w c_ 9q p@') # 匹配任意数字字母下划线的组合,但必须是两次! Q- v* t8 e2 r4 x& c
Out[38]: ['09', '7w', 'c_', '9q']0 ^8 B" C4 C1 a
# z; V& M2 w: ~+ @" w0 kre.findall(r'\w\W\B', '09 8? 7w c_ 9q p@') # 匹配的是两个字符串,前一个是任意数字字母下划线(\W),后一个不是(\W)$ p6 J+ L$ z! b. `! a6 A0 Y' j/ O1 Q
Out[39]: ['8?', 'p@']: |, E) o9 _/ d8 c5 m
; l. R3 O6 A2 m5 Dre.findall(r'.\s.', 'Constant dropping wears the stone.')
0 @0 l( b3 x7 _9 t1 T! z t5 hOut[40]: ['t d', 'g w', 's t', 'e s']6 }" j) }/ Q- L! \4 d
6 U' p9 z. { `, F9 r
re.findall(r'上海市(.{2,3}区)(.{2,3}路)(\d+号)',+ J6 F, H2 F' [8 H
'上海市黄浦区方浜中路249号 上海市宝山区密山路5号')
/ K0 o; `' t9 r2 l8 X$ l9 [9 x( q: Z& J6 S* s) z: W
Out[41]: [('黄浦区', '方浜中路', '249号'), ('宝山区', '密山路', '5号')]8 j$ u/ O% |4 b4 c
% W5 ~' Y, l. K* z1
6 d, x, C* S( B26 }; _) v7 x- i, q
3
$ {0 O: l* ?# l4: s1 v/ W# K' u3 c' A7 }
5. r! D5 K& V3 U1 K; M7 d4 e. ]& `$ i
6" V: x- x ?$ b' q' \: M
7
/ m' ?& U6 b+ V# k8 X& v8 K4 o+ K# A) X# y
9) \/ x0 m; t2 {3 P
10$ m9 K6 a+ r. Q* T. ^+ s9 \$ a
11
4 p+ y2 w4 E" ^3 g7 u1 E/ n12; r7 Z7 ^, \! I1 S
13& c* u8 [6 s; ]; p; c9 X) B
14$ D. S/ t/ {+ n# D( n
15
8 h! B4 [8 l' g2 h16( u) ?* y: q& C/ c: N) b1 d- D' T
8.3 文本处理的五类操作" a7 k! y/ H$ O" S. d4 j
8.3.1 str.split 拆分+ Q. B. a( a) s+ i: j' P
str.split 能够把字符串的列进行拆分,其中第一个参数为正则表达式,可选参数包括从左到右的最大拆分次数 n ,是否展开为多个列 expand 。: B/ C3 I- e. N+ x# _, ^
7 e0 x. Z7 `1 C) H
s = pd.Series(['上海市黄浦区方浜中路249号',
" p8 x: s, V# J3 L) ]+ X '上海市宝山区密山路5号'])8 W& {% {9 m; o% R u, u
4 d! M3 w# q3 O9 H1 _& h
& a0 \4 S5 y( t/ Ws.str.split('[市区路]') # 每条结果为一行,相当于Series
4 R# }9 v8 G5 C. g6 c, m0 i- XOut[43]:
( E, v$ \) E' g+ P/ [) k' s1 O4 t) n0 [上海, 黄浦, 方浜中, 249号]( N1 {1 S* V* z- e8 J! I2 h
1 [上海, 宝山, 密山, 5号]
4 x6 x7 q: ?0 i9 Z v% l6 Gdtype: object( t& Y6 N0 s: N4 N: z
: @3 E: S/ X$ p3 S; t* S, c
s.str.split('[市区路]', n=2, expand=True) # 结果分成多个列展示,结果相当于DataFrame' Z2 O6 v) f( H6 _( G6 S" a
Out[44]:
! U R) U. Z5 N9 x2 e3 }9 z4 ?: B 0 1 2
5 q: U6 ^+ h! \0 上海 黄浦 方浜中路249号
9 n6 ?; m( q8 v6 O7 O1 上海 宝山 密山路5号
# Y+ F, f* ?; V+ a4 @ S5 `. J$ E1
% Z/ X# Y6 K, c- Q' Y& |: k2; E; S4 T& Q# q8 r3 T9 u: Q* n
3* i1 a6 f) }. c6 K5 o7 M
4
, S7 c% x# }6 P- R5
5 Q8 R/ _; c. l* \8 u( X* d- V6
# Y& _0 P8 n" y0 _/ ~7 ~7 T7
( l$ O! K+ }% N- P% s8
' x1 s4 l, Q; s$ P; o9
5 r# n7 y1 D% k; J% q$ _+ B- s10
9 E# L+ w7 |; T; p11+ x4 d! U, [! k" e
129 n4 T. X- Z* P$ N6 F( E9 ?* \
13: J, K3 w: d; x. x7 v7 |
14
/ Z G/ k/ u: w; E( A; n15
! P0 q. \% V3 Y+ t 类似的函数是 str.rsplit ,其区别在于使用 n 参数的时候是从右到左限制最大拆分次数。但是当前版本下 rsplit 因为 bug 而无法使用正则表达式进行分割:
! p+ B, |0 Q, h# [! \! f" ? z% n" p* x4 f# P- v$ R& K
s.str.rsplit('[市区路]', n=2, expand=True)
, [$ {3 C( _ u: S$ j8 LOut[45]:
, d; R3 A4 b( z8 D- h& J: Q 0$ N$ p- }% L9 ]) M" Z) x
0 上海市黄浦区方浜中路249号
4 @' V2 g! x7 |) ?' _. w u4 a& ]1 上海市宝山区密山路5号
) ]+ C- e# B/ U/ R* |! n/ G& M1
0 `2 {) _% j; x; s! K9 G20 R, P2 {4 x4 L1 x5 @
3
+ m' x' [' \# \# G b. W" Z ~& j4
2 D5 j$ v5 t" v; T) X3 m; I( p9 T9 C5
! X/ w2 H3 T7 R0 O& _8.3.2 str.join 或 str.cat 合并
2 Q$ E; E* z1 k/ W8 K! A) dstr.join 表示用某个连接符把 Series 中的字符串列表连接起来,如果列表中出现了非字符串元素则返回缺失值。
/ [+ E1 t' M" I0 e* jstr.cat 用于合并两个序列,主要参数为:+ S5 z" W' x4 A
sep:连接符、) U) g. V$ W, t1 m
join:连接形式默认为以索引为键的左连接, K8 P' _ j# Q% D2 J6 A
na_rep:缺失值替代符号0 g1 B; n8 J. m
s = pd.Series([['a','b'], [1, 'a'], [['a', 'b'], 'c']]): m8 C6 R! e$ t0 ^3 N5 f; d
s.str.join('-')
& I; w! w9 i9 s' K. `Out[47]:
3 j, c, V# r$ g% W R x+ B4 r" g0 P0 a-b
8 S$ e3 x$ q; M+ v6 w+ |) k1 NaN: h; Q5 q# z! G! B `* p, a
2 NaN
/ B5 O1 d5 @7 C& f2 M4 Tdtype: object! N& m4 |2 w7 o! t; C$ d! E; F
1$ S' q: L+ H6 v! x c8 N. @
2
# A+ L1 z2 ]# J5 X& K3
$ o- f- O/ r8 ^6 R4, g7 p& b; G* f7 j
5+ K5 G7 [3 S- Q# D6 E
6" {1 S- x" i: f; ]
7& g4 D. r2 |9 \! G p
s1 = pd.Series(['a','b'])& G9 W' M, ?* l6 C3 s# \- |
s2 = pd.Series(['cat','dog'])
1 A1 W( S" Z2 F& q0 @# Ls1.str.cat(s2,sep='-')
5 i# P, L7 f" R( S/ J9 r. SOut[50]: 4 |: ~7 h/ @6 C' x9 ?, b: `, L
0 a-cat8 [% o1 |/ T" i' ^, |: k# s/ g
1 b-dog
. F1 a: S: {. L | n& X$ H: Cdtype: object2 P# M+ R P a9 |* `
) N b c* X# ls2.index = [1, 2]. y% J/ j3 i4 ]+ @: O( o+ b
s1.str.cat(s2, sep='-', na_rep='?', join='outer')
0 O {& z/ R$ ]. |# g: jOut[52]:
3 E8 [( q S* H! c3 v, Z# o* e8 o B0 a-?
( u" L2 e9 s* H+ E3 v3 l, @$ f1 b-cat
* t$ ]' L+ J6 E2 P1 h u6 c2 ?-dog
: l1 r% Z2 b5 O8 {, N1 xdtype: object
/ e: d9 A& ]0 O6 V/ a8 _$ X! F1$ ^/ a E6 K% x1 ^0 x1 m! Y1 g4 K
2
- z, l1 ~: _6 T# C' q5 z3. f, j( b- O. C) j8 D8 P1 c
43 Q4 O( B# o3 N' u. m3 t4 J( \
5" X* c8 f# o1 S& }5 E; w1 x! e
6
; Q! a+ K, a) B( _) o: U4 z- I6 O7
; A }$ t0 J% {0 y' n" G8; U8 Z& R( _8 v* t. t ?$ ~/ I
9. T/ D* r4 z. _% [+ b& W
10
& k1 j& q& v) ?' b3 C9 v/ l11
4 f/ b9 d/ G$ J( X) |9 A) Q12
: a3 n" Y2 M# E8 f+ D2 |8 M/ o/ h139 y; p" b# ~2 p# Z; [) x
14
+ O8 \' T( K+ }# w15
' R* [1 i7 k+ O& H2 b9 Z8.3.3 匹配
1 {/ ^2 H; A ] Nstr.contains返回了每个字符串是否包含正则模式的布尔序列:1 l, q H2 Z3 d4 \
s = pd.Series(['my cat', 'he is fat', 'railway station'])* o3 {) d2 C0 f* ^9 n1 ^ W
s.str.contains('\s\wat')1 B' V6 I( n- V$ w( h3 O" g
. h4 R2 l D: M: Q& f! R
0 True
5 C' o* P0 B, ~$ v1 True/ D" u- t6 L X# ~
2 False
& K- D& F. ?+ ^2 wdtype: bool
* V+ p& z2 g, F6 ~) X" n1* W& j, M- t. Z
2
9 y! P* `3 ^0 l6 X ~3
( C! d" p: f9 i5 v0 l% n/ s; }5 G4
; @1 K# X' t! e% w! R" i' E5
, x3 R8 O9 B; m' k- Y2 h# Z) _8 g6
0 ~5 C, D' q2 |3 {- M7 ~. x) G7+ r% O( ?$ h' y. U9 k! S3 Z
str.startswith和str.endswith返回了每个字符串以给定模式为开始和结束的布尔序列,它们都不支持正则表达式:
# W: d. ? u& G$ Hs.str.startswith('my')
" E* m, j# K9 j- p
( ] \. g1 ^) o; V0 True
, I# x5 i" q: E' k$ w a4 ]/ q1 False
" ? ?) { G( M; L6 O" S2 False/ d2 p' t& o/ f3 W1 j+ n
dtype: bool0 h4 U' ^( x3 O- J* V
1: i7 F3 C0 k3 `# G* u; G. }' U: R/ m
2
4 j0 S: S" N. Z$ B3
' F. G; b* p2 x. p4
5 j2 j/ V; a+ D, m3 V+ E56 ^- B8 _* k/ {" F l2 j/ y
6/ D, N6 \& f; W5 F! {$ K
s.str.endswith('t')
9 Q2 f# Y7 g2 L* M
Q$ S1 ~6 p: j3 ]( j0 True
; y6 A2 o1 Y8 D6 b+ I1 True9 U# D9 t2 h+ H8 Q7 u& Y
2 False4 D/ t6 k. w6 v. l$ `- I0 Z
dtype: bool( A/ W' A& d) q- D) i. r+ Z
1# E: Q" h7 I0 D
2
' Z3 u& u8 P3 N8 z0 I* i# v3
# R0 _0 i3 m4 y" j# Q48 {; q8 \( O/ ?: U5 Y! \- w* x
5
8 ?& X C+ s: e; F4 h6 \2 z6% j& B0 \, l+ u+ n5 i2 O" S. |8 L0 m' r
str.match可以用正则表达式来检测开始或结束字符串的模式,其返回了每个字符串起始处是否符合给定正则模式的布尔序列。当然,这些也能通过在str.contains的正则中使用^和$来实现。(貌似没有python里的search方法)' `6 p* C9 a4 Y: }4 @
s.str.match('m|h')
- g3 a- a/ ^! K" \7 E, @) zs.str.contains('^[m|h]') # 二者等价. r& M# H( C+ N
7 F9 H/ w" R( X! ]9 q0 True
0 ^3 `3 D5 T+ W% u& Q# b1 True
8 h, |4 V) c7 o2 False/ h/ q. K) c1 O& A1 m9 u
dtype: bool
5 E+ V1 h, b8 C+ b- [1
' M* |* E- ]+ `3 \1 {0 g2 }9 z0 o) r4 u& R; _7 ^1 @/ u6 z
3
: J7 ?" h% q$ y+ ^4# s0 z6 H8 Y4 s7 ]1 l2 R" l
5
7 h' n! W1 ^; \' h& v6
# m# p. G4 y W# E. D, C8 |7
/ ~/ J4 t) ?4 d% |9 y+ k; p( j6 hs.str[::-1].str.match('ta[f|g]|n') # 反转后匹配$ c% u4 J e5 a# x) D2 K
s.str.contains('[f|g]at|n$') # 二者等价
+ p7 b: p2 o4 Z3 P# S7 D) j
5 | Q$ r5 z) q+ B: C# L0 False$ R7 A5 F+ x( E4 z1 x/ _5 Z
1 True1 h( l) O% d8 E& g8 ?3 v, H
2 True
8 e8 w3 k# m- M+ @dtype: bool6 |* Q! |+ V+ ~+ z. j% y* w
1
3 w& M, c* {$ w2 ~9 Q; @6 q2
2 {* [0 g8 A* m [/ e" D" h33 k0 l# z' E1 r% G" c6 ]
4 g/ ^; h% Q' F4 y/ B
5. }5 R6 x$ f0 ~( p* K; t
6
! N; ]9 e. F( v1 l, f79 z0 ?) a. ^& z. g1 g& B2 g' j& k h: q% Y
str.find与str.rfind返回索引的匹配函数,其分别返回从左到右和从右到左第一次匹配的位置的索引,未找到则返回-1。需要注意的是这两个函数不支持正则匹配,只能用于字符子串的匹配:
0 n( o/ X1 ?* |- A% As = pd.Series(['This is an apple. That is not an apple.'])
; ^ ? Q& g) n4 C: c% r5 j& T/ R
s.str.find('apple')' Z) {9 x9 G4 z5 m% e$ m
Out[62]:
& c6 X5 [) o9 Y& R5 j) H3 K+ y0 11. D C7 t7 x6 q A. ?1 ?% x6 v
dtype: int64 E' q' {' E; l* i: n" V
( K3 C4 l) N3 l# O
s.str.rfind('apple')0 r4 T9 R) r- ~* `3 O) `( @
Out[63]:
& l9 Y A! B/ N7 L1 x4 A0 33
8 _) ^( {# l' ?: j/ M# Z% j5 f$ [dtype: int64
4 l, w h' N& v- U18 J2 H) u& B! Z/ N) V, C- \
2: o' {1 q0 b2 p5 ?8 V8 v
3* X& L; }' k$ g2 N; g3 \
4: O& Y+ p) }4 S' r1 o
50 h |3 G3 m% t S0 H9 S
6
: g; E* _) r- W3 ^3 _7
' X6 r! h. g, Z& h6 _, g8
" U w! D) g# k8 w9
r! M1 j3 D$ j1 s9 Q2 i10
1 o4 \, c& b$ Q% m& N9 S118 L, L8 ^, h6 o+ m0 t3 e9 Z
替换
2 s7 Q$ e# z6 r; A+ Kstr.replace和replace并不是一个函数,在使用字符串替换时应当使用前者。
7 @. ]5 C# O; z0 u. es = pd.Series(['a_1_b','c_?'])
7 h3 \+ n' c" C* z1 J" j) e# regex默认为True,表示是正则模式,否则第一个参数内容表示是单纯的字符串,也就是匹配字符串\d|\?
- h3 j) f; m8 n$ P6 b, Os.str.replace('\d|\?', 'new', regex=True) 0 u/ G3 c* ]/ e
) a+ d% \: p2 {
0 a_new_b
; I' S" a# Q L8 Q7 L6 q1 y1 c_new$ o. b' K+ F, i8 z' j6 a
dtype: object
, u+ i# Z0 s( }1
( J- K% K, f; O$ q6 b, {2
* j/ y A: R( {( D# F3
7 L7 k, V+ k. L4
4 p/ q; i u( m59 Q8 M; b4 _( d, ?/ g* r
6' ?5 R, m( b6 ]2 |" Z
7
6 ~* i3 X& o B3 V- y+ z 当需要对不同部分进行有差别的替换时,可以利用子组的方法,并且此时可以通过传入自定义的替换函数来分别进行处理,注意group(k)代表匹配到的第k个子组(圆括号之间的内容):6 {/ m7 G4 F% @
* y6 K* J% ?1 ?3 t, o2 G) }
s = pd.Series(['上海市黄浦区方浜中路249号',0 H. E% u/ z" h1 y& v V8 z- d7 c
'上海市宝山区密山路5号',+ s: ], x# V) |' K1 ^1 D5 k
'北京市昌平区北农路2号']) R& M A) l5 Y' m1 b6 f. @7 @
pat = '(\w+市)(\w+区)(\w+路)(\d+号)'% E, s. s% ], W; V/ T
city = {'上海市': 'Shanghai', '北京市': 'Beijing'}0 e# q2 a5 \6 A a Z
district = {'昌平区': 'CP District',
6 Z: K2 ~! Y( L2 J '黄浦区': 'HP District',
( r/ E0 R( I( O' ?# k2 q' T: m' l '宝山区': 'BS District'}
, ?+ i$ T$ f5 qroad = {'方浜中路': 'Mid Fangbin Road',
6 G5 } l5 A8 L8 D; W" y6 \ _ '密山路': 'Mishan Road',
: E9 y, o* Z5 P '北农路': 'Beinong Road'}
! u/ K2 Z ~, m1 Qdef my_func(m):
) U% g% [% L% y1 b" d# g/ Q3 n2 r str_city = city[m.group(1)]8 h; s- c$ z3 X9 n; g. u% d; ]
str_district = district[m.group(2)]
/ h w$ X7 r) W6 g: s) ?. l str_road = road[m.group(3)]% t1 Y9 P( Q0 p) R" d2 h
str_no = 'No. ' + m.group(4)[:-1]" A8 \1 I) F) o8 f6 k
return ' '.join([str_city,
, K+ F, J+ i# z+ M2 s str_district,3 @, V- Z- A+ b" O5 \; D
str_road,
- i! Y8 c. i5 r0 z4 i# G str_no]). z9 v8 B. q$ |
s.str.replace(pat, my_func, regex=True)
4 O( v# ~# d/ J% h
2 o' y* k8 m$ v5 e1
8 |) a3 F8 o% L9 V- c" e2
2 A1 J( k$ h+ _" m! M3) N, u$ H. i8 W O
4
' R+ X2 F$ [ c0 |# v8 U z7 a5
* ]+ p# |% \1 `: U6, t' L2 `/ ?; Q8 A
7
; K8 v1 l9 I; C, y: w) s8
+ r0 m7 G) c1 q8 R3 i2 k% d9
* I# N9 P$ W' M" E4 F, c10- G- R1 Z3 g. r2 z( F9 \1 l6 ?
11; h% A' S4 a/ ^. C0 X4 o
12
1 i! _4 C, y( g3 D' } x# e$ G13( @2 ?7 [2 s& b9 X8 Y `+ U" ~& k
143 _5 n" B0 P4 s0 o- [- j. \2 r5 F- X
151 s# \7 [: |2 p
16
6 g- T6 P2 T, ]) c17
- D# @& U; ]+ _; B, l18, V2 m P7 G4 T1 f, A
194 J/ V! X' r" i
20
; m7 U$ x' v6 Q21; B: z+ f; Y1 S; e% x# N# c5 u
0 Shanghai HP District Mid Fangbin Road No. 249
3 y; S* b# h1 D* {; r0 D1 M1 Shanghai BS District Mishan Road No. 5
$ \$ i* b: ]/ W+ u2 Beijing CP District Beinong Road No. 2
6 E9 V/ \. B/ S% { ~" v+ fdtype: object
% F x9 ]' X6 s4 @8 q G ?; @1# |5 F: ^2 K9 l* @8 g
2
4 Y* _5 Z) E3 x p% Q$ |3, [2 `$ X" o3 {' F4 _4 d+ s/ v$ W
4
8 O7 c# g" m$ D& q( ^" R( ~8 e这里的数字标识并不直观,可以使用命名子组更加清晰地写出子组代表的含义:# d& H9 d9 j) J: e2 S5 N6 h7 I8 O
; |0 ^6 r8 n: H0 B t9 `# 将各个子组进行命名0 b; M- \, Z. H8 m$ h; l
pat = '(?P<市名>\w+市)(?P<区名>\w+区)(?P<路名>\w+路)(?P<编号>\d+号)'
/ D9 y( N+ h+ xdef my_func(m):
# n' ^7 \# t! \ str_city = city[m.group('市名')]
% r2 @3 z' V4 c; D str_district = district[m.group('区名')]
3 V2 v `- `, n* h! P( i- @ str_road = road[m.group('路名')]
- ^ s' f# H* F str_no = 'No. ' + m.group('编号')[:-1]) A. L4 K8 \- N2 L, H7 G+ @
return ' '.join([str_city,
! j T s# K; z! j$ S) o: S& T; y str_district,
" i2 m3 |4 A8 o3 a str_road,
5 c/ v- n& ~6 U& L# a0 f8 F str_no])* L; O7 n' l$ E' u% k7 g6 P
s.str.replace(pat, my_func, regex=True)3 W8 w1 x( \; H% H
1
) |6 f9 I& ]% L! _$ B2
3 l2 A( A5 C z& M' y5 @. B! j3; ?4 m1 t9 B: K- l; w
4
8 K4 p! D: N" n8 v. G$ Q55 J h, d# E% }% }8 Y% u
6- w4 ?5 R- ~2 |0 e
7
& Y1 _; U' @0 E# ] P9 H8) F) E, A- _% V
9
# F* }) I, W* ^, ]1 T103 ]4 Y/ q3 m/ i' d4 h: O
11( j" ^7 |7 p3 ]" Z# r- Y
12
% C* C$ N4 Q2 I# W' D/ Z' g/ X0 Shanghai HP District Mid Fangbin Road No. 249
/ s4 T$ o" ^2 @; I9 W$ m, s' `( S1 Shanghai BS District Mishan Road No. 5. {1 W5 m' T* J8 F! O
2 Beijing CP District Beinong Road No. 20 \5 N; b8 J' r b! w. C
dtype: object0 Q+ S5 {: ~- ]9 D' ~5 w$ Q2 h8 M! t
1
+ S4 m( g9 t v) C8 }29 I5 x1 J5 }$ l7 r3 E0 _
3! h* o$ E, {1 R* R+ `7 u* n5 i
4
9 l- ?- Z/ Y( R0 p9 U5 C' ?- g 这里虽然看起来有些繁杂,但是实际数据处理中对应的替换,一般都会通过代码来获取数据从而构造字典映射,在具体写法上会简洁的多。
. q+ a. |% k8 S0 ?6 Z& H
9 n1 ^: Z" s @) G7 U8.3.5 提取, c! r( \+ m/ Y! X5 L6 X
str.extract进行提取:提取既可以认为是一种返回具体元素值(而不是布尔值或元素对应的索引位置)的匹配操作,也可以认为是一种特殊的拆分操作。前面提到的str.split例子中会把分隔符去除,这并不是用户想要的效果,这时候就可以用str.extract进行提取:
4 S) @" P- X+ Q5 Xs.str.split('[市区路]')/ ?+ o0 [; \6 T/ O1 C; i) ]1 g- `
Out[43]:
& @( }% X# E0 P( ~2 p" y0 [上海, 黄浦, 方浜中, 249号]4 Q, }3 B! Z( C0 g0 k; K/ P( D% R
1 [上海, 宝山, 密山, 5号]/ P# Q. S0 X, b# @- T- j1 L
dtype: object! g* ], J5 M4 o& e3 s5 _" ?
3 n) u0 H- R* d% W5 |8 R2 Z2 `pat = '(\w+市)(\w+区)(\w+路)(\d+号)'4 |% m+ c( x8 h) x4 ^
s.str.extract(pat)
1 K+ Y. ?0 M- J4 Y8 p% Y: gOut[78]:
3 E" G: K% | |5 ?6 F$ ^4 t 0 1 2 3 w# u( Z3 m+ H& j
0 上海市 黄浦区 方浜中路 249号
. y8 p: [3 h# S" P: e2 z; a2 N1 上海市 宝山区 密山路 5号
: u$ f$ u) R% D& Z( c0 C2 北京市 昌平区 北农路 2号1 [1 D- @( U$ H# H2 F! i
1/ I- S" K/ S9 ^' a2 N) T- q8 Z
2, e/ N: i5 b( L* n
3# D! y/ O7 ]+ K9 B" p) i
4
' v* T! [; a$ i: Z/ k5. \8 M" s6 e3 u: A6 Y' M
6
" V, p" I9 }3 D5 d8 I, p76 R- b1 _: S3 g/ q$ T) O! z- L& X0 a
8, ^' w% j5 C: s1 n! K8 O
93 F6 a; @/ ~- m9 B+ d5 a
10# n e$ Z9 H8 x" k
11
7 p' W$ D( K8 L! w$ H3 h' M- ]12
+ Z8 T0 w( X# B9 a13
8 \! I0 i# w0 A6 l0 Y通过子组的命名,可以直接对新生成DataFrame的列命名:
5 O! p" j9 D) I8 Q) {
' l" C1 K% k" H" y- ]" M: B' @pat = '(?P<市名>\w+市)(?P<区名>\w+区)(?P<路名>\w+路)(?P<编号>\d+号)', u' t5 r$ D0 ?! e' _" d
s.str.extract(pat)
' z1 V/ w' K: aOut[79]:
7 R+ E% H- Q8 a" R 市名 区名 路名 编号
" l* g5 v5 _* I' u( M0 上海市 黄浦区 方浜中路 249号
2 h$ v+ H6 G! s5 y) d1 \1 上海市 宝山区 密山路 5号3 }* l; W5 R) S: _' T
2 北京市 昌平区 北农路 2号
' D v3 m/ `( \1
- e2 w9 ]2 T9 x$ c8 H& G" i2
9 w7 `! d9 Y# X) m- v, ~1 p3! |8 k* f/ D! V
4
& ?# j: d2 P' }1 J59 e- w5 L9 n- i" `5 A5 P9 U
6
5 \5 O `6 a3 w( Z, B" ~7% E/ K' P' W1 S- m. M
str.extractall:不同于str.extract只匹配一次,它会把所有符合条件的模式全部匹配出来,如果存在多个结果,则以多级索引的方式存储:& M/ q9 R) Q [" ?0 n" G
s = pd.Series(['A135T15,A26S5','B674S2,B25T6'], index = ['my_A','my_B'])( q6 J1 i' y! {5 A2 q
pat = '[A|B](\d+)[T|S](\d+)'
$ ~# f' a. e7 E. i# i js.str.extractall(pat)/ C+ o+ \" s1 {+ o
Out[83]:9 t3 |3 Q( f+ k
0 1
% M/ k. T# i* Q; m+ Z/ q0 [- X* X match ) o. ]( g% g; B1 p
my_A 0 135 15
[3 D5 g. G. h 1 26 5
! c8 v- I/ }' m0 w# P$ u1 rmy_B 0 674 27 ^6 t: [+ R5 u6 w; [- S7 s
1 25 6; f% D: t" P9 P0 w# m c: c) G
1" \! Z0 J$ s6 T/ t
22 v7 g- }% `, B4 ^
3 `! n- Y8 w5 b' k1 x. {
4
. G1 S0 T8 |3 n# d5
. J1 j( Z9 }/ r. [" H( V6
! L+ A( R9 q* x: Q: V: {: P7. ?) Z9 H( @6 ^# C/ Q9 I
8+ Q$ q) j6 L/ n' z
9$ N2 D; S6 w) Z* e
10
) I% F- B1 q: u3 |pat_with_name = '[A|B](?P<name1>\d+)[T|S](?P<name2>\d+)'
* g) J- z+ R7 k8 h2 Hs.str.extractall(pat_with_name)
6 _& }) n7 l$ i0 g R, k1 xOut[84]: ! s6 [( j, \ F# y' t
name1 name27 ?+ X7 n5 q4 b
match 3 g5 `+ s( Y8 Y+ R& N
my_A 0 135 156 J4 y k& {$ H; g. y
1 26 5/ _+ j' k- F# @1 p
my_B 0 674 2
. F% {2 o4 v' q 1 25 6$ K" T# _2 B1 {" H) |( |
16 m; c, q5 I$ F$ A' Y$ J
27 N1 {/ A) Z2 t" O+ q, H% A& z) R
38 G& U) @' z, b# ~. R
4+ |& w! Z2 O# `' ]
5, p- ~; y. m8 e
6
4 I% w% F. c: u* T7
( w+ s6 u: M# T: v; A. m/ j" Q8
; h8 c+ {' u! y9
8 j: s+ z' I) H3 W" P6 b: ystr.findall:功能类似于str.extractall,区别在于前者把结果存入列表中,而后者处理为多级索引,每个行只对应一组匹配,而不是把所有匹配组合构成列表。
( g& o, i- O+ M$ G/ ts.str.findall(pat)3 D0 |2 _6 h- y, @- V$ f Y
1/ C, p/ }# K8 y' W* E7 h* z# j
my_A [(135, 15), (26, 5)]8 D; U" |# |* G6 \5 P: y; q5 z
my_B [(674, 2), (25, 6)]5 B6 Q$ g9 d5 O+ h3 @! G
dtype: object0 B! W6 H9 {5 |' s$ c4 O4 [
1
8 g9 t, C( v# ]6 S1 R2
7 f) z/ G$ `/ Y _3* C6 ?' W' ^8 n0 T1 g
8.4、常用字符串函数1 E H6 z7 J6 e' D8 g
除了上述介绍的五类字符串操作有关的函数之外,str对象上还定义了一些实用的其他方法,在此进行介绍。
/ t9 `5 L6 R' H% C- E
) E' ?/ z @) [/ `1 Z, ^8.4.1 字母型函数1 `3 b% U S b: O4 }; T
upper, lower, title, capitalize, swapcase这五个函数主要用于字母的大小写转化,从下面的例子中就容易领会其功能:
$ W Y2 R" t+ r! d* P& G, y- ?5 c1 Q- g
s = pd.Series(['lower', 'CAPITALS', 'this is a sentence', 'SwApCaSe'])
' d% f, ]) H! a
( G' G7 Y. y' S2 ?( p6 C Y4 ^* Qs.str.upper()
% ]* |" p* W% f5 GOut[87]:
1 c6 m8 ?- @" D# C0 LOWER
% \; r L7 E! Q$ q+ ~1 CAPITALS
5 ^6 X6 K0 ] s0 a g) I2 THIS IS A SENTENCE8 k( v! C9 ~9 W& O
3 SWAPCASE
5 S. \" [. Y/ N9 Q1 l! d5 |" D2 Xdtype: object
& I6 L S2 I- M+ \% j* Q2 s# u/ V& w- u* r0 t
s.str.lower()
4 t( O* X' n; X) A3 O" DOut[88]: 8 g" q* J! q, S8 j* R
0 lower
' E. [: ~0 V4 V1 q! V" w# r1 capitals
2 q0 z: r! v/ H1 {8 c/ L" X. k2 this is a sentence8 k0 I; ~4 L* J, ~& R
3 swapcase
/ p; g# b, n0 R D9 z' h/ Idtype: object( N' c/ ]( w( w2 ~$ X8 [% L+ |
4 E# K e/ {5 h; i3 y& O( E
s.str.title() # 首字母大写
+ a& ]3 c: S. U2 Y$ bOut[89]: 1 {2 t/ x9 {, e3 X
0 Lower
/ ^" t2 J9 |9 [1 n# F$ Z) q1 Capitals* q* o1 R; I7 h8 b! U; A
2 This Is A Sentence4 J5 |* w: |) e& J6 h
3 Swapcase
6 D8 }# E% {" xdtype: object
: m! |7 s0 \9 R8 O0 f" }9 \% Q) p3 W
! E% R$ ~3 H+ V+ Cs.str.capitalize() # 句首大写! i/ t$ G9 t f
Out[90]:
# T6 W+ a" u: Q0 Lower
! F$ }; |# u! s# Z, v2 w9 e6 L1 Capitals5 ~5 H: U y- N, A' S
2 This is a sentence! e0 t1 t0 N1 O$ s- T
3 Swapcase6 {( [- c; U1 a6 \# [
dtype: object D3 p2 v* S% f" f/ h
6 c; J# Y$ J% C: M0 h
s.str.swapcase() # 将大写转换为小写,将小写转换为大写。
& J/ i5 F5 c+ E7 l% Y( M- oOut[91]: % F! O1 o) F/ |6 l, w! z* e# H: z
0 LOWER. w0 Y6 e) Y$ F' \- _ m: ]
1 capitals9 c+ u" y6 N6 x& w5 C, |& Q
2 THIS IS A SENTENCE# ~& t5 x2 k% g) g3 x
3 sWaPcAsE
9 ~; n7 s& P$ I m5 [dtype: object" G6 L$ Z, i+ ~3 ]1 h
+ o- u. v7 Y4 R: e4 g2 @# us.str.casefold() # 去除字符串中所有大小写区别
. f8 X2 A* S& f. Y `: q( v
5 Q; l! D, A- Y J' b8 W3 q0 lower
+ Q/ p! j; L5 w: j% r+ Z" O1 capitals: j) B' S/ N/ k6 A P
2 this is a sentence
6 D9 b$ n8 p# v8 P0 t3 swapcase* O! u1 l( \* a* q" K6 J9 D0 H
6 ] }9 o# J! l- g
1
: y, Z0 P& F6 t: c# S2' o T$ t. ?$ C8 z" @+ e
3
; R) V. `0 v# s- ]5 q1 t h/ E' g4+ d: _7 e4 j, I, ]$ n
5
* P2 G) m% m2 G/ @/ }6
- F2 L9 D8 g- h' z. P/ J7
" v$ i% h8 r, r+ C: e9 J, ?' M6 V8
3 F! Q2 e0 }+ b" a9 `; R9
6 U* ^1 ?# p* o5 g6 l10, W: @( T& b7 r- i0 J w3 G
11( V* C9 ]+ B$ u! y
12/ F _" z9 O5 K, [% p" d% C' y
13, x- n5 R* g+ c% \# Q
14
' u% H! Y! C, u1 ^15
{. x. V3 O C$ ?+ |16
! u# @1 y( a! k3 j17
5 H, z# g) D* i3 e z3 J1 {180 y0 z& L. A# q
19
9 O. M* g2 V% O/ G+ i+ C4 t9 f20/ B& [" l/ W$ }1 M+ ^
21
8 j5 S6 I3 U, X) n. Y9 o- y22# L# b' e+ m/ z: L; Q& [% F" E
23
1 j) F0 j" G( ^+ t6 g% W24# x% W* d9 a y5 Z+ a) d
25 J6 c2 F$ g* ]
265 H ~ ], [3 g; F
27
+ `5 o$ u0 R# g1 X4 Q28
" w7 B% R- @; V0 p d# [0 V6 i29
- w Z; \6 F* E- T+ G! ^+ U30& Q9 f. ], D4 W5 f# q' k" V
31; @% X& ]; r+ v5 N. ]: Z$ {$ X
32& j. v( w9 V/ `+ [7 l
33
' ?5 l* O( L" Q34
2 r0 r5 C- F5 G3 T e, m359 {$ `* Q. V! _
36
; y( z. [; @. k37
, s/ A7 m) d b8 L8 N38
; R# R) V& V1 X4 `% b1 t39
5 n/ z- ~! T8 |2 d1 l! L40/ a+ B2 Q( U8 E; y
41+ f& |5 P h' ~
428 h+ h9 |- R! q
43" Z6 g; ?+ B# @2 q2 Q! X3 J% d
44. j$ Y3 ^, n! m7 `. ?* q
45* P3 O- r0 f" I0 T
469 ]3 G% A/ `9 p9 c
47 A2 g9 M* j; r2 e( j
480 g4 f! }& o8 G
8.4.2 数值型函数
2 r7 P8 P* J: W+ c4 r 这里着重需要介绍的是pd.to_numeric方法,它虽然不是str对象上的方法,但是能够对字符格式的数值进行快速转换和筛选。其主要参数包括:
5 T' Y& N$ p0 G; I) ?
# {- U' l5 r. s, R& K8 s* Berrors:非数值的处理模式。对于不能转换为数值的有三种errors选项:+ M7 T9 L1 }) X5 F) q/ ~
raise:直接报错,默认选项
9 B3 \' P& F/ [* ]% scoerce:设为缺失值
/ ]$ [$ Q s( n9 {! Nignore:保持原来的字符串。
7 l6 w2 J! F5 R* e8 mdowncast:转换类型,转成 ‘integer’, ‘signed’, ‘unsigned’, 或 ‘float’的最小dtype。比如可以转成float32就不会转成float64。
8 a% A* r, O& |( O4 \2 i2 S+ q6 a, Qs = pd.Series(['1', '2.2', '2e', '??', '-2.1', '0'])
7 F* D0 [2 A1 R" N9 E( O/ @" {/ w2 W, u* j; _( G; F
pd.to_numeric(s, errors='ignore')2 M; L, F( J9 n+ \8 _" [8 v
Out[93]: 3 } ]6 N) }# j4 N+ b3 w) Q, w6 e
0 13 ?2 E. m7 a n1 j0 ^1 b
1 2.23 `) m, V2 r. @ V6 q" _; [
2 2e
' r6 ?9 [$ ^/ k/ y- w4 W' V3 ??# \6 i! g- g! P9 V
4 -2.1
" p! p7 P) v; N. e3 S( U5 0
; u+ H+ |! x) S( L3 U0 V% adtype: object0 W- c* l# V0 T, |# X" d! c
& A( X B8 X+ }* Zpd.to_numeric(s, errors='coerce')
9 P0 q% u! p. r( z6 r" Z! u. k. a/ ^Out[94]: / q$ e( R) L- P3 e# {$ u; g4 ~
0 1.0% c. R8 v" C7 z% P% R# W
1 2.2; B" t4 ]3 [& u* \- i. a2 K; [0 N
2 NaN
9 s& W# D, S+ c1 l/ T {3 NaN
0 B6 u/ T' }1 j: r& X2 S4 -2.1* y8 ~. E" i( ^! W% E7 t' V/ z+ j
5 0.0
1 l; O8 |! ?! r3 j2 Gdtype: float64# B" S- w+ T1 X& `! f9 N2 G
5 k$ r2 C3 e! y. Z3 [1
5 A4 C/ p5 ?' B; ^1 s y26 O' A W& `# v: Y. ^
3
& E' f8 u2 S; X+ z' A4- E4 Y! Z$ t. g+ P5 M5 P G
5
( G" ^6 \' J& v7 B" M! |6
$ G' O1 Z; u# ^6 V7
d m: H6 [# \# v8 P! F83 R9 ]+ ^5 x8 |7 N
9% Y' q: {% E- t+ }
10
2 i5 n) W0 f4 G# O! m7 F' k11) K9 B" I5 i& G
12
5 ]6 r5 }" z5 @5 D131 f% t; N0 U3 j# e [- z, U
14
1 [" z2 E' J! ~15
* i9 B4 E; w9 t) b5 C2 y9 _164 D" A# E' J0 C; Y. F* n. Y; Q$ T: J+ Y
17
0 {: y6 r: K9 i18
: Q# a4 V i @0 a0 q199 ]6 e0 v& U! d) Y$ Y
20 M( z$ j& o9 U# M1 ?8 J4 b
210 [ R) x3 e5 K
在数据清洗时,可以利用coerce的设定,快速查看非数值型的行:
6 j" `/ u8 u* d# n4 c: L/ i! _1 D7 T7 R8 @0 Q4 J2 e
s[pd.to_numeric(s, errors='coerce').isna()]" U. `) k8 g Y. L+ L: w
Out[95]:
0 I- X4 _0 r" \4 _7 w2 2e
0 n9 ?7 k! }0 a3 H2 S4 y8 k3 ??
T( M9 D9 A: B7 e+ Kdtype: object- [+ k& n. s r' o. M k6 t
1
; o ?# Y M* W( E2# a* e- ~) \( N! O3 C9 A
3
5 j# I5 t$ p8 G+ }4 i. s4) [! X+ y# @. X+ {. _% u
5
) \1 d/ z1 X0 U0 C8.4.3 统计型函数. q: X, x2 X- h8 P: A: ^5 A" w
count和len的作用分别是返回出现正则模式的次数和字符串的长度:. { w% H0 ~ B; ^. I. @% l
! s( J! v, O; E# es = pd.Series(['cat rat fat at', 'get feed sheet heat']); n" F' n5 F. l
- @3 p: u2 I. F2 |8 G0 x X1 O) [
s.str.count('[r|f]at|ee') # |左右两种子串都匹配了两次" R3 _6 [, j( b9 n
Out[97]:
. b0 l/ l! }- b. J0 2
0 e5 W/ s0 A u- Y5 a: ~1 @1 2
/ c& a4 v. ?1 u' [: Ydtype: int643 @3 S( |% } L# M6 q. A# D
- D$ w6 f# v* b4 Z# J7 F% z
s.str.len()
" W8 y; _6 g* I% b6 Y# c) aOut[98]: 6 V, G" P, W) H) X
0 14
! Z6 Q" ~6 A/ m- `1 19
+ _' f( `/ X- d' y% D' odtype: int64
$ \' N6 B" h/ z2 u1: [* N \+ Y9 Z7 _
2
* k+ P0 F9 ^" {: b0 F7 [% D8 t2 i3! K/ W( f; X/ l$ g1 K: R! I4 Z
4+ ^" ^ s% K- G9 {$ `/ m7 h }. a
5
( `$ z% ^. P/ O3 H+ `1 h2 U- p6% C& z+ _( s! ~9 v `) H
7
' x9 H/ \6 c5 p4 Q8
2 U2 v4 X' Q. v# [+ F y y: ?/ q9
1 O/ |; @( L* B Y) E* j* |+ x10
: \0 I" ?. M/ V113 Z9 a% p( `" D& ]) Y- h, V
12# M. e. y. p. b& m
13
0 |/ H2 z' ~- j+ s& O o3 @8.4.4 格式型函数
+ @/ }# c1 Y! \# g 格式型函数主要分为两类,第一种是除空型,第二种是填充型。其中,第一类函数一共有三种,它们分别是strip, rstrip, lstrip,分别代表去除两侧空格、右侧空格和左侧空格。这些函数在数据清洗时是有用的,特别是列名含有非法空格的时候。: n3 V6 b( n+ S5 T
4 k" F5 L6 X+ v8 u# O0 m4 Xmy_index = pd.Index([' col1', 'col2 ', ' col3 '])
6 _8 I$ k$ S b7 l$ k
7 V5 G8 v7 S* q4 Vmy_index.str.strip().str.len()
( ~* p7 o$ J6 O+ P% SOut[100]: Int64Index([4, 4, 4], dtype='int64')
, C' ]* t$ @4 L+ D
& y$ H9 [2 ~6 i% V2 ~my_index.str.rstrip().str.len()6 }% N! v2 N2 }+ K' f, s9 W
Out[101]: Int64Index([5, 4, 5], dtype='int64')8 c* F5 k# e8 J7 I, }
8 g1 I1 o+ Z* C6 o0 P, Q
my_index.str.lstrip().str.len()
9 c7 s2 @( H6 ^* e) uOut[102]: Int64Index([4, 5, 5], dtype='int64')
) k" z! ^2 @5 B: g. J1" g4 m1 u! j' [- V
2# q& T6 t: x/ d; [
3- m9 O' x3 K$ d7 b9 K. f
4- k8 J1 i9 c% a o0 Q) J ^# C4 [! q
5
2 l4 ^! w+ ^9 d; c, b e6$ y: l* e {7 |$ v3 k# o3 |
7
7 d- I7 M5 ^) [$ P, c) C. ?1 \8& A% u" ~( m @' J' V
9
1 s! \3 f, G3 t( t" p10
- ^1 `3 [/ c. d8 B) g6 M 对于填充型函数而言,pad是最灵活的,它可以选定字符串长度、填充的方向和填充内容:3 \* x4 \0 C1 s/ w
: _! J8 O: g# O- E( p6 H! Xs = pd.Series(['a','b','c'])
& Q. v" a# P+ o2 E. S/ [! C
" w8 J: `7 A+ ~8 Ks.str.pad(5,'left','*')
# t7 w$ \5 W' ~% P0 S5 n7 F$ E" dOut[104]:
2 x: f' B0 [- ~8 @" Z2 @, U- y0 ****a
& ^# j) h4 u6 x1 ****b" P. M- I7 H0 {; y4 E$ f2 K, B
2 ****c/ y7 [( G' i/ f$ v; m& d
dtype: object( U3 w/ s5 R, ]' U; ?
! B/ S. B0 u% ss.str.pad(5,'right','*')
; }! a5 t' P- a6 COut[105]: + K) g$ V5 Z8 _4 ^
0 a****
# ^6 L) t- C( v/ l4 F1 b****7 a$ ]) ^# G8 a, O1 \: e( ]& g
2 c****
2 M$ D$ o6 c& u! n$ cdtype: object
! `3 [5 C" c# z4 q: x6 ?: b0 x1 q: R/ P( X- D
s.str.pad(5,'both','*'). X# W9 \/ T" k0 X, @4 q2 [
Out[106]:
9 N% W1 m7 [8 ~* W" P* ~, `/ g0 **a**
( h8 g( z9 Z/ k/ W1 L( [! ]1 **b**
/ r; Y* }# |- ?/ p1 Q2 **c**
1 _8 j/ {1 U9 {2 h$ p* Edtype: object e* e1 R4 m: H: o& A0 z( E9 N
6 k8 }4 A( g) K6 H G# r/ d1
+ K8 c9 O5 z( b, o7 Z2
& H. Y3 n0 F0 S5 V& N6 a3
" A+ v$ h6 i: M4
9 q t4 S( a" G5 Q$ `5 N51 g6 P; R3 P' Y, q% E9 r; H
6
, V- b; ?* L- H7
1 A) e) ], z# B" k8# C& `+ m `* o$ X* h0 v$ v! @+ i
9* m) m( O1 a. m7 s5 W! O1 o
10
8 l% E% c, o: B; i11- K5 ^6 l9 K# e( d1 M
12
9 e# ^( w- v9 A" K( O13( S, b T) Y; ^" }
14+ [: B( c0 N# Y9 G* a6 E' {
15$ I$ q6 F. m. e: Y+ P3 E
16
) A: x- j) Q% \* I17
+ `3 p. m# ^$ Z0 L' `) ?. V% }18
k \/ e' p& o$ l* |2 i9 U19
2 ]; Q. g& }% S! I+ @7 S6 Y20( h1 B. J/ c A
21# A8 z m3 z- d1 U/ A$ ?! m/ `, v( H
22
1 o; s! _; X* @9 O6 V. @ 上述的三种情况可以分别用rjust, ljust, center来等效完成,需要注意ljust是指右侧填充而不是左侧填充:
. U" P% n& h g1 D/ q% ?# b. Q: b
s.str.rjust(5, '*')* x2 P0 k' q1 m
Out[107]:
: t& i9 }4 g f) I+ f- S& f0 ****a* I$ {* H( T! Z1 [4 ~
1 ****b
% Y4 t+ p- q$ N" H2 ****c
7 W. s! q- ~8 X3 Q6 T: S: W- _9 Z9 ^1 sdtype: object
0 W, a* F/ T( W/ K6 R% r7 ^5 d5 `
* Y: r) ~; V! s& d* w5 [7 Vs.str.ljust(5, '*')$ ~3 d: ]2 }5 L A* K" l
Out[108]:
# s( D, K3 w) u5 J% L! G0 a****
7 _7 D3 H, p( D, w1 F1 b****" s( m2 H2 S0 @- K$ o" h; G4 R
2 c****
/ v5 @: _. L) q( {0 }; p' _dtype: object
- W* c$ A, _) Q& O4 d/ @% g1 Q @7 H' W6 Z
s.str.center(5, '*')7 [" w6 f" `% F8 z
Out[109]: 4 V3 @. L+ U, R, m3 K. F. e! T9 G$ r
0 **a**1 K0 g6 {: b, n; M- J6 R3 M Z; {
1 **b**
3 x' T8 y4 E& n$ D2 **c**! b5 B) Q4 X8 m' _4 k. X
dtype: object
v0 w7 n/ x6 i. P* R5 p, G! r1 g* m/ A! c
1) M0 r2 c6 Q2 P
25 S& J( o& q. b. D+ V
3# [2 Y! r3 J8 Y1 ^8 g8 f
4" Q$ p# I9 ~ j7 G! T4 H) e
52 x2 w2 @7 f. d1 E0 }" q) K
6- z/ `, n% A% P+ \- G
74 A7 x$ C0 Y& s5 g
8
$ M7 a/ j# S+ w4 T0 C/ u9
4 y! h' c% l6 b& T, E0 k2 S109 f) B( h1 f7 e* B& e# }
11% d1 J9 V( q8 g) I
12$ [7 _: g8 X3 v8 i' G
13
8 N# V3 G9 m* K, ]; s! {1 b! J147 U1 A; G6 E$ C2 ^# g
15
# k3 u1 t+ z: b3 w F3 H16# ~! L2 B; T# \) D
17$ h$ \5 a) E/ ]. O- ^, b
18
+ s$ d, I6 p2 f19
7 r0 y8 ^: T) s2 E4 q ^20
* p( D1 f" M3 ?" r5 Q( g0 N4 \. {) w* H 在读取excel文件时,经常会出现数字前补0的需求,例如证券代码读入的时候会把"000007"作为数值7来处理,pandas中除了可以使用上面的左侧填充函数进行操作之外,还可用zfill来实现。
: J% g ~0 L1 b$ f5 S0 o( M3 ^6 Q6 S k! }
s = pd.Series([7, 155, 303000]).astype('string')
: d) {3 ?9 s2 f6 H0 @
* r; J. q2 L+ i4 ns.str.pad(6,'left','0')4 `3 ?# }9 `, S
Out[111]:
$ S, t2 ]/ l; K5 z* ~9 x0 000007+ {1 P# N& Q& B/ |$ C
1 000155
+ J* y) d; C( h1 L- W3 x. M2 303000
/ ^4 J; H9 z. A7 d% X/ Edtype: string
. a1 t9 S( u" O0 Z# v4 d# @) x+ Z; n6 u; X. ^9 F
s.str.rjust(6,'0')
) [9 a$ f1 _) l4 D5 gOut[112]:
! s: u4 G* S" P- R0 000007! W3 O W, d+ v5 N$ ^3 T
1 0001556 j0 y" D( m) m- |; I2 |8 r; S/ S
2 303000
% z6 L0 J. z. Y) P* mdtype: string
/ L* i% u3 o1 {8 g! u
% [$ D: e/ J$ Is.str.zfill(6)4 [# A* F. a4 \& F+ _
Out[113]: 9 J$ D, t) }2 ^
0 000007. H( c* L4 @' B; d2 d( y
1 000155& ~0 J! q: c" w+ y7 N# b, ?
2 303000
4 K2 V8 ^0 j1 I/ O$ Edtype: string8 P6 f# c; a4 y- b2 w
' r+ V9 m6 R A* U3 ]3 t0 J
1
+ t% Z) s n! z/ g J$ b6 ~2. e- G' m2 i1 K& r6 l
3+ y/ {6 o) @( q2 U
4 v2 m) x" U+ D% ?5 R
5% Z9 ^0 G7 `! H9 O
6
! c2 C4 x( h2 ^- T7
5 f% z& k M4 C# K$ ?$ N4 I( a. X8
1 e4 O* ~2 A' O- o+ D" ]! m8 I ^9
1 ^* l4 h: v s( c1 G10
9 I J6 U' T- r* S. B) p% }% o6 P11
0 r) ~7 ^) j1 `1 s& I7 E( D12
|; M: n) ~+ H, B* t7 Y: }+ t13
% x$ Q- R4 a, F$ r; [5 F- l% ]/ D14
: ~6 i; \- i' ~( o# B157 ^0 r' {% ]! G* a1 r
16/ |) Y5 ^, H7 C% K( A, Z
17
$ k1 H! I( P- ?189 E" y" O8 o' r" f; v9 ^9 ], a
19
3 L C8 f# d8 F' L. {9 [0 w20
3 O' i, V, f; v2 j) n21
$ M: b' d/ N _) G0 l _$ T22
" a7 H; Q/ L& T; m" b2 A' P/ A8.5 练习1 s) T! l/ {6 z- Y& u
Ex1:房屋信息数据集" z" @" s8 @/ M; b
现有一份房屋信息数据集如下:/ a: I _5 K. J
0 j: [' c* M$ Pdf = pd.read_excel('../data/house_info.xls', usecols=['floor','year','area','price'])
: v. c& ?) O! ^( @df.head(3)
- ?" k# l& [" H7 L$ n; ~6 POut[115]:
" |! q% g* o* X2 H0 E5 V0 }/ o+ v floor year area price% I( q9 v" v3 K% |
0 高层(共6层) 1986年建 58.23㎡ 155万5 @7 d G' X1 k/ R
1 中层(共20层) 2020年建 88㎡ 155万
* P0 {1 g9 h( ^6 u0 U: U2 低层(共28层) 2010年建 89.33㎡ 365万8 J. m7 O" b) g
1; c4 S( |- ~$ I# l3 e
2: T5 |1 z1 F4 s2 m
3
3 V$ W$ p) p/ m% B8 V5 t4* y! F$ P* R% U* R0 w
5
* o- U& p( q; e69 g( W" s. j+ T$ q# K8 r2 p9 W
7; [2 E! Q/ X5 j
将year列改为整数年份存储。
( c8 Z% a/ T5 [将floor列替换为Level, Highest两列,其中的元素分别为string类型的层类别(高层、中层、低层)与整数类型的最高层数。$ E5 m) u" \! [, B
计算房屋每平米的均价avg_price,以***元/平米的格式存储到表中,其中***为整数
" S. R8 t* l' k& _( g7 q将year列改为整数年份存储。1 n* h9 L8 b& f8 T* o
"""
! S* T6 R, r8 [ C- d整个序列需要先转成Nullable类型的String类型,取出年份,再将年份转为Int64类型。
' B D& }; F2 ^7 O注意,转换的类型是Int64不是int,否则报错。即使astype加参数errors='ignore'跳过缺失值,
' B( V3 E" A( Z& y, v3 {3 p转成int后,序列还有缺失值所以,还是变成了object。
2 O4 C( j3 P( r1 ~. ]而整个序列转为Int,就还是Int类型,缺失值变成了 pd.NA 。
4 I3 @+ m, q2 Q0 X"""
0 G' X* ^+ j( U/ Vdf = df.convert_dtypes(), [: r/ _ [- o- K
df['year']=df['year'].str.replace('\D','',regex=True).astype('Int64')
0 s) c0 o4 \) H% M; Udf.loc[df.year.notna()]['year'].head()
0 Q2 x! i0 s; @" p1 n$ V& I# O8 n/ ~1 d6 b1 ^4 r( d
0 1986
; o7 K: u: b7 G1 m# F- M2 ?1 2020
/ p ?, C7 J: k% A8 ^7 y% b& r2 2010 p5 j$ N3 S6 M- K$ f1 Q( J
3 20140 s5 G1 ]" | [- I' e' u
4 2015
, F7 {+ d: n9 T3 uName: year, Length: 12850, dtype: Int64
( j. I5 w! v" Z) G9 @$ r) T4 I
* `& r; | v( J" M: R: j! a1# n% P8 t9 g) M
2; \, B1 O' ]" {
3
1 Z: V" i- u9 M4 M/ V5 V6 ?4
4 w3 @ d. x; {# n- O0 r: t) {" {! S5
6 c1 C* Z7 Q3 \& i! D6/ y# a4 D1 i; m( I8 ]/ I7 Q
78 T- D' s+ i9 T: Q
8" [+ Y5 A) M8 s8 n) ^. N
9
# n9 P' \+ M8 j" i/ i+ w7 S10
5 T; @ N, @! @/ n6 p* h: H+ S11
, l5 o3 v Z* ^+ A, g12
; h. k: [- v# e( l" A; [6 R0 U13+ F- g. n% S w. g# n4 q ^; P% d d
142 u I0 ^- }8 w
159 v- [! N7 o6 T7 f0 \: Q$ N: R3 U6 a
16
, N N* W" {+ j, o' n参考答案:
: {( a; l% B s" m" Y. b% _, w
9 m" G7 P& [' e, K# v9 x不知道为啥pd.to_numeric(df.year.str[:-2],downcast="integer")类型为float32,不应该是整型么4 B2 R" t; K& w& q# c
' F$ l# e+ w; g" D* b. e7 s2 p s! |df.year = pd.to_numeric(df.year.str[:-2]).astype('Int64') # x) }7 O. a* O
df.loc[df.year.notna()]['year']
" J( X0 v1 x3 m1. z9 J. D2 g) v5 L/ }
24 R1 f. w1 t- B, v- T/ v# g
将floor列替换为Level, Highest两列,其中的元素分别为string类型的层类别(高层、中层、低层)与整数类型的最高层数。
^ Q: e+ h0 d' o* z8 Xpat = '(?P<Level>\w+层)(?P<Highest>\(\w+层)'6 _4 S) q" z9 m9 c$ n. X, U% L
df2=df['floor'].str.extract(pat) # 拆分成两列,第二列还是(共6层得形式,所以还的替换一次" K; {' H- d# |7 R
df=pd.concat([df,df2],axis=1).convert_dtypes() # 新增列拼接在后面,再次转为Nullable类型
5 Q! }& q' J7 d: ~df['Highest']=df['Highest'].str.replace('\D+','',regex=True).astype('Int64')
. y/ |5 y7 R* |/ B e* Jdf=df[['Level','Highest','year','area','price']]
4 c3 e$ e3 u2 O# @& m/ Qdf.head()
" G; i" W+ Q* ?4 c
0 b4 d2 T' b, g1 Y2 n# ^ Level Highest year area price
4 f8 a& y( d# @+ N( R5 T {0 高层 6 1986 58.23㎡ 155万7 I( ^% e$ r# o3 C2 `+ [, Y+ i+ C
1 中层 20 2020 88㎡ 155万: q8 k& Q; d4 K# w1 W2 @9 _9 a
2 低层 28 2010 89.33㎡ 365万
1 X6 m, ^, x5 ]5 H3 低层 20 2014 82㎡ 308万
8 l$ j- o: V" L% E+ v, @. ?8 O4 高层 1 2015 98㎡ 117万' M9 U$ D3 T. q7 v! r* z" T
1
4 A: z$ o* L; }* D8 p2
K5 j% P5 l) U k& G, Z O3
7 z& A0 r. B/ `0 z: G) T9 V47 i+ `8 l* l5 ~: M% Y
5
# i# [7 h* Y+ G$ f67 t M7 i; F2 Z5 j
7, f( ^' X3 a; |5 j5 |
8) P, T/ L7 b( Y3 g& K- }
9
) l/ i( _( t, N10
$ {3 S, _4 f$ _: M( ^/ X: D- }* M112 S/ W" a* c5 `, ]4 a
12
5 E. M% Q( G0 c- {6 @- {13
5 o: H; Z. D+ `4 e k7 z9 z# 参考答案。感觉是第二个字段加了中文的()可以准备匹配出数字,但是不好直接命令子组了
: E7 N( [2 r$ cpat = '(\w层)(共(\d+)层)'
5 f7 g/ f' i; P) U5 snew_cols = df.floor.str.extract(pat).rename(, V5 K4 h4 h) e& Q: Q/ a' }
columns={0:'Level', 1:'Highest'})3 U, l0 [1 o: p0 g
9 u1 B# d- Q t' G: E) t# udf = pd.concat([df.drop(columns=['floor']), new_cols], 1)7 z( H9 v7 `+ f1 t& B
df.head(3)
0 K+ U( ]8 J$ z @( W
: D# j& k& M5 z- c, Y7 A8 zOut[163]:
' a1 n- l5 \; S$ b' H year area price Level Highest Z f3 G% i, E
0 1986 58.23㎡ 155万 高层 60 A: {; @# p9 H4 c! p' O1 W
1 2020 88㎡ 155万 中层 20; G! @/ t) X7 Z% N
2 2010 89.33㎡ 365万 低层 28
# a+ t# g5 _9 e% L1* a5 m- h8 s, h4 F" } h
2+ \4 ]/ K) ?6 ~+ d5 N" ?6 a
3: Y, `1 A# R" }5 w5 }
4- H% q+ N4 y, n$ _9 x- L# y
57 {! B. N. N8 ]: _
6
8 A: V$ N6 s$ V5 ?9 X$ Q1 }6 d7! M: M! }) s P7 q
8
% X5 b% Z" }9 H( e9
7 a5 b& t) x4 t# s. ]4 J6 ^10: l, D: O( y+ y5 L7 c
11- F9 S# _; [8 k$ z2 {# R8 A1 O& L
121 m% D" N! R0 X- m( g
13
3 Z! X% \/ N% C; n$ N3 F计算房屋每平米的均价avg_price,以***元/平米的格式存储到表中,其中***为整数。
5 L4 Z% A8 J0 X8 h6 `* W"""
n: C# H& M8 F' b0 b- e9 ystr.findall返回的结果都是列表,只能用apply取值去掉列表形式
" z0 b( B" F5 Y# o F) G3 D3 ]参考答案用pd.to_numeric(df.area.str[:-1])更简洁
( Y, p. j$ g; b, }& k由于area和price都没有缺失值,所以可以直接转类型
9 d6 W k7 S4 g" G3 E9 g. L, ["""
( R% G) w% s5 f. ~; C2 Fdf['new_area']=df['area'].str.findall(r'\d+.\d+|\d+').apply(lambda x:float(x[0]))
: v. s" H9 O& t, ndf['new_price']=df['price'].str.replace('\D+','',regex=True).astype('int64')6 r: l5 _7 q3 N, }, e# W/ ]
df.eval('avg_price=10000*new_price/new_area',inplace=True)7 |% \7 Q1 Z# `( A8 V/ V
# 最后均价这一列小数转整型直接用.astype('int')就行,我还准备.apply(lambda x:int(round(x,0))); K: ~, A' r. l# e9 {- a
# 最后数字+元/平米写法更简单
& v+ l; C9 x$ J. F2 k; l6 _df['avg_price']=df['avg_price'].astype('int').astype('string')+'元/平米'
/ j. D6 f4 q" X6 x W0 p9 Fdel df['new_area'],df['new_price']
% D% p% k# V' f* ?4 d* adf.head()' Q e$ L9 ?5 a$ z1 `
( c w; n* l/ y Level Highest year area price avg_price
1 I/ z) v. f o) @& q% Z0 高层 6 1986 58.23㎡ 155万 26618元/平米
; K& n4 [, `4 d7 h; `5 J; [: f/ N' s1 中层 20 2020 88㎡ 155万 17613元/平米
" S& f. M* k: t* W9 Q1 d; ^3 D2 低层 28 2010 89.33㎡ 365万 40859元/平米
7 p& C1 c& d4 `6 b3 低层 20 2014 82㎡ 308万 37560元/平米- J, Q% l" j( _6 c2 p
4 高层 1 2015 98㎡ 117万 11938元/平米' N* p6 R0 ]# Y6 i4 r/ x: |) K
$ g O7 { Y0 e% ?- c1
6 i3 [8 @9 i& C! k' y, P! f- }" [2) c( x% N" j- s
32 B4 L3 R; o6 u5 o+ u- r4 C
4- R/ z; q8 k' \- B- N( h/ d9 ?+ C
5) @7 K& F* `- Y/ H% G1 _# A! h7 s
67 C! [0 R1 `6 I. Q' D5 z5 G
7+ {4 B2 u5 a# j: s6 D
8
+ M- z4 q7 x6 c9 V( B) |, } a9
' Q8 M5 m! x y5 e7 {: K108 c* G' u, q; J) Y' {' G$ Q
114 n! w5 _, v- s% b. C' v
12
; w8 X5 }% n( o3 E8 v$ x13
6 ?" c! q$ i- a4 v4 L# O. D- P14 f4 [! t" }- f# b! j
15
, m" n( C* V( N$ y6 A16
3 u' Z5 Z8 T' D" D17
4 \: @0 s- X) Z. Z: Q18
8 @$ ]. B7 y* i) ]19
/ T! |4 w* V1 z8 U% A4 k! K207 v' `" k& u* I4 g# r# o9 M @/ o
# 参考答案
; w' y: h. K1 Us_area = pd.to_numeric(df.area.str[:-1])
1 d% {1 l. G+ u7 Xs_price = pd.to_numeric(df.price.str[:-1])
4 l. n- k7 e% h( Fdf['avg_price'] = ((s_price/s_area)*10000).astype($ p( q$ I% T4 l9 b& b
'int').astype('string') + '元/平米': r% \ A: L: ?3 p- N
+ C6 |+ i) _( p% jdf.head(3)5 V U6 p% p$ }) E( D* S! F
Out[167]: , y* \$ H0 D; j* I; \
year area price Level Highest avg_price* J2 o7 i. N$ G# R
0 1986 58.23㎡ 155万 高层 6 26618元/平米
$ L: I& b9 @+ c( [5 W3 L% {' m1 2020 88㎡ 155万 中层 20 17613元/平米1 m$ r. d0 [3 m5 ^% z+ [4 |0 @0 y
2 2010 89.33㎡ 365万 低层 28 40859元/平米
5 u% Y6 N6 ^' S0 Y14 x4 c8 Z: v3 t6 |% K
2
6 k2 _. y% {( _) Q31 n$ T; C3 A5 e" m% F) n
4 D) [7 |6 c& Z8 A+ u$ j- ?) h' s
5% g2 C; B1 Z* {2 i
60 F/ r: I1 }5 a0 L$ ?8 C% ?
7
6 u+ A3 d4 c5 G; `, d1 l! D b8. a8 F% g. @9 g1 q- h3 a
9
; a! u5 i# d6 c# y9 b101 \& }$ [6 U' y& q
11
0 C, E5 d0 r" H2 Z- u8 Q12
+ Z9 K( H2 L6 n. |+ _Ex2:《权力的游戏》剧本数据集
) j" g# ^0 B$ s' q, u* C现有一份权力的游戏剧本数据集如下:
- S7 z+ I5 c" |( u! |. @ m& g9 P0 [" W' @1 l; w
df = pd.read_csv('../data/script.csv')
) R" p1 a* V5 ^3 S& J7 c* t3 Q$ p% Mdf.head(3). p& @% S6 Q1 {6 g
6 ?- i/ J( N: ~' l: e/ }' }$ Q5 c3 u u
Out[115]: . h* g/ R. Z1 @# h$ B; ]) i& F
Out[117]: % t0 y% w( r; H+ b+ F
Release Date Season Episode Episode Title Name Sentence/ P# Q# e- O# }* C+ V/ A
0 2011-04-17 Season 1 Episode 1 Winter is Coming waymar royce What do you expect? They're savages. One lot s...
4 K J" z$ D2 m6 |0 ]& O; O- `; `$ N( U1 2011-04-17 Season 1 Episode 1 Winter is Coming will I've never seen wildlings do a thing like this...8 E k: B! z/ @! D
2 2011-04-17 Season 1 Episode 1 Winter is Coming waymar royce
/ Z. ~& G$ N: K$ o; U/ [1
: j- {0 C0 M/ ?* J1 s: j; S0 W+ n22 `; T$ [( T1 [/ I0 W- x1 `. H
3
4 D0 f! g4 ]# `; u4
( v+ x$ e; g5 F7 }51 y g( D+ x; u; L; f
6
' y# x. e9 J$ L, B7
1 V. l- b Y( l) z8& X; W {) D. ^: e% d
92 x, G$ T+ `; O3 Y; A/ Y
计算每一个Episode的台词条数。4 _& S! X' h9 I( B" i
以空格为单词的分割符号,请求出单句台词平均单词量最多的前五个人。! x0 e$ ^: _- P
若某人的台词中含有问号,那么下一个说台词的人即为回答者。若上一人台词中含有 𝑛 个问号,则认为回答者回答了 𝑛 个问题,请求出回答最多问题的前五个人。( j& `7 C( Z& c$ e
计算每一个Episode的台词条数。' ~; q5 F; o7 u; v
df.columns =df.columns.str.strip() # 列名中有空格
: F% e7 p4 f( H: _) X o5 g7 G8 V2 O" ]6 Hdf.groupby(['Season','Episode'])['Sentence'].count().sort_values(ascending=False).head()
6 s; ~& Q( X2 V, \- o
- V' ?: }, j7 U1 ^4 r! A( n+ pseason Episode 3 E* D4 `1 d: G, U h: Q9 a" F) O
Season 7 Episode 5 5057 v y& Y# }8 r8 j# F2 \3 P! W
Season 3 Episode 2 480
: F/ h- E5 d: s$ m; t& sSeason 4 Episode 1 475
+ K# r8 n' m2 a/ JSeason 3 Episode 5 440
6 b. t. w9 b+ k% _Season 2 Episode 2 432
& n' Z% E7 i) U8 Y- \4 r* S1# B; `. c4 L7 P1 k% ?6 l; V/ Z
21 v2 ~$ b" M- G: M' ] [
3
3 e2 W5 W! o, l6 |+ R3 _4" A2 ~1 W" H1 g7 @- S6 C. \
5+ {2 F* y- ^0 Y4 t9 ~
6) |1 l" U$ p* X+ _; P1 Y9 w
7' i# W' Q* Z. w& f/ e. [# ~+ V
8( x% H' i4 l; {/ V$ l
9
/ | ?' r9 T0 R8 a; p" r以空格为单词的分割符号,请求出单句台词平均单词量最多的前五个人。
1 ^% G" T9 n4 L6 o4 ]# str.count是可以计算每个字符串被正则匹配了多少次,+1就是单词数
$ E* _% s. f [4 i9 edf['len_words']=df['Sentence'].str.count(r' ')+1
, b0 B0 s4 X0 j% H" d1 Fdf.groupby(['Name'])['len_words'].mean().sort_values(ascending=False).head()( Y" T! J3 k( n
" I, k) d: p3 X: XName4 j: l2 g, W* T8 z* d
male singer 109.000000
% k' g+ t0 R/ tslave owner 77.000000
' |* I9 V( K6 [3 H/ z4 N: tmanderly 62.0000003 q+ ~" W& ^. R. G: T7 W8 e
lollys stokeworth 62.000000
: ~ }( k% |* `, m Odothraki matron 56.666667
% \: ~1 w3 w- f% }& x, M; AName: len_words, dtype: float64
* x3 _4 K4 q2 a8 ]( V# ?1
2 |, Z$ B1 Q5 g& d M% w, D2
$ c1 r) [; J+ v+ }3 M* z2 g: O. c' f) e* s
4: E! G0 G/ H5 H2 u0 ]) m
5& ~& O0 H; a x9 w6 P- p" G. L
68 s/ }6 q8 w) h4 q' Y6 ?& T
77 Q8 u! e8 _$ f6 @. T, S2 {
89 i! f( h$ V6 R+ {
9
- i* Y, R% S2 S% p- E, P106 }& K/ A) h) m7 ?
112 u0 `/ U) o1 v
若某人的台词中含有问号,那么下一个说台词的人即为回答者。若上一人台词中含有n nn个问号,则认为回答者回答了n nn个问题,请求出回答最多问题的前五个人。9 }( x/ o) Y* b1 q; W" N
df['Sentence'].str.count(r'\?') # 计算每人提问数
. z% c9 f( d) C& ^! w4 N- Als=pd.concat([pd.Series(0),ls]).reset_index(drop=True)# 首行填0
2 h# }& C) o% X, v! T! _6 ^/ A5 Ddel ls[23911] # 末行删去
% \6 Y* e+ u9 [5 Bdf['len_questions']=ls# v1 k2 L: H$ |7 n' _' H
df.groupby(['Name'])['len_questions'].sum().sort_values(ascending=False).head()
5 k" s# n7 e2 E! S8 t! K) a, `
& |. H; G% ?# C& ]. l7 oName+ Q% q8 n- N' J3 A( F
tyrion lannister 527
% h# l1 z0 A" X8 D% n3 ^jon snow 374
' r7 _1 |1 _: M. O7 wjaime lannister 283" Q$ k4 J+ p, q+ j- N& j; n
arya stark 265
- ]$ M$ g& ]/ ?1 t" a9 f1 B& bcersei lannister 246
: o+ t7 ?1 W5 c, a) x+ ^" |Name: len_questions, dtype: int644 d8 W- Z; P8 \! E% ?
5 C6 V! ?1 N4 U
# 参考答案
5 H- M( q6 z9 v4 C- Xs = pd.Series(df.Sentence.values, index=df.Name.shift(-1))( e( s/ B5 v/ [( m) O1 k, v% j
s.str.count('\?').groupby('Name').sum().sort_values(ascending=False).head()1 l% _' h, V t* ^5 z* q
: Q ]* @, {( E2 ~8 A: c2 s2 G: i
15 D& a$ e& H$ u3 D' W9 e! }! V8 O, f# a
2
: |2 z: @9 R# J( b3! W: \" j' O2 S6 E G* r& g7 D
4! C$ o: I' _& x8 n/ G) f
56 l: Y5 M% Z5 z$ t- d% l
69 N6 h: z0 |$ b f) z% j$ K
7
8 D, @4 j& o6 a9 P- n8
/ m- e2 n( Y7 ~2 O3 B- y, ^; U9- A7 ?0 [- r0 \$ K! P: W6 D( o! b
10
: L1 L% R( P- C9 u9 V; q4 v& m11% U3 ]. ?( A) ?! ^* ]) T* J4 b
12
# x; S* ?6 u; q T4 `: F13# t8 R% Y! I. S: Q( ^
146 g1 j, z0 u0 C; C" w
159 J( _2 P" Q% g! U: s
161 S' [5 S+ c9 l; g5 v+ z# v1 u
17* v; e! N5 k; S% D N. p
第九章 分类数据
8 |+ e' o! C3 R2 O" eimport numpy as np
X' S+ c! O; Pimport pandas as pd
- }9 {$ d y6 C+ b# k0 R1
8 s) R6 G7 N8 f% {, |; I2
( N: ~, r( `' r8 S2 V8 [9.1 cat对象
1 W9 y) U3 ^' \ `1 y% a9.1.1 cat对象的属性) W! J. v T9 l+ F9 w
在pandas中提供了category类型,使用户能够处理分类类型的变量,将一个普通序列转换成分类变量可以使用astype方法。- p; W/ S) R# D2 M, s
# a' ~3 Q9 X( V% fdf = pd.read_csv('data/learn_pandas.csv',' ~+ X2 w+ K3 n2 [' ]
usecols = ['Grade', 'Name', 'Gender', 'Height', 'Weight'])6 A0 R& b9 K6 v0 f9 O- i1 A
s = df.Grade.astype('category')3 A& _, q3 K& f9 _
3 w: G+ N& N* h6 w5 j: ?( j% h
s.head()
_! m7 |+ f5 y2 kOut[5]: 4 l( h& L6 Z! G3 {: c
0 Freshman
5 c2 G9 j" a8 w3 W1 Freshman' k( K- n6 t B
2 Senior5 U! f& c$ Y" P
3 Sophomore
8 C" U# j$ |: b0 L4 Sophomore, Z! E4 m0 T; l" ?/ ^
Name: Grade, dtype: category" v: w3 I: s; g) t9 k u' N. @+ P
Categories (4, object): ['Freshman', 'Junior', 'Senior', 'Sophomore']
% t6 a c! Q$ J17 D* j4 Q; \2 K
2
5 j* F; m! b% j+ _2 E3 I7 T. v: Z0 O" T" c! l6 f
4
W/ r. A/ g$ U1 X1 _$ D5
3 I0 H6 ]# v* c$ L4 }6( Z9 A, S' _4 g: e
7; t+ s0 ?# E& Q' C
8" r0 v- I' s7 L6 u5 a0 Q1 X
9 O9 A; w- O+ w- W; n3 [2 P5 W
10
7 O; l7 t9 d/ @0 S$ P1 z0 W$ k$ M11
! ^/ z7 v) ?4 y' S z7 [8 U12# b a t+ ^% Q* J" U M4 d; Y! b
13
- S0 T# z8 Y% S. N; r 在一个分类类型的Series中定义了cat对象,它和上一章中介绍的str对象类似,定义了一些属性和方法来进行分类类别的操作。4 A, p, D0 e7 y+ N+ C5 j& I
5 N" j( r# b$ S% P% k+ vs.cat
- A, Z( `! L3 G, p1 WOut[6]: <pandas.core.arrays.categorical.CategoricalAccessor object at 0x000002B7974C20A0>& l( c$ w, m1 _4 A1 B- ]
1
! S5 \* G( o5 A2
+ g; t- S2 C4 i6 ~& Z% M$ T- Ccat的属性:
, \9 ?8 _7 N7 v2 K' W& h
: K5 V% H: }. A# pcat.categories:查看类别的本身,它以Index类型存储1 f5 @2 |% H1 ?
cat.ordered:类别是否有序
. f) j0 \1 c! V0 r5 T( ~8 y- bcat.codes:访问类别编号。每一个序列的类别会被赋予唯一的整数编号,它们的编号取决于cat.categories中的顺序 ^/ o( w9 H% ?* X
s.cat.categories
3 [* P! ?' n3 U$ nOut[7]: Index(['Freshman', 'Junior', 'Senior', 'Sophomore'], dtype='object')
& e8 a4 u" W& t. U4 [0 E- a
9 Q( i% `1 y8 L4 F0 L: e' ts.cat.ordered
6 a c) A) l6 D: dOut[8]: False
! h( I, s# I: ~) ?$ R2 n0 P
& f3 R0 ^$ Y8 o; d$ o) zs.cat.codes.head()8 T- H4 z* x2 ~( c
Out[9]: ! p, J6 g9 P* M. D1 q) U
0 0! i e! D6 v+ G
1 09 N2 O+ R- r2 |. R$ D! k
2 2% \4 ?$ L6 f) g2 O$ P0 H9 @2 k
3 3% R% M" ?+ |2 H; O: _: P
4 3
8 F" D& w* g, k$ M2 gdtype: int8
1 q+ s/ ~) I; R! {1
# y) v: l# o6 b" c+ z# G" N) D2
$ P/ f* R: A4 F" o% D# b. }3) H( J3 \9 N: x) ?
4
- X" \0 L0 a- q9 T7 _5 U7 q, B3 f/ c# u+ G% m
6
* g4 N |+ M; @+ w7
1 K- l' A4 j) Z/ f- S8" x+ }) t' n- e% Y6 V4 z
9! {% u: b% J$ H6 h: v7 z: V# ?
10
2 E% ]: L% @4 m; W$ @11
0 W( \6 q8 @' D; @12
' a( ~8 } } g- a/ `7 X v* V) {13* l6 S6 l" c: r
14+ ]5 U6 s) I* F5 X/ Z7 y6 A1 q0 I
9.1.2 类别的增加、删除和修改
/ H1 \4 `7 W5 K4 S6 k; Z 通过cat对象的categories属性能够完成对类别的查询,那么应该如何进行“增改查删”的其他三个操作呢?8 T K7 Z1 l( w [+ t, C. S% p
9 S8 D. a S; i: O
【NOTE】类别不得直接修改
2 o {* X( ?, V4 y7 }% z在第三章中曾提到,索引 Index 类型是无法用 index_obj[0] = item 来修改的,而 categories 被存储在 Index 中,因此 pandas 在 cat 属性上定义了若干方法来达到相同的目的。
$ e. f0 i' |# Y5 Z; h$ I2 H/ z6 l4 l* Z: @" s) }
add_categories:增加类别
3 g- d$ \ s# s" [1 ~! cs = s.cat.add_categories('Graduate') # 增加一个毕业生类别0 T' ~; P6 [1 K
s.cat.categories
E* f; D5 s/ ]( B. e0 f% U6 x! {* a, L
Index(['Freshman', 'Junior', 'Senior', 'Sophomore', 'Graduate'], dtype='object')
# u) \* z- R3 o, S1 s1
+ R1 t, e2 t$ @1 Q2' `( f* H& F$ s5 t! `6 c% q" `
3+ h3 T7 `: }8 Q! z
4; _- w9 {* I3 Y7 A$ }: g/ Z" r8 {
remove_categories:删除类别。同时所有原来序列中的该类会被设置为缺失。
3 h L) F6 `9 _8 F- c! G6 A6 ws = s.cat.remove_categories('Freshman')1 T( [' i. l3 Y# h$ v T! h
3 J( P* G* P# q m/ ?$ as.cat.categories/ b: a& N1 I1 j0 \1 ]9 Y8 p
Out[13]: Index(['Junior', 'Senior', 'Sophomore', 'Graduate'], dtype='object')1 c# C& Z3 J& \) s
" O( ~( \( a5 K7 h% ^) G; K6 x
s.head()
/ ]; s" O. V& K; U# O4 U7 xOut[14]:
6 j/ q6 c5 ^. E( f0 NaN
# w J9 O$ j) g! D* H1 NaN/ [. k2 O9 k. \
2 Senior" `( H Y% ]' n4 r! s; T( E
3 Sophomore8 x6 y. z9 F' ^6 q( a( k
4 Sophomore
1 V1 K3 ]! b9 C) M& }* @7 @: GName: Grade, dtype: category
/ u" I$ |! f m9 o YCategories (4, object): ['Junior', 'Senior', 'Sophomore', 'Graduate']+ y8 ]' x& y7 v% e% _4 J1 F
1
% O+ i! |8 _" ]21 {$ O3 l' m, _) B5 V
3
" | c/ `( i' k% }4
& ], k# Y2 [4 P- P5
r% L% ~) {% T% \. n @68 v" I9 m& z' g3 R
75 b+ H6 |1 K( @ O# ?1 G" ?0 r
8
* c5 ]0 _1 |5 B! Y% t$ \9" O$ J4 W2 I9 @* u( o3 a8 B
10! Z; u9 ~' u0 {+ c# \- o
11
4 ~; N1 A& J$ I! K12
) k2 L4 t* m' ^137 k; Z' [* n/ ]9 \! g+ C
14
- B/ Z4 a2 G5 O* _" }8 V" t4 x5 Qset_categories:直接设置序列的新类别,原来的类别中如果存在元素不属于新类别,那么会被设置为缺失。相当于索引重设。
! `9 R" L3 V6 s$ [' V% Ys = s.cat.set_categories(['Sophomore','PhD']) # 新类别为大二学生和博士# i( @% w2 e# p* T5 X) M
s.cat.categories
& L% c* B$ v- h8 w7 O8 t; T0 TOut[16]: Index(['Sophomore', 'PhD'], dtype='object')
4 _$ T+ W' W& F1 Q
8 H1 C7 e9 Y9 E% y! j: j- t+ l$ n0 os.head()
/ c$ T2 n0 I0 |5 l. `/ kOut[17]: ' N0 y1 W% D. H, {4 y/ I! w
0 NaN
0 x4 e n# ^, f; Y& `+ W( o. C) } _1 NaN. g9 N4 l8 |! f' q
2 NaN& ~5 A9 Y1 P/ |+ G1 k
3 Sophomore2 J: j: a5 N$ N) Y
4 Sophomore
. i; }2 j9 _& D) v5 PName: Grade, dtype: category
* t* L4 }8 G# O- d: T9 s2 uCategories (2, object): ['Sophomore', 'PhD']
$ i' `* f8 L# E3 A7 Z5 \0 ~& {1! w6 [; J5 u) d; Q7 {
2; \% W# B( m3 i
3
5 o1 J E- E9 Q2 K45 y4 c' _6 w$ u# d# o5 Q
5% ?. u. V4 E4 P5 C! O; ^
6& h. a; i; u3 x; O5 l1 M2 c6 Z
7
6 j% H5 @+ Y7 z; ~89 z; } T( [# i4 J
9
8 l1 T5 `- V$ o8 t7 ]104 |. Z9 W& d/ _0 d1 y0 n
11- o' D7 R0 A+ ^. a2 I4 o3 C
12
5 D( g, l0 m" H/ [5 U* Y13; M% ]6 V2 X( c' q8 b& H- d. i
remove_unused_categories:删除未出现在序列中的类别
! Q( I9 H& c( E" a* Is = s.cat.remove_unused_categories() # 移除了未出现的博士生类别
1 v& c$ \- x' O; ps.cat.categories1 s% g% S( U3 ?
# W" r0 v9 P1 s# C7 hIndex(['Sophomore'], dtype='object'): M5 m. ]! O$ J' {. I; c/ ~- Q( L
1
$ b. x& m/ B2 \4 q6 ^9 n+ I( @/ p27 T8 o7 L4 L5 f2 t7 c4 r
3+ k* u& k* |7 G! X; E4 H
4
1 ]. J+ L' z3 Erename_categories:修改序列的类别。注意,这个方法会对原序列的对应值也进行相应修改。例如,现在把Sophomore改成中文的本科二年级学生:
! e2 X% b2 n; B9 _s = s.cat.rename_categories({'Sophomore':'本科二年级学生'})
{% F% P. P0 x- vs.head()
7 J. H. d- v3 \. k/ M# ?; ~) \# o( z) G; a( T( j3 P/ E+ h, {4 y
0 NaN2 q! X7 V: d5 {6 m& K; C; Y1 q, c
1 NaN
* ^% h) S4 o6 z9 w' A F2 NaN
8 q$ y; H7 @; z/ H& e& \3 本科二年级学生
/ d& ?* q' B) }4 本科二年级学生
/ I7 d3 F) G9 C ^4 `# o. d1 d eName: Grade, dtype: category
+ `; Q H8 L' vCategories (1, object): ['本科二年级学生']+ d0 j& }, C8 I8 v) D/ k' r: \; [
16 B% R% b* `4 G% w
2
, ?' _( [# N- ?5 q. v, [1 z. A3
# H2 z+ {- G7 I7 j4! Y# d& ]$ {" t
53 o; A; f4 X5 f1 L) ^
68 J- E+ M2 G! ]. X
7
- \+ i- [. m# K. I$ M! V# N8
# w- |1 \# H3 C$ u9
4 q# c: G8 C7 b1 j10
* B7 J9 f# b4 {. a9.2 有序分类
% W7 d5 W& Q7 t. d9.2.1 序的建立: H! e- m6 W0 _& |, n; N
有序类别和无序类别可以通过as_unordered和reorder_categories互相转化。reorder_categories传入的参数必须是由当前序列的无序类别构成的列表,不能够新增或减少原先的类别,且必须指定参数ordered=True,否则方法无效。例如,对年级高低进行相对大小的类别划分,然后再恢复无序状态:; O4 w2 ?% N( U
1 @% f" N: u" r, U! w ks = df.Grade.astype('category')2 ]; F) ^9 T: {5 O1 M
s = s.cat.reorder_categories(['Freshman', 'Sophomore',
& N- m+ o1 Y0 p& K5 i 'Junior', 'Senior'],ordered=True)
+ F! J. V% p/ d8 ?) W6 X" j8 U2 Zs.head()5 P S4 m/ r: d1 u+ m
Out[24]:
* `+ f6 c" t& c6 i) ~2 R1 m0 Freshman$ I3 K, E9 x' G+ l' c
1 Freshman
) D! o3 N2 i4 b2 Senior( N- Z2 }9 [& a& R, [
3 Sophomore7 n2 ^6 v) G: R! b8 M
4 Sophomore
; t: O/ `3 b" J& f6 `- l$ G: b7 PName: Grade, dtype: category6 X6 n/ W/ ~. X* z8 R
Categories (4, object): ['Freshman' < 'Sophomore' < 'Junior' < 'Senior']
" \4 h+ @0 v' R5 ]) {
" _7 e3 F- i8 P0 [5 y6 R& Es.cat.as_unordered().head()
/ M- ~' ?, O& m7 H1 E% h4 HOut[25]: " j& i1 z5 t5 d4 O' t0 Q! d, g
0 Freshman
$ j' e- W, @- ?) R' p1 Freshman. O' o) k4 U* U2 @0 ~' E% {2 U p
2 Senior+ {' {3 A6 z( g! C
3 Sophomore% }, U! J; r" ~2 z$ d3 I2 _6 s
4 Sophomore
& O* T" D+ h% H4 S$ I# DName: Grade, dtype: category; q/ L" G+ x$ _
Categories (4, object): ['Freshman', 'Sophomore', 'Junior', 'Senior']
/ d+ K1 Y! Q- s( z0 G
" y" W2 z Y" Z% b6 g( a9 A, G' i1) }5 i2 W/ T V r2 Y/ R! x
24 x) |& o8 L* g( `
3
" G+ R8 d0 e1 n4" V! r" Y/ A/ F7 d# A
5 e# \0 T; f0 o- r3 L( N# M
6
, ]$ c% N2 C* n! u6 |: M P2 q7
2 ]7 C/ i; H |$ f" d8
- i3 B7 D' a6 ]5 C9, v; Q, F* d5 f1 m- J+ J- g. L
10! f/ ]$ f4 B8 \: M/ u
117 W/ S! M* ]. S7 x
12
/ @1 m2 e9 m! ~) q" G13
5 a- F; ]8 N+ b, k& ]. K+ I$ L14
: @" L$ A5 I5 B8 f# R0 d) n15" f* q+ ^, F/ |6 i7 z, c4 X" G
16 S! [& l* Q+ U
17
3 G, e8 a5 k5 I6 x3 Z L- ?18
& L: ]2 C0 }6 \6 S19 o9 W3 M7 V! M5 n& J) S5 r
20; J, L& _( K; t! L
21
8 L3 _( F2 W- V8 O1 I% W8 k6 F22
. X# l0 D b( ?) c7 D 如果不想指定ordered=True参数,那么可以先用s.cat.as_ordered()转化为有序类别,再利用reorder_categories进行具体的相对大小调整。
3 e! y! U+ I1 K) N2 o7 o$ I/ V
# v$ |, q. J r3 u# Y! h7 X9.2.2 排序和比较
2 z; B0 x0 \: f+ h' N# H+ b在第二章中,曾提到了字符串和数值类型序列的排序。前者按照字母顺序排序,后者按照数值大小排序。6 h! }- N4 `8 Z1 d, v1 ^) t/ t
7 W7 J: B& ]1 H8 A/ }7 P
分类变量排序,只需把列的类型修改为category后,再赋予相应的大小关系,就能正常地使用sort_index和sort_values。例如,对年级进行排序:
6 h: v3 I5 J5 K, ~; P0 e! {( h2 s# V. H% d
df.Grade = df.Grade.astype('category')# w# E( @( L8 e0 n Z
df.Grade = df.Grade.cat.reorder_categories(['Freshman', 'Sophomore', 'Junior', 'Senior'],ordered=True)
+ I* J# A1 G$ V Wdf.sort_values('Grade').head() # 值排序9 ~& F7 T0 X$ m! ], p5 T$ ?( p2 I1 ]( o
Out[28]: ' g0 ^1 E& u* P, @+ T" E
Grade Name Gender Height Weight
0 T4 \$ |2 w2 _$ v( v, q0 Freshman Gaopeng Yang Female 158.9 46.02 {! ]. z- I, R# B6 p2 ~8 u
105 Freshman Qiang Shi Female 164.5 52.0- u) Y" H4 l& A a. P, Q
96 Freshman Changmei Feng Female 163.8 56.02 ?. Z* h8 `2 X. w7 Q6 w) q5 J" _
88 Freshman Xiaopeng Han Female 164.1 53.0
+ \) B$ C2 |; Q3 i4 h: i; N81 Freshman Yanli Zhang Female 165.1 52.0" `- I! i9 ~% |3 M& `
4 b( I: l( q' Bdf.set_index('Grade').sort_index().head() # 索引排序
! ?( s$ [* r9 ]Out[29]:
, f( ^4 j g2 n$ Y9 z) a Name Gender Height Weight
# `/ i2 R! E. t$ E. }4 {Grade
( O. R1 s8 u) wFreshman Gaopeng Yang Female 158.9 46.0
, ]: Z4 t( T: Z9 m P9 Z, BFreshman Qiang Shi Female 164.5 52.0
5 Z5 @/ R/ L* c( j& nFreshman Changmei Feng Female 163.8 56.0
! q% |" k! K0 Q) Y E4 eFreshman Xiaopeng Han Female 164.1 53.05 m( T1 C, m6 H! C2 a T3 G
Freshman Yanli Zhang Female 165.1 52.0& ~. m! `. z. r2 c$ q( T
% P( r2 p9 j- N0 E1
3 J4 ^5 W+ S8 g. E- A. s2- a' s6 F/ y0 l& {
3, K9 a4 x( q9 Z5 Q: C' l d
4
8 [- X9 \2 S1 g6 `: J+ h+ N x$ H5
% i, t' _2 R% }# `6
z$ M. a: d# S% L& p' f7
& K* {; I- r9 n2 X8 o' z4 I8
5 t4 M) P# {% t5 f8 k: B9
$ Z$ M+ o# W. i) _. u7 b! [10( ^# o, R' o q: F1 A
11
) A2 N8 |1 s% R8 Q3 J# T12! O% A& n% V* T0 X2 i F
13
. l4 ?; S _( P$ d3 @( l2 F: |14
$ {" t) j% A$ F. l' P. D) |15
; O( F- u4 \1 y$ ^2 P8 h16
$ ]+ ^ E6 B, I! d' e) e! e17
$ r' k; [1 t& ~( j& ]: s( L181 m0 c# {0 H' P" Z" a# I
19
* v3 o8 J% P/ H: L: G! N& {& d20 A5 T3 b$ Z# h% w3 z) R+ [
由于序的建立,因此就可以进行比较操作,方便后续索引操作。分类变量的比较操作分为两类:
/ U0 c; D: r3 w0 L9 Y
- q4 ]' g+ o4 J7 Y8 f==或!=关系的比较,比较的对象可以是标量或者同长度的Series(或list)。(无序时也可以比较)& o- u# P' _6 H: q6 P. J
>,>=,<,<=四类大小关系的比较,比较的对象和第一种类似,但是所有参与比较的元素必须属于原序列的categories,同时要和原序列具有相同的索引。2 D1 G4 t/ f$ A! O4 P& f
res1 = df.Grade == 'Sophomore'+ u/ a m" j* x* l
7 v& E$ a- R9 ], c. Rres1.head()( r6 U/ G9 h) i# o/ a$ u6 C6 e
Out[31]:
0 q* v. }6 v) v4 W7 V* t% M4 b9 d0 False* Y+ r- M7 V( ?
1 False+ B: a: Y& y" p+ o$ \6 {
2 False
- t0 i' T( x- X1 ?* ~3 True, t: D3 b, ?4 z. K. A6 H* Z
4 True1 H% ?( x$ F* P3 z% e
Name: Grade, dtype: bool$ W, \8 w# l6 v& S
% F! S" e* P' y2 J& ^! m& Lres2 = df.Grade == ['PhD']*df.shape[0]
' h% H4 q* j3 l9 ], c9 r0 g" d, V
" S* b' ]7 Q1 U9 S9 Bres2.head()
, v% ]. O6 V# V- `0 k% j7 UOut[33]:
! q( d: B6 L6 g$ `' F" s0 False, w2 [: J' Z2 G% s
1 False0 f/ W. C5 {8 o* a. Y
2 False, k% U7 P) c; j. P
3 False' u3 u* f$ O& D) q' b
4 False$ N/ G- c$ y6 G$ K/ Y
Name: Grade, dtype: bool
- b0 a( l; l6 m
0 i) o+ R3 L$ j( nres3 = df.Grade <= 'Sophomore'
" z& B% x) A! J; a5 R$ s$ }$ q- a
res3.head()2 c1 v5 z( I! C8 y/ n: D1 H8 k
Out[35]:
6 Q$ @1 U" u5 D1 E+ u0 True
( r& d8 b8 a2 B+ W1 True1 X# y$ q& \) h+ z2 X" G6 ]
2 False) T( |* m$ v( k7 ^* p2 q
3 True
9 k( S' t7 }/ h; f0 T0 a4 True# n, J& ~$ T% v1 N1 s
Name: Grade, dtype: bool6 M5 J# I& l2 g% {7 K
( ^0 o# s- b3 `* H- U) Y$ C5 x# sample(frac=1)表示将序列随机打乱。打乱之后索引也是乱序的,直接比较会出错,必须重置索引。
. I- [+ H q4 M: \9 b; dres4 = df.Grade <= df.Grade.sample(frac=1).reset_index(drop=True) $ j r, R# Y* c2 k
' C: l8 R; C7 p1 T4 i
res4.head()
; F5 ^, @% n! m! v6 d+ lOut[37]: 3 z9 h I; s i5 v3 T$ e* u3 O
0 True
: j, q+ ]* G9 c% u, @* o2 Y1 True& }8 j# f$ a. \9 h
2 False
* J( S1 n1 f Q' x" Y9 k3 True
% ^. k* q. T l: m& l4 True, h& K w4 h4 o9 ^6 T) V
Name: Grade, dtype: bool4 R) N% B, ?) }" p" s
# d, O6 z! Z5 ~3 R1# n) g4 h8 Z( W: B- q0 v
27 X4 \9 [$ S3 T: b! M# ^
3 q4 c1 Y, V% s* H" i( H# m
4
, {7 g8 l0 d( c7 m: e5+ Q( X! P: x3 E0 T
68 w( f, y9 H! u9 g7 P
7) Z# d: f" e7 u. {0 m! Q/ W
8
, K }- k3 w# V- N, t9: v& x: }% X* U
107 C6 @1 p) X: z3 K. N6 B
11
& E5 W/ Z+ l; J% \- O& h4 ~8 n ~0 f: f12
2 H8 \7 k% Y$ w1 O. R13$ Q0 G0 K# k( |, b5 z. C
14
5 R% A' J6 w; H+ g I4 u15 b& R0 Z- o% C, c
16
4 g& B( T# v- Y17
7 W1 {. x$ ~5 K5 `: @1 }" X, n18
, ^- S) i5 k1 ~ `( E197 S4 {, ?* X0 g; |6 Y7 I" [
20
$ [0 I5 J f, u1 T: D i21
' w6 H5 l( h0 @- G229 w, a+ C2 y [$ Z# g4 ]- Y' B, p
23
( l2 k+ I7 M6 X$ }4 u1 w% Z24) L- G6 H$ t% r6 N2 Q) t' A
25: { U" j1 _) \' U9 i( y
26
: D: y, w4 B: ?' a7 B7 }2 d27
# Q7 t5 j8 t N286 `# v. H2 e- q" d$ P3 g& r x/ `, d
29
5 b# ]1 _3 `5 v% F+ w30% M! b- s2 K& W; ]% P6 [3 e9 p
31/ K! I+ V8 K' z# I, U
326 q7 v; @# Q: O J) B
33
& A8 Z/ `" P# i& W0 B34
; @! y2 \- c" ~$ s! P+ V35* Z* |2 j( _. Q0 g9 t: B7 s
36
- A: h" x" o& g3 T& J# x4 s37
' s0 W6 O+ J1 z' D6 y38
) l: w- ^0 ^$ U. u3 X39) L# ?# j7 O0 x1 C- _9 G& k5 I
40
) Z l* ]1 q! n1 S412 S9 [% y& n% m' L
42; j8 {' q* n+ b* L# W* B
43
1 j7 m+ ^# B5 p7 S5 ^- g443 b! G1 i4 q% W, B
9.3 区间类别
1 R; V7 t! t# q, v9 G1 o9.3.1 利用cut和qcut进行区间构造
& t! Y& O$ @9 ?/ O$ m 区间是一种特殊的类别,在实际数据分析中,区间序列往往是通过cut和qcut方法进行构造的,这两个函数能够把原序列的数值特征进行装箱,即用区间位置来代替原来的具体数值。! Q1 p2 L! F& N% { Y, h/ p
# \+ d& g/ ^8 y
cut函数常用参数有:4 T" l8 T6 k% l' `" B6 @" T8 g
bins:最重要的参数。2 K, {# B- D0 [' W* p. O
如果传入整数n,则表示把整个传入数组按照最大和最小值等间距地分为n段。默认right=True,即区间是左开右闭,需要在调整时把最小值包含进去。(在pandas中的解决方案是在值最小的区间左端点再减去0.001*(max-min)。)
( k/ ?" P0 t) P+ {! s也可以传入列表,表示按指定区间分割点分割。! k% p S# F5 \
如果对序列[1,2]划分为2个箱子时,第一个箱子的范围(0.999,1.5],第二个箱子的范围是(1.5,2]。
2 ?) Z, O- l ~; D6 E; v9 c- h 如果需要指定区间为左闭右开,需要把right参数设置为False,相应的区间调整方法是在值最大的区间右端点再加上0.001*(max-min)。
; t ?- v+ x1 C% i3 Q, O
7 q( S1 P' i+ ~6 b) l! Js = pd.Series([1,2]): N6 _2 g8 o" Q; w. R( z! i
# bin传入整数! P; c4 `& o/ i
! j% y7 ]' G' p0 L( ]4 M" @
pd.cut(s, bins=2)/ x! H7 m1 K6 c9 F' G
Out[39]: - q& E' t1 k( d# k
0 (0.999, 1.5]8 Q0 `) Q3 X; [# a6 y7 w" E
1 (1.5, 2.0]% {4 T0 @$ t! k- w; E/ H
dtype: category
$ _: x% C9 S! W0 K( p" YCategories (2, interval[float64]): [(0.999, 1.5] < (1.5, 2.0]]
! ]4 ?0 P7 N e$ y# A# i0 X: q# o' q7 B3 K% U% ]
pd.cut(s, bins=2, right=False)
9 K; G) C8 c: COut[40]:
9 O4 C4 ~9 q; N' d& b. t7 j0 [1.0, 1.5)
' p8 J y9 B7 y/ T: i1 [1.5, 2.001)" h0 k0 w' S# |. Y, m$ V- f: W% n
dtype: category$ a% N/ i g! s6 E) p1 j
Categories (2, interval[float64]): [[1.0, 1.5) < [1.5, 2.001)]
" T6 J/ t( `, l! z) W
# p6 O: L3 j: [ m% L
P- ^8 A+ k8 J0 l/ y- e3 j5 z1 a# bin传入分割点列表(使用`np.infty`可以表示无穷大):( {/ t+ v8 p7 S2 V& H% w6 J2 B
pd.cut(s, bins=[-np.infty, 1.2, 1.8, 2.2, np.infty]), \; E- g' u4 b$ I% q& v
Out[41]: / U+ G# d; }) q4 P# C
0 (-inf, 1.2]
" ^1 x3 G1 e9 O6 b9 d5 p) t) R/ I1 (1.8, 2.2]
2 I3 Z+ V7 O0 K3 H" Tdtype: category: o! E- U6 L n/ L
Categories (4, interval[float64]): [(-inf, 1.2] < (1.2, 1.8] < (1.8, 2.2] < (2.2, inf]]; r( `. c) ]8 q) F+ c# k* _. ?% Z
9 Z; f, m" |. `6 E! c4 |" `5 E
1# L- c$ |/ q+ m i
2
& j- M. C- a: S( Z, U, H# y3 P! `2 M3
) ?9 Q2 \; I: J9 z5 W6 k46 D# F d2 m- o; y5 k3 Y
5" u# Z) a; V( [/ B" s$ P( g1 X0 f
6
) |/ g! B" c7 z6 K7. L7 X% s7 u2 b6 J8 s
8
0 L4 m# p; `8 u8 h9
1 ~! ^. q _# f/ }" }5 v" Q10
# c: [5 {# {4 k. x- V' [11! m, H" P' v2 p) B
12
0 ^6 J& k6 I1 h- p( B! ^13
( J0 W/ b: D% q; t T145 n2 A* J" z4 c4 N! M% V
15
( ^3 t* S: C% z/ i# f16# t& i' f9 k+ m% ?3 y
17
& ~2 R1 x( T) h+ U% d3 @18
# [( D7 q$ ^) L: L8 s19( q; [8 J# s- D$ ?8 u! ^9 f9 P
20/ r- c) \1 @4 L4 p+ Q2 O* E
21' ~/ `+ G& Q( ~4 j) b4 o, U
22/ _$ \+ B" x) n- x. Q. B
23
4 J1 h7 a" E6 @1 Q0 S24+ n) N3 u; ^7 n: O4 p0 W1 j! K f" B
25
( _# e+ B! H1 j5 [. `labels:区间的名字
- _: `8 Z# L8 I4 B, jretbins:是否返回分割点(默认不返回)0 ~! r; }0 O1 D: S
默认retbins=Flase时,返回每个元素所属区间的列表/ A4 Y7 c% x- x Y& _: Y; I/ T
retbins=True时,返回的是元组,两个元素分别是元素所属区间和分割点。所属区间可再次用索引取值7 J! ?+ l" ~! H* [8 G$ r$ o% h
. }+ g0 e; Q8 P$ x4 X
s = df.Weight
8 T R$ q: Q4 `res = pd.cut(s, bins=3, labels=['small', 'mid','big'],retbins=True)
7 G; s0 i7 n; t Nres[0][:2]
6 [' @. i9 X7 y t; Q8 I% Q9 m1 R5 f" T" b7 m, d
Out[44]:
1 w- `$ i+ t1 p* N* W2 K/ d% H0 small
9 [( w" f2 j7 L {- {6 i! P8 R; j6 x1 big
! s& p8 R5 \: P7 F/ fdtype: category
- C1 W" z% v* QCategories (2, object): ['small' < 'big']8 c- }4 A7 K% X
2 j! ?4 K; M' M$ G. C: D* R5 \8 P" C
res[1] # 该元素为返回的分割点
- ~+ p- f: @4 t* H5 n: WOut[45]: array([0.999, 1.5 , 2. ])
1 c+ d; q. G$ u( E1 `1
& b* Y; f6 z9 V. s. ]/ n2) o1 k* f% e/ P$ L8 D& I
36 Z+ U& P1 h: O- O) E( K* g
4+ v1 g' t1 X8 [$ P' E3 ]$ h
5$ b/ U: _2 M8 ?$ f: G' v# d
6
$ T, ^$ ?6 P) A( ?; m) b7
8 P# h# u" ^5 T$ f% c8% J* _* E3 `% S+ L( r: M" ?
9( b/ P# Y7 d+ v' }) y4 T& _
10
4 P8 v) s7 o4 b# H! }11
" O# e% {' Y" h% o5 I/ o5 W12; o- b; }1 S0 E+ v! k- [+ o$ G
qcut函数。其用法cut几乎没有差别,只是把bins参数变成q参数(quantile)。
: o+ i. `9 v$ @& Wq为整数n时,指按照n等分位数把数据分箱
6 R1 O- ?# t' ~9 [q为浮点列表时,表示相应的分位数分割点。
0 j. Y! ? S. }& z. M* U, j- s# b& is = df.Weight3 t+ r1 h* L' {- @* A2 R6 g
. {& F6 L3 W7 a: z2 mpd.qcut(s, q=3).head()
. D G- {( L4 u4 r2 hOut[47]: 3 T/ { c$ G I$ ~2 J% a
0 (33.999, 48.0]
n9 l+ h5 a1 _1 (55.0, 89.0]
' {4 z* | d4 n2 v* Y" e2 (55.0, 89.0]
! q! D% Q$ L- _4 y8 V1 l3 (33.999, 48.0]% e) E4 [4 U* r" A
4 (55.0, 89.0]3 o' K) {2 `" R; g
Name: Weight, dtype: category
$ U" Y! K6 ~& N) zCategories (3, interval[float64]): [(33.999, 48.0] < (48.0, 55.0] < (55.0, 89.0]]4 |4 b( h/ t: P# W) L& ]- Y
6 y. I: R% M: F9 U A3 I4 \6 Xpd.qcut(s, q=[0,0.2,0.8,1]).head()
' \: J% J; J' L; L3 }( Z# ROut[48]: 2 P5 p: |: X, C
0 (44.0, 69.4]- s, @! V4 ~4 o! s( L
1 (69.4, 89.0]4 v' a! a3 d |* [) f5 e
2 (69.4, 89.0]3 D6 r* c( v9 K, u- W4 E$ {, F
3 (33.999, 44.0]
/ U+ W! Q" w$ [- y5 h. d4 r4 (69.4, 89.0]3 d- G( ^: ^6 ^; M3 \" H: S
Name: Weight, dtype: category* M% B2 u; L1 Q+ G/ f
Categories (3, interval[float64]): [(33.999, 44.0] < (44.0, 69.4] < (69.4, 89.0]]( Q4 @" M- ~4 D4 `
- D1 \8 b8 l2 B- w2 r, x+ A1
, _" n, T# P* F" z# }, ~( C2
' B o: e6 D* M H+ I3
3 n: T4 x3 S) a, ~- [% c4
4 o9 B7 o) e1 P! b' e5
* D; x" r1 ~9 e5 M6
( J( P5 j' z3 ]. t4 _7& R! D7 b9 o# _$ q5 _2 a
8
: A* @6 i* A$ X0 o& a+ Y" u! O9
- x8 Z! h: Z l' j$ x8 ^7 e10
1 `! b M C# F0 F9 O' b11: t3 g5 @3 e" J1 |. Y% P' p
12
, U2 N& A! A8 d2 |+ `' e" y J133 O* _: O! B, q2 Q
14; m* [" b9 ^' W. z
15
9 u4 d" d, F+ k" j$ }+ C) o% J16+ z9 z# Z) c( ~( k, ^) C- e/ w
17
) K6 y$ M% I4 J4 g& x n184 I8 L; e& X7 C% V+ p) M3 G
19
2 U# L- Q7 w0 J! c$ W+ O' T4 y20' \8 K+ p% W/ \5 Y
21
' R) ^! e- `. \ Y% c9 _, v9.3.2 一般区间的构造& s: h8 X. A" M1 D
pandas的单个区间用Interval表示,对于某一个具体的区间而言,其具备三个要素,即左端点、右端点和端点的开闭状态。5 K A/ H5 w: ?0 V; d) F
6 Y, T# T( k. M; R4 Q/ k: _2 @
开闭状态:包含四种,即right(左开右闭), left(左闭右开), both(两边都闭), neither(两边都开)。
) p% O. F) X* |, i( Smy_interval = pd.Interval(0, 1, 'right')
" ^7 E* _' z7 Z: F. Z, P9 {! q; V; ^+ ~# b" v b1 v+ s1 j3 S/ V
my_interval- [! f- Q2 S, P/ H" _
Out[50]: Interval(0, 1, closed='right')
9 z) e1 I0 ]$ }1
: h+ s1 |( b3 o0 r$ ?2" u7 _& A; A$ R, Q
3
1 |! v& k/ G1 E7 Z9 d2 R4: A1 | R7 ?& T7 O% @; m
区间属性:包含left,mid,right,length,closed,,分别表示左中右端点、长度和开闭状态。
5 p+ T- Y( L) B8 c$ M使用in可以判断元素是否属于区间% S0 u: [: J7 Q* V# A
用overlaps可以判断两个区间是否有交集:: R% H& A" ?" |/ j2 k
0.5 in my_interval* S3 @# c0 q2 z9 f* [2 ~1 h
# i# A/ n7 h. u* |( @True
8 P$ y5 ?- F* a8 u& o7 x( d1+ B- S% [1 ~( c. e1 L
2
0 a- ~& U9 D6 n9 s% R2 Q X3
* Y& E( b. c) }* q" l' nmy_interval_2 = pd.Interval(0.5, 1.5, 'left')
* ]2 X2 o6 a$ l$ \) Tmy_interval.overlaps(my_interval_2)2 O t$ T& R/ ^% X% c7 q
$ k O& R1 }. l# P8 ?
True
" A: W6 T. g/ n12 G1 |' p$ ], H
2
, h9 f9 V8 f. e& O37 B6 [6 K8 I& H
4
: s' k k& B8 U pd.IntervalIndex对象有四类方法生成,分别是from_breaks, from_arrays, from_tuples, interval_range,它们分别应用于不同的情况:
& R$ g: E/ S" w% ]! O9 g1 X- n
* T- y0 V& K5 ?from_breaks:类似于cut或qcut函数,只不过后两个是通过计算得到的分割点,而前者是直接传入自定义的分割点:! q) r( k V. P& x" r/ i
pd.IntervalIndex.from_breaks([1,3,6,10], closed='both')
; X( ~* x5 ]* I5 _5 m+ l: d; ~4 G) G1 K' v+ o4 _+ z8 v
IntervalIndex([[1, 3], [3, 6], [6, 10]],/ o( r5 H [% C/ N, F0 x M
closed='both',
4 b# P$ q; O; |6 ^3 y dtype='interval[int64]')4 I1 N: h3 ? {4 E) M. h
1# u) B/ l% w# e, _
2+ L+ y% _+ `9 i3 C) H
3
5 z! v" y9 b6 i1 J9 X$ O4
! N5 X6 T1 n" V5/ u$ r3 A& Z r/ a8 G
from_arrays:分别传入左端点和右端点的列表,适用于有交集并且知道起点和终点的情况:7 [' s5 P- ~9 ]$ d
pd.IntervalIndex.from_arrays(left = [1,3,6,10], right = [5,4,9,11], closed = 'neither')
& H& ?" s3 K2 t" ^- {2 P& P6 |# `* Y! C1 L
IntervalIndex([(1, 5), (3, 4), (6, 9), (10, 11)],
0 v7 F/ n" i8 |( z, k8 k2 C% L! ~, a closed='neither',) ^1 b* a: N( H |9 r) E
dtype='interval[int64]')4 e5 I$ I' W" O) W$ T5 [% v
1
) F. X0 u, U; A25 q c! A. k h2 { S
3
4 X) @, D. N9 S4 V# x4; W/ I& t1 q# o9 w V, i6 F
5: U# G# H. i0 C* L
from_tuples:传入起点和终点元组构成的列表:
& {# [ ~' N9 s# D- p$ fpd.IntervalIndex.from_tuples([(1,5),(3,4),(6,9),(10,11)], closed='neither'): d8 N6 ~( t. L
# K0 G/ W) A4 W9 RIntervalIndex([(1, 5), (3, 4), (6, 9), (10, 11)],
% `% W( k) n; T! E closed='neither',6 G; X" v( P9 A* o
dtype='interval[int64]')
/ J$ y- u( C: _0 W1 u0 b& e17 B8 W( H( m$ C: q
2
/ d( l' a v _# u% [3/ \) Q+ u1 |) S; C& u2 `; X
4
% T1 i1 A I0 i4 S8 j5) ^# }/ @, r% Q7 u# g; K9 z
interval_range:生成等差区间。其参数有四个:start, end, periods, freq。分别表示等差区间的起点、终点、区间个数和区间长度。其中三个量确定的情况下,剩下一个量就确定了,从而就能构造出相应的区间:: M* [4 w! [+ _; J
pd.interval_range(start=1,end=5,periods=8) # 启起点终点和区间个数+ c* b0 j) y+ u
Out[57]: 5 P. o d& D/ _- W2 O+ C/ s
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 _& v) F& b6 Y5 x* A% |* C3 {
closed='right',
. B3 D6 _/ t) E3 T: o$ A- T" Q dtype='interval[float64]')
9 S! V/ G% L9 v
, `+ u( L- R0 O" ~, I; Wpd.interval_range(end=5,periods=8,freq=0.5) # 启起点终点和区间长度. q3 Z; w5 }$ ?2 t
Out[58]: " w( A6 {! f5 W z
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]],, ^4 H( q5 H* M0 A* L- v f
closed='right',
( ?& q- q( E$ C7 A7 K9 E- T2 r dtype='interval[float64]')
7 b O B; d' [- {1+ j8 Z! i3 }' }
2! o7 x0 g" z% g3 j8 J! S/ C
30 x1 P+ V. a( _' M" Z4 T
44 I7 c! g( T/ O' \4 G4 x) R
5
z% t" H5 U, }+ x3 o* G6
" l* q! P( `8 ^5 }7
" C; z$ Q) d b6 b: y4 {8
0 U7 p6 [: D* z; t( s7 Q9( U9 d. z# ]( s+ p. j3 D9 g4 I
10# G9 h* U, v1 d F @
11+ ]1 d) r# G5 h; X! o4 ^3 Y b
【练一练】
# ?$ X3 o8 V5 D 无论是interval_range还是下一章时间序列中的date_range都是给定了等差序列中四要素中的三个,从而确定整个序列。请回顾等差数列中的首项、末项、项数和公差的联系,写出interval_range中四个参数之间的恒等关系。% \& K9 R% t5 n/ i7 a1 e
7 @; o# a- I; {4 J 除此之外,如果直接使用pd.IntervalIndex([...], closed=...),把Interval类型的列表组成传入其中转为区间索引,那么所有的区间会被强制转为指定的closed类型,因为pd.IntervalIndex只允许存放同一种开闭区间的Interval对象。
4 P; `! W8 o5 U1 Z- @/ b3 E; Z
3 D: z: \* i6 `2 E4 [& Amy_interval
- |4 o- I& y% B* r/ t, }Out[59]: Interval(0, 1, closed='right'), A- f/ Z% g( _1 R8 r& Y/ p2 j
: @. Z, W% D+ g! U. p& f# I
my_interval_2
" k, C: w) d9 B9 cOut[60]: Interval(0.5, 1.5, closed='left')
* e* x6 r2 a q# R) L% {0 s1 r' V, |8 R0 h3 L% a" C: k
pd.IntervalIndex([my_interval, my_interval_2], closed='left')7 D. W9 ^8 ~( e
Out[61]:
/ ]4 U. g4 o5 b4 G& r! EIntervalIndex([[0.0, 1.0), [0.5, 1.5)],
- f- n3 s9 N1 q. E( O closed='left',
; ?/ t% i, g" Y& g- f dtype='interval[float64]'), F0 _, ^) v; o9 Y4 t [
1
4 X& c2 E E4 J3 ?: l8 C9 u2. ]+ t- m G5 j8 m0 k/ y
33 O" v9 Z' D7 B' l7 X* p
4 s9 f& V" J0 ?8 L
5
% N) ] n+ m6 X6
+ j8 n9 Y; q+ K) Y70 S5 O$ l5 Q1 ?5 G( ?; a& h# ~% [8 t
8% v, T( ^4 O7 k$ @/ ~3 o
9& I M6 I! k2 s g* z5 |
10
+ y5 i6 e3 R6 r+ m# E2 ^5 [11! z/ f# U" o9 _
9.3.3 区间的属性与方法9 n1 ~! s5 x+ n, G+ d# d
IntervalIndex上也定义了一些有用的属性和方法。同时,如果想要具体利用cut或者qcut的结果进行分析,那么需要先将其转为该种索引类型:) Z' z3 p' V# t! m/ K1 b6 j
; l i( E1 S, g) q' C: rs=df.Weight
, T) W" x' [) \' Z- kid_interval = pd.IntervalIndex(pd.cut(s, 3)) # 返回的是每个元素所属区间,用具体数值(x,y]表示
% T4 K$ \7 v$ o* N. aid_interval[:3]
6 ^/ I! g; C# @" X6 c
; P" K" M" M9 V6 BIntervalIndex([(33.945, 52.333], (52.333, 70.667], (70.667, 89.0]],0 c% B* }% l( c1 d) u
closed='right',
/ p; _- y0 E4 y name='Weight',* m1 J3 c" d- d+ Z& w& }
dtype='interval[float64]')
1 P% k4 g* `/ P+ w% M/ W1
, O% |+ ~) A3 x2+ m& j2 H+ n9 z! R$ Z) P; k& m# l
3
6 q. d7 s- y! s4 K" p4
. o" y- T( P3 B6 K5
4 [; Y; o. s+ W9 Q6; K! W4 J# K2 B2 P
7% S! o% P" ?! w. q, q. N
84 a! _6 q* q* Z
与单个Interval类型相似,IntervalIndex有若干常用属性:left, right, mid, length,分别表示左右端点、两 点均值和区间长度。
6 v/ S& ?& C6 O$ j4 gid_demo = id_interval[:5] # 选出前5个展示* w, w1 @7 z4 R. b
/ A& N4 a2 H/ a: D( O! wid_demo' z5 V1 n" a% ] Y
Out[64]:
5 q, f. u7 J% O* u; f3 zIntervalIndex([(33.945, 52.333], (52.333, 70.667], (70.667, 89.0], (33.945, 52.333], (70.667, 89.0]],
- @/ M5 l6 ^; I# ?5 T7 e5 ]% i* k ^ closed='right',
2 \" M6 Z0 E+ w0 J M! S name='Weight',7 h9 c! _- l8 }$ X4 m/ X1 C
dtype='interval[float64]')) V4 n. Q) z K7 G0 C2 `, ]; a, G
1 @6 o* z4 D' L5 E6 @9 X% t! `id_demo.left # 获取这五个区间的左端点& g+ w* i% B$ ~" ~+ V
Out[65]: Float64Index([33.945, 52.333, 70.667, 33.945, 70.667], dtype='float64')
4 L! r3 N! F" I% r, ^7 E' @$ J$ x H- p$ c/ z0 `/ b
id_demo.right # 获取这五个区间的右端点; G/ p, F% |8 v! f* F9 m
Out[66]: Float64Index([52.333, 70.667, 89.0, 52.333, 89.0], dtype='float64')
1 u. O5 m3 l0 N9 o7 ^' \9 ?
$ ]% x" }. m; a9 v: }* k3 {id_demo.mid, c9 z7 s4 M; ?0 d% G2 f
Out[67]: Float64Index([43.138999999999996, 61.5, 79.8335, 43.138999999999996, 79.8335], dtype='float64')5 ]8 h4 s+ b- B: [. H; @
$ n4 T4 `0 p3 L1 o" p6 h4 c! rid_demo.length
' |/ y6 X+ Q0 ?Out[68]: 7 `: g+ B# R9 ~" J% r8 o0 V
Float64Index([18.387999999999998, 18.334000000000003, 18.333,' j) d! g9 O- t3 [6 \
18.387999999999998, 18.333],
; D' I* i8 K0 m) v6 q' p" z: p6 Z0 S* A dtype='float64')6 {9 c/ a7 {" Z: X( Y
, f4 T0 n J. I3 C- K
1
1 y$ \: t$ [/ e: |8 u; C29 h0 v4 [: M6 S0 v4 D: a& k/ D
3
4 P2 @4 g- |6 v( j2 n4. r6 `) l% t4 W4 n8 z, X5 D
5, ]# j g) Y! r0 O
6+ S& q% w# Z5 @! C6 |2 C
7
& K( w. [. h' k% Y8 o8
3 v* q8 ~" k* {( I% i& @6 o" i) g7 v/ k9/ F6 {/ M' X7 ], E
10
( y, X6 ^$ r ~$ z1 }8 X+ z119 P5 c8 J: @. I3 B) C
12. ?. q9 L$ B% d2 `3 ^2 ^; R
13
; g9 I8 W, r0 ?14
% b$ K- B' L7 Y158 t9 K$ i& {4 z! r& S, f
16
% L( q" g- v( |( \0 E17
" i; e% G+ w$ }# x18- I: A: ]+ s. a
19
# q- l7 T2 p( ]/ E8 c$ E4 c20
0 F* ?6 F3 M0 q210 e& ~5 r& K( C7 N: ~
22
$ u9 ]2 t7 F' I( `0 X8 Q23* t: Q* T+ _* i1 m5 y7 ]3 J! }
IntervalIndex还有两个常用方法:
' E: `8 S) P, q& ocontains:逐个判断每个区间是否包含某元素
7 J" W( M$ J" ^3 R: S' A3 ^/ c2 qoverlaps:是否和一个pd.Interval对象有交集。" x) ]/ d* O/ E+ G! i$ F
id_demo.contains(50)3 ]) f, H6 e) r+ \( L+ A
Out[69]: array([ True, False, False, True, False])2 H9 C- W" d1 u$ w8 o9 D
6 G9 [9 \7 g9 f4 @5 g5 f X! O
id_demo.overlaps(pd.Interval(40,60))
/ o. w7 j* Z9 G8 a/ E' mOut[70]: array([ True, True, False, True, False])$ K& I2 @- ?0 @
1- e! ~% D4 Y' X" e9 I
2
$ R* L `7 Y& I' S3 X* F( {- R3) N! M& z2 \9 F$ M3 k# b' s1 [+ }
4
* A8 P0 R' S( Y) [5* B0 s+ t, t( ?: ~2 \
9.4 练习+ d* k% [% Q# {( i4 y- F/ K( t
Ex1: 统计未出现的类别, k" b8 T# n% |" c+ E, p, X
在第五章中介绍了crosstab函数,在默认参数下它能够对两个列的组合出现的频数进行统计汇总:
% ^& @* o+ t: F( e8 h5 {$ E1 N6 H# L0 L& L
df = pd.DataFrame({'A':['a','b','c','a'], 'B':['cat','cat','dog','cat']}) o- ?' w; s6 h9 O# D1 |
pd.crosstab(df.A, df.B)
- t" t j" h+ W0 V1 L4 Z S$ t: k: V/ o% J/ s, J) }( ~
Out[72]:
$ b- F7 n" u$ L5 T+ o. xB cat dog3 e: a0 b( b7 I3 R
A , w4 y* l# D4 v) ^. L9 j2 S
a 2 0% w$ c8 n; t+ n: f! K
b 1 0
- B! g7 b2 k# C. w7 N. X( ^c 0 15 A+ j$ c+ w \/ g5 G. `" ~
1. F, ~9 W7 _, O, Q3 t7 @4 }' ?
24 Z8 m3 O9 ?1 j1 \( M
3
2 ~: K! X1 a ~, h/ f4
7 t' d- r4 A. T% i U8 D55 S# S" b5 S L) H% R5 v* Z" z& e
6; S+ L( E" r1 k
7# l ?" K0 \3 g! D- U q8 b a
8+ S7 L& y6 |+ z# a( }, I L
91 U- h) {9 R6 a5 k
但事实上有些列存储的是分类变量,列中并不一定包含所有的类别,此时如果想要对这些未出现的类别在crosstab结果中也进行汇总,则可以指定dropna参数为False:
& U# n$ A; \* e8 F" W
# ]; I' Q! ?$ i9 }6 o: xdf.B = df.B.astype('category').cat.add_categories('sheep')
8 _' y8 p$ u7 U9 Rpd.crosstab(df.A, df.B, dropna=False)
, q) o# u. `- `, t+ J' I# z* x" ^' V* R
Out[74]:
" l# S+ v, \5 N/ ~B cat dog sheep9 F+ n. }; G! G7 b9 E
A $ Z2 u. ~0 R, G$ [2 J0 d& V: C
a 2 0 06 y$ U/ T! W/ Q8 Y1 K o' J, ?
b 1 0 0
1 y3 }2 H8 J5 \+ P* @" Vc 0 1 0
; U/ l- ~3 B3 |1 N, ]1
% ]0 q4 `2 X8 h/ @! s6 ] A! z7 ^' t24 l* |3 x d. \3 q. g9 W; D( r
3! M! k& F) v" R+ H( E
46 A6 u( B' \0 ?) L. h+ a
53 T- x% Z: R8 f& f, P" Z7 l W
67 p. h7 i+ a1 @- B/ `0 w
7
1 G1 w( S+ v% y# f8% t0 C% ^% e3 n
9
3 ?% [9 S: T! s# s1 I' D% l- [请实现一个带有dropna参数的my_crosstab函数来完成上面的功能。7 u) } k2 L- H8 m' b Q% p0 ?
# `5 X3 O% z* h! a mEx2: 钻石数据集8 M8 L+ g4 m5 m: v4 g
现有一份关于钻石的数据集,其中carat, cut, clarity, price分别表示克拉重量、切割质量、纯净度和价格,样例如下:3 E* @+ `& H4 @
) C. I# w6 c1 d& c# E
df = pd.read_csv('../data/diamonds.csv')
' ^' U# f0 d+ Q h5 Y8 `+ ?& `df.head(3)
5 c" V- \/ x* W: g H+ q. U( l
9 u* B K3 F/ d5 COut[76]:
! s# U9 A# ~* M- c: v L- g carat cut clarity price
4 |# J: k8 m% U- K+ V0 0.23 Ideal SI2 326
- G% \" W: W3 r. y% _1 0.21 Premium SI1 3269 ^' b/ l# r8 v) ~% G& V! ?6 W
2 0.23 Good VS1 3270 T [& R1 v9 f _! \
1
/ g1 A( y4 A# K% m2# E) O) p$ j' `
3
3 P8 A& m7 `4 H' H- s" e4
: q7 f$ r; r# K7 w3 |. [& V55 Y" j4 d9 S6 J6 n; S6 r% r" ]
6
& y9 t- b8 o" C3 S, m: t+ Q7
0 A ~: J6 h& ^8# i6 j0 P6 I. `+ ^ Q& _
分别对df.cut在object类型和category类型下使用nunique函数,并比较它们的性能。
7 v! i! y4 L) O) g钻石的切割质量可以分为五个等级,由次到好分别是Fair, Good, Very Good, Premium, Ideal,纯净度有八个等级,由次到好分别是I1, SI2, SI1, VS2, VS1, VVS2, VVS1, IF,请对切割质量按照由好到次的顺序排序,相同切割质量的钻石,按照纯净度进行由次到好的排序。: C _9 B; }9 M, E5 a% G1 a# I5 B8 [
分别采用两种不同的方法,把cut, clarity这两列按照由好到次的顺序,映射到从0到n-1的整数,其中n表示类别的个数。
% L1 K4 d9 B; R& W4 s1 v对每克拉的价格分别按照分位数(q=[0.2, 0.4, 0.6, 0.8])与[1000, 3500, 5500, 18000]割点进行分箱得到五个类别Very Low, Low, Mid, High, Very High,并把按这两种分箱方法得到的category序列依次添加到原表中。
! x3 E' T6 \4 @+ h7 Z0 ?7 c, i! `$ {+ b第4问中按照整数分箱得到的序列中,是否出现了所有的类别?如果存在没有出现的类别请把该类别删除。+ h$ d( q$ t5 e
对第4问中按照分位数分箱得到的序列,求每个样本对应所在区间的左右端点值和长度。/ P. O; Y& f3 `7 N, B( k
先看看数据结构:
$ @, t. Y8 J$ W. c( e, m! q, P0 s& x" g+ c3 I/ C
df.info()0 Y: H, I# S9 l7 Q0 t1 ~0 i6 `" q
Data columns (total 4 columns):! t0 H% e! n5 p( h5 {
# Column Non-Null Count Dtype
+ l3 f; B, f" ~" T4 S" ^--- ------ -------------- ----- ; Q1 X8 O8 u+ M& u/ r
0 carat 53940 non-null float64
# b5 @! R! J1 }' D 1 cut 53940 non-null object
* t. s3 B; d. [8 d* V 2 clarity 53940 non-null object
& K% `1 g, M& ?- ]' p4 ?+ y) `& j 3 price 53940 non-null int64
8 k6 n/ h" M. Z$ O9 \, wdtypes: float64(1), int64(1), object(2), G. O6 F+ Q1 e6 l0 _
1
9 z. x. O. Z9 o2
) D: K( j3 [8 J5 o6 V& B4 F; T3, V1 U, T" n( B* D, w5 ?) Q
4
. C I6 \7 o9 W5
5 Q+ ^4 j8 P" l% t/ Q6: L& n( {6 E4 c# u$ V
76 k5 ~) L6 V+ s, L; f; E8 G9 Y( G1 t
8; O( j! K- l) F
9: ^9 N6 a9 y% `+ m( ^
比较两种操作的性能
7 P( ]( h. [ a% q7 @9 A' b%time df.cut.unique()0 ]! f6 h2 s' i
7 B5 b* k8 j' Z7 N- UWall time: 5.98 ms/ W1 i: {& M! m1 M
array(['Ideal', 'Premium', 'Good', 'Very Good', 'Fair'], dtype=object)3 r( e2 P& c" |: r9 `
1/ |3 t" {1 ]# J0 B
2
$ I. F- B! [: W& f% Q" Z31 I3 ~. F V0 |3 ]9 _7 S
4
2 f2 b2 B" k) I% `# q, @: ~%time df.cut.astype('category').unique()
3 W. S2 v6 ^% k; B3 X! z8 r" l% Q) G) J
Wall time: 8.01 ms # 转换类型加统计类别,一共8ms; G$ q% E2 f! V8 ]& j: w
['Ideal', 'Premium', 'Good', 'Very Good', 'Fair']
" g2 z$ o" U9 a, J7 `/ E5 ~Categories (5, object): ['Ideal', 'Premium', 'Good', 'Very Good', 'Fair']' _ V8 v6 a- l( e
1! A/ Q7 Z, c/ X9 `2 P4 H: Q
2
5 S5 S; t9 g- ^ _3
5 n7 M; F' n7 @4
: t" a6 v* }% K$ ^0 l3 z5
9 |: v9 `- S' i1 }! ?9 ]df.cut=df.cut.astype('category')
. x# y$ P! ^0 r; I%time df.cut.unique() # 类别属性统计,2ms$ ]2 D0 f! }; d( h' G2 M3 K6 U
! g; F# w) |" y$ M" _7 y4 e
Wall time: 2 ms) e. y' V. g; a6 T5 ^
['Ideal', 'Premium', 'Good', 'Very Good', 'Fair']
) L0 A4 L6 q% wCategories (5, object): ['Ideal', 'Premium', 'Good', 'Very Good', 'Fair']
A+ k3 n! Q. Q19 `+ a6 u% i, C" ]
2) g, S1 M5 G; e, ` h8 Z4 z. p
3' ]) w. j; Z! t+ w% O8 o8 i
4
$ H1 ^) k W- J& I" f/ G) d4 Y51 J$ v4 ]( e3 n) E5 [
6- @' ^; f/ Z2 R: G$ P
对切割质量按照由好到次的顺序排序,相同切割质量的钻石,按照纯净度进行由次到好的排序。' E [! n5 w1 D
ls_cut=['Fair', 'Good', 'Very Good', 'Premium', 'Ideal']5 v2 q* k% T# L6 A
ls_clarity=['I1','SI2', 'SI1', 'VS2', 'VS1', 'VVS2', 'VVS1', 'IF']' ^0 ]1 X" T9 ?9 j
df.cut=df.cut.astype('category').cat.reorder_categories(ls_cut,ordered=True) # 转换后还是得进行替换
- \) N; d7 M% u# s. gdf.clarity=df.clarity.astype('category').cat.reorder_categories(ls_clarity,ordered=True)
0 C, e7 Z. ~) w* s* L% I! k% P; D9 L; V) f1 S
df.sort_values(['cut','clarity'],ascending=[False,True]).head(3)
) j t7 _$ `5 R) \: |3 b" y/ _$ d( ^8 z: c* s9 A
carat cut clarity price- Z* u$ C( R0 {5 U \
315 0.96 Ideal I1 2801
' I! E) e; R( p! S* b- H535 0.96 Ideal I1 2826) ?5 K* L& G% y; Z0 b0 N" e
551 0.97 Ideal I1 2830
2 A. U! t" Y: z1
* X* y1 H8 a- u# a; a, M! d T2
+ g8 p0 o8 R0 u$ W/ a( Y A+ H3& ^' \- U8 \: Y7 F6 b4 i
4
* s7 q+ N2 _! b* @5$ m! K1 g& Y2 A( e5 r1 r f
63 u- f0 P/ u7 z$ o3 r$ v
7
9 H0 H8 O3 S2 r7 `& c2 C8
) o* S' d8 k% ~2 X. y# [9( N* x. B( X8 G$ V
10
1 v9 i4 o" {/ U" ^8 v, @3 z2 F11
7 P O: [' m7 y/ g# N% ~分别采用两种不同的方法,把 cut, clarity 这两列按照 由好到次 的顺序,映射到从0到n-1的整数,其中n表示类别的个数。, W6 j8 J' _5 v* F3 p. @& r& i. L( b
# 第一种是将类别重命名为整数; M# Z" v0 H d2 S
dict1=dict(zip(ls_cut,[x for x in range (4,-1,-1)]))2 t$ F2 k! c: x; e
dict2=dict(zip(ls_clarity,[x for x in range (7,-1,-1)]))
7 b( S6 M" O! {+ I, Y! F
$ r w! x7 P h8 m: `" o" Y% Ydf.cut=df.cut.cat.rename_categories(dict1)
5 q' F5 d" p8 S4 x* Fdf.clarity=df.clarity.cat.rename_categories(dict2)0 ?5 `7 k% Y$ k% M
df.head(3)
( G, H4 \9 |8 H
* V0 ?2 I& v% C! J- p carat cut clarity price
' b' q) S2 N+ w8 i0 0.23 0 6 326
" D! b" o" A4 n* G: f3 @1 0.21 1 5 3263 j8 N# A; x% `# ^0 O; m
2 0.23 3 3 327( q7 _; a3 V3 s# a+ ]8 Q, r( S2 V
1
- k7 X( O2 g8 R; i2 w2
6 N- h1 _0 w8 q. N# ?' A3 D3
: l. B8 G' Q5 v; Z) t4
2 }6 h% C$ q: X. d5: ]7 H+ ^: ? \0 O. c$ S# s
6$ N3 k* L9 ^5 T( g( O5 t7 D
7
, `5 {2 R6 a4 P' B8
; j! s! w. c, ~3 F4 ]9# s4 v7 j4 a2 f
10
9 J) J' i" T( t' k$ G* ~- x11
0 V: e: D- m8 |- ~12" ^" T0 _# ~4 x+ l& A4 `( N0 x
# 第二种应该是报错object属性,然后直接进行替换% k+ ~' }, X& R/ R
df = pd.read_csv('data/diamonds.csv')
" N6 O, x7 R+ c5 Cfor i,j in enumerate(ls_cut[::-1]):
9 A! S' Q/ R4 G2 T* n df.loc[df.cut==j,'cut']=i
0 Y% `2 y5 V' x. m+ D
7 ?+ r8 `# s0 T: H: l$ T5 v5 u& L/ j, wfor k,l in enumerate(ls_clarity[::-1]):
3 z7 p( Q& Q/ h% m7 Z* _ df.loc[df.clarity==l,'clarity']=k
# V! @4 K g) x( f8 P7 D4 Ydf.head(3)
! F- Y6 C) q5 @
' Y' ]8 N' d# I: Z: J9 i carat cut clarity price
4 ]) f5 c8 c2 S2 u0 0.23 0 6 326
k; ]3 I8 \ T/ y! p, ]% [1 0.21 1 5 326
$ R6 }4 I: `, Q$ g; u2 0.23 3 3 327- v% ~7 x$ M8 m5 Z6 ?( ^' f
1+ X; b: C6 j8 _4 D) m' {
2' v8 Q7 V+ V% ]& F. Q
3
) ]- M( n! {' O1 G' K3 K# F9 e. f4 a) w! V9 r! X9 _: E! i
5
/ z7 ?3 T1 M% U) `+ @! A6, h( t) o+ P* }$ ]% l* s) v
7! K/ [/ P& ^' s+ L' q; @8 k1 S8 a
82 n8 v8 U0 m3 _' E8 H% E
9" Y' [5 A" d7 R) r, h
10/ b- [2 e+ g: L0 B; W& b
11
, w$ }& d4 o+ n* l* L9 j120 Y W2 X+ t/ v: V. T
13
6 K. H3 _0 l# x$ q+ l- Y% ^7 w* ]对每克拉的价格分别按照分位数(q=[0.2, 0.4, 0.6, 0.8])与[1000, 3500, 5500, 18000]割点进行分箱得到五个类别Very Low, Low, Mid, High, Very High,并把按这两种分箱方法得到的category序列依次添加到原表中。 R. h/ g- `5 B8 B* p
# retbins=True返回的是元组,第一个才是要的序列,第二个元素是分割点: l7 U6 [4 l* E4 N1 a8 R
avg=df.price/df.carat1 ~+ D5 z' K; k
( Q6 u w7 `- U
df['price_quantile']=pd.qcut(avg, q=[0,0.2,0.4,0.6,0.8,1],# S. g# E4 G* H$ V
labels=['Very Low', 'Low', 'Mid', 'High', 'Very High'],retbins=True)[0] I. p. ^/ h* |6 p. I/ ^
. A' Q# q1 o6 f) U2 j" O" N0 S& J; B5 Idf['price_list']=pd.cut(avg, bins=[-np.infty,1000, 3500, 5500, 18000,np.infty],
" I6 b8 C! ]7 Y1 s" F labels=['Very Low', 'Low', 'Mid', 'High', 'Very High'],retbins=True)[0]
/ D9 d# B5 G! X) ^5 O# }0 G; a' Zdf.head()
; W5 u3 F' n1 _! z; l3 Z, V/ c/ w1 m! L# z% d" D- r4 v# G5 {- ~5 \
carat cut clarity price price_quantile price_list+ w" @- b6 }. \# W2 x- L
0 0.23 0 6 326 Very Low Low; T# y4 T) W( j! i! i
1 0.21 1 5 326 Very Low Low
* J- y' {! v N- y2 0.23 3 3 327 Very Low Low( H9 ?3 i2 }. |
3 0.29 1 4 334 Very Low Low' _0 h' s0 U3 g' W% _
4 0.31 3 6 335 Very Low Low . j- |* c8 ]5 J) I0 r8 U% y& r
) i2 ]; n* g- T2 _& x) {1& e+ G0 Y3 E& f% i
2
2 u: M D$ o) I; B* `* x$ g37 ]1 m( M8 R: Y6 G& w8 P- D
4
0 v& [" _; Y) Y2 E$ j9 e5
- a. ], q3 |1 V# E; }: ]6
3 l1 h5 O: ^9 M, v7 D7
/ J1 d8 P$ a1 {+ f1 p% n( H8! r4 V# F6 P: ]
9
( [0 Q4 d7 G0 e10
- {1 X/ G( J/ K# r11
2 T; X% o4 A* t G' d2 l12
# J% e1 b8 o5 x) i3 i7 H' A13+ c9 u4 G$ J3 l" O
14
+ P2 Y# x0 q- g X15
& a1 O! I g( ^/ [2 E, i: v16
, B0 n* u! {4 I* V% O& I$ J1 b. ` W分割点分别是:- Y" W2 S4 V5 R; ]% f7 a3 f
3 y) e4 I1 n7 r
array([ 1051.16 , 2295. , 3073.29, 4031.68, 5456.34, 17828.84])- K6 V ~" ?+ V8 v! Y
array([ -inf, 1000., 3500., 5500., 18000., inf])9 n0 X9 n% ~, F8 g
1
; X& p# y0 s# H: b3 s: h20 g; j( _( Y$ r7 T; d2 O
第4问中按照整数分箱得到的序列中,是否出现了所有的类别?如果存在没有出现的类别请把该类别删除。
* ^% _) M# I1 F7 \8 V" i Udf['price_list'].cat.categories # 原先设定的类别数1 |& c7 e! I; O! s' d3 q0 i8 m
Index(['Very Low', 'Low', 'Mid', 'High', 'Very High'], dtype='object')& {" ~4 l! Y* i1 W) E
' D7 W7 m" ?. _) D% o% k# R9 p5 Ddf['price_list'].cat.remove_unused_categories().cat.categories # 移除未出现的类别
9 F" G6 w& U; s/ h# XIndex(['Low', 'Mid', 'High'], dtype='object') # 首尾两个类别未出现
2 U3 }9 _) G3 S" a1 E4 {1
# e0 \7 V# Y5 z3 P2
O" j8 h1 l2 X+ ?6 z" |1 y3
4 }( F9 H1 ?0 Z1 m* |4
1 k0 L9 E {6 E! R. L% k. Y5
; F+ S( G& a# ^7 |: r yavg.sort_values() # 可见首尾区间确实是没有的
+ h/ `7 y2 f# o31962 1051.162791
8 n+ g+ m1 `' u t& q6 H: T15 1078.125000: E2 } C5 P0 `6 n; V
4 1080.6451614 ~1 u5 O% u* A
28285 1109.090909
; c$ O. k3 H M) N) [13 1109.677419, p7 |8 w! B) T4 k2 z: I: ?. V
... 5 ?* r, n$ ]- m0 _
26998 16764.705882. n3 [& M9 P5 d T/ o+ b
27457 16928.971963
# d; N; t4 q1 g9 J! _27226 17077.669903
! @3 o& f( n: v4 R. @27530 17083.177570
, E7 [. k, v& h+ r1 q* i: E" j27635 17828.846154
, w8 Z4 J% ~+ A8 b. O1
3 z$ W+ e' V1 s. k, E: z6 _2
( w7 X( t- Z& b2 n" b+ ?8 o2 i35 i* o% I6 q( Y, }* a3 ~
49 F# R. H* W( w' R
5. {8 V2 e( C6 f) k' _; u) P
69 j" u6 a6 @+ \8 V7 x5 k G
7
" i |" J" ~6 R- O8
2 w/ c2 F Z0 p) `9
2 t& }3 h, i9 G# y4 @2 H" b10# R0 x5 Z9 k; s) o' B
11. d, t7 \/ h p; Q
12
9 P7 ]) I. o U7 r; S; n6 n对第4问中按照分位数分箱得到的序列,求每个样本对应所在区间的左右端点值和长度。9 ]: [* ~/ D5 C" z+ T
# 分割时区间不能有命名,否则字符串传入错误。
/ H }- j$ Y5 j. j% ?id_interval=pd.IntervalIndex(
, X+ |* y9 ]) ]/ g pd.qcut(avg, q=[0,0.2,0.4,0.6,0.8,1],retbins=True)[0]! P* s# I: c! s4 w8 y* ?) C0 o
)
, F. g6 N* h; g/ ?! `id_interval.left% \2 f9 K9 e; C- y( c
id_interval.right
$ r3 T0 e$ S" j! R$ P4 z8 `- {/ uid_interval.length # H& ~1 w6 s( G V. L: H( |
1 @7 m+ K* k M" ?4 S/ f0 o3 X8 [4 w
28 f- v% s+ {1 D: [
3
8 M. e" H4 w9 e+ z2 E4* ~6 d* R; J8 ]8 u( |
5
! J! c& V0 v- Y7 I, @6
- H/ S: K) d' V" i/ _! C( ~7
0 i$ _$ ~ n8 B- w l+ i第十章 时序数据 A5 L# q6 R8 B* z# l& t+ y
import numpy as np
" e2 R& ~( D `" ^) ~5 @import pandas as pd
8 o" x" q; |0 d8 u9 |5 R9 i12 N9 g# ~: o$ ]
2
& F$ F# C( j# S }) @
3 B# n+ ], X- x% P; s
" m' M- m; l7 L10.1 时序中的基本对象
% `0 p! G7 O+ P 时间序列的概念在日常生活中十分常见,但对于一个具体的时序事件而言,可以从多个时间对象的角度来描述。例如2020年9月7日周一早上8点整需要到教室上课,这个课会在当天早上10点结束,其中包含了哪些时间概念?- @6 f4 Q& V* Q
1 ]- k9 j2 Q4 B1 {' \0 y会出现时间戳(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的简写。" ?: o! R" J' h1 l
4 q8 K0 M: q6 @1 @% H
会出现时间差(Time deltas)的概念,即上课需要的时间,两个Timestamp做差就得到了时间差,pandas中利用Timedelta来表示。类似的,一系列的时间差就组成了TimedeltaIndex, 而将它放到Series中后,Series的类型就变为了timedelta64[ns]。7 o& L( n7 F. ?+ w0 C V4 K
7 M4 y' z% w7 f4 [
会出现时间段(Time spans)的概念,即在8点到10点这个区间都会持续地在上课,在pandas利用Period来表示。类似的,一系列的时间段就组成了PeriodIndex, 而将它放到Series中后,Series的类型就变为了Period。
" s3 V% T; q# Q: G( _1 [; r
# Q4 ?! A- H a7 g6 g会出现日期偏置(Date offsets)的概念,假设你只知道9月的第一个周一早上8点要去上课,但不知道具体的日期,那么就需要一个类型来处理此类需求。再例如,想要知道2020年9月7日后的第30个工作日是哪一天,那么时间差就解决不了你的问题,从而pandas中的DateOffset就出现了。同时,pandas中没有为一列时间偏置专门设计存储类型,理由也很简单,因为需求比较奇怪,一般来说我们只需要对一批时间特征做一个统一的特殊日期偏置。- b: E5 X: r1 h" |
' |3 E- k0 Y! u 通过这个简单的例子,就能够容易地总结出官方文档中的这个表格: G1 F0 [6 c- _; m x: I
- E- |9 I& m$ q) u Z+ A概念 单元素类型 数组类型 pandas数据类型5 y# E" \1 U( s
Date times Timestamp DatetimeIndex datetime64[ns]: Z! G7 V! @6 q. ^; f7 n( D: N
Time deltas Timedelta TimedeltaIndex timedelta64[ns]5 C$ F* z2 h8 T9 T. `* B( r
Time spans Period PeriodIndex period[freq]& d# }4 m o+ g% x! D' u9 ]
Date offsets DateOffset None None# ]3 p% s( n* z: a* \
由于时间段对象Period/PeriodIndex的使用频率并不高,因此将不进行讲解,而只涉及时间戳序列、时间差序列和日期偏置的相关内容。: H5 o$ n6 Y2 o! c: _, S) k- x
: Q0 e6 ?- w& E, L$ ^) e10.2 时间戳
9 T2 X& @" o# o. u/ {10.2.1 Timestamp的构造与属性6 L3 Q4 _" O; |' e( Z
单个时间戳的生成利用pd.Timestamp实现,一般而言的常见日期格式都能被成功地转换:$ S# S& S& ]* e1 P7 B) e& w" t! W+ p
' O V5 K% C5 b, ?! D' ~
ts = pd.Timestamp('2020/1/1')- E& ~; }- y- R: J
3 W4 G4 I, m/ ], ~+ Q, y/ Mts
: r9 O+ w0 _6 T6 @Out[4]: Timestamp('2020-01-01 00:00:00')" N1 }5 n& e: J+ n% F
0 e0 }2 i I5 {0 f; I, bts = pd.Timestamp('2020-1-1 08:10:30')* q' o& j9 N" | l& }, Z
^7 X# K' f' c; D1 r) j6 _- Zts* j& X5 m: {+ Q6 {+ Y
Out[6]: Timestamp('2020-01-01 08:10:30'); z& E( h8 D0 V3 o5 c# S
1
" U% T0 M& v! K0 Z5 {2 R0 O2
G6 i5 J/ w/ Q# K8 p$ G4 m3
: R8 B0 z2 N8 o( E: U* ]2 Q3 I4& K8 V5 R; V1 L6 g6 O6 {
59 i2 Y$ ^. _4 w. O) E6 d
6% Y6 c8 r' `1 M1 x! F
7
3 |; K. t+ W7 \6 `: b) o8 Y8
" k) C) ]0 }- x7 J9
, u, `% n" o' a% \ K9 `通过year, month, day, hour, min, second可以获取具体的数值:
. H6 ~, n- r8 W; m* ^- s1 J3 B' \! d" I% b+ ]: `
ts.year
3 a m: H2 D# c+ s3 FOut[7]: 2020# j! S. Z9 d5 \) D' {" M( @, z
% @6 ^. \ L# ^
ts.month- F) z0 B( @5 f' B
Out[8]: 1: ^1 s8 a, |4 D: a4 o
2 a" C+ |/ o! ^
ts.day. Q1 x- @; v0 }' A
Out[9]: 1) d( e8 R1 k* I8 u* X r5 E
% N$ ^" b) B) Q% m1 J) Kts.hour
B3 \" V; P: t: d B- cOut[10]: 8& N1 S; }. Z: q) k" r4 g8 [
8 C" U4 x6 Z+ X, ]8 ]$ }$ s% p6 sts.minute& A! I; J& @/ N! V6 `6 o
Out[11]: 10
, l3 h0 ]- u/ h
4 k) o4 _1 X4 u- a2 j Nts.second
T3 [/ a" N$ K* v1 b* NOut[12]: 30; _3 ^% G; X) F- |: `* A* b
: o0 i* E% g- N! Q" I5 X$ R' o1" O* L4 [* C r$ K) Q% x8 G8 V4 y* Q
2
& i- r2 q8 Z9 F4 B+ W) L33 O* `' Z' ?6 _1 g' A/ `
4
# {+ E5 p7 w' t7 @# ^5; j3 k# I% X2 F1 b3 e) v3 P
6/ T# z+ [& R5 w5 E* x& O8 J
7% Y# v( T+ |- z- u3 O
8
. [! l0 D0 ^. o4 r3 Z. S8 R! Z& p9, X+ s( N8 Q" J: {
10
3 V/ l6 C( u' s! P w2 s6 v11/ L$ B1 O$ Z% D' W; H
12
) I. }( L1 w% D. ?3 C) c& M13
, i7 P4 I7 O4 [4 i5 [140 D3 u$ a- J! [3 i/ b
153 N" N' \, w: ]
16
0 j2 Z. W% ?; e8 s17) m/ D7 b/ |6 N$ x
# 获取当前时间
. D1 `6 e( x9 G1 gnow=pd.Timestamp.now()
% g1 p1 B M( y/ s; \* C1
. K: M' j5 C* [. _2+ k5 z$ u. J# J9 U- u/ A
在pandas中,时间戳的最小精度为纳秒ns,由于使用了64位存储,可以表示的时间范围大约可以如下计算:' ~" V( a# X N( x) g1 k+ l) a
T i m e R a n g e = 2 64 1 0 9 × 60 × 60 × 24 × 365 ≈ 585 ( Y e a r s ) \rm Time\,Range = \frac{2^{64}}{10^9\times 60\times 60\times 24\times 365} \approx 585 (Years). o& N3 z5 M# ?6 o! {8 h2 l
TimeRange=
4 J6 p( X& N, i% I- u10 ( K/ B( G' o. o
9- _. n, K( N( l: y5 \% g' ^
×60×60×24×365& u( q- N' G* b$ E* U! \4 Z4 m
2 / P( ~1 l7 N6 J! G
64
5 c/ S& w9 C$ p6 R
( Z$ B8 C8 k* s# D; j; D% W% J3 Z6 s* ?
≈585(Years)% ?4 {% a& @8 i, J+ V& y4 g. b
6 {3 h+ e: Q! C5 \- y. \5 N通过pd.Timestamp.max和pd.Timestamp.min可以获取时间戳表示的范围,可以看到确实表示的区间年数大小正如上述计算结果:
( J8 E) G" {# U, R. i0 D( h7 p; d( @$ A
pd.Timestamp.max
! J" I x& [1 V9 pOut[13]: Timestamp('2262-04-11 23:47:16.854775807')! ]9 i5 {) B2 i1 [% e( x4 F
# u1 r1 ?) t, P1 a; `- ~. ?1 t( ]pd.Timestamp.min
6 z' C' { W2 V" \4 FOut[14]: Timestamp('1677-09-21 00:12:43.145225')
$ N4 ^ ^, @* \
% ?, q, T+ m: B" ^pd.Timestamp.max.year - pd.Timestamp.min.year
* F4 r# t4 }4 I# YOut[15]: 5853 g5 R* Z+ ^, T) h8 S s
1% Q! m3 ]) |6 L2 f' c; I$ w
2, L ~7 c, R2 M8 t$ g2 |8 [4 X
3
, X' Q y$ L |8 q' \0 I3 O4- G0 Q6 D9 s: p; j
54 o" v& I# C+ w) x6 S
6; E0 y" G( `, B
7
% o D% k x9 L; {6 M! j8& p6 w' w2 f* b2 b# a
10.2.2 Datetime序列的生成
+ |+ J3 Z' m' ?1 j* T0 @3 b" Dpandas.to_datetime(arg, errors='raise', dayfirst=False, yearfirst=False, utc=None, format=None,) s* |( u" V" A
exact=True, unit=None, infer_datetime_format=False, origin='unix', cache=True)2 o3 J$ Y- g3 C- I) [& d
11 x# f6 @' I- ?/ t5 H
2
4 a3 u- h% p1 h% S- y+ R( }pandas.to_datetime将arg转换为日期时间。8 O! { J% a/ d Y" F
/ Z. N/ F; } m
arg:可以是argint、float、str、datetime、list、tuple、一维数组、Series、DataFrame/dict-like等要转换为日期时间的对象。如果提供了 DataFrame,则该方法至少需要以下列:“年”、“月”、“日”。& f; y% x; H' H: [# I9 V' b
errors:
& e1 P5 X, {8 h- ‘raise’:默认值,无效解析将引发异常
* G& t* }2 {. ?4 s" O- }; b- ‘raise’:无效解析将返回输入, ~. @2 q0 }* X* a
- ‘coerce’:无效解析将被设置为NaT6 v0 z f5 f5 e: G! U
dayfirst:如果 arg 是 str 或类似列表时使用,表示日期解析顺序,默认为 False。如果为 True,则首先解析日期,例如“10/11/12”被解析为 2012-11-10。如果无法根据给定的 dayfirst 选项解析分隔日期字符串,会显示警告。% ?0 G2 I0 U' L3 q' n
yearfirst:如果 arg 是 str 或类似列表时使用,表示日期解析顺序,默认为 False。如果为 True,则首先解析年份,例如“10/11/12”被解析为2010-11-12。无法正确解析时会显示警告。(如果 dayfirst 和 yearfirst 都为 True,则 yearfirst 优先(与 dateutil 相同)。)
" t4 s: b+ X( p4 R2 e- H. \7 y& Futcbool:默认None,控制时区相关的解析、本地化和转换。请参阅:pandas 有关时区转换和本地化的一般文档' g# ^# \" A5 m' T# j
format:str格式,默认None。时间戳的格式不满足转换时,可以强制使用format进行匹配。
+ ~1 |& R0 f# g: ] x2 v) j) Dunitstr:默认“ns”。它是arg (D,s,ms,us,ns) 的表示单位,可以是整数或浮点数。这将基于原点。例如,使用 unit=‘ms’ 和 origin=‘unix’ (默认值),这将计算到 unix 开始的毫秒数。
; Q& d& w B" \4 {7 k' Q" s Ato_datetime能够把一列时间戳格式的对象转换成为datetime64[ns]类型的时间序列:( _- c9 B& E) I, r' o
pd.to_datetime(['2020-1-1', '2020-1-3', '2020-1-6'])
0 }8 W/ W3 Z2 m x: c' A
8 D* K A- l0 e4 Y9 v& JDatetimeIndex(['2020-01-01', '2020-01-03', '2020-01-06'], dtype='datetime64[ns]', freq=None)! ~ a% v( r) D2 Z
16 X# H4 m$ h) T. d, e8 {5 w
2) }6 R( [) Z5 o! f8 [# I
36 v2 m" E2 i/ k7 C9 C1 o
在极少数情况,时间戳的格式不满足转换时,可以强制使用format进行匹配:
* n- \( Q! x" ^- e# X% h) V9 ^6 Q) A) C
' r9 v3 h* [) ?( v e' Stemp = pd.to_datetime(['2020\\1\\1','2020\\1\\3'],format='%Y\\%m\\%d')
C2 K/ P' s$ S/ \" etemp
$ o9 s1 _: D, u, ]/ W# P- f% y; P: |
DatetimeIndex(['2020-01-01', '2020-01-03'], dtype='datetime64[ns]', freq=None). e$ {) o; u; _$ E8 W% K% K
1
) L2 c4 D& H3 {9 T7 `- ~; H2% |" I3 S& T/ i# `# |
3 D$ i+ k8 a4 `" N
4
* V. T& C( S6 v 注意上面由于传入的是列表,而非pandas内部的Series,因此返回的是DatetimeIndex,如果想要转为datetime64[ns]的序列,需要显式用Series转化:4 R4 r; z" |2 C9 C3 Y* c; b
$ b) \1 G9 [% p H2 C. Cpd.Series(temp).head()
/ `1 C/ ^. P) ~5 Z4 U% i& h0 T; F9 R8 W; E4 u& N
0 2020-01-01
4 j8 i" S& P4 {5 A" b; H; |( ^1 2020-01-03: _) u& E! l; Z8 V1 E( T
dtype: datetime64[ns]' r' |/ r/ o7 X7 y
1: D* Q: p n' N: H. C
2
}3 _+ Y- u6 b1 P4 ?3/ @5 R t0 K4 t! o! D
4% i* V7 H) u+ ^. `- E
5
* n! {9 I* n! D; P/ d. t4 Y下面的序列本身就是Series,所以不需要再转化。: N1 ]8 s2 K/ b# B6 S
& e0 U' |8 j6 j% z4 h
df = pd.read_csv('../data/learn_pandas.csv')+ {5 ?" _* _! Q# B; R* v
s = pd.to_datetime(df.Test_Date)4 F: t6 I' G2 l) l' x( c. h$ [1 z9 N
s.head()
( L0 Y( n `+ S/ F: c! ]% m' ?0 d- ~
0 2019-10-05
) j) w, |/ |1 n' R& H" O1 2019-09-04
# X/ ?. c) f9 v2 2019-09-12, ], [( _8 `0 S3 ]9 [
3 2020-01-032 M: @7 H- O _' N
4 2019-11-068 a0 Z# \; R, E$ w/ \
Name: Test_Date, dtype: datetime64[ns]6 j0 ?! ?: V6 S) Z4 }
1
5 r; \0 O7 X4 {. K23 M3 D7 h8 d, O/ W
30 e& v4 }9 M/ ~! ~5 ^
4
- D/ J" k; i# l5 ^4 Q! {5 C0 m( a; {1 |# g8 b! Q& B
68 J# p3 y5 c2 p6 A: }% |/ ^- Q; O5 @
7. X8 w H/ {8 ~) _; P; r. }- x
8) b7 N! {9 @4 e$ j
97 d: E5 L( Z0 a( R: Q
10
0 r1 z0 w4 H3 y8 h把表的多列时间属性拼接转为时间序列的to_datetime,此时的列名必须和以下给定的时间关键词列名一致:0 `( g; S' C; s! Y3 H. G& J2 g
df_date_cols = pd.DataFrame({'year': [2020, 2020],
+ M9 G8 q4 c- z; [- z( B 'month': [1, 1],, p5 U" w6 B' t0 A) d# b3 Q
'day': [1, 2],
/ P. }) t6 s: C1 u7 k& x# ~7 e 'hour': [10, 20],4 p% v. r0 {5 ^+ ` X6 i
'minute': [30, 50],
8 f3 U5 A X' [ 'second': [20, 40]})
* z4 e- j' v- \6 y# Cpd.to_datetime(df_date_cols)
1 d4 O% W5 ? Z7 \$ g" r( Z( V. ?, q1 ~' ?
0 2020-01-01 10:30:200 y2 _* W1 t6 h/ w6 P c& L
1 2020-01-02 20:50:40
3 X4 R0 U# L# M, C7 ?: gdtype: datetime64[ns]
0 ]8 o6 F7 ~: a6 D2 q1
+ {6 i# f$ C; P- T1 [& }. p% g2
' d$ B8 L& @4 `$ E. v0 ]4 |3* R7 {+ j( C9 [
4/ n2 x* a. l( X! n V8 A* a
5
7 k2 A r; O" W* x- _* ?6. |% ~% \ z# ?& l; n
7
! w0 D8 Z* G! c. H, o8* V1 g* H$ M+ d2 {! I7 _
95 r: B" i* L$ o$ @1 j% I2 Q
10
/ Q, Y9 k2 J4 B0 B11( I$ C8 w3 F, T0 U! e
date_range是一种生成连续间隔时间的一种方法,其重要的参数为start, end, freq, periods,它们分别表示开始时间,结束时间,时间间隔,时间戳个数。其中,四个中的三个参数决定了,那么剩下的一个就随之确定了。这里要注意,开始或结束日期如果作为端点则它会被包含:
1 g' @& v w- T) {) T+ X3 z+ wpd.date_range('2020-1-1','2020-1-21', freq='10D') # 包含
! p) l. z3 ?2 i$ ^/ ^% NOut[25]: DatetimeIndex(['2020-01-01', '2020-01-11', '2020-01-21'], dtype='datetime64[ns]', freq='10D')
# }: e! |+ K. o- e3 R
# r' H, \" w( Z9 Q+ e! {$ Tpd.date_range('2020-1-1','2020-2-28', freq='10D')7 |% E( l3 f2 S9 p. Y3 d& @: f( a2 i
Out[26]: 4 t% m; R$ H5 E C
DatetimeIndex(['2020-01-01', '2020-01-11', '2020-01-21', '2020-01-31',4 l3 x1 l# n, f
'2020-02-10', '2020-02-20'],
) t, I+ s4 ]/ S, @; u dtype='datetime64[ns]', freq='10D')# C! D0 X! ?) I; |, P& R, W) J
8 P& i+ J: J) C2 ~) [; s9 p! g: k
pd.date_range('2020-1-1',0 u9 `& L5 Y: l" e2 a2 Z
'2020-2-28', periods=6) # 由于结束日期无法取到,freq不为10天 q4 U; y+ V ~' j/ e# Q
- P5 l/ q+ T: M9 H6 g( @
Out[27]:
- R0 e: ?# S/ ^8 N+ Q. u( JDatetimeIndex(['2020-01-01 00:00:00', '2020-01-12 14:24:00',
( S1 b# K8 p! x. M/ d7 Z '2020-01-24 04:48:00', '2020-02-04 19:12:00',
3 `9 ~) Q* z* G: B '2020-02-16 09:36:00', '2020-02-28 00:00:00'],1 n# L/ d6 }/ R" f
dtype='datetime64[ns]', freq=None)
( L& y. D2 z/ v1 R9 [7 z
' U3 m0 D, @# x4 D+ K1
) C& W( h, D9 N2
5 ~8 y0 h( N h& x5 U4 C3/ g& ^* p/ L& T" ^+ |0 ?
40 Q& L4 R, Z2 x. Q, s
5
5 D5 l+ r. H8 @. ?) z4 j6
\, m4 }$ Z8 P7 f. U! j78 J, |4 l3 r1 o9 K
8. J% N0 S/ J* x; n8 X! R. n
9
5 m' l( N% o1 v" F- a o10
3 o6 U2 y$ {* m+ F2 y* u2 ~. {115 d* Z- \3 q3 C7 u; W7 l, S
12
! `% g' K$ { i0 ^4 T/ B135 p) p" I/ ? r
14' P1 W. [3 l( b* w
15
& q4 @! H4 M* e16
$ a3 _ p7 T2 Z17
* H5 u3 Z; q0 V1 q" \这里的freq参数与DateOffset对象紧密相关,将在第四节介绍其具体的用法。
4 x. ?5 |+ p. Q Y6 g0 d1 u
. e# r ]1 G- N/ G! y【练一练】
7 ]: G+ s- H1 z9 i" cTimestamp上定义了一个value属性,其返回的整数值代表了从1970年1月1日零点到给定时间戳相差的纳秒数,请利用这个属性构造一个随机生成给定日期区间内日期序列的函数。
' ^ G" m3 U4 B( s' I0 r, U2 {$ D) v; b, |
ls=['2020-01-01','2020-02-20']
2 ^! Q2 c+ L1 g% ^+ U# H- ]! F+ m+ idef dates(ls,n):* B! g; l Y: G3 E1 [+ N" v" f* _
min=pd.Timestamp(ls[0]).value/10**9
" g& u. N! X8 v" Y. a max=pd.Timestamp(ls[1]).value/10**9& }* H' f+ O. v3 r9 I
times=np.random.randint(min,max+1,n)! n, T4 ]0 f4 u
return pd.to_datetime(times,unit='s')
9 V6 B7 k9 D. x7 K0 I7 [! c# qdates(ls,10)
; U/ W( {2 m/ F+ A
0 C5 H+ ^- T; {$ Z1 ^. P- ODatetimeIndex(['2020-02-16 09:25:30', '2020-01-29 07:00:04',1 r" m3 S1 J4 `- u; Y5 `
'2020-01-21 12:26:02', '2020-02-08 20:34:08',6 { h0 o5 t+ w
'2020-02-15 00:18:33', '2020-02-11 02:18:07',# _5 r w8 m- s+ r7 s
'2020-01-12 21:48:59', '2020-01-12 00:39:24',
Z+ m2 s% l0 K6 G! b6 I: G '2020-02-14 20:55:20', '2020-01-26 15:44:13'],4 z$ Q1 l8 Y2 h, M1 e. g& Y: j& U8 n
dtype='datetime64[ns]', freq=None)
$ x1 W& m f- ~! z; f1& m H; G/ K5 a
2: o# T0 w2 F0 B5 c
32 R5 F3 U4 {8 J
4
" `( ?* W* n( q" e+ [5 G5
5 Q' l" C9 g: ^& a+ B; @4 u$ q6
6 k$ l5 w2 c! D7# i) ^+ |+ ?5 [' i9 S, m
8% E' c) U& x$ Z3 j7 w, X0 T
9
\7 l' I$ }7 C2 w: F* a10
! ~6 C7 `6 y7 u8 E: n! Q% Z' y" j9 N110 K0 b6 P. z) W' z2 d0 y0 Y6 S
12
# p! T1 f3 i6 q13
1 R" ] C' X4 m ?# ]" S- n! v4 d14; ~6 X- w( u# t" l4 P8 d. H$ n
asfreq:改变序列采样频率的方法,能够根据给定的freq对序列进行类似于reindex的操作:
% g3 s$ p# ~. b' z Ws = pd.Series(np.random.rand(5),
& A4 x+ ^$ {6 S index=pd.to_datetime([
+ j" [7 T- m# p4 E) J6 l '2020-1-%d'%i for i in range(1,10,2)]))
5 O5 M) Y2 S- p8 s" h& a% G7 W0 g7 A/ i; F3 s Z) j8 U
6 B6 X+ B, L/ U
s.head()" V; I2 p) ?. @; S
Out[29]:
m% Z# j0 `* v0 w* |) I8 s2020-01-01 0.836578& w1 @! s! z, }$ s# S
2020-01-03 0.678419
+ m& o7 |& X: G% w% L3 a$ ~2 }2020-01-05 0.711897, i d# s) D G% Z3 D' {: H- C
2020-01-07 0.4874290 K9 x6 c) C9 R9 [- o- L+ |
2020-01-09 0.604705' Q& A4 F3 X$ u; j" r5 U3 F: Z
dtype: float64# T, r& W! J9 H1 p; M
- K/ S" g- Q; c0 a8 t/ ]
s.asfreq('D').head()
' E8 ]" o6 i' IOut[30]: % f' V. y) B" J6 w
2020-01-01 0.8365789 T3 F1 c* T' b4 F w; O" V, T
2020-01-02 NaN6 _- H% V6 {: G, S: Z! P5 w
2020-01-03 0.678419
8 W! g4 P* U' o. w7 b) `+ T: Z" M2020-01-04 NaN( N6 c- S" r# X x5 w2 t$ u
2020-01-05 0.711897
' C- `" R3 d, K, f6 GFreq: D, dtype: float643 |6 ~$ t& r5 j: G% X3 ^5 S
! P4 m" Q- x( Q& ?+ R j' N
s.asfreq('12H').head()3 K. Y' t) w Q5 p2 x5 F, l: Y
Out[31]: # | v. ?7 z# g7 `
2020-01-01 00:00:00 0.836578% i- g! H' O7 B, A( B2 O( @" ^
2020-01-01 12:00:00 NaN+ B6 H$ X# K! T
2020-01-02 00:00:00 NaN
) f, n) d1 X# z g2020-01-02 12:00:00 NaN
4 D A6 P, D) t- i4 H2020-01-03 00:00:00 0.678419
* w' E* p p3 D' EFreq: 12H, dtype: float64
: [9 }# o: P) K6 k
0 @$ n+ N! h& ^9 s: O- @0 T2 e1! p3 c- b% B5 F) l& g; L- Q8 a
2# Q" X2 |5 S d6 t8 Q; X
32 h" k' `+ I$ c- u/ v4 m
4
+ [: u9 L8 L* U i+ y5' {/ S0 [$ z s6 y6 `
6* L+ ^4 L* h/ o) a
7. N1 e2 W$ B, E9 E
8
6 m! c: [4 V! Q4 W% o+ P3 Y9
# i# `% l# d; u4 v( b; `6 I7 }10
6 h7 x" O( G7 _( \11
# K9 Z+ S3 V/ r1 c! W& Q% y12
* ?, o, y. m$ S- w- S& N13$ I; I' i' h2 y) o; `
14
" l5 E K) ^" Q3 ` b15
# g: ]1 s! W- M" `9 `& D16
8 J3 M, s: }; g Z17
# e/ g2 B1 N7 X6 _9 w18
) s2 j9 z+ @! ^* T, O199 T* d3 @5 x+ x, o% ^
20' ?- Q$ u2 x3 B
21
0 t3 K+ Y8 F1 u+ T2 u; n/ [' o222 y, W% y2 R1 {" o
23
- l: I ^9 n7 p; a4 m24
/ m" W, U# v/ p$ @* h9 E25
8 C+ Z$ I4 L- Y8 x- |26% W5 l* Y: j( Z# p8 Y* N
27
1 G/ C5 T% b8 m; ^; p7 C28
' j0 s: P$ a3 z- I8 ^29" B( @ N$ {' X0 V9 U
302 x" D* G, g& a0 w8 O% K0 l
31
) n9 P* U$ a, B! Q0 R& q【NOTE】datetime64[ns] 序列的极值与均值
" c8 `, c7 r3 z* M: i# B/ a 前面提到了datetime64[ns]本质上可以理解为一个整数,即从1970年1月1日零点到给定时间戳相差的纳秒数。所以对于一个datetime64[ns]序列,可以使用max, min, mean,来取得最大时间戳、最小时间戳和“平均”时间戳。
3 `# l& r- Q# R$ r+ b: d8 B1 |; T% c$ b' N- f
10.2.3 dt对象
$ V7 u5 s6 {" N0 | 如同category, string的序列上定义了cat, str来完成分类数据和文本数据的操作,在时序类型的序列上定义了dt对象来完成许多时间序列的相关操作。这里对于datetime64[ns]类型而言,可以大致分为三类操作:取出时间相关的属性、判断时间戳是否满足条件、取整操作。+ t+ C+ {/ @; K1 Z3 ~
" Q2 `0 V: E- W g
第一类操作的常用属性包括:date, time, year, month, day, hour, minute, second, microsecond, nanosecond, dayofweek, dayofyear, weekofyear, daysinmonth, quarter,其中daysinmonth, quarter分别表示该月一共有几天和季度。9 y# [$ a* T: {6 Z. Z
s = pd.Series(pd.date_range('2020-1-1','2020-1-3', freq='D')). _( _& x- v+ i& b: o' i5 L
* }0 s( i; Z5 ~3 N* @s.dt.date, L. S2 F) ]4 C, W% N M" _, o
Out[33]: 6 b/ j3 j% U/ D& S
0 2020-01-01
5 i& M3 N1 D& r# q8 i9 x: S' ?1 2020-01-02
( `9 e& T1 z- R% w; j: Y, }2 2020-01-03
' c4 |" T* c' o' Q( y; pdtype: object8 x& X- ^2 o2 Z; ?: m i
9 {2 y7 E( {' }$ q: o
s.dt.time" [! |. O9 b# l% q4 N
Out[34]:
1 n5 P3 |2 r9 q! l3 Y5 K0 00:00:00
- p: o2 F8 E" U% c, z1 00:00:00
5 l- z/ F4 i8 f2 00:00:00! q' w0 }: A' \, Z
dtype: object
9 X0 }8 q. p q& x, t1 K; s& ]3 a$ J/ G7 Q
s.dt.day
+ y+ ?" F( R; A0 L; BOut[35]:
" i' D* Q% W2 m, I0 c) _% @9 i0 1
" d" F, V5 E% a" s% O1 25 N6 k [ p$ P
2 3
, s6 Z9 J' ]' R& C( u9 Wdtype: int64 C! F7 o6 F$ r# \
+ O/ p8 p4 ^7 n! vs.dt.daysinmonth- O* l, s* F/ A) N% _8 S, D
Out[36]:
/ w. z9 K8 J3 }: _* C! M0 31
: ~7 ~5 J) I5 V5 |1 {1 31! g: N/ Y4 M, L! u( w
2 31. \( Y0 e$ |0 q4 O3 ?
dtype: int64
' t( S9 y" O) r% i
' B) F2 O- h1 `# y1
% p T# P z6 Z c) {2
9 e( Y- u% Q0 R! v! n% o3 e- c2 E+ L+ {! ?3 D& F
4. d& o8 n9 w- J. Q9 w- i
5
+ U O5 Q, D7 f. Y6 `; N8 b6
2 C3 A! D0 N0 M" ]$ w o8 F0 I70 r: t* L6 X* S) a5 Y# s. f
8
" C# \3 c9 N: _' P9
3 G8 {% Z+ q1 g5 Y \* N; y10
, U! j4 m& w3 G6 E4 s9 x9 c% h11
* k( f* y. y+ d; ` B( I: N12- y: W* y9 y; r3 l% l
13- f8 a1 ~2 ~( L6 P- m
14
& ~. i$ U E5 F0 O7 t152 v" p' t/ h' D3 b7 }% h' g. T
16
% b r( ]" m' m- n" J# K177 L& j: m8 `* n x: ?) a0 S
187 \: s% L, x- ?5 W
19
# J V, {1 U' I( [) {20! a- @+ f1 K! u
21
$ p, C* ]5 N* i1 `, |" I! L22
" B# P( J c5 t; @- ~ }23
- D5 v% a9 x$ O1 a" \2 N4 |24
( d% O! C5 h; J2 `# g- L25
$ v X" v3 G/ v H26: J7 C' h9 W1 v+ @
27
* s8 w! R' G& s! z4 R6 C6 ]28# ?- e* d/ c8 n1 B9 z) q, O( ]1 R0 {
29
5 U9 w7 W' k2 c8 \* s" g- A 在这些属性中,经常使用的是dayofweek,它返回了周中的星期情况,周一为0、周二为1,以此类推。此外,还可以通过month_name, day_name返回英文的月名和星期名,注意它们是方法而不是属性:% U; d/ C- B% L' n2 A' {
6 v* |, w5 T/ c; j8 h4 u0 cs.dt.dayofweek. ]6 `7 l( `7 e
Out[37]: N) M6 C6 q, c0 e5 v' i" W* ~" B. ^
0 2% S' q- {* A; W E( c/ r9 d7 X- H
1 3! x% g& x/ U5 k9 d
2 44 l5 I1 V5 P7 [* N
dtype: int64
# k/ Q# z$ y `: s) T2 w# g8 U `
* T" t/ l' S2 m3 d. O1 V4 l7 D. i" Bs.dt.month_name(), X7 B! f3 x* f: H+ d; J- G$ ?
Out[38]: / A1 \# {9 g. w1 [% ~" R
0 January
$ @* d' _% |7 ]8 S0 [, F4 G$ j1 January( G$ B# L6 _7 s; c( m
2 January
7 o8 g% {1 n; B9 edtype: object
E' B9 c9 B. M& P) ~
( D. a6 X( R2 T( c$ Z$ O9 Zs.dt.day_name()
/ x0 k# g1 e. X* d& hOut[39]: ( q+ {& @# t/ F h" v1 C+ ]
0 Wednesday3 _* x1 c" T8 g( @4 u% T0 t- o
1 Thursday; w( j6 O) l$ a5 U
2 Friday# S: y0 H8 S1 [
dtype: object- ^5 h8 S# {' j- ~& ~: [7 ?
; I. [( }2 P4 z% @
1
% R, s$ o' m( O) }/ O9 Q21 P' A* t2 b9 w2 v# ]. P; p
3
h Y9 `2 |0 |7 [4 K `/ r4
( }, P6 J5 M# c# |+ q50 t$ u- x! x ^
6. f8 [- d& F: m1 m+ \9 u
7! \( A+ `0 p7 U% C8 C
8
, u. y4 n$ w0 A+ h1 O7 b, m& \9
& w* g* E' G# k3 t0 `104 K, a$ p/ o4 i+ P9 K5 u2 r
11
( L3 m* w/ p( }8 K) O" S12, |6 }0 o! G1 B( r
13* X$ n3 Y0 h! v; ]- w; J0 o
14
; Q+ o0 f: X7 Y9 v, y7 n15! m* Q6 h. b$ a H' Y2 E
167 j9 ?3 ^; u# t4 L8 {
17
4 k2 m9 O& M3 _& h2 F D18
`: _5 M" `& F1 t1 K9 k$ I199 }) {# T5 Z5 h. k* L( r- v
20
1 _ t0 K6 |4 Z9 m第二类判断操作主要用于测试是否为月/季/年的第一天或者最后一天:% Y) \& f: I6 Z' w) C9 I
s.dt.is_year_start # 还可选 is_quarter/month_start% l$ T1 e# _8 X3 ]) L: f/ L8 E8 X
Out[40]:
& P& L J# z4 W% @0 True
/ ^6 n/ J* C4 L2 A" l+ Q1 False/ ~) j% |$ p1 B# x: Q# k
2 False
) M1 y* c# F1 odtype: bool$ s+ W) v8 U! g/ ?% t
3 l+ g9 S9 X" [- ~2 n% \2 Rs.dt.is_year_end # 还可选 is_quarter/month_end2 t" V+ a i0 ~9 g# _1 x" U
Out[41]:
2 S2 y/ q" g; o: ]% H2 |0 False
9 `" F% \. |' o1 False
* l% r6 Z5 n7 j" _2 x; G& h' ?: x2 False: v' z$ p* y/ G
dtype: bool" `. z: \% s5 X) i4 ]
1
! s" h9 o9 p3 ~) R' z! d2
. `0 F" K, q+ i. I+ E: T* I3 s3
7 C5 u; H1 r2 g; y8 c# E8 C$ @: r4
# Z' H6 R$ ~6 w( S) X# ]5' V& ]# a t* E' Z8 I' e
6& p) k; y5 {' Z: L
7/ `& s( Q4 @5 T* G2 C/ m, {
8. o! A- ~" N/ t
9
4 Q1 y3 _, I7 p3 R% i! B10, _0 k. s1 s3 ]2 ]. m: r+ j
11. E# @5 a+ ?. t$ W
12! h* C* L+ {1 I" n, ]: A
13
- r9 ^1 n' a; ~6 w第三类的取整操作包含round, ceil, floor,它们的公共参数为freq,常用的包括H, min, S(小时、分钟、秒),所有可选的freq可参考此处。
& m0 n0 b9 E3 X3 o vs = pd.Series(pd.date_range('2020-1-1 20:35:00',
" Y5 O1 Q& q& o# g% { '2020-1-1 22:35:00',
/ P# C6 j" b1 H8 a R freq='45min'))
( O* Z; a; o5 w2 _% I6 [
8 O# K9 v# U, c1 W- N& ^5 F
" H; n; l7 a) G) qs
. V$ k0 r/ z+ MOut[43]: $ m/ {8 B% ?2 m8 r
0 2020-01-01 20:35:00) }9 x. \/ }8 p# Y5 _& N$ E
1 2020-01-01 21:20:00" z/ h( Y, ^9 k* M
2 2020-01-01 22:05:00
$ g5 U% [) n0 z* {4 p/ b3 Ldtype: datetime64[ns]
) X2 u: R% m3 w, K7 D! |# x: H2 I) ]0 a P2 Y1 @3 C
s.dt.round('1H')2 v- P% p3 @) r0 `' q% T
Out[44]: 9 J. g, }4 i0 `; V+ [- D! [' W, t
0 2020-01-01 21:00:004 G* I! O' h7 J+ w
1 2020-01-01 21:00:00; H/ {. w& F# e
2 2020-01-01 22:00:00
# G" V* r3 J0 Z8 o( Z3 P% N( g) Ldtype: datetime64[ns]5 S" I% n: M; h8 B* n
( j$ k4 }- H" J& s- h, k) k/ L' d
s.dt.ceil('1H')" m5 l* g4 @! @. F5 [5 b! Z" p" D# z7 N
Out[45]:
( A+ R5 J$ ?+ w2 A, I$ ~9 g0 2020-01-01 21:00:00
, e5 y1 U* ^1 B) V; O# x) r1 2020-01-01 22:00:00' W- }/ R: p% K' o9 @) G
2 2020-01-01 23:00:00' F, C. i. V% z G
dtype: datetime64[ns]+ T0 M5 e: P" S; N: ?3 U F
, h6 Z' i; y* [+ N9 Qs.dt.floor('1H')
3 F: G5 Z3 I# mOut[46]: $ |! {8 T! g l. L* k
0 2020-01-01 20:00:00, E5 b1 }6 [& t) W/ G( E' a
1 2020-01-01 21:00:00
' U% J; g# ]7 O9 i" m2 2020-01-01 22:00:003 S2 z! Q% x# z' i) o- {) F
dtype: datetime64[ns]
$ v# \- o- u+ n' i: ~6 u2 t1 x; F0 x, s: s' N) y7 u
1
! D+ A6 }9 N9 ~, g z8 I28 n1 @" {% q* s* o/ p; E5 r8 G4 v! t
3
* I9 F) j* {2 u) X, r4
" L: d" ?% w9 N, u& H* K5! K8 x. N6 b# c/ {/ N
6
8 ~: O: ^% _# Q- v4 t! s7$ O7 z- n" g) ^" \
88 `: N4 g* Y m. M
96 u5 |5 S: s, [* a; z5 B) S6 o
10
$ b) l3 I9 r! \8 {111 T1 t1 w' K6 o
12
* A! d. }# w0 _- t# C13) \, h8 i' ^- P2 Q" x
14& |5 M: K' S% S) Y
15, k% Q4 n+ Z, ?/ F! m! h
16/ C5 E1 x0 g9 n/ J0 r% h6 w
17
4 l5 S9 m6 `3 R# s18* _, F Y' } s" \% X, U3 {
19
" y; ^6 O, G: I# w K20
% v9 u" K5 Z3 `. Z' ~% m213 w) a4 o7 w4 X* ~4 W/ b
224 c, }2 _" I) y3 ^( G
23# N, b" F) M; E/ y) `$ p# }6 z
24
" E6 r# f' Y0 f% M+ v4 p25
3 t1 V) k5 q1 Q- P& U26
# B- T8 {1 m! m! F' ~27& H. j* w! S3 x- H% H6 J; V' h
28
9 d% N7 J) ^0 Z$ m8 ]$ J5 ]29
& W" R+ G2 A0 r, Z( U+ v307 { Q. D# O; d b7 V
31( N0 n- [9 F! M- b8 {9 L
32# {- p6 ^; k$ v; @# y
10.2.4 时间戳的切片与索引& T! O4 }) L9 l+ y8 _4 b
一般而言,时间戳序列作为索引使用。如果想要选出某个子时间戳序列,有两种方法:( c& g2 u! V5 H4 v i1 ~+ ]
) R+ }; w& k5 `$ w* u! S3 X利用dt对象和布尔条件联合使用
: k; [$ `- D1 e" M. U- T利用切片,后者常用于连续时间戳。 {4 J6 S. f$ O( e
s = pd.Series(np.random.randint(2,size=366), index=pd.date_range('2020-01-01','2020-12-31'))7 a6 D) Q# [% k/ i3 i6 } `
idx = pd.Series(s.index).dt
3 ?; n6 y3 h# Q4 E# V9 f4 ^) S2 ps.head()
% [, ~1 m0 _' \2 f% y! h5 n
/ U) X8 C, n1 R" G2020-01-01 01 m* C0 D* c9 l8 l. u% w
2020-01-02 15 W% X, N7 o9 i, l& W
2020-01-03 15 E+ Y$ v" O4 B0 X- f
2020-01-04 0; V1 ~' I- N$ m9 c. w8 b1 O9 o2 z
2020-01-05 0) b0 V8 G$ L# l. S, T& W6 Y
Freq: D, dtype: int32
6 \; l! i/ X: n& K& S/ k+ g13 h( J! @( [$ k" w4 ]: f. Y
2
- o) x) c4 o5 C, [33 g: y4 T1 d: t' y9 \. ~3 _
46 }$ F/ r2 P/ j6 R2 _
5. x, l [+ X( c
6; y! [% I; U' T0 P& L$ v3 f0 b
7
}$ r# R _$ Y: M, t6 ?6 O8 O% M$ h8
/ d. h1 r3 [ ~+ m" K. y; D9% Q; ?# Z3 ]& ~* G' }
10+ G! ]: Q/ T" x# l6 e
Example1:每月的第一天或者最后一天
. E0 O8 _5 V2 N& s
, i9 I: A- f' W8 t" T/ q( G* l2 Ls[(idx.is_month_start|idx.is_month_end).values].head() # 必须要写.values
: F7 o0 ?+ e/ e& XOut[50]: $ H# o" D# a& `& l% y
2020-01-01 1/ t) {! d, _$ B
2020-01-31 06 T. v* N& h1 I) c; N
2020-02-01 1
3 W) G L) k+ j; E2020-02-29 1- o. |# _' f8 n1 M5 [
2020-03-01 0
* _3 G0 X; V8 n: vdtype: int32
* N# G6 o& }2 o8 C0 d& u1& {# C/ t) ^& N3 E6 E( |& G8 `! r
20 X8 l# w* Q1 P2 a8 E
39 t2 p; k& }8 f+ i! B& e2 N
4
; H1 Z5 }/ b1 m5
$ }: S" Z" P% ~9 ~9 V! _6, x1 \2 `7 @: J$ D1 l
7
( }8 g0 d. l! W* r8
) v! r' D8 Q, f- L* zExample2:双休日
; i& h/ s! _' X V: _# [. a% S( _
: G8 C ?; N, ]s[idx.dayofweek.isin([5,6]).values].head()
/ s P: p' o% j& O+ k3 |Out[51]: ' n: s7 j& S7 E1 l# }" C& o
2020-01-04 1$ U, f) n$ [8 {' l; C1 m" C
2020-01-05 0
4 H' [4 E7 u" o" `/ G% R2 ^$ x2020-01-11 08 T5 D4 c# ?8 z
2020-01-12 1# L& |7 ~7 g1 U: O( F
2020-01-18 1
/ T- B7 E" v m. M4 ]* b# sdtype: int32/ v# @- T; l, l4 p3 o8 E' b
1
6 f. x( z+ _& C# U0 N: v& y23 H; D+ C5 r8 s% c, L8 I" a; o
3
: S; |, k; y4 y5 R w. k) S$ |40 f: f, Q" x+ T9 J
5
+ T( I! I% v; h y- Q! S, w64 Z7 B( p1 |' k+ W) P. E+ a) K) S
7
5 A& I/ l8 T* n) I1 z2 f) v( y# {. e8
9 Y! g B+ N' x0 }- ]Example3:取出单日值4 _7 V3 r8 s( |4 c& d
6 q( W8 N* l& a. Z
s['2020-01-01']
9 W2 I1 y3 a1 q7 tOut[52]: 1
! ?1 H, ?) E, T4 G, H3 N$ f8 R, I
1 m6 \% ^5 ^9 J& _9 ns['20200101'] # 自动转换标准格式. x) ~9 D! W3 C$ C2 Q! y. G
Out[53]: 1! C! w, r$ ?3 }
1' c+ ~1 V3 ]9 ~9 |, Q
2
' s" ?% S& J+ q7 x7 R& c36 G$ B4 K: m& m# o0 l$ f
4
/ U# m' k7 y: v! H' H1 }% K& t/ i5
$ K4 w0 Z) \, g( o$ ~1 i3 V0 @Example4:取出七月' u* g4 F. R7 N; _% }
7 O: b$ b* _7 ^% Ds['2020-07'].head()
; i/ L+ \, o# y7 O* G( C1 B6 V2 cOut[54]: , v& u: |9 Z: o
2020-07-01 0
8 I+ a6 {5 z9 \9 J( @+ Q0 m) ^2020-07-02 11 C# l" q! L, L6 q8 M; G
2020-07-03 0 k: O+ l! W: F0 \
2020-07-04 07 c. v0 }) _2 O" h8 @' J
2020-07-05 0) f; f& f! I# o6 r& I) \
Freq: D, dtype: int32
* m+ q: O) w @/ E" r8 X5 F1
: o/ t" S( Q2 g+ S2
# i. X3 d( \: T% y4 L35 ~: B7 c K' H& Q
4
0 l' z1 f( \( R5 K1 Y5
# M+ C8 X- }9 N) b" T# G) g: d6
1 w4 G$ }% j# p r0 M4 K. [7
9 J. v8 O& u( r8% {+ T+ d6 r, x
Example5:取出5月初至7月15日* T7 O( ]3 G9 G f# [
4 p! k1 I x& ]- F3 }4 js['2020-05':'2020-7-15'].head(): A& q5 `$ O, G$ w/ a( l* D
Out[55]: 2 P5 v/ r- c4 i$ i; h1 K' M* m
2020-05-01 07 S& W$ o' S! x. y' t9 r2 J
2020-05-02 19 w9 y7 L. \' N, @$ t6 f
2020-05-03 0
- A+ q _% p) g& A2020-05-04 15 ?1 Q6 s! A0 n) F. i! O
2020-05-05 1
5 t) Z" h& n' u. i8 {: g7 ?( CFreq: D, dtype: int32
! {* p& f4 N+ o8 b+ A! V: z i4 V5 y0 D2 v
s['2020-05':'2020-7-15'].tail() R" |3 u T9 M$ n6 ]7 {. F% Q
Out[56]:
0 j, }2 ~. g; w% w0 o2020-07-11 0
+ ]5 `& |" C& j; I {2020-07-12 0$ B1 w3 h) Y9 `5 H4 z5 u3 V
2020-07-13 1
6 G$ F5 V! T0 z% O6 Y4 q2020-07-14 0& f4 i" c+ x1 e/ C+ ]. C; J
2020-07-15 1/ h( J) V ?3 F. s3 ~4 r
Freq: D, dtype: int327 ^; f3 ?& ]; E5 v7 ~/ E0 o
, b0 f D3 C$ N) Q1 |
1
8 o7 {, W# @& x) S' Y2
5 V5 b: `0 ~& m1 t0 @% [5 f3
- ^, ?. j, H9 g5 ^5 ^% d/ _, f- {4
/ q5 E A) t; C# D- Q# Y55 D8 p2 X x& R' m ~, w& }( C3 Z4 [
6; v, e& p* N8 T" d7 y
7
0 f; n/ ~/ s4 e- J1 ]( A8! B+ u4 G+ c9 b/ @6 M
9! i0 I3 {( X" o2 J% d$ o+ V
10
V% e* w3 O' Z5 Z8 @/ @ x11! ]- S; g, }5 b& J# J* R
12* o7 h' _2 s' z
131 P4 x9 \7 l# |8 H. I& }, S: I
14 Q* g3 _* I, }, Q0 G. ^; C1 a
15
) g% [7 c- q: ]3 _1 T7 l16
. P) w7 D* D3 p17
5 t p; ^) ?8 C: J8 }& e10.3 时间差
! K% Z& N$ w5 l: W10.3.1 Timedelta的生成
$ }, l3 r# ]1 w2 z' V/ |' {pandas.Timedelta(value=<object object>, unit=None, **kwargs)6 K3 `+ }7 P! F" D
unit:字符串格式,默认 ‘ns’。如果输入是整数,则表示输入的单位。 K( ?9 n7 l2 W9 ^. O( M+ E/ L
可能的值有:
' \$ A' \1 O& a+ q$ d0 @1 W( t. f b, i9 X! E
‘W’, ‘D’, ‘T’, ‘S’, ‘L’, ‘U’, or ‘N’
* b6 I0 K6 m% a# J# [‘days’ or ‘day’4 s. ?0 \3 X/ [4 U
‘hours’, ‘hour’, ‘hr’, or ‘h’
8 b5 W( |# D; r, r5 d3 G t5 ?' P‘minutes’, ‘minute’, ‘min’, or ‘m’! `0 o3 ~# w5 k" j& j6 J
‘seconds’, ‘second’, or ‘sec’
- T' u( ]5 j6 R/ p- u1 l& k( D5 }/ @毫秒‘milliseconds’, ‘millisecond’, ‘millis’, or ‘milli’' y9 t) c# ^ ?
微秒‘microseconds’, ‘microsecond’, ‘micros’, or ‘micro’
: P- K6 r1 I3 H纳秒 ‘nanoseconds’, ‘nanosecond’, ‘nanos’, ‘nano’, or ‘ns’.
& |+ H& y- i x( f时间差可以理解为两个时间戳的差,可以通过pd.Timedelta来构造:6 ~, U5 q& Z, ? k, Y. {- s# K
pd.Timestamp('20200102 08:00:00')-pd.Timestamp('20200101 07:35:00'): w+ G3 n# v. a% e1 I' R9 x
Out[57]: Timedelta('1 days 00:25:00') l( D2 D0 T3 G+ v
, O# d( v8 F% a Y: m
pd.Timedelta(days=1, minutes=25) # 需要注意加s
2 Z! v1 o5 s( i, k iOut[58]: Timedelta('1 days 00:25:00')
5 W, `! }$ D- i. I Z: u* b0 ~2 f7 l- [# X
pd.Timedelta('1 days 25 minutes') # 字符串生成
' y0 ~4 A3 g& U/ VOut[59]: Timedelta('1 days 00:25:00')- o. d- W8 l. e; y* x
- Y/ \1 B. D" ]4 \- ~pd.Timedelta(1, "d")- i5 x- ~9 _" `1 r' I0 I0 s6 K
Out[58]: Timedelta('1 days 00:00:00'), U* {2 M. e4 c( X) F
1
/ ~/ u4 O; B7 {8 L) x5 Z2
0 b/ c2 n! I# w: q7 r% e0 ]7 S3
/ p2 k! z( O& h( s- e2 {8 X4 j: ` i$ k6 |- i$ \
5/ A& A0 V0 C8 I. o/ t% Z: n6 p: g- C
6* `! K2 D ^2 \# I) u8 d2 q9 v
7- W, r; y$ p; w: g
8
+ r$ m8 f9 N! }4 T3 _93 Z) G' |9 e B
10$ f4 G& p- }* @% Z% H7 d
11& W" I6 k2 C; A7 e* M$ v
生成时间差序列的主要方式是 pd.to_timedelta ,其类型为 timedelta64[ns] :+ D6 A5 i8 g5 x$ V9 [. V
s = pd.to_timedelta(df.Time_Record)
) @& c) i* Z! g" Y6 ~- Z0 g9 P
6 v# s. v9 w4 y0 N, D. rs.head()
% v! v3 }- Y6 ~Out[61]:
% E7 I+ W% x! J* u1 \0 0 days 00:04:34' N0 z' r! U8 M; K$ o% d
1 0 days 00:04:20
4 b4 w- e4 K/ x% ?. ?6 ?+ ]2 0 days 00:05:22% U8 H0 ]( C# E( d) O9 _
3 0 days 00:04:08
& K Q+ p# ]% [1 }$ N* H7 m4 0 days 00:05:22
/ D4 ?. c, i+ {" dName: Time_Record, dtype: timedelta64[ns]2 W7 g; ?# H) l7 l3 n, s, e8 X4 Y
1! g0 B0 l$ I; v, f/ ]
2! \! D! d) V8 n* R! j( D
3
1 _9 D( e; h4 r6 X/ c+ | D4$ l0 P) K, D+ W
5
& M5 B' I+ Y; R0 b- s6) K& ]2 g9 c2 s8 D8 D, P+ {: k
7" T3 @6 ?: {. `5 E t+ i/ d/ v8 x
8
) h$ v& j$ N, ^$ [$ K5 F95 o6 _" b8 \% V" N9 t7 h& Z
10
: }, T* e9 N# k( C+ M与date_range一样,时间差序列也可以用timedelta_range来生成,它们两者具有一致的参数:
4 u/ H- i% U; A' T. ~pd.timedelta_range('0s', '1000s', freq='6min')
2 B8 d. A6 w+ `$ cOut[62]: TimedeltaIndex(['0 days 00:00:00', '0 days 00:06:00', '0 days 00:12:00'], dtype='timedelta64[ns]', freq='6T')
7 D( z( o: A; u3 I+ R' H' K1 p0 Q+ a" T3 z3 ~+ o- P5 j+ f' q
pd.timedelta_range('0s', '1000s', periods=3)# _# G6 k+ [3 [& q3 |
Out[63]: TimedeltaIndex(['0 days 00:00:00', '0 days 00:08:20', '0 days 00:16:40'], dtype='timedelta64[ns]', freq=None)# M5 ~5 ^' A. l% V$ W. n/ G' a
1
8 m$ c0 j9 u q M' r2
2 i! r1 E! S- ^& D3
5 Q, X3 B0 c- w8 P9 p4
" E5 h( _' |6 q5 T55 S; Q( R& c- U
对于Timedelta序列,同样也定义了dt对象,上面主要定义了的属性包括days, seconds, mircroseconds(毫秒), nanoseconds(纳秒),它们分别返回了对应的时间差特征。需要注意的是,这里的seconds不是指单纯的秒,而是对天数取余后剩余的秒数:' l+ k# I2 L% n, m/ T6 z
s.dt.seconds.head()* o9 y2 f$ o, W4 S
Out[64]:
6 `% h! T5 q. n3 O8 h O0 274$ A; }/ ]0 u. c
1 260, ` \' e- }5 e% k; D
2 3228 c% f% W$ `9 a0 s
3 2486 m1 Y6 L8 M6 |+ P
4 322% ?% X' [# D% q* w
Name: Time_Record, dtype: int64
$ F) Q' A& g4 l3 r2 L9 c- B3 _18 D4 t+ K2 ?7 ^/ y7 u" @. c: |
2
: h6 t8 ^; @, u9 j! Q3 e3
& m _1 y* ?+ x" l1 I. `4
! m: I+ Q3 W" R0 N' R( G5
+ E: @/ p) O n- C' B2 T" Z+ ?6
x# ^2 q8 H* e7
( k* }# p3 z9 Z n/ z, E8 Q5 [! ]: E, ~3 q7 e& w) Q0 Y* w9 W
如果不想对天数取余而直接对应秒数,可以使用total_seconds
# i4 S& l- k; z4 _ c$ t1 `$ A( e3 L U* ~: `
s.dt.total_seconds().head()
( d3 P; W$ c! Z1 pOut[65]:
% r/ F8 T' W0 v* a3 N M2 f7 L0 274.0
4 e- }4 C, F3 y- O3 V0 a1 260.0
. N, [$ z J0 _+ ~+ h2 322.0
7 d2 m8 ]/ Y `- [3 248.0
/ [ O' Q6 G0 g" p4 322.0& c' Q& i$ A+ Q; c' P
Name: Time_Record, dtype: float64
2 c+ B1 p" G& C1 d% w Y! d; l1
2 W+ ~: V( u: C) E2
: H% n! J1 m9 Q" L8 I. @0 J3
; o4 {3 q: ?: O }* r4
' B7 j- t7 k# @# y. T6 ]5; ~" u: k/ Q D3 G; @/ l; h7 B
62 P7 v; x: G1 k6 L7 m
7
0 L2 C* K: v9 y K c& q8
: j1 n! W$ v! J与时间戳序列类似,取整函数也是可以在dt对象上使用的:+ I* p/ A" e* N
" f' g! [2 `) M5 p1 Apd.to_timedelta(df.Time_Record).dt.round('min').head()
/ } h, Y$ y7 O/ J% o4 z/ w9 h: SOut[66]:
G$ ?$ o' S- _; C0 0 days 00:05:00
8 n8 K ]$ [' f+ G, z- r1 0 days 00:04:00 \" n+ }4 ^9 e
2 0 days 00:05:00% J' c* E6 P9 R, L+ D/ W2 p3 S
3 0 days 00:04:00' }9 k3 I- K' x Q8 [
4 0 days 00:05:000 v0 G; ?3 P6 Q" Y: N; R
Name: Time_Record, dtype: timedelta64[ns]
1 f! j8 H m' K+ D) c( ^1
. ?8 ?% \3 O( c6 h3 s8 t: F2
6 I6 Q4 ^' G6 c: T# m( ?8 o1 \35 r9 T0 W6 H8 H8 \; T, A; w
4+ h* b( E/ T. R; o9 N4 @6 @- D# }% s
5
: t: I; Z0 X0 }) ?' `0 P* {1 n6/ I5 ^4 h( r, d7 }
73 X% v# C. e7 A; G5 k; v4 c1 @
8
. v- B% ] Y; p10.2.2 Timedelta的运算; X7 U6 m& i3 |- k0 t
单个时间差的常用运算,有三类:与标量的乘法运算、与时间戳的加减法运算、与时间差的加减法与除法运算:
7 O' C; C+ G$ j" i' B* r9 @td1 = pd.Timedelta(days=1)
7 Q5 q. K" ?7 E k/ Jtd2 = pd.Timedelta(days=3)
7 {0 v6 O7 S* K2 Z |ts = pd.Timestamp('20200101')
* C! o, j/ G; T/ s* n6 x
' b7 V( l, C# ~4 Y4 ^& _# W7 Atd1 * 29 K! W# }! N1 m$ L
Out[70]: Timedelta('2 days 00:00:00')' x' l \1 M% { C3 O4 l
: L5 Y% _7 a2 G, H- t$ l9 t4 E
td2 - td1- K6 [& E- c$ q: Q" l. S0 ^
Out[71]: Timedelta('2 days 00:00:00'). e1 i L) z1 q. s+ s2 k
& Q! o4 W1 M& T8 D
ts + td1
5 ^) c; T3 z- M3 VOut[72]: Timestamp('2020-01-02 00:00:00')
" ~7 A# J; a: r! Y9 w* Q$ L& L0 g/ w* I8 K9 Z
ts - td17 P% E0 m( V3 e$ J
Out[73]: Timestamp('2019-12-31 00:00:00')
6 W# u! D: f" d6 B, U: B1
( e4 X2 Q% h( K2 a" `! w% r2: x* T+ z9 U4 { G$ x1 L
3
6 h0 `8 A; \+ a5 a# a: I4/ O2 u# W6 R0 }$ N$ v% a1 m
52 t( k/ a( m k% i* P1 N" ^; }' a
6
! M1 J3 A( O @7# }0 Y- W2 V0 K' F, i
8
. _. g. Q7 V6 g. |9
( p5 L$ [; Z- E# _, F10) V/ {1 z6 D: J0 O
11
- Y1 K4 L1 R4 R' Y, W12
/ |; Q7 @7 B$ `# j# l13( H; I( T: P6 @
14
- ~ u9 c/ V) x/ {- P$ q D) q15
# o4 M* T8 u% b: J4 e/ q& `! p* d时间差的序列的运算,和上面方法相同:9 C1 S: a" Y/ G0 M& h5 x" [- O$ x
td1 = pd.timedelta_range(start='1 days', periods=5)
" ~! W5 V/ p, M, M3 X& y7 `4 H2 Ptd2 = pd.timedelta_range(start='12 hours',* X! Z5 u& i5 V/ ^9 p
freq='2H',% N- Q( c/ Q2 S3 T; c
periods=5)
6 g( y" ^- u. k+ Ots = pd.date_range('20200101', '20200105')
+ Z- F$ N7 I& R" W& g' \2 ptd1,td2,ts! C2 k' o4 I" d; {
7 U1 q; `0 @, W( z; H
TimedeltaIndex(['1 days', '2 days', '3 days', '4 days', '5 days'], dtype='timedelta64[ns]', freq='D')2 n* Q. J) i8 n( w: t' V' s* T3 y( l0 B
TimedeltaIndex(['0 days 12:00:00', '0 days 14:00:00', '0 days 16:00:00',* _" {7 @8 ?( n9 X$ {
'0 days 18:00:00', '0 days 20:00:00'], dtype='timedelta64[ns]', freq='2H'), `) k$ H1 l9 @1 O, t' J0 ?
DatetimeIndex(['2020-01-01', '2020-01-02', '2020-01-03', '2020-01-04',4 j( ~, H- u/ F7 t/ E5 i/ T# c
'2020-01-05'],3 M/ j( ]6 U$ U6 j0 l
dtype='datetime64[ns]', freq='D')( ]& f7 a6 @* M7 B& c
1
* N5 W: m( Y# ]$ N4 D2. K6 V1 t# u5 \3 U$ q0 p
3
* e' i) o7 k. G1 Z5 h4 y6 Z9 ~4
0 Y5 |! t3 `1 \9 j! w1 K5
" ^* T2 t" C+ b6
$ T& @9 v) w& _; h% q" z4 B% ?6 x7: {: G& d6 k1 ]! U, P1 I
8
7 g' V ?( O9 K7 I9
7 P' T+ S. N! I" F) m+ e5 ~104 \2 J( h f7 N. K: J5 ^
114 P" m/ M3 {1 C( q! h/ Z% u# I
12
4 n: a+ t) E2 a- K13
& A/ ?4 c. W M- T" \0 L; f S( Q7 btd1 * 5- _' e2 a6 W+ ~* b3 H
Out[77]: TimedeltaIndex(['5 days', '10 days', '15 days', '20 days', '25 days'], dtype='timedelta64[ns]', freq='5D')
! R( R9 ]2 ~) T: p- F; c' G1 X6 b! O9 f% T% X' l* C7 D
td1 * pd.Series(list(range(5))) # 逐个相乘
, }3 l0 m9 H1 u3 G# }Out[78]:
/ {3 t+ L0 n8 X' a% Q0 0 days& U0 C' ^2 v' N4 r
1 2 days3 v: e# o$ I$ h0 l! h# V
2 6 days
. l& y6 J1 b( ?5 [& t3 12 days& l) d; @+ s& p5 F) k6 a1 Q: A; d
4 20 days
4 R: j3 m, R$ M; u4 y5 c5 n/ p; odtype: timedelta64[ns]2 G9 }5 C* s7 y/ c& I
$ ?# D* ]; }5 I: w0 mtd1 - td25 W: } U$ c1 x# r" c# @
Out[79]:
- w, ?* g- R! g$ z- iTimedeltaIndex(['0 days 12:00:00', '1 days 10:00:00', '2 days 08:00:00',/ g' ^8 d+ V4 U. B0 w: a. ?
'3 days 06:00:00', '4 days 04:00:00'],
6 }6 }( ]$ D9 \# _ dtype='timedelta64[ns]', freq=None)
" @5 b4 ~5 b1 _6 U5 H. I
( r$ x, S! o. K6 {* }2 Jtd1 + pd.Timestamp('20200101')1 V3 t- E, ^+ P8 `% w& z" f% V
Out[80]: 4 ^8 E( t( ~! `! u
DatetimeIndex(['2020-01-02', '2020-01-03', '2020-01-04', '2020-01-05',
" z' `/ b1 U* T+ g" r7 I '2020-01-06'],dtype='datetime64[ns]', freq='D')# a7 z" ^/ _8 o# }4 r9 Z% R
) w7 Q) e N: h4 z0 }( J/ z) Z; ~! P
td1 + ts # 逐个相加
@! x- _- s, X- |Out[81]:
$ T; Q0 _. {3 t9 D4 f- t o$ oDatetimeIndex(['2020-01-02', '2020-01-04', '2020-01-06', '2020-01-08',/ {7 j# P" W, G6 U! f: y
'2020-01-10'],
2 s6 P% n# @+ m& u5 P1 ^! J dtype='datetime64[ns]', freq=None)
" v5 R; e- H1 z) z. g- m0 k& ^) ~: K" Q- H9 l
1
8 g# i' v! P( M( @* ?! b! @# S, G% t/ P2
" h: n& `' k- ]7 M0 r; B. M# [) _3
; V3 i" J' E: C1 {3 M8 v4' j8 E3 `7 x% O6 J4 ]
5
9 k5 y' t3 j7 L/ {0 S2 t6
" S u9 Z' r% ^7
. @1 b+ W' l. N$ m1 K$ K; {8% r( _# w {7 m
9
! O( `) Q# `9 l. }- D10
% x0 x' G; Z, H, Y0 y% s/ v% N113 a+ m9 J7 z1 N) i/ c
122 e) Q) I# f6 I% B$ k
13/ l- o7 U0 u- M
14
; Y* f! |5 _" _4 P15
; g: @5 Z$ w: H; p) _) L16' ]" X$ k9 Y% s+ u8 L
17
2 Y" K0 t5 R4 p2 D185 l5 q9 l: L2 u+ v8 S. J
19* R: l% c3 v# e7 W
208 o% u: ^8 H$ m5 \; f, [, x8 e
210 t" q0 e; R5 u) s( D7 v+ `9 O- K
229 f6 V1 ~0 X( H$ a: }5 @
23
" e( S5 a+ e. [8 i& b24
# c* l, e' D% A' X3 b% @5 a) X9 o25
7 B, c3 v! l2 K z5 _, M3 c) r26
' n. c9 t0 I# ?' H/ @27
9 c; v" r, R/ a6 R8 U0 M# b28
. L/ I% t3 H" y6 q10.4 日期偏置
# ] R4 I$ ~ U7 n" S10.4.1 Offset对象" @ Y* H f t F( b
日期偏置是一种和日历相关的特殊时间差,例如回到第一节中的两个问题:如何求2020年9月第一个周一的日期,以及如何求2020年9月7日后的第30个工作日是哪一天。* n+ w4 ~; A, _- w3 N0 y9 ?8 {0 p: O
8 D, n0 C: B. I/ Y' X( h/ s# ]DateOffset 类有10个属性,假设s=pd.offsets.WeekOfMonth(week=0,weekday=0),则: X' {) g+ l: y
" v3 o0 V% ?$ v
s.base:<WeekOfMonth: week=0, weekday=0>,返回 n=1 且所有其他属性一样的副本
5 `9 f# M4 T8 D; a9 Ws.kwds:{‘week’: 0, ‘weekday’: 0}
/ H+ J/ ^: b* j( Qs.wek/s.weekday:顾名思义' j* i" |% f5 U( J. |! y! h- s& r
有14个方法,包括:
0 {( Q1 ~, @7 r- |% Y9 R7 ?% u4 b
% I4 C7 M; D. \+ w7 s2 H+ w/ kDateOffset.is_month_start、DateOffset.is_month_end、DateOffset.is_quarter_start、DateOffset.is_quarter_end、DateOffset.is_year_start、DateOffset.is_year_end等等。
) u9 r4 G8 l8 M# k# M: Z$ z n7 ]pandas.tseries.offsets.WeekOfMonth(week,weekday):描述每月的日期,例如“每月第二周的星期二”。" W5 u# y8 q' K* w1 A
1 ~$ e) X5 I5 h! D有两个参数:
9 P7 |( m# [( [week:整型,表示一个月的第几周。例如 0 是一个月的第 1 周,1 是第 2 周,以此类推。. e( [# J: p1 d2 Q' q, z6 \
weekday:整型,取值为[0,1,…6],表示周一到周日,默认取值为0(星期一)# }+ Z) w- T% _5 i9 |
pandas.tseries.offsets.BusinessDay(n):相当于pd.offsets.BDay(n),DateOffset 子类,表示可能的 n 个工作日。. J# X6 X; @6 A7 Z2 j) X7 }# t
7 r0 M1 l) \: S* c* V2 W$ {
pd.Timestamp('20200831') + pd.offsets.WeekOfMonth(week=0,weekday=0)5 Z2 M8 {0 f& m0 M% F0 @) |- r2 B1 W' x
Out[82]: Timestamp('2020-09-07 00:00:00')
1 w# D, t$ Z% D, Z/ }7 W
e1 _- F0 D6 }9 m. L+ O) Q% G7 Gpd.Timestamp('20200907') + pd.offsets.BDay(30)
0 G4 |0 d0 r. \- w+ C: {) X. y& C* wOut[83]: Timestamp('2020-10-19 00:00:00')$ n+ J: _# i, Z( A" r' z
1
! U3 k/ a( N, I# j/ X$ p; _) @2 a1 ?- {* L3 m& J
3
) J- R' `, V9 |' J& }4 X3 r, n1 J- t4
7 {' v/ O* ?$ s9 [5- X) G& H7 B% K Y1 u0 G# D
从上面的例子中可以看到,Offset对象在pd.offsets中被定义。当使用+时获取离其最近的下一个日期,当使用-时获取离其最近的上一个日期:
$ e2 K4 J- m+ U; W/ K) H' e l* D M0 ]! }& H# E
pd.Timestamp('20200831') - pd.offsets.WeekOfMonth(week=0,weekday=0)
' }$ ~9 x5 A( J& t% U UOut[84]: Timestamp('2020-08-03 00:00:00')* b" V9 t0 `1 u5 e1 J2 R& N0 M0 C
7 ?+ f. z! o6 h! h! V9 Vpd.Timestamp('20200907') - pd.offsets.BDay(30)
. d4 \" Z: ]! ROut[85]: Timestamp('2020-07-27 00:00:00')1 ~- T) _% f0 A6 [1 E' f0 h
+ z2 x0 Z; f) ]; wpd.Timestamp('20200907') + pd.offsets.MonthEnd()
# [# r" |0 U9 _4 T6 fOut[86]: Timestamp('2020-09-30 00:00:00')
5 u7 s L; Y4 `: M& |0 {1% \% H$ J$ L$ L7 o* P! G6 {* c
2$ f4 D, d; z1 c# H: u7 f# g/ O6 n
3
) D# |) x% D0 {& ^/ g0 O5 p3 D4
# o8 G3 a; Y+ _: Q# |5
% F+ `* j$ ?8 L0 B8 i# c% f) o6
) ~& ~# ]8 A% Y* n$ s: u7
# j8 T- d b9 \# _' y8
5 o7 ^6 }$ n# a i( [( g4 z 常用的日期偏置如下可以查阅这里的DateOffset 文档描述。在文档罗列的Offset中,需要介绍一个特殊的Offset对象CDay。CDay 或 CustomBusinessDay 类提供了一个参数化的 BusinessDay 类,可用于创建自定义的工作日日历,该日历说明当地假期和当地周末惯例。
: H7 X( e8 P( i5 U! T 其中的holidays, weekmask参数能够分别对自定义的日期和星期进行过滤,前者传入了需要过滤的日期列表,后者传入的是三个字母的星期缩写构成的星期字符串,其作用是只保留字符串中出现的星期:
( D f4 h/ o8 i! L, X- y! Y9 |2 d/ t7 c) Z1 v
my_filter = pd.offsets.CDay(n=1,weekmask='Wed Fri',holidays=['20200109'])
* q/ V x3 ?4 Cdr = pd.date_range('20200108', '20200111')
$ i \. [- E- e- k% X( P4 s
/ p8 g/ d; q7 ]dr.to_series().dt.dayofweek
4 L& H; `2 V. K% ~% V# s8 kOut[89]: 3 J; K6 u. Z6 G3 G7 \
2020-01-08 2
3 a, o, a" X( p+ W2 }7 R5 A2020-01-09 3
$ U4 T7 n: I( F; N/ D; J& Q2020-01-10 4, ~6 m V# n1 W! K
2020-01-11 5
& ~, M( ^/ R/ bFreq: D, dtype: int64 @4 r( X. `' d4 D& i3 q6 G
; n( u7 z9 a. Z1 G8 ]& p[i + my_filter for i in dr]
8 U$ `/ N0 u/ a3 Y* D( U/ u1 hOut[90]: / L- Z, d+ I! D# Q2 g+ F3 e0 F; K0 I s
[Timestamp('2020-01-10 00:00:00'),
+ a. m Q3 i0 c/ A Timestamp('2020-01-10 00:00:00'),
; P8 F* |2 R& p) T N/ O Timestamp('2020-01-15 00:00:00'),
) a! X' l/ g; V {( Y) V$ m1 H; s Timestamp('2020-01-15 00:00:00')]
1 ?- D- c% M' V# X6 f
# V3 l+ q) I& ^6 F0 _5 b& u m13 T3 }% J+ j- ]6 }+ w) j* K* F
22 ~* C0 O. S1 F( L; r6 j
3 ] }0 m5 T" B8 G0 w; T/ _' S# V
4
/ N9 Y" z6 Y+ M- I; B) `9 Q5" z" F" [/ n( C/ Y( e: |- ~+ p, Y
68 v8 w6 Q; l; m- z9 R, O7 X) b& G
7
% Q) a( K6 b: \# T+ Z% M5 y8" W8 ^2 F8 [9 u' S7 T/ e+ Q
9
3 @7 I/ C3 e+ ]10$ F! ]. e. L% U# f: o* o: A5 l
11
5 l: |# c m$ e% a12
/ `) d9 k$ R/ D# X+ h W1 c% w13
& Z3 Q6 j* P! k2 _9 I2 W# M; z14% B! K# E3 F& n0 N& f+ V
154 `$ Q5 j" H" Y# E) V. C
16! [5 V, C: p. W9 A% d: d
17; @" d1 Z+ ]. n& W" \/ K
上面的例子中,n表示增加一天CDay,dr中的第一天为20200108,但由于下一天20200109被排除了,并且20200110是合法的周五,因此转为20200110,其他后面的日期处理类似。
$ c3 N' W) I! v+ m& E
; `2 l" K: Y5 C【CAUTION】不要使用部分Offset
& F) d+ P+ _! Q4 b& @" |在当前版本下由于一些 bug ,不要使用 Day 级别以下的 Offset 对象,比如 Hour, Second 等,请使用对应的 Timedelta 对象来代替。
4 [* V4 w. L2 J4 v
* ]& W% ?0 s1 b- r: z2 S10.4.2 偏置字符串
/ \& O: Z, D9 |5 J. T6 v 前面提到了关于date_range的freq取值可用Offset对象,同时在pandas中几乎每一个Offset对象绑定了日期偏置字符串(frequencies strings/offset aliases),可以指定Offset对应的字符串来替代使用。下面举一些常见的例子。! F1 g+ Z+ a5 |, E/ q
7 [- c2 D8 d0 ^$ [2 \
Offset aliases:pd.date_range函数中的freq参数,为常见时间序列频率提供了许多字符串别名。 也称为偏移别名Offset aliases。偏移别名列表点此参看(大概27个)。
2 a4 E0 g. G0 H% `6 l4 K/ U+ ~( Q1 T- ~& T" G, a: E! y
pd.date_range('20200101','20200331', freq='MS') # 月初
/ @ V3 z9 g* DOut[91]: DatetimeIndex(['2020-01-01', '2020-02-01', '2020-03-01'], dtype='datetime64[ns]', freq='MS')
1 K# j% ?" e) f- D# j5 n
! ]/ K# O! w; s9 d7 }8 Bpd.date_range('20200101','20200331', freq='M') # 月末: m, \. g7 C7 Y/ n" I$ I
Out[92]: DatetimeIndex(['2020-01-31', '2020-02-29', '2020-03-31'], dtype='datetime64[ns]', freq='M')
% Q& e0 [% E1 ~! b, X& d. [/ q1 T0 {$ Q. [; \1 w, T1 [) n0 i
pd.date_range('20200101','20200110', freq='B') # 工作日
+ I9 Q% i4 j) C& ~( g% ~& KOut[93]:
+ m" F8 N/ D! n/ T4 P# Z3 J% @, BDatetimeIndex(['2020-01-01', '2020-01-02', '2020-01-03', '2020-01-06',4 j- w# A( m9 `
'2020-01-07', '2020-01-08', '2020-01-09', '2020-01-10'],
Z! C5 E) s( ?1 M# g2 V2 o& S dtype='datetime64[ns]', freq='B')+ b; j7 M! i% I1 y
& @7 {5 k: j4 j& r
pd.date_range('20200101','20200201', freq='W-MON') # 周一+ J) o' b, W8 h1 b9 B- Y5 B
Out[94]: DatetimeIndex(['2020-01-06', '2020-01-13', '2020-01-20', '2020-01-27'], dtype='datetime64[ns]', freq='W-MON')
6 k( t- [2 B( j$ W0 S. R! Z4 I: O% M# s2 y, E k% M/ F
pd.date_range('20200101','20200201',( R w. d8 }' m/ X7 c9 S
freq='WOM-1MON') # 每月第一个周一
1 ^+ I' J5 g9 M8 z4 A+ e
9 f" @$ h# L- M6 H, HOut[95]: DatetimeIndex(['2020-01-06'], dtype='datetime64[ns]', freq='WOM-1MON')
( _* P2 T: N6 B5 {) e+ Y* W+ g y7 e0 N
1/ o5 ~5 d/ G1 e* t4 ]$ ]9 z6 N: K
2* g2 V# [( E) D4 s
34 C- |# U& L1 S4 y( s& | ~& b
43 m. h- U0 ^/ G- p+ t
54 M4 B( S0 g7 T+ m
6( y Y5 r4 D: S4 k& l
7$ Q) [, R% j; E; N: W! p3 X
8
% O3 x/ ^; j. {% @8 m- O9
& _' y, Y- p# |' O% }& h3 b1 ]5 V$ l10) p0 j' E( Y6 Z. w( `
11
- k' e* N9 {9 M& C5 L5 x120 E7 T3 o; ]8 }9 ]& Z X$ s" E
131 Z$ Q( Z& P8 N" f. w9 i
14
9 T" Q- ?! C7 T$ [; ]15
3 |3 S. j% F+ X0 O$ ]* z6 Y16. a9 x, Z9 L5 i5 m* k9 G
17
u" p, b k) r2 Q: m4 B) u' a18
) _1 N1 D9 m, _. R19) i8 K' S$ Y3 x/ `$ }4 P# l
上面的这些字符串,等价于使用如下的 Offset 对象:
! u7 Y1 D8 ~6 T. j
6 |( f G3 _% r) S0 O* s2 Ypd.date_range('20200101','20200331',7 w% d6 {1 J1 U& ~
freq=pd.offsets.MonthBegin())
+ m+ K$ i8 R& {/ R
, u1 N9 n j% [ y( [/ y* ~$ xOut[96]: DatetimeIndex(['2020-01-01', '2020-02-01', '2020-03-01'], dtype='datetime64[ns]', freq='MS'). {* q$ I# ?. X9 p' g" `/ _
3 E7 q- ?) C2 @9 R9 v1 h1 ypd.date_range('20200101','20200331',+ X" s5 G) \3 g) F
freq=pd.offsets.MonthEnd())& a/ d6 z+ V8 W5 ]% v
) X9 B, x( [3 B! a' s, KOut[97]: DatetimeIndex(['2020-01-31', '2020-02-29', '2020-03-31'], dtype='datetime64[ns]', freq='M')/ m$ ~& U# w9 y
, H3 ?( U2 N( k
pd.date_range('20200101','20200110', freq=pd.offsets.BDay())
- F. A: w& F$ AOut[98]:
; b5 D; k" \+ h* X! U2 }1 W. x) x* W+ iDatetimeIndex(['2020-01-01', '2020-01-02', '2020-01-03', '2020-01-06',
# A/ k0 r+ t1 ~6 T' n '2020-01-07', '2020-01-08', '2020-01-09', '2020-01-10']," H. c5 h& r0 ~3 L% M( o% I! X
dtype='datetime64[ns]', freq='B')
: X4 A0 @! Y+ U2 V; E$ D. C3 z0 N9 e
. o2 J+ t; \- \0 g& d; F" wpd.date_range('20200101','20200201',
9 S3 T, g2 ], H freq=pd.offsets.CDay(weekmask='Mon'))
" l) ^1 \: b+ Z, f: u0 Z, J/ `$ d; c! r
Out[99]: DatetimeIndex(['2020-01-06', '2020-01-13', '2020-01-20', '2020-01-27'], dtype='datetime64[ns]', freq='C')
6 X( i9 D1 z) E. b5 J$ W: G- m6 d' W: x1 w: I7 P9 C
pd.date_range('20200101','20200201',
! y" G# e+ i8 W5 o5 a* g2 Z freq=pd.offsets.WeekOfMonth(week=0,weekday=0))
5 W1 l, P1 Z. Z& l; z# |4 }( Z) q& w0 D* Q
Out[100]: DatetimeIndex(['2020-01-06'], dtype='datetime64[ns]', freq='WOM-1MON')
# w1 `) R0 E5 W" G {5 ~
4 Z8 U9 z8 W0 H& j18 s2 f: t0 S. v% e% G
24 S _& l. \( ?( L
3
~' V9 c% S% t8 [% u0 K/ v4
% J/ ?3 o5 F6 O/ B1 Y- H; |5, N7 {1 e% L1 [/ J4 `
6
4 d) ?6 N% n* d72 q# _' o, o1 @; D; y
8" t8 B6 l! e/ Z6 H2 ]/ ~
9
3 C3 |+ D/ ]( @4 n: C2 F$ W10* ~* s' h- d) @. X
11
2 G) f6 O7 _1 \; B1 R8 t. e12- U- i! Q& R8 q3 z0 I" A" h
137 T; E9 s2 E; Q) [' t
14
' m1 D5 }- r6 U/ V15% T8 ]) Z# U1 T/ _) s
160 `: S( G/ s% T! H, h0 k/ A* @
175 m" o6 X& Z% B+ k7 `% w
18
! v8 h# m0 S' I19
+ W+ t% n; ~4 d; x' ^5 |20
, [2 N9 R( P: a W( l; H+ i218 ]/ |# d C4 r6 M Q
22& _8 v, Z" y+ w' E
23
5 T; f. K4 w. c246 H3 O h% \( H! p1 Z
25; w$ J2 U8 f' E% x
【CAUTION】关于时区问题的说明
# Q, Q7 a; U9 }3 p9 X! m% B) E 各类时间对象的开发,除了使用python内置的datetime模块,pandas还利用了dateutil模块,很大一部分是为了处理时区问题。总所周知,我国是没有夏令时调整时间一说的,但有些国家会有这种做法,导致了相对而言一天里可能会有23/24/25个小时,也就是relativedelta,这使得Offset对象和Timedelta对象有了对同一问题处理产生不同结果的现象,其中的规则也较为复杂,官方文档的写法存在部分描述错误,并且难以对描述做出统一修正,因为牵涉到了Offset相关的很多组件。因此,本教程完全不考虑时区处理,如果对时区处理的时间偏置有兴趣了解讨论,可以联系我或者参见这里的讨论。& X! R, l9 \) v2 {( G9 J
; m/ ^! S0 a0 l$ ~9 U$ {10.5、时序中的滑窗与分组5 @3 P( f! W& B; R# f6 J
10.5.1 滑动窗口
. r& u8 C5 r% _& z" z0 c# C3 `1 V 所谓时序的滑窗函数,即把滑动窗口windows用freq关键词代替,下面给出一个具体的应用案例:在股票市场中有一个指标为BOLL指标,它由中轨线、上轨线、下轨线这三根线构成,具体的计算方法分别是N日均值线、N日均值加两倍N日标准差线、N日均值减两倍N日标准差线。利用rolling对象计算N=30的BOLL指标可以如下写出:: U. C5 G0 ]5 n9 t/ m2 e
- ^: Z2 ]: q, ^4 z% F: H& U4 M$ g
import matplotlib.pyplot as plt4 k9 k5 T s1 Y% W
idx = pd.date_range('20200101', '20201231', freq='B')7 |( K% \" ^/ g( R) g; M
np.random.seed(2020)
5 O5 a4 E, ^* O. \$ v: y% Z
* T/ z6 G& {# D' @data = np.random.randint(-1,2,len(idx)).cumsum() # 随机游动构造模拟序列,cumsum表示累加( Z. F: d$ |' v
s = pd.Series(data,index=idx)
+ O, y0 }& o1 K! P8 Vs.head()
, Y0 s( q: W$ ^- [( ~6 sOut[106]: ; l& w- ]0 D6 W& l: X1 [8 R3 C
2020-01-01 -1+ i& |* J" K8 K
2020-01-02 -2
/ f. j8 \+ q, k2020-01-03 -1
# u+ X" x" E, `' Y9 [8 v, A5 t2020-01-06 -1: `* _: s# F! f7 |. b& ~
2020-01-07 -2
& M/ R) Z/ R1 c& } G ?/ PFreq: B, dtype: int32% D; z+ [7 B* I
r = s.rolling('30D')# rolling可以指定freq或者offset对象6 `# e- s( J9 r; t9 `) d
. H. o3 L& W# V" @+ r$ Lplt.plot(s) # 蓝色线
- l$ }4 I) u8 dOut[108]: [<matplotlib.lines.Line2D at 0x2116d887eb0>]# ]3 \' s% b+ l# C$ R- F# }
plt.title('BOLL LINES')
. K& k/ h6 A: _3 T+ j( p" xOut[109]: Text(0.5, 1.0, 'BOLL LINES')
* ]4 k J! x( j/ ]1 A n& d5 H7 Z F2 P7 ]7 {. @/ U
plt.plot(r.mean()) #橙色线7 A2 T6 t% Z: Y
Out[110]: [<matplotlib.lines.Line2D at 0x2116d8eeb80>]
$ `% R" Z5 A( t8 b) H; h! y* \3 e7 Q! S$ p; e7 f. a* o
plt.plot(r.mean()+r.std()*2) # 绿色线2 X, e1 A; P3 s5 C! V
Out[111]: [<matplotlib.lines.Line2D at 0x2116d87efa0>]" i( d! z+ A& S4 _% ^/ X6 ^
* ]% B: C0 v0 B2 Pplt.plot(r.mean()-r.std()*2) # 红色线
; q5 n3 q# S* ^/ `Out[112]: [<matplotlib.lines.Line2D at 0x2116d90d2e0>]
( F0 X+ d5 d, z* l# }
1 J& b9 F G9 S( |: |12 S% l y, W/ a( s8 J; A* Y
2
! U1 C+ o% A$ V4 J6 x9 X* {3
2 }& A+ k! F( l8 o; k9 y2 M4
" Q9 ]" _; S% ^, G% s( E5- {' T) n0 v6 G$ J$ x' o
6
5 l$ \% W1 ?$ w% J' Z; ?5 b& ]7
* k) l" [1 I& ~8+ }4 d% _1 O- j& o6 R# j
9( d& W1 q4 ^# U
10: Y! p B* @7 ^9 k1 p/ m7 u I
112 n+ c7 P0 r b( y8 o
123 A# Z; ~* }: ^
13
+ N. f" l0 U: t% M* P B, K141 o* ~4 z* ]; Z5 K9 [5 Z' C
15
" M+ p/ j. X u16
9 }% S0 e/ ^& r! ?% |17) j, G% _9 X. A3 z
189 Y) i6 Q3 c6 o% S- R
19
3 t8 I2 Y0 ^' N4 w4 s20
# S# X% x- ]0 s# [$ y9 Y: Q4 g210 s) ?9 C2 c& ?/ A! F# l2 ?
22' H9 z0 ~2 \% @' g% d. `
231 d I* ?0 m! Q0 V9 O: `! E
24& f. o1 |' g, o8 Q& \. `* Q
25( o8 l" s$ n* I1 a( }5 x7 F
26
3 F1 m. {$ |, R- O5 O( h. x# p272 u1 P9 ], L' u3 A; {. V
28
; }; E" J, E& [* }/ N29
# M: q; k# x, k: f+ e$ l, @0 T/ l! W8 {% T a+ @+ M$ g9 G
这里需要注意的是,pandas没有实现非固定采样频率的时间序列滑窗,及此时无法通过传入freq字段来得到滑窗结果。例如统计近7个工作日的交易总额。此时可以通过传入多个函数的组合来实现此功能。1 h, T/ b+ p, } t# y8 M
首先选出所有工作日,接着用普通滑窗进行7日滑窗加和,最后用reindex()恢复索引,对于双休日使用前一个工作日的结果进行填充。
6 I9 E; Y9 x/ h6 a6 ?* ?+ ^! A) t1 O1 h, D H1 O6 B1 K! k8 l _
select_bday=s[~s.index.to_series().dt.dayofweek.isin([5,6])]' @9 m$ }* M0 q7 L
bday_sum=select_bday.rolling(7,min_periods=1).sum()5 h/ U( x9 L8 S1 @3 }; q
result=bday_sum.reindex().ffill()7 y \ ^0 d& `1 S5 E
result
2 ^$ P0 `; V) ?2 w' S
1 I7 @; S) j& q# W2020-01-01 -1.0* b J. |! c4 e
2020-01-02 -3.0# {0 Q; G6 w0 p' b: |5 @5 ?
2020-01-03 -4.00 C. U2 [+ Z8 M
2020-01-06 -5.0# q; L4 r0 {. v0 W
2020-01-07 -7.0) }4 N. d" {) m! ~# M, V
... $ R) I. b8 |2 A0 `1 \) _* x
2020-12-25 136.0
1 }" A [' E& Q2020-12-28 133.0( v7 Y- h! S+ L. U0 w- `
2020-12-29 131.09 O5 q2 a) T. G% w9 U5 i
2020-12-30 130.0
) |$ M$ w) ~7 `0 { o6 D2020-12-31 128.0& R) }& p ~+ E8 _3 V2 m' I
Freq: B, Length: 262, dtype: float64
: u6 `4 @! P# P8 V' y* F9 f% @" z: d4 d% C1 c5 ^& ]
1( r% T. D0 d6 b# k$ Z/ e& i+ |
2
0 A/ |7 S5 ?. A* R6 r/ n3
% V B4 C3 r6 k8 E0 o: g0 S2 U4
* E ^/ G' H" d2 j! f) q5! b8 w5 e6 f" n2 z9 r1 b/ ^8 o
62 ~3 s+ u* Z4 d2 ^& A: m
7) c, ?9 P u+ W" b* _# z, k
8
) s0 k3 O2 W5 { R9
; W9 K1 z) z; Y$ Z8 B$ v103 d# q# ~( K/ I( Z7 x9 x
11
7 i3 L% f9 M: U& h( K12
; k4 h- F" j- [$ V5 p0 I+ u13+ j3 Q+ n5 G0 d* c' M/ |
14
# @2 V% ]8 P, B" \$ Z* h15 X( [( v1 N: U# I
16' J P: l, t3 X5 [6 z4 u7 S
17
( b# ~, f. t( j% J- a- W3 p shift, diff, pct_change 是一组类滑窗函数,它们的公共参数为 periods=n ,默认为1,分别表示取向前第 n 个元素的值、与向前第 n 个元素做差(与 Numpy 中不同,后者表示 n 阶差分)、与向前第 n 个元素相比计算增长率。这里的 n 可以为负,表示反方向的类似操作。
, y" u! p! |- O& r/ n- g
5 y) P9 ^3 e, i2 Y- M 对于shift函数而言,作用在datetime64为索引(不是value)的序列上时,可以指定freq单位进行滑动:
% c; n' a) T/ W% S* m7 K* J+ n* u
s.shift(freq='50D').head()
7 r% i% S+ k$ a( E0 JOut[113]:
% b L4 L3 R0 i+ J7 n2020-02-20 -19 P& q, k; {7 X# a( I0 }/ w+ U; Q
2020-02-21 -2
/ [, r {. \6 X( L& K, r2020-02-22 -1
. ?9 I2 B, p& Z6 q& O6 \" T2020-02-25 -12 k/ L' N) @; d
2020-02-26 -2
+ l; ?: Y' a3 J u9 i( z( Adtype: int32$ W4 ]6 |( ~* T. v' C6 c8 t* U9 E
1
* D8 S/ w8 h* t4 O2
$ N2 L) Y1 v+ R3
2 E$ N/ N! }) \, s, F4
+ x8 s7 T7 d# U. p! u5
( o# A- Y# x4 h; r4 X65 [* z+ k A. L
7
. O: j! w! g2 I8
0 A6 y. T- @! }$ y8 ~9 u s 另外,datetime64[ns]的序列进行diff(前后做差)后就能够得到timedelta64[ns]的序列,这能够使用户方便地观察有序时间序列的间隔:& p# s% V3 e% i+ \
0 |8 u; l7 ^( p
my_series = pd.Series(s.index)
7 R3 S' r! i O5 _my_series.head()3 }4 \* H V; R8 ^
Out[115]: & y2 F- F4 D" S
0 2020-01-01) E9 d4 c, K% r/ M! h- |: I# g
1 2020-01-02
% Q( N, e8 w4 G$ X/ g; e2 2020-01-03$ Y: _9 |5 Q X4 P, I
3 2020-01-06% l0 Z- K' K+ _' P- e
4 2020-01-071 ]3 q/ ]) {4 O D9 }- L5 s
dtype: datetime64[ns]+ @- T( y P8 u: x# h7 m! u
% m9 A* ]4 u( ^. \% ?% R% O# }9 E
my_series.diff(1).head()
3 T$ E! ~7 c6 D/ ]' Z2 x1 aOut[116]:
z9 W2 R) q! g0 NaT5 w: u, v7 f) y! f; ?- L2 K' v
1 1 days% U+ D/ [/ o5 Q3 O
2 1 days5 }7 l9 E, Q0 ~/ M, z
3 3 days9 ?$ w' j( d3 @! u7 f% K( C$ q
4 1 days* w2 H/ A Z3 V4 n% `0 q0 ~
dtype: timedelta64[ns]( {8 t: l/ R7 U
+ {% g$ l5 @8 N; R$ A5 l1
5 H8 [9 L% d# i: v2
! ]; @% r) `" q2 M" O3 a! V3
2 Z2 r* S% Y5 o6 }4
; Z K3 L8 c! ^8 J5 |9 [3 D/ c* ~" J5
; J6 ^# \& n9 e& S" S% T$ e; k& e6
" i% g5 _9 e6 A* A& k/ `' s+ U7- }1 _0 E) S; k: {5 a
86 Q* |9 ~. N b, N& l
9
( Y# v( N9 G4 j3 |& X0 k; p101 k0 k6 B4 j" e' c: E, g, h
11! y4 ?8 A9 A7 \4 b
127 R9 t+ N# }- m( _8 M, H3 p
13
$ s( y0 ^1 H0 z2 V14
# l: ^4 E! g7 r6 A; ?152 ^( L/ \5 _" e- p) i" {# u* ?
16
# Q; _6 {9 r u4 h( ?. B0 s- y172 k' p# e+ E, v: W
18
0 A4 m" A: i% Q t% @10.5.2 重采样# ~1 g: ?5 D" f1 y, f' }% V. S
DataFrame.resample(rule, axis=0, closed=None, label=None, convention=‘start’, kind=None, loffset=None, base=None, on=None, level=None, origin=‘start_day’, offset=None)
/ g# p# @% k9 `% e常用参数有:" x% u9 l6 t7 G. O$ @- o. U
" P, x& T% ?3 c+ \. b3 ]" U
rule:DateOffset, Timedelta or str类型。表示偏移量字符串或对象. Y; f+ e$ P. Y! ^1 g J% g
axis:{0 or ‘index’, 1 or ‘columns’}, default 0。使用哪个轴进行上采样或下采样6 A$ y# W/ l9 E" a3 s/ U
closed:{‘right’, ‘left’},默认None。表示bin 区间的哪一侧是闭合的。所有offsets的默认值为“left”,但“M”、“A”、“Q”、“BM”、“BA”、“BQ”和“W”均默认为“right”。
6 ~4 a e" \9 K* D e" jlabel:{‘right’, ‘left’}, 默认 None。hich bin edge label to label bucket with,所有offsets的默认值为“left”,但“M”、“A”、“Q”、“BM”、“BA”、“BQ”和“W”均默认为“right”。
+ m: n% V& f% e2 K6 {convention{:‘start’, ‘end’, ‘s’, ‘e’}, default ‘start’。仅针对 PeriodIndex,控制是使用rule的开始还是结尾。/ Z( {! A5 r" ^
on:字符串类型,可选。对于 DataFrame,使用列而不是索引进行重采样。列必须类似于日期时间。& P8 k% K2 B$ M
level:str 或 int,可选表示多重索引MultiIndex的级别,这个级别的索引必须类似于日期时间。) R0 O. c( t2 U' @3 G+ Q
origin参数有5种取值:
+ Y9 n5 z9 M6 B6 N( F‘epoch’:从 1970-01-01开始算起% w+ y6 `: j, B1 B4 _
‘start’:原点是时间序列的第一个值8 G( Q3 j; z, g! x% s) B
‘start_day’:默认值,表示原点是时间序列第一天的午夜。* g# z0 y: W* Y: X4 [' G. P
'end':原点是时间序列的最后一个值(1.3.0版本才有), ?- w+ ~: h6 W% j1 C+ V
‘end_day’:原点是序列最后一天的午夜(1.3.0版本才有)9 M$ d+ x' v/ U6 p b
offset:Timedelta 或 str,默认为 None,表示对时间原点的偏移量,很有用。3 C& V6 e7 Y/ S, Y' d. j ]
closed和计算有关,label和显示有关,closed才有开闭。
( m* N' A/ ~; K/ G, @ label指这个区间值算出来了,索引放区间的左端点还是右端点,closed是指算的时候左端点或右端点是不是包含。
; \3 U- }$ x. G8 s G! W4 ?/ O r+ k) n# e5 D
重采样对象resample和第四章中分组对象groupby的用法类似,resample是针对时间序列的分组计算而设计的分组对象。例如,对上面的序列计算每10天的均值:8 m/ r4 {$ A# ?+ x+ d
s.resample('10D').mean().head()
4 `$ K/ [' ]& ~5 C7 \7 AOut[117]: % F! F2 }# j- Q# \1 K Y
2020-01-01 -2.000000
; s8 r0 |3 c U+ W2020-01-11 -3.1666676 D1 @! u' _ `6 N/ ~& U& L
2020-01-21 -3.6250009 q& w4 i7 K/ f! i8 o. K9 I
2020-01-31 -4.000000+ ]+ C5 L% _" `* x+ X* [
2020-02-10 -0.375000, l+ @% i% K0 Q5 O& C
Freq: 10D, dtype: float64, D* `& K, ]/ J) \
1
% X( h; _9 D# ]. B2
* I+ s9 m, v% [: c5 G% K3& X# K8 @5 p6 o0 I
4: B4 y8 C* h4 x
59 Z3 `6 C5 s! H$ {4 r7 _
6
$ y A6 L. o& Z9 k& u; b2 m75 ?$ X! r, L7 H3 Z+ s. N2 |6 H3 L
8: O5 t- C; J( D/ h q2 A1 |/ H/ f
可以通过apply方法自定义处理函数:' T/ @; m, R) {7 |/ ~% D
s.resample('10D').apply(lambda x:x.max()-x.min()).head() # 极差' D" E9 O* H0 @. o. P, R
9 b3 B9 `' c" T. G
Out[118]:
V9 I4 o* v' ]. |# t2 w2020-01-01 39 R: s9 R* U3 b! j7 Q
2020-01-11 4$ L% y6 z9 I0 T. x$ B* |
2020-01-21 4+ M* S, G& y" \9 N8 L
2020-01-31 23 a9 a( q9 C9 g/ N; X9 f8 I& M: e
2020-02-10 4% C+ n8 ~0 v; G$ S* V, L
Freq: 10D, dtype: int327 e+ ~; @+ G3 u3 R5 M2 Q' j5 h
10 Q1 ~' m, R' z* n' u7 `: u6 {
2
$ e. c# V* `# [3 O. v) p6 l35 _7 M- `# q! Y4 T( N
4
" {$ m4 U% B& ]$ s55 |, a* K0 y9 W D, _
6& |. _' f8 I8 E" S0 C1 Q
7
; U o, t5 P/ A5 `! K81 S* H. f# @& D! g! T6 \: t
9( c% R( J N8 e& F. r3 A+ k- g9 l
在resample中要特别注意组边界值的处理情况,默认情况下起始值的计算方法是从最小值时间戳对应日期的午夜00:00:00开始增加freq,直到不超过该最小时间戳的最大时间戳,由此对应的时间戳为起始值,然后每次累加freq参数作为分割结点进行分组,区间情况为左闭右开。下面构造一个不均匀的例子:8 r* a- G) e# t' j% }' t
; I, F5 F$ u, d/ y2 V
idx = pd.date_range('20200101 8:26:35', '20200101 9:31:58', freq='77s')
6 y1 a8 Q0 d" a5 Sdata = np.random.randint(-1,2,len(idx)).cumsum()/ p1 \6 U# E* N0 v1 P" y
s = pd.Series(data,index=idx)
" n$ w; U: `9 ls.head()
. f( d) S' k+ t6 `/ @8 {8 U9 ~$ t K1 a a5 \7 D+ { x! y8 L3 m
Out[122]: / r* M( r9 c% b* C3 m
2020-01-01 08:26:35 -1
) l6 q; i7 [0 y/ L! i' f7 o0 S8 W2020-01-01 08:27:52 -13 @5 g" y" i$ ?* ^3 `" J Z
2020-01-01 08:29:09 -2
% B0 Z- O {' F9 f& x6 N) Y/ z" Q2020-01-01 08:30:26 -37 M# @- ? T1 V" _: w& n
2020-01-01 08:31:43 -4
- J9 w& N/ `2 Q# c+ ~5 n* dFreq: 77S, dtype: int322 C! i; [! }6 f3 Y
1
* G! P+ ^ W' e1 H; ?* ]' k/ Z: c2
$ {9 _2 T# u, a2 ?( f34 ]* _0 {7 s5 V
4
9 o' O. w/ j4 a" X. c56 d# x7 t# ]! P; _) i
6
( c, T; s8 G% k/ H* r7
) T4 D. F& P l' R9 m! }7 f8+ J! }/ c* I, K+ h
9
0 q6 [ ^9 F+ \. j$ @10! [. A- w) Z, h5 F8 J: Y
119 z8 o! T3 y4 F9 s' \2 H
125 E" I7 \2 c5 e8 W
下面对应的第一个组起始值为08:24:00,其是从当天0点增加72个freq=7 min得到的,如果再增加一个freq则超出了序列的最小时间戳08:26:35:
( n( ]4 B; U: R* V7 \9 k/ l9 G7 i+ l* e7 W7 Z, O& N5 U9 o
s.resample('7min').mean().head()) a/ c+ n" Z8 k4 L! I. E
Out[123]: $ _$ R$ t* v: R* e5 y8 G
2020-01-01 08:24:00 -1.750000 # 起始值,终点值包含最后一个值
% E, t; w7 o* B) z0 W- e C2020-01-01 08:31:00 -2.6000008 @- @4 h% k- C8 I
2020-01-01 08:38:00 -2.166667
; G4 |% V. h l, i0 Y7 P2020-01-01 08:45:00 0.2000002 Q: }0 h/ A/ P, F" l, p5 D; t! X& d
2020-01-01 08:52:00 2.833333. R; L# h7 U5 V: m& r3 _
Freq: 7T, dtype: float64
. A8 S! s0 h! \: {+ F1
& a; @$ g6 `: P% E" ]$ ~/ l2) b% V5 O+ O. [
3
/ |. h W, S _ Y/ r, w4
5 G- c& z8 s4 o$ C3 F1 u5- q: W4 L% H5 j6 ?2 w, ^
6
. K- w1 h) n! ~3 e7* C5 b6 ]* v7 w0 r5 f) W! v
8
* w: p$ w$ j* O- J. ~ 有时候,用户希望从序列的最小时间戳开始依次增加freq进行分组,此时可以指定origin参数为start:' m% C: a3 J% S9 u7 E( c5 b1 @
3 Q9 n( o, K+ [/ ?$ V8 N
s.resample('7min', origin='start').mean().head()
: N7 C, o9 m2 I4 Q+ e! L* YOut[124]: 6 I5 c2 o* X2 g8 D3 w z
2020-01-01 08:26:35 -2.333333
& s4 D4 o2 J1 [# A' g6 ~3 E2020-01-01 08:33:35 -2.400000) G/ C3 h. P' L8 R+ v0 i) I- c
2020-01-01 08:40:35 -1.333333: ]& ]/ Q1 }! {! h# R0 s8 _. r+ ?
2020-01-01 08:47:35 1.200000
/ B2 s+ z: b9 N3 B/ a; J2020-01-01 08:54:35 3.166667
# i3 k3 D2 n1 E7 RFreq: 7T, dtype: float64
" q1 q3 [3 i. L. q/ L3 g1
8 y5 A1 K$ [& u8 P2
# B8 T3 O5 A d3' e/ F# k7 b2 x- S4 E4 Y0 C( F9 W
4& i0 L0 O8 Z: ]- U$ ^
5
+ Q% i* Q9 |- v9 Q% ^% w6
" c# b1 G4 x; c' _7
% d, t# ?8 `5 j, A8 j8 a6 V/ D6 m8
3 N7 Z* `2 b' h0 w0 G, m 在返回值中,要注意索引一般是取组的第一个时间戳,但M, A, Q, BM, BA, BQ, W这七个是取对应区间的最后一个时间戳。如果想要得到正常索引,用’MS’就行。( w( [6 V/ }. B5 C3 L% w
5 r' F9 T/ j& ~
s = pd.Series(np.random.randint(2,size=366),
; ^2 r" f" ^6 ]% K: R7 M index=pd.date_range('2020-01-01',
; `% q0 m/ ]6 N3 ^/ e '2020-12-31'))
4 ~6 T7 k3 w8 b2 [8 D$ @/ P7 p2 @: F2 S
2 s- ]% y& s% U6 s
s.resample('M').mean().head()' `7 H& I9 n7 J1 R8 w y
Out[126]:
: W! A( V6 ?9 ^( j' `2020-01-31 0.451613
5 Y, E' I) g8 |* I- o2020-02-29 0.448276
2 f `! s) p8 i2020-03-31 0.5161298 C2 Q# r1 m4 A: v5 e/ M
2020-04-30 0.566667
+ @7 j. i0 X, L6 l9 l# f9 w2020-05-31 0.4516139 @, x- _' O" _& n* e" P# |
Freq: M, dtype: float64
9 v! }0 U+ a4 U* m$ y
4 p8 I$ p" n _- J2 P& Q1 q ?s.resample('MS').mean().head() # 结果一样,但索引是跟正常一样
/ E( |) r' u7 l% E3 T9 ZOut[127]: 2 Q: \2 J; c$ o1 h7 ^) Y$ ^8 k+ C
2020-01-01 0.451613
% S& _6 u8 Z4 b2020-02-01 0.448276
. e) a0 q) l" l7 @1 A/ m2020-03-01 0.516129
, J& a }/ V0 `$ E2020-04-01 0.566667) [7 o* Q. c2 L9 w" G6 Y- r
2020-05-01 0.451613' `, e" e8 p" u# k5 v6 {
Freq: MS, dtype: float64+ i# a/ P1 r, J' ^
8 s2 z' T! U8 O5 a5 o+ r, c+ t
1
9 U6 g, j7 u2 j) |5 I) |& l* I( ]27 E: {7 _; W. Z( O
3
0 ^- E0 A. O( z8 t4
4 p6 s1 M* d7 \7 L1 e; \3 k" y( I5+ G& Y# ?+ x6 o& x/ v! p, A) g: V
6- u# g) ~) \& R
7
- S- [- k% E# g' o+ _4 k5 n8
! y; V9 P8 c8 {! w! H% |" }: m7 O9
6 J2 m9 W4 m" n- S/ S1 _, _" a9 Q10
. [3 x7 n! T/ d/ Y- a- U11( z) [) g- I6 f1 C
125 a5 e# x4 ^/ j b$ o
133 R) @6 n3 J$ J# b* i
146 C; _, q) W' k2 F( m. _. ^7 d' o
152 m% p' q% L! K- {; V0 N. o$ @
16
+ c9 [- d6 D0 ^* v17! [! \- ^5 Y% N' V |
18
9 i/ `+ }; h) ^' J" z& G1 e19. r' x3 z: v; S
20& M' ]9 L, k$ `" K3 f% k
21
. w, _+ [" U3 }! ~3 [& ~* C5 s* Q220 n' H5 n: o1 r9 T* ~
对于 DataFrame 对象,关键字 on 可用于指定列而不是索引以进行重采样:
- e4 P9 {" `- ] e9 U5 _d = {'price': [10, 11, 9, 13, 14, 18, 17, 19],5 t# X( f% v4 x* o
'volume': [50, 60, 40, 100, 50, 100, 40, 50]}& o( E o) Q6 [# A# X/ F4 X, S
df = pd.DataFrame(d)
, b. X$ N& i9 ], Zdf['week_starting'] = pd.date_range('01/01/2018',4 Y9 `8 v, _% Z) B% w1 H" I
periods=8,
' F: y& P& J+ |& O2 z1 z# p( y freq='W')0 K& L: l3 {1 J% H, {
df
6 @/ Z, N1 f. T! C5 K+ z ^ price volume week_starting
3 g; {- q9 h: {+ b' s! @0 10 50 2018-01-07
2 [, W3 }0 W8 g- [2 T3 Y2 c- q, X1 11 60 2018-01-14$ \, M$ t$ y% i) i' w& b1 R
2 9 40 2018-01-21
( r8 Y! n) x% Y3 13 100 2018-01-28. y, w% i/ K0 J1 R2 \' [: _5 G6 E$ s( y
4 14 50 2018-02-04, ]. [& G, ?! N2 O2 n
5 18 100 2018-02-11
* ^0 M9 I7 C, u' c; M, ^6 17 40 2018-02-183 H# M- O* t) V/ I
7 19 50 2018-02-25
$ C4 z4 R# B; ]( s0 R- I3 n+ o; qdf.resample('M', on='week_starting').mean()
( a# t) u, H/ C A( ]9 M( } price volume
( z: N, {2 c* e3 qweek_starting0 S" v# v7 v) [+ ^8 l; _1 N
2018-01-31 10.75 62.5! g4 R$ K7 ]" E# ~3 U
2018-02-28 17.00 60.02 [& L. ]- f# p6 y
- E7 U+ y" S4 d
1
+ R+ H( L; p R7 G) z1 M2
- I2 W' L& y3 w/ ?9 ]! q2 `0 Z3 D3( G% ]' _( C. G& Z* @" W. C
4
2 r0 ~& r: g- ~# f6 P. X4 f5& c" |0 e* j$ S
6
% { l/ j3 y4 _, E9 B) s- K70 E, T( E3 w: @& M4 \
8
" Z: }* V: m8 S* v% T/ V4 b9( \8 \8 H, Y3 Z
10# V" i9 H0 m$ V7 L# i* ^) L
11 i, X% z6 V x( c* d6 x. i" _; G+ y" U
12
- Q6 }: ?1 D0 Q$ K8 y13: _4 N! R2 i' H1 B$ T. J" p# `/ J7 f
14
' Y/ B V) h4 W2 N8 N- v) s15& M( {! c# B! V0 z
169 J/ D! R' a. v
17
2 r. L f# o% O+ S& Z! U( m4 [183 x& G6 U9 E5 F0 p3 y9 {
198 Q [ i; s4 q( g) N9 |
20
+ X/ f1 @3 v; {; }0 }- v# @217 v8 h4 x! b6 d! [ G3 O- V# J4 W% d
对于具有 MultiIndex 的 DataFrame,关键字 level 可用于指定需要在哪个级别进行重采样。! n# T4 t8 m0 p/ p: @9 B. ?
days = pd.date_range('1/1/2000', periods=4, freq='D')
# Y9 f# M9 V7 p0 |d2 = {'price': [10, 11, 9, 13, 14, 18, 17, 19],
) S7 Z. }" P2 M8 ^7 M9 C 'volume': [50, 60, 40, 100, 50, 100, 40, 50]}! [1 c; N& _' |; g# ?4 |
df2 = pd.DataFrame(7 @8 R7 [* N) L( c0 q
d2,) N7 v) T7 B, \) f6 c: P- F
index=pd.MultiIndex.from_product(' K+ c; i6 k4 d
[days, ['morning', 'afternoon']]: [# w* P7 J3 U4 H6 H0 M
)
5 A! V/ [7 P7 w! D; ]) E)
9 }7 }+ ^6 A3 O" G; t/ e2 ydf2
. }2 K. D% C0 ~* ^ price volume: S% T, [+ u$ J5 d# ]# n
2000-01-01 morning 10 50/ I9 W" K" Z' m% }
afternoon 11 60
( x- D% J2 v0 c% l2000-01-02 morning 9 40
" x" B r4 ]2 u2 s afternoon 13 100
2 {6 ~) n% | J+ O6 a2000-01-03 morning 14 50 ~' U, s4 H. j. f
afternoon 18 100 K7 {; t6 @4 D6 f, ^/ R
2000-01-04 morning 17 409 j# K9 y+ n( f2 A, r
afternoon 19 507 ^ A( K! l3 a% ~: H( p
df2.resample('D', level=0).sum()
, X( x1 C& @# I+ Z$ H6 ?" _( N3 E price volume
5 Z0 c5 U8 u- H* r) _% I8 j2000-01-01 21 110# x% G. H: G3 a5 |: |) Z
2000-01-02 22 140
9 i% p; \) U' c; Y& Q2000-01-03 32 150
9 h& s2 k9 @+ O' Q2000-01-04 36 90
* R* r3 Z! h- @" ^& N% ^0 H; A& H+ h4 Z0 D( o
1+ z, w w% }& K* b
2% I! g1 a0 S" F4 j
3 t& T- v% q( w- G9 }
47 ]. q D5 W2 [3 r6 K. [" L1 p* c- B
56 x" H; F a4 F1 v7 J/ B
6
2 q- U/ c/ `! [9 H9 g5 _: x7+ O2 F: |. \; s: ]+ Q7 c; w0 S
8
2 h( t- J4 W+ n* i) y9 j9+ `2 B7 e0 X2 N: L0 u/ ~7 G* J
10
0 n, f& [4 W! j* g) X/ S/ \9 n( r11
I8 G5 e W E( F, l* X! T124 P$ V$ {. \5 M @; e- \. l( R
13
8 T3 ]) i# J/ J) K- I" `14
1 m- ?' z: a9 b' F15
# c0 E) Y2 b8 f( n* M# ^16
3 y6 | a/ G* e! ]- d1 m17/ D1 k+ ~! i6 K. O$ {
182 ~ J! j1 v% e; [+ m* ~
19
# S) p9 }$ p6 ]+ @; r206 Q$ o- X8 t6 q) e+ W& z
21( F4 D' _5 k& B F4 T9 G
22
5 I7 B! u ]# r232 L+ B" k4 T; e8 N
242 v# C$ B$ b2 _) v& `2 Y
255 w) i# e+ X' z( y1 _
根据固定时间戳调整 bin 的开始:
u! K' p4 J- w* y8 ^2 jstart, end = '2000-10-01 23:30:00', '2000-10-02 00:30:00'
f: c2 F. P% i0 A* W+ ]: @9 d8 Xrng = pd.date_range(start, end, freq='7min') D5 o) F9 N7 g* Q$ Z
ts = pd.Series(np.arange(len(rng)) * 3, index=rng)+ E9 z* {0 H0 j& y
ts; Z" ^4 H. |7 j4 `: `, G4 F# _! E
2000-10-01 23:30:00 0; Y: `8 h3 `, e* i, H
2000-10-01 23:37:00 38 ^* d" g9 J* c" l0 }) T# k
2000-10-01 23:44:00 60 W1 g H6 u5 d; r4 F0 F
2000-10-01 23:51:00 9
! L8 \" ^1 C$ k2000-10-01 23:58:00 12
# z" _' M, r- e% Z3 k( }* s2000-10-02 00:05:00 15
) X% ^/ Y5 k1 r2000-10-02 00:12:00 18) E3 I$ M3 Q* k% g) G6 I6 D
2000-10-02 00:19:00 21
1 h' ]" K. v* Y3 |2000-10-02 00:26:00 24
3 c" U c2 v5 L8 X) YFreq: 7T, dtype: int64
. B& u, V* N9 ~* l6 ^1 g* j8 w6 M) p5 B
ts.resample('17min').sum()6 m9 m8 ^' d3 s0 _6 \ X6 {
2000-10-01 23:14:00 0, a z4 Z" l$ `5 V/ z
2000-10-01 23:31:00 9
6 B8 }; D+ ]4 u2 n7 u2000-10-01 23:48:00 21
% i6 a% @7 p# ]2 d: s- L' j2000-10-02 00:05:00 54
' d5 {3 B) z" d& X2000-10-02 00:22:00 24
0 `' b6 T; ]) R' w: }3 J8 ~Freq: 17T, dtype: int64
+ p4 d, N, H, S. l! h- w% V5 @- b0 V" \% C6 m; h' M, d
ts.resample('17min', origin='epoch').sum()5 r' a- l3 l% g
2000-10-01 23:18:00 00 A$ v& \5 t. @; V+ ?& m; X
2000-10-01 23:35:00 18
9 N9 v. ^( Y" k2000-10-01 23:52:00 27
5 G, J: }& Z; [1 }6 L/ W/ p7 T2000-10-02 00:09:00 393 E" Q1 ?/ X7 @9 e% T) _
2000-10-02 00:26:00 24
+ ^6 x Q* b, Z0 bFreq: 17T, dtype: int64
( T& \! a Q: b' L4 X3 \; E$ F# M5 y$ W6 q4 R% ]2 b0 g
ts.resample('17min', origin='2000-01-01').sum()
9 ?8 E" h y8 R& r0 f0 \6 D7 g2000-10-01 23:24:00 3! ^# I" g) {; i
2000-10-01 23:41:00 15( S6 A: A: o: V9 d; b
2000-10-01 23:58:00 452 J8 ^5 n& j+ `% x1 [2 G; C9 E
2000-10-02 00:15:00 45; @% ]% X( ?+ D
Freq: 17T, dtype: int64) S% A8 r5 y' J, J& T3 U$ R9 w
# C: O2 Q" G7 ~
1% b6 \5 J$ a1 h. I
29 K9 m' p9 t9 v" N0 \/ V. o
3( ?: _8 b# E, ]$ h$ A: e' k
49 F4 |( A3 c: j0 H( T
52 J! u! w+ S- s' W# }0 G" _# e
6
6 M) D2 z- ]9 x# t1 t/ v73 b1 h6 _5 U) t. ^/ X- ?
86 ]* I3 c3 j9 r+ L5 ^# ~; a$ O
9
8 t1 `: H# N- [! E( l, I) T10
! W' E9 d7 c# `' i! Y9 Y0 A11
" r& Z0 x6 q2 h0 b7 a. J2 q4 i12' [% x {- g1 @4 a
13
" z9 w6 T M1 o+ ^( ]" u' w+ W0 w147 ^* _ X' G$ k, u
15
% e4 g0 v. U9 M6 X* l( ?7 H5 d9 I16+ l" V8 y- W0 m* N7 g1 z
17; j- Y: M# U& w! `: F* M8 @2 p& r
18! x2 F7 h$ J P! f
193 n# D6 P0 m3 o1 r: ?5 I
20: d, I" g. P0 G( f$ o# o$ y
21
5 w f O+ P' h' v0 {, d22
( j) C' `% E z" V6 w ^23! P" D. F5 o* d7 K1 g. }
24" ]/ s. \+ ^1 s4 @$ i
25& r$ I, s) G+ W! Z4 M9 L: I0 Q8 s
26& k1 V# f" C% {5 o$ f
270 H: w7 u8 E& p1 B- F
28" [" z6 {2 ~4 I# `7 Y3 _: J
29
6 b) V/ [$ S6 S H3 Q30" E$ L! R+ h9 u
31
' z/ H7 A& v- t1 x# w8 [$ ^32
* e3 g$ v: G9 N) M" O9 i: e. _33
' z2 @' [9 ]+ W% p' W' ^6 h34$ n$ Z) m) E9 e' l& {) r
35
s# x1 \: }0 g# r+ n1 a: a& F36
6 S# F8 C8 ]6 h2 k& m6 Q37+ k9 f( ^) v7 [
如果要使用偏移 Timedelta 调整 bin 的开始,则以下两行是等效的:
& d" `8 ?+ [, b+ M! v# b1 }ts.resample('17min', origin='start').sum()
) U) i/ U: s0 ?. o T. d* D4 ]2 @* G# xts.resample('17min', offset='23h30min').sum(): G% y5 g" K' d
2000-10-01 23:30:00 9% B9 H8 X" a. K: o3 \/ w
2000-10-01 23:47:00 21
4 U o# y) d6 D# Z! n( y2000-10-02 00:04:00 54, ?+ o+ ]. X7 f, o9 t) w# _2 f
2000-10-02 00:21:00 24
( Y. h5 O1 Q2 B& e: TFreq: 17T, dtype: int646 e( w4 L8 y- H/ P! x
1- ]' j9 d9 ?7 v+ _$ a+ h4 `
2
* B e/ g9 [; j2 q, a; V7 N3
% Z0 M1 ?2 l- j1 o9 e4
4 ^7 j+ A2 e- m) f. P; j- G8 X- T- q5( |" p: P! v8 R' A3 }, {
6
" d/ y: _0 w" h' g" Y& f) p2 V( N7
& L8 O2 n. k9 S10.6 练习/ T, o/ x; m1 ~$ m. M% ? `
Ex1:太阳辐射数据集
0 E/ C0 k, r8 N, w0 J) v' R, N现有一份关于太阳辐射的数据集:3 x. Q) g f6 T4 A9 k: E3 T
1 i& e& u8 H6 G' B2 tdf = pd.read_csv('../data/solar.csv', usecols=['Data','Time','Radiation','Temperature'])
6 Z" y$ q" O& o2 t% T$ z" P: ~6 `df.head(3)- h4 ]6 G# g- d' i4 {" l, i
+ }4 k- ?! X: V l% W! W+ w0 P
Out[129]:
- b0 _* E, }7 T' z& B4 C2 q/ A Data Time Radiation Temperature+ h1 I8 R) Y9 K' Q) r- g% W5 V
0 9/29/2016 12:00:00 AM 23:55:26 1.21 48
* U3 {5 A' l2 ^0 @% R1 9/29/2016 12:00:00 AM 23:50:23 1.21 48
7 ?, ?, F- E3 M, F2 9/29/2016 12:00:00 AM 23:45:26 1.23 48( c6 T. {. d6 Z% R& H, T$ G% b& T
1
% h+ V# [6 ~& i4 v2
" p. }: P, m- x1 l) B" o33 W+ w7 d8 ^/ \- N
4
- ~3 l* D7 g' J8 C5
/ F! G( n( f4 b* n9 z' E6
) j: w9 J$ r( E/ ?7
0 o& Y" X6 L$ [, v0 u( j88 k* y6 u5 @ A4 m' O2 k
将Datetime, Time合并为一个时间列Datetime,同时把它作为索引后排序。5 g, _; K8 o5 J
每条记录时间的间隔显然并不一致,请解决如下问题:
; z) H1 w7 f; d. F9 c找出间隔时间的前三个最大值所对应的三组时间戳。; Y. p# g1 ]7 Y! T
是否存在一个大致的范围,使得绝大多数的间隔时间都落在这个区间中?如果存在,请对此范围内的样本间隔秒数画出柱状图,设置bins=50。
% o% z2 }& D% m& d4 V7 {求如下指标对应的Series:6 v7 z9 H2 }+ m# h# ]7 Z
温度与辐射量的6小时滑动相关系数5 _" q& ] }/ C# o- w8 f) V
以三点、九点、十五点、二十一点为分割,该观测所在时间区间的温度均值序列
# g- w/ ?- [ d9 x. K. [: y! F每个观测6小时前的辐射量(一般而言不会恰好取到,此时取最近时间戳对应的辐射量)* }) E. u" H6 `7 E
import numpy as np
: h. |* w. b! q, q5 _" T' C- pimport pandas as pd; [( a& d2 U/ {$ |0 |6 @
1" g; V. I# R3 Q$ o
2) `, L; E; u7 |' X; Z8 Z
将Datetime, Time合并为一个时间列Datetime,同时把它作为索引后排序。
, s6 h5 [/ j& h! H$ c+ | ldata=pd.to_datetime(df.Data) # 本身是object对象,要先转为时间序列$ g# [: p* l1 H& P% s7 F5 w" e# f
times=pd.to_timedelta(df.Time)# d# n, ?- e4 m1 x, t
df.Data=data+times- T' k! G0 b/ }- s# h" ~
del df['Time']6 I" Q' z" s4 Z3 ^. k0 ]6 q
df=df.set_index('Data').sort_index() # 如果写的是set_index(df.Data),那么Data作为索引之外,这个列还另外保留
: V" j# r7 ?/ h: Q9 l) Kdf
: _1 b/ [0 f- U- \ Radiation Temperature
d2 c6 h# s' k. C$ [Data
6 z* T) U5 [0 E$ G: L) C2016-09-01 00:00:08 2.58 51% Y" V2 Y" C$ ^
2016-09-01 00:05:10 2.83 51
$ s/ c* k7 g# ?0 k; h2016-09-01 00:20:06 2.16 51
2 ^0 U2 f1 I: d5 B) U2016-09-01 00:25:05 2.21 51! r! M" b3 O% h" A m5 M* c
2016-09-01 00:30:09 2.25 51% {; L. v; x$ n4 ^. Q
... ... ...
& o: h- O9 a. q5 j) `- }- I2016-12-31 23:35:02 1.22 41
I& }* I l; _8 z ]) N2016-12-31 23:40:01 1.21 418 t( U+ y/ c6 N- B
2016-12-31 23:45:04 1.21 420 _* n% d, f. k9 j$ X! r
2016-12-31 23:50:03 1.19 41
R# B( Z; h- y5 Q9 Y1 f2016-12-31 23:55:01 1.21 41
) u g1 n, ^( g4 ^/ L. i8 Z t5 k1 B. P" U" Z x
1
4 ?4 `5 y+ C% D1 c2
& N( C0 ~' M0 x4 I0 G0 P& w3 A4 |" D; X v
4$ I5 g0 i$ o, s0 A
5
5 y. [5 b1 Y3 ^0 e6
3 s4 d5 P( k& e: q) ?4 ~6 R! F73 O) J' I* n* l! t4 e( \
8
- G$ F W- c& m. M+ [9
8 U: |0 U4 h: J, X7 g0 i/ r10
0 f: R( V* b- g2 `11
) P) j- b: P# ]# B& ^! F9 ?( ~5 I) m9 k12/ C+ J% f, \0 R9 [/ ? z3 [/ v
13: s% S$ s$ Q- n$ l: F
148 a& c6 d5 J% \( D0 m9 G j
15
% | R4 T; k( b! M8 L7 b4 q& v: g! _9 m16
& b% o8 C# A0 \1 D* J& {8 d) h) w17' M: \' s6 e) }3 @
18
$ U! Z y5 T$ S% |19 A- u! B$ }' L0 n/ g) |% M
每条记录时间的间隔显然并不一致,请解决如下问题:% r! [- z7 g1 F
找出间隔时间的前三个最大值所对应的三组时间戳。. h' T! D9 F+ d4 ]- s# [
# 第一次做错了,不是找三组时间戳, e2 [0 k. F9 S0 T9 m
idxmax3=pd.Series(df.index).diff(1).sort_values(ascending=False).index[:3], _- x( ?4 P0 Q E8 e6 p3 r3 v1 w2 ]
df.reset_index().Data[idxmax3,idxmax3-1]
# v# B) C6 s* F2 V/ Y
4 ?8 l1 j- d% c9 l25923 2016-12-08 11:10:42
3 ^" U* ~. }% E% l0 x6 {/ u( G24522 2016-12-01 00:00:02) @9 c. g7 U9 ?) }. F% a. d
7417 2016-10-01 00:00:19# \+ h6 U$ N, F ] `0 j
Name: Data, dtype: datetime64[ns]
/ m3 x v0 f8 _2 Q1
& X5 Z9 u) P4 H2
# U7 T3 g; l! e7 }! F' m6 w3' Z9 ~8 K( Q( V& z2 ^& j- Y
45 [, W* l8 |6 G, Y3 N- w
5) o1 l1 Z$ J& E6 }
6( ~! U9 z* q2 I
7
) n- U+ g! ?( D81 V7 Z- P7 ?3 K7 d6 u
idxmax3=pd.Series(df.index).diff(1).sort_values(ascending=False).index[:3]
- Q& o+ ~2 p: Y- G$ b; glist(zip(df.reset_index().Data[idxmax3],df.reset_index().Data[idxmax3-1]))
7 P' i1 D% p9 }4 r% v4 ^$ y' H1 p
& g! c% I6 }# ?' F. ]% a; Y6 J7 r[(Timestamp('2016-12-08 11:10:42'), Timestamp('2016-12-05 20:45:53')), X0 h$ C" Q) R) P4 D
(Timestamp('2016-12-01 00:00:02'), Timestamp('2016-11-29 19:05:02')),
+ d; o/ h& z9 q3 X }) J! `+ ^ (Timestamp('2016-10-01 00:00:19'), Timestamp('2016-09-29 23:55:26'))]& Z$ z: }/ n- L/ r) Q; K& u
1
( A# p# h% X9 }( d. Q28 Q6 j9 W5 Z9 h: _5 c
3
% b2 `' x6 F7 n* n9 S/ P! R41 n# t! x0 P; e
5& B/ ~- E" U7 v+ w7 X( m
6
. G8 @- x$ ?& L6 \5 f2 r8 M t参考答案:7 V1 p' N5 P; o7 m# t& M. y6 _
+ e$ T A+ y$ g9 x
s = df.index.to_series().reset_index(drop=True).diff().dt.total_seconds()4 G% n: O7 A, O3 ]3 f8 B2 B m
max_3 = s.nlargest(3).index# l" k5 {/ ~) r: q Y
df.index[max_3.union(max_3-1)]4 G/ a8 n5 f4 t
0 P' |$ _& w$ j/ a, I% kOut[215]:
5 y3 c9 x% X" s) w( {& z' ADatetimeIndex(['2016-09-29 23:55:26', '2016-10-01 00:00:19',, \! M, `# h- n' x% r+ R
'2016-11-29 19:05:02', '2016-12-01 00:00:02',5 {9 m2 E. v! [1 a( i/ B, |
'2016-12-05 20:45:53', '2016-12-08 11:10:42'],
: O$ w; b5 O* g: V+ o; ]# g& r dtype='datetime64[ns]', name='Datetime', freq=None)
4 w$ u! y; Q: h! T1
* s/ Y, ^3 u7 a2
+ W; `5 ~* N* b37 j2 ~" j. T- m6 j, l
4
) ~( W' A) v% b5( [4 ?- m2 ^/ G7 V4 H# ^1 K5 _8 j9 j
6
* H0 x9 ~1 y0 l* V9 F L7 {7
3 w, V8 I' u5 ^0 }! K. S- X8
$ i# J# ?( {" k, p. c5 |9
# Q1 m# [7 }3 E( v7 \+ s6 s是否存在一个大致的范围,使得绝大多数的间隔时间都落在这个区间中?如果存在,请对此范围内的样本间隔秒数画出柱状图,设置bins=50。
) Q6 E7 H% M0 X, {, o6 c; t1 q# 将df的indexydiff做差,转为秒数后排序。再求几个分位数确定取值区间
7 N9 Y7 P* ?% V2 bs=pd.Series(df.index).diff(1).dt.total_seconds().sort_values(ascending=False)1 E V& V+ G. W- j
s.quantile(0.9),s.quantile(0.95),s.quantile(0.99),s.quantile(0.01),s.quantile(0.03),s.quantile(0.05)
( \$ Z, U! h, @" q
: v0 B1 Y) W. Q% ^+ ]( C(304.0, 309.0, 337.15999999999985, 285.0, 290.0, 292.0), m9 n- S1 |" F! F
1& J. p) X. |& a% C+ h4 r5 H& U
2
, X% `; a7 O# w, c+ e3# S% g6 A' o7 i1 ]. E+ x% D" m
4" l5 u/ y' Q( l H; S" S( c
5
: G0 ^& i* Q* b$ V0 J# ^%pylab inline* I) h2 K3 U% S5 ^7 A: w$ S
_ = plt.hist(ss[(s.values<337)&(s.values>285)],bins=50)
4 x$ _, j. q$ u j- ^plt.xlabel(' Timedelta')
0 c* L5 X' ^8 w" E0 p+ mplt.title(" Timedelta of solar")
5 c( F8 J- S+ R; r& Q4 t' K8 q0 o1. {1 [' J- d! f# e6 ]% W: ?
2
P8 C; @( ]! p, C7 F2 x. Q: l3
# F$ k* q; B- v* [4 L/ X47 H+ m+ L# S6 `7 Z; c6 |
- n; E9 I4 h* c4 M, a' v6 ]
9 w2 N; r. A( C5 T
求如下指标对应的Series:
8 g; Q" }) o" w4 v5 {温度与辐射量的6小时滑动相关系数. E0 d3 X7 F2 m/ v2 L0 m& C
以三点、九点、十五点、二十一点为分割,该观测所在时间区间的温度均值序列% {7 L" ?, t; B5 Z
每个观测6小时前的辐射量(一般而言不会恰好取到,此时取最近时间戳对应的辐射量)
. W) @, C0 i2 w, C% D: D: ndf.Radiation.rolling('6H').corr(df.Temperature).tail(). T! `* \: Q4 N6 z# G; P6 j
! W- d" R2 `1 x" Q: s/ W3 v+ p) U9 i
Data& Z q! s4 l" o# C+ |
2016-12-31 23:35:02 0.416187* O/ q( m6 ]! g, {) c+ ^
2016-12-31 23:40:01 0.416565! F4 P/ i: @& I! Q H9 ?. H
2016-12-31 23:45:04 0.328574
# r- ^$ I% k3 h/ w9 |+ c2 P: }2016-12-31 23:50:03 0.261883
( K* m: U: @; R+ L: r, M2016-12-31 23:55:01 0.262406
+ N9 @$ ^* }, L2 B' r' z7 q+ Bdtype: float64
Z8 t. W) l2 l ]# V1- a6 P# f' x7 M8 s/ B- N# k
2) `8 X( M; d! t6 L$ V8 o. m3 l
30 I g0 l# B0 `, o4 C
4& ?" N( j* H1 x0 r1 x7 s
58 E; V1 l! L. k
6
6 U( U3 i x+ j: h) j78 s t+ O2 A/ b' z* u& B
8, K; Y, D" z! W- y8 X n/ U
9# }5 Y) W$ O! R ^
df['Temperature'].resample('6H',offset='3H').mean().head()3 c4 C( c; ? y
4 v9 d+ r, H6 W1 ~* C$ W
Data0 H% o9 z4 A. Z* b' n7 Y
2016-08-31 21:00:00 51.218750( J6 V$ H# n, H. w
2016-09-01 03:00:00 50.033333
- [7 Y- }0 h2 P+ T" J5 A8 l2016-09-01 09:00:00 59.379310, e: D# ]* U0 i9 p
2016-09-01 15:00:00 57.984375
# w9 i7 m& {: z' }; m: `2016-09-01 21:00:00 51.393939
2 x8 y) a# X2 ] sFreq: 6H, Name: Temperature, dtype: float64
$ E; a; w/ y- t0 L' B5 j1
% |5 E! a m4 \8 C! a2
B: A; @6 s2 v' H$ H/ _, Y3
2 ^8 v% i$ E! v; ^9 j44 g: }' R2 a2 w$ I5 y) f J
5
: z/ v6 L. a% U- _ V66 i1 ~$ m5 j9 a: Q- A
7
. Q% n; `% M: ~$ c87 s4 y0 v. ~+ q7 X+ k2 _
9
! N J# t# S" S8 L# ?2 b最后一题参考答案:2 L8 C3 ?/ J& f& t
3 I9 z/ l3 a" D# w8 u) a( M0 |' N
# 非常慢
4 T& K* O/ Y, N: u1 ?2 b1 S9 amy_dt = df.index.shift(freq='-6H')
& I5 A+ H* T# o7 i& S5 e, ^/ ^) h3 f% w, Mint_loc = [df.index.get_indexer([i], method='nearest') for i in my_dt]
r& o/ ?+ x1 `int_loc = np.array(int_loc).reshape(-1)
, I2 o/ L- O' V% p( Z. \res = df.Radiation.iloc[int_loc]# g z" S: w% {' J+ A$ J
res.index = df.index
$ b, x( n$ X( Z( Q- d* X7 | }! L3 Y! Ures.tail(3)
' u( C1 I; |* x, v( [' M" {1- x; g' \& r- x) H
2; Q9 p2 i X) i- o, p7 S
3
# |4 ^, s6 N/ F4
9 U1 j! h$ i3 w- v1 H; I9 G, Q5 X51 d" Y: _# X6 x" o
62 a8 J# T) t& g+ b8 t
7
" L; Z' A' }, Q p4 n4 x* b# 纸质版上介绍了merge_asof,性能差距可以达到3-4个数量级
' }: m+ ?6 a3 _9 l0 f( Ctarget = pd.DataFrame(
8 F' E! L2 L0 S! J- w. j {* E& V8 A7 ~: o# D
"Time": df.index.shift(freq='-6H'),
3 m- c: b& g* w( A& J: o! ~ "Datetime": df.index,
1 q4 S0 d" C; }. l8 a9 X O) L }
! {$ O( l3 H& C: y)# Y2 }+ @ P" c; p8 M5 p" g9 r
+ m+ f) R6 o7 l/ o% [
res = pd.merge_asof(# X L8 A- ^) |, N
target,1 n% O6 I' D6 y3 T" P
df.reset_index().rename(columns={"Datetime": "Time"}), a: H4 v, x+ g$ K3 s2 W: y" v; ~
left_on="Time"," ^- A+ M1 h! C, i. r3 {
right_on="Time",
5 Y: X; w& o( s2 B5 E direction="nearest"+ u% ^$ v2 k* P
).set_index("Datetime").Radiation9 Q' I& x. E9 c7 w8 `5 T! N
0 |+ }) j1 ~- ]! f1 o
res.tail(3)! A \+ p* z+ R! h. e6 R6 m" C7 W
Out[224]: * h L$ P/ P# \- N1 y
Datetime9 G l* ]+ @- L7 d- ~3 e. W4 C( O
2016-12-31 23:45:04 9.335 A# I- a; m* f/ o6 O9 p& \, h
2016-12-31 23:50:03 8.49+ V0 f- z8 N; Q3 e1 h& g9 \1 M
2016-12-31 23:55:01 5.84
4 p/ Z0 m& Q* j# u tName: Radiation, dtype: float642 N9 P" A: \) Q& S1 ?( o
% K9 j4 U' ^ q7 D# b \: ]
1
5 O5 J3 J" A1 j9 E" H: @1 {9 \1 G2
) @0 c$ Y; V& [. Q; n3
! D9 R; g. r4 @# k4
) Q3 y1 z7 r3 u" Q8 ~8 ^5# C" s9 O) t2 z6 `
63 _ y, }: I8 k4 H6 j
7) F" N/ Y" T, h; j% x9 r
8
H! @( S6 h& C% I5 B' B9
! }4 l5 A/ \; e4 X4 h5 y$ }. ^109 K! F" t( w( ^3 ~3 c# a
11
X( _1 l& Y3 D6 k+ J6 e% @2 W, @12( c6 B$ ~: { @. b9 b* q) S
135 {( P i7 V9 o
14
/ F8 K. J! N/ x; J. ^, N15
6 t; n9 [+ t6 v2 f5 J4 n16( i+ ]. J! }# p
17+ u9 L6 ?% p0 s1 n
18
( _3 H& r( Z1 N! g5 t2 Z19. z7 s3 X' Q5 {. G) R% x( }/ l7 N
20- C2 J# m8 e0 G2 A- A. R/ R8 p- D
212 n1 N) G. Z+ u* F
223 i5 V7 o- p' Y0 U7 K4 [3 J
238 h8 X2 _0 J2 L
Ex2:水果销量数据集8 ]; @ W- d) m" f, Z
现有一份2019年每日水果销量记录表:
. A. z( m3 T; m. ~5 ?7 ^7 N' u% A$ |2 }
df = pd.read_csv('../data/fruit.csv')4 X7 v, F" A9 N( I) c' T; h
df.head(3)& [$ m' s- H- K- m
; j1 y! [" a5 L" JOut[131]:
/ q3 D1 {! K0 x Date Fruit Sale
9 g+ p+ c# l0 T( r0 2019-04-18 Peach 155 Y! }: |' n' \
1 2019-12-29 Peach 158 A9 f. Q8 _# R- G# k" C
2 2019-06-05 Peach 198 b. s: ~+ d1 I
11 G4 S1 R; |$ G5 D
21 ^3 F2 p& v N" m" s3 e# w+ Y
31 c( g" U0 }& p! m4 C
4
! u7 F9 o5 a* |( \5
0 V( k7 ^% D5 ~" z* h$ g6
' d$ ?5 J4 n; g2 B k7
0 H+ R/ L6 m9 ?. E86 i2 P) g" v! A" F6 E' \2 H
统计如下指标:+ ~6 F5 a5 o9 U; ~, e
每月上半月(15号及之前)与下半月葡萄销量的比值 z( D: d0 x0 Q9 K* k$ n: C& X: t
每月最后一天的生梨销量总和: R8 A# j1 ~ ], O q
每月最后一天工作日的生梨销量总和' L" _+ X E2 |) C) A4 i) _
每月最后五天的苹果销量均值3 {0 k* |- E1 K5 s/ J1 S3 B! T7 ]
按月计算周一至周日各品种水果的平均记录条数,行索引外层为水果名称,内层为月份,列索引为星期。
7 v# K( {4 \) Z: _8 ~按天计算向前10个工作日窗口的苹果销量均值序列,非工作日的值用上一个工作日的结果填充。6 O8 U- ~; U8 @% k& K* U r
import numpy as np( R; H" U; y2 J! E7 c- d
import pandas as pd
( j" E4 b5 v7 y7 ^# k( _1 P0 ]1
! v' z2 t1 F2 A- i6 i6 W# n+ i2
, l) h5 W9 X; k# {/ n1 L6 v统计如下指标:
$ ?9 b7 z7 y- l* t1 _7 j每月上半月(15号及之前)与下半月葡萄销量的比值" Z7 z+ `* f- o; O
每月最后一天的生梨销量总和3 N5 \7 q& y8 O% T0 u
每月最后一天工作日的生梨销量总和' C# W$ T, u9 o3 F6 W3 B/ V
每月最后五天的苹果销量均值
1 g: M, Y, G4 ~+ @# 每月上半月(15号及之前)与下半月葡萄销量的比值
$ \ q( I# D, x2 [3 p Jdf.Date=pd.to_datetime(df.Date)
0 A! s# L1 d, V. b3 B5 S5 csale=df.query('Fruit == "Grape"').groupby([df.Date.dt.month,df.Date.dt.day<=15])['Sale'].sum()3 X7 ?9 ~2 |. P6 z
sale.columns=['Month','15Dayes','Sale'] # 为啥这么改没用啊$ o( f' l% b6 M2 Y9 V
sale=pd.DataFrame(sale)
6 B) i% L4 P1 M3 ?5 n3 Fsale=sale.unstack(1).rename_axis(index={'Date':'Month'},) e! o7 n5 a7 x& e" V8 E; Y
columns={'Date':'15Days'}).stack(1).reset_index() # unstack主要是两个索引都是Date无法直接重命名
( \5 S8 n; W! x4 _sale.head() # 每个月上下半月的销量
( e s: } N" M: F% ~* S) Q6 f
% A$ G( ?8 A. u9 D' |, x Month 15Days Sale6 U( v# v" I: c- g. _& d7 n
0 1 False 10503
* ^) \+ F2 O. r& u1 1 True 12341
$ _4 j9 v- H. p5 K7 L+ f. U2 2 False 10001! k0 e, I5 O/ J1 c
3 2 True 10106
& d, a \. U5 Z4 3 False 12814
/ k8 J e! L" C, ]+ H& }8 ?' p" p% E0 j! r7 _
# 使用自定义聚合函数,分组后每组就上半月和下半月两个值,根据索引位置判断求比值时的分子分母顺序
* [) G- t) }9 [2 Usale.groupby(sale['Month'])['Sale'].agg(5 ?" ]- G- {; w8 I4 ~
lambda x: x.max()/x.min() if x.idxmax()>x.idxmin() else x.min()/x.max()): l* I7 ]; {: R0 ^; O, e, E
, I' ^: \& I: g
Month
* U2 T8 E! Z; h1 1.174998
2 `4 B. f: B" n. g+ T2 1.010499- \/ Y3 l& y# a
3 0.776338$ ~$ k8 U+ b' d8 @" U% a
4 1.026345
/ g' K, s, d0 O: n, t2 D; e5 0.900534- b/ t4 M _0 m$ `
6 0.980136
1 k3 {9 T; S0 s% c9 K- P7 1.350960
' f2 I+ Q' n. C2 Z8 1.091584
9 f) B# b5 ?' }8 E9 1.116508& y+ I, A k: a ?) n, Z. {
10 1.020784
1 g* U5 N! { r: n* e: P11 1.275911
5 O6 K2 x3 u7 E: R( R1 u12 0.989662
& C+ L, [$ X, f2 R* F6 F+ D6 t2 lName: Sale, dtype: float64* C5 S5 a, E) |3 { Z
2 F) J6 n( r3 ]+ t+ s" P5 H1 C/ a
18 i; ~) q, r# U: ?
2
! n' s0 T. s3 c& E' O: `% C3" d2 |5 f& W( c/ V" {* V6 P( @
4
3 F, h$ j% P2 K% G" o5+ y; Y5 v4 k$ }. V/ o: h* `
6
: G3 a9 ]4 e; N/ o7
: o$ ~: H( l {: m7 O82 f ]9 P/ G' L$ Y2 s
9
7 K: c; Y1 Y# ]6 i10
; E) R: U/ k5 G" T; g: G+ O- c! ?' S& q11
- W7 K5 s: L. y& j, t$ S; B+ ]126 M9 ^* Z* X- p7 Q$ C! L9 f
13
2 Y i; T( q: s6 _3 Y; W& X14
: M$ l9 C& g4 K8 S154 }2 x% m G! d W2 H
16
- f% F' J* X3 C0 T" I z- m9 {17
1 ~$ z/ \3 M( N) L7 Y181 O* b7 s1 Z$ R) L# r
19$ K3 {( e+ [0 r2 w" [
20
' B* w+ T3 @3 I6 E* {21' H# n! \# v; {2 ?, }; G' o! [
22
; I y0 @, ]2 d# z23
- Y# d; y4 M. \2 ]24+ ^6 I* G6 C/ F! C0 X. v. z
25
5 ` s3 J1 @7 l: ^# k. x( q6 \ F26
' K; @* R; u7 w( t! X- S5 l27& [7 y% j. K6 B0 T8 b& r0 H# ^
282 w/ q" b0 l4 t1 q! `5 z
29
* j5 R6 p& ~) k/ V30+ j0 W: x% a& {
31
! T4 f. C: ?' @8 U# U; R+ W32+ Y `% y' `$ K- z" k2 ?
33& W! M4 `* t K: |. ^% a8 V
34/ N' B( L! }6 K$ |" l% h, F) W
# 每月最后一天的生梨销量总和
' k. H, m, c: T. z9 b. M$ {df[df.Date.dt.is_month_end].loc[df.Fruit=='Pear'].groupby('Date')['Sale'].sum()
# V2 w" e$ D. `/ Q j
& d" P* D" W8 X$ MDate
+ Q, [7 v1 R2 O3 q/ V2 Z2019-01-31 847
9 ? S8 A; Y1 ]" s+ H2019-02-28 774. t$ t# M( N3 q" b* x( a
2019-03-31 761
6 n u; z( n; y3 F, v2019-04-30 648
0 K3 e. Q% `5 X2019-05-31 616
9 h; P; F+ y7 C/ J15 {5 f9 e+ I9 b; h' n+ D# z, Q9 j5 @
2
5 Z: I' x6 r6 F) S' O3
4 \! j. A% g5 c C4" [* B* ]* Y- i. B, q( D( `9 Y
5
# X' S) M& }- q6. {+ ^% [ A' s: F$ N
7/ N7 ^! A; n: j; _$ f
8
! i- v6 r$ F8 `6 ~7 z8 u9
& C3 Y2 Q1 [& P1 Y0 ]# 每月最后一天工作日的生梨销量总和" Z7 ?+ i4 ^: r" ^+ V
ls=df.Date+pd.offsets.BMonthEnd()# x: Y! ?; V7 r2 E6 L$ X
my_filter=pd.to_datetime(ls.unique()); m" J! ~6 [! }6 Q/ ]5 X1 _( B/ X
df[df.Date.isin(my_filter)].loc[df.Fruit=='Pear'].groupby('Date')['Sale'].sum()
, |! n3 R' k) a6 u5 ^4 U; i
/ F5 A1 }' X3 ^2 J/ O7 yDate: ]& D" Z* q8 c" U4 _
2019-01-31 8479 ~* I; P" R5 P0 V% R* g. x4 O3 k
2019-02-28 774
- u; M( O9 o/ N7 K& \+ S2019-03-29 510
8 w% ]$ T% J. L: v$ Q2019-04-30 6488 g/ ~! S; R+ G" J# J! j4 c5 t3 m
2019-05-31 6165 Y! f9 u+ Z" A5 Q# G% q3 L+ G; k
1
* m# V: \( e$ m/ V9 Q$ _. `2
/ e: i5 [6 N) g) m" H* M# K3
( F# i) B1 g9 t! c1 b5 G4
! l4 S9 }! h3 m! ^, L5
$ f3 U2 d# G! T2 M/ U+ k6
/ u! Q; y. Q, K- D) v7 }- \7! Z. o5 Q. C. w3 R
8
) M$ `8 } E, z* |9' O0 z9 R& Q2 Z# [5 B5 Z+ y) ~
10
' K- R* K; c& E. ^5 o11( \* @5 S& [; {! a6 m6 ?1 j& e
# 每月最后五天的苹果销量均值
5 b* Q3 O* ~& E- i3 Hstart, end = '2019-01-01', '2019-12-31'2 q5 [1 B/ w/ h% W/ a! B1 z
end = pd.date_range(start, end, freq='M')! U* p; r5 |; b. N" d/ R6 z' y0 k
end=end.repeat(5) # 每月最后一天的日期列表,重复5次方便做差
: h+ G w/ }2 |; J* G
- ^5 l% x( ~$ C- A: d6 ptd= pd.Series(pd.timedelta_range(start='0 days', periods=5),)
) d8 e% J2 Z: q$ qtd=pd.concat([td]*12) # 日期偏置,最后一天减去0-4天# Q& v2 ?4 o( J4 J
end5=(end-td).reset_index(drop=True) # 每个月最后5天的列表
" D* I; Z" t' f# J. s1 `6 ^. s# R/ c, m* y5 @" y: R7 s
apple5=df[df.Date.isin(end5)].query("Fruit == 'Apple'") # 每月最后五天苹果销量' C/ }0 x! Y- w) a
apple5.groupby(apple5.Date.dt.month)['Sale'].mean().head(): u' \+ a1 {- s i- {
* p9 C) Y8 W% M5 p8 _Date( B+ a- O; t2 D1 T( w* O
1 65.313725) `1 o$ n5 t' h% q! y5 m0 E* i9 [( ?
2 54.061538/ W$ c3 d( L v5 c9 p
3 59.325581
' c7 q) K: a1 H u5 N! o4 65.795455 t0 L' z2 S2 L# d1 k8 T
5 57.465116
' t3 V+ m" k: Y
! b' v1 T& Y5 M w; I1
& r( {0 q4 q- w. O% [- B. N2
) H1 X! f) R4 J8 A$ ?3
# ~" H b0 H7 | W7 J3 s4
/ B `6 |8 p+ L% [9 o) [2 e9 M7 B55 u$ J/ P! l0 ~1 J. p7 H2 |
6
- Y9 Z$ |, ]+ q& A) n7
9 I- Z) y+ O+ c$ H' P8
# r7 f! E! ?% p3 E9 S8 @9# L3 I6 g1 T! @
10! q5 Y" `3 [& w* ]5 F1 @' M
11
9 t4 c C* p' z: _6 f. x- k12! L* P) j, `% r# u
130 ]- b0 e# F# @# a" T
14
+ [/ [' N/ X5 F2 z15
$ R% D( ~- o; A7 W/ _/ X v4 I# o8 M16' M- {* i5 b2 x6 Y! y% g' L
17
6 A& R! v8 w1 b6 D F18
, k5 b0 _& \, G: G# 参考答案:7 j* q3 d4 F3 G0 \
target_dt = df.drop_duplicates().groupby(df.Date.drop_duplicates(
# z8 S0 `( n* {) k) R2 {# {' D ).dt.month)['Date'].nlargest(5).reset_index(drop=True)
" K7 X9 W" ]( I! H
4 b8 g0 Y! r) }6 Kres = df.set_index('Date').loc[target_dt].reset_index(
7 N1 [ W4 Z8 G" {7 Z ).query("Fruit == 'Apple'")
% y0 t9 ?( u* m4 x2 h s5 u) F; H8 j( f) P, ^) \' X
res = res.groupby(res.Date.dt.month)['Sale'].mean(* H) ^2 w' ?, e2 {: r4 D* v" K
).rename_axis('Month')8 l6 [9 @' C) L" w( i
# z B/ o; ^' u& P4 J. N6 p$ [5 k' S) c- z
res.head()
1 @. X t9 a8 e/ }4 B* hOut[236]:
9 ~$ h- W5 I/ ~ n" F+ zMonth( p E# V: K! a) M- V* e5 H; t
1 65.313725, M* o- ^1 T$ T7 z2 q
2 54.061538" }3 a7 N3 s, v$ l5 B/ A
3 59.325581" K9 F0 A z; G$ e; H6 L8 L" s
4 65.795455
# h8 o- L% p2 Z5 L- N+ x5 57.4651164 x( N/ ^, m5 a6 z8 w5 @
Name: Sale, dtype: float64
) e) A8 h' F( D5 } x- t
[( ?% ~7 Q9 W! U1 D" \% K) z1( w) X% Z6 R) r
28 j. \7 {& o% ?4 i' C2 E" [1 p$ P' O
32 ]& B# M8 V" o0 O! i5 d# N9 p
4
1 I4 l( H( v/ U1 q# P$ R( ]5' o4 J( U/ ^4 `* s9 P0 b
6
8 N$ b X' C, f8 T2 V7, f. m- Q. g* z/ ?% {! r$ p. [
85 S0 g! J6 N5 a1 o& A
9
/ @7 P$ `, S1 [! W! D8 O, N ]10! D: j+ z3 P5 _2 N, K7 z
11) |. {; c- s6 N% ]( Z6 b9 u
12
. A6 S N1 Q* ` T7 h' _13
8 [" v! \0 W1 @0 q" _( n4 w14! X! R+ X4 c2 O' h8 c$ z: g! u
15
* U: J5 I; K5 B4 p) Y16
! L. o/ C1 Z. Q17
5 P, X; a5 {1 a* \2 F% @181 b7 A2 |+ z; G% r1 j( ^7 j# |1 Z
19
/ X4 q i8 l1 B5 t20; _# x3 s: K# z( t2 ^* A7 S
按月计算周一至周日各品种水果的平均记录条数,行索引外层为水果名称,内层为月份,列索引为星期。7 Q; G3 o3 ?# p$ Y
result=pd.DataFrame(df.groupby([df.Date.dt.month,df.Date.: g2 W0 u8 c9 [0 [6 c
dt.dayofweek,df.Fruit])['Sale'].count()) # 分组统计 / l. }, F( ^, i
7 O& o( G3 s" c! B7 N9 S& x% Q
result=result.unstack(1).rename_axis(index={'Date':'Month'},
6 T4 R1 ]$ c/ q: z" L# A columns={'Date':'Week'}) # 两个index名字都是Date,只能转一个到列,分开来改名字." [/ _; r, e, o! x- M
result=result.swaplevel(0,1,axis=0).droplevel(0,axis=1)
5 i* a4 A* M* s9 D! Aresult.head() # 索引名有空再改吧
1 d- t6 e( a' k1 N1 \* H
3 V) [$ t8 ^ @1 [* T; t Week 0 1 2 3 4 5 6
! ^7 G/ i$ I* ^. aFruit Month & C/ W) z$ ~8 O- S7 r/ f0 m1 N! q
Apple 1 46 50 50 45 32 42 23
7 @4 }* M' k) J4 CBanana 1 27 29 24 42 36 24 35# d' k# u9 o; D( d# Y L9 X
Grape 1 42 75 53 63 36 57 463 p+ p* \# |7 W! T1 F& Q5 ]; i
Peach 1 67 78 73 88 59 49 72' e o1 S: n4 b. P7 b
Pear 1 39 69 51 54 48 36 405 J' s% A' ?9 a/ b
1
2 d1 W/ U; X5 r, L: P% b2
% A+ T9 ^# q6 Q/ q/ p6 K3
, N; i9 t- C1 O5 H' N42 s8 ?# s3 A* h
5$ I" p$ A3 T# n3 }3 _7 Q
6
+ Y( T4 u" x _ Q F6 v1 x7; D2 i( t6 j( V. q, h2 Z6 P
8
2 N; f; q q5 @1 W( J: A! i6 y7 y97 C( Y5 f7 z z& x
10
& J5 x6 |4 N' X2 t4 b11
2 ~2 q) [' a! ?( I# R12
( Y- w! J- ]4 G4 z13
7 y' l5 w! ?" H- R' N2 E$ S1 d142 A% X5 k) ?$ ]: h) K6 W* W* N8 _
15
: j4 F' v/ H. a# {$ K8 w7 L G# `按天计算向前10个工作日窗口的苹果销量均值序列,非工作日的值用上一个工作日的结果填充。
) k4 [; e; W& X( l. g2 J5 t# 工作日苹果销量按日期排序1 E5 `( x6 t# l# F: |# r5 F! Z
select_bday=df[~df.Date.dt.dayofweek.isin([5,6])].query('Fruit=="Apple"').set_index('Date').sort_index()5 U1 R2 G. |1 k& y* c7 T
select_bday=select_bday.groupby(select_bday.index)['Sale'].sum() # 每天的销量汇总" z0 o6 |2 X+ b V, Z# I
select_bday.head()! K8 L B. I0 T" x) R
* ~. a& a: u! K
Date- j1 X" I! C( I; U" b
2019-01-01 189
7 p6 {$ x; `3 J! a; i2019-01-02 482
. Q' {1 e% T7 f$ Q$ H" B5 n2019-01-03 890
7 U; {7 `. e v' l( t2019-01-04 550
8 Z' x8 c( Y4 W' k$ ^2019-01-07 494
0 |, H0 v2 u X8 J2 y, m# k5 S( _3 [) i. R3 ~2 N
# 此时已经是工作日,正常滑窗。结果重设索引,对周末进行向后填充。8 o- k& Y2 m! Z' C
select_bday.rolling('10D').mean().reindex(df.Date.unique()).sort_index().ffill().head()& \0 x. e5 j& t" B6 T4 U9 l4 \) d
7 p, ~ D# c; r# i. ?3 o7 }
Date! x3 c7 f0 T) m' c
2019-01-01 189.000000% J: f8 ~' c/ u* T' a8 E" Y% L
2019-01-02 335.500000 [, C; _# |! O2 n ]+ u
2019-01-03 520.333333
9 a3 i& f1 n5 v2019-01-04 527.750000
# r/ R# ]( Y* r2 Z* r1 l2019-01-05 527.750000
2 T& r9 [! @1 [* d, f* l# {2 ~
( X5 |% z' X3 Y————————————————% k, {" \/ s# w; x. L) i
版权声明:本文为CSDN博主「神洛华」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
' R' X1 E6 s' b! W4 k原文链接:https://blog.csdn.net/qq_56591814/article/details/126633913. n7 B: {+ n8 W0 g
; M9 t9 G/ }4 M. U
; Z5 J/ m3 Q( ]+ n
|
zan
|