- 在线时间
- 1630 小时
- 最后登录
- 2024-1-29
- 注册时间
- 2017-5-16
- 听众数
- 82
- 收听数
- 1
- 能力
- 120 分
- 体力
- 569584 点
- 威望
- 12 点
- 阅读权限
- 255
- 积分
- 176098
- 相册
- 1
- 日志
- 0
- 记录
- 0
- 帖子
- 5313
- 主题
- 5273
- 精华
- 3
- 分享
- 0
- 好友
- 163
TA的每日心情 | 开心 2021-8-11 17:59 |
|---|
签到天数: 17 天 [LV.4]偶尔看看III 网络挑战赛参赛者 网络挑战赛参赛者 - 自我介绍
- 本人女,毕业于内蒙古科技大学,担任文职专业,毕业专业英语。
 群组: 2018美赛大象算法课程 群组: 2018美赛护航培训课程 群组: 2019年 数学中国站长建 群组: 2019年数据分析师课程 群组: 2018年大象老师国赛优 |
8 v% a6 C8 q, {7 a. {
# \$ A% I2 e$ y5 X( e: ]: f0 L: E% y+ G7 w
文章目录% v. ^$ L- d4 ?" y+ A
第八章 文本数据( `* s7 f+ F' c- M' R
8.1 str对象
. w, }0 O- o; C k8 J" X8.1.1 str对象的设计意图; c! N: I- U1 s r' ]
8.1.3 string类型2 ^! A1 R* R9 X! ~& p. U6 d3 M
8.2 正则表达式基础
/ E5 v6 b! a4 F$ @, P1 U: V8.2.1 . 一般字符的匹配
0 T R, z' E# j4 `9 i$ \8 @8.2.2 元字符基础7 M: j# Q9 R5 [5 l5 E
8.2.3 简写字符集7 p" e/ ]' [. Y) j- m) }
8.3 文本处理的五类操作0 f% i: Z" R" R9 H/ |# I" B
8.3.1 `str.split `拆分
: i0 I* D. M! W3 v8.3.2 `str.join` 或 `str.cat `合并5 N# J4 x9 Q% K% f& [
8.3.3 匹配- u- I) l- {( N$ U9 r
8.3.5 提取3 K I- d- F. H) w; [1 X
8.4、常用字符串函数
. l+ b5 ^0 L7 P) C5 w8.4.1 字母型函数
, k6 ]6 S& A) s' ~+ p, g8.4.2 数值型函数# N6 U6 [+ c8 t) g9 m
8.4.3 统计型函数# Z. S+ g1 S2 @- h
8.4.4 格式型函数& [4 I3 x. L; j, v! C j D
8.5 练习
6 ~& y# q* N8 V4 W4 LEx1:房屋信息数据集
0 @7 V' j. y5 C C% c7 UEx2:《权力的游戏》剧本数据集: V6 J0 a3 H0 _( s3 T8 N8 H
第九章 分类数据- W3 k. s" `2 p- U2 d, j& \) _5 f
9.1 cat对象
/ E: y4 c# m7 G2 [' K' U9.1.1 cat对象的属性
1 Q4 f- n7 p! H" L9.1.2 类别的增加、删除和修改
( M6 Q8 S( {5 K9.2 有序分类
- p" V: |+ m0 i& U7 u' a9.2.1 序的建立
9 {; e/ Y2 H2 A- }; X9.2.2 排序和比较! C2 z! W/ G, t
9.3 区间类别8 ]7 g# s, I) c3 |
9.3.1 利用cut和qcut进行区间构造0 R. r) \0 V- Z) X* x$ M7 N
9.3.2 一般区间的构造
) N3 i) Z2 I7 V5 v+ }9.3.3 区间的属性与方法& z5 y( v% N* c$ a% {, b
9.4 练习3 \1 I9 _3 _, L5 c0 a
Ex1: 统计未出现的类别
5 _2 T0 Y& |) b/ v9 AEx2: 钻石数据集7 b# B9 ~4 R% T2 A% c2 R1 f+ [% _ x7 U" X) U
第十章 时序数据! f2 y1 m, \1 d, _4 Q7 e
10.1 时序中的基本对象: t6 e# k! E2 K% W
10.2 时间戳
$ L* u+ ?/ d2 O4 o4 a4 g5 ~10.2.1 Timestamp的构造与属性* z# X+ F& J# w# X) l
10.2.2 Datetime序列的生成
L# C, i6 ]& b) F& m% R9 q10.2.3 dt对象$ m% M& u& n9 ?( x: _
10.2.4 时间戳的切片与索引
$ x g" m3 ]0 r' P/ }10.3 时间差# w& T; K! Z( J& b$ X! c5 I
10.3.1 Timedelta的生成/ N$ x) z+ D& h4 E9 o
10.2.2 Timedelta的运算' ^5 J4 S- [' _
10.4 日期偏置6 i( I* A" a3 M$ j d; i4 G2 C: k
10.4.1 Offset对象* ~1 j! P/ O7 L; t" W. P
10.4.2 偏置字符串' ?$ l5 J* o: p) i- @
10.5、时序中的滑窗与分组- x; S7 Z8 Z) j8 z) u
10.5.1 滑动窗口7 e0 X( b; p; D: Q
10.5.2 重采样, t3 z2 A! t8 s+ [
10.6 练习
+ B# z% I# J: HEx1:太阳辐射数据集
0 N2 f& O5 m# Q9 u( [/ kEx2:水果销量数据集
8 \$ z9 Y1 A" [3 A0 Q/ X: J 课程资料《pandas数据处理与分析》、github地址、讲解视频、习题参考答案 、pandas官网
" J- _+ z$ z- `1 h0 l传送门:+ c# |$ a3 e0 Z
5 ]4 \7 P2 k3 s/ `! H( Rdatawhale8月组队学习《pandas数据处理与分析》(上)(基础、索引、分组)! w" Z$ Z% k- `
datawhale8月组队学习《pandas数据处理与分析》(中)(变形、连接、缺失数据)
: t; G2 N# G- S R' r! J, m第八章 文本数据# G: I7 k, w) t& O* G4 m: {% H
8.1 str对象5 n3 W, F; E8 p5 V6 y ~
8.1.1 str对象的设计意图
+ S: E7 V+ P" [. u# b8 H str 对象是定义在 Index 或 Series上的属性,专门用于处理每个元素的文本内容,其内部定义了大量方法,因此对一个序列进行文本处理,首先需要获取其 str 对象。在Python标准库中也有 str 模块,为了使用上的便利,在 pandas 的50个 str 对象方法中,有31个是和标准库中的 str 模块方法同名且功能一致,例如字母转为大写的操作:
7 S& `3 ~4 H' N+ {; V2 B1 x
5 A* e) G [2 ?3 X4 @% o4 `2 p2 lvar = 'abcd'3 ?4 Q: \. i# R( M
str.upper(var) # Python内置str模块& ^" y3 {1 X: v' B {) q1 z
Out[4]: 'ABCD'! l7 \7 q$ |3 O6 g' H0 J3 F; O
+ L3 y; v! N# W+ c6 r; @% U: o
s = pd.Series(['abcd', 'efg', 'hi'])
% X& {- ^6 R0 T
/ Y; n- G8 ^9 I% w+ A5 ?( s9 ss.str! {9 h- M4 [' i* B, T+ q0 v
Out[6]: <pandas.core.strings.accessor.StringMethods at 0x2b796892d60>
P N* a% }4 ?0 c; M& r- F; a' N6 {2 l6 O( l/ Y
s.str.upper() # pandas中str对象上的upper方法+ [6 z; i; y2 q* a* @
Out[7]:
: X4 V3 N. b* D O0 ?! H& a0 ABCD6 K2 O ?/ L3 W$ \5 V
1 EFG
2 J7 D8 C1 O, s1 J; s2 HI6 u% N0 `! |: ]
dtype: object# f7 g: I" {" \. o
1) U- V5 D0 \& E5 Z
2! X) P [$ r' u6 E8 h
3
4 T9 {! n+ z" L' D5 x# i- v4
+ h+ m) O# E0 y; z; v5: U0 w" e, l+ ?
69 k, Y3 C0 u. _* p" h$ d1 q
7
9 B2 B$ w: h- q* t; h0 {: l" b8
$ [$ m9 ~8 j- G6 f/ B" G0 o9
. p3 W9 R5 |8 y, }# ~10
/ _$ n1 ]" O9 R- e) A11' x/ S% {! V% ?8 U9 j& A
12
$ w8 h; I( J: C/ T1 U139 V. N. V. K) K4 P3 h1 K
14
' w r! f4 W/ S- s152 p+ U6 p) o- `: S( M5 b* a# ?
8.1.2 []索引器
5 y; [- v" W3 b( K) @- C( ]" m* [ 对于 str 对象而言,可理解为其对字符串进行了序列化的操作,例如在一般的字符串中,通过 [] 可以取出某个位置的元素,同时也能通过切片得到子串。) A2 n4 X; t/ U3 g
pandas中过对 str 对象使用 [] 索引器,可以完成完全一致的功能,并且如果超出范围则返回缺失值:
6 f0 J- y1 O, Y* n& G
/ N* a( c9 m+ [. a. Ns.str[0]
: v; P8 x9 \" ^3 Y4 o" L* t; YOut[10]:
* I7 K( j( X# L2 }# Q0 a
' z3 ?1 ?' n- s1 e/ v g) W( x! V
2 h
& A( z( J4 E3 `$ S# t) Y5 i: Mdtype: object
- y9 v0 F- K1 J0 i1 n
- Z& J$ l1 h1 ^, Q! K2 os.str[-1: 0: -2]
8 \- Y" n+ J" X0 r* Y8 ?+ @, D4 A# DOut[11]:
8 }' z5 |: F9 ?& S0 db
# I6 F8 b @+ l9 h( j( v$ H' C0 i1 g
* d# l! S v6 o6 F! J7 _' L2 i5 q5 V: {+ V3 V* |0 k1 B2 k/ m
dtype: object# D# F' p, p8 q% n! E3 o
- d4 @( k3 o, M/ v j
s.str[2]3 ?% t: r2 s) u0 y- a6 j& `
Out[12]:
' B( l% }; M+ g! n- c6 I& D0 c7 a2 e! \* l) h5 l# L" {1 c$ i
1 g
# U6 v& I) w( O2 a: b8 [( S2 NaN
! r8 ]/ D/ J" s2 S, [5 Xdtype: object# e+ F2 x% g- t: f2 U
' T: f; y! Q; C6 [1
' g3 r3 |- h n) L5 i1 U1 r( b0 t2
9 Y. C# r, T2 x" n7 _3( J4 |$ ~1 W1 l) A
42 I' G, b. v# q: x$ k
51 q* C2 ?& s: g+ N% I( I3 U" a2 T
6
3 W- ] N+ D5 G3 ]4 c7
+ x& \$ J! x* W. q8
" c' I# o6 k" B4 k. W" ^9
) s9 ^( t" ?# F3 B10
3 E8 r: O. k- j+ d- `9 x11" u) L- h* E- v8 K
129 K/ z0 _3 d9 e5 u5 l
13
0 `$ O# N5 G O' s0 h' h145 U% S6 L$ f( I' ~5 C
15
; H% u$ p: ^7 {# [16
0 a6 Z% W; q1 [3 L& f17
' k0 h6 d0 s2 x182 Z; m5 N5 b+ {& r8 q/ s" b, h
19
2 }% u: |% S' e20
+ h7 A& V* B" o8 J! U/ wimport numpy as np% z* J7 D+ X. U: I3 q) C% B: U
import pandas as pd3 x# G9 _7 V- Y7 X* T& n% f" u$ O! z/ Q- i
% \6 b- M. M7 z' X' D" N7 {s = pd.Series(['abcd', 'efg', 'hi'])
2 ^0 f! e: F M! g' ss.str[0]! i9 J+ B( w7 c2 Y2 c
1
. ~1 B* M2 l" _5 o! i2
Y( a" g5 q$ U35 t' p7 h- c! `, L1 U3 W7 v
4: F- g' M p9 g7 o" e; {1 c
5) Z) R* N: }9 ^/ ?
0 a. d3 f# W' K% n+ k# m
1 e
( d3 k' o; X4 U2 h
- l1 E& y. \" d' _dtype: object
0 F# E- w6 e ~. K( S1
8 E( p! w* p: J% ^; _- ]2 _' o) x3 E0 a' C% j5 B
3
! C% ]8 p8 q* Z7 g: _ U2 y( c4
8 u% q7 U7 m! J U8.1.3 string类型6 ?8 |- y, `5 a. r+ M, n( o/ Z+ k
在上一章提到,从 pandas 的 1.0.0 版本开始,引入了 string 类型,其引入的动机在于:原来所有的字符串类型都会以 object 类型的 Series 进行存储,但 object 类型只应当存储混合类型,例如同时存储浮点、字符串、字典、列表、自定义类型等,因此字符串有必要同数值型或 category 一样,具有自己的数据存储类型,从而引入了 string 类型。3 `) x1 X3 Y, P: Q. h! F7 f/ U
总体上说,绝大多数对于 object 和 string 类型的序列使用 str 对象方法产生的结果是一致,但是在下面提到的两点上有较大差异:6 E$ c4 w2 T4 R, Q4 S( F8 E
; |- i' Z) U9 e8 a) c: m/ `二者对于某些对象的 str 序列化方法不同。) W. F, p" A/ q3 d+ O5 B3 A& M
可迭代(Iterable)对象包括但不限于字符串、字典、列表。对于一个可迭代对象, string 类型和 object 类型对它们的序列化方式不同,序列化后str对象返回结果也可能不同。例如:' O7 b' I2 t- [& o
s = pd.Series([{1: 'temp_1', 2: 'temp_2'}, ['a', 'b'], 0.5, 'my_string'])
2 V$ \" ?( O7 H2 `0 is
0 A/ x: x8 I4 P* I9 G" O* s1
& r; e5 Y+ X1 Z# G! B3 @5 t5 f- j+ _2
- q9 z, G: S- U+ @; q0 {1: 'temp_1', 2: 'temp_2'}1 w0 q7 l N: g* V u: U' ]& K7 o
1 [a, b]4 t6 ^4 V& v G8 N$ X* ^- F
2 0.5" ?$ m: h2 z2 H
3 my_string6 q' I+ ]/ h" L- b
dtype: object" r+ s3 F" h, v2 B
1
2 ? C" o/ `( ~7 p" U+ y: \2
+ c1 k. ~2 L. { m% z/ q% G- m2 m" c3! u) c. l' i. I1 b/ [
4
( a4 I) G& T7 Y' t5( n* ^2 Q, g; C, X# O2 \0 A
s.str[1] # 对每个元素取[1]的操作6 ~, O, U- R g; c \6 H
13 H3 j6 m8 A7 d$ N1 \$ w( e, z
0 temp_1
4 v0 m0 ]. @6 u) ^4 O1 b
# N9 n- e0 E/ s/ D2 G9 v% J, H; N2 NaN& _/ d2 a2 G7 I
3 y( D( [ ^6 v; R" j6 N
dtype: object1 P9 N* ~% d) _0 m8 o
1. k- x n" E+ w4 r0 ^3 P8 l5 D
2" @ A6 f6 T7 I% {2 R9 H9 f
3, G7 n9 @1 O J+ x9 R/ J- w
4
& A7 C! K p3 z/ S$ T" g5" Y) r& Y$ J) v# ` u3 Z
s.astype('string').str[1]
3 X9 K5 j j# |* Z& n. L1' E! y# i; J: m# O' m1 _
0 1
$ g+ T1 G9 _, K# ?* h* _1 '
8 G$ u8 f8 m. b& h n8 d/ c2 .* S6 a5 o! E b) G9 H
3 y) c, ~ f' w$ v$ b* W% B ?5 u
dtype: string
7 X1 s1 M9 U7 C! w7 E d0 |18 c+ T9 l) J, }+ D. J
2
( Q2 D* e5 o- \" W% A3
/ v. Y5 O. x. U) g9 @% {$ m) Z% X4
1 ?3 @5 o, ^, o1 M( H% ^* q' r: q5
% y S4 q8 {( J- N1 N/ k除了最后一个字符串元素,前三个元素返回的值都不同,其原因在于:
, ?, X- N5 a1 u; }' e% g# _8 ]* e" a2 d4 [+ e3 K X
当序列类型为 object 时,是对于每一个元素进行 [] 索引,因此对于字典而言,返回temp_1字符串,对于列表则返回第二个值,而第三个为不可迭代对象,返回缺失值,第四个是对字符串进行 [] 索引。
# N; Q' d! d d- _string 类型的 str 对象先把整个元素转为字面意义的字符串,例如对于列表而言,第一个元素即 “{”,而对于最后一个字符串元素而言,恰好转化前后的表示方法一致,因此结果和 object 类型一致。6 D( J- M8 u% X/ u ? T' Z
string 类型是 Nullable 类型,但 object 不是6 T. f' ^4 j$ A+ D1 F- x5 l/ K B
这意味着 string 类型的序列,如果调用的 str 方法返回值为整数 Series 和布尔 Series 时,其分别对应的 dtype 是 Int 和 boolean 的 Nullable 类型,而 object 类型则会分别返回 int/float 和 bool/object ,不过这取决于缺失值的存在与否。
9 K$ Z. @% Y1 r- a1 r 同时,字符串的比较操作,也具有相似的特性, string 返回 Nullable 类型,但 object 不会。8 L& P0 G- ?7 }$ e7 x& Y
s = pd.Series(['a'])3 i4 q, s D0 S9 F1 L
8 p( M: {' j B5 S7 {
s.str.len()/ \2 l, Y+ ~# V) I
Out[17]: - y5 E8 k" }7 M/ L% E
0 1
H- u2 U# A- I& Sdtype: int64
6 @) m# R% }' G+ S& H8 X, W& T' R( w
s.astype('string').str.len()
* k& c/ d' a; G8 h" N5 n( TOut[18]:
; h. T+ r7 L+ Q# }2 F9 A- o0 1+ @* E- p4 F! a% g d$ ?
dtype: Int64, ^* I( R) N) y7 u
0 j G; `" _4 J, ?: W2 e
s == 'a'. v( O! o8 L5 P4 I; j. G
Out[19]:
' E" B; l4 t$ C$ Z4 A0 True
: x9 i! F- H8 x3 | O+ sdtype: bool* O) z& x2 H' I, ] o: g
5 ^% w5 ?3 b. Q, j8 S" B
s.astype('string') == 'a'
/ p q2 T c% k8 y% N8 r8 LOut[20]: # a' j' u4 u' F! z/ K% J1 k
0 True7 D+ Y& @$ c; V* k$ H5 F
dtype: boolean3 _1 P. e( `3 B+ `0 Y
: N) B# u' X \% Y& m* s$ z9 ks = pd.Series(['a', np.nan]) # 带有缺失值
6 o1 F# b' d5 }* Q- \) c; ~4 S* B. A/ q3 S% O
s.str.len()0 D' ]6 w' \) t8 M% ] m+ ]) |& E, p
Out[22]: + I" E9 F, d+ B' D
0 1.0- U/ Q; R, Q1 Q8 _6 ]
1 NaN
) g% z& n2 A5 c5 E) D* i0 `dtype: float64 j0 C8 T; ]: B' q6 D: `! v0 Q
# u/ R& b6 I$ B" |+ a3 M2 u2 T5 t. C0 Us.astype('string').str.len()
S" j& i; m3 w4 ]+ P; v& _Out[23]: ) n6 U! o# J: z0 q
0 1- g0 Y2 j% G4 l. c' k& `
1 <NA>" E5 m* U( N, j
dtype: Int644 \" q, K( R5 i _3 p$ W0 ~+ N
) C; V6 y/ N+ R4 l* Hs == 'a'
! _# @+ @" _8 E* r/ q. ~Out[24]:
1 c( X1 A! i) }" }4 S/ j0 True: j [! z& p/ J7 K
1 False) n. n2 d" R8 ^& Q! ]
dtype: bool
7 x" `9 v z3 p/ i; ]3 d$ A5 {3 l- r! E5 M3 F! j
s.astype('string') == 'a'
4 d3 L/ {% k) SOut[25]:
% t8 M; k. g+ m0 True
3 `" n3 o) d2 ?' e1 <NA>: z6 w4 g8 e5 M1 n: u" z. Z
dtype: boolean
; F* A( s- K0 }) {8 Z
! E/ n! k! G! h. ~15 M E) |. p, c* n
2
' Z6 m( j0 b3 c3 [; E3
: b( G7 X+ s" F2 c7 b6 N) q7 k4
* S% M1 @$ N# ~3 b57 s( E. ]& i- g" T* I; d
6
' z; H; K2 D- b( p3 z1 k7
$ ^9 a1 V" ]! J/ f @8" v6 i# N0 c$ S7 @4 j
9& l& V0 \% |; v7 E0 u
100 M$ D* ?5 o3 G9 P+ U! ~
11
4 R( u8 q. o# y$ \3 ]. P2 c12( C8 P5 _, {) b& l2 Y. E a
13
8 ^ ~/ R, ^3 }14
9 F3 @+ k" F5 v15
) |8 O! ?. K4 Y/ x2 n( y16- w* T w" Y& t/ E6 l( ?" L+ K
17
' k O. k: }; `$ w) G18 K2 W; l2 K, [/ o1 q, g+ [0 J
19
3 Y1 }+ ?: R: |2 A: W% c( ^* N# O ]+ @20
8 F/ _" A9 r; \- e* }& J21
+ ]6 P3 y5 S# `- O$ p$ Z22
' f' t' @) A/ V5 q" ~) n& A. d. h234 r* t% n& q" U" R7 N6 d# S" O# d
24* \; u0 q5 h, s' i6 b
25
- ?- R8 f; c! V8 z0 u- g. e" Q& O% B269 D9 b7 u: b" n A3 B) M' N
27
, \7 g, b+ ?* T28$ V. L% V# x, k0 c1 g$ ]6 ?! \
29
; Z8 c" Z3 U- V, Y4 h# y# g' a30
8 y; ~$ {% X( I/ e7 q- E2 d: c315 j7 e: u# M% j9 i+ S5 ~) v
32
0 }; D4 }0 Z; O0 a! ]" `9 x. q8 e8 l33
e) j0 C" i8 f5 }8 |3 ?9 W34+ E# [6 G o' d, s
35
, \4 t9 q( L% K/ ?8 y36
" K5 Q! u+ b" m* x% p( T37
) S1 F8 F/ N/ A3 g }38
) t* J+ q! S2 l D39
# q- X: t f0 T- Z) F; E `40
* p. C. R6 X& {7 C( ~41
0 z0 |0 p C0 P1 h& }' P2 ?42$ v c4 ?2 l9 g6 l, J1 c
43* p7 y' g B( x3 n1 Y& ?4 N7 e5 N
44/ k4 x1 U; m0 l* T
45
. h/ D9 ~' I' G! c" ?* F46
2 \6 l9 b. |3 _1 a' G47# L, ~8 V! P' Y/ I A
对于全体元素为数值类型的序列,即使其类型为 object 或者 category 也不允许直接使用 str 属性。如果需要把数字当成 string 类型处理,可以使用 astype 强制转换为 string 类型的 Series :
) Y! _. x8 [8 N" L* @
, ]* z+ m B/ Y2 ?1 Vs = pd.Series([12, 345, 6789])7 E. a! {! ~0 u$ u9 u6 R
0 H) L# b- I5 O, N5 u: ?* C
s.astype('string').str[1]% h7 a" M: U+ F0 G' I" a8 K: `6 C
Out[27]: , Z1 w* W% ?4 A! X( o) c) H2 n' c
0 2
" z3 W: K. E7 p+ m2 I& K. Z2 L1 m1 4
$ A& v! A& m/ l U( V- O/ E2 7
+ v- n- s. X3 Q6 @; s+ Odtype: string8 f- p' S6 H0 }4 c& K: `1 z
12 i |! v) H, [( H) x
2
' U$ k9 b& p* S" ]8 u* x3( H# Q, g2 h/ T0 a" f
4
* D7 p( _* n. i! Z% N; o% w55 S/ n" Q0 n; r, F0 L: L. c9 p! @
6
{2 ?: d/ r. S( Y8 t. R8 U! {! h3 _2 U79 @" y/ r* T! Q8 @
86 j: W& T/ B2 |3 p
8.2 正则表达式基础
1 X J t7 m0 D7 S+ [ u8 l这一节的两个表格来自于 learn-regex-zh 这个关于正则表达式项目,其使用 MIT 开源许可协议。这里只是介绍正则表达式的基本用法,需要系统学习的读者可参考《Python3 正则表达式》,或者《 正则表达式必知必会 》这本书# a' p' h" E% ]1 E, B& b% T! n ?
1 O1 V' W' q0 y. G# L& r. d6 ~/ X9 p* s' G8.2.1 . 一般字符的匹配! d. _- N9 g3 N
正则表达式是一种按照某种正则模式,从左到右匹配字符串中内容的一种工具。对于一般的字符而言,它可以找到其所在的位置,这里为了演示便利,使用了 python 中 re 模块的 findall 函数来匹配所有出现过但不重叠的模式,第一个参数是正则表达式,第二个参数是待匹配的字符串。例如,在下面的字符串中找出 apple :
: I2 o) ]/ |+ l0 F3 v6 X5 U# v5 V0 S/ E! v6 M( Z( x7 I" y2 H
import re# Q" w1 b$ ^/ }
/ ]; M" w- w# p- ?re.findall(r'Apple', 'Apple! This Is an Apple!') # 字符串从左到右依次匹配
& s* d4 p( f/ _4 IOut[29]: ['Apple', 'Apple']8 ^2 M& o) D8 M' r# ^
1: b# D0 @( J! {* z3 V( H
2, E$ ^; O/ ~2 }7 D2 o
33 t' I/ \& v4 I
4$ T8 O& P) L' y4 _
8.2.2 元字符基础
" W2 q1 I- E. a# }元字符 描述
K3 s: {2 h+ s A* O" X/ K. 匹配除换行符以外的任意字符
, R$ H# U3 N, {2 Y[ ] 字符类,匹配方括号中包含的任意字符0 ~- }" _* I$ z3 b# ?% ^; M3 J
[^ ] 否定字符类,匹配方括号中不包含的任意字符
4 \ u- A: _2 O, b* 匹配前面的子表达式零次或多次
: o& b5 l# N* r# B r3 @+ 匹配前面的子表达式一次或多次。比如r’d+'就是匹配数字串,r’d’就是匹配单个数字
S& r" Y; z/ w5 c? 匹配前面的子表达式零次或一次,非贪婪方式9 K3 ?- p- c- g+ l" f
{n,m} 花括号,匹配 n 到 m 次由前面的正则表达式定义的片段,贪婪方式: }! g# a2 `# P; m/ b
(xyz) 字符组,按照确切的顺序匹配字符xyz
8 @) j( p9 G0 v) X| 分支结构,匹配符号之前的字符或后面的字符
& f: v9 k0 C/ J3 a( d7 w7 G4 i\ 转义符,它可以还原元字符原来的含义) r, j0 Z! a6 [+ o
^ 匹配行的开始
7 e/ Y/ C6 E7 S! R+ y2 e$ 匹配行的结束$ S3 o; k; e$ ~
import re3 { }: S' R9 T6 I9 W
re.findall(r'.', 'abc')
2 {; _1 m& @; N/ z/ HOut[30]: ['a', 'b', 'c']
! A( c, N3 N: L8 g
: d" `$ f7 V- m% `1 y. s6 @re.findall(r'[ac]', 'abc') # []中有的子串都匹配
# k% n$ o% ^6 B. qOut[31]: ['a', 'c']
3 L& s F8 w; G- u/ A4 @) Q- f- u2 @4 q' G J9 Z. x9 Z
re.findall(r'[^ac]', 'abc') 0 O, T- k( N6 x
Out[32]: ['b']; V- E N" z% p
" D: P8 e# Y: C( s, n9 ire.findall(r'[ab]{2}', 'aaaabbbb') # {n}指匹配n次+ z0 ~0 s2 l! ~
Out[33]: ['aa', 'aa', 'bb', 'bb']# ~0 ~5 Y+ X8 s1 P4 e- L% U7 M
* t* H9 R) `3 K4 c9 t2 H5 vre.findall(r'aaa|bbc|ca', 'aacabbcbbc') # 匹配前面的或者后面的字符串
% u& f* g- r/ o# sOut[34]: ['ca', 'bbc', 'bbc']3 ~# q" {( Z' `- w' @6 ^
8 P& Z0 J. Y/ x* Z2 S7 ]# @6 z. U
# 上面的元字符都有特殊含义,要匹配其本来的意思就得用\进行转义。+ S, _' c0 S" h! Y4 C0 { @# ~
""": E3 Y' v. C5 ^( k) m: C
1. ?匹配的是前一个字符,即被转义的\,所以|前面的内容就是匹配a\或者a,但是结果里面没有a\,相当于只能匹配a。
$ j; `+ [8 n; _3 {2. |右边是a\*,转义之后匹配a*,对于竖线而言左边优先级高于右边- j/ V, U: |5 ^2 ^& ~; D
3. 然后看目标字符串aa?a*a,第一个a匹配左边,第二个a匹配左边,第三个a虽然后面有*,% U/ z# g9 S! G' x0 _ Y( m) X# h
但是左边优先级高, 还是匹配左边,剩下一个a还是左边,所以结果是四个a
, H) S/ D; c$ k1 r% M0 O; E ~ T"""9 V) B- s* v7 u# w. S7 w9 {
0 z0 m7 u9 z* K l3 k0 N8 xre.findall(r'a\\?|a\*', 'aa?a*a') # 第二次先匹配到a,就不会匹配a?。a*同理。1 [( _: p, o7 t3 x% f8 X
Out[35]: ['a', 'a', 'a', 'a']
2 J7 y4 j7 k' i! ^: W# S- ^5 b0 D- b6 E6 R4 y
# 这里匹配不到是因为目标串'aa\a*a'中,\a是python的转义字符(\a\b\t\n等),所以匹配不到。
: Q( `7 D1 \( f: V. x" ]; Y1 \: y# 如果是'aa\s*a'之内非python的转义字符,或者'aa\\s*a',或者r'aa\\s*a'就可以匹配到\字符。- t, \0 \# L1 p% o2 F1 q( [
re.findall(r'\\', 'aa\a*a') 4 b7 l7 ?, l$ a" g% b( @
[]
) h4 |: R9 p' |6 o+ Y1 s8 f4 r. u3 H# L
re.findall(r'a?.', 'abaacadaae')
) U9 y7 F/ W" w0 r @9 Q, K) wOut[36]: ['ab', 'aa', 'c', 'ad', 'aa', 'e']' J" {0 B: K+ ~0 Q: m4 l
# B7 ^+ G, v! t8 b5 u. I+ u2 G
re.findall(r'(\w+)=(\d+)', 'set width=20 and height=10') # 多个匹配模式,返回元组列表
2 G& z) i; O8 {) S! l8 D; B[('width', '20'), ('height', '10')]
5 \$ ]1 J+ S" r$ Z6 k) j( r e% U: T% O
19 c, b% R1 D8 U) K+ n U; x
2
8 I5 R, d0 \- e; k$ w3 O6 g4 G1 O31 V; b g6 w/ o' ^
4$ V4 m! U4 Z, ?! m
5# Q/ a% M) V) G' U% q4 a; ~8 Y# f
6
; Y- M. p/ g! _1 d* ^7) P$ h& Z, c C* B( d7 n
87 h9 n. F* R' D; q: k7 k' ?" F
95 o: a+ G+ P6 S. a- ~1 i% ^& p
10
L2 S! r- ~* C* i2 X6 T, l/ H7 p110 R \% \/ b7 u- f. |
12: h2 r) p) b1 p' n: i+ m
13& j+ y1 a4 a& w- Q4 _9 O! _
14( j. D5 |, ~3 J' H% [, t$ @' f
159 S0 p$ o8 V, v5 I' D) {: f+ `
16
& t. n$ x `5 Q17
9 t0 Y1 z1 W$ w1 Q* X* Y4 Q- ?188 ?/ z) j8 D0 u; l- O' Z* V7 H; P
198 q/ `! v. Y: w0 E, I. ?% y) b( \
205 _. e1 p+ s7 N% ^( j- b1 @" U
21
* u( V' Y" i5 c3 `22
7 A, s. \9 B: b+ x* B2 |23
! J. O3 M+ S2 g: |* a0 w! g2 e R24
$ Q9 Z. Q) y- w& v, F25. c: y* d( U, i( g+ b' x
26
. }- b2 r" v) I6 O c: Q, x. M27& I3 p0 M! `2 L; \* V4 @
28
: ]* C9 T7 `9 z29
& T8 {8 \) u& T) H* e30
J7 y) H6 E' F! ?) _. v _315 {9 O7 ?# T% y t4 O0 ]: u3 |
328 F5 s. t# c/ K0 M
336 E' B" L M4 ~2 \! Y
34
* }, b) y0 h& l* X35
( }0 U# Q1 D; h6 }36- `; y* G& ]( Z
37
$ e+ l( X- p7 v1 y' r j8.2.3 简写字符集1 q9 Y2 S+ H' S1 X; L
则表达式中还有一类简写字符集,其等价于一组字符的集合:
8 f: G: ~; M/ p& I3 D+ o1 ^# v" t$ k1 r; A/ } _
简写 描述
) x* ]" z+ ~3 A1 A7 u' R& o\w 匹配所有字母、数字、下划线: [a-zA-Z0-9_]* @% l& e. g) ~+ U# g6 N# j/ }
\W 匹配非字母和数字的字符: [^\w]
* x, Y( ~$ k6 |, o0 Y\d 匹配数字: [0-9]
+ P& T7 d1 u& S. Z. h% G\D 匹配非数字: [^\d]" Y. K* [2 W5 R4 B7 ?4 U: r
\s 匹配空格符: [\t\n\f\r\p{Z}]( m% `5 t% F4 X0 e6 o& o
\S 匹配非空格符: [^\s]6 e5 y: A8 b/ m9 N i( D1 S, O0 m
\B 匹配非单词边界。‘er\B’ 能匹配 “verb” 中的 ‘er’,但不能匹配 “never” 中的 ‘er’。
( ]) N K4 u: o( u; r6 G Lre.findall(r'.s', 'Apple! This Is an Apple!')
6 X5 L* R. `2 O( }0 V4 m/ iOut[37]: ['is', 'Is']
0 S0 E) a& Y8 |" p i" C" ^
4 ?) I+ P& @ w. a3 [ Y7 ]re.findall(r'\w{2}', '09 8? 7w c_ 9q p@') # 匹配任意数字字母下划线的组合,但必须是两次& p& w: K- ^5 K9 D+ E8 n7 Y7 z" j
Out[38]: ['09', '7w', 'c_', '9q']
. \% }4 P. _% g; v6 s
- A/ N9 E! v7 H4 N/ b/ Tre.findall(r'\w\W\B', '09 8? 7w c_ 9q p@') # 匹配的是两个字符串,前一个是任意数字字母下划线(\W),后一个不是(\W)
9 c; `# ~5 \- M9 u" U* A& POut[39]: ['8?', 'p@']- Y6 A- O8 a0 L' Y
1 q/ A+ J. ^+ ]( T! r% l- Fre.findall(r'.\s.', 'Constant dropping wears the stone.')3 x/ M( ?3 ~$ I# B
Out[40]: ['t d', 'g w', 's t', 'e s']
* t; k6 F8 H$ a! o( B7 ]- @# z% {( K) `+ r
re.findall(r'上海市(.{2,3}区)(.{2,3}路)(\d+号)',4 H' D }3 Z. }" ]% x5 X, N
'上海市黄浦区方浜中路249号 上海市宝山区密山路5号')* l8 R2 u- U# R d% j! v
$ t( T# O* {4 x' j9 {; G* XOut[41]: [('黄浦区', '方浜中路', '249号'), ('宝山区', '密山路', '5号')]
: i: c `9 l0 i& g# d
8 [; [2 e3 w6 G( N2 C18 o3 X y$ e1 Y& \
2
+ x9 A: Q0 `6 `6 Z39 z+ W$ j4 s! N9 ~ a2 F- N! {
4
. J I# S6 Y$ b1 e; p5
/ r, D; `. h, p) g6( M+ s/ |6 V9 Q- k& L& j' S0 J
78 t G! W! S0 }3 Z
8* r& Z; ~, M7 i2 B W
9
0 t$ P7 J* {, t' O+ k9 v0 V10: Q3 N% `& O$ ^3 O5 z0 n+ C& B
11
6 i: T. c* J) D( b: o6 ~12! H* E( ^( A! N
13
" N' R$ z2 W. c8 n h$ D8 ^! {# j147 l q. l2 \; s7 ^' c1 e2 r
152 n% K) j: |/ U1 e+ Y
16
& \6 l2 c* c! `$ [8.3 文本处理的五类操作. O7 T7 l. O: G! |# @6 g) D
8.3.1 str.split 拆分1 Q0 \9 \9 k% @2 n" m& }7 n5 F9 a
str.split 能够把字符串的列进行拆分,其中第一个参数为正则表达式,可选参数包括从左到右的最大拆分次数 n ,是否展开为多个列 expand 。
: ^" T. u: n6 ^9 B' b( M$ p
) c! @) e. j. W/ N$ d+ ns = pd.Series(['上海市黄浦区方浜中路249号',- s% I# ^0 q8 |! J+ P
'上海市宝山区密山路5号'])8 A. `3 |! u# l) j
! J6 \" ]* S$ d- o) Z( ^( ^
4 ~$ {% S4 H8 s- |s.str.split('[市区路]') # 每条结果为一行,相当于Series+ `' m) B& S& A
Out[43]: 6 ]' _) J' d& ^" t
0 [上海, 黄浦, 方浜中, 249号]5 j. |/ O9 ]$ B4 y, r0 v
1 [上海, 宝山, 密山, 5号]
! w; u, W0 f/ |* O3 [# k6 Ddtype: object7 r& |& o3 H, p* q- u p
" `3 p0 U+ R, |! }' V4 |2 D: S/ q
s.str.split('[市区路]', n=2, expand=True) # 结果分成多个列展示,结果相当于DataFrame: M' |+ f/ }8 B5 e" O' V* ^+ ]7 x
Out[44]:
! H2 u7 y! [. _ 0 1 2
1 S" }, z2 F- R, g& J0 上海 黄浦 方浜中路249号! F# v7 T2 u9 ^, J$ o: w
1 上海 宝山 密山路5号$ f2 A3 U6 ]+ v z
1
# e7 i9 u' y* V W' g6 c2
3 b+ Z& w5 H! O' A s3
* c) ~5 [; m) h3 q( Q4
* W% ?* U$ p& t, x5
; `$ _. [, c/ [$ Z- ^6
4 F5 m$ T# w& C# W$ T5 [$ [79 B/ f" d: A% G) @% l
8
; Q) O6 h! L" Y- e( O- I7 j9% S0 l, m( v) q; l, t
10* s4 s& x; K* y2 z
11
0 E i$ q, v+ _3 }8 E12
2 q' h1 Y4 W, L9 I/ Y6 ?13% J6 s0 u2 t) a- t2 u4 y# _4 t4 f
149 ?4 u8 D8 A0 `# R" G
156 p4 r7 k0 B. Z" y
类似的函数是 str.rsplit ,其区别在于使用 n 参数的时候是从右到左限制最大拆分次数。但是当前版本下 rsplit 因为 bug 而无法使用正则表达式进行分割:
) M b p: S. S" L
" w5 S. s5 Q5 A8 @: D8 E, ls.str.rsplit('[市区路]', n=2, expand=True)$ i$ H" G9 s" ~6 |' ?# u, |! S
Out[45]:
9 u2 e8 P% K8 n) c 0; `6 `& s/ R+ m y3 K' w4 k
0 上海市黄浦区方浜中路249号0 p3 z1 N; V! s1 e
1 上海市宝山区密山路5号# Y6 ]/ K3 H" }3 ]3 E; N8 d7 F
1+ u2 J+ S/ y h% t( `5 _
2 b& d4 @7 S8 b h+ ?
3) @3 \- X2 J4 ]# X1 F
4* \9 F6 Q0 W( \/ O5 V0 q: ~$ Y1 U6 I
5
; y" z& I( T0 t! l! R8 ?+ q# q/ d8.3.2 str.join 或 str.cat 合并
- Y1 X9 ]0 y2 D0 Kstr.join 表示用某个连接符把 Series 中的字符串列表连接起来,如果列表中出现了非字符串元素则返回缺失值。. e: @% v8 y) a: N* ?+ {& w
str.cat 用于合并两个序列,主要参数为:& \+ s$ \4 n# T: |6 T6 H. d( M- ?
sep:连接符、9 `$ ~5 a! f1 P1 g
join:连接形式默认为以索引为键的左连接
6 z+ T+ U2 `+ F1 j, bna_rep:缺失值替代符号8 V) q; m8 {+ ~# g
s = pd.Series([['a','b'], [1, 'a'], [['a', 'b'], 'c']])
' @9 W& h' j+ \& }3 P' I& C" `; cs.str.join('-'); X+ T: b- R- D1 l4 x
Out[47]: " l- T% e' w6 j2 h T, H" _1 U
0 a-b
% c" Q! ~# A& p7 D; c+ x. ^1 NaN! K: ~ B0 d, [
2 NaN
8 ~4 f& N) n# U4 idtype: object
1 N7 I5 Q8 N- w1
1 F! P. c( j w: d, B( v2 x2
9 {! @6 s5 f; F( B6 c; P6 G3
( e: y# ~- `1 D8 D+ V8 m. f4
! t8 D+ q( F! I8 X7 n5
) ~0 A) I$ O; L4 g8 F6% r, ~& t, o: J% Z9 A" ^
7
, @% J) O- `: E2 P' js1 = pd.Series(['a','b'])1 b2 [! N! n; e" Y
s2 = pd.Series(['cat','dog'])2 w6 M: S0 `1 p
s1.str.cat(s2,sep='-')
/ L5 V8 Y8 c3 o3 }. I# R" cOut[50]: 4 N2 m- d% l8 k# n! N* I
0 a-cat
3 B! w5 I4 a' ~7 b1 b-dog% f8 ^7 i) D: L
dtype: object7 o, W% m" k0 `9 F* J
/ G1 V1 C/ |: is2.index = [1, 2]
9 i# b1 o: [. zs1.str.cat(s2, sep='-', na_rep='?', join='outer')5 q+ E1 S u' x3 N2 I
Out[52]: 1 D" y" y5 d2 d) _( P
0 a-?
& T# W3 d8 Y$ I) [1 b-cat2 I# [ `3 V$ _8 b& }
2 ?-dog
2 Z) |4 L2 p8 V/ s' `% x) \dtype: object
2 B5 ?$ u: S4 H, |3 B% F1
. r3 Y) W0 F& g& I, B9 K0 T2
' K0 z5 ~; z* U* P6 M3
: y8 Q' J/ L3 |7 B/ @4
5 Q% P1 k4 d1 F$ T' R* {. z5
0 n: \. |* v; O6 R7 q) B6
$ g7 t3 T: ]# r& D: x7
" f6 ?% R- v& v J; K8 C8
1 ]' m: L8 S0 N+ }" T% v8 h9
( t. f$ ]# C7 a Y4 @10
" H5 d' O" [; k T11& e2 s9 @$ n; E7 p5 ?
12
& H/ y4 i, S# E- c# s13
, y9 j7 g6 F) s9 B14
0 \, I2 u7 p9 x& Q s* n4 \1 V15
4 H: c- b2 `9 o9 U" |8.3.3 匹配 i% g( {+ p3 @# s8 M0 n
str.contains返回了每个字符串是否包含正则模式的布尔序列:1 m8 L5 B7 T4 ~! ^3 }
s = pd.Series(['my cat', 'he is fat', 'railway station'])
- G5 D B; ^1 _: S0 {s.str.contains('\s\wat') I2 W: b$ M* Q( _+ @0 j; e
6 p! m% e- O, ~
0 True2 D6 `1 X4 p& H: D5 B K/ U4 t
1 True8 _1 A; m) |' S
2 False9 Q) j! `% ]8 c+ L, `: l* O: Y
dtype: bool; K8 n- n2 M: c: u: \; M
1
; n: _* Q. P4 M7 l5 m2
' l3 l' _* Q: Z: c3
& C/ K6 {! ?1 C5 M0 O* r, u41 [6 _) |, {1 V' g3 K4 T, K/ s
5
+ ~' J0 M7 Q q$ n67 `* G; t$ U, Y2 ?
7
) h+ {" Z9 S- Y* A; G3 G4 e1 zstr.startswith和str.endswith返回了每个字符串以给定模式为开始和结束的布尔序列,它们都不支持正则表达式:/ Z) F& z4 Y8 ^" z( v/ B4 S
s.str.startswith('my')9 n; S( n$ n1 z6 O( |: f
$ p' w$ n" D9 p1 h7 f. Y8 @0 True
# h: q8 W4 |, T5 b t, Y3 R1 False
7 x$ {$ p! I, [: D& g7 V$ }$ m2 False6 M$ x6 B3 K" |3 w- [% h
dtype: bool
9 V7 ]3 G$ d' `: z5 h1* E3 h1 ^* ?0 L7 h
2. @) l7 g m7 a, x# S6 y c
3
2 }& U- n2 a3 x7 y6 h w4
7 @8 C" p" m9 m1 o( s; C5
% M; w/ k: ~5 b1 @+ z( E6) M' g S- U% e; G, ~
s.str.endswith('t')# a9 i! h# C, Y C$ T' {
. @' c! a/ ?! K7 G* O; w0 True
! Q: X+ A2 J) |1 True/ e. {) V8 ? s: Q
2 False
( ^0 Y; I, |5 u. ?dtype: bool( \* O$ n3 H* C% ^3 r3 ~. ^$ C
1
9 E8 [9 Q9 O% ~) J# M4 c0 T2
7 e _% l9 D4 m4 I3
& M- d& p% ^; f. M' \! @" ]* d- r4
: q; z" g" Z7 F* l, y5
' t% {/ F( ^4 x) Q. m6 x6( n Q4 V8 d! A$ _- R' J" S
str.match可以用正则表达式来检测开始或结束字符串的模式,其返回了每个字符串起始处是否符合给定正则模式的布尔序列。当然,这些也能通过在str.contains的正则中使用^和$来实现。(貌似没有python里的search方法)
: B) h0 Y8 ]; X7 D2 l* h( Vs.str.match('m|h')
: m9 ~0 S9 P$ F- h* {5 S$ Bs.str.contains('^[m|h]') # 二者等价0 x) F: ]2 R9 }1 n. O5 ?. U+ C! [
. n* G! O$ p4 k, u9 J
0 True2 \ C0 i6 O( @: [
1 True' Y" d1 E0 d: ~. E- R2 e/ C
2 False) m+ V) ^4 m6 f9 p7 O
dtype: bool4 a4 U m& t2 { q& G1 Q0 {# K
1
* d7 I6 i: o R+ C2
; Q6 _8 z I. Z. L) I3
! I/ e3 Z# r" M5 b4
9 l5 {; q" i5 m, S" M5 j5
J0 S7 F6 U! x, d+ }69 I3 m; p& A3 p2 n1 s
7
- C- V* g& F, c$ ~+ is.str[::-1].str.match('ta[f|g]|n') # 反转后匹配7 n( i9 f G3 d0 G
s.str.contains('[f|g]at|n$') # 二者等价- d( p: i- t4 e; K: _
7 s/ W. H1 A" L0 False
. T8 T* i! f; u4 ~' ]! K1 True
2 h: |$ r( y6 h6 r( ]2 True
- L ~0 Z& c" Mdtype: bool4 m# O( \/ m% w+ |- @5 I4 T
17 M: T( ]7 {1 ]
21 e( s7 P7 y' c9 ~' y, l% G
3: I5 D6 n4 Q1 v$ A
4, u" d; d8 J1 s" y5 ]7 T1 g6 S! C
5( T# v1 e5 k* A( C% L% v
6/ [$ ?( B5 L$ s6 a1 T
7: S+ x. k9 e: }7 i7 g/ h! c
str.find与str.rfind返回索引的匹配函数,其分别返回从左到右和从右到左第一次匹配的位置的索引,未找到则返回-1。需要注意的是这两个函数不支持正则匹配,只能用于字符子串的匹配:
( x4 b% g4 \" ms = pd.Series(['This is an apple. That is not an apple.'])+ S/ n( R$ A3 k, `
6 m F! ~- h- N6 P5 F: Y0 F/ ?
s.str.find('apple')
, t/ V; }( ?0 ?Out[62]:
$ @/ t$ ~6 A2 S4 \7 j0 113 B, `% i# S& {, Q- g
dtype: int64
* k L* Z* t2 J( q
* s& A( N, y7 m* es.str.rfind('apple')
1 v6 n& y! D6 K3 Q; eOut[63]:
9 }( p! ?& f0 Y0 33
! ^0 |! r) `3 r; n; wdtype: int64
9 m, p! ^: Z: b* M" y1
" Y7 y @6 O/ o. J: P- E2
7 S# _8 y' o, b1 w+ x9 x/ A) v3 \3
) I1 s1 { a E7 F- C" n" H% [49 f1 k& c, E, @" N
51 i( E1 F9 `$ y3 a2 M. l
6$ u/ a$ z# \, ?
7
6 V4 d+ b- Z" n* b: B, A8 m+ s8
0 W3 l3 l4 w! z7 {92 b, o( P2 H7 H+ E
10
( l, d1 n/ Z5 a6 p1 g11. I; W t& [9 q) F9 e; s% h1 b
替换
" ?: i0 j1 J! H' X' K7 q$ Xstr.replace和replace并不是一个函数,在使用字符串替换时应当使用前者。
5 R$ z) d8 b% [7 Gs = pd.Series(['a_1_b','c_?'])0 d7 O1 p s; c2 [6 B/ V8 v
# regex默认为True,表示是正则模式,否则第一个参数内容表示是单纯的字符串,也就是匹配字符串\d|\?! m( ^1 y. H7 | N) O2 a8 \0 ], v( v
s.str.replace('\d|\?', 'new', regex=True) 0 U- L. m S+ T6 z7 C3 M" j
( n( B2 _7 q' B' R9 U3 F9 a
0 a_new_b
% T/ e) J7 g& b2 r1 c_new
. _' L- W8 \4 mdtype: object
% O! F3 F& ]( `# W$ P10 L' W5 P- K- X9 Y. k. n5 P
2% ?1 U: t6 n1 |* c' i# B1 v* |4 ]
3
* K, h; R" w. G$ E) ?0 x, \) }( @4
5 `9 y$ m' Q( k3 e ?0 r57 o5 P9 W7 I1 c7 e \+ N
6
9 u) J4 b* ?/ P% M; v7
( Q( ^3 ?8 G9 Q) F/ L 当需要对不同部分进行有差别的替换时,可以利用子组的方法,并且此时可以通过传入自定义的替换函数来分别进行处理,注意group(k)代表匹配到的第k个子组(圆括号之间的内容):
, h. Z; B/ k8 h5 `# l- t" M( P# |; e T
s = pd.Series(['上海市黄浦区方浜中路249号',
: f" U$ t4 S4 d! f2 B u5 j '上海市宝山区密山路5号',
/ I, N7 T' G# Z& A. O '北京市昌平区北农路2号'])
* t$ n: r6 F' h3 S/ t( Kpat = '(\w+市)(\w+区)(\w+路)(\d+号)'1 ^ f# N5 |$ a( e8 Z0 ~, x
city = {'上海市': 'Shanghai', '北京市': 'Beijing'}
V: b4 i- j zdistrict = {'昌平区': 'CP District',1 ^ J ~* C1 ~7 k1 W& }! \' ]
'黄浦区': 'HP District',3 ]1 K0 U! N* h# f4 X
'宝山区': 'BS District'}7 a0 ^6 B5 e$ q
road = {'方浜中路': 'Mid Fangbin Road',& c0 A. h+ @3 y; s6 g) m
'密山路': 'Mishan Road',7 ~1 V8 {! S6 b7 \% D( E7 X
'北农路': 'Beinong Road'}- l; `) R5 N) p% \8 F9 f
def my_func(m):6 J0 D8 K6 E6 Y1 J; I! y1 I
str_city = city[m.group(1)]( ~1 z5 q8 d% n& V
str_district = district[m.group(2)]
" A$ l+ N2 E. f; J str_road = road[m.group(3)]3 d* c. Q' H/ F( B- T- ]$ H
str_no = 'No. ' + m.group(4)[:-1]5 m0 ]) t- J" ]+ ~! n% O+ e2 a
return ' '.join([str_city,* K% I" P/ ?' J4 b3 d
str_district,
6 l+ }8 ]& x4 [1 G str_road,
2 G( {6 @9 J) ~: m$ Q3 ^ str_no])
$ l# W: r( P( |0 t* }s.str.replace(pat, my_func, regex=True)
# w# L8 |3 c$ }- _# A; A! O; E% w9 O. o& U. D& k: a- N
1
4 i" [2 A' n& l# H& s+ J2" e& |- ~, J7 x! ~
3( Y- u/ L' o0 J+ p
4: b$ J& t& I9 T g; U7 G/ D7 y
57 S- s* A9 P& w5 B: H* a
6
; }- D D3 [5 b6 E+ \: m6 b" V7
+ T/ d" k H4 k9 u' I, ?8
% C- K0 l7 R) F9 N! _& o9; { C8 S" V! p2 p! G$ Q
108 e+ |4 X! H* A1 v; F2 o
11, H1 V% a- [8 Q B1 q0 h
12
1 i! x, X1 l+ b# g; ?% Y13
- ~& u* f9 p$ D& a# k6 b14, Z# B; i9 U9 I; V2 u8 f% w
151 b, e5 K: n- u- o3 a
16
4 X& W8 K2 v+ Q: b17
, J4 X2 l% F8 r% r w* D18$ y/ M* ?& J- c, k6 D
19
" X" i' v/ U7 T7 p7 A. l20
P2 l* n/ i& }( H0 y: H21+ f6 A0 B4 y0 X' R# m! _' X
0 Shanghai HP District Mid Fangbin Road No. 249
" a0 ^& }* [, X1 W4 N1 Shanghai BS District Mishan Road No. 5
1 W) ^" f; L3 V. H2 Beijing CP District Beinong Road No. 2* S+ Z4 S0 D/ |1 h$ G; \
dtype: object$ o) d" O1 g/ ]; W1 _
1
8 a: r- `$ Z& M n, J- K2
$ ]3 v" f$ X: D2 {3
) N' Y6 Q+ F8 `1 E" Q7 i, Z9 O/ D45 x+ r P H9 L+ |; c. H+ X
这里的数字标识并不直观,可以使用命名子组更加清晰地写出子组代表的含义: h/ P3 {+ D( X+ P
; m5 c5 u5 T# u0 h# 将各个子组进行命名
: l& e8 D: r7 R; ]4 O }pat = '(?P<市名>\w+市)(?P<区名>\w+区)(?P<路名>\w+路)(?P<编号>\d+号)'3 W! z- j! ^, h! W1 d8 O
def my_func(m):
( a* C( W% _7 n: F, |: O& D. ?4 m str_city = city[m.group('市名')]# W+ r# a4 z8 a0 y9 c3 [# `+ \
str_district = district[m.group('区名')]
# N# q7 {% C2 h# N% z str_road = road[m.group('路名')]/ U. I* i @0 I( w
str_no = 'No. ' + m.group('编号')[:-1]
+ e$ Q6 a& F& t: {% _ return ' '.join([str_city,
! h6 _. W$ z( ?% j: \* x D str_district,( ^7 c: ~: r/ H& m! }( |
str_road,' E0 ]! }' |1 n) b6 I, W$ b
str_no])
& ]7 m# ~& i5 n+ h, is.str.replace(pat, my_func, regex=True)0 s6 q0 t# J: w+ o- f
14 W( T r) V# U* W3 C' O. L, F
2
$ X7 _, o4 S& V39 ]7 M- Q0 `7 h+ Q
4; O- J7 v+ k. N
5# w! M* ^: S& o. W/ \# q
6
% t" x' P7 d. m" N7* b/ I: c9 w8 s, ]. x) Y+ ?
86 C$ b$ s1 l' o: g; R5 O7 ~
91 }$ K1 e7 h3 s' _
10& o. G& R, h2 M$ s& I ]4 w2 f' }
11( }$ G& P9 |: D6 `3 k K4 @
12
4 V- W8 i: O( O+ v/ Y |. Q( ]0 Shanghai HP District Mid Fangbin Road No. 249
" j6 X6 f1 ^" j6 o5 @% ?1 Shanghai BS District Mishan Road No. 50 {, s. r# i, V! A, r* a7 P
2 Beijing CP District Beinong Road No. 24 |- _1 u1 P& ]( ~; o) T6 s
dtype: object+ n) g) P6 u- M' J* s4 {
1
7 W. d; T/ I4 \& o2 I9 v, u4 }8 x2
) k4 ?4 {0 o9 G, H) f& }- }3% Q- q1 `- e9 n
44 d5 [2 | t+ U
这里虽然看起来有些繁杂,但是实际数据处理中对应的替换,一般都会通过代码来获取数据从而构造字典映射,在具体写法上会简洁的多。
0 O' h& c, d1 U& z( N( u* A- o" ]: ]. V
8.3.5 提取
6 t% U3 }. y( ~4 astr.extract进行提取:提取既可以认为是一种返回具体元素值(而不是布尔值或元素对应的索引位置)的匹配操作,也可以认为是一种特殊的拆分操作。前面提到的str.split例子中会把分隔符去除,这并不是用户想要的效果,这时候就可以用str.extract进行提取:
! ]- d' t/ y0 o, ?5 y0 s$ ?s.str.split('[市区路]')
8 F9 L6 Y: p O! _/ `, |7 I" H6 qOut[43]:
. d5 i( M9 p& _( D( W4 C0 [上海, 黄浦, 方浜中, 249号] z6 k' Z# p. g
1 [上海, 宝山, 密山, 5号]
/ G! p. {2 N8 R6 t, ]: Ndtype: object
4 y1 J4 K+ E7 P6 E7 d
6 d- C- c% E7 l( ?& l) f. J+ C! `pat = '(\w+市)(\w+区)(\w+路)(\d+号)'
3 n3 P+ S% N* R# ~ Ws.str.extract(pat)7 p3 N' c( q j; O: ~/ i
Out[78]:
+ Y$ o# s7 e7 S$ N' b 0 1 2 33 t! ^' u C$ E0 c; o
0 上海市 黄浦区 方浜中路 249号/ O$ Z$ ?: W* S2 n+ Q# _
1 上海市 宝山区 密山路 5号4 |; N3 T+ C7 L: _) X1 V
2 北京市 昌平区 北农路 2号' c0 A$ T$ j3 U1 `0 K5 c
11 S$ j! t; I( ?
2
2 L% d9 I" U/ y9 I* X8 h ]3, m9 A" h7 D% A' y4 O; Y
4
+ a0 }( R# Y- ?+ m8 i( _5/ z2 A9 Z, Q2 E# ]+ n" M; R
6
, G2 P$ S7 K: k7 @7
, ?0 }8 K7 ]3 U( h. |8
' {1 o& n7 `9 l* }" @" c, ]9& O0 A: F1 y2 G" C; X
10
3 t* C( U$ `* ^3 `% S" X11
4 R3 g$ H5 k/ A& `4 C12- O, O Y7 E- j- [
13
9 h1 ?* m) [0 M通过子组的命名,可以直接对新生成DataFrame的列命名:$ N% @, O$ N9 H! a( y5 D
% A: S6 s7 E$ [2 U- |
pat = '(?P<市名>\w+市)(?P<区名>\w+区)(?P<路名>\w+路)(?P<编号>\d+号)'* F8 C2 h1 y" x1 G) m" e
s.str.extract(pat)
% L! f ]$ [# M/ i/ Z& COut[79]:
' I3 M% J7 m$ i( I% e" D7 P+ D% @ 市名 区名 路名 编号1 D9 A. L, W7 _! M5 z3 V
0 上海市 黄浦区 方浜中路 249号7 R- v6 G3 R0 M" P1 v2 R( a
1 上海市 宝山区 密山路 5号
9 Q" ~% k7 B! l2 北京市 昌平区 北农路 2号7 @. d( n8 B( n/ W4 u* C
1
& b) F+ k. L3 v/ k4 ?9 Z1 _$ p+ Q2
, k9 M _3 [, d$ S3
* f' {$ P0 N5 ^" ~7 n4 G4
1 t# R4 L: E% A% e2 L$ [5
" t t! l) P6 K' j6! i" @. M" E, O2 b3 v# C4 e) ?
7
& }/ z {' r* Bstr.extractall:不同于str.extract只匹配一次,它会把所有符合条件的模式全部匹配出来,如果存在多个结果,则以多级索引的方式存储:
9 ?2 ]" h5 Y5 o$ q+ Fs = pd.Series(['A135T15,A26S5','B674S2,B25T6'], index = ['my_A','my_B'])
5 {1 F: t0 J; ?! X5 D% Fpat = '[A|B](\d+)[T|S](\d+)'
. L) E- s1 _; K& i) hs.str.extractall(pat)9 M1 b) l7 @ A1 k# z
Out[83]:
. j3 {' ]6 c$ ~ 0 17 ^! ~- ]; i+ D* }
match + _8 H) u C/ |$ D
my_A 0 135 15
5 Y7 h# z; I0 d/ p4 Q+ a 1 26 5% H+ H$ I+ U4 h
my_B 0 674 20 q; H8 V: K" e. H5 F$ a( _! `6 X& e
1 25 6
: l) d9 A+ O3 m3 K. P4 M1. O5 w: U8 e1 r0 O! I
2
; Z7 B8 }5 [3 `3 L! a6 I1 ?6 U6 b34 C# d* D2 M8 |+ \( \
4; R5 x; Y0 h. ]; e; ~
5
k3 G# p) u4 l63 e/ V7 K. }- L, b( F6 s7 b
7( x- N, ^, J B& d; {; b
8) F d+ r% f3 f' t% R2 j+ u( t
9
2 X2 g3 s8 D5 k; R2 @4 x m10
( U& n# _1 T) {+ R: ?! Hpat_with_name = '[A|B](?P<name1>\d+)[T|S](?P<name2>\d+)'
. B0 D- ]; J: V) u! y1 r) Ts.str.extractall(pat_with_name): W3 ]& `1 D3 z/ _. S, s- e, b9 c
Out[84]: 7 N% v% {2 o, F( X4 j! Q
name1 name2
' C4 m# T% s( k& {; o4 S match
! _$ r7 {3 n$ ]! N& vmy_A 0 135 15$ _, _+ S9 h7 I2 r! j4 }- @
1 26 5! l" Y8 a5 X" Y: o( v4 u Z
my_B 0 674 2
+ _( ~* v- ^* q X9 c7 z 1 25 6: i/ ?. ~. v; v1 B/ x5 z4 t
19 p4 ?8 T$ C/ L! O- R4 S7 X" L* q
2/ B3 P: w3 f* c4 V) P: q
3* }0 S* H% T9 j( G0 C* I
4+ R8 {) K5 p( z" a9 [- E$ [
53 }* z% L* W, I, ~1 R7 d9 O
6) }6 c! Y: V) S; [) m& u4 a( l
7. ]' @% G( k( q+ z2 f
8# Y" Q6 W$ y* X4 s. ]' O
94 d2 P( c* f9 ^! G+ {* ?- E
str.findall:功能类似于str.extractall,区别在于前者把结果存入列表中,而后者处理为多级索引,每个行只对应一组匹配,而不是把所有匹配组合构成列表。
- b% ]3 A( d! T/ E+ y ]s.str.findall(pat)( ~6 ?& d" K; m2 L4 ?7 Y
1
$ H: o7 {# _+ E: tmy_A [(135, 15), (26, 5)]
! c3 l! L# q wmy_B [(674, 2), (25, 6)]
% }4 [2 Z3 A' j4 L5 vdtype: object% D/ {4 u7 i% N7 w
17 T/ U( ?" `! {7 O
2
5 d" c6 x" n( G' ]6 \' u( T3/ m0 a9 a) w! ~ L3 g5 q2 p& X' {2 o" s
8.4、常用字符串函数
. m; i/ A' A7 _: w: n+ s/ i 除了上述介绍的五类字符串操作有关的函数之外,str对象上还定义了一些实用的其他方法,在此进行介绍。4 W5 n6 }. F. K9 R. v n, r' O/ m
+ o+ Q& E8 g( z
8.4.1 字母型函数
- B, L- v8 X5 c+ J* t/ Z- \' ` upper, lower, title, capitalize, swapcase这五个函数主要用于字母的大小写转化,从下面的例子中就容易领会其功能:" e8 O" v/ }* {
( X3 v. P. k1 ^( W0 @
s = pd.Series(['lower', 'CAPITALS', 'this is a sentence', 'SwApCaSe'])7 s5 \ s8 g5 Y4 r2 H2 ] V
~- t0 j8 _% E8 B. F' Ys.str.upper()
, C' K" j6 H6 N3 B* b9 AOut[87]:
) }3 H# F/ A( j3 O0 LOWER
& N" L& w m; U2 C Y1 CAPITALS
$ t% E9 l! U; Y1 l2 THIS IS A SENTENCE6 d8 R% Q' l+ r+ g3 h: R Y
3 SWAPCASE+ Z% |6 _( Q1 q# d4 o+ q0 X! c
dtype: object1 {: p0 ?# P% a( f; _. [
* e' B D4 c9 Q' h
s.str.lower()( ]7 d' n7 ?6 |6 I' j5 W
Out[88]: 7 T" l. T }9 {
0 lower
4 S; n( j* G: U9 D1 capitals
. I1 C# _% R8 F- `2 this is a sentence+ M2 l/ Q# p- k& `7 `
3 swapcase
2 d4 F! _' L8 [* g, T. u$ zdtype: object# E$ G+ [3 c* c* n5 S/ U3 `
6 ^0 d4 D6 ^/ V% [* n# us.str.title() # 首字母大写
) ^: s: U- q$ l$ jOut[89]:
% t6 F$ [; p* M/ H" ?0 Lower$ w8 B5 b: O& L4 ?
1 Capitals
1 ?! C$ s$ B. _; D4 f* p2 This Is A Sentence
2 p8 @) g* z. p+ A8 Q! p3 Swapcase# K+ s6 s& i5 z( V
dtype: object) x# f$ ~; { d1 c2 H- o: E
5 B. t+ z3 O8 n5 r Z9 w0 s0 ?3 c
s.str.capitalize() # 句首大写
+ S$ ], s; E( V6 D$ Z5 r& \2 D# IOut[90]:
- ~5 P0 ^+ j/ d0 Lower2 \. d* o9 x* \$ J
1 Capitals! \9 @( p2 E# A/ D, ^3 z# i1 F; b
2 This is a sentence
9 u5 x7 }% B! A5 N. u3 Swapcase1 a/ ^: d/ A4 { P
dtype: object; t0 s% B7 q7 H* W
% {3 |7 z8 C; D: \s.str.swapcase() # 将大写转换为小写,将小写转换为大写。! W% f0 Z" l" L6 O
Out[91]:
" f$ ?( A7 j6 ` b+ j( n0 LOWER
0 v9 m7 e; ?8 q2 D! [& `9 L1 capitals
. [$ `5 G: X; I& c2 THIS IS A SENTENCE [/ l t1 o0 k5 Q
3 sWaPcAsE
8 i7 A9 ?/ J& \2 U; adtype: object
0 Y$ P/ e" b6 C( I. \4 b d
: v7 S$ g- o. Y6 f0 d$ ^. O2 w0 ps.str.casefold() # 去除字符串中所有大小写区别
, q: y8 A6 M# h# p& I
+ l1 m, Z E p1 @0 lower
6 c( P, Y3 x% b# a$ M7 ~) `" V1 capitals
2 b1 n# J. ~) |3 M3 O2 this is a sentence, ^8 ~" Q/ B) e0 z- U. m" S \, Y
3 swapcase6 y7 m6 c% l; z
9 r1 P% M, O# D2 J, n1
; s+ F$ f; g$ V7 e% `) s26 z- D/ B3 H3 p" u5 A) Q
3
8 r" I4 P% l5 f- t7 O* _4
6 r: x* M0 @; |' i$ Z7 M5
' `% c2 Y J5 f' F5 V* H6: H1 H( ^2 |0 F0 Q" v# _* }: g
7
! j6 m s3 c0 \) F8
, ?+ `; q' k' G; k3 G9
4 {; s( G9 M3 c2 R4 u( d( G: c4 q10$ r g# e- v7 o$ X: J5 C
11
) X( V/ K2 j/ A" a8 r' ]12
1 [2 ^/ g4 k( ^( L0 e3 V13
' N# [9 B; X& E% s14
/ V; G5 b# q8 s+ e6 t9 B) w5 \: Z( a. S15+ [& m; Z) W; |3 g
16
/ o, J0 F# Z4 N: {0 c0 O" d: e178 I5 e _7 W4 ?. V+ S0 l
18# q1 [0 q* j* J! \: _6 \
198 w1 L; }# l! \# x6 j, ?
20
1 Y2 D9 T2 o% |0 U217 x$ _: i6 A( ^
225 n! X! ?. y8 B5 S# J) B; D
23
) t' A F& L7 y; H249 D% _" D! z+ k6 X' y% e
25; r6 D2 F7 q8 v& r/ y
26
$ \* M0 _" k/ ~27
* t& _ `# B/ i7 z' r28! Z4 @/ r( S% T& W
29
0 {0 d2 ?4 S+ A* X L, O- Q30$ C: C u+ V8 l1 C8 O) P
31) g+ _2 S+ ]+ l: n+ i
32 I! _4 i) C/ P+ u _
33% d* y- O5 Z! m( {, \5 t/ ~4 i) k
34/ [ y* n3 {$ E. ^( J2 V
354 a# J6 x9 v) g) {; b+ I+ c9 l- W3 F
36
) h# e/ ~& J1 U; C7 E. k37( K4 V/ l% G1 y8 t8 T# \0 O
38" ~, X* [& G; J
392 L8 N! J/ ?9 W+ x
40
1 S, X" {, d. p) n41
2 c: ?* X) A- W8 Z7 T3 l7 B3 d42& Z* b$ G a& ^( n& }
43
6 W ~: n* t9 s }" ^1 I440 y1 f. [2 n$ `- p' e( w) ^+ J3 z
45
* L/ ]7 J$ Z4 F1 x46
0 z) E1 `$ x6 g$ c0 a47
/ a' _) {( m! n. i8 w( |48! U; P9 }$ u0 t1 X
8.4.2 数值型函数# R" } J) E9 V, @: {1 F. [* {+ M
这里着重需要介绍的是pd.to_numeric方法,它虽然不是str对象上的方法,但是能够对字符格式的数值进行快速转换和筛选。其主要参数包括:
1 O2 r/ k3 w" `% y( @
/ `3 J* H' u7 D3 O5 rerrors:非数值的处理模式。对于不能转换为数值的有三种errors选项:& g1 J$ A. I+ k! P* P" f; U1 ?
raise:直接报错,默认选项! D' Q' I9 v* W# g
coerce:设为缺失值
4 C" X/ |/ C% e( c7 a' [ignore:保持原来的字符串。
* G0 E; P' o4 q! Y4 |6 _& udowncast:转换类型,转成 ‘integer’, ‘signed’, ‘unsigned’, 或 ‘float’的最小dtype。比如可以转成float32就不会转成float64。
( |! G2 s: Y7 q! ]s = pd.Series(['1', '2.2', '2e', '??', '-2.1', '0'])
/ F- C5 A( }- D, n: G
; Y6 R( j, t0 kpd.to_numeric(s, errors='ignore')
/ n Y0 N. B" s; BOut[93]:
) p- r1 S5 P B' b. J/ u! S: ^5 Y0 1, `3 s$ P# O6 G2 I6 m
1 2.2! E* L2 Z- O2 p# W
2 2e
( e9 H7 R5 k& ?8 Y# W' N# E4 E3 Q% c3 ??9 A- J8 D. R6 n( O9 T5 Y
4 -2.1
- m o4 b r }2 n6 ] o5 0/ G1 `, l- g7 J x9 Z
dtype: object
/ u1 q% `8 e K8 C7 s# `7 h5 `1 Q4 x
pd.to_numeric(s, errors='coerce')
2 Z- \& y( }7 { _0 M/ zOut[94]: ' n6 v# o t& o9 A( Z. X
0 1.0
# b# R8 G' E" K W* q. X) B! Q1 2.2
, J2 f8 e; c% h: \2 NaN! r! ?+ E. D9 H5 N
3 NaN4 p S& [& @! o" Z
4 -2.17 c; A" i: E0 I$ j, {
5 0.0) @" v4 m6 }" h( p
dtype: float64* m& V) f. I( }, J- O1 ~0 `. w9 r
) s( n5 q" d A7 E! b( I1
; M0 P4 s. o$ i+ h& U26 R! w+ f" v' m" O' [# j6 B
3& U) K1 k& f, [ @
4) v$ h8 c4 ], `; b
5+ A3 l1 ?2 ?, Q: ]0 }3 J7 C6 H7 l
6/ ?; O: a+ X/ U+ v8 j* s) R! Y& G/ S
7
4 a6 h7 o' y. t2 _) E( x7 P3 o8
8 `9 ?3 ?/ \. u1 v' e95 g! Q5 D% ~( ]& u) B6 C; h
10
& X: U* K0 Q, t9 T' ^7 H0 f11& S: K: f1 A5 Y/ I6 s
12
7 v& e' j2 f) V3 A! Q13
1 y2 F$ H' r g9 ]- Y. o14
: X5 d1 w9 p+ u: u; `" {. f1 ]; b15
# |8 A* i/ v6 I( K16
+ _6 A2 {' M( E" w17& `! ]8 A5 W n: ?5 z( e5 J
18
5 t% D% I* d( P190 G) s/ G/ y5 J- \( v
20
# G, ^- d. E. | W21/ r/ w6 C9 X2 O |
在数据清洗时,可以利用coerce的设定,快速查看非数值型的行:+ X! N! ^% q$ f+ | I
7 x+ F: L {4 {* W$ Is[pd.to_numeric(s, errors='coerce').isna()]6 [: M. n( C! @7 m& D- b/ J4 O
Out[95]: 8 R g$ t! Q" e: ?0 v
2 2e' K1 s9 b/ S" @$ Y% \
3 ??( j4 v# k* y+ Z5 m5 |1 R- X" v
dtype: object) M0 K9 b& N2 K$ q! h
17 k1 l# A. N2 a* _
2
6 C$ o' Q& l$ X# @( C3
, _- M" a% R6 a* L4
( K V4 Y6 `; T( L, X5- l' C; U3 T6 A
8.4.3 统计型函数$ Q; V* z* H% M# Z" i; E4 y
count和len的作用分别是返回出现正则模式的次数和字符串的长度:
$ I' q0 h' a" d4 m6 ]
1 ~' O; c6 r( F4 es = pd.Series(['cat rat fat at', 'get feed sheet heat'])% z# x e) a( X
3 i" G2 g! h% O. Gs.str.count('[r|f]at|ee') # |左右两种子串都匹配了两次 L% q! q* }% P- L5 `: a) e
Out[97]: + k- c7 p* B# z8 v& P: w
0 2
6 w; e! [' j; x1 2
. o; ^! D5 _# }& Y0 x# v; Tdtype: int64" \' p9 p- |; q7 z {
* f$ ?( V2 ?9 P
s.str.len()! h& E0 K' N" ^4 G2 P
Out[98]: 0 @6 P* \. ?; r( {9 Q
0 14
5 @0 s1 u- l! s1 19) p% V& o% _+ X6 |7 J' a: \9 D4 q
dtype: int64
9 U$ \6 @( m- L' J Y2 K# P+ n1
$ U7 I0 J. @' R5 ^3 s2
7 z+ F5 U- b* t6 o7 g32 {+ @# k( l- L& W2 L- n( b
4
* f' Q1 y2 r% _+ ]! w* ~5
+ X7 V# K5 u) p S; n0 i4 a) N# k6
* K4 w9 s) V% ^0 c/ `7
$ D1 R/ S9 e5 \80 [, v+ \/ n- A8 n' G2 ~+ j; @
9
* z W" D1 P5 g, b u/ j10& {7 c7 U6 S: P1 {" K% z1 A
11# X: z. h3 u& F" O: [8 F* g# M
12
* F" \# n; f8 h E" N13
( d. Q- Y: i* @2 V8 `, Y+ J' Y' `9 O8.4.4 格式型函数$ q+ T& x5 F; D
格式型函数主要分为两类,第一种是除空型,第二种是填充型。其中,第一类函数一共有三种,它们分别是strip, rstrip, lstrip,分别代表去除两侧空格、右侧空格和左侧空格。这些函数在数据清洗时是有用的,特别是列名含有非法空格的时候。
, L" K" e- @1 E, i
$ q% ?" ?: V j$ [1 o' H7 q/ nmy_index = pd.Index([' col1', 'col2 ', ' col3 '])
- |; h4 c5 r) o$ M& T' V c6 _( l7 F# K
my_index.str.strip().str.len()/ v: e& E& W* K! x: V
Out[100]: Int64Index([4, 4, 4], dtype='int64')
3 d' w. q: x# P8 U0 l7 B6 d9 P$ @+ i) d) r- f9 ]
my_index.str.rstrip().str.len()/ q0 r$ n3 H3 T" o0 _" F" V7 Q% O
Out[101]: Int64Index([5, 4, 5], dtype='int64')& B: n0 f5 G* b& p
/ S- [, k8 \+ X; f% h8 N6 }4 Rmy_index.str.lstrip().str.len()
6 u# F0 c7 ~0 f) @3 jOut[102]: Int64Index([4, 5, 5], dtype='int64')) ]6 l- i @9 v8 n( L6 m# g' h
1
! x& o2 C2 n8 j& c0 R2
4 H1 H; C7 O: c! R2 ~3
7 m/ d6 c0 z1 ? J; b4
4 | F2 m9 Z+ F4 b4 s% a52 H% P! O6 e: V6 ], s7 c/ t6 K8 ~
6
; ^! g7 s7 t# e6 s7$ j: C2 r: h- g
8
$ n# S. H4 b0 v7 }, o) k9
$ U% t* `( z. X. `3 Z+ \103 ~% ` j/ O4 E0 y/ z" C; g% _
对于填充型函数而言,pad是最灵活的,它可以选定字符串长度、填充的方向和填充内容:
' `1 i* C( Z. ^ _- X6 E) x" g2 f" \; R% n4 t
s = pd.Series(['a','b','c']); d+ T# {" k( G) y
3 I! \' h$ O9 U v7 C+ ^# S
s.str.pad(5,'left','*')2 ?& [8 C8 r* [7 `( z, R8 S/ p6 M8 C1 }
Out[104]: / V: ?; j- Z; |+ w+ R
0 ****a3 \/ E/ {9 ^# G5 j) u$ v9 V
1 ****b8 H% w0 t& E, U3 ]0 s; {/ a5 a0 z
2 ****c
9 P& m: Y! D% k) tdtype: object
, T, y% c, C2 A4 O* M! O/ G% Z; b! M4 k7 w
s.str.pad(5,'right','*')* Y! P* k7 F+ f a6 O6 m
Out[105]: " s- S8 }1 P, u, J( z* T- j
0 a**** F' q7 }* R6 Q( @
1 b****1 A! a, o ^# q; q# ~# E) ^
2 c****2 t8 Q' c/ L! U- y9 E! h" T$ t' Y' _. ?
dtype: object
" j2 z7 ~: ?$ ^6 |: p% I/ ~% R: g! x5 N& B- A7 v. z% j1 Q; H
s.str.pad(5,'both','*')9 U* Z: d/ ?1 u
Out[106]: % d: C2 P/ {# I) A* g( l! s
0 **a**
& p* M1 I$ f- g D) U1 **b**8 _6 M* |) Y7 _+ u5 [. H
2 **c**! F0 U# a! O$ {% P) u$ a
dtype: object
+ A( G: Q% }; k) | t& ?6 V( [' G9 N9 z; p% J" _
1; t& F$ i/ Q- P3 @; @
2$ R+ @7 f# X# V9 G
3# D. i8 V5 D' G& a
4
& I9 o4 I Z) B) a5% h5 |4 [/ |( o# J- Q, b& D( Q
6
5 K( Z% [8 \, m0 y4 v) b+ c7
9 _! k' b6 X2 q% r! B8
$ g4 {; r' u; b* W8 i+ Z9
9 _6 P j+ ?! w- }$ g; E10! e v+ I: b }1 A
11
3 O3 }4 P& N& k2 p) ]' R. X/ U12
& B3 o, w4 Y9 J: ^: K# M/ ]13
& B8 b8 x4 ^6 T14+ J# l6 h% Q" {
15
8 q0 G. m% l) w- N' j- w16
# N$ _& n% J7 p( p" l17& v! |6 |; {" u+ s: G
18# N& h8 J& V( @, ^7 b
197 m9 @5 F9 b0 A" s9 \9 A% H
20) V/ @/ {3 Z. B/ j* a3 z
21+ v9 y" W" ^: c* @; W! S* y
22
; Q1 k' q. G6 Z' x 上述的三种情况可以分别用rjust, ljust, center来等效完成,需要注意ljust是指右侧填充而不是左侧填充:
( j D5 V+ Z. g; Q" b) L8 @/ I( O, V2 y! `! e/ L% x- [
s.str.rjust(5, '*')
9 E7 u6 c" ~' d0 R& M5 k9 NOut[107]: / m( {& w) K7 e% v
0 ****a, @# S& c* A7 W M% |5 l, U
1 ****b+ k8 I6 m- H7 A
2 ****c% _$ [! j& {- ?, v+ U
dtype: object0 M0 R5 w1 K& n2 _7 E" b
9 g% X9 y% z% V4 _
s.str.ljust(5, '*')
5 Z, H0 } d2 D! T' H/ uOut[108]:
7 {, s3 J* R. t9 n0 a****( o5 v& D9 X; l4 [% e/ p! w" T
1 b****( D4 f1 |- o: m% ?6 r, `9 J
2 c****
% c* R- |; T) M; T9 U4 Z# Ldtype: object; n" H8 ^" r, q3 D
! u9 q3 D, K8 {& Ps.str.center(5, '*')
, K2 R$ e* Y8 F( |1 D9 O( COut[109]: , n# A) f' ~5 y$ ]) Q3 b7 z
0 **a**( P% |6 g/ P4 l/ J; W6 K0 [
1 **b**) ^( z1 c% s* E# q
2 **c**1 U% q- \/ [0 Y& E8 P
dtype: object
+ y( G F6 L3 Q, Q' u
! o) u& C ]: m5 q+ W1
# I6 \* E0 j1 t- X6 O2: x3 c* ], Q8 ^1 D
3
! @ N. g- g' k8 o5 r2 w! n4
/ G# p L- q( l" G5
7 w6 l& F- }' N" P/ h( N/ }8 R6
" _1 [% O8 ]3 X& ~7. i& l) U4 f6 k( H4 R5 t9 a
8& A2 \. w, l9 g5 ?5 z" z
9
/ ?4 Y; m$ G' ^% i/ m/ h108 Q# j( _, I4 W: p& q8 V7 t
11' e, p! k5 { ?$ c$ y4 J: P: ~
12% _/ U3 l0 Y. Y0 q# `5 C& H
13
/ V# A+ [5 ]7 k14* K5 p8 C% Y2 Q1 A( w2 Z! d8 ^
151 W. x0 Z" i0 T! W* K' k
16
% L0 D' P' B; K% z& c$ p170 n% H- g: p$ | P. A
18
( P* Z( @! K% O g# r- X' x192 J) M! q$ H, I! W; s
204 |, Q+ A' G& k
在读取excel文件时,经常会出现数字前补0的需求,例如证券代码读入的时候会把"000007"作为数值7来处理,pandas中除了可以使用上面的左侧填充函数进行操作之外,还可用zfill来实现。
* O8 Z$ r8 F% q3 D" A0 q8 o, r: Y0 }
! f) t2 ]. r# l2 [; }" R% n' gs = pd.Series([7, 155, 303000]).astype('string')
1 s+ y& f3 [7 @* [8 X7 G9 P; A i% X. \# {; e& y5 t
s.str.pad(6,'left','0'): T' ~- n h9 }+ S' B4 C4 `! P
Out[111]: % D& r) n0 b. @* X
0 000007
$ S: A2 p. M4 ~ O" L1 000155/ I& H5 {! A1 w% u$ y7 c
2 303000+ c- N9 V& R9 z a( A% N
dtype: string3 }$ S# r7 F( c9 E: g g
% F( E$ F. ~1 I: e# d* e) C
s.str.rjust(6,'0')
7 E/ D* Y. @/ X, V; j1 k6 `7 WOut[112]: 3 S7 n8 Q# \4 F# V" E3 \; G; F
0 000007! o; ]7 k+ s: g0 s2 @! n% O
1 000155
& b4 I) D: G5 {3 x! N2 3030000 t) Y S, `; K- o* o1 f5 U. y
dtype: string
& a- `4 i+ i0 b; y3 m$ ~. N; H8 ~3 ?
5 O8 y: O& w8 M( L' G) ps.str.zfill(6)$ k8 |4 l& E/ m. M. M0 }
Out[113]:
, U; e4 D! i+ F0 000007
& M5 q+ y# f' w0 ]8 t9 P1 000155* H; ~; O |' K R* |+ [. g9 K& M9 R
2 303000
0 g" a/ b/ T# c# B) Q/ I D% L, Ydtype: string- M' S6 I% d3 k/ [
0 h8 E' E) t3 Q7 X, z
1
8 Z! Y# p( e: g8 d6 N% n2
$ Y, {% v9 S4 h* R9 C$ E/ m34 D" I: \+ x. V1 ]# C. V. d
4( G0 }: q, ?% b
5: d9 g6 T. v6 `7 c& I4 t6 A+ x; O
69 {$ V3 t, g1 u+ ~& u4 y) Q( Y( Z
7) j7 y+ T0 M7 v* L( w) Z
8* F2 ~/ @& S* d2 @' ?
9) e3 F! e9 d/ W
10
" N, _. D8 p4 C11
4 p2 ^3 w6 S2 O* i4 b12
0 M! ~: W" R6 @134 ~. f" K- ]8 H+ N4 K
14
! Z& [7 a5 q3 }( V9 ?) y/ }: t$ ~157 V0 r E) B: [7 {' f2 j/ J) }
16; t9 L' {6 b2 @1 q+ K6 t
17% R" ]3 t9 W- T5 E G2 O) D
18
5 l( e3 r9 G; i19* l: w# Z f7 W% u% i
20
/ {& `' D4 M1 a* Z$ ]21* x; X! S l; ^% s: H- y0 N
224 q7 Y) C) B, ^+ ]
8.5 练习9 b# t: n" l1 m; R
Ex1:房屋信息数据集# y! r5 C% b2 z: A4 C1 W- f ^
现有一份房屋信息数据集如下:+ c8 E. D& @5 Q2 {# c/ b
+ Z# ]0 d/ `/ x% M7 d* X
df = pd.read_excel('../data/house_info.xls', usecols=['floor','year','area','price'])4 G2 {: q7 u; ?( L
df.head(3)2 P9 ^( o( ], Y5 D/ E3 i
Out[115]: ; I5 a/ v3 A# I7 a- j9 d9 [
floor year area price
2 _& _: e& d5 O; }9 s7 K: ]0 高层(共6层) 1986年建 58.23㎡ 155万
# r6 Y7 O8 T9 ` k6 g& n2 j8 x1 中层(共20层) 2020年建 88㎡ 155万- z+ J" ?7 z2 ^# e" K
2 低层(共28层) 2010年建 89.33㎡ 365万* W1 G4 m' v; H0 `' I1 a
1
4 A/ i, a1 {# c- W1 C2
Q5 Q# i* }, h% C& w3* @0 G. k; s* g4 }6 s
4
$ h' M: T8 q! R K$ M' v( E8 X! l5 S* C% F# `/ m9 m
6% A6 b( k) g, \, }, w
7
v! Y8 r5 O) x; b5 R$ i. P将year列改为整数年份存储。
8 B- R* t0 L( o# ?7 u. {将floor列替换为Level, Highest两列,其中的元素分别为string类型的层类别(高层、中层、低层)与整数类型的最高层数。: C8 p! t: R D' B4 U: b
计算房屋每平米的均价avg_price,以***元/平米的格式存储到表中,其中***为整数
7 k1 Q: n# p# s将year列改为整数年份存储。
5 z- {/ x; N( o"""& [4 v% d5 h5 O3 E& y
整个序列需要先转成Nullable类型的String类型,取出年份,再将年份转为Int64类型。
* L' v* k: T2 Y0 t( ]" ]% g注意,转换的类型是Int64不是int,否则报错。即使astype加参数errors='ignore'跳过缺失值,( f; m+ S! ]8 H& D( E. J
转成int后,序列还有缺失值所以,还是变成了object。! b6 J. i% T7 n( K7 k9 u
而整个序列转为Int,就还是Int类型,缺失值变成了 pd.NA 。# w: Q' l! p* [( F$ K4 O! S9 q
"""8 Y( ?- L# S+ d$ O# @; c# v& K9 b* @
df = df.convert_dtypes()
5 G3 s( Y& `/ E6 O( T% i1 N) Y3 Tdf['year']=df['year'].str.replace('\D','',regex=True).astype('Int64')
$ A/ {1 e, l" \df.loc[df.year.notna()]['year'].head(), }( H6 C5 s2 S3 R6 B/ }3 R3 y5 v
5 _9 H* I6 z% z- b4 z7 q+ X6 ^0 b: K0 1986
: i( h* l4 z- ~7 `4 t1 2020
n( O/ Z$ x4 C! c6 _2 F* _/ `: {2 2010
) p4 d: e) }4 W( W" V7 A9 R3 2014
) s- |% @# K1 q6 R: L4 2015) R0 X" Q1 A# B) ~$ u
Name: year, Length: 12850, dtype: Int64) {+ Q5 L4 p+ U4 T7 H
# P2 n4 T W0 G6 l- Q% c; b
1
7 ~. \/ d" J$ Y, P2 f& c- E( H2
5 x2 |# o+ c5 [. G! F! U, S( }# f3 }) S3
# I/ E$ k9 Z m$ _+ h0 v' h4) L5 R( d* P3 u9 Y n% J
5
4 Z0 H/ N7 ^3 m- j4 r0 |6- P! S5 [+ E, q- }4 k% t& t4 W0 L
7
2 _4 A; k ?1 I" Y+ q$ p. F8
" o, B! n" C1 O4 Y* M/ R9
$ A/ |* n- b& `; b& U4 D, o; Y10+ d/ s) ~9 D) B- u- L9 a
11& u5 `$ @2 V/ @) o
12- w( F# M+ L1 v# T
13* J+ ?. I$ ^ B" u" M1 k
14
& P( U8 C7 B+ n8 u' Z15' H0 j3 m7 p3 r. x: o7 a) y+ K
16) A4 q+ R" K* d- N/ K
参考答案:
2 X2 [( p( U8 D/ {, |1 R4 H. W6 }1 C; k* E* X5 g. k$ \: x
不知道为啥pd.to_numeric(df.year.str[:-2],downcast="integer")类型为float32,不应该是整型么
: i/ Q& r/ @+ k+ D
3 c; d" l, {( h# C/ kdf.year = pd.to_numeric(df.year.str[:-2]).astype('Int64')
! }/ X$ t. z) L4 e% N7 I6 ddf.loc[df.year.notna()]['year']
' n# M" Q/ J/ d! m# a4 y- y$ N' S1
3 K" @( K+ K3 `2
, Q P2 a$ Z, G- G# k' \5 J将floor列替换为Level, Highest两列,其中的元素分别为string类型的层类别(高层、中层、低层)与整数类型的最高层数。
+ B( M4 B* r: E) e" jpat = '(?P<Level>\w+层)(?P<Highest>\(\w+层)'
9 g) |9 c4 H0 _5 Mdf2=df['floor'].str.extract(pat) # 拆分成两列,第二列还是(共6层得形式,所以还的替换一次
7 |2 n8 Y" r4 r( _. I* q( Cdf=pd.concat([df,df2],axis=1).convert_dtypes() # 新增列拼接在后面,再次转为Nullable类型# P( |4 y# R9 @4 T" n
df['Highest']=df['Highest'].str.replace('\D+','',regex=True).astype('Int64')
% }" t) j2 K% N* e6 bdf=df[['Level','Highest','year','area','price']]" W5 \2 c2 A" N7 L* a6 }
df.head()' I- V* _( V; ^. i0 G& y
4 x4 n6 O. ?" m) C. u2 f, D$ } Level Highest year area price
3 `4 e/ r/ h* _/ i ]0 高层 6 1986 58.23㎡ 155万8 A2 @) {' K! B& F$ q4 _
1 中层 20 2020 88㎡ 155万+ U0 [ I2 ^+ _5 o* }* D
2 低层 28 2010 89.33㎡ 365万
- W* a+ s) k8 C/ K2 M4 N3 低层 20 2014 82㎡ 308万
: W X2 [: N+ F9 h2 O( H4 高层 1 2015 98㎡ 117万# J Q! m/ I+ w1 R% H2 `
1
' D) G3 |) m0 l$ k$ K( d" i6 I2# J- E5 N* s" D* `( n; ?% s
3
: i$ v* R3 q& |4, H, z3 d$ i: ~+ f% `
5
) _; v' o6 k/ m6
: b: W5 x7 W% \% ]3 Z: i! |7' K) ]1 X! s1 w; Y+ ], f
8
" }* H% ^' O% g& T% T. l4 E95 Q5 R4 d# `4 q/ Z
100 E3 I) q% U* `' ]: R
11
n- q8 m8 p, z' s12
7 e9 Q" w' a+ l) i# N; t133 }/ d% g; G+ `5 b# Q0 g4 A
# 参考答案。感觉是第二个字段加了中文的()可以准备匹配出数字,但是不好直接命令子组了8 x' N- m$ z7 I _; b) b1 N/ r* m
pat = '(\w层)(共(\d+)层)', d; e. `- {$ n, ?; X, x0 h
new_cols = df.floor.str.extract(pat).rename(# Q6 B, c, d D8 \8 N, t9 G: y
columns={0:'Level', 1:'Highest'})7 W2 {" F$ @; s+ H7 i
! C) x2 j+ P6 \8 y
df = pd.concat([df.drop(columns=['floor']), new_cols], 1)! ]1 S' B6 K1 d; Q+ ], v
df.head(3)* `. |6 d( n( I4 p0 T, b
; U3 F0 k5 \/ Z0 Q7 y5 i
Out[163]:
3 w, e+ P9 O6 S4 N4 z/ e year area price Level Highest
1 \7 z6 R8 p0 o; |( @ D6 N0 1986 58.23㎡ 155万 高层 62 Q$ J% D6 O7 w0 t4 X. B! T1 r
1 2020 88㎡ 155万 中层 200 e1 }, o+ J" \: M$ r- z
2 2010 89.33㎡ 365万 低层 28
E* l" g8 X2 o3 v$ A! H1
) I: \# n; t! n' |2 b21 `: m) z$ H1 L* g
3
! e* ]1 w8 g9 C" E4
3 T: Y! N- z- H: e. ?5" f, o9 T3 V- I0 I* A+ o) c
6. t* `# ~' U; B* g8 R
7
; f- x/ D1 J- s j+ N8
. s2 |7 N6 \" G% c& D+ M& t/ ? o, R b92 l- T2 B/ ^0 \" y, G; l: S
10
5 r2 f5 I" g& ?11
' u. H! G0 `" G8 Q4 o. }12! ?4 v ~5 a( [: Z3 {
136 l5 H4 B$ `$ {1 x; ]
计算房屋每平米的均价avg_price,以***元/平米的格式存储到表中,其中***为整数。" @9 A$ z. b0 f" l0 ?" t& t1 p
"""$ {5 \8 d, y2 ?+ y7 h8 f
str.findall返回的结果都是列表,只能用apply取值去掉列表形式! z0 \4 Q, ?2 y0 i" e. U
参考答案用pd.to_numeric(df.area.str[:-1])更简洁
9 h- I/ }0 H H& N3 Z由于area和price都没有缺失值,所以可以直接转类型
5 H& w8 Y5 H2 }) W$ M/ C( s& B""") I; d c+ B2 L
df['new_area']=df['area'].str.findall(r'\d+.\d+|\d+').apply(lambda x:float(x[0]))
R# k I9 f* \df['new_price']=df['price'].str.replace('\D+','',regex=True).astype('int64')& B$ K) n7 R9 J# [. E. I0 p
df.eval('avg_price=10000*new_price/new_area',inplace=True)% p; R9 U) _7 d9 }' x( P. }
# 最后均价这一列小数转整型直接用.astype('int')就行,我还准备.apply(lambda x:int(round(x,0)))
+ d+ {8 b3 q9 k0 a( Y* u# 最后数字+元/平米写法更简单1 X0 c" M* v' x8 r/ F# {( N, K3 y/ D
df['avg_price']=df['avg_price'].astype('int').astype('string')+'元/平米'* K' `$ } T& E3 f7 [; }
del df['new_area'],df['new_price']
! Y0 g$ @2 ], K2 H6 w$ rdf.head()
" o. G; S2 e8 J8 B; L- K# @
+ f5 U6 M1 r: _- [7 S3 x Level Highest year area price avg_price
. P' v; d6 w9 V8 ~$ b& R0 高层 6 1986 58.23㎡ 155万 26618元/平米% P' @3 j: B$ I0 b2 \! h1 f3 l0 J1 z
1 中层 20 2020 88㎡ 155万 17613元/平米( \' c- n2 w7 H/ D/ O
2 低层 28 2010 89.33㎡ 365万 40859元/平米
+ l1 s# m. u# x3 ] G$ S" E/ J% @3 低层 20 2014 82㎡ 308万 37560元/平米
$ s K C+ W" Y' }2 j' z4 高层 1 2015 98㎡ 117万 11938元/平米
2 v) x0 D5 J/ m9 h! g! G: L. a, V
. M6 V6 S- L" v) k8 i& z8 w# v1
( K4 O0 M5 D' d25 o0 b6 ]$ \; C1 d& t
3$ l7 Y# J" \. k( B
4
9 \' g9 S1 x! W* j! x5
6 k0 ?/ `) M' [4 z6
# G- a/ x7 J& o$ D, Q7
& s+ ~2 f- g# H' M; d! v) J83 I# f& T0 j3 A$ ]
9" a# _5 a( F% L" e( `8 t. Y9 M
10
, b& Z3 V+ w s4 E+ f& N% e- @4 v11
+ h8 j8 E; }$ I2 \# a: b1 s12
w# O1 e; d0 e$ j13
4 ?" p0 @; g0 ]9 |142 Z m0 S! a! E: D
15
3 ]* j! [5 ^$ X( H$ J16
+ c: }: f0 B2 |) l- q, E8 ^176 L; V/ s! b( g- m
18
! l# n1 O" L$ m) Q8 X* o190 [: Z; Q2 B$ |1 D" u
20
V1 J; Q5 E! X7 {1 }& M# 参考答案+ B; {3 }( n: R; ], Z; e& T! v
s_area = pd.to_numeric(df.area.str[:-1])
% [) R/ ^& B% j: Vs_price = pd.to_numeric(df.price.str[:-1])4 r; S5 @/ U) K3 p' j
df['avg_price'] = ((s_price/s_area)*10000).astype(" V; P \; z$ V; Q! L; H
'int').astype('string') + '元/平米'
5 Z7 b. k7 w2 M4 V% K- o ^
: J' M4 S# b# P7 Odf.head(3)' ~4 ^& ~% z7 K
Out[167]:
6 E9 X1 ^+ c9 _* g: r year area price Level Highest avg_price
4 ^' f: \: H8 ^* F% D5 B0 1986 58.23㎡ 155万 高层 6 26618元/平米# |# g" }4 z+ q
1 2020 88㎡ 155万 中层 20 17613元/平米! p0 K U% Q# k0 @1 e
2 2010 89.33㎡ 365万 低层 28 40859元/平米
6 U) N# |7 z s! c3 C; G R17 r' [9 u' i. f
2
8 r9 F7 J4 y4 b3
/ ?, J; N/ k( ^4 D42 x% ~& Y/ W$ o" F$ @. _& m5 G
5
1 b" Z. B+ g4 O% R. c6
+ e! G7 N5 H- R! Z- Y8 S' Z B3 g7
4 A! P3 e2 y4 l) E; Z8
" y2 G* D- n9 H8 f- i9
- }# [3 ?: F5 Y/ n* ]105 }, h$ M- `/ u6 ^
11+ M5 L$ ^. s$ @8 ^; r; r. T( c4 K/ O
12
3 A, R( O, ~* J' \ L5 m# ?Ex2:《权力的游戏》剧本数据集
7 G S" r- e' g/ H& V1 w: T现有一份权力的游戏剧本数据集如下:7 n9 l! M7 J% y' ]# O9 U
1 V' O2 }# q# o3 u8 y1 |df = pd.read_csv('../data/script.csv')8 A8 i7 k3 \+ P2 {1 u; D
df.head(3)3 t3 R$ ?1 L* \! M: i& O& d
) S, ]2 v- v( d8 C5 \( G0 q+ w5 IOut[115]: 3 ?3 N7 J: z4 U" e
Out[117]: - w/ r- O, d3 C; E
Release Date Season Episode Episode Title Name Sentence- t8 O" C, [9 T6 ?, \9 i% w
0 2011-04-17 Season 1 Episode 1 Winter is Coming waymar royce What do you expect? They're savages. One lot s...
* h% r1 ~+ q+ E( Q# `. J1 2011-04-17 Season 1 Episode 1 Winter is Coming will I've never seen wildlings do a thing like this...
, Y8 C$ Y s7 H3 b( o2 2011-04-17 Season 1 Episode 1 Winter is Coming waymar royce 5 K- B9 e) h @* G/ e
1
( ]- R( k6 y+ R* V `8 D2
6 x8 o+ V% x5 E2 q3) Q6 O3 m! L/ o) }. _- {
47 _! e; W/ r n8 y$ n
5
( b- m% n& B* p8 D6 J" L6 R9 s6
! a' p- k& @* D i# j2 I! f7
- |$ v5 c+ F7 @& u1 P0 Q/ Y3 J8
" L. n. n0 J; U9
* q9 m* D7 A/ j* g) o3 F! w- q; b计算每一个Episode的台词条数。, S2 E& @7 {3 {& l, J
以空格为单词的分割符号,请求出单句台词平均单词量最多的前五个人。
. B& b6 @) t, _* [! a若某人的台词中含有问号,那么下一个说台词的人即为回答者。若上一人台词中含有 𝑛 个问号,则认为回答者回答了 𝑛 个问题,请求出回答最多问题的前五个人。
$ J1 X% {1 B# G5 Y: \$ h1 F计算每一个Episode的台词条数。 L' z) }/ q2 G8 @3 ]
df.columns =df.columns.str.strip() # 列名中有空格
% w4 A' ?/ J) ]df.groupby(['Season','Episode'])['Sentence'].count().sort_values(ascending=False).head() e3 v- L! y, K
2 B8 ~/ S9 Y7 |. |: Z4 I- A$ Y
season Episode ( i2 ~% w' g2 B
Season 7 Episode 5 505
' \/ W0 K! Y" }& W$ n/ B. PSeason 3 Episode 2 480
. j- [( o/ f1 ?- ^* `; Z9 dSeason 4 Episode 1 475
2 ^1 z. V2 w" }( [1 d jSeason 3 Episode 5 440
( D% p9 @) `& f1 O: [% n, GSeason 2 Episode 2 432
$ g1 R" W/ M& m* m. r' H [# u1
, I6 V) y, V+ k+ N3 F4 h2
; F7 J5 y" G7 j! g6 F3( _0 q5 l1 l5 t; [" T
40 I2 u/ N! [/ M% W o
5
! H7 B7 j- y2 Y) t% x5 d7 h- v6
& i* O- [9 \4 a7" t7 E4 N6 W; n4 F
8, a3 U, y2 b5 X0 c
9
6 k) p/ T7 u% G9 Q6 T以空格为单词的分割符号,请求出单句台词平均单词量最多的前五个人。* i6 e2 n" M0 l7 p, a
# str.count是可以计算每个字符串被正则匹配了多少次,+1就是单词数 ~+ Q# v( x% J. Q# p4 x: A7 x
df['len_words']=df['Sentence'].str.count(r' ')+1- e5 {. k+ I- B5 s
df.groupby(['Name'])['len_words'].mean().sort_values(ascending=False).head()
: }7 V* Y ?3 M) s: @
# ~8 M# ?' e/ }; m9 l8 mName
) v N' Y4 L) B) N4 n0 _3 k6 P& Gmale singer 109.000000$ K3 P( v4 ^$ r( V1 ]/ `
slave owner 77.000000
) U o4 I' e8 C* Rmanderly 62.000000+ p8 v# v5 E! ^; o; [
lollys stokeworth 62.000000
# y+ f8 ~2 _4 q2 [8 g# }4 z, bdothraki matron 56.666667
& C+ E9 m8 D8 W2 y- jName: len_words, dtype: float64- X' D, @: L) T/ e- n
1
2 T0 i6 q& y0 g+ k. L2
% e6 m- R, L9 Z& Y' ]3/ O% O$ d3 a9 z: \2 R: P5 @0 z$ E
4
$ Z Z- t' O2 f( U/ I5! I. ^; w3 q' ^' D
6% @8 |& q) X& p" ~1 y0 k
7
+ b& B6 H: d2 q8/ {- H o; P6 a* c
9* ` X: H% O! N: V3 I' f6 Z z; {
104 F: C. A" Z1 s
11! A. g% V8 u2 J' C ?
若某人的台词中含有问号,那么下一个说台词的人即为回答者。若上一人台词中含有n nn个问号,则认为回答者回答了n nn个问题,请求出回答最多问题的前五个人。
& E6 k% [. Q. @7 q: c; bdf['Sentence'].str.count(r'\?') # 计算每人提问数/ F, c" v/ C' ], U1 M; W
ls=pd.concat([pd.Series(0),ls]).reset_index(drop=True)# 首行填0
2 ^' ?2 z& p* A5 r* a$ T: Jdel ls[23911] # 末行删去( r. r8 C: l9 [2 X; D0 ~
df['len_questions']=ls& \' |! H+ k; n2 L. t7 y* n
df.groupby(['Name'])['len_questions'].sum().sort_values(ascending=False).head(); M/ q) v8 o$ y0 O5 O
* Y8 U- i4 b1 y. v. n: p* H( \Name) | q, y! ?5 C; D( @" h% E
tyrion lannister 527
3 S% M4 X0 J" l/ ?0 s5 Ujon snow 374
6 b ^7 V+ }- t4 tjaime lannister 2830 L/ Z8 [2 z0 b6 K0 z
arya stark 265) n' D- n3 @; `' ^7 r7 a) j* W
cersei lannister 246
; k& o' q( p7 h' |' M! zName: len_questions, dtype: int64& @9 Q9 R: Y2 `: o
1 ?# k( y- q( P$ v' g3 V
# 参考答案
: ?( y3 T9 V- M* l) ?5 ns = pd.Series(df.Sentence.values, index=df.Name.shift(-1))
8 r* v, L/ l' [% X( t0 cs.str.count('\?').groupby('Name').sum().sort_values(ascending=False).head()
& O7 L4 q% {) Z, j" X
}5 Z, R7 d& }6 g1
0 ^6 C8 |) ]$ z$ E2
2 N& G7 m( R2 A, \* k& W3
: P; r5 e- j$ x" i4
& J* r( s; |4 } U" P54 h- _1 q L1 a" o; e# H9 Q
6
* M3 W4 v }+ T7
2 A* R2 r+ a- _+ w7 ]7 d+ `6 x" L8
9 E5 @ g1 y1 A' l# ]9! L8 W5 p# r) \2 I& }6 M1 u
10
8 K7 W* f& _, V! v: W, g11
3 t1 U% w* k2 C! o& v6 T0 p12
. E" V+ r) d' O! l13
. V) ]! Q( Z8 v. N0 {14
# T: r$ I8 U: T0 e- |15
7 `# }/ ?# l; m* m# J5 u2 [5 X9 a16
$ l5 B, H. O3 z4 o: m8 }0 J17
0 R+ P- u9 E1 o; D4 U; b第九章 分类数据
& o& d a) A1 F% z4 vimport numpy as np
% o+ ?& Y8 K w9 T( Y6 kimport pandas as pd
0 Z8 f0 q: w# V10 J2 ?; R% E- d, Y- D* H- e
23 u9 _2 ?7 M' I) n0 o/ {
9.1 cat对象
' a& i3 u/ x) X( `* X0 d; s( f# S9.1.1 cat对象的属性% U* s- N7 Y% r2 l
在pandas中提供了category类型,使用户能够处理分类类型的变量,将一个普通序列转换成分类变量可以使用astype方法。5 x$ @7 B z, }, \8 M0 G# [
l/ O! Y5 i& ~- q
df = pd.read_csv('data/learn_pandas.csv',
& c+ t1 x- x/ X+ z usecols = ['Grade', 'Name', 'Gender', 'Height', 'Weight']). @* o8 N' A, ?( E
s = df.Grade.astype('category')8 e0 w& H0 x6 r7 ^! A
$ n3 a1 U R0 T
s.head()
. Z3 t C, u- M5 g3 oOut[5]:
6 H! Q2 A& _6 D( F' k2 C0 Freshman" ]9 A" D# @" Y$ D) l, e0 G
1 Freshman, g$ |- ^+ F- V: d, I. Y7 @5 K
2 Senior2 U# u4 c4 K/ a" @- D% Z
3 Sophomore
* h( X" f1 t3 F) ^5 T4 Sophomore
( P o* I' K& s" X+ }) ^. H3 RName: Grade, dtype: category; y6 k6 S8 J8 K+ d4 O; U
Categories (4, object): ['Freshman', 'Junior', 'Senior', 'Sophomore']
7 a$ y J3 B8 O x) R# p ?8 z# v# j. `1
& N0 T: O6 F0 L0 o% x7 c: M2- ` I& j4 |8 Q1 b, V0 x, c
3' s& a. W+ l5 m0 n( h! x
4( G6 ?0 ~' m% W0 F6 y- _
5
{% a5 ~7 O# o( O. m6
( v; k& t* l: ]3 J7
7 k1 N/ a# n* z) T2 Q8 u83 y- d2 [! A# @% ?: M9 ?5 h
9; A& s* m' B3 y& {! K0 Y
10; L' E$ H; u* v& o
11. P- {3 l$ Q w1 x
12& k- n9 D, _4 [- H1 K( t
133 R. B2 X8 k" s% `
在一个分类类型的Series中定义了cat对象,它和上一章中介绍的str对象类似,定义了一些属性和方法来进行分类类别的操作。
2 I# Y1 X# ~0 f) j) d$ ~
* ?5 [4 n+ k( c- N- P! E! \s.cat
4 a# K7 }2 T+ i. ^' [ ?Out[6]: <pandas.core.arrays.categorical.CategoricalAccessor object at 0x000002B7974C20A0>
: D5 ^1 ]: j8 K; e( j- g1) I' q4 W5 p, b$ Q. J! ~
2# y" {% N" p/ D" X
cat的属性:& V( B# y0 H9 Z3 q9 L7 Y% B
9 L9 [$ A2 V1 U8 `, V
cat.categories:查看类别的本身,它以Index类型存储1 R7 [+ k, T" X& z
cat.ordered:类别是否有序! L, z/ L. Y7 m- ^* `/ @9 E2 H6 T
cat.codes:访问类别编号。每一个序列的类别会被赋予唯一的整数编号,它们的编号取决于cat.categories中的顺序
9 F& a) Q+ c4 ms.cat.categories8 r! }8 M7 @$ P4 N) n, e) i( s( ^* V
Out[7]: Index(['Freshman', 'Junior', 'Senior', 'Sophomore'], dtype='object')* `8 b s( {# ?# t2 C) _# `' G( W- t
* J, O, }2 `; `8 Ds.cat.ordered
# P9 f8 l! v: p6 `4 Q8 w. x9 lOut[8]: False- [5 U! A* ~ @9 @- s
: f, S8 ^& a2 c, R- k' m* q9 w! rs.cat.codes.head()
: Y+ N. W/ V5 i5 X! POut[9]: - `2 ~2 h- M0 l- R: V
0 0
/ k5 M1 ^4 x) y5 Z) A1 07 [4 q3 \6 p! U# v3 K6 w1 M9 A( d; e
2 2
2 u5 i& R' z0 s7 Q3 34 P$ I3 G3 v% G
4 3
; i6 ]( @/ |; w, ^( i" E* K- H0 m4 ?dtype: int86 P! D* M3 D2 s7 m- v/ f, v
1
6 V. m1 Y+ H/ G24 Q/ }/ c5 m5 X; O# c& D
31 }' I; G/ q2 C% j& h, j+ X+ X6 m
4( N& S @9 {; r1 b8 [
5
& y: h# R) Y. {: y6
0 i( P# I- Z" m2 O2 Y+ x: w$ a( ^0 n% {7
" P; i' p5 R; e! `# L1 u83 F& m! a1 s. r7 |& v/ K# ~6 l
9- ?$ |2 d/ \/ A; F) }# X0 r: |
10
a9 b3 V, p, j! {7 ]11/ ~& j2 @- l) E. r
12
0 F/ M1 m7 ~6 `130 T- ?( l; V! T& v6 \. R
14/ A `; ]; m3 @+ x6 f, N$ }
9.1.2 类别的增加、删除和修改2 u, Q$ g4 l( H! D' D
通过cat对象的categories属性能够完成对类别的查询,那么应该如何进行“增改查删”的其他三个操作呢?
6 q( k- N$ o. U7 D* o+ M6 h! d. N3 s, |7 ^1 T+ ` }" B* B
【NOTE】类别不得直接修改
; h0 p/ L. ], x& b" U9 g; C7 x在第三章中曾提到,索引 Index 类型是无法用 index_obj[0] = item 来修改的,而 categories 被存储在 Index 中,因此 pandas 在 cat 属性上定义了若干方法来达到相同的目的。( h4 B5 {) T/ l6 |1 C
' F# f& V4 f; g+ h, {3 ]
add_categories:增加类别# P+ _. Y( L D( h
s = s.cat.add_categories('Graduate') # 增加一个毕业生类别
3 r; ]8 }" K0 `, a! x& zs.cat.categories* G" E( S7 e. d0 v) Z$ f2 U% a# _
. ^" K: X) m6 d' i; R( m
Index(['Freshman', 'Junior', 'Senior', 'Sophomore', 'Graduate'], dtype='object')
. J/ S' ?2 |0 F# \1+ n" t, u. J0 E9 N8 M( d6 e7 G0 E
2' H% ^/ R4 c, c' `
3
% a7 i# Z3 Q" n3 ^3 V4 x) m d" @4! |8 w' F7 Q9 j" P. L9 s4 D
remove_categories:删除类别。同时所有原来序列中的该类会被设置为缺失。
+ E+ d+ S) l; x" r. y' K& ts = s.cat.remove_categories('Freshman')4 g+ t* H) c, j7 K
4 n3 h! e. K5 w' [# I- P
s.cat.categories8 e, ^. N a# E" {6 O% V
Out[13]: Index(['Junior', 'Senior', 'Sophomore', 'Graduate'], dtype='object')
7 _! _* K8 B% D
% Z/ V" f, k2 |" w6 J# Bs.head()
+ o; g1 j$ o5 I/ `: `) aOut[14]:
# D: r( t4 n$ U# E1 @0 z0 NaN+ P' s0 T% N2 S. y2 L
1 NaN( \! J i' a: Z2 @: m* j' x
2 Senior
) T1 I( W5 {' Y: U. A, r+ Y- d3 Sophomore, ~0 {" B1 E" ]9 V2 D1 A; [* M
4 Sophomore
5 @; E$ a- S) x4 A- L/ aName: Grade, dtype: category
3 g Y h* j4 j: w, VCategories (4, object): ['Junior', 'Senior', 'Sophomore', 'Graduate']
7 i% F7 a" m2 ~: J1( ^+ P" `+ U" `( e+ k6 K+ m/ i+ z
2/ B7 R" V0 f3 Z" O+ |8 H+ p
3. |, S1 l3 j+ D0 n
4
D$ s/ S* X' X+ |$ X' I5
4 h6 \8 f' g& ~4 D* Y" j d; p6
7 I- _9 M- K6 \( n9 {. N7
! U& Q: I0 W! [( e' q2 Z+ z9 V5 G83 E" a8 G# @" {) p; n
9
4 x( m) b3 e/ A1 q+ ~10
3 s+ Z9 V7 K; |9 o: v2 {8 \11
$ l" D; N& \' O( P: \12
9 G( r% _$ k' J/ @1 K5 v4 d) B, r137 G( \- h' k/ C/ y! t4 t C
14# w1 l& ~6 B0 J) q* G0 }
set_categories:直接设置序列的新类别,原来的类别中如果存在元素不属于新类别,那么会被设置为缺失。相当于索引重设。
/ G$ W1 e7 T: N) Ps = s.cat.set_categories(['Sophomore','PhD']) # 新类别为大二学生和博士
* A3 Z5 D, B, J8 P* b0 qs.cat.categories7 l! O1 z: \" t+ |8 T
Out[16]: Index(['Sophomore', 'PhD'], dtype='object')
, W* Y0 n$ w5 e3 }8 @5 @2 d6 G( w
) j' q8 Z& W9 x0 K$ ^3 @3 os.head()
1 ]4 l- q l* |Out[17]: 7 Q' Y u7 ?1 S- h& B: j7 _
0 NaN
$ U. W7 _ v( C9 O% W6 _8 b, W1 NaN
# v* W7 `5 U2 O# _+ w$ C R' {2 NaN
& W8 X7 M9 A; O% F! p) | C3 Sophomore6 m# M2 l& E3 h' n5 K* e1 P8 y' z
4 Sophomore2 ?0 O( I* Y& m% T
Name: Grade, dtype: category9 g# }$ ~/ H8 ?0 Y1 b2 \
Categories (2, object): ['Sophomore', 'PhD']
' {+ n0 H4 G( N/ r$ Z, {2 t1
+ O" k4 |$ L2 [8 ~! g1 Q2 Y2
% R7 D! U( \" R8 X0 _( u3
6 @3 v& ?& c2 G3 e4
! Q! k ?5 D' q5 d* r, L7 b, n/ r$ \ T c
6+ K6 o' s. e1 f, l6 `1 n
7& a2 \6 ~7 V% s4 q" k7 W% I
8
# v @1 K- R& k0 `, ~9
+ p0 [ }3 `6 i' B N9 B2 w10. q# B- ~& t6 ~4 S1 C# u
11
2 \8 H1 ^; \( O12& P" x9 b9 Y' ^ q2 {" F! ?6 p8 e
13
: ^$ l9 F' ~3 r w) {1 Jremove_unused_categories:删除未出现在序列中的类别2 I! F) r7 K1 r
s = s.cat.remove_unused_categories() # 移除了未出现的博士生类别
: D6 L8 {" G+ @/ H+ n& w4 Y- N) V) is.cat.categories
/ s8 |. Z# ~% b
5 G# V+ B# k' M9 ]& t9 k% ]' O; JIndex(['Sophomore'], dtype='object')5 k/ @" P" L2 p7 J8 _% {
1
+ ?* K2 L, t2 q! X$ x5 `/ v2
* H. s( N" ~/ u3 t3 y: N0 y* H$ A0 L3
$ \, [/ _: H0 P% |8 E; C: G1 X4, I* a3 R$ D( d2 s$ h" u% s
rename_categories:修改序列的类别。注意,这个方法会对原序列的对应值也进行相应修改。例如,现在把Sophomore改成中文的本科二年级学生:
8 F0 c5 s: @" j$ W% ds = s.cat.rename_categories({'Sophomore':'本科二年级学生'})4 r" Q3 P1 H. s9 J3 h9 K; F; K
s.head()
( a0 a9 `: l2 m+ {* n. g b7 Q- f: C' @% c5 f2 }" v9 F- l- l7 @
0 NaN
+ W. V% L8 I4 y; e2 D7 K1 NaN' R b; f% W K
2 NaN
4 J( v& v3 a3 p! q7 A+ \6 Y9 I5 W3 本科二年级学生! g: {$ w: d+ ]6 J, w4 k, E' ~
4 本科二年级学生
1 G9 K3 j+ l# S s% [4 |' L: T9 ^% FName: Grade, dtype: category
7 a& m& h/ Z1 E- C. m8 KCategories (1, object): ['本科二年级学生']8 n4 N: }) K S A+ }6 D
1; O9 `) N+ ?7 \' C6 K$ I9 D
2
! b" s* ~: Y0 R( R4 w4 J3
' h3 n" C: H4 P" t4, r" B3 O% z1 S2 T- F- ]
51 z$ j, Z$ k; K2 [" Z- L6 W* N
6
) w. I$ g/ d7 r2 s; s! B/ Q2 d j$ |7
8 E& ]) d( Y, \, o8. [" _: B3 V+ B1 ^# C6 ]- H/ V. c
9. S: ?# n, j% B3 x
10- ~, e! l, |% v, N, Q) D4 b
9.2 有序分类
" M/ \: V) r% \9.2.1 序的建立, M4 a+ m8 L7 W$ g: I1 w
有序类别和无序类别可以通过as_unordered和reorder_categories互相转化。reorder_categories传入的参数必须是由当前序列的无序类别构成的列表,不能够新增或减少原先的类别,且必须指定参数ordered=True,否则方法无效。例如,对年级高低进行相对大小的类别划分,然后再恢复无序状态:
5 u- A3 X. Y9 X7 ^' ~' B- [
$ B; d! ^2 A* `, o/ C: fs = df.Grade.astype('category')2 c" e \! r8 b7 h) o W: D" A3 m
s = s.cat.reorder_categories(['Freshman', 'Sophomore',
% j+ `8 ?& p! t# P$ y 'Junior', 'Senior'],ordered=True), i5 Z$ Q# h. c5 Q) c1 T7 l
s.head()
0 m* f5 \! O5 [8 S' `8 j; H aOut[24]:
& y t |: { e0 Freshman6 y k3 I0 @+ O- Z( _; X
1 Freshman
( w# l# K7 T4 }! ~& M$ I2 Senior
% T+ b1 }( J* V) [3 Sophomore
0 O# e8 h1 E# l6 R$ M1 ^4 Sophomore
, i5 W2 f$ X1 d$ \% DName: Grade, dtype: category/ H+ C! x0 N. r- g" }5 k
Categories (4, object): ['Freshman' < 'Sophomore' < 'Junior' < 'Senior']- |4 M6 O+ m" m2 T3 s1 z, `
4 Q4 |7 `0 q+ [/ R0 ]. Z4 v0 Ts.cat.as_unordered().head()
( l" q' S+ V3 X6 K5 v. gOut[25]:
1 _6 j- V o' V- u" u0 Freshman
]% R$ D7 |2 Y' }6 A; v$ j1 Freshman
2 O! q9 n* b ]2 Senior U' J, c! b% a3 M, \$ D
3 Sophomore
& |1 Z0 o7 m9 I l% y; A4 Sophomore9 t4 D% j7 G# k" l0 c
Name: Grade, dtype: category
- K) [; k% A WCategories (4, object): ['Freshman', 'Sophomore', 'Junior', 'Senior']0 j1 v+ D1 p2 Z2 N/ b5 {
+ c2 ` V1 ~- s8 p% x; g5 G/ c1
7 p: F) a9 ]$ ~+ g2
0 ?& r( c0 G' \3 D: r3 v# w; |8 s: b( ~6 Z1 z- f% d
4# i I" O# Q% _' R; z2 [: g) ]
5
7 _5 s5 ?; D! o68 n f0 e7 M6 v' ~! z) l
75 ` N' P; E. f4 Q( T+ |
8
& A( |! ^$ {, e: v9
. G9 Q* g1 p0 D4 f! |$ @10
$ t) b4 _1 ~. ]0 |11
7 H4 e$ C, ?# \12* A+ [, s9 K0 P: J8 T, R7 i7 Y8 G8 j! ?
138 N" c9 g9 B4 Y4 A& A% x; f
14
. @4 ]5 m" p/ s3 ]15
! u. A# j Q8 p( C+ R8 c, ^16
/ ^( I1 Q2 [4 O0 h6 L17; X: {/ p, A1 q8 A1 z% C
18
4 b& k7 @$ Q3 e2 {8 p! x3 r19
7 `# i7 \1 }5 n' W209 Z! F# W9 e& [! L
210 W g W4 ]: q" M$ e* m
226 D( R$ u: t. ?. r# ]" J* S
如果不想指定ordered=True参数,那么可以先用s.cat.as_ordered()转化为有序类别,再利用reorder_categories进行具体的相对大小调整。3 u& D6 ?3 H O) I( d
! L8 x0 i. W1 m9.2.2 排序和比较$ K& k9 T3 L8 p. i9 F1 U- j
在第二章中,曾提到了字符串和数值类型序列的排序。前者按照字母顺序排序,后者按照数值大小排序。
) B6 w, f' q, R9 C: y6 n' W' M0 ? h) i4 p" ]
分类变量排序,只需把列的类型修改为category后,再赋予相应的大小关系,就能正常地使用sort_index和sort_values。例如,对年级进行排序:2 e5 q5 H- [; f; |0 J0 ?
( m2 {# ^& N) J! T4 u" E9 i& v5 m& ?* \df.Grade = df.Grade.astype('category')/ Q d' v* y8 @* S% @
df.Grade = df.Grade.cat.reorder_categories(['Freshman', 'Sophomore', 'Junior', 'Senior'],ordered=True)4 z' ]; a+ d$ j5 {7 n0 t* Q6 }
df.sort_values('Grade').head() # 值排序
0 a9 ?1 o% ^8 H$ |' L& gOut[28]: & Y/ m* j$ s+ q) f% V+ F/ u7 J
Grade Name Gender Height Weight: G' |9 L( n) q
0 Freshman Gaopeng Yang Female 158.9 46.0( a( y9 Y) r e0 ` n2 l+ Y/ V: ^
105 Freshman Qiang Shi Female 164.5 52.0
* t/ \3 ~) @" Y4 w; O96 Freshman Changmei Feng Female 163.8 56.0, L4 n2 n4 K1 N5 U( K4 G0 E
88 Freshman Xiaopeng Han Female 164.1 53.0
- q3 K1 |5 G* J81 Freshman Yanli Zhang Female 165.1 52.0
# D8 g$ [9 I8 T. C; Z+ O* W
: K1 t1 M/ G6 @6 O% {3 tdf.set_index('Grade').sort_index().head() # 索引排序
% s& ]; t! i8 v5 m) E# Q; dOut[29]: 9 r8 o% c: H" G3 L( c
Name Gender Height Weight
9 s' ?5 j2 ^5 F' N! zGrade
& _; u7 O# E3 k" B# L5 N X AFreshman Gaopeng Yang Female 158.9 46.0
* ~2 C+ F3 W: g, Q0 bFreshman Qiang Shi Female 164.5 52.0$ N! W0 [* `; D0 n
Freshman Changmei Feng Female 163.8 56.01 g' F, D6 K$ h! y3 c* T" `
Freshman Xiaopeng Han Female 164.1 53.0
8 F! ?8 C, }8 V5 UFreshman Yanli Zhang Female 165.1 52.0! _5 n) s1 [: ?. K# O$ A
$ U' K( t6 s; H" y1 x' x6 t# j18 w8 T* y% |, k; h
2
( V8 g$ N9 G/ h3
4 `1 {9 q2 A- H$ Y# g6 Y5 P4
& j0 v6 M/ G& j% q53 B# \6 L+ \; X
6
: E8 j/ }/ E4 l( I7+ x$ ?) f) j- i) [5 B/ S: x7 T' B# L
8
8 }" [7 n$ F4 ~& ]$ B9, O5 ^% v1 L# s
106 A8 H: W6 f+ J4 K" s
11
2 L/ v- A d! z9 z12
M+ O4 _/ \% @& l/ o$ d, n$ j133 m& P! f {9 z+ X9 E/ D D- j. [3 j
147 M4 E0 Z6 M1 ^% X( k
15
* @. ?' e8 u/ P$ G16
# P& D0 p- u' F( E& ^17
3 ]0 c) `0 O! j+ w" q6 K184 U. A. j7 H( y" K9 \% P
19+ W; {7 t- e# k$ s
20+ Q! L* D9 c$ }9 V
由于序的建立,因此就可以进行比较操作,方便后续索引操作。分类变量的比较操作分为两类:6 n# W; `, H6 S! \
' K; Z/ x8 k: i6 V5 k==或!=关系的比较,比较的对象可以是标量或者同长度的Series(或list)。(无序时也可以比较)
* ~. G: d3 W0 Z7 m$ L>,>=,<,<=四类大小关系的比较,比较的对象和第一种类似,但是所有参与比较的元素必须属于原序列的categories,同时要和原序列具有相同的索引。
! ?4 F+ j' x" p. w6 L7 ]5 U, Ares1 = df.Grade == 'Sophomore'! i7 m1 Y& u! z9 X; U
) O, x# d5 z1 Y- }' i* Y% o
res1.head()2 t( ?* T! \$ y' g7 c+ l
Out[31]:
( F0 A8 {$ z ^. @0 False
2 W. B1 n+ U* x1 c1 False
& r: @# t$ I! t# v4 Y2 False
/ o& A1 S$ P1 {' ]) q! ~; h. Q; e3 True& {4 H% h/ R5 L7 X4 K3 C; P
4 True
( v* }7 ~# m. C/ G( P/ ?/ uName: Grade, dtype: bool2 P S2 R5 y: V8 U) U$ c S' h& G
8 W) X2 c/ a; z) O+ Q3 l, tres2 = df.Grade == ['PhD']*df.shape[0]8 ?: Q! i. r# b. s' g
( A q7 Y' h5 n$ A a; J7 H) B
res2.head()
0 H/ e& l( W: C- e/ wOut[33]:
7 }" M# n4 C* T# P9 X! e) v) t0 False
: @* A# m4 ]5 I9 w) K1 False n( Y3 F8 h+ ]! o# ?6 Q
2 False
+ T( [5 N5 M( e. Z3 False( A) j' B' R) J. s
4 False
5 s" c D( L9 r& ?, h7 jName: Grade, dtype: bool
# N- a! c) ]4 H9 W" ^+ o! a
' r, S' @- O6 J9 P6 K A$ X, M, Zres3 = df.Grade <= 'Sophomore'6 g5 o n/ l' q6 c# J. \/ N
, c; l" o* m( I4 N6 I8 n) `* rres3.head()9 j' n) S* q3 b$ M% ^
Out[35]:
0 x! h0 ^1 c0 W6 \- h' \0 True
+ o, e8 H7 {; _0 j1 True4 Q' d+ N$ g6 l" s; R4 e
2 False" n+ w# H; e- i( ~+ [+ k8 e' m
3 True
# D/ |, H' Y( v) G0 u; _4 True
7 F' D+ G q, I! |% TName: Grade, dtype: bool2 V( I2 e" d9 q
- p G9 N2 R5 ]( c, U+ i7 X
# sample(frac=1)表示将序列随机打乱。打乱之后索引也是乱序的,直接比较会出错,必须重置索引。
' h. x9 r. B% G, P: dres4 = df.Grade <= df.Grade.sample(frac=1).reset_index(drop=True) ' D5 ]0 n1 T3 Q' |! ~7 |+ j, r
- v/ a) p& X8 J) ]/ Z- `
res4.head()7 w, ~! L: n* u$ \ P
Out[37]:
8 s8 a% t z+ ^0 r @0 True0 C6 G# T) W6 U0 c& d" W
1 True# R* C3 P( v! p
2 False, g+ }, j# k% A$ W
3 True
4 ?* s7 P ]1 F' c4 True
8 r% w( V" M J3 C; [Name: Grade, dtype: bool
, |, o6 u) h$ L6 t% ^- F8 r" j+ K2 u$ s) n
1
Q2 ]. n' P2 W+ f# o0 j7 f9 ^2
[. \" w( v0 q V/ E4 \& n3 g; O31 k, b9 w" y j( G+ N
4
. n' I/ P* o& h8 L3 b5- O- R" i% R+ N8 _& M
6
, O0 K! u* y1 l7 [/ x7
3 n. Z+ I" D/ p# P, \6 \1 V84 v" D5 ~* n) S4 |
9
& X, v; l' i" W10
) i) ~+ X# a2 l, s' H1 {11
! i2 ]5 L+ e. e12
* g! a4 K" k& l6 c131 y. i6 F* z9 w9 t; q% W
14& r/ f1 q3 }& U( o8 f# o
15
( k4 I$ S, V7 Q1 x16, g- @8 N l9 C9 K* W' [, C
17) B. |8 n J! m+ q, q5 k" m* A, _& v
18
' {6 F) n @3 C19
5 U6 N5 Y& H* z: j; Z& U$ M20
, ^, }$ D- m4 p* f213 Q0 `2 {& Q" y6 K
22* q0 B+ S1 J& A0 t
23; C; M t9 a4 B" i; N
24
; z4 r7 u( R1 }" Y* }" l0 r6 p* G251 l5 M& V3 W, I. F9 C+ `
26 m) N# U9 Z/ y+ q' D
273 C) ?* w6 t& z4 z0 s; X
28
0 Q {! O* H1 l- [) z29 ~" G' G6 v$ ^
30( r6 E+ x6 d* \" _6 `/ y
31
6 R H/ v; }+ a4 i32, r) P8 V% ^6 ]* h( v6 S& l
33. i! |! J- e) B8 E
34
; ]0 `2 Z* y9 ?; y; b35* _, O, B* I% a; J* @ p3 G
36, b X" i; c/ F3 X
37# y' z- G1 y, j" @* Z0 O- @
38+ P! P' F1 S |5 u
39$ Y, I3 ?! p b% E: z# n* m& o* |# ]
40
; R; q; b" a& D0 [- N' h" A t: v41
; R/ N! s2 Z8 u. B0 c5 o/ @1 y, Y( A! C42) F0 q j# w6 |% S/ X2 A/ e) U8 t8 ~
43
, g9 G" i% l; P m7 R0 N. q' \44
/ i. A Z. V! f1 v* g9.3 区间类别
! U3 o' e; d% K8 c" z! H9.3.1 利用cut和qcut进行区间构造
+ F' Y, F' K9 d0 @7 t$ q0 u 区间是一种特殊的类别,在实际数据分析中,区间序列往往是通过cut和qcut方法进行构造的,这两个函数能够把原序列的数值特征进行装箱,即用区间位置来代替原来的具体数值。* R( A) n* n( K9 `1 ^6 h
. t; B) P) D4 Z& v9 `( Q) ?' xcut函数常用参数有:
) |9 ? {& `. B3 Q% }bins:最重要的参数。; N! w+ \! D7 D/ V0 H4 ` E
如果传入整数n,则表示把整个传入数组按照最大和最小值等间距地分为n段。默认right=True,即区间是左开右闭,需要在调整时把最小值包含进去。(在pandas中的解决方案是在值最小的区间左端点再减去0.001*(max-min)。)
% n Q0 X/ m" } ~; ~也可以传入列表,表示按指定区间分割点分割。
' p) ?8 S) M; O; a6 F. C3 R4 [ 如果对序列[1,2]划分为2个箱子时,第一个箱子的范围(0.999,1.5],第二个箱子的范围是(1.5,2]。1 x" T1 L9 `" Q4 x) Q
如果需要指定区间为左闭右开,需要把right参数设置为False,相应的区间调整方法是在值最大的区间右端点再加上0.001*(max-min)。( q* `' O5 l7 E2 X; j
5 Y0 E$ B& r. t7 [ ys = pd.Series([1,2])
7 @ T/ _6 w- K0 Z( z# bin传入整数5 l6 [( b. s( Q0 H/ O
) w W2 }& ^ S
pd.cut(s, bins=2)
& @; n s0 a) g0 S$ t8 V5 dOut[39]:
* H/ k! E* @7 T$ T- T0 (0.999, 1.5]- E" X& L: J7 Y0 w
1 (1.5, 2.0]7 p( n9 E, ~, R' R* g; w
dtype: category
' a( L) T0 N% Z4 K6 GCategories (2, interval[float64]): [(0.999, 1.5] < (1.5, 2.0]]
& n k9 u% i/ l! W
, n3 \# c) s3 s/ @pd.cut(s, bins=2, right=False)
# P: }3 j) N9 b% x2 JOut[40]:
: w: S# y1 C0 u! E+ f/ P7 N0 [1.0, 1.5)
, w/ c. h D8 e$ y( I" t2 B1 [1.5, 2.001), I- {4 D, o0 q( ?% v/ R" B
dtype: category
9 |# d: Z7 d O4 v$ JCategories (2, interval[float64]): [[1.0, 1.5) < [1.5, 2.001)]4 ] G0 e6 H2 P
3 v( D1 g, Q5 m( `; \$ W5 O" w! }
' i0 ^# W6 B8 |) t2 |! ]& r
# bin传入分割点列表(使用`np.infty`可以表示无穷大):
( ^+ P6 ]# `: i* `pd.cut(s, bins=[-np.infty, 1.2, 1.8, 2.2, np.infty])
0 w7 b4 I! X# |: [" q) l6 p% G/ YOut[41]: ! ~1 O" K/ o0 I- w1 a! i3 Y6 X. C
0 (-inf, 1.2]; e+ K9 F8 h" r: S: q- G
1 (1.8, 2.2]4 l" f/ n) f5 t( r5 {$ g8 j
dtype: category
, O9 P9 \6 F& d, G OCategories (4, interval[float64]): [(-inf, 1.2] < (1.2, 1.8] < (1.8, 2.2] < (2.2, inf]]7 W5 F! m) u) B w- k
E- C/ X7 c- y+ H3 \# t: G3 G
1& q) u( X5 S3 \
2, C( f: a$ G( s/ v
3* k* b) q a: g l l$ m& |* e& g
4( k, |+ f( W4 n$ I+ Y/ T4 }
5* ~, z* @* E3 W' S
67 z5 N0 o' |& B& g$ z3 e/ }& h
7
. S( w1 `( d4 F: |* Z) J8
* Z, C' ^7 f0 X5 J1 A9 Q9
7 x9 V, T$ \: m$ I$ y2 r10
$ J" ~! p' S, \0 O5 V11
$ E6 I) u' R ~0 s12
- E* B+ d: r9 F! k6 K% J# D& R135 B: Z4 Y# ^7 o" n" b( A
14% L/ r( d' }3 ~0 V1 u3 \
150 E' c8 K% I' l
16
' J( ?0 \8 k) w17
5 Q1 X8 l; Q2 h- V18" n+ M4 \1 Z8 V
19
7 ^% T! ?0 V$ [ E5 L20: D) W$ Q0 \, c9 Y" Z1 E
218 G4 ~4 k4 C0 h# {) k3 ]6 H
22
c3 i/ d. e$ ~: N' s9 T' N23
. C' U# f3 |0 e$ |$ V8 e) J4 m( j5 p24; s# D9 N5 w+ |2 V
25% X& m. k; `5 l9 a, y, v
labels:区间的名字1 G, Y6 W1 t* ]7 T3 h( }
retbins:是否返回分割点(默认不返回)5 x1 r1 Z M z- G+ B9 Q
默认retbins=Flase时,返回每个元素所属区间的列表
! c# V% W/ F/ d3 u4 Eretbins=True时,返回的是元组,两个元素分别是元素所属区间和分割点。所属区间可再次用索引取值
% v% {* j. ]0 ?8 y; g/ |0 N' l* b! O' y# [/ U) d! |
s = df.Weight' R4 Q2 g0 K2 W4 o" I% w
res = pd.cut(s, bins=3, labels=['small', 'mid','big'],retbins=True)" M8 \8 ?* c" V! U, e* j( ?$ W
res[0][:2]
h) C" U0 r8 t& q7 E2 h% Z: C0 N8 E0 o0 Y, F+ L
Out[44]:
" g" F; W8 y0 ~/ l; z0 small# a: r( t2 l2 E$ b0 e9 G( V! j
1 big. K! Z) G! t' H9 h, ~/ ^
dtype: category
! }' B* [4 N) O: HCategories (2, object): ['small' < 'big']
- ]: s' d3 h: w
( j) _7 a( P E vres[1] # 该元素为返回的分割点
% N) {4 W! u) {7 \Out[45]: array([0.999, 1.5 , 2. ])
9 h- u- u( N& u! p6 p8 r6 X6 m8 W9 b1% M, L8 f/ N3 z+ ~! A
2
9 r0 R( t# Q$ f& \) `2 ?. _) v36 w4 q+ {- ?0 {4 L, {( f
4
$ i4 K+ r5 r; b; L51 J" _ l% S, L: f. [& @
6& g6 y& B3 v& Z9 n# M" h1 j
77 E; s8 u o# q0 }
84 F g- e* q3 Q+ I2 P" t
92 f. X$ d; V- z4 `2 q9 k$ c" G
107 w, P" |3 V1 A
11
! k6 c7 E5 p& [! p& s12
$ F7 m9 f- d- B0 a& Z5 nqcut函数。其用法cut几乎没有差别,只是把bins参数变成q参数(quantile)。9 x$ `7 A! H* B6 Q" M) i5 |0 {4 h5 e
q为整数n时,指按照n等分位数把数据分箱% a' Z1 L3 p( X( }; G9 l' x x
q为浮点列表时,表示相应的分位数分割点。
! P. }3 U9 l/ y, u s+ f, D7 hs = df.Weight
8 h: _& T+ }' b0 t; V) N
- B' B* K/ I% d, b/ Zpd.qcut(s, q=3).head()
8 ?/ _/ k+ z; |; [% _9 ROut[47]:
2 W7 a0 \4 ~6 o7 y9 F2 k0 (33.999, 48.0]
; f, h# ]8 J. j% N1 i1 (55.0, 89.0] J y2 [) h" f4 W. _, ]
2 (55.0, 89.0]$ ]8 ^5 P; @' `- a9 [7 I" Y
3 (33.999, 48.0]
: C! _3 o/ O! I2 V/ j+ ~4 (55.0, 89.0]
, Z2 c6 v7 R% DName: Weight, dtype: category& s' F, j, t! z8 o. S+ J& d6 M3 x
Categories (3, interval[float64]): [(33.999, 48.0] < (48.0, 55.0] < (55.0, 89.0]]4 x c4 U% _. {6 P/ p
+ U; V/ H1 W! b( v& _$ U6 I3 g) I
pd.qcut(s, q=[0,0.2,0.8,1]).head()* C* N" @: p+ l9 q' g Q! q% _ P9 @
Out[48]:
! q! W$ V V' r! e) _. [; m0 (44.0, 69.4]$ R1 T3 g' G6 y% m9 x7 Y' h
1 (69.4, 89.0]2 s8 I; U2 s& E! V
2 (69.4, 89.0]' T- r6 h t: C+ Y$ X
3 (33.999, 44.0]3 z$ ~& E% {1 K/ ]2 _
4 (69.4, 89.0]2 d4 Q) c# `. Y* {+ {: p1 S# ?
Name: Weight, dtype: category
! u2 e% {+ c: N# r; qCategories (3, interval[float64]): [(33.999, 44.0] < (44.0, 69.4] < (69.4, 89.0]]. }0 e3 ^$ U- j; [0 D8 Z" m
' i5 G+ z6 u7 z! M3 b& O2 B16 s# ?4 W& o/ i4 ^! v
2: Y: c6 f; }- X7 A1 S
33 |- r! R* H5 s8 n, q$ i4 g
4; a o& ?" z+ J- ]6 _
5; T' j& ~ F0 [/ f
6
: Q4 e( M* C7 G7 p. W c j* M" n7; H9 }( @0 v- C) p
8* c+ W/ N0 r: V' ^! ^
90 p9 i' Z/ x$ q0 M( n7 U! I" O
10, [1 p. q' U5 y. }
11
7 R* j7 I2 W$ F8 M, q12
$ r$ ^: C% y( i1 g D5 w7 X13: b) J1 X8 Y0 m- M
14/ S7 C( e# M4 H- r
15
* i3 T5 u1 W* z# a$ H16- h9 D4 M9 M8 ?8 h# _- J
17
; n: d, j# @# E! z' ^18
" a3 H9 n0 U5 P" D0 s) c19
1 ?* O) V0 p; r9 `20
; F7 a1 E( @% M) J$ F( D% U/ _21
9 @6 b2 L) h" a$ A9.3.2 一般区间的构造8 Q5 c- w; y. @# X& S
pandas的单个区间用Interval表示,对于某一个具体的区间而言,其具备三个要素,即左端点、右端点和端点的开闭状态。2 G- E$ B7 `) z" H2 l+ \* Y4 ^' w
1 I6 V4 Q' e7 L* a开闭状态:包含四种,即right(左开右闭), left(左闭右开), both(两边都闭), neither(两边都开)。, ~6 a# |) T; n
my_interval = pd.Interval(0, 1, 'right')
- [1 x" s. D. |
+ m/ Q. L' J) B- }6 Hmy_interval
( o8 X' a4 ~* q7 X( T/ zOut[50]: Interval(0, 1, closed='right')/ l2 ~5 Y" w; j. A
1
& v! }) l7 T5 F3 x) S9 d0 _2 @, n2/ l, b( O! H( Y6 C8 T9 e) Y C& ^* X+ c
3- i; m2 m5 o: e, j% g; A
4
9 O8 u9 @4 S+ b5 a区间属性:包含left,mid,right,length,closed,,分别表示左中右端点、长度和开闭状态。1 Z) r' W* m7 r, ^$ w* H( f6 Y
使用in可以判断元素是否属于区间! m8 F# S# G/ ]' l/ k3 A
用overlaps可以判断两个区间是否有交集:
% c! c) E5 ?2 A$ ]% u3 x( j4 O0.5 in my_interval7 o5 |& z) f# M- u% R) \, S
- ^- p. G5 Q& W" W" r LTrue: C+ O& k5 U: m9 U1 T! M0 I
1
+ [) S! Q) h2 ]5 ]: M' d7 G20 r: L7 y% o4 ~& C5 q- `# u7 J. P
3
8 }* U. ~2 U$ z& |my_interval_2 = pd.Interval(0.5, 1.5, 'left')
8 ?, e0 I$ \2 k- x' xmy_interval.overlaps(my_interval_2)
6 {6 [# `5 F$ k3 f4 v$ {' p6 ]/ b
True' s* r* w( m2 c) o6 t9 _
1
T+ S9 L/ F F7 K29 i1 z+ `% d% B
3& s6 O* s' o, r6 ]
4
8 y7 G3 _7 d; h- `( x& s pd.IntervalIndex对象有四类方法生成,分别是from_breaks, from_arrays, from_tuples, interval_range,它们分别应用于不同的情况:$ g" T" p4 K! `
- r! _9 P4 m6 L* H
from_breaks:类似于cut或qcut函数,只不过后两个是通过计算得到的分割点,而前者是直接传入自定义的分割点:( o t A& a) ^+ R' b
pd.IntervalIndex.from_breaks([1,3,6,10], closed='both')
J- n6 t- a/ K! Q7 ^* D, i, O: _. A7 ^0 X
IntervalIndex([[1, 3], [3, 6], [6, 10]],, ~7 _# j4 q" v7 h" w' E
closed='both',! [+ P' H3 v5 H, v
dtype='interval[int64]')
; [ K$ _' V; G1
1 i; q" F( l" y6 l8 L20 F! B* P7 z# Q4 p2 H& S
3$ h4 C" W# C% W% m; y" {
44 j# I+ ]; e( M" A0 i' W
5" x& {5 r1 [+ j) |
from_arrays:分别传入左端点和右端点的列表,适用于有交集并且知道起点和终点的情况:: S3 A+ ^4 ^: u6 q3 a
pd.IntervalIndex.from_arrays(left = [1,3,6,10], right = [5,4,9,11], closed = 'neither')
9 V6 G. r" P1 U' u0 _
) `! o7 K- Y6 L& PIntervalIndex([(1, 5), (3, 4), (6, 9), (10, 11)],
: R/ |+ j, f, r" p3 T0 b closed='neither',
6 w1 ~; n0 s: A+ B5 i+ [ dtype='interval[int64]')
/ B) y* D t7 E% H1
2 |/ d6 z9 ?* N& l" n% k2
" x- `' [5 O$ j, j1 A, L8 s4 N7 y M3
- g N, _7 N) V; i& M4
5 H3 D. J3 H6 B$ t( J9 u4 R5
! [* o" [6 j) ^ T- Yfrom_tuples:传入起点和终点元组构成的列表:7 @* W7 D* c/ X" z
pd.IntervalIndex.from_tuples([(1,5),(3,4),(6,9),(10,11)], closed='neither')) [/ ~$ t2 {* u
1 D3 x' Q; x! ]1 J$ nIntervalIndex([(1, 5), (3, 4), (6, 9), (10, 11)],
5 q8 t. Q% H- W) M, r3 L8 I4 O closed='neither',: l2 M& Z% |/ p3 l9 a5 }) z
dtype='interval[int64]'), Z' j( p, w* s, `
1
6 b/ p6 M- \& X% P$ f+ ~2
* t v. h/ W( n& g$ Q' }3( p1 T1 X7 ?5 Q4 s" p
4
. g K- r- }& b5( f* m1 p0 D5 r& ~" y3 i. [% w7 m
interval_range:生成等差区间。其参数有四个:start, end, periods, freq。分别表示等差区间的起点、终点、区间个数和区间长度。其中三个量确定的情况下,剩下一个量就确定了,从而就能构造出相应的区间:
' Y3 \! m/ E' o( |7 U2 v$ xpd.interval_range(start=1,end=5,periods=8) # 启起点终点和区间个数
7 D/ w8 B# |1 c2 m* e7 B5 n. AOut[57]: + n2 [3 V8 F8 X
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]],
( d% _5 @) |$ F2 Y5 j2 B& i closed='right',( r6 D3 x: L: s# H
dtype='interval[float64]')
/ ?& G! |& i6 j
2 U( y: M- w2 S0 o* w* `7 Qpd.interval_range(end=5,periods=8,freq=0.5) # 启起点终点和区间长度4 P2 @- m2 S4 U) ?
Out[58]:
5 z2 o$ `3 Q# L* W4 E3 oIntervalIndex([(1.0, 1.5], (1.5, 2.0], (2.0, 2.5], (2.5, 3.0], (3.0, 3.5], (3.5, 4.0], (4.0, 4.5], (4.5, 5.0]],5 y3 |* R1 T; l0 A
closed='right',2 m7 z$ s; a+ l% s. O: ~' a
dtype='interval[float64]')* j. c$ n3 S+ o- O
1- v) ~2 o3 ~8 ~, U4 m- k
2
( ~6 _, [" c; W: P& P$ X4 \7 e3! l6 R ]( {# Q' x! {8 a0 i. T
4
1 P. t, D% ?4 A5 Z57 P: l0 p% ^' i
64 J6 k( L( z4 ^1 d
7; @8 J- N+ c, s' b5 |, E
8
# N- p7 a' n6 W( u/ M: p- B) q9
* [, S. E8 v1 J7 v10
+ [8 |$ d* _( ]9 l11# `7 H! j# Y5 R: v7 d+ H
【练一练】) s- y1 k% {2 F0 `0 t
无论是interval_range还是下一章时间序列中的date_range都是给定了等差序列中四要素中的三个,从而确定整个序列。请回顾等差数列中的首项、末项、项数和公差的联系,写出interval_range中四个参数之间的恒等关系。* ?; O6 N3 b* N1 H9 D/ @
/ G; |6 B2 d3 V% W/ F& ` 除此之外,如果直接使用pd.IntervalIndex([...], closed=...),把Interval类型的列表组成传入其中转为区间索引,那么所有的区间会被强制转为指定的closed类型,因为pd.IntervalIndex只允许存放同一种开闭区间的Interval对象。
0 j7 l9 n2 ^' ?) w$ [( M7 ?$ ^8 N+ J+ M/ N
my_interval
( z# B. X) G2 o9 S& T4 q" ZOut[59]: Interval(0, 1, closed='right')
6 L; h. b9 c! W+ X- I/ j, i* Z8 F* Z8 G4 ?
my_interval_2% M' }. M$ h! g3 N
Out[60]: Interval(0.5, 1.5, closed='left')' O* u: Y$ M% f1 v1 T. p
: W, |( m' I6 }" Vpd.IntervalIndex([my_interval, my_interval_2], closed='left')
; ~' M" H0 y" g" r* NOut[61]: ' s+ f* t6 r; t
IntervalIndex([[0.0, 1.0), [0.5, 1.5)],
7 C6 h y# @. [) O' W/ ? closed='left',
" W% J0 X' Q9 r" |8 H9 c% Z; f dtype='interval[float64]')
O% B) V g2 W' [; D" R, X15 `7 X9 c0 A; _8 f7 f' U" t( f
2) U+ y$ f5 b; A& O2 C+ V$ B
31 P9 B9 [0 f" n4 D
4
# p% l9 M6 o0 Z( [% `* h3 R9 h5
7 i s; [! k( k; ]- j+ X, H d6( ]; @6 X7 v$ ]4 W" [
7
6 R( e$ d: }" J" X8. m" H: r! r, h! ~% B, @' u
9
9 t3 h1 X1 ?: g10
- F. a# H# J( r3 m% y6 g11/ O* _% y& {/ I5 Z5 \
9.3.3 区间的属性与方法8 u4 R% S/ u1 [7 e. J1 x/ r
IntervalIndex上也定义了一些有用的属性和方法。同时,如果想要具体利用cut或者qcut的结果进行分析,那么需要先将其转为该种索引类型:
! {8 s- S# T0 O7 t3 L0 C" Q; y4 U
s=df.Weight
( ^8 ]. P) J# d6 w+ c9 ?id_interval = pd.IntervalIndex(pd.cut(s, 3)) # 返回的是每个元素所属区间,用具体数值(x,y]表示
) @: ]; \% X- S& U. y, Eid_interval[:3]
% y4 ~3 L \, O2 p1 ]1 n, u# a% I2 ^2 p1 ^' ]
IntervalIndex([(33.945, 52.333], (52.333, 70.667], (70.667, 89.0]],
7 \8 A/ b6 a4 }8 C. R closed='right',
7 ~* Q% Z0 ~8 J1 W3 T name='Weight',4 @+ E6 n9 X9 r' u' R
dtype='interval[float64]')
1 c9 m$ [% T/ u5 X9 {$ u9 T1
. [+ L4 i5 h6 O8 `- X+ v5 C2
) q! S& @9 g" S3* A' m0 c: w5 G: _ f% h- n% t
4
/ O4 r. ^1 x2 a% _5 H5
9 a3 I& D3 L' ]; l: u5 E' n69 T1 r `" V1 L: K- d g, u: M
7
; a& d+ C# a" S1 T8; b2 d$ [# b9 I
与单个Interval类型相似,IntervalIndex有若干常用属性:left, right, mid, length,分别表示左右端点、两 点均值和区间长度。
2 _$ k6 p. \0 j, x0 X$ a- q6 Vid_demo = id_interval[:5] # 选出前5个展示0 K" p* M' ~% Y8 z6 w
- l( w" p: n0 d7 g" T' C% N5 wid_demo
( e2 ~( F3 C, p8 l- COut[64]:
/ F# R; Z" w1 ]& g* TIntervalIndex([(33.945, 52.333], (52.333, 70.667], (70.667, 89.0], (33.945, 52.333], (70.667, 89.0]],0 S2 S1 d9 ~8 A$ l, j# }& q. C! s4 z
closed='right',
5 A& m8 x% v. ^% x+ J: l, k name='Weight',
7 y' u2 S5 L1 h! n5 r6 z dtype='interval[float64]')& W9 I- c& P- c! y+ p: z! Z( P; U
) i' U# Y4 V5 ]6 L; v$ pid_demo.left # 获取这五个区间的左端点4 b* {0 O" `- r- R
Out[65]: Float64Index([33.945, 52.333, 70.667, 33.945, 70.667], dtype='float64')
0 }7 R. d4 L5 v$ h7 Q4 O: [' s7 j; }) r, Q- W& W
id_demo.right # 获取这五个区间的右端点" h, s6 }% m$ }
Out[66]: Float64Index([52.333, 70.667, 89.0, 52.333, 89.0], dtype='float64')
{% @% V4 z4 a9 S! J6 U& D2 u B" g: C, Y9 e$ k! I
id_demo.mid# |# q, C6 I/ d& p) g
Out[67]: Float64Index([43.138999999999996, 61.5, 79.8335, 43.138999999999996, 79.8335], dtype='float64') [4 H0 W. r n$ g7 d5 h
# L8 W$ V, C! N L/ \ o
id_demo.length I6 O6 l# `& a7 ^
Out[68]:
3 o- \9 ~0 B2 C& P+ Z9 {& {0 o+ nFloat64Index([18.387999999999998, 18.334000000000003, 18.333,* Q- E) u& W. c! p- ?8 }& D) _8 s
18.387999999999998, 18.333],5 p; m$ a4 S/ k) Y; G' Q
dtype='float64')7 T0 F% A9 V* Y/ x( ?
$ U' i8 B* l7 _7 ?# J* i- C/ d7 l9 v18 B8 }& Q7 H. O) o
2
3 m. {8 o5 y' \, P6 {+ v3
$ [. ^8 I q2 {2 F43 N! q" U9 }; p ?" S
58 @# I2 L, [% T% A5 y
6
+ |( S0 |& C- U7
. ]# w H: h% B% M$ ]6 e80 i8 e, E2 X! Z. G4 M* m% t
95 ^8 k$ M7 s4 j, N# ^
10
8 N& L. e3 J( D! `# T11
' T( [' b3 W* {' c2 L6 c+ h2 y12
1 n2 v( a4 ]9 a. x13; p9 H' O/ u" N. J0 Z
14+ T! M8 g' m+ c% T
15
) {) @3 \. s- V7 E5 T0 _16. j/ }8 v/ _- Y1 u0 l; u" [5 \2 t I
17
2 W% A" l, [) j' I( g18% |1 z# P* _ n; w
19. x: k, o% X' v; c- _& w
20
. D/ d5 h C$ g$ R21
; Z+ T, [2 g3 m( y; V4 l& k7 i% m22
+ x: E" `( C! n# ?23
+ L) }& a; P$ t5 rIntervalIndex还有两个常用方法:
- \; n( _3 J6 ?6 ?; ccontains:逐个判断每个区间是否包含某元素! e0 h( p8 e5 }5 e# D7 W# H. q
overlaps:是否和一个pd.Interval对象有交集。7 V4 ]) Z5 X9 }0 h" `( ?+ k
id_demo.contains(50)- [$ I; q) D2 r/ m% R% Z' @6 T
Out[69]: array([ True, False, False, True, False])/ m- W! ]1 @+ J1 T6 f
% O2 o+ A2 l# E& b) `' |id_demo.overlaps(pd.Interval(40,60))
3 Q7 T. z0 t. a) ~7 }Out[70]: array([ True, True, False, True, False])
2 i8 y2 A+ ^& N# v! z+ l4 e% ^1) S- V P: T( U& o& ?' b
2* e! |1 {! ~+ [( z, {
3
" Y! d& u. t' M% o, ]+ T8 x7 m6 R40 B4 L( H2 v; J
53 j: U; F4 C/ C9 V( _& \' p
9.4 练习& {0 e6 W% r9 D1 p6 m
Ex1: 统计未出现的类别
/ m( F9 ?7 [% d- _) N8 L 在第五章中介绍了crosstab函数,在默认参数下它能够对两个列的组合出现的频数进行统计汇总:2 j! P" k6 @, u. q
+ B& i. G% E8 `4 M+ {df = pd.DataFrame({'A':['a','b','c','a'], 'B':['cat','cat','dog','cat']})
' ^- _& V F* T" A- z1 L$ _. \pd.crosstab(df.A, df.B): ^* c. C. Q% \
" C# o+ Q+ |4 P( K# z# N$ Q
Out[72]:
1 X% r# T0 H3 d6 pB cat dog
( Q! Z$ P2 r, D1 `' qA 9 c) J7 Q3 H" F
a 2 0) F6 ^; F6 c$ f
b 1 0
! d k) E3 E0 }: z( F+ Hc 0 1
* b0 ?2 x* L4 j c7 F14 D3 V# s# r+ T' k3 _5 j5 q" e
2% Q" q& J2 ?+ a$ M
3# n/ i$ R7 @& `
48 X3 T8 r: N8 H8 w! K% i
56 w2 k" Q. ?1 u9 p, f- }/ n
6# B4 h8 J- w9 I( C/ i3 _1 c/ E
7
+ o2 j4 S8 `; s$ g8
O3 U' h& j0 K }& V: G: Q9- G5 j' l# i' N7 C: X: S+ |
但事实上有些列存储的是分类变量,列中并不一定包含所有的类别,此时如果想要对这些未出现的类别在crosstab结果中也进行汇总,则可以指定dropna参数为False:8 n; G- y+ ^: b$ u' X/ m6 k
1 H: ^. h/ g8 V& p# J6 A4 ^df.B = df.B.astype('category').cat.add_categories('sheep')( t6 ~$ M+ c( j% x# j( n
pd.crosstab(df.A, df.B, dropna=False); T0 ^2 u, W0 m+ ?- h/ U% T
. y: m( K' T! V: r
Out[74]: 5 s: [/ I! F# _4 N* E1 N
B cat dog sheep8 L& Q: j+ M+ X6 D5 G
A , ` v% o: h7 R
a 2 0 06 ~7 @) w' `; R2 x c# |; a! |5 x
b 1 0 08 _* n4 @ u, |9 j: c
c 0 1 0
- o6 P/ w: S! j+ X1
4 N7 ?1 n# V9 D' ?4 [) r; | {2
0 H! @' e1 W7 L1 ~' Q3
8 A+ {4 b% X8 M/ R+ Q3 B# X4
9 [+ r- x$ ~5 ~* {7 f1 B5
1 @, F/ p# [1 e: q r- \4 k+ H6
# y- d. }) d3 v) {7
) d5 _; k2 ]: g8
% u8 n( O. x4 s) c9 a8 R9
5 m& `! ~$ F; s请实现一个带有dropna参数的my_crosstab函数来完成上面的功能。+ p5 p6 S0 }; k4 N' g$ ^6 [
/ w6 g B2 H( `6 A' l) I) Q, ~
Ex2: 钻石数据集, ]8 }4 ^4 s7 W e3 h# A8 `& j- g
现有一份关于钻石的数据集,其中carat, cut, clarity, price分别表示克拉重量、切割质量、纯净度和价格,样例如下:; s6 l3 | H: l' ]$ S5 O
E8 ^1 ]5 c# L7 w" `! `: S8 D7 e+ _
df = pd.read_csv('../data/diamonds.csv') " e: Z7 W2 [2 y2 L* f3 D# ]# [
df.head(3)
. x t X& Z) x$ u
, x" ^/ {9 a2 {Out[76]: + }2 u& f. u- X9 u1 ~
carat cut clarity price
1 q" a M! F# O0 0.23 Ideal SI2 3264 q6 `9 G: `% b! \2 F2 ]2 N+ W: R
1 0.21 Premium SI1 3268 P1 W( G7 y2 L- Y6 s8 w
2 0.23 Good VS1 3271 @, C# m# {3 {2 `
1
0 X9 j" B9 G. c. Z$ o( z/ L" _$ [2
0 w1 F8 s0 X3 I2 [4 Z- E3& A- i7 R) F4 P4 I$ ]
4/ y6 u% R/ L2 r w" L% h7 h
5
' a3 a% P- v' q* R- \ A4 ^6
6 H% q6 m& T( r5 O# ]77 d/ h( n+ j$ [2 A1 o
8
; X" Z6 X) e6 V9 V: d' S( ~分别对df.cut在object类型和category类型下使用nunique函数,并比较它们的性能。
$ M; h7 p4 r' g3 W: \! k钻石的切割质量可以分为五个等级,由次到好分别是Fair, Good, Very Good, Premium, Ideal,纯净度有八个等级,由次到好分别是I1, SI2, SI1, VS2, VS1, VVS2, VVS1, IF,请对切割质量按照由好到次的顺序排序,相同切割质量的钻石,按照纯净度进行由次到好的排序。
0 p0 c8 }$ m4 I4 ^ L8 \: h分别采用两种不同的方法,把cut, clarity这两列按照由好到次的顺序,映射到从0到n-1的整数,其中n表示类别的个数。( p) y- ^7 ~! L% ~, h( b) i n: l
对每克拉的价格分别按照分位数(q=[0.2, 0.4, 0.6, 0.8])与[1000, 3500, 5500, 18000]割点进行分箱得到五个类别Very Low, Low, Mid, High, Very High,并把按这两种分箱方法得到的category序列依次添加到原表中。( \, i" W+ F" {, [; y; ^
第4问中按照整数分箱得到的序列中,是否出现了所有的类别?如果存在没有出现的类别请把该类别删除。4 J V3 J, d& I6 h2 B
对第4问中按照分位数分箱得到的序列,求每个样本对应所在区间的左右端点值和长度。( I C2 W7 T: I. H
先看看数据结构:1 j/ Q! L( `9 P+ V
( o! i$ n% y. a* T1 I1 @* C q" |
df.info()
9 W& O6 [0 W3 P R7 s' b4 zData columns (total 4 columns):
& @! G' d8 ^( F2 V) U # Column Non-Null Count Dtype
4 B8 v/ r. K) q. v3 D0 \--- ------ -------------- ----- 6 u& Z+ R, S* i5 p- u9 o
0 carat 53940 non-null float64& B7 I; b5 Q, i4 K8 K2 j* m
1 cut 53940 non-null object ' h# B, }' H3 }+ E& n
2 clarity 53940 non-null object 2 M& o7 G" F: z7 P7 {+ d# B" n
3 price 53940 non-null int64
, q4 m- ~4 S _% pdtypes: float64(1), int64(1), object(2)
7 |8 u1 p7 B" i- F7 ~ A& T9 T* r- P1. O3 `& X: F: _7 L. m
2
" g5 q9 I$ G X, G1 r- ~( G3/ {3 [- J$ U' h& t6 G" `
4
0 S+ C0 ~: q& Y% H7 y5
. I( ?5 E' T$ R6
1 c8 v2 Y8 l( p7
+ g' M# O/ ^- C; O7 L* ^8
- H7 [8 ]. ~$ `6 K* h8 P93 p& a3 l) ~7 D# Q! _
比较两种操作的性能. P' d7 s4 M* K
%time df.cut.unique()7 k. k {8 b# w
# X$ |4 ~! @* K
Wall time: 5.98 ms
, p, D9 L- S" K6 B. Q4 [6 Rarray(['Ideal', 'Premium', 'Good', 'Very Good', 'Fair'], dtype=object)
% W* ]0 U6 C8 T1 ^% n. D! u9 v4 ~7 E$ u1
: q- } b6 t& `+ k" [; v2# z/ d2 B- t" _* l$ u
3. U9 {/ t: I# A' R* n" A9 E
4
9 \6 ?" j+ n7 c0 M! ?4 x* s%time df.cut.astype('category').unique()4 I$ l" T$ R' q
. v% D. m9 E1 B6 T3 u7 MWall time: 8.01 ms # 转换类型加统计类别,一共8ms0 Z& \8 V3 {3 Q- X$ G
['Ideal', 'Premium', 'Good', 'Very Good', 'Fair']/ q; x) X+ ~6 W2 a
Categories (5, object): ['Ideal', 'Premium', 'Good', 'Very Good', 'Fair']1 m: d9 w8 ^) [6 W- P7 @" T
1
- K, j+ C* h+ A& [2; r. d2 x4 Z+ R+ J
3
# h; y3 r' Z. h& X; i4
1 m' a7 I) p( D I/ ?: b9 ]5
% K6 q( c5 }3 V! s9 X7 v6 q5 i ydf.cut=df.cut.astype('category')
7 Y5 l: ?4 s8 H%time df.cut.unique() # 类别属性统计,2ms4 X5 @, C/ J1 m! F0 A) I! I: _
/ s7 V9 A, ^. u. q8 RWall time: 2 ms' R8 {- a( L- ~% J
['Ideal', 'Premium', 'Good', 'Very Good', 'Fair']& u5 I$ U p" T$ V6 o- [
Categories (5, object): ['Ideal', 'Premium', 'Good', 'Very Good', 'Fair']
" h" f1 h, w& X. u$ M1$ Z( \' s. k# J% W3 f: c
2
; F8 B) ~9 r, F0 Z32 J% o x$ j% u7 W
4( b; {& u* S' l3 f1 S! O
5
+ l& q, h" r; z) O$ `6; W0 V% ^: s! A4 k! R- w l
对切割质量按照由好到次的顺序排序,相同切割质量的钻石,按照纯净度进行由次到好的排序。. O3 k5 @/ A; J& F* S: l
ls_cut=['Fair', 'Good', 'Very Good', 'Premium', 'Ideal']1 J7 N2 W P6 |7 a8 {
ls_clarity=['I1','SI2', 'SI1', 'VS2', 'VS1', 'VVS2', 'VVS1', 'IF']1 c6 i; {6 q, I1 K4 e6 a. ?
df.cut=df.cut.astype('category').cat.reorder_categories(ls_cut,ordered=True) # 转换后还是得进行替换
" z/ ^% h! ?& y& T1 L, odf.clarity=df.clarity.astype('category').cat.reorder_categories(ls_clarity,ordered=True)
9 i1 e7 e$ Z" g& H7 `) [3 \" N% W* [/ E' N
df.sort_values(['cut','clarity'],ascending=[False,True]).head(3)
! t+ l8 }* x3 A4 Y3 a
! S+ p" H9 s6 L- d3 u- n4 K carat cut clarity price' `/ P' {. c3 `
315 0.96 Ideal I1 28013 b; w+ }; F& U5 q" |( q3 u
535 0.96 Ideal I1 2826
/ ?: Z; X T0 O551 0.97 Ideal I1 2830
2 G: f. \3 Q! D: s5 ?1 |( Y4 ~1
E K6 i( w; P* ? @2
3 E7 R- r, f; E; p) |' m3 q35 [) g1 Q) q/ g1 U9 `+ T% a
48 \8 ?5 l( h1 O+ d6 y9 B7 X/ S4 O
5% z6 C L) @' M
6
/ `! M$ M5 \2 r7; ?( U1 C0 W! ]- F, f
8
4 ~3 q/ r. ~$ T' p0 e: {2 ^+ y9
! g' P3 v8 o- n+ Q U; V2 M3 U10
0 }9 u( C% P! n+ I0 x! S% [11) U: w6 j: h% G: X) N* S
分别采用两种不同的方法,把 cut, clarity 这两列按照 由好到次 的顺序,映射到从0到n-1的整数,其中n表示类别的个数。
" B8 O+ u" O% V) f$ p! Y3 C# 第一种是将类别重命名为整数" d% X* J2 x8 E- Z5 d
dict1=dict(zip(ls_cut,[x for x in range (4,-1,-1)]))
) J. H; y% F2 T( Cdict2=dict(zip(ls_clarity,[x for x in range (7,-1,-1)]))
9 z5 q: O/ v/ R
/ V( [: W- v3 n) @6 Ldf.cut=df.cut.cat.rename_categories(dict1)9 P9 D/ O. F8 e
df.clarity=df.clarity.cat.rename_categories(dict2)
7 B8 M6 M; U0 s% ^df.head(3)
7 ]! V9 G, j/ T3 N* g$ r2 n: V( E& V1 q- Q1 a6 w5 q5 S
carat cut clarity price
W( ?7 N2 x8 v' {0 0.23 0 6 326
1 g8 D6 U" \2 q6 L# E) l$ G( J' \' {1 0.21 1 5 326
! [. z2 Z% I5 W, J* I& ?$ s/ C% Y2 0.23 3 3 327
, e& q J2 j h$ s/ V1
8 Y. o+ k: f% x: r5 Y$ e5 b' X) Q6 H2
: x2 _# u o* h E. _" A3
' L. M8 ?+ e+ ~% B( L. h9 ]48 L4 Q# A0 A5 s$ ^4 x
5$ ~5 ~. D0 O- N y! X
6
: P% m% h4 m; b) q4 a7 q2 S q# v+ ?7* u+ V! t" y0 s6 a0 Q
8) m- ]; I0 I2 m2 f8 p" @
9& i: m2 H S$ c, D' A- F2 Q
108 L9 p d$ T Z5 q
11
A. R! h- [2 y* q12
, y& C2 b1 {" G" r5 I# 第二种应该是报错object属性,然后直接进行替换% s4 M9 D; ^" ^" _; k$ B
df = pd.read_csv('data/diamonds.csv')
( j& |3 s$ y. K; B7 Ufor i,j in enumerate(ls_cut[::-1]):2 W1 Q8 v+ B8 k# d
df.loc[df.cut==j,'cut']=i
, i) @8 \/ P: l. a0 U" O( S1 D4 r; U( [
for k,l in enumerate(ls_clarity[::-1]):
$ Y2 _/ e3 @1 G df.loc[df.clarity==l,'clarity']=k
4 z! Q: Y% u `7 ]7 E% mdf.head(3)
1 J( G- O, f0 a& k. i7 @: f& I5 ?; A9 i1 i* s/ ^, h- z7 j
carat cut clarity price
, W# C% P) D! }0 0.23 0 6 326
- X4 R& E3 K5 x0 x$ \! E2 a1 0.21 1 5 326
4 m; L! w( D2 H+ ~2 0.23 3 3 327
; x& O9 `: z2 b0 b# L4 j1+ P) n7 d5 @& g( d2 `( M
2) ~) u6 H, D: q0 V$ f: ?, B
3/ k9 {8 R# W! T9 e7 R1 Y# ^5 L- V3 s
4
; N6 _6 d% ^2 ]5: _# i0 g$ a p5 j# C" v; u( N+ j
6
& [) W- T, f; w5 \( Z6 B/ V2 L7& \% T8 m; @. J0 c
8
7 I& a# u& L6 }. v: p7 G: Y+ L9
8 g- p7 X# s5 s) e9 h# i* S/ ]. W5 Y10
' Z9 T, c& x, N# M# z, f' j11" S$ ^9 K* B3 t: u
12
: Q; F! }( C3 B13" p" j* D- _3 R; O
对每克拉的价格分别按照分位数(q=[0.2, 0.4, 0.6, 0.8])与[1000, 3500, 5500, 18000]割点进行分箱得到五个类别Very Low, Low, Mid, High, Very High,并把按这两种分箱方法得到的category序列依次添加到原表中。
( L9 M( [2 s' ?! S# retbins=True返回的是元组,第一个才是要的序列,第二个元素是分割点
* H; d5 q& q8 H; M- C7 ]avg=df.price/df.carat
8 U, ^, Z X6 n, t! I) t
1 t, ]3 K$ d1 F4 Sdf['price_quantile']=pd.qcut(avg, q=[0,0.2,0.4,0.6,0.8,1],
$ z' j/ S! J. ?# Y8 H' ?9 j9 i) \. _ labels=['Very Low', 'Low', 'Mid', 'High', 'Very High'],retbins=True)[0]2 {1 {( h. D7 B' W8 n* o& T
3 g! l$ N) n3 A2 P- ddf['price_list']=pd.cut(avg, bins=[-np.infty,1000, 3500, 5500, 18000,np.infty],- E6 I& F& `+ R6 S( x
labels=['Very Low', 'Low', 'Mid', 'High', 'Very High'],retbins=True)[0]2 z6 d; n* e+ n2 t, h
df.head()9 n% j- k( o6 r) k& C6 V
) q) s4 h6 I# r* W! y! F, J& w carat cut clarity price price_quantile price_list
5 E1 W- V- U2 H9 |( _0 0.23 0 6 326 Very Low Low3 E" E3 v, t) o3 w
1 0.21 1 5 326 Very Low Low! a' q; b# c _$ f" i5 k8 R' ^4 C
2 0.23 3 3 327 Very Low Low
- {4 [2 y, n y. O6 N5 v$ L3 0.29 1 4 334 Very Low Low
* Y- |; u, `$ p: X: P4 0.31 3 6 335 Very Low Low / R9 K7 N% I7 E
; z7 w" j4 s: w/ }4 L9 N* Q% z8 X
1
0 Z( D0 B- B8 k1 _/ T# {. k2
. O$ l8 h5 P5 k" W4 @3
2 ]3 w( Z3 y% J w8 I. Z3 H5 R" K4- u b O7 Y0 N3 u+ ~
51 i* }( e& G4 ^: v0 N' H2 z1 p) Y
6; q, r9 K# H5 ]1 Y- K3 Z; K% v2 s
7
6 R4 m: T: n$ ^) l0 L/ b9 P8
. T* p2 ^ _' h, P9
7 U, r. D( P7 B1 H% W# {1 p100 A8 H$ g$ z5 l8 t, `) b6 r' L
11
" }% }& K2 Q {5 B V9 y7 x12: \9 `" o# P: w* x: E
134 D9 \4 ?3 q& i. h7 |" @; H n
14
2 f6 A7 W% [( h3 I3 h8 W. j; R15
# q9 U4 i( U$ i! `1 e3 A% O1 h0 h16
# h3 B8 o/ S3 p9 J+ G分割点分别是:
1 t2 a9 {6 v: D8 V+ p' P# D1 ?, S$ j: ?5 D& P4 r0 J
array([ 1051.16 , 2295. , 3073.29, 4031.68, 5456.34, 17828.84])
% {; ^" D% j$ U/ _7 ?; V- Warray([ -inf, 1000., 3500., 5500., 18000., inf])% }6 V; W) L9 z/ A4 d: O$ n
1, x% T4 i1 N5 r; n# T& t
2! y# j- h5 N0 M8 R' l) ]$ F2 c
第4问中按照整数分箱得到的序列中,是否出现了所有的类别?如果存在没有出现的类别请把该类别删除。
' v3 s# @: S. F& ], `! Zdf['price_list'].cat.categories # 原先设定的类别数
$ ~: \6 j+ A6 d" E! h2 AIndex(['Very Low', 'Low', 'Mid', 'High', 'Very High'], dtype='object')& T' g2 Q/ q9 d9 \' |. f( z1 d- \' v
. j3 ^+ }' q, k( z0 F
df['price_list'].cat.remove_unused_categories().cat.categories # 移除未出现的类别8 f$ ^& N8 S! P2 V
Index(['Low', 'Mid', 'High'], dtype='object') # 首尾两个类别未出现
! ]* b0 E' X- s X1
f- r& }" s4 ]$ I( T' @5 y2
4 P1 z5 b% p' j- n# W5 S32 K) d j& y i7 n3 Z2 [
4
m7 T3 W' Q3 D1 P6 O$ Q1 S) G8 ?$ Y5/ u! b1 \* o1 ^& y& f
avg.sort_values() # 可见首尾区间确实是没有的
$ r" g3 P/ t5 l" t& h& ]2 s7 b; h31962 1051.162791
/ Q, n" J0 ^, {' N$ V$ n15 1078.125000) d8 L6 s: X; L* ]
4 1080.645161
# Z4 T: M8 z: T% t3 } p! _28285 1109.090909+ ?# N, o8 i/ p. b: M. \
13 1109.677419
7 Q9 O; z$ x' S% Q2 b6 D( S ... w, V- w7 h- a5 O- `' c
26998 16764.705882( q3 j# i- I- y6 Z) y
27457 16928.9719630 ^ N5 r+ z1 [% h7 C
27226 17077.669903
# d6 l. y4 a [+ K4 x27530 17083.177570
& Q: V A0 ?/ O# g+ P& N- j; _2 |- l27635 17828.846154
3 H, @! o: g3 a; f1/ p' s0 V3 |1 r2 I( o# s/ @
2
& O* \2 g* D: c* T) H37 K7 E! {% W4 M6 ]* Q+ ?0 `
4 [8 I [; ^5 s& A0 [$ c
5
$ t+ I; d( q# F/ }) i8 w6
( ?# o$ a9 N1 [7
4 P' M, X- a1 D' K0 V4 c8
. G M$ K0 E8 A0 ~' u' n; |95 G: H% _/ H) w3 B8 s8 d i
10
$ L$ [' U6 X) v1 _: [11
; a" _3 i: I; ^. n1 y" a12
. I$ U) }* m/ B- Q6 d: @/ d. q对第4问中按照分位数分箱得到的序列,求每个样本对应所在区间的左右端点值和长度。
3 d0 B1 d. B" I. F: |6 P$ `# 分割时区间不能有命名,否则字符串传入错误。: H0 L/ W5 d) O4 S
id_interval=pd.IntervalIndex(1 P! A! k! O5 u2 Z
pd.qcut(avg, q=[0,0.2,0.4,0.6,0.8,1],retbins=True)[0]: c. \# H) }' q0 X. Z
); ~) `1 q; x( ^ }# n S
id_interval.left
# q) B) `! C w- Y; `2 L. wid_interval.right- M' `* F/ D6 t' ~7 B% Z1 N
id_interval.length
& z! c9 K9 K0 o* W. \# I+ ]) P1( ?( U& x2 { K+ T
2, D9 f' p# ?2 U( G5 j2 {
3
/ E v. u& {0 ^. R4
; L+ a p1 L/ c( s2 [5: g3 k, y4 K( J. _! y4 F
67 [ ]: e7 U$ z& n7 c0 E7 o9 R
72 v9 Q$ l" _' N' `( o/ t# [7 P2 x
第十章 时序数据
+ g9 o E$ l$ i+ F" l8 t2 u* Nimport numpy as np
; {+ z7 L5 z+ u/ K1 m3 d' M7 |' qimport pandas as pd
3 [/ [5 q* F( h" ~. }1" V b2 ^4 E |6 q: H& @
2
0 e s1 [& ~1 U6 h e6 ^; C
4 }& |! e1 k5 C, p/ O0 H" W
" a; g. c4 c6 j3 _) P: U10.1 时序中的基本对象# @) d" S9 v4 M. h- `
时间序列的概念在日常生活中十分常见,但对于一个具体的时序事件而言,可以从多个时间对象的角度来描述。例如2020年9月7日周一早上8点整需要到教室上课,这个课会在当天早上10点结束,其中包含了哪些时间概念?
3 s$ b) V! Z4 X' e7 U4 C: x( A+ c) t& z+ P- p
会出现时间戳(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的简写。
! z$ a; W9 |% t' V' x/ C1 i) K P V3 f! L6 `* W; t
会出现时间差(Time deltas)的概念,即上课需要的时间,两个Timestamp做差就得到了时间差,pandas中利用Timedelta来表示。类似的,一系列的时间差就组成了TimedeltaIndex, 而将它放到Series中后,Series的类型就变为了timedelta64[ns]。
3 r" V/ j1 r1 }, \
7 A# f5 r1 b f会出现时间段(Time spans)的概念,即在8点到10点这个区间都会持续地在上课,在pandas利用Period来表示。类似的,一系列的时间段就组成了PeriodIndex, 而将它放到Series中后,Series的类型就变为了Period。; u2 b& z' _9 a( y4 x
) U& N9 g8 g1 [. A# M" `会出现日期偏置(Date offsets)的概念,假设你只知道9月的第一个周一早上8点要去上课,但不知道具体的日期,那么就需要一个类型来处理此类需求。再例如,想要知道2020年9月7日后的第30个工作日是哪一天,那么时间差就解决不了你的问题,从而pandas中的DateOffset就出现了。同时,pandas中没有为一列时间偏置专门设计存储类型,理由也很简单,因为需求比较奇怪,一般来说我们只需要对一批时间特征做一个统一的特殊日期偏置。( k7 S3 E( {: M/ }- t* e4 B
4 |3 u+ w6 t: r, v1 f$ B( @
通过这个简单的例子,就能够容易地总结出官方文档中的这个表格:8 C5 O+ r$ r% W0 g7 D
2 v& V' r7 ]$ v- K
概念 单元素类型 数组类型 pandas数据类型
0 L! I0 P- l- X/ Z! l) ^Date times Timestamp DatetimeIndex datetime64[ns]
" C* H3 A+ b4 \1 w3 u. k+ j( E# a* m xTime deltas Timedelta TimedeltaIndex timedelta64[ns]
) c1 ~; V5 {& O1 ~- ~. s: s4 gTime spans Period PeriodIndex period[freq]
& W6 }. h) n& ?' B- a) {! ODate offsets DateOffset None None
2 q! g9 ~7 i7 d 由于时间段对象Period/PeriodIndex的使用频率并不高,因此将不进行讲解,而只涉及时间戳序列、时间差序列和日期偏置的相关内容。3 Z1 I( n# q* w3 q
9 e Z4 w C1 ]% `' X
10.2 时间戳2 f* T! c5 X) }1 p( h! a0 r
10.2.1 Timestamp的构造与属性
7 L) {" ?) {. {/ ?7 t( ]单个时间戳的生成利用pd.Timestamp实现,一般而言的常见日期格式都能被成功地转换:
( |$ b( i9 s) ?! H( c
/ o; z" F# Z6 y3 ^/ _0 w$ G! Mts = pd.Timestamp('2020/1/1')
; q# @6 n" R5 l) A
1 f% u' u ]3 ]" D! Lts6 F8 Q3 M$ T. J/ F2 J. a7 z
Out[4]: Timestamp('2020-01-01 00:00:00')5 g; ]* d( s4 V. V
6 K% O0 a1 S9 K$ x* g: lts = pd.Timestamp('2020-1-1 08:10:30')
; P5 Y" u/ F; e+ l! l
+ U' g: M' \0 i: Its& z" x- \( w+ @$ l# [$ v4 |
Out[6]: Timestamp('2020-01-01 08:10:30')& e$ |' y% c+ [! |2 ~- h' k* U
1, Q& S0 Z$ }) x4 D
2" u8 R7 H! X5 X. X; X9 N( o
3$ l7 i6 I% x9 D& {, t- s! F! w7 K
44 ~2 o+ O) A, i5 e d; J
5; u' |2 B2 n$ X4 v" [; T0 U
6, [/ R* `, c/ a. P% S
79 |, k( ]: A. z2 v
8. q$ A# O1 _- ]
90 f8 B7 i% x8 u, b0 x( V/ t5 ]
通过year, month, day, hour, min, second可以获取具体的数值:% D$ `7 h ~9 d8 k0 k
) J, `: d& E# p7 Zts.year6 V7 h9 a6 ]* s$ j& i4 S$ s
Out[7]: 2020# C" l! @& [4 b- S
4 j/ M0 a( J6 pts.month; L- o/ ]% K! W" L4 Y
Out[8]: 1
, ^4 y1 J6 V' y# _" L$ K; k' J- ~" ]/ H v2 x
ts.day' U9 \# z$ J5 {* B) Y1 C
Out[9]: 1
4 t ^7 ]; g3 i) {2 r- n/ ^/ a/ D$ k
, h" [, L4 T0 X" Sts.hour
' |0 B0 l. `. @/ I, K5 [Out[10]: 8
$ G3 \) t) z% m- y+ ?$ t; |9 j0 C2 S) Y# g5 \
ts.minute+ N& A8 Z% v' k+ ~- ?
Out[11]: 10
# V8 G3 Y% D- t! q+ w+ N
- ` \- f( T" e3 o) l& w X6 fts.second+ B* r0 M& ~) j' |2 a7 o0 T0 V
Out[12]: 30
" y* L6 R* U1 k" I0 T
+ F$ D$ F6 V9 l; q* I. K0 [1
4 T5 x; j3 ?' X. c* o; [9 o2
4 H3 Y" P5 I0 b* Y {3. q, r" E2 A. O9 D1 m- ^3 ^. D7 v" _
4' U. Q) V) i3 @! z$ \
5( Y& T4 L! ]; L$ I' Z" |
60 G; j* R% s I9 X8 @
75 ^/ O' d/ t) k
8
' z2 o3 y- B6 G% G" u1 X6 \! K: w9
% J, c7 q- l o* u- p4 B. `10+ ?2 r! f3 f, i6 q" e C! g7 L" I! W
11
) ~6 y* ?1 M Z' y" X12
H7 h5 h) h3 m2 R( L; a s" b13
* l4 S. l% |5 [14% x5 G& c# U8 [
15. T7 E) C* @% F$ J, T0 T+ ^( _. T
16
0 E( i( {; k. h) P( a0 D9 D17
! J7 {% y. n* b. |8 [$ t% A; S# 获取当前时间, w0 R( d; a, C4 I; o, a/ R
now=pd.Timestamp.now()# w5 @; J/ t" U" O
1' y. H0 ^7 u1 N7 K
2
, }8 P' o$ G7 |* X2 P* D, a0 H+ l4 ^在pandas中,时间戳的最小精度为纳秒ns,由于使用了64位存储,可以表示的时间范围大约可以如下计算:
& Z* s) D. {/ ~+ iT i m e R a n g e = 2 64 1 0 9 × 60 × 60 × 24 × 365 ≈ 585 ( Y e a r s ) \rm Time\,Range = \frac{2^{64}}{10^9\times 60\times 60\times 24\times 365} \approx 585 (Years)
0 w' T' C9 ?; TTimeRange=
6 k! I9 u+ S1 A& N5 b6 x; v10 ! c" r3 e' _( R: e& c4 [
9
& C$ l* P9 Z% U ×60×60×24×365
: }. u4 a( n& u( I2 ( e, J# g$ Y7 N( z8 y4 F
64 k1 ^4 ]6 P8 o: X
* f8 d& ?" W' e# q
6 T! [2 j5 w, U2 x ≈585(Years)
6 x2 K T5 D) L" K% y
, V, ^7 ^. S6 S5 s通过pd.Timestamp.max和pd.Timestamp.min可以获取时间戳表示的范围,可以看到确实表示的区间年数大小正如上述计算结果:
) O$ J5 s# J+ z) m6 c
1 ^4 {$ i; V4 I& opd.Timestamp.max
- x* v# z9 D# c" i, s4 GOut[13]: Timestamp('2262-04-11 23:47:16.854775807')
( P1 Y/ m1 O1 I9 k7 Y2 ^$ ?' t7 }; c1 S
pd.Timestamp.min$ N. Q, e% j* J: b6 A0 u* \3 U
Out[14]: Timestamp('1677-09-21 00:12:43.145225')
, ~+ {, I3 T4 R$ a5 K0 h: }( g% j/ T: }$ z
pd.Timestamp.max.year - pd.Timestamp.min.year
& e% Y; ^' H( n; m5 {/ R& r4 J! oOut[15]: 585
* Y$ k+ y3 y1 _$ _5 l2 D$ N1
1 v, J' s. L2 ~% M+ l! y2
4 B3 Y6 j7 k( C; D35 s+ n/ J1 d& @. F
4
4 r: r. P! I' l5
" ?. x' d/ Y5 s% H. S" n/ D6
" x1 C- s2 L+ e, M# G7
6 `& Y p2 `/ l6 o: _8+ }& Z s; d# H. l5 R( D( I
10.2.2 Datetime序列的生成; ^- `7 V6 J4 @( P p: O
pandas.to_datetime(arg, errors='raise', dayfirst=False, yearfirst=False, utc=None, format=None,
' j/ m0 u5 f# }$ o exact=True, unit=None, infer_datetime_format=False, origin='unix', cache=True)
: g- p9 W8 B7 x e9 j0 \' F18 e. i+ B( D# d2 i* W
2
' s9 l. r/ \$ O3 _& O4 wpandas.to_datetime将arg转换为日期时间。8 @& |! U- v7 `3 b6 H
7 a, W3 S; q5 Y
arg:可以是argint、float、str、datetime、list、tuple、一维数组、Series、DataFrame/dict-like等要转换为日期时间的对象。如果提供了 DataFrame,则该方法至少需要以下列:“年”、“月”、“日”。
! e7 I0 Z7 a" O Y( f7 @errors:
7 e, X% g; ~; J9 `# x5 S* X- ‘raise’:默认值,无效解析将引发异常, o& F" V0 R: M# f& e) @
- ‘raise’:无效解析将返回输入
: r0 a; R# d% y, N- ‘coerce’:无效解析将被设置为NaT* J2 m' n2 S0 t, i7 r
dayfirst:如果 arg 是 str 或类似列表时使用,表示日期解析顺序,默认为 False。如果为 True,则首先解析日期,例如“10/11/12”被解析为 2012-11-10。如果无法根据给定的 dayfirst 选项解析分隔日期字符串,会显示警告。( j/ _9 ]% \0 M" L$ }! K+ I
yearfirst:如果 arg 是 str 或类似列表时使用,表示日期解析顺序,默认为 False。如果为 True,则首先解析年份,例如“10/11/12”被解析为2010-11-12。无法正确解析时会显示警告。(如果 dayfirst 和 yearfirst 都为 True,则 yearfirst 优先(与 dateutil 相同)。)
" v" y" w# T' [ r( |utcbool:默认None,控制时区相关的解析、本地化和转换。请参阅:pandas 有关时区转换和本地化的一般文档( x+ c- @- q4 R. N
format:str格式,默认None。时间戳的格式不满足转换时,可以强制使用format进行匹配。
. D9 Z/ N7 c) g3 lunitstr:默认“ns”。它是arg (D,s,ms,us,ns) 的表示单位,可以是整数或浮点数。这将基于原点。例如,使用 unit=‘ms’ 和 origin=‘unix’ (默认值),这将计算到 unix 开始的毫秒数。! c! M& v# X. w
to_datetime能够把一列时间戳格式的对象转换成为datetime64[ns]类型的时间序列:0 G& E2 z4 Q# g" P
pd.to_datetime(['2020-1-1', '2020-1-3', '2020-1-6'])
2 x3 ^/ u0 i( r {% b. \ A( z& V" r2 v! H* J9 D& K+ U8 y7 d6 ^
DatetimeIndex(['2020-01-01', '2020-01-03', '2020-01-06'], dtype='datetime64[ns]', freq=None)$ d# h; k! e0 Y j3 N2 Z W1 p
1" }% G7 I. {# V& w" N
2
4 F- J1 d0 A: G+ E3$ G* A2 H7 b9 F F
在极少数情况,时间戳的格式不满足转换时,可以强制使用format进行匹配:5 ~( f" Q4 V9 R. O/ M2 U
# S( j. X0 ?- w% l2 [2 b
temp = pd.to_datetime(['2020\\1\\1','2020\\1\\3'],format='%Y\\%m\\%d')
- S0 {% J7 x0 ?6 f- ltemp% g8 w$ A, E. V% S# {/ _$ H0 D
# ^% a) I8 T6 T# E$ G
DatetimeIndex(['2020-01-01', '2020-01-03'], dtype='datetime64[ns]', freq=None). i/ m9 Y- E% A5 F6 Q2 V
1; C- |- \- N2 u6 ]8 U5 H9 {
2
/ X" ~ ?" x0 W5 J2 ]3) Y7 b( v& Q h
4/ _/ H: Y5 Y! ]/ u
注意上面由于传入的是列表,而非pandas内部的Series,因此返回的是DatetimeIndex,如果想要转为datetime64[ns]的序列,需要显式用Series转化:
6 K: D" t: C( P5 u, C2 b
/ T! F) @5 p5 ?+ C; T6 T: q* [pd.Series(temp).head()) H3 w l3 u: q/ X. ~2 S' e V
; u# `; E* c% H. `3 Q4 i5 Z' X; w0 2020-01-01+ T( z5 ?# ~! b' C6 \; ]1 e
1 2020-01-03! D% s. N! k3 W* w' A' G: M% f
dtype: datetime64[ns]9 P* J3 [5 X0 s+ O& N9 F/ ?) X' f% }0 u
14 M3 C) J r$ G8 b
29 N2 O! D$ z. c* `
3) {. \4 D! X# ~# L
4
4 S( J( y8 o. ^$ j5 n5- G& x% o8 v$ }
下面的序列本身就是Series,所以不需要再转化。
& F/ p- W8 o$ e" n! {9 g9 E* E$ K, I0 y, g
df = pd.read_csv('../data/learn_pandas.csv')
( Z3 G4 w0 ]: w7 ~, F! js = pd.to_datetime(df.Test_Date)! e+ `$ M0 c% M1 x
s.head(): ]0 Z& C9 u+ T7 G( N4 _
" _) |0 s1 k+ l" ~* \0 2019-10-05
, l* i: H y* m! s0 l1 2019-09-046 V5 J: z( [: U% b+ f$ V7 ]( I# h
2 2019-09-12
2 h4 K3 _& i# S9 _' f& g5 t3 2020-01-03# L8 O/ p, s1 [+ n
4 2019-11-06
" _/ U/ P% A; zName: Test_Date, dtype: datetime64[ns]
. ~. Q# H+ h& K+ V/ H; F12 u; S4 p) ?- J9 r/ d: C7 V
2
1 J, W9 ~/ o8 ]3
& M% g4 \5 r/ [/ V) {6 Y4/ r0 ^/ ]3 }0 `# g4 u g. n
5
. c) N8 J# n1 x* Y2 j$ f. V6
, h" |4 l7 F* z1 M79 X5 X2 ? M; t
8
6 o9 n- ]$ X+ b3 G o! f9) l% |6 u" h9 S- `* j
10
, \+ j' H' V& d% T& n5 g8 h把表的多列时间属性拼接转为时间序列的to_datetime,此时的列名必须和以下给定的时间关键词列名一致:
Z- u6 ]9 l( Z+ F- _df_date_cols = pd.DataFrame({'year': [2020, 2020],
3 ^/ D7 U3 }" Z; }: q 'month': [1, 1],) }. n4 H9 e' [ E
'day': [1, 2],$ S) {( K# u4 s+ S+ g7 C( }
'hour': [10, 20],
y0 ~* z* A2 H 'minute': [30, 50],( S" p; K" j9 W' i4 v; Q) \
'second': [20, 40]})% G7 b" Z9 R( Y; u
pd.to_datetime(df_date_cols)3 K6 y1 c2 M; U/ x. t; u
8 [( H' W6 s. p6 a- E0 2020-01-01 10:30:20/ j6 p* v7 ^/ {% G" g9 S
1 2020-01-02 20:50:40* h$ i; B. w2 x! V& ~# _1 y
dtype: datetime64[ns]
/ l! K' Y0 L7 b1
7 V4 R3 K- W# k2& U0 O8 F/ B- K1 h; i5 l
3% @; S% w& G2 u
4
8 W S1 o' E1 t8 H4 E, J. k5
# {' } B- f5 ^ F; j7 N67 Z! k; y% A0 `, q" P4 G# [' K
7
+ r9 P. r$ x" h$ l2 h% F0 c6 g8
) G) c* ]" a; a8 X# Y9 B0 {5 s9- X; O: j: ]3 F. q" s
10/ i/ G$ f. f0 K4 }, ?* F" A. g
11
: J- D' K% y8 Q# k+ G" g! \date_range是一种生成连续间隔时间的一种方法,其重要的参数为start, end, freq, periods,它们分别表示开始时间,结束时间,时间间隔,时间戳个数。其中,四个中的三个参数决定了,那么剩下的一个就随之确定了。这里要注意,开始或结束日期如果作为端点则它会被包含:
7 F) m! T2 H6 P4 @0 g5 R9 Ppd.date_range('2020-1-1','2020-1-21', freq='10D') # 包含
. ]( B( t8 [6 h1 x# d9 nOut[25]: DatetimeIndex(['2020-01-01', '2020-01-11', '2020-01-21'], dtype='datetime64[ns]', freq='10D')
$ ?; h1 Q: ]2 s, I: \3 k8 |, E% `% ^" K" g/ l4 m4 C; v
pd.date_range('2020-1-1','2020-2-28', freq='10D')
' F# b! @& R6 C$ }& ?% vOut[26]:
$ E& w- ^) ~7 N7 ^DatetimeIndex(['2020-01-01', '2020-01-11', '2020-01-21', '2020-01-31',
' T' Q6 S6 N* o" W '2020-02-10', '2020-02-20'],
' B* R' _9 e5 M4 \) f dtype='datetime64[ns]', freq='10D')
% e, `/ b L- K& X: R' B* X) @. j7 f/ }- @
pd.date_range('2020-1-1',
, L8 r; Y6 [4 f o '2020-2-28', periods=6) # 由于结束日期无法取到,freq不为10天
! m* T; A& `7 R+ _' P) B% E! w _6 H
Out[27]: 7 v5 j2 _3 |8 K7 Z5 q8 U. R
DatetimeIndex(['2020-01-01 00:00:00', '2020-01-12 14:24:00',
! s2 i& p( w8 f% N4 B '2020-01-24 04:48:00', '2020-02-04 19:12:00',# |8 o' U7 H/ j* u1 k0 y' P) O7 {
'2020-02-16 09:36:00', '2020-02-28 00:00:00'],
% Y$ S- T; f% v' K8 f9 j dtype='datetime64[ns]', freq=None)" q1 G; h2 ~* A- r q
; W+ ]0 R. {/ b9 Z% p& j1 l0 Z, u) h$ K9 r" V
2
9 A! }$ W2 T6 Q! r6 f3
5 {% r4 O0 Y8 I9 Z, M, |+ E$ W40 D% e+ W- R& v. r7 \3 J* P3 c6 ?5 o
5
! `/ x8 D A4 ? E& o1 \6
4 L8 A8 d+ e; u% i/ r/ T7' z. c( u+ \( }. _; [- p
8
0 I/ W3 E4 x+ l7 B' a, M" w9
0 J5 {" {8 ]+ j% P+ y10. A8 q8 k& ]. [
11
8 e' G5 ~2 v! [& H3 v: q! c+ d12
$ f: ?* h; ^5 w" P- B# j# x5 a# y13, \1 _+ m0 y* Q( X( C
145 M+ I0 x& ?) X8 Q
15, l1 C, K! u, V% T" W4 ]7 y
16
% m4 r+ U- |& Z& w17
# j& a4 L: T8 p$ w这里的freq参数与DateOffset对象紧密相关,将在第四节介绍其具体的用法。5 _$ ?: ^4 h4 p; u, ~$ Y! Y
6 ?' F7 j3 O, n" g* ^2 P
【练一练】
. F& j8 q" s9 n6 s+ q- ^$ u# C) d: Y3 ]Timestamp上定义了一个value属性,其返回的整数值代表了从1970年1月1日零点到给定时间戳相差的纳秒数,请利用这个属性构造一个随机生成给定日期区间内日期序列的函数。# [( o7 C6 Z& E5 b5 L
" y& o9 j* A1 n1 d" Tls=['2020-01-01','2020-02-20']0 L, b S7 T6 ]: F
def dates(ls,n):
# Z2 @! a4 o- j) s min=pd.Timestamp(ls[0]).value/10**9
, w$ a6 K( _( G# m max=pd.Timestamp(ls[1]).value/10**9
O- v) R+ b1 F8 }6 N, g! m times=np.random.randint(min,max+1,n)
. @& H; x8 N6 C return pd.to_datetime(times,unit='s')
, V, x; j8 {. t' V( Tdates(ls,10) 6 q4 A( i5 ~, N' `
# V" u( P0 h0 ^: q6 ?/ RDatetimeIndex(['2020-02-16 09:25:30', '2020-01-29 07:00:04',: j3 R9 h/ f7 @1 W
'2020-01-21 12:26:02', '2020-02-08 20:34:08',
9 u, O0 a w% S' _4 I '2020-02-15 00:18:33', '2020-02-11 02:18:07',8 T: A+ I, R( u+ ?1 |
'2020-01-12 21:48:59', '2020-01-12 00:39:24',) d% d$ T4 z. Z
'2020-02-14 20:55:20', '2020-01-26 15:44:13'],
. L' @/ U T s/ X# ?8 J/ [ dtype='datetime64[ns]', freq=None)
+ d' L$ ]+ y" c2 n1/ S- O2 M" k+ t4 g6 T1 L0 D
2
: W L% y- ^' U: S/ }7 m$ t0 \0 Z3
! [% W8 ?4 t" Z- P7 d4, d+ h9 |" F% R7 E3 y
5: D0 I( W7 P* }1 A3 Z$ j, f
6
1 i' s. H& [9 H4 G0 N! J. m- I* {3 d76 f/ U7 h' Q6 n
8
8 q1 D+ X3 G7 K, s) w ^: _9
/ R/ E4 U* d; A! A10 K- N# m! q! m' r" v
114 K3 [) T2 O2 L5 g( Q
12
% T4 t- ~. R H13# i& D5 ^: T! F3 P4 x; _
14
/ U' t5 U5 n! p2 T& Wasfreq:改变序列采样频率的方法,能够根据给定的freq对序列进行类似于reindex的操作:- t) J1 F* ?' Q, K
s = pd.Series(np.random.rand(5),( w. ^& z6 P4 K$ J$ o3 N
index=pd.to_datetime([
5 x, f& @* m% [7 o '2020-1-%d'%i for i in range(1,10,2)]))0 X3 ]% {* q' I; x1 y+ z
4 W0 a0 t$ w3 M) z7 k2 F
% p# ?) {6 S" l4 K; W% \9 |( Qs.head()- {! \( E/ l' v
Out[29]:
" Z9 E2 P6 D5 C2020-01-01 0.836578! p, }: q+ f) I' K7 Q
2020-01-03 0.678419' D# a' [0 V, N( {, m: q, C9 J
2020-01-05 0.711897
- V: B5 M: { G2020-01-07 0.4874293 t' f" A' y0 X5 B2 H4 S
2020-01-09 0.604705
$ N8 W( z! p# {: n" `dtype: float64
7 {5 P0 M0 @$ ?4 o5 @+ O, u3 J; s
. n& D3 ^. e; t8 js.asfreq('D').head()2 x! q9 q. q+ F% b# \9 K9 \
Out[30]: ( v' z4 E' s2 |
2020-01-01 0.8365787 ~7 J' a3 S6 y; P9 T* ^& i! D# M6 z
2020-01-02 NaN
5 h9 ?; B1 w+ i* j2020-01-03 0.678419. ^% U* L# G! l; f3 r+ ~
2020-01-04 NaN, \7 r5 S7 ^, O. g& C5 o8 r' p
2020-01-05 0.711897 j( l6 e. `& m7 j" h6 H
Freq: D, dtype: float64
+ }4 S, m) T8 \. {; m1 @) j
c9 F. d) t8 C6 w! a& i! L, Cs.asfreq('12H').head()
2 R2 Z! a a! F$ HOut[31]:
; g u4 S1 Z2 `5 C% g2020-01-01 00:00:00 0.836578& `) `( _9 y& Y9 m2 ]! M/ p3 D
2020-01-01 12:00:00 NaN+ f1 t) e. o9 s2 L5 d
2020-01-02 00:00:00 NaN
H' r, ~7 v, E, q5 v l8 o2020-01-02 12:00:00 NaN" U: J$ x* E% M% @, S
2020-01-03 00:00:00 0.678419
5 n6 c$ F! W% ]4 m6 Y* h. S, `% YFreq: 12H, dtype: float64
5 C$ l% _+ D- p% O) X
, X# D; w; |! m1
$ Z+ x4 c o; s3 y0 E" @1 P' h! z2
5 h3 a2 ~) x" z# _$ c3
$ }' K) u/ T2 Z- b+ C& L43 {& s0 Z- H- Q7 M! e
5
& V* t# [; W' j9 Z6: V( D8 {+ J% n% i! M
7
3 s, h/ Q/ ]9 p" _' p8
7 y; }+ t7 L3 F" C94 b2 x% K* F3 E
10# h* z3 z- Z- ?0 O# w# ~) {/ J7 J
11% \3 {: H* u) g0 H( H
12/ N. N8 V M6 J3 ]! T: b! o
139 C8 G+ _6 j& j: I% y
14* |& [$ [2 _( y/ q3 P% ?- C. Q$ T
15/ Z N1 x: t! D7 a) A: ~" j
16
) {- H' H# W! h, x. F' m. j( a, C177 P. V2 j' \4 z5 E* d+ U. @
184 L/ X0 o1 C# K$ k: Q) T
19, L0 c/ u$ u/ C4 A
20
# E" P* H* u g% t) r21: M$ ]! [8 b% t0 B# m
22" a( H: ?" w# T
23; Z1 N) i' O# f, W
24 x) n2 u: \; i& `7 [9 U. a
259 R- Q8 {0 E& L) [: b
26% j n5 |' l) H" M3 K
27
1 _ ~& ~. M, A28
n$ `( G6 l( G5 L295 j5 i* N! N, z$ t; B' h1 W8 u
30
. |$ O+ l8 Q: [( x _31% f* l( M" {( d) _8 [
【NOTE】datetime64[ns] 序列的极值与均值
0 `5 P* ~* Q' g# m7 m8 _ 前面提到了datetime64[ns]本质上可以理解为一个整数,即从1970年1月1日零点到给定时间戳相差的纳秒数。所以对于一个datetime64[ns]序列,可以使用max, min, mean,来取得最大时间戳、最小时间戳和“平均”时间戳。9 r1 r6 R2 Q% R* l7 a
' e& }" g" b3 n8 u; T' ~( F" t10.2.3 dt对象" o5 Z- b( i/ f9 n9 p8 `
如同category, string的序列上定义了cat, str来完成分类数据和文本数据的操作,在时序类型的序列上定义了dt对象来完成许多时间序列的相关操作。这里对于datetime64[ns]类型而言,可以大致分为三类操作:取出时间相关的属性、判断时间戳是否满足条件、取整操作。: Q6 R$ |1 @) m2 A
) Z) Z: H; w! ~ L8 S第一类操作的常用属性包括:date, time, year, month, day, hour, minute, second, microsecond, nanosecond, dayofweek, dayofyear, weekofyear, daysinmonth, quarter,其中daysinmonth, quarter分别表示该月一共有几天和季度。
( g' Y& s# ?; T0 \s = pd.Series(pd.date_range('2020-1-1','2020-1-3', freq='D'))
g& p' O5 Z$ n2 V# x- o3 B: V0 f' L; B6 c* N
s.dt.date9 _0 F9 p! ~; T0 y2 e9 u6 T
Out[33]:
+ K8 ~7 k5 f8 q, x: A/ S* u9 n0 2020-01-01
( b& r/ Z3 ~ t+ {& X' i- v1 2020-01-02" m7 y' }6 {" y: ]1 }' T! J8 e
2 2020-01-03% m3 Z: s6 F+ \3 O! F
dtype: object* U$ p4 f& o( P% r) S$ b$ r/ g% l$ Y
6 m2 Z/ Y, G% U$ V
s.dt.time, W0 L: d, O6 q4 S R0 [! s
Out[34]: 4 I$ R! G0 }0 u, q
0 00:00:00* Z5 |6 l: Q9 Z* [, M) K: h
1 00:00:00
' L- V8 g. C1 R2 o2 L" j2 00:00:00# B' R' z6 g2 l1 X
dtype: object
* e1 Z! ^. A+ ?" q) ?& U' a* B X7 q% _+ n/ _8 z! D8 a
s.dt.day: B5 O1 d% s% t0 F6 Q
Out[35]:
& w5 v& G: a9 V" V0 G' E0 1& Q+ h$ n% {* v* c6 N& d5 J
1 2
5 [4 l0 y- {; I% O [. A& Z V2 3
K, o8 F- O! [dtype: int64
0 M( p# M0 G3 v) Y5 f1 b: q) O8 N* R, u" D: t6 h. y& v& b- O
s.dt.daysinmonth4 Q$ P' {1 |6 s, i8 g4 S
Out[36]:
3 D1 X: [( A! x& ]0 31* D( ~1 N l% J5 ~ O) L; ~
1 31- `! x, i+ |3 c% x5 v! I
2 31% Y. L+ P$ w6 n
dtype: int645 v2 z- {# O$ h0 g% O
* U. b1 V' X3 a- K0 N
1/ _/ m7 m7 ~" N" ~1 i
2
: i$ F- h4 {# ]) C+ {3
9 o* n; R5 D, i" T- k; {. b, O* K0 `45 `. y3 a0 C! O6 i8 q
5! y+ A. G. R- K* ]" ?1 C
6
9 C8 h1 E$ L6 w3 f5 s/ ]7) K% H* Q. e# _! Q; ?2 R5 f
8# s: j) w5 G( d+ O' k5 u
9
R! m/ J N5 m6 a$ W1 k+ I* B10
( y2 M( o. T& `4 S11
/ Z' R6 f$ o" c12
! w$ T' e C0 S6 V5 j3 P: F130 k- x+ F2 b! h; t, w, j
14
6 G# h) r9 {& X' W+ x7 [15
1 U& K" U* r; G+ V7 m! P16) o. u) b( ^( x3 M2 r4 v
17+ Z, s5 K/ A- v7 l
18, `: [, K3 D; v% s2 `; Z5 g/ W& V
19+ M; k% C6 N) O4 }9 j5 J
20
( n2 [5 G j! d$ Z21$ q' X6 f Z2 g" k
22
$ H% q* Y+ n* _2 P6 U23
4 u/ k3 s. _2 q3 V" s$ @# b24* i, B, E0 c: K( V# C) r C
25
* ~( n9 X: M7 z% ^3 S# w9 m26
3 {& S$ _( |2 D+ I' C9 P+ U27
. q/ u; i" R* b28
) G: a2 A& `& z9 q' U29/ K" J' k. A" H
在这些属性中,经常使用的是dayofweek,它返回了周中的星期情况,周一为0、周二为1,以此类推。此外,还可以通过month_name, day_name返回英文的月名和星期名,注意它们是方法而不是属性:0 y! Q' X; p2 _2 s4 b M* K
3 R) E8 X* ~! d) b' E
s.dt.dayofweek* }! v4 {* M. b& M2 d
Out[37]: " \5 i# o0 z, R% F1 p
0 2; _$ O# U- t$ {; m
1 3 k+ e7 _! I. _( t
2 4 f/ o& X' @6 T/ b! X
dtype: int645 g, t2 w, a* Q- H5 I
5 a; @! r7 `- R
s.dt.month_name()/ I! ]5 t- g4 Y% w) X# C4 ]6 V
Out[38]:
9 r; Y! V. l y+ t; @0 January
+ Y P1 S S, u1 F" m1 January; s N* v) J* @: ~! c, \1 H
2 January! y$ G, Z, Z: K8 Q
dtype: object
1 \' c$ a; j8 E8 ?' P
: t h* }5 {+ R+ `3 ss.dt.day_name()
2 D2 U& q0 m0 C, f4 K! ?Out[39]: 2 S5 L9 i: M9 h. Q+ Y
0 Wednesday7 t# l% w1 B) j/ u& ?
1 Thursday) o7 Q- O; f7 v+ h& W, R6 P
2 Friday4 e0 z6 W% K. A' q
dtype: object
* G$ l! a, S2 b" U8 }2 p, U- x2 ~
0 ?7 ]4 @/ w& y. D6 X! D1 c1
5 |% [, d+ D @, F8 s0 R' G @2
1 g1 Z; P" `; S, ~) C E2 W/ c! T3
6 D* P4 h& p3 }6 ^ @) X; r1 m4' y4 r Y, @7 _' ~3 H$ ?1 o( R4 [
5
: c$ S! [7 P h5 M6
0 w& c! _2 q f$ G' h7
R4 e, H9 X1 B; X0 z5 v8; _) k0 a5 y4 ]3 u
9 D/ f2 r8 j: j: i/ W8 `! R9 t! Z; \
10
- c/ l! h- N# T7 N11' G3 ~9 j$ E8 `3 Y
12- [6 Y5 Z0 M* H2 ]2 K2 X
13& J6 \, H1 w! N8 h% Q
14" i4 \ \8 L( o
15' W- {. `3 d% ~6 ]4 Z: b6 |8 k# Y/ M
163 k; ~% ?0 F! y1 U
17
5 Y/ N3 G% P, M1 ]5 w18" f# \, z6 z1 h3 ]8 Y
19
! B4 C7 B$ y4 h7 O+ j" t! y200 E' y7 |% ], w9 W
第二类判断操作主要用于测试是否为月/季/年的第一天或者最后一天:
% j% b2 w/ H5 o! s0 H' _s.dt.is_year_start # 还可选 is_quarter/month_start- C% _( W4 V9 S D. W8 p$ C
Out[40]:
% D: u7 r6 v5 |0 True
) L1 U% |# r3 t5 o0 ^9 E1 False
0 J( r# x( s, o5 K. |2 False
* a2 q6 t6 j' P2 Z" V7 Ddtype: bool% N- Y+ p+ A+ v# ?9 L1 `( b6 u
2 e6 e( z# K& Z0 G# r2 b. X8 ^s.dt.is_year_end # 还可选 is_quarter/month_end
6 x W/ n$ |6 O( m0 B3 U3 Z* COut[41]:
; t, E" t) G& a+ C0 False9 `8 ?, _1 _( t) _0 Z
1 False+ g/ C: D" ] a( e1 {" I$ N
2 False
6 U% U* W$ ]' s! ^8 sdtype: bool6 G7 y: P1 D+ _6 g+ q+ S! L
19 y. O$ y& K9 H, |- }8 _
2! f/ N5 H" P5 w
3
7 I r8 k# N3 a; R+ l$ }4
% ?; ^8 U1 `: d( b* {8 |5( F, D7 q9 \/ y; B
6
) g2 S9 L( L! M4 y5 m# ~0 s5 Z77 I+ @- i: Z* I) r
8: O; X1 {% T3 Z8 x1 u
9
0 m7 K% l) O" `/ b- V3 U10
3 {3 q# t* u% r! ~2 }5 V11
+ X) k; u J' J0 o8 n0 S12
, @1 p8 ^% n0 i( n3 g7 K1 |13* `- Z- z8 I p6 }3 \( _( B( I5 l! o
第三类的取整操作包含round, ceil, floor,它们的公共参数为freq,常用的包括H, min, S(小时、分钟、秒),所有可选的freq可参考此处。
" c! g2 B* t3 B. qs = pd.Series(pd.date_range('2020-1-1 20:35:00',
! `) `8 @. D0 c& Y '2020-1-1 22:35:00',
. j3 L" [ ^; K/ h; r freq='45min'))
) P ^+ s: l, n- i/ R7 G7 W! R# a" f$ F
# B' S% Q P3 u; s) r2 Bs. x# U2 d2 W6 ~; P# V1 s" A
Out[43]: ) I" p7 S& G1 N& j; V0 I
0 2020-01-01 20:35:00
$ N$ y, W4 |* s) O$ `! b Z5 a, d5 v1 2020-01-01 21:20:00: s" L$ M& C& B3 p. L6 j) D$ g
2 2020-01-01 22:05:00
3 Z. E& ~9 _! O* C" Gdtype: datetime64[ns]8 A) X( P; }" f8 E7 T' L3 J
5 `. j! T/ d4 l" {* ~, V6 z( z
s.dt.round('1H')" e0 G e0 Z4 f5 ]: L: v5 R
Out[44]: 1 x' l5 E* M# N7 B
0 2020-01-01 21:00:003 S4 c: x5 C0 m c; F
1 2020-01-01 21:00:007 E. |3 x; L& w% E/ S; q }9 |! a
2 2020-01-01 22:00:001 X; p1 i' j2 H( w8 e
dtype: datetime64[ns]$ M* F/ t, r- s+ p p2 Z( K' a
1 @% H5 S: T, a: `9 R a0 Rs.dt.ceil('1H')0 K8 Q ?' h! i: K+ H @
Out[45]: M0 E# y9 P: y4 c" o
0 2020-01-01 21:00:00
3 j: A# L( x! Z7 k. z9 ^' J4 ?1 2020-01-01 22:00:00, Y; V T5 Y0 h( }+ y% ~) @* }3 p
2 2020-01-01 23:00:00$ s# k G3 f+ y: @- f: \& A& C
dtype: datetime64[ns]$ c( O# z' _0 c$ O
1 u! S4 n( {# y. J5 r
s.dt.floor('1H'); i+ O$ n) p+ J0 }5 r% n& k7 P z
Out[46]: / `" g c& c# G+ \8 \
0 2020-01-01 20:00:00
, A5 S8 Q, y2 |9 K" s2 @3 _# f6 a1 2020-01-01 21:00:00
! B2 ~9 J5 `% b* m' w+ T) ?2 2020-01-01 22:00:007 L! }+ i, G* S- L8 \! K
dtype: datetime64[ns]
% k9 u; u! ]& P$ S/ l4 g7 I5 U# H0 ?% I7 B
1
$ B! S1 M1 A; n5 {" Y( w, M3 D: B2+ \" Y( j% r' Z6 n$ r3 b
3) s, x6 L! ^( U
4
- C; w6 N0 E6 X0 O$ v5+ g9 L% {( t H. m' O
65 G' w0 B: A5 G- K9 n
7+ s4 o" M, K0 |9 F. Q5 s I, o
8
% `/ u9 y4 x p& O) i$ G9: @8 r7 e$ O ~/ [
10* F8 t+ G' f C4 S& G
112 l/ Q2 \ _. V1 l5 F. g( T
12- d; r4 i/ x0 ^4 ]. T
13
( H1 c- V5 E- Q8 }# B2 @+ @14, a0 h' X4 f& r0 _4 d& ~# \# E
15
# j* U$ m3 c% ^& r w ^16
& [9 f/ c4 U' @+ p: k, x17
+ a) k) C9 Q/ _) s- J9 J18$ P( H7 r( o+ t+ G
19. f" P" o) v+ }' y0 s3 i. R% I
20' I5 R4 E% K2 [" |
21
4 c' R$ M4 e9 |( m- O, v. R3 _* N222 k! a6 r b8 C; x9 f
23
. q/ k A: L5 y24" } p4 p# O0 j, i; N9 x
25- R5 C; G, H* }$ _4 ?5 Y' T
26
3 d* P3 ^% y, B9 G2 }7 F27
1 i8 ]: ]7 H' c& s0 l0 K# o7 o g28
6 W. m4 X7 m2 }$ X; U: T29* e$ S: Y& r! E7 ~* e$ k+ X
303 l* n3 K7 C& h' x/ A" Z
31
3 K$ H9 \; _1 P3 ]. b0 k! r5 D+ |32
* d' F0 i& B6 L3 _10.2.4 时间戳的切片与索引
7 [3 e: a8 H7 D! ?7 }) J% o& N, K: [ 一般而言,时间戳序列作为索引使用。如果想要选出某个子时间戳序列,有两种方法:& C! E4 i- J! o" I
' @9 w& W) A% v
利用dt对象和布尔条件联合使用
3 W7 o& x$ y1 N+ B% `利用切片,后者常用于连续时间戳。' o- I( K+ I2 u
s = pd.Series(np.random.randint(2,size=366), index=pd.date_range('2020-01-01','2020-12-31'))" w8 c# S2 {6 g* y
idx = pd.Series(s.index).dt
$ ]" F7 R/ y' ?1 n, b4 d- [s.head()3 R5 Y) n2 Z7 }9 Y5 a
k# B8 @ ^: s9 F4 [
2020-01-01 0* w* @5 Z$ L8 E6 A! q
2020-01-02 1
/ N7 c8 X6 I8 L* G2020-01-03 1( j8 A/ ^/ x/ ], D4 F
2020-01-04 0
* j8 O# A8 G% T3 l0 I# s2020-01-05 0
9 ]5 b+ ? Z% q' x9 n) s0 yFreq: D, dtype: int32
' H1 ?- k1 J3 L1- g) e m- e5 v4 p' K: C! N
2, w- p" H) Z) s( W+ ? D r
3
& `; l+ X/ I6 b, v$ ~4
' @1 a+ w$ F. K5
1 O T K4 `. H# X7 w' o6( M* D O" F7 J! a3 | Z
7' u3 P! ^, M+ a- s6 p7 \
85 N- Y/ A! @9 J0 v6 O. n- T6 [
9
* c; g, |0 w0 r; \7 r8 s10
/ E- x c, _5 [9 }$ i% |# u; k8 bExample1:每月的第一天或者最后一天
, Z$ ]! G4 T' j4 L8 I# f% L( } E H, Z) Q0 X8 L1 S, b
s[(idx.is_month_start|idx.is_month_end).values].head() # 必须要写.values
. n- P# Z9 ~6 | Z& L1 U0 iOut[50]:
9 F3 v9 g% M R. h7 {2020-01-01 1
2 u6 e, E4 e, A/ Y/ D3 [/ T2020-01-31 0
5 q, K/ J# I5 K2020-02-01 1+ |8 _# K# R/ M. g/ y6 i8 Y
2020-02-29 1
s# A$ v5 O$ L0 N( L2020-03-01 0
, P" H, N" l. |) z: z+ Rdtype: int32" n K; h* ]& b8 L
1
9 H- K/ E, Y2 K8 c. i4 m2 ~* l- @5 o. m
3* s- I3 Y6 i; a! d: P0 J
44 a D/ m$ b" O, i
5
6 Z* n; h. J/ l/ j) M( H( L6
( r% Q( I" q/ d* J1 l- }- l9 c& o7) F- `3 D3 l- Q! Y1 d7 k
87 e2 \: f7 U; s- z8 m
Example2:双休日
0 g: B" [6 b7 y$ q7 C3 S9 p
, u) c2 X3 @/ \9 o3 i1 Z# i4 ys[idx.dayofweek.isin([5,6]).values].head(). f: o. e; O1 f0 d+ l
Out[51]: 9 q& {% w- X' S$ ~
2020-01-04 12 E9 ]+ n0 c( L& q! C
2020-01-05 0
# w& b( l& j: R+ {0 ~) V2020-01-11 0, ^, e$ `' w6 J; c
2020-01-12 1
8 _6 K3 x v" K8 R2020-01-18 10 X. w2 X4 O; V1 h; O
dtype: int32
4 L4 ]$ J$ C5 ~* }( `$ J: i+ ]1
* v* A8 u/ _, @( v0 v: n: q. _2
1 _, f4 j3 C- O2 l9 c36 c, n/ k1 Y' H
4
; ~6 H) z+ }% z5
1 ?4 Z, x4 [# }60 m; G) S v0 {) E c( }
7; q" I4 \% A0 C5 F5 W
8
! E0 M3 R) g4 e! {7 |$ FExample3:取出单日值
. w' E/ S/ [: @! ^" G: ` a: O; p1 k# f; b t5 V( I7 e4 M9 P: u
s['2020-01-01']
/ `/ t o; U' ]) f3 kOut[52]: 1
" U* @# ^: D: m/ P" p6 P
& {' B: k# A$ M5 s6 E: A; us['20200101'] # 自动转换标准格式
- Q2 U/ J8 \. {" f5 N) }Out[53]: 10 H" Q! _5 x) R' ]; ~/ ~# C
17 {, p, o5 G* t1 l
2
8 T% z H8 {8 k, z- ^* K+ `3. r* w& w* A4 R) R( s, e
46 { g: {/ M4 n; _1 M
5
9 J( h! ?. f/ ~, IExample4:取出七月
, j, S& e4 O" X+ k
: Z9 h& w- W6 t( c( [s['2020-07'].head()/ ]; v: I }* p
Out[54]: 7 |4 A$ {- D3 A" C8 y
2020-07-01 0. N7 H9 s* S* |, ]* Y- s
2020-07-02 16 |7 @' w$ i Q
2020-07-03 0
8 e, E- R" q" a- R9 T2020-07-04 0
' @% v+ M) l9 @3 T# S4 n2020-07-05 08 R' {$ G/ d. G4 [) _9 h9 v3 {
Freq: D, dtype: int32
8 Q( `$ n+ E: F2 C9 k1
, F2 r; l8 }" X9 n9 R+ |2
# N8 C9 S* f$ K2 ~- j. ]3+ b4 {8 I8 u! y% j( m: j+ l
4- \( R8 v0 G% h1 ?& ? i
5
7 `6 j/ Q4 w7 v3 @6 a6 z" F v6
# g$ B3 Q( Y! l! q! y r. m7
; c- A& T4 }& ^$ L86 q' \+ G: j& b, w5 i
Example5:取出5月初至7月15日2 o; E- W& E' q2 ]: b8 p
& v* O/ O* Q! s. R- T8 f1 [7 I
s['2020-05':'2020-7-15'].head()
! D1 O) c; D' o: k; I. @Out[55]: 6 M+ @+ d+ \' d! [ @5 @
2020-05-01 02 k: i" J( @3 X
2020-05-02 1
0 t( q, |" E; E, }$ k2020-05-03 0
9 F& _2 S' _" Q0 r, d6 S" p0 e2020-05-04 1- K+ S! P+ n3 |6 b3 T# S/ t( [
2020-05-05 1
6 Y* J3 H/ q' V% [Freq: D, dtype: int32
. V( f; K7 n1 j( t8 ]2 R7 B) s$ ~) q
( |" S5 b7 e/ h! xs['2020-05':'2020-7-15'].tail()8 i' R- P* h2 t8 v3 i
Out[56]: & K5 f7 T; u+ X# N9 l
2020-07-11 0% \/ e0 r1 g% y$ g$ R& D# X
2020-07-12 04 x& j; w& y) ^0 `; d7 N* o
2020-07-13 1" T9 d9 E0 X! l2 E8 d9 g
2020-07-14 0
( Y/ _5 T) J) D6 r1 E- {; p2020-07-15 1
* l5 E9 k! ~: r3 YFreq: D, dtype: int32) Z0 c/ Q8 d7 Q% u8 G3 }
' t3 x J3 P3 B' E2 @7 R7 p( t
1
\% v2 k6 P- S' e2
' ^. z4 n9 ]8 n- i% S# C/ G! H3
& ~+ {" o# Q6 P- s' a, D2 L7 `/ ~! @4
/ I5 c# v1 y7 T6 _) O* Z52 G7 M' W1 Z, W1 E+ A
6( o6 x9 [. ~& _$ B* q3 q1 u
7 {# E% R( m4 c( H
8
+ z! R; f1 J& A9 F+ u9' f5 w& p9 Z' U' j1 k
10
3 `) E; _0 t: v# E11
! a# V- B. V! h126 Y- M* D+ s9 E- t9 ?! I
13
* U7 d' i; u9 j! O9 `; ~14
8 I! M: b' y# q7 y& x( c0 {15/ ]! Y& }5 i5 K" b; }
161 w* C$ u# Q) s' _. F
17# z$ H" L% |& F# V
10.3 时间差" A% c! e' [3 `9 y( D& X
10.3.1 Timedelta的生成
" @4 R$ U/ G+ L8 tpandas.Timedelta(value=<object object>, unit=None, **kwargs)
9 f0 w5 A' N2 f% F unit:字符串格式,默认 ‘ns’。如果输入是整数,则表示输入的单位。
0 X2 _& T5 |) r" M* d 可能的值有:
( f, ?2 r# V/ J1 ]4 q/ I, C
( C9 W, Q' u" z* Q4 ^4 X‘W’, ‘D’, ‘T’, ‘S’, ‘L’, ‘U’, or ‘N’
! a7 \5 X: j- Z‘days’ or ‘day’7 c7 _6 ~/ b! Q# F
‘hours’, ‘hour’, ‘hr’, or ‘h’
3 n3 Z/ S/ a# b* E‘minutes’, ‘minute’, ‘min’, or ‘m’5 k: B6 u2 S# x
‘seconds’, ‘second’, or ‘sec’; h4 O: D) a: V5 t2 t
毫秒‘milliseconds’, ‘millisecond’, ‘millis’, or ‘milli’
% x( z3 y1 w% r, E) p微秒‘microseconds’, ‘microsecond’, ‘micros’, or ‘micro’
4 q' z; L9 M( m$ E1 U- t纳秒 ‘nanoseconds’, ‘nanosecond’, ‘nanos’, ‘nano’, or ‘ns’.6 w# @. ?* w5 l F% ^
时间差可以理解为两个时间戳的差,可以通过pd.Timedelta来构造:
9 g/ j! x- u! L" |' T" Q, h7 [pd.Timestamp('20200102 08:00:00')-pd.Timestamp('20200101 07:35:00')5 r, F1 b" k j. v
Out[57]: Timedelta('1 days 00:25:00')( ]3 e$ R6 `0 b8 y' H
# z4 O2 u0 K( ~
pd.Timedelta(days=1, minutes=25) # 需要注意加s
! o) y8 {& g! y( x5 C& {5 KOut[58]: Timedelta('1 days 00:25:00')6 T+ O3 k* A0 ~0 d# X
8 O7 x/ ~+ g0 b" X
pd.Timedelta('1 days 25 minutes') # 字符串生成: ^8 F* M: t. a! R2 M4 I) p
Out[59]: Timedelta('1 days 00:25:00')
# _0 t* \9 i/ c1 K( g0 A; o; N) g, ~. J' k. f$ @" A7 U
pd.Timedelta(1, "d")
4 z% B2 b* e2 [Out[58]: Timedelta('1 days 00:00:00')2 @+ J- O+ `3 K" A7 A
1/ L3 r' M# }# Z- s5 v0 _" V
20 ?" } \( @! B7 H5 ~) k
3
+ F# Y2 _% e5 z( H* u8 X6 X. O4
* F4 Y Y; i8 Y# f. l M56 j" S, y' L) W$ @% e8 H
6! O& S# h" c. m% t# _
7
- @& U" L; D- g, Y3 n' } h }8: q2 H2 e, O7 Y8 l8 P) B% ?% J
9
) y3 p! }3 a w+ {10
. o' L4 S4 ^% F4 u" w2 p V* G( S11
8 O2 x6 Y# ^( b生成时间差序列的主要方式是 pd.to_timedelta ,其类型为 timedelta64[ns] :
* A1 L1 J: B# \8 {' }% es = pd.to_timedelta(df.Time_Record)
2 _, G0 r$ i* I6 @- x) s$ c. F$ Y9 x, x9 M8 C$ P3 ^
s.head()
" g q9 P, t/ i5 N$ { a0 COut[61]: & J" E: \$ k9 N
0 0 days 00:04:34
9 @, e3 a" f# ]/ l+ C1 0 days 00:04:204 \; h4 ]( d; \0 N9 ?
2 0 days 00:05:22
$ q: S" U' l# {! m3 0 days 00:04:08- C5 T0 K& s6 V
4 0 days 00:05:22& s! Q* k2 X Y# p+ W7 f& O" E; K
Name: Time_Record, dtype: timedelta64[ns]
+ q( X/ \7 f. D, W1 _( R: y& u! M0 m8 w: z. h
2
6 V4 F2 A0 Q4 o' Q( ~$ \3, Y* k( G# @! c3 h
4+ ~+ v3 Q: p/ @& b9 e5 B
5: t6 M( X0 Q+ X. N4 o; q4 ]& @! f+ T! {
6
% n! B9 o, ~+ o! a9 g% p7
5 t! h3 t0 x+ Q8+ U8 q$ L0 m& m! m+ m" ~
97 h: v5 L' w$ U3 l
10
/ Y! k% q( U1 ~与date_range一样,时间差序列也可以用timedelta_range来生成,它们两者具有一致的参数:
" m. q( z3 F1 P! t$ Q4 p+ Epd.timedelta_range('0s', '1000s', freq='6min')3 {% H6 ?1 ]- |# } A/ ?
Out[62]: TimedeltaIndex(['0 days 00:00:00', '0 days 00:06:00', '0 days 00:12:00'], dtype='timedelta64[ns]', freq='6T')( ~3 V9 p- w0 X( ~# U- n: P) z
; r! @) {, T4 S1 x1 \1 Y% f2 C5 hpd.timedelta_range('0s', '1000s', periods=3)
9 S" k. g; [( K5 t9 i1 UOut[63]: TimedeltaIndex(['0 days 00:00:00', '0 days 00:08:20', '0 days 00:16:40'], dtype='timedelta64[ns]', freq=None)2 d: w1 }6 Y+ ^3 F9 o! J
13 @# q+ ?+ E" ]* }4 ]
2
6 s' I# q: M1 q$ p3
, k) n5 E6 A5 u4
4 e! j8 o/ ?4 L5 S0 P1 U# ]# Y54 h: S0 e' N/ q) h5 q1 a1 v R$ l
对于Timedelta序列,同样也定义了dt对象,上面主要定义了的属性包括days, seconds, mircroseconds(毫秒), nanoseconds(纳秒),它们分别返回了对应的时间差特征。需要注意的是,这里的seconds不是指单纯的秒,而是对天数取余后剩余的秒数:
+ \3 g3 c% }, I z( Cs.dt.seconds.head(). v2 ]; ~/ I; {4 R- u& c
Out[64]:
4 P$ g+ h- S: E1 M0 274
& z5 \0 v( ~" E5 |1 v# ^ }$ i' N1 260
: g$ g/ b# J6 {9 D( D" K2 322- O L. A. N9 D7 v
3 248
" x4 R: M% L+ z4 b" J' i: J4 322
# O5 B5 E" @( z" k; j- s; C! G# VName: Time_Record, dtype: int64$ t- }0 }2 D, n# g/ ?
14 \5 |5 G) R/ W( a# o8 ?
2
/ A! S1 \: q) D1 K! O3 k( _3
, D/ L/ @0 N3 T' i. x. b" d4
4 R* s3 [* ?0 R3 n52 @9 X% z. B% A
6
5 \/ g. u+ O9 c( _7 w v# g7+ l% A' J ^7 Z. Z
88 S$ V4 s1 v4 a' V' g
如果不想对天数取余而直接对应秒数,可以使用total_seconds" s& E7 \5 P8 v3 g: O2 L
0 t9 k. r% N; Q9 x7 N$ B
s.dt.total_seconds().head(): U1 F" ]3 H+ I6 x, a$ D
Out[65]: 8 K; g. R1 _ F: x! j( {
0 274.0
u+ L1 \* g* B0 |+ M Z1 260.0* J: h1 n: {# c, c v% ]8 Q
2 322.0
+ O$ C, ?6 y: J/ p6 r1 X3 248.0
7 z: i, Q' [& l _# Z4 322.0; c* q8 z" z* o9 v
Name: Time_Record, dtype: float64
9 b# S5 p: R" s7 B/ E; I; Y1! o5 Z3 t4 f9 K" t6 S/ ~1 C/ E
2
$ n: V$ N( K/ T. ^/ D3
6 x7 S+ L: p1 q; i5 k% _- V4
( O) P- e" D0 O) y50 l* g" t& V, j1 y: J. K- E- `) T
6
* x6 d: b8 W7 |1 P7( C- ~1 d, d, C8 g" H Z
86 j( ?" `4 U/ I$ V1 d9 G& ^+ G
与时间戳序列类似,取整函数也是可以在dt对象上使用的:9 v% P5 t H6 ]
" Y6 P# k4 i" C3 F
pd.to_timedelta(df.Time_Record).dt.round('min').head()) K/ k1 ~- s7 I# }, P
Out[66]:
4 s9 ^+ T" N. N% m a0 0 days 00:05:00
: |$ N1 {4 D9 K w# C1 0 days 00:04:007 p$ Z) k% S) G) y0 |! n, F
2 0 days 00:05:00
8 ]4 S% | o$ K ]) ^ \! i3 0 days 00:04:00
# g4 A' I; l2 w, r9 |4 0 days 00:05:005 ~" o a8 M' X& ?/ u! Q% `) h
Name: Time_Record, dtype: timedelta64[ns]5 p/ n& O# k2 W. Y9 F
1 t1 X+ C' z' D N1 A( ~" z2 _/ g
2
& { s' `( _0 n9 H( W* D6 |+ ?+ @3+ R* ~6 e5 n7 d; v
4
( P, |' [5 S. a# A, D59 R+ U- _ }9 s5 S: u) c
6' \1 A1 U. O" |, O; i3 q6 u C
7
" Y7 Z" s0 O' U, r7 o$ T$ u& n$ ~8/ O! I. H. T. v/ k+ g8 `; I
10.2.2 Timedelta的运算4 w* J* L! }; d1 q
单个时间差的常用运算,有三类:与标量的乘法运算、与时间戳的加减法运算、与时间差的加减法与除法运算:5 w4 p$ L; F8 | K& j3 V! r
td1 = pd.Timedelta(days=1)2 l' e0 q; _! O, |( G7 ^
td2 = pd.Timedelta(days=3)3 W9 o& a; i5 \
ts = pd.Timestamp('20200101')4 q0 W4 ]' e3 Y
7 K h& e3 W4 `. S2 o) N3 x
td1 * 2# f$ _* {3 Z) l! l! j
Out[70]: Timedelta('2 days 00:00:00')) `4 T4 _! n c' f( d& ~
% w7 y0 B) n3 M2 S$ g, k" Z5 q9 i8 u$ N
td2 - td1
y. M: D; V1 G4 t3 ZOut[71]: Timedelta('2 days 00:00:00')+ @1 k# P( W3 ~+ c2 L! z
r) E6 e0 C' h4 {ts + td1
4 z* N* v7 N$ t6 `Out[72]: Timestamp('2020-01-02 00:00:00')% E+ z; S, k. N3 A F$ Q1 W0 @8 T
; ~# t7 K9 w( U6 H+ dts - td1
5 J* U# c; R. J, B4 BOut[73]: Timestamp('2019-12-31 00:00:00')
8 c) ]6 X# q! k' ~( u; C# z( F1; I& t2 d2 V3 J1 S
2
^1 f1 W, t$ w. T+ j3
) h6 d# Y* L$ P8 S4 p6 ^# \4 o. x( f! W2 U6 Z
56 n3 t8 [8 ^# P {! l. _
6
; B& z$ j; v4 g/ N7, d2 V+ F* g6 J, ^5 X* c; h
8
c/ B! d( p9 y% x; N. ~9
0 n% _ X; u) t$ d% V7 {10
7 ^8 S, t, e6 ]; q8 b a6 ?) ?11. c- t7 J- ]8 F2 v% y: j
12( z7 C& I* c7 L7 M0 |3 F
13* C! R/ V B* u/ B& e
14
- s1 a3 j& X6 e" S6 F15
/ [- D' q9 m/ }" Q4 p: S0 R时间差的序列的运算,和上面方法相同:3 |6 x: b+ L6 K) X
td1 = pd.timedelta_range(start='1 days', periods=5) c+ E g" t- J8 [) ^+ }
td2 = pd.timedelta_range(start='12 hours',
- M9 I2 m$ Q' ?! q1 F freq='2H',7 I, [7 s$ [# r% @ k' L
periods=5)
& [3 n3 E* W! w7 n1 e1 P: m7 `6 Kts = pd.date_range('20200101', '20200105')7 e0 f4 w) }8 Z3 L9 r* U
td1,td2,ts8 a, Z8 h5 ?" V+ B3 S3 \4 l2 K( p
. E; c# @% I7 {; b+ w" i- ATimedeltaIndex(['1 days', '2 days', '3 days', '4 days', '5 days'], dtype='timedelta64[ns]', freq='D')
& R7 o- Q$ u3 r- V$ q6 M- F9 I7 [3 ITimedeltaIndex(['0 days 12:00:00', '0 days 14:00:00', '0 days 16:00:00',
; Y, R4 Y- B; T& m4 Y2 m '0 days 18:00:00', '0 days 20:00:00'], dtype='timedelta64[ns]', freq='2H')4 z& F, w" [& S. N: D5 @& u
DatetimeIndex(['2020-01-01', '2020-01-02', '2020-01-03', '2020-01-04',% i% C' }; h# v. y. h
'2020-01-05'],8 D' v& k& G7 M
dtype='datetime64[ns]', freq='D')
9 ^6 R" _* n1 l% _. e1 }1
/ A" S( b b' Y* N, K24 x! c' S( Z; m1 n5 C$ K8 w- J
3
7 ~; N! L! X& M) `& @4
* X+ W1 ]7 f- [9 F8 _5
7 Z9 \ B$ {; \- x& B66 k2 N+ O+ _: h8 L2 e0 b' v+ }1 p
7
' `) h. Z& v& w0 s, r0 _! \8
# h* r7 K1 L: v0 \& _9
$ Q* G0 d+ B) I0 _) i( Z3 {10* f- S# E, B0 @* y0 l/ \
11. a; b1 N" l8 j) h' [! {* f8 Z5 `
12
8 f8 d: |0 z4 m% ?- K B13" \5 z' }. j( V/ \
td1 * 5$ q* e: T* ?) b3 N' D( O
Out[77]: TimedeltaIndex(['5 days', '10 days', '15 days', '20 days', '25 days'], dtype='timedelta64[ns]', freq='5D')
- o5 ?5 v! |4 g( ?& x# {
3 X, B( Z2 b0 D' q! Q$ c& s/ l, F# ~td1 * pd.Series(list(range(5))) # 逐个相乘/ m' t: `; r. z/ q0 e4 m
Out[78]:
2 Z9 ?, g0 a( i! T! W e. i9 e( }0 0 days
% I1 }% G$ f* p1 2 days( _2 l+ } q& D0 j& ]- v
2 6 days
# s, [5 i6 G# t2 Q/ z- }, T3 12 days
4 \4 r6 b) @0 s b( k4 20 days; j C) ~; h. i( |- X/ E
dtype: timedelta64[ns]+ E% n' w( ^3 n* t- |: h
- D7 ]% F! Z/ btd1 - td2
1 `0 z' y3 N' _4 T6 |' t4 M, A( ^Out[79]:
$ R& K( x' g L# O, c! [7 ITimedeltaIndex(['0 days 12:00:00', '1 days 10:00:00', '2 days 08:00:00',
) j3 x1 l/ O' }+ a. w6 L '3 days 06:00:00', '4 days 04:00:00'],
, l" w+ @. Q" ?5 j. e" N, C dtype='timedelta64[ns]', freq=None)
8 Y) v" E& H+ |& |* D z' r9 Y
4 j" E/ E9 K v9 X }' Std1 + pd.Timestamp('20200101')
+ C1 m/ ~' m% f, zOut[80]:
% A' E. M4 N0 [7 V Y& F v gDatetimeIndex(['2020-01-02', '2020-01-03', '2020-01-04', '2020-01-05',
; d0 ~5 [8 Z: _ '2020-01-06'],dtype='datetime64[ns]', freq='D')
+ w1 s* n3 k+ e: }$ z( d: z0 _! L& t0 J$ V
td1 + ts # 逐个相加
4 @" Z: D( ^: @( n. a/ f- B, C/ K) qOut[81]:
2 Q* }/ t$ i" ~- A' e/ E8 ]8 c' \! FDatetimeIndex(['2020-01-02', '2020-01-04', '2020-01-06', '2020-01-08'," v. j& |; F. {5 A- J& ~% W: z
'2020-01-10'],
; p; S% w/ a# r' `7 p* M5 e f dtype='datetime64[ns]', freq=None)& T+ S* r* `; S% u% ?2 k7 c
( p" j3 S$ \, \2 d1 M
1
9 R# v8 y& `- N; T2
6 i. e( \* c: Q% R- p3
# P4 f6 U& N: h; Z6 W( d2 ~% H4; C/ ?* y5 p% {9 E8 _# y( c
5
9 M1 l2 M/ T/ E' m6 X7 d% O6
9 X: v( @% ]( K" w! k7
- u6 g \9 D; z, c8 i* y8. Y* V- [1 u3 h2 P( w0 j
9
- r& O% \ |( G$ C+ d10: L' S7 S1 d! K$ ]0 k' g8 I! B
11
d5 V7 k% e2 o% O: y- Y12
0 D& _& o C+ e9 ]13$ y# h+ H- n/ ], n- q7 D( Y# T7 h
14
5 d \4 l; h# M( n8 ]15
, }0 R* ~' [7 B) s- V168 Q3 [6 i1 z! ]3 I6 m# Q) x
17% s5 p+ u4 a6 ]9 u7 }1 M+ i3 \4 D3 S0 F
18
9 f e5 e- A: B5 K: m19
2 ]# o0 `1 x/ G3 v20$ i, t( X! ?9 p' I, o- @( ]
21
7 ^0 @9 S5 N8 b0 j0 B: i221 O; m" @8 H0 k( g7 Q; \
23 y3 d7 m" l( f3 V1 r& F1 O9 z ?- e" c
24
: e l8 M) k* ?" {$ Q25
6 v, \( S" X, z+ p( d3 i268 i: O( G) k8 z* g5 Q( t4 x, ?
27
( A3 k9 c$ c- M) x28
- x) a9 c6 O2 ]7 q10.4 日期偏置
; J0 F u. n, _+ C- g10.4.1 Offset对象
& {! O8 W2 a% v$ s; T% K 日期偏置是一种和日历相关的特殊时间差,例如回到第一节中的两个问题:如何求2020年9月第一个周一的日期,以及如何求2020年9月7日后的第30个工作日是哪一天。
8 J- j, }! l6 X2 }* Q% C2 k0 x% K, U, u) `8 J( I
DateOffset 类有10个属性,假设s=pd.offsets.WeekOfMonth(week=0,weekday=0),则:
8 K& y3 u9 |1 S5 x- L, E2 f9 d: J4 z6 S) h# Y
s.base:<WeekOfMonth: week=0, weekday=0>,返回 n=1 且所有其他属性一样的副本3 l$ x- ~+ w1 y( z9 ]9 F9 o
s.kwds:{‘week’: 0, ‘weekday’: 0}1 d2 O% ^% o; X( ^3 G: N
s.wek/s.weekday:顾名思义, I4 I- z/ U# z$ |! F
有14个方法,包括:
( @5 R, ]6 W& T' i; q4 K% ?1 \
# q/ n- k D" [- f/ wDateOffset.is_month_start、DateOffset.is_month_end、DateOffset.is_quarter_start、DateOffset.is_quarter_end、DateOffset.is_year_start、DateOffset.is_year_end等等。
* E1 p; d6 G- V3 Lpandas.tseries.offsets.WeekOfMonth(week,weekday):描述每月的日期,例如“每月第二周的星期二”。* e- ]: N: D/ Z- J( Y: |1 U
4 q. ]2 B! Z$ W( f有两个参数:0 j: I: k% p1 K; ~3 c7 M0 t; v
week:整型,表示一个月的第几周。例如 0 是一个月的第 1 周,1 是第 2 周,以此类推。 x$ M# t9 x* N! X |+ _" B+ S
weekday:整型,取值为[0,1,…6],表示周一到周日,默认取值为0(星期一)
" k8 q! M; F! O' D l6 ]* h& r6 e' Y7 cpandas.tseries.offsets.BusinessDay(n):相当于pd.offsets.BDay(n),DateOffset 子类,表示可能的 n 个工作日。
& B1 `2 F# L1 V* ^) Y2 S5 J7 d. D* z& q! M7 Y g, }/ }
pd.Timestamp('20200831') + pd.offsets.WeekOfMonth(week=0,weekday=0)
; Y& O8 t2 R. G1 rOut[82]: Timestamp('2020-09-07 00:00:00')
7 F+ \0 j, v" g4 B
[8 O9 w4 ^# J5 c2 S% fpd.Timestamp('20200907') + pd.offsets.BDay(30)
" ?5 S4 @$ f" Y) a& N9 @# AOut[83]: Timestamp('2020-10-19 00:00:00')
8 @% `, I1 j. {7 m1
% r! b' v. A! ?8 n' s2
5 h( N c" i( }7 b3
9 Q/ w* ~& z7 O7 Z& } l" K4
1 z: Y/ Y1 R1 u, m% w% a5
$ q" J( b" {: w2 F' X3 t/ @5 ?) F5 l 从上面的例子中可以看到,Offset对象在pd.offsets中被定义。当使用+时获取离其最近的下一个日期,当使用-时获取离其最近的上一个日期:& d5 q2 k0 m1 u3 h
1 \8 m% f% u+ {pd.Timestamp('20200831') - pd.offsets.WeekOfMonth(week=0,weekday=0)" X) `( n' w: \' R
Out[84]: Timestamp('2020-08-03 00:00:00')* p' c, x$ Y* v; K$ c4 N) s' J+ k
- g( F5 u p5 b3 Ypd.Timestamp('20200907') - pd.offsets.BDay(30)
- I9 s/ u! q- ^6 {- R) Y) \Out[85]: Timestamp('2020-07-27 00:00:00')
/ Q- u) c$ v: \- B
; z. b# v! T @- Z g% y' L, Xpd.Timestamp('20200907') + pd.offsets.MonthEnd()
% t' Z1 `6 k" w, g: zOut[86]: Timestamp('2020-09-30 00:00:00')3 k# t6 W A5 x- c6 t3 @
15 \4 o% R. @6 P. P
2
' P5 X: L6 n6 @& P( c3
) A8 x- Q1 t; e5 ?& V w7 [; u4
$ O8 n4 F3 |8 O% a4 ^; e/ T5
& |0 Y" a K: J+ V5 j5 E! S6
6 O: C+ ?3 h8 ]8 G& R+ x- ?7' f' Z Z+ e8 \% b6 E
8
1 N& Z+ U8 B0 V 常用的日期偏置如下可以查阅这里的DateOffset 文档描述。在文档罗列的Offset中,需要介绍一个特殊的Offset对象CDay。CDay 或 CustomBusinessDay 类提供了一个参数化的 BusinessDay 类,可用于创建自定义的工作日日历,该日历说明当地假期和当地周末惯例。
( s6 V' [/ M- P" M 其中的holidays, weekmask参数能够分别对自定义的日期和星期进行过滤,前者传入了需要过滤的日期列表,后者传入的是三个字母的星期缩写构成的星期字符串,其作用是只保留字符串中出现的星期:3 X, U1 z, K& ^! _3 R& Y: z# Q e
6 K" x6 l( b5 I0 Dmy_filter = pd.offsets.CDay(n=1,weekmask='Wed Fri',holidays=['20200109'])) C" d/ h2 i3 C' f4 G
dr = pd.date_range('20200108', '20200111')
$ Q" _" u! A% [7 Q2 {& _9 K; a$ E* [# ^6 |; l) C; Q6 D6 c- O
dr.to_series().dt.dayofweek/ y/ ]. d5 G( S0 {5 u$ s. K6 Q" p
Out[89]: % @4 c9 R+ U. D" G3 N) W
2020-01-08 2
7 E6 N0 J! v. }2020-01-09 3
) d" d0 U6 S; n2020-01-10 4
1 j2 ?! x7 S3 L2020-01-11 5
0 d# L5 M! m0 ~( w3 AFreq: D, dtype: int64
8 y" M. H# d8 \- C2 F
' H2 `$ a1 L, ?. D0 a[i + my_filter for i in dr]) c1 X- l+ l2 z" Q* x$ _# k
Out[90]:
: K! J+ G* N9 ?5 X[Timestamp('2020-01-10 00:00:00'),
& e& R5 Z' J' x4 F0 k- n Timestamp('2020-01-10 00:00:00'),
4 W& n# j/ p5 F Timestamp('2020-01-15 00:00:00'),
+ l" q4 x/ {, e) L/ u Timestamp('2020-01-15 00:00:00')]& x) m- T/ X2 S! H2 A3 E: Y
# x4 j5 Y* g; d0 {& I$ P
1
+ i: d+ [2 O" [ x/ \29 l# Z- M- R! s" ]4 q% P
36 y* U3 A) N; t" T% h# w8 T- b
4; D, v# N0 \4 U ~7 F2 M: T
5
* v& W% H( X$ w6
( b9 G# N$ T& o; |" U7
: N8 J' c# P) s# U" j3 x+ W' D8
, A [% O5 ^" V6 M5 j9
, H* d. @- S' M" W3 m x* a, }10) k# x2 Q3 f5 R$ j' R: p
11
/ \" M3 x# n$ \1 [8 k# g I121 B5 L( }; d+ ^. k4 \) U0 [
139 q5 h5 ?3 w0 A1 B4 X* N2 o4 O' ?
140 v3 Y1 y8 B+ o& K
159 p: d: ?- l- g7 V6 X. z
16
+ ~8 E! ]( y" V, D$ @- _17& M, S0 M6 v% W
上面的例子中,n表示增加一天CDay,dr中的第一天为20200108,但由于下一天20200109被排除了,并且20200110是合法的周五,因此转为20200110,其他后面的日期处理类似。, [% j9 H; m W; l3 S
' d5 u( `2 s" H7 `% N# T【CAUTION】不要使用部分Offset; I+ E8 Y3 C# W }
在当前版本下由于一些 bug ,不要使用 Day 级别以下的 Offset 对象,比如 Hour, Second 等,请使用对应的 Timedelta 对象来代替。4 S2 x' v1 Y* C: J$ `- t
l8 o( L3 d# U3 h( z- R
10.4.2 偏置字符串
, l5 Q8 T% C6 v/ O a; I 前面提到了关于date_range的freq取值可用Offset对象,同时在pandas中几乎每一个Offset对象绑定了日期偏置字符串(frequencies strings/offset aliases),可以指定Offset对应的字符串来替代使用。下面举一些常见的例子。( Q1 `9 B8 p5 E8 B
6 N4 \ m& s! l& ?. W8 D* x0 }" P
Offset aliases:pd.date_range函数中的freq参数,为常见时间序列频率提供了许多字符串别名。 也称为偏移别名Offset aliases。偏移别名列表点此参看(大概27个)。
4 H. A8 u+ ^1 j( x, v6 m8 O8 r+ H1 M7 F& r! T) d, G( G
pd.date_range('20200101','20200331', freq='MS') # 月初
! _$ T" G0 ~) n1 a2 xOut[91]: DatetimeIndex(['2020-01-01', '2020-02-01', '2020-03-01'], dtype='datetime64[ns]', freq='MS')) b* K+ E1 G9 _8 h7 T" a) g) v; X
$ A8 P/ T! `0 M1 }7 W' \( C3 { o
pd.date_range('20200101','20200331', freq='M') # 月末
5 G: |5 R/ r l" z4 D3 h8 P" hOut[92]: DatetimeIndex(['2020-01-31', '2020-02-29', '2020-03-31'], dtype='datetime64[ns]', freq='M')0 z j' Z ?8 V/ [' d! i5 M
# }5 M, q) X# {# _6 ^: M7 n' j
pd.date_range('20200101','20200110', freq='B') # 工作日" O: ]' Q+ b3 _
Out[93]: 0 u1 T* W n9 \' c
DatetimeIndex(['2020-01-01', '2020-01-02', '2020-01-03', '2020-01-06',( l' G7 H7 z* e' _
'2020-01-07', '2020-01-08', '2020-01-09', '2020-01-10'],6 K( H0 o! ~+ R8 y" E6 V
dtype='datetime64[ns]', freq='B')
$ t" |2 ?. Y; a: z: @) z- Y& `6 }2 V+ p
pd.date_range('20200101','20200201', freq='W-MON') # 周一7 I: E7 h5 T, q$ v! M
Out[94]: DatetimeIndex(['2020-01-06', '2020-01-13', '2020-01-20', '2020-01-27'], dtype='datetime64[ns]', freq='W-MON')
8 x S" H! G2 i
# {$ j1 P% C) x$ H$ R! z; D4 xpd.date_range('20200101','20200201',
4 p$ [& D" E; }, E! F freq='WOM-1MON') # 每月第一个周一
; _- O7 D) K9 c2 t- a3 s, }' m: A3 u) T) @6 _) ?1 V- M" N% L0 l
Out[95]: DatetimeIndex(['2020-01-06'], dtype='datetime64[ns]', freq='WOM-1MON')3 R9 Q* ~) t6 {9 I; t
+ ? g' s) s% w( d( J' m1
: |. n/ M9 }# m8 o, @0 B+ M2
+ t8 o5 Y" t- X- y! M' h2 o3) S7 T3 s+ N& H, F, T/ U
4
" Q4 E. Q+ O$ M! X' p+ q4 |5 ~3 r4 w3 [8 v$ X% T# {1 ~
6
7 V9 ?* `& b8 }2 ~, l5 a7
( n0 K: |2 r$ {- Q8
) l; d: [) X- o# O9( h: |) s+ V/ ?1 C, c: A5 ^7 \- y
10& Y% B$ |3 e l$ N4 t7 L
11
# V$ K, @8 C3 i* p12
& g$ F' c) L' c3 b# i13
$ C- o( Q, q8 a6 Q3 s" z14
. \5 w' K; t* N/ J9 m& E+ a: ?. j. h7 l15( q9 s, L( f k* x4 }- v
16: D# I1 y% I: g' t
17
" L8 B' P7 U) P( P7 W18! U, ?/ {% l- Q. ]
19
3 C0 s. u* h A上面的这些字符串,等价于使用如下的 Offset 对象:
& x5 P5 ^4 Y r' {) x1 l1 x2 p$ _) N" z. r( y
pd.date_range('20200101','20200331',
3 J5 i+ c9 Z# G, p7 r4 U' ^0 P4 } freq=pd.offsets.MonthBegin()): _7 n0 Y9 Q. y- ?
$ ?6 \2 v+ d- e- p% E H+ YOut[96]: DatetimeIndex(['2020-01-01', '2020-02-01', '2020-03-01'], dtype='datetime64[ns]', freq='MS')' \5 t8 q) J5 g- q2 |
9 l- m0 x; v2 D/ e1 K$ [
pd.date_range('20200101','20200331',( y' l' p* `7 W% M
freq=pd.offsets.MonthEnd())9 Y4 S& {. V8 a0 S
: H3 D7 O1 T, D8 m- o& S& n
Out[97]: DatetimeIndex(['2020-01-31', '2020-02-29', '2020-03-31'], dtype='datetime64[ns]', freq='M')
, a/ a4 W9 G4 b) d) w
' S. c' C4 P. `4 Hpd.date_range('20200101','20200110', freq=pd.offsets.BDay())
& e- ]0 M- T, sOut[98]:
: Q6 V/ a! `; v) ]0 q3 eDatetimeIndex(['2020-01-01', '2020-01-02', '2020-01-03', '2020-01-06',
5 b# I' R' v7 s8 v: i '2020-01-07', '2020-01-08', '2020-01-09', '2020-01-10'],
( p7 r$ |3 h7 H7 A0 P dtype='datetime64[ns]', freq='B')+ V& A8 `( {) F: [1 {% n4 e
3 Y; v! {5 `! Q" y" N7 J0 ^4 v+ Fpd.date_range('20200101','20200201',
; J% A+ s6 t' q7 k" p# ` freq=pd.offsets.CDay(weekmask='Mon'))
5 O& S) v; ~2 x# X$ _( k! i; j3 O) Y( Z5 J
Out[99]: DatetimeIndex(['2020-01-06', '2020-01-13', '2020-01-20', '2020-01-27'], dtype='datetime64[ns]', freq='C')7 u$ I( j+ W2 }
( Y4 ^: E0 ]4 G+ e
pd.date_range('20200101','20200201',# \8 q( [! a u4 D. X, C; ~8 E
freq=pd.offsets.WeekOfMonth(week=0,weekday=0))
% l! n& k- f! r& e3 h; n0 A
) B p$ G0 n8 W. g XOut[100]: DatetimeIndex(['2020-01-06'], dtype='datetime64[ns]', freq='WOM-1MON') p. l1 h' t4 l4 {
! F8 L% ^( J7 v7 r# a5 l1
7 D7 @# Y$ d1 S& V2
! ]0 G2 C# n/ o( `32 f; `) g- z' ^! L1 Z
4
' O! S/ I+ D% {5% {; `" l1 H+ d. a0 H8 e
6
, A1 m" T+ e0 x7: R1 V, ?" N8 Y
8
( N: r z) N7 H$ B8 \9
: k. D, o3 O' o& Z* M10
0 c) C- o4 s2 T4 Y0 X: B11
( `. X7 Z: W% S. d. n# E s; |12
1 }4 F3 I. F t, c$ W9 T2 z13
% y: |0 C- g5 b14: L5 e R! T- P5 K) X
15
7 t& f* u$ d" ~) P2 }16; M. }& @' b2 K+ I+ |1 [, G" H `
17( \" B2 `5 Y% A3 Z1 y \
183 o, A* G8 Q' G) u3 x% f, f( |
19
T: ^" k& i+ z9 ?20" @. h j* D. X' W
21
' m' U# Q4 N" }% v J5 _22
1 P. M/ O; r# T" R' d23
! E4 K# x) k+ ~$ F, ]0 {+ Y1 O% t24, C" U. O2 @9 T1 l9 N+ H
25
7 e8 W$ G; s' d' S2 q( k& z9 c【CAUTION】关于时区问题的说明
3 x `/ J) i! x9 C+ K1 y3 } 各类时间对象的开发,除了使用python内置的datetime模块,pandas还利用了dateutil模块,很大一部分是为了处理时区问题。总所周知,我国是没有夏令时调整时间一说的,但有些国家会有这种做法,导致了相对而言一天里可能会有23/24/25个小时,也就是relativedelta,这使得Offset对象和Timedelta对象有了对同一问题处理产生不同结果的现象,其中的规则也较为复杂,官方文档的写法存在部分描述错误,并且难以对描述做出统一修正,因为牵涉到了Offset相关的很多组件。因此,本教程完全不考虑时区处理,如果对时区处理的时间偏置有兴趣了解讨论,可以联系我或者参见这里的讨论。# |4 T9 H; t( n: T* d* N6 e( r
7 e% i; u& j# ^& ? u( m% o& f
10.5、时序中的滑窗与分组
0 t+ q* l% i& f3 Y/ ]10.5.1 滑动窗口) E6 G2 S7 M o/ I
所谓时序的滑窗函数,即把滑动窗口windows用freq关键词代替,下面给出一个具体的应用案例:在股票市场中有一个指标为BOLL指标,它由中轨线、上轨线、下轨线这三根线构成,具体的计算方法分别是N日均值线、N日均值加两倍N日标准差线、N日均值减两倍N日标准差线。利用rolling对象计算N=30的BOLL指标可以如下写出:' B+ s) C: D5 Y1 A3 v
1 A. S" r' D( x6 j1 x8 \" d! Kimport matplotlib.pyplot as plt
5 a. O/ ^9 X/ E% |6 Tidx = pd.date_range('20200101', '20201231', freq='B')
9 n2 B( d/ ], W4 ~; S+ cnp.random.seed(2020)4 x" c/ U! J! d1 `% h. t5 E
& S# ~; i9 N+ Y7 M
data = np.random.randint(-1,2,len(idx)).cumsum() # 随机游动构造模拟序列,cumsum表示累加" T7 c4 v, j$ G6 R: J5 z
s = pd.Series(data,index=idx)+ p9 b0 E" S# K$ j! R0 c
s.head()" T- T: N- h& e4 ~7 X3 o
Out[106]: . y8 R- e# {4 f3 V
2020-01-01 -1
6 n5 r* c! e1 _/ M& a P* e2020-01-02 -2
* H( ]* _2 m' ?/ ^0 I n5 g2020-01-03 -1
2 T3 n$ c" f9 H2020-01-06 -1
% h/ g$ ?, X: A( e2020-01-07 -2
6 E7 K) G0 M# J5 e0 c; ]5 {" PFreq: B, dtype: int32
+ d$ U: x: Q0 B, M$ Y: Kr = s.rolling('30D')# rolling可以指定freq或者offset对象
% F0 G+ B: Q# G/ x9 A( @& ?
_9 N |4 b3 y/ x+ w7 `6 a* d Nplt.plot(s) # 蓝色线0 h# k U; r' h1 c& U W# v u/ q1 Z
Out[108]: [<matplotlib.lines.Line2D at 0x2116d887eb0>]5 B5 _, @- i: k" J$ A8 x3 c7 } e& h
plt.title('BOLL LINES')8 G2 X) B7 x* N$ b# _+ I( y
Out[109]: Text(0.5, 1.0, 'BOLL LINES')
; }" `: f3 T0 l) z/ i5 r! i7 q; z) H3 T+ n$ i4 T8 Y9 l
plt.plot(r.mean()) #橙色线( q# G8 t$ C) B/ W& L. S
Out[110]: [<matplotlib.lines.Line2D at 0x2116d8eeb80>], ~% P: W2 z( w$ |
$ c" l, z* [- J9 R' w2 w
plt.plot(r.mean()+r.std()*2) # 绿色线2 p/ e8 Y0 [* f( M
Out[111]: [<matplotlib.lines.Line2D at 0x2116d87efa0>]
; [: u) K8 u3 k5 T. s. U) O; i1 G! W7 ~5 i
plt.plot(r.mean()-r.std()*2) # 红色线
/ n2 A v: |$ J) ] FOut[112]: [<matplotlib.lines.Line2D at 0x2116d90d2e0>]
0 O& X: ]. {4 L y
. Q3 U: e9 y8 P% ]) S1
# N+ y$ N' G! N" r. H2
4 ^3 U: x5 @8 e* M2 j! ^2 C3
1 O2 r& ?! w( y0 S5 e4' X; s8 O; Z0 k; m: B6 d# o! v
5" l# h, k5 O8 \
60 u* F* h$ Q" ^6 c8 V4 n
7# n' W8 R; r3 [+ F
8
- w( C$ V; `5 x* D. [) T/ S6 ^9+ l9 V u; V) U) w
105 `- @$ g7 G+ ?# ~1 l: ?
11( P( \* f( r' @) F2 N
12 y% x1 D8 W' s: I3 Q; U
13* ]6 w+ q8 v2 y0 L+ B% A
14
$ U! _6 o y$ G" a3 K, E6 {15
( g! n+ W V1 b. g, V6 i7 D5 h" ~16
. r9 e0 r% f; x% R% H17
$ I6 ]5 l4 [5 P0 t1 T o180 M) S! k) l2 s' b: X
19- k& p- s( B+ e+ Q
20, c4 A& {# a* k% } K: J! E5 m
21* E( P3 R `* L. f3 {# z3 x
22; x, V- u' h6 b
23
8 l" S0 O( m# F* z k; n24 |3 s2 G" E, @0 u
25* q5 {* [7 o) Z$ u! G) Y2 F6 h
26. N$ g) a! `- h8 S; _+ Z% O
27
( j. I2 j$ |' h; q5 D% J9 N* L; f6 y8 U285 K3 `1 _& J; u6 ]" z) ~5 ?' m
29
2 G; z& V+ z5 p7 [5 B
. s* k8 p4 _ _8 w 这里需要注意的是,pandas没有实现非固定采样频率的时间序列滑窗,及此时无法通过传入freq字段来得到滑窗结果。例如统计近7个工作日的交易总额。此时可以通过传入多个函数的组合来实现此功能。
" M+ i/ J5 L& n8 f/ q; f: V7 X( f' ^ 首先选出所有工作日,接着用普通滑窗进行7日滑窗加和,最后用reindex()恢复索引,对于双休日使用前一个工作日的结果进行填充。1 i. {7 q# I) s5 [
8 Z/ C0 [5 d, W. {. \
select_bday=s[~s.index.to_series().dt.dayofweek.isin([5,6])]
7 Q* S8 J5 A. Bbday_sum=select_bday.rolling(7,min_periods=1).sum()+ ]; k$ Z+ W$ r
result=bday_sum.reindex().ffill()+ I; x& ]5 x5 B+ u: S" c
result) o! g) K3 P% [
- l/ F. o/ U9 L: B2020-01-01 -1.00 t: I; U4 z& |' f! p. q( p
2020-01-02 -3.0. O# J. K' n& N
2020-01-03 -4.04 e5 g* p1 R. Z
2020-01-06 -5.06 X- z$ V" x3 S. n
2020-01-07 -7.0. V3 y+ w1 K: B" Q9 l8 h
... 1 n# ^! [; z0 E0 ~
2020-12-25 136.0
/ X8 E$ _6 d! K4 z2020-12-28 133.0$ I8 {2 m4 l2 J
2020-12-29 131.0
" }$ U, ] |7 m. V' G+ A" w2020-12-30 130.0
, q( {. S# B: o0 e- B% H; O2020-12-31 128.0
4 L5 C. O+ t s4 {, T+ IFreq: B, Length: 262, dtype: float641 ~' v2 j( q, x6 r! h/ A$ u( w; e' ~- b
- m `1 n$ ^6 e5 N1
7 \+ b( b9 B5 U: H/ _24 `0 a6 B3 R% ?, h& S
3* F5 E. T1 j" d7 m
4# o Q; a; A8 w
5
* v, D& R/ d% O) Q1 W& k; n9 H4 [2 }8 m6
: C, Y. T; V0 e* o. {9 `$ C7, a# N! l H1 D1 f& L, ~7 E& h, \1 q
8# Y, d4 s, G4 V1 ]' q1 K- s
91 a8 S+ e% ^: A" J
10
( n; e S, T0 c0 a" \( [11
+ s3 H3 C/ ^1 ]; l5 D4 Z0 C120 B/ }# h& ?1 V: J9 n
13; O! E' ]+ W, H
14
1 T( h5 P6 g5 V# w' i, U15
4 a3 C1 }8 w- q: P4 r6 d16
5 s* A; q) C/ v6 }4 x17
' b: v: @; m9 {% Z' O# b shift, diff, pct_change 是一组类滑窗函数,它们的公共参数为 periods=n ,默认为1,分别表示取向前第 n 个元素的值、与向前第 n 个元素做差(与 Numpy 中不同,后者表示 n 阶差分)、与向前第 n 个元素相比计算增长率。这里的 n 可以为负,表示反方向的类似操作。4 F3 l0 q" B9 d, u! ~
+ z. x( G& v; }* M+ }- Z. E
对于shift函数而言,作用在datetime64为索引(不是value)的序列上时,可以指定freq单位进行滑动:6 p8 j3 n( K) e$ G0 A ~/ d* j9 I
9 d% ]3 I5 ~$ a! L0 j
s.shift(freq='50D').head()
& ]: V* v' N: sOut[113]:
, m3 E# a, l! Z& i9 B2020-02-20 -1. U5 s3 T5 }& ?1 ^; `9 u
2020-02-21 -2; n) S% I' Y# ^1 c" A( i
2020-02-22 -1# J+ ~8 E" K1 S" v8 A+ F* u
2020-02-25 -1
, H4 b5 S& r5 \% T4 j# ?5 K& R, x2020-02-26 -2
- Y# F7 }6 ?4 }( M3 _dtype: int32% \; z+ B8 j" K+ B' F
1. o5 S# D8 `. c: u' z: Y% T
2
7 S& r# n5 e: |: X; \ W" N* Q4 V3
# D- \0 Q) z& n4
; F0 o+ e/ {. w; W5 K5
6 {0 y( z" e1 f% j! B. f6
8 l/ {/ G$ `* E2 e- J7
- C- G( i0 e* _2 |( S+ E8
0 J9 g, }" V$ v) ? 另外,datetime64[ns]的序列进行diff(前后做差)后就能够得到timedelta64[ns]的序列,这能够使用户方便地观察有序时间序列的间隔:
8 k6 f3 \! Z: U1 x
# S* S/ s- r" f) X/ rmy_series = pd.Series(s.index)- m/ k( |: v' c# L
my_series.head()
% r) \- N, K* |9 R: s; iOut[115]: + ~$ ? _( Q' N- m8 w
0 2020-01-01! f4 c2 q- e/ M' w2 a' x- d; N
1 2020-01-02
+ J; q1 \ r7 U2 2020-01-03" [' X) W8 ]# F# F; d
3 2020-01-06
) R6 E0 Q: e; b8 }4 2020-01-07
+ Q3 p: I/ h" l( Gdtype: datetime64[ns]: f: E9 B9 O9 n6 _
# d1 y( ~8 b! R& M$ Qmy_series.diff(1).head()
8 L% f; W' y+ mOut[116]: ' B+ j; ~: x* P; ]8 J5 k
0 NaT
, t/ f( }9 a+ q1 1 days q+ H3 ]- S) f5 r; m" a4 G
2 1 days
4 |9 k! G$ E2 y. Y! c4 `3 3 days
. \& P# U+ q( W5 ~ ?4 1 days
6 I4 t1 g/ f4 @8 udtype: timedelta64[ns]
( d2 @- J8 R+ x$ j- z
3 I# v( a0 { b1 N: a. k1 d$ N1 g) I" N+ n7 Z
2
* F i* r4 o9 \! g- f37 O, v: _7 D- C- h! P: a
4
2 [! O* o% p2 `$ y% k8 h/ o54 v1 C" n& j. b7 u i( @
6( `7 L) X3 P2 R. f7 G& u
7& R5 _6 Y" b; s, k' [ k- o4 A
8, S# T8 L* ^7 w# t$ C1 ]) y
9
5 j) d9 j2 }1 O/ d- o; w2 Z10
2 P! {2 \ B$ R7 D& b, O11
3 }' I1 K# Q ~! |2 P12
- W+ O/ U; `% W! r4 P8 m. c! O13) E) {! n8 E6 p9 ]) x5 T
14
1 O) i) ?- F. d1 U151 L& ?. [6 H! J' }# L
16: ?; Y4 S6 K9 S# F" y+ C
17
. c1 i. K. p! T& W% ^181 C m1 B9 [& Y& D1 e, m3 W
10.5.2 重采样9 i1 t1 H7 X; b
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)* L4 ^9 K9 c" l$ L3 f, f* G/ Y/ o
常用参数有:
( _/ d, ?+ X; z
; a7 M( x0 N5 \4 J2 R$ Wrule:DateOffset, Timedelta or str类型。表示偏移量字符串或对象
2 X* B( [6 L8 ~7 H3 U# A" w; Waxis:{0 or ‘index’, 1 or ‘columns’}, default 0。使用哪个轴进行上采样或下采样# }+ Q3 d$ u6 w( @9 i
closed:{‘right’, ‘left’},默认None。表示bin 区间的哪一侧是闭合的。所有offsets的默认值为“left”,但“M”、“A”、“Q”、“BM”、“BA”、“BQ”和“W”均默认为“right”。6 {8 t( m0 w7 [: ~3 p
label:{‘right’, ‘left’}, 默认 None。hich bin edge label to label bucket with,所有offsets的默认值为“left”,但“M”、“A”、“Q”、“BM”、“BA”、“BQ”和“W”均默认为“right”。6 D* j- `9 Y T
convention{:‘start’, ‘end’, ‘s’, ‘e’}, default ‘start’。仅针对 PeriodIndex,控制是使用rule的开始还是结尾。- a4 T n& i, \- X; U
on:字符串类型,可选。对于 DataFrame,使用列而不是索引进行重采样。列必须类似于日期时间。6 w1 U5 Y1 C6 l( a3 C$ M
level:str 或 int,可选表示多重索引MultiIndex的级别,这个级别的索引必须类似于日期时间。
; f! O3 r( M# T5 \& Z+ \" n iorigin参数有5种取值:9 l, G M. d" U6 D5 t4 c+ s' D
‘epoch’:从 1970-01-01开始算起$ \4 X4 q3 \& X, g2 p& C) U' }% z
‘start’:原点是时间序列的第一个值
+ b' g8 \% K- e, O4 D4 I‘start_day’:默认值,表示原点是时间序列第一天的午夜。& B' l+ b8 e! R, @8 R5 d6 E) ]1 E2 k8 o3 E
'end':原点是时间序列的最后一个值(1.3.0版本才有)
0 x) }& A7 `3 G* F# _, ^8 M‘end_day’:原点是序列最后一天的午夜(1.3.0版本才有)2 P. z: v2 M3 T& W
offset:Timedelta 或 str,默认为 None,表示对时间原点的偏移量,很有用。2 I7 h7 X! e' T i* n- K+ w8 y9 v
closed和计算有关,label和显示有关,closed才有开闭。
/ O- J2 g1 p& n: d: H: l# j0 w" b label指这个区间值算出来了,索引放区间的左端点还是右端点,closed是指算的时候左端点或右端点是不是包含。+ ?2 P$ m; r+ a$ K) `
3 l# m' K4 O/ T) L, d& s
重采样对象resample和第四章中分组对象groupby的用法类似,resample是针对时间序列的分组计算而设计的分组对象。例如,对上面的序列计算每10天的均值:
% m9 y! @( ~: as.resample('10D').mean().head()
) m6 }" s- [! m: a; qOut[117]: ) N. k a; T! y2 K% }' f# p
2020-01-01 -2.000000% F5 C0 q$ V! `0 m% m, D" L
2020-01-11 -3.166667
; b; X% z( Y% \ D2 F6 ^4 e2020-01-21 -3.625000
2 Q! k" D/ O+ i, D5 I2020-01-31 -4.000000
8 n6 z; k4 `, Z7 {2020-02-10 -0.375000% Z9 C7 w7 J% i. ?' x7 G
Freq: 10D, dtype: float64 b, C6 d* p& k/ l! d6 s. D9 g
1
Z: A3 u0 j9 f9 H2
" v3 Q) `) t5 Y& D# c ]3
1 h% `. k7 [, y3 q7 o# ~" ~: [4
4 r3 ]- n- h9 w% K5& x5 d+ l0 c# o* F0 w" z6 g$ }6 m
6
1 ]) l* h* u% `7( U; O+ }/ |4 R0 ^6 m2 i X
8
, I5 {1 u# o- ]' u1 m可以通过apply方法自定义处理函数:
* g1 G4 F# y; O3 E+ Rs.resample('10D').apply(lambda x:x.max()-x.min()).head() # 极差% g9 H$ n9 j( _- b% Q. Q- S" t% ]
5 Y* K4 Y; U5 q% vOut[118]: : f$ |; D7 C+ x3 B8 A
2020-01-01 3
* o9 D. ]" F$ I, m; ?2020-01-11 4
6 G; {$ n/ R9 V% s7 r+ |2020-01-21 46 N5 v& ~/ S7 d# e4 E: S5 Z
2020-01-31 2
4 d5 Y2 O$ R, f3 r3 B5 T) w. O2020-02-10 4, V! V3 j: x/ m" r6 s
Freq: 10D, dtype: int32: P8 I2 P) ]7 H5 G4 C; i9 F( Y
1. _ S( P( a/ h$ Q; X2 f/ J6 y
2; S9 s6 U; P5 C. i
3
" L% F ^( B. H; j" j+ E6 [) f41 p+ l. u# K" V+ w
59 a/ v! \& d/ p5 f" M& ?, ]% \( p
6( R4 {; W1 N; I' u4 R& n, k7 v
7$ M E3 p8 o9 y. M+ e: o% b
8 w5 f" z7 R- ~6 ]5 v d
9- F, t2 T' \1 `$ ~/ |$ f
在resample中要特别注意组边界值的处理情况,默认情况下起始值的计算方法是从最小值时间戳对应日期的午夜00:00:00开始增加freq,直到不超过该最小时间戳的最大时间戳,由此对应的时间戳为起始值,然后每次累加freq参数作为分割结点进行分组,区间情况为左闭右开。下面构造一个不均匀的例子:
/ V( R" y B2 L) B
' z1 J% I( x5 w2 B3 \- a. G+ iidx = pd.date_range('20200101 8:26:35', '20200101 9:31:58', freq='77s')4 F1 R( N9 O, L9 {
data = np.random.randint(-1,2,len(idx)).cumsum()7 y8 P. f/ _9 G1 [( t
s = pd.Series(data,index=idx)7 w# p, {+ q X6 [& [- A8 t1 z
s.head()
7 @8 x: L2 ~+ Z. w+ M; L, c5 Q5 }8 e& S" Y" m. o$ _8 q% W' w% p
Out[122]: 0 b. f7 Y6 U% f* w
2020-01-01 08:26:35 -1
6 u/ P" p( O6 ?8 L3 E2020-01-01 08:27:52 -1
& E: N2 h) ?9 C" u' W" o; [2020-01-01 08:29:09 -29 d* s* r4 M! Y* {: ^. w. J
2020-01-01 08:30:26 -3
! d# F r t+ z2 z: Y( ]/ ^2020-01-01 08:31:43 -45 G# y; A- C! D
Freq: 77S, dtype: int321 E5 p/ j. z" D% c, \8 K6 s* h, }
18 u6 z; Z) |) E6 ^
2
2 @! ?! i' v1 W. Z3
, A. q4 Q' L/ I% A% k V4
- A6 w4 ]/ Q( s r* k+ J8 d8 M+ \5, q* [1 |* k' Z5 a1 q2 \; G
6
1 q# k/ W ~- D- _73 e% e" ~1 `% s1 @6 O- n- `
84 a5 j6 y4 T9 _% [& G
9: M6 m$ u6 Q" @% d* b8 S' A
10) U, H$ R% H. j8 q
11
# @9 A$ \* J6 l4 r$ t; R! l+ b12
9 S [ h; Y% v 下面对应的第一个组起始值为08:24:00,其是从当天0点增加72个freq=7 min得到的,如果再增加一个freq则超出了序列的最小时间戳08:26:35:
6 T+ ~4 v- F5 E0 w- g9 K, E$ D! }5 i, e, V! {5 a) r
s.resample('7min').mean().head()
' ]2 }3 d, `& O, KOut[123]: % G9 {* Q5 U; [+ {
2020-01-01 08:24:00 -1.750000 # 起始值,终点值包含最后一个值
, X! h/ n7 e! H: t- Y. p$ l% l7 i2 n2020-01-01 08:31:00 -2.600000
: e) e; \. B. b& @8 `$ {! h2020-01-01 08:38:00 -2.166667
' o+ V$ W# Z! R1 B, r2020-01-01 08:45:00 0.200000. s% q; a. h, Z# J- c
2020-01-01 08:52:00 2.833333
4 r7 L3 p- i8 f" {9 q- e& X7 hFreq: 7T, dtype: float64
2 e3 e9 S# B, `# n- V' \1 v* c1
1 x; v0 d% B; ~. W2
+ R; N0 w0 A$ W3
! N& B N- s& M$ ~4) L' ~" N7 _7 s2 g* V/ T
5
( C! P, O" t7 f4 L" K4 H; ^2 _5 J+ z6
; `" l8 v/ o* U5 `7
. i, O/ w/ _, q4 O0 g' U3 H9 r, Z8* f: |1 A8 y0 Z, o2 b$ l
有时候,用户希望从序列的最小时间戳开始依次增加freq进行分组,此时可以指定origin参数为start:
: L3 j7 m% k! g% h! m6 {) R. Z" y ~( Z5 Z; X+ [
s.resample('7min', origin='start').mean().head()
1 e4 k5 Q! ?% J; \Out[124]: 1 W( w8 l. ~3 e3 w! i. `1 z
2020-01-01 08:26:35 -2.333333
- ?9 ]: P) A D$ O2020-01-01 08:33:35 -2.4000002 G0 a9 |/ u& I0 a6 B7 [( g, E5 r
2020-01-01 08:40:35 -1.333333+ W) {/ K# t5 M4 L* a8 f& a: H
2020-01-01 08:47:35 1.2000006 \( a) r0 u1 r" U1 b9 o( D U2 w
2020-01-01 08:54:35 3.166667% o4 y( i/ B8 v+ e
Freq: 7T, dtype: float64
8 S; M2 h9 u! B7 g10 L4 `8 v* X" B
2: p9 \- `* |& }( d+ r1 e/ @
35 b( g1 {# e+ J% }# l
4+ e4 z& k, B1 N; I _3 H5 V
5
$ ]: P2 a6 }5 L) l* ~1 v9 V. u+ D: v# L7 b6
: t! N: D& J( c. a7* ~5 ~0 N9 ]( }2 X
81 E# L( ]2 ^7 K' W$ Y9 X7 L
在返回值中,要注意索引一般是取组的第一个时间戳,但M, A, Q, BM, BA, BQ, W这七个是取对应区间的最后一个时间戳。如果想要得到正常索引,用’MS’就行。
1 E! w, Z$ G+ o! x- N7 o- D% j7 P
! Q$ U5 k0 a( p0 g) G4 x! f- ~s = pd.Series(np.random.randint(2,size=366),
! v. K7 {1 y/ b, L! D! w8 U$ c- z index=pd.date_range('2020-01-01',
8 r B% ?, k! N: [ e '2020-12-31'))8 U$ j" Z+ N) g3 s" N/ H+ p) ` C
0 o4 L$ _2 f* c3 b+ q$ `
5 S, W8 W( Q* F' b0 z& K8 ~
s.resample('M').mean().head()7 {9 X) p8 g2 k( O/ S
Out[126]: 9 x* f( h" V# u0 e# q0 p6 C" j
2020-01-31 0.451613
2 A D3 V( g2 e! F2020-02-29 0.448276( I+ k: y$ M, C7 v" B
2020-03-31 0.516129+ l' ]( G, n( i+ T7 }: P2 z7 k
2020-04-30 0.566667
/ ~3 J' P" C% T* e; l; `1 X( Z2020-05-31 0.4516136 m- v4 a# C7 f, d! s
Freq: M, dtype: float644 K( U; h9 V8 L
, P/ ~8 v/ |* X( l+ b. As.resample('MS').mean().head() # 结果一样,但索引是跟正常一样) S8 Q! l; k D- ?8 J* g
Out[127]: 6 X' Z' w' x" f9 M) O
2020-01-01 0.451613
4 V, c- r! ]3 R+ Y( g' i2020-02-01 0.448276
0 b& b% S; G7 |' M2020-03-01 0.516129
! H4 N$ ?! k% o6 u2020-04-01 0.566667
+ j6 g; t' ?6 _3 R2020-05-01 0.451613
5 O( R2 [! A, b+ z7 A" Y# `Freq: MS, dtype: float64
4 s4 d+ a8 L, _
) q6 d: f5 {/ e( z& t1
8 V( |! `4 o3 k2
# ]: @! s+ H$ S1 X$ N3) M: m% o. ?( \+ m
4# j2 ?6 N) x/ c/ M) }( Q
5
) m( q; M; c- {6 R1 Z. W/ f2 M6 L6
$ W% D; ]1 s" d, q& n6 q7
7 \/ A2 A, d9 F0 ^, {4 H* v, C8
9 |: s6 u( g4 I" N5 x8 y3 B95 W" J! P* a! k1 n; Z
10
C7 p; s+ m7 P$ t/ L! E113 w+ E, e$ d2 F; y
129 h7 R. D" o" g: \. F/ B
13
- R1 C9 v, G3 S/ ~1 A* A14
3 d3 r6 G$ H6 s! Y15) M4 a8 R- M0 ` I5 H) [
16
9 w$ n7 x1 W" y, E* x3 ]5 M17! W0 D/ P# _; U8 T) z
18
3 Z% h* L9 I) W197 I/ h, j8 |8 ]
207 F$ S6 s% V6 K5 N
213 p( |6 s ]5 i- O: Y
220 k( D* F, u4 ?
对于 DataFrame 对象,关键字 on 可用于指定列而不是索引以进行重采样:
+ y: E" x) x, X' m* @d = {'price': [10, 11, 9, 13, 14, 18, 17, 19],$ K4 e& y, r( e. c% u2 l+ h
'volume': [50, 60, 40, 100, 50, 100, 40, 50]}
+ s' s0 |8 Z8 L( `- t6 _( ^& Jdf = pd.DataFrame(d)( a$ a/ f: M1 u/ l/ n: [& x% H
df['week_starting'] = pd.date_range('01/01/2018',/ l$ ?9 [7 B6 @9 U |, O
periods=8,7 |: m2 G- L( ~# X7 b. z" |& t0 y4 i1 e; R
freq='W')) K" _( l( g" G" W* a. H B1 _
df# S9 }: @5 j7 s- S3 K" D
price volume week_starting2 t8 l- ]0 s. @( B& s8 }
0 10 50 2018-01-07* a# Y( L. a/ B4 g
1 11 60 2018-01-14. [3 H$ z; N& N% G
2 9 40 2018-01-21
0 `- x% N& G6 o c3 13 100 2018-01-282 v9 z7 l. @3 [; e0 l
4 14 50 2018-02-04
) Y4 N5 k% f) I5 18 100 2018-02-11
/ H2 C- l! m0 o/ J/ ]6 17 40 2018-02-18) f+ A" E7 J( R% R( E
7 19 50 2018-02-25
) R, H% p5 }' Y: t! vdf.resample('M', on='week_starting').mean()
( C* u7 y, P; _, a& _ price volume
+ ?6 ]8 V8 J/ a/ h9 K3 \2 kweek_starting
3 o0 K7 z* _$ @* m6 v- j) }; ~ g, w% r2018-01-31 10.75 62.5
$ {+ M7 q7 l4 |, _2018-02-28 17.00 60.0% D5 W g% N9 O0 |, {% l; s
; R2 \( _+ S: R: I* p K1' a0 N3 s# J& w. @" J q
2
3 E& c# |1 D$ \" M- O- H3
- o7 k, {. ]+ P: ^4
. m3 K* k+ c' `3 P' Y- O5
8 T9 e0 O! b/ t9 W+ ?6
H7 B% s3 w- I; {( ?& @5 f7( |7 w, j% f9 B6 ?- |+ J0 x
85 T3 L. k* j" p5 T7 M2 ~" ~. b- G
9$ [, ^( A! L9 f3 x0 }+ Z2 [
10
6 C. z- B8 r: _7 ?11
2 [ ^' I7 z# \0 O5 V. D2 i( u12
( ^' {3 m0 q) |# R! A9 J! ?13* o, Z' m# G8 z
14
$ Q( S; N0 k1 J) ~( w( ~15( |/ \! [3 V0 i/ n* E& j+ T
16( p3 [# S$ L7 x6 M8 S, U; y
17% L* J' D/ n0 r' ]
18; i( z; E7 V% T/ X. b4 \ ^" L$ j( d
19- H2 i( X9 o( e$ x j
20
0 j0 S+ N" s6 B5 Z1 \( J: H21; n5 J& [% e* p
对于具有 MultiIndex 的 DataFrame,关键字 level 可用于指定需要在哪个级别进行重采样。. r4 K v' H- t# t5 {+ E) }, {1 ~
days = pd.date_range('1/1/2000', periods=4, freq='D')
2 V: B9 K1 m. ?1 S$ Ud2 = {'price': [10, 11, 9, 13, 14, 18, 17, 19],
- S9 j" x* {8 t# V# C0 J; O8 A+ q 'volume': [50, 60, 40, 100, 50, 100, 40, 50]}
! T' ~0 b5 X. {9 k2 u5 M% Q+ idf2 = pd.DataFrame(
9 B9 |' {; V7 K' z' {6 l' Z d2,
; H6 _4 c+ I; Y3 j index=pd.MultiIndex.from_product( `7 i$ w* d1 n( c4 C% e, j% x
[days, ['morning', 'afternoon']]
1 M. O" Z! y. b- A- J2 I )7 r3 a6 B9 ]$ a) ?3 {' i/ F
)
8 F/ f4 z" q/ k& P3 b2 g9 Qdf2" b. v8 u5 t! X6 h; K$ ^2 h& @4 U
price volume
" e0 r( L7 S1 X* F5 w2000-01-01 morning 10 50( y5 p6 v$ W: W+ a3 Z
afternoon 11 60
2 J' X( Z+ \+ Z2 U# O! O2000-01-02 morning 9 40
% b" I. d: n0 _3 _ afternoon 13 100% ]" {* V: C0 L! i
2000-01-03 morning 14 50
8 z5 ]$ ]. x3 H' M afternoon 18 100
, M. Q8 G4 x7 R/ m2000-01-04 morning 17 40
1 R g6 y7 [1 Y afternoon 19 50! B# o3 d' Z/ D: s5 o) c
df2.resample('D', level=0).sum()
5 ^- w9 i* V9 [. O6 ^ price volume: g/ t" T1 ~7 ?# B q2 E' `
2000-01-01 21 110
|$ H1 C1 U5 m! d$ s" T! o: ?2000-01-02 22 140
0 M% [) U; Y8 S2000-01-03 32 150! W$ {7 n' f3 D2 K
2000-01-04 36 90
# e2 z/ x( m2 ?4 F9 ^8 [/ x) u: R% ?
11 L2 a+ Q+ Q# q. E
2% i& \3 R' ?' `) C2 _
3
. A9 K* ^. }7 E5 l2 {1 h s7 w4
0 u$ m2 Y6 m6 F5
: w" q {9 W+ P2 D69 B0 O' x1 Q3 w! v: ~
7& @# b+ W' Z9 t
82 Z |$ Q% C7 ^/ `) w
9
$ y, I! m; R5 w& R" ^10
+ Z& u* T, t( Q7 D+ I* [114 R" r* z/ k. o2 g
12& P% @! w, z% e: x
13
1 R+ x" G& u7 k4 q/ T, d14
, p7 z2 K1 G B" }* I3 x154 ^. a0 c7 l- a* V
16
" D2 W/ v) K( u; Q17
2 D: F: u) o6 \( b$ {18
/ i& E8 h" U7 s" z9 N) Y6 L$ E8 O19# D1 \1 j. r5 f! |7 }7 T
205 p8 U G' a3 F1 f& S
21
8 g" L( E1 k2 \& |( I; a( G9 ]' v22$ c; P! @& W6 [/ B$ v' D( v" P
23, m: }, Z0 J) Q% C
24
8 m3 g" W6 N) [% T25
, r6 @. p3 P& I" R7 O根据固定时间戳调整 bin 的开始:
1 `. d3 N8 g: V( i: bstart, end = '2000-10-01 23:30:00', '2000-10-02 00:30:00'
* K- K; K; ?3 a. x! D) [8 F7 @; Y$ P2 ]rng = pd.date_range(start, end, freq='7min')
- M% p& z8 k) h' X+ g0 [- Its = pd.Series(np.arange(len(rng)) * 3, index=rng)
. R* Z( s. B+ A2 n2 q. k7 ~ts
$ m0 j' }0 a. q, |2000-10-01 23:30:00 0
& W! |9 X3 L" c0 s# u/ p2000-10-01 23:37:00 3
( m1 T2 g* b4 v2 ?8 G7 E2000-10-01 23:44:00 6( x& n; i1 }- Z4 _- F- z
2000-10-01 23:51:00 9
% L( Z( f! V# Z l2000-10-01 23:58:00 12+ k9 o4 U0 |7 {) r/ W& e$ _
2000-10-02 00:05:00 15. Z8 V: h8 y7 Y [$ N4 H
2000-10-02 00:12:00 18
s8 X' \( o. P3 [. A( L5 A. p& Q2000-10-02 00:19:00 21
' k* X0 ?' y" [6 P. V5 a! E: A2000-10-02 00:26:00 24
" F/ U+ U! `" }; B: w0 T B3 yFreq: 7T, dtype: int64
" H4 X$ K L; D: Z% ?
7 [. ?9 E2 W- J; ]3 @ts.resample('17min').sum()
0 |6 j4 P. D3 ^3 v- s7 F2000-10-01 23:14:00 0
2 v* @& j+ j7 b2000-10-01 23:31:00 97 r$ f, V7 I( A K% D% k! U
2000-10-01 23:48:00 214 G J, C6 R' L `6 \
2000-10-02 00:05:00 54
6 b% E5 l: M1 {1 m2000-10-02 00:22:00 24! K! M2 e; E& N+ L" |
Freq: 17T, dtype: int64
T1 I5 A5 k1 q9 L
9 A( G+ n7 L/ }) ats.resample('17min', origin='epoch').sum()
1 q5 _; l. [* N9 o9 K. q2000-10-01 23:18:00 0
% h. H! i# _9 t8 C/ R2000-10-01 23:35:00 18
5 u0 j! `1 C6 i# L' _6 C6 C2000-10-01 23:52:00 278 d+ A* a' r. ~4 Y' Z! j( J/ O
2000-10-02 00:09:00 39
% y5 b+ ?$ J! N& m s$ X! {2000-10-02 00:26:00 24
& C8 r; o7 R7 P" n5 R9 y% Y5 pFreq: 17T, dtype: int640 k1 U! h/ @5 r6 q+ y
) c3 _9 q' b; q1 k5 \
ts.resample('17min', origin='2000-01-01').sum()
3 }1 K p4 ], J: H7 @2 u2 l2000-10-01 23:24:00 37 c: p1 f# ]1 l8 w. U2 W w
2000-10-01 23:41:00 15- y$ H+ b3 v' o7 ^
2000-10-01 23:58:00 45
9 \" T. v( x4 N8 M* b6 M2000-10-02 00:15:00 459 d+ ^+ {) {7 e" |
Freq: 17T, dtype: int64
8 z7 B+ o% y9 ]
2 Q" |) ~1 c3 e' H3 |3 H6 f0 i6 P7 b10 X9 y; G1 d0 G
23 t; G% z F+ F8 Q. z
32 f' l9 v) \4 W% v
4
/ f' W N. j( }6 E f# H4 l& u1 p3 l5
& H5 c3 @! z7 P6 P1 u- `6
$ d1 W) i3 d5 {9 ]0 \0 |0 j7
9 W& c- Z2 V" x84 D% l$ T& d9 Y9 o7 N0 ]2 k. p- `
9) I" l0 k5 C( {2 J
107 b( ~0 O! z1 `
116 W( O+ H0 S$ f9 x* S. X- k
12: c) W& c$ C, E) j4 g7 E4 b
13
z2 e: K( O! ^; x% w4 n: R5 b0 s14
4 f8 r* \0 S* W" E# X15$ a m% y. \4 |( ?3 X9 X7 }
16+ I+ D B- G* u1 V, p! }
173 v" H. Z' C% R( \( f
18' ]5 O& ? t" I) [* ]
193 A# a# ^$ F5 x- { ?. a4 L0 o D% F* S' p
20" [, m g- I1 y' S
21! O2 H* W% r" R# m6 ? G5 F% L. P
22+ K" X5 M" B8 Y+ D2 j
23# Q: ~5 U. s H+ |
243 G; g% ]! b- |6 |
25
% l& r: \$ i5 T! i5 Y26
* D" ]3 L* F) g4 r) T27, Q7 v3 j5 Y: f4 L4 N0 V
28) c# R& C7 T' y( d/ \' M
29
7 j6 }6 ]$ i) w0 n+ R30
. J) J" ]" C6 Y31
+ L% O0 _# w# v, |+ K: I32
+ Q; a; T% T- e9 l$ ]5 y( `33
3 B5 h' J0 Q% y* o7 r0 K5 u# S34; W- ^6 k: g; Z- k
35$ D6 g6 G, P8 V
36
& V' Z. ]2 h, C37% Q/ k" ~. ?) j8 o
如果要使用偏移 Timedelta 调整 bin 的开始,则以下两行是等效的:
J+ b" S5 H( W& `6 tts.resample('17min', origin='start').sum()
, i1 q3 v4 s. F" pts.resample('17min', offset='23h30min').sum()$ g6 C+ Y& M; c3 N
2000-10-01 23:30:00 95 G7 _* a* w2 o$ K
2000-10-01 23:47:00 21
" S! ]3 M% d- S0 T5 p2000-10-02 00:04:00 54
. z. ]/ f z# u4 }+ v1 ~) F2000-10-02 00:21:00 24
1 P2 c2 D7 b0 s) R; {7 GFreq: 17T, dtype: int642 \6 [/ a5 T- ?5 Q% x Y1 i e
1
* h3 R/ s8 B) G5 |7 M2
% k% y5 Q% [+ d K' d7 ^% ~ |5 k3
: W; u1 k* l+ g1 P2 ^3 j c7 C l4! w3 Y( i+ F; L% k3 x
5
# ?! e" Q' @4 Z# L6
. q( a7 @3 I7 L0 t% V5 z# H0 d7" k8 O! \9 B! T: G2 v- R# J
10.6 练习' v* E+ v, I2 t7 b: E4 i$ Q, m
Ex1:太阳辐射数据集, }0 P9 _ i4 W" q
现有一份关于太阳辐射的数据集:) w8 R& V: N# ?. O6 b0 l
5 K: @, ^# O- C8 o @& S6 Y( \df = pd.read_csv('../data/solar.csv', usecols=['Data','Time','Radiation','Temperature'])# d9 x9 F3 y/ J1 X! W, s0 p
df.head(3) \ X5 g7 G% W) p% M& u8 `
* q( e1 ?- } A" `- {; D& n R) A
Out[129]: : F" A6 x+ r4 e. j
Data Time Radiation Temperature
4 e' j7 `1 o( K4 q0 9/29/2016 12:00:00 AM 23:55:26 1.21 48* G) D9 }$ y y, s% g- Z: E
1 9/29/2016 12:00:00 AM 23:50:23 1.21 48" h) N6 x# W) Z J2 v
2 9/29/2016 12:00:00 AM 23:45:26 1.23 48
' C6 X$ n+ c; y5 k U1
8 d) S4 Y% S* P0 G' X2
8 J) h& q) @& O" h3
2 e6 d Z! b8 S, P4% l) Q5 M3 x3 L4 E" |" H5 |7 [& u
5- h9 o! {0 J, v8 i* V4 t
6
7 J7 A4 |5 A% l7" l o& q; X. V
8
* e& S. }) W2 t1 M9 v- ?" y将Datetime, Time合并为一个时间列Datetime,同时把它作为索引后排序。
, `! j# W+ W& Z* n! G8 W0 Y每条记录时间的间隔显然并不一致,请解决如下问题:& }' s& l7 \4 t+ P/ C: l
找出间隔时间的前三个最大值所对应的三组时间戳。* X1 J6 e* g* R/ y9 e0 x
是否存在一个大致的范围,使得绝大多数的间隔时间都落在这个区间中?如果存在,请对此范围内的样本间隔秒数画出柱状图,设置bins=50。+ \7 L( I1 y$ O& l' n- [( }
求如下指标对应的Series:8 i: I" K- F* l* x5 M; }% @, b
温度与辐射量的6小时滑动相关系数8 [: h- a1 ?& b7 J9 {
以三点、九点、十五点、二十一点为分割,该观测所在时间区间的温度均值序列& f, F1 L# r: E, l4 N9 O6 F
每个观测6小时前的辐射量(一般而言不会恰好取到,此时取最近时间戳对应的辐射量)
, x) D8 f: j( g+ ]4 cimport numpy as np
6 Q3 b4 B5 s. E' @3 u6 s' {9 ximport pandas as pd$ y. d3 {% v# E. s
12 f! e. F7 X2 [
2
; w' X* ?/ [4 O' I将Datetime, Time合并为一个时间列Datetime,同时把它作为索引后排序。/ _$ D) V4 }: H$ y
data=pd.to_datetime(df.Data) # 本身是object对象,要先转为时间序列
. Z; [4 N& o8 H7 jtimes=pd.to_timedelta(df.Time)* E3 l, t; W, x8 `- F# n5 E. h
df.Data=data+times
) o# g t+ p' C% w4 R9 h1 _6 Odel df['Time']' Y/ r) T8 t# h# |/ n% a- M( t! T' Y
df=df.set_index('Data').sort_index() # 如果写的是set_index(df.Data),那么Data作为索引之外,这个列还另外保留
: a" h- T, j: _' F, W& Y# y3 ydf! r6 e; d. l- R( }/ I+ o; `
Radiation Temperature( ^3 \2 ]. q- u& j: t
Data , b, T' h z7 Q- \. [/ l
2016-09-01 00:00:08 2.58 51) D4 j! [) e( y7 g
2016-09-01 00:05:10 2.83 512 ^& h- [2 D$ |! C
2016-09-01 00:20:06 2.16 51
- O8 u8 B6 j* M E2016-09-01 00:25:05 2.21 51/ ?: ^9 Z5 R( `. u, S
2016-09-01 00:30:09 2.25 51! h2 q5 R# R6 Q6 ]
... ... ...3 [5 m* I# x4 \! \2 @/ N
2016-12-31 23:35:02 1.22 41: s1 e5 u" T9 E2 D h- @4 h3 _
2016-12-31 23:40:01 1.21 41
3 E) d+ j' S7 G5 ]) h2 Y5 M2016-12-31 23:45:04 1.21 42! q- f$ C F6 U" r) y$ {0 I; ^2 r
2016-12-31 23:50:03 1.19 41- ]9 `0 K' ?$ t& Q
2016-12-31 23:55:01 1.21 41
, r9 k' {0 v- j' G: w
- z. G# @" ?4 w0 J6 ?* Z' `, b1, ?1 l9 {3 V1 \) x% r9 Z9 v
2
0 M, w# A7 }) z, b( o3) o, I/ Y* B ~
4# |* }% }6 K1 K+ g& A9 {7 i
5
! M5 |2 _) a: c9 ? U; u8 H65 N4 J8 i; x! r
7
2 g+ O- `2 h( w! d. m$ n8" u% |0 o; J$ s
9
{6 s+ J0 k* U+ W% k/ r* E1 E10
6 L: U/ r; h0 A N2 M+ C115 b5 i7 R* r% p# {
12
/ B! x& V/ p( D3 P# o7 l$ I R13
$ n. U' S, N+ ^, `2 }3 U% B14
' R3 ^# X3 m( |8 \5 U15
/ G# W: [8 A# G161 k0 B3 r# x0 x$ c2 P/ m
17
7 q' M! u: W% p( l* ~. }18
6 `5 ^0 @9 l7 {19
# h" J# H, V/ H1 l每条记录时间的间隔显然并不一致,请解决如下问题:+ b' k8 N7 M% F; N/ t
找出间隔时间的前三个最大值所对应的三组时间戳。
6 r6 l5 h5 {9 N4 d# 第一次做错了,不是找三组时间戳
3 g2 N1 s @5 E( eidxmax3=pd.Series(df.index).diff(1).sort_values(ascending=False).index[:3]* e) f! N2 z: E' f! g
df.reset_index().Data[idxmax3,idxmax3-1]* l! u& ~! _" H6 f* l
$ z) n1 w% U S$ T25923 2016-12-08 11:10:42* b& u' ?. B w8 l
24522 2016-12-01 00:00:02& F* d1 s1 l7 f; Z4 e( e* n% w
7417 2016-10-01 00:00:19
% m `. E% \9 F& ?Name: Data, dtype: datetime64[ns]
6 k7 p, p; w" h, }1
t0 u9 Y+ _9 Z6 M2: H' B& h2 i. O8 z) Q/ _' _- U6 ^
3' C# W. Z. w2 q! Z
4, `( M8 |- X/ J+ `0 f ]& T9 v
54 t5 [( \$ k( |* z8 m
6
: o" b; C- K& n1 a% k! {5 ^4 ^7
- H# ?3 Y* ~3 g8- d) D# m# h3 T5 b- R
idxmax3=pd.Series(df.index).diff(1).sort_values(ascending=False).index[:3]9 c- H$ ^7 `: F- Z: C- M, Z) e+ Z
list(zip(df.reset_index().Data[idxmax3],df.reset_index().Data[idxmax3-1]))
' d2 s5 G* A) w7 R# T4 T( u
2 n, x- n5 E8 V, U( R2 d[(Timestamp('2016-12-08 11:10:42'), Timestamp('2016-12-05 20:45:53')),3 o4 ?9 {( t$ G6 N* N
(Timestamp('2016-12-01 00:00:02'), Timestamp('2016-11-29 19:05:02')),5 }5 M- d# ~4 ]4 ^7 b, J
(Timestamp('2016-10-01 00:00:19'), Timestamp('2016-09-29 23:55:26'))]* f1 P5 _3 y% ^/ N
1
1 s! ?& n% j, f2
2 _/ m, p" j2 _& }7 D" r" f% _" v3& Y6 e7 M# P/ x& O6 G
46 a: b% t4 p3 P. R8 z/ d
5+ D) p% {" e. B( C. j: h8 U
61 k- [( j1 B- O1 f# \* K8 X# u$ p
参考答案:& p) w+ n- w% T
5 d3 f5 G+ t G0 b$ P4 y8 us = df.index.to_series().reset_index(drop=True).diff().dt.total_seconds()' w) N9 j% z& ]: ? y
max_3 = s.nlargest(3).index
$ _' @3 n* e! {2 j" I" [( fdf.index[max_3.union(max_3-1)]8 ~6 }, K* u8 c) y2 C g9 M
1 [! U1 d8 R" ?! V4 j, L/ yOut[215]: ; U- e/ L% }+ _% Q9 ]1 P
DatetimeIndex(['2016-09-29 23:55:26', '2016-10-01 00:00:19',
. D4 B2 k: O0 f, b2 I '2016-11-29 19:05:02', '2016-12-01 00:00:02',6 t4 g3 [. s% n1 I b8 f4 R- V' V
'2016-12-05 20:45:53', '2016-12-08 11:10:42'],2 h7 S5 J: u+ ]* U6 M: x
dtype='datetime64[ns]', name='Datetime', freq=None)5 X' A! f8 a1 H3 \1 Q+ R
1
: F; a; s- M$ n8 w1 A! e- o22 c1 q' } B0 _9 N3 x
3
, J6 f* [* _& f+ g. A46 j3 K: |) Q1 |7 o% O" r
5. t7 d# l# `* o
6" ?; d8 j5 _/ V p7 J- d& \
7
: z, c1 T6 y! |/ ]5 e82 R' E8 e6 n5 c% T' Q |
94 y+ Q) g+ _: b1 m: `* U
是否存在一个大致的范围,使得绝大多数的间隔时间都落在这个区间中?如果存在,请对此范围内的样本间隔秒数画出柱状图,设置bins=50。
$ H9 S9 v7 L2 N6 p# 将df的indexydiff做差,转为秒数后排序。再求几个分位数确定取值区间4 J8 ]9 Q l% S a! j
s=pd.Series(df.index).diff(1).dt.total_seconds().sort_values(ascending=False): h3 k# ]) Q. q3 N' I
s.quantile(0.9),s.quantile(0.95),s.quantile(0.99),s.quantile(0.01),s.quantile(0.03),s.quantile(0.05)5 \9 T1 j5 T6 c" k9 A0 K1 A* a
8 S* _# C2 k# O8 Z( F" M" z
(304.0, 309.0, 337.15999999999985, 285.0, 290.0, 292.0)
3 @/ D1 `) r; r5 O9 A$ } o& C1
4 {& c e: K' u3 ]& S' T% ^/ O0 _2# Z1 H$ Q6 _- M: N$ U$ J3 x
3
, V3 q* ]& z5 U% K- {: c8 u! @4
" T2 ?) U: a# R9 e5
/ d, k/ w3 p5 ?0 r6 ^! G- ]%pylab inline
6 o3 P) [. B- D* e, M! Q/ R_ = plt.hist(ss[(s.values<337)&(s.values>285)],bins=50)
7 X c" ^ o( ^( I! M$ mplt.xlabel(' Timedelta')
" r- z/ C2 H! g2 O" d/ ]5 zplt.title(" Timedelta of solar")
! K. l8 i; _; t! k! Q. r$ @1
' D3 M1 V2 F) ]/ K2
: ^- q/ L3 q/ f r2 f37 P9 n8 |! E) ]
4
0 [$ d! Z6 H1 u1 K! l1 p3 l/ {- C5 R, g1 g9 Z
g, w. C/ j$ h: w
求如下指标对应的Series:1 W u. P% \, C; _7 |1 i
温度与辐射量的6小时滑动相关系数. K$ S/ U' w7 B! g9 ]
以三点、九点、十五点、二十一点为分割,该观测所在时间区间的温度均值序列
; P, w! H5 ~9 v; Q每个观测6小时前的辐射量(一般而言不会恰好取到,此时取最近时间戳对应的辐射量)3 Y' F8 V- ~5 q+ Z# a. x. }
df.Radiation.rolling('6H').corr(df.Temperature).tail()% z' |4 k% o2 I3 l. T7 q. O
! [1 `+ A* C) }) Q/ b' m. P$ cData
`* L/ E$ @: C3 R# w) J) F2016-12-31 23:35:02 0.416187- ?4 u6 z5 Q/ H1 f! j
2016-12-31 23:40:01 0.416565
0 ?0 A8 a: w, B1 x# C7 x2 S% _2016-12-31 23:45:04 0.328574; Q: g3 ~' c& E- p; ?/ e# ~# r
2016-12-31 23:50:03 0.261883
! N" z1 ?+ i s/ h J( e, S7 H2016-12-31 23:55:01 0.2624064 L F' |! l1 {
dtype: float64
' t) n3 u: e8 d9 S& y. U- x1& ]# @3 w* \- K( @- w, t; A
2( {9 o" O) f" k2 z+ K0 p
3) @3 t0 M- v' e3 ^, d$ i- {# W
4
" Q0 ^8 C$ @5 P5: g& E/ z9 s- W( t5 k* a5 w
6
) n2 t7 x% _) E6 _0 t' _! S; l e" N7
* I. V h$ u# l7 s" b8$ }' H0 m- C- t/ {* F; X8 X& Q/ Z& @/ x
9: d6 q+ Z0 ]9 g+ H
df['Temperature'].resample('6H',offset='3H').mean().head()0 y( W. ^7 L4 }: L
1 B8 d0 f: T$ v, W% t- }6 xData
3 K/ _5 c1 q8 {& a2016-08-31 21:00:00 51.2187508 R3 E8 V8 B( N' q, o J8 t, M- `
2016-09-01 03:00:00 50.033333
2 M# _/ v6 o% j2 K8 U2016-09-01 09:00:00 59.379310$ j: L8 _/ B9 c' h# ]# }: x
2016-09-01 15:00:00 57.984375 p6 q, d. }1 k) o3 R
2016-09-01 21:00:00 51.393939
" w5 _( @9 ~. ^0 AFreq: 6H, Name: Temperature, dtype: float646 R1 o) P5 j6 K/ Q$ L5 N5 {. `3 {
1
: c8 J+ D1 k. l( N* V$ l2
& O( O9 b$ A) a; L9 t5 ]3% L( z$ q9 B# |' C0 r& W4 r2 m
4
; p5 w" L+ P0 P6 O$ ~/ O5( O3 S+ N4 F1 @% G3 m' \; x+ y
63 a5 }& Y& Y+ S5 e! Z
7
( \) \! l1 y" A% L; o z8
+ `5 Y% ?- U2 x1 n9
% C+ a P9 G2 r' I/ B8 n最后一题参考答案:! n, d, b+ J A5 N+ _
! J( r- l# A8 e0 i5 C# 非常慢& K; v# Q* s0 a3 C) q- I
my_dt = df.index.shift(freq='-6H')# `3 Z) S/ o( [( r8 N6 g' G) A9 W
int_loc = [df.index.get_indexer([i], method='nearest') for i in my_dt]3 p4 h6 E& `1 [! l' ?2 ~$ v1 M o4 U# @
int_loc = np.array(int_loc).reshape(-1)4 f/ S5 l+ z% ^# M) R
res = df.Radiation.iloc[int_loc] i( U$ v2 k ]9 n# r2 Z* D
res.index = df.index" w/ q+ V5 B# e" V, Z3 j! ~1 V" k
res.tail(3)8 w! W4 O% h1 ?2 ]$ E
1
% |5 Z% {$ |# |" K2 e8 V2# l( T0 \, D0 ]! ]
3
2 Y2 g1 u1 w/ A1 s$ `3 q4
% ]8 l3 S9 G3 I: Y9 Q57 O2 U2 G3 B, f( o* w+ p3 x
6
2 w+ m* U. q$ Q. r+ f5 V+ u* ?7
$ l' n! E/ i; H) a9 k- h/ Q' ~# 纸质版上介绍了merge_asof,性能差距可以达到3-4个数量级) l8 V" i5 p, S' Q3 E1 _/ E( i9 g
target = pd.DataFrame(
: W/ f8 W) v" g/ L! x2 D {
2 M* J9 i; g& [( b% b4 Q "Time": df.index.shift(freq='-6H'),
4 Z9 y$ P" t( |" P" s( x; ^" j "Datetime": df.index,& }& f+ B5 p* K/ R7 E8 S7 v
}
1 c, |. r2 P- v/ r: S _5 P)
R, i- `6 I* D9 h* T% j# {
2 C8 `: A" }9 Cres = pd.merge_asof(
& ~* u7 U5 J1 E target,& @' J8 p. x8 y
df.reset_index().rename(columns={"Datetime": "Time"}),
8 R6 w3 p# W% e; W! I left_on="Time",
T/ X/ Q/ n C+ w6 C2 Q R right_on="Time",9 p) x8 f3 R2 Z
direction="nearest"
9 P0 z3 X# ]" D5 r, r; G).set_index("Datetime").Radiation) N* `" A4 T" t' d; w: W
# o& k, T' z# @$ u0 V0 T3 sres.tail(3)
# j& U/ b, M/ g/ T- u, S1 i3 V4 b" sOut[224]:
4 z& L: P* j+ h' r# |Datetime
* H6 i' m0 L) q7 Q5 T2016-12-31 23:45:04 9.33
; x* P- k, h: s; _2016-12-31 23:50:03 8.49; \& Q9 T% h" |6 S( Y2 {
2016-12-31 23:55:01 5.84
! [2 N5 u- }3 ?, ^Name: Radiation, dtype: float64% D) p. M9 d& ^/ b# v# I/ N% e& _
6 y( q/ L8 g& x: N3 h% M! c% ?1
* ]; A" \3 }$ j9 t" V. y: J3 @' C5 d2
0 c/ c6 Z1 K0 F, U) q) H3
: i6 I- n8 ~& S( m5 ]4
& q5 Y& s9 h- d) }5
2 h$ N: a, K7 ?" B3 v67 P, x9 l% U. Z" c ]$ {
7
- h( M# s% W; e7 B; p8; `( r$ ~, M- @; n3 {
9
. A5 b1 |3 G; a+ b10% S1 X, H/ ]7 v- I2 a8 C2 V. d5 }
11/ O& u6 f! L$ a7 u. E
12
5 b. f) ]9 u- S+ R1 E13
& Q% j: Q/ I+ D! e) P( v4 S/ F8 y14
7 \: Z1 C' W& N0 e" U3 J15
8 h3 D6 q2 o+ _+ V2 `/ A- K16- J0 F* ~- v" v" w" Y. b' V. ~
17" |; k$ p% z9 l( G- \
18& h$ Z; D. X! _. r
19% B+ U( V9 b! n
20
. o& y2 h8 X5 e! `" i; R3 U21
] p; y8 g/ b* q22
- r/ b" H9 S% O W% g( O23
3 F& ]% w3 d) Q8 V7 H! P3 |Ex2:水果销量数据集
* S/ R% S( p5 K! d0 `' B3 v' l+ x现有一份2019年每日水果销量记录表:
. i" q( c( b/ f* i- u. V# N/ ]4 C. Z* [, C6 v
df = pd.read_csv('../data/fruit.csv')! k2 x+ |) T1 ?
df.head(3)6 o, S$ `2 \" E p9 u" }$ F
7 s! ?# ~8 A2 k: U) }: _ XOut[131]:
2 ^1 k9 t+ l. f+ Q Date Fruit Sale6 C3 Z( R+ B5 ?
0 2019-04-18 Peach 15& j6 B* K0 I. o, I$ B- }" Z8 Q5 C
1 2019-12-29 Peach 15
2 r6 H5 K: ?2 y( F; a3 E2 2019-06-05 Peach 19
`4 p( a: [# V9 m: |1" [7 h1 A; v9 t* a
2/ K' a* v( d. m3 t* a) D6 {. p
30 V( B) l. b) N* q' _
4
9 l& a3 n' r4 ~; c" ~6 ]5
1 l7 C4 i' H, `, z( L6
6 l- V# d, A' V. j/ U; D) z7
3 _! V5 b8 ^% E$ y; t8
( B: G0 I' e1 P/ m: h9 v* o统计如下指标:3 v- Z l+ g9 F$ G9 Z1 v1 h' ?
每月上半月(15号及之前)与下半月葡萄销量的比值
0 v8 |! V4 P s+ N每月最后一天的生梨销量总和
1 U) I: u e7 V每月最后一天工作日的生梨销量总和. B! A9 I" n6 |/ H' o
每月最后五天的苹果销量均值
6 F. [, q, M$ d5 O3 g2 @ Y按月计算周一至周日各品种水果的平均记录条数,行索引外层为水果名称,内层为月份,列索引为星期。
4 K) C" w2 Y7 ]- g2 q3 a按天计算向前10个工作日窗口的苹果销量均值序列,非工作日的值用上一个工作日的结果填充。
3 L/ t6 Q/ T- S& i4 o" F+ Iimport numpy as np6 j7 e5 k* s2 ?
import pandas as pd
% K! H, |/ f6 c# f8 t7 {1
( ]4 J' v5 ~9 p/ u- x W27 f! `& p0 ?' h+ p$ j! {( r
统计如下指标:$ e& D3 x9 \2 ~/ O& N2 o
每月上半月(15号及之前)与下半月葡萄销量的比值; L# K6 Z& _3 q
每月最后一天的生梨销量总和, x; {) |+ _6 h4 x
每月最后一天工作日的生梨销量总和
e7 ~( x* G* _! m) Q4 |# N每月最后五天的苹果销量均值
! O! `/ U0 v* l4 L# 每月上半月(15号及之前)与下半月葡萄销量的比值
( k9 l- g$ F, g; ^df.Date=pd.to_datetime(df.Date)
d. U) z0 L3 x n7 H jsale=df.query('Fruit == "Grape"').groupby([df.Date.dt.month,df.Date.dt.day<=15])['Sale'].sum()
; |6 W9 S/ a; tsale.columns=['Month','15Dayes','Sale'] # 为啥这么改没用啊
" ?# m" v) O9 D- Ksale=pd.DataFrame(sale)5 N" k Z6 T* ~
sale=sale.unstack(1).rename_axis(index={'Date':'Month'},1 n/ l9 x7 j9 \' }& z4 ?
columns={'Date':'15Days'}).stack(1).reset_index() # unstack主要是两个索引都是Date无法直接重命名4 @" ^3 u& _( d9 z2 u+ B1 @
sale.head() # 每个月上下半月的销量# K( T2 ^# K) c9 G$ `
4 i6 _0 o* A' p5 x1 j
Month 15Days Sale
# T% _' l, l; I0 1 False 10503- _! l6 x7 Q! U
1 1 True 123417 B2 K8 z1 i: ^7 U: K# z
2 2 False 10001# m/ w8 Q$ p6 _- ~# I& c
3 2 True 101065 a* ?! R; t. H- k. Q7 J3 F# b) ]' [
4 3 False 12814
* m, ?5 {2 q& Y2 Z! P6 x
7 Z- `1 ]! t, }$ |5 |3 S7 Q# 使用自定义聚合函数,分组后每组就上半月和下半月两个值,根据索引位置判断求比值时的分子分母顺序# J6 B- z, o4 A8 B: o; O3 ?' z
sale.groupby(sale['Month'])['Sale'].agg(
% |9 m) Z3 C! H0 P+ [ lambda x: x.max()/x.min() if x.idxmax()>x.idxmin() else x.min()/x.max())
# B: a' Z7 _' k8 ^; s& q1 [8 V C& r' h# ?" k
Month
& F3 L, ~- Q+ Z* {- {. F1 1.174998
; w! ^3 g6 I& C3 m9 U: ?* _2 1.010499
* `0 [" V! k/ l; [, O0 m3 0.776338
5 t6 F7 B+ L2 E" C. b! R6 x! O4 1.0263455 S; b# s" R) u, U2 J
5 0.900534
" [6 u3 j' y% r# k. y5 m k6 0.980136" M$ P: V$ o/ Q, c$ Z0 Z8 m; i& b
7 1.350960
: e" Z4 l- [. ]7 z4 h3 V( P) ]% p8 1.091584) j5 I6 ]" s! p! u' `/ x6 L
9 1.116508
8 @/ Z; W) E) R4 X3 C10 1.0207847 [& O* E) j$ H, Q; s
11 1.275911
" `/ _6 U$ d' b+ `* _' ]: t& ?6 G l12 0.989662
, d- E( ~$ }7 b. e% p" A' YName: Sale, dtype: float641 v8 l4 S, X# F2 Z7 ]- T% {9 T
! d q9 u9 r% \! @% \- _; ~. G
14 Z9 Z. U6 Z ^8 N6 c, r: R# X9 ]
2
: O( U+ }% }2 Q3. H/ L/ [8 E; j* A+ | A! L9 B
42 f9 [) ^2 ` I7 Z+ q. @! A
5. x8 _) \- t4 a2 k
6
6 n9 o4 t5 A9 E7; T' |5 ?6 k5 W8 N
8! V" O, I" L6 l, t4 Y; K
94 o* Q# G9 @" ~/ R# K9 p
10
& y* \$ T: u0 e6 L/ \ S- f11
- [, n, L" v, G: h12
: B2 B! N# Q5 y- z4 i8 O9 q/ x& k$ Y13
0 B' t) z" p1 s* z( Q, e14
' f9 c/ I i5 c; n" d2 m" c; Q/ W15
: _& g. ?/ ?: r" e16" [% R/ B1 }; G* y/ K
170 w5 |' ~7 r1 Z- V
18
$ ?$ s) C# C; J% r C19
* f# b$ n$ D, \20- ^, ]+ q% W) C% Q4 r: a
21
7 z: i$ C/ D+ m5 g! j3 f) w/ {22
3 P, h: X# r; f! E5 Z236 P& V! |) U, N- z5 h3 U" ]4 i
243 [5 r6 p$ \* \. b
25
* r% B+ }4 i# g' Z# h+ q26
2 G: G( Z( K( r, s* z27
, S' Q* A/ Z8 h% w7 J3 v286 x, A( ?9 L6 @, B: ^- ^) X3 h0 ~9 s
29) k+ B( x8 L" K6 S( G
30
' a% X$ O( v5 ~( V& ~; w; i! s312 _6 u9 `/ `/ G- `. O. \. \
32, w; |5 H, j4 P$ G
33
0 ]4 G4 }1 y0 s6 u348 {$ ^, L+ t. W5 b
# 每月最后一天的生梨销量总和
/ c7 q7 p+ P$ O) q/ A5 N+ m8 Jdf[df.Date.dt.is_month_end].loc[df.Fruit=='Pear'].groupby('Date')['Sale'].sum()( W- \: G, o+ I' d4 w( K; N' Z
: r; d* g) ~& P/ t
Date8 @* y" }/ E, I ]; }0 d
2019-01-31 847
% z5 Y9 l) y0 W1 D- ~2019-02-28 7741 i, B6 ]% a* z# b& y% e$ o7 p
2019-03-31 761& O7 g: x+ l) E. b# Y2 `# Z
2019-04-30 6485 b. I6 u: `& F a, h& B. `
2019-05-31 616
0 r7 D+ W4 G3 R0 e6 }1
9 M- X* G! e# Y1 l- T# i+ B9 r) w2
& r x" Y: |. S1 `3
9 P" Q4 Q- ]9 b3 _% {4+ b w/ ?2 B5 d- j" `( H
5
: p5 y# C! k6 K' [3 W6* z8 t5 K7 \3 I5 m% _: i
7* \) y4 h- N ^& N( F1 J; ]
8
: d- I% A+ n' o$ ?! p! R# A9- l$ e. j" O/ R' F7 o# ^
# 每月最后一天工作日的生梨销量总和2 t: J+ W0 d" m
ls=df.Date+pd.offsets.BMonthEnd()$ ^ \2 W1 W1 Z1 L5 k" N) ?4 G. h
my_filter=pd.to_datetime(ls.unique())
( t1 C2 q8 G! F7 x% {; z' P) n: Kdf[df.Date.isin(my_filter)].loc[df.Fruit=='Pear'].groupby('Date')['Sale'].sum()! i9 H `. ^/ Z; p
0 t0 ? ?$ h7 kDate
; u; U0 e* K. i' V% D) g& t8 f( z2019-01-31 847% z4 n3 f1 g1 C7 n+ p; G1 X) G
2019-02-28 7746 @& l# v+ a! t- w0 W H ? P; y
2019-03-29 5107 l: ^+ [4 R) L7 A
2019-04-30 648
1 v v5 c! @6 @7 O2019-05-31 616
2 Z, u1 L! |4 U% f0 ~& u1
0 l& G; H: i# U( s2' f$ L1 P9 R( K9 B
3
0 l- T) ?8 `6 z$ q0 j/ G44 ]4 G7 {0 R3 p( b& ]* M* ~
5
X3 j4 J7 E4 P% b& s# l9 w! A6- Z( }0 E$ k, S& E4 N2 y! c
77 g/ c2 Y- E4 I& H8 h; M. b2 x( Y
83 ^9 e2 ^2 D, J( _/ s. v9 d
97 |9 d. n7 Z2 X7 F: n# n/ u+ r
106 {! J! L# |- E6 c% {; M! {
11$ x; r( e) @% j% h1 a
# 每月最后五天的苹果销量均值
5 W8 A6 u$ O; U& I1 P0 n' |3 ?start, end = '2019-01-01', '2019-12-31'+ e+ \ K( z. i) e0 j/ j1 v2 l! H
end = pd.date_range(start, end, freq='M')2 j* I. q7 K- h: m U% U
end=end.repeat(5) # 每月最后一天的日期列表,重复5次方便做差7 @. n0 W) |: U4 \0 H
/ I8 ^: Y5 T/ Z& z
td= pd.Series(pd.timedelta_range(start='0 days', periods=5),)1 |3 v9 _# v8 U" z, c
td=pd.concat([td]*12) # 日期偏置,最后一天减去0-4天! i+ x' O" l) g* n3 T
end5=(end-td).reset_index(drop=True) # 每个月最后5天的列表
, H) q1 a$ t2 V) M: \
' E1 f" Y( y, w4 bapple5=df[df.Date.isin(end5)].query("Fruit == 'Apple'") # 每月最后五天苹果销量
$ K; U& X& J" D' }( aapple5.groupby(apple5.Date.dt.month)['Sale'].mean().head()& J' I- j$ N+ b, ^6 E
% W! i Y- `- Z: \& ?# h( |Date# c+ |* n+ i. D- m j/ v5 p
1 65.313725
5 r+ I# ^; o, Z) J, I+ F2 54.061538+ Z/ e$ B0 I/ D( M5 @$ [
3 59.325581* n n# W+ }) U h' G) h
4 65.795455
8 ]9 K M9 x% g" l2 m( t6 l" T5 57.465116
/ K0 A1 ]9 N; \$ \0 L1 m' `0 V: |8 i0 n" O3 v" U# d' i
1! O; a. ?1 l' @1 }3 B
2
! r6 @6 d m, w' T/ q3
$ @( O6 X. ?2 H* S2 X4
) L1 x* L. Y: g, A6 S54 h& v% w6 s$ E/ ~1 O
6( v$ I+ Z) I4 J
7
& H$ B/ C @! L& p1 ?, [8
' S$ n4 I, O' L% s; v9" i: X! P7 P3 y4 k6 o7 O; U& [ v% B
10
9 i* z1 G4 E! `9 u1 {11% T) [8 ~- k4 j r- d
125 n7 B: j4 b8 x3 Y
13# F* F) s$ S8 J8 f) z7 z! T
14
6 {. Y4 k# @, }9 y* ]15- r7 ]0 o# A% K2 K
16# J% w8 ?$ q& l9 C+ R6 I
17
' G& c4 I; y4 ?- D3 ^18
$ s# ]1 H, A7 A) |6 d+ y# 参考答案:
$ w! t7 |; Q# Btarget_dt = df.drop_duplicates().groupby(df.Date.drop_duplicates(% Z# X/ h& K, l4 ]; W( m2 F
).dt.month)['Date'].nlargest(5).reset_index(drop=True)# I4 E4 g* L s2 e# e% ^
# R( H9 A6 M3 L: F+ r6 I6 v* Ares = df.set_index('Date').loc[target_dt].reset_index(
3 x5 H) W$ y6 c, a ).query("Fruit == 'Apple'")
5 I3 W3 o* I* u5 F# x: }( I0 ^, w; \- q0 P* S) l: B# S. g
res = res.groupby(res.Date.dt.month)['Sale'].mean(8 {% ^/ \6 ?$ w+ y
).rename_axis('Month')
* o% e' m6 _+ C) ^/ n" o3 h# C! M4 U) S2 s
- p+ J* g% @# d g) N: Q0 ~2 \( yres.head() b# ]/ z4 l3 W
Out[236]: d s$ `# [8 Y3 u/ b/ r @# w
Month3 X. x% h- _) z. I: d5 G
1 65.313725
; Y2 g, g/ z7 c3 P: T2 54.061538
) `9 _7 k; |- o$ b3 59.325581
8 E, X, I3 t& V4 a& Y. ~4 65.7954551 Y/ g) B- N, t* N" R
5 57.465116
( ]) B8 B" o) P2 j+ e+ C$ u# yName: Sale, dtype: float64
( |, Y: ~9 w; c$ f
. ]) F" U! Z& V8 e1 m1
9 R) M$ S% E, V: e; O F2
* g9 t( W |" x) z3
v3 {" ]1 U% t) i& } i4
; \5 r3 r& l2 O3 o) n) M) N5, e5 G4 q5 i$ X/ k
67 @9 y7 N, f: j1 i
7
( k9 y0 g! b" y8 V5 k2 c4 V+ ^8
& N( x: a6 }4 `9
6 T$ h7 I/ R5 F* y# n8 X& P106 d0 N* m8 V2 o
116 P+ `6 ?1 j" A. d/ a' n ^
129 c% j, N* H) J S4 o0 f
13
0 p- P, n% l: k9 |2 X14! B+ [) `, j8 S m: D
15# T% T6 D; F6 C8 k/ K- g7 v
16, x& A- L3 P! G! r
17
5 ^) s# O$ g" ?# c# u3 _18& h Q8 q) O* N
19+ L# e5 M& c# [' J5 s
200 n% E" s" ^" n1 }0 C
按月计算周一至周日各品种水果的平均记录条数,行索引外层为水果名称,内层为月份,列索引为星期。" M. i! l. r3 m4 i
result=pd.DataFrame(df.groupby([df.Date.dt.month,df.Date.3 d& f6 b5 M6 i S( N0 Q# W
dt.dayofweek,df.Fruit])['Sale'].count()) # 分组统计
^) ~6 X4 {, J( q$ R/ I2 J
$ X3 B, W8 Y1 |) v! gresult=result.unstack(1).rename_axis(index={'Date':'Month'},
1 d! m6 B9 W. e4 ?1 H; t columns={'Date':'Week'}) # 两个index名字都是Date,只能转一个到列,分开来改名字.. d! S5 D: s- r+ D
result=result.swaplevel(0,1,axis=0).droplevel(0,axis=1)
x; L8 h; }. i+ a& yresult.head() # 索引名有空再改吧# x" ^' z# a$ A1 h
- h! L# P. O( P( U% F+ ~- C
Week 0 1 2 3 4 5 6+ E2 O* y9 M7 S0 b% H; ^/ S
Fruit Month
; d3 b) M& p. x% p2 jApple 1 46 50 50 45 32 42 23" w: [- I1 M6 W9 [, v9 X
Banana 1 27 29 24 42 36 24 35
; {( \* D7 r* `* L' K6 R) DGrape 1 42 75 53 63 36 57 46
' Q! \2 l' D A+ o( L1 z# ]7 YPeach 1 67 78 73 88 59 49 728 G( }. n$ n0 X9 u$ N
Pear 1 39 69 51 54 48 36 40
5 ?: V% I) j6 U9 u& R1
+ Y5 ?# r* _9 m1 t" T! {+ i! V8 l4 z2
$ Q3 [5 a6 y' F3 I; Q( u) [ t37 ^! E" B+ K6 G; ?7 x
4
; n& Z# i, t! M% j- v) h5- c& ~4 L% ^4 A$ G
6: R; W% I5 K2 B/ v' C! q7 q
7
' Z+ ~2 Y/ _- p1 H8/ \& G5 b* {5 y6 M
9( g3 a* S8 B# A: r
10
3 _3 Q( H' d6 q" L2 a11
$ g p s0 p$ n& o. a9 M' L! A12
+ `5 C) T$ B; h' L& b; e: Y0 g. d13$ E( U4 g1 ?, T- x" Y% [
14
3 c3 [8 D! F! H! ?+ X% [155 q# O; M) p% {/ s, n* c: t+ t
按天计算向前10个工作日窗口的苹果销量均值序列,非工作日的值用上一个工作日的结果填充。0 {& n- M4 l, J A6 J, B' `
# 工作日苹果销量按日期排序. g0 C7 ~ A& J" E8 t+ f9 E9 M
select_bday=df[~df.Date.dt.dayofweek.isin([5,6])].query('Fruit=="Apple"').set_index('Date').sort_index()
* d7 c8 l6 M c3 l5 v/ h; w6 b" vselect_bday=select_bday.groupby(select_bday.index)['Sale'].sum() # 每天的销量汇总# O1 b O" n$ x
select_bday.head()
7 t/ B; R+ ?4 ~! w' Z# ^ F+ Z$ ^, Z3 E* g
Date
: s: I2 _( l4 P; A+ }' Z2019-01-01 189
) y5 o2 Y. D7 N8 t- j7 M2019-01-02 482# |0 x- B. J- b; O' w9 T z0 U. \& Y
2019-01-03 890
" s) r( F& `3 I2019-01-04 550! u- U- f G+ ]7 C
2019-01-07 494
9 @" l1 ~5 K" G0 Z
1 v3 |& K7 |. @% d$ W; i# 此时已经是工作日,正常滑窗。结果重设索引,对周末进行向后填充。( L Y* N1 _9 ]& e3 H
select_bday.rolling('10D').mean().reindex(df.Date.unique()).sort_index().ffill().head()
9 s2 `0 M* b/ \( ^& I6 C1 h2 B" ?$ {5 G
Date
& x/ ]( J8 f1 O+ N& C0 l2019-01-01 189.000000
. d' n# } c/ _! O2019-01-02 335.500000
% t" m Y! j$ F8 a; @# `' N# U2019-01-03 520.333333. T# u0 U! r l9 x+ ~# l0 T
2019-01-04 527.750000
9 J6 M. J1 R3 h- D: C8 F8 L9 v, K& S2019-01-05 527.750000: `, s/ o5 z1 `( X. \/ J
& Q9 h ^# @& |( @. [! e( f———————————————— K2 l+ k1 @/ C' M. O) z+ C
版权声明:本文为CSDN博主「神洛华」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。/ b; u* ` \1 z: t- W. G+ C" i, w
原文链接:https://blog.csdn.net/qq_56591814/article/details/126633913 O9 i4 h7 x; s' V4 T5 S
# U5 s4 U- V* [3 h4 N) G4 d f1 t7 w
/ m1 K6 [; m( v- ~5 C+ N; ?
|
zan
|