- 在线时间
- 1630 小时
- 最后登录
- 2024-1-29
- 注册时间
- 2017-5-16
- 听众数
- 82
- 收听数
- 1
- 能力
- 120 分
- 体力
- 557798 点
- 威望
- 12 点
- 阅读权限
- 255
- 积分
- 172712
- 相册
- 1
- 日志
- 0
- 记录
- 0
- 帖子
- 5313
- 主题
- 5273
- 精华
- 18
- 分享
- 0
- 好友
- 163
TA的每日心情 | 开心 2021-8-11 17:59 |
---|
签到天数: 17 天 [LV.4]偶尔看看III 网络挑战赛参赛者 网络挑战赛参赛者 - 自我介绍
- 本人女,毕业于内蒙古科技大学,担任文职专业,毕业专业英语。
 群组: 2018美赛大象算法课程 群组: 2018美赛护航培训课程 群组: 2019年 数学中国站长建 群组: 2019年数据分析师课程 群组: 2018年大象老师国赛优 |
0 Q" g* w# @' I2 }' t8 w7 p7 N
0 R5 |' R+ D. Y/ h1 ^! k c* a ^+ `2 F; U: S. m8 w% c
文章目录
* v6 F, _. m, x第八章 文本数据
8 v" t. G3 h' E! R; O. ?. a8.1 str对象
0 f* d9 L; L# ?. O1 _4 M3 H7 C" ~8.1.1 str对象的设计意图
4 ?% t- z \7 V) f0 h5 f8.1.3 string类型; H+ b. x+ g7 A9 ^. k& B, E
8.2 正则表达式基础
# S3 W/ A0 X5 t8.2.1 . 一般字符的匹配" Y5 G' y/ H7 y. _- ^9 [& u1 F" k
8.2.2 元字符基础4 I" E. j1 L$ H# ?0 |6 e& s
8.2.3 简写字符集( ~5 e: H2 W4 N% e! H# O8 T
8.3 文本处理的五类操作
: _" ]' g& W- W- f/ T) h8.3.1 `str.split `拆分
/ h; H: k+ @8 B: I! S8.3.2 `str.join` 或 `str.cat `合并" s2 l2 X& k* F% o0 ]
8.3.3 匹配
5 g6 e. z- L: X4 a# A/ p8.3.5 提取( E* W) d$ _: d/ h# t2 j$ |( `
8.4、常用字符串函数
+ E* p4 U# t/ j j4 t/ E; S( E/ J9 |8.4.1 字母型函数6 u( d) _( o: n# W: G4 b& ~& \9 R
8.4.2 数值型函数
, Q. a7 f. c$ J8 f; d1 J ~, v8.4.3 统计型函数; {1 P& n' D3 l
8.4.4 格式型函数) ~" Z+ c3 R$ X0 K
8.5 练习
$ M8 l. n) \" C! n9 g( {Ex1:房屋信息数据集
7 C3 q) C* _* hEx2:《权力的游戏》剧本数据集" A' R1 O3 u8 Z
第九章 分类数据
7 o- ^) j1 D! |. m9 b( \8 Q9.1 cat对象: ?5 S3 C' R) \+ m0 J# T9 A
9.1.1 cat对象的属性; _; D: Q5 M$ f. e" e6 w- n- X
9.1.2 类别的增加、删除和修改
1 [0 M! b) O* O- `5 O! [2 s9.2 有序分类
* p2 M% [# j1 t; ]# `* I9 N, b# c* @6 X9.2.1 序的建立1 Z# R$ |0 n V6 k) u! M, ]
9.2.2 排序和比较
4 h6 `4 u% \, Z2 p6 f9.3 区间类别
, W9 u$ s$ \2 Y9.3.1 利用cut和qcut进行区间构造
2 v0 y* z, E2 m1 Q( m! e9.3.2 一般区间的构造8 [- V9 P* H. x+ h
9.3.3 区间的属性与方法2 M8 z8 R8 W/ O5 Z- W+ W
9.4 练习
( `0 y+ C' p0 {- \( X8 tEx1: 统计未出现的类别
8 a( t9 f' M& h+ d' ] j3 ]Ex2: 钻石数据集
' I/ X9 @3 }; D第十章 时序数据4 T1 ]% S9 l0 ]+ Q
10.1 时序中的基本对象4 t* L7 L8 w; z( E+ y$ A" n: E
10.2 时间戳
4 c$ D) \. v; P+ p& V, G10.2.1 Timestamp的构造与属性
8 n5 N5 ^' T2 X0 T9 L- N1 z7 c10.2.2 Datetime序列的生成
( |: d, t7 ~/ p- [, C10.2.3 dt对象! p* ]! m& ?! S; X/ Z2 z! G
10.2.4 时间戳的切片与索引
, s4 V: ?$ M: x* O; \1 R10.3 时间差 k# {/ n) J- T/ y8 }" E# E
10.3.1 Timedelta的生成, G$ j; K% d3 h: v& [: q8 e
10.2.2 Timedelta的运算 C" w6 ~, ]/ U
10.4 日期偏置$ Z3 v5 w7 J6 C! y; R: R
10.4.1 Offset对象1 u' f: V6 y3 K& [" h5 o" f9 T! ^, c
10.4.2 偏置字符串
! T% ]' f( W6 G; u' ~- h6 E, M$ T- H10.5、时序中的滑窗与分组
& s: x( e' p' O; }3 r, P10.5.1 滑动窗口
- {' ^: G3 J7 j ^6 I7 _0 C10.5.2 重采样9 h. ]9 \0 \8 B/ N, O
10.6 练习
8 g4 S/ Q5 _; Q% f1 _% rEx1:太阳辐射数据集
4 p; L X. H& c9 o/ hEx2:水果销量数据集+ @ o; h/ P, t; a- ?6 s
课程资料《pandas数据处理与分析》、github地址、讲解视频、习题参考答案 、pandas官网
4 o' e2 \, x: d9 i传送门:
% o' g; N) e& k; I$ W. y4 F% z# f1 ~+ e
datawhale8月组队学习《pandas数据处理与分析》(上)(基础、索引、分组)
* |5 R: T, k3 u; idatawhale8月组队学习《pandas数据处理与分析》(中)(变形、连接、缺失数据), \/ F: t" p e7 _( d3 \- d
第八章 文本数据2 J4 V" y x9 V! J
8.1 str对象
6 o" y) U \/ v" y) p8.1.1 str对象的设计意图
Z# d2 X. D) J( P7 J: F str 对象是定义在 Index 或 Series上的属性,专门用于处理每个元素的文本内容,其内部定义了大量方法,因此对一个序列进行文本处理,首先需要获取其 str 对象。在Python标准库中也有 str 模块,为了使用上的便利,在 pandas 的50个 str 对象方法中,有31个是和标准库中的 str 模块方法同名且功能一致,例如字母转为大写的操作:
8 d7 e1 z+ w- i2 Y1 [7 p$ h4 h) h# z) [+ Z
var = 'abcd'% k/ ~7 g2 ` k1 O+ k4 @
str.upper(var) # Python内置str模块. f9 B2 I$ A3 R1 N8 ^2 m0 W3 ^
Out[4]: 'ABCD'$ U5 c* s3 O: N! `
; c* u1 {! G+ a6 q$ u
s = pd.Series(['abcd', 'efg', 'hi'])
- I' x) P- y# o+ X+ { O
* _% m t- m" J! b& t; Hs.str
8 r4 T9 P) }- h+ NOut[6]: <pandas.core.strings.accessor.StringMethods at 0x2b796892d60>
% T0 b5 l/ v0 a3 e5 S6 X
6 A: R& ]/ T0 _! }4 Z. H3 Gs.str.upper() # pandas中str对象上的upper方法8 y4 c" s, J: H- F( E- E8 E
Out[7]: - E8 x& w( q: d7 U4 h9 [- k
0 ABCD
$ @( V( q, {& ~8 d" Y- s0 k& f- M1 EFG
: x+ r6 N0 \* P; W: [; y5 f( M2 HI" U& U* R6 U* z3 g+ s
dtype: object
( i1 V, _5 ]2 t0 N- U1+ P b- x! L- @, m8 r& V
2
+ P9 ]5 X3 U3 m, E3
( o, T9 ~( R7 h! p5 Q4: s r: T& O: v3 g9 |
51 P. O3 {0 Y; [
6
1 z" t& S! [, x g9 s3 q( |! |72 X) B( x5 j; ]* v
8
7 y' l& K+ i" W' X& N# l# y. h9; d' N, |0 H' e* Q @" L& \
10
, \# w" C2 Z* x n6 p" }6 D6 Z1 f11
0 t4 ^" e* Z i) B' `8 J% u12! o: q3 K2 J9 ^
131 `1 i$ [4 a% W6 z# q) H
14 v5 M( C$ T% l
158 h- b g# W7 S! D0 a0 i/ |
8.1.2 []索引器
8 j& l1 i1 R2 u- j 对于 str 对象而言,可理解为其对字符串进行了序列化的操作,例如在一般的字符串中,通过 [] 可以取出某个位置的元素,同时也能通过切片得到子串。/ F- T8 Q0 E. ?6 E! j* N& w
pandas中过对 str 对象使用 [] 索引器,可以完成完全一致的功能,并且如果超出范围则返回缺失值:
s* Z* d, v0 P K5 j( w8 Y6 @
3 o& _- T( Y3 v0 [) C. d6 C5 {s.str[0]2 r5 [3 L0 a4 o/ J
Out[10]: 2 J. v4 s% I- r1 @4 F' x% ~
0 a
$ h, p4 K. M. ]- @8 K7 D1 e4 S9 i* A& I( W6 V& G
2 h3 C: ] c% |4 D3 o( P$ s9 \1 ^ d
dtype: object
+ h7 R/ P e* c$ a
& ]1 ^) b1 V J3 X$ k: _9 S$ ds.str[-1: 0: -2]! [* H0 g4 `, e6 C7 A! K5 e
Out[11]: # S, `/ s: y. w7 l
0 db
9 p( _" {. M6 d1 g
" |& [5 E* i. r9 V/ R' A: j# a+ i2 i: G$ R+ j$ D0 k! |3 A
dtype: object
0 E3 K- E$ p' {5 p
2 e8 m" h; Y; x+ W- P# m8 }s.str[2]
/ m0 i9 x* d6 g3 y9 w( c- Z& B6 EOut[12]:
- I8 P8 j2 ^# Y* I& k5 {. r% u0 c/ C1 m5 \; q, b2 w: p, C8 h* u4 ]# x
1 g4 s4 M, | G: X' m
2 NaN
! k" a4 b+ B8 t7 Z5 [dtype: object4 L. A: {' J3 s# L; i9 e" R
( }) I$ G* p8 t, d% c4 ~1- }, W! M7 w4 e- v8 J+ a
2
8 y9 g0 k" d0 f1 a! G1 m2 Q3
& i3 e3 L w! _4- Y: l* W! j" X( c# q0 }
58 w8 z: e. s7 q
60 V0 W q- Y' r% |# Z3 m, T
7+ b9 W+ f. c7 ?7 ^3 l
8
9 U$ U! G" e; l1 w; u3 s" ~9
& M+ I0 Z- Z+ ?4 c4 f0 X10
- }! [5 P2 B8 f. s/ w117 b/ d2 c7 p0 l2 O* H, ?
12
! v4 D' k) v6 K. r! H13& w' K$ ^6 G& j" K
14
! n6 E+ I5 ]4 x' b- A15- f$ c+ e g6 g; t+ c% Q
16: ?/ Z- Y1 g- w1 t
17
9 ?) n& M z$ m, D8 N& p2 L0 o2 W% J5 }18* q8 G. j; M3 `
19
( L+ r0 s, i5 m. p; `4 m3 l) y" Z204 g9 Q) A* I; h5 f) @' a4 Y; T
import numpy as np# {. I8 p' x; O3 ?
import pandas as pd
+ { j% {$ r4 v+ h9 y4 D% h
n8 P5 j9 D0 ]+ z7 cs = pd.Series(['abcd', 'efg', 'hi'])
0 V. F r \8 h1 O' a' os.str[0]% h, a: V1 e5 }& A
1
! w1 Q4 [2 m5 Y2( v; i3 z& M7 q8 q- ~
3
6 v: P$ V* f. V' r+ u9 R- P( L9 L7 u; Q4
! r6 d3 u# W! o% W/ {5+ s& S) p% T6 S m
0 a2 V; @+ ^% e: S6 i; l
1 e+ R8 k5 I6 c6 n2 h
2 h# N$ s3 n( \& u0 O" u/ s
dtype: object6 N8 t* H0 m+ Y' Y7 j- y2 v* R0 k
1
: v: ?- r+ ~! _% d0 ^. D2
/ F1 e! m6 d0 q/ O: {3
9 `# y- B3 q# ]3 |4 c5 w4 o2 W; U4, A9 R* j! f" S, j3 ?
8.1.3 string类型
! ?. }4 F% x3 x3 I8 v! Z q 在上一章提到,从 pandas 的 1.0.0 版本开始,引入了 string 类型,其引入的动机在于:原来所有的字符串类型都会以 object 类型的 Series 进行存储,但 object 类型只应当存储混合类型,例如同时存储浮点、字符串、字典、列表、自定义类型等,因此字符串有必要同数值型或 category 一样,具有自己的数据存储类型,从而引入了 string 类型。* p! R& K- v% y2 m1 I+ i& I. m4 U
总体上说,绝大多数对于 object 和 string 类型的序列使用 str 对象方法产生的结果是一致,但是在下面提到的两点上有较大差异: D1 p% X. n8 P: W/ ^9 I
: w' U' K1 K) x8 w m8 S" w$ F. g二者对于某些对象的 str 序列化方法不同。
; b# O* L" d9 r3 V# P可迭代(Iterable)对象包括但不限于字符串、字典、列表。对于一个可迭代对象, string 类型和 object 类型对它们的序列化方式不同,序列化后str对象返回结果也可能不同。例如:
4 x( t) D" j" `s = pd.Series([{1: 'temp_1', 2: 'temp_2'}, ['a', 'b'], 0.5, 'my_string'])
( B! X: A. _ _8 l4 us
1 H0 T2 G/ O+ t9 c6 |1 W0 l1
8 i& a+ r+ U9 G5 q. Y2 `/ K: V2
- r$ t: u1 T {, m2 k, S3 Y0 {1: 'temp_1', 2: 'temp_2'}
5 S" ^! Z" c8 V& [% z' f- |1 [a, b]9 u$ l \! R, A5 M" u+ v
2 0.5
% m7 @% `! N) C. R# m3 my_string
+ L$ f6 T: i. F L; s, D! R' M& Ldtype: object0 p! ?# ^! ^; w& ^! _1 z1 X1 n
1
7 U+ g1 J* _2 \3 Z6 {; ]* ~2
6 b) v' S0 F5 z35 v% ?& u- \# {% Q
4
3 w1 i9 U7 P: c/ y( O5
J9 F2 ^. f9 K" p% Rs.str[1] # 对每个元素取[1]的操作
( Y& e2 w$ r& r4 \. m6 [1
; l# P0 J" ~$ o' P. |: M0 temp_1
0 J$ H3 S% ]% |1 b
. e0 q% n5 Z% Y9 G3 L/ L5 r2 NaN
& B7 M: ?% r3 O- E) ?3 y
- F5 F. J" `1 [& W mdtype: object1 I/ C- N0 R7 F4 F) w% U/ S
1+ p h* d: N, T2 w# \5 V3 B/ ?
29 I( ?; f d5 u' T0 y& i' A4 S
35 X. {1 i6 x, v9 @( z- i
4
5 G8 C1 K7 w% C3 r7 \2 t5" W5 D3 T" i5 n4 w% N- D) {
s.astype('string').str[1]$ p# l2 ~" Q* x( ^
1" m* q% H2 i( y' k1 Q& G/ ?
0 11 N- _- e1 u0 s5 A k
1 '# a- I3 I2 C& s/ O5 e; @
2 .- g& b b- u& q" b1 p! v+ G* ]
3 y; S B* \) W) @+ D) q( c1 U( w
dtype: string
9 b% p" T% m9 D# Q7 m8 t1! |$ ^6 s& k+ r6 Z7 ~
2( y! h% J6 o. m/ f7 ]% E" h' E
3
6 U C* f' }! \' D4& W( o( U9 _% L7 h
51 P( I& V8 H/ n B; q
除了最后一个字符串元素,前三个元素返回的值都不同,其原因在于:+ x+ L0 l6 i, [/ t2 b; b
6 j6 K; R% d+ Y当序列类型为 object 时,是对于每一个元素进行 [] 索引,因此对于字典而言,返回temp_1字符串,对于列表则返回第二个值,而第三个为不可迭代对象,返回缺失值,第四个是对字符串进行 [] 索引。
0 b, N" z5 q; p& ~8 ?; @. Mstring 类型的 str 对象先把整个元素转为字面意义的字符串,例如对于列表而言,第一个元素即 “{”,而对于最后一个字符串元素而言,恰好转化前后的表示方法一致,因此结果和 object 类型一致。
9 o- W0 ?+ `3 N7 A' F. ostring 类型是 Nullable 类型,但 object 不是
# M# h; `( V& Q5 W3 X9 ] I 这意味着 string 类型的序列,如果调用的 str 方法返回值为整数 Series 和布尔 Series 时,其分别对应的 dtype 是 Int 和 boolean 的 Nullable 类型,而 object 类型则会分别返回 int/float 和 bool/object ,不过这取决于缺失值的存在与否。8 k2 m4 X4 Z+ c7 L
同时,字符串的比较操作,也具有相似的特性, string 返回 Nullable 类型,但 object 不会。0 R6 s3 g- i. U* g0 ~; s2 K
s = pd.Series(['a'])
7 B w6 J$ h3 [' h
9 O8 s; B; N e4 e& |0 C, ps.str.len()
) T: f" M3 G) i4 `: qOut[17]:
' n8 A% h1 l2 h( i0 1& Q. y/ l9 {* x
dtype: int64
: D, Z* R2 c! ]% O
6 N% ^0 Q3 |( v V8 Q4 K6 Rs.astype('string').str.len(); _& ?8 o1 {# q. ^; O0 y
Out[18]: . X# [, `% B; u! J. l; ^
0 1
5 y; b/ g0 H5 k& G8 xdtype: Int64 X' Y: s. n% ?1 j5 k. a# ^
% ?- q7 G1 c$ @ t' ]$ k7 @5 [8 q Ms == 'a'
; Y4 b" X: c2 p4 x" LOut[19]: ! |8 s! N; d, j5 F: R- i* b
0 True
2 m0 v Y( ]( ]. m" Bdtype: bool
! D, ]. E$ ?/ r! G, h
! w5 l6 O1 Q) @7 _5 N/ c- ]s.astype('string') == 'a'% F1 W5 w+ M/ B
Out[20]: + ]! v! C7 ~# ?4 E+ d9 Y v7 {
0 True
7 B _% h$ J2 s- l1 O" Fdtype: boolean
9 q% s" r( ]2 a, T* h9 u* G4 J8 U8 K
s = pd.Series(['a', np.nan]) # 带有缺失值
- [# s' Q. m1 p% c; Z( I& R2 A5 z2 Z5 @
s.str.len()
$ Z M9 |( [ v: G7 M2 i7 v, BOut[22]:
0 W& R- C2 e C ~; @0 U0 1.07 V6 H! h3 i" x; ?+ q( J
1 NaN
1 r) {! ]5 o0 ~) G( ~& e% K0 @7 X ndtype: float64
3 s3 t" K+ t: Z/ v6 Q ]5 l& b- V4 b* ?
8 T* `" {: w. w. g# b9 ss.astype('string').str.len()
5 S' S1 w) X" U( }* x" _4 c$ YOut[23]:
) K5 I1 N9 m' `3 Z0 1
6 s+ Z& E7 c1 _( l! e, s1 <NA>
$ M5 u7 c! H( ^* P( Q- _0 Fdtype: Int64
, ]! m# W' P% H: Z; K- u( M) l! `6 t9 W" i8 F( L3 m
s == 'a'
& V) d" k6 Z! s4 G/ p$ r( nOut[24]:
G, N+ S2 X5 W' e0 True" L" H3 w T1 ?/ [# A, z/ _5 U
1 False) k# ]+ _) h' {1 o+ c# X
dtype: bool
% T) A/ @0 v$ ^1 R5 q
% a$ O6 C! B% Q8 Ds.astype('string') == 'a'
6 [0 k5 A! W9 ]/ K' a5 G8 w XOut[25]: ^% E, p8 h3 e/ ~, m
0 True0 ?# a" R+ z) X" `- `
1 <NA>1 i3 r* i; a$ |, _$ a, R
dtype: boolean
9 f+ F4 i0 C: l! O: _
h3 r8 K' w$ V) }, I( c( W' s1, L0 N0 s5 f* d% a& @
2
2 C5 b5 M3 n% {/ x+ D6 u' F! S3
! @9 i4 _" g- ?; z4 i0 j: k4
1 U9 v$ G$ L5 H+ \( [, ]( [5* W* X- d: H! L2 l
6# h# F, W4 b5 O+ E
7 Y/ S/ X9 [% d5 p+ r
8
+ j2 w* } g9 |: l% d9
0 ]6 A3 M& z6 ^2 V6 F10, I$ \" \6 x, p: O$ k- P8 }: w, J
11
6 Z: V6 [7 M, F) K2 l12
* C, G! S- f2 R, u13" u2 V, g1 ~- t
142 D( ?1 N& T% }* y$ B4 j1 {, B
152 I. q% n. G0 a ?7 L+ R
16
, Z3 A5 {2 M# T$ P2 H17
, g: \$ x& I( f% w7 u# v18
& o* d8 D/ S8 N4 `$ H193 ]" a9 Q' {8 t" Y! b
20
5 o% L3 X E, E5 N8 p* I21
+ ?) v+ Y7 T# s5 _3 h22+ Z) Z( k: R& R5 s" y7 N+ A
23
+ K) m) U5 Y$ ?) Y% p4 w1 j5 e24( t* G; Q. N3 A# R" d
25
/ J' i& c* Z" I$ Y d# t26# R4 t. ^0 D4 x6 w* J
276 N, }/ r8 |5 L+ t7 g, {; M: l+ ~
286 j; K2 X7 S2 E
29
. E1 l3 `: I2 t: w303 R2 v. I. W# o% C6 g& A0 a) R) A
31 j. r. {% y9 w8 \* @# G
32
8 _( e: g4 H6 U5 z5 ?1 Z33& Y4 f# Q I, Y4 L
343 |4 g4 O( v6 V W" I0 ?, s
35* [% |' b- w7 D, z, |$ K$ I
36: _ z3 e; y! d
378 L* l( {* i6 R7 ?% \3 t$ L
384 `4 }& K4 t- D& d6 C/ w0 d
394 O; B7 F* z( G8 A; S" k0 A
40
5 D6 x& D' A n- ^+ o41
7 H" g8 t, C/ ?428 m5 t+ E D8 l8 E
43! f+ {- \: r4 e4 S% V
44
]+ h) O* j6 U D/ j4 {0 k45
" a: Y; O+ a1 w" F+ Z) h46; B- B# ]: O6 K$ x: n- j1 _
47
. ]3 y" [2 ^; j( B) \9 M 对于全体元素为数值类型的序列,即使其类型为 object 或者 category 也不允许直接使用 str 属性。如果需要把数字当成 string 类型处理,可以使用 astype 强制转换为 string 类型的 Series :
$ B7 h8 a6 X- g8 r7 ^( ?6 j* t+ g. \4 O0 i( y
s = pd.Series([12, 345, 6789])
2 U+ U2 c$ h& l; L! X& I5 v
7 J' b `3 P% F5 o; _, bs.astype('string').str[1]. `# M3 O0 A' _! D
Out[27]:
+ V' Q) z7 A; h' ~, Q0 2# T) {+ \. C* T2 P
1 4
2 Z: o/ L% W O2 f2 7
4 M6 C8 _; k) g9 |; K& j. f& f8 Q; Ldtype: string) X: X/ @7 _" C% [
1
3 z0 g' u6 e7 d2+ s+ n& B% n& l5 Z) E1 N
3) Z. ^1 }3 C) h% S* V+ ^
4& t$ V2 T, e. e6 d5 n8 U4 b) {" l. G, f
55 j/ E$ q$ `; }6 o f2 L
6
L7 n7 d$ c! D3 q1 r: `# ^7
" D8 E; @/ t) Y! a i- ]8) w2 E7 S1 ?% W1 `5 _
8.2 正则表达式基础5 H$ m9 T5 L' u
这一节的两个表格来自于 learn-regex-zh 这个关于正则表达式项目,其使用 MIT 开源许可协议。这里只是介绍正则表达式的基本用法,需要系统学习的读者可参考《Python3 正则表达式》,或者《 正则表达式必知必会 》这本书
9 ?1 X& r3 G7 t R
! Q1 h- `2 d( q; _8.2.1 . 一般字符的匹配7 [1 E; K$ H& ]1 d+ `% C8 Y
正则表达式是一种按照某种正则模式,从左到右匹配字符串中内容的一种工具。对于一般的字符而言,它可以找到其所在的位置,这里为了演示便利,使用了 python 中 re 模块的 findall 函数来匹配所有出现过但不重叠的模式,第一个参数是正则表达式,第二个参数是待匹配的字符串。例如,在下面的字符串中找出 apple :
9 V1 ~! G4 v! b. w# C/ `5 G% l
import re
' L0 }2 {- c2 v* a: ?. v& Q
- I) `9 [8 S C" Ere.findall(r'Apple', 'Apple! This Is an Apple!') # 字符串从左到右依次匹配& N. w3 i( p0 ^; e! v
Out[29]: ['Apple', 'Apple']
2 M* [, _, e$ }3 l2 C1( S F* {6 `+ w! [
2# L6 o5 D+ n8 P
3# k/ |, S$ R1 F) y- v0 D( i
4
S1 {/ N5 R, H3 o$ S$ G! I2 j8.2.2 元字符基础
' G( }* w) ^9 S( l" j) P1 w元字符 描述( H6 K! T4 D! a5 m
. 匹配除换行符以外的任意字符* b5 w/ F8 V( q4 L
[ ] 字符类,匹配方括号中包含的任意字符
7 s1 F# W. u1 P. Q; v3 Z# \7 G[^ ] 否定字符类,匹配方括号中不包含的任意字符
4 L- R: Q+ [% B; Q; A0 k" O* 匹配前面的子表达式零次或多次
% W8 L# w0 b1 Q( s5 o$ m+ a6 j+ 匹配前面的子表达式一次或多次。比如r’d+'就是匹配数字串,r’d’就是匹配单个数字
. [ S" e; D3 z3 Y4 g? 匹配前面的子表达式零次或一次,非贪婪方式
* e2 O1 k7 Q2 Y/ \8 o6 b{n,m} 花括号,匹配 n 到 m 次由前面的正则表达式定义的片段,贪婪方式
5 w. n0 l5 p0 m" G2 R7 h& R. C5 L(xyz) 字符组,按照确切的顺序匹配字符xyz
+ `- F/ Z$ i7 A# C# M3 s- v| 分支结构,匹配符号之前的字符或后面的字符
: d' J) z% m) I% c1 D2 L5 G' ^\ 转义符,它可以还原元字符原来的含义1 u( `7 h: U+ t$ f1 W
^ 匹配行的开始
' g! `: D% X# I0 o2 B9 S' @ Q4 c$ 匹配行的结束5 U$ k( O* k5 |" C$ w/ l
import re
) p- X0 q( h/ L7 R- Gre.findall(r'.', 'abc')
6 Z+ H/ B! @) ?Out[30]: ['a', 'b', 'c']
. L* X/ v6 D( Z& Z% ?8 H# y
4 x4 a5 K( G1 U- u! s# I/ y, ere.findall(r'[ac]', 'abc') # []中有的子串都匹配* y* u2 s5 q2 O- B
Out[31]: ['a', 'c']
# t4 x6 V# v% f0 g# x* k- [4 m- @8 a; L' v
re.findall(r'[^ac]', 'abc') # S# Y' m7 q; A$ J0 f# N5 y) U( _1 Q; N
Out[32]: ['b']
& M/ M/ x K9 ^& R) y' l! j
$ B: P& _/ j0 k. Jre.findall(r'[ab]{2}', 'aaaabbbb') # {n}指匹配n次, O7 [* {, a8 c/ l3 k
Out[33]: ['aa', 'aa', 'bb', 'bb']+ f) F* s$ N0 @! {8 r
5 |9 \' S; {1 A9 q0 g% B
re.findall(r'aaa|bbc|ca', 'aacabbcbbc') # 匹配前面的或者后面的字符串6 X) y+ a& U! S/ {- U9 k
Out[34]: ['ca', 'bbc', 'bbc']7 k, W) [* L% i; q4 u3 o
/ h$ v- R& C K3 A3 F( @) n0 b
# 上面的元字符都有特殊含义,要匹配其本来的意思就得用\进行转义。
5 x4 ^% B& U- [# S F' r, L"""2 ]$ R( p9 y9 t' z' Z
1. ?匹配的是前一个字符,即被转义的\,所以|前面的内容就是匹配a\或者a,但是结果里面没有a\,相当于只能匹配a。
# a$ V/ W7 P. l/ C, M2. |右边是a\*,转义之后匹配a*,对于竖线而言左边优先级高于右边
8 S: F5 W& }- Y( K& T3. 然后看目标字符串aa?a*a,第一个a匹配左边,第二个a匹配左边,第三个a虽然后面有*,
/ f& @, U$ [" }/ e& r但是左边优先级高, 还是匹配左边,剩下一个a还是左边,所以结果是四个a
! d5 M: N5 a+ r6 c5 f; p"""
/ }4 J0 N& K8 o) O8 i# |* ?$ r8 e7 |# }# ]& b1 e# x( |
re.findall(r'a\\?|a\*', 'aa?a*a') # 第二次先匹配到a,就不会匹配a?。a*同理。1 o+ J9 [, m5 z# }
Out[35]: ['a', 'a', 'a', 'a']6 o/ w* p0 p* j7 q# C
7 Z5 ?# b4 V& N3 P9 n& j
# 这里匹配不到是因为目标串'aa\a*a'中,\a是python的转义字符(\a\b\t\n等),所以匹配不到。6 ^& G* g7 f# |
# 如果是'aa\s*a'之内非python的转义字符,或者'aa\\s*a',或者r'aa\\s*a'就可以匹配到\字符。
' n7 z1 y$ I7 M/ B) Jre.findall(r'\\', 'aa\a*a') 3 e' W- }% T3 z& E1 v5 ~# F
[]
3 e! \7 }& x) Z$ @, X5 O- X
2 C: y& o' T4 n% V* e% bre.findall(r'a?.', 'abaacadaae')6 I* P1 X* b$ u& ~3 Y
Out[36]: ['ab', 'aa', 'c', 'ad', 'aa', 'e']
5 K2 c0 I, D9 C7 f3 r3 M% f1 P/ I
' {/ k R) P& m! q F, n% u) n5 [re.findall(r'(\w+)=(\d+)', 'set width=20 and height=10') # 多个匹配模式,返回元组列表
& X J) D( u9 L+ w[('width', '20'), ('height', '10')]( v6 y6 j! Y% E5 o7 ]7 W3 c$ V
/ `4 g* }- r) L; M7 Z% N1
3 k$ C7 U4 _+ x& }24 q- x# X- @3 _0 ~! ?: u
3( w# W9 W0 w0 \# V6 r; ~
4" X q" f3 l7 \, F3 J$ x( @: i3 {
5
8 @' ^7 M8 _; a( {9 i! [6# {" Q; c3 a7 M- A8 }# B
7* U! Z# `. s8 |2 k# c: v; K7 l, x
8
, a3 j- z/ \" N- w: } k7 n9
\1 k) l2 C3 T10
( L4 s1 J: K2 W$ P11
1 ^8 ?$ I, O+ v# Q, L+ ` ^- [12
+ j0 u8 L/ v' B% u8 T9 @* H6 n0 R13" _. \ U7 {) w# J0 M. [
14
, G Y. {# g- P; j9 P6 E$ F: _6 y# O151 Y; a0 ?& w2 [( Q
16* s7 A4 X h, z0 K' a) N
17
$ T2 ?- ~. ]( G( m18. Q) v9 ~8 t! G! [$ U& h
199 d% a7 f4 h6 W( N* F4 Y1 T+ {6 ?
20, k: ]7 O- y: ^: X) C- K8 c2 [/ d
21+ b; b9 _) `4 r2 n# \9 t
22
( J; }' j% M8 P' b# u6 P23! b8 f0 `* M# K; j: Z# a
24
# X( c2 T& G2 \3 U* c, @, @) Q25. F) r3 p: @ ~( K: t$ p- q, p
26+ I2 k4 E2 e2 G2 }: ?" \, v
27
' N5 G" n3 K$ I# l, p7 Y+ }28: e2 y C& d5 _1 O
29
1 z9 i+ ]; r( C$ ^; _30, F9 c" h% A' Q; H* _
31( D8 l. p6 j* o1 Z( r8 {. K9 r* E
32, m" M! ^& o8 A: p
33
6 r' ]! D! T1 A2 f2 P34% S5 t/ e8 R- x, [( E; C% m7 [
35
3 ^: k! n3 [: t' V3 `36& g$ X, B( O8 i- ?9 t; c) ]
37# \ d, O F# H, c
8.2.3 简写字符集
& A, e) _! c1 |8 j @4 j8 i) P则表达式中还有一类简写字符集,其等价于一组字符的集合: o6 H8 n0 e* l" [# K
: w5 O. B$ O2 t/ G# H2 ]3 R; h2 g简写 描述
% y+ C! `5 h ^: W! a5 E\w 匹配所有字母、数字、下划线: [a-zA-Z0-9_]6 A0 n6 p, Y* E6 t' w b! Z
\W 匹配非字母和数字的字符: [^\w]+ K6 o: \- I3 o
\d 匹配数字: [0-9]- l2 x$ H% O) k* _/ {- S; l6 B, q6 C
\D 匹配非数字: [^\d]
& T( A0 r+ F9 E/ E M, H\s 匹配空格符: [\t\n\f\r\p{Z}]
; M* L! I! |9 h" i0 A; W# v\S 匹配非空格符: [^\s]1 Z2 X5 L" d( K5 o7 [$ q
\B 匹配非单词边界。‘er\B’ 能匹配 “verb” 中的 ‘er’,但不能匹配 “never” 中的 ‘er’。8 s1 H0 L- F$ |" ]" {* z6 k' G
re.findall(r'.s', 'Apple! This Is an Apple!')) q f; |4 ^, g' O1 h: J
Out[37]: ['is', 'Is']
% O* [( y% ]7 X+ `2 o" B: e! H: T- v5 X( |2 f7 w! W
re.findall(r'\w{2}', '09 8? 7w c_ 9q p@') # 匹配任意数字字母下划线的组合,但必须是两次
: a; X1 [- Q# q6 r8 fOut[38]: ['09', '7w', 'c_', '9q']
% Z9 a' R: A z9 k, i; Z |) k& n8 S! z
re.findall(r'\w\W\B', '09 8? 7w c_ 9q p@') # 匹配的是两个字符串,前一个是任意数字字母下划线(\W),后一个不是(\W): a! Z l8 t6 ?& G2 e1 r6 x
Out[39]: ['8?', 'p@']
1 V( R$ p' r0 i9 t& M- `: N/ m* ~$ }7 d, X5 B5 F! Y U
re.findall(r'.\s.', 'Constant dropping wears the stone.')
4 T5 x3 B3 D: |' b* f! }Out[40]: ['t d', 'g w', 's t', 'e s']9 [% u2 J/ X1 U R+ [4 `
6 `, I" r3 [7 L' }2 s8 r* v n$ ?re.findall(r'上海市(.{2,3}区)(.{2,3}路)(\d+号)',
3 O- P* C' r& G# X+ S '上海市黄浦区方浜中路249号 上海市宝山区密山路5号')
1 i( q n" O! N: ~: |, F* p' x* S- s
Out[41]: [('黄浦区', '方浜中路', '249号'), ('宝山区', '密山路', '5号')]
! J& a% c5 \& f; X+ b, a; C3 r0 k! J% @4 U/ N9 x
1
/ @3 Y Q: f) {3 k* E2, y) o1 [* J* y2 I$ A
34 E/ x5 N; d7 }7 f+ j7 J6 `% ?# d3 z
4
6 @6 s* Z0 a: x9 ~54 I2 p* W! a: c4 {6 K4 G
6' K& o1 k/ @" A$ y" }
7
7 a% x& P3 p/ g7 z; \7 T& o8
" E) q0 {, \. a: [' S7 p9
% F5 ^! I! |' \7 S# f+ b1 D10
3 _; H. d$ z( R1 m& h3 v111 r+ K+ [! ^* L& _' k" w& S
12
0 B+ H' q% J$ G! O7 u ^13- P% Q) c0 t4 T9 I T s1 i
14
; o; |' }2 I9 n) H- ~. l7 ?15
8 u% z ]" s3 q16 C; F4 _1 ~9 y# M( |/ f
8.3 文本处理的五类操作# E- H. }1 P& c' g5 r' b
8.3.1 str.split 拆分) u1 m( m+ _1 Q! F8 v2 G
str.split 能够把字符串的列进行拆分,其中第一个参数为正则表达式,可选参数包括从左到右的最大拆分次数 n ,是否展开为多个列 expand 。1 C% s* I$ A* P! ]9 M5 `+ @
7 ?/ d8 A- k/ H0 g# \s = pd.Series(['上海市黄浦区方浜中路249号',7 K- D9 @( x5 O( X
'上海市宝山区密山路5号'])
- G1 q/ P- i N' D2 T* O" p1 R3 I, x" Z, \2 {1 d1 ]9 \7 _. j
, C8 b* J% v0 V8 A9 Z6 f
s.str.split('[市区路]') # 每条结果为一行,相当于Series
, ^" s# d& x& Y# E' F) C, zOut[43]: # i4 h) c# _- ?5 s$ R
0 [上海, 黄浦, 方浜中, 249号]" T# X% J/ x, _( n) U# P' ?
1 [上海, 宝山, 密山, 5号]
+ U- X& E3 y: X% i7 ~ Vdtype: object0 l7 s; V1 J+ \
# G/ `" a( e4 K+ n3 h
s.str.split('[市区路]', n=2, expand=True) # 结果分成多个列展示,结果相当于DataFrame
- A1 l! M) W' H& N2 [; _, ~! jOut[44]: . h& X( U: ?. K, a& P" Q, r. D1 l
0 1 2
, o4 Z8 }# q; j0 F0 上海 黄浦 方浜中路249号3 c5 y8 p% g! W; H
1 上海 宝山 密山路5号
5 G/ w2 N9 ^. r+ D: v1( a2 P) n9 e# \, _0 D8 Z
2' U6 j* }+ P J
3
! `0 M1 G' R& C2 L: h+ j# R0 F; v4) C, }, S( R/ m) {
5
# K3 r, \ ]9 {9 m6+ Z# t% i" M1 ^' t
7
# b, ]( G2 T7 T0 g- c( C: {8
4 s4 r7 x5 X" ~* }9
% v4 e* i' ]" \# a5 d10
6 Y3 E; n0 v0 C/ d, r# p ~11
6 d M5 `0 S! U0 l12& J* ?5 v& D7 V; m! t
135 E3 ~7 y; ~2 C
14, V* t; K0 F& E0 b! N
155 g. A! |- A' u5 C
类似的函数是 str.rsplit ,其区别在于使用 n 参数的时候是从右到左限制最大拆分次数。但是当前版本下 rsplit 因为 bug 而无法使用正则表达式进行分割:: N, q: T( A5 ~7 H
2 J. _. C# F1 L- P$ G* [s.str.rsplit('[市区路]', n=2, expand=True)0 S. w/ X( z, T8 x- R# a
Out[45]:
1 G) F- C8 L, p8 J w* a- T& Z 07 h/ s) c R# ~+ ~/ G
0 上海市黄浦区方浜中路249号) L8 n8 j# @* d e' o2 c! q
1 上海市宝山区密山路5号
9 n$ c2 t" _, _5 m5 q0 b( Y1
% W/ q- X; O' {; }; T0 g) }8 C2
" H9 I. T, h) Y3
- R& N0 t) y6 p# h7 E49 e. x9 ~2 L- q U2 m* B
5 }& w& C, H# G2 w1 B4 e/ \
8.3.2 str.join 或 str.cat 合并2 C0 o8 G+ i- q7 k, R8 E# U: G
str.join 表示用某个连接符把 Series 中的字符串列表连接起来,如果列表中出现了非字符串元素则返回缺失值。
& V/ F5 P" ] O7 o9 c$ ustr.cat 用于合并两个序列,主要参数为:: A% t7 z) ]$ o* F% O( ]% m
sep:连接符、
8 b |5 A5 |" pjoin:连接形式默认为以索引为键的左连接
+ l4 y0 N6 ?. F! Kna_rep:缺失值替代符号
. O e" C( e$ {" |; ys = pd.Series([['a','b'], [1, 'a'], [['a', 'b'], 'c']])
% e, I+ {0 |4 B: Ds.str.join('-')
: @- U! d2 A1 O7 V' iOut[47]: % I+ x8 F Z! g$ E( Y1 e8 A
0 a-b
: a2 N4 X ^& y$ ]8 p& K0 ^1 NaN
( j/ K% p: e' b8 Y: h* P2 NaN1 E6 @0 k0 z0 h
dtype: object6 J8 z* g @. c& V: p. U ~
10 [# r, f j* T9 Y. T/ \0 C
2
4 z* {" z! f, f# m3! P) l1 t8 w' f7 ] b
4, W& E' |( \) B6 F" v( b. C7 {- w
5
2 S: [7 c. R0 i" m3 r+ G4 f60 G# Y/ t3 n Z7 `. P( r% D# G
7
9 F% P! Y# v& g! R m. z' ss1 = pd.Series(['a','b'])4 @5 W. A0 K( m0 G& o9 _- \
s2 = pd.Series(['cat','dog'])9 ^9 J( P# t: S2 K' ]! X8 j& d. \, P1 F
s1.str.cat(s2,sep='-')
' D, ?4 S0 d* s7 e7 F; y7 xOut[50]: & c+ Y# h$ j8 k, a
0 a-cat
; J( \: n1 q, }) u v1 b-dog
$ |- S* K4 n( d& r" Xdtype: object
5 W0 R' I* {2 ]7 q; g6 R4 x E o
& [% e9 x, _/ x+ p) \& C1 os2.index = [1, 2]
. r* k) s+ ~: o/ q0 ss1.str.cat(s2, sep='-', na_rep='?', join='outer')
( M- V6 h3 A, B6 ZOut[52]:
' Q0 u! c9 |' J1 }6 }6 X# B! {0 a-?
1 o/ Z/ J. c, d3 x3 x9 a/ F7 y1 b-cat
3 r6 E5 R2 d- t/ p* P& K y2 ?-dog
y% V) V6 H* S- \3 e( Idtype: object
* f; Q/ S( Y1 L' i) H+ ]17 |: }: M8 G; K' L' N& q4 h1 v/ W
2) U6 V _+ D( e, m/ U6 ~9 F
3; f( l8 o4 t2 Z d
4# f( k) f3 j$ f
5. V5 K0 f2 q- e5 f
6( h( A: @3 g# {- [. S
7
( k& v5 B( H& q. p# P8 b88 J% g% j) [1 O Q9 c5 c9 y- `
9
7 H* Z( t0 D+ Y6 q8 l104 w l$ [. k+ Z# x
11
) G) F# i: }3 @12# o9 c( S, @# [
13
# S, ~, P( R+ F5 a. t$ z3 x14
8 O" C! Q: ]8 U15
+ Z& D4 c. U. p! z& _8.3.3 匹配
$ L7 B4 J1 G9 s' A0 d: ~6 dstr.contains返回了每个字符串是否包含正则模式的布尔序列:) N% M9 X: ^7 a5 [1 o( `
s = pd.Series(['my cat', 'he is fat', 'railway station'])& r3 f: R& G& g! M! h0 e9 J
s.str.contains('\s\wat')
2 G3 [0 C& P! k8 X( T3 h( B1 M2 ]* Q7 j N
0 True
/ n, n3 n: J* N8 S" E/ c1 True
; F9 B3 N3 o2 {( u+ T2 False: B0 O- r* M& K' u
dtype: bool9 l* q/ I" Q5 U/ m) R7 v
1% E1 p. }/ K2 |8 T, ]9 N
2
, z3 }# O: A$ w2 F& Y9 u& a3 M) n( N3
5 f3 x' S0 r" U" z6 P1 C1 {4
, k. n; c- @7 T A2 [! J5
- h: v2 T0 w) D# @67 e! O0 r& D8 ]6 ?1 ], b1 ]
7
; T- k# W- E" O+ g6 S. Z% p$ sstr.startswith和str.endswith返回了每个字符串以给定模式为开始和结束的布尔序列,它们都不支持正则表达式:1 ~- ?9 y# Q1 m( Z- a& \4 f0 ~# M G
s.str.startswith('my')
3 w. U7 f% b: m b0 T) q% h" l& e" l- P* j+ z6 r4 h
0 True
' L$ }) q! |! a5 f. n+ e$ a1 False
3 u: t* y6 `0 K9 [0 q$ j2 False$ M. t& P8 w& c1 e
dtype: bool
( A6 Q `7 |% k p: p! W1. G( b9 N+ y. @+ ?0 Q- u
2' F/ ~8 K/ ?+ j" `% C8 @
3. [( _0 b" p1 k: E
4* o- M, ^# r' T2 U0 F4 b' Q2 {
5
& e% j- Z Q( Y& J65 Z6 S* b& H$ u+ H
s.str.endswith('t'), L% i4 A* V4 R3 u* i
9 R" m% h: [: D0 True
M. X' x, c$ }" W# {, C8 O1 True
+ \4 c$ R' F& r+ ~: M: ]2 False9 z8 b2 w# L; q
dtype: bool
) N* M- H8 e! Z/ u7 j1
1 C+ @5 i% f7 [3 g! d8 \% {2; L( m1 s/ u! C& t# P ~
3
. A8 L) w# m2 f" n H3 t' c# X45 A! l8 W: I- \: x
50 H8 O3 g& T% W5 m. V
6
) u! S2 q; q' \5 f2 q" `6 estr.match可以用正则表达式来检测开始或结束字符串的模式,其返回了每个字符串起始处是否符合给定正则模式的布尔序列。当然,这些也能通过在str.contains的正则中使用^和$来实现。(貌似没有python里的search方法)- C6 I/ f# K3 j6 n" `, c2 L
s.str.match('m|h')
" S8 H7 W$ F% K9 ps.str.contains('^[m|h]') # 二者等价
( E( h" A/ O% y
2 d4 j V9 b* E0 True
3 P2 E9 F" E) R3 b1 v4 L1 True" c4 w2 x$ h6 i9 Y, F3 b' ~
2 False/ { |( }# y+ F& I( ]; C! O9 a
dtype: bool# r6 l, Y6 C) y) K3 G3 t; u3 m
1
5 u0 y, ^/ H0 t+ v2+ }7 n! E* } i4 B
3: c R) t: _4 I9 j6 y* B2 x! x* J
4
- {4 F S- A' L0 A50 R4 ~7 r: `' S
6
/ r1 {- V6 O% P: x" x0 c' B7. K! g8 H- W; H5 b0 S3 |
s.str[::-1].str.match('ta[f|g]|n') # 反转后匹配
0 `( o7 A) L, H1 Ds.str.contains('[f|g]at|n$') # 二者等价' P p2 d/ M* d) K& {8 f* W S
" O9 O6 f2 N. A! B0 False9 a, p3 T r* _. N* X& P# e% `
1 True& C$ O: s9 F2 ~9 o0 j( z
2 True( Z3 L3 x# N, K6 I. p
dtype: bool- [/ y: ~3 H) J; q0 p) O
1
3 E" n/ R9 c/ Z2$ D0 M% L4 F' W& ?
3
5 N; w+ G1 M9 F( U- \4
$ H. u& E6 C! y- _5, H$ v v9 L' ?% L7 P
6& v7 ~) W0 T: K; [: _& R6 s
7
& E4 o. `" Y1 x0 N4 Xstr.find与str.rfind返回索引的匹配函数,其分别返回从左到右和从右到左第一次匹配的位置的索引,未找到则返回-1。需要注意的是这两个函数不支持正则匹配,只能用于字符子串的匹配:
" m8 K9 U$ f8 _ {0 Q$ O- hs = pd.Series(['This is an apple. That is not an apple.'])
- L0 {: H2 Y* B
9 u- V+ i1 a1 K9 u+ Ss.str.find('apple')) x5 }; s2 `5 |
Out[62]: + G, i; v, ^" ~1 T9 H7 K0 |
0 11% e. |8 E0 k: q1 c* l# A
dtype: int641 ~9 `, ]0 d- M8 R
4 E h8 H0 k! D( `- ?
s.str.rfind('apple')
0 E+ x+ d4 k& u* a# ^4 k5 n; @Out[63]: + E, q7 P9 B+ K7 K' ~' l" F9 ~
0 33
1 t% J$ n7 n/ |4 Y% v% ~4 u- ~dtype: int64
+ n5 W L! O3 v/ ?1
9 Y6 c Z7 p# O8 z- q; P2' P: w) \& K, m: S4 k
3
+ a* f% B6 Q( r2 S! r3 B8 s. s; ?5 R4) s. n3 {- C) Z4 F, r8 ^
5
, Z9 x7 l) x& x/ i: x* i2 ?& I; y6
b. L% E6 |! f5 v7 a7$ q& w* M" M8 e. F% d' O% q/ M8 p
8! J- S3 _3 u/ x b
98 W T. d+ ~2 T2 c3 H4 C B
10# F2 R3 A) G5 n2 H) x8 U" s
11
* _3 `) E5 Q$ r/ t: D% e) ~( `4 A- F替换6 U: }! g. h& r, t& y9 K
str.replace和replace并不是一个函数,在使用字符串替换时应当使用前者。& x1 ]) |, L/ _3 @: |7 w, J. z% r
s = pd.Series(['a_1_b','c_?']); v5 u2 e$ U/ `) f" p; [2 F
# regex默认为True,表示是正则模式,否则第一个参数内容表示是单纯的字符串,也就是匹配字符串\d|\?1 E% z/ D, o& `7 }% L1 n6 H3 D q
s.str.replace('\d|\?', 'new', regex=True) , E6 x, M4 J2 c# N
0 k* r/ Y: C; C5 s( n$ E V0 a_new_b+ t4 z$ l, e' K: y( v2 O% _$ {
1 c_new8 D9 \- f$ n3 x- @# Y" \& I
dtype: object
) O9 F) E3 N. ?" p* R1
- ]8 v+ T2 Q, _2
* M T: v4 k7 d# B3
8 ]$ F8 x* N- F45 m) v, Y M6 z) S3 m+ r. O( J
51 u- _5 O2 V7 }5 N/ y0 C; f
6+ Q0 C% ^3 S: a Z
7
. T3 z+ ~" }2 q& w6 ~3 r- O# ^- Q 当需要对不同部分进行有差别的替换时,可以利用子组的方法,并且此时可以通过传入自定义的替换函数来分别进行处理,注意group(k)代表匹配到的第k个子组(圆括号之间的内容):
. \" H- z: B. X) Y3 p" ]! s* X! [& [/ S N
s = pd.Series(['上海市黄浦区方浜中路249号',
, @- f4 g) y8 V6 c '上海市宝山区密山路5号',
7 f; X$ I! w# F* G% F '北京市昌平区北农路2号'])
3 L% [* D0 z1 B! f2 y( o- q$ ^pat = '(\w+市)(\w+区)(\w+路)(\d+号)'/ X# ~8 d/ f0 \5 I7 F
city = {'上海市': 'Shanghai', '北京市': 'Beijing'}
: r! D! ]! Y9 P0 Idistrict = {'昌平区': 'CP District',
; y) M) s# x; }8 R, i/ y; e* S '黄浦区': 'HP District',
4 t5 }( { T5 [" }$ ?$ @: S% C '宝山区': 'BS District'}( o9 x+ _- b8 p, T5 G
road = {'方浜中路': 'Mid Fangbin Road',
/ t+ @9 Y- j5 ^; _ '密山路': 'Mishan Road', Z6 Y7 D/ O4 c. X3 C
'北农路': 'Beinong Road'}. w$ w$ N$ J" W% L O
def my_func(m):6 C) ^& g5 Z$ {% d0 r7 E
str_city = city[m.group(1)]4 R/ E5 [, g. `7 _6 j: ~9 b! I
str_district = district[m.group(2)]# `) ?* g0 Y% }5 U7 w9 G' J; Z
str_road = road[m.group(3)]
! `7 u F2 R3 S2 j str_no = 'No. ' + m.group(4)[:-1]: I5 F# \. e( e" z1 h
return ' '.join([str_city,
7 @1 [2 C, E, \0 e1 S* t' V str_district,
' m! | b6 ]2 S' Y str_road,4 G$ R. m0 f& |( f2 i$ v
str_no])
8 a# n; U+ r, d7 @" {+ j8 \s.str.replace(pat, my_func, regex=True)
5 e9 p2 B' H" s4 W
7 w( A# ~3 q0 w1 h& S. U- [; P, Y1& r: S. d% Z' U; }" _! T
2
( ?# [. A# u: j! i; h7 Q( M% Y3! B6 K' W& s4 ^$ I, [! e5 U% D0 x/ M
44 E) y9 n- C' j
5
! B0 o+ e5 d% ]- y8 v5 T/ s) e6" M8 T3 q# A. V2 T
7# Q0 M: Y) q) C) t
8
' \5 l/ l0 d: S8 L5 m9" @" ~7 g T8 Y; {& y1 a
10
; s5 s) L* A4 z+ O, d11* ]& Y) T7 u' ?) K+ `- U: @4 G
12; B' @9 R6 s+ B4 ?! H
13$ a% G$ @. M7 \8 V, U- K5 {7 {
14
7 }& j% Q% I4 u0 \8 P& F Y' j15
' }( C' z$ n. C' L+ _: K2 L16- w* T4 i$ h' a+ a+ r& h( k5 e
17
* }& }2 h8 U, N2 P; B18/ s! t7 w: O- `" i& v3 k" ]
194 }/ s5 R+ U* w: B
20* U( N( n( J; T5 j( d
21
1 K% {6 C( G- |5 w: P, s( W0 Shanghai HP District Mid Fangbin Road No. 2496 X( f8 d6 ^. N" Z9 L
1 Shanghai BS District Mishan Road No. 5& W/ i4 k) ]+ B! [; F# h+ {/ O
2 Beijing CP District Beinong Road No. 2" n j2 H3 d7 k6 F! D: Z; m+ d. ]
dtype: object/ S4 V6 z6 R) I- l% ]( J
1
* l+ ^: x" H, |' q# _- A2
" i, Y* y# N {4 |' k! q37 C- d- |+ q+ ]5 ^* ^
4# O+ B( ~: g. }' \
这里的数字标识并不直观,可以使用命名子组更加清晰地写出子组代表的含义:
7 x* o$ D, a' W; s1 c1 W; M; }6 N( P, L/ t/ |9 k1 g
# 将各个子组进行命名2 u8 }7 o, J. J7 }0 t4 D, C) \
pat = '(?P<市名>\w+市)(?P<区名>\w+区)(?P<路名>\w+路)(?P<编号>\d+号)', Y) q5 D3 }: U$ H# I6 Z( M
def my_func(m):) N$ R- ~0 p. w9 t" e! C; i
str_city = city[m.group('市名')]! m) ?8 T- G1 w& W8 d+ k$ Y
str_district = district[m.group('区名')]
& q) w5 p2 j. v' @ str_road = road[m.group('路名')]
6 ^& a. q: c% f7 t$ Z3 P; t str_no = 'No. ' + m.group('编号')[:-1]
$ z6 L! e+ P; { return ' '.join([str_city,1 _0 O8 C3 q# I+ r
str_district,
( B Z* d, g5 d str_road,: A7 l& b. G: n, @9 r2 d
str_no])
; E4 @( ^# t4 e. u7 `$ qs.str.replace(pat, my_func, regex=True)8 B3 h0 Z7 `: w, b& H& n5 c
1
4 j( O" n1 c- M/ o+ o4 {2+ R/ s, p' `' t0 v7 o4 m8 F! I
3( A& S! i& W8 |
43 b( i: q: p1 L
5. K+ x, U* A* h% y- r
6
& c6 D6 ^, m* R9 m0 u75 r- z% N. W, D
85 M, _. U, z/ x/ ]0 P
9
4 i9 W2 T% P6 |# m3 Q8 S! P10
/ I N7 M7 m4 O6 ~* U/ d4 q5 F11& @! R$ E4 }. G# Z$ v: r
12
% V7 c' ]; U3 G3 h! M0 Shanghai HP District Mid Fangbin Road No. 249
4 t _; a$ ^" J! `8 W# n# m' _6 `5 Q1 Shanghai BS District Mishan Road No. 56 Y6 N1 D# D i" f
2 Beijing CP District Beinong Road No. 2
' A0 X' M% D0 y9 U- vdtype: object
. I1 F! y% S, ~; i; B, p1
2 x2 k Z7 E! N2 x2 s9 I+ C2
0 c. w: Q" A7 E7 a3
; c( R8 H7 x. @4# w/ Q$ z$ m. i, E( M2 e
这里虽然看起来有些繁杂,但是实际数据处理中对应的替换,一般都会通过代码来获取数据从而构造字典映射,在具体写法上会简洁的多。
+ T; }! X8 _( B' E! _: m+ u9 H8 L. g2 f' C9 ~; P4 D' G
8.3.5 提取
! k4 S5 g& j/ B5 J$ e+ estr.extract进行提取:提取既可以认为是一种返回具体元素值(而不是布尔值或元素对应的索引位置)的匹配操作,也可以认为是一种特殊的拆分操作。前面提到的str.split例子中会把分隔符去除,这并不是用户想要的效果,这时候就可以用str.extract进行提取:/ Z1 v! j$ i9 `- J3 R
s.str.split('[市区路]')( G+ w# C# V& o# b
Out[43]:
0 T( W$ u4 z, _8 \: j! e o0 [上海, 黄浦, 方浜中, 249号]
6 u. t6 _3 V& v* W3 X& o1 [上海, 宝山, 密山, 5号]
0 `4 ?0 g4 F8 o# C1 Jdtype: object% F( F" M1 k" |' @4 y
" W, O# y2 R, Y! n4 b" h
pat = '(\w+市)(\w+区)(\w+路)(\d+号)'
0 a; F* S; a$ v- D# T9 |s.str.extract(pat)
0 Y0 ?2 Z1 A Q; rOut[78]:) w: o; p- w5 a- D; C* f2 G4 f
0 1 2 3
4 j, E I: d4 l; O3 w0 上海市 黄浦区 方浜中路 249号
5 K5 R" \4 q+ I! u7 p1 [1 上海市 宝山区 密山路 5号
- \/ J6 J6 M$ H8 h( t2 北京市 昌平区 北农路 2号9 b' E0 Z/ m# b: F! v v) _5 k
1/ z3 ]0 t8 J* ^! M7 ?7 E P$ z
2
" A) \. e; z( B6 i3) w0 C U1 ~, V; z
4
6 D7 o/ `/ `, W+ Q# ?, q50 p; `" j% f5 E2 D" ~
65 j9 O( |, w# R3 E
7
2 _: `' b O c( l8& G( G# X. O3 Z5 g+ ]6 S& T* z
9
% o8 q* \( s+ B) k10# L; O" {+ j# m" k' S
11
9 U9 K! q! n- d6 p8 D12/ X- Y, S2 o1 O' J
13# d" A& J/ U/ e2 z
通过子组的命名,可以直接对新生成DataFrame的列命名:9 K& u: `8 h/ P% l( c
5 \' D$ S# e. v0 ^
pat = '(?P<市名>\w+市)(?P<区名>\w+区)(?P<路名>\w+路)(?P<编号>\d+号)'
9 u3 `- Y& O+ X, s9 f+ E" Ds.str.extract(pat)' x% Z: C! U" {" A$ C
Out[79]:
* N6 i& U, Q$ C( c 市名 区名 路名 编号; j0 B+ |" x/ k+ I- B. H: I6 c1 |0 _
0 上海市 黄浦区 方浜中路 249号 @2 h* Y( E% {# ~2 ~! [9 {
1 上海市 宝山区 密山路 5号
" s7 Y* s, P+ `, A/ v5 ^2 北京市 昌平区 北农路 2号
% X& U5 J! s% i11 F0 ]2 ?6 U: ^& R) O5 p0 z
21 Z5 B7 \# g+ k. t
3( x/ T$ v1 p. h
4
7 i8 E6 Q2 Y6 w# F9 w- E. P4 n59 ]+ F9 W$ \/ m
6
6 }1 k( O% o+ [- g+ e9 K2 f1 p& ~7
5 t+ m# ~2 g) o0 x6 a. qstr.extractall:不同于str.extract只匹配一次,它会把所有符合条件的模式全部匹配出来,如果存在多个结果,则以多级索引的方式存储:7 w* J5 O# U$ D
s = pd.Series(['A135T15,A26S5','B674S2,B25T6'], index = ['my_A','my_B'])2 D3 S' W3 ?4 o# Y) o% q
pat = '[A|B](\d+)[T|S](\d+)'1 M- s# r3 Q: C/ S7 l
s.str.extractall(pat)( Y3 U' [ P- i3 u( t9 _9 C
Out[83]:
% Q, J; E1 u+ ^# t; j8 s( y 0 1: b6 \; D( K4 E6 U' P6 s
match
2 `2 Z: v! k* fmy_A 0 135 15& S3 v) J* i, Z ~( t
1 26 5
) h' R$ F8 R* \my_B 0 674 2
" s! a M+ Q" r! G; M0 z: v 1 25 6
, M! ^0 ]) n9 X17 [* N, h+ K3 A
21 E1 Y0 z7 }) d
3
% H2 i9 w/ L* F4
) x( L- I; f* F$ ]0 H5
, i3 i( n) S4 a5 m$ V( k6
- X3 ]3 l8 v( y% A! m# l7
: @7 @" i+ V! z" M) j! Q8
( E/ _3 @! F8 W6 E9
- ?, @' j! a* ]3 s& f2 S9 Z0 c104 c3 n$ B4 }# R8 r- a! O; o0 F7 G
pat_with_name = '[A|B](?P<name1>\d+)[T|S](?P<name2>\d+)'
) H% y% @3 r/ v7 N) o% ns.str.extractall(pat_with_name)
' V) s' `" f# F( u! e+ b( YOut[84]: 7 R9 G$ \0 s- j, Q2 }
name1 name2' h6 m3 z/ l- E# }: ?7 V/ t
match
7 T8 Q( |9 p7 D1 G0 ?my_A 0 135 15
# ~ [( S9 \3 I! m# _* Z/ ^) ^9 ]2 @0 p 1 26 5
) E# ~7 Z" n& y( G/ Xmy_B 0 674 2
) o$ c) Y8 ], Y9 e) h) u 1 25 6
* p; P% c7 D6 T* G: h7 {- L- h1( j4 s. V( m. S: Z% b4 M
2
. P( V# d1 e9 Y& ~8 ~$ \3
& [* d- z( Z ?9 r4' I% c( p; `; ]6 y- z' g
5, h) c1 r) r v5 L m* \! y
6
2 g7 B9 P) A F2 D) n70 q4 o) r3 `5 a+ c0 W# I
8
`5 `2 L1 L/ d9
% \$ }/ u- d3 ~ estr.findall:功能类似于str.extractall,区别在于前者把结果存入列表中,而后者处理为多级索引,每个行只对应一组匹配,而不是把所有匹配组合构成列表。
, \, ~6 v2 Z! bs.str.findall(pat). H& `0 L8 O* g$ [: F9 h8 { \# m! Z
1$ a6 |. L; R. J
my_A [(135, 15), (26, 5)]' C0 C0 S: g% I* L# H' `
my_B [(674, 2), (25, 6)]
3 Q- m) i' B' U2 A$ e! D) E8 odtype: object) L2 }6 O+ Y9 R
1
+ F) v2 T9 Q& j5 L0 r2 o+ ~2! ?2 z% S2 G' C; n; ^- Q
3/ f4 l! F8 m* Q& h% ^6 ]
8.4、常用字符串函数
* G2 ~6 m( b* Z6 c) m 除了上述介绍的五类字符串操作有关的函数之外,str对象上还定义了一些实用的其他方法,在此进行介绍。
) g: W+ |- n1 }+ U) f6 d' ^2 N2 A' |$ R- r) D! k- X" _
8.4.1 字母型函数" W+ t) F8 Z+ y7 k. X4 X& R
upper, lower, title, capitalize, swapcase这五个函数主要用于字母的大小写转化,从下面的例子中就容易领会其功能:# _- B, a" N; V6 J8 W; v- Q9 E9 T
9 v5 R% [6 `/ o3 u
s = pd.Series(['lower', 'CAPITALS', 'this is a sentence', 'SwApCaSe'])
, H7 @+ e9 [; {! [* y( E1 }
% J& J) V3 P8 M/ `( z% Vs.str.upper()3 q' _* G: ~/ t# @+ P, j# Z4 W* H- u
Out[87]: + F) b8 _; S3 R. `) A
0 LOWER$ P1 Q0 Z" m0 K4 g( _
1 CAPITALS
" `& I7 c1 l; V2 THIS IS A SENTENCE4 E. c$ K* q. {" {7 Y& r1 `! L
3 SWAPCASE
1 E* w2 B" R+ U: w- zdtype: object
) [5 ~; S; n g% m0 |, P) o n+ L
s.str.lower(); j7 n* h- I. |. r& q
Out[88]:
% G: ~1 K0 h, h" f( {0 h* S" \& S0 lower o; i7 q; L. T+ _$ p
1 capitals
# j: Q- V" P, x# g* E2 this is a sentence
. b& n) T+ H3 D9 |- H- d' A3 swapcase3 i3 R4 T8 T3 f% S( D& Y% I8 Q+ R
dtype: object( T) R9 `% B/ `$ `, b/ x& K
8 s8 I2 i% v4 c; `* [' O
s.str.title() # 首字母大写
+ P( X! ]) n3 D$ U2 A. FOut[89]: 2 ]# G( M% [1 E- [4 h
0 Lower
; @! S; T5 `0 ^6 M% ]1 Capitals
5 F- |6 I3 Y/ Z9 W2 V' v2 This Is A Sentence A* n8 f# a3 E) F# b8 [6 T. E. [
3 Swapcase" T, _: p) ^, {' s5 P. Q% ?+ T
dtype: object
+ r0 s4 `) b, {5 T5 A
) ] Y( d4 U. G9 _3 `) rs.str.capitalize() # 句首大写
; z4 ]$ R; [6 I) i1 YOut[90]:
& R0 r9 n6 Q# F1 z& e; ~2 N3 X* ^5 o! E0 Lower& Y) m, f3 D% B' Z8 P
1 Capitals
' `# ?0 U* n) f8 `% C) O4 s2 This is a sentence, T' ]- T$ D- A/ ?% v$ Z
3 Swapcase
8 Z' T$ q; K ^# h. x- Kdtype: object
5 l0 i: l+ _! H9 q/ D
+ v, J) s; R9 O | r; Y5 K; Js.str.swapcase() # 将大写转换为小写,将小写转换为大写。% q, t" F X1 X$ }) k/ `
Out[91]:
5 |( n6 \9 v+ Y1 S4 d$ c; \ y0 F$ d0 LOWER
1 U* S" w% r8 K2 F" B+ a. K1 capitals0 M5 i3 |) ]: g# q4 B8 X6 d. p6 [
2 THIS IS A SENTENCE
+ a; m( E5 X" A& i& l3 sWaPcAsE
4 I4 H2 M/ n4 ~& V/ \3 K R7 Zdtype: object
, ]! t8 ^( X5 t: P @. c" T1 ?2 h$ K) _0 X! q1 H% Y6 p7 Q r
s.str.casefold() # 去除字符串中所有大小写区别 J: K @: o' t& Q7 x( H* N2 I8 \3 g& ]
~( P& |* n. l2 M" ?: P6 M4 T0 lower# H4 b% y- b" c
1 capitals
1 a& {5 x, v+ I2 this is a sentence5 A* L% U" ^9 l5 F
3 swapcase
; T& ], c7 ^2 M1 s7 N) z# _
! Z6 U6 P4 y9 H1 Y0 G15 i! o: y( ^& X8 Q
21 C) h; z; q" j4 q5 z
3
3 {7 A: ]. r b7 q* @- M7 m" @4 J) Q; Q4 o! U
5
2 S8 \* t, U: w. Q/ J6
, P+ J' ~+ y. S6 f7
+ M& R1 s! J7 b0 S) W4 L8+ W: W; V" D! `4 |& }; w
9
7 m' } B8 o- H7 k) T/ S10
7 @; J5 J5 P0 ?3 J" n9 ^; |11) N2 G% W- F$ ^1 ]3 s+ m+ Q3 w
12
; `+ e+ u1 i2 ]% m. V13
4 N+ ? X' U; E' u7 X14- E2 ^* V4 ~6 u" W+ `. ?2 X
156 m C4 W) y4 S p! t; U
16
) p1 s; M9 M# _" |' E17" ]! g/ ^. p5 Y" j. Z
18. Y" k1 [: v5 H! @
198 |* c h. K2 K
201 k' _! Q' ~' _. U7 Q
21
! |- x3 M m' ?224 O7 g O# y1 c& B# T% M
23) I1 A# J& @! w$ V* \+ b
24
# {$ K" S- b( | F3 T25
# Y# U% K6 o# M' q3 y" h0 {; U26
$ G. {0 u9 H" y+ N6 @27
' Z1 ?7 V( A+ r2 P284 k( l7 N, ~6 Z% r# X7 k/ Z
291 I! R/ b- N7 s
308 S& O m8 X4 n8 Y" B2 t' b
31
a, L6 L; \6 S: W6 P7 L6 ^32
. g# w8 e$ n8 W. L4 w* ~7 ?33
, O6 n4 p3 n; F" C- \34# D* }! Z7 c4 Q+ m
358 ^, X1 U+ |9 G9 }1 _( I
36
( @' ] j$ W( U6 w37
r( C+ Y; ]: u& i Y i. Y7 u38
( B3 d6 i+ h' h& b39
4 m& v0 P+ M: C; D40
. D7 C' E% E6 p) `( v41$ [8 f% G: F7 M' M4 I- Y. y
42
6 w7 Q% ^1 \9 z$ }43, g0 o6 Z8 b; b) T
44% F: i1 X. p$ F0 `: @
45$ _2 e) P" }' h3 y9 }
46# E* `4 O8 l5 { t6 L1 w4 X
47" v" N0 M: o5 D/ S
487 B; m# P* K: P! ]6 I& z2 K
8.4.2 数值型函数; M4 v' x8 B5 N0 r9 L- k
这里着重需要介绍的是pd.to_numeric方法,它虽然不是str对象上的方法,但是能够对字符格式的数值进行快速转换和筛选。其主要参数包括:- C$ P% v/ T+ {, t. _
; \4 _. n- S. q8 @. G0 X
errors:非数值的处理模式。对于不能转换为数值的有三种errors选项:& C8 A4 T( W. s2 u9 l
raise:直接报错,默认选项$ K" f- P T) }2 e" Z3 U4 l/ d
coerce:设为缺失值
+ @0 k- o1 ?# {2 o0 w- V( \ignore:保持原来的字符串。5 T( R7 o- L7 H" {4 Z
downcast:转换类型,转成 ‘integer’, ‘signed’, ‘unsigned’, 或 ‘float’的最小dtype。比如可以转成float32就不会转成float64。2 t2 d; q4 u9 E: ^$ S
s = pd.Series(['1', '2.2', '2e', '??', '-2.1', '0'])8 h" ?- S8 e2 N; L& x
; M' b6 u$ F" s) `pd.to_numeric(s, errors='ignore'), V1 ~7 r- F) _
Out[93]:
8 A6 X; ?/ x! u9 H0 1
# r; g/ {9 h D% H$ y; P! A1 2.2
% ?1 @; J& Z g, t2 2e2 c/ H1 i9 ]6 u; W! i; \; k
3 ??
, ?! _+ v4 @/ z# ]3 w4 -2.1
9 u5 j$ i3 U$ Y5 01 M% S. ]; I0 ?. l. ?
dtype: object
+ N8 T) s' S+ A- {/ {* \* F: V& `7 u
pd.to_numeric(s, errors='coerce')6 m- f8 a( P. R) b7 H3 B. H6 D8 d
Out[94]:
/ `+ Z' K# ]. n! p3 L. `/ R0 1.06 Z" ^" @0 `; o2 g) I
1 2.2
" k R, n/ {' ^2 NaN
0 {7 d; I' v( N8 y7 f) b& O6 I3 NaN
0 v2 I5 E' G" G1 s9 j, T4 -2.1
. @/ j. a( p4 @- e" L5 0.02 Z& B \/ J0 {6 \& n
dtype: float64
$ _# e( W- `# w+ ?3 T6 \, \) P* {! Y
1" R v' _. A4 W: m/ c" ~
2, G- J# _" ~: N9 R* |
39 d* X$ G4 Q& w* ?
4! ?/ G6 _ f C: D' o7 G4 h! P
58 |) |) E- K+ O/ Y2 w
6
3 X7 H' Q1 j/ W' R7
1 ?7 S h3 A* n- E4 [8
, Z8 M" S: \; M$ A9 S, k9
( n5 d) X, y, O0 y10$ B, k: n# O- P
11
0 ?) A/ H; z: s$ R+ L, \12
! q' p2 H }7 {+ G A, I131 \# o9 X, T& R; ?( {
14# d- y1 B8 X, {: J! b4 R0 G$ ~1 d4 L' _
15
: a) Y d0 a5 t# I16
$ ^. _* j6 {4 ^3 o2 C4 \/ n178 Y+ V9 r; R3 l% ]/ d( @5 M6 N
186 M/ h0 y4 L! h4 m* T) v0 I; n
191 Z8 x, B$ T& j/ _, ]5 e. x
20
, d1 j& L) b* C. h0 A21
; c' }; f. l) I g; W 在数据清洗时,可以利用coerce的设定,快速查看非数值型的行:
: y7 r! N" u& m
) T$ o7 V! ^) Z+ qs[pd.to_numeric(s, errors='coerce').isna()]8 }2 N9 D0 w* _! j: D, b
Out[95]: ?4 B( M7 W* w/ c8 V& n- m& [
2 2e
* y6 f' {4 F/ x' t5 K3 ??* h' b# x4 S# t: t- D# e
dtype: object/ A+ H& J4 \; T, m, s) a( k5 G
1. T: D# D5 t q2 Z2 P
2" H! g* n. z) z; J8 w- j5 H
3
- M4 ]0 S0 ]$ k6 t) n" V+ Y, m$ ~4
~: `/ d3 X( X9 h$ z5
/ a* A: S' o) B' `& F8.4.3 统计型函数' u; [- \* T8 ]/ z# V$ A
count和len的作用分别是返回出现正则模式的次数和字符串的长度:
% z/ E7 ]6 O5 X7 J6 @5 V# z% D' U, s C5 {
s = pd.Series(['cat rat fat at', 'get feed sheet heat'])
5 C) ~, d( @1 D) Y" x! w& Y) }3 z. L. M
s.str.count('[r|f]at|ee') # |左右两种子串都匹配了两次
8 ?$ N1 F' _ U/ j+ ?$ w6 gOut[97]:
6 w( r$ j" e) u0 23 }3 w, H# L; M) D( R
1 22 u V) e1 s$ h- U2 \
dtype: int64
9 h4 J6 v8 J ?" \. b& {: i8 t: j# z+ @$ w9 ~. H
s.str.len()
- F% ?, B! x$ G& C' P9 S, iOut[98]:
9 o5 I' j; M' `1 I% W6 a: Y! a! _0 14
8 \ M+ H# |) C; |+ o$ e1 195 y) I! L6 w! |+ i0 S1 N l2 f
dtype: int64$ o) \0 `8 o& P2 R b! h
1! W% X7 C" w; g# K2 b+ X E2 T4 L
26 n& {* |8 l3 B! b, q
3( l; @/ K1 @ }7 k4 f
4
) d9 e( p0 f. `* V) v5
9 P$ Q+ Y) M* D9 w) h1 [6 s- b6
+ L) i; b3 s" k1 x7
- K+ A& A! y. p( y5 Y8
4 x4 Q% b2 s% a! q% M4 R9
0 q* ^3 h: `9 A5 s3 M) e10
* l( L, c8 L8 j5 T* @+ [6 Y% O11/ T$ L; X* ~0 L0 O9 Z) C
12- z1 ]$ n: P( C/ ?3 Z
13: V0 X5 g; o, c! l5 q0 v
8.4.4 格式型函数
4 ]: W7 z7 X& U3 o, z 格式型函数主要分为两类,第一种是除空型,第二种是填充型。其中,第一类函数一共有三种,它们分别是strip, rstrip, lstrip,分别代表去除两侧空格、右侧空格和左侧空格。这些函数在数据清洗时是有用的,特别是列名含有非法空格的时候。 m' a( z7 `4 c5 ~* h2 Z
& u' n" ^3 v1 M1 F X( `1 z$ t! Zmy_index = pd.Index([' col1', 'col2 ', ' col3 '])
+ V, o( d8 r5 N7 o+ U& t6 T$ t4 |6 [/ x
my_index.str.strip().str.len()2 I; A& y7 a) E- Y2 O- `
Out[100]: Int64Index([4, 4, 4], dtype='int64')
" `6 A8 y4 r; z* f! C+ y& S1 }, L! y* c4 P, O8 }: |
my_index.str.rstrip().str.len()& u4 P5 O' \, t+ }: c+ \( M |4 j
Out[101]: Int64Index([5, 4, 5], dtype='int64')
- N" }, _% s' }4 W: v- Q9 p) B. ^+ Q* s0 f3 @9 z
my_index.str.lstrip().str.len()3 N. C! R$ ?4 A0 D5 ~; s( M
Out[102]: Int64Index([4, 5, 5], dtype='int64')3 J8 r" T* b7 z4 ]9 z! e1 O6 r
1
& N1 g3 y' K+ U; y3 N1 o2
u z o4 n' L' Z6 Z! U' b, L% E35 h; r# b* T( I2 s
4
* S! D% u- J4 F9 n+ R; ^5! G3 }- w+ Q6 n( X9 l2 m0 M
60 d+ [) A" [; A# W1 O) Q
7, R2 K5 w" n5 N/ K9 Y: ?
8
. ]& S7 a3 R7 w- J. E, B. F1 O/ l99 g0 |+ S- x5 V+ M7 R) b; b+ q
103 K$ S7 ^4 F9 B2 _
对于填充型函数而言,pad是最灵活的,它可以选定字符串长度、填充的方向和填充内容:2 F W& e; t' l
- H8 h: z5 B) C$ N8 Es = pd.Series(['a','b','c'])5 a/ k( O/ Y, {9 |# V
- \7 W) y- O' M: H1 C- {& n9 R) ^
s.str.pad(5,'left','*')+ Z! p; v2 U0 {. D. n9 C
Out[104]:
" B8 m% {4 _ _6 b0 ****a( Y6 V1 S9 x, r |' Q
1 ****b
4 J) I4 f# ?3 H9 ^8 B7 j2 ****c5 |4 H% k* p" [! D1 F' ~
dtype: object
0 G% \- H0 }- w1 t9 S
/ J2 W, T2 g+ _' gs.str.pad(5,'right','*')
3 P. ^# f1 }; F O9 P0 fOut[105]: , ^0 { D% A; ~
0 a****, m9 [, R! c" B
1 b****! H% W# J5 c) ?4 b1 w
2 c****
# d' P! {" I* T* B4 Q! sdtype: object
/ @" l# T9 H8 b: ]7 \% g2 _
[+ v( j# {9 m# j; _s.str.pad(5,'both','*')8 W+ C4 ~3 z( z% `+ W) Y
Out[106]:
' \1 w9 k; ]/ \+ j% R! S0 **a**" m) p4 @( c: {% @
1 **b**
$ {0 T3 g' m, ~2 **c**$ d+ X& T3 C' f& ^
dtype: object( b, ^) S* w( N E6 ?8 |
! c% h+ m6 G* \
1
9 ^9 A" V- a3 x7 k% E: [, d2
$ h8 Q8 e" }- c! A3
5 u5 u& F2 c. e! T" ~4
! E6 X9 O7 ?1 [: T: l ^/ l) ]51 `, M Z9 p- a+ e% f3 |$ q5 D
6
7 F: ]& ^) n! ?7 z7 F) ~7
. Z! s0 E1 _3 ^: m) ]8$ T% L) Y- X; z! I( `" m0 P# t
9
4 l2 W0 ? g- S: u. C. [10
2 |* Q6 m) Z4 Q0 L11
4 @7 n+ X" A8 k% F _, @. T6 h I12
" s6 l9 t# W; Z b* F. ?139 T3 F# f. X# \- c8 N
14" R$ _( V' X/ x/ L
15
4 f0 Z7 H9 _1 B7 I. y3 k7 R9 b16
/ a. F- U' m D17
7 C B: k z) }' _* p# [: Y18
6 y( W' @ u% y' s+ S; C& J19. D5 D% r9 J I
20
& Y+ b' |! P( s' }21/ x. W. ~0 ?/ C
22! |) ?$ } e9 u
上述的三种情况可以分别用rjust, ljust, center来等效完成,需要注意ljust是指右侧填充而不是左侧填充:
: R9 |0 v7 g, s! x" J3 K
5 f a9 l; y% ~' b7 r& z7 qs.str.rjust(5, '*')$ h- l) x8 m& C6 D. `; r7 U
Out[107]: ' O. A5 Z( ~1 ~0 T1 [
0 ****a7 A A% y. J8 v6 d Z9 o3 r3 b7 ]$ q
1 ****b+ i) b1 m3 W! P, `- R
2 ****c. C% x) T5 i3 b) B3 [& U9 I9 w
dtype: object/ s' f2 `, A0 M) E5 P
1 T: I' H. \4 ~9 e/ Ls.str.ljust(5, '*')
$ H3 w0 ?' _( U" g( sOut[108]:
9 R, M* @6 g* O2 I# `! N+ T+ e: {; @2 b0 a****
0 K( }) x: p$ q+ S. T _1 b****+ o V3 P3 A, @7 H2 z
2 c****
# }" ]2 C# i4 Kdtype: object6 s1 } {# M; i) }% F
) _% o T O' Bs.str.center(5, '*')* L7 ?) E1 Q" Q9 @
Out[109]: ; `6 e7 M" v# D
0 **a**
4 R( W3 T0 c. x" r" r1 **b**
3 D, B) Z% M% k. p. m* x9 J2 **c**8 L! `9 q+ u$ Q( g; l6 Y/ ?6 j9 V' L
dtype: object
; e. _# a6 n$ {/ p+ k
0 h/ O( p- V+ V7 W5 {1
% P3 @& D" f* K' ?" E+ a6 ?% ^2
" A5 ]7 h& B" C% D# r33 e6 ]! N: ?7 |, w
47 m1 y7 e; j" u
5
# d8 s0 }9 j4 ~, @- v) A# o0 H6
9 f# j2 b, U( V" [* r# V+ \7
% v- M* Y. A( v7 R, E& X$ X) {8
3 F, V5 X% v: e$ }5 Y+ D93 U: ]* U! v- Y S- s T' N% b
10 M4 j" `4 g' t! G
11& v1 T" s+ d8 c& u
12
5 U% E: S% N. O+ o13
6 X% N$ z# ?, f) k14- J$ `: W* \5 M: B
15
2 j9 o3 _* ^2 D0 d0 S16/ [' W3 e& L; h: l
17
7 |- w; x- L( F$ F18
4 i7 G4 t; l$ }1 k19
5 J2 Z( F6 @$ w8 p( ? Z9 ]0 A20
+ b, U# q6 X, M: ] d% Q! e1 g 在读取excel文件时,经常会出现数字前补0的需求,例如证券代码读入的时候会把"000007"作为数值7来处理,pandas中除了可以使用上面的左侧填充函数进行操作之外,还可用zfill来实现。* N0 E' Q A, h* G9 ]
2 p) w% R4 T7 w) q( [: s9 }4 ?
s = pd.Series([7, 155, 303000]).astype('string')
$ t4 h- p [2 m, R# d
" f& [# X$ Y, v( Ts.str.pad(6,'left','0')9 E) b# o ?0 ^- H e2 C
Out[111]:
0 C# N& R/ N& k2 c7 X9 N! Z0 000007
' l: L6 ^- W0 X, C( Q1 000155. E6 A }5 V$ M* _- a; E
2 303000) k- T/ i. V9 l4 H0 `- C
dtype: string
5 p. ~+ c% F, Q* p5 X7 x+ h' S) ?8 ]2 W
s.str.rjust(6,'0')' X% k3 m& n- ?8 ]8 Z' W
Out[112]: 3 o' o/ B" a% j
0 000007% ^; p: v9 w( R
1 0001553 u) D1 ]; F o0 n( S
2 303000( ], }8 y' O5 W0 |& v0 T
dtype: string' Y& I* c* l w" H
o P. r) M* s. r
s.str.zfill(6)
& _% {+ h0 s9 E- R" u4 J: F- WOut[113]: : O; u) ?& G; D1 t; E% \: C" \; ~
0 000007
. `7 [: b& I1 q1 000155
& Q/ }9 J9 I. t h) u0 e2 303000
, H0 K$ J* o6 e$ }& i0 m! Bdtype: string
% G3 u8 ~. t7 V% E! ~ l6 l
7 I/ R& H x8 e" H) @, U" T y4 X' e; }11 X8 h% p% Q' ~% L' Z
2( {5 [4 B( t% Y6 p
3
; h$ R+ x$ h: n5 A" Q! {4
, F/ i) W5 O. x+ r1 q0 x* [% g57 V, u3 x$ T- Z5 r3 r7 ^; a' ^
6, d. _) l. S1 p: x- d& ^
7/ z7 [# s# G1 `' s+ O. b0 t
8* q7 G, m6 {; R: o1 `2 _ F3 g
9
7 \$ n* f4 H# b6 C5 P10
, F" d% z+ B- F- V( m% v0 H117 |( `' o; _" |
12; u8 P1 V" i! X, q7 U
13 K1 J! A% Q" q9 ]4 _, ?8 s
14
$ l% [7 H( l& A% z3 h4 E+ H0 f! w/ C15
7 _. P' H7 p8 w: t+ ]% [165 {! ^+ |7 X" `6 X: V2 @! D& E) F
17
$ b1 Z3 I5 L% _18
1 o, X( H8 B- c( M9 Y# n19
5 ^6 Z* N% T' b8 i207 E" r8 i% Q6 p# y- y, _
21
7 Y4 f( c, f8 o! x/ V6 r% K( }0 ^22
9 p2 u3 m% {6 p2 ^8.5 练习
0 [$ m( y7 G4 p8 PEx1:房屋信息数据集
4 A% y/ i( T6 a5 c现有一份房屋信息数据集如下:% h n- l6 `5 F, `; H
+ U! H8 [. o, p" Y7 k) i: udf = pd.read_excel('../data/house_info.xls', usecols=['floor','year','area','price'])
" N9 y$ \2 _8 u; N5 cdf.head(3)
D( i! m* k8 h$ ~Out[115]: @3 t5 W! Z6 C" R' q
floor year area price- s8 S% _: i: }4 o2 `
0 高层(共6层) 1986年建 58.23㎡ 155万
5 v e$ T3 [3 w4 B3 ^8 W( p: H1 中层(共20层) 2020年建 88㎡ 155万: H1 K, s! c6 } u& t- ?* P& A
2 低层(共28层) 2010年建 89.33㎡ 365万7 m. }3 S! E7 V, X
1
8 n! u" a/ }3 P. v' Y4 l2& R& a6 M8 p2 f2 |# d$ Q$ O
38 x- n+ A8 j% ]6 l, S; m3 v- [
44 h. g2 Z) @7 m. V' ?$ Q9 x, s7 [
5
# g1 B& ]. m4 ]( J) a$ Y6
/ C: X. e' M7 O6 i" l; K X A7; ^' C/ _. a" E2 o. B: ^* s
将year列改为整数年份存储。; J2 q( p& N' H9 A9 d" }: i9 N4 d& _
将floor列替换为Level, Highest两列,其中的元素分别为string类型的层类别(高层、中层、低层)与整数类型的最高层数。& s! \6 R4 h) ]3 i7 u5 e t3 p5 f
计算房屋每平米的均价avg_price,以***元/平米的格式存储到表中,其中***为整数
' i5 ~- Y5 d% H/ M将year列改为整数年份存储。: p- y e8 t: m/ ?- k
"""
& F2 s7 \, T2 b整个序列需要先转成Nullable类型的String类型,取出年份,再将年份转为Int64类型。: j6 `. M7 {. ?& N
注意,转换的类型是Int64不是int,否则报错。即使astype加参数errors='ignore'跳过缺失值,
8 }7 l: }# o9 }转成int后,序列还有缺失值所以,还是变成了object。
! }6 B" T, Q' P, [3 c- \3 ]而整个序列转为Int,就还是Int类型,缺失值变成了 pd.NA 。1 G6 k z3 w4 F" Y' H+ y
"""
$ @. Z$ D4 Y& jdf = df.convert_dtypes()
8 p4 ^4 W+ f- C9 p9 Fdf['year']=df['year'].str.replace('\D','',regex=True).astype('Int64')3 F! |. I5 h2 _3 r; o1 M: e
df.loc[df.year.notna()]['year'].head()4 M \6 U) _2 {9 u5 z& Z
4 x4 Y* T4 m, V% H
0 1986- L5 u3 `( C: _. d2 ^0 V% g( H# x
1 20202 Y) {7 h r7 f$ L% _( `. q
2 20103 U0 z. H) V0 k; f
3 2014
6 Q5 z3 S8 n2 L* e$ ]6 H0 x Z8 z4 2015
m- Z' U( a: n) qName: year, Length: 12850, dtype: Int64
. P8 O8 e5 { P- ~9 d
1 s: K6 C: B* A! |14 T5 q1 ], e0 t2 J6 O2 \) O+ k9 }
2 [) _7 r, L' W* h5 E
3
2 g$ V) [/ S- D6 y4
6 B3 A* R* n5 }, Q" {% i5
% Y. B3 A% ^/ e0 V! {6% C* V& D$ n" D. q+ {6 e; Q
7
# ?7 q5 X9 _, }/ r! ?2 S A8; t) U# @' U" x' `
9
( G/ }* ?" a q8 |" o" @10
3 r7 F) d: y' r3 V/ _8 @" I11
% w$ {+ W- ^1 N9 t" p% M12
3 t2 U+ F/ g& p: R: E/ R! T13% y3 m7 _# A: R% j' n0 ^8 R
14
0 R4 M I' \2 P2 A! `( `15
3 f6 ^8 r9 R) ^& t16
7 m- _- i+ e5 u) j; r4 U参考答案:
( `: ?& A3 |. W Z6 e' q X- {+ ?: u* o9 m4 H2 \8 [* e7 t8 B; }
不知道为啥pd.to_numeric(df.year.str[:-2],downcast="integer")类型为float32,不应该是整型么- h) e+ p. I% Y( y* v# ^
% ~# i" ?, P. v% |
df.year = pd.to_numeric(df.year.str[:-2]).astype('Int64')
0 Z& v* B X0 [# k* Udf.loc[df.year.notna()]['year']( ~& ?6 y- z) ?$ F0 j0 ]1 n; Z5 Q( ^
1
; ?6 I: K' w4 A1 N" H2
& w! N4 M2 \+ l. a& M将floor列替换为Level, Highest两列,其中的元素分别为string类型的层类别(高层、中层、低层)与整数类型的最高层数。
- a0 y. i0 I- X& U: r, Npat = '(?P<Level>\w+层)(?P<Highest>\(\w+层)'
) W( {' p7 N6 S* B" n' E/ Idf2=df['floor'].str.extract(pat) # 拆分成两列,第二列还是(共6层得形式,所以还的替换一次1 E+ Z" Y& d4 \& W# ^ [3 d
df=pd.concat([df,df2],axis=1).convert_dtypes() # 新增列拼接在后面,再次转为Nullable类型2 G( d( w: B4 Q5 ]( w
df['Highest']=df['Highest'].str.replace('\D+','',regex=True).astype('Int64')
9 u& J- N- v+ {1 Bdf=df[['Level','Highest','year','area','price']]$ z7 M8 t+ q. ?9 H
df.head()7 H6 v" @, h" i* @ ?, {
" _, t" |1 N" V u Level Highest year area price @5 M4 \7 m; h% |0 J5 Z: P; ^& j
0 高层 6 1986 58.23㎡ 155万
) I1 L7 t- x/ i8 h1 中层 20 2020 88㎡ 155万. `8 x; v9 ^( y2 O: ], i
2 低层 28 2010 89.33㎡ 365万2 Z0 T: n- a' q0 h" k
3 低层 20 2014 82㎡ 308万/ R) r% T0 F1 M/ Y% M" S) a
4 高层 1 2015 98㎡ 117万
6 u* B1 a. u( A. f1
2 d8 P9 z$ A* d3 M( ?' z! B2
/ m ~# s; R- k, g0 f3$ F' Z/ G; [$ j
4
" P3 e$ x8 z1 w9 g/ q5 Y/ V55 _6 @% u9 c$ F1 W" h
6
R: _! }8 D/ _: I' M, C7
% V( n" d7 ~" Y9 b& E89 k% B0 u! ?3 S/ D W" o- X u
9$ H% @( a6 M& _0 q, {/ L7 E
102 W) S' [& n5 u, |2 z2 o
116 n4 r1 P8 o' ]( v/ H3 R& o; O
12
5 k: ^0 n+ C6 t# t' s: L/ `$ W13
/ {4 O1 U& }+ @" l8 a# 参考答案。感觉是第二个字段加了中文的()可以准备匹配出数字,但是不好直接命令子组了
) L- ~; ~- T) T% t( ?pat = '(\w层)(共(\d+)层)'- D' K8 F+ w. [* E! h9 G
new_cols = df.floor.str.extract(pat).rename(2 I2 l3 n U$ ~7 B0 u. Y
columns={0:'Level', 1:'Highest'})
( ]) J" h) M* h( m6 d
0 R, w5 Z; z0 v5 C8 Bdf = pd.concat([df.drop(columns=['floor']), new_cols], 1) t* ] K9 t8 E! o& P
df.head(3)# r% t* X! d1 n( z
% \. k/ p R9 kOut[163]: , g1 @' ? f* Q$ f. X" ?0 u
year area price Level Highest
& C5 f) p% k) T# W" L9 b+ ~7 @0 1986 58.23㎡ 155万 高层 6
& `; ~ Z" Y$ v1 2020 88㎡ 155万 中层 20
P' F: r, D& V( v5 V2 2010 89.33㎡ 365万 低层 28" k C6 q# u: {. e) `, c( M
12 q3 g2 j& h$ D. a/ N& }6 N/ A/ n1 Y6 [
2
& ^4 I2 i6 Y; c3
; K" y+ ^, ]; [7 `6 g. _/ {3 l6 k ]45 z' q, K) X2 i }
5% F! C4 L' o. Z. b- n* `
6
0 ]" w) w8 k1 T/ D* J0 v: w4 s7
* c# I! X l2 ~2 V) h8; Y( y6 \* g3 [! e
9% @* o2 ^5 N5 _8 v
108 I4 l8 t9 j1 k' [
118 k/ x( |3 w# V4 L5 J$ O
12' M/ f) Z5 i& }: s, p# R. v
13
+ M: |' y8 }1 f; b4 S* O' ~1 a计算房屋每平米的均价avg_price,以***元/平米的格式存储到表中,其中***为整数。
$ j6 g9 d9 }* T" ^# U( |- g2 A"""% w6 {0 l7 Y1 b' N; y
str.findall返回的结果都是列表,只能用apply取值去掉列表形式
& T! m9 C5 q/ D( Y$ b% [参考答案用pd.to_numeric(df.area.str[:-1])更简洁
0 u+ T D/ R4 z7 S& F# o1 p7 k1 E由于area和price都没有缺失值,所以可以直接转类型
" P4 z+ D) j5 A) O9 H* ~& ^. {"""$ s' Y. n, @2 |7 t- f9 {9 S
df['new_area']=df['area'].str.findall(r'\d+.\d+|\d+').apply(lambda x:float(x[0]))
- d# f: ~8 w: h9 k+ I& A* {df['new_price']=df['price'].str.replace('\D+','',regex=True).astype('int64')# N1 g" |4 F% [% ^8 L
df.eval('avg_price=10000*new_price/new_area',inplace=True)9 s6 ^9 @! w3 i" [: b! J7 A$ A
# 最后均价这一列小数转整型直接用.astype('int')就行,我还准备.apply(lambda x:int(round(x,0)))
# P/ {8 ?/ A4 X/ j0 l# 最后数字+元/平米写法更简单+ M3 r1 t9 y" X! T6 _/ g
df['avg_price']=df['avg_price'].astype('int').astype('string')+'元/平米'& B; P% N4 V7 O3 O5 ]) n1 T
del df['new_area'],df['new_price']
: w9 o* K( r% ?1 J; j/ j6 kdf.head()
2 g8 [/ W, O I/ z6 Q3 }- P) Z% h% r N J) S0 A) ]
Level Highest year area price avg_price
3 c a+ [% m, h" T U6 O; L0 高层 6 1986 58.23㎡ 155万 26618元/平米
( t5 v+ d1 V# N" N% L1 中层 20 2020 88㎡ 155万 17613元/平米
$ y1 @4 X& C3 C- T' B2 低层 28 2010 89.33㎡ 365万 40859元/平米
9 O* [' d" T! T) B& c& ]% j% \3 低层 20 2014 82㎡ 308万 37560元/平米, C5 `6 l S7 i& ~; d
4 高层 1 2015 98㎡ 117万 11938元/平米
* }% y7 ?- Q* k2 B/ i+ v* B9 ?" U2 y; u+ w: t* D; N
1
) V. c% [$ Z1 z' {2 O; t26 a# e7 B5 D1 B: i, W
3
5 z' f+ @8 k4 j4 R4
9 b7 m, y: k' l3 B5
! L$ R" G2 @0 Y! j6
3 U* q6 z+ A. ^* @+ ~3 Y, |7
; R6 T- U# o& j0 S1 ^2 W8/ N6 u. {9 h% m9 W- `- N5 t
9! ~, s4 n. _2 M$ c7 j
10: _( [5 S" e( [6 i; |8 w$ ?! y
11
- l3 l0 S) [: i- d% G: n: W) W3 |126 g- d8 b3 j4 H" g# `9 n
13
' A9 H$ ~6 d7 V7 n" s; v8 q14
- k; s' l- ~% e15
& o0 ~% K! c3 [9 B16
' z t0 i9 ?# q3 H5 j+ F7 k; G17
6 H1 ?6 T2 u& {) g8 ]18
; `6 s$ @) @8 U; a+ N19( y; S; n* F, H! S4 m
20
/ N3 m: w$ ~ M5 ]6 V9 }/ ?3 U. E# 参考答案
1 \( {1 `' h' v$ S6 D# m; _5 es_area = pd.to_numeric(df.area.str[:-1])# R* v) G+ @+ u1 }( ~6 \4 Z3 _% X, |/ ?
s_price = pd.to_numeric(df.price.str[:-1])
1 h z$ c _& H2 X6 R& ]. Y# c; fdf['avg_price'] = ((s_price/s_area)*10000).astype(
3 M) D* a1 i! j 'int').astype('string') + '元/平米'% Y+ z5 y4 D5 R9 ~$ }" E0 i9 g8 p7 f
% g, w" ?, W8 J% `1 Z& D7 i4 {df.head(3)
0 \( X: B1 K/ C5 h6 J$ YOut[167]:
0 f! ?# F& E0 e. b3 P year area price Level Highest avg_price/ u$ h) O& \8 r( m! t8 g" A
0 1986 58.23㎡ 155万 高层 6 26618元/平米, Z2 Y# I$ q H# {5 Y' K
1 2020 88㎡ 155万 中层 20 17613元/平米6 ~* R" t- H0 ~" g: X2 ~
2 2010 89.33㎡ 365万 低层 28 40859元/平米
+ q% y+ |+ d' @" e4 Z1' W7 l9 D4 ~# Z& p2 H
2- `. m. G& t5 U6 `0 D/ x
3" ]: x" t! Z# H
4$ l7 d2 ?) l- p; O( i
5& M+ n2 z# g; W3 s- @" G C7 L
6 d# L1 B* G& T* m0 K! K) L
7
8 D; H: b1 F8 O; w2 ?' s89 b) u6 [* _. ~; T- v3 D! v. T
9# V9 s# J5 p/ @5 @2 ~, N0 f( v
10
& m, D0 E9 Z0 ^4 Y: I/ Z8 q5 u11
/ I W& m! H6 M* d12
, o+ N7 Q& ?: d$ V! dEx2:《权力的游戏》剧本数据集
: Y: s4 ?# u% f7 n" G4 e现有一份权力的游戏剧本数据集如下:
5 p9 N: P( u' P4 J6 o, z& O
6 h$ o7 D7 H# x5 b' `df = pd.read_csv('../data/script.csv')
2 ~+ j, {6 J9 @7 ? W, r( Xdf.head(3)
9 W G! ]) g& \; ?) M. f! h2 O
% i0 u8 c X5 p' d/ D5 uOut[115]:
- e& P0 F# }1 j, f, AOut[117]:
V& h. Z; F( u, N: H+ \ Release Date Season Episode Episode Title Name Sentence. U6 Q9 u0 C2 P" x! y
0 2011-04-17 Season 1 Episode 1 Winter is Coming waymar royce What do you expect? They're savages. One lot s...3 P9 Z+ s1 w) P- }3 Q! G
1 2011-04-17 Season 1 Episode 1 Winter is Coming will I've never seen wildlings do a thing like this...6 c5 c' G( z7 g# s9 @& z5 a" @
2 2011-04-17 Season 1 Episode 1 Winter is Coming waymar royce 6 K" c: l. f8 t3 s! M* w7 }5 q
1
! v e4 q2 p; S. S/ R" {2& T) Y3 B- J5 |
3
$ j1 V! i/ R [4 s; d1 j% j! {4& k! F6 v _4 O3 O' {. z1 b" H# x
5
/ w8 X8 e( D/ }" C& N) z6" u% _# I/ P% c1 Z/ s, ?
7# Y" Q4 {1 R; Y; A1 Y
8* C4 w2 j1 q5 x: m0 _
9
$ X" |2 K* f( b计算每一个Episode的台词条数。
& R, t4 ]8 u: V, L f以空格为单词的分割符号,请求出单句台词平均单词量最多的前五个人。5 Z6 k. i b# C; C: j% y$ L
若某人的台词中含有问号,那么下一个说台词的人即为回答者。若上一人台词中含有 𝑛 个问号,则认为回答者回答了 𝑛 个问题,请求出回答最多问题的前五个人。" }( e% @; W; y6 V
计算每一个Episode的台词条数。
+ F5 O6 e: w' O! a2 L$ [df.columns =df.columns.str.strip() # 列名中有空格% o9 \/ D( u9 \8 y
df.groupby(['Season','Episode'])['Sentence'].count().sort_values(ascending=False).head() ^4 m7 o! s* t7 i3 s
$ Q# U+ l" O. E" @5 S: o/ S" Nseason Episode
( {, Q# q" X1 ]& h: Z$ ?) zSeason 7 Episode 5 505: }& t+ |: l' Z% @7 D7 C3 I- B
Season 3 Episode 2 4808 B7 n8 r9 V9 V& x$ A& Z) a
Season 4 Episode 1 475
0 c+ \+ x8 N# [. y4 B9 aSeason 3 Episode 5 440
4 Q* `, S6 D3 |* t: T2 S% K/ U6 MSeason 2 Episode 2 432. {. Q6 g; b! W6 Y j: ~8 Q
1
8 O! ?; P0 n8 X. r* F2( D4 V/ L' M) C
3
; e+ j S9 g8 P+ m6 k0 C9 |4 `4! @- w) n3 \, ?6 R
59 o v4 L; Z; }( w
6
U* ~% \7 {* v. W7
) D; g; i1 w% _8 [80 Y( E4 N$ M h
9
% H# }- e6 P" b* b以空格为单词的分割符号,请求出单句台词平均单词量最多的前五个人。
' {* K6 |& u0 w( M7 `# str.count是可以计算每个字符串被正则匹配了多少次,+1就是单词数* ?2 T. F. O) X& _7 {) P
df['len_words']=df['Sentence'].str.count(r' ')+1
1 C% a. [. ^1 }df.groupby(['Name'])['len_words'].mean().sort_values(ascending=False).head()
5 ^& v4 f: s9 y. L0 t$ m, w4 E( T9 ? B @
Name; ]$ F. l4 o H. P" h+ A
male singer 109.000000
% P' E# j; p2 r4 C) x8 pslave owner 77.000000
- ~5 U# `9 X' o2 N7 qmanderly 62.000000& E" Y' K5 N) b4 {7 {* ~6 Y* x
lollys stokeworth 62.000000
% v% y! T' j+ k5 Adothraki matron 56.666667$ L9 f) g4 V4 f" v3 z+ A
Name: len_words, dtype: float64, e& _. l* W7 s; G f
1
, o$ P6 ~! O1 O- S25 U9 Z) [9 U/ F/ f ?/ r6 P
31 I' y2 [6 n! i* j6 J
4
* A: u0 t @! w3 _3 [5
* u* n+ ?; o9 }7 L6
. E5 |0 K; V/ h( _7
, J: H7 H6 r1 `! |# D8+ \ b) ?, c/ [ s: B
9
. k. _4 u! x, s0 t5 {105 y$ C a) J! K1 T$ Q% }/ Y& F2 l S
11; R! C; d0 ]! m" l8 y; `4 H+ d9 _
若某人的台词中含有问号,那么下一个说台词的人即为回答者。若上一人台词中含有n nn个问号,则认为回答者回答了n nn个问题,请求出回答最多问题的前五个人。
3 z, R; S ~$ U8 @df['Sentence'].str.count(r'\?') # 计算每人提问数& ~' g- ?% \# ], [
ls=pd.concat([pd.Series(0),ls]).reset_index(drop=True)# 首行填04 b* g \" \9 S6 P3 U& ~
del ls[23911] # 末行删去$ M0 n. A: h5 f0 D
df['len_questions']=ls
% P' j( j4 o! edf.groupby(['Name'])['len_questions'].sum().sort_values(ascending=False).head()
1 K7 ]# B5 R8 g5 [3 ^+ o; E. P. n* m% s% U3 l9 d* `4 @6 S
Name. |# ~# o0 c9 \! Q# x
tyrion lannister 527
( E: J3 h, `8 F( _0 {, Xjon snow 374
# D7 g$ j3 R/ f3 wjaime lannister 283
; v# k7 {1 B) \" g, jarya stark 265& h/ {' O! s. i( R
cersei lannister 246
/ L# k6 w* |; ~2 N3 `Name: len_questions, dtype: int64
% e& {, K& O0 V6 ?4 n
: N5 j; Z) V( P; n c# 参考答案
P4 o, }$ A& \8 y% ks = pd.Series(df.Sentence.values, index=df.Name.shift(-1)), l( H7 Q; I g4 M8 w, l
s.str.count('\?').groupby('Name').sum().sort_values(ascending=False).head()) S$ T2 F- k7 _( r* T
* Z. k( n; V/ e
1! e: c/ g1 v, d/ X# v. b2 p# l; b
2 O& n& }$ R0 B( g
3
& G# S0 s% b" W% B; Y/ `4* e3 c( t3 ]1 e2 H0 z" A
5
1 u( R9 ?8 ?5 A0 \( ?6
+ V2 p6 ]: m4 L* h71 H7 q2 ^' Y i9 n$ c/ y' Y
86 b, `+ M5 A# Z. ]" Y! r
9
. Y2 o4 v& D/ e7 }2 ^* q# T10
9 [1 k3 `1 v f11
' p# F7 K. m) V8 b3 b5 z: Q12
- c; ]3 d1 A' Q% P t" f13
; D7 }: q8 E- k' U14
& Z, V, B7 N/ P( Z15
; c; ]) ^' H% u% J) Q* k) p; i16+ B% k/ e+ ?" n& e! J
171 a! U$ g' Y$ \3 j! F7 F
第九章 分类数据
2 T. ~+ ?+ _! e* z$ Mimport numpy as np
6 F K( J& q- P8 yimport pandas as pd
% m A* y8 `3 |8 j1 t1
/ e; }& D2 K* n H2
% E, ?# K3 s# o# I9.1 cat对象% i# i5 c( h; u5 M
9.1.1 cat对象的属性# o1 \& f3 v4 j+ `
在pandas中提供了category类型,使用户能够处理分类类型的变量,将一个普通序列转换成分类变量可以使用astype方法。
1 `' f/ @7 Q& B$ N5 k" l. l$ D! l, u1 C) @& ~9 b4 ~. L3 u9 Z7 p# p
df = pd.read_csv('data/learn_pandas.csv',
' R3 G4 S; c2 ^; t usecols = ['Grade', 'Name', 'Gender', 'Height', 'Weight']); Y# U3 f, ~; u( c- ?& G7 K( A
s = df.Grade.astype('category')" M0 |0 W* K+ a) N2 A2 k
5 H; \# T1 S6 Y) I9 j* C5 {s.head()' Y0 _- F7 r+ r6 w
Out[5]:
, z- ^3 a3 q* `$ I4 E$ `% W& w' e0 Freshman
: a$ v# E) ^7 ?! {, M1 Freshman
, x. l5 f% l7 Y0 ?. _. |* E0 K2 Senior
! S# V7 J7 q2 _3 Sophomore
_1 v6 e" u; s3 D2 C$ `# Z( @4 Sophomore/ S+ r4 C. }* N$ \: P9 {% m) G
Name: Grade, dtype: category0 x, C" T9 L* k( }6 D
Categories (4, object): ['Freshman', 'Junior', 'Senior', 'Sophomore']
) q4 R, j' d% b1
: \8 a3 U# B6 i5 u# ~2) M$ N' X) J( P7 \6 [5 [: S
3" w3 P, V$ I+ P h q2 z" E- B
4' }* t& K* h. V% r* a4 {) {/ S( T' \
5, R4 } r/ D: N% C. `+ G5 `, |& W: ~
6
: `, K# I/ t0 O& A5 v7
/ \# }/ X; P6 B* l& M9 Z: i' O8
% L4 O" U. d$ k8 T: c: d9
9 }- U/ a3 A% h104 l: y D/ W$ A( T" ]/ r1 G
11
, K+ d. T6 Z0 ~% `% T12
, P2 E9 l6 q0 n: L135 h# A. W$ I! w/ d4 }
在一个分类类型的Series中定义了cat对象,它和上一章中介绍的str对象类似,定义了一些属性和方法来进行分类类别的操作。
# z- w3 ?$ A0 h F! a2 Q2 m9 @
( a" v% l5 m- U$ h* h* Us.cat
& c; c# Y, i- ~Out[6]: <pandas.core.arrays.categorical.CategoricalAccessor object at 0x000002B7974C20A0>6 ]6 H( R+ e) f: a1 d$ X" C
1, B( o Y/ V d
2
2 }7 {" l7 d( ^3 m; m: wcat的属性:
9 A9 T$ l/ `3 t1 J% r7 `9 D. z: R/ ~9 v0 U6 T- U
cat.categories:查看类别的本身,它以Index类型存储% x/ U! u9 p9 g I- X% ^
cat.ordered:类别是否有序- \+ C& g. r+ H' e
cat.codes:访问类别编号。每一个序列的类别会被赋予唯一的整数编号,它们的编号取决于cat.categories中的顺序6 S& W; G+ Z4 f3 c& A/ {' i
s.cat.categories
4 d+ S a* M) ^, iOut[7]: Index(['Freshman', 'Junior', 'Senior', 'Sophomore'], dtype='object')& J4 {+ B$ N. }+ L6 t; o
, |. u9 U9 ~+ b3 g5 zs.cat.ordered* K& v# ?( \( w m% z m
Out[8]: False4 [- ]: b) k% [
0 T! S. z& J: b% us.cat.codes.head()
* D n' a& G' E7 v; HOut[9]: 2 w! a6 y4 p7 V$ P& J
0 0
3 M" ~6 K# ]) S1 0* V2 k+ V8 V3 X3 v! P1 u' |
2 20 ?, V' S$ P# g9 C
3 3
! \) n1 [. g7 v, {; f2 b" F4 3
2 l% p/ D' [/ ~9 x9 H6 qdtype: int8& l4 m. ~3 s+ H; m
1; n& `" T6 C; K1 E) R
2
& R$ \3 m+ O/ t) M( [9 h6 A3
: V# w; i) d: t, I/ c: G$ z4$ k) H; M+ o0 w; Y2 F! f9 _% ~4 D
5% {! K3 v6 r, y F" g
6- a9 t* x' ^3 Z' K0 j
7
. i' a. q3 ^: D9 n" V4 w+ _8: u! w# t$ Z. a N+ o2 }
9: f3 T) o l2 I$ R. J0 M: R6 a$ i
10
3 m8 A k4 |8 {# t/ {11
* K0 c- @+ h4 `0 d( C. h! ^12. ]2 n$ O( f: D$ V$ z7 L# w
134 }" ~* K9 @# g, N% B- R& H. [
14 y l& m, n2 z7 R2 {( ~# d
9.1.2 类别的增加、删除和修改
8 f9 g& l4 _% a. ~3 s$ c 通过cat对象的categories属性能够完成对类别的查询,那么应该如何进行“增改查删”的其他三个操作呢?( J; ?! H' n* ~' ~3 k
& T, w/ L6 J; w【NOTE】类别不得直接修改
( |: Y9 F$ W" M2 I8 U2 ?7 Z在第三章中曾提到,索引 Index 类型是无法用 index_obj[0] = item 来修改的,而 categories 被存储在 Index 中,因此 pandas 在 cat 属性上定义了若干方法来达到相同的目的。
; }- L# b2 n% ~$ |( \
; s3 |. Z O( G4 n0 [2 nadd_categories:增加类别' _0 H& d$ [" |. U. B- \
s = s.cat.add_categories('Graduate') # 增加一个毕业生类别9 y5 P1 J! y# r ]5 \
s.cat.categories
% m' y4 `$ \* C' V
5 b; W* l6 d, S) L- l$ QIndex(['Freshman', 'Junior', 'Senior', 'Sophomore', 'Graduate'], dtype='object')7 T7 u+ ~/ O) h K! _4 S
1
" N. @4 ], _* X9 Z; E" ^/ J2
) B4 u8 s" e, d; N39 w4 b, P' ^; Z6 |
44 `/ ]4 a! ?( R4 c
remove_categories:删除类别。同时所有原来序列中的该类会被设置为缺失。7 o/ B. e2 a' ]( K
s = s.cat.remove_categories('Freshman')* T8 B% K/ x' F, ~; z
1 \5 X9 Y% R4 X3 @ h+ ?s.cat.categories
) t8 k) h0 X9 E! I5 \Out[13]: Index(['Junior', 'Senior', 'Sophomore', 'Graduate'], dtype='object')
6 T$ _8 L% M8 m3 p c5 w$ I8 v/ _$ L
s.head()
^& c7 U& b% y0 W8 k9 b: AOut[14]: z' Q( V j7 `5 G
0 NaN
1 C- C6 ~1 Z9 s- ^: e$ p- q% h3 s* W1 NaN
- I3 b+ Z1 ]! z% V5 m% s( a/ X2 Senior# a& q8 }& Z, |9 l: D4 ]
3 Sophomore" A h) J1 U. E- D* X* a' t* e3 O
4 Sophomore
6 l! p7 S ~, X) T* v/ h- gName: Grade, dtype: category1 g. V5 v" N n$ j6 f3 {: B4 z& N$ r
Categories (4, object): ['Junior', 'Senior', 'Sophomore', 'Graduate']
) W" t9 }8 p! q' }( o6 z1
+ e8 ^6 e: O( c' G2) B z1 V2 K5 B2 P
3
" z5 c6 J7 c/ Q9 A5 Q0 U, A46 e( S, U. g+ v$ I
5
- M8 q, Z. L1 n61 S0 r' W/ r$ p" H. l0 {" ?
7
( V, M6 {# C4 n8
+ F. e4 B( R! u. }9
/ l8 S5 i" O. n6 E104 ]6 D3 P5 j5 u3 K2 `+ D
11( J" X. i: n! W4 R! m
12
/ a+ T! d) d3 G, V) `& w7 {13# ~8 ?$ [9 ]. \ X% U$ y8 u( a$ l
14
& _: j- C3 J4 s1 n8 _3 S3 aset_categories:直接设置序列的新类别,原来的类别中如果存在元素不属于新类别,那么会被设置为缺失。相当于索引重设。9 G3 g( u+ {; g' A2 j7 T! j
s = s.cat.set_categories(['Sophomore','PhD']) # 新类别为大二学生和博士# K4 `$ J$ e e/ J% k' f
s.cat.categories& x% z! Y @" G/ P* G H/ j- m
Out[16]: Index(['Sophomore', 'PhD'], dtype='object')
4 H$ @/ [9 Q" p9 k1 D) f* ?0 D9 W% e% F" `( m
s.head()
9 _) f: P' S5 f- J: O. mOut[17]: & z* P; ^; j" i8 Z: J
0 NaN
7 N6 Z; [: h7 |1 NaN
; A) P* ?9 s7 ]2 g2 NaN
) `5 n. A% Q0 e, z0 C6 F3 Sophomore, N& q: q: F: a. B4 I; L
4 Sophomore3 D) M4 ]' N& C- z
Name: Grade, dtype: category% Q2 A$ s; ]3 d( D; c& O& D! b
Categories (2, object): ['Sophomore', 'PhD']9 {2 A( i7 W0 z* H r: `
19 p5 Q* @' d' c3 E& y# p2 ?
2( ]0 q% g. B. q
3
8 O- U$ N5 R$ F2 ]* i" b( I4 c4
( y- M7 A4 X. O% j# U! [6 m' M) {58 i# e$ }$ t- e; o- w
6% z; L$ D) u6 R
7
& `% ~& [1 Q h) p83 L( c6 ~1 D. b5 F7 h
9
3 G, L. u6 D& C. H4 k10
7 l' A# o; Z0 |2 Y! ], n11( x" l5 p: J6 e6 G7 B% M
12
0 N) m* D+ v( V/ H( ]7 X+ I* k0 z131 y/ N. a4 f' P6 t2 l0 x. t
remove_unused_categories:删除未出现在序列中的类别8 j i4 L: `+ s3 L8 b
s = s.cat.remove_unused_categories() # 移除了未出现的博士生类别, F4 M/ U. K. S* f* ?) L
s.cat.categories- X: K% R1 Y- c8 ]" q& n/ ]9 y Q
8 b( G8 q3 ]$ X8 o" SIndex(['Sophomore'], dtype='object'). o* h3 i9 _) x4 S
1& b1 D' R* C; g V g' c
2
0 l8 c. b& v! x3
+ E0 u$ A H: D; k4 X. N# A5 O44 q8 r+ K6 h7 L ~( I9 H, p
rename_categories:修改序列的类别。注意,这个方法会对原序列的对应值也进行相应修改。例如,现在把Sophomore改成中文的本科二年级学生:
" E/ m: E: u$ G& y/ O7 }: ys = s.cat.rename_categories({'Sophomore':'本科二年级学生'}), I: c+ T% ?; p F- t) R
s.head()
2 u8 g' v# ?6 w3 _8 Y( e; S+ g- h& F( P5 E' L
0 NaN
9 `6 U4 Q* [/ d; Z k2 B$ ^1 NaN
. `, }/ [/ V/ C* }$ C& ?, a V2 NaN. m& K1 M6 U1 H+ F8 @+ X, S' y
3 本科二年级学生
3 [) P$ V& x) L& t4 本科二年级学生; e# q2 x% M5 m# f- j- G
Name: Grade, dtype: category8 |! g9 {6 S2 ^9 J
Categories (1, object): ['本科二年级学生']
; S' C, ^5 i1 @5 n8 Z1 ^1
5 W5 W8 o- M# x4 Q* b20 m v* h7 V2 V" b5 A* n
3! f6 N$ F* C' D( f+ i) I+ m
4
! r6 ^, T" G8 Z7 y* C4 [59 x" W4 ~* {% E+ Q
6
: \% {0 _" s) C+ p( Q$ @. T- J. u7/ D R( Y% I; w2 }! [8 t. w1 F
8
) q2 y' k. l) F2 _2 B! \1 ^9
$ i2 x% x" V7 Z6 Q0 |10
9 _2 M; T. z. o4 O3 |" f0 }5 R7 T9.2 有序分类
4 z: h D5 Q; G5 w; L+ u) j9.2.1 序的建立1 j0 @" T* W' {/ r" n& ~" M; } {
有序类别和无序类别可以通过as_unordered和reorder_categories互相转化。reorder_categories传入的参数必须是由当前序列的无序类别构成的列表,不能够新增或减少原先的类别,且必须指定参数ordered=True,否则方法无效。例如,对年级高低进行相对大小的类别划分,然后再恢复无序状态:
4 j4 e4 ], S# `
: O2 Q% @) i" O! Z! X' Ys = df.Grade.astype('category'): G8 n% j6 T1 E O: J0 p9 Q
s = s.cat.reorder_categories(['Freshman', 'Sophomore'," P+ `* r9 i" U; L% Z
'Junior', 'Senior'],ordered=True)
& d. j) y# K- ~s.head()1 ]3 @/ V: _6 e6 k) O# S! O( {
Out[24]: % `5 O2 a: |, n/ |( }+ R
0 Freshman
. M q) _9 w2 k) b$ N- x* ?7 m1 Freshman
' [, C6 b. m* W$ Z2 u$ O& \2 Senior
7 C; b# m+ U! b. q }+ Y3 Sophomore! S6 _4 r6 C; ?; h
4 Sophomore" }5 S6 S- u$ W9 P: T( M8 ^% \
Name: Grade, dtype: category& I$ H( r5 V, K m l3 @ v3 h
Categories (4, object): ['Freshman' < 'Sophomore' < 'Junior' < 'Senior']
* @, Q4 q# E& X* H! G4 [/ d% p& L$ M
s.cat.as_unordered().head()! A) d7 u' K& l: s3 @1 D
Out[25]:
, }9 i7 v6 O3 j3 K1 M p0 Freshman. B6 Y/ ?1 J- s: k% G+ M
1 Freshman
a! i& {+ k* H' u2 Senior
/ B& Q- r, N4 S* w+ T+ u- o3 Sophomore; O" M6 N1 Q" Z3 J- A
4 Sophomore' i; ^4 k3 z# l: e# T) ^ q# H9 C6 e/ w
Name: Grade, dtype: category' M1 c* B! x2 Q$ _
Categories (4, object): ['Freshman', 'Sophomore', 'Junior', 'Senior']
; @# U) O% O& x; ~* F, g* A: _3 q3 o, I( S" H
1" J2 ~: X; s2 z4 b5 Q
20 T0 f; z! b8 K( B- _% [& p. w" x
3
( a1 B* ?; @ Q* l4
; A( F) t/ T1 Q- o% w2 ~ G* M5
5 i* { @' t( L* ?) @1 {* u6
$ t' v$ \ A: s, ^& b7
! B' C* `% n2 P/ T8
3 k6 I4 v4 Q- t. s9$ Y- `2 j: l4 K7 W9 w; _# V
10
% h4 u. v+ l' ?. {( r9 p g5 q11; P I, \9 w% O; E8 I, A
12( y+ i0 j. [( G$ P( U7 i' m
13
1 i0 c6 n# z" u6 _) f14. w2 b& I( [% Q+ K2 ^
15
# E% p7 R5 E, {4 d1 L5 I$ w( B16
- ^9 Z4 \+ [7 J8 z* d17
1 H* F. r1 Z( t5 g& k. v" s) D. ~4 k18
" H+ H& V0 b ~6 l( l# V: e2 L19
+ p. }/ Z- R: o; l8 n20
" h4 y- s+ a' f; C& K n: R217 b m; [+ \. U7 @2 o; a
22
" D3 e3 [' B- _) P 如果不想指定ordered=True参数,那么可以先用s.cat.as_ordered()转化为有序类别,再利用reorder_categories进行具体的相对大小调整。4 O( I& }4 Y- l/ B
F2 f4 e" W1 g8 X# S9.2.2 排序和比较
8 \( z9 W6 K6 j K* I6 v% ?' t在第二章中,曾提到了字符串和数值类型序列的排序。前者按照字母顺序排序,后者按照数值大小排序。( L3 j3 m7 o3 k+ f7 Y O) S
( i6 v" H9 `6 @3 N/ E: C5 g 分类变量排序,只需把列的类型修改为category后,再赋予相应的大小关系,就能正常地使用sort_index和sort_values。例如,对年级进行排序:* o1 [" A! h$ G2 v- f
' [% i. j5 z# ~+ ?! l; p
df.Grade = df.Grade.astype('category')
6 u* r4 B$ d1 S6 o# B! udf.Grade = df.Grade.cat.reorder_categories(['Freshman', 'Sophomore', 'Junior', 'Senior'],ordered=True)
6 S' Z" u# h, k0 U: Fdf.sort_values('Grade').head() # 值排序6 v1 ?$ C4 t! i; s
Out[28]:
8 l% x) e0 [$ D" [* R% C) E Grade Name Gender Height Weight4 R" ` a9 O4 K
0 Freshman Gaopeng Yang Female 158.9 46.0& o, M/ ]- Z' C
105 Freshman Qiang Shi Female 164.5 52.0: w5 M1 D) F P/ ~9 e
96 Freshman Changmei Feng Female 163.8 56.0
& P+ m1 f+ {( Y! `8 H3 D88 Freshman Xiaopeng Han Female 164.1 53.08 ~2 j, k% Y# F6 V1 P8 S4 S) u
81 Freshman Yanli Zhang Female 165.1 52.0
, N/ N: l! k$ [. T1 O* D! N& m j$ {5 H# D( Z
df.set_index('Grade').sort_index().head() # 索引排序
: U, N, g+ X2 H4 u" }Out[29]: 6 v6 ]2 b/ s1 W8 r
Name Gender Height Weight
4 K* B8 Q5 ?+ V7 j/ c& z& `7 bGrade / z3 y5 }" n+ O- o3 m
Freshman Gaopeng Yang Female 158.9 46.0
+ c" k3 r/ M3 gFreshman Qiang Shi Female 164.5 52.0 L6 L$ V8 r5 o5 f. x! |, l/ `
Freshman Changmei Feng Female 163.8 56.00 t5 C4 z# v5 k3 I8 Q4 n2 Q& E
Freshman Xiaopeng Han Female 164.1 53.0
3 H8 D. M# u4 p5 ?- SFreshman Yanli Zhang Female 165.1 52.0; c$ A. P8 X, K( ~; }
- P P' W G( l, l7 J* i" T1
( @7 `3 a) h; V0 }: f1 z$ E* s2 ^! q( n* m, T
32 L* j# ~3 N5 N- s$ g- b
4
6 ] \7 D4 m9 k T5
1 h2 c: d6 M' {6 U1 t( v1 y& q6- @' I4 ~. J& r) B
73 _/ j9 N* j$ d& l) b
8% Z8 O/ t! e* B
9
. C& f& d, f, `10
% h3 ^ v0 {. N2 T& a116 u; q6 b, G' ]. |1 A5 Z: U
12 ~! `8 ]0 j; K* C. L/ b. }
13
1 I/ C! h5 w% @# b# O# l14
3 a/ E, F- v" L15! B& Q5 ]0 b( ]. F4 G
16
! U$ S; k1 W1 H17
' D: } n7 b) F3 U6 n& \18
. J) I6 S, \, i0 N$ ` a192 Y/ y5 O8 S8 ?9 e# V9 v
20
! M% I9 V4 Z3 G$ w" a0 |5 R 由于序的建立,因此就可以进行比较操作,方便后续索引操作。分类变量的比较操作分为两类:. l8 I k0 g9 }: Z3 Y: }/ q; Y
& @+ z5 K1 x1 [# o
==或!=关系的比较,比较的对象可以是标量或者同长度的Series(或list)。(无序时也可以比较)* W% f* h2 ?; o
>,>=,<,<=四类大小关系的比较,比较的对象和第一种类似,但是所有参与比较的元素必须属于原序列的categories,同时要和原序列具有相同的索引。0 {, {, L- ~7 q* g5 d; t, f7 l
res1 = df.Grade == 'Sophomore'
# I; N5 @) @7 P' v
& t- ~; y- U5 d! v% X Z zres1.head()4 R8 E5 c: E' T, g
Out[31]: 7 N7 X F, W& Z z2 ?4 ]! P
0 False
* b9 q! h: ^6 R3 F2 i1 False
: U! B( Z( ?9 ?7 K6 C* L$ v2 False
1 e4 E0 J* M2 V: j$ `3 True' `7 |! \& ?3 Y- V& Y
4 True% J c- O5 T, V' ]* A4 m
Name: Grade, dtype: bool
& B- N. J% H. \4 p' u# Z3 I% h4 n7 O9 O& R
res2 = df.Grade == ['PhD']*df.shape[0]
$ U$ s8 H: F/ L% a) X* F4 ?# ^1 f5 a, {' D, P
res2.head()& u% \6 ^+ d6 T* I
Out[33]:
! K( y3 N/ e7 S0 False
; Q; e( b* |/ z6 q9 {5 Y1 False
; @. m) F9 w6 L2 False
- H% R) p/ r6 i+ a# b' b3 False
% Y* N( G' T! C- Y+ Q0 j; H' A, Z4 False7 L1 b- M! ~8 E A7 w8 M
Name: Grade, dtype: bool3 D: f: ?" W* `9 g6 F
0 T# l$ Y4 m# a
res3 = df.Grade <= 'Sophomore'
- i0 p6 R0 M$ {& N! @& H$ `4 o
res3.head()7 Y T8 C' v& O1 ^) _
Out[35]:
+ r3 G- O) _( I' y2 ~2 |7 e5 u0 True: ~9 s$ V, W$ w0 e" `1 F' P/ Q
1 True
\( N9 p# B- `1 y/ _# Y, G% D5 v% o2 False
x% m2 r- Y; Y, X3 True
) `* }% Q$ L/ t7 {4 True H ~: z$ L9 I- C/ b, x
Name: Grade, dtype: bool
* W6 Q0 y7 d0 u: L" t: e" N+ r: Y6 s
# sample(frac=1)表示将序列随机打乱。打乱之后索引也是乱序的,直接比较会出错,必须重置索引。
* \2 Y% B( `3 [. k, J) W# ares4 = df.Grade <= df.Grade.sample(frac=1).reset_index(drop=True) 4 F% \8 x7 |4 @8 P) [- _/ a
, w, c# K. Z4 F( j
res4.head()9 H# T3 x7 c% y7 o6 ^2 B. t& t$ `
Out[37]: 4 y* @ e9 \& \3 ] B7 t
0 True' [# P( q* c& u1 g: F4 E$ ^
1 True9 {' Z, c1 v1 Y0 {
2 False
+ |" l/ I) a4 X3 True
0 |! R4 e o# L5 H6 I4 True
0 M' \* F n" F4 NName: Grade, dtype: bool
4 o8 O" \. S) |/ h
8 T& z9 I- w. W' T# `4 b& x. f1
( s$ H4 v. @" M, o/ `2
+ R8 l5 S. b& Z' ?" Y2 D8 T3
: s2 q- W* R8 ^# j# a/ O4( \. q5 t) C/ \
5* b, _5 x1 V6 o8 Y& I+ H
6
5 }# e. z3 @! h8 s3 g7
% z6 ^+ I/ h2 z6 ^8; G( `! M" @/ I0 @2 G0 N
9, R8 \ R5 o& @" |1 R$ N: l* B: l/ h* D
10
5 R2 x% r( ^% s0 Y* |3 z119 v. c) U' J* F6 G
12
' j2 {9 M0 I4 ]) m13' K1 ~/ C0 j5 [
14
+ b3 l! g4 g/ f5 d15
0 ]; X3 V6 U& c. l4 a16
8 ?+ \) q9 I. f8 t17
5 k# J* |0 X$ K+ P8 U18
& Y: E: v" s# V( ^4 A19
' j3 w7 {! W: @/ {4 d202 r& q, ?( N0 f7 v
216 Y7 V0 n* y* ?! U. J$ A# y
22
+ z Q: W( f; N, Z+ i, B23. h8 @) r- I$ {# i- {* O; i
24
2 |* m- `9 B% C25
' ?& `* |6 a" V0 x% o. o( s0 ^26
) F' O* v$ i v v3 e" e4 ]0 a e U27$ O' X# ^6 i, X4 L/ c' Y/ O
28. k) y1 f& q: l* l6 P/ j# M+ \) F5 T
29
& ]* o7 J7 b% e1 F30
6 q2 o" e1 u4 a4 {5 c31
' I7 {6 \7 V2 Z( t2 b. a5 D32
- b( Q( l6 R. K8 l4 f I' ^; x330 e# C, @0 a9 |* k
34$ o! H/ k+ z9 T" e" E3 d, k( I' Z
35' Z0 w, i4 i5 V+ B
36
+ r# D6 I" J' f' f V+ E- }# V371 J& b9 n |* R( G! J4 d0 W m
38
$ I2 w( y, F% a4 ?* Z, o39. \& k, Q$ A- K9 u4 K! P. U5 _
40' D+ r& f/ }7 c7 E4 m. P% ]7 C
413 S- W' p) Z. m- I r( d
42# T( k2 H* p s, ]
43
+ \- K5 _& u0 ^" ?+ ]44
# d1 g) z) L$ x; f) i% s, R* v9.3 区间类别
- I* R& E2 M5 d7 A9.3.1 利用cut和qcut进行区间构造/ b! O6 n k3 U) B
区间是一种特殊的类别,在实际数据分析中,区间序列往往是通过cut和qcut方法进行构造的,这两个函数能够把原序列的数值特征进行装箱,即用区间位置来代替原来的具体数值。3 V' w9 x D* Q: u8 K$ Y7 o7 _
# }, M) ?( {1 ~6 C3 a ^) c, X( g) [cut函数常用参数有:5 h$ W# i* t& _ I
bins:最重要的参数。
0 o0 f8 L& |' Z如果传入整数n,则表示把整个传入数组按照最大和最小值等间距地分为n段。默认right=True,即区间是左开右闭,需要在调整时把最小值包含进去。(在pandas中的解决方案是在值最小的区间左端点再减去0.001*(max-min)。)
/ T: W$ G9 i' {% H! Z R0 z9 D* T也可以传入列表,表示按指定区间分割点分割。
" M7 R& `& P$ U/ s+ P 如果对序列[1,2]划分为2个箱子时,第一个箱子的范围(0.999,1.5],第二个箱子的范围是(1.5,2]。* K- Q" Z, f+ \) |/ X* M6 |8 ^
如果需要指定区间为左闭右开,需要把right参数设置为False,相应的区间调整方法是在值最大的区间右端点再加上0.001*(max-min)。
2 M& w3 C8 ~ o$ Z H4 |2 w% W% Q' I4 \
s = pd.Series([1,2])
) E( n9 o# g# d# j) e# bin传入整数
) |) t2 O& L" W) G: j" r1 Y3 O
2 F0 ^* F$ {; N' i/ `9 t& N+ B# vpd.cut(s, bins=2)
$ E- G5 V' J8 z* N# u" y! W& bOut[39]: * i7 ?2 o7 d& f# E) k, m! X
0 (0.999, 1.5]+ ~ T8 T8 J" b) M4 a2 b' W/ Q
1 (1.5, 2.0]/ G( ]8 k5 i4 Y8 _! s, p- _# b/ {
dtype: category* h" a& Q. j" j8 ]. m% ~
Categories (2, interval[float64]): [(0.999, 1.5] < (1.5, 2.0]]
/ ?& ~5 P" k- T C
+ A# _, R$ y: p8 |" upd.cut(s, bins=2, right=False)
2 Y3 A8 F5 E9 u; d" bOut[40]: : m3 `/ B8 Y0 _3 O3 _1 g) j
0 [1.0, 1.5)
G% i, \" X4 C/ M) g+ N. \' \( u% z1 [1.5, 2.001)
- ~6 c u8 A; o+ S7 J. h: ydtype: category1 a9 l( w/ ^& ~
Categories (2, interval[float64]): [[1.0, 1.5) < [1.5, 2.001)]. V0 y5 g, T1 v3 v- @0 [! T0 T' q
3 d5 ]& @1 a5 L/ m) y& [
2 \% R0 X3 ]* n! g7 J
# bin传入分割点列表(使用`np.infty`可以表示无穷大):
0 \1 Z+ E5 N1 n1 f& S1 Wpd.cut(s, bins=[-np.infty, 1.2, 1.8, 2.2, np.infty])# x4 ]" A2 g* Q' ]3 \8 t
Out[41]: 7 G0 a' Y+ D4 M' Q0 V( K: |" i
0 (-inf, 1.2]
; R; M9 X* h, j( ?3 I4 p1 (1.8, 2.2]2 m" x) c9 s3 t5 l6 x0 _3 o
dtype: category1 t v. H! R8 x- @
Categories (4, interval[float64]): [(-inf, 1.2] < (1.2, 1.8] < (1.8, 2.2] < (2.2, inf]]
3 ]7 _) P! m1 M" ?4 R2 Z+ F% v0 J; Z8 w* e: k
1
/ j# i$ N, S0 F! H% |# _) E+ F2
8 v; g) L' ?- n! T3
3 I3 t( p$ r0 d( u4
. _0 ^+ q5 i) I$ {5! ~$ X2 m; A, r" e+ I% K. o, u
6- P* P7 N7 I4 _' a: ]
7 t+ Y. J$ s3 O& u
8
* t4 Z* n- H& E, l5 e# ]& a* j9
) J% x/ k* w/ B4 c5 K+ C4 ]105 C8 R# \; f7 _# V, I
11
! {9 Q. j7 |& `7 _12
; }' ]& G. r$ P% ]. j- d; t13- b" R1 K. a" I9 ?" J7 z4 i
14" I( H: Y. M, {+ G( _4 Z
159 s/ e7 H h6 K' }2 l" E6 b
16
, j1 e. J \7 |5 P* x176 g+ t! V& j$ E) E# t% s
18# L" w+ J" q4 s/ Y
19; a- {& P; C" D: T' O
20
3 [6 e9 l, C7 P9 o; n( w3 B4 R) S9 ^21# y* n9 R% ~. B: c0 w6 @, R
22% y I F0 r% W8 s* }
23
" S2 A H' [+ G; ?1 c4 K24
- L; v/ E$ O6 z25) n# U/ ^$ t$ D# v) X
labels:区间的名字
- f) A: I* G1 X; p- pretbins:是否返回分割点(默认不返回) v7 c* v; z& U1 x' N+ G6 \3 ]
默认retbins=Flase时,返回每个元素所属区间的列表8 L3 b% R+ `- w4 k# R# j
retbins=True时,返回的是元组,两个元素分别是元素所属区间和分割点。所属区间可再次用索引取值
$ n' A$ n; J7 t$ J1 J5 G" {( Y7 {' X* d" i8 Y- G* A
s = df.Weight
0 X3 Z. Q) T8 hres = pd.cut(s, bins=3, labels=['small', 'mid','big'],retbins=True): _7 ^! W# k+ T. x( g! x, j- {
res[0][:2]
/ { }, z6 N* r# J. Q# B' I4 U6 q+ @& g/ P
Out[44]:
# `# g& H( i2 E, ?- ^& z0 w0 small. p1 J! R) ~/ A0 Y1 h9 K4 B) y/ y
1 big8 p- t1 c; q. Z5 u l& J& F8 R2 V
dtype: category
6 U0 I8 _$ Q# L7 f3 jCategories (2, object): ['small' < 'big']* n* j# H% ]8 K# c5 l; c; ?( M3 S
0 e! _. g; ?% C( X8 X+ a5 n
res[1] # 该元素为返回的分割点
3 _ j4 Y: p* \5 K `. }2 S' bOut[45]: array([0.999, 1.5 , 2. ])9 ~0 t( F: T i: `$ D7 a; j5 F
1+ v& p" X( ^/ N. l2 r M
2
& Z8 ^' \8 P- ]- J3. q4 S1 W% Z3 ]4 g& ^7 r- Y
4
! @4 N% _0 ^6 p- E8 [9 q5% a+ {8 U6 E. a; s' V
6
3 T4 a: U! R& }( b. U b r7
0 s2 l0 a `5 _& q$ h2 J; c84 H# y7 U, Y+ n; L9 N
90 R4 S- J9 \/ X
10
* G9 Q# `9 p# a) a4 f2 ?11+ k, ?1 ]" |0 m. e4 h+ V7 f2 V
12
- f5 [ V( n n/ Q4 oqcut函数。其用法cut几乎没有差别,只是把bins参数变成q参数(quantile)。
' o+ O) X9 n, i0 U" C$ [q为整数n时,指按照n等分位数把数据分箱) \4 q2 z" C3 z0 h& g
q为浮点列表时,表示相应的分位数分割点。
$ u/ V4 d* T6 L r Es = df.Weight
0 k' j+ v5 z% t7 ~& d, d: L) h0 |
pd.qcut(s, q=3).head()) e: h4 v$ E" Z' d+ o! o
Out[47]: ! D. ^5 @5 F. a$ I: z$ _8 G
0 (33.999, 48.0]) t9 F# t9 u% S& f9 I
1 (55.0, 89.0]
0 T0 D: l3 F7 R" v2 (55.0, 89.0]
" u3 A7 O8 ~- G; Z4 B3 (33.999, 48.0]7 I: ~9 f% J% g7 k& Y
4 (55.0, 89.0]) P3 J/ C& V' r5 b* A9 k5 M f
Name: Weight, dtype: category
z" Z/ D5 f/ ]$ k3 R9 I! cCategories (3, interval[float64]): [(33.999, 48.0] < (48.0, 55.0] < (55.0, 89.0]]" w: n( m2 g% v D6 Y
. S4 v8 W. I9 l! ^7 Y. K1 y7 A% ~7 c. X
pd.qcut(s, q=[0,0.2,0.8,1]).head()
5 Y& T6 M/ [4 z, y" k3 mOut[48]:
0 W; T {) w& m0 (44.0, 69.4]
& m) Y/ B' i8 L1 (69.4, 89.0]6 z7 w2 C }4 G: ?# x2 U
2 (69.4, 89.0]4 [ [2 D3 l! O# w! y" B: p
3 (33.999, 44.0]) f1 v6 i) F# b& @* _1 I+ i* d
4 (69.4, 89.0], M6 G W/ U, V, j# P
Name: Weight, dtype: category
* n1 \4 C! {# a+ H; M* LCategories (3, interval[float64]): [(33.999, 44.0] < (44.0, 69.4] < (69.4, 89.0]]
! v, v6 I9 Q* N, C# s7 @! ]# ~1 w6 B4 e0 Z, I; e+ u7 a* x/ J
19 k4 V; ?- H- U6 a; o. f" k
23 ?8 G" i+ d6 |
3
1 [: o- J0 Q: C+ n( v) `4
9 d$ _1 E# j; U e' S6 \5
, ^ a) L9 w* J2 z" z3 N6& m7 X# b/ t1 V6 q, r
7, Y& B8 E( w* v ?0 o7 a; n
8
" [# E3 y6 Y4 c! }0 [& q5 K98 y' |* m& s1 [( Y
10! h* l) c8 x- k, h% U
11& v( X, S" E9 S8 ^# z N8 D; W
128 c- _; w" |* P$ U0 ^5 q: ^! c
13- z- I2 a( u( M2 c) ]! v y1 ~$ d
14
0 v2 h' D$ c! v# D- E8 z2 l3 e15
2 i7 s- x% F% p7 |16 E+ V+ N6 z P* B% q, N' `
17
* N; w( h# W- c7 I/ \18$ j# z2 h, k6 l& x. n, ]. j
19 H$ ?2 F2 Z* w8 ?2 Y# P, A
20
& i- r+ B0 D8 K6 y% N# t211 Q( Z7 |1 F) E8 Q" O* C' `
9.3.2 一般区间的构造
2 [% W5 K& D/ ?$ w7 y8 {; X: E pandas的单个区间用Interval表示,对于某一个具体的区间而言,其具备三个要素,即左端点、右端点和端点的开闭状态。
G$ A2 z' v4 {9 X
! i% X, m Q. _3 J) i开闭状态:包含四种,即right(左开右闭), left(左闭右开), both(两边都闭), neither(两边都开)。! @. K: G; t- \& |, ~
my_interval = pd.Interval(0, 1, 'right')( n, d' p1 Y; k
. B5 i- Y! e# ~7 Z/ F$ y. D
my_interval5 G7 Y( k% A( z% M' S* u9 z. V4 J
Out[50]: Interval(0, 1, closed='right')5 p+ O. E& P% I# X4 P
1
1 f% B0 y2 n4 p* W9 U( C a2: u2 M$ u+ |" S; ^# c3 P
33 z3 W- A8 \2 ^, O; t- E
4
6 K% S; X* Z1 u; s5 {区间属性:包含left,mid,right,length,closed,,分别表示左中右端点、长度和开闭状态。
( R2 `% Q5 |5 T2 c2 c; ]使用in可以判断元素是否属于区间
$ ~& }$ e( m, k! A. R% O用overlaps可以判断两个区间是否有交集:. a, i' P8 _. R
0.5 in my_interval" Z K, k! T" j+ n3 F
2 q s8 S! C$ |1 `) }$ N+ v; pTrue
; x- ~) Q! p& G9 U1 e1% V1 f( }( H' ]; v, n1 O7 l& S2 R& U
2" r) q0 W2 U0 z+ y5 ^- T& e( U
3
1 c/ ~. q1 S% b, Z( X2 Ymy_interval_2 = pd.Interval(0.5, 1.5, 'left')
& H0 o. A* k5 ^- z' a$ tmy_interval.overlaps(my_interval_2)1 U4 A& Y5 W. h
7 v* i9 M. N- P+ J9 _$ h( w8 ?4 ]+ |
True+ g9 [0 c2 S" r' b4 O; R! e
1/ Y1 j+ S4 i2 p# D' T
2; y) k4 N6 a. { o! l7 _
31 [( m j" G! ?* r8 L" \
45 J% d* k) ^: J% |
pd.IntervalIndex对象有四类方法生成,分别是from_breaks, from_arrays, from_tuples, interval_range,它们分别应用于不同的情况:
1 i+ `3 M2 W2 x) b- K. D6 ` S, ^# ]! |! e; Q
from_breaks:类似于cut或qcut函数,只不过后两个是通过计算得到的分割点,而前者是直接传入自定义的分割点:
4 ?6 R0 ]* z1 ?& W* _* I/ q B8 L/ fpd.IntervalIndex.from_breaks([1,3,6,10], closed='both')
3 j9 p$ C- [- H
# W1 `- N2 D6 t% m3 KIntervalIndex([[1, 3], [3, 6], [6, 10]],9 D2 H, m9 F7 v0 _' i
closed='both',
: P( A' J {, I4 s/ ~ dtype='interval[int64]')
4 w' } Q9 j3 Y" @9 f1; A: ?# Q6 e/ J' b- j
2
; t7 C0 ]3 A8 U% d! @& I4 P6 G3+ C, w; C5 U0 ?2 o
4" U2 ]% L: [6 Y& ^4 K! v
5( E' r$ o3 q4 R( K8 t
from_arrays:分别传入左端点和右端点的列表,适用于有交集并且知道起点和终点的情况:* Y6 n/ ?7 ?5 W: p1 H
pd.IntervalIndex.from_arrays(left = [1,3,6,10], right = [5,4,9,11], closed = 'neither')
8 K$ k! J/ {6 K* e0 A3 z% C8 M v) P4 L/ P( M
IntervalIndex([(1, 5), (3, 4), (6, 9), (10, 11)],9 k! y2 b5 R1 P" k1 l
closed='neither',
9 x! k" K2 i% I) u9 K$ a; _- o dtype='interval[int64]'). [" k! y# c" V( o1 b
1
' j9 g- m/ T+ ^+ R: s2) A( u' x V/ u8 l/ C! w
3
h: y# F9 j* Z/ h4
- O6 I( h: |- G) T8 x* q- D50 \, ~/ `+ w# q+ P) o$ z: F9 t; k! x
from_tuples:传入起点和终点元组构成的列表:, P% k' ^: q3 x3 Q; Y9 ~
pd.IntervalIndex.from_tuples([(1,5),(3,4),(6,9),(10,11)], closed='neither')! Y9 A3 C" G6 n' _' p
8 u" x; R. z7 O4 r. ]+ E# \7 E
IntervalIndex([(1, 5), (3, 4), (6, 9), (10, 11)],* `* @3 C0 p. ]5 W
closed='neither',
5 n1 f4 v" |) q7 R* @8 @% b dtype='interval[int64]')
( o1 S+ i I, {) m1
$ C( j2 T0 [: K5 l" y: M2( }( A3 w! J2 r/ @* e
3+ e L$ _ L9 e, E& ?5 Y( r: Q& w
4* H# w- W7 q- D$ N# `
59 E7 H7 H& `4 X [$ G
interval_range:生成等差区间。其参数有四个:start, end, periods, freq。分别表示等差区间的起点、终点、区间个数和区间长度。其中三个量确定的情况下,剩下一个量就确定了,从而就能构造出相应的区间:3 g# T5 W2 ]3 d" H# M& v+ _# A
pd.interval_range(start=1,end=5,periods=8) # 启起点终点和区间个数
; m6 k0 R* A' C! H1 ^0 SOut[57]:
' G w, l* m9 Z2 CIntervalIndex([(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]],/ ]5 ]- C" E; }, M( r
closed='right',
^3 E* d- ]5 L/ V: _2 W; d dtype='interval[float64]')
1 I* b: k% E% W& D9 u: r2 W& E' Y
2 u$ A: P. f$ c7 O6 \' c. k! apd.interval_range(end=5,periods=8,freq=0.5) # 启起点终点和区间长度# R& D4 A1 d+ |2 P
Out[58]:
' S( r2 @2 J$ \( h, k# JIntervalIndex([(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]],8 U& N! L H5 _7 V7 @; l
closed='right',
& P6 E. ~) x- v5 r: A7 r dtype='interval[float64]')1 o$ F" }! R5 Q
1
" p* F; C6 g ?! H' ?5 l2# M' ]" B( K: W' F
3
, u( X9 n2 W) ?! ]3 {4( ?( ^7 e. m& R, t# W( u |/ T4 F* T
5
; f4 u0 y, i( T/ t$ C7 P2 O0 D4 v6
- C P. `* H {( ~7; ~4 _5 {1 l! O2 I5 f$ _
8
& X* v! i2 Y! m: q0 [9
2 K$ ~2 T; w$ c9 r10! \: R% {& c3 d0 `! J/ g* d
11
( p% \0 F* {7 ?6 f【练一练】
1 v$ }2 b7 e- ^+ q6 W+ g 无论是interval_range还是下一章时间序列中的date_range都是给定了等差序列中四要素中的三个,从而确定整个序列。请回顾等差数列中的首项、末项、项数和公差的联系,写出interval_range中四个参数之间的恒等关系。, ?1 Q4 ]: T/ w, ~! f) L. l
9 W: ~ Y$ V3 X! l" i/ W* { 除此之外,如果直接使用pd.IntervalIndex([...], closed=...),把Interval类型的列表组成传入其中转为区间索引,那么所有的区间会被强制转为指定的closed类型,因为pd.IntervalIndex只允许存放同一种开闭区间的Interval对象。
; Q. i |& _7 F& K
7 W! D0 i6 X& |. U. O5 y0 Dmy_interval
* Y* Z7 f3 I" GOut[59]: Interval(0, 1, closed='right')( `7 C7 O9 u$ c8 Q$ l
1 O9 \& r% A1 {. D) v
my_interval_2# V4 A6 Y8 d4 v6 a" z
Out[60]: Interval(0.5, 1.5, closed='left')
# ]. o$ y3 P4 v
6 p& h- P$ |9 E0 v: s q( spd.IntervalIndex([my_interval, my_interval_2], closed='left')
8 e% X# d7 U" T6 }6 kOut[61]: ( u! x9 ]3 q. `# `7 Y
IntervalIndex([[0.0, 1.0), [0.5, 1.5)],
- l; i8 `- V+ O( e% x1 f- ^# Q closed='left',) o# p: d$ Z1 i" n' a0 ^( j/ N
dtype='interval[float64]')
# j# _- G1 k, m% {3 W% p1) j$ D- k- V/ {; w
2
( [& D6 h, v+ v% \$ G6 [3$ Q9 e* B) H5 I* U) g% u7 c1 f& z
4
$ D2 i8 L$ V+ f/ B4 M& P5- j0 ~8 Z: s, _
6
8 I1 L+ {3 C0 j7
2 |* J4 D1 j; i; C8/ j- a) c2 E$ Q
9
3 m& r5 j# U7 v! o2 Z. B103 u( s) V: x) S1 [
115 W' m! h( o* N: B5 z* {& c
9.3.3 区间的属性与方法
9 F" c/ |9 O* t- W" A IntervalIndex上也定义了一些有用的属性和方法。同时,如果想要具体利用cut或者qcut的结果进行分析,那么需要先将其转为该种索引类型:
' V; l/ j, _$ S! Y" M2 c8 `/ h
4 a. V# N: P& L9 Z0 D& w" Ys=df.Weight# |' u$ x! b; H
id_interval = pd.IntervalIndex(pd.cut(s, 3)) # 返回的是每个元素所属区间,用具体数值(x,y]表示" p+ |* L# V4 C# j3 j) H8 N1 @2 K# E
id_interval[:3]
$ |. b7 X/ \" b x& R& i K3 x" C- B) C; ] e/ [
IntervalIndex([(33.945, 52.333], (52.333, 70.667], (70.667, 89.0]],1 b) e4 }& i( t* o( N9 ]
closed='right',$ Z3 w6 |- [, u9 s
name='Weight',8 G: b" u' q+ ?+ K
dtype='interval[float64]')" _0 T% s5 ~: M0 J0 e3 u
1
5 z, `* H/ C! R& D9 P$ m2 g3 c9 t7 I' w2 n1 n- y; y* U2 j
3! Y" o" n; ]) W! F# m7 ~, ?
4 G# D/ j3 m, ], B7 ^) m/ k {: E
53 [9 S2 w$ R& q, t& q' U8 R
6
/ Z. J) J0 G6 C( l3 B& Z' Z7. g+ g( r1 ~7 {" ^
8
0 Z* Z; u: u1 H+ v7 p" I3 y与单个Interval类型相似,IntervalIndex有若干常用属性:left, right, mid, length,分别表示左右端点、两 点均值和区间长度。3 \& K# K1 N2 o4 i5 d5 x
id_demo = id_interval[:5] # 选出前5个展示
- t/ d e: e0 N) L
, }$ \) h; I# e! |& Y) o1 Y) E) ^1 \id_demo# y' ~( f% \0 o. m, j% A% @1 |# I+ p
Out[64]:
2 y% I. H; c0 FIntervalIndex([(33.945, 52.333], (52.333, 70.667], (70.667, 89.0], (33.945, 52.333], (70.667, 89.0]],- D3 n& Z8 z( F
closed='right',6 v; i2 }4 V2 c/ h
name='Weight',
) y) N. @8 F" H2 ^- d dtype='interval[float64]')
1 `, W6 D/ U( I5 v" }6 D% R0 T6 T: Q% I' z" F! f3 `2 m
id_demo.left # 获取这五个区间的左端点! U5 |. }4 Y, a8 l6 h* v. O; _
Out[65]: Float64Index([33.945, 52.333, 70.667, 33.945, 70.667], dtype='float64')
+ Q& z! l4 j4 K0 r- g1 _! [5 H2 `* }& O! ?' W2 C
id_demo.right # 获取这五个区间的右端点
( O1 ~6 Z; A# ^) v6 a8 fOut[66]: Float64Index([52.333, 70.667, 89.0, 52.333, 89.0], dtype='float64')5 P* k( p) B% X' V! L
) \! I! a# J) u5 K% z" z
id_demo.mid
5 o( M% }7 {# ]2 |" @+ a9 jOut[67]: Float64Index([43.138999999999996, 61.5, 79.8335, 43.138999999999996, 79.8335], dtype='float64')4 E! ]2 F7 P9 ?1 `# R( C# D( b+ c
3 @6 c/ s5 v9 c: `: s8 B; Rid_demo.length5 y8 z1 R1 q- J/ E# G7 Y% x" F: f
Out[68]:
; k% R G( e/ u0 b7 P$ ]Float64Index([18.387999999999998, 18.334000000000003, 18.333,
- x' Y' F& Z) i1 _# `6 ~5 k 18.387999999999998, 18.333],0 s; m; c/ w, b$ i
dtype='float64')& Y r/ m( o7 [. n% H
4 y7 j& l- s- @' F: A! a
12 k6 L" A: \5 t+ D4 n9 ]5 P/ [
2$ P' K u. E1 j6 Q. K0 v; F
3
( R- |; A. }& P0 w. T2 |4% F" n. _0 d1 U/ Y, Q1 @
5* J2 h* U7 {! v
6: K% n, |: e7 \$ p2 p
7+ S* V' ~2 }. S2 v* I U: s
8
/ D ~% ~# A1 |6 p8 Z, F9
8 Y# X& V% T) _! r, r+ O104 s Y9 x9 o1 F+ L( ^+ |
11
/ F/ u; D7 x% y12$ k! }" s2 y. |% T" v6 U; v; n' H/ ^
133 r# Y6 D8 c( }* Z4 ~
143 b" n6 @% \: ?% y' s7 @
155 e- S! M# B! } F* q4 H+ i: }4 Y
16
9 A3 p& ~: m# F s17+ ^( y4 t& Y" `9 @. H! P& c+ [% ^" ~
18
( J9 c; Y' g2 q$ n& A6 n197 t; l4 T" h; B
20
7 ^* u& `, b% R: }21
' `5 l. _" }5 L$ M8 V( N22$ t% m9 J( J2 h. M g
23
4 v) p! l4 c0 {+ c4 @' MIntervalIndex还有两个常用方法:
9 t3 s5 t& @3 f/ Ycontains:逐个判断每个区间是否包含某元素
, j) D# a) E* E1 T' K+ Aoverlaps:是否和一个pd.Interval对象有交集。
& S* Q$ L0 F+ b2 e. o" Cid_demo.contains(50)
# B- J9 @" u! q; O3 C8 j$ yOut[69]: array([ True, False, False, True, False])+ Q+ {5 Q9 S8 f t- O1 S
- z6 j# ~3 O/ a: N
id_demo.overlaps(pd.Interval(40,60))
) H$ @9 U# ?1 H1 \& }, jOut[70]: array([ True, True, False, True, False])9 z- C% O& d# H# l1 n
12 g3 }2 N( R- w* U3 ~8 Y' N
2
& f0 N* }+ f+ U; n2 T3
3 {" F, _7 n- I4
6 Q d( a0 | I" a0 L2 M( n. C0 C5
1 J7 `' F; M5 _9 R9.4 练习" q8 P- G H- l2 z7 Z6 l
Ex1: 统计未出现的类别3 \2 V6 L* g; o( t+ E' {; Y
在第五章中介绍了crosstab函数,在默认参数下它能够对两个列的组合出现的频数进行统计汇总:
; k5 k( U9 I% f0 {! ]: L0 w P% P5 s1 m" ^
df = pd.DataFrame({'A':['a','b','c','a'], 'B':['cat','cat','dog','cat']})
0 Y. T* y: o9 ^pd.crosstab(df.A, df.B)
* f/ Y; @7 N$ n z) N+ R: I$ Q, ^5 q; ?- Q' Z) E5 t! n; P
Out[72]:
: D7 k% f8 t& s% X+ I8 lB cat dog& T2 T# _; u6 P4 X3 G, C8 E. m2 w
A
/ o$ y' J; r- U6 k9 va 2 0& W# L; d2 q+ v
b 1 0+ F- S& o3 K8 j J) y4 P
c 0 1
; [6 b u! D8 a3 j1/ x. v5 f7 Y2 G: T3 s' t
2
( j0 b' T' `% G$ l3& X# y& f* a1 ]' e% z
4
* ~* \ w# F: \. x$ Y7 M52 Z8 M9 W2 X; F3 F/ |
64 G: v+ A9 \. a
7
. u. ?# l1 q2 w/ \; g3 a; Z6 G8% d( q: E$ L6 h3 U0 M& N5 d
9
- P1 R+ \1 Z6 E 但事实上有些列存储的是分类变量,列中并不一定包含所有的类别,此时如果想要对这些未出现的类别在crosstab结果中也进行汇总,则可以指定dropna参数为False:
' }# |4 Y5 j" k0 _* L' X
) i$ C2 g x; a( x( l3 C. xdf.B = df.B.astype('category').cat.add_categories('sheep')
) ]1 o8 q) e, l3 D9 P; N% npd.crosstab(df.A, df.B, dropna=False)0 s8 `7 g/ W& ]# Q' r* Z
) f; z# A7 z6 j3 `. X+ F( w
Out[74]:
# ` C( w6 T! j5 m$ u( ?B cat dog sheep
# R# g: ?4 ?- N" t1 [3 v+ gA }& p- f3 d1 P: r
a 2 0 0
" _2 d. {' N1 Db 1 0 0& B# O' J' C2 b8 ~8 [, h
c 0 1 0
6 @ d6 y) M/ Z0 j' ~1! n$ u' q6 x6 w" u
2' v( ~; U7 q& c
3( B, s# Z) e: e/ E7 w
4
. k- X6 s; K( _% v7 S5/ A* Y' J. `4 k! W3 {3 x2 [
6
0 r* D% G8 k5 F6 L; E0 z7
+ t! p) L1 H$ w( m2 U8$ o# B3 Q/ h6 \6 U- K
9( G+ K! H: |) i0 c
请实现一个带有dropna参数的my_crosstab函数来完成上面的功能。
" m; b4 X0 s7 C: M( k7 X& \( Q1 p, J/ t; g/ i
Ex2: 钻石数据集3 l4 T2 H6 X+ B2 @! @/ ^
现有一份关于钻石的数据集,其中carat, cut, clarity, price分别表示克拉重量、切割质量、纯净度和价格,样例如下:
2 o. I5 \# V3 ?3 t6 ^* n1 f
% i' G/ ?! L o: V8 O/ hdf = pd.read_csv('../data/diamonds.csv')
+ m2 U5 N6 E+ }/ n5 ~( k" jdf.head(3). y9 Q5 T2 d3 z* P1 m0 e; U
- ]& K. l& W& ^Out[76]:
1 o8 x% |4 d6 w% q& c" X- |9 u carat cut clarity price
. D# D. q1 K( `8 N/ K( J3 d0 0.23 Ideal SI2 326
9 r/ ?; C7 X4 U3 }# j1 0.21 Premium SI1 326
' _ o5 y; [! K/ R8 V, C2 0.23 Good VS1 327
+ q7 d% c$ v* e G1
% {- s- e% X; C. v6 P5 f8 [& P2: z$ E# E" ]* D
3* k: @: s( K( Q0 {; G& {
4+ L! S, y/ `1 H
5
+ B; `: s, v/ [- l7 H/ V5 ?9 i( R6; D' n5 L H7 ?. r# q! ?
7 |. b% P. S2 {* }3 a
8
# ^/ ]; e. t$ Z3 {/ r分别对df.cut在object类型和category类型下使用nunique函数,并比较它们的性能。 y+ k1 d: `6 b& G K
钻石的切割质量可以分为五个等级,由次到好分别是Fair, Good, Very Good, Premium, Ideal,纯净度有八个等级,由次到好分别是I1, SI2, SI1, VS2, VS1, VVS2, VVS1, IF,请对切割质量按照由好到次的顺序排序,相同切割质量的钻石,按照纯净度进行由次到好的排序。
6 Z7 m/ M, c% C" `分别采用两种不同的方法,把cut, clarity这两列按照由好到次的顺序,映射到从0到n-1的整数,其中n表示类别的个数。
3 l+ h c, \% Z S; `" t对每克拉的价格分别按照分位数(q=[0.2, 0.4, 0.6, 0.8])与[1000, 3500, 5500, 18000]割点进行分箱得到五个类别Very Low, Low, Mid, High, Very High,并把按这两种分箱方法得到的category序列依次添加到原表中。
0 x+ z1 ?% P1 K* z6 @- V第4问中按照整数分箱得到的序列中,是否出现了所有的类别?如果存在没有出现的类别请把该类别删除。
; a+ ~! J7 ?5 D7 B$ d; m对第4问中按照分位数分箱得到的序列,求每个样本对应所在区间的左右端点值和长度。
! |% F l. f$ R. D) ?7 {: T" ^先看看数据结构:+ \$ e, T& _: J$ P; T, j/ [9 i
, D" [" j( U* i! n7 `9 q. z
df.info()3 `+ Q3 b# x3 i. Q: d4 v* w0 f
Data columns (total 4 columns):! y$ f. c* n% \9 a
# Column Non-Null Count Dtype
/ Z% @. G2 j& y--- ------ -------------- ----- ; e# L# k9 p* ~. \; O f8 _
0 carat 53940 non-null float64
+ g2 Q) x, N. n0 _# M4 ^8 T1 g 1 cut 53940 non-null object 5 |! \: `$ f# G, X' u; \
2 clarity 53940 non-null object
, j$ }& d1 X* `1 j 3 price 53940 non-null int64 z$ f/ i5 N8 g+ x: ~
dtypes: float64(1), int64(1), object(2)
# _9 Z6 M- m" y8 C3 a0 @18 @! Z. d6 l' m# l# Y; m5 G
2
) v% e% ^3 @) x( n8 J s3
& k3 @" l: \0 I+ V) A4" h# q# C- c, I0 H9 p4 e1 G6 R: |
5
i2 `5 \* @1 C4 i' H5 r6
/ F( [& v. L% e- h4 q. g72 U' k7 p- l! Y: X. ]
8$ k" Z- Q" I5 {8 z+ \( ?( i# Y
97 f5 X. C5 J. R! q1 p
比较两种操作的性能
' u# }1 O( z; k" U* _) M; f, z& F" _%time df.cut.unique()
& h/ x1 C. B' X* a. @- q T" M" m/ s! Z" t8 [5 Y
Wall time: 5.98 ms2 l. I2 A& x. b: ^9 c* U6 ^
array(['Ideal', 'Premium', 'Good', 'Very Good', 'Fair'], dtype=object)& r: a9 I6 i3 [( s
13 v! m$ `$ E# `8 |, z1 q
2
/ o1 B1 ~- n9 d( Z% z8 M* p3
: c- P. w6 x% M, G9 q, K4
: g- c; m5 g3 P1 o, _) Q%time df.cut.astype('category').unique()
3 P% K5 r1 w7 Z, Z( p; z1 L5 j/ L: c/ \3 T6 s
Wall time: 8.01 ms # 转换类型加统计类别,一共8ms
- ]; B9 \3 \( d1 ]1 I8 N" u, s3 s; d['Ideal', 'Premium', 'Good', 'Very Good', 'Fair']
& K4 w, F, ?5 L+ J" H; WCategories (5, object): ['Ideal', 'Premium', 'Good', 'Very Good', 'Fair']' m) o8 d5 L7 ~4 S. \
15 @( ]$ K" B7 ~' o4 ~
2$ f0 n! L# W8 \7 P
3
8 o: X) D7 d. W. a3 b/ g4. J4 C5 @; Z3 `& s6 N2 w2 f
55 I5 ^; i1 k& o& m; g
df.cut=df.cut.astype('category')2 |6 N3 W9 u% \$ G! A
%time df.cut.unique() # 类别属性统计,2ms. C3 H" f* d! @
$ E, W8 o5 A' k( I4 k1 ?. Z
Wall time: 2 ms
6 }- }5 s9 K' `& |, `['Ideal', 'Premium', 'Good', 'Very Good', 'Fair']8 t& _: R4 T1 w" S! L& T
Categories (5, object): ['Ideal', 'Premium', 'Good', 'Very Good', 'Fair']
; I% }6 I9 B) M" z2 t8 j! q7 Y1
c4 N& r$ f$ Q+ l6 n8 s" I2' t3 O; u! g3 c1 I4 P
3
! i/ T- {0 U# o9 X4+ j1 x6 h6 w0 l2 l, |
5) a* z3 @4 F3 i; }( H2 I$ X
66 a0 V [; ^6 N& \, v: ]) m
对切割质量按照由好到次的顺序排序,相同切割质量的钻石,按照纯净度进行由次到好的排序。7 |- a. n& A& [2 M' u7 I7 K. e' B+ {
ls_cut=['Fair', 'Good', 'Very Good', 'Premium', 'Ideal']" `. D( h- d$ ^3 G: {
ls_clarity=['I1','SI2', 'SI1', 'VS2', 'VS1', 'VVS2', 'VVS1', 'IF']5 Q) @4 Q$ W9 ]& p7 z5 c( u: w
df.cut=df.cut.astype('category').cat.reorder_categories(ls_cut,ordered=True) # 转换后还是得进行替换
0 M9 D* P% L" r% o% a' D+ Y4 Cdf.clarity=df.clarity.astype('category').cat.reorder_categories(ls_clarity,ordered=True)
8 T$ _6 C- y! k+ i
4 x+ C4 E0 u2 v6 ^5 r( ddf.sort_values(['cut','clarity'],ascending=[False,True]).head(3)9 c* ?. H$ {0 e8 I& ]
9 v) B- L4 Q8 G4 a8 {0 f carat cut clarity price% v( M& K: q% H3 p9 Y
315 0.96 Ideal I1 2801
% p6 L' W4 k3 P( G: g, T535 0.96 Ideal I1 2826. l+ G- |1 X6 f2 h& Z0 K' P6 q
551 0.97 Ideal I1 2830' \. {1 P( l2 e# V4 h
11 L M8 m5 R" Y0 {0 u1 ?
2
; t8 O" j6 N m( B3, ~$ }7 G6 \! a/ `. n2 Y. @7 ^
4( {" z1 A9 U0 j! Z. M* R) O( K
5
2 {) M' `2 C9 z/ o" n1 Q3 g4 E- F65 W4 v1 L M, v- y5 P1 ` L) P* j
7+ l& y: Z1 R9 y1 r( C$ [+ c
81 I [# [8 Q; m0 k6 ^3 S# g
9% Y2 d8 a( C, n( c& O
10% N- n8 M* F1 I% W- w" g2 r0 k$ ~# z
11
; N7 M0 z0 K/ o( y8 [/ y/ ?5 X分别采用两种不同的方法,把 cut, clarity 这两列按照 由好到次 的顺序,映射到从0到n-1的整数,其中n表示类别的个数。) Q/ U6 z' F7 [6 t
# 第一种是将类别重命名为整数
$ R# w6 B$ O3 c6 ]' Bdict1=dict(zip(ls_cut,[x for x in range (4,-1,-1)]))1 W5 o6 q' W, C, g( g0 _( Y
dict2=dict(zip(ls_clarity,[x for x in range (7,-1,-1)]))5 [* U5 \0 ^0 R8 i
1 |. l1 r. K7 |4 W6 F7 G1 Tdf.cut=df.cut.cat.rename_categories(dict1)( o. E) `1 I( u/ \ a+ ?' d& ~
df.clarity=df.clarity.cat.rename_categories(dict2)
+ _6 G. @ v0 t. @df.head(3)
2 b' z8 `( @% \$ o/ ^5 v1 |4 X
8 ^, r5 N8 u0 d' j9 z4 i5 `+ { carat cut clarity price
" t& I9 S" M* P0 0.23 0 6 326
, V0 j( S7 r( Y- U1 0.21 1 5 326; ~* j" V7 { |
2 0.23 3 3 327
7 {: c) g2 E' d14 f$ ^: y4 ^6 ]% J7 s& y8 D
2
( c+ u8 G# ]) h4 e" r$ `38 g g/ D% M# d6 W. {8 l
4$ N1 B5 H9 h1 I. s, E
5
; I8 }$ h3 G; b! f; b, n& z* q6+ K+ Q' V. }# |* E8 [
7
5 j6 w* k$ @) C5 z9 @! U: r8; {! x/ v3 W4 p0 i5 }
9
' {# V7 h% r( d! J& ?( r o10( q/ w" n) ~: Y" V
11
. ?* F) ?3 H4 c$ |) I9 B12
( {" S8 j6 p8 `6 e3 ?$ ^# 第二种应该是报错object属性,然后直接进行替换
1 P4 M, K5 o0 J4 B) ndf = pd.read_csv('data/diamonds.csv')* i) B) P+ }8 e( Z* J3 P1 A/ `
for i,j in enumerate(ls_cut[::-1]):# ^! _! H( k1 f* Q$ Q$ x
df.loc[df.cut==j,'cut']=i * x( t/ O W% w+ _" M
4 f6 a/ k! A9 ?
for k,l in enumerate(ls_clarity[::-1]):
6 a( \: ~5 z* T- ]! w: b2 [* E df.loc[df.clarity==l,'clarity']=k0 j, Q+ q2 Y. H) S# b
df.head(3)6 w9 V8 }9 y. S- Z/ _: p9 j2 p
: C- i4 F# Y: r2 U: Q; a
carat cut clarity price: a) C# y/ r/ S% z9 y# F
0 0.23 0 6 326
. V' i5 c2 i) Q9 T: G A% ~ p1 0.21 1 5 326( X w) R6 F m2 {
2 0.23 3 3 327$ {7 S: a, |" `6 b8 H: l3 p t
1
8 N1 y0 ^4 x: _: d, I1 {; N2, y. J" y5 E+ |2 y
3; l+ q, y$ [9 W- s" B) o
4& u4 U- r; i- M8 t# _
5
, b) F6 D+ C( O! l0 {67 v4 y$ R' ?# W) {
7/ k% P0 V$ J" ?! M
8
7 Q8 k$ j1 D2 d5 O2 w9 W9+ z2 e& U- M& J5 g1 k
10
! v$ x1 p s! K2 B! r& O# w5 m113 f m( U: w9 f" I
12
. Z8 {: S" N' \5 U0 A13
( m8 }6 A1 Q3 i2 s& T7 [对每克拉的价格分别按照分位数(q=[0.2, 0.4, 0.6, 0.8])与[1000, 3500, 5500, 18000]割点进行分箱得到五个类别Very Low, Low, Mid, High, Very High,并把按这两种分箱方法得到的category序列依次添加到原表中。5 I& _4 P3 b# i ~5 K2 d
# retbins=True返回的是元组,第一个才是要的序列,第二个元素是分割点/ O% Z+ o3 N. I" G `
avg=df.price/df.carat
3 X5 `- S' _" e) g- x" h! Y/ Z3 r f* X, p
df['price_quantile']=pd.qcut(avg, q=[0,0.2,0.4,0.6,0.8,1],
5 [ B0 s+ Y& } labels=['Very Low', 'Low', 'Mid', 'High', 'Very High'],retbins=True)[0]) v8 J% [- J, d9 T
3 Q: k, m: ?5 L( L |
df['price_list']=pd.cut(avg, bins=[-np.infty,1000, 3500, 5500, 18000,np.infty],
/ G# k3 x v& V3 l# y7 K3 | labels=['Very Low', 'Low', 'Mid', 'High', 'Very High'],retbins=True)[0]
$ e# N; t, ?& u! s8 i2 d4 F5 K. Pdf.head(): B/ h5 p% I Z' u
1 q' e. v6 J( I" _+ u% L
carat cut clarity price price_quantile price_list8 J$ z+ Y2 d" q: k5 c
0 0.23 0 6 326 Very Low Low/ R+ c1 B& U% j9 _, V" W
1 0.21 1 5 326 Very Low Low4 H% e6 L& X! \: W
2 0.23 3 3 327 Very Low Low' T) p% Q2 h: K/ n/ L
3 0.29 1 4 334 Very Low Low# S+ d. r5 g, b& ?- L) X% U/ n, [
4 0.31 3 6 335 Very Low Low
4 Y6 V: p" A$ O+ k1 k
9 n' w, `4 j* P% F1+ F7 R( d4 B- ^ k
2- i$ N# J" v. Y' g6 I$ l0 r
3
/ A9 O, @+ ^/ k9 b4. Y( N) Z# x' Z+ h6 Y
5+ `) _% m; n$ @- o0 A! O9 g
6; h$ Y1 ^: J0 E# a/ I8 u
7. T6 A- T+ d3 L, x) ^
8% v' d' K4 P% j! P$ d$ a3 v3 G- u4 J
9' O$ `2 p8 X6 v: e# G3 S: i2 v
10/ r/ ^, [! f# k
11/ t" N; z& B( y& i5 ^/ w T# K) }
12/ t! P5 r& b9 U; s/ }$ B& ^
139 Q: `/ q% O) E6 d! d: v' Y) i) V# d0 ]
14- E; S# f1 }+ q" x. l
15
% E& Z, `$ E/ F# b& p16
3 l9 W( @; S) ^7 }分割点分别是:
! n, K9 K+ k/ o y( l8 s( l$ O7 T
6 z: k; s9 k! K4 S" `" w% F" narray([ 1051.16 , 2295. , 3073.29, 4031.68, 5456.34, 17828.84])
3 |0 U- p6 n1 }6 S. |" b% `array([ -inf, 1000., 3500., 5500., 18000., inf])' I$ S: Q0 V; h3 d- I+ v8 f
1
/ x) K4 n0 D! N( T27 w) q4 ^: F& l* {
第4问中按照整数分箱得到的序列中,是否出现了所有的类别?如果存在没有出现的类别请把该类别删除。! R! p S+ F! \7 p
df['price_list'].cat.categories # 原先设定的类别数
& `6 v5 D9 ` mIndex(['Very Low', 'Low', 'Mid', 'High', 'Very High'], dtype='object')- L. `/ f% e4 V3 K7 E
2 B" y0 a" J' _0 e4 S
df['price_list'].cat.remove_unused_categories().cat.categories # 移除未出现的类别
* c) M5 R; p3 V: _3 sIndex(['Low', 'Mid', 'High'], dtype='object') # 首尾两个类别未出现
) A' x; U/ Q: h16 B3 l" {1 d/ ?
2
2 W. h& f9 }3 b7 W |# R0 L3
2 j$ v& A$ M) ]% [2 X: K1 p4
2 r; t9 W6 N7 X' ~; F* Q50 r, J) g6 _, B. R) g8 o
avg.sort_values() # 可见首尾区间确实是没有的
" }4 N# Q+ r% g( l/ Z7 S& B; @31962 1051.162791
2 D! L" z! A# P- {1 `8 s15 1078.125000- S8 K0 i# p" n7 ~+ O P
4 1080.645161
$ Z) x P6 ^: d1 _* ]4 q28285 1109.0909099 z8 p7 D3 W7 N: f3 e/ x
13 1109.677419
) Y8 _% c7 c& ]: P% y ... 1 x* K0 _- D+ E+ o
26998 16764.705882, v& ?" P! Q; G3 |1 {8 A
27457 16928.9719635 ]0 C5 D5 ^- m) Y, l8 ]2 [- \6 P! r, L
27226 17077.669903
% q& ]+ x) l6 A8 Y0 u, G$ C27530 17083.1775707 _5 ^7 @! I' r7 |* |6 M
27635 17828.846154
$ x |8 T( b+ O% {! w {# p( `14 U$ I7 }- V" E$ f9 h
2: \# H1 H# u: R8 \9 m
3
- F0 |! q, T; w% t5 |7 m- C4
! P8 J4 U% z9 p% i5
. T( d; ~. g1 @* S3 K% o, B6
3 K) E# Z9 z! _2 k7 Q* a! h- s7* P3 s) I2 ~' l3 ?- p' Y# |- O
8& ]" h' O* I) g" |+ g' a" z
9$ y6 f1 S* X$ F9 z G1 e( \
10
7 [4 ~4 ?- |+ q9 G/ w* }0 I& v11' q2 q5 D) ?: b
12# M, m# f$ b+ R
对第4问中按照分位数分箱得到的序列,求每个样本对应所在区间的左右端点值和长度。# D7 K1 s4 m1 e, a0 C1 ~7 B" V
# 分割时区间不能有命名,否则字符串传入错误。
v) i8 }, t' X% P, sid_interval=pd.IntervalIndex(
@. y* E+ _) U9 H- w( U* C( t pd.qcut(avg, q=[0,0.2,0.4,0.6,0.8,1],retbins=True)[0]5 Q4 p, A* R8 r
)6 R5 ]! F. l2 j7 ?0 [
id_interval.left
# M/ @" q1 l6 Gid_interval.right2 m9 ^% c) v$ X# {
id_interval.length
! ?% y! Y7 d5 Y2 F. r. M8 x) ]8 [$ @9 L& z1
( T, a. X3 K& l3 E22 Y5 I8 l/ I7 X+ s, `
32 o% A e9 C' }! Q" o% M
4/ i! |1 c& V/ t
5
+ e/ ^- j! ^; A$ J% i+ [6: A- J) A4 W4 A, S6 r* L
7
) c, F" K* L; `& s% L! i第十章 时序数据
8 l" w- J+ b! |& l( q$ {import numpy as np
! S. d, Q# \3 A3 x2 Yimport pandas as pd
1 E" |% B5 a8 e/ }1" D! p4 z- w& ?2 B" j( y8 @" ^
28 T3 X1 j9 Z+ h4 M. F% x! d+ `
# w8 n) ^$ v1 }$ a+ U- C
0 x7 l/ V4 w. R0 _; O
10.1 时序中的基本对象
) ~7 g+ z# o7 l& h; w! z0 u' r 时间序列的概念在日常生活中十分常见,但对于一个具体的时序事件而言,可以从多个时间对象的角度来描述。例如2020年9月7日周一早上8点整需要到教室上课,这个课会在当天早上10点结束,其中包含了哪些时间概念?+ l% Q; m# _; f! E
% d# t: I0 _, Y5 _# h; l会出现时间戳(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的简写。0 P% n/ m% K9 P7 G2 y; |. C. F4 }
! w# [ ?) _9 f* J- F会出现时间差(Time deltas)的概念,即上课需要的时间,两个Timestamp做差就得到了时间差,pandas中利用Timedelta来表示。类似的,一系列的时间差就组成了TimedeltaIndex, 而将它放到Series中后,Series的类型就变为了timedelta64[ns]。
) n: q5 z7 H) h/ s7 t k
* ?+ | o+ V4 P/ a0 X会出现时间段(Time spans)的概念,即在8点到10点这个区间都会持续地在上课,在pandas利用Period来表示。类似的,一系列的时间段就组成了PeriodIndex, 而将它放到Series中后,Series的类型就变为了Period。
& x+ f' H% P! b$ o. {4 q8 U4 T* s" e0 z# I. y6 B, r6 P
会出现日期偏置(Date offsets)的概念,假设你只知道9月的第一个周一早上8点要去上课,但不知道具体的日期,那么就需要一个类型来处理此类需求。再例如,想要知道2020年9月7日后的第30个工作日是哪一天,那么时间差就解决不了你的问题,从而pandas中的DateOffset就出现了。同时,pandas中没有为一列时间偏置专门设计存储类型,理由也很简单,因为需求比较奇怪,一般来说我们只需要对一批时间特征做一个统一的特殊日期偏置。 l9 q! C& I, K, V! |2 g; A' A
( B5 i$ E' E9 a* p) Q. h6 V 通过这个简单的例子,就能够容易地总结出官方文档中的这个表格:8 D @1 h- p' y9 x( L, [
5 E( _2 q; ^3 n2 ~0 L1 n) I
概念 单元素类型 数组类型 pandas数据类型
0 n$ d8 K' `" n- hDate times Timestamp DatetimeIndex datetime64[ns]7 I6 p p( g4 R, k" Q- k! Z F
Time deltas Timedelta TimedeltaIndex timedelta64[ns]
5 W5 R7 m" j, G/ t$ i0 dTime spans Period PeriodIndex period[freq]
# o# Y% i( j- x) y1 n* uDate offsets DateOffset None None
% Q, O' ], K+ }3 b 由于时间段对象Period/PeriodIndex的使用频率并不高,因此将不进行讲解,而只涉及时间戳序列、时间差序列和日期偏置的相关内容。* p; y& n& a& W3 W: l
. p8 x& s) B1 {10.2 时间戳 A4 C+ C* |4 J6 G( x; Z
10.2.1 Timestamp的构造与属性
: N: U# o& D; K" {: V0 n9 R单个时间戳的生成利用pd.Timestamp实现,一般而言的常见日期格式都能被成功地转换:: U& e3 @% r8 F% e. J( ~" @
6 H' y9 ]1 y- Y$ E, D9 ~
ts = pd.Timestamp('2020/1/1')
# e( f; A) c: \( h5 x
1 h1 E n! f! K) B- Its$ D, S; [- u1 M" G: f* o% Z
Out[4]: Timestamp('2020-01-01 00:00:00')
% s9 `0 K, E1 C3 l Z C, A
' T- H3 W6 M) Q' m rts = pd.Timestamp('2020-1-1 08:10:30')
# t$ @2 V2 p3 o' {! d8 q. c! U0 P' d1 P( ]6 \, g
ts( i, C0 M$ X- c, Z- g
Out[6]: Timestamp('2020-01-01 08:10:30')' k# v! J7 {: I- @5 f+ l% @% s; i. x) |
1: |6 t: j6 r0 i+ I/ f
2
9 a4 t1 @& w+ W7 o' j% P* ]* U! n3
+ f/ D& d: Z. }% Z+ }. a/ T4
! O# y# Q& c# u5: X5 H" C" b4 \6 H
6
! k' ]% E1 c0 H2 a2 I" S6 r4 n1 W" R7. P8 {1 T9 g4 _% S M/ `
8
6 c( ~4 s( F' c/ j9* ~5 h# C. j0 b" b& f9 `
通过year, month, day, hour, min, second可以获取具体的数值:
H5 c" ^; x- H! U( \7 m: e6 ?; R9 x* n/ ~6 m: s+ e' g
ts.year) }, L" b0 A8 V* }, Q
Out[7]: 2020
" H3 m1 |( Z- T! m; ?4 ?: y5 {; m
9 |1 z D8 W+ j8 U5 Lts.month0 Z/ {" G3 {6 l" W: n& V3 n8 j
Out[8]: 1
) A, N- \, f8 z3 k& Q: c7 c8 W" e6 j* f$ n3 Q( t: c1 T
ts.day2 `8 _* |% R+ f2 f* U
Out[9]: 11 V5 G1 `$ j. q. z, F5 x& ^
+ F9 E$ ~) ]# |# D/ C+ |
ts.hour
; j, w1 n& [2 s3 ^Out[10]: 8
& ^( W# v* o1 [! [" y
* j E0 K. _" j' Mts.minute! L4 q# L+ V! z/ n& w6 \; p6 T$ N
Out[11]: 10
! Z5 a9 {6 ^6 w# t& c
. h' c, |/ P2 k4 Dts.second
# s- h3 u+ t* C& S0 \1 n# B8 Z. \4 kOut[12]: 302 K9 V, H8 l* V9 |' P
8 L- v5 U( n8 T Y% D1
" D. P9 t2 h& o+ n0 M% P0 D. F" M20 }6 C) _/ \: R3 k9 t
3
9 s( L: q+ f; X. z9 F% S3 N) P44 U! L' q7 G4 b* c3 Z7 x$ Y* C
5; b, h r% `9 B. q" q3 L. N$ R
6. R: U" ~ c9 R
7: w7 S# s7 j, ?' z* E0 O5 a0 w
8+ w% K$ \* d1 D% c
9" i, r) @: f0 C w* `, P
10- Q4 M5 Y3 g+ i$ Y, g$ r
11+ q" s2 u* T6 ?0 w- T
120 m- h$ U3 ^9 c, w5 @9 H' \2 r
13* a$ R. ?6 U* b4 C& a* F
14
K4 K' W( a9 w15
# F! @. V2 A9 `$ Y9 o16
/ J4 }5 X# M6 M6 {& l, r7 Q179 ]" D# q8 z# Q5 G/ q
# 获取当前时间
# j$ l# j1 I. R* ynow=pd.Timestamp.now(). A: ^. |* B/ b' K; Q
1* X% |& s6 H$ j, v& o
2
& \: R. @' ]2 E0 _在pandas中,时间戳的最小精度为纳秒ns,由于使用了64位存储,可以表示的时间范围大约可以如下计算:
8 D: _! J# Q% K. u* N4 iT 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). g. z$ K9 d' J" h+ Y$ M( Q$ T# H! z
TimeRange=
% ?3 j7 t& e" X10
{. f6 v+ Z, I& D9
6 Y4 S' k: D" t+ o% l7 n ×60×60×24×365
4 ]1 N$ X, g" H2
; E) @/ G" S' p64( h+ P# V5 M' j
1 N# E/ z+ o# b4 ` j! T- Z. ?6 ]
9 F# D1 I/ r% ]/ A8 U; B
≈585(Years)
5 w _. g0 i% T5 m/ v. Z# f7 H1 i) A4 g, j3 C6 c7 @( W
通过pd.Timestamp.max和pd.Timestamp.min可以获取时间戳表示的范围,可以看到确实表示的区间年数大小正如上述计算结果:
" M8 a ?3 @3 P0 R8 w; v3 Q' b( m/ |' }, l4 e
pd.Timestamp.max
- i! e' s* x6 O C. q3 ], aOut[13]: Timestamp('2262-04-11 23:47:16.854775807')- K% X( c- I* k6 o5 C1 ~& h: @
N. v4 N: V8 p+ ^; K
pd.Timestamp.min
6 U" m e0 z8 F1 ^" iOut[14]: Timestamp('1677-09-21 00:12:43.145225')
4 @+ Y1 A( b" I6 B
# c+ z: s2 I. W( o' _8 Y$ wpd.Timestamp.max.year - pd.Timestamp.min.year
( I! b/ o0 W# T" B: E: GOut[15]: 585
% R' M9 V1 U7 U A' L1
7 g. F8 ~6 G: I; H* O2
9 Q/ C% G$ v- r# q. q3; D! _8 E: R! E/ ~/ f! y, a
4' |$ _5 Q W z
5
* h/ j# S3 ?5 ?8 D2 q6
6 Y {" x [8 Q: d71 p; h' l9 T$ X' a3 K. z
8
5 P. X6 X: K5 J6 @% F10.2.2 Datetime序列的生成
4 f9 O! X& W T+ F7 q% c2 A# u) {pandas.to_datetime(arg, errors='raise', dayfirst=False, yearfirst=False, utc=None, format=None,( [ [# _+ l+ g9 w, U9 v
exact=True, unit=None, infer_datetime_format=False, origin='unix', cache=True)
' x) Q8 q1 h" d. |% a E: x16 ~6 _: i+ a7 M5 n& m
24 n0 F& r: M) ^+ o6 J
pandas.to_datetime将arg转换为日期时间。% {! P e* R' `+ D- L
4 u( M0 h/ i/ |' X8 d
arg:可以是argint、float、str、datetime、list、tuple、一维数组、Series、DataFrame/dict-like等要转换为日期时间的对象。如果提供了 DataFrame,则该方法至少需要以下列:“年”、“月”、“日”。
5 N u0 v0 e5 F! perrors:, Q0 `9 Y8 }) d
- ‘raise’:默认值,无效解析将引发异常
" G, g y- X8 ]0 H+ `, W- ‘raise’:无效解析将返回输入! Y6 W5 O+ j% T) x% `* Z. K! c
- ‘coerce’:无效解析将被设置为NaT
: w. f$ o, }6 x @dayfirst:如果 arg 是 str 或类似列表时使用,表示日期解析顺序,默认为 False。如果为 True,则首先解析日期,例如“10/11/12”被解析为 2012-11-10。如果无法根据给定的 dayfirst 选项解析分隔日期字符串,会显示警告。9 `, e' r: }" c, s# n
yearfirst:如果 arg 是 str 或类似列表时使用,表示日期解析顺序,默认为 False。如果为 True,则首先解析年份,例如“10/11/12”被解析为2010-11-12。无法正确解析时会显示警告。(如果 dayfirst 和 yearfirst 都为 True,则 yearfirst 优先(与 dateutil 相同)。)3 e7 L5 j2 j |7 k
utcbool:默认None,控制时区相关的解析、本地化和转换。请参阅:pandas 有关时区转换和本地化的一般文档
: E7 g' y4 w1 o, c+ f9 yformat:str格式,默认None。时间戳的格式不满足转换时,可以强制使用format进行匹配。
! a# y0 O: B0 I% I5 ^9 j7 {unitstr:默认“ns”。它是arg (D,s,ms,us,ns) 的表示单位,可以是整数或浮点数。这将基于原点。例如,使用 unit=‘ms’ 和 origin=‘unix’ (默认值),这将计算到 unix 开始的毫秒数。 h: i$ U. I' e1 ~" j
to_datetime能够把一列时间戳格式的对象转换成为datetime64[ns]类型的时间序列:# _ ~; C8 Y) U& u- {
pd.to_datetime(['2020-1-1', '2020-1-3', '2020-1-6'])
" D& J3 A+ y( b% g0 H- Q
" Q$ ?0 x/ M" Q- C% D) IDatetimeIndex(['2020-01-01', '2020-01-03', '2020-01-06'], dtype='datetime64[ns]', freq=None) X9 x: C. t* Y+ O3 z% i2 |
1
+ n, c7 }9 H/ }1 v2
+ }! K `1 L) }/ u9 x0 u3
% P; y% }1 F/ g, B- n在极少数情况,时间戳的格式不满足转换时,可以强制使用format进行匹配:- n2 g! b6 e7 v+ P4 s; T
/ S2 z; y/ [1 O; I, _
temp = pd.to_datetime(['2020\\1\\1','2020\\1\\3'],format='%Y\\%m\\%d')
5 }3 J6 B" N/ g; htemp
' A2 g! c" `3 g3 u% X. o6 w
) S; f% A& k3 } m% W/ EDatetimeIndex(['2020-01-01', '2020-01-03'], dtype='datetime64[ns]', freq=None)
) @$ ?$ S1 J; E! L( K. p1
5 L# u/ d" D/ E7 ~% A3 a' U8 n2
3 Y: G: n2 @1 S0 |* b3
+ l2 Z. S$ N% E9 C4 P46 K' K7 A) `3 b( a8 b
注意上面由于传入的是列表,而非pandas内部的Series,因此返回的是DatetimeIndex,如果想要转为datetime64[ns]的序列,需要显式用Series转化:+ M# X& H3 Z" ] q, k
# t8 [' g) p0 P* j* l. M3 R% mpd.Series(temp).head()
/ i, ~# s3 B4 }! L- R* I
% C3 D) A) h E/ O2 u& F0 2020-01-01& r5 u% c1 K. g3 P
1 2020-01-032 ~2 k: }7 e" N2 d8 m' g6 l
dtype: datetime64[ns]
* `; V6 Z: b% d17 P( A: ]1 w& d1 u$ G! ]0 Q
2
: A! x* w, w1 }3( F- Y4 [3 k1 u# x% l
4/ ^9 ^3 ~: ]5 _# o% ~) X
5
- X8 C/ |* N7 q3 X! S7 q3 g0 l下面的序列本身就是Series,所以不需要再转化。
2 m0 S' e5 u$ i: `( u0 Q: e R& f# a+ k' u, X
df = pd.read_csv('../data/learn_pandas.csv')9 O5 _5 x0 f* {. E q$ P# K
s = pd.to_datetime(df.Test_Date)* Q J7 j. Z* ~6 q3 C( P+ e% ?
s.head()8 F2 B* w ^$ H3 h8 \2 N
$ r# }& }( y6 J" R3 j0 2019-10-05
% c8 ?+ Q* e9 y( }1 2019-09-043 T( s8 [- w4 C5 K, r
2 2019-09-122 v4 j. M' }8 Y: K5 B% a
3 2020-01-03 r1 w5 J! s9 l' C5 L2 D% N
4 2019-11-06) f; N: g+ u8 \% j# }
Name: Test_Date, dtype: datetime64[ns]
+ G+ A3 M0 p- T) s- J1
. x, D3 Y5 X+ A( ]7 G5 Y25 D1 a- w$ a. y: K( N( p# L2 S' N
3) k# x- H( H5 |7 z
4+ u$ G5 x6 R/ e, g7 n, O1 }; J% V
5
9 W, [2 ^0 Z3 V5 X0 `6
. s$ v0 V0 y A, X5 z7 v( p' \! l5 j5 T% K; B1 A
8
" b( k- {( D* ]. R# [- p9
* O O7 ~/ t4 _' V2 P10
& B& \, p! x# W C2 Q# g4 s) L把表的多列时间属性拼接转为时间序列的to_datetime,此时的列名必须和以下给定的时间关键词列名一致:
8 @0 M' b6 W1 udf_date_cols = pd.DataFrame({'year': [2020, 2020], [) u: t3 v6 g. l1 V: i; `
'month': [1, 1],$ G: Q3 p$ c B
'day': [1, 2],
, }( m' A/ V# \6 g* Z* m 'hour': [10, 20],4 n- l' f) r; N5 U
'minute': [30, 50],
8 ~! O3 _2 M3 e/ p 'second': [20, 40]}). r2 H2 O6 `7 ^, M8 y' e! C
pd.to_datetime(df_date_cols)
- Y* E% Q# Y" y& j1 k& b! o5 d: s( L+ e# x3 P
0 2020-01-01 10:30:20
. S' ~1 U" n0 H% R e( @1 2020-01-02 20:50:40 B+ Q' g7 l; W
dtype: datetime64[ns]
1 K/ u( L1 Z/ t- |5 B/ M! r, p: } v1
" x8 D/ ~7 r- n9 p% ^7 w0 U0 T2
$ R' R" `5 N# [$ |5 u$ n6 R3
; J1 m1 P, M1 B5 l+ F48 q% P L- S! `+ F7 }$ Q$ \: G0 A, W
59 c& H+ Q5 r' i
69 L8 l% o) v. v+ v+ s8 L
77 L T5 _# K! v4 `
88 \& ^6 q$ c. p# D6 w
9; e" G: C/ Z7 I+ v3 S2 m) |" O
103 D) S. H+ {3 T C1 q
11& R; W; o- r) n5 g, e
date_range是一种生成连续间隔时间的一种方法,其重要的参数为start, end, freq, periods,它们分别表示开始时间,结束时间,时间间隔,时间戳个数。其中,四个中的三个参数决定了,那么剩下的一个就随之确定了。这里要注意,开始或结束日期如果作为端点则它会被包含:
$ Q: L0 v7 o5 @- x" spd.date_range('2020-1-1','2020-1-21', freq='10D') # 包含
+ K$ w* P* A! v g7 sOut[25]: DatetimeIndex(['2020-01-01', '2020-01-11', '2020-01-21'], dtype='datetime64[ns]', freq='10D')
( y7 n& k8 w- B+ S v" ~- x. r9 i' W& @8 d) R' \4 c- X( p
pd.date_range('2020-1-1','2020-2-28', freq='10D')
: w+ L- H% e3 h KOut[26]: 9 A* `8 Y1 M$ ^0 T" s! c' x5 A! ?
DatetimeIndex(['2020-01-01', '2020-01-11', '2020-01-21', '2020-01-31',9 V4 l F' I0 } I: T; W
'2020-02-10', '2020-02-20'],
8 n9 D/ ^ q. t* w dtype='datetime64[ns]', freq='10D')
3 [! w" F p$ T( l0 d
" G9 M6 j7 W8 e. o m" Q- F3 kpd.date_range('2020-1-1',
0 ]( }* l! S7 u9 k7 x& ~ '2020-2-28', periods=6) # 由于结束日期无法取到,freq不为10天8 Y. E/ X% U2 `
9 {0 `- I$ a0 M: ~" C; X2 e
Out[27]: " Q/ {+ H. F& r" A, R7 F
DatetimeIndex(['2020-01-01 00:00:00', '2020-01-12 14:24:00',
" e. |* B7 k. U* N B '2020-01-24 04:48:00', '2020-02-04 19:12:00',
: q! M+ ~3 i+ ]% h" H* o2 q! n '2020-02-16 09:36:00', '2020-02-28 00:00:00'],* o- T8 c( g9 f, G9 `% ?
dtype='datetime64[ns]', freq=None)
6 ?8 _/ f* X/ o# S* P- o2 E: r X8 A: B9 w9 Q, T
1
) T0 m, y5 x0 i2
7 A2 _( Q( W. m" c3( M0 i3 w5 Q, N9 w+ F% K1 @3 I
4$ o4 w4 S3 P, ^4 L1 }
5& g9 T) c4 T+ K( U0 A- R+ q/ U
61 ]$ y+ T8 f ]$ r( h0 {4 ~
73 ?- K9 L# n! @% a* ^8 G1 o( g
8
H0 D9 z" ~! Q0 S9
7 ?0 j) c- d) P* P" v' T10) K) m! C/ ~( N- o5 R+ I
11
+ G* B) y }& x7 c$ \+ n* r; T12
X8 _" {: l" N# k( F7 {13+ d/ j) |9 z0 R" m$ _
14! r8 `$ l4 ^) q# f) c
15
: |0 v% B+ c( [! H/ C161 F* K) R+ v7 X" K; W5 M, e
171 K4 }+ b+ k/ x% F' r
这里的freq参数与DateOffset对象紧密相关,将在第四节介绍其具体的用法。; x) `7 m- s L4 D4 {: O; a
: ]" O2 c- i; f9 F
【练一练】
. X: J* V9 D# g3 \ @Timestamp上定义了一个value属性,其返回的整数值代表了从1970年1月1日零点到给定时间戳相差的纳秒数,请利用这个属性构造一个随机生成给定日期区间内日期序列的函数。7 T- r" f+ [3 T4 M/ \/ @, }, k
" ]2 L' Y5 q8 b1 }4 R7 Als=['2020-01-01','2020-02-20']
o8 a) t7 d$ Wdef dates(ls,n):
: D- f& E0 T5 o6 z" H min=pd.Timestamp(ls[0]).value/10**9
# c( U9 X- U0 D6 t4 e. _ max=pd.Timestamp(ls[1]).value/10**9
; c2 g& T2 ]5 B. d+ M( n times=np.random.randint(min,max+1,n)/ L0 z; H* J, s8 ]: r6 C6 b6 @- ?. T
return pd.to_datetime(times,unit='s')
0 J' L# E- L. m9 e( B5 C3 qdates(ls,10) 5 m- l1 i" Q0 z2 ?( a
1 p8 f/ k8 Y' b; J; ]& r; p9 X
DatetimeIndex(['2020-02-16 09:25:30', '2020-01-29 07:00:04',2 m2 f, ^# D6 K2 {
'2020-01-21 12:26:02', '2020-02-08 20:34:08',' ?% a( `" X% {% W2 J1 a& @
'2020-02-15 00:18:33', '2020-02-11 02:18:07',5 T4 u* k3 J9 B2 w, Y- l
'2020-01-12 21:48:59', '2020-01-12 00:39:24',2 C+ k5 Z3 A- n8 O D
'2020-02-14 20:55:20', '2020-01-26 15:44:13'],
5 [; t8 h' j) H$ [" U dtype='datetime64[ns]', freq=None)! k% O2 K/ O7 l* X5 \
1
9 r, l8 k' O' D: S0 T6 @5 a2! N- `/ @9 Q% Z" ?! M) O/ T
3
8 Q- b( `# U- y41 `. L. ?: ]' Y& A+ j
58 {- ]& }7 P/ F) f# [
6( @6 W3 F) ` t: F" A2 W
7( y$ |+ P% L* q: y2 d7 U
8 z+ x# A& _" x- Z
9
# n# f9 W' V( J/ r$ ]% @10+ g3 v: U v9 u1 y$ ~
11
/ u# V* y0 n/ z4 Y123 D$ R5 \* {/ o5 L# ~& w: l" p* p
13
7 A5 v8 x1 Q8 q2 I14
: a" E5 u a) A2 H( c7 U: [asfreq:改变序列采样频率的方法,能够根据给定的freq对序列进行类似于reindex的操作:
/ l7 H/ Z( R& @0 Y! f$ N% cs = pd.Series(np.random.rand(5),
7 D& y4 T |2 f! W8 g2 W+ _1 U index=pd.to_datetime([
8 x3 P. U) ^, D1 C4 s '2020-1-%d'%i for i in range(1,10,2)]))
) u" t& A0 d2 G
5 n) z/ f0 ?2 S5 ]% r v1 [ }. Z$ z! d
s.head(): p* O. R; p9 P& _' x. V
Out[29]: 8 p& H6 F0 M9 y" j/ Y/ I7 G% f
2020-01-01 0.836578
. k% I# `* J& T* x1 K u! { ]2020-01-03 0.678419
! F ?/ E g. z& y) x4 N& y2020-01-05 0.711897- G8 l& U/ J0 V u( b
2020-01-07 0.487429' O3 F9 ^ Y, i) b, d
2020-01-09 0.604705; z+ [- w+ y1 M" D/ N
dtype: float645 Z0 z) o* X6 d1 q5 m: C+ f$ y
2 f: Q7 D& W, s* ~9 ws.asfreq('D').head()' i E. V9 `- P" F6 O" l4 }
Out[30]: # O& n7 u# b" q, ?. x
2020-01-01 0.836578
" r) N# L, N7 ~ w& \2020-01-02 NaN
2 i6 A7 j: q) r( W& k" g! U* u* L2020-01-03 0.6784195 p6 \$ t K3 P2 {7 j6 |1 _% K
2020-01-04 NaN0 s* H4 P! l! J$ C
2020-01-05 0.711897. F, z: t6 y1 n$ K
Freq: D, dtype: float64
/ h5 p' r2 s: ]2 u
- Q! l: \) x- M9 _2 W9 [s.asfreq('12H').head()
1 j3 C/ o% Y0 o4 V, qOut[31]: . A1 y: U o5 b4 N2 R' K: w/ s
2020-01-01 00:00:00 0.8365789 d, W2 N \. H* S* v- s- d
2020-01-01 12:00:00 NaN/ d0 M3 t4 Z8 ^, D0 r0 u
2020-01-02 00:00:00 NaN+ g: G) }; \, q5 ?/ _8 R+ S
2020-01-02 12:00:00 NaN5 [* v. \; W ?' z% q
2020-01-03 00:00:00 0.6784192 {) {- v+ O2 a( s
Freq: 12H, dtype: float64
* n& F" O4 {$ u+ s) N" X
$ C3 V: T6 o+ t- L. z: C1% O& |7 U: N8 \! h( ^8 p+ r4 ]
2
" Q8 E3 W6 J* k4 z- W! E31 x: h; R9 o9 [ i1 Q% y
4' k4 |3 H5 \2 E7 [. f
51 G7 S" J. q% K( G
6/ r& _8 M' m( S4 Z. u7 {0 u! W
7
# C; ^4 ^0 y* F8 O# u$ j! ]8: k4 m7 S" h7 p* _$ f+ Q& n1 c
94 ]9 j4 x7 S1 J2 R- F; Y3 }
10( v$ k6 u! W2 q. G
11$ N$ M/ G- W& F. p( S6 Y
12
5 p; `' K2 V& _6 L' b136 G2 O* W6 B* Q8 _1 u
14
, j) E, J0 ~# m1 [8 g& z3 S0 X' d15
/ Q! W6 x. k( X3 g1 v) y4 _% x+ q! U16
+ w) t' O0 v& _# A9 f" b& {! t17" B5 @$ K0 a8 d H6 {7 B
18" e- {( u8 ]4 T5 A) e
198 ]1 A) u' s4 ^, o4 x* a; h
205 T6 W4 ^7 C5 S% x n
21
8 w; J8 f1 D( O$ H# C/ w; N! f6 G22
" A' p1 G- |; T1 W23
; W& `$ ]) C r; n1 r24
( i+ W' Z5 b8 d" z0 [" D25
9 B. g5 O" D4 B26, b- z$ f2 ^+ C9 s5 J! \
272 n' B5 s& q! k5 Z1 \0 K4 o
28" R2 x5 ^, e: c3 R4 X5 b
294 O2 P v0 ]1 G# J3 ?' h* D
30
% [, D9 @+ a9 [+ `- r31
" |1 B' V' i7 w/ x1 I- O. `【NOTE】datetime64[ns] 序列的极值与均值
- C4 z$ c, e% S. b 前面提到了datetime64[ns]本质上可以理解为一个整数,即从1970年1月1日零点到给定时间戳相差的纳秒数。所以对于一个datetime64[ns]序列,可以使用max, min, mean,来取得最大时间戳、最小时间戳和“平均”时间戳。6 m7 J9 F9 x, Q2 q+ o" i
]+ B" w+ }. S3 R. s10.2.3 dt对象- y4 v" V/ Z- P8 |
如同category, string的序列上定义了cat, str来完成分类数据和文本数据的操作,在时序类型的序列上定义了dt对象来完成许多时间序列的相关操作。这里对于datetime64[ns]类型而言,可以大致分为三类操作:取出时间相关的属性、判断时间戳是否满足条件、取整操作。 z! S, c: f1 c1 Y: {; M q
4 a5 ]: z, Z% J) D6 y6 F; H第一类操作的常用属性包括:date, time, year, month, day, hour, minute, second, microsecond, nanosecond, dayofweek, dayofyear, weekofyear, daysinmonth, quarter,其中daysinmonth, quarter分别表示该月一共有几天和季度。
4 `1 b- k3 s7 E6 Vs = pd.Series(pd.date_range('2020-1-1','2020-1-3', freq='D'))* X5 k& g, H8 `) `
" ]/ @6 J. ~9 ]
s.dt.date
) l7 }. W: e" w$ r" rOut[33]: + D: W. `$ s/ t! B6 _/ @3 P
0 2020-01-01% N+ J( Q5 U: q: \7 l2 P8 P
1 2020-01-02
. c) V" r/ P2 f5 o/ S2 2020-01-03# a, |/ u3 X9 {6 `8 G# H5 |
dtype: object2 E4 ?* T& u( R3 G
, ]3 G% X! g( y: N5 t' {s.dt.time/ m) q9 w8 O4 b: |$ Z
Out[34]: 2 R! L+ `# p2 p3 C* I- o* Y
0 00:00:00
/ P$ B* u; c' p2 t$ P1 00:00:00
$ w& h. Q4 x: U# E9 ]2 00:00:00
: W, N1 q4 b$ V: K9 {, Pdtype: object8 T2 D8 L: ?: V0 i! I
" p8 S7 d- d+ h+ Ss.dt.day
; H4 ?% r! z0 U8 ?: j+ q8 POut[35]: 6 E7 b" N8 b* Z. i3 s0 ^8 h4 u
0 13 N0 d3 t- b( g5 B8 @8 ]$ b
1 2
! [& y4 J% [4 a7 x: `5 T& K u2 38 M/ i. u& e. u' D# [
dtype: int64) ]5 M1 v6 B8 @* b" s4 V/ u& u0 r
) J. }" g. L6 Rs.dt.daysinmonth
- X9 P4 C4 s2 q* f$ h" F9 ^$ FOut[36]:
# X4 F q2 ^+ N8 \& N. N' U( s0 31
/ r, q B3 K" S/ Q7 L8 ^3 o1 31# g9 A& y) ]: I! Q* y) ?$ \/ F
2 31
' K' j' o* e- B# @ ~* edtype: int64
' q& U( [4 Y! q" [# \& w4 n" M
' U" |5 w% O7 W" j9 Y1- c$ V- ~; ^; ^: M. H. K
2
" u, T/ M: O+ s3
& T/ [( J+ ]0 _4
# {. `4 F+ @* B% o7 z2 R5+ L& } p6 Z: N% }
6
6 N9 t4 U( Z7 a3 h$ h% o7: I" O0 ?1 N$ p! J t
8
7 e ^& o( {7 Y+ Y4 a4 q: u" O96 E/ s+ C/ ^$ `
10
! J* C6 ~- n' N/ h) k' j- K2 C* n111 v5 ^ J4 f: C1 k# Y
12* b$ Y5 V2 p/ F- H
13! d+ q2 R: S3 E1 `. @2 h
14
8 W7 z$ j+ x2 g2 V" M1 G$ ]) M15
5 m' [; z8 W( U# W16
# ~5 z" ^: P7 s) j" T" ?1 i4 }3 {5 m17$ c; Z. y5 F/ f9 \5 y
18
- g3 S v: Q2 J19$ T* C4 |' x( M( ~# u/ S1 _+ G
20
- D! B& X$ q% F, G1 ^! W21
. v5 t1 |" v5 F# o8 j22
: q% y4 k- ? [# {& o Y23
S p8 I& ?3 A/ \ x) ~+ P/ _24% y" ?( Q+ z9 U9 a( }" c* D! b
25: n; F* {- b) \; n# Y1 `
264 O* u [1 N& H# b: J
276 _) s; n. C0 o8 s6 S8 c2 v1 I
289 @. ?8 D% u) S- ]& K, Z
29
) }/ v/ Z v7 s* |5 P( t4 _ 在这些属性中,经常使用的是dayofweek,它返回了周中的星期情况,周一为0、周二为1,以此类推。此外,还可以通过month_name, day_name返回英文的月名和星期名,注意它们是方法而不是属性:
4 U5 B' @: t( d; ]. w, S- P0 H1 G. d
s.dt.dayofweek8 |; ^+ u' O/ [1 O" P* K
Out[37]: 8 k o2 t9 ]: `5 r" J' m
0 2$ c/ k0 ]6 U9 b' T
1 3, O- s. k* K7 C0 @0 r! }
2 4
, ~8 G$ G5 M% Vdtype: int64
4 ]$ t' e% _3 `/ a$ i4 ^7 G
$ m& V. p3 x- I5 \+ O+ `3 _, S3 Hs.dt.month_name()+ g3 O s' o. c8 f* }2 B2 U
Out[38]:
% _, d1 {0 J: }' ^7 P1 Q. }7 M0 January1 d/ `9 _, t7 v# p: A) j
1 January
1 x T. t/ P) r2 January
- x/ S# _6 M7 [dtype: object
% j$ {, c& |* U8 G8 P* [( A# x, D5 s6 ^6 j$ A
s.dt.day_name()! [8 R8 N: s) K3 f" ]
Out[39]:
5 h- t3 N( [3 S2 l6 i' E0 Wednesday5 T0 I5 D# T) V
1 Thursday
, A% Z- J& F) X# ?( R) j( _/ Z$ i/ f2 Friday
1 C; C) W" W* Edtype: object
& t k, v7 N' j2 h$ D9 \
- e8 B% Y- K7 S' n8 x19 K0 T7 p5 }# Z7 z9 K
2! x& l$ C! l% I& [
3" F5 {" d8 R- u; M& M* s* E/ B6 n
44 j9 I6 ~* E: C
5
. v H5 }: t3 f& G6
) e3 Y' f! n3 n. `* S/ u7
' ]5 n. l, ^+ T3 V+ B8
8 Y0 b3 s. ]) @$ ?% x' m9
. u9 X9 j- h* u9 y, r0 K109 ?# ?- n& e0 c' c+ f
119 T% R* C D" b( E5 `( n
12; @# I; d6 s4 i# ~
13
+ ?9 w# L" c$ W14! o8 z: H* ~- f6 M' x
15
3 i4 ?' ^0 u6 \2 Q167 D$ h+ G; C0 a: y( s2 ], w
17
1 a# x+ I0 E8 c2 f18, T0 ]7 ]2 O8 F) o# t6 B
19
$ e7 a! E2 h3 J6 d+ ]20
, g1 L% Y8 S* r- j第二类判断操作主要用于测试是否为月/季/年的第一天或者最后一天:
) i; E. Y* A1 Hs.dt.is_year_start # 还可选 is_quarter/month_start% P" J- l) V0 Y+ }/ N6 B
Out[40]:
. p) ~& d9 x3 w( x& v7 t0 True" P" [: V: Q4 O- X4 W8 u9 u
1 False
, _* f8 ^$ e0 m: c' Q) }2 False
. @, N$ v5 q' j9 _4 T3 S$ zdtype: bool/ c% i" V% b; X) R
7 P9 U2 ?" s1 J" c6 f0 Ts.dt.is_year_end # 还可选 is_quarter/month_end
3 J2 j6 C* \9 T6 T! u6 Z/ fOut[41]:
$ _% C0 ]1 {- o' |4 R9 U6 q0 False
" [3 W' Q- r4 s% n6 x3 x( `1 False5 ?/ L D. z6 T3 y6 T7 e
2 False
; Q+ E, M$ f& Bdtype: bool
7 |' G$ N: b, x/ \13 {7 v/ ~4 b6 A. X* n% x: s
2
8 h, x" h7 u0 \ s: A3
5 h1 G% P( Y: H' e1 J40 p( h( u; _5 E& K. `4 n1 j
5/ O. v! H5 T( r$ Y" j& j
6( R! d# V4 [, y! D" k
7
' ~; }/ B( m: P3 E( W) Z8
K# l- X' B: [, N- f9& ]( U3 Z: c% }& K: A2 R
102 {' M9 U4 O. r2 n& b5 I
11) Q* T6 C. a) o1 B
12
) }5 ?4 |; j% `$ E( _; k) W13
. l0 e0 \. Q6 y; m$ f! I9 q第三类的取整操作包含round, ceil, floor,它们的公共参数为freq,常用的包括H, min, S(小时、分钟、秒),所有可选的freq可参考此处。
) E9 U/ ~8 @: x- `$ Es = pd.Series(pd.date_range('2020-1-1 20:35:00',* I2 \5 H$ z6 V) Q
'2020-1-1 22:35:00',& P/ R& H) G" b( `
freq='45min'))9 k; n" v2 ^7 t9 C: G4 ^% R; L* j4 b
% M2 s8 O4 {( x7 m0 j
" s; V- j1 v W. q$ M8 x" m( n8 H# W
s
/ R6 D$ U# F* C" r' e; x. @$ COut[43]: 7 G3 P+ o' l5 T& G' [' Y& j
0 2020-01-01 20:35:00
; l9 p2 h5 E* X; o6 O7 b1 2020-01-01 21:20:00
6 W, s5 g9 y* Y$ n9 h; V6 b0 G1 Y7 C2 2020-01-01 22:05:00; q# F" p8 {) s! w% d6 H
dtype: datetime64[ns]
% N# W# d Z! k. l+ m/ E* _* Y9 a. F
s.dt.round('1H')+ E% _7 k. q% G$ J3 a \% {
Out[44]:
9 f) ^2 E: k" G O0 2020-01-01 21:00:001 W% m/ _, p! |' |% N
1 2020-01-01 21:00:00
3 F+ _3 v" t0 L$ X0 ~& U" F2 2020-01-01 22:00:00
8 }. f, c8 I: j7 ldtype: datetime64[ns]
2 {" t0 W4 P, y6 \" O
' C- ?" T' U( \# d8 l& Y2 ss.dt.ceil('1H')
; P; w. u, i# x( }! u4 w/ }Out[45]:
* T+ C% E2 C6 r! v/ b/ u# w" M0 E0 2020-01-01 21:00:00
8 ^9 p- M% V6 c ?4 h! v+ P1 2020-01-01 22:00:00$ J' J- ~) ~/ T4 o/ r5 c
2 2020-01-01 23:00:00
+ C9 o) c4 s: B! |. v% V9 wdtype: datetime64[ns]
% N5 Z1 T# O" _" U8 ?) v; ?
- u, A! H/ w$ j- J. |' Ts.dt.floor('1H')$ d8 B7 G5 f$ ?. b1 t
Out[46]: 3 A7 G! ~- M9 c( b" d
0 2020-01-01 20:00:00
0 }3 z, p/ m: [7 f) \. ~: ]1 2020-01-01 21:00:004 O' ?7 E: `7 f3 D
2 2020-01-01 22:00:00 t+ @% z: w0 Y# m# i/ O
dtype: datetime64[ns]) K# W9 O4 Y1 ?6 D! E0 s X
5 L, ~: j$ {- j7 q0 h0 Y r18 u7 w0 o5 `! I2 e6 W
27 ?0 L2 {( M4 b$ o
38 S' T! B7 j \& S
46 \5 f5 ]/ j2 [1 C% U) z+ f/ H+ l
5
8 d1 n: G( V) |6 H3 I4 Z+ l9 u63 a$ O- _" K7 n! m
7' l. r9 U+ Z0 G5 v
8. L9 Z; Y# c( Q2 V7 g
9
. m' `# g# a% ~" H0 w2 A. b" Z10( J, O( P/ I* m% V! D }# |3 j8 K6 }; A
11) b* l; ^/ ^6 H, Y) F9 N
121 g9 x6 P7 f- y' r
13! W2 g/ a* A }% ?# f1 Z# Z$ S+ n0 `
14
' v% S* h; w$ r- d9 |" J' E( ~" T15
, u$ N4 q/ \, E9 B16
5 c% T+ C8 |. J" ^17
k5 Z+ i. M" \8 _4 L; y18# B' `. M* _6 t) _6 K
197 R& o& g* B$ p- g% e# J4 m% u6 w
20
0 h* j9 H' j1 [8 G$ _5 R0 O21/ s' O$ n0 I* x- a6 \* }
22" n3 z+ I- K+ b3 B1 F; y
23. V: e; b' Q% ]9 u/ U# z0 w
24- ~3 _5 [; G9 S/ R. }" o
259 a8 | M5 d" s% q
26
) b6 E7 a1 B+ }% p3 H9 q" g27* d- V% O# r9 S1 p. Z
28
1 q0 _! @- } U2 T, b# F29
' p: N { D% W1 v/ m' q30
* v& Z4 n: S& y4 [- O2 X$ I3 l31
/ m" P1 R' |! q' |. s32! M6 n6 A/ }/ h% x
10.2.4 时间戳的切片与索引" W3 J# u, r$ u T; K9 t, r
一般而言,时间戳序列作为索引使用。如果想要选出某个子时间戳序列,有两种方法:5 H; i- i" \* v
* `( g H" P# k, c1 J
利用dt对象和布尔条件联合使用& b3 a6 _0 L2 _
利用切片,后者常用于连续时间戳。
# r( w( q% d' Q% s% Z+ ss = pd.Series(np.random.randint(2,size=366), index=pd.date_range('2020-01-01','2020-12-31'))
; c x* A3 k' {+ |2 eidx = pd.Series(s.index).dt
! V/ B' f/ W5 u0 E, q5 ts.head()- R% y* y; y3 |2 S, h
: Z) f% |) D- t( Z2020-01-01 0
8 H, t5 T1 ]0 z) }. S; {9 E# ?2020-01-02 1
% T8 j; D1 b& F& y2020-01-03 1. `1 b& H# J& r9 O3 n$ Q* h
2020-01-04 08 q! t& M! S& b6 ^% ]$ L# f
2020-01-05 0. s0 G7 ~# B- J6 B9 f/ T
Freq: D, dtype: int327 X6 {% @/ w0 s& l: _+ X2 m8 \
1
5 P. |9 V2 e+ O9 c2
# K* U/ m I$ V |9 |3
4 J+ D% J9 X3 S& W# Z4 l4
* L; _) I6 T8 a2 e5. Y/ g5 s! O: _/ o+ S' _- g- b | G/ \
6
( |) T% N3 Q0 ]" h7' a1 T; ?: u+ |/ Y
8% h" B& Q. x! n* N' m
9
: Q" d/ f7 d( A5 F10/ u6 Z1 \2 g$ m8 |
Example1:每月的第一天或者最后一天
: z% ]5 S; D' o, L/ w
; N1 \0 L( U; p$ i4 H5 h) Us[(idx.is_month_start|idx.is_month_end).values].head() # 必须要写.values
0 v5 e& J+ z5 b% W$ S7 UOut[50]: + K3 s/ v' [! d' P' N
2020-01-01 1
8 t' q: r/ A* P2020-01-31 00 N* N+ n U) ~* |* P8 c& ~* p
2020-02-01 13 ~ z2 n( A1 I
2020-02-29 1" _% q" g) e5 G- m* {: k, e
2020-03-01 0! x" V% a9 R8 X0 ]5 x& w
dtype: int32 P5 P% _7 Z1 f# w! s
1
) t4 I N5 L1 R, x* N% M% f8 _24 H& G h6 n4 c- s$ r3 b# V
38 N6 S" Z3 Z1 v
49 U* T- b' V! n5 O* N
5- g3 R6 p& D3 \( [" D
6' o* d" C2 P. c8 e. l% V
72 S$ X! \/ @0 O( Z1 {
8
: f. z/ R. |1 s8 J. t# V" xExample2:双休日' v: Y3 e- H# i6 o8 y9 Z# j/ j
$ F6 P6 z) I0 C. Q; {
s[idx.dayofweek.isin([5,6]).values].head()* m4 j- {% a7 B5 e" U' z1 Q/ h
Out[51]:
( u# @; D% G( Y* n# O2020-01-04 1. P) [, f3 y Y/ d7 P4 G
2020-01-05 0, T: U9 v r: w' @6 O2 M" J
2020-01-11 0
! s# a; U9 f& p0 w2020-01-12 1, `' _+ P% Q& o
2020-01-18 1
5 ?( \ h7 w, q' v9 `( y3 u. W8 \dtype: int32* g" i/ j5 _1 |+ C
1
# o& c0 g+ c' s2
2 z7 ~: r) x+ o, p1 K7 N9 t3" }# _! y+ c, E7 H, i# M: |& |
4* c0 x- C$ r' V3 W# Q! o2 D
5
1 w4 ^% M% j" B& l, B' j$ M7 m67 [. s6 ]1 a( y% i
70 Z; a6 n- o/ \* ~3 I/ v
8
3 D' p2 \& ?( K) ?Example3:取出单日值
& H& a( x7 I( v0 |+ l# ^" b7 k$ z* `* e4 l/ m; z* w/ C
s['2020-01-01']# y7 ~7 ^: D5 g
Out[52]: 16 s$ j' ]6 H5 u1 G1 s9 a' D
/ e! \( ^5 L. B9 L/ j
s['20200101'] # 自动转换标准格式
5 |* ?5 G3 R' v" P# |) p, XOut[53]: 1$ \8 ?" o2 Q) R; c/ Y
1, ]/ L+ b! b+ Z4 S7 @) r
2
: f2 W( o: y7 X/ d% A2 V3
/ k1 _$ A5 o2 C2 Y8 I2 |" g) S4$ z; o+ d/ b; }: q, [# F p, L" {2 p
5
3 o1 [6 Q9 N) H: g, p( nExample4:取出七月3 X8 I- }* r9 W5 s
( m1 u7 H/ O: ^5 R5 z, \2 F" K% }
s['2020-07'].head(), @( U% W. E5 p Z, u, ]
Out[54]: 8 D e) B+ Y( E$ Y' O1 W
2020-07-01 0- p7 D6 x! \0 i
2020-07-02 19 K4 P# B. j& _, W
2020-07-03 0
( Y% ]7 s6 C' M" h3 O) r2020-07-04 0
4 X) \% y j" `1 A" Q2020-07-05 0
$ E( Q. y2 D9 e/ L5 hFreq: D, dtype: int32: g( ?( u$ I- ~' d+ _$ {5 _
11 @! W0 o3 h. ?$ n3 V3 i6 E" ?( \/ \
2
5 w; [& M; b3 o; g( \2 q' i( B3
p5 A& l' z Z0 [2 e6 o$ M4
. C& i. C3 w/ M7 k4 Q) V& A3 T5
2 J% Y. ^- e* P' r' C" J: _6
& u; z3 k$ B( s% Y" {71 Y5 b% S- H: |; k
88 r3 M K. T, H& `, m( @
Example5:取出5月初至7月15日
; b3 p1 @4 E1 ~$ b2 L* ~7 a4 P, M7 o- V! A0 V
s['2020-05':'2020-7-15'].head()
$ c% V3 ^- C6 q- v2 gOut[55]:
; o9 ^1 q6 w+ ?+ w H+ G: C2 @2020-05-01 0
E9 x( d8 K+ {# ?2020-05-02 1% {" Q z" H- V! S% T. P7 n
2020-05-03 0/ {9 w3 ^& {# v1 [* q
2020-05-04 1( Q! g5 f" s* t% a6 W$ r S! L
2020-05-05 1
7 a" Q2 k0 A' S2 ^2 f! wFreq: D, dtype: int322 J* }$ ?( F+ N @
5 R+ t! S+ V$ |9 ?- d" @
s['2020-05':'2020-7-15'].tail()
: N, h. L! K0 i d) Z" bOut[56]:
$ f2 e, I% o% h s. z. U2020-07-11 0
9 K% v# B( u4 O% X4 g5 e' u0 M2020-07-12 01 o" j$ H. V0 b( D
2020-07-13 1
( D- I! m% Q+ U0 j d, F+ ?1 ]2020-07-14 0
4 w+ q( b# [, c) q! Q; N, O2020-07-15 1
. r! `; Q8 V+ m' k5 O7 jFreq: D, dtype: int32
. s: N4 w' T* @4 D
/ V) |9 c& H: T3 x3 h7 ?1- y7 a, l0 g5 M2 w4 J
2$ _4 I# [3 e' w1 ~. `2 A5 W
32 q5 Q K( d* i3 f" u5 m5 }
4 C3 @+ S* N1 j$ e- b
5. W0 P* |$ w/ {1 I) T
6
3 T3 N4 X& Z, c+ d7 D5 x+ C' ?& `) T) J' `! n
8: O8 M* N, }" b1 t) J* y* j/ A5 x
9, z3 F7 ?. U0 Q
10" A& N# }2 O7 O9 O4 S# ~5 A4 {& J
11" J* _, u4 W8 a
127 r) I+ \! _* K% w# \( w' A7 c" l
13" _) m7 y6 H$ i2 q$ C! O
14# x1 l7 g1 R1 _1 O2 s
15- R4 y: t0 |" x
16. k, n) w" _( e! k
17
0 U: R g" ?9 W d" g10.3 时间差" N% _' H: G' ?3 x2 r; I8 u
10.3.1 Timedelta的生成3 g6 E" C4 ]7 ^0 U) W1 e: o
pandas.Timedelta(value=<object object>, unit=None, **kwargs)
* E* C4 s3 C7 ]% E9 f/ V. o3 Q unit:字符串格式,默认 ‘ns’。如果输入是整数,则表示输入的单位。/ c4 g8 i ` t( S7 A
可能的值有:7 r* ~! [' w: D7 y
( \# V: e% a! h m, P& C0 X( r7 C
‘W’, ‘D’, ‘T’, ‘S’, ‘L’, ‘U’, or ‘N’
' F4 _( L ]" y6 |/ L- V% H‘days’ or ‘day’ e7 v5 o- h% L! k4 J
‘hours’, ‘hour’, ‘hr’, or ‘h’7 D1 V: ]) k/ M& j6 a6 e
‘minutes’, ‘minute’, ‘min’, or ‘m’
7 R* U4 Y2 H) N9 L‘seconds’, ‘second’, or ‘sec’
- z7 t- T" I. j5 G4 a毫秒‘milliseconds’, ‘millisecond’, ‘millis’, or ‘milli’7 I$ p7 s& ?7 {0 F% V* g& {7 y
微秒‘microseconds’, ‘microsecond’, ‘micros’, or ‘micro’
, V* H; U; h4 \% E2 \& `- ^8 z) r5 z纳秒 ‘nanoseconds’, ‘nanosecond’, ‘nanos’, ‘nano’, or ‘ns’.
?% i# d% l4 ?$ r) K时间差可以理解为两个时间戳的差,可以通过pd.Timedelta来构造:% N$ z$ I8 y) x( l q: w
pd.Timestamp('20200102 08:00:00')-pd.Timestamp('20200101 07:35:00')2 b1 z0 w) o' t1 Q
Out[57]: Timedelta('1 days 00:25:00')
' X+ B5 ^# }4 t
% X8 P4 g# J' R+ h6 [. jpd.Timedelta(days=1, minutes=25) # 需要注意加s* {% Y! ^& H) W& ?( v
Out[58]: Timedelta('1 days 00:25:00')
1 k5 }9 s# U6 ?/ Y, e
5 T" R, l! A: Cpd.Timedelta('1 days 25 minutes') # 字符串生成) Y, u$ t' z4 t- z6 A
Out[59]: Timedelta('1 days 00:25:00')
, s/ ]5 g4 V4 ~! y5 O. ?1 e# w" d. s6 L y3 N h6 x3 l
pd.Timedelta(1, "d")
1 k( h5 p; @. J5 L6 S5 S! |Out[58]: Timedelta('1 days 00:00:00')
9 [" E) b* u2 T5 e1 H5 R1
/ }5 K4 ]) U( p* p5 i3 g' H% p27 t4 v- U& F& X9 s0 K- F1 y' q6 ?' g
3
( N6 L. `# J8 Z' b& ^, q$ y& v- T+ H4
0 W" R5 l' K7 @5$ n, K/ J. E7 `7 f1 [) ?5 G
6, i2 |8 Z: H' M: a- ?' m
7& n, _. G5 q" V
8/ ^/ b3 K1 n9 L
9. l& Y& i. G! H9 E+ l/ m
10
' i& j) d/ z* }3 c- y5 l5 @11
[) N7 `- F4 ?6 l生成时间差序列的主要方式是 pd.to_timedelta ,其类型为 timedelta64[ns] :
& D: J2 o- R" T0 ]3 h3 }s = pd.to_timedelta(df.Time_Record)5 V* Y$ R! {1 q8 n5 o
% Y$ l; V# ?8 L$ Y* [/ zs.head()
9 x( r. p5 K+ `1 h3 ]5 H( D% `Out[61]:
! s0 W6 v3 U" @( z6 b0 0 days 00:04:34
" }9 t2 I' [; T5 t' W: c1 0 days 00:04:20# t- T, D) ?" r) {# q, k- u& c
2 0 days 00:05:22
( c1 d: B0 I8 k- s3 0 days 00:04:08
/ Y# C. y# M) o- @) h4 0 days 00:05:22
6 D% u+ o' T! z* |* Q6 ~Name: Time_Record, dtype: timedelta64[ns]
! F: x7 S+ {8 J$ k; \! c. Y14 V4 l5 F2 ]' X5 f; h( @2 ^* i# q
2
1 P+ h& g( j1 b3
; V) w% M$ y. \) n+ }- s4
* v P9 a* {; @, ^' s4 ~" }5, i, T% w5 z, @& Y8 ^' Z n
6
- w. l" A4 h6 H$ a% Z7
% x) i6 f8 A: l; @$ u% M, f. Z! v$ V80 i9 _) C w( E3 h
9
3 L! ]: c8 K3 |103 v% G9 S5 n! U) y# W7 t0 B
与date_range一样,时间差序列也可以用timedelta_range来生成,它们两者具有一致的参数:
# z9 M( O9 }1 f* d+ E) q& Z& ]pd.timedelta_range('0s', '1000s', freq='6min')1 j2 C; H8 h ^" Q% X/ W* s& F
Out[62]: TimedeltaIndex(['0 days 00:00:00', '0 days 00:06:00', '0 days 00:12:00'], dtype='timedelta64[ns]', freq='6T')7 f8 }& E* y5 A( E O
+ A8 U" _1 ~; z# y
pd.timedelta_range('0s', '1000s', periods=3)
3 j2 z4 |5 l q/ z) M" zOut[63]: TimedeltaIndex(['0 days 00:00:00', '0 days 00:08:20', '0 days 00:16:40'], dtype='timedelta64[ns]', freq=None)
: j# [& W) x% e$ n. H1: R: W3 j9 \4 m
2
S% X9 u2 T, R3 F" N$ h* Z* E2 e2 o& _
4. Q# r, E' {' }( X4 c; P B `+ ]
5
- n- f0 v2 N7 D8 Q' q对于Timedelta序列,同样也定义了dt对象,上面主要定义了的属性包括days, seconds, mircroseconds(毫秒), nanoseconds(纳秒),它们分别返回了对应的时间差特征。需要注意的是,这里的seconds不是指单纯的秒,而是对天数取余后剩余的秒数:
2 z! ~0 v/ u* K5 Us.dt.seconds.head()
# y, b: D% `& v# x6 s" R9 ZOut[64]:
, B. M3 A. b. G- M0 L2 B0 274
' j& J1 P0 B3 H# R! Z" q7 t) M1 260
) K$ L& m. k% C! U2 322
- |/ H4 G2 \' _% ^3 248
4 O/ I4 \" r) C- f7 _: {( I4 322
) Y* b; `# o- x" lName: Time_Record, dtype: int64! W& b; T* {+ t# W
15 Q% e$ G1 y8 J) `, q1 _* H
2! e2 K7 o' o+ D! Q1 A! w& m
3 ]7 O4 J+ S0 ?! n: o/ }
4, t3 `& A% ], R9 I8 T- ^
5& z; _* C, ]5 Y5 |) r& |* j
6
, n2 B" h9 k; r ~72 z9 k/ ^% y/ I8 u& ~& u& L
81 e) {3 v4 e2 T: {0 m
如果不想对天数取余而直接对应秒数,可以使用total_seconds
, v' V6 t* s; \2 ^7 z
! P6 g! d1 ?+ ts.dt.total_seconds().head(): B$ w. u4 H) {9 S( T6 F- Z; G
Out[65]: 9 Z1 S" d; \" R/ I3 O4 r4 f; K, P
0 274.08 S7 N( g5 i* [/ r8 C/ L
1 260.0
9 ], D& ^, K& o( n( g+ V2 322.0
7 Y; L5 p/ ~5 Z' [3 248.0
) D" o2 j+ z% |$ t' M7 I8 G4 322.0+ F4 \; @8 t2 B8 I; _1 [5 _
Name: Time_Record, dtype: float64
5 w( h' |/ }( d4 P: k19 f% @1 ]/ U: P, Z( B6 e
2
- J3 u" f, k& u' O: e( h3 v) h$ c, M; k0 w. L7 H
46 p) r: } `, m$ s4 g5 v, ]7 y- e
5
5 j! E5 O ^9 ]7 l% X. j6
& A, }8 K% ~+ S( k7
1 K P0 C: F/ f8
1 E& i3 b* F: D- O# K! q3 k与时间戳序列类似,取整函数也是可以在dt对象上使用的:
% G; Y: G* M2 X3 {6 O4 \8 f* n8 g Y: y9 Z0 q! P
pd.to_timedelta(df.Time_Record).dt.round('min').head()) [) v) K. p: _
Out[66]:
" M3 V' s6 ^. R2 @ a+ M3 l0 c, S0 0 days 00:05:00 \( k3 X' J# f$ P4 p/ z5 `
1 0 days 00:04:00
5 L! a9 R3 B. H: K R( j7 t) J; G2 0 days 00:05:002 C: ]* | L, p- Q$ i
3 0 days 00:04:00+ E0 p5 U( v5 f, G4 q4 N1 U$ l
4 0 days 00:05:00& t8 G' m1 w6 a: u8 V( q
Name: Time_Record, dtype: timedelta64[ns]
7 s L! x' p P. H15 k/ h, ^' q7 a3 J* l! |
2' ~! J4 X: ~! z8 F4 Z
3
9 K3 c0 X* r% T: O4
- S7 J9 E+ h& M5 w2 Z! P5
& J: u: t% C/ k3 l& y& C. _# ]6# N1 u; t; c9 a a7 v9 h9 Z
7
: s- w3 J) N$ L3 a0 S2 f( F0 E, T3 `% i8( o0 ^, S1 @& ]" }
10.2.2 Timedelta的运算
[+ B' [1 T: e( {6 i: E$ K单个时间差的常用运算,有三类:与标量的乘法运算、与时间戳的加减法运算、与时间差的加减法与除法运算:6 ^5 u% {, g0 a6 Y* ?6 ?
td1 = pd.Timedelta(days=1)
* v o9 {0 ?: z7 d n: Vtd2 = pd.Timedelta(days=3)+ a5 [! T, l2 o6 ~
ts = pd.Timestamp('20200101')5 Q7 E( B5 z% i; X
- m* U) F2 p l6 }( K0 V
td1 * 2: M. z) ]7 _7 W7 N& e: {
Out[70]: Timedelta('2 days 00:00:00')$ d& h% B( @5 ]# U8 e" d
. \) [' }, w/ f6 ~! `td2 - td1 J& P, t$ z! N4 r5 |) B
Out[71]: Timedelta('2 days 00:00:00')- u/ N8 E/ A+ p( @
* ]! X, H$ r! T9 _ts + td1# |; o( w( X- _1 \* N
Out[72]: Timestamp('2020-01-02 00:00:00')" {. k5 Y3 E! J; |8 ]9 E1 C/ E
8 F6 q) X( G! d3 \
ts - td1( d3 p' |9 p: \4 o, c g
Out[73]: Timestamp('2019-12-31 00:00:00')
" w! R- F# A9 p( [0 J+ d6 h1
; j8 j$ v% _% M3 W; H7 `2* y+ q6 {7 U- x) W
3
# q3 Y' H- y, [- ^) N& c0 `42 E4 ?# ^; Z% w5 B* A' s) ?9 C6 H
5
' Z% B7 D& G7 f( h68 _7 r. o! _/ N. \0 a
7
- Q2 @4 i b' Z( w! W/ m$ s8
/ J& f4 Y7 m, Z9 L- i% c C, K9
- U' N6 o& k2 r+ w/ C0 F10; t% G3 x- D5 E5 b
118 V3 D' l, i: R$ d- b
12
! M; ~" w f4 B) `0 V13
& x# O E4 S' B# i2 \14
$ s% P9 f' d/ u1 l* `2 \15
- n) ?$ c4 e! B& r时间差的序列的运算,和上面方法相同:8 F1 Y2 w7 B# H/ \8 j+ l" _0 l) | b. {
td1 = pd.timedelta_range(start='1 days', periods=5)
8 a5 X. O$ `( ^9 ytd2 = pd.timedelta_range(start='12 hours',
- h( Q3 r& R4 X0 y freq='2H'," V* U" E$ L' G8 `4 f& f% r( `5 Z% i* ^
periods=5)4 u/ R2 M$ p {- U
ts = pd.date_range('20200101', '20200105')
8 B4 G7 r8 C1 x- \2 I) utd1,td2,ts5 w* ?, P9 t5 o( y/ g6 s! W
) y- V r7 t6 R! ^, Y0 _TimedeltaIndex(['1 days', '2 days', '3 days', '4 days', '5 days'], dtype='timedelta64[ns]', freq='D')) Z5 l3 _# b) }
TimedeltaIndex(['0 days 12:00:00', '0 days 14:00:00', '0 days 16:00:00',7 \- ~/ K$ s, J' G& r5 i2 F
'0 days 18:00:00', '0 days 20:00:00'], dtype='timedelta64[ns]', freq='2H')
0 J. Q3 {/ \ m$ rDatetimeIndex(['2020-01-01', '2020-01-02', '2020-01-03', '2020-01-04',
4 e6 P I- o2 ^ '2020-01-05'],8 Y1 F U- ?( `, l
dtype='datetime64[ns]', freq='D'), O8 i4 g6 t, k2 k
1
) z" i8 W2 Q% T7 I+ X2
$ \" s3 c$ E- Q s3
# {' ]0 l9 c$ V& h4( y/ T5 g" y! ]( E0 y3 G/ m
5% ?" X) P% P! J/ o' Z P
69 y2 c" c# ?0 }' V" D* \
7 O+ v* p9 A2 C4 M `8 m" |
8" f6 p+ t" I) \: f& d! d
9! Y f+ B( U9 F4 n' l' |
10
( W F( I& Z, z' {7 ^! S11. L/ @3 z# ~& f& D
12
9 J V3 I H& t8 _3 I, J130 |7 d9 U# s3 @$ }/ \
td1 * 5
0 \" a6 ~% |# J/ w8 jOut[77]: TimedeltaIndex(['5 days', '10 days', '15 days', '20 days', '25 days'], dtype='timedelta64[ns]', freq='5D')
; U7 B- g: g. Z# Y0 O) |
Z! Y4 G; m9 Rtd1 * pd.Series(list(range(5))) # 逐个相乘
! a. L8 r: `. U1 J* ~Out[78]: & S% c9 i: h# i$ O2 t$ t
0 0 days( a% T6 t& B* i# n
1 2 days
' |- f% m& m! d) c2 6 days* T! q: V/ ]% h1 a+ M9 i* y1 R
3 12 days- n& W' \" p7 r" ?" A" u9 H# E$ r9 G: ~
4 20 days2 i/ \3 f3 ]; b+ V. @, u
dtype: timedelta64[ns]
- N" |9 W5 C, `8 L# J( @ E4 l& T+ O/ H+ t
td1 - td2
7 ]0 z9 x0 C4 B) ROut[79]: : S9 _, d& S/ p$ l# h5 M
TimedeltaIndex(['0 days 12:00:00', '1 days 10:00:00', '2 days 08:00:00',1 a/ c' n5 {# g
'3 days 06:00:00', '4 days 04:00:00'],
, R4 ?3 n, V+ Q# } dtype='timedelta64[ns]', freq=None)
0 j# m. o/ f4 l0 f" D: v
$ a+ i- M% i1 z; I+ [: H* Y4 g( Atd1 + pd.Timestamp('20200101'), ?, Q& J8 \, t2 W2 U+ y) r( i z
Out[80]:
3 u, G C7 j! m' pDatetimeIndex(['2020-01-02', '2020-01-03', '2020-01-04', '2020-01-05',( v1 z4 {( D$ L( l$ p
'2020-01-06'],dtype='datetime64[ns]', freq='D')# \6 ?% W$ ^" V) K
3 `3 p% ~/ \. i+ h5 p9 Ptd1 + ts # 逐个相加0 H1 {4 v' Z/ o' O& e0 I5 v
Out[81]: : H3 R; v# L' Z2 l% t3 o0 o& T) l9 {
DatetimeIndex(['2020-01-02', '2020-01-04', '2020-01-06', '2020-01-08',
9 }7 H N& B2 M' b. C% Z" o '2020-01-10'],
) G1 ^! u6 t" I- M# N dtype='datetime64[ns]', freq=None)
" u G3 `9 S$ J, u4 }7 I1 y" x: K( v3 I1 i/ K
1
8 W' ?" k4 E6 L: E, \9 R; Q28 Q& ?* t8 o& t- Q( T3 L' x
3
" E! U) M' B6 c3 ^8 a4
, }$ o' ?: L& o+ ~1 x: Z9 z4 h57 X# m/ a/ w5 P/ r
6
) v- b6 L6 S. q5 r) Y2 j7; y4 P! v* M0 t9 t
8
5 P: E+ \& K! @8 Y1 C9
$ V2 E& e n! C. h& L1 d10- O: Y2 m* E0 T* b( M: R
11
7 U; n, b) l" i) k' A12
: [3 Y! B) U4 g, h6 l139 u$ [. X2 Q! u( c ^; V
14
0 J2 Z1 h9 o- |2 G. Y152 g$ S$ P! s% W: H
16
; A4 H- B# A" m g4 W17& G1 L( K( _* }/ \! W3 R
18
) A8 l/ @3 k2 e8 Y) T0 K9 R19
6 g3 e' I# v5 _' s5 D20
7 a+ r6 ?* H) W" ?21
# L* c M4 Y$ t) g! c9 V) \22
) f( ~! h, J; C6 }6 a% e23 E9 j. T5 l& Q# ~4 f4 c4 B
249 L/ f' M8 N9 T5 F0 I1 b
25
7 M% x) {( }+ \; F3 X: x" E* D26
5 M4 y" b: p) h7 c27
- }2 g6 f! u7 c4 ^0 ~1 v# s6 y28* t% J. {* J: t
10.4 日期偏置
5 O: _3 u g9 O* ~) B10.4.1 Offset对象
2 U" e2 d2 j: [ 日期偏置是一种和日历相关的特殊时间差,例如回到第一节中的两个问题:如何求2020年9月第一个周一的日期,以及如何求2020年9月7日后的第30个工作日是哪一天。! C: S5 {! P! Q1 w" s) K
2 I' X. a6 e3 k& E3 SDateOffset 类有10个属性,假设s=pd.offsets.WeekOfMonth(week=0,weekday=0),则:
, q' X* S5 D! y' \( {: }" a1 H
4 P) c9 \) A6 {' s8 Q. @1 t1 T4 C! @s.base:<WeekOfMonth: week=0, weekday=0>,返回 n=1 且所有其他属性一样的副本8 N/ X7 d- \7 @; n: i, _; m" d/ {
s.kwds:{‘week’: 0, ‘weekday’: 0}
' N7 U( z4 g4 |2 xs.wek/s.weekday:顾名思义
* d. f6 {% P$ N& ?- k9 ]有14个方法,包括:
$ P) A+ |9 c0 I# c) R6 ?( H# T& b3 S
DateOffset.is_month_start、DateOffset.is_month_end、DateOffset.is_quarter_start、DateOffset.is_quarter_end、DateOffset.is_year_start、DateOffset.is_year_end等等。; u6 b& Z* j2 K6 L0 O
pandas.tseries.offsets.WeekOfMonth(week,weekday):描述每月的日期,例如“每月第二周的星期二”。
3 E5 L ~" {) l0 {
3 m3 G. |5 w& M7 @有两个参数:
6 ^' ~3 y+ }5 I' t* L+ yweek:整型,表示一个月的第几周。例如 0 是一个月的第 1 周,1 是第 2 周,以此类推。 p4 R5 O1 c: v7 e+ m8 B
weekday:整型,取值为[0,1,…6],表示周一到周日,默认取值为0(星期一)
; W) A+ g; K; Npandas.tseries.offsets.BusinessDay(n):相当于pd.offsets.BDay(n),DateOffset 子类,表示可能的 n 个工作日。+ {' J2 W) Y; B) e5 n! U Q
% `7 Z q/ S3 M2 Y+ r8 q; \
pd.Timestamp('20200831') + pd.offsets.WeekOfMonth(week=0,weekday=0)( U! N3 t- k: Q
Out[82]: Timestamp('2020-09-07 00:00:00')
' J5 ^9 g: R- }6 a& ~
' E1 q% U2 s" ~& t* f3 ypd.Timestamp('20200907') + pd.offsets.BDay(30)
( v& E) m0 ]9 `6 o/ f1 ~Out[83]: Timestamp('2020-10-19 00:00:00')
! M0 l& x8 k$ g2 o+ T @' Q. V; l19 E; I1 D v1 H7 w
2
7 r! ]. y4 _7 H# K( a" Z3& d' L5 S f7 t6 l! k8 R' g, ]# c
4
8 {+ I0 ^ _! i% F5 v+ Z; z/ s5 Y- D% |; M& i
从上面的例子中可以看到,Offset对象在pd.offsets中被定义。当使用+时获取离其最近的下一个日期,当使用-时获取离其最近的上一个日期:
- M2 g7 n1 M5 C) I9 ]
: f. @3 @) y: Zpd.Timestamp('20200831') - pd.offsets.WeekOfMonth(week=0,weekday=0)6 Q W9 N5 M8 U$ }8 d" O# v
Out[84]: Timestamp('2020-08-03 00:00:00')/ J- }/ T! ]8 k6 y x
. |# p: L! v U9 x- F" O4 g c
pd.Timestamp('20200907') - pd.offsets.BDay(30)( S$ M* ^6 G+ w; q$ _" L+ O
Out[85]: Timestamp('2020-07-27 00:00:00')9 F8 m/ c5 d8 L) _3 m
1 M2 n( F" [6 ]! @
pd.Timestamp('20200907') + pd.offsets.MonthEnd()
h9 o$ v J3 Z+ kOut[86]: Timestamp('2020-09-30 00:00:00')
) _* o/ B4 v4 t# Y; p1 R1
5 a3 f+ r7 a/ u7 P! y/ b! w5 d22 B, D* Z. _" F7 [
3
, p& Q' Q2 E8 |! e" q' j: W4
6 h7 }3 q7 d+ i* n7 w/ Y+ b% Q53 N5 J! h2 Y% d" \+ @; m
6! e2 D- d! m6 S3 O1 S
7
$ t& V6 V# M4 D3 Q83 |0 k: p; x1 X: n
常用的日期偏置如下可以查阅这里的DateOffset 文档描述。在文档罗列的Offset中,需要介绍一个特殊的Offset对象CDay。CDay 或 CustomBusinessDay 类提供了一个参数化的 BusinessDay 类,可用于创建自定义的工作日日历,该日历说明当地假期和当地周末惯例。
E$ B+ A6 b* P+ E# K 其中的holidays, weekmask参数能够分别对自定义的日期和星期进行过滤,前者传入了需要过滤的日期列表,后者传入的是三个字母的星期缩写构成的星期字符串,其作用是只保留字符串中出现的星期:) V$ i% [8 k( W/ Y d, i! H V
9 l5 g; ^# s; Q" e- |" n8 T/ o
my_filter = pd.offsets.CDay(n=1,weekmask='Wed Fri',holidays=['20200109'])
' C2 I/ K b7 A% E* y2 Hdr = pd.date_range('20200108', '20200111')
( C0 m$ [# d! B& g; ~
+ z& [: l5 ]$ h. H4 S Gdr.to_series().dt.dayofweek; T# v6 O8 [6 a3 c" \' N- v. M
Out[89]:
' H/ ?6 I( R" A2020-01-08 2
8 z ]# j) n4 f0 V6 ]" j$ K2020-01-09 3
4 u7 g! \0 t6 r2 W4 h7 `" ]2020-01-10 43 [ T* a7 ~. I" _, O8 l+ z
2020-01-11 5$ f" [( [" p9 v; @: H0 m# |
Freq: D, dtype: int64( O P, e+ p3 p5 Y
8 |; I. J' O# B4 q% ^; R! i' c[i + my_filter for i in dr]5 Y; `* P$ B, J, h7 o7 m5 M) A
Out[90]:
5 ?' @: ?4 t# Q4 D) U[Timestamp('2020-01-10 00:00:00'),
: n* |/ V* j6 k9 ?8 d Timestamp('2020-01-10 00:00:00'), K7 i/ M) M3 @$ m1 j; z3 u/ O
Timestamp('2020-01-15 00:00:00'),- _7 }6 _- j* V o. S, D
Timestamp('2020-01-15 00:00:00')], |; r8 f/ C9 V# J) H
3 T1 ?, w2 ?8 p5 ?. b Z3 G
1
# E1 Z1 Y" u# Y( @8 g0 J; a, z2# e) q3 }6 P' C8 s! r& W+ D% s5 c% k
3
1 H2 `" V1 K& X3 A, [! D4; V* k$ o9 ^. f1 w: b- p
5* Q0 ~ j' N5 {$ M0 `1 a" _" t- [
6; @" O6 ^: ?, z; c6 j# d6 @
71 H$ `% V9 _4 Y
8
- t7 @* a: ]3 s0 [. Y3 j1 k/ k9; C, E) w9 Z; q& W
10
! m4 ~) M, t; x: u8 v3 I, o6 e11
; [. {$ x1 P: k12
( R! ^, I9 r& ~13# P/ Z# T, D l- e
14; X- }/ n% {" K6 q& y8 U1 o
15
}$ J3 n3 k* Q) D16
/ n: t# ], Y) g8 b4 r17
7 L" T6 o- c9 \ Y# X 上面的例子中,n表示增加一天CDay,dr中的第一天为20200108,但由于下一天20200109被排除了,并且20200110是合法的周五,因此转为20200110,其他后面的日期处理类似。% W6 d3 {$ X) W& H
4 Q: @+ M4 p D1 z% H6 B
【CAUTION】不要使用部分Offset" a8 N3 z* A4 k$ h7 }
在当前版本下由于一些 bug ,不要使用 Day 级别以下的 Offset 对象,比如 Hour, Second 等,请使用对应的 Timedelta 对象来代替。
5 M" a1 k& q) [! r8 g! G; A7 ]0 O- s: J
% r2 w ]5 [( b& ]& M7 Q10.4.2 偏置字符串( t+ O: \* E4 ^
前面提到了关于date_range的freq取值可用Offset对象,同时在pandas中几乎每一个Offset对象绑定了日期偏置字符串(frequencies strings/offset aliases),可以指定Offset对应的字符串来替代使用。下面举一些常见的例子。# J; x& y3 z$ ]: u: M& R
% M8 O2 C* x9 c9 W Offset aliases:pd.date_range函数中的freq参数,为常见时间序列频率提供了许多字符串别名。 也称为偏移别名Offset aliases。偏移别名列表点此参看(大概27个)。
. d5 L+ b* _2 ]4 M/ B+ A, k; Q* |& O2 j, Z; Q
pd.date_range('20200101','20200331', freq='MS') # 月初
: L! O; ^) T$ g$ mOut[91]: DatetimeIndex(['2020-01-01', '2020-02-01', '2020-03-01'], dtype='datetime64[ns]', freq='MS')
# z) J& F, X! f8 q
$ m" m5 m1 Q' U- D- M. }pd.date_range('20200101','20200331', freq='M') # 月末
% O+ y" m9 }/ M' P. t/ T+ aOut[92]: DatetimeIndex(['2020-01-31', '2020-02-29', '2020-03-31'], dtype='datetime64[ns]', freq='M'), D0 A x! J5 ]4 p8 w, Q n) @
4 W" @! X# j p0 g. K I+ J
pd.date_range('20200101','20200110', freq='B') # 工作日
# Q5 Q& [# Z* ^3 v; Q' SOut[93]:
1 J5 K2 N' B. Z0 D) b$ J4 TDatetimeIndex(['2020-01-01', '2020-01-02', '2020-01-03', '2020-01-06',
8 w! \% j) E7 F2 o '2020-01-07', '2020-01-08', '2020-01-09', '2020-01-10'],
# V* o* z5 w5 y2 ?: y; | dtype='datetime64[ns]', freq='B')
# Y" V! W. n6 y
7 @8 c1 j6 N) k% @pd.date_range('20200101','20200201', freq='W-MON') # 周一
5 g! D- X6 M8 h1 c, ZOut[94]: DatetimeIndex(['2020-01-06', '2020-01-13', '2020-01-20', '2020-01-27'], dtype='datetime64[ns]', freq='W-MON')) d( q" @) V& H, K/ K( b
1 T7 M2 ~. v' y- m) g5 G$ Y
pd.date_range('20200101','20200201',
; m, x8 w" ?/ n, I freq='WOM-1MON') # 每月第一个周一
! F6 k/ Y1 V3 \* k4 ?2 [4 V3 Q, `
$ p Q+ U) e' l" c7 m* s4 ZOut[95]: DatetimeIndex(['2020-01-06'], dtype='datetime64[ns]', freq='WOM-1MON')
! J( I( ^2 M T. ~* |6 S ?# i3 B" ^; A! g; G& w
1
) ]1 e7 r+ X- s1 f- w6 m2
f* q! s5 t& T a3: F% y0 t! j6 m" A ]) J+ f0 L
40 ~4 l# u1 P2 G, W& I% y) t# |* G( D
5
! z2 l$ x; T+ |! u+ o9 S* Z) Y66 v) r$ @7 j! p. v1 }# n8 @; o% M+ S& W
7
\7 d, p+ g4 S8 L# V" q( ^0 d8+ E0 y4 e o, l+ g8 w6 W8 |$ w
91 l8 e' W. o7 b
10
* a; c: D/ `' U3 c: t# v! A11- b& }! Q/ q; X0 [; D X; N
12
6 r0 i. I: j9 W, U- o13
. E0 U6 c, D/ C+ R) r14
7 T/ r7 W' |9 j V15* M" z. \3 `. ^5 {5 I
16/ M7 z: N k$ X. K
17+ w0 ~# m+ Z* T6 Y3 d5 r C: Z1 P
18
+ _9 i \6 v9 t& \1 p- l19: Q- i3 C5 Y& N4 v4 T. I% w
上面的这些字符串,等价于使用如下的 Offset 对象:
$ r" y( \' a4 n7 @
8 g% V9 T: N- Z/ a Hpd.date_range('20200101','20200331', K* P( F5 Q+ v3 V
freq=pd.offsets.MonthBegin())0 H/ r; F9 i+ ~8 A2 Q; {$ g8 k
# A2 w) E0 v N+ f+ E. K3 @+ B6 {
Out[96]: DatetimeIndex(['2020-01-01', '2020-02-01', '2020-03-01'], dtype='datetime64[ns]', freq='MS')
, o& f. [* |2 t
& ~7 O! R) S2 Y+ D- C: vpd.date_range('20200101','20200331',# t0 v& y0 [' ~7 l7 i9 |
freq=pd.offsets.MonthEnd())8 q$ q4 X% L, s
2 B/ g6 p3 @$ n
Out[97]: DatetimeIndex(['2020-01-31', '2020-02-29', '2020-03-31'], dtype='datetime64[ns]', freq='M')8 y8 w: T7 O3 S- [- W6 K9 R( t8 d
7 {( Y2 y( C% P$ R
pd.date_range('20200101','20200110', freq=pd.offsets.BDay())
% e, a$ N, m& n4 a0 JOut[98]:
R. H% P7 X& a9 k% o% u: GDatetimeIndex(['2020-01-01', '2020-01-02', '2020-01-03', '2020-01-06',0 S8 t$ T4 K) ?( ]9 J
'2020-01-07', '2020-01-08', '2020-01-09', '2020-01-10'],
0 @1 H. v4 x8 C2 W- u3 \ dtype='datetime64[ns]', freq='B'); Q% a8 P# _; @0 t$ P3 |; P5 H0 g j
- ~. B0 N$ X2 A5 spd.date_range('20200101','20200201',7 U' C" f/ S1 y( a) k/ x9 v
freq=pd.offsets.CDay(weekmask='Mon'))6 C2 s0 e: {# Y. n+ Y: v% }
6 Q( f$ V% U% sOut[99]: DatetimeIndex(['2020-01-06', '2020-01-13', '2020-01-20', '2020-01-27'], dtype='datetime64[ns]', freq='C')- ?0 s' n+ K& ?- \1 C0 [
' w' U( ^9 B: ~ R/ F) S6 `
pd.date_range('20200101','20200201',( X9 q" E e' B$ r
freq=pd.offsets.WeekOfMonth(week=0,weekday=0))
; H+ D& |1 G# H$ S# v2 d; t
7 R Y- u4 _! G ^2 Y# E4 tOut[100]: DatetimeIndex(['2020-01-06'], dtype='datetime64[ns]', freq='WOM-1MON')& q) T& h. _ C& D6 r
' {, o; a. N z1 V/ Q$ t
1
1 L. B4 @5 N- g7 }0 { c8 g2
" u8 W; P; Q/ B8 {" q30 T5 F0 _; q+ F/ |% u, u6 R
49 b0 R, _) F& B' A( u3 `3 b
5: V; }/ A" s, j: l2 u+ ]
6: W, s/ q4 i0 _! S0 k% d
71 S" r; o8 B8 v+ }& Q+ |* F
83 U: ^5 X3 f8 Y0 H- ?+ r
9
+ O D$ _9 A Q101 j; J. J- o+ C; M, f
11# s( `; Z$ o7 s. \% j/ l
121 B6 P- t/ f2 w; q& \( y4 G
13' z& a5 g3 L7 n7 {1 P* M4 {
14
: n3 q m+ L5 g5 Q% }; n9 X15
k9 p) k* O w0 Y% J8 e16
0 P* }) c3 z; H9 K( C17
8 U5 W( s. f9 ^1 J6 s18
* e5 }, K3 L$ w9 M0 p2 e' k198 E/ t) K( V1 T3 Z
20
/ D' G- ~- i2 P2 I4 o21
/ W4 y. b# Z8 |3 |! i" y- c5 N22# u( j- ?, x5 y Z9 c% _
238 C" `* ~4 t3 j5 |
24
; @% ?5 K8 U, Z( X6 F25. H+ m) w d% o1 J$ U9 i
【CAUTION】关于时区问题的说明6 ^1 I4 R* |/ r9 G. i z
各类时间对象的开发,除了使用python内置的datetime模块,pandas还利用了dateutil模块,很大一部分是为了处理时区问题。总所周知,我国是没有夏令时调整时间一说的,但有些国家会有这种做法,导致了相对而言一天里可能会有23/24/25个小时,也就是relativedelta,这使得Offset对象和Timedelta对象有了对同一问题处理产生不同结果的现象,其中的规则也较为复杂,官方文档的写法存在部分描述错误,并且难以对描述做出统一修正,因为牵涉到了Offset相关的很多组件。因此,本教程完全不考虑时区处理,如果对时区处理的时间偏置有兴趣了解讨论,可以联系我或者参见这里的讨论。: M2 Z8 Z6 q4 y& c9 ^
! S+ y+ {0 u" |4 w
10.5、时序中的滑窗与分组
/ K" |% N z7 S+ Z0 _6 S3 o% p10.5.1 滑动窗口
8 W4 ?4 [# n9 s 所谓时序的滑窗函数,即把滑动窗口windows用freq关键词代替,下面给出一个具体的应用案例:在股票市场中有一个指标为BOLL指标,它由中轨线、上轨线、下轨线这三根线构成,具体的计算方法分别是N日均值线、N日均值加两倍N日标准差线、N日均值减两倍N日标准差线。利用rolling对象计算N=30的BOLL指标可以如下写出:: a' T7 R: [+ b6 @* i
8 o3 T6 V! f) R$ t3 n Ximport matplotlib.pyplot as plt
- j0 P( S6 s1 y1 @& Hidx = pd.date_range('20200101', '20201231', freq='B')! H* i! q$ O0 K A
np.random.seed(2020)
# F7 k& Y' R3 D/ h! Z; p- E; B' [- @) s0 H8 A
data = np.random.randint(-1,2,len(idx)).cumsum() # 随机游动构造模拟序列,cumsum表示累加
7 o+ o+ [' o, o9 x/ `) rs = pd.Series(data,index=idx)
( R8 P7 j/ ~/ ]7 s' Rs.head()
3 V/ V k6 P9 R+ [& N2 gOut[106]:
7 r' ^. a9 Q) ~1 {6 ~1 _% D2020-01-01 -1% u* | r0 a! { G5 M# ~5 b
2020-01-02 -2% s( g5 g$ y+ ^! C: T' s# a
2020-01-03 -17 A9 a7 n, v$ k: G' p( Q
2020-01-06 -17 N5 d1 m- L& |) _9 t
2020-01-07 -29 _5 S: m/ n+ a( G/ _
Freq: B, dtype: int325 O! m8 J3 y+ U9 Y8 q; b
r = s.rolling('30D')# rolling可以指定freq或者offset对象6 P; D0 l ~4 I3 m4 G
* R( }! t; b( ?
plt.plot(s) # 蓝色线& U/ N2 V1 E1 C$ l5 w2 u7 S: j
Out[108]: [<matplotlib.lines.Line2D at 0x2116d887eb0>]* b' R( w- U% C3 {4 X
plt.title('BOLL LINES'); J1 S; Z3 n$ l, C7 C
Out[109]: Text(0.5, 1.0, 'BOLL LINES')& ?1 @ k0 _: h' j4 Y2 r
9 f) ? I f2 b: c% y+ ~1 I
plt.plot(r.mean()) #橙色线
' A8 {* ]: b4 MOut[110]: [<matplotlib.lines.Line2D at 0x2116d8eeb80>]
! J+ J. ^' n7 s, j! s
1 T. s9 |. e1 J% @, E; n: Bplt.plot(r.mean()+r.std()*2) # 绿色线
' l$ Y* d5 `, Z2 [" OOut[111]: [<matplotlib.lines.Line2D at 0x2116d87efa0>]* U* d2 B& f* Z7 F* X' F0 n4 f" F, O
4 D; i% `" T0 I/ K* `( |
plt.plot(r.mean()-r.std()*2) # 红色线6 s$ [3 x' P$ ~4 D, a$ o7 S
Out[112]: [<matplotlib.lines.Line2D at 0x2116d90d2e0>]
* {3 j2 s6 m& ]- z; G! C1 N
% Y4 E8 T' ^. _- q4 C7 r1% f, {2 }. V' s* ?$ W$ O% W1 k( J
27 ^, F; X) ^& Z( `0 Q
3
+ I& ~8 Q/ P* r8 S; r8 b4 B4 D4
' O- T0 N$ G- n5 S% S: Y4 K3 \$ o5
" |7 L( g, V1 ^( }6
- \2 W# u, z- p& D- Y! C77 T1 S3 X, F( a) N" q
8
+ @( K! R6 m7 j7 J; h# s7 a! E9: c8 H4 O7 `5 J" t- B
10
5 w4 j! _& s4 c2 t8 l; e11
. A6 f, X( _' f$ b125 ~& e4 e1 l4 i! m$ {
13
+ k! d4 |# D- I* v. L2 n3 v14' {( T- U2 `" N0 E: Z& g
15
/ r9 }# T3 F& p16
- `' {& ^/ W" S" |5 H17
+ B" w, X5 ~ L1 \1 `; ~4 L1 M18' t2 L% T. z: Z: o7 p& j
19
+ u6 B8 c+ x7 H" Y203 N% @7 [7 q3 J3 Y
21
|* W# n" y" s$ S# A% \' B22: U. w) [8 |, ]
23
9 j! r2 _2 ^8 U. Q; Y$ r# _8 [6 ]24
& _# H. d, |8 k/ i25# [& b2 @% H0 W
26; _/ M+ K- i- ?/ q6 ]5 m
27 N0 ?+ @6 W- K- O, V {
28' G* ^% V, }; q/ Y, z: b
29
$ c+ z% e0 Y6 a) Y/ n
; C: B, L) O$ d$ A. f$ o 这里需要注意的是,pandas没有实现非固定采样频率的时间序列滑窗,及此时无法通过传入freq字段来得到滑窗结果。例如统计近7个工作日的交易总额。此时可以通过传入多个函数的组合来实现此功能。 p) X5 a/ W( j( I: b
首先选出所有工作日,接着用普通滑窗进行7日滑窗加和,最后用reindex()恢复索引,对于双休日使用前一个工作日的结果进行填充。
0 U" ?$ Y( U8 t4 \1 ?/ f) y Y& e7 ~8 i4 `2 W
select_bday=s[~s.index.to_series().dt.dayofweek.isin([5,6])]. L# E8 \% R! J7 z+ A% F# J4 c
bday_sum=select_bday.rolling(7,min_periods=1).sum()4 [ r: ?: T5 j6 W
result=bday_sum.reindex().ffill()
. T: B: D0 z: X& R" m* F3 hresult
- X: g9 ]: z! C; S% I6 n2 C+ J6 b' m- j# J, I
2020-01-01 -1.0
3 J# |" D( B& E- g8 D* @2020-01-02 -3.0
; G0 r/ q& P; E) C8 M+ d" [4 @2020-01-03 -4.0$ Z V' c" D; S$ h- M- ?
2020-01-06 -5.08 |! z, V" _& x2 O% R, k9 P) l
2020-01-07 -7.0; F& u% c2 u" w1 `. V6 d
... 7 n* C5 c* r6 }; f+ I! K
2020-12-25 136.0
: t+ ?8 p' n! \( a0 K" l# A, n6 ]2020-12-28 133.00 F+ ^8 y# F9 c9 k& S0 ~
2020-12-29 131.01 W7 V# X$ {. k7 W
2020-12-30 130.0
9 V9 l1 L/ k; n5 w% p3 F% p! k2020-12-31 128.0
* m0 Q6 f" w7 y9 PFreq: B, Length: 262, dtype: float64
1 C3 ]/ k, O% w. ?. R p$ r; |0 v5 l$ I* b$ s$ @
1
% @( Y. T* M4 A) F/ E2 G: s2: x2 P8 p7 L! v/ B
3
; e1 s& i% O7 e% ?: e1 S4- L& \" U, i9 x% C6 }9 X+ w) k1 q$ d2 n
5
1 J% ]: T. T' u/ P6
' y' H% Z* |4 T& T) e7
7 a( n/ H2 g q' }0 p% |0 {8
0 L* @2 x/ \8 p( g( \ y9- S3 m$ D# y* y4 F
10
5 X9 ~ Z* n/ s112 W) F) G2 x( Z: N
12
/ Q6 a7 ]3 g1 X1 t9 m13
. a% K# d5 n! z N7 Z8 F; X14) f5 P' G" s( F
15
1 b: S7 Z% O" O, _% D! H16
& j6 ^/ Z% H- R9 j, I17" v" J* v9 z8 K, b3 o1 z0 q
shift, diff, pct_change 是一组类滑窗函数,它们的公共参数为 periods=n ,默认为1,分别表示取向前第 n 个元素的值、与向前第 n 个元素做差(与 Numpy 中不同,后者表示 n 阶差分)、与向前第 n 个元素相比计算增长率。这里的 n 可以为负,表示反方向的类似操作。
3 r' f( x+ q% [0 H1 ]
$ P4 Q$ ^* G; h k( Y# U" L; v; X; q 对于shift函数而言,作用在datetime64为索引(不是value)的序列上时,可以指定freq单位进行滑动:
3 @/ h; k/ D. c5 x' `6 f0 @* n! p6 D: h! Y: Y/ `1 X3 R: p& e! e
s.shift(freq='50D').head()9 g4 e0 H/ O% q
Out[113]: 5 z/ b$ V, z9 h0 M; Q3 b
2020-02-20 -1
% g# ^/ ~! J4 P2020-02-21 -2
( R: [( O) C; R! T- t/ f& e2020-02-22 -14 k! L" H9 T: o+ ~
2020-02-25 -1$ Y( |5 r- F9 g! ]8 X9 B
2020-02-26 -2
) \2 v- N l+ b+ y) Idtype: int32
2 Z. Q' e2 v1 O' |1
% v. M' q; ~3 Y# u" X1 w5 ~2. @5 R2 T. H4 i
3& A! T2 P3 L# i# D2 T- S. ^7 {
4
* E% z" l1 ]0 H) L( q* _) ?8 T0 ^; o5
! \5 g, U3 n0 @$ m6! _6 J7 d- J, U1 Y8 w5 \* p
7
8 ~( H& u7 S3 m9 C+ z8
. R8 ]. F0 \! O9 `$ [# C 另外,datetime64[ns]的序列进行diff(前后做差)后就能够得到timedelta64[ns]的序列,这能够使用户方便地观察有序时间序列的间隔:! x6 ]+ V5 D. ^. U( S# m+ Y
6 g* J2 d! G; y7 v' w
my_series = pd.Series(s.index)
6 e5 w0 O* q: y# [my_series.head()
& |3 p |+ h: O4 R& nOut[115]: " h, r! q; @7 U& b# g7 p& C- ~
0 2020-01-01
" A) e2 k2 b" {1 2020-01-02
% ?& q2 Q! o& ~5 U3 O: u2 2020-01-03
; V( l) y; ~& @" i3 w3 H3 2020-01-06: K/ q6 @' {3 x1 q) c, f$ {
4 2020-01-07
9 L/ _3 \& K: b& s+ g8 V* a/ fdtype: datetime64[ns]
# x, F5 y, a/ o& W2 e% T, i; k
% |) L' i" R9 c" g2 M/ e( Ymy_series.diff(1).head()
1 q$ H% K" y5 U& e5 lOut[116]: 8 m/ r+ i3 A2 @! Y) Z
0 NaT* u( _8 n8 l3 C1 n. s6 u
1 1 days
9 S/ i! P8 {, E7 P2 1 days( b) ~: A: I; z: n0 s
3 3 days4 T) ~$ w4 [7 E! i/ q4 Z
4 1 days
2 i/ i! t& g9 |/ x6 b# h2 o Wdtype: timedelta64[ns]
9 x: o3 l% q8 f/ R8 w6 l+ r& V& ~3 e# E/ R7 r+ l
1
0 D% f6 W2 S* e. X9 ~7 h2
) Z& B* Q8 @# ?3 N$ l3: `3 w! T1 P8 e) o I! M0 x% i: S
4
# }8 B( W4 f& w% T6 v3 w5
6 p# U/ L' A, u6 \( ~. Y6
% R9 ?! N( m8 B! b2 g9 f( {3 ?7$ ]0 J' u. z( ?% b1 F# d, K
8/ x+ X9 Y- f5 r; F* o I! ^) n/ e; Z
9
# W) _2 c) R2 H# g8 ~. k10
8 t M+ I% g% M) }3 r" e11
6 q! h( H8 J! A2 D! {124 Y* R9 h3 P% x2 z
13
! d) w. x+ R# |9 r2 Q8 l- c14% K( X9 D. W/ F5 ^+ O: B! L
15
( R" a) Z9 m+ J166 p' I' n' U' k! |. [3 J7 R
17
3 F# \5 c: f( X& T3 b18: x# ]: l3 G0 q+ L9 M# A! _
10.5.2 重采样
# o4 W; `! [. e# `9 B5 I3 @ 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)7 s2 w; P/ `; J4 V/ v
常用参数有:& J0 P$ p2 F4 V; ~5 K+ ?
# l: S/ m0 w& l& w; O2 g: `3 K+ }rule:DateOffset, Timedelta or str类型。表示偏移量字符串或对象
7 @$ a' l* Q* Y' w9 \- V8 R2 Iaxis:{0 or ‘index’, 1 or ‘columns’}, default 0。使用哪个轴进行上采样或下采样' \* ~; C( |! R9 Z
closed:{‘right’, ‘left’},默认None。表示bin 区间的哪一侧是闭合的。所有offsets的默认值为“left”,但“M”、“A”、“Q”、“BM”、“BA”、“BQ”和“W”均默认为“right”。; ]- ]( Q" J$ c) x9 Z {: O
label:{‘right’, ‘left’}, 默认 None。hich bin edge label to label bucket with,所有offsets的默认值为“left”,但“M”、“A”、“Q”、“BM”、“BA”、“BQ”和“W”均默认为“right”。& e& X$ O" U2 b* {0 j, G! b
convention{:‘start’, ‘end’, ‘s’, ‘e’}, default ‘start’。仅针对 PeriodIndex,控制是使用rule的开始还是结尾。/ P( X: \. |, Q1 ?5 [7 D2 a) Q
on:字符串类型,可选。对于 DataFrame,使用列而不是索引进行重采样。列必须类似于日期时间。& C. E1 l7 h, X, P
level:str 或 int,可选表示多重索引MultiIndex的级别,这个级别的索引必须类似于日期时间。
. }* F1 v! e! x7 {origin参数有5种取值:5 G0 ]7 G: l3 h
‘epoch’:从 1970-01-01开始算起
( a) d5 |1 |& a‘start’:原点是时间序列的第一个值
' ?7 ^- d7 c0 @) V5 c‘start_day’:默认值,表示原点是时间序列第一天的午夜。
9 s5 x4 l: F% ?. k7 \/ I'end':原点是时间序列的最后一个值(1.3.0版本才有)6 t) z- n& |' @ Z( s& D1 A. D
‘end_day’:原点是序列最后一天的午夜(1.3.0版本才有)5 V% p& ^* ?+ H' R& C
offset:Timedelta 或 str,默认为 None,表示对时间原点的偏移量,很有用。
2 X: L8 G5 N; O2 z0 E4 U0 W closed和计算有关,label和显示有关,closed才有开闭。' l1 a& j' N% a. y. x% E) a
label指这个区间值算出来了,索引放区间的左端点还是右端点,closed是指算的时候左端点或右端点是不是包含。
+ z; u0 m: W: ]) i; R. o7 ]" K, X2 v! w3 Z
重采样对象resample和第四章中分组对象groupby的用法类似,resample是针对时间序列的分组计算而设计的分组对象。例如,对上面的序列计算每10天的均值:" D. o8 e0 T% L! _4 d E
s.resample('10D').mean().head()
2 C. k# d/ e. b" |8 g9 SOut[117]:
1 x# ?: G6 }) F+ D2020-01-01 -2.000000" ]* j4 t5 W4 w$ q! R" U
2020-01-11 -3.1666670 l, k. H# q2 b: B3 B; D3 u4 E
2020-01-21 -3.6250006 ]& W. g5 l3 \, M3 N7 f
2020-01-31 -4.0000008 a/ L& P" ~. U6 B' f
2020-02-10 -0.375000. {2 i6 e3 M' A$ w( o
Freq: 10D, dtype: float64
. a; E$ q3 b; @9 s, W. h1
, k2 h9 L1 E; D6 q) }2
^: E; k) @# l3
4 ~# A+ l- d8 b) s4
; H+ U% D+ z- e. ?: |3 P4 `5
/ }( U! W7 ^4 t) k6
* z9 c" e5 n6 P7; q& r1 ~# |/ \/ h
8+ `8 \! D/ _% y
可以通过apply方法自定义处理函数:
$ @* O' j" [' l; Bs.resample('10D').apply(lambda x:x.max()-x.min()).head() # 极差" b' e; Y4 c3 D3 e5 q
1 O0 _' O; f. A$ g- L
Out[118]: + q Q: B& ?. D: G2 o, L
2020-01-01 32 Y$ m$ S3 a O* Y+ P0 v2 l
2020-01-11 4
# C$ h/ m4 E! J+ R j0 j, X2020-01-21 4; `2 l3 `5 D/ O* } [" ?
2020-01-31 2+ D/ E" Y8 n! I. p2 N2 {& R
2020-02-10 4
' ^! j. F* K( ]Freq: 10D, dtype: int320 b- e$ R+ z; z
1
7 P. o( B0 P' Q( I2. v& G' b$ ^$ V2 g% l/ b7 Q* @, C+ P
3% W8 y) M: p9 v% X, t; {9 O
4
+ D b) V, ~& J% `: C% k% c. ?53 S- `* T- K7 `' n |7 o' w; j) j/ R
6
5 o4 H8 ?9 y: ?! A) I: _7
/ p( z; Z+ ?7 G3 g" Z8
I4 p! t/ |& @7 D! O7 D, ^+ u) V9; w3 m% i2 ^! H7 _, o! K' b' ~ V8 X; ]
在resample中要特别注意组边界值的处理情况,默认情况下起始值的计算方法是从最小值时间戳对应日期的午夜00:00:00开始增加freq,直到不超过该最小时间戳的最大时间戳,由此对应的时间戳为起始值,然后每次累加freq参数作为分割结点进行分组,区间情况为左闭右开。下面构造一个不均匀的例子:
, L: [+ ^7 G4 z
0 y( ], _; K1 q4 p" jidx = pd.date_range('20200101 8:26:35', '20200101 9:31:58', freq='77s')
v1 w: K4 t5 M( S: Cdata = np.random.randint(-1,2,len(idx)).cumsum(): F0 V! y8 f. ]2 j1 s9 y6 q2 `
s = pd.Series(data,index=idx)7 n5 e3 t+ S: t* a7 J4 ?
s.head()4 a9 L/ h8 q% D1 E3 G
9 G; Z( c; K4 S4 q' k+ P9 L+ n/ `Out[122]: 5 e3 X" s6 N% ^. ^5 c! Z
2020-01-01 08:26:35 -19 g5 Z# h- `/ S: J5 y
2020-01-01 08:27:52 -1 g6 m1 n- q, L- H
2020-01-01 08:29:09 -2
# ?- ^7 |8 @8 S; D2 E0 w' `2020-01-01 08:30:26 -3
; v- p. L0 H- z2020-01-01 08:31:43 -4
! W* S8 i8 a+ U U. UFreq: 77S, dtype: int32; }0 g0 E# C+ N; N6 i$ @
1
: H% Q4 z! ]$ G' F29 _* z! c& c- c* }' d0 V
3
. h$ w' y1 T- D9 v2 S' \7 `( V4
2 e) R/ k% o5 J6 R/ ^1 N% j5
k/ i+ p( |, J& F( m1 R; r64 |( [1 D2 E" {$ N' u$ q% y. N. A
73 ?; h* L0 q( W* T( A7 _
8
/ p4 [' t2 i: j" E+ p9
# c( ]- U6 n3 S7 g10
1 o3 r3 |# e3 A% [11" b; K/ Q. a6 s. w% k/ q" y
12( b* R- P; d$ X5 K8 _
下面对应的第一个组起始值为08:24:00,其是从当天0点增加72个freq=7 min得到的,如果再增加一个freq则超出了序列的最小时间戳08:26:35:# g4 g7 D+ g9 D- h6 d2 A+ \3 m0 \
8 O; D$ y& d6 y8 w7 W/ gs.resample('7min').mean().head(), W$ f, \- S+ J9 c2 t, I! M& @1 j
Out[123]:
) P/ D+ \: v! B: r( t# I n2020-01-01 08:24:00 -1.750000 # 起始值,终点值包含最后一个值# r3 f/ F( `* H5 d* D# c8 i. w
2020-01-01 08:31:00 -2.6000003 N6 A, |2 g; t! \& O |8 Q
2020-01-01 08:38:00 -2.166667
$ G3 L+ l% `# ~& @. G" Q! O2020-01-01 08:45:00 0.200000
8 A! a0 @, Y( \# s; b2020-01-01 08:52:00 2.833333, r+ `8 x7 p+ [3 D
Freq: 7T, dtype: float64( i0 ]* J! L% M1 X/ Z. T/ R
1- V9 y0 Q) N+ ^
2
, Z; o; ?+ H5 N: w3. J* g: D: Y4 k- J* e5 X. O
4
" ?* `" x8 Z# `2 P& ?5
" V" |7 U6 y# S: a+ q67 u) A6 j E& w/ G- A! j6 P
7& Q1 e2 j) h; b3 {7 u
8
: D4 C( b% N0 K- H; @ 有时候,用户希望从序列的最小时间戳开始依次增加freq进行分组,此时可以指定origin参数为start:, P: y/ c1 {$ Y: }9 u3 E2 ^) i
+ Z+ x' L/ V9 E9 Rs.resample('7min', origin='start').mean().head()# D& U& d3 o J" B
Out[124]: * {2 r8 T, B1 o |3 u
2020-01-01 08:26:35 -2.333333
! |' I2 j6 |$ S* l2020-01-01 08:33:35 -2.400000 \9 @8 @/ }$ z3 t) a5 l
2020-01-01 08:40:35 -1.333333
$ v! i% s- y* H- h- n' L2020-01-01 08:47:35 1.200000$ Q! U1 Q+ [/ }* ^5 ]3 H
2020-01-01 08:54:35 3.166667% w. X% z: z1 D7 ]' G( s% I/ Q: ^
Freq: 7T, dtype: float64
2 V0 J; t& P' n. `0 ]1 U! j( B+ i9 y; r& R, B
2
% U/ i. s0 Z- B4 L3 {6 v3! O" h* u* ]2 V, [: u
41 \0 ]5 U. R1 y" n9 F
51 s, l& I3 f; v3 N6 i7 w
6$ N6 b% K3 f0 i) R8 C
7) r2 ?. a1 p6 L) H6 X
8& s N* Q( f* J- \$ F
在返回值中,要注意索引一般是取组的第一个时间戳,但M, A, Q, BM, BA, BQ, W这七个是取对应区间的最后一个时间戳。如果想要得到正常索引,用’MS’就行。* v4 o( G0 t9 n1 z9 s4 @6 m
3 l$ q/ a% X; I9 u
s = pd.Series(np.random.randint(2,size=366),
0 R' Y# p5 D { q8 `" W+ R/ X index=pd.date_range('2020-01-01',
" _+ I! o% `# K' i/ H' x '2020-12-31')) a {- K5 b1 N5 B* n. y: @
& F+ X3 ?; J$ |: C2 r( T" |
0 v3 j; T* p3 D% @' \( us.resample('M').mean().head()
( x3 w+ q2 z( _: P+ C# pOut[126]: ! f& P1 X* J( ?1 f9 j& A
2020-01-31 0.451613
' C; T& i- X, t4 I$ M, I+ q2020-02-29 0.448276
+ K# |9 _! e# p* ^7 ]9 B5 a2020-03-31 0.516129# x S/ l& m! ^
2020-04-30 0.5666679 \5 M4 ?. o; U `$ y
2020-05-31 0.451613
" V5 u9 e% M, M3 S2 VFreq: M, dtype: float64
, V1 w* q# O4 @( u& t
1 [0 ?$ M/ T' a1 c1 g. a, Zs.resample('MS').mean().head() # 结果一样,但索引是跟正常一样
4 I6 l- B j- T" U/ xOut[127]: : s+ f' Z0 a- Y9 @! r
2020-01-01 0.451613& k1 Q" j) |* f- a! q- T
2020-02-01 0.448276
$ ?) ?9 x9 t4 d2 Z& g! B0 y. N7 q2020-03-01 0.516129
* x* |, M( ^0 h2 `. q% E# z2020-04-01 0.566667
, O5 B' A% D% t6 I0 y8 X! P, S2 V4 V2020-05-01 0.451613
3 a1 |; ?( I3 ]( Z9 v5 n' i/ o% UFreq: MS, dtype: float64
- l: c# C6 ]" O# L+ v2 ^: l/ T
- {( C3 E7 A. e O- w, l- q1
% O9 ]; {, F( D+ J- B2
$ C; `) Q0 f0 a# P2 }2 n/ V, Q9 m3( ^+ f+ j4 K& }3 B. ? r" l- c' t0 Y
4
8 t- q3 e/ ]: l( l4 X! {5" v1 I7 U/ ?& H$ B! G5 v9 z
6% S) T) }: G( q) ]" W
7! e8 [$ F- c1 _# n1 Z
8
; D2 T. [& d5 ^9 ~& _4 V) u9 k. N9 o z" M$ o5 d7 ~; z1 _
10- d# h% [; z4 M( _7 t% t
11
+ Z+ u; ~+ D0 {( P1 J12
3 B2 ^7 G; I E: }! `2 o% ?) ^13) h8 X' D* j7 r5 [
14/ O# ]) o% @! ^8 X
15$ q) }+ F/ M6 y; Z a
16
, T( z* ^. D% [4 m, v. }17
3 y0 z/ `- \( v$ o3 N4 J: ?18" ^8 p& K; y0 k1 Y' z2 u
19
% N: w0 O5 W$ e2 A3 i206 |5 y0 U+ L R1 Y$ W9 ^3 T3 d
21
2 @3 i7 [+ O" V0 x8 {' o' n22
$ ], T8 u2 O; `& a/ i对于 DataFrame 对象,关键字 on 可用于指定列而不是索引以进行重采样:: ^: }! ] g* g/ U$ l
d = {'price': [10, 11, 9, 13, 14, 18, 17, 19],
6 |3 H. a, Y$ k( v/ H 'volume': [50, 60, 40, 100, 50, 100, 40, 50]}
: M( }4 L) N7 m" E+ ]' Udf = pd.DataFrame(d)
6 U+ |8 _ z2 l6 F2 V6 d# ~0 bdf['week_starting'] = pd.date_range('01/01/2018',9 r0 e. G/ _7 S* Y- A( @
periods=8,
# H) |* t# z3 @' n freq='W')" h' @* v; D, @' s) o# E
df
$ s# [1 B: y8 E+ q( X) R price volume week_starting
2 O& K1 ^, \9 |2 |, M# `0 10 50 2018-01-07
; A9 o$ d. w* z1 11 60 2018-01-14
+ N: H% a6 |/ U5 h% B7 ?" n2 9 40 2018-01-21
. Q) B7 h9 y* T" K3 O: B9 ^3 13 100 2018-01-28( A! b7 j# {4 |6 H! F: Y2 Q1 j( v
4 14 50 2018-02-04
7 A8 |9 s8 A! f2 ?7 }1 X3 ]$ L5 18 100 2018-02-111 Q6 @' c6 ^* e0 X1 y
6 17 40 2018-02-18
' E! b9 C9 Z; t1 @7 19 50 2018-02-25
; H8 d+ w6 _+ D) f3 {df.resample('M', on='week_starting').mean()
9 n( T. n9 x: a/ D& A9 c price volume d2 M* |1 a4 m- Q& A- i5 n9 O
week_starting
& P' r, K( j2 \2018-01-31 10.75 62.5
; l* _9 o4 {# _5 c2018-02-28 17.00 60.0
. p) t+ v% M- M1 C* @9 B" n
* m' x- o+ g+ {! }1
; Z1 A7 R) Y4 f7 b2& Y! @8 ]9 Y( B7 k2 F1 x
3
3 Z; c& v0 d. ^$ A( N! N4! r+ F4 |8 A, Q2 p/ N
5
2 j" G+ a. ~. C9 Y) S! J6
. U) \3 ?2 T1 U! w& g9 z7; A3 w. S2 {( R
83 V; z- g+ b- p; Q# S: L
92 f. m. }5 `/ [( `
10
1 p% Q6 m1 ?" q8 r/ Q11: F* o* V+ ^- e
124 g, ^* e: @$ F- S
132 K; \ J& |' @2 W+ C
14! E6 S& `' I3 r+ C! u
156 D) e3 q3 O* P+ |7 E" c2 B2 p
16+ y6 y6 [! x F( m) a7 n$ k- B$ U' }
17! _- ?7 `9 j4 R* A% T T: Z
18* ^& m8 e: M0 n, g# f
19
9 p3 J0 a9 ]/ D' e! @, U P20: ?) u7 J+ t# Z+ f
21; \/ B2 U5 ^ [) _
对于具有 MultiIndex 的 DataFrame,关键字 level 可用于指定需要在哪个级别进行重采样。
& k# Y7 r) P* P# w( I- Hdays = pd.date_range('1/1/2000', periods=4, freq='D')
5 ~( l6 d6 }7 n+ T6 Fd2 = {'price': [10, 11, 9, 13, 14, 18, 17, 19],
9 Y. z& {, x, b# A# R) H5 r 'volume': [50, 60, 40, 100, 50, 100, 40, 50]}1 B" S& u! _( d1 m8 R6 \1 h8 d
df2 = pd.DataFrame(
, i6 |/ K+ ?" `4 V9 m) d5 D8 G4 a d2,
' q- G2 D# N! p- f) Y8 b! G( S/ \ index=pd.MultiIndex.from_product(
6 {# \4 r4 u& j- t4 S, p y [days, ['morning', 'afternoon']]5 U: l a% t: T9 u/ z( x$ J
)
# a& A: e8 a: E* s8 K). S1 S, U* N" ?" J d' C0 I$ H3 V( F
df2
0 A$ s$ [/ {5 n) f% ^ price volume
& S& x! R: Z4 R& M- `2000-01-01 morning 10 501 p+ x( e1 w5 o6 U
afternoon 11 60( V, `$ p. |1 J+ Q& _
2000-01-02 morning 9 40
9 R2 c+ s/ Z- F. ^6 k0 K afternoon 13 100" F3 O" B6 Q2 q0 D6 z
2000-01-03 morning 14 50
8 i5 s# p5 _6 h% f+ c4 b# c: n afternoon 18 100- N/ u6 E. _$ d3 H9 r" n
2000-01-04 morning 17 40
: h# B1 Y& D, e1 {: W afternoon 19 50
; s) L8 m. h" Z' }" z6 Idf2.resample('D', level=0).sum()
! h& h" M* K. t) _: O$ @ price volume
. s* Q/ J0 {$ O2000-01-01 21 1106 R0 p1 F$ N5 H3 C+ Z
2000-01-02 22 140
* W( T; H5 l+ B' Y' M+ `1 l( `8 T2000-01-03 32 150
' D/ a4 m u' H- c, h# ]2000-01-04 36 90
- C8 H' I- c8 H8 l/ g7 v8 Z, x6 {' ]7 F9 o
1
/ h$ p! z/ g7 K& K& f# z26 M3 ]& [ c, }$ F
3
1 i9 b/ `5 U! |. C4* |8 ^$ R9 ^/ p* O+ \8 p
5* ~0 V! k: W ]' J6 t8 Q6 R
6
& `2 W) q2 X" e' I; p( r7
+ w! S V/ [3 C' A8: L, r3 M. a( F: l! Y* S- y
9# i+ ~ w: |$ m* ?
104 U- r# e. d' k" P. @
113 g4 r# i1 f: N3 c0 i% r
12
( ~0 s& r+ P- j+ u" W6 F j13
7 v( k' O6 f& x" J. M14+ C2 A) B$ Z9 G4 S0 [6 S- ^
15- W3 ^- {! M$ Q" Z2 D3 O
16
- n: v4 l& \4 e% _172 i7 i6 y8 ?( j
187 b1 q3 I7 }2 K5 B9 S/ d, t
19
2 `$ b: X1 \/ \, F20
! w$ Z f: Z/ ^, M- w# x; t219 M) C7 p* h7 h! i/ J8 W* J7 g1 F
22' n7 X) Y9 |+ M
23 W r1 U( s; l( Y: ], G
24
5 c$ L0 H* L- e! ^3 D" X0 @25! S$ z) v: d, [' d' }( S; z
根据固定时间戳调整 bin 的开始:
# Y: F) S) m5 H5 Z0 {start, end = '2000-10-01 23:30:00', '2000-10-02 00:30:00'8 K8 Q& K) i6 e" H0 z
rng = pd.date_range(start, end, freq='7min')0 U3 j; K# o1 A
ts = pd.Series(np.arange(len(rng)) * 3, index=rng)1 w$ O5 ]$ ^: n
ts+ p8 J5 b" E n, U+ S* }; W) V
2000-10-01 23:30:00 09 r% R6 [* B+ G1 v9 L: ?0 h+ Y
2000-10-01 23:37:00 3% B# ^9 O" w& h
2000-10-01 23:44:00 6
' {6 X( K8 o' ?7 c1 d* Q2000-10-01 23:51:00 9
1 ?* Q" V5 s$ {% S8 k4 M: V% B4 M2000-10-01 23:58:00 12! U! Z: J9 [& Y# ~
2000-10-02 00:05:00 153 `4 f9 r4 N0 b9 f& C8 e/ Q' A3 K
2000-10-02 00:12:00 187 V. t5 e9 d, x+ V& }. L1 X8 x
2000-10-02 00:19:00 21
$ X! ~& m8 `0 ]" s* Y$ v) |; h8 N2000-10-02 00:26:00 24
/ j2 W: L5 R3 R1 \; k1 o9 tFreq: 7T, dtype: int64
" K5 F& m( I9 T' p/ S: E( y- U2 F. k
1 }2 p; [$ U' k- j& E/ X3 ?ts.resample('17min').sum()) y- v% s& i4 l& c; v8 r
2000-10-01 23:14:00 00 `7 w0 s; e) I" u* N* D G6 J. o; q
2000-10-01 23:31:00 9
) @5 s( _$ k/ |( s2000-10-01 23:48:00 213 Y/ [8 e4 h- X% o) Q5 K
2000-10-02 00:05:00 54
" P- e: r; P/ [8 j ^ I2000-10-02 00:22:00 24
, @9 ?, c: u6 A, IFreq: 17T, dtype: int64
, Y3 r! E2 z1 [8 T% M# O) N5 q' \* u8 R7 k5 ]; `
ts.resample('17min', origin='epoch').sum()
: I; Q0 Z" Q% a# u4 ~' `2000-10-01 23:18:00 0+ d( A2 H0 K( f
2000-10-01 23:35:00 18
" [4 i5 o3 U m& b/ b7 @2000-10-01 23:52:00 27
% W1 |! ~6 U( x# _2000-10-02 00:09:00 39
# E- E$ V5 M v4 X$ g$ w2000-10-02 00:26:00 24; L& \; ]! l0 ^$ l
Freq: 17T, dtype: int64
0 ~( |; V: \" f% _: d8 l7 G
, L7 w$ N/ L. M6 z/ ?ts.resample('17min', origin='2000-01-01').sum()
, K2 f# k$ H# m2 |0 B+ g2000-10-01 23:24:00 3! ^. z, Y2 ^% t5 A- ]7 n
2000-10-01 23:41:00 15- U1 O# }; E4 p0 ?
2000-10-01 23:58:00 45- V/ l C+ G" ]5 l
2000-10-02 00:15:00 45
3 v; |/ e( a8 u: ^) |6 FFreq: 17T, dtype: int64
% U) v7 F' j0 o
^, W7 E) _! f0 x8 p# s, p. @15 h$ q+ y$ Q }% Z
2
% [) t: D/ K) a5 b( X3: V. y1 B: k, J8 K$ p2 e
4* E5 e& c( k5 a6 S7 {1 W
5
' p6 w. ^2 T/ d: |. v" b9 e6
" i W4 ^; o( c7
5 A/ Q" x' a* K& k6 U3 c8
) p- D4 Z5 o' L$ f9
, t) i- I8 t. J' e+ _. x2 e10
) N# Z2 C" Y+ b8 W7 K11+ o5 ^& A$ G1 I" M; \
121 G/ W* q, ?4 c. {7 A6 U3 u0 M
13
; M* I6 y( J3 \! g y! |7 v/ g, B14# M8 \$ R, X$ M' I: K x
155 g: z) r* y) Y. L
16
# i2 E- H4 V! r177 f+ ?: {& p6 }" @: E
18# K1 [( ?) n3 k" ^6 m
19
) r" l8 h7 \; S. v200 B9 J7 O5 e- v6 X' o: w2 Y
21* X g; J6 f8 x8 H g2 x L x y0 p# f
22/ u7 f1 T" p" |% x9 y+ a8 c$ O
23
9 S d$ R5 o4 F5 U: X9 @( R24; R! D% `& k" h: i
25
' X3 k1 G3 s/ R6 y3 O3 m26
, M* c# Y+ e* N A276 k2 Z% N& V6 m8 _. b2 Y
28! o- l+ |$ m1 W2 t+ ` t
29
) \0 D7 l5 `+ }0 o30
! X9 p6 `* h% S& m+ Y316 U2 t8 o2 p: r5 r
329 A' n1 z7 r$ V1 Q r
33& |+ p6 u/ k$ L; @( P# H
344 k" o; U2 X+ u6 b) V& |
35% R' t5 x* F4 g, k0 H) U# q) b+ @# p
361 B8 G0 E5 _0 j' i& C
37# s$ x" l0 M- }0 `4 J1 C) c
如果要使用偏移 Timedelta 调整 bin 的开始,则以下两行是等效的:
7 B8 I% i" K) q nts.resample('17min', origin='start').sum()
& ?4 |" b# E8 t: M! W- Z( pts.resample('17min', offset='23h30min').sum()
L- K! ~0 w k: _, @2000-10-01 23:30:00 9/ J: m0 Z% V; R1 s- Q
2000-10-01 23:47:00 21
: S3 ~! e" o7 g0 ^5 s2000-10-02 00:04:00 54
- ]+ Q4 c) {1 K# u) |2000-10-02 00:21:00 24
" q* D+ k$ j# TFreq: 17T, dtype: int643 I4 L+ |5 F5 C
17 |) k1 E& I7 I# A) W. O& U9 v
2
$ y( M) c, N6 V0 L E3! ]4 j; t/ z% c" i) a& G
47 ]$ H+ |' W7 d
5
0 F5 k* }" @4 @7 r# D$ u% n" i6
4 _, ?7 f. s1 Q" r& m. Y7
/ f! e& T" Y( j10.6 练习6 H- `: y/ s; g) K1 v3 z
Ex1:太阳辐射数据集
6 |4 r7 B7 Y; V {9 {9 L: }: z2 w现有一份关于太阳辐射的数据集:* k) O. I! H+ ^! X
/ u4 K1 X3 G5 Q j vdf = pd.read_csv('../data/solar.csv', usecols=['Data','Time','Radiation','Temperature'])6 K2 o% }3 \9 [# d7 m3 f% A7 _
df.head(3)8 G9 i* E4 B0 x N- t
0 {/ e. E5 f4 p" L# ? j ?Out[129]:
, `9 I( X" x5 I. q9 Y- e3 E Data Time Radiation Temperature& s& G& a: J/ C
0 9/29/2016 12:00:00 AM 23:55:26 1.21 48
) Y. l: Y: A! X1 9/29/2016 12:00:00 AM 23:50:23 1.21 48. t# r: X l+ {3 F
2 9/29/2016 12:00:00 AM 23:45:26 1.23 48
4 @1 k! R# ]6 {11 _3 k- X! s0 Q# D! b- w" X. C
2
4 R+ t) @" g* ^9 z7 x; v: W3
: s1 i8 W) B5 }3 a4" K' A" |# L; G, ]1 T
57 m: K$ Z4 R/ j/ W
67 w' A1 g8 j2 W6 u
72 v0 f- d" O) _' }! q1 a& A
8$ l7 u' S- }3 J! Z f3 C
将Datetime, Time合并为一个时间列Datetime,同时把它作为索引后排序。
0 S+ K$ z6 W2 t1 K g每条记录时间的间隔显然并不一致,请解决如下问题:+ \8 L) y4 S% z6 w
找出间隔时间的前三个最大值所对应的三组时间戳。
6 t% v T l' f4 @- W: e. _/ X是否存在一个大致的范围,使得绝大多数的间隔时间都落在这个区间中?如果存在,请对此范围内的样本间隔秒数画出柱状图,设置bins=50。) K7 U* r7 m4 d2 \; k- u" L8 Z
求如下指标对应的Series:
. U2 Y3 p% r/ l& w' B: C. @温度与辐射量的6小时滑动相关系数+ I1 J1 ^" U; f* T6 p* {
以三点、九点、十五点、二十一点为分割,该观测所在时间区间的温度均值序列
* ?+ T9 v: @; Y5 k4 A! E# q0 `每个观测6小时前的辐射量(一般而言不会恰好取到,此时取最近时间戳对应的辐射量)
5 C+ J7 P& E; [9 simport numpy as np
( Z b0 k2 `& z2 B3 _import pandas as pd
1 M' w: k; ]& n7 t1
# J! n* X% O& n8 l) U2$ w0 p( d! C* O# u2 U8 j
将Datetime, Time合并为一个时间列Datetime,同时把它作为索引后排序。
. p: m) E& b4 q6 c' ~6 M8 F6 Zdata=pd.to_datetime(df.Data) # 本身是object对象,要先转为时间序列
7 W1 U2 v! L+ F/ I' W2 Ltimes=pd.to_timedelta(df.Time)
, T9 {) |! [8 M% b; O2 ` v% Y. edf.Data=data+times- D2 T. J: ?% W/ d9 w
del df['Time']. K+ X8 e+ x* J2 g, X" F
df=df.set_index('Data').sort_index() # 如果写的是set_index(df.Data),那么Data作为索引之外,这个列还另外保留
4 ~0 K8 ]) J& V) B5 D% Xdf
) X& d o# k& |8 z Radiation Temperature- Z# M5 p; g: k$ |7 A5 B, f, l5 d
Data
( x+ V4 d5 m2 o! K4 f! V Y" |2016-09-01 00:00:08 2.58 51
" s* O! q7 N3 Y$ N/ h3 w- K9 x2016-09-01 00:05:10 2.83 51
) H( Y/ i3 c# M) R' M! N6 M/ s2 c! k2016-09-01 00:20:06 2.16 51) U8 R$ Z/ u$ d' j( Q& v
2016-09-01 00:25:05 2.21 51! B$ A+ S; M9 |/ z9 r% S
2016-09-01 00:30:09 2.25 51
$ `; x& @% ~, x3 Q* l... ... ...
9 Y7 ]: W1 p9 o: C2016-12-31 23:35:02 1.22 41
- N% h" h6 X7 m* t' N, e1 a2016-12-31 23:40:01 1.21 41
4 R7 O3 S7 t& v$ j2 s0 k/ z2016-12-31 23:45:04 1.21 42
+ e& ?+ C9 `& \( s8 f7 p2016-12-31 23:50:03 1.19 41# f) l! z; u0 e4 P) Q, J
2016-12-31 23:55:01 1.21 41
8 g, Z/ E8 O; C6 k% C$ E& G3 a7 c. R9 [" N U
13 d8 k- L* r6 |3 y- J
2
1 t0 e3 L+ I6 H0 u3
& @! R0 ?8 C7 j0 m& w' C( d! q4& |( _, J l6 n- v$ _7 d
58 y s, k5 s% ]8 `/ R+ [0 i' k! F8 j
6
3 T3 p' v: O$ j) Z# M7
# c4 p* B: H# k- D( G% Z4 u' P* A8
* q: @8 V' B0 p [1 S# M# L9
+ h' s) e( b7 u8 D108 _# |( Q2 b: o
11: B9 Z- C9 c" z8 L% e' d
12
7 x6 i7 S- ^" k13
$ {) E5 s v9 N- g! d0 V3 W14$ |5 ^" V: U& W+ b2 x
153 i6 c W7 p3 l; i0 |
16* P8 z3 P, W3 a# [, D
172 E6 _# N% j: V+ E7 e& j2 ^
18: i* F6 G5 A- d+ E! ]9 M/ f+ o
19
G: k7 y/ [9 J- F每条记录时间的间隔显然并不一致,请解决如下问题:# ~" b% Y7 c; W* l) A: v; O1 x
找出间隔时间的前三个最大值所对应的三组时间戳。4 Q) V: I/ z- I8 B
# 第一次做错了,不是找三组时间戳: K2 ^+ v. x/ K( O1 f) p) p
idxmax3=pd.Series(df.index).diff(1).sort_values(ascending=False).index[:3]8 u& Q$ p) a* D1 G, ?/ }+ H
df.reset_index().Data[idxmax3,idxmax3-1]
% Y" S' L% T1 t5 L+ {9 G i% R
, u8 k$ j- |& { ]25923 2016-12-08 11:10:42
7 _$ m/ @3 k; V; m$ X; ^& }24522 2016-12-01 00:00:02
5 h% y. d$ @3 \. C0 _& b7417 2016-10-01 00:00:19 d9 k8 `$ J* y" @* c8 Y: x5 X3 U `
Name: Data, dtype: datetime64[ns]
! J" K% @2 o( ?0 L# H p& a1% J: Z9 ~9 J, _+ O: }( C0 q+ `
2
8 h! i5 k, g9 A! _8 \; U( {3% F4 c' [" b9 f4 T6 y" i" k
43 M( y9 ^/ ~( t: P- h% g, v' B
5& ~: S$ y$ E5 W) F, y! i
6
" I% b w$ S" i2 @2 p3 t72 S8 Y* Q6 O! V9 X
8+ i O$ h: B, X" m0 Z
idxmax3=pd.Series(df.index).diff(1).sort_values(ascending=False).index[:3]
7 g3 j" _8 X5 p x9 ?list(zip(df.reset_index().Data[idxmax3],df.reset_index().Data[idxmax3-1]))
2 w' B3 k4 P1 z" j E0 R% J4 |+ n9 `, j" A' y. _: |
[(Timestamp('2016-12-08 11:10:42'), Timestamp('2016-12-05 20:45:53')),
( P8 \) M& P, [2 H- _; c (Timestamp('2016-12-01 00:00:02'), Timestamp('2016-11-29 19:05:02')),
0 j4 l4 L; D. w w: t (Timestamp('2016-10-01 00:00:19'), Timestamp('2016-09-29 23:55:26'))]
8 Q" T: ~. A4 `; `; V1
9 N5 _+ o5 V w, q+ _/ J2
4 Q- x2 E+ I% C% B6 w% ?3
1 K7 ~2 o! `8 L' W7 O4' m6 F& n* R. t& j3 }8 p& Q" s% H
5 ^' d! W% |4 a6 J
6
5 Q9 T! F$ j9 V参考答案:
i& L4 w7 b8 A) Z& @& J3 c- W: r3 ?# W- G! r# N
s = df.index.to_series().reset_index(drop=True).diff().dt.total_seconds()# U' G% P3 j0 h& j5 N9 d, F
max_3 = s.nlargest(3).index
! d! j, D0 H! z, i c9 r+ ]' s/ \df.index[max_3.union(max_3-1)]
1 [' U! I1 F, e8 z& I& L
1 H' D8 X, L1 i. hOut[215]:
+ Q9 [0 [( F: o) B3 s" z* EDatetimeIndex(['2016-09-29 23:55:26', '2016-10-01 00:00:19',- t* H. ~4 Y! I" H# f- e5 A
'2016-11-29 19:05:02', '2016-12-01 00:00:02',
( s) J" K& ?# U# E( e( ` '2016-12-05 20:45:53', '2016-12-08 11:10:42'],
0 ]- U+ L- @. P3 A0 a w dtype='datetime64[ns]', name='Datetime', freq=None), I" r% g3 u7 u- k
1! @, w$ j4 y" ~+ q7 p$ `
20 e+ e1 I. j3 U$ o! {
3: a" B& L n9 {
4* W/ [. T# a* z% S
5
9 C+ x2 ?- r2 Y$ t6* y1 n# M( R l+ X5 C) P" n% C
7
6 y6 c! ? ?' r' K- |- b8- B9 x: h; x+ W0 K& l
9- p3 M |5 y) \$ k5 {& n2 j
是否存在一个大致的范围,使得绝大多数的间隔时间都落在这个区间中?如果存在,请对此范围内的样本间隔秒数画出柱状图,设置bins=50。9 v1 w c8 @; i& c2 E
# 将df的indexydiff做差,转为秒数后排序。再求几个分位数确定取值区间- a# k) d! f" s* L
s=pd.Series(df.index).diff(1).dt.total_seconds().sort_values(ascending=False)1 ` j! V5 \# K8 o" j7 }
s.quantile(0.9),s.quantile(0.95),s.quantile(0.99),s.quantile(0.01),s.quantile(0.03),s.quantile(0.05)# f" t4 b8 i$ T& _$ Q
1 S! _" u& l, j1 b( y3 c/ N) d7 z. u(304.0, 309.0, 337.15999999999985, 285.0, 290.0, 292.0): o# S: C" Q% b+ a7 ~, f) m. z0 d
1
% |, M( A8 E, e. t8 J9 k( c7 i: L2- H4 e$ m: |. H1 ~/ ~
3
6 z; Z4 Y; r' E8 a7 b7 r4
* B. G7 R" E$ i0 r9 V. V5
" \+ x' ^) l* o% O4 @) t%pylab inline
) E+ V* t, y. I: L5 A_ = plt.hist(ss[(s.values<337)&(s.values>285)],bins=50): h4 ?2 d6 Z4 A# V6 ]
plt.xlabel(' Timedelta')
. f9 F* ^ s) n* B1 T7 Z% }. h* B3 `plt.title(" Timedelta of solar")
. Q2 Y( ]7 d \" f3 y4 }. K& y; x& i' o+ {1% q3 W( {6 f3 D) }/ x
2
1 q( D# [3 g4 v+ D3
& T* _' v9 t l) k6 e! E; [+ r4
/ f4 E" q" X. ^9 c
! `0 h6 m" _$ T! ?! N, O; `+ ?) h! ]* q, i, F3 \% N
求如下指标对应的Series:- n1 t; F/ C1 o/ ]
温度与辐射量的6小时滑动相关系数
! s/ K! ]- y; K- P& Y以三点、九点、十五点、二十一点为分割,该观测所在时间区间的温度均值序列1 F& F+ s; e; Z# m
每个观测6小时前的辐射量(一般而言不会恰好取到,此时取最近时间戳对应的辐射量)% c+ r/ V: z* y6 v3 J8 I' y8 S
df.Radiation.rolling('6H').corr(df.Temperature).tail()/ o8 R; |. Z9 w
. ~1 i* j# m8 X5 ZData, R X6 ~/ }: B7 `
2016-12-31 23:35:02 0.4161872 G' G; v6 l1 r$ C* W G
2016-12-31 23:40:01 0.416565
! M4 I% f; d U8 _' R2016-12-31 23:45:04 0.328574" e: `/ y/ C( u+ \" x- n r+ e
2016-12-31 23:50:03 0.261883" Y& C/ b1 n% V- Z+ u
2016-12-31 23:55:01 0.262406
# V7 O6 [' }6 i. |6 zdtype: float64
( f7 A* a$ O9 s4 y: _8 ]" a" H. y1! C5 {) M$ ?* k3 t' @
2
+ ?) v. \, U( O& j7 [" J. g3
9 X* u9 `0 X+ m6 w. U0 }5 c0 i4
5 v# p4 P! K1 K+ a& X2 u5
* J/ o- d/ v; d$ H6
$ H( o6 A. [; t* ^# V3 w: q7
- S5 I* A9 G: I4 U+ H$ V8; l1 R0 T6 e w) c
9/ v' m% v2 J/ q( ?+ c! V T! _: l( Z
df['Temperature'].resample('6H',offset='3H').mean().head()
$ g2 M3 S+ c5 p- u" f% u: K6 I' |; x$ Q
. g" |7 z1 s4 s' w- xData
; w: `0 M- v& y7 S( q0 k5 k4 Z2016-08-31 21:00:00 51.2187500 w b3 I4 x: L8 g. y- ~: N- m/ M
2016-09-01 03:00:00 50.0333330 w: M' v3 U7 \1 ?
2016-09-01 09:00:00 59.379310( U" L( w6 C# g* J, e
2016-09-01 15:00:00 57.984375+ u" [, _3 q0 R& E4 y
2016-09-01 21:00:00 51.393939$ A$ e/ o) S+ F) _0 t+ X# y
Freq: 6H, Name: Temperature, dtype: float640 w5 P. }9 ~- Z( J
1
5 ]" v# K* K* v- n' @# w) t2' Z. s& n& w9 ]# y! ]3 [0 F. g
3
% m0 o! K7 N0 [) v: t: E) G4
* _7 N& Y6 W. i9 b( {5
/ t" r2 L0 \$ x1 F. v6. u3 ?! k) _3 S# m
7
8 a; p- Y" c% @! h5 k0 R* [, A8 y8
) O, X! Q6 l- r1 m1 W9 e. L8 s4 g( }, |' A% R0 u. g
最后一题参考答案:9 ?# ]* b0 E, t: o! S: i
) F F; R3 Z1 Q9 O i7 N$ r) m( y$ z* j
# 非常慢
& e- D% c& @; }my_dt = df.index.shift(freq='-6H')( U* C+ J% O0 b+ F2 y! R3 A
int_loc = [df.index.get_indexer([i], method='nearest') for i in my_dt]3 G3 H: T8 d8 B0 A8 k
int_loc = np.array(int_loc).reshape(-1); @' L0 {1 p T3 w
res = df.Radiation.iloc[int_loc]
M2 O& G9 _, N& t5 K, Fres.index = df.index' l9 I5 T- u8 o) V2 o& p1 o
res.tail(3)
4 e! ~2 M. a0 e$ [0 |, ~18 N Z& p6 G- |% I! v, K
24 X. I% X3 v- @" K4 ^; v `7 V
3
$ @ N3 d6 n \2 c# A4! m; {. z* n* e4 o2 |9 E6 Q3 a
5
; e5 w5 \. { u) v0 L6
h) W7 W" M7 c' F7
: j- N% E8 |7 S9 g1 X# 纸质版上介绍了merge_asof,性能差距可以达到3-4个数量级
) r' Z, W/ R# p) G% h) ktarget = pd.DataFrame(# b, x3 K: A, {/ j, y
{6 Z5 S. U0 n% S8 d
"Time": df.index.shift(freq='-6H'),
. V8 [9 {( t- z! a "Datetime": df.index,
/ t7 q3 T! x- |& G }# n3 t) Z# R \$ S- [* Q
)
! N* z+ V4 G: x0 N$ d, P% U' e" {
res = pd.merge_asof($ \( O7 E/ n7 C' r$ S' T
target,
0 e; C9 u$ p9 n# q: e" P df.reset_index().rename(columns={"Datetime": "Time"}),
" k" D) z" j- x/ ^9 B$ j left_on="Time",
) I" _1 z/ g0 ?9 p8 r1 N+ u right_on="Time",- M9 x8 f. V& Q5 n) f: v
direction="nearest"
$ q5 B& p M1 o4 @5 u4 H0 F" v+ ~).set_index("Datetime").Radiation T' P( r6 a+ {2 t' w) M# t
- b9 p- F4 v7 |/ K
res.tail(3)
$ O+ E: s! a- oOut[224]:
- @5 p# q; I8 N* cDatetime
1 N; j3 q6 A. X/ j. k& T2016-12-31 23:45:04 9.33
* j# w3 F7 v* D% N! y3 {+ T2016-12-31 23:50:03 8.494 @9 O: l% O7 w8 {, Q# e
2016-12-31 23:55:01 5.84, a- e- H2 g4 A8 K6 L' Y2 I
Name: Radiation, dtype: float64: n5 E) {' a6 S$ D# u
5 y1 z3 m" K# n' H0 y" ^2 }18 d8 V* T" R$ T5 b
2
& s" O- U5 s1 Q; _6 Q! Z0 K, s35 `' u, ], s4 W$ T$ K3 n; U) Z4 x1 C
4
4 W: L; r- Z }5: F. g$ _5 l! g+ T
6# ^- z; m( f, @" J0 J1 E, z
7 {+ y/ U/ h1 p* P7 [5 G9 k: l* ^; l
8
# \, N2 Q! p( x98 M5 D+ N, M, K: R
10
& S- L7 g) x% D- J. N( D% }11- Q+ u9 l \+ O3 o$ a" |2 k5 f# g$ M
12
) H8 _3 T5 o6 Y6 m* w$ ]! T, Z13
7 X- R) p* o5 C& b14
! w/ U0 H/ {+ ~6 [" k( c15
) f9 C6 Z. t8 O% R" S3 H16) M$ |* y; s1 P& m' l
170 S3 o! J( L* \! @" F: h' z' k
18
* A. Z) p- _9 h19
- P" Q2 w9 o7 p* @ j z7 m" W# Q20
! O2 T1 L7 ` C# u: e1 V1 M3 d' ~21
, ~4 ?2 F1 W) i. k1 N$ A q" t22
. g" [( c: J4 F; M- ~23
2 `6 w% t# J( w. O) pEx2:水果销量数据集
- @- y+ I2 c0 j2 Z" B现有一份2019年每日水果销量记录表:, L0 a5 ?( R: b" k. U
$ i$ O8 ^' u) }3 \$ Mdf = pd.read_csv('../data/fruit.csv')+ v* b4 D8 N4 _
df.head(3)5 g8 R3 P6 z2 |. g, S) Q& a: y) G
$ R5 [% x& o: t6 Q* u2 z' Q) \% I
Out[131]: 1 X- V% f2 X# @
Date Fruit Sale7 u3 Q5 U' O5 k1 { D
0 2019-04-18 Peach 15
- G5 a& y. r7 t% S1 I& B/ I1 w1 2019-12-29 Peach 15' D; s3 Q2 k6 M1 \4 e1 Z
2 2019-06-05 Peach 194 c8 A k6 s& f: K# z
1$ e2 ^: ~9 n4 \
20 Z) ?# ?& P; I) S3 p) z7 q1 a
30 ?* D3 r6 R0 E. s! K# R% k
4' i0 Y1 `3 z% V% j0 [# h+ `( d" U
58 G+ i# q# u1 q; R" j: d# X
6% t4 z( Z' `* `% C$ x' p% [
7
( J$ }$ }) w' ^' {( L5 E; t8+ V' u1 O( l g2 F( |& u
统计如下指标:- E" p; i3 \5 x* t' I( k, w
每月上半月(15号及之前)与下半月葡萄销量的比值
5 o7 j# n9 G7 n6 I! M( T每月最后一天的生梨销量总和
: j. { ^& H: O" r8 u- p每月最后一天工作日的生梨销量总和
% `9 n5 p4 h4 D$ S: a4 Q每月最后五天的苹果销量均值
! w& a$ {: S) G" F5 A7 `0 \按月计算周一至周日各品种水果的平均记录条数,行索引外层为水果名称,内层为月份,列索引为星期。& m+ X: f* _% T9 f- n
按天计算向前10个工作日窗口的苹果销量均值序列,非工作日的值用上一个工作日的结果填充。
: N: q9 l2 j( \) w4 limport numpy as np3 G- g2 o: `- c- d' h
import pandas as pd
1 U6 ]" R+ D9 `/ f4 g/ _. i7 Q17 h7 O4 D; \$ p( i2 e0 X8 h4 x9 ^
2
% [6 y z2 c+ b7 G! V. S. n" N& y统计如下指标:
) n) g( @3 t2 ?! t每月上半月(15号及之前)与下半月葡萄销量的比值
, ?1 p! S. p" j9 o! d2 B+ A) u6 G m每月最后一天的生梨销量总和
( Z1 Y v4 y3 Q6 `% D6 e! o每月最后一天工作日的生梨销量总和
( E, ?* A3 k+ _9 M9 |每月最后五天的苹果销量均值
0 E. v6 t# V7 j: }, R7 b" B# 每月上半月(15号及之前)与下半月葡萄销量的比值
0 b- W$ a! _3 R+ x, A! ?df.Date=pd.to_datetime(df.Date)! B+ w e2 f( X7 U9 ~0 K( W
sale=df.query('Fruit == "Grape"').groupby([df.Date.dt.month,df.Date.dt.day<=15])['Sale'].sum()
" s. S' ?$ y+ W% ?- e4 Fsale.columns=['Month','15Dayes','Sale'] # 为啥这么改没用啊8 [/ y1 h/ s# E3 l3 }2 f$ T( ]
sale=pd.DataFrame(sale)/ f% `7 Z9 L$ |& E) E% J. V( F
sale=sale.unstack(1).rename_axis(index={'Date':'Month'},
" H' j- p4 q1 s# y columns={'Date':'15Days'}).stack(1).reset_index() # unstack主要是两个索引都是Date无法直接重命名
2 S7 ? C+ @1 hsale.head() # 每个月上下半月的销量& W2 n4 a9 O& L4 v# N+ f( F3 v
8 ^ W/ r+ V! ? F' O9 q" a6 y Month 15Days Sale
7 Z6 @: d( C9 ?0 \4 t% q) c! y0 1 False 105036 U9 ^. Q0 i2 f' p0 X
1 1 True 12341
' Z& V# X- Q o4 t2 2 False 10001
* k {) c3 P, e. l" Y f6 s& g3 2 True 10106
3 h m8 u0 X5 r& g) }( p4 3 False 12814
7 A; S- ~ ]. H/ o C, k- _5 O9 |) n2 H4 k- U) A4 J
# 使用自定义聚合函数,分组后每组就上半月和下半月两个值,根据索引位置判断求比值时的分子分母顺序
% O4 K8 x. e1 V; V6 L/ |sale.groupby(sale['Month'])['Sale'].agg(
+ [* M0 D& u- g2 J8 c" J lambda x: x.max()/x.min() if x.idxmax()>x.idxmin() else x.min()/x.max())
9 q( H! h) _5 r
4 m5 q" @; `" t6 A) RMonth/ A, M( B- m$ z( ?; U* C) h
1 1.174998
1 j) ]) F9 I& t. G) z2 1.010499
6 E! U, q+ ^: q* U; G3 u3 0.7763381 J! M2 s! @+ q4 Z2 u o$ i# ~
4 1.026345
# H( F. P0 w. _5 0.900534
( P$ B, }, e$ t# o/ x6 0.980136
6 ^2 l9 j2 C( Z7 1.3509607 L9 e% \( G8 p! w
8 1.091584
5 c5 V K! y) u9 1.1165081 D9 w9 F* J, Q h# Q R
10 1.0207844 y" Q0 Y8 I" h8 \6 Q/ v8 T: Q
11 1.2759112 q, {" r% c, Q
12 0.989662; w8 X! V1 p+ d# @. j. n3 W/ n) \- q
Name: Sale, dtype: float64/ ^+ X4 @ d8 k6 \+ d( k
6 w, F! z1 M6 p: U
11 B2 {: m* `- G1 _5 A
2; L3 {+ E! {1 \- i( X" L
3
' U& a; B* E F& X5 Y4/ m; s% a, H" _% ^6 I
5
d1 D" E2 L& u3 R' q! h65 P& r. _& t: T. V/ D0 l
7# t# b1 a M! k9 L7 M& }9 {
8
~+ m- Q8 Z; q( x9 c9
: M H9 d! b7 t- w0 z0 A3 r$ W10
3 T z+ z6 S1 I; d1 a7 @5 E: a' k11
. c5 t" ]2 |/ H12
0 z$ W* g, q7 r% x13
2 d/ ]$ f+ S4 z/ N% d9 ]. c14
0 r* h! C& `3 p- h" U) O- |15- O: s4 P( [' d" g \
16
8 I9 }4 k' O& Y0 k% d17- C- C3 o; u; q- N# O, q- ~! g @+ W
18
5 U e. m: i% d1 t% Q8 ]0 Y/ s% R19
8 s* } d. h/ Y# U7 b209 W w6 O0 ^( p. E0 W# A
21
" J% k" d& T2 l5 D22
/ C* K: N: A. I& t/ [; J* h2 ?23$ }6 }: ]4 {6 C
24& I/ V5 N9 O' g, X' |
25
) ]! I. e! v5 L5 c26
2 O8 J! Q' T0 @, l5 X" m3 m27
% z' `! I' u3 H- j% X4 v, Q1 T287 C5 E$ l7 N5 B
294 {, g" R1 ~- `! p, a
30; }) Y4 E4 g- p
31
0 a! _6 \- {' Z# f3 @9 }# C321 j2 o$ M: Q6 i4 B( k' e
332 N- I( c6 X2 P' b' _
34
- k* {# j+ R: J( h# 每月最后一天的生梨销量总和
, ^ i' X& J7 i% u5 c5 N5 p* tdf[df.Date.dt.is_month_end].loc[df.Fruit=='Pear'].groupby('Date')['Sale'].sum()
b. I1 _+ u z0 N
) p# R3 O* K' y% u+ Z0 W7 fDate- _, p a. |. S, u0 |- I
2019-01-31 847: l3 P1 _; Z! w9 @8 T% n$ P6 r6 E
2019-02-28 774
: a- }1 R" `) G' Z) m1 J) r2019-03-31 761
& R( x: M0 |/ E7 V1 a2019-04-30 6484 D6 u4 d0 U$ _
2019-05-31 616 G. W* [7 y. ~" L/ p) `5 T" x
1" D. j- O2 A/ e) G0 a% U
2
) u" v; e) `: _3
" X. r1 w/ `. w5 S! ~+ t0 w4 `9 i4 ~# _4, | |$ l9 z" N' B8 P
57 x" V9 M: n9 Y: S/ r0 y
6
. m$ H, {) X9 r) s6 Q0 ~7! Z% G: E. |# g" f) F
8
( F7 a/ U- K" X) ]9+ ~$ e% O! c( y% O S: F; G0 M
# 每月最后一天工作日的生梨销量总和
! F# L' [/ ~6 c7 a, f2 Tls=df.Date+pd.offsets.BMonthEnd()$ M) z2 O' S1 s& g, q) E
my_filter=pd.to_datetime(ls.unique())2 i: o% V$ r7 }. G
df[df.Date.isin(my_filter)].loc[df.Fruit=='Pear'].groupby('Date')['Sale'].sum() [- z8 m* w$ o5 W2 c2 L" s
* F: }8 z% e- O" g* J" u1 _1 S' |, C
Date
4 L* I& q1 x5 o, ]8 w( I; j! Y2019-01-31 847$ n+ q$ d# i) y- k& S
2019-02-28 774
/ u, }: Y& U& [& ^ t2019-03-29 5100 j6 N5 o: {1 B" K6 p
2019-04-30 6483 o) s- f: R0 L1 x
2019-05-31 616
/ z& \2 F# f1 O* L, u1. l, A6 V* Z7 R0 h
2
! w# q- B8 { b' p3' F7 R# B. Q5 i$ \' _0 N& Z9 j
4" E: g ?4 N8 m2 _- r
5
/ w- p8 ~6 \4 q: _: H6
4 b% t$ U" U* p% t& `2 B; s7
. B' v( x) O. g! S( U C! c8
* k% b' _# f4 S9: p! Q5 {+ ^ {. j# R) S- F0 ^2 \
10
+ j& z/ F$ o1 [# E& |11' l6 c" i, y8 P! K" M4 W6 e
# 每月最后五天的苹果销量均值
r2 F- @- \9 b/ x9 gstart, end = '2019-01-01', '2019-12-31'- ` {, c9 D5 ~) H ~
end = pd.date_range(start, end, freq='M')
! @% J7 N8 \% ]end=end.repeat(5) # 每月最后一天的日期列表,重复5次方便做差7 i$ M' ], S) ?: I" j
O A: M+ E, b6 U2 x0 ltd= pd.Series(pd.timedelta_range(start='0 days', periods=5),)6 u+ Y" C" u8 L, n' b
td=pd.concat([td]*12) # 日期偏置,最后一天减去0-4天! p9 q8 f% b, V8 d, \
end5=(end-td).reset_index(drop=True) # 每个月最后5天的列表$ Y9 ?: P4 c9 p
6 j$ q. I4 k3 }$ p+ vapple5=df[df.Date.isin(end5)].query("Fruit == 'Apple'") # 每月最后五天苹果销量. ] T8 H: v- U# o/ C
apple5.groupby(apple5.Date.dt.month)['Sale'].mean().head()
$ N+ e5 {& O c5 d# o* z& T3 c W
Date
- D1 i: y9 x5 C; L, c4 q2 m' f1 65.313725
2 ]/ c- H* w8 l/ ~& \2 54.061538: T& d7 d* B' y! V) _) }4 f1 }; M& w
3 59.325581
# { S* I; o3 ^9 m9 _/ C) \4 65.795455
$ _8 u$ R: B J% J5 57.4651161 @* ?3 w9 W' e' a
1 N" f6 f( K0 O
1
* b% B2 T9 S7 H# x# J* Q* J* E2
0 p' X2 C5 h( N7 d9 X3
( h. @5 k$ m0 ^% o; R7 }2 _, [4
$ I, g. ^. a6 N! T- X1 P: e5
- u9 V6 S5 j; C; @1 Z6
* x- v% o+ D7 b6 ~# T" V71 ~8 h) `) y: s4 {$ Z; X* k
8" ^& U2 Q$ F8 ]1 b+ @) s3 T- B$ u
9
9 x+ A; l. X! N) {10
/ O" ]$ R* N+ }11/ C1 s. h$ F7 p$ j% a; g: \6 s1 e
12( h, @( Y6 v# U' B
132 K) T- s! H" g* F" N! t
14
) d" C8 Q: W q' V% m0 E7 e157 @# b$ n7 h# p
16/ n1 `! z# e2 h9 E3 q; l
17
$ h% `" S4 }0 \6 k2 q; i" N6 b& U18
0 f5 `8 C- t3 ]( ^9 x( H& P# 参考答案:+ k5 h7 g* ?5 R% w
target_dt = df.drop_duplicates().groupby(df.Date.drop_duplicates(8 a3 K/ G5 w# X \0 \+ u
).dt.month)['Date'].nlargest(5).reset_index(drop=True)3 `, h8 o! c& S
' D" A3 _8 T4 l9 eres = df.set_index('Date').loc[target_dt].reset_index(' s/ x2 z2 w4 ~8 R" v: z; h2 [
).query("Fruit == 'Apple'")5 J, R# y+ ~5 J7 q6 g3 U# C
! u! E0 A6 O: B( J2 x h6 A! Kres = res.groupby(res.Date.dt.month)['Sale'].mean(: l6 f4 f5 y E% R' b
).rename_axis('Month')
% W4 u5 N6 Q: R; B! u- O; m h& m2 @- S1 g. ^
) Q1 N4 I2 J% Z; }1 p" [- B% A
res.head()% f& |' O. K+ W0 f% Z
Out[236]: 5 K. Z( w7 Y1 h C( z* k
Month
9 g$ a2 V2 x0 k) a2 p% @2 R$ J- P1 65.313725
' _6 w% w) F* f* v2 G0 a# m( Q! L8 i2 54.0615389 L2 M) a' y* `
3 59.325581
' p+ K0 q4 Y @( S6 L& \4 k$ F+ `4 65.7954555 Q; M3 L% i9 C
5 57.465116* ~; n- i! o, D9 P+ H" i. m
Name: Sale, dtype: float64
/ Y3 W2 O( g+ H# C: ^2 F
& j9 \0 ?( I, w8 }) x; H1+ c9 o5 z6 M6 _$ j: q
2
& a& r1 b, K$ p f8 [% W35 V: a! n) q. U: n1 L6 S* s4 k5 P
4
+ b# _- D+ x6 W8 z5# z3 z" X1 r: K7 m
6
2 R' g8 ?9 p$ A. I/ N: K5 b7' I3 O! u$ z% j: ?
83 V/ y5 i, X [
97 n3 P' w! S- ~5 A/ ^7 l
10, \9 F5 S/ ^% `5 B2 {
11+ @9 O* ]) ?5 S/ z# ~
12
7 ?9 c3 k1 F# G+ {+ q0 x2 I0 ^13
) M& j' C+ v7 _. q147 t3 I$ c% x/ a
15
1 z9 h1 ]; w* h! y3 u `; f! Q/ |1 U16 J0 Y; V) m, g6 a. l: G9 ^ W% a
179 ~' L; z& Z. B B- }6 O
18! {& v$ B) ]2 x0 Y% R: ~; U9 ~
19
& J8 T6 X! H7 _) ]# ^& G203 S8 v" e. T' A# B) L3 c2 X
按月计算周一至周日各品种水果的平均记录条数,行索引外层为水果名称,内层为月份,列索引为星期。
. X8 l0 f T' q% z8 Q: ]; p) U4 mresult=pd.DataFrame(df.groupby([df.Date.dt.month,df.Date.
" A1 ^9 W+ Y7 j' | dt.dayofweek,df.Fruit])['Sale'].count()) # 分组统计 ; u0 q4 [* S$ v2 i: x
6 b. i. J2 v7 c2 N# g- l6 B) @
result=result.unstack(1).rename_axis(index={'Date':'Month'},6 e, E" `7 ~. @+ M7 b
columns={'Date':'Week'}) # 两个index名字都是Date,只能转一个到列,分开来改名字.
8 c+ ~, M- A# i3 x( ?9 c8 a' }result=result.swaplevel(0,1,axis=0).droplevel(0,axis=1). w/ w b7 e. G- P7 R
result.head() # 索引名有空再改吧% G' e# w/ c6 y4 Z, V% ?
+ V/ U, s2 i D
Week 0 1 2 3 4 5 6
" Y5 h Q5 S& R* PFruit Month 6 A3 g. ~* o# Y" k
Apple 1 46 50 50 45 32 42 232 u' m' I J# Y7 [
Banana 1 27 29 24 42 36 24 35) J9 I; y6 v: N8 M9 p% q3 |; s
Grape 1 42 75 53 63 36 57 462 ?6 r E5 w! Z/ @
Peach 1 67 78 73 88 59 49 720 k+ E+ s! E3 Z' Y0 J2 s
Pear 1 39 69 51 54 48 36 40
' z# ^- C9 ?- V- A# M2 }9 U% b1
2 [+ _( ~5 `# o( I* b! L2
/ j$ D* s4 O" I. @3/ E0 j4 M L& N2 y" D* u2 p
4# O7 |1 q, i4 d! ?. c6 ^; n3 ]
5
* H9 k, [( {. ~ i5 X6
8 e5 o" H4 D- h1 u: Z; p8 f. r70 C a$ n. B# y
85 |+ D3 B8 L5 X7 `
9% l4 f: s/ X. S7 t% n: C8 {
10
; W3 _, d, d. h! H) t114 w) }- u U: N8 W" Y, z
12( l. |8 x3 _9 h2 T: x
13
. N* v# t0 m) d: D1 ^14
0 w( X) W7 H: n: E, W" I15
3 o+ n* j# X# t; D0 b! X按天计算向前10个工作日窗口的苹果销量均值序列,非工作日的值用上一个工作日的结果填充。+ r9 x7 W4 a5 T3 R2 H9 F2 Q* u
# 工作日苹果销量按日期排序/ U, h3 r5 p7 m S$ l( ^! ~
select_bday=df[~df.Date.dt.dayofweek.isin([5,6])].query('Fruit=="Apple"').set_index('Date').sort_index()
& d; R- ~4 U" c% N9 iselect_bday=select_bday.groupby(select_bday.index)['Sale'].sum() # 每天的销量汇总( q6 q2 b5 t2 g
select_bday.head()
0 n/ t) d7 W5 _. t+ l' v0 k* S" q9 Q( V+ I
Date
4 l! j9 \7 I! x4 x" u& v2019-01-01 189
* X) y, w5 @* ]' `- {( a! H4 |: d2019-01-02 482, U0 e/ a! K& z4 s1 \& y2 ]
2019-01-03 890
, v' s- ?& F: X2 C; \: b1 p: l2019-01-04 550
3 B" m7 X u w2019-01-07 494. E6 w% t" X" U/ T6 e
; W' s7 q z7 p0 I# 此时已经是工作日,正常滑窗。结果重设索引,对周末进行向后填充。
4 @, R% Y: Z% Y$ h2 T+ yselect_bday.rolling('10D').mean().reindex(df.Date.unique()).sort_index().ffill().head()2 i) s: K" l$ u* ^9 d0 J. n# P
3 V$ @) A9 k# s" T v' M
Date
* D& A5 @( v- a+ {! P. t2019-01-01 189.000000
% H: a" P- O) Q: W2019-01-02 335.500000, \+ Q( U, ~" t4 e* `" C
2019-01-03 520.333333; I3 A, G9 N3 t0 d$ Y. D
2019-01-04 527.7500009 `# T0 G3 H: h) X# @" o9 s
2019-01-05 527.750000 T7 e" @7 _5 o" l) @( v
8 J# {' t/ P) x
————————————————
; @% j, ^; I1 e. B3 ?- p2 M3 }$ \版权声明:本文为CSDN博主「神洛华」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
! S6 q- f' F3 U) V8 u原文链接:https://blog.csdn.net/qq_56591814/article/details/1266339133 a9 M. }8 x9 \$ w; s# E3 r
6 g: P% K0 S l: h
& ~0 N! e' h' \6 t2 @) b/ E |
zan
|