在线时间 1630 小时 最后登录 2024-1-29 注册时间 2017-5-16 听众数 82 收听数 1 能力 120 分 体力 565640 点 威望 12 点 阅读权限 255 积分 174915 相册 1 日志 0 记录 0 帖子 5313 主题 5273 精华 3 分享 0 好友 163
TA的每日心情 开心 2021-8-11 17:59
签到天数: 17 天
[LV.4]偶尔看看III
网络挑战赛参赛者
网络挑战赛参赛者
自我介绍 本人女,毕业于内蒙古科技大学,担任文职专业,毕业专业英语。
群组 : 2018美赛大象算法课程
群组 : 2018美赛护航培训课程
群组 : 2019年 数学中国站长建
群组 : 2019年数据分析师课程
群组 : 2018年大象老师国赛优
0 Y2 R3 D2 S- r: J7 v0 z
* U2 L" I' a" G+ P6 e ! H( f1 W! z! e8 Z. C2 {! L
文章目录
7 R d6 g9 X+ E& H3 v 第八章 文本数据( v% T4 w, D2 S J7 v2 p
8.1 str对象1 e" t, j7 A7 T& C- M: t
8.1.1 str对象的设计意图
5 m! p- k% K3 ]: ~, L7 U 8.1.3 string类型! W) Q9 H* `) t9 L) |
8.2 正则表达式基础# p' t* y1 Z% o$ J$ T% F( d
8.2.1 . 一般字符的匹配
" n2 q e& F2 \9 {, x' S4 w" B 8.2.2 元字符基础0 @* S4 [' n0 U) t
8.2.3 简写字符集
5 v9 B% O8 s" U; D 8.3 文本处理的五类操作 B: x$ j% c" q# ?+ j. K
8.3.1 `str.split `拆分
7 t0 E0 m. }" ^% p Z 8.3.2 `str.join` 或 `str.cat `合并
9 ]( n" B# x4 o# M Y: e2 K 8.3.3 匹配4 }8 ~ M3 V' e. Z, O
8.3.5 提取
8 z0 s9 p7 r! U8 H8 y& I3 h 8.4、常用字符串函数# H m8 B- t9 f8 z7 J
8.4.1 字母型函数
6 J; W S4 ~* a2 {, r4 ~% D% t 8.4.2 数值型函数# g% R" p- b( P, K1 m3 R4 H
8.4.3 统计型函数
. j" i& X$ @& D/ F' X$ C 8.4.4 格式型函数
0 V- B" n$ [2 ]+ V( X t: D `1 S 8.5 练习
: K* U" J/ E1 p8 r1 s Ex1:房屋信息数据集- ? {% @7 n+ M0 w$ B) X1 j
Ex2:《权力的游戏》剧本数据集
% s% T, j+ l# W+ W 第九章 分类数据
+ s/ F- N5 M, U 9.1 cat对象
4 L! H1 G. O& b7 P 9.1.1 cat对象的属性
& O) [: N# J) q# r4 F: a8 O& d9 M' } 9.1.2 类别的增加、删除和修改
+ C$ {( r2 p4 \" n- \/ Y, Y 9.2 有序分类; X) o3 h9 y4 u9 N' \# F2 r& W
9.2.1 序的建立
' D0 N# a5 i8 r5 y 9.2.2 排序和比较
7 u) P6 |) O+ x5 h, o9 |0 p0 k 9.3 区间类别* o4 x+ A9 {: Q F
9.3.1 利用cut和qcut进行区间构造7 v1 N# _0 u/ o9 W# L" P0 _) z
9.3.2 一般区间的构造
! r. k* T1 F7 C 9.3.3 区间的属性与方法
2 W& v4 z/ w9 Q 9.4 练习, f9 m6 k5 p- W
Ex1: 统计未出现的类别5 J& y; @, k# w! P) _; I- l
Ex2: 钻石数据集
; r1 Q+ M8 h {& n 第十章 时序数据0 s: z% B( X" d3 J' u; { E6 I4 m
10.1 时序中的基本对象% T1 u2 \& e8 T6 Y* T
10.2 时间戳
6 c7 U" C0 ^) f6 D: \9 T/ \ 10.2.1 Timestamp的构造与属性
) T# m3 ^, Y! H" I 10.2.2 Datetime序列的生成
) j, \: v# s. Z- a1 w% u6 ^ 10.2.3 dt对象
0 \3 c! @% {; f/ n" I 10.2.4 时间戳的切片与索引
/ F9 Q) q; ~ j8 H" ]; _ 10.3 时间差
& x, G& i& g7 q; g 10.3.1 Timedelta的生成8 P9 e3 g/ I) z& V" r0 W
10.2.2 Timedelta的运算7 \" V2 U3 G4 a' k( v* P& j
10.4 日期偏置
2 Q& x5 E4 z8 F* _. h 10.4.1 Offset对象
6 n- R7 O( W- j9 W0 m$ Z) ?/ b 10.4.2 偏置字符串
3 R8 R$ P4 n, P8 d9 }+ w$ L# z 10.5、时序中的滑窗与分组
c6 H. Z: i: E R0 Q, n2 W7 ? 10.5.1 滑动窗口# W8 B5 S0 O2 b p/ F
10.5.2 重采样: {% r/ {2 J7 r. s
10.6 练习
2 P& s/ w+ q3 C9 k( R Ex1:太阳辐射数据集2 e. i+ Q3 A9 V3 \8 X4 b
Ex2:水果销量数据集
8 f& E+ v: z6 ^* J+ S2 O 课程资料《pandas数据处理与分析》、github地址、讲解视频、习题参考答案 、pandas官网# ~ b: B T1 }* N7 z1 a1 {
传送门:( P- _; N& \; j* Q
' q0 L0 F- u6 H) o) [3 y& v3 |
datawhale8月组队学习《pandas数据处理与分析》(上)(基础、索引、分组)3 O6 j8 B% h! u6 I; T9 B
datawhale8月组队学习《pandas数据处理与分析》(中)(变形、连接、缺失数据)
& T( o, L- q z1 l- G7 E- P 第八章 文本数据8 A0 ]2 ?/ R0 h
8.1 str对象$ F: \0 j: p* i0 f" ~
8.1.1 str对象的设计意图3 K' I& |4 T3 C" k
str 对象是定义在 Index 或 Series上的属性,专门用于处理每个元素的文本内容,其内部定义了大量方法,因此对一个序列进行文本处理,首先需要获取其 str 对象。在Python标准库中也有 str 模块,为了使用上的便利,在 pandas 的50个 str 对象方法中,有31个是和标准库中的 str 模块方法同名且功能一致,例如字母转为大写的操作:+ H( d! `5 F: L+ J
% u' X7 T# \4 S5 d' Y9 K1 n
var = 'abcd'1 i. M; O3 b4 v# f* A+ J8 o# K
str.upper(var) # Python内置str模块
8 p9 p! V) D7 ?- t- v! p* p Out[4]: 'ABCD'
% R. n* C9 h+ ]3 \4 ]7 Z : M6 v6 W3 T) R6 h
s = pd.Series(['abcd', 'efg', 'hi'])
3 B3 W5 t+ }" d) q; O% Y. R
8 u V$ M* c; ~1 ], ] s.str5 Y% K: Q( ]% C# h
Out[6]: <pandas.core.strings.accessor.StringMethods at 0x2b796892d60>
- f% J9 q [! W2 i; d% w : ~) d- [' W& Z6 X. M
s.str.upper() # pandas中str对象上的upper方法
& c1 j2 F3 i, v5 R8 {. Z Out[7]: / {9 }. b8 a' ]! j7 ?$ t
0 ABCD
' c8 k5 o4 X! {8 R& w 1 EFG3 b' }0 H) V- `/ G2 k
2 HI
( Y; l: ]$ X" \. x! o- | N$ R dtype: object
# S- h1 L+ Z. {0 Z2 { 1; O8 X! E$ b1 Z5 `
2
9 @) a( `! L% f- N 3( G4 K- W4 O+ M. o; E% S
4
. y2 Y! a, y5 b6 P 5
1 d ?' P/ x% J% }8 }4 |1 b0 f0 A+ n U 6
" C8 n1 u0 P$ o) C' p6 v+ ^1 P 77 k4 a, N M8 o8 T& e. ?
8
! K3 Z% F4 E; b: w 96 z6 Z; V4 B | [5 b2 N- g: C2 j
10
% J+ R' x1 s! Q% G) p 119 M# Y: Q, h1 j. l4 v. ]
12
9 Z! D5 o( z* P7 p* z 137 t; @2 }: q2 |2 X- M; Y
14
M' e/ G1 X! @5 p( K W 154 N5 O {8 [$ A. W8 E, f
8.1.2 []索引器
# O; d9 d7 P8 }% d! S0 v 对于 str 对象而言,可理解为其对字符串进行了序列化的操作,例如在一般的字符串中,通过 [] 可以取出某个位置的元素,同时也能通过切片得到子串。
3 Y9 |. t o* R) w; V+ ]% Z pandas中过对 str 对象使用 [] 索引器,可以完成完全一致的功能,并且如果超出范围则返回缺失值:
+ K( X f; t/ e' T
6 _. y) g, Q+ ~- Z: p& \ s.str[0], X! T- N$ Q/ N2 O" v2 V
Out[10]: 3 K" g- a. a! r& m" ~
0 a
# X" P$ u3 { D/ p2 M w; P) T 1 e4 C) R& N" I ~) x
2 h& [# t6 r) l+ L
dtype: object
' I( m9 H5 y: j% H$ I5 D, Z 1 P$ m0 E) L# D, X- K! ]8 \
s.str[-1: 0: -2]
* C$ ]. z7 ^1 \) ^: m0 I0 V2 ~ s Out[11]: ! K6 V9 ?! ] g; K
0 db* U, B* }/ M& [% M
1 g
3 R5 N- i2 w* @ 2 i
4 I D; C, L) R dtype: object. S* R8 X# }# I# t6 `3 s" t# G
7 e9 d- W* v/ x' k
s.str[2]
, D. J K, F& a Out[12]:
" R L* k& t2 c7 q% c& t% r 0 c
- n* [2 } ~( V; ]3 i0 R/ B7 R 1 g
H7 F& s9 J, H! h: o' K1 s 2 NaN, P* f; R; Z) A! \ B5 X
dtype: object
: p! |& B, N5 X* U' p9 U5 m # E+ }& a+ X: `% ]( m/ `
1& `& R$ g, e$ m- |" |+ _
2
1 P- Z4 K5 Q; u6 {8 R; y- { 3
/ e; r. b+ P- a3 Q 4
6 [: C* S" Q+ x" \9 c 56 J" d; P4 V3 ?
65 ^; }- U4 I7 k o
7
. \) m) A3 ~0 m5 `# C7 V 86 H* P8 f8 g/ ^9 A
9
; |: E6 j' b. e3 C 10
/ q& p, i" E& m( m/ _% L* k 11, f6 s0 ?/ m; ~1 b! n
12
" w+ |& T" i3 N 13
" d- a& r4 m0 ]( ?3 k: y$ k2 | 14# J: O& n$ g3 {/ M
15
% D" a+ L) I6 y( @& h( O" x 16
0 u! i z8 v1 K8 R, \ 17
" o% O! I+ R) D& ^ 18
0 y. z" {) X5 b, E: g 194 `, h" f* `' |- A9 ^
20
2 W' z! H: D$ e! Z& T$ k import numpy as np/ \( x, n2 h8 [6 C7 m% U
import pandas as pd
7 {8 g" D3 u/ B/ B- _
7 s$ Z3 W B( \$ k s = pd.Series(['abcd', 'efg', 'hi'])7 p, m. b5 x1 |6 c3 C( |
s.str[0]
! v# f' M) w( f! z7 T @ 1
0 H/ @* c) h7 n 2
9 ]5 w$ v3 I" ? A/ \9 T 30 k0 @: s8 ?# h u
48 B( j8 y" u9 i& _7 x. p
50 ]; D' }$ \5 o" ?
0 a0 m- `4 C- o3 d
1 e! T. o. Y/ R# h, f
2 h
6 m* E' r' U* _, q3 S+ O- X dtype: object( z, Z5 V9 z& g- l8 A* }: w2 w( } _
1
# D* H3 T, J8 W 2
. }0 ~! e8 a( _* Y& I# Q$ ]- W" i 3$ v- x1 D+ w" ]/ v4 D5 {8 U7 ]
40 [8 j9 e9 y$ [* Q: @$ T) ~6 W& x
8.1.3 string类型
8 Q, K! Z4 d) g4 ~4 d9 B 在上一章提到,从 pandas 的 1.0.0 版本开始,引入了 string 类型,其引入的动机在于:原来所有的字符串类型都会以 object 类型的 Series 进行存储,但 object 类型只应当存储混合类型,例如同时存储浮点、字符串、字典、列表、自定义类型等,因此字符串有必要同数值型或 category 一样,具有自己的数据存储类型,从而引入了 string 类型。6 v$ M/ r. z8 e6 z& J
总体上说,绝大多数对于 object 和 string 类型的序列使用 str 对象方法产生的结果是一致,但是在下面提到的两点上有较大差异:
7 ~2 f! N9 Y: I6 n. g( h4 k. @$ n) Q t: y& C) g* b* h. U- S! o
二者对于某些对象的 str 序列化方法不同。
3 R2 x) g1 m' W# T- }1 s+ d 可迭代(Iterable)对象包括但不限于字符串、字典、列表。对于一个可迭代对象, string 类型和 object 类型对它们的序列化方式不同,序列化后str对象返回结果也可能不同。例如:6 {" W" n: S' R P
s = pd.Series([{1: 'temp_1', 2: 'temp_2'}, ['a', 'b'], 0.5, 'my_string'])
0 K% S+ ]8 X O/ p3 Z W- q% g s
A3 T' k1 f, a: C" }* L 1
7 p9 g9 a/ p, D/ K. b9 i7 ^' x T" a 2
6 b- N" F3 `5 q7 y: l( C 0 {1: 'temp_1', 2: 'temp_2'}
; N; h0 W, G' n4 l# G/ s2 ?( f 1 [a, b]+ f$ F# F8 h* ?0 m
2 0.5
# i- `, ?4 T! T* ? 3 my_string
/ p, i; E/ S) z1 h6 [) e/ ~ dtype: object, f1 v+ w; s! |6 C; N8 g
1
- A4 h' c0 F& y8 M 2
- \0 b1 U0 X3 ~- ? 31 t" d" S% O5 x9 X, A3 T
4
+ ?) k) X8 O: m! W( M 5/ M; j [7 p2 F/ p
s.str[1] # 对每个元素取[1]的操作
8 O- B8 {6 x. y/ Z& y- M" W; R; W 1
/ l: m/ v/ x6 z2 D! i u5 } 0 temp_1
7 |' V, M; m8 W9 h: O 1 b
" M1 @; ?& e3 Z7 @1 _8 S 2 NaN
e# ]+ e; s3 q: c 3 y
( t7 Q ^4 s# f2 r+ ]8 o& J dtype: object+ v- k5 H0 ~0 p
1
; Q: M8 ]4 d) z 2( E6 R( }5 g6 |
3 t4 ^% } Z4 x/ j) \
4
" u. p& k9 N" n. W8 } 5
% U9 R$ u( @' V+ R" h' d5 @ s.astype('string').str[1]
/ y' @2 o0 s7 m/ f3 A( @ 1% I& p$ a3 {4 }: V" _9 w: e- H# R( G
0 1; b, M( v3 B+ a3 U( o5 h( P- Q
1 '
) v" S. h" L$ K7 F: y8 u 2 .
3 d7 Z8 r: X1 O8 n, n* b- I$ X p/ ^* L 3 y
: l9 K9 S+ \ {3 K. h% q dtype: string
2 A1 E- H9 m7 A# | 1) r! Y+ h/ p( p) \! G9 c( H- o
2
1 U6 T+ h" [: J3 s, _ 3, J5 }8 u, K8 W( R+ w1 A$ Q
4- P/ |/ l7 K9 t1 }
5
t' F1 o" @7 J" s 除了最后一个字符串元素,前三个元素返回的值都不同,其原因在于:3 k X- J2 f# S; v
% k2 Y7 J0 u( i4 x, x; W$ O# J
当序列类型为 object 时,是对于每一个元素进行 [] 索引,因此对于字典而言,返回temp_1字符串,对于列表则返回第二个值,而第三个为不可迭代对象,返回缺失值,第四个是对字符串进行 [] 索引。
: m7 r0 {; @% J2 F string 类型的 str 对象先把整个元素转为字面意义的字符串,例如对于列表而言,第一个元素即 “{”,而对于最后一个字符串元素而言,恰好转化前后的表示方法一致,因此结果和 object 类型一致。5 I2 r( P: e5 W- _1 }4 L! N
string 类型是 Nullable 类型,但 object 不是
+ ~! m \1 U9 }) L2 m8 e 这意味着 string 类型的序列,如果调用的 str 方法返回值为整数 Series 和布尔 Series 时,其分别对应的 dtype 是 Int 和 boolean 的 Nullable 类型,而 object 类型则会分别返回 int/float 和 bool/object ,不过这取决于缺失值的存在与否。
. L( @7 G- F0 t5 V- K8 N' W: J 同时,字符串的比较操作,也具有相似的特性, string 返回 Nullable 类型,但 object 不会。
4 F# q8 E" X8 L! H s = pd.Series(['a'])9 ~0 c X& H5 o7 O* G$ a
! T" K) h% t3 Y8 v: E) `, `3 a) h3 q c
s.str.len()
; n+ W" j' v- `' O5 y Out[17]:
/ d; j3 ^& u; c* C 0 1" w1 |- [# I. X/ ^1 a
dtype: int64
3 i0 v3 M) o0 e5 E) B8 C0 ? : k) b {& B9 Y$ m P _
s.astype('string').str.len()
6 {# u' ]2 g7 V' j! B Out[18]:
% ~( i# l# N) L0 ] 0 12 H2 |# g% K- |# G' E1 p
dtype: Int64
, g( R* `) v. H; R* o( {" X2 K9 M
* c6 r0 @& D- w P. M. l s == 'a'' O! [+ h% N+ Q9 W! v. ^4 l
Out[19]:
" L/ H# V' S+ l' b1 P6 c 0 True
4 H' Z+ I0 U4 i$ Z1 \) o" H dtype: bool2 h9 f; J, T" O+ t6 D% J+ F- V M( B
( t* n# R" m2 D. e! l
s.astype('string') == 'a'& x+ h' \2 W5 ^. }
Out[20]: r3 [ `$ i7 u$ H
0 True
* `/ g6 `% B0 K" `# h dtype: boolean
" {% i* ^: Q8 F6 g+ ~7 q M # D5 e0 G- |# @6 ^3 ~5 J
s = pd.Series(['a', np.nan]) # 带有缺失值
. T) v- i! a: S0 X3 L d6 j
; v9 g# H0 g( @3 B s.str.len()
7 h7 h( P. d/ E Out[22]:
. O$ ]# V9 r# j; z+ Z6 y 0 1.0$ A: R* H! S4 ?" B
1 NaN# I- s- [ ~; s' s
dtype: float64
* ^5 J h9 w* X8 }; x
; M/ l* Q! M. N s.astype('string').str.len()* s3 c6 G+ b* v: V) B
Out[23]: 5 }0 U0 T) Z( M9 w7 s* b
0 1/ Z0 w- T4 K7 H
1 <NA>
4 |! d R$ k2 A/ c+ ` q0 E" z& N dtype: Int64
- H# m1 B! l, V% S f4 c6 Q/ D
4 [& E2 H) P1 Y9 G s == 'a'' `; t* a3 y% R2 E
Out[24]:
7 s7 i( ^8 V* [ 0 True: ?' C x) |& f' }0 }% n# {
1 False% ^% V6 m- H. y
dtype: bool, }, E; k" U4 q9 o3 W( f
& N8 Y" x7 Q- y
s.astype('string') == 'a'
: D, B- X0 P: [ Out[25]:
! F9 ]# s& h3 v0 |% P6 R 0 True
9 S( C0 E# M5 l2 M2 N$ V2 M2 Q( z 1 <NA>) X% N6 `8 u' O# Q/ U# i# V7 ?
dtype: boolean
' T3 w, R S# x& L7 ~6 z; G
}2 ^0 c6 p- `6 H" u% W 1
+ N, x" m: w0 e4 R# j8 h" _, H 2
9 J! T5 d; C, N0 x+ h2 w9 s 3
( G6 y1 j1 m- U. z& J 4' |! g) a. @5 ? g/ \$ p: C0 M# x
54 r4 E7 n. q( G
6
/ J" |" f2 O0 ?+ m* X- z 7, ~. E( p) c6 \1 D
8
, ]& p7 K$ }1 F 9# z* F, _ ~. f3 B5 D, G1 `
10
) ^6 }) g( m" W; O) X" S 11
6 [4 M! F% j& x 125 \3 k A9 l% O7 h, g. Z9 i
13
, e- Z6 _, |2 ^2 V, u; V 14% ~# ]5 E- U7 ?, {+ A6 L6 k
15
, Q6 J a3 g1 i+ p9 e* C6 r 16
% H$ ^- v& G) a& T2 [' g8 P5 N( y 17/ @- K0 B9 b& q
18
" X5 M: O6 ?# n" j 19* d! V$ W7 j6 h/ h, z- W3 H
20: P& X W. z4 W9 d; e7 w
213 J' q4 Z& i( c6 c" [+ R: y
228 Q% I& N, z0 E4 B1 Z! g i
23
7 H- M' l- u. c! B- ` 24
3 @6 d# [: q8 N9 n 25, H8 e2 O2 L4 H+ K- d( W
266 w# I2 }; F. G9 y8 l0 V: Y2 ~4 v
272 C' C% {6 E. J Z/ L
28
4 {# J$ ]/ K& c! e 29; t* P' g. }0 v" b, v
30
: j* `/ j( H, ` v% Q6 c5 E 31
7 O6 c; N) B3 k 32" a- j( e3 X, g7 R7 ~ ^
33& t/ [# h6 Y3 {+ B# v1 y6 R
34
/ l$ S+ q/ p6 Y5 ?5 r$ j 35& J! x6 ~) U; Z# l/ c3 C+ e( w
365 P4 G8 j4 q1 x! Q# U, y
371 f; d8 l" P& q/ M2 @( G: z
38
4 e6 T. u2 ~0 `+ ~+ v9 v 39
- M3 v( a: k4 c. e* O) B4 _ 40
- j7 m3 M1 P1 O ?# a- ` 415 B/ N: ^$ T; ?
42
$ I4 L+ {" I# ]5 I, \; ]) S1 U 43- ~# {+ v) E2 v4 K
44
, I: _' e/ A$ x# U0 n 45. j S$ Z, ~7 b7 [8 T% N4 d
46
6 t; X. I' P! ` 47
8 r+ H: @+ ~ k) }$ ^2 N& ^& ~ 对于全体元素为数值类型的序列,即使其类型为 object 或者 category 也不允许直接使用 str 属性。如果需要把数字当成 string 类型处理,可以使用 astype 强制转换为 string 类型的 Series :
# h+ g c) h: @# D7 W
- A' _3 h2 W: B* K. I0 `/ K* [ s = pd.Series([12, 345, 6789])
( p- ^! q# A+ u ( G6 V) r- x4 C/ G& {- N! f
s.astype('string').str[1]
! i2 R' E v" a; E0 v0 I. F+ G Out[27]: 1 y+ h0 F1 I& L6 j) Y. L! m
0 2
% }& i! O, p: U6 m( K 1 4
5 I- I6 @( y( A 2 7
' o. E3 X6 K f6 x# L @( t" B2 _ dtype: string/ N* O3 O' j4 ^# o
1
" r! ]8 e3 W( k: ]; \6 m( y 27 T! D# |1 [& E0 s7 t
30 |, Q/ L$ I2 z# ?" [/ Q
4
1 B5 l! S5 I' R. M, C" w' p 5
9 Z' I+ ^$ V0 h' ^' `7 _ 6. p; Q: L9 h; u2 O
7- q& l) f) F) G& N! i& |
8
+ o- E9 ^0 G/ `$ [ 8.2 正则表达式基础1 l* j. }/ B/ {8 @
这一节的两个表格来自于 learn-regex-zh 这个关于正则表达式项目,其使用 MIT 开源许可协议。这里只是介绍正则表达式的基本用法,需要系统学习的读者可参考《Python3 正则表达式》,或者《 正则表达式必知必会 》这本书
# z! S/ m* y' s% }% j0 {
# r: q6 L( N6 y. [% Z# s, h 8.2.1 . 一般字符的匹配
8 s1 F2 P6 o3 ?! [* s; r 正则表达式是一种按照某种正则模式,从左到右匹配字符串中内容的一种工具。对于一般的字符而言,它可以找到其所在的位置,这里为了演示便利,使用了 python 中 re 模块的 findall 函数来匹配所有出现过但不重叠的模式,第一个参数是正则表达式,第二个参数是待匹配的字符串。例如,在下面的字符串中找出 apple :
5 S2 o, p e2 }
1 |1 p9 h) U' \5 s' Z1 {7 X import re
0 v' y4 w* Q R0 n( g( E
4 W+ k3 z6 B% X3 z2 q re.findall(r'Apple', 'Apple! This Is an Apple!') # 字符串从左到右依次匹配/ O+ d1 i# ` Z5 I" v- l% |, _: p# ]
Out[29]: ['Apple', 'Apple']
, [3 h7 A* }# n9 V; X, P& e( U, g 1 _. Y# v- w( E: H# g4 D
2
. w# y7 G8 k2 U6 D- k0 L 3& c# Z n/ Z, {, U4 ~
4! n: ] Y, M/ L" R' k
8.2.2 元字符基础1 q! g5 N+ w2 i3 v3 U
元字符 描述& B4 b0 \+ _+ D0 A1 m& k& X
. 匹配除换行符以外的任意字符
3 Q$ H2 W: F# E; o3 V4 |. a [ ] 字符类,匹配方括号中包含的任意字符
. g7 t4 h; } m+ {5 l [^ ] 否定字符类,匹配方括号中不包含的任意字符
4 J" a. ?/ ?( M* X6 [ * 匹配前面的子表达式零次或多次/ l3 P/ |1 K+ V3 ^; {( g- O [
+ 匹配前面的子表达式一次或多次。比如r’d+'就是匹配数字串,r’d’就是匹配单个数字
, {' y; B/ M) {3 v8 _/ E" n, g! B% k ? 匹配前面的子表达式零次或一次,非贪婪方式/ L& L8 t- Q4 S b- l% f
{n,m} 花括号,匹配 n 到 m 次由前面的正则表达式定义的片段,贪婪方式1 [! `0 G7 X9 d) U' }% D4 ^
(xyz) 字符组,按照确切的顺序匹配字符xyz
3 ~" T/ ^% X p) D& e | 分支结构,匹配符号之前的字符或后面的字符1 G8 d: {' c% O+ o
\ 转义符,它可以还原元字符原来的含义( a& W7 W4 M! I* k. p
^ 匹配行的开始# ?/ ^ P) Y$ K) ~/ X7 _
$ 匹配行的结束
: t) P, P1 |5 r. Y. i: r1 q import re
" g; b0 q9 B, H. D \$ n' a re.findall(r'.', 'abc')
$ z+ _" H, t. u# e; W Out[30]: ['a', 'b', 'c']
" _' |; O: q+ l* D4 U( e- t# E 6 z, H, z! S, ]( c. I9 d/ D0 }$ b
re.findall(r'[ac]', 'abc') # []中有的子串都匹配8 x& i% t I. g c! M
Out[31]: ['a', 'c']
0 M8 m. z- w. J7 y4 D$ q0 D
. K; l& l5 z1 @; n re.findall(r'[^ac]', 'abc') 0 `" E$ a K. S p7 l( ?0 ?
Out[32]: ['b']
, _ F: D W1 {; { 0 W+ r8 {7 f/ }! c' k
re.findall(r'[ab]{2}', 'aaaabbbb') # {n}指匹配n次4 Q5 L; } C8 H7 k' P/ y
Out[33]: ['aa', 'aa', 'bb', 'bb']
' y# Y6 ]2 Z [/ Q
) f! r: A( }! h2 L& \* X! A8 w re.findall(r'aaa|bbc|ca', 'aacabbcbbc') # 匹配前面的或者后面的字符串
G+ w9 J1 w4 G5 x' X0 m Out[34]: ['ca', 'bbc', 'bbc']
% g7 e- Q$ P. y/ o \" i
, M9 s5 m; b" h) W" H& T3 a/ R/ w # 上面的元字符都有特殊含义,要匹配其本来的意思就得用\进行转义。) R# `" L Y; [0 k& t u7 B- H
"""4 x1 i! S% q& {4 Q
1. ?匹配的是前一个字符,即被转义的\,所以|前面的内容就是匹配a\或者a,但是结果里面没有a\,相当于只能匹配a。6 i( C) Z- u3 g
2. |右边是a\*,转义之后匹配a*,对于竖线而言左边优先级高于右边
7 X# B' \: r$ J1 X8 \+ N! V8 Z 3. 然后看目标字符串aa?a*a,第一个a匹配左边,第二个a匹配左边,第三个a虽然后面有*,
& H2 o% R, i# ~' ?* }3 ^ 但是左边优先级高, 还是匹配左边,剩下一个a还是左边,所以结果是四个a) u4 F" z& r9 _2 W; w4 n" v; ^/ o% T
"""
# M ]0 n! T4 y w' s
# z( F* N. d# E. I re.findall(r'a\\?|a\*', 'aa?a*a') # 第二次先匹配到a,就不会匹配a?。a*同理。
! `1 M9 i( a$ ^% m Out[35]: ['a', 'a', 'a', 'a']
+ ~7 A# q0 [& \* V O$ v" b" w
/ H( g" M: J1 w! P. z # 这里匹配不到是因为目标串'aa\a*a'中,\a是python的转义字符(\a\b\t\n等),所以匹配不到。4 k& N1 j$ ]; O: I4 q! V
# 如果是'aa\s*a'之内非python的转义字符,或者'aa\\s*a',或者r'aa\\s*a'就可以匹配到\字符。) |6 [) O4 O( r8 I+ L
re.findall(r'\\', 'aa\a*a') + d0 f9 B \2 M8 n, G: `1 [
[]- j Y! |2 y' l0 q- k; v* w" w
' r3 z" I W& `- Q8 K
re.findall(r'a?.', 'abaacadaae')( v G+ N$ ~: p& h; j7 r. Y1 R3 f
Out[36]: ['ab', 'aa', 'c', 'ad', 'aa', 'e']/ w z7 l' ^5 U% I
; i2 t; O8 Q" r% |* v, t
re.findall(r'(\w+)=(\d+)', 'set width=20 and height=10') # 多个匹配模式,返回元组列表
9 o- Z3 F/ K) w' ~; g. P T [('width', '20'), ('height', '10')]- a+ d$ L( p3 K' k" k
9 c$ `! g7 Q/ ] 1+ M9 N& s% V, a& f2 k9 ]' H
2
# d" f: X4 _* f 3
$ P% t. C9 O5 S 4- m# m2 _" }) q- S! ~
5
& V6 F( j: M, N+ {* G/ l 6- ?1 w/ N+ k2 M/ U& S! U" g: a
72 K! x: ?/ P) O: H& ~( O, A7 q
8
+ c O$ R* t0 e/ g 9 A" d, z2 _1 _6 c: A8 \$ g1 S
10" L" \; Q, `& f! ~( L& l
11+ [+ T/ S) s9 H( _4 ?
12 H# V+ Y e1 _8 J+ @
13; C8 g ^+ u5 [6 K1 x; C V
148 W0 j+ y' I9 H4 _
15
" p3 m3 q0 ]" |" ~8 r8 ]! c' _; O 16" Y8 g$ I0 Q3 t0 [- M
17
/ A% _. A0 u( g 18! ?* H) z/ e( a7 Y# g+ y
192 R! I) O0 V; t& H$ @+ H* a. A
20
7 ]1 h+ M3 p& j, k 21
4 t0 {* G- ^! T) Q 22
j- p( q, c0 E$ A 23+ o3 F \2 a) w7 h, O, c* G. v+ f0 D
24/ H, x- b+ t6 [- d: T' H
256 M" i/ P4 E% d5 _5 Q
26
1 E0 _! t) N1 G' U! S- U- p 27/ L" P/ S% h7 c' O9 C
286 G* o( }' d, T1 o5 Z
29, i w# R3 P0 |8 C0 l; L% A5 ^
304 R7 \6 |" p; l- X% K
31
) w7 n: _4 }! l5 {# m; F5 s2 { 32
4 m9 S [9 O8 a- r' y2 k 33* H; [/ c% T V" {" O
34
" M% W; v7 N" z) g 35' N( T; J/ }: B) ?3 u
36
6 N1 M; d# r, ^9 H# S 37 s$ K: A9 O8 V/ G" L9 t& q. f+ K
8.2.3 简写字符集( M& n8 I3 m7 t& [% B) @
则表达式中还有一类简写字符集,其等价于一组字符的集合:
' I6 L3 H7 u) w! S* d
* ^, t. [$ K3 h9 i1 L4 J 简写 描述
5 i; A# } B- B, p. w% g0 I \w 匹配所有字母、数字、下划线: [a-zA-Z0-9_]- r- h6 r N% h5 u$ f! L
\W 匹配非字母和数字的字符: [^\w]! ]; _; |/ i" \. ? r0 w2 l
\d 匹配数字: [0-9]+ |9 |- R" g. a( U# _; a" i4 N
\D 匹配非数字: [^\d]3 h5 ?3 e7 @: g1 W" x
\s 匹配空格符: [\t\n\f\r\p{Z}]5 P) w T: T) `7 l: r
\S 匹配非空格符: [^\s] o" |# H8 o* k, z, F0 ]) e; P
\B 匹配非单词边界。‘er\B’ 能匹配 “verb” 中的 ‘er’,但不能匹配 “never” 中的 ‘er’。
( k. E6 q5 _: f. S$ x/ H, o1 { re.findall(r'.s', 'Apple! This Is an Apple!')
9 c: q. i' C/ o& Z: a1 w7 J( G. b4 Q Out[37]: ['is', 'Is']7 w0 |1 O6 e r( H
: n+ R- d6 R+ p4 O. x0 l9 a6 C, P re.findall(r'\w{2}', '09 8? 7w c_ 9q p@') # 匹配任意数字字母下划线的组合,但必须是两次1 n. \* D5 V7 @" K6 J7 i8 g
Out[38]: ['09', '7w', 'c_', '9q'], V% t) L) x% C7 q3 W
# U) a' P( {7 r/ G3 @6 R% i# I1 N
re.findall(r'\w\W\B', '09 8? 7w c_ 9q p@') # 匹配的是两个字符串,前一个是任意数字字母下划线(\W),后一个不是(\W)
. v6 A3 F. N. }3 D! w Out[39]: ['8?', 'p@']
; X5 t- `2 }! N2 r8 P 9 G! C8 U! q) f1 y2 H# E2 ?
re.findall(r'.\s.', 'Constant dropping wears the stone.')
) [) @9 ]7 X% z; ~6 p Out[40]: ['t d', 'g w', 's t', 'e s']
& ]$ E# t/ g, O! ] ?$ a
' J& N7 U7 l4 k( E re.findall(r'上海市(.{2,3}区)(.{2,3}路)(\d+号)',0 _( u8 M( @' d) \" P
'上海市黄浦区方浜中路249号 上海市宝山区密山路5号')
' u* }; H$ r a: Y; N6 ` $ K5 j$ ~5 P/ b! k% k
Out[41]: [('黄浦区', '方浜中路', '249号'), ('宝山区', '密山路', '5号')]
0 y# K/ b7 z" b* r: z! a
( D& d2 g" o* f$ c 1% }/ l7 S3 s8 J- y5 C
2
6 a1 u& p; L0 z" L 3
* I+ Z+ e- C$ y; P; b 4
/ }$ D9 g% E$ A4 K3 [6 C0 s+ S 5
( K1 C; k; f) a: {$ s 6) N5 n0 ?. ~, ^3 p h
7
# g; i! n4 E J& x* W. l+ R4 R 8. ]( h) v+ R' i" c
9
) `: O' S# [3 k: T% ]9 T6 N 10( x' h% R# N" V; c4 D
11: q9 E. e: K5 @2 F
12$ H5 y4 z9 J5 b& D$ A
13" U8 h g9 X% ^. D& K
14& u P7 B+ e' I
15# ^$ X" J% y% ?2 }9 L- z, u
16
8 y- ?( B0 J$ g' ~) f: k+ q 8.3 文本处理的五类操作
2 K2 K" W# Z, z! q% G/ B7 p" \ 8.3.1 str.split 拆分; J, G/ |0 T/ J: v8 Y8 a2 J/ X
str.split 能够把字符串的列进行拆分,其中第一个参数为正则表达式,可选参数包括从左到右的最大拆分次数 n ,是否展开为多个列 expand 。
3 r e: p. ]% O6 z" H ! P* ?8 B9 O& X2 V; {/ G6 H5 u
s = pd.Series(['上海市黄浦区方浜中路249号',+ p, L/ H) L2 G7 U+ B1 T' M
'上海市宝山区密山路5号'])
3 s4 P0 J/ W4 y, Q9 K/ o( [6 L
6 x8 h: Y3 \$ K, H4 n : V% s) L, O& h# d
s.str.split('[市区路]') # 每条结果为一行,相当于Series
; {/ h% l; x4 l3 t9 y Out[43]:
: K% z7 A% s; v6 W 0 [上海, 黄浦, 方浜中, 249号]
8 ?7 ^" s* c4 v! s% ] 1 [上海, 宝山, 密山, 5号]
' p# p0 h6 B, H, |& D dtype: object
6 W ^9 k5 c+ X1 @* N) M
( Z2 _8 b; J8 E1 d s.str.split('[市区路]', n=2, expand=True) # 结果分成多个列展示,结果相当于DataFrame
! D9 O2 |/ k9 W9 m, `, o Out[44]:
- Y) p% O5 [" u! h 0 1 2
+ v: r8 V* v: I0 R9 C' w 0 上海 黄浦 方浜中路249号
" |2 ]! N3 i7 N 1 上海 宝山 密山路5号
( m; A- t B4 s6 o 1
# i0 v. B/ b$ }/ C- b h 2
8 o5 A/ |/ h6 k( C 3
, Q* _3 Y# V2 E. B$ Y, x 49 \1 V1 p( E E$ @* A/ Z! Q7 s
5- \/ t, ~( L! M7 z% t
6! g8 b: b1 A1 p5 W6 w( |
7: S: D; Q# K* B b
82 y' u2 B3 x4 \
9
/ r! [' V; d1 ~. \0 p) p& ? 10
6 u0 T: q! w# o3 q. Z; n# K5 E/ S 11% A2 w( {5 ~5 G$ \0 c+ T& |+ `& H5 y
12 k2 ^# F6 J1 t6 g
13
0 {" d5 K g9 w- T 14* R, y, {( h1 M( d4 |( V* S
15
3 H) z! r9 V2 [( S4 g3 _0 c 类似的函数是 str.rsplit ,其区别在于使用 n 参数的时候是从右到左限制最大拆分次数。但是当前版本下 rsplit 因为 bug 而无法使用正则表达式进行分割:: I+ T' j: g/ A/ K5 ^$ s7 h
3 W. E" v6 d* |6 u, b( |
s.str.rsplit('[市区路]', n=2, expand=True)0 f; l3 X) b+ p/ C
Out[45]: ; O) @' k# U" Y7 L
0
& M7 R+ H0 ^4 Z5 p4 {0 R 0 上海市黄浦区方浜中路249号
, |: [4 Q/ J* Y# P% y' H 1 上海市宝山区密山路5号
# S! p T$ a. P9 @; T6 W2 f: S 10 u: ~8 M& Y- O: p0 d
2" ]& W: K' ~* M2 Q
3
1 j) V* g3 A' M- O1 v: t V 4# ~ s! P8 L4 h3 s
5" S4 n6 |3 C2 ?" g; \& j
8.3.2 str.join 或 str.cat 合并! U5 D3 K3 e% V0 }
str.join 表示用某个连接符把 Series 中的字符串列表连接起来,如果列表中出现了非字符串元素则返回缺失值。. B5 B9 m3 F( r5 v: H
str.cat 用于合并两个序列,主要参数为:2 H' I! ?+ `' i* b# `
sep:连接符、
# H; z: n" W; v4 W8 Y9 s join:连接形式默认为以索引为键的左连接8 e: Z& v$ y3 j; h& |' j2 S F! R9 O8 k
na_rep:缺失值替代符号
0 q( a# d) p8 v" h( Z8 { s = pd.Series([['a','b'], [1, 'a'], [['a', 'b'], 'c']])' y( a% o) @ o- S* D
s.str.join('-')7 r- ?6 N5 R9 m9 b4 B: F
Out[47]: ( {* @& t$ S# P( x) A
0 a-b) l6 \: f# K0 i
1 NaN
3 _- S! m# Z# F( | 2 NaN
4 i8 y- M) j* T9 h+ M dtype: object
- Z1 ?/ G2 |" Y3 H 1
5 v+ ^% K- Z3 s* U 2; P2 h1 z0 C* a: |4 C( w
3
; j# l7 V" c( p# _7 N" M 4* L6 J! V. ~; C# J7 {
5
( X* R5 r& d M7 ~ 6
6 C7 ?0 ^- l# Y6 S 7
' M ?0 _/ C% ]+ k s1 = pd.Series(['a','b']); v' g) w+ b# T+ z* _( O% _
s2 = pd.Series(['cat','dog'])
; O( P9 |% K& O. B/ q7 b* o$ r% r s1.str.cat(s2,sep='-')( Z* b( C* w: A) S! K
Out[50]:
( U V( x9 ]6 i$ F 0 a-cat
7 j$ ?' ^# X. s4 A j- |! [ 1 b-dog& o0 q# z/ ?' i: `$ P: L
dtype: object
5 t: \2 t& O9 P/ l5 l5 J7 J 3 I6 J" f4 B; U' p. n
s2.index = [1, 2]
1 n# P2 \! {# G7 M s1.str.cat(s2, sep='-', na_rep='?', join='outer')$ r. l2 b- c8 u' D' h
Out[52]: - a5 s; O, o6 T: N$ x% n6 E
0 a-?
- }) k" V6 L) B3 i+ w 1 b-cat9 ^0 V5 p+ I7 o9 F* o9 z$ U) l" `$ l
2 ?-dog- k2 R. S, v( t
dtype: object
: u: g8 @) B1 G9 P) l 1$ e: F2 |# J; l) v; m2 X
2! Q; C( r: k- G
30 N! y; D+ O, }
47 Q6 @* G6 [0 ]9 u. q& _/ {
5
7 k* ]5 H+ L# s; n4 C 6( r& N0 k' s! S, F1 x
78 ~! e. T0 d: W6 X0 E
8
, N* _) y/ L+ N# R' Y 9+ g; F1 s9 P( a4 Y( i3 z/ N Y
10' A0 L: }6 P' P! B6 r1 d6 V x
11* P3 R5 F' B& G- N& c, H5 }
129 p2 U5 q' b k. ]
13+ h) m0 j: e8 ] h4 X% Z+ d
14
' ]+ |3 k" C$ a$ [ 15! b( c9 e: S! T4 L1 Q3 A
8.3.3 匹配
0 X6 a: Z E$ z4 ^ str.contains返回了每个字符串是否包含正则模式的布尔序列:
6 S7 v" A; W% V. V7 \; o. ~ s = pd.Series(['my cat', 'he is fat', 'railway station'])0 |7 G& O( V% |
s.str.contains('\s\wat')
( U. |' `* a, n$ X: C& A1 i1 n
$ H# S' _# r2 F1 s 0 True7 o0 @+ W/ S$ x; C1 I. _
1 True
+ i# h1 { k' ^: s- U1 m 2 False
u0 p* s& Z" E) q- p) r- J" ^ dtype: bool
, e( L. y% B' c" @ P 11 _) {; m; N* [4 O
2
& f/ c' S4 ]- B+ J5 a K 3
9 X0 g9 C! h1 {8 {) } h 42 c, M& v; H; O: k
5
/ ^+ r' q+ f. C& f; a, K 6& t+ z v; u) [, B1 z
7
9 a5 I8 \; o9 G d0 V str.startswith和str.endswith返回了每个字符串以给定模式为开始和结束的布尔序列,它们都不支持正则表达式:/ o' F: W4 l i, }7 {4 N* Y: T
s.str.startswith('my')
* _9 c ^; M W& S' L: c& T E
/ J5 e" u& f, K& ` 0 True1 w* R, s9 t! T0 A8 F8 z
1 False
. V/ w; w2 p& ^% a; B/ S 2 False" V5 x: m# K6 ? ]2 g
dtype: bool
2 ]* F/ u4 R) h5 L1 x5 M 1
) `- _/ p3 Z( h! K d" ?- P 2
; T* b/ e/ f2 R9 w- t! @" w/ k 3
7 l, C* e5 V% T6 j! I 4
1 b. Q9 c9 W- T; H& U' T 5) D$ R& M4 e( x& A* A8 f( y2 R8 f B
6" U# F2 p( l9 I- T. A0 ?% `& J/ [
s.str.endswith('t')
7 y/ S) ~0 U( ~& I: ?5 \" o9 ]8 v , h$ ]9 @6 V% v( {. {9 @; y4 Y5 C
0 True2 z8 F" o; f) O X3 V
1 True
/ ?4 l! J! p1 D( W- `4 ^ 2 False
8 p3 V2 y) A) f+ W7 d dtype: bool% i! J6 x* o( d6 }( A0 }' e; N
1
# l- m; S% P* \/ H/ ` 2 c: z# Q/ S. j& M( B" m
3+ N4 ?9 h, U+ g2 [( A6 h
42 Y. e) A4 J3 I" X: N+ t% ]; \
53 ?' o7 m; g; [) d- M8 J
68 ~1 G. A% l# _
str.match可以用正则表达式来检测开始或结束字符串的模式,其返回了每个字符串起始处是否符合给定正则模式的布尔序列。当然,这些也能通过在str.contains的正则中使用^和$来实现。(貌似没有python里的search方法)
7 C0 k6 B/ {' D/ P' V. ? s.str.match('m|h')5 S) L# f; c" ^& G% B
s.str.contains('^[m|h]') # 二者等价
5 R; e& A$ c0 c' A' X$ U
) H/ [" }" Z7 ?4 F 0 True" _" v5 U7 Q" m3 x
1 True% C. \# A" }" e6 C7 _ K, @* J0 w
2 False
2 R! u/ \8 S s( r2 w9 m dtype: bool% g- M' }8 G$ R6 P
1
' O! s9 ~" P1 F2 D( Z" \' z2 T 2
& {8 N0 C m- j9 p! @: _: w 3
8 A) F+ o: ^5 h5 ]" e 4" K3 d4 ]4 r: j! S5 x
5( d/ F5 y h: E* B% O
6& {/ |) g- p1 ~2 S0 k }" N4 Z! z
7
K) C0 J* S _. q0 ? s.str[::-1].str.match('ta[f|g]|n') # 反转后匹配
. L$ z" a' ~: N6 ~ \' K s.str.contains('[f|g]at|n$') # 二者等价& C# P( s+ p$ d; }+ k' T. [' v
% I" z0 l3 W7 c2 x) n' n
0 False# o- B% J' r& D" } Y" ]% u# r5 K N( a
1 True0 n) O3 x7 n `
2 True
/ B) w8 Z. f; A i% ]8 ~& { dtype: bool
4 R/ [2 ]/ m8 I 1
' r7 C p: j/ z8 Q0 v 2
. a4 t1 e2 Y7 A0 M0 E& I+ B 32 z5 c1 V x7 }
4, S8 X3 U% e9 ]4 v! h$ L+ P4 |
5; a; F1 Q1 Y4 v% J
6- C7 U" e4 D6 B- `+ [
7
6 _8 _, ?6 T- m! d+ a0 U str.find与str.rfind返回索引的匹配函数,其分别返回从左到右和从右到左第一次匹配的位置的索引,未找到则返回-1。需要注意的是这两个函数不支持正则匹配,只能用于字符子串的匹配:
( d8 g3 i# s6 t( Q# y s = pd.Series(['This is an apple. That is not an apple.'])
- V6 @, d; B' Q$ c: h' M m. X3 K4 H# n! B1 k
s.str.find('apple')2 O; R, ]/ d' P4 A& j/ ]
Out[62]:
( p, s: M8 q& {) \: Q- `7 F2 o 0 11
% c$ B! {0 t1 \8 X K/ p4 y' X dtype: int64
1 v, e4 e6 Q6 O3 Z- H# H2 X
* q9 m5 e7 g8 w" t0 _$ }3 s/ [, q m s.str.rfind('apple')
0 y1 P- y) u; h+ c) M Out[63]: ( Y% i$ e0 j$ v9 L: |8 J, G+ o
0 33
4 R0 l2 }" N8 W dtype: int64
% y5 b7 p$ j, B& g8 x( ? 1) p# ^! }/ q, ]8 X: V ~) a; u# V
2
* }3 V( r5 F9 I+ N& B1 t 3
/ W& v4 L p9 ~; v- w# z 4, u: |8 G, u3 Y5 { X3 w9 X# d/ D: F
57 H9 ~: {; J( d* e( p+ l
6" ]* ?2 ~- Q; p+ R- E) o' {4 ?3 h( V
74 v2 [" [1 o* R/ G( u* h
8
F/ a2 H- K/ y 9# _+ D4 W, O; {7 D9 [7 W
10
4 ?# z( [0 @: M0 K) K 110 e8 V5 I l5 k7 n& x4 A' g, z& Q; o. m
替换( O1 F: D2 b# ?, F8 X! ?
str.replace和replace并不是一个函数,在使用字符串替换时应当使用前者。7 m# h2 S9 @& ?$ O- F/ G" `
s = pd.Series(['a_1_b','c_?'])3 w7 \2 K, J9 m" G7 t% C
# regex默认为True,表示是正则模式,否则第一个参数内容表示是单纯的字符串,也就是匹配字符串\d|\?* W' Z! A% K. X! X- {0 i- I X- ]+ e
s.str.replace('\d|\?', 'new', regex=True) 9 E, a* F( _+ P2 @ i6 }( R2 ]7 M0 O
; Q+ d1 K ?* K$ j0 w
0 a_new_b9 R- m+ y+ t; T# S4 U) @% E
1 c_new6 h X; J$ }/ b. Y; s7 v9 C
dtype: object
& c5 T6 s: m; R9 { 1
+ r9 E& i( {* p7 ^1 s5 g 2
$ e+ b' z1 L1 V O+ f 3
- `8 F/ U# @- j2 j. ]3 R6 E 4
( t8 J9 Q. e& D: ~# R9 X+ n0 Z: ^8 ~ 5; g3 U5 [( G( s4 O/ h
6; F+ ~% [0 Y1 W, L* E( E" l' f
7- [, F, D$ F, V: C
当需要对不同部分进行有差别的替换时,可以利用子组的方法,并且此时可以通过传入自定义的替换函数来分别进行处理,注意group(k)代表匹配到的第k个子组(圆括号之间的内容):' d& e" K& G3 c" `" Y
' b3 @7 K5 V5 p# t% G7 H
s = pd.Series(['上海市黄浦区方浜中路249号',4 k" _8 B, x+ l8 d+ x" P
'上海市宝山区密山路5号',
C7 M. P* E/ o/ q0 i, s '北京市昌平区北农路2号'])% L1 b; o" o7 T$ C
pat = '(\w+市)(\w+区)(\w+路)(\d+号)'% i5 B7 {5 ~/ s2 s9 b) t
city = {'上海市': 'Shanghai', '北京市': 'Beijing'}9 C! _. Z' E. o- n5 ]3 D1 J7 x
district = {'昌平区': 'CP District',
$ }2 R( ?% X5 \ '黄浦区': 'HP District',$ d! z) d. v9 N7 \4 c( V3 S3 V6 L
'宝山区': 'BS District'}
" e4 _# J: l; q7 K0 L9 X: J road = {'方浜中路': 'Mid Fangbin Road',
# l7 E# u4 Q, L) x% b& E7 D* C '密山路': 'Mishan Road',
+ `% X! y+ I# D '北农路': 'Beinong Road'}7 U( Z4 ]* I8 G2 ]! |/ \
def my_func(m):6 u1 Y' s k: d
str_city = city[m.group(1)] F- {# f8 o2 p/ o/ s* H8 D# h% }
str_district = district[m.group(2)]
% {7 y% }" U8 C" h str_road = road[m.group(3)]4 s! u- w9 [/ u' k
str_no = 'No. ' + m.group(4)[:-1]
' u8 C. c! b' K! a- K- x return ' '.join([str_city,
0 m2 h; K& m* p& H! G5 W str_district,
* d" j: K' ?- x& M$ _8 n* C str_road,
+ `7 I( ^! R2 O3 @* n& h e str_no])
$ r9 A' Z+ n5 ^' N% d- A" V s.str.replace(pat, my_func, regex=True)
E2 e% { A( \: o1 z) T 2 P2 I9 g0 [. E
1- g* @; Q- {5 a& e6 t. [
2
; e' g) P5 m# h! c3 K 3% f% S. B L" B: h) q2 j$ t; @1 L4 @
4
* V; Q( I5 T5 s; H; X+ h! ^# Y 5$ ~, u. ~+ `/ W7 ?5 q8 F
6% o4 \ h) h' g, T+ \2 ^/ Y
7
/ n; d7 m" X, r 8
0 N4 K2 F/ o; [% O% N* P8 c- C3 g. ^ 9$ F/ |+ F' e. e8 O# o6 X: E/ R
107 ~: s, g" ?& Z5 K, c$ a4 A: _
118 k. y; G3 _' X4 d: a- T$ C
12
+ o6 G8 l' q5 Y 13
# ~5 M0 e+ x3 v. q: ?' t: f 14% T4 h* t- L2 c* q/ j' _
15
0 S( ?. u8 |1 M7 Y* u7 h 16
8 C# u6 x! ?' h# |. V 17
$ ~ {% n: I* K; T0 x9 n8 r. H 18( X. G& E, p3 i2 t( Q# @" H2 K
19& Z2 H$ U N1 S9 b3 H1 b `
20
. Q% P% q3 N; q. D' R! v% K# a7 n 21% C) x+ U) O2 r v* M' q0 x( u0 F, W
0 Shanghai HP District Mid Fangbin Road No. 249: ~- m) r* S" Z+ J
1 Shanghai BS District Mishan Road No. 5* J6 I' A: C) w3 H
2 Beijing CP District Beinong Road No. 2
, J- M# x6 t N6 u0 R- d dtype: object. \. u, f' r. S+ y
1% U; G' v) @( K+ T0 K* Q0 e
2
2 Z5 B' t! ~7 a+ k$ | 3
& R6 J/ u/ B5 F8 a* @* v 4" p5 z; l- L) S k7 y* p" |* R
这里的数字标识并不直观,可以使用命名子组更加清晰地写出子组代表的含义:4 m0 a f& _" c% t: @
6 {3 j) p# P$ [0 G% e% g, g8 n
# 将各个子组进行命名8 {" j+ w& N2 W
pat = '(?P<市名>\w+市)(?P<区名>\w+区)(?P<路名>\w+路)(?P<编号>\d+号)'
5 B7 u0 s: p/ x def my_func(m):
7 x: k4 ~( \% `+ z9 d str_city = city[m.group('市名')]. c0 o) Z! P6 N
str_district = district[m.group('区名')]( B: M' x X1 a* s0 J/ U' w
str_road = road[m.group('路名')]
) V" w* M' m6 K! h str_no = 'No. ' + m.group('编号')[:-1]
. y( r7 [( J2 K return ' '.join([str_city,9 W. \1 q) O) w1 z
str_district,% b" S" {. h! E/ C( Y& L
str_road,
: Z! Y( h; H9 t+ B. i2 u8 i) B str_no])
/ `: J0 e. n7 v7 t s.str.replace(pat, my_func, regex=True)& [: u2 D6 w& t; [3 l( O
1
* N/ ~1 l# t! x6 p2 }5 U7 I 24 l( ^6 f" w. z$ P0 M8 M7 b6 L
3 C* d& [5 f; f9 x `6 W. w* R3 u
4
: ~( O% h& a9 Y" i# r# k6 v' ? 5
* X% _4 Z, T% x1 f$ j1 i- f( ^ 6
2 r, ~, h, E4 ?. e5 G 7! j( o6 s* k& h! ?$ |
8" i! E$ T3 F' Z; P7 @3 @" C
9
! L, n8 l# o7 g; r6 c 10
) j# Z) T% O: e. ^2 G2 v 118 s$ } K6 k. F: W: v+ H
125 X( Y* y. g0 Z8 B% p$ E, T. Z. l- r+ e
0 Shanghai HP District Mid Fangbin Road No. 249
, a9 J; p/ X5 h' ^. W; Q3 d 1 Shanghai BS District Mishan Road No. 5* c# v7 e8 s1 h' ^% i- t7 F
2 Beijing CP District Beinong Road No. 2
9 F- u4 S& L6 z4 B0 u dtype: object
) n X! I' T; h1 t# {. U 1: U6 x! q( P4 o: @ F1 B
2- N+ ^3 e2 ?8 h w
38 t% K! C5 x5 i$ c2 L
44 E; T: j- N q9 x
这里虽然看起来有些繁杂,但是实际数据处理中对应的替换,一般都会通过代码来获取数据从而构造字典映射,在具体写法上会简洁的多。; v" v* D5 B' \7 Y& i6 h
1 w" A# `! |( V6 `' A
8.3.5 提取3 }- U; P0 J2 q+ L* d* n0 z) ?8 Q
str.extract进行提取:提取既可以认为是一种返回具体元素值(而不是布尔值或元素对应的索引位置)的匹配操作,也可以认为是一种特殊的拆分操作。前面提到的str.split例子中会把分隔符去除,这并不是用户想要的效果,这时候就可以用str.extract进行提取:: o8 R8 w2 j- r% a
s.str.split('[市区路]')
6 n9 R! i2 v: m4 s3 q" X Out[43]: * X2 e+ h2 \9 ~$ T! e$ \
0 [上海, 黄浦, 方浜中, 249号]
6 j' o. {- j* B$ {" o 1 [上海, 宝山, 密山, 5号]
0 V3 m* U* u% d1 U0 g2 T7 V1 I dtype: object
. r$ q5 m: P; b 2 k- ]% n8 Q/ m
pat = '(\w+市)(\w+区)(\w+路)(\d+号)'
$ @1 ]" A) `, A* i n3 H( v: u8 M$ q s.str.extract(pat): z, E# Y! G& j1 S* c" f( Q' l
Out[78]:7 p1 n/ s. g8 v8 `7 @
0 1 2 3, J, C' l) ]) b* P2 V- @
0 上海市 黄浦区 方浜中路 249号# Q6 z" h+ J, u5 R$ b
1 上海市 宝山区 密山路 5号
; t" q( m) T# G' }6 I7 z0 Y. x# P 2 北京市 昌平区 北农路 2号
: a" ?& {2 f6 L 1
5 Q0 M2 t# i: E7 u | 21 t' p; S; p" ~5 m
32 L3 A6 ~- b) i- P n& M
4
' A1 Q( b1 m# n6 J7 p! h' z 5
) t# c, A3 f. A8 b- K3 G, e7 s 6
/ {# T' G, E8 z* X/ o$ W9 w 7
" ?( U% L4 ]' Q: N0 {0 H) r 8/ O6 ~* {2 k ~3 v7 G/ b
95 I# ~2 L1 W2 S5 g# U l: _
103 D8 V$ I( G; b7 d# J2 {. |
11; b& {" Z1 i) P, n# U% q' b: W5 Y
12
2 T# J* w- g7 @% ^ 13
) F; T2 j3 T# L 通过子组的命名,可以直接对新生成DataFrame的列命名:
4 y4 `9 h# _ _7 Q$ x' w ' }& w6 k. X* b, k4 i
pat = '(?P<市名>\w+市)(?P<区名>\w+区)(?P<路名>\w+路)(?P<编号>\d+号)'
- M' e; n( S: o4 N' U s.str.extract(pat)
; S& p1 n! j( U# k* B Out[79]: 3 c* N. |" V6 |9 A4 }6 e
市名 区名 路名 编号
3 T; C8 w. i/ {0 W* i 0 上海市 黄浦区 方浜中路 249号* a9 V3 w$ O7 K7 X/ j$ V% _
1 上海市 宝山区 密山路 5号8 g6 |0 S; i: P
2 北京市 昌平区 北农路 2号" h8 F& @: B$ b( D/ {. g; E
15 C3 l3 @% T: w) B, E
25 K0 N$ a8 D& v8 Q7 S+ I: s) ]
3
# B0 P+ R0 M' u) h8 U* k 4
1 ?7 u* P W W I* Y. b b 5
9 Z* d8 y' N# p% R; } 6' M+ l$ ^. E: j* @* K9 y; z# K/ I
7 j. |6 X8 B2 x9 T7 K
str.extractall:不同于str.extract只匹配一次,它会把所有符合条件的模式全部匹配出来,如果存在多个结果,则以多级索引的方式存储:
7 M4 I" M* @/ `( l+ J8 U2 R( T s = pd.Series(['A135T15,A26S5','B674S2,B25T6'], index = ['my_A','my_B'])
5 N$ d) ^3 w8 u% T. I' X pat = '[A|B](\d+)[T|S](\d+)'# R @3 K, l: b+ b1 |" c$ c( l4 S
s.str.extractall(pat)4 e6 V2 f: ]. d8 U5 a8 A% Q
Out[83]:: {- R0 B3 l7 A8 N/ _1 q. C
0 1+ P0 p, I1 o% K
match 5 C6 _8 }$ d1 _; o+ [
my_A 0 135 15: ?: e& ]+ b5 d# U; s, V9 J2 R
1 26 5
|$ ~2 C: y) J5 S. ] my_B 0 674 2
: v6 k$ V4 s3 @ U+ S& L6 E$ |0 M 1 25 6
^4 i5 L! E) A) J6 p 1. B" S* x6 e2 W
2" ^( o7 s( i! r- p
3
3 N+ R' e$ x4 v( j 49 b( y: Q6 d5 z1 Q6 n. {. u, M9 B
5/ D" J8 R {% i
6
6 l1 E2 A3 |5 @ 75 L8 [2 w, r+ g z, r5 w: w3 q4 }$ k
8& \% n' V7 h' j( p! N: g
9: K' V' {) O6 {% Y/ R
10, w9 O5 v% A* H; J
pat_with_name = '[A|B](?P<name1>\d+)[T|S](?P<name2>\d+)'3 r& A6 A( w% ~- X8 {
s.str.extractall(pat_with_name)
$ C. N4 Q2 c( n6 R. o Out[84]:
2 ~% {9 s) a# _. T4 M7 O! L% Y name1 name2
e5 W5 ~! S/ G% ] match
, _5 Q; ^% m# V my_A 0 135 158 |# u9 z) e: P$ Y
1 26 5+ |8 l% k# v+ _- ?0 c& P5 x
my_B 0 674 2
. r5 x4 T: d7 _+ ?' @ 1 25 6
/ S/ C' M; ^4 o8 t 1
% ]" P- H8 }0 b& g 25 M$ \; p& u( I& W) N/ l4 n, ~! p
3
0 W4 |3 V- v8 X 43 v. x/ ^9 R2 e; I8 S- ]" u9 i
5+ h3 t2 l, S* c9 A3 l1 C
61 Y4 j* G2 d! ^* g% ~$ H
70 O' `7 W* O" t( q! p
86 U; P# R$ w3 w
9+ ]7 \- V7 \; o% ~
str.findall:功能类似于str.extractall,区别在于前者把结果存入列表中,而后者处理为多级索引,每个行只对应一组匹配,而不是把所有匹配组合构成列表。
1 z$ p8 x# s* r$ Q s.str.findall(pat)
; H! `, [& @# ?& f 1 W* g$ [: V6 Y: w) @8 L
my_A [(135, 15), (26, 5)]+ V" n2 `7 R. [% K
my_B [(674, 2), (25, 6)]- ]$ E+ a( l4 k2 ^% w6 B) b/ S- t& ^" @
dtype: object
# B* f0 F& y* E. A0 x- } 1
7 ~" A' r O7 ~ 2
5 P# g2 |2 B) b7 w- | 3
/ a1 ^3 L& g, F2 z 8.4、常用字符串函数! B. ]9 [9 Z6 h1 {. Y. r
除了上述介绍的五类字符串操作有关的函数之外,str对象上还定义了一些实用的其他方法,在此进行介绍。# z: }6 R2 x8 o E' c
1 O' @' z/ T3 U2 [7 ^ 8.4.1 字母型函数
2 n v5 {2 i$ T4 h upper, lower, title, capitalize, swapcase这五个函数主要用于字母的大小写转化,从下面的例子中就容易领会其功能:
1 @. j! }9 C# ]5 g; M' O% }7 |/ _ 8 W% T+ x& I+ |: a4 Z8 ]. _
s = pd.Series(['lower', 'CAPITALS', 'this is a sentence', 'SwApCaSe'])2 t* n7 l( d7 f0 o( D E! I' R, }
" H8 z9 b2 B( v s.str.upper()$ Y- ~) Z! A- x
Out[87]:
, E# y! W/ V/ e- s# x4 ]9 _/ x I 0 LOWER8 E* h# U- J; M) M7 w ~2 S0 z" j
1 CAPITALS
% y6 ]. _8 x5 V4 u! z" d 2 THIS IS A SENTENCE
4 o" m" d7 {1 }% K 3 SWAPCASE
+ T- ?+ \. l/ o- s1 h dtype: object
5 ~ m5 e6 Y) W0 ?4 l * t( l5 z0 `2 U6 k
s.str.lower()7 H! x% m' ^& ^% U9 b
Out[88]: : p4 [5 C. G; z& t
0 lower
+ x* E @' G6 f( `/ g& {& t/ d3 y 1 capitals r9 T, q u y; P
2 this is a sentence
B) R9 }! f6 a4 W4 s d7 J, G. J 3 swapcase$ q; J$ h* {( i/ I4 e' s
dtype: object
. {* i3 ?; _ ]8 h3 t5 j% U" l 7 r: \) k4 H% d6 {% Y. v k
s.str.title() # 首字母大写; q# R" x0 ?9 I: U
Out[89]:
7 H5 a& q& G- ^, H# K5 r 0 Lower
, ~' q. a7 J% B' U7 @/ \ 1 Capitals
) T% `" x+ ] O2 Y1 ~7 k* n 2 This Is A Sentence- Y' Y5 z, t9 @2 d7 i3 _% O3 c
3 Swapcase
: f0 U& \" c# ` dtype: object
6 ~5 h A$ ?; @- Q
- F' X! F7 y2 L s.str.capitalize() # 句首大写
, s& g, ~1 \& X: s! r0 ^& b0 t Out[90]: 6 [+ w& c9 }8 K6 `7 e/ g; W7 v0 h
0 Lower
2 E9 h7 x6 {; ?3 A* e" ?0 { 1 Capitals- t) Z0 I5 } q- e5 h/ z( d3 K
2 This is a sentence& q% ~/ T* Y7 M5 g @7 a
3 Swapcase( O7 {2 C6 W/ B
dtype: object6 s3 U( K. }3 F9 H+ ~ U1 L
8 z, T0 I' j0 y- J0 A4 Q s.str.swapcase() # 将大写转换为小写,将小写转换为大写。
$ u( D4 A, t' ]( i! z Out[91]:
' C* O* b1 C6 c3 |2 Q& P$ f+ h2 b 0 LOWER
8 ^+ [* B2 }" V" N& J 1 capitals
" E4 w% J# \: J" x 2 THIS IS A SENTENCE# i$ _: @6 @* ~
3 sWaPcAsE$ M+ X: f" f9 z1 S3 e; F4 {9 w
dtype: object/ S {- p" P- ~ f$ b( T* s
- C! `% `: g' S3 J2 ~7 d. W: y s.str.casefold() # 去除字符串中所有大小写区别
. ?- [/ `6 E8 ]: t3 `$ z# Q ! m. d, m' k4 H7 H; }4 |5 h' M$ T
0 lower4 I6 o$ H/ R( f% i; D2 A
1 capitals
8 D" x3 m1 o8 L9 W4 H 2 this is a sentence1 Y Z% W( R3 s+ E8 r
3 swapcase
9 Z e. @" s3 s; E5 Y% @( S6 \ 2 m) _, H: G0 [4 e& y. R% P4 |; B' t
1
4 I; C+ o( Y: L) B5 t2 [. Y 2
* R! M) L6 G' b- j1 i6 {6 d 3- `' {; j8 a( |3 T. W
4
9 l- R" |3 C. e 55 ~* _: B% w0 q
6
5 \1 R7 ]' |+ k$ E$ N9 L. \ 7
. |( Q3 a2 d4 M( [* ~ 8
8 x) i1 X- i& `2 V+ S 9+ l' ~& z) l% x' d3 B/ m
10, R# R2 @2 P+ e+ ^/ Z
11
" w/ {9 T4 F$ _0 n+ O! e 12
7 ]( e! I0 P6 T' |/ i 13$ S8 }/ x9 ~) T' Q* h
14
$ M! D8 H4 d' C5 ^4 k 15, M) ^' D6 d h
16) i0 [# Y: S% t3 r6 ?
17
. F& D, ^' v, N$ u% J$ Q 188 D$ l6 g! c6 ?9 T
19
0 P- n7 Q w3 ~$ Z 20: ^; x$ `4 F% r0 r, F
21 h. z2 L) g7 d. G' O
22* l! a; E L! }
23
7 d# j- I! v, m: U/ \% I0 k 24
5 R! Y0 t5 k+ X& d/ n3 b 250 N4 o! M: T: e, A% T
267 y6 I- Q2 F7 C$ x& H
27
; _/ _$ }( N: Q3 }! P4 f 28
8 k$ W! s# p9 h7 y$ G% \% M0 @% v' Q 29) S4 ?5 L, O8 F8 ?3 G
30
: [2 p' L; i9 {8 ~- Q: A$ L 31, L4 Z0 a% M4 D& I. k4 q
32
3 F/ y) k8 G# m- N 33
# N6 O) W8 U5 t: e 341 ~- | ~. \9 b
35
7 w8 o h- L3 g( c* n 361 _1 T5 h8 H7 I$ _
371 i$ ?$ W i, O- d
38
/ v, _/ L7 g4 S) _+ _, q 39* b1 G. c% A$ g" E X2 _* y
40( V' N2 J" {/ k
41# V6 S F( w m: D
42
" D% R+ G" ~; |9 p' K 43* s6 x. c5 i+ G+ q0 U
44
. G0 S8 H N7 s: o& v4 Z2 b 45
* U8 b% c1 D, I& [) Y- Y 465 M: D3 g3 j ^9 E3 ^7 Y
474 `4 B, k; @) f' R" \
48
9 \( V4 x. c) Z- d, l# i 8.4.2 数值型函数2 y, }2 {$ U0 b' `$ U6 l0 [
这里着重需要介绍的是pd.to_numeric方法,它虽然不是str对象上的方法,但是能够对字符格式的数值进行快速转换和筛选。其主要参数包括:
" n3 v. V' j* |' s2 N8 Q% T. ^
- W ^/ d; b, U! }. a errors:非数值的处理模式。对于不能转换为数值的有三种errors选项:' F' k* z. O; w5 n! c9 g; a
raise:直接报错,默认选项
2 }) ~2 |% Y7 S+ q coerce:设为缺失值
! Y/ L- G/ k7 U5 r5 n" c# n ignore:保持原来的字符串。) H/ l d. t3 g+ a* k. Y
downcast:转换类型,转成 ‘integer’, ‘signed’, ‘unsigned’, 或 ‘float’的最小dtype。比如可以转成float32就不会转成float64。
$ y5 |& V' A @* c$ j s = pd.Series(['1', '2.2', '2e', '??', '-2.1', '0'])1 l$ _! b- Y. s5 o$ ~ T
, v4 U* r, r, w8 L6 Y7 K pd.to_numeric(s, errors='ignore')# [3 a- M( K5 X5 p* I" M
Out[93]:
5 s, `7 l7 s! @6 o {8 A" u 0 1
0 Z4 {' G/ `+ d) _! c 1 2.28 m" z" N# N$ W0 w6 C0 \" x8 S
2 2e G) @) N% F- B4 j
3 ??
4 P g! ]' p2 E) U: E: x 4 -2.1* i1 W. T: W: q& ^; z
5 0
5 w& ^& r9 v9 b0 u dtype: object4 |% L5 Q! n& s
# B% [0 ^! H- I9 x+ K( @ pd.to_numeric(s, errors='coerce')
) V% m% E3 g8 h! N: c( j' S Out[94]:
5 V1 [6 l6 n& x; q5 n) z: ? 0 1.0
. Q- m, C& g0 k 1 2.2
, R7 L: k( {; d* F5 E7 N x4 M 2 NaN
$ W' j% C/ H% ~ 3 NaN- A0 ^2 m( G0 c$ ]
4 -2.15 g% A5 L( x! C+ m% ~( [
5 0.0
( ]* t2 t: B) {* C dtype: float64, [2 \# U% N7 u4 R3 k
' _1 i2 p. `: ?. H1 ?
1
$ `" x( ]+ v; X: z) G 20 n+ d# f' Y% P7 i1 i* t
3
) l, f- S' o/ \. v 4" G+ T8 p, n4 ?9 h
5. V7 ~* n2 g& J+ ~
6
. ~ T- \6 X- T4 Z* d& p 7
# k r9 N# s b( `5 R 8$ \1 U1 Q8 n5 C
9
1 F+ {' R( s2 X, \8 |' H6 ~$ I 10
) q9 i+ o. J! W1 Z( f 111 k7 J# P7 K; L' K
12+ `% P$ M& H7 ]6 w( I# Z
13
( `& G( h/ v3 d6 H, b+ ^+ G6 N 141 L5 A2 } }! p6 r
15
' a7 k8 u* \ y9 d% \, N- D' h 16
/ \) M2 p" T9 W 17
8 R$ c0 C( V1 {9 n. o8 N ^ g 18
% E+ P6 `% G4 H: Z9 R3 v( M8 t 19
8 Z3 c0 |+ p" p! G } 20
1 O$ X; T: C5 C3 y' [% g 21
1 n0 A0 y# ]7 H% c! W 在数据清洗时,可以利用coerce的设定,快速查看非数值型的行:
" }( Z# v2 X" i' W * O; I4 F) o& c- [7 u# i
s[pd.to_numeric(s, errors='coerce').isna()]
4 D4 t5 Z, R. X) Y Out[95]:
+ ]- L/ _3 _2 v, E! ? 2 2e
, J+ t. C/ {. y' R 3 ??; `8 f! S$ p7 c. \7 r
dtype: object' U% `3 h/ l- l' ?) m; |, d
1
/ V* h8 @6 j3 q' U% v4 f 2; o, v! t1 x% N" p; a
3
, w) N: s1 M7 A/ J/ C. k1 ` 4' U I; U; K+ U7 m R6 F
5) w- [( `( [% @4 H! h
8.4.3 统计型函数
8 W3 t" y9 P% E3 j2 u0 G/ n count和len的作用分别是返回出现正则模式的次数和字符串的长度:
- _; A8 W: b1 C& L* z" m . H* C, T! B$ u' ?, o- z
s = pd.Series(['cat rat fat at', 'get feed sheet heat'])& _, o2 W- w9 L7 M: a: N. `2 r
4 [5 _0 r" `6 b% o' {/ y, d0 _, ]
s.str.count('[r|f]at|ee') # |左右两种子串都匹配了两次( g) m6 \4 A& g& U8 R; z
Out[97]: 8 g+ X! [; B4 T: ]/ }
0 2
( _3 J4 w* o( P! ~4 O7 q 1 26 u& H. `7 I0 l0 Z- n( g9 |
dtype: int64- L" b. i0 \+ z, L+ t6 e* Z
$ @& M6 _& X" @4 I5 t1 M s.str.len()
# _- W5 u1 }+ f1 e8 |% K" K$ O Out[98]: 4 h, M7 D( l0 } i* X
0 14( B3 y/ ^; G& ?
1 19
1 i8 e1 J5 V u; g dtype: int64* X3 p; a: h+ U; t* W/ G! _, e
10 c: z$ A4 Y; b) r7 c3 `; q
2
1 @/ H2 ~9 I! Z+ @ 32 n2 i! E/ j- |" X; @
4
/ I( Q: N* |5 v7 t( v/ X1 H 5! L/ \0 ]$ `/ m" J; S; q' m
68 l5 {5 W G2 e' U5 z; R$ n4 T" o9 ~. ]0 v
7
- y5 N( M+ u% W$ _; \ 8
z1 I4 w0 ~: x0 U 9
$ A6 d1 ]7 ^) P, a" R, m3 C 10
! i$ C% D3 L& E: t0 N 11
+ q! i; H l5 Y& O. b 12* _% ]+ K ]1 q! q& C' A* `; K
13
; [& {6 _2 ?- i$ G4 k+ G8 i+ W 8.4.4 格式型函数
6 R' p5 m9 C, l0 w/ x% @- C 格式型函数主要分为两类,第一种是除空型,第二种是填充型。其中,第一类函数一共有三种,它们分别是strip, rstrip, lstrip,分别代表去除两侧空格、右侧空格和左侧空格。这些函数在数据清洗时是有用的,特别是列名含有非法空格的时候。
/ k$ v6 E4 i3 O8 Y3 ` * g9 s2 S1 ?: @1 k
my_index = pd.Index([' col1', 'col2 ', ' col3 '])
* G0 I% _+ \7 \! a& u- {+ x
( R# X' h! K* \0 |' X' w, J$ B my_index.str.strip().str.len()! ?$ j' z0 h: g4 P" p+ P8 U& i
Out[100]: Int64Index([4, 4, 4], dtype='int64')
3 Y) x t; o" ], u / a7 F4 B- t1 q$ [6 a7 z% E
my_index.str.rstrip().str.len()9 q5 g% @, M# I6 w7 n& a1 t
Out[101]: Int64Index([5, 4, 5], dtype='int64')1 U& j9 ]5 b. ~% ]
8 J! j) o9 s. u: h& Z9 s my_index.str.lstrip().str.len()& a4 u. v/ B5 p5 l
Out[102]: Int64Index([4, 5, 5], dtype='int64')6 B7 y% l6 v1 M1 I: p) x8 D+ l
1. N- y+ Q1 l7 S6 F! G, Y& g2 ~
2
) L3 D, _/ N P/ v" `& N f9 q4 N0 Z 3
. t: B8 e+ f& _* x 42 s; V2 ^7 k" [9 l4 _
5$ d4 I! X0 A& a" m
6" ?1 Z' @" P+ C4 ^' {
7
8 U( ~! z9 ?; t7 j+ w& V/ q 84 w( h% q9 `: ~) k
9. a1 A& l7 L0 u
10
# g( P# N2 j' h2 t3 r 对于填充型函数而言,pad是最灵活的,它可以选定字符串长度、填充的方向和填充内容:
1 a8 p+ F- x$ p
- c. ~3 e, p6 k. E( r0 K- { s = pd.Series(['a','b','c'])
) a0 W6 v/ e* @1 C- h1 M$ e
7 d6 c, ]1 w& M; [# B s.str.pad(5,'left','*')
. V) [0 q7 H' h) m) K Out[104]:
6 `- o! R$ J7 }* d8 A- s" Q1 `6 `0 ^" p 0 ****a! J( z- x/ n. x- h
1 ****b5 [" E' ^- q/ N( q/ z5 c- N
2 ****c0 h* T. U1 P! J$ D6 F6 y
dtype: object% u) L! d2 \0 [9 {
$ k7 D; I: z1 w1 j5 z. a s.str.pad(5,'right','*')9 I- q2 Q8 l) @+ j2 f
Out[105]:
7 r$ ]6 p% I9 h 0 a****, |% \$ O, R4 _: ?
1 b****: `; B8 g1 Q% d) e
2 c****
) l& Y" O# a: p8 S0 M3 s dtype: object. G7 v: V2 ?2 l7 x, Y2 s: ]6 [
% o+ E v$ Q8 N' f3 C s.str.pad(5,'both','*')
s3 `* C1 C) I Out[106]:
1 H$ k0 ~8 w9 B$ ^7 u 0 **a**% y# R- ^9 k/ B0 q
1 **b**
, Q, s R! o8 g/ Q4 K 2 **c**
$ e- {) {. K* ^/ I7 L) [ R. s. t5 A dtype: object
9 x+ F0 G; R0 Z( x# `
7 Y: e' J# [0 ?0 w2 A U 1* E% B5 w g7 |: U' R1 n' N
24 J$ E4 V7 A- `/ G8 h" }
3
$ H* V1 l2 d* X8 i- [* R+ x, y* ~6 @ 4$ [. R3 K5 q% r W* [: F/ _
58 {0 ~- S% {; I# v& t
6
1 U4 B% R9 S2 G' J, N- P/ E# w/ O0 r 7, D+ X5 V" Z" H! d3 {* V
8' P& b0 Y% ^& M( P! F" Y# y3 @1 ~! D
9
& k6 p# J5 B/ ]5 C5 J 104 H1 C7 n& i* }3 Z8 e$ O
11" y% b; K- z( `+ R3 q4 e/ C0 P! r
12& T; s$ x' L3 `7 j, H& }! f4 {
13
; d+ _% e- E1 S* _4 } 14+ b. ~7 j" k _1 d
15
2 W" w; N7 E/ }; Q! f 16' e; z$ c% Y1 ~* h4 R1 b: H
17: w9 `4 G* q0 K' Q$ Y( i
18
# C3 J9 J1 z( q* T 19
4 p; w/ g3 o" {0 k 20% _; t9 S: `; x; _5 s
21
5 t3 R9 F T6 k3 S4 x0 } 22
4 E' i. [6 B2 N2 U 上述的三种情况可以分别用rjust, ljust, center来等效完成,需要注意ljust是指右侧填充而不是左侧填充:
8 c: X) l3 L! r" U/ A, f* c: b 7 j6 e; S& [: U& f+ E1 k! s3 g
s.str.rjust(5, '*')
; }9 t5 p+ R. { Out[107]:
! n% Z$ i, ^" @! x8 s+ J: t 0 ****a
' [; t9 D: ~) b+ J# G 1 ****b1 O/ ~/ i2 X5 a4 ^
2 ****c
% s5 W$ n0 p% m9 L x0 P) T& P. H dtype: object+ x$ ]* Y% C: H# H6 g& G1 n
0 D' b" ~8 f: T4 u$ L! L
s.str.ljust(5, '*')' b$ C+ v" U$ _3 V! x+ V2 N: m
Out[108]:
a7 Y9 D, v L% B; b 0 a****
& }% {) a" \; J* F$ R 1 b****
; S$ K( Q- C& q3 w5 g3 @0 v 2 c****
8 x \3 `5 ]) m- a( H0 d dtype: object
( E$ `/ C" b6 V. P
( H+ I: i* g/ J$ r$ ]5 X s.str.center(5, '*')% Z9 H q# Y+ ^4 P, v6 Z6 {" a {
Out[109]: # x9 S( l5 u5 _ j5 ]0 K# a
0 **a**
) F; y, F, O" o' }3 V9 D, Y 1 **b**, }: t: w3 i p+ m N9 M$ P
2 **c**1 F1 r' F! M: }& t# n7 L0 M H
dtype: object
/ J! X- [5 w7 v9 n( [. D * @/ }% t W! d/ G+ {5 b4 V1 U1 Z0 i- B
1$ U) v+ b) x. q) Z6 O3 B
2
% }: J4 [4 P2 o, t2 ]( E# E 3
6 D6 @2 [* k) p7 j2 Q 4# }5 w: I. e' Z! P6 g, B
5
' m1 Z5 Z% T# ~2 S% W6 H" v: N 69 O; l. e9 P2 R. M9 h8 N
7
; J, S! d4 d, D- Q$ V 8
: ^7 ~8 j: L/ T3 | 9) g: |5 \( s0 |/ X( ]& _; W
10, n; B0 m% ]/ B+ A. t( c$ n+ g8 E+ }( h
11
: Y* }% _# M$ X. q& y6 T4 h 12- u5 Y& y! X0 F5 k: ~" A( V3 w( J. r' c
13' M' B4 }; L' j0 _/ p9 o( `
14
( _2 e. K4 S+ H8 N' q7 L 15
: O7 W; o7 a8 Z/ _5 q% H 16/ |3 J+ c5 i# f0 [ H; L) `) M
17
0 P& r5 q" Z m. z$ J C 18
$ T, O* h+ U5 h 19
! P% f3 U) E5 Z" n; L! i, A+ A# s" _( p 20
) A7 o8 g* n/ ]( V 在读取excel文件时,经常会出现数字前补0的需求,例如证券代码读入的时候会把"000007"作为数值7来处理,pandas中除了可以使用上面的左侧填充函数进行操作之外,还可用zfill来实现。
( c4 x6 I% J/ U& e# F: b
' m2 p( Y; C5 W9 C! N7 F0 S s = pd.Series([7, 155, 303000]).astype('string')! I! F0 w0 l! T2 Y4 t. G7 O6 Z7 L
! W- x; C# ^7 g+ ^* v6 e7 \
s.str.pad(6,'left','0')
. ~& d! V6 i M3 g: p' Y, M Out[111]:
; n5 O; u9 _; n8 e5 g% f 0 000007" B6 t, J! \* Z( k. x. b
1 000155
; K c* E. O u6 w+ e+ o0 k 2 303000
' D. ~ F1 \$ O/ v. O. v dtype: string, y9 w8 I2 D* _$ s" b" a8 a. R
8 g4 k. j/ L7 o3 C) X4 w
s.str.rjust(6,'0')5 z2 \. E; C7 W2 S* _& Z$ [: K
Out[112]: ' u n: B7 p7 c( t. j0 V* H
0 0000079 @$ ?* @) ~, D/ y
1 0001550 R" G$ e% i! q+ U9 |" n
2 303000
" J: L2 X g- U' t( {6 f dtype: string: W' j4 E+ X: a3 @
$ [/ X# g6 l2 k- i3 S- k
s.str.zfill(6)
# `6 Q" A3 A, ~* ?' H Out[113]: , T( K; i4 G+ y* C0 w8 h. X: Y& H
0 000007
) k m1 k: N$ a) @; F 1 000155) ]; T7 r7 {& h2 ^( B2 B
2 303000
+ l6 `- ?5 N1 S' D7 n: C- M. Y dtype: string3 K1 d. G, u$ R* e
, @+ ?/ n7 A* f$ H) z2 H* H 1
( Y, t: Q6 G" E( x; d, D 2
4 X- E1 _, N, @; X5 h( r 3
$ R* ~6 l7 \/ { 4/ E7 u5 C) v! {1 D: `
57 D4 y, [, s. ^' h
6 V `4 ~1 B: p6 s, i+ Y! ?& B
7) U8 |8 v7 k6 p+ [: K
8# b7 W+ C+ Y/ k1 w
9$ w7 y, U# G. M/ s. P$ k1 u
10
; O8 d8 B0 T# T5 C% j+ y, @2 x 11
8 C+ T! g, N6 h3 b! N 12
6 }6 ]7 e% W. J: Q 132 D P" m1 X5 L0 Q9 w: a( o7 q
144 I1 @% E$ w; W8 o& d6 P6 S
159 X6 ~ g) i9 r# J; O+ F
16; A0 ?$ U0 M! T0 n* s" M* W
17
" O) C! D% A2 l9 D8 W$ i 189 @$ f* x7 ?8 s- s+ R7 a
19$ _$ N* `* n8 z7 ?4 t! ~
20
6 O; S% `1 G3 \& n 21
6 k5 L: t3 b# y: w) _1 O 22& C# P$ ^7 y) ?: a/ j+ n, C
8.5 练习# j9 a" Z7 x& H7 S" e: @6 Z+ I. B
Ex1:房屋信息数据集
* }6 w B8 ~" D& A 现有一份房屋信息数据集如下:
9 w1 X/ k' i" @ 4 l9 _! D& ? N3 V' n( T1 o. y
df = pd.read_excel('../data/house_info.xls', usecols=['floor','year','area','price']) @7 m! y4 V% F" a3 N
df.head(3). Q. i9 B3 B( O* K8 W
Out[115]: . V0 f7 z. ?) H' T; A# v
floor year area price
( q2 H) a0 q- S, U$ c 0 高层(共6层) 1986年建 58.23㎡ 155万( V0 T. E5 i. x2 ]
1 中层(共20层) 2020年建 88㎡ 155万3 `; c: g: {) H& N
2 低层(共28层) 2010年建 89.33㎡ 365万' \& k) H$ A9 m8 P5 t( P
1
8 r) V, a! y5 U$ p$ X 2
' A9 h5 y$ z* b, @ 3
. b" }) _, w/ w6 q 4
7 _& L I" P* @3 P, T4 C2 U 5
H0 b. D( W( g* f9 J 6: E; t8 D7 r9 d1 m
76 L; [ z2 ?2 ? H( [# v
将year列改为整数年份存储。9 C' c5 N. r2 r
将floor列替换为Level, Highest两列,其中的元素分别为string类型的层类别(高层、中层、低层)与整数类型的最高层数。5 t% S2 ^# @/ p* l- I0 h
计算房屋每平米的均价avg_price,以***元/平米的格式存储到表中,其中***为整数
2 Q$ X. S, u- O 将year列改为整数年份存储。
9 P3 L- a0 m7 a0 h% y. T, E& J8 F """" i% O: w) ^! [' C6 n; k
整个序列需要先转成Nullable类型的String类型,取出年份,再将年份转为Int64类型。
# O0 y8 v+ b, G) V( o 注意,转换的类型是Int64不是int,否则报错。即使astype加参数errors='ignore'跳过缺失值,
4 e" c) u/ q, g2 j 转成int后,序列还有缺失值所以,还是变成了object。; R' [; ^4 @9 I3 z, E/ ~6 a9 B
而整个序列转为Int,就还是Int类型,缺失值变成了 pd.NA 。2 |' r8 s. X% M4 ~8 O
"""
. s" [9 a8 W% x2 l% _1 [ df = df.convert_dtypes() s+ p; O8 W1 m, Q
df['year']=df['year'].str.replace('\D','',regex=True).astype('Int64')
7 Q$ ]) h7 U$ ] df.loc[df.year.notna()]['year'].head()1 @" I T V( W {! Z) t
4 c# l; k4 H: q/ M+ ]: Z/ M* @ 0 1986
4 S4 A* }) l- @2 C% a0 g: C# n' r9 m 1 2020
2 ?% A* A; |) B6 @% w% J 2 2010( I/ d2 a4 W' M9 m* H
3 2014
# Z4 J( ?9 x0 K, f9 x a4 v" ]' A 4 20153 o" D. Y& ?$ F$ R" i
Name: year, Length: 12850, dtype: Int64% u& A4 e* B; a8 @' \; w2 G
. Z) @, g- u3 X8 {
18 S& e" q: A0 v9 r8 m+ X K
2
; e7 C2 o0 f( K 3& L. D- |& x0 B3 x* z; y
4
9 B; i4 z) W' U- Z: m 5
2 c' s9 N; g( S) N; t 6
* d, Z2 z% n: p5 p8 X 7
5 o7 M' S( A* p& b/ x7 Z; E) {/ | 8
, A( i3 H* c) l% O 9
9 ^$ W s! {& S$ ?: z 104 @! u9 ?, j u9 x2 a& L( ]; t
11
2 T) i! `& Z9 Y" M! y 122 B( ^+ ?: F) X* i" |. }
13
* O3 V4 ~8 ^% \. `/ j# Y 14
n+ J: l9 ^) A, m' B 15
( J" z" c! U4 N) W 16! n! `& v- {9 K$ m- t
参考答案:
5 B" L$ I* u9 l1 |$ R& d) T# b) i
! z0 _. S) @' U- \4 _8 a1 E2 H 不知道为啥pd.to_numeric(df.year.str[:-2],downcast="integer")类型为float32,不应该是整型么1 r+ T2 T. A1 L \! L' s
3 i1 W/ {7 X8 ] df.year = pd.to_numeric(df.year.str[:-2]).astype('Int64') , b1 b7 F: c: z$ q4 i
df.loc[df.year.notna()]['year']- a+ d5 k# J3 ?3 C4 r) K
1
+ j6 h1 {# `/ V5 C2 n 2
) L! Z( @5 y+ h& H# Z$ b( w$ j 将floor列替换为Level, Highest两列,其中的元素分别为string类型的层类别(高层、中层、低层)与整数类型的最高层数。
$ ^! o: Q" e3 }" ^% j3 Q& e pat = '(?P<Level>\w+层)(?P<Highest>\(\w+层)'
, V K m( Z# Y( x) S df2=df['floor'].str.extract(pat) # 拆分成两列,第二列还是(共6层得形式,所以还的替换一次8 R# J/ L- u. r, |+ V) p
df=pd.concat([df,df2],axis=1).convert_dtypes() # 新增列拼接在后面,再次转为Nullable类型
/ Q5 J3 ?3 B4 `' z% y q1 e7 r7 [9 u df['Highest']=df['Highest'].str.replace('\D+','',regex=True).astype('Int64') 1 `0 r* y% U) Q! M0 K& {$ H" d
df=df[['Level','Highest','year','area','price']]3 {( i {1 f8 T4 y% x( s
df.head()
/ g( T! H7 d2 {, C / ^$ V6 \, R/ u8 S4 v+ ~
Level Highest year area price
/ i2 ]" z2 X ~8 p8 H 0 高层 6 1986 58.23㎡ 155万
/ O+ M7 m9 V2 p/ H1 l 1 中层 20 2020 88㎡ 155万
! T6 ^8 w) A2 ^& F. u 2 低层 28 2010 89.33㎡ 365万
; @. ^0 p: _: A1 R+ f) Y/ q& G 3 低层 20 2014 82㎡ 308万
6 T3 r7 d: m( i 4 高层 1 2015 98㎡ 117万
* C1 u3 r; G6 u- ^" b- L 1
( H8 R: W# i' R( V, P, Z 2
4 X- y" E: Q8 g" X 3" L; ~% f6 \9 [% @- S( X. o7 Y
4
. _% n' ]# h8 F5 }6 I# G. p8 I& V3 N 5
' z% S5 u# I) k0 Z/ d# |; K 6
- v; ?% O4 m l& O7 w' X 7
+ Z! V+ Q0 M1 m5 d 8* a( ^, \5 g$ Q# A
9
" l9 C; u' q) U 10, W% |# j) H; \! g
114 Q/ m# q5 \: ~% ?6 U j6 ~1 ^
127 f/ V4 H0 ?2 V3 O) \( A% O3 \
13% T- G; o: X" n5 `, M# Q4 y8 }6 H
# 参考答案。感觉是第二个字段加了中文的()可以准备匹配出数字,但是不好直接命令子组了 V. D) |. X& `2 n* s u% ~: q
pat = '(\w层)(共(\d+)层)'. r1 E1 c2 b/ B1 ~ ?+ c
new_cols = df.floor.str.extract(pat).rename(# I# L; y& w7 Q( U5 N; y
columns={0:'Level', 1:'Highest'})
- r0 t A2 c0 K& @
0 {. B4 C+ l p& n8 { a i df = pd.concat([df.drop(columns=['floor']), new_cols], 1). P1 |& i5 O& S9 k8 L
df.head(3)
Q* ?+ I, y# A6 w5 P
* c6 L& I5 t& g' _7 }0 G Out[163]:
$ @7 R' V" |/ U* z% j year area price Level Highest3 i8 R& o. z9 \6 E7 D0 p& Z7 F1 w
0 1986 58.23㎡ 155万 高层 6) d8 ]8 Q- s, K W9 B& Q
1 2020 88㎡ 155万 中层 20
3 }" G4 D3 ] `8 H8 S 2 2010 89.33㎡ 365万 低层 28
3 J9 y* \0 B; x# p7 C$ J 1$ r0 N7 Z8 } t" C* j' x
2: `/ `9 u, J, L: }3 Y% k8 l2 J
3
$ b" t4 B# g$ E7 i 4
1 Z: t7 [+ M& g; q2 r7 e 5
- c8 ]7 a, j* k9 ~1 A5 m 63 t! C* L* @- f% u/ R3 h/ ^9 N
7
: v+ C# |( a6 t( ` 8
; C" w4 T4 V$ ^ 9; `9 `7 t' q- o. h
10
' f) o2 A8 }, u% ?6 M. \/ x; B+ ? 11
% T. o0 i; B6 v. S1 |" E 12! P9 A% t: X9 q* c" d
13( ^) T& |9 x5 I
计算房屋每平米的均价avg_price,以***元/平米的格式存储到表中,其中***为整数。 x7 _% R0 ^6 i* J7 _$ Q4 `
"""
* x6 h: v" B% C% ]/ R str.findall返回的结果都是列表,只能用apply取值去掉列表形式
1 b, S" B8 @6 ` 参考答案用pd.to_numeric(df.area.str[:-1])更简洁$ R/ n9 |/ T' B# {0 e1 [( u0 i
由于area和price都没有缺失值,所以可以直接转类型6 v+ S( x/ v C/ u4 m
"""/ P0 i' K0 r, {2 h
df['new_area']=df['area'].str.findall(r'\d+.\d+|\d+').apply(lambda x:float(x[0]))
9 G& b% l- L' ?0 Y; R# v) x0 {+ Q df['new_price']=df['price'].str.replace('\D+','',regex=True).astype('int64'). l# V$ B% @& ^. e# e
df.eval('avg_price=10000*new_price/new_area',inplace=True)
9 S$ }% l. c7 x # 最后均价这一列小数转整型直接用.astype('int')就行,我还准备.apply(lambda x:int(round(x,0)))7 a @7 m: m+ l& [. O. h1 R
# 最后数字+元/平米写法更简单5 g0 F2 y% ~4 w4 A& r+ S: d
df['avg_price']=df['avg_price'].astype('int').astype('string')+'元/平米'% j& c6 m3 O! Y2 z, b' }' R2 @
del df['new_area'],df['new_price']! C n1 D2 k) D. n$ |) [$ K, q8 |
df.head()* {4 Z( n: c. ?$ y
7 M" s6 x: n1 f! \9 j6 M
Level Highest year area price avg_price
- N8 i; `$ I! a% [. c% [ 0 高层 6 1986 58.23㎡ 155万 26618元/平米
$ j8 F: a9 o/ D2 ~ 1 中层 20 2020 88㎡ 155万 17613元/平米 g4 u2 q; D! a# F% N$ ]& h
2 低层 28 2010 89.33㎡ 365万 40859元/平米
# ~. Z5 c* h5 } 3 低层 20 2014 82㎡ 308万 37560元/平米
1 }( a/ P: m1 p1 f 4 高层 1 2015 98㎡ 117万 11938元/平米, T: C2 F9 m/ L2 Q4 m- h2 _1 x
2 m! }$ {; j1 `+ d- s* N, L% J
1
8 ~4 Y5 S/ p( b1 Y' X+ N, A* O 2, N( F6 A" {4 r1 j( g: c3 D1 m: I
3
" Y: ?0 ~/ g' X; q7 d* `" N 4
% w s6 m' q3 a T G 5
! R: Q- C$ f. ?! x 69 y: g. K6 I* ]! i5 N
75 a3 `8 b$ W" M0 T( t$ L
8
/ o0 _- z) F( V/ a, u( C6 n 9
1 F4 A2 w/ ]6 D3 b; E& l/ q 10
: a/ }+ [, ~# _: K- A 11
. p1 t8 p2 y! c6 t9 G! m 12$ O- x" Y _4 z) M5 P
13. L" x8 D, j% I4 u" }
14
" |% p6 o7 d% }% ~! ? 15! F" h, ^, {. B0 E: R
16, j! G8 [- j: O# f2 c
17
$ n- I$ M1 L% |, x( N8 X# ~ 18
6 N4 N% N. T1 F" ]7 y 19, `& w7 y/ D! p) K9 V
20$ h; Z7 J4 w& R1 q
# 参考答案) u. q- x+ U2 P: V/ A, u
s_area = pd.to_numeric(df.area.str[:-1])
" @5 g/ @" Z3 b+ } s_price = pd.to_numeric(df.price.str[:-1])
& T' h& V& O @2 U Z. P% G df['avg_price'] = ((s_price/s_area)*10000).astype($ \2 ^, A5 M# f3 q
'int').astype('string') + '元/平米' g3 P6 M' F4 g- B# e
4 P: O. l# M" u df.head(3)9 x; c! K, S% p" f( y$ E3 L
Out[167]: ! G+ _" }, C' E$ `
year area price Level Highest avg_price
! E/ a/ H: Y+ E, v0 V 0 1986 58.23㎡ 155万 高层 6 26618元/平米
' B6 K2 O0 N# K) ?8 T 1 2020 88㎡ 155万 中层 20 17613元/平米- n7 v* I$ g, Z; _$ t# l
2 2010 89.33㎡ 365万 低层 28 40859元/平米
2 ^% G6 y0 _0 S6 K3 L. `8 S# Z 1
& _ d: f9 ^4 i$ Q 2
) s: a. {& u& i+ f8 Y$ C* t 3
" Y! D& \; e, U 4
" w, l/ c" I- T9 I: ]" c6 j 5
& p! p; |# w# y9 [; K& }& Q 61 G% M* v# k+ F, C
7
$ |7 l* D1 S% z. P$ g4 Y 8, n' e2 }) E( J9 K6 ?7 e
9
& m- g! b. Q5 w( N3 X# o 10
, _6 o6 H* [2 Y, ^7 ]4 ^5 s 11+ F! d3 c8 C) z8 l/ A
12% i5 i; T& X/ R6 t+ B1 q
Ex2:《权力的游戏》剧本数据集
) V0 b- G5 M9 p0 P# o+ G" B 现有一份权力的游戏剧本数据集如下:2 e6 D% u' Q' }; V0 }+ {
& @7 d6 O, J1 P( g! a% I
df = pd.read_csv('../data/script.csv')* R" ^! W0 V; h- W- S
df.head(3)$ g. y/ P& b4 f
2 H* e+ _% L1 n8 \) `6 I7 N
Out[115]:
7 m1 `% ~: n/ D% j Out[117]:
4 |2 N5 n" l8 ? Release Date Season Episode Episode Title Name Sentence
! _: O0 z% [; Y' x) G 0 2011-04-17 Season 1 Episode 1 Winter is Coming waymar royce What do you expect? They're savages. One lot s...
, b2 Q+ ?: e- j 1 2011-04-17 Season 1 Episode 1 Winter is Coming will I've never seen wildlings do a thing like this...
9 ]2 R4 j' J" |' A. n1 d3 K 2 2011-04-17 Season 1 Episode 1 Winter is Coming waymar royce
; u, j* O5 b+ n' P' C1 Y# Z$ m 1& e, b% }6 X4 _7 o/ o
24 C/ {% t' _3 V9 E& P9 p2 E9 U
3; j9 R" P T7 L$ }+ v
4& Q) m: X$ q' z/ I4 r0 K
5
7 Q& [) e5 g. `) {. s 6
+ o" _5 y. L5 L( z) f 7
, E& ?9 N1 }! L# k) w% } 8# o6 w* I/ a" G4 a8 u+ ?& @2 i% `
9
$ j% d0 W/ ?. f. g. g5 l3 @ 计算每一个Episode的台词条数。
+ [7 [( N0 B+ W6 _% ?4 h 以空格为单词的分割符号,请求出单句台词平均单词量最多的前五个人。
7 q" Y g+ A7 j3 X1 R 若某人的台词中含有问号,那么下一个说台词的人即为回答者。若上一人台词中含有 𝑛 个问号,则认为回答者回答了 𝑛 个问题,请求出回答最多问题的前五个人。& H) b) N* f0 ^4 v
计算每一个Episode的台词条数。9 [) O5 A- l( S2 R
df.columns =df.columns.str.strip() # 列名中有空格' |0 C2 Q! P! {2 ?) N, R% H" U! {
df.groupby(['Season','Episode'])['Sentence'].count().sort_values(ascending=False).head()
- Q( n; O6 R9 m, I! m6 Y5 B h + @5 x' L0 Z3 o& i5 A
season Episode
: o' l4 p5 j+ x9 G' i" A/ K Season 7 Episode 5 5056 A% F% {/ y4 N
Season 3 Episode 2 4806 q) {5 U0 {7 ^1 C% Z
Season 4 Episode 1 4755 g( \$ o6 f0 J% L0 b
Season 3 Episode 5 4406 e0 C+ A8 G* @
Season 2 Episode 2 432% l' ?- m* w$ I! H
1
: M2 w5 M8 w+ V* B7 s9 \ 2
9 v e+ {+ N- E: S7 f4 B+ N) d 3
4 r5 u2 R; S: l8 V3 y( Q/ m 4* P( ^2 N( W/ T9 Z1 g/ r
5% y8 d% P% j: ?6 }# N; A
67 y; q1 M, K: o' ~
7
" L1 m8 @- m8 d1 L* Z 8
. g0 y U$ @0 d+ v. X 9
8 L- s% T# M8 v' c; |8 N+ p 以空格为单词的分割符号,请求出单句台词平均单词量最多的前五个人。
6 o) s0 g5 I, W6 X0 p # str.count是可以计算每个字符串被正则匹配了多少次,+1就是单词数
, X; D& P) F$ d5 w df['len_words']=df['Sentence'].str.count(r' ')+1
: w/ V' }) P; v df.groupby(['Name'])['len_words'].mean().sort_values(ascending=False).head()$ n5 k0 R2 g/ E! ?) q+ T+ z+ i
% V4 n0 H0 Q+ U9 J Name
: U) ]. L% a/ t& g _* ~6 g9 n male singer 109.000000
+ w6 W* Z/ {$ u+ r# ` slave owner 77.000000( b7 X* w4 q3 D5 i& i
manderly 62.000000
O# N/ R* V, `! ~# O0 I lollys stokeworth 62.000000; b; P* J4 u( W
dothraki matron 56.666667
* r! {( a5 \, U! A Name: len_words, dtype: float648 q3 k: B( a: u- [
17 n; H0 y+ [% Z7 R/ K H P
2, k8 x5 X. U, T. X- f6 V
36 k1 F+ W/ z: M. |& M
4
* f3 z; c5 N+ U5 h 59 Z; ~( @9 s% A
6
2 O* H: \; o" z+ z/ s; v$ ]% J 77 n4 H% L( ?. A* e
8) [' z f1 S0 @
9
- I; r" B9 Y4 S3 W* ] 10# i; o" l# n$ c/ M* N7 i3 A/ j6 h
11
* F! g! c5 N2 ^' r, n$ v& W 若某人的台词中含有问号,那么下一个说台词的人即为回答者。若上一人台词中含有n nn个问号,则认为回答者回答了n nn个问题,请求出回答最多问题的前五个人。& w! [3 i' g+ M: P8 T; k7 \4 F2 A
df['Sentence'].str.count(r'\?') # 计算每人提问数; |2 [% M" p6 d6 N9 @
ls=pd.concat([pd.Series(0),ls]).reset_index(drop=True)# 首行填0
0 Q5 Y& J* M: s del ls[23911] # 末行删去
/ e3 ~" L* a C- m; V/ P; E df['len_questions']=ls
" O$ ^: m! U1 T `8 U p df.groupby(['Name'])['len_questions'].sum().sort_values(ascending=False).head()% p+ F1 ^. ]/ v; b) u% `3 ?/ l
; \0 }) {% b- O |. A
Name
y6 b! Z: t2 f9 K3 i# m q# a tyrion lannister 527 a/ e9 s' _4 u8 h
jon snow 374, u2 l; }' d0 J- k( k0 `- u4 Q
jaime lannister 283
. ~6 V+ w' G, Q arya stark 265
8 c/ O! ~2 X( ~; F7 B- t+ e/ m& l cersei lannister 246
7 ~& ]! l" |. ^6 w Name: len_questions, dtype: int64
, o- z" _3 K0 Z& }: d, j
$ L/ ~- L6 |& ~& o # 参考答案
* M4 t Y9 H) s, ^2 M+ H' n" p0 [ s = pd.Series(df.Sentence.values, index=df.Name.shift(-1))& ^* k# c; }2 u# I
s.str.count('\?').groupby('Name').sum().sort_values(ascending=False).head()5 m$ B: ?" e7 w& O* D
- {0 s+ c* _) G7 R, u% A 1
D* O) V7 Y* ^2 a) p8 F, n 26 g8 W/ v* T& F/ F \! v: M9 ^% w
32 j$ u* c( V8 ~1 O
4
* l( T8 h: v( c9 x% W& {" r 5
& T% s/ k# i* V! Q2 s7 M( j# ? 6
1 }4 g) c! t6 \# O3 ]( e5 d0 g 78 I3 [$ X# P5 l' Z4 X/ s8 e$ f! J
8
9 B$ N- o* ~! ` 9: e0 g/ ~ h& x+ |
10
. {7 B1 a! F# d 11' @- U+ R& V# c0 G8 S
12) A e L/ c5 m, ~+ Z
13
1 p- T- v! a. r: h" K5 ? 14
2 C( l( j; L- I8 {& t 15
5 z' A8 d0 |, Q 16
- X( u. O0 _* s0 I; K' ]5 B) L 17- f& s* c/ X4 p4 K& P
第九章 分类数据3 h" X' z: y; m2 W) k
import numpy as np
$ m, P! i5 l2 a0 \ ]* ^8 ]6 W import pandas as pd
f/ Z% y% }# z+ q 13 O% |9 [. D% }% p; n
24 Y0 w- O2 c P) w' E
9.1 cat对象
' Y, N1 K+ v" h' [ G 9.1.1 cat对象的属性
; k9 c0 [& B0 o. f- x 在pandas中提供了category类型,使用户能够处理分类类型的变量,将一个普通序列转换成分类变量可以使用astype方法。
; d3 F5 S7 ^% a( e" v2 r9 P : {! ~8 }* R5 C
df = pd.read_csv('data/learn_pandas.csv',2 ]& V( G( z* x' I( I
usecols = ['Grade', 'Name', 'Gender', 'Height', 'Weight'])6 e5 |5 p+ @9 g# J. a
s = df.Grade.astype('category')# J) r+ `7 p2 W0 C
2 k0 ]* @* O7 } s.head()
8 Z" { K- m1 g8 t. b8 a, x d% h Out[5]: / w6 W. B1 ^: |$ k
0 Freshman, O8 w$ k7 S' o6 E( r
1 Freshman( k2 P) U! P) B7 T% N: c3 `
2 Senior
c! H1 t- X* I2 Y 3 Sophomore
: q' x7 i q+ S# H, A 4 Sophomore; Q1 e3 `, m$ L. w4 |6 S* l4 b2 R
Name: Grade, dtype: category
2 v6 |( Y8 R& i$ B Categories (4, object): ['Freshman', 'Junior', 'Senior', 'Sophomore']7 _5 j/ x3 y- J+ O& \
1
5 i! X/ O4 }: O' q" N. W 2
7 @' B* n! d5 N/ f# o! d! @ 3
" W8 k9 j2 _& H% }1 \: I: d 45 D& D" U4 j/ B: T# B' H m& O
5
# d6 D5 M7 B7 N/ @ 6 p0 j" a, g6 ~% z# s8 g6 o
7: j [- t, b' k0 m5 C& z, U
8
2 `# |3 |' ]. Q 91 d' O% Q6 u. Z1 K# K9 A# ~
10
) G" E& u7 H8 z 11- f i. u, ^ W0 ~' k
12
1 a/ s! r# N2 E 13% b1 J) F5 h5 G- _4 y X. w4 F
在一个分类类型的Series中定义了cat对象,它和上一章中介绍的str对象类似,定义了一些属性和方法来进行分类类别的操作。
. W& a# [+ G. [% S7 n1 q( d
$ `( C2 {& f/ k' S# s' U s.cat, a% J# H; h! m* i0 c; {4 a9 M
Out[6]: <pandas.core.arrays.categorical.CategoricalAccessor object at 0x000002B7974C20A0>0 Z0 L" d9 S9 P3 g
1+ A( K) n+ r: a7 g
2$ U, t9 I. {4 a
cat的属性:; X1 e$ E5 j5 L; T6 T) z$ s
/ u: {8 T2 D& {# I* d cat.categories:查看类别的本身,它以Index类型存储6 I N' K( g. x8 X" c- a
cat.ordered:类别是否有序. C* C. g3 d$ k# ^5 v, ]) x
cat.codes:访问类别编号。每一个序列的类别会被赋予唯一的整数编号,它们的编号取决于cat.categories中的顺序
, M' f# b1 T% B3 t9 G s.cat.categories! Q0 s9 U/ \9 W- b4 G
Out[7]: Index(['Freshman', 'Junior', 'Senior', 'Sophomore'], dtype='object')( f2 _$ h8 P; _/ t* h# v* \7 b' t
# ]6 Y# W/ Y& F$ x, {
s.cat.ordered% _- q" U3 a4 K4 H
Out[8]: False( O) F# k: L8 A( \4 m3 h: F/ d" F
: p: @9 h: r& H5 q s.cat.codes.head()
, G2 o! i7 x4 m Out[9]: - p L4 l) \/ a! C# Q/ x' Y
0 0; b! m, M: ]$ _$ v
1 0
/ Q1 f, a" f2 l0 A0 k- V$ P3 a 2 2! `/ |0 f( z% a% a6 T% J* M
3 3
8 t8 e0 L b' T( P- | 4 3; O0 ?3 ?+ @$ W
dtype: int84 \' `* `; S' b2 S+ [3 D3 M
1
% W3 U5 }9 U) _9 G" S 2' L; T. S( Z( z) x) `2 N' q
3
$ X. X% @5 K2 W$ b' k 4; u% Y2 Z! g5 D5 j0 S- s5 r! w
5
2 r0 g8 u+ T+ m) y6 Q" Z 63 d' d( H, `" Q1 k
7
: b* B8 C$ z+ Y2 d# W& L 80 H8 U# t% x% i
9
; o5 m1 k8 }( j- E, {/ N 10
0 R K5 }. s4 ], b$ W* Z9 Y 11
U1 x1 t3 a; u3 S% l+ x 123 Z+ i. @4 g3 A, ?6 d4 ~8 b, x
13
F- B8 a6 d$ {, J: L" ~ 14% Y; s% Q! R* ~
9.1.2 类别的增加、删除和修改
+ X$ t$ }2 r; a8 Q* T7 v1 L 通过cat对象的categories属性能够完成对类别的查询,那么应该如何进行“增改查删”的其他三个操作呢?1 \$ G* G. ^7 t5 ~, T. o
, i) e }# r8 K9 x3 K M
【NOTE】类别不得直接修改9 q& B' _. X! o
在第三章中曾提到,索引 Index 类型是无法用 index_obj[0] = item 来修改的,而 categories 被存储在 Index 中,因此 pandas 在 cat 属性上定义了若干方法来达到相同的目的。5 I: p8 @* M* a# Z9 [
1 P0 ]4 P" G6 u& K5 T/ |. w
add_categories:增加类别. R- i1 y8 i1 R* M" t; \$ N# `( P
s = s.cat.add_categories('Graduate') # 增加一个毕业生类别
2 u) n# C7 j1 W. o s.cat.categories
! v4 _$ \9 R+ G. t8 V! r; f
3 w9 T! {2 o8 G+ E+ E0 q' Y O Index(['Freshman', 'Junior', 'Senior', 'Sophomore', 'Graduate'], dtype='object')
2 x/ q- u, Z# B7 W8 H# t 1% c; W8 n! [7 @
2
3 K; }! q# z, \1 }. ~6 ?0 K 3
1 a* t) q G0 E5 o8 B6 G 40 |2 I8 O1 k' V
remove_categories:删除类别。同时所有原来序列中的该类会被设置为缺失。8 {1 f" L& E" ^% h& I. x! V1 u% p
s = s.cat.remove_categories('Freshman')
8 z1 y% [ n$ s) x% g
& c. P- e* D1 o, f4 l9 _" T, n% A s.cat.categories
* d) J" }! P! J4 {7 l3 j Out[13]: Index(['Junior', 'Senior', 'Sophomore', 'Graduate'], dtype='object')
( j! f+ {- O* B/ J( w0 x* G; y
2 T2 `) J/ x$ m& K# E s.head()
* N P, D$ O: j3 } Out[14]: . H$ t5 C/ q a$ q
0 NaN% k2 @& H0 r- {: k
1 NaN
2 w2 i2 R! N9 e7 H' s' [& ` 2 Senior
/ ]! C0 Q" j) f2 {; b- n 3 Sophomore* ?; G" B7 I& ?9 \5 k
4 Sophomore# S1 p% u4 h) x) I- x
Name: Grade, dtype: category
1 t7 T: F5 S5 j' s# H Categories (4, object): ['Junior', 'Senior', 'Sophomore', 'Graduate']* Q! G# b" G# h
1
2 t3 F# B4 a3 Y/ P) ? Z& a- p; L 2
0 j1 j) M* y& C2 N* }6 ~ Z 3$ x) g% T( f- G7 L) L
4
8 P! e5 t! R s/ I* M6 b* i 5
! z( X3 k3 x5 S) z 6
# J. i5 X* W5 @9 G& V: g 7
" E1 i! ?$ I7 X0 i1 b 8* C( H" y, ? f
9
* K% A* K, i' q, ]: F9 X 10
' ~. R8 y) N* F0 o 11" z+ n2 A+ b! [% S% a7 c2 W
129 s, S; k4 d |/ S+ Y
13
3 _6 a$ }( P! w' M, z. \9 N" B 14
( S' L/ L+ r) s+ r P$ b set_categories:直接设置序列的新类别,原来的类别中如果存在元素不属于新类别,那么会被设置为缺失。相当于索引重设。
% i S @# d$ V; f, E0 a. B8 Y s = s.cat.set_categories(['Sophomore','PhD']) # 新类别为大二学生和博士7 ?- f$ ]3 A$ Y$ k! B
s.cat.categories& [1 ]" N. q; ?& V) p5 z( Z4 ]& [3 @
Out[16]: Index(['Sophomore', 'PhD'], dtype='object')
& m/ r% ?* m: u& @, d) i4 I( g0 L - n5 c ?( x* [2 K5 q6 E$ p: ]. L* @
s.head()
{# F+ n0 u1 A3 T, u) ]3 `2 z Out[17]:
# }/ Y" l5 ?* @3 y' ]5 [' @1 h/ ^, o. X 0 NaN: U# P. m& F4 R$ r: k) }/ f
1 NaN
& N$ B7 V9 s8 D* {* C+ w 2 NaN
* l" Q6 \2 W* r; ? 3 Sophomore, B9 [, J' R: p9 T, ^
4 Sophomore
. a, I2 A9 n3 u3 d$ y$ G Name: Grade, dtype: category& a7 }: C* a3 ~( v/ z; A$ g
Categories (2, object): ['Sophomore', 'PhD']
- ]) `" r. y/ a8 }+ }4 c* i 1/ }6 l- R& t0 D
28 u- F- q$ U( M8 {' ~) r) C
3
8 d, ?0 A* |* h* k: G# @$ y2 w 4
7 E' w, m4 ?8 V( j) ] 5
5 m) j- D0 ?, v7 W, m& v' L- ] 6
& U j, M8 f. O 74 n& h7 T5 Z* g4 l& S0 j
8 I$ O! {* u# n. Y5 h. k
9
/ ^6 Y# P2 B# D9 D 10
: C: g' ^4 r8 z9 \5 O+ I 11$ m3 P0 r' n0 x# l7 M
12
# a' C$ H3 J# G- x0 {9 T z J 138 v& J) T3 j' }4 N1 N+ v/ Y5 a
remove_unused_categories:删除未出现在序列中的类别
4 X9 J( b0 B- _2 a4 \ s = s.cat.remove_unused_categories() # 移除了未出现的博士生类别
0 G4 ?' p! x9 z5 H s.cat.categories" ^3 d9 G% z9 P- y" R' ^
, Q" D. f, ]! f# `& V/ F. Q
Index(['Sophomore'], dtype='object')
3 [4 ^/ F8 R: g- C- c: c# m( w# f 1
4 h7 C- p0 b5 Z: K4 W2 { 2
9 l1 Y* M- j8 x: p( t; N% f c 3% D) B; N. b* F' N$ A+ c
42 R% m$ d( T2 _& {
rename_categories:修改序列的类别。注意,这个方法会对原序列的对应值也进行相应修改。例如,现在把Sophomore改成中文的本科二年级学生:& E1 X& r; f7 C2 Z
s = s.cat.rename_categories({'Sophomore':'本科二年级学生'})9 D; e. n- t# f- K: q' j
s.head()3 o3 ]3 u$ D, |* |/ M1 L8 Z
5 V* T% P( g) p4 Y2 \7 V
0 NaN7 O6 @$ k1 X2 n4 Q2 u9 L$ K# S/ K: u
1 NaN* z' }# l) h0 Q4 G! k, d4 \
2 NaN+ f, T9 w g$ e0 f. Z. i6 Q! `
3 本科二年级学生
, [' u/ c% b2 g6 N 4 本科二年级学生
+ l Y3 l! @8 R. }" Y Name: Grade, dtype: category9 }8 G( n' x' h8 u1 U5 N' g
Categories (1, object): ['本科二年级学生']
- w( e: C4 E/ G0 d) j# @( J 1
" r& |1 H& X N( t: X1 f$ n; w 2
# f' E- E+ Q: C5 e) Z7 } 3
) k7 R6 i. Q$ f& W1 a; J 4
: d0 q4 O! K1 i1 }5 L k 5. I2 x. p( f, o! |: E1 S
6
+ O! p# J& V* ^- N$ v- w 7
4 c3 x% ^: p" E0 [ D. h+ [: p 86 P9 I5 o/ \! ?# [+ c4 E6 u: c
98 w, b5 _0 V7 C5 R# r9 W9 U
10! G' y! w* _3 n$ T1 ^7 j, I) U
9.2 有序分类8 z8 @0 C! H# P6 F9 G2 D+ x m
9.2.1 序的建立6 ]/ ]9 G b7 {! H/ t5 J9 ^
有序类别和无序类别可以通过as_unordered和reorder_categories互相转化。reorder_categories传入的参数必须是由当前序列的无序类别构成的列表,不能够新增或减少原先的类别,且必须指定参数ordered=True,否则方法无效。例如,对年级高低进行相对大小的类别划分,然后再恢复无序状态:$ w) d+ n- }+ Y3 { M; {
2 f$ v$ m, E& r0 |5 b1 y4 O s = df.Grade.astype('category') c, y: N1 ]6 N3 X4 e
s = s.cat.reorder_categories(['Freshman', 'Sophomore',4 D0 P$ i& V9 p) c; M( B
'Junior', 'Senior'],ordered=True)
* l- H6 Q4 Z5 M. D s.head()
) J8 F o, J* i3 d Out[24]:
2 \, S6 R9 ]$ n- M; B2 ?: m9 ]$ g 0 Freshman9 F3 {' f7 z8 y1 U$ S4 I
1 Freshman6 _- n O \% S+ w, k
2 Senior
3 {6 x# e5 y/ |+ i0 k- w# x7 Q0 E, ] 3 Sophomore
/ F. U6 W: S P$ N; |3 X 4 Sophomore
* _2 |! `6 Z3 a4 D& N2 A Name: Grade, dtype: category; J9 t- _, o, _
Categories (4, object): ['Freshman' < 'Sophomore' < 'Junior' < 'Senior']
' p T( T/ H' k9 b
- m3 t; i! Q! F" Y4 c" I s.cat.as_unordered().head()0 g9 j2 T9 y" s0 P$ I: Y X8 M
Out[25]: : y, p: w1 g' I) s1 f+ T
0 Freshman# _6 b, O. L: ~
1 Freshman
8 S- N! A3 y) n1 d: n 2 Senior
- J8 @/ Q" {, O. c; w5 \ 3 Sophomore
/ U. X j/ x3 ^2 { z: V 4 Sophomore
; O' ]( j, d% X; K) z Name: Grade, dtype: category
/ R) }7 j/ h4 \8 z y Categories (4, object): ['Freshman', 'Sophomore', 'Junior', 'Senior']
$ d# z2 J; O9 _5 i6 L# {& c 1 O, L7 h' p$ S6 P4 U
1" S j" y. @/ G7 H
28 e' L: s/ M! A
3. k2 q+ @ J/ D& M. h
4: W& e! b4 f V- V" v1 y
58 b4 B& }4 J0 n' e
6- ?" \ ^) x* X( Q
7
( L+ a0 _/ q+ Y* a, M0 v) M/ @ 8
% N! f: U2 p: z* M- m% v% j' R3 L 9) o3 w9 Y$ X3 r! {0 F2 F/ `
10
@' K) y* b; {. r. K 11
7 Y- d. E/ Z+ G4 i0 k/ H7 A) \ 125 D& u6 r! N. B* M# n9 u2 ^% X
13
+ `5 g7 k- l6 ]% w+ ~# x" Z 14
3 F/ }: \6 S0 t: F# | 15' }4 t6 R: [& T: n1 @
16$ A8 W$ M6 T% \3 I- d( i/ }
17
" C& u x9 u; k3 n5 c9 @% M 18
! o w q- M9 L; p' I/ b, S 19
! ?7 Z$ ~6 r) F" Y6 b" d3 e2 D; a4 | 20/ J* A5 n0 B3 u. ~& A8 C: D" O7 q
21
6 D1 n- O' E- J8 k2 V/ Q 22; d0 Y$ E; y. v) T+ G9 C( m
如果不想指定ordered=True参数,那么可以先用s.cat.as_ordered()转化为有序类别,再利用reorder_categories进行具体的相对大小调整。
2 J( l) H Q* P6 r5 v
" A6 D: v3 s9 d; d/ B4 a$ v 9.2.2 排序和比较# ~6 Z9 A7 y6 C
在第二章中,曾提到了字符串和数值类型序列的排序。前者按照字母顺序排序,后者按照数值大小排序。
' h) o* q. @2 E/ b4 ~1 s: j + F" u M, J; E/ J
分类变量排序,只需把列的类型修改为category后,再赋予相应的大小关系,就能正常地使用sort_index和sort_values。例如,对年级进行排序:6 R" }. ^0 I5 x, P
& j. O" {4 J& U
df.Grade = df.Grade.astype('category')* @6 |" M5 N' ]. ]. s: L/ N/ ^) ~) y, S
df.Grade = df.Grade.cat.reorder_categories(['Freshman', 'Sophomore', 'Junior', 'Senior'],ordered=True)
2 E; c) y9 q1 P! E* o- K df.sort_values('Grade').head() # 值排序' F0 L8 ?: X# f; N, J- V# {& V
Out[28]:
6 D+ H" i0 U: y$ X. d) ]: B Grade Name Gender Height Weight5 a" y4 k( ^0 H) M: W2 T& Q
0 Freshman Gaopeng Yang Female 158.9 46.0' t/ E: d5 W. l/ y( @9 Z2 A& Y' Y
105 Freshman Qiang Shi Female 164.5 52.0# _6 j! }) S7 Y( S( n# F9 p
96 Freshman Changmei Feng Female 163.8 56.04 {8 ]; w" h, Z/ w6 Z% u" e& X
88 Freshman Xiaopeng Han Female 164.1 53.08 {0 _( _" ~! |" ]& J: N+ n9 s
81 Freshman Yanli Zhang Female 165.1 52.0
! n- o- o& m. k7 R# d( d" J * C* y2 W4 y8 D, b2 D
df.set_index('Grade').sort_index().head() # 索引排序
2 Y" x Q0 e4 c4 ?9 c: m Out[29]: & ]& V) I* z9 \
Name Gender Height Weight
: D) m4 C& a; f Grade * q/ Q" d# d5 \2 @1 w& S3 n
Freshman Gaopeng Yang Female 158.9 46.0
# x! x; T3 X* z Freshman Qiang Shi Female 164.5 52.0
. G' k; E) n; f. p Freshman Changmei Feng Female 163.8 56.0
; ^% }& G. Z1 `6 V/ H# J Freshman Xiaopeng Han Female 164.1 53.0
* N$ P# C: p4 L! r0 { Freshman Yanli Zhang Female 165.1 52.0
J( A5 H6 ~$ k0 e1 O( g5 J. B
2 }# |+ l, E+ ?1 \. l/ p 1" L9 Z9 S6 C: Y% }5 x' H3 l
2
, i0 B# }; \" I 3! y' M9 c2 U( k/ l) n
4
$ m' z6 x+ V$ T7 l 54 P7 x! a5 T9 n1 i+ k3 S0 d
6+ P3 ^ x% ?. j; [4 }5 t6 E
7 ~; z5 {9 o- M3 `8 o0 i
8. X/ t) `+ g( H2 u H( P: j
9
* \) ^* J" t7 f5 c: H 10
. H/ W ^ }+ m8 [$ \; j6 s* Y v! _ 11
* b/ E K& q* F2 B( m/ t 12" ^, J4 m* n0 ]8 _& [4 v
13) U/ l( v* O/ k6 N
14& |$ \: r b& _' u% C, p% V; n
154 D& `, c7 V1 y! y
16
0 T! j1 N6 n3 _& \ 17
/ ^$ `/ \& I' e0 T, C+ [7 S 184 D1 B8 W$ ]* n# e" h. i
197 z4 k9 S! X' {$ e
208 M$ S* C# q* ~
由于序的建立,因此就可以进行比较操作,方便后续索引操作。分类变量的比较操作分为两类:$ e1 I# _, w5 i: C7 U
1 I& w$ R4 l5 v5 ]) d) A4 d0 Z
==或!=关系的比较,比较的对象可以是标量或者同长度的Series(或list)。(无序时也可以比较)
* L3 B# a* M" n: d- X5 Q% U >,>=,<,<=四类大小关系的比较,比较的对象和第一种类似,但是所有参与比较的元素必须属于原序列的categories,同时要和原序列具有相同的索引。
3 h) C+ X! H/ X res1 = df.Grade == 'Sophomore'; E9 ~+ W* f4 z; M& G# E4 W
/ Y: H$ z- p# }! O res1.head()
$ @, v& D: G3 A' m) I- n! l, } Out[31]: ) R2 i- n* C" ^2 ^* P& e
0 False
+ X: Y& w4 c7 D# }( i+ @ 1 False
. O% Y! K$ s* h* _' S 2 False! H: h6 w6 |2 \1 U; \8 k" x
3 True
Y. m; ^6 d4 j* X% { h 4 True
" I& w3 s# ^/ C( G Name: Grade, dtype: bool
0 R7 s5 n2 F! e" w9 j' H' O) e3 E! H
! u4 G+ k& H* ^( ?" [ res2 = df.Grade == ['PhD']*df.shape[0]+ g' X0 B* B4 z
y% a1 W0 S6 \; d1 P' i4 f6 @ res2.head() J" a0 t, T# B& l
Out[33]: + s# r5 E+ }% l
0 False+ h6 C ~4 N* F3 Z/ @- h
1 False
; N8 I5 ]8 F6 A7 O# y: @ o9 H' F) r: ~ 2 False8 Q) X a: J; E+ @0 c! [
3 False
0 O+ w c) \ Z" R2 V; E$ Q 4 False
; }8 a& e T) ` Name: Grade, dtype: bool
$ I& R' I+ w- z1 P, ]' I& p 3 a; m0 _! b3 {0 [2 \
res3 = df.Grade <= 'Sophomore'
9 G3 ?4 l0 ~; N c& k3 Q( v 5 `) N& d$ m4 |5 A, ?$ I3 }! G
res3.head()+ _4 E# ^' R0 ^8 a0 E% w! y' S
Out[35]:
0 z2 W- P' [6 m, i/ m, ` 0 True3 G; J7 R! P; p2 v
1 True
8 y( i$ b( {3 j) ~ 2 False2 z" j/ ?* U- t7 l" D
3 True
5 O! b; h" e# s* j" S, y# T( j 4 True
" W+ E9 b$ I/ a" x$ S& x3 a Name: Grade, dtype: bool8 p& i9 J& n7 e& p( j- y/ S6 Z
/ v7 e+ r, u+ U2 A: Z# r9 N # sample(frac=1)表示将序列随机打乱。打乱之后索引也是乱序的,直接比较会出错,必须重置索引。5 ^! r2 {6 @+ s0 Q# M S* a
res4 = df.Grade <= df.Grade.sample(frac=1).reset_index(drop=True) 6 ^" M7 q! u1 V2 R* w' T4 R
; I' |6 S+ u$ o# m5 o res4.head(), f3 ^' _3 L2 F: }8 k
Out[37]: ( I# K2 [! e, c9 |# x# p
0 True
0 ^& \6 r- A& S/ R6 e& X8 t( T 1 True
/ i: t. ?1 w* ~+ V( a 2 False( V# d( w' }% K$ s
3 True
" m( J# V) o! }5 J; X 4 True6 c- p; g5 T5 v# F- \0 v
Name: Grade, dtype: bool. Z8 {( d& Q; `& G" a% D' |3 u
* y; P3 o+ k. d. P
1: M0 T/ |/ X) ]3 X% l: x, C
2
* ~% ^( S- t# C& } 3
; {4 C* _, ^ N( v$ D5 U. E 4
. V, g+ \% X7 K7 V/ g; ^1 C 5
5 p. R3 X2 b5 p+ ~ 6
/ u" H1 ~- B' S1 h0 @ 7, O$ |/ j$ D# V& u. v' ~0 R
8. O& r- U7 y: L# v0 O+ e
9
: |' X- g7 ]. c1 f, c$ w3 w/ a* A 10
, s' {: W. W1 L: ~ T- e 11- w: |7 h: y) O2 X
12
! `+ j$ ?1 ~' D7 H* z 13
[+ z, ?& }& F6 } 146 S- E0 z# `% \! ?* _: n0 y2 k
15' X9 s+ x. [/ q) J9 E7 l
164 U4 c* E: N6 \$ @3 f) s6 Y
17$ p: m/ k5 `! n' E! ?
18/ G6 ~, r. f8 z
19
1 y$ d- M% k, ]7 m 201 f5 v6 {3 P7 ~5 c& e p) o* r% v
21
4 k0 J8 M# c* o9 l 22
( j% x1 p/ O8 ~" M& G# a5 s 231 c/ Y0 o6 s- O$ K1 I
24' u R5 m4 L u- V w% F7 `6 X
251 E* L9 B6 ~2 P
26
6 J: s, C- b- _/ J: u 27' [+ {) P. `1 o0 y
28
" B1 y# E/ a- E 296 w" B4 ]2 z& W8 @" z+ x- G
30
, K% r: Z, T0 H. n 316 E- u; c. p% D' S# U( {
32
, X/ b0 W% a( p$ Z# \ 33
O7 k5 D, {+ B; W7 Z. K& J9 T 34+ k$ ?2 }) B3 L: b- q/ x
353 m% c# a1 V4 }* w$ ~/ C5 q) N
36% l9 w2 a' S/ S
37' h6 K+ m( W; U2 G
385 q9 P- }, T8 l3 E8 O- A; e
39
2 p7 Z: {, k0 P; U 40/ r2 I* k3 j. B5 |. n/ Z6 C7 n
41, M, }1 Q# `2 d2 m/ f) J9 Z
42
+ s- E$ ]) m# k, H 43
8 n6 x8 R6 w* n/ p. r/ @8 i 44
5 T& \6 g! {. Z# z/ S 9.3 区间类别5 y+ N5 H3 K; W$ @; n: V8 j; E
9.3.1 利用cut和qcut进行区间构造
5 K* w1 u9 \4 ~! ~" B9 I 区间是一种特殊的类别,在实际数据分析中,区间序列往往是通过cut和qcut方法进行构造的,这两个函数能够把原序列的数值特征进行装箱,即用区间位置来代替原来的具体数值。' F* X3 w- h* S4 K, m
* U/ t1 z/ F" N% M cut函数常用参数有:
( Z- d$ a; e) s- \, O, ?- P5 U& q bins:最重要的参数。
3 P: c, [, D- _# i+ A 如果传入整数n,则表示把整个传入数组按照最大和最小值等间距地分为n段。默认right=True,即区间是左开右闭,需要在调整时把最小值包含进去。(在pandas中的解决方案是在值最小的区间左端点再减去0.001*(max-min)。)9 `1 D7 U# b/ r
也可以传入列表,表示按指定区间分割点分割。* K. D- U: A) A. y! S5 O0 C
如果对序列[1,2]划分为2个箱子时,第一个箱子的范围(0.999,1.5],第二个箱子的范围是(1.5,2]。
+ ]0 r1 }2 }6 p$ f0 w3 G$ j3 M, o 如果需要指定区间为左闭右开,需要把right参数设置为False,相应的区间调整方法是在值最大的区间右端点再加上0.001*(max-min)。
, a O4 a) U/ P7 ]5 I ( r1 a u( O' @3 D+ i/ B( y
s = pd.Series([1,2])# h7 O9 ^, f1 e9 p8 t3 m
# bin传入整数
- P4 l+ V! G% ^/ o9 A8 F6 q
/ g& n% L3 V9 B pd.cut(s, bins=2)* W( q4 C5 Y) h* N
Out[39]: - d! x+ z S. j! I
0 (0.999, 1.5]1 J( J2 r% S( I- z) c
1 (1.5, 2.0]
" g$ ?" L0 n" V' S dtype: category
1 K$ C4 y0 o7 G, L8 b$ `& `3 h Categories (2, interval[float64]): [(0.999, 1.5] < (1.5, 2.0]]; u ?7 i! ^* q- _
( N5 X# _3 m) C" O) n1 J+ i' {
pd.cut(s, bins=2, right=False)' a' K8 A; ~0 i! o7 o0 Q& x( ^
Out[40]:
7 a" o) b* z! D+ U! ` 0 [1.0, 1.5)1 S6 l7 B( f' d
1 [1.5, 2.001)+ }2 r& o1 P4 m
dtype: category1 f1 G" D, q9 s/ \
Categories (2, interval[float64]): [[1.0, 1.5) < [1.5, 2.001)]
2 Z1 X2 J1 W% ?" b/ {. E. R) K r
2 t5 n: D/ m. e5 [1 R x+ ` 8 Q/ R0 N9 o4 F y& D7 v/ }, w# [
# bin传入分割点列表(使用`np.infty`可以表示无穷大):% H7 Y4 |# Z! l6 q( j% q
pd.cut(s, bins=[-np.infty, 1.2, 1.8, 2.2, np.infty])
/ H; U5 c- [) E6 o( ?6 i+ F Out[41]: 6 N/ f, K, O" O u& W# Z
0 (-inf, 1.2]
' O. d9 ?0 W; r2 K4 U 1 (1.8, 2.2]
& d5 J8 [6 H1 ]) k- ? dtype: category
2 y$ }1 R2 `& ?2 ^ Y# W( v8 ? t Categories (4, interval[float64]): [(-inf, 1.2] < (1.2, 1.8] < (1.8, 2.2] < (2.2, inf]]
) w4 L: ]( x f& u! t 5 b: E% F0 y& X# Y9 V
1/ X% z# f- t. g3 m1 A
2
( X+ M5 ]8 Y( {- o% n- g 3
! e7 d2 H- V- i# `) ]" K 4
$ f6 W- _" A0 T 5 ~3 y$ @$ e1 U6 H( g# \4 R1 p0 Y
62 a7 W) V! h' J; l' ^$ x
7
1 S" L( U {: O+ ` 8
m8 v! j" i% [; J" j: x 9
) c3 Q5 _: S5 {4 w' h& U/ R' ]& E2 p- F, Z 10" y2 r7 \; V5 L6 O% N
118 G5 V' P! f0 q' i; m" q* [
122 Q$ E4 L* p# ^ x
13- p, V. R y' U, y" q- Y
14$ l) Y3 s7 U% N8 i2 }
15# T: \6 N( u" ]' g1 K( ?+ s/ ~3 b$ H3 K# d" G
16
Y) t2 E+ N- n 17
: \5 D8 Z. f% s c# i' P 18
- H" Z* I" \ t5 j! _8 t1 Y 191 @6 J1 R" f0 @" ?$ f
20
6 b; }3 M) A, p& H; ?- {( B 212 a3 R0 M# {* K+ Y7 n ~- N4 H n
22
$ T: N$ Z& G/ G J1 B+ h 23# Y7 w. k. ]& J5 D. P6 b
24
- @3 F1 }; X% o9 W f 25
' m" Y) a+ @+ F3 O9 }7 u2 q labels:区间的名字
! |$ k; a; R3 U( H' _/ f3 a retbins:是否返回分割点(默认不返回)
# F: k+ k' b' S. t, p' I9 | 默认retbins=Flase时,返回每个元素所属区间的列表: U: J6 w j ]& F; _
retbins=True时,返回的是元组,两个元素分别是元素所属区间和分割点。所属区间可再次用索引取值) L; O6 B& s, `' A, _
+ z; \( M- U2 u, ~4 u% V# q s = df.Weight% l* j/ R4 I8 I& E7 L9 x2 j
res = pd.cut(s, bins=3, labels=['small', 'mid','big'],retbins=True)
2 ~; o$ o. Z+ x) q7 t res[0][:2]
3 n) h$ H X f' ] D3 {# g) Q. ?- ~6 f$ _- d: ~
Out[44]: 3 b3 j" n$ z/ R% o
0 small' T- }$ S) L, }# k2 X2 [
1 big
' K- _0 b/ g# @ x3 H dtype: category
B0 H; Y7 Z) v. q# H Categories (2, object): ['small' < 'big']
H4 n v* T X ; m: q" ]" C3 k/ y. E Q9 |, U
res[1] # 该元素为返回的分割点% R' j. S4 h- e3 H9 L' d, K; k
Out[45]: array([0.999, 1.5 , 2. ]) ^( v' \5 r/ p9 `
1
# E! q/ \+ i' v& X. s+ t" p8 |9 ~$ g 2
1 X/ d+ n! ~2 S0 R$ ` 3
" [4 k& o& i6 t( @+ t 45 g9 \7 s5 f7 F
5
4 v* T; K9 v2 c 6
% x" y* Q" Q6 U& u; y+ `% S( a, P" ?$ p 72 C( z' }, n" s; b, a
8
. j* }: K( p0 q7 h9 c 92 a0 ~3 W) ^3 ^+ o
10% X) n2 o! l. i: H. N
116 V; m& o0 S9 K7 x# m
12
- p0 O# h4 z0 d) O qcut函数。其用法cut几乎没有差别,只是把bins参数变成q参数(quantile)。
$ D/ n8 j% L( t q为整数n时,指按照n等分位数把数据分箱( T% g: a. V5 o( i
q为浮点列表时,表示相应的分位数分割点。
. X: C3 V: m- E4 Q s = df.Weight0 P* J: C4 J4 h
/ x6 p j* _9 z2 Y( }7 D
pd.qcut(s, q=3).head()9 B9 x/ @' O P5 K7 ?) K5 L N& c
Out[47]:
8 q- B- Y: W6 i4 U% [; M) I 0 (33.999, 48.0]" y4 I+ R0 L# R9 E; p
1 (55.0, 89.0]& L; c2 ?8 V" W, K1 y
2 (55.0, 89.0]4 M, }; Y5 p! a) }, I) \, ?
3 (33.999, 48.0]# T0 \) o! P9 K( w
4 (55.0, 89.0]% E% {( X- ]' K; ]! Z8 |' u$ ?
Name: Weight, dtype: category' i* F4 r. d' N h
Categories (3, interval[float64]): [(33.999, 48.0] < (48.0, 55.0] < (55.0, 89.0]]' n1 ~$ H- C6 L. M6 L: Z
9 B4 Y* F6 G' P! r9 t4 z: N& z pd.qcut(s, q=[0,0.2,0.8,1]).head()
* u, b8 {) C9 x5 ? Out[48]:
/ u; G' z" N, _# U' L4 ~, e1 d 0 (44.0, 69.4]
5 H/ H& b& b4 r& j. T( B& ` 1 (69.4, 89.0]
# m+ X: P) Z' a) m; Q 2 (69.4, 89.0]- z" C) D/ S/ W7 H+ r( J
3 (33.999, 44.0]
( T$ ?. V$ @! J* n: l 4 (69.4, 89.0]
% ?8 K5 ?6 D1 v& S0 d- x% l( Z Name: Weight, dtype: category
7 z! R6 Y* P( J" N Categories (3, interval[float64]): [(33.999, 44.0] < (44.0, 69.4] < (69.4, 89.0]]" q; i" |# v3 q) j7 R* E- \) d
0 Q0 U6 u, w$ M3 ~$ h( Q. Q$ s/ m) S7 p
1+ e& W. W+ k, }7 I$ I
2
2 f" O% I8 t, R" k* F 3
3 K7 `0 e+ b8 P2 e: i 4) `) o: X1 Q5 G* L2 h7 }4 X
5
D8 R" b h) E' l 6) I" a. Z( F0 Q; M2 S/ g, T ^$ G6 {
7
% O& a" o @' v& W! j7 p 8/ t. Z. k' p p4 @! ^; a
95 S7 w2 L, U8 c3 b: E- o; T+ ]9 H
10
1 A2 M p& O6 c! ] 11
. q4 n7 k: s, n8 k; a 12# K' @, J9 B& d* _$ G
131 [ Q( _$ G. k+ L
14
7 h1 i4 i9 J& O 15+ v+ w2 i5 a8 W l6 d. f
161 i' M9 r" U5 X! E& N V8 @) {$ T2 A
17
1 d- ^% d, e, A& Q 18
( X( J& W; R, a! n 19# A( \8 o/ G- i3 ^
20
) |) m- b+ f/ f. ~/ L( B( l 21
: q- S. Z4 V3 p2 \% B 9.3.2 一般区间的构造" b) W; A* F* ]4 ]) F/ i
pandas的单个区间用Interval表示,对于某一个具体的区间而言,其具备三个要素,即左端点、右端点和端点的开闭状态。
8 S' `5 d& x! P- M4 X, H
. B2 M/ N! h8 l7 w7 L% y' t: ? 开闭状态:包含四种,即right(左开右闭), left(左闭右开), both(两边都闭), neither(两边都开)。 M* s; ]8 d+ X8 A
my_interval = pd.Interval(0, 1, 'right'): S& v; v' T# ?/ W E- P
0 Z* E/ C0 _& m, L0 ~& K$ t( |
my_interval
8 B+ }6 J( [' i% [ Out[50]: Interval(0, 1, closed='right')% M2 r/ V# D; S2 U- J& w+ \% G
1, {! \+ y" |' N: ^ M; U" b
2
, E8 R' e( R1 H+ k 35 k* t/ |* o3 |+ b( |9 e
4! x8 y% t ^" Z3 ^! B) S
区间属性:包含left,mid,right,length,closed,,分别表示左中右端点、长度和开闭状态。& V& i7 u0 q( M1 ~' [, e8 i- ]
使用in可以判断元素是否属于区间/ Y* O3 u, C# K! Z0 K4 ~+ c
用overlaps可以判断两个区间是否有交集:
+ W5 F( P( q3 d6 W# @* s$ n- i% q 0.5 in my_interval) s' G2 ]# u& z- r
. W, y7 f3 Q/ N* k5 ~: F# F4 Y$ J True$ C) @( M* q3 R% _3 k) c) N
1# j6 {2 b! _ X* @% b
2, Y- |' h* Q8 z% R
38 q* O+ J% u) c: P3 k# O
my_interval_2 = pd.Interval(0.5, 1.5, 'left'); {% |# ~: [( N# ^
my_interval.overlaps(my_interval_2)% ]$ \7 [& A3 T" A
( L5 }3 O) H( m+ S' P True) D+ J2 I+ N2 A- H
1: H- w" ]6 k) c! U+ A
2& O& J+ L% ~! v: B) L1 p3 k: O
3" n* t; K7 A# v, V2 Q5 K9 ?- w
4
$ o) k! h9 m& p5 |' ^ pd.IntervalIndex对象有四类方法生成,分别是from_breaks, from_arrays, from_tuples, interval_range,它们分别应用于不同的情况:' r0 u$ O8 f1 B5 h, E
) E F% W, a) K7 c( h* V! I from_breaks:类似于cut或qcut函数,只不过后两个是通过计算得到的分割点,而前者是直接传入自定义的分割点:; P+ N9 U. L# v$ }
pd.IntervalIndex.from_breaks([1,3,6,10], closed='both')
5 Q h' }7 D1 l& ^) ^ 4 o0 f. X; s+ g) L: Z/ u
IntervalIndex([[1, 3], [3, 6], [6, 10]],6 t5 I' N2 O0 z7 c9 l
closed='both',
/ D3 V; e) ^, X/ n) @# n8 ~ dtype='interval[int64]')
4 r" N# V( O, |1 D; [5 _" Y 1
& O; z8 Z3 K4 d 2: y* e6 {/ S* m; v1 ?% S! s
3
! e' K' q! U/ T4 s9 T 47 } _" X" M8 t; Y$ P, f
5
7 y. k. }/ ~" O5 L from_arrays:分别传入左端点和右端点的列表,适用于有交集并且知道起点和终点的情况:
- ^5 D. h4 T7 }8 ^! M3 g pd.IntervalIndex.from_arrays(left = [1,3,6,10], right = [5,4,9,11], closed = 'neither')
# I, K! @9 T) q3 \/ W+ r 4 C* h9 j" H% o. c4 Q4 o P
IntervalIndex([(1, 5), (3, 4), (6, 9), (10, 11)],3 ]# K) f3 G* Y2 M, Q1 z( x
closed='neither',
g- \3 V( w. m4 ~ dtype='interval[int64]')6 w2 ?6 e2 ?, T( u! j
1
/ g7 [( Z' v) d3 @4 \ 2
) f; y! L3 Z% w, F8 l+ I) _ 3: D2 G' G- R( Y/ @1 ~
4
' v3 b2 ?# r3 R' J5 g. k 5
6 i4 s, e8 g" i+ | from_tuples:传入起点和终点元组构成的列表:
3 ~# U- H9 ]% {" m8 J) x5 Y pd.IntervalIndex.from_tuples([(1,5),(3,4),(6,9),(10,11)], closed='neither')# B0 c: g0 k8 t) P' W% `. H6 J
}1 k8 w: {. ~+ B9 H
IntervalIndex([(1, 5), (3, 4), (6, 9), (10, 11)],6 }3 e( g" z- G. i* A
closed='neither',# y) E- P5 A6 E u$ B$ y- m
dtype='interval[int64]')
" z# A7 `2 u8 @- i4 G 1
$ |/ [) S5 ^( Z 2
5 E" c5 J P* @ H# y# P 3
2 T+ h4 m" D1 g$ ]4 v4 U& S 4
$ ?8 {: C" a( R5 ^4 O$ ] 5
3 M. h2 [7 l2 e" S interval_range:生成等差区间。其参数有四个:start, end, periods, freq。分别表示等差区间的起点、终点、区间个数和区间长度。其中三个量确定的情况下,剩下一个量就确定了,从而就能构造出相应的区间:
2 d: T" V- j) O) m) }, R pd.interval_range(start=1,end=5,periods=8) # 启起点终点和区间个数1 P4 N5 o, d" m* a
Out[57]:
3 D0 V {- s; @ 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]],* H! E+ w9 P7 g' m; P! B5 |6 z
closed='right',
3 @, c/ a8 z- \ dtype='interval[float64]')
3 q, J% u6 D) B- U4 f( \
5 F* ~* n" Y E1 A- W( N8 N4 E pd.interval_range(end=5,periods=8,freq=0.5) # 启起点终点和区间长度
) Z) m* L0 c. o6 ~. @ Out[58]:
' _) M, D' g0 @! S 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]],
/ z' ^; r- }6 _1 ?- s# a6 w closed='right',) h- |1 A4 ^8 t* S) I3 n
dtype='interval[float64]')
' q" o0 C8 U# P t b 1
/ j, y( v% `, v% \ 27 c- Y! ?& H8 d% {9 ^- L
3+ h& n. X7 g+ ^% @+ v2 O3 T) y- e, c
4
7 _# X! t! S$ N5 U; J' I 5
% E8 {' h. P3 A- M 6
4 ]6 F- `# W( z( V% r- w( v 73 Z ^# N8 {2 j4 x: W9 r% [3 a
8
. I: J) A, b7 x% K 9
3 ]/ ~5 L5 I/ L* M 100 h- j+ F) z+ C6 w( a) J, \0 s. F
11
9 C5 N0 h/ V1 n7 g6 c, q 【练一练】6 e6 b: f# M& l% l0 w7 z
无论是interval_range还是下一章时间序列中的date_range都是给定了等差序列中四要素中的三个,从而确定整个序列。请回顾等差数列中的首项、末项、项数和公差的联系,写出interval_range中四个参数之间的恒等关系。8 i3 m& F, [/ u; @) T3 M7 B8 B' R
$ v. N$ [+ J! y 除此之外,如果直接使用pd.IntervalIndex([...], closed=...),把Interval类型的列表组成传入其中转为区间索引,那么所有的区间会被强制转为指定的closed类型,因为pd.IntervalIndex只允许存放同一种开闭区间的Interval对象。: B. B* R; z/ y! Q3 Z( g4 n
5 w. z' K+ J, {! Z7 K my_interval
- X% p! o& D- f5 f% d Out[59]: Interval(0, 1, closed='right')
t! A% e, r. u/ E* t ! H+ V+ D8 T0 N: d' g
my_interval_2' P7 M& Z; j/ C4 @) T
Out[60]: Interval(0.5, 1.5, closed='left')
# q( U& N9 j$ P ]4 ^7 M2 H ! ]0 N% S P6 g( y! ]. ]8 D- K
pd.IntervalIndex([my_interval, my_interval_2], closed='left')
; S% i: ]& o" G* t% B7 g Out[61]:
/ R; a# H L) n8 F+ r IntervalIndex([[0.0, 1.0), [0.5, 1.5)],2 n7 c1 T9 n ^! p
closed='left',
- B1 e% S; b U: n- k dtype='interval[float64]')
* v3 V6 N, l# ~ 1
0 u4 W; ~5 s, X7 G/ H 2) ?$ j/ |/ k* S$ N: a6 v/ e4 h
3
3 z$ W3 y V! K& J 4' ~3 g) R" r3 l9 {& ~
5" i4 U, T4 o' W# W
6
2 Q) d( b( @3 I U+ V0 s/ F 7
0 h z# t. q# L; K 8, _6 V7 q, J/ D5 v- F) f# W: e
9
7 z- ]/ z' ?+ f9 P6 D3 d; A 10
9 G) C; Q& [# o 11* o7 o3 L1 h) @( i' Y8 k) f
9.3.3 区间的属性与方法
% }# i/ }4 @7 c: `7 H IntervalIndex上也定义了一些有用的属性和方法。同时,如果想要具体利用cut或者qcut的结果进行分析,那么需要先将其转为该种索引类型:% P: K. M5 W+ n# `* E
2 V' H+ v# q6 p$ ^, }9 g. y$ W
s=df.Weight+ P; l+ Z# V, q# l* D
id_interval = pd.IntervalIndex(pd.cut(s, 3)) # 返回的是每个元素所属区间,用具体数值(x,y]表示, S1 l- V& ?7 K8 H* Q6 Y9 W% |8 z+ `5 K7 r
id_interval[:3]8 j8 O1 b1 y8 [1 I6 E7 f' q1 l2 ?
1 t4 w' ^8 n7 j IntervalIndex([(33.945, 52.333], (52.333, 70.667], (70.667, 89.0]],
& m% _+ \6 a# A* u1 c! m closed='right',
' Q0 u. [9 g& [: Z. f% ^7 E- O name='Weight',
1 X3 l& M( P- c4 |0 g) D dtype='interval[float64]')
! v9 N |5 p7 T( U [. l+ `; G 1
) a9 K* m( U! N 2
- k) R; _* K; Y/ ^ L, R 3
9 C& t- y+ R% E7 G7 \; J7 U4 e6 a 4
# L+ l5 ] o, n 5+ r' a/ q' Y% v" v' Y
60 y7 o9 Q. |! L" M2 u8 c
7
R9 y8 e; W1 s 89 F, B+ V: o8 `8 c p4 j
与单个Interval类型相似,IntervalIndex有若干常用属性:left, right, mid, length,分别表示左右端点、两 点均值和区间长度。: P8 K' I) U8 r) {3 Z! N e4 j
id_demo = id_interval[:5] # 选出前5个展示! a# H6 I& y i, [
5 J( h9 n1 b. D2 S$ O* @ id_demo
! ~9 E8 Y" r$ V) I4 l& [ Out[64]: ) O; t9 m) t+ b8 k6 E, I
IntervalIndex([(33.945, 52.333], (52.333, 70.667], (70.667, 89.0], (33.945, 52.333], (70.667, 89.0]],
1 \8 }3 ~% t S5 ?0 u closed='right',3 v9 ]5 t0 z% E1 }* g8 }
name='Weight',6 Z+ D9 S! ~5 J5 H9 F
dtype='interval[float64]')
3 I0 x0 m; N. f: z: \0 ? 5 K4 O0 ~1 S+ B( W5 M
id_demo.left # 获取这五个区间的左端点
C# l2 ]7 L& g( x9 ?, n, A Out[65]: Float64Index([33.945, 52.333, 70.667, 33.945, 70.667], dtype='float64')% ]! A0 T, }$ d9 c1 M7 n: l
" x0 p( I$ b) ?$ |2 x5 v id_demo.right # 获取这五个区间的右端点- b/ M; G, H1 X" d J1 W
Out[66]: Float64Index([52.333, 70.667, 89.0, 52.333, 89.0], dtype='float64')7 ]1 _/ Z. w+ b( c$ l
2 L& w; f% R6 O* c id_demo.mid
/ m W o0 w/ {# W8 H) X' F Out[67]: Float64Index([43.138999999999996, 61.5, 79.8335, 43.138999999999996, 79.8335], dtype='float64')
( C* G; l/ H0 g- G; h
; u# J8 s5 I& O) x3 x) Y id_demo.length' ~; B% W8 i) r% J
Out[68]: " L* _: A2 v; _5 @
Float64Index([18.387999999999998, 18.334000000000003, 18.333,
5 K3 L0 L; _6 z2 `, @% q 18.387999999999998, 18.333], ~" F, M7 n( q1 B
dtype='float64')
) j) o; k" p) P1 v: s' M2 x% j / a: s3 A3 ~0 V0 S
1
( r2 p- t8 c" Q" Z 2
. L q: M3 a+ ]2 P4 [9 \ 3( w( a3 }5 K7 h& [' T! Z6 T# g! N
4- [( ~# \, M# G' P
5
* n8 i" a# s$ d8 }# I; T$ Q' J 6+ z$ D- N% x- C! L5 K
7, a- p5 ?/ M o+ d- m
8+ }4 G* ^) H. u9 N2 ]$ n
98 K! u- \2 i5 X0 q! l& t
10* p' z2 S4 m4 J# n
11
% o$ r9 W" B3 s6 e 12
2 a" N( r* y5 P. b# {7 x 13
) _' Y" U" p4 S) S3 S' b% C 14
) w* [1 `. |2 `* @, p# _ L 15
( J8 D3 q% [# g" i' ~ 16
1 o4 Z# w% S8 w. q! N 17
% Z. U- ?" E2 Y2 e) o0 n 184 H0 x2 w) B& A
19 o+ K, f/ m3 r6 o+ B# z8 i- y: B
20* t! t4 s1 z7 c$ ^$ n
21
* d' p- m6 O1 m7 m# C2 h3 h, o+ b5 y$ h 227 y# u& K/ P1 ^* z4 c; D( Q& c
230 X' s h0 P2 ^& l- S; t8 R3 a4 T
IntervalIndex还有两个常用方法:5 K# S. ] [$ a5 g$ Q- l* s- e3 t% t' u, m
contains:逐个判断每个区间是否包含某元素
/ S0 p$ ^. G' q overlaps:是否和一个pd.Interval对象有交集。
! {) O$ o2 \: m3 L3 z id_demo.contains(50)& \: y3 O! b2 O- {! D6 M6 c) {( C
Out[69]: array([ True, False, False, True, False])
& k9 x: C: @! |" T/ ~ V , ?0 i8 I0 Y( S( i& m
id_demo.overlaps(pd.Interval(40,60))* o+ ^7 x3 ?* ~
Out[70]: array([ True, True, False, True, False])
0 Z& Q9 V4 I" i D( S 1
7 g" `) e {6 |4 _ 2% p$ F/ d5 i9 h9 M0 o
32 ], n p1 x- e2 A
4# E+ ?# I5 a M* k9 C: Q- n$ v2 k( O
5
* _2 o) _( z2 a3 d9 H X 9.4 练习
N0 V- n4 g: y* Q2 V# S1 |0 t" @ Ex1: 统计未出现的类别
# |- i6 k9 d) r; {/ u- ^ 在第五章中介绍了crosstab函数,在默认参数下它能够对两个列的组合出现的频数进行统计汇总:2 ]! R0 W# ^4 V& o. h- d
% g1 v& |7 ], {' M8 K
df = pd.DataFrame({'A':['a','b','c','a'], 'B':['cat','cat','dog','cat']})- H4 Z6 R! `# P- Y" a
pd.crosstab(df.A, df.B)6 k# Z8 N1 }8 f' a! c1 d
$ S6 H8 P- w# E% q3 t+ \5 { Out[72]:
. U! F9 f1 J" F5 [ B cat dog
# t4 ~3 Q, u* n+ ^! \. L A
( R$ {: k7 _7 ]! D0 a! c3 I' P a 2 0
: M9 l7 u$ j% J t# N( k, ~5 ^ b 1 0
$ Z1 J& q% f" X W$ C3 S c 0 1
; e1 A/ Q' |+ |9 U0 F 1
1 q; n* O3 z) c5 o) p 2( f* W, ~# Z* E& J- W6 i
3, J7 `5 F0 r4 R+ q
4
4 v' C, U2 e. y$ m) \4 c 5
6 {, S# E4 s" t8 X( D8 _ 6
; L4 I2 r" D' m; P3 t( V2 N 7) {5 ~6 U( ?2 K
8+ s/ T* G" X! ?# {! i
9
5 e" r! V. o$ b; F% ]2 U 但事实上有些列存储的是分类变量,列中并不一定包含所有的类别,此时如果想要对这些未出现的类别在crosstab结果中也进行汇总,则可以指定dropna参数为False:
- m6 M1 t) T" P: D: a! H
7 y1 z& a( ?# k4 U df.B = df.B.astype('category').cat.add_categories('sheep')2 J& N0 ~( Q8 s9 [
pd.crosstab(df.A, df.B, dropna=False)
$ r) B6 Y+ W& w" @ J
7 F- `4 s/ |" i( p- a) T Out[74]: ' k+ K: y2 B* R( H) p
B cat dog sheep
$ ^1 W$ q/ e3 z A 1 ^& {' B) u$ u/ ?8 s
a 2 0 0
' m9 |0 b( U9 a1 E; U% Q) b b 1 0 0
9 F: {* M+ {& H* t, g c 0 1 0
; N) C/ m3 j, P 1
7 A3 c$ A' ~+ X+ g! O2 G& y3 b# ~ 2
4 g: o2 f: a) v8 R" c' K/ }7 J |$ O 3; L% {" R/ V( q. u1 U; W
4
: Z: t9 e6 k& R# ?% @& H2 o) A3 K6 h( W 51 A: C7 {- D3 M% k
6
1 w8 |0 b0 |0 b7 }- I4 J6 F e9 L 7- N- R2 d: C. [5 `
8% Y+ w) U7 {+ i: _4 P4 `0 j6 U
9" U( [4 n8 A6 c3 y: \5 m
请实现一个带有dropna参数的my_crosstab函数来完成上面的功能。
/ `, g, Y! t# U7 n3 _% ^ ! V" l( x/ Y$ z" n9 f3 f" ^; S
Ex2: 钻石数据集
: P/ T" S9 ]* R) j8 z3 C 现有一份关于钻石的数据集,其中carat, cut, clarity, price分别表示克拉重量、切割质量、纯净度和价格,样例如下:* ` d( _4 @( h& Y0 d2 L
/ s( U5 d3 V4 S: B
df = pd.read_csv('../data/diamonds.csv')
" w, b+ A/ s( h' k& Z df.head(3)* i3 b6 j1 [- y1 ? V
, `8 L8 ~: q& E# R s! l
Out[76]: 0 @8 G3 ~- ~# v+ P/ U! ~# d/ U
carat cut clarity price
% a7 s6 q6 y% ?' c5 z* E 0 0.23 Ideal SI2 326- n+ |8 }$ G5 e( \3 V# x+ R
1 0.21 Premium SI1 326# P7 n" O0 U! o" U5 z+ d$ D
2 0.23 Good VS1 327
3 M7 n4 Z2 x8 _ 1
; j' \ }# F5 A& m2 v' q 2
1 I- X* u" N( ?% k; F( G1 M- v8 ? 3
& c- m$ K7 l: A+ r2 P- v+ g, D 4+ K: M4 q# V% P0 d0 K7 p
50 Q5 \/ v' m0 r( I6 |2 g t6 W, W
6
# E. d1 S( l8 D. m2 p8 b5 U5 m 7( r% S D: z7 y$ d/ M5 I
8
3 t# Q l, ?3 Z7 {) ?( @ 分别对df.cut在object类型和category类型下使用nunique函数,并比较它们的性能。% Z$ }( \; h- V w5 `
钻石的切割质量可以分为五个等级,由次到好分别是Fair, Good, Very Good, Premium, Ideal,纯净度有八个等级,由次到好分别是I1, SI2, SI1, VS2, VS1, VVS2, VVS1, IF,请对切割质量按照由好到次的顺序排序,相同切割质量的钻石,按照纯净度进行由次到好的排序。
$ j+ d0 z6 m6 \& r4 F% i6 F, I 分别采用两种不同的方法,把cut, clarity这两列按照由好到次的顺序,映射到从0到n-1的整数,其中n表示类别的个数。
$ B6 R% {& ~1 t, _/ {. u 对每克拉的价格分别按照分位数(q=[0.2, 0.4, 0.6, 0.8])与[1000, 3500, 5500, 18000]割点进行分箱得到五个类别Very Low, Low, Mid, High, Very High,并把按这两种分箱方法得到的category序列依次添加到原表中。5 o0 Y& a. }' }6 T9 D4 L
第4问中按照整数分箱得到的序列中,是否出现了所有的类别?如果存在没有出现的类别请把该类别删除。
: U: C$ y, \ N* B! s 对第4问中按照分位数分箱得到的序列,求每个样本对应所在区间的左右端点值和长度。
5 t# |# y# W6 K; Z3 L 先看看数据结构:
$ W4 L$ P; M9 e6 ~4 i
/ {/ w @; z3 s0 e! x: Q* t% L df.info()2 l* O$ ~2 n, f8 W
Data columns (total 4 columns):8 t, ?! ?" q) \
# Column Non-Null Count Dtype
: t- ?1 u3 F" D --- ------ -------------- ----- 4 A) q, g A6 l6 O0 W
0 carat 53940 non-null float64
6 ^$ H* r, i% Q& ^ 1 cut 53940 non-null object ! `) E U2 s; ^9 B& ] x* L
2 clarity 53940 non-null object c/ m+ Z: S ~/ B
3 price 53940 non-null int64 * B9 n1 s# W0 l7 w; e4 I. y
dtypes: float64(1), int64(1), object(2)" T0 J+ o; A! c' D# p0 j
1/ Z5 S4 \) e3 ~& p3 _% K
28 v, R% R8 j7 }, k% A- C( ?
3
+ E1 o6 Q1 c5 T% q' P p# w 4. f& K$ z, Z3 j0 C, K0 `2 q
5/ [; n$ S: n! G( N
6
* {5 o" ^! G" h9 n4 t l( W3 P 74 |! h" |$ k" V3 h# M
8
! I. ?/ s# ~& a; C% y 9
h P8 d. g1 l# `: R) }/ Y 比较两种操作的性能/ I0 o; n, _; v7 O4 r8 n
%time df.cut.unique()2 Q" t& z" H( R
! m; ` z) @1 c* F5 F6 n8 C Wall time: 5.98 ms! [+ m5 O! s1 Q# Q' e* ?* A( O
array(['Ideal', 'Premium', 'Good', 'Very Good', 'Fair'], dtype=object)
1 l8 V1 q/ J/ ]- s 1
, J2 b2 f* x" D- G, H v 2* \' E$ J# y6 g* S! A/ Q
3
! |* n# W l1 J" G! u* @' E 4
# r- I% I' a3 P. t. P" M# B& o) J* J %time df.cut.astype('category').unique()& v$ ~6 }- D! q' R0 b
# k, [* u* |! Y6 ^/ o
Wall time: 8.01 ms # 转换类型加统计类别,一共8ms8 N9 Z& O8 ~+ u' a* |2 Q
['Ideal', 'Premium', 'Good', 'Very Good', 'Fair']
, J, ?/ q- B, d' U Categories (5, object): ['Ideal', 'Premium', 'Good', 'Very Good', 'Fair']
1 t. s) f/ P- b3 U 1
& R+ Q' s' t/ ]7 \5 k 25 A2 ?0 G I- Z9 q9 ~# t
3
# g# }; C8 t1 X# U+ k 4
C6 l; _1 I1 N( n+ Z) ]1 D 5
- P9 e! J" y4 ]7 Z0 x df.cut=df.cut.astype('category'). J) s( m4 Q [6 c
%time df.cut.unique() # 类别属性统计,2ms$ X O+ w' u: S2 e
8 d0 Z: h: c- |* `9 r
Wall time: 2 ms. Z3 }* U2 A& Q3 k# m& ^
['Ideal', 'Premium', 'Good', 'Very Good', 'Fair']" R2 y) j+ p) H: H
Categories (5, object): ['Ideal', 'Premium', 'Good', 'Very Good', 'Fair']
+ U. H- x) i0 T3 \) H* W: F7 a8 L) V- w. Q 1
0 k' ?( E. H0 [1 b9 h 20 o$ F/ F p1 \! q9 e
3
4 P# P. U2 T' a 4" M; G% }8 M: b
5
2 `8 z4 [% ]' I3 `( [5 y 6
# l1 I* n: w1 _; V7 w2 n$ I 对切割质量按照由好到次的顺序排序,相同切割质量的钻石,按照纯净度进行由次到好的排序。
4 K6 b( V7 V, w$ l) I ls_cut=['Fair', 'Good', 'Very Good', 'Premium', 'Ideal']; k& C% X0 X: Y: }9 ]* {2 O
ls_clarity=['I1','SI2', 'SI1', 'VS2', 'VS1', 'VVS2', 'VVS1', 'IF']; o! \3 l9 y/ i' B
df.cut=df.cut.astype('category').cat.reorder_categories(ls_cut,ordered=True) # 转换后还是得进行替换0 s9 r* D! f2 s4 n! C
df.clarity=df.clarity.astype('category').cat.reorder_categories(ls_clarity,ordered=True)
( U T3 n" Y7 K, i0 M 9 o. }5 b2 t; W7 O4 H$ {/ T
df.sort_values(['cut','clarity'],ascending=[False,True]).head(3)
6 Y1 r4 i( W! s, R9 k* i- E* y6 ? " s( \8 z2 [) v( M$ E" b# W2 E1 o. s
carat cut clarity price7 b1 s) k; `! ]
315 0.96 Ideal I1 2801
k$ v, u9 c% M9 R+ N" _3 |& R 535 0.96 Ideal I1 2826
# ?- l5 \ y- \- r 551 0.97 Ideal I1 2830
6 l9 f) H- ]- e7 w/ M. A 1! H! _9 ?; w. I1 A
2
% ~" R+ [ w8 H7 q 36 i# H+ E) u. A( |, x7 R" S7 t
4
5 R( H+ L1 \0 j u- s6 C. W 54 U% r9 v* B( K* s6 o2 \1 T
69 e% D7 n4 |; k1 v) g& T! b
74 w4 e+ N1 I' Q$ u
8
+ R- x7 Z3 v h8 m2 S# ] 9
' Z7 s. g. Z* M% ? 10/ R, o8 e' A' |: W- W
11
' m. x/ A: u; M" y! X 分别采用两种不同的方法,把 cut, clarity 这两列按照 由好到次 的顺序,映射到从0到n-1的整数,其中n表示类别的个数。2 v, h( p; N' \4 J
# 第一种是将类别重命名为整数1 q# c# r, ^- }9 z3 T' V3 E
dict1=dict(zip(ls_cut,[x for x in range (4,-1,-1)]))
6 _0 ~- D0 x9 V: y& @ dict2=dict(zip(ls_clarity,[x for x in range (7,-1,-1)]))& C9 h* s* F, O# F) ]
2 _7 A7 r+ {. T6 ^
df.cut=df.cut.cat.rename_categories(dict1)
* a5 q9 K7 d! i% }4 m. e df.clarity=df.clarity.cat.rename_categories(dict2)$ C' w. C5 {4 c- t7 v+ X2 t M8 b
df.head(3)
) S$ S `0 S7 H [/ v( w4 A$ c- d
" o) c0 \6 u) U% d' J: X carat cut clarity price6 T- t! f% v! W; w' h4 M6 J+ A: i. c0 J
0 0.23 0 6 326& L* Z+ v" t% [! p' B; C" l3 e
1 0.21 1 5 326& b% R( k( v7 \) O1 J( I- f
2 0.23 3 3 327
) O7 b9 L( Y2 {8 N% W 1# f% F5 t2 n6 |) h( J9 D/ f
2
/ z# f* a+ a; d" U! U- j& h 3
2 J1 Y' Z! h: p( Y 41 }0 Z2 @$ h# Z" I
5
. ?9 F% j! S6 J* o& Q- I }0 x 67 G, a! V+ g0 d+ t
7
; A( a. r- n$ d' A3 [! m2 z/ R% {6 t 8
( J2 q0 T. j& R+ Z& O) F 91 N: |+ A) Q+ D7 B
10! m, l) U2 O1 H! N2 T# l, [0 k/ q+ j
11
2 x) I" G* | s* }5 Y. Q 12
3 t8 W1 i3 J! F* N$ F3 o) a # 第二种应该是报错object属性,然后直接进行替换
& t7 N1 }1 `2 T$ A% U2 R+ G: U df = pd.read_csv('data/diamonds.csv')
* E% K0 e X/ Q% n! c* ] for i,j in enumerate(ls_cut[::-1]):$ o( d) [& P% x$ l8 S8 j% f
df.loc[df.cut==j,'cut']=i
0 ~* `6 k; u. }5 ]6 p0 z5 A0 N1 G
. V2 S4 S% t% ?3 | for k,l in enumerate(ls_clarity[::-1]): X' @* b0 |8 I" h V
df.loc[df.clarity==l,'clarity']=k
* ?4 ]! r! `1 S& W5 Z: s3 d! I L df.head(3)6 q" p7 B N. M# D. g# C
$ [2 ?1 y& D: L
carat cut clarity price/ E# z8 i2 J6 o4 U8 s8 H3 O
0 0.23 0 6 326
( v. ^2 p, h# V% E3 s" x 1 0.21 1 5 326
8 c# D# t$ ?/ d: U* o 2 0.23 3 3 327
- `# a. ~/ \- F" y4 } 1 X$ A. N1 @1 s& t1 E
2
. _/ M6 Q: {& V 3" [! d: c1 y: ^3 ?5 O/ ^2 t
4
# l# l( |: K5 ]6 b4 w j: W. u& ` 5' s3 x, H1 }# K
66 s6 d( x7 I$ M: @+ g ~
7
: x) ~ k0 G1 Y F+ z0 _. V6 o* o. k 8
3 P" w9 n \7 ^. I( v9 x5 q$ S8 w 9
, ~% O( m' u3 S* i3 g 10
$ `5 V3 H f! L! G 11
% ^' X. s4 F% d1 b; f 12
6 q2 g! K1 J$ p7 s/ X$ P# ]5 o 13
) |/ ?) Y3 L. d5 T* q8 G 对每克拉的价格分别按照分位数(q=[0.2, 0.4, 0.6, 0.8])与[1000, 3500, 5500, 18000]割点进行分箱得到五个类别Very Low, Low, Mid, High, Very High,并把按这两种分箱方法得到的category序列依次添加到原表中。
* h2 o/ i9 f/ Y; C! _ # retbins=True返回的是元组,第一个才是要的序列,第二个元素是分割点
9 L/ @; c3 M) ?& A- e5 x5 [ avg=df.price/df.carat
9 r) c3 e2 f- @' N % [7 {; P5 @% p5 U
df['price_quantile']=pd.qcut(avg, q=[0,0.2,0.4,0.6,0.8,1],
0 ^9 y$ N4 G0 k0 ` labels=['Very Low', 'Low', 'Mid', 'High', 'Very High'],retbins=True)[0]
$ Z: @% f+ G- d+ t6 ? + w/ c1 a$ {( ?' e8 r7 S! @
df['price_list']=pd.cut(avg, bins=[-np.infty,1000, 3500, 5500, 18000,np.infty],
* A* m1 _) v. {: W$ J6 F9 l labels=['Very Low', 'Low', 'Mid', 'High', 'Very High'],retbins=True)[0]( O. ^6 Q7 n9 c. G' L4 l
df.head()# ?7 t G0 o1 D& l6 J
; f. }8 h8 q8 m3 _# _
carat cut clarity price price_quantile price_list" w, J# j& k! V( f+ y/ {# s
0 0.23 0 6 326 Very Low Low
" Z! [3 Q8 B2 ?: c+ L 1 0.21 1 5 326 Very Low Low- U4 d9 ^1 e$ O3 Z X. v) d; h
2 0.23 3 3 327 Very Low Low
1 Y2 C. d5 ?, m$ Y' n. [: ^( k 3 0.29 1 4 334 Very Low Low6 S5 s( j6 ~/ n5 E
4 0.31 3 6 335 Very Low Low
) U6 z; w$ G5 I" d
1 i' Y+ X* w: Q3 {2 F 1& t- q' L2 l, f7 }" L# ? f
2& H8 m2 ^, P! {! I& m
3' v9 j$ X! N4 ]6 j( J/ W
4
8 ~, N* M+ j: {: s! O/ ~% x 5* ^6 x, f. D0 E4 ^. W2 o9 p6 I$ v
6
: Y7 y# ~# b* ], K" a5 C" R* m. M 7
k" k; m( a* y1 \- n% ]; X" c+ a 8( n& ~% e8 U5 O& F/ m& J8 ?# D
9
7 Q/ w3 d, n3 C; H6 y+ ~# Z* b, |" y# p 10
% o: [% |, B: C7 t. ~ a 11
# ^! }6 N: h# w" }6 F' p 12
8 R2 M8 O+ L- n& X1 x 13- R; I8 F2 A0 C6 @6 k8 a
14% l- C9 V1 _7 n o0 S& Y1 v3 J
15
" T- K% N; F* D, R2 s 163 n* v7 b6 x0 ^9 X4 s
分割点分别是:, X. _6 V. J$ B+ ~' T+ t+ I
+ R" V- t0 z1 E; I- g) q7 R. R
array([ 1051.16 , 2295. , 3073.29, 4031.68, 5456.34, 17828.84])
$ Z- l& M" J" z) b- Y9 \6 H array([ -inf, 1000., 3500., 5500., 18000., inf])
4 J. m& ^! y; g. y3 V 1# f- u1 W/ [3 e0 _; y7 W! v: E
24 ]* y. r$ I& y* S7 [8 m: U
第4问中按照整数分箱得到的序列中,是否出现了所有的类别?如果存在没有出现的类别请把该类别删除。
3 Y. Y1 w5 _! ^8 H6 E, V/ k df['price_list'].cat.categories # 原先设定的类别数% I6 h: S0 T/ H
Index(['Very Low', 'Low', 'Mid', 'High', 'Very High'], dtype='object')1 y _2 Y: h1 H4 e: h: l1 p9 J
4 j- b$ ~) ]8 }$ D0 Y* y& I& i
df['price_list'].cat.remove_unused_categories().cat.categories # 移除未出现的类别* E0 T- R- P: E: m
Index(['Low', 'Mid', 'High'], dtype='object') # 首尾两个类别未出现 I: t. ^" i7 Q4 D
1
/ A- P4 w9 @6 \# C 22 o! H( l) F: p3 |$ X
37 H4 K& i: {" ]" B W N; p
4: l) \( ?2 B- q& t' w7 Y5 W
57 b" ~# N8 c& P- V1 M
avg.sort_values() # 可见首尾区间确实是没有的
8 S! A3 H' `! V* |% Z 31962 1051.162791
1 P7 b7 h. n7 A" x/ Y. Y1 ] 15 1078.125000
9 v# j. p8 p) a, H7 {7 K 4 1080.6451612 U1 l; Q" i! v/ I
28285 1109.0909091 l: ^* o1 J: S& k+ [+ i
13 1109.677419* c- `8 T* T* X) g+ C: g
... ; F5 E/ e# Z/ p/ d; {. i: T" A
26998 16764.705882
( K$ z5 R/ x$ m7 |- w, o. Y! ~ 27457 16928.9719636 u$ U5 _! u3 p+ P( U
27226 17077.669903$ W, d/ F8 j* T; C0 F) v
27530 17083.177570
4 M1 _+ o+ ]) v5 E1 b 27635 17828.846154
2 b. \# d' a8 C6 K. U- q5 D6 U2 d 1# O9 V R9 ]6 c/ _# D% [
21 D6 ?* F% S9 i9 I0 l) ~; \
38 H: \$ ]' N* o- O; Y
4
- ?6 a" s- U# u) }3 a8 I2 \ 5+ y L" F6 k2 _5 P2 u0 j; B
6' K5 A* `6 l2 C# x: Y6 V' R+ g+ E0 w4 f
7- j1 b1 c+ i% c% j
8: m% v# [$ g2 A9 D
9
9 Y; `, L3 V+ e# B 10
3 _) v: M' @0 ]8 ~& V4 ~+ ] 113 x" c! H% V/ t/ T
12
& b V# Y2 v' A$ x: a5 |# d 对第4问中按照分位数分箱得到的序列,求每个样本对应所在区间的左右端点值和长度。
8 ~( G8 C+ ^: e # 分割时区间不能有命名,否则字符串传入错误。7 S$ C7 R) H5 t, w# x! J+ [
id_interval=pd.IntervalIndex(
. h% k0 _& N9 o' @' g pd.qcut(avg, q=[0,0.2,0.4,0.6,0.8,1],retbins=True)[0]
3 ~/ Y8 Y8 T$ d" m )
( K0 T% \' g; ~ id_interval.left1 a2 G2 ` N1 E, U" N: M
id_interval.right
v! }( L& ~, _' P id_interval.length 6 I9 f2 J5 m3 h6 ^4 T
1
9 w, k7 N, e2 s# { 2( m4 w0 \' ^+ `# e
3
1 M S" z: R# G& {0 P5 q1 W6 m 4& H& B4 t! I6 e
5
$ ~! _/ o4 E* M7 d Y/ I. m/ Q 6
1 [( c5 e( d4 R 78 f' R+ @' b+ u* _7 _& O* [
第十章 时序数据7 L& |2 U! T, ^% B2 v; q
import numpy as np
, I. W2 T, X. `5 H8 z- f- N# e import pandas as pd
9 a1 ?1 \6 i, r. c0 s 1
$ V1 d& u# D( J2 R! U# R N9 k' |% ` 2# o& a- H1 |8 W. C" d0 m
5 p$ T2 d N7 @, D$ a2 Z- S+ J
) Y T% G* I5 s+ i 10.1 时序中的基本对象! w$ I% U% p- |) X o
时间序列的概念在日常生活中十分常见,但对于一个具体的时序事件而言,可以从多个时间对象的角度来描述。例如2020年9月7日周一早上8点整需要到教室上课,这个课会在当天早上10点结束,其中包含了哪些时间概念?
/ g3 E$ u8 ]8 o9 m + C; a# B- l* [0 l
会出现时间戳(Date times)的概念,即’2020-9-7 08:00:00’和’2020-9-7 10:00:00’这两个时间点分别代表了上课和下课的时刻,在pandas中称为Timestamp。同时,一系列的时间戳可以组成DatetimeIndex,而将它放到Series中后,Series的类型就变为了datetime64[ns],如果有涉及时区则为datetime64[ns, tz],其中tz是timezone的简写。
% w; w; U/ m4 S5 w$ g; T" h9 w* t
' ]* d' L% X( S$ |, [) k1 [) r! R 会出现时间差(Time deltas)的概念,即上课需要的时间,两个Timestamp做差就得到了时间差,pandas中利用Timedelta来表示。类似的,一系列的时间差就组成了TimedeltaIndex, 而将它放到Series中后,Series的类型就变为了timedelta64[ns]。
' v3 m5 j+ t. w: h& _% K " N: ], k8 U: B* N: Y
会出现时间段(Time spans)的概念,即在8点到10点这个区间都会持续地在上课,在pandas利用Period来表示。类似的,一系列的时间段就组成了PeriodIndex, 而将它放到Series中后,Series的类型就变为了Period。
3 p9 F! H7 @6 y8 B/ m1 O" Z 5 S; P" C3 @- D5 d( M
会出现日期偏置(Date offsets)的概念,假设你只知道9月的第一个周一早上8点要去上课,但不知道具体的日期,那么就需要一个类型来处理此类需求。再例如,想要知道2020年9月7日后的第30个工作日是哪一天,那么时间差就解决不了你的问题,从而pandas中的DateOffset就出现了。同时,pandas中没有为一列时间偏置专门设计存储类型,理由也很简单,因为需求比较奇怪,一般来说我们只需要对一批时间特征做一个统一的特殊日期偏置。
6 Q, y5 S4 y0 ~. P" J5 ?5 L) n0 a
: {8 r9 y4 m* W7 { 通过这个简单的例子,就能够容易地总结出官方文档中的这个表格:# j0 \, z5 G, }. Q u: U
2 z: a, S4 a: b9 d; k M) ~
概念 单元素类型 数组类型 pandas数据类型
% w$ c, K' e0 g$ |) C4 [" a" ]% W$ D) e Date times Timestamp DatetimeIndex datetime64[ns]
7 K- S/ B# v% x9 y Time deltas Timedelta TimedeltaIndex timedelta64[ns]
/ `' o' \( o3 m- U8 \2 H3 Y Time spans Period PeriodIndex period[freq]5 L8 |0 V/ ], o0 D
Date offsets DateOffset None None8 ~- l: g: h' c& s# p1 H# {% V
由于时间段对象Period/PeriodIndex的使用频率并不高,因此将不进行讲解,而只涉及时间戳序列、时间差序列和日期偏置的相关内容。
$ v% ^: G$ _% @: v; r2 Q: f % i& B& J# d' s; B! U
10.2 时间戳
( B- J1 o4 U( E& B; g, m 10.2.1 Timestamp的构造与属性
. }: i1 \+ t7 E! H% d 单个时间戳的生成利用pd.Timestamp实现,一般而言的常见日期格式都能被成功地转换:
& S. ^$ Q7 x3 m! h ' n& V0 x; Y) [
ts = pd.Timestamp('2020/1/1'); z2 u* O; K3 k9 p V
# m0 ]: `6 B- G H. d
ts+ ]# r8 L* R: A( \$ U% q/ n
Out[4]: Timestamp('2020-01-01 00:00:00')' Y+ V' e$ q' | L2 Z* B0 o1 t
0 Y9 G8 e, q% z1 V6 v1 J% J ts = pd.Timestamp('2020-1-1 08:10:30')
# e8 U9 C6 ^3 u1 G / t8 |+ |# L; f
ts# m1 f# j/ V. P$ _ N: n) C
Out[6]: Timestamp('2020-01-01 08:10:30')
2 a1 S e: G3 d$ l 1; a% m* v w+ k- B# U
2- N) C# A5 P" |% J n/ `
3
8 f! r3 L! J" y1 W& o 4. o" `* [4 E4 H0 }
52 U& ~7 o+ n7 }( \: d0 j
61 {, V# \; C4 }5 n8 v6 z& a: ]
73 x4 v* L, f: s/ R; [
8
9 `2 _$ c- o; p2 W/ p" ] 93 K( F, @- @8 L1 C! J- U
通过year, month, day, hour, min, second可以获取具体的数值:
* L! y) z- Y% P$ @" q
) o2 W2 m$ O1 M9 N6 g ts.year) ]) o# N: h7 v2 w" x4 D1 x5 I9 [
Out[7]: 2020- o; [0 [+ I4 g7 m+ a' o/ ]
& S3 M; {, w* W% s
ts.month- P- `" k9 J% f$ |( P* H0 n8 r
Out[8]: 19 f: u8 ^( c+ F$ F6 z8 @) T; v3 h W
) h7 I1 }$ C1 a9 Z2 ]4 \ ts.day+ V7 ?- C1 a: [! s
Out[9]: 18 B0 A g$ |4 p" ]) l$ H# s
/ C) `; i9 t0 _: N2 N) ^
ts.hour) X7 o& O+ L% a+ v. T1 W
Out[10]: 8+ k$ s1 ]+ \# B% \/ Z+ y+ h! N( J
0 @ r) X% U$ I/ \4 p+ | ts.minute5 T& s0 F. t, H5 j. c
Out[11]: 10
& ^4 h( ^0 M, d, i+ n2 Q
+ M5 G) \" R& \0 s i ts.second
! P0 X8 |* B9 i2 z: ]6 x Out[12]: 30
! L6 Q I, |, S
9 @5 u! x' R( ^# D- ~% Z$ w 1/ [- s( V' m' k% O, P
2
; j8 V! z( ]! Z, y, T4 R9 {: c5 y 3/ S V/ u* G4 u& |( r4 {$ K
4
3 ^+ O, Y1 f- u 5
- S# f/ [ f. t$ j6 L 63 S9 [) L1 U) d. \ M0 o" R
7' y' b8 X% i; c9 \
88 Y7 A2 q+ R/ _5 {
9
$ e4 e2 [- `& y# ?8 m 10 d, H) @+ [" l7 u0 E
11. O/ x; B* E( u/ B$ T c+ a
12
2 r% y0 [# L. ?* A q3 R4 A 13( v' K% S+ z$ D3 b6 B( _* a
14
1 r- m3 `9 j$ T$ V 15
; ?1 F# `+ f3 B8 n- w) C 16( h* d. ~ F7 Q$ |8 W9 K" ~8 ^: N$ t
17# b" T" |- c- i
# 获取当前时间
6 c3 Q, m4 K8 d' U% q now=pd.Timestamp.now()
4 r" ?$ @0 |* _( C$ H4 B 1 T! V/ Y) c" k6 W6 K+ @2 y/ j5 ^
23 O' ^" A) \- ~3 ?( m; q
在pandas中,时间戳的最小精度为纳秒ns,由于使用了64位存储,可以表示的时间范围大约可以如下计算:
+ A$ v( {3 N& s9 N* o5 ?8 r, K' w T i m e R a n g e = 2 64 1 0 9 × 60 × 60 × 24 × 365 ≈ 585 ( Y e a r s ) \rm Time\,Range = \frac{2^{64}}{10^9\times 60\times 60\times 24\times 365} \approx 585 (Years)% b* o) k' j/ v, F
TimeRange= ) F3 l$ b9 E G i: A( o
10 ' ]* Z) F4 S7 b# \
9
+ c) l8 r. e8 e; C/ @* i! |, ? ×60×60×24×365
8 q1 F* U( T# z5 ?- g9 |: r 2 2 e) r" D+ U0 x3 r1 z
64
: P4 k4 I/ O1 R0 K
x: f) _6 U, Q+ n; R
' T5 [/ l V* u7 q, a ≈585(Years)
4 v, _2 z- d" h, F( ?2 { ! h# t9 _, [" n+ F9 F1 w5 R( H
通过pd.Timestamp.max和pd.Timestamp.min可以获取时间戳表示的范围,可以看到确实表示的区间年数大小正如上述计算结果:
' l1 P! C# ?! ]8 s5 a/ K
v+ h5 o' d- T9 E pd.Timestamp.max
2 _4 N! |; v4 a: c @/ d Out[13]: Timestamp('2262-04-11 23:47:16.854775807')
$ J) j" O$ N8 v! Q9 Z& N6 p
- b/ Y2 l6 J. v! X' z/ B: Y7 d' { pd.Timestamp.min
. `" ^) V* a: f% e% R Out[14]: Timestamp('1677-09-21 00:12:43.145225')
4 n" c/ m& o) n- L6 g$ T8 B
& f1 h3 l. \6 N2 S, h/ E pd.Timestamp.max.year - pd.Timestamp.min.year3 p" H% S0 N5 J
Out[15]: 585
' K; @# f6 v; \: h6 T# C 1
, J$ M H/ C) [% ~ 2
6 H# ?. \0 F8 Y+ ~3 M# C1 l 3
" T0 Q3 \1 s# K! A E% H 4% F8 q X6 Z' u- d; P @
5
! K; y6 o: R5 `* c; n* f 6) B B: S0 s1 b% a) P$ `5 E; X
7
/ y& S; W( v+ E% |2 g4 H 8
+ h Q3 ]( j. g 10.2.2 Datetime序列的生成
$ l; {& p" _) w/ a7 { pandas.to_datetime(arg, errors='raise', dayfirst=False, yearfirst=False, utc=None, format=None,
, k u: V! U! g3 T exact=True, unit=None, infer_datetime_format=False, origin='unix', cache=True)
" d" D9 \' h$ B' c4 B) _ 1
4 k, R' }! j$ w9 [& V8 D1 V; n2 M* H 2
- t* o) [# R$ u" r' Q' T pandas.to_datetime将arg转换为日期时间。$ F/ C( ~5 `/ `3 i
1 W( H% f' z; A8 U4 j+ Z: E# u arg:可以是argint、float、str、datetime、list、tuple、一维数组、Series、DataFrame/dict-like等要转换为日期时间的对象。如果提供了 DataFrame,则该方法至少需要以下列:“年”、“月”、“日”。
, g+ N( U9 f: P6 ?2 a- X/ d errors:
1 I- Q' x7 Q8 R g7 }" Z% ?, j9 @ - ‘raise’:默认值,无效解析将引发异常+ h$ ^$ p. j) u# |& P
- ‘raise’:无效解析将返回输入1 [* a, b" s2 P. J9 ^% @
- ‘coerce’:无效解析将被设置为NaT6 Z1 Y$ z' y# X; B8 o7 U. [
dayfirst:如果 arg 是 str 或类似列表时使用,表示日期解析顺序,默认为 False。如果为 True,则首先解析日期,例如“10/11/12”被解析为 2012-11-10。如果无法根据给定的 dayfirst 选项解析分隔日期字符串,会显示警告。
: s2 d) A5 Q" Q: R5 P& ~0 j yearfirst:如果 arg 是 str 或类似列表时使用,表示日期解析顺序,默认为 False。如果为 True,则首先解析年份,例如“10/11/12”被解析为2010-11-12。无法正确解析时会显示警告。(如果 dayfirst 和 yearfirst 都为 True,则 yearfirst 优先(与 dateutil 相同)。)! L- c" \. e" H. B1 y. i
utcbool:默认None,控制时区相关的解析、本地化和转换。请参阅:pandas 有关时区转换和本地化的一般文档
' t# Y( e" N w$ `/ t) e+ g format:str格式,默认None。时间戳的格式不满足转换时,可以强制使用format进行匹配。
+ w/ P1 \' h1 ~' _7 u1 b& q& h unitstr:默认“ns”。它是arg (D,s,ms,us,ns) 的表示单位,可以是整数或浮点数。这将基于原点。例如,使用 unit=‘ms’ 和 origin=‘unix’ (默认值),这将计算到 unix 开始的毫秒数。
5 I# v6 `' m/ m9 `: I: g, O6 B to_datetime能够把一列时间戳格式的对象转换成为datetime64[ns]类型的时间序列:
9 r' x! p W6 ~* d& l5 x pd.to_datetime(['2020-1-1', '2020-1-3', '2020-1-6'])
9 j& S4 Y% D, h
) @( v: W, f( L C! N DatetimeIndex(['2020-01-01', '2020-01-03', '2020-01-06'], dtype='datetime64[ns]', freq=None): W/ ]" n8 n) f4 E5 P
1
. f# s/ o' b7 ^! n6 k' Y3 H 2# ]7 W& Z/ r6 K: J$ S6 r2 M
3' r4 I1 n6 j8 F! @8 `( z
在极少数情况,时间戳的格式不满足转换时,可以强制使用format进行匹配:- r) ], ^7 O. }" K
5 d7 L `4 {( g9 I/ Y) V2 `3 ? temp = pd.to_datetime(['2020\\1\\1','2020\\1\\3'],format='%Y\\%m\\%d')
% J/ y+ {+ S0 P( C7 F, C% t% Z temp
' A9 ^* i" B1 s$ a2 [ Y$ B
3 `3 _8 q1 H4 C: F DatetimeIndex(['2020-01-01', '2020-01-03'], dtype='datetime64[ns]', freq=None)
8 u4 u: `2 x1 s# ~- Y) N( P 1
; ]1 q9 V3 E7 z, W$ r4 ~3 [ 2
$ d5 V9 `- J, y4 r. d* `: m( y 3
$ j! [3 b7 u0 A 4
. u6 m' J4 _: U* t3 \" ~$ z. b 注意上面由于传入的是列表,而非pandas内部的Series,因此返回的是DatetimeIndex,如果想要转为datetime64[ns]的序列,需要显式用Series转化:& R( q7 E2 {5 ^0 x* Y3 |/ ^/ e ]; x
4 q" e0 ~! e: [ pd.Series(temp).head()& Q Z0 s$ |2 s. b! |
9 e C3 a# \3 b; D2 x& Q 0 2020-01-014 r4 t ]% m9 f6 q! }1 ^
1 2020-01-03) `6 h% p1 G' ~ O8 D/ o1 z
dtype: datetime64[ns]2 ?% A9 p( f" h2 z C! [/ k; j, u
1
5 j9 n) `7 X: i" U- } 22 {7 l8 l8 t/ l/ B
3
- v$ F8 }9 J3 @% }1 p4 R4 ` 45 w: }- Y7 {& Z; p% C
59 F+ s) d9 |2 k4 R$ d8 k
下面的序列本身就是Series,所以不需要再转化。
2 h: [/ R7 J8 e ( b* ]; K1 W, q! U2 g& `
df = pd.read_csv('../data/learn_pandas.csv')
5 O4 ^: [" a$ E" b1 R s = pd.to_datetime(df.Test_Date)& Y% u% s$ K- f* ~; _' S& ]5 v8 {
s.head()! u) q' d/ s/ k4 c$ G+ W
+ `, F/ D1 P5 k' E; D$ t1 L( ~ H
0 2019-10-05
2 ?# R3 f+ x G$ e% j 1 2019-09-04: H: i) V! w7 p/ O0 S
2 2019-09-12, m3 c% ]/ W& B
3 2020-01-03$ f9 p U* `6 p
4 2019-11-067 q6 v+ t# d# N4 ^4 |
Name: Test_Date, dtype: datetime64[ns]
C# H! e! b2 f 1( p& L+ q V% e6 n- J) o% D
2
8 V' b _7 U9 H' r 3+ ?, l1 Y- F: R- ~+ a; h4 |% b
4. k' X3 U! [/ D, v2 \( b# |
5+ u2 l- o [- U: P. K; N
6- Q- L; o _4 |# S% T% b
7' g c, X1 |$ h3 ^
8
0 Y1 j% c5 N: ` 9' l& P* P+ Q( o! `0 Y p
10& P$ x" y( I) h' n. `' [
把表的多列时间属性拼接转为时间序列的to_datetime,此时的列名必须和以下给定的时间关键词列名一致:- E% I- \, }6 R
df_date_cols = pd.DataFrame({'year': [2020, 2020],
# e+ ^1 h: I$ n( F5 t1 E2 o* ` 'month': [1, 1],: L: o7 O2 O8 N* |1 t$ _. h# ~
'day': [1, 2],
. j$ [1 T5 Q4 u 'hour': [10, 20],2 |( l* F: e6 e
'minute': [30, 50],# [+ E7 c! @, _& t; r! \: B# `
'second': [20, 40]}): w8 o9 k! h3 W. }
pd.to_datetime(df_date_cols)& r+ i3 @4 r0 w: p! L
: `! E( n; u$ P9 S" t
0 2020-01-01 10:30:20
f% T2 N" r* C$ x0 ~4 Q3 k7 ~ 1 2020-01-02 20:50:40
; u; ^+ M, A2 n1 u' _ dtype: datetime64[ns]
' k3 y2 u9 n7 V( A$ U9 U 1
! B3 s8 r9 u% N2 e) H 2
8 Y* T* U$ j. {4 [ 3
o0 G9 R w5 g0 Q% t 4
' i3 R5 g" B: p 5
5 q! x5 D9 \3 q$ B2 Y" @3 y1 j 6) {* [% h" ?7 k' m
70 ~# L8 S6 w9 K$ Q# F' B
82 r1 w- f5 T! b. q/ \
99 X0 G/ q- ]8 l& A3 d
10: K6 t: D) z. D% [5 @& S1 _3 B
11- H: h1 [7 e: y4 J1 F' b
date_range是一种生成连续间隔时间的一种方法,其重要的参数为start, end, freq, periods,它们分别表示开始时间,结束时间,时间间隔,时间戳个数。其中,四个中的三个参数决定了,那么剩下的一个就随之确定了。这里要注意,开始或结束日期如果作为端点则它会被包含:! z6 G3 D1 y4 t$ }' }6 M6 f: F
pd.date_range('2020-1-1','2020-1-21', freq='10D') # 包含 O: z& u6 `& s* \
Out[25]: DatetimeIndex(['2020-01-01', '2020-01-11', '2020-01-21'], dtype='datetime64[ns]', freq='10D')
9 O( m! Z- J# l, B+ }/ X4 K. [6 d8 R
: d1 C' ]6 f( Q4 t pd.date_range('2020-1-1','2020-2-28', freq='10D'). e3 j3 E; g5 `
Out[26]:
; M6 I! X. _/ U! a1 m+ c$ m5 q DatetimeIndex(['2020-01-01', '2020-01-11', '2020-01-21', '2020-01-31',/ ~8 U4 ?& ]6 U! z3 k
'2020-02-10', '2020-02-20'],( v# `* P6 ^1 ~
dtype='datetime64[ns]', freq='10D')
* t7 f7 q7 E3 |+ n- L* z 9 ]! s* a2 o$ I# G" w7 P) [& R
pd.date_range('2020-1-1',
: I9 b) o$ ]2 k8 Y6 d% Q '2020-2-28', periods=6) # 由于结束日期无法取到,freq不为10天: ~2 [' T( R! {9 a; x" n& l5 H
6 X; I( m, _6 f- J4 o6 a, E% O" t
Out[27]: - B5 i( I1 S! c5 f. d0 W5 t
DatetimeIndex(['2020-01-01 00:00:00', '2020-01-12 14:24:00',
* v9 k; K5 e- z3 W& `) L9 E3 s '2020-01-24 04:48:00', '2020-02-04 19:12:00',8 H8 L% x y( {+ L
'2020-02-16 09:36:00', '2020-02-28 00:00:00'],8 z% [8 j+ z* q8 J! D: p! `" w
dtype='datetime64[ns]', freq=None)+ G, I, I3 ^( f
! p' e- Q" h/ i, B* K6 t
1
# X% d" X$ c7 r4 G 2
/ h& M9 U( B" y1 ~% ?$ \5 l: q$ F( E 3# Z/ R6 K( m+ l: h9 b% e+ ^
4 K" H7 o7 y8 y- n
55 G. A1 H9 D( }1 ~
6
. k, ?1 h8 W( O# v& p" Y 7: `5 F( M% l0 ~. B# g
8& q' q8 o' K, v( ^& W2 g
9
- J5 D; L6 j/ a 103 i" J2 d4 l6 J
114 M" a* L0 m' |
12
3 i' ]* M% C6 Y/ G+ P4 [: l 13
, T0 T2 q$ U7 {, c 14
1 p% p8 s: ^7 ^* s; s! B3 L! n 15
* ~; g' [) m4 |, x# n( o5 G7 ` 16
b3 U- y( \! f, J* h% Z. C 17
" ~; ^+ [: y8 C8 i4 O: Z 这里的freq参数与DateOffset对象紧密相关,将在第四节介绍其具体的用法。: G* A! l& d& v- c9 ]& O
1 `' A j# |2 f- q# F 【练一练】; o5 o V; l, r# B/ K. A& R, n: ]
Timestamp上定义了一个value属性,其返回的整数值代表了从1970年1月1日零点到给定时间戳相差的纳秒数,请利用这个属性构造一个随机生成给定日期区间内日期序列的函数。
. x q# ^. w% C) o# ?0 f% c# i7 J# W. A
$ k4 z- S- T2 Q2 \, X! Q& { ls=['2020-01-01','2020-02-20']# u, ^0 a( B0 ]" S2 @6 C
def dates(ls,n):
; I* M0 x) x# ^6 ~% r% G min=pd.Timestamp(ls[0]).value/10**9# _ h7 N0 F5 ]
max=pd.Timestamp(ls[1]).value/10**9
2 m' ^9 N/ I' ~& u0 ~ times=np.random.randint(min,max+1,n)
( w: j, ~+ l/ r. ^ return pd.to_datetime(times,unit='s')
9 E ~1 S: s0 o, J/ G5 L& t! n dates(ls,10) 5 Z0 f, e/ J% e ?! @3 z# r9 u
5 S7 s9 W3 G' p0 Q4 p& R3 E DatetimeIndex(['2020-02-16 09:25:30', '2020-01-29 07:00:04',. Q" K* j U/ \, ?
'2020-01-21 12:26:02', '2020-02-08 20:34:08',- m2 O$ C3 ]8 u8 m! O; A3 |$ o
'2020-02-15 00:18:33', '2020-02-11 02:18:07',
6 v# C+ u. h9 @4 z7 p: G '2020-01-12 21:48:59', '2020-01-12 00:39:24',3 I1 S& S) O3 Q A- `' i
'2020-02-14 20:55:20', '2020-01-26 15:44:13'],
( h ^) b" E. T0 G7 A dtype='datetime64[ns]', freq=None)
% H7 N6 l3 f5 a7 ] 1# L. X {) n' a6 p; y% I
2
. Q% a( U6 d) { 3
+ z0 p) K( Y5 u4 O 4
7 X, b# H) o1 n/ _! i 5
& F; e. r, @5 D& ~1 f9 G 6
j' m) P! m/ x& f' J9 B 76 ~+ Q5 t/ w6 F. d" S9 m8 R0 S" k4 L
8, Q6 [# O/ ]( G k' g* x: @
9
# M9 c% m- }, x 10( B* v; W* T; ~
11/ P& o- c9 ?/ Z6 ~* x
12
( m4 @# r/ t6 V8 k0 _/ v 13
) l1 Q; P& `3 ^; t4 x 14
% f1 \# Z, R! \% g4 s7 h asfreq:改变序列采样频率的方法,能够根据给定的freq对序列进行类似于reindex的操作:
) f9 c' R) ^4 F* L6 X. r s = pd.Series(np.random.rand(5),* c y; x& J) p% Z: e
index=pd.to_datetime([- r* c1 ?6 x9 _8 P
'2020-1-%d'%i for i in range(1,10,2)]))
* Q, G' h: Z2 f4 L6 h 5 t9 ?1 g, D/ `6 t
, N1 d/ R% ]: w4 O1 b B0 j% C% n
s.head()7 z6 y! I9 W2 P- z! M }% Q- Q
Out[29]: 3 @- Y7 F* A1 C$ H1 O! w% u* E* Q
2020-01-01 0.836578! o) w3 s' ]9 r6 D, H
2020-01-03 0.678419+ U, A3 ]* X6 l5 b1 z
2020-01-05 0.711897
2 S: Q+ a4 y* E1 Q" k 2020-01-07 0.487429/ z$ k8 ]) K& ~
2020-01-09 0.604705+ j8 [- z# E8 c/ a% P
dtype: float64
8 J# g( X8 c" l; _8 Z! g( g1 i 9 A6 G$ i' Z* I2 i, y5 O
s.asfreq('D').head()
) X5 Y! e! r6 |; I! D Out[30]: ; Z! A0 I8 }) {$ v8 @( S
2020-01-01 0.8365782 N' m8 |6 @# i( t1 T, c
2020-01-02 NaN
: e+ U: f( R- D1 a2 @# H 2020-01-03 0.678419
0 H& y' n& ? J6 [6 z 2020-01-04 NaN
D* U' r: c# S4 i6 L; @1 } 2020-01-05 0.711897
3 C6 L5 Y4 H: a% { Freq: D, dtype: float64+ n9 {' Q) s5 v: }2 B
" q1 S' `. A4 a0 W
s.asfreq('12H').head()
3 {4 [4 {7 b0 ~; R4 a9 i Out[31]: % c5 B: f0 V$ P
2020-01-01 00:00:00 0.836578
0 b z7 Q$ ?/ M 2020-01-01 12:00:00 NaN
" ?. d* r* O& V# V o 2020-01-02 00:00:00 NaN
* O& ]: Q3 ~4 e, @- u- y. x' G 2020-01-02 12:00:00 NaN
( N6 \8 u. G D3 v/ e 2020-01-03 00:00:00 0.678419- s+ V/ h2 D4 p5 P4 G' k6 k
Freq: 12H, dtype: float64
3 E, l5 a" D H& j2 G% n9 U * W) W! ]: [7 Q: t |: s" o
1
( h) M' c! r! R9 y, o 2
) x H# ~! Z9 v ^' t 3
- v; w# Y8 c# p4 @9 [ 4
- I; K) [. T: ~+ v 5
+ h, ]6 C' y# [: g' o 68 b! R6 N$ w1 K2 N1 @# Q
7* Z" z0 C2 K3 m. a2 ~
8
& } s# u7 R0 R. n 9
! j ^& v+ ]2 N" ^8 n 103 k1 D2 Z. M) `. o k
11
+ ?+ s* o5 L l; _8 T+ i 125 a. \8 _9 Y& \ o. ], L: n
13
+ V* q+ u, t- R- u5 a* U5 @ 14
+ f0 b( _" g C) |" t3 ^9 a4 Q1 I 15
$ ? G5 L( A+ F& m6 j 16
6 X [$ _. X" ^. [$ Z4 g- t; c F 17+ `& ^2 r, C+ W; M
18
- k5 ^% q+ L7 R! _ 192 r7 _% U: k: ^6 y5 p
20 ]8 @: F* {: t# s6 a( m
21% M ]/ V! |$ m
22
2 F' J; H( D, p# Y( q 23
! X3 s0 j* z; T$ P 24
! X/ T- [% h/ W& X! I 251 c$ \) k1 i, x8 L2 a
26
$ ^3 ]. w2 D- {8 K1 i" i6 u 27
( M& J `8 b! Y9 ?1 W0 p 28
- d; [3 d7 I* P+ B7 h2 k 29
2 h: _! K6 G6 b1 f6 h" h 304 @3 |! w9 u* g L
31: u9 P+ R$ H# c+ V& G$ g) m. q
【NOTE】datetime64[ns] 序列的极值与均值/ R6 l! f0 f' K a5 ~
前面提到了datetime64[ns]本质上可以理解为一个整数,即从1970年1月1日零点到给定时间戳相差的纳秒数。所以对于一个datetime64[ns]序列,可以使用max, min, mean,来取得最大时间戳、最小时间戳和“平均”时间戳。* p8 k/ D! Q% O
9 _; O& d, z1 M) q6 I5 U6 |# D 10.2.3 dt对象# \% @9 T0 a' I9 m% U2 }) J
如同category, string的序列上定义了cat, str来完成分类数据和文本数据的操作,在时序类型的序列上定义了dt对象来完成许多时间序列的相关操作。这里对于datetime64[ns]类型而言,可以大致分为三类操作:取出时间相关的属性、判断时间戳是否满足条件、取整操作。* ^, Q5 T1 Y, u6 ^9 v( E- q& y0 W
3 Q! d1 O' T" e. U* q4 F7 l' a, H1 h
第一类操作的常用属性包括:date, time, year, month, day, hour, minute, second, microsecond, nanosecond, dayofweek, dayofyear, weekofyear, daysinmonth, quarter,其中daysinmonth, quarter分别表示该月一共有几天和季度。% p" t; ?& P7 L% K
s = pd.Series(pd.date_range('2020-1-1','2020-1-3', freq='D'))
6 \$ U6 E; [# H9 I+ i. b
% h! b2 d0 K) z' T7 ]# B0 }" f9 N s.dt.date
0 G7 t$ c3 W& W Out[33]: / w( L1 V7 N/ `4 W6 ]! o! p
0 2020-01-01
/ y4 j9 P; h" S- k 1 2020-01-02 t; e9 F# P& v3 J; D& ^) J$ {3 X" r
2 2020-01-03; [5 ?3 x' _! }6 ?
dtype: object
f; h1 M$ x6 S" p, V4 A # H' o1 V" U, V5 ^+ N3 `# k) n4 ]+ Q
s.dt.time
) O0 f! S7 B5 o b5 W Out[34]: 5 e6 \' `- R1 _" u
0 00:00:001 B6 U& r, N6 b, U3 c
1 00:00:003 |( S; F7 {% {+ t4 q9 o
2 00:00:00" t2 U+ s( m7 G! f& P
dtype: object; u( u+ y" C* f. ~3 ?
7 F1 {: M, M. K0 R$ V, C8 L4 q7 M
s.dt.day' @( q* {) h1 v4 _; Z/ E1 x
Out[35]: - Z4 `. k2 P) b) G8 U# r+ `
0 1
- r1 }2 I; p* T* i8 x2 P 1 2
) P& x6 V! y+ S7 r4 | 2 3
5 L3 ]' v6 `1 b dtype: int64
3 N9 C8 t s. w1 { * l- n# u: A$ i- [1 Z( C Q, O% m
s.dt.daysinmonth* Z; p/ n$ c5 s
Out[36]:
: P; K6 T: C' J 0 317 J# g; Q* C* o6 r, b3 H$ r
1 31- X+ f ?4 C/ D% H/ T
2 31" P* ~- L! W' B' P. Y
dtype: int64
' s& y* |0 C) x0 H
: ^" m, D: W0 X 1: ]& M0 O# w0 S0 o
29 l) ^8 Q+ K: ?- c
38 F' h6 U, y4 p: _; i
42 m6 T8 |+ v+ L1 G% u: f
5
+ P% ~( S2 X1 j9 v* R/ L/ ?, ? 6& y& b- o( j$ e( }
71 f4 t. q& x7 J' W& J0 G' k
8
8 ?0 [0 ~/ T* h: N" l 9
0 L0 T0 [# T7 X7 M% t 10% m$ e: v! v3 J# ]
11; `, G7 H: u6 P
126 I6 V6 R: R2 H E( P
13
0 S$ K3 ]' j3 }) c2 B 14
* q% e! x- q, P+ j 15' ?! w- `# X& z9 q F* d9 f
16
+ ]3 E1 c( Z+ M: V4 j! s 17
0 R/ t! L+ K3 z' @' }( r/ \2 z 18: f/ F1 z2 B/ y
19! Y- d/ B" e. {1 S ~
20' a/ [1 [$ V% \7 d
217 S% G' s* ]3 g& V0 `
221 X! u5 O) n4 c8 t! Y1 p
23
( ]3 G4 {7 Z! F, C$ i' k- h( Y 24
% O. l6 k4 g$ o 25- ^! P7 r) q' s6 h4 C
26/ z, K+ b0 \. Z0 j+ Y. e
270 |( A$ d! W, D3 K' A
28' E% {5 P8 g" Z$ k( e
29
% Q& R7 a# O# W( U& S% I 在这些属性中,经常使用的是dayofweek,它返回了周中的星期情况,周一为0、周二为1,以此类推。此外,还可以通过month_name, day_name返回英文的月名和星期名,注意它们是方法而不是属性:0 Q' D. c6 s# t( j
; H; c, @ A6 x+ r! |4 v( Q s.dt.dayofweek1 a7 H' N, {$ y9 Z) p: ` v! H
Out[37]:
" `" \* B% r4 R5 I2 J. I 0 2
* O9 N/ D0 H) n* h1 \ 1 37 [: m$ p$ K: ~" _
2 4
( e- u- R n0 h8 `: X+ W: z; E dtype: int640 E$ |0 S$ B. e
7 O ^. b6 ]3 S3 y. S s.dt.month_name()% l$ o0 w1 v9 E. ?- _7 S) D
Out[38]:
7 r6 m# Y+ q& c9 C 0 January
' W' u; l: i$ n: G; x: d. W5 y 1 January. D/ J$ {. V% O1 ?" D( a L
2 January9 F4 S6 \1 y* t h& J. ]" H6 Y" g
dtype: object
! V2 g8 g& k7 k& ^
6 C" R0 L; M" `$ W2 l s.dt.day_name()+ \5 m, P; D6 P6 |7 g- j
Out[39]: " m; m, C. P. I: h$ q& v8 |
0 Wednesday
+ u' {* x! O0 U8 r9 V6 { 1 Thursday
% Z% c+ H# t! {( I/ q0 P 2 Friday
* T* ^; E/ a/ _1 j( c) v dtype: object$ x/ [5 K6 I( k. W( u' e; `+ x
0 B. n8 [8 f8 `4 {! A6 E% y 1$ o" A. ?& I' O& x V9 Z
2& v3 r1 b! v6 h6 E, u
3
, j* _9 G0 t. k8 n1 y. J, f* l0 V 45 J# c+ e! y6 s$ X: R" s, q
5
3 z7 N+ w, j8 B8 m9 X4 ]2 z$ q 6
W% }0 @: a7 n* E, g 7/ t, j8 [. l5 j5 u5 G. D
86 W+ [- @4 ?% r4 D5 u h
94 [7 p3 m# ?4 Z; d* r4 W2 X& _
10' R, t- N# [5 r1 Y6 y
11
+ x8 o0 O& p. M+ Z" r1 } 12
6 z; `' A; ?1 X ^ 13+ v( |7 }. i$ `* g/ `4 G V! V
14 D( r0 J2 p/ p, q2 t: j4 F) H
157 E! m( g5 Q8 ^; ~
16
5 F( O1 A) F9 q" E' u* n2 c 17# t2 @$ W+ n/ _4 C& I
18
i9 C& v( g1 E5 Y+ y6 G' O 19( s) Q# s: P$ x& q+ T( Y5 @) j0 O
20
, v4 z- t: m( H' n9 m! Q9 p 第二类判断操作主要用于测试是否为月/季/年的第一天或者最后一天:
! d8 S: U. J8 D3 t: X s.dt.is_year_start # 还可选 is_quarter/month_start
# M: z( o: v1 ~) S Out[40]:
0 M. Z4 G: {4 Y& z- |3 E) ] 0 True6 \8 ?0 P- I9 r" @9 Y+ N
1 False% j; v% {7 [; H$ U e
2 False5 V! O7 W" c K$ X- p2 l
dtype: bool
3 c+ n2 q! `0 [" }5 @5 \8 Y( a6 j - k' n( r. R4 \* K
s.dt.is_year_end # 还可选 is_quarter/month_end, p/ }$ a' L E/ ^$ b% x$ g3 J3 m* M
Out[41]: 1 a2 R0 {- P3 j& O6 ~# w& c
0 False
. D8 z) u: h- S# G 1 False
% Q0 L- z0 ] K9 V' Z# ~9 i, R. \8 v% }1 } 2 False
- M! ?5 s2 v5 {! f dtype: bool
' T! l( N5 _. }' r$ [% [ 1
; O# x/ B5 m& R3 V# |0 z 21 V! c; O7 K- D/ w
3 [% m7 m8 l; ~4 O5 H" E
4
8 V" K# a3 t6 t 5
' _ h/ V0 H" K, t' E 6
7 ]1 J' \1 V+ C# c8 m' q: i 7
# _9 Y9 p* p' a! w/ V 8
1 ~$ U& t; V. {& A8 B* u8 c( `% o/ W. w 95 v* K+ k5 e, z- ^
101 l0 f: Y" {- X; H/ V
11
( m7 }( W } p, B 12/ Q1 H8 d, ]% ]8 e2 Q
13- ~5 V% Q0 Y; ?- q( {' L) m* x; M
第三类的取整操作包含round, ceil, floor,它们的公共参数为freq,常用的包括H, min, S(小时、分钟、秒),所有可选的freq可参考此处。
! o& w( b- g; {( p* u s = pd.Series(pd.date_range('2020-1-1 20:35:00'," l0 u8 M. Y$ v/ \% F9 @5 h4 r+ ^! g
'2020-1-1 22:35:00',
6 A( ~$ [, u- D freq='45min'))
U, M) s h: j7 a 5 H& W5 R" l6 f) E) p- Q
. O$ Y" v$ Q4 S' P s2 _; {2 ^( d' O9 G9 {
Out[43]: 8 I8 O: L. D+ D6 c, D2 h
0 2020-01-01 20:35:00
0 \4 j6 {7 a( x. M, e, ^ 1 2020-01-01 21:20:00
7 L3 ]5 V; K/ R2 [. H1 L 2 2020-01-01 22:05:00+ U; w! D6 a) l/ Z: \, u
dtype: datetime64[ns]
6 j$ w+ Z- W' h / F% R/ [, Z5 R8 x/ Z
s.dt.round('1H')
; Y# m! z/ ~4 x Out[44]: " p) N( E# H5 Q: x% D0 ~1 w# m
0 2020-01-01 21:00:00; J' C U& W' v5 @
1 2020-01-01 21:00:00
" v0 a L* Y- v2 K+ J2 |! q+ l7 h 2 2020-01-01 22:00:00
- ^: J9 y7 @- n: {# o dtype: datetime64[ns]% a3 P: L5 m; j& J. _/ c
- e6 ~& z; Y& f, s6 M( G/ T
s.dt.ceil('1H')5 v [5 t& C5 F& ]
Out[45]:
& }( P# }7 d# A6 e7 m/ c% Y 0 2020-01-01 21:00:00
4 g" \/ C! i Y( H2 r1 I 1 2020-01-01 22:00:00
2 I% ^( y' d( L2 i: }6 S# ^ _ 2 2020-01-01 23:00:00
/ o! d [1 x+ q6 l6 |9 W0 C dtype: datetime64[ns]) @: H4 G2 p6 T
+ ~7 _8 b% }# ?
s.dt.floor('1H')/ V$ J$ Z* H- C1 V! S
Out[46]:
2 Q/ W H6 d: G 0 2020-01-01 20:00:00
* j" l6 p4 Z; Q. R 1 2020-01-01 21:00:00; m- Z; v: o, J
2 2020-01-01 22:00:007 z2 @1 Q6 l! P
dtype: datetime64[ns]
+ I( O0 y% _0 n3 i $ F' v, d, Y8 Y* @8 C- }
1! I4 ?5 v- ~' x8 v# c. H* o) E
2 a6 g' k3 j$ V! O( p5 w
3 t( j0 E% E+ z6 m% B9 H
4+ ~/ Y) B# w, j6 B7 W+ j
5
; D# Z1 X- @1 a8 E 6
. a. z, [ O7 K1 @7 u 7$ ?; | |$ O' Q3 K* S
83 g* x$ `9 C; L; J) `
9
( U; L2 K C; l9 E; }6 G( g 10
8 E* u l" [- a0 t 11
1 m* Z0 o8 G$ o, m( x. c ? 12
+ \% e/ r9 j: b7 h9 Y7 b( X. o- Y! s 13
. R$ v: L; e8 ?" Q) ]) Z 142 |+ r2 W1 @$ j+ z* Y @
15
9 ]/ O* B1 e0 @8 |6 A. |/ R 16
2 ?- J1 v. g5 X4 M6 S Z 17
/ \) `, O$ e# O7 X. a 18
& u6 Y8 T' |7 A; u" E% D: |/ ] 19
' L: Z: j' H, C, i- ]' G- T 20
, ^; d8 C- k$ J6 Z6 j* o ~5 k 21
! |0 r f$ K6 m: |5 E( | C1 H# C# Q 22
" `1 g2 B5 h& p) f 23
7 x3 _2 m2 E1 ]4 \3 a' g8 D 247 b/ [: w& H3 P7 H8 Z, ?" w
25- ?9 }5 M+ Z2 [5 K
26
Z% |" r5 e9 D& H B0 `( L, u0 @ 27 F9 J3 N- F% I; A
28. O% D9 H8 a7 S! u: }) y( i
29
, j! k0 h$ h U 30
) u* l$ u- A" g& z 31$ G8 `2 K9 f1 B
32; P$ `; R- S4 f' i
10.2.4 时间戳的切片与索引: m: a3 W/ ~ n$ n, D9 {6 R
一般而言,时间戳序列作为索引使用。如果想要选出某个子时间戳序列,有两种方法:# {) W( d- I1 m! y4 w3 k
) \% r2 I V, [ 利用dt对象和布尔条件联合使用
7 x2 [* u. E4 A3 P 利用切片,后者常用于连续时间戳。
+ q7 g0 C; w( r% ]. r: _ s = pd.Series(np.random.randint(2,size=366), index=pd.date_range('2020-01-01','2020-12-31'))3 r0 A5 ~' [# b; s9 P! ]% D
idx = pd.Series(s.index).dt
; w) g/ }2 ]* O! t4 N s.head(): `2 X" ^; c) `' F
! i8 V7 b! X0 L4 f* ~8 Z4 e
2020-01-01 0; Y& ~: k$ ^5 B" J5 c" @4 U# D8 \
2020-01-02 1
- j- T/ ~# B ^6 ?( ?8 R 2020-01-03 12 @) Y- v) _ S2 n
2020-01-04 03 Z. f8 S a3 h! s
2020-01-05 0, Z3 }: n& c/ U, U# k V+ H
Freq: D, dtype: int32; L7 }/ L4 n# Y* `# j5 {1 {* {
1& x: [# x3 h+ |" s
27 [" k: f; ^7 P7 L4 z9 X
3; f3 ]+ w4 B: c9 T! H& L
4
$ B3 G; c3 n4 Y( I0 Q3 P( Y8 o 5
/ c4 p6 F7 Q% X- T( T 6
& ~# c: t' @3 ^4 P3 }+ h 7
1 u3 m- z; V8 X: b: m 8
0 C" P: ^3 g+ u( I' n$ E. o j6 Y0 V 9
0 R& C* i$ Q7 M" |& V. |* p) C" L) u 10
# r) F0 U8 a7 K% o. G+ D# W Example1:每月的第一天或者最后一天- ~" k% N2 ]' I, V% ~
- f% c. k6 H. X- l, ~ M s[(idx.is_month_start|idx.is_month_end).values].head() # 必须要写.values
$ Z: Z- E" U6 F9 k4 H# C Out[50]:
4 F9 @% G# D! C 2020-01-01 1
6 t# I; `# q: Q' ^ 2020-01-31 0# p8 m2 q. g- V: M$ R" q
2020-02-01 1
6 r7 R; Z8 @( O' T! k, x 2020-02-29 13 v3 [! X4 [% s* c
2020-03-01 0
( Q/ j! f5 c! x( W# k- t dtype: int322 @ p2 }( g5 |# Y* H) P. {
12 l5 L5 q5 \/ J" ?: w# j K' Z
2
( I3 z8 T% ~- o; j 3% C9 G/ @4 d$ c; K! J: Y- Y
4
, d% M* J5 K. |/ \9 k. ]* G6 p( X 5, n( _+ q; f* t
6
' a" x8 c: e" C7 O7 [$ [. I) D 7
+ o5 D+ v6 W1 q, | 8
3 Z, r3 Q, f4 N8 W Example2:双休日7 y' K* e' L z
' c! A% P; ^- b6 I$ m2 k s[idx.dayofweek.isin([5,6]).values].head(); W7 K- v1 p: O: D! c
Out[51]: 9 Y7 N, L1 P% r3 V+ w1 g
2020-01-04 1
# m9 W2 _0 i6 R) b) J6 g3 E: N 2020-01-05 0: f5 C; k! H3 i. a: N
2020-01-11 0
$ Y- K# ?3 v1 A; w6 U7 j 2020-01-12 16 U" m. _. C# i V& L7 T7 Y& B
2020-01-18 1
# l$ b) {# I5 n dtype: int32
, y4 |$ |% U3 v, z' D( u# P 1
) ^" I+ m" a$ n8 f1 T2 a: d! D 22 W, W4 q0 V7 l8 k4 k$ [
3
0 F1 {" ^7 E% L: D+ i 48 Q" _. r5 c2 S# n
5
) s# ?: ~5 ]$ m- ?2 f 6
% k5 _/ n8 I, Z0 P+ y 7
# K6 N2 ^4 B6 A. B: d- P. R. b 8( W' {8 u9 j7 c, {" J
Example3:取出单日值& |' A8 X9 Q% l. j3 {8 r$ g
! ]* c( ^7 n8 c$ C3 e$ H- Q s['2020-01-01']
3 P" t/ [ n" t/ o3 n, d Out[52]: 1
, N# V5 _2 z) V5 ^) u5 M. O6 _4 \! u ; ~" R5 f' A! ^% Q4 A7 i
s['20200101'] # 自动转换标准格式2 p( C) T* N& N4 }
Out[53]: 10 Q) F9 o E# o9 E" Y+ o5 I
10 @0 e) \- ^. G+ N
2
6 R/ q7 y8 t' l! F s1 K9 |. R! h | 3
' o/ |( ?, l- Q6 U: V1 x+ {1 E 4( S1 t# C9 j2 y3 L( L; w+ C; G
5; m7 C" O. a4 d
Example4:取出七月
& b' p1 `$ {8 ^0 p4 S) L7 m 8 k4 E( A4 x U2 l
s['2020-07'].head()
* r; Z' E* ]/ s$ x/ ^% X Out[54]:
2 q, m6 e) {* s6 x* j# C- U# t 2020-07-01 0/ n# t! p8 M' l; v3 q. z
2020-07-02 1
) ]- R# Z+ R# c$ L 2020-07-03 0
" R" n( P, g5 P8 }* q7 W4 w 2020-07-04 0
; v7 e" H, C" J 2020-07-05 0
7 n" n, a8 ~! x3 j/ U8 f3 T Freq: D, dtype: int32
8 O% [! e8 P6 _( W 1) O' }! L) r0 |- z6 i# V+ l4 m1 @
28 s* ^+ o1 z2 r4 V: `
3, |; u: c* P5 Y
4 H5 \4 u# w- }& P: o
5
' f% `1 ~7 i$ B; @9 B3 q 60 {# m7 L1 {& {! d! H0 d* b
75 \7 \" c. i0 z7 S, Y
8
$ F1 n8 Q, d6 j3 W# R8 P( n Example5:取出5月初至7月15日5 a) v# }, k5 s5 p& Q- B m
. I) f" v$ h! c- H s['2020-05':'2020-7-15'].head()
0 Q0 @1 T. I F P- d Out[55]:
8 S+ _5 A3 u0 t. M0 }9 o) L 2020-05-01 0( o S2 T8 X, Q! ~1 M: n
2020-05-02 1! j3 e1 O b! N1 u. c/ q& s
2020-05-03 0# f/ w( f" b( W8 v+ _1 b
2020-05-04 1& W& O2 g6 Y8 _; z$ V( \: j
2020-05-05 1: V: f$ n6 D8 ? N% o ?( E' ?
Freq: D, dtype: int321 f( {% a$ r' ~$ _" l; a' R) F
* J& X# W: L4 R k r
s['2020-05':'2020-7-15'].tail()
' R$ \! I8 Z; s0 n) Q Out[56]: 5 d7 J/ x0 M I8 X
2020-07-11 0
0 \" ^) M* a5 z9 u4 C 2020-07-12 0
/ ~/ ~. E6 q0 ^. n7 l 2020-07-13 14 z1 d8 B3 R. ^6 {
2020-07-14 03 B7 Q; X! A; v B
2020-07-15 1
! D( _( A( P, ^ y- L: t. J- h Freq: D, dtype: int322 o# d6 e( B1 S; Q) _2 `7 |
5 y$ p# r- ^2 p, J4 W' | 1
$ ]; W& _/ c8 t% h, g 2' I; |1 ?6 g2 ?0 {6 s
3/ T! o$ [/ l& e
49 q( f( F4 S k7 C) s4 I8 L1 l
5
) F2 B/ R1 B1 o) m 63 v; ~8 a( K) R; q/ U- A
7: K' l) z6 [* K8 Y0 g
8
6 B( [# F' r( Z; \4 [: Y0 d 9
& x9 b7 R( a" y$ ]$ E" }2 P! A 10
2 g3 K" [5 U/ w4 f- H& P 119 p. J/ w! ^& v# j4 n& @/ G
12
& x1 [" H, s: A" f/ _ 13% l; c+ v8 G8 p$ I5 P7 a& j1 ?. n
14
+ x2 I7 G* Y4 J, [- R2 v 15; V. Q% G/ ]( g6 o! c/ t
16
5 R2 z# W8 Q- Q7 k1 q+ s/ K0 O 17
# l- v }4 H1 T; \3 a% B i 10.3 时间差7 v+ c8 J2 w, l
10.3.1 Timedelta的生成
- B1 Y! L3 B( Y6 u. O' M" }/ B: X pandas.Timedelta(value=<object object>, unit=None, **kwargs)+ ?4 h( m9 C: l2 ^" C
unit:字符串格式,默认 ‘ns’。如果输入是整数,则表示输入的单位。0 D2 I7 z6 n0 b
可能的值有:
' R% T8 q/ n# F
$ S* O* a) u! N2 } ‘W’, ‘D’, ‘T’, ‘S’, ‘L’, ‘U’, or ‘N’& T7 K' ^" @+ I6 U4 C* y+ R
‘days’ or ‘day’ B0 v, [# }' S( L$ b1 {% M
‘hours’, ‘hour’, ‘hr’, or ‘h’; G q0 N& @( t. l5 l0 T% L
‘minutes’, ‘minute’, ‘min’, or ‘m’
2 s* f# A; g8 P/ B ‘seconds’, ‘second’, or ‘sec’
) T) `% ~. |1 O3 ?& @: n! v6 w6 c 毫秒‘milliseconds’, ‘millisecond’, ‘millis’, or ‘milli’
4 ]9 V7 I8 Z8 k+ W, S2 [ 微秒‘microseconds’, ‘microsecond’, ‘micros’, or ‘micro’) J6 i4 H5 p4 a- m; I
纳秒 ‘nanoseconds’, ‘nanosecond’, ‘nanos’, ‘nano’, or ‘ns’.
2 \( d3 S+ E( p, m2 G 时间差可以理解为两个时间戳的差,可以通过pd.Timedelta来构造:
" n& r. U& ~" V pd.Timestamp('20200102 08:00:00')-pd.Timestamp('20200101 07:35:00')& z$ k* f3 v6 e2 R" }! x
Out[57]: Timedelta('1 days 00:25:00')6 e7 u$ [5 U2 \
: k% j/ [* Y5 x* s, M/ [8 D: H pd.Timedelta(days=1, minutes=25) # 需要注意加s
/ i" A# Q0 {+ L- W% k Out[58]: Timedelta('1 days 00:25:00')9 `; v0 J1 z. L- z9 }
4 b2 `9 B8 Y& F- m S
pd.Timedelta('1 days 25 minutes') # 字符串生成
2 W% @2 A1 X- e% \/ P! A Out[59]: Timedelta('1 days 00:25:00')+ S2 M( Q% k" [
3 L: U, V; @2 H. g8 j
pd.Timedelta(1, "d")( z% T3 c* j" ]* k
Out[58]: Timedelta('1 days 00:00:00')
) A9 L" l' m9 G2 P7 \ 1, |5 p7 X- i' G" u
22 o0 z3 @& f5 x3 F% n
30 V' I4 X ~2 @" {) T
4! D9 |5 q: P- u; s
59 P: {% Z; K9 h4 }
6
5 u' a$ ^: g% u% ` 7
6 X3 L- o5 Q$ x" A 8
2 M$ ?2 r* {9 d B# p 9" U; |. H$ l. g
10" s7 {. P! ?! E0 k0 W6 ]
11
3 L- V; G3 _- k l/ F& _ 生成时间差序列的主要方式是 pd.to_timedelta ,其类型为 timedelta64[ns] :( f; B$ E5 {- m" o
s = pd.to_timedelta(df.Time_Record)% Y3 _# C; `3 C8 R/ _! s
4 M* L4 W/ W# t. K4 y- [
s.head()
# c4 u" c+ z2 `8 F3 z Out[61]:
% b) Y1 d m6 I; W: b; g 0 0 days 00:04:34
2 \1 ~; Y3 Q1 i7 c' q 1 0 days 00:04:20
# |; ?- ]& i9 V4 _' Q 2 0 days 00:05:22 s$ _7 _; S( Z
3 0 days 00:04:087 ^8 x; [ ?. f, U
4 0 days 00:05:22
9 j9 Q) G0 p; \7 E9 | Name: Time_Record, dtype: timedelta64[ns]
+ g/ d: Y' h+ u$ ~4 q 1
7 M0 C( V. I/ m4 w, `3 | 2+ N& K |$ z) k- x. D, ~5 }4 k( f9 r
3
6 K. R+ \- o- @/ { 4
: c$ m8 |3 L% B- i" J7 j+ {6 v 5$ T6 w+ |8 ^6 h' f
6
, t j7 z$ f3 B 75 f2 j0 C# |2 D9 Z/ M: x6 D
8& g- q: P1 W# Z4 }4 o
9
; u9 f4 C, a$ g% b8 `1 l v 10* l: ?1 j( o2 G- X$ _
与date_range一样,时间差序列也可以用timedelta_range来生成,它们两者具有一致的参数:
$ G, \' F( ] w2 ^9 t4 ?) W7 l pd.timedelta_range('0s', '1000s', freq='6min')) p# @# r& M2 C. _9 B& b
Out[62]: TimedeltaIndex(['0 days 00:00:00', '0 days 00:06:00', '0 days 00:12:00'], dtype='timedelta64[ns]', freq='6T'). ~' E9 M& ?2 D! R4 K
# E7 P ] B; M* A2 r G
pd.timedelta_range('0s', '1000s', periods=3), ^3 C3 ]; y' T: X$ r
Out[63]: TimedeltaIndex(['0 days 00:00:00', '0 days 00:08:20', '0 days 00:16:40'], dtype='timedelta64[ns]', freq=None)" }. l1 q( R/ k2 G% {5 z
1
% o6 q' X7 T& O5 k8 F 2
% _ k4 u9 ]8 R1 |% B- a( W 3% e4 b; [; K% C8 C7 f$ @) \
4
# i B1 u6 o5 w 5
' X4 B/ @" |3 z6 ?: ~& A 对于Timedelta序列,同样也定义了dt对象,上面主要定义了的属性包括days, seconds, mircroseconds(毫秒), nanoseconds(纳秒),它们分别返回了对应的时间差特征。需要注意的是,这里的seconds不是指单纯的秒,而是对天数取余后剩余的秒数:( U) ?; e& T, A& U* M \$ S" c
s.dt.seconds.head()+ h. E4 S' q7 ]
Out[64]: 9 ^" p7 M, B9 B6 B& g' {2 C
0 274; O) m6 a4 |3 p0 B* b
1 260' a) I5 }1 z6 e' Q
2 322
) ]8 B! F7 C+ G+ r9 Z 3 248
0 w/ r9 D8 L. ?$ A, ` 4 322
; h( y5 L( W! } Name: Time_Record, dtype: int645 v9 l4 p0 x ]+ D9 |
1: `% o. c. |+ ^5 r, ~+ Z
2
$ }( {2 j3 c# e& @2 e- l5 b5 y; }7 V) h 3% i! t, T- H: D
42 v9 L: J6 S) v" N
5: l p. G5 q8 {
6& x0 ` S6 q. j
7
5 X) t7 ^; E' Y) L 8! J( |. N& |. Q3 {+ o6 g v( w
如果不想对天数取余而直接对应秒数,可以使用total_seconds8 ?" Y# }6 i% b+ p) O- y
7 Y6 p" |6 E4 l+ w s.dt.total_seconds().head()
3 z; d5 Y3 `: X, @! X5 q Out[65]: ' @6 a& v8 X% O9 P9 h: R0 f
0 274.0& e, k% |3 {- t
1 260.08 K' P4 D- `1 E1 E9 X* s
2 322.0
1 h- t" V- y1 q" {8 @% @ }$ t0 t 3 248.0
4 b) ~. K1 L0 Y1 P$ J7 y 4 322.04 x: o6 C9 X1 l$ i
Name: Time_Record, dtype: float64
' H( ]# ?+ U7 ]0 E0 J/ v' @; O 14 {3 [9 L5 B; V) Y, G
2( G1 `2 O, Z0 `0 Y
3
[( x! `: I1 X5 R: C- b, I1 C- z 4
y+ ^) G2 C5 s) f! T 5
3 r! b. c- d! v 6
! V% N1 }* r, h4 C6 z& J 7
: r3 Y I' N2 r# l$ k1 j' v* d7 ~ 8
- O9 X. D: U- I+ T 与时间戳序列类似,取整函数也是可以在dt对象上使用的:1 ?* }8 n1 u4 o7 L
+ m9 H0 X8 J! p0 E
pd.to_timedelta(df.Time_Record).dt.round('min').head()
' f7 a, W4 L- p3 W( l: o! B' ] Out[66]: ; U: z" Z& y8 E- J
0 0 days 00:05:008 o/ \; k, q& I3 w
1 0 days 00:04:00
" j' x1 r$ I% i$ w! w, O 2 0 days 00:05:00
, a4 N, l5 }" P9 I2 V! V, A 3 0 days 00:04:00
}0 ^, T+ f* a6 C" ]# i 4 0 days 00:05:00& h0 o* ~* G% i5 {. P2 I) d
Name: Time_Record, dtype: timedelta64[ns]9 u) g. `$ H* T0 i# f6 y# s _1 H
1
; T6 X* V+ l6 j) O2 p9 L7 u 2
6 @8 Y O/ ~- P 3
/ V* q" l: o# S" t5 C 4, y# t+ ^; G. J' f
5
\. Y' V2 O% H0 q- R1 j 6
5 a8 F" T1 |- f7 n& `4 F 7
; ^6 t1 w) q; } S( Z$ C% @ 8
/ U$ j) ]9 H$ p$ j7 ^2 a+ e 10.2.2 Timedelta的运算( k0 x% \ X+ m8 t& v" C* u
单个时间差的常用运算,有三类:与标量的乘法运算、与时间戳的加减法运算、与时间差的加减法与除法运算:
( z7 u% _* C r td1 = pd.Timedelta(days=1)
/ W8 z6 l0 G5 ~$ i) ?% g, o td2 = pd.Timedelta(days=3)" O9 G1 ?# m: z
ts = pd.Timestamp('20200101'); E2 r0 r# R( e9 e- P( b2 W0 P4 t
& Y" l! O2 p# X+ L* c td1 * 2
s k" b/ j: b Out[70]: Timedelta('2 days 00:00:00')9 E3 I! {- R' b) @: @/ D& S
0 J H" _3 L. s3 v9 D2 L5 H+ Q1 T3 n td2 - td19 {, n# C9 J2 R [; L, @; |
Out[71]: Timedelta('2 days 00:00:00')
9 @5 M3 d- W( M1 ]% c 5 u1 n" o# Q t" K T
ts + td1
1 Z. j5 G" n! }' Q$ o# S9 E Out[72]: Timestamp('2020-01-02 00:00:00')
/ c( `8 ?6 {4 j. Q; j" \" h, b/ e * x) W- i$ r' r" ?5 H+ ]6 L- e
ts - td1
( ?- H% ?) i( Y Out[73]: Timestamp('2019-12-31 00:00:00')
$ y0 o% @, \2 i$ s6 {2 Y 1
8 P5 z7 N+ D7 |% @6 z+ B 2/ r+ ^, Z4 I: s, j
3
; u' l m% x) }) Z0 \ 4: P& _7 z2 c I& `
5
4 ?7 r4 ~& z8 ] 6. O- {- z E! k. _
7% C! i& `/ _. [# X0 P( A5 c+ X
8" N& m; P0 k5 \ W
9
* V" Z+ M! h! Q 10+ _9 Z0 H# d0 m6 R7 i
112 ^7 I) s4 k9 |4 Q0 w
12
5 R9 f/ u9 v) N2 c 13! O7 y/ C9 d) y
14! A) n! V" T) f7 Y/ W& w" Q
15% s! n, x# q/ z) n. A& s: B
时间差的序列的运算,和上面方法相同:
2 U$ u, B. M# N; v/ Z td1 = pd.timedelta_range(start='1 days', periods=5)" x- \0 X+ |( u" V7 N1 [
td2 = pd.timedelta_range(start='12 hours',
& q/ ~% N/ W7 t0 j, b freq='2H',
9 Q2 @& r: P( m9 Q! B periods=5)7 {# G+ ?& X! J
ts = pd.date_range('20200101', '20200105')
7 o$ S0 s6 S' v$ Y td1,td2,ts9 z- q5 i: m/ T" ~
1 ? {. q1 A0 u5 ?; x2 B' q TimedeltaIndex(['1 days', '2 days', '3 days', '4 days', '5 days'], dtype='timedelta64[ns]', freq='D')
" Q5 }: ^! G# m TimedeltaIndex(['0 days 12:00:00', '0 days 14:00:00', '0 days 16:00:00',
4 I7 k% Q) d2 {! j' W '0 days 18:00:00', '0 days 20:00:00'], dtype='timedelta64[ns]', freq='2H'), k6 ~1 B* g0 |" u4 s5 Q, ? T' ?
DatetimeIndex(['2020-01-01', '2020-01-02', '2020-01-03', '2020-01-04',. Z: @& n6 @% i& j" J7 v# K; `
'2020-01-05'],
' @4 b7 n3 S2 `0 E- ] F dtype='datetime64[ns]', freq='D')
' Q7 d- Z) `+ y6 K y4 Y" { 1/ ~6 M/ L5 N4 _. f- k0 x) s
2
& o. K% a6 r' U4 g% F: p! ` 30 l+ _- r- R. V7 J. _! e4 \
43 |) {6 @3 y6 K ~
5: e7 u7 U0 j8 l0 j1 [
6# B& M+ C/ ]0 n
7
- Y: o/ p. J, }& ]% o5 ] 8
1 y2 P. F! T* Z" C+ A1 ?& P; ? 9
5 v' } A/ G4 G5 x8 ~7 J 10& `' r0 z* k. F9 U2 B7 N1 Q
11, i' u: [2 v$ {
12 @# t& l: T8 W* |. V
13- y& w* y8 P0 ]/ F7 t/ x+ b
td1 * 5& T7 r; s# {5 @5 X4 k+ j1 @6 W
Out[77]: TimedeltaIndex(['5 days', '10 days', '15 days', '20 days', '25 days'], dtype='timedelta64[ns]', freq='5D')
3 [0 j; w' U7 p' D$ X
, j( d) d$ z; r3 c8 K td1 * pd.Series(list(range(5))) # 逐个相乘% ~6 [/ B1 Z' B( s
Out[78]: & @7 l- S) G( q ]7 ?& @
0 0 days
5 W5 C5 w7 a0 k1 d 1 2 days5 N, g- M! G, b
2 6 days8 }, b) i& U4 }) c6 D* k! r
3 12 days
1 O2 W+ ?% U+ ]: R% a* i9 l7 `: F 4 20 days
* k. O: \3 w& V/ o2 u dtype: timedelta64[ns]" X0 r6 Y3 k5 I$ \
7 y: F4 A3 ]& W6 u5 u. ?: r" p td1 - td24 s4 a' E5 c% j
Out[79]:
) c& J0 d6 {# j. o$ s; S TimedeltaIndex(['0 days 12:00:00', '1 days 10:00:00', '2 days 08:00:00',
7 x5 X. C8 \( k- C) k* P( e; B$ m '3 days 06:00:00', '4 days 04:00:00'],
9 E$ t& P9 U4 q' [1 a dtype='timedelta64[ns]', freq=None)
6 \. k1 Z; F) p& I: a
s! t% q4 r1 j td1 + pd.Timestamp('20200101')9 h, R- ?' f- R% S( z% A# P
Out[80]: # @2 _5 H6 P8 P4 i, I/ |9 h
DatetimeIndex(['2020-01-02', '2020-01-03', '2020-01-04', '2020-01-05',0 @. ]# i P9 b7 ]6 j/ e
'2020-01-06'],dtype='datetime64[ns]', freq='D')4 l) c% ^, P- `$ _
+ G# c9 N, @ m! h
td1 + ts # 逐个相加$ K9 L1 t# G, x8 Q# e3 R
Out[81]:
+ _; Y+ o! `1 t" t DatetimeIndex(['2020-01-02', '2020-01-04', '2020-01-06', '2020-01-08',6 L# F, T' g0 X1 u, D
'2020-01-10'],. a) y; u; J! a7 y& H3 s# O
dtype='datetime64[ns]', freq=None)
$ N4 M4 P/ e/ B, K0 G
) H& T M F0 ~) Q* k' R' m 1
, {; x7 L( x4 s1 G6 K 2
4 {0 S+ C9 D, s3 ]& H 3
8 Z" @' s5 l4 {, _ 41 w: c4 n6 L, a: L6 |
5
6 D0 y/ e, Y; q& W 6
0 O6 w# S' r# T# G$ s 7
# g+ F/ H8 Q j! x3 v! F 8
% p9 y; e5 a2 Q: o 9
9 Z, C. ^' ^& Z b1 x! }) z 10
4 J6 y% w/ `, A d 11: R0 ^" G! F+ |+ x. H
12% _ N1 ?( D2 g! I
13
6 N) v- Q8 X! p, r. K' l: x/ I# g 14
* }5 j) J* `# i/ J9 g8 ^ 15$ }* R0 n* J. F6 }5 n! J+ Y
16
% A! T+ @5 c& M; N' l 173 e) A% c. |/ w& f* ~
18
7 U5 x6 H; q* g8 k9 Q1 f, s 19& @0 @9 y% \& [$ e9 u: |
206 g( u- y% V4 z7 i! [; N$ ~1 r! I
21/ Z& N. R S9 x( o+ @
22. o' e j! z5 }9 J. w
23
, a' U; u6 [2 W( E8 a [ 24
% e+ \/ W7 S0 R5 H, c) k+ G+ D$ y$ O 25
. K( @% [* A7 Y5 W6 X 26+ O0 K: T/ V' [* {$ ?9 Z% I7 ?
274 H0 K1 E$ a" \( d) ^( _
28/ _% c. I1 I8 d# p% X
10.4 日期偏置
, k* d* s: ]; D7 c. \' I 10.4.1 Offset对象6 C% _9 }) ?5 |% ^
日期偏置是一种和日历相关的特殊时间差,例如回到第一节中的两个问题:如何求2020年9月第一个周一的日期,以及如何求2020年9月7日后的第30个工作日是哪一天。
8 J, y9 n2 Q% @: p
: C: \6 t$ Q; t5 ^0 E# n DateOffset 类有10个属性,假设s=pd.offsets.WeekOfMonth(week=0,weekday=0),则:
! s3 ?+ X+ i) Y * ]) K8 @+ C4 e& _( j
s.base:<WeekOfMonth: week=0, weekday=0>,返回 n=1 且所有其他属性一样的副本
2 l8 P4 a; l# h" s; j! R' ? s.kwds:{‘week’: 0, ‘weekday’: 0}
' t: r7 X3 y; }* u8 l s.wek/s.weekday:顾名思义* ^$ U9 F1 d( N4 \
有14个方法,包括:5 h; }! r! [+ J6 }: r! N
% L9 a" U& c0 Y" W9 O' r DateOffset.is_month_start、DateOffset.is_month_end、DateOffset.is_quarter_start、DateOffset.is_quarter_end、DateOffset.is_year_start、DateOffset.is_year_end等等。' Q0 M; O% o& v# p/ u
pandas.tseries.offsets.WeekOfMonth(week,weekday):描述每月的日期,例如“每月第二周的星期二”。 b8 _- Q3 |% e; q
' p. e2 Z' X( i& b% x( ?; { 有两个参数:* Y/ J* g( k/ ~& Z
week:整型,表示一个月的第几周。例如 0 是一个月的第 1 周,1 是第 2 周,以此类推。
8 j7 t8 V2 P0 I weekday:整型,取值为[0,1,…6],表示周一到周日,默认取值为0(星期一)- Z0 n# A$ Q9 `3 x5 }
pandas.tseries.offsets.BusinessDay(n):相当于pd.offsets.BDay(n),DateOffset 子类,表示可能的 n 个工作日。2 T, Z5 M" W# r2 j1 z. e e
6 u6 H' ?' j: m" R1 p( E% S pd.Timestamp('20200831') + pd.offsets.WeekOfMonth(week=0,weekday=0)5 t3 v( @& O: k8 C
Out[82]: Timestamp('2020-09-07 00:00:00')
3 f7 D9 ?9 o2 @5 Z( p' Y- T
6 O, c% Y0 H" h4 b5 D3 ~' [9 i pd.Timestamp('20200907') + pd.offsets.BDay(30)3 e- g$ B! l4 Q; Y% p4 ^
Out[83]: Timestamp('2020-10-19 00:00:00')8 ~6 C# \( l% ?' D, R
1. w* o9 Z: u+ m1 E& C8 X# ^
2
$ x2 _9 B; x; W% B6 O5 d7 H1 G I% F5 e 3* J! @; ?7 P: }; T+ w. i/ Y
4 n- u4 @# K, @9 |8 `, d
5
& c8 M! ~/ B" Z0 Y( W 从上面的例子中可以看到,Offset对象在pd.offsets中被定义。当使用+时获取离其最近的下一个日期,当使用-时获取离其最近的上一个日期:( u5 t" y) g; r; u0 I
! L+ I h/ [- W& s! A( c5 W, F. w8 W x( R pd.Timestamp('20200831') - pd.offsets.WeekOfMonth(week=0,weekday=0)
( C# \1 o" M [. u Out[84]: Timestamp('2020-08-03 00:00:00')
) V- m1 C) V$ X0 D$ K
& s9 @) J5 z8 E, P+ m pd.Timestamp('20200907') - pd.offsets.BDay(30), d" J8 \8 x9 K6 j- R" t
Out[85]: Timestamp('2020-07-27 00:00:00')
9 Z& Z( b7 [1 Z, ~( v% q0 R/ l* u
. r. r0 y8 j5 C6 R& K" e) |' @ pd.Timestamp('20200907') + pd.offsets.MonthEnd()
+ N- }2 j3 ?9 o. R* U# Z, o Out[86]: Timestamp('2020-09-30 00:00:00')
5 p( ~+ _5 A" ]! r o0 j 1
: `9 g! A& N7 |. n9 M 2# i& h7 I/ f2 @4 ^; \$ K
3
$ P4 k+ I: x. u) m5 W6 ` 4
8 j2 t1 ]4 l' x2 t6 v 59 d4 y B' z! u! L' B; N8 M
62 n* M; w; `+ \1 Z. p
7
" P9 R: N) q \# b 8
9 ?+ Y4 q/ t" x# `- [3 w 常用的日期偏置如下可以查阅这里的DateOffset 文档描述。在文档罗列的Offset中,需要介绍一个特殊的Offset对象CDay。CDay 或 CustomBusinessDay 类提供了一个参数化的 BusinessDay 类,可用于创建自定义的工作日日历,该日历说明当地假期和当地周末惯例。
$ T' l( G3 F6 Z- n 其中的holidays, weekmask参数能够分别对自定义的日期和星期进行过滤,前者传入了需要过滤的日期列表,后者传入的是三个字母的星期缩写构成的星期字符串,其作用是只保留字符串中出现的星期:
: J& M% D: k5 x4 o* x3 W 7 Y; p% o$ Y' n, ^2 I+ Y3 e, m/ @
my_filter = pd.offsets.CDay(n=1,weekmask='Wed Fri',holidays=['20200109'])
3 l2 N% f9 c$ R. }2 r1 { dr = pd.date_range('20200108', '20200111')
1 {6 E4 o/ r7 x. K4 W F
5 M' `6 p, h' R* W& | dr.to_series().dt.dayofweek
9 B6 \4 ]) Y0 h0 t+ d' h( _3 `1 y Out[89]: ' d$ @5 d, {) r8 J
2020-01-08 2* J% e& b; n6 a: p" d
2020-01-09 3
1 z* [% E+ T6 V; G$ |! [ N* H 2020-01-10 48 {$ E' ~1 J" R7 e" _* {
2020-01-11 5
: S9 d n' O, a Freq: D, dtype: int64
* _' q+ g7 p0 i 5 O0 F; X/ T/ R: j+ |0 k
[i + my_filter for i in dr]
9 W5 a- C: y7 F4 R- K2 V Out[90]:
' n& D5 @+ o% L+ J, ` [Timestamp('2020-01-10 00:00:00'),
4 S3 U4 h" ~9 A- o1 J. x Timestamp('2020-01-10 00:00:00'),
5 R: ~& b8 E% D Timestamp('2020-01-15 00:00:00'), J3 z) J. }* t+ K
Timestamp('2020-01-15 00:00:00')]
2 W) z: C4 h- k5 K# T # t4 m8 I4 @3 Z& F( V
1+ _3 S* c' f* L
2. ~8 g4 E2 w" E3 G" O0 L8 O
3
3 B p( R" E$ w; m9 \ 4
4 b: z K9 g6 `& o9 m/ | 5
* \8 I) \- A. p6 L 63 i% r0 X- R, v
7
4 W7 a7 n, f* K% U4 @3 p$ Z" m 8
1 S; W: T8 ^: r P# a 97 L$ {7 D) _3 f
10
- i% G0 s) ~3 _! s 11
" {4 Z1 L- N9 E% Q* f5 @ 12
7 _4 J& [. j8 u C+ u* {( n 13
4 a4 p+ g& R0 ~$ i5 }6 e$ X# Y" G( ` 14# X4 J) k s* X2 C/ F2 g
152 S9 W$ i" Z5 }
16
. P0 ~. d! Z Y8 w$ f6 p5 ]* _- ^ S; { 176 G: ]6 t9 v W& @5 A( b: `/ f
上面的例子中,n表示增加一天CDay,dr中的第一天为20200108,但由于下一天20200109被排除了,并且20200110是合法的周五,因此转为20200110,其他后面的日期处理类似。1 d8 n$ `# F0 @; E
; D3 q9 ?) K- s' m/ I$ c# I 【CAUTION】不要使用部分Offset. N; A0 Z9 v8 c% N2 \1 e
在当前版本下由于一些 bug ,不要使用 Day 级别以下的 Offset 对象,比如 Hour, Second 等,请使用对应的 Timedelta 对象来代替。
& H& A6 ]/ b7 j% z% a1 ]
Z/ R* H' }+ P% Q, a+ V 10.4.2 偏置字符串
: E+ P }" J1 F3 D; L( o 前面提到了关于date_range的freq取值可用Offset对象,同时在pandas中几乎每一个Offset对象绑定了日期偏置字符串(frequencies strings/offset aliases),可以指定Offset对应的字符串来替代使用。下面举一些常见的例子。+ f' |. m5 y' X; f1 W3 ~& v
' B% h+ J0 D- }: w$ P1 c
Offset aliases:pd.date_range函数中的freq参数,为常见时间序列频率提供了许多字符串别名。 也称为偏移别名Offset aliases。偏移别名列表点此参看(大概27个)。
/ I4 G1 W# h8 l4 c* R 7 ` S1 p% Z. E! E* r' J
pd.date_range('20200101','20200331', freq='MS') # 月初
3 ?# e( N$ ?5 k X+ { Out[91]: DatetimeIndex(['2020-01-01', '2020-02-01', '2020-03-01'], dtype='datetime64[ns]', freq='MS')
6 p% k/ z! ]5 }3 w
$ p7 N0 }/ R; _# G( |& \ pd.date_range('20200101','20200331', freq='M') # 月末
. t, o( K) V! W( o. J Out[92]: DatetimeIndex(['2020-01-31', '2020-02-29', '2020-03-31'], dtype='datetime64[ns]', freq='M')
, ] B. s+ F* c" B4 C( W- J6 a
; p9 {8 L, }% r& N pd.date_range('20200101','20200110', freq='B') # 工作日% E1 D, F$ B: O# [- D
Out[93]: & K0 g, q3 F" {% m! A* g% [, O
DatetimeIndex(['2020-01-01', '2020-01-02', '2020-01-03', '2020-01-06',
+ }2 p" \0 i1 E& d; U '2020-01-07', '2020-01-08', '2020-01-09', '2020-01-10'],# \ g, A" @, H
dtype='datetime64[ns]', freq='B')# D% C# O6 ~$ P1 P
! l0 x6 T5 F8 n9 k2 `' H# {( O7 O
pd.date_range('20200101','20200201', freq='W-MON') # 周一
0 N- I {: h* t- E Out[94]: DatetimeIndex(['2020-01-06', '2020-01-13', '2020-01-20', '2020-01-27'], dtype='datetime64[ns]', freq='W-MON'); S) \1 f2 I4 k7 z) l
( D" v1 S. |8 L$ S7 y: f' H pd.date_range('20200101','20200201',
' k: l% l! t4 s0 | freq='WOM-1MON') # 每月第一个周一0 d/ G8 }1 K D
_( g4 C5 x! a" ~& b Out[95]: DatetimeIndex(['2020-01-06'], dtype='datetime64[ns]', freq='WOM-1MON')) B" M d% u; K6 ^6 w: G/ E
# ]8 N' B! T# _" G! x$ P- ^9 E
1
9 J+ ? P* w+ N, I9 i# L 2
7 H8 A- r& S8 D5 g 3
3 C% Q- j! w c! w 4
. L! C" { r. _' {4 ]9 q3 b$ b 5
! |% R) p4 b) P6 \ 66 t, k+ n* o. k- x6 I
7
' u b. N' r6 { 8
% A9 g! W5 C' U8 q: a 9
6 p6 W, f- _) J1 l- G 102 s; ?& s3 k: U$ n' u! N9 H$ p5 x
111 B, X2 M* Q9 O6 t7 N- X! ^1 Q6 e
12
5 j- |+ S, W& `0 w9 h# |0 \! n2 ` 13* T( b9 j4 L7 o
144 c) n, V+ g- I5 k: P6 q: V
15: m, E% \# w* u7 x& i
16( U2 M: \8 t' d; D
17
5 P: m# _6 N) \) U7 ]; c* [ 18
$ h4 H# P3 z8 V2 v7 R7 b 199 G2 z$ V/ g2 o2 e, v3 u
上面的这些字符串,等价于使用如下的 Offset 对象:
' R: t) W* k5 k% O, D, M4 D+ f) @
% e1 B0 D- O" q% g pd.date_range('20200101','20200331',7 P7 O# U% a9 I
freq=pd.offsets.MonthBegin())' B% G. \( s1 M0 P! d: T3 p6 z& l* C
$ _' { O# ^! e6 ^9 I
Out[96]: DatetimeIndex(['2020-01-01', '2020-02-01', '2020-03-01'], dtype='datetime64[ns]', freq='MS')- K# \5 F. x; n
; x- e0 {$ }( s b" b6 Y" K, @
pd.date_range('20200101','20200331',% B6 `8 m y$ S$ R' S
freq=pd.offsets.MonthEnd())
( J6 Y0 R$ L2 E9 J i) F/ P! H; I: b
L; N/ Z3 D3 O1 e/ d+ N, [ a4 N* W Out[97]: DatetimeIndex(['2020-01-31', '2020-02-29', '2020-03-31'], dtype='datetime64[ns]', freq='M')1 T8 r+ h+ d4 q( r
7 r4 E! v" y5 ^. o pd.date_range('20200101','20200110', freq=pd.offsets.BDay())
! g! f3 \& D$ I' W# y+ j- j Out[98]: ! `- j" z' R& a9 Y
DatetimeIndex(['2020-01-01', '2020-01-02', '2020-01-03', '2020-01-06',
3 p# w; f3 s! V+ C) N4 Z '2020-01-07', '2020-01-08', '2020-01-09', '2020-01-10'],: u% d+ G9 p7 K
dtype='datetime64[ns]', freq='B')7 H6 P _: t% U2 l% L+ C
# I, y6 x" h8 ~, T% R
pd.date_range('20200101','20200201', [( }: O, K! j% v* d
freq=pd.offsets.CDay(weekmask='Mon'))
4 |' o8 q+ |% @9 R) A ) N6 e' h- {7 N7 e- `+ X, \. y) R
Out[99]: DatetimeIndex(['2020-01-06', '2020-01-13', '2020-01-20', '2020-01-27'], dtype='datetime64[ns]', freq='C')8 D" z- P$ f) C I" m4 T/ ]
2 }/ q" s% J" x. C+ i. U1 ^ j. p. u; t) i pd.date_range('20200101','20200201',
7 B+ W3 `* @) D. _ freq=pd.offsets.WeekOfMonth(week=0,weekday=0))
) T. s J. B0 q 8 \ H }2 d+ A& x. y
Out[100]: DatetimeIndex(['2020-01-06'], dtype='datetime64[ns]', freq='WOM-1MON')
1 N7 E* \* \4 W' L4 J6 p$ E
( o% B( g! k7 [' H4 M 1/ `8 { S( V! p+ |( F+ m ^
23 A/ B! o/ q* r8 c- G
3" i. G# N: }) m. T. M2 m
4
% p& }8 O6 N5 x+ @2 r. W 5. y% T* B5 t! O( P( c! y
67 L# V% [3 l3 o, Z9 }9 h1 m. W& W( S
7
* ?1 D6 _% ?5 r4 \ 8
9 I& w! ]( r* b a [3 C3 ^3 @( T) _ 9! a% O3 R+ `% Q0 \# V0 o( k0 O5 }: Q) g
101 j8 K A$ z* J, `( s$ [
11
2 B+ L% w8 V2 [+ T3 O/ l$ m' G 12
+ H7 m5 Q0 O) G9 n) @- \& x9 Y0 } 13
% e# g/ B. A3 E 14% l7 k2 r0 S. z" ]6 H
15
, S; a9 p, w- i1 B) q 16
0 z% b- T% J& g6 W" D2 P 17
4 A3 U2 o- J9 y0 k+ ^6 m, e 18/ ?% z7 _0 o8 x! i+ G
190 }, F- Y, n' T, s; H/ {% q
20 a& x! p% J8 ?2 L. C5 m2 ]4 R
21
# e; f4 B5 z1 }: o, @$ H 22. A1 j/ _7 ^" s3 H
234 @9 x' g4 w& k/ g
24
0 M% c6 c, _2 g 25
) L$ |6 F3 @! C" h, L3 P 【CAUTION】关于时区问题的说明
, G5 W# V) F$ f3 I. `; P B. E* d 各类时间对象的开发,除了使用python内置的datetime模块,pandas还利用了dateutil模块,很大一部分是为了处理时区问题。总所周知,我国是没有夏令时调整时间一说的,但有些国家会有这种做法,导致了相对而言一天里可能会有23/24/25个小时,也就是relativedelta,这使得Offset对象和Timedelta对象有了对同一问题处理产生不同结果的现象,其中的规则也较为复杂,官方文档的写法存在部分描述错误,并且难以对描述做出统一修正,因为牵涉到了Offset相关的很多组件。因此,本教程完全不考虑时区处理,如果对时区处理的时间偏置有兴趣了解讨论,可以联系我或者参见这里的讨论。& e8 n6 r/ X3 @
: [& D( g* {' l 10.5、时序中的滑窗与分组
+ ^: E$ ]5 b; @ 10.5.1 滑动窗口
b/ G/ {; [* [7 I6 R 所谓时序的滑窗函数,即把滑动窗口windows用freq关键词代替,下面给出一个具体的应用案例:在股票市场中有一个指标为BOLL指标,它由中轨线、上轨线、下轨线这三根线构成,具体的计算方法分别是N日均值线、N日均值加两倍N日标准差线、N日均值减两倍N日标准差线。利用rolling对象计算N=30的BOLL指标可以如下写出:% o" V+ b$ V& ~& t; `
v4 e0 l3 L0 X9 v import matplotlib.pyplot as plt0 b1 \7 `) H, G# f
idx = pd.date_range('20200101', '20201231', freq='B')& ~+ c7 ~+ c# {7 s: r7 X
np.random.seed(2020)
9 f2 b* n( }( J0 X6 Q9 @* C7 g& |5 ^
4 v$ r" V+ G) b data = np.random.randint(-1,2,len(idx)).cumsum() # 随机游动构造模拟序列,cumsum表示累加
2 c0 M: h4 D6 {. f1 c1 x s = pd.Series(data,index=idx)
- b/ g9 c% b* M7 i s.head()
7 i' w; N6 ^! u0 N0 g2 L: G Out[106]: % g) U# {3 a0 M5 ^: J$ _% e
2020-01-01 -1
8 n* `! \) ~8 |% E( S2 X# v 2020-01-02 -2; ]6 a& m% L! D8 |- Z
2020-01-03 -1
( i1 k6 w, X! w6 }0 p 2020-01-06 -12 L8 E) F4 Y& B, @: w. W3 k$ o
2020-01-07 -2
8 a; M0 M, \' z0 s1 f Freq: B, dtype: int321 O* q6 a& M; o1 \7 }( ^; D
r = s.rolling('30D')# rolling可以指定freq或者offset对象
. }3 O1 z& A' j
2 W# c1 B& U( h2 T plt.plot(s) # 蓝色线* _& g. [8 V7 g
Out[108]: [<matplotlib.lines.Line2D at 0x2116d887eb0>]. X: T2 N, d8 x
plt.title('BOLL LINES')
; U3 w9 H2 G" A* \1 P# ? Out[109]: Text(0.5, 1.0, 'BOLL LINES'): I& r6 l. g; R7 ]) `
B7 C+ t7 ] J. T |* I& \ plt.plot(r.mean()) #橙色线
3 N4 w5 T% ~: @* z3 t* Z Out[110]: [<matplotlib.lines.Line2D at 0x2116d8eeb80>]0 h$ r5 L9 r) T
' z; q- j) X8 L+ D- a; L* t ?' m) W
plt.plot(r.mean()+r.std()*2) # 绿色线/ R% `9 V: L/ j+ N
Out[111]: [<matplotlib.lines.Line2D at 0x2116d87efa0>]+ [, B# r5 z5 H, d7 Q
/ s% d$ u5 V0 b, O/ F; d plt.plot(r.mean()-r.std()*2) # 红色线
% p% `' l. u: f, [. ]3 I% o Out[112]: [<matplotlib.lines.Line2D at 0x2116d90d2e0>]
* ]: f$ |8 C2 B. ^/ K
) Z+ P( h& h6 ]9 p1 J 1
T8 r) ^! j+ ~ 2$ O6 O6 f$ _+ D% q
3
, P- c1 M, C* n/ a 4+ ]" \5 i8 g+ ?
5+ a- q6 c/ T) n
6
3 N, N' f j2 c6 I 7
8 u( ~! J2 a0 a+ j) ]+ n; C 8, `+ w& E: C }& ]8 d c
9" z8 [& b0 r0 ~$ L
10
# g) R3 x( {6 E" ~' A 11
% O- Z! N2 H! Y( s( ? 12' H* @8 h7 v s3 o9 @8 S6 l
13
( E) }- z, Q" a" Q 14
: ^ Q% ~. u4 r Z 15
1 b# O" _% h& Q0 J 16
) G' h$ h" S- b* k4 h8 Q 174 l- G! x0 m6 X6 z
18
! e; T- L$ d) w) Q8 x 19
q$ @9 ~- {9 T8 t0 X 20
: T" W q A/ `& E3 g( m! I: ? 21
$ Y1 f+ e% w i 22; G: y/ N! ~; n
239 _3 N2 S2 e$ K! w# O: A3 {
24
* ?" S% |' Z, U" H/ ` 25
( B( I& S( G0 h' v9 z 26
# z; ?; G: r* s k% z6 o8 p$ Q 27! |- `; U1 b& S! n
28, U- D L; d, R" ~
290 |8 w7 E9 m; {5 i: t. m( E; ^; o
' r+ Z" U/ L# Q. y
这里需要注意的是,pandas没有实现非固定采样频率的时间序列滑窗,及此时无法通过传入freq字段来得到滑窗结果。例如统计近7个工作日的交易总额。此时可以通过传入多个函数的组合来实现此功能。
' _" E' Z5 l+ j' R& H 首先选出所有工作日,接着用普通滑窗进行7日滑窗加和,最后用reindex()恢复索引,对于双休日使用前一个工作日的结果进行填充。% X$ s }% a5 f# Q4 y
% P" C4 c* h! h4 v8 H; u+ H
select_bday=s[~s.index.to_series().dt.dayofweek.isin([5,6])]
+ c2 A( I6 ?7 r3 Z9 p6 V& m bday_sum=select_bday.rolling(7,min_periods=1).sum()
& D o' P0 t, c result=bday_sum.reindex().ffill()& o- a c. D# }1 t
result
9 y; }$ u" `( V
/ E' e1 q3 i1 V, b 2020-01-01 -1.0
_1 N+ I! G2 E' q6 J0 y4 f 2020-01-02 -3.0+ d: m+ d$ X, a" }" F- o
2020-01-03 -4.0* m8 j0 y0 ^( c v! Z' a; @
2020-01-06 -5.0
$ K1 H/ J* b6 j0 k9 {& p 2020-01-07 -7.0
- `2 K( j0 C: d9 V ... & y/ {/ y2 W7 Y. L
2020-12-25 136.0
: O* ^0 V7 Q9 ^7 s: Q4 B" p 2020-12-28 133.01 W# C K: Y6 w
2020-12-29 131.0
: r6 R# M. N1 e$ k2 b! k0 H' x$ V 2020-12-30 130.0
' K& z% Z+ v% \. V! s# A. |3 { 2020-12-31 128.0
& x& ^6 S& h' G& y, w Freq: B, Length: 262, dtype: float64
6 u) p2 j( }7 @: q* W6 r ! {( v$ k% z6 P- r9 }* _. Y
1# Y* [4 }! w3 i" v" F4 {6 o" Q
2
W6 b+ D0 B. V- c/ A( [ 3
; z* y6 S4 c- k. G 43 O. v0 z$ f1 j* T$ M e, M, {$ I
5" w. U9 p L, T4 n+ b
6
2 w$ X* {" y, k `3 x7 ~ 7
5 b$ }& o# r- u5 } 8
! i" Z: U7 c, j' ]" b/ x* q 9
2 P& r% f7 d- w% h, M& M1 R3 u 10
) Q y: C. w. V4 ~" `: M. g9 u 119 i1 o) |) v4 A- @5 u4 d) i
12
1 q$ n: Z2 J, J- l# T 138 }/ S& D* {3 k8 V0 O0 ^
14" k/ g6 B; u- Q' {' h
155 n: f& J% L/ ?2 R
16: ~" }! _2 Y8 h8 D
17: _* s8 ^3 W/ \# I
shift, diff, pct_change 是一组类滑窗函数,它们的公共参数为 periods=n ,默认为1,分别表示取向前第 n 个元素的值、与向前第 n 个元素做差(与 Numpy 中不同,后者表示 n 阶差分)、与向前第 n 个元素相比计算增长率。这里的 n 可以为负,表示反方向的类似操作。. ~0 N1 j/ ]- B
2 [2 x" _; W. K2 B3 ?* a h 对于shift函数而言,作用在datetime64为索引(不是value)的序列上时,可以指定freq单位进行滑动:+ P7 \6 H$ |% F7 G0 Z
; R4 u4 B# w1 m; F* w
s.shift(freq='50D').head()
8 f1 t E$ X- u* g- Q& S* Q Out[113]: 7 n: S: p# R; t4 s
2020-02-20 -1 d6 V3 I! _$ ]4 k
2020-02-21 -2% h5 @6 E' M; b( S W! C2 g0 I3 ` u
2020-02-22 -14 h7 E. J1 P9 f
2020-02-25 -1
M6 F, G# F) t& ?9 h7 h8 D 2020-02-26 -2
9 t+ {: w: I9 }# j" o& T# g/ A4 { dtype: int32* }) [) ] O8 f M$ P
1! J! [6 ^( E# ]9 d, k+ U" R
2
8 v6 g) P& F2 A7 g! z0 m% l 35 I0 R4 m$ I# N* X- E# r& p
4 G/ q9 U4 n2 c7 L! C I
5
) Q% u v- E, G- z& k+ J6 [0 Z 6
1 E' C$ d Q; A0 @1 @1 G I8 E$ I 7
0 N R- M) G8 L* B0 i' S 8
5 d1 p2 e! v1 _% \% h) z 另外,datetime64[ns]的序列进行diff(前后做差)后就能够得到timedelta64[ns]的序列,这能够使用户方便地观察有序时间序列的间隔:
" ]9 A4 Y) ?/ X: s# J" @; c + P: ^, N* ]3 W Z+ f
my_series = pd.Series(s.index)& F! @; x" f$ G; |5 K7 n
my_series.head()8 I- c! Z4 B- E- o9 Y* V
Out[115]: 4 j+ W" a; T$ U# H% x* L
0 2020-01-01
2 s9 O6 n3 w$ Z8 W 1 2020-01-022 \* C; x& W3 U) ]! D
2 2020-01-033 }+ ^) a1 q. L9 o
3 2020-01-06+ G9 {6 {- f; o& _
4 2020-01-07
( B4 W. w2 p4 f3 I2 E+ @& u8 v dtype: datetime64[ns] v" N1 I: e; W. K1 G' D9 F4 q8 Z4 i
3 l ?' t) S! q" ?7 Z+ Y my_series.diff(1).head()/ n R9 ?& }" X- q/ U0 a* @
Out[116]:
& p) D5 q* V( {3 f 0 NaT; q& K. z( |, y
1 1 days) ]' B4 U" r+ Y$ j9 h
2 1 days
, |: J1 x' R1 O/ U3 D 3 3 days
, N+ a1 J& m% R 4 1 days/ r9 [% j1 x2 J( Q7 b j6 s
dtype: timedelta64[ns]. G$ q1 u# H2 u' U
8 N7 R/ }# D5 }- y6 t+ c
10 B: D7 b9 [# j
2
' A- T* w: c! A) M7 S6 T6 ^ 3
, m0 T: U w- v5 _& w G# d 4
2 T J- P* W* v) K 5/ \; A- j. [ j0 b1 \4 w& Z/ o! l* p
64 K, o' V" g) t; z9 |5 F$ x' m. c% Z
7
z) A' L4 R3 o 8
+ @! a+ {- B7 j 9
6 _1 F/ `' `# L+ A8 z P2 g3 f2 K 103 \/ E' Z* g7 ?8 ~6 x, S- u
11
5 c2 a) Z/ i' d 12; k2 }% b6 C1 L% S! D* o% ?
13
" K; G" _& \% O- r* q/ l& P 14# } N& |. Z! f; a% q U& C. D
15 K3 c7 g& U6 `# W2 V$ P
161 I$ u* z% M! `% L! o4 I
17; f, \0 ?. H4 R
18& v0 D T5 S+ P+ A- r
10.5.2 重采样
+ B6 r: o' \1 v& h3 z8 x U) ~ 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)/ x2 m5 Z: @; t$ R% A0 P5 `7 X
常用参数有:8 q7 \3 H$ q) u5 J0 P3 {3 X
& _' e& b7 L! J) b' B
rule:DateOffset, Timedelta or str类型。表示偏移量字符串或对象! R) w A5 V) g: ?7 h
axis:{0 or ‘index’, 1 or ‘columns’}, default 0。使用哪个轴进行上采样或下采样
/ f; t( {8 M0 a* k2 \ closed:{‘right’, ‘left’},默认None。表示bin 区间的哪一侧是闭合的。所有offsets的默认值为“left”,但“M”、“A”、“Q”、“BM”、“BA”、“BQ”和“W”均默认为“right”。
8 F# i' M/ S6 P4 V; k( l label:{‘right’, ‘left’}, 默认 None。hich bin edge label to label bucket with,所有offsets的默认值为“left”,但“M”、“A”、“Q”、“BM”、“BA”、“BQ”和“W”均默认为“right”。( _) U/ r9 D- n- Y2 o w3 o. j+ `
convention{:‘start’, ‘end’, ‘s’, ‘e’}, default ‘start’。仅针对 PeriodIndex,控制是使用rule的开始还是结尾。( b- g) i9 W/ W8 o( e4 x
on:字符串类型,可选。对于 DataFrame,使用列而不是索引进行重采样。列必须类似于日期时间。
3 z9 o/ _1 Y4 j level:str 或 int,可选表示多重索引MultiIndex的级别,这个级别的索引必须类似于日期时间。
+ u- t0 m9 k* q: Z8 ~ origin参数有5种取值:
; ]. i" z0 V9 s ‘epoch’:从 1970-01-01开始算起7 M7 E+ _& u/ }0 L6 n
‘start’:原点是时间序列的第一个值. m0 Y4 W, V6 {1 `% N. p% u% T. I
‘start_day’:默认值,表示原点是时间序列第一天的午夜。- H, H8 ^) ^" M+ E2 g. f
'end':原点是时间序列的最后一个值(1.3.0版本才有)2 }5 x0 Z0 B! x8 i |/ O
‘end_day’:原点是序列最后一天的午夜(1.3.0版本才有)
- P& w# J/ H- U* P offset:Timedelta 或 str,默认为 None,表示对时间原点的偏移量,很有用。 T2 v7 Y& ^( U
closed和计算有关,label和显示有关,closed才有开闭。" i4 \, x6 Y0 N) E" p0 {5 e) ]3 j) U
label指这个区间值算出来了,索引放区间的左端点还是右端点,closed是指算的时候左端点或右端点是不是包含。
- s( ?: Q2 j9 N$ \5 \3 a7 }7 F' B
& p3 t+ Q5 H5 ]0 ^6 ]5 m 重采样对象resample和第四章中分组对象groupby的用法类似,resample是针对时间序列的分组计算而设计的分组对象。例如,对上面的序列计算每10天的均值:" o" k' V6 f+ [ V: S) n
s.resample('10D').mean().head() J) D9 `8 A+ S/ ^) U
Out[117]:
- s& @ U4 Y2 s' Y! H V 2020-01-01 -2.000000/ l7 T/ K! J( x$ b
2020-01-11 -3.166667
" h& J" K, X( r+ A, S7 f 2020-01-21 -3.625000
& T1 Z2 t- `3 v: I7 D' { 2020-01-31 -4.000000+ q0 h4 V# P$ _, b$ k
2020-02-10 -0.375000
3 v2 w" ^$ l# a0 Y' ^" M# l! o Freq: 10D, dtype: float64% j4 S) Y+ |& T1 c' V6 g
1
6 z& j( x, k2 P- N, Y# [; [ 23 p2 I5 b1 K' C% k8 }
3
# O) `; b4 U! l1 Y' M: d+ _ 4+ m& J9 h5 \! o5 ?, W
5" \7 l. J/ a0 {2 }" ~1 |
6# C5 j# q8 y# S2 B3 h6 e
7
% I3 P1 i) @0 w1 ~9 | 8
! I& d* i3 I g 可以通过apply方法自定义处理函数:
+ ]# S: M- H1 A+ S( I s.resample('10D').apply(lambda x:x.max()-x.min()).head() # 极差
0 b( I/ u" K/ V4 _2 s 8 ~0 X7 ^4 v$ O, @
Out[118]:
: q; G, `( X% n" a" O' _9 | 2020-01-01 3* j: e+ h. \9 R9 |3 a' d
2020-01-11 4
: S: w) q. B& Q& ] 2020-01-21 45 z6 v Z- N5 @" c, b
2020-01-31 2
5 \) N. k9 e7 ?' G7 |- m0 x 2020-02-10 4& b7 l, J3 P; V8 h* f
Freq: 10D, dtype: int32
; M1 Y! |/ Z+ }8 h: M! m7 Q# V! Z/ m 1
) T! v6 K8 K- j8 Q8 S3 T 21 [; Q/ O" g# e& z. q
3! u) ]: J1 l" q0 x
49 G2 n. D. |" m- v: D h
5
) F! s; r9 N$ D, z8 p* G0 ^3 Z 6
7 Q v2 G( R: _. D/ v5 F) M 7/ `. W1 i E$ Z: h
8
# H6 _2 G% S" T- s; d% Z 99 s2 {- Z( P" S# M: \, c
在resample中要特别注意组边界值的处理情况,默认情况下起始值的计算方法是从最小值时间戳对应日期的午夜00:00:00开始增加freq,直到不超过该最小时间戳的最大时间戳,由此对应的时间戳为起始值,然后每次累加freq参数作为分割结点进行分组,区间情况为左闭右开。下面构造一个不均匀的例子:- h4 \6 k7 Z2 ^! I( u" @
3 n, D0 H! E8 i A- P idx = pd.date_range('20200101 8:26:35', '20200101 9:31:58', freq='77s')& x. I/ U: G* w) P5 l* v9 N# i
data = np.random.randint(-1,2,len(idx)).cumsum()8 y* H( s4 R. v T; m
s = pd.Series(data,index=idx)
6 } L! v# G& {7 ] s.head()
7 r: H7 |% U, U) {( i8 @# S 0 d9 u0 v3 Q' K/ c( d
Out[122]: ( J! b, h/ @) C6 O5 P I, X( @
2020-01-01 08:26:35 -1& E9 m: I1 z0 h+ b. E
2020-01-01 08:27:52 -1; `# N0 x; w: f2 y0 F
2020-01-01 08:29:09 -2+ C$ w: O. j+ E0 o0 K- `1 V6 @
2020-01-01 08:30:26 -3
* |, R4 d4 ^8 O- O 2020-01-01 08:31:43 -4
, t# k! B( x$ u1 X2 Z1 v9 p. ^ Freq: 77S, dtype: int32
) j' t3 k. W; W" w9 B 1
/ y) W* A% s, M' R6 j5 g s 2& j1 ]- B( ]7 X* Z* `
3- ?4 n/ K3 S1 i) r: B
4
( F, W) H( V* w5 c; F" C 5
1 v, p. i7 j: h' @ 6
2 C, Q1 T$ c" O: J# q( m3 J' h 76 ]6 _0 b3 w/ H8 U2 T
8
" j. c2 F( n- b/ a# [ 9
, l* Q4 d& c) w4 [0 m% c2 S 10. l6 p% K6 w+ c. S$ K) ^- C
114 p: R2 g4 y5 S, _+ v! N
12
% _% \( f. c, d4 K2 } 下面对应的第一个组起始值为08:24:00,其是从当天0点增加72个freq=7 min得到的,如果再增加一个freq则超出了序列的最小时间戳08:26:35:
- |" F, B. J" v; B$ D
) X$ Z' Y6 q* ~5 s0 H, G* n s.resample('7min').mean().head()
! T2 R- ?8 y$ i5 i z Out[123]: . W5 a7 o9 m' ~4 [0 t2 _
2020-01-01 08:24:00 -1.750000 # 起始值,终点值包含最后一个值
, @% F E/ Q+ s1 j 2020-01-01 08:31:00 -2.600000: {# L: E1 V# D# R" u$ W+ R- c
2020-01-01 08:38:00 -2.166667
* A. l% `6 ^7 X, c0 x& ? 2020-01-01 08:45:00 0.200000
7 O# l [- I3 G 2020-01-01 08:52:00 2.8333335 n' I2 v( P+ b2 k
Freq: 7T, dtype: float64; ~8 x! L% b2 h1 E* V0 q7 b
1; @- I3 ^* h* P+ H! v5 L, ?
2' T6 I* |$ T4 i& p& ]
35 O- O7 U5 [2 P2 i
4% v! z6 y4 Z$ i/ ~# y( V
5
% H( O9 b% e- V- R+ ?# ^ 6
6 L$ @) I8 r ~/ M4 j 7& F1 `. ?6 [( q+ `- X6 S) u7 k
83 ]* U4 H; ]. c6 z$ |* P
有时候,用户希望从序列的最小时间戳开始依次增加freq进行分组,此时可以指定origin参数为start:
4 R Y0 r3 Z4 N, {. X ( n; n- h( T' j4 p' b1 J
s.resample('7min', origin='start').mean().head()
4 K U- p3 v+ T( V Out[124]:
! C4 c' ]! l7 l a! W 2020-01-01 08:26:35 -2.333333+ S, D2 b- r6 [& x2 q7 }- R& g9 Z
2020-01-01 08:33:35 -2.400000
: N: z- v9 p0 m" h3 L9 z 2020-01-01 08:40:35 -1.333333# s0 c, K8 C( `* ]. b. j
2020-01-01 08:47:35 1.200000 w+ R) h8 F* s% Y3 i
2020-01-01 08:54:35 3.1666670 l& C, m: H( f$ ~2 ^
Freq: 7T, dtype: float64
( t/ K, p' l9 I; r5 ~3 f- D 1
# U y. _6 R( Z/ B3 p P5 O 26 q0 ]3 }0 v1 y/ j# s: m& W( _
3# J* F- F" ^9 W* S j# p
4
( O; H; U# F% M# y( T# R6 K, a R 5* {+ I A2 C& @: [
6
) r& c/ n' [& p9 p) e- s) c5 n; E 7
5 J4 S& `$ D9 o, c2 `$ x 8
$ B' s* Q9 T" @4 G/ M( S- l 在返回值中,要注意索引一般是取组的第一个时间戳,但M, A, Q, BM, BA, BQ, W这七个是取对应区间的最后一个时间戳。如果想要得到正常索引,用’MS’就行。, a& s4 s7 l2 h5 q" r6 A% i( Z9 v
, _3 \3 i5 G$ W4 B: F. K" M s = pd.Series(np.random.randint(2,size=366),: R, [3 I$ O' D7 z" J& R3 O6 G
index=pd.date_range('2020-01-01',
2 j& J6 j& C7 B '2020-12-31'))
, ]6 e1 }: b4 { 8 O+ O7 d* W/ n2 k
9 k# a! l) h( I- u$ B
s.resample('M').mean().head()
9 ?8 l) o# X; }+ G2 @7 C Out[126]:
: A$ t8 h) ]6 H. @8 ?9 c 2020-01-31 0.451613, h9 Q1 B( Q2 P
2020-02-29 0.448276
. }8 s0 G n3 F0 Z& w2 @ 2020-03-31 0.516129
& j4 h+ Y9 v. { d# H) R$ [ 2020-04-30 0.566667) q- m9 v3 b! g2 d% `# m, m, _
2020-05-31 0.451613- b0 m6 n: @8 H' T7 d
Freq: M, dtype: float64# ^( @9 _9 Q: x( U& Y- G/ j
: O. p; }6 y9 `( F$ S' s
s.resample('MS').mean().head() # 结果一样,但索引是跟正常一样) b" s5 H* _8 a& ~ H" r
Out[127]: 0 Y! z, V/ o. p+ |9 q c
2020-01-01 0.451613
& t1 @: i% @8 g1 Y# W8 W2 y5 x( } 2020-02-01 0.4482766 G5 H& r. w/ l2 W8 M: J
2020-03-01 0.5161293 Y0 i; T* K9 F: ]
2020-04-01 0.5666676 Y- c' R7 l! T% B, z; [( g+ R! |4 m" ]
2020-05-01 0.451613% `; k8 d+ r+ l6 i8 t
Freq: MS, dtype: float64( C( Y7 q1 o p6 m& b6 G4 @
- f, k' J- Q4 s5 _7 C. q, J0 c 1( M5 B4 ] ]/ P, G' P
20 Q& d$ b$ Z+ k% s7 w2 f
3
1 Z5 a) L) D" s! g 4
0 M0 Y0 O; F6 h1 E 5, v' s7 J' k. R& _' W! t7 K4 Y, C
6
2 t$ G9 y' | i 7! @6 k8 F- s, q K& q
8$ g1 H+ x" Q; H# N" y
9
7 G0 j+ U6 O' k- Z 10
7 x. Q+ l$ X, ?9 \+ w 117 k0 D/ {* z/ A; m; v7 o, B
12/ B& x9 t$ t1 r( ?/ g# E" {1 q
13) k$ M8 W5 N9 @7 i" {' O7 k
14, ^, ~# Y3 I. [) @2 f
15
0 |5 a: Y# `+ E4 k 16
0 ]0 Q' N" r8 s" M$ I5 {9 f* M 17" A7 R* H% F4 `! n( y! e- w
18
6 [( {" L. F/ X, a# W; L 191 V) g) h/ o) z& O8 E# u
20 N0 i0 \" T. u1 z1 F
210 @, D, o! }! W4 C) T! v7 h
22- N% r+ c% f' {& i# W7 Z
对于 DataFrame 对象,关键字 on 可用于指定列而不是索引以进行重采样:
+ e7 |: W1 E0 d! {8 S- ^ d = {'price': [10, 11, 9, 13, 14, 18, 17, 19], ~' [5 |& `; @& b6 F* T
'volume': [50, 60, 40, 100, 50, 100, 40, 50]}$ S% L3 m( d' E& O6 N
df = pd.DataFrame(d)
; @1 Y8 y% D* }/ Y df['week_starting'] = pd.date_range('01/01/2018',
& p) ]. Y) O" j0 V# O: V periods=8,
7 @) k, G1 m j) B' X freq='W')3 i# I- k- S# j- Q, C, @3 c0 M2 I
df
* Z: l/ }; c( d+ H) M# P price volume week_starting
8 g0 @9 [ a& F; S& s- R4 H3 j 0 10 50 2018-01-07
. u' m% }, {' W7 |; u4 r 1 11 60 2018-01-14
( O p' ~; D& m# m3 g3 ?5 o 2 9 40 2018-01-21
+ K# K4 Z4 e, ]' }. r7 L 3 13 100 2018-01-28% y2 M! V7 h, o. r
4 14 50 2018-02-04
1 {6 C& z* _+ r2 I8 Y+ z K 5 18 100 2018-02-11
' B* z- p' c" d% U; e 6 17 40 2018-02-18: B/ W0 N! `0 a5 ~+ I
7 19 50 2018-02-25
3 k+ j: _/ c$ v+ ~ df.resample('M', on='week_starting').mean()5 ^; ?- ~# Y3 w+ S
price volume/ L* j7 c2 m6 C& C" n7 ^) [
week_starting' _, u, P& F- p# T* o3 P/ a
2018-01-31 10.75 62.53 p9 r/ j8 y# K; J- H* T7 c' |
2018-02-28 17.00 60.0
# h9 f- r1 P3 d8 O4 F/ u/ |+ | . V. R& `1 R5 Z3 |: d
1* J- y. G5 D" n
2
" g3 o: P5 Y+ T 3 q! ^+ y8 S: A' Q& ] _
4, f Q0 C8 k. h" b( Z! |; ]& F
5
0 i6 `; f( ~. B/ ~4 S1 d 6
% q* ~1 P g6 S' O+ A 7" h! G) A3 B4 I; p2 ~1 m
8- @* B& T% ^1 L
9 Y/ B B3 ~5 ~0 e. s. d: U
106 G7 V$ y9 c ]
11 F8 q4 f/ r/ H# F. z
12
/ ^/ z( G# u0 _8 I2 z6 [ 13* _/ [7 o' U5 X9 y& Z9 r9 [
14
( k4 {5 I2 ~( O& V( J 15( ` |9 r. g ]0 \
16
& e2 J) }9 }. L5 g6 J 17
3 O+ H6 a; d, }: a+ a 18) @8 e5 l, v6 u$ Q# ?, G
199 g6 f6 d# b9 \5 Y' ]
20
2 `* {7 j% O9 b, Q. g 21
6 }( g& P& W+ n8 r8 Q3 X& k' j/ D9 Y7 j 对于具有 MultiIndex 的 DataFrame,关键字 level 可用于指定需要在哪个级别进行重采样。4 F, z. [% Z+ r5 f
days = pd.date_range('1/1/2000', periods=4, freq='D')
1 r9 N. y) N! G7 d9 @ d2 = {'price': [10, 11, 9, 13, 14, 18, 17, 19],% X7 \8 r- ?/ S: s @3 d/ m% O' \" R
'volume': [50, 60, 40, 100, 50, 100, 40, 50]}
$ o5 Y9 p3 ?; Z df2 = pd.DataFrame(! n+ x) H0 E) c! k' B
d2,1 \1 `- G) _4 x8 r$ N8 @' T1 T
index=pd.MultiIndex.from_product(
+ q: m! Q6 e- D0 ~ [days, ['morning', 'afternoon']]
$ o7 _2 {" }/ N! v+ c; m, R )4 K0 h1 t2 z0 y1 i: x, K* m; ?
)3 j) C* a" r' X" l" Q# k
df29 d2 G# c8 Z, ^- P! m+ h
price volume
$ T7 k6 ~5 x* a 2000-01-01 morning 10 50# ]5 M% ^7 s c: e, b
afternoon 11 60. S; I& u2 ~; m. C
2000-01-02 morning 9 40$ ^- Q2 I* L1 ?/ ]) T: t
afternoon 13 100
4 B& { S% p. Q* `7 z 2000-01-03 morning 14 50) h) W" w" j6 y/ i
afternoon 18 100
+ ?& V; n& E. s# G$ V 2000-01-04 morning 17 40& r T+ L. L. P; J' ], G
afternoon 19 50
) ?. Z# ]1 C" S) I, ]2 l; R/ {2 g df2.resample('D', level=0).sum()
# G$ d; H. G- ^) f k3 n# I; I* L price volume8 Q' A3 w8 u6 E/ H1 {8 _( N; W) Z
2000-01-01 21 110
) U" h6 B! Q- J# Q 2000-01-02 22 1404 c: ]: f0 m# O) J1 z
2000-01-03 32 150
8 J- r4 F1 F( h/ Q# b2 ?6 n 2000-01-04 36 90
4 _1 g( d4 _+ x
( u: J f1 O% c; S8 x4 x 1
* Z3 i6 I+ L2 R' X6 M1 R 2& G! X' c. Y& |6 ], N6 V
3, P L% g; O9 M4 s! _! u5 n
4
& ?. `* A, J8 e, E 5
/ p1 L \1 Z: H 61 }1 k8 C. ~ D: C5 x9 U
7
( E! m }9 x' s! t 8: o9 v, m. Q2 p9 D: ?/ q; @8 E& J
9: f7 L/ j! ]' @
10
6 y/ i8 M ]! A N+ w 116 t- P) G! C8 j: t5 e. f) x
12
% @8 r6 C. ~+ { B% F' |( K( B 135 X, c+ r4 w& J
146 E5 I: M2 M- [) p _ V
150 i, J- W" W% S
16
8 d, v6 S6 X+ t- i* t 17
* Y1 ~9 X% w6 r 18
* J1 ~+ u4 R+ B$ t, @; l 19
% \$ o, h( K9 d9 w 20- |! k( q( M9 y' @6 t8 C! w
21% k/ C8 I0 w5 L. q7 _
22
& ?' h) k: |1 B* M' M) n; T 23
! {! e8 b5 t3 P4 G 247 w0 h5 t/ b# o- k0 e; q
25; l2 e, x6 i# H/ h( H3 E
根据固定时间戳调整 bin 的开始:
5 P. O; f; u7 o6 c3 ? start, end = '2000-10-01 23:30:00', '2000-10-02 00:30:00'$ [' Q, M$ k3 ]& n+ ?( r
rng = pd.date_range(start, end, freq='7min')* i3 }1 d W9 b: o9 W* |8 W
ts = pd.Series(np.arange(len(rng)) * 3, index=rng)8 s0 ^3 f# T9 y. W1 J( S: C
ts
3 M8 X+ i9 H u* A- j/ n 2000-10-01 23:30:00 0* A- ] b! L: ?/ A* C' ^
2000-10-01 23:37:00 3
( Q8 n- {0 B4 r$ a8 r 2000-10-01 23:44:00 6
4 f7 ~! c( J% }# } 2000-10-01 23:51:00 9
5 s& g8 v' f' s( p* u$ j0 ^* H% o) k 2000-10-01 23:58:00 128 X$ f4 r* n! G, T4 J0 I
2000-10-02 00:05:00 15& @3 b$ [# [) s2 h4 R2 W' U
2000-10-02 00:12:00 18
8 t8 s- W y& K9 J7 t- s 2000-10-02 00:19:00 21' R8 Q. [) w; g4 a. b0 i
2000-10-02 00:26:00 248 R2 L# l) ^4 V! O2 n& I1 R0 V( y
Freq: 7T, dtype: int64
3 O% ?; T$ q! L$ q! A
; w( S( m; t- ?; ^; K ts.resample('17min').sum()
1 e4 J& U; S* e( L/ M5 O' _ 2000-10-01 23:14:00 0
- D( M( i& d. {% m 2000-10-01 23:31:00 9% g6 [1 j8 |- x) w1 k( K/ P3 H
2000-10-01 23:48:00 21
$ P" `, R5 ~( W$ q0 A 2000-10-02 00:05:00 54
Z! {/ L1 M/ m' p2 M 2000-10-02 00:22:00 24% k4 j3 |, f- q' G# i! j
Freq: 17T, dtype: int64
4 R( Q) s! g8 D1 i4 o8 R 0 {- i. E8 R i; S# [3 @
ts.resample('17min', origin='epoch').sum()% A! k2 x& N6 z5 e
2000-10-01 23:18:00 0
( g# S1 g: Y/ l! x* X 2000-10-01 23:35:00 18
# N- v6 Y4 `- B. h7 [/ W7 l 2000-10-01 23:52:00 27- r C( |: E7 i" a! U! y+ w
2000-10-02 00:09:00 39* r$ Z' J: g* y
2000-10-02 00:26:00 24* H$ c$ f8 w* |2 j
Freq: 17T, dtype: int64
- ?* c5 l2 f" D3 L - @( ^! O4 v t F( m
ts.resample('17min', origin='2000-01-01').sum(). e- y+ n. y* v% ~, Z( z3 ~
2000-10-01 23:24:00 3
2 p8 g% A( P. p' b7 Q 2000-10-01 23:41:00 15
. m2 U, b2 o1 P2 S7 O& Q4 f5 r _ 2000-10-01 23:58:00 45* e8 a3 {3 F# s
2000-10-02 00:15:00 45
% f0 Q) {% Q& U) M# c# q# e0 [9 f Freq: 17T, dtype: int64
: H$ l/ U/ Z! |7 h s/ n) P
9 ` _& V9 q& K7 t 1" F Z, k4 Z& e! u) l4 `
2
0 a; y; M8 M; F( X7 E3 [0 m 3
' \: }& ]" v4 r. m 48 C: c# a# ]9 O7 K
5
) U$ S2 L! b* S1 E g H( b6 L 6
: q2 F4 |0 X' _* O. Q8 m 7& t, j& \; Y) {9 K
8
' f' x3 V% Z( s% W. J 9
! w% x4 ^/ m: J 10' Q+ u. ^/ v3 g1 C5 Y2 F
11
! E" x' T; p8 Y* R9 ]. }3 u7 a 12
( s9 r$ [0 u9 o 13
3 r4 v! }% E* D# X! Q' j7 \$ H( \! L i 146 U7 Y8 h2 V: n0 Q* _! E2 g, J
153 d1 u- T, r' [& x
16
$ B# K4 }3 Q! _' V3 m" V 17
1 g' c% g8 F. X& S5 ] 18
, b- u; J1 n# i% C6 a 19
) a3 }- R' o# j3 Z# f# K G1 V" h4 x2 r 20
6 ?2 y: c/ \ W0 O1 d/ L, d 21- `. g3 P& c- N2 @
22/ V* o7 J: C, ?$ \4 D1 u% m
23, }( z9 {5 C0 E/ z- g! C
24
" d8 ~' ?; z' b! A2 v, l4 e$ u% J% H 25& n+ y7 b0 H5 F$ i. Q( e
26/ A6 ?/ U. E% c6 z+ p# M. r% G
272 f& C3 {2 E7 r, [0 p! ~
28
' x. l; X" b5 V T) w3 ? 29" C( u0 g' T6 j. S9 h. `
30
0 T n9 z" K- M v 310 j: m- K" c3 U3 L3 o' [* q! @
32
, L% [9 G ~- {/ f* E( N5 E4 B 332 H7 l9 X/ ~/ K9 \
34
( W9 F$ T& i$ r c3 N9 U# o1 t 35$ U, }4 A% w, s& a
36! o/ S: _* A* N3 c6 \( d! E
37
% Q$ h6 P# f8 @2 [2 z$ n6 m 如果要使用偏移 Timedelta 调整 bin 的开始,则以下两行是等效的:6 Y5 s9 p$ ^! p& U
ts.resample('17min', origin='start').sum()
- L' R# D9 p. P3 b ts.resample('17min', offset='23h30min').sum()
9 O+ G0 P2 d& a- N: i 2000-10-01 23:30:00 94 g9 U0 _9 E+ {2 `2 U. @
2000-10-01 23:47:00 21% E; a6 {) B. |- q A
2000-10-02 00:04:00 54
' r& w( y7 H' J1 o2 M 2000-10-02 00:21:00 24
$ ^5 p/ Q/ U1 ~ Freq: 17T, dtype: int64! J( X5 L! L% t9 t. U4 ?
12 p0 l+ y% `+ A% E: a
2
& P3 H* O9 ~- @+ l 3 d' w' I; ?0 p
4
( C8 e8 N4 a' h1 u# P; M 5% z( X; g8 ]3 I7 H3 P0 r4 S
6
* G! G% e! z6 S+ P* l' V0 U 7
$ F' p; Q3 Z, R8 u a- U8 p 10.6 练习0 A4 b# |" f5 F
Ex1:太阳辐射数据集
; @) W. K8 x0 @ U% B0 ~ 现有一份关于太阳辐射的数据集:, L+ i' w" c/ S$ d
& e8 g& `2 ?6 n df = pd.read_csv('../data/solar.csv', usecols=['Data','Time','Radiation','Temperature'])
- O! R4 u1 w% A df.head(3)
$ b' H7 A4 E g, N
; s8 @8 H2 y- X/ _" J Out[129]: ; x$ v) ?& m% p
Data Time Radiation Temperature+ b/ e6 @& F; g- C
0 9/29/2016 12:00:00 AM 23:55:26 1.21 48! F' m( Z/ K; J; J5 I; `
1 9/29/2016 12:00:00 AM 23:50:23 1.21 483 D. z4 c) A4 Y. c' Q! u+ I9 P
2 9/29/2016 12:00:00 AM 23:45:26 1.23 48
0 n' b; o; I6 Z 1: K. [' U ?% h! k
2
0 j' P8 V4 j; j( N1 D' H- h 3" b+ f" `. W7 M a/ r
4+ S L' `! U! H. k- h6 h
5
1 T1 ]6 g p$ p 6/ F G+ [! J3 n7 a0 B8 x, i
7+ Q" y2 N+ e# v3 j
8
: z4 R2 W1 n* x 将Datetime, Time合并为一个时间列Datetime,同时把它作为索引后排序。
. |3 Y4 q. p% c$ K# N( p 每条记录时间的间隔显然并不一致,请解决如下问题:
6 s6 V0 u& O: E8 w( {- s. D+ K, J8 D 找出间隔时间的前三个最大值所对应的三组时间戳。0 z0 Y2 g' w5 _
是否存在一个大致的范围,使得绝大多数的间隔时间都落在这个区间中?如果存在,请对此范围内的样本间隔秒数画出柱状图,设置bins=50。5 q. ?" j3 g6 R7 ~
求如下指标对应的Series:+ t6 q5 L. }- k; e$ P- D! _
温度与辐射量的6小时滑动相关系数
7 z" R. i* X1 S8 ?4 f8 A 以三点、九点、十五点、二十一点为分割,该观测所在时间区间的温度均值序列
! w& M( _% y7 f1 t3 I* Y 每个观测6小时前的辐射量(一般而言不会恰好取到,此时取最近时间戳对应的辐射量)
1 J: g- L3 [- d6 _ import numpy as np
2 J3 I. B4 \* ?- ] import pandas as pd/ U+ l p8 K b8 b, a! R" _
1; l* _8 U# O3 ~
2% ?. p6 J% Z u5 L" x+ y- y+ ~
将Datetime, Time合并为一个时间列Datetime,同时把它作为索引后排序。
' K9 D$ H3 U# l( b9 b3 i data=pd.to_datetime(df.Data) # 本身是object对象,要先转为时间序列. P! h" q3 {7 F6 ~' J9 X- {) o
times=pd.to_timedelta(df.Time)
+ Z. d' Q. z4 o( a+ A/ M# f df.Data=data+times
: D0 G3 s1 S Q3 _( f9 u del df['Time']" j% C6 s! b/ I& |
df=df.set_index('Data').sort_index() # 如果写的是set_index(df.Data),那么Data作为索引之外,这个列还另外保留9 O6 B9 C! {, [; `5 I" q
df
5 @; I. Y6 a" R Radiation Temperature ^( d' B! F+ O. b; b1 l* @
Data
3 t6 _+ Y* @. j1 j 2016-09-01 00:00:08 2.58 51
. M, {6 x/ @2 y: X 2016-09-01 00:05:10 2.83 51
& {5 s, p+ B) j" y 2016-09-01 00:20:06 2.16 51! C s9 i6 \* w4 {" M
2016-09-01 00:25:05 2.21 51
. l) d1 J. k; p% z% S) {4 _ 2016-09-01 00:30:09 2.25 51
[1 [# x0 u9 |* z ... ... ...
& }' q- I- c" I2 ], V( T 2016-12-31 23:35:02 1.22 41
$ ^1 ?& v, h, ^; z9 I- y 2016-12-31 23:40:01 1.21 41* c7 Y: L( o5 c% R
2016-12-31 23:45:04 1.21 429 }* v! F. Q& k3 j% D
2016-12-31 23:50:03 1.19 41
& G; ^7 \" E5 u1 ^0 ]+ @2 S 2016-12-31 23:55:01 1.21 415 F) i# b8 o( ^2 O$ A/ i
3 C1 I v4 O, q 1$ g/ E; Y+ |. b4 Q C
2
! B# z: |' U: ^ 3
" ^3 N% ?: m5 `* G 4
: n/ l& y1 b1 J1 @) K; z) H 5
0 W r0 c" b$ }( N* g7 C7 _ 6
5 \( F7 A. U1 Z) X" L, ` Y1 i 7
# o" f) Q ~- | 8 `7 c3 j5 X G5 {& x" t
9
Q) @, y- Z6 ^& X' \, w: x 10
- |2 {: P, M' e2 T 113 \ s% r" T$ C2 B8 J7 Q/ ^2 Y
129 m w/ U4 C2 Q2 j
13
9 \7 k" y/ {& V$ ]0 ? 14# p% v* D& b3 V7 m5 p7 ^. J
15
" X& s, m0 B: I* M! r. {$ G, c6 V 16
6 \; Z2 [$ x7 ?, a+ d$ n# k X 17. D2 x7 F/ j1 b1 N; f
18
' ^; Q2 E- R7 m3 o0 r$ `% A1 i3 Z 19
' |: j1 R- V; b 每条记录时间的间隔显然并不一致,请解决如下问题:
+ m2 P; E/ W2 ]! ?/ A+ G E7 i, ^ 找出间隔时间的前三个最大值所对应的三组时间戳。0 u* @8 e0 @7 E- t2 j9 m
# 第一次做错了,不是找三组时间戳
- l0 B* V" P" L9 F idxmax3=pd.Series(df.index).diff(1).sort_values(ascending=False).index[:3]
" l: x% ^% d ~% t. \ df.reset_index().Data[idxmax3,idxmax3-1]. x/ B( b, ]( x3 T; {" [2 N! S
$ s2 [7 j! V/ x; Q4 A7 l2 G( G
25923 2016-12-08 11:10:42
- I3 j) K5 a) Q O$ y& Y9 a 24522 2016-12-01 00:00:02: [% y6 s ^6 v1 r3 ?) Y
7417 2016-10-01 00:00:19; l/ I+ \- F/ B4 L. l: v; Q& m
Name: Data, dtype: datetime64[ns], J" u/ {$ s+ d; d( p3 ?9 t
1
9 {5 f, Z4 c0 t4 v 2
. D8 O1 A( Q0 J, G9 o& u6 b 3/ C \1 Q/ Q3 A( ]( Y
4 p1 L* o7 T, f4 i* b
5
+ G6 ^, A6 |0 r* l' I) b 6
: H( v2 |) h* r, ^; ? 7: J5 s2 G3 f9 ~. R
8
, E7 |# O" e* Y" h5 \' K* |4 P idxmax3=pd.Series(df.index).diff(1).sort_values(ascending=False).index[:3]7 s* T% d7 _6 d4 ], T1 l9 E M
list(zip(df.reset_index().Data[idxmax3],df.reset_index().Data[idxmax3-1]))
4 P: k# b. K% L5 R6 z) |% [+ i
* g( X) h5 i+ a2 J+ G0 { [(Timestamp('2016-12-08 11:10:42'), Timestamp('2016-12-05 20:45:53')),, p8 D2 f: ` [# K' u4 Q9 r. w8 l
(Timestamp('2016-12-01 00:00:02'), Timestamp('2016-11-29 19:05:02')),
5 F! o/ A( T$ m( \& s (Timestamp('2016-10-01 00:00:19'), Timestamp('2016-09-29 23:55:26'))]) k1 `# x9 b' @; _8 [
1" X3 i2 f, A/ l% u
21 u! ?8 Y) u: }7 M
3
& z6 O8 g: W3 x- ]( W7 ?- N 43 ^# s2 S0 c* i) Y, x* h0 m: s$ D
5- u8 B) L* X8 p2 Z; f& L
6
, g2 x2 e, N p6 P9 t" o 参考答案:
! Y* i; P& q& m! w6 U0 `
! i& N. C3 g* s5 i0 v/ Q s = df.index.to_series().reset_index(drop=True).diff().dt.total_seconds(); j: S& Y0 O4 p( F# x
max_3 = s.nlargest(3).index
0 ?% x6 t& }0 ?0 L& R! H2 x+ m% j, D df.index[max_3.union(max_3-1)]
& v, z& b# K' r- H9 c% N0 O8 t
6 i3 f5 U/ s4 v! Z1 X" H Out[215]:
9 n6 {2 M6 `# U& L( t2 j1 F7 I DatetimeIndex(['2016-09-29 23:55:26', '2016-10-01 00:00:19',. }/ b& W$ q- R
'2016-11-29 19:05:02', '2016-12-01 00:00:02',
* v$ g+ _7 R7 G '2016-12-05 20:45:53', '2016-12-08 11:10:42'],( ^% {6 x+ V: C
dtype='datetime64[ns]', name='Datetime', freq=None)
% Q6 z3 ?: p4 F5 U3 `# T' g# g& h, ~ 1
5 c, X& B9 {/ T- m 2
t1 b0 u& Y. E ?% v, m" I) E 3
9 w+ k8 ^0 |& ?4 U/ Z+ y 4
: J8 ]; ?9 r! f, I# @0 X7 U, }6 Z* S 5
) m) H' f" \% w- ?* I 6# E/ Q9 e7 D' w3 o* s: Y
7, x u5 Q& g4 D$ _$ f
8
6 u: o! O8 \* A" }2 y& C9 ~ 94 m6 m$ D- K8 f. d, W4 V; M
是否存在一个大致的范围,使得绝大多数的间隔时间都落在这个区间中?如果存在,请对此范围内的样本间隔秒数画出柱状图,设置bins=50。
; W7 V: j" R2 C/ V7 v # 将df的indexydiff做差,转为秒数后排序。再求几个分位数确定取值区间3 E7 n- f* C- t- z# _6 G
s=pd.Series(df.index).diff(1).dt.total_seconds().sort_values(ascending=False)1 W- i2 @6 e% g8 B! w
s.quantile(0.9),s.quantile(0.95),s.quantile(0.99),s.quantile(0.01),s.quantile(0.03),s.quantile(0.05)
- u+ [" ^8 }" y/ \7 l
* h3 d% G. K' l( _ (304.0, 309.0, 337.15999999999985, 285.0, 290.0, 292.0)
' F+ n% o3 ^( |, f& N 1
/ u$ N9 H v2 K, K- j 2
3 v: ?: l- l# w# K- _2 H3 ^0 ~8 V' | 31 k- ?" J+ a$ C' i/ b
4
C, v) a* d* g% E5 v* i) E$ I% T 5& `+ u( a+ X7 L) b
%pylab inline; D. f1 A9 i' m: @
_ = plt.hist(ss[(s.values<337)&(s.values>285)],bins=50)
5 R. Y+ x7 g" ^' M" Y3 x; G- k* y plt.xlabel(' Timedelta'). n+ z c. X }; Z, ]
plt.title(" Timedelta of solar")
, C) T* f" I5 q! B3 |6 C; S( a- ^ z 16 l% Q0 r% |: F& _7 _$ o3 [- p# Y
20 n) V& {3 p9 C
3' Q. l, x9 w$ H/ x# q2 u' a
4% m4 k5 _( {, k0 p- g% C+ r+ T$ `
, c9 V |" b& e8 d+ _% X- g
! x3 E/ `# |+ r( P* e$ H4 [1 K 求如下指标对应的Series:
) B3 X- t( ` C6 S' p: |) F, G( T 温度与辐射量的6小时滑动相关系数
+ {# M9 e, ?& {; i* {) `* u' V, H 以三点、九点、十五点、二十一点为分割,该观测所在时间区间的温度均值序列
$ @) @$ Y) B* z# A" d( O- y' O 每个观测6小时前的辐射量(一般而言不会恰好取到,此时取最近时间戳对应的辐射量); g8 F2 ^" u% N# z
df.Radiation.rolling('6H').corr(df.Temperature).tail()) e# |/ }/ s+ R
+ W2 J: K& q% }5 m$ o$ o# Q Data
# k- m) }+ `/ V' v& y3 v 2016-12-31 23:35:02 0.416187$ Z5 A( r1 p" I5 x1 g
2016-12-31 23:40:01 0.416565
" O: A1 u6 {$ ^7 l' E1 } D- v 2016-12-31 23:45:04 0.328574
. F5 E9 l" @: R4 d& A+ U' Y 2016-12-31 23:50:03 0.261883
, L1 t/ }7 j' t& T& l& g 2016-12-31 23:55:01 0.262406, i# Z8 z4 J/ p4 F! R3 J
dtype: float647 Z! L6 T/ t* t$ a2 _( X1 T D
1
T- V, @5 H% G4 s4 {, a 20 n- ] G5 y; I& t8 c2 Z
3) b0 i$ ?! R/ b
40 u* h) n4 o. _
5
" U5 n' X1 @6 G- B4 K5 ^, Y' g 6( O2 V% G! \' S% e
7- ]: R9 U5 J- y% Y( V" N6 W
8
$ D, } S# y! m 9
& } t; Y E1 y df['Temperature'].resample('6H',offset='3H').mean().head()
5 v4 n. d+ b# z
6 r# W4 p# P. k. S Data8 j9 Z- c" I2 \
2016-08-31 21:00:00 51.218750; g" P# v0 r+ Q3 q; a8 U* Y
2016-09-01 03:00:00 50.033333
2 G' _2 V4 ]9 w* A 2016-09-01 09:00:00 59.379310. t& R ^# j, g2 e) J$ r
2016-09-01 15:00:00 57.984375
$ b( l0 N0 B- W2 k! M& s3 I 2016-09-01 21:00:00 51.393939
0 `( R: v! Z p( W7 U Freq: 6H, Name: Temperature, dtype: float64
/ m% G, \" Y# _/ V 1
- X: K4 `: l5 q 2, h, Q+ N$ e* W. Q
3# d, x2 `$ z" A& G$ I7 z
4! \' I" |) ~% q5 V6 d+ z/ m+ z G
5
+ Z% a" {* c: m# e3 F: \0 K 6
: @5 @2 X1 M4 K% ~$ L8 l, h5 W 7
+ c/ F1 Q7 p8 J; N 8( z; n% l* W2 \
9
8 X* \# [/ E$ y2 K% \ X( O( H9 U2 u 最后一题参考答案:
, v1 W8 n- n7 N$ N/ x U 4 H8 ~5 J% r4 N- I; k& w; J0 N; N
# 非常慢 p5 b7 i! a4 D. T2 m7 a7 s
my_dt = df.index.shift(freq='-6H')9 q/ x5 R& Z4 ^ M7 s7 i
int_loc = [df.index.get_indexer([i], method='nearest') for i in my_dt]1 ^' Q, h' U1 p* G
int_loc = np.array(int_loc).reshape(-1)- g9 ^; h7 ?! U& }5 k7 C5 j/ q1 \
res = df.Radiation.iloc[int_loc]0 ^( ?- z; `, u9 [
res.index = df.index
2 p9 W8 p' w; a res.tail(3): C. [ F# X( c: W' f" N
1
& S# t( p6 L. M* ?9 n; q' N/ h4 T 2
1 c* H3 W, J4 b0 E- E 3
4 {: A4 o; c7 W5 H 4
+ b! c. W6 Y" s 5; z5 H! E/ h! B7 D# {. w" r+ l
65 M* Q& ]* x ^' Y% A/ f
79 g# }! S7 i( H6 M
# 纸质版上介绍了merge_asof,性能差距可以达到3-4个数量级
5 X, j# b# C, s' S1 z/ r target = pd.DataFrame(
9 _/ x- J! H" { {
1 B {; S% p( p( M "Time": df.index.shift(freq='-6H'),* v1 b1 d+ G' V) W
"Datetime": df.index,
% d$ z5 p% P% p3 O) P' E }
& e# t1 N) Q0 w0 G% S7 q+ i5 z )
9 W! N+ f4 o6 i' f, H& _1 i
' e% j: l+ M9 J5 S: [3 F res = pd.merge_asof(% C3 c8 U3 e+ x0 f* E* z4 u: \
target,
) Z a* I' C0 t) N df.reset_index().rename(columns={"Datetime": "Time"}),6 d/ X f+ k1 @9 K; y# d
left_on="Time",7 m6 k+ D7 H3 @7 Y: s( b0 r( H
right_on="Time",
( F9 C3 _" `' ], m direction="nearest"
9 u& V) n+ G$ v1 w7 F ).set_index("Datetime").Radiation
! t! E6 p J3 [ E
* b" v3 O9 ~% B* C8 s E! j. L res.tail(3)
6 O* Q7 m& l. L0 u1 J* [ s Out[224]: # J* S# m% C/ V. e( Q Z% V' y
Datetime4 p. r* f! `- Z- j
2016-12-31 23:45:04 9.33
$ c9 ?: H$ ]+ x$ [+ n 2016-12-31 23:50:03 8.491 v4 y4 d2 T* o u! _% g' S& m
2016-12-31 23:55:01 5.84
2 [# k. _: u0 w% C+ ^6 w Name: Radiation, dtype: float64
9 ]: F# i3 N& U' d: J( G8 Y % |! `1 z4 A% f5 `
11 v0 C R0 ?2 ^3 U& \' `7 K, M+ i
2
4 z3 I( w( U3 R, N$ x. B% \ 3
1 A5 s0 `2 m3 \! z& }7 w$ l 47 l8 n# K( ?' K" R0 D- O
5) K3 u- s1 l* p- H% p6 m8 R' @/ K5 h
6
$ A4 y" _7 R" U4 r: H& u6 W 70 f; E# k: D- @1 E/ W
8" L Q. w: r+ U! F; _
93 `" o0 j: r% b* m) H# a. q P
10; Y) W% N! [+ K0 I0 F2 q/ b: A; {; [5 ~
11; B7 k4 ? O+ P6 w- z# h
12
, h3 V4 ^. f" z+ o- o6 f 13
+ P' B4 c. G1 S! G* j 14
+ x$ w- K$ A2 G' {: |7 ? 15
Z) _4 I9 Q( {' G! y0 x# D5 x: k 16: R3 F4 f G: V7 Y
17
5 U( v- b! q! H5 C- E) U 180 G& Z7 A/ {9 d5 p8 f
19
) J4 S% A4 J8 i" f4 f9 U' ] O 20- |! p8 m+ P( K
21
+ {* U! p8 t3 H( T 22: z8 g7 H+ ~, V1 S9 ?% b- g9 a
23
5 F; Z, v3 I( e2 p+ B q: d Ex2:水果销量数据集7 Z# D4 Q, h5 W# E1 C
现有一份2019年每日水果销量记录表:
7 _* s2 ?" {* k* Y' r9 d* M / A* m/ U3 S. J0 l" n/ |4 e) [
df = pd.read_csv('../data/fruit.csv')
! T. e3 B& A! {( E8 h6 o df.head(3)
0 c# F1 _' _6 f/ i% B + i/ w3 y) k/ T: s, C
Out[131]: + Q. q/ M( c- F
Date Fruit Sale
( k- l$ Z0 p% u% c9 H% Q 0 2019-04-18 Peach 15; V1 L. e) a4 N0 ?) z7 R1 \' }
1 2019-12-29 Peach 15
+ t# O$ m5 p8 ^ W I' Z 2 2019-06-05 Peach 19
/ _( }5 [; i. a v2 T 1
; O5 i3 O7 T$ H; _; ]1 T* N 2
/ v5 l7 ?" C& e2 N; ^ 3
9 s0 w' M' A) M) F7 u7 ]: L 4+ \9 m/ t0 N" j" ] h( `% G& X
5
" V! \5 b: e' [; }" T7 {/ f 6, t+ n. n) g1 O. H' p
7
; l; r: {; d) Y. h Q$ v. v: q 87 |8 y% ?$ B% n! r4 L
统计如下指标:
5 e7 k; I8 w: {4 c& k* H6 X 每月上半月(15号及之前)与下半月葡萄销量的比值* V& |- S$ H$ k& Y9 a6 x! {
每月最后一天的生梨销量总和3 m! Z# N, D6 P/ Y1 Q! N
每月最后一天工作日的生梨销量总和4 J6 \7 ]# J- u) ^' L7 L+ d
每月最后五天的苹果销量均值
& J& P! C1 ^" S* j4 _4 @# [ 按月计算周一至周日各品种水果的平均记录条数,行索引外层为水果名称,内层为月份,列索引为星期。
2 J; w) H/ ~1 X! d 按天计算向前10个工作日窗口的苹果销量均值序列,非工作日的值用上一个工作日的结果填充。
. A9 n0 g( g1 P+ S1 V) Y( ` import numpy as np/ b/ D8 x9 v# q! X9 p
import pandas as pd0 l: k# L$ s2 r6 w
1* `: U# E2 [: T" Z8 e" m: D* r( t# X
28 y8 E, S( r# u* Y. }! ]- _
统计如下指标:& h: [' u5 h4 H
每月上半月(15号及之前)与下半月葡萄销量的比值
8 |3 |# _. W3 x 每月最后一天的生梨销量总和
c- h2 s f' c D9 |5 [/ c+ H 每月最后一天工作日的生梨销量总和, J" o( D* b I5 O
每月最后五天的苹果销量均值' B, T% V& ]8 V k: y
# 每月上半月(15号及之前)与下半月葡萄销量的比值; l, j0 I, x# G0 Q1 F- t* C
df.Date=pd.to_datetime(df.Date)/ y3 ^' |4 o( ` }# }
sale=df.query('Fruit == "Grape"').groupby([df.Date.dt.month,df.Date.dt.day<=15])['Sale'].sum()
* t! }& ~, a7 b/ E# s" S. `% b sale.columns=['Month','15Dayes','Sale'] # 为啥这么改没用啊
9 u+ @* @, p; q' M- C* B- t' N sale=pd.DataFrame(sale)
6 J% W) l9 x* C$ ^6 C3 g6 X7 W" n5 X3 ^ sale=sale.unstack(1).rename_axis(index={'Date':'Month'},) c1 t$ w# a4 [% X3 l
columns={'Date':'15Days'}).stack(1).reset_index() # unstack主要是两个索引都是Date无法直接重命名
. [5 Z4 D) D" x( `8 B1 t" W' K sale.head() # 每个月上下半月的销量
+ c2 y! T" n4 ]8 b
( i# k) N; l8 D; ]5 I7 ` Month 15Days Sale3 P/ m+ V K2 J% Z
0 1 False 10503
1 c$ B( ~/ b, P9 z& e. u% j 1 1 True 12341) C* R( q5 o4 q6 c# _8 t% E, E
2 2 False 10001
, F" o! _& q. C; W 3 2 True 10106
2 c( D; m. x; F* Z! s* J 4 3 False 12814
9 C/ H2 g% N3 A& T
6 a" j8 ^1 U% e! }+ a; V) t2 { # 使用自定义聚合函数,分组后每组就上半月和下半月两个值,根据索引位置判断求比值时的分子分母顺序
9 R- W/ M; O) Z- J sale.groupby(sale['Month'])['Sale'].agg(
4 y& O" h5 r3 o+ K; v a lambda x: x.max()/x.min() if x.idxmax()>x.idxmin() else x.min()/x.max())0 y7 } o" e' k% t9 j" w) ^ n
, K9 N4 Z8 k" T; E7 ]3 C Month
2 ~! J. f/ [' f, w3 |3 B 1 1.174998& F- W3 i6 A" ^ G. C2 k) V
2 1.0104994 d2 F! @/ S7 {& U
3 0.776338. n1 r3 s1 b- V' G0 j) A. x
4 1.026345, [/ C- y+ p- W! [+ e& g) d
5 0.900534
' R+ B( H+ r ~' }4 m+ F; T 6 0.980136
2 u( L* v) K; G! \5 d 7 1.350960
6 g7 d, N! A0 l 8 1.091584
2 v. k% w6 a8 [$ J, I# g* ` 9 1.116508
- e; ^ _6 t: d1 G* ?8 X" V9 l2 { 10 1.020784$ k: X4 P2 |( h4 F; n& a+ X
11 1.275911
& y0 K& |) D* ~! @" m% z4 E3 M 12 0.989662. C3 j( S3 ^9 T! X
Name: Sale, dtype: float64
/ Z8 D. O$ `8 v 2 c* d @# Q, u8 v1 ?& W
1+ F4 v% M3 P; g
26 f3 l9 m" y! z. a- F+ X0 m/ N
33 E* ?6 V, K1 n0 f' a! x* W
48 G9 y( D$ G: x5 h, v0 _
5" i; j( e3 h( |' I% ?* M, L% `
61 e4 `1 O5 L* w5 O; ~
7
. u4 g F8 ]8 z% `( Z0 ? 87 G0 v( w. j' a4 z- z
9
g; Y8 u c. [ 10
/ Y7 U; r2 m6 s5 e$ H* ~ 115 _1 S1 | r( B7 l Y1 n4 w
12! b8 U7 |) A& N( Y Q8 N/ M3 M8 j
13% k7 ]- `! |. X
14
' ]' m6 K" _) {. ]7 b6 } 15* l; {$ U7 O, T/ q- {* T5 B( P4 u
16: ]5 F% d" u# }/ Y9 q0 O( s" @
17! `* V1 o% @+ H1 \+ P
18
' \7 Z' q3 ?; q# R' U0 M 19
; i# u) s; @" C6 @ 20. k& l) ^. y( h, V* X6 E' ^' k
214 h0 T5 v4 C$ l7 x( ~/ w: a
22
7 B5 h" U" N2 |! z& m+ i+ B 23
0 v1 f0 I3 z. m/ A 24# | g4 S/ z0 `0 w- ]
253 W5 o% ^- A5 \: S4 g. V! K" i4 k3 E
262 A( E- g, j/ W. L" P
27
4 c0 w5 \; T. {/ b F/ r 28/ N( M7 ^8 N+ r; m, Z5 x* u9 X
29
3 {6 F; |$ n) j9 H$ a1 Z$ W* K 302 r" o& V6 l5 g! z, H
31& t2 _* A- W' p- }0 r
324 {, F) h) V. e- W: F4 p9 C
33
1 H5 A# U# C7 }9 F 34
, c6 v+ p$ g2 b1 X, B3 Q* o5 L # 每月最后一天的生梨销量总和6 a. e& U, P; Z2 v1 a
df[df.Date.dt.is_month_end].loc[df.Fruit=='Pear'].groupby('Date')['Sale'].sum()1 s, x& Y% X6 |
6 w/ I' S! F e7 ^ Date2 u. L# c( s) q, V( k# A
2019-01-31 847% u) ]) L" w( o( h: G6 r
2019-02-28 7747 `" v$ a, N& B2 J0 j
2019-03-31 761
8 v5 \( @9 d0 v 2019-04-30 648
9 _+ i/ A% n: `) m4 v9 ^0 H 2019-05-31 616
; T9 _, B4 O0 l' L 18 j, @; w5 m3 ~5 [- w3 p
2, [( S* r! V( G- r$ v: h) z
3
5 m6 d) @# J% `; b4 \ 4
, k# L% k$ `' I/ M+ `7 b& ^$ j 5" q2 P, i/ K4 ?. n4 m
6
+ U& \& g6 p# m7 m 7! h3 J& l; D1 T: ^: o7 d' O1 E
8
8 R) f: Y# L, \) i2 }/ J p" A 96 G+ Y+ |/ E* t+ q
# 每月最后一天工作日的生梨销量总和& \. v* D2 b7 v: z
ls=df.Date+pd.offsets.BMonthEnd()
& F0 g3 e: m- N; X7 P* v: b% f7 p my_filter=pd.to_datetime(ls.unique())
7 m2 F+ z9 S1 K3 W2 F ^3 x7 O df[df.Date.isin(my_filter)].loc[df.Fruit=='Pear'].groupby('Date')['Sale'].sum()
% a9 O# W; u! I$ h + N* P8 W. ?7 f! @1 ~
Date
5 Z1 C; r. M: @( _ 2019-01-31 847
6 E( u: Z5 B' Z# v( B$ N# `. B 2019-02-28 774! x: O0 M$ _' ~. ~, c
2019-03-29 5103 ^/ n& O0 j; L' A
2019-04-30 648
! `8 Q$ J8 d, Y! R1 \( s4 r 2019-05-31 616
' T/ U# Z& J, c 1; ~: M' F# q- A
2
$ l9 V2 e2 ?) E! t5 Y4 P. m3 w, a 3
2 s0 R# @& G- {3 G# ~ 4
+ e" |( o# V* n7 K" N: w" C 5% k9 \1 Z0 J0 M d+ I
6
0 m* x* [' f ?" h8 [ 7
. w' p J9 n/ V+ s1 D$ [ 8! D! B9 f+ `, |$ G* t6 N
9
3 q8 H" ]) X( T; c1 l4 @! z h 100 F8 ]3 v: m3 L
11
7 V$ c/ M7 v3 K: S. @0 _ `# R2 N # 每月最后五天的苹果销量均值
! l6 `3 U8 V# A: _4 P" R start, end = '2019-01-01', '2019-12-31'# k, ?( O7 z `7 D9 g
end = pd.date_range(start, end, freq='M')+ v5 i- [$ h( k: N) f, R+ j
end=end.repeat(5) # 每月最后一天的日期列表,重复5次方便做差2 _/ m+ ]* q& M3 s
/ s/ J; h: C& r, |8 H2 A: E$ p td= pd.Series(pd.timedelta_range(start='0 days', periods=5),)
6 ` s/ {. C5 G td=pd.concat([td]*12) # 日期偏置,最后一天减去0-4天
: h0 R( R6 j2 D$ [& r. M end5=(end-td).reset_index(drop=True) # 每个月最后5天的列表
4 Q: O' v8 \$ p 8 F. S$ q! {) W
apple5=df[df.Date.isin(end5)].query("Fruit == 'Apple'") # 每月最后五天苹果销量8 J0 ^ O) z B1 n2 v* Q
apple5.groupby(apple5.Date.dt.month)['Sale'].mean().head()
2 o4 F3 U! a X+ m
0 h, h2 o! S4 w3 W% b: f Date7 P& h0 F) m) X
1 65.313725
2 E. N* o9 r Z 2 54.061538, z2 x- Y5 A4 n8 Q- ^, L U* e# h7 k
3 59.325581
. |3 i5 r: p) i; _2 w 4 65.795455
/ H: L4 r o' @$ `! L o 5 57.465116 N1 ^8 f7 v. J
/ L) ~6 W/ P% h5 W: } R, M9 B 1
# A P: `- r) c7 q1 W0 K1 u" b 2% d8 d$ L- M% C6 z- O$ ]
30 w* V' @" g! u/ l1 l7 a! ]
4
# m' p; a$ f$ a! Z+ Y) ^0 v4 x7 B# V 5: x& S0 o8 L4 }: u
6
" M( \/ L# F4 r/ F! | i" G, c 7
6 \( A. f5 y! r+ C 82 {; O7 @: k- {) ^$ Z) h
9% U) {2 L5 h2 V
10
- W: k9 q0 Z( o E 11
6 T4 K, C) H; X; ] 12% d! R& H2 {6 y6 y6 [' O
13
- X* o4 |. c* \, D U# v9 {+ U 14
- f6 e4 D8 F! e9 } 157 ~: E. k: g& {$ W6 @( W
16
- i9 E; O0 ^9 t) e 17
* _& h$ H3 c# f" b+ ~6 X 18: ^( h4 ^: w, O0 w* U
# 参考答案:+ W4 p- Y' @' X" u. E g
target_dt = df.drop_duplicates().groupby(df.Date.drop_duplicates(
4 O4 S: n7 S( F* g* V ).dt.month)['Date'].nlargest(5).reset_index(drop=True)
" Z+ K2 V' Q% q% U" S
* N7 ^; u1 _8 g" U& k: E res = df.set_index('Date').loc[target_dt].reset_index(3 W# M$ ^2 _: b7 U+ f5 h
).query("Fruit == 'Apple'")
9 E, y1 C) K' u5 R
" z& x7 }* `6 R* H/ U1 i+ g: Q: G res = res.groupby(res.Date.dt.month)['Sale'].mean(0 p/ P, {9 A- C6 ?! b8 j4 M$ O
).rename_axis('Month')1 Z* f9 P, H" ~4 Y* c u
, C# m) z5 S7 b2 Q2 Q% @/ h3 b
( g0 l% V- Y" H% C; P res.head()
+ U# K# Q* h, ] Out[236]:
9 j1 |2 n+ }; C# G0 C5 h( w, i Month, Z) o, G: m" c' Y4 b
1 65.3137254 s; B0 Y* B. u1 m/ m, {! [; [
2 54.0615385 ^2 v3 N# J4 h' ~1 X( W' I9 S
3 59.325581
$ y* K# w2 D" [: q: Y) t8 T% C 4 65.795455
5 i9 d, f: U# ? 5 57.465116
- } B; [5 a9 W- c Name: Sale, dtype: float64% l% X, `6 ]7 t( D* |9 H3 o! ]& p
+ K, H# A/ F" ?! y$ l: a$ X 1
, p& k& L& K; _, `/ l# r( o 2, B: z7 V0 k. n% k9 U6 `
3
6 n `6 K; c9 T+ P' a( | 4
( ~2 ~; F K" `. B' w) V 5" L! r4 K7 \7 V6 L6 @: Q( E; x5 q
6. ^% u+ C8 R5 W9 b# X2 s
7, G1 ?# n0 g7 U
8( t3 l1 p9 f" L4 o/ T4 D" O, V
95 H m4 Y0 p: q1 c$ x% w
10
0 V5 \8 k3 |) |3 P3 a8 Z, _7 s 11
, F4 z/ B, y* [$ s/ l* P 12
% v8 z: c3 n* t( @5 L& M* G 13
: n3 t5 C/ s1 x 14
" M9 s5 n' p' E( d2 z" {5 t 15
, T! i& Y# q8 ^' Y9 z 16: F) F) t L h6 U
17
* y+ i( j" h2 J) k: \: y" T2 @: B 18
! {0 g+ P: S1 V" R! F3 U* R 19( h" Q5 [0 s7 c. R- E
20! u2 L; E7 f2 x u, I5 u7 W/ a
按月计算周一至周日各品种水果的平均记录条数,行索引外层为水果名称,内层为月份,列索引为星期。
5 u8 t4 n6 n# d8 y$ j result=pd.DataFrame(df.groupby([df.Date.dt.month,df.Date.; x* q8 `$ c* y O5 e
dt.dayofweek,df.Fruit])['Sale'].count()) # 分组统计
! u" w7 m/ U$ q$ [ 5 [; [6 ] X& a, _
result=result.unstack(1).rename_axis(index={'Date':'Month'},: {: h" E0 ^* g1 w1 `
columns={'Date':'Week'}) # 两个index名字都是Date,只能转一个到列,分开来改名字.
) }( r# t* v3 F+ Q% f$ O- { result=result.swaplevel(0,1,axis=0).droplevel(0,axis=1)
# u, a( r' \+ k result.head() # 索引名有空再改吧
/ K; B" ?* C4 w : a' l) Y) A! C( U0 i1 d) o
Week 0 1 2 3 4 5 6
) w) d! p6 G: `) l Fruit Month ' s- g$ w/ r+ K* o4 h
Apple 1 46 50 50 45 32 42 23, L9 H7 L- E1 G& N
Banana 1 27 29 24 42 36 24 35
* O. h8 f) Q& m2 t Grape 1 42 75 53 63 36 57 46 E6 w2 b: T+ F1 a. k+ a
Peach 1 67 78 73 88 59 49 72/ B) Y; e r4 g/ p. W% W+ h6 X. m
Pear 1 39 69 51 54 48 36 40- E! u4 o7 J) ^2 f. S0 ]
14 Y5 R9 P2 B* k! q& m/ Z
2
( }: P1 I" b% c, H% q 3
, A! A9 |( ?" P* a ^ 4
2 ]) Y) y: D4 \# W- H9 b+ F 59 w z+ i" G: R. l. f) h1 S1 {
6
5 A. ] n- u8 P$ T* r 7* I7 m2 _' o( n! z! h$ I
84 }( L0 S( `% ]
9$ C }! g; g) {' {, k( M8 T2 O+ H
10; @- X' |. {. c o5 v
11; }3 @6 _ A$ P3 T
12
6 r/ y: o n7 |/ F5 \ 13
5 |) S" z/ e- }1 S% p 14
3 i. V; p$ h4 K8 k 15
- b; S; d: j$ A% ^( z 按天计算向前10个工作日窗口的苹果销量均值序列,非工作日的值用上一个工作日的结果填充。
^: E/ ^; q4 I7 ?! y5 l4 f # 工作日苹果销量按日期排序$ p: U5 p9 w' c4 K, D$ g" X
select_bday=df[~df.Date.dt.dayofweek.isin([5,6])].query('Fruit=="Apple"').set_index('Date').sort_index()3 {; p! {' g ~4 ]* T
select_bday=select_bday.groupby(select_bday.index)['Sale'].sum() # 每天的销量汇总( z" {' H9 }% h6 r8 f+ `
select_bday.head()
W) V7 Q2 Z$ v( f' E0 g + ~. R, z5 ~! F" N0 k+ m5 v/ G0 s0 r
Date# i0 P$ }5 \7 I, ]
2019-01-01 189
! N- S9 N3 ]5 G7 B6 ^ 2019-01-02 482
% T' C* h7 Q* D* \9 X% p! E 2019-01-03 890
, b- C9 v5 m _/ H$ D( t! J, \1 n 2019-01-04 550
7 f* ^4 s' F5 ~7 b 2019-01-07 4941 U7 d% u0 x$ B1 v
' z+ M: m0 R( [
# 此时已经是工作日,正常滑窗。结果重设索引,对周末进行向后填充。
/ Y2 U" l' p) u select_bday.rolling('10D').mean().reindex(df.Date.unique()).sort_index().ffill().head()
% g( q! }7 B) E0 b . l" _" q9 h5 M% K
Date
, ?3 |) o. C; i 2019-01-01 189.000000! z5 ~4 B& j' {0 n; j( Y* i
2019-01-02 335.500000% U2 S" i) p" ~7 v/ J1 Y7 e9 [! j5 z
2019-01-03 520.333333
3 X- B( b9 s* s# Q" k3 p+ P 2019-01-04 527.750000! X4 ~% q8 T" _/ r, N/ G" S
2019-01-05 527.750000
. h- }) C% ^: [$ Z8 L' r* S/ u & c9 N) s" m" `9 Z y! t9 p8 a x
————————————————
) t8 j/ b4 E5 i 版权声明:本文为CSDN博主「神洛华」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
; U& D3 \5 R9 { x5 C6 E 原文链接:https://blog.csdn.net/qq_56591814/article/details/126633913
9 \6 y" w- |. d! O2 z9 _ - D# m) d/ u1 T( s8 i% Y0 `- K
2 Z9 [+ |3 B. C* j
zan