- 在线时间
- 1630 小时
- 最后登录
- 2024-1-29
- 注册时间
- 2017-5-16
- 听众数
- 82
- 收听数
- 1
- 能力
- 120 分
- 体力
- 563345 点
- 威望
- 12 点
- 阅读权限
- 255
- 积分
- 174226
- 相册
- 1
- 日志
- 0
- 记录
- 0
- 帖子
- 5313
- 主题
- 5273
- 精华
- 3
- 分享
- 0
- 好友
- 163
TA的每日心情 | 开心 2021-8-11 17:59 |
|---|
签到天数: 17 天 [LV.4]偶尔看看III 网络挑战赛参赛者 网络挑战赛参赛者 - 自我介绍
- 本人女,毕业于内蒙古科技大学,担任文职专业,毕业专业英语。
 群组: 2018美赛大象算法课程 群组: 2018美赛护航培训课程 群组: 2019年 数学中国站长建 群组: 2019年数据分析师课程 群组: 2018年大象老师国赛优 |
* ^) ]- B' X: `, H0 T/ ?9 O
: U- c5 _+ K0 M" L. i) S
! q1 W! l5 x, Z4 P" s文章目录+ d' x0 O6 ]0 g2 a) u
第八章 文本数据- w/ e" E( B9 w
8.1 str对象, j8 ]4 ]) _: |1 {
8.1.1 str对象的设计意图, T5 V0 c# O* @% b& p
8.1.3 string类型
) n* x3 l- _7 {$ _! D2 s8.2 正则表达式基础
5 y6 z% g) T2 h) S- i$ N8.2.1 . 一般字符的匹配( g" m9 N& x+ ^
8.2.2 元字符基础
; ~0 @5 S) m' F K; u8.2.3 简写字符集" I0 M4 {: o3 S* p" [! k. K
8.3 文本处理的五类操作
/ x# ]4 f+ U0 C o2 M5 N9 h8.3.1 `str.split `拆分
4 Y1 i9 C0 k( W1 H( S0 L S$ a8.3.2 `str.join` 或 `str.cat `合并
$ y2 P' S- e3 R5 j- Y& E8.3.3 匹配6 | o! B' U, M9 C4 G% ` Q
8.3.5 提取+ j7 O1 |5 H# y! e2 |+ r2 Z; k
8.4、常用字符串函数
( |* ` ]$ R& y, c8.4.1 字母型函数) |$ g0 H: V2 F) s
8.4.2 数值型函数
% T4 c, |) ^, E( I# N# P# h: U/ N8.4.3 统计型函数) b+ O( p- I: W! F% @# b7 q F j
8.4.4 格式型函数+ Z* w* o; U3 N2 j- g7 B
8.5 练习' K0 Q3 w/ D! ]! y5 D* c# H
Ex1:房屋信息数据集
5 d9 a a1 ^( M, g3 ?7 F. n0 Z2 nEx2:《权力的游戏》剧本数据集
& e' Q0 A3 n ^, X. b# K第九章 分类数据" [6 o! ~( b2 N& y7 G
9.1 cat对象7 T1 C7 P7 ~4 R) D* d7 D6 t
9.1.1 cat对象的属性
U) r2 P) b- z' H |* T9.1.2 类别的增加、删除和修改
7 Y: j- E0 P1 H# u& j$ N) F9.2 有序分类
' p4 S/ A* \/ _5 Y# m: o9.2.1 序的建立+ E, X6 T4 F, h; @. x+ J
9.2.2 排序和比较* K4 n3 n8 d3 v* F8 m% [* f
9.3 区间类别
* p5 n1 H& i- J0 s8 M5 H9.3.1 利用cut和qcut进行区间构造
7 I3 Z$ F7 o7 W( h- G9.3.2 一般区间的构造0 t4 T2 M& g* l
9.3.3 区间的属性与方法; H+ `; {# D3 g2 N5 a# e
9.4 练习
2 G$ G% ^2 T d) |Ex1: 统计未出现的类别- `5 ~0 _. }) B" y5 t/ s& V
Ex2: 钻石数据集
% `7 H' m( Z% x( G, @" P第十章 时序数据
, s% h# U3 B5 s p, I# q+ b10.1 时序中的基本对象' J/ }' U. G# s% e' x
10.2 时间戳
( g# o" L- X+ C7 T4 _" S& k. {1 \' r10.2.1 Timestamp的构造与属性
8 z& `! I8 e9 p1 x6 x10.2.2 Datetime序列的生成9 u! f7 {9 `2 i/ }- h3 H6 e
10.2.3 dt对象
) |# p4 v- S1 L% {0 c6 r- _0 @10.2.4 时间戳的切片与索引& I T+ D: k! v3 Q: J5 K7 S
10.3 时间差
$ ]- N& v2 t- u7 c6 h10.3.1 Timedelta的生成
6 a% M/ ?8 ?9 i( s0 e1 v/ X5 o+ C10.2.2 Timedelta的运算
7 K* }5 {, ?$ D2 y% m0 n10.4 日期偏置
" Z8 O% v/ }( E3 g+ w10.4.1 Offset对象: J7 ?1 H; K) ?7 S
10.4.2 偏置字符串
: R9 \" x/ }; p* I+ L$ ?' T; r10.5、时序中的滑窗与分组- b& i; m1 t: B* s# E' n
10.5.1 滑动窗口' s# w& h6 K4 m/ Y) y6 r5 \
10.5.2 重采样
! _/ a) f1 R4 D5 r) ~10.6 练习
6 n4 U+ n( Y \0 IEx1:太阳辐射数据集2 K X( N9 K/ L) w& @% P
Ex2:水果销量数据集1 Z! E; A& ?9 D4 u( l
课程资料《pandas数据处理与分析》、github地址、讲解视频、习题参考答案 、pandas官网6 ` ^! w% y5 W8 m+ R+ N/ O
传送门:
- g! J0 n$ Q& j- R2 F1 d0 b' b/ @: X% p6 r6 t) P% d9 G, b
datawhale8月组队学习《pandas数据处理与分析》(上)(基础、索引、分组)/ w6 ^5 f- W7 a
datawhale8月组队学习《pandas数据处理与分析》(中)(变形、连接、缺失数据). X C" S* _+ ?% {* l' g
第八章 文本数据0 q( _7 [9 n! {" m6 ^! T/ P
8.1 str对象) B, L' N+ t# G
8.1.1 str对象的设计意图' F1 f+ X- H- }) J( m
str 对象是定义在 Index 或 Series上的属性,专门用于处理每个元素的文本内容,其内部定义了大量方法,因此对一个序列进行文本处理,首先需要获取其 str 对象。在Python标准库中也有 str 模块,为了使用上的便利,在 pandas 的50个 str 对象方法中,有31个是和标准库中的 str 模块方法同名且功能一致,例如字母转为大写的操作:
+ N, \* n6 c/ S6 B0 u' T. B- Z, X( d. m; G+ R" ^, ? R' c
var = 'abcd'3 t8 R' ?4 {& j5 ~ Z
str.upper(var) # Python内置str模块! u( S' c- e* `3 e* `
Out[4]: 'ABCD'" m: `2 w& q) r0 c
) R3 y, b3 o8 l1 O$ p+ O' H
s = pd.Series(['abcd', 'efg', 'hi'])% z B I/ c0 `$ _9 Z0 j z% B
# H% `, K0 l& R8 l, \
s.str D2 J6 {: b2 Z
Out[6]: <pandas.core.strings.accessor.StringMethods at 0x2b796892d60>
7 j, D0 C1 a' X( `- e9 A
; C& a8 J* C3 Y; ]( \s.str.upper() # pandas中str对象上的upper方法
& q* v# h" B) l& B3 mOut[7]:
& z, O7 \$ D. @1 I( R8 o2 G3 e0 ABCD
4 w7 i: G; _2 \. W. G1 EFG3 }+ g+ Z" G2 A* I
2 HI2 E4 }' O7 \7 |7 x( {
dtype: object5 f2 ~9 o9 I1 p% P6 r; i. G
1
/ L9 A/ ^5 N/ E5 L6 j# W22 c* k2 d( m t# V: |
3
+ M9 q5 V6 L# c' P e! |4' X, ~! q5 S0 n2 ~, g
5
0 o& H$ d: _5 B9 H u6. r+ b: k; j( W
77 Q* v! M4 q7 J5 ?+ b' F$ E
8, _7 W5 w# h, _8 B4 r, p4 M6 X! u
9! }5 U; \3 q; e9 \* h
10' }6 Q; b+ O8 t) Z2 H3 w, s2 l
11
9 p/ `- F5 \9 |9 K7 E0 W12
2 [, u9 Q3 b# u8 i! j% o13
; l0 [2 E( {8 o0 ^14
* B; G* e9 H0 W" g15
" v( T$ d, ?0 p8.1.2 []索引器
7 V" \' r4 B0 a/ U8 u$ ~# g 对于 str 对象而言,可理解为其对字符串进行了序列化的操作,例如在一般的字符串中,通过 [] 可以取出某个位置的元素,同时也能通过切片得到子串。
, K, w; H2 H( y! ?. u pandas中过对 str 对象使用 [] 索引器,可以完成完全一致的功能,并且如果超出范围则返回缺失值: `+ R! C! t% r- \3 t5 I
0 g/ e( a$ o- ^' ] g
s.str[0]
* [5 U" r! b5 U! \3 ~. q: fOut[10]:
: w* Q, d' g2 h4 v1 D0 a
$ x$ o6 @9 w' H$ F+ m1 e
" Q4 c, f! k* N9 J) M, } _2 h1 A! _) t3 O2 A% N1 a& d) u
dtype: object' x' s1 v/ a5 D5 B/ \0 |1 P
P# g3 r' o: d& O/ ?s.str[-1: 0: -2]
/ r/ A( d6 D( E, S. oOut[11]: 4 q( b. I) @9 d7 r- W) y& R! a
0 db
1 u" ~& M) a y& R6 r. ^7 |' V1 g
) \6 H2 I( }8 N3 \: B- Y6 {2 i
5 S& R, R: K* _' P2 \/ Pdtype: object
, G1 ~! a- `# @2 G S* Q4 ^; C1 P* y" e8 N" d. J
s.str[2]* c" T4 r7 Z' k3 G0 E. B
Out[12]: + E+ U7 g: L# S- x/ ?% |
0 c
- h% a$ M6 ?# P& y1 g
- L5 j6 o+ V) {0 q+ y2 NaN+ m7 W3 u- g A& q) O: C" l2 C
dtype: object
$ \; m; x5 Q l+ g, d+ Z( P$ B1 v1 a( |/ l
1
; [& o0 [5 z+ b6 c7 g& i23 f9 q+ h) D! k; x4 i9 a
3% e$ J# S/ Q( `: H1 w$ q7 p
49 x$ [& _% M. ?8 r6 y
5$ H- c, h# S& L' f1 q) K6 g4 F' e
6; e" T- H* E1 j& c- }! x6 E
7
' D, ^1 K9 S% z- B8
, U2 d7 U; y8 C5 v( C, _9 F9) \: ~ ]6 }( |( _
10
9 Y' r t; o$ j% G. S) _" X+ N! E11
6 d/ y7 @7 `" M% d! }$ B0 Y12( N0 h/ ~4 D: H, Q5 g+ M0 C. w+ o
13
! P% g- q$ @. m# t% Q! t144 h3 j1 m# b$ b8 Y9 m. v/ W( L- u/ {
155 w; x" r2 }; Z) |+ p1 }: e5 k. w# c# j
16
, N/ V8 w6 g2 Y" m3 L5 w17$ o8 v7 k: f! }, _8 {
18+ Z1 t" }" m4 E4 q6 C
19/ j$ z: B) y- [# ~& k# x
20
O8 h* _& q& ~4 iimport numpy as np
( D0 n, j9 B' Q0 t+ Z/ M6 p) u7 cimport pandas as pd
+ x+ \ E$ \5 C- I9 O) V5 U3 Y' e$ |+ V& n$ W
s = pd.Series(['abcd', 'efg', 'hi'])- d2 K) H" ~3 o: ]' ~6 w, Q
s.str[0]
) D/ O! h6 B- o7 `+ s! @1 M1, [/ b/ V2 D, L) w9 S
25 N7 j0 F* r! C+ d7 A8 c
3" J5 A4 }* P" D8 K
4& h, L7 @3 M+ D% Z
5! d% j: O- k* e4 e) V( C! @, J
0 a
( `; O$ I6 O7 k" h% @6 w8 p1 e6 o. _$ v& [- s; L! n0 M
2 h
5 Z6 I \& M6 p0 ~9 Y0 qdtype: object! I- @- A" x$ q+ v1 x# g+ {2 m. G
1. I3 K$ {! V, l; ]& u1 T
25 `" h, D3 `3 X0 u& s% g- D
3$ v1 J9 [6 n$ _! ?' H, P
4 T, l- O* E8 T( g
8.1.3 string类型 j/ @1 V. |" A
在上一章提到,从 pandas 的 1.0.0 版本开始,引入了 string 类型,其引入的动机在于:原来所有的字符串类型都会以 object 类型的 Series 进行存储,但 object 类型只应当存储混合类型,例如同时存储浮点、字符串、字典、列表、自定义类型等,因此字符串有必要同数值型或 category 一样,具有自己的数据存储类型,从而引入了 string 类型。
# v7 W; r8 E* @" k, T( | k 总体上说,绝大多数对于 object 和 string 类型的序列使用 str 对象方法产生的结果是一致,但是在下面提到的两点上有较大差异:
3 A% a2 m# L* t8 C1 y
7 g+ p7 d2 e/ t; @4 i( d3 x二者对于某些对象的 str 序列化方法不同。
. {8 d* x+ L/ K9 [1 }0 y# I8 y$ Z可迭代(Iterable)对象包括但不限于字符串、字典、列表。对于一个可迭代对象, string 类型和 object 类型对它们的序列化方式不同,序列化后str对象返回结果也可能不同。例如:% f7 F: ], A" d/ ~7 D
s = pd.Series([{1: 'temp_1', 2: 'temp_2'}, ['a', 'b'], 0.5, 'my_string'])
' _6 m9 }: H2 ks
. U5 t: j+ ]7 m4 A- a, |) s r9 D1( Q1 r+ |+ O) Z! L+ @
21 Y6 G( E& I& f5 z
0 {1: 'temp_1', 2: 'temp_2'}
5 T" i) v. T4 N$ T# `1 [a, b]
: t" Z, r1 h: Z: _2 0.5
: f7 k7 g* P: F4 {' W3 my_string
. n. T! ^( q0 Ldtype: object- |1 R3 }. b# ^' `+ }
13 d; i; `( D+ P. S
23 T' v5 ~% o: y5 C4 r" Q
3: f! X- R6 Y% a$ f; {6 I1 m0 B4 o# R
43 O! E. J" g, q# a5 `
5
m* ?" p3 Z8 Y# \2 x! m( Zs.str[1] # 对每个元素取[1]的操作
& q$ k$ `& j5 M: N0 l5 N4 ?1
% X" L5 S4 ]5 |- ~% x! B0 temp_10 W5 Y! g6 q( f+ h8 P( f7 ]6 k$ M
1 b. G5 [* d6 _! R
2 NaN
$ e% j! _1 \% f# ~1 m3 y
8 `! S6 {. u- k, Jdtype: object6 p8 o8 R; ^% g S' O; v6 P
1
+ u* D! V5 s6 ]2
1 v+ r8 G( P$ W5 M3% N% V) K0 h: m
4
" H) Q/ T" J1 a5 h& g# b5
/ \: R! f, I) u% `. y5 \s.astype('string').str[1]
9 ~7 b2 z; A2 ~/ W4 \7 U# F+ v15 n0 o* _6 q! T( K% n
0 1
" s9 r" h" O8 H1 '
& l0 @0 [8 M# {0 ]8 w3 X2 ." t( n5 y$ O4 d! S, y
3 y. I. j8 d' R4 ?4 ^; c _. Q
dtype: string
, j+ x" y6 M! h; B* }7 I1
' N- J; q# n, T/ @8 Q1 q2
! Z- q+ i# s; `+ @3
: S& _& y0 x3 b. }. x' F46 ]6 C- e x( S+ o
5
9 k$ \, f. V1 t1 j T除了最后一个字符串元素,前三个元素返回的值都不同,其原因在于:
5 f3 t h# o# Z0 e% d
# N/ _' e8 k' [) e5 Q7 w当序列类型为 object 时,是对于每一个元素进行 [] 索引,因此对于字典而言,返回temp_1字符串,对于列表则返回第二个值,而第三个为不可迭代对象,返回缺失值,第四个是对字符串进行 [] 索引。) d8 j) y- N9 G. S
string 类型的 str 对象先把整个元素转为字面意义的字符串,例如对于列表而言,第一个元素即 “{”,而对于最后一个字符串元素而言,恰好转化前后的表示方法一致,因此结果和 object 类型一致。
2 h9 C. o( [5 M" R @1 Mstring 类型是 Nullable 类型,但 object 不是
( K8 {1 }: [& v. x 这意味着 string 类型的序列,如果调用的 str 方法返回值为整数 Series 和布尔 Series 时,其分别对应的 dtype 是 Int 和 boolean 的 Nullable 类型,而 object 类型则会分别返回 int/float 和 bool/object ,不过这取决于缺失值的存在与否。
2 e _* w E2 [; o5 N 同时,字符串的比较操作,也具有相似的特性, string 返回 Nullable 类型,但 object 不会。; s3 }5 S% l/ r5 { s0 Q& \9 U7 Z
s = pd.Series(['a'])
- z4 D9 }8 i; ?+ N
% g/ e# p3 Z1 _; Y8 ^3 E2 y, p" ^s.str.len()
4 O6 r! p$ N$ E6 K; XOut[17]:
- H$ [$ y6 W6 F0 y3 q3 V! I0 1 k& u$ b# [9 h9 }5 D7 m
dtype: int64/ q! u8 P, `# C8 i. U- M8 j
. d$ H, B* ^& Z2 d; r! i1 e% Ks.astype('string').str.len()
2 e; u' F% j0 F' yOut[18]: ( C5 D t+ {7 p. w4 g/ E9 \0 c
0 1" r9 M x, R8 K& Q) P
dtype: Int64
! @- l% _$ ~, y3 f! G& ^. V
3 d( k+ R, Q% Vs == 'a'
8 h8 [ h; Y0 H+ v9 Z+ HOut[19]: # z9 A; z' k! G9 G
0 True% H4 i z+ ~ b7 o5 C5 b# H
dtype: bool
) m8 w. B% q$ g6 U+ I+ ]% r; r0 [' C# x* v. U9 ~; X1 F/ H
s.astype('string') == 'a'4 |2 [8 L% W# h3 b( y$ s
Out[20]: 5 w* k, A& Z. y2 N- U+ R
0 True
+ s' Z9 s" d7 U! i% r! pdtype: boolean: J/ R9 g/ x& H
- {) C% a) A- s- p9 Os = pd.Series(['a', np.nan]) # 带有缺失值
! a. u3 d+ N7 \; d/ l1 s
}( I2 O) a, @s.str.len()) ]: t* z0 o/ s" |* s+ o5 I* X
Out[22]: 3 F) ], n3 q8 T0 p& S$ d$ \; i
0 1.0* }+ c5 K% F. j: S$ D! A- M6 F, j
1 NaN9 |$ w. ]3 Z6 p! A ?
dtype: float64% `& J0 Y& b, a" W: y/ ?% b
2 W) ^8 M, b/ d5 D) m& S
s.astype('string').str.len()# n- X( |$ m/ z9 r- X" M, j
Out[23]: 0 j1 |* t% d( m
0 1% e+ N" q% B$ P+ j+ L: G0 N
1 <NA>9 [" k7 {1 H5 Z% x: X2 ~% I
dtype: Int64
- ~4 N+ `' ?& i3 [0 S; A8 X
3 `+ v/ ^1 w$ d6 Vs == 'a'
; m8 H C- G6 g l0 D& AOut[24]:
X3 w# \4 {, \" C0 True+ X* `4 G& f. e: i& U! o' g
1 False* a6 q( r9 N B5 t; W+ [
dtype: bool4 ~+ ~$ g: P9 E; J: p2 \4 q
0 g9 z2 \) l4 O' _2 U- |0 `# {
s.astype('string') == 'a', u; r. l4 ?5 ]9 H) t
Out[25]: 1 X5 G8 R3 t t8 L/ o; B9 P! r
0 True. e; R) x4 N* H0 n) v! G
1 <NA>
$ {! `, C# |9 i$ Q( j& |/ ydtype: boolean
\: y& Y/ F5 u' T
. i5 D; ^' {4 D9 y1
- }+ o* ^0 k$ o+ D) t- a2
( ?! H$ j- E: i+ A3, F% P+ G- L8 U% l. ^0 h# y! y
4
- a: ~2 i1 a- x2 m' J5
6 y/ {1 c, V# C$ W61 f( s. {/ h, I6 v' {* S
7
1 M. \9 }; F# V" Q, Q( {, ?8+ l) u! c }# H. H% S- q$ J" V
9' k' ~2 h) r1 O7 z' x* j
10
5 W5 `6 W2 z/ k1 e" [; O11
6 c7 Y e- {$ N' i2 u12
; E J) {- _) V2 Q2 l5 n ]6 H' s13. O! p* R5 f* s' |4 ^
14
6 u. M5 \+ d* c( |+ ?% c15- s! i% k4 h1 Y. K
16
$ e6 s0 b* L, i+ l17
2 `. l2 V& ?8 N; I6 K# k% J' O5 k18
: L5 y6 y' P. o ]0 B199 l- j+ m. P1 R, K/ `- i( P4 n
20, @- X3 {; q: |# }# x" Y
21
, T5 d" Q( B7 M y! v/ s1 e22
, g" M6 ?' [6 `, f' c23
9 ]0 G6 v2 N6 Q& |: L9 ]24' h/ n; b0 P+ C+ C
25
1 U2 [5 q+ _" V5 k) x8 `$ w) i26- j+ n) d- z |8 u8 i* t/ C) l
27
! E% m4 R5 L8 g7 L8 h2 `28
" j! r4 i. W. g3 E29
4 v5 i# }% J6 p% Q% `( z30
3 |0 R8 s! p# H$ X31/ x/ z8 B1 h9 G. g! X+ m
324 B) V5 \* ]3 ^9 T" j$ B: H
334 N$ g# K, K# b8 g( d6 v! c
344 k5 p2 Q3 @& l
35( X! q; F$ t* d3 _' f3 x7 `' Z
36
* A. u$ U9 z/ H' L7 l37) G% L. P8 e7 e; D( S1 X7 V
385 G7 ^- ?$ ]' x
39
* P. ]0 L/ `8 D- r* M7 C1 [: |4 z40
/ Y- a5 A( n: B y41
" B2 l% C- \& I, t/ |42# L. ?) ]6 S# M* b$ n& g
43
, L7 k+ O# H, }% Y, }. u44
1 ?4 g6 h& G A45$ ?" D6 _* a5 O% E' c+ X7 G3 `2 u
46
2 X0 U8 q* z4 H. ]47- O/ g8 p% i/ F
对于全体元素为数值类型的序列,即使其类型为 object 或者 category 也不允许直接使用 str 属性。如果需要把数字当成 string 类型处理,可以使用 astype 强制转换为 string 类型的 Series :
" k+ n. s6 y; G1 T5 `$ h) p# I9 U I0 j% K
s = pd.Series([12, 345, 6789])3 X- t/ S, U4 `/ x5 ]& p
" M( P/ X }3 i4 i! }( P
s.astype('string').str[1]" G& J. K3 _7 e
Out[27]:
/ T( a7 ?6 O4 A9 ~1 l! S0 @0 2
/ k3 @# A$ r% D* }1 4( D, w, D# r$ k! b$ X, N
2 7
8 C/ k: L5 z. j, I; u- g: pdtype: string, t0 c7 r) C3 ` I0 [
13 v1 A6 v# ?% R: a- K5 W% J, j
2
0 Y! |' G( B- M) W. \) y9 r6 j3
7 H3 w% y! D; o5 Z48 i7 x/ r& T4 R1 s3 n) @" T
5
/ D% S" [; d V9 o A2 C67 n" S2 y& P2 P+ e! U
7
7 \2 _5 v, o( f8
2 x3 e6 { m7 a- f" b" H8.2 正则表达式基础
: ?8 C/ {5 Y u# c. z5 X: i9 m这一节的两个表格来自于 learn-regex-zh 这个关于正则表达式项目,其使用 MIT 开源许可协议。这里只是介绍正则表达式的基本用法,需要系统学习的读者可参考《Python3 正则表达式》,或者《 正则表达式必知必会 》这本书) M, ?% v9 Q& Q
U( g4 D! v4 U/ u& `6 R
8.2.1 . 一般字符的匹配! A) w( w4 H7 w5 b8 \
正则表达式是一种按照某种正则模式,从左到右匹配字符串中内容的一种工具。对于一般的字符而言,它可以找到其所在的位置,这里为了演示便利,使用了 python 中 re 模块的 findall 函数来匹配所有出现过但不重叠的模式,第一个参数是正则表达式,第二个参数是待匹配的字符串。例如,在下面的字符串中找出 apple :
( G5 h. F/ x* j3 V3 ^& [! E) ^
9 H! Y, W2 O) U% kimport re
) r- {. J' |9 z% x( {4 D
" b4 }; _! \) [7 hre.findall(r'Apple', 'Apple! This Is an Apple!') # 字符串从左到右依次匹配' X8 _) u9 \; a3 i y+ q- R" W' P
Out[29]: ['Apple', 'Apple']" ^: @* d& l" O. I' \: @1 k; ]
17 W9 }0 \2 ^+ H0 M4 O* r* Z
2
6 k Y* p3 Y0 d7 T! H3
. N% {" f: X) @6 F5 K4) w5 f- U# k+ n$ v
8.2.2 元字符基础' l% u& {8 }5 M3 K5 q9 X/ Z
元字符 描述5 `, Z9 h# f! ]. S( c n, R* D
. 匹配除换行符以外的任意字符6 `/ Q4 s9 C6 u3 j4 ^
[ ] 字符类,匹配方括号中包含的任意字符
/ m/ B) @/ K' U# F[^ ] 否定字符类,匹配方括号中不包含的任意字符+ ]+ t7 j5 Q2 N- C* s( U, ]
* 匹配前面的子表达式零次或多次5 f. w0 k. K0 _1 T2 a& `
+ 匹配前面的子表达式一次或多次。比如r’d+'就是匹配数字串,r’d’就是匹配单个数字
' C* J* l0 v9 Q' E1 n? 匹配前面的子表达式零次或一次,非贪婪方式% k& x6 ]6 z! l2 D6 ]! d3 i
{n,m} 花括号,匹配 n 到 m 次由前面的正则表达式定义的片段,贪婪方式
' b! |' E$ h9 H( n( [(xyz) 字符组,按照确切的顺序匹配字符xyz
) V% H. i4 i$ O+ ^1 O* D| 分支结构,匹配符号之前的字符或后面的字符
# q: J6 `, |% Q( Y, Q\ 转义符,它可以还原元字符原来的含义5 |- ?+ O% b6 \
^ 匹配行的开始
" x$ |: I8 V4 \: c$ 匹配行的结束* j4 w" m9 A, ^: e6 T; J+ R+ s
import re
2 @! a/ ^7 O- H/ ?' w- \ |' Ore.findall(r'.', 'abc')
# E* C/ p& m% WOut[30]: ['a', 'b', 'c']
, d- q# Q/ q, ^, ]* T) p1 x3 m' E; R
re.findall(r'[ac]', 'abc') # []中有的子串都匹配
2 N$ g, u7 y6 [- X f" MOut[31]: ['a', 'c']
7 w% j3 c D4 G" q+ ]# t' v6 h8 y/ \) q. ~8 s+ W
re.findall(r'[^ac]', 'abc')
+ o L: S3 X! f$ B( SOut[32]: ['b']
. f% N/ b1 n$ y. q. b
1 v# p5 |# a8 n0 {2 y+ U# Z @re.findall(r'[ab]{2}', 'aaaabbbb') # {n}指匹配n次
7 F7 W, r6 T: y& r0 T# O& DOut[33]: ['aa', 'aa', 'bb', 'bb']' q( ]" P7 W% B, r$ D5 ~' i
* w+ Y" x% B: f, l8 S3 qre.findall(r'aaa|bbc|ca', 'aacabbcbbc') # 匹配前面的或者后面的字符串2 R" E! G s: P; f
Out[34]: ['ca', 'bbc', 'bbc']4 X" P1 X* \# Q Q" d6 z
5 z( H8 _, I3 P2 I3 Z# 上面的元字符都有特殊含义,要匹配其本来的意思就得用\进行转义。5 P* N4 l5 M! ~% u/ e# F$ h9 _; U
"""! o. z8 E1 ~' b
1. ?匹配的是前一个字符,即被转义的\,所以|前面的内容就是匹配a\或者a,但是结果里面没有a\,相当于只能匹配a。- a9 [! `& N0 A! n6 t
2. |右边是a\*,转义之后匹配a*,对于竖线而言左边优先级高于右边
/ F y: x2 N2 C% k* m# p& n$ I3. 然后看目标字符串aa?a*a,第一个a匹配左边,第二个a匹配左边,第三个a虽然后面有*,
% J2 y( U; @ |3 F3 Q' @' B但是左边优先级高, 还是匹配左边,剩下一个a还是左边,所以结果是四个a
( d4 l! @$ T, h"""/ J& L( X# D8 W* A
4 F }3 A( o& l' l# O3 H2 P& k
re.findall(r'a\\?|a\*', 'aa?a*a') # 第二次先匹配到a,就不会匹配a?。a*同理。2 s1 A! H+ u- N! y2 g" y- [* j
Out[35]: ['a', 'a', 'a', 'a']
! b: C5 }2 n' B q6 N- j0 R
& S6 ^$ H! k+ \8 X ?" ^# 这里匹配不到是因为目标串'aa\a*a'中,\a是python的转义字符(\a\b\t\n等),所以匹配不到。
5 g! m8 Z* O1 E( \) p) \8 u# 如果是'aa\s*a'之内非python的转义字符,或者'aa\\s*a',或者r'aa\\s*a'就可以匹配到\字符。
! B! {. W( s# }3 `) G. mre.findall(r'\\', 'aa\a*a')
: j; L/ e- ~1 W2 Q3 G3 z[]
3 u1 |8 |4 S) F# R+ V" W+ p
2 A$ r4 ^7 V2 z8 i/ H; zre.findall(r'a?.', 'abaacadaae')( q$ k5 V6 }4 I! r1 i5 o% ?
Out[36]: ['ab', 'aa', 'c', 'ad', 'aa', 'e']
( u1 k: G; {: ]- ~- t m2 K
`! C' ?5 z; e" P6 i# Y9 Sre.findall(r'(\w+)=(\d+)', 'set width=20 and height=10') # 多个匹配模式,返回元组列表
4 i* u3 Q' Y; U2 U# @) l- _9 Z[('width', '20'), ('height', '10')]
# f+ U V+ h2 ^# a$ v7 i! r
" @" V. H% I# T7 N3 M% }2 C, A: x1
7 k: E/ X% R1 D- h' F2* y6 U# x' s. Y U7 A: L1 G% G7 q
3) R/ _6 c# H X+ K4 a
48 g5 e' f, N: @. J' t/ J1 @
5+ o; A7 x# Z, D% L6 S- i `, M- A
6
) u4 v* |- T- O79 e: v9 @8 E k3 B8 b) g
8, g v+ ^# L* T8 v& t$ f; f B
9. G& R s0 K7 l/ R9 C; N$ ~* h# V( s
10
8 l: R% h; i1 k! t* [4 K1 q) c7 H11
4 s: x% w4 F& E8 ~/ v2 H) R12' r7 z' G: u4 G- X: a3 {
13! R# z' @: f- A! c) k! f
14
/ E3 w9 @* c" L/ Z* k15; v+ [3 A) X/ n
16* R, h: w1 Z0 ]7 F" c; _: z
17" F. U# m% e2 |+ g: `8 A
18. A p* s+ N0 b9 u5 a8 @! ^
19" R$ _+ D5 \0 u0 n m& \
20
+ W! M5 I8 [2 A- |* l, j213 H+ R3 [, B3 v6 P4 C) G6 f
22' g5 H5 d3 ]" Z. w: p
233 c3 S) U& `& [: {& C# E; @
24& y$ O8 O d3 s5 Z) W- [
25
1 O0 p( f0 s$ G: i+ e0 t) `. Z26
) r5 t- A0 c% F. t8 @: e27' E2 r8 L+ g: o& v0 m8 f
28
* `/ ?; S3 A2 s% M( ]$ S' l1 Y29
( B: n+ _6 ]. W i- u$ J* n30
" D8 d% ]& a7 s5 b! d# Q31
+ X5 Z3 |% F7 U; ?! I" _$ Y32
; x1 c/ C* K7 J# E9 S4 o33
8 Q' ^6 }- p+ c( H3 ~5 }34
# F+ i/ L$ `2 B2 ^ s35
. F) T; f+ L' p7 U. K* d _36
9 U1 @- A1 @' |0 M9 v/ S37
4 b, R* n2 s4 T' A- [8.2.3 简写字符集
- F% O! V. U: l则表达式中还有一类简写字符集,其等价于一组字符的集合:
4 b, I8 M' D- T% e- `0 S0 P; j1 o/ j9 `4 G. y: i
简写 描述5 \/ Z \; B$ s5 S3 w
\w 匹配所有字母、数字、下划线: [a-zA-Z0-9_]# A/ |: {, I8 i
\W 匹配非字母和数字的字符: [^\w]! o7 D% ]- c+ B; n: m$ h# v* {
\d 匹配数字: [0-9], P! e/ S3 V; q# R6 |( b
\D 匹配非数字: [^\d]6 w6 R" S( f/ X# s- R
\s 匹配空格符: [\t\n\f\r\p{Z}]1 }+ g% Q, ?- o# t8 O
\S 匹配非空格符: [^\s]
8 Y+ l" @0 X$ \\B 匹配非单词边界。‘er\B’ 能匹配 “verb” 中的 ‘er’,但不能匹配 “never” 中的 ‘er’。
* ~( d5 ]. E" O H% f6 [0 Jre.findall(r'.s', 'Apple! This Is an Apple!')! b, b* w% f) J. b+ z; e
Out[37]: ['is', 'Is']
0 v8 m" z& e7 I' _1 O. V
* w* _4 {/ e1 X: r5 m; \/ {re.findall(r'\w{2}', '09 8? 7w c_ 9q p@') # 匹配任意数字字母下划线的组合,但必须是两次/ D, A1 p E* v& B' B
Out[38]: ['09', '7w', 'c_', '9q'] o5 p U0 r! S6 u: r* x$ Y
6 G$ q8 v4 \3 X" ?, E
re.findall(r'\w\W\B', '09 8? 7w c_ 9q p@') # 匹配的是两个字符串,前一个是任意数字字母下划线(\W),后一个不是(\W)+ {% k) Z s7 x" L* X' A& S
Out[39]: ['8?', 'p@']
. Y# s( P" R4 X/ q$ t4 N9 O! h: I$ K$ h
re.findall(r'.\s.', 'Constant dropping wears the stone.')9 V( f5 j9 m$ M9 [
Out[40]: ['t d', 'g w', 's t', 'e s']
+ n6 i6 \9 C2 I) Y5 u- @' g- B% u' q+ V
re.findall(r'上海市(.{2,3}区)(.{2,3}路)(\d+号)',$ O0 n- W) k5 N# Y7 k) n
'上海市黄浦区方浜中路249号 上海市宝山区密山路5号')
s0 l, T% T% S" x3 I: Z7 I
) T0 i+ T. Z9 Y$ o7 U4 P& e* _* _Out[41]: [('黄浦区', '方浜中路', '249号'), ('宝山区', '密山路', '5号')]& N! Z, T( y3 b: t& p5 n
3 H/ C' m7 e7 T& b J5 K! ?
1
: m- I& D% A5 i2
6 \9 L- }0 f; ]+ d36 {2 b0 V- H# p+ Y7 _. S8 L; |( S
4
2 a; ~; D- G" g8 o$ M! X! W, I5! ?7 l' m. i2 @ `, d
6& _" j8 r, X0 h" v5 B
7
- D# e5 o: v& ~3 h- A5 b% w8# l# C. Y- x- z7 `
9
2 I+ _5 K( V0 X) y) y108 S6 L! r7 B- o9 n3 ?1 V
11& L( u3 m' o% \+ g# F
12& O( Z: ^+ @' d/ s
137 d& j) E( @ q$ d& j7 R
14
1 I1 v; H+ |! V# G, }6 ]' s) P15
, k" h& a3 e4 w4 N+ u* u$ N" R; t16
" v# O4 W* I: Y' l8.3 文本处理的五类操作
7 Z4 `) x) i, e1 N& M+ X7 r8.3.1 str.split 拆分
: }2 W" N3 J& x |: a* ] str.split 能够把字符串的列进行拆分,其中第一个参数为正则表达式,可选参数包括从左到右的最大拆分次数 n ,是否展开为多个列 expand 。( q9 c5 l! R3 ?, h ?* }" C
. Y# P. _/ i) e! O/ s" k+ c7 n
s = pd.Series(['上海市黄浦区方浜中路249号', p$ o) O J2 a" {, ^4 N$ T: m
'上海市宝山区密山路5号'])
5 o! P5 m4 h0 b2 F5 t5 I
! b# W% P8 g$ |1 o# T$ `
- n3 Q0 g2 ]7 |3 i( a5 Xs.str.split('[市区路]') # 每条结果为一行,相当于Series5 N% z7 C7 O. L( b
Out[43]:
) O' t& Q/ L3 |; \% _# |0 [上海, 黄浦, 方浜中, 249号]
4 s1 ]% P1 u8 Q( o0 }1 [上海, 宝山, 密山, 5号]3 N1 l' |8 Z+ e
dtype: object4 J. X8 D3 N: y: }$ B5 P
# n: Z$ _* U0 [* _* Z7 |7 vs.str.split('[市区路]', n=2, expand=True) # 结果分成多个列展示,结果相当于DataFrame$ w+ W$ a, I! y
Out[44]:
& p8 W; ?: x. {8 L7 A 0 1 2
# S5 K1 m+ e* G3 X& K6 l, q0 上海 黄浦 方浜中路249号
6 R8 D6 Z2 D5 P1 上海 宝山 密山路5号 u b! t1 D3 w! c! l
1
7 \ k% \9 b+ B( T7 {% t/ I+ \* V2
+ r, Z, u4 y5 |' e. o3
' j8 e, [* H# e4
" g1 L6 A, M! Z1 n5
# @% k% G1 D2 H- o6 }9 X2 O6
9 i1 h# \1 A0 d' U" e7
. K! V) J4 o( c) Q: o) c- d# E84 W0 y6 \! Y- @6 ]8 B# P
9' R4 B* O& [$ U4 m# N* L; Q0 G
10
6 b# {2 b2 G& Y11) a, H" g3 P- ?* w' X% t) n; o
12
& E* s+ }4 C9 W9 v* Z6 z, {! q8 @13
. a( \; F; L G; r14
$ H. T9 s o5 L15 a; Z6 f* b# n) J* M% c
类似的函数是 str.rsplit ,其区别在于使用 n 参数的时候是从右到左限制最大拆分次数。但是当前版本下 rsplit 因为 bug 而无法使用正则表达式进行分割:
9 X/ e# O) e& a' [- a* t/ l& a8 y0 o1 Z7 a4 a \) ]7 a
s.str.rsplit('[市区路]', n=2, expand=True)* S3 ]8 t" s/ ]3 K1 p4 q9 s
Out[45]:
) u! @0 l4 b6 z; {! K; f! b' f 0
" a% i8 i9 u7 B! A7 ]0 上海市黄浦区方浜中路249号4 R. l6 H9 t/ ~7 c! O
1 上海市宝山区密山路5号
9 {* ]+ ^2 o5 q1
9 _9 Q2 c+ b6 b2 C& a# _. [) v2
. A' r* O- i9 ^4 o S+ M3
6 W9 i/ d" a3 V# w' i# X& U4) u$ w7 n! ?8 r4 U
5
2 ]+ A( H; O) w) {2 l, E: S* z8.3.2 str.join 或 str.cat 合并
/ S. |* w7 ~1 o" K# vstr.join 表示用某个连接符把 Series 中的字符串列表连接起来,如果列表中出现了非字符串元素则返回缺失值。0 B2 A+ G+ S {! H1 a8 |: S# ~
str.cat 用于合并两个序列,主要参数为:/ F& f3 L) I! w3 {0 q
sep:连接符、9 h$ A3 S$ }; N+ R1 J# |/ v* G. f
join:连接形式默认为以索引为键的左连接
) E/ M7 O* I# h# e3 [6 e% `na_rep:缺失值替代符号0 ^. ^$ P1 g i& W7 o( k+ F0 k7 @) {; _
s = pd.Series([['a','b'], [1, 'a'], [['a', 'b'], 'c']])1 g2 b" x" ?1 a& u. t9 T
s.str.join('-')
- T. h, d4 p" x+ y4 lOut[47]:
3 [6 s4 i) r0 \$ x& I0 a-b
# Q- n8 s/ V$ p) J1 P( y: f1 NaN
) I6 V' F! U& S2 NaN1 Z1 R7 J# x3 L8 h7 O
dtype: object9 i3 P$ S2 g$ x! W' L
1' V8 l* H) U* q7 }5 Y2 g: `5 x
2
, b6 |7 s" k. v3 f5 s3
# b" J w; C) e! e2 n4
4 o6 G" D. W0 O; ^1 a; P7 Y51 c& g Y f0 ^" i o
6
* A5 n0 j6 N) D& p* ~! e: D7$ m Y& U# P: i' \/ ]3 i5 j
s1 = pd.Series(['a','b'])
) g H2 e1 Z6 W7 ]- Ys2 = pd.Series(['cat','dog'])* ?) Y1 L+ ~, F0 ~
s1.str.cat(s2,sep='-')
) t i+ `$ ]& r$ L* v) v% ?1 LOut[50]: ! z8 I) V c' c/ h. G. J
0 a-cat- {( [+ l" n* L' |
1 b-dog% A4 X/ S9 q$ m) \5 d& b; D- o; \
dtype: object& |6 C4 n( {' Z$ q* F
4 H8 _. C4 x, Z, |9 es2.index = [1, 2]
" b% t7 {6 D) J5 Y) Ys1.str.cat(s2, sep='-', na_rep='?', join='outer')
; i( |2 h9 d6 V6 n& vOut[52]:
; K' U" R7 }( q( P p& B3 e9 t% H6 i0 a-?
+ E, v! _2 D2 n1 A m1 b-cat) M1 r- Z v ?7 v
2 ?-dog/ ] m9 O# {9 y* U4 S! g$ E
dtype: object& j5 {7 Q' w; i8 J
1# O- J J, B/ }3 \& O1 c
2# e* c* \+ j3 D2 B3 O6 N. G
3
5 m$ Z( k6 ^" i4$ z+ b) |+ T" Y8 L
55 P9 _% V h& [6 z# {
6
( j2 S% v( c2 Y! ~7% ]) ]( i; }4 z2 I4 j! _
8' O5 Q8 M# C" \& A: S
9
( D7 w+ {7 z- r, ?" f/ g; P- I* d10
+ e+ |6 A& Y9 s* Z! M113 R( ?* U; U7 h+ A' |
12
S( A" E. o& b136 K9 }3 v% W# Q! t
149 N6 l7 \' C/ d; o4 g
15+ n% X2 i/ E, M( a
8.3.3 匹配
9 ^% i. A1 Z" X& Vstr.contains返回了每个字符串是否包含正则模式的布尔序列:
* q# {& j4 |3 ^% f. s7 {; Zs = pd.Series(['my cat', 'he is fat', 'railway station'])9 v$ O" l2 z o5 Q
s.str.contains('\s\wat')
" [, k) T6 ?( }! e" ~5 n1 E
6 i& g! u# y ]9 {1 R* z0 True( c- }# L; u6 n% E
1 True
3 t% p+ E% S* ~2 False
) [) p7 W8 v$ z+ t) zdtype: bool3 X4 y0 N2 d- R5 C% N3 H
10 X( p- b+ M. \0 [/ _! W; z' Y
2
0 I# s, N+ B- e" W% i" {3
. d7 Y: p& A2 z, I, @4
5 N) z+ {' l E" j% c# G$ R) E5" x5 h$ W8 f* c7 A( _
6
% n' C+ i8 ?: K! f. [0 x( q; J7
; _) F; p3 Q/ h' a3 estr.startswith和str.endswith返回了每个字符串以给定模式为开始和结束的布尔序列,它们都不支持正则表达式:
: U) ?" A" I* l3 q0 G1 Is.str.startswith('my')
6 p; ?9 E2 @0 F4 w; P* {( N" A4 l5 @- x
0 True5 D! Q& z' g( v3 Z9 X* g* J
1 False
; Y0 d" {1 Y8 a+ D0 h; F2 False7 S9 M) y) r4 v8 i# I* _
dtype: bool
8 |4 j/ U. l2 F [1
5 X8 P/ P7 W6 p: q2- u3 ]* I4 Y: R: L4 _
3
' K" J) ?! H: ^4
: p1 c$ Z1 a# U- b$ e4 J5
% G9 d5 p1 n: J- B- Z) |% _/ t0 X6
- b: D' {. w7 Es.str.endswith('t')+ W8 R& n* L C% O; c
v) C- d9 r9 V2 z) E
0 True% ?9 Y/ ~8 `' O. I* T
1 True! `" @. ?: L# g2 |
2 False6 ]' u g( _" u/ H
dtype: bool
+ _( q% B9 _; C* L5 P0 |1
0 L- K1 V+ q9 j21 ^. H, p5 o1 `
32 @) P' t/ t* u# x5 |; ]
4
! N* W. T! C1 f' T: i" i5
- ~5 [0 P* U# L7 d' @6
2 ]% F7 ^) C4 O* \str.match可以用正则表达式来检测开始或结束字符串的模式,其返回了每个字符串起始处是否符合给定正则模式的布尔序列。当然,这些也能通过在str.contains的正则中使用^和$来实现。(貌似没有python里的search方法)
2 _5 h4 N; X2 a" n% ls.str.match('m|h')8 y' ]2 V: M; E, m% ^* a1 R+ {9 a# z
s.str.contains('^[m|h]') # 二者等价
6 ~: }5 a+ r( B7 D B) G% V) G; D
0 True
8 s* o* e' U; `1 True5 p; v! z6 k- O2 M- j
2 False
7 M! c2 u2 r5 ^/ Edtype: bool0 f. k0 z7 f7 a: q7 i
1
3 ]! \& _# \4 Y6 _ E( M% e/ W9 y2+ i% \: W7 v- W9 N% q
3
4 c) x5 J; s! ^+ g7 n1 ]' s49 X: v0 W8 i+ A$ f
5
$ A5 f4 {# h0 d, p$ |6
/ q7 t. B/ E" k- ~, v7
/ N* |& n: R9 |% Is.str[::-1].str.match('ta[f|g]|n') # 反转后匹配6 j9 ]2 \% ^/ @2 F8 }
s.str.contains('[f|g]at|n$') # 二者等价
& O. u& d& z! V) g3 \2 ^ @. ~' K& v
0 False& S3 l& V! c$ l$ T5 D
1 True. F: L% n4 O" i) L1 K
2 True
7 m* x" z: B0 T( k; ^% Xdtype: bool! o2 @# A; d# d' j
1. n+ z: n, {$ w% u1 r8 B
2
( H( R# \$ o% @) t30 C, f7 w: t9 V( ~3 R
4
2 _7 Y" X+ p0 V+ Y% R2 m5
; C) Z% J0 m! I" k3 ]" p2 p) |6
+ S3 r9 ?" ^5 ?2 p4 x) t7
3 k6 y" y8 n8 O: v% }str.find与str.rfind返回索引的匹配函数,其分别返回从左到右和从右到左第一次匹配的位置的索引,未找到则返回-1。需要注意的是这两个函数不支持正则匹配,只能用于字符子串的匹配:
2 {0 Z4 M& B# D' v6 `s = pd.Series(['This is an apple. That is not an apple.'])# I- o5 ^6 J1 U& J2 r
3 N- t* l! E* B" N; Es.str.find('apple')- @ k$ x( A+ v2 J# l6 ^# W* Q
Out[62]:
3 W' `' z: |1 b" ], q( g0 11
- \4 r4 X- Q6 j0 ]7 M3 o7 wdtype: int64
% g& j% P9 }5 L' Q# j4 a) V5 V* F& ?% i7 @+ v% F3 G0 K
s.str.rfind('apple')# b. {% t! E2 Q
Out[63]:
D0 u5 \3 v3 K2 a3 h ?6 X! M0 33) ^+ H" Z; C& ^4 z0 R
dtype: int645 W5 q( v4 ?) Q7 p7 i6 b
1/ v9 ]$ _% D$ K
2
, ~& r- s. k' M' {6 D$ `7 J# v7 ?34 G" q5 c# h H) ?+ r" D
4
0 }' z3 M4 L) s3 n0 D2 y1 v5% m7 N+ q- e3 Q9 x( O; i$ C
6) g; u0 z7 [' R0 Y( N, L
7
( m! r( Z. Q: ?' ]$ g8; @- `! Q! x' x3 w' K
99 X8 q8 {/ i2 r; k5 @
10/ u5 o' I4 G/ {9 _9 W0 P
11
5 H1 }5 t! v- d9 C9 ^# `9 z2 p替换
2 ~1 q+ H: t0 h9 _: c9 x: n# s N+ F kstr.replace和replace并不是一个函数,在使用字符串替换时应当使用前者。
- i# h8 V8 ]' ]- d9 q6 rs = pd.Series(['a_1_b','c_?'])
% }/ k( j% K6 Q; u1 ^, a7 A' `3 S3 F# regex默认为True,表示是正则模式,否则第一个参数内容表示是单纯的字符串,也就是匹配字符串\d|\?
1 i& ?5 S" `3 f Ns.str.replace('\d|\?', 'new', regex=True) : \& e( z0 N" X8 Z& o6 J$ Z
% w1 @/ U( u& x7 W
0 a_new_b
9 F0 O7 t* P1 X0 x1 c_new
" R3 D% J) e, G$ |7 |7 t7 K: Udtype: object3 u4 _7 T4 J% s5 U$ \) ^
1+ I7 p. B) |. F7 S. c+ q! P% D
2' C# g0 k" H5 k8 c$ E3 | F
3
* `% g+ }6 G2 p% G4
' _9 L3 o* ?( ^. f- _) m59 b* j, e7 d" v' w% X9 Q* r* R
6
1 |; ^4 ]6 t5 _3 b) @3 \+ L7 B" u0 b* ^. R% q
当需要对不同部分进行有差别的替换时,可以利用子组的方法,并且此时可以通过传入自定义的替换函数来分别进行处理,注意group(k)代表匹配到的第k个子组(圆括号之间的内容):$ S( c/ B5 e6 k$ ]
# R4 d0 ]1 c4 fs = pd.Series(['上海市黄浦区方浜中路249号',
' j8 }( `6 i" j) n* R7 g3 ^ '上海市宝山区密山路5号',
1 Y) c6 L! i7 i4 m% h3 W '北京市昌平区北农路2号'])( j, I" s: y. M
pat = '(\w+市)(\w+区)(\w+路)(\d+号)'7 x$ S0 s& [/ z) w9 o9 S$ I3 p
city = {'上海市': 'Shanghai', '北京市': 'Beijing'}
( `: a( Z2 D- ndistrict = {'昌平区': 'CP District',! @7 ~4 [$ F$ z) d( S8 }' _+ V. D
'黄浦区': 'HP District',
% l; V' R, V" z) K6 j8 w: } '宝山区': 'BS District'}% m: F) L4 g7 A# D2 X
road = {'方浜中路': 'Mid Fangbin Road',
8 E8 Q% M. c' L4 i. _8 P/ a '密山路': 'Mishan Road',
0 g! C3 X7 l9 O, Z* S( b9 e '北农路': 'Beinong Road'}2 w; N) | Y' A: r& q
def my_func(m):
) `+ Q1 k# O+ U" d2 V p* ~ H str_city = city[m.group(1)]
6 l: v/ \4 W _8 B str_district = district[m.group(2)]' H9 ^' ]$ q [, B2 J
str_road = road[m.group(3)]: O! `5 i3 V3 W: t% F1 p
str_no = 'No. ' + m.group(4)[:-1]
# u' h1 v% I! I& X0 ^* a. c return ' '.join([str_city,) u8 U- Y7 l' f
str_district,
+ n! ]! @- R7 E# _! I* B! j str_road,
! P8 B% d: s0 G1 F# a4 n str_no])! P3 }5 h3 W5 F$ Z: R
s.str.replace(pat, my_func, regex=True)
/ ^" d/ v, H0 H7 B, M) a# `2 I# X; J4 b# B0 c8 |
1
/ \% V9 I! j- T9 H" Q6 Y2
$ @% G9 ?5 P+ T' G9 E3) m# H8 O+ [+ x X- @ H: r
4
" d' a: X6 Z- b+ g3 Y3 C& i7 q4 h; _5
7 H8 X' P6 s V7 T6 N6
* O3 h$ O; e0 C. ~7
' D* E$ M" ], j8 q/ o8, n0 E8 k1 ^/ u/ \4 ^; X9 U
9
& K2 r) M+ ^ \& Y- y102 O+ s0 B& U6 P; Z& T
11
/ o! E$ X: P6 y; m125 `/ f% b" A( v* { E
13
" |6 R/ a. x2 g3 ~! e, d- I. B14$ I% W M$ Y! A) O
15
% P5 c" f+ p: W, M% o) X8 G4 a, ~16, a- F, D) d/ t* D; ~/ D) U
17
) H( y- m; n# {7 s& j18& K$ R3 |' G, C2 ^
19
0 J: C1 S) ]& p3 @4 h" l3 l20
, T0 C! e6 G2 k219 Q" c ?# x2 f9 `( y
0 Shanghai HP District Mid Fangbin Road No. 249
% H/ ]0 [$ Q- e5 v: l ?+ E( h1 Shanghai BS District Mishan Road No. 55 G4 |0 A+ I$ e5 A' Z6 t
2 Beijing CP District Beinong Road No. 2
* i+ B1 y* U: w5 y3 l; ndtype: object! w9 b! F5 f) s: N! R1 L
1, @# o+ p9 Z2 K9 P7 _$ S
20 H5 N! ~: Y1 g9 t
3/ i- j: E. Z) ]7 ^2 V a9 C% q4 X
4
9 l. [7 x; K# b6 M+ U. V4 o这里的数字标识并不直观,可以使用命名子组更加清晰地写出子组代表的含义:
1 n& b: H7 R8 s
4 n$ P# v8 v) @1 \# 将各个子组进行命名, _+ P* Z8 f7 c5 {
pat = '(?P<市名>\w+市)(?P<区名>\w+区)(?P<路名>\w+路)(?P<编号>\d+号)'( ^3 {$ l k9 U9 J
def my_func(m):0 w% x+ q2 @/ N: Z4 v7 H* N
str_city = city[m.group('市名')]
" n% o. f5 J* U! B s" T4 S str_district = district[m.group('区名')]
* ~5 A2 ?; }! \5 O str_road = road[m.group('路名')]
) Y T: \: o2 F6 u6 w2 r+ F5 d str_no = 'No. ' + m.group('编号')[:-1], x$ A+ D0 b) b' k0 C' G4 V
return ' '.join([str_city,* b; G6 x. T$ ~6 @1 j
str_district,% w7 P# ?5 |$ G, O
str_road,
8 n. h2 M1 D4 _7 @4 z str_no])1 v% N3 X" v, \, @+ R) v# U& i4 t
s.str.replace(pat, my_func, regex=True)
& B$ _3 t- ~7 S2 C% h1: E y0 r5 v; h
2
" j" P3 V$ b. W9 d/ I- B+ v5 c' D3
! g3 Y5 b7 `; v' @4) f9 {! m7 E: \
5' @. n6 y% Z5 i# q8 y
60 w' d" L0 o" b: T
7
3 R% X: W, d% Y8
0 [6 A% P5 s, z ~9 u9
9 o: ]& J J% j: {5 z10, p7 S" s/ ?% f
11* v/ ?6 A* L" f# k* ~
12& Z6 b. S% o& v7 n: ?$ W, D6 G
0 Shanghai HP District Mid Fangbin Road No. 2491 S1 Q# W$ N0 [
1 Shanghai BS District Mishan Road No. 5; e+ w( Z2 b n' m2 ]. b
2 Beijing CP District Beinong Road No. 2
7 |# f" j$ ]; Y& K0 Fdtype: object
7 D+ M1 M& a9 X& x7 u/ `1' ~! _1 t/ e$ Q5 B+ J
2) w+ g) \+ f: K9 w
3
_; C" s* f( _$ a+ \4% G6 X* c; e) d4 y: \0 w0 x2 V
这里虽然看起来有些繁杂,但是实际数据处理中对应的替换,一般都会通过代码来获取数据从而构造字典映射,在具体写法上会简洁的多。5 |% r/ q5 N/ y3 R o0 Z1 q1 N7 D, f: _
: t8 o( O: C2 V- i: r( L8.3.5 提取
5 S* [, l* d: h/ I; j9 _( W2 r4 y5 rstr.extract进行提取:提取既可以认为是一种返回具体元素值(而不是布尔值或元素对应的索引位置)的匹配操作,也可以认为是一种特殊的拆分操作。前面提到的str.split例子中会把分隔符去除,这并不是用户想要的效果,这时候就可以用str.extract进行提取:2 e- `4 y+ F# F
s.str.split('[市区路]')9 ^/ } b' z; Z/ c
Out[43]: 8 g, |' u. U4 m* o+ n; k
0 [上海, 黄浦, 方浜中, 249号]5 O0 }2 o2 H3 R" z1 Z
1 [上海, 宝山, 密山, 5号]
) h; ~6 z. U) }0 y& W* G4 K. q# idtype: object& W. S5 O( }, T7 ?
; u. Y( T; m- \6 `1 x1 R
pat = '(\w+市)(\w+区)(\w+路)(\d+号)'
- I6 g" r* n( v M8 e A6 b: ~s.str.extract(pat)
2 c+ ^/ k: i( E) o/ Z; q: \. gOut[78]:3 B5 O: g5 [9 A; F
0 1 2 31 j* H- c$ S* z* H5 o. V
0 上海市 黄浦区 方浜中路 249号
) ] l7 Q5 g9 m+ G. A' ~" \' h4 L1 上海市 宝山区 密山路 5号9 I0 l& Z$ ?/ ^; F7 @
2 北京市 昌平区 北农路 2号
, g$ e$ k2 e; J' g0 z0 F1
4 R; Q2 U2 P! S4 e2
+ s6 x+ t6 D: q. @/ M3
' w# C; t3 v9 a3 Z/ y40 @( L8 |5 ~" X1 N z0 R. N, f
5 W8 s7 e& R5 @6 e- v
6
5 s! { L2 z" q. R, Q. |5 y. V7+ o% }+ P; K0 A
8) P5 {8 \- [) p. O; K4 h Q8 O$ b
98 o8 V- }/ t* T. X" |6 I' i
10& ^5 \9 i& }, f1 E" H3 B% o
11* H3 q, g6 e: b* T) J2 Z
12
( J+ L, Y5 U+ }. f, ^% U1 [" i, Z13
3 F0 L, L3 J6 h; `+ X通过子组的命名,可以直接对新生成DataFrame的列命名:
1 d; U7 d% C" v) \; n& \0 i
7 x+ {- o% O1 N- T$ ]pat = '(?P<市名>\w+市)(?P<区名>\w+区)(?P<路名>\w+路)(?P<编号>\d+号)'8 h( N4 I5 J1 a+ [; u0 X4 ]4 m
s.str.extract(pat)
% r3 M3 P9 x! |& x! zOut[79]:
$ e( i$ p) L. | f 市名 区名 路名 编号
9 }" g, _' A- P6 j- g, b0 H8 `0 上海市 黄浦区 方浜中路 249号3 h0 U3 N$ F+ n a+ }, s- U4 n! p2 x
1 上海市 宝山区 密山路 5号
O w2 }% z5 x* C5 N2 北京市 昌平区 北农路 2号
, h6 j5 y6 H3 j1
6 v' T; l. F1 U/ ^2 o e0 t% F8 M; G2# U) W# S, ?* P9 y( l" x
3 j: X. H1 F' W
4. T0 {' q6 J6 w" H% R5 C
5
) C& f2 F" ]8 g% {% `6 a) A6
; _7 I {! t! `: \7
" Y1 H% ] ?" F7 U8 \str.extractall:不同于str.extract只匹配一次,它会把所有符合条件的模式全部匹配出来,如果存在多个结果,则以多级索引的方式存储:0 w; ]- O8 z# ~! D) R
s = pd.Series(['A135T15,A26S5','B674S2,B25T6'], index = ['my_A','my_B'])
" O r# k L& lpat = '[A|B](\d+)[T|S](\d+)'' M9 s7 x5 ^9 n* ^# A
s.str.extractall(pat)
8 \% r+ W" b$ m0 Q9 eOut[83]:
3 J9 ^; e/ A* `0 S/ w& T Q 0 16 Y2 Y4 |" D& I$ ]7 ?
match $ P z( G( j) G9 F) ?
my_A 0 135 15
" R4 p9 p8 R: s% H) z; O7 C( M 1 26 50 Z o. |% K7 S% m1 V1 R
my_B 0 674 2
6 J1 H" I6 x) E% |, V3 c) ~ 1 25 6/ H, p& c4 p o( k e( v
1( Z/ B0 U! G" ?6 a: I& ` `
2
, o, r' b# }% y" ~" t& ~3
2 p% Y l T9 P2 E3 a, Q3 Z% o4
7 u1 K9 L: ]6 @6 w% J( m5
: c8 B0 v$ T% }8 H, s6' o# T; w6 @" i" C! L
7
0 {# d: o1 u0 X4 F& w. e$ ?8
" N, Y7 M% y0 C5 [/ \; X. }9" w4 H( S( p/ R6 @. f. e
10
, {* Z/ d$ C) D& S: X5 J( U5 v! Zpat_with_name = '[A|B](?P<name1>\d+)[T|S](?P<name2>\d+)'
2 @7 X# s' z; R' `s.str.extractall(pat_with_name)0 N' Z- ^# F3 T0 E/ ]( z0 w
Out[84]:
. B9 h3 w3 t/ F9 x2 e0 G6 d1 @ name1 name2
, p: v2 p: r4 w, W) y match / v' R5 q; S' J; ^/ X7 ?
my_A 0 135 150 b/ c* i$ s' S* n
1 26 5
9 Y& E- t# a1 Mmy_B 0 674 2
' h! J) U4 g4 v" g2 b- J% u 1 25 6
2 u7 N/ N7 s# u( G* X" G1
5 H' j& N& `% L2
& {5 u5 n# B9 G6 I K" h3' p- k1 f& `: y
4
. g( E4 h1 v) u) P5 x5
9 R) o8 i+ w$ N" u5 m6: c0 T( a- d8 j" O, e0 F: ]
7
% h& r( e& a4 |% `4 C8
/ D/ W \% M( m q* C9$ a8 j! z( p, Q
str.findall:功能类似于str.extractall,区别在于前者把结果存入列表中,而后者处理为多级索引,每个行只对应一组匹配,而不是把所有匹配组合构成列表。
! C4 p0 |" A$ I3 n+ A3 ns.str.findall(pat) m7 L' ~3 {3 W- B7 e
1
% @! @" {* B9 {8 H5 q: k6 q5 }my_A [(135, 15), (26, 5)]8 ^) D; S" M1 C0 B
my_B [(674, 2), (25, 6)]
0 a3 r4 k3 a8 _2 U& ?dtype: object
, Z7 B7 z) Y: Z5 j5 s1
3 A% O0 W: e; i% o6 g" ]2! ?# G0 L8 p4 ?5 U2 x% E4 f' Z
3
6 b8 k% n! X2 W8.4、常用字符串函数
* K. c. Q' | a0 r' j7 m 除了上述介绍的五类字符串操作有关的函数之外,str对象上还定义了一些实用的其他方法,在此进行介绍。
3 n( \6 E9 {1 }. M% ^- W& G! R$ e" }
8.4.1 字母型函数
) y ^. Q( @/ c" T1 z: b upper, lower, title, capitalize, swapcase这五个函数主要用于字母的大小写转化,从下面的例子中就容易领会其功能:7 z4 a( U; O/ }- u q& |6 |
, v4 G+ ^8 B C# v# c1 J) D6 L7 qs = pd.Series(['lower', 'CAPITALS', 'this is a sentence', 'SwApCaSe'])
+ L5 n8 }2 W4 Z
9 D5 p8 Q- {2 B! Js.str.upper()
+ E% w5 X! Q3 a7 C [Out[87]:
/ n8 V5 I, {& ?0 LOWER; ]! s0 @9 Q0 u8 P. O) ^( n
1 CAPITALS
1 {, t$ O: N) R7 K$ u+ c j1 t2 THIS IS A SENTENCE
+ P& _$ [! |+ \! ~3 j6 K3 SWAPCASE% C- R9 b0 _! ^; T% n
dtype: object. G4 i2 W. a0 ]& H
) x* R; y) \: B e) N- v
s.str.lower()
1 R5 L! M- q$ W& eOut[88]: 4 r* k% M) v6 @2 e* |4 o
0 lower4 l4 A; o; b1 q
1 capitals- V7 L- e7 w1 ~4 U) m" R/ O
2 this is a sentence) r1 U+ d4 p% d! d/ E
3 swapcase! x$ V3 k+ I' P/ H
dtype: object
6 a2 d3 E- D) a" X) ^8 E
! X$ g1 n9 y" I$ v$ ^s.str.title() # 首字母大写
% | K# l0 [9 Y" }; H; wOut[89]: 1 y+ `* L D" V
0 Lower/ g: Q( S/ q- F4 O- p9 k5 ~9 g
1 Capitals8 v5 q6 h$ W5 O! r) M }
2 This Is A Sentence
- Y# Y1 m. }; W* C9 ?: @3 Swapcase/ d0 W6 v' A5 B4 }2 Z5 {+ F
dtype: object
% `# }- s; A/ A
/ @% S3 f( a* a/ z+ w% ds.str.capitalize() # 句首大写
) Y7 { o3 b0 {# q2 }/ D0 [Out[90]: a! f$ l* C4 M& Y6 `/ d3 S
0 Lower1 Z7 E+ N) d' R' ?( R
1 Capitals
! i( e! |7 [+ I7 d8 g9 P: U* w2 This is a sentence
1 L1 ?9 n( W# D3 Swapcase7 k( n0 g5 a7 e, n/ v. R0 c
dtype: object: a5 N8 Q" \) P/ c
+ p: @' Q, q6 K6 |$ [+ z
s.str.swapcase() # 将大写转换为小写,将小写转换为大写。
1 {$ @7 x! m. U+ L5 ^- n- y9 IOut[91]: # R4 ?+ n/ u, N$ B6 j3 H3 O
0 LOWER6 O( N1 f) K. W8 `0 W
1 capitals
5 D- c5 m/ z/ w: |/ }0 k2 THIS IS A SENTENCE8 b% V9 D; E% T, v# ]) c
3 sWaPcAsE t( e' u0 T0 Q$ y* T# w
dtype: object
" m- B, c0 K+ @# g' y' E Z
+ }# V- m5 k! Es.str.casefold() # 去除字符串中所有大小写区别
0 Y1 Y! c( X+ C ~: ?, |( x
1 W: G3 ^2 E9 ]' t0 lower
3 |5 \3 q5 {! u0 ?( V1 capitals
' ]# Y* d! H, ?1 X9 ~! t# `2 this is a sentence W# R- s" ]* S! C( R0 V
3 swapcase% i8 q2 U" @- |" R
- Z1 a, r' [1 x. p: X0 r/ k1+ |/ V2 I+ j' b0 g/ {4 ]
28 @% e) a, c# Q* e* i8 v+ t6 B/ U
33 _7 q5 K' b5 G4 Q& p% K. D# p
4# c# p8 v# w# j/ z) J
5
* i6 r( x- e7 T2 B& m6
' x; c0 _2 D8 U, B7
1 J; K6 B' `6 V* k5 Y( h88 |, B0 d1 W. Z7 a+ ~: F2 \
9
" ]! f- \: q& K( n; Z10# a: ]9 F' @+ L1 d' p) Q4 `
11( ~( Q) R2 x+ E6 B5 w1 R
126 Z1 O+ K7 x' A9 Q+ J& Z5 y0 @
139 s3 ?: E. ]+ u" E3 {+ T
14; i, v3 V$ W$ c) N2 Q0 V( f& d
15( J$ R3 f* E) b4 [4 q
16% [- U& E# Q9 q7 ?( _) k: Q+ S
17# O r3 p8 q$ i+ |' Q
18/ h% P8 h& T* c( B
19 P5 Q0 B/ j! q7 J
20
1 l% L) M3 y1 T( u* H21
! D3 X/ N3 u" G/ z2 W$ u! K22
0 h: p3 i4 f( i1 g9 _23) L7 S3 h* S) n, b
24: r: T* \* f: \
25- C* a& R* G3 ]8 P* ]. t, k- y
26
, \% _' E; b' R) C+ ?9 _27& L% w& f3 ]6 X- P" x. B* j2 Y
28! }& S7 u# H$ k9 O1 i: g
29
0 f- y( }+ D) ~0 o30
; b7 w, B8 Y/ ]4 L) @31
2 f' X. ?7 |4 q32/ W: O+ n+ b3 `
33+ H% c; F! Y/ \1 f' \' q2 ^! B! x
34: |5 Q3 ]. P0 Y1 f8 }
35/ g# @# x! \+ t8 ~
36
& E. X H: ]# p' S37
5 I1 b! T' w B38
7 n5 O' y/ T3 R- S39
# n- C+ b8 \6 {2 z( m6 I40% J9 K9 n. K, w* e1 W
418 m$ G. }( t. Y+ { K. {, h
426 M: d2 {( l4 N* Z% K! f' N# [
43
; `. m* i7 M' L7 `: c8 g# F44
6 J+ d% j- Q% x9 R45) {. H5 ]/ w, @- Y( s
460 K$ Q; u9 c+ B& G2 I1 e* R$ I# A& E9 M
47- k6 S N2 {# T. k3 U0 _
48
/ x( N+ B& T# u1 T7 `3 ?; A8.4.2 数值型函数
% O8 C+ M) l6 h9 { 这里着重需要介绍的是pd.to_numeric方法,它虽然不是str对象上的方法,但是能够对字符格式的数值进行快速转换和筛选。其主要参数包括:
, U+ [3 g$ B; q$ s
* A& P6 m+ x. ?- S) [3 Ierrors:非数值的处理模式。对于不能转换为数值的有三种errors选项:9 I4 R5 v) I' {4 H+ m
raise:直接报错,默认选项
: [- A7 Q* L ^( m! Z4 t. Q: Rcoerce:设为缺失值
) p1 V& `, e4 ]8 g% Eignore:保持原来的字符串。
7 ]8 L" X \6 I5 ddowncast:转换类型,转成 ‘integer’, ‘signed’, ‘unsigned’, 或 ‘float’的最小dtype。比如可以转成float32就不会转成float64。
0 F! D+ \+ w" c; ]3 x: ?* [s = pd.Series(['1', '2.2', '2e', '??', '-2.1', '0'])
# m$ G$ o2 C# `8 T/ H9 W G
# _( V3 K: A+ t& s1 \6 Spd.to_numeric(s, errors='ignore')
2 c+ g. m" K6 aOut[93]:
) k" a5 B* c1 Z- F2 \1 H0 1; K7 T9 f, s& s) x! K
1 2.2
$ n) T: z3 ~! V2 2e$ i8 N3 K' R# F2 y6 G
3 ??
: @ B- M1 N& k3 j8 I- u$ V6 `4 F4 -2.1
% L- B) _, D- }- `: ~# `3 B5 0
2 ^1 I5 I; d3 _4 s2 @! ~; i) y1 qdtype: object9 P- d0 x' }( I/ f2 \4 T% X( V
: A( U) v, T3 Jpd.to_numeric(s, errors='coerce')+ K. X. H( ]( P
Out[94]:
4 p- H8 Z1 M: I0 1.0% j* Q+ Y! v0 Y' r o
1 2.2
# O" U% n$ d& K! {! U# f2 NaN
, T7 u# m2 v( }3 NaN1 E& A# P) Q9 O9 J& c) j
4 -2.1/ I$ Q- [# F F& ]# `! h' \
5 0.0/ Y- O: t' M% ?+ d
dtype: float64
% V: \ ]* F' H1 F% g- Z+ X2 T6 }6 s. q8 j
1
1 H8 I" Q6 S0 O; O, x# P28 J3 [% Q) b! B0 Y6 I& K% M9 Q
3
& Y/ N& }' {) T, E4
- c* Y0 o! a6 M ~. }58 l; x( F$ t* W2 [' o
6
' W. |* y+ w$ I5 o) d. d9 ~! c7
* R L) e6 L1 @) ?86 Q) I9 Q @1 s' _
9( Y* w/ t. ] h0 Q. X- r
10
5 k* E- G; g+ K11
0 z( I; e/ _# _0 u* [5 G12
$ ]7 u8 B( a1 K) [3 g# E l) g13' E; ]+ j2 G( s2 e! ?
14
. D: _$ i2 D+ s15+ D& H+ z/ i/ p
164 \5 ~- y/ \& i. h: q
17
; Y7 G1 T) F( E( I/ N' _8 B18
6 O2 G8 j8 E( c4 r7 {6 v, z19
. A/ h0 t6 B0 y+ R. z" I0 f/ T- G20
5 u4 U% E5 T* v+ |' B# F21- I6 b- e1 g, h6 J' s0 F$ c3 S1 R
在数据清洗时,可以利用coerce的设定,快速查看非数值型的行:) M6 Y3 w4 A5 o7 C" t$ g' \ i+ g
3 ]# T. b- Y) Y2 ?! d% U
s[pd.to_numeric(s, errors='coerce').isna()]' Q- s, Y4 `. M& O. }$ z
Out[95]: 1 @" L- k9 Q; `9 ^" Y# t) y
2 2e
9 d; H- H8 a3 }) {3 ??
- z2 [2 e9 |6 j/ ddtype: object0 v: r& W; V7 n
1
0 f, @' T9 A/ P6 T& J; ~2
- P8 ]/ g( [8 a3
" |5 ]) X" O9 E; S5 r4 w4 ]% l# U. z. b8 D: v5 g! u9 T+ M& c0 U
5
" t( V/ d2 Z. y5 p' k! d0 B8.4.3 统计型函数
. P- B7 p/ T$ D) ^& B3 A count和len的作用分别是返回出现正则模式的次数和字符串的长度:" D9 [+ Y4 h3 }) h) D/ c" o) N
9 _# x! {- }* _4 j; [, [
s = pd.Series(['cat rat fat at', 'get feed sheet heat'])
) M; t( B' N: k5 {5 x
; v4 H9 d9 s) Ts.str.count('[r|f]at|ee') # |左右两种子串都匹配了两次 C% Y- t8 ?0 ?6 m
Out[97]: $ A7 D/ j( ~& @. x; d
0 2
( Z6 ?/ K$ i2 v/ p7 |1 2! e6 V& ?( d' [" P
dtype: int64
" F/ Z0 F4 t% v5 l* [& R& p) U- `) H# w% c$ y7 p
s.str.len()
! E! F6 K$ I+ M' C- }Out[98]:
1 g- o7 s' t: A2 B r0 145 S" s9 [% h3 A
1 19
u% {# e0 Y% z- Ddtype: int64
- x$ N( J3 |. ]& `; r( u2 b& F1% F e+ Y7 q2 F I' ]$ k1 ~
2
* a- ` Q! r: m/ U! m7 p) s3& d8 w: J1 f( Z
4
4 v# l4 v% }+ J& j- ]5: _7 h8 Q" u3 E% `& p9 Z
6
, K7 f" F1 s3 I0 A4 s# O' M7
. E. {& ?* z' m) x5 M- Y v: R8; o5 ~( k: {% R( o
9' o! a; G- |& e( L2 F7 W; D
10$ I2 o7 H( W7 E9 a
11
. Q2 H3 N& F; V% W12
2 i7 n8 M( w" E; S13) ~- ]6 c: x2 t' N, x$ y
8.4.4 格式型函数5 e" m4 b9 Q: ]+ l5 [* s+ W1 M5 H) |
格式型函数主要分为两类,第一种是除空型,第二种是填充型。其中,第一类函数一共有三种,它们分别是strip, rstrip, lstrip,分别代表去除两侧空格、右侧空格和左侧空格。这些函数在数据清洗时是有用的,特别是列名含有非法空格的时候。6 Q q9 z) X0 G; F5 F
1 f3 |, @; N3 `+ d2 N1 k
my_index = pd.Index([' col1', 'col2 ', ' col3 '])$ T- M" h) A$ U, b7 @/ V: C
. _' w# S+ F: k# V6 Q1 \my_index.str.strip().str.len()! B' A5 m: V# m; L) ~
Out[100]: Int64Index([4, 4, 4], dtype='int64')
) V$ Z3 G5 ^; o7 O7 ?
3 [' z4 @; P- m0 z- T" R8 w S5 Zmy_index.str.rstrip().str.len()' e' E$ [! I0 F3 i0 W2 b v$ d5 V8 K
Out[101]: Int64Index([5, 4, 5], dtype='int64')
4 f$ z+ _' w- S* |, Z
2 x: Y# V0 i6 m9 Z* \2 tmy_index.str.lstrip().str.len()
k* c/ m) m! U! N2 v0 j TOut[102]: Int64Index([4, 5, 5], dtype='int64')( i8 s9 `" `& B
1
. W- s9 D2 ?2 b* g: X. c2, Y0 i7 p, k I; G4 i, T$ y
3
# I: t7 {7 N* w# |: P( e" M' ~4
6 A0 Z+ r% t( L2 k" Y$ T5
. F" l- S \2 j2 }; Y# Q f! [+ h& A6
|) Q' f# [ |! B- y9 d6 l0 w9 c75 ^9 ^. q3 H4 s
8 d4 H7 V" H/ e% F
9
2 [$ F% K( _& q9 e W10
2 u, n5 A0 B' n: D# v' |3 C! \ 对于填充型函数而言,pad是最灵活的,它可以选定字符串长度、填充的方向和填充内容:
2 b8 @1 R; F5 t1 r/ ?; ^- H( m; i c, ~" c, I( b s! s
s = pd.Series(['a','b','c'])
) O/ P9 u, H9 c" e7 G: P( }& f0 s/ l% m
s.str.pad(5,'left','*')
- ~" L% [4 r3 L( f" YOut[104]: . q! l+ r$ I2 j: i8 L
0 ****a
0 T. D* |+ O7 Z; ?; W1 ****b `9 w& b- F0 g- E9 U
2 ****c
0 b! X4 |& W+ e' }: B5 P( ydtype: object
% J; u6 U- O2 D4 ~( y# H: `8 P2 u# J
s.str.pad(5,'right','*')
- X2 z- l' `3 @% l9 _" ?Out[105]: ' ^4 w& d( H/ Y$ W$ R
0 a****
% F1 h1 o' F" G2 L1 b****
& L- T/ ]3 f& h2 c****0 T) |9 V; q6 _* }# [& r& ~
dtype: object9 I- ^& }% Y& \
3 r2 V& c2 E0 w; Z/ s; t, ]/ H9 J
s.str.pad(5,'both','*')$ P% } G! y" i0 D# D
Out[106]:
; e8 D$ O' ^5 E3 `5 C+ d) s0 **a**7 c! L" y7 I& Q' o( A2 m& w
1 **b**/ s/ F' t, p7 E, Y, K! G8 O
2 **c**
& v7 v2 k+ S4 n4 d! \! M' Ldtype: object
; d1 y# @/ o6 C! G9 T2 U; N& E
, L! S# X5 U0 g+ v1
- Q7 @ \; Y, R: Z% g# l6 R2
' i9 t K+ F0 S \1 b0 j& B3
6 m* N k; R- x: [4 K$ g) }4
$ c# T- y7 r' ~4 V5
' R( ~1 ^, L' h0 ~0 e68 Z/ l$ a% i/ \! J. K) ]5 D
7% }+ @& P4 }8 N' B" n2 z
8
6 R& Z8 K$ b: \9( t* n$ S) d' ]2 l9 p; ?
10
1 y" p; M' a3 E( M11; i8 @# t' x. f4 N
12
: J6 E. ^9 }$ t/ @% L9 b9 M13# e( A$ s( m# e/ p3 Z7 Z/ B- _
14
# }6 D! [2 I0 U( G: I0 Q7 r9 X4 n, I15# [1 Z0 E9 D* {' _2 T+ j
16
: t- B! J2 S* o5 [17
1 g% ^$ l0 M8 t2 H/ a( q: p18
6 h: Z2 P( e; M/ j. p! x% q) H19" h& g; L) b2 P; [
20
+ S* T) ^7 B7 w215 m# ~7 ?# j: p2 }1 U% ?7 T4 z4 O
22
; x& c8 \' i4 e, y8 W 上述的三种情况可以分别用rjust, ljust, center来等效完成,需要注意ljust是指右侧填充而不是左侧填充:
. u6 F4 g5 m; f# ~6 G3 o, E* B- C
9 m' t! z2 C2 k N' c, ms.str.rjust(5, '*')0 q+ `9 A$ I7 \0 _0 v6 `
Out[107]: % i, Z- G# T( J |2 M8 C3 n
0 ****a3 l3 k$ s) G& o3 }5 e0 Y. X+ `
1 ****b/ m( {/ _1 {# R# g& e9 R+ G
2 ****c& Q8 j0 R- ~ m- I# `
dtype: object
6 h) Q1 v( n" W3 O' k Z8 M# e0 f0 C" @ x
s.str.ljust(5, '*'): f, D" E; J S8 Q @8 F6 X
Out[108]: / r* u6 l$ \( i" Y/ K8 `
0 a****
' p6 `9 I, Z6 a* c- J1 b****+ I6 t( X* s$ n9 m
2 c****8 V+ u" |8 r, N/ j6 P1 h- _; O
dtype: object
' V) J# k6 [6 s3 @# q1 P8 p% l) O% v# m; V, Z' B" K
s.str.center(5, '*')
! h2 H8 ]; Z8 t$ x7 zOut[109]: 1 e5 ]- E5 t, X3 F; |' [5 O) f; Q
0 **a**8 I& `. z0 F, c S/ ]
1 **b**; N/ N! k! V+ F1 k5 I7 \, |' {
2 **c**
! J7 C8 F% ~/ G1 Z! L3 E4 b idtype: object
( y8 Y" H. U ]$ [& {2 w" ^) K0 s! }: [" K0 p2 O
1; m0 N. _! r( @. L; B
29 v- O- X- g; M9 j( F& S- n0 ^. T/ y
3- a1 k; f# A k9 W+ L, C
4
' O4 J! j$ n5 C9 J7 N" F51 b8 f. R! H @" u
6. `+ ~! X) {' U! Q
7+ d6 y& @3 k1 n5 w- z
8" H4 }5 M2 Q$ x! j F# d3 s
9' r2 M/ Q$ M+ x1 o$ K; J3 D
10: a. } A- P* _' r6 Q6 X
11
5 T: v/ a, E/ C& h2 j" p: T127 n2 n( J( z* c4 U) z% | R$ `
13% \8 t' K/ U7 B7 g. a
14# ]5 r( d7 q3 t8 a: M* f; x& b
15
- L2 G1 P# q" N6 Y: M16
# u9 X7 T. C, Y P17! j# r2 s' n8 r7 h) B0 i( Q4 Q, p
18
# y/ O% O; Y% R19
! [% _/ C. v* q( Y b; h2 A20
+ Y F; j, s. E$ ^/ N0 L 在读取excel文件时,经常会出现数字前补0的需求,例如证券代码读入的时候会把"000007"作为数值7来处理,pandas中除了可以使用上面的左侧填充函数进行操作之外,还可用zfill来实现。/ t& d+ h# z* A/ Q; h$ b
5 K% Z. b' K p) D" Js = pd.Series([7, 155, 303000]).astype('string')% U2 q' N& l( B& m" E" \
' F; N4 G: ^) I' d3 ^
s.str.pad(6,'left','0')
7 R% i q) d- a6 yOut[111]: 2 _, |( `( Z% w1 q' V5 [7 _5 J F$ d
0 000007 H6 u$ { O: X4 H
1 000155
, _" k6 w4 J& Q" A9 F% M+ Z o2 303000
+ U$ w- M: F8 {4 h$ V- O6 C. gdtype: string
( F$ E$ Y- ]' X3 [0 T, K2 \' O6 e8 x; D
s.str.rjust(6,'0')
2 L" a0 o1 N8 DOut[112]: / v7 m+ f- C& \$ `' h
0 000007/ I0 E" L7 T' d6 k
1 000155
; D! R, E! d9 ^& y2 303000
( t- C# k, u! \8 f! tdtype: string
" l. @% i& M, b) \ n( V' ] S: i: R; Y- b$ n* T4 I6 K8 \
s.str.zfill(6)
3 @5 y1 F+ }5 }2 e& x7 J: z2 n# AOut[113]:
B" e" @+ k0 N0 000007
7 L& V5 D! h. x6 c' Q2 t+ [8 p/ N% e1 0001558 f% E O! \8 Z
2 303000. z% M0 a5 l9 k% V4 {" }
dtype: string) e' b: J- I8 g, h9 K: }
2 v9 ]8 A6 C( z5 ~) @6 g* i
1
" J) I0 v, E3 I- P2
" {$ U6 m5 K! p6 R7 c3
+ A( R7 C6 x% b: B' x40 n! r/ Z, X# |& m3 K+ U
5
3 A p- P Q9 o64 v$ v) q# N* S R6 [3 N
7
. i) i! f+ B$ B4 \% B6 H% [84 X! P' Y9 O& L7 F8 P4 r0 T. g- a
9
5 X! Q. N- @5 y1 g) Z+ `10, t' i* K0 P( Y7 r
11
+ I5 \: B* Z6 ]( U12( i h% \4 e$ K) T! E
13- C/ I& {/ i! p; _7 J1 }
14
' a) v" F& `7 Q# x+ D$ }15
: `0 d' u; w# P& I+ G% P16
( q9 Z* w& u+ P" M; S* a$ k17% c: [$ ~5 ^8 n2 \: y6 k: V D
18' {* \9 V" \% D9 l( Y' `/ w7 T
191 Z# j3 ]) f$ i% |! j" C# Y
20
8 d2 ^; {5 G) c1 ?! L5 U21
# x+ V, p! I- B) k3 n' r22- i5 C% {% {2 v5 N6 F; c" b
8.5 练习7 R9 X, s( d1 _9 X" O
Ex1:房屋信息数据集5 w$ a# v# M$ |: d- I" N
现有一份房屋信息数据集如下:" O+ h% S: u" @
0 A3 }$ W. p% wdf = pd.read_excel('../data/house_info.xls', usecols=['floor','year','area','price']). E/ j1 a3 b6 U I6 d
df.head(3). p/ P L9 J6 W3 X7 I5 f( r4 X" s
Out[115]:
" I- j' x9 x' P/ F0 V floor year area price B5 I, I2 ^3 _
0 高层(共6层) 1986年建 58.23㎡ 155万
, q6 e4 p( B8 {# d6 B1 中层(共20层) 2020年建 88㎡ 155万
% y- s) n- V( F& c( r0 a4 }2 低层(共28层) 2010年建 89.33㎡ 365万+ j# T1 ?1 y5 V, z
1/ v' K1 c* P. a& I7 W$ n( m+ f
2; Y5 Q' k1 Z5 Q& ~' k
3
- l- y) n) v! J/ u. \- O P48 s/ F4 t' }) L' ]: u, B9 C Z
5
8 ] u, b K9 i% {0 X$ u" }' o6
- U, e, x6 d( d7+ x/ @: ~9 V( J6 g8 S* @1 H- _: _
将year列改为整数年份存储。6 ^+ @, D+ \3 h5 `0 S, q( |
将floor列替换为Level, Highest两列,其中的元素分别为string类型的层类别(高层、中层、低层)与整数类型的最高层数。) d9 K9 u4 o- R. x' Z
计算房屋每平米的均价avg_price,以***元/平米的格式存储到表中,其中***为整数6 @8 C& p- g, ~: n9 W1 i: j
将year列改为整数年份存储。
. n+ x+ }+ d/ ~"""; @ \: V+ b/ d% t! X8 l& Q
整个序列需要先转成Nullable类型的String类型,取出年份,再将年份转为Int64类型。
5 `8 U3 b! }" C- N8 Y; d/ j1 K3 {0 l注意,转换的类型是Int64不是int,否则报错。即使astype加参数errors='ignore'跳过缺失值,
2 ~4 S5 g" ~) i6 b转成int后,序列还有缺失值所以,还是变成了object。9 v: X2 h3 M- o* x6 L) J
而整个序列转为Int,就还是Int类型,缺失值变成了 pd.NA 。
$ G' r' j! n% x% I9 }6 ["""
3 b: B$ x; s I! qdf = df.convert_dtypes()- V0 g% o; \" }# I: F# B# @
df['year']=df['year'].str.replace('\D','',regex=True).astype('Int64')
; U1 X) T+ D; @4 \: j# Edf.loc[df.year.notna()]['year'].head()
/ j$ G5 U7 g7 T! ]7 c
# y9 N. m, z1 L8 F" `4 z% ^% r, m d0 1986
8 ?0 e* q, N5 q W0 j. T1 Q1 2020
5 V0 j9 ]9 y- [0 U1 V3 d6 x9 X2 2010
8 m# b7 P. o3 f M5 Z( K ]3 2014
; m* B# T. V, s1 |. R4 2015
/ H. m) P- O9 B2 W6 zName: year, Length: 12850, dtype: Int64, M( t( q; x3 ^/ C _. c0 E
5 U9 A4 I- l M2 j
1
- k: d3 V9 C' g1 z& ?+ m! C* `2
! Z( w; {5 g1 L$ f7 S' w, e3) O W" _6 `+ {0 g: ?* [
49 n$ j) o+ \5 J
57 X! u3 b5 \% I+ r6 A+ l, K# D2 G
6
. z5 T7 R( `: ]* E0 Z76 V E; Q. }9 U; f
8
% |/ r6 i7 ?7 P' B5 J0 g9
1 w" Y6 j7 K; y10
1 _9 P+ h4 o$ a11, w0 P. M: `7 |8 L6 h% W
12
$ j6 B- I" Z) y) I% r13: B: K# n2 a, Y% R; d# D
14
5 E2 V+ o% d4 z: w15
# K+ d2 ~$ i& ~9 D, P( ^9 E16 H0 P* h1 Q; L, A( Y3 B4 q0 \0 \
参考答案:
: b2 w% {% r7 R5 ~
) A+ }: k' }* U7 C) j q/ x不知道为啥pd.to_numeric(df.year.str[:-2],downcast="integer")类型为float32,不应该是整型么
' G" `/ M- C2 P! Q1 o& v+ ?) Q/ _9 g
df.year = pd.to_numeric(df.year.str[:-2]).astype('Int64')
, l8 U2 q$ u! V4 Z6 udf.loc[df.year.notna()]['year']) W6 j+ ?( \8 a# J
1+ J) p6 X. r3 X2 Y
24 U1 G& b3 J. r0 w1 g
将floor列替换为Level, Highest两列,其中的元素分别为string类型的层类别(高层、中层、低层)与整数类型的最高层数。
) ]* X6 o6 k% U$ D/ X6 [9 Qpat = '(?P<Level>\w+层)(?P<Highest>\(\w+层)'/ V* _' w) g( y* k
df2=df['floor'].str.extract(pat) # 拆分成两列,第二列还是(共6层得形式,所以还的替换一次
8 @. d& R- r6 g/ W( p+ {df=pd.concat([df,df2],axis=1).convert_dtypes() # 新增列拼接在后面,再次转为Nullable类型
0 c" |" P! x) u6 Edf['Highest']=df['Highest'].str.replace('\D+','',regex=True).astype('Int64') / Q" y6 T" V* ^! t) t
df=df[['Level','Highest','year','area','price']]# D, ?: n& } |
df.head()
# T9 `" \, }2 J
- B( _5 K) S6 v4 x Level Highest year area price
* V+ T' ?' Q8 a0 高层 6 1986 58.23㎡ 155万; I* w# ^+ k* D* u: ]& C
1 中层 20 2020 88㎡ 155万
" n+ l& i$ }* k- D" U2 低层 28 2010 89.33㎡ 365万
: r( W: X( i7 j1 N* h% w3 低层 20 2014 82㎡ 308万
. }2 W; E, l* h$ W5 H3 W+ h4 高层 1 2015 98㎡ 117万. S" z" Q/ |) X) B
1
) t3 o& O% y7 d) w9 y2- Q/ I2 z. L1 k0 d& G
30 B+ @& m" o9 D1 L" i
4
2 u3 }. c. g0 d7 }" F8 _4 Z59 R" L8 V! \) @$ g! W
6( h' a! G7 W% E( p
71 h% X: I7 D, k! z: j. ^
8) o& Z3 }) ?$ y2 x+ P% v
9' L) Q1 |# }: S9 F. s5 @, R
10, F8 `( X! Z6 B9 X$ x
11
3 y9 l. E/ i: U8 P) R12
( E j) K& P( ?% d. \* u! k13; `; K8 K/ y- i0 x$ ?* ?' K# w5 D
# 参考答案。感觉是第二个字段加了中文的()可以准备匹配出数字,但是不好直接命令子组了
# e* ?, Y. j% d7 e' M7 Ypat = '(\w层)(共(\d+)层)'
5 s: Q1 R/ \: i0 pnew_cols = df.floor.str.extract(pat).rename(7 J" }' Z" X6 O% S7 u( V
columns={0:'Level', 1:'Highest'})& K! @ U% y" W% C# i1 C+ g
' n9 h1 H) y7 b3 z, U
df = pd.concat([df.drop(columns=['floor']), new_cols], 1)
6 x' i1 u3 n+ Tdf.head(3)
6 Z6 \" ]9 u9 B- l5 e8 K6 Q7 E, q7 p$ `4 s" o, o& C
Out[163]: : r' X9 C* U; R {- Z3 U; _
year area price Level Highest
1 W) J* ~/ M. Q; L! z0 1986 58.23㎡ 155万 高层 67 j: {9 h7 F" T) H4 k
1 2020 88㎡ 155万 中层 20
# I- f/ V1 V K. R5 r2 2010 89.33㎡ 365万 低层 28# j5 `& x- ~7 h# l6 d& P& P
14 Y u) B8 H3 o# `
2 n1 X2 w( I1 C' n$ I
3+ w8 t. @+ h3 q- k" @
46 V( J6 _& p; N' M
5. i+ e. z$ l4 H0 r
67 J& z/ P/ o9 G* @" Z: C! v9 T
7
7 p. d/ l+ u7 i8
: w2 V2 F, z* x0 K/ ]9
1 n/ D8 {, ? M5 b10
) n( ]+ G' V: g% [11/ l8 w0 z; ] J( z" q9 V6 X
12$ d9 |# X0 {" }! d6 X
13: l! o B6 j- E9 M7 u9 S% b
计算房屋每平米的均价avg_price,以***元/平米的格式存储到表中,其中***为整数。/ D7 t% E4 X4 |) W. l
"""
; y- s( y- U! c. qstr.findall返回的结果都是列表,只能用apply取值去掉列表形式
9 b1 ?4 R7 \* q9 W! b参考答案用pd.to_numeric(df.area.str[:-1])更简洁
/ i+ }% Z. X$ @. \1 _; V; @由于area和price都没有缺失值,所以可以直接转类型
! H1 V2 U' x& s"""# C' ~1 f J" p% e/ Y$ e
df['new_area']=df['area'].str.findall(r'\d+.\d+|\d+').apply(lambda x:float(x[0]))( @+ P: r5 P, O5 N D" t
df['new_price']=df['price'].str.replace('\D+','',regex=True).astype('int64')
+ ]5 X+ i* ]- Cdf.eval('avg_price=10000*new_price/new_area',inplace=True)
3 A. J1 T7 [+ \2 Q) L5 k& v# 最后均价这一列小数转整型直接用.astype('int')就行,我还准备.apply(lambda x:int(round(x,0)))
( X+ v/ f# U$ l1 {# _( {# 最后数字+元/平米写法更简单
: C$ _6 ?: g; a9 N3 r; [# }/ kdf['avg_price']=df['avg_price'].astype('int').astype('string')+'元/平米'
6 t' w- W" m! J0 O2 _0 X% o# w$ \del df['new_area'],df['new_price']
+ f$ B. E. O! H! U" [+ idf.head()4 F2 o1 h" `% s- d* Q* ? ^! M/ H/ z
: B3 J% s! U4 N( V/ \! P0 a1 r
Level Highest year area price avg_price l6 l3 ?( S5 ?
0 高层 6 1986 58.23㎡ 155万 26618元/平米( f& ^, V: T& f( d$ Y0 ~
1 中层 20 2020 88㎡ 155万 17613元/平米, N( r/ `* D% v7 r" W
2 低层 28 2010 89.33㎡ 365万 40859元/平米$ g7 o5 U/ j5 M
3 低层 20 2014 82㎡ 308万 37560元/平米
% p; j, d2 I+ _% B5 z4 高层 1 2015 98㎡ 117万 11938元/平米! C7 b v+ T: @6 J/ b
* y7 K$ p- v1 \1 f+ o10 s/ M) V/ `! p2 D
2
% m( C3 q" }" e' V- C, X8 P' ^- z* Y3; m! l$ L; s$ T _ J
4
& g6 c; A) c% ?58 z+ y6 ~- |9 L4 p
6, I4 B( U) {: }9 F2 C4 n
7
& O3 G2 L9 ~1 h/ B+ i |89 s* M) ^" Y: C8 ]
9
% ` F0 H5 g" X8 I( c' x10
, q. W# _! o8 M% [11
7 t( }7 z5 q. ~12 ]2 e. y7 }2 X/ f6 @
13
; k! w: I% W# N! ]+ @14# H( F# w3 P; c8 Q
15
0 M: e& S% h O7 l3 M2 \16. n. I; _% v) x1 [" m1 H
17
' H) v0 F8 y, N18
( {# p3 {3 ?6 i8 t( h i19
. U5 z0 a( T e: n9 s200 Y& v& W6 D$ k4 l
# 参考答案
+ E- v; W) s x. ~* fs_area = pd.to_numeric(df.area.str[:-1])
) p0 I1 F' a1 v! V: }s_price = pd.to_numeric(df.price.str[:-1])1 h; }/ s4 i- U( i. u- ^4 \
df['avg_price'] = ((s_price/s_area)*10000).astype(
* x( R2 V' S3 a; ]! i 'int').astype('string') + '元/平米'
( O( m# m, v/ }5 M' T: {1 B
% B) h/ h# c& L5 Q5 a6 zdf.head(3) X$ X& d7 y& e" S* H
Out[167]:
" l# o8 r( d) k/ S4 F3 g& T year area price Level Highest avg_price
7 M% L% \* b9 ` O0 1986 58.23㎡ 155万 高层 6 26618元/平米
T d! v7 ` g1 2020 88㎡ 155万 中层 20 17613元/平米
3 `% o" y! Q6 x _. Z2 2010 89.33㎡ 365万 低层 28 40859元/平米, ]: @( J) b3 R, G; g: X
1: W2 n6 J6 i( Y/ @# e
2% G( D' H1 T# ?% c
3+ Q! ~- l" T8 J. D
4
' p/ M$ j& k" h ^% E5
' |: t2 E1 u; q' O9 [6 n0 X: L" v1 [62 E8 H1 ^. W+ }, v8 j% u
7
! D7 p8 G: e) u) w8& p5 G& l/ [2 |& [- k
9
6 g* g: P) ^- B: Q10
& h8 H1 b2 v Y* d# l4 P114 ~& T/ o! ^; y$ t, ~1 m$ d
12: ?8 x2 h# t8 j2 \. K/ [
Ex2:《权力的游戏》剧本数据集+ N" j4 o0 E" t! l' Q
现有一份权力的游戏剧本数据集如下:& ?) x6 E9 Z3 E! X& X/ ]. Q A
3 l: m! V. m1 o9 Q( Tdf = pd.read_csv('../data/script.csv')
* I! }3 A, j, m. S/ o- gdf.head(3)
# o& j$ r2 G Q4 B3 A* s' f0 y; l7 L0 q4 D6 k8 l) U; ~
Out[115]: " w! k$ k8 J% H# s4 U! j
Out[117]: 2 I1 f* L- h2 F6 B, O9 W7 h Q' j
Release Date Season Episode Episode Title Name Sentence3 n8 Y1 ]" ]# P
0 2011-04-17 Season 1 Episode 1 Winter is Coming waymar royce What do you expect? They're savages. One lot s...( g5 k1 t5 T5 `3 b
1 2011-04-17 Season 1 Episode 1 Winter is Coming will I've never seen wildlings do a thing like this...5 ~1 k' P3 p+ B, T3 l
2 2011-04-17 Season 1 Episode 1 Winter is Coming waymar royce . T0 S. r V& b- u2 v( B9 u; a, o
15 U# @: C! Z W# @4 y" R
2! W8 i& j+ R, f. ?; y! \# G ]
3: `, I1 F9 B7 }6 \; e( e# Y
4
; a2 Z1 D4 R6 V* n% w5
8 `4 ]1 g- u7 ~. R; p3 O1 r- y, V7 d$ D6
& M. @2 D) ?' {% u+ d& p; X7& T! k, p0 D& D2 z/ s5 A0 O2 c
8
3 H9 F3 f( [9 T# n( F( t9
: ]* B8 m- w' B/ l: @; u! }计算每一个Episode的台词条数。$ @6 D4 v# p1 O* Y; J5 [8 b) Q
以空格为单词的分割符号,请求出单句台词平均单词量最多的前五个人。- X* m/ {5 S' v3 f1 N# ?
若某人的台词中含有问号,那么下一个说台词的人即为回答者。若上一人台词中含有 𝑛 个问号,则认为回答者回答了 𝑛 个问题,请求出回答最多问题的前五个人。
" {; f; g* d, [) w* g& W计算每一个Episode的台词条数。
+ p: Z* Q! i9 v) A8 H/ |df.columns =df.columns.str.strip() # 列名中有空格
% f7 ?- l4 w- h jdf.groupby(['Season','Episode'])['Sentence'].count().sort_values(ascending=False).head()
5 G, a0 \% n/ j2 |
: z+ q- l( ^0 }season Episode 1 Q) X* C$ x" N! J B' E+ b
Season 7 Episode 5 505
S7 o7 A8 ~* T B1 Y6 ?Season 3 Episode 2 480
! @! W% L- q7 y: \, m. a; FSeason 4 Episode 1 475
/ E) f+ l4 T3 z8 P4 \Season 3 Episode 5 440
3 ~$ D6 |( n" OSeason 2 Episode 2 432* y, f, v6 B; J3 m ^8 Y( A4 }
15 ?! H+ Y+ s% ]$ b# A' l' M7 B
2
$ W0 \0 n" D& P' ~1 S3
$ q6 m( ~( T8 ^* W2 \: N; D4$ G" J; I8 f6 ^' E! i+ y2 L. M
5- j, E) }( X2 n" ~# }0 b0 a% `
69 Q0 P' |+ G) J0 a" ?0 s I K/ X
7
: B, c7 \7 K. S; g& Y) Y8# V3 S+ S4 p# J& y
9$ I6 @5 m/ ^4 G
以空格为单词的分割符号,请求出单句台词平均单词量最多的前五个人。
8 U7 n+ M0 ]* ` Q6 B# str.count是可以计算每个字符串被正则匹配了多少次,+1就是单词数% C" p+ O# }) Q& A$ r
df['len_words']=df['Sentence'].str.count(r' ')+1" Y& b' G% x# j- F# X
df.groupby(['Name'])['len_words'].mean().sort_values(ascending=False).head()
. j, I4 W) d7 A% i
$ R8 d3 \2 T: p" V- c- W8 m* T% eName
; K$ t& [" ?$ |' [( P% }, p1 Qmale singer 109.0000006 }: R+ U2 @# j: x( s
slave owner 77.000000! l) n6 e N( l$ g
manderly 62.000000( J4 K+ O/ O2 m) u0 F& P, I
lollys stokeworth 62.0000009 x2 a4 f# B4 h2 \: l6 ^4 a
dothraki matron 56.6666670 M$ B1 F" i2 w8 O
Name: len_words, dtype: float64# Y/ c3 o7 N& O6 r; J! Q) d7 J
1
7 ]7 b8 a% p |9 b, c5 `' j2+ [! ^& j; r8 B4 J7 c: x3 V
3& t1 h o1 ?) Y( J1 q! K. S) g6 G
4$ t$ B0 a" P$ T
51 l) k8 ]% u3 n$ G# S8 a
6. X. v0 p5 I$ l' t7 k, p& v. m. v5 K
76 B0 x: C' @9 s4 P+ I4 A/ g
8
% g- X$ l {, k; g& a( \0 G/ L9+ \6 x% h. [: e! Q
104 g4 D3 o2 s$ _) C4 x6 s
11
( J, K6 x% E% Y$ i0 L, W9 G9 Z' S若某人的台词中含有问号,那么下一个说台词的人即为回答者。若上一人台词中含有n nn个问号,则认为回答者回答了n nn个问题,请求出回答最多问题的前五个人。
9 r: t) C, t' s ^/ Vdf['Sentence'].str.count(r'\?') # 计算每人提问数
# |0 Y7 g3 E4 I1 Fls=pd.concat([pd.Series(0),ls]).reset_index(drop=True)# 首行填0! }# ]# G, ^8 }, q6 z1 G
del ls[23911] # 末行删去3 T( G8 r1 N+ H% I+ l
df['len_questions']=ls# R: J: ^% l% I9 Q
df.groupby(['Name'])['len_questions'].sum().sort_values(ascending=False).head()) b2 k1 P% d( z: e
8 k6 I8 H) M+ z' Q7 x' r/ I5 yName" _: H* D$ s6 F b! X% T2 C
tyrion lannister 527
! k- n$ x5 H9 N' ljon snow 374; X8 p; H* X( }; L$ @
jaime lannister 2836 U) g/ M# E6 P/ Y& M+ m) o
arya stark 265* W- E" h7 ^1 C ~6 \! B
cersei lannister 246# `) p \+ V( }
Name: len_questions, dtype: int64" m, G1 |! A$ m$ c. x2 X8 Q
) L+ z# r/ R, w
# 参考答案9 Q- r) X0 E, G0 G: u4 H1 F
s = pd.Series(df.Sentence.values, index=df.Name.shift(-1))
+ a% v& l% Z! a$ ?s.str.count('\?').groupby('Name').sum().sort_values(ascending=False).head()( L- z" Q6 u- i7 ]6 T/ z
3 ^" L* q t3 J5 g+ ?0 l- H4 \1
" M* M x! s3 s, W/ R2' P+ P8 Q2 \+ K0 l- S
3
" r5 ?. H% Q2 N0 f3 w4
- o/ w! r/ {# c* B% l m( d0 `2 ~* N5. H" j( p" k& o
6
! T5 V3 j1 ?9 |& Q+ R7
7 Q2 g0 i( @# R8
9 l% h. p& _ }1 h& e6 m* I9
2 f9 n% Y* k5 o) ^3 A. @6 Y) c10$ x" w( r5 F. c. o4 M( u( g8 x
11' v. Y# p2 M s: X4 @
12
# T0 c1 M1 [* C13# A# J, P' t( | T
14- ^0 w- B" K( W( }! k0 g3 x9 I0 N
155 |, u& \5 K' S+ B3 K; O; ~* j7 L
16; ~: n2 E; N5 X, E9 f3 A+ E( _
17
p3 w3 M& r1 ]' Y3 P7 q第九章 分类数据
8 \2 Q- t6 I' K) W3 o, iimport numpy as np' C# N6 r- a( Z3 z. B
import pandas as pd
# \$ ^& F# o8 I3 s9 o# r! i3 W+ E1: x; _: D+ y; _; y
2
- @; X8 U; f( B2 c; [2 N4 v9.1 cat对象# v, Q$ a# p4 ]; ?9 v2 L
9.1.1 cat对象的属性
. p: Z0 K; E m- L( k 在pandas中提供了category类型,使用户能够处理分类类型的变量,将一个普通序列转换成分类变量可以使用astype方法。: E( H o d3 b G* m8 D( t
& a. W7 u9 r) R' d: [/ z
df = pd.read_csv('data/learn_pandas.csv',/ o8 z, D/ j% x
usecols = ['Grade', 'Name', 'Gender', 'Height', 'Weight'])
) i/ {) t3 H% D8 m0 I6 ms = df.Grade.astype('category')
5 M' H4 n& w" E# R9 O$ D$ K) g$ t7 b- w4 d; [% K8 f4 h
s.head()$ r: g+ |; \0 h6 T. k# `, V
Out[5]:
( w5 `8 X1 y3 s- O7 [. \0 Freshman
6 ~( V$ r" D3 f$ X- j0 s1 Freshman1 ~; u3 \. z: I5 X3 ^
2 Senior* ~1 v- e; ?- k2 U }
3 Sophomore
" R: E7 o! V2 ]8 w) C& X4 Sophomore
' T: H! f6 M8 ~. |8 A" D/ c. M- LName: Grade, dtype: category5 O0 p* G5 u, z8 z4 G6 @1 q. I. g* e% {
Categories (4, object): ['Freshman', 'Junior', 'Senior', 'Sophomore']5 o& h% s: D# B, i2 F0 K) i
1% }2 [. s+ g2 Y1 X
2/ \% b1 E3 l' I4 s7 p4 c
3% [6 n! n1 {' Y' e; O U7 q
4$ H+ e" g' y! b
5( C$ ^2 J) Z$ b7 M% S _# X
6* H* n# h! m# @( g+ S# }
7# }7 O- ^" N# |& H# v$ A* R
84 T! L- W3 b# N" A5 E8 U* D
9. S8 z m4 L5 a
10
& i5 R+ w: i) `# V119 g3 l/ A$ g$ O
12
6 C& Z' g) m# b. S2 [( f# M13$ b. z7 |: j0 `6 q& X0 _( ^' q
在一个分类类型的Series中定义了cat对象,它和上一章中介绍的str对象类似,定义了一些属性和方法来进行分类类别的操作。
3 i: | k) i9 f5 [; d+ J$ E, \
8 S V5 y: P6 G1 P: f. m& z0 C( c& ]s.cat
7 e6 x9 K/ d. R7 c3 _6 gOut[6]: <pandas.core.arrays.categorical.CategoricalAccessor object at 0x000002B7974C20A0>0 D, I8 j0 h7 e
1
6 G( _$ y, C& L7 p/ _" {& p. J2. |# V1 l: Y# |5 N# ~
cat的属性:
" K; S3 S$ _4 Y8 L6 |! w1 ^# G* y
cat.categories:查看类别的本身,它以Index类型存储2 l& [9 X5 w' @' @+ i; Z
cat.ordered:类别是否有序
$ B3 F# s ?' f' j& l( U4 N7 ~- Acat.codes:访问类别编号。每一个序列的类别会被赋予唯一的整数编号,它们的编号取决于cat.categories中的顺序
& K4 v0 P0 Z# ~+ r$ W& rs.cat.categories3 O% H$ |: K/ L' ]8 P
Out[7]: Index(['Freshman', 'Junior', 'Senior', 'Sophomore'], dtype='object')( ^9 w+ I7 a4 N
. W. V1 M9 o9 L
s.cat.ordered
1 Y- M. W3 }4 }$ {! TOut[8]: False
3 c( f, t/ o- F
+ d8 e; t$ y( \ w! [s.cat.codes.head(): ^9 M/ q- y8 ^4 L
Out[9]: + x2 B( ?2 E9 v8 v. O# _( m9 j) m7 ~: |7 C
0 02 B: U0 M* \; V
1 07 I* A# M7 f8 P0 C1 v
2 2, T& v! C; A! |1 u- S% a
3 3+ x% d9 L8 Y; S2 L c' @
4 3, Y! m$ N. e+ n, s; z1 Q j- H
dtype: int8( h9 n( P/ j: u7 J3 B" o7 O
1
# `6 q8 J4 ]/ d( j: T- D24 F1 O7 K% S& O$ d9 Q' E1 F
35 ]: z+ T; r$ Y' G3 B9 K
4! @( w$ C7 w9 c0 \! w# h7 F, c0 J
5) f. [0 h: h; Q7 E9 c
6
" e- `: d* k+ j D7 z: F7
^% W4 @1 i/ [* V6 p6 W8) D5 f/ L u* X+ d; Y
9& @* O( j; Z# m9 ?' u# f# q+ y
10) Y+ W2 J% u# H6 o$ j3 q) N( [
11
; _6 z. D% U5 ^- l9 I12
, l9 [$ T- p9 e7 n0 j( _& g2 |13
2 K% v* g8 _$ u6 A3 `0 a14
+ n. m* a5 ^2 e+ d- e9.1.2 类别的增加、删除和修改
& |" C9 M7 T$ m6 q% U 通过cat对象的categories属性能够完成对类别的查询,那么应该如何进行“增改查删”的其他三个操作呢?2 X9 x$ @4 z* `7 G: S
7 |% l) j7 }# s【NOTE】类别不得直接修改" |1 x! m$ a6 q. N0 h& _5 R
在第三章中曾提到,索引 Index 类型是无法用 index_obj[0] = item 来修改的,而 categories 被存储在 Index 中,因此 pandas 在 cat 属性上定义了若干方法来达到相同的目的。
; X4 F6 f& x( U S. b+ t: L7 X6 a+ p- J! Z
add_categories:增加类别
0 r3 u; M2 r0 F% M S I8 i' _s = s.cat.add_categories('Graduate') # 增加一个毕业生类别
8 K, j5 u4 R+ O, F: B4 W* ds.cat.categories( M: `+ W* W% {* f4 e2 M1 |
$ c- v: ~3 C7 G$ x' S
Index(['Freshman', 'Junior', 'Senior', 'Sophomore', 'Graduate'], dtype='object')
1 S" O; ?+ b: O e17 o! t/ o% [* b0 W# ?- y7 ?
29 T# r# F" |8 f4 U4 U0 P
3
$ j2 ?4 u5 R& Z; d' \45 h/ l8 r' J) \+ Q1 i* S2 p- y
remove_categories:删除类别。同时所有原来序列中的该类会被设置为缺失。% C( Z5 d% @" [# H; F, i0 X
s = s.cat.remove_categories('Freshman')
3 K% ]8 l& t) N* ~+ A [+ c; T0 E- U, {7 e# C6 u! V- P
s.cat.categories6 Y: ~( t( U) {1 ]' h9 t5 d
Out[13]: Index(['Junior', 'Senior', 'Sophomore', 'Graduate'], dtype='object')+ C4 @) n7 E. k S: E( ]% Y1 B
, p- N9 k! J4 z7 k! |6 ^# X+ Hs.head()
0 `- a( |: x6 IOut[14]: ^6 T) o) x5 w+ C! F n
0 NaN6 h+ z9 A" K9 B# W4 M0 K
1 NaN9 F1 }* {. Q; K5 ?1 m
2 Senior+ O$ I. F5 x4 ~0 P8 o" F* C( ^
3 Sophomore2 ?2 c/ ^6 E2 L$ q9 z9 Q, S
4 Sophomore
- c8 p" F7 p* [! T* `Name: Grade, dtype: category9 N9 l; u, U: m+ k# C8 @) p
Categories (4, object): ['Junior', 'Senior', 'Sophomore', 'Graduate']1 U+ n% ^% v% ]3 ]
1
/ X5 q9 q% r) Y% I- o2, p( m8 Q3 ?' B) A, G/ a- S6 t8 Z; X) D
3* a1 F8 f1 D ?) F
4
/ h6 j+ ^4 O6 s54 V- p1 N" s& C5 p3 h" ?
68 C3 n. }: g% B' ~
75 t- {3 [7 l3 l5 Y
8
0 j6 r O; t t" j! i7 A9( s, ^) ~& f0 o* m( t" S
10
4 z4 e. H2 M7 q# s# t% o11, h3 e- h. e* G8 u9 S
12
+ [# q+ Z$ W6 w! @4 [- @13
8 b7 j- D! B6 z5 @14
4 M. B1 k6 Q% S4 Y h# Rset_categories:直接设置序列的新类别,原来的类别中如果存在元素不属于新类别,那么会被设置为缺失。相当于索引重设。) H9 E9 }2 ]( ^4 g, w- o- f
s = s.cat.set_categories(['Sophomore','PhD']) # 新类别为大二学生和博士* j2 M0 z. |7 ?9 s% X. S* Y7 S
s.cat.categories' E5 l( c8 Q- C
Out[16]: Index(['Sophomore', 'PhD'], dtype='object')
/ c' s# a0 h9 S: v4 T, p: z# n$ [0 `' ]; F# z z
s.head()1 m4 b5 V4 K0 Y2 x5 |) L
Out[17]:
* L/ C1 a+ s0 d) G1 Y) l0 NaN
, p( q& I+ T3 A. ~' K1 NaN
4 i) b( B( H( |1 k1 P4 b* i, O2 NaN
; r5 K, n, e( U3 Sophomore
! ~2 @% H0 L# d* [. Q4 Sophomore1 Q& M/ F& o. C
Name: Grade, dtype: category
' o# t# @* L g8 }; x( A gCategories (2, object): ['Sophomore', 'PhD']$ H3 S6 F/ e! K8 n, S2 V5 y0 C
1" r# D; m% i3 C; Y
2
e2 m$ f+ |( Z" E37 O M8 q$ _1 \( e) q
4
" L( _# @2 b: @/ X5
' T9 o. z' q* T4 ^1 j8 [) L# Y6+ e+ g) e( \5 @& Y2 V
7- M6 C* b; i% ~' G7 B) p w2 k W- h* L0 _
8
: Q9 q1 b4 \. D" N& q9' \( D- Z4 ?& p7 X$ {* u' }
10' c$ i! l) t x( ]9 Y* \# H; ~
119 d6 F/ J& P6 v) C: x& f/ W& w1 f
12
7 d' W8 d+ \# U+ G, i5 s13
) r: f9 t! L4 B7 W: v2 cremove_unused_categories:删除未出现在序列中的类别
; T4 P- K) l- k' j0 v5 qs = s.cat.remove_unused_categories() # 移除了未出现的博士生类别 ]4 J E. y) f3 ? A$ Q
s.cat.categories
* C+ V8 b3 | _9 Z
2 f# `1 s4 c; h+ B8 y, H, bIndex(['Sophomore'], dtype='object')- A. g: @$ G+ m0 P4 `
1
I* E4 U& l; T% V2
]8 a0 L9 y) ~3" f( ]9 q2 C$ G! Z7 b2 K; G' f
4
, Z4 z7 D) O X, Arename_categories:修改序列的类别。注意,这个方法会对原序列的对应值也进行相应修改。例如,现在把Sophomore改成中文的本科二年级学生:
. `* s* p4 \ h: t6 \$ L# \4 es = s.cat.rename_categories({'Sophomore':'本科二年级学生'})
/ E* b+ x. |2 T Y; l$ Z. _s.head()
C' y1 Y8 g- ^: c
0 |6 J; r! }: t( C4 o5 I0 NaN r0 s6 |6 P) ^, k- l E
1 NaN
/ P0 Y9 X; J$ |2 NaN
8 l5 }8 e [+ I" Y0 C) U3 本科二年级学生
0 _! [5 G( v3 `$ M4 e4 本科二年级学生. k3 D2 ]0 J" C. _- c) a
Name: Grade, dtype: category) c, i ]+ S. q9 K' a% `
Categories (1, object): ['本科二年级学生']+ Z& d! g. h9 X9 A3 H& i
1
. L8 P: J/ u; j# E; V4 B8 V+ W. w* l2
+ ?: Q0 b8 s$ L$ Q; X5 j' @3" G& D; V8 `$ [
4
( g8 Y1 o/ @8 {' x$ A5& ^ I8 A$ `7 R2 z) c
60 a3 u( z) Q% P3 N8 o. Q
7
# x" C4 | W: D6 U, ~81 C5 L, B6 b9 ?% F3 J& l: J
9; i3 u, Z+ Q+ P8 @3 v
108 R% J8 j# r3 e$ `& @0 G! x& g
9.2 有序分类
9 r/ { W% }! s9.2.1 序的建立
9 \" g2 l7 H& V$ B 有序类别和无序类别可以通过as_unordered和reorder_categories互相转化。reorder_categories传入的参数必须是由当前序列的无序类别构成的列表,不能够新增或减少原先的类别,且必须指定参数ordered=True,否则方法无效。例如,对年级高低进行相对大小的类别划分,然后再恢复无序状态:8 i8 K% o) A A' _! f8 j) o
4 e$ o. E3 K3 W- T9 L) H2 X
s = df.Grade.astype('category')
, _& O4 L+ J! P$ |) {, As = s.cat.reorder_categories(['Freshman', 'Sophomore',2 g$ ^6 m5 z' U
'Junior', 'Senior'],ordered=True)8 T0 d) e% g1 E2 ~4 R
s.head()
4 V" v5 l& ]5 z5 K& e, u mOut[24]:
. I% P- @) q: l" y2 G+ n0 Freshman9 h9 J7 c; I' ]1 b
1 Freshman
, p j" W; Q) S* o7 S4 P2 Senior
7 {6 D, i4 x9 V( M2 ~& z0 M3 Sophomore( d& B4 E4 w; c, r
4 Sophomore: ]! j3 W! t% C0 p
Name: Grade, dtype: category( u* v2 E2 T% N1 v6 d1 X
Categories (4, object): ['Freshman' < 'Sophomore' < 'Junior' < 'Senior']' d( m: P/ e6 D# E1 R
3 W9 N) Z9 ]" k' j1 s% z7 l* h. s7 e4 qs.cat.as_unordered().head()3 q$ z6 @, P9 [0 k
Out[25]: : W z: j: T6 M" r" S
0 Freshman( X! q% h4 O) D6 C9 L V
1 Freshman
9 C' j1 v) j5 T( D2 Senior
0 p- j. P# M/ g3 Sophomore
( `& O; t% Q9 Z$ C$ V# W6 x5 I4 Sophomore6 V1 Z" Z' O+ ]" }- x9 H
Name: Grade, dtype: category
* a$ E) T: C. S: w- _: Y; |Categories (4, object): ['Freshman', 'Sophomore', 'Junior', 'Senior']% U6 y" ?5 l6 J; Q% a
, i6 ]* d1 u( L1
7 X P9 R& F: m. x/ T- q: c21 p0 V" r; o! Q$ p
3' H8 ^0 U/ p- S5 t: j- N
45 x2 m G1 J Q3 _" [5 c
5
" i2 n3 M% @4 K, R6
2 z& _3 @5 ` U3 S( B7
8 O' g1 u6 \5 D0 U8
$ e. h& }. q0 L; E9& v1 O$ R- R0 O+ J6 g6 c
10
6 d8 {0 p+ Q! [. T11# \0 T) O! y; \# e! [5 w( o
12
, u+ [' @1 L) `. u* V130 q+ ~0 s6 z0 I1 M
14
: ^4 D1 J. H% l% Y5 W9 [, w15
$ t/ N# |) r$ H16; z c: W5 a. R% P
17; R1 [! u% Y+ u0 C' B7 Z* G5 O
18
+ w8 j1 X! a/ \: S K. R, p19
, m8 c8 g S3 l4 L) V0 Y20
/ ?0 W, F# i" c5 P. V5 E21
4 e- k# @) a. u22
5 J. S; W% i. r 如果不想指定ordered=True参数,那么可以先用s.cat.as_ordered()转化为有序类别,再利用reorder_categories进行具体的相对大小调整。
0 |# S0 ^) W1 A/ X1 ]: I5 m% H6 I) w7 N, z
9.2.2 排序和比较0 i6 n+ I( m5 Y8 p* _
在第二章中,曾提到了字符串和数值类型序列的排序。前者按照字母顺序排序,后者按照数值大小排序。. U2 X/ Y9 t' K; C" Q2 V2 G; q8 M' j% G
% R' c; _4 Y8 e4 U" q2 Z
分类变量排序,只需把列的类型修改为category后,再赋予相应的大小关系,就能正常地使用sort_index和sort_values。例如,对年级进行排序:
! `% e' i. x3 [ c2 {, E" @5 ?- ?" n. U9 g1 V9 }
df.Grade = df.Grade.astype('category')* G4 D. U: F. x* W
df.Grade = df.Grade.cat.reorder_categories(['Freshman', 'Sophomore', 'Junior', 'Senior'],ordered=True)
+ {) {$ D7 x2 Q. Y7 l0 H7 |df.sort_values('Grade').head() # 值排序. Q1 p( W; ]( ?$ s% j( p! W
Out[28]:
. _+ I) ~) n# ^2 V Grade Name Gender Height Weight
4 p% F+ D. T1 \. _( x0 Freshman Gaopeng Yang Female 158.9 46.0
3 Q3 h. ]4 Z {! i. _9 m105 Freshman Qiang Shi Female 164.5 52.0! C9 f F( t, o% H$ f
96 Freshman Changmei Feng Female 163.8 56.0
! f. R0 Y# o# }, x88 Freshman Xiaopeng Han Female 164.1 53.0
0 O! S% B, Q$ N# U4 `* S( g81 Freshman Yanli Zhang Female 165.1 52.0% T* U0 Q( _% _0 y7 s
% r- m: B- P0 ]* s0 K- O- n$ ?5 z# q
df.set_index('Grade').sort_index().head() # 索引排序
! |0 U( j; {( q4 zOut[29]: : K6 `3 c( f. V! W( j* n
Name Gender Height Weight
" M9 m: ~ _7 E2 _* JGrade
" B$ M' D. H8 _; S% _/ Q0 XFreshman Gaopeng Yang Female 158.9 46.0! f6 K1 b/ K2 B1 B" D
Freshman Qiang Shi Female 164.5 52.0
# C, x) ^% V( v; ZFreshman Changmei Feng Female 163.8 56.0
9 Q+ {: C3 }" V TFreshman Xiaopeng Han Female 164.1 53.0
2 |9 ?4 ^1 m9 I N( e. a9 @Freshman Yanli Zhang Female 165.1 52.0
! d$ }* n( s* J
" t# I+ }1 \: W- K) l( N2 j& M1& t. `( F/ A+ u5 }
2, o6 u K h2 _
37 `& ~$ Z0 L. S7 _
4/ M5 }6 e C6 `* _; s9 M' N3 G
5
) @: r8 S4 m: S2 z+ Q- q! z* J- W' X6
4 \3 `+ R/ \9 v0 n7! `2 b6 x# s& D" j
8% S: M" |# s( v1 J* G2 t
96 |4 `! o- N* e3 n% l5 c
10% P+ s8 G5 f1 o! M1 l
11
. e D C# _& X2 c" z$ I8 h0 B8 C12
% Z3 Q. \1 ^1 N% g' j; s- e13; D5 f) e* x& j+ ^1 J$ D
14. z6 l& W: o+ b; t; A
15( b6 e1 v/ k4 @9 b! S Y* l
16" G7 |# g# H: d3 M2 q
17
5 @& c# }/ I; v$ B( m& I18
; y$ C$ ~) R/ s199 R* B. E1 w( J$ L% q
20
7 W" j4 B' X1 a. `, t0 S1 u/ _: V 由于序的建立,因此就可以进行比较操作,方便后续索引操作。分类变量的比较操作分为两类:
. L$ g& A8 D+ B! w/ b0 d3 c+ G3 @) t/ s! Y" Q$ b& e
==或!=关系的比较,比较的对象可以是标量或者同长度的Series(或list)。(无序时也可以比较)
! ?0 H3 s% T% E! [! e9 z9 r5 [>,>=,<,<=四类大小关系的比较,比较的对象和第一种类似,但是所有参与比较的元素必须属于原序列的categories,同时要和原序列具有相同的索引。
9 b5 K+ N: s6 e$ Zres1 = df.Grade == 'Sophomore'0 o& u2 `+ W5 {: j3 y2 E
1 u& F2 w7 F* H1 |, X0 E8 E) ^1 j
res1.head()
$ y" w" R* f, _2 lOut[31]: J( O/ k. O) D9 e( M% ~" M
0 False
" G# w8 r) \& |1 False4 S# h, ~+ D! a' k- {- ?
2 False
& X' b( [* i( H9 V" x- B3 True8 i; a" o' z+ N/ X8 w* B- _9 R( X
4 True8 ^, N E+ M- n7 l
Name: Grade, dtype: bool4 @7 E6 ?9 `# h& _ _6 M, K$ z2 s
" T$ j9 s& a% |% y' [) {" Hres2 = df.Grade == ['PhD']*df.shape[0]
+ `; W- f, `5 d! W6 k; E( q8 A% \
) I+ \+ ]' ?5 l n% _" `# h6 m' t fres2.head()0 o8 a) o- A, F- t2 t4 \9 I
Out[33]: ' v t# M2 n a
0 False
$ M$ |; n) n" L1 Z( p; e1 False: ?" E* \& k1 m1 m( B9 Z, e
2 False
9 ^2 W9 P3 Q, v" y3 False
. Q) R( k4 O9 X2 M4 False1 @. O2 E% ^: N: V! k7 X' U; [
Name: Grade, dtype: bool
+ h% a7 F. N, H" H. ]% R& T9 X# D( m) z
res3 = df.Grade <= 'Sophomore'. p5 `% Z: l9 R) R3 [; |
3 C! |0 d+ I; d# X" Z) q! E
res3.head(); I; i( Q' v- l9 f2 ^, Y- {
Out[35]: 4 S3 w$ D- |9 \9 ]- H
0 True8 P5 J9 W9 m2 E1 l; Q
1 True; Q3 \8 \! I) W3 j9 ~' I
2 False
# @1 N( f3 e8 k. F# c9 a4 n, S* A3 True/ ^- R' {9 Z% H, {) m$ ~
4 True. Y$ _& j2 J- O& j0 Z9 a+ M
Name: Grade, dtype: bool* P$ r/ J# c' B% `/ T: R- e' J
; K' i' c/ d$ S8 A$ I/ _# sample(frac=1)表示将序列随机打乱。打乱之后索引也是乱序的,直接比较会出错,必须重置索引。" c% E" D& h) h; a$ d
res4 = df.Grade <= df.Grade.sample(frac=1).reset_index(drop=True)
& J* b$ g7 @# b% o3 g0 `
2 g5 ~. w: q2 w9 C9 j" tres4.head()8 p+ Z3 @" C- K4 r" z# t5 o% Z
Out[37]:
6 \0 ^ H& a6 P9 S0 True
/ h' W0 W) f; r1 g% d9 C& @: J, I1 True% A, L* _+ ~, |* A2 {) q
2 False
4 N; V+ s7 N6 g) l6 u+ j2 y8 \3 True- g. q |' W, P% v1 [
4 True# c/ O B# M1 @* [1 m
Name: Grade, dtype: bool
: P' T% |3 g1 J B( [4 _$ ?% ?! M9 n" M' m
1
5 v& p. W: u0 a& A2; U1 E4 h- o& p# E
3
0 P+ G. U; g( l, F41 v. G7 V. T) G H! f' ~# {
5% u% O- \" b' ?1 M
6! }7 t) X/ \4 p0 W2 y
7
: K! M! |* D5 g3 v& T( d) a8
+ W* _9 R, N! v. }- R9
$ z) Q% d% l, H! k- s/ R3 {100 Z9 X% ]/ ^9 [! f+ y, M( X `
11 g' t+ f7 V9 u8 y
12( s9 V* c1 P( O! S8 z( d
13
! a( l3 m* B; D/ k' R' ]4 F3 E2 |# ~140 Z E- w9 ]3 W8 L, Z
15
6 P- f4 @% s& m) J! ^7 [3 B7 f168 E b& K+ i" m" v, r8 P% `0 W2 s
17$ l( | j* }# w, F+ V
18 C/ r1 @: r/ G2 u" |+ V
19
9 s7 U% m3 O2 c- A; A205 T0 {( h2 T3 z0 n" `. Y) T
21+ N9 |7 q* n8 w
22) ?! o: S8 t9 q+ \9 \9 e8 M6 C
23
# }: j5 [' E$ x1 ^24
' a0 }' h* P5 y6 Z: A25
5 y) P! n7 q5 f; u% v0 P4 r26
$ B; P* B9 g; g- g: v9 }27) z$ C, ^+ Q8 q8 U: T" d
28
3 q2 P% x* R5 F. s8 B: |* Y29
# u# f) Z) S% [/ A; q% n5 u30
! x" W# C8 s( d. r% I. S( D, q31
9 A j& K6 h) k/ I9 X32% v* b) C2 O$ o1 S" O
33
3 [" o' E& b6 @4 w" o. B* I34
* a" E( l+ R3 k6 L35
4 M5 X. c! Y# n5 h8 c1 Z9 ]36
/ t. @) A3 j! u# i5 A37# [2 p/ A! k0 x* g- @" G
386 {% \7 j' K! V+ ^
39
. a" c7 Q5 {( _; Y# i40
* R; ]% ?9 o* u1 @7 h, D1 }41
& x4 e9 S8 \/ w! c426 M) r4 \+ r. G! x [
43# T- _# G. h u" X0 d+ S3 E+ }: a
44
: o& w8 N2 A0 e$ h8 O9.3 区间类别
3 @1 \8 `# `# u j5 A9.3.1 利用cut和qcut进行区间构造
: N# H% x& ?( l 区间是一种特殊的类别,在实际数据分析中,区间序列往往是通过cut和qcut方法进行构造的,这两个函数能够把原序列的数值特征进行装箱,即用区间位置来代替原来的具体数值。 s) Y0 |9 z6 I" h* c
3 v$ {8 s- w$ n8 w
cut函数常用参数有:
* C' P- n% q+ jbins:最重要的参数。$ S" \- P, w' D) H7 j
如果传入整数n,则表示把整个传入数组按照最大和最小值等间距地分为n段。默认right=True,即区间是左开右闭,需要在调整时把最小值包含进去。(在pandas中的解决方案是在值最小的区间左端点再减去0.001*(max-min)。)
B+ h9 G% A q$ |: [/ f/ Z/ q也可以传入列表,表示按指定区间分割点分割。8 {1 t2 ^6 f, D6 h) t
如果对序列[1,2]划分为2个箱子时,第一个箱子的范围(0.999,1.5],第二个箱子的范围是(1.5,2]。: g6 d) }, X7 Y+ x2 P' t8 M
如果需要指定区间为左闭右开,需要把right参数设置为False,相应的区间调整方法是在值最大的区间右端点再加上0.001*(max-min)。
2 F+ |' ^( q3 \6 x3 |4 ^# j- i" `! n
& Z5 p6 N5 d+ k- i% Js = pd.Series([1,2])2 {" y. R% |/ b* v
# bin传入整数
8 \3 f! Q5 d, ?4 U3 ?' f0 v9 \; K. D" z; ]/ V" Q! {, s) q# b/ |' P. o
pd.cut(s, bins=2)
; t4 C' W+ z' F0 xOut[39]:
. V, j: [# p9 A, Y- e0 (0.999, 1.5]
; ]5 S/ N* K6 Y! u8 Y1 (1.5, 2.0]% o. v9 Z' ?1 J4 V( t& ^3 G/ H
dtype: category% m' `9 ]* X8 [4 k
Categories (2, interval[float64]): [(0.999, 1.5] < (1.5, 2.0]]
# p5 W m/ \2 m1 L: Y
2 d* d: W+ ~3 @. d/ w3 epd.cut(s, bins=2, right=False)* l& z: ]1 }, _0 A: k8 S, P
Out[40]: " l+ w) x4 B( J: L+ U/ o
0 [1.0, 1.5)
, J7 }( n: k+ \7 b, K0 L- ]1 Z1 [1.5, 2.001)5 t7 C# R4 x! l7 S0 R7 h
dtype: category6 q Z. z+ V D: I4 p
Categories (2, interval[float64]): [[1.0, 1.5) < [1.5, 2.001)]
! P1 \3 m& X% E5 H1 @# Z. Z$ J! v5 V" g
+ b+ d& B+ {1 t4 N; D( u% Y( N# bin传入分割点列表(使用`np.infty`可以表示无穷大):
; @% y& V" L. y7 k" cpd.cut(s, bins=[-np.infty, 1.2, 1.8, 2.2, np.infty])1 B0 i) i1 K$ P
Out[41]:
$ K* ?. N; W7 R2 X0 (-inf, 1.2]
' J6 D8 g" l& D/ m0 e' C1 (1.8, 2.2]' S9 y+ g) g3 d1 ~9 Z1 l3 {
dtype: category
. M$ b/ s& C$ @) HCategories (4, interval[float64]): [(-inf, 1.2] < (1.2, 1.8] < (1.8, 2.2] < (2.2, inf]]
3 \- ?9 t0 k d# S" d
4 u- y0 }' }$ k, G7 a6 a1
, U: ~( J9 X/ }0 G: P; s$ M2# V- L( Z9 r4 O: X9 q) K
3' f8 l6 i) Q' I) {5 B8 K
4% \2 b H8 V, T/ \7 W& \
5
6 ]0 D6 ]! O2 }7 `$ c+ ?67 e7 w7 L2 ^0 s( r6 |
7
# o& ^1 l& D0 F5 y! B# R. D% Y8
* F- P3 M7 E) h# t1 R$ E9
8 [0 E: d( z F* B10- A$ |9 ~: i+ |$ U
11+ u8 A9 u+ F) G) N
12
+ M6 G( l) e. `) v. W. @13/ x6 n* h: ]6 i p5 {' O
14
; o. t/ o4 I A, B. @+ N15
7 O( f8 {7 x& T7 T \! E4 S168 L/ T r# i- ?+ O
17
% _% C7 c) W; }2 @- ]18
7 w% ~$ x+ b0 o. k6 D/ J/ {191 m2 i$ G* a, m" k% e$ ]4 i
20
6 u \: q6 x$ b0 y3 Y0 k6 y21
$ N. O; U3 p0 i0 D9 w22
; t+ W! K+ _- L+ Y2 k232 l; H' S7 s+ H6 i1 _3 Q7 q2 ?, t
24
! F9 }6 q' e! c; \25
) M6 F5 T' I& Slabels:区间的名字$ H, V/ F9 |: i& @. x6 g) p% i# b
retbins:是否返回分割点(默认不返回)) I2 j9 V% e1 h+ I: A
默认retbins=Flase时,返回每个元素所属区间的列表
. m$ {7 D9 E9 e$ l, Wretbins=True时,返回的是元组,两个元素分别是元素所属区间和分割点。所属区间可再次用索引取值
5 M; G* v+ U7 s3 [2 d. h) V8 K0 Z4 A5 z4 I, g1 H# b
s = df.Weight
/ }% c" o5 P: p9 n2 q* V" Jres = pd.cut(s, bins=3, labels=['small', 'mid','big'],retbins=True)* J1 f0 W9 G) K# J+ u! `$ O
res[0][:2]
9 U) [" n* k4 J- ~
$ q0 x6 M! |6 qOut[44]: + d5 V7 H* d; J+ |8 {: H: S" t
0 small5 b, P5 X0 v: m. k) B0 ]
1 big
3 c1 [ c. O/ G" \dtype: category
* @: `# B" L/ q; @ xCategories (2, object): ['small' < 'big']" A) M% c& v' S+ t+ ]
" x9 d3 s! V& {5 @- `res[1] # 该元素为返回的分割点* R& P# [; _ C
Out[45]: array([0.999, 1.5 , 2. ])$ i) W9 ^, w- J' V% x( W
1/ b' n& V8 A& |- l; [6 G% [" ]0 `* K
20 t0 B* a$ P! L' r# H& Y
3+ \/ f, w$ P3 n$ [- ] l
4
1 V% ~( Y/ B9 i8 {6 m3 S4 c W56 _/ n( {" o7 _2 a. G
6
' Z) U7 Q) b' i5 ~" J0 {7 t- B. g# R7" c: v+ ]; ?3 T8 r; M
8
' Y2 E( j2 y" c) M8 d& P, a+ E, d- L9% m4 N3 z3 a) U
10
/ ?# L% z: ?( q3 {5 O11. b+ z0 A6 ]6 s7 B6 y
12, f* C" `( R9 X
qcut函数。其用法cut几乎没有差别,只是把bins参数变成q参数(quantile)。
5 O( _( z/ C: \# l1 vq为整数n时,指按照n等分位数把数据分箱7 P& H5 T2 A2 i8 q* d" v i
q为浮点列表时,表示相应的分位数分割点。
; Q# ~3 v1 x [2 ^" J& @2 L3 As = df.Weight
: E; N+ {; Z8 |# U1 i: z' G1 F3 P; r u' x0 o) w; L2 u
pd.qcut(s, q=3).head()
: l: `! a: N+ V9 e2 l9 H. }, ^/ |" COut[47]: $ M$ K2 o, C! l/ t
0 (33.999, 48.0]
1 H6 D( x; N& d! N1 (55.0, 89.0]
: j% D* X U: C0 G1 ~" [2 (55.0, 89.0]
# }$ I6 x/ w; X1 W% h3 S$ Q3 (33.999, 48.0]7 \7 Z3 Z1 M$ W+ {
4 (55.0, 89.0]% g' | L# s } D5 C) C
Name: Weight, dtype: category
, j3 ~- U* m5 }, ?+ gCategories (3, interval[float64]): [(33.999, 48.0] < (48.0, 55.0] < (55.0, 89.0]] n, u5 T) T I c" _# X
6 Y& A) O% N0 r" B
pd.qcut(s, q=[0,0.2,0.8,1]).head()
! N9 Q/ I0 }$ K# b# J' f% LOut[48]: ' i8 }% j( ]$ b& q( Q0 @$ L# z; ]
0 (44.0, 69.4]" i! m: a( W; y4 j
1 (69.4, 89.0]
& }) U8 v- g0 T! u2 U- }5 b) E0 C& i2 (69.4, 89.0]
$ f5 a; j2 V; T3 (33.999, 44.0]8 B: ^; @/ O- L8 _8 p* f
4 (69.4, 89.0]! b. B7 D# O- T# X( ~
Name: Weight, dtype: category" w/ _1 L# `1 t
Categories (3, interval[float64]): [(33.999, 44.0] < (44.0, 69.4] < (69.4, 89.0]]# k* C6 ^! f2 J5 W5 c/ h- P
4 r/ z0 w1 @8 U; O/ x! j2 @1
! Y0 ]' z) s% C; W/ |( |+ ?# \2
: N* }- v" E1 s3 ]" F/ g+ V9 [3/ ^/ `' |! f! U" r% I
48 ], ?4 ^2 F& ^7 G% ]9 K9 L
5* D' P0 I- h1 R
64 ^; |7 `: s' @; h5 e: \0 V
7
# y' c# {! k+ }( t+ C+ k* M89 f6 z2 e# T* W( Z7 a
9
1 G6 l6 X: D8 [- J) D& f Q10" T3 z2 j5 G% j1 F' k
11
0 z& H4 n4 o! L) k2 Z! \5 D120 }7 w2 c- F4 X6 n" K/ H0 T# B
13
9 x1 i3 {9 Y+ U5 c+ O14
4 T( \7 E9 t D15
; c5 B' \* @# D; x* F a E% r16
. C, O" B6 s9 l" O/ g% \7 a! @ @. N, |' o17
: W) {( L b' G. x5 G+ j183 u- ]0 L8 i. K& \1 J% Y
199 c0 r! n) c k- d% [8 M/ Y
20" H5 f4 C9 @1 C# \
212 [0 Y4 C: u1 }2 F7 r
9.3.2 一般区间的构造! y2 c$ V `6 Y
pandas的单个区间用Interval表示,对于某一个具体的区间而言,其具备三个要素,即左端点、右端点和端点的开闭状态。
, ^ d8 M6 t. b' A2 ]( V* c( `" O2 E# O6 y6 R" o; B
开闭状态:包含四种,即right(左开右闭), left(左闭右开), both(两边都闭), neither(两边都开)。
/ A" S4 R4 r# m2 }my_interval = pd.Interval(0, 1, 'right')0 [1 b) q$ n7 t3 E) r1 x
4 n) h7 v" z$ ~, wmy_interval J4 d% |) r% D5 N
Out[50]: Interval(0, 1, closed='right')
- B, g6 Q6 r6 g4 c9 w# \0 {1
, D3 q( N& a. b# @0 N4 S2
* Q% g3 \- h/ {4 s3) m. q- s3 H" U- h- F$ w X
42 d3 f) U7 K) t( \+ U- s. H1 g
区间属性:包含left,mid,right,length,closed,,分别表示左中右端点、长度和开闭状态。+ k' E9 o8 U* t* B* ~% [
使用in可以判断元素是否属于区间
7 E5 u! S4 I# m- @/ V7 e用overlaps可以判断两个区间是否有交集:- {& [5 [2 o$ d; {
0.5 in my_interval
& a1 i* h k5 @3 s3 S! Z" H
1 a' v# q& y' f8 R9 tTrue4 F: K) w. @4 k" N7 Z8 ]5 G& W8 u
1
8 Q, i1 i% N) Y$ t$ }" h. R2
m: B. A: O: i& @, A3
! S' [7 B9 ^# d6 f5 hmy_interval_2 = pd.Interval(0.5, 1.5, 'left')0 ?0 ]# F' G. r( F
my_interval.overlaps(my_interval_2)
7 ^! I% t7 @+ t6 n# I0 a8 }/ G& _# ~! E2 E1 o
True
7 q, \, m/ R' i8 X3 e1# S: n, K! O* K, w
2
8 l( n. v: d6 F+ V+ K; m3
; U# L. y p. y3 Y4
/ |# m6 F' m9 ^1 y2 Z pd.IntervalIndex对象有四类方法生成,分别是from_breaks, from_arrays, from_tuples, interval_range,它们分别应用于不同的情况:( r; m9 E8 O3 b1 x% @
& N' n; ^/ p+ o/ Yfrom_breaks:类似于cut或qcut函数,只不过后两个是通过计算得到的分割点,而前者是直接传入自定义的分割点:
! H5 T d. [! l5 v+ Vpd.IntervalIndex.from_breaks([1,3,6,10], closed='both')
; n3 q% A$ s3 m# f2 |3 ]8 d5 X" r/ \) }& o6 n3 F+ B! }2 p
IntervalIndex([[1, 3], [3, 6], [6, 10]],
) }5 k2 v" m8 Z, c& R7 g9 L1 w closed='both',
8 d- l% m- t" v8 [! n% E" V" j8 | dtype='interval[int64]')
: v. w: H; N: b11 @$ S+ K. d- e6 H8 U' S
2/ Z! o" E3 ?) D# g7 A! z
3
! v5 m- d1 i8 N- N4 m) t( n4% |$ |4 d) I) ^+ s, `0 H3 o
5
& x/ }' b$ E& x6 E. d) q, {from_arrays:分别传入左端点和右端点的列表,适用于有交集并且知道起点和终点的情况:
1 v, \% G$ s! G, ipd.IntervalIndex.from_arrays(left = [1,3,6,10], right = [5,4,9,11], closed = 'neither')- w& ~- O; g$ M$ z& ~& Z$ _
9 X6 J' H- Z1 sIntervalIndex([(1, 5), (3, 4), (6, 9), (10, 11)],
0 ^2 @7 g, B2 K p) N8 _9 R. E closed='neither',
* X9 ?' I9 e8 G/ P9 F4 H5 q dtype='interval[int64]')
- g) K' f+ ]# i" T2 H- n14 D4 P# p' ~6 |% a# Y, {( n' S! r
2
: u0 r) O- k1 J1 q! j3$ p6 `$ z& s4 u! ~; A# v
45 y- o. b$ f. K9 F
58 I- Z* X& V( ?( b1 S4 r
from_tuples:传入起点和终点元组构成的列表:
/ a4 N( q7 k4 I& z. R% bpd.IntervalIndex.from_tuples([(1,5),(3,4),(6,9),(10,11)], closed='neither')
$ j1 o* Q( S! g% p3 \: D5 ~4 }3 z6 ?9 m- V" e7 e
IntervalIndex([(1, 5), (3, 4), (6, 9), (10, 11)],
! a2 z y4 p: b, C closed='neither',
: {7 C# ?+ P7 j: p1 K dtype='interval[int64]')+ x$ z* \( ~0 Q, @) g
1, X) e+ T; F! T. e5 h
2
. ?* v5 F, x, M" r4 R3
$ `/ @& E2 h5 y# x. ^4) v h$ [( W% v* k
5 _4 u* [6 T$ r3 ?* R9 T2 |) A
interval_range:生成等差区间。其参数有四个:start, end, periods, freq。分别表示等差区间的起点、终点、区间个数和区间长度。其中三个量确定的情况下,剩下一个量就确定了,从而就能构造出相应的区间:' C2 ~! M: y& P
pd.interval_range(start=1,end=5,periods=8) # 启起点终点和区间个数, e6 J; m0 p: g
Out[57]: $ h0 _1 I u+ l: Z" @7 N; I
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]],
7 j A/ R; V! R& ^: r" W closed='right',
7 o5 r' a' m! I, `; E, \ dtype='interval[float64]')
2 Y3 X, n4 P% ^1 e1 U
1 `% C, l& z0 U: Y1 K6 Apd.interval_range(end=5,periods=8,freq=0.5) # 启起点终点和区间长度
+ i; ]1 K* \/ p# hOut[58]: + m. Q I' [/ \0 X) ] q# j
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]],6 j* |9 y* h' d" _, ~
closed='right',6 h8 q" E6 |% N* N, Q: P) P; |
dtype='interval[float64]')
5 S9 k ~( W |5 X, C10 @( z# c; s; ]' ~! t+ W& n3 U
26 Z! o2 Q2 c- s( |! ?, S
38 p( l, E& V3 \7 `
49 G$ v; _( a- Q( A2 w1 V
5
7 Z0 @2 v/ h8 w65 j5 ]4 C, h$ C3 _7 }. R
75 G$ ]) q2 v& P
80 D* d; G+ }1 [4 X
98 s' c* C& H; _ }6 i
10
& v8 K0 e; X/ V/ l8 F119 K' y Q6 C9 P3 |& M+ |
【练一练】
6 t1 Z, z( i2 t' S. r7 |5 l8 h 无论是interval_range还是下一章时间序列中的date_range都是给定了等差序列中四要素中的三个,从而确定整个序列。请回顾等差数列中的首项、末项、项数和公差的联系,写出interval_range中四个参数之间的恒等关系。, m" D% _" W3 I
& P- g/ ^" v, R7 a6 o5 d 除此之外,如果直接使用pd.IntervalIndex([...], closed=...),把Interval类型的列表组成传入其中转为区间索引,那么所有的区间会被强制转为指定的closed类型,因为pd.IntervalIndex只允许存放同一种开闭区间的Interval对象。7 o# S2 L z! [$ d8 B) J
( z" D1 T- L2 e! F! W j( Emy_interval
% L, h- | c0 {# v o2 dOut[59]: Interval(0, 1, closed='right')# n0 C0 x5 s7 H& d h/ i% ]' Q
& r, w( b# ]2 h: W l( c" z" K+ |my_interval_2) N' h# b* `& Q, H6 N
Out[60]: Interval(0.5, 1.5, closed='left')9 d0 M) t, p. ~
5 z: [7 \, C# b+ gpd.IntervalIndex([my_interval, my_interval_2], closed='left')
4 ^) k- e, _: WOut[61]: 4 k6 H. ^& n) c8 |/ }2 _& q7 S
IntervalIndex([[0.0, 1.0), [0.5, 1.5)],, D1 }% [/ w& f1 N! z; Y
closed='left',1 X( K. z' n0 B! _5 {
dtype='interval[float64]')" {9 I# U9 W1 F9 |# M
1, F/ v; `5 ]$ A. t: a
2
( \1 z+ I3 f" f4 W8 x1 I3; F- c. O G% w8 k% B. t: W
4
/ V# f" a: |" }! v5/ M/ S# ^4 }7 n
67 b9 ?2 u+ c8 u9 Z& W$ A
7
. ^" s6 H" q E. ~1 B% S$ h$ U8
# l+ F6 Q+ g* E# h: {* U& ?' U* }9 d% S$ M% r0 C& I8 a7 S& T. v# ~
107 O) N: C$ J4 d/ ~; e e
11% v2 i" u& g: Z% t3 m
9.3.3 区间的属性与方法
0 u% m6 Y) ?7 [& f4 G IntervalIndex上也定义了一些有用的属性和方法。同时,如果想要具体利用cut或者qcut的结果进行分析,那么需要先将其转为该种索引类型:; y% {) d+ I* t$ p1 \. K
# z- {* ^- B( L0 |- B( m# @
s=df.Weight, O; U( j1 b l4 y$ x8 ^2 k
id_interval = pd.IntervalIndex(pd.cut(s, 3)) # 返回的是每个元素所属区间,用具体数值(x,y]表示
! L4 h+ y% b* g1 u0 T& nid_interval[:3]
7 X% ~0 T- X, A' e; P
3 R2 F; b% {. u4 YIntervalIndex([(33.945, 52.333], (52.333, 70.667], (70.667, 89.0]],# p9 ]6 M$ J; F0 D
closed='right',9 M5 w: h' X* M3 G% d5 E
name='Weight',5 j' L; t9 ^! }5 M9 i& B
dtype='interval[float64]')
3 A# k$ y4 s+ n3 ]8 Z) C* K6 \1
& L' y5 A9 N6 H; q ], }2# h. E& U+ F: x4 f+ z( ]' u
3
% R5 D$ N1 ?/ `2 S. h# E4
& J5 X/ C+ b9 [2 @# u5$ y! k4 |6 n- _1 ]) x4 Y
6" Z$ @2 x( a: L- T5 R) E/ w% \
7; \4 i$ I6 b0 F& S' X
8& D2 p _2 D( g8 p4 W% q
与单个Interval类型相似,IntervalIndex有若干常用属性:left, right, mid, length,分别表示左右端点、两 点均值和区间长度。
" d: Q" m `. c% B. V Xid_demo = id_interval[:5] # 选出前5个展示3 s3 F4 W4 B& J! R
) v" F# \. Q& G) |) k8 C, u& Eid_demo
, `5 S# |9 K, ?$ ]9 ?Out[64]:
! c x4 J( m$ E9 y) x- n* tIntervalIndex([(33.945, 52.333], (52.333, 70.667], (70.667, 89.0], (33.945, 52.333], (70.667, 89.0]],
& E0 j k0 i+ Z: N) s3 L closed='right',2 L9 i- E! ]; w0 }9 }, d; X: B
name='Weight',
- R3 o" q% O. _' G dtype='interval[float64]')( s0 n7 z8 C! Z6 R
, Y# G# e! H+ R- R" Eid_demo.left # 获取这五个区间的左端点0 I' X+ `4 t k( [! `( M
Out[65]: Float64Index([33.945, 52.333, 70.667, 33.945, 70.667], dtype='float64')8 } J0 s" n$ A% Y% R1 x
* t- X- H% o5 W2 g1 Y' ]" {& Uid_demo.right # 获取这五个区间的右端点
+ z8 d9 @0 d( ?6 ?* xOut[66]: Float64Index([52.333, 70.667, 89.0, 52.333, 89.0], dtype='float64')8 ~0 h8 ~9 a( p. P5 i3 z5 d
5 t, ^) u8 Q3 P. G/ i3 Pid_demo.mid: x9 {0 M! x1 x" s) |
Out[67]: Float64Index([43.138999999999996, 61.5, 79.8335, 43.138999999999996, 79.8335], dtype='float64'). r3 {) [$ `/ v* r, B( Z
6 S4 H( T6 j1 j/ C" Z% l
id_demo.length
) n1 Q& z- [2 N) m1 Z# S' uOut[68]: 4 D1 }* E/ s, A1 z
Float64Index([18.387999999999998, 18.334000000000003, 18.333,0 X1 J7 {' o- c" Z4 @" f
18.387999999999998, 18.333],) s* u$ G2 G% X5 j" K0 |5 r1 X
dtype='float64')
1 \( X; O* z. I! ]
) v- f" J2 r5 W' m R1& Q! @3 T- m2 |9 }+ c
2
* |- w" W3 C3 O, a. b' E" {3: Y# _: c3 N @+ @
44 Y, p# v) E" p1 d# m7 v9 F* N5 ]
52 K+ S3 K+ k3 @ H
6
s; Q* t3 D6 f# k7
) @7 v, c: R; I8 k8
2 `1 ^, l3 v, |/ E$ R7 p" T9, \% t G1 w3 _0 ?
10
3 A2 F+ @: Y4 i% s" q4 D+ O11" l; R* h$ U! W3 R1 y9 Z
12$ s3 v0 z# D& D3 W" ?
135 [! i. @- Y7 R1 F9 e( Q0 [. _
14) B4 m3 ^" a* A
15
% b' ?0 h, h9 V16
: m h/ w7 F" h9 F; `17+ A" l S( G" N( d6 y3 m: z
18
# ?3 C! F" C1 X- S- S19+ S4 I' }& f4 Q. P* {) |
20
+ D* F0 j/ K9 }: o z; C4 q21! W8 m1 Y8 d& r, a/ ~
22
4 w5 l% g( N% p% l23
! X. V) x3 n) k& K& x+ oIntervalIndex还有两个常用方法:
5 I) A0 w' N- | v: d" Kcontains:逐个判断每个区间是否包含某元素
, e% ]2 \% p0 ^ q* k5 toverlaps:是否和一个pd.Interval对象有交集。8 ?; o8 V* Z$ B( Z1 [
id_demo.contains(50); a: K$ o ^3 G' n% z8 t `: {
Out[69]: array([ True, False, False, True, False])6 Z d/ f1 {: t/ X
' w. h1 g" R! h
id_demo.overlaps(pd.Interval(40,60))
3 e5 J* y8 k* g9 }0 a1 R5 F- M' d% H% NOut[70]: array([ True, True, False, True, False])+ E# n4 g- y) N3 J, \ i9 h& d
1
0 Q# t2 k4 @" V2 a! t2
7 D' J+ E0 \ ^; v3
3 d( L. d6 L1 F# z5 N! m4) M% y( B8 T( |& g1 l9 ]" ]7 q: R0 l
5" r6 i- m1 y* ^+ h* v" D, Z) E2 }! N
9.4 练习, [& ?9 L0 y. Z9 Y
Ex1: 统计未出现的类别2 D% H$ f5 H* A: R+ U: Q8 J. _+ S
在第五章中介绍了crosstab函数,在默认参数下它能够对两个列的组合出现的频数进行统计汇总:! L2 H I7 V/ t
P6 z) J) E/ J( L5 ?! m0 b9 ]
df = pd.DataFrame({'A':['a','b','c','a'], 'B':['cat','cat','dog','cat']})
& p- d O) Q( |4 hpd.crosstab(df.A, df.B), `9 T( C$ k* E2 i; Y" Z
4 E6 g3 w. P+ F0 o7 V7 iOut[72]:
( M2 O/ \; g8 F& @) |B cat dog
* x, i5 V* n9 t# wA 4 `' z2 P( S0 L( I0 E- \
a 2 0/ u! t! q$ F# @5 q& Z" Y; _
b 1 0
0 X! p& M$ r! I) D, ]* lc 0 1
# y1 g4 w( O$ w% u1
; o+ X% ~% j* e9 M% u2 `23 F8 B7 v7 R+ s/ p# H' l4 x9 }
3; x0 p3 z+ m; \; I, d& o/ g
43 u! M* E0 E: a- `0 j1 Z1 \ \; F
5
; T3 G8 d' k7 r2 U4 Z9 t: C6# n# z6 q7 i$ b6 W. K- Z
7: d3 ?& A. R$ I% n
8( o8 ~0 k6 M+ F& K6 u5 V: u
9" e( D5 R, q: E5 C, j# X
但事实上有些列存储的是分类变量,列中并不一定包含所有的类别,此时如果想要对这些未出现的类别在crosstab结果中也进行汇总,则可以指定dropna参数为False:' c0 m4 q6 A3 S; ?# N
; p: n+ P) a& O6 N" u: c) adf.B = df.B.astype('category').cat.add_categories('sheep')' i$ t, D: W$ h2 R/ l- x
pd.crosstab(df.A, df.B, dropna=False): e' s& Q3 E& `
$ o" m2 D) z1 F# F2 `) EOut[74]:
( ]7 e8 O% w$ {. v4 e% nB cat dog sheep
% G Y- }3 c% e7 q6 ^A
% W4 O: U1 e; I7 Fa 2 0 0/ {/ l7 P9 I( P; E" q, |$ Q; L0 _
b 1 0 0
% _) A; H7 l# ac 0 1 0
8 `& }$ V0 z [5 E: ~1: q7 p# P" x( i
2, i+ M% W2 ]# |' k' I: }1 R) M: r
3+ c/ b+ }- Y7 l' V( ^) j) W
4
2 x7 n' V4 \5 H8 v; b+ p) N7 V# U5- N* ]- E. o+ S( \1 d# Z& ^
6
/ z% j3 Z9 r3 k* B7
' p6 {5 K1 v0 p1 [' K8
$ D) X( }; y2 u4 g- Q2 p2 E% P9
0 R' z; `" G& Z- @请实现一个带有dropna参数的my_crosstab函数来完成上面的功能。
. c; f ~. x6 V6 D q) t, C
& G* k4 m! q2 ?2 i. pEx2: 钻石数据集
- z9 [) M i0 }% T& r 现有一份关于钻石的数据集,其中carat, cut, clarity, price分别表示克拉重量、切割质量、纯净度和价格,样例如下:
! ]6 @( i* W" E" Y4 `; N
6 ^8 w4 `. H1 `' Q( Ydf = pd.read_csv('../data/diamonds.csv')
; Q( P( L( O. Gdf.head(3)0 a' K) |5 _3 J; k
- b$ q5 S+ ?) a, L2 tOut[76]: % d t& Z3 E* ?: z; C
carat cut clarity price9 [) O- F7 F/ j. m3 N- ]) X0 ~
0 0.23 Ideal SI2 3263 J7 ]- Z& x# {
1 0.21 Premium SI1 326
% w6 R- ~0 v6 ^! k8 y2 f' ~2 0.23 Good VS1 327: o% ]' Z# Y" R( H
1
6 W$ ^: \8 c) C- e* C/ c0 @2
. d: |8 O7 A' N" y3
0 p* `! _ V! [# ~" V1 J4
4 p0 w, K$ m" d& A7 Z5
, O7 i6 F1 R; ^, t9 E) O0 x P68 z" q$ D$ _/ I+ k1 p
7# f4 e/ v! k( m! C: `# K
8' q( x0 h; Q- q* a
分别对df.cut在object类型和category类型下使用nunique函数,并比较它们的性能。% |" l* M4 I7 _, t5 ]
钻石的切割质量可以分为五个等级,由次到好分别是Fair, Good, Very Good, Premium, Ideal,纯净度有八个等级,由次到好分别是I1, SI2, SI1, VS2, VS1, VVS2, VVS1, IF,请对切割质量按照由好到次的顺序排序,相同切割质量的钻石,按照纯净度进行由次到好的排序。/ M" u4 f5 H. c) a' z' |
分别采用两种不同的方法,把cut, clarity这两列按照由好到次的顺序,映射到从0到n-1的整数,其中n表示类别的个数。
7 ]7 f, @2 h% P$ y" H: m1 s% M' q对每克拉的价格分别按照分位数(q=[0.2, 0.4, 0.6, 0.8])与[1000, 3500, 5500, 18000]割点进行分箱得到五个类别Very Low, Low, Mid, High, Very High,并把按这两种分箱方法得到的category序列依次添加到原表中。( T' F: ^" Z( D) f2 e) G
第4问中按照整数分箱得到的序列中,是否出现了所有的类别?如果存在没有出现的类别请把该类别删除。
& M0 x' U8 C3 a% }, E) I% O& _对第4问中按照分位数分箱得到的序列,求每个样本对应所在区间的左右端点值和长度。
9 B) ]; W b3 x3 D先看看数据结构:0 x5 V: C# C! t' W5 r
9 g# a% P$ S- c) l1 l2 Z. ?8 k, m& ldf.info()
( }) [9 L2 B- r: u ?+ JData columns (total 4 columns):
1 s" v$ z' [$ r, r5 { # Column Non-Null Count Dtype : d! U- D5 c4 k2 A+ W- {! B
--- ------ -------------- ----- - d, [7 Y4 S' e/ @- L
0 carat 53940 non-null float64' `) ^. E$ W: v& }& n& H& c
1 cut 53940 non-null object & g( ?1 s* R2 N, \& o! k6 v9 T- _ L0 `
2 clarity 53940 non-null object 0 i( q& R2 V! t. v/ m( r! {
3 price 53940 non-null int64 6 _" P6 o0 P, S3 n, j
dtypes: float64(1), int64(1), object(2)" \$ V0 Q; H k5 q
18 D0 n6 y4 H8 ~8 u5 x6 s
27 Y6 _9 }6 V, r! v; U1 g
3
# A5 Q+ L8 J; f; |* p4
$ A+ u- o* R8 Y( f5
: q: v8 W3 O# Q6
! u3 f7 D: _7 f! d. x7 t0 j( E: J71 w) T0 h* M+ ~! U! d* z9 s
83 I1 o2 o, T1 S5 l* y8 q
9
" n, c' b3 J N% A0 |+ A比较两种操作的性能" L; L0 q( M2 w, m
%time df.cut.unique()! B& _+ P; X% A
F- n- ]$ D N7 V
Wall time: 5.98 ms
( ~ r$ i' q6 f7 Z9 r* x. V4 z; R' rarray(['Ideal', 'Premium', 'Good', 'Very Good', 'Fair'], dtype=object)
7 o5 W- V4 ]: L6 J$ r1
& l2 _4 g% g/ l8 U2* i: u, l3 M$ O& m2 K3 z
37 V# `* |& r; y* y, ?. B& ?
4
# A! _% {3 V4 S* k' R$ h1 b% y$ l%time df.cut.astype('category').unique()9 u. }' K- M& `6 F
; E) T2 e/ |: ~! x! Y: ~- U( Z4 O
Wall time: 8.01 ms # 转换类型加统计类别,一共8ms
: q2 D; ?& g: p: m['Ideal', 'Premium', 'Good', 'Very Good', 'Fair']
, @: B. c2 G; T- f2 s% }Categories (5, object): ['Ideal', 'Premium', 'Good', 'Very Good', 'Fair']3 s( \) }+ u% z# V
1
8 F, W' u2 W) }7 e0 c! ]4 o2
+ E6 F: E* e; ]) s+ Z, A3
. u4 R. G+ O/ h7 N4
' t) S, a1 L; c& e' l5- o% c) g; c; m' h( N. ~8 c
df.cut=df.cut.astype('category')
( e) _" L+ s; H9 t) ]%time df.cut.unique() # 类别属性统计,2ms
6 R/ C6 h5 f. d- X+ R' X5 |) [$ T% S( O
/ U1 n! i0 ]+ d' j! |Wall time: 2 ms
: k- P5 G8 E5 h1 m& {# X['Ideal', 'Premium', 'Good', 'Very Good', 'Fair']
, G" B2 O2 |/ s* D3 RCategories (5, object): ['Ideal', 'Premium', 'Good', 'Very Good', 'Fair']
7 m, g6 N2 C1 o+ k( B1
8 I3 U: o4 ~4 D( B$ d2 S2
" H3 L1 S8 t( i: w9 g- ^" J, K3& G; R/ k0 n% I2 h; G+ [1 ^+ C
4
* |! J w" K3 X! ]( h5
8 h; _6 B( M/ n& v% v7 Z! b6$ a2 i6 {+ P; ]
对切割质量按照由好到次的顺序排序,相同切割质量的钻石,按照纯净度进行由次到好的排序。/ L) @' t4 K' r4 {; ~% m) f- v
ls_cut=['Fair', 'Good', 'Very Good', 'Premium', 'Ideal']$ L2 e8 N$ z8 m/ n4 ]7 b. O/ A* m/ ?, R- Y
ls_clarity=['I1','SI2', 'SI1', 'VS2', 'VS1', 'VVS2', 'VVS1', 'IF']. G' q! E$ p8 _! ^. I# [2 Z
df.cut=df.cut.astype('category').cat.reorder_categories(ls_cut,ordered=True) # 转换后还是得进行替换
% h4 ]! ?- F! x2 f, M# a$ v& d8 Z! wdf.clarity=df.clarity.astype('category').cat.reorder_categories(ls_clarity,ordered=True)
9 T$ A9 I1 J; G+ b/ \
" H9 V) z( {8 {2 H( h4 Mdf.sort_values(['cut','clarity'],ascending=[False,True]).head(3)8 W) b2 ^* u1 x/ B3 M4 s
- [2 e" H- T9 { carat cut clarity price
, Y( v( r+ U) N% C! @, F/ A' X J315 0.96 Ideal I1 2801& `2 e; e/ h7 E9 Z% |( W
535 0.96 Ideal I1 2826 A7 E3 J. v7 w' T* c0 I2 u, p( ]& Q
551 0.97 Ideal I1 2830
7 ^" I2 }9 t$ Z1
* ]) ?% h, f8 W1 x1 E+ c2
; X/ C5 }; ^& t' f3/ C1 y! g* }0 ?9 X
4
' }% v3 G. S9 t, ]5
; l* `3 z- T* Y/ D p6. T4 v4 d8 S* d' Y1 N
79 w& Z5 Q' ]$ ^
8: D$ L6 Z) L: h! z j
9
- |: Q. {% f: w* w3 B10
9 ^' A$ H. H( @11/ g! i3 Y% m/ K1 ?2 T. n' a" |
分别采用两种不同的方法,把 cut, clarity 这两列按照 由好到次 的顺序,映射到从0到n-1的整数,其中n表示类别的个数。 P0 n& v" w$ n' h# L5 [% p
# 第一种是将类别重命名为整数" u( }1 E) ^1 b W/ z$ }. o' c
dict1=dict(zip(ls_cut,[x for x in range (4,-1,-1)])); r, l5 m& Z3 t3 [/ B
dict2=dict(zip(ls_clarity,[x for x in range (7,-1,-1)]))
* g1 ^( d- |8 y j) E( V, G3 L) ?+ ^* l
df.cut=df.cut.cat.rename_categories(dict1)
2 t8 j$ s$ F- R! ]) S, kdf.clarity=df.clarity.cat.rename_categories(dict2)8 I9 r# e# _1 p2 i; G% H
df.head(3)2 ^) L" |9 M3 n$ h3 P; t" {
9 t' {5 M& O9 `# N" ]% a6 m
carat cut clarity price2 s1 h. | G S- Z$ I
0 0.23 0 6 326
+ {6 @1 V; O: h2 W- {1 0.21 1 5 326
8 b2 o& M' u) S4 Z# l2 0.23 3 3 327/ y8 @0 b9 K) p; l& }$ m' n0 R% K
1/ E* u7 C- Q8 i. h/ o
2
# D( J; y4 n+ h4 U, z3/ S1 B, W, K/ ^/ _% h _0 G
4; P: R+ f+ P: X% ]# v4 ^# g* ?
57 n0 _: V5 w- \* |
6 x F9 F) M. W6 e# m# t
7
8 Z$ N- w$ L5 v8 i' w8# t( V( C8 X; t3 e g( X% P
9
5 {0 T2 j# R$ t1 `4 x. n10
$ l* A/ W1 w1 o0 c% [: r8 X- ?11' w9 _8 @# w n8 l2 |/ m3 y/ S
12
/ [, Y1 c+ f; K G6 A" K3 i8 F* J# 第二种应该是报错object属性,然后直接进行替换
9 \7 D3 p3 O A/ x8 Edf = pd.read_csv('data/diamonds.csv')6 O; W m' R5 s+ |' `
for i,j in enumerate(ls_cut[::-1]):
2 L X B1 m# X6 u df.loc[df.cut==j,'cut']=i
4 g; ?$ i5 q* V+ |2 E2 _* q' {; l! [) @' L4 H* b8 P
for k,l in enumerate(ls_clarity[::-1]):
" D: y9 z+ \( I. c df.loc[df.clarity==l,'clarity']=k
' H/ G- S* u7 F3 ]+ |df.head(3)! {0 O/ R8 d3 f4 d+ x* L
# a9 |% F$ c. H7 Y
carat cut clarity price* b2 Z' C/ G! ?6 w
0 0.23 0 6 326
1 Q2 [3 u7 W- h3 w1 B5 Z6 e1 0.21 1 5 3262 x$ w, m3 Q& w9 y- c6 J
2 0.23 3 3 327
% j% [5 `8 M; Y) q+ W- \) U4 ~1
5 a, C# M Q/ }2
( Q; b, a9 R0 M2 S ^1 z u0 w, l3
% x# c2 _& K1 e3 K3 \: j Y4 F7 }4
+ H8 ]% w; a4 [9 L: i55 J. F. P* Q$ C% M! [) m3 g
6
' y1 y) h8 a/ g' y* t; n. h' S7
: H( W/ [1 d! N: Y7 b8# z) p' a* G; s+ V& S& l
9# I6 ?; Q$ r P0 r) M( J+ @
103 C3 i4 q5 p# u8 r) u- E3 U
11
+ {' U! d! t' t0 S& w# b, G( ]. J12& H/ p l* m. w3 b- b; `
131 y( F. R6 u7 t- x
对每克拉的价格分别按照分位数(q=[0.2, 0.4, 0.6, 0.8])与[1000, 3500, 5500, 18000]割点进行分箱得到五个类别Very Low, Low, Mid, High, Very High,并把按这两种分箱方法得到的category序列依次添加到原表中。6 e+ S, d/ I! J6 P
# retbins=True返回的是元组,第一个才是要的序列,第二个元素是分割点
& D) n: G& h: L; F; Mavg=df.price/df.carat! e7 b7 ~* |) v' M2 S( a, M
! t9 F/ U8 w% A" v4 i, `2 _
df['price_quantile']=pd.qcut(avg, q=[0,0.2,0.4,0.6,0.8,1],! k. O c. T5 o$ f
labels=['Very Low', 'Low', 'Mid', 'High', 'Very High'],retbins=True)[0]- R1 b# x/ F: `) a" y, G# U6 }
. ]8 |1 F; L v1 X' x- _$ ^df['price_list']=pd.cut(avg, bins=[-np.infty,1000, 3500, 5500, 18000,np.infty],9 i! _ {7 P0 s0 i. T* `. Y
labels=['Very Low', 'Low', 'Mid', 'High', 'Very High'],retbins=True)[0]$ [* G* ~' v/ Q- _
df.head()
6 D" d$ Z) O4 v* \% w, q. A# \" D1 A) q; w' y
carat cut clarity price price_quantile price_list: q S: q+ y# I0 a7 g( E. @8 B
0 0.23 0 6 326 Very Low Low+ A7 I6 x6 ]; e. S8 @
1 0.21 1 5 326 Very Low Low' o9 M8 `+ E' G
2 0.23 3 3 327 Very Low Low; i. d. f, A) C( I
3 0.29 1 4 334 Very Low Low' K' c' H2 V& L5 R& f L
4 0.31 3 6 335 Very Low Low
( u( w* m2 r H; H% q% Z
w/ h. Z% [( Y4 v$ ?1; {1 ^5 G' K+ x7 ~
29 ` L! O1 w, J1 Y, M; [
3( W1 y" `. g1 E9 Y' Y
4! m+ Y6 N: R( Z0 Q& E6 G3 X2 r, J
5
% g) g1 ]* s+ ~( @# r2 v6
, `! `( t* }8 v2 n. H0 ^/ V7 |7
2 i$ r3 t2 O" L: V" j Y3 m6 s" a80 T+ o/ B& J: p4 M4 |% f9 q5 F, U- h: E
9; ]0 P) \+ n" ]- a
10" P4 t7 S8 c- i* ^/ l" p& R
11. f* b, B1 m8 ~, b" B
12
9 x* M. _! m. O+ o' Y( ]! h- Z' H13
0 W+ N- E; L, n" i1 C" k$ E6 K14
* m" m8 |! t0 i; w8 q7 C# P( U0 P15
: @% y$ W' K6 Z. B0 f3 ^* j+ f9 F16% W7 t: F! N6 p
分割点分别是:
9 A% r- W: H9 \& c
, e7 \5 i) z: ~# Q, C; Jarray([ 1051.16 , 2295. , 3073.29, 4031.68, 5456.34, 17828.84]); _3 G' H* R/ ]! o0 ]3 R5 p: U
array([ -inf, 1000., 3500., 5500., 18000., inf])( f" q2 j& x$ o, p
1& J4 b; p- k+ E: e
2
. E( E5 w- G& \- m5 U1 R7 S第4问中按照整数分箱得到的序列中,是否出现了所有的类别?如果存在没有出现的类别请把该类别删除。! a& f" z0 H- w: V$ Z; I0 B6 t
df['price_list'].cat.categories # 原先设定的类别数, W% K/ ?; ^( f# J
Index(['Very Low', 'Low', 'Mid', 'High', 'Very High'], dtype='object'); f4 V0 H3 @" f0 Z
7 t5 Y9 q! @4 h# X: I1 ^df['price_list'].cat.remove_unused_categories().cat.categories # 移除未出现的类别 N9 d5 x w9 A3 Z. u
Index(['Low', 'Mid', 'High'], dtype='object') # 首尾两个类别未出现
. l5 X' i& A) r, |7 F1
# R' q* Q, q1 P) E21 A" C# A7 `4 B6 p; T9 ?
3, b8 A1 k! M# T Y# S' f1 w
4
5 i, V m3 t: u- W52 i1 Q2 r$ f& O, a" t) ~' ^
avg.sort_values() # 可见首尾区间确实是没有的
- U+ S5 X9 j p. j. R31962 1051.162791 O- m- i" T0 N1 j
15 1078.125000
_5 G9 X) i6 j& d4 J4 1080.645161
/ V T' U( d* g0 |. B# M8 T28285 1109.090909
/ |! M8 E, L- Y- r- M13 1109.6774194 W. T) W/ V' O% u: V) H
... 2 U) o$ v- G: K" q
26998 16764.705882" p; _2 ^1 C1 n `! K- J6 W
27457 16928.971963
$ B( P, N% ]6 N: K, I7 B2 f27226 17077.669903
& q9 W1 q0 P4 s, ~* k! A27530 17083.177570! W. P5 w! d2 ?. A
27635 17828.846154) I+ _. C: T1 `% ^( Y& g9 ? T; U' J( d
15 h8 H& j; _, ]7 D" g" q, p
2
0 \; K8 |3 `- a2 z9 c3' R5 `1 Y. I8 [' ^4 B$ G8 i- P
4
' O0 \- V/ w# Q6 l& w, E5, {/ s' a" T. \% B' {
6" m: J; H$ i( A/ {9 C7 G
7: u/ |3 H" t, N. q
8
% I. Y& G: K" c# T% U' H5 ? a9
* ^( k( A% o/ f8 A S10- |" h; d! C# F2 O
11
0 z2 f5 l9 O9 N1 y5 r. ]& e12& w5 G# q: p$ I* k! p
对第4问中按照分位数分箱得到的序列,求每个样本对应所在区间的左右端点值和长度。% {" S) n) H9 T: `: v" B+ h1 o
# 分割时区间不能有命名,否则字符串传入错误。
) z1 }4 A4 B1 T$ s' ]id_interval=pd.IntervalIndex(
+ g0 {, J7 J4 f0 E pd.qcut(avg, q=[0,0.2,0.4,0.6,0.8,1],retbins=True)[0]
8 Z- d/ ^5 p& j6 X' J )
2 P- g! w: Y0 T# [( i' ?& ^! uid_interval.left
" X& r4 ?2 B9 c8 L- O8 ^2 iid_interval.right% k" h8 p% d* M
id_interval.length + e- C" [! n0 p- r9 a3 J5 L
1
6 z) H) s; X Y# ~2 E. F8 U# t2 Y5 u4 `
3! M8 Q2 S5 l! {
4, M; I" r3 Q- H+ c5 b
5
5 o7 H2 @$ `/ q% d0 P6
2 C5 Z4 N0 u% k+ o5 b7 i7
7 `$ k( _$ I" j第十章 时序数据; ^& Q K+ V9 `( z
import numpy as np6 V* { V6 w: m% G# ^. c
import pandas as pd* k% ~8 h' Z5 p, w: ^& S
1
2 v8 m5 Q* M8 C/ S' \2
0 G1 e2 m2 s: Z# O1 ~; y( G# f/ K4 t. h! i8 B, @ T
1 d6 H- D4 I' y10.1 时序中的基本对象+ n7 |/ n* L, z: V3 |1 O O1 F
时间序列的概念在日常生活中十分常见,但对于一个具体的时序事件而言,可以从多个时间对象的角度来描述。例如2020年9月7日周一早上8点整需要到教室上课,这个课会在当天早上10点结束,其中包含了哪些时间概念?
7 D( }3 P3 R$ R/ w) V9 f8 Y. z$ a* t+ P1 Y! O
会出现时间戳(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的简写。
4 w( d3 O! o) o
+ f, c+ i6 B/ m0 A$ Z会出现时间差(Time deltas)的概念,即上课需要的时间,两个Timestamp做差就得到了时间差,pandas中利用Timedelta来表示。类似的,一系列的时间差就组成了TimedeltaIndex, 而将它放到Series中后,Series的类型就变为了timedelta64[ns]。
: b% |( J3 |, b: [
. Z1 ]! C/ _6 p8 B, q& r会出现时间段(Time spans)的概念,即在8点到10点这个区间都会持续地在上课,在pandas利用Period来表示。类似的,一系列的时间段就组成了PeriodIndex, 而将它放到Series中后,Series的类型就变为了Period。- Z4 X( Y, @% b7 }3 Q
3 N2 T! N1 u- p" O+ Q" J会出现日期偏置(Date offsets)的概念,假设你只知道9月的第一个周一早上8点要去上课,但不知道具体的日期,那么就需要一个类型来处理此类需求。再例如,想要知道2020年9月7日后的第30个工作日是哪一天,那么时间差就解决不了你的问题,从而pandas中的DateOffset就出现了。同时,pandas中没有为一列时间偏置专门设计存储类型,理由也很简单,因为需求比较奇怪,一般来说我们只需要对一批时间特征做一个统一的特殊日期偏置。
' S9 _" U; u6 p7 ?9 V. [, ^7 t" H
- x* S4 @/ k2 _ 通过这个简单的例子,就能够容易地总结出官方文档中的这个表格:& a4 a" s( `4 m. Q
3 ]3 ^" f9 v: L, _ Q! ]. s1 X概念 单元素类型 数组类型 pandas数据类型) M1 B0 t1 W9 p* A
Date times Timestamp DatetimeIndex datetime64[ns]+ c0 N" Y$ t. [7 e* O
Time deltas Timedelta TimedeltaIndex timedelta64[ns]% p' a7 _& M ?+ u. q
Time spans Period PeriodIndex period[freq]
4 U" U' M4 N( a0 }# O) H- jDate offsets DateOffset None None
; W6 Q' n1 t7 t/ U p) o9 w+ p7 @ 由于时间段对象Period/PeriodIndex的使用频率并不高,因此将不进行讲解,而只涉及时间戳序列、时间差序列和日期偏置的相关内容。
" `/ n. K8 i; s) O+ X* z. I5 b l- N) L' `# X
10.2 时间戳
$ F/ m" A# j, @0 ?! Z: N9 n10.2.1 Timestamp的构造与属性
7 {! D+ R" Q) W& L- X2 z单个时间戳的生成利用pd.Timestamp实现,一般而言的常见日期格式都能被成功地转换:
! b$ O' r0 j! W. v. s0 `& T
7 Z/ e% a2 `2 Y9 c& Bts = pd.Timestamp('2020/1/1')
" G0 p& `& Z' y/ V$ ]6 J. i- y. T, z% p. E
ts# c6 q+ E$ s: i" |, W9 `
Out[4]: Timestamp('2020-01-01 00:00:00')
( q, b# B' s6 T( x8 ]$ M/ o) ~" O# ` D. X, }$ l* ]
ts = pd.Timestamp('2020-1-1 08:10:30')& B& Z! B: t" Y+ _' l7 R
: T8 L3 @0 }5 |- h } dts( E% O! Y( {% t6 f0 X$ p9 s
Out[6]: Timestamp('2020-01-01 08:10:30')2 A( ]- m! s# ^5 M P
1+ }6 H" H! a6 C6 A
29 V3 o; E$ Q1 v
3. J n, ^, P/ U" J( o$ S0 \6 r
4/ I; t4 C, Y8 t. J: f8 f9 s; o
5, R. R+ X! d9 [2 P
6+ B: A% j: K7 ?$ }3 z
71 n _' p8 K4 ~: {$ D3 v
88 T6 e9 C9 L+ h# @8 g
94 S0 M& l1 H) D$ S7 K5 v0 D
通过year, month, day, hour, min, second可以获取具体的数值:. _# \ d! `1 T) P2 }
5 _9 q0 x; o/ l1 z: j" vts.year
7 i& C* m. f# S( d% G5 }; @8 POut[7]: 2020
( i2 N4 v2 l/ J9 i; t) W/ g- u6 Q
0 i$ X8 y* d& C: B& k0 zts.month
& }' C+ P9 [3 ~" m! X! C9 uOut[8]: 1
! y% C/ z H' {
! J( f- h3 k/ q4 y' A w# \ts.day; d/ Z1 y5 K" H$ \2 G& L
Out[9]: 16 d, t, H: f5 {7 @% n8 W |, j T/ D
; C9 Q6 n2 ~5 v t1 c# kts.hour/ Y" j4 T0 J9 O# T+ a* ^4 q
Out[10]: 8
7 r5 p) W2 F0 i" |% r0 y6 P, f' ]* w. s7 ]5 s$ ?7 [
ts.minute
8 V ^) S* k m( AOut[11]: 10; Q/ N L2 L* I3 l, m
$ X6 O& [. X4 _ts.second7 I3 L ?7 K" r2 K4 q
Out[12]: 30
0 I/ ^ W5 p; A9 t# F) p
4 W# n. N o# r7 A2 h2 S; z/ W1
" k) G* y* Z+ h2" i0 t2 `5 d/ t7 E! u
3( F) {9 h3 a$ M' d- Q+ a6 L O
4
# R' P+ {! T1 d- \+ m5$ O6 `0 L% |* e' F+ ~, d: T+ K
6
o) ~) r1 V! z9 ?7. @" }+ r5 z/ n: d
8
: ~. w3 M P; P$ ^9# G+ q! U5 O' e( Q: Y
10
, }+ {7 Z- F9 n% ^8 d11: {1 @; U& }0 `, X
12! d, g4 l1 i) h) w; Y
13
- a4 U$ B$ b8 n14' L4 C6 T1 z7 f
15
- J' {, ]- G& _' ~: c& ?5 f5 \16 n. v# \ G. [* }
17
' \$ c7 Y' t# B. S3 ~# 获取当前时间$ j% X9 }$ N* g7 P
now=pd.Timestamp.now()" b: T$ N, I2 W* x! z
1$ ?2 C2 _! e' E" |: h
2
, ^# `2 J. q; J, D% j在pandas中,时间戳的最小精度为纳秒ns,由于使用了64位存储,可以表示的时间范围大约可以如下计算:" l3 g: S7 E/ c1 H8 Q7 J/ y: t
T i m e R a n g e = 2 64 1 0 9 × 60 × 60 × 24 × 365 ≈ 585 ( Y e a r s ) \rm Time\,Range = \frac{2^{64}}{10^9\times 60\times 60\times 24\times 365} \approx 585 (Years); q# X' @0 a9 Q( ]# O) B
TimeRange= ! H1 `9 C7 ?! K& Z4 M
10 ) N- t1 g, a: n# C) e
95 y* A( U5 r$ c/ s7 B( }6 G
×60×60×24×365
7 P* y! h$ S; T2 M0 }9 v+ _6 e- d! [2
* f8 Z7 j* w' M64: d; Z' Z1 k4 C& J- D
; c+ f0 e2 p1 F3 g
1 B' ?! ^1 l+ p) b ≈585(Years)
' l+ ]# l2 [6 u' x2 O6 {; K/ G( R5 w) C1 t4 D
通过pd.Timestamp.max和pd.Timestamp.min可以获取时间戳表示的范围,可以看到确实表示的区间年数大小正如上述计算结果:
! T$ ?# c9 v2 @ |1 Z2 Z6 \
% J1 u f3 r. \pd.Timestamp.max- d3 g B, x$ e |7 M( e
Out[13]: Timestamp('2262-04-11 23:47:16.854775807')
( {; P2 I& A% a' t$ K" d! |( C/ ^; Y( d, g' i* P
pd.Timestamp.min+ [: a% D/ N' b! Z
Out[14]: Timestamp('1677-09-21 00:12:43.145225'): _% N t& o$ G7 z. v" W3 n6 }
! d, q# Q( R. O+ `/ d: w! B" spd.Timestamp.max.year - pd.Timestamp.min.year# p1 i6 l4 D+ r4 f; B
Out[15]: 585. l. r7 F8 F# Q9 Y/ T8 d
1
9 ?! I) }- _. C: u2
2 }5 |; V; a7 m4 q3
- l- k% s7 q) \4
: w2 _5 ]; I; M% y, h0 G55 r+ N- p* v; e: {8 m5 `( U+ D; a
6. y% X# R! @ S2 }+ Q( o0 ?1 i
7
' o+ A: `3 ?5 a2 E82 `6 f: P4 A$ F. @5 d" g
10.2.2 Datetime序列的生成* |, a6 H. l5 J; Q
pandas.to_datetime(arg, errors='raise', dayfirst=False, yearfirst=False, utc=None, format=None,
" f+ D3 @2 r1 V) m# P exact=True, unit=None, infer_datetime_format=False, origin='unix', cache=True)
# s. k5 G' @4 O% L( `# t/ L7 d; _1; p& E$ y, {6 |9 j# l. ~, D4 X4 M
2
8 y6 ~; g( p; |8 p6 \( u2 fpandas.to_datetime将arg转换为日期时间。9 V, E3 Y) t ?
+ S; a$ _) V) V9 M4 @arg:可以是argint、float、str、datetime、list、tuple、一维数组、Series、DataFrame/dict-like等要转换为日期时间的对象。如果提供了 DataFrame,则该方法至少需要以下列:“年”、“月”、“日”。* G3 S8 u0 g/ Z; _8 k
errors:+ o1 q# I6 n7 N6 c0 u" y" S# U. s3 t
- ‘raise’:默认值,无效解析将引发异常
* ~& g* ]! i4 ]' t+ M$ t0 z" @4 z- ‘raise’:无效解析将返回输入& X/ \, P6 x2 R! } {5 W7 B
- ‘coerce’:无效解析将被设置为NaT
! K! _+ l( F8 h9 L, cdayfirst:如果 arg 是 str 或类似列表时使用,表示日期解析顺序,默认为 False。如果为 True,则首先解析日期,例如“10/11/12”被解析为 2012-11-10。如果无法根据给定的 dayfirst 选项解析分隔日期字符串,会显示警告。1 E( R3 F" K; }+ k
yearfirst:如果 arg 是 str 或类似列表时使用,表示日期解析顺序,默认为 False。如果为 True,则首先解析年份,例如“10/11/12”被解析为2010-11-12。无法正确解析时会显示警告。(如果 dayfirst 和 yearfirst 都为 True,则 yearfirst 优先(与 dateutil 相同)。)* N |( b8 ^0 ?9 }: S& E2 Y
utcbool:默认None,控制时区相关的解析、本地化和转换。请参阅:pandas 有关时区转换和本地化的一般文档
3 |/ n6 M! ?6 W, F" eformat:str格式,默认None。时间戳的格式不满足转换时,可以强制使用format进行匹配。! u: _/ T8 A* u+ R
unitstr:默认“ns”。它是arg (D,s,ms,us,ns) 的表示单位,可以是整数或浮点数。这将基于原点。例如,使用 unit=‘ms’ 和 origin=‘unix’ (默认值),这将计算到 unix 开始的毫秒数。% Y6 r5 c+ d% o# y+ p8 ]* w
to_datetime能够把一列时间戳格式的对象转换成为datetime64[ns]类型的时间序列:
2 B' W, {- J& f9 ~7 Upd.to_datetime(['2020-1-1', '2020-1-3', '2020-1-6'])% ^4 Z0 ~+ t+ W# M) Q5 U
, T' Q& u1 z" S1 M" [$ T
DatetimeIndex(['2020-01-01', '2020-01-03', '2020-01-06'], dtype='datetime64[ns]', freq=None)
9 p6 X5 P% O5 ]& M& E1
9 V. J% Q% J0 \& U" K- {23 a8 Q2 Z7 e0 O. e6 [
3
/ h4 Y% C# C. \5 Q4 w- D$ u在极少数情况,时间戳的格式不满足转换时,可以强制使用format进行匹配:
" c1 W% e {( @0 n8 x' I) N Y9 D+ W
temp = pd.to_datetime(['2020\\1\\1','2020\\1\\3'],format='%Y\\%m\\%d')
5 _% S( p7 u+ y# Q/ a" wtemp
4 H6 P7 W: l" ?8 v! g4 V8 s! m7 H. K W
! b( C' r7 U% v: C/ SDatetimeIndex(['2020-01-01', '2020-01-03'], dtype='datetime64[ns]', freq=None)! C0 N& v* M$ v( ?1 q+ y$ }
1
! S: }; P4 o' m( j: e2/ s% X3 s, K4 F$ `5 a- N, w1 r; C/ l9 I
3
* M8 U( a* _ {: B4
. }' Q; f! S3 P0 `- F& e5 \, a 注意上面由于传入的是列表,而非pandas内部的Series,因此返回的是DatetimeIndex,如果想要转为datetime64[ns]的序列,需要显式用Series转化:! X! ^# s! E& c; A
8 U" [! A* ]% mpd.Series(temp).head()& J: G. _ P" A/ E
, a' l& z4 R0 z; P3 t% ?/ T$ o
0 2020-01-016 m {) P# @# \
1 2020-01-03
! B+ c: j! A) p8 A8 J* \dtype: datetime64[ns]
6 a4 S7 Z$ A# {2 `* K0 K1
! j' F* b2 {# @; \2
& l6 A8 g6 _9 B, o& `6 R) E37 @4 d; p2 I3 `1 B$ C
4+ w# ?+ Y8 K/ d8 h
54 i- y2 T" |) t. J) V
下面的序列本身就是Series,所以不需要再转化。
0 r( r+ p5 g4 _9 G% A' S5 }, ?1 F* H8 F1 S
df = pd.read_csv('../data/learn_pandas.csv')
) P: A) x5 B @s = pd.to_datetime(df.Test_Date)/ K& f. j: k9 P
s.head()3 A+ n% ]( f" J0 @, m
: y& J! z+ j/ H" _3 K4 q
0 2019-10-05. L, l. i& s3 ] z5 Q$ L3 m
1 2019-09-04
# K$ K. d! Y; J' V8 v# T e2 2019-09-12
& E: y+ N V q8 ? h2 n3 2020-01-03
& x* h8 ^; E+ [$ t( a. W- E, I) |% c4 2019-11-062 T! f$ ]( X6 C) R% h( D" S9 I7 `
Name: Test_Date, dtype: datetime64[ns] P3 f* i: R& N8 l3 ~4 {6 _
1
9 {% _% T6 d) v: o8 e& J8 z8 y27 L# Q% [+ d p% C |
3
. w- e5 v/ O/ s4
3 l+ m* ^3 b: o4 T2 \! C+ I5
5 y7 ]) d* Y( n" d6
6 ~9 d* U3 ?8 ~3 u! F7" v! a* H' a/ v& G) b$ o
8; E7 M- D% Z8 c( E- d& l/ I3 H# \- ~
9" R; m% f! O7 E% p% W
10
2 C }- w6 k$ b8 e$ p把表的多列时间属性拼接转为时间序列的to_datetime,此时的列名必须和以下给定的时间关键词列名一致:
- F; p( @4 L; p3 d6 tdf_date_cols = pd.DataFrame({'year': [2020, 2020],# h' [ ~6 I) I
'month': [1, 1],& I1 {0 a) ^1 Z3 k" d
'day': [1, 2],$ R, |' ~3 j+ z' u5 M) h
'hour': [10, 20],) N0 n B2 B$ U- W: H7 w1 ~; D
'minute': [30, 50],
! ?) z' o/ K4 _1 G 'second': [20, 40]})
! z2 }. ?/ P3 }6 j) ]1 n% K% Epd.to_datetime(df_date_cols)
3 L/ y3 ?* t, G: H. [8 K5 ^4 T
}. y2 N) _* H8 i0 2020-01-01 10:30:200 H+ G7 d$ {2 b+ E% T% C) j# M
1 2020-01-02 20:50:40
. p& F6 U- T8 h: o8 T7 }- b1 ?dtype: datetime64[ns]" I" t$ ] V3 d2 r) @: Z
1
L2 z% |" x2 g2 N26 Q. v% ]- Q( [8 J* H5 t( w
3& j5 E4 \3 X5 j# W
4# ?. s1 a- c/ X4 J1 C3 G
5
7 k' L/ R$ f3 ]; x4 Y$ }+ n! R6- }, b5 G1 [/ I, `% x
7
w n, d# z1 q- s( b8# E/ i- D1 M+ T6 O* Y0 ~ L- g5 _
96 I: t1 T, ~, a& h" A8 Z5 m
10
6 G P9 |8 ~- |. ~" E7 P/ d+ Q8 [1 |11, |+ r- V' v) e5 O
date_range是一种生成连续间隔时间的一种方法,其重要的参数为start, end, freq, periods,它们分别表示开始时间,结束时间,时间间隔,时间戳个数。其中,四个中的三个参数决定了,那么剩下的一个就随之确定了。这里要注意,开始或结束日期如果作为端点则它会被包含:
, A: e. e5 ?$ a0 o, Spd.date_range('2020-1-1','2020-1-21', freq='10D') # 包含5 I& v# T, M. P$ [& X2 J* f
Out[25]: DatetimeIndex(['2020-01-01', '2020-01-11', '2020-01-21'], dtype='datetime64[ns]', freq='10D')3 k# L, h1 _3 ^2 V5 ?# {2 l
! ?9 z6 F, G( ]4 P5 N. Mpd.date_range('2020-1-1','2020-2-28', freq='10D')! W$ ]1 `# O. a# Z
Out[26]:
4 }3 }- V) q+ G$ y eDatetimeIndex(['2020-01-01', '2020-01-11', '2020-01-21', '2020-01-31',) L7 A4 [, q! L- Z8 ^
'2020-02-10', '2020-02-20'],; O, I. l) F4 m5 u
dtype='datetime64[ns]', freq='10D')3 l0 m" U6 } L1 ]3 x
f: K: c, q9 z* K& n7 g! i, t5 S# ^% V
pd.date_range('2020-1-1',
1 T& R3 i* u/ v8 w" o '2020-2-28', periods=6) # 由于结束日期无法取到,freq不为10天+ s1 X. z* |' G4 z; K* t
& Q2 {) H$ l) s9 f
Out[27]:
4 u- w2 T" t- q4 }6 l1 tDatetimeIndex(['2020-01-01 00:00:00', '2020-01-12 14:24:00',- y5 T8 }8 L+ k+ |( u. b
'2020-01-24 04:48:00', '2020-02-04 19:12:00',$ ?$ f6 h" ~6 e+ X% f! Y" W' X
'2020-02-16 09:36:00', '2020-02-28 00:00:00'],
/ g; H3 ]/ ^% Q# s dtype='datetime64[ns]', freq=None)
) E+ o6 \. D# Y1 {% w$ E. U
9 w4 P1 T! p" I" | _! B, j3 ^1( |! p3 s6 J/ k: t9 F
2
; C/ j. z; @: S5 x6 w4 _: [# s3
- S+ q- n4 S. d. E5 R$ q5 ?' U4
' S: S9 q A3 y- h51 ^/ I G* c2 x# |. z
65 t0 F8 f) q1 v3 N7 J5 e# q
76 G# |- B* {& B* d5 }) n e
8
) I) s9 I! i" L, T+ x9
. w! K$ i) I2 C* c/ ~' y) D( A10, h5 i* Z' y7 b
11
x! ~# N' E# A% L12
. b; w1 Q0 Q. \- J13
; t% S4 Q. ^. P$ I; ?- @: q! t14" \4 d" F" L! S7 t$ L) y* c* `
15/ A8 m8 y% Z l+ U, Z$ e3 V4 g
16
6 t+ C- f5 P/ x/ D+ x) `173 \8 A& _- O9 K `' w
这里的freq参数与DateOffset对象紧密相关,将在第四节介绍其具体的用法。4 a) T v2 W4 y. T/ v3 x t
0 c0 }5 `! p6 k L1 m
【练一练】
) j7 j; ]3 s) z% `* r' vTimestamp上定义了一个value属性,其返回的整数值代表了从1970年1月1日零点到给定时间戳相差的纳秒数,请利用这个属性构造一个随机生成给定日期区间内日期序列的函数。
7 G" G3 D2 A6 D% m" j0 C7 N
8 j# u2 A( b) X8 R: O) Fls=['2020-01-01','2020-02-20']
* o/ s6 Y8 r3 _1 Ydef dates(ls,n):* |( f* S( z. V5 I+ B
min=pd.Timestamp(ls[0]).value/10**9+ K0 r0 m8 h: ?1 L
max=pd.Timestamp(ls[1]).value/10**9
0 H1 p/ q! Z$ {& p1 ]4 ^3 s times=np.random.randint(min,max+1,n)& \9 r" j/ h+ ~+ ?; K% ` i; y
return pd.to_datetime(times,unit='s')
2 A* Z- c9 g$ k) O1 Edates(ls,10) 2 W: [0 v. K1 M1 h+ S( C5 E- q }
& s( Y2 x$ N8 O( ^
DatetimeIndex(['2020-02-16 09:25:30', '2020-01-29 07:00:04',/ B7 }3 Y. o) K; r
'2020-01-21 12:26:02', '2020-02-08 20:34:08',
" c; h. | G7 d, ^/ }3 K' V* v '2020-02-15 00:18:33', '2020-02-11 02:18:07',2 q4 G* e* J5 x0 r# A A6 S5 W
'2020-01-12 21:48:59', '2020-01-12 00:39:24',' T# \1 a& P' D# f/ _. w
'2020-02-14 20:55:20', '2020-01-26 15:44:13'],! r& y+ U, X1 H0 F1 Z3 ?
dtype='datetime64[ns]', freq=None)5 }/ S) ] B9 ~9 B2 ^+ t
11 n2 f* Q, V! }2 h1 Z& Y
2
! G7 t* e0 Y z- u3* k+ ?3 _" i; }' W
4
( M* R" A# B) Q- O/ K- C* P5; K- ]6 R. P* W f
6$ p$ _% e5 V. N$ W
73 Q- l6 O5 }6 p4 t$ V; b
8
3 Y1 P1 k( U5 A& ^, k. `. ]98 B6 l# y) @+ e: d
10: X3 u" U P& }3 L, W5 v0 q' F# S
11& b, t* u+ b* x
12
6 P p) G/ Z9 `+ d0 w6 P+ K6 u13
' Q; s; B( h/ `/ e6 t) n( R7 U/ b14
* `1 ~1 o S3 G' S& Q6 Z) Kasfreq:改变序列采样频率的方法,能够根据给定的freq对序列进行类似于reindex的操作:6 |9 \5 O# c0 i* x; R# Q8 l$ x( V
s = pd.Series(np.random.rand(5),
" y! h M; m2 ^5 d/ ~5 G3 g index=pd.to_datetime([
( l# P& g; Z, y! g$ E! } '2020-1-%d'%i for i in range(1,10,2)]))" L3 ]) g7 X+ `, [9 s
' K0 v: \" @0 ?! Y
/ ]& W: v5 J1 P }6 gs.head()
% j" M8 `" ?3 ?, R$ f: f) iOut[29]:
6 ^" Z* E! H! B1 m2020-01-01 0.836578
, k$ c0 P4 ~+ e) `6 Y& U2 S- \) q2020-01-03 0.6784193 V# `0 @* E" v) @, I
2020-01-05 0.7118973 p5 l0 |$ R" ]' T4 Y1 t0 r
2020-01-07 0.4874295 n6 z8 g9 R8 w- P' k( y+ {$ i
2020-01-09 0.604705: |" W& p9 X1 s% y% a: l2 B
dtype: float64+ X- ^5 H- P3 Y* ^1 B
( h6 V" C! x3 E% ]) h
s.asfreq('D').head()
# r6 F. c& f O, E/ TOut[30]: 7 l! l) V8 b7 ?, {& s3 b0 L3 n
2020-01-01 0.836578
% p& e e/ x4 {5 g' m4 ?2020-01-02 NaN
6 O [' G% _! R, q d$ S2020-01-03 0.678419! V- A" X' A- ~. C% P
2020-01-04 NaN( q# `* f3 M c
2020-01-05 0.7118974 ^1 L' a3 q8 g _- J
Freq: D, dtype: float64
! j/ X7 \/ l& t' f
% Y8 E+ I8 z2 B6 ~' u) N$ Es.asfreq('12H').head()
, `+ M/ _" q- V- M% P0 YOut[31]:
8 l6 z- Q; B& [* p- y2020-01-01 00:00:00 0.836578
! {: {. F1 f6 g2020-01-01 12:00:00 NaN+ \, i4 B, h: m6 q. }
2020-01-02 00:00:00 NaN2 I/ }# G/ x7 o! W4 @+ `4 c- L- B
2020-01-02 12:00:00 NaN
- Z; y' F% \/ v& N" b! K2020-01-03 00:00:00 0.678419
$ a# h6 {7 t! [/ L4 gFreq: 12H, dtype: float649 S! M& {2 F# Z' [1 U
/ b) ^2 B' ?* [$ B) f/ P9 A1) ?" q' [$ e( N% T: l0 A+ l. e) K
2
2 }- X& n5 Q- V+ u3% U# K% }" m2 |+ n2 [- f: z
4
1 R/ t( q J5 u; s! U& U5# B3 r; e& u( A1 t$ C O+ ~; s2 o
6
& x6 h) n9 X/ ^7! n0 g/ N4 x: N( R8 E
8
; w1 T, c0 @0 ]# d0 M93 o0 r, m2 F6 [; _# v
10& i6 c% Q, ] I& v+ G! e: h
11
) H9 v" }3 M: Q12
7 q2 v. j# I" U1 _+ C13
3 ^/ V: V% m; M0 K \& C9 x14- V8 B* m _( K- i3 p
15' T' ` l5 ]% y# z" g8 s8 k! q3 O8 L
16- x* t! y( L Q5 R4 t
17. A6 z# s% w& F* c. h7 @( i
18) |+ ], S' g. c( e d, K+ F" V
19
" P' n7 }" ^$ Y3 J. M1 q20 E( R2 w: N: g- M! C
21% g4 J& M3 g8 Q+ V$ a& [
22, Z4 l: h0 D9 T2 S
23; f) k3 C8 Z% M. T% U
24
0 ?3 O z: G; h, d; ~( a5 M253 y& q5 d6 Y2 G f8 \' h
26' V* ]9 G: Y* O7 d5 N
27) K% U% ~/ e$ K0 `
28' f. S2 K; h( A% @1 ^6 c$ P! I# o
29' S6 v8 ^" H. d, r5 ]5 U
30
! O! t/ o' h$ ]31! F2 y/ ^' J; t9 e' o
【NOTE】datetime64[ns] 序列的极值与均值3 H# E1 m$ a p4 N# A7 R- Q
前面提到了datetime64[ns]本质上可以理解为一个整数,即从1970年1月1日零点到给定时间戳相差的纳秒数。所以对于一个datetime64[ns]序列,可以使用max, min, mean,来取得最大时间戳、最小时间戳和“平均”时间戳。5 l* S8 r8 Q6 l* D% w% q8 Q
: \$ b+ B! s1 Q) S1 \
10.2.3 dt对象
( S/ P3 B: j7 _ 如同category, string的序列上定义了cat, str来完成分类数据和文本数据的操作,在时序类型的序列上定义了dt对象来完成许多时间序列的相关操作。这里对于datetime64[ns]类型而言,可以大致分为三类操作:取出时间相关的属性、判断时间戳是否满足条件、取整操作。
; ]4 s$ a$ ?" U* z9 q4 l/ L3 a
第一类操作的常用属性包括:date, time, year, month, day, hour, minute, second, microsecond, nanosecond, dayofweek, dayofyear, weekofyear, daysinmonth, quarter,其中daysinmonth, quarter分别表示该月一共有几天和季度。; N# M9 g% }3 n" ~! X+ c" N
s = pd.Series(pd.date_range('2020-1-1','2020-1-3', freq='D'))3 `. x _5 }1 {$ G+ m; }5 m/ T
8 K4 S; _9 g9 j: S, bs.dt.date" r$ ^7 a# ]$ n, R8 v
Out[33]: $ t, P% r6 @* {. V: q- O$ o u
0 2020-01-01
% i$ \0 R6 S# [- @% h% i1 2020-01-02
: z, D+ p2 O/ O9 w. w7 L2 2020-01-03
, c. o) q T7 e A6 Bdtype: object* ]% H$ K v& x9 N
# f r2 V# T, e- |s.dt.time
' G, U- X7 b+ b. y/ F6 H5 cOut[34]:
9 s& Z& P; l. h& c4 q' h! ~- i0 00:00:00
0 v# Z6 Y b/ d: x% Y1 t1 00:00:00, R6 S. n q. R" z9 ^( E3 M
2 00:00:00& N# ~9 ^/ \0 J. c. O; T* J1 ^
dtype: object
& Q& {; D( h8 A- }' s6 z1 S" ]% M. C8 S" U9 `
s.dt.day
1 Q: |- b4 ~& OOut[35]:
! c ^. }3 F9 f% O+ N! {6 R0 16 b8 \% S9 C H1 E
1 2
m% m' r& {; n" o+ s- `6 }" ]3 _2 3# x8 l# e$ }: d5 q
dtype: int64
" ?5 Z) u6 j4 V. ~1 P4 }( ~% m. J L8 z) y* P8 a, T' |2 v* R, u, P& ^' _
s.dt.daysinmonth6 j& c& a) {2 `% f8 y
Out[36]: 8 x- s( I# }" p& r# J4 e! D
0 31: ?. T; I8 k, m5 {5 Z3 g# X
1 31
) ]0 X& U1 y4 F7 K2 31
5 X- ^9 A1 u! S2 H7 T5 Edtype: int64
$ |4 I3 M; E% T# b- b+ q; R
% [ @& I4 K) c# U7 s. ~12 r. |: f- H7 i- E& B# t* O" u3 g# ?
2& W' [6 s. C9 b! ^8 x
3
- l: p% ] ~' `( H) G2 _43 }2 M" L2 S& X( k) r9 W7 V6 M
5
: [ N1 L3 i) V# i# F6
1 n8 V2 W- K4 `3 L$ g M76 d- y6 w0 ]1 d" h. \
8
* q: |: i8 i7 j: A8 d$ g9
* c- ]" P; x7 C4 }/ f( n) q10/ M6 y; [$ G8 r, u. G, \2 |- T
11' |- G! g1 v+ k9 E$ ^( [4 ?0 k
12
- Y8 S4 k6 y2 _' t; S13
$ d- _; i2 y' Z14
* i' v" r9 Z$ ?/ u15
8 ^9 b( ~+ V9 t. ^16/ J! V& a0 i3 C, G5 i
17) R; L6 G5 ~" a7 W- h2 r y5 e3 W
18" K6 H+ g, y' L$ W) ?
19% n3 A# A9 U V4 V/ q' O
20% l! V6 `5 R! I( e9 c0 n
21# e* ` e1 |+ M% q
22) @- F5 }& a0 c8 D
23
1 `6 n5 V. Y: L1 L H N' o24+ n6 V9 L$ M2 o, z( A1 ]: w' B
25
& O/ e5 W3 d: b, o26 Y' H" _" J" t/ b5 ` H; ?
273 ?$ i% J" H ^& x
28
% p' {0 d7 ]4 V; x Q: ~! H29
; d8 [6 V) w# F5 e. @ 在这些属性中,经常使用的是dayofweek,它返回了周中的星期情况,周一为0、周二为1,以此类推。此外,还可以通过month_name, day_name返回英文的月名和星期名,注意它们是方法而不是属性:
! B4 ~4 T( `9 U g. p# c4 {
4 t' [8 B- h5 J4 p1 Js.dt.dayofweek% f' f- m! S- k
Out[37]: / L( F r7 N- A4 f
0 2
; X$ ^9 @4 Q# L: ^1 3
* r0 [/ Q# R: Q# X6 e9 N2 4
9 u6 g$ g; _0 D$ b; Fdtype: int64
U5 Y' ?, T$ j. x2 H4 K
; c6 T* R) o7 Es.dt.month_name()
* U& B. M& |: `( f/ xOut[38]: ( ]$ V" k: T. W. G, }6 I6 R% N
0 January
% x( t$ q; p2 j( k% q. A' v/ e1 January
# Q3 J+ ^, o9 @- ^, A: G: l2 January: o( _: B/ V# c* I! V6 c
dtype: object6 H; c+ r1 [& X4 d% k' z
8 q7 X. N1 b t- N$ C. H3 G! R
s.dt.day_name()
7 Y; u# F4 u' B& n0 ~Out[39]: F q; h/ I- G9 J
0 Wednesday
1 P6 J7 B& I7 q! X9 [1 Thursday
7 t% l- c, i7 G6 F; j2 Friday7 w. E6 t/ f& U* d; h' _9 o2 J
dtype: object: e/ s1 u9 z4 z6 N* {( A/ i1 T
3 ^1 V$ ^/ x1 I" ]$ H H' R& o, z
18 g. p7 r% ^5 z$ r- B) l
2% s1 r! q0 ]* w
3
6 w/ s& y1 i) ^' |6 A% w% r% ?4# ^) t$ E1 ` r7 J' b
5! u! }1 g0 H7 ^+ E `4 A1 q
6
/ S+ I" m' h" _ Y4 j$ ^7; G- D$ H4 s0 H; S0 |, ?, b
8' J j! h$ j- H! ~* G. [8 l
9
! F# ^4 q7 b8 e" e8 g: H' Y106 }, ^6 g4 B+ W- e: Y
11
+ ?5 h3 v" h$ s12
3 v% v% T& {* R8 b13
, M1 v' @- ^1 p+ S3 G14# T/ I3 G5 A! K9 @$ e
15
9 j) t( S! x( S' @9 L- E3 @16
?5 B7 G- C* j8 b+ m17
5 N8 {/ n3 A* C( n4 R18
3 @$ E$ g3 t9 a: q( t" I19) S6 U, c" k9 \' b
20
0 k4 }! G' i; N+ k. Y* N3 _第二类判断操作主要用于测试是否为月/季/年的第一天或者最后一天:* w7 B; o& e, I$ q) h% |
s.dt.is_year_start # 还可选 is_quarter/month_start3 z3 j% D s% F9 P5 X2 r8 d' z7 K% ]; l
Out[40]: 0 j6 J/ q2 @* T" Z3 H
0 True
4 `& T, w6 m: k8 W) X1 False1 H; Q, ~* D& o' W2 x" J
2 False
) V) ?7 e u8 W0 u8 v( ~: f+ [dtype: bool
, T+ E) O0 u( k' @1 t- U+ P5 T* Q4 Q+ K, F* B+ `
s.dt.is_year_end # 还可选 is_quarter/month_end% a; p" V# B% Z! X6 N% ^( [
Out[41]: ' F& S3 `9 T1 X! ^9 z; Y! d" E
0 False
7 u! P- X. D0 r: O8 c1 False
- j# Q, t) k5 t# Z8 v7 `2 False: M7 E9 G; |6 f6 D3 F* X
dtype: bool
8 |9 ]5 D. Z" f/ C# E1
( _ g7 W1 h5 P# c9 p2
; m* v. G* J! T6 C) G! f/ }3
' O) ]4 e- t* P0 _7 ]. W4
7 H7 S/ |! \% Z/ X" u5
$ L0 K- a) z. c3 b8 L( e" u6
& [# x2 s- d8 l7
- a8 _! {: v. D( d$ w* o8( V) z: F" t. A6 a
9
: s+ U1 C% U2 I6 @+ w1 }! }10% d: l* A9 k! u# a
11
9 F' M/ [" j2 m4 P0 g! n8 @12/ F( ~; |( m. R) b
13
+ m5 p$ f7 m2 j) J# V第三类的取整操作包含round, ceil, floor,它们的公共参数为freq,常用的包括H, min, S(小时、分钟、秒),所有可选的freq可参考此处。
' {! l) X8 p7 a0 y2 C' ks = pd.Series(pd.date_range('2020-1-1 20:35:00',6 g! r# ]% ~$ C8 A, ^
'2020-1-1 22:35:00',
* G$ ^ U: O4 S( f' N freq='45min'))
# v# ?/ K' A9 f1 B* d/ Z- d. H6 ?2 D8 b8 C2 |: L
( N; P1 `9 r6 H& [5 P
s
& u. H+ I6 ^, L, t4 x* [Out[43]:
. ^5 O8 [2 ?$ X9 A5 t0 2020-01-01 20:35:00/ p3 _/ ]% g; w R) u$ z, J S
1 2020-01-01 21:20:00
$ X5 ]0 I' y* k) o2 2020-01-01 22:05:00! Y8 C z2 a, L1 j) X
dtype: datetime64[ns]
" P! E8 ]- E/ b! e- X0 F. Y! L' [1 b* N. W- P* n; D) B
s.dt.round('1H')8 g+ y0 v/ `5 `3 W# t% r1 R
Out[44]: ! V3 T7 }$ m; U7 m7 [/ K: m
0 2020-01-01 21:00:009 y( j8 C* \9 K$ u) F
1 2020-01-01 21:00:009 E( G: K4 M' U9 }; z: N4 G
2 2020-01-01 22:00:005 v& }* [0 I( j& P
dtype: datetime64[ns]+ _4 W* W+ \/ _2 l9 o: I" w3 c4 X
1 f$ {% m$ G+ W" ]5 Y: cs.dt.ceil('1H')/ b! F2 S0 W H1 C4 U
Out[45]:
/ g' U3 R) W4 K F0 t0 2020-01-01 21:00:00
5 Q, Z8 G7 y$ m) h) Q1 y" |1 2020-01-01 22:00:00
, U* o' w8 m! `; y- x+ j; o2 2020-01-01 23:00:00
1 Q; Y$ S0 p, X: u' Idtype: datetime64[ns]0 f$ L+ ?& r, |2 h8 Q
; b) I. p: ^& }; a& h
s.dt.floor('1H')
" p2 a9 |! w$ A% ]/ ^0 POut[46]:
! X$ x- C4 B2 ~5 N6 y, g0 2020-01-01 20:00:00$ k7 W( \$ g6 w9 s1 {; O" X0 A
1 2020-01-01 21:00:00
7 R2 T! h0 B. q6 A- i/ D2 2020-01-01 22:00:00
5 v" y$ |( d; z6 z- q' z" m' }dtype: datetime64[ns]
1 @* p4 y9 V, I" K$ b+ u' y# `, [4 q
19 m, T' o' K' f' r/ V
2) e" }6 e1 z0 b9 F- ~
35 W# [4 o1 o! W6 U
40 Z% y& ^8 ]2 S# h# B
5& A# \8 H5 R3 z" x7 j% ], A! A
6
) Q, j" p R, g8 F7$ I4 h, K3 ^( k* Y' V) k
8
6 n7 b: [* s) I9
' w" h, J. `, X9 _, u& S10
! l: j; h' @5 w3 L11
8 G0 [/ ]; \+ o z) p12
' p( L/ d! I# Z G' X. T( \13* `5 q- p% A8 U! @7 I
14
% X4 [2 a5 G! j0 J15" n3 `; b9 I1 J% N- \3 w
163 c* m4 E% ?; v4 n) v
17
' u7 `; J) z& O! D9 d- l18
# Y% k. @+ e- L. \& d9 d8 u19
$ |+ M: e' v$ u `3 |- x20
& S, @6 Q7 n1 w# z% V21# y! |3 \7 |! B- ]0 Z7 r( _; x" }4 \
225 F3 D" I" B: W; R! g
23; a3 _& @. e6 N
24$ V8 K9 z/ x2 x; L, P; ^
25
5 ^- }; C8 F6 k4 D# b1 I9 B8 l26& s5 [! Z: h7 c
27! D ]* A( @ \
28
+ o. d+ C8 d6 |7 z: B* x291 e# [: L7 M/ _" ]
30
6 x" l5 \. i4 F1 O317 M) D u8 c4 a- P, B
32! k n7 S6 H4 Q& R8 p3 ?
10.2.4 时间戳的切片与索引" u6 f$ q' D4 R9 t. R
一般而言,时间戳序列作为索引使用。如果想要选出某个子时间戳序列,有两种方法:6 D1 d- U9 s+ N# l, A# @9 ~; i
* j/ b& z8 |4 a3 n利用dt对象和布尔条件联合使用
5 p3 B$ Z7 P. c( k2 Q% j+ T利用切片,后者常用于连续时间戳。
1 T& l! Q1 d) |. K2 {s = pd.Series(np.random.randint(2,size=366), index=pd.date_range('2020-01-01','2020-12-31'))
1 ]. H- \' ~; U0 e' y: `7 bidx = pd.Series(s.index).dt0 z- C( t9 w) \0 B0 q
s.head()
6 c- K4 v# I( f5 L: f% V$ m2 d, e& K" A+ J
2020-01-01 0% H7 a7 J% x2 _5 {2 `; l I
2020-01-02 1( N5 \7 h/ E; G& K: U
2020-01-03 1
1 W% g- e" N4 v2020-01-04 0) D) q( I: C) a4 E" e8 T4 V( Q
2020-01-05 0
" G8 r, m5 @7 r0 L! ?- N; p6 H5 tFreq: D, dtype: int32& m5 Z( G% }5 g3 M6 v% ~" d1 D& @
1
% r9 }+ ~ p& ~: K( W: m- L( O2
8 W& Z' P( H4 m3
4 l7 L, g9 _. Q. _4
0 o+ W4 A/ P6 @+ w) J5& ~: z1 q4 e" R8 l; o2 \2 J9 f0 P
6
5 k! }1 }6 T9 d3 Z5 O. D74 G! I! r: }4 q. X- Z6 c$ b. V
8
1 C) }1 g& j$ f9) z$ p* s) V- g/ y- d
10
6 C7 U: c& s$ |# qExample1:每月的第一天或者最后一天4 ~! x' ~7 P3 S K
- S- W4 S1 W1 W9 Js[(idx.is_month_start|idx.is_month_end).values].head() # 必须要写.values$ H& n A' V ~% k& K2 `! j
Out[50]:
, |9 G# y. t- Z# o. H [+ |2020-01-01 18 p* H% W1 Y, r$ h, ~
2020-01-31 0
! u2 r3 |) z6 O; z4 l) `+ m$ c2020-02-01 1! A! X5 h0 E' C* S
2020-02-29 12 Q9 X5 _& F% W- a2 c: V
2020-03-01 0( K C- {) w0 ^. n% o; L
dtype: int32# g; B2 V: X* ?. T2 b
1! m* H# Y) _$ W7 i' `3 G! J
2
# |; L1 v: H! P `0 W7 T& [( b3' F5 I2 S) ^% \/ W& d/ |" S4 @7 {
4
3 o0 K* Y' W3 @! |* T/ ^5
6 N1 V. I' E2 R/ t6
% L; E' X/ i3 O7
_3 [) U4 q. [8
4 I3 H4 \8 \4 C- j" U( hExample2:双休日1 `; _6 e% J c8 A! Y
j8 h; l# R. y0 Z: \s[idx.dayofweek.isin([5,6]).values].head()
+ D+ V* C, M) |, ^8 G) a) OOut[51]: . |1 ]9 J! \8 L& \6 a; L
2020-01-04 1
6 x. e8 k: h0 ~. p- l7 L) K2020-01-05 0
) f* e9 @0 v2 C R" V) a2020-01-11 02 r2 z/ q# J+ Q+ g3 ]5 H
2020-01-12 1/ B0 q7 F/ }$ c
2020-01-18 1; V3 P7 l" o/ W) C
dtype: int32% Q6 Y: x9 Z! z) G5 ]
1
' i7 y* a9 r% X$ \8 i2; i: z0 h! U. ~+ F# b
3% S. @3 M8 q3 \" l
47 l9 o& ]) @% ^0 o6 y! v- v
51 e7 b5 D4 ` `" [' C% r( J8 V
6
! `+ \3 ?- K( ^+ A7
8 d p1 j6 {( i7 `% n8
2 b& ^- |. \ N9 qExample3:取出单日值! Z5 \. m1 s! c' k* i# n8 q% U% v$ ^
6 s; O9 L( P! L: i9 q. Z1 ys['2020-01-01'], s* l: z; u, _
Out[52]: 1
$ R/ w) d8 }5 _8 F* x& x; N
' a! c+ y _# B) S) i: G! t! ms['20200101'] # 自动转换标准格式
- h" J& D0 j3 gOut[53]: 1
2 O! o# U8 @; N) f& i1
' K) _9 @0 B& |+ z; X, t. V2
5 j* j7 K/ K, n9 X; G" C$ j. f( H36 J# j2 W4 L: O1 W" X$ h
4+ t& h5 a! ~ T6 _ y" |
5& z. o# A# R c1 P" _
Example4:取出七月4 u9 K8 C& ]% W: Y8 o- D- C# p# o% f
; ~ P) A/ `# ~5 ~. c R3 U
s['2020-07'].head()
5 z% F' [% ]* u$ S. Q5 ?7 Y, t# G8 KOut[54]: / I! d1 O; d$ I! M9 W
2020-07-01 0
1 L [+ e1 [6 h. c$ n2020-07-02 1
: P( B8 Y5 D# X$ w; o- A9 n2020-07-03 0
" `1 y* c9 W% r- E0 \2020-07-04 0
+ ^/ ]6 d( [9 ?& L' @8 R2020-07-05 0- l5 F% S8 p" w. m. w# A3 O) ?& ^
Freq: D, dtype: int32& C% r6 G$ Q9 P7 r+ v. ~' N9 }
1
& ~8 L H2 }; q8 D+ C' x6 y2# e1 @ N6 ], H
3( g, ?8 u- A) z1 Z
4
1 S$ e3 Q# J6 Z4 k/ g: P9 R2 T k5
$ B. _7 K9 X, m9 _8 @4 w60 N% |' P8 m2 I# s! G* i. n
7
2 J9 c) e. g: U# i: t1 Y$ Q5 m8* Z4 F+ s& e7 m% B% v; O+ G' w
Example5:取出5月初至7月15日
2 @) e. E/ e. q1 P, K8 r7 S/ ~. T2 R) j$ b& F. p
s['2020-05':'2020-7-15'].head(), [2 W/ O& y- W. l$ z
Out[55]: , K* Q7 u' x, Q
2020-05-01 0
) N4 Y. X9 _. h/ s. `2020-05-02 1
7 Y' ~- n8 e6 o# o7 e. s2020-05-03 0, ?! Z# |( ^/ S G9 c
2020-05-04 17 W1 A5 M2 ?, ?, N, X: u7 V
2020-05-05 1
a7 }$ [: C! b9 ^' ? ?. nFreq: D, dtype: int32
6 O6 R& F; n& k3 [8 b4 C
5 W4 ^7 g0 f* j# S6 |! @s['2020-05':'2020-7-15'].tail()
% K3 q" R2 L3 T/ u: n7 _3 ^0 ~Out[56]: , x! G1 g/ q( p( A2 E; p
2020-07-11 06 u, A* G* ]: w% ?, u
2020-07-12 0
; L# {2 | I/ q: ?: m1 @5 B2020-07-13 1$ [2 ~) i. q2 c4 @2 ^6 l
2020-07-14 0
( O/ s" O% E* j) ?% C* y+ [, @2020-07-15 13 S1 w1 k' d# z
Freq: D, dtype: int329 o/ l( v% v% ~, @2 X3 p' d2 J& _+ H
; C! ?8 x2 S- F. D
13 F* A: m, z6 B+ b( {
29 Z. y$ d6 \: W/ u6 f
3
7 s: e/ d* [, d7 s41 v$ o- e- O& q. l2 v
5( [5 Q! d& a8 `5 ~
65 X4 i# h$ ^! e
7" J$ t& i E. d7 S
8# z5 u' S4 X5 b) ?( h* S% i+ `" |
9
( K7 ~* y7 V3 o; w: |, f+ `) f10
% R3 ?$ a1 `" k11' R: x9 w U7 ~, t
12; g, C* {3 n* M' A
13
2 x" H* ~1 f- P4 a2 S, f d14
3 u' {6 b5 S+ \$ D/ A2 y& f7 m15
c% _" j) V: k3 `$ y1 U( r16
4 `9 t' c6 O# { g17
9 R) l& a1 l4 y/ H$ Y6 s {/ |2 G10.3 时间差* y& ]& I+ o+ D& z3 ~+ F- f
10.3.1 Timedelta的生成
5 H5 ?) Y, z4 ~5 I' k6 Cpandas.Timedelta(value=<object object>, unit=None, **kwargs)% X( ^5 T& C. f# u- H
unit:字符串格式,默认 ‘ns’。如果输入是整数,则表示输入的单位。
8 k* ? z# E8 a' c7 F+ H6 [ 可能的值有:6 H% P7 h L3 F6 [9 o! Z) ^% v4 P9 c
( O0 U+ L( m9 F7 L
‘W’, ‘D’, ‘T’, ‘S’, ‘L’, ‘U’, or ‘N’; j6 M+ E3 B, t4 y. O9 k
‘days’ or ‘day’6 l& B+ X; [& U9 g7 \* S
‘hours’, ‘hour’, ‘hr’, or ‘h’2 ^$ Y& C1 v) U9 f6 i2 O
‘minutes’, ‘minute’, ‘min’, or ‘m’
7 @' H7 |) ?: J‘seconds’, ‘second’, or ‘sec’7 ^7 g4 D8 K0 N
毫秒‘milliseconds’, ‘millisecond’, ‘millis’, or ‘milli’, p0 s8 B+ H/ ~# z, a6 d3 W
微秒‘microseconds’, ‘microsecond’, ‘micros’, or ‘micro’
~' r4 Z" ^- g. r7 B纳秒 ‘nanoseconds’, ‘nanosecond’, ‘nanos’, ‘nano’, or ‘ns’.
|; b3 q/ E; S! l, Q- ]时间差可以理解为两个时间戳的差,可以通过pd.Timedelta来构造:
/ T) d! ^; @. j* j8 b; e2 P9 n" J+ A+ wpd.Timestamp('20200102 08:00:00')-pd.Timestamp('20200101 07:35:00')
7 d8 I5 p7 {# d- M% f; VOut[57]: Timedelta('1 days 00:25:00')
' I& ?5 e; ^8 |! k6 g" w
9 T. P0 y! G9 Q/ v, y' gpd.Timedelta(days=1, minutes=25) # 需要注意加s
6 K; O4 \5 t- v4 WOut[58]: Timedelta('1 days 00:25:00')
0 u1 m9 q8 I6 ~( j/ g! R, K$ {( i' w, h' O, L( R/ S
pd.Timedelta('1 days 25 minutes') # 字符串生成
8 y# _( ]( _: L% V. ?$ NOut[59]: Timedelta('1 days 00:25:00')- `. | I+ }3 E6 @
8 ^7 E* W6 _ f, S
pd.Timedelta(1, "d")
/ m) m c h& @0 [8 y$ D9 iOut[58]: Timedelta('1 days 00:00:00')
/ I: ^3 Y/ L* x) y9 d1* J3 |4 J# F) D' t7 W+ g
2. v' Z7 x/ c, E
33 p' M D* I4 O& q1 v+ Y! ^7 W
4: V* [ K, D! F/ O
51 F' S4 X! ?* T; f& l) l
6& g$ U7 [, [5 C& A, v8 F d' Z
75 B: n8 N+ R3 [5 r3 `
8: `5 R. W; x# g: m% |
97 O6 n3 \# ^: J7 c' q4 M+ X; a
10' C1 I# J. m9 |
11" U5 r8 d4 {. q: ~- E/ ?7 ~2 B5 h
生成时间差序列的主要方式是 pd.to_timedelta ,其类型为 timedelta64[ns] :
3 ~, k1 U" |, s5 ?/ Zs = pd.to_timedelta(df.Time_Record). h: r( Q' B/ ?7 Z& F( A9 q
Q3 f! L v6 Vs.head()
0 O( u, [/ X# `) s& A; O4 u( B7 x# VOut[61]:
; P5 K/ Y- g- J! N5 h! L/ f: N0 0 days 00:04:34
! A& F; }; H, }: r1 0 days 00:04:20! h( }/ A' Y, q7 `+ ^
2 0 days 00:05:22
0 c7 b7 {& v) G4 i& ~) B6 e3 0 days 00:04:08
. A! g+ t2 u% Z3 s" H u4 0 days 00:05:227 p' k% U2 Y: c( k; t/ E- d
Name: Time_Record, dtype: timedelta64[ns]' `; p) Q6 O6 B x
1
! d& Y/ T) f7 x. Y9 `! r& w2, W: h1 h1 b+ U3 r8 V% p
3
* o6 r! y3 g6 c# H4
* ~% |9 S! S. f# n( G" H" b" x: D5 A* r# p; _% g: W
6
6 z; M3 l, `5 p. U. L7& t- ~8 g: q! E' h
8% V' v3 ^$ _3 Q% r. k( U
90 I2 M, R h$ k& r5 x
10
) a% k n1 ~: |) Z5 Z" b2 D' p* x与date_range一样,时间差序列也可以用timedelta_range来生成,它们两者具有一致的参数:3 J1 F. O: A9 f7 h7 ]( _& N
pd.timedelta_range('0s', '1000s', freq='6min')! v8 [$ [1 {7 x& K/ s* G" ^
Out[62]: TimedeltaIndex(['0 days 00:00:00', '0 days 00:06:00', '0 days 00:12:00'], dtype='timedelta64[ns]', freq='6T')
- c2 z j# V" g3 p. b1 G, y& b
' }: ^! ~- O$ T% dpd.timedelta_range('0s', '1000s', periods=3)8 e6 I, Y0 E4 R
Out[63]: TimedeltaIndex(['0 days 00:00:00', '0 days 00:08:20', '0 days 00:16:40'], dtype='timedelta64[ns]', freq=None)9 f6 k2 W3 K6 U" o" }
14 O; T! ~1 j( s. R3 Q
24 \" e) \2 O; N: e, |& L
3
; H \9 g0 Q9 \4
6 _7 O/ l# H' f8 k1 O9 m2 T) w1 A5
e+ }4 D* j' b* g! t* B& O对于Timedelta序列,同样也定义了dt对象,上面主要定义了的属性包括days, seconds, mircroseconds(毫秒), nanoseconds(纳秒),它们分别返回了对应的时间差特征。需要注意的是,这里的seconds不是指单纯的秒,而是对天数取余后剩余的秒数:
/ ?/ t0 R- N: w- J1 gs.dt.seconds.head()- {% `; L# x3 Z# o3 m$ S+ c3 F! i
Out[64]:
6 l7 ]" {1 h. w0 274
" k/ A) b8 d. N0 o1 260/ q; D5 \- x, B: b/ I
2 322- D# A5 L1 T2 h# ~. h$ x8 S. \
3 248
3 l* Q( u; I2 m4 322
0 @4 J7 a ~6 mName: Time_Record, dtype: int64
/ s6 C9 `& @1 {$ S6 P! t. y* Q1
, j$ a D& v! B& O2
) R' o# i0 b' J. R3
5 w7 x4 b% _0 q( Q5 |6 l4
) `, f/ J+ G+ K) i: e! e5' T% c. Z7 q" E/ A6 s$ T, p
6
; J5 i( G, A# K9 s7 H0 b7! P* _* l& f4 L4 u4 |1 ?$ ] \
8
: W. {8 K' h: H8 i如果不想对天数取余而直接对应秒数,可以使用total_seconds
. b& Q, K; H9 | s) A: g, X$ ?% w2 Q2 n) g& X4 ]; i; t
s.dt.total_seconds().head()
. G6 {8 b W. T$ G. H+ OOut[65]:
3 Y& ?2 _+ U- E" k6 G0 274.0
! d" z- F8 D; w2 B: k% C. J s1 260.0
7 g+ x! x( V6 b2 322.0
|6 t2 ^* Q8 i' r' L2 N3 248.08 a- O9 T* h" x
4 322.08 z9 `5 f4 Q, e2 o" k, T, y7 o
Name: Time_Record, dtype: float64
7 d) h) g% [- C. F% Q1, C; ?! \2 H8 e. d
22 ?5 R; d" h3 u4 K# d2 k8 K
3# Y6 {# \" h7 l0 G' }) H6 |# [
4
- J8 I! @' ~% n+ @* T5
0 G' z6 v- O. w6+ T* v+ y+ j9 y7 m- I' x$ P
7. M6 g/ O& [5 z- w) z# T
8
; M% m0 T5 X" J: J U与时间戳序列类似,取整函数也是可以在dt对象上使用的:
4 b, N% h% F$ Y/ J" x! _- s+ t V: H1 X' d
pd.to_timedelta(df.Time_Record).dt.round('min').head()
' H8 q+ x' S* C) n4 bOut[66]:
4 {! h9 t w, U; R0 b; r0 0 days 00:05:00( P6 Y& ?9 \! M& W
1 0 days 00:04:00% c2 G. w* K# a
2 0 days 00:05:00
6 d6 e w/ A. d$ H3 0 days 00:04:00- k9 T" q% {+ v8 J* M: l
4 0 days 00:05:00
% b; J! x2 k: L! T$ p" v7 q" `3 [' DName: Time_Record, dtype: timedelta64[ns]$ f4 \% ^/ r$ O
1
a6 D3 M% d5 r, _2
) b" S' x) j1 C2 Q: ?3* p1 G4 V* \) M+ `* V+ ~0 R3 j
4
8 P! T; Q, D! q4 c! v0 H5" e( ]8 T4 o) U
67 S3 K+ |- Q( y4 V+ r
7
. |6 ~+ q8 D8 m% f) g5 I% s5 \8
) x6 x1 K$ o1 I1 r9 r10.2.2 Timedelta的运算
( J+ z7 _. V9 L7 e3 V) ]0 ~3 {单个时间差的常用运算,有三类:与标量的乘法运算、与时间戳的加减法运算、与时间差的加减法与除法运算:
: b3 I; K) u. L0 vtd1 = pd.Timedelta(days=1)
) f. c7 @7 l8 b" z" Q- vtd2 = pd.Timedelta(days=3)
" \8 k% U% j" Q6 w( Dts = pd.Timestamp('20200101')
* ~' b- o0 K1 F" ?4 z
) ^: n, c9 I0 W8 ktd1 * 2" S4 A" y$ S6 h% a' w1 T4 c ^/ f( ^
Out[70]: Timedelta('2 days 00:00:00')/ V. p0 A: m# J0 n ^8 c
! o' `, [3 Z2 A0 ]# z* G
td2 - td1
9 \& Z- `; a3 |Out[71]: Timedelta('2 days 00:00:00')
9 x% }9 d \: F$ B8 @! D7 c
. z) o, [) A6 ]2 m1 its + td1
; y( [. H# _* x# c1 v& L- e5 AOut[72]: Timestamp('2020-01-02 00:00:00')
; `- G' I0 h6 W# B
% }! b. o2 ^" x8 E' F3 xts - td1- f( ^- V, `9 o
Out[73]: Timestamp('2019-12-31 00:00:00')$ m; C% w& r$ m
17 H1 ?/ Q. E3 ^$ U* o1 m* a
2
0 ?. x N! Z" |. N" p8 N- k+ h3; E# X7 |: t, U$ H
4
$ `# _7 Y `9 ]4 M) o9 ^# N ]5& e. b0 z7 F! K" p$ @, X( Q
6$ Z, I2 \3 _0 L z
7 s. V( K$ B0 F& ?
88 Y0 | Q3 Y8 M. g2 O7 d
9
9 m5 O ^/ d, Z' R10
4 V! X1 f4 L& b& T11
G9 w. x. k) ]' b. r- Z12$ ?) r3 u6 y! h8 d
13
* a% {( _0 u- L: I14
8 b9 H7 s# C! I- J# S# y. n15
* ^0 }" H! n9 L( L7 ] c时间差的序列的运算,和上面方法相同:
4 E+ H" Z% n+ @td1 = pd.timedelta_range(start='1 days', periods=5)0 a1 L; s# b" t7 n
td2 = pd.timedelta_range(start='12 hours',
! P$ N# y D( U" {" t0 j+ \ freq='2H',% u* _* W. N; p; Y5 N) v7 X
periods=5)
) J) `8 h7 R3 C$ Vts = pd.date_range('20200101', '20200105')
7 X9 V, c6 @& X9 K& H* dtd1,td2,ts
) W' k; m, b6 H- M6 g8 F) {6 }- Q- Y' H: h
TimedeltaIndex(['1 days', '2 days', '3 days', '4 days', '5 days'], dtype='timedelta64[ns]', freq='D')
' K) D+ D! {5 _ cTimedeltaIndex(['0 days 12:00:00', '0 days 14:00:00', '0 days 16:00:00',( b' m1 l- U# o V9 |( J
'0 days 18:00:00', '0 days 20:00:00'], dtype='timedelta64[ns]', freq='2H')& r2 d/ \ A& b! |2 ^: c
DatetimeIndex(['2020-01-01', '2020-01-02', '2020-01-03', '2020-01-04',2 S6 F2 L q) h; V9 B9 i: k1 o0 v$ E
'2020-01-05'],# `2 R- ^) C( a! k
dtype='datetime64[ns]', freq='D')- I& e' H$ d) M9 n; [* p. q0 m" `
1
9 s6 q# } S0 M( ~8 P2$ @' |, M2 |# K. v8 ?' [
3
& J0 g5 a. m# t! y) j4
: N' S o3 P1 r7 g5
; g9 d# b0 ^7 f4 A8 s x6
' n4 `- s4 u% ~% F# W; u( X7
9 ~: G8 C$ A D2 Q& s3 X8* I- o' u% y* z$ ]. V
9
2 e. A9 _. I7 _" p, l6 ~) {10
! P9 p8 r1 o9 c6 x3 ^# h% G) q11) ]& [4 T* I7 \4 ?% ~" _8 k6 q
12, b( k2 V- p2 X8 O
135 q* Q# z- n _7 \% `! Q8 t
td1 * 57 n) o/ {+ o6 b) x
Out[77]: TimedeltaIndex(['5 days', '10 days', '15 days', '20 days', '25 days'], dtype='timedelta64[ns]', freq='5D')" R- o0 i$ @" ]9 Z% J9 `: B
; X+ H' V( m+ Y8 I. O: c0 B$ g3 {( E9 a
td1 * pd.Series(list(range(5))) # 逐个相乘- j- a4 Z- X4 m l
Out[78]: 3 I. t# r, p: _" R
0 0 days
* b- n4 r. H5 h" Y. T1 2 days* g/ e" u& W& ?: T" q4 V' f: A
2 6 days
* P, d2 z$ v3 t$ A, m0 e' ^3 12 days, w& ]' M! p7 ?; g: @4 O
4 20 days
9 ^% J" l, G9 m& H4 K& t) Bdtype: timedelta64[ns]
. D b8 Z p& p X, [# P! f
; A8 b( U3 |- s: g4 rtd1 - td2. s' m, j! z, c- U$ Q
Out[79]:
" [8 J! c7 C/ H9 J7 o/ I; ?TimedeltaIndex(['0 days 12:00:00', '1 days 10:00:00', '2 days 08:00:00',; E7 m; ?. n; |: t6 R* G
'3 days 06:00:00', '4 days 04:00:00'],
. k2 M1 E9 P Y( W' p" W dtype='timedelta64[ns]', freq=None)& i7 p9 ]% Y' |* E. q
( V3 d J+ y1 m0 r
td1 + pd.Timestamp('20200101')$ t O( @$ A6 C! s, Z6 ?" c# o
Out[80]: 8 L# @, ?6 B5 W) @& ~! m6 h
DatetimeIndex(['2020-01-02', '2020-01-03', '2020-01-04', '2020-01-05',
( D6 ^- O, E# R5 A9 g '2020-01-06'],dtype='datetime64[ns]', freq='D')
$ @5 Y8 R8 l# F( h% N8 Y" {$ l- h: x6 d+ ~2 N) V
td1 + ts # 逐个相加
1 l! ^, g5 O. [% j4 }" ROut[81]:
, K3 A+ x$ R( e5 x7 ^* zDatetimeIndex(['2020-01-02', '2020-01-04', '2020-01-06', '2020-01-08',
1 {" n5 ^) @- O7 j) I7 } '2020-01-10'],: B1 o. b1 {* Q; S/ w5 I9 P
dtype='datetime64[ns]', freq=None)
7 h. j- t' h5 e: X- `' p& W
" H/ R% R, ]7 ~. Q0 L& B: |! Y1
: t8 E$ M: z( q6 F( x7 y7 o2# u* |! d: ~3 E6 Q! T: I
3
& S: V$ P) q. [) v! w4
9 f( B5 S5 a" W& T5" K4 Z* v- y( ]6 ^8 \
6
! Q: O% f9 m5 i+ s) g5 t7/ o/ Z' V5 S5 R# G& O$ v# y' J
8% T" z) ]1 N- v. w4 k7 G& t2 w
9
: U% n& i! M% \) U10
" V$ O' d: P7 r6 A$ H6 P1 Z+ ]. {6 c11. a0 a1 A0 j1 X/ g+ q0 x
12
: p( B* ^+ S- y: {. Y2 G3 @13
( r c7 u) V$ b& Q14
& s& f4 L3 K. Y5 H7 S K1 X( L: Z15
% W$ F8 _ |2 I7 x% L8 g- Y16/ N4 a" j7 k: y
171 Y" y( ^" j) J) W9 t9 }: p
18
" B, ~/ \! u5 n& }3 U2 D: V19$ g0 |6 M! k- W: m6 t
209 F7 M- ^" j) h' Q) a H* A/ L/ M
21* b& ?/ Y! z! R# [9 `
22
# s1 V0 L9 P' b! t23
, e1 `& N4 n# A( w" q24# c3 `4 i- R: }6 l- _
25
, \. E5 e2 ~7 c7 d. X7 Q264 U) c0 c- y" O" a9 x, ~0 Y! H
27( t2 e" y7 K9 V6 V7 y5 D7 N# l
28
+ {, U4 H! V: x9 l3 k9 f' k10.4 日期偏置( H( N6 b3 P2 f7 _8 x% s, G' T
10.4.1 Offset对象+ z. D. v6 `/ e7 `) g
日期偏置是一种和日历相关的特殊时间差,例如回到第一节中的两个问题:如何求2020年9月第一个周一的日期,以及如何求2020年9月7日后的第30个工作日是哪一天。
6 r, s3 m# T$ ]) _6 D9 u$ P2 |. h( X9 h/ t, V! T% |& S# a
DateOffset 类有10个属性,假设s=pd.offsets.WeekOfMonth(week=0,weekday=0),则:
5 {. h9 X* t7 m0 l1 q: ~
# F, c9 r2 t- s4 M3 ts.base:<WeekOfMonth: week=0, weekday=0>,返回 n=1 且所有其他属性一样的副本, {) _: b6 U; j% ]. C% a) z, t
s.kwds:{‘week’: 0, ‘weekday’: 0}. m3 A9 K9 z; W7 G/ M4 K) t
s.wek/s.weekday:顾名思义
! L) W; Y3 r4 v2 x有14个方法,包括:; ?6 {9 A8 [' d8 `3 r
& @2 u) ]* o L
DateOffset.is_month_start、DateOffset.is_month_end、DateOffset.is_quarter_start、DateOffset.is_quarter_end、DateOffset.is_year_start、DateOffset.is_year_end等等。
3 Q) Y" k$ g0 lpandas.tseries.offsets.WeekOfMonth(week,weekday):描述每月的日期,例如“每月第二周的星期二”。) | r5 f! B8 p$ A# l
" ^: V9 ^% X8 e2 S5 d: d/ b! u有两个参数:
; j2 l8 o' J; Q; Iweek:整型,表示一个月的第几周。例如 0 是一个月的第 1 周,1 是第 2 周,以此类推。
% x7 V2 e2 K* n7 V, [4 ~weekday:整型,取值为[0,1,…6],表示周一到周日,默认取值为0(星期一)
; x1 h+ y9 i c! D8 Apandas.tseries.offsets.BusinessDay(n):相当于pd.offsets.BDay(n),DateOffset 子类,表示可能的 n 个工作日。* k$ y: D. B8 v9 C
* _, g6 `% `# y& ]% b* A
pd.Timestamp('20200831') + pd.offsets.WeekOfMonth(week=0,weekday=0)4 Z) e; |: J& ]( ?6 c+ W
Out[82]: Timestamp('2020-09-07 00:00:00')/ Q- r$ B6 c% ^6 u1 ~7 K
- C) q; l7 t& [8 _# N; U
pd.Timestamp('20200907') + pd.offsets.BDay(30)
6 w/ L9 `+ j: y) m( X2 K h6 ]Out[83]: Timestamp('2020-10-19 00:00:00')8 l) w) R/ x b8 ]( l: B# D9 l+ X
1
4 C: a, @& J1 @2
7 D1 A) c8 g% U! e& p3! E6 o6 `% y! V+ n$ e
48 [8 e. {$ f+ H. o# k
5$ c: G/ e R4 Y: n" p0 c
从上面的例子中可以看到,Offset对象在pd.offsets中被定义。当使用+时获取离其最近的下一个日期,当使用-时获取离其最近的上一个日期:5 P0 ?' f# F y k
8 j7 Z: G1 g+ @pd.Timestamp('20200831') - pd.offsets.WeekOfMonth(week=0,weekday=0)
( t# {) w( Y) w1 rOut[84]: Timestamp('2020-08-03 00:00:00')( f) w" e- S- S7 m) |
. K4 I; r: Z. w# T9 I" X4 N/ ?
pd.Timestamp('20200907') - pd.offsets.BDay(30)0 X& `3 g& V' h- Q' f" z5 ~
Out[85]: Timestamp('2020-07-27 00:00:00')
0 i3 _0 `$ w7 Q4 H4 N) D/ }. I+ @
pd.Timestamp('20200907') + pd.offsets.MonthEnd()4 h% M. s7 `$ p" H" k. g2 u
Out[86]: Timestamp('2020-09-30 00:00:00')6 s7 i; ^& h+ g6 D2 {8 p
1+ D* k9 I8 ~/ r7 H3 N
2( c- R1 o1 w1 O$ K0 |3 X3 I! A8 C
3' |$ K- I! p3 }, h5 c( r( p
4- |" }1 y" r$ z1 h& b7 Q
5
# \3 |6 M1 f M2 A, N; s, E6
1 \, r1 C+ Y& H5 @7 k3 W/ T7
5 V. d& E; S' p5 y9 k5 u- A! N9 p8+ ]- x% O# E3 b# ^
常用的日期偏置如下可以查阅这里的DateOffset 文档描述。在文档罗列的Offset中,需要介绍一个特殊的Offset对象CDay。CDay 或 CustomBusinessDay 类提供了一个参数化的 BusinessDay 类,可用于创建自定义的工作日日历,该日历说明当地假期和当地周末惯例。
6 X: i2 g4 @* [ 其中的holidays, weekmask参数能够分别对自定义的日期和星期进行过滤,前者传入了需要过滤的日期列表,后者传入的是三个字母的星期缩写构成的星期字符串,其作用是只保留字符串中出现的星期:- x# x; n6 F' j, ?6 Q& r
9 G: t7 t2 J1 ~
my_filter = pd.offsets.CDay(n=1,weekmask='Wed Fri',holidays=['20200109'])
3 |3 i8 D+ H. Ddr = pd.date_range('20200108', '20200111'), M( _: d$ Q) W1 X# P* ~ h* G
8 W- \" o/ q$ s4 udr.to_series().dt.dayofweek' ]; f+ H5 }! q* i/ T
Out[89]:
4 [2 o" ^" ]; A. F. J9 u7 I2020-01-08 2
5 `( I/ b8 R w; j) m! c& a. j2020-01-09 3
0 \4 F; ^' M" A2 |! c+ q2020-01-10 4
, O6 }& m' C' ]0 d p2020-01-11 5
7 Q" h# H& Q$ L) JFreq: D, dtype: int64
$ E2 G2 P3 f/ S; F9 P0 }3 O
! A" }; w3 V# q[i + my_filter for i in dr]. {- K2 y1 G# I" d5 E
Out[90]: # ?6 r, W0 G4 X/ {# j$ v
[Timestamp('2020-01-10 00:00:00'),
+ A" G$ J0 P6 ^: w5 ^ Timestamp('2020-01-10 00:00:00'),
7 a5 G, M: ^7 X( J; l Timestamp('2020-01-15 00:00:00'),( W& W7 E A/ e A- e
Timestamp('2020-01-15 00:00:00')]) Y+ M9 T- n3 }& M* r& ?& s0 `
- _' m, W4 J9 e1 j
1
6 w" v \: s4 T5 W& Z! k6 x0 Z24 z. i+ S7 c3 t3 P! | u) o
3/ m6 q/ t% g) z1 E7 Q6 _. U8 C/ K
4
/ q* @5 @8 a2 p/ l5 ?2 {7 H5
" H# ]/ X& X5 e6 r$ I9 P! l6
1 W3 W; U0 E% v/ m$ U7
, R/ O9 Y8 L0 f' R) \8: w1 n! [0 L# Z- Z6 |% s
97 D( q5 b4 p1 k$ v
10
( q8 @8 d/ _1 V11
0 B& D3 _" `% [# T% S: \121 Q) F) Y! C9 \' c9 G
13
, P4 p- ~7 J4 J3 {+ N14
! H( Z8 I4 B; t9 H9 q8 E- k15
! ^- n7 P# U6 O% g* z16
4 C4 K0 l" L; Q1 [) s8 m17
5 X# S* g9 h7 N7 i" D) m 上面的例子中,n表示增加一天CDay,dr中的第一天为20200108,但由于下一天20200109被排除了,并且20200110是合法的周五,因此转为20200110,其他后面的日期处理类似。5 Y) \& \ j9 y/ ?) f! ]
0 u" R* m: J* n) W8 I【CAUTION】不要使用部分Offset
& A2 n1 R, L3 O+ U. e/ Y; j4 k在当前版本下由于一些 bug ,不要使用 Day 级别以下的 Offset 对象,比如 Hour, Second 等,请使用对应的 Timedelta 对象来代替。
; r8 `6 P/ h5 p) u; n! T
% @6 o) W, L! r! [4 e% Y8 E10.4.2 偏置字符串
7 M" K3 W @. d. t' m% l$ Y 前面提到了关于date_range的freq取值可用Offset对象,同时在pandas中几乎每一个Offset对象绑定了日期偏置字符串(frequencies strings/offset aliases),可以指定Offset对应的字符串来替代使用。下面举一些常见的例子。& v% s9 i+ I, |, F! A" k8 q
8 B2 t3 j2 z* e# s9 U1 l* ]2 l0 K Offset aliases:pd.date_range函数中的freq参数,为常见时间序列频率提供了许多字符串别名。 也称为偏移别名Offset aliases。偏移别名列表点此参看(大概27个)。( d# p/ @4 |! r. E8 k: }
/ i1 z3 b8 `. | r& ^, ^
pd.date_range('20200101','20200331', freq='MS') # 月初
+ Z' r: J# c0 g9 UOut[91]: DatetimeIndex(['2020-01-01', '2020-02-01', '2020-03-01'], dtype='datetime64[ns]', freq='MS')
+ G1 v- r4 H2 \- }
9 ~+ b( \+ u( k/ W/ _! Qpd.date_range('20200101','20200331', freq='M') # 月末
3 S1 ]* @, }& s! b- U1 ^Out[92]: DatetimeIndex(['2020-01-31', '2020-02-29', '2020-03-31'], dtype='datetime64[ns]', freq='M')
, j1 t3 e5 n" V" C d' f: ~; k' m0 ]& a& A4 v; ]2 M" |* o3 f) c9 \0 ^! j
pd.date_range('20200101','20200110', freq='B') # 工作日9 C! r- Y! @# b& W
Out[93]:
, S a) |2 U' RDatetimeIndex(['2020-01-01', '2020-01-02', '2020-01-03', '2020-01-06'," w1 k$ b, [, {
'2020-01-07', '2020-01-08', '2020-01-09', '2020-01-10'],
, [# |$ H6 T/ g" `+ }+ m: y dtype='datetime64[ns]', freq='B')
2 s# M! R6 L$ f; ^" I1 }
$ X8 r' X0 Q, n b* [1 Ypd.date_range('20200101','20200201', freq='W-MON') # 周一
8 h4 y4 F/ s6 O5 LOut[94]: DatetimeIndex(['2020-01-06', '2020-01-13', '2020-01-20', '2020-01-27'], dtype='datetime64[ns]', freq='W-MON')/ w6 P8 S" J/ Q5 ?9 x. J: i. y
7 q0 D/ n3 Q8 B
pd.date_range('20200101','20200201',
% |$ y% M# u9 E4 @/ n, x freq='WOM-1MON') # 每月第一个周一+ ?9 I5 Y6 d" g; m3 @ a- m
0 u3 g) ?, P! R' j: h& J9 f
Out[95]: DatetimeIndex(['2020-01-06'], dtype='datetime64[ns]', freq='WOM-1MON')
1 X6 ~; D- F5 t$ m. E* a
* U8 F$ ^5 q7 R) P& N1
. }2 j3 L5 {, y4 m) ~+ @4 H* [0 Q5 C2+ R$ w1 B% g+ h1 i/ x: G( |% `
3( J; e& E2 ^- S7 c" c
41 X5 n0 i+ i$ d8 N& {, w5 T2 C* s
5
: U* F. e# x9 h6 l6" h0 S2 q( r9 T
7
. g0 ~! v' x/ T6 _6 ~ U5 S86 m( E* i, |6 z9 [- }& m
9. V, q8 ` @6 B" ]% y$ W! a( P
10
# I! e, V9 z4 J* u( y11
8 F+ [" ?( o! a. k9 ]12
5 c# }- m2 i6 \; k13. v* j* w( G, I# |
14
$ J4 F+ Y* A5 o2 T! K1 W' P) @' [+ M15
. N* E0 I t, F5 v0 ]16
: E# k) n& A: y( u- W9 X3 h, B17+ J* j' M! M& x2 _) u6 d
18( N! ?5 I2 q1 L6 D1 X6 U8 y
19
* N# z/ Y5 C3 o! ?. d' r9 [上面的这些字符串,等价于使用如下的 Offset 对象:& Q2 f+ {3 @5 j4 s4 T7 A
, E1 b' V' G$ R0 P3 @# G0 x) p8 T% v
pd.date_range('20200101','20200331',) k0 x8 i5 H5 v3 I" _$ _
freq=pd.offsets.MonthBegin())
3 |" A x$ k Y& ?( {( p! V
, u2 ?; n3 i% q- n; V9 o( mOut[96]: DatetimeIndex(['2020-01-01', '2020-02-01', '2020-03-01'], dtype='datetime64[ns]', freq='MS')
( Z a/ j3 N/ d( L8 b$ u- `6 [ J4 y6 W2 [" J A, R# G9 Y6 X/ _
pd.date_range('20200101','20200331',* x% m9 _% V3 [" A5 z0 F
freq=pd.offsets.MonthEnd())
! T, O7 e: j& \6 s
8 Z- F; V8 n2 b2 ~, ~8 p* o# yOut[97]: DatetimeIndex(['2020-01-31', '2020-02-29', '2020-03-31'], dtype='datetime64[ns]', freq='M')
1 j% _4 x, K6 U3 @- T" @4 M4 @" w4 ]" z% v
pd.date_range('20200101','20200110', freq=pd.offsets.BDay())/ I# o, e$ ?4 x2 K, }
Out[98]: # W8 [' k5 m+ M5 l7 m3 [& f0 S3 s
DatetimeIndex(['2020-01-01', '2020-01-02', '2020-01-03', '2020-01-06',
* W; y6 r) @$ {5 u' Q '2020-01-07', '2020-01-08', '2020-01-09', '2020-01-10'],
/ ?" N& ]+ X; N. l1 k/ e* H0 b X dtype='datetime64[ns]', freq='B')
. e h! X# B* `. C# ]% k8 v9 ^. U4 |+ \$ g. m/ n. c9 h
pd.date_range('20200101','20200201',9 r+ T- y; Z- b! A0 [/ {
freq=pd.offsets.CDay(weekmask='Mon'))0 J5 Z! _( V' m
j* a0 w* Z3 X; E8 Y) U# ?Out[99]: DatetimeIndex(['2020-01-06', '2020-01-13', '2020-01-20', '2020-01-27'], dtype='datetime64[ns]', freq='C')
9 L2 q8 s# o, w" X
- D# z$ f. h, D! C) }" ]2 r3 {2 Ypd.date_range('20200101','20200201',
; o8 I0 B3 [$ I freq=pd.offsets.WeekOfMonth(week=0,weekday=0))8 m4 z2 l! g' e3 C1 v2 \, o
6 N/ e6 B) B8 @' r: N# U% JOut[100]: DatetimeIndex(['2020-01-06'], dtype='datetime64[ns]', freq='WOM-1MON')
: |3 y( O4 T3 ?; v
' @7 t; R3 S. m1/ K+ A7 z0 c' e/ ~- A
2
& m- v* F) j2 ]: O2 K3, r% [1 T& m+ a; e+ ]+ f4 @+ M; c
4& ?, f6 ?+ T6 w3 V1 N* x' j% I
5! p9 q3 s/ q% D! ]& w
6
7 X5 U$ U( \" \8 B! K1 J7' ~7 h. B2 H+ B
8
) c) U8 n/ S/ E% L# J91 j% [; p' ]9 f' U! l; ^& l
10
# m8 x' z- Q/ c1 V' c- m11
' T. Z6 }' W5 J& W: X12$ u% C D% l8 H3 V
13" G+ h! ~& J: W: |' n4 H
14. S0 _! I* K/ n
15
, W! u& N4 X+ H0 o( C( J# R7 z6 U160 T1 N0 ~: }$ r9 w
174 N5 E4 ?9 S5 B+ D/ d9 s+ [- M
18% T8 ^2 z2 e: E! ~9 }( _
19
8 H% N7 F7 X( c! c+ f20
! @5 c% j: ?% @/ e0 ?! L21+ ]3 F# z7 a+ s% ~6 N- o4 u
22, h. o' r1 _1 E7 S7 n* \
231 V1 E1 ^* `; V( l: f# w4 Y' H: v7 \1 @
24) o! c/ H) u, E8 J& ?" }0 i
25
0 A1 W) L8 H" R+ d7 C【CAUTION】关于时区问题的说明$ W+ W. P5 x& f4 B5 F; n6 g6 P
各类时间对象的开发,除了使用python内置的datetime模块,pandas还利用了dateutil模块,很大一部分是为了处理时区问题。总所周知,我国是没有夏令时调整时间一说的,但有些国家会有这种做法,导致了相对而言一天里可能会有23/24/25个小时,也就是relativedelta,这使得Offset对象和Timedelta对象有了对同一问题处理产生不同结果的现象,其中的规则也较为复杂,官方文档的写法存在部分描述错误,并且难以对描述做出统一修正,因为牵涉到了Offset相关的很多组件。因此,本教程完全不考虑时区处理,如果对时区处理的时间偏置有兴趣了解讨论,可以联系我或者参见这里的讨论。4 Y* C7 e% Y% ~. F5 a
) R# `4 m: H# O2 P+ T& L" I$ ^
10.5、时序中的滑窗与分组
9 S$ A+ l) s; o5 X$ {1 u4 N2 {# Y10.5.1 滑动窗口
' t' m6 S4 w9 x: P) n7 u" ` 所谓时序的滑窗函数,即把滑动窗口windows用freq关键词代替,下面给出一个具体的应用案例:在股票市场中有一个指标为BOLL指标,它由中轨线、上轨线、下轨线这三根线构成,具体的计算方法分别是N日均值线、N日均值加两倍N日标准差线、N日均值减两倍N日标准差线。利用rolling对象计算N=30的BOLL指标可以如下写出:
( B) s+ o: B$ j5 x) h1 d$ Z3 H3 @) }/ O" u
import matplotlib.pyplot as plt5 E) [" y8 v7 c
idx = pd.date_range('20200101', '20201231', freq='B')
- L: {# ]3 I/ ~* Jnp.random.seed(2020)6 _# u3 }: d0 p3 ^; n
; j6 Y. ^0 C( A6 l. D5 i5 |1 |. |0 m
data = np.random.randint(-1,2,len(idx)).cumsum() # 随机游动构造模拟序列,cumsum表示累加/ {! C/ {, m1 F
s = pd.Series(data,index=idx)
" y( Z1 U+ j) fs.head()
6 D- v2 z! _3 V% I; bOut[106]: . S0 a7 ^, G. G/ H: Y1 J
2020-01-01 -1
4 a0 h! H, R" M8 F: f8 u) J: ~2020-01-02 -2
9 s2 i2 P: i% X: s7 j0 ]2020-01-03 -13 N2 h7 a2 Y7 f% ^- w& q+ O
2020-01-06 -14 t4 y" ?, `; B' U& }7 O4 a/ k6 Y
2020-01-07 -2
/ n/ L. E) ?5 f6 `Freq: B, dtype: int32/ S+ j; j4 {* h( R: v
r = s.rolling('30D')# rolling可以指定freq或者offset对象5 ~& L% t2 m* K1 n% B/ ]1 Z
% _3 Q3 G( w# d) c5 g' ]plt.plot(s) # 蓝色线5 d. _6 O! A8 ]/ L& l
Out[108]: [<matplotlib.lines.Line2D at 0x2116d887eb0>]
9 K* L1 F3 c4 Bplt.title('BOLL LINES')
! o0 m( R1 p9 g# H2 P3 d2 ^; [Out[109]: Text(0.5, 1.0, 'BOLL LINES'); `6 ~2 e: F( R0 ~" C* n/ G
& y" y3 A. d4 @+ y6 L& uplt.plot(r.mean()) #橙色线2 b! n M8 G6 O5 F
Out[110]: [<matplotlib.lines.Line2D at 0x2116d8eeb80>]
9 C; P8 O3 c) }
( X- c e; L9 H( D0 e+ Uplt.plot(r.mean()+r.std()*2) # 绿色线/ p2 c$ T+ E" P0 m
Out[111]: [<matplotlib.lines.Line2D at 0x2116d87efa0>]
, ~ h$ m9 w( V4 z! J3 D" z5 u4 @+ }4 O) }5 I& ~* H
plt.plot(r.mean()-r.std()*2) # 红色线 }0 ?0 U. a, n5 l8 Y$ q/ S
Out[112]: [<matplotlib.lines.Line2D at 0x2116d90d2e0>]6 m ]& w* T% L$ B& r
9 Y2 L; s+ h/ ~; \/ i5 `1
0 x& N! O; t% }8 F2! j3 Y; h0 v) c$ o' d% f+ A
3
# ]2 F4 {$ x5 L8 [- O: Q' ^4
' I7 ]/ |$ ^. V* D5& c3 {- ?! L. Q9 Y
60 s! D3 y, `' F$ \; _: Z# e
71 J% ^2 H5 k3 s
8
. x5 V: g- E! a9
% ~# P" w X0 N u10; q6 N1 _ Q/ y" q: X2 Y/ ?6 c* c; Z
11* a! g% ?8 [! d5 M5 D) U4 g1 H
12
3 W. n( E5 Z& ~/ X! v13
% K8 {; P$ ^, {9 a) e: g14
! [ P! P! R, z& |8 k- R& X- `153 ~0 f; v' t o9 p& K A+ g
16
( R7 g1 u A" m, C; H17 Z2 L/ Z; v0 Y
18" G# I+ `) w6 j5 k U' y* T" K
19/ n- j9 e v4 {8 Z
200 \. d% m$ _! O5 I
21+ R9 H3 s2 H3 D( w
22
8 ?- @6 h- o9 j3 F9 \23& P0 M; x, K8 i4 Q6 b5 j, z0 J
24
. E& d* C, e4 g25/ b5 z, x3 w' {0 e) J# _. u: B9 l
26
3 Z+ Q" J% K% {( ]2 [27
& o' E# v3 p% x; m$ [286 f( I- H G, r& `8 [) v9 m1 g% ^1 @
29
4 ^% c: d; h4 _5 J+ _. P' R4 m o- y1 u
这里需要注意的是,pandas没有实现非固定采样频率的时间序列滑窗,及此时无法通过传入freq字段来得到滑窗结果。例如统计近7个工作日的交易总额。此时可以通过传入多个函数的组合来实现此功能。
$ J# ?+ g* j2 }# q6 F: s4 E1 r 首先选出所有工作日,接着用普通滑窗进行7日滑窗加和,最后用reindex()恢复索引,对于双休日使用前一个工作日的结果进行填充。$ H3 g$ x5 I8 i; e+ n) u, P
) P8 u O) c4 y; ^! Z; |! h# U
select_bday=s[~s.index.to_series().dt.dayofweek.isin([5,6])]0 V3 _2 e' j+ B5 S
bday_sum=select_bday.rolling(7,min_periods=1).sum()
. k+ j! U' n+ v% t# l; O: bresult=bday_sum.reindex().ffill()
8 i4 Q1 n/ E1 o- E7 b# j1 U% ^result
7 O6 A+ j. _! R2 ?$ E% P! j2 N9 o' D8 g2 E0 L/ N
2020-01-01 -1.0
" b& ~! p4 [4 [+ s2 T2020-01-02 -3.0% H! t5 M+ X2 Z' f! Q
2020-01-03 -4.0# D& G; I* O3 |" q; n! s" Y4 o
2020-01-06 -5.0, [7 G3 R$ `' f A, {( |! }
2020-01-07 -7.0
. p6 l0 v; ~1 }4 f5 K1 s+ }6 \: w V ... # l" B; V1 s' i: D1 A
2020-12-25 136.05 s5 e1 U$ N+ `
2020-12-28 133.0
! Y5 z* F; ^3 }% b% m2020-12-29 131.0+ W5 t: X3 M5 k; X8 m9 _
2020-12-30 130.0! e" @/ P- V ]6 t
2020-12-31 128.00 }# |; B0 ]+ B( X3 G T, w( C. G
Freq: B, Length: 262, dtype: float64( I7 b4 h3 K7 H+ o: Q$ q; J
* C- |* ]) `% w* O' |$ O+ {$ f9 n15 t8 ?* V) f+ R5 h5 ?" Z+ \, B0 ~
2( D7 A. w$ N+ ]9 l8 c
3& j1 y( X3 E0 N$ ~
40 v- c# c2 @! z& R+ D: I; |
5% U9 ]" ]. H6 u- v4 G
6
2 y; X' x4 v( d, q1 U7
# ?, \' \3 }0 H- a1 G8
+ i0 O1 [. G x! U) M9
/ T2 {$ d8 }. i. q R1 e. v( O10" b3 n/ l5 R2 Z1 ]
11
2 M/ N1 b6 p; E# Z. \. E V' a12# W! i4 `% P, d0 b
134 q: G& l/ v& j, x9 ]6 ?
14
# g; V# L, V0 x' E3 G9 R: r15
) V& N3 x; y* Q ^ N6 X2 I16) h' a$ j$ r; F; C
17! E+ V8 W7 l! E( |# L
shift, diff, pct_change 是一组类滑窗函数,它们的公共参数为 periods=n ,默认为1,分别表示取向前第 n 个元素的值、与向前第 n 个元素做差(与 Numpy 中不同,后者表示 n 阶差分)、与向前第 n 个元素相比计算增长率。这里的 n 可以为负,表示反方向的类似操作。' e: M0 u( o. G) Z
' J; n8 ?9 f8 [9 `+ t- q& H% h 对于shift函数而言,作用在datetime64为索引(不是value)的序列上时,可以指定freq单位进行滑动:
( `) d9 Z' |8 Z0 {. m' Y+ D. i
! [$ A. w; Q+ l6 f2 ~s.shift(freq='50D').head()3 ~& W6 d. N- [5 _0 {" ~( M( K+ l# X
Out[113]:
* h/ ~. v, \& u+ t" S2020-02-20 -1# s+ x* B) N7 o& m
2020-02-21 -24 h; a f3 q, w h' z
2020-02-22 -1! \+ w$ m8 Z* A7 T( X" A v2 `% O8 \ z
2020-02-25 -1
. ~! A, s( H* f, S9 a2020-02-26 -2# S) x7 Q6 t Z, x9 Y& }9 b
dtype: int32
1 Z. Y" O. }) R. A; Q1
- v# ]$ ^( R1 S Q& V" @2
* m6 M+ T3 V8 o1 X8 q- p3
6 E' B' K$ }& ]0 T- U& V4
" l, y# j" A( P' L& g# B57 N$ f- l$ A$ O1 Y% A
6+ j/ }! ~# ~* W* V% L9 M+ ^$ s: L# h
7
% E# ^ V8 Y: ~7 i7 a" ?2 |8 {4 g8
3 w% F1 r' _" E& n+ Q+ v 另外,datetime64[ns]的序列进行diff(前后做差)后就能够得到timedelta64[ns]的序列,这能够使用户方便地观察有序时间序列的间隔:
/ l4 U* p! K6 R, \" X3 P: X1 \' A' p0 [
my_series = pd.Series(s.index)
3 P6 ? L9 |4 B$ Q# ~* @+ kmy_series.head()
8 ^, u9 {' s2 v' D* ZOut[115]: & W4 R& X( P( E2 E; T
0 2020-01-01
, x/ Y) W' k) T$ b1 2020-01-02* O; x' K! {6 f4 m& K
2 2020-01-03$ _& @* n2 Y7 _0 \) q
3 2020-01-060 x+ t" b5 W3 v- B8 {; [% d' X' i ]6 r
4 2020-01-07
$ ]0 h% Z, o8 i6 D6 [dtype: datetime64[ns]2 V/ x' d0 @5 u: D- n' o
' v H8 [8 w1 @0 |/ y3 u/ W: s
my_series.diff(1).head()
8 H' T" |, g, v \# eOut[116]: # ~# S1 O9 F1 \ ?7 b9 _
0 NaT0 y, I2 l. u( J. B2 w3 L- C+ p
1 1 days
3 ?- o8 Y0 E5 }$ @4 j% P k/ F% ]2 1 days
/ p5 \/ T! i' Z. W' m" V$ k Y' \; ]3 3 days
+ s0 l0 l2 Y% R4 1 days
8 `* M4 R- Y6 n$ _* idtype: timedelta64[ns]! f' F" \4 P! h1 h& I
. h& _# S7 e/ E" T" O7 ?) _0 i; f1+ r. o7 u x9 h5 _9 p: p$ d3 P7 }
2) c5 @+ V. Y, I7 U! o
3! n& A6 m$ |( s/ G2 e
4. D$ S+ F0 e, r ^, J# o- `/ l
5
& J+ ^; h$ m3 d1 I- o( Q6; B+ O9 N/ c ?
7& F! V3 ^3 @; K( ]4 a
8
+ A. a- p D" F: |4 o9
' T2 U9 f& V4 p3 Q3 e; }% I100 L6 @' a% A- `( T+ [7 p4 B9 @& {
11
# w! W _$ l! s+ r# ]9 y" S9 t120 Q8 s; j& m9 Y/ u r
134 i: `+ [7 O- X; d; U7 U! W
14& o0 C7 R7 b: h5 ?4 b$ P
15# g$ ]* @7 _( e+ o! Q- {9 c" U. D& R, H
16 b7 F, s- v, G8 a
174 W. B. k* m& @( p! }' i( m
18- B7 r5 ]7 C$ Z: u/ S+ e
10.5.2 重采样/ M/ z; y; E) e$ x, p8 I9 q
DataFrame.resample(rule, axis=0, closed=None, label=None, convention=‘start’, kind=None, loffset=None, base=None, on=None, level=None, origin=‘start_day’, offset=None)2 h! ~) G4 r% w y* s
常用参数有:9 j2 x" V1 w) T7 j; G
1 f9 s2 k& s" hrule:DateOffset, Timedelta or str类型。表示偏移量字符串或对象0 \6 Q& M0 x2 F3 n K% O
axis:{0 or ‘index’, 1 or ‘columns’}, default 0。使用哪个轴进行上采样或下采样% S' H9 {' W! R3 J d9 N1 ?1 d
closed:{‘right’, ‘left’},默认None。表示bin 区间的哪一侧是闭合的。所有offsets的默认值为“left”,但“M”、“A”、“Q”、“BM”、“BA”、“BQ”和“W”均默认为“right”。0 ?% H; i Z; }. e# G8 A
label:{‘right’, ‘left’}, 默认 None。hich bin edge label to label bucket with,所有offsets的默认值为“left”,但“M”、“A”、“Q”、“BM”、“BA”、“BQ”和“W”均默认为“right”。
7 u) h- r$ R0 D5 x$ M. bconvention{:‘start’, ‘end’, ‘s’, ‘e’}, default ‘start’。仅针对 PeriodIndex,控制是使用rule的开始还是结尾。3 w" O A+ I. c: X
on:字符串类型,可选。对于 DataFrame,使用列而不是索引进行重采样。列必须类似于日期时间。
) m9 E8 n* h4 E1 f( J: [/ Y( Flevel:str 或 int,可选表示多重索引MultiIndex的级别,这个级别的索引必须类似于日期时间。
: Y: U1 M; |* Q8 V& j7 s5 porigin参数有5种取值:/ J) ^' U; J2 ~, R$ L, c3 s
‘epoch’:从 1970-01-01开始算起
2 w4 Q4 y( e2 q, s‘start’:原点是时间序列的第一个值
: Z! R/ o# {' F! \! C‘start_day’:默认值,表示原点是时间序列第一天的午夜。$ o# f e6 M% T/ o
'end':原点是时间序列的最后一个值(1.3.0版本才有)3 q" B& A, ~! q7 X& L% L! w6 N
‘end_day’:原点是序列最后一天的午夜(1.3.0版本才有)% N+ p; P1 d. x/ E6 k7 v6 Z
offset:Timedelta 或 str,默认为 None,表示对时间原点的偏移量,很有用。
x7 W0 h9 E! v& q0 r closed和计算有关,label和显示有关,closed才有开闭。; B- Q \9 Z3 |3 ^ K9 M" d' @/ X
label指这个区间值算出来了,索引放区间的左端点还是右端点,closed是指算的时候左端点或右端点是不是包含。
- q0 y. D+ H1 c& S
1 T% d% R0 S5 t1 I% [重采样对象resample和第四章中分组对象groupby的用法类似,resample是针对时间序列的分组计算而设计的分组对象。例如,对上面的序列计算每10天的均值:) H6 a$ ~" n- k, A9 _1 e- B) P
s.resample('10D').mean().head()7 l1 m% i: `3 ?7 A+ M6 x; E
Out[117]:
+ f x% z; W7 c) l* h5 N% f- {$ f2020-01-01 -2.000000
! g2 O4 @7 w r s& E2020-01-11 -3.166667, D& I5 I8 K K1 h4 e
2020-01-21 -3.625000
' }3 n5 ^2 V& K2020-01-31 -4.000000
7 l. R, i2 s* }% c5 l8 g* ?2020-02-10 -0.375000
) g$ {0 Z- S3 [7 P* |/ `Freq: 10D, dtype: float64: T2 e! A/ j. H0 w
1# j2 g' k& [' Y" J: e
2) Z8 I9 {# t' a3 t% O0 d
3# r3 ~* B2 e9 ?, X; Z
4- Z' |% ~$ \4 U j
5+ T; ^% \' C- d( [$ r
69 e3 W9 z, [3 V2 a1 c
7
4 v0 p. L9 F! i5 y/ K8 F7 b2 l8: C+ }1 A5 ~% K* q) H
可以通过apply方法自定义处理函数:* j7 ~( t/ l) ]
s.resample('10D').apply(lambda x:x.max()-x.min()).head() # 极差( a6 _$ j L2 X, q8 j
3 U- I. l+ i9 _3 ~) R1 U) E
Out[118]:
1 W# H2 W+ _2 c, y9 ], f* f2020-01-01 3. w1 C& x L- D
2020-01-11 45 a0 }! v) J) x: ?1 L8 r
2020-01-21 40 W2 T. l- E" ~4 @% Y4 d
2020-01-31 2
9 U: R$ t6 l3 y- ?2 ^* O2020-02-10 45 M% P# F0 K' R( I: V
Freq: 10D, dtype: int32
/ Q1 c4 s6 E/ M) d1 Q1
+ c( b+ l/ D4 Y4 ~0 x21 }: S& K5 b/ x: W) B/ f: V
3* `0 T4 m4 W9 V5 Q. @" J8 S
4
1 D# `5 E: J6 G, O( k+ H5
+ h, u9 o/ X1 v! k- P! m6/ N9 f& ~3 e' B) K
7
6 }4 a' N. E, y% [- i$ H8
& S7 {6 O- b; N$ v7 G9
! W( K4 f( o( l. \3 q6 T 在resample中要特别注意组边界值的处理情况,默认情况下起始值的计算方法是从最小值时间戳对应日期的午夜00:00:00开始增加freq,直到不超过该最小时间戳的最大时间戳,由此对应的时间戳为起始值,然后每次累加freq参数作为分割结点进行分组,区间情况为左闭右开。下面构造一个不均匀的例子:8 ]. J- t2 ~$ Z1 t: q
( I$ x5 z- S0 t" e9 u$ K m. Y' U
idx = pd.date_range('20200101 8:26:35', '20200101 9:31:58', freq='77s')% D; J% l8 ]; G: M
data = np.random.randint(-1,2,len(idx)).cumsum()) p' ^- W% h0 o! y7 P+ X+ t& W* L' @
s = pd.Series(data,index=idx)" c# T9 H0 q4 }
s.head()
( O4 ~- ?4 w7 P1 a1 y$ n& X Z4 W V; m0 s
Out[122]: * [+ R" Z9 _% w. g# Y
2020-01-01 08:26:35 -14 I% v+ v J$ ?/ P3 W
2020-01-01 08:27:52 -1
/ T2 X. g3 T8 I0 ~: [3 g" P2 J2020-01-01 08:29:09 -2
$ L" ~ H, V' K9 S, C" G2020-01-01 08:30:26 -3: t& q- f( D1 o4 t+ r
2020-01-01 08:31:43 -4
* W1 g# l3 B+ J( c# M# }/ {6 \Freq: 77S, dtype: int32& T6 ]/ ~: W7 u# I; [4 ]. ]
1) R( i; ?, k; Z/ x3 }0 `5 S- z" b
2
) J: ?$ \( @% {6 c2 X3
9 A& `+ e O6 F/ h4/ R! s- X% e/ F0 H# ^4 [6 N) J
5
# |7 m5 B4 A5 }0 ?: z8 G9 c6
$ u6 K7 V% v5 j7
' M* L E+ Q" A. i8+ x' J& Y' P, ~ ]- x5 A
9
" p$ Q! H4 j+ }8 n/ |! W10$ M* h( K' B% Z4 k1 R% i& _
11
7 |# Q8 j6 T! y* [; t1 W* ^12/ D+ |( a! @" K( o. l1 F. K, S$ ?
下面对应的第一个组起始值为08:24:00,其是从当天0点增加72个freq=7 min得到的,如果再增加一个freq则超出了序列的最小时间戳08:26:35:6 m' y8 `) ^( Z+ g2 q f
+ h! r a$ e9 i
s.resample('7min').mean().head()
$ b6 B( G4 i+ Q% oOut[123]:
& j8 w: V0 ~+ r, _2020-01-01 08:24:00 -1.750000 # 起始值,终点值包含最后一个值
# z) H# y, N/ L9 Y( V2020-01-01 08:31:00 -2.600000* A4 \) A& X' U% Y& @9 z5 H
2020-01-01 08:38:00 -2.166667
. R: W% k y' b7 @/ d8 B2020-01-01 08:45:00 0.200000# P ?$ K6 U" e
2020-01-01 08:52:00 2.833333
. i9 {6 F+ z6 O$ @Freq: 7T, dtype: float645 \, V) X1 O N. s1 K2 E. |
1
: n) Z4 n& E q5 d& ^$ M2- o4 _, ?9 D+ j1 X
3 ?; |# e; Q% E% ?
4
: d* s+ g) ?+ t7 |56 \8 H; o/ M G, R6 z# ^* b
6- M" Q8 V( t, p& k7 T: F
7
9 s' |' C E, U7 d* Q# @( p% d0 C0 J8/ U8 i9 O g" N" c
有时候,用户希望从序列的最小时间戳开始依次增加freq进行分组,此时可以指定origin参数为start:
( D# J; x: `+ u( G. l0 [: i# `/ \3 D3 y/ E- Y7 o N
s.resample('7min', origin='start').mean().head()
. _5 C& j7 D: r) C3 wOut[124]:
* Q) b$ l" y' \0 l, F3 r. e2020-01-01 08:26:35 -2.333333+ p5 |) L0 z0 D. i8 C+ B" ~
2020-01-01 08:33:35 -2.400000% [; t( l- y: I0 Z! \. H
2020-01-01 08:40:35 -1.333333
4 y A7 d' F7 U7 r7 q2020-01-01 08:47:35 1.200000& O1 M* W. n' \% f
2020-01-01 08:54:35 3.166667! Y* W" }* v: X( @: p
Freq: 7T, dtype: float64
; `: m" P; N# r1, R2 L1 v! h5 x, b7 V, F
2
y J% t' C/ S5 z1 ?5 h" u30 R1 Y1 j& ]9 r2 }7 }, {# k
4
% l. i; O( G! }* h D- S5 W5
* ^1 S; w3 T2 H- [* Z# _6
; P+ K/ a: W- M L: W7. a, A3 Z( b, p9 Y
8* E! n' ~7 Z- r( a# \% U
在返回值中,要注意索引一般是取组的第一个时间戳,但M, A, Q, BM, BA, BQ, W这七个是取对应区间的最后一个时间戳。如果想要得到正常索引,用’MS’就行。
7 r z; Y, v: ?8 `0 O
" h) B3 [" b3 u7 `6 ?) Z0 h; {s = pd.Series(np.random.randint(2,size=366),* s9 w2 ^& Z) r& ~
index=pd.date_range('2020-01-01',
' f1 i. D9 p3 K' J) L '2020-12-31'))
. I5 X f) [' G8 Y+ U
! Z* Y9 n8 X5 s4 | r
! a, n$ \' k) b; }7 bs.resample('M').mean().head()
v& `8 F$ e. U/ `Out[126]: 6 U% s& t/ _" [8 z' F8 X
2020-01-31 0.451613
9 ^# N4 g" K9 S# {) p/ ?6 j; g2020-02-29 0.4482766 E, @3 m& _6 K+ |7 q5 [
2020-03-31 0.516129
% W2 [8 k" N# I2020-04-30 0.5666673 Q& j; d. V$ c. \5 c) m0 n. F
2020-05-31 0.451613
5 O. P b& \2 sFreq: M, dtype: float64
& M% [* W# z0 T( V5 q4 d N6 F! d' H% j: M/ O2 M y
s.resample('MS').mean().head() # 结果一样,但索引是跟正常一样
0 h- c: t& l: ~/ BOut[127]: + f& n0 Q) @6 V& a( {, v
2020-01-01 0.451613
3 T c9 ]( P1 }0 H" I U- v! o q2020-02-01 0.448276+ Y4 _9 S& m; g! v
2020-03-01 0.5161292 y3 P4 \9 j" A3 o! v: j- q3 p7 j( x
2020-04-01 0.5666679 @; P1 V6 m' \" ^
2020-05-01 0.451613: c4 a. l7 h1 p' U r! Z
Freq: MS, dtype: float64+ q3 U. J8 g2 `7 E5 R# J% J* {, t
1 U9 K/ t; g/ o4 r7 p
1
' }5 ~; w" w" O0 v a/ j8 S% G- i2
. y; a9 W- ?; }8 O( A9 o. D1 |3) p3 M6 J; H9 P/ h% V( S
4. X9 D2 C. b7 L1 G' v# X8 J: x8 U
5
% ]5 _' l3 u; A0 ^2 e6* E$ n7 U7 x' K. V) W" \
7
7 z% e3 q+ K7 U# n9 o6 @1 a8
: g, G* V( s$ M9
! N* z/ _2 O& S( @! w10
4 Y; m, a- W* g/ j5 I8 y11
l* Z2 H. y) Z7 Q12- V9 }8 h8 p& L4 ?3 g9 ^* V! f
130 N8 J- a3 Y6 R. A4 u9 i" |& A
143 O+ V$ Z9 U+ ]! y1 `" W
15
( Q4 ]* Y" [* y' I16
# D$ G5 [$ i1 N- f% p! Y7 J) @17' C4 a( N t1 w5 Y, T, w' H9 a# s
18' ]1 M' D4 k/ q# G* s
194 \' U6 {7 M5 T" x: H7 o$ k
20# I$ ?0 d5 b% R3 y) G5 O' Y" S
21
a5 O$ W* U" R221 t& w. ], O- E1 M
对于 DataFrame 对象,关键字 on 可用于指定列而不是索引以进行重采样:3 u( d& T; K& J6 Y
d = {'price': [10, 11, 9, 13, 14, 18, 17, 19],8 o) r" |: u/ a! O% [4 e' [) q" ] Z7 r
'volume': [50, 60, 40, 100, 50, 100, 40, 50]}! s8 ~8 X/ A, T8 C* ~! F
df = pd.DataFrame(d)/ ^: m3 |- a5 m: B, T9 j
df['week_starting'] = pd.date_range('01/01/2018',
- i* O9 b+ V2 p# G. s0 i7 i0 V periods=8,' g5 I/ Z; H [* T+ W' u
freq='W')# \4 k/ U2 a( P2 N
df& s: ~0 t6 ]9 k R
price volume week_starting1 W1 E( o' ]( D0 L% _: r
0 10 50 2018-01-07
7 y) m8 X- E8 M' F6 n5 l1 11 60 2018-01-14
% D: X8 Y+ H: `# _2 9 40 2018-01-21
0 g) `) A4 p. W8 A+ P9 a3 13 100 2018-01-28
7 v: R0 p1 o5 g* c1 y5 n( {4 14 50 2018-02-041 i" Q9 D. \& `+ `
5 18 100 2018-02-11
' C$ ]* e0 W3 s- }+ @1 X8 \; v6 17 40 2018-02-18( [, V' E5 b& l$ D
7 19 50 2018-02-25' m3 W5 H9 K# M) K$ N
df.resample('M', on='week_starting').mean()
4 F( F7 \7 t( S price volume7 N( x; C" r. ~* U! d# B2 }+ M! D
week_starting- D& [, ^# [$ [, e) _( P9 _- h; ~
2018-01-31 10.75 62.5# {: m- R) F! i4 L! d+ m
2018-02-28 17.00 60.0% i, V* @0 v2 Z# S
0 n' ?, J7 Z2 H9 L11 t* a' d- L1 I4 v* |) s! B
2! c# U% z" u" j( N* U' t2 E% B
3
l2 u3 y3 R5 }4 S% E8 q+ ?2 @& u
53 n+ {* K( T8 W& u9 f
6+ N. T9 {0 C# X2 `/ N+ q
7( T' _; w' J' X7 S* r
8" Y5 `" M7 B' A* V" K! x
9# T- x7 m: l2 l1 J/ b' @
10
0 Y& G) e3 ]8 }11
+ C( |) D1 t5 l/ r4 A129 E8 v& L5 c) f/ S, F
13 ?: c+ u/ v: P% l* C' r0 [
146 R4 ~/ N$ Y( m1 U0 w5 e
15' {8 p6 `( ?8 J, h$ b
16
6 T$ v$ ]( S8 T) a5 {+ a1 p$ N1 k17
/ n* f: A& A1 N! ]7 M18
( G7 d$ @+ j. M$ h9 L8 Y( R0 _19
# d( z' j+ c$ g6 [9 {7 O. i20
, }) l: I- C' L: G' {0 t& U21* `/ ~; `4 ~. h
对于具有 MultiIndex 的 DataFrame,关键字 level 可用于指定需要在哪个级别进行重采样。
9 F# z2 D7 d# W' }days = pd.date_range('1/1/2000', periods=4, freq='D'). `2 `, Q. B6 Q" J# A- F& I
d2 = {'price': [10, 11, 9, 13, 14, 18, 17, 19],
% q+ D& n( h/ K: [! T 'volume': [50, 60, 40, 100, 50, 100, 40, 50]}! B9 k% E( |. O: p! N9 ?* @( h- O
df2 = pd.DataFrame(7 @4 N# {1 f* d' f
d2,
% j3 c! ^3 O$ S* B; K6 T! t, q index=pd.MultiIndex.from_product(
/ F4 \" o8 E/ \* P s- o h [days, ['morning', 'afternoon']]% T! ~) U8 | J0 |+ d. p) X' ^
)
) k) ?2 m2 O* q' U- H)" p& A6 y; H5 t. A7 c# N. {
df24 @8 U% g" g" p
price volume
, T' Z; f: d7 L- E h+ Y; F2000-01-01 morning 10 500 T! f! b- B" J9 ?8 F* x
afternoon 11 60
8 e, Q) J5 y- X% e# f+ s/ ^# Z* N+ q2000-01-02 morning 9 40
P; A. b! c) p8 U# B: r! q6 _: N afternoon 13 1003 O. U' ], p( p# O$ `1 B
2000-01-03 morning 14 50# o7 P# ~ X6 x
afternoon 18 100. g* e( \, E7 C: H2 k. y
2000-01-04 morning 17 40
' W9 P% h ^2 u4 y- x afternoon 19 50
+ \( a. W% c/ w: H$ Y( edf2.resample('D', level=0).sum()
" @' }$ w. K- {$ T7 S A price volume5 D; s* V! z5 V7 Z: c. `
2000-01-01 21 110
% g& \4 [, b: S/ w2000-01-02 22 1405 N/ [7 F/ o$ O$ U( t
2000-01-03 32 150
w4 _# L4 V) y3 o: J; K2000-01-04 36 90# @( m) Y# z Z" q- L
& P+ J7 I* E( _0 ?6 N2 ~# a1
4 L2 ^! h5 }6 ~# b6 y# Y: F6 ~& ?2 M: z' S7 [0 `8 R5 h
31 z: L2 @5 x! P
4
: J: i% S* o5 q; V+ y- Z59 H8 N, a$ R9 Q$ [3 ` f7 _
63 o, ?$ x7 }7 n+ G: ^5 s
78 t. r( _5 `; M. p' c
89 U- J( @9 f& H5 Q1 B* K. I
9! y& g% ~( r2 ?! m
10* I( i9 _: S! X2 [: @
11/ g; S% d" Z# g# k+ q* C" t
12" c: W8 ]* A% Q! L7 y# Z
13
; Z) D1 C/ Q- {- h4 @ R( m! I147 }+ i4 I/ P' w8 ^7 H4 R
15
1 T- {) T3 x0 B) Q$ q7 A16$ U, ?* |- Q; V. o
17
6 t" X( h- C3 j0 m' X9 P18
% i$ l* z# G5 ?; v/ B6 o# A193 Z$ k) Z0 C9 K3 f3 Z
20
9 `/ m+ c/ o" E21
, E1 W1 w& R b; S227 c% G( f- P c* l: o: Y* J+ ~. g, J
23: ~& F# @+ L1 U3 C: U+ ^
24
. t; M6 |0 G$ ` @1 a9 H- B; O25
& E, W( V( b* [: d* \% b+ h根据固定时间戳调整 bin 的开始:
+ T0 h7 q8 l5 w! ^2 E' astart, end = '2000-10-01 23:30:00', '2000-10-02 00:30:00'
6 p$ d# U" j6 Irng = pd.date_range(start, end, freq='7min')0 N- N: l: }# w9 F* |
ts = pd.Series(np.arange(len(rng)) * 3, index=rng)
8 G; h, I; @% U: p/ T6 _: rts2 n. u5 B. z4 C6 V R7 b
2000-10-01 23:30:00 0
/ f: p) ?; m8 F- z/ I2000-10-01 23:37:00 35 X4 y' q2 {5 f* v2 @9 ~+ a
2000-10-01 23:44:00 60 f/ K" O: \+ V6 G; h! i
2000-10-01 23:51:00 94 m T6 A" {) U3 D
2000-10-01 23:58:00 12
& ]0 {, \5 f* U2000-10-02 00:05:00 15
V+ T8 C/ p! ?: R, d5 t; [, v2000-10-02 00:12:00 18* |8 ]* S( [5 E" t# e
2000-10-02 00:19:00 21
, d/ [. L: W) F6 \5 Q& u. N2000-10-02 00:26:00 24) B' y" P! ]( B* M( c: w
Freq: 7T, dtype: int64
/ l: Y' _6 f+ |, x# }5 `' E% S5 q9 H% H) o
ts.resample('17min').sum()
5 I# u( Y* s+ K% \8 ^ O8 `2000-10-01 23:14:00 02 G1 P3 T8 q4 J# r8 h
2000-10-01 23:31:00 9' b) a+ G: P/ L8 P
2000-10-01 23:48:00 21, x0 ~$ K: ]2 E9 B
2000-10-02 00:05:00 544 f' W* b; [' T3 T
2000-10-02 00:22:00 24% W, J4 }- ]* ~6 x" S5 L
Freq: 17T, dtype: int64
/ ~% h4 Y7 G. U7 ~" v; ~$ C3 [# B) T( M, j: ~$ D3 l& [' _
ts.resample('17min', origin='epoch').sum()
! d9 w' @% A& s. N( \6 v- r' a C y2000-10-01 23:18:00 04 Q' A' d$ @1 E+ g# @/ u; Y
2000-10-01 23:35:00 18
1 { W$ y0 ?0 w0 t8 g# H, j I- H6 q2000-10-01 23:52:00 27
+ S8 Z4 I a( J5 j0 W! p5 I2000-10-02 00:09:00 39
8 R9 [, _: |* N+ S1 e6 T2000-10-02 00:26:00 240 m5 [2 Y0 G' W* L: I( u
Freq: 17T, dtype: int64! T' ?" b' r [3 e0 _* Y. a: F
% B7 Y# ?+ Z* {8 n4 |$ E
ts.resample('17min', origin='2000-01-01').sum()
+ Q/ q, ^- H7 `3 {2000-10-01 23:24:00 3
* j$ E/ u ^% ^) w2 D/ z2000-10-01 23:41:00 15
+ h) d( f2 H: m( n2000-10-01 23:58:00 45
7 m* P& [& C8 a0 N b# q4 H1 M2000-10-02 00:15:00 450 h4 _& ^9 c( R; r) z
Freq: 17T, dtype: int64
! L2 F0 F: E: H, R5 n" g! N6 W( u v4 T/ }
1
, ^) e+ X& e! A' d' W2/ c7 D7 F' w, ~) P8 d* ?
3" ~1 z% g1 x" @, ^& d
4
3 |& I+ L; V2 ^ y% g+ D ?4 |59 g8 m0 r3 ]+ G& G* B
6
8 |! n) Z3 |& N( v7 [& t7, n$ q9 O- E/ I) \
8
" P7 C! X9 h. A6 E9
7 f( t7 _: i! b9 R2 F105 b; r \, i& j
114 O) J9 H7 ?) ]) ]3 u! i; q
12
& W$ b! W. ~8 p13! Q2 B$ t# F2 n3 y7 ~
14
7 ?0 @; n3 _/ j1 V15
% C. Y1 o: V% A7 H1 R16
5 _. j% ^* b! [9 l177 F" c% n' v; r1 E
18* o7 o4 R/ k2 J$ q
19
" P+ C: e7 p( e* F2 [20
& X6 O" ?% s! C3 K( o' X21, ]5 a8 z0 b/ B# t/ W' |6 s4 m
22
' @$ S! `% c! W6 r# O5 _23
, s( U; H9 A4 R$ G! ~24
2 t' w0 j. t# b& O* r25$ Q3 S6 |1 ^, ~" ~3 I7 n3 K
26
4 {; i, ]( \" ?, J; x" `# O0 k27
5 F, k* J+ o, i" B28
7 f1 B4 I3 e8 X- A7 q2 [29
# x- [ b9 y+ [6 v& x9 m30
& }1 e/ ^+ V( t31$ L+ e8 m+ S- s7 k9 x
32+ i4 j; m. ^8 m
33! k6 e* k4 i1 E" S* n, @
34
- h9 W+ P- I, ~35
9 ~ H) h. g9 J& j" Y& A36
7 M) \- x1 j9 I37, g0 R# X5 ?8 B; D ?# B
如果要使用偏移 Timedelta 调整 bin 的开始,则以下两行是等效的:8 @$ G' f2 l' R. h" H. ^, a/ k0 l
ts.resample('17min', origin='start').sum()
, J) V, t! N5 B' `1 Q- e. W5 Ots.resample('17min', offset='23h30min').sum()
" v6 U$ N) |3 M) f; l2000-10-01 23:30:00 9
0 u+ K# N+ r' b% c9 c1 ]2000-10-01 23:47:00 21
) h2 X' D( v( H8 w4 f2 U% S2000-10-02 00:04:00 54
- S7 p0 i7 `! v7 r" G. c5 ] z! I5 [2000-10-02 00:21:00 248 P3 _2 _) h0 l5 L! g- O
Freq: 17T, dtype: int64. i7 s; M9 }& v2 q! P& q8 Q# x
1
1 ], B0 r3 ~9 ?2
4 l |' j( {' S' t36 y1 v' x% w! H* u- h3 h2 Z
4. C0 [! U$ `* {+ k/ A
5
$ T0 P/ [! ~! B7 d ]$ L' k) P% x6
7 A( J: E8 u, f1 ]7/ w9 X0 x& W# j
10.6 练习9 T- { ?' a0 x- b: ?) w3 e
Ex1:太阳辐射数据集: k: T- A' U5 A1 K, | h. i# A
现有一份关于太阳辐射的数据集:
x) ?6 @* a; c3 o; Y! v/ S8 e- c
df = pd.read_csv('../data/solar.csv', usecols=['Data','Time','Radiation','Temperature'])- y7 F2 p) `2 L/ N% s; y
df.head(3)
6 S, J/ H2 j0 p) f* p& V" {- B% p( r- P# j' `4 r
Out[129]:
, f) X# w& p- D* R' _9 x" c Data Time Radiation Temperature! }" c0 I$ R: B7 _2 z+ ?
0 9/29/2016 12:00:00 AM 23:55:26 1.21 485 O; b& P9 a5 `/ a4 B1 D& Y
1 9/29/2016 12:00:00 AM 23:50:23 1.21 48
5 E; q+ _6 J2 ~+ J6 ?2 9/29/2016 12:00:00 AM 23:45:26 1.23 48
- r3 x, f) R; l4 y! C16 e& k/ i' \# f+ Z
27 P3 L9 v, |: v) }7 P- ?# t- G2 E
3
) W$ x8 T- [, A* A8 Y4
4 A% ^# p( P! z5 I7 y( D, ]5( O* m! a( y7 u3 c. F7 P) J
6+ v9 ~7 x. u& G' I/ w+ L, q) f
7
# I( T* p' D3 R( Y8
3 r! J$ [ R9 b2 u2 a* B. `/ f将Datetime, Time合并为一个时间列Datetime,同时把它作为索引后排序。
% k5 U$ l; U$ V. k8 v每条记录时间的间隔显然并不一致,请解决如下问题:
) W! g" c, H9 t$ |9 S9 w0 g找出间隔时间的前三个最大值所对应的三组时间戳。6 v+ B* o* T0 Q X X
是否存在一个大致的范围,使得绝大多数的间隔时间都落在这个区间中?如果存在,请对此范围内的样本间隔秒数画出柱状图,设置bins=50。
( J( E! r4 ^/ u. j求如下指标对应的Series:( l% d/ o7 M" a P# L4 J
温度与辐射量的6小时滑动相关系数8 q& {% [5 E" w" n g& D
以三点、九点、十五点、二十一点为分割,该观测所在时间区间的温度均值序列% O* t y" ?, v: S( z# h
每个观测6小时前的辐射量(一般而言不会恰好取到,此时取最近时间戳对应的辐射量). F, s( B! X m! K. S P
import numpy as np7 [- l( D' ]$ v2 D
import pandas as pd+ u% E- ^1 x6 S( D/ O7 e
15 V/ o% S8 x9 d& x
29 w* ~+ e& [4 y$ w
将Datetime, Time合并为一个时间列Datetime,同时把它作为索引后排序。, k# G1 k6 O4 W
data=pd.to_datetime(df.Data) # 本身是object对象,要先转为时间序列
1 r8 p# }' T% l% v5 Gtimes=pd.to_timedelta(df.Time)
) `- e6 E6 l: d$ Y* ?% S+ `df.Data=data+times0 t: U1 e- e8 n2 p
del df['Time']
3 I+ n( @0 o/ ]1 Q# B' bdf=df.set_index('Data').sort_index() # 如果写的是set_index(df.Data),那么Data作为索引之外,这个列还另外保留
; R% h2 L0 p) S. M# t% U$ \) idf
7 i+ M5 A2 ]! Z% e/ R$ i Radiation Temperature
% E( F6 [, x5 aData
4 l) {' Y/ o0 j6 k- w: o# W2016-09-01 00:00:08 2.58 51
2 j) X/ T$ s7 B9 R8 o' r2016-09-01 00:05:10 2.83 514 b: x$ t+ U! E2 v6 ^5 e
2016-09-01 00:20:06 2.16 51
( c9 U* I2 k+ r9 H& \: s2016-09-01 00:25:05 2.21 51
9 B; H( c; i( `* u- |1 l0 `2016-09-01 00:30:09 2.25 51
0 H/ y; c) l% A6 ^0 b" p5 L... ... ...
, y l8 P: d q i% U2016-12-31 23:35:02 1.22 41. a& g% i! R" G+ B
2016-12-31 23:40:01 1.21 41
7 J8 _" Y: X: b& X2016-12-31 23:45:04 1.21 427 V% o: n; D: }! a6 R2 x) C& G; @3 J
2016-12-31 23:50:03 1.19 41% B, @8 `0 s# J n v
2016-12-31 23:55:01 1.21 415 X( c! L" `9 y# Y
3 h! ^1 ~' i; g& I- @* s$ m
19 g% c% @2 u3 O4 D9 s: t7 N5 ~
2
u" t0 N9 | g) [6 @% }. |3; C7 C4 U. I; l+ r# }2 n# n
4
1 |7 O+ f, s) `0 v) R; i5
$ w; `7 |$ h) X) `7 s3 [$ w6
* o7 b# `* g+ x8 [/ A7
. t9 j* r% L( S2 R8 a% {8! i5 X1 }8 [6 T$ Y% H, V
9
/ x1 w; |. ]' @ _9 ]10
: C- |( P2 ] i/ ?4 s) q" T( g* T11+ e) }9 {& b( r! h; T+ y3 M
12
8 f. ~4 {5 h* B5 N1 a. p% R13& B0 k3 o6 p: i( q& ~- F) x, G0 [
14
( _& Y/ J# b, K, Z' Y15
* E" q. f* L5 D& t$ a; O160 o) G+ a2 k9 y0 C; u8 @
17
1 e' v7 S: w9 Q3 J h18, c0 G6 c( E# w m+ ^& U P' I( u
19! M" J2 v( A) H; O1 ? b
每条记录时间的间隔显然并不一致,请解决如下问题:4 I1 A3 b4 W0 b
找出间隔时间的前三个最大值所对应的三组时间戳。# R6 z, P# L* \' a. _9 ]
# 第一次做错了,不是找三组时间戳
) W- B# J, j/ l! ]idxmax3=pd.Series(df.index).diff(1).sort_values(ascending=False).index[:3]# }0 l# P6 a) @# S# i, v9 C
df.reset_index().Data[idxmax3,idxmax3-1]
( b! I# m1 C0 P. A- N z
2 ]- B& {# @0 W& n9 ^8 L25923 2016-12-08 11:10:42
+ K' U1 g1 D, \+ b8 f) M/ Q4 I( W24522 2016-12-01 00:00:02
$ {$ h. Q9 V+ x( U7417 2016-10-01 00:00:19
' @6 F v! h$ y! sName: Data, dtype: datetime64[ns]! q* C$ k, o" F0 C
1
I( O) J3 Y! k0 i20 h8 w" L& d) y# q/ C
3
5 h3 K9 n: f) X' s( I: l4( O% z' `) P( B9 o* [2 l0 [
5
, | m) G) }6 C$ A. a6
6 S5 d% r" G! H o7
# |; n9 q& f8 l: Y# o8
6 D! C, T% h# [1 p3 \+ J& A4 h: {) ^idxmax3=pd.Series(df.index).diff(1).sort_values(ascending=False).index[:3]
$ L' j" g8 s, w \' hlist(zip(df.reset_index().Data[idxmax3],df.reset_index().Data[idxmax3-1]))5 f+ e3 _" L+ C, W+ o$ G9 @4 l, r
7 F3 E' v' V9 x7 f
[(Timestamp('2016-12-08 11:10:42'), Timestamp('2016-12-05 20:45:53')),! P7 W) g* t$ [9 L" k
(Timestamp('2016-12-01 00:00:02'), Timestamp('2016-11-29 19:05:02')),2 z) H* v( a" G: U. \4 f
(Timestamp('2016-10-01 00:00:19'), Timestamp('2016-09-29 23:55:26'))]0 |6 Y! w7 F9 P
1
) k! ]# }; p" g5 z2- D0 [, K2 L" b, U; m
3
# X; Q X- [% p& f3 ]" ]4! q" R3 G# d! J
5: x7 f6 _+ t) K& s" q7 x
6
& `! g7 d9 P7 u, o6 f参考答案:
; D2 ~) T0 o7 m1 \2 H! |6 F
. M' N% q% W4 _) Ys = df.index.to_series().reset_index(drop=True).diff().dt.total_seconds()
! Y1 q" \( P. ~max_3 = s.nlargest(3).index/ U, U- V; A% r5 s( Y
df.index[max_3.union(max_3-1)]
3 M7 V1 t$ v9 ]5 c- M. g' p2 M$ z- U& O1 D; a
Out[215]:
- P# z2 q1 a" XDatetimeIndex(['2016-09-29 23:55:26', '2016-10-01 00:00:19',
/ A X( X5 G8 O! l '2016-11-29 19:05:02', '2016-12-01 00:00:02',
/ Z; n( w6 ` p' n '2016-12-05 20:45:53', '2016-12-08 11:10:42'],/ ^& s% ^8 J+ I$ m
dtype='datetime64[ns]', name='Datetime', freq=None)
# \8 ?$ E4 J. L0 q1
4 y' i: N' K& M8 Z/ \3 S4 {2
, e( W$ j2 a& n* Q3+ f. ~+ A, {; i# O3 ^$ k3 G
4
+ U8 Y" E8 N4 L" a* V u: d4 ?4 \5
' W0 h. ~8 {9 E* S: o4 e, g62 U4 G g- V3 h- J% {3 a% `8 P
7* M1 w+ p" K2 t/ q) g. w5 E9 W r
8+ h; R1 H& ]9 {& S( A0 o
9
; e2 t" B: |1 h/ f2 N; B& k$ o是否存在一个大致的范围,使得绝大多数的间隔时间都落在这个区间中?如果存在,请对此范围内的样本间隔秒数画出柱状图,设置bins=50。$ \ _0 ]1 ^6 {3 r; p
# 将df的indexydiff做差,转为秒数后排序。再求几个分位数确定取值区间' C/ p' l0 j: K1 w( L) w9 x1 f
s=pd.Series(df.index).diff(1).dt.total_seconds().sort_values(ascending=False)
9 h7 w. r8 G. L% vs.quantile(0.9),s.quantile(0.95),s.quantile(0.99),s.quantile(0.01),s.quantile(0.03),s.quantile(0.05)" T" m- a, m9 h. j( w; W# T
/ j7 U# x2 u, n9 U" Y7 W(304.0, 309.0, 337.15999999999985, 285.0, 290.0, 292.0)0 b B' r# o9 O4 w; Z4 T# X
1
- |# [: P6 O. q- Z1 ~2/ o+ Z! \. j4 F( W4 v+ H. I
3
* o: W3 E- I/ v# k6 O6 L40 \3 w% T1 ^3 |, V- C8 U+ G3 v
5
1 K) [1 ?& L( ?8 {, u7 X+ @%pylab inline3 p1 B9 O; x9 E3 x
_ = plt.hist(ss[(s.values<337)&(s.values>285)],bins=50)
- U' f9 ?2 z) q& r J Q: lplt.xlabel(' Timedelta')3 w$ ~( i1 e% k. O2 o
plt.title(" Timedelta of solar")
% d1 N" h1 P8 F7 G- B1& T w/ f/ h5 D1 w {2 B
26 M( W- K% I$ a5 N& T
3
. r& A$ S( P% h1 b: N! U42 d, q) u {5 q6 x5 [/ E
) _6 S" X, H" H7 B$ |. R! L
. C( ?. F5 s3 f, g: Y* h- ^0 T求如下指标对应的Series:4 K5 z! k. p( r8 h
温度与辐射量的6小时滑动相关系数
% W- i! Q3 T5 [3 `; D. {& e6 t以三点、九点、十五点、二十一点为分割,该观测所在时间区间的温度均值序列
% u- |' E' Y5 a6 X5 H9 F" t& Y* I每个观测6小时前的辐射量(一般而言不会恰好取到,此时取最近时间戳对应的辐射量)
* I& O5 A& n# G& G; I) C5 Fdf.Radiation.rolling('6H').corr(df.Temperature).tail()2 T4 V1 X! E# \. m2 E" _
- V) w8 K1 v& `) M) R, W
Data
/ Q: e. s. f O2016-12-31 23:35:02 0.416187; v0 d R/ I# j/ Y. @; b! U) L" R
2016-12-31 23:40:01 0.416565* J4 O/ U) v7 T& O4 C3 D# t
2016-12-31 23:45:04 0.328574
: ?7 r, r3 z3 m( L1 e2016-12-31 23:50:03 0.261883
$ O# m) V# t2 t0 r2 { o2016-12-31 23:55:01 0.2624066 C" |1 j9 I8 a
dtype: float64
9 I/ Z% o2 }1 m: K8 j+ z9 j1: ?; n4 i2 ?3 G3 k' f# E$ Y9 W
2; c* x+ N% W5 W
3
/ f( B" r" k; V9 I1 s40 Z# \! e! p0 x8 w) ]/ T" D+ j
52 C7 u4 s- C/ ?4 y. o( X
6 P. }) Z* x# m) U2 @
7 f7 B6 n# H: l) y9 G' I9 j) z
8" N( U) f3 f2 D& N8 Q/ c
9
! M6 U7 \7 l% O" y$ G# gdf['Temperature'].resample('6H',offset='3H').mean().head()
* B/ W' S, Q2 n" d2 k4 g8 Q" g- g8 f9 E7 U. \5 R7 @" z
Data1 {. X; _ g5 @: Q/ G, P
2016-08-31 21:00:00 51.2187505 B& d. s r6 [5 w
2016-09-01 03:00:00 50.033333' `8 z) e2 \, D
2016-09-01 09:00:00 59.379310
9 d- s, t0 \3 A. _7 E2016-09-01 15:00:00 57.9843752 d4 Y4 B& [2 c, C/ s4 K3 L
2016-09-01 21:00:00 51.393939
1 i- \2 q, \1 f5 o% }Freq: 6H, Name: Temperature, dtype: float64
, c p# V. w S# _9 _1
: v; P4 Q7 G6 a: p4 I0 \* j2: U" {& N$ ?; {
3
$ K6 x+ Y& p3 t1 B/ B. t: e4+ N, j- M/ y- N* v
58 U" u& \" v* y3 k2 b3 x
6% g! L3 z4 Q6 }# ]4 ~
7
6 G. C2 x& L" v8
% k7 ]& J O9 T' M( n, J98 o9 H) C1 P" T( a( M, c
最后一题参考答案:" P: M, | I) i) S- m
! B" \+ ^& ^' J9 a! d% @6 b X
# 非常慢, |) x2 ~5 L+ ~ W, B E }
my_dt = df.index.shift(freq='-6H')7 J; ^; H1 I) U
int_loc = [df.index.get_indexer([i], method='nearest') for i in my_dt]: O! |/ m; c) m+ R* L
int_loc = np.array(int_loc).reshape(-1)
( s6 k2 A4 l, w6 y4 ]& x xres = df.Radiation.iloc[int_loc]
. a, o% u0 [% G _* p* w1 M# Q9 s) Sres.index = df.index
' R, f5 p) ^; F3 t5 @- Z6 K9 P7 Wres.tail(3)
8 S$ }& N7 U& M) N' i6 \1
% A! s& _" V! }* m9 Z2& O+ v8 k; \2 }
3
7 \9 ^# | ^; [4 a6 K; m4
2 p' l/ |3 n+ u; ?, [5
, ~1 ^7 } ^- I- k* }6
: g2 E' v; g: r8 E7 j, ]- l5 x1 L7
* L1 W! r' `0 I9 J# 纸质版上介绍了merge_asof,性能差距可以达到3-4个数量级
8 Z" v6 K' R' `target = pd.DataFrame(. b) w+ Y% G' J# U+ y9 v" h$ J
{7 x c h6 z5 W; _
"Time": df.index.shift(freq='-6H'),
4 C/ M. _" A+ ?/ e "Datetime": df.index,! a0 L" D! H: b) b" I( ?9 |
}: ]' p7 i6 K/ X* J! P0 \) w1 K
)
6 V$ K: o- G6 {0 d: @5 T( E6 e& q* u
res = pd.merge_asof(" H. |, N& D, U$ N/ [
target,/ [2 K |( g9 ]5 [
df.reset_index().rename(columns={"Datetime": "Time"}),
3 x* {" X4 \! U; P% x; K left_on="Time",
$ |) X$ f: Z0 d) _) Q% p right_on="Time",0 I; e J/ H( A+ F% b3 ?$ o
direction="nearest"& ^# X7 v9 x7 y7 G
).set_index("Datetime").Radiation. n) d$ g+ B$ m! i T& j
+ c- r5 H! ^4 V' _ s/ ?res.tail(3)
; j' f5 [# x) J+ t# N& ]2 [% n( pOut[224]:
# o# T2 L3 h, I& o3 \8 }Datetime5 h X- k+ a g/ X: \& M
2016-12-31 23:45:04 9.331 f" H( y& G: z6 @+ o
2016-12-31 23:50:03 8.49
. D* A( ^ ?2 z6 M2016-12-31 23:55:01 5.84
; C% ^& c8 i7 {# OName: Radiation, dtype: float64; R( r0 w/ u1 t3 b
, g" y) V* P* ?8 ^
1- o. {. @1 T7 L+ i8 C, l6 o- p6 }
2
4 }, d9 x- e9 A3 g" b3# V$ j' m- o6 }$ ^9 R2 g
4
3 {0 z* x8 |7 a% O$ k0 W9 j5
% k0 j+ T$ b! o! W6# f* A# o4 R& b( I0 v
74 y0 p( Q% G5 O. L- T, K$ E
8
A# E5 |" M- e; h/ L2 U9- Z( q5 E+ P7 k; U# X
10
1 e5 _6 Q) B: [. y# y11
" r+ J9 c3 N$ T6 \3 v6 ?12
2 `9 ~8 l/ K4 ?, i13* K0 |& D, T: R; X) `8 I
14# U6 N) v% G. j1 @' V; g
15" D( o7 C% v. t4 E. O2 Q
169 J1 N3 _7 D4 c4 [6 p2 `
17
2 E9 B `3 \& j5 U5 d( p# J: d* S189 d G0 m' l# S
19* I9 m% q! R! U- T3 x" R; h1 O/ t/ T2 z3 M
20( E; k1 e9 `1 i3 [9 D, x
21+ v9 d4 e5 j: W
22
! ]* f* e5 Q' \* E6 y/ b23
$ m A% i# c3 ?9 QEx2:水果销量数据集1 \0 |4 u# _3 l# F; h) L. r
现有一份2019年每日水果销量记录表:: K9 R5 J; N9 L3 ^' b" o- X! N
m7 O0 J. A& I- U- Y' O% }
df = pd.read_csv('../data/fruit.csv') {, Q* V$ y6 e' _2 f/ S7 E8 M! z
df.head(3)
) j5 w# Y4 N9 v8 \+ M8 }$ d* i3 l F5 W
Out[131]: & @7 b0 T& A+ X7 s" {
Date Fruit Sale
2 \8 A4 Z3 G" z3 i0 2019-04-18 Peach 15
7 r! r3 Y* C" M* R) J5 d; z1 2019-12-29 Peach 15
: p3 n, R, |3 |: M2 2019-06-05 Peach 19
/ G/ V( ]% b9 g+ O# Q16 j) N; l5 s$ s
2
5 @- \1 i# i3 F' A& r) l0 o) |/ N3; D. a1 V- C# U/ }7 @
4
5 G' X9 |" n8 P1 Q56 @$ ^" S8 F& m) {% _
6
2 Y: T7 d3 {2 ]# x7$ v& h9 R' h" p
8
6 ?$ }+ \! `8 b3 T+ t! j; w统计如下指标:
/ Z; B! Z. ]2 a9 A2 G每月上半月(15号及之前)与下半月葡萄销量的比值* r+ b7 a f9 d) P8 v
每月最后一天的生梨销量总和
' X# l) f: x# a- L6 r2 x# |每月最后一天工作日的生梨销量总和, I/ ?" Y% @+ q$ D) Y3 ?
每月最后五天的苹果销量均值' _7 y* o) N/ W2 P# ]( T8 O
按月计算周一至周日各品种水果的平均记录条数,行索引外层为水果名称,内层为月份,列索引为星期。
5 N4 d# z( X+ _0 x5 M. u9 M# L按天计算向前10个工作日窗口的苹果销量均值序列,非工作日的值用上一个工作日的结果填充。
1 Z+ T/ Y! S6 x; W8 _import numpy as np
5 f0 `, F7 t# }import pandas as pd
4 P5 ~+ ]8 g! S" k1
4 _: [( v: k( S2
8 ~: L5 x! l4 B! ]3 z2 E统计如下指标:' e* ^# d2 ? R! K+ ]5 {! R) O
每月上半月(15号及之前)与下半月葡萄销量的比值, \3 \& ?. }% i# Z) S0 W$ a1 ]& d
每月最后一天的生梨销量总和
2 y0 }, r8 z0 g' m每月最后一天工作日的生梨销量总和
9 l& V1 B8 @# P! }3 j每月最后五天的苹果销量均值3 ]( A3 k* b% P
# 每月上半月(15号及之前)与下半月葡萄销量的比值9 s. T7 u/ Z' h+ Q- v0 f
df.Date=pd.to_datetime(df.Date)
4 ^" q0 O& `/ b, i9 |9 bsale=df.query('Fruit == "Grape"').groupby([df.Date.dt.month,df.Date.dt.day<=15])['Sale'].sum()
' T4 I& `6 L+ y6 msale.columns=['Month','15Dayes','Sale'] # 为啥这么改没用啊
; a( d/ ~* V6 nsale=pd.DataFrame(sale)
. b3 e, U" i) ksale=sale.unstack(1).rename_axis(index={'Date':'Month'},
7 O/ |' ?) M8 C0 r: p columns={'Date':'15Days'}).stack(1).reset_index() # unstack主要是两个索引都是Date无法直接重命名) a2 A, w: h4 I' }
sale.head() # 每个月上下半月的销量
+ U# C, U; p( e/ F! a4 |
( a" d* j% l* \ [" b* A( g. e4 M Month 15Days Sale/ n& h2 H m, p
0 1 False 105039 `) r1 c( |: L
1 1 True 123411 S8 o7 s# R7 _* g6 _
2 2 False 10001" |: u0 r/ p# a* w' H
3 2 True 10106
" \# r$ x: w4 Y* Z8 M2 ~4 3 False 12814
}% _: c3 |( @% y- ]* q
1 N) y8 p# g5 s+ l( ~) s# 使用自定义聚合函数,分组后每组就上半月和下半月两个值,根据索引位置判断求比值时的分子分母顺序
2 ?5 `: l5 H3 z- s% ]7 _8 [" k- G) G" jsale.groupby(sale['Month'])['Sale'].agg(
' j& C, @( i# g s lambda x: x.max()/x.min() if x.idxmax()>x.idxmin() else x.min()/x.max())
& S! D( ^9 v; X- t8 \! r7 o
! Y; n5 S+ E4 E- hMonth6 V4 J# X% x: Y' g) K' v3 G* b
1 1.174998" W, u$ l" q- ^8 ~& l ^3 L+ c
2 1.0104997 s, a# y1 c. O; ]7 A
3 0.7763383 V6 D# O Y8 c& v
4 1.026345
/ _; P) X% E6 `) [5 0.900534# }" }$ S% I& {' o% O6 @1 `
6 0.9801368 \% ?# ~) C5 ]" ^( v
7 1.3509609 v6 X# R1 A& r
8 1.091584# h6 h+ L# f6 I2 {$ Y
9 1.116508 d) Y. r f( o6 l0 l/ `# |
10 1.020784
) d4 ^* m( k+ l% c) R' ?9 k11 1.2759119 K4 v8 G* i2 _* ~- p
12 0.989662
; |8 u9 W2 g, P* U! L* s2 n$ Y% f; mName: Sale, dtype: float640 a1 U3 m0 G) [6 H) y, F
. O1 q; }$ v4 M3 a, s1
9 t, _8 {* M H$ W9 _4 m# J- V9 i+ C2+ c# n/ C {! H4 S2 O
3
A. X: @$ p! v1 |; P4
, _1 H; c# O1 Z$ R5" R- g9 }& B+ z. B
6
! L+ o- _/ x+ B7
2 Y( ~0 [. h8 X, O& V83 @4 n" \1 N0 A. g
9
. R0 I, ]( S- I6 a10
9 c9 {$ \# v* j* q" `% y11
% c! I, _, K4 i" f12
7 a1 {* q6 y v% q/ F; J9 Z1 H13
: F2 J& J0 S" G8 u4 }: Z8 h14
+ a" Y6 z, p$ S1 e& p$ p W15
& I) ^: B( m5 I7 E16
" H7 s; b) ~8 R* S* y6 V; _9 t+ g! _17
6 q* ], T: P r18& T4 l3 q* e1 w1 o+ R
19( x1 }# ^$ Q- @ r6 s1 p4 k
20' c- s/ |4 c: |$ H, ]! _ t# ^
21- V) X6 a3 t, h' ^
22( X6 X: j. c! ?
23
8 P5 V& a# ^! v9 n: Q% o5 [. g& [24
7 W! q# R" k r/ B6 w2 P5 |, r254 q9 H' H% q" c, m7 v- I3 U
26) S) c8 C$ s0 K; C! y% ]
27. q% n/ C$ C' ~5 M6 J& _0 H4 B
28
3 G, f8 b7 K9 K29
0 Q3 _# G; n3 ] B$ O, V; |/ x3 D30
- g) K7 r! } w$ t) l316 {( ^$ f$ y! T2 g
32& a+ s3 r8 I" Q
336 x) a, v9 A" L' z4 i* }
34
" m* C* E+ D& n* ^# v1 K6 k# Z# 每月最后一天的生梨销量总和
( k# V$ Z0 ?1 K" qdf[df.Date.dt.is_month_end].loc[df.Fruit=='Pear'].groupby('Date')['Sale'].sum(). i. l$ D4 W& J" Y% d) l
, n2 e; y7 g# s6 g$ u. d/ y# n1 b* IDate
/ P' D m) T$ {7 u& g) f2019-01-31 847 W" H$ q# N+ P: t
2019-02-28 774
0 y8 u2 q$ h _. z3 {( M; |; d; H2019-03-31 761
# ]2 V) x8 ~; N7 b+ @4 ^. f2019-04-30 6485 ]8 o( p; s% d9 j/ g0 L
2019-05-31 616
0 g$ c3 D' j. W/ H u" Y' c- F1& G) Y- @: k9 S5 Z
2
; ]) p$ l+ W! O! t7 f3
; f, R+ E8 o. W9 f4
3 E; ^- ^ ~2 G5
) h; w# s- m/ x. ~" Y# `; U: \% a6
7 K+ R2 w3 A7 Z( W- o$ a% k7; O5 v! L8 H) L* h4 {
8/ O2 h+ r& z6 [
9
9 y3 @; ?: |4 o; ^8 ]# 每月最后一天工作日的生梨销量总和
* c' O( k& ~9 v3 U Yls=df.Date+pd.offsets.BMonthEnd()" b$ H ~ ?8 W; B7 } r1 ?$ U+ Y2 u
my_filter=pd.to_datetime(ls.unique())
4 ?/ U4 D& `+ _& Z6 Ndf[df.Date.isin(my_filter)].loc[df.Fruit=='Pear'].groupby('Date')['Sale'].sum()
6 {, @0 p( h% B% U$ u
" |% ^# G+ W. lDate
6 ^$ x( o" ?% ?5 F2019-01-31 847
3 `) o4 m% M0 B" @2019-02-28 774
8 q% R/ A$ [# N7 J1 P" [# Z2019-03-29 510# @- b# n. F7 g: n1 a
2019-04-30 648! Y/ Z: r% X/ x* p- O3 W. G
2019-05-31 616
" z' f5 ]4 c; |1
3 E, y' B2 ^- @7 M. V2$ @. D5 n0 [; E% F- H1 P9 y
3
5 |8 o1 r# ]# W7 R; \4& T0 d; Y* G$ J+ D
5/ B9 c, u% n9 ]0 M
6
/ ^$ _) N" F# n3 c8 Q7
4 v U% N# j( r0 K. {+ y8 F8
( ~: e/ x/ A3 I9
8 e8 i* `# K* A! n3 G: o10; y% }: ], d1 I$ p) W
11
: F8 p9 Z+ b/ N7 g9 s9 u$ h# 每月最后五天的苹果销量均值
+ Y4 c* i/ j$ u, rstart, end = '2019-01-01', '2019-12-31' |4 G% F% q5 g' f) h8 p8 q
end = pd.date_range(start, end, freq='M')
# o; w$ J2 m `& K6 ~* vend=end.repeat(5) # 每月最后一天的日期列表,重复5次方便做差) x! T1 V, k+ i) x2 c6 I
]7 I8 z+ k; ~
td= pd.Series(pd.timedelta_range(start='0 days', periods=5),)
7 N# Z% H4 N" w. d" rtd=pd.concat([td]*12) # 日期偏置,最后一天减去0-4天6 c6 h p, W" I& N$ X; w
end5=(end-td).reset_index(drop=True) # 每个月最后5天的列表
/ \& r( i M' x( n' T" u6 Q, J
8 L$ y8 M ?2 F3 F ^" w0 Q6 tapple5=df[df.Date.isin(end5)].query("Fruit == 'Apple'") # 每月最后五天苹果销量
( T0 z% A- m$ M- i: ?apple5.groupby(apple5.Date.dt.month)['Sale'].mean().head()2 m' K1 S( w( a, x! d1 h
k7 U" E# }" p8 i3 T4 D
Date
A. F0 C5 K, T3 M5 A7 @4 M1 65.313725
2 w o0 ]0 b6 L2 54.061538
, W/ p% r L$ y! t/ K3 59.3255810 \. G3 }7 W4 b Y1 C# Q
4 65.795455% b' V9 J- X5 M" S$ U; Q( M
5 57.465116/ x& I/ k: l+ t0 m% e
+ j( k/ P4 R2 b- r6 I9 z1# Y6 a7 }6 [) c
2
6 Z. y& }3 f: ?6 ?+ O% j3
( D# z) k0 Q0 u, x- Y9 W" D4
; L u1 a( k8 u8 {" |" Z4 m5 v9 C, ?/ S2 N) K
68 F$ `: J: k% I5 S5 _0 I; j" U
7
0 t, Y l* D- }6 x) V4 p80 k4 j7 D) ^3 P2 K# H" v0 b) I
9' r& F8 V- l! Q3 X7 b5 p, f
10( n0 F8 y! S+ r' ~
11' E& l+ k( ~* k0 q; ? ?
12
! W2 L: X7 M* V, i" X135 J* @. ^; O$ _% l0 c
14
4 m8 Y7 B# d% O/ l/ `15
, e$ [# g) B, O% U16" L; n3 F# r$ S9 r8 m" J
17$ O- p+ g% F- @' W. g& o" _$ L# e# Z' @! g
18
" a9 t' H% _! x% _4 ^# 参考答案:/ l; n1 K; U \* ]
target_dt = df.drop_duplicates().groupby(df.Date.drop_duplicates(# R& `* H( @, F7 h; r+ w8 B
).dt.month)['Date'].nlargest(5).reset_index(drop=True)
3 k ?! H7 i% a A7 l ~3 q3 r# _2 D$ r& Q7 z7 Q8 J! W
res = df.set_index('Date').loc[target_dt].reset_index(! E6 D+ B3 F' b* u' Y* q
).query("Fruit == 'Apple'")2 O/ P! ^ s. g/ X' _( _
+ W& j+ p# ]- h7 m& C% \+ Rres = res.groupby(res.Date.dt.month)['Sale'].mean(
9 O! ]% R0 z0 \7 y ).rename_axis('Month') d( c3 U" k* M! m
0 {) y1 ^, k s* h3 W: [5 M
+ U9 _+ O8 b( d- l# Z9 kres.head()
+ G4 f5 B9 x1 Y" g% ^Out[236]:
' z* {, n, T7 l3 w6 ^% ^. p; j- |7 ~Month
0 ^! o" B. A$ g1 n' F1 65.313725
9 [: r m& U; X1 U6 ]2 54.061538
# Z$ E! f. C- o6 t* [3 59.325581/ d4 s$ n8 L+ f$ I4 X1 N3 w2 `/ t( t
4 65.795455
. [, ]+ ]. D9 d5 57.465116
- `7 S2 X8 y* a! z0 C- Z% S" ZName: Sale, dtype: float64- H9 i5 m) y; v+ S4 z
- p. Q/ L& ~/ O/ ]3 t7 {1* {- r, `- ~% J% G. L- L0 R
2
4 Y6 |4 i' A( p& a; }3- |" U, U" c6 E, F
4
5 Z# ~5 H1 H+ k6 s50 k8 z4 d' R( G; r
6; k9 B8 C% @* e& z$ P/ T
7
/ U7 q! D1 x; F! Y- u8
* s4 I. y. }( a6 a: C# }9 V' ^; Q4 j- ~ P2 M& o
10& u/ B( i$ i9 a& D0 w. @$ a
11
5 E/ b$ F6 H& M123 g6 t! k# Q1 B5 z
13
" u& o+ x6 |( y7 W14" x4 P/ }! \& ]% j9 s
15
1 l( G7 ?, l( ~' F. W/ n16% \ d6 r0 Z( ?, u! ]6 L6 g8 Q7 z
17; C" K9 C% ]( U, i' g7 B
18% q7 k$ k/ |) K/ V
19
( ^' U( M1 q7 M y: e2 P# @20
# s. {' `. z0 K7 s! r! {按月计算周一至周日各品种水果的平均记录条数,行索引外层为水果名称,内层为月份,列索引为星期。
- O# H/ z( X! U8 Dresult=pd.DataFrame(df.groupby([df.Date.dt.month,df.Date./ O/ D/ I9 w: k
dt.dayofweek,df.Fruit])['Sale'].count()) # 分组统计 , o5 N! J! `7 F* n" n
$ p) E1 j& _4 J" e% h1 o! ^
result=result.unstack(1).rename_axis(index={'Date':'Month'},
6 q- ^) [4 p; o8 z' ?8 G. h1 u columns={'Date':'Week'}) # 两个index名字都是Date,只能转一个到列,分开来改名字.
% X1 D3 }' ]' S' eresult=result.swaplevel(0,1,axis=0).droplevel(0,axis=1)" f# I/ Y) F6 b! L$ A& e7 D9 ] E
result.head() # 索引名有空再改吧
9 {1 R8 W. }" X" s& I3 Q6 D: Y, r* }. S# A$ i/ {1 T# J
Week 0 1 2 3 4 5 6! H6 g+ `: K. R: U0 R, _
Fruit Month
* N3 ?5 i2 Q7 F& n5 uApple 1 46 50 50 45 32 42 23
. m G- h6 @0 u( {9 M! t/ FBanana 1 27 29 24 42 36 24 35
8 H6 e8 a- M& V' _Grape 1 42 75 53 63 36 57 46: ^7 \% `+ o8 J) L8 l. O
Peach 1 67 78 73 88 59 49 723 `7 I* I% y, N
Pear 1 39 69 51 54 48 36 40
\+ V" @+ S3 j1 r3 s( j5 i) ?18 {9 N( k: N8 y+ ^ I8 M! s
2) j/ {2 j# v+ O4 [
3& F* M8 p* ~) }. F; _4 ]- C
4
! @3 t3 U* v+ D5" K Z' I! L0 t1 n! o P* T1 j
63 q8 l* X" i+ r1 b
7
: |" F3 B0 j2 M0 q0 }0 p86 }. o2 j: k* \: B
9
. C7 }* D! ?4 j107 B' y7 X; A$ ?9 q
11
8 N& r/ q5 s2 n* @12& v7 N/ b, j; b" E3 N- F
13; v; {* {! p! X" b* {3 _* |
14
; g: ~- @3 L! E& g: ^5 J$ I158 {1 m! q1 P5 {8 a8 @2 a# _0 Z
按天计算向前10个工作日窗口的苹果销量均值序列,非工作日的值用上一个工作日的结果填充。 n- v" t; b8 _) t+ f$ g" R: q
# 工作日苹果销量按日期排序
M: I1 V1 k% x# x/ a. {select_bday=df[~df.Date.dt.dayofweek.isin([5,6])].query('Fruit=="Apple"').set_index('Date').sort_index()
% F1 L0 X! p; \select_bday=select_bday.groupby(select_bday.index)['Sale'].sum() # 每天的销量汇总
# A# O& `; G6 C3 @select_bday.head()
* S2 D& P6 u5 C3 {) G! h, Z$ E5 ^( M' J2 W( O7 F
Date0 a0 K& h/ v& a6 K7 X! N5 `1 [
2019-01-01 189
' x% {7 P* v- K( [: j: `; b2019-01-02 482
' Z! l! N/ y) {2 X$ I: ^% F2019-01-03 890: i. r, }7 g. D: M8 M% M
2019-01-04 5508 \ R% R' g: @! a
2019-01-07 494+ y1 c$ R8 o3 {! \, b l# g
a5 N' F# h% g+ v9 r# 此时已经是工作日,正常滑窗。结果重设索引,对周末进行向后填充。( z' [7 d/ j* h4 }9 x, E, T& c4 C, b F
select_bday.rolling('10D').mean().reindex(df.Date.unique()).sort_index().ffill().head()
. [4 l. ~3 M! C: \0 j3 b: d2 O" z& b- s# l. }3 ]
Date
- m3 p. x0 ^) d$ |1 X' f2019-01-01 189.000000- b) h7 y! G8 }3 T" V
2019-01-02 335.500000
6 M& W" p, W; |; C+ m0 E2019-01-03 520.333333) n4 R R" B' O& e5 ^# G
2019-01-04 527.750000* J. m% g2 x) ?9 h
2019-01-05 527.750000
5 R8 _1 @, G( a9 d8 J7 O4 x9 W4 E3 N2 ~! F! L$ [6 i6 @' g/ o( x
————————————————
/ G- u2 i0 a1 I5 @7 b, Q9 g版权声明:本文为CSDN博主「神洛华」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。1 }9 u) H2 p1 S* F
原文链接:https://blog.csdn.net/qq_56591814/article/details/126633913
" Y2 ]& Q }* d2 u9 c# y9 n) W5 D6 K; o# J- i. t! o& P. B
; z$ l5 F/ t6 y2 P7 Y& Q, t4 }
|
zan
|