- 在线时间
- 1630 小时
- 最后登录
- 2024-1-29
- 注册时间
- 2017-5-16
- 听众数
- 82
- 收听数
- 1
- 能力
- 120 分
- 体力
- 569586 点
- 威望
- 12 点
- 阅读权限
- 255
- 积分
- 176099
- 相册
- 1
- 日志
- 0
- 记录
- 0
- 帖子
- 5313
- 主题
- 5273
- 精华
- 3
- 分享
- 0
- 好友
- 163
TA的每日心情 | 开心 2021-8-11 17:59 |
|---|
签到天数: 17 天 [LV.4]偶尔看看III 网络挑战赛参赛者 网络挑战赛参赛者 - 自我介绍
- 本人女,毕业于内蒙古科技大学,担任文职专业,毕业专业英语。
 群组: 2018美赛大象算法课程 群组: 2018美赛护航培训课程 群组: 2019年 数学中国站长建 群组: 2019年数据分析师课程 群组: 2018年大象老师国赛优 |
( }5 B& o/ E& B2 s
3 O4 |3 {6 S$ l p# f8 m
# S# g- O2 x0 G文章目录" H: \ A7 @! D& D( m
第八章 文本数据
3 f! a* V% Y; k. X- }' E, d$ F( V. c1 Q8.1 str对象% \/ a1 n( z4 e/ w5 }4 U1 j, k$ U
8.1.1 str对象的设计意图
. V+ }6 T1 D _9 `4 C8.1.3 string类型* D4 T' [7 X2 g i1 S/ b
8.2 正则表达式基础
8 g% w% d) M4 ?, _. I8.2.1 . 一般字符的匹配$ b& k. g p, g
8.2.2 元字符基础. K! k% a- H( ]
8.2.3 简写字符集
5 J5 L6 C. r+ H) R. p3 Y! l: b8.3 文本处理的五类操作
! S. e! Y) K( A5 E8.3.1 `str.split `拆分' J }$ ]8 Y& x& I: A' N
8.3.2 `str.join` 或 `str.cat `合并7 o& p; h! [; H
8.3.3 匹配
4 W1 U6 _# ?" r) q9 @& r8.3.5 提取2 w5 H( x4 Q) D
8.4、常用字符串函数( T d2 @; V c) l' _4 L( i- s
8.4.1 字母型函数
Q* c" y+ X5 J( `2 B+ b8.4.2 数值型函数- m2 x W3 K0 F: H2 R5 c% _
8.4.3 统计型函数
% D7 `6 `' E2 b) d3 ^% [8.4.4 格式型函数
0 T0 y& g: f1 G1 M! ~! p$ k8.5 练习 P/ J0 f; B3 j3 c
Ex1:房屋信息数据集
^$ [1 c1 `3 u0 o- GEx2:《权力的游戏》剧本数据集
- B2 m1 ^6 m3 |0 _# `第九章 分类数据0 r' o( t9 G$ P0 N% p
9.1 cat对象
/ ]" G) ]1 u- V" u" ^- U9.1.1 cat对象的属性
: p% R$ L" r! u8 ^) \( Z9.1.2 类别的增加、删除和修改' r( p& i: a- Y, t- Z/ H6 c, @
9.2 有序分类
) m+ Y$ C- X. C3 U4 W) k1 D& w9 Q9.2.1 序的建立
. [* z9 Z, E ?3 c9.2.2 排序和比较1 D- a1 f% a. V3 {
9.3 区间类别+ [, O: ]. Z D# M9 {1 t
9.3.1 利用cut和qcut进行区间构造
+ {* } q( |4 P9.3.2 一般区间的构造0 X' n1 B' p. ~; R) @
9.3.3 区间的属性与方法
0 L& O4 F7 A& Y- C9.4 练习% O6 j( m! a+ u, o5 u7 `: B
Ex1: 统计未出现的类别7 d' E5 B% R1 h2 @ e- W
Ex2: 钻石数据集
2 ?$ l- Y+ f7 X4 \, Y7 `8 n第十章 时序数据
3 ?- F% |7 Z* a* O8 K10.1 时序中的基本对象. q$ [1 f) H& [) m5 }, z6 e1 ^
10.2 时间戳! t1 U# @4 H( O4 W: h
10.2.1 Timestamp的构造与属性
% W0 l% a M# F1 q( Q1 K4 W10.2.2 Datetime序列的生成; Z0 i7 I% v1 s; {; m
10.2.3 dt对象
: z \1 e; G) Z2 y10.2.4 时间戳的切片与索引5 B7 L3 v6 \1 D+ R% V# s) o
10.3 时间差+ \+ q& s8 O( r& |$ b
10.3.1 Timedelta的生成2 a4 f, U$ N5 k5 @) w( m
10.2.2 Timedelta的运算( R2 l) `) M4 d2 |
10.4 日期偏置
1 H2 U* X2 q c+ B4 t4 K10.4.1 Offset对象) B- l+ v$ u' o8 ?5 L
10.4.2 偏置字符串* ]2 o M: m1 ~; i' U
10.5、时序中的滑窗与分组
R, o5 C. l/ @10.5.1 滑动窗口( P, q' O! P5 a+ [' M, x: H7 E3 b
10.5.2 重采样: r# M* Z! k9 q1 |
10.6 练习; G5 k. G: G" J5 o
Ex1:太阳辐射数据集5 j5 v y5 H- Y! g8 X, Y
Ex2:水果销量数据集
. E2 L! `4 b( Y: d1 { 课程资料《pandas数据处理与分析》、github地址、讲解视频、习题参考答案 、pandas官网
0 ~- R* _4 l6 |3 ~' o传送门:
: c& i6 A7 p! o- Q2 p8 X
/ p5 N X& ?$ L- Q# k& Bdatawhale8月组队学习《pandas数据处理与分析》(上)(基础、索引、分组)1 A, G' X5 O$ |" [$ C2 |
datawhale8月组队学习《pandas数据处理与分析》(中)(变形、连接、缺失数据)- u( M% }9 U+ n' Z* ^. u
第八章 文本数据3 Y3 `6 m* }! _) b! G1 {" g/ r" F0 _7 V& v
8.1 str对象6 z# ?/ }* ^$ x2 ^
8.1.1 str对象的设计意图
* L; n/ Q0 }2 V. G( Q str 对象是定义在 Index 或 Series上的属性,专门用于处理每个元素的文本内容,其内部定义了大量方法,因此对一个序列进行文本处理,首先需要获取其 str 对象。在Python标准库中也有 str 模块,为了使用上的便利,在 pandas 的50个 str 对象方法中,有31个是和标准库中的 str 模块方法同名且功能一致,例如字母转为大写的操作:
# r' |6 W" } |. b! V; `/ V+ D3 R4 d9 k0 B
var = 'abcd'
: J3 @) A4 Y4 Y' ~/ Sstr.upper(var) # Python内置str模块
) w X0 n! R0 oOut[4]: 'ABCD'
* j M4 [( J* w% u. F X! V& K) F
) I% Y: o9 n( d9 ^$ Bs = pd.Series(['abcd', 'efg', 'hi'])
2 I5 J5 H( H9 k& P/ C+ j: N
- b4 p( S2 d6 Us.str' k! z. I, y5 A8 X( m) Q6 Z
Out[6]: <pandas.core.strings.accessor.StringMethods at 0x2b796892d60>" \% b7 U7 f. t9 ^1 W0 z/ D2 d
: {# I0 w/ Q1 k& Rs.str.upper() # pandas中str对象上的upper方法
8 P' C" Q* e- jOut[7]: 2 L/ t* i' A5 @* L2 B; a. f
0 ABCD
# I: I, V8 _8 s% y+ w, S7 r2 u1 EFG
: @* s( I( {+ m+ f- j2 HI& V0 i7 R8 J% h+ p3 @5 S7 [
dtype: object& O: M9 r8 n! W
19 j1 s/ u+ t7 P1 O0 g: i; P
2
% T4 U. y5 N5 x$ s3
+ {4 O4 C" w4 _- K. q5 V% e4
1 ~: T4 ^6 I0 Q+ U z! F5
" _7 o9 `8 v- x; u0 D& p6
* k; K- s; _% N" s( \7
& D7 k1 l# ^. i H- ~! ^+ J8
- m% s1 o$ |2 R1 E0 `- j% k97 E; v9 D" N5 M: I6 t2 E. O6 E
10
! ]- s7 w& |% N: P; I' k11, U! @- i4 }, J
12+ r0 M! \/ |. {. x
13
' {8 `8 c4 H3 E14
/ h3 y' q5 c0 j& B15
" Y) L/ D5 q4 [! ^) u5 w* B* T# U8.1.2 []索引器. \# S& g N! I" v% n7 \5 W
对于 str 对象而言,可理解为其对字符串进行了序列化的操作,例如在一般的字符串中,通过 [] 可以取出某个位置的元素,同时也能通过切片得到子串。
6 I4 k/ o7 s' h. M, n: K; n T pandas中过对 str 对象使用 [] 索引器,可以完成完全一致的功能,并且如果超出范围则返回缺失值:
; v5 J# X l* O2 w1 _9 }3 }* f9 Y8 d* m% e
s.str[0]( W" j( o5 M" k6 t
Out[10]:
+ }$ o9 k4 @$ T0 a3 S5 Y* k% ]: b. \
1 e* l# z, O ~; k. `) o/ F( q$ _
2 h, B K* e6 v2 J, [
dtype: object: j1 v% i0 ]/ T6 ?
: j% W* j; J! W4 x' F( o( k- ]2 j& x
s.str[-1: 0: -2]
j. z# ~9 u3 v6 D$ p0 w- ^+ eOut[11]:
" ]( z* r9 e: q0 K3 v6 K" E# Y- O0 db
2 Q% D6 L8 o6 `2 P: `1 G1 g
; p+ U2 x7 ^ ?2 i
2 ]; P* K) L# x2 fdtype: object, v e# J9 v( i' U2 M+ y% g; k
% A/ G9 h; C! f4 ks.str[2]
" Z! ^: F. D. H. VOut[12]:
) `" ^) Y- v3 G% {! x/ k0 c
9 R; i* ~. q8 e! Z' b9 G1 g
5 m% p; m' p, L0 w2 k2 NaN
7 S& p/ V. m" l3 bdtype: object
2 ^8 Y3 |7 ~- H+ @/ N: @% T- A) F. e' M7 ]
1
o( R! d" [! g) T! T' d7 S2
3 J; D6 _% J* ]( w" h3& _1 \2 F/ R- x$ K$ S* ]& K: Q
4
0 w7 P0 A! g$ `6 d5
_% u. `9 Z$ V, `7 u+ L% z) _. h9 D6 s6
+ E# B3 W7 Y, M( q% y, N7
" V1 W/ B% p, q9 k4 G$ c87 H6 t) c+ ]% f
97 {4 H# M7 I0 Q0 _' x
106 i) s/ c/ B9 s, u* d/ I% [
113 s6 e8 w# C9 V
127 H3 x3 p& g. X" _
13
& U% @. x0 R, T0 \& F4 G' Q14
. L) J1 g! t4 H5 S- l9 F9 y2 f; B5 a15+ I% l: w$ T4 |) U8 D
16
) I" d. ^5 X& Q( U8 s" N1 n17
! [9 Y3 H- \1 [! Z186 v! O3 u5 x% [0 o- q
197 U3 }* z2 X }3 E
201 C/ c( A" ]9 [. B- r
import numpy as np
7 E/ q# u# L; \1 T6 v6 cimport pandas as pd
; p7 f# ~2 r' d9 f9 A: k& V% e; r/ b! g8 ^: y
s = pd.Series(['abcd', 'efg', 'hi'])
/ `2 y! S) e/ l3 _5 a Fs.str[0]
7 |7 o* E2 j( b6 V1
' @: o- V; e0 J) D5 @9 W$ Q22 G! `* Y: R6 g
3
# L! b6 t5 w; R4 A/ H) F/ V4
* b2 B& j# t$ I: Z) {1 E5
$ c; Y" V" D$ O% \/ k0 a* S) J: X3 f% F- z) l$ _& P1 B
1 e M/ `) C, H0 l# Q( w4 P3 p) [
2 h
% a4 _$ N& \0 x' D# n4 ydtype: object
( y9 t* J! N+ N- e' p" E' K1
: r: ?7 y( \, N2
8 f1 M) L2 @! K4 [: o30 n0 f' }9 U! ^0 z/ V3 W
48 l: M. q% E" j3 f0 w$ L- f
8.1.3 string类型# K: A$ D! `& U) M" |
在上一章提到,从 pandas 的 1.0.0 版本开始,引入了 string 类型,其引入的动机在于:原来所有的字符串类型都会以 object 类型的 Series 进行存储,但 object 类型只应当存储混合类型,例如同时存储浮点、字符串、字典、列表、自定义类型等,因此字符串有必要同数值型或 category 一样,具有自己的数据存储类型,从而引入了 string 类型。9 g' b- F8 O! F1 g1 p
总体上说,绝大多数对于 object 和 string 类型的序列使用 str 对象方法产生的结果是一致,但是在下面提到的两点上有较大差异:
C) e3 ^; o1 t8 o% }- H
3 z9 c6 | |, N# a% j. Z$ ^& P/ }二者对于某些对象的 str 序列化方法不同。
% A4 J. t/ X' V3 R2 D& B可迭代(Iterable)对象包括但不限于字符串、字典、列表。对于一个可迭代对象, string 类型和 object 类型对它们的序列化方式不同,序列化后str对象返回结果也可能不同。例如:) a) W0 E8 A6 O; Q% j9 n
s = pd.Series([{1: 'temp_1', 2: 'temp_2'}, ['a', 'b'], 0.5, 'my_string'])
& y8 ~& Z1 f$ F, ps
$ G! m7 @3 K; e v$ f* `- [, @, n9 e1* n0 }+ {. {1 x. a7 F
20 D$ Q7 ^3 D# J# f; R
0 {1: 'temp_1', 2: 'temp_2'}
$ l8 l! M1 m# `. f: q" @1 [a, b]& N1 ^4 D* ?1 y) ^( N
2 0.5) Q6 K# G; `1 W4 ]' n6 q
3 my_string
+ j/ y6 h1 Y, q/ r! R, r: Fdtype: object
# J2 R1 ^4 `5 p5 g" Q& O$ y( b1% L9 e, _1 I& j
28 ]9 B z0 Q j2 i# Z: B
3
4 T: P8 C3 s& R/ X8 i4
! Y7 U4 v4 b6 V6 J* z7 p& `5* }9 g# B2 G. {% q
s.str[1] # 对每个元素取[1]的操作
, ^1 E: L6 i! x" X1! A! p5 G8 j+ K( V: n) ~' k
0 temp_18 k8 I$ l, v W; J/ T. L! A" q m/ o
1 b+ M, L q8 ]6 E/ p6 `- P
2 NaN, Q! s, q* K0 D
3 y
) @! b: U+ U( E+ s: O! gdtype: object
, b7 f# I% x& m: D: @9 k19 D" B2 P6 a2 d3 k5 H
2
& r& L3 P/ r2 Y3 ] \- B1 Y3
5 Y# H# S8 W8 w& ` Z4
; F: j: \: }1 V0 U" l* M9 a4 D [5# a' x' U# u9 m. V7 S5 k; o w9 V
s.astype('string').str[1]
3 f/ y, o( s) m5 V% Y1: u( n% W6 j; \" n
0 1- e- K; a3 B3 l0 C
1 '8 g, h8 g( S: ?8 s! G" Q
2 .
: a, c! y1 w. o2 I9 z3 y) Q; H- g- c; G! q; V
dtype: string6 ~& V! C ~" y' w7 }" P$ I
1; y8 F0 q/ o j; z2 t5 r% Q& f
29 @- }/ P( n0 Q. @4 q
3
% q( V2 r2 a8 B* a4
/ C% K1 c* ~# w5$ S+ u. J: i" i
除了最后一个字符串元素,前三个元素返回的值都不同,其原因在于:
0 ~% Q) @5 p* `# N8 X5 ]/ H8 R* h5 P, `
当序列类型为 object 时,是对于每一个元素进行 [] 索引,因此对于字典而言,返回temp_1字符串,对于列表则返回第二个值,而第三个为不可迭代对象,返回缺失值,第四个是对字符串进行 [] 索引。# X) m0 Q8 G4 b! W6 }3 k: t
string 类型的 str 对象先把整个元素转为字面意义的字符串,例如对于列表而言,第一个元素即 “{”,而对于最后一个字符串元素而言,恰好转化前后的表示方法一致,因此结果和 object 类型一致。0 [* J% w2 q( ^' V
string 类型是 Nullable 类型,但 object 不是* i1 Q9 H7 M+ {/ o7 e1 L
这意味着 string 类型的序列,如果调用的 str 方法返回值为整数 Series 和布尔 Series 时,其分别对应的 dtype 是 Int 和 boolean 的 Nullable 类型,而 object 类型则会分别返回 int/float 和 bool/object ,不过这取决于缺失值的存在与否。# x$ } u* v, v
同时,字符串的比较操作,也具有相似的特性, string 返回 Nullable 类型,但 object 不会。7 q4 q% i' g3 z/ E, G l$ a
s = pd.Series(['a'])
* o, B$ Z7 Z3 f7 I
; K5 J5 ?* ?8 i: r$ E% J3 ]s.str.len()
7 C: l$ j3 t9 x4 hOut[17]: ; n% T5 ~. P& T. `4 B
0 1
, Z6 Z q: F, R9 o* gdtype: int64
. h! u8 J9 v! Z2 @/ x; c) H) v( s; b- |& S3 v) {
s.astype('string').str.len()
+ } k! U9 M/ J/ z" bOut[18]: " g3 ?2 N/ c3 P$ P6 U3 J* k- ^
0 1
# J- s) J( j7 p- `* G, Kdtype: Int64
0 W L1 _" X; i9 I' ` B) D8 N9 x; m! n) a
s == 'a'
; K5 y# `/ D! `: j* uOut[19]:
% Q1 g* W" l, K+ |. L$ E0 _- b0 True$ P4 r/ n B, [1 O5 ?
dtype: bool
& |6 R: Q$ ?7 `0 x5 E
3 a5 T: g! B4 [ G8 i1 l s% v0 Ls.astype('string') == 'a'9 H: T6 f5 O% ^5 Z0 E; k8 S
Out[20]:
. w7 ` T9 g+ d5 e* S0 True
, q5 \+ f6 B! h6 Jdtype: boolean
, w, K/ g. B& m0 Z$ s
4 V( b% _4 o" ^9 ds = pd.Series(['a', np.nan]) # 带有缺失值5 H/ h6 X8 N5 E) ?
8 F5 M/ ?3 L' @& O1 u( B
s.str.len()1 ~7 S; S7 \$ H$ a4 L
Out[22]:
1 K, d" w4 q* y# a0 1.0, a- J3 `" k+ M1 [ V" ]$ D9 D m: Y0 o5 l
1 NaN
- Z; @$ W7 n; Jdtype: float648 J ` `8 c! b( C6 S
$ o4 f5 J4 R* W* x( H2 b0 n
s.astype('string').str.len()
0 W; v, O1 k5 ]$ wOut[23]: / Q [5 D, W5 q2 F. D
0 1% |' |! c9 o! @
1 <NA>
5 o6 x8 R8 K A9 X& B4 e( Ndtype: Int642 c0 G) D% N' x5 Y6 [5 }
0 h! i% n4 T4 G' q& Z D! {. n- ls == 'a'
5 J _& G3 t3 z: l ^* NOut[24]: / `$ n6 j( ?5 q8 E( R. ?
0 True4 F3 \+ {6 D, h* P0 B
1 False
( [$ _0 \% o: S. t, f* zdtype: bool, C& \7 B9 t3 ]5 r
5 c. G. y# F, X5 U# d N+ ~s.astype('string') == 'a'/ t; g: k" {) f- h, R8 W/ [. {- j
Out[25]:
) i8 H& X# m# f- Z8 n) m5 {$ ^0 True
/ F$ b1 G; N$ i4 v1 <NA>6 t- _5 w/ a% J
dtype: boolean* @/ q% p- k4 N* X# W
9 B* ^: g' X- ?+ B1 T' ~ H
1
6 j+ E' m9 b; J: D4 L/ Z2
' _$ M- {! w3 Y: t. Q3
& a" m$ ?8 ?; e6 l3 L6 |4
: _* O7 @1 Q9 L, K$ a8 T# J5
9 N3 l8 Y4 {3 Z6) G" U& R' |5 B R
7
: h& y9 `7 E0 |/ _3 c' v$ x. k' }! z8
: g# D l7 {$ W; {9 l9
: R5 u, {5 L% t) @8 `, H4 w10
" q4 R6 e* y M; P2 P6 ?11
: I+ a5 e2 ^2 N5 X# U$ k2 k12- s6 [/ A6 `' [- z8 O4 H1 c
13" e" p' C+ w r& g6 y
14$ ~; b* }! c3 M3 i& q* l H& u4 s E, Z
15
( }3 c/ I4 L& \4 d16$ C4 @# ~$ ~- a9 ]; a B4 q
17
' k- s+ ^( e$ x/ `186 Y8 N9 K2 {0 \4 Z
19
7 i. J% e' K6 ]' R$ E) A1 l20+ y/ ]& M" L5 U2 ?
21
& `/ h4 L- v- F8 X1 o, f+ Z- u22
, ^! \+ P+ m" [( b' i/ C4 D233 t3 Z- O. t( B3 L' u
24
* u5 N# `- ?0 ?' M+ ^9 l# M) d! R257 L3 W1 h" k+ a7 ?
268 u7 ?/ [5 X# z/ x& y; I, C4 s
27
3 d0 e9 V3 |2 T9 G, `2 o+ V/ H4 g$ w28/ @0 M, C: ^! c; {
29 K' u; T7 \3 |. i9 O
30
0 }0 v# C* k6 V* w. t. Y31
0 D7 n$ K* t$ E0 [5 c7 T7 |+ ?" q32
/ O8 J. }* e* h; C* H33
. W. h+ | t2 s# r/ H- |34: b: p) ?: W2 W- ~6 P d0 ~; M
352 Q6 v6 A7 d& M V; S
36
/ x/ g+ b; O5 J/ v% q$ ]/ |37
; ^8 `/ L3 J5 c, Q; n) c9 M38
0 J# y6 W: {- c) C- S! B {; h398 Q5 @0 P9 _3 M; ]
40
- X; b+ m7 z. u/ I! M41
' S2 P2 y. t# b0 e. p( x3 ^2 ]42
$ r* a5 R6 t) {436 f& `& n! p2 A3 U+ m7 y" x
44( K) O3 Z& a5 I
45( X9 \& q# J, Z0 d; N1 H
46
) L% K$ X) x) b! N* l; n4 W; x47
# T: o2 B/ B% o0 y4 K 对于全体元素为数值类型的序列,即使其类型为 object 或者 category 也不允许直接使用 str 属性。如果需要把数字当成 string 类型处理,可以使用 astype 强制转换为 string 类型的 Series : w- `! A* z2 Z" M" I; N
M5 a! e2 R! s0 r& {& {% s; ?
s = pd.Series([12, 345, 6789]) v1 P% r# r5 i \
b. S3 B% {- ]/ |
s.astype('string').str[1] @' k% q0 W) M* `% f, m
Out[27]: $ W2 R4 I; ]" }1 \: f0 g
0 2! r" t, D6 o- F' y& a, r. D" I
1 4/ i$ s1 N% Z* X4 i/ }
2 7" j% d u d7 J0 |7 E9 u: N
dtype: string
/ ]3 k5 H& v& v! }9 ~1
4 m" W" p$ S7 o8 w$ W23 G: f5 R, s6 b* l
31 I/ ~6 h8 ^- B6 i& d( x
4
) B+ P4 ~7 x/ P# \: L) B1 K3 E" [5
0 H' |- C4 [; o2 _% g" X0 v4 |; x6, c; K& W! m0 O& \0 c
7! O0 ?! r9 T( [) q) q
8: D. k7 K9 x5 z6 T; V0 Z) |/ f" R9 C
8.2 正则表达式基础8 U$ [1 ?* o( O8 W* `9 h
这一节的两个表格来自于 learn-regex-zh 这个关于正则表达式项目,其使用 MIT 开源许可协议。这里只是介绍正则表达式的基本用法,需要系统学习的读者可参考《Python3 正则表达式》,或者《 正则表达式必知必会 》这本书$ V4 a( a: u/ q R1 I* X" g2 o
" g4 J: b' u- |8.2.1 . 一般字符的匹配3 S+ E9 b7 o- y( q
正则表达式是一种按照某种正则模式,从左到右匹配字符串中内容的一种工具。对于一般的字符而言,它可以找到其所在的位置,这里为了演示便利,使用了 python 中 re 模块的 findall 函数来匹配所有出现过但不重叠的模式,第一个参数是正则表达式,第二个参数是待匹配的字符串。例如,在下面的字符串中找出 apple :/ o A# t+ c- R7 h
l3 i6 h) [: T( K
import re
' R+ U- T% n0 g/ {
& _$ t1 x, M. H* |1 are.findall(r'Apple', 'Apple! This Is an Apple!') # 字符串从左到右依次匹配" K; ]5 M4 _2 l$ C; t i# i7 w2 Z) z
Out[29]: ['Apple', 'Apple']
3 |* O' n! {7 V+ _9 j3 I1
# z B. t9 t8 w2
: S3 K" S4 X( I9 {, Q E. U33 ~1 S U+ Q2 z% z
4* a; [# `9 T9 m. e: ^
8.2.2 元字符基础% ^% R8 p7 E- n4 k. _+ t5 _) H( M$ q' P
元字符 描述
) H4 e, S& H+ y1 g. {. 匹配除换行符以外的任意字符
$ Q H0 k. |4 h; ~; u* {, k3 v- X[ ] 字符类,匹配方括号中包含的任意字符
* k1 j8 M7 I# \ g# N+ L; k/ ?[^ ] 否定字符类,匹配方括号中不包含的任意字符
* v, q) [# g2 `! {: }: e5 T( l/ ~2 R* 匹配前面的子表达式零次或多次- M7 ~# M, k" A. J. C
+ 匹配前面的子表达式一次或多次。比如r’d+'就是匹配数字串,r’d’就是匹配单个数字
7 ~& M9 A: c8 {4 D& Q0 {? 匹配前面的子表达式零次或一次,非贪婪方式
( ~/ E$ G2 h8 h5 O, q" a6 r9 s{n,m} 花括号,匹配 n 到 m 次由前面的正则表达式定义的片段,贪婪方式
6 A4 p9 e( X) f# k- n' @: C$ `(xyz) 字符组,按照确切的顺序匹配字符xyz+ |( I. A% K3 P2 m
| 分支结构,匹配符号之前的字符或后面的字符
. w; e: Y! n6 I: R5 B- @3 C- ?\ 转义符,它可以还原元字符原来的含义5 o' M) M7 E6 L* \* P! C
^ 匹配行的开始' X p$ T- {1 C9 E4 q
$ 匹配行的结束
- b8 R; n2 C8 aimport re p, N; i- J) [; N! i
re.findall(r'.', 'abc')
% r( w: I# _/ A9 c: u+ }+ [Out[30]: ['a', 'b', 'c']9 T, I! T ]3 i9 H3 d& n
! n( ~ c" z' W; x% j A, Dre.findall(r'[ac]', 'abc') # []中有的子串都匹配, S6 w: U+ x+ p
Out[31]: ['a', 'c']
& _0 u+ n0 j$ E) t
$ b1 l7 ?, A" `6 fre.findall(r'[^ac]', 'abc')
% o! d! w" h7 Z2 {$ v' _/ KOut[32]: ['b']
4 `' l" P2 C" }4 A% m/ U+ g
) _' T, L4 Y t9 d0 s& L* zre.findall(r'[ab]{2}', 'aaaabbbb') # {n}指匹配n次
, _5 Q! }* x) x+ J6 u6 COut[33]: ['aa', 'aa', 'bb', 'bb']3 ]9 {. N3 B- \' z
# I9 L( ^/ s0 n4 g" @8 W
re.findall(r'aaa|bbc|ca', 'aacabbcbbc') # 匹配前面的或者后面的字符串
6 z) W' j/ c4 Z$ ROut[34]: ['ca', 'bbc', 'bbc']3 ?+ h" H8 ~4 g' n
5 X0 L' R7 ~$ k, J, p# x6 ~
# 上面的元字符都有特殊含义,要匹配其本来的意思就得用\进行转义。
4 A8 _% o; |# C" I"""1 X# m' Z5 E& f2 G3 w
1. ?匹配的是前一个字符,即被转义的\,所以|前面的内容就是匹配a\或者a,但是结果里面没有a\,相当于只能匹配a。% T; g) s- j' K$ l. y
2. |右边是a\*,转义之后匹配a*,对于竖线而言左边优先级高于右边
( l+ [3 ~( V: W g: `; l+ m3. 然后看目标字符串aa?a*a,第一个a匹配左边,第二个a匹配左边,第三个a虽然后面有*,3 j' _1 o: B* Y! A) ?% w
但是左边优先级高, 还是匹配左边,剩下一个a还是左边,所以结果是四个a6 Q! u; \3 ~2 J
"""
/ h' C' N8 h4 [7 g, ]: o3 R. |. f$ M! [3 t+ Y9 Z1 \
re.findall(r'a\\?|a\*', 'aa?a*a') # 第二次先匹配到a,就不会匹配a?。a*同理。
9 u" |' W S2 T* n4 r6 R: Z4 EOut[35]: ['a', 'a', 'a', 'a']
) w: [2 f; s# y: v# j1 Y, L3 \" o6 z3 N x- t( O) @/ Y t4 d2 q
# 这里匹配不到是因为目标串'aa\a*a'中,\a是python的转义字符(\a\b\t\n等),所以匹配不到。: `; K1 t: r' @
# 如果是'aa\s*a'之内非python的转义字符,或者'aa\\s*a',或者r'aa\\s*a'就可以匹配到\字符。
; T2 Z8 t: @" m7 b1 c/ Y- pre.findall(r'\\', 'aa\a*a')
" m* t. O+ D1 a[]
. W/ k: P/ ?1 \$ G
, Z6 S, H7 n9 E: w- W! u! |! V) lre.findall(r'a?.', 'abaacadaae')/ q% h" F1 a: A5 ^) K2 A2 Z* l) p
Out[36]: ['ab', 'aa', 'c', 'ad', 'aa', 'e']6 D3 O6 m( L+ ?0 ^, y& o- y
8 c: S! d, {6 ]) N9 _- c7 z3 I! @3 rre.findall(r'(\w+)=(\d+)', 'set width=20 and height=10') # 多个匹配模式,返回元组列表
& Y1 z; X$ W6 a9 o0 C[('width', '20'), ('height', '10')]$ C9 N' a2 I! i. ^! [& G# B# _1 q
8 u; S. Q: t5 I$ F. g1$ U$ B" N( L" v! J6 j
2
% l: F0 c* E* k3
2 V8 L0 \) M/ _" T% b5 y7 ~45 k8 X$ k: ]) a9 X$ ]8 I, p
5
+ c* I5 r( d$ Y2 w- N6
$ P5 r$ N8 V* |' ?- n, m% ^7
5 U9 D8 @& K3 X$ x) ^5 ]% J! u+ }8
# d a) G- K% e! l9
* f9 C' i7 k2 D10
) q+ q# _/ m e11
4 q3 }6 l& a1 \% C3 m12
# {$ U: C, l7 U8 ?$ J# w. S13; Y% `/ L* W5 ]8 t! r& P( ]/ O
14) G* Q! x2 R, {1 P$ E0 [
15# b4 g) C% B0 I7 O8 P6 J
16
7 Y' x/ u: r q; {17
! e8 A; w+ a: _' z18, s A. {* S" R- @- n$ ^# O* D
19
- @+ e) q7 q: h4 `- J" I20' J; [ |) B" ^# E/ E
21
* ]$ J( a0 @+ H# l22
& ]' \9 V8 o4 {& H6 D) z23
2 m i$ _. B( W* n/ l" _8 _24
( Z( p- g# y8 D25
: l8 G' T- r6 M4 F. W26+ Y: L. q; V h) e" L
27
0 ^; A. G- f0 X7 T: ]4 H28
/ x6 W$ Y3 w; f. |& H& ~8 O; f29
2 c! `' F: ]( ^: C: l, }# ~30
0 n, p2 x- Y$ |3 l f: a31
" f4 I ?" K. M8 H: m5 g* j32
# g: E- h2 b( `! z" h& |33
6 j* K3 @- |/ E' k5 v) N8 o1 G348 e+ w* b% z# K3 D1 Z+ L
35
% |6 g Q6 P! p; o6 Y36# Q, f" M, e5 l. j [9 j' ]
37
( z, t4 I K) }0 r4 y7 V& H8.2.3 简写字符集
! J# o0 ], i7 D; w则表达式中还有一类简写字符集,其等价于一组字符的集合:
! ~! U5 V% ^# [8 i% y
5 W7 x `, y* v; D& p+ h! f/ V简写 描述# s6 H" Z4 g# X1 e* F0 P0 j5 e0 R2 {
\w 匹配所有字母、数字、下划线: [a-zA-Z0-9_]
8 M& Q Z; f% p1 S2 x\W 匹配非字母和数字的字符: [^\w]' k2 b" B( J% z+ E) y; o* i
\d 匹配数字: [0-9]
3 m, q: r! R2 k. D) k, z$ I4 T9 ?\D 匹配非数字: [^\d]" m0 d$ h* ]& J0 J9 k! t
\s 匹配空格符: [\t\n\f\r\p{Z}]! ]) F; |" ~" ?7 w! r
\S 匹配非空格符: [^\s]
2 ?6 m& K% K$ r9 Q8 J/ X. x. ]\B 匹配非单词边界。‘er\B’ 能匹配 “verb” 中的 ‘er’,但不能匹配 “never” 中的 ‘er’。6 i. Q) P) ^! a
re.findall(r'.s', 'Apple! This Is an Apple!')
M1 }9 h: `4 u& i& j( A2 cOut[37]: ['is', 'Is'], d8 X, \4 I8 M: e8 r" I3 e
" w/ I( a/ o+ |9 W% y" I
re.findall(r'\w{2}', '09 8? 7w c_ 9q p@') # 匹配任意数字字母下划线的组合,但必须是两次
$ V6 ~3 _( x) mOut[38]: ['09', '7w', 'c_', '9q']
1 v0 Q# x% w0 ~' [" b1 p5 {; J ~: B
8 ]# Y# T( ~$ g* x! k3 f' n' k) hre.findall(r'\w\W\B', '09 8? 7w c_ 9q p@') # 匹配的是两个字符串,前一个是任意数字字母下划线(\W),后一个不是(\W)6 y6 d z+ Y6 x( j
Out[39]: ['8?', 'p@']. ]1 t8 X! F6 F% ^* G
0 i( C0 l( p0 s
re.findall(r'.\s.', 'Constant dropping wears the stone.')
; a0 Q5 s( ]# ]% k/ E9 SOut[40]: ['t d', 'g w', 's t', 'e s']
3 }( t' L0 z4 f1 y6 `' ]; I
- \+ B9 Q( U2 m- n- Bre.findall(r'上海市(.{2,3}区)(.{2,3}路)(\d+号)',- H0 A& L( F. r+ T8 h
'上海市黄浦区方浜中路249号 上海市宝山区密山路5号')
- i8 `3 j" \" T. A! N% `- q, ?; j8 P- v2 `- T; K. c
Out[41]: [('黄浦区', '方浜中路', '249号'), ('宝山区', '密山路', '5号')]' M8 c5 W2 i$ y% Z1 j" K
% X& o: ~+ c; j6 x
1' f$ h" T$ q- a3 I
2
7 v4 A9 J. w4 N; C6 m6 Y$ W4 x35 d' |. e# \- K" ]9 ]/ Z8 C/ |; a
4
) R, E; [5 v _58 [/ N6 j3 z1 @$ \- T
61 w) n" V$ \/ G2 R; B
7
* t) |( q- \& j) q83 q5 B. w" x( g
93 y) E, O4 ~1 n: u
102 D: C" P1 U! Y# c/ h3 r2 ^) \
11 A7 w% j$ Q/ B9 ]
12" D( C! Z F% [! i; n6 N% b* |
13# o& k, D, y" t
14
9 ]& R. l' d$ j; s15% E2 e4 e$ o4 [, f: }
167 o r5 _9 z3 l
8.3 文本处理的五类操作
( K6 L6 L" K( E( a6 ]3 o# l8.3.1 str.split 拆分- I' `/ [# V' }3 J! K2 q
str.split 能够把字符串的列进行拆分,其中第一个参数为正则表达式,可选参数包括从左到右的最大拆分次数 n ,是否展开为多个列 expand 。
! `9 Y; u% f3 [; O0 b( M+ x# u5 A9 r/ C! y2 l8 v6 m% a
s = pd.Series(['上海市黄浦区方浜中路249号',8 f/ u" B8 Z+ ?9 E0 {( \8 a+ R
'上海市宝山区密山路5号'])9 h, ?) p T6 R/ |
. w7 E; Z$ q2 j/ p+ G2 o" j# X3 l$ k. O5 s3 b/ N
s.str.split('[市区路]') # 每条结果为一行,相当于Series
3 N D } O6 ]: C4 _( ]& O% L3 EOut[43]:
3 M% g0 W. t# M: W. v$ \0 [上海, 黄浦, 方浜中, 249号]
$ Y9 `% `4 ~3 n1 [上海, 宝山, 密山, 5号]
( o2 n, w5 ?: a2 {: mdtype: object
6 E% K. u; p' T% T ?& ?7 k# M9 b& m f3 |' z6 K
s.str.split('[市区路]', n=2, expand=True) # 结果分成多个列展示,结果相当于DataFrame5 N$ E9 j% G1 W6 w! w# r$ I5 s
Out[44]: ( Z& K! F( ^9 h. a& t- J8 Y% R
0 1 2
8 T& F9 {8 i* r) N0 上海 黄浦 方浜中路249号2 X) {# j3 P1 Z! j3 _
1 上海 宝山 密山路5号+ k6 B( Y! X H# N
1
; D ^; }' ?- G9 I. T4 Y23 r {2 X O1 y" P) M
3" E! _* K' M: H- d0 {! V8 [
4% y2 c4 Q6 y; i! e _1 j/ O
57 e |4 ?8 Y/ P4 E, I( u- Q% {
6
2 W; [$ t' }, e4 O5 f7* U3 ?4 e5 S4 k6 l# _! h x
8
# Z6 ~( x& e( B3 p. P9
; a" j2 m# P b, }* A/ f9 n101 d( k8 `2 v. P- x& `4 V1 H: K
11
/ Z! Z9 V( L+ t/ H' c8 v12, z2 a+ G0 k- h# ~
13
* k' l) U* o& T ^7 u14" K( W( ?3 `7 R1 d
15
5 F* E }* c# Q. I6 O6 |4 H8 V 类似的函数是 str.rsplit ,其区别在于使用 n 参数的时候是从右到左限制最大拆分次数。但是当前版本下 rsplit 因为 bug 而无法使用正则表达式进行分割:4 A2 f2 V6 W* i, l6 s3 U
9 L& I3 }( L7 [3 `, d' W
s.str.rsplit('[市区路]', n=2, expand=True)
0 f) `" @! w3 u/ m W2 \Out[45]:
2 j& Z+ B, f7 e p# K% s3 K 0
% c, _0 ~( ? {: H& f5 l- U( X, D, _0 上海市黄浦区方浜中路249号
& C C9 S3 ~6 F6 g1 上海市宝山区密山路5号
8 a6 W" ]* V' Z1 P1, Y: \' s# W; D% J% X
2* U& X7 P3 E+ Q3 G, ?
3
" R1 t$ G9 W2 f6 P4
. G, M& ^4 w. A; `5
# t' Q* w, d3 J8.3.2 str.join 或 str.cat 合并
4 i# O- B) T C/ C+ M4 V+ `str.join 表示用某个连接符把 Series 中的字符串列表连接起来,如果列表中出现了非字符串元素则返回缺失值。
6 |/ b# m1 m- c, F( P5 ^/ Jstr.cat 用于合并两个序列,主要参数为:
1 N4 q* Q+ @: r- asep:连接符、& A' u0 W+ T0 b1 S+ ]8 M2 D$ o
join:连接形式默认为以索引为键的左连接
& T/ @ m* [) h& Z- _7 F0 Lna_rep:缺失值替代符号
. \" H/ J, ]5 g8 R' T1 b$ `s = pd.Series([['a','b'], [1, 'a'], [['a', 'b'], 'c']])
9 \+ T) K% l! r5 f- x, Vs.str.join('-')3 s# Q) P( o" ?- S; G
Out[47]:
; y- |; ~$ L% m9 ~0 a-b
# n# q* d7 T- V# g, y1 NaN6 A* |3 Q6 T3 h, T: P- |; a
2 NaN, P+ z$ v7 T% e
dtype: object
- W0 ?. P6 c& [- F( Z. K0 Y+ L% x: ~1
7 J# Q* g- F$ I- I2! {* U3 R$ D- }% {, d) e) J. ~
3
' u5 D Q9 p0 |) _+ E9 E47 O* H) U& Y7 T- F6 n9 k: t
5
( i* J0 I! M0 y1 \' I1 O60 e/ _8 d0 b8 I/ Z9 k$ Q( _
7, O- l& D* N6 E3 R( n
s1 = pd.Series(['a','b'])) G$ u* d$ v+ p4 l
s2 = pd.Series(['cat','dog'])
* K0 Z- D5 H4 F9 k) gs1.str.cat(s2,sep='-')
# G/ S) X3 _7 t! {. mOut[50]:
! }2 a% v5 R# K+ S( E0 a-cat1 M I0 K3 x1 _( n* } d, w" h
1 b-dog* d6 l, F/ |1 o; C+ _
dtype: object; U7 S- {. @3 I
% k1 e8 `, r6 N0 D, i6 H
s2.index = [1, 2]
. G# N) Q% y, c/ ds1.str.cat(s2, sep='-', na_rep='?', join='outer')/ F- |2 H; ^6 i9 R2 L
Out[52]:
8 A; r1 C/ }8 O) D4 J7 F# F0 a-?
: B! _! z2 Y/ s+ v0 V" n0 V) i* P1 b-cat
; g' v) Z5 O$ K/ {2 ?-dog7 T: T5 \/ m* f6 a" e3 s
dtype: object
% {/ Y a+ I' S C1) l' A, i7 L4 x" J" s8 b6 d
2( q2 p! z8 z* V; X6 {4 t4 J p! h0 z
3
# s7 Q* A2 B% a6 s4
" Y$ _! H; `' i. r- _6 m55 _$ r* ~9 ^$ A( I& O; x* [- G
6
; W, m- |& B6 e+ e; m* m6 R7
Y4 T- _5 Q5 R0 v* I8
4 H2 M6 R% x" a2 F5 l, G9, m/ @4 y; G1 w8 f* L
106 s8 ?" H: A% ?# F. v( M% ~
11, k2 G( W# d! v4 Y6 Z
12
6 U4 O& \" X; ]4 [: K! x13
0 H7 O7 J4 @! A8 q+ S14& Y7 g0 j) L% S4 H, q7 v& N8 ~ K; F
15- d5 x+ R. n( W/ Q0 S
8.3.3 匹配9 j+ A v! R' \7 j I7 V6 c
str.contains返回了每个字符串是否包含正则模式的布尔序列:
, r2 H( s) t1 Rs = pd.Series(['my cat', 'he is fat', 'railway station'])
0 |- F3 C# c* q$ ys.str.contains('\s\wat')
2 X7 ^% g$ J1 r: a: Z. Q* u1 R3 g1 T0 K, f) c9 _% f# w( B; e
0 True
& C( W9 v' p; R' [" J1 L1 True% H& O$ p7 V& ^& j) ]9 O9 R( E9 I. [
2 False
1 u3 Z1 [5 u6 ~, |, J) udtype: bool" v/ y! E. Q/ C, ~/ s* V
10 L2 U* ^& l; P8 d
2
- q( B1 h' t/ f. N) s- ]8 P4 y6 x# S3
8 L4 f. p4 R7 A+ ^' L4; W; X1 D* X$ s0 w+ |: j
5
* Q; Z- ?* O# S3 E. |63 P; B0 v% e6 o9 A) ?
7
4 [1 ~; z X. Y) l4 Z+ \7 G3 Bstr.startswith和str.endswith返回了每个字符串以给定模式为开始和结束的布尔序列,它们都不支持正则表达式:- s& y4 ?- m& ?0 O# u; @
s.str.startswith('my'). R5 Y; O# [, e4 y+ w
$ S6 v, P/ X: q0 True" N+ `6 |& D% H) ^7 A
1 False
/ R3 F! O% I. l2 False
) i6 [# J% g$ P6 h. ldtype: bool
/ G7 g% [# t$ n t1
+ d$ P. X7 F+ Y. D. s2 {# F' {2
; N! U4 {3 {0 M% ]+ g) a! l4 |4 B3
. H. I C( x& @9 {44 [" r- {" ^7 p+ o
5
4 E8 I4 |1 m5 |& M# m( R6
" t( G5 {" m3 O. E0 i L1 Ls.str.endswith('t')
: h! [& z! o6 X, W w. @% M
1 v- s( E* c$ ^2 y! [0 `4 M6 D& C8 x( Q0 True3 }. X a. G4 ?& w# {$ w
1 True9 z, A F' D' Y4 z
2 False: w- Y$ U& }6 j% y% e8 m
dtype: bool
3 _; J. U0 h6 ~- x1
0 E2 S; \; m Q# k E2
" y# j% k: D- @2 P3
+ q% d0 q: O! C1 P2 Y4
5 k/ W5 u1 N+ L+ w" ]! t& A5
0 V* A) [* @% g% u5 [6, T) Y0 }9 a7 d; a
str.match可以用正则表达式来检测开始或结束字符串的模式,其返回了每个字符串起始处是否符合给定正则模式的布尔序列。当然,这些也能通过在str.contains的正则中使用^和$来实现。(貌似没有python里的search方法)" ]5 n7 y1 e P+ h* X1 }" H/ M* M
s.str.match('m|h')
+ k+ o2 s3 M. c0 E+ D0 Ts.str.contains('^[m|h]') # 二者等价& W5 _( I' m4 _& G
6 |0 F7 D: }4 f! s0 True
: X2 w5 S% M% B5 g) ]/ J1 A1 True
" a* G4 t. q7 {4 b1 X1 V2 False
/ E7 `, y9 {, }: x5 g: gdtype: bool
9 l3 N2 D1 ? b9 A7 R8 J1 e! n$ `7 h$ M
2$ p' N* X2 ~' ]. v7 P9 \ S9 [
31 U& p+ ?3 ]% t+ {
4
. e- O1 {& N6 s0 u+ t, [" ^# Y; g5
& @( @& j& e( L% O64 z" k, n: {; i, N1 W* E; T# o
7
: Q: F$ C4 K1 A: [2 Ps.str[::-1].str.match('ta[f|g]|n') # 反转后匹配- k" Z, {0 C- L. w/ ~
s.str.contains('[f|g]at|n$') # 二者等价! l4 r8 [2 V* N2 V( U! M; H4 J
" |" f! M0 u* C& ?) m W U0 False
/ I4 ^4 l$ Q$ u/ r- @" R1 True
p- G( h3 g" z- A$ c1 a2 True
" z: J: y7 N7 ^+ L; B; c. Sdtype: bool' h! q6 a! W. c8 Z/ {1 _
1
( c+ ?1 n& q) U, `22 ^4 }/ R# V- n
3+ {) r8 c, V6 M" x- R
40 o( F& u2 }9 x
5$ s% n$ h" o- ?1 P7 m5 a# M
67 O `: j6 A7 Q {8 W
7
9 F! Z" ]4 T/ V* }) v, \& pstr.find与str.rfind返回索引的匹配函数,其分别返回从左到右和从右到左第一次匹配的位置的索引,未找到则返回-1。需要注意的是这两个函数不支持正则匹配,只能用于字符子串的匹配:
& D4 k7 }& P- T4 @s = pd.Series(['This is an apple. That is not an apple.'])
# c+ L8 J+ O6 G3 h" ~! q7 v8 V3 h ?% a
s.str.find('apple')# Q5 I1 |1 a# s8 ?4 P
Out[62]:
n% q* n" _5 {; y0 11
3 [$ f8 W! g6 Fdtype: int64
0 T! Y) q2 N9 Y" x
- E, f8 [, i+ bs.str.rfind('apple')
. y8 q$ a2 e) F' H% a8 }( JOut[63]: 6 F! n' x) |8 D |& u. V
0 33
$ i& Q! _* U- F; Ddtype: int64' V2 ?2 Y# V; v( z7 w0 c
1
; f2 |$ c [3 i f2 _& y1 P2
) H' _: N% r1 `- q0 g' Z: W1 {( O3
. R& q1 a" a; P8 O+ z: r) ]4# @" d5 a" R* [9 T+ J. z
5
. S' e/ Z4 T" X/ r( m# b+ |6; y) A) _5 c! P1 K. w' ^ b0 i, `" z
7! `7 z h2 E$ f
8
& d2 K- x/ X3 g0 J& l- N" \ L9
, J5 k: j# z6 L10
- J) V. X$ |9 ~11/ e* @/ a( w1 y; W
替换" K2 Q/ L% `% j- V
str.replace和replace并不是一个函数,在使用字符串替换时应当使用前者。
: b$ U2 x. g+ p& o. V1 Ys = pd.Series(['a_1_b','c_?'])
) y" @1 u8 L- _# regex默认为True,表示是正则模式,否则第一个参数内容表示是单纯的字符串,也就是匹配字符串\d|\?& t' U5 C+ R- e8 A" K3 Z
s.str.replace('\d|\?', 'new', regex=True) % i1 y9 @) n9 }' Z6 Q2 v2 _
) H" J5 W0 ]$ ~& O9 J; J6 J
0 a_new_b
8 V/ A2 f% J" w+ Q1 c_new
3 |$ d) T' t/ _0 j& K3 W( Pdtype: object
; ^: q! D% E& n& l1 m/ P# ]1
7 r( }3 z1 I$ i/ K% u0 h( N, S24 E* Z) X0 Z3 Q$ q
3' e( `5 | P4 v( }1 ^, @' o
44 @' K0 E8 s/ N* d$ c
50 e" h+ Q8 }# s
6
/ e) v4 p: I+ x4 ^7
; e5 y5 } a4 D+ Y8 u, A# F 当需要对不同部分进行有差别的替换时,可以利用子组的方法,并且此时可以通过传入自定义的替换函数来分别进行处理,注意group(k)代表匹配到的第k个子组(圆括号之间的内容):
3 V9 M& o7 q# s: S i- e5 D# R/ u3 i1 m- g3 a/ a/ E0 L( N/ I; Y; Y& S9 p
s = pd.Series(['上海市黄浦区方浜中路249号',; U; i" d" x5 W+ e h
'上海市宝山区密山路5号',+ z- |& ^7 I( V9 Z9 ^
'北京市昌平区北农路2号'])
; \% s! r8 P6 epat = '(\w+市)(\w+区)(\w+路)(\d+号)'2 | ^2 G' b8 U- s0 s( \5 r& ^
city = {'上海市': 'Shanghai', '北京市': 'Beijing'}
7 p4 ~, W+ s1 o$ \7 _' T1 E6 @( ndistrict = {'昌平区': 'CP District',0 F- w* B E) k9 M S+ [2 T# I# f2 F
'黄浦区': 'HP District',! d0 H/ @& c( M. {# m/ I1 I! z n6 `
'宝山区': 'BS District'}
, R) k$ q6 L/ v1 R, q9 Qroad = {'方浜中路': 'Mid Fangbin Road',4 R6 h/ r5 o1 K, P# y( c
'密山路': 'Mishan Road',
# o. V- ~' ~. g) f5 @4 n '北农路': 'Beinong Road'}5 j9 H# i3 q0 e9 B/ @4 e
def my_func(m):! L. n: g! ^. I8 y3 K
str_city = city[m.group(1)]
: @9 w2 i% C( l6 V str_district = district[m.group(2)]$ `, f) j3 Y. W2 S, K/ r
str_road = road[m.group(3)] V% ^' I+ I8 p3 }9 m. t
str_no = 'No. ' + m.group(4)[:-1]! w" n8 w5 S& k' [- ?
return ' '.join([str_city,: ]3 l# e4 E9 b
str_district,1 V: q/ L+ x3 O( i4 b2 C6 ?
str_road,) ~" @ D3 k5 A* j( D
str_no])8 o5 x- |! D1 H$ E1 Z; H4 X) D5 g
s.str.replace(pat, my_func, regex=True)/ L" n2 _! o9 R1 u" z
/ d* x2 @1 B2 w* K6 Y5 y1/ R: s- f; o, Y8 |' Z* O* i! C
2
& V& x2 q" J- r$ }3
9 E/ V3 X) E$ O0 e# V0 o4
: P5 d0 w& [; B! f) E# \0 a/ N4 j5% @7 v' `: Y9 X
6
5 N+ x- P( h$ V+ t4 k78 U: u- Q! b2 `' h& U g& d
8
. i4 J E7 Z; R$ }- {1 O9
. A7 W9 z* l. @3 r10
: T* b' i/ j. A; m11
+ f, ]5 d" |1 R. L- u( B5 w- w/ S+ E12: P: V9 G) a" j, _" {3 y8 ^% N
134 A. U3 J' o! ^) z' g/ \
14
) B3 w% _0 D0 T8 f4 |15
5 C$ x( p0 c: d167 s/ D" R' D. I1 U, J- I# t
17
, t R) \- Z# a* N O18& H9 M; t! L6 D; V: N* E( S$ D8 u
19
! T9 [( z, J. e& Z- P3 b7 O20
' K) ^1 J8 A/ `( i21/ x3 K: X3 n, ?+ x+ \! G
0 Shanghai HP District Mid Fangbin Road No. 249
# n: Y& X- _) N1 o, [& C# f/ b4 L5 c1 Shanghai BS District Mishan Road No. 5
6 \0 Y9 U3 d. ?" b) @, v2 Beijing CP District Beinong Road No. 22 }1 v7 D3 K) N( t7 g' @! z9 p
dtype: object
1 r! P* e2 @* O. @* c# z$ p: ?1 k1
; h( _# X. }) \4 ]' p( H, i2" _: o4 O! I7 r, L9 N: e0 R
3
$ `; I+ h9 f1 g" R1 }! G: Y4
% r2 c; w; P1 O5 [) l6 q这里的数字标识并不直观,可以使用命名子组更加清晰地写出子组代表的含义:
6 N0 U: J1 P) A. N6 N/ ~7 ~5 V0 |% s7 X# Y
# 将各个子组进行命名1 a" e- U# `. d' r! } H
pat = '(?P<市名>\w+市)(?P<区名>\w+区)(?P<路名>\w+路)(?P<编号>\d+号)'
; p' s; Q$ R; x& P+ f( B, Ldef my_func(m):
+ l& X* J6 _$ b, i* Y str_city = city[m.group('市名')]
, ^9 x+ F! B, z4 ~! ?9 @, l str_district = district[m.group('区名')]
& D$ X& r( z; V str_road = road[m.group('路名')]
! V0 ]* }3 D. ]) b( I str_no = 'No. ' + m.group('编号')[:-1]/ ~6 { o, }+ _! Z/ v) T4 e
return ' '.join([str_city,( j1 u& |0 U" Q$ L1 E9 t8 s
str_district,& p5 e0 f) a* A s# m' H# A
str_road,7 [* C8 N7 z* k$ ?; h' z
str_no])
* S5 D" ]: A# O5 w; V7 J, Rs.str.replace(pat, my_func, regex=True)" D# Y* U: f6 Q) V1 P" {
1# j7 n0 B1 p+ U/ k" {& M
2$ k( i6 v- z: I( l/ j
3
# T& u- g% F' _* e) s2 T. u/ R$ s" _4
7 ^$ t# w( r" J* |9 N6 ]3 k4 Y5
& c8 a% S- P4 s& L! e: s2 B6. n! h/ n) @5 w% _& k$ U. ~
72 c) H- Y8 W I
80 Q( J4 X! u. b( Y% E3 u* p1 ]
9
0 C! g2 K3 B# T2 u10
0 x8 q2 U' @, i* w4 e7 }9 L. `: a11
7 ]0 y7 `4 \6 J/ K) u9 o: V$ W12
" w' B I/ R3 \$ }0 Shanghai HP District Mid Fangbin Road No. 249- S: m% x2 f" i G( j' q8 [3 B: z* J
1 Shanghai BS District Mishan Road No. 5
+ J2 I l* A( S: z3 M2 Beijing CP District Beinong Road No. 2, H2 e' e j9 l# |
dtype: object( l+ h% x, g8 N+ e" R- W
1: o. ~& @2 |) X4 h
2. v; e1 ^' c6 ?8 ?# s9 `
3# N" m. U% k& w' q, b9 @- [
4, f0 ]7 N: d5 [4 j2 X
这里虽然看起来有些繁杂,但是实际数据处理中对应的替换,一般都会通过代码来获取数据从而构造字典映射,在具体写法上会简洁的多。
! o% N$ F+ V/ W( d* F4 `* c' d/ H3 B" j; x
8.3.5 提取
( s6 ^, A- c$ e8 `. j' V) zstr.extract进行提取:提取既可以认为是一种返回具体元素值(而不是布尔值或元素对应的索引位置)的匹配操作,也可以认为是一种特殊的拆分操作。前面提到的str.split例子中会把分隔符去除,这并不是用户想要的效果,这时候就可以用str.extract进行提取:
1 w% L' q" r! b! }) l9 ds.str.split('[市区路]')0 b$ V- t4 Y! Y/ z5 f. E
Out[43]:
4 |( ^. p& u) p- Z& D0 [上海, 黄浦, 方浜中, 249号]* m7 Y" n* n9 E' R, g
1 [上海, 宝山, 密山, 5号]/ ^# B" ?. s4 R1 ?, T% n# ~
dtype: object
6 R+ r* P* s i5 F; u% Z1 Q. x9 M! M# d/ |/ b2 \4 M
pat = '(\w+市)(\w+区)(\w+路)(\d+号)'1 I# E$ X j+ x" p
s.str.extract(pat)/ n! F' A; L$ X4 R
Out[78]:
' Z n& z5 D5 E; d! J8 N 0 1 2 3
- T* F/ I$ S9 f2 a8 J5 \0 上海市 黄浦区 方浜中路 249号0 T9 D1 t; `$ s' e7 O/ w _* c; s$ `
1 上海市 宝山区 密山路 5号
! s3 i7 g) m$ m6 \0 @2 北京市 昌平区 北农路 2号+ s# M6 O% q, k# a& Y& s' Z$ @
1
6 J. f3 b9 u( [/ s! X8 g p4 R2" W- z* }# O8 }! a% L5 e' H8 R+ C1 a l
3
1 v8 i3 o* G) s$ [% I4 C( y4
1 Z9 @: k; r5 M- G1 G# L5; P! t6 ]* H8 f
67 k/ d5 g, J& S! ?, f
7; y- c8 Z" [# S3 H F
8
9 T, i7 c& r$ t: [; P; K6 r96 E- ~+ U) l/ A4 C2 e6 x
100 G4 ^7 G! B% d" N
11
% S0 N0 k7 F# V7 M12
6 _* {/ [2 i/ Z+ |( @13. X1 f, T |# `5 C7 ]
通过子组的命名,可以直接对新生成DataFrame的列命名:
3 \) {6 {* U1 i. Y% V$ f8 U+ Z* f2 s7 i8 c
pat = '(?P<市名>\w+市)(?P<区名>\w+区)(?P<路名>\w+路)(?P<编号>\d+号)'( h- S' X7 q3 H" F
s.str.extract(pat)- f0 o5 s1 l8 a2 k) R, o2 K
Out[79]:
' }3 M" b) a, j+ b( t( H 市名 区名 路名 编号# R* t0 O, c+ }2 q3 L
0 上海市 黄浦区 方浜中路 249号
, d" h! A# H' }: M. ] Y8 P; @6 A1 上海市 宝山区 密山路 5号
6 q5 B0 J8 v6 x4 f5 {! T6 D2 北京市 昌平区 北农路 2号# B. p5 S0 U- h+ [' v; z* ` I. n) _
1
/ D: I' E% p( L. K A" ^1 Y- b. {24 y& A* J- r9 p) a9 T( t) {
3; w" M b- `) N; x/ Z$ N
49 }4 B; T+ I% p
5. P- ?5 L# \3 u! Z9 s
6
" Z/ K. P. Y* Y6 q78 k& N4 ?! s, d% j
str.extractall:不同于str.extract只匹配一次,它会把所有符合条件的模式全部匹配出来,如果存在多个结果,则以多级索引的方式存储:
) \! r" i6 j1 W, A& Ms = pd.Series(['A135T15,A26S5','B674S2,B25T6'], index = ['my_A','my_B'])/ ~# X. f7 |1 }9 T) `3 R
pat = '[A|B](\d+)[T|S](\d+)'
( t( ]' [) K1 t) us.str.extractall(pat)4 E m, K, c# X; X
Out[83]:3 w/ E4 B0 y8 n& l$ M
0 18 M' R( ]+ R/ p' m
match
8 W! V+ y* \/ F$ l6 d2 Nmy_A 0 135 15
! d; A6 l/ K7 W2 h, t0 ?3 U 1 26 57 D- L$ R& i/ I! B
my_B 0 674 2: I: N D" o/ E \& W
1 25 6
' m7 s. B( r4 ]. g- u1+ k. ]" O$ Y* ~9 K+ H
24 x+ h( i9 U3 K3 M( {
3
6 z3 a& H5 n8 m9 _4( m" j: p" C( X
5
. Y; X, |+ Q% u: v6 O67 u/ ~0 [) r/ C! r7 w: e- U9 Z
7% ?- ?! }$ l1 D' ~* K
8
. D+ F& s$ n& ]9
8 f( [& c3 d' A4 j. N/ {6 a10
/ e( [3 @* `) a5 D" w# \3 \pat_with_name = '[A|B](?P<name1>\d+)[T|S](?P<name2>\d+)'& ^/ s8 _. o# g, k
s.str.extractall(pat_with_name)0 T# d, T+ O* y/ c
Out[84]: - N( Q( p8 a! w$ O, t
name1 name2% @4 m( K9 y8 d
match
1 [- Z7 ^. k5 _' B1 S! G F( ]: r" imy_A 0 135 15
0 M: Y. E0 m& l4 z 1 26 53 [4 \. G) P$ ]0 E; c6 z! H
my_B 0 674 2; @( f" K) O `: C/ S. H
1 25 6
) x5 `" L5 t0 ~/ ~! l' w1+ ]6 x! u' \% z8 x$ [
2
& [" F* |- x$ s6 P. `( ?0 Z3
4 D. g4 w% U: p# T4
; b: S6 v! p- D58 x. n0 y& v+ O3 ~; Q* Y
6& S4 X. y0 p& A0 @0 p
7
1 @, _5 w6 O7 O7 r8
$ U" M& Q( h$ j+ T9
( Y8 T& q$ E; P$ g) ostr.findall:功能类似于str.extractall,区别在于前者把结果存入列表中,而后者处理为多级索引,每个行只对应一组匹配,而不是把所有匹配组合构成列表。4 i' {/ o; W' L$ U- t/ S8 G6 W/ G
s.str.findall(pat)
. i( U5 J$ m2 W; E/ p1* b# `; P& r3 @4 e2 H
my_A [(135, 15), (26, 5)]' B3 \8 _) Z6 l
my_B [(674, 2), (25, 6)]
' k9 s# C2 ]+ `8 |- wdtype: object- \ o1 @9 _2 v* n$ z
1) s7 w. `( `6 E' x5 P9 @
24 T/ L. D( c$ q, X w( g, w/ q
3: w7 X; r# m6 g
8.4、常用字符串函数
# n A$ i2 y2 c: o1 L& l" S 除了上述介绍的五类字符串操作有关的函数之外,str对象上还定义了一些实用的其他方法,在此进行介绍。
/ ^7 P6 }5 y$ s2 T* Q4 U
/ X. R* C5 S" H( U3 m r1 R4 S8.4.1 字母型函数
: s" j# q; d, O0 q# \% D. R upper, lower, title, capitalize, swapcase这五个函数主要用于字母的大小写转化,从下面的例子中就容易领会其功能:: q! ~' B% A/ q' `* m4 l5 c
8 l7 X! k* `; j! N* ~
s = pd.Series(['lower', 'CAPITALS', 'this is a sentence', 'SwApCaSe'])
# V& Y0 {. G9 P+ ]
- U1 Q D( `6 js.str.upper()2 R# I7 r6 z6 t$ E
Out[87]: + q1 `1 L- H) N! T u2 F- _. K, Z
0 LOWER
6 e1 s$ h# T; @$ D6 `1 CAPITALS
; o+ C+ i) V4 P" t; R8 U: M; o2 THIS IS A SENTENCE
/ ?2 }$ I. U- i- @9 g1 @1 K- D3 SWAPCASE
7 }! E8 Q" X! mdtype: object
1 `% t8 t0 M; U( S+ Z1 ^+ Z- q9 D; @8 d# e% f$ b& N6 G6 H
s.str.lower()
2 H' M/ O0 L! R8 B" Q, dOut[88]:
M2 J/ ^( w! c0 G8 p+ r, T0 lower
3 T7 K$ G, @" t1 capitals% G. e4 {. ?" h/ J5 l7 t
2 this is a sentence
3 E( D5 o7 C5 Y6 o0 _3 swapcase
/ E& {& A. | m9 q; Fdtype: object
( }) J$ l7 R Z0 f, U3 y6 i2 M
% I) X$ q z% \* F8 u# ss.str.title() # 首字母大写
5 B3 ?$ z+ l2 UOut[89]:
" g% U5 S- [% m0 Lower
& G0 n0 C$ n! G( S& k o; B0 \: Y) k1 Capitals% P& A& G9 U: L3 \) t# @0 p3 O
2 This Is A Sentence
6 n% {& d& g: P3 Swapcase
7 K* _9 j4 [: w) B/ K% Edtype: object
, U# ]- T, S+ B( B
- j4 J8 S; A+ N0 ~0 D! b6 Bs.str.capitalize() # 句首大写; C7 @2 {2 U/ D& [' ^* t" v
Out[90]:
. J) j) V2 V! K" o n& ]* O5 Y0 Lower
3 A. b! B) d2 L) ]1 Capitals# d0 S5 |+ B- |1 U
2 This is a sentence: l; Q$ |* L0 z3 c* V, A& F. t- Q4 E
3 Swapcase
% O! c! ]2 O2 k8 adtype: object W5 C! z% \9 F/ n. U" l- ^3 m
: m8 [6 S' P% `4 f- v% Ms.str.swapcase() # 将大写转换为小写,将小写转换为大写。: W7 k3 z* S! C! L
Out[91]: % r9 a. h+ B! |* e
0 LOWER
+ E4 d0 B4 d- x' i) y4 Z, O: `- h1 capitals
% S( I0 d+ r5 q8 V& D1 [9 t7 r2 THIS IS A SENTENCE
# w( q* y. ? g0 t* h$ u% Q- {0 z3 sWaPcAsE
m$ O- I* Q4 _4 [* k9 @2 X0 |% Mdtype: object
: O. Z! s2 P& }( B* R( f! }+ x2 B' b& f) \
s.str.casefold() # 去除字符串中所有大小写区别: X2 s6 G6 A0 B, \) F( f2 ^! I
3 @* }2 d J: q. v" `/ \; q3 ]
0 lower
5 Q. {7 D u- z' {: t1 capitals
; n7 W0 A# `8 n$ a7 q" ?" B2 this is a sentence
1 x7 s1 M" q, y8 U k3 swapcase: s4 @+ m7 P8 l4 h4 Q5 v
J2 w, O& n Z# x* B: Z1
. d' a6 i3 a- S2+ {8 ~% y* S6 s, K0 [. o+ l& m, @
3
3 u! G9 w# t' g# d5 s& ^; [. p40 W; b& K |4 r/ j0 d
5
! z- `9 Q* u- O( f6 w4 Y. r3 d+ @9 ]6
8 S' O! G3 m4 f+ F7
. t) ~. z2 n$ n; ~. y3 [8
( a/ V2 f: K0 L9 Y9) V! ^5 [7 i) \3 ?, t
10; g Y5 t! K3 @. J4 L" d! f6 I
113 S0 E* _! j* ~& b7 T7 z' a
12
) U& _7 ^7 t' H% t5 y1 P; ^13
E- J0 ~& c$ E4 F! P* Y8 ~14
4 D0 R" m Q0 U$ |5 b {1 g15' C/ c: F2 L3 b9 t. g( j
16( ^, H, c. l- {" D- |* e# c
17
) R, _4 C2 e$ C8 {' W18/ f; @+ o# ?0 M* E$ S( G& I# N
19! R; Z! C( y2 {* ~) \ ]
20
- _. C P& z! B) E4 ~0 R/ j8 K- `21, l( g D7 Z% g# W" y+ Z
22
! w& o9 @) R+ b; k, U23
- O) j; S: n8 {! z24( a5 j% L& E3 \4 O" m; Y d
25
) s1 B6 d6 E/ e1 B264 C& M* H" k! A6 {
27
& S" z: _) y0 ]9 `# k28
! ?$ o! i3 w1 w29; Y0 `5 p9 H' p
304 f' S, f. A z! \' c5 o
312 [2 t" P7 c5 e( |( \' q6 o( b
32$ C2 q6 [. V& ]7 k3 i; \
33
/ O9 H, \0 N# f v- _- C34# B9 N& u! ^9 k! W4 ?
35
9 Q# O6 B, _3 @6 i% R) _+ w" c; d; m36
! d. \' |3 ]# {377 w& i7 d# p4 B7 m# ]! L
38' D: z; g+ r! w1 D" }
39
$ b) y$ h2 f0 M8 v3 o6 l w. Y40$ C5 P. V( d, T8 G2 `
41
& H) q, e) V2 ?# z! ~42
; _- ^9 f" B: Z! L; F" f2 `4 q* Z1 ~43
+ n8 X: M Z! Q# e/ p449 E0 t9 r7 @9 L' P+ \
45
6 g e! u" D; ]$ ]# v468 r6 v" q- `. l/ g1 U
47
- g2 W& B+ w7 x! x48
" O, q5 Q0 b) {8.4.2 数值型函数
, ]9 ^) z* d# N, p' ~ 这里着重需要介绍的是pd.to_numeric方法,它虽然不是str对象上的方法,但是能够对字符格式的数值进行快速转换和筛选。其主要参数包括:
2 H$ K! Y+ \6 `; v, S9 |# f8 {4 c) I4 h* w! e( ?! `3 a1 e0 {0 s g
errors:非数值的处理模式。对于不能转换为数值的有三种errors选项:
6 D. o; I. ?. X% ^# n; Praise:直接报错,默认选项
/ U, I C* k- _. Kcoerce:设为缺失值2 w- p! ?) c1 H; P
ignore:保持原来的字符串。
3 `% g# t; x9 m4 N& o# Wdowncast:转换类型,转成 ‘integer’, ‘signed’, ‘unsigned’, 或 ‘float’的最小dtype。比如可以转成float32就不会转成float64。
0 w) z6 N# }0 ~" I, Rs = pd.Series(['1', '2.2', '2e', '??', '-2.1', '0'])
. r/ `3 b9 [$ P
: T4 A* s1 e8 P+ i) ipd.to_numeric(s, errors='ignore')0 A/ ^; |5 G- I( e" `! A- i7 p. p
Out[93]: " M0 Z. i9 N4 }2 w: \
0 1
3 ~; K5 F' \% U& n1 2.2
! j& ?$ M! R+ u& r0 F2 2e8 U# G: f0 C! J; s
3 ??$ Y9 ]% ^' @: b9 y
4 -2.1
% e3 G" a8 { u) J- f' w- f4 q, E( S5 0
2 d7 r, P" i0 q; ?9 P6 `8 T# `dtype: object# \/ ~5 C- i* ^+ k2 G8 b- u
' Z7 i- P; [$ Q3 Ypd.to_numeric(s, errors='coerce')
1 i2 G) t* P1 }7 l2 E2 JOut[94]: 2 R8 a6 Y5 q4 Q! x! o" E7 M. f& Q
0 1.0
; q4 |" U1 |& ]- c- z1 2.2; u9 f8 j, J7 t8 ^5 U) C1 i. \0 C N
2 NaN5 }; l: a( \) b. K2 k0 z. E5 W$ ~
3 NaN' E: I6 t4 o% D
4 -2.14 n) p* ~' T* l Q9 e9 J2 @# O
5 0.0
4 r' u6 u! j6 Q; {dtype: float64
+ V& i K7 K' s; C& \% p3 o
" r( I- f$ h" `4 i. [- U/ E1* O0 Z( V; p4 U) v6 N* n
2
5 G( U( a- D% S% a3
' }" w; C" ^# r8 P4
( F/ X0 b9 u5 v8 S4 ^( J. w5
* W6 w+ T( z$ |6 M# d- s. {1 p) h6 ]) o; \
7
$ j+ X) H/ `8 f! v8
7 h& s( ?, N, n95 r" c: e. e# {+ j, C5 j* C
10
7 N7 G) l/ a4 q11
) R$ R# N) Q Z+ s12
6 [7 Q# R- X0 i r# Y13
( p# H! V; G: |$ W4 Y; ~145 P! l! a. G5 a" e f3 u9 S
15
; |2 ~8 Z+ ~/ Q+ U1 S* j- k16
3 E |+ F- Y0 v/ {9 d' u17
6 U0 h! ?; U- Y3 E6 Z$ S18
7 z& n# g0 A' F$ d$ N% e19" |+ m/ I$ j5 F. e2 @
20! Q$ I9 [. _. q8 P: X
212 |# z# c2 @; U/ T9 Z r
在数据清洗时,可以利用coerce的设定,快速查看非数值型的行:+ B5 D- O' P9 M% X) z( V7 \
1 @+ L# @: Y0 [s[pd.to_numeric(s, errors='coerce').isna()]7 ?2 \/ U3 d: n. J3 ^9 L
Out[95]:
' C! |6 N+ O' @' Z' T2 2e6 \9 X# c4 }% A. v. C, M; ?9 @
3 ??
, y& ^3 _. Z' odtype: object; h1 [/ Q& k# B) {* {3 S5 `( Q
1/ V& x; l8 }: I( g) o
2
% B' b) U# m1 c8 L3' M" @0 x( M, H7 A; M: u& r0 R
43 N u# h- A1 k6 f
5
) W, E( P. V) h/ C) j. G* Y3 a8.4.3 统计型函数. I" P* a( z- O, [9 G
count和len的作用分别是返回出现正则模式的次数和字符串的长度:
! O: }" {) q5 F- R8 ~' V& E7 \3 I7 T/ ]8 w2 ^; W0 Q
s = pd.Series(['cat rat fat at', 'get feed sheet heat'])* z T+ u! b5 D: h* d
. A0 \6 O" s$ G# I" C/ Hs.str.count('[r|f]at|ee') # |左右两种子串都匹配了两次
6 c! |0 b9 A. t% ~2 eOut[97]:
9 O/ Z" ?6 [# \' m7 M! c* |& s4 m4 ~0 2
( k# O( Z* {( Y9 f' d1 2. o* i7 m# D* P/ E* c# R+ `2 O
dtype: int64' h" p4 A; O" I2 o/ ?
2 F( H7 Z: q' |; g" s B, c
s.str.len()
2 h4 U* F: A" e5 M. w" EOut[98]: % Q1 e' d" l' x7 N
0 14: ]2 i$ F) Q! \8 r( i
1 19
w* \/ G4 V' l6 t; l# d, ldtype: int641 a9 |- t7 e# j s
1: K8 E' `! Y3 v, ^( _: r
2) D$ n+ L) T9 N2 z/ J( R6 |
3. ^+ I! s" I$ z7 e" b! O
41 o* i/ @4 N" T7 m" U- g" B
56 t" `+ e3 i8 j( a
6
5 T! y( p9 d5 u- o$ x7 E7
- N' v5 x* ?# y# k8- e7 ~# m- g, H' _
9
1 y/ S+ {2 Y" T1 q10- V7 J9 ?% w2 K8 I
119 W3 S- I! T( \/ Z0 H* C: e
12
* I) p0 {! B& ]# s13
* v# U! Q! a$ R5 u; g- i$ Q* C8.4.4 格式型函数
8 `5 ?' W. I. J 格式型函数主要分为两类,第一种是除空型,第二种是填充型。其中,第一类函数一共有三种,它们分别是strip, rstrip, lstrip,分别代表去除两侧空格、右侧空格和左侧空格。这些函数在数据清洗时是有用的,特别是列名含有非法空格的时候。5 `/ d& s+ C, y2 y
1 v$ S0 @' J2 H% P& j
my_index = pd.Index([' col1', 'col2 ', ' col3 '])0 r' b& J7 c- B: D z
/ [8 d8 D9 |4 X7 o) }2 ?. u
my_index.str.strip().str.len()' R: o: v/ v$ ?+ W; y! t
Out[100]: Int64Index([4, 4, 4], dtype='int64')% z9 s7 M1 J n+ o7 F7 z
' F+ U+ ?1 E! z! E6 [% Omy_index.str.rstrip().str.len()4 P+ g) {3 F& E$ T3 R& A
Out[101]: Int64Index([5, 4, 5], dtype='int64'); }& l8 z1 `' N3 u. {) T
1 U' D" Z* T6 s$ U$ N D
my_index.str.lstrip().str.len()) Y1 e, V9 T5 S* C2 [
Out[102]: Int64Index([4, 5, 5], dtype='int64')) n. X/ {; O0 Q: n' f; W
1
) D( s1 g2 V0 r* C n8 G2% W% {7 @; w3 ]4 y3 i4 d" ^+ r
3
( u- A" h- Z* C8 Q6 X4
- ]( g" U( i. m. q0 S) Q" I: L5/ O2 t& A! X6 }
6
1 f- f- q; o5 K7
" t8 B. m: F/ k6 v' ?8
2 X/ T/ Q6 v b6 m- K' n+ t+ ~9
8 r% f" i6 [+ Y! I. ~4 @' c10! K5 p# Q/ ^7 @( k Q! M* H8 Q
对于填充型函数而言,pad是最灵活的,它可以选定字符串长度、填充的方向和填充内容:5 d K. k5 p: l+ ?# e0 C
% q6 N! J( g3 z) W4 s. ]s = pd.Series(['a','b','c'])
0 x9 v6 ~' z# b, {, {5 O5 d+ b
* x7 [6 w: f/ H1 `s.str.pad(5,'left','*')
6 p- N* n9 |' Q* C% K" HOut[104]: : I) X+ E- Y3 E. T6 P% k @
0 ****a3 t* \2 H1 d& t3 _/ S4 i! ^
1 ****b
& ~0 ?! p; f8 T: r8 j2 ****c4 a8 u9 e& j, `1 e6 Y
dtype: object; I: c; Z# R5 L$ C. N; D
& s& |) Q+ g# i, c9 A( A/ [) Ks.str.pad(5,'right','*')/ m$ _2 a7 a8 x) H6 S
Out[105]:
% A# V; l/ z1 L0 z4 J& N( j0 a****
! w9 W/ q' k& o% M/ k1 b****
% ^3 B0 k0 k( ?5 ?: N2 c****
5 ^* H4 a- y4 }2 a! udtype: object2 e) r4 D; Z& D
' Z9 d7 m+ C) ^& ~: V
s.str.pad(5,'both','*')% y1 |! k3 r) y/ @; q: D
Out[106]:
J) O6 G2 n4 z- u4 o' h# g0 **a**
9 \& g8 j- q# e" S7 v N$ W1 **b**- p" G2 b1 ~- e
2 **c**
, O+ |; O8 j) a; K0 ]+ O5 u% Ldtype: object
; l* g$ Z" k! k& C- `
5 m% ]4 v0 d9 H% @" z/ J3 N1- G/ H% f7 a, {1 H
2
( E' }! _- J5 J+ s# ]3
9 r+ S4 ~1 a. V4 a5 @/ S+ C2 I42 }$ I7 A2 c. Z- E3 P& {
55 n9 `/ N# q' W8 O7 l0 A
6. K8 E! X# t" n _1 U" d1 G9 M
7
+ Z2 `' |/ \6 Y8 e1 L/ l7 G& P8
1 t8 w8 K' B6 _) D+ U. w( y3 ?* U- y9/ q. @4 a7 x6 x! e% C1 D6 O. h
108 J3 i1 X' R5 z9 b8 }
11# y; T+ s) ^2 D
12
8 H! P. p/ u5 t4 |- o7 f1 e134 \+ L. V* n) o! t
14$ n# {- r! w) W' ^- F5 F2 s. S
157 a+ T: n! A4 m
164 R. J) c) _+ x# \# {
17 l" f8 u$ k" T5 E W
182 Q' w* v) S+ l" I
19/ \4 ?" g$ e; Q& w2 T: e6 v
20' C5 w3 E& _1 ^
21" Y3 D$ B7 Y. v$ @" M% o5 P0 `
22( B# R" n* v: Z, {
上述的三种情况可以分别用rjust, ljust, center来等效完成,需要注意ljust是指右侧填充而不是左侧填充:
+ d$ |, u9 t% t0 g5 n4 l4 l* A* Z" n7 b& I6 l b. H) k
s.str.rjust(5, '*')
n4 S+ I! w6 ~9 w2 AOut[107]:
+ H7 o6 B" \0 m8 t' u0 ****a
; p0 g1 D% m6 R/ {1 ****b& a; t1 o( m$ W4 B0 x
2 ****c
4 t* ~0 V( G5 M" N( W$ j) V7 {dtype: object* a" [: Q* ]/ m! l# U7 T
5 g, L- o3 b9 B7 |( Y7 Y
s.str.ljust(5, '*')2 N! X# h2 `' T e0 d' E
Out[108]:
) R- s2 S. W" V3 ]- D0 a****
: o, E7 `! l0 `) D1 b****
4 |3 s, \; s$ {# u3 L$ P2 c****5 |' Z5 ], L5 ]
dtype: object1 Q6 B/ L7 C8 y. a+ {- q# _, a
6 T6 X, N& s# P' B
s.str.center(5, '*')
, F4 @$ H% ^1 e& e& X O, hOut[109]:
" d9 x3 z) @/ I9 J1 K, P+ W; L- U0 **a**
O+ `6 H" B; b7 b/ J1 **b**
; R$ m( `" q+ A/ N. z2 **c**% i+ ~7 n4 D0 C/ C! f8 Y7 |1 F0 k
dtype: object
+ {9 g+ a7 Q% B: W" h+ n8 e
4 x; w& e6 ]7 x3 g2 w1/ e% J, } g( ` t T
2
) J+ J* z, P1 h$ j) {3
0 E) T& v# v% o6 z' G4* j& E2 a( V) T( H
5! v! W, Q6 x3 M) h/ S5 p& @3 M
61 ?! C9 p" M( I
7+ X7 P* {7 V7 k/ b( E4 s
86 k# A6 [! w' B" p+ ?
93 l( n" z& J6 g4 L$ d$ L" Z, b
10
" [0 V1 V) O; S5 k! x115 e. n3 F: d/ I, N
12
( ? Q) Q: { T; O- b( {% Q t13
' S; S9 _1 g6 `0 |1 I' ~/ F' o141 u* w" W, p1 A; J7 Q+ z; D
15
0 M' `, i5 W9 W: r16& i$ g* V8 {! @# a$ k3 O! {+ Z* U
17! {7 h) J8 i) x4 e8 z+ T" m& S
18
9 `, f7 u. L1 _. _19, F/ J: ~/ e$ u _5 U: I6 r
209 z! {5 t3 K6 G4 o8 t& V" ~
在读取excel文件时,经常会出现数字前补0的需求,例如证券代码读入的时候会把"000007"作为数值7来处理,pandas中除了可以使用上面的左侧填充函数进行操作之外,还可用zfill来实现。3 a) |- g0 C t, t& G$ b
- Z7 i% [' \0 u$ W8 qs = pd.Series([7, 155, 303000]).astype('string')
8 P% c8 }' a' w) ?, T4 z% e) w; W2 Q: b- A5 ^7 h, B4 u" O
s.str.pad(6,'left','0'); O5 B+ C% m% P" q# y8 i
Out[111]:
" m1 W$ `' X V0 000007
; C/ N/ l4 ^! a, l7 m6 B1 0001559 H; m2 q- R& J. P
2 303000/ W0 I3 O5 X) j2 a4 j G2 s# E+ n
dtype: string% P' ?4 J- m9 d% L$ }1 r# d \
3 w% L" \+ g% u, `s.str.rjust(6,'0')
. b6 R) I+ r% dOut[112]: 0 u) _$ q" R$ a e2 ]7 l
0 0000076 V2 l" k8 R o# X) d* i
1 0001557 ~. V3 E8 F7 E) U1 h# h; r
2 3030000 h4 p d2 S9 \- C
dtype: string
& b4 k6 @$ D/ {. Q( d+ I0 Z' D, K
0 ?. Z- Y9 A0 A" o, T- j, b& O& Fs.str.zfill(6)' n% Y. |& x5 G. q0 @& J
Out[113]: . N% ~) L4 h" ^ q
0 000007( Y9 a- X3 u& p% j
1 000155" \& ~# R, g/ Y) o2 V* y; r5 g1 L
2 303000 z) w6 C2 W# }, ]
dtype: string8 {$ T% A9 u% V H+ \1 G
6 _$ V" v; T" Y7 b0 Y5 C
1
R4 c3 `* `( Y5 F# R' X* `2
! P) R5 \0 |7 ?3
$ Y- b3 q3 Z' C' u* F/ s7 B9 l4
! P! @0 r( A8 D! D9 g% a9 [& Y% P5
2 V0 X, t _" [7 C6$ H6 y7 `6 Z p$ @& n9 W
7$ ^ [6 h, ?; E& C7 R( c8 c0 V
8, A* o% Z2 W5 ? H) }# B7 U
9. L0 e }5 y( P$ ` M
10
' K! V& `8 u/ n7 L. u8 U11( ?) ]6 _, I: V* A* A
123 i0 T' v5 `# o! a
13
! d, e/ _" n" M* z8 G14
. F" v$ e: t' H15
$ s5 |+ g# ]1 ]( @165 J" u( o) I) L7 I
17
; W/ i/ }- Z) O" [8 l18
/ j' R M' |7 O6 j% i4 o19
P" I3 ^* }7 u9 G2 e20. r% L3 H' Z, j$ k
21, a: m E: {& q# O
22
X& E. {' W% }$ M a6 ~8.5 练习+ w2 @/ A" n7 f, b
Ex1:房屋信息数据集
" D8 C/ V1 j+ m现有一份房屋信息数据集如下:: q. K9 a# j" H, q5 ^3 F
: k# N! J/ R, U$ @& J7 P1 ?2 d+ w) ]
df = pd.read_excel('../data/house_info.xls', usecols=['floor','year','area','price'])' Y; j+ L% M+ ]- N8 E' b: x
df.head(3)
( t$ [( B- K3 l7 N/ m! U- r' SOut[115]:
1 f1 M* ^/ ^' k8 E4 v floor year area price3 N( q9 [" w3 @2 B# E' b) }* ?" P
0 高层(共6层) 1986年建 58.23㎡ 155万
, z+ k$ Z8 O& m" R4 r6 c1 中层(共20层) 2020年建 88㎡ 155万- |' S8 d+ X# k' [! _5 @0 |( j
2 低层(共28层) 2010年建 89.33㎡ 365万6 N5 j6 F7 B9 p& u, }/ U
1+ }4 t; T' b: x/ q2 P; |
2
# r& h g% M! E3 O H0 f* I/ A5 y7 ^4 v
45 R8 b g3 u1 U% s* r
51 ~& u) N% `' r3 I# P" E
65 T) G: M7 t$ Q0 `# Y5 b# {3 z
7/ P4 r- q" b ^4 S9 c
将year列改为整数年份存储。
5 L- A/ k9 J% q; p7 ^/ n将floor列替换为Level, Highest两列,其中的元素分别为string类型的层类别(高层、中层、低层)与整数类型的最高层数。- \6 h1 ^' v' S% w* K5 y7 \
计算房屋每平米的均价avg_price,以***元/平米的格式存储到表中,其中***为整数9 Z4 Z0 K3 v4 f; w. ^+ W( S& _
将year列改为整数年份存储。; ?; A" v* L4 V- }
"""
, ?* z& d8 y. H整个序列需要先转成Nullable类型的String类型,取出年份,再将年份转为Int64类型。
: ~6 [2 y: q! i* X注意,转换的类型是Int64不是int,否则报错。即使astype加参数errors='ignore'跳过缺失值,
4 j& e# f$ W C- A& Y$ Z转成int后,序列还有缺失值所以,还是变成了object。
; ~0 M q3 A# S7 L2 S( Z而整个序列转为Int,就还是Int类型,缺失值变成了 pd.NA 。( Y2 h) t$ B( {3 Y8 x% ^
"""
, V0 t" m/ l/ _- a. x7 _( Zdf = df.convert_dtypes() Y( n6 e) E# L
df['year']=df['year'].str.replace('\D','',regex=True).astype('Int64')# Y' h9 R6 I1 `4 Q
df.loc[df.year.notna()]['year'].head()2 q8 b& l1 z! W7 O
, e) c8 y6 c5 S. Z. u0 1986
: j" x X0 r5 I6 V1 2020
3 N, W9 u- k9 G6 W' O' `. f+ F* a2 L2 2010
% [1 q H7 h6 o) t* Z7 g3 2014
5 I' W3 C1 B1 h( d: ]4 2015! U; Z* S9 s1 P6 Y% l& ~2 o% d
Name: year, Length: 12850, dtype: Int64- X: B# `/ q' }- k* ~$ D
% w* w& {/ L( q2 L
1" y6 I' k) ~* c0 b2 N* d& n5 G
2
% D( k. G1 e3 I5 [5 q6 k9 A* |3
6 A: B2 V( u* U% A j0 I4$ a6 X0 T4 V& \7 `; a8 o
5
9 {( O# F4 G0 m4 L0 U- V) ?1 _69 D. n) E4 t: V) w
7, y( |' k0 G9 r% P+ S; z O3 i
8: f4 w6 \2 Y! w9 [0 }, s& e
9
" v" Z8 ~0 i! ?0 c0 x10& J3 B, `( i5 h4 N# \, s
11; }# \) M) N0 z1 [& q, G3 V
12
- X5 a* x0 u7 j13
, H& W: s; c. \. D3 o14
- S/ j' W1 s: I- t. b% l8 r153 N$ V% q$ j) u0 n! v6 ?
16
e( A, Z& ~& w2 i参考答案:6 F. [8 `; s" K7 y% r+ j
# s% B/ M5 p- {& G U
不知道为啥pd.to_numeric(df.year.str[:-2],downcast="integer")类型为float32,不应该是整型么
2 | N. L9 Q+ C2 x- |5 _( Z0 P0 V# [3 y
df.year = pd.to_numeric(df.year.str[:-2]).astype('Int64')
2 ^2 y3 Z' U! @# ]2 B3 V7 X" Zdf.loc[df.year.notna()]['year']
+ _1 c% g; o" W* T1
, m/ A) u5 \& u8 Y! }29 u9 z8 w1 Z: Y, @
将floor列替换为Level, Highest两列,其中的元素分别为string类型的层类别(高层、中层、低层)与整数类型的最高层数。
" J! {# y( l1 b( j# x% Tpat = '(?P<Level>\w+层)(?P<Highest>\(\w+层)', K9 D9 n7 N' m- p
df2=df['floor'].str.extract(pat) # 拆分成两列,第二列还是(共6层得形式,所以还的替换一次* d4 E5 x) Z/ ~& X
df=pd.concat([df,df2],axis=1).convert_dtypes() # 新增列拼接在后面,再次转为Nullable类型
' k1 u4 H$ n9 q) U6 Z( Vdf['Highest']=df['Highest'].str.replace('\D+','',regex=True).astype('Int64')
" z9 [+ e- B+ c- \ R( udf=df[['Level','Highest','year','area','price']]
1 J; b! s, v; o* ]% l. Edf.head(); C! U. p1 |" D3 R
" I; B d4 O9 C1 A8 G Level Highest year area price
; |+ S, n! b T& o, V7 C0 高层 6 1986 58.23㎡ 155万
4 B) ~3 p) ?( ~1 t$ F. e4 a1 中层 20 2020 88㎡ 155万# G; i/ ~# [* x) m( ?/ h
2 低层 28 2010 89.33㎡ 365万
# u* W/ B7 `" a2 [- c3 低层 20 2014 82㎡ 308万
9 R2 Y7 t5 H" ^; z& p4 高层 1 2015 98㎡ 117万 w n' @0 G1 Y$ V2 @, f6 k
1
( T' y F0 v& \+ G" P1 |$ `2
3 k9 @5 {, V. n9 ?8 g+ M35 M# t- B3 A9 w2 I
4
: s! Y2 P5 x* E8 O) e* c5
# ~) c1 q! e& z, F4 Q: M! o6
7 ^1 r) Y" i1 O: j) A) D. V7+ f, \2 w& G, h5 A# f7 x
81 X |* c( z, ~/ Y; T
9, F$ n+ }' }/ C. w6 U2 k, X% b0 n _
10" [1 r- g" }' I
11
6 v0 P& N5 F" |; |% D6 a. S. r12
) S& k7 X" }0 T2 x2 x$ q13
: g- O3 Y: E3 a0 v3 p# 参考答案。感觉是第二个字段加了中文的()可以准备匹配出数字,但是不好直接命令子组了2 |7 ^' V! x7 i5 q" U$ [
pat = '(\w层)(共(\d+)层)'" E: [8 }! {# W" L6 |* {
new_cols = df.floor.str.extract(pat).rename(# l! r( Q$ o0 t1 O8 D6 y. J
columns={0:'Level', 1:'Highest'})
# Z2 Z! g4 v: ]" m' n Q6 G: K
# H, |- B2 U5 Y N4 d) fdf = pd.concat([df.drop(columns=['floor']), new_cols], 1)
3 r l! E3 t# R9 Y- L' xdf.head(3)
8 H# I. r5 D& ~& h/ l6 V3 t
6 K' b& V( j4 j) v6 Q# U# T) gOut[163]:
2 a1 G( v( d {; [$ @7 r& z year area price Level Highest+ {+ o/ o9 F$ ] y6 i
0 1986 58.23㎡ 155万 高层 6
2 A( }7 \: q6 q( A( w- a1 2020 88㎡ 155万 中层 20 B* ], M) x2 F: C
2 2010 89.33㎡ 365万 低层 28+ v/ f1 H& V8 P! A, f& J
1
- K) `8 B1 W+ b L( c& a* n$ `2; y$ \; Z3 x# f& y/ O
35 `; [( w# x6 b& L* x
4
$ H, l6 \. {2 B5/ s3 i' \7 s" E- ]& M! o2 O& h5 x
61 ^: R0 D } V8 @+ Y+ n' g
7
+ b* w3 J, y( x* F1 y s# N87 G e& p' `# E- O( z* t Y D
9
2 {9 [! ]9 |. k" m& _1 G$ C' R10
0 M J1 Q" k* F2 M7 I- l& B11" ^8 V2 s5 K s: E
12
% c2 ^$ q% `% x5 r' _) U) T6 r' K y' K13
& u) d2 ?/ e* y$ v9 G4 ~" N) S计算房屋每平米的均价avg_price,以***元/平米的格式存储到表中,其中***为整数。
7 m* {/ U! M9 f- H"""6 q' h1 M% m9 F9 e% d9 s9 }% T
str.findall返回的结果都是列表,只能用apply取值去掉列表形式
7 Y: A1 |' H/ g3 E9 Z! U参考答案用pd.to_numeric(df.area.str[:-1])更简洁
+ T) t/ F+ n& R% I+ B0 V% @! c7 j由于area和price都没有缺失值,所以可以直接转类型' v! ?4 `+ U# K3 d! x) d& j
"""
: y: d" f' O/ y7 ^( \* g; D# Rdf['new_area']=df['area'].str.findall(r'\d+.\d+|\d+').apply(lambda x:float(x[0]))/ Z# t! z# r1 b% F
df['new_price']=df['price'].str.replace('\D+','',regex=True).astype('int64')
+ N' b6 ~& L W1 idf.eval('avg_price=10000*new_price/new_area',inplace=True)
" q7 L- G8 o4 G# 最后均价这一列小数转整型直接用.astype('int')就行,我还准备.apply(lambda x:int(round(x,0)))
7 y7 M6 q9 P9 v: C5 o( V" X8 f# 最后数字+元/平米写法更简单" H% m( \3 z0 L' G) J/ ~9 n- j) {2 d4 E
df['avg_price']=df['avg_price'].astype('int').astype('string')+'元/平米'
. Y3 i- x; }5 y$ s7 ]! L1 ]del df['new_area'],df['new_price']0 {) w3 u6 `) n
df.head()% W5 |$ T z; J. v! v8 { @" _0 \7 {3 e$ r
- t" B) a% G( r3 Q7 Q) N5 \5 e Level Highest year area price avg_price
: y/ H- z: \: P( s: z2 l* A0 高层 6 1986 58.23㎡ 155万 26618元/平米) b9 X* H- j& \" ?: Q( h& Z9 `) B% ^. j0 f
1 中层 20 2020 88㎡ 155万 17613元/平米& f! m5 _1 M" B! r
2 低层 28 2010 89.33㎡ 365万 40859元/平米- a9 R1 E% g1 Z0 z. i j
3 低层 20 2014 82㎡ 308万 37560元/平米% k. {( _ _/ A
4 高层 1 2015 98㎡ 117万 11938元/平米8 E; |* m, r; o' c
4 U9 n8 [& i% ^- @' I' s, V& X1
5 X3 H x; f3 M: {. m0 A9 t2+ F) k7 [7 C( t: C
3
2 H) \4 J9 X; M& R0 g5 Q W4; g+ n* z% `/ Q( x
5
) j) C' u0 \5 O! G' @3 m7 v, f( b! q6
6 k3 j" e* \# e* h7& L. @1 ^0 D/ d- q
8
" s: g1 w0 t# @* X9
/ ?) [& k+ W# z- W% C10
" ], r7 r6 O# |( A. N- ?; E% x118 p% n0 B" [( x# V0 V) d7 ~8 k
12/ W% `! n0 k5 w, V8 ]1 E. v8 q4 P
13
3 s% U4 N2 _6 ~- o* D! D14
5 M+ ^7 v1 I- J" B( [2 B) O15& i' E3 B9 J: w+ t6 u; ?9 g" V
16
/ O- i8 ~$ Y* y+ `- B d0 w+ C17
* c! M# p) d; X) N18/ U, c, T" P2 ~; M3 }( p6 w8 ?- g
19$ T4 T, |! ?; C2 `, @ G
20% B9 Z8 O6 N9 p* N9 g* [4 J9 S
# 参考答案
8 p; b4 K& I9 V* y5 G' ?s_area = pd.to_numeric(df.area.str[:-1])
) T7 T H# \" C! m0 d% Rs_price = pd.to_numeric(df.price.str[:-1])& M% D2 U; Q" o' b. T: C' ^; V
df['avg_price'] = ((s_price/s_area)*10000).astype(0 }8 Z; |0 }6 |4 l$ f) E
'int').astype('string') + '元/平米'+ m& U- G% k1 p) \" w
. u& r, T5 T6 T+ G
df.head(3): X5 B* u. [6 b& k& v' J
Out[167]:
9 c$ r9 d. z1 ?( s3 G8 z$ a year area price Level Highest avg_price
, W1 D9 Y6 L. Z+ p" |0 1986 58.23㎡ 155万 高层 6 26618元/平米- D2 j$ w/ U+ M- e, Z. ~
1 2020 88㎡ 155万 中层 20 17613元/平米5 g3 g, `# v" S! a* i6 w4 }8 g$ B
2 2010 89.33㎡ 365万 低层 28 40859元/平米
4 J3 ~9 m7 n: C7 y1
2 D h4 n8 c7 ^- T+ h24 r6 q( w0 a3 z/ u
3
; g3 l. q4 Y! W8 D9 o8 ^4) n. ~& \ r# P* t% R: | r$ Y' b! |
51 S( }# x6 a S* V
68 m) P5 U& N" T. r) }
7& c- t/ R( M1 @4 m& y# }& e
8% q! W0 z9 l& L! l7 r
90 ~) B! t! U1 R) |) X9 d
10
; I/ f7 P1 M; O# M% M11+ I/ e+ I+ ?% P# j; T, Z
12
8 O5 T9 V3 H) b# gEx2:《权力的游戏》剧本数据集' l3 ?3 t' d( {
现有一份权力的游戏剧本数据集如下:2 N/ ]0 K, r8 J5 c, z: Z
& Y. {3 a- y( z" G; n/ m- W7 N
df = pd.read_csv('../data/script.csv')
5 b3 X6 ]$ U' Y8 |$ a# ddf.head(3)
& s4 [. z, X1 [9 P' @7 Y/ o" v. Q1 X: K! c$ u5 z8 a5 J4 ?
Out[115]: " C0 j# k3 o; {1 {3 q/ U4 ^: Q
Out[117]: . q% K/ D; v1 T3 x7 d" e
Release Date Season Episode Episode Title Name Sentence7 j/ o: Z9 v2 k$ ]. Y. I( L4 B
0 2011-04-17 Season 1 Episode 1 Winter is Coming waymar royce What do you expect? They're savages. One lot s...4 c3 {! N, j/ i6 C; {3 l4 i
1 2011-04-17 Season 1 Episode 1 Winter is Coming will I've never seen wildlings do a thing like this...' W0 e9 {' K4 h& ?
2 2011-04-17 Season 1 Episode 1 Winter is Coming waymar royce
E7 N( N- M. M1
0 i. ?8 u# Z" ~; f* ~* I& t! P6 a2! q+ ]+ i7 D. M1 J% C4 k2 P2 @& @
36 n. L( R I) i l% Y6 Y/ y/ o
4
# q5 |1 U+ V1 l5* N1 r4 z! m y8 Z7 {
6/ z1 {& d4 j7 {& a9 v o% Q" X
7
9 D8 F: i2 n" ^ ^; S8 D$ i, D/ w* }" O. R1 t. h8 x
93 |# M8 s; J3 A! O u- D3 o* x
计算每一个Episode的台词条数。) ]# M; o( C% T7 u7 y# r0 {. U, A
以空格为单词的分割符号,请求出单句台词平均单词量最多的前五个人。) L7 M6 F( @* ~3 E* m
若某人的台词中含有问号,那么下一个说台词的人即为回答者。若上一人台词中含有 𝑛 个问号,则认为回答者回答了 𝑛 个问题,请求出回答最多问题的前五个人。
' `$ T N, c8 M2 h计算每一个Episode的台词条数。
/ }/ X5 k. k; [8 I& F4 ddf.columns =df.columns.str.strip() # 列名中有空格
9 ~% Z4 [6 l5 v& D+ A# Z4 B( Q5 Jdf.groupby(['Season','Episode'])['Sentence'].count().sort_values(ascending=False).head()2 j S" l- ~$ s2 H% f; j# Z& x$ j7 h
6 P% u2 B; A, d( x0 o( x* a0 wseason Episode ) ]2 j+ ?( V. D4 b! O
Season 7 Episode 5 5050 r: R& _2 I! m
Season 3 Episode 2 480% o* ~8 L) O) v' t) s, N$ O
Season 4 Episode 1 475
' s" k3 z. Y+ c$ Q7 X6 L* _/ gSeason 3 Episode 5 440% d. M0 @: h, R1 I
Season 2 Episode 2 432
; p% C8 V- z4 P/ U1, } {: ~ a( c5 k* M. o; v
2
7 g% ]; b+ @& b; Z$ C3$ [3 v1 R3 m! m3 N4 x4 h0 y+ ]
4
5 B& a5 y5 Y& R5
, r4 s, `- j. v$ z9 I6
7 E: ~ Q2 m/ X2 O8 t/ F79 }1 O% C J; a% }
8
. T( L: z# }- N- |) `9
, \& K, z* c1 W. X; K5 m1 S/ Z以空格为单词的分割符号,请求出单句台词平均单词量最多的前五个人。- t: [2 J$ K/ q4 R
# str.count是可以计算每个字符串被正则匹配了多少次,+1就是单词数7 j6 h* J' ?" ], o9 S3 H( {
df['len_words']=df['Sentence'].str.count(r' ')+1
' B5 j o9 Z, E( A$ b' Fdf.groupby(['Name'])['len_words'].mean().sort_values(ascending=False).head()
2 o* m1 Z s {! O" ^5 Q3 H2 b( C+ @; z' W8 P
Name
2 ^/ A. I' r3 {4 Y& Fmale singer 109.000000
+ M. U6 L7 {& ?' hslave owner 77.000000
. X" e1 F8 E- {1 ]; y/ xmanderly 62.000000
7 V& c3 d- s8 ]lollys stokeworth 62.000000
( Z* t7 S- G; y$ X( cdothraki matron 56.666667( d" s/ Q: n; h. W1 a- k0 O
Name: len_words, dtype: float64* f$ Q8 ]6 j( L. E8 K- ^
1
& p' H! V7 w. N f% H2$ d: {' x* l( o5 x2 C1 m
3
" v2 ]; v# y; o" T+ i4
" K4 Q) B! F( ~7 I5' g; G# w. K. V* o8 U- h8 i# I' x
66 g1 U, F3 t: e
7& Z$ K* x7 Z/ S# R$ X
8
" g' @2 U, S' O3 F: x9
) }) w( R) ]/ N/ i7 g10
_) u$ u8 W: ]( ]& f11- s. n5 ?7 M; v& T0 t: V) Y
若某人的台词中含有问号,那么下一个说台词的人即为回答者。若上一人台词中含有n nn个问号,则认为回答者回答了n nn个问题,请求出回答最多问题的前五个人。
4 u. I, B0 a; q# C# ydf['Sentence'].str.count(r'\?') # 计算每人提问数9 F2 {2 r6 T4 P8 |
ls=pd.concat([pd.Series(0),ls]).reset_index(drop=True)# 首行填0
" k& V% K" I4 I2 z! Tdel ls[23911] # 末行删去: }+ e3 G; v. y7 h2 e6 Y
df['len_questions']=ls& d S @: @2 {% |; _
df.groupby(['Name'])['len_questions'].sum().sort_values(ascending=False).head()
3 [0 f% Z, D D v
~1 |8 @& S3 ]2 gName; G! ?# B; T8 l7 s: m) I
tyrion lannister 527
6 w9 e( x, h4 zjon snow 374
7 m+ i) I' T5 t |) U o8 G+ \: s8 ]jaime lannister 283* j i0 R: e7 N: n2 D
arya stark 265
/ {- v' u9 u3 g1 a0 dcersei lannister 246+ k R X$ d& Q0 P3 p
Name: len_questions, dtype: int64
, ^" i2 ]" \1 x# }3 {3 [2 T. l9 \ B7 ]2 S
# 参考答案
, r" C& Z" S" J, |' Us = pd.Series(df.Sentence.values, index=df.Name.shift(-1)). \1 ]. P1 u/ i
s.str.count('\?').groupby('Name').sum().sort_values(ascending=False).head()! u% J* m% r" M: o
8 k0 P( y& W* |5 x$ L5 u1& o: D9 f3 J, x G1 B D
2$ V- q* Y# F* ^* i
3
8 W* |" o/ \' a a& R8 Q8 L48 n# S7 t8 c8 D2 x. w/ p- v7 L
5" Z F6 j" m: I- O% y
6& S2 z# n/ @4 r9 ^, M& k" S8 n3 `
7 ?- g, g6 c/ m
8% p/ z* j1 M$ r/ W4 e. n
96 X% j- e3 u3 Z
10
+ j# g5 U% r. d6 Y+ n5 `9 }% ]" \11
( A& F+ {$ [- e( ?- u) B0 f9 n12/ ~" [6 D2 E' m$ W F! K
13
- Z1 \* M) V% w2 M14* p( o1 p0 q9 u8 t% Y; e
15
6 ~ b1 @% N( v1 V/ y4 _16
! U [+ A) F( ] h$ t171 T$ b) s3 Q6 p
第九章 分类数据/ S* }" |) y, k/ R
import numpy as np
& R, O- Q$ F" c/ Q$ R O+ P& vimport pandas as pd( B: f9 H+ i# R% l* e6 I! c' b
1# `* A) l& `+ I) k: V) R
29 q- c" H# k3 c# y
9.1 cat对象
; }; P& L% L, ]9 m9.1.1 cat对象的属性& z# C: f. E/ z- P }$ {' i
在pandas中提供了category类型,使用户能够处理分类类型的变量,将一个普通序列转换成分类变量可以使用astype方法。
; q& ?: |, I% ]0 j5 i1 C9 `, K+ _; m
df = pd.read_csv('data/learn_pandas.csv',
/ V$ s& b! y. k! z; z4 C usecols = ['Grade', 'Name', 'Gender', 'Height', 'Weight'])/ Q' s+ w; Z4 e/ [* ]: i6 [
s = df.Grade.astype('category')
# f, `- B9 ~* I# I; R$ l
5 Q. \ f% L s$ j$ Es.head()+ {# ?3 Q b. o8 A3 B6 O3 N6 n1 I
Out[5]: . G% R4 b. a2 f8 ?. _
0 Freshman
) @9 g: A; \5 l: A* X1 Freshman8 w# R% e. ] v8 ^# |; O7 U7 b! ]. x
2 Senior& A G# N- b! j" N9 ?- V5 i
3 Sophomore
( \: O3 C# s6 [# w4 Sophomore
$ c7 j. o6 V& W" a( L* RName: Grade, dtype: category
G0 A% B' \# n6 HCategories (4, object): ['Freshman', 'Junior', 'Senior', 'Sophomore']
' |, q" I5 i1 @13 c4 }/ P4 E3 R- o6 b( N
2
4 j. ?* B* I- _# N( N3
( i: f4 N; C. h; O4 F$ X4
. C" H/ u0 y* M0 e7 E, [6 K$ ]# D59 v E( Q+ }& _
6
" ^" e. R- a. Q5 o8 Z7
- f" r4 K) w. {: f; `+ a86 d& I! N5 V( p+ c
9
0 g: ~) V: Z4 ?/ m0 T4 Y10
6 S2 Z `. J6 X7 M- N11
6 P. ]. Z. |$ k G) q* T/ z12 H/ _; h" w6 W1 q3 t8 O
136 B$ q! A, p2 Z* T1 v9 R7 B
在一个分类类型的Series中定义了cat对象,它和上一章中介绍的str对象类似,定义了一些属性和方法来进行分类类别的操作。
" B, @( Z3 U$ h& v9 Z8 B2 g9 ~0 g. h! U$ ~
s.cat/ e8 H- }' x. T4 R5 F
Out[6]: <pandas.core.arrays.categorical.CategoricalAccessor object at 0x000002B7974C20A0>
8 e# @) \% g& }% M k2 C1
! {6 U; ?$ a4 |; q- w8 j7 w: w' u/ ?2. n( P& e" M( I$ p1 p2 H- _& C
cat的属性:# i. u: T. X$ @5 q% i7 A; m
2 U3 n" Y+ _7 q# |4 m9 R P7 Dcat.categories:查看类别的本身,它以Index类型存储
: U( t1 X* ?. R7 j. vcat.ordered:类别是否有序
& ^% j+ a" g3 {4 _& l- z5 @* jcat.codes:访问类别编号。每一个序列的类别会被赋予唯一的整数编号,它们的编号取决于cat.categories中的顺序6 K Q7 }( j9 h, K
s.cat.categories: S6 o* E( J6 [' ~& d
Out[7]: Index(['Freshman', 'Junior', 'Senior', 'Sophomore'], dtype='object')
5 \, x6 p8 V! g p9 E: v3 d3 O3 n; H, l; c) g
s.cat.ordered
! a( F0 g% o$ d3 AOut[8]: False
$ u3 h$ ]/ a' [/ Q5 s0 k, x& g
* L! ?$ p* B& O9 h: Vs.cat.codes.head()& e k* g" H3 i) c
Out[9]:
# _0 w$ P6 d! g1 n0 0
/ a: r: Z, k# U0 [5 C, O5 {: V% A( g1 0
5 ^! [6 ^+ N, s) c3 k2 2
, Y" ~) o& X* T7 P3 3% i( x! X8 g7 G% C+ s0 R& [2 `
4 3
- q3 f3 l1 v6 G5 J N! Z- w8 f6 Ldtype: int8
1 Q- Q( F% d5 }/ N) T7 \: N1
0 k4 j) {* }% B) E+ W2
2 m$ V- y* M, f2 q/ I; O9 C& h4 ?3
7 i4 s! P3 t& n/ t2 F- Q) q7 D4$ S8 V+ t# M2 U* O8 q
5
) F4 j: j% I+ p. s& I% S6
$ f, b8 B. Y- v' S7 q. K) Q% f# k$ [+ }7
% r a. c+ D% `$ ^6 a$ v# |8
\0 B, T/ F" g: m# ?. d6 ^# C9
+ k/ o# f J8 r* q10
* ]0 Y- a* i1 |! [/ R11
3 ~2 N. J/ y; Z12
+ E! L& ]' n4 a13
- }7 |* b. ~/ l& ~14
5 N3 E! l% U, U7 n) P/ b9.1.2 类别的增加、删除和修改4 ^+ L0 g$ j* S h& O1 f2 w( n
通过cat对象的categories属性能够完成对类别的查询,那么应该如何进行“增改查删”的其他三个操作呢?- p, z) C" H2 H: H
6 c a# ?8 q2 P( q2 m4 p2 e【NOTE】类别不得直接修改# g2 t; l n$ M$ ]9 g" `- Z. O9 k3 C
在第三章中曾提到,索引 Index 类型是无法用 index_obj[0] = item 来修改的,而 categories 被存储在 Index 中,因此 pandas 在 cat 属性上定义了若干方法来达到相同的目的。' @; j) A, ~" T, f- q
4 A* o: T/ U! K9 h
add_categories:增加类别: ^7 B/ m* _% E* ^% S' B
s = s.cat.add_categories('Graduate') # 增加一个毕业生类别2 Y! o6 V: f1 ?' n. Q
s.cat.categories
5 B( @+ u0 {! U6 U
( D# h+ `( v# d/ pIndex(['Freshman', 'Junior', 'Senior', 'Sophomore', 'Graduate'], dtype='object')+ s0 q/ R9 g$ R/ P
19 ?, l$ c; l* `9 A7 X
2
- k( l5 Q" V& T3, p0 c/ X% K l- B5 T2 B
4
2 a/ s/ C0 a" dremove_categories:删除类别。同时所有原来序列中的该类会被设置为缺失。! L9 C* {; _% d% R1 k
s = s.cat.remove_categories('Freshman')8 X6 c+ o; t( E, G k5 ~: }
- K! K5 `0 a) Q# p. c6 ]s.cat.categories. w! F& g+ \6 E! z0 {
Out[13]: Index(['Junior', 'Senior', 'Sophomore', 'Graduate'], dtype='object')
, i" I! g" \: {! i
& Y' \8 c/ R. |, g# bs.head()" b( y, S2 T$ o$ k- l1 m3 o
Out[14]:
+ \3 _4 E& i5 d# ~8 \0 NaN% f1 i4 m. h3 E# v5 T0 ?# X
1 NaN
3 ?7 K% V- l3 l# ~9 X* B# F$ z0 ?2 Senior7 ~/ }* C S' E5 I% w' _
3 Sophomore) F" `7 P" I6 U1 Y7 p
4 Sophomore* U. j# \$ Q0 B) {
Name: Grade, dtype: category: c6 a4 x9 h& W% T9 T' k( [: i1 L
Categories (4, object): ['Junior', 'Senior', 'Sophomore', 'Graduate']8 @2 P# ^! Z( u# H; n
1
9 y5 z6 X, M' g2 [% L27 C8 R5 X2 f/ ?; n
3
/ V: L; n. z9 [# U42 _6 I6 o$ f( E
59 H+ h y* L3 J. T2 Y3 l1 `
6
; `6 D7 E* {2 u+ S8 ?7
: D! `% e! ]" @+ X6 {5 T1 R8: E0 N. J0 v( W' ?/ t# H2 i( u, z/ t
90 z; E1 {6 Q r% l! L: \8 c
10
7 ?7 W3 ^8 I2 {* a11
3 P3 R' h) @0 b- }) p12& \* p* F5 H& |) L. ~; o4 d% w1 h' b
135 B$ T# I" q* ^1 j/ d5 H
14; c+ S0 a: ?7 ^. ^6 X1 F+ r7 i# b5 B
set_categories:直接设置序列的新类别,原来的类别中如果存在元素不属于新类别,那么会被设置为缺失。相当于索引重设。0 s4 o% f% G3 A7 Q$ A z
s = s.cat.set_categories(['Sophomore','PhD']) # 新类别为大二学生和博士4 A9 W( H/ Q+ v, ~6 T
s.cat.categories( h7 O9 p! ~$ P6 }# s: W
Out[16]: Index(['Sophomore', 'PhD'], dtype='object')
/ n9 y/ o) u! b; d) s3 a$ ~0 B# V+ N3 Y4 |, p4 f3 j2 [
s.head()7 M: L) s& }9 Y5 _$ V
Out[17]: 3 N1 n, y0 K( S' Y& I
0 NaN
9 x/ _. i- k/ [! D7 ^. o. Q: A5 S0 H* x1 NaN
L/ A3 l; Z, h9 h* j4 z2 NaN1 t% Q4 d: |" J" D
3 Sophomore
) R+ }3 N# W; f6 w4 Sophomore
/ l/ @2 B. ? j+ s3 B# \1 ?Name: Grade, dtype: category
3 o& ~' D- J' m" o4 tCategories (2, object): ['Sophomore', 'PhD']
0 T0 q; J N( z ^% u) M; U1
# v- H$ v# S% a2 c2
& V2 D# @5 B0 f0 {* H* V$ T) X3
8 r0 ` B: S0 b7 d4. X) a' I' ~. \" z' j2 b+ R
57 {% c' V3 G( T2 t. B2 c
6" i4 Z9 H6 |9 |" ^7 s
7' q' w) m' M" ~9 \
8: R4 B# f; P0 y7 R7 k% O
9
1 d. {' ^, ^3 ^2 \6 b10
* K* \" q. n6 e4 ~2 \% B11- `/ S/ n9 N$ m( N
12
9 v/ R& x0 z6 w+ c( Z4 c2 U6 j1 `4 ]13
8 [! m! ^ C' j/ aremove_unused_categories:删除未出现在序列中的类别
1 M; D5 ]) u. S0 y1 Ys = s.cat.remove_unused_categories() # 移除了未出现的博士生类别
; P; T5 o; Q! u7 ~s.cat.categories. O- d$ O6 }. o k
! h2 b, Q5 m' }5 ]8 S
Index(['Sophomore'], dtype='object')
' ^" |5 _: |3 E6 g* y+ h1
, N0 ?6 g& X# g# Q8 [2
" ^( Z( v- l& J* W3
# P1 ? I( K9 K4* e/ Q5 _4 W1 ^" `$ C1 a
rename_categories:修改序列的类别。注意,这个方法会对原序列的对应值也进行相应修改。例如,现在把Sophomore改成中文的本科二年级学生:! S0 V- K. t0 W+ a6 @! I
s = s.cat.rename_categories({'Sophomore':'本科二年级学生'})! [$ [0 V% n% [$ Q6 }/ i' L
s.head()
; H7 ~) G+ K( m/ w" R7 ?
& X- P* Q# i0 B# ^- D/ z1 H0 NaN6 X, `3 v& C' Q3 t
1 NaN
& R" a! e/ W* W9 S2 NaN
& l3 g+ G4 {2 T( q3 本科二年级学生
' [) h" m3 ]. l- K& P- _4 本科二年级学生' b M7 h k1 R' R& j
Name: Grade, dtype: category$ P; l6 ?. T# h% x4 G" S7 q1 m
Categories (1, object): ['本科二年级学生']
3 B$ x1 ^9 @+ e. M& s1
7 n/ M$ g5 G# ]( c6 c# I2 a20 J3 j" b' @6 ]2 M" E3 |/ l( _
38 W/ n% i* a Y( ?: v( W3 g' X
4
# s& g& W5 V6 e50 Z1 \6 L _' m; v/ V
6. C( U8 L5 F' D6 d1 U" D$ ^8 \
72 | B X# U" t8 J
8
- Z/ W7 A4 C. p+ u) g9: c. T" w$ g+ }
10+ c5 q; m) W' v1 s- f9 k; S
9.2 有序分类# R, y8 T$ P. _" L- e
9.2.1 序的建立$ c9 X; u* @ ~5 g
有序类别和无序类别可以通过as_unordered和reorder_categories互相转化。reorder_categories传入的参数必须是由当前序列的无序类别构成的列表,不能够新增或减少原先的类别,且必须指定参数ordered=True,否则方法无效。例如,对年级高低进行相对大小的类别划分,然后再恢复无序状态:0 I {% y( ^4 _9 ]( ]
, H9 ~2 _4 q) J0 [# X: E
s = df.Grade.astype('category')
; Z; N1 ?$ ~: @+ ]; f8 Ws = s.cat.reorder_categories(['Freshman', 'Sophomore',
0 ]# u) O0 o$ S, Z9 _5 J# c4 { 'Junior', 'Senior'],ordered=True); {5 `! a. t ]0 A. M8 N5 n
s.head()
3 i: n: O! M8 ]Out[24]: % M9 V& S/ ~# }
0 Freshman8 \- }- ^6 f4 p2 I: k
1 Freshman
0 g i7 |# S" s( |2 Senior
# l" u; @; I$ h6 |- ^, a( S6 r3 Sophomore0 P1 \8 S5 o7 y+ e+ ] p h) b' C
4 Sophomore
+ ^0 C3 l, K) _5 `! B4 U- v# C) oName: Grade, dtype: category
5 G; t# X- ?6 y/ wCategories (4, object): ['Freshman' < 'Sophomore' < 'Junior' < 'Senior']5 {" {+ P% ~. d6 H9 J1 \0 h# G. v
# {6 t. G7 v' m# l( F7 l
s.cat.as_unordered().head()/ q9 M) I* e p
Out[25]: ' }( y" L7 F9 w5 _
0 Freshman( ^' ]' w" `" k0 }5 X. {
1 Freshman
1 f% w. ~# h8 W" X4 w! E8 z! V2 Senior
* B* k4 [4 A' U. t5 v3 Sophomore
; R* D. ]# O+ K$ P8 B4 Sophomore P& J S7 K3 |5 Y4 J
Name: Grade, dtype: category
% q$ w) R8 U6 n: O0 [/ p/ j SCategories (4, object): ['Freshman', 'Sophomore', 'Junior', 'Senior']
9 y0 E7 \: m4 K* N' {7 O3 q1 B' u6 A% T9 p! j T* b0 Q
1
. z" z* |3 W# j# S3 f2, x; X# ?9 r) m' t) K" c+ B
3& f4 a9 Z7 ?5 k
4( ^- i( d6 d$ f& m
5
# l7 k" m* y- }- c% g3 h) C. B6
, E h7 Z6 a! e7
" y* J* j) ^! d. @8& m/ ~. g/ X, l, H. }/ F
9
6 q5 F# w) q7 M& k( j10, }- N$ i0 q5 N8 O; J" g" R7 d
11* R3 r/ M M' K
126 w7 V5 \ n7 Y- j/ ?+ y! M1 m
13& g h o* r# Z% [
14
f# Z, e) r1 X. I Q9 J9 V15
1 ^( l- d, f, V) X- f9 i7 c16
1 Q* s4 _9 n- e+ P17
$ m4 f) O' }" z4 @6 h4 b18
/ D2 r8 z0 V; ^& f6 N! x/ E19% G" K5 q1 ]3 L# ~7 l
20
$ s5 E; d; t) [21
4 c0 s2 {% l0 V4 w; P! z' ?22
- z* `9 y3 J% t; M% T 如果不想指定ordered=True参数,那么可以先用s.cat.as_ordered()转化为有序类别,再利用reorder_categories进行具体的相对大小调整。
1 s9 `0 i; P- [% a: m# l4 [$ ^0 I6 K9 U$ n, X
9.2.2 排序和比较, O/ a% ~6 q0 Q9 M$ ]# U& a" [
在第二章中,曾提到了字符串和数值类型序列的排序。前者按照字母顺序排序,后者按照数值大小排序。) z" q! b3 R( }2 j* E
. }" W# z |) A6 F k5 h* U
分类变量排序,只需把列的类型修改为category后,再赋予相应的大小关系,就能正常地使用sort_index和sort_values。例如,对年级进行排序:7 J; ~9 T* G% n i/ }6 d1 O0 u# I
( w" X+ A) ~9 ^. m
df.Grade = df.Grade.astype('category')
9 f: p2 j9 K- A0 a7 v) odf.Grade = df.Grade.cat.reorder_categories(['Freshman', 'Sophomore', 'Junior', 'Senior'],ordered=True)+ Q! A6 k* {- H0 b2 F# M, c: z# r
df.sort_values('Grade').head() # 值排序
/ `8 `/ B+ D& h8 \6 A0 [Out[28]: 4 Z- Y- m8 R1 w. c5 e3 Y' d
Grade Name Gender Height Weight
1 Q; `# R1 ]) K+ F/ ~' L2 M0 Freshman Gaopeng Yang Female 158.9 46.04 |' }2 N4 {3 h) S- O" O
105 Freshman Qiang Shi Female 164.5 52.06 h/ t7 n1 o5 E( [' K' [3 ~6 E
96 Freshman Changmei Feng Female 163.8 56.0
6 E. _, s5 S5 j5 G88 Freshman Xiaopeng Han Female 164.1 53.03 n! G! ]! _) |1 U; S
81 Freshman Yanli Zhang Female 165.1 52.01 W3 B5 i# b- L: j ]6 x
8 _" q" x( ]6 _8 ]
df.set_index('Grade').sort_index().head() # 索引排序
9 Q# g( v4 P2 D: u8 Z! f, ^Out[29]:
9 ]9 z' A1 V+ H6 O) i5 F Name Gender Height Weight) O8 n4 _& g, ]' x% q/ c4 h
Grade f. M5 w2 p: z* f" G. ^
Freshman Gaopeng Yang Female 158.9 46.0
( C4 B) |# U4 {8 [# [. ^9 g- OFreshman Qiang Shi Female 164.5 52.0
% u& m- I1 G2 S2 @$ NFreshman Changmei Feng Female 163.8 56.0
: Q" u1 P; e$ w# p% o+ B$ k3 w& P) m0 {Freshman Xiaopeng Han Female 164.1 53.00 G4 @' B4 d2 L* G. F8 u2 m; o
Freshman Yanli Zhang Female 165.1 52.0% R2 A( h! Y( l6 ]
, s: C0 S4 _* l' z' r i( i \5 ]: A14 l9 M2 L& [( O1 e" ^
24 c% ^# V/ W$ R6 o% G
3
; ^, Z9 Y/ B& m |2 J9 i) G2 |2 y4
$ c" d7 B& Y5 `5; h' |8 |1 i D7 N. g
6
1 }' O$ K' q# {& q* J7
4 K" h, l4 M. f7 l' k8
' ~! C9 w+ P' h S1 V! u+ s& C1 G9 M( \. n* w+ _) D
10
+ ^) _# z- V( U9 S; E- L11
1 h+ j8 A! E0 P9 N( s! K; J12
5 X) X* u. q2 G* a13* r y1 M# ^) Z: }5 v9 o; f
14. g5 F% _* o/ ~* Y
15
$ `: ^. D8 e" ?7 s. W1 c1 p+ N16
1 P2 u8 V' I+ |17* e% [0 s7 ]. `: L& W! L
18$ m: G& _5 P+ I& P6 C, G4 o% R
19! {9 A6 f. P- h0 b. j& L
207 O. G! M. i2 e- N1 V$ [
由于序的建立,因此就可以进行比较操作,方便后续索引操作。分类变量的比较操作分为两类:- N) A$ F" Y4 a' C2 v+ x% E
; b& i8 w& o$ {* d* V5 A9 E
==或!=关系的比较,比较的对象可以是标量或者同长度的Series(或list)。(无序时也可以比较)
: `, ]* g# W) V6 [>,>=,<,<=四类大小关系的比较,比较的对象和第一种类似,但是所有参与比较的元素必须属于原序列的categories,同时要和原序列具有相同的索引。
( U, R s3 k" B) m% s# gres1 = df.Grade == 'Sophomore'
0 i+ ]" Q, H$ D% x0 d3 G2 E2 x8 a6 v6 G1 y+ R& E9 n1 J
res1.head()
' E" \2 p9 z- N' kOut[31]:
6 o5 ?, j2 |" Q4 }0 False
' X/ j0 A R G" f i1 False5 r* i0 d. S( _0 ^
2 False9 ^ w `& V5 |# H0 r5 |5 y+ a/ O- T
3 True
5 P& h W" i1 W; P; Z9 o4 True
5 w5 ]. X8 ?& P3 _5 X, dName: Grade, dtype: bool5 C: ?) f- g/ }% ?
& O3 V% y& R# e+ bres2 = df.Grade == ['PhD']*df.shape[0]
' F# ?: A+ Q; Q8 R+ G5 Y- e/ |) _, m) q- D/ g
res2.head()
8 _1 Y( _8 [: [7 @' E5 mOut[33]:
/ b- a* V) F9 ]0 O/ ^0 False
& L3 o: ^2 Y! j. K$ {# k7 y# o1 False, P/ }# N3 b9 ?: |% Q( i
2 False
+ o$ Z, j0 M; k: n2 m! z, r h3 False
I4 X* H+ }/ l, I$ d# p' S# C4 False6 I" `$ [ m; X9 }) E
Name: Grade, dtype: bool" t, L% r( J+ L% u+ j3 u
$ W }1 I ?1 j8 h( H" {) B0 A& P
res3 = df.Grade <= 'Sophomore'
* \2 R$ h/ K0 l1 q
& X+ W" }" d4 z: C' s, nres3.head()+ O+ `6 B" W5 q$ o& \
Out[35]: 3 X3 f8 x* Z, v o7 J
0 True
4 m* z4 q' c0 ]1 True$ B' L E3 N0 I( M/ q6 Y4 n2 }
2 False
- j f5 O2 y1 h: u3 True
; Q! y" c7 I/ d; E4 True
( m& o/ H2 Q( v0 c5 P- qName: Grade, dtype: bool- e1 u% w" K3 }" }
2 [* i, B/ o9 n: j; M3 U8 ^
# sample(frac=1)表示将序列随机打乱。打乱之后索引也是乱序的,直接比较会出错,必须重置索引。
2 k. J- e3 t& z4 O. g) [; x4 d+ qres4 = df.Grade <= df.Grade.sample(frac=1).reset_index(drop=True)
7 E) k8 m) j! W Z! `+ a* \2 r" ~( F, f, @$ F2 e
res4.head()
) e3 h: I, L- pOut[37]: 5 U8 @" O4 m/ x& V7 @" ]
0 True
2 ~8 G9 Q$ ?6 e/ O9 `1 True
4 Z t, l7 |5 m" X5 }' p4 c2 False
: w9 Z6 d9 k) _' e3 True) P& p% P8 d+ C$ ^# u
4 True- j* h( R# `5 R
Name: Grade, dtype: bool. Y, D* g1 V2 u- d; k9 z4 _
q A& ?' o3 N4 x% S' l m1 b1" I, O' K9 H" c4 M: g
2
7 G( d, y1 R% u9 M* Y# K3
4 r; t! m9 X& J+ e( l+ @9 B: d4, y4 x0 {1 z/ X
5
! X5 @- A1 `# q# |. B6
% }0 X5 B" {% j4 K; t5 ]7% k* S) q" c3 z$ @
8
* Q+ H5 `6 l+ u$ s/ W9
7 G& s- i/ o2 K8 j+ I10
) j! F4 C$ G! {5 U11
4 [3 V1 ]8 N. W12' ? r4 U& E8 D" a1 I
13
, T) v4 E1 r* c I14
! n# T) j* d+ _: d5 x15& H, F; X$ p& I& m
16$ f; ?8 H" b# x/ `. X
17
) V0 O% j! L, j' M7 Q6 a18
- ~8 F7 D, @' `. E$ B192 L2 X- C7 M+ \+ E$ A$ I
20
# r2 e$ [3 r8 g0 m9 e6 e0 S4 @3 M5 V6 z21. O3 j$ v: O* [: {1 p) h7 R/ U
22
4 J9 K$ a+ T" v" \, j2 t8 J23
! F( X* H6 Q8 Q* P: _24, ~6 w% y. t, z6 Q, h
25! E4 z. V3 \# x0 v" _
26
/ F# ^- g4 ?/ N: a: H27
* F( f1 n0 Z& U, V1 P. o; q3 X28* t _& }$ _9 k& o; h* @* B
29' S6 l6 l" S3 ^- ?. Z) ~
30$ W7 Y6 o* s/ f) k. t
31" j& Z |" W. o# m2 b0 I1 W' o
326 F$ s& v V( Q; Q5 a
333 m' A6 g, O) s1 [7 Q" N6 {
34
7 d# l: c: [4 O" X5 A0 ?7 r35
; q" X$ e6 S5 S# }36$ ~$ D' L/ M- f8 O5 d. _
37! v, L, T( D% e: F$ y
38
( B; m$ n |# R, d# k* E# j39 v. x1 I8 Q, q# j( f
40
3 N8 n% G/ c3 Q/ P+ C41
1 `8 `# M$ M$ S2 K1 `2 U42- Y0 b) G1 f; M4 r" o
43
, P% s) h) z8 i6 }! a) `- K- y44
7 ~. |! A+ J% \0 `, t7 B, L. X1 p! ~9.3 区间类别: t4 D8 }$ I, ? P
9.3.1 利用cut和qcut进行区间构造: p! W" ? y! K b
区间是一种特殊的类别,在实际数据分析中,区间序列往往是通过cut和qcut方法进行构造的,这两个函数能够把原序列的数值特征进行装箱,即用区间位置来代替原来的具体数值。
% q5 N- _, Z9 e
( O) v! {; g8 K9 C/ v/ ]cut函数常用参数有:
- S$ {! g8 A- a& V: hbins:最重要的参数。/ w, v9 _. I6 T+ Y h; v
如果传入整数n,则表示把整个传入数组按照最大和最小值等间距地分为n段。默认right=True,即区间是左开右闭,需要在调整时把最小值包含进去。(在pandas中的解决方案是在值最小的区间左端点再减去0.001*(max-min)。)
! a; I$ X- \1 Y/ n9 ?! I& C也可以传入列表,表示按指定区间分割点分割。! X7 s3 N0 l0 R, h' R' i
如果对序列[1,2]划分为2个箱子时,第一个箱子的范围(0.999,1.5],第二个箱子的范围是(1.5,2]。/ l1 ]4 ?+ C# b% C |2 @
如果需要指定区间为左闭右开,需要把right参数设置为False,相应的区间调整方法是在值最大的区间右端点再加上0.001*(max-min)。; x& W. \; A0 H' W- K
/ ]8 \+ O# W' }1 I1 p9 F, P- M
s = pd.Series([1,2])6 Q6 J; D" b& e+ e
# bin传入整数
" I% @ b, c, D. B, \" d
3 _0 ?1 N# q1 X7 Q( ipd.cut(s, bins=2)% k% D+ p `1 n6 _
Out[39]: * p3 h Z9 t1 S! d! @
0 (0.999, 1.5]
- P. k7 v+ I& N" h: |+ Z# T9 s/ i9 {1 (1.5, 2.0]/ z1 o& Q0 }5 r2 V& i# }4 O' t
dtype: category
- U4 C: p: N) l0 u, ZCategories (2, interval[float64]): [(0.999, 1.5] < (1.5, 2.0]]1 H2 s& W3 K4 @" G! y
. G* _3 Y, z" E7 w7 }9 h7 Q
pd.cut(s, bins=2, right=False)( N* I# Z% u3 d. n6 {3 O
Out[40]: $ o" j' q0 R4 e0 S3 h& s
0 [1.0, 1.5)
& D* b: a3 s- b8 O5 I1 [1.5, 2.001)! ~/ r' Q) U' s: Y- F
dtype: category) K; C l9 Z; R& r3 h r$ E7 k0 R
Categories (2, interval[float64]): [[1.0, 1.5) < [1.5, 2.001)]: i8 ]- o# N$ G e, _0 D, D
4 D% F& E3 C! N1 I C
# g2 w% p# B n+ k* f' [2 _# bin传入分割点列表(使用`np.infty`可以表示无穷大):, W* o y# D; u0 j
pd.cut(s, bins=[-np.infty, 1.2, 1.8, 2.2, np.infty])4 d( B; U, v1 V( M3 ]
Out[41]: ! A, s3 C& I5 ]
0 (-inf, 1.2]
3 e9 J7 K+ R' E! `7 X1 (1.8, 2.2]: {1 u7 K2 H2 G# l3 H
dtype: category: W% b4 k) T( J: u( \
Categories (4, interval[float64]): [(-inf, 1.2] < (1.2, 1.8] < (1.8, 2.2] < (2.2, inf]]. R, m, S' m+ b4 V1 W/ v& f* \5 v" A. H
i8 c3 q$ S7 r( e: B n8 r1
/ D+ t- V9 D3 c) h" d9 T& V. g2/ k) [: a$ I; f7 U3 D# e" s
3
% ]2 Q- s: L: z4
. a* Y, N: G4 E7 k5 I2 Z59 l; F! h2 e Q9 o
6 ^6 G2 o; e9 ~- t& T! Y
75 A1 J* E5 G! p. p& ^8 j
8
* m! R: Y, n* z8 k) ?# T2 \9( e8 P1 z- s2 Z) A1 q% d* U* F
10* S: j8 v E6 y) D' x
11
) j, T) V; h' H12# [3 S% ?3 H4 @: d$ b+ k- j
13& K( Z9 a' ` p _ |/ Y
14* r8 b }; P4 V
15( y4 }8 ?+ C5 c! ]
160 ^/ ?. m3 g3 V2 O/ s9 S8 q
17% a; R8 N/ w* d6 C- F* K
18
" H# @- S8 ^) j6 B' q19# m- ]9 R. ?2 S( Q; f& P% a L) \" L
20* O: C! y# g% C4 Z0 Y
21" k: N, Y1 E9 p, U
22
' _1 C& l% ~' Q( k! v234 ]7 B; O# l7 o* j
240 P! ~. N( i8 k7 r# d* t5 M, r
25
' B+ m# y, x+ @3 j6 [# H4 jlabels:区间的名字! w; c4 E* ^2 d; e
retbins:是否返回分割点(默认不返回)
; d9 M% m0 Y. n- M& d默认retbins=Flase时,返回每个元素所属区间的列表
! Z( }7 B2 A: D1 h' yretbins=True时,返回的是元组,两个元素分别是元素所属区间和分割点。所属区间可再次用索引取值* h2 ^# z- K3 d
$ K/ x. N Y* @0 i' \. is = df.Weight0 S# h$ f! N6 G6 H) x+ F
res = pd.cut(s, bins=3, labels=['small', 'mid','big'],retbins=True)
+ E/ ~5 ]% F8 f2 N: u' Wres[0][:2]
& L C) [5 e9 @; I6 Z
6 t- ?# X/ b/ q# bOut[44]: / O I8 a+ ^! T, H- G$ {
0 small
. [: u) ~. c- \4 N, x1 big
|/ S% X" \# d6 Y1 u, Cdtype: category
) E. h- }2 D. I; i5 O3 O* |Categories (2, object): ['small' < 'big']
. J' h' l6 @" p; V+ Z/ _/ @( x8 L4 E+ i/ u6 \1 B4 y8 `$ s
res[1] # 该元素为返回的分割点
4 ?6 {7 T- D" G, n. @Out[45]: array([0.999, 1.5 , 2. ])" m6 L# R% A3 U: D l0 T
1
1 I+ Y5 c4 y' Y% Q0 S* b2
. {1 G# t! ?5 |/ s4 C34 C# \- K+ P8 I* e; h: Y
4
: z* g4 n! J& d4 g( B# O7 k( ^5
! }$ U8 }5 N6 O9 R60 I% {6 L$ G5 a
7/ C, R3 g4 J8 ?" @7 d% k
8
4 K0 B. L1 H* P- V, W8 X9
- ~* ~# E( q$ n( }0 ]/ f10
8 g: ^# a4 T: g' q11) h! P% k- W! ^- Y9 I% h
12
/ N; c H/ U! Cqcut函数。其用法cut几乎没有差别,只是把bins参数变成q参数(quantile)。
' q8 y6 Q6 u1 B* w/ Z6 k- Xq为整数n时,指按照n等分位数把数据分箱
8 [ x4 y( H; i! N5 t! A, ]q为浮点列表时,表示相应的分位数分割点。
8 r3 @# ?3 `5 q% `s = df.Weight/ c( V/ @- G/ K% U5 Z
9 y- q- R( o( w7 _7 o5 l) {: dpd.qcut(s, q=3).head()/ d6 d# n* ?5 _8 v
Out[47]: ' r$ {3 G2 W0 a7 k: K; e4 q& \
0 (33.999, 48.0]. A$ e: ~- x3 |7 b9 F7 L- D, m
1 (55.0, 89.0]
1 ^" _5 R' @) v0 K9 U- \. K2 (55.0, 89.0]; g; n/ L0 m$ ^' B
3 (33.999, 48.0]/ O# l; I* D' C! Y) }* n% f( B
4 (55.0, 89.0]5 [& B* W a, a' M8 o
Name: Weight, dtype: category+ Z/ T9 J: v/ c2 S, @: w! w
Categories (3, interval[float64]): [(33.999, 48.0] < (48.0, 55.0] < (55.0, 89.0]]
5 l; `$ v- m, Y" y# K1 F+ y
+ N4 s* b% Q0 fpd.qcut(s, q=[0,0.2,0.8,1]).head()2 J$ h9 Q% u8 s; M: R7 R
Out[48]: ( @; w6 V- ` `+ o& R/ B% D
0 (44.0, 69.4]( {0 W4 P) z1 I8 y* j2 k! j
1 (69.4, 89.0]
+ P1 m3 _% Y; U, [- Z. ]0 r! D2 (69.4, 89.0]
6 y9 B1 m# |. ?/ d% Q2 N) x5 t3 (33.999, 44.0]6 w& T5 s$ s& Z2 [
4 (69.4, 89.0]
* W0 z) c) w0 ?Name: Weight, dtype: category o, R. s& Q4 S4 D0 B$ C6 U
Categories (3, interval[float64]): [(33.999, 44.0] < (44.0, 69.4] < (69.4, 89.0]]
% D/ X" K: H9 i" V Z& H: Q5 b6 @
1" K" D3 Z$ _: V8 h' z" p
2, E- I' v. s! t1 g9 [
3! u8 I6 U1 w' T; T \/ {. n
4 f) f! K/ V" ]8 `5 j/ {
5: }% X: }2 Q9 @2 R4 I; ~
63 W# L& {" h) Z1 p8 n3 a
7
1 T+ n% D) z! l8 b3 X# w8
" b) k7 |, p2 v7 k/ t0 g( M/ B0 V% q9
% e5 L9 A/ F9 K. J10
, [6 e+ M5 {" G7 |( S11& Y* ?" k, G: `6 K: S8 G" Q
124 Y8 o% A4 E) ^# O8 H
13
2 @; t+ a2 J% K+ }5 ]5 E14, C& c; D* h; ]4 X/ r: b T
15 H! R2 u# M! E
16" X& x6 u$ N: s' ~5 W I9 M- L; f
17( L9 @0 N& T. k3 P0 J
18
6 O" ~% ^6 [. o u) n19
/ F( G/ l9 O* ?7 I+ M20
' R9 N. }8 W7 Y, `; }7 f21, _- V( `: y- k4 U! v' U4 k2 s: b/ R
9.3.2 一般区间的构造
: A- l! [* ]: x, H u) z) d pandas的单个区间用Interval表示,对于某一个具体的区间而言,其具备三个要素,即左端点、右端点和端点的开闭状态。
5 |/ Q& J+ b: \6 s2 G0 U* p
u8 ]2 Q. z w/ Z' Q1 x# a& V开闭状态:包含四种,即right(左开右闭), left(左闭右开), both(两边都闭), neither(两边都开)。
u6 m2 p7 E) H: f. umy_interval = pd.Interval(0, 1, 'right')
- w4 F9 ?5 v' U& ^2 H8 U1 a$ i* i C
my_interval! z8 P( e# A1 R# q
Out[50]: Interval(0, 1, closed='right')
$ A, Q& N& Z5 Z ^( [. ?1
1 T; Q8 H' V! A2 e/ _+ D' q2. l3 ~" I( y; \" [: O
3
8 Q* Q' a$ x8 j; i9 h5 S4
8 u! Y* z" L; w! a; y$ Z$ ~区间属性:包含left,mid,right,length,closed,,分别表示左中右端点、长度和开闭状态。
/ E8 F# H& T7 K使用in可以判断元素是否属于区间
+ M& D* g* h$ T& z3 k' I* B, g用overlaps可以判断两个区间是否有交集:
+ d0 m) J! r* h1 J' O" `0.5 in my_interval' V) D4 q( E) @# b
) N2 M: d; q7 B$ A" K4 ~" l5 V
True: d# C6 J9 ~8 p
1
! R# l, ~! L# j2 T' m9 c2 D2
7 I1 e T/ T R# G+ I3 F s3
x0 f8 q- P8 n" a6 C4 qmy_interval_2 = pd.Interval(0.5, 1.5, 'left')6 [* ^' O2 [: Y2 A$ R) ~* o
my_interval.overlaps(my_interval_2)' {8 v [% a" n
8 f4 s9 M* w- d' P" \
True) n+ w, x2 y' O0 E$ j
1
( M! S+ S2 y0 y) o; w0 _' X23 t E# l; v: y
3
/ A- S9 g, I" T) t4
' K+ L' c% S8 c, g, i3 t2 B pd.IntervalIndex对象有四类方法生成,分别是from_breaks, from_arrays, from_tuples, interval_range,它们分别应用于不同的情况:
! N7 c! s4 I9 \* N6 W9 P! X+ z# f, U* L" k0 e9 y5 Y0 ~
from_breaks:类似于cut或qcut函数,只不过后两个是通过计算得到的分割点,而前者是直接传入自定义的分割点:
; h. z2 Q7 @, ~2 y4 b: `% n. Ipd.IntervalIndex.from_breaks([1,3,6,10], closed='both')
( j5 Z J4 ]& h* A" b- }' K/ W/ ~; W# K, B* \4 U5 ^
IntervalIndex([[1, 3], [3, 6], [6, 10]], s) D7 I( g8 B! q6 [( r
closed='both',
, Z2 a6 d1 |$ N- s: R) M dtype='interval[int64]')
7 k/ C% u- F7 h7 ^3 f0 v1
2 u+ w" `, {' f2 g$ Q0 Y2
/ |7 E9 ^+ P# r* I- N' H: p3
6 P/ {+ d( {$ ?2 ?5 v. R4$ x' E. y, ?2 f; Z! c
5: B# M+ C/ r7 I) M
from_arrays:分别传入左端点和右端点的列表,适用于有交集并且知道起点和终点的情况:
% R$ C/ y! \7 K& L. h* Cpd.IntervalIndex.from_arrays(left = [1,3,6,10], right = [5,4,9,11], closed = 'neither')8 Y8 n' {3 N/ {" }2 b
- U0 ?7 X) g% F* uIntervalIndex([(1, 5), (3, 4), (6, 9), (10, 11)],- u; G9 [% }. B) d) A" L- l
closed='neither',
. V3 @! c, H: v& W dtype='interval[int64]')
) L V4 L) W7 p- Z9 e/ p1
& M8 [* R4 C# _& o/ ?' w2$ `4 X- O: a. v: }8 a: m+ |
3* Q0 P) A# t% u1 r
47 W; L. Z. s8 A( X$ m( P
57 D& u; b& ~1 K, c
from_tuples:传入起点和终点元组构成的列表:
* ]1 N. c+ `% zpd.IntervalIndex.from_tuples([(1,5),(3,4),(6,9),(10,11)], closed='neither')3 w( O4 C) \2 F
$ T* ~; @6 t0 S3 X% k- mIntervalIndex([(1, 5), (3, 4), (6, 9), (10, 11)],1 }; k3 i1 N/ b
closed='neither',
6 E. [+ I) ]) G+ @+ A5 I dtype='interval[int64]')2 u' x1 o' O: j* w& o/ \
1
6 Y! @* Z/ R' {# j' X21 C! V" V6 g/ V
3
" @; N( U5 S4 ~, ?# Z9 N! ]: ]- j42 o( X+ q4 F% Q$ O1 y( P
5
% p& ~& i+ d- |: b; u& K9 r0 xinterval_range:生成等差区间。其参数有四个:start, end, periods, freq。分别表示等差区间的起点、终点、区间个数和区间长度。其中三个量确定的情况下,剩下一个量就确定了,从而就能构造出相应的区间:- ?2 n' ]3 Z) Z ~: ~
pd.interval_range(start=1,end=5,periods=8) # 启起点终点和区间个数0 i2 @) t; q" j" g- Q: ^; R F
Out[57]:
+ v$ z# ?* `3 C3 XIntervalIndex([(1.0, 1.5], (1.5, 2.0], (2.0, 2.5], (2.5, 3.0], (3.0, 3.5], (3.5, 4.0], (4.0, 4.5], (4.5, 5.0]],
2 V7 u. H4 D0 o4 L. \' W. t6 T* B2 B closed='right',: Q0 t2 `9 @2 ]5 s4 G
dtype='interval[float64]')
/ B9 v9 B: L% D; u# L0 T9 z" P
. s; t5 m8 m4 X& L9 |% G' gpd.interval_range(end=5,periods=8,freq=0.5) # 启起点终点和区间长度
2 X8 s6 _1 e YOut[58]: " W: c! L. W4 D+ g$ K+ q
IntervalIndex([(1.0, 1.5], (1.5, 2.0], (2.0, 2.5], (2.5, 3.0], (3.0, 3.5], (3.5, 4.0], (4.0, 4.5], (4.5, 5.0]],+ f. p/ d* k9 L
closed='right',
8 d3 `! c8 S6 X# }% W dtype='interval[float64]')
7 X4 _; k! v- r3 c) X$ O% E1
5 o& u; N- j& j1 W+ H2
5 R, u6 B7 j8 S9 L% a# ?7 W3
7 C: Z" S0 n* k* L) d3 f4
, @! Z5 z* d1 ^: @2 y+ h4 I5
$ m& V6 m1 _, `2 U1 ]6 j6
8 C8 y. G7 P9 ^# z. e* i( z4 U7" {0 R- p& p* W4 y# ]( [+ l
8
$ f" ~& }4 P) n: p6 {9
) Y1 W' P. l z# a; r4 U$ J10
' n" x5 l2 v3 @3 P h6 |/ ^4 B11# a7 o }3 e0 j+ O! k9 R
【练一练】
: }' g. g7 J- m+ p/ e 无论是interval_range还是下一章时间序列中的date_range都是给定了等差序列中四要素中的三个,从而确定整个序列。请回顾等差数列中的首项、末项、项数和公差的联系,写出interval_range中四个参数之间的恒等关系。, z+ a' P: H5 ^4 @
# s- E7 o$ ]9 G, `# n) g3 ]1 N
除此之外,如果直接使用pd.IntervalIndex([...], closed=...),把Interval类型的列表组成传入其中转为区间索引,那么所有的区间会被强制转为指定的closed类型,因为pd.IntervalIndex只允许存放同一种开闭区间的Interval对象。
/ x5 h/ r2 G5 T# B! ?
9 I- I+ n* |) D2 [* imy_interval
$ `5 a8 I! N: a- E# bOut[59]: Interval(0, 1, closed='right')
0 r- ?' b! O# z2 P9 ^) h+ ?# O+ z2 l- q
my_interval_2
9 h+ R6 P/ U7 e a, W4 wOut[60]: Interval(0.5, 1.5, closed='left')
% N$ z8 _: V9 e9 k0 J0 i% |9 X; I2 f# b. ~
pd.IntervalIndex([my_interval, my_interval_2], closed='left')# [2 J a2 K# ]% H# m3 f3 j
Out[61]: / h7 u& n0 A3 r
IntervalIndex([[0.0, 1.0), [0.5, 1.5)],
, d/ u# F- R1 e a' k closed='left',5 O- [" X8 }' q9 R
dtype='interval[float64]')& {* N, l. @. @4 I( A/ Q4 v2 g+ k
1& N+ G( j5 k Z9 |. |
26 P$ l! Y/ b4 s2 _5 H' q
33 d3 W5 A7 T$ X# B, T) x* P
4% q; E; F: r1 V% n- Z8 q; a
5
; L% F0 W9 L% S% K. m" U, W6
: x9 a+ e/ z0 y6 a! Y: d/ p$ Z8 Y7$ h' x5 x1 g7 o# p. B# J
8
P" l2 q) k9 C& c90 I9 m/ |7 P& w7 X0 \ E
10 I h- X- Q8 j, N5 H
11
: e) P4 h G9 }# f- y9.3.3 区间的属性与方法- b1 |* t) Y f
IntervalIndex上也定义了一些有用的属性和方法。同时,如果想要具体利用cut或者qcut的结果进行分析,那么需要先将其转为该种索引类型:
* ~: R; i2 w9 S) F6 K) f3 `( |2 ?* e) Y9 f) D6 J, \, H, P2 _
s=df.Weight9 [ O+ o7 q! ?; {+ r
id_interval = pd.IntervalIndex(pd.cut(s, 3)) # 返回的是每个元素所属区间,用具体数值(x,y]表示* Z' [3 _" p" x9 t( ~9 Q
id_interval[:3]/ c6 H9 Z% ]% M; d+ C: y" i
* M8 K: ]0 V5 k. p8 NIntervalIndex([(33.945, 52.333], (52.333, 70.667], (70.667, 89.0]],/ I) ]# M- T" l9 ^6 b. x b
closed='right',
! w& o |: y! x5 ~9 @" O9 k% D name='Weight',
3 E1 G5 e4 h9 U' B1 B dtype='interval[float64]')% G/ w) Q' N! b: i
1
7 |0 o7 v2 l. n( e* _. |" h25 Z! K7 L8 F" l1 C# T C6 q
3
- ~; ~; R2 G& t3 h47 l$ O V; T- q. B& C5 ?
5- b. N/ x9 U& V1 F- z5 j
6
: {- ~3 K& b; L7# x! v: x& u$ F
8
+ `" K: l1 O7 u& h1 G$ |$ U与单个Interval类型相似,IntervalIndex有若干常用属性:left, right, mid, length,分别表示左右端点、两 点均值和区间长度。
- r* W9 T5 ?( `) T. @( [id_demo = id_interval[:5] # 选出前5个展示
. I0 Y0 e- B$ r( F( S, W$ R
& z0 z7 D2 |; s' n, I9 cid_demo
- @6 z( L* M% j; m9 LOut[64]:
0 `; E( Y- E d2 L" a7 nIntervalIndex([(33.945, 52.333], (52.333, 70.667], (70.667, 89.0], (33.945, 52.333], (70.667, 89.0]],! ~- o4 x/ V4 D% L- X
closed='right',
) V! ^+ \8 g6 r8 P" p7 U name='Weight', N/ e2 d) e! I
dtype='interval[float64]')% J! B4 b. V$ J% b- u
: L4 \/ H K0 G3 ~. u5 y8 iid_demo.left # 获取这五个区间的左端点 J V& ^! H+ A
Out[65]: Float64Index([33.945, 52.333, 70.667, 33.945, 70.667], dtype='float64')
4 C. w) d* D& F+ u0 u+ [7 R) S* W% b8 D
id_demo.right # 获取这五个区间的右端点5 ?3 @% O' D4 y4 J8 p
Out[66]: Float64Index([52.333, 70.667, 89.0, 52.333, 89.0], dtype='float64')9 o) N& r5 v, }; n* E! K& |
4 W9 h+ {: C+ @- s4 L3 i; w! Wid_demo.mid
( D" Z5 {; J* I1 oOut[67]: Float64Index([43.138999999999996, 61.5, 79.8335, 43.138999999999996, 79.8335], dtype='float64')
Z; \/ y9 d g: I
4 j9 Q& X( G2 T0 T) X3 ?6 M6 ?id_demo.length F3 j4 P) _. i6 g
Out[68]: & e( g3 X+ l# ^ l6 j7 g" z$ B
Float64Index([18.387999999999998, 18.334000000000003, 18.333,8 _, L4 M" J8 I: d. B: e
18.387999999999998, 18.333],
7 U: \2 q3 j" N0 p2 J! U dtype='float64')& q' H. h/ w- ?: |3 ]
& F* h' y4 _5 j2 N17 x# b0 I" D- q8 `. T8 B
28 E5 _/ \! P q. R' h' m
3' G' T: J7 d: N i
4
. F; U/ x( R- d2 c2 V7 O5 I6 q2 C/ V, ]
66 I5 Z! Y; g& E! l/ D( `; R
76 O+ i! `, [, N% t |
8
5 R7 r) `" Y4 X- ^5 a7 q* g& L8 X95 P; t3 k, C! _ P; s* M
10
: O' ]1 S, O0 m11
6 J4 \8 e- ~# B C( d7 c123 K; P2 `& g$ W2 U
13
# k; X" {# v5 N* y8 C14
g, |4 {4 }5 e. U! x+ y' R15
\' s6 i# }( u/ _16
% T$ I/ |! }6 k( N' b1 e7 ^17/ M+ @, q4 [ f; d+ z4 T( O
18
' N4 }7 x" m2 P: Q1 c' [1 J; Q3 b19
1 H" s. f/ e3 u6 Y, ]% a20
6 f _3 }# E' E& Z" W. S. e; `/ y217 O6 D1 x: g" i& m# J
22# [! l9 K( A6 A/ \' O4 S' ~
23; P, X6 `3 m8 T6 m) M+ K
IntervalIndex还有两个常用方法:
- u* s+ x# a- a3 ]contains:逐个判断每个区间是否包含某元素* h% Z- F4 Q+ d9 A: `1 B
overlaps:是否和一个pd.Interval对象有交集。
& M, \9 L. k" j- l) kid_demo.contains(50)4 x |2 ^3 {- }
Out[69]: array([ True, False, False, True, False])2 u/ [4 Y8 e6 |
, b0 y8 Q& X$ n
id_demo.overlaps(pd.Interval(40,60))
* q8 ^4 Q4 E0 n, I. ?7 YOut[70]: array([ True, True, False, True, False])5 S, [! D9 T8 }: k
1
@/ g/ c" x s8 }+ Q' T' O$ e2 Q6 ^9 w0 E! X# ]( g
3 A0 h% a6 q) t8 `. ^! o
42 @/ G- B; S! K) ^# Y. \" z
5+ S V9 g9 o" f; B4 N
9.4 练习
, D; r; W; _) S* v- ^Ex1: 统计未出现的类别% q: ~# B! O0 m9 k4 @" |
在第五章中介绍了crosstab函数,在默认参数下它能够对两个列的组合出现的频数进行统计汇总:
( T9 q" u+ q1 z W4 U! \0 `# q/ G- I6 I2 l
df = pd.DataFrame({'A':['a','b','c','a'], 'B':['cat','cat','dog','cat']})! K5 ]3 B7 I$ R; g3 o3 Y
pd.crosstab(df.A, df.B)
: H6 W( h# d+ o* j& p4 y9 b$ _6 b$ P! u' ^0 i
Out[72]: - e5 t* X0 I! d
B cat dog
( h% h- ?3 p4 H, EA ( I* z$ h, Q- F9 u8 K2 d; w& r% w
a 2 0
2 M5 P3 [6 h& S+ i$ s. |& {b 1 06 x' B. ?5 d7 S- H+ @
c 0 1
6 c) }1 A3 P/ |# Z16 y2 l3 b; T0 F+ M. Q$ A# l5 H( M' H# A
2; K' C P) s$ v0 c7 Y
3
: M& D. g8 n8 q1 X40 L9 T: S \" N! B4 M, s
5
9 @, f u( ?! w1 u @, i2 S6
# r+ h3 v/ H! _; q7
- j0 K6 Y% I8 {7 e0 y/ {) V82 M3 o% t" A4 ]; e- X2 R, y
9
% |1 P# v1 |( V9 m: i1 n 但事实上有些列存储的是分类变量,列中并不一定包含所有的类别,此时如果想要对这些未出现的类别在crosstab结果中也进行汇总,则可以指定dropna参数为False:; A) D3 `6 X! Z j( u; o+ N) ^
) {/ C% d9 d ~* odf.B = df.B.astype('category').cat.add_categories('sheep')
, `1 z) E3 Y1 a( p5 ^pd.crosstab(df.A, df.B, dropna=False)7 G8 h1 a1 Q( q2 h; f- V) J6 C0 Q
4 q! h+ C6 B$ ^# i# ~ v( `: r
Out[74]:
. m5 h( T9 v# }# V8 J8 e* B' LB cat dog sheep; J, {: P9 v4 m1 o6 J
A
7 O. D* o( r1 C- l$ ^+ `+ C$ Da 2 0 06 O( a4 ^" p$ ?2 _) M7 g& x- ~4 w
b 1 0 0
5 W. W6 ?) Q1 u# @/ U3 ?5 r. {. t3 Yc 0 1 0# @0 @3 @# J* t
19 j- q7 ]! X5 ]) T1 |
2$ n! a% T8 J* s. S& Z
3- T3 ~* S+ ]! O
4
& A2 B4 B( ]$ e9 P53 p& h3 _1 f- ]: D0 s8 `& e
6
, G' o" H- J. m0 f7* B4 ?0 B- v1 g2 G/ v7 ]( u+ F
8
5 c% U2 ^, @8 F+ \. z9
$ S. V# f& s. f请实现一个带有dropna参数的my_crosstab函数来完成上面的功能。 d& n' K7 W7 H
* l8 H0 \% R+ k" y2 t+ n, y* D+ qEx2: 钻石数据集3 D" \; \+ M8 B' v2 [
现有一份关于钻石的数据集,其中carat, cut, clarity, price分别表示克拉重量、切割质量、纯净度和价格,样例如下:
: b/ ~5 s- f# f; ~* ^# Z) z3 \5 S4 V8 L5 k( s$ [: j* D* U+ R
df = pd.read_csv('../data/diamonds.csv') 9 u# _# s! @6 B8 ^! }
df.head(3)$ p9 k9 r5 R* [( z0 O
0 ^# o2 N! `. J5 q. l0 U
Out[76]: : b4 X& C- Z* o# \" E8 `
carat cut clarity price
1 F7 x% Q8 b+ I9 H ]0 0.23 Ideal SI2 326; I" }# ~5 G$ f: |$ u8 F1 Y) \
1 0.21 Premium SI1 326
% M$ Y; w# Q/ w+ Y$ F2 0.23 Good VS1 327
# ?: f. Q; w* \2 C7 F3 {+ F% i12 F n5 m* w# a# W6 N
2
; Y& g/ }/ H7 g; d, ]+ N' \& H39 i7 G$ w! I" Q$ l
4; e4 w2 \2 v% U
56 U( }0 m5 F# w, Q ]& h/ v* f- ^
6
! J6 y# e* H8 _$ A4 O7. k- m' J- v M% X3 ^/ H2 a5 A* _
8
4 O8 a& T# q+ I: `3 F. ]分别对df.cut在object类型和category类型下使用nunique函数,并比较它们的性能。
# q: F9 ~ h9 G1 n* m8 F+ ^钻石的切割质量可以分为五个等级,由次到好分别是Fair, Good, Very Good, Premium, Ideal,纯净度有八个等级,由次到好分别是I1, SI2, SI1, VS2, VS1, VVS2, VVS1, IF,请对切割质量按照由好到次的顺序排序,相同切割质量的钻石,按照纯净度进行由次到好的排序。, F0 D1 a8 W" b1 u3 P% N
分别采用两种不同的方法,把cut, clarity这两列按照由好到次的顺序,映射到从0到n-1的整数,其中n表示类别的个数。
$ b1 |) Y: u% @% `对每克拉的价格分别按照分位数(q=[0.2, 0.4, 0.6, 0.8])与[1000, 3500, 5500, 18000]割点进行分箱得到五个类别Very Low, Low, Mid, High, Very High,并把按这两种分箱方法得到的category序列依次添加到原表中。
5 ^% F" P% ^$ u第4问中按照整数分箱得到的序列中,是否出现了所有的类别?如果存在没有出现的类别请把该类别删除。
, C/ b4 r) y# x$ w- v/ \8 [6 M/ `对第4问中按照分位数分箱得到的序列,求每个样本对应所在区间的左右端点值和长度。7 o, m+ C3 Y# c7 s" |7 u' {
先看看数据结构:1 |- u# n& I- A
1 c! R, N5 }' ^1 a
df.info()
' Y& Y9 a7 O! M" U o1 f# IData columns (total 4 columns):
/ ^9 W$ N6 r& _+ V # Column Non-Null Count Dtype
) U9 ]6 ^ v* P" |: z--- ------ -------------- -----
2 }8 O* P5 H0 h" k 0 carat 53940 non-null float64+ M) `2 o8 R5 @ Z3 p' i( ?
1 cut 53940 non-null object
7 y4 ^" d$ A; @: W$ W9 `# b/ _2 ^) n* z 2 clarity 53940 non-null object
6 x. `4 ?& p U, m( c8 N l" a 3 price 53940 non-null int64
# F4 g% K+ L9 @dtypes: float64(1), int64(1), object(2)# b; f+ V0 h" e" w9 H5 @' a
1: }% n {' v8 h$ C8 G" O- S
2
( [6 t! p7 v# J; o5 _3
! C1 x8 f2 E/ T8 E" Q) _- k4* B( B9 O, H* ~3 ]! R) E) Y
5
0 }1 s, E0 X) L2 z6 r: m/ z9 D6
1 F- P- y, Y P. t0 X6 j* D7
" y4 D- J7 k* U# e8 g0 G8 p8 l+ i) q6 B1 F; p7 B
93 p O" Y+ V) r+ f9 G, l
比较两种操作的性能
5 d* C5 ^" Q9 s9 U%time df.cut.unique()
5 J$ e" {. F% K0 {" H) `3 r) E# a' j
Wall time: 5.98 ms4 y m& K# ^2 W: D; D0 C( W+ I
array(['Ideal', 'Premium', 'Good', 'Very Good', 'Fair'], dtype=object)2 s8 ]; f$ i& \( f- o
1
2 X( E) M. X0 }- T A& y27 }+ ]: x. T$ J
3
: D" j; e0 ^% Y1 c- l4 D6 @4
* i7 p u2 ~$ I! y4 `! D2 l3 s& _%time df.cut.astype('category').unique()
9 ]) `8 X. k0 a4 B0 Z! n* r, w! ?6 f( F% C# X
Wall time: 8.01 ms # 转换类型加统计类别,一共8ms
2 y$ N5 E J& U7 n['Ideal', 'Premium', 'Good', 'Very Good', 'Fair']
* W5 m8 R& |6 i$ s, zCategories (5, object): ['Ideal', 'Premium', 'Good', 'Very Good', 'Fair']" \; n9 ?9 P" k
1
) x" Q9 r% U' D$ v3 F+ M2
G2 C' l$ z7 C( b9 `) `35 T9 F$ g ^9 q) O1 ^, m
4
% D, a/ Q4 z7 i54 J, e" B) e4 b0 Q, ^
df.cut=df.cut.astype('category')
. h. r3 Y9 y& B% m%time df.cut.unique() # 类别属性统计,2ms+ `5 d" _' P/ l. K' ^
N+ h3 U" ]$ G
Wall time: 2 ms) q; g: X7 l1 i* ?5 s' o( n- R
['Ideal', 'Premium', 'Good', 'Very Good', 'Fair']
* p$ w7 u( a- r8 U( Y) TCategories (5, object): ['Ideal', 'Premium', 'Good', 'Very Good', 'Fair']; ~! `, A: f2 l$ {; E8 b1 R
1# R2 Z _# G# i, K( e$ w6 f
2
5 N0 R$ V. t. u4 |3. }( U( @) x7 j- Y; r+ D1 E
4
k$ |6 Z8 ^1 ]0 D9 b! X5
( F! k& \9 ?. q# M$ U/ _! ^7 |6- U' o6 y- @8 a% m
对切割质量按照由好到次的顺序排序,相同切割质量的钻石,按照纯净度进行由次到好的排序。
4 R. H. s1 f7 J. @! M& |: r8 _1 Qls_cut=['Fair', 'Good', 'Very Good', 'Premium', 'Ideal']2 p: t( o" R, v7 s( _7 w4 V
ls_clarity=['I1','SI2', 'SI1', 'VS2', 'VS1', 'VVS2', 'VVS1', 'IF']- h' m' l) I/ K3 ?+ B, z
df.cut=df.cut.astype('category').cat.reorder_categories(ls_cut,ordered=True) # 转换后还是得进行替换# @7 R# ?3 o" p! _$ W/ |7 e8 }0 t
df.clarity=df.clarity.astype('category').cat.reorder_categories(ls_clarity,ordered=True)
- F# y4 Y: _, W. o5 J" F3 |- `/ [4 A3 a3 T
df.sort_values(['cut','clarity'],ascending=[False,True]).head(3)
* F L5 G1 w" @, U. M
$ U( p5 q% R, J2 P$ D carat cut clarity price
" a: e! O8 o @ k315 0.96 Ideal I1 2801! [- o. e/ R* x2 ?6 j( v
535 0.96 Ideal I1 2826) R8 `" x- X0 I% c8 z& |% h
551 0.97 Ideal I1 2830& v' g& {: H5 h7 l
1
3 I V: R n5 V0 Q: i25 X$ @& |. d+ L
38 i# Y0 B+ Z" @/ v
43 o% Q" d% z9 ]+ _
5* T4 e2 r% T( y$ [! y; @
6
3 p4 Q; O/ K; P7
2 Y. v1 P) v% ~' {8/ u8 x9 b; G: i" i; X7 W0 }* k- _
9
) r( E( [# D* o' Y10
" j' F( @# K G: j) P, ^& n11' |# B7 ?8 k, ^3 i0 t8 n
分别采用两种不同的方法,把 cut, clarity 这两列按照 由好到次 的顺序,映射到从0到n-1的整数,其中n表示类别的个数。
8 U1 t) d! x% ~. C6 [( b; |# 第一种是将类别重命名为整数
9 t+ Y j2 e- @1 z" I6 p- Zdict1=dict(zip(ls_cut,[x for x in range (4,-1,-1)]))
% q' F6 a) m/ E: i( V: Odict2=dict(zip(ls_clarity,[x for x in range (7,-1,-1)]))- W" }- ?: B. T6 }! t$ ?6 `# Z
- a& o& E" ], N( P$ O* ^# m
df.cut=df.cut.cat.rename_categories(dict1)
" o- g) o, }& s, s3 Bdf.clarity=df.clarity.cat.rename_categories(dict2)$ V2 H% I1 N% N" s( P
df.head(3)5 q+ c; d/ l# W1 C7 n- B
0 Q; `% a) e* w* C) D3 k
carat cut clarity price3 L+ r* i1 t$ G3 ?; v
0 0.23 0 6 326
9 q8 T2 e5 A6 R, n1 0.21 1 5 326
1 D7 Z( \0 Q) N8 P9 F2 0.23 3 3 327
( T( n, D s- { Y5 f3 ~' c1
3 ?2 w$ q$ h- V- G% U7 w! N1 g2
* m. n% c+ C$ b# S' e& U2 K B9 s3, G' ]) Q8 u" e
48 l6 ?! X$ [# y3 T' f9 p
5' j% e) |1 Q# {' @& U8 ~( ~
6
8 _) ]- T8 B( a7; X6 O! s& F, g* [+ k( x9 [, w
8
i# h. u, {" U, D D5 H8 `9
, r$ s$ l! t& A0 [$ B2 U+ h10
2 m- D" E6 A$ m; }; v$ G/ l+ M* {11
0 u; N/ K% K7 t. m& z& o" e: m7 b, q12
& M; ~+ v. s8 h( c4 v# 第二种应该是报错object属性,然后直接进行替换% r2 K6 e# ]( J- J2 _! i8 p% ~% ]& `
df = pd.read_csv('data/diamonds.csv')# M; L, Z/ k; a& u& Y5 p2 ^
for i,j in enumerate(ls_cut[::-1]):2 O* h( v# x! _; e8 M$ v* N
df.loc[df.cut==j,'cut']=i
0 u- B2 F2 i/ I9 f% Z. o+ f( |* u5 S [
for k,l in enumerate(ls_clarity[::-1]):
+ z( x" G: C( T" L df.loc[df.clarity==l,'clarity']=k* G9 W4 m/ S' Y' ^4 |" t' L. U$ ~
df.head(3)
5 G; h+ J2 ]$ d2 }: T
d. v$ P- Q2 t5 G- l carat cut clarity price$ a. e1 I; E9 q9 D8 e5 m
0 0.23 0 6 326/ z2 ]2 M) p6 ~& R8 B, G% l m
1 0.21 1 5 326* w" {! A1 s/ _6 A3 X u- y
2 0.23 3 3 327
8 d: v: ^. b+ b: W2 U9 {4 m1
( o/ Y. h' v% g. S: o, q2
# } Y A4 K( z9 F# P* B8 \3
) c" }) Z; |; R6 _/ V% z4! r5 p6 ? o9 }6 g' K: K! I
51 v$ w: A- z0 P1 j) |2 X) V8 Z" D
6. o( `5 [ b" j. g* b% `
7
' ]( c# K! s2 x- m! M8, f! D' f: g+ X% i! {7 h& u
9/ v7 i3 N0 [% r3 \7 @1 E
10" Y+ d0 A' S4 U: o8 u4 e! F
11; [0 v: |+ p! P$ b
128 x' H8 P6 E+ W$ p% F4 M1 {: u; Y' G
13& j' W9 U$ h# h. u7 E
对每克拉的价格分别按照分位数(q=[0.2, 0.4, 0.6, 0.8])与[1000, 3500, 5500, 18000]割点进行分箱得到五个类别Very Low, Low, Mid, High, Very High,并把按这两种分箱方法得到的category序列依次添加到原表中。
1 _. f6 Q# T1 \: J4 x# retbins=True返回的是元组,第一个才是要的序列,第二个元素是分割点0 J& d# A. N# q0 T: T" d
avg=df.price/df.carat0 c3 P; y* y: Y- {; Y7 R: y
: ^, v; c4 S( v' T+ F
df['price_quantile']=pd.qcut(avg, q=[0,0.2,0.4,0.6,0.8,1],
( d5 E; ~4 E# S0 k labels=['Very Low', 'Low', 'Mid', 'High', 'Very High'],retbins=True)[0]& t8 m9 A+ \/ j. A
% Z9 I. d" u* ?' Edf['price_list']=pd.cut(avg, bins=[-np.infty,1000, 3500, 5500, 18000,np.infty],
" { {! Q( Z+ b# ?7 _: }7 |! ~, ^ labels=['Very Low', 'Low', 'Mid', 'High', 'Very High'],retbins=True)[0]
6 Y" p5 `! v1 p; [8 G3 P: v1 `df.head()
( R( o4 G) J- U- ]+ u n# }5 ~- Y- r6 L% I3 G0 U
carat cut clarity price price_quantile price_list
) U4 M: t" k6 w/ b3 s0 0.23 0 6 326 Very Low Low
. l% L# T- G3 z- o# U0 T( s1 0.21 1 5 326 Very Low Low* _, D, |; A" S4 a: l0 ?; ?
2 0.23 3 3 327 Very Low Low
a" C0 t4 K" B& Z6 z7 x" j3 0.29 1 4 334 Very Low Low
: o/ R: r3 [$ u" [+ ~. h4 0.31 3 6 335 Very Low Low
* X+ a6 }7 U9 M" P8 A
& c; w+ b4 K. E( Z4 r1& T# |2 |. A! c4 b: D* P
2& m, ~, P0 ] a u1 |! Z% w& E! u
3
' e" j0 [/ ?6 B2 s, a( H45 O" ]7 ?4 L- M5 |. }4 S4 K: d# _
53 j7 g2 i" Z* u8 k& S9 d2 Y
6
! O* t; `, X: f7% [& _3 V- P$ T
85 K4 k N; `6 M. q% e' N2 f9 R6 o! R
9
) ^8 O1 r" d+ Z1 [, U/ c10
; f! w& d: W6 V! a) v$ o4 p11* y2 l) P# F; e3 b) \2 r6 A# {
12! J u! F" X; A2 q4 i7 C: G
13, {9 F2 \3 o2 k# g
14' _( x& Z/ n- N7 R, s; W; Z5 d
15
# ]3 T. F! _. V" C6 d16- J% W. m) ?$ ~3 |' N/ g
分割点分别是:. ?, s) \% w1 J6 j4 ~
( }! u7 t. A7 i) j! d* i
array([ 1051.16 , 2295. , 3073.29, 4031.68, 5456.34, 17828.84])
+ q0 I. q$ _: o. e( q) D, Aarray([ -inf, 1000., 3500., 5500., 18000., inf])* u4 G. [3 q* {4 ` i/ \; Y6 n
1- s7 n k9 ^9 e* A4 S" x5 ^" L0 s
2. Q# x9 o$ ^0 U. ~9 p4 _
第4问中按照整数分箱得到的序列中,是否出现了所有的类别?如果存在没有出现的类别请把该类别删除。" S! v( Q- E6 P6 e2 x
df['price_list'].cat.categories # 原先设定的类别数
+ ~* i/ z& {5 M) C6 Y* TIndex(['Very Low', 'Low', 'Mid', 'High', 'Very High'], dtype='object')' p6 z/ P1 M+ I& {; k% Q
2 u$ `3 Y! ^ B1 y, \! q( \df['price_list'].cat.remove_unused_categories().cat.categories # 移除未出现的类别# m, q7 O6 D' O% J
Index(['Low', 'Mid', 'High'], dtype='object') # 首尾两个类别未出现
2 N# d- W+ \! p: W1: v4 u7 A5 [6 m( F- q
2% D& x8 s6 j" e* T/ s* t$ a) \
3
/ o; ]/ n& [3 h3 B( N: }4
' _" p0 H) h/ k# f/ Z- y9 e5
% Z, V2 i8 z8 \& a; n+ O7 }avg.sort_values() # 可见首尾区间确实是没有的- J( A* i% E$ j+ x' M
31962 1051.162791
8 @; a E w, A; F+ n15 1078.125000
5 H; f8 O$ n, a8 H4 1080.645161
: n0 V3 y/ y$ k28285 1109.090909
9 I Z0 N5 s* N& f" ^: @13 1109.677419
5 u5 a& @& g/ o+ G# ~9 W# v6 f ... 4 a4 J9 Q! V3 Z6 S
26998 16764.705882
$ y) W6 t, j+ @& A27457 16928.971963& P- Y$ h! o. N+ P5 ?: V8 J
27226 17077.669903 t8 V5 {; X Z5 H) O# ^0 v1 c
27530 17083.177570
' W# V( ]* ]3 \27635 17828.846154 H9 t1 @8 C5 K1 U3 H+ ?: Y0 b
1/ t5 K/ I3 a. r( e: [
2
- E/ T( b& M) x4 w7 g3
+ o: I2 ?2 ?. u- r$ ]7 ?43 L7 h$ M7 C" n
5
1 z1 K8 W/ N1 v) C) k/ ~% N$ _, Y63 a; U k" l* H, N3 i7 v: W
7
' S* l' m, O8 w; y+ f8, n C1 [) Y7 }
91 |* L% o0 @, _! n" D% O
10
3 l$ @+ s/ |7 r& W11
# O8 Y# h, w) F$ e! d7 W; d8 L12
4 G( b* M0 e Y+ V# h- k1 {# G对第4问中按照分位数分箱得到的序列,求每个样本对应所在区间的左右端点值和长度。+ Z7 p" A, p( V$ N) X, I
# 分割时区间不能有命名,否则字符串传入错误。
5 j' t0 M' \ k/ z( ^. eid_interval=pd.IntervalIndex(0 ^6 Z; D4 X3 a
pd.qcut(avg, q=[0,0.2,0.4,0.6,0.8,1],retbins=True)[0]5 V8 |/ ^1 Y& N0 ]. r; E% x' q
), s! K6 P6 `" Y c4 _+ y8 _( D
id_interval.left
6 P8 L$ V( ~; \ v/ Vid_interval.right
9 `0 U+ ^8 H; Z7 c7 c3 e* J% fid_interval.length , c. ^8 `% z! q- \! M
1
" f$ v: Z0 [, [" R2, a% M2 k" i! O0 Q
3
( p; B; B- d& B% x4" L& J2 a( T% j" n, b% V
5
" W& D# t4 a% n. h7 a6 }6
0 O) t U$ i: s5 x. j9 d6 u7
# D. M8 B h0 d' u+ G3 E2 k第十章 时序数据( d2 |; q$ d b* R" w4 h$ \! r
import numpy as np6 V } S/ _# X+ y5 r: c. i
import pandas as pd; G/ U' P$ w* e9 M; {
1! p: V# u+ S; G
2( N! e; y) w ^$ g( }
, @! `9 m- i. n1 }
" D* i! S/ a+ \6 S4 A
10.1 时序中的基本对象$ U1 v2 k0 H, F5 y% _ n
时间序列的概念在日常生活中十分常见,但对于一个具体的时序事件而言,可以从多个时间对象的角度来描述。例如2020年9月7日周一早上8点整需要到教室上课,这个课会在当天早上10点结束,其中包含了哪些时间概念?: W# j' P2 _7 n& U$ i7 q
z) G$ N) Y" e0 T/ Y会出现时间戳(Date times)的概念,即’2020-9-7 08:00:00’和’2020-9-7 10:00:00’这两个时间点分别代表了上课和下课的时刻,在pandas中称为Timestamp。同时,一系列的时间戳可以组成DatetimeIndex,而将它放到Series中后,Series的类型就变为了datetime64[ns],如果有涉及时区则为datetime64[ns, tz],其中tz是timezone的简写。
$ \9 Z) R; [+ E j0 K* X& x- y, l$ B+ N0 Z0 F
会出现时间差(Time deltas)的概念,即上课需要的时间,两个Timestamp做差就得到了时间差,pandas中利用Timedelta来表示。类似的,一系列的时间差就组成了TimedeltaIndex, 而将它放到Series中后,Series的类型就变为了timedelta64[ns]。
0 o4 B X, m. ?& j. @9 L9 X/ o; a5 z
会出现时间段(Time spans)的概念,即在8点到10点这个区间都会持续地在上课,在pandas利用Period来表示。类似的,一系列的时间段就组成了PeriodIndex, 而将它放到Series中后,Series的类型就变为了Period。" }( G0 P. F' `# p6 R o
! ~0 K& \' y1 S, h% z1 m
会出现日期偏置(Date offsets)的概念,假设你只知道9月的第一个周一早上8点要去上课,但不知道具体的日期,那么就需要一个类型来处理此类需求。再例如,想要知道2020年9月7日后的第30个工作日是哪一天,那么时间差就解决不了你的问题,从而pandas中的DateOffset就出现了。同时,pandas中没有为一列时间偏置专门设计存储类型,理由也很简单,因为需求比较奇怪,一般来说我们只需要对一批时间特征做一个统一的特殊日期偏置。
) Y' T- F$ `3 s: s% p9 r3 V
3 z- k# T7 ~4 u5 {( M" P 通过这个简单的例子,就能够容易地总结出官方文档中的这个表格:
" J- D Z2 m2 E) \, {8 y3 b, q2 l. c1 C7 M: f8 L
概念 单元素类型 数组类型 pandas数据类型
8 k) a! h5 n2 P# V2 {Date times Timestamp DatetimeIndex datetime64[ns]; ?% A# Q" {6 z7 Y1 ?# `2 N
Time deltas Timedelta TimedeltaIndex timedelta64[ns]- Q: P; R* f6 ]4 Z, z9 f( v4 S1 {
Time spans Period PeriodIndex period[freq]
! R0 ]( ?" }4 kDate offsets DateOffset None None
8 I, U) J- v( B, _* b; N) b 由于时间段对象Period/PeriodIndex的使用频率并不高,因此将不进行讲解,而只涉及时间戳序列、时间差序列和日期偏置的相关内容。
. H7 i% F; G5 ?+ {- T, `4 p7 H# d& l1 b( v
10.2 时间戳
% c0 i) L6 |7 N9 h2 q" Y" y8 w10.2.1 Timestamp的构造与属性9 G+ v7 N( a& E! {% |
单个时间戳的生成利用pd.Timestamp实现,一般而言的常见日期格式都能被成功地转换:+ V% _3 r# M! V& Z9 ?, ^
F/ }% _" Z: b, g- U
ts = pd.Timestamp('2020/1/1')( C( }# S7 t5 s( J/ l+ E
# f% Z2 L( |8 B9 } p) [ts
4 A( H" j; Q' z# c: kOut[4]: Timestamp('2020-01-01 00:00:00'); l0 E1 O" r' V
* @+ E7 u' [. K8 g7 {; x2 pts = pd.Timestamp('2020-1-1 08:10:30')
/ r0 M4 p, ~6 U+ \+ ?3 T1 y4 o$ @9 t2 X% R& F/ x
ts
0 l* y8 g2 u, R* q$ ^' A+ p5 POut[6]: Timestamp('2020-01-01 08:10:30')0 r) F$ a. P! W8 b. e1 \
1
- i. Z8 J, J- I$ O: a, d2( p: c/ R7 j S5 {( s
38 U5 l3 {* Q) V" c
4$ D& C9 x6 f& W5 J* J, D
5
7 ^- y2 l( t3 c6
0 E0 t4 p! Q4 ` a73 ]# b8 w! f5 @/ s* m+ C" D
8
, q) H: p/ L8 r( {9" t* v/ `7 P: h
通过year, month, day, hour, min, second可以获取具体的数值:
* h; E# C9 V0 p( A9 E u; Z2 `
% ~4 |$ N1 i/ I9 M, Y1 Q0 ots.year
- x) W" q& O; A# A+ f* bOut[7]: 2020- v6 J; r: h! G4 z7 c2 e
. _+ Y8 W7 g1 r6 o, m6 c7 xts.month
& p$ i5 f( m4 w, ]: WOut[8]: 1! w7 e9 }7 f9 N ?9 w; S' c1 b/ K: E2 m! {
4 u6 s: L4 S$ G4 p! _ts.day2 a2 J9 C: O3 H. ]5 u4 r/ s, q: ]7 a
Out[9]: 1
0 y# K4 }, |+ o1 ?$ I; K. ^3 J+ u% o# ~4 z0 F7 |. W
ts.hour
* S8 F% }: O) E0 W0 c' GOut[10]: 8
0 M5 i$ ~6 f, P1 d# \; @( N8 u0 p. A6 H4 v3 U" Z
ts.minute/ i0 U% X+ u3 I# g- U' F+ I6 v/ G
Out[11]: 10/ D4 _0 k7 K. v1 Z% t& {
* g* ~1 z5 g0 u* g, ots.second, v/ u$ l. d1 z
Out[12]: 30
# O8 D4 k8 I: i0 x6 Y
5 `6 z4 G( U1 \; z$ S3 V# ^1
( {# e# S6 T) Z* Q4 H$ ]2
$ `- r8 n* {9 M2 j6 K- `3: ]6 L. v! O& }* O
4
" V" O, w a& p0 I& ~53 o, } l9 v# w0 ?; }" F
6
# ~" m+ ^. J# p. S6 Y; [7- q. K$ I0 L: W
8- w/ R* {% U( }
9
, u& l4 q' N; \$ E& {4 N) j107 C4 _9 N6 v& R! x% G' ^: A
11
2 K' M7 v9 \2 L* Y Z5 [12. z+ g/ M& a. R: c& v' L
13! H( G& [# Y4 x8 j- K: Z
14
5 k' q2 B2 H) X8 m, {4 w" X15, d# Q. q6 h3 H( }* K0 R+ C) o
16, u! X, L6 r9 m+ r
174 ?* r1 l# A1 K; L* c
# 获取当前时间' S# Y5 N# q# @
now=pd.Timestamp.now()% X! `8 X! p. o( L k5 u1 J& ]+ k
15 J; S/ U' q: \( { J
2
* D! d! D# g3 _+ N+ H在pandas中,时间戳的最小精度为纳秒ns,由于使用了64位存储,可以表示的时间范围大约可以如下计算:
$ c1 r( G$ R: K x( X. t- c ` QT 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)6 }$ |4 w. Z3 q% J) O0 q4 ]+ N2 p# j
TimeRange= 8 Z# k4 [9 E# [% q' K' {
10 , Y- h" l5 e( ^2 z7 [
98 I0 c" }- |6 L& o T$ y+ T2 |5 U1 @
×60×60×24×365
6 K& E$ C. y' B2 3 q" d! b) E9 D0 n# S
643 h, C1 [, P) \, Y3 K; }
( V2 ]# T7 r9 L z
# c) i7 P5 o2 [0 | ≈585(Years)
; D9 _+ _* ]0 t. y
, U" x3 z! }. }4 j# C* s# h通过pd.Timestamp.max和pd.Timestamp.min可以获取时间戳表示的范围,可以看到确实表示的区间年数大小正如上述计算结果:
8 d, n& W; P; z8 E3 N; A' j) d f$ q5 h, Q+ P( V* ~
pd.Timestamp.max
- J9 P) {2 i1 W. @$ z+ ^2 T' V8 KOut[13]: Timestamp('2262-04-11 23:47:16.854775807')) T1 _. G# ]2 D3 [+ x1 l, {: Y
* @ V. Y+ j: s
pd.Timestamp.min6 a. `) e8 |1 W" H7 @
Out[14]: Timestamp('1677-09-21 00:12:43.145225')+ z& s0 ]$ D U' a
1 A; m$ n. f- \8 `$ npd.Timestamp.max.year - pd.Timestamp.min.year7 u' a4 `# a4 _+ P5 _/ D
Out[15]: 585
2 K; Q3 T Z, E) h1+ h' z3 h3 w4 H
2
% h5 T- x: W# P: \: ~ e+ v3
! g2 Z7 _* K' C% n/ E( ?4
( y+ h6 r9 o* Y3 r5 R2 |' h# j3 Q8 l5
4 s, S0 k; r3 z4 P {62 G2 |' ~8 x, ~8 h4 u3 D$ |$ E
7* v& |5 Q \& d9 D. {0 F8 C) K
8
) G( s5 m* E0 g O10.2.2 Datetime序列的生成
# f [0 C9 P9 b. vpandas.to_datetime(arg, errors='raise', dayfirst=False, yearfirst=False, utc=None, format=None,0 b+ b$ }% a; i$ R0 h8 r2 |$ F) O
exact=True, unit=None, infer_datetime_format=False, origin='unix', cache=True)9 m. L" ~4 D8 d9 j5 i, t2 k3 P
1
4 h. F1 s# u4 o, `9 P) `2
4 k& c! X5 v( \, d0 r A( Opandas.to_datetime将arg转换为日期时间。
: s! [$ O+ M9 p% P( S6 P
1 n6 S% m0 ]! ~2 rarg:可以是argint、float、str、datetime、list、tuple、一维数组、Series、DataFrame/dict-like等要转换为日期时间的对象。如果提供了 DataFrame,则该方法至少需要以下列:“年”、“月”、“日”。7 A' S7 r7 q6 F
errors:
5 y7 ?$ _* @8 n- I. W, G- ‘raise’:默认值,无效解析将引发异常4 e" I/ n6 a- b0 N
- ‘raise’:无效解析将返回输入9 e3 U1 ~5 |6 N) j
- ‘coerce’:无效解析将被设置为NaT
' e4 U; M/ W' Q7 r: ^dayfirst:如果 arg 是 str 或类似列表时使用,表示日期解析顺序,默认为 False。如果为 True,则首先解析日期,例如“10/11/12”被解析为 2012-11-10。如果无法根据给定的 dayfirst 选项解析分隔日期字符串,会显示警告。
; e3 J* q% p, @ S- @4 Eyearfirst:如果 arg 是 str 或类似列表时使用,表示日期解析顺序,默认为 False。如果为 True,则首先解析年份,例如“10/11/12”被解析为2010-11-12。无法正确解析时会显示警告。(如果 dayfirst 和 yearfirst 都为 True,则 yearfirst 优先(与 dateutil 相同)。)
( y8 ?: z1 ]2 d/ J( i7 E2 rutcbool:默认None,控制时区相关的解析、本地化和转换。请参阅:pandas 有关时区转换和本地化的一般文档
" ~ L j+ L H P) C6 h- E/ ~format:str格式,默认None。时间戳的格式不满足转换时,可以强制使用format进行匹配。
, n; Z5 y. Z7 G; V" Dunitstr:默认“ns”。它是arg (D,s,ms,us,ns) 的表示单位,可以是整数或浮点数。这将基于原点。例如,使用 unit=‘ms’ 和 origin=‘unix’ (默认值),这将计算到 unix 开始的毫秒数。
d; o( z7 ~7 w2 q( @" l# Jto_datetime能够把一列时间戳格式的对象转换成为datetime64[ns]类型的时间序列:% X; K) k$ J9 `8 p! V
pd.to_datetime(['2020-1-1', '2020-1-3', '2020-1-6'])
! f0 s/ v* g+ _$ ~) ^ H# @4 o }; F& e7 T2 g% r3 ?
DatetimeIndex(['2020-01-01', '2020-01-03', '2020-01-06'], dtype='datetime64[ns]', freq=None) \: O: P) u6 K
1: s+ S$ [# r1 j7 `: x
2
' W5 K$ T( ]" ]: B4 f35 F+ I2 c. F3 e. x1 ?
在极少数情况,时间戳的格式不满足转换时,可以强制使用format进行匹配:
) I( v, e% P7 T) ]1 R; ?3 a. U( @3 q6 R& W8 }+ D0 ?0 X
temp = pd.to_datetime(['2020\\1\\1','2020\\1\\3'],format='%Y\\%m\\%d')! y; w% D( `7 k1 P
temp
& @6 y% Z$ [5 T) }- _/ [4 N4 V0 d, F2 k+ Z5 a) Z, H G
DatetimeIndex(['2020-01-01', '2020-01-03'], dtype='datetime64[ns]', freq=None)
) ~$ X' c7 Q' K1 Y3 Q3 \1
; z8 X5 u5 e- s4 r2
9 k- |+ a* O" D5 |: H0 _3
: X& E; x- ?9 ?, L; b; J4
( t5 A* A; D, C2 D* u; S6 f. @ 注意上面由于传入的是列表,而非pandas内部的Series,因此返回的是DatetimeIndex,如果想要转为datetime64[ns]的序列,需要显式用Series转化:, b0 i3 v5 p3 x9 r% q! a! z; h% Q
9 n E7 M1 Q- ~7 r$ {; u& v, C' h
pd.Series(temp).head()
+ n5 o( g+ m' ~6 J8 G! x* I( u# U8 b
0 2020-01-01, \1 m, e" F5 Q1 ~2 e& B! x
1 2020-01-032 b* [/ n% C u2 S4 M- V* A* o4 m
dtype: datetime64[ns]+ q/ | f( \* a
1
4 S) B5 u: ~1 V' o$ S2
: b. ^6 d1 V! u3
6 {+ V- j/ i: |6 E/ B4
6 `! c3 ~+ p" j U5
) d) h4 ], i3 I' Z/ }; s下面的序列本身就是Series,所以不需要再转化。& S, K9 \% f; u. X
9 M5 L/ k7 s: n! vdf = pd.read_csv('../data/learn_pandas.csv')+ |: h+ X5 Z: V5 j
s = pd.to_datetime(df.Test_Date)
3 [* [' ^! Q, s- j5 W- N* `1 [ [s.head()
9 o) T9 z: `4 T( o" r
# Y$ c" B$ R7 \3 ]5 L0 2019-10-05( x+ z4 u; c; c/ T0 U
1 2019-09-04# V* O, j5 ?' O4 D5 D3 K# X
2 2019-09-12
7 P2 A4 g, B* X6 O. v0 _, n' Z3 2020-01-03
) s+ F0 \2 e( Q% T. |$ b' y4 2019-11-066 x$ W9 _6 Y- X" V
Name: Test_Date, dtype: datetime64[ns]
5 Y6 Q5 [9 g! d6 w" c- _% B0 ~1* U, r3 k$ [: A* a/ ]" O; |
2
; [# k4 S# y7 q) h3
# l" i% N8 W! N) u) j' q- j3 r4! m% h# e8 a5 ^7 z7 Y8 c
51 \9 G, w; C# g6 P; P% W
6' t9 B$ O: V, ]
78 K/ k) s1 r* z3 K
84 C' W" T1 c# l" F1 C6 ?" N2 [
9
$ ^0 a$ y" ?* \/ [7 y' c* f10$ w1 W( P5 H" A" F- s% h
把表的多列时间属性拼接转为时间序列的to_datetime,此时的列名必须和以下给定的时间关键词列名一致:' t2 O( P$ @5 O
df_date_cols = pd.DataFrame({'year': [2020, 2020], ^) U a4 j1 H
'month': [1, 1],
; a( l5 t3 p$ i# V( Z 'day': [1, 2],, j' z" E( B5 W) u3 t* m: y0 j8 p
'hour': [10, 20],
2 W4 V1 ^' M& S8 S9 x! g }" T6 y& p 'minute': [30, 50],# n7 k8 g, A3 S7 R
'second': [20, 40]})5 a6 l6 x+ d% s8 v
pd.to_datetime(df_date_cols)
$ d& [' V/ c) ]5 m1 T4 Z1 s6 L, c$ u1 z9 e- b$ ^
0 2020-01-01 10:30:20
) \1 n @9 N, J, _5 ]0 s; o2 C0 z1 2020-01-02 20:50:40
) j$ J* M: M- u% w/ n# Y- idtype: datetime64[ns]7 Y# B( t8 ?% e0 m8 Z' C
1( J& e* Y0 E: H4 D% m* X: l
2! f6 B. `2 {9 C) F
3$ Z* l2 K, Q! v$ ~
4
1 U {' v# h$ O5
' ?- n4 B# z& c+ \61 Q! e" F5 e/ ~/ ~; R
7: B4 o3 c. O5 p, B: D$ V. @
8$ t5 j: \+ J0 h" H4 v% c
9# \3 W! R2 p' }8 a& V* y
10
* D# V: T% T: v# E4 q11
3 y% w z- V0 O6 p1 Bdate_range是一种生成连续间隔时间的一种方法,其重要的参数为start, end, freq, periods,它们分别表示开始时间,结束时间,时间间隔,时间戳个数。其中,四个中的三个参数决定了,那么剩下的一个就随之确定了。这里要注意,开始或结束日期如果作为端点则它会被包含:
; k) S/ u, w( j) g9 Kpd.date_range('2020-1-1','2020-1-21', freq='10D') # 包含
0 O7 R1 u/ Q8 V9 m/ Q# H% FOut[25]: DatetimeIndex(['2020-01-01', '2020-01-11', '2020-01-21'], dtype='datetime64[ns]', freq='10D')
3 W# j( V; I) `8 u2 c! X3 I
3 G, a0 B+ U8 ]2 f: Y1 M$ ~2 m, k( spd.date_range('2020-1-1','2020-2-28', freq='10D')" [5 Z- n4 l: i! M
Out[26]:
8 l6 X: P0 \$ u& {/ x) @DatetimeIndex(['2020-01-01', '2020-01-11', '2020-01-21', '2020-01-31',6 j/ u$ s( n, f; K, ]
'2020-02-10', '2020-02-20'],4 N1 |' T) O ]8 V4 o1 e
dtype='datetime64[ns]', freq='10D')0 {- d# o( C. m* `* H( ~ Y
4 I; v: e/ i4 q! U9 bpd.date_range('2020-1-1',6 N' F! F! F. O* [. H2 r$ |2 {
'2020-2-28', periods=6) # 由于结束日期无法取到,freq不为10天, `" G4 o! U+ N8 V
: D4 s! b7 h9 v+ UOut[27]:
[, r! A+ _* |0 Q q) J* CDatetimeIndex(['2020-01-01 00:00:00', '2020-01-12 14:24:00',
! O4 P0 i+ [9 |9 I) _- t '2020-01-24 04:48:00', '2020-02-04 19:12:00',' D; Y- d, P& e; N, n6 _7 {! \
'2020-02-16 09:36:00', '2020-02-28 00:00:00'],0 _6 y) a* r2 i
dtype='datetime64[ns]', freq=None). @$ q! Z2 I) `: |4 w
0 A) ~0 a$ a1 I% x4 b' Q/ P
1( F" W$ v4 U' p& e( T. Y& S
2+ f( X4 N k3 X: u b2 u( _+ ?
3. y7 _( t& p* s5 x( Y
4
% A0 a' a6 B/ J' O$ M5
! N: e/ Q. D0 }4 f! ~6$ X$ Q) r0 F6 ~( P6 x; v ^
7. Z, S0 l9 s( @4 \4 G! \1 ^
8* N2 [3 \, h8 W; h+ r9 [7 _
9% a8 x, E$ n: D4 ~
10( O# [/ n0 X' i; @
11
" T. f8 K% F8 ?+ T5 V0 Q12
3 S @1 w$ \* n1 U" Z13
1 X+ }6 V- U6 ?/ o" F4 n144 L2 N! |' s1 v4 G* [3 j$ r8 b' H8 G
15
# \8 {* d3 h& t4 k! g) K) W6 @* u16
& q1 Y7 m) T# ?8 n+ k17
2 b: D; {: J% v9 K这里的freq参数与DateOffset对象紧密相关,将在第四节介绍其具体的用法。3 i0 v# Z2 W# v8 k7 G. M" p8 ?/ P
* b) y1 {* s6 N
【练一练】
% N7 _: ^3 H& k! t% D9 nTimestamp上定义了一个value属性,其返回的整数值代表了从1970年1月1日零点到给定时间戳相差的纳秒数,请利用这个属性构造一个随机生成给定日期区间内日期序列的函数。
0 R# k% Z( q3 b7 R- d8 h) u
. ]+ u* Y" X8 }) G; |ls=['2020-01-01','2020-02-20']
' u O5 e) y* p6 n1 m' @def dates(ls,n):" L7 V8 R* B5 w& Z( D& S
min=pd.Timestamp(ls[0]).value/10**9
; @* ?6 Y U, S2 m8 ?& {8 O$ ^ max=pd.Timestamp(ls[1]).value/10**9
( R. W, u$ M% R( `- | times=np.random.randint(min,max+1,n)
1 O1 B) s) c; K1 }" y6 u& X" i) l( n return pd.to_datetime(times,unit='s')# s# W( V$ x. W+ E% V
dates(ls,10)
, d4 Q5 H8 F/ N8 k/ n( Y! F. Q' T9 o0 w1 r& F7 R- I& m+ y
DatetimeIndex(['2020-02-16 09:25:30', '2020-01-29 07:00:04',
7 d9 V) i) P( r2 n$ \ '2020-01-21 12:26:02', '2020-02-08 20:34:08',
" G8 t+ X6 `" e* R3 Q '2020-02-15 00:18:33', '2020-02-11 02:18:07',
; D: q9 Z$ ^2 U0 ]9 n '2020-01-12 21:48:59', '2020-01-12 00:39:24',1 R2 r, w3 F8 f$ ?4 z9 R
'2020-02-14 20:55:20', '2020-01-26 15:44:13'],
' t$ l& w2 E$ `2 T2 m dtype='datetime64[ns]', freq=None)! W- A6 r2 J. E& B
1 ?9 z# I8 Y5 g7 s1 p; Q
2
' q* l2 i- W. j! u( W& u* x4 H$ C3" P) C% ]$ j7 Y9 S- B7 H$ z
4% ], j% y/ ?5 q& A5 e1 P
5# M" p" U/ E4 i: v! W- |# B& e6 A, B
6
7 S1 a) z# e6 e7
1 T& O2 [! A9 |1 p# }# R& C8- R9 ~% t, J* X
9. x- ?% ?2 D. C6 x$ k
10
) `1 @& \ H' U$ q/ }# t114 d, W) W& Y0 K: o) {$ ^" N1 d
12
9 b/ I" K6 B. w, Y0 Y134 O9 P+ U5 y/ q3 K1 r" W% R" H
14
) r% X8 S, I" t1 M5 Oasfreq:改变序列采样频率的方法,能够根据给定的freq对序列进行类似于reindex的操作:
% e, l# i- L$ p% ks = pd.Series(np.random.rand(5),
( [1 @! u6 |! @+ j index=pd.to_datetime([* w- {% p t. m' A2 }
'2020-1-%d'%i for i in range(1,10,2)]))
5 U0 t8 m* m m% t# U0 H& G5 }5 d" |; w1 l( O$ T& o
; X" m1 w0 `4 y8 W. Q
s.head()
; ^2 ?4 J; f2 i- L3 x zOut[29]:
/ t3 e9 B( N4 a# M2020-01-01 0.836578
- |* W+ U# v4 n' v% a2020-01-03 0.678419
; l( M+ B8 ~& P2 m2020-01-05 0.711897! t7 G2 Q& p- `9 @
2020-01-07 0.487429* {% ]/ V" ]! U
2020-01-09 0.604705
$ t& L: S9 t. X9 b% cdtype: float64% K5 m) ?3 i, I/ ~4 E# j5 M Q& k r
+ m+ Z: g7 y& u# R% y7 K
s.asfreq('D').head()5 q% |) P+ O$ b/ t
Out[30]:
f7 H) y+ q- ?/ q8 c2020-01-01 0.836578
! f: V! J1 a) ~2020-01-02 NaN+ v7 N6 u( [; q R7 d# K
2020-01-03 0.678419
; \: F J9 o: k& N2020-01-04 NaN
, X7 J$ J4 O$ o( s0 Z0 n. h2020-01-05 0.711897
8 A& V/ f1 X* n: u5 P2 JFreq: D, dtype: float64' C5 n. p8 u& F" b4 `" f
& V' M7 \+ s& T. ^! I1 Ks.asfreq('12H').head()0 M" K0 @4 x1 j) e* g! Y7 g
Out[31]:
) K& \6 ^3 b% B) h2020-01-01 00:00:00 0.836578/ r( G" O1 l1 v; s- B
2020-01-01 12:00:00 NaN; v( P h' f6 P l
2020-01-02 00:00:00 NaN
. T6 |' Z! a5 J' g2020-01-02 12:00:00 NaN6 C/ J o: f( H$ z, K' l; O
2020-01-03 00:00:00 0.678419. G1 f* d) P. B2 p; U
Freq: 12H, dtype: float64
4 l2 n+ {6 q" V( x. j- \
7 ?" O1 \' G. F2 Y, u1- I w% w! y" c
2
2 Y! a# d' s# d! }3
6 i' q# F' E0 p; c4 W) B. D9 S4
5 m( o P* f3 p/ K$ X/ P55 }, j3 p7 ]6 P8 k, k
6
9 C D2 f; L- S7) O# d5 w- v: X- `4 \; |
8
) M8 }7 S* o- O/ \2 K. Q2 U- n6 E9
, a2 D2 k$ k7 F* j1 C& X1 l10
1 q3 n9 w+ ]9 |) r- z8 e3 Z) J& ^11: o- e9 v4 G/ c" @+ m3 h6 H
12& A0 a/ c: g3 k8 y* f0 ?3 o* |
134 g& {5 `9 {; T, y. S' H
14/ ^% T! r) O W
153 d8 q# p* B E! U
16& {" }( D, m* q
175 L9 g1 L) j4 ~ @0 Z
18* I: y. w7 \ n* S
19
9 y2 u- Q, H, \/ x3 D$ R20$ x. ~4 B+ w/ f4 ]1 W; p4 Q
21( o$ N* d! _$ P# v* F7 a$ \' v
22+ W" V+ R e) L/ ]: o! t
23
C9 N) {) V2 k! I/ u24
! s& J5 }) D3 P* Z25- B5 M; @# i9 B9 ^& Q
26$ |3 z! _% f# b" Z, u. F
27
D0 L0 ~3 k) m$ J! c28$ v9 t8 h: v7 l) ]' G0 l
29' S# j: N/ g) g" m1 y+ k! |
307 x. I6 S: E; s5 w
31( B4 N4 V5 T) n8 p3 ~# P( X
【NOTE】datetime64[ns] 序列的极值与均值+ H9 _7 I, F( N4 j$ y2 U
前面提到了datetime64[ns]本质上可以理解为一个整数,即从1970年1月1日零点到给定时间戳相差的纳秒数。所以对于一个datetime64[ns]序列,可以使用max, min, mean,来取得最大时间戳、最小时间戳和“平均”时间戳。* N5 u% h! {/ {7 w
5 A+ L5 o! M" B1 d. ?0 R9 u10.2.3 dt对象
) t T' c0 n8 @0 F/ p8 f 如同category, string的序列上定义了cat, str来完成分类数据和文本数据的操作,在时序类型的序列上定义了dt对象来完成许多时间序列的相关操作。这里对于datetime64[ns]类型而言,可以大致分为三类操作:取出时间相关的属性、判断时间戳是否满足条件、取整操作。. B. D% o$ z# v* _% l' y: \# w' D2 p
/ L& J0 a! d" K: o" ~ n1 J; k第一类操作的常用属性包括:date, time, year, month, day, hour, minute, second, microsecond, nanosecond, dayofweek, dayofyear, weekofyear, daysinmonth, quarter,其中daysinmonth, quarter分别表示该月一共有几天和季度。
: P3 z- l1 u2 n1 }s = pd.Series(pd.date_range('2020-1-1','2020-1-3', freq='D'))
) T5 b7 u [2 P1 J2 k9 |* V" Y# j; B# i
s.dt.date$ s" z) n$ t f" E5 X9 _" _
Out[33]: & W. X7 P% L2 |# v( V' b* c
0 2020-01-01
" I! l! \/ C9 X2 o1 2020-01-02
- u, x' S( C$ t6 x5 X2 2020-01-03- [. O2 Y5 a- u7 ~7 }
dtype: object7 d6 F" W1 j0 c0 p# S, P* b- @$ _
; ~/ @! b% Y9 E0 [$ ss.dt.time
/ b) R2 p! _; y5 bOut[34]: 0 d+ c1 d' W8 |! `
0 00:00:00
0 w, K- @+ d$ T6 a# o0 S& k1 00:00:00+ D: n. B) c0 ^7 h. C
2 00:00:00! C3 G6 e0 \7 f
dtype: object
, u& o" _$ @8 C5 w; B
9 g0 V5 t8 J h! V" xs.dt.day6 W6 a! u7 s- T$ F7 c) H' V
Out[35]: 2 u: Q2 q! y+ [/ c( B7 U
0 1: ^8 C, R8 `6 x0 ~
1 2
4 E; W. t, t2 t2 E+ S! Q2 3
" K# ?$ y V! Q+ n3 O. F9 i+ R) c2 {dtype: int64
0 }5 H1 s, s! U4 @: T# @4 G' l/ y
s.dt.daysinmonth: J" S) B# I' V9 ~% [3 }
Out[36]:
$ a" t N. s1 L+ u0 31
7 a8 c; h7 w" c8 N. K1 31
9 r- N) \/ h. ^2 31 X; W3 D! J% m% V2 y6 S* M' `5 X
dtype: int644 d1 x9 |+ l' H1 g" H* Z y
6 G( N: J6 u) U/ [! C
1% X- L5 E. O) G. n* j2 [: [
2, M& d) |6 r5 C7 A
3
* a( e9 |# G ]# |* y* {4
6 g0 X8 i5 z' i6 h1 P! q8 y5
! \0 p7 i$ k! V0 b$ y63 @6 A3 y+ L$ U2 I, v3 s
7
. s. q- M7 n6 J( \$ z' w! K: d81 x' S l% H& i* }# g
9- m% X& Y6 `/ N* r- n/ R
10
% r7 n8 T* |( x/ G8 o# A11, _& |; Z) v$ i% x, h) K
122 Z0 e# ]) D" s2 {3 c4 m, d
13
. G w0 ^$ k: u8 ~7 o0 P143 [6 g; R+ X. Q1 U' ^9 \' X
15
3 L, k/ `) @" O* t0 f7 P16
: H% f0 t3 L% K0 m17
# c5 Q/ r: D, a. x18
! p+ w6 Q( J% n# l A: n) L19
/ S5 G: Q* C! x9 I1 B; g! Y2 B200 |/ y+ x& w+ b- U, q
21% D7 M5 u# @) v; m1 E+ Y) C9 h
22- w5 m' x4 d* g
23) s+ J+ x- r0 i% \2 z
248 E B8 y& p8 d% Q
25
0 l8 V- ?3 t: \" X8 D2 |. r26
% R) Q3 g$ u2 k& l6 a27
4 Z' k q$ g# s3 x! {% N28& r2 q: D' O8 H; S9 b
29# F, g8 y M w: M+ R( u9 ~. \% w
在这些属性中,经常使用的是dayofweek,它返回了周中的星期情况,周一为0、周二为1,以此类推。此外,还可以通过month_name, day_name返回英文的月名和星期名,注意它们是方法而不是属性:2 w& g, h4 R1 W) D7 ]' V
9 X a# z! ]" g! J) H
s.dt.dayofweek2 u. f1 w2 w/ i: S1 c0 O
Out[37]:
/ @7 I7 P: C- D) l. [ T0 2
% R1 y0 U8 Q% v8 D5 s% f$ S3 X* g1 3* ]7 }; M- x) ]2 u
2 4' | B8 b, }5 E& \" q
dtype: int64# T, k- |1 V3 E: S0 ]" h
2 [ I/ e" D5 M" is.dt.month_name()7 f5 g- d H/ e2 h
Out[38]:
" ~! q: B8 d/ g {1 q' Q0 January
7 X9 W) z0 d1 l7 l' V' Y1 January
0 k+ a; F; x4 U( l5 B8 I2 January
/ h3 a5 Y. Y$ `. S l9 ydtype: object1 _, w' x3 k- a1 @: c7 M
& e A' }* t5 K0 [' N4 I4 M7 b
s.dt.day_name()+ H% A, v9 j' i% |' r: L3 x
Out[39]: . |3 u& N0 z' A
0 Wednesday
3 v$ H# v! w5 r6 c! O1 Thursday; _ ^# Z: b0 M) `3 d/ t" N
2 Friday. o/ A: J* O" ^$ ^2 Y) W% I# P
dtype: object
) k ]1 T R1 z! s- J) _
6 h' |1 A+ p- t3 Q5 [, `8 c1) H6 L9 Z; P% {! X
2
4 o/ s: \1 Y: r$ J5 y& e. j3$ x6 a6 s1 n9 d! T: z( ~
46 _5 N+ h" |6 f" l, v9 M
5
0 l$ y) I# C0 b6 l }- T* {" U! N+ N6 U$ @- Z7 g% Q1 j
7
/ R( f0 J+ `) R$ a1 V8/ K0 I1 M+ o$ k' G; c( c# `/ ~
9( g+ v; \9 C; d
10
( B7 S9 b7 t, [% ?11% J' P2 M, S, d4 I/ S* d
12
1 W- k( o$ B/ E5 b- a7 C6 E134 p% V. d% x n, ~; \
14" n$ _: b+ k& k8 q
15' J9 n: }. b2 v" B* S; E' F1 o; w
16
- _, {+ h8 I w: a17
6 Q) t9 k+ |( ^' C18$ ]5 s. x. J4 U T8 a9 |' \
19
* g7 S" C- j% v9 M' ?% o0 p( q20% j4 G; N/ D6 w
第二类判断操作主要用于测试是否为月/季/年的第一天或者最后一天:9 s# R5 T4 A* k# \2 _. W) a" d
s.dt.is_year_start # 还可选 is_quarter/month_start
/ K& d5 P* Q+ s1 {+ b0 j0 vOut[40]:
) u. J+ Z2 K1 j) N, W& a9 t# w0 True) V8 ^" i$ I. w8 O. k
1 False
/ [! k5 N9 a* S. a3 o; i3 C2 False
1 r5 j* [- k! ?% S0 ~3 Ldtype: bool
t! Z/ Z2 k8 }8 l4 U! t1 k" {3 X( E& ^5 Q! N% K+ `) Z Q$ j: ]
s.dt.is_year_end # 还可选 is_quarter/month_end% f% _, d ~! _2 ~
Out[41]: 0 W3 U8 R1 C" q* J! l
0 False
4 V& A# { Z, V) R w. f: f9 }& Z1 False/ ~, _0 } g. q2 |4 Y7 t
2 False( ]2 ]7 k2 |2 P1 Y: X' R0 o
dtype: bool; h/ n) C* S, ?: h( X% v b4 l
11 Y3 R, [; W* ~, c( J( s
2# |" f. @8 `) K5 z
3 E$ d0 f3 Z# ~1 ~% M# U$ b
4$ X" Q+ C( ?! R, H
51 s# ?% k8 b) _- P; Q2 m% u
6
0 k; c4 ^% V. k6 A' @/ t7 ^7
; j; l2 H3 T( G% ]% L7 M8
8 _4 ^* J# O Q, t- L9 s( T6 c95 T; m- r. r# C+ U, y# q; O0 ]
10# J( ?0 I/ K& _) T0 Q$ w. E- L
11" ?& C j0 y4 ~* U0 h4 V
128 Z; D1 D' m& b: E! H% w2 a- h6 z
13
" u7 W- D: i0 I& S第三类的取整操作包含round, ceil, floor,它们的公共参数为freq,常用的包括H, min, S(小时、分钟、秒),所有可选的freq可参考此处。
; `7 l8 e, @: Ls = pd.Series(pd.date_range('2020-1-1 20:35:00',
, e& ^& R8 [" x \; n '2020-1-1 22:35:00',
" M& ~* c& r+ k" t0 m6 ~( d freq='45min'))
' e4 u" Z. h0 @% Z! n3 H3 R
, @6 A5 I- B/ S
. Q7 n( B' \0 is) Z2 F& i: b% S8 ]
Out[43]: ' C! Q6 j8 w1 l& s. L5 B4 n
0 2020-01-01 20:35:00
4 A, T$ r+ [9 E" I1 2020-01-01 21:20:00
( c: k& U+ \3 e" L2 2020-01-01 22:05:00
( A' G5 _: e: j0 Edtype: datetime64[ns]
- @$ J; `# T' X! H' d, V9 s5 U/ J2 K1 D' H& `+ ?5 ^
s.dt.round('1H')3 v6 d, O; M3 ?
Out[44]: 9 o3 o" x6 J6 ?# `3 k& ]6 d
0 2020-01-01 21:00:00
8 B3 D$ f" U& N+ W4 M1 2020-01-01 21:00:00
# r% k* d; z9 N4 W1 S, ^& N" d2 2020-01-01 22:00:003 Q( X$ O4 o' g5 ~
dtype: datetime64[ns]8 v; z$ p" y$ `" ?6 {: c" f' Z; h
% Q5 H( J" k& t2 h* M& M" {/ [
s.dt.ceil('1H')
5 }& C( a# c3 f, k& `; f8 Q# ?Out[45]: . d/ @9 A' y S) y: B; ]# K
0 2020-01-01 21:00:00
) }) g2 a0 u( G( L, G: y2 t* s1 2020-01-01 22:00:00
* _0 t! W; A* W# U% y J2 2020-01-01 23:00:00# U& [; S0 U+ z# I8 G% F: N
dtype: datetime64[ns]
8 \# m6 B) ?; V) ?6 x7 h6 W5 {4 u% C% V
s.dt.floor('1H')$ e1 X# _- F% h! u+ ~
Out[46]: 6 k, p; A0 j* D. N! R5 S/ e
0 2020-01-01 20:00:00
# ]: i6 ?: k. X0 |- x2 j1 2020-01-01 21:00:00. v* [8 B1 a; K, q- |
2 2020-01-01 22:00:00- x* d+ }8 d9 L# `* J
dtype: datetime64[ns]0 n+ O& ~- X9 S
1 N' N3 \, K; V1! O( D( i( W+ I7 ~
2
3 R0 T: Z- ^1 l. g3
2 Z% P+ T. D A45 c: w1 T0 \2 f- Y9 ]
5
8 @6 n! X& ~6 O2 `# g% |9 e9 o3 A) |6, e3 W9 s, o+ o4 J
7+ @& h# L- c# b7 y0 J- D
88 I' I& d {. x/ D9 o) p' b
9. v( k+ t5 @) t( `' _
10
, ~+ w/ ]" K0 q5 S# {11( {5 o @; T7 K, D: [
128 F1 {; z0 Q6 s0 d' c
136 ~6 {) O4 F! F0 H8 W% e3 y6 r
14
4 M9 ]& M7 x% P* p6 Y v15
& j# c6 s% _; s, W+ [% ? m" f16
) P1 n! G3 m% d17! ^& |: \* ~0 w9 j& o8 N2 L
18
* X0 c' i( d/ J. k6 i19% U0 j8 J# b2 a" ~
20
, A4 F. Y% V0 w, I& {. L21
6 W9 T/ h* t+ ^0 p7 ~+ _22
6 }$ x0 i4 I7 p. {* P23( N, V9 w7 K4 q$ d8 k3 r( \( r
24; r4 m9 W+ R! k
258 {; `- m0 ~& ]5 F. h
267 x& ]5 n D- V5 g) l1 d+ }- c
27+ Q" D% R( G" o. @* F6 ~
280 j2 C8 z. h' Q- f3 @7 ~0 \
29% B# |( b! _1 E5 E. @
30/ ^% r1 j6 b3 v3 W6 t3 E7 w
315 A6 @/ a, y7 H/ X& s( Y
32$ R2 J* y4 G! N, U5 f
10.2.4 时间戳的切片与索引
- l% b; r& d: R' S( U- e 一般而言,时间戳序列作为索引使用。如果想要选出某个子时间戳序列,有两种方法:, ^8 e& }# C0 s6 `+ O* `* j+ w
" x: x) p, c' s C
利用dt对象和布尔条件联合使用
/ i- Y. w& o. f4 o7 u利用切片,后者常用于连续时间戳。
6 {( R, U, m! [s = pd.Series(np.random.randint(2,size=366), index=pd.date_range('2020-01-01','2020-12-31'))
& v# E% Y, w! @1 K/ Yidx = pd.Series(s.index).dt! v& `, q" A$ S) Z9 q
s.head()
4 \1 V0 c) r: ^" R6 u M) ` `
& j- V" O+ l) G9 V4 m/ t3 I* v7 a2020-01-01 0
* v4 n0 X3 `& r5 N* `2020-01-02 1
+ y, \) ?8 b3 a$ L# _ o2020-01-03 1 {7 x, u2 q) v+ K$ Z% r' y7 y* B
2020-01-04 0
/ j( w& J5 F L2020-01-05 04 {, N6 G4 W& J0 e9 B
Freq: D, dtype: int32
7 T+ O6 U: D* D/ s0 }1% U) y% i2 k% u7 ?' M! U
2! }# U3 k; o8 H, ]3 ^4 K
3- @- B* K+ n" S [2 N
4 s% {( d" V$ c" ^4 R, c
5# q3 t9 y/ N# {$ l1 {& s8 g% x
6; P- ]( B! }3 s! N- Z
7
& `3 L* T B1 |2 d0 C8
/ N7 k2 H$ h! A+ P0 \9
) ?5 a& r8 ?, M10 \/ U% a6 d% N
Example1:每月的第一天或者最后一天# S% S( R# n6 u2 c0 `0 F& @
& G% B3 Z2 k& p3 y, B" t
s[(idx.is_month_start|idx.is_month_end).values].head() # 必须要写.values; m# E) W. g- e0 }! G3 T
Out[50]: 9 R' p. ]- _7 S+ S2 J
2020-01-01 1% k0 E0 C# }. g" x2 k: t2 ^
2020-01-31 0
/ l& [1 H% ~! k2020-02-01 1- M/ M, }% S. @0 U7 b1 P1 |) @
2020-02-29 18 b& l0 t* K8 x7 `& o. h9 g
2020-03-01 0/ W. F& c7 r+ s% D: }) |
dtype: int32+ D) T/ d( Q* V% `! j
1
% @1 Q/ Y# K* R; v- c2
: c; X8 V4 Z1 q; H5 `/ H/ ^" r3
" f: | m: v4 Z4 i' D. ]5 @$ N& k7 k) e* m& @
53 m: _' ^* \/ [8 k9 K
61 b$ w. E* f# g/ E$ X$ P. C3 g
7! F1 i" _" b- O, T9 V# p, J* w) P
8
$ `& b+ {1 Q7 V; T% mExample2:双休日; C: x! X% J! t9 k4 I: I- n
L% i% T6 S' M* Q
s[idx.dayofweek.isin([5,6]).values].head()
5 o( W2 _3 t( i* W7 [4 J8 TOut[51]: % \# m: x/ A* s0 T0 o
2020-01-04 1
; \, h. [8 j0 ^# ` c v2020-01-05 0
9 V0 e5 W# ~+ n% U1 ]7 s2020-01-11 0
, i3 W& s" q! P$ q# u4 U; p- A6 D/ j2020-01-12 1
& l3 k$ B; c) A3 n# z7 |# U2020-01-18 1, }/ W/ v( }7 U1 |
dtype: int32, m, Z2 N4 U7 Q5 u; I
1
* x. P% M5 T* I# Z1 K2 Z# C28 P* w0 t: O1 e
3
5 Q1 J1 B& n: Z) z& Q% m* i3 c4
' V$ Z: U' T/ @5
2 H9 h5 j; V5 }1 j5 \6 i" Q# J6
8 k: |* ^) u- S8 W7; ^+ ^7 O% u/ V6 |: x
8
. M/ U& d! D. |5 fExample3:取出单日值
: M3 G% j- u# Y3 {( |
@) u. q/ q( z! Ys['2020-01-01']
+ Q, }7 n1 @: B: Z1 ?, v) M2 @Out[52]: 1
+ Q3 |/ m( {/ A! S" e+ \( f: d6 ^) D6 W3 J4 r
s['20200101'] # 自动转换标准格式
3 k, c- ?1 ~$ b8 c/ v e+ h' dOut[53]: 1( X: I& ]9 {' i% ]
1
B' [* L- x* I5 r+ C2
0 H' t( }. w. i" t' G( k) }' U3
- n% \* e0 d8 e" A4. ~% B8 W8 ~6 r; _ A2 P, x. v
5
4 {( v6 q% N) {Example4:取出七月' U* i+ i) N. p5 \; m7 Y- `8 R
% o7 H: s7 ?5 Q# L; Qs['2020-07'].head(): ]/ \4 G8 V# @
Out[54]:
% N$ Y0 S& ^+ O) q, Y n d$ M8 p2020-07-01 0
" a: h8 ^) Q/ j# K* m0 p7 ~( H2020-07-02 1
+ x: A( N( y2 h3 W/ |2020-07-03 0# W+ R! G! P, S
2020-07-04 03 Y; Y+ ~2 f0 b9 L
2020-07-05 0
" Y$ K2 j7 u& d. FFreq: D, dtype: int32
* ], p& K: T' @, U6 z* ?( a* m6 D1$ u1 r! w& k* t9 G; k# m G$ z8 {4 K+ v
2
$ [# O5 ?% `- y2 R& {" j! O3
9 C2 A7 R; t, D! ^( T0 k+ v4
6 t" ]- Q$ \: a5
W' O5 ^& h2 n3 y3 q& \63 n. h! y* h+ a4 u8 t
7, g ]. v9 u9 J
87 Y9 g d, y2 f
Example5:取出5月初至7月15日
8 s f" x$ O1 q, K9 p3 J( g7 |9 ?* H! m3 t
s['2020-05':'2020-7-15'].head(): N" c/ h' N/ [- s4 U7 u: e; o
Out[55]:
4 b: W: L9 ^9 P" Z1 ^/ i! s3 M2020-05-01 0! I$ a2 T; [5 x; G4 W
2020-05-02 1
" ^! P$ X( h% ~6 F2020-05-03 07 J3 J; t2 C5 ~
2020-05-04 17 e8 }5 D7 i- y# L
2020-05-05 1
7 Y" {; V4 }- T, U) W! h5 GFreq: D, dtype: int32
" b, i# ?* J( L, y# P! s
6 y; S( ^) x; _' V: L8 k2 U0 `s['2020-05':'2020-7-15'].tail() r5 _( L7 l: G. N! q
Out[56]: . Z. M. n2 Q0 j5 I5 h# u9 Y* u
2020-07-11 0
9 g y, M* Y8 x0 F2020-07-12 0
1 Z# d% B- a, Y+ k2020-07-13 1& f% v8 H5 t) I3 E
2020-07-14 0" R! O- f( B* ?/ ]! m* ~
2020-07-15 13 C0 c9 x$ a& N, d2 H d* `
Freq: D, dtype: int329 Q0 a7 `# F0 f1 U6 P8 S" D" t
6 F& i5 E, K: k1 P. K' X
1% G7 H) F8 M, |, `) G2 r# o
2( b1 R6 g0 I7 H+ V
3
9 d) u( H9 Z2 j/ s8 `1 B4* D/ @$ [/ t9 }. ^ w4 V( v% q; Q
5% v* x Y y3 ^9 T% v
6' v9 V( X4 d5 L. \
7: d9 B0 ~2 N' ~' t* y g
87 f: N$ @% j% P. t1 _9 ?
9
. N6 d) t) K' B, f7 R) A10
! T& ?8 y7 e, ~/ d9 P4 v11
0 c9 n a% r9 j* `$ J2 [1 N129 {) D2 H% M: g7 R
13 l+ Q" \5 U& F4 b( {+ r
14
# C4 _+ z% {# j8 }) z% A15' M4 @3 w$ A" P" x& w+ a( V; ]
16
4 |0 }% c9 P. k5 ?* g" y17
+ E6 ]1 G' v# t3 C10.3 时间差. d! t. n3 F) V" U! d3 e, ^
10.3.1 Timedelta的生成# ]/ G5 [1 H H1 G5 ]; o" _8 b. E
pandas.Timedelta(value=<object object>, unit=None, **kwargs)$ {4 x& |3 ]6 }& D m
unit:字符串格式,默认 ‘ns’。如果输入是整数,则表示输入的单位。! H& A% {) T2 H0 L( Y
可能的值有:
8 _- o2 T7 `6 F# y/ H
- q) Q6 M& G6 L" e6 d‘W’, ‘D’, ‘T’, ‘S’, ‘L’, ‘U’, or ‘N’
, d5 f" W! Z7 S+ k& s/ \‘days’ or ‘day’; M ^/ _( i5 d4 M
‘hours’, ‘hour’, ‘hr’, or ‘h’
3 f2 T! f: d i) G7 e g‘minutes’, ‘minute’, ‘min’, or ‘m’
1 R& Y. h% D/ o‘seconds’, ‘second’, or ‘sec’
) L% K. v& W8 m0 [$ Y毫秒‘milliseconds’, ‘millisecond’, ‘millis’, or ‘milli’2 M% D3 V1 F- r2 s
微秒‘microseconds’, ‘microsecond’, ‘micros’, or ‘micro’
) a, ] J ?* V6 _1 h) x, `纳秒 ‘nanoseconds’, ‘nanosecond’, ‘nanos’, ‘nano’, or ‘ns’.
0 ^0 E* X! M( H* l! ^5 S6 ]时间差可以理解为两个时间戳的差,可以通过pd.Timedelta来构造:
% T& P; A* ]5 A6 n0 e1 Z& h& npd.Timestamp('20200102 08:00:00')-pd.Timestamp('20200101 07:35:00')2 d: O+ a3 ^4 j) v8 n6 o7 n
Out[57]: Timedelta('1 days 00:25:00')" c% j1 D3 G9 C- h' s" P
, J/ S( i: _5 z0 _
pd.Timedelta(days=1, minutes=25) # 需要注意加s2 o: ]9 r+ T5 j! q# `3 j" k
Out[58]: Timedelta('1 days 00:25:00')6 m& s% _' A% Q6 ^, l( v" N) V* T# l
) W* p# C1 L) Q# ~1 Ypd.Timedelta('1 days 25 minutes') # 字符串生成
6 e& X7 R, A( A$ {& x! n6 r gOut[59]: Timedelta('1 days 00:25:00')
' Y' W; `. B5 q# s, s
3 W! g% W: D/ J F/ [' p8 hpd.Timedelta(1, "d")( Z) V. E8 Y. D# O. ~1 r
Out[58]: Timedelta('1 days 00:00:00')
7 ^6 A8 P8 s8 Y Z1
2 _6 }+ j1 M: x9 I1 Q2
; f, K- A) Q; b, j3" X2 h, D5 J1 o J, v) I: L
4& _1 H$ n0 _0 c5 e! k. B5 P
5
7 n( \% U& `$ Z( W" L6
: n8 f% i" B4 W$ `7
0 m- U& ^$ k) A- X2 ^ Q8 B+ q8
4 m9 J& s _# i' g; E* J+ k9, G9 p. U. U, u3 J. v, x/ w6 O
10$ D: a2 h& H( k- L# p- Z
11
1 k3 j& v' r1 a0 h/ b生成时间差序列的主要方式是 pd.to_timedelta ,其类型为 timedelta64[ns] :
' @8 c! h7 b* A4 Ns = pd.to_timedelta(df.Time_Record)
# w+ c1 P) m; A$ {/ U+ v8 ]8 V3 c* J* K4 R
s.head()
h2 r @4 ?' i8 X: _; D0 ~5 } H2 oOut[61]: 8 g* V/ P8 ]9 {; K0 T. E
0 0 days 00:04:34
" G) c( e+ X" B- W# Q& L1 0 days 00:04:20
$ t' U! Q- e+ ] U: ^+ { W2 0 days 00:05:22/ W( m! i' T/ B+ T9 m
3 0 days 00:04:08/ U6 x- U( q" f) n7 L4 g7 \
4 0 days 00:05:22
1 @4 f" U! k& R+ X) ?7 WName: Time_Record, dtype: timedelta64[ns]% A2 S/ ?. h) _0 X$ ]
1; i4 A8 s! T* n4 O6 [1 ^
2
% c( ?& _1 v- \3" t; G& @) C; F& c! D! [
4- U' K+ A& m$ ?6 A( ]
5
- `2 y, z% C Y4 {! k/ A6+ C$ j V/ d B6 s
76 q. i: l6 Z' @
89 H) N) b/ `, O; L+ ]; y K
9
Y2 n0 t, y, i- L: U10& Y ^7 Q% N7 M; q2 h# c- R
与date_range一样,时间差序列也可以用timedelta_range来生成,它们两者具有一致的参数:
3 o4 g% H/ Y ]2 @0 U" Qpd.timedelta_range('0s', '1000s', freq='6min')
. S5 \! ?* f$ f9 lOut[62]: TimedeltaIndex(['0 days 00:00:00', '0 days 00:06:00', '0 days 00:12:00'], dtype='timedelta64[ns]', freq='6T'). W9 B5 o; N- g9 |; B' \
" _9 A" y' D$ Y Cpd.timedelta_range('0s', '1000s', periods=3)
) R {9 |9 A% ] O z' _ lOut[63]: TimedeltaIndex(['0 days 00:00:00', '0 days 00:08:20', '0 days 00:16:40'], dtype='timedelta64[ns]', freq=None)' p9 _# c, O7 z! c0 }9 X
16 |/ S5 k' b+ t0 K0 V6 D& x
2
# x' t5 b& j/ U1 c36 p) X+ H; @' [; a" c) u3 G
44 Y6 J v" G: g+ {; z2 M
58 _4 l W8 P' ~+ N/ u
对于Timedelta序列,同样也定义了dt对象,上面主要定义了的属性包括days, seconds, mircroseconds(毫秒), nanoseconds(纳秒),它们分别返回了对应的时间差特征。需要注意的是,这里的seconds不是指单纯的秒,而是对天数取余后剩余的秒数:3 @: ~( |- H' q N
s.dt.seconds.head()
* z% [0 [; Q; k: n3 oOut[64]:
0 }- ~ X7 n* i$ Q0 274+ ^; q: F7 i: A4 g6 }
1 260# }4 F* J. I& W( O+ f9 u
2 322
* P. X, ^5 j8 Y& ^# W9 P8 ~8 D3 248: p3 x" h' y/ K8 D
4 322
$ N6 ]5 H+ o% r9 K' K. _* NName: Time_Record, dtype: int64; \% K# z0 T) {! l
1& M4 V' ], } p. L8 i) ^
2
, j% N/ u; M; T# o- J6 y" U' o3) Z, q9 J g7 T" P) p9 V
4$ c: {' |3 [- s! r; k3 j8 z
5
7 x/ d& H. Z5 X: V6 C6
4 A8 O+ O. C2 L7% h Y7 D. u9 d- l( K: d
8
- m" v; `' q% W+ n9 T; n如果不想对天数取余而直接对应秒数,可以使用total_seconds( Z8 e K0 j# K
" x3 T' u' ~6 [ W4 {s.dt.total_seconds().head()
" Q7 C. s. n* r% I9 [" k( \ pOut[65]: 5 Y7 p. }- k$ l9 S+ j1 P9 ~/ g
0 274.03 p Y# Y- t( Z$ ?
1 260.0! u! s& ]" O, x
2 322.0! i5 j& R: e1 j
3 248.09 k0 r: p/ w! ?, ? Q
4 322.0
0 y+ `% W3 r8 D+ U) N( dName: Time_Record, dtype: float64
$ T4 n' ]7 U9 o) D' a, ^3 @+ M1 d10 C& O; e# }( k, _9 R2 I9 d
2
) x" G) v. B! L) k6 o3
$ P0 x5 p, _+ t8 ~$ H' ?5 l4
# U3 V3 I2 i! a5 P- B; f5
1 i2 e5 c( V6 z s. |( M6' J( t: Y8 t5 @) o& q
7: u5 I4 k3 g) q5 f5 T
8
6 _8 z5 Z3 [2 c" S: \; u% L1 d$ T2 y6 O与时间戳序列类似,取整函数也是可以在dt对象上使用的: ?& o5 K$ m* r; t8 v+ d
& y6 a( J. J! \$ r. m; Upd.to_timedelta(df.Time_Record).dt.round('min').head()% p5 }! r3 t: r- H8 ~. Z, V8 _1 B
Out[66]: 7 E. O* w9 G k6 c! e
0 0 days 00:05:00) @3 M' P& q4 H' x
1 0 days 00:04:00; P1 J8 b. A2 _5 i5 ]2 g, I* B
2 0 days 00:05:00# \5 D% M2 V z: t1 c- v
3 0 days 00:04:00
' y" s, H$ k7 ~% J8 q8 q4 0 days 00:05:00
! r2 u+ E1 a% eName: Time_Record, dtype: timedelta64[ns]7 P; p( K9 d, ]# N
16 q K& N7 k% R- F
2
) {0 E% W5 ~5 F- _$ j! ~34 ]' Z9 K% z. l% n* `" {( {8 Z
41 O$ i$ Z; L/ V1 w9 e- |- F- R
5 L. `* Z/ d& R9 \
6
! s# S' Q& ~9 K, g1 g( t7
2 X' X/ r) ]4 L V- n9 Q+ B& b8
: G% A* u5 r+ S( e! x3 {3 G10.2.2 Timedelta的运算; {: M' f" x- g3 F& ~4 S# N
单个时间差的常用运算,有三类:与标量的乘法运算、与时间戳的加减法运算、与时间差的加减法与除法运算:
# S* F0 V& ~3 T X: k+ E: `$ htd1 = pd.Timedelta(days=1)
" ^6 q, `' W# {2 a3 l6 O1 X- ?2 j( Wtd2 = pd.Timedelta(days=3) q( W) y$ s6 P5 ?# u
ts = pd.Timestamp('20200101')
/ ?( W: J; ]* k% I) f- b6 I A3 u) J
td1 * 2% C3 `$ a' @9 ?
Out[70]: Timedelta('2 days 00:00:00')4 h+ ^. L/ l; j* {+ T2 \. w
0 c( o6 _# M) U2 F6 [2 k# D' C) O
td2 - td1
% e" ]0 {8 d0 t. S N6 KOut[71]: Timedelta('2 days 00:00:00')6 v1 f3 `! w# z: M( d7 {
+ l ^; \6 x( _
ts + td1
% ?) `. I, U+ @; F$ KOut[72]: Timestamp('2020-01-02 00:00:00')
, o/ `7 R0 {& d/ [
6 B; x3 A: T0 O) P8 T; D4 `7 V( cts - td1+ l" [ C- w0 q" |; f! b( T5 K6 s
Out[73]: Timestamp('2019-12-31 00:00:00')$ _2 V1 r. t# I# @" X
1
! L# a ^4 I, b2, w9 c5 }0 x' X; Y9 U \' N6 n
3
4 D2 C0 a# h* w+ s" K- ~( Q' j4
+ Q; ?0 z3 B* ]9 z E5# x; i1 Z7 Q% y! L0 q
6
8 G& F6 O3 y3 D9 e7" `0 M) P/ F3 X4 r
8/ f' \2 U0 l) d8 V! U; @3 k
93 N O. F- m" a. [8 f* O+ D
10
" j _* C- a7 f8 l, T110 H7 m- q' ?; x$ E: y* |+ f* O5 l
12
+ ]7 N: w' c3 z& G' v$ ^13# f! {# l0 i, d$ T
142 G% x7 n7 M% e
15
; f6 A0 v6 I) _$ R2 r时间差的序列的运算,和上面方法相同:% K! W5 ?/ l5 i- ` Z8 k/ I I" r
td1 = pd.timedelta_range(start='1 days', periods=5)3 U' [+ w2 X1 h/ l: P
td2 = pd.timedelta_range(start='12 hours',/ I b1 j. L( S6 m, i
freq='2H',
0 {( N* X+ ~' u" M( B' E periods=5)1 d+ ]: |5 {3 B% T5 H/ k
ts = pd.date_range('20200101', '20200105')
6 H! w: r! N8 E- @2 r4 n/ Atd1,td2,ts3 q! C$ x, u" @: X6 t0 y' {( f2 O
+ l1 @+ Y# i* C; y9 T+ w) B, i
TimedeltaIndex(['1 days', '2 days', '3 days', '4 days', '5 days'], dtype='timedelta64[ns]', freq='D')8 K- S9 p2 P$ A8 Z$ H* G2 i
TimedeltaIndex(['0 days 12:00:00', '0 days 14:00:00', '0 days 16:00:00',9 S i& K% B$ t8 D" q1 F3 o
'0 days 18:00:00', '0 days 20:00:00'], dtype='timedelta64[ns]', freq='2H')$ b& V' v" s1 q
DatetimeIndex(['2020-01-01', '2020-01-02', '2020-01-03', '2020-01-04',
a' Q% K# G) y2 X, w '2020-01-05'],# }0 z! U* }1 n3 W$ X. V7 g
dtype='datetime64[ns]', freq='D')
. A4 @% T, z4 C' w. } u19 \& n2 [/ x& ]
2
$ d" a( D9 ], ^9 o: y3- M3 m- a! G3 Z0 _: M: I) X8 _
4. o& q% Y3 e: Y
5
X% d7 v2 ^- F! q, ], u6( j% X) Q- q+ I3 k r
7( t/ g6 m2 I$ v3 A
8
8 H& W h- G9 s, J9
' O) W# n# d; ~. X104 A( G; e- ?+ }/ d
118 j6 o2 z+ S* f- {1 a3 e
12
5 g0 g* @* _) l# s, N13
+ u! r# D6 I' ?! \ u( S! k1 vtd1 * 5
" a8 c6 a! N: m+ x- O) e. ~Out[77]: TimedeltaIndex(['5 days', '10 days', '15 days', '20 days', '25 days'], dtype='timedelta64[ns]', freq='5D')! X3 e8 ?* c6 s7 G
' G, P* w+ m& x5 A% d3 G# u7 Dtd1 * pd.Series(list(range(5))) # 逐个相乘$ Q8 B% h3 F' h6 a! C# R) C
Out[78]: : f1 {! c: R5 L- ?6 M
0 0 days: q5 b4 |# X0 V1 D0 ^3 j9 C* \
1 2 days
! G/ Q7 y4 Z4 c9 ?2 6 days$ |2 |5 P u0 u" R
3 12 days
4 O; @% W0 c' I) |, c* E1 s Z4 20 days
2 }2 N* W; }9 _- A% Kdtype: timedelta64[ns]8 k1 k8 ?6 q+ r R' G: W5 M
5 ^0 C* z% u1 I- T
td1 - td24 `) g5 y$ d# @# @: p/ N3 }
Out[79]: , X3 J7 @% Y, f# ?, t
TimedeltaIndex(['0 days 12:00:00', '1 days 10:00:00', '2 days 08:00:00',
5 v# L" m, T+ q, F* l9 _ '3 days 06:00:00', '4 days 04:00:00'],7 m+ t0 Z3 Y; F) E$ W
dtype='timedelta64[ns]', freq=None)
+ L7 Z# Y! W+ E2 G/ S+ L x1 s1 T
, S4 j4 \3 C2 Q- A8 y) mtd1 + pd.Timestamp('20200101')
5 u* I% a$ X3 {: H+ l' v8 i! ~' D5 \4 sOut[80]:
2 J7 Q1 x+ W$ _DatetimeIndex(['2020-01-02', '2020-01-03', '2020-01-04', '2020-01-05',9 Z. e `6 d2 U- S
'2020-01-06'],dtype='datetime64[ns]', freq='D')) Z" ^- t, t: U, j* b0 k
, _( q4 a* _8 M: m$ n9 W8 J9 }td1 + ts # 逐个相加
1 [% ~, g4 Q# a$ H+ b5 ROut[81]: 3 Y+ {; H& ]& t F: ^! K
DatetimeIndex(['2020-01-02', '2020-01-04', '2020-01-06', '2020-01-08',' D& C4 Y( }$ ^
'2020-01-10'],
. d. K ^# A/ A& t/ ?6 v/ f1 B dtype='datetime64[ns]', freq=None)0 u. p8 A: B6 e- D4 b% ]
$ f& A1 g+ O+ R2 O) x12 [+ ?( Y4 P7 E& `% w
2+ k" F4 R$ u; y8 X6 `1 C
37 E6 n& r. j- J& B$ b- k
4
- k7 k% [$ V r! D: N7 _0 e5' o8 s) Z, }( D- L
6& Q i9 n" N( O+ o' c! ?1 o" f) Z) U
7. s, h! w+ m" w, W) s# v6 X# V. e
8 q- v( e/ F6 f% r1 O
9, Y) k$ u4 g/ N
10
. S) d; H0 T- o3 v _6 ]- W11
% J* u$ j! s4 a" F122 g* \2 Y! T' \7 V1 W/ i
132 L8 N. I* W. I9 Q
14
# ^% V: b: s/ u {+ W15
9 \" T4 K# k; U, Q ^( F' u; q: Z16
+ b% ?+ @+ P; }5 {' ~$ q0 x% V4 Y17
2 ~8 }& g2 u9 T3 A' [6 ?18 ^# ^0 k4 a1 q
19
! U$ \8 B& N2 q: r' j1 q8 M, F; {20* v i( a0 }+ x W5 w
217 L$ T! w# K8 Y7 T* B
22
& z Q+ X* Q' R- D7 S1 d23% } `) f% o8 u" ~
24
* M2 Y9 S6 d; o8 Y& ?254 H! h* u7 O6 N5 H% {
26, a* K( K& h" f( q- l! V( Y
27; X9 z/ |# E! T$ h+ E( O
28
( E. e# i L B10.4 日期偏置
k. D p, q1 V$ D7 b# v' a& k; g, k10.4.1 Offset对象
+ D4 \0 y& P. R. j% X* o 日期偏置是一种和日历相关的特殊时间差,例如回到第一节中的两个问题:如何求2020年9月第一个周一的日期,以及如何求2020年9月7日后的第30个工作日是哪一天。! d, D" e! u, v' L- J
1 S6 Y0 y8 }$ E; T) ?6 y% N
DateOffset 类有10个属性,假设s=pd.offsets.WeekOfMonth(week=0,weekday=0),则:
% W& w& }9 @4 d8 F6 H8 I' H4 v: l" C8 Z% G& K" G* V! E K
s.base:<WeekOfMonth: week=0, weekday=0>,返回 n=1 且所有其他属性一样的副本
0 k7 i8 W/ c5 s/ W+ ]s.kwds:{‘week’: 0, ‘weekday’: 0}* Z! [8 s' }& D$ m2 H g
s.wek/s.weekday:顾名思义
1 X- a- Q6 u, {9 x }& e2 S有14个方法,包括:
! Z3 k, Y! t/ F, p) T
3 C+ Y* A2 o& B; W8 Y# y% ADateOffset.is_month_start、DateOffset.is_month_end、DateOffset.is_quarter_start、DateOffset.is_quarter_end、DateOffset.is_year_start、DateOffset.is_year_end等等。
D$ l4 v7 {+ ^pandas.tseries.offsets.WeekOfMonth(week,weekday):描述每月的日期,例如“每月第二周的星期二”。
2 |5 m$ J+ b8 C. i$ V, f! v C1 h- R( `
有两个参数:
; m0 U7 l& N) @4 D$ Z* E3 wweek:整型,表示一个月的第几周。例如 0 是一个月的第 1 周,1 是第 2 周,以此类推。
& W1 ]0 E: a% z& Hweekday:整型,取值为[0,1,…6],表示周一到周日,默认取值为0(星期一)
9 m3 u% y$ C( N4 |2 ?pandas.tseries.offsets.BusinessDay(n):相当于pd.offsets.BDay(n),DateOffset 子类,表示可能的 n 个工作日。
+ W* ]8 Z8 W3 x. ]" G; _6 W0 g5 I6 c; D2 J: g g0 ?
pd.Timestamp('20200831') + pd.offsets.WeekOfMonth(week=0,weekday=0)9 | {$ i) S) y
Out[82]: Timestamp('2020-09-07 00:00:00'); n& k G- d# \9 i( p
! h+ c& x" e6 s3 Tpd.Timestamp('20200907') + pd.offsets.BDay(30)* S' J6 W9 d5 l- ^. `% Z2 r
Out[83]: Timestamp('2020-10-19 00:00:00')
, _" P ~- [& L' [: M5 s- _1" H' |, A8 U% X4 U; ~2 w) [, Q) S9 ?
2
3 P+ W+ z2 w, H ^1 a8 C36 H8 d7 t/ f! X& |: H- g
4, W) R4 J& g) L7 S' R8 n7 a
5
; U$ H7 ^ m+ g+ P' m9 J' J 从上面的例子中可以看到,Offset对象在pd.offsets中被定义。当使用+时获取离其最近的下一个日期,当使用-时获取离其最近的上一个日期:& v1 ^0 X+ E; I: O
- ?* |6 W7 N* u
pd.Timestamp('20200831') - pd.offsets.WeekOfMonth(week=0,weekday=0)
/ N. `2 G. Y9 ~$ u' i3 vOut[84]: Timestamp('2020-08-03 00:00:00')8 r8 i, y, z) W! F; c$ q$ U
& d3 W( J( e+ u
pd.Timestamp('20200907') - pd.offsets.BDay(30)
; y; e4 s5 W- _1 \' v" LOut[85]: Timestamp('2020-07-27 00:00:00')
* X8 e3 M3 {; ^- N& v+ a7 B7 J4 K7 ]6 k9 I/ k& q
pd.Timestamp('20200907') + pd.offsets.MonthEnd()
& v/ R _" f' C/ p+ m) ^# NOut[86]: Timestamp('2020-09-30 00:00:00')9 a: g0 Y( H7 m+ \ a
1& M( s7 l2 A$ M8 A' t3 f
2
; Q+ x1 U4 a0 B5 u/ N2 f/ i3
2 c$ t& n \+ P( }8 x& i4
4 }0 c& m7 @6 l" P8 x7 v5
4 l" Y6 }# t. O, {0 N# l60 K/ x8 d/ P, g7 ?
75 e, [/ m: u/ ]7 ` @* Q) r
8
% y0 l. p, j, z5 X3 b 常用的日期偏置如下可以查阅这里的DateOffset 文档描述。在文档罗列的Offset中,需要介绍一个特殊的Offset对象CDay。CDay 或 CustomBusinessDay 类提供了一个参数化的 BusinessDay 类,可用于创建自定义的工作日日历,该日历说明当地假期和当地周末惯例。
- b) C5 L. I) {6 v 其中的holidays, weekmask参数能够分别对自定义的日期和星期进行过滤,前者传入了需要过滤的日期列表,后者传入的是三个字母的星期缩写构成的星期字符串,其作用是只保留字符串中出现的星期:# _6 C1 A! m6 `4 |3 T* G
! L9 ^2 T0 O7 M. s$ ?2 _my_filter = pd.offsets.CDay(n=1,weekmask='Wed Fri',holidays=['20200109'])
' K. f* v9 M! M5 z$ O- Ndr = pd.date_range('20200108', '20200111') O% E: v6 Q. e& E
0 x, t- C; H4 ?3 M# ~5 jdr.to_series().dt.dayofweek
0 |/ X, V! q! q6 A% GOut[89]:
8 W" _1 e6 I0 s5 H3 s2020-01-08 2
: _) o% |7 M9 c" s- i0 q& l4 ]9 O2020-01-09 3
: R( `/ T- U6 W/ m& G a- w2020-01-10 4: y0 L$ e+ |1 C4 s2 z+ ]1 A6 E7 X% T
2020-01-11 5+ R5 z* x/ @) v5 a
Freq: D, dtype: int64
( S- i# ]0 V5 v, p- Y, h! @- [% o! ]
[i + my_filter for i in dr]
! _9 N ^! m1 h5 J8 t. A4 H& ?Out[90]:
: F' S7 }* J, o$ A* [- M[Timestamp('2020-01-10 00:00:00'),# u# [4 \; f. C+ Y) @. F3 w$ x, D
Timestamp('2020-01-10 00:00:00'),
% y6 l/ a2 B; C( ] { n Timestamp('2020-01-15 00:00:00'),
, g" h* y3 |5 T' ^ Y Timestamp('2020-01-15 00:00:00')]: H% x& b' B% i c7 O
! h0 H j, B4 C# b0 y1
4 U: F6 m6 _2 |. ]4 v+ ?/ Z2
( g6 d: r2 o& Z' o# P37 J+ Q7 y* \( B [: d g
4- r4 ?, H- r; J( L
5: r5 w/ {* t& ^2 w
62 h( q/ k6 J# [. R; q- \( k
74 X& @3 C# d6 a- E
84 M: Y$ A, `0 M
9
# o$ ]6 P' w% G" F( u6 m8 A9 n1 S; y10
3 c7 Z8 M% Y0 K. f- y% N# M11" ?8 S, f( t* K
12
7 R4 ^* K9 J$ b: S& B13
" \- X& L% h% Q% N14
* w c) ?( U, i4 h15
4 P9 s- ^2 H' G# U16$ p6 a- {4 l/ u" X. k0 q
17
7 f0 {3 o: O/ t8 i4 x% P 上面的例子中,n表示增加一天CDay,dr中的第一天为20200108,但由于下一天20200109被排除了,并且20200110是合法的周五,因此转为20200110,其他后面的日期处理类似。9 l0 a4 E. n5 L6 V: U S6 g
% g7 j+ z6 m) u# ?
【CAUTION】不要使用部分Offset
$ @9 R- Q3 ^, y3 H, _在当前版本下由于一些 bug ,不要使用 Day 级别以下的 Offset 对象,比如 Hour, Second 等,请使用对应的 Timedelta 对象来代替。4 B% Z" s! S. K; P( k" H' U+ y2 s
6 Z% U6 J- K3 G; `# M10.4.2 偏置字符串
# x% C) h7 f' V. v5 f. ~ 前面提到了关于date_range的freq取值可用Offset对象,同时在pandas中几乎每一个Offset对象绑定了日期偏置字符串(frequencies strings/offset aliases),可以指定Offset对应的字符串来替代使用。下面举一些常见的例子。% X0 W$ W E# K" H/ r; }
& S2 C% a. F5 m( R+ `+ |/ p
Offset aliases:pd.date_range函数中的freq参数,为常见时间序列频率提供了许多字符串别名。 也称为偏移别名Offset aliases。偏移别名列表点此参看(大概27个)。$ h; U& k4 W, V4 |$ D
/ ?9 d H3 Y! y9 {
pd.date_range('20200101','20200331', freq='MS') # 月初
! F- M# x; c+ |: D8 \( [Out[91]: DatetimeIndex(['2020-01-01', '2020-02-01', '2020-03-01'], dtype='datetime64[ns]', freq='MS')
9 D% E* j4 t9 M& M% n
* Q1 J4 c# D. J3 X. U2 Qpd.date_range('20200101','20200331', freq='M') # 月末
2 S6 i) o8 @; N% Q9 z1 C. L- u! kOut[92]: DatetimeIndex(['2020-01-31', '2020-02-29', '2020-03-31'], dtype='datetime64[ns]', freq='M')
/ q, t j8 l) `: Z8 X5 R0 _& V3 W5 |. \7 h0 w9 K2 H; ^
pd.date_range('20200101','20200110', freq='B') # 工作日% l9 U: _9 A9 u
Out[93]: 2 q! u; D1 d4 [3 ?- h0 o' [
DatetimeIndex(['2020-01-01', '2020-01-02', '2020-01-03', '2020-01-06',; v# ~- u" J5 G; ^+ P4 Q
'2020-01-07', '2020-01-08', '2020-01-09', '2020-01-10'],
; z+ Y. H; K- G& k, G/ A dtype='datetime64[ns]', freq='B')& R' _1 V% C$ S+ B
$ e5 c6 b6 B1 F( o
pd.date_range('20200101','20200201', freq='W-MON') # 周一
3 T# E, v9 N& X4 NOut[94]: DatetimeIndex(['2020-01-06', '2020-01-13', '2020-01-20', '2020-01-27'], dtype='datetime64[ns]', freq='W-MON')7 M" ~, e8 Q4 J. `( a; O/ A6 Z
$ K( Y! |" g$ b2 |7 S# Wpd.date_range('20200101','20200201',* ~3 i* ]1 l# A0 }
freq='WOM-1MON') # 每月第一个周一6 |: U% v+ Y- h9 _! a2 P9 E4 R1 v
8 ^9 i" h6 N/ ^5 ]Out[95]: DatetimeIndex(['2020-01-06'], dtype='datetime64[ns]', freq='WOM-1MON')
8 z; _; D$ V( `8 A
& t. ~' `4 x+ p2 s" {1
4 N* Y( F+ r' h2 U2
& t8 i5 F9 ^" X+ w2 C4 q! F39 B9 Z' Q0 L4 E
4
8 ?/ C% c& s2 b/ T5
/ F. d. X$ g0 {+ p' q/ e1 E" i6$ D$ h4 M! u* c
7
* d$ a) P h1 N8
: R* y" h8 l1 s: {( p! W2 [) B$ E97 O) ~: q& h* X& G5 N( P1 B/ b
106 g- K" c! q1 p
11
1 ]0 o9 ~" ?/ o j3 e6 v* v12& L' Q+ C/ _, w% B3 k. {1 g8 z9 h
13. a5 d0 d9 f8 |; o
14/ |" {; s) v0 X% y' t
152 N; I" X3 T- v$ g
166 S# W7 U) P* f# G; ~% Y
17; D: J4 J O* L2 |- w
18- @7 k' s- V& J) @* b A' q: @
19
* U1 P& a, n! i$ B/ x8 i& o5 `7 Z, ?上面的这些字符串,等价于使用如下的 Offset 对象: t4 M5 t) M: h& E
6 _6 T' ~, F& y& ]: a, |" Q" G
pd.date_range('20200101','20200331',8 p* ]; @/ ?' v3 u$ |/ i( w
freq=pd.offsets.MonthBegin())
8 C! {* m6 C0 O8 j( K U% x: ~) g4 t$ ?. b, S
Out[96]: DatetimeIndex(['2020-01-01', '2020-02-01', '2020-03-01'], dtype='datetime64[ns]', freq='MS')" g) [$ ]& i' u5 D4 G8 k+ D
% V* Z- i' k7 O7 ^3 mpd.date_range('20200101','20200331',7 d! R n6 `% z+ k
freq=pd.offsets.MonthEnd())6 u! O/ \, Z1 z% G( @0 M
: |. g# y* ?! m) O" s. c& z% N# `
Out[97]: DatetimeIndex(['2020-01-31', '2020-02-29', '2020-03-31'], dtype='datetime64[ns]', freq='M'); ]0 u9 `3 Y, Q1 o5 Q
0 Q5 a9 R3 c) X/ ppd.date_range('20200101','20200110', freq=pd.offsets.BDay())
- g4 f: H+ B' y$ @# _0 rOut[98]: ; j; @7 z; D& a- Q
DatetimeIndex(['2020-01-01', '2020-01-02', '2020-01-03', '2020-01-06',
9 M% X, S/ f w+ F9 S' Q '2020-01-07', '2020-01-08', '2020-01-09', '2020-01-10'],
7 ~4 R8 C6 l- `" X n$ S* A* d dtype='datetime64[ns]', freq='B')
0 l! T2 d! z6 m8 o3 D: n# s" n1 u5 B# p, X
pd.date_range('20200101','20200201',
$ s0 t! k5 q1 C t" f freq=pd.offsets.CDay(weekmask='Mon'))7 D* J$ J! w' \2 ]
& D% N r7 j: `9 F1 k2 oOut[99]: DatetimeIndex(['2020-01-06', '2020-01-13', '2020-01-20', '2020-01-27'], dtype='datetime64[ns]', freq='C'): `& |4 z! i1 \ Q' W% [4 h
U' {9 y( \, I( a7 b* d" vpd.date_range('20200101','20200201',
# T( `( E( _4 { freq=pd.offsets.WeekOfMonth(week=0,weekday=0))# i4 e* W7 [, N# r
0 B1 c! g) X0 qOut[100]: DatetimeIndex(['2020-01-06'], dtype='datetime64[ns]', freq='WOM-1MON')2 N e' Z4 u- [4 V" o
) ? _" |" @8 B+ a/ a: V
1
6 D$ P) \$ e! z8 Z3 s2
9 V* F8 z1 ~* W( H6 Q1 c* K$ U3 ?3
' q9 y9 R4 t/ p6 e/ G4 V; R$ Z8 i4
/ B. ]4 F$ L# B0 A* d52 r5 O9 {9 J( g& `) f
6
) r* d. b$ z' o( I0 I76 Q7 u v2 n- r: [) ?$ g9 C+ j
8
. t/ P& F. \ K2 ]2 i" K9% n( j/ n1 k( Y6 Y7 _9 ^- w
10
3 k+ t2 T Y6 A11
* q C* L2 U$ K" Y12+ q, o3 h* }3 M# N D% r
13. K: Y5 ?9 a7 [
14/ j4 Y( ~' J' Y4 l8 ^2 M9 ^* ?. x
15
2 f6 e" [+ A( J8 b- s6 R& R/ s16
2 i# r- o" m- Q! E2 @) E" s17
/ b( H" K; t' i0 @+ \18# i& T7 u6 U% R
195 K+ h* Y* }( }% h/ w$ m: S
20, B6 G/ B: Z! O
21" ^( k8 [ B/ D6 i5 C4 m; ~
22
1 _; G {6 z' b8 P# I230 L/ e) h. S) }* G% \6 }3 ~
249 E7 [# s- P8 w/ B, v0 h9 h ~- M
25
2 F) O; Q6 A: {7 H8 Z- x! @【CAUTION】关于时区问题的说明$ p* [* t2 R; f4 U; H
各类时间对象的开发,除了使用python内置的datetime模块,pandas还利用了dateutil模块,很大一部分是为了处理时区问题。总所周知,我国是没有夏令时调整时间一说的,但有些国家会有这种做法,导致了相对而言一天里可能会有23/24/25个小时,也就是relativedelta,这使得Offset对象和Timedelta对象有了对同一问题处理产生不同结果的现象,其中的规则也较为复杂,官方文档的写法存在部分描述错误,并且难以对描述做出统一修正,因为牵涉到了Offset相关的很多组件。因此,本教程完全不考虑时区处理,如果对时区处理的时间偏置有兴趣了解讨论,可以联系我或者参见这里的讨论。5 u# T9 K: t: M4 R, e1 l
3 l! q2 _$ F2 n10.5、时序中的滑窗与分组
" ~$ _+ m1 Y0 }1 m10.5.1 滑动窗口
1 r5 u) S5 y8 f3 l& a3 B" ] 所谓时序的滑窗函数,即把滑动窗口windows用freq关键词代替,下面给出一个具体的应用案例:在股票市场中有一个指标为BOLL指标,它由中轨线、上轨线、下轨线这三根线构成,具体的计算方法分别是N日均值线、N日均值加两倍N日标准差线、N日均值减两倍N日标准差线。利用rolling对象计算N=30的BOLL指标可以如下写出:9 ~& O& d$ U) y) b2 e
?: X8 _5 f8 h' y- G3 [8 D9 kimport matplotlib.pyplot as plt! c. ^- Y5 i' P
idx = pd.date_range('20200101', '20201231', freq='B')
: |% V- ^% D: X' _$ G' F" qnp.random.seed(2020)
# `* g# }1 `) w+ H. R
7 _* f2 ~5 G6 _5 p4 `" Y3 Mdata = np.random.randint(-1,2,len(idx)).cumsum() # 随机游动构造模拟序列,cumsum表示累加
4 E& o$ ~% _/ A" B# Ks = pd.Series(data,index=idx)
( D8 w+ Q: s- os.head()
! }& X2 ] ~4 G& T9 sOut[106]:
* C" {" Y0 [/ ^- M" o4 O, h2020-01-01 -1
6 a2 B1 k! T0 ~' R6 j2020-01-02 -2
( U7 y( B0 u% a( C% r2020-01-03 -1# ~$ [; j* N, g3 z% o2 Q+ @
2020-01-06 -1
2 |5 \' F$ x9 Z# { K% U2020-01-07 -27 Q6 a+ H: @# `: h! V
Freq: B, dtype: int32, A4 s2 j* f. X" i- t
r = s.rolling('30D')# rolling可以指定freq或者offset对象
$ L2 V; D; e- j ?$ j, _' Y. o. N' _( A" c! y
plt.plot(s) # 蓝色线+ [' }( |! L9 M; _" W3 w
Out[108]: [<matplotlib.lines.Line2D at 0x2116d887eb0>]) m# x0 N a- n# H
plt.title('BOLL LINES')
, I. a, a( e# bOut[109]: Text(0.5, 1.0, 'BOLL LINES')
3 I! A; j" X1 [9 L* J, M8 `. I0 f8 ^4 R' b* \$ b" o
plt.plot(r.mean()) #橙色线; M! C. C9 ]' A' C, Z( b4 n
Out[110]: [<matplotlib.lines.Line2D at 0x2116d8eeb80>]. C9 @& }# y* P
/ I% W$ a! D+ Q
plt.plot(r.mean()+r.std()*2) # 绿色线
' K" c) \5 Y: o9 \2 m1 G bOut[111]: [<matplotlib.lines.Line2D at 0x2116d87efa0>]
" `) U5 }, \8 L, \0 Z
5 X' S5 T4 ]8 v. u8 L) \1 [$ ~plt.plot(r.mean()-r.std()*2) # 红色线- h+ x4 g* H3 }3 W
Out[112]: [<matplotlib.lines.Line2D at 0x2116d90d2e0>]+ ?$ D$ `0 R' \) q m
0 x4 m3 V- p9 r" Z
1
$ p i/ [" a/ ~# r4 }2
; N2 A; S+ c5 U( L% Z/ w( @3& n: @3 w7 Q/ R
4! ]# V: g+ N- o2 J- Q
5' \3 `& e, T; S( G$ G) Z
6: V. q1 u% M( r4 p+ g
7
3 l" r' ~8 {, j, q8: R9 K b& j$ K3 L
9
' M# ^& } ^2 d# i7 ^% ^6 P10+ @; S% k6 K% ?3 t) w
11
: F# B, U& [( X12
% c$ g! s5 ]+ D13
* k% V9 S2 S4 f14. M0 u) u9 ?" _( j
15/ b, \/ p5 V! c; A
16& C$ f4 Z0 c, H w1 ]2 A
17
/ L% v) C; A- ~( n: f; V( L18( q0 ~% W" W, ~# Z) K, X. A
19
0 O5 }6 g3 k) v/ X& ]( ]3 Z4 j& |20
, j) O# n0 l# i t- v* ^/ S21
5 Q: D2 s0 H% z" ~# Q4 c22/ |" ~& Y0 b& i, y1 d
23. v; X/ J& I" k# y6 d
245 h$ a' j0 A1 a; d/ W+ G
25
2 N: J" b' l. _8 `26 }9 s8 T# a" U2 l3 Q4 `
277 A: c1 @3 n1 e7 J
28
6 R6 o I' F5 P8 O6 ~( @295 l. L+ S: E: u: O. M# S( b
3 r5 y: ?# d/ u- c. o% Y) L+ O" p 这里需要注意的是,pandas没有实现非固定采样频率的时间序列滑窗,及此时无法通过传入freq字段来得到滑窗结果。例如统计近7个工作日的交易总额。此时可以通过传入多个函数的组合来实现此功能。
% H# D* f" M0 ]) U! ~/ L 首先选出所有工作日,接着用普通滑窗进行7日滑窗加和,最后用reindex()恢复索引,对于双休日使用前一个工作日的结果进行填充。
1 R4 S4 T D3 o8 R3 O& a; D/ g9 ?3 j" s7 o9 ?% P7 p9 Q
select_bday=s[~s.index.to_series().dt.dayofweek.isin([5,6])]
# x: D6 i: x8 y6 o* J9 k7 d, gbday_sum=select_bday.rolling(7,min_periods=1).sum()& n% S4 J6 P8 h( Z
result=bday_sum.reindex().ffill(). L% H. F5 t, w0 }# j# C/ z
result+ k7 U: W- ^! h, P/ F
/ i; k0 ]9 x4 K) e j( d8 O
2020-01-01 -1.0
. A c3 Q q8 Q! }, j4 q8 s) L2020-01-02 -3.0
+ Q7 G/ M; Y3 Z% }% ^2020-01-03 -4.0
1 C' v1 H3 s6 r3 N2020-01-06 -5.00 F5 V, Q3 @! `$ A2 p7 d# e# V' S7 [
2020-01-07 -7.06 M6 Y& e' [: p9 i5 K
... ( ]7 g' ]& n7 S* U$ A, }9 Z8 ^
2020-12-25 136.0
' B! U( z5 t. D2020-12-28 133.0( p! b& A8 d6 f/ e; b0 n( w6 w
2020-12-29 131.0- C D7 h1 a$ w: L
2020-12-30 130.0
/ G% @6 k# D( \0 E% `: Z4 T2020-12-31 128.02 s# {" j3 q4 |6 G; d* L0 i
Freq: B, Length: 262, dtype: float64. O$ u: L- C' n* X) e* A% B/ G
X* E2 ~' J3 q# L, }9 u; Z
14 v p. }0 j( T; r0 P3 R) b' E
2
0 n+ R8 O( A3 ~2 t$ v2 A& j3+ |+ J/ s( G4 r3 m/ N5 L
4& ~5 {( `/ M8 K5 X8 z3 b9 \9 O
53 I% n9 e; l# G) z8 `- X
6% C7 W0 W; e! X- l
79 B4 O) _! }6 N% h& y
8
3 T! M8 _5 f" Y3 F9 }. ?* e, g9, K. n; E2 J% Y
10
. K& m" q& @$ K4 c11, C8 V- {1 B7 U, o
12% `5 Y/ S6 T( M' R
13
6 O! n: T: J8 c/ d5 ]1 ^146 J j K: B/ B, P- T: K d+ h r
15
% u! U" [% m/ A y1 e16
+ L% M' u; r1 h175 G$ P1 U/ K, \2 n
shift, diff, pct_change 是一组类滑窗函数,它们的公共参数为 periods=n ,默认为1,分别表示取向前第 n 个元素的值、与向前第 n 个元素做差(与 Numpy 中不同,后者表示 n 阶差分)、与向前第 n 个元素相比计算增长率。这里的 n 可以为负,表示反方向的类似操作。
" e. {0 L+ o" H. o2 o
7 l: j5 \- B* d 对于shift函数而言,作用在datetime64为索引(不是value)的序列上时,可以指定freq单位进行滑动:
7 ^0 k1 x* p% [9 {8 ^4 b( ]0 P; z N
) T# K) J5 w4 z ?7 @s.shift(freq='50D').head()7 b- l6 b3 w% d
Out[113]:
6 V7 `/ T# W! C% r2020-02-20 -1
' z0 {% Y/ {) a; U9 `+ k2020-02-21 -2
( N: o4 S" [# q% A* f& B, }2020-02-22 -1
( n9 z" [# k Y9 B t2020-02-25 -1, D, E( L/ h5 d4 V p6 S
2020-02-26 -2
/ m; Y) \( c: N2 c4 P+ Y# p+ O& h' idtype: int32
# ]2 t% _, S) ^( J/ W1. K3 L7 W" t' Y
2) M' _) t6 V4 Z
3
. T- l( H- y2 d% `# t8 x4
5 R, _! x& }- B- H$ w2 k. d2 j5
' L: n5 ?6 o. A8 r: N* P6
% |3 y# _, k! Y; q7 }- v71 Q' y4 T- |1 E7 {! g& [) A* W
8
6 g. P; i% a4 X* g* x 另外,datetime64[ns]的序列进行diff(前后做差)后就能够得到timedelta64[ns]的序列,这能够使用户方便地观察有序时间序列的间隔:
2 B) _' W) R3 Z( W6 y: O. o: E
8 e$ ^# Y+ V( y r$ x) g; a. d8 fmy_series = pd.Series(s.index)5 f& g' `+ \7 Q* Z; @) G7 s0 i1 ]6 x
my_series.head()
+ a" M- l7 O) F( C: \- U6 I$ G7 O, iOut[115]:
7 N4 W7 f! r3 M! `0 2020-01-01$ F: l+ d/ w$ a5 [
1 2020-01-023 D% K2 q+ p6 U R5 |% |. U% ^: ^
2 2020-01-03
* Z+ ` W/ d- N6 H1 J2 M0 Y4 u3 O3 2020-01-06* Z0 L( k9 B$ X; W3 w0 n
4 2020-01-07/ ?# z4 p! M: i2 z
dtype: datetime64[ns]
5 y" d `) L8 ^& |/ Z' s% M) h r+ h7 h* A9 ]
my_series.diff(1).head() b7 Z5 |2 {$ b3 V
Out[116]: " E# _3 i( q- h! K ? o! x' u
0 NaT
) K0 z* c9 w* L1 C; w1 1 days! \. P7 ~9 Y5 E- K* ]: x
2 1 days
0 k5 I" V/ j* h7 g3 3 days
- `, a! W5 l- J; G6 k4 1 days
& s% t. D# |3 q* P* `) j$ Z& ]dtype: timedelta64[ns], R" Z, }6 W; L: I3 u6 q
: r; N/ @$ R! f$ S# L% D% J
1
8 I3 Z! W+ C$ q( b4 z+ D2# L0 q/ E$ i3 Q8 m4 F- Q' u$ C
3
) z/ u' Q. q+ V% x43 [3 e, ?# t1 U" I6 r
5 E+ L& c. U( g" ^: o& z
6
. H4 V* p( A: w n5 S2 B/ b7) O: |: T3 y2 `, ]2 x3 `+ f
8! S3 s6 d: j' V2 ^$ t. I
9) l2 h" W' H- ^: w
10# i0 B: m& Q' T: F
11
$ L6 }' ]( u; g$ d% V, ?! Y123 M* ^9 e7 Q- s+ [
130 U _! s+ ~5 G: |; {; \; G9 [$ ~
14
6 `1 Z9 y8 i0 B' z5 v# F156 Y" I6 B1 u3 z' _- \4 R
16) ]" p$ n$ r# h7 w
17
6 e6 c" A' N7 b$ n$ y6 f+ s6 i1 f/ n8 a18
! |7 `6 c, L- v10.5.2 重采样
' M" T' P# s( J q 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)1 L: p- \. N2 ~* v6 y4 o+ t) a
常用参数有:- c$ U0 _, p" A) m6 V
k1 Q O; [% X2 J+ P% P, prule:DateOffset, Timedelta or str类型。表示偏移量字符串或对象
' S- e- g9 d+ _' A9 K5 \8 y" ]axis:{0 or ‘index’, 1 or ‘columns’}, default 0。使用哪个轴进行上采样或下采样
4 R9 K8 U" g& }% m) Q" Vclosed:{‘right’, ‘left’},默认None。表示bin 区间的哪一侧是闭合的。所有offsets的默认值为“left”,但“M”、“A”、“Q”、“BM”、“BA”、“BQ”和“W”均默认为“right”。" N/ q" n9 q6 S& w3 u2 ~6 [7 f4 G
label:{‘right’, ‘left’}, 默认 None。hich bin edge label to label bucket with,所有offsets的默认值为“left”,但“M”、“A”、“Q”、“BM”、“BA”、“BQ”和“W”均默认为“right”。
+ q3 y% q% `# H8 v) s" Dconvention{:‘start’, ‘end’, ‘s’, ‘e’}, default ‘start’。仅针对 PeriodIndex,控制是使用rule的开始还是结尾。7 l! W) x; B, i, ~/ }) O1 v7 z
on:字符串类型,可选。对于 DataFrame,使用列而不是索引进行重采样。列必须类似于日期时间。! E) \" D* n! y/ ?9 Y
level:str 或 int,可选表示多重索引MultiIndex的级别,这个级别的索引必须类似于日期时间。
% j0 J! o1 W. r+ e \# ~' Dorigin参数有5种取值:. v! C. W) x& N+ ] M/ [
‘epoch’:从 1970-01-01开始算起( \; b. B/ @2 M4 ?: [9 j
‘start’:原点是时间序列的第一个值" ~: e* @* ^, @' k5 o( [" |# D4 Y
‘start_day’:默认值,表示原点是时间序列第一天的午夜。
; ^1 A6 ]3 ?, ?) T% @0 e'end':原点是时间序列的最后一个值(1.3.0版本才有)- r, l) x% \' m; Z! N: n+ n
‘end_day’:原点是序列最后一天的午夜(1.3.0版本才有)
, }8 J: Y: S+ l) hoffset:Timedelta 或 str,默认为 None,表示对时间原点的偏移量,很有用。6 ^' H) ?( J: ~- n7 k( \' e
closed和计算有关,label和显示有关,closed才有开闭。! ^! O9 Y7 t7 `8 f; J3 v- L, K
label指这个区间值算出来了,索引放区间的左端点还是右端点,closed是指算的时候左端点或右端点是不是包含。5 Z/ H+ ~9 b* e" ^! x- _
' j- I: L9 z" _0 A9 N& |6 J/ j1 E重采样对象resample和第四章中分组对象groupby的用法类似,resample是针对时间序列的分组计算而设计的分组对象。例如,对上面的序列计算每10天的均值:5 n# H& F0 m' K* l" y0 U4 Z% K/ L
s.resample('10D').mean().head()
( i- L! t# X* ^ g8 h1 C4 VOut[117]:
! M J5 w" f1 ?2020-01-01 -2.000000
; u, h/ |/ U$ i. c9 @ F7 ] o6 @* j2020-01-11 -3.166667! ]+ m# E* w, d) d3 M! i P
2020-01-21 -3.625000
! X V( |7 W# Z# X2020-01-31 -4.000000 J8 ] S3 N; s& `; [
2020-02-10 -0.375000) }$ q2 p2 k B% I
Freq: 10D, dtype: float64
4 \( i4 B# b3 [, ?% }8 o14 @& T; N1 Y0 ?# ?% Z/ y
2( M3 x+ G* ]9 P) ]- h
37 q1 G; ^' ]( P5 B$ m$ [; M
4
% y# n1 r; V% x5 ?0 r3 S. [0 [5
) I0 m. }- ?. I6
9 F1 U+ b. ^/ `/ b b& P S! L7
: b/ ?0 Q/ H( t& l5 _- g4 Y- c8- M H$ H$ w% g$ M. l
可以通过apply方法自定义处理函数:) U: x! Z' G+ `8 ~ i$ ~! o$ ^3 J
s.resample('10D').apply(lambda x:x.max()-x.min()).head() # 极差
4 B# g8 `# y" a L: I8 Y
' P% o: ~& {, W9 |6 q" iOut[118]: 8 Q. h( r# {) V8 b# e
2020-01-01 3
2 ]" [ r2 K* I: J& E2020-01-11 4
5 r2 O* \: O) g- ~# V, l4 b& b2020-01-21 4/ M% G& H) \8 c9 x% K
2020-01-31 25 K4 J$ ~$ d/ @7 N, l$ x# {
2020-02-10 4# S0 ]% i5 L/ Q( k9 y
Freq: 10D, dtype: int32
3 |* H- m# o7 G6 u0 c1& q. o1 ?' P6 s0 L# C% [8 U, I4 k e
20 E! W0 K4 F: n% C7 B5 B5 X/ t6 g$ N2 Y
3
y' O0 e/ t1 X. B3 p4; y1 g$ ~% F3 H7 w$ S# D( F
5
! b% O6 H" `3 \/ p0 A' f! U6
8 R, B* d! l% G& w, s7
7 V* i! W4 J! [% H$ ~8 H5 g8
. { O* L" J B0 w& x: M98 C8 @/ k: Z" A7 G+ b2 c" c
在resample中要特别注意组边界值的处理情况,默认情况下起始值的计算方法是从最小值时间戳对应日期的午夜00:00:00开始增加freq,直到不超过该最小时间戳的最大时间戳,由此对应的时间戳为起始值,然后每次累加freq参数作为分割结点进行分组,区间情况为左闭右开。下面构造一个不均匀的例子:
: c! d$ J& ~0 a4 ]; y2 i9 i5 M- E; d) Q7 {. }. \! v
idx = pd.date_range('20200101 8:26:35', '20200101 9:31:58', freq='77s'). p! ^* Z1 }/ k9 c0 _
data = np.random.randint(-1,2,len(idx)).cumsum()7 S6 V! a' h. j6 ~1 S# Z/ r4 b
s = pd.Series(data,index=idx)
9 w$ c/ k5 r: W7 Y# I) as.head()
7 z& L5 [- L% l
( D- b9 a- N \; s: }% p; @) i" F; H4 COut[122]:
* e8 _1 U1 Y% u3 m( o4 E! W, w2020-01-01 08:26:35 -1
* x8 }, C8 f" ?, p2020-01-01 08:27:52 -1
4 C9 Z! e. J% D4 V- n, j2020-01-01 08:29:09 -2
3 }! \: ^9 a; M( J7 t- ~2020-01-01 08:30:26 -3- R) e( P( C& v+ R
2020-01-01 08:31:43 -4$ g1 f! G2 M1 k4 M. |0 |
Freq: 77S, dtype: int32
& i& G% ?2 s0 e6 b7 G$ b% S12 b* r1 }4 i2 n" E' S7 \+ s$ X9 O% k
2
& l6 C1 i( B5 I; H3
2 k; \ r$ k1 ^' g4
/ k6 Q- c; H6 s$ U: e H% m- t0 h5
4 X! O7 G6 Z$ c7 ]) H# h: n6$ n3 h4 z0 R7 ~) A( T
75 \# S D' B. C: z1 G* V5 I
8, E" p: e: L: w6 u5 d
97 R2 \, R) d( L6 @$ x
10
& M* P2 l0 C# t: M1 Z6 I11& X9 A. L) ]$ V/ P9 l* A
12
d2 n- B! t; ^ 下面对应的第一个组起始值为08:24:00,其是从当天0点增加72个freq=7 min得到的,如果再增加一个freq则超出了序列的最小时间戳08:26:35:+ Y* J6 B D6 t' D7 M5 ]) p: f
& g9 E1 @4 \/ `9 `2 Y7 y( p8 X
s.resample('7min').mean().head()) s! [, ~, r/ ^3 x( N) E
Out[123]:
" O0 d5 {* [( ~2020-01-01 08:24:00 -1.750000 # 起始值,终点值包含最后一个值
9 b U) V: a# b5 t2020-01-01 08:31:00 -2.600000) A9 P- U* _ b* w2 ~
2020-01-01 08:38:00 -2.166667. X" Q. ~ Y3 E1 A
2020-01-01 08:45:00 0.2000006 p1 b+ w& G) k& ~
2020-01-01 08:52:00 2.833333
& A4 M( I9 O2 I+ i8 c4 {Freq: 7T, dtype: float64
0 a" g* P- H* L# R* Z$ m10 ]! `8 G3 u2 n, J1 M+ Z/ n1 y
2' ~ u3 _, K6 e. u7 ^
3
9 e' M! |$ N4 x8 o2 @$ w4 k" m$ @45 N0 E4 s" R( x1 j0 P
54 Q: Q0 Y, ^- [, W
69 b8 y7 X& A$ G' `6 o! J6 T4 i$ Z* T* h
7
. e# d# l$ l% Y# n8. x4 C) _% N0 [; S1 c# b9 H( }
有时候,用户希望从序列的最小时间戳开始依次增加freq进行分组,此时可以指定origin参数为start:% Y6 L5 M8 e- E0 U
- \$ L5 i7 X( G0 ps.resample('7min', origin='start').mean().head()- m1 e& x r0 r
Out[124]:
, ]; H; K) P; O/ C# o& h: Y3 B; I2020-01-01 08:26:35 -2.333333& K, n8 v* \. i
2020-01-01 08:33:35 -2.400000
& u! Q9 `) N9 _* Q. ^2 F, Z' q2020-01-01 08:40:35 -1.333333& t1 I5 M. J: a' \
2020-01-01 08:47:35 1.200000' Q( h. M; Y. ? Z- y4 M
2020-01-01 08:54:35 3.166667
! r4 E1 j& _! V1 j! `Freq: 7T, dtype: float64: L- L0 B+ h) p" Y' [6 _9 ]
1
+ s% \" w9 X$ u$ o1 G# J23 l+ [2 a9 J7 G7 O" s/ S0 I
3. v8 j. Z( u1 Y* s+ w. [. a$ @2 ~( c/ c
4
6 a- B. B9 w( e4 Y1 B2 _52 m% t& F" z4 P. V y. W" ?
6
8 y' B8 S9 @- k9 o+ J2 O# R7
. I' l, H- m @& ]) X D9 E; F8
2 W# J% M+ e+ C0 b3 U8 X; l- Q 在返回值中,要注意索引一般是取组的第一个时间戳,但M, A, Q, BM, BA, BQ, W这七个是取对应区间的最后一个时间戳。如果想要得到正常索引,用’MS’就行。
+ W( W4 X1 w0 a4 ^# l: o! F7 J: \ ~2 h$ M
s = pd.Series(np.random.randint(2,size=366),
( p5 r, J3 g( S- E index=pd.date_range('2020-01-01',
6 N9 q+ V2 E8 t3 s* b& d' U- P5 B '2020-12-31'))
! P: s: ]. t/ ^6 b
2 ?5 K# s O! J9 e: a: \; D+ o, n u/ s& V
s.resample('M').mean().head()
/ P- N$ b9 a: x. E! M$ F, z' XOut[126]:
2 T3 F; b& {" H% k, g1 T) J2020-01-31 0.4516133 ]0 S2 H9 V( x4 G7 Q) t% ?" X, U5 S/ _2 ~
2020-02-29 0.448276& I2 W3 c7 p3 k( E& j
2020-03-31 0.516129
5 h$ u# M0 o% g$ r& J- U7 `2020-04-30 0.5666676 `& A( w3 l# T/ ]% @/ [6 T+ r% n6 O
2020-05-31 0.451613
. }6 i. m/ r) o+ }( t0 N' CFreq: M, dtype: float64! A$ e# n9 X% A" B* l% L
' J" b" O+ l. \9 z+ B2 [s.resample('MS').mean().head() # 结果一样,但索引是跟正常一样( C" {6 O, ]$ I( W
Out[127]: : N, H- j; g$ \4 A" g4 ~! F
2020-01-01 0.4516132 g9 |' M6 ~2 ~9 }7 }
2020-02-01 0.448276
+ s2 ]7 F& T7 F8 V2020-03-01 0.516129" |2 C% _; A. a* F; L7 [
2020-04-01 0.566667
- Z/ d6 T2 {8 |, B/ q4 s2020-05-01 0.451613, e7 N. F! }' e+ N
Freq: MS, dtype: float64
" p' m) g0 o9 j# D9 _! }% V: |) U2 ?% j, M0 @4 u$ }
1
' _: f6 F9 X6 u% P) e3 W* z2 \1 E29 {6 Y: D- b5 U9 k; N
3. D2 n9 F; `4 Z; T- g( ?" Y% B
4
8 A; ]8 y( y; B9 [' e55 d8 |# X: Z* H0 L/ l
6
' I6 F3 L) m1 l, O- X7 E: Q8 K g72 {# i) S' O' `( q( m; y
8* i4 g0 _% A% s# `: [1 n; }: k
99 i- P6 d# @' x0 {& ]2 d
10& ~1 c( g7 i) @, h* W
115 F6 g9 N' y8 O6 G8 ]0 H* d+ U& ?& h
12& x# T. C8 m1 Q+ Y% \# ^* i
13* t# V9 ~, L# K% G: Q
14
1 C1 z/ H, x; Y& y7 h15
5 D9 F0 l2 d$ M; l' |16
! i- ~/ O8 _- b2 m( a17
# K# ?3 d3 |6 R* z+ c `18/ s! I5 f$ t. K! m
19
0 m7 S* x4 `$ l# U( h20
1 h9 k) P" r: }& A I1 h21
/ m7 }2 v4 ?* r; s8 e. ]22
1 b4 F8 [, B) t对于 DataFrame 对象,关键字 on 可用于指定列而不是索引以进行重采样:
9 C3 v8 }6 Z- |9 _ k Pd = {'price': [10, 11, 9, 13, 14, 18, 17, 19],
! J/ F* _! L- z8 ~1 ` 'volume': [50, 60, 40, 100, 50, 100, 40, 50]}- H" m2 v' @: y5 R9 z+ F
df = pd.DataFrame(d)! S4 j: v L' `$ R. P @" K0 ~
df['week_starting'] = pd.date_range('01/01/2018',
- e# i2 e, |3 m3 i periods=8,
6 A5 P7 L* Y6 x' G: ^/ l% X! Y freq='W')
; `4 Z4 J8 [1 B4 L9 odf- q" @5 i' G. r" I
price volume week_starting
' M3 p( _( A+ i+ p4 S0 10 50 2018-01-07( j% D7 {4 V0 x' K5 t& p
1 11 60 2018-01-14
, N6 n) W" d4 v2 9 40 2018-01-217 v/ m# c; ^+ ^, J/ G3 u
3 13 100 2018-01-28
: q3 ]4 |. ^1 Z5 Z: F4 14 50 2018-02-04
+ {! p Z) N$ L) e* B) A4 U5 18 100 2018-02-11: v8 o( |7 W7 c0 b
6 17 40 2018-02-18. U: j6 Q- J- W* O5 i" @7 G' o
7 19 50 2018-02-25' _- ^- A1 R$ M1 Q: H: }; u
df.resample('M', on='week_starting').mean()! k9 v4 J% o( O- J) L
price volume0 P; R( Q4 h U- k: M4 t
week_starting- ^+ F( L/ a8 J% Z8 Y
2018-01-31 10.75 62.5
" Z* K5 Q' ~ ]/ ]) m/ a. ^1 X* P2018-02-28 17.00 60.0
3 n: I% M; x5 V( T. Z8 P
( T7 u4 N8 b1 f1
' y, F8 q! e6 K, t2
7 S2 i/ l+ H& s& J, g) h& V3
% v; Q) u2 F- `3 h( O4
* F7 p6 `1 r0 s( v5
; t; S) U$ Q9 U* P& _/ R' C6
& v3 W, n& m( h- p8 p8 s7
7 ?' |6 R+ l8 \- O6 r. F9 ]8
r i3 I) U+ ~( S/ A4 a A9 v/ M" r& U% L4 `% N
105 d/ r3 l4 N0 W0 l$ T
11
; L* U i1 x: r1 s) n' `12: v1 m7 I7 @' s; L4 [+ N
13; F4 Z/ R% O$ e$ o {7 A5 v+ G8 h
14
, ` |. @( r) H15
( t7 E( Y5 S/ q. w& I$ G3 |8 G) A3 p160 p3 V' t) S+ ~' ]) g* W
17
, O* {4 \# t m" L18
' W% Q+ U5 ^ D8 D* x194 b, W" Z- f' A ^$ P* r
20
( N/ ~( d4 [: B: M) Y, ?# ?21' ?8 h' g6 ~4 k8 @
对于具有 MultiIndex 的 DataFrame,关键字 level 可用于指定需要在哪个级别进行重采样。) Y5 M* Q5 g/ n5 F6 D4 }8 Z
days = pd.date_range('1/1/2000', periods=4, freq='D')9 G6 [9 L# l8 Y) J' Z# w' z
d2 = {'price': [10, 11, 9, 13, 14, 18, 17, 19],8 _& L6 ]5 q5 V* T- V6 h& S9 p
'volume': [50, 60, 40, 100, 50, 100, 40, 50]}; K. n: _$ g0 {$ P' g
df2 = pd.DataFrame(
' E1 d! ~8 ]' h- s ^# \9 |, x d2,0 J9 I q) ~4 f4 c/ b k h; t
index=pd.MultiIndex.from_product(
9 {2 b7 J* T8 o7 |* H [days, ['morning', 'afternoon']]
: M) L4 ~' N3 x/ q8 ? )
) T+ t! i, i8 ]! e/ N)
( y* u. j8 X- r1 b1 ldf20 f0 m: s8 C- C0 W- \/ V
price volume& J4 P! P' g' ^6 P( P
2000-01-01 morning 10 50
/ l+ j5 n( L7 F9 u& r afternoon 11 603 }1 d, C/ C0 B1 a
2000-01-02 morning 9 409 G. U9 }1 F- R. D1 O) X, h9 `
afternoon 13 100& A a5 _4 I! O5 l b3 L8 k
2000-01-03 morning 14 50+ j9 r+ J. `8 m0 a
afternoon 18 1008 u; @: g5 {6 L( F6 P) K
2000-01-04 morning 17 40
& B7 W1 I' U. ^) f+ r afternoon 19 503 ~/ \) Q6 [7 {
df2.resample('D', level=0).sum()- M& v5 T; S5 O6 e' g+ e. U* Q
price volume
2 c4 f4 i2 m1 N( z/ {$ S- q2000-01-01 21 1108 o( d+ Z' X- o7 M$ a0 Y
2000-01-02 22 140- a8 o! z& H3 m* M
2000-01-03 32 1508 E$ _5 c; i1 I) W R
2000-01-04 36 90
6 [% U, E/ X, ^1 s0 z, ?) b, K' C9 Q1 g4 u* g4 a: ~
16 Y5 ]( m/ ]( g3 i
26 E7 ~; m- m6 j2 R: E% {1 s
3/ X$ t( G7 K5 ^. y6 Y, n- D
47 v o3 A# R9 C6 ^
5# q+ a9 O( c7 x9 w
6
1 G7 B' k, P, V8 S- F7, w) T/ d( A2 t2 q% }6 @
8
8 Q6 F' w$ m0 k. I0 f9 ~9
G' N& l2 h& c( j N10
; M3 t+ T6 B* L6 b11
2 c; E% A; e6 A; b7 R. V! C12
- G T" N1 q/ L" b$ S; e3 s135 u* z# t* ^, \3 ~
141 k2 v* c" T# a# v$ G
15
) F( I% J+ U# k3 I% v' n16% P, W* c! s0 {
17
( J- I: {6 I, b) H! B189 c o/ S I$ n6 ]: Q
19
' L% n- v8 D. s+ ^% I$ H20; [ ~# q- P: H8 \ b' r
21
8 Q; p* F5 v# E/ z+ p# x0 L228 @5 Y0 S+ m' b) C
23
7 w2 D+ m; `4 K7 Z. @24- P- k& u' L+ ` k3 h! _
25
( D! e* F7 [, c9 G根据固定时间戳调整 bin 的开始:8 g' e* `& `& \, s
start, end = '2000-10-01 23:30:00', '2000-10-02 00:30:00'$ B4 R; r8 p1 N4 u8 O5 T
rng = pd.date_range(start, end, freq='7min')( ?: c: s9 M$ D7 {3 I6 J
ts = pd.Series(np.arange(len(rng)) * 3, index=rng)& [' {% T5 e$ Y" D1 R( G- K: r2 m4 |
ts
5 y6 E h- O9 M+ v2000-10-01 23:30:00 0
: _% U. `6 }1 a v- k2000-10-01 23:37:00 3$ \; G$ d% k* k6 |* k; _6 P
2000-10-01 23:44:00 6
1 d6 h* z' V$ a+ m; Y1 x, p; v2000-10-01 23:51:00 9( h+ {: R$ W. X& [5 a2 t, }* d8 l
2000-10-01 23:58:00 124 u1 w( V. F% z' V6 ?
2000-10-02 00:05:00 15
$ E3 m# i# T0 }$ G2000-10-02 00:12:00 18
9 ]* B0 t5 B# }7 a5 Z" o E2000-10-02 00:19:00 21
J, d) B( i8 H* D6 [2000-10-02 00:26:00 24
. G. ^( s6 g9 e6 I9 T" I. [Freq: 7T, dtype: int647 P, }. D- D. a
3 i/ s5 f1 H7 A% ?
ts.resample('17min').sum()- {6 V4 t2 f3 b8 S: d/ p8 q$ f
2000-10-01 23:14:00 0+ ?% _6 T9 T% n
2000-10-01 23:31:00 9
8 \7 D+ a! ~+ Y/ G: e2000-10-01 23:48:00 21
! S) v! y2 W% K5 g7 B2000-10-02 00:05:00 54
/ ^- d+ I3 U4 P2000-10-02 00:22:00 24! ?: G, v9 O0 _; N* j2 X
Freq: 17T, dtype: int64
+ C. `0 \, S& J" D6 q! ^5 s/ ^- D4 S/ t, Q& @& F+ w
ts.resample('17min', origin='epoch').sum()! t; Z. s' ^( Q
2000-10-01 23:18:00 0" V! {4 B" `, H8 V6 C
2000-10-01 23:35:00 188 m( t2 b. [3 W
2000-10-01 23:52:00 27
2 O! c- C1 p8 u0 S" U2000-10-02 00:09:00 396 [ x8 v- |9 z- J$ t3 Q
2000-10-02 00:26:00 24
: K" l, r$ B+ B# d3 eFreq: 17T, dtype: int64+ e, y& V3 _+ e4 m- a/ a# K
% u4 f" l' w2 X$ \4 ?
ts.resample('17min', origin='2000-01-01').sum()
, ^6 U# T/ I% l2000-10-01 23:24:00 3
* q! q+ e9 h: {- {! s2000-10-01 23:41:00 15
+ s" ]9 V; Y4 Z& U' I- ]. V2000-10-01 23:58:00 45
+ I: D# ]2 X. U+ K# |2000-10-02 00:15:00 45
2 D$ y( @0 N) t0 H6 E! d6 uFreq: 17T, dtype: int646 _% l; J, b! p( [, R& v) i
8 I8 h: g1 Q" c$ b- u
1
$ g2 b$ C6 L" u9 o; R2: v/ h, j# V2 @# w! a/ V9 I8 q
33 P! S1 F6 Y6 a* A7 Q
4
4 P2 O) e3 w: o- f5% i; B" _( `6 v2 L# Q2 ^. J
6
5 ~& i$ C: G9 u# v. y7* T7 ~! F: n6 Y1 ]5 y
8& d7 K8 j7 J' |2 R! r3 y- k
9
/ D& ]. |" c( h9 O4 z10
0 O7 h: I) S# h11! a) i$ B1 F! {+ h
12
- A7 Q, l' q1 S, A' N13
2 U9 Y) D4 O$ u% f: s) X3 u142 v" [; [+ l- y6 p$ [
15
' X$ G; v/ b& }6 {, B16
% ^2 R4 y0 y J2 K8 u17
* {4 d% J# h/ ` v) ^18, k+ x) P8 s/ }$ {0 o, Q
197 R/ J) z \8 H6 G/ a
20! @$ n- R# q" Z. G5 n$ q
21
* d& q) ?6 u1 R# h+ m) {, Q22+ d) V& j v; T0 h; k
23
# |( J; ~! W# e5 W3 H24: [$ |7 W1 w0 H [8 p' c3 I
25. r3 T* _+ M* @9 J# L0 h8 h$ P
26% I0 D9 U9 d: `2 r; l/ Q
27
9 G+ q) X! X6 M) B28
" L, ~, g% `) M7 T( O9 x297 ?2 |9 \4 z6 u& } M
30" O% c- P; ~ t. J$ x' _4 z
31
8 f' y, S }1 G( H6 {32
$ e- M% P2 F$ b3 e8 x+ g, G6 \33
' Y( Y7 K9 V2 H2 @34
. E* u( E" e, l3 x1 X- k35
, }6 e B/ U; H$ Q7 y; m36- e u; d+ O2 a
376 H* |" X7 l* t( G
如果要使用偏移 Timedelta 调整 bin 的开始,则以下两行是等效的:
# c' p3 L5 i0 {8 H3 ets.resample('17min', origin='start').sum()
- x+ ]& @8 r3 bts.resample('17min', offset='23h30min').sum()
' R' g- q: ^/ z5 `2000-10-01 23:30:00 9" U" U: A) c, y3 z+ b/ ]/ a
2000-10-01 23:47:00 211 e6 n) O3 j% o0 p0 W4 k; ^
2000-10-02 00:04:00 54
/ S% l% j+ u* p. o$ P( l1 z2000-10-02 00:21:00 24' A; t! ^4 h: m7 o
Freq: 17T, dtype: int64
3 ^9 }9 i; C4 j M" B2 @( D1
d4 c0 c8 ^' `( g' g: ]. \/ J2. \# Q+ f# d5 C: _+ H2 y
3
" P& |/ \, k6 p/ e- N4
: F2 r. V2 o: s4 ~) }! G51 l& Q8 H# T5 j9 |: h
6
5 h' A. a2 j9 i1 ~7 V6 p& F4 w7
* d! f6 H) l: ^) { @8 d" o' @5 [( {10.6 练习
4 b9 P# c! Y0 W- mEx1:太阳辐射数据集
* u: E$ s% |; |& h$ q8 ~现有一份关于太阳辐射的数据集:
& R0 F, P: X& ?' ?& D9 _1 ~# ?
/ P& Y* P& ?5 \df = pd.read_csv('../data/solar.csv', usecols=['Data','Time','Radiation','Temperature']): ^9 N' k! V4 p) o
df.head(3)! U7 ?* L B- h" {$ h, g; W
@, |4 m9 a$ O2 {. A2 w
Out[129]: 8 I8 ^, F( S& s8 G N1 ?: K5 h ]
Data Time Radiation Temperature0 A4 u) |) h4 H+ _9 B
0 9/29/2016 12:00:00 AM 23:55:26 1.21 489 s4 X8 D5 G( q; w0 g
1 9/29/2016 12:00:00 AM 23:50:23 1.21 48$ t- O) d+ q6 Q
2 9/29/2016 12:00:00 AM 23:45:26 1.23 487 k, U. O8 {* i% t+ S3 U9 a$ M& k
1
x. q, }( j H2
8 c! S" e: A# p1 p% {3
" O: ^* n) Q( _8 k, |47 P7 k& K8 o z( v$ s* G" Q+ E
5
) E7 b/ j3 [$ s" u. G$ d60 K1 \$ {5 E! ?6 O' u, Y
70 M3 B& b8 {8 O6 y1 L3 o( `9 S
8
) C: s0 Y; e' U将Datetime, Time合并为一个时间列Datetime,同时把它作为索引后排序。
9 u9 x4 ^. N7 b2 v: y- W9 @. B每条记录时间的间隔显然并不一致,请解决如下问题:8 g1 _8 X& H8 Y9 K: A- }+ P3 [# ^
找出间隔时间的前三个最大值所对应的三组时间戳。+ w/ U! A* O$ M
是否存在一个大致的范围,使得绝大多数的间隔时间都落在这个区间中?如果存在,请对此范围内的样本间隔秒数画出柱状图,设置bins=50。
& N& }9 j3 ^7 q6 V求如下指标对应的Series:
9 u( f1 S# L7 c" q% \5 ]" ?# c温度与辐射量的6小时滑动相关系数
0 D+ {0 H. Z: E f" n0 Y以三点、九点、十五点、二十一点为分割,该观测所在时间区间的温度均值序列
* G) ^ C% S' I- M F每个观测6小时前的辐射量(一般而言不会恰好取到,此时取最近时间戳对应的辐射量), T& |2 Z y; S" T9 M
import numpy as np$ i8 H' x/ [2 j# J6 F( w
import pandas as pd
4 O, b q' K* K" q. ]* o$ l1
( ^- D* D8 N+ C! w! V. ]$ y2/ E! |$ B C* l( I& M; u
将Datetime, Time合并为一个时间列Datetime,同时把它作为索引后排序。* F1 e/ a5 f8 I% X1 N, M
data=pd.to_datetime(df.Data) # 本身是object对象,要先转为时间序列2 W5 c+ k, n/ w* S. O
times=pd.to_timedelta(df.Time)7 L; k& l* f1 z* q# \
df.Data=data+times: b2 f/ z( j4 | u9 Q
del df['Time']9 W& u8 ~4 \+ F$ _
df=df.set_index('Data').sort_index() # 如果写的是set_index(df.Data),那么Data作为索引之外,这个列还另外保留! V1 V* `$ s& L
df
1 l+ v* e6 ]. p, L Radiation Temperature
# {& `5 M- e3 \0 P0 m. OData # @2 X8 N' E# d% T4 Y- z
2016-09-01 00:00:08 2.58 51
% @( n5 r& e q2016-09-01 00:05:10 2.83 51
5 z4 u7 U% i( r. L2016-09-01 00:20:06 2.16 511 K. _4 z! f0 e7 M
2016-09-01 00:25:05 2.21 51. F4 _# m6 ]5 t/ B/ Z. b. T8 G* v
2016-09-01 00:30:09 2.25 511 v( f" }7 ]) P q$ o/ l. w
... ... ...: t- O/ v& H. _ e
2016-12-31 23:35:02 1.22 41# Z# j9 U0 @+ Z5 }8 y6 Q
2016-12-31 23:40:01 1.21 41
' q# b5 K. J9 ~5 N$ N2016-12-31 23:45:04 1.21 42 [# S5 K# k$ A- v8 ~1 b
2016-12-31 23:50:03 1.19 41- \& O U7 {- X
2016-12-31 23:55:01 1.21 41
9 J) J5 k: V/ p& q, Q2 I: P% W; }3 i
14 G) X8 U2 c6 f# v
2* G; {" z1 Q5 ]" n& R, w0 G
3( S; {4 `4 o) Y4 R ?9 T0 `
4+ P( m' }2 |7 J4 N2 o3 y8 g& a
5: s2 V3 O( O# m. H. _! O
6
$ ^! Y* | z+ ^! s7
6 R+ f* q2 H- l8
" }9 v* h8 Q9 ?7 I- [9 z8 e9" Y; o, g; e1 z. W# k
10
! G! i3 t1 K8 v11; u7 F8 b/ l& q. B
12
" k5 j/ z5 D) u4 A- G) c8 N13
! S! E( }1 n9 ?$ M9 L+ d14
5 O5 e- S+ ]! i" o15) N7 j4 W( u6 ?
163 @: K4 W/ S4 t6 ?! a
17
$ b% S9 c- x2 i! A18# | _# b, [2 o
19+ w3 U+ A* n/ ^* a
每条记录时间的间隔显然并不一致,请解决如下问题:
$ ^# I$ T9 o( j: q8 W6 c找出间隔时间的前三个最大值所对应的三组时间戳。
- z/ l% ?- d0 y/ V. Z; o. |. f# 第一次做错了,不是找三组时间戳( R4 A: ?6 L( n4 _
idxmax3=pd.Series(df.index).diff(1).sort_values(ascending=False).index[:3], n" u! e3 _& E/ N: ]
df.reset_index().Data[idxmax3,idxmax3-1]
6 T- o- p4 q( h2 ~2 Q' e- _! l0 i0 r
25923 2016-12-08 11:10:42" ~3 n! ^4 Y# Q# C: I
24522 2016-12-01 00:00:02
/ [0 ?. m% X' t$ a9 H( M8 q7417 2016-10-01 00:00:19
5 r! {2 K; m. E6 yName: Data, dtype: datetime64[ns]
3 t" u! h& N0 o7 p1: }. j3 A( M$ r1 t- ?& A1 O% ~
2
\! K6 u& z( ], m2 w* q( T3
2 p7 T% U/ Y0 a7 w5 m4
; U+ k; s% {- P2 p/ f* U5$ W. q7 n- ^$ Y7 |3 l. j
6
h9 A/ k% f& ?/ \77 }1 q! X C' I8 R/ H4 c
8
8 B' t: m. Q# J6 n: F v/ lidxmax3=pd.Series(df.index).diff(1).sort_values(ascending=False).index[:3]
! p0 [1 }4 e2 e- r) w" I0 Z) }list(zip(df.reset_index().Data[idxmax3],df.reset_index().Data[idxmax3-1]))* z4 b C, ^! S; R7 u/ [7 X% i0 c
, g7 A: S6 n, [' b+ X
[(Timestamp('2016-12-08 11:10:42'), Timestamp('2016-12-05 20:45:53')),
" {. M# p6 s1 [% C: ?) | (Timestamp('2016-12-01 00:00:02'), Timestamp('2016-11-29 19:05:02')),
+ @1 R Z; _* R' h2 n: A (Timestamp('2016-10-01 00:00:19'), Timestamp('2016-09-29 23:55:26'))]
1 c4 B% k' s* F7 f! w$ ~1
2 T$ {" P6 x0 w, f8 b% ~7 i2
) `, t# J. o' K' `) X$ ]3
& j, y3 I. ~$ @+ o7 U) `2 s* b4
. |( N& e5 P0 E P5
2 D$ h6 U$ b3 Z6
7 q3 E+ [! } M t参考答案:- `% q( y+ Y5 c/ t1 H7 ?
8 d5 q2 @* I1 _9 o
s = df.index.to_series().reset_index(drop=True).diff().dt.total_seconds()( O2 Z% l, @* _
max_3 = s.nlargest(3).index$ ^' g/ m$ @9 d, f
df.index[max_3.union(max_3-1)]2 J; V2 s. u; H1 D6 J
& I1 Y) M. }3 F" b4 M# w# z7 XOut[215]: , b" _# L+ _9 ?
DatetimeIndex(['2016-09-29 23:55:26', '2016-10-01 00:00:19',
, Q2 ]5 M" ?, s& d4 y A '2016-11-29 19:05:02', '2016-12-01 00:00:02', p) {8 ~+ G) X- ^0 U3 L
'2016-12-05 20:45:53', '2016-12-08 11:10:42'],. R; e& O0 S6 x* y$ l
dtype='datetime64[ns]', name='Datetime', freq=None)
/ Z! w7 F+ {9 C6 L* u) I1
9 U7 T- g6 r' \# L, ?- v2" f. H4 k) _: F) q, l$ e% V
3
0 N" d: g j5 {% p; U4
6 E6 h2 q' i( Y; u50 I% f, {3 w, v0 O
6) v1 w/ ^1 U+ U+ g* V
7
1 I0 l/ B8 j+ z) v& W8% t, b' b# d5 @
9
' [+ C% i# U( x8 E& Y! p是否存在一个大致的范围,使得绝大多数的间隔时间都落在这个区间中?如果存在,请对此范围内的样本间隔秒数画出柱状图,设置bins=50。- z7 M- c% `; f. k- f$ m. r; l
# 将df的indexydiff做差,转为秒数后排序。再求几个分位数确定取值区间
0 v P9 I4 w. Qs=pd.Series(df.index).diff(1).dt.total_seconds().sort_values(ascending=False)
9 L: \- E" F- s) F8 y2 B: ]) `s.quantile(0.9),s.quantile(0.95),s.quantile(0.99),s.quantile(0.01),s.quantile(0.03),s.quantile(0.05)
% k& t! J- [' D6 L5 g- U: B/ @: k" x2 k9 N* G/ y) ~5 h
(304.0, 309.0, 337.15999999999985, 285.0, 290.0, 292.0)
( T, _8 B8 D& a1' V" ?" J8 ~, }7 N" a9 Y
2
; b1 G' F) h6 f, ?& Z! z0 }3* w" `0 l6 U4 w" J
46 D/ y- b. Y9 }" v6 P4 {
5
- m; C% \" E' G6 x: O5 G( l; e, U%pylab inline C) T3 O: Q! s* Y
_ = plt.hist(ss[(s.values<337)&(s.values>285)],bins=50)8 Z) j) T9 p; U6 h% c
plt.xlabel(' Timedelta')
Y9 X) P+ I _8 S4 ^0 wplt.title(" Timedelta of solar")" ?6 Y, m W k1 O
1+ D& C5 w! c$ D% s
25 q) c K# L0 J7 y
3) ^7 O) c3 G9 T# c& @
4
" k) \7 g1 |9 H" P! Z, P
( C( T. C v8 I7 O. K5 J$ V' U0 G: {# ?8 W* W1 L
求如下指标对应的Series:
9 ~/ `" i$ d6 _" I' a温度与辐射量的6小时滑动相关系数. D( l' G* e/ [% | {# ?# e1 M" \
以三点、九点、十五点、二十一点为分割,该观测所在时间区间的温度均值序列6 L9 Q! J2 u) \4 G1 z5 r
每个观测6小时前的辐射量(一般而言不会恰好取到,此时取最近时间戳对应的辐射量)5 y' w6 q$ x: P. y
df.Radiation.rolling('6H').corr(df.Temperature).tail()
0 O, O. H, X" @
0 @' T1 i6 E) IData" ~: a! Z% q" ] p% H
2016-12-31 23:35:02 0.416187
3 u% U1 Y& ^, v4 S2016-12-31 23:40:01 0.416565
5 z2 g0 ?! Q b0 ^! B I/ _) g2016-12-31 23:45:04 0.328574$ Q Z! Q+ L: m' a
2016-12-31 23:50:03 0.2618837 k. K2 B/ ~, Q5 W
2016-12-31 23:55:01 0.262406
6 h( d5 b& n9 t- @: Odtype: float64
+ I1 y' J" j/ V: P, L7 @' [) G r1
$ Z5 T3 {8 P( l `; h' {2
) h. `9 A% m: F5 m4 X: f3% _" | s# I6 ]- n; \- W0 ?
4
# r2 S4 E" i5 A' v# a: \! Y! c5
: ~+ ?' A9 l, s) d$ v6
! L. Y$ f: R) ~2 Y! ^6 z. |73 x* G r! Y( l& h+ V
8
4 F+ c) c% [0 z( a. a: D) f" q9+ k) c; T' B/ w6 r9 K! ~
df['Temperature'].resample('6H',offset='3H').mean().head()# Y% I' E' t$ e7 E
3 x0 ]6 Y. ^2 d: s. ?8 y# m7 n8 r+ M3 IData
, m7 U j' `) [- l9 c2016-08-31 21:00:00 51.218750& |, E) a7 F7 G! D$ ]7 h% S
2016-09-01 03:00:00 50.033333
% g5 _$ H; q2 Q' Y3 K2016-09-01 09:00:00 59.379310: f5 W0 @( e( F# ^% A5 k+ R$ I8 \
2016-09-01 15:00:00 57.984375
7 p" x, e' ~: `! y H- c& a2016-09-01 21:00:00 51.393939: k8 ?7 z9 r- x9 F5 X
Freq: 6H, Name: Temperature, dtype: float64& i0 S) q& R! p0 T7 Q
1
# L; }; [8 H. F% g' J0 D2- A# c1 Y* ^ N$ A9 ~2 R: _ E0 Q
3
( ~. j5 F% L' Y6 d0 E/ Y" j( S4# S: l0 t) W; E7 G
5) B+ G+ |# g/ B$ S/ t) Z$ \
6* L6 p+ E' w0 @% S
7 t0 ~( t9 D0 u% O! S: P# v: f
82 Q7 ~! `, L* y5 s( @5 a
9( P! T( c0 E& V% |
最后一题参考答案:
4 x( ?% l/ i) f* f
( U& z! |; T% e* j' M' [# 非常慢! [4 |9 U5 R) h
my_dt = df.index.shift(freq='-6H')5 [1 |: ~9 G8 T
int_loc = [df.index.get_indexer([i], method='nearest') for i in my_dt]+ J8 | q% t2 [+ I: B& ~4 I
int_loc = np.array(int_loc).reshape(-1)
) o% u$ s8 s1 r( G. ^res = df.Radiation.iloc[int_loc]
p; f/ y, M; S/ R2 |; G5 Lres.index = df.index
+ N: @( L, l5 _! F4 s) \1 Rres.tail(3)3 l) N2 p3 M* |! D3 J9 d
14 P0 J, Q4 z, U' j8 ]
23 W) ] V0 @, d8 }: s
3
6 h2 N' K% I9 \* `- f2 O. E+ F4
) K% Z, W7 V% s7 C7 Z4 Z3 L56 ~! ?/ w$ b5 l: E/ f# ~5 z
6/ |. A/ |4 j! r f6 }& }, N+ L! [: a
78 a& P' J3 D, z/ S+ B/ E% k
# 纸质版上介绍了merge_asof,性能差距可以达到3-4个数量级
3 |+ K P7 Y) P. `! ^target = pd.DataFrame(6 }+ @3 J. ?3 r5 G+ N- R
{; b. H# c# B2 [1 M
"Time": df.index.shift(freq='-6H'),% n4 K- a) t6 X* B9 I: V" j
"Datetime": df.index,/ F2 Z+ I+ f5 w' m4 U4 m$ s
}5 ?2 Y! s$ w. K, U& B! k
)
0 A0 @6 V: w9 T$ F6 s7 N. J5 e6 R) o. l. y% T& a: K0 ~' n
res = pd.merge_asof(7 d" {! L+ W; W, g U2 `
target,
1 U, F. S& i; A7 g: e$ K# V df.reset_index().rename(columns={"Datetime": "Time"}),
8 t. R4 K$ g- l- }, \3 t6 W left_on="Time",# W: c. v" {0 e2 `9 ]
right_on="Time",
+ D$ E* h+ X. {9 O/ C2 A5 b direction="nearest"8 n) y( p$ E/ O5 h9 _5 Y
).set_index("Datetime").Radiation5 b; i4 \1 p4 a
: `5 p7 q: D r& q# R9 [2 s9 w
res.tail(3)
' H4 U( \: |* g( rOut[224]:
4 C8 @2 L* C+ y) ZDatetime7 Q- x, M2 R) r4 r. `9 F
2016-12-31 23:45:04 9.33# G; n# l( ^/ ]. a
2016-12-31 23:50:03 8.49
6 H/ ]0 Z$ e, \7 b8 }5 s, \2016-12-31 23:55:01 5.84
$ @4 z- ?4 D8 X! x( \' QName: Radiation, dtype: float64
$ P" k# U: u {- {& v7 R) ^& D- X0 m, }( T/ {
11 k# t! ~9 g8 w1 w* h& o
2' O( T1 `4 K9 V" x
36 J7 f8 j' d0 s. G b9 z& v
4
1 P3 G* I9 {& _; G% I X: ^1 c7 A9 S, ?9 t5, g0 r$ Z; J3 b0 L2 ^2 C p
60 K9 \$ k3 ?6 ?4 f! b) p
7$ n S" B3 J$ `. g H
8
* S% `+ t& y/ T+ ]% I9% a0 B: R% K H! ^2 N
10( U, U, p$ x3 b9 w# V, ?
114 j- t; T- N8 x- n, ?
12
7 F% R; w0 m3 R; L7 G& A% f13
: \! V0 b" v# w14& }5 X, ^/ m: b7 L
15
6 c* |) f/ h1 J16
2 K' i/ O) b4 B- T8 d# _17
/ o) o1 d# a3 ~: Y7 e18
$ s3 ~/ x$ ]/ E19# ]5 z7 \- V# G$ b- F1 [
20
, B% y+ a) Z& D/ b1 ~4 z& y21; d3 I0 _& e1 v; g( `8 M' k
22' V3 {- ?# e# y! U# _
23
" r6 q9 ? q( nEx2:水果销量数据集- {# s9 o' m- J5 Z$ H' z( t g) }
现有一份2019年每日水果销量记录表:
; |# ?5 K3 u( W. y# ~& r2 o
# P$ S8 d6 `& @5 J7 [, ddf = pd.read_csv('../data/fruit.csv'), @ j! L; M7 g- @* K5 U
df.head(3)
. D+ q* J$ z; y# M3 j( w+ i: c" T8 a. i7 P8 Z
Out[131]:
# Y' M& v5 W d. Y: n, \5 d' C Date Fruit Sale
! H( B) H1 e: p( ? _0 2019-04-18 Peach 15
4 ~8 r! {$ w8 _7 {# r# r8 a1 2019-12-29 Peach 15( q4 {6 k/ Z6 v& u: Z
2 2019-06-05 Peach 19" }; h1 K% i. M2 J! `/ A
1. A* u4 O7 {: Q1 i$ I
2! t" G. H( c) i( j/ n
3) e: L' U/ S% z8 E
4
0 U; i' y" M, J/ K5 Q1 @7 l55 ~6 n6 \5 Z+ P, I1 |4 S' G
6
5 @/ N2 ^2 X6 O; O/ u- r7* r s: u4 h. b) H. F# e! X; U# F
84 ~" {# b0 _" [, B# x7 U. n
统计如下指标:
8 j$ ?8 ]# }$ ^( ~9 v& ^0 B8 |每月上半月(15号及之前)与下半月葡萄销量的比值, J6 Y! A7 @7 E! N/ u1 M" v
每月最后一天的生梨销量总和
7 E# z. j5 {" i" J2 I1 `每月最后一天工作日的生梨销量总和
' D: l9 P6 C+ r+ L0 z! i每月最后五天的苹果销量均值6 F4 T8 r% J. B0 r- Y8 \
按月计算周一至周日各品种水果的平均记录条数,行索引外层为水果名称,内层为月份,列索引为星期。
# q+ k) W. ^9 Q6 `按天计算向前10个工作日窗口的苹果销量均值序列,非工作日的值用上一个工作日的结果填充。& y- Q$ J8 E& {, ` E" O
import numpy as np
; t- O7 P6 e& c( _/ dimport pandas as pd/ p x# Z: n$ A4 p( C' h# Z
1
- n9 |* s& C! ~0 E F2 u* S% T( x& x* P" K3 _$ C
统计如下指标:
) t$ f' D+ v4 s9 J7 l每月上半月(15号及之前)与下半月葡萄销量的比值
, k& \4 ?& j) p4 _每月最后一天的生梨销量总和
+ u" v. Z: t c* o每月最后一天工作日的生梨销量总和7 W, ?" |; o' L) p0 \, A6 K
每月最后五天的苹果销量均值, }. p: Q( o' D! x% w! d
# 每月上半月(15号及之前)与下半月葡萄销量的比值# k' t( A, |1 P8 B
df.Date=pd.to_datetime(df.Date)* w8 C6 c) l8 T& @# w' u6 @
sale=df.query('Fruit == "Grape"').groupby([df.Date.dt.month,df.Date.dt.day<=15])['Sale'].sum()6 L3 N. {; {, N+ F( }' i
sale.columns=['Month','15Dayes','Sale'] # 为啥这么改没用啊5 r3 J; P8 P7 m* u" w+ d
sale=pd.DataFrame(sale)5 y2 ~ i+ i y$ l/ f
sale=sale.unstack(1).rename_axis(index={'Date':'Month'},
) f/ y; C5 }" H/ x5 x r n columns={'Date':'15Days'}).stack(1).reset_index() # unstack主要是两个索引都是Date无法直接重命名2 p" A3 }$ g' M( ^- m5 J! P
sale.head() # 每个月上下半月的销量
: }4 |0 |% R; B9 T1 g$ H. v$ a, L% @& }9 y4 O* q7 B
Month 15Days Sale
5 n* ^2 |. \) r9 t* R5 D6 n D3 w/ |, ^0 1 False 10503
4 |% F' Z# h0 T% F- c6 L c* v7 _1 1 True 12341
$ Y ^" {5 z" l' i$ D& ]2 2 False 10001
2 N/ }: n7 b/ g% Q$ {3 2 True 10106. y; {; R8 T3 G" l. l+ B" L1 K( b
4 3 False 12814
Q; ?' x( g1 `$ E. ~4 a% x& m) ~6 k0 Q
# 使用自定义聚合函数,分组后每组就上半月和下半月两个值,根据索引位置判断求比值时的分子分母顺序% }6 }9 {, [9 w. L1 \
sale.groupby(sale['Month'])['Sale'].agg(
# k& V, }* y: F( X7 |" j lambda x: x.max()/x.min() if x.idxmax()>x.idxmin() else x.min()/x.max())9 l6 P0 j9 P3 V1 U: k; Y
* L# l6 L5 ?- {' E. ~! tMonth( H3 F' r5 B+ r4 d
1 1.174998) H& q* Y1 s- \. k$ t3 D( d
2 1.010499: @, n+ c4 |7 P l: {
3 0.776338
) i5 ]) g7 b+ H3 E: m1 z4 1.026345
" Z9 L0 H: X1 ]/ b. f5 W/ @; _5 0.900534+ i% D+ o! [4 F( n3 J/ F
6 0.980136% ~- M' F6 j/ W5 S3 a2 E( F
7 1.350960
7 v2 S0 Q3 g! R M' n( z. q8 1.0915841 g7 V3 J! e3 z' O. h% v
9 1.116508- \- w# u0 a1 f' O9 h
10 1.020784
- }! i2 d4 I {1 |4 z1 d9 a* `" o11 1.275911
3 J# e7 B- P# Q R d% V% z12 0.989662( i. a3 J) m7 Y p {. x. w
Name: Sale, dtype: float64
v* b& M ?8 r& G2 Q9 }1 L6 P
' G. ]" f: q/ z1 y3 z+ s% i1
. v2 e7 p& |8 u+ k2* Q+ S6 x8 L: h
3! w, G4 _* X0 v x6 Q4 @' z2 A
4
- X0 n, D( K/ q2 ~! Z7 N55 Y0 M, X+ M) d5 @% s( O. u6 y2 E
6
0 ]& t' p9 f+ i7
7 T: H5 t, U# F+ v8
1 O9 Z' l9 d/ `, b92 S! x, B2 ~- e9 D0 h) e
10
( g* R n3 S( h' b- n) ?( y7 A( _11
5 t# Z4 B# l8 i1 @; B$ u- G12& F8 T% i/ f1 V; J) P
13$ R! @5 p. E; B. y& |) k- a6 F* e
14) |) t0 s# i9 h" |7 E
15# Q6 ?# S; X, l$ ^
16. y8 c3 X- l" A1 {7 X- J7 n$ v) }
17" D1 ^/ g$ M8 Q% K/ j: u* x; q
18( |$ ^+ I; l4 t. o4 V) `' U
19' s$ Q% C$ q) W; G; \6 } [0 `
20
. u! Q, F2 a& ~21
8 |/ A/ r( W3 B5 ~& t. \$ M' ?22
( p! e9 L" [; U2 d0 n* e% j5 u& N1 v2 H23
3 r/ F9 N" `3 a242 j) e& I+ M$ M$ t0 Q; N
256 C. Y& _$ z9 W( H( t; Z2 I# v& k
26
) `( i9 E( [& v( {, U/ U: p27
& r$ m; H0 V: R$ @' J3 N) L' L28
+ y: {; x1 q0 Z" c& |! A1 a. i29
6 w3 U8 n2 S+ V' U30
6 L- ?+ x6 M5 H' Y$ m. m! O% V31
: h; @+ O; D6 Y9 H7 C! [32" b) \' {3 ?) i: M% \+ g" Q( G
332 |% p' _* }/ F
34
' [' X5 C4 U7 g) q# 每月最后一天的生梨销量总和1 j5 X& O' R4 i' a9 G& l1 l$ [
df[df.Date.dt.is_month_end].loc[df.Fruit=='Pear'].groupby('Date')['Sale'].sum(); E" F5 J) x( C, H' ~
7 W$ R* R4 Z4 Q. D+ D; H4 i$ N: uDate' B* M6 }* X+ X
2019-01-31 847
7 i+ t5 b" ^( j6 A' x2019-02-28 774
' W1 Z$ S1 u; A2019-03-31 761
8 n' C. }6 [. I: q/ P2019-04-30 648/ ^! Y/ o! p+ {+ c/ G- v( F
2019-05-31 616
/ c4 h9 {, C0 K+ k3 p1
, M' W1 W6 K; G6 h2
3 n" [9 L0 [. J' ~7 ~3 j0 ^3
C& J; [! f' v. f4
3 a) Q3 {6 I; N! Y- t! R5
$ |) P0 \5 [( ~5 ?7 v3 I5 S6
8 D! e1 M; H& G3 T7
6 C+ O7 K; a+ |& z0 V" ]* w8/ c9 E: f) I8 v! Z6 c0 X+ T
9! h- v. c+ ~' l( }6 S+ ^
# 每月最后一天工作日的生梨销量总和" Y1 I; F6 D' L m: N& U, J
ls=df.Date+pd.offsets.BMonthEnd()
) A" F8 [+ j v7 l' m- Rmy_filter=pd.to_datetime(ls.unique())
3 T" @. \* h+ v( {: S4 \df[df.Date.isin(my_filter)].loc[df.Fruit=='Pear'].groupby('Date')['Sale'].sum()
: A; g1 N0 l+ ~/ H3 G- Z7 _. P/ m8 W/ P" A8 n6 }# |; L
Date1 B6 G1 i2 [* M2 t
2019-01-31 847+ b, ^/ k4 \, C, ^
2019-02-28 774$ @0 {8 H. Q, M8 F
2019-03-29 5108 q6 B5 l* Z. c* b1 y2 `% `
2019-04-30 6487 N f# ?6 E3 j: ~2 X9 y: y
2019-05-31 616
4 T) Z) m) C7 J! j* \6 c0 H* B) }7 r1
) d3 S8 {2 j6 F- ]$ n4 K B0 J2- d3 }0 d1 d' n H, |( ^0 h
3
& p/ E4 c: p; P2 ^4
! P& _$ }" s, Q7 T52 ^5 X; W/ `# r
6
# b; J3 X9 ?$ b+ v: A) U75 z$ s( _& Q# h* x, v
8
& H* t' {. A4 h. F9 c0 b. g9
# y# N1 H% q! A7 e4 R( X7 Q& G2 u9 ^. N9 \10
! H6 F" ?% Q( w! d. X11
" H4 Y; w$ G7 m8 I# |* v( D# 每月最后五天的苹果销量均值
) O; ~; N) P% G% V2 r b+ Gstart, end = '2019-01-01', '2019-12-31'
: c- n4 Q+ g4 cend = pd.date_range(start, end, freq='M')
7 R( v, C7 `5 W7 `; T9 ?end=end.repeat(5) # 每月最后一天的日期列表,重复5次方便做差
: W( `6 F4 q) i+ B% T
# |0 o' p7 l4 P3 m4 a, C otd= pd.Series(pd.timedelta_range(start='0 days', periods=5),)
. |$ ?1 f9 O. R1 W! j2 U4 ^td=pd.concat([td]*12) # 日期偏置,最后一天减去0-4天
7 {( i1 G8 m; u( o7 W; P$ H% t/ pend5=(end-td).reset_index(drop=True) # 每个月最后5天的列表
/ N- t, f' \$ v0 P ]# h2 E; h% v. @; j5 g k: D- J* j
apple5=df[df.Date.isin(end5)].query("Fruit == 'Apple'") # 每月最后五天苹果销量
3 I1 C1 W, e9 K/ ~apple5.groupby(apple5.Date.dt.month)['Sale'].mean().head()
. H) W: J+ V$ N% y o# y
d z4 e6 @3 A# @: y2 ?% E- L' `Date
, K( R7 J( S' [/ ?1 65.3137257 ]- i& @4 e2 G& b) \$ B) l# v" s1 q
2 54.061538
! E' ?& ~+ E4 l0 m1 Q% ? r* ^3 59.3255817 r' B" t$ @$ A& Q. E; h9 `2 Z" T
4 65.795455& d: o/ ^* U7 H/ ?" o0 S" d. j0 h
5 57.465116 c. Z3 R/ w7 l6 F! H
6 ^. ^" Z! F6 n/ G8 t
1
& }# n8 M0 `/ L5 w' Y3 f0 u2
4 f8 G }6 {0 y( k; c39 m3 z! } W1 p! ]( a6 q- b1 }
4+ \: d8 j N; ]- E$ V6 J
59 w* n+ c6 g9 R
6
9 N8 t3 Z8 v( J! C. W71 Q, K- s1 d' @. H
8. v% S" B$ w" k, s" c; S: @' N
9
) q" n% o! b, y10- l1 U9 s" W4 S! D/ I
11
$ N, f8 U. @/ ` T+ Y127 f# C# L, \+ U T9 P$ I
13. v* `: v0 C/ p$ x: S3 S/ \. @8 h
14- q3 D. s. ^0 n2 c( B# y$ n6 H
15- p+ r8 ?% \. X( j- k' ^6 y
16
2 i( J' O- b& \7 O0 k6 Y170 C6 R" c: A* L/ ^9 V
180 _3 v' T" K k% {5 E8 ]# _6 v" d
# 参考答案:" n% T! W& W$ g* [
target_dt = df.drop_duplicates().groupby(df.Date.drop_duplicates(
2 K' Q' ~5 R' W, y k; S, Y3 Y ).dt.month)['Date'].nlargest(5).reset_index(drop=True)" @2 m2 Z2 f2 D
( V. P: V8 K4 v! q, r
res = df.set_index('Date').loc[target_dt].reset_index(1 [7 U: g U- k6 w$ E3 r7 Z/ q
).query("Fruit == 'Apple'"), e2 y5 N( l5 p3 O1 r u
5 v" g3 B0 t. s9 P0 q Hres = res.groupby(res.Date.dt.month)['Sale'].mean(
$ [, o M) ?8 z- n: b5 H ).rename_axis('Month') ^5 u: h( |4 H/ T( U* B
2 }3 M, s# [8 o1 t
% }' \1 _$ T- _% p. `6 gres.head()
8 h! K3 {, z0 n n ROut[236]:
2 A/ g- [) h- y2 q9 r% I1 bMonth( |+ [; {1 c: k( D F) s/ K
1 65.313725
4 Y- G. R( |) g4 M0 n( O) X2 54.061538
# V H) U4 [) [3 59.325581: b# t6 L# u* Y! n9 F5 M
4 65.795455
" A& d* s6 e* {% m5 W1 L3 E5 57.465116
/ N+ `& O$ c- z/ gName: Sale, dtype: float64
5 [- j: n6 B( w/ m0 t G4 q4 }" k! S, O+ Y& X
1
& A9 f- b7 Z6 G5 ]$ R+ r4 c) T4 l24 [0 `) F+ j9 z5 v" g, O
3; S/ I' o3 a; C( \
4
/ `! y; P. T, L& w4 y3 H Y' C) x9 _5
: V1 y' d" f: ^8 K6' |; i5 A2 E- g3 [& B( A- i
7
2 r7 H2 S) P7 K% j' X5 S8$ u6 S' d# i) M1 c, e! T' f
9 i8 E8 \- B+ a7 i1 U9 u( w6 a
10
7 ~+ y) L# j- y/ m& \, ]5 @11. m' R N7 m" D0 ?* {8 j/ r: [
12
* h! C0 |6 {1 P" Z13+ C! D9 R# O& V
14
* y9 w( S& n6 q f156 h1 l0 q( G9 Y0 P4 o6 Q, j' W
16. D4 T) l( [$ e7 B
17
6 f d) T, u0 X184 g* R- l. f% d2 m- E# K9 h4 h
19
4 [$ u. ?' ^ m) j/ c: c9 q, [20
( F, M5 m+ H! T9 S按月计算周一至周日各品种水果的平均记录条数,行索引外层为水果名称,内层为月份,列索引为星期。
8 }, q2 K! q( P. g4 X9 z. @: Oresult=pd.DataFrame(df.groupby([df.Date.dt.month,df.Date.
, f( x$ S2 [' B: K+ E$ p0 w" Y( _& N dt.dayofweek,df.Fruit])['Sale'].count()) # 分组统计 5 O6 L; R p7 Y6 }0 X7 T
! o, k( z8 T1 G, P9 d: U7 p
result=result.unstack(1).rename_axis(index={'Date':'Month'},
: S' @2 ?: J: v& M4 f' S columns={'Date':'Week'}) # 两个index名字都是Date,只能转一个到列,分开来改名字." G+ v& m+ n- M' q
result=result.swaplevel(0,1,axis=0).droplevel(0,axis=1)7 p- p# Q( h% S7 h5 s! ?% ~
result.head() # 索引名有空再改吧) r1 C5 i; `7 q8 ~' r! B' `
: L" o9 l i' V Week 0 1 2 3 4 5 64 x: `# ], b- G$ a
Fruit Month ( a7 f5 i1 z" w3 s% b
Apple 1 46 50 50 45 32 42 23; r( @# x, B* p* c5 [! q" F
Banana 1 27 29 24 42 36 24 35
6 P4 V" U/ H5 O; r. p. CGrape 1 42 75 53 63 36 57 46
+ T' A+ M; T7 m3 }& k" vPeach 1 67 78 73 88 59 49 727 A$ p0 ]' J, Z0 [" @
Pear 1 39 69 51 54 48 36 40
6 N/ v: q7 ]4 s# h/ y1. V5 |* k+ V9 W n1 @
24 ]/ Q7 L+ I4 c! }& V; ?
3
. T6 Y6 F1 ?- h9 ]8 a/ h4; |/ G1 L6 N. L8 h4 W8 ~3 g
5* Z+ z* c$ O% H
61 O+ O5 d0 g3 a/ ~0 ]
76 y1 o; w& x7 B" D5 \0 x) `/ J
8
* C2 M+ |8 L& |5 r9; \5 E$ K/ u/ p4 e0 A- k- ]3 F! Y
10/ j/ s3 o6 c5 I- m0 a
11! H) I' a. s$ Q9 [. T
12
2 J& f: D+ d! s4 p132 C" ? w9 O! F: k4 \9 I
14! `- n9 m1 Z3 c* `$ _
15
. ?' C0 C' [$ H: ~2 E0 B e按天计算向前10个工作日窗口的苹果销量均值序列,非工作日的值用上一个工作日的结果填充。5 |6 j: M; c* \3 Y% I8 W. V
# 工作日苹果销量按日期排序; h- j0 E0 O7 t: M3 [ s
select_bday=df[~df.Date.dt.dayofweek.isin([5,6])].query('Fruit=="Apple"').set_index('Date').sort_index()
* n% d, b& b5 k4 ^! Iselect_bday=select_bday.groupby(select_bday.index)['Sale'].sum() # 每天的销量汇总
8 J, {. L ?0 f2 m- S; O, a2 Sselect_bday.head()0 C( T1 ~) e0 g) o. m9 H( I
5 m! X! f7 R/ }5 _
Date
% V1 ]/ S( S, U% n; C2019-01-01 189, \7 w7 B5 J3 F5 A. r5 ?+ ?$ f
2019-01-02 482( `" H( v. W( E2 P
2019-01-03 890
3 q& D% |' Z: V( n ]2019-01-04 550
( G8 M/ B; _# ]0 o5 _" J2019-01-07 494/ ^# r; r) ?7 R
7 q' s6 J1 u6 v/ p# 此时已经是工作日,正常滑窗。结果重设索引,对周末进行向后填充。3 P% }* B! G/ A! j# g
select_bday.rolling('10D').mean().reindex(df.Date.unique()).sort_index().ffill().head()
. j B9 b: ~8 _) W' P& J, Y
2 V# K: C Y5 m, U8 Y* oDate; d& Z1 p# c1 g0 H7 d! d4 S
2019-01-01 189.0000001 F+ K( ?* K- Y- p
2019-01-02 335.500000- d! [8 L6 u O
2019-01-03 520.333333
# r- f1 o# F, l/ k2 R2019-01-04 527.750000
9 F& _* r' q x) M8 y2019-01-05 527.7500001 O I( {3 P( A7 ]: Y
( T* i6 {- i2 R+ m————————————————' [5 D. {1 P# l
版权声明:本文为CSDN博主「神洛华」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
; t0 Z# r, k8 z+ r, w1 i原文链接:https://blog.csdn.net/qq_56591814/article/details/126633913
; u! J2 U7 L* k$ u" o% }; b
% [' y/ [) f1 q$ Z; q z& G6 n y _
3 D$ c; {" p( M$ \$ T |
zan
|