- 在线时间
- 1630 小时
- 最后登录
- 2024-1-29
- 注册时间
- 2017-5-16
- 听众数
- 82
- 收听数
- 1
- 能力
- 120 分
- 体力
- 563261 点
- 威望
- 12 点
- 阅读权限
- 255
- 积分
- 174201
- 相册
- 1
- 日志
- 0
- 记录
- 0
- 帖子
- 5313
- 主题
- 5273
- 精华
- 3
- 分享
- 0
- 好友
- 163
TA的每日心情 | 开心 2021-8-11 17:59 |
|---|
签到天数: 17 天 [LV.4]偶尔看看III 网络挑战赛参赛者 网络挑战赛参赛者 - 自我介绍
- 本人女,毕业于内蒙古科技大学,担任文职专业,毕业专业英语。
 群组: 2018美赛大象算法课程 群组: 2018美赛护航培训课程 群组: 2019年 数学中国站长建 群组: 2019年数据分析师课程 群组: 2018年大象老师国赛优 |
9 k+ I. P" b% M) `2 l Y- j
9 N. `! ^+ q9 A8 [0 `! ~! n+ `7 w" U7 C, M6 l5 G [; ~+ H3 |$ v
文章目录0 r J4 [1 O; M& m& T7 b! Y
第八章 文本数据7 k9 c: g! p: e- F
8.1 str对象
% A( e( S2 I6 S$ p/ q$ j2 s8.1.1 str对象的设计意图
2 g* k) \" l4 I) o8.1.3 string类型5 k5 C8 H3 y8 t0 ]2 w
8.2 正则表达式基础
! Z6 L% \% w3 s/ F1 B! j8.2.1 . 一般字符的匹配" n2 A: _! h4 o) y- m: P- s/ ^
8.2.2 元字符基础; p5 T9 i0 ^) `6 t) y% T
8.2.3 简写字符集
6 T& Z% r' P# L0 l! _ x8.3 文本处理的五类操作# t- U6 R* L$ z2 y2 l
8.3.1 `str.split `拆分
* ?/ V/ O" b$ \ A2 J# J) O& z9 i8.3.2 `str.join` 或 `str.cat `合并; C8 P w9 w/ ^& J1 {. m5 p6 ~
8.3.3 匹配4 W, B2 M6 U: H8 Q; `3 {% f! V4 Z: \
8.3.5 提取
1 ^' K$ b# A% R8 F- r9 {# f: U8.4、常用字符串函数
, U9 H' K( N/ S/ l8.4.1 字母型函数/ Y: J5 x( T" _
8.4.2 数值型函数4 E( F: c- a/ v0 f, h( k o5 v
8.4.3 统计型函数
M+ V. g' m/ N4 N |8.4.4 格式型函数
3 ~6 R! j% l* x. H6 m0 e8.5 练习
6 C. Q9 B: G. ]Ex1:房屋信息数据集% B f1 J+ N4 A) X; A8 _ M4 [& j
Ex2:《权力的游戏》剧本数据集1 L, Y: Z. \/ R7 Z# q% Z0 ^. h0 b
第九章 分类数据 _6 m# I# J$ D$ ^' _8 g
9.1 cat对象; H. X4 f U# B; x' a
9.1.1 cat对象的属性8 F7 y3 H1 \' V4 @& t+ v. |
9.1.2 类别的增加、删除和修改, m, `- D5 r& P5 c! e, t
9.2 有序分类+ ]* v. @, X8 D+ J& U1 f; ]! I
9.2.1 序的建立2 R4 [0 l5 @! N0 G
9.2.2 排序和比较$ J) y" j3 o# {; n9 ]) z0 _8 Y
9.3 区间类别
: ?( Y( s+ m3 N9.3.1 利用cut和qcut进行区间构造
7 |$ _4 o; @4 E, P7 ?7 E# ?" M9.3.2 一般区间的构造
2 A- \" k4 c7 K" L) |2 s" A9.3.3 区间的属性与方法% U; k+ x6 G6 Y. W& [3 S
9.4 练习
, d. U x9 i7 n, v4 LEx1: 统计未出现的类别" |+ i0 ~9 X- W b5 x) F
Ex2: 钻石数据集7 e* m5 `, S3 S. b: \" Y* b: }
第十章 时序数据
. [$ T+ t% G7 y* F( Y10.1 时序中的基本对象& R( \( c$ B( H* G* \4 A
10.2 时间戳
9 }- Z0 m$ i' s1 m10.2.1 Timestamp的构造与属性
: ~; m! f. g8 ^0 C, k U) m10.2.2 Datetime序列的生成% w) x. X3 E5 G
10.2.3 dt对象
% z& R$ \6 a" Y4 M% s10.2.4 时间戳的切片与索引
0 E! i9 S- G: M- ~8 e) x* D9 Y10.3 时间差& a6 C7 A, A1 _$ _9 Q0 Y0 Y
10.3.1 Timedelta的生成0 f( K$ J# N# M( Q
10.2.2 Timedelta的运算
5 Q7 u% t3 o& T' D10.4 日期偏置
( p$ R7 `" _ k( a/ |% V; ?; @10.4.1 Offset对象
6 B2 ^: Z+ F. l, w) g10.4.2 偏置字符串
. _9 ^$ u: D; c- K- _( L10.5、时序中的滑窗与分组
/ i2 T3 Y) H2 q: a10.5.1 滑动窗口1 o8 V& F, s8 }7 Y8 H# }1 x
10.5.2 重采样- O9 ?1 ~& I+ a, ]
10.6 练习2 D9 g% r A" A4 W) J' G# r9 ~
Ex1:太阳辐射数据集- O( @4 N0 G+ o& J6 @
Ex2:水果销量数据集
7 ^6 \- I- d9 m. m: J4 N 课程资料《pandas数据处理与分析》、github地址、讲解视频、习题参考答案 、pandas官网* K* _& D( m" r: F
传送门:4 r/ E. c. p' B3 }
" j! B \8 M4 L* K+ ~datawhale8月组队学习《pandas数据处理与分析》(上)(基础、索引、分组)
: A# E* s, x& a/ [8 t3 \datawhale8月组队学习《pandas数据处理与分析》(中)(变形、连接、缺失数据)
6 R M; c" F/ j7 q+ e9 [第八章 文本数据: t+ T5 X6 w( I. j
8.1 str对象
& y# w! }9 a' a8.1.1 str对象的设计意图
$ c8 ^/ `$ w( ^, t, P4 R" h str 对象是定义在 Index 或 Series上的属性,专门用于处理每个元素的文本内容,其内部定义了大量方法,因此对一个序列进行文本处理,首先需要获取其 str 对象。在Python标准库中也有 str 模块,为了使用上的便利,在 pandas 的50个 str 对象方法中,有31个是和标准库中的 str 模块方法同名且功能一致,例如字母转为大写的操作:
, ~5 T9 U3 |' S; z; B( V! S |
% L W- C1 V$ Q4 a6 q* s/ qvar = 'abcd'
. \( d" b& R( y) E+ U% M Ystr.upper(var) # Python内置str模块9 W5 T2 h+ U- R( O
Out[4]: 'ABCD'
9 l1 O- |& v/ Y2 P- y5 u- ^0 d! Z8 {7 T7 j6 E( T! m' T
s = pd.Series(['abcd', 'efg', 'hi'])
2 ]) j; \ u9 @$ P- ^5 V) M5 T$ t
9 ^$ O) c( x. f4 is.str
! H% y2 _. U) ?3 } ^8 eOut[6]: <pandas.core.strings.accessor.StringMethods at 0x2b796892d60>8 [4 `1 z& x5 x; E1 _: h5 S
2 d5 c+ C* h7 ?
s.str.upper() # pandas中str对象上的upper方法& K9 f0 E# Q5 O0 O
Out[7]: 3 x3 x' |/ E! O5 C
0 ABCD" i0 {/ l5 p' @, X& e" [) I! C$ _
1 EFG
" c R* h8 Z( t+ `2 HI
5 u3 K$ e1 y8 Udtype: object
2 J: Q' s( N% _- e1! K s: B" |9 c" G2 H: f
2
+ B* Y Q$ j6 N+ b1 W& X L3" ~* ~ ^( O8 {8 ^
4
L+ \6 d! K0 p2 e+ [5
4 K9 M" I" k% {+ g1 h# F4 L: u: w6
, A5 }. r; D5 h; l# K7
: D" G8 ^1 [/ W2 s4 P' `5 S8
# s: I& X( z3 { [9& g9 R' G% m' t0 R4 ^/ F4 m
10- b( a6 p% J: Z S* V3 ^4 c9 d
11
4 O3 D( g3 O, S% z" t* f12
0 M0 j/ K6 ]) v9 P5 ]9 S13/ R4 j' f/ ?7 v9 N7 p( s$ c3 a
147 [* u9 Q; T! `8 K) `) ?8 R
156 c3 N f1 |! a( M2 t
8.1.2 []索引器6 S" ^2 G/ |2 D$ o: z
对于 str 对象而言,可理解为其对字符串进行了序列化的操作,例如在一般的字符串中,通过 [] 可以取出某个位置的元素,同时也能通过切片得到子串。9 ^+ L+ q- C5 k/ Q8 X" H
pandas中过对 str 对象使用 [] 索引器,可以完成完全一致的功能,并且如果超出范围则返回缺失值:
7 I% h2 k2 ?, C7 D2 R% Q! K! x$ t
s.str[0]! C5 A' j1 S. o' ^* f/ z, z
Out[10]: + W' {+ E5 S9 b
0 a
% j4 W0 j$ e9 @9 {1 e
, D0 }$ I2 C, n; p7 m: o: h2 h
' `1 W0 j0 ?# W4 Idtype: object' @7 N. S' ~% _9 E& F$ p
* R0 P( \' y4 y3 J* o/ Z _5 ^% As.str[-1: 0: -2]2 u5 E* j, p( ~& @' K
Out[11]: 9 b- E' Y& ^1 ^# |6 }: j8 h
0 db1 h; O8 ]" V- t
1 g
4 }: L P _" J z! ~4 r2 i
; W! c. I2 I/ s' c3 Fdtype: object
: ]+ Z$ X2 C+ m5 z' \
& L! e; ?% S* [; |9 V) b8 J3 ?, @s.str[2]" }! V0 x4 {3 ~. ^% u' a
Out[12]: # e+ J4 o/ V1 e/ f0 m0 M
0 c
; |1 U4 Q6 d6 V6 [1 g
/ m9 G# Y6 T, C; A" \5 o2 NaN5 V$ B, v3 G: {5 O
dtype: object
9 |7 c9 i j+ l' Q! i( d
# h$ a8 x/ S- d! r& o& X18 Z8 ~) k8 Q! _5 E7 c& O+ P
2
0 V+ _. r# t2 r9 `3/ D# `# h8 a9 e6 y) d$ q7 G! r
4
6 x7 h, ]7 G% n0 W# R [5 |5
' }$ Y9 `+ H/ D- N* j8 F6
% m$ s$ \3 [$ y) G' t6 o7
/ B( r7 E1 R; |8
- Y6 p) @0 {: U9
9 _+ q& U) j) a. n9 R100 N0 ~( d4 N. S9 Y. M* L L& t, T
11
( @9 \" y3 U1 q9 F3 V, U; Q7 S12% Z( @3 h6 ]8 J, Z4 b# O
13
8 f6 ?* w$ I. e$ k' z! b14
/ z+ t$ w3 w* y$ m0 l15" K9 s" I4 n4 b* A! b
162 k# |. _. a% ?7 m! n
17* W$ X) f; g" L
18
3 K8 \% |/ x3 ~: p1 ?193 Y/ J! Q" M' m# |$ _
20" p; [) M- c' x/ |; B! _9 z
import numpy as np0 C1 v( N8 f! g
import pandas as pd
; S- M* i; @) G+ [. a' V% [' ?
' \/ L; }" S6 A4 S0 h3 z Qs = pd.Series(['abcd', 'efg', 'hi'])7 h) i0 n. V" B+ B
s.str[0]
/ L. c4 D3 c; R- Y3 t, {( O7 x10 H- d6 n5 s8 A S7 W# `7 s) Z1 I" D
2
H( J, i5 @4 k+ ~; o; M3
: V+ f( \. |3 b) F! Q4. \! ]+ J1 ^- |. T+ i# Y9 G, _
5+ H' S! R7 p# n: u
0 a
! {/ j7 @) ^4 |/ K4 d& g6 @# D1 e
/ y3 n9 i5 w3 y' B0 W% f( O2 h! ~* [" n; X0 s+ O; S+ Z# J
dtype: object' J' _" f% B5 `3 F7 b8 I+ Y8 y
1( B0 Q7 w# I7 W
21 V. i" o- m$ d" U& }
3
- q5 g) ?$ c& u' k3 o4 J* K! W; [4
: O0 h4 `% p9 t8.1.3 string类型
5 F" {2 V A5 R. e; i% ]: A1 I4 s 在上一章提到,从 pandas 的 1.0.0 版本开始,引入了 string 类型,其引入的动机在于:原来所有的字符串类型都会以 object 类型的 Series 进行存储,但 object 类型只应当存储混合类型,例如同时存储浮点、字符串、字典、列表、自定义类型等,因此字符串有必要同数值型或 category 一样,具有自己的数据存储类型,从而引入了 string 类型。3 J# i A+ G3 f& p, b y
总体上说,绝大多数对于 object 和 string 类型的序列使用 str 对象方法产生的结果是一致,但是在下面提到的两点上有较大差异:
% ~) S' x1 i7 ]3 S! G! p, y* P9 v! y9 v3 g" W
二者对于某些对象的 str 序列化方法不同。3 q7 n1 u4 p2 ~ N- ?4 G0 y
可迭代(Iterable)对象包括但不限于字符串、字典、列表。对于一个可迭代对象, string 类型和 object 类型对它们的序列化方式不同,序列化后str对象返回结果也可能不同。例如:
( u3 _* Y, J$ Ds = pd.Series([{1: 'temp_1', 2: 'temp_2'}, ['a', 'b'], 0.5, 'my_string'])
3 h @" a* A$ H( Hs
2 g1 H* V d" ]3 h& r* H7 n- `/ Q1
1 m3 g" q4 X# m2
7 v5 H/ M3 x. S" o+ _0 {1: 'temp_1', 2: 'temp_2'}! m2 G( m* q! D1 L3 `: r
1 [a, b]( y( [+ n: @" I) P
2 0.5
* v ~% s7 \' ?- L/ A* Z" i3 my_string$ a+ ]+ o. d; [- i4 {
dtype: object" V' P9 i2 P% [, y+ I
12 R4 `8 B8 z+ Y0 A5 q* M5 l4 ^
2
# y4 s1 k# N/ `/ f3
* c6 j& y' X& ^# t# _7 a' n4
3 J* U, o1 m( i# k& I5
( @$ W& G; g0 ds.str[1] # 对每个元素取[1]的操作
" l/ v$ D$ Z# z' k" N" E1
6 W6 Z/ M7 l7 ~7 _; q0 temp_1) e+ I5 V% J) K5 h
1 b7 d: w" r* ]1 l( n8 {
2 NaN
; ~& o! K; X8 t* h! B: W3 y5 |: ~# p& C3 R7 B8 d' b( ]' K: J
dtype: object
0 R& _7 y, x( M1( z0 P7 P6 q9 {, I
2% ~7 K# ?4 `! c+ \
3
. [( M; m$ j* k, i0 B4' \0 F. D, M) _. X* Y6 g& H+ F
5- E8 P2 S) y% Z3 c$ y
s.astype('string').str[1]
) D+ Z+ P; T8 H2 p# F1
! d, E" `( F. ^, K0 19 N6 H) N4 p: z8 |) g8 G" O
1 '
# A# v8 q/ O: u$ u! j6 Z7 r2 .6 |4 ?/ [% e. _; V6 j' E
3 y
) _6 {9 `8 {+ vdtype: string
6 @8 E2 \0 q! c: ], C1
2 W9 Q; b" E# @; z2( [& Y1 n% e- c, {3 b( ~
3* v7 O$ ?$ k! ~$ X/ s3 v' X% S
4
: ~% z9 y! o) Z1 e54 N" y+ S' h: v* _& g# \+ b
除了最后一个字符串元素,前三个元素返回的值都不同,其原因在于:
% y- O' L: e+ O* i; M3 h6 }
! a. Q* v) m- J" i) u4 Y R' _' _, k当序列类型为 object 时,是对于每一个元素进行 [] 索引,因此对于字典而言,返回temp_1字符串,对于列表则返回第二个值,而第三个为不可迭代对象,返回缺失值,第四个是对字符串进行 [] 索引。
3 q8 K- W! @+ s% R% {/ o+ Sstring 类型的 str 对象先把整个元素转为字面意义的字符串,例如对于列表而言,第一个元素即 “{”,而对于最后一个字符串元素而言,恰好转化前后的表示方法一致,因此结果和 object 类型一致。
i1 q5 v* l8 c5 Fstring 类型是 Nullable 类型,但 object 不是
0 a9 o4 w, {9 i5 E" p$ ]4 `- {; P, V 这意味着 string 类型的序列,如果调用的 str 方法返回值为整数 Series 和布尔 Series 时,其分别对应的 dtype 是 Int 和 boolean 的 Nullable 类型,而 object 类型则会分别返回 int/float 和 bool/object ,不过这取决于缺失值的存在与否。+ s( D* U t- B$ T0 }
同时,字符串的比较操作,也具有相似的特性, string 返回 Nullable 类型,但 object 不会。: K: |; A' S8 M8 ]% M8 h m+ m+ _
s = pd.Series(['a'])
7 _' u0 I- h' [& V C$ n! q4 T; m
s.str.len()
/ W. T4 X) ]/ W7 l; J9 m0 D8 wOut[17]:
) t& t. z# l+ I! b0 1
- E) C, u2 q1 h( v- Sdtype: int642 q$ w S5 l6 ~7 t# z
) Y2 q0 _' B/ x* J: xs.astype('string').str.len()& y$ K. m( l3 G4 k" W
Out[18]: - e0 ?: |; T, S, Y
0 1
- X5 \; ?1 O& h% D+ @% x" Ddtype: Int64$ A' {2 U2 z% p) a
- I# q$ F+ E1 D4 @+ j% Ds == 'a'
2 w$ d4 D+ u, N- S% P! _ T! kOut[19]:
, F2 k! ^6 ]6 S3 Y7 L( N1 H+ O" E- Y0 True. ]! M, r; X" A3 k3 O4 v
dtype: bool
- y$ }6 H5 ?; t1 D
. U: X6 J D3 R2 U; us.astype('string') == 'a'7 F# f' s8 S K | [
Out[20]: ) b0 @6 v/ D6 V7 Y) h) w
0 True" @. m3 b% n l. c
dtype: boolean
) u. K; l5 K- l+ N. Z0 D' S7 i7 i& f* L
s = pd.Series(['a', np.nan]) # 带有缺失值* {) Q a, r1 T' q
9 A/ T' C* ? @3 }3 R+ cs.str.len()+ l2 F5 u; l- Z' l7 X
Out[22]:
* {6 S1 ^" ^. O, l: B+ N5 g0 1.0: V! v2 b1 O$ S
1 NaN
" Q# y; e- R5 ^% q2 h/ n# Ddtype: float64# V6 b5 w0 j: o7 v4 {) q# Q
6 `' s; F% m/ l1 c1 ~( hs.astype('string').str.len()
1 `5 K/ Q, A7 i6 M$ h% q6 GOut[23]: # E4 |. N4 R% j# L9 I4 ^0 O3 y
0 1) S! D( X% s+ |" }- f
1 <NA>$ I0 Q+ R, }' i% H
dtype: Int64- k* B9 Y7 k5 ]7 E
7 m- u! _% R8 X: h# J! ], q
s == 'a'. w4 w3 J) X( C; f
Out[24]:
4 F- d. H5 [6 \6 F9 c0 True5 I5 ^$ z, v) s# x
1 False; O. t5 `/ G: \8 M7 R' u( D9 ?
dtype: bool
6 M. N9 D8 D& ?. O: o3 H* {1 E2 _6 X- }% ^6 X$ i3 {/ E: a b
s.astype('string') == 'a'
+ J5 f& z1 N3 g0 Y# kOut[25]: $ |8 ]( p2 d5 t
0 True
' A6 k4 N3 d* x+ C: C1 <NA>
' h1 S: D% i1 ?" i% v' s8 r9 \dtype: boolean/ w9 e' m/ q# v! j r: \
8 M9 [9 F( Y- {" j* ?1 ~' S ^1
7 f+ P6 M1 D# F7 I2: l7 Z% l$ M7 V0 e4 e
37 H: |3 O: {0 F
4: ~! f! b( a6 a& V$ @, b4 r5 O
5
% V( f0 g9 b; A& F3 \, \. G4 v6
: Q2 D$ W9 p* E J( c2 Y* n, i7( p3 j% z- e1 F Q3 J" T' g( N
8# ^( Q' C8 S! Y& k- {8 w8 s* `* c
9
q7 ]% @9 D- V# F( ]10
: j6 z7 [9 B7 y# }; u! O/ P9 ~8 J11" O& R* X8 r! Q4 G5 W5 {
12
% `( K3 L) }% l13
S8 ~$ F8 z% v" W) q14
' ^* z* @' Q l. D155 L( w' ]8 P0 [; _& {3 b
16
# c& @! s6 N& N$ r7 `% A8 _17
2 K2 W, E# N; Q- S' ^8 o2 ]18% g$ v, e( O$ j7 i& o2 G6 {
19
, [$ e8 W& o* d& q203 o3 n% C1 I% b+ M' F3 g3 h7 K
21* [6 W, T6 |( Z# F- Q i U6 B
222 ] T' }8 {( ]. s! F( X3 t
23
( w0 x4 C% o* m3 N6 |24
7 c* V: ]# A2 Z3 x25
& \. k6 [2 p* A5 ~: i. v0 ]; ]26# I" ]) j3 L% t5 k; A
27: o! g% t \* X# d+ U
28
0 q; z- Q! c; w29
' \6 v; p7 C! {8 }- N303 n# }, h; C; P2 d, w0 _$ M
317 e: C; A4 R" ]$ E4 k/ R% u
32
$ k+ @/ f- j0 [ k u* E33
! K& l5 i. i7 b( ~' K4 \34
6 ]- k O6 `2 ^35
o M. C' ^( n* S* E5 H+ A36$ g3 F. e' ? r" i% B
37
7 U# E: J4 U; T" \38
. L% |4 L. H& ]% g2 x39
9 G( W/ Z0 z* o# a" o6 j' Y" @1 C3 r402 x% v1 z( q8 m* ?
41
. s A8 s9 y" k( U6 X# O423 E: t2 B1 k6 d& W2 k; q* `( A
43: i$ i0 n! `% Y5 n) p
44- W6 t: f/ t! ^/ n% i5 C
45
' \ U# B( j: r# d# J465 q8 O+ @( c2 y) G( d& l) |( ~
477 I) J8 K: g; A' }/ T! H2 V
对于全体元素为数值类型的序列,即使其类型为 object 或者 category 也不允许直接使用 str 属性。如果需要把数字当成 string 类型处理,可以使用 astype 强制转换为 string 类型的 Series :1 Y; [6 u2 b h8 B& e O# Z
) a6 o1 W/ t1 V$ T7 M. s" As = pd.Series([12, 345, 6789])
/ @0 Y- G" y7 k) G9 o5 t/ i, E8 G9 u2 T
s.astype('string').str[1]/ C& q1 q! G. e6 N, Y! _( q# z
Out[27]:
; l; {. ]8 Z/ W+ b0 2
9 V/ V4 F/ T. G. c3 `1 4; L5 B K' Q% Q9 e* d0 B
2 7- D+ i% S9 Z: t) Y$ Q+ _$ H. [$ s
dtype: string( w( q" r8 @$ F- ^7 a0 c
1
' r. w. D8 Q! d" c7 ?2& ?( j% ?! S. @7 _* i
3% W" W: u- P2 h5 r
4/ w+ a/ b! K8 m6 Q
5: V+ U' z. s6 @4 S' r9 }4 b" ` j
6/ G1 L; {! H# I1 d2 d0 e r) q
79 ]9 q, V9 U1 F/ x* L5 h- k' k
8
% o7 _: @$ G8 d2 V8.2 正则表达式基础' f* k: @8 _/ h$ L; \ u- u
这一节的两个表格来自于 learn-regex-zh 这个关于正则表达式项目,其使用 MIT 开源许可协议。这里只是介绍正则表达式的基本用法,需要系统学习的读者可参考《Python3 正则表达式》,或者《 正则表达式必知必会 》这本书/ h7 E$ E& \ X& ]
! U$ j7 Q- ]3 T" U8 {8.2.1 . 一般字符的匹配
/ X% K# [ x) Z7 v3 i正则表达式是一种按照某种正则模式,从左到右匹配字符串中内容的一种工具。对于一般的字符而言,它可以找到其所在的位置,这里为了演示便利,使用了 python 中 re 模块的 findall 函数来匹配所有出现过但不重叠的模式,第一个参数是正则表达式,第二个参数是待匹配的字符串。例如,在下面的字符串中找出 apple :: Q4 m6 r2 }9 [$ R6 Z: ?) G3 R
( T% U. t: x# G a
import re
) D5 b( G$ T Y/ I
. J( P% R! [ q$ `% u5 bre.findall(r'Apple', 'Apple! This Is an Apple!') # 字符串从左到右依次匹配, v- W7 {6 o% D7 r( W5 @: A
Out[29]: ['Apple', 'Apple']" G1 b% E/ u( c' z2 I2 v& F( d( a
1
: w$ S7 o$ Q4 T# f$ G2
+ \- i3 V. a( @; Y# E7 D( b' w3
. F* s% t* O6 g4 B5 e9 U, w4( a# e9 R5 Y9 O; n' N* S
8.2.2 元字符基础
; E/ a2 _8 c2 k$ N4 ?5 [- }: c/ `元字符 描述+ a/ g9 T5 x9 k
. 匹配除换行符以外的任意字符0 z; E$ M7 H$ |# v
[ ] 字符类,匹配方括号中包含的任意字符; e7 \# `5 [, V f6 f3 d) d% P
[^ ] 否定字符类,匹配方括号中不包含的任意字符( p5 g; s' ], v. C9 g2 p: ^
* 匹配前面的子表达式零次或多次$ D# B: y7 Q( l `% \
+ 匹配前面的子表达式一次或多次。比如r’d+'就是匹配数字串,r’d’就是匹配单个数字
3 W' v; U; Z% l% y? 匹配前面的子表达式零次或一次,非贪婪方式
- P. Q+ f# G9 e' |: h{n,m} 花括号,匹配 n 到 m 次由前面的正则表达式定义的片段,贪婪方式& q$ O$ f9 T7 @$ F E8 N1 x* G( h
(xyz) 字符组,按照确切的顺序匹配字符xyz1 ?3 j' J& \$ K4 n/ U4 x
| 分支结构,匹配符号之前的字符或后面的字符
4 U+ B" ], v8 e4 l\ 转义符,它可以还原元字符原来的含义
# |- R. u; l+ I1 s, {( k^ 匹配行的开始
1 W9 e2 E, o& d5 f7 v$ 匹配行的结束
# v3 o I+ Z0 h$ M5 Aimport re
# O6 P0 f( ~# C- D1 r- R8 Ire.findall(r'.', 'abc')
* @' Q$ C: I/ x4 ~0 D0 }Out[30]: ['a', 'b', 'c']( }/ t, b* G/ _* W3 h
* ^3 ?/ k3 Z! J7 I: D: v1 Y8 h
re.findall(r'[ac]', 'abc') # []中有的子串都匹配4 K+ k7 g$ G0 k+ R1 W
Out[31]: ['a', 'c']" w- C- j) @/ L6 a' W' m4 d9 |
& E7 P4 n$ V8 s, e& P9 b
re.findall(r'[^ac]', 'abc')
7 b) o& M. h, i4 D. }Out[32]: ['b']$ l3 w! A" I) F! B U
- s6 o8 \. k( J+ U1 ~2 i, r Hre.findall(r'[ab]{2}', 'aaaabbbb') # {n}指匹配n次2 Q- P9 n3 d& I" t
Out[33]: ['aa', 'aa', 'bb', 'bb']
1 L5 m5 b; N( t/ a6 L
, O: E) L/ _, ~9 i \$ `re.findall(r'aaa|bbc|ca', 'aacabbcbbc') # 匹配前面的或者后面的字符串
& s" Z: e" y: v- QOut[34]: ['ca', 'bbc', 'bbc']
# A! f7 b4 G" k( ?- l* Y+ O# ^2 T+ E1 D/ E
# 上面的元字符都有特殊含义,要匹配其本来的意思就得用\进行转义。3 V9 H; q( Z- R. F" l0 F! }
"""
4 k0 ~4 ~- p$ \1 R1. ?匹配的是前一个字符,即被转义的\,所以|前面的内容就是匹配a\或者a,但是结果里面没有a\,相当于只能匹配a。0 P9 j0 g4 u4 t
2. |右边是a\*,转义之后匹配a*,对于竖线而言左边优先级高于右边* w9 u3 @; P! u* |; |4 W& i
3. 然后看目标字符串aa?a*a,第一个a匹配左边,第二个a匹配左边,第三个a虽然后面有*,
! @3 u2 n' I+ w/ A2 h但是左边优先级高, 还是匹配左边,剩下一个a还是左边,所以结果是四个a
. u; a% E* f. r0 g& c& A5 ~" i1 M"""
& d/ L- b* f p8 h. ?, B- R+ H/ u2 f
re.findall(r'a\\?|a\*', 'aa?a*a') # 第二次先匹配到a,就不会匹配a?。a*同理。
: m8 D5 [$ V5 XOut[35]: ['a', 'a', 'a', 'a']
0 u% k/ D& o9 w* a. o4 Q, v R
5 E! }+ B" x5 m' ~# 这里匹配不到是因为目标串'aa\a*a'中,\a是python的转义字符(\a\b\t\n等),所以匹配不到。
3 a! z# L' d6 {) n6 l# 如果是'aa\s*a'之内非python的转义字符,或者'aa\\s*a',或者r'aa\\s*a'就可以匹配到\字符。
1 _8 g" r: {" a) _% |. ]8 K" Cre.findall(r'\\', 'aa\a*a') + ?" P$ O% W+ I. A0 r" X) }& K
[]( O; K3 N6 h& ~/ X+ L
/ M- A7 {( M- t2 q/ i
re.findall(r'a?.', 'abaacadaae')) ~! z4 o8 ]) D* f( L5 J: y0 z
Out[36]: ['ab', 'aa', 'c', 'ad', 'aa', 'e']8 r% v: K" T- a* b3 [
) u( g. \, V& E, }5 F6 Wre.findall(r'(\w+)=(\d+)', 'set width=20 and height=10') # 多个匹配模式,返回元组列表
3 x+ u% T+ t+ h& C/ z1 t5 f+ ~[('width', '20'), ('height', '10')]/ l0 h& L/ Z; T( V$ d
7 O: R( }5 T) r* a9 t( q Q8 f# l
14 r1 q, R: |& ?, a9 C
2
: u3 k4 }! ~% o; V' q3 b# o3
# w$ l+ T( p4 |- q, `# M4
0 m/ w5 x1 u: ]* Z2 R- |5
: A8 M% d" y$ H! N6 {6
! j' n+ _; C, X& g1 H7
' b- P0 T9 ]5 W- w( i8
" G% o( ^% s c4 a, G* x5 c- ~9
+ e' [( A/ i x/ {) r, ]2 i& R' Q10
0 s0 q$ a# e1 X11
) L9 g+ n' x9 U; e7 g6 L12
# P% O( _* `" O6 G13- M6 y/ d) p; n1 \9 }% E* q& x) h4 d
14
1 R; G: O# _( x# m5 d3 b15
! i/ [' I: H( Q* Y/ z) H% M167 K; ]& p( Y+ g7 H( r( B( r$ d1 [4 f; @
17
* y4 A" h: [* _: C( H6 l18
; t- q' b5 `; [! V/ q7 W4 _19
1 J( S) r( E* _200 ?+ N! c9 w' q6 z) k1 f
217 K( a: K$ N0 K6 Y5 b
22
! M4 [! V; r! G8 D- q23
. _, v$ P0 @5 h7 O% g. V' X, ?24
/ B- V7 W# u, F: K8 m- y3 O25/ l4 d3 e1 [& H$ u
26" m, i3 W9 T u! E; B
27# V. Y3 B8 Z& v9 w* o
28
! C; _( C; [. I M29
7 K$ n" u: P- g# w" F; Z- ~30) l: d* p/ C8 w9 Q6 D s' N
31
. b C. S" x* S( d! X$ | N32
) t# F' \# p W! u( o T: U33) } y7 d H, {0 _& I
34' S: u- C. H/ v+ h! ? A
35+ v0 m! j* {1 A, d* ~& a
36
0 L' }- F0 h: [9 L37, p6 [! S2 r8 |
8.2.3 简写字符集
( }3 G5 K) t% B/ U/ s2 H则表达式中还有一类简写字符集,其等价于一组字符的集合:
3 W4 E6 ^7 A; y3 p. Y
/ z% M& |$ m. W简写 描述* Z' c, U( Z. i# i9 L$ p( A
\w 匹配所有字母、数字、下划线: [a-zA-Z0-9_]
5 c& o2 U9 W+ ]" J+ r& o) o1 X5 q7 Z\W 匹配非字母和数字的字符: [^\w]0 D% W/ P! N o) O) g: N9 w# r
\d 匹配数字: [0-9]
/ @% F) I3 r3 x/ G5 A\D 匹配非数字: [^\d]
/ E1 e/ r& h" r; V2 Y. y) p0 i\s 匹配空格符: [\t\n\f\r\p{Z}]4 `* T/ z4 P! F* j$ n
\S 匹配非空格符: [^\s]
9 I- D+ w% V% d; x7 W8 `6 `7 J\B 匹配非单词边界。‘er\B’ 能匹配 “verb” 中的 ‘er’,但不能匹配 “never” 中的 ‘er’。
% i0 V9 X; w+ f- J& y: V& ^8 }0 [. ?re.findall(r'.s', 'Apple! This Is an Apple!')0 O3 P" W5 I# d% G$ T
Out[37]: ['is', 'Is']
, ^# x& h7 E* c5 w5 r
8 o q" L3 z0 c+ S2 lre.findall(r'\w{2}', '09 8? 7w c_ 9q p@') # 匹配任意数字字母下划线的组合,但必须是两次
9 k* i0 `+ E: Y$ p5 ?7 rOut[38]: ['09', '7w', 'c_', '9q']7 J7 h! L0 ~$ _% o( G8 |# h/ h
0 k* Y- K, q. A& tre.findall(r'\w\W\B', '09 8? 7w c_ 9q p@') # 匹配的是两个字符串,前一个是任意数字字母下划线(\W),后一个不是(\W)
2 L( C1 s3 x6 y* rOut[39]: ['8?', 'p@'], H/ N4 X4 ^. C5 G; V) n
5 f3 x3 U, ?& j" Xre.findall(r'.\s.', 'Constant dropping wears the stone.')
5 T- T- _4 D" k$ w; lOut[40]: ['t d', 'g w', 's t', 'e s']. p# q# r" v2 [' `/ q
! W# s9 ^1 z# z5 b# A
re.findall(r'上海市(.{2,3}区)(.{2,3}路)(\d+号)',
6 V. A2 T7 e, _ '上海市黄浦区方浜中路249号 上海市宝山区密山路5号')% _8 P @3 L, x) G
$ ]7 {; d; b& ]Out[41]: [('黄浦区', '方浜中路', '249号'), ('宝山区', '密山路', '5号')]
0 s0 F& W$ }; M8 j9 t; h3 D1 w; b; c W* `) p
1
1 t/ b( r$ X; g ^% b) _2
$ I5 s! x$ D2 P3 [, o6 e3
) o7 R3 _& D: J" q/ s8 {4
& H0 p+ J) X8 X+ d5# H( c7 }" g# o
6' i6 i/ j8 |; P1 _4 _
7
! R5 X! q( y' ~# P, H/ z" h# c* k3 V85 G9 @2 R2 u4 v _$ q% X: ^
9/ E& d; A) {/ N$ f4 f
10
u0 }: C- F6 M8 W& ~* W11; |2 P& n i- i7 _
12/ o- W: V( e# b9 G
13
- }; G5 f; c/ t2 l14
3 L9 R; \% k$ w0 A/ y: q! i15
# T M1 I" i5 H3 N* x) Q/ H162 H( U+ b! ?/ a+ R
8.3 文本处理的五类操作; u/ {0 E2 x- s# \, k) t" d
8.3.1 str.split 拆分3 V) ?( C W; j9 u6 J" ?
str.split 能够把字符串的列进行拆分,其中第一个参数为正则表达式,可选参数包括从左到右的最大拆分次数 n ,是否展开为多个列 expand 。
5 _1 `; V/ L* T/ y' X* `( i# i+ U9 R* Q& h" S6 }& r4 f4 o% q
s = pd.Series(['上海市黄浦区方浜中路249号',# }6 D/ u# d5 X0 W" W7 Q8 A2 s
'上海市宝山区密山路5号'])
+ f, ]8 G0 c2 H5 b1 U9 d7 h3 d/ ^ @6 E: {+ ~- F9 d" [
" A+ y4 M6 x, L n& G: C0 D; L H
s.str.split('[市区路]') # 每条结果为一行,相当于Series: p3 w- k5 O- [/ X
Out[43]:
9 p# N( Y& m3 k0 [上海, 黄浦, 方浜中, 249号]6 ?9 j E* K6 k
1 [上海, 宝山, 密山, 5号]
6 y- o" v4 n6 J4 Wdtype: object& A, R7 p6 u3 B$ M7 q
- _3 G5 r. g! hs.str.split('[市区路]', n=2, expand=True) # 结果分成多个列展示,结果相当于DataFrame+ G/ l/ l2 h+ f5 v- l. [5 i
Out[44]: + P+ ^" ~. E, y
0 1 2
; L; y( \6 W8 _6 m3 v0 上海 黄浦 方浜中路249号* Q) O: a. I+ @) ]4 A S" ~
1 上海 宝山 密山路5号
2 G4 D2 Q: B& S5 C4 A/ ^14 \& [) b; u( Q0 W# q5 i! E% |
2. |$ _ M2 @$ w' }- ^+ m
3" @3 S. R( s9 q0 o# x' T. ? L
4
8 A- v: G9 ^! b( U9 J! z5
& s! f l+ q; a5 Q, j2 w6
/ r9 ~& w/ Z7 N" s8 l6 ?/ M: P7# a, H: s1 ~" h+ X
8
7 q d! j6 v7 h8 d& t# V1 z9- D* D. d2 X+ S9 |
102 j- e" V. u7 c! J' Y4 }
116 L4 p( f2 A8 R( }% C
12
0 w, D* ?1 {% E- l/ K; }4 q7 v1 P13
+ E- g( W P/ p# X0 @ w8 d1 W14
0 F, c% U" [2 U# k" l15* i/ r3 w) A0 w
类似的函数是 str.rsplit ,其区别在于使用 n 参数的时候是从右到左限制最大拆分次数。但是当前版本下 rsplit 因为 bug 而无法使用正则表达式进行分割:
3 r C) {' O9 Y; }" D' v$ o; |4 I
* v0 r3 W U5 s9 J8 k% Z# Ls.str.rsplit('[市区路]', n=2, expand=True)
/ C! ^' H4 R+ u2 `) v% G5 }Out[45]:
$ j* o7 d) e& K0 C/ D, K 0( ~ T# t) r; i. X$ |- h7 `
0 上海市黄浦区方浜中路249号
% p0 K9 B. V9 I; t1 上海市宝山区密山路5号0 R: F' ] ?4 s6 b$ @8 V
10 h& j; H/ I5 ^" t2 m+ ]
2
, C) k9 i1 X0 `$ C- w7 O# G3
" r8 @# l; s0 @ H8 p6 t4
* \1 z! O" h c2 o4 v5: p; r! X5 E! v4 D& O
8.3.2 str.join 或 str.cat 合并! t8 U3 o$ t2 ^6 t" c; g/ T: b3 `
str.join 表示用某个连接符把 Series 中的字符串列表连接起来,如果列表中出现了非字符串元素则返回缺失值。
, q) q7 O0 n6 y* ~7 j. ystr.cat 用于合并两个序列,主要参数为:
7 P. k K: r+ M" T* s$ q( @sep:连接符、
9 T: ~8 Y3 n5 ~- @4 _join:连接形式默认为以索引为键的左连接
! j" g/ A3 w, ]1 B& ona_rep:缺失值替代符号/ i+ G# A) V6 y2 m
s = pd.Series([['a','b'], [1, 'a'], [['a', 'b'], 'c']])/ T# f. Q; c7 A1 \
s.str.join('-')
$ D0 K3 D I- gOut[47]: / {% |+ q/ ^) b0 m. l
0 a-b9 V6 @8 h2 ^. t/ R
1 NaN' ]; L0 Y: R! _$ W6 g
2 NaN/ b6 S$ Y) u' c! v; Z& A
dtype: object
* k4 s' N8 `5 m3 y; c6 v c1! z* I3 l! ^2 f* d1 {
2' K: y) _, f/ T$ O; P/ c" x
38 Y5 Z. C9 T$ h( q I& U
4; L0 E/ ]3 f" {
5* Y8 I7 ?, }& o/ h; H' J2 n
6% {- x$ g- B& M E* X
7; N8 i- ]4 G3 h0 s: a. W2 x- {
s1 = pd.Series(['a','b'])7 y) N# W& Q2 |8 ]. x/ \, _
s2 = pd.Series(['cat','dog'])' w) d; e2 ?4 J% Z, E3 @9 g# I
s1.str.cat(s2,sep='-')
3 n* b' M9 f' x' _0 o# A k/ ZOut[50]:
/ n2 z5 j" N0 t8 `0 g0 G, P0 a-cat1 h1 O7 } M$ c: q0 B; O
1 b-dog8 U0 j" g. @- B T, ~1 Q
dtype: object" a. z: ^4 R6 O/ k' e6 R
& s$ M7 V" n/ }8 F* |$ h2 n* P9 ]s2.index = [1, 2]
1 g' h6 u7 z/ ?2 b4 |- a Os1.str.cat(s2, sep='-', na_rep='?', join='outer'). r" V' M8 ~: A$ x, u
Out[52]: 1 X. p; h( B; ^ z; Y
0 a-?
3 I6 X+ u* m1 w1 b-cat
3 p0 @9 ?7 _# H; ^* t2 ?-dog
- l. d7 N$ k" jdtype: object
; S! |* {8 u; x3 K/ Z1* y: h: d; ~6 E! _# c- m
2
5 l" p( M8 D* N2 L1 D R39 A( G& i# q3 Q# Y9 W" Q+ |
4* O7 I0 \& s3 }6 {' l# k" `
5( e, F0 L' U* u( S6 G
6) Q* ^* f+ _/ Z" T! I1 h2 `. j
7
5 x: W6 a0 _, j; J8
0 g* E8 z9 s6 t( \8 ~7 D1 J& i z9
' f: v: N# z; a' s; _) ~' n5 B10! l! e! R7 Q h, M
11
} C: C( `/ U, _) a: h12' W6 w5 `; I7 I4 C" P' @/ J
13
- R7 m$ Y9 m+ {. |) G14, |7 z( l# T* n, n/ p! I8 W
15/ k- }! f" M, f2 x" L
8.3.3 匹配% ?! S; ~% e; b6 ]" D N, `
str.contains返回了每个字符串是否包含正则模式的布尔序列:! K! }: ?* p0 I1 r7 U: J4 d
s = pd.Series(['my cat', 'he is fat', 'railway station'])/ |7 Q! G- ]8 m8 t7 N( @7 G% i
s.str.contains('\s\wat')' V% a7 O9 a n" ?; e
- d/ X+ V7 ]( b8 G6 n+ f
0 True
I. A. h6 B. y0 t d! k9 \% u1 True! D; f7 f7 e6 H9 L* J& p, T
2 False- l; V) i$ ]* P- [
dtype: bool
: H& w0 [* b! A9 o6 y. W1 {4 g! Y1
: Z7 G2 M0 c, {. L8 ^2+ u, l8 n1 r1 r) C' m
3( c* ~& w2 ~" V& K
4
+ Q3 {/ y9 U5 z1 ]5 e, \0 |5
& W Z4 \/ K3 P7 t& Y: R6
# P' B) g2 y8 a' I. `+ ?7* {/ Q- n2 g- }& I" k4 P1 C
str.startswith和str.endswith返回了每个字符串以给定模式为开始和结束的布尔序列,它们都不支持正则表达式:
6 d/ y: z: M. K& As.str.startswith('my')
5 f8 h7 _# |: B6 z% S7 j( r' B; P. }! d' E& R0 L
0 True0 B; _! A( B$ E" K- L$ N
1 False# j3 {2 p6 |, e
2 False, H+ o- {7 r3 d, _- ?1 ~5 p, E
dtype: bool9 c7 C' c9 A5 q* ~6 r
12 X1 s4 ^9 B ]3 S g7 K
2# e3 Y$ O& `4 e0 z) a
37 G& `, v V3 a+ B5 [9 y
4& r+ S1 e% n/ b+ Q- L
5* k/ A B) O2 X) Y
6. z" w& w4 Y ?' a9 v1 F; i! _2 a m
s.str.endswith('t')
8 T4 [% Z0 j- F3 d! V+ t$ J1 q% q# [: s7 }1 [) y! ]
0 True
8 ?' @; W# o; C* n) j- S% J1 True% Q' d: y! G1 o4 B! N% }
2 False$ T4 S/ v) l; }; q% |- n
dtype: bool
4 X0 k. ^$ k# W( K6 [, Y1
! n. Y0 u5 ~. u2
0 \# C! S9 r7 f% E; |' s35 t4 ~: H. H) a3 w1 e3 E, G/ _
45 m# g* j. o" \# I% F8 _
5
4 v5 h' k7 n, f! i6. r0 s9 s1 M! }! w1 L+ D0 H
str.match可以用正则表达式来检测开始或结束字符串的模式,其返回了每个字符串起始处是否符合给定正则模式的布尔序列。当然,这些也能通过在str.contains的正则中使用^和$来实现。(貌似没有python里的search方法): r. g( T$ M" \! |
s.str.match('m|h')! B: X2 f- B! a- G! o! X
s.str.contains('^[m|h]') # 二者等价$ N4 y6 l& F. w0 k9 A4 o. [' J
( h% c* q2 }4 J# m3 F0 U5 t0 True
2 S( W/ u* v# X& [1 True
3 [/ ?: H* M) L0 s2 False% v4 [% W8 W! b; M( j
dtype: bool
3 Q( c; K1 @ A7 f5 E1
, E8 @' v& Y6 h0 ~1 M2% a* V: o' W: y# T
3
, J3 Z6 e4 H8 f4, k! e6 m3 y3 w2 e! \& w# z P
53 ? j8 l& ?3 ~8 P! P0 J8 H
6
" L7 Y2 P9 o$ U: b+ u79 u/ B/ g5 s- V" C
s.str[::-1].str.match('ta[f|g]|n') # 反转后匹配: |0 u' B3 V' \
s.str.contains('[f|g]at|n$') # 二者等价$ _% u2 }: r4 q( H0 Z
0 L) l: F7 d' z5 f0 False
" b. R" }) u: Y8 ~1 True
$ c4 x3 k }3 z R5 X% w2 True* K( T1 |! r$ r- w7 ?
dtype: bool0 m- H& U9 J- N. `
1
$ ~0 \7 A# x! Q) S9 l; C6 M24 h0 R) O& m3 t/ s& p: `9 p
3, _9 t+ G1 a2 S T
4
' t2 Y6 `; R6 ]- e5% o, F! N: ]! \( V; Y6 z
6
0 V* B1 u$ e y* C$ I7 t7
- h0 I! `7 T3 Z2 B fstr.find与str.rfind返回索引的匹配函数,其分别返回从左到右和从右到左第一次匹配的位置的索引,未找到则返回-1。需要注意的是这两个函数不支持正则匹配,只能用于字符子串的匹配:
4 d( R* V) H; k, H: l- ~/ Ys = pd.Series(['This is an apple. That is not an apple.'])) W/ f4 E% W4 e* y
# a6 ]6 |! M$ R- I, Q$ w7 Ms.str.find('apple'): s* K5 e+ f( _; \% g8 K: q* r
Out[62]:
3 g& ^% _) Y7 ]: s! J8 m1 g- t0 11
0 w; P' o. @# k) G/ x0 [0 ^( g5 l$ Vdtype: int64
I" r# D& i) C
f6 i2 A( \# K" F9 t" E7 Ms.str.rfind('apple')" i" h5 O5 C# J2 O$ {2 _
Out[63]: & {$ q2 l/ S0 c7 R9 j' P
0 33' X1 N3 Y* n: j* c6 y1 T+ G
dtype: int64
. J! d4 p1 A) |+ B5 ]1
6 Y0 r; ~" q9 @$ b26 x7 g L1 [! z% Q' I0 r
3
. X6 {1 @6 n# S1 e( m$ G! \0 g$ v4 [4 R0 G5 s ? a( L( D! m/ S
5
* |5 U8 U V# z6
- f4 c) i+ }/ S! `7. ^! R) h* ~: x$ c/ W: C, B/ w9 q
8
: `9 N+ d: v5 {& _0 o9
e# W( |+ R2 M- r8 H- X# x& d0 b10: G2 M; V# W; |: D/ n
11. B# v8 o# w: c! ?
替换
$ x% v# f3 m8 K9 Lstr.replace和replace并不是一个函数,在使用字符串替换时应当使用前者。
& W/ r6 Q! D# c* ?* E1 T: }- k! _s = pd.Series(['a_1_b','c_?'])
% V* U& n) t5 F G- W# regex默认为True,表示是正则模式,否则第一个参数内容表示是单纯的字符串,也就是匹配字符串\d|\?6 v- l7 l8 Q/ K3 K+ [
s.str.replace('\d|\?', 'new', regex=True)
9 u" _# P P9 X+ K, u8 J* X( S" k3 n. A0 }0 W1 U# O) w
0 a_new_b
) R. }* C3 y2 U2 x* Y8 ?6 q1 c_new
+ C5 \+ {8 @6 D6 Z9 p: e, g! jdtype: object: S- I5 n3 ]) V$ D( Q# N% ^$ A
1
. X) C; p0 ?' Y# p3 g7 B2 F2
0 M' D0 s# | E8 V1 R ^: {9 E3
5 d2 L4 e% X3 q! Y4
2 g3 G( `3 k& Z8 k2 L5 P5- m8 w; S4 y' s$ b( S
6
- b2 c( n6 W4 o3 u78 Y7 T# a: N' l, b6 w
当需要对不同部分进行有差别的替换时,可以利用子组的方法,并且此时可以通过传入自定义的替换函数来分别进行处理,注意group(k)代表匹配到的第k个子组(圆括号之间的内容):0 k0 }' M+ g. W) n% G& a
- F$ A. B/ ]: x, `* M
s = pd.Series(['上海市黄浦区方浜中路249号',/ p: e9 ~' b4 Y& @" w
'上海市宝山区密山路5号',2 F) g. n ~4 i+ n
'北京市昌平区北农路2号']): _! J/ M' k) N' T# Y/ W) c( X0 O# V
pat = '(\w+市)(\w+区)(\w+路)(\d+号)'6 }: E5 |5 j& ~9 {( ?
city = {'上海市': 'Shanghai', '北京市': 'Beijing'}# ? g2 X* _7 d1 k7 [. S% ]7 L
district = {'昌平区': 'CP District',# D$ Q4 x0 L+ [* e" \4 b. y
'黄浦区': 'HP District',
3 V2 l1 U; H) i! h8 x1 @ '宝山区': 'BS District'}
3 P J) H. @$ \road = {'方浜中路': 'Mid Fangbin Road',
$ H3 i7 \8 B: d e" q' N2 i' O '密山路': 'Mishan Road',
8 }5 |8 z2 B1 [) N$ | '北农路': 'Beinong Road'}
) E6 n7 S" X2 v. q, }: xdef my_func(m):
7 c; Z( i$ T$ k( B% L; P1 `" H str_city = city[m.group(1)]
( C4 J9 h. |: x0 p: ` str_district = district[m.group(2)]# }8 ~3 I0 l7 }* x5 j* B
str_road = road[m.group(3)]8 s, Z, x: } g; _( `0 d- p& j6 k
str_no = 'No. ' + m.group(4)[:-1]
7 _( i! g1 H- X& M return ' '.join([str_city,
8 U, @7 x2 o" i; e! P" d- G str_district,7 l. u8 G( i8 l7 e# s/ |
str_road,3 h2 u: s. \9 [3 |2 x- y. J
str_no])$ n* S9 q: B' G7 I6 [' W6 d8 R
s.str.replace(pat, my_func, regex=True)
$ j2 Q+ W" b" K- j
* a9 Z9 x! t: L" O+ T" N12 k4 a- u$ A1 @. R8 q% s
29 e( d9 [) c, F# _4 B
3
) C( H9 H7 ^, W* @( t# M9 c4
. y6 E1 X9 Z, d! \) K, n4 q0 v5
, Q0 @+ v M) w- V- t8 k7 e6
* Q4 v5 I/ M6 M- J4 f) H74 k: w) x5 b6 {# J! C$ a
8) |: t3 }4 o' O, K, ?% ?4 `, s! [
9
* y; t. f0 e; J10
4 \$ v! L8 C# P9 a. M4 S11* b0 p9 g# F. M$ C8 m
12' L. m5 q2 e9 {" M
13" r. r, e# A# Y: J8 G+ ~4 Z" V
14
" E& T/ R- y* C" u4 U* k15
- o! v& Y! N9 c4 ~( J) w6 h16
/ }& \/ _+ W0 W17( A+ A0 F( Y Z+ R
18' `/ [6 s# B' ~) V7 g2 X" \
19
6 [% E, k6 _6 t% ?7 t! K/ w20
# L5 M( r) [( `7 s21
* |& e8 Q+ q2 b/ X0 Shanghai HP District Mid Fangbin Road No. 249* P9 O, Z4 i4 ?" `. K
1 Shanghai BS District Mishan Road No. 5$ U) h( Q4 P. X* ^
2 Beijing CP District Beinong Road No. 2+ |/ F3 I: j$ K+ Q! d
dtype: object' W2 a' n; X3 B# w- C0 b. v
13 J- l! {4 l6 {& f
22 G4 |9 c/ ?8 x' ^
33 |; \. D |7 s# }( e( n- e. s
4
% V7 v C; F! l5 @. M" K0 t3 h! B这里的数字标识并不直观,可以使用命名子组更加清晰地写出子组代表的含义:, |! Y* @7 R0 E6 h
( z) }7 d. J. H7 }! k7 ^- S# 将各个子组进行命名
( F9 ^! v6 w5 j9 f) _& E, rpat = '(?P<市名>\w+市)(?P<区名>\w+区)(?P<路名>\w+路)(?P<编号>\d+号)'' j8 V9 p+ X6 E7 ]. Y, ]0 K; C& u5 }
def my_func(m):& R* q$ `2 C6 E* F4 H; `
str_city = city[m.group('市名')]7 d$ T" K! B# E: ]
str_district = district[m.group('区名')]
* `! P# }/ f7 K, ^8 M) ~ str_road = road[m.group('路名')]
$ P0 p8 z; j3 m3 f str_no = 'No. ' + m.group('编号')[:-1]3 v/ b2 |1 J2 _8 ~, S l! \! {
return ' '.join([str_city,. [/ ~5 Z& g7 U4 @! J A
str_district,
& t) K3 `7 Q$ o str_road,$ Y. W* n8 ]1 e4 e9 t
str_no])
. r/ M$ T- E8 @7 e" J5 I& P0 O: u/ w& H6 os.str.replace(pat, my_func, regex=True)/ I) h @# \& g
1
" p# |+ H7 }3 {/ I5 l2/ s' G3 h" S( y4 r3 W" |# F1 W; C# j/ |$ j
3$ Z' ?; C, R' R% j! m3 t. ~+ L. m2 ~
4
6 Z" s% q. H" p5 w! d) l2 J5 Y5
. q' ?- `: w7 d5 V1 a7 _60 q& U2 |0 I1 C) S# n* I7 ] ~: X
78 M2 \1 t7 k, M% u
8/ p7 k2 t' i( [, R) G4 r
9
9 E1 L. B0 y5 S- I10
, r7 m+ B/ ~$ a( k5 W8 o7 l, z% R11
: j/ o: L4 y. e$ y' v: P8 S3 J121 N% M2 d% m( h$ A3 m
0 Shanghai HP District Mid Fangbin Road No. 249& l( g+ V! l" H) ]* |( Q$ W4 C8 b
1 Shanghai BS District Mishan Road No. 5 m4 k3 E F M' e
2 Beijing CP District Beinong Road No. 2
7 ~( W8 T! |! e' o5 l' Qdtype: object. i' F& |) e) f6 P
19 Z- i; O( w; I0 ?
2
( G$ ` m# g" S9 S l3
) M" i$ s1 r1 M+ p; c+ D48 [- v! [0 y; `1 K/ i
这里虽然看起来有些繁杂,但是实际数据处理中对应的替换,一般都会通过代码来获取数据从而构造字典映射,在具体写法上会简洁的多。
- V* h$ O3 u1 f0 `! K1 h* x, i* F+ V, e: `& ^3 a5 b8 {! Y
8.3.5 提取, m" v2 \+ v5 F; Y: D8 J* Z W
str.extract进行提取:提取既可以认为是一种返回具体元素值(而不是布尔值或元素对应的索引位置)的匹配操作,也可以认为是一种特殊的拆分操作。前面提到的str.split例子中会把分隔符去除,这并不是用户想要的效果,这时候就可以用str.extract进行提取:, D, b- l6 u3 U# a& X
s.str.split('[市区路]')3 n9 O" \' D4 ^) A2 Z3 w% O. E
Out[43]: + g) \0 q- |$ b8 k4 f
0 [上海, 黄浦, 方浜中, 249号]
& I! y8 ~5 n# e- o1 [上海, 宝山, 密山, 5号]
( H/ e* z- q7 ~) V- e. B: `3 }dtype: object
6 x3 f# G! ?% x: a) o2 [- q- `. U7 \) N, A
pat = '(\w+市)(\w+区)(\w+路)(\d+号)'
0 J& N# E+ O' v7 vs.str.extract(pat)0 j$ X) e& F2 x* @) [" y/ Q: L5 }) f
Out[78]:
- {) v/ O/ \9 C: ~6 B" T+ k# x2 g% U 0 1 2 3
3 r& X5 Z: L) U: ^0 上海市 黄浦区 方浜中路 249号
( l1 ?8 u" g# Z# i1 {2 _4 u1 上海市 宝山区 密山路 5号
( D- Q- |& |9 x+ T0 g2 北京市 昌平区 北农路 2号
- w8 a1 b* B% H4 p+ N1) Z+ D) G" b! W0 e
25 ~1 ^: v, Q4 T; x
3
k) b' k A5 v4, V6 K" J/ k7 U7 ]* v7 Y* p; q) a
5. E0 Z0 u. C$ Y G. C0 o
6
% G! A: }- E6 M: y- ?* r* j: }7 [( u- Z70 ]9 P! |+ }4 Z2 N6 U7 ~$ o
8
+ ^) T& S* E. V" W9
! t$ M4 l3 N3 H. R4 e8 K10
9 r, s% s+ {' k% A6 {/ Y1 u11
: M; w+ @, ^4 F12
# i1 D4 P k7 d/ e$ e1 Z13; Y4 P5 Q/ G1 ^5 P8 L! G
通过子组的命名,可以直接对新生成DataFrame的列命名:
; f8 `! y/ E1 Z( ^: H
T/ m: |2 A5 M+ upat = '(?P<市名>\w+市)(?P<区名>\w+区)(?P<路名>\w+路)(?P<编号>\d+号)'( b% J& g7 _! Q) }5 E
s.str.extract(pat) K3 k$ X1 ^8 m% @$ j6 P
Out[79]:
/ W8 @2 {8 x m3 X6 T 市名 区名 路名 编号7 U, @! q5 k X
0 上海市 黄浦区 方浜中路 249号
. G/ R4 u0 E t2 E6 z1 上海市 宝山区 密山路 5号
7 C& C1 Y% _/ Q- R2 北京市 昌平区 北农路 2号
, A+ N* Z8 @. s6 F s: a17 y2 [* u! j& k" `
2
, V( w* M* I$ M6 [ W2 V' |3- l/ x& T- A4 I! j3 f) I
49 }3 k' Q# H) ?( t7 s
5
- U( ^1 k3 r8 O* m6
+ `$ f" E5 i- g9 K# K- c* q73 b# g5 ]( [( n$ ]. G* ]
str.extractall:不同于str.extract只匹配一次,它会把所有符合条件的模式全部匹配出来,如果存在多个结果,则以多级索引的方式存储:2 z" |3 q4 w, ~1 z
s = pd.Series(['A135T15,A26S5','B674S2,B25T6'], index = ['my_A','my_B'])
' o; Y1 O3 p4 `pat = '[A|B](\d+)[T|S](\d+)'/ I! Q4 O! S" I4 h9 D4 k
s.str.extractall(pat)
" z* L ^6 |- I5 ?9 O9 tOut[83]:
: X7 [1 a7 X9 Q" G7 b 0 19 X# s+ W6 k' ~9 m
match " V: V) w& J2 b! J+ M
my_A 0 135 15
7 Z! K2 [; Q) V 1 26 5
( t* [ Z5 a" C6 n( ymy_B 0 674 2
4 @! l: v+ k* ~0 ~7 n$ l- u 1 25 6+ P, j/ Q8 J9 j0 z6 x: i$ e
1) N) ]) t8 P) P# x, x/ J3 k0 N" @
2
1 L |& i! |7 W* N3
- C0 M- r; ]+ ?. s/ F" g7 Q, c4
& O* P2 h2 d1 R- T; Z8 \; L5: F4 {+ M; s- Q+ W6 f+ y2 V0 {
64 p* ?9 V D. C$ j7 R
7
) E* [4 l0 z, k% Y9 b8 o8
0 N3 M+ o. n* y5 P4 @9/ o5 o; s4 B9 Q# i6 h& _% P# l( t
10& i! e1 }0 n3 t: y& O% `, R4 Y
pat_with_name = '[A|B](?P<name1>\d+)[T|S](?P<name2>\d+)'$ U, ~" H) I/ N0 w
s.str.extractall(pat_with_name)- I- E3 {; _ e# j
Out[84]: / r5 e; E a! A* v/ l$ |
name1 name27 n9 F% Q _5 ~3 p9 `2 G. B a
match 0 f* @9 @2 ?6 `8 `( z6 l& a
my_A 0 135 157 y( [' k( E1 H7 }+ _) J5 t! A
1 26 5
+ j8 v/ t7 n2 Z+ ]; q* Ymy_B 0 674 2
+ l+ s7 W& X/ f% g5 G 1 25 6
( U" ~: B6 [7 c- p" }" h6 U5 `1, j# `) U0 D) v& W9 K8 V) w% s, m& l
2
+ W2 R# i8 F, O3 _3! z2 R9 \) h$ Z+ N9 C3 x
4
" U1 w5 T& ^" T/ U! k( q58 E Q8 X) E0 M, v2 x4 a- y
6
" h4 @# Q9 J& V4 }7! o* B, A2 D" W
84 g' t/ K! J; x+ W+ v$ o
9* m6 Y8 e# s$ W: A
str.findall:功能类似于str.extractall,区别在于前者把结果存入列表中,而后者处理为多级索引,每个行只对应一组匹配,而不是把所有匹配组合构成列表。
. ^% P" D" V% xs.str.findall(pat)
& \. R# X$ y4 I9 ^# V) p1. g/ M% ]2 G. q0 _/ f! {
my_A [(135, 15), (26, 5)]
. ^2 U3 } {3 X! z: P7 b0 k1 M4 Imy_B [(674, 2), (25, 6)]
. h0 |7 `) |# Zdtype: object
Y/ X5 B4 f$ E3 u- d12 |$ b5 P$ H- N2 B7 A0 A# W+ @
2
4 F2 n8 G7 R9 o: ~- O3& t- W- y4 D+ g- V; t# G" [3 m6 L& g
8.4、常用字符串函数; J; t# p, x, v3 u* ^% t
除了上述介绍的五类字符串操作有关的函数之外,str对象上还定义了一些实用的其他方法,在此进行介绍。2 I$ F8 _$ L& D0 B$ p
5 B. k, O' H! l5 n# H
8.4.1 字母型函数
9 w9 X6 o. ?2 L `/ R upper, lower, title, capitalize, swapcase这五个函数主要用于字母的大小写转化,从下面的例子中就容易领会其功能:& y* a; i' z' R" ?2 p
6 w Y6 I$ E4 E) D2 A) t$ W; ms = pd.Series(['lower', 'CAPITALS', 'this is a sentence', 'SwApCaSe'])
$ _+ Q$ f# g3 D, z6 w
. N9 |8 s6 |3 ]. z# f2 ws.str.upper()% p9 C) Z* P% a, Z# \
Out[87]: ' Q: P0 _' X: ]7 @: `
0 LOWER. k* z& O, S1 A8 F! ~; D5 p/ n
1 CAPITALS
# C S% W+ E- A2 THIS IS A SENTENCE; r- i5 _! \2 }2 J2 n" R% O1 o
3 SWAPCASE
3 e8 K0 [: W) q( o6 {+ mdtype: object u3 e% [9 z8 l$ e3 a! @
4 J5 P" x/ K6 G: m2 v- b
s.str.lower()' D) ]; R5 C( D4 O J e7 ?
Out[88]: 1 _, F) x: \2 `% ]* B
0 lower6 I$ A9 b5 D3 i( L, {7 N* T
1 capitals) P0 C! {! v! {! j: U. G* R
2 this is a sentence' c8 _) p! ]1 O0 F: q( U! `8 h& z# b
3 swapcase& r. O2 d" g' E* O' u
dtype: object+ {1 ]+ y5 E/ x% x0 n* L7 F+ L
, @" ~0 k# B; X' J" B) g) ?5 [s.str.title() # 首字母大写
2 F! @# ~: s3 A4 vOut[89]: % i3 b5 D8 @- }0 T: D
0 Lower# t; J8 V9 {) O3 ^4 n$ n
1 Capitals
1 s9 z% t! S8 z2 This Is A Sentence0 _4 s8 @2 n& E7 o( r
3 Swapcase
1 c5 @) p3 E6 q& g2 rdtype: object
/ t4 |3 o( d' f/ }' @0 O! C2 I! c& U6 Q6 i
s.str.capitalize() # 句首大写4 f6 o {; r4 e- U( t
Out[90]: % u4 Y& a- b6 r
0 Lower& W! K$ }, ^0 c P( Y
1 Capitals
; h6 c" ]" E; l b' m2 This is a sentence
) F7 P; S' f* I0 c3 Swapcase
- G* Z2 K [; f+ q" pdtype: object% U! V1 f ^: {8 m& g
3 r" r5 t" O7 c K- K% p
s.str.swapcase() # 将大写转换为小写,将小写转换为大写。; J! \% L' o+ F% n
Out[91]:
% z P! c9 Y1 [* B; |0 LOWER7 @( ], s4 b% v: b5 H
1 capitals; `0 ]& j7 F: y7 x% Q: ]: Y6 N
2 THIS IS A SENTENCE
/ d' N4 K* v+ G2 v2 i" `3 sWaPcAsE, o; u3 Z: }. Q' i% R+ B X
dtype: object
9 i& r5 K/ P- b0 C7 {0 A1 s% G5 h' |$ I3 f
s.str.casefold() # 去除字符串中所有大小写区别
2 ^! f9 B5 C! H! ?4 |) Y9 A" [9 @$ X) X8 O9 {9 L; ~8 p7 z1 V
0 lower6 W/ [( Z3 Z- s- u) b& Y
1 capitals$ c+ E8 M0 o! X
2 this is a sentence
1 f M; J2 Q/ T6 a3 @2 k5 o' o3 swapcase
r. K! U& H" m2 n% T
9 }9 |! p& V( ^9 [( T" c* N1: m4 T# N% g# ?, `5 {9 s) H V: n u
2
+ k# ~, j$ C# a/ ~% C3( t0 l) I) Y' _6 U
4
8 s o8 d- [9 K, S h4 k5 \5
: L6 [( R" ~* v) _6) n/ _' w& [# s, X
7' ~9 B- ]- D d
8
% v/ \' M- }# Y4 \+ s9" W" g, F: o1 ]
10
r4 T! A A2 n$ |; o11
: R# V( k! l q& ?1 ]12
/ @: f% z5 K& T# l8 q8 K7 c. z13# b9 f: z) F1 S4 I6 _8 Z/ y
145 I, Q! F' c' S; l, A: Z) r
15' b5 w& e9 @: ^; Y3 k5 h; V# L
16, y4 x0 H+ H' j5 S9 A$ c% a
174 }" e% p2 D' z: d5 {% n3 a O
18
/ N: s r; ~, k/ G1 z" D19. N* H: e) m) `; |, C
208 D- M8 v+ G6 B0 v( n3 m4 B
21
* I6 X+ ~* A& e, ]1 ]" u22
5 g8 k% Q& b% f' A. U23* f4 I0 W; ?# J6 Z n, m) ~
24
# k0 N# F ]/ f" v259 ?5 c0 r. c! S6 j' V8 p& ^- Y: B
26
A( u! H) Z& O, U& M27. y& d' k: v# I/ ]5 @! L1 h4 B# V, c+ V
285 E# s2 Q, {( C$ j) E1 s
29
+ |' m( x6 ?1 F# |30
% ]8 h' H) V9 ?: q1 j31 g. p/ [ R j. S( x
32
( O8 v' M0 n/ F/ Z- M) G1 U+ R/ M% s33! x" T! y$ I7 y' y h
34; k9 b+ z* x5 e" B
35, a+ u6 D9 _ b, d0 v/ v: }* C3 @
36
7 B- Z( o7 {$ z ^* b5 N( @6 x0 N: q! z37; l$ k( ?- F) `0 }/ U- N! Z) b
38
6 R% b; J, W' b! Q: B* k395 y: t1 u3 M. ~0 f _
40" O6 ?6 u3 h( o9 l @# i
41$ X1 X4 s3 V: h4 h( y) e" ^) @
42
9 t/ k) \: b/ f2 t43
4 c4 Y9 M1 P7 u7 ^. l445 ?$ ~) w6 }$ U0 ?) A! ?
45
$ d2 r- C% X6 {# F0 m5 x% q8 \46
! L8 z' R* l7 C5 T$ h1 o47
2 i: ` C- }; {48
% v* ^9 v6 B# T! q8.4.2 数值型函数
" }0 `0 g! [4 i# C$ { 这里着重需要介绍的是pd.to_numeric方法,它虽然不是str对象上的方法,但是能够对字符格式的数值进行快速转换和筛选。其主要参数包括:
: ]( g, t# _# S" _/ T( @0 x
; U& D0 q& ~$ z# p& V- g; x8 herrors:非数值的处理模式。对于不能转换为数值的有三种errors选项:. ~8 q/ Y2 I* A, M2 _
raise:直接报错,默认选项
$ k) @# O! L. s# g5 ycoerce:设为缺失值+ W( d7 ~5 A* a0 i
ignore:保持原来的字符串。
- k$ b! o$ V0 \' m# W1 I3 Wdowncast:转换类型,转成 ‘integer’, ‘signed’, ‘unsigned’, 或 ‘float’的最小dtype。比如可以转成float32就不会转成float64。6 L% ~( Z( s! v* x$ @
s = pd.Series(['1', '2.2', '2e', '??', '-2.1', '0'])
9 p/ X6 J! L% ^) z$ }5 a8 k( b% I& O8 L* {6 i v
pd.to_numeric(s, errors='ignore')4 A8 T* v/ Z+ e4 d$ Y
Out[93]: 1 u0 {; X; b+ c+ L6 H; i `) F
0 1
1 `1 x+ g- p& v) m0 Z5 w1 2.20 B" E x8 h7 D8 O, V
2 2e
0 a8 B; @$ q/ y& H. b% {3 ??" k2 n* v6 H! x2 v q3 ~
4 -2.1
; k; @ c1 v% v; \& a1 Z) u5 0
" m/ ~4 x" S& M0 H& [8 R B* Odtype: object3 U" G; a5 L6 T- y& A+ ^
5 ?1 |/ v% }7 E. ypd.to_numeric(s, errors='coerce')
/ \) _4 f1 W$ v2 W, Z9 V1 V3 YOut[94]:
" F) j- ^( x0 G* D" S0 1.0
2 u" B2 B$ M9 L8 {$ O5 ~* p$ c1 2.2
3 ~/ L( J. i; u1 A& N- q8 I2 NaN7 `, }" j4 y, u, F1 C6 w) [
3 NaN" Z" V# X3 C. ^' n
4 -2.1- w* P/ a6 X5 \% y
5 0.06 [( @$ v9 p2 h! f& |
dtype: float64
! Z7 m" \0 P# [% b/ R% B9 S, U$ @( X+ a5 o. E" ?$ i2 S! _3 t M8 |& u
13 q" `. y6 j( K t$ l
2* r8 N+ i6 | a# U# Q
3
$ M7 G! w u8 ?' u- t) @2 ?- Q4
; ?; l' A( Q+ i- p5" J, j+ r9 t' f5 r+ v1 `- P& O V( m
6
) B' W; n X0 ^7
/ L" g/ S, m: T- B, Z3 e/ N6 l8
1 w& K4 p$ c2 G, N% h0 R; j6 D9& }4 B6 b% \ n9 M- r; ]" W6 H0 j
101 i$ X' S2 L. q+ w4 r1 E
11
! N2 t& J% Z$ x" u12+ s f1 e1 m# ?( N# L6 `
13
2 g) R- _4 t, y: N5 m% R& F! n14
! Q4 X" \4 z M$ @15
4 Z, y8 B2 y# U* Y16/ B% I* |& p' c" y" y& Z% o+ r0 o
17
0 L6 G7 q8 `7 a" x; y18
+ X* R5 R, O- [19
5 Z" f3 I3 c, {. `20
% _1 r% T. s" o* Z R% p21
# @% Z" B- ~( m% |5 F5 _6 d 在数据清洗时,可以利用coerce的设定,快速查看非数值型的行:
; X! t) q5 z. l( {
9 J" a: }) f! U0 J Zs[pd.to_numeric(s, errors='coerce').isna()]* S7 o; e6 z! @$ I1 o
Out[95]: . v& M' i* p- h9 ^+ Z6 C1 a3 {
2 2e% t0 S6 v( q4 B( m+ @+ h6 \/ N) b
3 ??
F% a- }& J4 {( q; ddtype: object
$ d8 j8 R+ c L( v1
0 {1 h1 u! { T* V; E+ a2
$ ^4 a w" V" Q, |& _3
& N. R9 M- L8 q$ I9 \7 w( |40 u: q! U9 i, I( h- o
5
% R0 x2 Q/ _$ {) F2 t \8.4.3 统计型函数/ y+ X3 o% R8 A# e
count和len的作用分别是返回出现正则模式的次数和字符串的长度:
6 D3 q: l9 y1 k# t' v! J. ?& a7 n- b) q
s = pd.Series(['cat rat fat at', 'get feed sheet heat'])9 Y& d1 W1 S1 e( r7 Y" L+ _: C
! N; x% D+ a3 X, ~- J, A! @( U$ M
s.str.count('[r|f]at|ee') # |左右两种子串都匹配了两次
9 c/ d& v0 ?! Y5 {1 |Out[97]:
* c3 c8 |5 A- C0 2
! |- V3 ~3 w: I+ t7 _% |1 2
* f) B! {, [3 P, |$ f! sdtype: int64
. e) h0 W: b! q# G7 t( Y3 X& h! s4 s: k3 F# P* v: n- P" ]( v
s.str.len()2 a/ U8 e* O3 x% I
Out[98]: % G+ y5 H1 l8 c |- S, _: V
0 143 C. H# }( u- e6 N. n! a2 ^
1 19
# z e9 ^* I+ M udtype: int641 [9 s2 ?# n: w7 G; a( E
1
! ]+ ]7 ^# ~$ @. g3 Q( n27 S' ?, c) E N) {. b0 \7 z
3
5 g$ n7 o8 C. O8 n46 S" X& \$ h. z. C# U5 C+ g- Y
5
7 C" K3 e5 x+ ~7 h! z! D2 b) d6$ I! |5 i _, S6 h2 E0 H
7
- }. m7 W/ g$ w* Q+ s& S ~8
) \( T7 F7 i. |1 a93 h. q: k2 B4 F; ]) ~$ H
10
. Q. g, i Y% j- ?; n: p8 m11) o2 e- T2 @) j) R6 G6 l, s5 D" ?, r' f9 {
12
3 L5 @( \9 S. K8 H% Q13
. U) B" }. i- z0 x s: k; M8.4.4 格式型函数 q; V$ R( p4 c% ~0 I2 N8 F
格式型函数主要分为两类,第一种是除空型,第二种是填充型。其中,第一类函数一共有三种,它们分别是strip, rstrip, lstrip,分别代表去除两侧空格、右侧空格和左侧空格。这些函数在数据清洗时是有用的,特别是列名含有非法空格的时候。5 s! V. t" T" j% u* w
& {$ q4 ^; [0 G! Q8 _) W/ A5 n/ p
my_index = pd.Index([' col1', 'col2 ', ' col3 '])
+ U4 ?- ?2 V& a; C7 t
& A" S) Y$ z" fmy_index.str.strip().str.len()
: j$ d9 h" Z( S/ ~; ?Out[100]: Int64Index([4, 4, 4], dtype='int64')! z4 x, _! ^& s! z" x
1 ?$ e! o& N# s0 f& w" R* y! s$ v
my_index.str.rstrip().str.len()
2 ?, Q/ j m5 f. UOut[101]: Int64Index([5, 4, 5], dtype='int64')
A2 i1 s2 ?0 P- d! B& `: M/ s( G* U9 A* _+ @0 C2 z1 u
my_index.str.lstrip().str.len()
# |5 w9 U% `/ o2 _Out[102]: Int64Index([4, 5, 5], dtype='int64')" L% }) P+ c: N0 H5 ]5 U
19 W, K/ [* q7 C! u* i3 o6 ?
2
* T( C' ^* c6 X3
; j- g, e& p* t! F* X! V4
5 G6 n6 Z! e9 u5
& z1 U! t: y) F: {' H# L6
# C- k& l z! |72 v- ?5 H) p6 X& u+ ?% S, G
8# _6 v7 N5 B. j, E& v
99 X8 g# n6 z/ c" K& \/ o, s; W. ~
10$ O& V, D0 t6 w6 ^6 D. ]# J
对于填充型函数而言,pad是最灵活的,它可以选定字符串长度、填充的方向和填充内容:% d4 p" a: b+ b; z
. ~: a2 N& a6 U( s; a* [s = pd.Series(['a','b','c'])0 S$ X' L& {+ d/ q7 j7 E
: p$ W9 I9 T0 N0 T5 rs.str.pad(5,'left','*')3 _9 O; k+ B9 A
Out[104]:
3 N+ Y- \4 w/ y) C3 J" P# Y7 `( q, u0 ****a0 ?' w g& \ ]3 V, p: ^$ B
1 ****b
" ] C/ a* ^- J, Q: y8 m2 ****c
' ]; c6 A9 e4 `9 pdtype: object
4 A" B8 Q! @: x: r" u
+ a Y) _" K. ]0 Js.str.pad(5,'right','*')& [. n( j! h1 d6 Z+ r p, e
Out[105]: ! f/ L% R8 L2 o3 w3 g; {
0 a****
: v7 N' g: n9 G( y) A1 b****1 c$ }4 H3 n- _4 m, c2 S" C
2 c****
6 P! x! B" h% `9 t5 i9 Ldtype: object
: ~& S7 {2 W& S' g4 ?5 d7 L" F
: j! u8 D2 V k, o/ ls.str.pad(5,'both','*')
* \' @+ E! X. l% @7 [Out[106]: 7 x. B) D$ q1 ?, t) P) a5 O* {8 N" d
0 **a**1 W6 k+ C% ]9 a1 h5 |
1 **b**
" V) l1 M, h G2 **c**5 ~( ^9 y- I: e
dtype: object
* [& H* E2 q- Z6 [' s% V" J* x0 m) l7 j1 T% ~7 L; f
15 x5 R# y4 _7 \" o
2! |- F. I2 I. J! ]
3) A( g1 G; [+ X: k0 B
4
5 b5 Z0 g5 ]9 S. R$ ?! h# ~5
7 z+ v( t6 a/ N [6" G7 t# T8 S) Z3 p, n
7% \- u. j9 s% {% K9 K ^7 ^
8$ g# Q4 V4 T$ i" g, g$ P ~) h
9
1 s0 }' g% a9 X# \8 z9 P10# z1 O% X s# U* d0 c
110 F( M7 }2 y4 t1 \
12* _) H/ B! c4 Q+ }9 v+ F0 a
13
& {& W% h3 m; ~3 C) U14
, \: H8 z0 [6 D( t6 ^15
4 k0 ^$ g0 v# a$ `4 b/ t16
$ ] I A D& H: O, L1 o17& C l ?* Z3 g5 y0 Y
18* Z3 r$ V+ l7 g: h
19$ M' |0 ~& [) ^& h
20
. S0 w: c$ c$ _: j21
& g/ i( |) T0 d" _" w223 h) f, Y2 T/ _
上述的三种情况可以分别用rjust, ljust, center来等效完成,需要注意ljust是指右侧填充而不是左侧填充:/ E: |: a( l- L& d3 D E
6 ^: b$ q: H* `; G' x0 O# ?6 O
s.str.rjust(5, '*')
/ d$ j( D7 P7 h2 BOut[107]: 8 T v' {9 |4 b
0 ****a
) w6 E; Y" b/ g2 c1 ****b
- I0 e* R( x) J4 s8 i2 ****c9 q* ~1 U3 s* H
dtype: object
% m) z- n4 G) c& _4 w' F0 T+ B9 e; C2 ^0 I
s.str.ljust(5, '*'), P: K* @4 R! ]
Out[108]: , ~: ?* a. T" P, x7 u0 O
0 a****
: ^( V, _% a- X& M& ^2 E% Q1 b****. Z7 t: B; c% `% _
2 c****5 g' i: t6 V+ F
dtype: object
' A4 V" y* n5 j, r
3 [- q; Q6 g; s2 P% M3 k& cs.str.center(5, '*')
* _* W$ H5 P+ I* \, {Out[109]: 8 L8 d3 H( i6 M E: `/ O2 L8 z+ v
0 **a**
- z* W" H. d: X+ v" [! m; ^' D* H. @1 **b**
C8 Z4 D; j& V R: C2 **c**
; p# I6 Y9 X& ?. ^" |dtype: object9 k# a F3 ]1 v* \
; b/ }; g* T( B1 A6 {5 o5 v4 Y/ J1
. p) D* g$ O+ K3 A2* s/ C1 F2 e; ^2 s) M
3+ s( ]7 M; I+ q4 H
4
+ M* v6 N- c5 X4 t! D5
h8 _) x; L l( ?6& y V, e0 P3 x& @1 y
71 v3 L$ u3 r$ r4 [! x# P+ P$ s6 N
8 K" G- Q: I1 n- r, H' g
95 i% _0 U# @6 [6 j- `% l* l
10
- @" a+ ~) f; y& S. G+ ~2 S11
7 R& I2 L! m3 ]) B12
+ I7 E2 \# l& Y8 t137 H- Q- N( z5 B5 _
14
/ ^6 {0 I1 m; @3 L' J5 W& ?15
3 J! v% F$ q% p7 n# N16; D" W K1 K) i" L* J- s
17
/ T/ h3 N$ W& W0 i" B18
2 X+ E4 E, e2 _. T9 a! w5 f19( F3 V6 J$ e" x% p ~
20
0 R/ U5 k# {" u5 F4 _9 B 在读取excel文件时,经常会出现数字前补0的需求,例如证券代码读入的时候会把"000007"作为数值7来处理,pandas中除了可以使用上面的左侧填充函数进行操作之外,还可用zfill来实现。
; ^/ }% r! o; Q" M. ? t
+ M. G* \$ w: M0 O" Vs = pd.Series([7, 155, 303000]).astype('string')
" f+ ?) A2 v& S' {- B" `/ O7 @
* C& G$ N- L3 ?/ O0 ws.str.pad(6,'left','0')
9 u2 L* a5 n5 J( r- p( [' ROut[111]:
5 z3 `" e- e2 Y" {$ P0 0000078 z A$ y+ q2 Y
1 000155
* A2 V* B# _, o7 d: U) _2 303000
; f$ P8 P; w# n8 ]8 n1 A. D h% ddtype: string
" z* U2 _4 B* u4 ~2 } G' R0 ?) }. I. C2 n9 \. p
s.str.rjust(6,'0')3 g4 h: i( s N6 e/ ~9 `
Out[112]:
- r4 {1 Y2 _, a6 M0 000007* f/ Y2 Z2 O4 E2 W( m' F
1 000155; D7 b' _7 D, p& h9 m# l; z" }
2 3030007 [3 c S0 h5 ~# s# h8 A" v& c( S
dtype: string
! G/ ]% A4 B: U8 ^9 }
) S7 p, Y; q- h% v$ [" n0 [s.str.zfill(6)
# z9 I% m% F" x+ K* ^6 y1 y0 k$ EOut[113]:
6 |- C! M% Y: i6 v, f8 }( x" d0 000007$ L# n: ?& h& U. C. @+ f" N1 o
1 0001557 D# x( F' L8 G- C- X# }9 \: w4 d
2 303000
& u8 ]9 Y# B3 l3 K8 J! P6 I$ J5 Y+ l2 [dtype: string# p, F9 M# M. Q% q8 |5 X
) K3 v, ?; f9 ^15 }6 l( s9 H/ u' [
2' W; |# R: {1 V/ H6 p. B( _
3, W8 E% F' T5 ?
4
2 I3 _6 D. n3 U; }& }5 {57 z+ g5 C) K' @" P- q# a. I; p
6" _" t1 f1 c" [: ^; m4 Q/ @9 s
7
( L8 b# s6 ^! M; T ?; d) I8
9 X# h% z2 \4 x2 H9
! N0 j# ?3 m$ |5 T% _! u7 T102 S9 K2 w0 ^4 {4 A
116 \' }2 `; y6 W/ H- E- ?& k( U% J
12
9 U3 }+ Z8 o" R! @1 ^, l; Z13+ d/ S9 N1 {$ G( M; J
14# v |4 g/ x( F3 q# ]
15* @8 ~5 f a6 Z' x. w; h1 I
16
$ N4 c8 S/ |; `, P17
2 K: J a7 J) a18
5 N; f" k# y3 l- \( J19" p# k' x3 R2 \4 E, w. n. _2 M
20/ m7 w6 n6 u* T2 ]
21$ K A; a) o6 U/ F$ N( b2 }3 [
22
/ o+ [! y- O3 ]8.5 练习
: O! g- ?% [6 ] wEx1:房屋信息数据集
+ Z4 Y$ m7 ^4 o C4 u0 j现有一份房屋信息数据集如下:7 ~. G4 `: [5 C' z; L
1 x& ~5 D, c3 t2 g9 [8 d( }
df = pd.read_excel('../data/house_info.xls', usecols=['floor','year','area','price'])
- k; b1 U7 h9 ^! edf.head(3)- U9 q" S1 u$ b5 h( _
Out[115]: : Y+ M! s% Y% |4 B/ E; ^1 `; Z
floor year area price
$ q6 y) {# U0 m; Y" Z4 G0 高层(共6层) 1986年建 58.23㎡ 155万% }$ Q, y7 d4 |$ [- D9 e7 e
1 中层(共20层) 2020年建 88㎡ 155万* i- a& Y3 C; B
2 低层(共28层) 2010年建 89.33㎡ 365万/ l+ V% N) {/ W7 ~# v( t u
1
2 j' k. h \* T5 h2: B5 w: u& Z8 I% M. b4 V
3/ F6 x% B3 v( |* ~
4
/ L+ i" @$ `0 h$ G$ ]. V5) D; ?2 g) O4 P7 \* Y, S3 R4 {
6
0 t9 W( x% [. [7
+ x7 P; \/ {3 E7 a$ D/ `* F/ k( p将year列改为整数年份存储。, Z4 X5 {' |. ]
将floor列替换为Level, Highest两列,其中的元素分别为string类型的层类别(高层、中层、低层)与整数类型的最高层数。
( H! B3 V3 L: ~) q: i' ^' J计算房屋每平米的均价avg_price,以***元/平米的格式存储到表中,其中***为整数
* `2 [. N4 k9 f8 Q7 D! K* {4 C将year列改为整数年份存储。5 `7 T: I9 F4 {5 x0 Z; @# j$ B& j& W
"""
7 |& G& m! R' @5 a: F# u整个序列需要先转成Nullable类型的String类型,取出年份,再将年份转为Int64类型。
4 [" @0 D! E* r5 \7 Z/ X" j% g注意,转换的类型是Int64不是int,否则报错。即使astype加参数errors='ignore'跳过缺失值,
) _7 ?/ G5 x- `) ^转成int后,序列还有缺失值所以,还是变成了object。" X; l( c5 |$ s4 C0 z
而整个序列转为Int,就还是Int类型,缺失值变成了 pd.NA 。4 n- p# W+ i5 Q" n& ]% h
"""
4 a6 O2 L; a, h0 a4 y9 |# b) y" u, Ddf = df.convert_dtypes(); D! [2 T9 X3 J) k$ I
df['year']=df['year'].str.replace('\D','',regex=True).astype('Int64')
# x( @! d4 E! h& t t0 Zdf.loc[df.year.notna()]['year'].head()
5 }$ a8 p- D& {1 ^! W% I T. N: h+ s. K
4 `1 q) j' o2 x, d+ a4 N2 h- Q0 19860 d* d3 J- _6 i. z) t
1 2020
+ g. A, f1 Z" _+ t1 X8 u# n2 2010
' y( E8 Z+ r/ O" x5 [9 N# Q7 s3 20143 q \6 D, ]! ^ |2 J
4 2015: C( z( q Y, i/ @( |# ~
Name: year, Length: 12850, dtype: Int64. {' a: s# g! e9 G; i. |+ g3 j3 s
( \: i% G* m4 ]' K7 p7 |7 }9 l8 d, a1( K" M2 c. I1 o( o' Y) X
29 _/ Y! C2 g1 ~/ K
3( v% h2 B! l7 a8 S
4/ i, t0 b) K" x/ O1 L
5) _4 k; V& T1 E, L9 z# G7 N- [
6
9 c+ `3 K8 ~( d+ {) M5 o* H& ]7: ~7 U6 s( l' p2 h3 |/ c
87 y( u" i# n0 K9 a( n( U8 K
9( }* S) y4 A" D* C
102 P1 M% t3 @: D! W% A
11
0 d9 C, D8 y7 C126 A3 F7 E0 h4 E
133 ], j' p4 R" W! ?* |& |' j0 K3 [
14) R8 C2 j" r; l3 P0 V7 {
15
2 s: W3 D7 J* h N7 r% @1 I16
7 L$ w# ~8 p! h$ i! g2 R! V- u参考答案:
, o7 r4 ?5 r O4 t( m/ H
2 o8 n7 _! ?6 d9 K( r不知道为啥pd.to_numeric(df.year.str[:-2],downcast="integer")类型为float32,不应该是整型么, g# x. ~8 O: d7 D
6 g4 w1 y2 U* {( l& `4 t* X5 d jdf.year = pd.to_numeric(df.year.str[:-2]).astype('Int64') ' n0 s2 {" a& s# t# V
df.loc[df.year.notna()]['year']1 K/ P& v4 h$ Z; V
1
9 x* l% p! W% R9 |23 w! M! f: e0 r5 T# y2 y+ T
将floor列替换为Level, Highest两列,其中的元素分别为string类型的层类别(高层、中层、低层)与整数类型的最高层数。& W! p1 C. N. e' T
pat = '(?P<Level>\w+层)(?P<Highest>\(\w+层)'
& L" A4 K" M- u4 r. _8 t( {df2=df['floor'].str.extract(pat) # 拆分成两列,第二列还是(共6层得形式,所以还的替换一次8 e" F6 X. Z+ h6 b, i
df=pd.concat([df,df2],axis=1).convert_dtypes() # 新增列拼接在后面,再次转为Nullable类型
+ P! Y6 G6 G2 p( u( Vdf['Highest']=df['Highest'].str.replace('\D+','',regex=True).astype('Int64')
; O6 r3 x: V. E5 l. n% Kdf=df[['Level','Highest','year','area','price']]
! Y# c9 ? ?0 o' @; I( vdf.head()% e5 ~) v) l* {& J6 U
) q. Q* V3 K V6 |( v$ y
Level Highest year area price7 i3 n, P; g+ A6 D
0 高层 6 1986 58.23㎡ 155万
/ u0 o }6 X. ?0 g* [1 中层 20 2020 88㎡ 155万+ e" G6 i/ S" s
2 低层 28 2010 89.33㎡ 365万# Y! y& K$ P! K! _
3 低层 20 2014 82㎡ 308万* E; K3 K6 @# N8 j
4 高层 1 2015 98㎡ 117万- S0 T' p7 o" x) b4 I
1
- j& ~6 W9 B- `4 s* J" I" S2 T2( E- N: n4 r9 }# S/ M
3. ^( E7 g8 j8 ]! h" b0 \
43 ^% I8 J8 p" g; A, r3 P- X
5
6 n& ^6 b$ q1 k7 C& _( A0 u$ S6
L; J6 y$ M- d! o0 q0 ?7, E. K; a- F/ O. o
89 U1 G0 Q( G5 [# Y6 |+ g0 _/ i
9
3 e9 W U2 C6 J5 @( ~/ v10- I1 B/ M' o; |0 p, }1 f; f. d
11
) `( t& ^; L$ ?: e/ L/ R12% I; z2 J1 h8 _9 H9 {3 j4 \
13
% y* e* s6 U4 x# s% n# 参考答案。感觉是第二个字段加了中文的()可以准备匹配出数字,但是不好直接命令子组了- v4 x1 t# K' J7 a2 E1 b( D
pat = '(\w层)(共(\d+)层)'8 H1 d' J% g O0 D' ~: _
new_cols = df.floor.str.extract(pat).rename(
# C2 |$ Z1 _* `5 p columns={0:'Level', 1:'Highest'})
7 @# l8 |- v7 f' }4 H/ ?2 }! h
df = pd.concat([df.drop(columns=['floor']), new_cols], 1)
2 q- z7 u, p) s3 | gdf.head(3)3 F% f/ B P8 ? O, F+ N9 r
+ J/ |- I0 {1 S7 B$ F; @Out[163]: % G- W5 \# I' N* O: B$ \! C1 [. C
year area price Level Highest
* K; g- i1 p# Y/ E# i* @# {3 M0 1986 58.23㎡ 155万 高层 6( O! x6 r" w% b) I6 p
1 2020 88㎡ 155万 中层 20
% f5 z6 D. w5 }6 F+ x2 2010 89.33㎡ 365万 低层 28
: A+ q, _2 p4 @" H' ], G& t1) O a3 g( `/ u O: G
2* I0 N" x$ w4 S \9 j+ a
3* |& v. L7 U4 T, U" `6 e3 W
4
5 O: o1 w- x# X7 z) @% l& L, [" Q5
/ i# y5 q, L0 r. l2 S: d6 g6
4 N0 ^. ]9 s1 ~! d7: D4 E% U; @# ?' ]0 n2 Z
8' \- t9 c( S6 ~3 F! u" i
9
( s }2 ]6 [' z g1 q10
3 Z3 ^$ E/ n! p/ N% _112 [: f. X4 m1 K1 b' N
12
3 s% ~4 y2 X' w( I13
1 e- n( k" {, A8 ?1 O5 Z计算房屋每平米的均价avg_price,以***元/平米的格式存储到表中,其中***为整数。
* c4 n! d2 E/ V- G"""- M8 O. d+ y" h' i1 C
str.findall返回的结果都是列表,只能用apply取值去掉列表形式
) L8 ^2 T3 U/ S5 [参考答案用pd.to_numeric(df.area.str[:-1])更简洁6 e6 H. \, ^! m7 f5 p
由于area和price都没有缺失值,所以可以直接转类型& P( }) W) b( h9 K3 P/ A0 _
"""
9 B5 H' D8 C b P, ?: u `df['new_area']=df['area'].str.findall(r'\d+.\d+|\d+').apply(lambda x:float(x[0])). o1 p9 e! x' w( |
df['new_price']=df['price'].str.replace('\D+','',regex=True).astype('int64')
! Z" g: x& z% j Y L1 `df.eval('avg_price=10000*new_price/new_area',inplace=True) z8 R2 e& s y% P0 g* R" X% Z& h
# 最后均价这一列小数转整型直接用.astype('int')就行,我还准备.apply(lambda x:int(round(x,0)))
% o6 ?% F$ h& {) |( d# 最后数字+元/平米写法更简单
) t9 @0 s# e( b- r9 ^7 Kdf['avg_price']=df['avg_price'].astype('int').astype('string')+'元/平米' R' i- u q6 j8 H+ v: |3 j& e4 A
del df['new_area'],df['new_price']
1 a) r2 I& \4 ~2 Wdf.head()8 ~3 W Z% x, L0 q4 Q9 j- A; |* k
9 X3 B+ v; _% r6 Z+ p. r5 U
Level Highest year area price avg_price% L& s9 b# T! O4 _
0 高层 6 1986 58.23㎡ 155万 26618元/平米
4 @$ _ m5 |. s I5 D1 中层 20 2020 88㎡ 155万 17613元/平米( \ N5 X! o: s2 P# J
2 低层 28 2010 89.33㎡ 365万 40859元/平米+ x; Y. y. V( z: P4 S, V
3 低层 20 2014 82㎡ 308万 37560元/平米
* ?3 Y h3 a2 N4 高层 1 2015 98㎡ 117万 11938元/平米
" Q' T5 q( m- _5 {# m* _7 A/ d1 L
1: F# f; X$ \1 }7 ^; g. e
2
& M9 l. c9 M2 j! R6 c0 f" x6 w5 }0 o34 B) a( J6 R: _/ f! q
4- P) `' ?6 M; X& u, R% Z
5
8 N& o- J, x+ y+ r+ q' o1 ?0 [! i2 W; Q6
, b! o. R" y. M1 a% u. k1 Y74 \! Y, f3 o7 o* d* N9 U# @
8
! q" c9 x, ?) L& m9: |! _' C4 }% s- v
10
& b- N. ?% @; I- ^" W1 n, H8 [11, e3 U5 y, I/ u
12
/ L! l' n6 U4 e! S, Q$ [13( r& w$ }; u' Z
14* k9 `, p1 I# _' S0 p
158 E1 h' P. W3 r6 s2 g& P' |
163 d$ E' y9 i, |/ V0 b
17: S- X$ y+ O" P0 m0 H! `( O
18
) I3 N0 R9 A. Q( j8 ? Y9 Y3 y4 I- S196 {* y- ?3 C: \
20. _$ x8 Y8 t! p8 @; H. m
# 参考答案
' B& S, d1 ]! _" |s_area = pd.to_numeric(df.area.str[:-1])) v% n" H. J4 T2 P1 h. m
s_price = pd.to_numeric(df.price.str[:-1])$ o x* W9 }% A
df['avg_price'] = ((s_price/s_area)*10000).astype(& p& }1 j n9 N% b
'int').astype('string') + '元/平米'
L& f1 q/ D* K' b
6 c: G" U, v8 F. N3 {6 `df.head(3)1 ~ }: C6 m f' J- h* U1 A
Out[167]:
9 G# Z& S& m3 E5 k) A# I year area price Level Highest avg_price: s* N3 I! X# C% F% \
0 1986 58.23㎡ 155万 高层 6 26618元/平米8 S/ M/ t" y' G: g/ J% y+ i9 T5 \
1 2020 88㎡ 155万 中层 20 17613元/平米
- F7 S7 w6 w' e9 l8 X2 2010 89.33㎡ 365万 低层 28 40859元/平米
, n( P' i/ y, A1
u4 G/ a! W$ P4 j: _) F6 t" v2/ T7 X) s* w" {/ V1 Z; n% ?
3- D, \! S$ L: |6 L
4
9 v- i) C6 ^" i' U8 T5 O9 g: `9 O5
( g- G4 e Z4 ?% w6
. L, N" P3 L$ V; ?7
9 r" s3 T ?( T6 q7 J7 T89 M! T9 Q: f, b6 r" g6 g
97 P1 p4 _) s; b2 f2 @ X
10
' b7 R& O* V( W( c' b: o1 q; z" F% s9 N11
; A, E+ z: c v12
2 z& B- t2 N1 S0 fEx2:《权力的游戏》剧本数据集# P! J+ q) q; W" r+ o
现有一份权力的游戏剧本数据集如下:
+ S- a8 m( ~4 S6 N5 R+ `( W! H! r' P6 W3 Z6 B
df = pd.read_csv('../data/script.csv')# p$ m9 D: \( i8 T' }
df.head(3)
- E# d* C( c3 I8 a9 v6 V! ]
* T' w+ j; h4 }1 s& g- Q7 XOut[115]: 8 }1 |% L3 v' z5 t" W
Out[117]:
! O8 O& T7 Q* B3 a8 M! f& K Release Date Season Episode Episode Title Name Sentence( K' i! P7 @3 F4 g
0 2011-04-17 Season 1 Episode 1 Winter is Coming waymar royce What do you expect? They're savages. One lot s...
3 i" I: @: _8 d1 2011-04-17 Season 1 Episode 1 Winter is Coming will I've never seen wildlings do a thing like this...
8 Q9 \2 L3 X) V" Z2 2011-04-17 Season 1 Episode 1 Winter is Coming waymar royce : y8 y, O" L: ]
1
& u" I7 b3 @$ D V4 P1 p2
5 N: O2 `9 ]( H: o8 N3
* J9 X* Y' f4 C4
& y* b# i+ h" G# o. y. H3 C$ G5
0 o R+ }8 ?+ v62 U" ]. C" K. A# g. `: G, X! k
7
, ]0 A5 P7 k8 V; l" P8
7 G: J9 m0 i" \% U( d. `94 R( @/ ]7 Q1 J4 A1 P
计算每一个Episode的台词条数。/ b) L7 n& {7 J1 [) f
以空格为单词的分割符号,请求出单句台词平均单词量最多的前五个人。
8 u& `2 d- n( |5 ?若某人的台词中含有问号,那么下一个说台词的人即为回答者。若上一人台词中含有 𝑛 个问号,则认为回答者回答了 𝑛 个问题,请求出回答最多问题的前五个人。" r* F( @- j/ H6 d: l( P7 w% c1 }
计算每一个Episode的台词条数。
, e W0 j6 V& l! bdf.columns =df.columns.str.strip() # 列名中有空格
0 w& }& y& c/ D Pdf.groupby(['Season','Episode'])['Sentence'].count().sort_values(ascending=False).head()
; _" g& h7 @, w/ Y+ \; x( o& w1 j" T& O
season Episode
- p4 ]! Z. O0 \" w9 Y( F8 V$ I2 ~+ ^, tSeason 7 Episode 5 5057 L7 m" S: B1 V
Season 3 Episode 2 480# e6 t. ]3 [0 @$ ?! J( z
Season 4 Episode 1 475, P% F" K' G6 Z+ }* @
Season 3 Episode 5 440
- B6 [* B y7 m0 JSeason 2 Episode 2 432& ^0 K! s P* i( S& ~; q( i- c
1
! ]' K/ d F' M; N5 i2
7 l3 C1 _1 I( `1 [2 p, J- b( h) B3
( F2 V+ w+ `7 I, N47 n, {: ?; p' B
5
9 _3 C% v1 V' \6/ K& M" ~! n) R7 ^: b( \7 v
7
( m9 \5 @5 _/ I' O80 ~0 z2 V/ o- b0 ^
99 O, J; K9 J2 m Q% r% b5 T+ Q: T8 ^
以空格为单词的分割符号,请求出单句台词平均单词量最多的前五个人。& P0 }, M9 X+ y0 F7 P
# str.count是可以计算每个字符串被正则匹配了多少次,+1就是单词数
2 y6 I; I5 E$ gdf['len_words']=df['Sentence'].str.count(r' ')+1
/ ^6 A) D' q5 y. n7 ddf.groupby(['Name'])['len_words'].mean().sort_values(ascending=False).head()0 O8 C. R- R2 g' k* E8 W
% Z, T4 E9 z( s8 P7 NName
0 {5 d, k5 C1 v1 L) Y4 U9 Umale singer 109.0000005 g M) [! Q9 o' @/ t4 f" W
slave owner 77.000000
: I4 D: Z$ p$ }manderly 62.000000
) b. d& V$ x( V: ulollys stokeworth 62.0000000 J$ j* V. H: C7 M$ \0 _: Y
dothraki matron 56.666667$ p+ @% w- o0 x& G2 F1 q0 w5 N. h
Name: len_words, dtype: float64
4 `$ Q. o/ O5 l: m- b' Z1/ P9 g& d6 z/ V
2
1 n/ v) d/ C5 E5 y0 f$ p. _3% {2 S4 d' [% R% s% s3 q
4
$ x9 A! A5 P+ v; [" _0 W5
0 A9 R4 p6 B y: [6
( ]1 _3 G7 ]5 M( l/ ?7( x7 Z3 a1 O; O6 d! M* m
8
: a _) x i' `6 [ `/ K9& n9 M0 H! u4 a$ G& U% P" ?" D
104 |1 O+ ]0 q& a' F
11" C# `& F2 O8 g* Q7 a ]7 e
若某人的台词中含有问号,那么下一个说台词的人即为回答者。若上一人台词中含有n nn个问号,则认为回答者回答了n nn个问题,请求出回答最多问题的前五个人。: V- Y2 g) V; _4 P
df['Sentence'].str.count(r'\?') # 计算每人提问数
4 w" m4 H, L& f- D- xls=pd.concat([pd.Series(0),ls]).reset_index(drop=True)# 首行填0$ L$ x) q+ ]4 ]" O' ?7 V
del ls[23911] # 末行删去! }: ]% U3 e4 ]+ B, @2 K$ _
df['len_questions']=ls& V+ T/ f) [* b( _
df.groupby(['Name'])['len_questions'].sum().sort_values(ascending=False).head() a4 v# I; b( I g8 e1 b
# M. u' n. Q' ~
Name
3 p1 [. S0 m" G8 o2 X" o* }: jtyrion lannister 527: c+ a, f0 s; j) u
jon snow 374
0 ]& f! @3 Y8 r% b) K0 z% djaime lannister 2833 O$ o8 [4 h$ h
arya stark 265& L# T. ?" N: R+ {* t j( Z+ ]' `
cersei lannister 246
. \" H/ F! [9 m; v* e+ {6 |Name: len_questions, dtype: int645 s. l8 }* i7 G: y5 U8 M3 Q/ ^
; L9 N8 o1 T0 y1 e# 参考答案% l" N4 }" M/ u; N4 e( Q
s = pd.Series(df.Sentence.values, index=df.Name.shift(-1))
! ~! j# J6 ~+ q5 |s.str.count('\?').groupby('Name').sum().sort_values(ascending=False).head()% j/ E. |4 [0 R0 [# A6 G
1 w- X% n( F9 Q) |" c
1
* s% g: c) j7 r G2 }" v+ h* E I' w
3
; S7 B0 }- R4 W4
0 s- ~! a* \( F* b; ?+ t- N59 F* y* ? J! i6 J: M) j4 r* X
6
2 w" k+ m+ x5 h3 N. I7 w7 s) v0 B7
; Z" ^5 g" B* H/ t, O8- \/ r* \4 E3 O0 J
9+ A! K$ _6 Q) c1 f2 a: {
10
$ w8 t* ?: {. H- H) ?11
6 ? @4 o, N2 @! ~( r% C9 x12
1 M: {% j3 `7 P% a: {3 s13! P! @2 X& K2 K- O* d7 g0 C7 d- h
14
0 E) c4 c" n' f8 l- _, i8 C3 N15+ Q5 }8 j. v$ l+ o
16
+ U4 V) W [( d' E" M17
& |& y5 r7 p- S, y% q3 F; x/ W第九章 分类数据
5 X1 r& J# K! I; [/ bimport numpy as np
% l# i6 u4 A/ f" R4 _( ~9 }9 Cimport pandas as pd' F* ? Z& v* z4 M% r) }
1
* [" b8 B. _; T$ t) L2
- U( c/ |, C' }( i: U7 a. B5 T9.1 cat对象: [+ A+ K, h/ a4 X
9.1.1 cat对象的属性1 ~; W1 q- ^- [/ i2 M
在pandas中提供了category类型,使用户能够处理分类类型的变量,将一个普通序列转换成分类变量可以使用astype方法。
, @3 ^7 u- w( L5 R4 F
' k% S$ B8 l# E9 [) d4 {df = pd.read_csv('data/learn_pandas.csv',4 n& a7 R4 q' _* g( O
usecols = ['Grade', 'Name', 'Gender', 'Height', 'Weight'])
( \0 K2 @5 g5 C9 b; Ds = df.Grade.astype('category')
& v3 {& J0 ?9 H2 f3 j% ]' A; M0 ~9 v* `
s.head()
6 b: X ^, B7 |& R+ R( POut[5]:
* ^" O$ j, G' B2 V" w' _, n0 Freshman
4 k, D0 s% c& G) a( y% Y4 h0 m1 Freshman. n5 c/ \" R, r: z3 Y& l% z
2 Senior
# }" J+ g- _: q: C4 U3 Sophomore( o! K% {7 z& f4 ~4 Y3 f
4 Sophomore
: n5 [6 I( x! K! a. b# N& CName: Grade, dtype: category
. }1 A! u6 {/ k8 u2 R$ rCategories (4, object): ['Freshman', 'Junior', 'Senior', 'Sophomore']
4 k- U( y2 g) X1. D) V& i2 S* C( ? ~
2
6 [. V& S& `& P3- u, x' m6 l- O/ m3 v4 ]! L' r9 l
4
9 x7 n9 C* ~- S5 z( v7 |3 V& j5( X/ q/ {: I4 }: V
6* T" H# ` l6 A2 U! G: g0 t
7
- F2 L) ^$ ]5 ]3 F! f* `83 b4 w* I/ v2 b! _! f* }) l
9) Z" o" d2 X8 O7 N j
10
, m: T7 S6 G" H6 Q! z8 J* E4 h11. P7 F z. `& M5 Z7 O o0 p
12' y/ d5 N8 _' e* R% R B1 g2 W
13
3 d+ ^) R f: v+ H4 e 在一个分类类型的Series中定义了cat对象,它和上一章中介绍的str对象类似,定义了一些属性和方法来进行分类类别的操作。
1 ~+ Z# s2 J2 r, M' m' S' |& L, J3 `+ `/ ^- X2 }4 B
s.cat
( U$ A% B* T/ T/ YOut[6]: <pandas.core.arrays.categorical.CategoricalAccessor object at 0x000002B7974C20A0>+ R/ {" o0 m; q7 O& c: g6 p
1
2 v! Q; C/ p7 c* h8 z' P- p2% ?" F5 E6 g3 E. i a. q
cat的属性:
4 k7 o7 s% P9 X; i2 U* |8 g- H8 z4 O( o: @
cat.categories:查看类别的本身,它以Index类型存储
. v7 a/ Z9 L2 B+ G- z) Ecat.ordered:类别是否有序
, X6 i0 }! R: Y( |: r/ I" ^6 ucat.codes:访问类别编号。每一个序列的类别会被赋予唯一的整数编号,它们的编号取决于cat.categories中的顺序
1 j9 R% ^" `3 N# f$ l3 bs.cat.categories( f: m6 E: N+ ^& K0 ~: T
Out[7]: Index(['Freshman', 'Junior', 'Senior', 'Sophomore'], dtype='object')# f2 s" H" c. v* Q5 i1 D! {& H
( G- j: c/ o) y" ls.cat.ordered
1 X8 v* q; e" y* t2 t+ sOut[8]: False
; R! A. k& d x% H2 G- l
3 P0 X; g7 @: r( v" ]s.cat.codes.head()& c$ a; w6 w- @3 O
Out[9]:
# c) L* `" W, G4 f0 |) ?0 W8 E0 04 J5 T- ?: Y+ D8 Z* L
1 03 i: l. u# _' f, {6 c
2 26 t+ ~1 c- T7 K# S" N
3 3
2 j1 o" q! ?0 B, d: a4 3
4 r2 R* w5 z5 e6 r \dtype: int82 X$ b. Y$ V6 e1 d$ r# j" n" ~
1
1 O, \7 A% c& J' h( G3 q7 D2
1 Y3 G! g3 a6 b) D4 P/ K1 x3
# `1 {* |& [8 t! @) A7 i+ i4& E: X! \4 ]) _ P& q$ _8 X% F4 L
53 |7 ^6 C* h N
6 g6 b% Q7 x8 w3 P
7. H* \$ ~, G! n1 c2 ]
8
% T' S9 q/ {- D. J+ U9
9 J1 K/ ]4 B' S0 q6 S10
* g) T. h- [' u, u w8 k) b117 v& b% m5 b- }5 n. g) \
12
( {) M$ @; W4 S1 ]0 G! T; ?+ F; v138 t1 m: k0 u9 e9 Q# R0 G8 n% l
14) C& W U5 ]& N% a
9.1.2 类别的增加、删除和修改
p1 p! ?" N* p6 l* i7 Z2 [ 通过cat对象的categories属性能够完成对类别的查询,那么应该如何进行“增改查删”的其他三个操作呢?% t# A A A0 u( E- \
4 I; ~ k [' D& |+ B# Y2 t
【NOTE】类别不得直接修改
" ~0 B: Y$ R" f在第三章中曾提到,索引 Index 类型是无法用 index_obj[0] = item 来修改的,而 categories 被存储在 Index 中,因此 pandas 在 cat 属性上定义了若干方法来达到相同的目的。
9 f4 u" ~+ C6 o1 A# ]% ~( {! G( v7 I( q3 O, l; s( ?9 U
add_categories:增加类别
) _6 ^* C/ h8 \2 `s = s.cat.add_categories('Graduate') # 增加一个毕业生类别: Y5 Z0 U A3 z, A8 F9 _1 U
s.cat.categories
& e8 i* k, V/ e' Z% I4 q
4 t, E7 B) M/ z0 S. SIndex(['Freshman', 'Junior', 'Senior', 'Sophomore', 'Graduate'], dtype='object')
* l# V- `$ v/ {8 [- H; F4 S) N1
# g$ r' E, H6 e8 q0 A" t8 h2- j4 p/ H+ I/ d4 y& c$ n3 q
3. c" k1 y0 Y0 u$ J. [
4
6 ~- [0 J2 d6 mremove_categories:删除类别。同时所有原来序列中的该类会被设置为缺失。
1 a! ]% a/ S+ J" v" |s = s.cat.remove_categories('Freshman')& d8 O1 }1 [" X# a9 `9 ?5 K/ U
& ~' T5 T8 l6 d) v) d0 Js.cat.categories
; l; `! N4 [/ P' a* a% |. HOut[13]: Index(['Junior', 'Senior', 'Sophomore', 'Graduate'], dtype='object')) C6 T5 H0 b1 [
5 f- ?/ Z! h1 |. s
s.head()
- Z# I. G# e& t2 Z& rOut[14]:
; P9 q& o5 S# o& K9 i: H- z0 NaN( ]- L Y7 _4 G& u& ]1 @
1 NaN: e) y- `! k; a( M, y- g
2 Senior
G. B6 L# b' M( g L3 Sophomore
8 b- R ?( v1 Z* ~: y& t4 Sophomore
- y5 ]7 L( \8 k( cName: Grade, dtype: category# }9 j1 D6 _: H4 O: q
Categories (4, object): ['Junior', 'Senior', 'Sophomore', 'Graduate'] L* Z; n! E# \ c
1/ B5 N% ]; n4 f" w
22 n" L* o8 k! k, [4 H. o
3; e3 h% i* u# A3 ?! D( l6 o
4- ?& q- |* g0 \/ B
5% u% D% E0 |; f. l) ^: q
6
6 w1 `" Q- Z1 _$ b7
. q$ V0 [: i1 o$ g8# F5 M& w' G6 c, Z" o* a
9& d" d; o0 L& K( q( W
10
' m3 U; e1 p& z/ L U; ~' k118 x$ e1 N. S( e
12
, V+ `" h1 w. i2 L136 V. g1 H: a0 I, j! Y; p4 C
14% k/ n) X; y- d* f. B. E
set_categories:直接设置序列的新类别,原来的类别中如果存在元素不属于新类别,那么会被设置为缺失。相当于索引重设。
0 |$ c4 b2 j* xs = s.cat.set_categories(['Sophomore','PhD']) # 新类别为大二学生和博士
0 Y3 {! ?3 \! _) Z9 ps.cat.categories9 x" y( S4 [5 T+ Q* G k
Out[16]: Index(['Sophomore', 'PhD'], dtype='object')! A" V* _) V% n, U I- q& M: R
4 B$ w+ m, N7 M8 Q; u
s.head()
' V- ?$ \1 n+ J) v5 ?2 gOut[17]: ! z) _& y: Z- {9 L
0 NaN
# l" W- l- J9 T7 i5 x) w8 c1 NaN- s5 f# _; m6 J% L/ _, Z# {
2 NaN7 g. p, x W# G/ u* v t! [+ ^- j4 f
3 Sophomore
) K: {6 W, p! o# B4 Sophomore9 S1 M# Y2 \7 P! \% m" o
Name: Grade, dtype: category
I: \9 f: ?5 C6 b* FCategories (2, object): ['Sophomore', 'PhD']8 X Z2 `" N/ r% f4 \" ^) j
1
! t0 n1 O3 G& p, h2! g' y+ l+ `6 P1 v) t1 n4 g" |
36 {) E* s2 ]: n, }$ ]
49 k. g' i. x) [8 @; I1 k3 a" v
5
$ y# i% j) ~0 \2 g0 o" H8 I6
" H. m9 u5 n4 }4 ?7 r$ ]4 B) Q6 C+ o/ w+ |
8
! y3 B, d5 g% y( ]" \7 F, O* l9
* p2 ~( w7 g+ t& p! o @: t2 v) J10
$ A0 J+ U! v" ]9 R: q4 B/ a11
6 a7 f; ^% X, ^$ F7 [% s5 E12, b3 o0 U/ E3 V
13# _6 f4 b* F% N: Z6 c
remove_unused_categories:删除未出现在序列中的类别/ d# g! S& H5 G& r" [' {0 l$ C
s = s.cat.remove_unused_categories() # 移除了未出现的博士生类别
) ]/ S$ Y, r8 k4 h7 U* Js.cat.categories- t% s i) w1 K5 |2 ^1 p
6 x3 p) v8 K7 [6 ~Index(['Sophomore'], dtype='object')
8 O; p% J6 F$ G5 K1$ G- {1 n: S9 o! I
2
* G2 O$ n4 S' L" f4 H; L3! | b$ C Q! f' m" a+ P. p, Q, l0 A
47 o$ @% Z( c) b- U! T
rename_categories:修改序列的类别。注意,这个方法会对原序列的对应值也进行相应修改。例如,现在把Sophomore改成中文的本科二年级学生:: b2 Y v, K- ?! ~8 q0 }
s = s.cat.rename_categories({'Sophomore':'本科二年级学生'})
9 t# R& U' f* ^! B0 G4 A! Es.head()4 A5 s- g4 q7 J, @
9 p |# j; _ W3 H5 H6 C
0 NaN2 p7 W {) V" N
1 NaN% B6 c$ t2 u }7 L6 k% E
2 NaN8 I: D" q0 _+ N) e
3 本科二年级学生& e8 A) M# d2 Z. a0 L
4 本科二年级学生/ ]/ q& P4 m$ B* W, y
Name: Grade, dtype: category
0 Q$ I1 }) o0 [5 XCategories (1, object): ['本科二年级学生']
' [4 H: a/ G: U8 ]$ E5 p# e1
# U2 h: {) r$ i, R9 N, V0 s2$ G# e8 o% a& N" P
3/ z& w7 S+ D3 D; o6 l" A, F6 T
4
' l% t& d& a2 ?' S' _. `5: \# R6 r6 ]4 y" s( H
6$ e6 n0 P6 v. L8 k H& G
7
4 N% a4 o; Z, \4 ]: `+ {% o/ m' C84 O+ C7 X; n% P) W ?
95 q' F" a Z3 E% b. Y
10
; }9 Z% ~7 F2 [9.2 有序分类# `+ m. D: i) Y/ I, P+ i
9.2.1 序的建立; l7 ?; P3 t; i
有序类别和无序类别可以通过as_unordered和reorder_categories互相转化。reorder_categories传入的参数必须是由当前序列的无序类别构成的列表,不能够新增或减少原先的类别,且必须指定参数ordered=True,否则方法无效。例如,对年级高低进行相对大小的类别划分,然后再恢复无序状态:- C* i" F) l: v5 }. D
' Z/ x. W! T) l$ Xs = df.Grade.astype('category')
9 T2 d) m7 X- D4 H! ]s = s.cat.reorder_categories(['Freshman', 'Sophomore',
* M- @$ G) L1 C0 M( Q j 'Junior', 'Senior'],ordered=True)9 R/ X% @. s( ~$ s! |8 ]; ^
s.head()
. \/ C8 j& B$ v( G/ N6 nOut[24]: " H! l. l9 B/ Z; N1 _- o; S
0 Freshman
) V" y& A. Z# r1 Freshman
/ S! ~, ?& ]1 X2 Senior
6 e/ M9 C2 h3 g. [) ~# m* ?6 U$ L) x0 g3 p( q3 Sophomore
3 t1 x% D9 _9 N* V! e* [: N4 Sophomore
0 z* R! r, `' ?8 `8 a( t- DName: Grade, dtype: category
8 B" E* W& ?: \1 x7 Q. d: U' \ eCategories (4, object): ['Freshman' < 'Sophomore' < 'Junior' < 'Senior']) ? D2 U2 i& @' F
( |; s, v: Q! }
s.cat.as_unordered().head()
' z6 a5 H6 t. a0 `/ _! @* Y* iOut[25]:
9 W# {+ T' T. C; V- v0 Freshman
# H% i" g' W2 G1 T' Y1 Freshman
+ [# q7 r1 B2 B3 S8 W' {2 Senior
+ T# J5 l! R( Z2 a* M1 v7 l3 Sophomore
& z- l9 x1 {& E; e$ J4 Sophomore% k+ x- E2 Y% {
Name: Grade, dtype: category- g4 _& C5 p$ o
Categories (4, object): ['Freshman', 'Sophomore', 'Junior', 'Senior']& n' B, S: o9 e D
+ n' o7 b8 p+ n0 G4 X# _$ G1, ?( L' R" I0 J0 M4 @5 m
2
5 \! U1 C4 F/ @" [5 y2 K# i6 c( I3/ U3 O6 H# o! V: @$ B2 k
44 z( q2 `* f: _6 |! A
5
6 B0 T: V$ J8 Z# G) |6
M% s8 h# U/ Y% G# \! S7
$ a) l1 z' j# z# o o2 s% m8+ r. {, B. ^0 D/ j' A. v/ n
99 P: Y- ]9 r" q* k( F& \! _8 s+ T; y
10! {) E* |( W# A& f7 {
11 x2 b; L3 x' Z2 z3 S3 I7 N/ o
12
, L0 S1 V: `9 R0 p132 b2 _" S" ~2 {* d$ j0 k& Z. f& X
14
% c& ~8 l/ y* _1 ]2 J+ I! @- Z15
4 M0 w" }! w# I16
0 q/ l& ^" {, h$ ~& a6 U8 `; U& Y& j17
0 j" ~0 S4 G1 t( j7 C18 p* {+ M. H) p) K& L6 ?( f
194 f" H) q9 ~" l" W
20
1 s/ a) X0 w4 \. T21
& E; }( N+ o4 {! b1 Y K7 Q22, ^: }% E3 e ^$ b# e( F/ z6 n
如果不想指定ordered=True参数,那么可以先用s.cat.as_ordered()转化为有序类别,再利用reorder_categories进行具体的相对大小调整。
8 S0 A: v( \9 C {/ N% Z- U X+ _" p, Q. ?/ w4 \
9.2.2 排序和比较$ t9 U- f) B( F; ^
在第二章中,曾提到了字符串和数值类型序列的排序。前者按照字母顺序排序,后者按照数值大小排序。4 f4 O+ N* Q8 X; I& @
2 Z& R3 n g, o& D 分类变量排序,只需把列的类型修改为category后,再赋予相应的大小关系,就能正常地使用sort_index和sort_values。例如,对年级进行排序:. w8 c$ \2 K) i: a- G3 I7 j- |
0 F- y$ V' W( c$ Ldf.Grade = df.Grade.astype('category')
R/ o& T# }5 m9 R' H' l4 idf.Grade = df.Grade.cat.reorder_categories(['Freshman', 'Sophomore', 'Junior', 'Senior'],ordered=True)( c: M, s) ]3 r4 H! Z
df.sort_values('Grade').head() # 值排序
( `, E- s0 v* ]$ S4 y7 X, aOut[28]:
5 W) D! R! c3 @) b. U" a$ Z: m Grade Name Gender Height Weight
3 s; T6 o& o7 b4 f$ Y' J0 Freshman Gaopeng Yang Female 158.9 46.0
( z7 X, I3 {" b" \105 Freshman Qiang Shi Female 164.5 52.02 S- H3 D5 x* ]4 g0 K2 N; F8 }# k
96 Freshman Changmei Feng Female 163.8 56.0) i- Q- a1 \$ W: r( [8 p
88 Freshman Xiaopeng Han Female 164.1 53.0: V% t4 K8 {( T- j# m
81 Freshman Yanli Zhang Female 165.1 52.07 u% w& O, @% F
$ D# `! ~) [2 W/ R- C; Qdf.set_index('Grade').sort_index().head() # 索引排序
. M7 ^3 ^) s! t" R, OOut[29]:
X2 x6 s' e& D. q! n+ Y4 A Name Gender Height Weight6 |( a" w/ f; l
Grade
6 n# O% d6 [7 x+ S" J8 A; MFreshman Gaopeng Yang Female 158.9 46.00 n* [; W4 `# z; @8 M
Freshman Qiang Shi Female 164.5 52.0
. [0 r H: I. y' p3 ^6 ^! DFreshman Changmei Feng Female 163.8 56.0! F1 ~0 a0 k ^( l, R! D9 n! q) `
Freshman Xiaopeng Han Female 164.1 53.0
1 Y) C4 i) d! u5 p7 `Freshman Yanli Zhang Female 165.1 52.0: a( W% U- b i9 c* d! r- }2 J
) k, W, c% [% N- {
1
" C0 K7 U' m7 ?" {8 X: [2
. d$ d* c5 R' \# P) M3, B1 b+ a$ L1 _2 [
4
0 f) z6 Z4 z Y+ x6 w+ Y( {5
; v" y0 x1 {5 v R6
; m" p2 R& P4 I* D/ ?$ S: }" x7
/ T( R, P) X5 U81 L* Z& n" n9 H) n/ e+ w9 [. L
9, }1 U/ w/ X- B3 j I
10
: T$ d. u; F2 P) u e, S117 S% r1 B9 T; d! k. l
12/ y F8 U9 V I+ J% s
13
6 P. h7 t5 _5 c0 F14
: S: Q# y) i r5 k15, d' e( l' o! W* b% L5 S
16
0 I4 E8 D* t4 K6 S' B. e$ h3 V17' J9 D( k( Y, s7 A' s% A
188 E3 K% ~. N! e2 M" R2 s# h
19) l9 S4 g/ Q0 E- K1 S8 |7 L
20' W, ?! L& z: q
由于序的建立,因此就可以进行比较操作,方便后续索引操作。分类变量的比较操作分为两类:' g: m0 {7 B3 _3 a+ P
; g& i0 h9 p; [" z9 _" ^==或!=关系的比较,比较的对象可以是标量或者同长度的Series(或list)。(无序时也可以比较)
) }" m7 z& _/ r# \# T& |>,>=,<,<=四类大小关系的比较,比较的对象和第一种类似,但是所有参与比较的元素必须属于原序列的categories,同时要和原序列具有相同的索引。. }2 H& G2 n) Y0 V3 z& l# k
res1 = df.Grade == 'Sophomore'
% f: \8 M3 m: s1 K3 J* b, t/ l' N8 `) H/ ^5 H. W
res1.head()& ?% ]! J9 F8 B. j
Out[31]: 4 K# G7 C6 _; E
0 False
% A# }% H* z( ]1 False
3 |9 e6 k3 w6 G/ y, I2 False" d. x* v% {8 U8 T8 I4 i9 z
3 True. G3 I$ t" H/ z
4 True
. n4 B$ r4 a+ M$ s4 Z9 cName: Grade, dtype: bool8 g0 m( Z! I: P* O
1 t" G1 r5 _4 o7 _+ ^
res2 = df.Grade == ['PhD']*df.shape[0]
# e' w9 F7 J( E" n
& T- @0 S7 { y. T/ f) Q; `res2.head()1 p& a# I6 |% @& `& z' e' n
Out[33]: * q: k' b# `0 k' l
0 False+ R+ b/ U! S1 L- k4 C! r) m
1 False
m8 h0 w4 R# v: k, m7 w7 m7 Z/ w2 False+ ^ R) }& P: c" e. M( }
3 False
# {8 b6 X# T1 d) i4 False3 `$ C s3 b! i- D9 f: t
Name: Grade, dtype: bool
% |9 E+ J$ q$ Z- M3 O% s2 [+ A/ L8 e/ V! p$ N6 S" @# x7 L
res3 = df.Grade <= 'Sophomore', V+ m/ {4 {; @) P3 m! |! }
& Z; X" b* d8 d+ y6 ^- ^res3.head()
4 J* p+ \( I4 r/ N0 a g5 ]8 iOut[35]: * t- G' U# ?7 Q+ _2 f
0 True, P7 ^2 \# h. ]- R0 n ]
1 True
8 [5 ^5 c4 P; i* `0 ?2 False; k; h7 I9 X7 ^7 F
3 True
' }/ x0 C( t0 [. E7 L; ]7 G4 True
: z- g5 t% O6 I! x+ KName: Grade, dtype: bool
+ J) u! ^; I3 Q! x: u
2 j/ N6 T3 ]1 q# sample(frac=1)表示将序列随机打乱。打乱之后索引也是乱序的,直接比较会出错,必须重置索引。) E, ?2 S" ?% q2 w
res4 = df.Grade <= df.Grade.sample(frac=1).reset_index(drop=True)
3 i" z( n2 }! e* ]0 u, Y% T" `1 A$ J- k- v: t
res4.head()- W0 @; \! @6 F5 z
Out[37]: 0 \' a7 i* l7 H
0 True4 f2 O* i: I% c4 Y7 U$ s' Z
1 True& F; N7 ]% p S S* b
2 False
5 I o& p! A$ N7 }( w, T4 R3 True
" @2 M" c+ w B4 True
1 f$ p7 D& x* {0 p/ z0 r- ^Name: Grade, dtype: bool- ], q; h! Z5 N0 d9 P8 `3 q% {
* ~/ v- d1 A& _ i" c7 D
1& p z! h7 W" }6 }( |, M( M! c' B
26 f5 j$ s7 w; p7 x
3
W) L/ U6 T! \3 r% X" n4 g4
4 j* S+ Q# q5 N4 ?7 s# b5
- @5 P" i* R& l6
$ h* A# B8 V: M3 A# e' E' J* [7# T8 ]4 b" ?4 t5 f7 F
80 `' U* E4 U( H0 J/ l1 y
96 q' C2 o( j( ]; N( `
105 R0 n9 v4 R& h" Y& _+ N
114 b$ ^8 M- o# i: U- q
12
" G; E/ G6 n2 U! y$ m0 M: U' O13
1 J2 P" y: j# Q, ?14$ F4 h( S% e; T# V+ d9 q2 f5 h' }
151 P( }$ T( v7 }
16, u5 l+ [7 n+ @, u9 ?5 d3 Q' u- ?
179 S6 m0 x. X5 D; ?% `1 r( ~
18
; [9 K. p7 W' D: P( Y# Y19
) V7 Q& i/ I0 c+ @20
# Z4 E( ?! ~4 v0 V* J21, Y% `5 M: P* D2 a6 u
227 a' d+ q2 [+ N. J9 L# J" O7 D
23
+ I7 s, q. ^0 W. E24
. y" n+ |9 i2 H6 B* J: ]2 y25
1 B+ H0 E" j) \) X; ]0 V2 u261 R- K! G" p: ?+ L' `7 o" \
27
; s# u- U6 C1 Y7 E; \6 T283 l4 Z7 C; J, r
294 ]0 x; l( _3 [% K0 Q' s6 Z+ c
30 n: l a, F# j: h: H* K
310 g8 N% G: b7 j. z8 a; I% c9 m
32
' c( C( i& S( M9 W1 @$ y33
4 p& A9 ?$ S) X/ S7 S344 O+ V4 T3 p3 Q7 A
35
+ I& e: P- A R& n& C36* D7 a' ?( y- @, W
37$ {# }8 @- @; s( U9 }
38
5 _* Q) Y% X; e7 a) _8 I9 a39- G) Z9 ~) m* m7 C: v
40 E% F ?- e/ C# Y. n
41
- w5 B* p9 \$ v/ I' H+ R& @8 O42/ s- y. U$ c2 w3 l: r& E
43
) d0 k/ V; I2 N" t1 ]446 t U6 ~! \; O. L4 [' P0 h
9.3 区间类别
" J2 Q3 i+ Z- Q ]4 Y5 e' }9.3.1 利用cut和qcut进行区间构造
' k u) M! i9 I+ s 区间是一种特殊的类别,在实际数据分析中,区间序列往往是通过cut和qcut方法进行构造的,这两个函数能够把原序列的数值特征进行装箱,即用区间位置来代替原来的具体数值。
& _: Q" @6 U( Z' Z- Y
8 L) m. \7 i, s; I& j2 ]' mcut函数常用参数有:- E, J$ o5 `% l. c8 |
bins:最重要的参数。
, s: n1 T% o4 o如果传入整数n,则表示把整个传入数组按照最大和最小值等间距地分为n段。默认right=True,即区间是左开右闭,需要在调整时把最小值包含进去。(在pandas中的解决方案是在值最小的区间左端点再减去0.001*(max-min)。)
' |0 k% A3 z" f$ L; _: u4 \/ W也可以传入列表,表示按指定区间分割点分割。
4 g* [" R, W. h' ~ 如果对序列[1,2]划分为2个箱子时,第一个箱子的范围(0.999,1.5],第二个箱子的范围是(1.5,2]。
0 `3 |7 K' T: |8 ^( X0 q; { 如果需要指定区间为左闭右开,需要把right参数设置为False,相应的区间调整方法是在值最大的区间右端点再加上0.001*(max-min)。$ D# j7 O( ~* i7 ]/ `
. s4 V; r$ B& `; f; D8 P ys = pd.Series([1,2])* z" T. D1 M% g5 x% }4 N
# bin传入整数
7 y7 Q0 t8 ^! R, L) Z
' Q) x1 D3 o9 b5 u' i- ~pd.cut(s, bins=2)7 f( t9 {* S. r
Out[39]: 7 N1 F% j: D* q4 L% ~0 p' w
0 (0.999, 1.5]$ H/ e _+ s5 r: Y! w
1 (1.5, 2.0]/ g3 w g4 b+ a: H8 c' _
dtype: category
8 p7 j# k1 D6 w! E. P: qCategories (2, interval[float64]): [(0.999, 1.5] < (1.5, 2.0]]; S/ @4 ^8 g$ Z! {2 [
. U% _, L( z0 e2 t) h! ~pd.cut(s, bins=2, right=False)( X+ A8 i" E) V# w1 Z
Out[40]:
" F6 @. ~$ ]) H/ @0 X/ S. x1 t0 [1.0, 1.5)
7 p9 g0 L- \7 r/ O$ }1 [1.5, 2.001)
* V" [5 r: i1 m; q, bdtype: category
8 m+ v6 n7 P- ?3 _Categories (2, interval[float64]): [[1.0, 1.5) < [1.5, 2.001)]
" [1 y6 I s+ ]+ X' C& Z2 A i: P, T5 H" W$ ` P2 X( S& x
5 n* ]1 s5 C! h
# bin传入分割点列表(使用`np.infty`可以表示无穷大):2 A/ r, |7 `# s7 O p) d
pd.cut(s, bins=[-np.infty, 1.2, 1.8, 2.2, np.infty]) ~# e) u/ A, v* i4 q1 q/ N
Out[41]:
. P/ X4 k( j: D8 Q0 (-inf, 1.2]
5 ]7 d% M @' I/ T1 Q, p$ O1 (1.8, 2.2]4 m3 ~; f4 Y* B# e/ Q5 Q3 }" }
dtype: category
" h- h: D7 r" ?) T0 N/ ECategories (4, interval[float64]): [(-inf, 1.2] < (1.2, 1.8] < (1.8, 2.2] < (2.2, inf]]& K: k1 R6 j. o1 X- g
" H3 v- n! q& l& N' t* M18 k: J8 J: g2 i4 [" `9 y
2- L! `' S* ~% y
36 X, D$ |! S& [' k
4* Y* C4 [+ Z, M2 g r$ H6 M
5
( D+ n5 T8 N; W! t1 ]0 s. F6
, D: f# A0 F& {5 M7 c9 q0 Y4 \7 P: y* N+ E, m0 `
8) b; \: R9 E- A, X9 x
9
9 v- V1 Y* ^8 R9 n" S8 Q+ O10
3 i' x) X& @3 b5 |11
3 R" k) U( s1 S. ?$ o7 h12
7 O4 ~; k! l' E6 h) \0 K! J9 j136 f6 I @8 M5 E# _
14* i, \8 t7 {0 v e+ n
15( s5 o5 ?/ A+ X. O
16# T3 w/ f$ h# P) y& ?
17
+ K4 H- x; m+ d% u# x, V$ a+ N18
, y H# I% S' A1 t" j' y; [19! E: y! V# _; `+ l3 L
20
# n0 ?1 [' O0 p) u2 f21+ X* A2 {. V, h3 v# R
22/ I* t% d. y; o/ J
23
8 G% T3 f% Z0 {8 H# r24
. [: j0 O2 V6 s& Z0 y3 A' r, y25
, k. H I- L! |' H9 h# O1 ^labels:区间的名字$ A1 ?3 E. c7 }$ m3 P
retbins:是否返回分割点(默认不返回)
; G* b3 n+ q* \! @7 _默认retbins=Flase时,返回每个元素所属区间的列表" X! L1 z1 D- X; c
retbins=True时,返回的是元组,两个元素分别是元素所属区间和分割点。所属区间可再次用索引取值
3 F3 _6 D; P! @/ M5 m& K2 ~+ E* L1 w% A6 f, k; \1 J$ J
s = df.Weight, |% d( R5 w/ h
res = pd.cut(s, bins=3, labels=['small', 'mid','big'],retbins=True)
& `( k! E0 R) h+ ^; i0 [, W% @( sres[0][:2]# b2 a4 ^; X5 k' S
/ C/ I O/ z; E1 |Out[44]: " j4 I2 `/ ~: k( a4 k
0 small
4 q5 i% L4 F" O! N1 big l, q, M9 T: C* a- e7 q9 F
dtype: category, m' }7 M, _; _: D3 |; N
Categories (2, object): ['small' < 'big']) ~* C4 d# P* j/ `& x' Q* i
0 f. `7 j4 ?$ ?0 Ores[1] # 该元素为返回的分割点
1 F, M& ?; B U: _! k, @Out[45]: array([0.999, 1.5 , 2. ])
% ?' {; m# o- W! l* Q1
, ` T- T: `6 F' b* A, P2* a0 T7 q* T2 F I1 Z6 t- L: [% h; V
3
0 ]0 c+ O6 c" u& ^7 G9 b1 i4. Q6 R" R, V; _
5
% r# ^0 p L. W; d9 O6 G* F& O6
, J; K9 |/ U! g7
! O! ~, ], O% { p9 V82 V+ k6 t' |8 y9 N& k1 ~
9
; X/ `: n4 Y, q6 e109 d# f' i1 T5 y7 Y+ o
11! r* z8 d& t5 M) L6 g! y
12" C7 d/ L$ u2 w# d7 w
qcut函数。其用法cut几乎没有差别,只是把bins参数变成q参数(quantile)。$ t( ~7 ?7 G2 [. D$ p$ V: ?* @
q为整数n时,指按照n等分位数把数据分箱9 h4 w0 O5 A/ ` x! x" X/ r. W
q为浮点列表时,表示相应的分位数分割点。 G' E, a t) Q- p) |0 [
s = df.Weight
% G1 H7 N/ t/ d$ x" L1 p7 V+ p/ A0 g: R0 Z9 B" y, G% _
pd.qcut(s, q=3).head()
8 B$ Q$ \- e- ~: s+ t4 z0 o& P. [Out[47]:
8 ]; l9 M/ _9 ?9 P* s2 O) `0 (33.999, 48.0]
9 J) g/ K. t3 _5 N: V# @% h; I8 _5 C1 (55.0, 89.0]
- a* @# W8 s, L% O2 (55.0, 89.0]
& ~7 L& ~- y: N: {. h1 z) y& X7 O3 (33.999, 48.0]
- C* W" J6 w& ]; `4 (55.0, 89.0]3 P4 ^; T/ |- c. a
Name: Weight, dtype: category
5 u* K1 N& B! D) F% YCategories (3, interval[float64]): [(33.999, 48.0] < (48.0, 55.0] < (55.0, 89.0]]
' u5 d" ^5 \' u+ e& a' a9 `
# M5 w; y# a9 D. bpd.qcut(s, q=[0,0.2,0.8,1]).head()8 b( H8 D/ I$ `
Out[48]:
* {. y1 [% j; v5 |3 t- l0 (44.0, 69.4]
) m/ b1 c' c' q5 o- W$ U( Z* g5 Q3 f1 (69.4, 89.0], l5 V* P- L) j& N7 I. R# U( Y" S
2 (69.4, 89.0]
& [( X M# O+ f- p3 D3 (33.999, 44.0]
9 T" }( R2 k, E) N, Z4 (69.4, 89.0]$ b' k" Z, k1 m2 F' i3 _3 m
Name: Weight, dtype: category; y2 ~% q4 S4 l+ m, e, }
Categories (3, interval[float64]): [(33.999, 44.0] < (44.0, 69.4] < (69.4, 89.0]]
8 y/ j+ D9 m( b, t) Z* w5 z/ U" \8 G1 C8 a
1
1 d- p. @! s* [0 ]2
* Y' [4 c2 F% X. I% y; o4 K9 n6 {3
7 d) N0 z/ R; ]9 ~1 d o4
. O" q$ g% s" @: `; q57 M. Y, P6 U: A& V/ m. Y5 C
6# W ]8 I! O" x/ X- p4 B
7! j1 S6 O" r5 ]% x
8/ F) b. ~+ D" F& D$ ~
9/ ~/ L) X4 J4 X( i6 ~7 C
10 ~) ~8 {" T( u1 p0 d, R: j: `
11
" S! T$ Y/ x2 v% E124 A/ h" C! B4 l, C/ u& }3 `" G
13$ N+ U, u8 I, x8 ~8 j' b
14
7 T o" a# U/ f. ~+ Y! S15
, s/ _! g- {8 e6 q V16& Z+ t& T, \: X% v2 s5 B
17( K7 H0 O1 f$ K8 p* ^" R0 K
18* D) l- a3 i8 W
19
- q3 r+ v: r; {. C! d$ ~/ e20
" r0 o( T' d; E, q+ N/ x8 X21# Y1 K+ P9 M2 r% x
9.3.2 一般区间的构造, r; ?0 v: B6 P+ l. ~ [
pandas的单个区间用Interval表示,对于某一个具体的区间而言,其具备三个要素,即左端点、右端点和端点的开闭状态。
0 L+ Y7 V% S& D5 K* N- G
2 @& U0 ?( O- ]% x/ z0 F2 I开闭状态:包含四种,即right(左开右闭), left(左闭右开), both(两边都闭), neither(两边都开)。
- ~1 N t% f# ~' o7 Emy_interval = pd.Interval(0, 1, 'right')) f$ _6 J& p D
0 R* c) w8 q7 ^# {$ T9 n9 {, Rmy_interval" m" o/ r& A; k, K1 Z7 a8 Z. E
Out[50]: Interval(0, 1, closed='right'): Y4 T; X9 Y# F; \
1
, Y- n& V$ u, G3 i9 w7 V3 z2
" P9 d" R3 i8 R/ b* U3
8 v7 a5 Y1 T1 I" R5 H: s" Y4% u' B2 B u3 m3 `# A
区间属性:包含left,mid,right,length,closed,,分别表示左中右端点、长度和开闭状态。
/ h3 b! }4 ~7 f/ H+ t! q7 m使用in可以判断元素是否属于区间4 b: b1 c( F; d$ A3 x0 I
用overlaps可以判断两个区间是否有交集:
. N% Y6 o- ]& P; K0.5 in my_interval
9 G# U) I" B6 @9 U6 r7 e4 q3 e) a6 ^
- ~# Y* x( _# f$ c; KTrue t3 L6 n! Y9 t. `( ?
10 D- ]& b8 l# a' f5 R4 O
2
& j/ ~2 t8 ^, P( | [8 ^! L3
8 }9 T/ Q/ ?% m9 omy_interval_2 = pd.Interval(0.5, 1.5, 'left')
* K4 E% L( ?! F2 D7 qmy_interval.overlaps(my_interval_2)
) P7 S( a- Q) q2 M7 l5 N I9 s
% ~$ m3 k: r: m1 W* R8 |3 m( UTrue% w% ?9 y P! W( u+ y9 v# r: L
1
; g: \% P) X7 e6 I( q: `2
5 q0 B/ }3 K3 D/ H35 L U5 ~% C- a, c1 h1 w
4: L4 G+ |: ]0 G
pd.IntervalIndex对象有四类方法生成,分别是from_breaks, from_arrays, from_tuples, interval_range,它们分别应用于不同的情况:
5 ]& E1 [! s4 @3 T* @/ f: S7 Z; k3 `; w6 q1 w) T
from_breaks:类似于cut或qcut函数,只不过后两个是通过计算得到的分割点,而前者是直接传入自定义的分割点:/ ]+ N; B# x) G: N" K/ \, v2 |) ~
pd.IntervalIndex.from_breaks([1,3,6,10], closed='both')
2 E$ I- G" x+ s# n3 ]/ U7 f' x9 U
IntervalIndex([[1, 3], [3, 6], [6, 10]],5 V) Q" `) }9 l) w# H9 B$ ~
closed='both',
% E3 h$ l( }3 e n' F% k; d" J, O& P4 t dtype='interval[int64]')$ D" v& l f2 K) W' F
1# J. b! U; \& e
2
- s7 l0 i5 v5 n" E3, ^. K" _9 b! d# |0 @/ s5 r* q
47 ~. F1 V+ K+ U+ k% R
5
' ~$ M. W& w5 R1 V( B5 a2 L. X8 u/ jfrom_arrays:分别传入左端点和右端点的列表,适用于有交集并且知道起点和终点的情况:; }) F5 f1 o- k+ q9 y
pd.IntervalIndex.from_arrays(left = [1,3,6,10], right = [5,4,9,11], closed = 'neither'), b4 g2 ^' a5 e; X+ n! F% v
. U2 z2 p5 _9 e3 I0 a; H
IntervalIndex([(1, 5), (3, 4), (6, 9), (10, 11)],: Q- P; r) `7 ?
closed='neither',
( g; H- I6 _( p* [* J5 }9 u+ k# H dtype='interval[int64]') p' s6 g. {3 g
1/ a- n* W$ P+ ?! W# H: W
22 Q* j ~( p \
3- U- o7 e* s1 G9 P P, n
4+ m" S1 l7 [: }9 T2 m) Y
5
' M9 V( c8 \# nfrom_tuples:传入起点和终点元组构成的列表:' U$ p* @ q) c: l
pd.IntervalIndex.from_tuples([(1,5),(3,4),(6,9),(10,11)], closed='neither')" }: l! ~+ n' B2 l3 {2 s7 B
! c1 `( E/ E0 [- z4 aIntervalIndex([(1, 5), (3, 4), (6, 9), (10, 11)],
" v* L2 _! N! s- g; v" k3 a closed='neither',
/ I: O0 Z P* M: y* p+ e- Y/ \ Q dtype='interval[int64]')% V- M, W# T- W' D0 a& ]
1
9 k `! {. t0 G) \$ u# V24 b. Z. o# k9 @8 G- G- s* i
3
. |& G1 `% g# ]# F1 L4/ y9 w% N$ u- ^" ^3 s9 O
5
! j/ r) k6 w3 j0 ointerval_range:生成等差区间。其参数有四个:start, end, periods, freq。分别表示等差区间的起点、终点、区间个数和区间长度。其中三个量确定的情况下,剩下一个量就确定了,从而就能构造出相应的区间:
1 Z% W7 [3 |7 W1 g% Y( rpd.interval_range(start=1,end=5,periods=8) # 启起点终点和区间个数, ^$ ^& Y- d1 p) \
Out[57]:
6 a' r9 u% H* N$ l- h* b, ]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]]," Q* Y! J, C2 E d! A2 d* K
closed='right',
) \; `7 W$ i5 a/ Y/ i# s1 P; k" j- H2 v dtype='interval[float64]')$ E2 d/ g* `/ X3 l7 E
4 }, R0 {2 |* s" X8 j ~pd.interval_range(end=5,periods=8,freq=0.5) # 启起点终点和区间长度
1 B [' @, ]4 ]; D3 Q- b$ eOut[58]:
! Z' a' C0 v8 {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]],. V- h1 [- B2 v, H* ~0 F, u8 @
closed='right'," a0 r% V7 r& \ T* {
dtype='interval[float64]')$ F0 D: V% y/ G0 D
1 _* D) b/ z C6 m
2
* E2 L# ?! A% l5 F5 n2 t4 J3
6 E; V5 t6 o9 j( t. W/ [4
9 U& V) a( U) I" g0 D( a$ S$ W5
* i7 D# b3 a1 {+ @6
7 {' P' Q' d7 {/ ?( U: M+ ~; y7
$ l5 z Q9 P# V. x# g# i8
6 Y6 E8 C0 X( M/ D- F8 r90 F2 O6 M5 F1 Y0 s0 T/ |: I$ b$ G
104 ?( |( F* i; P( E6 U: k' i8 Y
11: Y" Z9 ]3 j0 B
【练一练】, r, H" I& T% S! f9 q4 ~8 `
无论是interval_range还是下一章时间序列中的date_range都是给定了等差序列中四要素中的三个,从而确定整个序列。请回顾等差数列中的首项、末项、项数和公差的联系,写出interval_range中四个参数之间的恒等关系。
3 H" B3 ?. ~ A: I F) E# t+ V: ?) D% ]
除此之外,如果直接使用pd.IntervalIndex([...], closed=...),把Interval类型的列表组成传入其中转为区间索引,那么所有的区间会被强制转为指定的closed类型,因为pd.IntervalIndex只允许存放同一种开闭区间的Interval对象。
: g3 Z+ m$ v0 Z9 x+ I" ~$ {# q. {! R# ~) O* ~0 T/ D
my_interval4 d# l: ^. y o4 O
Out[59]: Interval(0, 1, closed='right')
# I3 E3 x. p& g$ d6 H
% u9 l/ U c/ L1 z7 rmy_interval_2
# ?) f4 T7 {% s4 r' j4 |Out[60]: Interval(0.5, 1.5, closed='left')% [. ?4 q' p7 u- d9 T. x7 v+ w7 r
" [* g' _& L9 ?, ]0 g
pd.IntervalIndex([my_interval, my_interval_2], closed='left')
& }: f g- Q) }7 m7 N) l3 e5 {Out[61]:
0 ]- U, x! y, y* ^IntervalIndex([[0.0, 1.0), [0.5, 1.5)],' S2 B' i( ]8 V1 e' { Y
closed='left',
5 a2 L/ p" Z+ B* L' s dtype='interval[float64]')8 y& Z% c0 ?8 k9 V, X
18 i; }7 o; _, i) i' b, p+ C
2
2 x; \& I9 Y" [4 n/ I3
8 `0 U% b/ }2 v) y# V+ h4+ s2 R: H$ d0 y
51 X* m# H' `0 r/ T
6! U* L5 m, V9 K6 ^2 S& k
7
7 g& g& v- c# K& c86 O/ c$ `# g! D
93 u S* d" c0 y0 Z7 m5 R1 N
10
8 U% ~+ \; X8 M- z! v& a9 X6 ?! u11
4 G7 G8 W6 {+ W5 Q, ~9.3.3 区间的属性与方法% z7 c# p0 z% b/ I- X9 x" U: `
IntervalIndex上也定义了一些有用的属性和方法。同时,如果想要具体利用cut或者qcut的结果进行分析,那么需要先将其转为该种索引类型:
1 B0 | S# _3 M8 Z. ?( Y( K4 w0 x6 m: M% S' Q
s=df.Weight% L' n- D0 B/ D" D' ?% M
id_interval = pd.IntervalIndex(pd.cut(s, 3)) # 返回的是每个元素所属区间,用具体数值(x,y]表示
, p8 q$ ^7 {3 N( pid_interval[:3]# s% `8 ~1 [4 ^4 h# P/ p9 u1 H+ O' K
( y# I2 D& X. S8 \0 g
IntervalIndex([(33.945, 52.333], (52.333, 70.667], (70.667, 89.0]],
, ^, a* J- n4 B. d closed='right',
; q) r6 O) R1 A$ k name='Weight',8 b7 d4 ~" N' K
dtype='interval[float64]')' O7 ?$ N" T; G7 R; e& m! i) C
1, Q% Q, N& z8 O4 e7 E c
2$ B) K8 N6 P# p+ S. f: L3 J
38 n8 M1 N# b/ J3 m1 L) ^7 B
4
) [. L. _+ @4 c) m0 e5
8 ]2 O8 }8 L' k4 {3 {+ o9 R w6$ V+ ~0 y8 w3 N& J$ C: S; I
7
; e4 z3 ? Q( [8
9 x: J. L- y/ \) D1 l! w与单个Interval类型相似,IntervalIndex有若干常用属性:left, right, mid, length,分别表示左右端点、两 点均值和区间长度。5 E M* V: G5 ~* Z% Z+ [, G
id_demo = id_interval[:5] # 选出前5个展示( m$ V6 e4 T3 O
' s' H4 H' L1 F N5 t
id_demo: }& H7 p6 p2 @. q
Out[64]:
9 }( l! L! x: Q$ \: r" b( d& HIntervalIndex([(33.945, 52.333], (52.333, 70.667], (70.667, 89.0], (33.945, 52.333], (70.667, 89.0]],
+ l6 `; R) g8 r) V9 l closed='right',
" d* V; R7 J3 i8 F! N7 ]1 Z name='Weight',
' q! y% h! S- ]2 S dtype='interval[float64]')
5 X& Z Q8 `4 P1 D8 j% }, [, }! j
id_demo.left # 获取这五个区间的左端点
" ~2 l t. m9 J8 G( lOut[65]: Float64Index([33.945, 52.333, 70.667, 33.945, 70.667], dtype='float64'), [4 L3 l$ A6 P% E# ~! g
+ A: `0 @5 t- D/ |; {id_demo.right # 获取这五个区间的右端点
1 A" c. I( `* bOut[66]: Float64Index([52.333, 70.667, 89.0, 52.333, 89.0], dtype='float64')
, l+ n) M/ A' ?, `# P% E0 f6 L6 F* x+ u
* ~3 d9 p1 ~, Pid_demo.mid" K. g$ E! X& S1 ?& o% C
Out[67]: Float64Index([43.138999999999996, 61.5, 79.8335, 43.138999999999996, 79.8335], dtype='float64'): |6 n9 J1 m) v* m, v
* F$ o/ ~) _- z, {id_demo.length
% c$ n0 q2 P6 MOut[68]:
; v2 u9 ^: Z3 N3 Q/ p+ e8 RFloat64Index([18.387999999999998, 18.334000000000003, 18.333,
) `5 f6 a2 K* C 18.387999999999998, 18.333],+ l6 C; ?( q1 a0 R+ f, i# t8 }6 A
dtype='float64')
" x. E+ C2 g$ w) }
' F [2 E2 p4 r1
: Y# f# n/ I" R; w7 G. G. s2 P2) m4 E: j' ?' u* T7 {, f: ~6 F
3
/ V& e$ Z2 T" z44 c! s9 W/ J6 [1 U0 x
5( c3 u$ D* O% m2 P! N
6
% U0 A6 u" p! p& i$ x6 s1 E5 q70 H2 m* I1 R4 i, i8 Y2 }
82 ]) v Y" j3 m! c* u/ \
91 {% l$ b" c; F! t* B* w$ H
10
) ]: P" i; u5 j y5 Y; k3 _11
/ Q; S: }5 L' b12; I! ]2 S9 @# X/ s7 v2 f
13
0 b. z9 k( T; [/ z# }14
) q8 L8 h5 \* I9 [- n# R154 `4 R6 m* E% U e x7 a ^
16
' u1 h6 J2 {! ~2 G/ Q178 T0 I. F z8 j; ?: _
18
9 b) i5 ], j) ^( {3 ^; e19( y6 `1 R5 M8 F0 f2 M/ j. c
20
8 b. D5 @& O" |) o7 c& j; G21
/ m1 p1 N% u. S: a22" H8 ]& [: i4 E1 K# E0 a1 H) N
23& x! u0 q' \, N
IntervalIndex还有两个常用方法:
) ^# E* i! S* m" pcontains:逐个判断每个区间是否包含某元素) |4 g% C7 O. I. U5 M7 W
overlaps:是否和一个pd.Interval对象有交集。8 `) b- L/ A% G7 W1 `: c( C+ U
id_demo.contains(50)9 F, Y3 P3 B. L
Out[69]: array([ True, False, False, True, False])
& v* x: \/ T" T! H4 ]; }
* B7 j2 r& a `0 F8 K2 K s/ S6 xid_demo.overlaps(pd.Interval(40,60))
* T0 F6 e2 P$ ] e7 f8 x$ NOut[70]: array([ True, True, False, True, False])* f. _; Y( U2 b
1
1 \. W- \* M! h2 o7 u9 y2# Q! m+ \& Z x" y" b( ]
3
$ S2 i. a2 @2 T1 _6 E( [, o41 a/ h0 g3 T- e0 A- g; `9 q
5! J) _- h' {5 C: B- H* t! P
9.4 练习
( s6 }& @: l4 a+ U$ j$ KEx1: 统计未出现的类别5 j6 n2 \; S* M* }
在第五章中介绍了crosstab函数,在默认参数下它能够对两个列的组合出现的频数进行统计汇总:% Y& M# W1 I# ~# C: m4 c1 D8 k
: Q2 B8 E6 k8 ?0 [2 B+ M( Ddf = pd.DataFrame({'A':['a','b','c','a'], 'B':['cat','cat','dog','cat']})0 d& P! G+ r: s z- U3 G
pd.crosstab(df.A, df.B)
4 {7 {/ ~$ w0 w# V( P1 w" n$ b' {( _$ Y9 o# ]0 C
Out[72]: + \, @& U- y2 j- G+ U$ |1 y8 f
B cat dog
5 w, t$ C" @, k6 ~, h8 HA 4 P7 {# r9 m/ x
a 2 0
@/ Q" q; C8 a/ [b 1 0/ w% w- a9 n e& v4 G% c
c 0 1
( ]! ` `. {( r. E- r+ t1' J4 ]: y# v b8 Y# ^# c
29 \7 j9 D: R8 r% A3 A7 t( o
3" z& H8 l' ]/ d3 x; u @& z
4$ X$ w. x) }, _8 \4 S2 m; r' G
50 W. X+ n6 w' b( E, l. x8 W% m
6
, X+ Z! q" q- N. u" Q5 Y+ M7% u S1 _6 ]& }' D/ u, h/ q
81 [7 P0 `6 n% C1 ~3 e
9 ~0 [+ ]6 Y( N# e- y; r
但事实上有些列存储的是分类变量,列中并不一定包含所有的类别,此时如果想要对这些未出现的类别在crosstab结果中也进行汇总,则可以指定dropna参数为False:
6 Z" L; o: u) C+ W) K* t; A- U! d6 Z$ z
df.B = df.B.astype('category').cat.add_categories('sheep')3 ^: a7 E/ R# K5 S8 t8 g
pd.crosstab(df.A, df.B, dropna=False)2 o @8 [/ Q# N/ I/ y& @& B
% N0 x" B$ T+ ?
Out[74]:
% {; z4 O" w1 w1 h) N$ qB cat dog sheep
) z4 ? x8 F& L/ iA : C7 h) [" e) G# }0 g. j7 R$ q5 S
a 2 0 01 M; @3 g3 A) c* t) \
b 1 0 0
8 o/ m6 Y* ?$ W! t6 }0 Sc 0 1 0$ l2 k7 r2 h5 T& e5 R* e
1
/ j. i M3 v! q2
5 R+ X, H- U1 L6 h3 c4 {+ ?) x7 r" X8 G( ]3
- @. P- g# [- Z3 P4
0 Y* \& d) U( |8 k9 k: c5
$ k; g- D, a% A63 f; x" l' P, ]+ k! |
7* U+ o) a. p' _" R$ o2 T
81 B, N/ @: l( x- X# [
9, n" @- }! i6 m+ o6 E% ?, ]
请实现一个带有dropna参数的my_crosstab函数来完成上面的功能。' `7 P, n& z5 A1 s) I, S5 j* h3 Z
/ {1 X% o1 w% A+ @Ex2: 钻石数据集) y; x: z$ S6 y- j4 p
现有一份关于钻石的数据集,其中carat, cut, clarity, price分别表示克拉重量、切割质量、纯净度和价格,样例如下:( G. ~$ ]7 F# D n9 Q( d: x) U
+ s* E7 }# `2 n
df = pd.read_csv('../data/diamonds.csv') 6 Z0 \7 o- p$ z
df.head(3)
% P/ H* o0 W4 S! L4 d# D) @7 Q0 n( z2 F, `. ^ J2 H
Out[76]: 7 ~' y7 [% D0 ^+ G
carat cut clarity price
) {! m+ w9 k* T1 ~+ ~, g0 0.23 Ideal SI2 326
& R2 }; Q6 z: N* Q0 [; U8 E1 0.21 Premium SI1 326+ s+ C3 x7 K, D, f
2 0.23 Good VS1 327
' _6 f: E& b4 x; M1 R: B7 d1. A: p$ N% x0 U# |
2
+ j* G6 f1 `; t5 `4 K/ s k37 k, B# N: v* p3 I( S0 a+ i
42 C+ |& _' D: `% t
5
( x$ A# k) e1 b1 |( @4 P8 I65 k& Z7 p( V' S! g& x: A2 B" ]. c6 z% H
7
, |0 @( _* k" d) q ]' w8
6 d5 o) t E2 g) B分别对df.cut在object类型和category类型下使用nunique函数,并比较它们的性能。0 O# z" z5 m6 u# C( K
钻石的切割质量可以分为五个等级,由次到好分别是Fair, Good, Very Good, Premium, Ideal,纯净度有八个等级,由次到好分别是I1, SI2, SI1, VS2, VS1, VVS2, VVS1, IF,请对切割质量按照由好到次的顺序排序,相同切割质量的钻石,按照纯净度进行由次到好的排序。$ ^9 q% F# p1 v% A
分别采用两种不同的方法,把cut, clarity这两列按照由好到次的顺序,映射到从0到n-1的整数,其中n表示类别的个数。3 u' D, O7 b; g6 u% H: x( h
对每克拉的价格分别按照分位数(q=[0.2, 0.4, 0.6, 0.8])与[1000, 3500, 5500, 18000]割点进行分箱得到五个类别Very Low, Low, Mid, High, Very High,并把按这两种分箱方法得到的category序列依次添加到原表中。
H0 S; T3 _& [3 U( ?2 m第4问中按照整数分箱得到的序列中,是否出现了所有的类别?如果存在没有出现的类别请把该类别删除。
5 m1 d7 W/ f' _# b2 |& g对第4问中按照分位数分箱得到的序列,求每个样本对应所在区间的左右端点值和长度。
& {, [/ `# {. K: i9 f0 u1 a先看看数据结构:
* |9 B$ i1 H9 x: m, ?6 w( Q* J7 Z ^( m" M% m. @1 t1 q% k
df.info()
" H7 m4 O9 p0 {! rData columns (total 4 columns):
" Q3 }. U: S X; Y" |# r1 b # Column Non-Null Count Dtype
- d& \3 [" b. i2 B; d--- ------ -------------- -----
7 l( O. z) ?# _. ]" w+ P5 N& } 0 carat 53940 non-null float64
4 V9 l" e8 X! ~; o: D 1 cut 53940 non-null object
5 `. A- a, F$ t* y+ R0 e 2 clarity 53940 non-null object
5 E5 b0 H/ @4 Q) u* J; t 3 price 53940 non-null int64
# R4 w. k% l1 F: Cdtypes: float64(1), int64(1), object(2)3 |7 i3 u" @. f1 k0 {
1+ P) b; B; E' z
23 I6 K/ }! J) T
34 b/ p% s/ W: c& P
4
: |4 A% ] g+ c2 ^3 C5 X5
# q" L3 h" e6 b6 h- O9 @* T. m6
5 j# `3 j8 F) ]$ r e0 F7* [, ]7 y6 d- ~8 b6 U/ z* s7 A' _. \8 Z
8
: t2 {* ]; e( t8 D; f' V. O' d9
; t" A7 {& H( I比较两种操作的性能
! ]) C" |" w' }; S- c%time df.cut.unique()
# e# K7 r5 D. O
' L- \! p1 k& }; c5 Z' J9 cWall time: 5.98 ms( I; x5 j4 r/ K7 Y; @& ?5 `
array(['Ideal', 'Premium', 'Good', 'Very Good', 'Fair'], dtype=object)( f8 @9 e- W6 Q4 b1 m" F( c
1
- G+ m- Q# T, d, {) l! A0 g& z2
7 k4 Y1 o, w3 n" j6 @$ O3: e3 y' L* R: l, C0 B
48 Q3 U5 L! ^9 Q! H" o! m; k
%time df.cut.astype('category').unique()) E+ y' J% @$ b% k. U" l
' z. `0 b4 k, U( ?8 I+ z5 oWall time: 8.01 ms # 转换类型加统计类别,一共8ms
* f- V+ W7 j' j5 {) V. e9 S6 f['Ideal', 'Premium', 'Good', 'Very Good', 'Fair'] d' A) w4 u5 p$ s. B3 _
Categories (5, object): ['Ideal', 'Premium', 'Good', 'Very Good', 'Fair']6 H/ l9 M9 C' S: u' n- U
1: g' D1 @8 g$ P3 k
2+ u: f9 g& J) Y) v: n
3
& ^. e, m( r5 [2 r4# s3 P; k: j2 e( S
5
7 |5 d6 H# d, Idf.cut=df.cut.astype('category')
) {7 `# ^ Y6 j& l" Y%time df.cut.unique() # 类别属性统计,2ms$ x) |4 j# p/ k$ w# m
`5 A$ c v: M2 bWall time: 2 ms: A, m! j/ {4 Z! K: `- V. R* B
['Ideal', 'Premium', 'Good', 'Very Good', 'Fair'] Z6 w7 J: a% L6 @# ?2 z9 f
Categories (5, object): ['Ideal', 'Premium', 'Good', 'Very Good', 'Fair']
! V; {& P5 ^+ S7 q) E& y6 W9 d$ @' Z1! z z" `# D# W
27 V8 J; ~0 V9 E! Z* m' D
33 R8 b& D! S' {5 m- m$ l" \/ g$ d
4$ J% |, p# B% D- s
5
/ a& r" x1 N, F7 B8 A) S1 n6: r4 d7 ~ q+ s4 [% M5 T
对切割质量按照由好到次的顺序排序,相同切割质量的钻石,按照纯净度进行由次到好的排序。
7 N4 s+ a) Y; J0 |ls_cut=['Fair', 'Good', 'Very Good', 'Premium', 'Ideal']
1 P, u; Z% D3 N) F& R! Zls_clarity=['I1','SI2', 'SI1', 'VS2', 'VS1', 'VVS2', 'VVS1', 'IF']
0 p' T6 D. Z0 C$ |" ^df.cut=df.cut.astype('category').cat.reorder_categories(ls_cut,ordered=True) # 转换后还是得进行替换
* Z8 f( `1 E+ q: e" `df.clarity=df.clarity.astype('category').cat.reorder_categories(ls_clarity,ordered=True)( |3 }% A V8 m& R5 }
4 s# O3 I5 T( u# L$ |) B7 a+ W5 ]
df.sort_values(['cut','clarity'],ascending=[False,True]).head(3)
# `; h H2 j% o) _5 j T) j
# S9 B* l( ^) @6 F9 } { carat cut clarity price6 m( p) `5 Y% ?
315 0.96 Ideal I1 2801$ I) `/ ?$ K" O2 Y+ W
535 0.96 Ideal I1 2826! e, K/ H+ t) }
551 0.97 Ideal I1 2830) F: _& w' j' S0 a, E! O+ I% G
1
" @8 r# z; e+ \# K6 t2
2 g7 b |/ Y8 E1 _ v' }( X$ ]3
$ [9 |! }" b" X& E; Z2 y/ d) L4: a7 r0 d* u& D, n) D
5, {+ b% ]) P" Z {: G/ i
69 R2 Z3 }1 H; V" x1 _9 X/ G4 d/ _
7- y5 V6 w1 u" d7 {, F
8
0 F6 |: a! q4 a% s+ P( z9
5 U- u0 \5 f; [4 ]8 w10) e( v9 X9 g i' A
115 d4 z9 u, j% C" {4 x0 U
分别采用两种不同的方法,把 cut, clarity 这两列按照 由好到次 的顺序,映射到从0到n-1的整数,其中n表示类别的个数。3 X+ v+ D( W5 }9 l2 ?; D R& M3 M
# 第一种是将类别重命名为整数- U" J4 @ |5 z: W3 \9 E
dict1=dict(zip(ls_cut,[x for x in range (4,-1,-1)]))
, n- k, |, b* x) k/ Q% bdict2=dict(zip(ls_clarity,[x for x in range (7,-1,-1)]))
. A4 _, M2 j( I: \. L: g, S5 W8 X+ S. B. ?' }
df.cut=df.cut.cat.rename_categories(dict1)( |; \; F: s$ \4 V, j
df.clarity=df.clarity.cat.rename_categories(dict2)8 H% A$ R+ K4 r+ b; Y
df.head(3)
1 e# [+ [) g/ T# i* R$ Y( n3 r8 h) ~1 O2 V* ^' d n
carat cut clarity price2 N# I, u* _! S7 k& G
0 0.23 0 6 326) e5 B3 C. w6 P; N# q! i8 j$ \
1 0.21 1 5 326 q% c. K' m, c3 g/ T5 I
2 0.23 3 3 327
% d* d' z! v# H, i1/ \0 X2 r0 J4 f$ O$ T( i" n3 `& W9 _
2
\" C) W) Z& Y; g- ?3
/ k( ?2 h! {" x+ `$ u9 J1 X% {9 C. a4
+ B! j$ a- P: ]6 C# q5
3 M+ ^: w1 i4 [1 P6
0 g" ?) e6 K) p4 ~7 Z" v7
! C; Y. }1 Y8 j J0 {6 n8
( A V" }$ ?* G9 J/ l2 Q5 A9
1 I% N/ ?/ T1 J# Q10! Q% Z0 ?0 ?, _: V( ?
11
4 X: ?! y- a. P( ?- t' k126 l/ f4 B+ \5 Z1 J
# 第二种应该是报错object属性,然后直接进行替换
3 g" b! i% Q0 k9 [7 C! ydf = pd.read_csv('data/diamonds.csv')
8 O( G* `/ k$ S4 X {5 q- Sfor i,j in enumerate(ls_cut[::-1]):1 S% u' I4 O0 \6 c: l3 i& `4 C( n
df.loc[df.cut==j,'cut']=i
7 p8 R+ N: o* D, j8 c2 ?. n
7 R, k" b. d% y$ D. B) |! bfor k,l in enumerate(ls_clarity[::-1]):
2 v$ t7 Q7 k' h* B df.loc[df.clarity==l,'clarity']=k8 \. |& g1 r- Y3 B" j0 t
df.head(3)
3 ^- m4 ~0 M/ j1 B
& ^) [* _2 G, i! y w carat cut clarity price4 ` s( w: s7 u8 j. ?+ q. D
0 0.23 0 6 326# D* p* @ q/ p) ` r( d
1 0.21 1 5 326
, B* g2 V/ v7 `4 B2 0.23 3 3 327
7 W4 ?. v' i7 ^1
8 `& r5 [* y! R/ R' z$ S6 I21 X0 A" Q/ K- R. k$ x) I% h
3
q6 g( d6 U3 ~0 J6 x* `4
3 K6 I7 L' s& P# Y/ r a0 Z5
, h7 q/ I' O: w6
+ F# Q' f4 L# D" r% w% @5 u. F; @71 U' F, G6 e7 A, c/ `* K% M
8
% g/ \# {4 M( t9
) p$ a; d; @ [10
1 c; ?9 t" e/ R9 C11
2 t& A F, S3 T6 y$ ?12 W+ g& u, m: z* l9 C9 ^
137 \- h) ~2 M$ {7 s4 Y6 r. b. r
对每克拉的价格分别按照分位数(q=[0.2, 0.4, 0.6, 0.8])与[1000, 3500, 5500, 18000]割点进行分箱得到五个类别Very Low, Low, Mid, High, Very High,并把按这两种分箱方法得到的category序列依次添加到原表中。" _6 f, y# l( ]6 O8 j
# retbins=True返回的是元组,第一个才是要的序列,第二个元素是分割点
0 g7 c$ q4 ~/ f" s/ ^2 xavg=df.price/df.carat
9 f% ~$ l0 o) X
5 l2 a! S( f) Adf['price_quantile']=pd.qcut(avg, q=[0,0.2,0.4,0.6,0.8,1],: z/ O" J# Q6 [2 ?/ z
labels=['Very Low', 'Low', 'Mid', 'High', 'Very High'],retbins=True)[0]- h- H2 J' H* Z. q$ p
9 `7 X# A2 `, r: U8 E: f! x4 b
df['price_list']=pd.cut(avg, bins=[-np.infty,1000, 3500, 5500, 18000,np.infty],
3 Q/ L+ ~8 b7 Z labels=['Very Low', 'Low', 'Mid', 'High', 'Very High'],retbins=True)[0]
8 [- J+ P# ]0 Vdf.head()4 w9 L+ j9 [8 o
, ^7 E) u. v- N7 _1 B, r+ t- X$ c carat cut clarity price price_quantile price_list# O$ r9 E& B' _, V) s8 B; S ~! f5 o) m8 Q9 N
0 0.23 0 6 326 Very Low Low
6 g5 y; X8 \ u1 P6 \1 0.21 1 5 326 Very Low Low& d; o: |# |( L& W4 |4 z* V7 J( B
2 0.23 3 3 327 Very Low Low+ }& @3 m. b8 g' g/ b) ^- \
3 0.29 1 4 334 Very Low Low* K) w! Q+ z& c: T7 F4 ~
4 0.31 3 6 335 Very Low Low
% X$ J, i& K' ^+ @6 _
1 {& `! R0 z& Q7 p [# Q19 f7 H- r9 E! L4 x
2) ` d+ P7 m6 B& t7 y( d3 {- ~' f
3
5 ]) z% x, r! K- Z5 g2 X( J4! ^4 m6 t# M0 k4 Q8 k
50 h. [* C. Q% `3 W
6
V" C0 F0 `9 h; T/ h7; W: Q: @; A1 X% H7 I
8
# W% j. Z6 ^3 f. L0 Q! D- W& M& I9. s# z6 v) b8 G7 s0 x
10
8 x3 K/ d% ]* y* h/ R9 J11* N/ \/ [ O& Q! D$ }% Q0 x
12
. y U' d5 ?* f( `; x4 \' N130 b Z8 X' y$ `* a) t1 R7 b
14 y$ E. w; C3 j9 ?! z; S
15
2 e( D5 \+ Q9 M; E; N* i16
$ e* C! B. ?: Z3 W1 g) ~' `! n分割点分别是:' d$ m3 B6 {4 A3 D7 y& K+ c
# w; L6 z8 a7 Uarray([ 1051.16 , 2295. , 3073.29, 4031.68, 5456.34, 17828.84])5 Q9 q' `2 a4 @& e
array([ -inf, 1000., 3500., 5500., 18000., inf])
$ m E9 j& ]6 r6 c: t1) h, a& u+ d- [. t- ] i
2
* d7 {1 j' C2 c. k第4问中按照整数分箱得到的序列中,是否出现了所有的类别?如果存在没有出现的类别请把该类别删除。/ S ~& |2 E8 G4 a+ E% P" { S. c+ ]4 }9 R
df['price_list'].cat.categories # 原先设定的类别数
6 o3 l4 ], {* H9 G/ JIndex(['Very Low', 'Low', 'Mid', 'High', 'Very High'], dtype='object')" z& U1 }) g- f
: V o; s5 c4 M" B9 A$ ^. l1 S
df['price_list'].cat.remove_unused_categories().cat.categories # 移除未出现的类别$ w, ^) ]+ Q" W0 m
Index(['Low', 'Mid', 'High'], dtype='object') # 首尾两个类别未出现
* h( i8 w2 e( z18 r/ `1 u& F* m* H$ ?( B# g5 h
22 l, @( A& ^7 L" ]& R
3+ H. `! O- n7 R4 T1 g+ `/ X4 ?, k1 S
4( X2 G0 i! W8 F2 i
5( u, @4 T! G9 u" }% P1 N( \3 C
avg.sort_values() # 可见首尾区间确实是没有的
5 a0 |% y9 D. O31962 1051.162791
# G; W+ H' Z, Q( N" E9 n15 1078.1250009 j! X- {$ O4 W( q1 v" o2 ^
4 1080.645161- @' t+ |% f- E" g9 U
28285 1109.090909: W H" c5 ^4 v/ j, x2 G0 ~. ], z
13 1109.677419
8 o' O& ]0 B4 A6 P7 j+ J1 H ... ! ?4 P$ o# h+ M) P/ |. X0 c3 w
26998 16764.705882! F! ^% l* j, t
27457 16928.971963
9 m3 E u# x5 ]27226 17077.669903 G# O: g# G m/ `2 J4 m- }
27530 17083.177570
7 H- D# R: O0 k& W' {: O27635 17828.846154
0 {6 E3 c% n) w/ V. M1
2 P$ ^ x, o( K4 Y; K29 m! I9 h% t' \% E2 W* n8 I& j
3
. o9 I5 l9 y# P6 d0 H4
: o) B' b8 {+ a7 \# J52 l7 h1 J7 q1 S# `: x: f
6
( A4 \. D4 k7 ?: P5 P4 {9 ~! H7& @( ]* \) h% U& z4 \
82 R* i5 ?) u5 o8 \* J# l
93 v$ }' g6 W; R
10
: m* M3 _$ m$ v# ~8 p114 d; o" _% r+ X# k' L
12
; ~2 u& O; `3 P$ `2 [对第4问中按照分位数分箱得到的序列,求每个样本对应所在区间的左右端点值和长度。
) C& X! x& G9 E5 p- M. M: }3 o( g$ U# 分割时区间不能有命名,否则字符串传入错误。+ O( [. J# o( o6 n. d
id_interval=pd.IntervalIndex(
- S0 A( ~: I5 J# L* C8 b pd.qcut(avg, q=[0,0.2,0.4,0.6,0.8,1],retbins=True)[0]9 c; P( Y7 n2 O0 y6 c
)
0 E- }" k H/ |" [/ `) }id_interval.left5 ]7 l2 C% n( ]5 S" j4 M8 ^
id_interval.right3 M8 `& F- N# U! D
id_interval.length
5 k$ F( m& L: C5 }0 Z) j1
* e$ |5 C- D# l2. Z3 X5 c x; A: ]0 f
39 a# \/ i+ F: H7 {5 G& A; \
4
. E& ~5 e$ `8 [; v! l+ Z5
% d$ |- O0 P' C: L' a3 a6
9 [$ F4 @9 d; l0 t. Z( F3 \7- \5 n& b2 B; h) N# F9 D
第十章 时序数据
9 X) {& n0 {, ?1 _5 F6 k6 b9 jimport numpy as np
6 U2 n: ?# y1 ~2 A6 {* _, C oimport pandas as pd6 \4 c) s3 p Z" s K, r$ k
1
/ s& U9 }7 V, _; k3 j2 J% r- j9 w5 l3 {# I5 X9 B7 ^# n/ x
5 i, p' a- r! g; ]" G
I- q" A& b) |* ^$ _; C% g! D
10.1 时序中的基本对象
0 ^6 S% e! a) Y, ~6 h) @; J 时间序列的概念在日常生活中十分常见,但对于一个具体的时序事件而言,可以从多个时间对象的角度来描述。例如2020年9月7日周一早上8点整需要到教室上课,这个课会在当天早上10点结束,其中包含了哪些时间概念?9 _% }4 ?( p* |0 y/ ]4 K
6 ~' G6 ?. X7 P* u- w7 t会出现时间戳(Date times)的概念,即’2020-9-7 08:00:00’和’2020-9-7 10:00:00’这两个时间点分别代表了上课和下课的时刻,在pandas中称为Timestamp。同时,一系列的时间戳可以组成DatetimeIndex,而将它放到Series中后,Series的类型就变为了datetime64[ns],如果有涉及时区则为datetime64[ns, tz],其中tz是timezone的简写。9 [0 x$ u2 b1 a+ e* ~
% V" c5 z1 v1 S- ?9 t2 b4 {
会出现时间差(Time deltas)的概念,即上课需要的时间,两个Timestamp做差就得到了时间差,pandas中利用Timedelta来表示。类似的,一系列的时间差就组成了TimedeltaIndex, 而将它放到Series中后,Series的类型就变为了timedelta64[ns]。- q B& n) ^' H3 D7 M1 W2 X. |
0 \" q8 K1 {4 P- w会出现时间段(Time spans)的概念,即在8点到10点这个区间都会持续地在上课,在pandas利用Period来表示。类似的,一系列的时间段就组成了PeriodIndex, 而将它放到Series中后,Series的类型就变为了Period。
) R% ?5 n! W/ n4 X! W% }+ N$ ~1 R# r- ~' ?
会出现日期偏置(Date offsets)的概念,假设你只知道9月的第一个周一早上8点要去上课,但不知道具体的日期,那么就需要一个类型来处理此类需求。再例如,想要知道2020年9月7日后的第30个工作日是哪一天,那么时间差就解决不了你的问题,从而pandas中的DateOffset就出现了。同时,pandas中没有为一列时间偏置专门设计存储类型,理由也很简单,因为需求比较奇怪,一般来说我们只需要对一批时间特征做一个统一的特殊日期偏置。
# r! D2 p4 p6 z! g! }2 T; [: b& {: `4 |3 t- q( A
通过这个简单的例子,就能够容易地总结出官方文档中的这个表格:* R. O- d' x3 C' z' E
0 o+ H ~$ R& ?( I3 [7 |, y& j概念 单元素类型 数组类型 pandas数据类型4 K4 i4 Q# W) A1 D+ o
Date times Timestamp DatetimeIndex datetime64[ns]
& t$ P3 V' V2 }6 c. J4 kTime deltas Timedelta TimedeltaIndex timedelta64[ns]
# r/ B' B% \. x# _/ n: {* c* ETime spans Period PeriodIndex period[freq]
( b; q7 U9 C9 ?" ^Date offsets DateOffset None None
9 Y4 T S- Q: I; }- [! T 由于时间段对象Period/PeriodIndex的使用频率并不高,因此将不进行讲解,而只涉及时间戳序列、时间差序列和日期偏置的相关内容。, q2 F: ?' N4 }/ z
/ V2 q9 k, Z- b* R! d# z& X/ C10.2 时间戳
0 T7 e! K7 V, A) U10.2.1 Timestamp的构造与属性. M1 M" ]% c0 `5 A6 z8 Q! T) ?
单个时间戳的生成利用pd.Timestamp实现,一般而言的常见日期格式都能被成功地转换:9 i4 z {1 m- T
% x. Z' @3 w+ C- M$ {3 }ts = pd.Timestamp('2020/1/1')! y/ P1 s7 F6 n: p; c7 w7 H5 Q
) \: L$ @; `1 Z6 Z6 ?: `+ I
ts
5 Z3 Y5 f/ |% ?Out[4]: Timestamp('2020-01-01 00:00:00'): Q& ?' h* x% |$ s% R
5 L, Y3 e2 m3 C2 s. u* t2 ]7 _ts = pd.Timestamp('2020-1-1 08:10:30')
' V/ y) ]! ^+ K6 k/ Y" G9 [+ C1 s" y0 B" Y
ts
* U. x |5 L3 ?3 a* F. sOut[6]: Timestamp('2020-01-01 08:10:30')
3 X: B- E* t# q# e+ X1; [+ Z; O. x+ a. M+ J3 Z
24 p0 y; a) W2 |$ f
3
7 v0 h7 u7 G( z" i% c40 r, F# q, D/ B& j9 l
5
) [. J" ^: h" Q. S* y6
( S4 [7 F' n; J/ p) [' \4 v7
' _3 B3 F! T$ V, P/ j8
- s& e. o, W) S# C, ~: S9( Y. `, Z" ~( h" d# W3 f
通过year, month, day, hour, min, second可以获取具体的数值:
+ T* ?2 l8 n4 R
# [# @0 `) H. I b0 `# ]ts.year+ |/ W; m2 t+ H
Out[7]: 2020
0 L$ l v* O) T3 g2 w/ r0 p2 l$ y9 P: H. f+ }& b8 M
ts.month4 A, A1 f9 R& V) O
Out[8]: 1
/ _' l/ g& g# \8 u: ? e; }/ }7 \2 j3 W- @
ts.day
/ b- }/ J7 H" m! NOut[9]: 17 Y# o( @3 h+ ~5 [( m
0 [+ a, a9 I1 tts.hour
, c5 l7 b4 _, ]" s, IOut[10]: 8/ K. B( R* K A! g1 J4 a# n
' r5 r( ~" I3 ~ `( J8 L! N
ts.minute* ~: }2 Y$ x- \' K9 Y3 x) \/ l
Out[11]: 10
% b. f* O+ C. Q& a. M( T' e- R, G2 e9 x! d
ts.second
) J( J8 R+ w$ j: n8 N% \3 aOut[12]: 30$ t) X# {+ |, u$ M+ G l2 m
& C$ `5 q* h' C; k" b/ g2 v l+ y1
' E& A" [# `9 c; D* D2
# R0 x$ [7 \" X" t30 l# F, ~' W8 N
4$ T6 v2 H8 U i5 t" ^$ `! H* `6 A$ h
5! Z, v* Y1 @# m! @& r
69 y9 Y" p# J1 T) B3 O( E2 t" p% B
7
) |: H8 d- y. o. m' k8, s. u1 F# B+ y1 a
9: y6 w- }0 P4 H8 Q+ V# \; b+ f
10
) w( k' d5 z. W2 ]- Q+ V# A11* U" c5 @% J1 R% r1 x, j/ P
12
9 ]3 u5 U5 \" U+ J/ t13
A6 _, T. _ \. B4 ~& }, L' y14( Q& T8 l5 O, B4 U; q
15, u4 L3 K! l9 g) ~
16: G+ n2 [4 J b
17* m- e* |0 I7 X+ L$ n; _( Y" D
# 获取当前时间$ U& w. w, Q7 P7 z' b# n( e- j c
now=pd.Timestamp.now()
$ J3 ?0 K0 j3 z- \7 s: E18 J0 I5 d a/ W f6 ~7 ~% `& v6 T
2' [9 C7 S$ ^" y! r5 a
在pandas中,时间戳的最小精度为纳秒ns,由于使用了64位存储,可以表示的时间范围大约可以如下计算:
) \5 Z1 r7 G' G6 [2 VT 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)7 R9 e: }% Q: ^
TimeRange= 3 w U9 e' M D
10 # z# s$ C" a O3 K. Q
9
4 Y% c- t& A3 [ ×60×60×24×3654 l# L: t5 F9 L8 q: k$ S
2 ( |7 M$ f- }8 r& F
64, b4 S8 U% v) D
0 L) v% a$ J$ g* S5 ^* ~$ N
9 a4 b* V9 f" [8 O: \ ≈585(Years)# d5 W. E( ]& f( U2 P7 x, }3 E
/ K- r+ A: z5 J% d通过pd.Timestamp.max和pd.Timestamp.min可以获取时间戳表示的范围,可以看到确实表示的区间年数大小正如上述计算结果:
6 L J) X& T$ _& F! {
; y& ^' z! t3 O3 Jpd.Timestamp.max
3 p* U5 L/ U$ D( f1 _# U9 {4 IOut[13]: Timestamp('2262-04-11 23:47:16.854775807')- V+ E% G8 Y; }
8 O' a3 Y/ s. A6 i: Z
pd.Timestamp.min6 u6 d! F, |6 Y- V
Out[14]: Timestamp('1677-09-21 00:12:43.145225')- p& B8 i8 z( K
6 N1 f+ c, a9 R- ^) x0 e; {/ epd.Timestamp.max.year - pd.Timestamp.min.year( C3 G3 q M D; X0 p* V
Out[15]: 5858 O1 y/ j2 ?4 A( x8 r' [% ^8 a
1
; T* U2 J9 B- Z `$ L3 @( y& J2; F/ D2 D# E; v0 ^
3
3 d1 _/ f: m# z4* W0 b# [7 ]' J& f( Z, ^
5
$ d0 G; q" ?' T- J6$ u( }& T f! N! e5 x* K/ I Z$ g
7
# {* e# _' a: I1 d8
. w! ` e6 {9 n" W10.2.2 Datetime序列的生成/ N4 Z( c! K' t3 D$ H
pandas.to_datetime(arg, errors='raise', dayfirst=False, yearfirst=False, utc=None, format=None,9 r% L5 g7 |8 u0 c! g/ t$ p$ Q3 ^' I9 @3 a
exact=True, unit=None, infer_datetime_format=False, origin='unix', cache=True)1 U2 ]+ p, |5 g8 |2 b0 v' A
13 ~/ ^7 u- U7 [/ Q) z) |+ _
23 @1 H% c- b9 G( Z7 c& ?/ |2 [, g! }
pandas.to_datetime将arg转换为日期时间。
, e. ?! H. E) B7 C/ k) g5 c) Q% R: q2 n2 c- I3 t
arg:可以是argint、float、str、datetime、list、tuple、一维数组、Series、DataFrame/dict-like等要转换为日期时间的对象。如果提供了 DataFrame,则该方法至少需要以下列:“年”、“月”、“日”。8 k$ B* Z6 |2 m( g. h
errors: p( u+ Y. D& @
- ‘raise’:默认值,无效解析将引发异常
: D/ S- X+ ^. l; C4 Y7 E- ‘raise’:无效解析将返回输入* k q- T3 O. l1 E) F) G: h
- ‘coerce’:无效解析将被设置为NaT, Q: x" E/ z9 y& c; d! {
dayfirst:如果 arg 是 str 或类似列表时使用,表示日期解析顺序,默认为 False。如果为 True,则首先解析日期,例如“10/11/12”被解析为 2012-11-10。如果无法根据给定的 dayfirst 选项解析分隔日期字符串,会显示警告。7 }0 @' T9 [1 d5 y* O
yearfirst:如果 arg 是 str 或类似列表时使用,表示日期解析顺序,默认为 False。如果为 True,则首先解析年份,例如“10/11/12”被解析为2010-11-12。无法正确解析时会显示警告。(如果 dayfirst 和 yearfirst 都为 True,则 yearfirst 优先(与 dateutil 相同)。)
8 P" o4 j2 g) p" Eutcbool:默认None,控制时区相关的解析、本地化和转换。请参阅:pandas 有关时区转换和本地化的一般文档
; i3 U1 j# t, e- D, \2 Uformat:str格式,默认None。时间戳的格式不满足转换时,可以强制使用format进行匹配。$ C( f/ d" I% v: Z
unitstr:默认“ns”。它是arg (D,s,ms,us,ns) 的表示单位,可以是整数或浮点数。这将基于原点。例如,使用 unit=‘ms’ 和 origin=‘unix’ (默认值),这将计算到 unix 开始的毫秒数。
3 v4 [9 ]! \0 ?# q+ o% i- Ito_datetime能够把一列时间戳格式的对象转换成为datetime64[ns]类型的时间序列:. [( T; n+ A8 r8 b1 m
pd.to_datetime(['2020-1-1', '2020-1-3', '2020-1-6'])( Q2 T/ E/ E% E3 A% F7 ^
% P3 J" V) k7 C' ^" T0 q' }
DatetimeIndex(['2020-01-01', '2020-01-03', '2020-01-06'], dtype='datetime64[ns]', freq=None)0 }' Q. C6 e" ~) b3 c2 O
1 m9 _9 `; R4 c: |
2
4 t# X6 l! q$ J7 _3# E% M0 p9 D: d
在极少数情况,时间戳的格式不满足转换时,可以强制使用format进行匹配:1 |! K! R& A- m
% e9 L$ S, p i4 `* T
temp = pd.to_datetime(['2020\\1\\1','2020\\1\\3'],format='%Y\\%m\\%d')
" {6 G) A0 k: P& o# `# {0 r! @temp
/ }7 |$ O( U) f9 S# c$ H# R3 S* q/ |
DatetimeIndex(['2020-01-01', '2020-01-03'], dtype='datetime64[ns]', freq=None)% p) C3 t" T y' X. h, S. c
1, j0 b* s1 Z) X$ ^" ^
2$ S7 a! V) A3 ~5 Q, n0 x. x
3' x$ x& J, u3 B8 U
4
/ R" P+ M7 w$ Q6 b9 P5 `; S" | 注意上面由于传入的是列表,而非pandas内部的Series,因此返回的是DatetimeIndex,如果想要转为datetime64[ns]的序列,需要显式用Series转化:6 @" W' J( K, ], d- j. w$ n5 P
+ N7 A4 N9 S& p# k# i, E- l) P; l. lpd.Series(temp).head()
: f3 Y# [" Y. @
8 @+ y4 m9 I6 M7 [2 H0 2020-01-01/ S; R* Z- S" {) @/ y2 u
1 2020-01-03
. ^6 R$ F- ~9 o0 Z: k2 b7 wdtype: datetime64[ns]
1 @4 T% u2 a5 s/ r' O8 I1
, q+ P$ Y" a8 R) O. U4 M2
: A/ V* O4 t# L( T- m! X3
" E) j9 }6 b2 i- I) X9 j41 ~& W! l2 [5 w) {! V
5
7 P7 J( Q! y7 O. g9 C+ I! C下面的序列本身就是Series,所以不需要再转化。
" z9 m( ]1 c9 N$ E( ~; L" N# c% r' E- ]7 p A' s. _
df = pd.read_csv('../data/learn_pandas.csv')
) G# w& v4 ?) E! _s = pd.to_datetime(df.Test_Date)% Y& Y2 b5 c+ J# d
s.head()5 v2 ?* j9 k& ?. I0 `7 ` s
9 Q6 X' H& l: A2 t8 _% J$ |* ^0 2019-10-05
& Y6 u$ ]. ^8 B! R8 E1 2019-09-04
# A" W- n! Y8 t! h/ w2 2019-09-12
! \0 k5 ]8 |1 c8 h8 r3 2020-01-03" y2 B3 x! Q; i! ]; ^
4 2019-11-06
3 d: b" v, I j$ FName: Test_Date, dtype: datetime64[ns]5 m- ]" y, g" N9 G- w
1
* Y* X1 H4 ^# K, A2
7 e( S' D! t- Z Z* X3
; @7 n \5 j" V" |( s( r& f) h0 u4& F' l+ ^4 A9 n& f; W; M2 H
5% s$ n4 J2 Y9 j" R9 E
6
: ?& y& k z) K1 l7. y8 K$ ]( D. T. v7 l; W- Y
8
3 I! Z Q3 H n, b A9
' B) V" ?0 K$ K! A; z6 J4 U+ V10
2 t! U4 q/ s# j- i0 z" _2 a把表的多列时间属性拼接转为时间序列的to_datetime,此时的列名必须和以下给定的时间关键词列名一致:
- x& B, v" e2 L- Ydf_date_cols = pd.DataFrame({'year': [2020, 2020],
4 y. `8 I. \# B+ y( V# h 'month': [1, 1],6 H, t9 T/ m5 [3 y( h
'day': [1, 2],
8 `! L' t/ X) f. f( v( Z 'hour': [10, 20],
7 Y4 x$ S; x0 U% f1 ]: y 'minute': [30, 50],
! G4 I% }0 z1 E& S) @3 [+ p2 k6 H9 x 'second': [20, 40]})7 e3 {* e: g! D! o, \ j# }
pd.to_datetime(df_date_cols)
: G, U+ ` ~- W$ U/ U4 \. g6 F- H6 R& x+ B
0 2020-01-01 10:30:20
9 A4 N( Q- S0 c7 g2 S1 P1 2020-01-02 20:50:40: k \( h) L3 }* ?3 ]7 V; {
dtype: datetime64[ns]
# I% q+ j1 A' z/ Q6 |1& e E8 Z! m _
2
$ n' v( O+ `/ M0 g* F3
2 U% _4 w2 Z% |8 g6 t$ }" O* L4
! }" \/ p3 ]6 u" T3 A0 j- X4 ~5: u& \' u8 k5 m) K3 j5 c
6) B6 }2 g, T4 u( h2 E2 N% V
7
* o$ l G7 _# Z) H$ [. F8
) H8 V; Q% {( O( j! ]( p9
; h/ P. S, z* _1 f10
% z9 n Z/ r8 q3 g: a/ H+ w11" M C3 B- M- ^% t! P
date_range是一种生成连续间隔时间的一种方法,其重要的参数为start, end, freq, periods,它们分别表示开始时间,结束时间,时间间隔,时间戳个数。其中,四个中的三个参数决定了,那么剩下的一个就随之确定了。这里要注意,开始或结束日期如果作为端点则它会被包含:
4 L) [ }* U! B( Z2 @8 tpd.date_range('2020-1-1','2020-1-21', freq='10D') # 包含
+ G- H K5 n( o7 _Out[25]: DatetimeIndex(['2020-01-01', '2020-01-11', '2020-01-21'], dtype='datetime64[ns]', freq='10D')- b$ J2 B8 C4 S' Q& j5 O* d
6 ~" T2 T0 }: Q4 b6 @pd.date_range('2020-1-1','2020-2-28', freq='10D')
5 d: R' k$ b6 d. hOut[26]: ( r4 _- j* m% m9 \0 \4 C j
DatetimeIndex(['2020-01-01', '2020-01-11', '2020-01-21', '2020-01-31',$ Y, i) e$ v6 o4 D
'2020-02-10', '2020-02-20'],$ h/ K4 E/ F* v! ^, j$ f- o3 b
dtype='datetime64[ns]', freq='10D')
9 j- W9 ]* o3 o2 Q7 I+ y; `5 ?3 ?% g' c3 t
pd.date_range('2020-1-1',
$ d, W/ Y; r. K& U '2020-2-28', periods=6) # 由于结束日期无法取到,freq不为10天* z. J Y! b! r, R5 _ N/ p6 T8 ~1 G
" Z: M: ` @/ R ~
Out[27]: d- ^/ X* K& e+ y* n: ]- g
DatetimeIndex(['2020-01-01 00:00:00', '2020-01-12 14:24:00',
( G4 \4 ~& y0 `5 J '2020-01-24 04:48:00', '2020-02-04 19:12:00',
0 g) Y. A0 q! a0 ~4 m '2020-02-16 09:36:00', '2020-02-28 00:00:00'],
; H: ~9 T: A! W! F3 ]' P dtype='datetime64[ns]', freq=None)
3 C! x3 @( ^7 {+ U7 @. }4 y: W9 ~2 R* }/ A# H1 [, _
1: T9 f: ~' L2 ?8 _9 y
20 I' Q$ h% n7 w& s
3
8 @7 h2 g B& O9 [" S+ f( o9 y4
( S/ O: N j6 H$ v) D! y' \5
6 l1 d( s6 `/ V. R2 k6
0 j0 e4 `" J6 E7
" T. n N0 X4 ~0 ^0 F9 b80 M3 F; E. u( r* J a( }
9
( u7 I, Y9 D# B10
0 |# S& h7 L; N# z11
5 _9 F1 i1 q% r6 g7 v. W! ?+ j12' K4 f- _6 q( m: P3 m4 I
13
( H. |, X/ [/ j14
/ G0 s) z! ?' e+ v15 C9 @$ n$ J) O) N, S
16
2 E1 j; o9 N4 ^- S( O( }17
$ p7 M! W5 C9 D" s这里的freq参数与DateOffset对象紧密相关,将在第四节介绍其具体的用法。: y2 d9 U4 r4 s' C1 F' U4 q
' u V3 _, W; T' m【练一练】
5 y; g3 k+ v/ A! R3 q" CTimestamp上定义了一个value属性,其返回的整数值代表了从1970年1月1日零点到给定时间戳相差的纳秒数,请利用这个属性构造一个随机生成给定日期区间内日期序列的函数。
3 z F) J9 j* Q: |6 {6 P, Y3 ~2 c
* y8 p0 F3 V" U+ X% Jls=['2020-01-01','2020-02-20']$ `- \5 K& F% z- @$ G+ \( D
def dates(ls,n):
6 O: E1 G6 t; {$ h4 \ min=pd.Timestamp(ls[0]).value/10**9
1 z; Y F% P; z% Z8 }) _0 t max=pd.Timestamp(ls[1]).value/10**9
/ N# X- r( H7 \1 R1 p* S* [ times=np.random.randint(min,max+1,n)
( F4 ~) K9 E2 x4 v! c return pd.to_datetime(times,unit='s'); j3 k1 c5 e3 v! y3 w0 u* Y$ g
dates(ls,10) 4 ^$ E. D8 u: w, r5 E
, r9 E# [+ ]+ J4 D% ?- n: C4 t
DatetimeIndex(['2020-02-16 09:25:30', '2020-01-29 07:00:04',
- m6 C' e. T3 R, X" p# B5 m '2020-01-21 12:26:02', '2020-02-08 20:34:08',, o) _: P% L; z6 W3 j
'2020-02-15 00:18:33', '2020-02-11 02:18:07',
5 F! e3 A5 \- @$ i# J4 C '2020-01-12 21:48:59', '2020-01-12 00:39:24',
& [% X: m. s& m& c5 J2 L5 h '2020-02-14 20:55:20', '2020-01-26 15:44:13'],
8 | ~; ~, u3 @ dtype='datetime64[ns]', freq=None)( y: _: a2 k) k1 S+ y5 o0 j7 P3 I
1: s7 g% K& r7 G" {- E: U3 }
2
& ^( s p5 z+ N7 I3
# j' b; h) v& |+ N4% Z% l6 I7 v- D- j5 W- Y
51 K0 X. `, G1 c
6 \5 S+ E; o6 z
7
- U2 n. j6 H6 \3 I% ~, B80 T% l- S" F( i2 W! u0 R$ T
9
! b, {" ]* N. C" z3 F S10
0 j* O% H( O. ^, E3 M2 c11
( n5 T, k9 \/ U! a, t12
7 P u6 D* I7 Z, C8 K0 f+ X13
2 p$ O8 R0 ^+ m' ?; t: @6 g14
1 c+ a" [2 e. C, oasfreq:改变序列采样频率的方法,能够根据给定的freq对序列进行类似于reindex的操作:
$ l: ~2 ?( M& ^2 c- \2 Hs = pd.Series(np.random.rand(5),
6 \2 P5 Q a- u index=pd.to_datetime([
$ x. T% c% U$ K '2020-1-%d'%i for i in range(1,10,2)]))
# b4 t3 e! \# {: ^
: t4 Q6 n7 p1 o3 V8 N' l+ ~$ N! A3 J
s.head()8 t1 A6 @6 H7 p! G/ D: c, `# W$ m/ V
Out[29]: 0 r# N2 [6 P# R
2020-01-01 0.836578
5 L! ^4 W2 L8 `& J- X2020-01-03 0.678419& C& U7 t4 S7 g# d9 w
2020-01-05 0.7118979 @" j" C/ u$ t a7 f1 X
2020-01-07 0.4874293 H% M, O6 X5 ]( U
2020-01-09 0.6047050 |( S F, @3 F. G+ d+ o
dtype: float64
: t. w# a1 \: Z: j7 \ c! z4 |+ T/ q4 P
s.asfreq('D').head(). ]+ P$ N5 d3 ]' ~/ \9 u* k
Out[30]: [. u; o1 V9 q. z' E
2020-01-01 0.8365788 T% |3 L" D. U" a) a3 S$ K
2020-01-02 NaN7 {0 F& l4 P& j; w
2020-01-03 0.678419/ Q& p4 C+ e. B7 _
2020-01-04 NaN
; P( T4 W: ]. s+ A2020-01-05 0.711897; d+ J; Y1 |% A# L
Freq: D, dtype: float64
1 i" N* t4 w* @2 q* @ _6 j! i
- g6 F8 R* A+ hs.asfreq('12H').head()7 y3 Q+ U; Y6 P, n/ h8 @$ D
Out[31]:
; j7 x% `" k! A# D C6 ^2020-01-01 00:00:00 0.836578
" Z. ~: T/ o- M6 L7 z2020-01-01 12:00:00 NaN
1 g, z& ], O3 O7 w1 k2020-01-02 00:00:00 NaN
9 y3 C0 K, J# h' }) A2020-01-02 12:00:00 NaN
- N# f8 w; }- G& b/ g2020-01-03 00:00:00 0.6784190 _, C: L- m0 m/ x
Freq: 12H, dtype: float646 r& ^' g$ Q& I
- l+ t/ Y, I% n1
; s5 t4 o0 P! `" i, w2 L9 W28 P; Y- ^) u3 W5 l
3, h5 ?4 j7 s& r- o5 r6 j; \
4
+ ?; E2 R+ D7 S9 e. [) M5
. y: e6 R" z, R# o2 j4 N9 o) y6$ F! q- H2 k" `! @) A
78 s) `& O4 i" a0 y
8. E# L, Z+ _/ ~# T( ~( _4 R2 v5 E
9, S7 u" @' S" W- t
10) E4 d8 Q9 k; D. ^7 W, Y8 @, `
11
4 C6 q8 h5 J; C. @$ y: h$ W12
/ F* T& \1 T. m' \2 {13
! @. I, M/ G. j" D$ b3 _! X8 C* X14$ G9 X& S# Z- |. P6 H( I d+ a
159 ]. Y! I6 L" s. s
16
/ U8 l5 `, N* c. T) P17/ b; N# c3 }: H, @; n# A7 {
18
2 b9 n( t$ ]. D& Z$ ?( i& X9 {8 o19
8 _/ k* Q- T5 H! O203 @& `! g5 ]- f& Y/ s) ~ ^* V
21) `& A1 Z3 H: }4 ^9 |3 Q8 n
22& Z1 n- P5 }# [! x9 ~/ u) b
230 a% [% |6 u8 t( N- q
24, y c( _' H1 k8 h: X' t; U
257 G, V4 f8 k z8 g" K+ \
26
1 \4 N- l8 R# d276 \2 \ h) j! ~! h* A
288 q3 S0 S0 D* j7 F1 d8 ]9 B: o
29- F! ?' g; f4 g6 G( _, |7 u6 a0 ]! O
30
5 u) @1 s' _) x31+ G; \1 E& q" D+ I- Y+ j2 L0 h- m
【NOTE】datetime64[ns] 序列的极值与均值
' t# a2 m3 o! ]6 }3 Q( \8 D" { 前面提到了datetime64[ns]本质上可以理解为一个整数,即从1970年1月1日零点到给定时间戳相差的纳秒数。所以对于一个datetime64[ns]序列,可以使用max, min, mean,来取得最大时间戳、最小时间戳和“平均”时间戳。
4 q L; n/ S4 C u" h) L9 b5 j/ o: A& J" u/ V; n) N
10.2.3 dt对象
& A& k8 y$ `& a: d! F 如同category, string的序列上定义了cat, str来完成分类数据和文本数据的操作,在时序类型的序列上定义了dt对象来完成许多时间序列的相关操作。这里对于datetime64[ns]类型而言,可以大致分为三类操作:取出时间相关的属性、判断时间戳是否满足条件、取整操作。7 L7 j O L" y
, {" p- z) |. K
第一类操作的常用属性包括:date, time, year, month, day, hour, minute, second, microsecond, nanosecond, dayofweek, dayofyear, weekofyear, daysinmonth, quarter,其中daysinmonth, quarter分别表示该月一共有几天和季度。: H& [* K$ E( q2 M
s = pd.Series(pd.date_range('2020-1-1','2020-1-3', freq='D')), I: _# V& L' d
; p6 R( ~) U' {" H7 d2 m
s.dt.date6 I& c& @& O! V# G9 r2 l2 T% K
Out[33]:
- d) j, z$ F! S0 2020-01-01! k+ b& e( S; d3 { y, k
1 2020-01-02
. w1 |3 [- |- Y! m4 J2 2020-01-03
1 s7 g! N2 U; l. _dtype: object
3 A8 l5 ^: `0 z4 _9 s! C5 j! h
. N$ N7 `0 j, K L9 H0 i/ rs.dt.time# a! O' T# {1 g* V
Out[34]:
; X% [* \- R$ E: L- M" t$ i) e- j4 y0 00:00:00- b; _9 p' m, v9 J0 E
1 00:00:00
, L: _$ q* V- Z7 G9 l- {' I* k2 00:00:00# {+ o( N) \8 \+ f/ t5 n
dtype: object# A$ j% h3 K3 e& `: ~
B$ w4 g3 Q8 I. v6 M
s.dt.day1 @- m0 B% `. T' `
Out[35]:
- F* e* o( D3 u0 1
% E& ?+ h% T4 _$ C3 x1 2
' L8 | Z4 ^2 T. T6 N& \1 A2 H/ D: Q2 33 C* n3 E1 T+ X! H! X4 ?
dtype: int64
1 q. U- z) e- c% g. f$ z& p& \5 _! U( T8 `0 p) k) ?! m# L X6 M
s.dt.daysinmonth
7 z7 g; b4 A! I: gOut[36]: 4 ~/ z1 N2 m4 ?6 ]! q( V
0 31
1 h3 }, E8 w; x* g% X. C- `4 z1 31
2 y; @1 ]; O2 l# M9 O- F) n* c2 31! d: q/ N, i9 X! H# O4 Z
dtype: int64" |2 y8 g% Z) _8 b: p) f
0 |8 @; w* M, r$ N! N! x7 ?" _13 y1 q+ t3 t" h T6 J# ~% h
2
+ _6 Q: U# n; h. q3
; f1 S2 U0 u! r$ j4" W9 M* y* V! u _+ B! }7 m( g
5
+ o& Z- G9 y3 m0 |" \6
$ i) y/ K. X9 v0 e7 r+ M2 M7
: z3 P& u/ |! K1 `8' \% ?8 l6 a) c1 l% z
9
/ Y# P+ `$ ~( y% K% G- Y6 C9 G10; U/ X7 [" r# S; F* R1 V- w+ W3 ^8 N9 j
11/ y1 e% L6 [ ?2 ?; S. l4 i
121 d2 I; M% ]0 C. v- ?' n
13
. K) `6 u2 ]7 }14
7 L8 o" }* O: |( \1 S% E15: e7 M9 s' T0 X6 d
16- s$ r% \& R" V+ z5 V
17/ {) l0 W" c( b
18
4 H8 i+ n# g a# M( p" N4 K19
8 K% _, k# z( u; z3 E20
4 @4 Y1 x l4 U: @+ v8 t21
& q' q" b9 t/ ]; i( K, i3 ]22
4 D. X2 w! A9 K% ]" o% J23) E. [* V9 V3 A- H) v: ]! n
24$ @, U! V5 G; n& }6 }* F
25
. g: X. M1 |9 Z5 [" k26
8 U# y( y* C9 ?( K9 ^275 X- K5 [) g0 _! w, R
28 X3 }: V- `; Y9 o/ v, z; \
29+ E, H/ @+ m( C7 a' s& I
在这些属性中,经常使用的是dayofweek,它返回了周中的星期情况,周一为0、周二为1,以此类推。此外,还可以通过month_name, day_name返回英文的月名和星期名,注意它们是方法而不是属性:: _7 W/ I4 S2 e) |6 \
2 @0 ~8 `! ^0 p. T' D: C
s.dt.dayofweek( L3 F2 u% D: T6 [) ?
Out[37]: ' h( o2 [3 G& s4 J* ^$ w' U
0 23 u# ^$ L. K6 a" \2 [) A
1 3
/ l) t9 s1 U3 P N9 B" }2 4
( a( z1 d1 G8 ]dtype: int64( E, E i2 H7 e6 w+ q3 }' b& P
/ z- N$ ?7 _6 Zs.dt.month_name()( a- J* o5 N/ B K6 x0 d
Out[38]: " n7 q6 \' S5 m$ N% t/ l. `4 D
0 January' j% u7 W B1 {2 q! m. j* m, O
1 January
4 Q+ B: |$ s! ?/ B6 I2 Z7 ]2 January2 t/ y: x) e1 h+ z4 f
dtype: object
- p; q( M1 }& R+ t# Z3 r' a5 m# A0 v" F
s.dt.day_name()
/ P" x O) e" A2 w2 O7 ROut[39]:
3 y V8 K' A( ]$ G0 Wednesday: P+ U7 ]! d% N- d5 f1 N, t7 y
1 Thursday0 o+ I$ |1 ~* W P" e
2 Friday* B) T" Q7 G* r7 V8 Y7 U
dtype: object
; ]1 T* B5 \0 y9 G" f/ B! L4 |* M
9 a9 Q) V/ J9 m13 S* C+ x* L0 y N. j2 x
2" L2 G5 G X. [1 Q" j
31 N1 N) A' O( p& }. D5 t
4
" K0 Y+ \( b/ O5
5 O; |$ G/ k/ f2 ?6$ F- a; s2 E ]+ r$ n
7
; Y- [, \- y0 ^/ _2 R) |1 n, G8" a/ G- a" ]) q L& K3 q3 |
9' E' A0 |/ s% n7 U/ e" ], F" T4 P/ T
10
0 ~& j% J7 r1 c11
, E F) r# |0 p# f2 \4 m12
1 b: f7 d) t6 z" c. G+ T+ M138 {9 G4 m: }/ Z q7 e
14
& [2 v8 t/ |. M15+ P# t& V# P5 j( k
16
4 d# c" j! [+ Q( j- i179 z8 C+ }, K$ C4 d/ V* j5 z1 j
18
* U9 `& M7 V' R5 B191 w- ?- t- D* y
206 U# Y: M" A% F+ N. q, s
第二类判断操作主要用于测试是否为月/季/年的第一天或者最后一天:/ t/ C+ p8 L! A
s.dt.is_year_start # 还可选 is_quarter/month_start0 K E* b7 s& [! L" C
Out[40]: 7 W: g" S! u2 i5 t% i* p" P
0 True
6 y& M$ I% t" T' N( l v1 False8 W0 q8 M: S C- r; \
2 False
: I" W( E, |; u; t0 o" hdtype: bool
* Q, ~# t3 {, T. W! j
0 s9 n! N/ O& ^3 p, F$ h: us.dt.is_year_end # 还可选 is_quarter/month_end
/ O" q8 J( F/ H# J' h% x1 ?Out[41]:
( {! ?+ a7 i0 f2 H0 False
# i# }. [) I' ?$ o& [- [' Z1 False
; H% m5 d# D4 X# B+ Z0 {0 n2 False
6 F, y# |7 f: a7 R: |8 Bdtype: bool
" L% g3 d- O- I- u) Q1
, D$ U$ ?! S6 X2
$ C, v& d) m& v; ^/ @; A9 O# G3
6 `: n0 T6 D$ N/ a$ j, x# i4
- w8 s. ^) u( A2 }/ H5' X6 Q9 H3 H8 }! J' r/ S. t6 W
6
/ l- k( f) ^5 h2 g; [& F+ n2 n7
7 x2 o, p% j8 Z+ @0 X& A87 s( y& I. @+ M a% e; R! m; E
9( x6 L+ K: h6 I; J5 u1 `
10
% Z# c# l/ I7 k" w. f0 O* K% m11
4 v/ D- _* M. x9 {4 a# z) ?1 V( h8 l12: y& ]( C1 E& p7 d/ T& F
131 g: K6 d+ u1 R: M- d
第三类的取整操作包含round, ceil, floor,它们的公共参数为freq,常用的包括H, min, S(小时、分钟、秒),所有可选的freq可参考此处。5 N9 A+ v% U, N6 z$ I
s = pd.Series(pd.date_range('2020-1-1 20:35:00',( O: D0 y. `5 s& t( c0 u6 R, S
'2020-1-1 22:35:00',
7 M4 T: Z# {* _% V9 b$ H, K, R3 f freq='45min'))
( _, L% I' F0 V! G" V5 o' D {& p% V; X3 O. L6 w; G
# H; {+ Z; J7 s% G) K0 h# x" N
s
- \1 M/ n6 \+ W$ AOut[43]: ! B6 o8 e9 O) |& W
0 2020-01-01 20:35:00
. P" a7 K4 V& t* g1 2020-01-01 21:20:00
% A# F# ^- o9 h) ~/ s, e( E7 ]$ _2 2020-01-01 22:05:00; @! P& E( T7 ]9 Z
dtype: datetime64[ns]
5 h5 m g5 p4 Y9 e G5 i) A
9 D7 k6 i5 s0 Ls.dt.round('1H')6 B/ v: l9 B: D
Out[44]:
9 L1 L, z/ g- O* g" c4 ~$ j0 2020-01-01 21:00:001 n0 P; f( z/ q& S& E8 e8 ^
1 2020-01-01 21:00:00
- a* q) \( {( T M2 2020-01-01 22:00:00' r* @2 h3 U$ x3 ]' f3 \ i
dtype: datetime64[ns]
+ V8 E& X4 b: `% ~8 f$ O* Q+ M9 f- A; `
9 @+ ^( O% @- B9 B2 xs.dt.ceil('1H')
9 m8 I, V/ y) M2 d3 jOut[45]:
! w# ]( j- O# Q2 B# w& l1 g0 2020-01-01 21:00:00
/ Z, w2 K. l; ~# p: T4 i+ y1 2020-01-01 22:00:00
# q& d) C+ U0 ^2 N' w& r2 2020-01-01 23:00:007 a& g9 {# h8 t
dtype: datetime64[ns]0 [& p: B' X" ~/ N: O! ?# v* v% K
8 h. Z& A7 |4 o3 m+ [ n( ~% t& q. Z
s.dt.floor('1H')
# S; p9 ]" I" a$ I! qOut[46]: # D1 C" |8 a# ? W# `( C; @
0 2020-01-01 20:00:00
+ t2 P8 b6 e7 [6 U2 W* G9 L# k1 2020-01-01 21:00:001 o& U5 A# B6 Q' _/ A) N% Y$ j
2 2020-01-01 22:00:00% p8 S& G j0 D
dtype: datetime64[ns]
0 f( Z) k" t1 D5 J; E+ l: a% ]% Z u$ ~3 v/ K' ?" X! P
1
& k( d" ?) \ z2 }5 L* w( A2) ]5 E! i2 M% C& \! I$ o" B4 e4 ^
3; y9 W4 f* V1 H) N+ p: r
4
& c3 g0 u- A+ I- L7 \6 |5) k$ y2 W: l: |! Q# d
69 D* f; P2 O+ @' J5 L4 j7 {
7
2 c6 J, |4 N8 W84 Y4 U1 R/ r# @- O
9# b+ {, H6 ~9 E* \' q
10( C- ?2 E& z; z0 H
11& A5 r' b( i7 P% L
12
, W- h# Q' P. }8 W2 M13* W6 K2 F+ [ r! }+ l
14( u, [9 G9 H9 ~5 w
15. w4 T8 C" M: v9 U
16
. B. t) W9 {& b0 }2 V17
* b. E8 L% O; ]1 {$ g3 T5 \18
/ G' g1 z, c# ^4 w& S# m4 r; O19; p/ J9 T; l# X1 C1 q6 B/ C
20
( _, B; x/ j/ \. u# E% b6 f2 p4 X218 T4 R* V3 c- U/ \
228 D& j% j. L, L8 M
23
- o0 |; h$ k7 b# T; t5 p7 S245 n. ^$ i8 w$ ]+ F4 c v
251 ^( z6 }! u% q. T& v0 x+ b: x
264 E0 j5 u# f C6 m
276 L r4 ~9 a4 V/ |6 U" K9 i
28$ z1 W9 X& ?7 p' K
29
7 v( z! x7 e2 ^4 \5 R301 s' R6 j* w1 T r
31
; ^7 P" f2 n0 [4 d32
5 r: t) f! C7 Y" B9 _5 d7 {10.2.4 时间戳的切片与索引; e, @: @' `7 }, W- [* g! j- r
一般而言,时间戳序列作为索引使用。如果想要选出某个子时间戳序列,有两种方法:
$ P' _6 `7 K: k; L! k3 G' f, B8 N$ O8 F; O
利用dt对象和布尔条件联合使用( X! i0 l* q2 f
利用切片,后者常用于连续时间戳。3 Q" i4 P; k! c% A& d4 h
s = pd.Series(np.random.randint(2,size=366), index=pd.date_range('2020-01-01','2020-12-31'))' n2 {& m8 `! l+ s8 S. L ? a
idx = pd.Series(s.index).dt- b) c* u, }" J5 }! t
s.head()2 w% n0 f& ?& [) A& ?- R% q6 Y
: j9 a# F7 @8 r% f0 |
2020-01-01 0' O, G) T& F- n3 A
2020-01-02 1
& M4 D7 F8 h& N3 d8 t, ^2020-01-03 16 z4 a, w, O1 _* g) ?8 Y
2020-01-04 0
1 a# }7 Z& N r6 ^& e# @2020-01-05 06 ^! K! H' w5 F" v. n6 e
Freq: D, dtype: int329 a6 ]% ?8 c ]: h2 a R
13 H2 l* K* P! R1 U" I
2
* A) z% J" O2 v( f6 u3! i2 D$ h( D! Z
4
5 l/ M$ W3 T( Z; X. L" h53 O e( z: m( {- x: T
6
! F# h6 q7 o* H& l; t7
* u% i9 n, | q8
N8 K8 b. b" p99 R" T* o9 b7 x: ~
10+ m% i$ O; C( [
Example1:每月的第一天或者最后一天
% Q, x1 L- z. m6 p9 s/ W
% j1 S# ?& c' Q1 _/ p# d/ j6 Os[(idx.is_month_start|idx.is_month_end).values].head() # 必须要写.values
+ T+ n u; ~- P! h; DOut[50]: / i$ q' S* H/ M
2020-01-01 1
# A( V/ H- \/ {& q2020-01-31 04 N8 ?7 o$ U, i7 s, L* j
2020-02-01 1( @" I. v E- L; n e( {
2020-02-29 1' F5 b. G4 Q# J4 |/ d5 j: { h
2020-03-01 0
, n m5 W' N+ D0 C0 \7 v5 s. hdtype: int32
7 w- J% K4 {; h; g. M: U: G1
# O; t9 T2 g; f( _2
+ M1 P7 x/ {- V4 C/ T/ i `31 p/ i0 m, Q/ j2 i/ a8 e7 B
4
/ j& X/ M+ E+ Y2 |5
" R( [/ h& `1 _/ f4 `6
( [* y) H5 l% \1 [7( G6 x' l7 m3 K2 g& y* B
8
/ h& b7 J+ t) c1 ^, p VExample2:双休日: s" m/ c) B+ v6 u1 J: @$ n
, \9 M1 d( t/ C8 u. y2 N- w; @
s[idx.dayofweek.isin([5,6]).values].head()
( R. X( J0 v IOut[51]:
: z' e3 N& k+ V$ {9 Q: F' C2020-01-04 1
+ t" P0 y s4 C$ m2020-01-05 07 H/ W, Z2 P* g" h- ?" B
2020-01-11 09 ^( a. S' F: X5 {* R$ E
2020-01-12 1
. n/ f% N( {& s, _' t" M) G- Q2020-01-18 1" }3 F5 G! t4 X0 Q; v0 L
dtype: int32 e5 o6 x: c+ l' J: v
15 }2 R, ]# o/ u- w# u( G
27 D: a. }, i5 a# ?
37 V, }" c9 j+ H9 U9 _6 p
40 a* l7 G& V4 j8 c- p
5
2 n u9 S- U% ` Z6
! W$ W6 I' J! i' B: `2 T3 q77 a7 I' Z9 d" T
8
/ y& Q) J9 [# E5 N+ y* M/ U0 aExample3:取出单日值# y+ M. g# h r9 v0 Z* s1 D, i; z
. L1 g# f) y4 h( I! s+ I
s['2020-01-01']4 H3 W& Y( d n
Out[52]: 17 h; O, V3 ^- U" X0 i/ s
' F, P n# v* x) h: Us['20200101'] # 自动转换标准格式
' D _0 F. u8 @* z9 D) DOut[53]: 1" D& ^6 n, Y) b, S) S
1# o; D( [. ?# A0 D) p
2
$ ]) {" o) G* `" X) X/ |9 L+ ?1 z30 f9 q Z& Z6 Q8 E o
4" W+ J, j# L) l" m
5# ] g$ X/ l) t4 w& ~8 }7 `
Example4:取出七月
' ? V5 ]8 ^1 ^1 v5 {' `. j
0 q) E/ @! \5 n& |s['2020-07'].head()
) N% j4 [+ @- `0 C# JOut[54]:
0 o7 e/ J6 h9 s- v" {# T2020-07-01 0
* p( P2 v9 f$ _. \, U$ \2020-07-02 1 C5 v" Z" U0 P& h+ D
2020-07-03 0
! l, O5 p2 M- K9 W9 t0 t2020-07-04 0" @9 H \4 U6 _$ q1 A7 L
2020-07-05 03 Z$ K1 k3 c; \/ G3 ?
Freq: D, dtype: int32
, n( B& @ P& ^3 C1
- J9 H7 a. g; C: [% v2" F+ t- }* L* o
3
0 |" I7 v$ {0 }5 v( e; s [4& l" O( n1 I# N6 {5 P7 `( E/ H
5
[+ E& D6 D8 Z1 b, T6
/ v; ?' Y' ~& A. U6 d7 F$ Q; w79 N8 e; g0 b: i* \# X: p. F9 i
8
( ^! h9 T. V0 h3 q3 J2 k( M4 rExample5:取出5月初至7月15日% t$ r1 }% L4 q! f8 @- o5 k
$ h6 y* y2 o' q# i% n
s['2020-05':'2020-7-15'].head()7 |5 l1 I; J( G- s, V
Out[55]:
* ^1 J9 @" [7 K4 C7 `2020-05-01 0
9 E9 n1 u% Y! `' _$ w# l2020-05-02 1# [/ T3 T* F2 U6 o
2020-05-03 08 u6 T: C7 g8 Q7 B7 P# ^( w
2020-05-04 1
2 t: _4 R) E+ o' t8 ?+ C2020-05-05 17 B: V: o0 T9 ^/ @) k1 G
Freq: D, dtype: int32+ y: u/ J! c/ G3 f& j. v; W! o
2 l D6 W& C7 K( C1 i( g8 {; T
s['2020-05':'2020-7-15'].tail()9 q# w* |" B) I. }% _% E
Out[56]: & P" o- c' ^6 F* C2 p# X9 ~0 Z8 C
2020-07-11 0$ x* Q, }- `) `7 K% l! E* ~" {
2020-07-12 0; |/ y/ ^/ Q) |& R" D3 h5 ]
2020-07-13 1
K8 s' \* D$ y/ ]2020-07-14 0
0 n* _! g- q! E9 O" F# K+ A2020-07-15 15 x( |- r9 H: F7 Y5 J
Freq: D, dtype: int32 v1 u r+ y5 j; s8 Z& D0 H
& v3 Z+ V% Q4 a1
, g2 l3 w' O- T5 A6 j0 j. I/ d2/ R; ]) i+ G" A+ U) b4 q
3) k, R9 j* {# K, I* F8 p
4
* _3 |5 \# i* L/ E" ]- \8 P9 A5
- w1 W% e- h. @2 O, W" E7 {) l6
( e# J0 {6 i" M/ u& C7 ?" m+ u- i7) R$ e o1 d2 A; u. J& U
8* n# x: H) g! b5 F0 c
92 I! F9 U$ v0 r: A6 I B. ~* f
10- n3 B3 I4 T* b( [& n
11. `3 {4 y* R/ ?. b) M/ t0 L* _
12- L$ e# O9 d3 @. h- o
13
$ k5 B1 ]1 Y7 n& j% G8 O14
; \: T/ C; Q1 \& v4 l15( q: O' o* w ]/ u2 w0 v/ d
16
7 a2 A3 d% f6 n17
S/ F$ X" a& J1 Q4 ^10.3 时间差% L( U) S7 a% R- f$ a: f* I" j/ c
10.3.1 Timedelta的生成
, t4 V0 y0 C2 U2 Spandas.Timedelta(value=<object object>, unit=None, **kwargs)
0 d, {% [" A8 r3 B unit:字符串格式,默认 ‘ns’。如果输入是整数,则表示输入的单位。/ I* d f6 C# B2 o
可能的值有:. T3 b, n8 s: I0 i
5 ]5 b1 l5 W n$ V" |8 p! v‘W’, ‘D’, ‘T’, ‘S’, ‘L’, ‘U’, or ‘N’
+ y" G6 I j8 J' ?% j' Q: s/ i‘days’ or ‘day’
/ h0 B8 C6 w$ l; a: D‘hours’, ‘hour’, ‘hr’, or ‘h’$ R# }% v! r' {7 p7 j. W8 a7 M
‘minutes’, ‘minute’, ‘min’, or ‘m’% K6 i, R. r, r9 E- g
‘seconds’, ‘second’, or ‘sec’
0 k8 h# h$ H5 a; N( q" I* K- G6 Y毫秒‘milliseconds’, ‘millisecond’, ‘millis’, or ‘milli’. |+ r! y' T! ~$ n7 x
微秒‘microseconds’, ‘microsecond’, ‘micros’, or ‘micro’ D' K/ b/ C% e5 r! t" `( D8 Y
纳秒 ‘nanoseconds’, ‘nanosecond’, ‘nanos’, ‘nano’, or ‘ns’.0 }+ F3 S0 f- Y- g
时间差可以理解为两个时间戳的差,可以通过pd.Timedelta来构造:' ], G! r! T& t5 M+ \( N
pd.Timestamp('20200102 08:00:00')-pd.Timestamp('20200101 07:35:00'); H- r0 \3 W, Q/ H, M7 V
Out[57]: Timedelta('1 days 00:25:00')
8 {$ Z# o$ c' O# e" }0 h0 ?9 C
' I$ M7 f5 B! L. E, t" Opd.Timedelta(days=1, minutes=25) # 需要注意加s( d+ e+ H; N+ c- U" b
Out[58]: Timedelta('1 days 00:25:00')3 v% ^0 ~8 b: T; l
* S: h. ~- Z1 h( K* X5 K5 z
pd.Timedelta('1 days 25 minutes') # 字符串生成7 w h, k& A: _( k6 E
Out[59]: Timedelta('1 days 00:25:00')
* L8 q7 w5 ^: N5 B2 u0 x5 ~# g' g( |
pd.Timedelta(1, "d")
& T$ E% S d z0 _; KOut[58]: Timedelta('1 days 00:00:00')+ |: ]' j8 _: y% ^& t/ T/ t- U2 X
1# [8 y U4 N1 _" t
2
; `* I2 W% ^0 z# s0 V/ l3
: Y0 f5 S% _" O3 g3 X4 f2 d4
% l0 h. V) B4 O/ \2 H- [9 P: F5
6 w9 F* y6 b m5 x" D. N% y, `6
1 Y w- ^# p4 e9 C70 q3 G4 w% i3 X" V( K: [& D
8 Q: V0 p' o0 W6 V9 g T
9* w, Q- O; G* A" i/ D' l" L
10
) f# K7 X, k5 a m$ ^11
& F! e- ?( [" V生成时间差序列的主要方式是 pd.to_timedelta ,其类型为 timedelta64[ns] :
# f4 o! `% c. J- Xs = pd.to_timedelta(df.Time_Record)
- F) W$ w3 g+ F" m/ M$ L' y, h) k S, K* I
s.head()
$ \0 f) g9 \, ]3 ?6 p ]( ROut[61]:
4 r$ m' H% G# N/ D2 E0 0 days 00:04:34
( x" A* [3 S& h1 @! x1 0 days 00:04:20
% r" _! V5 X$ f2 ^2 0 days 00:05:22' ~, L( e& }/ Q, y
3 0 days 00:04:08! U) ?+ |4 i9 o. v0 `" s
4 0 days 00:05:22# D, l0 F( C5 m; D8 G( z: ?& N" I
Name: Time_Record, dtype: timedelta64[ns]
8 S, H! b. [4 {; k. I% B1
~; _) j+ l+ k! n' l' I5 s" R2
, g- y" [7 G4 E- l' V$ V5 L ?3$ ^ W6 S4 j; m2 W. {* c
4, W0 [5 D; k! ]- ]1 k
5
* r q+ p- U9 q6
- a( q, U# `$ P2 \0 @7! Y i/ C! Y+ i+ X1 I+ \# B# @
84 _" B1 o1 a" u% v
95 p+ L" H/ n& R- V, \
10
- o; m1 P6 Z- D; i. r与date_range一样,时间差序列也可以用timedelta_range来生成,它们两者具有一致的参数:/ a, ~3 e5 `7 A8 _
pd.timedelta_range('0s', '1000s', freq='6min'); ?" A8 T7 w$ y0 w
Out[62]: TimedeltaIndex(['0 days 00:00:00', '0 days 00:06:00', '0 days 00:12:00'], dtype='timedelta64[ns]', freq='6T'), o) C( e$ I. }9 l" C% |& q
, Z0 |' ]5 W- {1 V/ opd.timedelta_range('0s', '1000s', periods=3): E9 |6 U7 N8 Y0 Z+ z9 q" b
Out[63]: TimedeltaIndex(['0 days 00:00:00', '0 days 00:08:20', '0 days 00:16:40'], dtype='timedelta64[ns]', freq=None)
6 a$ b$ g+ n4 K. i* p2 k6 B16 w6 T6 h6 E2 M8 n+ d* H" }4 n
28 q4 V3 [4 |3 j! l& @% H/ u% d3 m
31 W9 Z6 U: r1 w' K6 C* Z
4! {# J2 b1 K+ g
5
1 p0 S: I( l% ~" ?4 L$ K ~对于Timedelta序列,同样也定义了dt对象,上面主要定义了的属性包括days, seconds, mircroseconds(毫秒), nanoseconds(纳秒),它们分别返回了对应的时间差特征。需要注意的是,这里的seconds不是指单纯的秒,而是对天数取余后剩余的秒数:
% @! m% C, R& z0 us.dt.seconds.head(). U/ X4 ?% `% F* Q9 k
Out[64]: 7 `7 d! @ r" Z7 w2 c+ k
0 274
/ y0 S4 d! V; s* U. A1 260
# J5 d* B( T9 D" d& U1 p2 322# u! e0 A9 J) N! J, Z
3 2487 h$ @9 u6 p j
4 322# C1 c9 X( ]8 q4 s* M
Name: Time_Record, dtype: int641 u6 T3 P+ w" R( y( V% Z; i
16 r E! K3 y) y# Q( E9 x4 D; M; g
22 \& o: o( c/ B7 r# L% H& v
3+ v: v" }5 \) P3 n5 t9 ]6 a
4- d; ?2 \4 `" M
5
( {5 b8 `4 d) E" E. W6# K4 O/ ^% o0 R6 Q K
7: t8 a% }- E. t, \" F+ i
83 Z* {: C3 H7 z9 z; |0 ]
如果不想对天数取余而直接对应秒数,可以使用total_seconds2 h( X# E: _" n) k+ Q9 ~
, _! e ^! u' e m0 e# ^s.dt.total_seconds().head()6 B1 } X6 L0 ]( O- i9 E# p
Out[65]: " A1 o* y2 O6 s
0 274.0
0 f. v, a! D' [, e3 U9 D1 260.0
+ S! w& d; A' h' }& B2 322.0
& H' Q- Y( [7 Y( f5 ~8 O4 I5 {3 248.0' o. U! m) r+ [; R
4 322.0
; i" }8 i& s# I) a& C5 v) {Name: Time_Record, dtype: float64, j i& D% U: t) k- T% @
1, Q: h7 v l6 t, R s
2
1 f. V% i, q; A" P$ Y3
0 [ D) h" {! X( J$ ?% Q4
* a m# R+ w( e) t# b1 k2 g5
3 V0 N2 @/ p- s( C N3 r6 E64 M0 Z5 R) Q/ W. X* c0 I( u
7
/ N, E4 \1 _9 ~0 L- q- s1 o! N8& B2 i) n- i7 m4 i: |, k- R7 _% n
与时间戳序列类似,取整函数也是可以在dt对象上使用的:5 c) u+ o2 I4 K
, o% O0 v) R; \) Ypd.to_timedelta(df.Time_Record).dt.round('min').head()
! ~/ i' o% z. P5 t- B- ?# c, }Out[66]:
7 H$ U2 K. R* I. g0 0 days 00:05:00
" }# p0 d- t9 [- J; h7 ~1 0 days 00:04:008 w1 v# N; [. p ^
2 0 days 00:05:00
/ J# A4 b5 i/ O, c3 0 days 00:04:00; i5 Z1 D0 c6 _2 o6 K& y- N8 J
4 0 days 00:05:00; Y, `" r/ F3 O/ p4 c) W ^% U
Name: Time_Record, dtype: timedelta64[ns]
/ T9 L8 @# N9 ~* f d0 Y! U1$ ]$ ?1 M; D; y
2
7 v" c5 T( B" e- K3$ Q/ a+ v+ ?4 {' R: y* v" g( B0 O1 b
41 s( g* r- S; X! q7 O7 ~ j( F
5
7 I Z) f. ^+ W66 ]) U: U+ e& _$ _$ t! f6 `/ T
7
$ P2 {5 l# |$ f8& Q# v& L* M* V: `) d$ M+ X2 H
10.2.2 Timedelta的运算
( V# U- P. l$ F( {单个时间差的常用运算,有三类:与标量的乘法运算、与时间戳的加减法运算、与时间差的加减法与除法运算:
8 f2 q6 O# L3 Std1 = pd.Timedelta(days=1)* i7 a# l. G+ [3 v3 c
td2 = pd.Timedelta(days=3)2 Y% b( y- S* V
ts = pd.Timestamp('20200101')
3 V- t9 S3 C" B1 y2 J" p; a8 n/ L6 Y3 W c+ n. P
td1 * 2
, d9 k [( U1 h6 i, sOut[70]: Timedelta('2 days 00:00:00')
/ v% t3 b. Z/ y
9 T5 j K3 b; r* Ktd2 - td1
/ ^3 ^- \% Z( S* K/ }& COut[71]: Timedelta('2 days 00:00:00')
' y1 H) l( S, F
# g. [5 A) u2 [! ~) \) o) z1 tts + td1$ W/ G v1 w L5 ], E' c" g6 s
Out[72]: Timestamp('2020-01-02 00:00:00')
6 c8 ^- ?5 Z1 }3 f
; x; C. `1 w3 [& D1 I* t% }ts - td1; O% [+ g- e* U( r
Out[73]: Timestamp('2019-12-31 00:00:00')6 L( y+ s/ }" j1 {% u
1
' `: u/ L7 _( F$ s7 Q( M l# L. R2
3 p: V4 q& ~% W. n' I6 s9 F; v6 d; j3
+ u9 n" C+ T% j" J8 g46 Y) v7 h4 P- L/ v8 v2 N
5$ S* ?5 g- N* ] L/ `. S/ o8 B
6" N5 s. M( u; u
7* F1 i; H4 ?( r
88 ]; N, k; g' h |
9" T- M3 C* P: m8 z. o& C" J
10, ?, ?" S Y) H6 ?& w. g4 M
11& c' f$ G2 Y, K( y: F9 \
12
4 k3 n5 L9 g3 h# e' k4 y13
8 `5 R0 j8 v6 {% [14
5 ~% y% x7 I! u5 E, {+ {154 q' s# s0 ?) d( c" ^- t( _: }) n
时间差的序列的运算,和上面方法相同:2 l0 j8 o7 ^6 H" f2 \
td1 = pd.timedelta_range(start='1 days', periods=5)
: U' C; n/ h- P' k& W1 Gtd2 = pd.timedelta_range(start='12 hours',6 o' _2 P) s0 @3 ?( \
freq='2H',
* N1 I: H6 A. I9 ~ periods=5)) U/ z& J3 G! s3 ~: L# h3 @
ts = pd.date_range('20200101', '20200105')7 v- \1 p% p. j
td1,td2,ts8 j; e2 y0 U7 h4 I! U
' h, ~6 I* _0 v/ F
TimedeltaIndex(['1 days', '2 days', '3 days', '4 days', '5 days'], dtype='timedelta64[ns]', freq='D')
/ y, G5 e9 u( `1 tTimedeltaIndex(['0 days 12:00:00', '0 days 14:00:00', '0 days 16:00:00',3 @! R$ a8 v7 f. t
'0 days 18:00:00', '0 days 20:00:00'], dtype='timedelta64[ns]', freq='2H')
6 g& x1 w) M E/ F% ]DatetimeIndex(['2020-01-01', '2020-01-02', '2020-01-03', '2020-01-04',1 u1 X, `2 U) d, D1 t7 W) m1 ~' @
'2020-01-05'],1 A4 a/ m" G8 d, w" C4 [+ d
dtype='datetime64[ns]', freq='D')" n+ e. k* Q4 ~6 h
1
& b+ D9 F' v% C2 _8 [2: k+ T- N( a1 i5 _0 O3 p
31 Z: |0 @7 X8 @' c: y
4/ m4 m8 u, z; l( D+ g
5$ w$ f6 U! P/ j+ v
6! J6 r) S1 d K/ |- y/ w5 [
7+ w0 [4 ~: \6 l9 e
8
" N( o( Z- f+ t* e$ l9# G9 Z4 X; e! \9 g6 h# |
10/ n- W+ M7 k& w) e6 w: k2 K* _
11
7 a4 L4 E1 h' p4 p# j) j12
: ~' O0 i2 v1 ^4 I1 T3 g( F4 _13
) x/ J0 Y8 }4 Ftd1 * 5
8 e( o) G' [! x- q9 b0 QOut[77]: TimedeltaIndex(['5 days', '10 days', '15 days', '20 days', '25 days'], dtype='timedelta64[ns]', freq='5D')! b" ~- v8 n; Q g; N# M
. b* a" _& a8 A
td1 * pd.Series(list(range(5))) # 逐个相乘& O( H2 V% f9 j3 T4 }$ ?6 r) J
Out[78]:
1 O% R, [0 P" D2 W9 n0 0 days
& D% _( ~) S' Y. J" z+ \1 A$ H1 2 days
& u: _5 i& d8 R6 L$ |+ T6 X/ e; y2 6 days
4 u a: U% N$ ]7 D% v; S3 12 days
$ n/ |# C. t- s; O4 20 days4 a- e- ]: T# K
dtype: timedelta64[ns]
4 K! c1 ?2 ?: y0 z5 J; x5 X1 A3 Q$ i
td1 - td2$ B/ T9 P4 k, Q! W
Out[79]:
" M, f* a5 `# o! M' zTimedeltaIndex(['0 days 12:00:00', '1 days 10:00:00', '2 days 08:00:00',
8 n1 t: b! S) c* k '3 days 06:00:00', '4 days 04:00:00'],
# k: S! H4 {: L/ _ dtype='timedelta64[ns]', freq=None)$ F$ x9 z3 S$ D( B# G( @5 ~
% Y' t* c: W* h' f4 P: ktd1 + pd.Timestamp('20200101')
8 R/ S% ~* ^. ~% z: s; x2 R% }Out[80]: ) H: S8 X( F# J7 c
DatetimeIndex(['2020-01-02', '2020-01-03', '2020-01-04', '2020-01-05',
4 I3 A$ L" m1 `6 Q( L2 l4 t '2020-01-06'],dtype='datetime64[ns]', freq='D')
( A! y' {; H+ K; Q% L: B
6 V" J( j: a" u5 {, Vtd1 + ts # 逐个相加
; W @- e- _( Z, u7 P% [$ sOut[81]:
" Y. X( X( X& B& c i8 H. C+ |% x- @DatetimeIndex(['2020-01-02', '2020-01-04', '2020-01-06', '2020-01-08',
2 S$ A% `8 ]1 z2 ~: P6 m$ v. j '2020-01-10'],& r4 J8 z6 b1 A% P* g; R9 a1 E
dtype='datetime64[ns]', freq=None)/ j1 m! E. F& Z9 ~
2 j0 t$ V( |/ x: V4 u0 z0 j- X1
: S3 Q# E- [8 l7 y4 O5 b' w0 }23 D' `' e1 O, y4 ~
3
. g0 F9 z; P" ?% f4
& F& |1 ?9 u* W. [& h5. U% S8 V4 p/ w
6: t6 F e1 @! Y
7
2 N5 L4 C: n& r4 w8( H9 G, Q( Y5 f1 N
9
8 b9 Y5 w/ {) l5 K' p2 ?10
* b, K! C2 T q# R. u6 |8 Q2 @11
9 \& u- k" q1 d- c125 @& k' N% f1 B/ }# C
13! x* {0 Q: L3 j
14
, Q- A: G4 l! C' G' x" _7 E( E+ H7 l# @155 ~1 _; E6 G0 {5 i
16: [# c2 c; l! _' M6 D
17
. @& b4 m q' ^7 Y18
& @; u7 T& Q: Y( _19
1 a' q* j6 O: l6 y s; L20" j$ j- J/ b7 S2 n/ q1 f
21
' Y2 @; n8 k! U u; b( h( i8 ?) H, k224 p4 q9 s' P6 ~* E
23
( S' [9 W6 h/ S& B& x9 g. i24
" A8 S9 _4 f G. d: T25
- @" ?- R/ N2 f+ I26. }0 v/ t9 h' |! K0 A& u, Z
27
& E" J4 a: F4 H o28$ g+ c$ b+ X/ v0 ~. H! y
10.4 日期偏置
) H. v$ E" y( v9 m) V0 E3 R10.4.1 Offset对象
2 L. Q: o# ^3 O5 t 日期偏置是一种和日历相关的特殊时间差,例如回到第一节中的两个问题:如何求2020年9月第一个周一的日期,以及如何求2020年9月7日后的第30个工作日是哪一天。% ?2 a& \4 I# A% Y. i
( ~/ G4 C4 O, Y+ S& s
DateOffset 类有10个属性,假设s=pd.offsets.WeekOfMonth(week=0,weekday=0),则:
) N9 C/ W8 Q) M; h* I4 @
' i. L' o. P6 U+ o1 W1 B3 Ps.base:<WeekOfMonth: week=0, weekday=0>,返回 n=1 且所有其他属性一样的副本
( u% v2 f1 a% Ms.kwds:{‘week’: 0, ‘weekday’: 0}- P* \- W) |1 \! Z: e; S
s.wek/s.weekday:顾名思义" z+ R+ @$ `# A
有14个方法,包括:
! [$ V K% Y+ L5 |6 K7 _; _" ]) b1 r, t
DateOffset.is_month_start、DateOffset.is_month_end、DateOffset.is_quarter_start、DateOffset.is_quarter_end、DateOffset.is_year_start、DateOffset.is_year_end等等。" e* k# H& l1 r# ~. n G
pandas.tseries.offsets.WeekOfMonth(week,weekday):描述每月的日期,例如“每月第二周的星期二”。
( f, S& g7 o! W- O
3 g6 ~1 t- s6 B/ _% }+ B有两个参数:5 V1 s8 C- o* ?
week:整型,表示一个月的第几周。例如 0 是一个月的第 1 周,1 是第 2 周,以此类推。$ o( | E6 y5 G# R( T, B
weekday:整型,取值为[0,1,…6],表示周一到周日,默认取值为0(星期一)
; R6 K* D% T% K) U2 n; `! N$ {pandas.tseries.offsets.BusinessDay(n):相当于pd.offsets.BDay(n),DateOffset 子类,表示可能的 n 个工作日。: r6 Q8 H! L2 d# S- Q, @9 [) u
: D# Y/ s% k7 K( S: opd.Timestamp('20200831') + pd.offsets.WeekOfMonth(week=0,weekday=0)
# U5 s0 @0 J( v8 D5 V5 }2 X& B$ sOut[82]: Timestamp('2020-09-07 00:00:00')! E6 ?- J6 r: e. d/ V
; G2 K9 t& H; {6 M5 B4 [4 _3 @pd.Timestamp('20200907') + pd.offsets.BDay(30)5 u: v0 J4 g- h+ A* A
Out[83]: Timestamp('2020-10-19 00:00:00')7 }8 X |2 x2 b. {
1
1 V7 S9 O$ V0 q. l, D9 s2
7 X( d$ N7 V1 M, N6 C3
5 e5 s+ n/ v9 [9 }4
' B- g$ W# ~9 h5. j! Q f5 J. B$ x
从上面的例子中可以看到,Offset对象在pd.offsets中被定义。当使用+时获取离其最近的下一个日期,当使用-时获取离其最近的上一个日期:
2 y/ I, Y1 k2 t ?, p2 G- D
, l& o, j6 J7 Q# x# spd.Timestamp('20200831') - pd.offsets.WeekOfMonth(week=0,weekday=0)2 W( k" m4 Q, M+ |
Out[84]: Timestamp('2020-08-03 00:00:00'): ~5 w. `; l1 e9 g( y; X, G
! \0 ^: ^* Y+ Q6 Q' T) |. Q0 m
pd.Timestamp('20200907') - pd.offsets.BDay(30)! c+ C4 c8 ]0 T% n2 m1 b0 @/ e
Out[85]: Timestamp('2020-07-27 00:00:00')5 C+ J% \2 ?5 K4 A% f: ]+ q- g9 T& ^
1 Q7 ^# C+ R- L: H0 }8 s; X# M4 ]% npd.Timestamp('20200907') + pd.offsets.MonthEnd()6 x* v9 s. T+ O& u0 p" E6 E, } [
Out[86]: Timestamp('2020-09-30 00:00:00')5 _" C4 V" U$ K8 P& O! J2 c2 x9 K
1
' y4 s( `7 k: k; n' R$ ^28 g/ [) K! L, `# ^9 k& [3 P
3/ l# u9 B( J! Y. u% H* \; F
4
; b/ N; z# d- N: B5
$ b) H/ e% x$ B60 L- j. F6 Y( Y) X% W
7' P* W# q7 Z' H5 i% R8 \) Y* s
8. D% }1 R# ~9 C0 n& j7 y
常用的日期偏置如下可以查阅这里的DateOffset 文档描述。在文档罗列的Offset中,需要介绍一个特殊的Offset对象CDay。CDay 或 CustomBusinessDay 类提供了一个参数化的 BusinessDay 类,可用于创建自定义的工作日日历,该日历说明当地假期和当地周末惯例。
3 e& Q9 p6 ?8 y7 T1 P5 w) Q7 J2 v6 X 其中的holidays, weekmask参数能够分别对自定义的日期和星期进行过滤,前者传入了需要过滤的日期列表,后者传入的是三个字母的星期缩写构成的星期字符串,其作用是只保留字符串中出现的星期:) G" Q7 t$ [0 I' Y; y, X
) o, O$ y" T4 s; d7 pmy_filter = pd.offsets.CDay(n=1,weekmask='Wed Fri',holidays=['20200109'])* I4 s! D& o4 |( N4 x, T: F
dr = pd.date_range('20200108', '20200111')% Z0 \) ]; j( z$ ^1 c/ q4 d2 l. q, s
/ z5 E. a* B/ R1 O0 cdr.to_series().dt.dayofweek
, o' V2 k" m1 M. M$ i, kOut[89]:
F$ T+ b! f5 x2 y# A7 H5 d, f2020-01-08 2 g3 D9 b: W7 i! `! }& H0 @0 q
2020-01-09 3
+ @4 d( z9 {) P% ?( }+ S( r2020-01-10 4( A3 U6 B* j" H
2020-01-11 5# k; q; N+ k4 J
Freq: D, dtype: int64+ W2 w/ V h d
9 p% L) `0 j: s5 o
[i + my_filter for i in dr], @9 @8 p0 b4 i! z+ G: a
Out[90]: $ P4 B) P( ~3 v8 H
[Timestamp('2020-01-10 00:00:00'),4 l* b2 ?% ~$ o8 n) h3 o c7 r9 c0 X
Timestamp('2020-01-10 00:00:00'),
7 \+ `+ p- F, Q" U Timestamp('2020-01-15 00:00:00'),
$ X% H0 s1 d3 Q( N5 a' p/ k- d+ g Timestamp('2020-01-15 00:00:00')]
$ k) Y0 G$ i# C* F" t$ Y, e# p' m+ `4 p. u" c* h
13 M j9 [2 G7 H9 h
2; @; J5 t% \: C8 |/ F2 x+ J* y
3
- u- E7 U8 U. {0 k3 m+ m1 m4
2 y2 W1 [2 q! s56 C. p9 ?% x; l9 r8 j
6# a' o, Q) d6 ]3 Z- Q& ]
7; Y8 X- _' o% h! b
8+ o6 B& l6 T6 B4 I0 W
9
6 h m& j6 s6 \" J$ q) }10# k5 o4 k* @% q
11
0 m: y7 r5 Q8 ]; t$ ?( _! y128 b( _+ C' [9 I: H
136 W+ X, q z8 g$ X8 ]8 I7 S" T, `
14
( w, h6 H6 w" Q/ A1 N) r15: r/ k& j( h0 E6 b. ?' W" R
165 ~0 q: e- u! k% o* e2 o/ B
17
4 r9 l$ D& G; s0 a% u 上面的例子中,n表示增加一天CDay,dr中的第一天为20200108,但由于下一天20200109被排除了,并且20200110是合法的周五,因此转为20200110,其他后面的日期处理类似。/ [" f( S1 V9 v* g/ V: m( l
- k) m9 @1 ]3 c% _2 F【CAUTION】不要使用部分Offset
/ v: t' W# i) G在当前版本下由于一些 bug ,不要使用 Day 级别以下的 Offset 对象,比如 Hour, Second 等,请使用对应的 Timedelta 对象来代替。
" v5 @, m* a3 p1 U3 D8 X* v: G
. i9 _' a% i) F3 f' U, Y$ Y( w10.4.2 偏置字符串
: J, S3 Z4 A+ I5 B, U' V. q 前面提到了关于date_range的freq取值可用Offset对象,同时在pandas中几乎每一个Offset对象绑定了日期偏置字符串(frequencies strings/offset aliases),可以指定Offset对应的字符串来替代使用。下面举一些常见的例子。0 K' n) f3 g [, f. E
$ Q$ q x3 A# k0 ^: ~
Offset aliases:pd.date_range函数中的freq参数,为常见时间序列频率提供了许多字符串别名。 也称为偏移别名Offset aliases。偏移别名列表点此参看(大概27个)。4 i& f9 }+ A, U& e6 M3 w
; @( K6 E& j3 [ k; x, Q: Y+ Q
pd.date_range('20200101','20200331', freq='MS') # 月初& ?& s" o, W0 @ E5 ]: Y
Out[91]: DatetimeIndex(['2020-01-01', '2020-02-01', '2020-03-01'], dtype='datetime64[ns]', freq='MS')
* \' p7 V" ^3 t& _- F4 `$ o
. I. i, _' k T. D: Fpd.date_range('20200101','20200331', freq='M') # 月末
$ A; P& j& Q$ R3 {Out[92]: DatetimeIndex(['2020-01-31', '2020-02-29', '2020-03-31'], dtype='datetime64[ns]', freq='M')8 P2 Z* `9 Z: m' t) ?( v* P
4 W& o4 W) E7 r. h
pd.date_range('20200101','20200110', freq='B') # 工作日. R, a! c- v+ b
Out[93]: 2 V. p* b$ B, a4 {
DatetimeIndex(['2020-01-01', '2020-01-02', '2020-01-03', '2020-01-06',
7 l% i4 ?# j# T4 g* } '2020-01-07', '2020-01-08', '2020-01-09', '2020-01-10'],7 K9 `3 q* Z z. J0 M2 R* @" ~
dtype='datetime64[ns]', freq='B')0 L) L. ]) A; g: A L
+ W& ^5 n3 e0 E! o5 g; R7 |, v
pd.date_range('20200101','20200201', freq='W-MON') # 周一
; \) g0 g1 o9 ~( Z% q- a* sOut[94]: DatetimeIndex(['2020-01-06', '2020-01-13', '2020-01-20', '2020-01-27'], dtype='datetime64[ns]', freq='W-MON')
, o6 f/ G+ {3 F4 k( I2 q3 X
( }/ p1 k" n, {8 V- ?8 lpd.date_range('20200101','20200201',
5 H c0 E! O3 z k freq='WOM-1MON') # 每月第一个周一
* M& ]( V. J5 N
# u' D* p; m; \& ^Out[95]: DatetimeIndex(['2020-01-06'], dtype='datetime64[ns]', freq='WOM-1MON')
) U; E% e6 |! W% H) n6 q& @' p
5 M; {* e9 E4 f& K" @: f1
k5 ^" A3 s/ d" w* C% j4 Z2# Z3 [2 n5 |4 _
31 P X; E. S3 q% c k. I& n% L
4
7 h- e2 B% w; q! Z: B$ c+ C9 f3 q5* X$ V, w( P$ J! ?
6
, N8 h* O% Y: w" r7 A! C: _6 S7 {( b+ R# {' t3 F
8
5 L; d6 ^# f- X8 Y9
8 W- ^4 Q8 g' a# E10' m1 Y% j, k6 d, Q& l6 ~" B7 Z
11% C" K8 p" m' E3 w
12
7 O' V4 l: b7 ]3 S3 z5 i+ i13
8 T9 Z. a, P+ q- a* N+ T O148 I1 r, x( I* }1 M2 K( u! l
15# h1 f; E u3 x6 t5 d( v
168 ?/ Z {/ J8 z0 z; ^% g. G0 x8 r
17
! I8 O6 V5 l9 l/ N+ g: }/ N18
: L. K- B6 Y* G. l) q; E& V1 `19
, q+ s2 U6 j/ m- z* Y" K上面的这些字符串,等价于使用如下的 Offset 对象:. L9 c9 \. Z$ }6 j/ t+ Q- ^
% m8 n7 o1 o% O& U6 X, @pd.date_range('20200101','20200331',
/ K |5 n* T, B; y3 {+ w( d freq=pd.offsets.MonthBegin())
8 s0 \7 ?& r8 M( m' ?' ~
2 l3 H6 t" W" r1 n H( LOut[96]: DatetimeIndex(['2020-01-01', '2020-02-01', '2020-03-01'], dtype='datetime64[ns]', freq='MS')! N$ {+ w* v D0 H
! l9 z4 v* L/ X% }pd.date_range('20200101','20200331',& H) d J. m y* _# q; ]( ?
freq=pd.offsets.MonthEnd())
1 D1 w/ A$ M* k( M( U7 Q- ?% |9 D) y6 F/ i! h
Out[97]: DatetimeIndex(['2020-01-31', '2020-02-29', '2020-03-31'], dtype='datetime64[ns]', freq='M')
1 A8 a6 q2 j: a5 R1 O/ A
' U( y& b" o# `$ ?1 q h4 K. Epd.date_range('20200101','20200110', freq=pd.offsets.BDay())7 K5 d* n6 I! Q1 I8 e8 r6 {1 p4 X
Out[98]: . k% l1 ~8 X9 ~- O' {/ V/ u. u1 y
DatetimeIndex(['2020-01-01', '2020-01-02', '2020-01-03', '2020-01-06',8 K9 x; q t! O
'2020-01-07', '2020-01-08', '2020-01-09', '2020-01-10'],
0 \& H! o/ ^; d% k dtype='datetime64[ns]', freq='B')! c# c: ~+ h0 k# f( `; ^$ A1 u
6 T' `6 q! ]5 g# L/ C5 R
pd.date_range('20200101','20200201',! n6 y/ j6 |+ R
freq=pd.offsets.CDay(weekmask='Mon')); h& W7 n& L6 _# f0 f) G
* l' D3 H! `! I6 fOut[99]: DatetimeIndex(['2020-01-06', '2020-01-13', '2020-01-20', '2020-01-27'], dtype='datetime64[ns]', freq='C')& B; e+ R% m$ d4 B6 o
) q+ s" }; }. d8 `. xpd.date_range('20200101','20200201',
& |( c9 {4 v# P2 v7 e freq=pd.offsets.WeekOfMonth(week=0,weekday=0))
7 q' h% c' ^! j: C$ P
' ^8 K! ?$ h6 K& w) Z* F2 q( X; {$ G/ hOut[100]: DatetimeIndex(['2020-01-06'], dtype='datetime64[ns]', freq='WOM-1MON'). V3 y! V- I5 w8 c
& ~" Y, W: Z( v3 ^7 q
1) _3 Y9 M, n7 |/ M: c5 O
2 y& j# B- G; f- Z6 v+ E
37 Q0 K( C" E0 v' M# h
4 T6 I6 I) I. B3 U+ s
5) i; X/ h! P3 s$ V" i
6! @0 p% V) o& R0 X) q, n5 w/ L8 P
7
, ^8 N% ~& ?$ @0 y8* b) N% `$ a) R# Z
9
: m5 ^ z: w3 M* c* I+ A0 Z106 Z% y5 Y. p7 ^0 b% u, B
118 s# F* @7 o1 V9 J
124 ~2 g! H: V2 j& d3 O4 r
13$ o5 _. Q+ p3 ?/ a7 _
14
5 }' N4 T r7 M2 O0 X/ f15
C* c3 f* p0 ?% q3 y. R7 v2 R16
0 o0 F& H u, H17; N$ X, q* D6 R7 f9 W7 t
18
2 S: @. F2 o" z1 z* ]! h3 R198 j( d7 _: x* H5 L3 Y7 x* Q
20 b( V& u! M- ~" R) ^& N8 k
21
) F" I) z5 d, y' B22
4 j. Q( @6 b5 k. L7 F1 q/ A9 U23' y# f/ S1 m: {* W9 I. \/ k. _, l
24
d( K9 \5 w3 [/ N( j( f$ t3 m/ b25, c2 B" }, u2 P6 x6 t
【CAUTION】关于时区问题的说明
- L# y! N9 o. ^. [; u0 b& A 各类时间对象的开发,除了使用python内置的datetime模块,pandas还利用了dateutil模块,很大一部分是为了处理时区问题。总所周知,我国是没有夏令时调整时间一说的,但有些国家会有这种做法,导致了相对而言一天里可能会有23/24/25个小时,也就是relativedelta,这使得Offset对象和Timedelta对象有了对同一问题处理产生不同结果的现象,其中的规则也较为复杂,官方文档的写法存在部分描述错误,并且难以对描述做出统一修正,因为牵涉到了Offset相关的很多组件。因此,本教程完全不考虑时区处理,如果对时区处理的时间偏置有兴趣了解讨论,可以联系我或者参见这里的讨论。- d& B% G7 B' |: w% |% k J
+ \% i j/ U% x) A* g
10.5、时序中的滑窗与分组" S" [ W. E! h: W
10.5.1 滑动窗口0 O$ n" b+ v/ ?1 z2 U
所谓时序的滑窗函数,即把滑动窗口windows用freq关键词代替,下面给出一个具体的应用案例:在股票市场中有一个指标为BOLL指标,它由中轨线、上轨线、下轨线这三根线构成,具体的计算方法分别是N日均值线、N日均值加两倍N日标准差线、N日均值减两倍N日标准差线。利用rolling对象计算N=30的BOLL指标可以如下写出:
, [7 \, {7 d4 D; f
/ c$ X6 P/ [6 k5 c) X W/ |import matplotlib.pyplot as plt) y! q. y( f6 q2 N# X
idx = pd.date_range('20200101', '20201231', freq='B')( u. ?& l; j2 k. t P
np.random.seed(2020)
0 z0 J E8 h" L6 d
% ]2 F$ O! q3 T Q$ p# M1 P, fdata = np.random.randint(-1,2,len(idx)).cumsum() # 随机游动构造模拟序列,cumsum表示累加
; L% i, ?5 r t" Ts = pd.Series(data,index=idx)
9 o& c1 ]( H u& Js.head()" Y* a& \3 O/ g9 F! K% L. R; _
Out[106]: 9 R- y# f+ S. E/ W
2020-01-01 -11 F3 i, l- Y) c, U! M9 V
2020-01-02 -25 g; a4 V0 S; t- ~; s5 r# x% l
2020-01-03 -1
" r8 I: q, x$ Y2020-01-06 -1
) {- c2 I; Y8 [& ^2020-01-07 -2
# L+ Z- H6 }$ @# ]Freq: B, dtype: int32' u' o& o% t' D! |$ O4 \
r = s.rolling('30D')# rolling可以指定freq或者offset对象+ h% V1 F/ c% U# H
+ e0 f' q- C1 X$ D
plt.plot(s) # 蓝色线
- p3 G F8 D6 t( U3 oOut[108]: [<matplotlib.lines.Line2D at 0x2116d887eb0>]7 P2 v) X: X* D: X/ b
plt.title('BOLL LINES')
` j, E6 @! E& S" q1 A, SOut[109]: Text(0.5, 1.0, 'BOLL LINES')1 i5 ]5 f* ]3 N; t
$ U' l' J0 B5 A5 a1 c& [' `
plt.plot(r.mean()) #橙色线
/ i: r, j4 D" _; `) v9 @Out[110]: [<matplotlib.lines.Line2D at 0x2116d8eeb80>]
8 Q4 X3 h1 Q$ ~2 D
2 H& x; a+ j7 `( \3 Vplt.plot(r.mean()+r.std()*2) # 绿色线' P2 @$ }4 g: H& i. e
Out[111]: [<matplotlib.lines.Line2D at 0x2116d87efa0>]
. M/ x, A, d1 G
" g q- N6 X4 k |plt.plot(r.mean()-r.std()*2) # 红色线6 L- [3 _# q2 r% [7 _8 t1 o
Out[112]: [<matplotlib.lines.Line2D at 0x2116d90d2e0>]
/ n5 s6 g( l2 g3 w( v
1 e% y6 B- y5 [* N b1 {7 \$ Y( e. ?: Y11 |9 k8 t" P. o! L) A
2
5 L3 q% q- L+ ~& e3
3 v; v* y$ V9 y' N2 m, I4 p& `% A4% c) i, d5 k& ]% ~8 U$ S9 \' o
57 k) e- p, p% ]/ |* @
6$ l( M+ |& X6 O" i
70 ~* X0 f% W5 x7 a$ P
83 q/ }& V& E$ P; m! C ]
9( `4 s. b) @4 @: U
103 ]3 U' ^" j* @+ A! J0 _& T8 \
11
- W5 B* j7 u/ i9 v" ?12
; d T9 s2 J, ~- D& S13
) z+ W; P/ m: X ^7 v/ A+ W9 v14
& T7 W9 R5 ^' G4 f1 A15
$ i0 T/ R! t3 R; h/ K16) T' k, l L6 A2 J" z
17
6 W, J8 S) \5 k; E5 u! g% k% Y0 S: x18
K! E: E5 ^# y6 O19& E$ d% e. G/ n6 G. \! J
20. f* H) [" g: O2 Q- F8 j8 I5 v7 P3 o
215 O7 l$ T: \& y2 N
22& \% F8 f) w$ G5 H
23
: h+ _, D! J: k A+ Y24
. I1 @* m- e2 K25/ j& H/ |) s8 V5 u5 S
26+ ?3 z* X1 [) f0 i6 t2 j2 N
270 k% U2 }% {( g" @
28
( b5 _. ?+ }$ F3 X# M292 `8 |, t# V ?8 h
" W2 c5 A2 L3 R- {0 j 这里需要注意的是,pandas没有实现非固定采样频率的时间序列滑窗,及此时无法通过传入freq字段来得到滑窗结果。例如统计近7个工作日的交易总额。此时可以通过传入多个函数的组合来实现此功能。+ j+ f1 u: B# J5 I7 J9 k& n
首先选出所有工作日,接着用普通滑窗进行7日滑窗加和,最后用reindex()恢复索引,对于双休日使用前一个工作日的结果进行填充。
0 O" K& M6 e4 k2 v; |
$ B& X( p: t3 u* Rselect_bday=s[~s.index.to_series().dt.dayofweek.isin([5,6])]
1 [2 r( h& u b) c) t. fbday_sum=select_bday.rolling(7,min_periods=1).sum()* `( s* F# |- k' Z6 o& j. f
result=bday_sum.reindex().ffill()
1 T9 ]- g% X* i$ @5 S9 |+ W' Y1 t/ |result
+ |7 e0 B/ n# Y: F( W/ m# K, F3 e' h9 [ [4 Z% Q
2020-01-01 -1.0
! U1 I. S8 J+ x2 C3 s$ `* O i' S4 ~" }2020-01-02 -3.08 ?/ y* q$ n9 m! r
2020-01-03 -4.0; ^( P! O+ p5 o. t5 B9 s
2020-01-06 -5.0
! V) W6 W2 M7 y) B6 b2020-01-07 -7.0
* o; U3 C& _8 X$ u+ y ... 5 d- B+ A7 H: p: {9 u! s
2020-12-25 136.0
5 Y# W& @" L/ b! E5 t ^2020-12-28 133.0
' a8 @# {& z% H' |/ [5 ]& I2020-12-29 131.0! g+ r0 _% I2 }! l8 V: Z- @- C3 e
2020-12-30 130.0
6 p5 O$ [- _/ V0 }" Z3 A2020-12-31 128.08 G/ K2 s' ]) _: `8 [, t3 p) g
Freq: B, Length: 262, dtype: float64
* r( `, g! N* E/ a7 R! C# m) }9 }5 L, S4 ~4 S, ]4 G/ |
1% q) K3 j1 X/ C5 \/ @
2) z% ~* }! f7 p5 Y! v4 F9 U
3. E x* o( V6 ^; x) N- _4 S
4
: y6 S- \, q5 H O( q5" P8 F O! w1 \1 S c# ^
6
. |; Y8 T, h6 g7
( c$ F& J+ F% b! e/ N7 ^89 _7 R" o+ E6 {7 x
91 K' I- k* c# I1 u; H
101 t& q9 I0 I+ @" y
11
- f3 Y# H7 e) E125 H- F8 x7 q. k
13
6 H! |- \1 V. W3 Q0 F4 M2 E2 B; ]14! H7 E" S' l$ `# W
15! n4 v n$ O2 y9 y
16
# e+ j. p* N% i: @- @, I17
; G1 U, j& l/ ]$ x% K' ^5 C shift, diff, pct_change 是一组类滑窗函数,它们的公共参数为 periods=n ,默认为1,分别表示取向前第 n 个元素的值、与向前第 n 个元素做差(与 Numpy 中不同,后者表示 n 阶差分)、与向前第 n 个元素相比计算增长率。这里的 n 可以为负,表示反方向的类似操作。
& B, S* X; P. o# I# e0 G
3 C' Y# x3 y H f" y 对于shift函数而言,作用在datetime64为索引(不是value)的序列上时,可以指定freq单位进行滑动:. T5 g& n- ]3 I+ d( z) G4 q2 u
7 N, z& @$ i' }& p( J# y1 O- \s.shift(freq='50D').head()* `- `. i( f' T* F7 C3 J
Out[113]:
! y0 a/ C% G* a8 \& D2020-02-20 -1
3 u1 E& z' m) ~' S0 N( D& L2020-02-21 -2" f& r' V9 A5 [( l& u) U* ]
2020-02-22 -1$ {2 }; r0 j/ G7 T
2020-02-25 -15 B! g, f4 m9 A
2020-02-26 -29 U+ W6 L) Z9 O3 o, w$ @
dtype: int32
t2 L# _! Y; w% p18 o3 j& F: A) P* ~
2
8 }+ f9 f) s1 h! C+ F39 A" j- J4 W9 w
4
! M2 d& c; W4 W/ I5
8 d9 ~5 `, m2 D/ S& |- H+ [% H% m6
0 a* A: E3 U {. ^8 m' G74 h6 b1 \( L$ Q3 w" f* x2 ]0 x
8% @* a# T* H+ n% A/ n+ L' ]; a
另外,datetime64[ns]的序列进行diff(前后做差)后就能够得到timedelta64[ns]的序列,这能够使用户方便地观察有序时间序列的间隔:
! |- j! a$ r9 s; `2 F4 I7 \/ R$ ~4 D8 M* o; Z, F6 y8 \
my_series = pd.Series(s.index)* d6 f4 v* n) n8 ?! s5 \5 U% Q1 x
my_series.head()8 g' K0 G+ ^- {! L1 B1 Q% |
Out[115]:
S6 N5 |) @$ ]" G- A0 2020-01-01
: c9 |+ d# U! v$ E' M1 2020-01-02
7 {9 j1 ^/ Q' E$ j. G) Q7 a, Y2 2020-01-035 J) U! {0 L3 v( |. s+ n6 K; R
3 2020-01-06 ~4 b9 @; q/ }( G. G/ G
4 2020-01-07
9 G* J0 f$ }/ S+ gdtype: datetime64[ns]; X8 x2 W1 M& E" f; R8 D! Y
0 D1 M: [: s: L' s
my_series.diff(1).head()
# ?/ i) \( L" l2 R% OOut[116]: 6 A% X j- ~+ Z! C7 @: }
0 NaT
" N) b1 @& o4 Z$ |. @1 1 days
% K" z% B% z, f2 1 days* }5 [4 ~* z6 b' R& h% z
3 3 days
3 b7 n7 F2 T& ]+ T4 1 days
- i# _/ j2 V. Ddtype: timedelta64[ns]
; Z: b3 X. Z& w! F& O
' c5 S: K5 v0 ~" y1
. O# p/ A8 u+ n( Q, }21 h8 b { ~ [7 n' h9 V
3# T J* M: c) X ?' C
4
2 Q0 A- a) p7 \0 O/ g+ h5! X2 k7 I" f4 h6 y: D
6
1 l: Q# @/ K& h+ F4 j8 f! `( }% \7
& Q' u8 S( h5 e8
3 H1 m4 ?, x, A$ W/ w' P) d; K9* N y4 h+ A# _% e8 U; Y
10* _5 C2 n c" R1 D( q6 q: u$ K; H
113 m* u: P& M% h5 E& ^2 f
12
7 Y: Q# o: W5 u- D9 k13
8 G7 h1 Y Z2 t2 m; y14
; ? o3 m% a4 q: v, Y5 r0 B# g15; l; W$ K; E2 J+ E% u$ e
169 B0 n( P/ D) X$ D& w! Q" s) s5 o/ T
17. F. W! I& T! K& r9 y9 K* b5 y
18
7 }9 k: l0 U- S8 k10.5.2 重采样, h0 d) F3 T8 j
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). ~8 r1 Y4 W4 Y1 E2 Z4 q- ^5 B
常用参数有:& x* H( N+ p. k# f
8 W+ `9 }: S, w
rule:DateOffset, Timedelta or str类型。表示偏移量字符串或对象1 `5 }" Z$ F& Q7 ~
axis:{0 or ‘index’, 1 or ‘columns’}, default 0。使用哪个轴进行上采样或下采样/ J' q0 ^' i2 W
closed:{‘right’, ‘left’},默认None。表示bin 区间的哪一侧是闭合的。所有offsets的默认值为“left”,但“M”、“A”、“Q”、“BM”、“BA”、“BQ”和“W”均默认为“right”。( g2 V. @2 Q1 y5 D7 m, @; ?
label:{‘right’, ‘left’}, 默认 None。hich bin edge label to label bucket with,所有offsets的默认值为“left”,但“M”、“A”、“Q”、“BM”、“BA”、“BQ”和“W”均默认为“right”。
% i; E8 L! H! ^convention{:‘start’, ‘end’, ‘s’, ‘e’}, default ‘start’。仅针对 PeriodIndex,控制是使用rule的开始还是结尾。9 M9 x. k) V8 u! l* x# {6 s! n
on:字符串类型,可选。对于 DataFrame,使用列而不是索引进行重采样。列必须类似于日期时间。
! W7 j+ e2 O' W. N( |; flevel:str 或 int,可选表示多重索引MultiIndex的级别,这个级别的索引必须类似于日期时间。
+ r" ~' ~! n; p5 [1 P) D4 j' Eorigin参数有5种取值:
& k& d: o% V0 |5 y& C8 e‘epoch’:从 1970-01-01开始算起; D# ]! @$ [% Z' m y$ F: Q6 s9 B
‘start’:原点是时间序列的第一个值
: v1 P( P5 `9 a‘start_day’:默认值,表示原点是时间序列第一天的午夜。
- H' \: Y t7 X# W; }4 K'end':原点是时间序列的最后一个值(1.3.0版本才有). @( T, P' n; i7 j1 X/ W
‘end_day’:原点是序列最后一天的午夜(1.3.0版本才有)
5 j7 x0 {! t3 w. f- E' j. }offset:Timedelta 或 str,默认为 None,表示对时间原点的偏移量,很有用。 t3 j3 T- N! N3 |, }+ V
closed和计算有关,label和显示有关,closed才有开闭。5 s7 [. _5 Q/ @9 a
label指这个区间值算出来了,索引放区间的左端点还是右端点,closed是指算的时候左端点或右端点是不是包含。+ k1 Y. I$ O, L
q) a: p7 I0 P& e8 g/ s: \
重采样对象resample和第四章中分组对象groupby的用法类似,resample是针对时间序列的分组计算而设计的分组对象。例如,对上面的序列计算每10天的均值:1 E! o" O. C& K/ v8 {' U
s.resample('10D').mean().head()1 }! n* {, Z! E4 j
Out[117]:
" S8 E) w6 t/ N. V' i2020-01-01 -2.0000008 Y8 ~6 @* f" Y3 b: V1 K/ |
2020-01-11 -3.166667
$ E7 l7 y% @. }! B) h( }2 B) x2020-01-21 -3.625000
" i2 C0 i' _* S+ C: v2020-01-31 -4.000000
9 l5 }4 x0 C5 m2 Y3 [7 i2020-02-10 -0.375000
9 h& u* X; ^# u# ^) G2 FFreq: 10D, dtype: float64- j8 c0 ]+ T6 N# D+ W
1
4 T/ X: a( F8 }7 r9 K( ~5 M1 F% k2# M" U5 {) H E/ m
32 j5 {$ P- K% B6 p! C
4# X+ Y K2 g% { ~2 Z4 d
5
9 {/ `* T1 A! o; x# x, t. |6
5 o6 k; O9 C! l6 A$ l! z- S; ]$ ]74 u% z2 o) |& C% Y( J
8
& r8 ?8 ]4 W( ?! q可以通过apply方法自定义处理函数:9 n+ _; \/ K* P4 l; ?4 W) [
s.resample('10D').apply(lambda x:x.max()-x.min()).head() # 极差3 b# I4 T' Q) R f) Y4 s
1 S$ j" K0 Z' u4 V$ d8 z; rOut[118]: % G/ D" b& p+ N9 \& w
2020-01-01 37 h5 j3 n7 t- n( e+ y7 ?0 t
2020-01-11 4% [' P: G0 C7 R- q" g
2020-01-21 4
" j4 `+ Q" I' l2020-01-31 2' t& [$ p1 w! ~4 W& O; A$ }
2020-02-10 4
2 H# v4 ]+ V. } C, k9 w0 I mFreq: 10D, dtype: int324 v' B' b: Y9 f
1. \7 X6 \2 ?* M' ^" I" m' a
2
; L" U. J9 v- ?* z) |3
3 e' Z& X8 t) k! v42 {! Q5 d8 G3 B
54 T2 ?3 l6 |1 B/ A8 P3 [4 r0 v& d
6
. I( ]6 B, k+ n3 i* x5 D4 O M7/ T& L! `0 s4 S# e- Q
8
& D6 S, i) o% ~/ p6 r93 s. Y: H d) `. S
在resample中要特别注意组边界值的处理情况,默认情况下起始值的计算方法是从最小值时间戳对应日期的午夜00:00:00开始增加freq,直到不超过该最小时间戳的最大时间戳,由此对应的时间戳为起始值,然后每次累加freq参数作为分割结点进行分组,区间情况为左闭右开。下面构造一个不均匀的例子:
4 C: Y- s1 }3 @! K8 @) d: [( C8 Z9 d$ f
idx = pd.date_range('20200101 8:26:35', '20200101 9:31:58', freq='77s')
" o5 Z% a. _ n0 R8 U$ {- O0 qdata = np.random.randint(-1,2,len(idx)).cumsum()# ?7 p) f a0 ^& L% N
s = pd.Series(data,index=idx)
7 E" H! @! w: W! {1 Vs.head()8 C( m! ^/ E8 X+ l
; m5 z. b( B, \7 y- \. f2 e
Out[122]: k! m1 V0 l& A
2020-01-01 08:26:35 -1: ]" p% G8 d- O7 J% R
2020-01-01 08:27:52 -1
0 C3 g* k7 Z7 J, v0 ?5 T9 }) t2020-01-01 08:29:09 -2: b9 f) I8 ^3 a C" R* f
2020-01-01 08:30:26 -3
& H% _/ I& h) S3 E0 F2020-01-01 08:31:43 -4* B' j* o" s+ y1 E' k! U$ Y
Freq: 77S, dtype: int32
" ], F* I- b7 q( _; b8 O13 ], E3 G$ U, n# H7 q0 P- L
2
- e" S# `5 k7 @ q' o4 _3
2 h1 s/ y! l; O" o& }1 g1 T& b4/ n% A3 t7 @5 ]* ~7 U: x
5
: t0 D0 P% j+ q( w3 o& W6% n" e: K) v1 ]. A
7; w5 I+ |0 h2 Q; |2 W( D
82 S4 u9 K1 i' @; ?' k* s
9
: U8 }: X, e. P- Q, E; r% a10
& n0 x: N* x& q2 j! o5 j113 r# j, ]. K, Z" ~% s# q1 G2 B
12
5 C& y4 P( m3 Q' \, K' P5 k 下面对应的第一个组起始值为08:24:00,其是从当天0点增加72个freq=7 min得到的,如果再增加一个freq则超出了序列的最小时间戳08:26:35:' l. h8 Y% g+ S% D
5 g4 Y8 t9 c( Ts.resample('7min').mean().head(). g3 J6 F; w* S; m: X. b. n9 h
Out[123]:
1 i/ D- y3 H8 R7 q' @6 S# t1 J2020-01-01 08:24:00 -1.750000 # 起始值,终点值包含最后一个值
- L: H2 _; A7 w7 ?2020-01-01 08:31:00 -2.6000003 u% l6 W1 H) x0 |' e! h! W; m
2020-01-01 08:38:00 -2.166667
, K0 c) l; {- V3 [( N7 v2020-01-01 08:45:00 0.200000
0 D' v. n8 u2 ?' m2020-01-01 08:52:00 2.833333' I7 X4 y9 W5 x% Y
Freq: 7T, dtype: float64$ R3 e: X' i4 U- ~$ Z% ^# q
1
' i! `- c% A- |1 k2& r1 d" h B) a# ^' X+ l; A/ S
3
) s2 j: G/ K5 G$ H& ~* M7 L4
7 d4 b4 r% u4 ]# c/ q5
% L: p% v F9 n9 }5 Y B6
# E8 ~+ h7 T! ~3 D1 e8 a8 i! ^$ E3 r7
1 ?0 F( O1 W4 y3 C8# Z/ H% `! @% S- L9 d0 n
有时候,用户希望从序列的最小时间戳开始依次增加freq进行分组,此时可以指定origin参数为start:
' Y$ m" `$ U# t# X* N [2 S0 ^
1 \$ p/ j. E8 w F$ S- l) Ms.resample('7min', origin='start').mean().head()
: u: W, s8 O0 J" H, {% |Out[124]:
# p8 s: c/ O' Y1 y/ W* M0 a0 c2020-01-01 08:26:35 -2.333333
% d& W5 Q' |$ F# G, y, b2020-01-01 08:33:35 -2.400000! U5 i6 Y$ p2 M5 D
2020-01-01 08:40:35 -1.333333
b$ y' D' b, l& v/ h) y2020-01-01 08:47:35 1.2000008 @9 f/ [! X) z, o: A8 r
2020-01-01 08:54:35 3.166667
) r; R) k; K0 c; Q. fFreq: 7T, dtype: float64, I% g! G7 ?8 Y7 |
19 L* V( P, \( G- t# {9 N
2) ?1 E1 V M% b0 `+ n/ [
33 r) ?# h; ^3 s+ V; p
42 j9 X, d& [: J! j$ T: }" t# I X7 j
5
, S$ h, S6 o1 o- A6 H6
3 g" u& j6 `9 t/ d3 }4 A! _7! Q/ _2 w/ Z# |7 V8 |
8% t/ e0 W3 f- o& U; d9 S" L
在返回值中,要注意索引一般是取组的第一个时间戳,但M, A, Q, BM, BA, BQ, W这七个是取对应区间的最后一个时间戳。如果想要得到正常索引,用’MS’就行。1 E+ t' o) O/ V/ ]- T
: W! c: ]/ R9 W& R+ a5 Ys = pd.Series(np.random.randint(2,size=366),; I" _; X6 H# R/ g- _5 X# {) r [
index=pd.date_range('2020-01-01',
2 k* z- {) P8 ^+ i, d5 u '2020-12-31'))) E) p; {9 j$ m1 Y1 h0 i8 y
. ^! j2 o, D$ j" q; r0 a9 A! h
0 b' [. e- n, H+ W7 a- Ps.resample('M').mean().head()
( E7 @$ A- U7 `% N/ YOut[126]: ( ]& J8 x8 w ]
2020-01-31 0.451613* }8 C8 Y+ h9 I' G
2020-02-29 0.448276, R9 V7 S) I# k: k4 k* x3 ~4 T- [
2020-03-31 0.516129
l# z( Y" T$ J) z1 \$ k2020-04-30 0.5666674 B4 h% m; p v: V8 d0 D6 c
2020-05-31 0.451613( z- H: m0 U m
Freq: M, dtype: float64
5 Y: F& O6 m8 k3 }& `) `
c7 a6 v/ I) P, y3 w5 l8 Ps.resample('MS').mean().head() # 结果一样,但索引是跟正常一样
: T/ i. K- _& ~Out[127]: % l5 M6 ~4 [& U3 a9 p! u
2020-01-01 0.451613/ f( q7 p0 D: ~( n
2020-02-01 0.448276
4 k0 K+ }4 v6 E% ]2 T7 [2020-03-01 0.516129# n' q8 Y( z% b3 F
2020-04-01 0.566667
' D$ }9 c# D6 F/ C9 \; q3 F2020-05-01 0.451613
4 Z9 p0 H* ^: }$ w# ZFreq: MS, dtype: float64- _8 i4 h& Q# g7 j
$ M1 v" Y5 p: L; Z' {" O6 ^16 C0 Y4 \# w! V2 p( h
2
$ j" B! N( ]$ p" E( z$ K! n30 b5 ?. c( } h4 Z) ]8 }1 [
4) W: m8 n: H9 s: H; e* m$ n6 b
59 W, f5 q" S) Q3 g
6
0 u, ^) Q5 q, m/ f8 ~2 o/ q7. t; m& Y8 b, d# D8 y' {
8
3 P" V8 b9 U1 \ w7 \92 s: [5 l4 k6 Z& ]* L- E4 d B
10
4 c/ T8 x8 |/ d11
$ D# C, v' n& ?: G z12
3 ]' G5 h8 k/ Z4 t" Y* H+ g13
" W# P5 b1 P g7 Y14 c8 k9 c5 c; u: w8 G: H0 r
15
! M3 _$ f+ |+ e' l6 O [16! r4 G6 N+ M! f: q* ]6 c1 \9 w
17
( l$ c7 m( {; m/ k6 Y$ p186 J! D. Q. `) w/ b9 Y" R
198 E, T2 L6 ]7 h7 D
204 _% h( p9 d* U% K- m
216 j% m2 @! k' j
22
2 N, M' T. u" `0 {; q( a对于 DataFrame 对象,关键字 on 可用于指定列而不是索引以进行重采样:( G7 Q# F% S/ D* v
d = {'price': [10, 11, 9, 13, 14, 18, 17, 19],% ?5 J/ q9 Q0 ? w) i) [
'volume': [50, 60, 40, 100, 50, 100, 40, 50]}6 A' E) t8 ~0 J. \5 x
df = pd.DataFrame(d)/ L$ Q. P( T4 t* ?' Q! j5 O+ A1 ]
df['week_starting'] = pd.date_range('01/01/2018',# Q9 h" a. b4 `# W- c7 L2 ~% ?3 b
periods=8,( E7 B. u- U( y5 W5 J v
freq='W')9 K/ f# K; u0 @% @+ | ~) k
df
# j6 s. m9 q/ H price volume week_starting8 M# D% j& w+ w* y( Q! h2 w
0 10 50 2018-01-07
) W2 y7 U; w4 \7 c' x1 11 60 2018-01-147 k$ l4 L6 ?( {; e" @ q6 N2 G' s$ N
2 9 40 2018-01-21
9 Q& [# q4 e9 t% u3 13 100 2018-01-28
1 o' z8 a5 M- l" Y) l/ Q4 14 50 2018-02-04
/ H Q' [2 Q* f- `8 V5 18 100 2018-02-11( \5 Q7 {) D) ]
6 17 40 2018-02-189 ~' f6 z, s) k7 @8 d1 p6 E& }
7 19 50 2018-02-25
9 N* _5 K& j* |7 a9 x% n1 e( W& X5 cdf.resample('M', on='week_starting').mean()4 u- }0 y" a" C; }
price volume
) D `% j2 {! E( }+ ^4 j8 Lweek_starting
# z: o8 H3 i9 u% w( B3 D, P: f+ U2018-01-31 10.75 62.5
* _2 u5 \. R8 _# v2018-02-28 17.00 60.0/ O, v/ W; H5 X
- ^* I, u7 U& Q( V2 V5 D& O" E
1
+ U) H8 V0 M* h2 q3 G* o2 s! L) D6 L" U1 x ?8 r6 Q
3
' { R# _6 c( M3 ?/ t: U( h4
" ]9 c- A2 e0 h8 u- B5
0 z$ U9 D8 ]2 u) T. D8 t6, K1 }. G6 p) u3 d- h
7( C& B j9 X) X8 V5 I
8+ M" R6 o7 n& y x5 n) k9 |
9
* J8 E* J0 D% W# G1 @3 I& ?, U10
! [8 _# t0 x& w+ l4 m# e- O11
- y. |5 ^0 g5 }12
8 R; d; I3 t. X5 g13
* b( z- e! g3 Q/ i' v7 L0 V14
9 b* O& p; s7 b1 ]; r$ l; h15; q8 F/ I) I* E+ d+ g+ e
16
9 q( _4 q: {! A- J/ P17
* U A' t6 @- c- O# d18
6 }% i1 A& N* c0 m6 ?/ M19& t7 H- T: J+ W5 j* ^
20
2 i: }! Z" K4 Q4 q, a21; t* j4 `3 v7 \
对于具有 MultiIndex 的 DataFrame,关键字 level 可用于指定需要在哪个级别进行重采样。& {3 ^+ Q. o) J& t) i7 a
days = pd.date_range('1/1/2000', periods=4, freq='D')
( j, w+ Z6 t( h4 r0 Md2 = {'price': [10, 11, 9, 13, 14, 18, 17, 19],) V6 F- N C* Z5 k5 M r
'volume': [50, 60, 40, 100, 50, 100, 40, 50]}6 F1 N0 H, K7 o v/ M7 Y
df2 = pd.DataFrame(5 {. B0 u, h* x9 `+ y: v/ s
d2,/ t, v9 u8 c3 S7 X, T3 N
index=pd.MultiIndex.from_product(
8 G; i2 w3 Y- \# F9 w [days, ['morning', 'afternoon']]+ G( C& W5 b% ^
)
3 Y) ?" y" o- X* i" })1 @8 X" T2 W& J2 s3 M
df2* E* D: B% i/ `6 C: m
price volume4 `* q2 }( K$ t* {) {* r
2000-01-01 morning 10 50
1 B0 L$ A6 x7 [' H afternoon 11 60
! E0 o* B- e% X' t/ ]# a7 G2000-01-02 morning 9 40# G9 u m3 G0 q" u7 z! @
afternoon 13 100
7 O7 j: c: a3 S: P( W2000-01-03 morning 14 50
. i0 o4 {2 S- j9 p9 q afternoon 18 100
: I4 m v5 }0 i N; e, ~2000-01-04 morning 17 40. z- z9 P; P( y5 t7 h2 s
afternoon 19 50
) b- k# _+ H4 Y0 d$ G5 f1 Udf2.resample('D', level=0).sum()* E3 r4 B& _2 |* w( B; [* I
price volume
6 ^, t ?$ p" E6 m2000-01-01 21 1102 m: D A' x0 D" [( y P& k5 R1 y
2000-01-02 22 140
' n9 e) c1 ~' C! |1 W" t N1 o2000-01-03 32 1508 d1 c: t9 x0 n
2000-01-04 36 90: J) u. d3 D9 F7 M8 o
& D7 z0 ]3 C' D- J( r1; |* ]: h8 S3 I3 \
2
: g/ s! ]! I& N3/ [( A- }& @4 J0 }% ?, W" L
4
1 J+ u3 M4 n7 a% C52 F6 l; h- F7 `! k2 l) v% g- x: Z
68 N1 L, h, F) J4 Y; g2 W
7' c! O% i) ?6 B5 Z0 I S' j7 `: ?
8
" U. A- G) N' k. b; h. y9 J; p1 E: q4 t- R
10
1 d; B- a2 ]- m7 d* d4 l111 ]4 j* ^! M9 t4 G
12
1 c' j' O0 X/ j$ O" V13
! U. |4 P j& y, }5 y140 e. f% U! J; [0 r# C c
15* X6 e8 r e5 R* r
16
; a5 l$ z* w# X* G17
5 K; y E( H5 N2 C, C, H$ C( V18! }5 E0 x% d! h1 s2 c( `
19
7 f) L% B7 o" p0 Z4 Y5 p, J+ [205 u6 W$ I1 a/ ^' W2 ]' c1 i
214 s* l; }5 n6 _ m' K( }
22
! J; w6 s! V, d$ P233 j' e/ a1 j' B: Y# ^
24
, y" {* ~3 G4 t25$ |/ u) Y! V5 g4 w8 B6 I
根据固定时间戳调整 bin 的开始:* S1 m' H2 t, l
start, end = '2000-10-01 23:30:00', '2000-10-02 00:30:00'
$ k! w9 x! S4 L. R8 K+ v/ Qrng = pd.date_range(start, end, freq='7min')
+ C9 N: p8 a9 Q# |- Tts = pd.Series(np.arange(len(rng)) * 3, index=rng)
; @* o5 {& A# Y x9 wts
4 e" h& p5 ~/ M6 m, ?9 Z2000-10-01 23:30:00 0. S+ Q5 ~ ?; Q" l- r" n
2000-10-01 23:37:00 3
9 H( j) [) f2 k7 o9 p' M* V0 `2000-10-01 23:44:00 64 R! f9 u1 _9 V" S4 {! V
2000-10-01 23:51:00 9
% S @0 H) U) N) j2 D0 t# x2000-10-01 23:58:00 12; q9 I6 d/ c$ w2 Q9 m- m' x" u! @
2000-10-02 00:05:00 15/ a: q1 q% {. l
2000-10-02 00:12:00 18. N ?- V/ v1 g4 d; q# @% d
2000-10-02 00:19:00 21
2 H( h3 N8 u9 R8 a- a Q8 ]5 X2000-10-02 00:26:00 24
: w8 w r) }8 U' g& rFreq: 7T, dtype: int64" c- U* m$ Q* P- M J5 E
+ x9 _9 [2 U- i+ `5 u* q$ C
ts.resample('17min').sum()) K _& C: t% {
2000-10-01 23:14:00 0
' U* v# \ {! M0 ?. m5 A: y2000-10-01 23:31:00 9
. n* A0 G: Y* x8 L" g6 e1 `. U2000-10-01 23:48:00 21
9 z) u+ K/ v% ?$ Z; k8 m" Y/ ^5 z2000-10-02 00:05:00 54
' l, G& ~" q+ b* H3 b; \2 Y2000-10-02 00:22:00 24
4 n, C" \ f# z# d& K0 ~) f3 k7 X7 MFreq: 17T, dtype: int64) a: p1 Q& S( e7 l
0 t: e- F, T) Mts.resample('17min', origin='epoch').sum()5 W) P9 Q1 t" \( u4 z: K! \( c
2000-10-01 23:18:00 0
8 Q1 A) C* ^5 W q/ x w4 i% D+ p2000-10-01 23:35:00 182 d/ X# o0 H( W- A+ B
2000-10-01 23:52:00 27
3 J. x; Z2 m6 D Z8 C! @- K; i2 Q3 T* @6 `2000-10-02 00:09:00 395 g" V$ U3 u: [! i
2000-10-02 00:26:00 249 p3 q, e: P1 R( L- h% b$ A2 y. X
Freq: 17T, dtype: int64
7 x* R& Y, {+ I# o# D
( g+ w, B' O, E5 Q( Hts.resample('17min', origin='2000-01-01').sum()/ ]4 J- {" v x
2000-10-01 23:24:00 3
6 i" {) ~& @! |7 x( o2000-10-01 23:41:00 15
% |- ~6 L6 g3 w( q0 V% s2000-10-01 23:58:00 450 h+ l/ a0 F# r. D/ {2 L8 S# S
2000-10-02 00:15:00 45
& X+ T$ Q6 J0 T8 j( S p% sFreq: 17T, dtype: int64
- t4 _) c8 b! [
. B! p# T# C8 p+ A1 m) h19 ^* u- I9 @0 c$ X' e
2
8 N) z. ]3 y7 t+ V* d3+ E4 Q# l8 d9 C, [6 {1 p
4" \ _* l& {. J5 S* q+ F8 J5 G
5. E% s+ y$ z" _5 v6 l
6
# m- [6 F; S: p4 w7 o7# x/ k2 P# t1 u. Y8 @# Z
8
, T2 h' v% n7 i2 r- I' r5 b9
/ f) W. \. W) |* q: p$ G8 g/ v; I10
- F6 S) c0 }# j119 ^5 I6 q7 d$ E7 E" r: e) V6 N
12! i' X" c5 G) H. z& f7 l: C
13
6 C: `6 @$ M, R% @142 ?( \/ V/ l9 i+ a+ ?' R
15% U" O8 |+ \4 E, _& g1 W# C) z
164 F. S9 V( s; d8 N" F
17
# Q( ?' s- ^( b5 w( t" X187 z# m# ~1 c- n, C( [4 @" g0 E
19
6 ?. X! `$ ^2 {2 o" ^20
. `/ f9 C4 b4 A217 X! I3 Q$ ]: O8 x' R
222 V3 w+ K7 g/ E( W7 W
23% Z- }8 _) Y4 [0 g
24) \7 ^8 u1 E' \' X7 J& x
25
5 I: V* U, Y1 N26
. M E- a) ]5 P- D+ }/ d27) a% X/ M" }. p0 ]( ?9 \. l
28% N; C" C% d& ]2 s# F
29* r( g" k* Y( Z6 c/ g& N
30
- j* b7 b+ |7 |2 H* y2 ~31
. X. [9 h7 G5 h32
+ N! j- N1 A$ s; Y" g3 H336 q7 d x% U' a# u5 A1 L+ v \
34
: m; x9 S2 R9 B. v35. f! X4 J# h/ L) N1 q0 A' _
36" ?- Q) U! d$ n, C
37
4 D1 _3 o+ P' W! l如果要使用偏移 Timedelta 调整 bin 的开始,则以下两行是等效的:
0 h- n; U: H. tts.resample('17min', origin='start').sum()
2 k3 ]& j- x gts.resample('17min', offset='23h30min').sum()
, E6 F, j: P9 u$ F/ B- K2000-10-01 23:30:00 9# x) b5 }8 j/ U7 x+ \8 a% ^$ E& _
2000-10-01 23:47:00 21! {8 x' p' `% v q
2000-10-02 00:04:00 542 Z' N* ` \, x0 c
2000-10-02 00:21:00 24
: J& h8 X6 q* k4 f0 u# O2 r' tFreq: 17T, dtype: int64
2 v) ^6 \5 a7 H, m1
+ H' t7 {0 a/ @# A( K2# B1 l( y/ b# P; b
3' y2 g2 j; m5 P. a4 S5 W2 h: g
46 e) t0 ?7 a# E2 i3 _
5
7 K2 X8 d& g: e0 L' W6
1 C* u" H2 E3 ~% O8 B* T+ t7% G. v7 ~9 A0 u& u8 ?1 y4 j" H
10.6 练习3 H2 J" E% S k$ u; }. Z
Ex1:太阳辐射数据集
: i8 J' T. o9 ^2 U& b0 v, K2 G现有一份关于太阳辐射的数据集:; k. K+ }/ y: c/ S2 w) X, h6 K+ x
! p$ t( w. u% \+ G% B
df = pd.read_csv('../data/solar.csv', usecols=['Data','Time','Radiation','Temperature'])' O' F' o1 r$ I; c- i& \8 a
df.head(3)' i4 Q8 [& M; m/ m9 w7 _
. U* M" i C* ] ?2 M" f% \
Out[129]:
' `" l: \% D, x: M Data Time Radiation Temperature
; ?( M8 V! L5 _0 9/29/2016 12:00:00 AM 23:55:26 1.21 48, @4 a0 O: N2 C9 o2 ]6 B6 M% g
1 9/29/2016 12:00:00 AM 23:50:23 1.21 48 j$ X7 F: ~( J
2 9/29/2016 12:00:00 AM 23:45:26 1.23 48( w {! h* H' Y4 K! q" ?- H$ N
1+ U1 F, \ l/ \1 z) n% B
24 R, v5 d# \. o9 }) y: u$ K
3
" L) N9 K5 }8 m/ y/ g' }4 }! q$ b( ^4
6 m6 ]! k) H i; l5/ p0 \/ U8 i- J9 v7 f: `
6
0 J9 [/ B4 Q3 e+ e7
) V2 ~2 c4 K! w$ S6 j8 Z- ]8& Y5 n% S7 P- Y3 ^1 c# U, `
将Datetime, Time合并为一个时间列Datetime,同时把它作为索引后排序。# w |; x5 p# ?0 [
每条记录时间的间隔显然并不一致,请解决如下问题:4 l7 Q4 R& s1 T6 ~5 [' V
找出间隔时间的前三个最大值所对应的三组时间戳。- }7 ~, \3 Y* Z4 d1 W
是否存在一个大致的范围,使得绝大多数的间隔时间都落在这个区间中?如果存在,请对此范围内的样本间隔秒数画出柱状图,设置bins=50。6 v- c3 t- j3 z4 `( c( B& k
求如下指标对应的Series:0 w' L" _3 R: [2 E0 H9 m* d6 H! `
温度与辐射量的6小时滑动相关系数
, o& z! s/ }7 g! m以三点、九点、十五点、二十一点为分割,该观测所在时间区间的温度均值序列
) i5 ?* J1 c3 b% v, H每个观测6小时前的辐射量(一般而言不会恰好取到,此时取最近时间戳对应的辐射量)+ f0 G. \1 C3 C* q* r# c2 ^8 I- i
import numpy as np! `# h0 t3 p# M# {& q
import pandas as pd
% |6 A8 j9 s% ~8 T14 B' y8 b3 e/ j2 U/ l! K1 y% h1 C
24 _0 Q9 l; k/ G& _ W0 y
将Datetime, Time合并为一个时间列Datetime,同时把它作为索引后排序。
0 m' k1 A' b0 @8 }+ Z; wdata=pd.to_datetime(df.Data) # 本身是object对象,要先转为时间序列* n5 n% i4 }7 s* p! C
times=pd.to_timedelta(df.Time)1 [0 e; {' D" Z8 U- m1 S) C
df.Data=data+times
3 w! F, I( O6 ?' N( adel df['Time']1 Q. Q% e4 R# H7 M. |) R
df=df.set_index('Data').sort_index() # 如果写的是set_index(df.Data),那么Data作为索引之外,这个列还另外保留
* ?( y* z7 l4 Zdf
6 K2 J) B/ j) H! y Radiation Temperature& T H" |2 P- U
Data . C# e- J# @2 O/ W2 t. a
2016-09-01 00:00:08 2.58 51
& y$ c0 l: Q, n* g2016-09-01 00:05:10 2.83 51
; `0 A" \; u- e/ @) h2 d2016-09-01 00:20:06 2.16 51
& X! z! Y: ?* Y2016-09-01 00:25:05 2.21 51' _: R7 v5 [5 [
2016-09-01 00:30:09 2.25 51
$ P; \# I: x7 t( c3 F* ?... ... ...) h: J( Q- ]0 A) y
2016-12-31 23:35:02 1.22 41
2 D# y& f6 Q3 v$ [: S% d2016-12-31 23:40:01 1.21 41+ ?7 W2 t0 q$ a5 V) X' \9 y
2016-12-31 23:45:04 1.21 42
0 u* H* ]3 r, t y, w, }2016-12-31 23:50:03 1.19 41. _* Q4 o% O. C4 U) p! H- a$ x3 }
2016-12-31 23:55:01 1.21 41
# _! B6 ]$ u! A% ~. {4 D1 n' t+ C9 ^- _( c( e; o2 a1 Q! l
1
' t Z2 S9 c+ d+ O' u2- A% A. z7 V6 Q3 T7 k2 m/ f
3
- s% b' c5 j; e+ N1 J! T4
3 K- k1 V- W8 A# ~2 p x6 I& ^5- g- F N6 p0 l8 g+ ]- X7 r; [
6
( C5 u4 @$ E1 q1 E: P* X+ v+ w7
$ d# y; t6 x' z' \8
! b& D$ E+ A: f$ p% ~1 t8 _+ C- L: g9
% g2 ~7 m y' {2 \1 Y7 V5 W10+ R' \; m) W. r, b1 h
11
) M( m2 L4 ]3 p9 ~1 n0 t% ?$ X& q$ r" L12
. a3 i) i0 E: X& B8 m+ E13/ v( G9 K0 ]: `/ r/ }7 p
14
' l5 m. {3 }% {. _15" v H8 g3 E% I* r! |
16: D' d3 _" @/ U! m( G
17
8 l. [7 B+ u0 W) L; d18
7 t6 H$ q6 n* b) C4 i198 V# G6 @5 F2 f4 [+ d( M8 V% X
每条记录时间的间隔显然并不一致,请解决如下问题:
" u, y0 ^. e7 Z( {: D找出间隔时间的前三个最大值所对应的三组时间戳。
R. C( Y4 h! v: C# 第一次做错了,不是找三组时间戳 |; I, X2 d0 \% j' `8 K
idxmax3=pd.Series(df.index).diff(1).sort_values(ascending=False).index[:3]
# D9 N! D# E; S" G8 ~df.reset_index().Data[idxmax3,idxmax3-1], J( u, Z$ Q3 S! r' @$ q- G& V6 @
0 m# i' i$ _9 f! r8 O1 H
25923 2016-12-08 11:10:42& E( O: Y, z* j
24522 2016-12-01 00:00:028 n+ L {: ^; |: \
7417 2016-10-01 00:00:19& C, v1 x- U! i# G
Name: Data, dtype: datetime64[ns]" C a% \. l$ U) ?; ~' x
1: E8 d4 v- B# ?7 n* z& X; s& _
2- E5 E5 b, K! g) Z/ h
3. S4 e5 V0 C. n7 x& v% ~& Y0 {+ }
47 l3 ^6 p! O/ x$ e
52 }/ ^% T& E" s7 r" v' Z
6
8 s4 R2 W! h4 V# j2 W7
1 k6 C$ j8 }7 F r. H" f0 { z0 o8" {! a$ h/ d: d. J
idxmax3=pd.Series(df.index).diff(1).sort_values(ascending=False).index[:3]
/ F; M5 u2 F. Z/ d5 nlist(zip(df.reset_index().Data[idxmax3],df.reset_index().Data[idxmax3-1]))
& V9 X7 s3 [8 x& E' ]4 g( I- c: ]9 F" x' L5 M! {# C/ k
[(Timestamp('2016-12-08 11:10:42'), Timestamp('2016-12-05 20:45:53')),! I6 ~/ D; o+ o* N" a9 R
(Timestamp('2016-12-01 00:00:02'), Timestamp('2016-11-29 19:05:02')),: y5 ~9 E8 G3 ^) ?$ P
(Timestamp('2016-10-01 00:00:19'), Timestamp('2016-09-29 23:55:26'))]
3 S _) E L3 Y0 ^/ L- ]10 \, }0 B- I7 P" y& ?
2! x5 U' l# \. M7 `
3
3 N' V- L# j7 V/ H) V4
, ?& p: r) j! L! [7 r/ K3 e4 F5
$ i' k1 F( z& A) R0 E6: u& b0 E( R+ G
参考答案:
) p3 ^0 d. S0 u* O
- p- H- T: m! f$ |" x- l9 is = df.index.to_series().reset_index(drop=True).diff().dt.total_seconds() E5 d- V1 h0 c9 e( c! z$ e
max_3 = s.nlargest(3).index: K5 d1 _( A$ @' B5 u5 G
df.index[max_3.union(max_3-1)]
, A' } w$ z" l5 k9 |
$ y9 U0 F! g% S3 S9 O' m9 XOut[215]: % ^8 S% @! K+ h T/ ^0 |
DatetimeIndex(['2016-09-29 23:55:26', '2016-10-01 00:00:19',3 {* @# E1 [* G; E
'2016-11-29 19:05:02', '2016-12-01 00:00:02',
v) l2 @2 s$ E '2016-12-05 20:45:53', '2016-12-08 11:10:42'],
7 I5 i' h6 _! i dtype='datetime64[ns]', name='Datetime', freq=None)1 e& G& \* a. W: Y! w1 R
1
\: M/ g5 X( V- ]. O" _% ^2- |% l$ k. s7 ` ~
30 L. L7 R$ X) S2 X
48 r0 s& w% L' ^2 ]0 o/ x4 b' ?" R
5: M6 s. ~# Y) y% S) f' @! g
68 f6 ~" B; y( W9 J! ], O8 |
77 f2 o' e* S+ d0 p0 k$ N
8$ V# V0 w5 P0 r g2 f$ B& a- n
9
# a" b4 D$ o1 K3 q# M6 e8 Q是否存在一个大致的范围,使得绝大多数的间隔时间都落在这个区间中?如果存在,请对此范围内的样本间隔秒数画出柱状图,设置bins=50。
/ n" ], R# |, F6 x# ^# 将df的indexydiff做差,转为秒数后排序。再求几个分位数确定取值区间* Y* I5 L) L/ D c! Z( Q- ]
s=pd.Series(df.index).diff(1).dt.total_seconds().sort_values(ascending=False)
. L! f. w( E) n/ Y5 x. r: m4 As.quantile(0.9),s.quantile(0.95),s.quantile(0.99),s.quantile(0.01),s.quantile(0.03),s.quantile(0.05)
2 L# `( @. p6 n: M% P' y5 I6 Y/ l' q
(304.0, 309.0, 337.15999999999985, 285.0, 290.0, 292.0). Q8 S) u% d" M: ]
1
& F" e1 z6 p3 {1 t0 K2! U1 c4 d# h1 `* L; Y
32 X( \; I- f( v- E8 B4 G0 M
4
, N0 V1 c4 J- j; O0 y# Z3 s6 G5
# f: ]8 G$ t1 c8 I+ Y* b& o%pylab inline
, J3 n; O; x/ J_ = plt.hist(ss[(s.values<337)&(s.values>285)],bins=50)2 }* O, T' d+ @5 A A5 ]3 `
plt.xlabel(' Timedelta')
1 |! k6 `& c* V; Yplt.title(" Timedelta of solar")
/ p# J7 R1 x9 d8 T# Q. |1
6 n/ u# ~1 B7 ^2
8 i+ ]" ?1 i+ d; x' Q, t# ~3 L, g3 n4 t+ \, e5 d
4: J5 ~# j$ S; O7 b- J9 m, [
; Y3 i: o" ^; V5 [
! X$ b0 w* S6 X2 @$ z. w求如下指标对应的Series:% J6 X. ]/ g" L: T" @6 f7 y
温度与辐射量的6小时滑动相关系数
1 g( Y0 _& W# m+ B8 {$ }- H, k以三点、九点、十五点、二十一点为分割,该观测所在时间区间的温度均值序列4 \. y- v4 r+ u1 I6 J
每个观测6小时前的辐射量(一般而言不会恰好取到,此时取最近时间戳对应的辐射量)
1 Y$ `1 w9 j2 A" Wdf.Radiation.rolling('6H').corr(df.Temperature).tail()
5 O" p3 e; b, `% ?: h3 X# j! C8 P9 i: P* D" d
Data
' ^$ ?! r& I/ {0 c3 y: ]2016-12-31 23:35:02 0.416187, s. i6 d% k: x
2016-12-31 23:40:01 0.416565
0 k# c" g- @8 E. d2016-12-31 23:45:04 0.328574
) Q3 C1 f8 d. l' `% [) \) F6 g/ L2016-12-31 23:50:03 0.261883( f; U! t9 f' W1 b, a
2016-12-31 23:55:01 0.262406
5 b5 l( i- b7 j8 u2 M2 y P' J4 ?dtype: float64
7 Z4 S, J4 \6 ~2 b) |% z19 ? f4 \+ K3 j4 b. |/ Y6 U
2- G1 O3 ]) F5 y9 `* k+ ?
36 m1 ^( t5 v" l4 x
4
% v3 v$ p" F1 @+ l5
) ]. ]4 |4 r. w5 ~( N) s# ~* H: O6: ~; }7 U- u' J" j
7
* ~9 A; s7 A9 g1 `7 w8( I6 G# R9 J$ H9 O; {9 ~7 k: ^
9- Z$ S7 |7 i2 ?% G) F
df['Temperature'].resample('6H',offset='3H').mean().head(); V( f/ C3 J8 [2 o- C
: ~; Q9 I4 _" `1 f' G9 l: N
Data
: B; S m. o, J4 D' ^2016-08-31 21:00:00 51.218750" v, `% m) b& [9 L S
2016-09-01 03:00:00 50.033333
: Z2 D: d, T: J" F0 G: G1 c. F6 U2 v2016-09-01 09:00:00 59.379310% j7 u; G# ]. n
2016-09-01 15:00:00 57.984375* J/ x; d+ j! G& X/ s* J
2016-09-01 21:00:00 51.393939
* Q% Q/ P) ]- R9 q* IFreq: 6H, Name: Temperature, dtype: float64
2 N# d4 p& J" u. p% [1# F- E: U/ t; U* B. G6 {- P/ o- N% l
2
$ h$ L* W( V' x9 o9 U3
4 ^7 L/ V1 o/ [ [0 x4 H- ]4
5 _3 P/ q/ E( X6 n9 s& c5
M; D' P& R, u5 ]& C6 s: c7 h6
8 u& V) V- e" g8 f/ X9 J7$ B G B3 O& `4 N; V% b+ f
8
& k5 ^. N5 [2 K1 o) s# F8 Y$ e, A9
c/ P/ H- G, G" t& b j最后一题参考答案:2 w% X& g) [1 S; b
0 x% ^) u8 O+ W. S9 w& k# ~# 非常慢
% N/ O/ ~% C" R' b Qmy_dt = df.index.shift(freq='-6H')4 y8 q. e( f8 Q8 r
int_loc = [df.index.get_indexer([i], method='nearest') for i in my_dt]# z) A1 ]1 g% v5 q7 e/ o
int_loc = np.array(int_loc).reshape(-1)
& }6 Q9 u- L' W% `/ M+ t% Zres = df.Radiation.iloc[int_loc]/ @, p; G: `4 N6 a3 f
res.index = df.index
+ z* |! i2 O: ?5 Gres.tail(3)% y6 ~0 s: P; Z, C' ^1 E- w+ s
1
6 t* U- B' S+ b5 X$ _: b g r2
1 R" c* F2 Y3 I1 `3
5 U( Y9 M; _' p0 F4
3 }9 r, b! |+ x% [4 ^5
0 V9 H- z0 E7 J8 [$ ]. Z4 V0 \5 ^( \1 g63 p6 r" c! ?4 F6 K9 Z
7
5 S3 Q0 e6 Q5 E* P/ P# 纸质版上介绍了merge_asof,性能差距可以达到3-4个数量级( B# Z3 B* v6 [8 `; o2 D8 G
target = pd.DataFrame(. S# R9 H9 B. j) M) ^' A
{
9 u& y+ ~! ?5 a- | "Time": df.index.shift(freq='-6H'),
, _: d* i [+ K% j( D( _ c* j* _ "Datetime": df.index,; [! G- I* h3 v! k3 b- S- g
}* }: Q! i0 ~0 u
)8 B- @7 m, Z- D7 |1 A
; W n1 I5 a+ Bres = pd.merge_asof(
. Q; i0 ^& q; d" x& m/ i0 \ target,
" _: B% O2 s- f* p df.reset_index().rename(columns={"Datetime": "Time"}),
0 \& o# b6 Z2 n- ? left_on="Time",+ T8 ^; D5 Z4 D& U/ z. d
right_on="Time",
2 P+ P2 c+ b3 B& Q5 Q( ^ direction="nearest"
; `( j' K6 _! u1 M& F) D2 h" M).set_index("Datetime").Radiation
# x* Z; ]$ N5 L& G# T4 d. x" @. d C! o1 |& _- U: @2 Z
res.tail(3); E0 \& I7 D7 r0 q( s! b
Out[224]:
1 F4 P" b# K- f4 X$ @0 }Datetime
$ C3 @, G% ~, ^$ A2 X2016-12-31 23:45:04 9.33
, e0 G; R3 ^) m" x) `: p2016-12-31 23:50:03 8.49
( ^6 X( w, x. g+ C% U) |2016-12-31 23:55:01 5.84
% c5 m, y) v" z: D4 d OName: Radiation, dtype: float64 g+ a5 D! A" k! z! b
0 N- h) e% T. ?- f# T1
$ u5 B9 H1 e: e2
m8 }: J S* D" s8 q8 `) b4 @3
H# v! B. U# i5 e7 g3 z# v4
8 U: h# [: n8 I* R: a5% G' R. A! y) B! i$ t* x# B
6/ a1 A' J/ a7 _( Z- ~
7
4 A( P1 C! i h! J: v y86 J# }6 {: D2 Q3 U3 T( v
9
' i; z- a" z9 L+ q10
4 k, h9 m, y9 R0 \% c11
2 O9 M0 Z1 X1 ?12
, u0 j5 _ f, z1 u! K4 F139 Q4 y5 k9 k: ^
14
1 S; ^, G+ |! K) N3 x3 E15& o' [; O* e7 v3 L
16
U( X) N% ^9 }6 T+ [" o/ h17
4 ^9 v9 [2 A) w5 d7 h# J2 g1 J# O182 d) X7 c# q5 F- B; g5 n+ E, u* H
19
$ f+ z. b( y5 |/ T& X1 L3 }20
_1 F8 a: g% r$ C& I6 z" G21" ~( K) [! H3 |2 l, f
22) Y" F4 b& k1 y A$ `
23
, e# e1 E% {. A- ZEx2:水果销量数据集1 m! I7 S) \, o p& X- ^
现有一份2019年每日水果销量记录表:$ H" i, e9 a2 T9 d- Y+ ^
/ L$ }; V$ A) p& qdf = pd.read_csv('../data/fruit.csv')
9 F# U- Q2 U7 X0 g- Wdf.head(3)
" [0 ^. P; `6 T
3 z/ k# K+ w; p" N" I* gOut[131]: 6 r. M3 {( v# h5 ?! b( a+ R
Date Fruit Sale" f/ A0 X, a. s" t
0 2019-04-18 Peach 15
6 x/ j3 ~! l' {% m( q/ r1 2019-12-29 Peach 15
9 i( s. I6 d8 {( x2 2019-06-05 Peach 19) I- a5 T. |; M; e# f4 M4 ?& M
1) }! G# ~$ @* v. p5 Q0 m; @
2/ D3 I6 ` }( L' Q0 x/ c6 W" t
3
0 O1 P7 g% M5 ]6 ]! K# i3 \0 G9 Z4
; g+ ^2 K) t/ }6 o( s5. Z2 y# L$ n O" I# q- j+ p
6* `5 m! _2 h, |( C5 D+ `
78 y2 t9 @2 T3 W2 R$ E! p7 k
8% ]5 r" Q) j+ F H# `
统计如下指标:$ ?/ s* {9 {% ?2 y2 ^: J
每月上半月(15号及之前)与下半月葡萄销量的比值
) W. c: s) ~3 ?6 ~5 V/ B每月最后一天的生梨销量总和
9 [; p" X0 P2 B( s: @+ h" {, a- Z每月最后一天工作日的生梨销量总和5 { y1 |3 r7 G% u5 o+ J
每月最后五天的苹果销量均值
. U( Q4 ?7 g! P9 [' h* z" F按月计算周一至周日各品种水果的平均记录条数,行索引外层为水果名称,内层为月份,列索引为星期。4 z4 G0 y3 A7 I1 I5 L: B
按天计算向前10个工作日窗口的苹果销量均值序列,非工作日的值用上一个工作日的结果填充。: F4 c: V. e% d3 ^1 W+ m6 E) K
import numpy as np7 w; `+ o* y$ W- E! H# a7 n
import pandas as pd
0 J! v6 L" y9 C( ^4 s3 w1 x1 A& s S0 c, O) y% n% {9 b0 O5 D
20 _/ p5 ^( }, L& P3 [! ~1 v
统计如下指标:
9 Y7 s- d+ e) p每月上半月(15号及之前)与下半月葡萄销量的比值
K" B/ s. @1 f+ [9 p2 {5 ?0 s$ t; y每月最后一天的生梨销量总和" p( F1 N' _4 K) H& ]% [
每月最后一天工作日的生梨销量总和+ Z$ U! J5 x9 W) p+ ?& M2 k
每月最后五天的苹果销量均值
8 i* x$ Q7 E5 Y7 \# 每月上半月(15号及之前)与下半月葡萄销量的比值
6 J( g$ A" F( S. J; y( ^df.Date=pd.to_datetime(df.Date)" q( Q- q: _/ z8 n
sale=df.query('Fruit == "Grape"').groupby([df.Date.dt.month,df.Date.dt.day<=15])['Sale'].sum()
- y$ `! l! _9 H0 bsale.columns=['Month','15Dayes','Sale'] # 为啥这么改没用啊3 I- f& |5 L2 r) w
sale=pd.DataFrame(sale)
" w. F, Q7 M- ?- z0 jsale=sale.unstack(1).rename_axis(index={'Date':'Month'},
1 Z/ k- A5 {9 S, O ?, Y columns={'Date':'15Days'}).stack(1).reset_index() # unstack主要是两个索引都是Date无法直接重命名
3 ]: S3 a) {( ^1 u8 asale.head() # 每个月上下半月的销量$ B0 y ~7 k! f7 ~6 [
4 V# O4 E' X7 u9 e# k; v
Month 15Days Sale; S) k' p: Y+ V- G
0 1 False 105033 e7 y7 N" X; {# W6 h3 O% u- y% }3 T
1 1 True 12341
2 K; M# K" U7 s Q% O2 2 False 10001
5 n; k% Y6 U! R0 a$ D3 2 True 10106
0 s+ S0 T! @" o$ Y0 f4 3 False 128142 ~8 c! W3 P) t9 c! m
3 p2 `3 p& a- _+ `4 g+ L8 F# 使用自定义聚合函数,分组后每组就上半月和下半月两个值,根据索引位置判断求比值时的分子分母顺序 {1 b9 q, X8 e3 f+ I. J" S: D; L& E0 R1 q
sale.groupby(sale['Month'])['Sale'].agg(1 o! ~# d& ?# i& z
lambda x: x.max()/x.min() if x.idxmax()>x.idxmin() else x.min()/x.max())% h( b1 d/ B: f4 A+ y- y& b8 J- g
% E- r0 A( x6 s7 }& d
Month
: G; l8 U7 }% J1 1.174998
! t: y5 K; H! Y2 K) j: z7 p2 1.010499
2 x; c2 `6 |* K4 M6 U: X: U9 o3 0.776338
: Q( G1 o: @1 {4 1.026345
7 ^1 J- f8 @- a, C5 0.9005340 T2 A. v1 \6 f$ Q& m8 E4 s
6 0.980136
4 q0 @3 P: P6 Q; T/ H4 R0 P7 1.350960( j# a S, B& I7 U. ]1 j6 w& o4 @
8 1.091584
" D; b1 T/ X$ y( U! r" m+ V" G6 S9 1.116508
+ S: G0 B* {+ l- }10 1.020784
+ ]/ l% W' X8 k4 c0 b6 B# b11 1.275911% l9 O7 }" _/ P2 d: @ T3 b
12 0.989662
: ~( M- V/ ^' P" X) n! rName: Sale, dtype: float64" M* m; y1 [- ~1 M+ ?* b% \ a- w
: v; b& ^$ N* j! a. s1
- t3 B& K7 w# c1 z1 ~$ ?( x; x2
! w+ O, @( S3 t$ ]0 r! y, n3
: X& d a' P Q* |! K( D8 U3 `3 T8 `4 _4, [/ N* u: _8 c7 c" K1 G) u# L
5
, l( X% M8 O( V- T9 f( ^! z* X w6 c65 R+ G1 ]8 V2 s, S( N4 J/ X2 |
7
- y# j& }; g& U8
3 y; X8 \3 n3 D; l: d- T* i9
1 \$ E! U: i% `1 R10
# o9 S) X7 }3 m, s. [11
( h( s4 O+ u% [# e3 @8 T12
( ?' H3 s3 q/ H. O& V4 x& \13& ]* T0 k4 X& {/ C, Q L9 Z6 N) G5 [
14
, G# N/ K/ q, u6 a$ E. h: F4 Y# |15
& o, U, M- B) ~16
5 Q3 M, [) x# D( L17
! M* n" b' w* V. C1 C0 B' b! r4 [18
: h2 Z( P/ b3 @/ Y" V: M0 A: z193 g6 [" l$ O8 _! {" d
20: Y2 i3 ^$ ?( K0 J* _
21
]3 X2 B3 J8 i4 p, [, s( L22/ d$ g) u( O4 h. Y; U% r. f( n
23; A$ S9 u: A* }
24
4 ]' B) `, V* A! i! l, M25
( x; v% w( {# A26
4 n& \! U; O4 ^! U. Q27( S* M; \7 }& h9 U" b
28
% _" {* X; }" Z2 [: E ^29( ^' O2 h* e7 N, a7 R0 J$ a
30+ q9 ]% ^7 a- c O+ A# K3 A
31
9 e1 k; X! @4 H+ O32) ]$ [3 {/ A% o$ Z
33) j( s! F& c/ S C
34
- T9 a2 t5 @; s! z# 每月最后一天的生梨销量总和# |: \0 v+ ]6 q
df[df.Date.dt.is_month_end].loc[df.Fruit=='Pear'].groupby('Date')['Sale'].sum()( `6 A) j/ { ]: M9 f5 n2 \
- C% `* w& O4 G7 i8 \( ZDate
4 W o. o6 f2 F0 p2019-01-31 847
* Q! F9 R) Z" Q9 p0 x2019-02-28 774. ?; ~' g2 O( h: u6 g
2019-03-31 7618 ]+ u3 ^6 h& E3 h( w
2019-04-30 6482 \' k& `& S9 P9 u) f. N8 K$ _# J
2019-05-31 6169 k, [/ h4 b- s
19 _+ N: D. }9 [9 D# |
2
a: N- p# k( F35 v' g9 R9 K- ^
4" `! H4 E: ^0 m: f
5 Z" P$ m3 D# {3 T! }8 O! S G
6
`# j: g C6 t* v0 }2 B7- \2 d6 `+ g7 m5 u2 O( m- I; D6 K" _
8( ?; F" T, Y |# U
9
S( J3 Q9 x/ H* U! j$ ~3 f. V# 每月最后一天工作日的生梨销量总和 j) N. l p0 h
ls=df.Date+pd.offsets.BMonthEnd()
# `+ r: g; b2 Y/ n" cmy_filter=pd.to_datetime(ls.unique())( z1 s5 ~' A+ o- X& ]2 }4 M
df[df.Date.isin(my_filter)].loc[df.Fruit=='Pear'].groupby('Date')['Sale'].sum()
9 E5 b- X, r1 X: k
/ }4 ?; ]; Y& G4 k+ [Date
5 @4 H$ ~8 |1 v$ F, |9 I2019-01-31 847
/ { X! N6 e7 B2019-02-28 774% ~3 B1 k; Y9 R/ ]
2019-03-29 5104 g- d5 F! y' O8 a
2019-04-30 6487 K0 T- g& Z. f" q2 k& U
2019-05-31 616
+ X1 `+ U/ a9 a% C4 _$ j9 W1 i9 K+ T; z' L8 C
24 I- e r4 G6 p
3
( F+ ]/ O- v: ], o) G; l4- G H4 P; ^( y$ N" d1 Y# a# x
5
" C" ^- X6 `3 w. x. o6
6 V2 U& r$ n- Y) V/ y$ s" E% D7% s, l$ I- y4 W1 A
8- b, V/ r4 Z7 W' y
9+ G2 S1 b* t; p) _7 M7 ]9 x z; @# \
10
- J4 _7 {5 I+ v' d0 o8 [11
+ e O0 C+ ~6 I/ n, ^" C# 每月最后五天的苹果销量均值
2 k# S: [2 I8 M8 U6 [0 G. ]start, end = '2019-01-01', '2019-12-31'' C% X9 w+ u! o- m6 a
end = pd.date_range(start, end, freq='M')
# A' Z$ R; N$ v1 c# Xend=end.repeat(5) # 每月最后一天的日期列表,重复5次方便做差
/ X6 v2 n. p& G& b! A- |5 w8 V+ C
2 O9 I$ Z. ]) l# r" w5 ]td= pd.Series(pd.timedelta_range(start='0 days', periods=5),)
- @' ^& j% q7 y( ~; ltd=pd.concat([td]*12) # 日期偏置,最后一天减去0-4天4 Q& r$ r) M: c7 t, P$ n4 N, w
end5=(end-td).reset_index(drop=True) # 每个月最后5天的列表
' q( U& K1 @- w. X
9 q% Z4 ?- X" D# i' Fapple5=df[df.Date.isin(end5)].query("Fruit == 'Apple'") # 每月最后五天苹果销量0 u: w! P! r ~! S
apple5.groupby(apple5.Date.dt.month)['Sale'].mean().head()$ P, a0 I1 ~6 z9 d) I+ r; N5 S
* K' f# u" Y! [3 s. f& W
Date' ^- s: p/ T t. M$ u
1 65.313725
) J+ {6 R) P) t" i( Z& K# M2 54.061538
, j2 o; @0 x7 ?- v6 \$ m3 59.325581
$ L8 G. H. W6 [: f6 ^& j) J4 65.7954556 [! p# F7 K" Q0 X- `" r
5 57.465116
2 |9 Y$ {( q2 G
1 A: a8 w0 z) t8 E4 f; B1' n, t* P0 y2 ]: K S% j* y
2
4 P. `0 R5 f% m6 N8 x7 ^0 ^5 W3
3 v1 R5 j( n2 [4
( e2 H/ X6 p% H5 i9 X: I) V& C: l5
) e' a, O4 h1 \6 u' a/ f- _+ B9 R6
: E9 L1 L" T1 ~! C$ G4 g1 ?! a7
: }( f" I5 r2 [( }+ s3 W8
$ I& ]3 e1 [6 Y90 U' |( N- ?! i' E7 M
104 \! u0 _* q7 Y* `8 D; |
11/ H+ s4 }0 _3 I; i( ]3 G
12/ X) d }+ n2 o: L$ v# `
13+ b& Y$ I5 s( Z$ E5 [
14$ M3 A8 w# _2 @( H. `
15# M) j9 d* n4 ?" Z" P
16" _1 G0 ~( j4 {9 R0 r7 S% l
178 u$ o# @9 l) y2 |& s
184 P, S3 R4 w4 w% K
# 参考答案:
: x7 k8 v$ a; u* G$ _9 S% {! x" D& utarget_dt = df.drop_duplicates().groupby(df.Date.drop_duplicates(
) }- |, }8 }) l) W$ Q' p ).dt.month)['Date'].nlargest(5).reset_index(drop=True)5 Q* A& Y5 C3 }- j8 Z
6 M9 D5 `, i7 E# Y0 y1 _ m
res = df.set_index('Date').loc[target_dt].reset_index(
% B9 h; c0 D J$ y ).query("Fruit == 'Apple'")8 f C9 D" D* A+ w
$ m" @5 R7 k- T6 a: h. eres = res.groupby(res.Date.dt.month)['Sale'].mean(8 \, O+ }9 a) |
).rename_axis('Month'): w3 K# x# i- j; P5 A
& |" P7 I& \% @4 M# m# x# }
& j( P( u7 ]" C; T Z
res.head()2 I3 u" [% W ^* D! I
Out[236]:
/ Q" k7 _" h) U; [* c3 @Month
9 h6 [4 I% N% v% D/ o. `. A1 65.313725
/ S) f4 p( z% i2 54.061538
$ `0 i7 v, L0 l! _ Y$ _3 59.325581( b, F8 E' {) u8 X
4 65.795455
5 _* ^% b( s6 i# t3 }5 57.465116
. P: I8 E7 Q3 G+ X) t( d* K7 M2 HName: Sale, dtype: float64
& K \# C8 A" X" @" V
# q& j8 K% M! K% m1! R7 N; s& i$ i
27 W. h' X. J& L' n/ P/ ?
3
+ y6 ~2 N/ I; i: T/ x2 M4 |4: N1 f |- h* D# h u, ?0 z- Q
5
# `! O8 f: b( J; n+ ~9 t$ D6) f: V& Q) _# ?: _3 e
76 m6 C" u) F- j- e- M( N S" j! I
8
5 w+ j @ z6 s' b) P D/ D9
8 R8 \% F* I- A102 E: A! J |0 `, D! t ^
11% c1 f0 u' t0 z+ n# x8 S8 k. l
129 x, d! ^- }! I. p) r. w1 V% h
138 ]% }/ E8 G, O, g/ T* i6 I
14
8 J5 c; b; ^5 m+ f- e7 s1 L. N- C15
% W. P" q# W# k169 i0 r( m# `4 o9 V
176 r- s6 Z3 x* c: R. ~* [
18
' J; T( `7 _# n3 s' M19* L& N% Q3 b8 F* Z1 L. z1 {
20
8 k, D c, [2 c4 a; K! u; G* E按月计算周一至周日各品种水果的平均记录条数,行索引外层为水果名称,内层为月份,列索引为星期。8 F1 y! i ]. V; F6 L
result=pd.DataFrame(df.groupby([df.Date.dt.month,df.Date.+ w( o$ v2 Q# u: ]( j1 U! G' F
dt.dayofweek,df.Fruit])['Sale'].count()) # 分组统计 3 y' o* P) \6 [6 b
1 S1 m, B( T, m# g8 M @
result=result.unstack(1).rename_axis(index={'Date':'Month'},+ P0 D3 R& j1 C. ~3 a
columns={'Date':'Week'}) # 两个index名字都是Date,只能转一个到列,分开来改名字.
# ~3 z; Y. k' T) N6 ?result=result.swaplevel(0,1,axis=0).droplevel(0,axis=1)4 U0 Z1 v4 P, h0 Q! K8 h9 q0 o4 @
result.head() # 索引名有空再改吧# H2 [6 j2 i8 v# C6 n
9 J/ u7 `7 O* D# Y5 i. C7 z
Week 0 1 2 3 4 5 6" L* L2 E6 R! k% p
Fruit Month
, D, E* D" \! P8 z# a+ YApple 1 46 50 50 45 32 42 23% M! P5 d; a- k5 A3 l' T" l/ i
Banana 1 27 29 24 42 36 24 35
+ c/ m3 T( {) V7 W0 X' A' lGrape 1 42 75 53 63 36 57 46
2 N; A. q- S/ m1 x7 \) HPeach 1 67 78 73 88 59 49 723 f$ B. I# X7 H' z9 s
Pear 1 39 69 51 54 48 36 404 o4 ? _/ D3 p7 [7 t* F2 Z8 h
1+ ]$ h/ M1 y. V
2
! y9 s7 E1 R8 l* d1 I0 m30 R9 n( \, G- p+ z: \* w$ \
4
& N/ A Q: q! T. r, t58 y) [' {. Z& Q" s
6# f1 \ L( _* F
72 r. P, B" w# Y$ d
8% G& r# s3 [) j8 C
9
% {( q) Q. Y8 Z" j) g& w1 q10
5 N0 l. T6 r" Q/ D11* g, P: K% ^* ^5 x, J& b' M
124 y2 t# _- ?* y( w3 g4 [
130 V& {* s* F% h$ c2 @
14
# B# W7 Y/ V$ f8 g15# x$ h/ |" H1 G# W: ~$ g/ r
按天计算向前10个工作日窗口的苹果销量均值序列,非工作日的值用上一个工作日的结果填充。
4 ?& y' B" A1 T; Z# d# 工作日苹果销量按日期排序; y( F1 c! R! Y+ X( h/ @7 U
select_bday=df[~df.Date.dt.dayofweek.isin([5,6])].query('Fruit=="Apple"').set_index('Date').sort_index()3 Z0 ]1 n$ }; h0 B
select_bday=select_bday.groupby(select_bday.index)['Sale'].sum() # 每天的销量汇总
& g7 q+ b+ }( E+ T* e; tselect_bday.head(), z7 P- p6 s7 T6 I
3 q2 ?/ Z) g+ {4 FDate! _1 G6 v: \$ t# B
2019-01-01 189" ]4 B* Q, u3 ]7 w5 A
2019-01-02 482/ P( n2 @: p) @, e' w
2019-01-03 890- W' S( T( [$ T/ p4 d
2019-01-04 550+ ~- N; T& R- O: ^9 a5 I/ O$ `
2019-01-07 494
/ j3 f. Y8 c/ O& ?7 Q* R& F6 A5 }, l. T+ S
# 此时已经是工作日,正常滑窗。结果重设索引,对周末进行向后填充。 @# Y7 Z9 Z) g3 b0 \. i
select_bday.rolling('10D').mean().reindex(df.Date.unique()).sort_index().ffill().head()& Y( f" T2 a. V5 _! n, d
! r. L+ U8 B, F% ] O' I4 U: N: n
Date
! ~5 g1 D+ R5 M# c) _: u9 h/ |2019-01-01 189.000000
: z' F' I2 T/ N2019-01-02 335.500000
1 j- \7 y; X( K# D4 h9 z2019-01-03 520.3333339 M* S$ f+ n' P. m5 j0 x
2019-01-04 527.750000
% K3 y4 z! @9 L' y* U2019-01-05 527.7500002 T; ^2 _7 a6 `# y$ e
- Z8 B9 r6 Q! S- h! r w* X1 C0 E q————————————————
+ q5 L7 I' M7 s4 A7 j# Z, b6 J- r版权声明:本文为CSDN博主「神洛华」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
" R9 a; }% w& m' M; }# R) _ a% q原文链接:https://blog.csdn.net/qq_56591814/article/details/126633913
2 [9 I1 K- Q9 X5 F! _1 Z$ w
. X: o: ]* }7 e' i" g: V, D1 ~* Q9 `' Y4 R' L
|
zan
|