- 在线时间
- 1630 小时
- 最后登录
- 2024-1-29
- 注册时间
- 2017-5-16
- 听众数
- 82
- 收听数
- 1
- 能力
- 120 分
- 体力
- 565164 点
- 威望
- 12 点
- 阅读权限
- 255
- 积分
- 174772
- 相册
- 1
- 日志
- 0
- 记录
- 0
- 帖子
- 5313
- 主题
- 5273
- 精华
- 3
- 分享
- 0
- 好友
- 163
TA的每日心情 | 开心 2021-8-11 17:59 |
|---|
签到天数: 17 天 [LV.4]偶尔看看III 网络挑战赛参赛者 网络挑战赛参赛者 - 自我介绍
- 本人女,毕业于内蒙古科技大学,担任文职专业,毕业专业英语。
 群组: 2018美赛大象算法课程 群组: 2018美赛护航培训课程 群组: 2019年 数学中国站长建 群组: 2019年数据分析师课程 群组: 2018年大象老师国赛优 |
: A; }# a; V" \- W
5 ~7 T' [, q _0 v4 v* J, V% |
" a$ m- ?! S3 \1 P& r文章目录1 f$ d5 Y$ j& m4 A7 x
第八章 文本数据
8 w$ b# \# C1 }. M- V, V. R& s2 ~% c) E8.1 str对象" W' I1 G; V5 m; j
8.1.1 str对象的设计意图2 j% c; m' |+ u$ J" y' X8 L9 e
8.1.3 string类型
$ c' r" s- q2 t8.2 正则表达式基础9 n) A) g- C" g$ P$ ^$ y
8.2.1 . 一般字符的匹配' }6 b. R4 U/ X" z- Z, e0 j
8.2.2 元字符基础8 u8 @2 t5 Z) V" g* x% j/ c
8.2.3 简写字符集3 U# O4 n2 M- u: d( ^- j
8.3 文本处理的五类操作/ H- N) K% N$ |& y. L
8.3.1 `str.split `拆分2 @/ [8 \. X/ y! ~$ C% z- K* Z
8.3.2 `str.join` 或 `str.cat `合并
& A8 m0 W5 [9 z7 p8.3.3 匹配" [4 c T2 g s# e! {& G
8.3.5 提取
3 ^) ]5 R- h7 y' q8.4、常用字符串函数
3 B2 U8 W2 y: o% L- s8.4.1 字母型函数6 Z" x, }6 L$ Q" |4 e T
8.4.2 数值型函数+ j$ i: B2 r# ~& P0 L+ Y
8.4.3 统计型函数# w/ T4 q" S% Q4 M$ S
8.4.4 格式型函数
0 z5 J. c, D3 U0 x4 l, X1 z8.5 练习
% o2 {4 e. u$ AEx1:房屋信息数据集
' _8 T, q: y2 I% @* j! dEx2:《权力的游戏》剧本数据集! m4 b! Q: M9 @7 z
第九章 分类数据
4 ]$ K. K1 X0 \7 J! X% P9.1 cat对象
) l6 s% A% O7 h: ^3 a9.1.1 cat对象的属性5 ^7 P+ e2 N% Q+ s$ M$ W1 J. x" Q
9.1.2 类别的增加、删除和修改
+ b$ \0 {3 G. N1 Q/ D e9.2 有序分类
" Z7 k/ y" i2 t9.2.1 序的建立
7 ~/ i( C& a/ w4 `& i( c# a9.2.2 排序和比较- x- \$ C7 h+ ^
9.3 区间类别4 s8 X. G' X' ~) U" m6 T' ~- f
9.3.1 利用cut和qcut进行区间构造# _( f% \1 F- H: h3 p" s
9.3.2 一般区间的构造! n K. e) f$ ]; i2 J( t
9.3.3 区间的属性与方法
! C4 J# r' G/ R; ]1 \7 y: @9.4 练习7 o% {% a0 O* O8 F1 f1 z2 r0 [
Ex1: 统计未出现的类别- M/ L. {: b- g/ R/ O# a: v& _, o
Ex2: 钻石数据集
6 s# d. j3 N4 Y3 X; {5 v3 ^第十章 时序数据
2 }8 g* w7 k% M1 ^) h' I10.1 时序中的基本对象
8 X% e4 U/ f" M9 W) t10.2 时间戳
+ N7 D' j- u5 k5 u5 ^: Q, {10.2.1 Timestamp的构造与属性
3 J& r; X+ X7 h10.2.2 Datetime序列的生成
( L9 q, V* [) M8 x0 n10.2.3 dt对象
* ~: R9 V+ B- X7 T& t10.2.4 时间戳的切片与索引
# s* {$ x2 _* y% m10.3 时间差6 v7 J7 O' S! G% F6 q- m9 W
10.3.1 Timedelta的生成0 n0 [: c7 k5 E' |6 x/ N2 O! h
10.2.2 Timedelta的运算, W5 K3 M, u. p" B$ P6 ` ?4 N
10.4 日期偏置
% ^1 Z8 D" o: { b4 [10.4.1 Offset对象. L; M9 K) U. s5 C/ q- L9 |2 k
10.4.2 偏置字符串
6 N: M \, v& |4 ?, s) m) z" o10.5、时序中的滑窗与分组
7 B& m% ?! W. v10.5.1 滑动窗口
2 J" |/ X' G0 I$ V10.5.2 重采样
. `8 h$ \( G' Q( _10.6 练习
5 n7 a; r. ~7 N) f9 z, L* W0 h2 h+ m% OEx1:太阳辐射数据集$ L( Y' a4 q( h
Ex2:水果销量数据集0 {& l/ r, x! W' O: B7 o$ d* O
课程资料《pandas数据处理与分析》、github地址、讲解视频、习题参考答案 、pandas官网
9 F, ^, J2 z" ^7 S1 a* a3 M传送门:' K* d" k1 F* }/ o* H6 {- d
3 F* D& o' [. X* r" K# M( J' Qdatawhale8月组队学习《pandas数据处理与分析》(上)(基础、索引、分组)
: J: y/ ]" O8 c. C2 gdatawhale8月组队学习《pandas数据处理与分析》(中)(变形、连接、缺失数据)
4 ?( R# T, t( D: b第八章 文本数据
) R7 e/ G/ B& H2 K3 ~7 |) n# T8.1 str对象3 s/ C3 n( E" T9 H+ i
8.1.1 str对象的设计意图
) p I' X3 H0 {$ }7 K str 对象是定义在 Index 或 Series上的属性,专门用于处理每个元素的文本内容,其内部定义了大量方法,因此对一个序列进行文本处理,首先需要获取其 str 对象。在Python标准库中也有 str 模块,为了使用上的便利,在 pandas 的50个 str 对象方法中,有31个是和标准库中的 str 模块方法同名且功能一致,例如字母转为大写的操作:1 M7 Z+ l: Z+ A( D1 L
5 }. B y4 X" H5 {
var = 'abcd' b, X0 ^: ?, W+ L( ^. K
str.upper(var) # Python内置str模块 e: g/ Z5 L7 r/ a% J, o
Out[4]: 'ABCD'
1 u- e9 T' e3 V9 [
$ k, a4 u% e( w( `s = pd.Series(['abcd', 'efg', 'hi'])
6 y8 j' E$ f# L9 w: g! L2 N3 x8 `6 @7 F ?6 ?$ t3 }& f
s.str
$ Z* K# L- D9 Y; ?8 g2 cOut[6]: <pandas.core.strings.accessor.StringMethods at 0x2b796892d60>
% N. e2 H. Q! J/ u7 o
- B3 k5 d: u! q9 F2 Cs.str.upper() # pandas中str对象上的upper方法% y* j" I1 V2 o
Out[7]:
1 g, l* n0 {% v0 e- ^/ ^0 ABCD
/ G. @5 T5 A1 S. I2 z1 EFG
$ b7 ?) T {0 l: Z1 V2 HI
% E( z5 K( f3 E3 d9 gdtype: object; d9 a- z( E6 m4 b: b) L7 r
1* {8 ^3 s. w' I6 ]: C! w
2' D/ h, ]& i' F9 L
3
8 f. U- O& R$ h: V4
; i. ~! j. k& g* T5( h& P- D* F7 s& s( s
6
- q9 f) ?( B+ {% f9 M4 k0 E! y7
4 N. t8 X1 T8 {' [& v8 S2 m8; V) k0 ~: Y& T
9
4 Y2 ^9 y8 C1 z5 i10
( W2 o8 G8 ]% t- p5 t# |11
3 s7 r5 n1 m3 [! b3 n3 I12
3 R( W& q2 Y4 H2 E1 I' D13* P9 ?& w- ~, b. L8 i: Y
14
0 A6 \* S; u3 l& X1 e) x+ L8 Q15
M& I0 F+ V2 s3 [0 N' l8.1.2 []索引器 J. J9 `3 a: o7 Z4 @
对于 str 对象而言,可理解为其对字符串进行了序列化的操作,例如在一般的字符串中,通过 [] 可以取出某个位置的元素,同时也能通过切片得到子串。
3 D' u( [5 c/ I. [! R# b6 i pandas中过对 str 对象使用 [] 索引器,可以完成完全一致的功能,并且如果超出范围则返回缺失值:
5 e! O4 Y- U1 M$ [1 ~8 `1 a w
6 h# ]. P- X/ Y s& \/ z! ?) Ys.str[0]) E5 e" z" l. M- [" P! q4 M
Out[10]: . C* I- I- s" B! D
0 a
6 Q5 T5 K" [) |: L( E' S9 G) l7 Z1 e
0 k# t% \2 u1 G! k* h, n2 h* e: F8 _. w) Y& h/ {8 h
dtype: object
n/ ]# C4 c8 `5 H
; D5 Z: ^. |( D( U z! Vs.str[-1: 0: -2]
?, |+ _' S7 m. V* t8 K/ gOut[11]: : c, U4 p5 |( U/ ?# Y% N' E8 M
0 db$ K: K- h- v; S8 O
1 g3 a4 I; e( u0 P: A# C1 B
2 i
$ X& c, Z5 `& Z+ w4 Y, f' d' W }% Odtype: object
7 T* P, d F# }. c0 |1 c: d2 x! y4 [- w2 N& c) z5 q, ]
s.str[2]+ n( m/ E- `- E" h9 l' y
Out[12]:
% v q4 B& M6 H; h, }# }- ^0 c
2 r0 Y2 A: s. G/ a* l1 g
9 A: h: R. C7 X: M: j2 NaN. O! h ]+ X( k' D7 n1 S0 N
dtype: object
3 x5 k5 g; _/ b g$ O d
+ |- s% E: s# M: f3 Z0 ^1
5 J. H' A* Y- o" e28 N* }7 q" }+ J- I' |6 H! Q
3) g- n g4 j" E$ Z
4
) a4 t4 i# z4 q7 e3 k5
7 h0 m8 L5 L* ? v6
5 M0 y# I' X' n# Z9 C. Q7 j3 R4 w+ T: z; G
8- F! V# N) w1 i* f* v
9. k, A- E, t( D- }6 o* O( D6 b
108 a q' n5 i4 X8 R% R. C! ^" _
115 ^% A% V1 y1 @
123 _2 O1 Z6 } v& ?0 S
132 ]+ u5 z4 B+ x7 ?+ Z
14 t1 _" Y7 M5 w0 Q0 V
15( s* Z* e, |9 K' S; w
16, a! g5 K1 e/ Y# s. Q8 q5 y
17
0 f8 x4 w8 s P* s18, R- a$ }9 e$ g1 r' @0 A" p
19+ @$ j& y, ~! J" `7 A5 |' D. q
201 ~4 ?& { K f, j/ _) ~
import numpy as np+ l7 |( b, P7 p% F
import pandas as pd
8 p& T7 y) C2 z- _* N" T3 B% Q7 M: _# Z+ t
s = pd.Series(['abcd', 'efg', 'hi'])/ t) u% |- j* k4 Y9 Y
s.str[0]
1 Y$ z6 |% H4 [7 [) @1
( k! M! ]9 k. f, A# Z' D; m2
6 K/ v* j7 Q% W6 G2 |8 V30 t, T; b, k4 k' t0 Q
40 e9 d. l3 ] A$ q$ r y
5
& O/ T' a1 H% w" t9 u- L i X$ m0 a% Y" t- P# {$ \3 g5 F
1 e
6 v/ M \/ y" ?; g) J( |5 L' q" p2 h
6 ~, w6 C3 f! adtype: object9 X- _ k6 ^, L/ e
1! B; n3 z& s% D: x2 H
26 Z! N# P w& ^( n7 q6 r. Q
3( g5 m) x0 {. U3 L( p
4
* D- l5 J4 `& ]) M8.1.3 string类型
* Q8 N3 G0 ?, z* k/ L6 k1 y 在上一章提到,从 pandas 的 1.0.0 版本开始,引入了 string 类型,其引入的动机在于:原来所有的字符串类型都会以 object 类型的 Series 进行存储,但 object 类型只应当存储混合类型,例如同时存储浮点、字符串、字典、列表、自定义类型等,因此字符串有必要同数值型或 category 一样,具有自己的数据存储类型,从而引入了 string 类型。
& z2 G: n. E9 D: p" L" S 总体上说,绝大多数对于 object 和 string 类型的序列使用 str 对象方法产生的结果是一致,但是在下面提到的两点上有较大差异:
# s- i% Z$ W; ?% ]: |7 ^8 J7 a
! J" D- s5 i1 r, L* [. L3 V/ W r" Z二者对于某些对象的 str 序列化方法不同。
+ {& ?8 N8 d; x( y, z$ }+ R可迭代(Iterable)对象包括但不限于字符串、字典、列表。对于一个可迭代对象, string 类型和 object 类型对它们的序列化方式不同,序列化后str对象返回结果也可能不同。例如:. h7 O/ O& K1 o" \7 n
s = pd.Series([{1: 'temp_1', 2: 'temp_2'}, ['a', 'b'], 0.5, 'my_string'])! p9 L! r1 J& l1 y+ s& E
s
1 U; m& o$ A; \+ C6 d& q G4 }1. ?9 U% \9 U* T- X( }" ^
2
4 A' L d& ~) C0 {1: 'temp_1', 2: 'temp_2'}
6 W% Q# V/ k4 h. C: m0 m+ a1 [a, b]- J" o& J: @4 P
2 0.5
4 ~3 Y6 @+ ?9 h3 my_string
8 f6 {/ p9 Q" {3 Adtype: object6 V! S, _9 x) b/ X8 z
1+ Z( o7 W8 m1 R2 h4 K P# `+ T6 ~
2
+ @/ E) E: A5 ?1 p- b2 o" P @3
: w) P$ t- j7 F! g4
* S B8 b( ]6 O, g5. c0 E! E- w: E- ~% k$ V
s.str[1] # 对每个元素取[1]的操作* U+ r7 z, \% {' A* S
1: H5 ~4 n6 {; |; I) Z0 m( M- q
0 temp_1/ F ?% R! A% z& v. `
1 b* u: E! h: _% T- {
2 NaN( g2 K; ]7 }% W) m8 X
3 y
4 v* m3 N' z8 ~9 Y0 ^" E0 Edtype: object! ^2 w( ^" @+ H. G: I
1
! Q) [' m! X; B# D0 J7 a$ X2" [. d% E+ k# Z4 x
3
* E' h# w1 c4 Q P, a- f) H7 E45 _( F% _7 K J" L$ K0 A3 S
5
4 |" k/ y# G2 N3 M/ ws.astype('string').str[1]5 q! N7 E7 R9 ]; _
1% l$ H5 L/ g: A1 @5 p8 R; G4 X! S& q9 ]
0 1
' O. G' E) i4 ?) m1 '/ O- g8 P& _% v- t0 e( g& ]$ w
2 .
6 D- m9 K9 }9 R) [4 W" @* v. N3 y" ?6 J! ~- W; y( Q
dtype: string
; C5 R+ z9 l2 s2 |: U) Q$ u1
1 L) L. y7 v1 Y9 W$ M2
- |$ W( B5 k* x% p6 Y) x37 E2 _& |0 ?! x" v* W6 e D
42 @5 l& D6 }7 c% ~) M- V2 F5 h! j$ |
59 x7 c$ [ r2 Q- }1 R
除了最后一个字符串元素,前三个元素返回的值都不同,其原因在于:
2 [6 {% r9 W' j. C
) x4 l0 I1 I7 @: C9 _当序列类型为 object 时,是对于每一个元素进行 [] 索引,因此对于字典而言,返回temp_1字符串,对于列表则返回第二个值,而第三个为不可迭代对象,返回缺失值,第四个是对字符串进行 [] 索引。
, y3 f+ a8 T, M0 @) p# ^string 类型的 str 对象先把整个元素转为字面意义的字符串,例如对于列表而言,第一个元素即 “{”,而对于最后一个字符串元素而言,恰好转化前后的表示方法一致,因此结果和 object 类型一致。2 d0 K9 s* Q: U. N! z" ?
string 类型是 Nullable 类型,但 object 不是
4 k! E2 @3 y! k- ]. Q6 W 这意味着 string 类型的序列,如果调用的 str 方法返回值为整数 Series 和布尔 Series 时,其分别对应的 dtype 是 Int 和 boolean 的 Nullable 类型,而 object 类型则会分别返回 int/float 和 bool/object ,不过这取决于缺失值的存在与否。! U; Q1 ]" Z$ q
同时,字符串的比较操作,也具有相似的特性, string 返回 Nullable 类型,但 object 不会。
5 H' O& o3 O' H' Is = pd.Series(['a'])
0 W8 K7 D" L( o' j$ f a
+ D0 I% }- F2 o) N+ z& i: js.str.len()
0 Y! l( h2 W0 z* _Out[17]:
4 B1 ?, n1 p2 _ x7 j* k% [0 1( ]: ]% B9 }: g, x' q
dtype: int64
2 t7 ]! u4 P. ^: l8 d" b% ~8 y0 c
, j, T' U7 }) \3 J6 ~s.astype('string').str.len()5 i1 ~" x) J7 X' u6 \- }
Out[18]: 6 O: ]5 X3 q7 `2 u
0 1, _# p ^; n" V$ a/ L& Z
dtype: Int64
" W, C9 K& J: s" l2 I
) p2 Z, E9 _3 S+ D# q% [" K6 Ps == 'a'
+ X3 k; q0 u" q6 e' b2 p2 FOut[19]:
3 J# T- Y* f1 C0 True
! l; E# N: z4 O, {0 F3 Q* Sdtype: bool/ m( D7 F$ x6 r* ?* _
# V% U( \. [$ a$ y# r
s.astype('string') == 'a'
+ T. |* A s! ]. H3 bOut[20]: & a! D" d4 T" F/ i e) p
0 True
! Y% T C$ g+ x9 b q5 i! Sdtype: boolean
* O/ I" m$ _7 `/ j) i/ h
+ z3 d' W8 l7 T, ~, ss = pd.Series(['a', np.nan]) # 带有缺失值. S* r* u1 ]" `: j3 g4 R
) I! y& Z3 e9 X$ E4 I/ }: }- M
s.str.len()4 d/ D* u$ ]. A! b2 T1 w
Out[22]:
$ F G7 e7 H* t3 F5 i* Z6 l0 1.0
+ D& B" {; o+ H1 k2 B& o1 NaN" G: s+ U2 r/ ^' N- c
dtype: float64
. C$ C8 F. b9 S; }* x' m8 n, s7 e9 a6 u* J, _3 e* S; B
s.astype('string').str.len()3 t: t8 Y2 o! u$ @
Out[23]:
2 g* S9 v, V0 M9 i1 G0 16 ], ]: T) z. r* q5 t5 I
1 <NA>
3 l( L$ z# u, u( G. m" ndtype: Int64# \) i) v# ?: e+ f
0 i: s7 n `' C( U3 Os == 'a'& a( [6 N1 ?) T
Out[24]: ( s: j" c$ A0 ?9 W2 a
0 True8 m6 T& z3 b4 ?/ A
1 False3 M7 b: r4 F; m* M' j& L2 E% \6 l
dtype: bool5 u1 i5 J' X. |% k
# x5 {5 p0 L, A5 \6 L3 W1 N& n
s.astype('string') == 'a'
. B1 q+ {& i( n5 g) Y' Y0 e( _Out[25]:
+ @% k- x+ p& A0 p6 v0 True' i9 G# f7 G5 Y8 T5 y
1 <NA>
) ~7 H# @$ D" x; C% \dtype: boolean5 P2 d7 O* X' c" D
4 W" _' \2 p! ^, j* \. H6 C/ u1
1 d. J3 H8 L5 E6 \4 d2
7 Y X: }) o( m3
' ]: L# a6 |; o( k% F/ g* D4) X! _) ^6 L; j
5. N; U1 t' J8 G2 g" U q9 p8 c
6
x ]- [) N% |% W: ^' n7 S0 ^# W7 F; D& e+ z
8
( c8 k; u, Q, ]3 {9
, j6 q# A' s2 r) c+ u10
9 R* R- i+ h) N* ~$ m- e# v11) u/ h6 M+ Z+ r' Y
12
6 H r0 ?* `* u1 ?* } j# u( l3 ~132 m, `+ E, f! q/ P4 F2 R
14
: v# Z g0 v/ A0 x, V15, F0 d* D1 f0 j! c" G9 S0 F
16) g- M/ q! j5 M6 s/ o
17
* N; R+ j; \; y0 B9 T% K4 }18
5 O4 W1 P/ }" P" a& p7 k192 J5 L+ c8 w- j5 N! n: @/ @
20
9 L8 y. }* W/ e4 t6 p* s21
8 X' P% e i1 }, [8 Y6 n" ~, X8 D22+ C+ V+ |$ R: B! M
238 @8 t9 l2 @5 z$ d l
24* D3 H" |7 j/ L
25
0 q: m* i# J1 {7 P! d26
7 F3 ?/ N6 C& |! b1 x5 b Y1 c+ i277 U0 N3 l; y* L$ P% V
28
2 b/ d' E/ L5 g8 G, U295 ^# {; f. L, t3 K( B# [2 U( ^
30
& S' }1 X. X" b5 }. G31
- q& b( Y X, J/ E32
7 s; R0 `. C6 H+ ~$ K) k33! t. q1 z+ u Q, n( R" J4 C1 O e" g
343 O7 r: h' q9 ]. F1 f: e- F
355 }& [8 K: i$ Y* S7 X4 c0 _
36- I: N, ]! v1 u$ d% A
37
' m- l0 e1 M6 v1 g38
# n7 U X) K3 k& q( ]39- y v0 ]* C2 S: n7 e0 m
408 z6 `8 n; B: X! F* W
41
. ?$ |, b0 U( N42
6 E$ E# m! A5 P2 d6 ]430 m1 M6 T: W/ C- R. u. W
44
2 z" s( i! K' l) c45
- b# n. C+ B8 Z46% }4 I9 ^, T7 Y3 r
47+ K8 u$ o4 Z+ `0 S/ @
对于全体元素为数值类型的序列,即使其类型为 object 或者 category 也不允许直接使用 str 属性。如果需要把数字当成 string 类型处理,可以使用 astype 强制转换为 string 类型的 Series :! U- C% u* y: _4 i
% i0 Z; R5 c5 y/ Fs = pd.Series([12, 345, 6789])' J; c4 p. U1 B2 q# ^* k7 R
, P8 |6 E& L" S: J# ~, g! X* l' N1 @s.astype('string').str[1]5 _+ p, \, Q' T2 o7 K; L
Out[27]:
. u7 Q4 q1 F% z! b) \5 @0 2# Q6 ^; ~5 ~/ u: y- ^* ^, _5 _. _
1 4
( @: c1 p: y! ]0 r/ M$ X2 m" W; }2 7
5 N* B% \& z' m3 Xdtype: string
: V* _0 T9 I( H- S- d3 d6 q2 _, A17 o8 \& R5 c1 k- z: J( x. F
2" r7 {' I/ G6 H7 I* t! \
3
- ]; B; }/ z5 C' p, f. s+ w9 i4
9 P# X% d1 j5 G0 U; Q5& N$ V- h7 A) M: e) b& ^2 t
6
' s% n) c( O' X! V2 X; j; p- G7: Q0 }. H% v1 j' w* k
8
" g/ V) U# }. L! f' C8.2 正则表达式基础5 b7 y' n) W& z; P' R7 G- M+ A
这一节的两个表格来自于 learn-regex-zh 这个关于正则表达式项目,其使用 MIT 开源许可协议。这里只是介绍正则表达式的基本用法,需要系统学习的读者可参考《Python3 正则表达式》,或者《 正则表达式必知必会 》这本书
( i: ]/ G& N1 H4 v$ M% B- A; m( o$ d" C
8.2.1 . 一般字符的匹配
5 z6 ?! @* y) O I正则表达式是一种按照某种正则模式,从左到右匹配字符串中内容的一种工具。对于一般的字符而言,它可以找到其所在的位置,这里为了演示便利,使用了 python 中 re 模块的 findall 函数来匹配所有出现过但不重叠的模式,第一个参数是正则表达式,第二个参数是待匹配的字符串。例如,在下面的字符串中找出 apple :
: o! V& F5 N: U# J: P) c6 m* Q# K/ N3 A
import re- k4 M- s i) e# j* o
" B% B& ^' J; q M; y6 V* Xre.findall(r'Apple', 'Apple! This Is an Apple!') # 字符串从左到右依次匹配# X, o& Q3 M! w! i( ^* T
Out[29]: ['Apple', 'Apple']) Z1 T! [4 q* U* a
11 {9 C( B& E, ?
2
9 ]: O9 k1 H2 J7 P k3
) U% x# p' E* e4, f: j( V: o# z" {5 z% J. [& t
8.2.2 元字符基础" h2 h* S" @% K+ \: _4 Y0 v
元字符 描述
H. s8 X$ [( C( Q* Y. 匹配除换行符以外的任意字符2 T! r7 M- p: J. {' Z
[ ] 字符类,匹配方括号中包含的任意字符
6 u! k6 R' `) \" r! b, M[^ ] 否定字符类,匹配方括号中不包含的任意字符7 q E) c j6 `/ T, m6 f5 S
* 匹配前面的子表达式零次或多次
) y# | D% s/ d+ P e7 x! H+ 匹配前面的子表达式一次或多次。比如r’d+'就是匹配数字串,r’d’就是匹配单个数字
. F9 }9 r- u, V- x, V* p; F% r* l, f? 匹配前面的子表达式零次或一次,非贪婪方式
. r# u! f1 i% D2 u. V f2 C8 V{n,m} 花括号,匹配 n 到 m 次由前面的正则表达式定义的片段,贪婪方式
' W/ W6 E7 ]5 }. o# a! M7 l" _(xyz) 字符组,按照确切的顺序匹配字符xyz
% O6 W/ k0 x/ s1 @4 a| 分支结构,匹配符号之前的字符或后面的字符
$ k2 N0 ?! S4 K {$ D\ 转义符,它可以还原元字符原来的含义
8 l: e( q) X" y" m9 W; v^ 匹配行的开始0 o- j3 v; x. u& e0 }* V
$ 匹配行的结束
: {" y" X \6 ]0 |$ K5 ?5 limport re
/ Q4 e. R P$ P3 ^re.findall(r'.', 'abc')
- I a- z( D+ ~4 s( uOut[30]: ['a', 'b', 'c']2 [+ F% B/ N" ?: l4 \
' R4 `. r0 U, D* D+ }/ p3 q- kre.findall(r'[ac]', 'abc') # []中有的子串都匹配
% Z3 M G! k8 t$ UOut[31]: ['a', 'c']" f) l; H; q5 A3 w& y9 }
. h9 D4 t! \" y& J* Q- ^
re.findall(r'[^ac]', 'abc')
6 @ F$ ~7 a* |# R0 y \$ R7 ?Out[32]: ['b']
; h' E5 A& S4 M% X
( G( Z0 x% e+ I6 Y1 U) I0 ]" o0 rre.findall(r'[ab]{2}', 'aaaabbbb') # {n}指匹配n次
1 {7 Z; ?( L4 h, s n9 zOut[33]: ['aa', 'aa', 'bb', 'bb']
) e* n8 k/ R: w$ S# l) [
3 E9 C" d: R* G/ D0 ~$ a2 Kre.findall(r'aaa|bbc|ca', 'aacabbcbbc') # 匹配前面的或者后面的字符串
9 [* d4 i3 p e. m. X( ]Out[34]: ['ca', 'bbc', 'bbc']1 E( P3 c. P( a4 Q
% X$ J4 ?9 o2 ~2 T
# 上面的元字符都有特殊含义,要匹配其本来的意思就得用\进行转义。
$ S) v# m9 G, D8 i) _+ q& x) C% y"""
' l6 d- m. `" @( k0 b6 ?' v1. ?匹配的是前一个字符,即被转义的\,所以|前面的内容就是匹配a\或者a,但是结果里面没有a\,相当于只能匹配a。& M8 z3 ^7 Y7 S6 s
2. |右边是a\*,转义之后匹配a*,对于竖线而言左边优先级高于右边 `3 o4 @+ V& k- J6 ?. b
3. 然后看目标字符串aa?a*a,第一个a匹配左边,第二个a匹配左边,第三个a虽然后面有*,- V$ O' J/ q* Y4 v( m8 J
但是左边优先级高, 还是匹配左边,剩下一个a还是左边,所以结果是四个a2 z/ `5 j! q+ e
"""
" Q4 I$ [! s' P" z% `1 X8 P4 C \: b+ D |/ _( o, Y! U
re.findall(r'a\\?|a\*', 'aa?a*a') # 第二次先匹配到a,就不会匹配a?。a*同理。& P; T" I# Y2 U5 @4 K
Out[35]: ['a', 'a', 'a', 'a']
4 x( E Q' y. L- z- o x- f( X# q6 S% _& `) q
# 这里匹配不到是因为目标串'aa\a*a'中,\a是python的转义字符(\a\b\t\n等),所以匹配不到。5 p/ D- G L9 A& ~- ?, q1 ~2 q
# 如果是'aa\s*a'之内非python的转义字符,或者'aa\\s*a',或者r'aa\\s*a'就可以匹配到\字符。4 B" Q7 _! c* r" \4 G3 _
re.findall(r'\\', 'aa\a*a') & n& e( P. `" |& H: R! j
[]7 b+ r" u7 {/ n# s9 h$ @" {
! m, o( U/ M8 H
re.findall(r'a?.', 'abaacadaae')
& @% R! `, P, ~7 U6 J8 \% t. _; v2 oOut[36]: ['ab', 'aa', 'c', 'ad', 'aa', 'e']
: _+ ]. e% [; f& i
* X: c' ~7 M5 \% E2 r8 mre.findall(r'(\w+)=(\d+)', 'set width=20 and height=10') # 多个匹配模式,返回元组列表
) n {1 K! D+ H2 Y/ @[('width', '20'), ('height', '10')]! i) {: n6 p! `, S
* T+ \ x4 [$ a9 w1
/ {* I2 X' _# f2
! z% ^) ]: j; A+ p4 Z6 G* r- L/ ^3' M0 l9 p; n2 ^9 v- c/ D* P) |, v
4# ^; L8 z$ n+ Z; T" }( c
5
F/ v7 }' [* N2 d- I/ x+ v6% D4 D/ H5 E4 }7 v c
75 N. x% a7 n& g/ Q7 T6 G# H6 D% s
85 H) n! |8 N9 a3 L
99 w- d) c3 Y& N
10
. V; o2 \" p+ t3 @- l, m11& j; I3 {, Q8 Z# E# W+ L
12
2 N9 r1 @4 A: O1 g$ v3 Y( {13. N- J" K4 D7 o+ n
14
6 ?& u. X, K$ R3 g15( D1 V) A/ p5 p. w1 Y0 p; r5 F
16: ^) |+ j$ y' w% a
17( I0 a7 p8 v- { c. c
18
, G, v6 ]2 d! d$ K2 h* j19
$ W1 X. l2 _# q" r0 E200 ?/ Z( P% e2 X$ W
218 J' @7 @& C/ @' |
22
8 V4 @& z- F4 y8 B5 E4 ?239 D2 W( r3 L, t I' {* j8 G. x! k
24
( J2 C% D( D- R6 Z h+ d3 s8 _4 b# K C25
- {$ v( m; y1 f5 `' \8 ?; V26
/ A# N- d4 d$ h* u27# I3 Y7 @$ w3 U* ]! P
28" {5 m6 G2 G5 a$ `& `, ?1 Q$ h
298 y* }! r2 s# c( v5 E' B& ~
308 G# l2 ^2 ^' k# }
31
! L! j- b3 ` B; v5 |$ y32
1 l/ D2 }, W1 b" ]2 p& x33
$ \9 H: Y3 p8 g: B8 ]34' V6 c$ t5 A5 ?: O5 u; M
35
* }* b% o- R# d+ y0 M% y36
& F# L) o+ O# _* y37
m2 h }4 r' w# P3 ^5 K8.2.3 简写字符集- A. O! @2 ?* o' A4 H
则表达式中还有一类简写字符集,其等价于一组字符的集合:
* t3 V% {7 n, U* z* \
( b$ J* u, b8 y% K# K1 u7 r简写 描述
$ b9 T8 O4 w- L7 a2 q) X i2 T\w 匹配所有字母、数字、下划线: [a-zA-Z0-9_]
* f7 l9 S4 V, K+ ]+ e8 c$ `\W 匹配非字母和数字的字符: [^\w]( @/ A8 A! ~( ]9 f% q+ l; @+ D. e
\d 匹配数字: [0-9]0 F* h+ y# ` ~7 Z. C
\D 匹配非数字: [^\d]
# Y9 N% U; R; t4 O\s 匹配空格符: [\t\n\f\r\p{Z}]
3 W, m# H3 s& o9 O# w\S 匹配非空格符: [^\s]' u% D9 K2 v0 l5 @# C% F7 u$ `7 K
\B 匹配非单词边界。‘er\B’ 能匹配 “verb” 中的 ‘er’,但不能匹配 “never” 中的 ‘er’。
; l& `% p1 w3 A; x: l: V* g bre.findall(r'.s', 'Apple! This Is an Apple!')4 X( I; L" f, m9 T/ @6 y$ z8 I
Out[37]: ['is', 'Is'], L: B9 _& V5 [- E4 v, k7 j
* a% @8 I7 B5 p+ m6 v
re.findall(r'\w{2}', '09 8? 7w c_ 9q p@') # 匹配任意数字字母下划线的组合,但必须是两次$ Z9 ] V0 D3 W9 s" H0 t
Out[38]: ['09', '7w', 'c_', '9q']! G S0 R/ G+ w) z
; c; F3 E5 d2 f, qre.findall(r'\w\W\B', '09 8? 7w c_ 9q p@') # 匹配的是两个字符串,前一个是任意数字字母下划线(\W),后一个不是(\W)
! M+ [8 V$ Z6 A/ K: M Z( cOut[39]: ['8?', 'p@']
) n6 o8 i) i; U: @* F* O0 z& ]# E4 U X! Q# h) `( ^
re.findall(r'.\s.', 'Constant dropping wears the stone.')
, t; i4 P/ W2 \( |+ b+ hOut[40]: ['t d', 'g w', 's t', 'e s']/ V4 b7 P1 [) H+ v
! z P/ h3 g! S& Y0 x- are.findall(r'上海市(.{2,3}区)(.{2,3}路)(\d+号)',1 K' H0 n; `* X$ b' F
'上海市黄浦区方浜中路249号 上海市宝山区密山路5号')
$ k+ ]+ R$ D( V. u) D; f+ ?: [1 K6 ^1 O/ D# b7 Z1 K
Out[41]: [('黄浦区', '方浜中路', '249号'), ('宝山区', '密山路', '5号')]
. e, P9 D* D1 \4 S' I' n4 U7 i- o S5 q5 G- X: R6 E, q+ ]/ e
1
4 H( p4 U4 \. G( u) O2
, h& F2 A) G5 V. I" f7 A; P3% l, F/ C% K2 L+ B+ p! D
45 G2 @4 @7 P6 {+ T9 }" G
5( G8 E2 ~: I- p7 V! m
6
! J8 ]8 U4 [# l* U- j0 u" g7" N, ^! l% D8 Z9 _4 x1 a
87 ^3 }% {& v( A* t
9 k! ^' s% u7 B w) }
10
- d: D% A/ {+ `# W" }114 R3 F1 {( z2 r; W; _
12
6 u( T u! R- h+ x: \' Z- a1 b7 [, f13
4 a$ n+ B/ d2 H5 k$ n+ d" V- i148 |5 B( Q6 ^2 A; l' {& J2 u9 X
15
# R, I& H+ g! T# ?+ d9 m16& F Y6 I3 k+ \9 a/ I6 V
8.3 文本处理的五类操作
- Y$ A6 M' L4 H* v3 g8.3.1 str.split 拆分
p$ _7 w8 K' P str.split 能够把字符串的列进行拆分,其中第一个参数为正则表达式,可选参数包括从左到右的最大拆分次数 n ,是否展开为多个列 expand 。1 J6 u2 d! F! e4 y& ]
3 h4 O% T0 {8 Gs = pd.Series(['上海市黄浦区方浜中路249号',
8 ?1 q1 V% y1 n0 a7 k7 B '上海市宝山区密山路5号'])5 V& t5 X( |4 o1 r& o8 Z3 y; z
9 n- \+ V0 s6 I. h6 `' v; S; j9 y
s.str.split('[市区路]') # 每条结果为一行,相当于Series
2 j! J7 Q( F/ nOut[43]: 3 P+ ?- J( C8 s5 t
0 [上海, 黄浦, 方浜中, 249号]
3 G1 Y9 O8 G- |1 X; M+ N; f# m1 [上海, 宝山, 密山, 5号]
3 i! t. ?, G P" o2 |, [dtype: object& i+ X B9 s9 z7 F& f& O
: \* N% E" q, c, X4 e% `s.str.split('[市区路]', n=2, expand=True) # 结果分成多个列展示,结果相当于DataFrame$ ~, X) B7 m! C( }
Out[44]:
6 ~8 W! H5 ~: l& }+ L 0 1 2
# e; U8 N5 D, \4 a0 上海 黄浦 方浜中路249号/ }4 K/ c3 J! I( D9 U f
1 上海 宝山 密山路5号, H4 W8 d+ }6 G+ P$ {1 M
1
E4 Q* q E, [& X21 T. M. x& d+ x& s
3& b) r. V' a2 g) S0 n# T
4& b, p' `5 d5 n- ]9 ^% S; H
5
. x* }+ ]1 R2 O; S9 ]64 R$ m* p/ Z& H1 _
7
. J2 X {) y8 V( h4 d# U0 I3 ^ t. u8" u1 v2 a- W$ C. k
9
4 P i" I/ v/ F" g9 W' C& V# z10
6 g3 I( z3 W! ?/ t3 [11
) ~! E% t# A* `# a+ l. J12 G. Z' J* D4 w6 g8 t
13
9 |# ]6 \0 O) V14
5 ]9 ]! l4 O9 Y% V15
+ O9 U5 Y! ]4 o% z) p 类似的函数是 str.rsplit ,其区别在于使用 n 参数的时候是从右到左限制最大拆分次数。但是当前版本下 rsplit 因为 bug 而无法使用正则表达式进行分割:6 C: z; z. [$ }" L8 `
' W' T9 B8 I l. x$ X o5 {6 I' {/ As.str.rsplit('[市区路]', n=2, expand=True)4 a3 o( w/ B2 ?
Out[45]:
: ~3 Z# i- c' Z S# j3 t3 X$ B 0
; A6 ?' n: v0 Z P% h/ Y6 y0 上海市黄浦区方浜中路249号
( b. E3 |1 p& w& ^1 上海市宝山区密山路5号& g r, ]% H$ G
1
, W Y y# l7 Z6 m. \# d2) H4 Z# w# h& W+ }5 Y
3# ^! T4 w; w. d- E4 x' u) G
4
: [! H) D; k/ g. ^4 O9 x: ]5
' I# g2 O# e0 g0 d/ a8.3.2 str.join 或 str.cat 合并
( ^% `; l( @- Z: A: O( a( L P% ^' Rstr.join 表示用某个连接符把 Series 中的字符串列表连接起来,如果列表中出现了非字符串元素则返回缺失值。- X5 [, P' t# z+ x3 S
str.cat 用于合并两个序列,主要参数为:
7 l* R& v& n) J0 ^# [3 p! |sep:连接符、# I9 O7 h, \/ k) }
join:连接形式默认为以索引为键的左连接% @# e/ d4 y( V; u6 T
na_rep:缺失值替代符号$ h8 p$ a/ @: l V" ~' E+ [
s = pd.Series([['a','b'], [1, 'a'], [['a', 'b'], 'c']])
. C& l9 p+ g/ c! j r$ T+ O4 A: ss.str.join('-')9 g' |) I( I6 B8 d4 |
Out[47]: * y$ r' I- |8 \0 I$ A
0 a-b4 K' ~2 j2 o' X7 e) M$ d% N8 b
1 NaN8 m6 F$ N/ E7 F
2 NaN. `+ J9 S. Q8 v- N4 D; D t- I: c" U$ C
dtype: object" y. y6 [) M* e( ]* G
1
! H- d% K8 D) v0 n2
' P& [ O: t* e0 _5 ^$ ^32 x2 u2 q% a" o j
42 K- D2 z9 D; q# I
5" Z" {8 R- N) o/ y- f/ |
6
: {; F8 L S+ t9 {7
( Y$ K! n" k/ x3 K* p9 ]* c! i( W# Bs1 = pd.Series(['a','b'])+ L; a0 o8 a0 P t
s2 = pd.Series(['cat','dog'])
1 j- a3 ^5 c9 P, u# Ps1.str.cat(s2,sep='-')/ n3 G) o7 ^" `! `
Out[50]:
, m4 _3 L% e( G+ p) f4 Q0 a-cat
: R j" x- z8 k- U0 I# e, h1 b-dog
3 a- V; f4 W& T$ k* U0 p, U% [dtype: object- x3 Z. a: a; ]; V& W/ @
( ~% |: o- z) K4 [9 }; N0 o
s2.index = [1, 2]- E6 p$ V3 l7 ~9 `9 r
s1.str.cat(s2, sep='-', na_rep='?', join='outer')" n8 ~: n: V W! g; U
Out[52]: ; r: _8 e. I8 t8 l6 x6 C; @
0 a-?4 m+ U _/ q& @& Q
1 b-cat
& H! {' O v& z1 h4 [$ ]2 ?-dog
: b; H' i, F- `4 F) h/ t+ ddtype: object! O( R' {/ I% P/ S4 c
1 d2 s0 ^. B3 t4 w' t4 |, M
22 x* H$ ^2 o4 a3 N" w0 N9 n4 f
36 s5 w3 X. @1 J& a
4
& S0 R3 t, h% E4 s4 h1 J1 i2 l55 ^( k) o) |+ p$ V
6
* O6 I% U/ ~3 v7 L% x U" L/ l7# s4 p1 F- T( O& d$ m5 ]3 c
8, V+ t; y3 J) I1 U5 u
9
( A! \5 j& @9 O7 c* N10( P: l& d* \! O. U' Y. _) ^" h
11
- I- {$ `1 q) s8 c2 h$ r12/ d4 ^ z# C* C) h1 [ n
13
) \9 p7 v. {/ A* F; S14- g s- A# g2 N5 B
15
1 p! M5 k1 M3 E$ \; D7 j- C) |8.3.3 匹配# G" w A/ H$ ?$ D% `
str.contains返回了每个字符串是否包含正则模式的布尔序列:" `4 T7 `3 k' g/ T p
s = pd.Series(['my cat', 'he is fat', 'railway station'])* @" F/ w9 K Y' _& ~2 ~
s.str.contains('\s\wat')* ^2 [, n" _% G9 C p2 @' z
- ~8 g5 O1 ^- m& \0 m" j b9 A2 C
0 True
( T' S4 q7 B% F1 True. A8 X3 {5 e0 A+ S7 Y$ W% v; U
2 False
% q* C. s7 h) w! t! Jdtype: bool
9 Y& H) X$ T. |( m1
( U7 X/ s2 ]& I x2
; X% y; b5 n1 \- D+ ~- o6 d8 ^- @4 h2 k38 N e d- n& I9 K
41 Z% n4 V: `( q! L
51 Y0 h+ f' z- ^6 n
6
( U7 D1 A9 e" }, E7 x( _& |7
1 x) ]$ ~: [% X' f' x1 F5 X% Rstr.startswith和str.endswith返回了每个字符串以给定模式为开始和结束的布尔序列,它们都不支持正则表达式:
! |, W* m0 U5 I5 N3 Z0 Ps.str.startswith('my')
6 _; S. N: V Y3 H/ V5 m
/ T% Y: k7 p( Q0 True) b G9 H6 G. ~# \( V+ ^
1 False* S3 _" \3 n) t/ |
2 False
8 w, m \1 Z2 e8 {* z' W; xdtype: bool% P; D9 l. H; d, p
18 u$ u% h2 d: n2 b/ N4 ?
2- w% K0 _) [7 q# H F2 g. `! [
39 v2 D: G% ]9 y7 _ Z$ @
4
o5 H4 F' S% E/ f; V- T: U6 S5* C, V+ [0 P1 j1 Z
61 |7 W0 B1 {* _
s.str.endswith('t')( k \! F5 r4 n4 {. X
' c) q5 ~( c k5 Y; c. P; S3 d5 U0 True
, b! l+ b4 [4 n2 d. V* N1 True3 ~+ M* k b- W0 e
2 False0 U+ z1 b! S s5 n- R2 v! A# z
dtype: bool/ r8 M7 k3 v! C; k: P& }
1# r5 U+ N/ `- n3 h/ [+ M: N
2
; h' R) Y$ n' W3
6 J. [. G8 B1 z3 |) k0 E/ M41 ^! B% f( |" X4 s' n1 o3 J
5
2 j2 G2 y3 K6 \7 R+ W& Z6' p1 K8 j( X9 k5 }; ]
str.match可以用正则表达式来检测开始或结束字符串的模式,其返回了每个字符串起始处是否符合给定正则模式的布尔序列。当然,这些也能通过在str.contains的正则中使用^和$来实现。(貌似没有python里的search方法)/ p: \8 t" v4 M& }1 S6 O& [( e! M
s.str.match('m|h')
7 M& s; h) L$ u; U$ `s.str.contains('^[m|h]') # 二者等价
. n+ @, I; n0 G# R% V
$ }9 {$ B/ {5 ^4 u. E0 True
) I6 C9 u& Z* P8 u+ A5 p1 True
, ~) P5 m- P: }2 ]6 E: Y) U0 U: |2 False! K7 T3 J) @( q0 h9 A, A4 r
dtype: bool
- B" x$ K! d% u$ s6 @' l+ H, [1
+ _5 ?, H% I$ @* x6 R% t26 R( D9 C" V" Z6 I
3
! ?2 n; p% _- @6 Z4
2 F+ Y' L U7 c5
5 g1 U! C6 H, W( S. {" Y5 I69 E6 P( A% \# S, J( ?7 ` m
7, s) f( ~3 Y" @. |& h3 |" z. G
s.str[::-1].str.match('ta[f|g]|n') # 反转后匹配5 \9 E5 |$ ~0 Q$ L+ S) o
s.str.contains('[f|g]at|n$') # 二者等价& G% F# T A: U
( J V6 C) {5 e0 o. K m6 `0 False
8 j9 I7 M( H g6 |, ~1 True
' R- ~9 |% b, B! m3 H2 True
! m l" r* N. v1 T+ Z' Bdtype: bool
* C1 c! ~! I j8 y5 _1
" N- |, D( e/ [$ X' i8 ] r2
+ u" y0 R" E0 L9 I7 H( w3
; X$ V- {+ }) ?' l% ]3 D5 o4- F! a$ Y6 h1 t& j6 w
5$ d- h9 z+ S( w; d/ V6 n
61 W) d1 h; K) P% F: @
7
2 V# a5 X6 C# R! f% `7 hstr.find与str.rfind返回索引的匹配函数,其分别返回从左到右和从右到左第一次匹配的位置的索引,未找到则返回-1。需要注意的是这两个函数不支持正则匹配,只能用于字符子串的匹配:
* W; C: n; X) t2 W' Y9 R* R; \ b& ws = pd.Series(['This is an apple. That is not an apple.'])5 O: J' U) T, x
* Z) v) Z6 F2 F2 \& B3 X
s.str.find('apple')/ _6 k* u7 V% Y) {3 m" P& F
Out[62]:
5 k; J7 b3 u5 b# [( w+ f/ P0 111 }& @8 }: Q1 f3 Y# f
dtype: int64
# i3 P: L" C$ B3 O2 o4 ~" c$ E
( c7 `* s$ x6 ~' {8 ^s.str.rfind('apple'), T1 A" K9 K/ e# T
Out[63]:
- T5 l! I" K" X$ S2 R9 V0 336 w9 w/ x: g7 Z+ a" @, D/ g
dtype: int64- k* r' G6 a7 K
1
7 J6 d7 X' F0 a" G: ?2
1 l1 Q! x$ V% v! N3 q5 V8 h% b/ C3
9 J' Y9 O4 p3 N. W4 d4
, O( a' D. B( T/ S6 W. i52 q4 M: |4 Y1 M/ q' y9 ~4 l, N1 |
6
& h$ o; _5 B3 P/ e8 s4 H7
% r" N# `& p, M' s, G5 _80 ?( L: m) K- U& E5 u3 k
9
% X! e v) r4 n% ?& e) d) L( Y10: M$ F( I2 k8 L% k% F2 ?! q* `/ L
11/ @, a! r. ]2 x6 F* H
替换 e* b7 w* p. C; [" E/ c7 d( @4 m" w
str.replace和replace并不是一个函数,在使用字符串替换时应当使用前者。/ ^# I, r, o% T! R) K2 m% n$ g
s = pd.Series(['a_1_b','c_?'])
2 y( |- _/ C8 P+ W7 \/ M# regex默认为True,表示是正则模式,否则第一个参数内容表示是单纯的字符串,也就是匹配字符串\d|\?
! J2 {9 z4 h N; w+ |! H3 s, Fs.str.replace('\d|\?', 'new', regex=True) ! V9 J w9 @- `- [/ |
) W/ w' t: D9 l
0 a_new_b" w! V+ H' T3 m6 E' b: G5 v
1 c_new- V* G$ ?! n$ M/ h! f! H& {. X+ Y4 b& E
dtype: object
2 b$ S! v, V" [% s4 p% M# K17 X( z* K s9 k/ @. Y) o
23 K' `& E0 ^3 a, ~& s- E$ ^5 C
3
2 F6 G. M U& S" s; Y( {1 X$ X4
! C$ C/ H) J* b; x/ t, `/ M$ b7 G5
" ~5 v- G/ z: ?: g6 Y" }, L# X, H6
F1 }1 K! [! A; x& x7
7 y" Y7 w! h& U* v 当需要对不同部分进行有差别的替换时,可以利用子组的方法,并且此时可以通过传入自定义的替换函数来分别进行处理,注意group(k)代表匹配到的第k个子组(圆括号之间的内容):' n4 J( X0 K2 { c) u" p
# m( v/ r9 f- Y
s = pd.Series(['上海市黄浦区方浜中路249号', E- W4 b, D$ u, Q+ p9 T# u# f
'上海市宝山区密山路5号',9 R5 ~4 B- \1 m, B& M$ E
'北京市昌平区北农路2号'])5 ?+ [4 ?3 n0 R* m+ O
pat = '(\w+市)(\w+区)(\w+路)(\d+号)' v9 g/ h/ q h8 d: k
city = {'上海市': 'Shanghai', '北京市': 'Beijing'}
& e& C& e+ _8 _, h. U6 W% hdistrict = {'昌平区': 'CP District',
# @0 k' y# g- v, R( v; A '黄浦区': 'HP District',2 w. d5 G5 K5 K1 g: O# Y9 f* A
'宝山区': 'BS District'}
& [4 R7 p& l, Q: U$ W) x, groad = {'方浜中路': 'Mid Fangbin Road',
0 l8 }& l M& ^$ V! b '密山路': 'Mishan Road',- O- m- B9 t% O3 A! h! c- |
'北农路': 'Beinong Road'}% ]+ o( x2 ^. q( r
def my_func(m):
. p# I7 Y6 e# k0 R5 f. S str_city = city[m.group(1)]
! f( o* {) r) v str_district = district[m.group(2)]2 Q8 D4 I8 B5 o. a
str_road = road[m.group(3)]: p7 N) E1 V, M: I
str_no = 'No. ' + m.group(4)[:-1]% e/ L! `. w2 [% g2 F# Z R
return ' '.join([str_city,) P/ G/ F, c- x% @% i+ Y) p8 n' f
str_district,6 J6 l$ e; Q' _0 E0 b
str_road,
0 m- A) N# _( E str_no])
& R9 V1 s* b% V4 V& Ys.str.replace(pat, my_func, regex=True)
2 A d! R1 J$ C+ n9 G
. H! R7 S& D$ @1 W9 M1! g( U1 D8 n# n v
2
0 E0 E' L3 X, s# N d$ A30 k5 W0 i) T, `+ p/ a" v, g) i9 k
4
: P- t h. a$ P1 S0 f) b4 B0 @5+ w" G2 X% k* m6 ^& e1 s
6
e+ ~- b6 q, q0 `7
3 C3 ^) k% r! F! u8
: k8 i$ L8 }4 O) u0 v- [9' [2 C* P% r Y' x7 U
10
1 M1 M6 n) [$ l3 q. F; P11
. p6 y7 X j% l121 G$ p$ a1 b# f6 K
13& ]- |* I4 ]/ ^- f* U
14" G5 n' D+ e. Z
15
. \% N4 h4 Q- t16
& L7 G+ {9 A0 o. V+ e5 d17# m" P% y+ Q( G9 N9 u
18
6 ~' Z4 n6 F* L. \0 W19& O& P0 I# I( c$ Z+ S
20
- R+ S. Z1 d5 i0 D# D) N n2 O- q5 Z21
4 u p5 L( B5 c1 J5 {& A" X0 Shanghai HP District Mid Fangbin Road No. 249
6 I' r* ~; z% W1 Shanghai BS District Mishan Road No. 50 q0 U" s+ \; c& k
2 Beijing CP District Beinong Road No. 2
* o: o3 x9 m/ \' g, [dtype: object+ ~# Q( D: Q4 Z5 P3 w
1% N6 k4 B. x, g/ }) ^" C
2
% y* z7 p' s( p+ s3
% z4 ?8 t% a$ c7 J1 F- s4! j( y% }5 Z* W& d7 l
这里的数字标识并不直观,可以使用命名子组更加清晰地写出子组代表的含义:, v2 i0 s+ D# r+ o& q$ X @5 s
5 ]) O; q# N9 L, D z
# 将各个子组进行命名: ?- m& o |% W; M8 J- Q. E
pat = '(?P<市名>\w+市)(?P<区名>\w+区)(?P<路名>\w+路)(?P<编号>\d+号)'( O2 h$ @& c; I0 b9 l' h. y
def my_func(m):. Q$ ^4 J7 B* e0 v
str_city = city[m.group('市名')]9 |8 T( D1 C* x
str_district = district[m.group('区名')]
$ F0 {+ ]+ X! k5 \# o3 y5 l str_road = road[m.group('路名')]
+ ?9 b- I2 B% ^5 _; _ str_no = 'No. ' + m.group('编号')[:-1]
; |6 S+ Y9 C: }6 n return ' '.join([str_city,
; z0 Y* X) a8 N0 ]: z str_district,
1 u+ a. T3 _- v) J; o; a str_road,
0 Z8 j9 p# O8 [& }% `4 ~) Q% I1 W str_no])
, {( v* F9 ?3 G- _: O1 Ls.str.replace(pat, my_func, regex=True): _) D+ {" V& ?6 c
14 F# _7 n1 \9 X* s% b. A+ J% C
28 P- P2 Y, a# D. `
3& E6 z& t, P9 ]8 n* d
4/ Q( t) Y1 b: [) _6 U; _
5+ o% m2 y6 Z9 Y1 l4 A) D
61 A4 Q. T4 Z/ V0 h
77 g) u- A) d; i: v& d2 O
8
" L8 T" w$ `& K2 g4 B3 F9+ k7 a1 Y/ Y, W7 l( w# a
10' F" y" y! C! z( g
11
' t8 Q" p, \% o5 |! h4 q$ T121 K# A) e( k/ B; a6 w
0 Shanghai HP District Mid Fangbin Road No. 2495 ^8 Q" d# G: j3 Z; [' H$ q0 D
1 Shanghai BS District Mishan Road No. 5
% T R9 R0 S' p/ n! I2 Beijing CP District Beinong Road No. 2
3 H: C1 L: l0 e8 ?+ ^1 _dtype: object& ?, t, m+ _7 M" y' _" m9 G: k: @
15 Y5 C: h1 C. @: W+ [; K5 Y
2! G! Y5 |! A( d$ g, Q
3# I9 s9 }) l- A4 s0 F- K2 R( Z! ~
4) M4 u, ~5 {! e/ C6 i; P+ k
这里虽然看起来有些繁杂,但是实际数据处理中对应的替换,一般都会通过代码来获取数据从而构造字典映射,在具体写法上会简洁的多。# [6 }! k1 @6 h9 B
4 z7 U% j8 e- Q% R) U
8.3.5 提取6 W. V% G; ~$ `" E, \. M
str.extract进行提取:提取既可以认为是一种返回具体元素值(而不是布尔值或元素对应的索引位置)的匹配操作,也可以认为是一种特殊的拆分操作。前面提到的str.split例子中会把分隔符去除,这并不是用户想要的效果,这时候就可以用str.extract进行提取:, w1 w% \2 D" B
s.str.split('[市区路]')
' O) b+ ]# Q; u- V% M4 U1 ~% I. dOut[43]:
9 @' h% S. w; @# W, l6 q0 [上海, 黄浦, 方浜中, 249号]9 ~: V" v/ g+ S0 [4 E
1 [上海, 宝山, 密山, 5号]" r& ^( h8 k: ~4 e. j7 E
dtype: object6 a# Z" k: ^$ ?3 V# T: x7 c
! ~% o7 C9 ]( b( mpat = '(\w+市)(\w+区)(\w+路)(\d+号)'0 _6 `- s- y2 _- y
s.str.extract(pat)
4 \! y1 A' p% F w" uOut[78]:
) R! @! X. Q; s+ C# r 0 1 2 3
7 H( t2 O' o+ J; p$ }) w0 上海市 黄浦区 方浜中路 249号4 h1 S( o' W" V' O- ^2 z' N
1 上海市 宝山区 密山路 5号
& B- T0 d& d7 B" r& C% c2 北京市 昌平区 北农路 2号
3 t1 c( K; W) C1+ d7 f8 M8 `$ D; i
2
6 @! l) |+ x. Y7 s- N( q3; q& M G5 g; l( @
4
% I" G3 u" ^7 v5
V+ ?1 m/ P. h; l! O6
: [/ {$ e! q" e' n, R9 @% Y3 Q6 u7
7 z2 b( h. U& R1 s& r8
& n$ o `# H* d& W# ^* ]# U' P9
" o# A7 j4 D1 V: _* v" f105 b) d! ]6 w3 z1 U+ u
11$ B r9 i9 k4 P& e( o2 h% z
12
% q5 b" L# e9 W: S) {) s9 H13! \1 v7 o$ F+ N# ^! R8 e9 Q0 U
通过子组的命名,可以直接对新生成DataFrame的列命名:# ]2 L- o8 [' d/ ^
4 H P T; t0 M i M" F
pat = '(?P<市名>\w+市)(?P<区名>\w+区)(?P<路名>\w+路)(?P<编号>\d+号)'
1 Y4 q$ d7 r5 ~0 l9 n$ As.str.extract(pat)6 j) u2 I, c/ a8 W+ }% X0 }
Out[79]: 2 V; ~" f+ p, V/ e( ?. [; g* Z7 ]
市名 区名 路名 编号
- }1 k5 q4 F. ?" v0 o- `0 上海市 黄浦区 方浜中路 249号( J0 L; B4 w" C& j: P! K) X* i" J
1 上海市 宝山区 密山路 5号
( P/ W$ s+ s: X* g4 x8 u2 北京市 昌平区 北农路 2号
9 _: k; ]6 u$ I1
7 w: c5 ]- a+ m. p- @2
) I9 a: S1 g5 [1 a) B7 |2 g3
# P1 P# F: |; b' j f* e4 y2 o' I6 p e: j" E
5
# M/ \( O6 D- N& d$ R6
a6 m( B7 \' W5 z8 e2 g/ \7
: I! Z K# H; q4 G( `: a. x6 {8 Qstr.extractall:不同于str.extract只匹配一次,它会把所有符合条件的模式全部匹配出来,如果存在多个结果,则以多级索引的方式存储:$ F- I1 X$ Y& r& j( {0 f. ?- v4 `
s = pd.Series(['A135T15,A26S5','B674S2,B25T6'], index = ['my_A','my_B'])/ n O8 A8 n n# k
pat = '[A|B](\d+)[T|S](\d+)'
2 q8 u0 v3 v9 hs.str.extractall(pat)& l, m- R; x F: M4 x
Out[83]:7 @' E& c4 w V: l5 b# ?
0 1' P s0 j* [& E, ?# e, C
match ; M9 H4 @* x+ \! l, v6 f/ H
my_A 0 135 15
/ Z% G* i, ^9 J3 |9 M0 \ 1 26 5
; G" O; U; E, }( z" Qmy_B 0 674 2
/ `2 _. p0 P- e& K$ [- u( x# Q 1 25 6# W' p* C( l/ i! O9 C, o
1' \4 v) ], `" @- J: O# y1 J
2
+ S7 S3 C) _# T& U# O# v3
) y. D+ n1 r8 N1 _4
, X( w: u: H) R4 A6 B; y& n5
! p+ Q9 M1 t# M6
) k$ ]" M# _1 z9 r. |. G0 G0 x7
3 @$ H: Z( u- C( j* E6 v85 I( j; y; H4 w+ T$ \; _& e
9; q' m3 K6 G+ _7 a
10
; j; U4 U4 J/ m* L' }; Npat_with_name = '[A|B](?P<name1>\d+)[T|S](?P<name2>\d+)'
( t* \, Z4 b& D9 Ys.str.extractall(pat_with_name)* r. G: S Z+ X' V0 Y
Out[84]: ) f+ N, C9 }8 O' y J( ?( C) t
name1 name2
/ k' k4 E" i& ^6 W% `0 G- s2 O match
! p' g3 H8 E) H# \7 Qmy_A 0 135 15
1 G% G5 @2 K2 N C 1 26 5
; T7 E) ^6 Q5 s3 ~/ Z, N- d' H Amy_B 0 674 2
% X. c7 U8 k+ V7 \2 A 1 25 6
- i5 u; t* ~ r13 m6 S; T( h6 O7 W5 p0 e
2
! `! w4 _6 j! w. f3
( E$ D. [/ {8 j0 G+ k W4
$ v; M4 [& { I3 e$ `. K% |57 W# e- n1 R1 O% ~% Z
6
8 A x* A. h9 R7% C6 Y8 ~* P5 i6 ?
8. u$ L, F. T: P' u, m1 P
9- b# O, j @& y( d
str.findall:功能类似于str.extractall,区别在于前者把结果存入列表中,而后者处理为多级索引,每个行只对应一组匹配,而不是把所有匹配组合构成列表。( f o0 X9 |% A& G) L3 r3 h) J: e
s.str.findall(pat)/ @7 @5 G8 \$ ^6 K. e% a1 v2 s
1
7 H6 w& }9 _" D) ]& [5 j. f8 emy_A [(135, 15), (26, 5)]
! z# G& c. M7 ^0 J' [. Y1 U3 mmy_B [(674, 2), (25, 6)]4 B! O; z8 z8 Q5 B
dtype: object
1 x" M6 x) S# X1
R$ L7 u d5 D( L1 Q2
+ S5 m# N5 i5 n( A3
! w8 p* x7 @5 M8 i) C0 Q8.4、常用字符串函数3 b' }7 B2 ^2 v$ p L! H6 D
除了上述介绍的五类字符串操作有关的函数之外,str对象上还定义了一些实用的其他方法,在此进行介绍。
) d5 k$ a# L5 i: n- w
9 x3 [ h* y/ N) r4 ^% r1 o8 m8.4.1 字母型函数
2 W6 A7 A: Q# R O5 J2 ^ upper, lower, title, capitalize, swapcase这五个函数主要用于字母的大小写转化,从下面的例子中就容易领会其功能:
9 T- p9 S- V1 x8 ~, p; w+ J& S8 j6 X5 h1 a8 M0 q6 m5 A1 ^
s = pd.Series(['lower', 'CAPITALS', 'this is a sentence', 'SwApCaSe'])3 h v! H& i! X6 ^6 s( Y* S
$ n2 G5 k5 H1 v1 w* @. P
s.str.upper()+ r/ F) r6 B6 M+ Q
Out[87]:
; D. i" X& T# m' Z- O6 P7 \' a0 LOWER. v7 y5 u4 w+ p, _: q1 o- q
1 CAPITALS4 W! q& {$ Y4 h* X, N4 e, {; b/ Q
2 THIS IS A SENTENCE/ J2 O! t! \$ n. `1 g/ N
3 SWAPCASE: d2 D) ^5 {' R( @6 m: m. @; X
dtype: object2 F8 o1 h% v+ g0 s
) m% k7 k6 a) v9 {
s.str.lower()5 ~. }; Z4 d2 k
Out[88]: + j/ j6 Z' d; @& u" ^
0 lower
5 j2 V3 @6 [' L8 G( U I1 capitals0 q) b( J# Z- c$ @/ L) j
2 this is a sentence/ u ?/ E0 x4 m5 I8 I) d
3 swapcase! j, l& P* }: A
dtype: object
+ I! @- j" A+ [6 Q8 h. L3 B' Z) L V" n" W1 P; t$ @
s.str.title() # 首字母大写
' ^( Q, d1 A7 {Out[89]: 1 @. l4 y% R) d# U, m
0 Lower
6 E$ |1 Y P3 ~) O/ U1 Capitals/ {, d4 G. D$ I5 P7 _& r% c
2 This Is A Sentence; M Q1 A) a( {! S, p/ \% P' T& M
3 Swapcase# r* s/ q7 O. B# O* n8 ^
dtype: object4 M! \- s0 ]9 N' m
5 T7 ~$ A$ f$ {* @s.str.capitalize() # 句首大写
' F$ n: x: Q% W! _3 tOut[90]: * F% Q4 p: ]- B/ Z
0 Lower
% I' I. ~) K/ U$ [1 ?1 Capitals8 Y# m h9 k# A) q
2 This is a sentence
' {7 z, Z2 D3 }; z- J2 F, I% L3 Swapcase' U$ f8 ]4 K0 \* x- w$ f
dtype: object2 Q: I# |9 v8 `9 m0 ?* f- |, A
4 H0 c" ~# `4 R9 y& {/ t2 j
s.str.swapcase() # 将大写转换为小写,将小写转换为大写。9 V$ D4 z. J4 D4 `& B' ?! f9 t& z
Out[91]:
# E. P0 |3 H0 Z* ?( R5 B* m6 B+ ]* P0 LOWER6 H2 v, p; s2 P5 z/ s) j
1 capitals
$ t. [4 q/ @1 J/ x& W0 f1 W9 V2 THIS IS A SENTENCE1 O3 o7 X* p) O, C/ q8 f
3 sWaPcAsE6 t( i/ X& U2 {" X( R
dtype: object
; `3 B4 f2 A; e# u6 ?2 U# R% D0 L* i- k7 @! X
s.str.casefold() # 去除字符串中所有大小写区别+ k+ ^: `+ J/ \$ ~- B
# L' Z [9 i1 |& h$ H0 U
0 lower; D4 O! [1 N" o: W2 g
1 capitals
) t0 e2 q& M9 A1 ~8 B0 N2 this is a sentence
& S) b) r: _- P: t3 swapcase
5 ~, J1 M+ a# d) }: s' R+ Q! q; h) Q) a5 s) z
1! M) ?- K ^6 J& a
2- @, n/ U+ G3 V( P$ k- Q' y2 ]
3
5 |% J! \$ e/ ~' E) s8 Q4 I4
6 ~6 |! e, P! Y T& y7 f) H5
( q, Z8 r" P* X; N" H, L! b' n6( T& _# z. g* W5 R: o4 Q- U
77 P3 `% s& K; m8 n* U6 [' d
8
4 ]% }5 O! c! Z0 `3 J u* c9+ U) S' g1 `7 _! {( z1 J
10* W# z' a+ l6 ]; K8 v
11
1 ?+ ?- p* e O) s" f1 W9 W12$ m- Q0 u! c" G3 e3 a
13
3 t2 ?0 s4 f) a; w R1 {7 m- _14
" [; h) l! c. k9 {5 A7 u3 @% E15
5 j& B% J. |& r: d- W: `- V7 Q& Y16
/ m( K4 l7 ~+ _170 Z( |6 U3 t' B: G
188 T) s6 s. q) @0 g; d+ r
19$ \6 D! y: a2 _9 F
20! L5 f9 n. o* m8 C$ I
21: ?- G. R8 G% [3 H# p8 x& t
22 N4 B" [ H5 B5 E! R" w% s2 Z
23
& l4 L0 @3 `! R$ q24& _0 Q6 v! X) s. J3 ?4 ^$ y, Q' S
25" R* `7 |/ P: F( T8 }. P5 `8 Y
26
- ~0 X+ B4 ^/ ?3 h" r7 W! ]279 Y0 d2 q! m2 D2 m
28
$ Y+ x& A5 A/ c( ^" j295 i2 o$ G/ }$ p
30% \# \ u! z4 k2 n8 U
31
0 A( I* Q) O* x D( h) W, K7 H# r7 v322 A, s1 ?! {. X( a5 z$ [7 m
33
' c2 g7 P6 ]8 [8 z34
3 c2 c1 I# }- z! O5 k" B5 }) }7 s35+ G% K- j! R2 M3 K* q
36- x( ^4 \+ @$ J( d5 k5 k) I0 |
37! q6 ^9 _/ V) {0 R! M# n# i2 H# d6 r
386 V( v- p* d0 o: ` \+ [
39+ V( E7 d" ?4 r7 L" ]& G
40& u4 e; D+ X8 y' I; T
41* i- f9 Y* V& `* [+ H
42
/ ] n; r6 h0 {4 M43" u/ W7 b2 w: w
44
5 O0 L: `3 Z' j2 k5 R45
- o6 V! f* Q+ U' E46: B9 {3 {7 E: I- f/ P) {6 G5 k$ t; y5 E
47
- {# j0 u2 f/ l% }4 P) u( T& C9 \48% Y" _9 F# }' A$ k8 u* T5 ?' P
8.4.2 数值型函数$ H; x( N& y5 g/ t: ?3 D- Y H
这里着重需要介绍的是pd.to_numeric方法,它虽然不是str对象上的方法,但是能够对字符格式的数值进行快速转换和筛选。其主要参数包括:. c5 k" Q! f/ `! G0 A
@3 T* Z7 p. ?, M" f
errors:非数值的处理模式。对于不能转换为数值的有三种errors选项:3 S6 [- X) g4 h. i, t
raise:直接报错,默认选项
* O+ K8 C: }1 |/ Z; ncoerce:设为缺失值- ?- t0 K, R* i2 Q$ w7 f
ignore:保持原来的字符串。% p" W4 z* ^4 P6 N: x5 Q3 L
downcast:转换类型,转成 ‘integer’, ‘signed’, ‘unsigned’, 或 ‘float’的最小dtype。比如可以转成float32就不会转成float64。. N2 s. g$ N# n- e+ N
s = pd.Series(['1', '2.2', '2e', '??', '-2.1', '0'])
% }! o4 G6 u& X) m4 H7 Q- `$ S8 D8 Q& T B( g
pd.to_numeric(s, errors='ignore')* v! A9 A4 m5 M1 d. a
Out[93]: ( ]( {/ ~, P+ p# M/ Y0 n' l
0 1. } z5 c! c* |. S5 ]
1 2.2
0 Y/ \4 f- M$ _% y2 Q2 2e$ O0 |/ t0 n/ N' x5 L, H
3 ??# L$ p% ]" a b9 ]$ ^
4 -2.16 ~) w6 z) X, `8 ]% R9 i
5 0
, t( A& q# F: P7 ] R+ xdtype: object$ B& H6 P3 n( [7 j7 `
) L; l# X- u- a, L0 Q+ Qpd.to_numeric(s, errors='coerce')
1 y9 @. `* E+ R& f& VOut[94]: # ]5 Y3 U) F Z
0 1.00 ?1 |; q" U% u! o# ~! k
1 2.2
$ e' ]7 R6 `0 X& y: r2 NaN
& E+ z2 b! ^3 u! C$ ^" |* u3 NaN
+ C2 O5 b* @* b) b4 -2.1) x' o7 l0 t5 e0 W
5 0.0
: Q; J* x, N8 cdtype: float64# n3 @! T X* w M& B! C6 \
! c$ w$ _1 @+ k% n4 r# y5 a
13 G- ^- Q. ~) s6 z; O
2
! q* S7 \* E" _5 ^3 T- V% X U1 H. j4 a# L* L
4
M( y4 N* P* W! p+ i7 ?1 m) c5( K' }3 C1 D# r- P. i8 Y
6
- \6 L1 `$ _8 D6 Z7 ]' b# _: \2 o7
0 k% I+ ?+ {2 P8
" a; d' k* `/ U9 D) X- ]$ E6 H94 e( b q h X9 r+ K. e$ K
10
: I& R) {0 v3 f3 Y$ w11
! d% ?( i7 f" Z- K% d125 I3 O; E0 `; Y1 v! z: p7 _
131 L0 Y1 s0 w5 f1 {/ e
14
. p- E& @' R: t# P' A7 A15% }0 V4 @3 H+ h; v5 k
16, e9 X9 d8 X4 ^+ M1 g: M8 }) T
17; A8 Y; e$ t. Y4 U/ Y& [
18
! W; }+ E2 Y% l Y6 _# e19! t$ j7 _8 B& ?& i( y
20( O" H/ x; } u9 o4 [. z7 V! d9 e. ^
21
/ l' g3 Z5 _' P/ l 在数据清洗时,可以利用coerce的设定,快速查看非数值型的行:
$ @7 ^2 {( p6 o6 ]( F% o; X. O: U+ H8 w8 T/ _
s[pd.to_numeric(s, errors='coerce').isna()]
" C. J9 T. ]5 NOut[95]: 9 F& ?! G# C# f) U; q$ X( J/ l
2 2e
4 f, f; q" b$ S. E8 ^3 ??( e6 ~3 Q( ~ b) ?% c) `3 I
dtype: object0 |) k: x. L. o5 @6 }! N) v
1
5 y; F' A5 y9 o h5 E2
z1 k7 u" F( k# g# ?, V' r3 [2 `" a3" `; z+ [# c A7 {8 I" k
4
: W/ z+ ^: Y2 O5
" M f1 Q$ ^( F8.4.3 统计型函数+ `4 M5 k8 M2 \
count和len的作用分别是返回出现正则模式的次数和字符串的长度:$ ]. L- a: Y& \/ U! u: S5 x3 ~! }- s
/ c" ^1 l4 V0 `. J/ L# ^1 w
s = pd.Series(['cat rat fat at', 'get feed sheet heat']): F# Y/ Q: _+ T: e; ]1 L
% q+ c* D5 O; t* V5 ]2 p) P2 os.str.count('[r|f]at|ee') # |左右两种子串都匹配了两次$ f2 _( F- n3 f7 D7 J, H
Out[97]:
6 K# s1 z! S9 L& I0 g0 24 m4 P2 {8 c& ^+ `# ~2 N; m+ K! x! E8 F+ ?
1 25 W) x5 m6 c5 T S& k- ^! y# c
dtype: int645 W& X- g5 B" X8 X* N' `
+ k$ s9 w! K n, \
s.str.len()) a/ h' Y: K" k7 p* g: o
Out[98]: $ i" L( q. L j! O) \
0 14
: |2 e2 [* T+ j: c1 Y1 19
. F! |- g1 e; y5 B1 P8 ldtype: int647 ]1 c' k2 ]4 V
1
% T+ r$ X5 J* F* {% d5 K29 z, X$ L% b! t. ]$ I, |- z7 E
3
" G" \) x- W1 R3 J% R1 o8 Q# m3 N) k46 ] d: g8 }, g% k6 k+ L: R
5
$ p/ K+ v, B: y5 v/ v% z0 L6: D& M) W# C4 N% l5 F
72 ~ ]; Q2 C3 e- d
80 |% @5 R7 z1 n( i0 S) s7 ^, t
92 v& K5 ]) |3 [0 H
10
) s5 f2 V( Z# R$ V11
- I* q7 `1 N) Z a. a- E6 C) h3 ?12
" d& |7 [" W$ K* }! F( K! a7 Z0 _" J13
- i8 S6 q C. F& \( ~8.4.4 格式型函数
+ w5 Y% _1 e+ a) b 格式型函数主要分为两类,第一种是除空型,第二种是填充型。其中,第一类函数一共有三种,它们分别是strip, rstrip, lstrip,分别代表去除两侧空格、右侧空格和左侧空格。这些函数在数据清洗时是有用的,特别是列名含有非法空格的时候。
4 ?: [4 e7 V3 H9 e. g4 R' z3 J$ F/ T
0 @ a# \+ E6 [* S' y4 V7 }# mmy_index = pd.Index([' col1', 'col2 ', ' col3 '])
$ E4 o* A' [. ]: B1 L9 e9 d3 C( S) }7 G- ?
my_index.str.strip().str.len()
! A+ U7 s! n" L2 h7 ?Out[100]: Int64Index([4, 4, 4], dtype='int64'): @: f2 L% d7 b0 U1 P3 ?
7 r- j1 L* b9 p6 J8 B- I
my_index.str.rstrip().str.len()
4 h# |2 K2 J6 u ^2 M2 `$ COut[101]: Int64Index([5, 4, 5], dtype='int64')
2 ^* O. l9 u( O6 |, [ [' N
# V, P' F3 E+ L! e5 A- Rmy_index.str.lstrip().str.len()3 Y. g9 Z. X$ y, D
Out[102]: Int64Index([4, 5, 5], dtype='int64')
1 O: @* R4 u$ m8 `& q7 c1
+ ?1 F. e$ [# M9 G1 g2
, t3 r+ _; ?0 m" a; f8 p4 F o/ I3# u' n! v, g) f7 R. i
4
- f P1 k/ C; l% f1 o7 n50 I1 y7 j" E; S, }5 w
6+ J5 \. H2 h1 z/ i
7
/ ~ R5 ?8 }+ k) W- w8 }, v82 y' Y; g m4 r& ^5 g
9
; K1 f6 m+ Y* Y3 D! h/ \- q$ b% C10) O0 M/ j" Z ?$ D0 X6 M1 s u* J6 S
对于填充型函数而言,pad是最灵活的,它可以选定字符串长度、填充的方向和填充内容:. E4 F( e( K7 @8 ?
3 i' k1 Q) v7 G! as = pd.Series(['a','b','c']): R5 I& [$ C% k: d5 B* P1 v9 X! B
& V- b0 c# m6 O* Z7 T9 G6 ds.str.pad(5,'left','*') _ f* S! n% x
Out[104]: 1 z+ \' R3 R: V! r4 H3 x/ |7 X+ Y
0 ****a
& Z, x# F8 a$ j1 b! A& U1 ****b
9 Q1 i1 L9 u- Y9 E2 ****c
# y5 i( J" w, O" }2 Bdtype: object
, [/ |! U% Q! S$ t
/ }& Q. I: a$ Y x2 e0 Q/ Is.str.pad(5,'right','*')
4 R# r2 B* H2 @& X) _0 S# ]' BOut[105]: . {' {3 Y% ?! O4 C% f& C2 S
0 a****' J0 O5 M( u# m8 o( a
1 b****
' J8 R$ R( ~- D+ k2 c****
: z; m, Z1 I& E' ]) V1 y2 M) Edtype: object4 a1 I0 y, G! v3 i. g* G+ h
j+ T0 Y5 R) ]+ e3 O; X1 p ~1 Ns.str.pad(5,'both','*')' s, }6 J2 ^; I" V* ^
Out[106]:
, w z( k$ ?/ u( c% I( O, ?0 **a**
0 Z0 T8 ?5 j4 a' ~3 n1 **b**
3 ?* j& s. ?! V% Y9 j2 **c**
6 i5 \, D( R' ~5 q8 j! x" k" Q1 Gdtype: object
# p% R7 a" P: y
* _' k, b& y! C: A+ d* s5 o1& o1 _- b% n4 F: m0 B
28 y) C9 |) D0 @4 b5 ^
3- f, \6 j/ I2 G2 }0 u; ~
4
' R8 v6 r9 f3 Y+ `5
% o( ` O/ e* y$ b* G7 p% d4 J6
+ O0 ]4 S- r! d* [. v) t79 d: |* w' d q
8/ D) D+ _" n; d0 A
9
, W- `$ q/ y6 W* }9 b1 j' b( V10% ]8 H% \& t1 D; W7 a9 a
11
: q2 d9 ~9 r& I; i" A; ?5 ?* p12; m! z- J8 f7 G" M
13
+ g0 a6 g6 ]7 i- u) s14
5 ^8 Y4 M$ D( F( T6 r }# L15
: [0 {* ~8 v6 v6 O16
+ o; T0 I# B% [$ d% b8 g$ Q17/ ~: j& t, ^- T( M
18- W7 k) k" @5 h, U
19
$ }8 A9 ~/ f0 i' C8 z9 ~2 `20
% Y1 G, W' P8 h21: U6 D3 L) @- f) }* S
222 q( T. ?$ P1 L( r& ]
上述的三种情况可以分别用rjust, ljust, center来等效完成,需要注意ljust是指右侧填充而不是左侧填充:
' d6 H* V t* e( U
3 h' M% O* I1 Q, _, r7 J; ss.str.rjust(5, '*')2 [, y; b+ N" u# o
Out[107]:
1 c1 d# c6 M, g n% W N/ T0 ****a
" \8 U8 l3 ]6 }# A2 t1 ****b
: \4 y- M k6 C; w2 ****c6 _! a* w. S: D; _; E! _: K+ J2 e
dtype: object
2 W! F+ T- {2 X U1 ?0 s w& l1 g9 \% t# l" s) d/ k# U
s.str.ljust(5, '*')
, g: Y" y% k/ M& y4 ]Out[108]:
8 s. | ~# G. T6 z9 b, ?* {8 F0 w0 a****
9 {/ }" p, X; e/ }4 [1 b****
7 j7 j w9 k0 r# a2 c****
) t+ T+ I" W7 f2 tdtype: object
8 ^4 s$ a" ~& @2 n6 L% m, o. |! h( m6 V! Q3 s3 Z
s.str.center(5, '*')
/ i# X' G2 v1 n% \/ X6 Y: xOut[109]: / h# H: w5 R, t m
0 **a**
% F9 `# _% `1 z4 @# n! b3 J1 **b**# O( ^ e1 @" h+ O- M
2 **c**
4 I+ o& W/ F1 t6 V: gdtype: object4 s; A* ^$ P% j N8 I R
$ C6 w& A, j, [9 O" x
1
2 ], c2 M% G# i% S' U2/ x% g7 e' d5 P( p% P8 p/ h
37 z6 e, w# Z3 | }1 Q
4
, [. p$ I1 z5 D- z5
6 }# C; l, W+ ^" ~7 W6% P }" T) C# P1 d
7( F1 y" A. J# P- q1 H
8% T7 R2 w4 z w4 R2 L' M; h. J: M2 J4 f
9
! d1 q; a4 u' c Y10
# r" i* g7 z$ b: E# w11
: ~8 c% i7 y. H12
3 I& ~* j) O4 F5 ~. ~13( h% W, Y' R0 V7 ]4 W
148 G: ?3 I; R; W
15
$ f c+ p- U! x2 Q8 q' ~9 i7 `161 S4 E# L: @/ ]' i
175 `7 }3 M! V- |4 Q V
18( P ^; M8 C/ g, n% q2 h
19. _6 \2 ~ D. c3 i4 z
20
4 @# D3 v( D1 f5 | 在读取excel文件时,经常会出现数字前补0的需求,例如证券代码读入的时候会把"000007"作为数值7来处理,pandas中除了可以使用上面的左侧填充函数进行操作之外,还可用zfill来实现。) f* v( x( i4 u: s) j5 Z5 G
7 P) U1 k* o1 [4 vs = pd.Series([7, 155, 303000]).astype('string')
# C7 F# m7 R1 D0 ^: E9 `9 q. R
/ N$ p0 m8 W! ~s.str.pad(6,'left','0')$ v, @0 h8 s, y+ n* M
Out[111]: 4 Y- }$ p+ f, L4 R
0 000007
$ u- Q: s2 _0 f4 p& K7 V7 `1 000155
9 {- y& G( F! c! Z R2 303000
: |8 _- Y' h9 R5 S( Gdtype: string
3 {# X: G- [( {! I. x
, {2 N$ K3 W+ y* Y& H3 b% ?5 N8 ?+ ws.str.rjust(6,'0'); [1 \+ J4 n) ~: \' v
Out[112]:
: `4 l; d0 E) Y* t3 X$ P, H) \0 000007
$ `) ?3 H* Q, W1 000155
8 k/ R3 W" F: ?- U# Y' D3 f2 303000
" v& u& [* K. Z/ L6 R/ w. n! \dtype: string
: X) ]' ~% r6 ~7 y, W T4 a; E! T
* j N' {# i1 Ss.str.zfill(6)
# ^! }% l2 N9 t! g( LOut[113]: 4 \* o2 O- M" r/ p
0 000007+ L# R( G6 ?) V% c& w$ U% X
1 000155+ V1 d6 S1 Y J/ `- ]( {
2 3030000 q# k8 t/ R$ Z) S& R; E2 r
dtype: string# c1 M3 Z1 Z' r3 k/ y" m
! ~" M# Q& l U+ f0 m; C9 C/ i19 f2 o2 O6 k( _4 k
2
0 O6 K! y3 N, N0 j6 ]! o3
6 n$ V$ T/ b+ f4
5 u5 u j* o, q4 ~0 X57 w8 l+ L. d3 P. a
6# y+ H, E2 ?& c0 N
7
& k/ k6 Q4 \- x0 t$ l! l8! `& }7 A8 r: B0 l) x1 {- U0 z
98 }: }8 C2 \( A/ R
10& E/ g4 [ e. m# H' |
11
6 f8 r# ^2 z @2 r9 X; L: v" t2 g121 ]: o; a. t. \) Y
13
: n9 } S5 P6 u2 f14
7 [. {; {+ ~% Z% m' ^) ^# I' h15. c) ?( u5 T0 P6 v; W- S; \8 y* |
16
o N. y; Z+ M) R: e17
: g6 \7 a+ P4 t3 A18
! ]! E/ E* L( q8 R19
1 Z4 s! w* x: [. q, D5 v208 N* j# h: r" S$ {
21
7 j3 n% k- O3 [5 e& I( i; u22
3 B A) u% U9 Y$ g8.5 练习
! _. i. L) M& y& y3 p) [$ X# O% C% FEx1:房屋信息数据集) o: l" u& [+ F' Y
现有一份房屋信息数据集如下:5 j& R8 B. J4 o: B2 Z8 l p/ v
2 c S5 L2 d, F' T
df = pd.read_excel('../data/house_info.xls', usecols=['floor','year','area','price'])* s$ T* x; L- w' J
df.head(3)
9 f! v- n; \3 q/ vOut[115]: " ]1 ?# z2 P! [% |% v3 Q" S5 Q
floor year area price
* \& q! G& U/ J( E% z8 i+ b0 高层(共6层) 1986年建 58.23㎡ 155万
l5 o- e9 L: H: s1 C# ^7 m1 中层(共20层) 2020年建 88㎡ 155万2 `8 A; f: Q: }' T4 j
2 低层(共28层) 2010年建 89.33㎡ 365万
+ I% o+ V Z' o" S/ b% y1
" n/ j8 L( {& E. t2
- u% Y7 T% |, |" U) h$ S3
4 x2 D4 E1 d4 ]4
2 b$ C. \. \ U0 Q2 M3 \' u8 L55 ?# C2 b. L/ O& L) B0 k1 v
6
+ P' c* o c2 C# J2 A! M72 U, E! P; z- a2 E
将year列改为整数年份存储。: Z7 h F" g) j* k
将floor列替换为Level, Highest两列,其中的元素分别为string类型的层类别(高层、中层、低层)与整数类型的最高层数。! e) @) [. }/ Z# O) q
计算房屋每平米的均价avg_price,以***元/平米的格式存储到表中,其中***为整数
. ]3 C0 T6 S2 y0 c将year列改为整数年份存储。
3 w3 w* f" Q. P/ @- s* K"""# K. q0 b) `' o0 s
整个序列需要先转成Nullable类型的String类型,取出年份,再将年份转为Int64类型。
! z6 P, ~* H% A- k9 s注意,转换的类型是Int64不是int,否则报错。即使astype加参数errors='ignore'跳过缺失值,
+ g/ c/ K8 Q/ a- P- u7 d- U) `% W转成int后,序列还有缺失值所以,还是变成了object。
6 ?6 n* B( O- e; j e' p# x. c6 F$ e而整个序列转为Int,就还是Int类型,缺失值变成了 pd.NA 。
" d# r. C. B6 y9 E* {# Z1 v4 X2 C""" ~* b, E) r( g: v" M
df = df.convert_dtypes()7 P- l1 W$ F) ~# Q
df['year']=df['year'].str.replace('\D','',regex=True).astype('Int64')% T4 E, n, u3 {& R% N
df.loc[df.year.notna()]['year'].head()
0 H; I' I3 g) h6 z# L
6 p, {( Q: p( {# i# H. ^, @4 [5 ^/ J/ T0 1986 k% }# Y# _) X) n
1 2020
$ x. s% q/ k5 w& {: ]! {2 2010. K% V- l4 B' K. l
3 2014. ?; l* H- Z W( U: `
4 20159 U" f! S8 F D
Name: year, Length: 12850, dtype: Int64' \* t' j z$ A& t7 F$ C
! n" R+ q* _2 A+ L$ I
1
7 G) w; w; o* Z1 v7 Q% d2
- z! a- _1 r9 S33 l7 w: @8 K2 R* h
43 c9 Z2 t7 l: W' c/ a& s0 U
5
8 N. @. k) I7 n# U; ^9 ^6
* R# L: C% j4 P4 u5 {7
0 J) b5 w% b4 ]: H( G# G* @8
+ p/ b+ L$ Y% v) {# `# Z" ]9
' `" @( b* l& T4 n* Z109 t* X+ c o: ~/ g* L
11' V. K3 ^) i9 q6 C6 _; B0 _( h; l2 q
12- w8 [. [; X! _* ^
13- |4 m& P, K7 m. ^; i, S& e
14
: [8 c1 @& S: r' X/ R8 X( N1 l15
1 @: L) M, g* Z0 {- i165 P1 e* p2 O1 c0 M9 G
参考答案:+ k% {! j' ~5 e: k" \/ b
- z' N. M) X4 G. p6 L
不知道为啥pd.to_numeric(df.year.str[:-2],downcast="integer")类型为float32,不应该是整型么
0 @0 J+ m8 j. i( u' S
^! J3 w R' f# B% s9 Udf.year = pd.to_numeric(df.year.str[:-2]).astype('Int64')
/ s3 x& s: L& A* g, [& Wdf.loc[df.year.notna()]['year']: ?* b$ D5 h- B) n8 q2 x
1. u- S% k1 W7 X8 `
29 g1 Y# L/ ~+ b) u# x0 R" V
将floor列替换为Level, Highest两列,其中的元素分别为string类型的层类别(高层、中层、低层)与整数类型的最高层数。
5 @& {9 V# N2 k5 y8 J" G8 fpat = '(?P<Level>\w+层)(?P<Highest>\(\w+层)'
1 p* f' z# a3 m- q* }df2=df['floor'].str.extract(pat) # 拆分成两列,第二列还是(共6层得形式,所以还的替换一次/ s% q0 a1 k. Y( y0 ]9 P0 c/ W
df=pd.concat([df,df2],axis=1).convert_dtypes() # 新增列拼接在后面,再次转为Nullable类型
+ U9 J0 q. V; e1 X, K9 Udf['Highest']=df['Highest'].str.replace('\D+','',regex=True).astype('Int64')
. v% h7 x: X5 B. O- f$ sdf=df[['Level','Highest','year','area','price']], J& O* I/ e% Z: ~# n0 O; F
df.head()1 t" a3 x$ d" M5 W- z3 ]' v: c
, V8 V4 P* O5 z) ]
Level Highest year area price
+ A' v/ d$ `/ c0 高层 6 1986 58.23㎡ 155万0 u8 u( P4 F8 g9 g2 |+ i
1 中层 20 2020 88㎡ 155万" e. Y7 p C* m
2 低层 28 2010 89.33㎡ 365万6 D4 ]6 J8 W2 ^' \
3 低层 20 2014 82㎡ 308万, h; k6 H3 V4 W7 m4 E+ T7 Z- j
4 高层 1 2015 98㎡ 117万
3 [7 }" q2 [) Y1
7 c/ @0 f2 W7 |- G) }# K$ V2
: }, x# ^& [# S5 P8 W3
& Z- |0 A; \: A8 H4
+ y: Q. f: i6 }; h: e% T5
* c$ ^6 A* g0 _5 f: d6
9 t6 Q0 D: w7 f& B2 X- O& s0 o7
3 z* b7 | E6 u8 c8
5 c; F) F: S1 l" I1 |: z' l9
+ a2 |$ R! B" o9 _% I# e10
6 m$ o& z) z. @6 U+ s5 M11% i6 _& y5 }" c4 t% n' {/ D
12
! Y) c! ]4 ?" q/ j. z13: D) b: h2 a8 ?9 y5 |' \
# 参考答案。感觉是第二个字段加了中文的()可以准备匹配出数字,但是不好直接命令子组了5 p( d! e/ @ G0 B
pat = '(\w层)(共(\d+)层)'4 [& E) h) S9 g# l9 [$ I- t
new_cols = df.floor.str.extract(pat).rename(" x% l) b( S7 Z0 y
columns={0:'Level', 1:'Highest'})! |* }+ u( e0 i8 s* P
' V' G" z- @$ D$ A# i
df = pd.concat([df.drop(columns=['floor']), new_cols], 1)
5 o3 R; R( q) }8 E6 G/ k! Z; mdf.head(3)
* I m% G& V/ ], v( P) T
0 h1 K$ Q) u3 u( [3 b4 MOut[163]: 3 W9 t- X5 F0 S5 H7 `
year area price Level Highest
( n7 F0 X! X; P7 s( `9 e+ k0 1986 58.23㎡ 155万 高层 6
, R: Y4 }' b% c0 ^, J1 2020 88㎡ 155万 中层 20 c! r: ~1 f" N/ Q F* h
2 2010 89.33㎡ 365万 低层 28+ F3 A! H0 C* N4 L# |6 R
17 x! h: S; V/ K* D4 L2 E2 I
2
8 c+ x3 m) a) b& t* i3
# f' {) B0 N: Q( A# S4" W. w" @/ }( N6 n6 ~
5- F' r, P! q4 a& I
6
6 ~2 v7 s# B; F" e74 {4 ^; g$ [- F* S/ z0 a1 c- y
8+ m" r# A% Y, w- ]% W) R6 a3 u6 G
9
+ d# t3 L4 z9 Q4 |9 _% q10) p( S. c8 }. E, v( B
11/ M8 h" Z0 F/ d5 t8 g; J
123 G% E( c. e$ R1 f
13
4 R0 i' y- j- T) B+ N计算房屋每平米的均价avg_price,以***元/平米的格式存储到表中,其中***为整数。, g, B0 w2 h4 z' k' t
"""3 t% q# X2 E2 k1 S" L1 {
str.findall返回的结果都是列表,只能用apply取值去掉列表形式
3 \& W4 w+ J% Q0 [8 W参考答案用pd.to_numeric(df.area.str[:-1])更简洁: v5 C5 c; A" `7 D! f
由于area和price都没有缺失值,所以可以直接转类型- ]' @+ N' ~7 i9 b/ O
"""
% R" e2 s; R+ p0 Y3 L1 h( m- Gdf['new_area']=df['area'].str.findall(r'\d+.\d+|\d+').apply(lambda x:float(x[0]))
. n4 ~+ ]3 V0 }% o: X- l$ tdf['new_price']=df['price'].str.replace('\D+','',regex=True).astype('int64')& {! I* } `* C
df.eval('avg_price=10000*new_price/new_area',inplace=True)9 A3 x0 Y4 b; s* A! u& Z/ Z. G# k% }
# 最后均价这一列小数转整型直接用.astype('int')就行,我还准备.apply(lambda x:int(round(x,0)))& }# B- s" c8 j/ S
# 最后数字+元/平米写法更简单' x; U) a4 d$ w- {. O0 M* \" J
df['avg_price']=df['avg_price'].astype('int').astype('string')+'元/平米'! T, O0 _, A" @0 `0 N# Y
del df['new_area'],df['new_price']
4 @' f& a% Y7 H0 Y. [9 wdf.head()& o- ~* e! p0 r! q9 m/ |8 N0 h
1 H4 J& G) F; ?" i
Level Highest year area price avg_price/ X+ }3 S' F6 b5 T2 P* [
0 高层 6 1986 58.23㎡ 155万 26618元/平米- Z. r% h+ h& A5 \, g
1 中层 20 2020 88㎡ 155万 17613元/平米
7 Q0 X$ l' O/ |, Y2 低层 28 2010 89.33㎡ 365万 40859元/平米9 r i5 |+ t; X j( O
3 低层 20 2014 82㎡ 308万 37560元/平米! j A, V/ o) B
4 高层 1 2015 98㎡ 117万 11938元/平米0 X: B1 v: N3 w2 \# \- x
9 p8 m' [) }) F; w- |9 q
13 P) \/ K* N0 U# X
2
0 m G# s2 r9 H9 T9 e35 n- F1 [0 c) i
4
/ G5 D" Q! u: x# ~5! O& V! Z: s1 l% s' e
65 R, S3 G4 m0 D" ^1 ~8 t& d/ r a$ b- g
7
2 c; _6 S, q5 X( ?6 o+ h0 U82 Y }; }5 P8 T1 M
9" i: y1 q$ k% v' \. [
10 y+ Y& f8 L, r# |4 ~/ E
114 w0 Q, X1 h4 h+ b9 x4 y: W
12! B; G X- R* e2 C/ E, o4 H9 i6 s
13
: K" P/ v" a3 @! w6 f14
+ j% t X0 \0 ^) S! K9 `0 }% M; X* M15
/ c' w. s7 o# K) Q161 t2 D6 L" w- L. H7 F! R/ ~
175 F, W) _5 `' d# @4 @7 ?4 R
18% z7 L3 X, Y5 N$ p0 y2 ?
19
5 k& ?9 z' C }% a' ?/ m; Q20% ?4 |; F, D1 {$ ?
# 参考答案
* I6 x# |7 M Y/ K5 W) J" v2 Cs_area = pd.to_numeric(df.area.str[:-1])
9 D6 D8 q6 R2 e* q3 Is_price = pd.to_numeric(df.price.str[:-1])
k2 @$ a" W. F' E; }8 xdf['avg_price'] = ((s_price/s_area)*10000).astype(
8 w& }: X) g! }5 V1 @0 z" E2 f 'int').astype('string') + '元/平米'6 X, ^9 n8 \! H. w" X
( `' Y+ \ u: Z! j' y
df.head(3)3 i( C8 G7 u0 P8 u
Out[167]: % n0 b! S' y7 E2 N! C' A2 C
year area price Level Highest avg_price9 J: y+ ]- X2 |8 B7 i; Y: x
0 1986 58.23㎡ 155万 高层 6 26618元/平米
9 S0 Y6 t& J' Q* u: ?* _4 |1 2020 88㎡ 155万 中层 20 17613元/平米
4 E; s: z# c4 l0 Y W: f2 2010 89.33㎡ 365万 低层 28 40859元/平米7 M5 t1 y" N$ b3 ~. ]
1
$ V+ e8 j# Z' B0 N& O, @2, M) L/ `5 o; D7 d0 M
36 W& M5 B' G5 T- z% t
42 {; B$ k- U8 `1 f. P
5& y S+ s, K; ?& l }
61 S# d3 x/ t4 K5 N& s
73 x# e" e* @2 M1 S! X1 r" M# A
8
- y! Z$ ^5 @4 N- y* i+ Q9' R" O( ^* a+ K0 I# `3 ?
10, F0 R* ]8 k% _
11
V% z: v6 F: S/ c5 q! F! B12( I9 T0 X$ _% N. { q+ T
Ex2:《权力的游戏》剧本数据集& @- k1 ?7 T4 D% E& l
现有一份权力的游戏剧本数据集如下:
) G) D m7 E( Y5 D, ?! O. Y9 H0 ]
df = pd.read_csv('../data/script.csv')1 l8 f) P$ F9 `* i/ j. ~2 ]
df.head(3)
% r$ e. N# L) m# f& F" ?1 C X# C$ ~
' {2 w" C' t4 J8 \9 K% k5 g. uOut[115]: - h& \ J+ a) ?- f2 J# j6 V
Out[117]: , X' P& b6 {; w; t# w& K
Release Date Season Episode Episode Title Name Sentence
' O0 B, K+ p7 n0 2011-04-17 Season 1 Episode 1 Winter is Coming waymar royce What do you expect? They're savages. One lot s...
7 \) R7 z% z+ c# O1 2011-04-17 Season 1 Episode 1 Winter is Coming will I've never seen wildlings do a thing like this...# F* n& C! K ?: F" H
2 2011-04-17 Season 1 Episode 1 Winter is Coming waymar royce # a1 B1 i4 \, P. p" J& J7 x
18 w. O/ G+ ]; M, @2 R3 A
2
- Q" P. b$ V9 Q2 I w3' p4 y) F/ n1 c) h2 D' L5 \! H
4+ Z4 X% @5 G/ s5 l
5. ^$ _* U. \* P0 r; Q6 G
6
8 K7 [( D2 w1 [$ J$ s" ~70 J% R& r5 l( J/ L1 B, s, |
8
1 d z# e+ O+ T+ t& f* p: a9' N) {! U# Z6 i x. ?5 q
计算每一个Episode的台词条数。& n' x1 }1 Y% Z' }5 r7 f
以空格为单词的分割符号,请求出单句台词平均单词量最多的前五个人。 I, k( G) \$ k5 c$ f S9 ~
若某人的台词中含有问号,那么下一个说台词的人即为回答者。若上一人台词中含有 𝑛 个问号,则认为回答者回答了 𝑛 个问题,请求出回答最多问题的前五个人。& \5 x$ K) i( q! v( b$ m! m
计算每一个Episode的台词条数。
3 m [2 N' v/ c: T/ s. gdf.columns =df.columns.str.strip() # 列名中有空格
7 R5 W# n4 {" S" t: idf.groupby(['Season','Episode'])['Sentence'].count().sort_values(ascending=False).head()7 l, s$ y5 g+ x; s2 e
7 E2 n: [ h( _: b$ X9 Yseason Episode 3 O: k; l% m4 } L2 f8 k, M3 d
Season 7 Episode 5 505
8 `+ _) t0 [* m% b9 L8 _Season 3 Episode 2 480
9 K0 a$ E2 L3 l- P; |' ASeason 4 Episode 1 4752 G6 m0 h% O/ X9 P( m% s2 |
Season 3 Episode 5 440( ~7 \* M S! Q' W
Season 2 Episode 2 432
9 T: Y+ S9 A0 f) t0 K) f19 G% q* r8 E, L# y, v; M( y
2
( R. {2 {4 C# s. B% ~* j' y3$ r+ v% q4 l, ~: P T! S: ?
4# R. d3 q' S9 [) Y
53 [; b4 Z6 Q' I" _. W
6
" U z l$ ]2 L: u7- a. S0 _' T. u+ c$ ?1 I7 j7 z2 o6 I
8
# \ t0 e" V2 ] f" A# r9% d" p+ D; Q U
以空格为单词的分割符号,请求出单句台词平均单词量最多的前五个人。
+ o0 @: y; T0 M% u. ^# str.count是可以计算每个字符串被正则匹配了多少次,+1就是单词数% \! M t" N% N8 `; T8 w. z# r
df['len_words']=df['Sentence'].str.count(r' ')+1
8 P, R' d0 \; s$ S/ A% y8 q- vdf.groupby(['Name'])['len_words'].mean().sort_values(ascending=False).head()* r4 L2 j F% q4 O6 W- c9 z
4 e7 l6 P- j0 s8 k3 jName
. d9 w9 e- D$ l0 Emale singer 109.000000+ Q6 `4 O0 w' G8 ^' C8 L
slave owner 77.000000
! S! A: [! v: w, W7 z, a) H; amanderly 62.000000' L5 g2 n. }7 G6 b
lollys stokeworth 62.000000% P9 U, W- S* V7 ?% |5 Y' j. \: l
dothraki matron 56.666667
& k v* m8 `/ J! PName: len_words, dtype: float643 j( \& z( }/ B4 x
18 d( d" p* ?4 _; u$ M7 w
23 F. [* a+ @6 m1 }! x7 ]
3: ], R3 y/ h1 u7 u2 A" b0 w
4
C7 N3 U8 T6 k5 ^5) }4 z5 Q' @1 R
6
) r, \1 U8 }' W6 I75 `) x% x* q0 N4 @( k* U5 |/ P8 n
8
: S8 b0 }' l( l9
2 _* k7 b" E- u7 ?/ }10
3 a% P" p- r- [6 V; P: O; Z" x$ L116 j, i, ], ~2 y% e' T
若某人的台词中含有问号,那么下一个说台词的人即为回答者。若上一人台词中含有n nn个问号,则认为回答者回答了n nn个问题,请求出回答最多问题的前五个人。1 e3 d8 O& R$ I6 m, r
df['Sentence'].str.count(r'\?') # 计算每人提问数& E |" P+ ^5 L) T7 `- _' ? z
ls=pd.concat([pd.Series(0),ls]).reset_index(drop=True)# 首行填0; Z' z& E% J0 j; M- p5 E
del ls[23911] # 末行删去
6 i/ T, Y6 |1 \. ~* @" Udf['len_questions']=ls* Q% x% s- B, X* G$ A- U; k4 U
df.groupby(['Name'])['len_questions'].sum().sort_values(ascending=False).head() g% n3 M% e3 G5 T( l( r5 A! f0 f7 N
M3 ~; Y6 M7 F: h2 r7 FName1 O' B7 p% |/ s5 o9 E( S# P
tyrion lannister 527: z1 `& I+ K" M0 f; L6 `( R
jon snow 3746 n9 J/ Y `5 ~: ?9 W1 @) ~
jaime lannister 283: k/ O6 D8 G& X* z+ @% \' |0 }
arya stark 265
; `* w2 p% J! i* Gcersei lannister 246
, \4 [) f5 @ i! tName: len_questions, dtype: int64; n Y# U! x8 @4 H
: \- K- C% K/ z+ U2 J/ L# 参考答案
: U- a7 B# o7 y C, R$ x" bs = pd.Series(df.Sentence.values, index=df.Name.shift(-1))8 r2 l8 j, r1 T( l
s.str.count('\?').groupby('Name').sum().sort_values(ascending=False).head()7 Z. Z5 h2 B1 O ]9 o
5 C) Y8 I& |! w/ u; q! z- G
1
" }& H7 A6 c. F1 [, V3 c* y2 L, ~" d* j% ]
3
7 D3 W" Z" E' r( N5 K( R4
1 f/ g* ~+ z8 [& a2 J" k: V5) I+ ?$ |2 Y4 b$ ^2 |
6
, J% M: H# R1 B. n71 N5 Z, P& g7 O! J
87 A- z3 E% X& I/ a3 ]) I
9
1 e2 R0 r! l6 m+ Y10
6 u6 Z$ N3 s% ~6 [/ U* g11( Y& d' g& F1 Q6 K0 H
12. Y& E! ~2 s" A; h
13
) I S! @; W& I0 I% {( R9 C14( @9 c9 F5 S! F( |6 l! W7 w: V& Z
152 [( Q3 U" n) F; I
16
) ^4 R7 g/ a% I17
[, I& M+ f% E1 ~! B; ]7 `& y第九章 分类数据% e3 _9 G# o, E- ]6 x" ~
import numpy as np
+ f, r+ R% c3 @' R% t3 Ximport pandas as pd
0 J6 @8 o0 C w0 L/ @9 m1
, J4 o& n0 r* R3 N2
0 K* @4 o4 M R/ U7 }9.1 cat对象0 X G8 W1 e7 L9 B! {
9.1.1 cat对象的属性5 ^2 B# `( r" V' |8 [% J0 ~
在pandas中提供了category类型,使用户能够处理分类类型的变量,将一个普通序列转换成分类变量可以使用astype方法。0 ~6 o q4 }1 g! M6 O
+ R; r g7 t) q) V; C: m kdf = pd.read_csv('data/learn_pandas.csv',
" r: U6 B8 o* k g$ \3 a usecols = ['Grade', 'Name', 'Gender', 'Height', 'Weight'])
7 x$ \# t: S$ \! c; Ws = df.Grade.astype('category')
8 K9 `3 |* V" @7 H* b6 ?2 R+ y$ x9 e, f. ?2 c* y+ X
s.head()! H8 o1 Z* ~ l" o, \. E7 l+ |
Out[5]: * @1 ?9 w# a+ ^' M& a) g
0 Freshman
) @, U$ ^& I( @# C: n4 Y) M1 Freshman
' ?' c( N0 f2 R& E2 Senior
" @" t; \1 g' b! x- X9 |3 Sophomore/ {1 w0 f5 H- U
4 Sophomore9 l& A! Z: ?# I" t
Name: Grade, dtype: category- j" v( P0 N7 V2 Y4 c7 G
Categories (4, object): ['Freshman', 'Junior', 'Senior', 'Sophomore']7 f' U5 h$ M5 |0 i6 g/ k9 I
1; N Q- |4 N. z
20 W6 J3 o' @" W! q' `/ G* @
3 Q5 s$ T- `4 l, D8 G
4
3 t4 F$ z3 \3 i1 F" T5
; w' k! X2 g" Z4 P6
/ l" i' u1 q1 H! s9 u- D! p7
5 ?7 x' @- t8 K, E2 |% |8
3 b8 _2 c. X* O( d9
2 ~( S# B; k( X- K+ I101 M- e: B1 X3 n' X, w' E6 Y+ @
11
8 ^; r' S2 ^/ ?9 ~7 e) {5 B9 M12
0 \) ?" x2 q2 `# g* O' V13* N0 e+ ~5 ?2 a a$ ?( @! j
在一个分类类型的Series中定义了cat对象,它和上一章中介绍的str对象类似,定义了一些属性和方法来进行分类类别的操作。# D$ C M& q2 N0 I: x' Y8 A8 D7 j9 D
6 e+ A6 O) w; L @
s.cat
) N, H6 L& R, ^ R% |# x1 H; aOut[6]: <pandas.core.arrays.categorical.CategoricalAccessor object at 0x000002B7974C20A0>
|1 c/ Z# D7 i x6 M1
5 G: P' y t$ y2
W- m7 A+ V G. Tcat的属性:+ I1 D! `# v4 p: \0 M k
* @; K/ K2 p0 J5 T* L* x
cat.categories:查看类别的本身,它以Index类型存储
4 b f6 g, W9 i& ucat.ordered:类别是否有序* l2 U- p( B: w; x0 B; j p) }- w5 I
cat.codes:访问类别编号。每一个序列的类别会被赋予唯一的整数编号,它们的编号取决于cat.categories中的顺序# }! n5 D/ V& h; |4 y9 R) W$ G: K0 e
s.cat.categories
# C7 A" }4 X8 M, Z. `Out[7]: Index(['Freshman', 'Junior', 'Senior', 'Sophomore'], dtype='object')
. \, Q+ E. Q# F* i5 M* O9 B9 y! E* m" K @3 _0 [! u
s.cat.ordered
( b6 W. q9 S* z% z" r, XOut[8]: False* q& { y* y2 ?: y" b6 Y# y' F: U
7 J" r0 c5 A9 |) ?# B9 Gs.cat.codes.head()
' b" O; e% E! l6 COut[9]: : P7 X8 _- P1 l0 m. N2 L
0 07 Q5 M& v- E o% U* k" N* a7 }
1 07 {" _% r0 d; [) f
2 2
. f/ a9 Z B4 i6 ^7 |- ^* i3 34 \/ V* G; u. |- V4 I/ J8 `! ~
4 3; N# |; N) V8 O6 I9 g" I! m
dtype: int8+ q' k: b b ~6 I* n( w! d: X" X' |* k
11 V* [# \" g# b# P5 C# I- K6 j+ e6 O/ q
2
% n/ A& k1 M' H! ?0 ^! i31 c3 n( h3 ]# m
4. A k+ U% \6 V/ i9 J4 {
5
, I6 x6 ]; L, f7 ?* `60 ], \! j4 o% A- ]8 _
7; s5 D, N! A2 |/ H6 q, f9 l6 w
8& v, M2 ~8 O& K- R2 E
9
7 i* q1 T2 c+ S+ I$ M, p10
' e9 g% b+ _. S. ^7 O- g+ a11
* a4 ?$ a, l( @) F7 U9 ?127 N4 N1 J; m$ p1 B- r8 X, D% c
13
; f3 _. Z7 f: c6 W/ B+ i3 F7 G14& z; M2 T: Y$ e0 B) T
9.1.2 类别的增加、删除和修改. K/ p; v; G( |4 i4 C
通过cat对象的categories属性能够完成对类别的查询,那么应该如何进行“增改查删”的其他三个操作呢?1 Y4 \9 I% \3 E' u M
5 ^0 n, ^2 `0 T# n1 A+ ?【NOTE】类别不得直接修改
o( r( ^% ]- I. P! c, \/ }在第三章中曾提到,索引 Index 类型是无法用 index_obj[0] = item 来修改的,而 categories 被存储在 Index 中,因此 pandas 在 cat 属性上定义了若干方法来达到相同的目的。
. s% s% r5 c3 Y4 S2 d: K2 m% M% D: Y. @) }# O
add_categories:增加类别+ r! D( h: V- _% } N" p' ]/ P
s = s.cat.add_categories('Graduate') # 增加一个毕业生类别8 o7 d) m2 L0 f4 P9 Q/ d6 Z3 \% _
s.cat.categories5 [/ A. F; _* ]% h) I3 ]
6 n; M7 T* ?3 @; D' \: jIndex(['Freshman', 'Junior', 'Senior', 'Sophomore', 'Graduate'], dtype='object')
1 U) b4 _2 h8 G9 T1
4 p. i/ F# E* x0 U: u1 Z& i. C2( v' q0 B( w& I
37 @8 t2 o' a- n7 z# P
4+ e: N) O/ F( ^2 \$ [0 g
remove_categories:删除类别。同时所有原来序列中的该类会被设置为缺失。4 b. L6 V% ~' w' C$ p9 ^9 }
s = s.cat.remove_categories('Freshman')
- y3 d5 R" p2 V& G6 s- F" m# \2 O1 p% h, M, Z( D) O
s.cat.categories
1 y U# c* B0 B8 }) ~0 K- R. Q* vOut[13]: Index(['Junior', 'Senior', 'Sophomore', 'Graduate'], dtype='object')
! g. ? M) j& }
. J. K3 q4 j$ r" \4 U! Xs.head()
2 A! G( H& h/ C$ eOut[14]: 6 e7 i4 B9 _- a
0 NaN
: o! t% T& Q( J1 NaN
8 D: \; l$ V9 q2 J# d' U$ X2 Senior
' [- B% q3 a) v3 Sophomore; } b7 r- l, h/ O9 Q1 [6 w
4 Sophomore
1 A' R! j8 C, Y+ e# ?/ |( RName: Grade, dtype: category
' V' h* I o( V8 C& |2 UCategories (4, object): ['Junior', 'Senior', 'Sophomore', 'Graduate']5 N/ [: a" f: R- d e
1
# X) a" U1 D8 g5 ?6 [3 p2
4 P. H: R5 y& R l- A& d! r( v! R3* b/ A, q/ L1 |9 I
42 d! o' |, d [/ s* L
57 e" A( n# ~. N# q/ z
6
6 k- O) z/ a" F* c- Q( v6 h7
( d$ l6 L) @) V4 i$ a, b% ^8
; R2 l5 H+ H' g: n: H91 Z' \( C! u" d0 G
10
6 `; c4 z# f* _3 ^) {11
, d0 J- O! f) Q6 F/ D1 V/ V12
0 v" D# T! q, H, P8 K9 T13' h" Y9 `- Z9 c$ K8 I5 R
14
8 h8 A8 Q5 z# { v u: tset_categories:直接设置序列的新类别,原来的类别中如果存在元素不属于新类别,那么会被设置为缺失。相当于索引重设。4 ]/ ^5 ~% b9 S) u! K
s = s.cat.set_categories(['Sophomore','PhD']) # 新类别为大二学生和博士
. v/ J5 S; `. \+ A) ^: C" n/ v6 b7 `s.cat.categories1 |5 K3 E- e Y0 w7 f+ `$ Z
Out[16]: Index(['Sophomore', 'PhD'], dtype='object')
' c9 L! d- g; j4 c" I$ k0 Y7 F. y' m
- U/ R! X$ }4 `! x- Rs.head()
! i& _5 L* a$ f; W' |" d1 s* KOut[17]: 4 J2 e% ^6 g5 @% r1 t
0 NaN
5 _: u. S/ R! }7 w5 d) L ?6 M. \1 NaN$ W; Q$ {/ x/ }- @
2 NaN
. h0 A& I9 v8 S: X( ^! X0 q* R3 Sophomore
- @5 S8 u. g: ]) T. F4 Sophomore z$ g8 R0 F x, b/ C/ i
Name: Grade, dtype: category
- E! J# l9 |6 x- BCategories (2, object): ['Sophomore', 'PhD']4 w) |: M% L% f* N/ T, o
1
$ i* Z$ x2 |+ }+ Y# j, E2
9 Q9 i( { Q* F% a( S0 `/ \2 G% x3
- L2 F" A" h& C6 w F( S7 \4% W- O3 e$ |* ?2 |$ u0 |
5) N0 f; M3 Y% d) f- d0 Q. R
6
) a/ `& J7 b0 v* g4 S1 u7. M2 Z% ^- K+ ~1 i8 A K
8
) n! C* G- Q# j7 K: {& u/ _9
" E5 Q% a! w) a* I2 z5 s+ \10
, v: s5 _9 T" E; o, Y6 R11
0 k' U& X8 @1 r12
. O% g- n( E' q5 [% m0 z0 O4 Z$ n" b139 [5 d+ {- ]0 \4 X7 a
remove_unused_categories:删除未出现在序列中的类别
/ F1 k. q( X4 `: ~6 js = s.cat.remove_unused_categories() # 移除了未出现的博士生类别
. W8 q& {- T( E7 K, a" D9 C3 i @s.cat.categories
/ F$ S+ O* P) D w+ m
t; a! E/ C, s3 BIndex(['Sophomore'], dtype='object')' l- @! b" \ G
15 _. _; X( |& |! N$ B5 E( D
2' Z7 m4 L0 l- ?( a
3
3 M1 P6 U+ z, Z47 Z" S8 I3 U/ |/ m2 x
rename_categories:修改序列的类别。注意,这个方法会对原序列的对应值也进行相应修改。例如,现在把Sophomore改成中文的本科二年级学生:( j4 P( N" p) D1 \& }
s = s.cat.rename_categories({'Sophomore':'本科二年级学生'})
1 ~) P1 R/ e$ S A* b$ S7 Vs.head()
. Y( @1 A( W$ U* W. V A0 K: e7 Z6 ?
0 NaN
, g0 a0 K! `$ D. A1 NaN) O6 Y% f% C8 ?. W/ ?6 H2 F
2 NaN+ s/ h& j: ~7 C3 u# `
3 本科二年级学生/ }$ N( a7 r: x
4 本科二年级学生
* Y$ B+ Y* x" L+ f! Y: {# IName: Grade, dtype: category! V0 @2 x8 C5 L1 g D
Categories (1, object): ['本科二年级学生']
& a3 {9 V9 G( `7 c: F+ V: ]1
. ^7 q) S3 K1 @26 D5 z* `, f. l+ q
3
+ ^! p# X' A" W. A0 ^# F& \4/ @, P8 F% ~6 V# p8 G! A
5
( J5 {- l1 G# C2 A68 [" s9 q: I2 _8 B* B# C6 @: c
7
5 T) v4 P) e% g+ u8 B+ {. q8
' y# [- F; S- I/ U ]5 k" v' g$ Q9
! \* g U `. `3 y100 |% C; m2 K' \4 M( K
9.2 有序分类* z) ^2 H" n9 q1 w
9.2.1 序的建立 `) l$ b* h1 C' x3 }1 V7 p9 k% I: j- r
有序类别和无序类别可以通过as_unordered和reorder_categories互相转化。reorder_categories传入的参数必须是由当前序列的无序类别构成的列表,不能够新增或减少原先的类别,且必须指定参数ordered=True,否则方法无效。例如,对年级高低进行相对大小的类别划分,然后再恢复无序状态:' W6 D* r3 N, G
6 j% J, A1 A: v& t0 J ~, I) U0 \s = df.Grade.astype('category')$ Q1 E, o- ?# e' Q4 n b" v
s = s.cat.reorder_categories(['Freshman', 'Sophomore',
5 E9 n- h- h+ S% ~3 H 'Junior', 'Senior'],ordered=True)
% r- D" ]9 N9 z. \& U( Ss.head()& l: d) a2 o* k
Out[24]: ; S3 Q2 V0 [0 `+ p7 Z/ ]
0 Freshman
2 j! {, q# w9 c' u: B( K3 S) |3 L1 Freshman
* n6 {' m* H( S2 Senior
. B2 W. k2 o8 Y$ [3 Sophomore
3 }2 X( n4 J2 b, | K6 F- a# l4 Sophomore
- N3 B- v% h6 `: K$ g, KName: Grade, dtype: category7 F$ Y" l4 l$ P5 T6 T
Categories (4, object): ['Freshman' < 'Sophomore' < 'Junior' < 'Senior']
0 M7 `6 O' b' h2 z. S, [; V$ z& X& J
s.cat.as_unordered().head()
|* e P }- H9 dOut[25]:
( C' N, P* ^5 e$ N/ l9 E0 Freshman [( |6 v2 [; j' G
1 Freshman
/ s8 ~, t, x# g$ |# ^: ?+ L+ \) @2 Senior$ d6 I/ {7 p8 O
3 Sophomore# N: S1 @ e. |" n1 H* ~
4 Sophomore+ L I+ }# _3 b1 n2 |$ o
Name: Grade, dtype: category
- k- f4 ^* q1 v& K% HCategories (4, object): ['Freshman', 'Sophomore', 'Junior', 'Senior']" `2 q/ m7 ?; a% w: z7 s
; Z, J0 B& Z6 ?5 Q* ~
1
5 v/ r1 m5 e5 {( c1 Y2
6 `. A2 b7 n7 A7 X2 j* p3$ @! `: ` g$ K
4
4 a1 z' [/ h+ c5
" S# x) I: d: |' P! f69 G" c- `7 J. B/ `
7
7 ^" M& ~/ h3 o9 S9 n+ ^& K/ Z2 A8
+ e) r9 `. u8 n: P5 ?2 G- [9
" r2 J/ a* b6 v; r10, r/ ]: M% a' q9 k. ]
11
# @6 o, B- C E5 \4 R: s( E12
, `& e' z3 m" N) B1 f13
5 Q4 m" ^0 O# C5 v' H14
+ B& d0 {( C2 Y155 K! Z; Z# |8 w5 l0 U* J4 p
16" ^, O8 G1 G8 S9 _3 C! I7 l
17/ Q! Q! A+ P& U$ r8 i
18
0 ]* N# C+ S% Z9 B7 a7 C198 R. p1 a. V) T) N3 J( \
20
; W. V; J5 o. D6 d1 G21. A2 Q P4 ~: l: Z
22
( I/ w2 r! z5 F G" g) C, E! ? 如果不想指定ordered=True参数,那么可以先用s.cat.as_ordered()转化为有序类别,再利用reorder_categories进行具体的相对大小调整。
# O& c) ?1 q4 _1 d
4 z6 z# c& y0 {) j9.2.2 排序和比较: ~5 s3 f4 Z( Y$ x* |5 E
在第二章中,曾提到了字符串和数值类型序列的排序。前者按照字母顺序排序,后者按照数值大小排序。
: O& D- h! p# d9 {
1 d2 P" ?' F1 |/ C 分类变量排序,只需把列的类型修改为category后,再赋予相应的大小关系,就能正常地使用sort_index和sort_values。例如,对年级进行排序:. X( C# P; M: ?/ \3 A% ]
: M" ?" h2 R5 z. m0 D& F0 F) K' ^! {& j
df.Grade = df.Grade.astype('category')
8 f( v( J& a/ a, Y2 v |df.Grade = df.Grade.cat.reorder_categories(['Freshman', 'Sophomore', 'Junior', 'Senior'],ordered=True)
+ |! D8 J: o; [ B9 M4 a; q0 Ldf.sort_values('Grade').head() # 值排序9 P, N1 r3 X/ X
Out[28]:
( V5 E8 b' `4 m2 A Grade Name Gender Height Weight
7 m+ J* ?, P' \, u/ W; H4 @: D0 Freshman Gaopeng Yang Female 158.9 46.04 K, Y7 W" f: X4 Q( l
105 Freshman Qiang Shi Female 164.5 52.0
( K W. s% P H0 C% O2 o96 Freshman Changmei Feng Female 163.8 56.0& m& f, o$ H& O$ O+ G& w
88 Freshman Xiaopeng Han Female 164.1 53.06 {: k& \' v" s& H( i2 ?
81 Freshman Yanli Zhang Female 165.1 52.0& `3 G5 n, {. m7 P3 C- r- u
7 t; ^" I# d6 F4 C( Idf.set_index('Grade').sort_index().head() # 索引排序
; V4 V- q+ {: eOut[29]: , P2 g6 B: D t
Name Gender Height Weight
3 V6 ^6 c5 m- v- @8 EGrade
. B2 h- ~2 V5 v* W9 a2 H( qFreshman Gaopeng Yang Female 158.9 46.0
) {8 G2 R3 O6 E! A% c2 ~) N- QFreshman Qiang Shi Female 164.5 52.0
6 Q- I7 F5 c! t& k4 S, I6 xFreshman Changmei Feng Female 163.8 56.07 y& Z% |8 o3 O) U! E( r; e
Freshman Xiaopeng Han Female 164.1 53.0# F3 H* m9 I k: u
Freshman Yanli Zhang Female 165.1 52.0
' a( ~7 P w' j- \
/ g" B- N* F, Q/ a/ J1
3 v; ~& ~# s; s J! |9 ]* F, q2, ^& q0 B! v6 S0 c
3# R6 n4 X( `2 y6 e" j& M1 i) x
4
" P% s" ~3 {/ G$ D5* m+ m1 f- B1 J! v7 P7 @
6
& ~+ m5 ?' Y* h5 w- o" b9 d$ p7
1 K& ^& `, c; E& a+ p4 D, A8
& p( P" ?3 n3 E" \1 Z, _9
. H, D( I3 Q7 f2 x10
0 B; a' w3 f' V: A) q( f" A" [: X11
( M M0 @& ?3 q/ U: I7 q+ I& Q12) p/ Z% ~) h$ j" M* O
134 N( q* W. R4 j. i" I8 ~
14 m, u, [9 O3 P1 B K& e
15
0 H& l' N* F* N _' s# N16
4 ~0 [6 f4 I3 j- J7 [5 g1 ^17
3 Z% V. e. y6 Z0 @; p- Z18
W! g8 g# e1 Y, k0 n: C19: J5 m6 V+ @- X# B
20
n! ^7 O: r7 `7 s 由于序的建立,因此就可以进行比较操作,方便后续索引操作。分类变量的比较操作分为两类:
* q: W; O, U2 E9 [
# D9 Z$ R, \! E: Z" r==或!=关系的比较,比较的对象可以是标量或者同长度的Series(或list)。(无序时也可以比较)
( D4 ?8 k; g9 l: y>,>=,<,<=四类大小关系的比较,比较的对象和第一种类似,但是所有参与比较的元素必须属于原序列的categories,同时要和原序列具有相同的索引。, P/ f- C- c2 ~% v
res1 = df.Grade == 'Sophomore'2 h# u2 M0 z: j" Q; j7 k
) t6 ?' y$ b) o9 O% nres1.head()
8 ^# ^! t$ j( x ?Out[31]:
# d/ d6 F7 P5 i$ ~0 False! _* V, R1 T# n1 `* B
1 False+ {" I2 f' u; V7 t4 B1 D
2 False
5 n, t2 w, q! r3 True
4 a$ z. e( S1 |! B6 k) L" {4 True
/ U0 X3 }% u0 e' H4 xName: Grade, dtype: bool8 I8 s$ j/ K$ U
; q. _4 V- w. O) {/ `* O. ^res2 = df.Grade == ['PhD']*df.shape[0]; s) \2 U, o& f. W- |* I
- u& s: }+ O" u" h8 vres2.head()0 J+ {8 I8 _# J5 t( [& |' U
Out[33]: 4 S- Z" ?8 x A
0 False6 Z6 m# B, [" y) ?$ ?" e
1 False+ W$ X7 s+ _6 _. C, `2 I7 k
2 False8 D! j" l* m, G7 D) L' K) b
3 False6 M, s# ~7 P0 O: B
4 False
/ X$ U" {0 a: y6 I$ o0 dName: Grade, dtype: bool# H5 C1 u- q: N4 [* @# \
/ ~% g" ]5 O# Mres3 = df.Grade <= 'Sophomore' o4 G3 l4 L! ^" M# C7 k2 j; z
( E/ e4 Q0 S8 K }" \7 S. t, t/ Xres3.head()% L0 ], Z' N$ Z. Y% Q% T
Out[35]: ! w' ~# m5 H" L# K4 V5 D
0 True. D/ `1 s1 D* Z% I( M/ y) s
1 True
9 w! o& @7 k' N f& c2 False
+ l! A( o4 r8 D. @) y }, x3 True
% H* \1 h% F9 ]& K4 True
' n# W k4 h+ s) b9 m2 F7 e' H4 q1 CName: Grade, dtype: bool) {0 f/ A1 J2 y& j
6 _$ M0 ], z8 q
# sample(frac=1)表示将序列随机打乱。打乱之后索引也是乱序的,直接比较会出错,必须重置索引。9 _- `% x. y( e0 X" u
res4 = df.Grade <= df.Grade.sample(frac=1).reset_index(drop=True) 1 |, K; J( y* z
5 V/ a% B0 a1 j, L, \
res4.head()% h! S3 O% {; a1 V% _. n z+ [
Out[37]:
1 N+ H- X2 Y: b, l0 H% Z5 s* r* {0 True7 U) K7 P5 b Q+ |
1 True# M6 a+ @! u% N( Q
2 False, q; q5 q9 N1 [* y) G- p8 a
3 True
5 m% ?- S+ {( Z: U4 True
( `0 ^7 G# c) [! WName: Grade, dtype: bool; l6 B$ N/ ~. e
: r/ b4 W4 x) m6 `1
& K: f1 p# ^; b+ U9 w25 U- b% ` y5 e* X6 E
3% R8 z- c8 U/ a* c3 R& |
4
- V% W0 ^" n# I; S5
; V' x- n* h) d0 r65 L6 ^" ~# O3 A) Y4 s0 W: g
7' v+ |; q8 m' \+ m D0 v- T2 N# W
8
9 K4 h! y( C8 {9
; V/ O% f7 x! G& E! ?10
4 X) \+ {' I2 o% j* Y% a8 b: e4 Q118 w2 ^" b, s! S( {7 L! O: _; |
12
) N& m+ V9 r9 C13, f7 u( q; R2 J0 i6 J
14
* h3 l2 e1 s3 x! M15
: N. P% [! b+ A+ Q" H160 [: g; X! \7 S0 G% ~
17
* e0 c: W2 c: l' r+ [" R% I! r18% \0 X5 E+ x$ I7 k. a+ j
19
7 g W) V" x- A8 l20
4 y @+ y4 p: j/ ?5 F218 s) l* J9 h6 S6 X8 I, m" I2 N
22
3 G# Z q+ Q5 x z+ y23/ D7 s$ h; F4 g
245 K4 m: i' {: H) Q7 ^& k
258 [ N# s0 M2 s. Q- E
26
" E% g, V6 V4 e27+ ?1 A2 |% S& _. [5 D
283 N1 c e( @% k* K* l, |7 `
29
1 Y% G8 Z3 J- b) ]308 o4 a: z5 ~; n+ N) r; m$ M
31
. _( Z3 z, @) I! b$ b8 w32
4 `' |+ V' F% \/ l a |4 j339 d6 F1 r, h# j* i: @% u. U- g
34
$ C# [* J" r2 ~5 y" F35+ S# M$ T# |6 J) n' a/ y
36
9 j0 ]( |3 w4 m5 z) c37
+ e' y9 }5 z/ u6 E# L389 s" {/ F7 B3 m/ M5 J
394 k; v* @# ]- C$ i8 X4 M& z( m
40
) B" @5 p" Q& P' D% E+ d& r0 H41
. a( `0 T" I# B' N5 B" [42- I1 w$ U! U9 }( M0 l' i3 u" J
43
2 w0 r4 A: U. c1 K$ L: Z44: n" b% V3 O+ b+ ~
9.3 区间类别
& f6 U- O8 N: M2 ^9.3.1 利用cut和qcut进行区间构造1 Q" |) N$ ^' x5 ` p# n1 e3 V
区间是一种特殊的类别,在实际数据分析中,区间序列往往是通过cut和qcut方法进行构造的,这两个函数能够把原序列的数值特征进行装箱,即用区间位置来代替原来的具体数值。
+ X$ w, g; I% }, A# E
& j2 R2 _* P- f+ r) J. Bcut函数常用参数有:" b# C- B# Q) x8 Z4 d4 g$ }! c
bins:最重要的参数。' ~7 }' F0 p/ [ Q* b
如果传入整数n,则表示把整个传入数组按照最大和最小值等间距地分为n段。默认right=True,即区间是左开右闭,需要在调整时把最小值包含进去。(在pandas中的解决方案是在值最小的区间左端点再减去0.001*(max-min)。)2 L7 B3 b8 k+ Q: G; ]$ y
也可以传入列表,表示按指定区间分割点分割。 C4 D4 _; v: O1 T
如果对序列[1,2]划分为2个箱子时,第一个箱子的范围(0.999,1.5],第二个箱子的范围是(1.5,2]。
& a {" p3 _% P% O; g, a 如果需要指定区间为左闭右开,需要把right参数设置为False,相应的区间调整方法是在值最大的区间右端点再加上0.001*(max-min)。
/ A0 t6 ` l, }) m* _& p. l% h
% n4 C& m% w7 Z' C9 o% N" m- m3 y) gs = pd.Series([1,2])9 l l5 s6 s7 V* y: a
# bin传入整数0 R: T- l" Z( ~% J
& U6 I; D- B+ x0 v& `pd.cut(s, bins=2) g/ [- g; S5 l$ C
Out[39]: j8 ]2 f( ~: \4 t6 x7 @% z1 H
0 (0.999, 1.5]1 E6 t0 r+ R P
1 (1.5, 2.0]
1 P% J% Q6 E* u( o. Sdtype: category) a. g* [5 I! D' |$ p3 U
Categories (2, interval[float64]): [(0.999, 1.5] < (1.5, 2.0]]/ c! X% `" ` T* l; B" T2 o* {
! J2 n$ y5 z( _# }5 Z: U+ xpd.cut(s, bins=2, right=False)
" A. B7 h' `) l. y( J! AOut[40]:
/ g+ V4 n1 n0 g9 D7 M4 {* T0 [1.0, 1.5)6 r) k3 c4 ?& c% e6 ?$ O
1 [1.5, 2.001)
+ @# m; j/ x( l& Adtype: category: r% X* R: D2 T1 x
Categories (2, interval[float64]): [[1.0, 1.5) < [1.5, 2.001)]
, k+ z2 Z; f/ D- p9 V$ u: B
2 `; b& n5 o7 j$ q; }
% ^" N! [4 I1 W, s* a4 V# bin传入分割点列表(使用`np.infty`可以表示无穷大):
7 Q+ k H8 N# v/ O5 n! g- Q: Rpd.cut(s, bins=[-np.infty, 1.2, 1.8, 2.2, np.infty])
5 n8 y; ~7 r& Y' a: G. eOut[41]:
3 P' U _2 i$ {1 q0 (-inf, 1.2]( O: Y8 @* p9 {- c j
1 (1.8, 2.2]
' {0 B+ P; h) U* F3 w f5 idtype: category
: Q2 T6 @6 K+ {/ S: L2 ]( oCategories (4, interval[float64]): [(-inf, 1.2] < (1.2, 1.8] < (1.8, 2.2] < (2.2, inf]]* i9 y: Z4 K8 [/ C% _
0 [6 e/ Q/ Y' Q: X4 H! n
1
U* ?# t; m% T2& S* N7 I" f( P( K
3
% p. X) S U+ j5 t4
' T; @" d# w% L# e: i# j1 |4 l+ G5
; ^, ~3 Q* s9 ^, I- S$ r6* x! Z& Y$ J7 F0 I4 E1 m$ `4 \
7
7 Z( y, l } `# G% F, A$ }& [8
$ ]) r+ k8 s$ ?& ^' b9' E" L8 d7 _; }2 N
10& O& I! Y- C W" S- [
11* f1 v5 ~$ ?6 R: k
12( |7 u$ F/ K8 ^! E5 O0 V7 l
13+ K. Q# ?6 a# I# \" h2 g7 k
14
) U; v3 O9 `: }" j: W15
7 E8 @7 ?& a# Q2 q% n! P; A& k+ M$ {16
6 ?" N% U' U( k% i: T) C; x17, J1 j: t* C- u7 q
180 b% g( J3 u, I- f+ A. H. F y/ w( `
194 K+ L, n: a, z6 l2 M, k9 ]' O" Q
20/ |+ t' J3 X9 v1 f
21" X& H/ r2 U" X9 ^, j+ a
22% e+ ]" ?) ?* {
23
8 [( q# T- r1 O# W2 V( W: o24: H/ N; `, w* m6 b+ p4 B" M+ V
25
0 w! m y3 `# A$ {5 Alabels:区间的名字
3 x" k9 [1 f: @, g; {! ~! Wretbins:是否返回分割点(默认不返回)" ^! \8 H* c K; q% l! q0 K- s
默认retbins=Flase时,返回每个元素所属区间的列表/ p, B/ F0 m0 _
retbins=True时,返回的是元组,两个元素分别是元素所属区间和分割点。所属区间可再次用索引取值& K( C, U0 L; n
9 W0 O' z/ _1 t
s = df.Weight
. u4 B8 A' M& i7 _2 c+ Fres = pd.cut(s, bins=3, labels=['small', 'mid','big'],retbins=True)% r0 w9 o7 C5 v& |" X/ T) b
res[0][:2]
+ y+ o- \' x/ F) R
- B5 T& d$ u$ n, BOut[44]: * U' f% o6 a1 i) u
0 small
5 P3 v0 I3 D, x2 J' Q( G2 ^/ i0 v1 big
9 w1 P' V/ ^1 w0 P' Qdtype: category
. I8 _# Y( a; E6 N5 p4 {Categories (2, object): ['small' < 'big']' W( t* o ?. C9 \* w
6 L% }. T. g |5 Mres[1] # 该元素为返回的分割点/ C5 y* p) [" m( N6 d. e9 t! C. f8 p
Out[45]: array([0.999, 1.5 , 2. ])+ M2 p/ q7 V+ A! |2 K6 K! j! E
1
7 S. t$ P4 X% l3 i/ p2 S6 ^2
( a3 O+ E% C" j4 j$ T* ]% b5 G5 m$ p3
0 s+ P. a8 S9 X8 l4
' T, n! q+ x& C1 k7 G2 w# m- P- `* T5
$ t1 }4 @7 e4 c1 a: B, S7 a1 q6
; z% B' P3 ~, |- ^7
; t6 V1 L2 I" x A. C" b% A5 m83 x0 }3 _# o/ I
9
: X: D$ F' {' |( k& Q8 v! y8 Z! Q10
4 r U7 M9 n0 p) W11
4 W, i1 \9 |2 s, \. ]9 J% a$ A12
" l* h% S( h- {* }3 h1 q7 r) wqcut函数。其用法cut几乎没有差别,只是把bins参数变成q参数(quantile)。
0 O0 w( e0 n$ xq为整数n时,指按照n等分位数把数据分箱
/ D4 D* J r0 g4 g% [6 V$ ]) Zq为浮点列表时,表示相应的分位数分割点。
3 i3 W. R9 O* |- js = df.Weight
) i) g8 ~- [$ J' o. }& n
1 V3 D3 j# E& c/ Tpd.qcut(s, q=3).head()
" P' ]. D, Y+ z. p! X; R- lOut[47]:
: M0 a/ x5 L2 P5 b3 w% i3 |0 (33.999, 48.0]
3 i9 v Q1 B- _, W: {. I# p- E1 (55.0, 89.0]
! h7 @/ G' t* Z4 c( \1 o* d1 d( {2 (55.0, 89.0]
7 o4 b% ?" V. l* j6 m3 (33.999, 48.0]- y V! l/ ]$ z3 ~; S( H( k) g0 e5 K
4 (55.0, 89.0] ?: C. K- J& q' Z1 Y9 j
Name: Weight, dtype: category/ t1 u0 C; J# W8 y8 E% }$ L2 ^. u
Categories (3, interval[float64]): [(33.999, 48.0] < (48.0, 55.0] < (55.0, 89.0]]
1 k1 P0 W+ Y' V4 Z- l8 [/ [! }+ G i) F( F; _; j" c
pd.qcut(s, q=[0,0.2,0.8,1]).head()4 {8 V6 U: J, G" N1 y
Out[48]:
) _8 D7 k: {4 ~8 M* R/ B9 K! ~0 (44.0, 69.4]
" X/ J3 Y1 ^8 A s6 A4 `6 g1 (69.4, 89.0]: [* ~. p* V3 u5 R' k& R( h
2 (69.4, 89.0]
3 d9 v \7 N' T; q% Z# U' F, g3 (33.999, 44.0]2 x* L7 }5 m8 Z: U7 G
4 (69.4, 89.0]1 z' y! m0 L3 H: w
Name: Weight, dtype: category" n. J- e. Q8 Y. B9 b
Categories (3, interval[float64]): [(33.999, 44.0] < (44.0, 69.4] < (69.4, 89.0]]4 k4 j( K" s* F2 s. h
) D+ B; u2 m+ V' b
1
. i; P- y5 n* f% k& ?. J2
# T" X" k* Q4 f7 a2 i39 F+ ~( c2 U) S9 y
42 @9 d3 l- U7 ]" k- G
50 J" j5 k C0 A3 q! I% ]2 ?% X
62 q3 r- h* ^" V( u
7
" U0 z& K) H- h8
. F1 [6 u1 W, T$ {+ G' a, m/ |" P93 \/ ^. l6 j: P' D: {5 n# X
10
/ q. ?6 U* z7 t11
) e) I/ P# @# p% N# j12" }9 q' L" n; X3 a1 U1 R) A
13! t8 u( [! t6 J( O
14; y; x' x9 Y9 y5 u" a
15
. V6 E# r# n$ Z8 M16
0 |5 | @% p9 Q8 A3 v( S2 k17
& R8 W# t( X0 Y4 Z18
! f8 f9 b9 L! b! L% j$ c5 M19/ W" S" }$ }8 X+ F* t
20
' B. B- N! s: ^7 d9 R21
) s1 D) u: u; e0 F2 x9.3.2 一般区间的构造
* O3 u$ g, \2 F0 m, h. L0 n' I5 [ pandas的单个区间用Interval表示,对于某一个具体的区间而言,其具备三个要素,即左端点、右端点和端点的开闭状态。
& i; s2 _1 R' v$ u) }5 g5 d' \: f' }7 j) X0 V
开闭状态:包含四种,即right(左开右闭), left(左闭右开), both(两边都闭), neither(两边都开)。+ [; M: R1 b8 O3 {) S# U
my_interval = pd.Interval(0, 1, 'right')
+ o( m: K) {% l9 V1 _ h$ j- V8 F( c1 }
my_interval
# `% W5 e, c! N: U- _& qOut[50]: Interval(0, 1, closed='right')4 Q9 I# x' k& }- [9 B
1
( q. A0 L4 k3 G1 Q25 o q: A' f4 d$ h" `, f% A
3
b2 z! D) B' f' m- L: w5 Y4# K- K) a: y3 `" }. w2 M. g* i6 ~1 W( U0 W
区间属性:包含left,mid,right,length,closed,,分别表示左中右端点、长度和开闭状态。
6 U5 T8 D" W* H; f使用in可以判断元素是否属于区间
( C8 {" j; H2 ]用overlaps可以判断两个区间是否有交集:
! j# I+ S1 ^) w6 F0.5 in my_interval
8 N" Q% e$ I$ Q9 k6 M/ L* f* M% ^8 ~ y' s
True* d/ {4 t. y; m0 N5 m7 K5 u
1, [8 @+ B. H: O' x% N) a2 b. z+ o+ y' ~2 O
2
, ^* {3 h# w6 }; r& ^6 h3
; q% o) e8 _2 U& \, L9 i \* K1 Umy_interval_2 = pd.Interval(0.5, 1.5, 'left')
1 w( `# `4 e+ N/ _& ~' G3 ]/ v" Imy_interval.overlaps(my_interval_2): \$ ~- a" D* B" n! M
0 S. k$ c; c4 t, s* K7 g# s2 J
True
' X5 b3 G5 ^9 t; [1
! P% P1 A9 I7 `' K4 D! o. }2
4 q ~1 ?1 s y% ^2 J% X2 Z3
/ S5 u2 j; E3 a5 s4) W) B/ C+ `5 r, N' ^3 n$ W
pd.IntervalIndex对象有四类方法生成,分别是from_breaks, from_arrays, from_tuples, interval_range,它们分别应用于不同的情况:& Z9 n" z" k* r/ }
+ q& E4 |/ u; G$ B3 k$ s* I4 q
from_breaks:类似于cut或qcut函数,只不过后两个是通过计算得到的分割点,而前者是直接传入自定义的分割点:
( L6 v9 ? e8 D, F7 B. B# i" ^pd.IntervalIndex.from_breaks([1,3,6,10], closed='both')+ d1 z, m. ^6 f2 y' R
* b1 [: p7 ?: W( _5 G
IntervalIndex([[1, 3], [3, 6], [6, 10]],# a6 v; V2 U# N" l9 Q5 c
closed='both',+ Q1 N7 v8 ]- @+ o
dtype='interval[int64]')
( W6 n7 t. m. q18 V$ e7 i. ]" ]' e( N
2
6 Y6 O; `/ V# C& i: W, z( k3
" f2 W2 ?# J/ J6 m: M; |" Z$ N. d4
2 r3 T4 n, y. N& l5
( H, q3 U% v- ?( ^% N7 {from_arrays:分别传入左端点和右端点的列表,适用于有交集并且知道起点和终点的情况:/ C! q' @ K7 ]' W' r0 N% ~
pd.IntervalIndex.from_arrays(left = [1,3,6,10], right = [5,4,9,11], closed = 'neither')# Q+ {) u% V8 s
3 ?, w, n2 f5 t3 L8 C: ^7 \IntervalIndex([(1, 5), (3, 4), (6, 9), (10, 11)],9 y7 d* Q2 ]6 _+ l
closed='neither',, A) l% ^: n/ S8 ?: O% @$ M8 a
dtype='interval[int64]')6 Z h- [; a, s+ K+ V0 y8 J
1
6 N7 Q# D2 Z0 x- k' s0 @. {* [2
0 o( r) o' k/ V9 P0 t2 \3
/ y# f) b7 h) ~8 @3 b0 D4
% m5 ?+ Q" D# y0 I8 t5% u' l$ k1 g# c) ?1 L
from_tuples:传入起点和终点元组构成的列表:
0 d' _: F* u0 J! F8 j8 Npd.IntervalIndex.from_tuples([(1,5),(3,4),(6,9),(10,11)], closed='neither')3 z1 Z7 {+ y0 N" S2 h" `* s* Z
" C4 n; F" b0 B$ y% n4 _
IntervalIndex([(1, 5), (3, 4), (6, 9), (10, 11)],
+ T) c" b5 g9 T. d6 ~% Q closed='neither',$ H! E; J# {/ j* l, x. n
dtype='interval[int64]')8 t) e# }- \: C6 }/ x
1, ?' X. U$ e% k+ U( |' h
2
1 b2 _& A0 H" K7 k3
& L2 `; M* U5 T* O% z( `/ W7 E: Z45 {$ w. Y$ x G. R3 | E, B
5
) M& V5 ~: j" ]+ i5 k/ M: C* y& S- minterval_range:生成等差区间。其参数有四个:start, end, periods, freq。分别表示等差区间的起点、终点、区间个数和区间长度。其中三个量确定的情况下,剩下一个量就确定了,从而就能构造出相应的区间:
9 n0 A# T3 b- E% ~- wpd.interval_range(start=1,end=5,periods=8) # 启起点终点和区间个数' j2 T6 X, @- ~( }2 h/ `( w
Out[57]: + ?+ D% E5 i5 }3 G+ G' O* [3 z
IntervalIndex([(1.0, 1.5], (1.5, 2.0], (2.0, 2.5], (2.5, 3.0], (3.0, 3.5], (3.5, 4.0], (4.0, 4.5], (4.5, 5.0]],* |- H6 @9 r" @7 C6 Z
closed='right',; H3 E2 D; ~" D M7 b p) B
dtype='interval[float64]')
' d( `+ {. `3 [+ h: u
( [3 p% [* H, x7 ~pd.interval_range(end=5,periods=8,freq=0.5) # 启起点终点和区间长度" ^" \ W5 a% b* z; ^, ~6 Q# o
Out[58]:
6 p6 {! M9 H8 R! y1 A1 FIntervalIndex([(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 u( L( Q9 S" K
closed='right',
2 `. N" P; z0 o" D! K6 ]% _# M( j dtype='interval[float64]')
0 C% J+ n3 M& R" p5 B1$ \$ v, O. L5 |% X8 N7 v. R
23 N, ^* }. ?% Z1 M! m
31 p+ ]9 q6 `5 I, Z/ F
4
& `( L. A5 A [% _( m9 Q$ a5
2 s2 Y. V. r& B Z; \5 o# _6+ J- w- M) X4 b' T9 S% [
7" h# `% G3 Q$ q
82 w' E6 Z1 |7 q" O# |
9
9 w8 b7 z) m2 C* z. p, \- K10
2 g6 N5 x) `" C k: }6 W11
0 |; g+ s8 |9 [+ ^9 a7 e【练一练】- T1 m5 U7 x* P4 e. a5 ~ \$ y l
无论是interval_range还是下一章时间序列中的date_range都是给定了等差序列中四要素中的三个,从而确定整个序列。请回顾等差数列中的首项、末项、项数和公差的联系,写出interval_range中四个参数之间的恒等关系。: N" \- Q# N7 P6 M$ {
$ f1 G9 ~5 l8 ?
除此之外,如果直接使用pd.IntervalIndex([...], closed=...),把Interval类型的列表组成传入其中转为区间索引,那么所有的区间会被强制转为指定的closed类型,因为pd.IntervalIndex只允许存放同一种开闭区间的Interval对象。
" J: U1 B! Y# N/ [7 M. U
6 t M: p& U. T1 wmy_interval* V8 Y: m6 h+ b
Out[59]: Interval(0, 1, closed='right')4 e2 {& D s. z" p
0 u% A9 h3 }" v* ^" t+ i: |
my_interval_2
3 E. q, Z5 ]* nOut[60]: Interval(0.5, 1.5, closed='left')
, U' t$ ]# `3 g& t- {2 H9 ~% N8 h; p% Z$ v# o& E5 q
pd.IntervalIndex([my_interval, my_interval_2], closed='left'); N) n! u4 X3 z) q& f, _# }
Out[61]:
}' i% p, \$ ]0 cIntervalIndex([[0.0, 1.0), [0.5, 1.5)],
3 h& S: F: @: @6 X1 r- g/ H closed='left',) \! F/ F8 Y, s1 G
dtype='interval[float64]')
- ?; A7 L( e3 R* f/ i1! R9 {7 a& h, ^4 `/ |: p
2, ^( c1 P3 o! \; `6 h
3* `$ p4 u! {) X- z
4, U2 O+ R( X" d+ |
57 \* K( m7 r7 H9 L2 E. y- V
6$ O, n e* T, E/ ^+ k
7
3 i4 a$ S3 w: n( X8
& L( ?* _" h; F& ^9
4 @( @! ]5 `, @2 M10. u" O7 Z4 j) C5 v7 i4 t! l
11) {6 x) L7 S5 ? }" W! }( ^3 F
9.3.3 区间的属性与方法 U6 L. k& P4 k8 w& N
IntervalIndex上也定义了一些有用的属性和方法。同时,如果想要具体利用cut或者qcut的结果进行分析,那么需要先将其转为该种索引类型:
; {# y H7 Z- H+ H2 S( K1 a
, C* w L9 ^* Es=df.Weight
. B0 u# n9 @, s0 k5 x( H6 z! Vid_interval = pd.IntervalIndex(pd.cut(s, 3)) # 返回的是每个元素所属区间,用具体数值(x,y]表示
; q1 f+ @* \5 x% [' D$ \0 _, Lid_interval[:3]
) }6 M$ K# [' n+ b* {- d& M. V' `3 a5 |8 m. C3 g' ]
IntervalIndex([(33.945, 52.333], (52.333, 70.667], (70.667, 89.0]], {8 u1 e2 E# }9 Y& k6 k' o7 j
closed='right',
1 L; g$ b! Q8 P name='Weight',
4 A, [( R0 |$ v0 G( R/ y dtype='interval[float64]')% H% B Z* t3 F. c2 W
1
8 L M9 ?1 p3 i( I2 W# h( Y Z X2
/ x$ s W8 L( W C+ n( G, w35 f& h' J" \: g* c* t: b+ v
4- A2 i0 V- c4 S+ o
5
. Y5 u8 l H* Y$ a8 ?; ^+ M+ V6" x, Y9 ? ^7 L3 c1 t
71 u3 A, `, K: y+ S- k
8; R2 a K9 X' Q) `1 v
与单个Interval类型相似,IntervalIndex有若干常用属性:left, right, mid, length,分别表示左右端点、两 点均值和区间长度。
- h- [, ^' W! Nid_demo = id_interval[:5] # 选出前5个展示) ]- n( {5 \! T( m% ]; z e- a
! L! S5 e% V0 g
id_demo5 S6 W0 @# Z3 q6 \1 ]' z0 _/ }% U$ w. \( C
Out[64]:
+ ] M& \6 Q; sIntervalIndex([(33.945, 52.333], (52.333, 70.667], (70.667, 89.0], (33.945, 52.333], (70.667, 89.0]],# L' B7 a% R. \2 N
closed='right',
. f2 C; m2 T6 A- M: F( R name='Weight',! T+ E; C c8 |# V; C
dtype='interval[float64]')
4 q7 Q: f$ X1 C" c$ l, d( e$ R, @& v
id_demo.left # 获取这五个区间的左端点' i- s% p; y; h5 O' e
Out[65]: Float64Index([33.945, 52.333, 70.667, 33.945, 70.667], dtype='float64')
% ^, F" r7 _4 k& l4 s/ \: c' L1 W' _# ?$ z0 Z0 I+ l' j( e$ L( k1 }* y
id_demo.right # 获取这五个区间的右端点
7 A; j" ~+ c2 V( F! F T7 QOut[66]: Float64Index([52.333, 70.667, 89.0, 52.333, 89.0], dtype='float64')3 [) ^1 T" [* m" F4 N- R+ |
& e; h2 O9 M8 K3 O% Z/ p& @3 M$ T# B
id_demo.mid- w8 f: S* Z5 Q0 l% _ B! \* d
Out[67]: Float64Index([43.138999999999996, 61.5, 79.8335, 43.138999999999996, 79.8335], dtype='float64')) k% [8 Z7 [" `0 `4 L: {0 v
' w; X* q( h- q- T3 P6 _
id_demo.length
% B4 q, ^& q1 C& X7 ^$ `0 cOut[68]:
; k2 M+ _' f" L: C8 L6 PFloat64Index([18.387999999999998, 18.334000000000003, 18.333,. A! i6 [ j; I4 B3 c R
18.387999999999998, 18.333],6 S( K8 z: j6 D* O# }! [. G3 J
dtype='float64'); l3 T( |- d- C' ~
$ z% O! A7 k' K" K4 H; E+ H0 |
1
+ u n6 c. C; c+ H/ w% c2
2 I* b4 k+ S4 N8 e& D' z& J8 w3 B3
+ n6 a7 u1 W5 w. Q1 C. E( t% \9 v5 W42 l! C& S' z9 k. ~4 j
5 t9 Y) P1 {- L5 c
65 W) x4 T( ?+ {$ `+ G
7 B+ p" L" P: E% Y$ r: c
8, {% d* ]1 x* K
9+ ? s) L7 x) x1 [
10
- M) ^4 a/ \0 V( f) ]" `" D11: W' i# u0 H' E J* B
12
$ c# m+ r- G9 _# |& j4 a& d$ h8 X132 z$ m! c W% Q; S
14' u9 t4 v8 y. o9 f
15
2 Z! H" q$ }. @# ^) u/ V: n1 t8 q16
* r1 j$ I; Y2 V5 @) L172 O+ m/ W1 e9 I4 v0 ?; g8 N; z$ x" n
18
' R8 u0 w/ Z5 A1 x5 W$ |" E& C! t! d19
9 R) q* S5 P% _( B) {- g20
, A1 o1 q# h) y* N7 |21% @( R8 u4 s: F
22
$ W' Y7 ?1 H8 \( W2 t$ S0 c |23
3 l" Q& h: ~: h4 `) b7 t( sIntervalIndex还有两个常用方法:
Y3 z; Y! b6 n+ hcontains:逐个判断每个区间是否包含某元素
5 l4 I1 y5 K( p6 qoverlaps:是否和一个pd.Interval对象有交集。" h% z/ ^9 W. K: {
id_demo.contains(50)6 ? V; N- d( ]1 ?; @. U, A; K
Out[69]: array([ True, False, False, True, False])
7 k" L4 X8 M/ T5 @0 x
. i4 J; Z% D0 y$ L; Bid_demo.overlaps(pd.Interval(40,60))
: R- [: a ]* p, g, aOut[70]: array([ True, True, False, True, False])+ a* l- U9 o' n6 l$ S* o: H- J m
1* Q5 P2 f3 _- X# m& S
2
# s/ L1 }. j+ _& @; T33 X% A' n* g- H; g( N
4, u( r: ?, _5 Q$ j+ |
5
0 `! f( t. S6 f9 C2 }" S: Y2 P9.4 练习
. ~/ q" k3 i* C$ P- J4 IEx1: 统计未出现的类别
c8 y4 G" R8 ? 在第五章中介绍了crosstab函数,在默认参数下它能够对两个列的组合出现的频数进行统计汇总:
$ L* J: r. p! |& q B
3 ^8 p- [" w! m( v3 Adf = pd.DataFrame({'A':['a','b','c','a'], 'B':['cat','cat','dog','cat']})1 M. v( \8 X2 P8 v1 c( t& b
pd.crosstab(df.A, df.B)
4 N8 a9 n! P0 e0 K
/ f) ]( z1 F' I7 QOut[72]:
' L) p# j- t% y. O+ R! q) p. BB cat dog* ]" {, }+ u7 e2 h
A ) B2 z; F' k+ K; T8 c
a 2 0% U" ~$ X) M; u+ r1 h6 R
b 1 0
- H# j0 B1 Y5 Sc 0 1" w7 N$ V/ e+ q }/ e
1
3 j# `" r! {' J/ p+ H6 k2
; i0 _3 v; m9 |, P: e9 F g! m6 s3" ]9 I0 ^6 m( j" J' C1 [
42 r: ?6 p/ M7 v" D
50 ] O8 |% J& F5 H. p5 Y* t
6
$ C* q7 y$ f& U+ w7
. G( S |7 M( X5 s2 x8+ d0 D- l# K3 g& I6 s0 L
9
$ k% m" i9 o# j0 k( s8 x, z; V 但事实上有些列存储的是分类变量,列中并不一定包含所有的类别,此时如果想要对这些未出现的类别在crosstab结果中也进行汇总,则可以指定dropna参数为False:+ J( @& j/ F7 l& j+ U
1 e/ _6 n: _0 M+ P; y
df.B = df.B.astype('category').cat.add_categories('sheep')0 [ I1 U6 |" E8 x6 b; a0 x3 Z
pd.crosstab(df.A, df.B, dropna=False)
* H) j/ j/ p8 [) J) B/ l N8 @# @, `8 {$ `
Out[74]: 2 R+ v/ T. D9 k! [: E& z& V
B cat dog sheep( j/ U2 c- Q3 ? k
A 0 H7 ^& W* M8 N0 \$ h! N# A/ W% n
a 2 0 0+ E' ~7 f) @1 b8 H3 e( S& t2 s, j
b 1 0 0 q; e4 r: G3 f0 X7 r$ r* f
c 0 1 0
! a d' ^; F1 _$ x; {8 T% z1
0 N+ N& b2 I3 S) y% K1 Q' `2
3 x1 |7 L" m: P$ P3- P3 w" o3 W$ P' l
4
6 p6 I) D3 e& X4 O& s* M5
! S4 i! b8 i. j6' D8 S* M6 F4 W: P' q' r
7
, o' G7 z0 h( P/ g6 ]+ P8 o$ E% f3 l6 c4 l# ?) O- ]
90 U4 V9 M3 T0 h. a) {
请实现一个带有dropna参数的my_crosstab函数来完成上面的功能。
( E. Q$ ?; K: E. X2 K5 H1 m- v J$ f1 A3 E: V, }8 u
Ex2: 钻石数据集
9 g6 {7 B# e' e/ H4 P6 I 现有一份关于钻石的数据集,其中carat, cut, clarity, price分别表示克拉重量、切割质量、纯净度和价格,样例如下:
* w+ U5 c* J) U. O
. l B0 r3 K8 T7 Z( X' Wdf = pd.read_csv('../data/diamonds.csv') 3 `% e$ s& p: t
df.head(3)
6 G! ^( H# o/ l% ?; e. I
. [' {8 b, V6 T9 t4 ]4 z1 B) vOut[76]: + Y% T+ M. j; y
carat cut clarity price
" a4 e7 h! H( H* S' O) Q0 0.23 Ideal SI2 326
8 f& q% @3 p+ B4 v; w5 N1 0.21 Premium SI1 326
& f# b' G3 G' O) p! R, l5 C0 c2 0.23 Good VS1 327
$ b; j) `4 z5 z+ f; l1. K6 c! a* E& Z& @3 m* P% W
2. p9 w% M5 s* k6 a( a) ]% u
3
) W& b1 M6 E0 x, H' n4
( d) J& ]% E# K, }% x7 y/ j4 X2 M5
( X/ c( d B v9 u6
% K7 W0 k8 W5 Z7
! N6 n; i. H _8( E G6 X2 \8 W3 [
分别对df.cut在object类型和category类型下使用nunique函数,并比较它们的性能。
- |& Z" }% a; s' O钻石的切割质量可以分为五个等级,由次到好分别是Fair, Good, Very Good, Premium, Ideal,纯净度有八个等级,由次到好分别是I1, SI2, SI1, VS2, VS1, VVS2, VVS1, IF,请对切割质量按照由好到次的顺序排序,相同切割质量的钻石,按照纯净度进行由次到好的排序。) s% P7 C: S! \1 w
分别采用两种不同的方法,把cut, clarity这两列按照由好到次的顺序,映射到从0到n-1的整数,其中n表示类别的个数。
. G. E1 m7 l2 `) K) _9 J# D$ r对每克拉的价格分别按照分位数(q=[0.2, 0.4, 0.6, 0.8])与[1000, 3500, 5500, 18000]割点进行分箱得到五个类别Very Low, Low, Mid, High, Very High,并把按这两种分箱方法得到的category序列依次添加到原表中。( ?7 [/ n/ h z
第4问中按照整数分箱得到的序列中,是否出现了所有的类别?如果存在没有出现的类别请把该类别删除。( n+ ^4 i7 z5 U' d
对第4问中按照分位数分箱得到的序列,求每个样本对应所在区间的左右端点值和长度。
8 E/ o/ H2 y3 I K9 P, j+ H" Z4 ]先看看数据结构:
8 R% @0 [+ ~, Z% E6 Z- X/ Q6 ^6 Z( i6 X* z- P. c3 u/ E6 q. t
df.info()
7 t! D0 s7 M( [( q Q/ ^& K7 y8 [Data columns (total 4 columns):
- {0 K3 g. `. |. f/ n2 W* r r # Column Non-Null Count Dtype
, t$ P3 L1 Z- n6 U--- ------ -------------- -----
. { ~8 }) F0 ^& t6 f3 ? 0 carat 53940 non-null float640 b" s: Z( t3 u6 Z1 i
1 cut 53940 non-null object * Z. a+ P! ]5 _, _4 h
2 clarity 53940 non-null object
1 c3 I0 S2 B( x, {6 B0 q 3 price 53940 non-null int64 8 [: d2 }& D+ ?3 X) |# o) J3 q
dtypes: float64(1), int64(1), object(2)+ I9 H5 ^& G: l* s6 P! z# {
1; Q: _. {+ {- }+ |0 I- V; z
2
1 Y% b, @' w$ D5 `5 K, D3- ?8 w+ J3 I! }! [9 @
4
: T+ k+ ]5 [$ o0 ?( g1 [5 a. f51 V$ ^0 g* l U: Q* ^- B2 i$ ^
6+ t. d$ Y j( C( s/ G. i
7 x. w/ Y( z, Y
81 w! | o: V$ N/ M( h, @: e
9
6 N. F8 R6 i5 h; z: c. y8 G) d比较两种操作的性能
9 J+ d; z% S. ]' c+ D, q4 a9 M%time df.cut.unique()
3 F$ y' n( H' d" } b) C4 |, S$ H4 _3 H& I3 I( g
Wall time: 5.98 ms9 W$ ?' F& ~; E. d; T- \0 i% k
array(['Ideal', 'Premium', 'Good', 'Very Good', 'Fair'], dtype=object)1 h3 J1 P4 [. T9 F6 s- M. s
1
$ d9 ] u2 V% A, Y; k+ B2
+ M" O3 u1 G8 ]6 d6 S; t3
5 T% _9 K! J( M! e4 n% I4
# {" t* T1 D5 s0 D/ b; m7 x6 t$ X%time df.cut.astype('category').unique()* N0 V7 o: _$ c# x. F2 `
+ S8 B0 J) h1 y7 w8 q* H9 c3 n
Wall time: 8.01 ms # 转换类型加统计类别,一共8ms* ?; b3 q* R9 }6 _. k
['Ideal', 'Premium', 'Good', 'Very Good', 'Fair']& b. t7 `3 {0 m" o4 h
Categories (5, object): ['Ideal', 'Premium', 'Good', 'Very Good', 'Fair']
8 |& e7 r. p [) t$ t6 O3 k8 L1& |: T' s0 |0 Q/ r9 ^$ G' m0 s
2" ]2 K; j8 r. l, c/ D( M% o5 O
3
! |$ H7 E. J# u- y, |+ l2 [4
* j# D$ W% Z3 ?2 x/ C7 l2 n5
8 Q5 {" P/ h" Mdf.cut=df.cut.astype('category'): |' w5 }! s. I8 t+ Z
%time df.cut.unique() # 类别属性统计,2ms+ c. ?7 z+ ^+ ]4 E4 C
5 `" ~" ^0 w9 W8 o0 i1 @- @Wall time: 2 ms
, V7 W; w& F- s: p/ e/ _7 O['Ideal', 'Premium', 'Good', 'Very Good', 'Fair']
2 F/ }7 U% f' MCategories (5, object): ['Ideal', 'Premium', 'Good', 'Very Good', 'Fair']! q* \4 _6 e' b) Q( F3 Q4 O2 |
1! z8 F+ {" ]3 n! E
2
( ^( o5 i3 z6 h4 g/ y! y0 d6 @3
& r% ?7 W2 j; X6 x& }+ ^4
8 G: ^7 E, p# `) e1 J. j1 H57 T, k) d- U0 _) N& O
6
3 x ?, V, x7 S( N$ M对切割质量按照由好到次的顺序排序,相同切割质量的钻石,按照纯净度进行由次到好的排序。4 j3 T' c2 u! \6 @- P( z$ ?
ls_cut=['Fair', 'Good', 'Very Good', 'Premium', 'Ideal']
. z' o$ ]# ^( @; H" X0 Rls_clarity=['I1','SI2', 'SI1', 'VS2', 'VS1', 'VVS2', 'VVS1', 'IF']/ d8 F8 k0 L0 Y+ |! |7 \/ x# R3 W3 W
df.cut=df.cut.astype('category').cat.reorder_categories(ls_cut,ordered=True) # 转换后还是得进行替换2 s( |! ~, {" ^' T L
df.clarity=df.clarity.astype('category').cat.reorder_categories(ls_clarity,ordered=True)
# n- I j9 r6 h3 N2 }% x+ h& W5 P, p: l' p( Q% N: x
df.sort_values(['cut','clarity'],ascending=[False,True]).head(3)
9 c% G/ ^$ U, |) X8 k: I- [! F: |+ {" O2 F% A
carat cut clarity price
+ j2 F. U' n/ z: G8 W* C315 0.96 Ideal I1 28016 N. U G- K* {4 V5 L7 B
535 0.96 Ideal I1 28268 f. q& ~" o1 M; w
551 0.97 Ideal I1 2830, B: s# B5 r* D* E. ]; \4 }
1
) T$ F* k0 `; R; o' G+ U% V0 D. x4 \2
# R# Z" F! O9 W9 m0 j3 z/ {, `( F3
# R& P& X4 X! w9 w9 \: w+ |4
/ x" K6 L( J9 J7 I0 W5+ q* M( C, P# R
6, \2 j5 a. M2 J( d" t$ P. a6 T
7. G7 r! b3 s% ?. o0 o$ [
8
% Z7 a/ E. r" z' X4 A9
% ] G" D6 \) E10
3 s+ |3 w3 @) ~5 N- N( R11
4 Q I- s7 X: |7 W Q+ W4 z$ N分别采用两种不同的方法,把 cut, clarity 这两列按照 由好到次 的顺序,映射到从0到n-1的整数,其中n表示类别的个数。" l, e5 K+ o+ n4 a1 R
# 第一种是将类别重命名为整数9 c" _6 T. }. a" P/ }: s
dict1=dict(zip(ls_cut,[x for x in range (4,-1,-1)]))
3 C/ G% P& y0 v( B, `dict2=dict(zip(ls_clarity,[x for x in range (7,-1,-1)]))
+ Z. p9 s# b- }2 t) o; |- c
/ |4 ]6 q3 s" n0 N4 \df.cut=df.cut.cat.rename_categories(dict1)
. \ g8 c U% [: O7 \0 ?" idf.clarity=df.clarity.cat.rename_categories(dict2)
# h! o* ~+ k4 @2 S. |df.head(3)
8 p) u3 c& D& T6 B7 v% Q# |( z# G2 U$ ~& T4 i- v8 @9 G, P- e
carat cut clarity price
4 m. l: _4 |0 j% r3 ?% O0 0.23 0 6 326' u" D& A& h; q# k
1 0.21 1 5 326
4 k/ m( z6 e0 E( X% m) |2 0.23 3 3 327
8 [& t! Z3 i+ J1. k( B0 }# l( `- E. [2 T
2
" `9 R" g1 c4 Y3 X- c# ^39 r: V* B$ M9 k5 p; B: J" t, J
4
6 {0 w' ?) ^5 S0 m& G/ K- @8 \5/ E$ Q- k( ?: w, P
6
' ^6 O- u3 p4 c/ I7. U% N( z& A, d
8' x) q4 k, _" x/ i [" N8 V
9
4 o, l4 k. o% D0 B7 H' h4 B U104 v2 R) ~( j8 [! x' W# y, Q) D
11: Y+ }9 t/ @0 M
12
3 s' Z1 h4 @$ F3 Y& r2 E; ?; b* {# 第二种应该是报错object属性,然后直接进行替换
6 y0 s: }5 `! n% P. g) jdf = pd.read_csv('data/diamonds.csv')
2 \& q$ a' Z& u' ~* ]for i,j in enumerate(ls_cut[::-1]):3 v/ E+ w: q) j
df.loc[df.cut==j,'cut']=i
" \; D# P( a8 W. {( {, e* I( ]. a$ z. r/ f
for k,l in enumerate(ls_clarity[::-1]):
a" u8 P; a; g, A( w; e df.loc[df.clarity==l,'clarity']=k
; F( c+ I% A# d6 f, ldf.head(3)
0 b3 C4 l, e% Y( m8 }2 G) o) W2 O8 [6 A
carat cut clarity price+ z( W/ @9 p9 u5 Q2 \; K5 s2 k
0 0.23 0 6 326+ Z5 {3 b* J; ]* l
1 0.21 1 5 326' N4 G7 f( H/ E ~$ e: q
2 0.23 3 3 327
' z o: K6 E8 \% K6 }. Z0 t1
, x/ p" E! I2 n6 f) b7 u/ P% }2
, ?/ a$ Y3 ^, [( I5 B1 D3
9 V8 U$ Z) e5 B% y+ ?; T: s3 \4
( W' V0 y( R* U$ v& L+ X; G52 [+ o. f: q: J' `
6
( R% j9 S/ i6 A3 l7
" j, q5 R9 t! j) Y& F- ]/ \, w80 I @( n# G4 p, T3 `
9; S; \& B3 b% Y! f. G
109 n' y6 E9 m& P$ F9 t9 u
11
6 ~+ ^0 u5 o/ p6 c/ F9 c7 i p. i12
8 X F3 {" D8 \- O' Z( u13* G7 s3 W" _; N6 u1 w9 C
对每克拉的价格分别按照分位数(q=[0.2, 0.4, 0.6, 0.8])与[1000, 3500, 5500, 18000]割点进行分箱得到五个类别Very Low, Low, Mid, High, Very High,并把按这两种分箱方法得到的category序列依次添加到原表中。, I$ V3 v' R, B) E9 f8 {/ T8 [- n
# retbins=True返回的是元组,第一个才是要的序列,第二个元素是分割点
7 D' g2 h* ~" `( l# navg=df.price/df.carat
6 ^9 z6 f5 x$ a) `( `# T0 M3 B: ^1 G" {8 ?7 `
df['price_quantile']=pd.qcut(avg, q=[0,0.2,0.4,0.6,0.8,1],
4 c: n: m9 v' \1 V labels=['Very Low', 'Low', 'Mid', 'High', 'Very High'],retbins=True)[0]' r# _$ J N% f4 L/ \
- O7 X# T0 c5 L `5 i( M8 C, Ldf['price_list']=pd.cut(avg, bins=[-np.infty,1000, 3500, 5500, 18000,np.infty],1 v6 o- F! x; k9 L& x$ M
labels=['Very Low', 'Low', 'Mid', 'High', 'Very High'],retbins=True)[0]
! F/ p6 {3 Z8 G" `6 \ ]" Z! Cdf.head()
- H: m9 B1 u* x, F8 D' K o J% F9 d9 j# S# O0 V) A ]- [ u
carat cut clarity price price_quantile price_list
3 R2 Y- B1 Z# V E$ ^0 0.23 0 6 326 Very Low Low: [4 q# i7 V/ d; z* c/ z1 h
1 0.21 1 5 326 Very Low Low
) p( q! t, Y6 O0 o8 ^* E d2 0.23 3 3 327 Very Low Low
. Z; i+ ?/ N' R! u3 0.29 1 4 334 Very Low Low9 l# D. q5 x% `# J' G+ {+ S+ w9 ^
4 0.31 3 6 335 Very Low Low 4 z& E1 q6 m( `7 Z
& G* \; k. i t. z) U/ v15 t; R. w! ~0 ?2 G1 Q5 s5 H
2
% B3 n: |/ d, |1 U0 [3
( c a/ J! ~7 }: X, J( Y4
$ J8 E6 ]9 T' R5: T1 j2 [8 `/ w* n" h
63 k( w7 I4 u4 n0 Z% E
7% O3 S" _; n! P) V, N1 X
8- Q8 x- W$ r' V ~2 \
94 l0 b/ T+ s* a8 ^7 _
10* a& m# v* v, K; A6 D" a
11. E4 c2 J, q& H! Q
12
' |7 h* V8 p8 I3 X$ j3 X( I137 }% Z* ~& x& A: r3 r" n
14
Z, \1 ?' G/ B( Q' b y* U/ H# w154 ^8 v* r* d! Z7 H# M4 Z/ d% D
160 E" H- H" x1 {' j+ t/ K- l+ h
分割点分别是:5 {& h) a+ T! \5 B
0 L4 ]- c4 S& T4 n2 Y# n
array([ 1051.16 , 2295. , 3073.29, 4031.68, 5456.34, 17828.84])8 R# T6 N( m* R1 d# r
array([ -inf, 1000., 3500., 5500., 18000., inf])
9 w+ _! s$ W0 z. X- a18 Q$ V9 B( k' } V% e [) u% q
2
$ o2 C' ]- @6 `; Q4 u第4问中按照整数分箱得到的序列中,是否出现了所有的类别?如果存在没有出现的类别请把该类别删除。0 D& V9 S, K B/ i, _6 f: P
df['price_list'].cat.categories # 原先设定的类别数) Q5 B, d1 W$ B1 C
Index(['Very Low', 'Low', 'Mid', 'High', 'Very High'], dtype='object')
9 W2 u0 i( ?" p1 L1 A" Z3 e
) ?4 [; k: I4 e9 c8 @0 `" a0 udf['price_list'].cat.remove_unused_categories().cat.categories # 移除未出现的类别- f. O) E! H3 x5 e
Index(['Low', 'Mid', 'High'], dtype='object') # 首尾两个类别未出现0 F4 h5 Q5 b$ P
1
$ ?/ Z: D4 d9 V9 K- C& A2 V2
! _9 B: h) z: T5 R) A6 Z# x2 X: j3, l9 }2 q6 [0 C
4
3 M6 H7 w% l" q% M! m7 Q5( ^6 b( e4 B% T2 s; b
avg.sort_values() # 可见首尾区间确实是没有的8 D- [* R7 \' H6 S4 b7 E; O
31962 1051.162791( o2 i& R8 z' V6 L6 l
15 1078.125000
) N V' X# [0 b) j2 z6 M ^% `* f4 1080.6451613 E3 @$ p. p n
28285 1109.090909+ G3 v8 ~/ F! H3 J* A" P
13 1109.677419, P; a* N+ N) l+ G3 Z: \
...
4 z! g2 H$ T, a I0 N5 L6 f ~3 y26998 16764.705882
6 n( W+ A6 W( |/ ~! [4 D* u: t1 E" t27457 16928.971963! Q* H4 X/ d; s' F0 v
27226 17077.6699031 n/ M' p: f* M$ G( w
27530 17083.1775708 c( R) X U* j G
27635 17828.846154
, V8 Z; ?, a) K9 G0 z+ V11 z3 V, R! X( t# Q6 ?; a
24 i2 w) X, M+ O6 t: _2 G z- ]# Z
3
2 J/ {6 |* b. E0 \" g4; a4 z) A& @8 R: R; S4 A
5
9 [$ a' ?' N- x# U& W. t6( e3 T" G) R5 ?0 b
7; Q5 k$ N9 \1 [+ s Z
8
6 b: o2 e H7 L. R- _9# ?- q. ] _" o9 n$ z
10
3 n& F3 J. V/ e3 ]; e2 D117 S% |0 K* ` q$ h$ c
120 N s4 x# w. {
对第4问中按照分位数分箱得到的序列,求每个样本对应所在区间的左右端点值和长度。( B4 u$ }' H, |. E# |* V3 J
# 分割时区间不能有命名,否则字符串传入错误。9 R& C; u. y+ M
id_interval=pd.IntervalIndex(
/ ^' j. j9 ]) }- [2 p& v- f8 Q6 i pd.qcut(avg, q=[0,0.2,0.4,0.6,0.8,1],retbins=True)[0]- I# h" o+ x* F8 _8 E ^
)
x" N' a% l0 Z7 P sid_interval.left
* d2 k: n P$ I! ~+ f8 Z6 Gid_interval.right2 Q: O* B7 Y7 G: z, \
id_interval.length
O9 r8 W4 n, M# C m1
6 @! A" z, [+ X& n9 {" [- n% t21 H2 B6 m- x, G! P
31 r6 @4 T: z) b6 r8 ?
4
& r, J4 O9 ?% _50 O) n& a4 x' x" p! h* m: n# |9 M
6
$ D: {: l4 x9 P0 y( s8 B, p1 w0 {7* G. t- z! f/ p5 \
第十章 时序数据0 x5 M1 V2 }+ g# D% A4 n5 i
import numpy as np C- V/ ~% n8 X1 R' G. {7 \2 Z
import pandas as pd
" h Y9 U7 o2 K13 j5 z0 N! T! y1 X; T2 ]2 K
2
: \ i5 _, S; _( N2 m
4 o; g& l1 N d% M3 s6 U/ Q+ F% w0 R& o) J" Q
10.1 时序中的基本对象, x* W" q0 T) N0 @6 G& W7 a
时间序列的概念在日常生活中十分常见,但对于一个具体的时序事件而言,可以从多个时间对象的角度来描述。例如2020年9月7日周一早上8点整需要到教室上课,这个课会在当天早上10点结束,其中包含了哪些时间概念?6 D& [1 A$ ~% W% @1 A8 g( {
/ ]7 ^0 `1 Y/ E会出现时间戳(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的简写。; h; ~) {$ c$ K* G$ L' ]
+ v1 `8 N: D5 V2 R$ Z% u# J" X; X会出现时间差(Time deltas)的概念,即上课需要的时间,两个Timestamp做差就得到了时间差,pandas中利用Timedelta来表示。类似的,一系列的时间差就组成了TimedeltaIndex, 而将它放到Series中后,Series的类型就变为了timedelta64[ns]。7 P# }% m6 T1 P
( Q' t2 T3 G1 |) P; \) Q5 V" G) ^* M会出现时间段(Time spans)的概念,即在8点到10点这个区间都会持续地在上课,在pandas利用Period来表示。类似的,一系列的时间段就组成了PeriodIndex, 而将它放到Series中后,Series的类型就变为了Period。( X0 N ]7 u9 Y" V' L
$ y4 t/ d) O8 m- r2 R! k会出现日期偏置(Date offsets)的概念,假设你只知道9月的第一个周一早上8点要去上课,但不知道具体的日期,那么就需要一个类型来处理此类需求。再例如,想要知道2020年9月7日后的第30个工作日是哪一天,那么时间差就解决不了你的问题,从而pandas中的DateOffset就出现了。同时,pandas中没有为一列时间偏置专门设计存储类型,理由也很简单,因为需求比较奇怪,一般来说我们只需要对一批时间特征做一个统一的特殊日期偏置。
3 M. l* o' {& V+ t
3 u! S& y% Y" b1 d" x; T 通过这个简单的例子,就能够容易地总结出官方文档中的这个表格:
; b9 ?% I9 y8 X9 @' D; j8 ]2 |3 J0 P: P" @. {# [6 @$ i+ G. z
概念 单元素类型 数组类型 pandas数据类型( j4 Z1 D0 B2 X) M% O
Date times Timestamp DatetimeIndex datetime64[ns]
7 M, @: [, |) \+ K1 xTime deltas Timedelta TimedeltaIndex timedelta64[ns]
8 J/ v' {/ r# z# Y4 k# vTime spans Period PeriodIndex period[freq]
% u! Y9 g" P( x/ QDate offsets DateOffset None None
3 S3 _, y/ a! i: w 由于时间段对象Period/PeriodIndex的使用频率并不高,因此将不进行讲解,而只涉及时间戳序列、时间差序列和日期偏置的相关内容。7 h( _; `. b9 d' B) o
D! [# Q1 g/ E# t: v) g
10.2 时间戳, r) z3 n8 g3 g; j( [! l) q
10.2.1 Timestamp的构造与属性
: s. Y4 F& A3 {5 Z% L2 d+ I单个时间戳的生成利用pd.Timestamp实现,一般而言的常见日期格式都能被成功地转换:
: n- [8 W! z. w8 Z9 p) h5 a6 T# e9 \7 Z" f
ts = pd.Timestamp('2020/1/1')
# [, V+ e& s& B6 j. _% j; ?4 \* w! H& c* o. x5 W
ts
+ K0 ?% t* f) P0 u; Z! T& dOut[4]: Timestamp('2020-01-01 00:00:00'). \( f' R3 P. y
* U; U- I6 p$ L( S& ]
ts = pd.Timestamp('2020-1-1 08:10:30')
( d+ [9 P; u2 ~/ J* y, A( W( t4 q2 ?+ t
ts# T1 J8 e% `0 w6 ~3 U
Out[6]: Timestamp('2020-01-01 08:10:30')
, L% Z5 {6 F3 w6 W! G V. o+ V( x1, s" s+ g# x7 Y4 M' w2 B
2
6 w7 T) z- a+ F' _* M& G3
" i' S n z9 [! p" e! Y5 E! g% ` T `4
9 |. ]* Y q; F$ m+ Y1 m- {5
2 p% @* i2 J% w. L% W/ P60 `% F4 u9 K; D. H6 G
7( {6 P" L# R) g9 E8 |, c
82 J4 l+ U# N$ I4 ~9 T- R/ q+ I
9# ` D5 j5 Y# N' a7 }: K$ @
通过year, month, day, hour, min, second可以获取具体的数值:1 N2 v" q4 i5 W0 H
6 \/ E" s( \ @- E' v. b, J. c
ts.year% R3 F7 I& D/ V' N7 b- q
Out[7]: 2020
( d# M2 M+ `3 c! X7 [
' r( S; E6 Y/ V2 n0 Q7 _! d. kts.month
+ L: K q" X1 i' I$ ?; {" C! _Out[8]: 1
' f# I% W! |- Q" `- q' n; z" K% n; X' V0 G
ts.day
* m* d- T% j' P+ A& M" ?Out[9]: 1
" r- v( G; |( \0 L& W8 P7 o" }5 \( j# I/ k( X0 Z
ts.hour: `3 [+ N2 D: p
Out[10]: 8- h4 V' J4 L3 k
' I! D6 w+ u7 `8 Z0 I, C& g. X8 \ts.minute
% j& k9 i" y% ROut[11]: 105 ^$ V) ` ^/ ^4 `9 w
7 m% H8 L I7 w, m& \! H' C
ts.second; C4 b/ G9 O' E# _
Out[12]: 30' n) H* D: d5 K5 L5 J, z
1 \+ @: P- r8 w# \$ V$ Y1 e+ a0 ?9 F/ X5 ?% B
2
& J% H+ g( U( T4 H3
9 a i/ l9 d1 U2 f; x) _$ b4. b H/ D: {/ v" }' K+ m7 C0 p3 b
5; V0 j6 V( i" @4 J
6
" E o+ X8 C' o2 z; s7( y5 S9 ~1 W( ?/ U* y% F
8/ f1 a) J# k# P! s; _
9$ t/ d1 k0 f/ |$ H G3 D
10
/ Z1 ]9 G W- M$ x, D11+ L, W9 g; |4 h0 r8 A1 T, |
12
% s% h. t% N$ b* q13
0 _/ F3 L# b+ Q14
3 m/ n3 `/ q: [- T15; M6 T4 G5 i& y8 v
167 I* W- P# n' `# j
17
% f# C' T" A! V- u0 o: ~# 获取当前时间
$ a6 k5 w' M8 W( r& ]9 H2 tnow=pd.Timestamp.now()* Q4 S! }* U/ V( `1 H3 x
1
% s: x% w3 {* l+ D26 o% n! ?! g& }: X( K9 z- X5 d
在pandas中,时间戳的最小精度为纳秒ns,由于使用了64位存储,可以表示的时间范围大约可以如下计算:0 J: L5 z+ f% t! @% _7 W
T i m e R a n g e = 2 64 1 0 9 × 60 × 60 × 24 × 365 ≈ 585 ( Y e a r s ) \rm Time\,Range = \frac{2^{64}}{10^9\times 60\times 60\times 24\times 365} \approx 585 (Years)
( C+ j3 U) P; C2 U: K* ETimeRange=
# a' q, u5 X3 U$ G; U5 _" @10
5 a' i$ }" \/ U& [92 G* K) t0 S }, W5 d* t: V% e
×60×60×24×365/ i! l9 y/ {# x
2 : l& c) R3 S. Q* W6 O
64
+ m, r5 ^) ^2 K( K+ S3 A- Q
! E# h( a4 B4 b6 b3 B0 {, V: K" M/ q7 I9 K* e0 h9 B
≈585(Years)
4 S- J0 H& t! i: ]4 A2 O- A! F& L- i8 T$ u
通过pd.Timestamp.max和pd.Timestamp.min可以获取时间戳表示的范围,可以看到确实表示的区间年数大小正如上述计算结果:( }2 w& T7 T/ W& u
( d' n% [; B) z/ z% X) a) Y# W5 {
pd.Timestamp.max
& u4 i# X$ S; W+ e7 V4 N j! KOut[13]: Timestamp('2262-04-11 23:47:16.854775807')
. f+ P# y) ~6 H2 K- e7 c8 T& L/ C' t( o+ `% [% Z
pd.Timestamp.min+ i8 V/ J6 a F/ a4 V! i( o- [0 J" l
Out[14]: Timestamp('1677-09-21 00:12:43.145225')4 t$ y+ E9 W8 x# Q7 e8 \' |0 k
2 o4 i% O( K7 j$ |2 V" Vpd.Timestamp.max.year - pd.Timestamp.min.year) h% z/ y, F7 ]& i3 F
Out[15]: 585! w1 D( t- L9 i. T5 E3 y8 [9 o
1
T+ v( @' f+ a5 b! p, q! \" `24 q1 J; S+ }4 @; h
39 R6 Z3 i, K4 \$ G6 r; V
4
* l5 w& h% }; Q: ~0 v# P5 p+ b H5
+ x2 I# X, Z: C$ b i, W" F6
6 `( Y6 J- Q) j1 N- ^1 l4 L4 X1 A75 U5 t; U. v) k9 Q, ~
8" k7 C1 ~! N2 t# N
10.2.2 Datetime序列的生成( M$ Q& w- i# b. T; N6 T
pandas.to_datetime(arg, errors='raise', dayfirst=False, yearfirst=False, utc=None, format=None,. o( l, O1 @! }$ B. [' K+ s2 @
exact=True, unit=None, infer_datetime_format=False, origin='unix', cache=True)
9 I6 {3 [& t; V9 g( }1! f% f4 s, {" L: T) M2 J% _; n
25 m M, j6 ~. G5 S9 k( J6 z9 n
pandas.to_datetime将arg转换为日期时间。
1 P3 ]6 t4 B, q1 z6 c
: U: g; c# U6 u+ F. qarg:可以是argint、float、str、datetime、list、tuple、一维数组、Series、DataFrame/dict-like等要转换为日期时间的对象。如果提供了 DataFrame,则该方法至少需要以下列:“年”、“月”、“日”。
% y( P: f/ ~* Derrors:
5 ^5 T. `% ?. B. L" ^# V- ‘raise’:默认值,无效解析将引发异常
7 M3 i# l6 v2 b- ‘raise’:无效解析将返回输入
7 u/ l" t' D# j! x- ‘coerce’:无效解析将被设置为NaT
, n3 h3 v8 ?" P5 K5 Fdayfirst:如果 arg 是 str 或类似列表时使用,表示日期解析顺序,默认为 False。如果为 True,则首先解析日期,例如“10/11/12”被解析为 2012-11-10。如果无法根据给定的 dayfirst 选项解析分隔日期字符串,会显示警告。
6 h) B# s5 G6 h& d1 c# M7 X4 T# xyearfirst:如果 arg 是 str 或类似列表时使用,表示日期解析顺序,默认为 False。如果为 True,则首先解析年份,例如“10/11/12”被解析为2010-11-12。无法正确解析时会显示警告。(如果 dayfirst 和 yearfirst 都为 True,则 yearfirst 优先(与 dateutil 相同)。)
" ~4 i# ~. A" F9 d: B, O2 Y+ a0 Nutcbool:默认None,控制时区相关的解析、本地化和转换。请参阅:pandas 有关时区转换和本地化的一般文档9 }! E% Y! f* T5 w
format:str格式,默认None。时间戳的格式不满足转换时,可以强制使用format进行匹配。
( R1 c5 c" b7 }: j7 }5 ^- punitstr:默认“ns”。它是arg (D,s,ms,us,ns) 的表示单位,可以是整数或浮点数。这将基于原点。例如,使用 unit=‘ms’ 和 origin=‘unix’ (默认值),这将计算到 unix 开始的毫秒数。# v- R$ U0 @8 d. Q$ l5 G" {
to_datetime能够把一列时间戳格式的对象转换成为datetime64[ns]类型的时间序列:0 m3 N' G2 H! X6 [. ~ b C" {* B8 X
pd.to_datetime(['2020-1-1', '2020-1-3', '2020-1-6'])0 U% V/ L# w1 `3 Z2 t% ^
. W) {! H" V& K4 x
DatetimeIndex(['2020-01-01', '2020-01-03', '2020-01-06'], dtype='datetime64[ns]', freq=None) o: ~( a5 D: r- m; @* w& b
1
; L- d! k6 l8 L- L/ i0 Q2
) u/ q" J0 E. m! u$ V6 S7 i0 \6 V3
4 @; x; [" s8 {9 v! J3 H在极少数情况,时间戳的格式不满足转换时,可以强制使用format进行匹配:! O: Q9 J4 V2 J0 [* u* M6 @ s: E
, c( B! E5 H! y& z3 P$ [- }+ R0 q
temp = pd.to_datetime(['2020\\1\\1','2020\\1\\3'],format='%Y\\%m\\%d')
, x' x, V' k9 P, u' a0 b% }5 Htemp. P6 o- i% A; o0 ^, J
- B: I" n. t" U5 l6 NDatetimeIndex(['2020-01-01', '2020-01-03'], dtype='datetime64[ns]', freq=None)
& ~0 f8 `$ S: a2 T9 Z1
4 I" A, I _8 G* g2 h" ?% b7 ]2/ l% W7 |1 N0 J& ~" b
3
3 ?, ?/ H; j( W( z+ c4 o+ N4
6 }, |5 k0 Q" o" _( U 注意上面由于传入的是列表,而非pandas内部的Series,因此返回的是DatetimeIndex,如果想要转为datetime64[ns]的序列,需要显式用Series转化:
: |0 d5 f; ~ C- n: U- A& w' r' ]; f' a; }3 f+ e
pd.Series(temp).head()% h# x4 F7 m" t, K" n) h) F; h
) A5 i# z- s" k3 { l3 g( z
0 2020-01-017 C6 ?7 _/ m/ a; ~0 H [! N; M
1 2020-01-03/ b* k7 O& P/ d1 }- Q' `
dtype: datetime64[ns]
& |; i# y9 i3 F0 t" v7 R+ l. t1
! {) s! k8 e2 M/ V3 h2
& v) M A) n; {0 U8 y6 r3
- z/ D( ]% b$ y/ f46 @! l' I N& f: a3 N" m% Q2 U
5
3 a- g6 N$ b9 g. h% S下面的序列本身就是Series,所以不需要再转化。) ?. F" G$ C2 X0 }6 G' z/ P
8 p: o/ ~# c" C1 Hdf = pd.read_csv('../data/learn_pandas.csv')
& i1 M7 j H5 O& J: ]* `' {s = pd.to_datetime(df.Test_Date)
7 D V* z M; m& B T# Vs.head()
" C" `+ J# y( f% P
5 z: i3 e+ D l9 w' w+ `- \0 2019-10-05* v2 Q3 U- r' y$ h
1 2019-09-04
' [6 V9 G, l7 ~' x/ B6 _& y' n2 2019-09-12
8 x6 L# v+ n; i2 ^ Y) o& x: |$ X3 2020-01-03, K0 h! c. s2 {/ d7 g
4 2019-11-06
- U8 }$ H4 O6 I0 bName: Test_Date, dtype: datetime64[ns]. c; J( f% n4 `+ N3 x
1' c3 j, w& x3 j- c' Q8 w, z
25 ` V/ t; F0 m$ K4 _1 ^! R
3
$ E3 S- c% m s4
3 z5 U' Q$ [- b! B5
5 K+ T! Q0 A3 G3 g/ {( x6
+ B% a s5 ]+ g* U& x1 Y7
8 J5 V' A4 r& Y8
5 U7 z1 Q/ |0 a) `6 ^- I9
. z8 @2 \% t/ ~9 |10
4 X% V% x5 w4 z/ N/ t( H8 n8 `把表的多列时间属性拼接转为时间序列的to_datetime,此时的列名必须和以下给定的时间关键词列名一致:
! S# ~- U; g9 Ndf_date_cols = pd.DataFrame({'year': [2020, 2020],
$ P4 _1 _- \0 W8 Q+ D2 o- |8 a3 I$ X2 t 'month': [1, 1],
# B! d; r. j$ {9 m; U; Y 'day': [1, 2],2 T, ^9 Y: e! o+ z* g7 _
'hour': [10, 20],% Q7 l" U) B0 V7 K7 t
'minute': [30, 50],$ w3 j9 E5 C. N8 ~
'second': [20, 40]})2 K/ d8 k( D' N- p; [
pd.to_datetime(df_date_cols)- l j8 ]% P3 u. L+ u( F
0 l* l# V( U% V! k4 {, c0 2020-01-01 10:30:20
$ ]6 o. p1 ]* @" y: Z1 2020-01-02 20:50:40
" \# }) K2 M! s8 L t i+ g; W) Tdtype: datetime64[ns]
+ @# t# A' B; m/ d1
; c5 c) J: f+ @2
8 M2 l% T1 E$ ~- L; T3* S8 L1 I7 L6 e! O* p
40 m0 u% x v" ^0 z1 }
5: R; `% u6 Y3 `
6
, q2 I& z2 b' ~5 ^' o/ K7
7 l! A' B* N$ ` `! B8 M y) B: F8
7 ?$ \ v; G0 D! V) \1 b. u% e0 A @9
& G/ x# l$ ^+ D10+ t4 v% j I" V# H$ c9 I
11, v0 [8 N) T7 E( w+ ~3 q X
date_range是一种生成连续间隔时间的一种方法,其重要的参数为start, end, freq, periods,它们分别表示开始时间,结束时间,时间间隔,时间戳个数。其中,四个中的三个参数决定了,那么剩下的一个就随之确定了。这里要注意,开始或结束日期如果作为端点则它会被包含:8 \5 X' \+ d8 X7 y
pd.date_range('2020-1-1','2020-1-21', freq='10D') # 包含
' K R( }3 l; Y( vOut[25]: DatetimeIndex(['2020-01-01', '2020-01-11', '2020-01-21'], dtype='datetime64[ns]', freq='10D')
( {/ p' n f N( u6 d0 U9 x3 R! ]( L9 k; V S& X+ q
pd.date_range('2020-1-1','2020-2-28', freq='10D')( m0 h! d2 O" Z4 H+ u
Out[26]:
7 m4 I% U" D5 M/ P4 q/ k! U5 nDatetimeIndex(['2020-01-01', '2020-01-11', '2020-01-21', '2020-01-31',) V O g5 j- ?1 ]/ t
'2020-02-10', '2020-02-20'],
U. v2 _4 s" R! H dtype='datetime64[ns]', freq='10D')1 q9 H! @$ Y8 ^6 f, k
- u& x, Y* w$ g9 Q
pd.date_range('2020-1-1',( ]5 N% p& t* B% O; k
'2020-2-28', periods=6) # 由于结束日期无法取到,freq不为10天5 ]/ U5 [6 j) g7 Q/ h" H
. Y1 O4 C! c" O# gOut[27]:
& z, x) Q( c' Y; v) [$ x* T. |DatetimeIndex(['2020-01-01 00:00:00', '2020-01-12 14:24:00',
+ G0 P; g/ t+ t/ P9 S" I '2020-01-24 04:48:00', '2020-02-04 19:12:00',
2 s7 o3 y+ C( J2 p3 q0 V9 k0 R '2020-02-16 09:36:00', '2020-02-28 00:00:00'],& L) }; w; \& v5 z/ m4 K
dtype='datetime64[ns]', freq=None)
3 @2 B+ r x1 S) H6 q0 L
* L b# l8 @1 f3 E1
/ l$ {$ x* `& _2# C, y2 M$ f+ E. o. |3 f& g( L
3& A( Y% F% a1 T1 w) |
43 H) P) Y( G" M7 T4 P
5. Q; ]7 W* a& l4 y6 a% X
6$ i7 c8 H+ q: x- ~
7
9 f$ X' P! x' {7 u8
; |; g" s6 e; x2 ~7 \# u1 e90 @# S* C0 K& o2 D& _% o
10( D& L0 c& ]& C+ q. g
11
/ ^. @' y% [( z, \: Z% w: E/ X12
* z6 L- u U8 e) ~, s" B1 Q13
7 F. A9 G+ d$ @5 n: y14
V2 c. u( T. F8 f9 B15
: i Y" F: F. x! e. ]" |16$ L. ~ z( K( U- O7 b2 c
17
7 p: `) L) ^2 c% A* S- C这里的freq参数与DateOffset对象紧密相关,将在第四节介绍其具体的用法。
/ [1 Y- W; Y; I( q' H$ X
# [8 O# y' Y4 U【练一练】) z# H$ p4 r) s& a
Timestamp上定义了一个value属性,其返回的整数值代表了从1970年1月1日零点到给定时间戳相差的纳秒数,请利用这个属性构造一个随机生成给定日期区间内日期序列的函数。* _# ~! t) v; T4 b
, O+ c! z- y0 S) n1 m
ls=['2020-01-01','2020-02-20']" I* y% ]" r0 ^, H x8 N
def dates(ls,n):
* x$ J: X2 p9 G8 v3 @% X9 h3 S min=pd.Timestamp(ls[0]).value/10**9- f4 C6 k2 ? P" {% ~
max=pd.Timestamp(ls[1]).value/10**9. p; \& P( K- r" L- C
times=np.random.randint(min,max+1,n); C$ b! \# H4 [3 s) B
return pd.to_datetime(times,unit='s')
! M& }# ]1 p3 fdates(ls,10) 7 h$ T9 C9 v2 T
6 s3 t- [ m8 s) a0 [0 CDatetimeIndex(['2020-02-16 09:25:30', '2020-01-29 07:00:04',; \' D7 D8 |, S! p( b k' q# P
'2020-01-21 12:26:02', '2020-02-08 20:34:08',- V/ _. A; l' P: j. @! |
'2020-02-15 00:18:33', '2020-02-11 02:18:07',' D, I) X/ U9 l9 `# `& t: W- N
'2020-01-12 21:48:59', '2020-01-12 00:39:24',
5 T0 {0 S' v+ W& S2 G* e '2020-02-14 20:55:20', '2020-01-26 15:44:13'],
. k6 O2 w' F4 g, z dtype='datetime64[ns]', freq=None)
$ a8 B% ^7 c# p3 F0 G! a) E; t' f1
4 P9 b- q6 Y9 I/ _1 c- M2
' {2 q2 ~. b, \9 ]3& j1 S# C: V" Q( x1 R
45 z8 s6 g) R5 q% M) R L
57 s% Q# V9 x% X$ O3 f
6- H+ t8 c2 I0 ] x/ r
7
3 F+ F; ~" U2 D8 ^ z4 x8
! n! ~0 u4 A6 s- L. X; ?9
+ g7 ~: ]4 v; {10
2 x: F) `, @5 K11* i) ?1 o. R1 c
12, k$ | ~7 V! C6 |4 @) Y) d
13. }2 Z" w+ S+ u" P6 Q/ d
14
5 `- [. x2 o1 M% a( kasfreq:改变序列采样频率的方法,能够根据给定的freq对序列进行类似于reindex的操作:
2 u2 q7 Q* n% M; ~8 I3 Js = pd.Series(np.random.rand(5),
( r+ L6 C9 Y4 B+ o, Y4 M! ? index=pd.to_datetime([
6 P: [ I! v3 E6 Y- b; e' [# G& x3 g '2020-1-%d'%i for i in range(1,10,2)]))4 C# a7 |* V# Y- e5 O
! ]; [1 c) I6 t5 K {, [5 _
9 Y: R3 z$ J' q7 b# M% Fs.head()
7 E9 _- ?5 o7 _Out[29]:
8 c$ X, m, b5 c7 q4 U+ m8 o, m2020-01-01 0.836578/ p6 h8 C M$ N) J* c% q( S
2020-01-03 0.6784195 [2 i9 M% F: X
2020-01-05 0.711897' S! l7 t& M Y5 |2 O |9 f$ E
2020-01-07 0.487429
" e+ F. Y" k' w0 N) h G5 L2020-01-09 0.604705$ G+ m+ {4 d# R2 [
dtype: float64
. |. b! n+ C- e9 B
% I6 N* U) _- E. ts.asfreq('D').head()
8 l: l7 ?' s( P: ?! u% C6 nOut[30]:
4 v8 I3 m* ?( q7 M& N2020-01-01 0.836578# Y7 C9 v( H) _1 x) `
2020-01-02 NaN3 a, d7 P7 R' S5 s. D
2020-01-03 0.678419
1 _1 P7 @/ i* A6 ]$ M. Y! x& j. |2020-01-04 NaN/ f( c: f5 Z4 i& z$ ]& M
2020-01-05 0.711897, |* p& w3 R, {0 R3 ]# z* u, c
Freq: D, dtype: float64
& A7 ~! i4 ]+ X' _3 E
' Q% j) s! w; M6 s) Us.asfreq('12H').head()( k& O. J( D7 W( O1 o" A8 C. ^
Out[31]: ; l5 H" t7 M- |4 }) v
2020-01-01 00:00:00 0.836578" p" U: Y4 I( _( U& H! s$ L
2020-01-01 12:00:00 NaN
/ e M& N( ?7 S; d6 ~0 n2020-01-02 00:00:00 NaN
9 S, p' T2 R- b- @; R2020-01-02 12:00:00 NaN. @# u: d U" L. b9 s3 L) @1 G" Z" N2 v
2020-01-03 00:00:00 0.678419
6 m) i1 c, o3 _" J: S. u4 BFreq: 12H, dtype: float64
" b- a1 q+ P4 @" N
: T/ A: C" T" f, z" w* X1
+ A2 F: k! _* p7 L" ]2
+ o; I+ p* b. J# ]6 Z35 t( l) p+ S# J3 E
4& }: ^, t, t7 J8 {
5+ [& U7 H" q Y0 ? W4 b: I
6$ u9 P1 P: A {7 n, G( h
79 T7 N4 l& l6 J5 p7 b- u Z/ D$ ^
8) x% v. t: G+ B! |, e) E
9
2 i& {. K/ k4 k7 v8 G10; [, E& x/ A" R% X. d3 h! L
11( O/ f& A3 g% J6 _; O& m$ x
12
1 U1 j* A' K. Y, D" y2 w0 o13- I. x* Z4 Y$ `, l$ R
14
2 i! k- n( M T$ a) ~15* M% R' H# e. u2 @: b$ i8 H
16/ ]) [, i, w# ~5 T6 r4 T; f
178 B+ Y% _! r6 o, j# q+ z
18' m+ D* E/ q& K% b/ x& Z0 h
196 G" k) d" j1 B% `# j2 C$ M
20" f& D9 K! V" f) i# x( q
217 ~" @# s3 O4 U$ m; J; p# M" W
22
8 Q: ?! V% ]) d* v6 o- Y4 I9 x23. W$ t, X3 J) @0 r9 g9 y
24, v5 z, ]6 q8 G# {' U! B
25
8 Q7 W y& T/ h: I+ T26: x/ P4 m2 t; m5 |* o" {
27
+ M* v Y' `( s7 G7 Y289 u% n& Z' V" y+ x6 H
29) b3 \* b4 z- u# ] E7 {
30
, b. k8 K8 Y9 _( O9 |. S- V31
' H$ x2 M5 A# ~+ l【NOTE】datetime64[ns] 序列的极值与均值
4 b; f( m: a$ p; I) y 前面提到了datetime64[ns]本质上可以理解为一个整数,即从1970年1月1日零点到给定时间戳相差的纳秒数。所以对于一个datetime64[ns]序列,可以使用max, min, mean,来取得最大时间戳、最小时间戳和“平均”时间戳。
% m2 n# k6 ~% n! N% d6 o. L' [3 K: z# R+ p: O. g5 s( Q
10.2.3 dt对象' p- Y6 ?' s$ d+ J4 Y
如同category, string的序列上定义了cat, str来完成分类数据和文本数据的操作,在时序类型的序列上定义了dt对象来完成许多时间序列的相关操作。这里对于datetime64[ns]类型而言,可以大致分为三类操作:取出时间相关的属性、判断时间戳是否满足条件、取整操作。, t' l+ y: I Z5 |! x
' s, o, Y$ k' M" t' o. |! m
第一类操作的常用属性包括:date, time, year, month, day, hour, minute, second, microsecond, nanosecond, dayofweek, dayofyear, weekofyear, daysinmonth, quarter,其中daysinmonth, quarter分别表示该月一共有几天和季度。
7 D2 w) r+ z ?2 ~) Ws = pd.Series(pd.date_range('2020-1-1','2020-1-3', freq='D'))
! a3 [3 x& E3 T# [7 E$ H
U- X; B! J8 i0 b( ds.dt.date
' U: z8 F0 v9 I6 _. q& qOut[33]:
; l7 [; }( g6 L0 2020-01-01
' s2 z M4 @) m7 v6 y+ R: m1 2020-01-02
2 ^3 O. w* C" }9 i( S# H5 y2 2020-01-03
+ [! y3 p, H0 P9 b6 Tdtype: object+ K& l, ?1 |# B% }* x t* J
7 F" L3 M# h9 K8 O
s.dt.time
$ c9 a0 M6 ?3 ]8 a. n3 }Out[34]:
7 d6 d" q+ k9 e$ ^2 S0 00:00:003 ?+ `5 L7 ~' C/ v5 I4 Z0 L. a
1 00:00:00. c" `: y4 u( K0 D
2 00:00:00
" \2 R* j- r; _7 r% p$ mdtype: object0 F, g1 V$ G$ N; P R2 h& J, a
, W- E+ _% R0 c' Y- e: ]
s.dt.day
$ D0 `1 {2 K2 i7 k. s8 o/ K; lOut[35]: 9 l5 O7 a+ _. s( i0 J7 i
0 1
+ l, A0 L! {4 G- T! i* s7 Y. t1 2/ M9 B5 a/ E. t' [3 H& | q1 I
2 3* w5 B( O% H( {7 H( D& o
dtype: int64
% j J1 S6 k$ \* r6 v& P. O% t+ x) [6 ^' t9 u" y7 Y8 L
s.dt.daysinmonth
2 p" A" R( R3 W1 dOut[36]:
, M/ j- x9 A) x9 G0 31* }2 J% d% R3 p
1 31
0 W7 H9 V* `. C/ }, f1 Z2 31
# y- \: n' u# [, N, v' k0 y" F* fdtype: int64
; ]/ B/ t Y5 m0 l* B8 z7 F9 r" c8 M. d0 H& F1 t1 o
1
( J1 c! `3 h- m" A {2
, a# S1 b; v7 C$ h# o* |3
" H& I' Y9 o" @9 F' K! e4* C+ L1 ?1 g! s' ^& W7 R6 y
54 L. R5 H) p7 B, f7 a9 W, `7 }
6
* R$ M" b* e( f, d# S7" J8 w7 u/ N+ {& h% f# x
8
* D; R* W" A W. T8 w: t' P91 ]/ m4 {: b4 G" o+ {/ i
10
1 p7 ?- }' c1 O11
4 t; U9 {( x# R- ^+ g12- M8 g/ ?( F- b
13
; \! Q$ G5 ]3 a% c& ^ C1 `8 B14- Z; q0 H' a8 [! K
155 h! z# o2 V7 C9 G3 T5 a4 T: e _
168 b5 m7 p& _+ g! u9 ?7 c
17
+ L' R. O$ e0 n0 _ x) c18
) C+ _+ g) }6 n5 U: Z19
, E: g! K3 w# |208 P2 H- F/ }3 g6 B' j
21
7 s# [, N- Y H8 K7 ?22
# n" Y' x, M$ k23- {1 N& j, P+ ?
24; v# i! D% m: p, ]2 `) ~7 o
253 R8 |( V6 `' v6 @4 G: P q
26
9 v7 u- \2 G, `% q N1 u27% s4 U3 D$ M' o, F) C
28$ e2 `; N3 A' }% J1 l4 C
29+ [: f+ ]9 c4 S& h6 m. z
在这些属性中,经常使用的是dayofweek,它返回了周中的星期情况,周一为0、周二为1,以此类推。此外,还可以通过month_name, day_name返回英文的月名和星期名,注意它们是方法而不是属性:# [+ Y, T$ J; g- s" v
1 M0 U; C1 E. o4 P; vs.dt.dayofweek
- \( N+ \) N$ w1 g9 j9 p( O" u. A8 F% POut[37]:
: H, w p3 Q1 T9 O0 2
' n* u0 m j* V) ?1 3* f9 |* o [) n! e" P$ N
2 4
( L1 f/ V7 e3 b7 T$ Bdtype: int643 c1 _3 o# y5 K! t
( E$ ?+ T# h9 v) `s.dt.month_name()
8 `% ?5 b# D& E$ D# h5 g+ U, ^Out[38]: 6 s8 r% `" K, y; D" m U) ~
0 January
: d( {4 [" E: ?( `; `! }1 January
" @- L$ e: y! f, V: Y/ |" ]2 January" h+ o% x. b- B
dtype: object1 G+ Z( ^( R5 _+ s
$ `7 c( B2 ~3 V* R+ K
s.dt.day_name()$ Z# {! k! n, G
Out[39]:
+ P0 b" U% F& i( y0 Wednesday
3 f% h9 g" x5 b) r1 Thursday
$ J, s Z+ w6 {4 t" \! W7 {2 Friday; s N+ p( n7 Z; q( E
dtype: object
& F( L; W0 J2 g+ F
0 _' M# x% c; F1; e+ f1 e5 P6 U! B) y3 w( z
2
" u( H2 H w$ J5 O5 r5 ], o3
* n2 N5 z( m( l7 s c( K8 b6 t4
- J' L1 X2 k- m5 h0 `+ d- n! Q5
# S d. n6 ] S. N61 X; \+ `, p% a0 P" K& H: i
76 N7 V" \' ^2 ?$ f0 l* j: S8 q9 J
84 u/ o. j9 A. ~* V- C- ]% Q
9
) H7 J( w( Y$ r$ a, n6 |4 @+ z108 e$ d: n% B* |% H; P0 G$ V
113 V9 e0 p- Q& h/ g: R2 Q
12
0 U+ X" X- R6 _7 }0 j6 O* P13! Y! n; r F0 `6 D: J
14$ @. U0 V0 U- R, ^" e
15
$ L$ D x5 H* t1 q h3 z. e16
; n. r/ d3 c2 ~+ I174 {. L! j& I1 n8 a3 ]
180 x2 @. D/ j1 k6 p( q8 I
19# {8 I. Q L" L4 T% e2 N6 G) s
20
+ K6 B4 D% S! R- H" Q/ W2 S第二类判断操作主要用于测试是否为月/季/年的第一天或者最后一天:
' x- V" K$ {- [4 M( ls.dt.is_year_start # 还可选 is_quarter/month_start& Z* p% x4 @7 |! P: e y" s. D
Out[40]: ) s! A6 q+ H: U# d- L! T6 X$ T# K) C$ L
0 True
. d2 r# b; }7 ]( f3 _1 False5 g4 V1 W; m) u/ R
2 False1 A1 F9 U2 L6 t
dtype: bool
C4 H- q- d5 V; Y: w* S% m- I: ~, c. F' H. F! M( e4 P J
s.dt.is_year_end # 还可选 is_quarter/month_end
7 R5 [2 ~, A. e3 `4 X* |Out[41]:
9 I5 M; v3 P1 }" i8 e0 False
; V# U5 a: E4 @4 Q2 A( f5 i7 {1 False& t) H% Y# n, z
2 False0 x) C' Y. I9 N7 u
dtype: bool
1 _3 Y7 L+ e" s/ s9 }1 }, y* R& [) V9 x
2
& H: e; T' W( |: K( Z3% J' G: y' D% K2 H
4
- g# m% y( U. J0 f56 D4 B) p; j6 _
6# m& `7 t4 _( L) o5 X
7
' m! d: C/ m# T0 e4 c3 G: \& v8
& B: x, G4 N5 i3 f y9. i0 E( Q I8 l/ E9 O
10
' _- U: H) u8 P b6 W11* a- o7 x/ L0 D; I& V( b F5 ~
12* v8 y- e) ]/ l, v3 E ]" X' c
13
$ i3 o" h6 g# w# J第三类的取整操作包含round, ceil, floor,它们的公共参数为freq,常用的包括H, min, S(小时、分钟、秒),所有可选的freq可参考此处。
% x# _7 G4 c" }, y4 Y7 F: ws = pd.Series(pd.date_range('2020-1-1 20:35:00',
- d5 \( D! F! l9 v7 @ '2020-1-1 22:35:00',
' z4 P' y0 L1 r1 q* d1 x freq='45min'))
9 r8 P8 y6 M. C* e
) q. V$ d" V( ?7 ?' m9 g5 K* J; j) m/ y5 H" }* s) y8 {# [. ~
s g9 ?& N, t" N( l
Out[43]: ( ]& j) _+ r& w0 n; X
0 2020-01-01 20:35:00
0 e3 |9 L* ~* [- \0 K9 ]8 H$ d" h1 2020-01-01 21:20:00/ `* \" S! H" O4 q; @( P3 n- }# \4 u. S- b
2 2020-01-01 22:05:00* D. }) g7 {* v1 R6 c
dtype: datetime64[ns]
% _1 e4 i. h6 ]% G. W1 x# V
0 }* K0 [6 b1 n8 v L) Vs.dt.round('1H'): r% P; ~1 y3 `
Out[44]: : e2 u8 i5 W8 w9 C% y
0 2020-01-01 21:00:003 n; G! T3 X7 P
1 2020-01-01 21:00:00
' u3 g% f6 Q$ k$ g$ e& K, \2 2020-01-01 22:00:007 B3 M* W( o# n: A0 G! X0 {2 ]
dtype: datetime64[ns]3 t, u: a1 Q$ q$ l, e9 P
+ y8 ^( {; j0 J* Ks.dt.ceil('1H')
; Y! g3 J. C0 o0 bOut[45]: " Z* C9 Y6 @3 T$ m
0 2020-01-01 21:00:00
3 O0 [/ I8 t$ B4 Y" w5 G3 S1 2020-01-01 22:00:00. G1 `# n& L) J0 Z* J
2 2020-01-01 23:00:00
% {7 T K7 m5 J3 r( {+ y! Adtype: datetime64[ns]& X* f$ b* m6 h. `
, P$ I& Z$ r4 L! S: f
s.dt.floor('1H')
- P% P& Q' f) ^6 \4 r& aOut[46]:
1 |' _" p# a4 w0 2020-01-01 20:00:00
2 \0 G$ W5 P1 F1 2020-01-01 21:00:00( Z* V3 t! _1 O8 v3 `4 \, ]
2 2020-01-01 22:00:005 o$ l! @3 }8 y! _/ C- i: V" }
dtype: datetime64[ns]6 I" e/ k9 o! m0 Q. c3 i
+ v9 v. m5 h6 }" }' j7 Y' D1( n7 H' K0 a' @; P+ ~5 I* S
2
2 ~9 z' c0 ]2 X9 m' {/ S9 t39 K$ v& f" X8 l, o
47 Q4 l; Q6 k: Y2 O+ a/ |% g
5) r/ z! T# G6 d2 @2 _5 Q
6( }7 n7 R8 x$ p
7' a2 S9 `' h- {6 ^) w$ i
8
4 G# d1 H( t/ f/ h3 w. `90 }# b: A: V: \
105 `0 S9 d1 T% @9 S" G }6 M
11
! T8 E& F) G" N* h- K, w12& [/ A) n! | r
135 j' e1 n& v7 {* _) d" [ G
142 e! d3 H6 f- G% ]. Y7 ^+ d, h
15
4 x4 Z) a8 y1 q! R- t. u' h169 T3 e" b: ]3 n3 \$ N
17
6 p( s' t4 |2 E* o* b2 W( N18
% K$ u1 G7 E6 a: l194 a) D& N* R7 V: |' z2 T, ?
20
; n, r( a# |- {( y& p21
, w6 \( J* z7 i, [% `( r4 f- N22& k2 Y" H2 J- U7 V E
23
( ^5 [& j' ]+ F- r: j24% s" f6 Y- m- Y( L
25
8 R, \; G1 t# X: l3 A26
/ g% ^! K+ x( [4 Z9 ~, V27- r- F0 J# E# N6 m5 L4 g" a" \
28# z! K& J W: B
299 I9 k/ |1 g& M9 w5 u- z$ t. l
300 h( \3 i2 [5 A% o4 y0 P
31 L+ M, u8 m3 B2 q- f
32 z$ P5 X. j+ Z+ { P# z7 l
10.2.4 时间戳的切片与索引
/ U' f% p6 N" ^/ `' O 一般而言,时间戳序列作为索引使用。如果想要选出某个子时间戳序列,有两种方法:
2 A6 ?4 x3 t% e8 G5 j9 i: _' e5 k+ N& }0 U
利用dt对象和布尔条件联合使用( l5 j, m, K/ A
利用切片,后者常用于连续时间戳。% W8 M+ A& Y, k% {; n
s = pd.Series(np.random.randint(2,size=366), index=pd.date_range('2020-01-01','2020-12-31'))
# {7 A$ e" H N7 B) ~idx = pd.Series(s.index).dt5 W- K+ s( P( ^4 ?/ ^
s.head(): Q" @; g- T1 k- ^
3 @% a3 g& s& P7 W0 N9 `2020-01-01 0- r: U1 {, [. V( w
2020-01-02 16 f2 Q5 t/ p; T7 {5 @. k& B: W
2020-01-03 10 C: o8 S0 }1 V+ o, F* h2 O
2020-01-04 06 O. B+ [$ R) E7 X, x* Q- u6 U# O/ u
2020-01-05 0
Y5 G# P4 A2 s. X5 G4 _% LFreq: D, dtype: int32( M. X2 q( @& Q& Q: G
1
/ x4 I/ y+ u# d- B9 N& G+ x2
- r7 v j- {, o- Z; t3
( O. d0 L/ u. m4 v ` `' n3 @8 a. o1 E, c9 @% H G
5
' G. z* K' R: h4 g; P8 Y6
* {' S" N4 G. G) N: e7% u' r- p' K o1 Y: X7 N6 ]3 h9 E
8, h/ h3 m, I+ u1 Z1 y& G4 v0 J# d
9: Z6 o( e; `: f2 [: F+ l+ n/ j
10
( J1 y* P# M- B5 w5 M) _Example1:每月的第一天或者最后一天7 a9 t8 w7 z3 L5 {8 u8 H
' M: \4 h6 L3 [' w0 O
s[(idx.is_month_start|idx.is_month_end).values].head() # 必须要写.values, ]; S8 o6 F( v5 {
Out[50]:
3 q1 f& V) j* X9 G2020-01-01 13 z, {& y) }* Y4 A) N1 H% `
2020-01-31 0
' g: P1 J H) q6 h% Y2 R2020-02-01 1
9 j6 r# M$ S1 Q0 ?/ h" D2020-02-29 1: I* ~/ D4 r4 q9 F5 D }) x
2020-03-01 0
; F; @2 Y) d" J& kdtype: int32
. w# n# p, O* K& g; {5 j1
8 W9 d' p3 e# |! O( b2$ [2 q3 v8 x1 n8 e4 U. D
3
! j1 m9 H; r) r3 {/ K2 {9 p4
9 y! @) r% o) [ L1 J5
c' G- }3 v3 S I) c& Z62 b% _7 t# i4 g* N& {/ o. X
7; Q! J7 I& \1 c$ m
8
0 H+ z: l& y: s; W4 r" A' H: ]# eExample2:双休日' H# a( Z, @9 ?6 ?3 l% Z; l& y; y
7 P- U! b' `/ A* c: K1 w$ l, ]s[idx.dayofweek.isin([5,6]).values].head()
4 _6 a D4 e6 |Out[51]: % c# }. {& c3 L' `, D
2020-01-04 1
8 o2 }9 @% b4 g) k2020-01-05 0
" Y( P! @( p- C9 h5 S7 _- Z2020-01-11 0
; D/ D3 v' ^; _! W5 d5 {) ]2020-01-12 1- F" {+ E0 F( ]" }7 A
2020-01-18 1- X1 v6 h1 Y9 s. W2 H
dtype: int32
% n: m" m+ n' O( j( t) y B1
$ F. `2 i3 i: p% z2
/ M8 s2 T) T3 i m0 e3
3 e% P- Q1 d: T; E, U% e4
: ^1 D Y. c$ D. n u1 @, T5- S& d& v5 S- M' p4 |
6
1 Y% a1 W3 H" l" o3 I i Q7" A& w9 F# B: f* B; b
8' ~' t( S0 j( D
Example3:取出单日值+ s7 g: ?9 i# U( r9 a- h' s9 J9 c ^
+ k3 m% y& v4 v, G& v: l8 B& Ns['2020-01-01']
) |2 s( Q, S9 C1 C$ t8 k) MOut[52]: 19 e& x( ^& w0 [' b7 c# I- @" b4 u8 s
, p! Y0 L' F( d" ^
s['20200101'] # 自动转换标准格式
9 p) X( @( `. y: K }Out[53]: 1
5 K9 X7 x; T' a1
6 q; k7 X- c$ q6 g v6 v. a; b5 v2" I; {% j Q. ]2 ]4 B1 W
30 X% V# Q$ U7 j. L! r) ?- W/ ^
4% Y d0 E* X1 {' E& s
5
8 D3 ]; Y* C$ p5 [2 B$ g) V7 h" HExample4:取出七月3 _) Q) S6 i* }' R A
! P" o( A5 @# r1 Y6 P* es['2020-07'].head()
7 L6 ?/ Y, { X1 Q8 f. C8 XOut[54]:
% J6 S) y2 Z8 o1 y3 W6 v2020-07-01 0
) z( k3 x! {7 E% u. G" m2020-07-02 1 x. a3 p. y' T% ~2 f
2020-07-03 06 z6 m8 p; S! z" [& Q( t( S
2020-07-04 0" D& c( H. f8 o( Z8 N. N2 q, y( J
2020-07-05 0# K' v, c Y/ R* P! j
Freq: D, dtype: int32
# }. O' ]. i" b8 @) h+ U: K1* ?1 _) h) v9 q$ c& N
25 ?. g" G. U" q( T& p2 i8 }5 _
3
- b6 L7 }7 Q2 f4 C% R46 X; v: N; S6 y) `! r7 G/ X* A
5$ e$ g8 p8 B; n9 U1 K+ r
6
/ s, U1 ]5 Q6 \. ^" }$ s \7. Y- F T& J7 n$ {: v% K: g& g0 v1 B
8
6 X! e) Q# ], ZExample5:取出5月初至7月15日: Q2 `+ t6 N( W5 t
* Q( Q4 s% e# \7 @4 s! E5 T9 }
s['2020-05':'2020-7-15'].head()
, Z7 F1 _2 ~9 u. lOut[55]: # c _6 N$ @) C( r' e
2020-05-01 0$ \3 S$ y8 \8 D' j* D7 y2 e- r
2020-05-02 1
& O& R! ?5 @+ w- z( |% o+ O2020-05-03 0
4 q! ?5 B! G8 u* c7 Q2020-05-04 1
- r$ D2 r9 ~% |8 ]2020-05-05 1
7 s5 B" o8 j5 n( }; CFreq: D, dtype: int32
0 M; y# J0 Z2 W' b
8 v+ U7 q/ F& L7 Os['2020-05':'2020-7-15'].tail()
4 Q0 o, D( q$ _' M. {Out[56]: + R, M: B& Q" G6 r/ {- a. }
2020-07-11 06 i( V/ Z0 R ~8 y& n0 s, v8 J
2020-07-12 0
' K/ j$ N4 y7 Z& o" p! j2020-07-13 1/ p5 v& _( T4 o3 f* x& L
2020-07-14 07 T8 s1 h7 ^" | }. }2 A8 _
2020-07-15 1% Q5 e4 Y) ~! f7 L1 e
Freq: D, dtype: int32' A P# h% i4 C V: R k) O
# \: c) x# i+ x. K* w5 l/ b1 g; ?& M% d; L/ ~$ C
2( s; k3 o4 d2 Q
3
* v9 R+ ]: K6 |: C- g9 B H( j1 a4; l( ~$ [0 d3 F# R4 j
5; U; O6 V/ f! G5 [, Q9 ~* D
64 K9 u# [1 x4 k/ q
7$ b( ~; a5 I% G6 i! M
8
( S' \# f' D$ F" z4 S9
, [( |; u# S7 v- H10
8 P- Q( m& ^: e. _/ O: u11
. A/ s$ b6 }- \' p) X: ^" N2 p12
5 b! W/ x* w( w! b13
9 k4 W" r4 \) ?1 q14
: [2 Q* [ W- v6 h15
( v7 d9 ?- n0 h# c$ G9 d: F16
& v& A$ ?* L! z( _& W: u- B- Y17+ f- c8 f+ \4 t$ j( [& ^ ]2 c
10.3 时间差: h' l9 ^! A: M8 j8 F/ `' k! f
10.3.1 Timedelta的生成
& w/ F3 b: u/ H- ~: R4 X5 r" spandas.Timedelta(value=<object object>, unit=None, **kwargs)
' r) m4 @$ K4 b, P7 ~ unit:字符串格式,默认 ‘ns’。如果输入是整数,则表示输入的单位。! [8 L8 O2 M6 y1 Q% |
可能的值有:0 H8 ~+ Q2 L7 M- f! ^
, L p" p5 _2 t& O9 B, L
‘W’, ‘D’, ‘T’, ‘S’, ‘L’, ‘U’, or ‘N’
$ @- w4 ?" z, j+ q- k5 m‘days’ or ‘day’
; R% B! {1 Q% v& |‘hours’, ‘hour’, ‘hr’, or ‘h’ K) v# ~7 i" e
‘minutes’, ‘minute’, ‘min’, or ‘m’& F# A! @' c+ \' @& O
‘seconds’, ‘second’, or ‘sec’/ Z" M* V7 j4 a Y" X% J1 ^
毫秒‘milliseconds’, ‘millisecond’, ‘millis’, or ‘milli’
B. c) m1 x* ^- K0 P( y" H4 T微秒‘microseconds’, ‘microsecond’, ‘micros’, or ‘micro’0 \: H5 i$ z f% n
纳秒 ‘nanoseconds’, ‘nanosecond’, ‘nanos’, ‘nano’, or ‘ns’.* `( F& L$ R- T0 f- M% h8 G0 T
时间差可以理解为两个时间戳的差,可以通过pd.Timedelta来构造:
( T+ D; X/ O& \2 D& V' m6 Npd.Timestamp('20200102 08:00:00')-pd.Timestamp('20200101 07:35:00')
+ V V5 q$ l+ p$ E2 a# {) S, KOut[57]: Timedelta('1 days 00:25:00')& f+ r. ]0 M6 T; q! U, Q. V- ]
. H( O E. |% T4 v7 q% y
pd.Timedelta(days=1, minutes=25) # 需要注意加s `& q) c- C. Q/ u7 N' l" Y
Out[58]: Timedelta('1 days 00:25:00')
+ C- D0 |: }/ s3 y* V( b7 Y8 V- y. ]$ o3 U) G( w
pd.Timedelta('1 days 25 minutes') # 字符串生成
8 e0 X; }3 G; T6 d% X, a, k/ xOut[59]: Timedelta('1 days 00:25:00'). h9 T/ o$ p; @
8 L. c2 S% h1 e: n/ d z% x# }pd.Timedelta(1, "d")
$ [6 r/ X2 w3 f% R2 AOut[58]: Timedelta('1 days 00:00:00')6 L& w% Q) V: y8 B% g$ e
18 x( v4 H9 d' q& K+ A
2; ?1 N/ a/ {7 @/ f. `, |- E
3& \ R' z" B) R' ^. i
4
5 t e7 V6 y5 \! C; I5
c- M* h; G4 M a5 a, W1 Q* {64 ~$ G2 j3 m$ x! i$ { u R8 Z
79 M# W, @7 F0 \' P7 _0 X
84 o; S4 C5 c/ R5 ^& }4 T
9
1 T. q" f' R; _# D101 K* l. [' P3 J6 H: ?
11! ~& K- {& \1 h$ W4 k) a
生成时间差序列的主要方式是 pd.to_timedelta ,其类型为 timedelta64[ns] :
2 U# S' g! A) k9 Q Us = pd.to_timedelta(df.Time_Record)6 S, @7 {) {! M# R9 s
2 f; z9 M4 e8 H3 {5 x. e9 Vs.head()
: t( E7 j8 w0 L* ~ L. m. sOut[61]: , J. M2 S# x+ d0 r& r. P' y
0 0 days 00:04:34
( J) c7 |1 m$ J4 B$ \1 0 days 00:04:20, C" M; {% g* ?- ]2 f
2 0 days 00:05:22
5 D! z- C( `& |! X7 R3 0 days 00:04:08# K1 b1 h7 c; u4 X( t
4 0 days 00:05:22- l% o, I, A# O( O2 Q- O
Name: Time_Record, dtype: timedelta64[ns]& C- ]1 W$ D- `* j2 t5 ]# _1 g
1
* L, D1 e5 K% p5 ]2
7 m4 n- ^( T# N# }4 p2 b8 j3
B2 u* |& w: ?7 { T. [40 ^) [# }. B! S7 ?- w
59 ~2 P8 @( j) f" ?1 `. I& @
6
0 L4 s* _: K. R+ }' T7
* N8 X3 I+ y* N6 q/ t6 E1 Q84 P) v, _1 f f" ?2 s! g
9' C1 ]8 n# q5 I8 O o" {7 ^% U' j- X
103 @/ i j2 T$ _ Z4 Q+ z2 j( H; a
与date_range一样,时间差序列也可以用timedelta_range来生成,它们两者具有一致的参数:4 z& C) i" }1 L: V/ V+ b
pd.timedelta_range('0s', '1000s', freq='6min') i1 k- `! m9 ~( T
Out[62]: TimedeltaIndex(['0 days 00:00:00', '0 days 00:06:00', '0 days 00:12:00'], dtype='timedelta64[ns]', freq='6T')8 V: g! o1 v6 t6 N& z2 y$ U: A, e
5 C1 a9 I- g* V; p. p7 [
pd.timedelta_range('0s', '1000s', periods=3)
6 w5 j3 w8 N) ~' O) k0 GOut[63]: TimedeltaIndex(['0 days 00:00:00', '0 days 00:08:20', '0 days 00:16:40'], dtype='timedelta64[ns]', freq=None)
2 u0 S+ h' i" c M4 n$ Y7 X: {6 e1
' Y0 }6 ~8 [5 ^$ |$ G2$ o7 U8 C+ |; V) S* Q% E, s
3
: ~0 p9 j8 t1 r1 }3 e42 l" I: t. C# l- a8 h
50 x1 } V1 s- Z2 C
对于Timedelta序列,同样也定义了dt对象,上面主要定义了的属性包括days, seconds, mircroseconds(毫秒), nanoseconds(纳秒),它们分别返回了对应的时间差特征。需要注意的是,这里的seconds不是指单纯的秒,而是对天数取余后剩余的秒数:
+ i0 U& }* g- ks.dt.seconds.head()
. O5 f/ _0 r% QOut[64]: 9 C# j- E6 d4 w
0 274
/ c$ i: b0 y* p' `: X. @1 260
) |* P* k$ Q* P/ u; t2 B5 Y2 3221 L* K5 @! U& N$ [, s5 @
3 248' |6 N9 {4 \( {% \3 D7 S8 m
4 322/ F+ Q! ^. E8 z0 h; `2 n) Q1 v
Name: Time_Record, dtype: int64
# T2 Z5 X- C; G4 s7 V7 d1
9 r* D2 `/ d- [+ {6 I29 ^# d3 \4 I! ^% ^ d2 p7 ~' D+ i2 \
37 ]: @2 r: i- R# O
4
. O0 L' k+ O% \2 b; o51 i; c2 O7 r; l
6
2 [- k6 l- o/ I7 D1 C7
3 h& [, B+ Y5 w6 ~' s" f80 o, _ U: O8 o6 e
如果不想对天数取余而直接对应秒数,可以使用total_seconds7 r: I9 u& V2 e: Q( L
$ K3 `3 B) N- f- F9 S' J$ rs.dt.total_seconds().head()' p. _' f- \$ C# ] o
Out[65]: 2 D% s3 d7 T6 ?. W! s4 U$ c( f8 T
0 274.0
! |- x. U: w$ K3 i1 260.0
# j" e; D/ }% T" L2 322.0, B1 x) O* H! p8 M
3 248.0
' x2 X Q' h2 y& d1 A4 322.0
, V4 o0 N. @7 Q0 VName: Time_Record, dtype: float64
% J. E. b: I9 c1 e* [8 v/ Q! q/ @$ Y5 H
2
. V9 E, O0 H$ o" b% M2 c* J37 Q; O& Y8 S) ]3 N3 `$ p9 [
4; t+ C1 t3 X) v1 Q' G" d3 Q
5
% F/ u3 V$ k9 j- ]" m0 Q6
# Q: y5 g( g b& P7
2 d0 B: j9 ?" x. x8 a84 H/ ?3 M9 ~0 ~, y8 M
与时间戳序列类似,取整函数也是可以在dt对象上使用的:( o! e! _4 W& O3 S3 b ~( A$ b
( L3 N0 _" A; Z8 Y, I3 }& w. Dpd.to_timedelta(df.Time_Record).dt.round('min').head() H) O+ a* E( ~5 T
Out[66]: 0 i: c1 I4 f1 \0 R2 c% q
0 0 days 00:05:00. E0 S! Q- _+ {& J) B$ P
1 0 days 00:04:00
0 E; n. {8 `, H! e$ F$ w9 U. a2 0 days 00:05:00
" v. x0 A% Y* b: T% b% d( O3 0 days 00:04:00
0 v- N7 A& P$ Q5 u' Y2 Z/ A3 l4 0 days 00:05:00' m) ~$ l$ x' x) [( _
Name: Time_Record, dtype: timedelta64[ns]
# X3 T) Q% E5 A8 D8 @! w2 l4 w15 x: |: F9 N6 a) p& G3 d% b
29 e* B$ r3 ?0 c
3 P; n, W7 D: }% w: g0 t' M" M8 _
4
8 {' u3 F/ {. h4 B! s- w1 O$ b5
$ ]$ _2 f3 I# ]6 f63 q5 G. `2 l7 K1 q- B2 r
7
- S. u }" q6 P3 C8
& X' z. ~$ u0 G4 `0 m10.2.2 Timedelta的运算3 I! x) v% p+ c! @ e7 @1 A3 {9 J1 S
单个时间差的常用运算,有三类:与标量的乘法运算、与时间戳的加减法运算、与时间差的加减法与除法运算:( p# Q) y: n5 f4 A
td1 = pd.Timedelta(days=1)
, e1 D9 ~! p8 S3 rtd2 = pd.Timedelta(days=3)
9 t+ i/ R+ A5 t+ kts = pd.Timestamp('20200101')
1 t' V: G/ o5 {- p* E! e5 `: U
- U. _6 r" g( a5 o" O4 ~td1 * 2
7 e; e: y- ]1 c; I, S: O) V$ HOut[70]: Timedelta('2 days 00:00:00')
; d' a/ l+ Q. n) }4 W% m4 z' q- X0 |: ]4 c% K, k9 g6 K7 Q0 y5 [
td2 - td1, q1 }& F0 W0 l( c1 D$ T( m. w
Out[71]: Timedelta('2 days 00:00:00')& J, ]% ?- k6 w- @4 F* T Y
$ o' i/ y E% m# X
ts + td1
. C$ @6 l E6 e5 _9 r0 gOut[72]: Timestamp('2020-01-02 00:00:00')
* v& M" Q# ^+ D; | v! v) }7 i: q" {5 m0 S F0 g* ?2 ]2 d, N
ts - td1
! i& r4 V9 U5 z x3 VOut[73]: Timestamp('2019-12-31 00:00:00')
+ c% o# W4 U: C& A5 p# Q1, q2 m1 ~9 d& {% R6 F
2
" C+ y0 m1 d; ~3" l0 t5 Q1 \; ^: j P, _2 m
4
, t5 P" @( [5 K1 f5% U% { y! ~8 l0 \+ ^* d5 S9 \! ]
6
3 B( e3 f" u1 E9 c! ?& Z( K7 m7
0 X/ i' n' \9 b) w/ w8 o, K- _86 A2 _% K! M1 {$ R- G
9) w3 N. l' j1 V3 l1 w0 i
10
. Z- `: r& l# p, ^: ]' E5 q; B- v114 x1 i4 |8 z' x; O
12
+ E; i- j8 |' R$ h+ N& e137 b6 C. e3 L6 \( ^0 t
149 ?3 p' j, Q& H" l, O8 d4 d0 D/ l
158 B5 r( F- _ |4 w5 g+ b: p
时间差的序列的运算,和上面方法相同:
' d: G0 E. d" I9 M" G6 u% l; _: q2 \td1 = pd.timedelta_range(start='1 days', periods=5)
: d. Z# |: b' H4 K2 j2 a! O dtd2 = pd.timedelta_range(start='12 hours',6 J1 e* K& Q, R6 S
freq='2H',
6 b( z4 ?& K+ x8 v4 K( _9 ] periods=5)4 {7 r, s2 Y0 _# r- r, d/ J
ts = pd.date_range('20200101', '20200105')! H: d% e3 Z# q- g. \5 d R
td1,td2,ts4 r5 O! i: C$ I" c" T g9 X
) h8 h# |7 t, R" O0 o% ^
TimedeltaIndex(['1 days', '2 days', '3 days', '4 days', '5 days'], dtype='timedelta64[ns]', freq='D') H% e" M' B! H2 G1 F$ Y
TimedeltaIndex(['0 days 12:00:00', '0 days 14:00:00', '0 days 16:00:00',
3 p4 K1 m2 q7 R2 i6 C/ c '0 days 18:00:00', '0 days 20:00:00'], dtype='timedelta64[ns]', freq='2H'): Z7 y% ^, a a0 `7 }8 K" r
DatetimeIndex(['2020-01-01', '2020-01-02', '2020-01-03', '2020-01-04',
# D. q, ~; w5 ]9 U '2020-01-05'],$ i9 |9 b& S/ V. C% y* I9 C5 K5 q
dtype='datetime64[ns]', freq='D')1 k" ]1 U2 e) h) p- X
1! A# u2 n3 g' d' D; e% w
2) H) {7 o: B: g1 Y5 J
37 O$ J- \3 F9 g( X, r3 M2 J" s+ j) O% ?
4
& I& U. K( }- l57 {: _! J* S; z% W7 c$ ~& j
6! W) t* O4 Z8 e! x6 t: a. L9 B8 h
7
- I$ R+ [1 P ^! s8
3 ?. z1 K. i3 R$ o7 `9/ ?1 Y; V4 R. j# N4 t4 g; X' w) A! M
10" L. m$ b" q# e! z5 M
11
! @ ~! r* x2 _5 w12' v1 E# Z5 r; @' w
13
- u8 Z$ ?( {- Etd1 * 5# I( G' w6 {1 [% Y& @. U
Out[77]: TimedeltaIndex(['5 days', '10 days', '15 days', '20 days', '25 days'], dtype='timedelta64[ns]', freq='5D'), S! n% i0 I0 f/ U1 o6 V
9 H. |/ w5 r! t- [& ?td1 * pd.Series(list(range(5))) # 逐个相乘
6 W7 d5 }% x) \% [Out[78]:
+ ?/ e6 ~% Q' E* z0 0 days
* H5 `4 F) k; Z- B6 Y. y0 ^' D6 @, b+ o1 2 days+ b2 c6 j: c# _4 ^4 a7 n; k
2 6 days* [; J) I3 d6 n: I# L: q
3 12 days
( _! E+ I$ w* a4 y i9 w9 ~4 20 days
M/ L$ y' y8 y+ l+ ^! D5 zdtype: timedelta64[ns]
) p$ c8 @* J% ]" R7 z: H0 Y; _$ s3 Z5 l4 P1 V" }- r
td1 - td2
; {! ~5 v3 x* Y& {! mOut[79]: 4 v; _+ u9 g: Z: u+ D
TimedeltaIndex(['0 days 12:00:00', '1 days 10:00:00', '2 days 08:00:00',
# m+ d! P6 q% U5 W7 m '3 days 06:00:00', '4 days 04:00:00'],
0 H" b& O' k! X! s5 l dtype='timedelta64[ns]', freq=None)
3 J; @. x7 c+ N" I, V" n% M+ M5 M
3 w' ~; j6 k, G$ ~. S+ Q3 `8 Jtd1 + pd.Timestamp('20200101')
' o) k6 }; Q# B9 D& v$ P8 i F7 [Out[80]: + N* J7 D! m( R1 I; W0 m9 L
DatetimeIndex(['2020-01-02', '2020-01-03', '2020-01-04', '2020-01-05',
6 |7 i7 k1 d- S8 X5 n '2020-01-06'],dtype='datetime64[ns]', freq='D')
6 l3 h$ _1 Z& X
7 O/ V9 ]. q& V6 Itd1 + ts # 逐个相加2 S r$ s: m: @5 t" m+ i5 u! I' H
Out[81]: 9 o1 |. P. c0 ?) R3 t
DatetimeIndex(['2020-01-02', '2020-01-04', '2020-01-06', '2020-01-08',8 c1 B: U u) K
'2020-01-10'],
2 F4 H, _0 E, A) G; o5 a dtype='datetime64[ns]', freq=None)% N3 Q" e1 R0 @, f+ \
/ V J5 u2 Z; S* N7 ]' J' {
1
. O* X; [9 d. }) l$ n2$ v! k5 i8 s# g, B3 E n' g
3: \5 y7 @/ I P# k- _' N( M) C
4
: I) ^( y! z1 `! A3 w. l5
2 b1 u3 D9 p6 h) m# o" g3 y6
- G* ` T0 P# W' U& `7, A; {) t7 i8 v
8, K$ ~, E, e& U) `" A1 z
9
: K7 o& E5 s( q6 C) i+ J+ t10
/ d) y0 B. ~, S0 y11# l5 |4 C2 L* o3 ^! L3 d) a
12
0 l" M6 e' {8 r1 u& z& V% m3 |13
( A. q: R6 u$ z' b3 i+ u14; H2 d I9 B2 y( \( l
153 C7 _, x+ ~: M+ e& v
16
Z; x9 [( X1 L8 I# X# E178 Y2 U4 t7 X0 q- V
18% f3 E8 A* s1 M* z
19
8 g- s8 N6 ^6 x5 G6 s+ b. b; I20 Y1 f* F: q9 N
214 Y: A! `' H, I! {# C) c
22
( G+ S+ r' q5 d h23
2 _7 x! L9 S( f24" n) `* r' u) X+ x9 _! o
25
5 t: C3 _0 M2 G! E+ I& `5 t2 k26
9 B" q) W H( c' o/ K27
0 m9 Z3 q) ?% i6 N: {$ r2 d28/ a, X; A% q% t
10.4 日期偏置8 ^) M/ c5 R# B/ F+ [' }" g! I
10.4.1 Offset对象
3 `/ R" u: i9 b 日期偏置是一种和日历相关的特殊时间差,例如回到第一节中的两个问题:如何求2020年9月第一个周一的日期,以及如何求2020年9月7日后的第30个工作日是哪一天。
. K/ @' W4 x& G; M0 G
5 Q: ~5 e; }3 \' [' a0 QDateOffset 类有10个属性,假设s=pd.offsets.WeekOfMonth(week=0,weekday=0),则:
4 {' {5 Q9 t, `3 C: ^; B, ^8 r
" Z( t! A: n5 G+ ss.base:<WeekOfMonth: week=0, weekday=0>,返回 n=1 且所有其他属性一样的副本+ G+ Z8 s8 a; m! D( i
s.kwds:{‘week’: 0, ‘weekday’: 0}1 v# Z D9 W7 W% v: ~
s.wek/s.weekday:顾名思义
3 v# C: `6 G% v7 z4 \( P有14个方法,包括:' ?9 g5 l7 X9 @
& m' J9 k, _- O5 P- n7 m2 T. j5 R6 MDateOffset.is_month_start、DateOffset.is_month_end、DateOffset.is_quarter_start、DateOffset.is_quarter_end、DateOffset.is_year_start、DateOffset.is_year_end等等。
) q) t* L) h, e8 ?0 m+ [# hpandas.tseries.offsets.WeekOfMonth(week,weekday):描述每月的日期,例如“每月第二周的星期二”。7 { t7 m" p- B2 m4 z6 g: ?5 Z$ c3 W. Y
) F1 ~4 `8 K+ Q& r6 g- S$ E有两个参数:( X4 A) c+ H2 J5 p
week:整型,表示一个月的第几周。例如 0 是一个月的第 1 周,1 是第 2 周,以此类推。
! u8 D& s. U0 J+ K* T. Jweekday:整型,取值为[0,1,…6],表示周一到周日,默认取值为0(星期一)
* O# ]9 ~3 L1 w: |7 epandas.tseries.offsets.BusinessDay(n):相当于pd.offsets.BDay(n),DateOffset 子类,表示可能的 n 个工作日。
5 q: `; @9 }8 b& H/ j; ~( N9 d' m
pd.Timestamp('20200831') + pd.offsets.WeekOfMonth(week=0,weekday=0)7 u9 ~2 A* N8 W, {! f( ~
Out[82]: Timestamp('2020-09-07 00:00:00')
9 b; E* x0 f. | c2 E0 G
j) l# b. O' N: S6 Kpd.Timestamp('20200907') + pd.offsets.BDay(30)4 s k; S# b! N' w1 a* @
Out[83]: Timestamp('2020-10-19 00:00:00')( w& O. ^/ ]& d1 {) h( L
11 l: m' @- ?1 V; h3 Z1 C% n0 m
2
8 w& d* q) R$ N- x' t. ~+ J3& G0 {2 R- F+ }" D
4
/ W9 p; ?; R/ ?* t$ T/ t! g/ d5
7 v3 y' B6 y& v 从上面的例子中可以看到,Offset对象在pd.offsets中被定义。当使用+时获取离其最近的下一个日期,当使用-时获取离其最近的上一个日期:
7 N+ X' q( d0 e7 {/ w5 n; b! K0 y1 S* _: T1 o, h: Z- S; w) l
pd.Timestamp('20200831') - pd.offsets.WeekOfMonth(week=0,weekday=0)
2 y, W q2 D9 ~Out[84]: Timestamp('2020-08-03 00:00:00')
8 @" |2 Y. D3 y+ q" G/ }0 W
. {- F* v! h2 q1 _pd.Timestamp('20200907') - pd.offsets.BDay(30)
0 ~: ]: B" S8 W0 l2 C6 NOut[85]: Timestamp('2020-07-27 00:00:00') B7 `0 D9 Y) f4 v% x2 J
, |, X/ F3 w3 |% G: H
pd.Timestamp('20200907') + pd.offsets.MonthEnd(); \2 Q. y: T( J0 K
Out[86]: Timestamp('2020-09-30 00:00:00')# s1 C8 j: L8 {% I0 G
1
5 `* z K# I/ H1 a7 y( Q* s E. ~2
1 x2 f$ I- }3 R5 a32 ^, A/ ~" @2 `4 N7 O
42 T8 O, s7 O' \: L, x
5
8 p5 B3 ~8 i0 e: ^ i, e: F6
6 x J! P- |' m2 O% S* |; \9 h73 N# G0 ? P: |1 @2 P. y* T3 o
8' Q; R) y2 q5 h/ i
常用的日期偏置如下可以查阅这里的DateOffset 文档描述。在文档罗列的Offset中,需要介绍一个特殊的Offset对象CDay。CDay 或 CustomBusinessDay 类提供了一个参数化的 BusinessDay 类,可用于创建自定义的工作日日历,该日历说明当地假期和当地周末惯例。+ [9 [, I* N: H9 @$ w1 `: c
其中的holidays, weekmask参数能够分别对自定义的日期和星期进行过滤,前者传入了需要过滤的日期列表,后者传入的是三个字母的星期缩写构成的星期字符串,其作用是只保留字符串中出现的星期:
, x8 o( g. O$ b7 P/ H
. f3 \8 H9 r( h* mmy_filter = pd.offsets.CDay(n=1,weekmask='Wed Fri',holidays=['20200109'])
$ i4 a3 i) r# h1 q) p4 Bdr = pd.date_range('20200108', '20200111')
3 T6 o8 Y3 ~5 h% m* H4 P U
) j0 m+ |" V( Z# @, ~9 j; Mdr.to_series().dt.dayofweek
& f6 R P1 M7 D+ G" z DOut[89]:
0 D4 g/ w8 E/ E* Q; I" {7 `2020-01-08 2- |6 h3 u3 W; U9 w/ o; X5 z
2020-01-09 3. y* e p6 n6 E4 X
2020-01-10 4
' x( P5 a& Q+ s6 {, q2020-01-11 5) U& d4 L! w, b# ~
Freq: D, dtype: int641 x( x; [1 k; N7 T: I
& j5 _) k' W% A2 l$ g
[i + my_filter for i in dr]0 d& C/ B/ `* T5 r. R8 c
Out[90]: 4 n7 D! l) t9 k3 P+ [8 v5 p) {7 I
[Timestamp('2020-01-10 00:00:00'),
8 t( M' i# [* q+ w4 `* } Timestamp('2020-01-10 00:00:00'),
8 Q! _6 P- ^( h [ Timestamp('2020-01-15 00:00:00'),5 Q9 ^4 D3 s/ E# X7 Y* _
Timestamp('2020-01-15 00:00:00')]' r$ ]3 d1 G* v- N+ g$ u, _
) c* Y, R: b( M) N: r1
Y5 V3 Z$ i& j* f P Z' x1 O2) Y) Q; C: p2 N' Q7 }6 A
3
- C* U6 X$ {5 _: B' D$ F5 [7 }! C4$ m" O6 M; G; F1 I- i
5. ~$ A. t5 j8 h/ a
62 g7 S0 f0 d) p& @
7
/ J* J; T. q0 m6 Y) R7 H4 B+ n8
5 B7 C: Z* T" ~9
( ]% i7 b+ ?: p" W: I10' u$ D7 b( O. q& k O
11+ Z6 u5 Y: m+ W1 k
12
, E: @# X5 n) V4 X13
J5 }) _) ]% g' l$ x14' ?& ]/ q. e8 [; h! f5 u1 N4 h
15
; W3 `7 C, }6 X: O$ P: W168 e& S+ [2 S* ^* H$ ^1 `
17/ i4 t$ y# e1 E, t1 z, h
上面的例子中,n表示增加一天CDay,dr中的第一天为20200108,但由于下一天20200109被排除了,并且20200110是合法的周五,因此转为20200110,其他后面的日期处理类似。( v3 K A5 N- B) w( N4 p
# }" `+ h a# f: t& @【CAUTION】不要使用部分Offset: R+ i- @* U6 T5 z8 g
在当前版本下由于一些 bug ,不要使用 Day 级别以下的 Offset 对象,比如 Hour, Second 等,请使用对应的 Timedelta 对象来代替。" G2 O* h6 t0 `6 W2 H
" P- Z, e6 A) S. P2 y) W5 q6 |% r
10.4.2 偏置字符串
$ W- W. p |2 \. e; A( ] 前面提到了关于date_range的freq取值可用Offset对象,同时在pandas中几乎每一个Offset对象绑定了日期偏置字符串(frequencies strings/offset aliases),可以指定Offset对应的字符串来替代使用。下面举一些常见的例子。 O- a' E" P) G8 D0 g* W9 O6 j
# Y8 X8 `: [. Q
Offset aliases:pd.date_range函数中的freq参数,为常见时间序列频率提供了许多字符串别名。 也称为偏移别名Offset aliases。偏移别名列表点此参看(大概27个)。$ T4 V3 ?* v4 Q9 [ s! X8 [3 [
' Y) X/ c7 a8 N* V
pd.date_range('20200101','20200331', freq='MS') # 月初
2 }' G1 V6 ]" y7 Y H4 K3 @Out[91]: DatetimeIndex(['2020-01-01', '2020-02-01', '2020-03-01'], dtype='datetime64[ns]', freq='MS')* ]" L3 |* a; p
; g" ^ |. w0 {* ^8 G
pd.date_range('20200101','20200331', freq='M') # 月末% k% ]" z2 b5 M& ?/ F5 i$ n
Out[92]: DatetimeIndex(['2020-01-31', '2020-02-29', '2020-03-31'], dtype='datetime64[ns]', freq='M')
/ |9 Y: r' @# l! M+ P) q; z& a0 ^, h, J4 y$ n# c" z8 a
pd.date_range('20200101','20200110', freq='B') # 工作日
, P# m/ A0 J! F; `7 _$ M2 X: DOut[93]:
2 \6 J( g1 O. h9 pDatetimeIndex(['2020-01-01', '2020-01-02', '2020-01-03', '2020-01-06',' E4 Z3 W0 B, Z& s; |
'2020-01-07', '2020-01-08', '2020-01-09', '2020-01-10'],
$ F% H/ F7 ~0 i( V dtype='datetime64[ns]', freq='B')- v4 c/ }9 h" D" Z' ^. L1 |
( b1 g# b1 j4 hpd.date_range('20200101','20200201', freq='W-MON') # 周一
; J! g) ?- f& ^3 c& WOut[94]: DatetimeIndex(['2020-01-06', '2020-01-13', '2020-01-20', '2020-01-27'], dtype='datetime64[ns]', freq='W-MON')
; c1 a3 I1 b% s1 T$ l- Z
' ?/ {* o! F5 T" X; rpd.date_range('20200101','20200201',
( ^0 N+ \& l ^; J+ Q1 I freq='WOM-1MON') # 每月第一个周一
- I: w: d1 a1 y. a. ?
' h# Z) W5 c; o M- j+ r* NOut[95]: DatetimeIndex(['2020-01-06'], dtype='datetime64[ns]', freq='WOM-1MON')7 ~/ P. ^ e7 G h+ h( C1 ^) R
" b3 @$ @0 u" k! M- v1/ k3 ~& W7 ]& r! q( J
2
5 y2 ]) g! ^. P% ~/ K) o, V' r- j% Z3
# m$ b( E- q( k7 v" O40 J& b$ A2 ^( F: m
5
2 ~7 r& M( n$ T8 @: l; F68 o/ k; {# v: e q J- }
7
3 E$ q, g' |) D7 B1 ?: \3 t3 a8 Q8' n" a* f( q- S5 d; d- }6 X0 U
9+ k; Q* y7 e* D2 _8 u) P! Z( {- B
105 w$ n' \. S& R b" v7 {' W
11/ W" F. o( E4 q0 m5 l
12
8 V3 n: E& N2 Z5 Q/ [13
0 E: U( e# F. S14
9 B! L0 ?2 z3 Z( M7 E/ n: q158 s1 r; z8 y+ U" M" D3 t
16
/ x$ Q+ e6 g. p( L/ R$ D P17; N0 `: d# H# y2 g
182 t! S, N) @) [8 w! q
19' u) k: Z0 }+ v: d
上面的这些字符串,等价于使用如下的 Offset 对象:/ T; h3 c5 j' N* m
( ^6 e4 i' w( j) c4 K0 ypd.date_range('20200101','20200331',
3 \) k. x% j T+ s" b% O freq=pd.offsets.MonthBegin())0 b0 ~) V2 @; U% p4 k
& o. c7 c7 G$ c* w. qOut[96]: DatetimeIndex(['2020-01-01', '2020-02-01', '2020-03-01'], dtype='datetime64[ns]', freq='MS')
8 {8 K. }. H; n! G& e- ]0 w
$ a+ ~& x1 @! N1 }8 Zpd.date_range('20200101','20200331',: S3 B: F2 ?4 ?0 A( F. l; n, o
freq=pd.offsets.MonthEnd())- H6 B5 `. v Y( G/ ?% `
( D: v" Q) V) s) G7 q8 B1 ~& Y% iOut[97]: DatetimeIndex(['2020-01-31', '2020-02-29', '2020-03-31'], dtype='datetime64[ns]', freq='M'); o6 Y/ x1 t% ?# s7 y" \0 u. o0 Z
! b# |, C# ^1 M( _pd.date_range('20200101','20200110', freq=pd.offsets.BDay()), z6 `; x# p8 N7 J9 _6 {0 V+ e% Q7 y
Out[98]:
. T! F, y# t( J$ D( B- O3 O+ RDatetimeIndex(['2020-01-01', '2020-01-02', '2020-01-03', '2020-01-06',
, V0 y4 p1 H1 \' H; A '2020-01-07', '2020-01-08', '2020-01-09', '2020-01-10'],
3 Z6 M. O( z- n1 \: v dtype='datetime64[ns]', freq='B')
# w" s, z, v5 I& \
3 Q) A0 l1 j, r- l2 Xpd.date_range('20200101','20200201',8 i3 u, h1 k0 m, L% Y. j* v0 e
freq=pd.offsets.CDay(weekmask='Mon'))) b4 R5 M/ a5 |' f: b3 {
6 T3 Z, A3 E( [1 t0 N7 POut[99]: DatetimeIndex(['2020-01-06', '2020-01-13', '2020-01-20', '2020-01-27'], dtype='datetime64[ns]', freq='C')
4 n6 p9 b, x5 V' x8 p2 G6 g( c- Z( m- q$ x* ^
pd.date_range('20200101','20200201',
+ g" G$ j8 g& F% f) E/ Z freq=pd.offsets.WeekOfMonth(week=0,weekday=0))
9 U/ r1 L0 ?) Z. j+ ^( v) z9 C* ?7 M" v+ y- K% P$ G7 {
Out[100]: DatetimeIndex(['2020-01-06'], dtype='datetime64[ns]', freq='WOM-1MON')& |; L2 g- K6 G5 D3 ?
4 Z6 c; c; s+ @# T! ]& Y* K1& {. A: q( Y0 K* v/ q- K
2+ d6 _ L4 Z; s% J' e: \
3( k- a/ H8 ?* M, H" |- \% x
4+ T- R4 k2 P. e6 N. j3 {
5
P) G( T( i7 R6 \( s. n6
( L3 X5 _/ S' V/ M2 U4 A7
* a) C8 {' E* U# ~: F7 ?& ^87 ]( t/ @! \7 b. e. ^
93 G( i% [+ `+ S% w' E$ @9 R' E
10
1 T. _* `' |1 ?1 f% t! \+ R11
9 e; Q7 s6 S; M% {8 R3 t12
+ b" G/ D7 F! c, o( a( [& \/ B13
) O# U9 W y* U: I5 L14
, v7 V$ N/ ]8 E, a2 ?5 j; j3 Q15
$ I) Z/ R p6 |& E3 W0 f, Q3 b: P16
9 p. o8 W! c. T( p5 W: Y1 h1 _17
- t3 p' k/ [& U& b& z18
' J% ]" Z3 `, W+ o) f, ]" I197 g4 m: |. d$ l4 Q+ ^$ w
20" r' K% S3 }3 i9 \ f
21" d5 W9 R( a' G+ V$ {4 F4 x2 A
22
7 X- f7 A& u% M7 V7 P: F! g23( [ |) n2 z/ `" n( h
24
7 {; W; w# ]. C. p255 Y# h6 E, `( h
【CAUTION】关于时区问题的说明
; I m0 q8 P o1 |4 F2 ^ 各类时间对象的开发,除了使用python内置的datetime模块,pandas还利用了dateutil模块,很大一部分是为了处理时区问题。总所周知,我国是没有夏令时调整时间一说的,但有些国家会有这种做法,导致了相对而言一天里可能会有23/24/25个小时,也就是relativedelta,这使得Offset对象和Timedelta对象有了对同一问题处理产生不同结果的现象,其中的规则也较为复杂,官方文档的写法存在部分描述错误,并且难以对描述做出统一修正,因为牵涉到了Offset相关的很多组件。因此,本教程完全不考虑时区处理,如果对时区处理的时间偏置有兴趣了解讨论,可以联系我或者参见这里的讨论。
8 X' d) c0 i$ f1 _7 z {! Q
0 A" X# G: L* F10.5、时序中的滑窗与分组
2 ^- `+ {8 T. H# C8 F. H7 y, s1 V10.5.1 滑动窗口* A$ }0 H$ R% ?2 {3 i0 P
所谓时序的滑窗函数,即把滑动窗口windows用freq关键词代替,下面给出一个具体的应用案例:在股票市场中有一个指标为BOLL指标,它由中轨线、上轨线、下轨线这三根线构成,具体的计算方法分别是N日均值线、N日均值加两倍N日标准差线、N日均值减两倍N日标准差线。利用rolling对象计算N=30的BOLL指标可以如下写出:5 l3 z; e7 t* K6 h! {& }
$ u" l4 g6 f9 Uimport matplotlib.pyplot as plt* \9 m6 v6 l6 K1 G9 V% z( M% v
idx = pd.date_range('20200101', '20201231', freq='B')4 ^" X1 \! a) s6 z/ D
np.random.seed(2020)
8 f4 W' Y( o3 g* A' @. N' s# _9 k+ ^- l: e0 _
data = np.random.randint(-1,2,len(idx)).cumsum() # 随机游动构造模拟序列,cumsum表示累加" R0 q" `' l& D8 M% t; F }
s = pd.Series(data,index=idx)# f! p! T0 F! |: ^$ m
s.head()
5 S) t" t4 Y$ L$ dOut[106]: , i+ Z) D Y: n# L. S
2020-01-01 -1+ }+ @. u. s8 o
2020-01-02 -28 \1 P3 U) C. |+ j: d
2020-01-03 -1
3 O2 d2 i5 U) e& I$ c2020-01-06 -1
. n* F: b( }. w- N. K1 y) u; S/ ], G2020-01-07 -22 f f( @$ C0 L
Freq: B, dtype: int320 z/ n& l# c: {
r = s.rolling('30D')# rolling可以指定freq或者offset对象; o1 R+ T7 t6 l- ~
3 m6 j6 C$ z# U& v8 N
plt.plot(s) # 蓝色线
; J( r6 K" b' Q. ~' G7 y6 t/ HOut[108]: [<matplotlib.lines.Line2D at 0x2116d887eb0>]+ H$ T0 O, J' o0 G2 {' F
plt.title('BOLL LINES')
5 T) @1 r$ X1 R. uOut[109]: Text(0.5, 1.0, 'BOLL LINES')' Q; V8 P7 q$ B% W/ D
$ g4 }" j" W C, ~% h1 L$ M
plt.plot(r.mean()) #橙色线
1 H% J9 Y. `2 q) QOut[110]: [<matplotlib.lines.Line2D at 0x2116d8eeb80>]
5 ?8 P' a |* @; Y: x3 g
7 b9 S: h8 p% ]* ]- U- f1 eplt.plot(r.mean()+r.std()*2) # 绿色线- J; ~9 v' v" a, f" m( O
Out[111]: [<matplotlib.lines.Line2D at 0x2116d87efa0>]5 @; O% ]% e( f! l5 \. \+ ?
- E# S5 }- b# f+ I9 f ?: F* t
plt.plot(r.mean()-r.std()*2) # 红色线3 G4 H o. G1 T; w
Out[112]: [<matplotlib.lines.Line2D at 0x2116d90d2e0>]8 [* Y8 C' | W( y: J
+ }: T' _+ Y8 I( h) x& F1
K8 h9 E h( U, ^; p0 p0 J2 x2* _5 c9 J) b% s" o) g
3
$ i3 B# c. z$ M4 h V, r46 u7 k3 b* C0 l% M9 W% b* h
5: S, Q9 z3 h: b' A0 a9 @. P
6' z# x7 | [+ Z8 a3 S" \" Y! f
7
2 }9 f, B6 L9 }( y8, C/ y6 a; {' {7 V- J) \
9
3 q0 A2 D: `) J& l- u5 f10 g/ ]) L+ H' L! N' `0 U U# P0 W; I
11
9 O$ L2 M: G, [; ^' m0 ^121 J5 J* g5 q3 b$ G: f3 I$ c6 f, @9 M
139 X* [- q9 x+ ` p
14
- O) O* O3 Y+ m. }/ l15
4 @$ P0 g) l8 S- m16% w- e" u$ _4 p
17
0 B& L! o' x( r' S& z6 X18& a- p" u" L; C! b! O/ w% D, ?- R$ U
19& M$ m) X {. l5 `
20# r$ I& b4 H, p1 P
214 [3 H; v: Q6 j
22; T& T9 A8 w1 M8 C, _1 ?- W+ \! e/ G
23
) O/ k( H; ?, w247 U' u* y+ z6 h! `( n
25) I% {5 ]3 V- [+ t3 {" g
26
' ^2 R: A. ~) C, j; v: U+ K! t; U/ {27
( b2 ?$ _+ E. Z, Z& o28/ H+ g0 u3 Y' `+ m9 v! G! }
29
; ]" m# G e. }. j# \9 r
: H% i1 z0 |' _8 J; g2 L 这里需要注意的是,pandas没有实现非固定采样频率的时间序列滑窗,及此时无法通过传入freq字段来得到滑窗结果。例如统计近7个工作日的交易总额。此时可以通过传入多个函数的组合来实现此功能。
3 n8 k) ]. h+ |5 U' {2 B 首先选出所有工作日,接着用普通滑窗进行7日滑窗加和,最后用reindex()恢复索引,对于双休日使用前一个工作日的结果进行填充。4 k3 `* w2 x1 O- V/ y- E7 p
: _; L: w1 z( [9 a2 M0 U. n
select_bday=s[~s.index.to_series().dt.dayofweek.isin([5,6])]' ^! J# d- z" v1 u( e. N& y
bday_sum=select_bday.rolling(7,min_periods=1).sum()6 ]! G) t, Y: d+ d# t9 Y% f$ e
result=bday_sum.reindex().ffill()
8 c9 O, o5 n( g+ k, R& {* hresult
9 _! r0 Z7 z$ J- u% z, \
) V- X/ [5 M+ X& z2 J2020-01-01 -1.0' z( ^+ ~( ?) m+ g1 C4 j
2020-01-02 -3.0! J8 G. \3 p _) k
2020-01-03 -4.0
n4 H u Q6 w4 }% ]3 A2020-01-06 -5.0
1 ^( o7 B0 F$ g9 v, t M2020-01-07 -7.0! ] I1 X# `, j( s. C0 N
... - ^, q3 _# S2 o7 Y, `* U
2020-12-25 136.08 M5 s# J9 n1 O
2020-12-28 133.0
. Q& r* T9 l5 b( J0 O6 d& r! I; q2020-12-29 131.0
6 ]! X' z& v% m! g, a7 t- K' I9 R2020-12-30 130.0/ u5 j5 @8 V8 E% N
2020-12-31 128.0 s. ?+ H2 n$ P3 J0 }
Freq: B, Length: 262, dtype: float64; U% O4 _ J: r6 Q. l: X
' |9 L7 f. W$ Z1 a" h
1
: E! T+ p& I, e2 s1 P2* ?' d' s( _& W; e5 j7 i* C& h
3. y1 C& O' P. N5 i1 H* E& _
4
/ B9 v% T9 n& p' q+ E* d5
2 D0 g8 {0 u( g! I6
1 J% `, ?" [/ w3 s+ _, o( r6 z7
) i0 q. q( E9 S7 c. O9 ]- l) V82 k+ d- y- G! s+ U/ W/ o7 i
9( k4 W9 |2 }4 _* g) T* L
10
7 V" ]- ^" A% z3 L, M9 Q11
$ E y4 O$ T" |2 V( `" I12# `# Y9 A1 [7 Q
13
/ W3 p Y, N" G; _5 A' v" h14
7 b8 r3 V8 [8 Z! b152 G! I0 h8 B, r/ V/ F& N! R
162 M" u* J% S1 k0 D1 h: A
17
: h7 s8 m! w, v5 y8 ^ shift, diff, pct_change 是一组类滑窗函数,它们的公共参数为 periods=n ,默认为1,分别表示取向前第 n 个元素的值、与向前第 n 个元素做差(与 Numpy 中不同,后者表示 n 阶差分)、与向前第 n 个元素相比计算增长率。这里的 n 可以为负,表示反方向的类似操作。
9 B3 l2 B8 Z. d7 n* J' Y% j5 F, r- g# ?# J( T% y
对于shift函数而言,作用在datetime64为索引(不是value)的序列上时,可以指定freq单位进行滑动:
: |; k& o$ e! v6 D8 }2 F* O9 H8 w1 z5 B4 f8 Z8 `! Y% p/ k5 g
s.shift(freq='50D').head()5 J$ \0 p2 q1 T% [+ e8 }1 U( ^
Out[113]: + ?& k8 w+ p3 K- T9 j
2020-02-20 -1
7 G9 ]3 H7 x5 `; u2020-02-21 -2! T/ a' K6 L5 h# O: j- B" O3 t- G
2020-02-22 -1
/ U" W& j8 ]8 O* j' r9 d4 d8 @( b7 s. B2020-02-25 -1
- ?0 S4 @, S- y2020-02-26 -22 E! Z4 U/ z- ~' h0 s9 x
dtype: int32, e& T1 \7 j7 I9 @$ M" a
11 K( i2 ^( c) N3 G% ?
24 Z8 w' V4 M5 D# ~
3
/ z8 v' e2 c( n6 y2 R) O4
3 o5 {' T& Q; ?- u, {5
) H: @' z, D) T6 h1 ^( L: L3 G6
. _4 ^* L( d, c8 R+ v! ^7
9 Z o) N: O' {7 e, c9 {9 b8" B' Z+ O5 J& O5 t& I- ]0 i+ n) F
另外,datetime64[ns]的序列进行diff(前后做差)后就能够得到timedelta64[ns]的序列,这能够使用户方便地观察有序时间序列的间隔:
/ Z# w" b, U# z2 m- [1 r2 ]9 T4 B* n5 I
my_series = pd.Series(s.index)
' d* R$ Z4 Q/ o2 imy_series.head()3 q- d e |+ P( M$ L( j
Out[115]: x# i2 a7 `4 l& O6 b1 K: u
0 2020-01-01
7 b( h% h! N: P0 V. Y- B* E1 2020-01-02
- F. U8 H: A: J+ ~2 2020-01-03
7 S2 }. s/ d2 Q& w! _3 2020-01-06
' k6 I6 H. ?. l0 G4 2020-01-07
1 {; [* ~8 r$ b( a9 [- edtype: datetime64[ns]
0 p1 n' I4 L- S8 M1 C, g5 E) K3 i6 {2 y
my_series.diff(1).head()4 `: i E f; I, G& n: j
Out[116]: * E! C+ g, h2 V
0 NaT
8 H6 P) e6 _* U @1 1 days
; k5 L" c1 J4 I" x4 e. u9 ?- P: ^( c2 1 days
! k2 A v- E( k) [/ t8 l3 3 days6 S& q9 H5 s- y% C- {( h
4 1 days
; e, P1 S" w" P8 Adtype: timedelta64[ns]$ t6 I& ?& W" z$ s) `1 N4 `; c. b1 z
3 a. i$ A4 f. {: w+ m, p: K$ B1
6 W' t7 u4 B! H- s1 O! S* _4 Q2
* M6 g4 J0 `, g$ z K' J, ~( P3
0 q% u4 q' T+ K) \, c) X) `44 U) T0 k/ R# K, d# U
5
8 Y9 F- p" q2 R6 L: _5 d- F# N! W1 @) B6" T! [0 z Y% Y# S) L W7 E! L' a
7- L1 U4 t' y: Z* B; M r2 I
8* X' [ F1 o5 ?4 [
9# l- y9 W; y7 u8 ~7 O1 p
10) W/ X3 Q+ h; s7 H
11
! m8 U9 G( B o2 L0 C* Y+ B/ [12
/ L$ Q& C0 r7 D5 u0 e8 O137 x, F% ~3 }+ m, H% i
148 P% H5 d1 s" J6 c- L$ ]
15
1 X% b& v5 |7 q) X/ c9 m16$ d$ e; a& ?" p8 t- E. d! m3 U; a* A! P
17
: \ K2 x+ J1 H6 S. p; ~; o18- f$ A6 s- }4 A4 j. ^% _3 E
10.5.2 重采样" o1 O! c% `: [4 l* p9 o
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)
0 h, c- F" ^, L0 p; r常用参数有:
' f# n: F3 _* S9 y/ z' y! W& c
0 X% q# [/ U6 f0 U! Drule:DateOffset, Timedelta or str类型。表示偏移量字符串或对象
- o( i' n1 E3 I5 u% Uaxis:{0 or ‘index’, 1 or ‘columns’}, default 0。使用哪个轴进行上采样或下采样) B, V7 a1 I8 X5 \' G
closed:{‘right’, ‘left’},默认None。表示bin 区间的哪一侧是闭合的。所有offsets的默认值为“left”,但“M”、“A”、“Q”、“BM”、“BA”、“BQ”和“W”均默认为“right”。
- ^3 g& p0 R8 ~# i ] slabel:{‘right’, ‘left’}, 默认 None。hich bin edge label to label bucket with,所有offsets的默认值为“left”,但“M”、“A”、“Q”、“BM”、“BA”、“BQ”和“W”均默认为“right”。+ X! [- P! S! i* E) |# g$ R0 G
convention{:‘start’, ‘end’, ‘s’, ‘e’}, default ‘start’。仅针对 PeriodIndex,控制是使用rule的开始还是结尾。/ ~1 s! |+ V x' \7 d( o4 G& _
on:字符串类型,可选。对于 DataFrame,使用列而不是索引进行重采样。列必须类似于日期时间。
; U' M# r. h8 _4 K2 @$ l$ Slevel:str 或 int,可选表示多重索引MultiIndex的级别,这个级别的索引必须类似于日期时间。
; V9 t9 B: ^) k2 x9 z9 Korigin参数有5种取值:
# f# \5 B" x: y6 m F! I& T‘epoch’:从 1970-01-01开始算起3 I/ Y+ ?$ [' t
‘start’:原点是时间序列的第一个值
" `* G" y+ F1 R. y* w: f‘start_day’:默认值,表示原点是时间序列第一天的午夜。
6 O0 u: ^2 q# a6 ?" Q# b! [/ a'end':原点是时间序列的最后一个值(1.3.0版本才有)3 w# y; H! `7 A6 w |4 g$ N9 F
‘end_day’:原点是序列最后一天的午夜(1.3.0版本才有)/ I. L9 }9 C1 c$ y9 W5 l
offset:Timedelta 或 str,默认为 None,表示对时间原点的偏移量,很有用。: ` \. \- C" U
closed和计算有关,label和显示有关,closed才有开闭。
# n% A w# x; ^3 c6 v- S label指这个区间值算出来了,索引放区间的左端点还是右端点,closed是指算的时候左端点或右端点是不是包含。
7 q, A# w1 Y& f0 @9 X. x3 c/ w$ \
* C9 N: z) v9 b4 J重采样对象resample和第四章中分组对象groupby的用法类似,resample是针对时间序列的分组计算而设计的分组对象。例如,对上面的序列计算每10天的均值:- B: i! X9 S$ L. g% i
s.resample('10D').mean().head()
; A3 B; p/ |8 f ~Out[117]:
% n& y8 I( u, X u w8 T o' ?2020-01-01 -2.000000
) J9 C* e* Y4 C+ v2 p5 |$ H4 \2020-01-11 -3.166667) ^1 n1 c+ `5 X4 g
2020-01-21 -3.625000
! K v1 W& r% J- V6 c2020-01-31 -4.000000% |, M) Q$ S/ n4 c- N
2020-02-10 -0.3750000 g- O, G* z9 f5 F( S+ p
Freq: 10D, dtype: float642 X& ]- V/ R% f. r" p9 v
1
u# h0 n7 @9 w. @2# {8 T9 d* R9 g* Y! }: Z2 U
33 v. I2 c' M( D& o$ ~; k8 Y
44 P* x* k; u8 }4 W% v
5
3 E, F$ h+ F$ E- p( ^7 W6; |5 p( b6 T$ j! R
7
$ R4 S: W' ]- u6 [8 ^& o }8. Y- p: q- |/ O1 O: i7 r0 B1 F
可以通过apply方法自定义处理函数:
$ K% j% w) F3 p2 Ks.resample('10D').apply(lambda x:x.max()-x.min()).head() # 极差
, m: E- S) p/ U. C
& M1 f" V/ _; m- ^& [Out[118]:
- M3 P, s2 h% w2020-01-01 3
) @ [& c" r, ^% x; J3 J- M2020-01-11 41 f0 |& b ~& O. q5 G
2020-01-21 4
( }( f1 G' [6 h2020-01-31 2
3 s, T. R8 T+ E# m* b& K; a4 \2020-02-10 4+ H u7 c0 N# H# A% ?0 Q5 I* g
Freq: 10D, dtype: int32
7 V# Y1 `( }4 _, e8 G( P1
5 D( j+ ~7 P) v0 T2
% Q3 r* c% g$ ?/ D1 c2 G2 J, Q3
- [) z' _% a( [, M4
& L# a- d% U$ X5
4 {) V: I# ^; v8 y- C$ h2 i6
3 x# W- r# N; k, [) O d7 \5 c9 X; K. o9 O7. }3 L4 }$ [6 R! s+ c! Z4 w
8
: y( J" F% w' o. |9% [; z+ p# |- b5 Y
在resample中要特别注意组边界值的处理情况,默认情况下起始值的计算方法是从最小值时间戳对应日期的午夜00:00:00开始增加freq,直到不超过该最小时间戳的最大时间戳,由此对应的时间戳为起始值,然后每次累加freq参数作为分割结点进行分组,区间情况为左闭右开。下面构造一个不均匀的例子:
( |$ j( x4 q+ v Q4 b
# P/ X: e8 H s7 Oidx = pd.date_range('20200101 8:26:35', '20200101 9:31:58', freq='77s')1 j9 O& f: G5 N0 R
data = np.random.randint(-1,2,len(idx)).cumsum()- x4 _6 \6 x. U1 w! q0 e
s = pd.Series(data,index=idx)
5 Q2 _2 s& r/ ks.head()/ B# i# {# f( |- C& Z( i
+ m; l! t( Y" d; Q, {& F" ]
Out[122]: , J% `1 p/ g$ \
2020-01-01 08:26:35 -16 C2 f" t' @7 D7 T0 J: o! Z
2020-01-01 08:27:52 -1
; X! Q9 z! C/ H! c( I* K2020-01-01 08:29:09 -2* G r+ T: c% c+ z: C3 J( c o2 @
2020-01-01 08:30:26 -3. k+ B. h& t5 T! j/ G3 i4 n
2020-01-01 08:31:43 -4
, s$ x5 l8 t* b7 t' ZFreq: 77S, dtype: int32+ q+ Y5 ~% _, X7 y4 z2 a
1
7 p2 g P6 e' l* X5 k6 ]2
; f* V0 S8 n! P% v3/ E7 I4 O5 I/ Y: v0 I
4" N5 p0 Q ^: ?( z8 r
5( o, z5 R' u x# T$ j
6; s+ L- J% H _0 s" D0 ?' y
7- N: V1 I/ q9 M
8
% F, P7 {1 I1 E1 W8 ^9/ L2 W1 G( r3 D! R* m/ e# U) D
10
7 i2 E" a# ^3 }8 V3 B# S11
! a$ O: n B3 J" t12
1 }: j. O7 i) ^2 A3 `! M7 g6 B" Q% o 下面对应的第一个组起始值为08:24:00,其是从当天0点增加72个freq=7 min得到的,如果再增加一个freq则超出了序列的最小时间戳08:26:35:
3 l9 X) S0 @( m, ]2 c) x5 `% \5 V6 z7 U. L8 H% o
s.resample('7min').mean().head()
1 ^' t% @) q1 P* ?9 q! c# a9 K ROut[123]:
& H/ R9 P9 f4 d) H, U. m2020-01-01 08:24:00 -1.750000 # 起始值,终点值包含最后一个值
0 ?+ q; h7 F: n2020-01-01 08:31:00 -2.6000002 {% m( [7 ~0 B+ J
2020-01-01 08:38:00 -2.166667
) p3 ^8 F" [' I% Y4 C$ h5 B/ }5 _! Y2020-01-01 08:45:00 0.2000000 d. p$ Z$ t2 O( {: T
2020-01-01 08:52:00 2.833333
% k# _5 C, h, {: x6 CFreq: 7T, dtype: float64& l# C2 z5 q: [7 v4 A( M4 n2 I/ a
1; h# `9 P) P/ c3 l! @
2) K# O: r) K" u# ^
3
& q& I2 l7 d2 L3 O' b4) w- Z; q' c7 Z0 i! g1 I
5
# H) u' G8 v2 _; y$ d/ L9 p6
# K: I& g2 I1 K5 a" v70 S: S6 o1 K0 a/ a; G9 }: I
8
& J+ O, _5 G6 z$ \* _8 _ 有时候,用户希望从序列的最小时间戳开始依次增加freq进行分组,此时可以指定origin参数为start:
0 X& d; [9 y6 {& n2 V/ w, A
7 l4 P1 A) \/ a+ l8 fs.resample('7min', origin='start').mean().head()# @& z8 |: q5 |, H! }9 r9 a
Out[124]: ' {5 P* I* x# ?) U- n
2020-01-01 08:26:35 -2.333333
! O- Q0 d1 W! a$ ~2020-01-01 08:33:35 -2.400000& k2 |* [' V9 h3 h1 `
2020-01-01 08:40:35 -1.333333
; B9 ? _5 H! v, N, {+ c2020-01-01 08:47:35 1.200000. {& @" n& o# s" ^# x
2020-01-01 08:54:35 3.166667
* \- Y- @$ |/ [$ ` ?Freq: 7T, dtype: float64' `7 X/ m% |. A2 Q2 R
1
8 X) S2 l+ F7 }* l. y2; u# |, i% {, H0 ?1 p
3
9 p7 ^ x$ ~5 h& ~( e- t: ]) \4" `5 \5 k. N* o% C4 Z
59 ^, F. L) n9 U2 u9 c3 M. O1 a9 S0 t
6
E7 s! x; y1 Y# I8 l6 ?3 E! a; G g5 H75 P5 x1 ~- R1 X, k
88 z d- m7 B7 P1 r2 s9 a. w0 @8 m
在返回值中,要注意索引一般是取组的第一个时间戳,但M, A, Q, BM, BA, BQ, W这七个是取对应区间的最后一个时间戳。如果想要得到正常索引,用’MS’就行。. g6 v; c3 C( D4 @: R& W6 Y
. U& L$ i3 P1 ~0 w
s = pd.Series(np.random.randint(2,size=366),0 R$ [7 O4 P1 X! h" v
index=pd.date_range('2020-01-01',
4 I- P. p" t; Z2 A4 F% n1 { '2020-12-31'))& O; A& u1 W. B- N2 M0 G- \
& `$ p% q9 [( I: g
. P' e6 ~4 z& L5 b% h5 is.resample('M').mean().head(); P( R7 ^% W& @0 f7 h* ~% Y
Out[126]:
4 o& W+ B; A; e' p$ ?2020-01-31 0.451613
0 F! r9 y( ]8 K4 z& h g% s U2020-02-29 0.448276
+ ]4 _) k9 x( x5 W! q0 V2020-03-31 0.516129/ D& [7 {- X5 z V3 W
2020-04-30 0.566667# y/ H2 s4 \* O+ l. r; K
2020-05-31 0.451613
+ Z/ R; d2 @' A( J9 O" k: GFreq: M, dtype: float64
3 C$ V# V* Z" K& c+ Q1 r2 y2 } M) g" @- M) o( W7 E# h) ]+ M) Y
s.resample('MS').mean().head() # 结果一样,但索引是跟正常一样- b( [& |( }( g4 \. I
Out[127]:
, e4 @* Y9 B6 u+ t: r3 C2020-01-01 0.4516139 b9 h6 k* X' }, G
2020-02-01 0.4482768 Z0 A0 m. s. [& K7 n( ]
2020-03-01 0.516129
. Z0 D8 B! N0 L$ y: I2020-04-01 0.566667
+ i) j4 x% |+ t8 c+ T9 d K: }/ z% U2020-05-01 0.451613
. n9 m' T' a3 m& L0 [" [Freq: MS, dtype: float640 }+ ~+ a, }+ L. m( ? T3 U
- ~( b8 H' C1 U" D% L2 H. a
1% w# H; {6 Q$ Q* Y5 Y+ O
21 n: V6 { q6 [# w' |7 y' J2 S2 W
3
) l6 e7 T. |& r, l/ t) J, z* z4
c& u9 l9 V8 {$ R59 p) i+ B, ?( ~: l' `8 P
6
9 ^; y* W: w7 o' B, s$ U% Z' \7
; n( n7 R) j) J ]2 r5 z8
% W' U, h" G7 j( q0 @* u9
: r. V3 @9 S6 U# C* @10
) K! S6 ~! d4 d11
1 ^3 r) e: h1 P3 y12
) H9 ]$ T1 ~) ~, Z7 i13
# I4 e! f8 {" E" B& f14
2 l# ]2 C) y& K9 @5 Z5 E% k15
: Z! @. T/ M, W169 v) }0 l% k: R
177 h1 B0 E2 e/ k5 \/ z# t, W: U
18
) R2 `8 {8 e! Q/ `19
! X9 ~+ b! C, D- X20
( M( U5 h5 u) v e; w21, q; ]" ?6 k: H" v6 F
22
! R: Q4 J2 S# j1 g对于 DataFrame 对象,关键字 on 可用于指定列而不是索引以进行重采样:' u* J2 g7 A$ ^! H, }4 O
d = {'price': [10, 11, 9, 13, 14, 18, 17, 19],- d( ]* P8 X. I7 _" ]" H$ ?
'volume': [50, 60, 40, 100, 50, 100, 40, 50]}
* Z0 g2 t6 e D1 kdf = pd.DataFrame(d)7 _5 ^" ?$ Z* s/ n
df['week_starting'] = pd.date_range('01/01/2018',* k! b P1 _$ u2 G+ E. B
periods=8,- k$ e {; l5 O) L0 M+ z( u
freq='W')
9 I5 u8 H1 M6 Y& L0 x( b( Q8 H/ ~df- E7 V4 j; f1 s0 O9 F/ m
price volume week_starting
5 s: r- I8 ~# |& H0 10 50 2018-01-07; s6 U. f( L X3 P
1 11 60 2018-01-142 P6 F/ e- x& Z3 f$ t% M, M+ v$ H$ ~/ k
2 9 40 2018-01-21
6 i% z, B8 k9 I% C3 13 100 2018-01-28
. W8 W$ W) D' w8 w4 {4 14 50 2018-02-047 k4 b, ]$ V6 i7 b( C% h' j2 K9 q
5 18 100 2018-02-11+ T& q8 B6 x5 p+ X Z" l
6 17 40 2018-02-18: H/ o2 D6 e8 B9 D) X
7 19 50 2018-02-250 t+ H3 O+ o( i% [: f
df.resample('M', on='week_starting').mean()/ d. [. a: S5 z% a/ P
price volume
' q% F' [1 o4 J. Q' Vweek_starting
( A, l* A6 Z- ^2018-01-31 10.75 62.5
% z) Z# U4 z# e9 l& c5 ^, G1 I2018-02-28 17.00 60.0
4 ^2 n1 ~2 ?. {; V2 G# D5 @( T3 R) q
( c! Q0 y! [ ]5 ~% @% E, ^* m1
4 i: V/ t E. X! ^2
. {- I! F2 Q+ ]# ]; t5 ^0 H9 g3
& l+ P2 e X7 x$ t3 l8 M4* [ }( k0 U# ^& u$ ~+ R5 |
5. W' c" a1 e% Q w2 z
6. P6 Y7 N+ Z# G- f) P
7
$ K! ~. B4 j3 K. ]+ \. W8
( a' v7 o" `. z7 N9+ r# H7 K9 ]" ~% ]
108 D+ y+ s4 t' ~, \
11
$ e) g( L2 z# R# P- ?# G, k12
8 ?. u+ m$ C0 ^. P+ o13
/ t! ` `4 Z9 t/ A. A$ Q+ w5 m6 ~14$ Z! H5 t8 f7 ~5 I8 S6 W: G
15; O2 M3 v8 Q( V) {5 G6 o n- m$ P
161 l$ J7 U8 g3 h7 Z0 F5 _
17
) J& U: s- T- E& ~. ^18
8 [1 x0 C: s t% m19
+ |: t& ~: m8 q U5 E0 J# N! `20
! o$ x* b4 P3 y4 {1 q$ P217 G2 Z+ I# y/ Z' K2 O
对于具有 MultiIndex 的 DataFrame,关键字 level 可用于指定需要在哪个级别进行重采样。' S$ R+ |/ h8 _
days = pd.date_range('1/1/2000', periods=4, freq='D')+ Y5 A+ |% }/ }
d2 = {'price': [10, 11, 9, 13, 14, 18, 17, 19],5 m% Y6 @' i3 v
'volume': [50, 60, 40, 100, 50, 100, 40, 50]}
9 I4 w. Y1 [' z) Odf2 = pd.DataFrame(
. l# J' f3 [' D0 Y* q% }" m d2,7 m2 W2 r, [) L. }! ?% l
index=pd.MultiIndex.from_product(& F5 x/ X. t5 Z& z9 O8 N
[days, ['morning', 'afternoon']]& `& z v( X) Z$ Z. j
)
2 l* r, S9 I6 E) Q; b/ P. P, v)
' K2 ~8 c/ u: Z7 udf25 q) M$ f" b' E1 s: j
price volume
- Q9 ?7 j1 ?! }' W$ p4 K, z: E2000-01-01 morning 10 50- C% l# L1 {* j/ U/ f9 _ r% ^
afternoon 11 608 c! L4 d1 F9 w) c4 C r1 N
2000-01-02 morning 9 40
$ p. t8 s) }% n* ]1 O$ n5 B afternoon 13 100+ P4 e* l/ p: R, O4 @1 t4 g8 X" O
2000-01-03 morning 14 50; `* {# j. E7 `5 a, l9 W
afternoon 18 1008 z6 _# ~6 q* ]& G# z4 r5 b
2000-01-04 morning 17 40
7 T5 P- V3 A7 p! j! H& X afternoon 19 50
& T9 A: F7 t2 s- }7 Ndf2.resample('D', level=0).sum()
$ q; H0 R1 b# E% U# E y9 z& b price volume
2 Z! C1 T+ h4 [3 ?2000-01-01 21 110
( w: t2 `4 x8 a8 {2000-01-02 22 1408 n, O( C' B0 g; A+ D! `
2000-01-03 32 150
/ U( w6 n" M1 P9 s5 T2 [2000-01-04 36 90
$ l) @& ]" G7 x& {1 m1 w
0 X. Q' @: T! k* B0 U& t1
! h0 `9 I* K1 H; q2
9 c" ^) |6 d" z* b) o6 E: b0 M3
- N% D0 T, J- ?# ~4, T$ r% m8 j! U0 @
5
f$ X, v s- O; V: }67 d$ x' g0 V( Q3 {2 i- Y. q
7* u* ?1 c) ]& J z
8
w( H, _) E9 l8 x; K9
6 h, l2 G# I4 N" m. ]. ~107 l3 ~( T: ^, Y. Q5 S/ E. [% w# f2 |
11
3 p2 a) f5 f/ l4 k, @129 h. `4 j: O7 d5 a8 i J
13
, D* F; E* ^) C# c) a. D; r6 ?- U14
# C# w2 ~$ k0 H5 t4 \' g15$ X. E; [2 c5 y" [# Q2 E, s# A- n
16
, H! t$ @3 ]2 i8 l# v' x+ [- \0 t17
/ C, q8 O, a) T; ^* _! \18
, c/ C; @) \* |# z4 h7 \" k19" F1 y! K1 B# c H
20
1 f! E7 [7 ~+ f8 t$ O @! {3 r21% C* ^( J* Y. \# i! {
22
; u3 K' j, Y b u; P% E0 U23( T, D* f" u7 x4 ]4 ]/ i
24. C3 [7 w9 y \7 S$ X' i3 s
25' Y' k% W( x# u. A
根据固定时间戳调整 bin 的开始:
7 c, z- R7 \+ dstart, end = '2000-10-01 23:30:00', '2000-10-02 00:30:00'8 A; K/ j4 v0 [$ b( y
rng = pd.date_range(start, end, freq='7min')
; _1 ~2 }) _6 }' n! i$ wts = pd.Series(np.arange(len(rng)) * 3, index=rng)+ C. m, G* z ?! g+ n
ts
% g: u0 {7 z: g! d1 g1 p2000-10-01 23:30:00 0
' `* o5 _/ G6 A8 K2000-10-01 23:37:00 3
3 u8 n0 `5 g6 X5 z/ {; _3 Z0 Y2000-10-01 23:44:00 6
' s5 W% k4 X# g6 X/ r! ?2000-10-01 23:51:00 9
4 c7 x& b& K! I4 P. M& Y5 U- g% r2000-10-01 23:58:00 12
" e9 ^0 X# ?# [2000-10-02 00:05:00 15
9 o6 {2 W2 w0 N6 c2000-10-02 00:12:00 18$ P9 j5 t% H( c3 }9 n9 C* X7 O
2000-10-02 00:19:00 214 J, F! E8 P) p" D
2000-10-02 00:26:00 24( q4 X1 m/ _6 x: Q
Freq: 7T, dtype: int64
* x0 c! s; e. Q+ M! B! M) C; q7 Z9 `7 W! J. U) |3 t; Z! T
ts.resample('17min').sum(), j# ^0 B+ H# _% `2 x( ~7 I6 f
2000-10-01 23:14:00 0" Y5 Z& U9 [, l* U$ c
2000-10-01 23:31:00 9
0 z t9 T9 O0 N2000-10-01 23:48:00 21
. H/ D6 G0 J+ W) u6 E7 B6 `/ o2000-10-02 00:05:00 54
; g2 U7 y% N1 K% d7 i2 f2000-10-02 00:22:00 24
( M4 e# {) s8 b/ d: jFreq: 17T, dtype: int645 j, l, u1 I4 q, `1 }( e2 d
) I( R; c0 G* b1 Y; P: [
ts.resample('17min', origin='epoch').sum()
2 ~- u* b3 `- |# j( t8 v, O2000-10-01 23:18:00 0: l4 j& x8 L" |: p& ~$ \5 [! v6 O
2000-10-01 23:35:00 187 C+ W8 h8 |: T; c1 j6 E
2000-10-01 23:52:00 27) A5 i$ ?) R6 j" j$ t7 l
2000-10-02 00:09:00 39
& W2 E2 n) x8 p$ M2000-10-02 00:26:00 24% W, b4 M" U3 _
Freq: 17T, dtype: int64
# y# V+ z" i/ w7 d: [/ B1 r9 K3 e, I) y; }; R s6 U' Q
ts.resample('17min', origin='2000-01-01').sum(): _) l, W3 \6 O$ |' |
2000-10-01 23:24:00 3# p" `0 P& y- Q0 E5 n9 A8 Y3 V
2000-10-01 23:41:00 15# J0 @6 I+ `) ] D+ s
2000-10-01 23:58:00 45
+ f( L0 W) ^1 k( h- h5 ?2000-10-02 00:15:00 453 B& I. q5 v" D7 h; r
Freq: 17T, dtype: int64
1 H; H( c; \/ c2 K8 c
" l5 `5 T# u! K9 f x1, _: W. k! h7 V3 e
20 t; |7 ]: M# P j* d
3
/ l8 S; z+ K8 M% w% _1 w) s43 q) i$ o4 d# B' c3 `
5+ F& f% f& d% J7 J4 g
68 R5 W0 x( O2 R9 _) B0 k6 r1 V
7
" a# Z1 L, z% T, u- [' z8
! O4 C4 u- I0 G# k2 ^6 k! h3 P; s9' H: _" O4 ^2 T/ V8 T
10
7 b7 h$ }8 J6 I) _4 S11
/ D7 q7 ]/ T' {4 V124 u& ?, S* X' w
139 C5 H$ O7 ?3 \5 u
14& P3 r% L1 I8 G% h( p
15
6 S. J& [% T8 A% b" O16 {) V9 r+ r- B- g
17
. o; h/ r" t4 g0 z6 x& p& O5 N( f7 l0 f18+ ?' k$ j5 o) W
19
. b) r; H0 k* S5 L# [. ~20( U" e5 T3 I# B+ B
21
$ L' ?# f# x5 A$ e# P22. ^" O, Y2 H# s, w4 G$ Z& R
23
8 I+ M4 }. D2 A7 {24
( v: l5 F. W( A9 z8 I25# J' `# E x$ n2 t" p
26" j# C. C h" u. Q* s
27
6 _& ^+ `3 K% m% g. W% c287 m8 a6 e5 A6 F
29' M" g: B1 N7 H
307 J; l3 a4 H, L/ {$ W+ f0 G. S$ E5 Q5 n
31
1 s9 V- |4 f+ t, s, G32
' O$ l2 ~% P& R9 j- N33' Q1 ^$ k) c% c, C# Q$ u1 Y9 b5 `
34
6 S: L% i& [- y* k# }. b/ e35) ^1 Z4 T5 L5 u3 X% P" [
36, o& I' ]: [/ @4 j# |
37
9 O1 h0 v" S, ]6 q; S& G" s8 ~如果要使用偏移 Timedelta 调整 bin 的开始,则以下两行是等效的:7 i! O" v/ P: K
ts.resample('17min', origin='start').sum()3 Q1 `/ B; e2 Q! U6 s5 E" g: E3 F
ts.resample('17min', offset='23h30min').sum()
8 h8 ~# q% G0 Q# C5 I2000-10-01 23:30:00 9
* `, W, ?+ }. O4 g3 G7 C2000-10-01 23:47:00 217 f3 @$ ~) s. k+ m; W
2000-10-02 00:04:00 54, s& ^. Z" {/ ~# Y0 C2 l
2000-10-02 00:21:00 24 p6 P: K0 T% ^7 o. G
Freq: 17T, dtype: int64
& Q7 W2 E5 L, e. J* J; U& h* R1
& `+ ~3 K: ~8 V2 j2
3 s6 _, o; I3 [0 ?" j# D4 S3: L. \$ {8 v% `. E7 C/ V# G5 _
4% h; {: \% ~% O/ t' ^) [
5
$ c: c/ Q" a. y; u$ F4 v6
' V' J$ \/ {2 A5 r8 \9 P. [! C7" S3 Q @+ b: c, f2 j% V
10.6 练习
; {$ k2 E* G4 o$ r* r4 `Ex1:太阳辐射数据集
8 J; [, ^4 [+ _2 {现有一份关于太阳辐射的数据集:
2 v# N# e* Z/ u/ F3 ~3 W3 }7 |
. b" p: L7 ~! B4 @2 D. ?df = pd.read_csv('../data/solar.csv', usecols=['Data','Time','Radiation','Temperature'])
9 c$ U. S9 \9 {df.head(3)- N. i* j+ R' j7 x
1 S' S" O( `- D' o, N3 v; w
Out[129]: & Z4 N9 h, C7 h
Data Time Radiation Temperature9 p7 x( w [1 t2 d
0 9/29/2016 12:00:00 AM 23:55:26 1.21 48
5 r9 _4 z! R# |5 n1 9/29/2016 12:00:00 AM 23:50:23 1.21 48
3 }9 N* \% r6 h2 9/29/2016 12:00:00 AM 23:45:26 1.23 48
) Q# G5 t/ a, @! w0 z) ~, [+ d1
0 a$ N6 t9 n3 {2
, \3 [' B! \+ ?( E3- p9 ]' I6 W# F# h: H
4/ m/ B3 c9 G7 r# \8 z) j+ b" ^
5$ v3 x& f% ]- l2 i; f. _' [
6# V% Y" Z% C7 X4 c
7$ J4 X c& |. H
8; J: M% t, i3 Y
将Datetime, Time合并为一个时间列Datetime,同时把它作为索引后排序。
* T$ W/ D# p* e* v" z/ S3 R每条记录时间的间隔显然并不一致,请解决如下问题:0 N/ w9 i* h8 t) r; @
找出间隔时间的前三个最大值所对应的三组时间戳。& [: I" f/ o }, D+ y8 R
是否存在一个大致的范围,使得绝大多数的间隔时间都落在这个区间中?如果存在,请对此范围内的样本间隔秒数画出柱状图,设置bins=50。! E9 r1 H7 U4 R' l) ?
求如下指标对应的Series:
9 a# `! H4 V. F% i8 K. C+ }温度与辐射量的6小时滑动相关系数: ?& a4 e. K% B$ b* U+ q2 a3 l- m
以三点、九点、十五点、二十一点为分割,该观测所在时间区间的温度均值序列8 ]3 i/ ^! J" M4 U+ S- e
每个观测6小时前的辐射量(一般而言不会恰好取到,此时取最近时间戳对应的辐射量)8 }) V4 d, j/ t* b% W% c
import numpy as np
8 I8 m! n1 n+ dimport pandas as pd2 o! N. c# [+ S! ]" M" O
1
0 I3 @' i; V) j! n2) _7 W' b0 E) C- ^
将Datetime, Time合并为一个时间列Datetime,同时把它作为索引后排序。
8 m0 T% I9 J8 O1 f3 A6 J. Sdata=pd.to_datetime(df.Data) # 本身是object对象,要先转为时间序列
7 ]2 H" G4 K# ~1 Wtimes=pd.to_timedelta(df.Time); t+ T6 H; C6 n" j
df.Data=data+times: c7 a9 ]- X, p7 R) W& ?. h
del df['Time']2 m/ c9 m, \* G: e* X
df=df.set_index('Data').sort_index() # 如果写的是set_index(df.Data),那么Data作为索引之外,这个列还另外保留. N; S, l, v" ]
df
4 p$ b' V8 P$ H4 ~ Radiation Temperature
1 j r/ Q& K8 F4 h4 U" |Data 0 L& v; I$ D% R
2016-09-01 00:00:08 2.58 518 T0 J. K) o; N' o |& B; v; _
2016-09-01 00:05:10 2.83 51
9 H2 R2 r0 C4 E9 `% _4 }2016-09-01 00:20:06 2.16 51% |: ]' H( ]2 o/ Q4 M8 x" t
2016-09-01 00:25:05 2.21 51
: H* Y7 }1 b0 `7 s P& `2 `2016-09-01 00:30:09 2.25 51
# @, r/ `6 F& G6 {% J3 d6 H( @... ... ...
) `5 o5 P Q6 u# u5 _# T2016-12-31 23:35:02 1.22 41
- ~' |/ _- F3 D+ j- h1 n6 E. r/ [6 j. s2016-12-31 23:40:01 1.21 41" J/ r( q; W6 b7 @2 y! U
2016-12-31 23:45:04 1.21 42
: B, A% Y: J9 T1 F; e: G0 N2016-12-31 23:50:03 1.19 41* n# N; e$ i' d4 l4 _9 T3 E. `1 X
2016-12-31 23:55:01 1.21 410 _* _# U. J* ~2 b' T0 v
+ S0 Y; ^) S. ^; V+ j
18 r/ \0 W! Q! {" \
2
S- ]8 A8 w- U( {; V/ l3' B' m5 e Y' l! C) v5 N
4
% H. X) u- k4 d5 i3 I5
7 Z k" A) P) _" u5 ~/ L+ M62 V4 M0 ]; b% F
7; @1 p* M- I* X* r9 L- e- E* D
84 H7 l. R9 @) U# C/ S' h
9
+ F5 T. g: u) A/ j107 K4 k7 \1 B: w t0 c& P9 w
115 J1 F* \2 O6 Z! D+ ~$ z4 ?
12
; u, h% y! ?4 \) B$ S13
2 c* r3 A8 ~2 C$ Q. x! z% |5 h14
, a) X# s a4 C$ v- E: N6 {159 \; j# d( `8 c: F& L& d8 k1 w) `
16
% Q8 b) E, a+ U: V5 i B17
1 `. z$ A! V9 W l! R185 [ n; O1 D- S9 r& g; f
19# g' J- `9 g$ v( _
每条记录时间的间隔显然并不一致,请解决如下问题:1 I1 M, s3 T* Q( h; r8 F, J
找出间隔时间的前三个最大值所对应的三组时间戳。5 B( N L: r( ?9 j6 y$ D
# 第一次做错了,不是找三组时间戳
' q* K& |* N( k# tidxmax3=pd.Series(df.index).diff(1).sort_values(ascending=False).index[:3]& w; [. D8 [; w |
df.reset_index().Data[idxmax3,idxmax3-1]* i1 A- N, h& ~! |+ @7 g0 _
0 C+ `! r" x: g& Z" ?25923 2016-12-08 11:10:42
. _' N4 [9 U$ H1 _, [5 L, r24522 2016-12-01 00:00:02; K1 i8 n8 L7 e/ V, I
7417 2016-10-01 00:00:195 @! ~3 r G$ |$ Y8 I
Name: Data, dtype: datetime64[ns]6 u. p" B7 h4 `' D- U6 O3 K* g
1* F* f. K) [% z
2( R' d( W3 ]4 l& L) E/ P
3
% P/ L8 y/ P/ A4# ^2 y3 z/ B/ n: T4 W" `
5" u$ a% Z2 H0 {, F, [5 J( u7 |( c
6
6 Y% Q* j. M: m7; {* t: E1 [' N: k7 j
8* l% G+ \- Q+ y9 M7 j% x V* e# d$ h
idxmax3=pd.Series(df.index).diff(1).sort_values(ascending=False).index[:3]$ n, e7 h D; N$ x- w/ V
list(zip(df.reset_index().Data[idxmax3],df.reset_index().Data[idxmax3-1]))
( \( X3 m1 b9 r F$ n0 [* {1 n( S6 B1 @3 \; |
[(Timestamp('2016-12-08 11:10:42'), Timestamp('2016-12-05 20:45:53')),
; ~7 D' K6 D/ }: ]2 m (Timestamp('2016-12-01 00:00:02'), Timestamp('2016-11-29 19:05:02')),
L0 K; d$ n/ x1 G% ~$ Q6 r* ` (Timestamp('2016-10-01 00:00:19'), Timestamp('2016-09-29 23:55:26'))]4 R0 Y/ W% q" e6 ?* V
12 f0 F) k5 O8 P( H4 X& b
2; @ q* _3 b& [/ L
3) b3 {' O) h$ g! W" A
47 Q$ L/ ^( o& |
51 k& u3 V% z- O7 z$ D/ L! P, V
6
, n; D( _7 v, `3 b W, ?7 _参考答案:
* X3 S p# Z6 O9 Z* o( R
4 T2 D# ~1 {1 J+ N9 X2 o# Z9 vs = df.index.to_series().reset_index(drop=True).diff().dt.total_seconds()0 m1 D5 {! K8 ]9 n
max_3 = s.nlargest(3).index4 y. w, g, T8 b( H0 V) B/ w: E
df.index[max_3.union(max_3-1)]" i& \' c7 O1 X! z# M' [' g7 s
! o9 ]7 k% _# B4 POut[215]: , k4 }$ F+ X; J0 H9 d& }& c
DatetimeIndex(['2016-09-29 23:55:26', '2016-10-01 00:00:19',5 g+ q1 H+ `, K9 C! R
'2016-11-29 19:05:02', '2016-12-01 00:00:02',
6 D3 v: O( }* F: D3 E& z '2016-12-05 20:45:53', '2016-12-08 11:10:42'],2 h3 q8 v5 p" D: F, z9 L
dtype='datetime64[ns]', name='Datetime', freq=None), Y7 J8 G# ]! E$ @4 ~2 [- S G
12 Z# u8 d9 H6 H/ D
2
; w# j5 y$ F) m' X8 ^, F4 A: v: ]39 |0 f( @8 ^" H) J2 w. m
4
! s5 m* u1 v& _- a5) g ^" t. e* c* K- S O& y
62 E# R; D/ w' N5 g- j$ ?/ ~
7
# N0 W3 `3 s8 U8
9 F$ t% B2 |, M3 _9
& D3 i* ^7 Q+ n! z9 R' B是否存在一个大致的范围,使得绝大多数的间隔时间都落在这个区间中?如果存在,请对此范围内的样本间隔秒数画出柱状图,设置bins=50。
: j/ H# ^+ z4 A) X0 P2 r) i# 将df的indexydiff做差,转为秒数后排序。再求几个分位数确定取值区间
0 h# v: z" j7 M0 e4 Ls=pd.Series(df.index).diff(1).dt.total_seconds().sort_values(ascending=False)
( k/ B( a- ^; ns.quantile(0.9),s.quantile(0.95),s.quantile(0.99),s.quantile(0.01),s.quantile(0.03),s.quantile(0.05)- |) r b7 j% y' A' s. s* a1 \+ V. {
6 \/ ~- V) U6 ^" a+ C' o: e
(304.0, 309.0, 337.15999999999985, 285.0, 290.0, 292.0)
7 Y4 M- Y* P* ^1
- \6 X! u, g1 M( Q# b2
% w% p# L+ j, `0 a4 D3 w% w( _% l( d) D2 I! s, L
4
# E b9 _+ h' n% j w, L: s# h5
2 f1 X9 d+ ?, Z$ M%pylab inline1 a( G5 Q; K# \7 [& X* T
_ = plt.hist(ss[(s.values<337)&(s.values>285)],bins=50)( T5 r+ e; Z9 C4 X
plt.xlabel(' Timedelta')
7 l- N# z+ R. b" [8 Q/ P1 X, lplt.title(" Timedelta of solar")& r, ~1 F' { t7 p
1- A5 W- S- {- D! E7 a
20 ~# c- s0 Y R+ \
3
8 z$ X5 ~6 w* m. ^. _3 w' d4. q- a& Q9 I/ B
# m& \ o( B' p, G# c
/ N0 E2 p" Q2 f
求如下指标对应的Series:
0 S8 v( v- W" s温度与辐射量的6小时滑动相关系数
* V6 ?& o' W- l! a, k0 N以三点、九点、十五点、二十一点为分割,该观测所在时间区间的温度均值序列 z3 X. t6 Q6 z' W0 d
每个观测6小时前的辐射量(一般而言不会恰好取到,此时取最近时间戳对应的辐射量) e6 F" {7 H( Q. `" ]0 Y
df.Radiation.rolling('6H').corr(df.Temperature).tail()
' m- U9 D# X7 R& v w* z5 a
" V, x4 C5 k8 K2 ^! {5 XData( S+ w; x" P/ P- |* f& v
2016-12-31 23:35:02 0.4161875 h" ?4 K Z! L
2016-12-31 23:40:01 0.416565. z5 K& b+ ^' o5 W
2016-12-31 23:45:04 0.328574
! t7 s5 V# O: T7 W2016-12-31 23:50:03 0.261883, s3 |0 L7 d( W& U; K; j
2016-12-31 23:55:01 0.2624068 F8 m$ ^6 L) d# |
dtype: float64
1 d8 v2 U; y7 Q4 P4 T2 p$ B1: J1 c' `/ d- P. \6 d/ h
2) ]7 z3 T$ a9 b2 v6 X& S
3% }2 L0 I6 A6 E8 j6 D; N2 Y, X
40 X1 Z- p# q3 d0 `6 G( L( I0 N
5' i$ |+ g; i5 Q) I
6
. `6 m% U. o6 h* v! ~: ~7
: T: X e! d' _% x$ y* i- k8# V" Z: _8 ^8 h' R8 L+ ~6 B6 t
9
! @4 w6 U2 @, V1 X. d7 M7 ddf['Temperature'].resample('6H',offset='3H').mean().head()
2 m. ~# E# |" r0 Z& n% R- B1 \$ {; |* u: ^
Data5 G/ }7 |. q8 h; K4 l
2016-08-31 21:00:00 51.218750
0 v; i, g3 E3 p$ J: `2016-09-01 03:00:00 50.0333330 [5 ?# j7 f. D
2016-09-01 09:00:00 59.379310
. w% B1 o& W- m* M1 H* F8 }$ o2016-09-01 15:00:00 57.984375
0 N* V7 p+ d, V2016-09-01 21:00:00 51.393939
. B4 }1 ^0 [# gFreq: 6H, Name: Temperature, dtype: float64; Q3 |. E; _2 e6 P) Y
1& `2 p) [' C ^* y5 M f9 F0 `
2/ q' l( f1 z: I o- |7 D
36 x2 ]9 Q7 Z$ s5 ^6 w2 r
4' {. K4 `& \: ]5 F; ^
5
) s m \5 Q. o4 N w, M: t& c6% w! J8 [+ _, I( _5 @
7
& u% y F- b) s) f9 b# g( W- O" w8
# O2 A3 S* K5 n/ J4 c3 I9' q: G; T$ l, e8 q5 j
最后一题参考答案:3 G9 v8 ^4 K0 k) }5 b0 W
* w7 U: T. g* T0 l9 W5 V# 非常慢% q1 W! J% `8 q9 ?8 ~5 p8 B4 H! }
my_dt = df.index.shift(freq='-6H')
! `" W+ l4 M- ?" D5 Kint_loc = [df.index.get_indexer([i], method='nearest') for i in my_dt]7 o+ K# e9 n5 P- v& |& t1 h
int_loc = np.array(int_loc).reshape(-1)* G% R# w; d$ U* c2 C% S$ \
res = df.Radiation.iloc[int_loc]
! o2 s4 j" o2 L% {# wres.index = df.index
2 {0 O4 W% c; d- M `0 W* Hres.tail(3)
( P$ @9 q$ _0 o0 ?1
7 W, s! r6 P, `) |9 F* b: ~2
. d; y1 M8 v5 a5 X3$ i# h0 g$ S# o1 `- j8 A
4
- S c* S9 ^, d5 v8 ?1 U5
( w& S l/ t$ }& G6: G/ K( H. \ M X& a8 P0 B
7- i4 s: I/ c H# N
# 纸质版上介绍了merge_asof,性能差距可以达到3-4个数量级( C! C0 N8 D: q9 M
target = pd.DataFrame(
2 ? ~# i* t* q/ o6 H {
9 C- x# s: U1 j% d+ Z z" [. b; N "Time": df.index.shift(freq='-6H'),
* x- S- `$ K& ?$ P& ]5 `" S "Datetime": df.index,; J6 W" U# B6 v/ \2 ]9 m$ p( G7 m3 x
}
3 E: d7 N9 }% O" })
5 h }2 H3 U: z- j
) {7 y# w5 B+ T4 L1 L( ires = pd.merge_asof(9 K, J9 ? s# \: ^6 l" P
target,6 t9 a5 g! k/ T! K
df.reset_index().rename(columns={"Datetime": "Time"}),
! Z7 e& l& }4 Q- B, |) r- _0 { left_on="Time",
; z) B0 t: Q R+ D- F right_on="Time",
9 @# p V0 C9 c( z direction="nearest"
5 F7 n1 I0 o" {).set_index("Datetime").Radiation
. V, a5 N I1 L7 W. V C
) P7 f. k/ X" {res.tail(3)
# E( i$ y4 R2 g: `Out[224]: 9 T7 y8 a( P3 i
Datetime! c( ~- x7 H& E& s6 @9 k
2016-12-31 23:45:04 9.338 t% J/ u9 \0 y, C+ a8 R
2016-12-31 23:50:03 8.49
& u0 M5 h0 g. Y. P5 k, n' A- t2016-12-31 23:55:01 5.84
" _5 |3 E F, ?4 {% ~- q$ n& XName: Radiation, dtype: float64
. Y) ~9 O# G0 K
2 J( L) E8 ?% Z! _1
4 l# \# _5 G, Z5 h S Y' f2
+ k$ _. r& I! s) C* X; Y# ?: D3
X' n4 _5 ^, G* U) q4: ]/ \1 n* _1 A u: ?& T( W( ^& E
57 i) {# ~- R+ p7 ^3 y+ i/ S! J
6
3 D7 c/ A& T+ d/ N7
- C; T# c; U" y8; j' d, v0 Y" f3 \
9. ?7 O K0 J2 f& i$ @
10& q# Z. h) c0 Y+ `( j
11
; f/ x6 a$ N* P6 x12
! x' j8 f I, Q: t13
8 |3 F% K# f+ `% |$ o# e14
/ i" l( R/ B4 w( J% p8 ]$ M7 l# b157 E$ {- |6 S: l& {8 p
16
5 [2 Y5 k+ v. m9 Y* ^# g& b17: f# b( b s. M7 r$ p
18
; Y, q' h6 t; ?# Z' f19- y8 c* E: Q1 }7 \( b
200 V7 M6 ^$ B o0 [: K) `
21
6 w0 i4 j0 J. ?22. Z2 k' B' K; U! R' t) C" J
239 t; R) Q+ @( f2 ^7 V+ f
Ex2:水果销量数据集. D; q) k0 @2 @1 F n# a
现有一份2019年每日水果销量记录表:
; @, m" Y4 h7 A
* F5 i3 d2 K C" u: e5 F' Ydf = pd.read_csv('../data/fruit.csv')
# \; |% Y9 H3 O6 P. r! Pdf.head(3)9 `" I% z3 o. m! a- q7 }
+ n! X5 O, _+ b. `1 W/ Z# |- h6 _2 COut[131]: % x4 L6 a* `6 q; [ \
Date Fruit Sale
" s$ T0 R6 A* A0 ]0 2019-04-18 Peach 15
" D) i6 z* b- M4 z5 w. R; i% h1 2019-12-29 Peach 15
- s0 R! f+ O; [2 2019-06-05 Peach 19: Z) T+ y7 ?0 k* q" t+ l
15 _$ h/ \7 s* _
2
; H1 `5 p0 K$ R5 i* {( y: e- ^9 q% v3
0 c6 T- ~3 P2 Z1 L4/ T1 N$ s* |+ V, N& e' _
5! E3 O( R" G7 l! ?2 h6 n- a
61 X5 e- x, l. B% l5 q* M
7
0 |2 F3 E, F6 a b8) l7 B9 V3 B; C) Z! w9 A
统计如下指标:4 I" Y) O- O$ T& k
每月上半月(15号及之前)与下半月葡萄销量的比值" g0 ~9 H% y5 f! _
每月最后一天的生梨销量总和
) E) o+ V& q7 g; H每月最后一天工作日的生梨销量总和7 ~# G- c$ j- S5 O& B2 s* ]+ `
每月最后五天的苹果销量均值
# }4 i1 `* l8 T+ M2 @按月计算周一至周日各品种水果的平均记录条数,行索引外层为水果名称,内层为月份,列索引为星期。
/ g4 ^: n9 u( K2 p5 L按天计算向前10个工作日窗口的苹果销量均值序列,非工作日的值用上一个工作日的结果填充。
( l9 v" p, a" \1 O) x) k. `import numpy as np4 X* C+ Q! [# v7 c- S. T
import pandas as pd- N; K: d# S( ~: K
1
$ ]! a8 c9 \& k( C( @2, d( n6 Z& _) R# u- l* Z
统计如下指标:
- `, X: {1 m, s5 V6 N' l每月上半月(15号及之前)与下半月葡萄销量的比值9 O3 R2 n4 { t+ [. r R% H
每月最后一天的生梨销量总和% E, q! X2 C- J9 }) y' |( r
每月最后一天工作日的生梨销量总和
, p4 A4 g' b8 F. ?每月最后五天的苹果销量均值
( L) L7 D: W" u# 每月上半月(15号及之前)与下半月葡萄销量的比值' a6 K: Z7 k* \; V; W
df.Date=pd.to_datetime(df.Date)
: Y$ Q7 i: P1 O. d$ k) `$ osale=df.query('Fruit == "Grape"').groupby([df.Date.dt.month,df.Date.dt.day<=15])['Sale'].sum()
( ^9 y) Y" D7 i8 gsale.columns=['Month','15Dayes','Sale'] # 为啥这么改没用啊+ Z: ?/ C0 k5 l' U5 g4 k' ?
sale=pd.DataFrame(sale)
2 h6 C; _' I- C* b2 D2 y% }sale=sale.unstack(1).rename_axis(index={'Date':'Month'},, } p" @! r0 F; _" m7 ?- [8 v
columns={'Date':'15Days'}).stack(1).reset_index() # unstack主要是两个索引都是Date无法直接重命名9 h& P3 S% d! h/ J; d
sale.head() # 每个月上下半月的销量
9 G3 `1 g3 g/ c7 q
* G9 @% E' y1 ]2 M2 Q Month 15Days Sale
* l" o. n4 ^. L0 1 False 10503+ b% z/ p* n6 k
1 1 True 123419 c3 U P$ [( r" p h4 k1 V
2 2 False 10001
% q0 X( E; l7 ^9 v% P3 2 True 101068 U5 k: k, c/ u1 `6 K$ N
4 3 False 12814; M0 q3 d6 j. c; a4 u. H4 {
0 Q, [8 G' v2 Q; \# 使用自定义聚合函数,分组后每组就上半月和下半月两个值,根据索引位置判断求比值时的分子分母顺序& [5 j0 M! ]) j
sale.groupby(sale['Month'])['Sale'].agg(
- ~; C& N: S2 n1 q B9 p# K" I I lambda x: x.max()/x.min() if x.idxmax()>x.idxmin() else x.min()/x.max())
! y5 m0 P( O" g! w4 V8 F; B( k$ S, h
Month
" p* r" f: |" J1 1.174998
; d/ F9 H/ @; e n2 1.010499& R( [% V5 n1 r; I1 h
3 0.776338
5 [# D1 A4 \) a3 ?4 1.0263458 i5 `' e( a. U, J0 I
5 0.9005342 I. W' S& z3 l% c
6 0.980136
! u5 U2 E2 s0 |( F# t' s7 1.350960% X! y& W3 |4 V
8 1.091584
4 }% \8 O& I% V6 l9 1.116508
& S2 W% ]; k: C10 1.020784 G0 L8 Y" [+ i! f' S* A
11 1.275911
/ I0 ^+ O4 I q0 T3 B) R12 0.989662
5 ~0 D4 O7 k3 ~8 j# XName: Sale, dtype: float64
9 l2 q/ h9 ?; m+ r! c4 B
. q0 E! i" J9 n( d& C$ U1- o( O5 h D% m3 z' x. u
2 Q' E5 L# I: K, O& C. [) v" M
3
( j& c9 p& Z( y2 ~40 S' q7 m6 u7 ]) s8 n
5
& W) i: [6 ?1 j: t+ g6
( B% l( _7 q4 N+ w1 g4 \9 M7* s$ f+ s2 W3 [4 @8 ~* d
8
/ P2 @& U$ x' N! y2 R& o9
- C! P% p; ?2 u3 _ q$ m103 D& Q5 m3 v7 Z% b0 s4 C
11) p) p! b$ I' t4 B' Z4 ^1 h$ S" V
12
7 `( j! w9 r+ b# t0 e) D3 \2 ~13
5 D0 C$ H4 v' C+ M14, l- z f3 H4 n
155 a) Z0 ] f+ s! J
16
) `0 p" u8 j9 E% w17
! r* s% u# b# y" |) Z18+ N ^+ t, P; i0 ?" C% ^
195 u. v1 ~: a3 |1 |
20" [& N. ]5 h) C% [. x0 `9 }; g
21
7 _. ]9 P* b6 [4 L7 z; X, ?22* e( V- ]3 b. a; e
23( Z7 G# L8 G/ H: Z2 n) L+ ]- ]
24; f0 i8 l) H$ \
256 _6 ? u" h$ f' S7 B7 @5 o) b
26
t0 D8 r% l { p27
4 d6 }% q# n" B1 Y6 g28
3 Z0 }( s3 [& @0 m; R# t295 k# ~8 y* r" Y8 @$ _1 ^0 |, }
301 s" Z/ P$ j# \5 V8 y8 K# l
31
( `4 R6 X# l* ^2 D5 h32
' Z7 C$ t4 I0 Z, J336 C# ^* p; h* |7 `; l# z" x
34' N, q% W4 A/ H. n7 \6 H& {
# 每月最后一天的生梨销量总和5 e* @$ I/ g8 \) N
df[df.Date.dt.is_month_end].loc[df.Fruit=='Pear'].groupby('Date')['Sale'].sum()2 s( c. j$ v0 i9 j0 {
% N# u9 X, y/ o/ R, b& H5 ^/ YDate5 P9 ]( k4 E6 |4 z, G- J( t0 N
2019-01-31 8471 ]- c; b% M V4 t
2019-02-28 774
7 q& w( A: v8 f3 h' q2019-03-31 761
2 U1 ^' e; Q/ p4 A" ^2019-04-30 648* A4 \4 i( h; B$ T
2019-05-31 616
' T( \' J) A" I- c9 _10 F6 ?) G/ G4 ]+ \: _
2$ w6 U1 R2 `9 ^5 H/ C
3# v) w S5 O( F# {) m+ s
42 y- D$ R- q8 G! I
5& R/ v5 p- e; @7 S
6+ G0 q: I, |9 f, B* G3 J8 H
7$ _% L. ?+ }# q4 G) g5 ?
8
% o6 _7 P" u: m6 u" K96 C$ S* B/ q8 B0 w: a
# 每月最后一天工作日的生梨销量总和% [; n$ O5 T% P% @% c7 R
ls=df.Date+pd.offsets.BMonthEnd()
) [: [( j# |. b% Cmy_filter=pd.to_datetime(ls.unique()). R, ~0 C. v( B+ C
df[df.Date.isin(my_filter)].loc[df.Fruit=='Pear'].groupby('Date')['Sale'].sum()2 S/ M) f) v5 y
0 s7 R+ Z( D) ^1 f* _+ KDate0 j& _! [2 X/ V s( k
2019-01-31 847
1 r& ]( T9 _/ ?2019-02-28 774/ N* Z, f Y9 a" R# A
2019-03-29 510
+ Y6 v9 }$ h* J! R0 I6 n2019-04-30 648' c K* W" i6 S* O6 `* P! g. k5 y! @
2019-05-31 616: g: l" b7 {8 { k- S/ p4 o, c
1/ M# T3 q. L c! ^
2
: c1 ?5 o9 Y- N( F33 O- h) A N$ D; l4 T
4( A' K' j( R) t6 Q2 h
5$ O9 S8 w( B7 A7 i
65 [( M7 G; h) o
7
- q5 W9 p, x) m8
& D+ I+ h6 D4 \/ x) j9
5 A% W) ^% O3 F10
' Q+ a) q) x1 b% s: K& t- m11
5 y. S* _% {% U% e- J' a3 g# 每月最后五天的苹果销量均值4 o: E5 A& f3 E8 ?8 V( @' x
start, end = '2019-01-01', '2019-12-31'* @8 ~$ c4 u: S4 [# h
end = pd.date_range(start, end, freq='M')
0 V8 o& \% k$ m; z- kend=end.repeat(5) # 每月最后一天的日期列表,重复5次方便做差8 A/ r5 z' E6 s2 L6 j5 E2 e k
% U* w: [/ @1 ?5 W+ y0 H5 L
td= pd.Series(pd.timedelta_range(start='0 days', periods=5),)
) A4 K+ B `7 ?0 n* y- X+ _td=pd.concat([td]*12) # 日期偏置,最后一天减去0-4天
0 `, h: |0 Q, }8 ]9 u4 eend5=(end-td).reset_index(drop=True) # 每个月最后5天的列表
7 d- }' i1 M& Y7 N" U# w) F# K3 e# @4 G1 N2 R7 _
apple5=df[df.Date.isin(end5)].query("Fruit == 'Apple'") # 每月最后五天苹果销量; f: v2 t( q) ^1 E2 y8 J. D
apple5.groupby(apple5.Date.dt.month)['Sale'].mean().head()0 y: z+ {9 x8 X/ `& t
2 ^! j( W1 r* x" W3 L# c0 gDate) z8 U* \0 R) P" D4 S- q
1 65.313725
- w: B; K* N. z' P2 x. s5 w2 54.061538
/ k E5 g/ U/ i& i0 S3 59.3255810 u& R+ ~" M8 H! ]& l: N& b, R9 } e
4 65.795455; Z O/ Y( w8 d
5 57.465116
# `; a f+ n; w: |
0 Y3 A2 S( ?: S& V# a1" O a. Q1 k; C U. k5 F
2
) Q: _5 H% ] ?1 @& Q9 A3- s- z5 b; m& r* [8 h# m1 p6 A0 @8 i
40 P5 L7 Y- x9 s( G: M$ w0 w' L; T( B# h
5
0 @. \- c+ B$ S0 o Q1 F62 W+ D1 y) v* b; w$ s b
7 A% d- g5 A3 ]; m+ h, O
8
( v1 T l$ }# ?& @' e& M4 c S9
7 {, D, ]% P/ z$ d# [10+ F( b8 D% W, u
11% b. b( i: K! K+ Q, v4 m& \. {
12+ C- ^. b7 a7 Q8 j
135 Q) o( y' o4 K+ n9 o
14% z- @, \; U, j7 f5 v
15
! Y# h0 H! k4 Z4 E165 t4 K6 ~4 O8 z1 L
17
; O4 J8 o6 m, M; @! g188 t1 P/ ~1 Q! b. R( z- G x
# 参考答案:' R" l* _! Z) ?3 s% F
target_dt = df.drop_duplicates().groupby(df.Date.drop_duplicates(
* g. u( d- W. P. f7 R ).dt.month)['Date'].nlargest(5).reset_index(drop=True)
" N3 x y, P, G1 s" C7 t
5 m y9 J8 q7 N. B' h! ires = df.set_index('Date').loc[target_dt].reset_index(% K* [& z; @ c8 F; m4 A L
).query("Fruit == 'Apple'"). q' Q- i- N0 u+ D: f/ {. Z
8 O/ ]4 J o3 {% j2 X: Y
res = res.groupby(res.Date.dt.month)['Sale'].mean(
0 a& |6 [2 F& l* r. v3 | ).rename_axis('Month')" X* q+ v, r5 y8 W( l9 [
* m. h% n, Y, V9 D5 ^7 I, j/ t/ t
- d; R. B, X; h! i) vres.head()3 F8 h! W8 p% X4 I# k7 X- f! R
Out[236]: . W' e; v q% c
Month( _; i* Q- f% ~7 C
1 65.313725
# w: S/ H; `, \8 S1 H7 k; r$ y2 54.061538. w# H" ~( K4 H4 A& {& l+ ]. t
3 59.325581 g+ F P# R9 r3 }0 c
4 65.795455
5 S1 f H, x6 U; Y7 l2 N5 57.465116/ {' m% r0 _' Z
Name: Sale, dtype: float64
% z& k4 X* n" B- d! q7 E% [: s8 O
% x# p# U8 U* ?% A: t- u4 l1* }4 d: U: E8 z. n
2, R% l8 e9 _+ m% z9 Z& d
3+ |' m! ^. I+ Q( K1 B
43 g) B2 e& j ]) q) K& U
5( }3 y9 w+ P& i: }
6/ z" e3 g; w- y, t, c
7
2 Q/ J( A9 H( [$ q8
7 t# O- Y- A0 H/ ?2 g( I99 u' S2 m! k/ v C6 C: x
10; b8 N _2 L" J7 `5 |* {' V
11% o4 b# J4 t) Z: U+ B4 v1 X5 w- M' x7 D
125 {. w V' s" \( G* i8 `7 e
13
: E1 S$ Y) o1 l2 L1 v( {* c14' y9 Z& E) o* M
15
6 M8 [0 b p' J9 d X0 l16; {/ M ]7 |7 \2 H
17
( M, F" D% ?6 l' v4 O6 O18- @' b: c; `* S
199 a5 g4 K1 m. ]% L. i7 H; T
20* m3 l/ k% ]) u5 x1 _' w+ j2 q- N
按月计算周一至周日各品种水果的平均记录条数,行索引外层为水果名称,内层为月份,列索引为星期。- k9 H6 u+ T1 \: Y0 b( o) n
result=pd.DataFrame(df.groupby([df.Date.dt.month,df.Date.
( A" _7 @+ J" Y7 Y# K2 ]; w: `" t dt.dayofweek,df.Fruit])['Sale'].count()) # 分组统计 0 [5 W4 j# R$ K" {" x
& K0 D- q/ |! }' ?* L
result=result.unstack(1).rename_axis(index={'Date':'Month'},
% O7 g0 h9 w0 X% r% o1 \ columns={'Date':'Week'}) # 两个index名字都是Date,只能转一个到列,分开来改名字.1 E! O: f5 F. G8 q1 e8 {7 D7 L
result=result.swaplevel(0,1,axis=0).droplevel(0,axis=1)! _. U5 k2 B+ v, x# W' U
result.head() # 索引名有空再改吧
1 h: \ }5 S6 p5 X
& k. e% X: n& C1 z" u9 J. |, Y Week 0 1 2 3 4 5 6
) r- v) G6 d0 B, y- \1 J3 ]( pFruit Month 0 k4 q" ]9 P8 z) u& _# [2 C; y& P
Apple 1 46 50 50 45 32 42 23
' b. C* z+ b5 c# O2 ^Banana 1 27 29 24 42 36 24 357 m( M ?1 n# U2 j" s# S4 T9 X% I: Q
Grape 1 42 75 53 63 36 57 46
: v5 K* ^1 J# Q ?Peach 1 67 78 73 88 59 49 72
+ z! `% ?$ a5 C" i; `; p* ]- SPear 1 39 69 51 54 48 36 40
% W2 q! @; Q$ Q. i+ n# E; q1$ c% }+ `1 o$ D* Q
2
! F3 v7 U1 w) s3
, F2 R: T0 o Z4- v) M$ T8 u) T
5
3 J2 X, J* q9 }( Q7 _6& q: o2 @& E8 |: W
79 V: H% z4 p! r! e" f
8% e/ x$ T5 A2 Q' B# D3 T* v
9
3 R& F* p& ~, O% [1 @- n2 E$ r10" b9 s& P% K; E! P: _8 N# ~
11
, S% E: C4 x6 c" I( E12: P7 {5 t7 M7 { M8 }
13
- m, M7 @' c' @. K' t14- p# j/ c8 g7 I0 p1 k C3 Z4 s5 S
15
7 F' c/ g& I5 a; H. w按天计算向前10个工作日窗口的苹果销量均值序列,非工作日的值用上一个工作日的结果填充。7 l. ^, L: a M4 C0 |2 V
# 工作日苹果销量按日期排序
" @0 _4 ~ ~2 \, k9 V5 z1 a* s! Y" K; xselect_bday=df[~df.Date.dt.dayofweek.isin([5,6])].query('Fruit=="Apple"').set_index('Date').sort_index(); L5 U4 Z e3 _3 _/ v$ H& h. z
select_bday=select_bday.groupby(select_bday.index)['Sale'].sum() # 每天的销量汇总
3 P& m+ _, h# b7 e" cselect_bday.head()
$ L) l7 R# {3 n
! Z( K+ e6 j3 J5 P/ fDate5 y: { P) C: c+ i' J/ b$ s1 e
2019-01-01 189! N, l# J; ]. k) u4 V
2019-01-02 482
' ] e. R+ k; F9 c; x& t- A2019-01-03 890
, W' e8 m i/ z/ Z+ [* |* B8 {2019-01-04 550, |0 r" y' G# J0 U
2019-01-07 494- f; H2 J1 N3 y5 U+ c8 j
' ~& ^5 m0 X+ E4 ]6 U# 此时已经是工作日,正常滑窗。结果重设索引,对周末进行向后填充。
# C2 f% v) l$ ^4 @) A' Lselect_bday.rolling('10D').mean().reindex(df.Date.unique()).sort_index().ffill().head()
$ j& i; o( q: N+ w6 _9 Q) O+ `* o; {$ y
Date4 s* e# M# ^3 y* ^- K0 X
2019-01-01 189.000000
6 S! M+ w9 A* q8 W1 m2019-01-02 335.5000005 @. u5 }- z2 ?( ~0 Q; s8 ?
2019-01-03 520.333333
+ i, k. `, M, { }: T& [2019-01-04 527.750000' {1 I+ n. U y, Z
2019-01-05 527.750000
; U' q7 E5 L$ r; W/ Y( d1 N0 d- g' X2 m& H& l$ ^2 f$ z
————————————————& }+ G* E8 O0 U
版权声明:本文为CSDN博主「神洛华」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
% M3 |4 A- P! L9 \; }8 x原文链接:https://blog.csdn.net/qq_56591814/article/details/126633913
) y. E: N r# d% A/ f
( W8 |. b' V4 T& l. L0 \! _0 K" u3 U) X
|
zan
|