- 在线时间
- 1630 小时
- 最后登录
- 2024-1-29
- 注册时间
- 2017-5-16
- 听众数
- 82
- 收听数
- 1
- 能力
- 120 分
- 体力
- 563347 点
- 威望
- 12 点
- 阅读权限
- 255
- 积分
- 174227
- 相册
- 1
- 日志
- 0
- 记录
- 0
- 帖子
- 5313
- 主题
- 5273
- 精华
- 3
- 分享
- 0
- 好友
- 163
TA的每日心情 | 开心 2021-8-11 17:59 |
|---|
签到天数: 17 天 [LV.4]偶尔看看III 网络挑战赛参赛者 网络挑战赛参赛者 - 自我介绍
- 本人女,毕业于内蒙古科技大学,担任文职专业,毕业专业英语。
 群组: 2018美赛大象算法课程 群组: 2018美赛护航培训课程 群组: 2019年 数学中国站长建 群组: 2019年数据分析师课程 群组: 2018年大象老师国赛优 |
: c( u( R% e/ Z* |
/ w5 H1 j% [" R0 l& Y2 H% p3 l2 X4 @2 A$ e7 `* G
文章目录
9 Q% h: |" u1 u& G0 M2 `* Q- f第八章 文本数据
$ X+ S" Y; n8 v8.1 str对象
. k6 Y2 [* W$ ?. D+ n8.1.1 str对象的设计意图2 c! s. Q. N' g: R" k
8.1.3 string类型) ^8 _6 Z% V7 j" P
8.2 正则表达式基础
Q7 W$ v B5 R! z8.2.1 . 一般字符的匹配
) |* {( x0 d c$ B( \4 V8.2.2 元字符基础' S! j" R8 }# [
8.2.3 简写字符集
, y) b7 g. u; v, B$ |* l8.3 文本处理的五类操作
- p8 r+ f L0 d! _8 n8.3.1 `str.split `拆分
* i- E! c6 u1 v5 ^# S8.3.2 `str.join` 或 `str.cat `合并
8 [1 o! a2 Q3 B8.3.3 匹配
7 ]! V% Q$ |! V- r! j5 I: M8.3.5 提取( d" p: y9 J; s Y$ _
8.4、常用字符串函数
+ G- T6 T8 I" [4 e' K( u8.4.1 字母型函数
) ~& L. ^$ i5 w9 Z8.4.2 数值型函数
- F( r. x6 S% p- I5 j8.4.3 统计型函数0 A5 ~- } l* y- B6 T7 f+ \. V: G
8.4.4 格式型函数9 Q4 ~; g: ^; b Z1 [, n
8.5 练习
; o* f" i5 X3 ] i6 fEx1:房屋信息数据集
" S+ _; i# s2 w2 KEx2:《权力的游戏》剧本数据集* S" X# \# m$ _. Y" m s
第九章 分类数据4 K0 g: E9 i$ M7 c
9.1 cat对象0 n p' Q, G6 i7 M2 ~/ ~6 p
9.1.1 cat对象的属性% u# D) e2 D, j) e4 j
9.1.2 类别的增加、删除和修改2 u+ i7 B6 @5 e1 M8 u% d
9.2 有序分类( a [4 d$ w+ d0 T) s* C8 ~: y0 Z
9.2.1 序的建立! q; e f- c2 e2 D
9.2.2 排序和比较
2 n- A1 ~* v3 p7 g; G1 {. l# K9.3 区间类别
# M9 x" Z& D( y3 @0 h9.3.1 利用cut和qcut进行区间构造
6 ]/ O, T# q: \9.3.2 一般区间的构造5 Q9 {) x$ K8 ?& {1 k$ s
9.3.3 区间的属性与方法- M/ A5 I ^% {2 U1 N2 i
9.4 练习3 T) k6 K% n r! d9 s6 n
Ex1: 统计未出现的类别
! e$ S$ t$ N% S3 t4 |Ex2: 钻石数据集4 ^* j2 c6 r" y/ Y8 t
第十章 时序数据
2 o1 o4 f$ K9 ?/ v) E10.1 时序中的基本对象6 u$ M" O" G4 q
10.2 时间戳
+ g: @$ j R* c* Z10.2.1 Timestamp的构造与属性# t, S+ K; H. a6 Z* W
10.2.2 Datetime序列的生成' S1 T* Q* \: B0 y
10.2.3 dt对象. z( }# @2 O: X
10.2.4 时间戳的切片与索引
& Z! z+ N0 M K& s10.3 时间差
; M1 H0 h; L: c10.3.1 Timedelta的生成! {* J; b7 ?5 P
10.2.2 Timedelta的运算) M5 b4 I* w2 p: k0 Q+ [9 o4 w$ Q
10.4 日期偏置+ s4 y* R" A. S6 t! k+ I: J
10.4.1 Offset对象
5 A6 T7 c, |0 H. S) Y4 q10.4.2 偏置字符串
( Y9 w& _" x8 w: c- [5 c2 T10.5、时序中的滑窗与分组& V5 D+ p& S* j& c# ?' X, k- C
10.5.1 滑动窗口9 d" t3 @6 s( H* {. ^9 J
10.5.2 重采样
+ I5 U0 O" y: u; c+ W+ y& _" Z7 M10.6 练习
$ ^$ N& S0 A6 q# W% [) MEx1:太阳辐射数据集
8 n2 V" a9 C% U+ C( _Ex2:水果销量数据集0 k7 q# P) X# j5 }
课程资料《pandas数据处理与分析》、github地址、讲解视频、习题参考答案 、pandas官网2 p/ [) P: U# c# F( ^: @* X
传送门:8 ]4 S5 J+ H8 P$ b
. k* q, i6 y% S$ a
datawhale8月组队学习《pandas数据处理与分析》(上)(基础、索引、分组)
1 {2 f! e) J& u( A8 L) Ldatawhale8月组队学习《pandas数据处理与分析》(中)(变形、连接、缺失数据)
. @/ a8 ~! X; b第八章 文本数据( H' e- |7 _7 e& M; }
8.1 str对象: j8 e4 l3 I3 P% p6 w- ?5 {
8.1.1 str对象的设计意图$ X) c5 `# R$ Z3 K( A: u: R- e
str 对象是定义在 Index 或 Series上的属性,专门用于处理每个元素的文本内容,其内部定义了大量方法,因此对一个序列进行文本处理,首先需要获取其 str 对象。在Python标准库中也有 str 模块,为了使用上的便利,在 pandas 的50个 str 对象方法中,有31个是和标准库中的 str 模块方法同名且功能一致,例如字母转为大写的操作:3 V' v9 K6 \' l$ z3 ^5 @) j
' U, L, U: y- U
var = 'abcd'
7 B" \* R4 L$ q4 C4 g! sstr.upper(var) # Python内置str模块
; n' V: ~. b! [. v8 GOut[4]: 'ABCD'
$ ]' V# t+ y0 Z" j3 B/ A/ n h5 z) e3 c) [1 O
s = pd.Series(['abcd', 'efg', 'hi'])% F4 s2 b3 r, J) ^' |
$ ]) w( ?( q* t7 l% C1 \
s.str. t7 }) M% ~6 }7 ]$ e l
Out[6]: <pandas.core.strings.accessor.StringMethods at 0x2b796892d60>
9 F6 U% U4 x0 L9 F% U6 |) @7 X
s.str.upper() # pandas中str对象上的upper方法
9 t8 P" K3 ^0 M7 f& u3 [Out[7]: ; @+ }# j5 ^7 j$ E: a
0 ABCD( C6 H1 T H$ |! N& e0 S5 S( C
1 EFG
# i: ? w" @4 ^# b5 l! C" I9 v3 }2 HI$ p1 [2 Q: q; l0 X3 h W$ O
dtype: object
' R7 n% q+ i, b1 n1
( c8 I+ |+ m/ z7 ]* r7 R" T; c* ~2" x/ q% O" U4 G4 D" r% P
3
9 R8 I" S, b8 L8 E41 A1 Y, J4 `' O8 r" q, q& M
5
0 u# i5 V; Q. m' l4 o6" k% k1 N! C3 a) F2 ?
7# y7 s7 H+ I+ T5 C1 q
8. k; g) Q/ Y8 \8 ^9 q
9 ]7 G/ K5 P6 d5 S. e4 m: [+ R
10
7 {% R& d$ O6 C$ [5 R11* ?( v1 ]7 y4 N. W8 U, J' n0 R% x
124 @7 t# w- L9 @! P% {
13' R( {+ O% W. W, Z
14+ g' i& v. G6 J" T, o: v
150 ~4 q! O9 d, G7 Z+ [
8.1.2 []索引器
6 X" ^% f+ O* \ 对于 str 对象而言,可理解为其对字符串进行了序列化的操作,例如在一般的字符串中,通过 [] 可以取出某个位置的元素,同时也能通过切片得到子串。
/ @- F% X3 W! N1 I pandas中过对 str 对象使用 [] 索引器,可以完成完全一致的功能,并且如果超出范围则返回缺失值: t' a8 b, A( L7 T( ~
~: `( p5 `& @: b* ps.str[0]
+ n% P( z0 y# v" e% F2 S' POut[10]:
" {. [# H' ]& I/ x0 E6 A0 a
2 }* J7 A9 W3 q4 m1 e7 a; R# u6 K. ]& o
2 h
, ? q9 l1 e' M. C) B* |dtype: object6 }8 V7 e1 y; e1 z
5 O3 H) [' Q' v5 Y% ^5 F
s.str[-1: 0: -2]
% V! l# t( g1 |; [+ ?Out[11]:
2 `3 m: U9 v1 I( _ s" l x/ U2 t0 db2 {9 N$ N3 O: w; J" }
1 g. W8 F' [( E) j* l) o( c
2 i
( m1 C0 w( `1 Z! H9 sdtype: object" J' u7 H- e- ^5 X+ [' _7 d
# |3 l8 J3 R* J$ i4 f( N) o8 ?
s.str[2]
% }5 s$ _' n) Z. G+ A `Out[12]:
" E7 r3 V+ z( h! w* `. U( j0 c( d) j4 J( l+ l* i! G, t
1 g7 l8 L" `0 n0 D( A4 g& h
2 NaN# E* V% M, R/ Y8 w$ v1 ?8 F% s; f
dtype: object
2 A3 \2 t* \7 z& r2 l# N+ v R5 _
r& R2 n- M9 i0 G1
6 r$ `3 [& ^ ]) q c- N$ H+ y2
( u; h5 L+ J4 B) H m3
+ D. n. ?( A4 K4
! o0 y" \: x5 ]: }" W- Y4 w) \5
/ g7 Z" ?3 _$ D/ n" Z- R/ u6
4 h) ]# k' u2 y7
- M/ U* Q' q2 K, L% R& W. a" J8$ L' K/ Y0 `7 _3 f! R! \. T9 n
9" b, X* h4 |" a9 p! G
10" r: N6 E6 C! B8 l0 {2 b( p
11' X8 z2 g) c4 @, H
127 k/ _. Y' Y3 `3 T
13: W) D: x8 z, e/ j. w
14/ `7 p) N5 }$ z$ z
15
& f+ C) k7 J+ c1 d* l16
3 {6 j" j1 F6 ?3 H9 m m0 M1 i/ S' T17
: N5 t/ X: I8 Y/ ]' W7 s+ g c18* ~: C: k( ~" E `! [
19
g9 T5 ]% u9 y4 F20: a* N# c; @( l, x5 c
import numpy as np" K3 k: s. M: `: d
import pandas as pd
5 Z( L! r) p9 R
& t% ~7 g( o8 x7 Xs = pd.Series(['abcd', 'efg', 'hi'])
0 I* `3 I* q) _, xs.str[0]
# a5 T A |% B# J1
! B' T9 s8 b2 p+ B; f+ ~2
/ ^, Z+ q) Y; l$ W% J! a* o3) D6 Q0 C6 ?$ J) Z& h: n
4
2 \ ^2 j% ~+ @7 \4 T5
# |3 ~+ t! w% U0 a
; ?- P/ \ b* i- l- m1 e: n0 E9 D: k$ R8 ^6 D$ @6 h: h
2 h
% X7 ]+ b3 n6 h9 f9 v$ n1 H. bdtype: object& k2 x W. H" g! G* z* w$ \
1% j! k; i! D" `2 e0 s
2 h; Q' Q2 _0 c' [
3
& q% R* a1 d3 w W2 ^4
8 V1 l+ A5 o6 }% ?) v: {$ Q) h! t8.1.3 string类型) b; ]& N) C" n2 k
在上一章提到,从 pandas 的 1.0.0 版本开始,引入了 string 类型,其引入的动机在于:原来所有的字符串类型都会以 object 类型的 Series 进行存储,但 object 类型只应当存储混合类型,例如同时存储浮点、字符串、字典、列表、自定义类型等,因此字符串有必要同数值型或 category 一样,具有自己的数据存储类型,从而引入了 string 类型。. _: p: k9 D3 k1 D+ d
总体上说,绝大多数对于 object 和 string 类型的序列使用 str 对象方法产生的结果是一致,但是在下面提到的两点上有较大差异:
- q" K5 Y' @( ^6 ?; L: R$ l9 N4 I! z0 V
二者对于某些对象的 str 序列化方法不同。
, Q8 }# g% n/ w7 }! V* I) a( l6 z, S可迭代(Iterable)对象包括但不限于字符串、字典、列表。对于一个可迭代对象, string 类型和 object 类型对它们的序列化方式不同,序列化后str对象返回结果也可能不同。例如:
; v% B( }+ N# _s = pd.Series([{1: 'temp_1', 2: 'temp_2'}, ['a', 'b'], 0.5, 'my_string'])
* ~1 R/ O2 T1 d4 |: v+ |s
/ g3 S% j0 }$ S7 a0 t9 B! K: \" K1! {( Y* m- t4 T$ h; t
20 \* n8 U: W0 a1 ?
0 {1: 'temp_1', 2: 'temp_2'}
. u4 T% f1 {3 X9 Y$ r* P1 [a, b]5 q$ Q5 U2 f" c
2 0.5$ O, f* W6 |5 v& @
3 my_string
2 U2 K) T f' Y* }: Edtype: object
, ?! u& C5 Y6 M3 E& `5 [1
2 H+ _$ ?5 N8 N* |7 g- p" |2
6 }4 U% B ~' r3/ Y3 b! p" F! A4 C
4
8 i) ]/ O' l2 g& K$ h5
' v- e3 q& [5 D1 `4 U# ?s.str[1] # 对每个元素取[1]的操作
* f/ b. v3 |5 e: z6 S17 G' l6 S. K c5 ^
0 temp_17 u: N( h( a( K, N4 G1 ], i" v
1 b
1 o! k8 p. x; {% S F3 F7 V9 ?* M2 NaN; ~4 _) x T8 n$ Y5 ^8 {/ R$ s* C
3 y$ H, y1 B: n k2 ]: Y: O
dtype: object
1 Y9 S0 _5 H; \3 k6 v: T# t9 q10 J( \4 |/ f; W! Y
21 q) U$ X! A" h8 W
3! I& @% `1 w1 D6 U3 {& q* H, m9 ~
48 ?! N ~2 A- v% O3 c7 e. {/ \
53 }/ ^$ `9 {6 X3 ?* K% h) L
s.astype('string').str[1]" P P+ w7 T1 a) T3 h; o8 ^
1
7 t* f% ]! U/ H& e0 1
( X# Y+ U2 {0 Q1 '
1 E0 U8 v1 t# H& k2 .# S) S! @2 m) c% `( J% H
3 y
8 K" I: C) V5 G' B+ w* a+ {dtype: string2 k& O* s7 p( S/ x3 H
1
) T8 n$ {8 m4 l; l1 Z5 b2
5 U5 ]; v4 s- X% k% W4 `$ B3+ g1 q1 U) W4 T5 H+ C2 `
4
( f/ ?5 A% L1 b: j9 Q( }% F5
- O0 U/ ^3 g ?2 i+ F除了最后一个字符串元素,前三个元素返回的值都不同,其原因在于:
$ {' `0 d7 ? v' D1 ^# z# W: J, H4 L9 z9 {% k9 }
当序列类型为 object 时,是对于每一个元素进行 [] 索引,因此对于字典而言,返回temp_1字符串,对于列表则返回第二个值,而第三个为不可迭代对象,返回缺失值,第四个是对字符串进行 [] 索引。
6 [. z7 W9 Y- ]" mstring 类型的 str 对象先把整个元素转为字面意义的字符串,例如对于列表而言,第一个元素即 “{”,而对于最后一个字符串元素而言,恰好转化前后的表示方法一致,因此结果和 object 类型一致。
4 C/ x* e1 \( N: j8 V' t3 estring 类型是 Nullable 类型,但 object 不是# ^# V4 X! |: Y" c: P
这意味着 string 类型的序列,如果调用的 str 方法返回值为整数 Series 和布尔 Series 时,其分别对应的 dtype 是 Int 和 boolean 的 Nullable 类型,而 object 类型则会分别返回 int/float 和 bool/object ,不过这取决于缺失值的存在与否。' p( H O' d0 x
同时,字符串的比较操作,也具有相似的特性, string 返回 Nullable 类型,但 object 不会。
( |* r7 | U/ ~# Os = pd.Series(['a'])
& \- z+ s3 @. ?/ ]6 L% a# P4 o9 y+ o2 m
s.str.len()
1 L6 E6 ~0 s \& b" cOut[17]:
& A7 _+ l3 h( K5 T O0 13 ^0 A5 A% m* P( k$ n5 o i
dtype: int64) }5 t5 @8 _% `- g* G5 z$ Q- T# _
* n: g$ u7 s. S6 w" X
s.astype('string').str.len()
6 B+ E" `5 k8 q* V, D2 XOut[18]:
$ d, c7 w0 E+ {4 {" {8 `0 1* \$ B, w0 O9 K* G
dtype: Int64
b) l s+ u% Z. ]! S6 g
) z5 G+ V0 B7 r `6 F: u1 G Ns == 'a'
4 y3 R. d% E# ?4 t; C0 o- h& s6 O' YOut[19]: 7 L# C) q8 i5 n' z
0 True
1 [4 a0 |$ D( A" \dtype: bool
) x& I4 D m6 n# \( ~. W0 ~2 Y7 v% M9 x5 S
s.astype('string') == 'a'; i6 |3 l& N7 j4 w5 q/ ~0 o Z# P
Out[20]: t* X( F2 U) c1 x. n/ R0 W6 e
0 True
, L4 i: y% u; o* V7 Sdtype: boolean
f& _* H) ^8 U# |, e- }+ d& k5 h# ~! l Q8 C/ l
s = pd.Series(['a', np.nan]) # 带有缺失值
- w# @ p5 W* d' r8 g
0 R, j s5 V) {9 ]) N* z5 v* O* Js.str.len()) F9 o3 t2 i- _# @9 c+ n- b
Out[22]: 7 M, U, _9 J5 ~' w; V3 Z+ i
0 1.0" D7 ~6 d( V3 T. ^
1 NaN! s/ d# |0 v' V1 L ~
dtype: float646 K! f# b @6 h; [& C( [! ?
' q2 j! X. |; |0 u0 d0 s5 Ys.astype('string').str.len()! h* V+ A# I6 x% l. Y5 K
Out[23]:
( E; o5 c" \" C* u+ Q* D0 1 Z2 r! P0 k# Z& C
1 <NA>' r5 f7 d( Z+ P0 F) P' {
dtype: Int64
4 v" R. Q2 M7 L) J. l8 A% T
9 x" L! V8 ^& x) Ms == 'a'
9 b: i# `( Q' ?5 t4 q' @5 p+ POut[24]: 3 X8 F+ X7 h9 H7 |% J! \
0 True
8 e: i& N* }* _4 @1 False3 z! a. g* E- h1 E
dtype: bool! d8 f3 @" u& `7 x& ~
" f$ |4 }! N! G% L6 E0 k5 \s.astype('string') == 'a'
* \$ h( z6 u8 qOut[25]:
3 f( A* Z: [8 T3 e: F6 N% p: @1 C0 True
( S2 I& [/ n6 D) [1 P/ u' P1 <NA>+ D+ f7 w+ w+ l$ a/ a
dtype: boolean/ ?" F' |! O7 q4 r8 O1 H, d$ ~1 @
- r, C6 r: s4 w; ^' T
1
0 s' h) R4 b* d) u" X% c29 a; V/ ]+ c+ h+ W f% G
37 p$ c3 H6 x- f; A/ b$ i# \$ J
43 Q5 O& r: U' I
50 i" \2 I# Z6 k8 S) f
6
9 s2 J8 r$ ]* d% ^7
: D C' Q3 _! S! p3 e+ @82 W6 S6 U5 E, q0 `% X
9' {: _$ i# A: m Z6 [: @
10
& E) B* P2 n, N+ B2 a11% ~5 p4 d% g K$ ^4 ^0 j
12% v/ v1 {5 V/ Z# Y9 f6 T
13
2 {, h: g: t5 `" V/ |14
- x) K" A' ~1 z; y15
4 j4 f" g( L8 s. W. U+ f' z4 R16
: A$ r2 u X3 c* d& i1 T17
4 g' ]$ p2 @2 W- U' d8 j18
J4 R1 b3 ^; E- j o19
3 K% i; ^: m: o# y0 ]# ^( m20
8 |' \% O- K ^214 z2 h+ d; r( |- k% Z
22
! G/ `7 R) w6 n5 O8 j; `23
" i( K2 V" Z- f, m2 R$ W. E24
) Q& `5 v% G: M255 X4 S8 {+ c" n) e% p
26
8 ^3 }5 }3 X& g278 O) N+ } M+ A1 {9 s$ t, Z
28: |6 D- } h* ~' W# e
296 N* j+ ?. E1 O- q
300 P5 a& [2 J+ L$ a9 T) Y! O
31
' a3 B4 D& t+ Q$ G- h! \+ o32; ?. z6 \& U4 A& [- s- O4 M5 J
33
7 N4 [. K0 m, @4 B9 Q, i V }- b% r {34- G) C) U( `5 z4 C
35% R9 m2 m6 `( x8 j' B% }% W
36: I$ V6 e* |1 u3 W7 j
37
0 c4 i) h; W- n. M% |8 C38
4 z, d0 E. d; K% A39
5 Z# G7 }: {% z8 z5 ^40
' b+ \- F# L+ g5 w! k" |* h' r8 `41, W0 ?& m! Z: v. R: A/ i
42, {2 Q5 j$ V9 H4 l' n
430 x# L: L+ _- J2 W4 A
44
_! G- \9 a( J9 {$ t/ }5 L45
7 q. q, Z6 T/ \& G6 h& U& ]46
" B% e" |& @3 Y+ n7 K; G0 j47/ a: m" w0 g) r
对于全体元素为数值类型的序列,即使其类型为 object 或者 category 也不允许直接使用 str 属性。如果需要把数字当成 string 类型处理,可以使用 astype 强制转换为 string 类型的 Series :
( D/ i& j0 p* m: c- E& W
; t: H$ @9 E- @8 P( B. D5 Ys = pd.Series([12, 345, 6789]): L! A: A" S q! T- ]
& R; b4 Z ~1 C0 v
s.astype('string').str[1]
$ @! K9 _9 V0 ]3 c' rOut[27]: / p: E- q) y) K# c- q
0 2( y8 p& x' r5 G/ t* w$ _. ~% h. o
1 4
& X) G0 {4 i+ y/ N* t2 7; [( a4 Y" |" J3 k: T
dtype: string
& ]4 e/ @' s* {. z$ M1 ]1 r17 G% m% Y/ m, G. z7 j6 E% D
2
1 I9 F$ d( k4 l, S, B3
6 g! w# v S* x9 J3 v- z4 K+ |4
* O- S3 W5 p. ~) v# t4 i0 p5 w) F5
9 ?: I! R% ~$ W4 g/ H6
/ e5 _" t) _6 S% {9 Z7 o, P7
: O f9 R; p8 d7 c# T4 S8# q2 o) p: g/ w* O) W
8.2 正则表达式基础3 z$ e2 X" ~2 K1 J% r% G& w Z
这一节的两个表格来自于 learn-regex-zh 这个关于正则表达式项目,其使用 MIT 开源许可协议。这里只是介绍正则表达式的基本用法,需要系统学习的读者可参考《Python3 正则表达式》,或者《 正则表达式必知必会 》这本书+ L( B3 R) @& A. P
, ^; \ I! k" I8.2.1 . 一般字符的匹配5 o: n- _% h$ M7 a" W% F" Z
正则表达式是一种按照某种正则模式,从左到右匹配字符串中内容的一种工具。对于一般的字符而言,它可以找到其所在的位置,这里为了演示便利,使用了 python 中 re 模块的 findall 函数来匹配所有出现过但不重叠的模式,第一个参数是正则表达式,第二个参数是待匹配的字符串。例如,在下面的字符串中找出 apple :
5 d% H9 X9 g2 {3 E2 t" @' c1 _* `. `
+ P! g8 z. ]$ H4 e- s# qimport re
3 o1 F: ?( ?) V8 l/ u' E0 j( T( @, e& U
re.findall(r'Apple', 'Apple! This Is an Apple!') # 字符串从左到右依次匹配7 R! O5 P: C8 C P3 k5 D# W
Out[29]: ['Apple', 'Apple']" b; z9 ^$ h% _/ U# }) y3 z
1 C/ e) H5 O! l6 t9 A
2" k" v1 V4 o/ [; M5 a! }4 ]
3' ?' R: ?' L" q# X7 P" A7 Z
4
% u/ C- [3 |5 K' H. b$ P$ F8.2.2 元字符基础1 F. \) V+ E2 Q/ |; X
元字符 描述
) C- y( ~$ R7 P" o. 匹配除换行符以外的任意字符
0 [ ]" j: L& H5 s; e5 {# `[ ] 字符类,匹配方括号中包含的任意字符/ c8 q1 ~0 x. X& a1 Z6 k6 t1 U: g
[^ ] 否定字符类,匹配方括号中不包含的任意字符
* p$ r% Y0 z' \+ k% b* 匹配前面的子表达式零次或多次
3 _( _: K0 R( v; K+ 匹配前面的子表达式一次或多次。比如r’d+'就是匹配数字串,r’d’就是匹配单个数字7 k0 W: a" P/ X# B
? 匹配前面的子表达式零次或一次,非贪婪方式9 h( |: B/ W: x) }
{n,m} 花括号,匹配 n 到 m 次由前面的正则表达式定义的片段,贪婪方式
$ b3 n- P; n& z4 c0 g7 A0 v(xyz) 字符组,按照确切的顺序匹配字符xyz1 D3 W+ L s& M+ ~; J
| 分支结构,匹配符号之前的字符或后面的字符
4 A. L/ ` D3 T2 q4 e! _0 h\ 转义符,它可以还原元字符原来的含义$ W m( N7 X( s7 D8 g
^ 匹配行的开始. @& H1 B- u+ p8 L. x6 H% X
$ 匹配行的结束
# h1 ~( U5 g% g3 `* j- y" X L" |* nimport re1 y Z% `, ^2 @$ ~% B) B
re.findall(r'.', 'abc'): r. \7 k, R/ m8 l$ l
Out[30]: ['a', 'b', 'c']# W3 C0 b+ ]/ |2 `& ^! m
: C1 z1 Z" W" l# q1 v3 p' k
re.findall(r'[ac]', 'abc') # []中有的子串都匹配
# l9 S1 `; Z2 `1 y! ^3 ]# K( COut[31]: ['a', 'c']
4 }# z) G4 {0 i$ \1 U: p: P- W& ?# P7 I# o+ ]+ s* @- g/ l2 a
re.findall(r'[^ac]', 'abc') 4 k* w# @/ c1 V% \; p
Out[32]: ['b']
3 J9 H; W2 g& V" P. @' a" V" z) p0 T$ P, M' p5 M) I
re.findall(r'[ab]{2}', 'aaaabbbb') # {n}指匹配n次
' _7 B/ I3 s8 _/ kOut[33]: ['aa', 'aa', 'bb', 'bb']
8 N0 ^6 u* C0 I$ M- W9 s, Z9 j* V" G1 b$ h3 V
re.findall(r'aaa|bbc|ca', 'aacabbcbbc') # 匹配前面的或者后面的字符串4 T9 m$ M; P/ l* P8 x1 P( F
Out[34]: ['ca', 'bbc', 'bbc']2 ^ G7 l+ A9 v, p5 W2 I( g
- ^3 q" z, x7 z& j
# 上面的元字符都有特殊含义,要匹配其本来的意思就得用\进行转义。/ Q( r" i& K" R. c6 `' J& N0 D
""". T% T7 Q6 q3 j! a- G" {4 I) z
1. ?匹配的是前一个字符,即被转义的\,所以|前面的内容就是匹配a\或者a,但是结果里面没有a\,相当于只能匹配a。/ P4 a' y# w7 f% z0 X
2. |右边是a\*,转义之后匹配a*,对于竖线而言左边优先级高于右边; q4 F3 x4 Y) W T+ m
3. 然后看目标字符串aa?a*a,第一个a匹配左边,第二个a匹配左边,第三个a虽然后面有*,% }" v; H3 K/ O; b& X- a
但是左边优先级高, 还是匹配左边,剩下一个a还是左边,所以结果是四个a
9 i4 g# {8 r( y: i"""
+ O( E+ J# T7 [: n/ f% O9 Q- k& `# p# {! ]2 f
re.findall(r'a\\?|a\*', 'aa?a*a') # 第二次先匹配到a,就不会匹配a?。a*同理。$ c+ O( }8 S1 B; W4 X9 O3 `# |6 M
Out[35]: ['a', 'a', 'a', 'a']: v: Q# ]7 M) U1 \; Y# h
% S* k% q x- N6 v# 这里匹配不到是因为目标串'aa\a*a'中,\a是python的转义字符(\a\b\t\n等),所以匹配不到。
0 N: `) }# j2 J+ ? e6 l# 如果是'aa\s*a'之内非python的转义字符,或者'aa\\s*a',或者r'aa\\s*a'就可以匹配到\字符。
- \, @. y0 q$ e, a( U) K Q2 ~4 s% Ire.findall(r'\\', 'aa\a*a') 4 y2 b3 i& n( D( A
[]
& [ p6 o6 Y+ j$ j, R
& r ^% W, W, N1 k7 r$ Kre.findall(r'a?.', 'abaacadaae')
' w, \( a0 V% `; N- e& vOut[36]: ['ab', 'aa', 'c', 'ad', 'aa', 'e']+ c9 S$ u* r% S+ q7 d- A
' p& C2 _. s" e' q( {4 @- vre.findall(r'(\w+)=(\d+)', 'set width=20 and height=10') # 多个匹配模式,返回元组列表
6 p( \& Q0 I2 ?' C3 S3 b; A' u[('width', '20'), ('height', '10')]
) {6 G' h2 z5 l' b5 T7 e u! }) ^0 l; w" Q! ?
1
Y$ [% {! @4 w27 \) K9 X$ [; h+ ^6 u
3
8 x6 u) d, k: m8 C+ N9 f4! y9 W$ i0 B, \% ?" b2 ]* T/ h
58 z' O# k! q! U" ^
6
8 p8 B3 e; o% W; G/ @0 g' N7( Y1 r" u1 I* r2 J8 k
8
5 s6 f- r$ M9 t4 F9
; p9 c& X0 @: Q8 U: X108 K1 s% V8 ^: Q" D9 D {
11$ b: D+ D6 ?' Y
124 L3 x6 F% i9 I9 K# A0 W0 F
13& K* n# i+ V6 s, N; f3 U! s4 ~
14
& ]0 B6 ` E" W0 ]7 j8 s g15
9 m. x6 U& Y; |: O' Y* M162 R2 a, t2 R" f7 _+ s$ q
176 n2 G z$ u: s4 _: L/ e0 C
184 u' H" ^7 I3 D+ ?, ?0 A- D
19! S" s9 S8 L! m: N
20! V1 b3 k. O, V4 N/ w7 a3 u, P; H
21
8 B) z$ z+ ?* W/ ~9 W! }; F22
% O6 e9 R9 R6 w23
( T' V0 [ ~3 P5 |0 U3 _, h24* K& H4 X, x$ X1 a$ a
25 b3 t8 g+ {/ u, n c' U
261 k ]7 }. k3 t' B
27' i, j" u! o, J9 O- M
28
7 q' Y e9 c1 r1 e29- M+ A4 F/ M1 m- a( B, J) d
30
6 C8 ]- U3 g' z$ A. m317 c( O6 R/ |1 o; s. z
32
; A) m# Y, A; L' p+ F33
: H9 a! l) A2 o- c34
$ [: z3 I* u. d7 r35
0 b6 d! y! j) [. \3 {0 {36
% M9 J5 R) X+ |37
+ w8 L5 @+ N$ O m8.2.3 简写字符集/ h* c, q I& z$ i- V
则表达式中还有一类简写字符集,其等价于一组字符的集合:2 {8 G- X4 n2 S9 O
: f! p/ k; D0 D3 `. Q简写 描述& X4 u- z$ f" U8 ]; ]3 k
\w 匹配所有字母、数字、下划线: [a-zA-Z0-9_]; Y/ S" o7 t& g. b6 i
\W 匹配非字母和数字的字符: [^\w]
2 s; W% I$ d, d0 J( `- {\d 匹配数字: [0-9]
2 ~# |. J% B6 P" H# K/ y) }\D 匹配非数字: [^\d]
* D7 F% R; W5 {8 F V/ G4 c\s 匹配空格符: [\t\n\f\r\p{Z}]
/ H, K3 I5 ?! Q; D3 a" {5 s\S 匹配非空格符: [^\s]/ O3 z* T; }5 q9 [1 o. E
\B 匹配非单词边界。‘er\B’ 能匹配 “verb” 中的 ‘er’,但不能匹配 “never” 中的 ‘er’。
, R; d6 w" _! v6 D6 ^re.findall(r'.s', 'Apple! This Is an Apple!')
! C- `& K+ _( F1 |% k2 BOut[37]: ['is', 'Is']* V0 l! c8 K8 a6 T7 Y+ h" q. ?
1 k& g9 l. }6 m' u. F4 zre.findall(r'\w{2}', '09 8? 7w c_ 9q p@') # 匹配任意数字字母下划线的组合,但必须是两次
% z1 f, N# Y4 O7 O4 f/ W# B3 xOut[38]: ['09', '7w', 'c_', '9q']# E( Y* p y: a# L/ V- w
6 }+ m, r1 P8 v' h0 h1 ~
re.findall(r'\w\W\B', '09 8? 7w c_ 9q p@') # 匹配的是两个字符串,前一个是任意数字字母下划线(\W),后一个不是(\W)
4 }; Y# u3 [1 y1 ROut[39]: ['8?', 'p@']9 T* ?8 r$ m5 @8 i4 ^( m
1 U; L5 W5 h) ]$ V( ?re.findall(r'.\s.', 'Constant dropping wears the stone.')& C1 l5 V$ Y7 u J' A7 J
Out[40]: ['t d', 'g w', 's t', 'e s']' t$ i* ^$ f- |! u' f
# X4 C% a' D* S/ y6 |4 H8 Gre.findall(r'上海市(.{2,3}区)(.{2,3}路)(\d+号)',/ \# d# w% @% Y# ]* X
'上海市黄浦区方浜中路249号 上海市宝山区密山路5号')0 r- y4 U+ R% Q1 M2 w" ?# V
$ ~" n* h* `5 U4 e( q0 HOut[41]: [('黄浦区', '方浜中路', '249号'), ('宝山区', '密山路', '5号')]
6 r# C, [6 E8 H9 ?! \8 p
' n$ t, i5 J+ R5 t13 ]" u7 k. V; h4 Y
2
2 v/ i% \: Q6 m& ^" Z4 Z7 x; F3
/ ` w( M. x5 ?' r! H4
. Y+ C T9 d& H7 h1 L0 U' s8 \* Y5% z4 y- i7 [2 N. x4 ^4 L2 U
6
0 i% p& Z# R, Q4 z. G7
2 ], Y4 _" i8 O7 S. S8
! c+ ?8 ^; i2 I91 `. Y6 w& L& K4 S
10
, [! T: l2 T" w2 v11
. g; z! C/ r9 H8 Q3 ]127 I6 j/ |8 r6 ~+ I1 C
13
- `: J9 v* ]1 S' W0 R# Z% r3 X14 T* i N: R$ T3 p ~7 q0 n
15
- t7 I4 q6 `# g. |167 C& i5 E- n/ N
8.3 文本处理的五类操作
& G- u7 C, |+ g- ~; n8.3.1 str.split 拆分
& r/ v5 X& ?4 O: n8 y str.split 能够把字符串的列进行拆分,其中第一个参数为正则表达式,可选参数包括从左到右的最大拆分次数 n ,是否展开为多个列 expand 。
- R) R5 N& [0 b+ J; M( x9 T/ d% a! ^. M; M
s = pd.Series(['上海市黄浦区方浜中路249号',: V8 A0 r1 F; e" S
'上海市宝山区密山路5号'])1 B6 `3 z* @/ r. |% t5 L" W
# M* ]0 e8 k8 O, Y
5 U2 y9 W# ?1 gs.str.split('[市区路]') # 每条结果为一行,相当于Series+ Z7 Z. v! v/ N h
Out[43]: , |% s7 ~" x0 a U4 s
0 [上海, 黄浦, 方浜中, 249号] u1 Z& \2 v7 t; k- R0 q
1 [上海, 宝山, 密山, 5号]
2 L5 T- X* J* ?9 Bdtype: object, }9 T M6 I3 K Y7 `: y( {; f
r2 M) K( o' P7 Z- w4 c
s.str.split('[市区路]', n=2, expand=True) # 结果分成多个列展示,结果相当于DataFrame
8 j( x5 K: n ^' T9 K POut[44]:
; r4 B6 ?0 Y# D: H; p& P 0 1 2) K; O+ R) x8 I3 r: e9 C
0 上海 黄浦 方浜中路249号
4 p0 \* y1 U9 Z5 j V" `1 上海 宝山 密山路5号
P5 |' v* C9 l1
' P) s4 O2 p' S. j2
5 m. h9 h6 K+ P. j7 B3
; k' V6 @. G$ S( T- w44 m2 T4 x3 d! f) K1 @
5
3 L6 [: z2 |2 l. Y* |; I9 T& G6( Z! I! K4 B3 I
7 _" Y# S4 m, {8 S
8
6 ~7 o, T) Q3 J; `) d) R' ^9% H2 f" [6 P* ? G6 G
10. p: c9 X2 w3 ^
112 t1 x8 A" G1 O
12
3 F! K9 s( J' D( R3 }, h13
, Q% L8 g4 m6 ^" b9 R14
! M0 P! L* V" _$ I6 S15
6 U4 L! }* c. F4 f 类似的函数是 str.rsplit ,其区别在于使用 n 参数的时候是从右到左限制最大拆分次数。但是当前版本下 rsplit 因为 bug 而无法使用正则表达式进行分割:
' C! [$ T' E7 t/ i
5 G$ U- d6 t1 e1 G6 ]1 ls.str.rsplit('[市区路]', n=2, expand=True)
+ Y2 J- v1 C- [+ f M/ G. o: g% vOut[45]:
3 E+ o4 G/ ?3 N- q 0+ F/ c2 a: e" J8 N$ s& m% T
0 上海市黄浦区方浜中路249号
3 P% R" \( [ E1 上海市宝山区密山路5号# ~0 i. q7 I! g7 {9 t! a. k
1
d: {8 r! ^$ e a21 n: [; q- r- Z: I2 C0 E0 q( H
3) r8 V+ n2 ?- V" S9 Q; ?" `
48 O. s$ p4 s+ Y7 a2 l) H O
5
% g9 j( n' i2 t" ]3 |. s( C8 |8.3.2 str.join 或 str.cat 合并7 b- `1 J" N/ X" p
str.join 表示用某个连接符把 Series 中的字符串列表连接起来,如果列表中出现了非字符串元素则返回缺失值。
7 f3 i, L. Q( ystr.cat 用于合并两个序列,主要参数为:
R) \! u" i& U1 P" q' b# t# psep:连接符、4 V# W5 k: U8 k& g- ` P' q
join:连接形式默认为以索引为键的左连接. s! f( ?4 E0 U: F J
na_rep:缺失值替代符号2 ^+ _# \/ W1 i" d9 L$ l$ D; G' l
s = pd.Series([['a','b'], [1, 'a'], [['a', 'b'], 'c']])! u: S, |: t0 ^; W7 e0 ~
s.str.join('-'). f( l' s3 l* m$ y, f% L x5 U
Out[47]: % Q" Z0 a) j! D
0 a-b" Q% l* t# _. N# W1 j# l
1 NaN
( `! B. A$ `: @- F2 NaN5 ?0 M) i6 Z' [ e3 k/ z5 K5 ~/ e( Q
dtype: object: L- J' |7 V- e: D9 a7 I
1
! T$ u: j' v! d2 {, w2
3 f% q' d5 s' [; g. I3
: d" T* }7 X6 z5 L9 I4
2 w8 g3 [. ^% q$ I5) u) Q" F! ? H: O
6# q! J; e. [( M+ L9 g3 @7 Q- H' `/ b" R
7
$ @0 z/ J" r) P0 N* ~7 Q! x' ^s1 = pd.Series(['a','b'])) [+ i) G( o& ?3 y o, ^' G8 g
s2 = pd.Series(['cat','dog'])$ r. T! h! t8 [$ R1 M
s1.str.cat(s2,sep='-')$ ~+ W. o# _ I0 m$ g5 B- a
Out[50]: $ d$ Y& s) f7 p) y
0 a-cat
! x g+ y% z8 c* c1 b5 l. w2 A6 d1 b-dog
: J w D% B5 Q& W) |+ F: ]; Tdtype: object! ?$ H. z: q" O k
- Z v, Q8 ?1 b2 |3 }) n) vs2.index = [1, 2]
- }$ G, x5 p; z. `( j- Vs1.str.cat(s2, sep='-', na_rep='?', join='outer')
7 l, v( t/ U, v! u+ oOut[52]:
$ t2 B; e% m4 k0 a-?
* D& ]6 P$ @7 _6 V7 [- K1 b-cat
6 f# a8 V e7 F6 l; r- R2 ?-dog
; g0 |1 q2 M; l9 O8 v" F1 Sdtype: object) c0 F/ H. ~5 q7 z4 y2 }7 i
1! g# v; B& B7 \- B9 v- M; }
2
; O/ b, H" t2 l' d8 f% I3
9 U& B, K c+ m- h" a9 q4
; C+ p, i% [6 g- I5
2 B& I/ Y) U1 ^+ \7 [6& |+ x: e1 w6 U& O8 R8 ^: y
7
. Y; e+ g( j* @1 E# R3 M8- {# ?" q/ X: j
9
3 c d1 s* H: U8 F \1 [10$ p' ^% g' i* Z* P H: V2 V2 K
116 [: Y* i4 y# i
12
4 Z v+ Y! G4 _13
9 m& }* E9 {6 T7 [14
, K5 j& @; e& Z5 N- C) {. v% i158 X3 Y+ }3 e) J9 z% O7 U1 {
8.3.3 匹配
) d% q, [$ P" D* Kstr.contains返回了每个字符串是否包含正则模式的布尔序列:
# {$ c/ G* P% N0 r! [* T/ L bs = pd.Series(['my cat', 'he is fat', 'railway station'])
/ Y- c6 Q& P' C+ q4 h8 N: u& A. \s.str.contains('\s\wat')0 N! o4 O% ?5 D3 j% ]! C
% X5 c* b3 |$ e7 i0 Q
0 True
+ U4 b# M- B9 j1 True( `3 J' ?% n+ G( H7 O& u9 c
2 False( ^1 J) J# q5 ]) _! @' I, y
dtype: bool+ J) e0 j# O: A) |& J6 k
1. \ _( y4 @. F. M+ H
2$ V5 g! y- z+ j _) Q7 ~$ ~
3
- d' z2 B3 `' h3 I44 j4 f6 m5 D, Y" m. R
5. k" @. C( R4 W+ Z" \: R
6
; h% J8 |- J% Q7; B/ y7 G$ Y% y! `# r% `
str.startswith和str.endswith返回了每个字符串以给定模式为开始和结束的布尔序列,它们都不支持正则表达式:9 V. R7 l8 g; e1 ~) Q9 U
s.str.startswith('my')
4 S+ X: [5 z3 F& C/ |& w7 \. E Z
0 True. b2 [5 u& [# [/ J& y3 U
1 False
: {( ?0 O% Y% }9 d$ D$ e3 P2 S8 O2 False8 d' j! k# |" u8 l3 P% `
dtype: bool b# n2 O, B) Y) W1 r. k- z6 S
1$ n& n( B2 W: ~4 D @
2
+ M3 N5 t8 g9 N' T3
/ S2 H; I f0 Y L4* ?4 [7 I4 E/ g H8 r% o i
5! w# ~- i, K4 n. _7 |- d, X
6# W) f0 M8 O* `, ^, |1 }, @
s.str.endswith('t')3 L/ S' Q/ R7 A1 O1 [0 O! S
! G+ p* }9 V% z7 x* k" [- O0 True: x1 k( y* h9 [& Y7 z9 U
1 True
j+ E5 |- ? J, l3 j$ @2 False
( a# }" c$ `: `! wdtype: bool
/ x2 z* {4 m# j4 K* V19 ?! Z7 \" J4 U6 c1 X
2. _ q/ I6 t4 f! s
3
! e" p0 Q7 q2 K) Z: f; S3 h$ A4
. p1 {* E: W8 c$ p9 Q; o% [# z6 D5
. t' p- A5 s' N$ n! z8 [6# x1 c6 I- s& t
str.match可以用正则表达式来检测开始或结束字符串的模式,其返回了每个字符串起始处是否符合给定正则模式的布尔序列。当然,这些也能通过在str.contains的正则中使用^和$来实现。(貌似没有python里的search方法)/ p- K5 V( ^0 @2 d- ^7 u
s.str.match('m|h')
+ S2 [ v+ V2 }& as.str.contains('^[m|h]') # 二者等价
7 `1 w; f7 K# L! s" e
! V# s; e# r/ z% O/ v( c4 K: N; y" ?0 True
$ w Y: D* w" P* a" Z1 True
/ @% z5 C. U7 Q7 s! u2 False9 [* X) k% r1 Z
dtype: bool
4 [7 w/ e' @0 \' i2 B3 L12 i9 U3 m- t3 O( H4 i8 m, [* x
22 @8 R* [5 O( J, e' Q
3! ~9 M! } P& W- x+ c
42 D& j+ {" e9 [) h( l5 M! @
5) C# `3 u3 Q1 N4 ^1 ]5 n, h4 s
6, o8 _) O4 s1 n
7
- o5 D4 v% _& es.str[::-1].str.match('ta[f|g]|n') # 反转后匹配. Y+ D" V. l \/ T2 a) D
s.str.contains('[f|g]at|n$') # 二者等价
3 @: s! k" W. c$ G1 Q( r* D0 c. l3 w/ [& `+ n0 D/ ^
0 False: b1 J9 X8 ^* I I& a5 b
1 True2 D# U# w: L& z1 Q
2 True6 S; V8 T! M; E9 ?
dtype: bool9 J& V; i2 P; q9 `$ m V
1
5 L$ `( o3 S* F; V4 a3 e' Q2
9 C* _ N& w: O3& W1 O+ I6 s6 h( ]- {$ I7 `
45 {% U5 ?+ s: n
5, `5 ]* M; J6 O) [" c1 t+ M
6& Q9 _+ @. U+ N/ ]
71 A5 A# Z# E! Y6 g- X
str.find与str.rfind返回索引的匹配函数,其分别返回从左到右和从右到左第一次匹配的位置的索引,未找到则返回-1。需要注意的是这两个函数不支持正则匹配,只能用于字符子串的匹配:% l& N$ @5 C) H$ L0 F
s = pd.Series(['This is an apple. That is not an apple.'])
' z5 c* ~3 R" Z! Y" R- s: V
* f' j% [) O3 X5 p. Q8 ss.str.find('apple')' D' B! S+ s+ X- d
Out[62]: 9 k- _2 M0 \! r" ~9 o- ^
0 11
7 q, H0 @5 \' \! Sdtype: int64
& a9 x' V$ L* R' K8 [7 [, V' ~5 U) R
s.str.rfind('apple')
, G4 l$ g0 v n$ r. wOut[63]: 4 w! Y, U6 [; n* H4 U- w1 p
0 33
4 g' ?' q0 d: a8 h* A6 S9 \4 hdtype: int64, L+ G% z' K! O: {. o6 p
1
+ |: B; a2 [+ u6 }2 V H2
' F+ H u& z0 Q3
9 k8 J M( G* _* X. |( v, x4
+ `9 f- t: l% @: y; l' n" N59 ?( i; N" i$ p# C5 s) U" r7 A5 C
69 K& G) c* C" g$ D
71 R& s7 e) f" _+ {, M7 j6 h4 G' q0 l1 j
8' G8 M' k/ X' I: R; E
9
T3 l+ B" {" \5 B3 m' ~: d. w10
2 b. p l( a' e1 B& n+ H( m11
7 m) r) {. Y# U1 t替换
0 Z5 Y- @) F" j% j! Istr.replace和replace并不是一个函数,在使用字符串替换时应当使用前者。: s% B; ?% ?1 {- E/ e4 F
s = pd.Series(['a_1_b','c_?'])/ \8 v0 Z0 E6 {8 ]
# regex默认为True,表示是正则模式,否则第一个参数内容表示是单纯的字符串,也就是匹配字符串\d|\?% V1 V: D3 ^6 z- w* i3 `/ M
s.str.replace('\d|\?', 'new', regex=True)
# R0 k! o& g- V
; | B( }0 Y% L% N+ H0 a_new_b
d" h. \6 u% n: ~4 f4 m, ], c" Q/ N1 c_new R: @# o0 G+ a4 }8 f
dtype: object, p$ h. v8 z+ {
1: W1 _/ r( v0 X
2
* o) s7 V' h; R. I2 r30 A5 e; ]3 c) U% Y2 P3 w! p
41 {- }5 N. [6 X: s
5; n( Z$ D& S w% e- Q
6* O7 V& {& K8 V0 L0 j! ^2 b7 W
7
' J$ X! i* r# a1 ]: B! D, L4 p& t5 A 当需要对不同部分进行有差别的替换时,可以利用子组的方法,并且此时可以通过传入自定义的替换函数来分别进行处理,注意group(k)代表匹配到的第k个子组(圆括号之间的内容):/ Q) _1 g$ ~- y
) w: y: }8 C3 H$ J% q/ B2 ~, us = pd.Series(['上海市黄浦区方浜中路249号'," h* ]& t: f9 q6 g+ @8 [% Z; f+ D# W" s
'上海市宝山区密山路5号',4 @4 f4 U( ^! i9 E0 B4 z, P3 p
'北京市昌平区北农路2号'])
$ m7 a1 W6 v0 t6 B5 D2 Ipat = '(\w+市)(\w+区)(\w+路)(\d+号)'' X M6 B' |3 x" W
city = {'上海市': 'Shanghai', '北京市': 'Beijing'}
5 `( |7 `$ p& @! ~( ^district = {'昌平区': 'CP District',8 m$ H# J3 G$ P
'黄浦区': 'HP District',
- \" J0 y3 R5 r y3 [ '宝山区': 'BS District'}1 M# z8 U1 {& x2 u& A) P/ c: w
road = {'方浜中路': 'Mid Fangbin Road',% L6 A% F, N, g3 F, m
'密山路': 'Mishan Road',5 y/ m) L% v7 s! S7 o" G
'北农路': 'Beinong Road'}* K W( w2 h( l) z$ W8 u+ x1 \
def my_func(m):( u; G: |5 j0 P3 C+ d7 [0 ?7 U( _
str_city = city[m.group(1)]
5 _) H, x8 q1 a2 {+ k X str_district = district[m.group(2)], ~4 D o# K x% c
str_road = road[m.group(3)]
* A; b8 ]* [, m+ `9 {" U str_no = 'No. ' + m.group(4)[:-1]
; r s7 @/ t3 M return ' '.join([str_city,
% @: h _7 Q" E& g4 c9 L |% D str_district,
( ^- D( y L& J$ r str_road,/ D& e9 k" S$ s) y. F9 d' y
str_no])% o8 v: }2 a" t! w
s.str.replace(pat, my_func, regex=True)
, t) H2 a3 a! l* Q& E8 j$ ^) f: y) a& p; {5 ?1 ?5 f* j
1
; c5 y( b% ], v, ?- H( F! P2
+ ?. i$ U+ A- h7 m+ x3
. p- ~5 _" b _; w4& c. j; _1 x3 T
5
) \' Q: i$ W, @8 X6
& Z' @3 L/ z# A' a' n7 E+ `$ }/ l: G! i7* u; ^7 F, b* v( H- R
8, O( K- C4 A$ Q: f! Y) `* Z' _
93 _+ j4 c- a. I3 }
10
$ ?& O2 r6 d5 K y11
+ P* y+ U' N- j( T- b12
L: w; w- _) ^. s133 n% A& q% _, X4 I
14# K0 @2 A9 Z! f2 ]5 U
15
& D' ~$ x a+ a: N9 x6 q& A16$ S2 Y( [( q+ r0 e8 s
178 X' |* U% Q- t2 s* c- Z
18
6 Y2 Q' ^# Q& Y0 w19
' Y- u2 L) a$ [201 | ]) B8 q" d/ `
21) Y9 N4 U( {9 a0 M1 x. r1 ~
0 Shanghai HP District Mid Fangbin Road No. 249& m6 \2 I' O5 {: H ]
1 Shanghai BS District Mishan Road No. 5
- k2 s; ^' C& B, L( N2 Beijing CP District Beinong Road No. 2
( @4 o) w: |. t Gdtype: object0 W# k2 j p/ \2 Z: E+ }
1
3 f& \% a! k6 l, D21 a: g2 A7 \' |' {/ ?9 ?) n
31 y) {+ b5 A* ?" G
4
$ ~$ F. K* M8 z u0 D* `- H$ m这里的数字标识并不直观,可以使用命名子组更加清晰地写出子组代表的含义:, w: m+ [ s& d6 |
4 D0 O; D6 }) N& ^: X- R2 s/ Q) I# 将各个子组进行命名( M. T+ G3 z2 b0 x
pat = '(?P<市名>\w+市)(?P<区名>\w+区)(?P<路名>\w+路)(?P<编号>\d+号)'
: u/ b2 S9 m$ i4 Y% m( |& pdef my_func(m):9 l% p# T5 A7 c+ R
str_city = city[m.group('市名')]$ ?. a5 ]3 a5 T0 h4 R. q
str_district = district[m.group('区名')]$ u" u1 m" ^/ d$ u
str_road = road[m.group('路名')]$ W2 d. R# C" X3 L
str_no = 'No. ' + m.group('编号')[:-1]. u, ] E/ U n
return ' '.join([str_city,# ~1 ]* t/ s7 d2 p& e Y
str_district,
5 F% [+ y% U/ ^. T1 S str_road,% F( b% Y8 E5 [9 A ]5 L
str_no])3 K8 s& E: k) C" K
s.str.replace(pat, my_func, regex=True)
, m$ a8 S# p* F: s s& t1
0 V+ J! m: T3 s3 U2 k% D4 M$ n: p: g, }% I7 m. ?
34 r% v4 E( Q% t/ k/ S4 l# v% b& ~3 D
4
) T/ e7 e! R R& L$ _5! C* S: J! h' ^$ Z; r7 v3 `1 p
6% u) T4 C$ r+ @+ ~ u2 I- j) |7 m
7, ]4 n0 a& o5 x I
80 @4 v' a8 S+ L5 ]7 N+ o2 p
9
/ ~. b" L0 v; }, C10" R' i4 C" l) ?. H) ?
11
( U1 @2 h) C4 w12
% P( @% b, l b) D) ~: y0 Shanghai HP District Mid Fangbin Road No. 249
3 I6 e3 w0 b Y( y* S) b1 x( E1 Shanghai BS District Mishan Road No. 5! Y8 E6 t3 _3 }4 k2 L
2 Beijing CP District Beinong Road No. 20 G# [' K, }1 W* m O/ O
dtype: object' j# l6 \ V ~- x% w
1
; g/ q- \0 l2 ~* P+ |. ~; J2
* A5 q: H% O2 }: }9 [+ o0 H3
$ _1 ?) J; k3 Q. v7 A8 t( @ p9 A4
3 ?+ Y, N" N- X: C! @; Q 这里虽然看起来有些繁杂,但是实际数据处理中对应的替换,一般都会通过代码来获取数据从而构造字典映射,在具体写法上会简洁的多。) a: `7 R' C8 a9 ?9 h
- X' P8 f b" x5 ` ^7 i8.3.5 提取
# P5 E& a! B8 e1 {& o- ]- estr.extract进行提取:提取既可以认为是一种返回具体元素值(而不是布尔值或元素对应的索引位置)的匹配操作,也可以认为是一种特殊的拆分操作。前面提到的str.split例子中会把分隔符去除,这并不是用户想要的效果,这时候就可以用str.extract进行提取:! I. T/ |4 ~: \$ ~1 a8 \
s.str.split('[市区路]') p5 ^* \, l9 d/ V1 O0 g( x
Out[43]: ( S% x4 `* E- W8 A$ ?$ s
0 [上海, 黄浦, 方浜中, 249号]
4 v: S! {2 J4 @2 ?1 [上海, 宝山, 密山, 5号]
5 \% h. o4 z, `3 b' U Z. @/ bdtype: object) c0 p- A& Y9 s1 |
; n- H5 v/ `# g& ?4 N6 a4 f
pat = '(\w+市)(\w+区)(\w+路)(\d+号)'
/ C( q- b: \" t2 q; Vs.str.extract(pat). b4 F2 K$ Q8 p$ c7 F0 t
Out[78]:
; y" Z, s Y1 O! x3 s% p 0 1 2 3
$ t1 b, A% o4 i0 r1 C5 G0 S0 上海市 黄浦区 方浜中路 249号: U2 ~1 T* h ~4 o
1 上海市 宝山区 密山路 5号
+ ~& Y: a5 E) R# m& B2 y( U% S+ @2 北京市 昌平区 北农路 2号
$ x6 @( j3 H* C4 |' q1
7 M2 r3 |0 C% Z1 }2" j- ], T, r+ G1 B7 Y* F+ a z: l
3
+ S/ ^8 X; j4 s9 j# e/ f& y5 r! l45 e1 l, }3 a v6 U* D8 I
5% S( g& d0 H1 A
6
5 p" Q& }% o* _3 A$ r( d0 ~7. i; x4 X: |. O8 |' v: f
8
0 W( G& { x- |91 x8 s1 M/ A5 e3 g
104 _3 \/ g3 U* E4 m% P+ K8 C
11( |0 }, E7 y% H8 z0 H
12) P* g4 R- @( T
13& z5 M* r v. G, y
通过子组的命名,可以直接对新生成DataFrame的列命名:
1 Y+ A2 M2 r$ u: i* Z+ _; i; \
% F$ N: {+ ]% apat = '(?P<市名>\w+市)(?P<区名>\w+区)(?P<路名>\w+路)(?P<编号>\d+号)'
* c$ }' z9 I9 R2 u/ w! | zs.str.extract(pat)
& X! y6 b& s [; h" {' @4 `Out[79]: % E6 A* `% Y$ R) s r$ N
市名 区名 路名 编号
$ c. {5 q$ U. }3 o0 上海市 黄浦区 方浜中路 249号
: U5 ?3 K8 v M* [# H1 上海市 宝山区 密山路 5号) ` E* E$ O& ~0 M+ [
2 北京市 昌平区 北农路 2号& O, q5 _1 s! B5 } L- x8 E8 J
1, [9 J, W) i, P0 C* ^
2& X8 ]8 N7 S+ M. E Y# T9 G% P
3
& w6 S0 j6 Z6 G* v2 j9 ~- a* b4: \5 r/ K: Y( s0 X0 w
5+ M5 x8 }7 P% g) J/ g% V
6
& j2 y" I. W( x0 S74 ^$ k6 }5 r. A
str.extractall:不同于str.extract只匹配一次,它会把所有符合条件的模式全部匹配出来,如果存在多个结果,则以多级索引的方式存储:
9 G# d; c2 O5 w9 E5 K& H+ ?s = pd.Series(['A135T15,A26S5','B674S2,B25T6'], index = ['my_A','my_B'])
+ M% Y6 _7 f1 [pat = '[A|B](\d+)[T|S](\d+)'! h) ?* X# U* I5 f# r
s.str.extractall(pat)8 s. P0 p: T- G* T
Out[83]:
* S' a1 a( |( i5 G1 X: E; J 0 13 w, d1 L) n' m. x9 ~) ]! V
match " ~2 G+ l. T/ E T! k K8 {% w. z
my_A 0 135 154 R( [7 X. l6 }7 \
1 26 54 M* |4 H8 I1 |
my_B 0 674 2
) g+ S2 k+ q: [ 1 25 6
6 q+ v% T& q: {& p" |1( S& c- K( g* Y' E- f* A* L
2
" r7 X# ]0 a w, y- H4 `3, O3 t- d/ l0 ?) l7 f A
4/ i. p) u1 L, u2 v/ B
5
$ O; k4 H3 }* m1 |8 }6( s, ], Z! _& Z! d% }2 A
7
! X1 ? z2 g2 {# b2 @8
6 t# R% p$ G2 i; v( o0 R9- P% j$ ?5 m6 f/ z+ k. S
10
( I! [5 Z$ z) D3 f8 x0 o Mpat_with_name = '[A|B](?P<name1>\d+)[T|S](?P<name2>\d+)'
7 m: {) _4 e+ W9 [" ?3 z0 Is.str.extractall(pat_with_name)
3 W/ t9 J: H B; E7 X5 I6 Z& G1 ?Out[84]:
% [" ]; w+ ?5 m6 x# e- C8 e W+ g2 _ name1 name2" ?; Q* y! p H. V2 k- O9 Q
match ; C" B6 M* J0 O
my_A 0 135 15) m+ S" i- Z5 m6 u$ G, i
1 26 5
3 h$ l1 x9 x. {3 {& ^1 fmy_B 0 674 28 R5 a* k2 ~3 m7 k/ w: R( g, u9 Q$ ]
1 25 6
; ? W# s$ `; Z1% @3 b+ m F7 A
2
/ |3 i6 Y/ G" R" ]4 f3+ c3 _( y$ E( n9 Z" ?$ u
4
5 Q: T: g, Z: r5
9 [8 a1 Y3 S& `& R# M6
! T; ^" S6 h. D' O7. B7 ^9 V! ?$ r5 Y# t: X
8
! q2 s! |( ]: E5 L9
( A! u! D6 h3 u8 D4 Astr.findall:功能类似于str.extractall,区别在于前者把结果存入列表中,而后者处理为多级索引,每个行只对应一组匹配,而不是把所有匹配组合构成列表。0 t* S* m4 M# @0 c' _1 @# T
s.str.findall(pat)/ Q3 ?+ C7 S8 ~
1
& G! R) N2 B1 v7 Gmy_A [(135, 15), (26, 5)]
# N2 M7 n- l e8 d8 ?my_B [(674, 2), (25, 6)]! {8 n7 {1 k1 A; K3 I9 m" z/ Z
dtype: object8 N% U) n3 ?6 u* }# c/ q( g& m
1
' X9 X0 j c$ E. t* v) q# l2
2 ]/ z: F6 I% t0 `' G3
# X6 x0 N- E8 ]4 Q8.4、常用字符串函数
4 }9 ?8 E8 q9 |0 u- c- S5 B5 o 除了上述介绍的五类字符串操作有关的函数之外,str对象上还定义了一些实用的其他方法,在此进行介绍。
* N, L( R/ e# m/ B U4 j* y
: Y. x X. u. r/ O& S8.4.1 字母型函数
$ `# u/ l+ k1 \9 c/ q upper, lower, title, capitalize, swapcase这五个函数主要用于字母的大小写转化,从下面的例子中就容易领会其功能:% t( _' O; Q5 }* M* | ]: O
+ x( Z6 _; i {* h+ f1 X, |7 z: ~7 ^s = pd.Series(['lower', 'CAPITALS', 'this is a sentence', 'SwApCaSe'])
- Q. z. R/ _# N, C/ N" C# P; @2 [ H
s.str.upper()' z( p* Z" D) X2 Z! B) R5 S
Out[87]:
4 S# H$ G( z2 U* [: ?0 LOWER" R4 i) N4 G9 S9 A! r7 X G
1 CAPITALS
/ `, B9 g/ @8 T, x- u- a5 Z2 THIS IS A SENTENCE
; Z% F: v% ^1 V3 SWAPCASE
% H: i0 x5 m& I1 i9 O2 J3 hdtype: object
" b# k2 J+ T* A; V/ ~% L: k
- D% o' Y; c# l3 e5 A J js.str.lower()7 {4 x0 Y+ `! S+ A( \
Out[88]: % h. V" l5 d5 V
0 lower
$ F3 Y( v0 G V3 f9 @% }1 }1 capitals
) ~5 |& i8 w* Q- t1 j& D9 V8 e+ B2 this is a sentence
7 B" H) [' T& w4 o" \; F3 swapcase
8 G$ R& C: v: {% \6 Udtype: object
, k0 T1 [* a+ {# C0 A3 e9 [$ _3 {' j* r1 p# F7 [
s.str.title() # 首字母大写
! ?& g8 K. {$ q. w* qOut[89]: ( @5 M1 {/ T) W" n8 k9 ^2 A
0 Lower
+ o2 V( V" w& d; a1 Capitals; s) l/ h% [5 o) M9 }' z- O
2 This Is A Sentence
! l9 {- e5 Y/ q* n0 V3 Swapcase
6 n6 Q5 u7 t! X G; w& udtype: object; M) a, J, q! V- ?7 }
4 u. t% _; S/ Z' f! ]/ @- i7 js.str.capitalize() # 句首大写* ]9 i6 {; N9 z
Out[90]: ; w6 k7 n% A5 I' h% U
0 Lower
3 O3 I, R- `; M# T3 D( M- }( t* C1 Capitals; Y' \0 F4 t3 X9 g1 Y8 ]
2 This is a sentence _: B% J8 a' ~
3 Swapcase
( J- ]- I/ x1 K& z7 {& `dtype: object
0 ?6 H7 M! b: t; c5 ?$ t: N: z2 C S
s.str.swapcase() # 将大写转换为小写,将小写转换为大写。
8 J" v5 c8 T2 y7 |3 X1 \' j5 yOut[91]: : A! X" g+ x& h& w
0 LOWER1 U, b: ]9 L5 E/ A W/ o
1 capitals0 u+ H" M- d* n6 ?5 z' e( p" z
2 THIS IS A SENTENCE
3 w8 Z8 S! h3 ^0 K( ~: ]3 sWaPcAsE
8 @3 y5 X! Y9 `; a2 ldtype: object
& l' B% i! w* ^3 w: f" \: L7 {( n3 K& b1 i" T* J
s.str.casefold() # 去除字符串中所有大小写区别0 P$ b1 n* ^5 s) [. ?. V
7 s8 d% y9 G) W! p0 lower
7 Y0 ^7 _5 L3 v3 `% K1 capitals$ o$ N& W8 ?( |& p* Y, w
2 this is a sentence: w$ ?( _- X7 p7 ]
3 swapcase s# X) o! Z+ b" |! f9 ^3 V
; {1 E7 L! d. w6 f5 {! }6 _1
6 Q" g' q! W4 f! g+ p! `- r2
" ^9 P/ ]% ^' q" k$ f8 Z# w7 ~31 @1 X2 ]/ a, t9 E B* d
4, h2 p$ k# ^ L, Z5 I! ?: Y
5
5 y$ D# I1 M# E% W6 Q6
# n6 D/ P. d, F: ]71 H9 [ ]* ~ r9 P% o" k
8- s7 T) y w, p# H- w
9
$ Z9 ]3 O& x# u" [5 s# s102 J, M. h! P! l4 ~! J! `3 Y& [
119 D* \' _2 K+ W3 d
12
3 }) `" B8 u" r$ j6 w13
1 k) A. S5 k% u" U) Z, Y/ C14& T- t0 i- U% n4 Y+ r2 B+ l: C3 `- F1 R
15
; J0 L' D, S! [, [4 o6 l163 _' }, K; ~, F& M8 n$ T
17$ Q- w& j' S* p: i0 L
18
- b" [! E& `8 e4 m19
" u# o$ W" Q' ], i4 N* ^* ?' V20
% Y+ P3 ?/ Z1 v7 n0 M# j7 i! E7 e21
" J' V" v% I2 L8 j2 D! g7 {- v H2 m22 |: J+ v% H# e' K* X3 w! K
23
1 k/ d0 ~: J' D$ S24
; j- s3 M0 C$ Z5 V25
4 Z, A2 U) Q) t' z9 }26
E* }% G. v* s: A4 A; E27
# w8 H+ J6 Z) t7 Z& p# X5 G4 w28
* m% ^0 h3 S6 s, U( x! y& X29
. o$ O7 n5 ], n Z- G0 D0 T/ `30
( B" S+ v3 }" s31
7 Y4 B. x) k Y# _7 e; q; m3 `32
0 t9 R# h) Q: j. `335 P: R, Q+ [, f8 v
34/ N0 M- Y' A( [5 V4 e( y
355 ~. s- {3 Q6 z$ ~ [! Z0 j0 M
36! R7 L; T/ G0 M" ?: m
37
$ i, f/ L: t7 p ?; V9 f38! U, X( ?: M- _0 r$ o5 W
39
0 ~9 T, {5 @+ A1 x# b0 C0 z40' u. d; Y R U0 K" j# a: ]
41
0 Q5 a! i0 ^0 a+ Q1 u; s7 r$ ~42
; S; p. F$ t8 K) h6 W$ k3 m( e43
- U' @" x. o1 I: u447 p [% r% @( L
45
0 n9 V7 h( Q& N7 W0 L46$ P. p+ @8 [' a) S- M+ }6 X
47
3 o1 W7 p5 @& ]. p. O% q5 u% w48
8 T7 d% k) v' S% D6 @0 ~4 J8.4.2 数值型函数5 A& A+ h+ h6 H7 C% r" |/ O
这里着重需要介绍的是pd.to_numeric方法,它虽然不是str对象上的方法,但是能够对字符格式的数值进行快速转换和筛选。其主要参数包括:% j7 P0 n2 z) I$ Z% x) ]( W: w
+ D8 v. B4 j+ ~' A. q1 e* ` _
errors:非数值的处理模式。对于不能转换为数值的有三种errors选项:
+ } s+ B' l& j d9 w2 c3 \raise:直接报错,默认选项/ ?& Q! R: i( T: K" w
coerce:设为缺失值
s. R! ]0 w# H0 O; f. }" b# eignore:保持原来的字符串。
. C+ H9 J0 d* x: l) T" ddowncast:转换类型,转成 ‘integer’, ‘signed’, ‘unsigned’, 或 ‘float’的最小dtype。比如可以转成float32就不会转成float64。
& N4 H( f) o9 Y( x. ]s = pd.Series(['1', '2.2', '2e', '??', '-2.1', '0'])
& O8 S3 a+ G% O- W3 y X3 J* [" k5 k$ u8 g, l
pd.to_numeric(s, errors='ignore')0 x, Q$ Q5 B. z4 F
Out[93]:
: \: p* B1 R5 ?5 |/ m0 1
* M% i! G. Q; Q, W# }+ n1 2.2
' J r" g3 S1 j, R6 ]6 f3 M2 2e6 r9 r) h) h5 @% T! ~ x
3 ??2 h3 G9 T5 s; l% ?8 L
4 -2.1
4 r) W8 }" g, @, l& _5 0
8 _* X* k; Z1 o/ ~9 V, ^4 Y4 Ydtype: object
* y- ] D* `2 [: T3 i; V# R
1 U5 r. ~7 c2 Z t' ypd.to_numeric(s, errors='coerce')
! U* d, E5 H9 t& ^7 f5 a \Out[94]:
) n; X! T, _9 R/ \6 i0 1.05 `1 O" o2 n- b9 q+ U" _& g
1 2.2
+ o: M0 s' }4 ^/ k1 D$ x" T2 NaN
2 X% Y* b# p: t O8 j& M1 y3 NaN
" S) ?& O) g9 T& P" @: m" a4 -2.1+ P3 Y6 J1 D' T# e- B0 f
5 0.0
: Z$ P/ B$ U$ t. ndtype: float64* D1 d8 r7 a) |' d+ m+ K' C
+ Q& g# o( p( Y/ b! P, D1 `
1( {6 n& K/ [. x8 F$ J
2
) c( \* Q- _5 Y/ q) l" x2 K3
! B- }5 _+ k7 n6 P ^* m4& {9 O* A7 p7 Y( M- L
53 L' F: l' `6 k! G9 |
63 v/ t: ^ ]. [) e
7
# y9 |/ d# m1 Q4 Z9 K- B3 ^8/ U2 \0 p; ~: W; {: [8 j
9
9 `$ i! Q# W1 C) o10
9 e8 j# R3 B: K, t6 q11* X/ f. k5 h0 x
12
) j* t# T; L1 u% ]1 k13
) z `0 c1 B% H" _" t$ U; q14
7 ^0 [ S. H- t$ L( X! {15
/ s- Q. q# S& ~! a. j16
9 f; ?' p x- c* N5 w17+ ~- i" a. e# |9 [7 S2 p7 O
188 g$ r0 P J1 ]* ]7 D
19% [8 p( J) M, n: ? n5 ^2 r1 f
208 T3 b0 c* O/ k% B) L* R+ O
21
2 Z9 h2 H8 P! G7 Y" o. ` 在数据清洗时,可以利用coerce的设定,快速查看非数值型的行:! E1 X$ V1 H* H' w- u9 _) H
" F+ J0 I5 Y2 S h1 `s[pd.to_numeric(s, errors='coerce').isna()]
; q' O* N" a' m" Q+ ~$ {3 A z5 I! FOut[95]: 9 ~: E1 i. }# j v3 a4 V+ K8 @! c
2 2e( i$ S# b; i' ~, x- ]
3 ??, I- [/ A- e/ V
dtype: object
* k1 S+ u& M1 D3 I8 j" s# y2 U18 u7 c3 [) F' I4 |! Z b$ z
20 k, o/ Y4 h3 N1 D& \
3
* {+ u5 k" J+ ]7 n5 q1 u& ^42 C7 G; T7 e0 B
5+ A" j2 m& Q' p
8.4.3 统计型函数0 z1 R1 c: C; ]5 J' K+ u
count和len的作用分别是返回出现正则模式的次数和字符串的长度:
& |) M! s0 g: K$ ~
; R3 G& [# \& v0 Q9 L _9 u; ms = pd.Series(['cat rat fat at', 'get feed sheet heat'])" O n# H5 d9 L2 r
) m: f6 m, [5 j9 @3 L$ q8 d c% m% a
s.str.count('[r|f]at|ee') # |左右两种子串都匹配了两次/ s, n& B2 h1 ?% Q
Out[97]:
# T: v1 G4 j& V7 U; x5 a0 r2 W0 2
' q& e& [- |- t' Z1 2
. u- V2 C" n$ Z+ p% qdtype: int64
; q; J% J7 b7 o9 \3 j, a' q# M; @& |6 {8 I6 S+ g2 u/ M1 s
s.str.len()
2 Q( s4 _# y3 S6 H2 x# K1 k1 zOut[98]:
5 S, M; ^) a$ h0 y8 \9 t# ^0 14" c# r& Z1 j5 o+ G) \' p* l' D
1 19
" g0 C; D( l: F, Y; t; v0 V$ h- gdtype: int64
% r3 [- B; q8 F/ N8 B3 V* d11 n y/ K7 o3 D' v" n$ n9 C
2
& e' K W; J& ^0 N, Z) U3* N5 V6 f6 M& b7 R. ^( R6 v
4
8 |. \, @3 U7 K5
. m1 ^/ y; Z" O" \3 I3 s6
1 {( p4 s. L. f, C8 ~0 M7& n' l& m9 s! }+ J7 c: ^4 Z. j
8% m M" I+ {6 {6 U' |3 }" r. w C
9
! t! C" T3 X: j/ p0 d* h% ^3 t' @10
6 x6 Q9 W# @' D# _+ G! F! _11" m9 n6 v4 L! R2 y; f
12& ~8 p0 k% \: w
13
/ {+ @& {; G8 L8.4.4 格式型函数
( v' C$ F% e, q$ m 格式型函数主要分为两类,第一种是除空型,第二种是填充型。其中,第一类函数一共有三种,它们分别是strip, rstrip, lstrip,分别代表去除两侧空格、右侧空格和左侧空格。这些函数在数据清洗时是有用的,特别是列名含有非法空格的时候。/ v$ H* Q8 W! F' ^' w, L" n; v
. U& L" W+ y7 f$ \- n
my_index = pd.Index([' col1', 'col2 ', ' col3 '])
+ q& B) d' c! e* H' d: |7 l7 `5 r( ^4 T
my_index.str.strip().str.len()! A/ |* ^. f# q0 n. {# n0 \
Out[100]: Int64Index([4, 4, 4], dtype='int64')2 z" J; ?9 \) d
7 z% H! u& N. X( o1 d6 @- Pmy_index.str.rstrip().str.len()! d, U! s% i$ A3 Y% r9 I2 _
Out[101]: Int64Index([5, 4, 5], dtype='int64')
/ S, t0 y2 v) L/ E
+ O8 H$ F N5 \5 G: ~my_index.str.lstrip().str.len()
5 l$ W3 ~& a! Y% k6 C% a) |Out[102]: Int64Index([4, 5, 5], dtype='int64')- I6 s* R" Q* m
15 L+ S5 @, x) ] b4 b0 S
2* p4 L$ M1 C3 B1 n6 _& W. P/ x9 s
3
0 t( y% @5 w' M, {49 ~' f, Y3 S8 u( ?7 Y/ f
53 M( O( j: L& e- e2 [+ e- V: y9 L
6; r; A! R. G; }/ B/ C
7
! N- _" |$ I: e; x+ A. g0 H86 w7 W* ~0 }* H1 J
9
- J! t$ x: O- f& n/ x7 l. n10
4 a b% Z( m Q/ P; N" K; f 对于填充型函数而言,pad是最灵活的,它可以选定字符串长度、填充的方向和填充内容:
) F) W% ]% H* @8 L' H. g
& c* V! q; Y. R/ O8 K1 P& R' d3 f5 I% Ws = pd.Series(['a','b','c'])
7 A9 R& [5 g: t' k# V/ O4 u$ i& M# {# s* w; |* ~# e
s.str.pad(5,'left','*')2 I* _/ H; r$ U/ s% ^
Out[104]: % k1 y/ Y& w+ {4 z
0 ****a- Y9 Y" G. M. E2 c0 m; O) U: z
1 ****b
" u1 _: R4 T/ W; f1 T1 B, N2 O2 ****c
! s1 l6 W( K# Wdtype: object4 N5 e! p% k8 y; H
4 b! M+ S1 W/ V' e9 [s.str.pad(5,'right','*')% A: t- V1 V9 _: Z% X
Out[105]: . q4 A+ @0 ]# O: A7 V" V! w4 ^
0 a****
+ M3 d6 A: F. y4 Y1 b****
' z7 v& p+ R8 M& j2 c****3 G- |; F! c- o' k4 e0 p2 [
dtype: object
, H# u+ a- Q3 ?0 ^' f5 d- ~( a
" f* A: F' x# w. M( S! j+ Ws.str.pad(5,'both','*')% X7 q; j5 O' W6 B
Out[106]: ; I' h1 L! J b/ b8 Z
0 **a**
) b4 J. P; L7 G$ w6 `: d1 **b**
+ \7 t, D4 b2 N" O2 **c**7 e" e B! x q4 f" l. ^
dtype: object
1 l3 L" ?. V: x8 v/ L6 v1 K- w* l9 \$ m% M9 C$ g+ d
1
+ B8 P- V7 H$ c8 T2
$ d0 l. q. d' M3
- @: n1 m; \+ t% ^' i' E4
3 c2 l/ _$ |( q- c |5$ E. r7 g1 ^% Q* C5 h1 F
6
, p* c, k0 d7 K/ x" ~/ k0 s7' X x* I+ M5 k ^! |, \3 B. e
8& [: }* O3 J+ w$ d: z+ k, q7 f
9. ^5 i. H$ M" M) R
10; U, `7 W$ P/ A. e& `, O
11
+ f6 R0 N/ S. y* I12
/ o& C9 A* f# c5 z" U0 \13- @% Z7 K* e V* l i# D' u
14
2 i6 y& W, s* k! E6 x' b15
8 E' U& E+ A6 T/ j166 Z) z7 W. L: i& @( o D1 H( E0 o
179 n: ?- m: W4 [1 }$ {1 C- b6 Q
18 s4 G) \4 P, L
19
# B3 G8 G! O& Z0 E" I20& B7 O6 l( o3 \3 J q
21& ^& B0 B/ E) c) E" V
22
5 o4 `; d ]! `# H, Q 上述的三种情况可以分别用rjust, ljust, center来等效完成,需要注意ljust是指右侧填充而不是左侧填充:- Z% k- c4 j' p: B/ f3 Z
3 B- s; ~# _4 U7 Ks.str.rjust(5, '*')1 `1 t$ ~, l( F% I5 d% t
Out[107]:
, K$ O+ F, Z+ T4 `0 ****a
$ {% h, @; |5 t/ k9 L) R1 ****b
, V Q8 Y8 E* t2 ****c
- N( {7 e6 i* d4 Odtype: object8 o; ]: p( y4 o' W$ w; I8 G- V
: \2 j' ?- C* p4 @: C, }' |5 w4 G
s.str.ljust(5, '*') u) x; h8 J- C+ \9 d( W
Out[108]: 5 j$ h# M1 |$ Y) A- t
0 a****2 v( s0 S& A9 v( e3 L- }9 i
1 b****
) y4 G+ S3 ?/ v( t8 ]) F, Y2 c****( a( ]; z( B2 j& @4 Y6 X/ e' G
dtype: object
. C1 ]4 w7 y: T
& {; E8 S& N6 Y2 k1 {/ f7 F! H. u; ls.str.center(5, '*'), ^7 F* d$ s" \! ]8 r; D
Out[109]:
0 U, y! u l. P: r/ u" L7 q1 A0 **a**3 |9 W# g& f( U
1 **b**8 T8 D% L. ^+ d) X
2 **c**
$ C+ Z$ W- a; R0 C+ \5 _3 ]6 Qdtype: object
) L( J' [# C1 }! H; _
; F2 E0 b0 @# ^1
8 j6 t/ x, \4 h. n& x$ E2
. O! S$ [ M* J6 ~, x6 z- p7 V3
5 |$ o6 y8 ^6 a5 z" p4' ]2 F/ ~ q: {9 D0 k
5
1 V. j* H% c# j1 Q6
5 j, R0 _5 L! |9 S5 ^: ]5 O7
4 A- N% o# e0 q8, U8 s0 n6 u1 H; B# {
9: y0 j4 P0 P& _1 V% g$ r# m/ H/ r
10
4 R2 ]8 p6 f r4 f" i, G8 z11) l, I2 q3 `) s! }& ~
126 }- H0 R- h- G8 I( k& c0 ~$ a
135 D/ v! N5 ^4 S S- Y/ n8 U6 T- S
140 J. c8 }, i7 v/ d6 |/ M
15
: ^4 N: V2 ?2 ^2 f2 `$ L; g16$ F a; @5 I" y" B6 x. X& ?5 z
17 X4 X1 p$ e. B* ^
18
) K2 J8 B, D3 b' W% R193 ?9 n' [ P" A5 W/ [
20
) p% Z: d' v; R# G. e4 w 在读取excel文件时,经常会出现数字前补0的需求,例如证券代码读入的时候会把"000007"作为数值7来处理,pandas中除了可以使用上面的左侧填充函数进行操作之外,还可用zfill来实现。
- g* m& S* {' }/ e- P( B. G; k3 R7 F( n- Z8 l* H
s = pd.Series([7, 155, 303000]).astype('string')
, M% {5 [/ S1 z( Y( E
, E4 k3 ~& X- p$ u/ c9 w- V3 K9 Ds.str.pad(6,'left','0')
' r; F% s [5 {& Z6 WOut[111]: ! \( h" O' ?1 J2 x
0 0000079 `( k0 O I: C+ U- s3 n& E p
1 000155* D0 i. M P& }: o1 a0 b- L
2 303000/ H d0 @3 V& U f3 D
dtype: string/ U: s1 J- J2 z: @: t
+ f1 x3 l( Q2 h! A
s.str.rjust(6,'0')+ g) \: i' ?# h
Out[112]:
5 e% f2 x; W' |4 N, y' h4 a0 000007$ r! I+ T' v* Y/ i3 J5 `( y
1 000155
, q% @, }9 `8 p/ d6 g. h) B& F2 303000
: a& h7 ?) g( y4 B" `dtype: string
7 w' _9 d# E) @4 Q, I& M/ v0 b, M4 K. [% w
s.str.zfill(6)" r9 ]+ L& O" z1 ]' R0 x2 r6 ^
Out[113]: ! l6 ?1 Z t7 O9 b& e
0 000007. L, B* I' D1 Z1 [3 |" b r9 ?+ M
1 000155
/ g% G6 ?3 b. p1 H! V2 303000) U+ M }2 z6 e
dtype: string
! }4 S- F7 h( B9 N8 T4 r7 |7 \- s8 _7 ]: H3 v, G: m; F9 k$ v
1
- N% ^4 i- w' G! p# z4 ^2
8 O; G( {" n) g3 \7 `: P3
) w8 k; l" }: \" k$ M4
" h K) b) p( A% E1 x5
" l9 j( o9 {/ X! O+ ?7 f2 t67 W }6 V3 o9 y( c
70 e, \4 e8 h) D/ O$ `1 P
8
7 ~+ b# `* S* Z; J% C4 O: C9
# Q7 s& ^3 l5 x10
% k0 v" p) a: |/ M11
0 b5 B' V$ S0 |5 ?! n' [* U( N126 a. h$ e6 x( T% L" K
13
0 v* }( X: S; r! o8 B: C14
" P4 b1 Z" `; {5 f8 l$ w158 U9 m( J& r$ h$ a a# @
16+ M _# F4 U3 s0 _% R
17/ E1 K& O" s- j& A* Q' h j3 ^
18
- i! n6 ]9 A( N: Y19
/ Q; l5 X. ~1 N* {; @20
% b% e j9 U) e- T0 J9 h4 L, x- E7 E21
: ?4 g* s! P4 t9 O0 K22
' I% {) B/ a3 ^* I% c7 G8.5 练习
/ R7 Q* N8 U B. R. s3 qEx1:房屋信息数据集
( u( ?9 \! ?; d% F现有一份房屋信息数据集如下:+ _( |: i7 Z& p0 V3 Q0 v) G6 |! C8 U
9 X( {, q2 w% _( P) M/ n5 h+ Vdf = pd.read_excel('../data/house_info.xls', usecols=['floor','year','area','price'])
# F3 B0 a9 |7 G4 {df.head(3) w+ |; Q/ F# h5 A' k# |# c* L
Out[115]:
3 I# L; c: b0 c floor year area price
/ r7 K% B9 h9 @1 ^* b1 A) T4 g0 高层(共6层) 1986年建 58.23㎡ 155万
, X, {( x. M8 T( h1 中层(共20层) 2020年建 88㎡ 155万
% [) _# s8 _7 m; x6 T2 低层(共28层) 2010年建 89.33㎡ 365万8 l3 q& D/ g. p4 ]3 J+ v
1- }/ P2 t R# c/ y
2
) C, Y8 i9 z4 m+ p3 z3* U' U: n/ D' w2 `' m3 J, c
47 K0 f \" S6 m8 e9 U( s2 u+ |; B1 f
5
& O/ W' Z9 `0 {6
9 d. q4 |& d! i$ Y+ H" B- ?5 e$ O1 I74 g( ~9 w) z8 R9 A5 |
将year列改为整数年份存储。, w3 q4 |. B8 B* f U
将floor列替换为Level, Highest两列,其中的元素分别为string类型的层类别(高层、中层、低层)与整数类型的最高层数。
, e& v3 t/ G0 \/ m! O2 C$ X计算房屋每平米的均价avg_price,以***元/平米的格式存储到表中,其中***为整数
& _8 W- T2 N% v4 C6 l: m2 }将year列改为整数年份存储。% V9 S" _; L' W9 m
"""
! F4 u% r+ o; e整个序列需要先转成Nullable类型的String类型,取出年份,再将年份转为Int64类型。9 y% I! r) |5 y
注意,转换的类型是Int64不是int,否则报错。即使astype加参数errors='ignore'跳过缺失值,
% L& u4 g7 _$ y; X3 }& c+ k转成int后,序列还有缺失值所以,还是变成了object。
$ s- O6 \9 o0 b/ |而整个序列转为Int,就还是Int类型,缺失值变成了 pd.NA 。
1 C: y, [( n* {/ V"""
# V5 ^2 g" ]& z8 P) zdf = df.convert_dtypes()
. E# y0 ?( g0 H6 ydf['year']=df['year'].str.replace('\D','',regex=True).astype('Int64')0 Y2 B! q2 L' ^# g# t
df.loc[df.year.notna()]['year'].head()
8 p7 F9 X y2 W& B6 A: ^1 Q
& Y6 A# M1 n# j' s0 1986% k+ f7 C% u8 ]& C
1 20204 a: V* U [% G3 I J/ |
2 2010/ A& k, U; G) j( J3 A0 \0 l
3 2014* R/ T* g2 S5 [# T' ]. x6 s
4 2015
# ]* K; b7 q; _% rName: year, Length: 12850, dtype: Int64+ u, v# J7 F: v8 c2 Q0 F& c
; e1 Q- A7 M8 d+ r, l3 f
18 e2 \6 t( b+ J" B# `* K3 s
2
! @4 S0 Z8 G6 y% E3 T* h3
' y$ _! }8 U- b; E/ b/ n9 n. P4
! O2 n" D" {2 y9 x' N5
# O1 g4 N1 Z3 @9 g6$ I d; k# i1 v5 a* S' V: k
76 O' r' |4 b; p) S( ~
82 u* s" m& x! h; \- ?6 ]
9
1 X: P9 F9 M9 `( ^) Z4 l& a" @107 \& X( J7 Z5 R* t3 V. g* f
11: y1 m3 V. J& T% m* e
126 [6 F: v9 f2 d# c
13. X. F; P, g# H' o
14
. J8 Y- U2 \9 h8 y# g1 y15
" i3 |. r' x# Q16# y, g9 B1 e$ c' h! R
参考答案:
- c' K- G5 r& v& g
: S! C! u) y7 f5 q9 t不知道为啥pd.to_numeric(df.year.str[:-2],downcast="integer")类型为float32,不应该是整型么
; J9 `1 L# c3 _8 ~; B
% ~8 K! U' r3 O9 u) Z: tdf.year = pd.to_numeric(df.year.str[:-2]).astype('Int64') . b- k6 h, v9 {
df.loc[df.year.notna()]['year']8 r9 e) U; o* R" _& J, S: a4 m9 M
1
' f7 L/ ?2 D7 k0 g23 s# T) q( J/ J& S! d
将floor列替换为Level, Highest两列,其中的元素分别为string类型的层类别(高层、中层、低层)与整数类型的最高层数。
& n$ G- S- o1 k1 y( Rpat = '(?P<Level>\w+层)(?P<Highest>\(\w+层)'
6 K! J) _5 U) G2 ]) L% d( v7 k2 Hdf2=df['floor'].str.extract(pat) # 拆分成两列,第二列还是(共6层得形式,所以还的替换一次
: o# V' _: N# s; Hdf=pd.concat([df,df2],axis=1).convert_dtypes() # 新增列拼接在后面,再次转为Nullable类型
2 ?; L# f; `1 h% S9 s adf['Highest']=df['Highest'].str.replace('\D+','',regex=True).astype('Int64') ' H6 r1 j5 @7 F6 v1 l: r
df=df[['Level','Highest','year','area','price']]* |1 a: v r& L% o
df.head()
% d0 Q4 k1 ~- N- U
5 E5 i5 i4 ~! T0 ~ Level Highest year area price" d7 w* m r' m& z, l% A
0 高层 6 1986 58.23㎡ 155万+ I7 @4 M& G4 I4 N4 {
1 中层 20 2020 88㎡ 155万2 M. g$ O" f. l$ `# y9 h/ y j
2 低层 28 2010 89.33㎡ 365万# f {3 [/ k/ ^" c Y( k
3 低层 20 2014 82㎡ 308万
8 q' H& g- z+ [) m* f8 s+ I6 ^# H4 高层 1 2015 98㎡ 117万
$ v, x1 N1 ]: u' V5 H- H12 y% h+ Q' o2 k; f/ q( K5 K
2: M, L+ S4 S" ?: m+ e8 Q! Z; e
3
7 Q8 Q2 s' W6 c/ I9 ]8 V' I4
- R* Y- U0 `! g; r& b8 ~! {50 S0 A- S9 J: v% B* N* J/ s
6 l2 _2 F+ v/ L" y
7( u( E) [% C3 O5 L( `7 X" W, Q
8
$ A4 k" N5 u0 g2 B0 V1 B |/ y: Q9
" {6 C( L3 h+ Y, _; ~1 s10
* d3 j5 v. C7 c% f+ I* A11
8 p+ I" b" P% M6 u. c. X12
: @- T& t! F& L5 K. C6 s4 h13
6 X& @- N# C4 _# x! m8 O# 参考答案。感觉是第二个字段加了中文的()可以准备匹配出数字,但是不好直接命令子组了3 b- d& J: a K# P4 o. F
pat = '(\w层)(共(\d+)层)'; R) c3 L4 a# {) G; C, E& `8 W
new_cols = df.floor.str.extract(pat).rename(
; j M1 ~9 x: J" G6 {* D columns={0:'Level', 1:'Highest'})
9 s$ [7 X' o4 b& o. s- T9 v& s& e) E: k7 }+ x, Z2 t
df = pd.concat([df.drop(columns=['floor']), new_cols], 1)( y/ O4 @) r' w* G8 E- Q, D
df.head(3), b& p# ?$ T8 z6 S
% e6 P4 L/ f3 |8 @' M2 v8 v- _ h/ Q
Out[163]:
9 ]) W6 `& J( t3 Y2 k4 u& z year area price Level Highest
: M5 o, C; R- E( b* [. m. j2 n0 1986 58.23㎡ 155万 高层 6
7 L2 j9 a0 c9 Q6 q, Z, l( ?5 j1 2020 88㎡ 155万 中层 20
5 u- l2 y8 K; n2 2010 89.33㎡ 365万 低层 28' r. E3 J+ i; |. j# Q3 z
10 t0 `& }% z. ~' V! H$ x' l
2
- @7 c2 H" r) R1 b- z# Q5 K4 a' ~( N38 {% T; `7 I/ w& { s6 A
4# h2 F0 k+ P' W9 w: V* [% y+ f
5! g, x' f/ ]# G
6" A6 |* z+ ?% h8 Y
7
3 k! }" q( A& v. s9 A8
, `: O/ r( a. a5 h& S9
+ N, s- _& a' d- w10
" t$ a" C" ]- T4 W6 A2 I# D& F11+ e' E) b' { t+ m, Q: U3 r
12
7 y8 K: U6 n) p5 E8 Y13 `( a7 r- O7 u$ A: g8 d* z: D2 c
计算房屋每平米的均价avg_price,以***元/平米的格式存储到表中,其中***为整数。
) u$ N9 t/ D/ \6 B9 a& r, Q4 U5 V"""
( y) h6 {2 I+ t: vstr.findall返回的结果都是列表,只能用apply取值去掉列表形式
3 X# V( t3 C$ d$ s) n" Q参考答案用pd.to_numeric(df.area.str[:-1])更简洁
8 E4 O% H1 N& @5 Z0 ?8 w; Q3 p由于area和price都没有缺失值,所以可以直接转类型8 ~- s+ ?! v A. p1 B3 S% V/ v+ Y
""" r' J0 p9 z# x T
df['new_area']=df['area'].str.findall(r'\d+.\d+|\d+').apply(lambda x:float(x[0]))
% U9 D! I3 |& K/ p# ]. rdf['new_price']=df['price'].str.replace('\D+','',regex=True).astype('int64')
7 O# j) o5 t) h& j# rdf.eval('avg_price=10000*new_price/new_area',inplace=True)& \4 I+ M8 O5 I2 \9 o$ d7 x3 I( v
# 最后均价这一列小数转整型直接用.astype('int')就行,我还准备.apply(lambda x:int(round(x,0)))
0 V2 }# }" V Z: r# 最后数字+元/平米写法更简单
: A( r& e6 C8 p+ k( a7 Gdf['avg_price']=df['avg_price'].astype('int').astype('string')+'元/平米') _) l0 b- p7 w
del df['new_area'],df['new_price']3 \* F" q5 G1 Y$ z* h4 c1 |
df.head()
/ L* F2 o8 h( B! s! {. I$ c
6 j' D3 g4 T7 r; a, J! q! Z! \; ~# E Level Highest year area price avg_price
8 w3 |, d/ T: C* h- Z, Z0 高层 6 1986 58.23㎡ 155万 26618元/平米
. F1 G+ n F- P1 中层 20 2020 88㎡ 155万 17613元/平米! U+ X4 N+ a- K* G* V1 z* \
2 低层 28 2010 89.33㎡ 365万 40859元/平米9 A/ n# P! B5 u- R: Z
3 低层 20 2014 82㎡ 308万 37560元/平米
. ?9 E% U7 E! a# B6 l+ u4 高层 1 2015 98㎡ 117万 11938元/平米
: E& Q1 C- C! \: H9 B/ b" r
/ n' p0 s) m* S9 H8 R# x1
, B4 M; W- O+ {' U0 n26 F2 y8 n, E% v2 c
3
" t" o$ s) \5 h- {4
2 b# ~+ J: H) O5( ~" `& P0 R0 }, p
6$ G5 @0 j6 l" v+ C- r! H
7
* @! a6 m) i: r6 s7 H+ [1 B8
! N: m P% o, V8 ~- m/ F/ \9
8 n- W! A/ @* n106 T( R$ ^% q) ~' D. d; L9 f t
116 j M0 X0 |. W7 C7 v H6 O
12
, s# j$ w$ E. p6 \9 O: ]# V2 f136 P+ G8 [* A; z: ^1 Y% F
14
1 U6 f# X% }) K: }: K150 u) K5 ?% F% X0 g% t
162 H9 Q' Z0 c# V2 ?; O
17% v }* Y1 J; b- _2 [- e7 A
18
, ` t! O! ^5 t+ E19
& Y/ b/ E9 L- D2 [0 y2 |: {- r20& u, K! T9 g4 F
# 参考答案. { V7 ~1 e I8 a
s_area = pd.to_numeric(df.area.str[:-1])
9 }+ ^7 K# b3 w0 ts_price = pd.to_numeric(df.price.str[:-1])* D: f8 U* e" Y' e" R) ]% o2 z3 k; [+ e
df['avg_price'] = ((s_price/s_area)*10000).astype(
( l d; b) g! G8 u9 [: g9 I$ F0 | 'int').astype('string') + '元/平米'7 I, @8 w! W3 _" t+ v
" S, U c+ }( U' k
df.head(3)2 o2 P5 r. g6 |
Out[167]: ! n3 V0 Z" _- w
year area price Level Highest avg_price
: x* S4 |* q; u5 A3 x0 1986 58.23㎡ 155万 高层 6 26618元/平米! o7 E9 m; c7 @4 W3 V7 p/ p" |# j
1 2020 88㎡ 155万 中层 20 17613元/平米* d2 n) P2 P7 q" p1 t3 C: R( y+ K
2 2010 89.33㎡ 365万 低层 28 40859元/平米$ Q J! M! ^) O2 [# G' D$ g
1* ^' P+ \4 d! O2 `" d( c+ v& h8 w8 v
29 j8 E( y; S. q2 ]
3
9 P6 o: M6 t v- a4
% P$ r' T, d- f, Q2 V9 Q5; j" m5 S x3 |! B, g
69 Q7 |! i! m% Q9 j& ~. n1 E
70 M% R; H e2 r9 z
8
! ?' ~3 M& k1 o. y- Z, [9, `4 R! T! p/ N/ |0 o1 @
10! \6 x& O8 \& t0 e
11
, D3 j8 u! B; y4 U# L12: `' {' |0 h/ ]/ O2 r a! p8 M$ s
Ex2:《权力的游戏》剧本数据集
3 j! `: H* C9 A) z# T* }现有一份权力的游戏剧本数据集如下:. p0 x% D6 Z% r9 @
}8 Z* Y) D: }) a
df = pd.read_csv('../data/script.csv')1 f2 k1 p2 p* \/ O R, u$ Z
df.head(3)
R3 m0 {7 o- Z( n0 C' @: ~7 V$ U# A
Out[115]: + v+ o6 c" j& Q. V0 H$ Z
Out[117]:
' k. k5 G" J- r7 i- t) { Release Date Season Episode Episode Title Name Sentence
# _! [8 \0 q. e( h$ K. Z0 2011-04-17 Season 1 Episode 1 Winter is Coming waymar royce What do you expect? They're savages. One lot s...
& D: P" J, l1 t8 y1 2011-04-17 Season 1 Episode 1 Winter is Coming will I've never seen wildlings do a thing like this...
1 Z8 z* U1 z" P5 p8 \. I2 2011-04-17 Season 1 Episode 1 Winter is Coming waymar royce + F$ y$ _4 i) p! A' ?
1
0 A9 s, j, T: N, [5 G1 U. `2! j8 h4 J! l4 n. S
3
: p4 u; ]# A: @6 E2 }$ `4 r4( m, F# y/ {/ G9 H3 u
5
3 ?( m. y: F; r1 _7 j6' J) [: Z( G$ Y3 r
7, ?5 n& `! j5 n' ^
86 X* A% J% Q1 l
9
4 Y' W1 t; Z& B$ a1 B计算每一个Episode的台词条数。$ Z/ r1 Z1 s" e: t% T6 }
以空格为单词的分割符号,请求出单句台词平均单词量最多的前五个人。
6 X0 n9 |$ ?8 A( L; m7 t5 O# R若某人的台词中含有问号,那么下一个说台词的人即为回答者。若上一人台词中含有 𝑛 个问号,则认为回答者回答了 𝑛 个问题,请求出回答最多问题的前五个人。
, X' }4 b$ H; y" D* a; U计算每一个Episode的台词条数。; E" B, Z: q# ?7 v7 z3 b: F4 G
df.columns =df.columns.str.strip() # 列名中有空格
L% K* Y L6 s: Y: {* p" Mdf.groupby(['Season','Episode'])['Sentence'].count().sort_values(ascending=False).head()
& ~& H( x( ]$ K0 c; {$ h8 e
7 R- i5 y, r1 O5 n. l3 [ Z; b8 d3 xseason Episode
: [8 i" T% r' Y, A7 }$ ESeason 7 Episode 5 505
7 p( K" X3 {" g! G* `* dSeason 3 Episode 2 480
6 T3 P6 {' E) |- H$ QSeason 4 Episode 1 4755 d0 i0 H5 X, a5 k
Season 3 Episode 5 4401 o! w& | N3 N' H' X7 Q! b( t
Season 2 Episode 2 432
f# t! Q8 O( E" i! b0 [3 k2 ?1
: {+ w8 \7 c" `$ J2
; \0 ^ ]/ x" c! n* w# C$ L3
# D! |2 U1 O) }; b% D4
& A7 ?4 h% i# I* d- b55 l# T8 i( N) c6 O% R6 D
6# G4 C5 b0 s8 B$ |% q! Y
7; d6 |6 p# Q3 ^- R. R: l
8
! ~, J% k6 W8 |5 s9 t! Q4 R9 f1 E3 P v3 l. h. d& X2 a
以空格为单词的分割符号,请求出单句台词平均单词量最多的前五个人。 S( z* {, [8 `; i3 `0 d
# str.count是可以计算每个字符串被正则匹配了多少次,+1就是单词数
- f+ @! r% }4 O/ ~) idf['len_words']=df['Sentence'].str.count(r' ')+1
& V. E+ W j( jdf.groupby(['Name'])['len_words'].mean().sort_values(ascending=False).head()$ n9 d: d" q8 ^ I
! C3 _( }7 w+ A5 U
Name
7 ?8 [2 b3 b8 X k. }1 qmale singer 109.000000
2 P, d* y5 E8 V' ? q* P' eslave owner 77.0000008 A! w; E) I2 z6 y
manderly 62.000000
, q. T7 w; ]) ~# R" n( U" ~+ Nlollys stokeworth 62.000000
2 g- ~% E3 r7 l. {- Y: @' edothraki matron 56.666667; N7 V; D4 p7 N S; C. D, |9 H
Name: len_words, dtype: float64
2 [* x8 d8 g! ?* g7 t; m5 f0 }1/ N$ e. z/ m+ q6 N
2) J% i: T1 R: {' X( q
32 f ?1 d7 H* R+ r3 ^
4
$ |9 ^2 c* c: Y3 h! T* x5
4 Z4 S+ H: J; P' v: O6
" t3 C: g0 o: ?4 ~# g3 n) W% D7
/ {0 `/ k- Q! D& [& {8
& k4 Z- C, {6 ?( }" F- S99 B a' a9 k2 W- _3 L0 b
10
_6 J" L: k, f11
% X, z7 R9 z3 d' l, I+ ~+ @+ I% d若某人的台词中含有问号,那么下一个说台词的人即为回答者。若上一人台词中含有n nn个问号,则认为回答者回答了n nn个问题,请求出回答最多问题的前五个人。! K- R. V5 \+ j6 Q8 O. Q
df['Sentence'].str.count(r'\?') # 计算每人提问数
9 L# P3 y8 W( Y- U6 P$ e* s) Hls=pd.concat([pd.Series(0),ls]).reset_index(drop=True)# 首行填01 I# [) {5 Z# h2 x# |6 M
del ls[23911] # 末行删去7 j5 [' I9 G) k; W
df['len_questions']=ls
: Q v! I) X$ b/ ?- k( jdf.groupby(['Name'])['len_questions'].sum().sort_values(ascending=False).head()) i. d* e+ V Q# y+ Z
" M! i+ F' d2 d, n$ ZName- h1 D( n, H' J5 ~5 J
tyrion lannister 527/ ]6 q/ k. ]6 B8 A9 l
jon snow 374
' m) y1 N" C% |! o. Y$ mjaime lannister 283
, q, S. F2 s" C1 _) y* |arya stark 265
( P2 g0 w- h9 V7 y( E) [# |5 A: @$ Zcersei lannister 2466 ?8 y( ~2 {4 t4 S) x+ `0 O
Name: len_questions, dtype: int649 m# A Z7 ~4 B) @. e
% m* @( |- {3 s$ R3 |
# 参考答案
+ S- @1 a! j% D1 d; ps = pd.Series(df.Sentence.values, index=df.Name.shift(-1))
- F2 _8 U# e5 c$ ~3 V$ D) Us.str.count('\?').groupby('Name').sum().sort_values(ascending=False).head()
& I" |8 r7 |; ~; C& ?$ [$ m* Q2 E+ }( R" |* q
1
A: E1 V' ^6 J2- P7 H: v* R9 _! v1 S
3
$ ?( n G( p7 r6 E# e$ ^ C& g4
3 Z/ I/ z( U" Z8 Q% U. ^5
. T' I: e1 ?5 g6 R' ^# \' r6' @* u! r8 T+ [9 M1 m% I( P- ?
7' j5 _3 t6 d/ ~5 T3 ~( {
8, u* u% J1 K& O2 U- B1 m1 @5 E
9) {1 x1 T9 X/ I( f
10
) a) F' [: [& E, L1 J11
1 i: p/ x7 t, x! a7 V! I12
; h( u/ M5 t" F; u" [13
0 [9 g" L6 b. B4 b [% A14# _* {- O- L( c* v$ g; ~+ O$ N% L
15 X$ w! L3 t& w- y' y* q& v! R
162 x& [. a$ Z/ K) T8 p
17 b) z8 h$ q# B6 ` @; e, X2 D/ }" L
第九章 分类数据
4 _* _, I6 A. S+ cimport numpy as np' a8 h. n0 r6 }
import pandas as pd! j4 S5 ?; _! ?1 p8 s
17 W4 g* f0 T& }! W0 s0 B
23 _& i* A* T, {( @8 d6 `$ w
9.1 cat对象
( P2 K' n; f, Q8 p; P) C" J4 F9.1.1 cat对象的属性2 ]+ b B9 l" p
在pandas中提供了category类型,使用户能够处理分类类型的变量,将一个普通序列转换成分类变量可以使用astype方法。0 b6 E! l9 j: B3 `
3 r- T' W8 E; ^$ \% q7 y9 ]. s
df = pd.read_csv('data/learn_pandas.csv',( ]& i" [ E6 F: Q- b
usecols = ['Grade', 'Name', 'Gender', 'Height', 'Weight'])
. |7 |+ F! e: ~$ Z- K, {s = df.Grade.astype('category')
( P, Y2 q9 I' A' P. H
; O; h \" h6 h0 K* `s.head()* Q b/ @7 V g: h( @
Out[5]: % B" B4 o; Y5 y8 a) T- V) {
0 Freshman
$ m2 R. X2 E) f b4 j% S1 Freshman/ [3 O: G! {" X: _
2 Senior6 I% f* q$ Z% D0 U; K/ ?
3 Sophomore
1 M8 E0 b: Z# m* J5 a4 Sophomore
8 q g; ^. }5 W% |7 KName: Grade, dtype: category1 W$ a a( _% k5 \) }% k2 q* a
Categories (4, object): ['Freshman', 'Junior', 'Senior', 'Sophomore']$ s9 `/ A1 V) a; B1 m8 k" q, \0 _
1
0 Q- i6 o5 V2 v, e9 m3 M j2
' G5 M% P" K8 F& ^$ l3 z% V3 [ V# s7 q. c4 Q% [- p
4, r) ]+ G7 C, ]! I" ?5 |8 q
5
& o- `1 f& ~- U6
, g9 |+ B4 T! W7) ^/ m4 Q1 Z- H; |% ?5 W
8/ s- O, x$ ^* z+ P3 q! | {# Z
9- k$ [7 F; @' L. y# y
10$ H; p% e6 ]% z1 E' y* P) U! |
11
/ U6 ]2 Y$ w9 w0 j+ I12
" _3 s% y( s9 `$ D13) ?8 v! g) a( X- S- a. H
在一个分类类型的Series中定义了cat对象,它和上一章中介绍的str对象类似,定义了一些属性和方法来进行分类类别的操作。
5 y0 f: c# t/ U: s! J5 L6 P) D
# H1 F5 s9 G2 u: Vs.cat
8 O- W: ~* x Q4 k% ~Out[6]: <pandas.core.arrays.categorical.CategoricalAccessor object at 0x000002B7974C20A0>
! l W& z/ K8 g7 o8 O; G- @1
: N4 M% q# u5 h* L+ E! ]: {2
, g' A X# L- T, o7 v3 F$ ^cat的属性:) w4 K4 _( K: ]' h* U2 X5 G
9 T" @5 N9 [/ n9 f+ U9 t5 X: X4 [: z+ _cat.categories:查看类别的本身,它以Index类型存储
; `# ^5 j/ r$ F9 Hcat.ordered:类别是否有序
k+ ^. F; e0 F! J9 R4 ncat.codes:访问类别编号。每一个序列的类别会被赋予唯一的整数编号,它们的编号取决于cat.categories中的顺序6 h" j6 w0 s% g8 d! D
s.cat.categories9 g, B2 E7 B$ p
Out[7]: Index(['Freshman', 'Junior', 'Senior', 'Sophomore'], dtype='object')0 ~7 K, k- L- M+ j& b8 I6 s
3 j2 @4 T7 O, e/ H0 j
s.cat.ordered
" ] O& ]0 s& z% f6 lOut[8]: False
- ~: x6 r0 j1 H+ o
5 H# w% F: }5 Us.cat.codes.head()0 e) W, \: I1 M/ O& W7 ^
Out[9]:
- G6 N3 x9 q; K/ p6 H0 0
X7 T% x. S3 h9 o7 O7 w1 09 o) b- K$ y1 W0 F6 P
2 2
- Z9 H+ @# \; B& |2 x( Y3 3" e) @9 d# R! {3 c: H& C! F. @
4 31 n v3 E$ \# i+ c9 [
dtype: int8
( g4 z9 w. m' `5 c( E# O! b3 W/ d1 w1
7 g" n8 V$ \; Z- ]" s& l; Q2. h$ F. _1 a; ?, x& p
38 |5 t; y# {! H
4
$ y; h& ?+ C# w" n* z7 F" i1 v# H5! _# j0 A' U" `+ N
6
1 R3 e; f' d$ s; A! N1 F' G7
5 I$ i5 V- | [# {9 I& O5 v6 x8
+ J( s6 M" T) u# y* w/ e98 x- _; x1 y- x9 o; J( Q* {: G4 c
100 y2 w# |+ A1 A' H/ j {
11% ]4 w! U2 P# r* G
12* ^% h4 M9 `1 F
13
/ F& N2 n+ h) }" M+ y14
0 V0 c" w9 r# M* v. U. B9.1.2 类别的增加、删除和修改) q' I+ D$ c0 ]! W: w8 V4 |$ ?
通过cat对象的categories属性能够完成对类别的查询,那么应该如何进行“增改查删”的其他三个操作呢?* B: z' K% d W- i( ?, p
8 d+ s& K6 x2 M, L
【NOTE】类别不得直接修改+ t4 x1 K9 O7 t( _7 g
在第三章中曾提到,索引 Index 类型是无法用 index_obj[0] = item 来修改的,而 categories 被存储在 Index 中,因此 pandas 在 cat 属性上定义了若干方法来达到相同的目的。
% y/ t; ?' `! c8 v2 i- d0 E, q9 x+ d
add_categories:增加类别* F" G4 H( B m6 z
s = s.cat.add_categories('Graduate') # 增加一个毕业生类别
: P8 b; f: J% ps.cat.categories8 _4 `5 v6 K! d' Q; q) n
. [/ `! ~! ` f. n0 n- H* q
Index(['Freshman', 'Junior', 'Senior', 'Sophomore', 'Graduate'], dtype='object')
' S7 [5 @: s) o17 f+ } Y/ K j* p( A
26 E# _7 V; R: K e0 l. `
3: j9 [6 T% P5 h$ U# l( t( b u
4
; ~$ J' i) D8 n4 \% w% F# Z6 premove_categories:删除类别。同时所有原来序列中的该类会被设置为缺失。2 {; h7 U3 @! X2 z$ W, |/ O1 A! f' ]
s = s.cat.remove_categories('Freshman'): `4 ~: A* Q. O- Y2 q. J4 H/ v7 u0 l
. U R: k1 y7 G0 A. As.cat.categories' I% P* I Z9 t& R1 k4 a
Out[13]: Index(['Junior', 'Senior', 'Sophomore', 'Graduate'], dtype='object')
! G" I7 a' @/ X( Z s
, D, P- r1 T2 O2 \6 J9 N) A$ ss.head()
X' f7 s+ q' O% `( @# [0 p4 _' FOut[14]:
" o: _( r' M6 s# F5 D, j0 NaN% r6 ]; G2 V1 W$ F: t6 i( x9 P
1 NaN9 E% J4 j- A B C6 Q
2 Senior
0 M2 t7 N; H7 I3 O3 Sophomore
0 g; {; w4 X$ q4 Sophomore
. F0 H' l0 f) EName: Grade, dtype: category
# e9 R, c, E0 `. u) C1 A- a7 hCategories (4, object): ['Junior', 'Senior', 'Sophomore', 'Graduate']
$ Y2 g& }. q6 D- f p1 w3 O2 y0 Q0 y8 @8 J% \& ]: }
2" ~" I j3 t* r# K. t L5 M B/ u) g
3
]3 d$ a L! f7 g3 b- Z) w4* m. I. E+ g- L" S- b
5
' H3 {$ \- O- s! y* N6
. e# P. r; {0 d6 B) v1 w) j$ b0 ?: t7/ s0 m1 u& z4 ], b( b- v
8
- r# d: e% ]6 T' y, Y" Y1 K' x7 o8 t' Z9
$ p- j+ i5 H1 A4 i: A' B10
# t+ a% M. o r119 @: K( N; p7 ?( ~/ h
12- G$ c' [* n' t" `1 j6 ?) L3 `9 y' l
13% c2 V; K/ e6 v) u6 [4 T5 W; [
14" a8 G- `. \$ \, v2 t$ k; O4 T! L
set_categories:直接设置序列的新类别,原来的类别中如果存在元素不属于新类别,那么会被设置为缺失。相当于索引重设。. j% [$ S- `8 Y U# I2 [
s = s.cat.set_categories(['Sophomore','PhD']) # 新类别为大二学生和博士
& u# l* U- {2 V; s: x- ]9 |s.cat.categories
+ a2 h2 S' w- ^. \/ B9 _& T3 jOut[16]: Index(['Sophomore', 'PhD'], dtype='object')5 k- R: f" g9 w$ B
3 x( q8 b1 n! A2 w" x
s.head(). Y3 V! B5 `! {; v/ y1 o
Out[17]:
6 Y8 m+ |) @ V+ c7 v/ M# y6 t( @* Y. {0 NaN
" H4 s R5 P# K) l6 L1 NaN2 ?! M7 N" C: ^; \$ ]$ ?
2 NaN
" p5 y* R1 t; X l3 R' o3 Sophomore; W0 E4 z t. L0 x1 M4 Z1 c
4 Sophomore/ @# q% ^* e' _8 M8 A
Name: Grade, dtype: category+ D1 v$ ^* R: u% W
Categories (2, object): ['Sophomore', 'PhD']
7 V' j, Q' Z9 R" C0 e1 {( R1' I7 R. \- W% c0 g, N% `4 M
28 t+ A) I; a- M, C
3
) g5 M& W# O8 v4 D+ O8 D) q5 b+ o4
+ l& {( k: q% |; }# H9 x& \6 ? j* R5% m6 _0 H9 u9 E: Z
65 \9 C% y; N0 t# o
7
% ?; Y0 D! ?" t9 J: p, \8
z8 Q f8 i0 `# M$ z3 b91 U! `( x$ C. D: F. F1 q
10. S$ Z) j# l( L, ?; b* \
117 b- t- l' Z) d" M
12
( T7 B3 k$ g( q) ~9 {; {" X, H" W& n13
4 t- q7 d' o7 q. q, jremove_unused_categories:删除未出现在序列中的类别) K( u$ R* g3 h' o
s = s.cat.remove_unused_categories() # 移除了未出现的博士生类别
8 N+ Z5 J' k3 X& E( gs.cat.categories
: j/ U. ?$ ]6 H9 `. }7 }$ C% s. s: K( b" s" e8 X
Index(['Sophomore'], dtype='object')+ R# {+ x# p" C3 m# L# @" W! o
1( ^. c2 O8 f( Z( Z! y
27 a) C& h" V# M: G, O. m
31 n4 B4 S5 i2 i U& M4 v
4
& W1 N- b3 \6 W6 Q7 V j1 |# f* w. j# Erename_categories:修改序列的类别。注意,这个方法会对原序列的对应值也进行相应修改。例如,现在把Sophomore改成中文的本科二年级学生:7 E7 I/ d1 Q1 n. Z9 ?! V, _# d
s = s.cat.rename_categories({'Sophomore':'本科二年级学生'})
& l) p" N7 F1 P. Ws.head()
" v+ u5 S: B3 x7 i K. Z( Y( @2 `& w2 A$ X! \
0 NaN g6 o' m2 O6 j6 T7 V
1 NaN
" n. x! K: b, G% z& H+ \* ^7 p5 Z2 NaN
7 j* B* ]9 H+ o# v$ [( i, n$ N9 |3 X+ a, h3 本科二年级学生
1 y7 j. \; [- T- Y2 \' v4 本科二年级学生
/ Q/ t& A }+ F4 H7 lName: Grade, dtype: category
% @5 a, S9 F8 @" TCategories (1, object): ['本科二年级学生'] c2 Q8 B- R! C
14 |% D0 T7 R+ `5 ?# P
2* x" ~3 Q* @! l9 {
3
4 m. w) I- V2 }" U/ w7 a4
: m4 T) z* y9 B) \$ p# P7 n$ }: O5* S' b8 ?# D1 m) n( A
6
. W4 Z" t& d* t7
( q$ F/ ?: K i5 C7 m+ C8 w, ]; v" Y) I8; ?& v$ a, L, `. v5 `
9* b7 V( Q2 g( c3 B
10$ N2 x: ^/ r6 Y
9.2 有序分类
: G& Q# `$ x. o9 f+ X. @9.2.1 序的建立
9 h3 C, T7 ?7 `: A' r& a 有序类别和无序类别可以通过as_unordered和reorder_categories互相转化。reorder_categories传入的参数必须是由当前序列的无序类别构成的列表,不能够新增或减少原先的类别,且必须指定参数ordered=True,否则方法无效。例如,对年级高低进行相对大小的类别划分,然后再恢复无序状态:
# ?( a5 @4 {" T# T0 v3 v1 ?7 _& p; D7 O# O) y2 {3 _" g
s = df.Grade.astype('category')
* \; N, b$ e5 i2 ns = s.cat.reorder_categories(['Freshman', 'Sophomore',0 F2 }; ~% U3 }& Q) n# i- I
'Junior', 'Senior'],ordered=True)( C6 |8 D/ b/ T! C8 ^& s
s.head()! H. e0 ~0 ~9 t) A+ Y; ~
Out[24]:
W/ C( z; Q$ y0 j( ~0 L& I# O0 Freshman" Y. B: O3 d" q
1 Freshman- j" h: U* e4 U# O @! n
2 Senior
. h1 v# Z4 U: X- Y3 Sophomore9 M* \ Y9 E/ Z" l7 r d
4 Sophomore
) r+ G2 n% b, ~. r! \4 DName: Grade, dtype: category* k7 h2 S' c: I# H$ d& [8 u/ c4 V
Categories (4, object): ['Freshman' < 'Sophomore' < 'Junior' < 'Senior']
0 q+ S0 @$ ~4 n3 n& u4 h* ^- T5 H+ p: w
s.cat.as_unordered().head()
% T; v' u" d1 A4 H& [5 K! M3 UOut[25]:
4 Q* c. A$ Z d- S0 Freshman7 k3 v% Z. l J! S
1 Freshman+ s( [" g$ W+ _% H
2 Senior, E. k, U4 K& ]# \( w
3 Sophomore8 t: a) a" ^: ?. G( r) \
4 Sophomore1 u6 \. d r, i" ]7 M# A
Name: Grade, dtype: category1 W. t! P. d5 R$ ?% m. H2 r4 |+ E$ A
Categories (4, object): ['Freshman', 'Sophomore', 'Junior', 'Senior']9 V+ A2 B7 h. a
R. {. v; C9 X4 U) M* x9 s9 e: b' j1
( t x7 e, A2 V) N) y$ j7 D& W" f4 b2
* g g4 |. U3 _# u5 p3 e6 n0 Y# @$ |
4
, }1 E4 d3 T/ e6 E0 m- C54 i/ o1 t; v2 V, t% [. }# j
6
6 o& B; T4 d' A6 \7 F* f7 i7
# T+ h% @/ P% a. y( t0 k82 c9 q7 Q. P* w# L" v- |: _; F4 F
91 i0 ]1 m- f% b) x( g* l. H; L
10' T- o8 @5 l( \1 z
110 r$ Z" I) o" }$ r6 B9 b
12
5 B m- n, k' W! H$ K135 d2 G) |. x `4 ?5 E. E% b
14% m3 K+ C" B& w2 J4 m
15
( }% S# s3 M" r. ~ W& m16, s: o z/ k. u: ]7 @
17
! x& j6 n4 h% D2 b18
1 S' Y' R3 ~! ~+ D/ u: v19
$ Q8 Z! g" J( c- z o/ @3 s$ J20
+ a' i% D9 G; a8 w21, }$ p, k: Y9 h2 r# N
22
0 w2 J9 [. a. ]. K% D) Q! E 如果不想指定ordered=True参数,那么可以先用s.cat.as_ordered()转化为有序类别,再利用reorder_categories进行具体的相对大小调整。. G7 B( _6 @( E6 l
* c" d- f/ J% d% c' ^+ l; F
9.2.2 排序和比较# b6 Y, ]9 x; @% M/ b$ O# P. @* `
在第二章中,曾提到了字符串和数值类型序列的排序。前者按照字母顺序排序,后者按照数值大小排序。
# I( l( z6 I7 K- e3 R3 v( |
3 y, a3 P( U5 i& B; [$ Y" d' k 分类变量排序,只需把列的类型修改为category后,再赋予相应的大小关系,就能正常地使用sort_index和sort_values。例如,对年级进行排序:4 ], ?! _7 x0 `* S: T2 Z
4 M0 b7 C6 E: d& O7 ldf.Grade = df.Grade.astype('category')1 O' V# D1 Z; l' t+ G0 [
df.Grade = df.Grade.cat.reorder_categories(['Freshman', 'Sophomore', 'Junior', 'Senior'],ordered=True)
2 ?- G5 a" F% c9 s& |2 U, Kdf.sort_values('Grade').head() # 值排序 |* b# _4 p( a( j, @$ K. v
Out[28]:
3 u; ]4 G+ j; P1 j$ I6 d4 `5 w% v% B5 J Grade Name Gender Height Weight9 N$ `' u/ z6 m+ {" N7 u ?
0 Freshman Gaopeng Yang Female 158.9 46.0
+ y: h+ ~0 K6 q/ E& E2 n. t5 }0 u1 B2 q105 Freshman Qiang Shi Female 164.5 52.0
* @/ {& d4 }" c( i96 Freshman Changmei Feng Female 163.8 56.0
" t) b& k+ i: L: P, ^ j$ \88 Freshman Xiaopeng Han Female 164.1 53.02 V9 ]/ o; z: x2 j8 _) s8 w5 v
81 Freshman Yanli Zhang Female 165.1 52.06 D2 `1 b T+ u% B) n
! I7 d9 d) v( [$ B' q& Q2 w
df.set_index('Grade').sort_index().head() # 索引排序0 ~3 H# E8 S8 f
Out[29]: + m: @/ C& [1 u& y3 { s
Name Gender Height Weight9 X4 M. M' x" N. r2 e. y* ^
Grade ( b3 f' b( _3 U2 H+ \
Freshman Gaopeng Yang Female 158.9 46.0
3 n. b( W" ]0 x$ F. i# ]3 K/ G& wFreshman Qiang Shi Female 164.5 52.0, O! b* B, W# T: A) E4 H
Freshman Changmei Feng Female 163.8 56.0
, @7 |' T) I3 w# d( _9 n/ cFreshman Xiaopeng Han Female 164.1 53.0
. M/ b- }& N1 t- iFreshman Yanli Zhang Female 165.1 52.0
+ H. N4 P! |8 i' c. Y( ~ ]6 Z9 [+ c/ T" I+ l: W5 `- A
1
; R z! o3 b% a* i) y2
, p( G% y! G% }' C" f% ^$ I3 h3( W+ M8 |3 M5 _9 S4 W8 h& n: J
4
0 I( J6 y! I/ w! |3 [- [& c$ G5
: D+ f& [/ a9 }0 B6
8 g4 q" z+ Z, d0 F; u) ?7
% C7 x; F4 a* }5 d7 K& O8. e/ ~5 i0 x: G5 Y: K
9
9 H6 S3 O. x4 T7 l( }! p& r10
# i) G6 G% ^/ p( G X. A11( O q$ W2 s+ M3 [ R
12
6 C6 d m. N, C5 u. X# S4 o: x13- {4 }0 m4 G* X6 D
143 C/ L1 U4 g/ S3 o8 d2 ?. s
15
2 p0 q' k- \( o$ l3 e# D$ k16% m- p, |( w7 F, p. D
17; {/ z8 ]. W! B2 @5 |+ S
183 j: X7 b, Z5 a$ j% W9 h/ \
19" v9 M# n! ?8 a4 i1 _! O3 V; l- q5 T
209 c9 R1 k9 m7 w0 |1 Z1 Q2 J5 E$ p
由于序的建立,因此就可以进行比较操作,方便后续索引操作。分类变量的比较操作分为两类:
, W0 G' v) n& T7 o" h9 R: P. s& F) u; k% s+ |
==或!=关系的比较,比较的对象可以是标量或者同长度的Series(或list)。(无序时也可以比较)0 H, [; J* v3 u
>,>=,<,<=四类大小关系的比较,比较的对象和第一种类似,但是所有参与比较的元素必须属于原序列的categories,同时要和原序列具有相同的索引。$ m; \9 n8 i: T' @) O4 {3 j
res1 = df.Grade == 'Sophomore'
7 E9 {4 ~3 Y# q" X" K% Q
) u. X: _ Q/ y* {, d1 M7 _ y: \$ S2 pres1.head()
' P0 J- `, r* w$ QOut[31]: ( R9 }# X8 c' T7 B* h" H+ L+ j
0 False: H% X2 K& J, |2 J
1 False/ U1 t! d8 Z2 \$ ?7 X
2 False: y8 p1 }+ M( M) i/ y9 C+ O6 y
3 True
. y; Q; t% _1 x5 A2 }4 True
% j8 Z& F: Q/ D& _ V$ m+ E+ t2 r/ nName: Grade, dtype: bool
- c# c# x {8 x5 \6 B4 ]- R; C
A9 ]+ k2 m! G, U! @res2 = df.Grade == ['PhD']*df.shape[0]6 _0 M$ n* a& e3 ~ d4 h" O
+ Q( E W, _4 v; e7 O" G7 W7 t) |
res2.head()
# s- Q/ ?+ Z& M7 @. S" L* HOut[33]: # D" K( o9 ^+ \- q0 O( K5 {. ?8 a
0 False$ Z% x2 j! v8 G: e9 W5 d0 P" H3 f3 t
1 False
( R* q5 ?) s" ]; o- q2 False( R: A, H+ {" Y8 \
3 False
9 s" b1 V2 A: F' r* t4 False+ C {4 T! W& q0 b: o7 N3 O
Name: Grade, dtype: bool
1 e5 B/ n0 b+ F5 K: W; Q9 ^3 r
1 e# L1 ]" G& N- Z! |7 W4 z1 Pres3 = df.Grade <= 'Sophomore'! V1 E( T7 X. ~2 h( \$ g# V& d" h$ L
# @$ M4 ]2 L3 W& V2 R5 w) Kres3.head()# O7 I- r9 @& N2 O
Out[35]:
# i6 k ?: d5 U$ B, g0 True
. Q+ n( d# R& O1 True2 Y9 y" E. }7 H8 {; w) d
2 False4 J$ q+ L( i7 i5 B1 T$ ?
3 True2 _. v0 E2 N' Z- y
4 True
. I! u. l# D4 X; l/ U0 _3 Z+ J* M" UName: Grade, dtype: bool6 S4 I5 X' B, i9 w
" V+ Q' a! `" F) ~0 @/ [
# sample(frac=1)表示将序列随机打乱。打乱之后索引也是乱序的,直接比较会出错,必须重置索引。
# B/ e7 k; P6 C; K q' a% n( l- Ares4 = df.Grade <= df.Grade.sample(frac=1).reset_index(drop=True)
9 @' \- i% F( i9 o" R* A6 x' T( L) N$ z& O. c* G5 H
res4.head()
! r: \# u1 H* A. e7 c5 rOut[37]:
9 i% @7 k2 J1 J7 e, N+ P' U0 True
. ?) M* T$ F( h* w: D1 True4 `+ r; |, A/ E# @5 L7 y$ E4 H
2 False/ G! ^( Z+ b/ `' x6 f$ L
3 True# K/ v, J4 O2 ?$ o9 Y0 d: o
4 True
& Y- t. d1 Z0 Y/ `6 uName: Grade, dtype: bool) f3 v; Y3 V2 x1 A9 D: l
$ A( N8 N3 ~9 D6 K ~2 [( [
15 h/ w+ Q% P0 W+ a1 {; D4 D" G
2. V/ D' F) W9 z8 R3 q+ v; Q+ N
3
, F) u' Z5 X) @4 l! ]! U" U4
, L; {4 j7 y8 W9 m2 K5
& O+ ?( Y' }, @ D1 B5 @6$ T6 Q9 w d) G3 V6 z
7* ~1 `8 r/ E0 S3 t/ j/ p7 o
8# v3 W+ S9 R1 o/ F! b) {
9' ]* Y3 b# j, K. Y6 L
10( l# W' D# U3 S' P1 H; c
11
- l5 [: E* v. K, ]12) C' \$ j+ I5 a+ w, i& A/ L
13) K6 z, N4 r5 L$ K' ^3 u t1 i
14. {/ |2 R) o" a2 C- G& Q
154 {) w$ ^4 M! t" ]+ `
16- p8 ^4 v6 J8 {
17
! U1 ^0 d5 [+ F# m: v6 }18
6 `% {' P/ X9 X4 B; ?: E# P; l19# s) X/ O4 [" f. B) [
204 C( i1 {( o8 Y
217 A3 a: {) a% ]# N) i3 o0 v
22( d: H9 n% m$ D4 {$ B, Q
23+ E6 u) Q" ~7 Y! G
24
, a- U. C4 [( w& k8 V25
' x1 Q! [: i# g" Y* b* n, Q26: Q' ?: s5 p7 M* ]
27
3 D# A! C3 e& V5 u! a28
; u6 G+ K7 K6 C7 X1 p5 r29- s1 S9 w, D. C9 I7 ~
30
; H3 J: E4 S) s31+ Z- _2 h8 y6 j8 S T
32
; d. t5 e$ i4 }33
' ~- V2 j; v: @# w34
- v8 _9 S( l. b5 J: a- B; ]! ?35
4 I# X. ~- u ?% b5 U36
( V. j6 x. s v% t/ z37* A6 K; b8 B5 D* Y: U
389 x5 o4 y- w4 w- |
39
7 x- i9 W" O6 m2 a407 R- B" b* ^: C$ A r: f' M o
41
7 X3 G: ^0 X. P4 J# n/ z* B42
. {" R+ c! h1 \, [43! L3 r! n4 _9 k( G7 I& Y
44
" Z: q) U, P/ x& g& _8 l+ A5 _9.3 区间类别
. }+ O( C0 k/ c5 o9.3.1 利用cut和qcut进行区间构造
, d& B- a4 x) }5 Y; A d# N+ M 区间是一种特殊的类别,在实际数据分析中,区间序列往往是通过cut和qcut方法进行构造的,这两个函数能够把原序列的数值特征进行装箱,即用区间位置来代替原来的具体数值。
! o8 k0 L0 @+ Z) ]8 X, a6 A, f% D( G3 n7 Z& |
cut函数常用参数有:9 J9 X6 Y! f1 h R o# Z
bins:最重要的参数。, u1 p5 w% G9 _! [
如果传入整数n,则表示把整个传入数组按照最大和最小值等间距地分为n段。默认right=True,即区间是左开右闭,需要在调整时把最小值包含进去。(在pandas中的解决方案是在值最小的区间左端点再减去0.001*(max-min)。)
5 K& p1 v7 h! z2 u* t1 I+ P4 ?! u也可以传入列表,表示按指定区间分割点分割。
1 m( `: s: ]; X5 D. l: s 如果对序列[1,2]划分为2个箱子时,第一个箱子的范围(0.999,1.5],第二个箱子的范围是(1.5,2]。
/ I' ?9 Y- m3 y% e3 l% B 如果需要指定区间为左闭右开,需要把right参数设置为False,相应的区间调整方法是在值最大的区间右端点再加上0.001*(max-min)。
% x1 D& i( d% `0 M' [4 x
q) W# i% }7 h) [s = pd.Series([1,2]), V: I0 x/ u2 ^) r# k- }
# bin传入整数
/ r8 U7 ^' P5 Q6 z. M/ E$ |9 I, z# B& A2 L
pd.cut(s, bins=2)/ Z# t: ?5 |3 y8 w5 b' S3 q
Out[39]: . ]* D$ X9 M% Z) b& I
0 (0.999, 1.5]
6 }$ |, g( Z/ W1 r# Q$ X1 (1.5, 2.0]
- d; |" Y% j) D; |+ E odtype: category. l% E8 [1 l+ N/ ~* p# b4 X: E
Categories (2, interval[float64]): [(0.999, 1.5] < (1.5, 2.0]]
% n7 m u6 E# B1 v( P: C; J& u; |0 D: R- \0 j
pd.cut(s, bins=2, right=False)% ?2 y9 F! o! T$ ~2 C
Out[40]: 6 K& f1 Z/ Y5 W+ p
0 [1.0, 1.5)) n& E2 N5 s. N# @
1 [1.5, 2.001)
1 `2 j0 Q q4 b9 ] \1 Z; w: Bdtype: category! i/ `. d* }/ H1 b" k; V" K4 R5 w
Categories (2, interval[float64]): [[1.0, 1.5) < [1.5, 2.001)]' q e/ l- J2 |# l" U- \
# b4 r7 w4 W& s
6 }* p( K6 y. U# bin传入分割点列表(使用`np.infty`可以表示无穷大):, q7 D2 N% \ @% ^9 u0 Z% g7 }
pd.cut(s, bins=[-np.infty, 1.2, 1.8, 2.2, np.infty])
- F( J( m4 [4 T. `% o, B! L! OOut[41]: ' Z, L+ ]- X; E; K- P: C. Y. I
0 (-inf, 1.2]
8 _' |( ?' T7 D/ W1 (1.8, 2.2]# o& P# E5 Q( d$ q6 F, ]3 C: t) ^
dtype: category
8 b7 \+ S/ `& T* [9 S- X" mCategories (4, interval[float64]): [(-inf, 1.2] < (1.2, 1.8] < (1.8, 2.2] < (2.2, inf]]
5 W4 `% U2 d' y' v, Y$ f. A
2 V) ^# K/ ] F1 w1
, \- k, e5 v* N' O5 ^1 C v' T0 [2
8 e. L- e$ o- V& ^% }2 b% r3
# K4 o& X& D, F0 ~* t- q9 y9 G4
7 z9 c! ~1 W) z- X5! w0 T6 q" ~' r# B
6
8 J; N) i) d# S7
( T3 @ d9 a9 n" R8 D5 |8
/ G' M% u: K7 M, ^9
9 F: K2 q& H& ^# ]8 o+ Z* T: O* H107 T) _. b) Y0 I! [
119 G. v; v; j+ V& K1 K
12. d7 [/ j, i% u7 N5 h& [9 ]
13
; B0 h# t* x) u; v) R$ f4 p0 z14
( A. z! T1 s' C* h# Q/ x157 y9 \) Y' c9 K3 N9 U$ b& s/ B
16# I5 w ^0 {# D% B! B5 P$ I
17
5 Q2 z- z; ?/ |0 Y+ S, u$ `; R0 w' R18
, a- {5 @( E* |* B* r& X19/ P/ i! Z; @ D. R9 Y
20. E4 c( M8 \0 t- t4 t
213 e- G* M Y6 Y8 A
22
. M5 [8 I/ r& h232 R; R8 I3 v# I0 ]
24+ `- V( ~7 |! E, s# n9 w" i
25) U/ k' X. R3 H$ X, c
labels:区间的名字! J+ l: d2 B6 |
retbins:是否返回分割点(默认不返回)8 J `7 B* v$ n& N9 Q
默认retbins=Flase时,返回每个元素所属区间的列表' T8 _0 P% W- F5 z& h W g
retbins=True时,返回的是元组,两个元素分别是元素所属区间和分割点。所属区间可再次用索引取值
1 d1 W" H/ \, @8 }5 J7 m, o6 G2 W- ~, y, v7 r
s = df.Weight Z7 U- Y7 \( Y6 {9 J
res = pd.cut(s, bins=3, labels=['small', 'mid','big'],retbins=True)
* X2 {5 Y: f5 ?+ \( nres[0][:2]7 g7 ?$ e( G7 } e: y2 R% Q
. q7 L: ]8 \& [: \Out[44]: 0 T9 s- j, v# T2 q; J0 t+ X& I
0 small0 Y9 Q8 j* H1 j U" x; S% `8 E
1 big8 j& }2 e; W3 g- B& O0 H4 B. U3 \
dtype: category
# X4 w( p# q: b- W! H: VCategories (2, object): ['small' < 'big']
3 A% j) Q! c, s/ A9 e5 E4 @/ E0 ~* y* P
res[1] # 该元素为返回的分割点
: F( R3 n" C8 h2 HOut[45]: array([0.999, 1.5 , 2. ])) U1 M2 E5 F9 I- i: E1 @6 s& o
1; m2 O6 M* E& H; F1 D
28 U! Y# y& W' {; g1 F
3$ _# ?# L2 [! S
4
: P; B! w3 i2 J5
5 D1 ~& a: O! ~; i0 U6* W3 J! L) s; S4 [/ X* N
73 ?9 A+ ~2 h9 q$ y- V
8. b& m; `, ?1 y1 h+ [3 h
98 N z* y+ d; ^; b
104 v& u& p7 D' q3 @, P( i
11. x1 q7 ^$ x& o" d0 t- N
125 ]. }" B( F* O5 Z& I* l7 ?/ \3 e7 o
qcut函数。其用法cut几乎没有差别,只是把bins参数变成q参数(quantile)。9 c w p: u0 o
q为整数n时,指按照n等分位数把数据分箱
1 `, z& G) n. ^4 m2 h: Pq为浮点列表时,表示相应的分位数分割点。
6 w3 C3 O {6 Y2 Os = df.Weight$ X0 N0 ]* n6 \: j0 J
* d' U9 m6 \3 w: l
pd.qcut(s, q=3).head()( u% P& a( o/ H* e- t
Out[47]:
9 Q2 `! R/ `0 z0 B1 m0 (33.999, 48.0]8 A# s" ~8 r& ]0 y' n L+ ]
1 (55.0, 89.0]: i! ^- ?! n) m Z# A* r: J
2 (55.0, 89.0]
x! C$ G% k8 }3 (33.999, 48.0]& a3 w7 }+ u) ^$ h, r
4 (55.0, 89.0]* n7 q3 c' G& M8 b
Name: Weight, dtype: category
: o7 l1 p9 ~* G: [) H" eCategories (3, interval[float64]): [(33.999, 48.0] < (48.0, 55.0] < (55.0, 89.0]]
, t. Q$ A F }. {
5 s9 }( Q$ ?: Z# R; r1 {9 ipd.qcut(s, q=[0,0.2,0.8,1]).head()3 Z8 w; i2 C5 N/ K# ~" ~% V
Out[48]: 2 y# `' b, V% ~( X, r' _) }
0 (44.0, 69.4]
& c* J7 V" x; } U0 c1 (69.4, 89.0]
1 h, m% A7 A' x) j! l2 (69.4, 89.0]- C1 I% t4 |" l6 c* G
3 (33.999, 44.0]
3 }/ K q$ ?5 [- R5 T$ j% J4 (69.4, 89.0]
8 D) }9 M4 m/ j+ MName: Weight, dtype: category1 l+ K% [; B/ [; X/ m4 p- I+ G
Categories (3, interval[float64]): [(33.999, 44.0] < (44.0, 69.4] < (69.4, 89.0]]
6 d/ u! C- R' ]( u0 U7 z" N
# H- v Y; `" ^. y1
. F, O M* w* r! ]4 v! x7 _24 ?* m5 m+ n( U9 S( g. r
3
( V3 I. q0 M5 ]# ?8 z# i1 j. f4: z7 x8 Q: S. @: X0 |2 {2 D
5
9 D# a# v4 d, E8 X1 C! B$ K0 b, a/ P6
' A A( Y( s9 L0 B1 j! K73 F8 j/ z; [. K
8
5 g2 w7 k( F/ x1 t5 ^1 v" l8 U9 m9
( T6 `9 X, ? j( V" X' G10/ Z; @( `) `& P* x, f& X
11" c# F: t n& J1 f9 P) J+ Z+ T8 a
124 h/ s# q. J, q: W9 x; }
131 U3 L5 j8 U( u f( Y6 P. k
14
/ L: V9 p V) S" @. I15
, I2 r- E- r" c7 P5 j16
$ u0 d+ ^9 M6 k* R- A8 j& a3 E17
* _ w+ {0 q7 U: `/ f1 ?7 H; I180 `$ \$ h! a6 H; N+ h6 _6 Q
19" J4 a( R) |; I- Y3 o8 [' }
20) j, a# P; R# }& q
219 s3 K, G z9 I5 |8 L$ m C
9.3.2 一般区间的构造$ ^* ?1 L9 T8 C/ i" f
pandas的单个区间用Interval表示,对于某一个具体的区间而言,其具备三个要素,即左端点、右端点和端点的开闭状态。& d) a+ W5 T. L1 B& B0 Y
3 ^2 }( O5 M; s, p; Y开闭状态:包含四种,即right(左开右闭), left(左闭右开), both(两边都闭), neither(两边都开)。
0 L: D/ Q7 y8 s. d# f" x" Umy_interval = pd.Interval(0, 1, 'right')
8 E4 B$ B6 M5 I0 m5 Y& L0 c+ ]' Q$ O. k
my_interval3 @" ~; v$ k1 a1 i1 ]& C4 d
Out[50]: Interval(0, 1, closed='right')9 D+ c1 }$ `' B8 D2 D% O8 s
1
/ o" I& o) r% ]# v j23 z5 x4 L. _( S( q3 R- G" J0 ^: y
31 p. `9 v, ]7 D2 m/ G6 z/ S2 x0 q
4
+ U7 R" s2 F9 N* A! O0 }/ L$ ~区间属性:包含left,mid,right,length,closed,,分别表示左中右端点、长度和开闭状态。
% x* G8 g( \- Y, y( t7 D j使用in可以判断元素是否属于区间/ M8 s; y( L6 m4 O3 S5 B
用overlaps可以判断两个区间是否有交集:
0 R/ c' d& t" d0 E0.5 in my_interval
4 e n$ K6 m1 Q, g* g' z% p! P) D+ h7 ]- n8 {1 Q
True' W3 \. Q" @( E: x h
1: K, f8 e/ [" {9 E6 }# n/ I
2* `$ z3 c& Z- B5 c
3
% s; y( W, q& }( {- n' A" }$ J: wmy_interval_2 = pd.Interval(0.5, 1.5, 'left')- N/ ]6 E- g/ B1 K/ t
my_interval.overlaps(my_interval_2)
! |) z& R1 F+ {# \1 k5 u. _( j# y0 }: @* c. r" c+ T
True4 V* k. i. }+ m' L8 V) ?
1
. _* U% U5 f& `- A* \2' {% w+ e. |+ ]9 \. ]8 Y7 ?# E2 y
3
2 Z+ W) E# v5 s8 n/ P4: g4 n0 ]- J: A% G. y) [$ ]
pd.IntervalIndex对象有四类方法生成,分别是from_breaks, from_arrays, from_tuples, interval_range,它们分别应用于不同的情况:% f6 ]1 ~1 g4 ]& J& K( B3 K7 G
1 @5 J# b6 ^7 w8 ^! o
from_breaks:类似于cut或qcut函数,只不过后两个是通过计算得到的分割点,而前者是直接传入自定义的分割点:
9 |! s2 i0 l& M" B' npd.IntervalIndex.from_breaks([1,3,6,10], closed='both')
! K" q1 c! n: Q2 z
2 a; P) }) a# ?' }IntervalIndex([[1, 3], [3, 6], [6, 10]],
. H/ n$ b- I# T; i+ } closed='both',
0 D1 u/ _# f! Z# z0 P dtype='interval[int64]')0 `$ Q/ E' y3 _1 \* B
1
2 j5 ~8 ?/ ^- h2 o. G4 O2 I2
' `. o7 Y$ @1 R- Z3
+ s1 Z; C- j; |# ]6 v" m$ m4
8 `) |/ j9 O, g0 |7 c: v5
/ u- s7 v3 M$ k3 afrom_arrays:分别传入左端点和右端点的列表,适用于有交集并且知道起点和终点的情况:: \% ]3 E$ X6 i- D
pd.IntervalIndex.from_arrays(left = [1,3,6,10], right = [5,4,9,11], closed = 'neither')
. q5 |9 S+ M L
F8 ~) d3 Q# \) VIntervalIndex([(1, 5), (3, 4), (6, 9), (10, 11)],
, U- U6 ?6 I, G: J5 ~4 x- g$ m( } closed='neither', N' _( v3 s7 y: }
dtype='interval[int64]')
8 X& u/ d, \9 g8 R/ \ N. x1# V9 _6 I0 v8 J6 \7 j
22 e8 r1 Z5 N6 u8 L
3
, O' q( I; U- ~2 `4* [0 i3 ?$ H8 Q) ^7 o$ w; R+ R! U
5% o6 E) d" @9 Y/ \5 N
from_tuples:传入起点和终点元组构成的列表:
2 O' L2 s) K8 H1 s' ipd.IntervalIndex.from_tuples([(1,5),(3,4),(6,9),(10,11)], closed='neither')
; _8 M* |7 J8 `/ s8 } G+ H
7 t2 h {% m. a2 A. L+ V3 e. SIntervalIndex([(1, 5), (3, 4), (6, 9), (10, 11)],2 q" T6 j- g" W- H- t: j! X, A2 f
closed='neither',7 O3 n* ?) U! }% ]5 F9 u- n" e9 p
dtype='interval[int64]')
$ W3 W3 N8 j# a, Q: m8 \5 M; b1
" [! I7 R5 V& W$ K2! G4 Y3 s5 @& _7 j* Q( y: U+ N
3
+ i8 n( J8 G" ]( o$ D# T* z47 n# j0 I% W$ O' B
5
7 }, E* F+ b8 h+ ?8 Rinterval_range:生成等差区间。其参数有四个:start, end, periods, freq。分别表示等差区间的起点、终点、区间个数和区间长度。其中三个量确定的情况下,剩下一个量就确定了,从而就能构造出相应的区间:
! W' \( Q0 o3 @' ipd.interval_range(start=1,end=5,periods=8) # 启起点终点和区间个数1 G4 {- U% X* Q9 e, B# t
Out[57]:
6 ]% T* I$ z! l: tIntervalIndex([(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]],
' R$ T- G3 y( n, o4 t2 _4 E closed='right',
% T, Y. R) t1 x& n4 Q dtype='interval[float64]')
" y, k4 z7 |2 M
# D9 f$ {6 N" }5 o4 d: w4 ?pd.interval_range(end=5,periods=8,freq=0.5) # 启起点终点和区间长度8 G- T" p" d. Q6 n9 g8 N$ W
Out[58]:
4 b; ] r. G9 y7 H3 U' n0 OIntervalIndex([(1.0, 1.5], (1.5, 2.0], (2.0, 2.5], (2.5, 3.0], (3.0, 3.5], (3.5, 4.0], (4.0, 4.5], (4.5, 5.0]],+ x5 ?2 H4 f) R8 b3 i2 E
closed='right',
# X, g$ w9 M5 F X9 W, k. ~1 I; C8 R+ l dtype='interval[float64]')$ N8 y/ A) b3 \8 F
1
6 J/ \& k) P6 b F23 w2 t' a% m. h2 F, f
3. V. \, {" h1 L" L6 V3 R
4% X: ]6 w4 O+ v
5& D. p+ c- H9 W; ?
6
1 x9 i; q4 i! g7" H" X- W/ i# l+ {/ _4 C
8& c A2 E. G2 _8 p" n
9! f/ B# a, Q3 V* j& ]! {- ~% e9 h( C
10
, m$ m2 F8 ?8 U# M* p3 h$ K. o1 a11
1 W$ M7 q1 o8 R( j! }" v2 T1 I【练一练】
) L; \6 Z0 a3 d M 无论是interval_range还是下一章时间序列中的date_range都是给定了等差序列中四要素中的三个,从而确定整个序列。请回顾等差数列中的首项、末项、项数和公差的联系,写出interval_range中四个参数之间的恒等关系。9 y0 ?9 {+ S& f2 x. ~, C
/ h3 K% w* u, F# O- {- `( m
除此之外,如果直接使用pd.IntervalIndex([...], closed=...),把Interval类型的列表组成传入其中转为区间索引,那么所有的区间会被强制转为指定的closed类型,因为pd.IntervalIndex只允许存放同一种开闭区间的Interval对象。
5 k/ ]; i7 p4 i0 u B$ G0 M8 F( P
1 _' H# \( k2 umy_interval
Y1 p4 `9 a+ Q5 }% A! QOut[59]: Interval(0, 1, closed='right') e$ y8 t5 v: w, W( `: J
. f; S: v1 |6 o4 Bmy_interval_2
/ m$ N" O* {) Q* |# uOut[60]: Interval(0.5, 1.5, closed='left'). G; [5 c; ^% |5 P
2 z7 t _! u, w: y# G# r1 ]( G6 C. P
pd.IntervalIndex([my_interval, my_interval_2], closed='left')3 H6 A9 e/ y% s( [1 O
Out[61]: * Q. a* Y& E' {, C
IntervalIndex([[0.0, 1.0), [0.5, 1.5)],
( L1 I- W6 z* O9 |- P/ Q4 m. Z closed='left',
" z- Q! v5 c1 M) w dtype='interval[float64]')
9 J1 s+ o1 g ]. ]4 c2 C1" p2 L$ n" g4 z4 B8 g
2
% E: E; T! x; L: G3
/ e/ J/ W- H: e4
; U: p" q( E9 f4 c5! `3 [) G& J5 L9 |5 t) p7 W% G
6' K& g9 d6 s: J
7
1 f( ~7 [, d5 C8' [& p3 A. Y/ B" m
9
5 q% v$ J1 u) ?3 V5 L6 z6 A$ c10+ z/ W" c( T7 D/ C
11
+ Z) Q3 t0 _$ p9.3.3 区间的属性与方法1 @( _' p3 `. }+ j! {
IntervalIndex上也定义了一些有用的属性和方法。同时,如果想要具体利用cut或者qcut的结果进行分析,那么需要先将其转为该种索引类型:
2 g' V: P1 u: ]- o; E4 N- V8 }: q% R' ]* W" c/ a
s=df.Weight+ X9 B! D: u# y8 t' `+ x
id_interval = pd.IntervalIndex(pd.cut(s, 3)) # 返回的是每个元素所属区间,用具体数值(x,y]表示
: T+ e- `% a( Z9 o2 a5 Kid_interval[:3]
0 r) g% |* i8 ]& n* C2 w7 x
: b2 F( X9 o5 nIntervalIndex([(33.945, 52.333], (52.333, 70.667], (70.667, 89.0]],
) @8 _* K. N! h- T5 ?6 W# ?3 V" T closed='right',
' x9 Y* G$ a- Z1 e1 @" z name='Weight',' v/ L5 ?& |9 n( X I
dtype='interval[float64]')
d$ [, R4 }+ C2 A. N5 p' b* K' v1* s# ?- Y# M/ {, i' |) Q4 C4 Z+ o! C
2
5 k B8 I, t5 `" U4 [' @* I3
' Y8 E6 s9 p, F42 \. Y- x6 \0 a ~
5. C# \/ W$ S7 d( q. m8 i
6
: |4 N& e) E; i! Z$ j1 n! d75 |) f& f9 u# [. a) Y2 x3 x# a0 K
8
: w% X0 b7 T- c$ M9 c( Z Y, r( c& [与单个Interval类型相似,IntervalIndex有若干常用属性:left, right, mid, length,分别表示左右端点、两 点均值和区间长度。1 x2 g8 z; h: Y. Q9 X
id_demo = id_interval[:5] # 选出前5个展示
9 n. z0 B5 y% `& V1 H& F R: |- j) Z8 Y6 f0 m/ w! c
id_demo3 C) s# w/ H1 r3 z( n. c0 {
Out[64]:
5 S- S7 [2 w" x6 c9 F9 z6 sIntervalIndex([(33.945, 52.333], (52.333, 70.667], (70.667, 89.0], (33.945, 52.333], (70.667, 89.0]],
+ R& N5 z" P) ^' ^ closed='right',
5 W+ O1 k7 y( k7 S3 V% d name='Weight',: w( e- ?( y: x" Q
dtype='interval[float64]')
) \8 v" a, m) ?3 y6 x8 A% f- Z8 A% ]* i7 Z y- D
id_demo.left # 获取这五个区间的左端点" \; d# G" h8 y S% n7 P0 \
Out[65]: Float64Index([33.945, 52.333, 70.667, 33.945, 70.667], dtype='float64')
$ [( }. f6 h' i0 r
$ A' e0 I" ~2 }- L: h/ X# eid_demo.right # 获取这五个区间的右端点* V' q7 b( F% e, l
Out[66]: Float64Index([52.333, 70.667, 89.0, 52.333, 89.0], dtype='float64')
0 }! }) l' J$ v+ H! F$ {
Q3 r" ?9 `- B2 mid_demo.mid
( v( X8 L0 k" {- oOut[67]: Float64Index([43.138999999999996, 61.5, 79.8335, 43.138999999999996, 79.8335], dtype='float64')
% B, n1 h: }5 p4 u, @. K3 O5 |* v
- j8 e: c% W; x! w* Vid_demo.length7 N; @# X' N+ A7 p& ^% [! ^0 f
Out[68]: 0 I. n8 j7 o0 m$ K# F/ Y: v
Float64Index([18.387999999999998, 18.334000000000003, 18.333,1 R' l; X/ }& h/ P# P! R+ q0 P
18.387999999999998, 18.333],
6 i* t5 z u& ?3 O: K7 s% J$ W0 q dtype='float64')& L" F1 t9 z' i+ R
4 D- e5 @1 \8 f1
0 \7 O2 p8 f+ w2
7 C) `7 z' L! t" ?; n- x3
5 T# b5 M# j- X4 l2 H$ h) a' G
5$ f: o5 q* M6 t" g; G
68 r- E8 i8 ~5 E: S5 R/ x* J
70 B5 x' a8 B* ^
8
, I3 \. l* ^ _9 k; X9
9 I Y8 @: G" f10* j3 @$ D i. i5 v* T1 s
116 y* y* w$ @1 X: z! r7 @4 a: i
12
7 B' b& F5 f/ \" W13
. B; _# W$ A/ Y f* G14- `$ J5 S: Z) U# O) g2 U
15& o& X2 Z6 @ E! }& k7 H
16
# F+ l! E( j. o8 P17
( f3 S6 o+ c' ^18; v5 i! Y: ^& X3 w
194 m& `0 R8 `* J! Q: {3 D
20
0 _2 }0 ?$ C( {$ ]$ u# \21
7 h9 E$ P2 v: v3 K, F# N2 {22- C+ p' \4 X& N; U: Y5 o, j
236 a- b* I4 z/ G
IntervalIndex还有两个常用方法:2 @+ O) B7 d' `1 A
contains:逐个判断每个区间是否包含某元素7 Z8 i: C5 D* M) X
overlaps:是否和一个pd.Interval对象有交集。
- K" [+ s# z0 q% B$ Gid_demo.contains(50)$ m! z$ |# q) q F+ A# g& s
Out[69]: array([ True, False, False, True, False]), `$ Z+ f, X9 i: i4 x v! p
8 T; c9 ^+ K% t" r wid_demo.overlaps(pd.Interval(40,60))5 o1 J: J& D9 Y* \3 k
Out[70]: array([ True, True, False, True, False])) p2 V+ h( m6 R& k- S
1
2 Z E) u" ^) t/ x2
w |9 `+ X' Y2 I# z3 v5 d3
; b$ v9 e$ i' g2 S: y' j, `4( l3 A- y' d2 j t5 `
59 V3 a z& A9 {7 h: N
9.4 练习
5 W' T7 v- e) w3 g+ V% zEx1: 统计未出现的类别
0 ?& I+ I! e' i 在第五章中介绍了crosstab函数,在默认参数下它能够对两个列的组合出现的频数进行统计汇总:
) q! F+ @ r! g9 \5 J# U- N7 {+ F; u7 ?0 x* r
df = pd.DataFrame({'A':['a','b','c','a'], 'B':['cat','cat','dog','cat']}): `0 [) N7 M0 J8 w. b' O4 \
pd.crosstab(df.A, df.B)
8 F: t/ a e$ ]3 F) k" c" ]. T! c
/ Z8 y, y( \* f! [Out[72]: ' v# T5 j$ A' L! w. t1 B( p
B cat dog
1 m! v8 b9 |: @3 z& {, k/ h& w, ]A
; }. ~6 T7 g2 o. P; P) \0 [a 2 03 p% p' ?. P: H8 k
b 1 0& s) r& ? L0 n T' K1 j* K
c 0 1" F {& E( b3 s7 w3 w7 @/ E
1: C5 Q( a$ l3 _) C; \; o
2
( p8 A; y( y {7 v3
2 \. q1 q; S2 S2 v) r1 e' Z4; f- a. @/ P& m3 @
59 Q" _* }. Z: h* F/ w* l5 d
65 U4 Q O! t/ l6 `3 O& L: ]7 n$ y
7
; \ e: S( Z8 e# n- g$ l h8 _8
2 E4 `: l% I2 L1 J0 V B9
, c! S" g$ m) `5 e" v' ]4 x 但事实上有些列存储的是分类变量,列中并不一定包含所有的类别,此时如果想要对这些未出现的类别在crosstab结果中也进行汇总,则可以指定dropna参数为False:
: G7 ~! C6 W$ b0 V# ^+ l B% [
0 @* ]5 r P {+ K" z$ S, Rdf.B = df.B.astype('category').cat.add_categories('sheep')( a. F8 v5 H2 H/ b3 w2 K' q( X D
pd.crosstab(df.A, df.B, dropna=False)% H" m+ Y! I# h8 m4 c
, ?( f, T, n6 n. F1 t
Out[74]:
, a: A& ^# q( \; ~( qB cat dog sheep
& }1 [/ o! p( ^3 M3 J" vA $ z2 |) \; R$ j: E6 [. F2 J
a 2 0 0
$ U+ f* M% u/ @6 X1 U$ Mb 1 0 0
3 W' r1 H- [7 ^* n8 Pc 0 1 0- u: C: y5 H+ i" P
1' k# c$ o' r. p* ?
2
; E$ w) E7 D* i' G& ^8 v31 H: u) z$ f6 j) i i
49 w' I( o+ O! m. ^6 b) _" Q
5
1 _/ _# ~8 l- M: v6, D6 B! ~7 A6 `
7
$ h% Q+ B/ Q/ ?7 W8
: c- e# d* {" V9
m! H, p# C; n: n7 d/ M3 T& U请实现一个带有dropna参数的my_crosstab函数来完成上面的功能。) b- a x$ L) X7 O/ c" V
2 p, h, O4 m; X$ z1 O$ Q- Z5 {0 L5 gEx2: 钻石数据集
% w+ y+ w# w3 _( ` 现有一份关于钻石的数据集,其中carat, cut, clarity, price分别表示克拉重量、切割质量、纯净度和价格,样例如下:
, A9 h- @* L+ _( H' Z% v& E* L8 p. q8 @5 l5 w
df = pd.read_csv('../data/diamonds.csv')
9 s; b4 E- t* Y' l8 }" G* edf.head(3)1 o4 `/ ~0 Q: y. ~# X
# W# ~+ ~9 d" e$ X& D- ]
Out[76]: 9 s: }: V: {8 ~$ ^" d' W* d
carat cut clarity price5 n1 |7 _% X3 i
0 0.23 Ideal SI2 3265 l- A5 Z/ `! C$ {" H/ s
1 0.21 Premium SI1 326
: o2 i9 \# s) Q; ~: Y# N2 0.23 Good VS1 3273 T8 u I: Y2 Q4 M
1
6 y- W2 }' }2 P2 A; v2" R/ p) y( ^+ [
3
9 {; M6 y1 B( r! B4! [: s" i; E& T# v& @3 w
5) ]! P: \( Z V5 Y7 I
68 |# a9 }+ k4 H; ^
7, q7 Q3 Z; f7 v& R; j, T" `1 `
8
8 w5 @& ]$ } F分别对df.cut在object类型和category类型下使用nunique函数,并比较它们的性能。
, H' j2 k/ m8 O- [钻石的切割质量可以分为五个等级,由次到好分别是Fair, Good, Very Good, Premium, Ideal,纯净度有八个等级,由次到好分别是I1, SI2, SI1, VS2, VS1, VVS2, VVS1, IF,请对切割质量按照由好到次的顺序排序,相同切割质量的钻石,按照纯净度进行由次到好的排序。% K V( q7 |- E( ~8 ]) D
分别采用两种不同的方法,把cut, clarity这两列按照由好到次的顺序,映射到从0到n-1的整数,其中n表示类别的个数。
& d& [3 D3 j* n, }" v' ~对每克拉的价格分别按照分位数(q=[0.2, 0.4, 0.6, 0.8])与[1000, 3500, 5500, 18000]割点进行分箱得到五个类别Very Low, Low, Mid, High, Very High,并把按这两种分箱方法得到的category序列依次添加到原表中。
9 d+ x; I0 |3 J4 w. ?第4问中按照整数分箱得到的序列中,是否出现了所有的类别?如果存在没有出现的类别请把该类别删除。
) G. y/ n' y. R5 `% P7 M对第4问中按照分位数分箱得到的序列,求每个样本对应所在区间的左右端点值和长度。2 |/ ^ u3 Z! g
先看看数据结构:+ _. ]5 u. d$ n) y4 k8 T
0 j5 P3 [, d. zdf.info()
2 O+ t! ?1 J$ p7 U/ e2 B9 MData columns (total 4 columns):
# T' A2 q; O1 v! T4 x3 u5 q e # Column Non-Null Count Dtype
c a7 N5 l0 c5 A1 r--- ------ -------------- -----
$ m' [& ]1 J& r* [- _' p# } 0 carat 53940 non-null float64 f- t9 ?4 Z! Y+ A! _
1 cut 53940 non-null object
7 K7 \- w3 A% f! U0 z 2 clarity 53940 non-null object . q0 |) ?" g' ^) ~6 `1 ]
3 price 53940 non-null int64
: A- w8 b6 m: J0 E7 W* @dtypes: float64(1), int64(1), object(2)! b$ ?3 y4 f @2 ^
1
/ {3 E, d2 T+ C4 o2
# S9 k: a9 @; A6 f7 \3
/ u& F6 S" A1 a+ ?4
0 d; n3 A4 D4 T$ v1 G3 C$ d53 E6 k! U" n* r# }
66 ~! h5 T4 J+ m+ G
7
% x/ a$ J0 X2 p }& m, a& a8) Q. a x9 ]" @5 f9 Y
90 y8 A1 ?9 H' \8 E
比较两种操作的性能
! z' W! @: ]5 Y5 K! I%time df.cut.unique()5 f$ h! O& W6 l4 H- ~. [
" G4 J" K4 `6 A; O, e) `8 z0 CWall time: 5.98 ms
2 p- ?7 F( ~# Q% E6 Jarray(['Ideal', 'Premium', 'Good', 'Very Good', 'Fair'], dtype=object)
. u0 Q3 \) V5 W! k; ~1
6 m/ T0 T7 D; i. L2
" @& |5 G$ O7 F8 ?3* _* \+ d' g9 H- K. \
4
9 H( m9 k/ d L' c%time df.cut.astype('category').unique()
6 s' v( f* Z6 N* ^
& }: y& D0 d; ? [ ?, tWall time: 8.01 ms # 转换类型加统计类别,一共8ms$ [' i' b5 m, y p" G
['Ideal', 'Premium', 'Good', 'Very Good', 'Fair']" ~' K; i" m j: m
Categories (5, object): ['Ideal', 'Premium', 'Good', 'Very Good', 'Fair']
: m2 S! C, \& A/ F1, K; q! {/ ?1 f9 D
2/ C! J/ b- c/ K0 v
3
* Z5 K. Q. O* F" R" Q45 b9 @4 \3 x, X7 p: B! n C
5
/ Z6 X6 X6 J8 @' [df.cut=df.cut.astype('category')! f4 p: h! q, N( F0 n* ?
%time df.cut.unique() # 类别属性统计,2ms
2 v5 W+ s. T9 D
$ o# I. ? J# J( n# y" yWall time: 2 ms0 _9 m0 n* Z2 r4 q% B$ S7 q
['Ideal', 'Premium', 'Good', 'Very Good', 'Fair']
2 s+ C3 o+ d, Z( s; j. M8 O3 fCategories (5, object): ['Ideal', 'Premium', 'Good', 'Very Good', 'Fair']
! H: O- W/ R: E h/ G1
0 s) ]* h! I% v4 S' R* ]2
9 g8 P% l5 P; \3 T( C4 u' C3 _# @/ _
4
3 x) ^ V, i# e" q# J1 h+ `7 U) U; X5# F4 q7 V2 T8 V* P$ F7 F
6
5 _% k, ]9 K( L4 q3 N1 b对切割质量按照由好到次的顺序排序,相同切割质量的钻石,按照纯净度进行由次到好的排序。: |1 u3 m E" T4 v# }& Y e
ls_cut=['Fair', 'Good', 'Very Good', 'Premium', 'Ideal']
. E$ a, v t! x8 O. P4 Bls_clarity=['I1','SI2', 'SI1', 'VS2', 'VS1', 'VVS2', 'VVS1', 'IF']
0 r! S2 u1 V+ i9 I6 o5 `8 [6 adf.cut=df.cut.astype('category').cat.reorder_categories(ls_cut,ordered=True) # 转换后还是得进行替换
0 N1 ]7 m/ P9 a! odf.clarity=df.clarity.astype('category').cat.reorder_categories(ls_clarity,ordered=True)4 B: n$ q$ M; `" I
5 }' q" m1 X d$ K5 a1 h
df.sort_values(['cut','clarity'],ascending=[False,True]).head(3)- n: ]- o- _9 _0 ~- x+ G
! N X; {; Y. |+ J8 f4 P- ] carat cut clarity price. o5 C& v( v2 D4 d; r$ p: | ~+ j; L
315 0.96 Ideal I1 2801
6 E9 t0 x: `3 B( t$ p% [! L535 0.96 Ideal I1 2826/ I0 _' N) Y( R: X: |
551 0.97 Ideal I1 2830
4 O7 M5 Y+ }) K/ Q8 d1
. H( i' C% _; h23 n" e5 e: s5 r* w: X2 c, R$ f
38 K: W+ N9 \) g( R
4
8 b; ]" @/ x8 q+ n* ~5/ v( t6 }9 d# X* P5 d; T- i. m
6
% k2 T- T/ K; P# B, f& @7) K4 C* G& W3 i9 }/ R
8; V$ G- b0 q3 R+ p0 L
94 v# b0 W9 A0 r
10
1 P" z3 x$ m9 P/ u" A) j11) _7 j1 t, ~9 o4 e. w* @- d
分别采用两种不同的方法,把 cut, clarity 这两列按照 由好到次 的顺序,映射到从0到n-1的整数,其中n表示类别的个数。8 l9 `7 U7 |2 I6 \) B
# 第一种是将类别重命名为整数3 `( _1 E k P2 v
dict1=dict(zip(ls_cut,[x for x in range (4,-1,-1)]))4 k) l0 ]0 _" ^# ?& K
dict2=dict(zip(ls_clarity,[x for x in range (7,-1,-1)]))$ G' z! P' E& I+ f& F' U o
& Y/ {/ e0 P% N" wdf.cut=df.cut.cat.rename_categories(dict1)
8 w3 Q/ B- S$ Y3 Gdf.clarity=df.clarity.cat.rename_categories(dict2)
/ @: L: e' d* }$ fdf.head(3) w- Z, Q! u8 Z- ^
) F E3 G7 n) {) N! W+ c. Z' J
carat cut clarity price: [2 o8 V6 }6 r) F3 P5 Z+ k
0 0.23 0 6 326; f+ Y% k" b7 u# w; w" {
1 0.21 1 5 326$ f+ ~2 K3 D2 P/ ?. M) j) T, H
2 0.23 3 3 327
6 ]- I6 x( l0 U7 j) q ^1: f5 C3 r" d! t* X/ E
20 b2 \" _6 r1 K, ]5 Y
3
% p, i9 ~* F* Z C- W0 ~43 ]# A+ k5 C3 b, T( r {4 y
5
- b! Z7 W- V1 F8 @1 T) G* L6! X8 X3 _& U! ~9 |' ]
7) Q/ q! u" {+ n. g& C( R- L
80 i# S# O" w! Z& r' G
9
- a( @& W8 x. I G8 B* X) e. C10
2 C; e6 y3 X+ {* v+ d11
' S; Y( c3 Q* p6 l+ `0 u# f9 `128 ?* r0 I: o7 V
# 第二种应该是报错object属性,然后直接进行替换
, u+ @" ]3 y8 Kdf = pd.read_csv('data/diamonds.csv')
8 A3 F& i+ V( Y2 efor i,j in enumerate(ls_cut[::-1]):% X' H/ b+ i0 b+ S j" p; e3 T) \- I% |
df.loc[df.cut==j,'cut']=i 7 x" ~$ l$ O6 M' v( ?
2 B7 b$ C* S7 W3 k# f# f y+ Kfor k,l in enumerate(ls_clarity[::-1]):
+ T. B( u- f( t5 ~6 I" W' @8 l df.loc[df.clarity==l,'clarity']=k
% K& E! y: z2 L, Hdf.head(3)
2 K7 {5 ]; C; L* ^9 I# V9 |$ y0 X8 |* i$ b8 P% M% M% c
carat cut clarity price
: J E8 n+ e" d7 _# Z0 H- j0 0.23 0 6 326
7 L- G) b& `! K+ F3 F1 0.21 1 5 3265 E+ A3 o, p: M$ W% [
2 0.23 3 3 327
" n( y* g% l" W1 p8 j" g& e' t19 i ?5 i0 g4 D6 F
21 c% ?9 m, {! d9 O, E& Z: ]
3
: k- a- v3 ?: m" P9 M. z: E4' F# n+ M7 }6 J- {3 C% A
5! v0 E4 L/ P" X d6 {0 R3 o6 g9 l) h
6/ l- C9 c, b- }0 e- ?
7/ k9 N9 _ O* ^
80 [- d! }6 K2 W
9 c) ?- j+ J1 D% W
10* r8 l6 E, U' s( }5 A; u; \0 |
11$ I$ W( R2 s0 h h! m5 b: S" q
124 R% p: j/ l, b' {, y9 f
13
, z; g x: Y0 h! P对每克拉的价格分别按照分位数(q=[0.2, 0.4, 0.6, 0.8])与[1000, 3500, 5500, 18000]割点进行分箱得到五个类别Very Low, Low, Mid, High, Very High,并把按这两种分箱方法得到的category序列依次添加到原表中。2 ]5 {/ ^* ]7 F) \
# retbins=True返回的是元组,第一个才是要的序列,第二个元素是分割点: y# M9 m- w' X6 M0 V
avg=df.price/df.carat, H& Q) e4 w3 J1 l4 v; ?
: I; p1 }5 V0 [ F U7 O) N8 `' |
df['price_quantile']=pd.qcut(avg, q=[0,0.2,0.4,0.6,0.8,1],
9 V+ T+ h. v! ^ n2 U% K labels=['Very Low', 'Low', 'Mid', 'High', 'Very High'],retbins=True)[0]6 {! ~ K* {3 w& S1 d, t: ]
a( V7 D0 ^. w* z! W1 ]# Udf['price_list']=pd.cut(avg, bins=[-np.infty,1000, 3500, 5500, 18000,np.infty],! X( f. L" g9 W& m
labels=['Very Low', 'Low', 'Mid', 'High', 'Very High'],retbins=True)[0]
) f3 H, K0 G/ Q( t6 ndf.head()
4 t. r7 S* Q/ _$ o, e0 c! |# O. v/ E. l" k3 j
carat cut clarity price price_quantile price_list W8 O0 ?$ d! {% q
0 0.23 0 6 326 Very Low Low
5 j" W2 ^) D- ]8 V i6 l1 0.21 1 5 326 Very Low Low
4 G: i2 ?( y: ~" W7 ^$ X4 W v9 |2 0.23 3 3 327 Very Low Low3 L; _4 [6 j0 l
3 0.29 1 4 334 Very Low Low0 L0 J9 {- Q7 N/ f0 s
4 0.31 3 6 335 Very Low Low : m- W1 a" Y/ V$ a1 R) {' o: y$ i
* a- t; ]& O0 i+ [' H18 s4 G* M. ^' W% o& W5 Y. ]1 {
2
" ~, t; ~( ^7 G: U3
+ i, D6 ~8 N0 U! A) G; z6 B$ R4* m1 A8 z& e$ u0 V3 K9 x1 y
5+ c* Z* ^0 Y+ |0 k4 H
6
, ~! k7 V+ O' G3 P6 r9 F* ?. Q3 V, o7
5 v1 s* W B/ P- X& J. ^8
8 S$ L$ }2 T% a9 x c9
. c3 [/ |1 U9 ^7 K10" K! H8 x# u/ R9 B) u! p. a
11. w6 f, j2 L# ^- Y
12
- X; T6 x' a( n0 |. r4 q3 j13/ L, h6 f3 s3 i
14
2 J# O( }/ a( l2 C0 v# W: @15
+ L$ {( }; g: J4 i& f0 o16
, J1 B9 k2 ^3 S5 S分割点分别是:
3 [1 L) Q$ l' |9 I8 l1 Q
' ^7 S$ F3 k0 w" d) F: uarray([ 1051.16 , 2295. , 3073.29, 4031.68, 5456.34, 17828.84])6 B' Y/ [& @3 ^
array([ -inf, 1000., 3500., 5500., 18000., inf])
0 K0 e, A. ?3 n4 h10 R& b- ]/ ^7 c8 N+ o& i" b. j4 ^
2* |. ]; O5 b; R) |* F& _9 w4 z( Z
第4问中按照整数分箱得到的序列中,是否出现了所有的类别?如果存在没有出现的类别请把该类别删除。
6 a* T( s, Q: J1 Zdf['price_list'].cat.categories # 原先设定的类别数/ G! M! O% X! a1 R% ?
Index(['Very Low', 'Low', 'Mid', 'High', 'Very High'], dtype='object')
7 V: N( l. R# K& e/ Z8 D* | H% Y6 A( ]2 w9 b! N- ]% h2 u! E
df['price_list'].cat.remove_unused_categories().cat.categories # 移除未出现的类别
R5 V9 V+ ^9 G. w) HIndex(['Low', 'Mid', 'High'], dtype='object') # 首尾两个类别未出现, u* b$ u+ [6 R* Z" j
1" ~( J2 H! p/ P; j: H* T% V
2
( U7 ]7 {) m& c3 V. e/ m- m. _4 N) S
44 h+ O c# w1 E
59 b9 s, D7 H! Q/ a9 f* y
avg.sort_values() # 可见首尾区间确实是没有的. B- [ H" Z% s. P/ Y7 C& Y, p C
31962 1051.162791+ j7 V% X% _2 w2 T6 v
15 1078.125000
- _2 R4 B$ x3 ^& q4 1080.645161; K2 z, d+ v7 x8 I
28285 1109.090909
5 }' D0 J2 w9 S3 ]: e! b- R13 1109.6774193 d4 q9 {) L. J# k
...
Y7 l2 H% f' {26998 16764.705882
* `7 t( p6 x* `5 J27457 16928.9719636 M/ f2 y+ p3 u3 @) n
27226 17077.669903
9 h. O8 B" q+ a9 D! W27530 17083.177570 [. ^6 |! F+ P# G% o4 o D4 @
27635 17828.846154
' }9 C, s; g9 u& Z1; a/ B, d! ~ O+ A1 ]
2; j( n+ W* E0 }
3& A2 L& X2 F9 U
49 r; R: [+ q8 w: h
5* v7 ~1 }- Y) ^# S5 `
6) V0 n. _9 m! N/ n& {
7
8 k# y' J2 b1 j" k9 D. I4 U8" \! i4 h% s8 L5 w
9
+ k& N6 W6 s( D4 q10
$ N2 `5 [9 l x) U) s: D! n' I6 ~11
. M8 E) H$ e _" y12
% ^/ V# e6 k% x, I& Z; l2 j对第4问中按照分位数分箱得到的序列,求每个样本对应所在区间的左右端点值和长度。& s& g1 o) O+ m
# 分割时区间不能有命名,否则字符串传入错误。
$ F8 b" r& s, |; p; @* b6 Lid_interval=pd.IntervalIndex(
# _5 B& K9 r- R& q: r pd.qcut(avg, q=[0,0.2,0.4,0.6,0.8,1],retbins=True)[0] H1 C; s5 ?+ l9 l: y! R* C7 F, O
)& }. P8 \9 O/ e4 v
id_interval.left# ^4 A3 {3 I' O, @7 h* k& e1 m
id_interval.right
1 t f& B+ W# y3 Z) ?9 iid_interval.length 5 u0 j8 f- E3 h/ l8 |/ U: {
1
' y2 x. ^1 I% t0 y( K2
6 _) _: K4 d# i3
3 b! s' l- f: R, F" E1 v: d" w44 @, ^6 n( {, x* d. {+ S) N- G
5
0 b6 m+ E. t' C- z/ N2 N6, g1 ]7 \7 b. \0 _$ V2 r( F
7! D4 b, {$ H# ^
第十章 时序数据
* @ X7 x! i# cimport numpy as np
0 F$ X+ }" \% n7 ?7 aimport pandas as pd
+ g! m8 Q5 a( \' @! {. q3 Q: S1
4 v7 @' ^+ R$ J2& P$ G3 G1 d& @
. x# L8 r1 f1 y& b0 x" O0 {
- M8 u5 Q) y6 M. m10.1 时序中的基本对象
9 w+ H, C$ s/ a# A% O# {; L& { 时间序列的概念在日常生活中十分常见,但对于一个具体的时序事件而言,可以从多个时间对象的角度来描述。例如2020年9月7日周一早上8点整需要到教室上课,这个课会在当天早上10点结束,其中包含了哪些时间概念?+ e; W1 f' I# F2 g, ~) ~' Q/ E
. z5 u1 k/ I1 B+ I
会出现时间戳(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的简写。
7 s. s7 c5 E1 u* n2 d# m+ R7 \/ T3 g$ e5 u, D
会出现时间差(Time deltas)的概念,即上课需要的时间,两个Timestamp做差就得到了时间差,pandas中利用Timedelta来表示。类似的,一系列的时间差就组成了TimedeltaIndex, 而将它放到Series中后,Series的类型就变为了timedelta64[ns]。
) P O2 C X% Y; L0 _# z' y/ l2 O8 o, y4 Q) N
会出现时间段(Time spans)的概念,即在8点到10点这个区间都会持续地在上课,在pandas利用Period来表示。类似的,一系列的时间段就组成了PeriodIndex, 而将它放到Series中后,Series的类型就变为了Period。* N M9 C* C+ G( ] w
( s9 ?: S* n5 G% u& H# O% _0 N- t, g会出现日期偏置(Date offsets)的概念,假设你只知道9月的第一个周一早上8点要去上课,但不知道具体的日期,那么就需要一个类型来处理此类需求。再例如,想要知道2020年9月7日后的第30个工作日是哪一天,那么时间差就解决不了你的问题,从而pandas中的DateOffset就出现了。同时,pandas中没有为一列时间偏置专门设计存储类型,理由也很简单,因为需求比较奇怪,一般来说我们只需要对一批时间特征做一个统一的特殊日期偏置。
# V7 u. r5 h# x, O: Q7 V
1 L7 _9 C" P& y( M 通过这个简单的例子,就能够容易地总结出官方文档中的这个表格:
) Y% ]4 n/ R! j: y$ F( D, F- W' X5 L4 J$ g/ I z. U
概念 单元素类型 数组类型 pandas数据类型
' u! E, D; T& K- S7 {+ rDate times Timestamp DatetimeIndex datetime64[ns]
3 {% V7 z4 |5 ^. z5 q0 Q+ {2 lTime deltas Timedelta TimedeltaIndex timedelta64[ns]0 A G5 @! Y( ]/ f7 b/ H, n4 _/ `
Time spans Period PeriodIndex period[freq] E; {9 x, B! W2 R. w
Date offsets DateOffset None None, Q' B8 V$ G2 h6 x2 I D
由于时间段对象Period/PeriodIndex的使用频率并不高,因此将不进行讲解,而只涉及时间戳序列、时间差序列和日期偏置的相关内容。3 h9 [, T* v% t1 |2 P) F5 \! _+ ~
: o7 ?% }7 c* H10.2 时间戳0 \% R$ `9 V F2 t/ e" p' Z% \7 E
10.2.1 Timestamp的构造与属性( ~+ r) Y" f4 ^+ z- L* R ?
单个时间戳的生成利用pd.Timestamp实现,一般而言的常见日期格式都能被成功地转换:
( ]+ n0 Z) ?7 j
/ m* }# \# k/ r: r- Kts = pd.Timestamp('2020/1/1')
5 z* H* {1 P) x, E6 R0 o8 T: |' J% n V& c1 q: j. U
ts; e, K) q6 M! J p! E
Out[4]: Timestamp('2020-01-01 00:00:00'): `9 _# N7 _3 s, B
+ V! G9 C" P+ u2 ?* R8 u$ V) f
ts = pd.Timestamp('2020-1-1 08:10:30')
* e" A7 y5 W9 v/ |# h
8 `, }- |% [9 Pts) y, V# w/ q- S( z# F( F
Out[6]: Timestamp('2020-01-01 08:10:30')
; t1 {* B+ z! X9 b/ n- Y2 i+ K# Q1
5 D# H% \8 j k4 p- f% P2
* I9 } {6 I' I; N' C4 F' e- i3
% l# i# d5 W" j/ M3 N0 \42 h$ S* V8 w9 \; }/ o( m4 |7 i
5
5 K' B0 ~$ I$ Z* r6" ^4 e% j+ S4 ^* @
7
% c- p8 z/ V$ ]8
- r- x w' ^4 C( }7 G9
7 d- B' h7 q4 u9 F+ u通过year, month, day, hour, min, second可以获取具体的数值:
! a7 Y) S* `8 [1 t+ O& G
8 Z% E. F* l3 j3 D; b0 lts.year
! O, P7 t _2 y- Z6 v) p' p& gOut[7]: 2020: ~7 P, v/ U5 e2 t' A; U
! x. |, ^# H1 y: }! }% _6 hts.month2 J- c( z0 B$ H: [ r7 Q9 \
Out[8]: 1
/ O7 R& b+ d& i# p {. I; Z. J# G9 o( s6 ^% I
ts.day
/ l W0 K- h aOut[9]: 1) |* u6 J9 L4 O6 Y# ^1 O
* Z7 E7 w3 }( Z* R% Q1 fts.hour ` S0 \) t. N0 a
Out[10]: 8
# h L5 n" _3 L' s0 ]1 i! }2 f, R9 E: H5 d
ts.minute
2 ]$ F$ r3 f9 @$ qOut[11]: 10, c& a" e* J4 }5 X9 q( I9 e
9 N! s" e4 ?7 `) G* I
ts.second. k( l, e [8 l6 O+ x9 u6 M
Out[12]: 30 y& x) _6 b3 _& s$ l% I
' g! `, ?% U! }+ r( H$ [/ m4 j1
2 `# \+ n- |& U5 z: m4 {2; {! j( g; P0 F& f
3# G6 {: b5 O% m, M- r* A
4
9 d$ l' g9 K2 g2 D$ f9 u5
7 h | J8 v; T! y6& r( E9 O: r& x
7
1 X0 h! w/ |2 a% x" b9 @. `8
6 O7 a2 w5 y' C: ?9
) k" q* @; q0 v T! Q8 H( \4 g10
% q+ `; T1 I) @3 k11
& d+ ^' k. ^. i; Q" g& T12# Z. V. D2 q, ~8 J' l8 |6 B4 l
13
9 y6 g' T r6 l14
* R; c8 @% c" u/ ~; v; W15
! k4 ^) N6 ~# h+ Q$ L$ [) }16) B4 G. u! ~3 d' S
17
7 d" ~5 d6 v. Q! e; S# 获取当前时间- W4 `" U( O% C+ D* ]9 @* Y* r* h" t R! G
now=pd.Timestamp.now()
* \5 W4 v. k4 W! B7 _# L1
v! J( K8 q8 N" d1 n8 G. |6 [2
- o+ n# c+ I- O) A/ u8 o* Q在pandas中,时间戳的最小精度为纳秒ns,由于使用了64位存储,可以表示的时间范围大约可以如下计算:
$ k! p7 B$ L, F3 jT 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)' y2 K- R7 d" l' @8 J
TimeRange=
2 l. F! o9 u4 c8 |, y10
; v3 t" X7 n* f$ R9 a5 ~9
: e0 c& J P! x6 j q. y ×60×60×24×365
) X$ U2 o2 Q# S5 [# ^+ L$ j6 l5 B) q2
/ F4 E/ X1 C: n% f8 V5 G. c) I64& S) p* E- C" P
# Y* B7 }7 t( H/ U0 `
* L# p# E8 f' i3 e9 P8 L; r3 ^
≈585(Years)( T0 q% g; o* F, h+ U
3 H, n& Z( O0 h m, ~通过pd.Timestamp.max和pd.Timestamp.min可以获取时间戳表示的范围,可以看到确实表示的区间年数大小正如上述计算结果:
$ F6 v& F# F) [" O7 g6 \# |' Q
; H$ I4 A3 S* Xpd.Timestamp.max
1 O# o4 e$ W! H. p! Y+ i: EOut[13]: Timestamp('2262-04-11 23:47:16.854775807')2 F' r! H. h5 j- e1 w" ~" F3 {/ n
8 a7 t `* ]) k# `
pd.Timestamp.min
* g& V8 ?3 y6 N) N! `2 G2 qOut[14]: Timestamp('1677-09-21 00:12:43.145225')' D3 r8 [9 C2 X1 j. d: u
7 G$ w, I$ \0 }$ ^
pd.Timestamp.max.year - pd.Timestamp.min.year
8 s0 m8 F# y8 d- S; S! e$ {Out[15]: 585# \+ M/ F8 B4 e, N
1
. F# Z$ v/ H0 Q2 Z2' K& a3 I/ g4 w* _4 F4 a( D
3! w% u8 a3 p( J- F3 u. r/ ?
4
2 n5 j. P+ O" j) d5- `8 |6 }7 s, B* U
6
3 j. [/ N6 `8 {& e9 H70 x3 d4 P7 b. |1 E0 i/ ~
86 ~6 V+ t6 N0 a8 }
10.2.2 Datetime序列的生成
s1 ?1 w l# G$ r; v4 I$ w2 |pandas.to_datetime(arg, errors='raise', dayfirst=False, yearfirst=False, utc=None, format=None,) {+ i5 |" @, F5 O# A
exact=True, unit=None, infer_datetime_format=False, origin='unix', cache=True)$ z; k8 b5 @5 t5 K: q- C7 |
1: r, G0 p3 U4 d/ |* _) b
2, g8 w% h, p& m7 V7 B
pandas.to_datetime将arg转换为日期时间。
$ Q- c! j0 J* \7 O9 ?) O8 l: ^
! W/ q7 {6 V3 B. ]3 H0 Q' w! Darg:可以是argint、float、str、datetime、list、tuple、一维数组、Series、DataFrame/dict-like等要转换为日期时间的对象。如果提供了 DataFrame,则该方法至少需要以下列:“年”、“月”、“日”。4 S& k5 P& k% C4 j$ I& u
errors:
- v! q' ~6 p4 h) m, o0 a- ‘raise’:默认值,无效解析将引发异常: }7 @0 b7 i9 l
- ‘raise’:无效解析将返回输入8 a4 ]2 F _& K) j; Y
- ‘coerce’:无效解析将被设置为NaT4 K _ [5 _& |, K% {" q& V; g$ g; Q
dayfirst:如果 arg 是 str 或类似列表时使用,表示日期解析顺序,默认为 False。如果为 True,则首先解析日期,例如“10/11/12”被解析为 2012-11-10。如果无法根据给定的 dayfirst 选项解析分隔日期字符串,会显示警告。
, [: T- T+ F% M1 \4 [6 Wyearfirst:如果 arg 是 str 或类似列表时使用,表示日期解析顺序,默认为 False。如果为 True,则首先解析年份,例如“10/11/12”被解析为2010-11-12。无法正确解析时会显示警告。(如果 dayfirst 和 yearfirst 都为 True,则 yearfirst 优先(与 dateutil 相同)。)
$ t# j$ }4 B0 ~( @) C1 j7 ]* `utcbool:默认None,控制时区相关的解析、本地化和转换。请参阅:pandas 有关时区转换和本地化的一般文档6 z0 b6 P) c$ b- X5 a9 D; D# D
format:str格式,默认None。时间戳的格式不满足转换时,可以强制使用format进行匹配。
. n+ {, |8 s r+ C" e" }unitstr:默认“ns”。它是arg (D,s,ms,us,ns) 的表示单位,可以是整数或浮点数。这将基于原点。例如,使用 unit=‘ms’ 和 origin=‘unix’ (默认值),这将计算到 unix 开始的毫秒数。
9 j* o% H% y! K% L) Ito_datetime能够把一列时间戳格式的对象转换成为datetime64[ns]类型的时间序列:
) x( |6 P" t4 {2 m% Rpd.to_datetime(['2020-1-1', '2020-1-3', '2020-1-6'])' E" R( |- O! y# F8 C
. m, B. }; l2 D+ vDatetimeIndex(['2020-01-01', '2020-01-03', '2020-01-06'], dtype='datetime64[ns]', freq=None)0 n4 r+ ]& z2 {* a) P' e
1
. _/ ]: S( R" P- B. V2( z6 G R7 {9 B- `( Q0 ~
3
V) ~- ?: Y# b" P$ b) i在极少数情况,时间戳的格式不满足转换时,可以强制使用format进行匹配:
8 l: h; O' p' H3 U2 v" T5 _4 G" Z" x4 ^) ]9 E9 f
temp = pd.to_datetime(['2020\\1\\1','2020\\1\\3'],format='%Y\\%m\\%d')* j$ ] |$ i' ^4 z8 ^2 [' \
temp
9 s$ ]" h( o# m6 n: s' ?% ~& K
1 j' x' O* t+ ~1 T/ bDatetimeIndex(['2020-01-01', '2020-01-03'], dtype='datetime64[ns]', freq=None)
$ S7 S9 h( ~/ u4 Z* K( [3 {1
) [- X3 n# @9 J7 z% F0 I3 C2" i% z# G. L9 S0 @
3) w5 ^5 q4 e' k1 u& i2 Q7 c, C
4
9 S$ O* u8 F8 `* O0 m. }0 s 注意上面由于传入的是列表,而非pandas内部的Series,因此返回的是DatetimeIndex,如果想要转为datetime64[ns]的序列,需要显式用Series转化:0 Y, a& ?- X& z5 R: V
1 Q. m4 A' s. j4 zpd.Series(temp).head()
, r5 v- r6 K/ Y4 _. H/ @7 i$ r0 v$ `7 o
0 2020-01-01! C. j, G& f# c1 `+ _- T# ~
1 2020-01-035 K# C( y: d' y2 @* l( s
dtype: datetime64[ns]2 G- O6 w8 ?: L1 x9 c& P- y( y7 q$ X
1" s7 I$ Q- T9 i, ?5 }3 `: L
28 j4 x3 ^3 ]+ p. O
3+ l# m- J$ x0 M$ ~! r5 k
46 K7 j! p/ Y1 f0 D7 X/ ~7 r
5
; H6 n: b" @( K: {- L下面的序列本身就是Series,所以不需要再转化。
% G! {' |0 y3 U
/ c& x! D- I8 @/ Tdf = pd.read_csv('../data/learn_pandas.csv')( x- ~: D) p8 y$ X, U
s = pd.to_datetime(df.Test_Date)" l$ `& ]! o$ p) ]% I
s.head()
) P/ y n6 V W2 v8 f
9 x7 r6 v4 t# D1 B/ [3 L0 2019-10-05
+ d- k4 l$ d/ G% {8 }1 2019-09-040 P/ N) S7 e8 D/ c1 }0 K6 p: U
2 2019-09-12
- j4 ^' d& G1 e; m. }: `& l3 2020-01-03
R! n0 b' M- ~5 E4 2019-11-06
: ?. e, C9 V8 \Name: Test_Date, dtype: datetime64[ns]# k- ] \; V/ C' B
1- L7 g2 b5 T' J( N9 E6 x& M
2" \$ H" G" g( k! ~% m
3
2 @4 Y+ Y- n6 Q6 _4 H4$ f4 t5 V) v6 ~+ Z0 d h. |
5
! ?& p* f7 Z- |! s6 o6& }! S/ z4 }0 ?7 o5 t
70 u9 ?' L8 H$ i6 u: L( S' n
85 a7 Z6 k. F+ A; h. w% |, b/ g
9) `: P8 {- b! U4 g; G
10& ~* p; Q- r' o* Z/ a( D: i( s
把表的多列时间属性拼接转为时间序列的to_datetime,此时的列名必须和以下给定的时间关键词列名一致:$ u0 _6 Z5 _ Q: |5 ~8 [
df_date_cols = pd.DataFrame({'year': [2020, 2020],
# S8 O7 `- @# a$ ]$ p 'month': [1, 1],; p" j1 z) ~% X- {. e) h+ g
'day': [1, 2],
; o+ O# e: B4 U Y 'hour': [10, 20],
% y6 J& J u2 B. B 'minute': [30, 50],; G# \' c' f2 X+ {2 d
'second': [20, 40]}) N" c2 e* H* Z4 ]0 f5 D( h, s
pd.to_datetime(df_date_cols)6 z$ M' S/ D! [- ]" H0 N
' K6 \+ A# G0 v5 b
0 2020-01-01 10:30:202 F/ f, q. X8 E6 ]4 N) H* F. `. ]4 a
1 2020-01-02 20:50:40
' z. j, k4 r7 F& Zdtype: datetime64[ns]
8 r" Q/ R' `* w8 A& `3 \1
$ c; a/ x" l8 g- u" j+ M1 u2
$ C! I+ v. r7 [3; {# X4 n/ d0 _/ x
4
M$ w; p# Q$ @5
; W" n& Z( C; H& `68 D" S4 ` i/ m g/ J9 n7 D
7% u. f" v2 P7 _6 q3 f. O/ c
83 e! P, s& B- v: L0 Y5 A7 A2 N
9* x" V! |1 c/ `4 p6 J9 {
106 b) N( d7 f- H& I0 y& @
11
3 q) r, u! p9 r" l# V; N! F& }5 Udate_range是一种生成连续间隔时间的一种方法,其重要的参数为start, end, freq, periods,它们分别表示开始时间,结束时间,时间间隔,时间戳个数。其中,四个中的三个参数决定了,那么剩下的一个就随之确定了。这里要注意,开始或结束日期如果作为端点则它会被包含:2 I4 j' G. T* T# F) m
pd.date_range('2020-1-1','2020-1-21', freq='10D') # 包含
- r w3 Y6 P" e" a6 AOut[25]: DatetimeIndex(['2020-01-01', '2020-01-11', '2020-01-21'], dtype='datetime64[ns]', freq='10D')
* `2 d. h4 y/ G' r: Q% Q
) x3 l/ R8 k/ I; R/ wpd.date_range('2020-1-1','2020-2-28', freq='10D')
5 ?# t/ _7 r8 QOut[26]:
6 U7 ~: B6 W. Y% t# xDatetimeIndex(['2020-01-01', '2020-01-11', '2020-01-21', '2020-01-31',) n: R* ^/ |" `5 }1 I
'2020-02-10', '2020-02-20'],3 J6 X$ V# R4 Y' ~4 n
dtype='datetime64[ns]', freq='10D')6 e5 t- S" i8 O1 X
' t( H: _; V$ B9 J: @# M/ qpd.date_range('2020-1-1',/ X [& r% w" i$ G% h
'2020-2-28', periods=6) # 由于结束日期无法取到,freq不为10天* q6 f! x. x, q* R# T
' ~/ J4 G0 W# l6 }5 L
Out[27]: ) X5 |: g- P) P; c
DatetimeIndex(['2020-01-01 00:00:00', '2020-01-12 14:24:00'," s f; c& `6 ^' Y: L$ ]- k
'2020-01-24 04:48:00', '2020-02-04 19:12:00',
& h5 z) T' h* `' Z0 O9 Q '2020-02-16 09:36:00', '2020-02-28 00:00:00'],8 ^6 Z9 |) S9 i' L3 _( R
dtype='datetime64[ns]', freq=None)( s6 s/ u/ J. E3 R' J
& P! W1 s3 ?$ n9 Z* n* h) r* T
1+ h3 z) z3 q% N. A9 u6 C
2
: _* X. D: i) [1 c. y+ B; y2 {1 `3
n y" e- p$ L4
5 z1 K: W: H# e2 F5
% [- N3 x" |/ M+ u- [6& W+ {0 \, I" X+ I- B- m( h
7: u2 M/ ], m$ \
8
3 E0 _ D. h7 D+ P# O, m9
! {( Q& |" G Z# T E5 Q5 R10
" V8 x9 Q: U7 s2 S& m Y- q11% a* } Z8 ]( k
122 M" S* x: I. {4 _# z2 h8 [2 W7 o3 g
13. k F5 `4 X: D
14
/ d0 x; `/ m0 k q$ s15* K5 `+ L: F0 b9 m% m
16; Q- d8 N8 w4 q. q0 ]: A
17
" M, z/ ?, ~ @; H4 [这里的freq参数与DateOffset对象紧密相关,将在第四节介绍其具体的用法。
3 E- `* S0 O ~% H8 H; X& l: n
【练一练】
! A+ P7 ~/ C- N7 v, i. r( jTimestamp上定义了一个value属性,其返回的整数值代表了从1970年1月1日零点到给定时间戳相差的纳秒数,请利用这个属性构造一个随机生成给定日期区间内日期序列的函数。6 W8 Z9 u5 e$ b7 q* J4 `. ?5 ]
% t/ Q$ N d; c8 m
ls=['2020-01-01','2020-02-20']
5 K& |3 ^! N3 Y3 M: Qdef dates(ls,n):! x0 U' m6 W# q$ u
min=pd.Timestamp(ls[0]).value/10**9" U3 {$ t/ N! U1 ^; v
max=pd.Timestamp(ls[1]).value/10**95 K! c7 r/ h* C5 x8 `
times=np.random.randint(min,max+1,n)
/ R2 b b$ Y: Y9 X/ ^ return pd.to_datetime(times,unit='s')
, q' I& s% {8 ^' C% _dates(ls,10)
+ M- L9 J* z! r1 Q
3 x' r; \- F( Y) X$ }6 `) pDatetimeIndex(['2020-02-16 09:25:30', '2020-01-29 07:00:04',) Y+ y& `6 Y, ]5 g2 Y) G8 Q
'2020-01-21 12:26:02', '2020-02-08 20:34:08',& O: a) ~! e8 ^$ l
'2020-02-15 00:18:33', '2020-02-11 02:18:07',
! n6 E* i5 d9 F! j '2020-01-12 21:48:59', '2020-01-12 00:39:24',! {! ~0 p, K7 b) q# E5 h
'2020-02-14 20:55:20', '2020-01-26 15:44:13'],# b! a3 U' j; b6 W& Q5 u) N
dtype='datetime64[ns]', freq=None)& J5 I) ^1 v. d# K7 Y$ ]
1
6 V0 l: U+ u. r$ A$ o22 d% N4 M7 ~# i
35 _" N% A! v6 e ]0 l3 s$ R2 A
4
; ~$ Z* m: _) l# S5
8 u3 @- h6 O7 `5 Q1 I6: p2 ?$ n6 H$ [
70 u4 V- S+ i: {0 y6 c+ T- B
8
6 C8 O- O s: f1 u97 b: D, g; K* m3 L4 `, S5 G
10
! `, v( u/ P9 B5 b$ d11
8 ^* J$ Q& |- M) P9 ?" U: f# K12
3 c3 {; `4 X$ Z% K% h: ~: B13
* D4 q. {( E" s; |4 r7 t! l14+ i7 f+ w4 B4 t0 U. o" j4 v3 N
asfreq:改变序列采样频率的方法,能够根据给定的freq对序列进行类似于reindex的操作:+ f2 E+ A4 n0 J- M9 i s* ^& W- ~
s = pd.Series(np.random.rand(5),
) a, s$ Z. X l3 K index=pd.to_datetime([/ F$ q$ N( i3 U% g5 T
'2020-1-%d'%i for i in range(1,10,2)]))
: H1 r- |% N! L9 x) a& r# S4 k5 Y& d' S' }$ L( r6 U( j6 T& i
P L2 @. y. N3 d) C
s.head()) [2 S" m! p$ j5 ~9 H
Out[29]: $ E: u; H) P; D% E9 Y2 s1 T# c
2020-01-01 0.8365781 k" A7 r* h4 v* ], P+ F
2020-01-03 0.678419% B4 y) r: e: n. z
2020-01-05 0.711897
: H- L- o6 s, \# U6 p2020-01-07 0.487429& V, h3 w7 X5 H3 L3 G# p) U
2020-01-09 0.604705" n" o% f8 S- b I4 i" R" A
dtype: float64
* U" b9 S: k5 X- k5 _7 b5 `/ Q* u* P
0 H: `! Z* @7 e- n% Qs.asfreq('D').head()
/ K% ]: M$ ]7 a- }0 dOut[30]:
$ Y0 \+ H# q ~- P* @6 B/ `2020-01-01 0.836578& ]9 X" \( ?( R* T
2020-01-02 NaN& [: F2 a9 N6 Q/ K
2020-01-03 0.678419
+ g# i D+ ?9 k G5 W* ^2020-01-04 NaN0 s6 ^* J: y2 M d
2020-01-05 0.711897& Q- b2 S) h( b0 I& I
Freq: D, dtype: float64
/ O5 `8 F+ i3 Q
- V. B. z" f4 q! |3 Us.asfreq('12H').head()
1 b! h3 Q8 ?5 V3 S. N; I4 \Out[31]: $ S7 w v! ]: ?* o, y
2020-01-01 00:00:00 0.836578
9 @8 e, W6 P9 E7 I2020-01-01 12:00:00 NaN
6 H9 R6 J: S7 m2020-01-02 00:00:00 NaN D1 q/ h5 ~& n, O+ `
2020-01-02 12:00:00 NaN7 u( Y) M0 H& P& D
2020-01-03 00:00:00 0.678419
3 D+ s, |! f. Q) BFreq: 12H, dtype: float64$ v, X# t) k# h% V1 w# u
. B! F" f7 [5 o- z) w7 n15 l7 m# T0 [) C7 V# f9 B
2
+ y6 T0 M. s2 Y. v5 V5 Q6 l3
* [6 r* n; B& V7 m( p8 I' H5 S4
. e% f# p% d V+ M: ]5. k6 B0 K" F; x* C1 c
6) ?7 M* _: p$ o z5 t* u/ W/ q- b# G
7. L: O' L4 k9 a3 P+ j
8
6 b) o3 b# Q. N* r' g& }, y1 D3 E9
( L; L8 {$ l+ D) O% o10$ Q% h6 ^; p6 G- v3 W0 n( Y( e* Y
118 `6 G! }( k4 w' z: Y
127 v8 t. v5 o1 W
13
9 o% { Y1 l3 J6 n% Q" ], r& C8 {. h14& }6 Z, h. |2 e4 W
15
0 b- D: z+ ?: s& U: g& L( O16% Y! o+ }* O _9 e3 c4 s# o: l
17
5 L! E9 |, H s; c1 d- a( k8 C6 ?- v6 D ~188 \: S6 B, R9 s: `
19
2 u" ?% X! y" @$ L0 h$ ]( @5 e20
+ ^+ z' o3 C+ [. D6 m2 s21% [% q9 z# U$ |- E; B
22
' W0 K* H. S* C$ D. B" h9 ^ E23, E- E+ j' m h" ?
249 E, g" R3 m. t$ @8 O# q0 j) ^- E
255 l, Q2 w' x0 L( ~6 c2 F( b
26
. j* q0 v) I1 T8 C8 H+ s27/ S, e# ^7 P# F% c3 }
28
6 f% W& L ~" d! t5 c! O2 k29
# d) B% ]( k5 b# `( K303 C4 K( L/ [. x# z* `/ A
31$ q* h4 v: v4 }8 k; _8 J7 q& Y
【NOTE】datetime64[ns] 序列的极值与均值
x9 G7 ^2 w) P1 ]! y9 S4 v( H 前面提到了datetime64[ns]本质上可以理解为一个整数,即从1970年1月1日零点到给定时间戳相差的纳秒数。所以对于一个datetime64[ns]序列,可以使用max, min, mean,来取得最大时间戳、最小时间戳和“平均”时间戳。 s/ ~" O2 c& s0 d4 D& L3 i
- T& K7 V, Q& d* Y8 V3 ~6 Z8 m
10.2.3 dt对象3 u/ l1 R) q$ g2 x8 z
如同category, string的序列上定义了cat, str来完成分类数据和文本数据的操作,在时序类型的序列上定义了dt对象来完成许多时间序列的相关操作。这里对于datetime64[ns]类型而言,可以大致分为三类操作:取出时间相关的属性、判断时间戳是否满足条件、取整操作。
. u% P# M; W1 e, b/ }5 g/ |& E2 k
n' \3 M3 P- @0 ^1 p4 g第一类操作的常用属性包括:date, time, year, month, day, hour, minute, second, microsecond, nanosecond, dayofweek, dayofyear, weekofyear, daysinmonth, quarter,其中daysinmonth, quarter分别表示该月一共有几天和季度。
( y3 [2 j4 q7 j/ S# Ss = pd.Series(pd.date_range('2020-1-1','2020-1-3', freq='D'))
6 i3 \8 g% E: n7 A+ [- f, E9 O9 {* s/ m( Q; W- E. A
s.dt.date
7 T7 [9 @+ w8 ^Out[33]: d- u; s# H9 V6 ~
0 2020-01-01- I! Z3 u/ P, K# J! m
1 2020-01-02" b& S9 c- d. L/ w3 _( g
2 2020-01-03! O$ `+ ?$ b- u+ `
dtype: object
0 y8 M: ~$ E- z( ~: W2 D9 {9 X6 u& l8 V* ~* q6 [0 K. P
s.dt.time+ A6 P/ p% m$ U+ d' e
Out[34]:
; W/ w' `, ]" ~- [' B) N2 [& V0 00:00:00
7 v1 K8 [% g6 Y# h9 W5 d* T- I; c1 00:00:00
# d9 U" m- a+ z/ Q2 00:00:00& R2 H5 ]: \" b( r
dtype: object9 h- T3 x1 F b6 i2 t
8 L2 l. S" S2 I( J: n; as.dt.day& d; u" ?2 w' H+ |
Out[35]:
" \- a1 w( I e* I- m* J0 1& k$ j+ a9 D1 `5 ^: e: U& x
1 2
0 Y' _* E3 m) [# v. X2 3 Y3 B7 t4 N" q- |4 f5 U
dtype: int64
* t3 f! Z4 X5 Q$ K& q: ^; R- u i, |, E: R% v, F
s.dt.daysinmonth
9 ?! T) t* ]& x. c7 V5 m/ N* KOut[36]: ( X. B/ \" J( U: B+ }
0 31
. Z5 F& i( x4 p: h7 N' q i1 31
' T+ m, w+ T5 z: A0 E& E) e* H2 31" n7 { L2 O/ S' v% {# C
dtype: int64
1 x% r) \! z, K8 v! O. p$ I( G Q* y
1; m) d3 b I. A
25 D7 u! B2 p. t, P w
3 p; g+ `4 J; M$ p! }
4
$ W: s. o* R7 C0 u4 Q6 r5 W; ^! m4 z0 K; [" W
6
( V/ d( }7 ~# d1 G# I- p/ }77 \! |1 {+ Z4 W' P. @2 R
8
# b) X7 l( A& e2 M( _1 w0 q9+ I5 ?# u2 M: y8 a/ I7 L$ F1 ~
10
: W) ~ d4 M! ^4 r9 Q6 w11+ B1 i7 j0 [8 X$ d9 U! n8 A7 b, y
12
8 m: p1 S/ L: E13
8 p6 Y, }/ |$ [- u14
" m6 Z* r6 O2 |/ \1 E8 N3 x15$ Y5 ]1 [: h3 V. {, r
160 I1 {8 \. D( H; w c0 H# G
17
! L1 q/ ^0 c) q18" o9 G. Y! s! C% M2 \
19
8 ]/ r1 h* x1 c, M9 P1 w' e6 {9 h20- ]3 K8 G2 H- N3 ]. W' z% e
21
3 {( z* H2 y; T+ _5 _ E) d22
8 X& {) h! A+ Q23
. G4 V! m3 a8 _, G+ W g24
B* R* N6 }/ T5 _25
3 s; B/ d5 W n/ V. s% a. T26
2 r# C/ _! p6 Z8 t0 I27
1 `+ O0 }9 J. f28
8 ~4 \3 P4 p0 b+ `% U29
3 Y( Z4 ~1 e, |8 c 在这些属性中,经常使用的是dayofweek,它返回了周中的星期情况,周一为0、周二为1,以此类推。此外,还可以通过month_name, day_name返回英文的月名和星期名,注意它们是方法而不是属性:" X& z& b+ Z+ R
9 p0 E& ]* Q8 M5 o# ~# ?: @) H
s.dt.dayofweek
9 E( e$ U/ N9 v7 c$ i4 ~3 YOut[37]:
* ]9 v& R, w: J; X. a( k) ^$ R* f0 2
5 @4 g! }$ i% U1 3
- L2 R* z+ e0 Z. q! D# S2 4. y1 I+ ^! `0 i& d5 Y
dtype: int64
) r- ^0 O8 Z; Q8 [+ o' x! T) |2 b1 x' I8 c& Q8 o# J" E2 D/ Z# r) P5 L
s.dt.month_name()+ L& @. g! x0 M3 @
Out[38]:
: \6 E: C: m9 } c6 [4 c0 January7 V$ y0 @* }" U* a
1 January
) `9 B/ w5 `; H7 V2 January9 P) k( v3 ?7 r6 }& D
dtype: object
U8 v& a" T" l# R% w9 K1 o* P3 S" b0 c. v' V8 l4 L% L# [
s.dt.day_name()
2 ], A* z4 P& X& o% `$ y' V$ N+ POut[39]:
; `& g; t+ y2 a$ K' ]5 \, ]0 Wednesday: [- v" i/ Z: J' K$ o2 U9 [0 @$ U5 G
1 Thursday' e0 z) P/ ~- S
2 Friday
7 _7 d( P! o' ^( o, Udtype: object
) x2 {) i; G6 T, E$ i# W. ?
' S) G, J, L: o0 R1
4 N3 _% _: O, v$ b8 \( `8 W% R2
2 X1 `7 Q; _2 o8 x, q: t, x& D; O0 ?3; D/ ^% t6 U$ k' a- [: T, {0 E5 }
41 s' w5 W# Q" p( ^7 l
5
2 M# C/ c0 Z: n; G. T6+ e- G% Z) G* M, u
78 T7 f7 i# ^! U! p' }% q+ p
8
. w4 D1 [. e) `5 }6 i7 j# u9: A+ W' F+ k* B/ |1 T$ c
10
. l+ S6 @- i7 g2 Y7 t: |1 L11& i+ R5 G9 U4 y
12
; H8 q) {6 b" @* P1 G+ p ?8 X13
+ b9 N. d. |7 }* i14
_# u4 |1 T# b. W' d2 \" g' `15# b$ W: x; _# j/ M) W u
16* ^) ^) _! Z9 Q: B( l. [2 z
17! r3 I( K$ g+ ^* S1 F
18- {" K1 Y! k) p& m, A
19& [ V% w$ f! L0 C6 m* U
20, ~8 [' f& m! J4 u2 H* t
第二类判断操作主要用于测试是否为月/季/年的第一天或者最后一天:$ ~" U. q( h0 L) H
s.dt.is_year_start # 还可选 is_quarter/month_start
' G7 R+ Y. m5 u4 x% k! ~, aOut[40]:
1 c8 `* Y$ N; ]5 G& o0 True) n& l& M# ?2 B! Y* a' J3 @6 W
1 False: w9 o+ ]9 M9 U3 r" q! Z
2 False; o/ M5 Z: A/ `1 k
dtype: bool% R! ~1 P4 Z5 L7 @ G) [1 y1 S
. }- ]' H& z' e3 [s.dt.is_year_end # 还可选 is_quarter/month_end
3 s2 f1 A+ \2 U0 A" D$ M; ]Out[41]:
3 F3 l9 i) z! o& k# r- p' O& i! f0 False
" s! d$ I% O2 p* @- i& e$ `5 K2 E1 False' j% z, e4 \' ]+ K! |" }. g$ {3 K
2 False# u; Z% q: @1 h* k S1 s; D
dtype: bool" A9 p& g3 R! r
1
. c" Y% _4 t7 Y: @+ G# A0 v2
: y% b! {/ {+ f( g8 H% [37 t, D& X3 r0 y8 n1 Y+ f6 d
4- O+ `& h! x: J. [ p! ~ u* X
5
8 E! {+ b! }8 Y. \# F60 }/ B9 a: L1 c6 ^
7
9 }9 [# o& I. X9 |9 s8, S6 r2 G9 `$ T5 Z9 _/ S L
9# k& p. E+ A4 \6 |% ` Z$ `, e0 O
101 O, [9 k5 ~9 m. a* s Q9 _ v: o
11
6 r! {: Y$ M$ z4 q8 `3 G12" a# U& V; W: h. G: O
13
. F2 z% t* M" Q/ ?第三类的取整操作包含round, ceil, floor,它们的公共参数为freq,常用的包括H, min, S(小时、分钟、秒),所有可选的freq可参考此处。/ h: n K( ]3 O! R4 `, V( A
s = pd.Series(pd.date_range('2020-1-1 20:35:00',
. B; Z/ `0 H8 Y! l '2020-1-1 22:35:00',
) {4 i$ T, X* V9 C7 y# y6 o* c freq='45min'))
) D+ n+ _% \9 {' _* l$ z4 m- H6 i' _" p' U' u( r
' |% A m) a$ {2 U$ O: k( G+ ts
& U: |, W- k2 Q' TOut[43]:
2 e4 e* w" g) I0 2020-01-01 20:35:000 t2 _" R \" l1 H) y
1 2020-01-01 21:20:00# t* F$ F$ s6 M
2 2020-01-01 22:05:00
3 E+ U" y! q& `8 f, H7 Xdtype: datetime64[ns]
6 v* d9 H' D; t9 v2 h! e& ^1 C8 t2 J, @
s.dt.round('1H')
& n% o4 Z. L- M5 N+ ]4 pOut[44]:
" r, g' I4 ^3 f: [/ x- e0 2020-01-01 21:00:005 ?- J6 N w- K9 O- Y& h! [
1 2020-01-01 21:00:001 W3 y) n2 d" F7 V1 O
2 2020-01-01 22:00:00! `( u! o- A* H' W
dtype: datetime64[ns]$ R8 ?. S q# Q" a" N2 ?
5 I/ e2 }8 d m- J. h. F
s.dt.ceil('1H'). R8 d+ ^6 |/ {0 g8 r
Out[45]: ! q, Z/ U" r+ [, w! M
0 2020-01-01 21:00:00
e% y3 z' i+ A+ n, u6 ^; j: b1 2020-01-01 22:00:00
* I& @& B0 x% M6 b- n9 |- t2 2020-01-01 23:00:00
2 @9 j; W2 g! T' l. ]+ s6 Udtype: datetime64[ns]
. O) V4 ~3 M, g
/ h) J+ v/ b& t3 V! gs.dt.floor('1H')
- Z! w+ e) [. m% s% G, AOut[46]: $ d" J# b+ C7 ^: T, F! \, C
0 2020-01-01 20:00:00 Q# ?/ t* s7 T
1 2020-01-01 21:00:00% M- s* n( q- L [* c* F
2 2020-01-01 22:00:00
2 I( q+ Y/ ?% G. udtype: datetime64[ns], r. ^6 U5 v1 n3 ]% I
/ K3 v, A. Y& A5 Y3 j
1
/ M1 P# I# e9 X# O& t: a2- q m" B( K" M5 r
3" R/ q2 B( }, J/ V
47 y: s2 T$ u. r
5
- a8 ~! h6 a2 e/ j; A" X$ t63 p+ s; }* C8 O1 T
7
8 b- B0 w) B) H( Z$ S9 N8
?3 G; t* G' Q1 H2 |. B9
1 U; B7 Q6 Z& C8 }: P. H( N10+ h: [/ E" q' E$ r% ?4 M
11
2 R4 g0 p6 Z* Z: `: Y) a12- \! |5 {2 [) I
137 v: k+ m+ V+ X( T! t
14 U6 Q) E; S) t" D9 i7 N7 ]0 Y# k$ K
15
k6 l. ?: u* t9 C( p" `16; t5 S8 y W- M& x+ t/ ~4 t H
17
( ~, f8 S$ a2 `$ ]18
* X- N0 F( \9 a& @" P) L19
2 P+ c7 t- Q5 v$ g1 q1 F20
# R: Y$ {2 {4 l' s21
( |! k& N6 K. B, I$ ^22, n+ |- `* w% L" Z8 V
235 w/ V% X& p/ B: a; x% n
249 I2 c( Y# g/ [$ {9 U) F5 J
25
- _3 q! `( ~' ?/ I& ^26
5 r/ @- z' W0 p- K/ [1 z' e27
- |# a' u8 P% |' \. s5 \( K7 k V28
i* v* N$ h0 V- r: b29, U9 W2 i' |1 M4 d ~
30
' n# p: t$ q' D2 \# O31
' U$ J' U" |" t322 v$ N! `) c( ]4 W5 {6 g
10.2.4 时间戳的切片与索引
" d8 \1 l1 ~+ h } 一般而言,时间戳序列作为索引使用。如果想要选出某个子时间戳序列,有两种方法:% J5 l$ r v* e+ h
G; g. i$ t/ O) Z7 Q6 d) i9 f
利用dt对象和布尔条件联合使用9 g( k9 U2 c3 ~% S" q
利用切片,后者常用于连续时间戳。- C" b, w m8 b
s = pd.Series(np.random.randint(2,size=366), index=pd.date_range('2020-01-01','2020-12-31'))" ~% L; ^1 j+ r
idx = pd.Series(s.index).dt7 x4 U5 \3 _; F9 g+ V1 `
s.head()
* {* N6 @: H% \8 b2 F' `3 s) q* e3 h2 L: M: j7 n: v9 q1 \
2020-01-01 0
7 M. K2 V* `% V, v2020-01-02 1
- o/ a/ `. Q- {# E' i+ m2020-01-03 1
4 I) i. g- T" c& ^4 p2020-01-04 0
/ W4 D4 V/ O, d n* V0 v6 ^2 o2020-01-05 06 `6 r9 L+ {! v
Freq: D, dtype: int32% w9 y4 }$ |. F7 ^
1
8 b- k7 r: \" c G# n. P6 h1 Z2* S2 Y6 O% @+ n6 x8 K
3
: i& I0 w6 v8 u2 ^, |4
, h" U# m: ]/ C0 a8 o4 q% p2 B2 M5$ P9 K. j5 ~" U- G# O
6
* |9 q3 C2 u: t( e# X4 T73 W. X' ^/ G; y5 _9 v
88 r8 [' z8 r- i0 a; h- s" ]
94 ]4 \* t, g! b0 G7 K% J0 E
10
7 D' x' ~, N, r5 d" C: ]6 }Example1:每月的第一天或者最后一天) ~0 `$ P! p2 o# @4 M/ e% C
! h8 p+ K+ `! R: F O' C8 ys[(idx.is_month_start|idx.is_month_end).values].head() # 必须要写.values
% G8 K5 y; F+ K+ C% N" \; d% POut[50]: 3 b- s* D9 o7 ~6 T! p/ j/ Y
2020-01-01 1) b$ i+ Z+ l8 v( H ?) P1 ]
2020-01-31 0
; \ D: p' X" l' o& U2020-02-01 1
$ }7 q% Y/ C4 L2020-02-29 1
6 s3 f5 ^- ^* k" K2020-03-01 0
# V6 B8 S% t1 L0 K# P- Ddtype: int32, }1 P5 f6 V3 _# m+ g
1" R7 t7 I% w1 G9 ~4 N
2" a, ]$ }# q- v. H
3
: l, g. {8 G% D @2 w43 ^. B, c/ W" N; R
5
7 P6 \5 C1 a* ^; r& U6 t% B6* Q' o& B; Q# S& N; E
79 K8 W. z2 Z+ t- ~" s/ Z! p! }
8
, ~. x) e5 m- d3 @3 \- EExample2:双休日
) U7 h/ F' D7 g4 |8 r$ f5 A! \) F9 | f6 [3 ^
s[idx.dayofweek.isin([5,6]).values].head()
, C& D0 L, b9 [# LOut[51]: " b' z; s$ n3 k2 l
2020-01-04 1' s. R! D; r9 Y' z- K, s
2020-01-05 0
& G3 Q3 }- X# r# T# `' z5 b; q& P0 W2 X2020-01-11 0! h# R4 T3 c% e# P0 W' Y8 P
2020-01-12 1
' E$ J9 A5 X9 H. c/ Y2020-01-18 1" b2 t# y/ V0 a1 X5 B7 ]1 a
dtype: int32
# T4 L1 C8 W2 D2 b5 ]& S1
* W$ X5 A- i2 ^) T4 Y) f b: Q* g' q, n28 o* u( F' r8 P* V% `
3
c: n4 r- \3 k$ f7 h! ?/ J5 F4
& J4 L! X0 M: e" \; x0 _4 x5
- [# `! X, T: X+ L" W6, A" e9 {- _( g! j
7! A+ K* m9 k ~# X
8
) c8 ]5 C* R4 c- y, n$ mExample3:取出单日值5 T% S- Y; b% B$ `+ z2 b `
+ B2 g- y/ {. B" G& u4 Hs['2020-01-01']5 Z- j, z9 B' K
Out[52]: 1
7 Y# `7 E, J& b2 r8 ~) z5 z7 W
$ ]- U4 |6 Q3 F8 Hs['20200101'] # 自动转换标准格式
1 Y; E; ~# u* v4 H: ~8 ^0 ~! w3 ROut[53]: 1$ {& g: V( M, w* B* p3 R! ?
1
0 H- r2 N4 F9 C4 X& v% G' @- A; y2$ r3 |# d C3 x _7 r' }: T- \
3$ c- Q" }, \6 d+ d) j/ V
4+ Z1 Q% K; N/ e6 c
53 H- a# M I0 F: ?/ y
Example4:取出七月6 U9 s' I) x: Z( U
8 q8 y1 E! } J# M' B
s['2020-07'].head()# d" ]! f+ I. M& o( ?
Out[54]: 1 [! E% }; o, V. |3 {
2020-07-01 0
( }: J! j% z& g/ P2 _0 [7 v! t" T2020-07-02 1, n. B4 @! B$ q6 X j5 `' m
2020-07-03 0! Z6 _# \$ m1 Y7 m3 X4 |, b
2020-07-04 0
) O- P' V+ {* I% y: u; @2020-07-05 00 d) v, p' v9 e3 h' _* N! u
Freq: D, dtype: int32. [6 j, v' L5 Z0 b$ c/ r
1
9 }) I! @* E: f5 w, `9 {; ]2
o, L4 ?% B/ j3# I; V3 ~( y. q+ ^2 I) r. R
4
# \( d' ?0 S) }5' J, J3 F0 _4 h: O
6# S$ o5 _2 Q6 e) Q% ]) F2 }# s
7
" p9 h" ]2 S7 U% N( l8
/ G" C; y$ Q+ o7 [* q! o sExample5:取出5月初至7月15日4 P3 B1 J0 g7 e0 g# h9 O* D
/ a2 ^/ D( G# B% d5 {' Y
s['2020-05':'2020-7-15'].head()
4 H- @8 ~. ^; G& x. a: g% ~Out[55]: ' g" l e- V/ H1 x$ u9 F$ ^
2020-05-01 0
& M. v. z$ i; y: |3 L$ g2020-05-02 1$ e) X) A6 J5 K" j0 l) g H
2020-05-03 0 r! A8 E9 g$ G- |
2020-05-04 1/ ?1 u8 H/ n% _0 C6 \' O
2020-05-05 1' R. J( k0 }' z4 ^
Freq: D, dtype: int32
' N4 V" B8 T5 B1 z
P7 b/ N2 L+ s6 A1 \s['2020-05':'2020-7-15'].tail()9 Y8 A/ W% G l6 x) Y t5 t
Out[56]: 0 A* h1 n G5 G: u+ z# \
2020-07-11 0+ d, l7 P+ L* j6 p" ^6 ]% H! t2 c
2020-07-12 09 H7 P$ U9 |6 R0 Z/ a+ ?
2020-07-13 13 d# ?% e e7 W( ]) H/ d
2020-07-14 0+ m7 l, z% v; b& z; N" R' N+ n4 z) _
2020-07-15 1
: F* X8 A* A4 i8 q7 y+ \Freq: D, dtype: int32
# \/ Y# T# @( h, B, p0 m( Y, w$ @1 m5 B+ M
1
0 h n1 z W/ z3 f1 \( u9 y9 u* j1 t26 B$ J5 y7 w( z7 d; s+ ]& w* J
3
' g2 c2 ~. U) {" c& j42 g% r5 [' i" ^3 [4 D/ G
5
5 e) n, _- S9 P+ O) H8 I" r65 c) a% [; i7 m) a& G
7* t) g! }* N- a# G- x
8
* L: ~7 ]. H6 }& P1 Q9: L8 f* G4 {* o" U
10
1 [) Q) c2 p" p$ z; y116 {" S! ]# c% ?
12' C6 \. D1 t& Z% ] f
13
6 p! p' Y& r6 f. i0 @; ?14" ~+ {7 ^, m- V X
153 l, a) Q0 \/ r; Z+ {
16
( h- g5 O3 q, k7 T& V7 B) }17; K9 }* i. w# y. l8 H) u, e: ~
10.3 时间差2 ]* [- A$ _! @9 u. s) F9 J
10.3.1 Timedelta的生成
% e/ g; ~1 v9 apandas.Timedelta(value=<object object>, unit=None, **kwargs)8 ^: E4 [0 W! n. \1 P/ S" [
unit:字符串格式,默认 ‘ns’。如果输入是整数,则表示输入的单位。
8 j* @5 ]3 F* F 可能的值有:% `6 s# W' s" Z0 G: i7 Z
/ O6 n! b; V' r8 Z1 @! |
‘W’, ‘D’, ‘T’, ‘S’, ‘L’, ‘U’, or ‘N’7 u; Q4 m: |* D, x+ Q8 T
‘days’ or ‘day’
I/ G5 I" g9 t0 f# J‘hours’, ‘hour’, ‘hr’, or ‘h’
. h& H0 ^' F3 X! l6 x5 S) Z‘minutes’, ‘minute’, ‘min’, or ‘m’
1 s- x$ G) e- x8 p& F‘seconds’, ‘second’, or ‘sec’
. X+ j, i5 k$ c. q) }& T3 Y8 y; V7 I毫秒‘milliseconds’, ‘millisecond’, ‘millis’, or ‘milli’2 I2 l5 L: ^7 d$ |9 |
微秒‘microseconds’, ‘microsecond’, ‘micros’, or ‘micro’; h. W7 e6 ?: d2 \6 @
纳秒 ‘nanoseconds’, ‘nanosecond’, ‘nanos’, ‘nano’, or ‘ns’.; `* L9 D2 I* S7 o% R8 w
时间差可以理解为两个时间戳的差,可以通过pd.Timedelta来构造:
* F" b/ L+ E" V* A( fpd.Timestamp('20200102 08:00:00')-pd.Timestamp('20200101 07:35:00')
6 k$ S* |5 U$ x- VOut[57]: Timedelta('1 days 00:25:00')# [( D+ Z1 n7 x4 p
- i6 w- n* o* r3 R. {/ zpd.Timedelta(days=1, minutes=25) # 需要注意加s
& ? _/ f2 W3 k7 Y; Y( r Z/ fOut[58]: Timedelta('1 days 00:25:00')
7 C8 W6 \5 a$ l' E, a9 _% _% u, V4 T/ G+ |! Y( u% v- i* H* R, ^6 w6 H
pd.Timedelta('1 days 25 minutes') # 字符串生成
% g6 ~/ Y: f% ]) V0 LOut[59]: Timedelta('1 days 00:25:00')8 R1 q3 N# n# M& R* A# }
/ i$ B; G/ X3 m' B' z M# gpd.Timedelta(1, "d")
. L8 f6 C& e8 }; t. MOut[58]: Timedelta('1 days 00:00:00')
' t# y V$ l8 b0 x: g3 v6 L; O+ D13 _; K9 B- S# S; Y/ E6 R
2
) i; ~$ E3 P8 g& ~3; v u7 P) M* h$ z1 s
4
# ~" M' U4 f0 p' m" z54 b$ I0 x1 ~" Q- X4 o8 A
6
8 W6 D8 B2 T; |; x7
7 a& H5 {" q7 e4 c3 Q: }8
2 `; M8 m# A0 K$ M* Z9
- l$ o, j* X5 Z/ }10
7 H7 _5 B8 Z/ D) z6 M& a11
4 m& ^( i1 j& @$ s) z/ t7 F; x生成时间差序列的主要方式是 pd.to_timedelta ,其类型为 timedelta64[ns] :
1 r/ f- f( A( D# n8 y- H2 T; _. \s = pd.to_timedelta(df.Time_Record)' x* z, M8 { r7 ]
3 e0 X9 G$ j. j/ W/ h( g4 I) ls.head()
7 ~9 S3 U9 a% r: yOut[61]:
2 r. M3 _* x0 v1 ?0 0 days 00:04:34: ^) w# v% N: Y, M; B- N f
1 0 days 00:04:20+ V1 c5 Q' _, X. E! \3 Y1 E, ?
2 0 days 00:05:22
! w4 s+ G0 |0 O; i' A3 0 days 00:04:08
: _0 F( i; O, Y8 K4 0 days 00:05:22
C6 J& M4 T, ~& T& A4 jName: Time_Record, dtype: timedelta64[ns]
( S8 J( ^1 x( U |, B, B1- Y. [# Y/ S) f2 r& W, `, |
2" E ^, W6 A5 l2 ]4 r3 S
3- A. ^( T/ f, L B* r! ]
4
- P% d+ m( A8 e& ~: l( b5 e+ f5
6 }5 w/ E1 a1 {: w64 h4 f1 B! |0 {1 W |3 P# q
7
/ J5 {: I: d5 Z z4 K: R9 k$ v8
- @* u4 K) w# s Q* M9" V D4 X8 T- S% a5 {2 ~# U
10
7 }2 o- Z7 _2 X2 d与date_range一样,时间差序列也可以用timedelta_range来生成,它们两者具有一致的参数:% E" |1 x) B, B$ l
pd.timedelta_range('0s', '1000s', freq='6min')) ~7 [' R% k7 k
Out[62]: TimedeltaIndex(['0 days 00:00:00', '0 days 00:06:00', '0 days 00:12:00'], dtype='timedelta64[ns]', freq='6T')
5 ~5 d& k9 J+ {3 ?& G3 `
/ K R% {1 e! y* {' t* ipd.timedelta_range('0s', '1000s', periods=3)- M# l2 U' l+ q' _- w
Out[63]: TimedeltaIndex(['0 days 00:00:00', '0 days 00:08:20', '0 days 00:16:40'], dtype='timedelta64[ns]', freq=None)
/ |2 N, r: O8 Q) R/ P1
% E4 m. K* J1 ^7 p g9 U2 s2: {( u1 ^* o0 A; E4 h& ~+ w
36 c( w+ @. ^/ V) ~" I( j
4* [7 [5 k6 X2 a* s& V0 w' P
5
' I8 J, g% `3 s' ?& F: J% N- o# m& O3 w对于Timedelta序列,同样也定义了dt对象,上面主要定义了的属性包括days, seconds, mircroseconds(毫秒), nanoseconds(纳秒),它们分别返回了对应的时间差特征。需要注意的是,这里的seconds不是指单纯的秒,而是对天数取余后剩余的秒数:5 L4 B( m2 d6 r @: l# R
s.dt.seconds.head()6 V7 j+ r2 z3 U
Out[64]:
4 i6 O$ q5 |9 L0 G+ V8 R! i0 274
: v- \2 ~ t1 Q( L: m J6 L6 e& j4 V1 260
- Y6 `' N M, q; `2 322
; H% c9 C) W/ B! Z% r: m3 2480 i' h. V; V7 T4 r1 Q$ j! [
4 322
+ ]" X, l" F$ X! A) RName: Time_Record, dtype: int64. M$ y7 ]; }2 ]1 S' z0 a
1
2 c5 j( p! U8 ^$ J% V; B" R* m2
6 s$ B8 y% L& a; ]+ D( Y3 r) S3
3 H! L( C; C( u2 D. O, W4
4 j5 {2 G0 O. O; \5
9 X# R) x' m2 M7 i% v, B, J9 f6 F66 x4 h6 ^: p8 C+ c. J
7
2 O& i1 T& |8 G( j& E* @7 H3 \1 L8
. e2 B; d" Z) t3 v8 Q" E, ?0 ^如果不想对天数取余而直接对应秒数,可以使用total_seconds X( ~! N6 B" I9 z% ]( x- c2 J% x
( c) R: g% J5 v7 B6 d7 [s.dt.total_seconds().head()9 D M1 i' l# \
Out[65]:
8 ^, q! n% |, c4 I0 274.0
7 L/ A) l* k* p* R! Z1 l5 r1 260.0
5 T5 Q- b2 g/ `6 _2 322.0
0 p, `2 J W9 k$ p+ C# z" y- I" o& f3 248.04 E3 B J; L- b$ {; b' D5 j
4 322.0% z0 ~; j, {, ?) w
Name: Time_Record, dtype: float64
+ @" o1 K# e/ g$ h1
2 a S) ` c1 Z& U2: w. S/ ?$ p+ {. ^& L
3
9 t7 V8 t6 Z: X. q. @! n- x4+ R! y- g; M. d
59 V6 F- e$ Z# k" w; \5 v$ R& S
62 U4 A% c% P4 L/ Y f" u2 x
7
; C* d) Q6 B/ o8" ?# C; F8 i( p1 p1 e! N4 d
与时间戳序列类似,取整函数也是可以在dt对象上使用的:' d. |1 ^* C2 X( d
9 m& p) L. {: ^* e0 S0 a9 S1 B. Ypd.to_timedelta(df.Time_Record).dt.round('min').head()
1 V, p J# }; k" l* D, zOut[66]:
% C0 C* J' r$ ?- j, o# c0 0 days 00:05:00
" f( x) T; F- P7 f% y- [4 O1 0 days 00:04:00
) Y& d; _' V1 H" J w3 Y2 0 days 00:05:003 b% q7 v6 Q8 k P
3 0 days 00:04:00
0 a. c, ^% [1 s6 ]0 x/ ~1 W4 0 days 00:05:00
3 E3 C9 Z# c! rName: Time_Record, dtype: timedelta64[ns]
6 F3 ~, t, j1 K2 S' P1
* p3 k' ?/ ]) v* k( m8 C2
6 U/ V- z% ]' H* C: `3 {3* N; L [6 y' ~! m- i( s8 ~
4( m3 U6 c0 s+ b/ a1 q$ w
5: v0 O7 \9 v M8 c5 x# ^- X
64 G: i% B I/ u j' r; u% [" V
7( @1 i8 W' ~8 W* m; v0 z
82 P" E5 ^3 o3 g: R0 o
10.2.2 Timedelta的运算; p A) |8 l% v: a1 G- u
单个时间差的常用运算,有三类:与标量的乘法运算、与时间戳的加减法运算、与时间差的加减法与除法运算:# Q+ e' W8 Y# u. K; f7 o6 ^. R
td1 = pd.Timedelta(days=1)1 l% v3 t& m) s: i5 N/ H. I* R
td2 = pd.Timedelta(days=3)2 A4 ?9 J5 O/ x. F
ts = pd.Timestamp('20200101')8 r- A* @+ q7 K/ R* [3 J1 Y0 c
" E" h4 x0 j, {2 Htd1 * 2* E6 j' A: m8 G1 h2 J/ s( e
Out[70]: Timedelta('2 days 00:00:00')
$ K* D4 n3 _' I2 Z& D" f6 R6 {' x# r: q% {- y- H5 `' Y5 s7 Z
td2 - td1
# r( o9 w. \ }" dOut[71]: Timedelta('2 days 00:00:00')& @& j: V& {( C& ?6 h7 W/ X' O
& g) r; A# } v; M4 |2 ?
ts + td1
: f+ m' d$ f5 n( U: p/ v. a9 D- EOut[72]: Timestamp('2020-01-02 00:00:00')# p6 N3 E6 l- b7 O9 ^
) v1 f$ I& E! vts - td1
/ K" _# v! O& m9 H# wOut[73]: Timestamp('2019-12-31 00:00:00')
5 B+ V3 E1 T$ l2 _- y! g1
" K$ g2 L0 M% \6 H" [" N4 v2
6 u) Z- ]) |5 l" {9 L39 p4 g+ Y9 A' j3 y- V+ \! I. G
4
0 J. t. c1 F6 _' b5- b2 k3 c' R& k2 R$ z% \' U+ n
63 Q. h+ v* P$ h6 d
7
( V3 P/ T }7 h2 ^) r$ J( U8
) P/ |: Y( L8 h! ~5 J- c) K9
9 U/ f8 d$ z! A4 }' \106 ~- ?- B y' G: D
119 f) x- e4 s" G @' A
128 R, D! D7 f2 t/ \
13
2 s$ A7 G/ g h1 [# Y* p4 e142 @( a- a" W1 I. ^2 ]; b
156 L; D- _* y0 p3 @ E: A
时间差的序列的运算,和上面方法相同:
0 {* K8 A3 [# r8 ]# ptd1 = pd.timedelta_range(start='1 days', periods=5)
1 |; V7 }9 [4 q8 ftd2 = pd.timedelta_range(start='12 hours',
( p- g1 b }. _- R6 }' f0 W freq='2H',
2 r0 d6 l2 X3 f; E( t periods=5)
7 F" T1 h7 @ L) |8 ~3 _ts = pd.date_range('20200101', '20200105')4 [' n6 h8 B8 ]6 J0 u; o9 [
td1,td2,ts
4 d, N8 v. r- e# l! ^# u: l H) D' \. |0 g! A7 \1 N
TimedeltaIndex(['1 days', '2 days', '3 days', '4 days', '5 days'], dtype='timedelta64[ns]', freq='D')
+ s# e7 z; ~: T% n/ u1 cTimedeltaIndex(['0 days 12:00:00', '0 days 14:00:00', '0 days 16:00:00',
4 D" k" i7 O! D '0 days 18:00:00', '0 days 20:00:00'], dtype='timedelta64[ns]', freq='2H')
- v! C( ?( |. ~5 v% zDatetimeIndex(['2020-01-01', '2020-01-02', '2020-01-03', '2020-01-04',
4 x/ P& T2 h7 @% B! Z '2020-01-05'],) X6 n) l5 E2 A) O: V. {6 R
dtype='datetime64[ns]', freq='D')
% E5 \- j% Y( w; G" t# j1
$ \% D. l* e4 W4 P" @2, t+ z: u, ^1 `* Y/ E
3
( z) N, J2 z. S6 W+ F0 Y4
* P9 Y+ {4 R) j54 i, i( F! A& w
67 }6 c* _* ` n) O; c) r5 Y! r) Y! s
7& y( m7 Y; Z" I2 I
8
/ U: M) f3 ]1 d$ }+ v% q0 s7 q9/ d# {( y9 t! T/ y: b
10+ a! y) ~% y3 S! w
11$ {! Z! {; K" Y3 t/ `/ D6 o
12
1 v, l9 L9 l7 S13
# v/ |2 V3 m1 c$ Ztd1 * 5" s$ D6 k! N$ |; E9 z
Out[77]: TimedeltaIndex(['5 days', '10 days', '15 days', '20 days', '25 days'], dtype='timedelta64[ns]', freq='5D')! `5 o( F, R/ o
0 Z' S Q9 G, z9 G
td1 * pd.Series(list(range(5))) # 逐个相乘
# g. j. X! A) d& Q, P$ KOut[78]: 3 A$ e$ |; H0 T0 U
0 0 days
+ t, Q4 B* r2 ] f# w4 O1 2 days
) i# V' e z D# s5 z2 6 days
; _& A: b+ q, t. S3 12 days
/ q4 O& B( ` O7 m4 20 days
+ u* c( w& e6 idtype: timedelta64[ns]4 I4 s! d {8 ^! S, r4 a" C, e' ]
7 P/ O/ o5 j- L) m0 y6 N
td1 - td2
' I" U [6 M5 L" tOut[79]:
- {* f! `& M( l1 F4 S/ `TimedeltaIndex(['0 days 12:00:00', '1 days 10:00:00', '2 days 08:00:00',) z1 M, F/ u( G. A6 h# e
'3 days 06:00:00', '4 days 04:00:00'], q6 U3 l* w4 G' Q, h
dtype='timedelta64[ns]', freq=None)
, t. j4 c# ]/ q* }4 {: r. n6 k. W7 E- r$ X( L \9 K9 ?
td1 + pd.Timestamp('20200101')( _# h! a7 t1 E
Out[80]:
% o. P ]- c6 ^5 `& |" o: L$ U; {DatetimeIndex(['2020-01-02', '2020-01-03', '2020-01-04', '2020-01-05',
8 v ^+ x6 r- C. B8 ~ '2020-01-06'],dtype='datetime64[ns]', freq='D')
5 O/ O: N+ l- C0 ?7 v! u# [5 M; l8 p- x
td1 + ts # 逐个相加
9 t! `! s7 R! B& l0 yOut[81]:
1 n1 x4 d' G' t) q% d l4 ?DatetimeIndex(['2020-01-02', '2020-01-04', '2020-01-06', '2020-01-08',
1 j1 h0 ?4 }; e# H '2020-01-10'],+ g$ e, R/ c6 Z3 _7 a* M
dtype='datetime64[ns]', freq=None)
1 R1 O0 h% l* ~- z6 k! [+ P7 z& e3 _: C6 T/ l! O2 U3 Y
13 X$ o3 F7 v# ?; V* \) {6 X
21 J! x* p3 p7 W: {1 I9 ^
3- [# i5 @/ `: Z! @( x3 f
4+ G. T( c* K; t W
55 z; n2 P' \% e( W8 n
6! B5 I8 X8 v$ {3 C
7' K3 v; E' u) }6 ^/ T. B3 f" L% w" Z) Z
8
' Y5 R S) E ?9
Z; J: m" F, u) J10. v# H- Y' d8 e+ U6 {. A+ o+ U
114 z1 c8 Y8 ~0 i" F/ C
12
& t- [& R% E4 `13: A+ s) N- [0 W8 k! Y' K, E
14
" [* g6 Z. Q5 f; V+ R* C) e7 }5 A$ `15 c# O8 Z* t1 u/ o& V2 n
16
( f! J. I) r, b! c* A17% r' D5 M" A X+ v% S- x, P
188 e$ p9 Z% ]! X+ [; h& S3 J
19# m* y+ i5 i% j7 X
203 g' A: j$ \9 C+ X
21
8 j* K9 S9 R- }5 n225 ]# w) ]! Z1 u
23" X {3 y1 H7 J/ U
24
' f# A$ h! l! T6 P0 _25
7 P0 ?4 z* R- ~+ _7 }26
, N6 _& p; ?1 I% i' D27
* _# X- Y/ o1 s" @$ z. z28
7 i& _9 Q+ L1 ?3 @% N9 F10.4 日期偏置/ Z1 \) a' y! P6 l
10.4.1 Offset对象" ?8 V+ \: V# ^3 B" ~, G! A
日期偏置是一种和日历相关的特殊时间差,例如回到第一节中的两个问题:如何求2020年9月第一个周一的日期,以及如何求2020年9月7日后的第30个工作日是哪一天。/ q* x( x7 y, |1 j4 L5 ?
8 ]7 N$ g/ }3 V8 X' T% O/ k
DateOffset 类有10个属性,假设s=pd.offsets.WeekOfMonth(week=0,weekday=0),则:# N% I3 _" v* K
. g! y1 w; Y1 n( c
s.base:<WeekOfMonth: week=0, weekday=0>,返回 n=1 且所有其他属性一样的副本. N, @: `9 Y. X A9 }
s.kwds:{‘week’: 0, ‘weekday’: 0}
- T' ]) q- O) P" T4 p* t, Ds.wek/s.weekday:顾名思义) t$ T4 X8 A3 u: |" n
有14个方法,包括:: D& @( }* ?% h5 a6 B2 \
8 ?7 T% P* H7 K3 n2 iDateOffset.is_month_start、DateOffset.is_month_end、DateOffset.is_quarter_start、DateOffset.is_quarter_end、DateOffset.is_year_start、DateOffset.is_year_end等等。
?3 m; S1 E% s8 cpandas.tseries.offsets.WeekOfMonth(week,weekday):描述每月的日期,例如“每月第二周的星期二”。9 h7 [# S+ ?2 ^$ w' w$ x
) H+ x1 T2 O7 D D2 |
有两个参数:
1 b: }. o& ?9 f. M, Tweek:整型,表示一个月的第几周。例如 0 是一个月的第 1 周,1 是第 2 周,以此类推。% f8 b8 A y: k5 g
weekday:整型,取值为[0,1,…6],表示周一到周日,默认取值为0(星期一)
" u! H1 u3 T+ x- F8 n8 Gpandas.tseries.offsets.BusinessDay(n):相当于pd.offsets.BDay(n),DateOffset 子类,表示可能的 n 个工作日。
- y3 @ s( F# e/ o
" M8 `! H) Y4 }2 x A/ i6 ~pd.Timestamp('20200831') + pd.offsets.WeekOfMonth(week=0,weekday=0); S+ C$ m8 v4 F( s7 g8 A* T8 m
Out[82]: Timestamp('2020-09-07 00:00:00')
7 u" i2 _3 t# R7 P! e1 K4 ]2 u j$ N* g1 z1 Q8 u
pd.Timestamp('20200907') + pd.offsets.BDay(30)
5 u8 `) ?4 E9 C4 G+ E: F2 m) yOut[83]: Timestamp('2020-10-19 00:00:00')2 w# B$ t' D( H
1
1 g: ~5 D" W" Y& E% _2
' B1 W2 `$ ?3 m) _# q3
/ O2 h2 r" b( `% h9 ~ g* y X40 z/ `& ]$ z% ]5 f1 q; h
5- u6 T3 L8 j2 e) @) E: @4 a
从上面的例子中可以看到,Offset对象在pd.offsets中被定义。当使用+时获取离其最近的下一个日期,当使用-时获取离其最近的上一个日期:2 h: x2 p. N. ^6 w }8 Q2 X! v0 O
& l3 n N: m, l2 m/ e4 T' Zpd.Timestamp('20200831') - pd.offsets.WeekOfMonth(week=0,weekday=0)4 ^( j9 Y, B* `* @! @
Out[84]: Timestamp('2020-08-03 00:00:00')8 y; ^4 y# i6 u1 h! P
# _1 [8 T& K/ m5 A6 u4 ]2 w( G
pd.Timestamp('20200907') - pd.offsets.BDay(30)
7 D4 z* M, C, v; v/ p" V/ V2 sOut[85]: Timestamp('2020-07-27 00:00:00')) N. c, t3 [1 s* Y" l9 h2 n, V" A
9 y" N4 h" ?# I: h- R, dpd.Timestamp('20200907') + pd.offsets.MonthEnd(); F; B Y* U* @) [% `
Out[86]: Timestamp('2020-09-30 00:00:00')% J; l2 ^ S7 Z3 v: I+ `
1
' t: {( a9 O+ }' C( J( |23 u H$ @7 ~$ G! y2 K
36 Z" J" O& w7 t; s7 t$ v& d
43 G1 {1 x; r6 `% d
5
3 [" u8 m9 C0 ^* z5 O6
; w( p$ P) r% y) I# z/ r7; ?- ^4 R$ N/ N1 [0 m6 }
8* s1 `1 G/ z+ F) D
常用的日期偏置如下可以查阅这里的DateOffset 文档描述。在文档罗列的Offset中,需要介绍一个特殊的Offset对象CDay。CDay 或 CustomBusinessDay 类提供了一个参数化的 BusinessDay 类,可用于创建自定义的工作日日历,该日历说明当地假期和当地周末惯例。
/ ^) p5 o- K. y7 X5 S9 \ 其中的holidays, weekmask参数能够分别对自定义的日期和星期进行过滤,前者传入了需要过滤的日期列表,后者传入的是三个字母的星期缩写构成的星期字符串,其作用是只保留字符串中出现的星期:
& P. E0 A6 F1 w- q: b. n. |: {
0 f1 u( Y4 v; F+ D) M, Fmy_filter = pd.offsets.CDay(n=1,weekmask='Wed Fri',holidays=['20200109'])
5 F. N3 `. Q) B% J- d# g! G: ]6 S O8 qdr = pd.date_range('20200108', '20200111')
1 b* {$ v* y) _, L9 a4 s' P7 }1 N3 m* f5 b! m, }6 a
dr.to_series().dt.dayofweek
' ~+ W. s" d, |Out[89]:
. l- \9 \$ M9 {/ r- E* q2020-01-08 2! L! {4 S; L1 H/ ]
2020-01-09 37 A; ^4 Z& ^3 A/ u& d
2020-01-10 4# Z1 E' k4 q$ U# `. C1 [
2020-01-11 5" F# j8 t) ^$ T9 B
Freq: D, dtype: int64$ W# l4 `) k6 Z( K
; `! G2 t( Z! Y2 _5 S3 H[i + my_filter for i in dr]
8 A8 a$ F# a$ {9 E1 Q! g9 n7 eOut[90]:
0 c9 e" _& m+ `' ]9 ~/ W4 w* h[Timestamp('2020-01-10 00:00:00'),
: P, u& O* P6 e1 i' d0 V' v/ i6 z Timestamp('2020-01-10 00:00:00'),: a: o; m% s: W7 d
Timestamp('2020-01-15 00:00:00'),
" y4 R! q' S4 d Y7 _. H0 x Timestamp('2020-01-15 00:00:00')]
6 M# U% u5 w& B, I: [ c8 z: U! M" K/ k
13 d9 H; S6 F e/ f
2" y4 M5 S8 b- I( _
3
( H, M d! v% G9 S& N4
9 t8 s7 G; I4 x5
6 ^( H' y2 n" ^6# w( B- y- ?2 B) I
7$ C+ z* a9 o0 ?* p; W; b; y
8
9 [, {% |4 x! J6 n9% j: j7 |. H! C; {7 m5 t! F+ c& r
107 g4 p% Y) L' a. q# W
11
. p$ n6 H' M& f; G6 I12) s- O* C6 G9 P+ F5 C: e( n
13$ ^5 g+ A& v& b( b% I9 h
14
; d4 _; X, a0 }3 ^& n" z15, [$ l8 k' |3 K5 y; t
16" t& ]* m- f" q8 U
17# f: Q" i; l2 v. p# m4 c2 O, i7 q
上面的例子中,n表示增加一天CDay,dr中的第一天为20200108,但由于下一天20200109被排除了,并且20200110是合法的周五,因此转为20200110,其他后面的日期处理类似。+ K$ r( S9 s7 ~, ]/ F4 Y5 ^1 E
& W2 K& z3 Z+ ^
【CAUTION】不要使用部分Offset
. B% A& ]+ g8 r在当前版本下由于一些 bug ,不要使用 Day 级别以下的 Offset 对象,比如 Hour, Second 等,请使用对应的 Timedelta 对象来代替。) @- n( T7 @( Q, r- b
) i1 i9 T4 K. V, G) Z4 o10.4.2 偏置字符串
1 W% J7 J3 P, t1 h7 @+ q 前面提到了关于date_range的freq取值可用Offset对象,同时在pandas中几乎每一个Offset对象绑定了日期偏置字符串(frequencies strings/offset aliases),可以指定Offset对应的字符串来替代使用。下面举一些常见的例子。
8 c1 _' e/ v/ j1 u. I6 L
' a5 [$ v9 y$ m! W+ D Offset aliases:pd.date_range函数中的freq参数,为常见时间序列频率提供了许多字符串别名。 也称为偏移别名Offset aliases。偏移别名列表点此参看(大概27个)。& F$ ^" O* n, v. M, o& K$ e
! \/ V t5 W: m: K
pd.date_range('20200101','20200331', freq='MS') # 月初
" S6 r4 s5 y( w, E3 s$ COut[91]: DatetimeIndex(['2020-01-01', '2020-02-01', '2020-03-01'], dtype='datetime64[ns]', freq='MS')
% j( } `9 U g5 L! G- r% N' j
pd.date_range('20200101','20200331', freq='M') # 月末+ c7 w% `. Q8 ^! I9 d5 q3 f
Out[92]: DatetimeIndex(['2020-01-31', '2020-02-29', '2020-03-31'], dtype='datetime64[ns]', freq='M')
4 `5 J, Z W0 Y' w
2 l, }- h* Z' k* T. o; @+ g/ v) ^pd.date_range('20200101','20200110', freq='B') # 工作日: Y9 F8 A8 V0 ]5 Z, F( o
Out[93]:
, u. i$ F T$ @" _DatetimeIndex(['2020-01-01', '2020-01-02', '2020-01-03', '2020-01-06',
9 K! b8 Z+ @$ j '2020-01-07', '2020-01-08', '2020-01-09', '2020-01-10'],* W4 K1 c; m' u& P+ l
dtype='datetime64[ns]', freq='B')$ e* A. M% s7 m, z
1 F' t6 ?- @0 j+ S' Cpd.date_range('20200101','20200201', freq='W-MON') # 周一& b( N! K! S [0 ?& k6 }
Out[94]: DatetimeIndex(['2020-01-06', '2020-01-13', '2020-01-20', '2020-01-27'], dtype='datetime64[ns]', freq='W-MON')
7 B4 A& A5 ?+ H# k. L, @6 R* f+ a r3 u3 e$ z) B2 D
pd.date_range('20200101','20200201',, ]5 U/ F" z1 c3 Z: _
freq='WOM-1MON') # 每月第一个周一, d2 O6 C U% P" `# U6 E3 P) ^
; X3 m4 Z, Z9 S9 W$ e
Out[95]: DatetimeIndex(['2020-01-06'], dtype='datetime64[ns]', freq='WOM-1MON')2 _( z1 I2 K+ |3 Y
4 j5 _9 F) B( p
1
7 t# l/ ~7 ~, F: B5 Q9 v6 e- Y, Q28 `4 \ J! }/ v, {
3
6 z1 W% I- t) r& n2 Q4" m3 b2 m; n/ t0 M5 a
5. w) e3 C0 G- x( }4 q, l& S0 o
6" \, s2 A; {" w
7% r8 s0 G9 m7 X7 Z2 A1 F
8
3 x5 ^1 X0 K b9& }$ T1 W$ V& ` ]
10
$ ?/ D* ]& k3 \6 _: C11
1 \* f* P+ J: ~# `& @5 g12
* b+ M4 P' {$ f% d13( X: a% T- M: b) f
14- h6 ^ U4 d) K4 R, v. K
15
2 z- \# @ ]: A( @+ F163 Z- k7 F2 y% W! q H- c
17
* H/ u! Z o, x7 B9 P18 h8 y: m+ l: @ k3 @, p& y Q
19
* k# z$ O0 M M上面的这些字符串,等价于使用如下的 Offset 对象:+ n" \, _3 U4 I8 K: \
$ n; r- q3 q) a4 |% }
pd.date_range('20200101','20200331'," D+ t/ O8 U/ A* z5 R
freq=pd.offsets.MonthBegin())
' i6 i9 x: \; _" j u2 k9 d6 f D2 {$ T$ W1 g: k
Out[96]: DatetimeIndex(['2020-01-01', '2020-02-01', '2020-03-01'], dtype='datetime64[ns]', freq='MS')
?5 T5 i1 @' v8 G" I' R7 H1 [: @% }/ |2 g; g' ?6 b- [9 v& s
pd.date_range('20200101','20200331',
( {( x0 ^! Y( G: O freq=pd.offsets.MonthEnd())
* C1 x! E- [9 w! j3 ]* x3 R+ ]3 f$ p
Out[97]: DatetimeIndex(['2020-01-31', '2020-02-29', '2020-03-31'], dtype='datetime64[ns]', freq='M')" ^6 P' M' D' \6 }& H
+ F0 m D! x$ R# J' Wpd.date_range('20200101','20200110', freq=pd.offsets.BDay())* O" B/ ~8 \7 Q8 ^
Out[98]:
) v( G' J0 G% F4 YDatetimeIndex(['2020-01-01', '2020-01-02', '2020-01-03', '2020-01-06',
: i9 o2 z' o0 g# o# I5 L1 r3 s '2020-01-07', '2020-01-08', '2020-01-09', '2020-01-10'],
$ z8 `+ _7 b9 d9 ^ dtype='datetime64[ns]', freq='B')
1 x" q" C9 ]8 P# Y1 O. C' f: ^ r7 n* P/ d6 N+ o
pd.date_range('20200101','20200201',. T. D7 l* ?5 r' s/ @( L/ Q
freq=pd.offsets.CDay(weekmask='Mon'))
" X+ Z) {7 Q7 x9 g
4 `" Z6 {0 j+ QOut[99]: DatetimeIndex(['2020-01-06', '2020-01-13', '2020-01-20', '2020-01-27'], dtype='datetime64[ns]', freq='C')
9 A& b1 M3 c1 ~; p4 C" G n. Q% ?% P3 v
pd.date_range('20200101','20200201',! A) o# H8 [: w+ |5 M5 _! }" R+ h
freq=pd.offsets.WeekOfMonth(week=0,weekday=0))# ?0 ^$ E/ y( V4 b6 } ^8 I4 I
4 ^+ V0 g# V) [3 }Out[100]: DatetimeIndex(['2020-01-06'], dtype='datetime64[ns]', freq='WOM-1MON')
4 Q( f8 h' ^9 E+ J9 a' d+ h2 t0 M! V6 v; F+ H8 Y
14 T# ~( d* m# y: e# [/ _
2! Q c6 `6 h1 p. K; X, x. C
3
) E a" N _/ A+ W0 m4
4 C e8 g9 ?+ r6 i" w$ x3 ^& E$ ~: y5
/ S9 I. B5 W" e' d) Y/ A* ]6
* D2 h+ f" Z6 N5 _- U. I7) B0 c; q/ ]4 F V f
8, {0 H5 Y* o8 Y- v
9
0 F. z/ |% m" Q10; }8 d$ Q2 a2 g" M1 L3 @. Q
11
9 \- |4 z6 ^* A12
, G- r$ I' b1 C' [6 A+ q13
6 C$ G7 E4 B# P, n145 M$ b( ^* c! O( ?
150 F! z( t9 P6 F" R' U. k- i
16
/ J* V4 W- t9 Z+ o1 u17
, u# d7 n2 [) h p18
- S, C9 W. z& N$ e# d2 S/ }19
2 D4 t+ V" ?2 n* G7 c* J5 i! ?20! R' N7 I& U8 h0 m
214 U+ a2 c; |, L$ v9 Y, g0 q
22
8 ^6 o: ]2 o) a$ i23' m, \& }( F/ C& k$ A a8 s; L
24
9 m9 Q' K0 B& `2 S( O y5 x250 i/ o6 B! I* [$ H: u- Z
【CAUTION】关于时区问题的说明+ r) R) g# L& }3 d E) s' h+ l# y) J
各类时间对象的开发,除了使用python内置的datetime模块,pandas还利用了dateutil模块,很大一部分是为了处理时区问题。总所周知,我国是没有夏令时调整时间一说的,但有些国家会有这种做法,导致了相对而言一天里可能会有23/24/25个小时,也就是relativedelta,这使得Offset对象和Timedelta对象有了对同一问题处理产生不同结果的现象,其中的规则也较为复杂,官方文档的写法存在部分描述错误,并且难以对描述做出统一修正,因为牵涉到了Offset相关的很多组件。因此,本教程完全不考虑时区处理,如果对时区处理的时间偏置有兴趣了解讨论,可以联系我或者参见这里的讨论。
, R9 n! j, ^3 ?+ V. ~! }
) p2 Z9 `5 o1 w3 _10.5、时序中的滑窗与分组
! ^. P ^( t. O% N6 ~10.5.1 滑动窗口& K% `. b2 @' U7 O h
所谓时序的滑窗函数,即把滑动窗口windows用freq关键词代替,下面给出一个具体的应用案例:在股票市场中有一个指标为BOLL指标,它由中轨线、上轨线、下轨线这三根线构成,具体的计算方法分别是N日均值线、N日均值加两倍N日标准差线、N日均值减两倍N日标准差线。利用rolling对象计算N=30的BOLL指标可以如下写出:
) O) } \ U/ {8 [) P) G5 j
/ ?; A! ?4 t4 B/ |1 simport matplotlib.pyplot as plt
9 i v* W. Z" V6 M4 r- T4 eidx = pd.date_range('20200101', '20201231', freq='B')
3 V6 A0 d: m0 L/ L$ rnp.random.seed(2020)6 X1 {8 D0 h& I8 L. `& a) I( | \
+ J( y5 y3 Q: {
data = np.random.randint(-1,2,len(idx)).cumsum() # 随机游动构造模拟序列,cumsum表示累加
5 m/ ?% e$ M' {0 i+ |6 c& T& ms = pd.Series(data,index=idx)
- r; ?/ S5 F$ i" Ks.head()* p( Q6 A4 |! N; O) t4 J
Out[106]:
* @* ~+ n4 Z9 a; {2020-01-01 -1
1 p; v `: W) v7 T' [& _2020-01-02 -23 t2 ~( W, w* _8 C
2020-01-03 -1
. J# c* K- Z0 s/ X2020-01-06 -1
+ W5 r( L: Q) i( j, d( A2020-01-07 -2/ p7 R- M$ s5 g" G B
Freq: B, dtype: int32
3 N) Q: W( E& e2 t w- Ur = s.rolling('30D')# rolling可以指定freq或者offset对象
6 y: r: r7 X( Q- b- P( H5 }
% W: J3 h) A# A, v; u5 |) Dplt.plot(s) # 蓝色线
6 ^8 P6 X3 X" ^0 N) I. [! K9 z) @Out[108]: [<matplotlib.lines.Line2D at 0x2116d887eb0>]
- R! N/ O+ H S# m. H$ \, K u8 lplt.title('BOLL LINES')8 \3 V( |2 B1 d _: C& U
Out[109]: Text(0.5, 1.0, 'BOLL LINES')
& b! r" @6 B, q
- n M0 m- u, E" e4 j+ |9 hplt.plot(r.mean()) #橙色线
( P% r2 ?9 O* u/ J5 o; Q9 ] M# OOut[110]: [<matplotlib.lines.Line2D at 0x2116d8eeb80>]. J) c' }) B( m! F9 S9 l2 |
1 @% j: X7 g' ?/ l5 lplt.plot(r.mean()+r.std()*2) # 绿色线( A4 m+ d+ o' `% }& g [
Out[111]: [<matplotlib.lines.Line2D at 0x2116d87efa0>]
% F- Z' M/ q2 b3 z
! G V+ r; h( H5 Q: Yplt.plot(r.mean()-r.std()*2) # 红色线; s. k$ V0 p- Z# s4 H7 I8 }
Out[112]: [<matplotlib.lines.Line2D at 0x2116d90d2e0>]+ i w3 z! [0 [0 Y V/ w) q
8 |" A" H1 h5 z2 e
19 g/ s7 l0 B% y" {+ z: A* o) I
2$ ~5 E2 |1 w1 l' q
34 H, O5 P3 W4 ]' m4 k0 N7 T
4$ s T$ t, s' w; e0 j& l3 B1 |
5
. x1 P+ i/ G* A7 c4 e5 y2 N6' |' |& P& W; |& g7 C/ ?' Q# M: {
7: n# w: L0 N* Y- \0 @ X2 b1 P
8
3 d6 E# x& U9 k T9- z5 S; g, h. V: d
10( H9 v7 f/ S4 n# G
11" n$ J1 t# w( d v0 A" h
12% R7 t/ }, n. D0 e
13
. F' p( x( u& t7 _7 v4 d w2 S8 A149 S* h* V0 l1 c) C! W& m9 k! q; l
15
, n5 a a. |7 _9 b* c168 s" Z, a9 Q3 o g; h1 U
17" x U% v2 {! O# |
18
_3 l. p" u+ E' Y* q19' [' H2 Z# W" s6 v: }
20$ m! ^7 r9 z; G/ h/ A
21
; ]8 s9 L# | H2 L8 l s) ^9 m225 Y; `/ J& f9 n2 W
23
* I8 h" R! s5 t24
; J# R" v* J2 U9 [5 p6 Q25
0 ~4 R. ?9 l; e5 Q# T9 U& |26; p7 o9 ?9 T; M2 m) T& r
27
5 s }5 C6 y0 L% j28, A z* L! ?/ g1 s! y
29; a5 I" I1 j* E5 U# v) r P
Y+ V2 y7 g- c$ b [0 W 这里需要注意的是,pandas没有实现非固定采样频率的时间序列滑窗,及此时无法通过传入freq字段来得到滑窗结果。例如统计近7个工作日的交易总额。此时可以通过传入多个函数的组合来实现此功能。8 J5 b. R0 ?4 J
首先选出所有工作日,接着用普通滑窗进行7日滑窗加和,最后用reindex()恢复索引,对于双休日使用前一个工作日的结果进行填充。
# f: f8 B) Z" W! a2 }
8 |3 t) x0 P8 r5 i3 Qselect_bday=s[~s.index.to_series().dt.dayofweek.isin([5,6])]
- w: W1 M+ ], d/ j9 ?+ A! w1 ~bday_sum=select_bday.rolling(7,min_periods=1).sum()
9 U! P$ |% ?' b- f- ?' ~; O1 A! uresult=bday_sum.reindex().ffill(): z2 w; Y& I' A. F7 j) N) B! e
result3 r) y' X4 ~- i; v) `
8 Z$ H2 @3 s" n- ]% Z7 X. p
2020-01-01 -1.0
$ W: v( F/ u; b( s. p* U2020-01-02 -3.0
; V" E+ l$ ?1 M! r$ s: E2020-01-03 -4.0
8 R0 g# Y: l# t$ T1 ~' E2020-01-06 -5.0
! @. J' I8 x7 @5 U' }# G7 F2020-01-07 -7.0* v1 j% K4 t5 o
... 2 E3 F. l1 u( N
2020-12-25 136.0
) O0 c; x$ c% E3 P2020-12-28 133.04 V; q1 X' |) N" ?. C7 p' G6 `7 u
2020-12-29 131.0$ d i# Y" ]1 i: S
2020-12-30 130.0
5 i+ C! A/ a' S2020-12-31 128.0
. R3 Q* }* a1 H8 `7 f& p: j VFreq: B, Length: 262, dtype: float64* e5 V0 C, A* x9 L. U5 g4 U" }
- ]" p, ~, {5 D
17 o, X) j0 t) |! D0 ^
2
" }; @+ R' ?! ], n; S- x5 C3. ^ s2 j9 ^9 h0 j
4
& m: e- F# B+ E% B6 ^# P% r0 H1 y5
/ Z! Z( p1 O/ @6 _( N6' r0 [8 W3 E" B" I* o+ B" H
7
' B {* g& T) f8 e9 i8# s4 Q" _2 z# H$ \
9
6 ^* m0 X: S3 w8 D* H+ M" u106 g8 [* {/ n+ |2 p! h7 S& ^1 {
11* h- c0 e: ^: d& ]
12
' e' \7 B8 \" C136 s& x }. q# g6 v
14
# W4 Z" g0 |" D/ `) c( i5 C8 @( X15
; j* x* \( ?) m$ a) \, t4 B16
6 _% D: M. w. w$ b K17
8 t* t% h; i G: I Y+ X9 J shift, diff, pct_change 是一组类滑窗函数,它们的公共参数为 periods=n ,默认为1,分别表示取向前第 n 个元素的值、与向前第 n 个元素做差(与 Numpy 中不同,后者表示 n 阶差分)、与向前第 n 个元素相比计算增长率。这里的 n 可以为负,表示反方向的类似操作。
9 t9 ~% W4 K4 D
: D2 D- P& m* _, B7 d; W, e$ L 对于shift函数而言,作用在datetime64为索引(不是value)的序列上时,可以指定freq单位进行滑动:5 H9 R+ f2 ~/ D( H! x' b
6 ~1 C4 b2 S5 i
s.shift(freq='50D').head(), [ }* y" C8 I
Out[113]: 5 p+ J$ l* A$ x! Y. H
2020-02-20 -19 R: L$ m" R/ Y! I& f
2020-02-21 -2
/ v0 U$ Y: o2 w' {2 o; j! c% v2020-02-22 -1
! J$ A% n* ]1 ~$ N }% P2020-02-25 -1+ q( E% v9 @5 }- K. V2 e+ [7 h
2020-02-26 -2' R' r- J& M! t* {. t
dtype: int32
% ?2 m& E! L2 `1 f& U' V4 S a1$ \0 ~( ]3 Z+ [. [0 T) W
2. s7 R* T9 ?* r0 [7 L
3
: |4 H; O4 Y. s" v- F. w, S: R$ E4
7 R8 l) A0 J+ n2 s# ?1 u5
& Y* a( b9 w2 }6
C# a( {$ v& O8 E2 b5 g* L9 R7( G5 u/ E5 w; f* ^. z
87 N4 B+ D( }& q1 k+ q- D# R3 y
另外,datetime64[ns]的序列进行diff(前后做差)后就能够得到timedelta64[ns]的序列,这能够使用户方便地观察有序时间序列的间隔:
: ~# A3 k0 Y2 \6 c4 Y* K
+ r d& f2 ?* Y( G$ Xmy_series = pd.Series(s.index)
: W6 @, g, S! C! e* q7 K: ?my_series.head()
3 b0 P+ ~1 x1 Z c2 e0 c( R; iOut[115]: ; }# }: N" r) a2 Y4 T2 s
0 2020-01-01
; N) F/ \! s+ }1 2020-01-028 d7 O2 C, N: }3 n# m7 y5 |
2 2020-01-03( B8 D$ ~+ G9 l5 P, ]6 P8 w
3 2020-01-06, Y# N: O: w- L3 c
4 2020-01-07
3 f4 `' i" _% x8 I& X' J" Qdtype: datetime64[ns]
' ?+ q+ _# p6 x- [
0 O- T2 ^: v @- S6 Cmy_series.diff(1).head()1 Q6 z* [3 N7 f' T
Out[116]:
. b. J; h* |: h8 {% \* ?' ]& _5 I0 NaT6 g' R+ P5 [( A
1 1 days
. V. H1 T9 h5 e$ v. e/ B2 1 days; e M+ l" Z4 @/ [; b6 ?) l! k
3 3 days
V: D! L: v' f G, r4 1 days- r" _& p P- B
dtype: timedelta64[ns]
; u0 E% y' m& i# O; T9 q
6 q4 v1 c4 ?6 `1. F* H5 o- X% {
2
6 @) I8 A* T+ R1 s2 i. ^) F+ O: t39 v' _7 Z6 z4 r
4
7 ]: l# v5 `1 l: o/ l% ^, ^5
( E' [! D5 Q+ ]" m6
' s; ]& E7 ^; n l8 X7 f6 S' O7
; I) Z9 ?+ g5 d% E5 s' V4 y3 ?$ ^8+ m+ l% ]* A* M4 J0 I: ?" t
9
: P) U' K, J' F10
) ^9 }( G* t: U- k11
5 c. ?; [3 r; G* ^1 a2 F- b4 J12 H a4 j2 y! n0 S5 B+ c
13! J# }9 {0 B3 q- d6 [! V
14' z" y5 g- B4 d |7 y$ y
150 J" H: t3 G( {0 R0 c: T
16# w& D( T P; D& K$ r# n8 z ~
17
: G6 q [' `' y1 f187 I: D# d- M: _
10.5.2 重采样
5 a( p( v! ^8 @1 l* h# c 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)
9 y0 }: e, q9 U- v P P0 q常用参数有:" v8 U6 J8 C0 x2 b P5 w
, ~9 Z& O9 O6 S$ Mrule:DateOffset, Timedelta or str类型。表示偏移量字符串或对象
" @% J! ?! {, s' M* C4 Eaxis:{0 or ‘index’, 1 or ‘columns’}, default 0。使用哪个轴进行上采样或下采样6 S1 c/ X% E3 e
closed:{‘right’, ‘left’},默认None。表示bin 区间的哪一侧是闭合的。所有offsets的默认值为“left”,但“M”、“A”、“Q”、“BM”、“BA”、“BQ”和“W”均默认为“right”。/ R( {+ Q" R; @' _! e9 |
label:{‘right’, ‘left’}, 默认 None。hich bin edge label to label bucket with,所有offsets的默认值为“left”,但“M”、“A”、“Q”、“BM”、“BA”、“BQ”和“W”均默认为“right”。0 r' @! J8 ^9 N! l% X
convention{:‘start’, ‘end’, ‘s’, ‘e’}, default ‘start’。仅针对 PeriodIndex,控制是使用rule的开始还是结尾。$ u6 a; n. W _) S# O" I: s# j
on:字符串类型,可选。对于 DataFrame,使用列而不是索引进行重采样。列必须类似于日期时间。( d5 E: o- Q4 |6 r
level:str 或 int,可选表示多重索引MultiIndex的级别,这个级别的索引必须类似于日期时间。! w9 L& }( m# w. C h/ o U
origin参数有5种取值:
4 r0 b$ ^0 Q0 |‘epoch’:从 1970-01-01开始算起
9 Z2 c+ Q! W$ V4 W‘start’:原点是时间序列的第一个值
6 X1 k1 q% G$ g* i. h# f% t; ?) g‘start_day’:默认值,表示原点是时间序列第一天的午夜。
8 z, d+ `9 x! g( U- ?'end':原点是时间序列的最后一个值(1.3.0版本才有)
" D( _( H5 D$ ~3 W4 r‘end_day’:原点是序列最后一天的午夜(1.3.0版本才有) F8 x3 X6 G* n
offset:Timedelta 或 str,默认为 None,表示对时间原点的偏移量,很有用。
* { `3 K0 L' v9 ^ closed和计算有关,label和显示有关,closed才有开闭。
8 a3 z6 P# J& P' {" O9 s2 G label指这个区间值算出来了,索引放区间的左端点还是右端点,closed是指算的时候左端点或右端点是不是包含。2 g. C# M: q: T. A
c' F) V. v. m' N3 A- T
重采样对象resample和第四章中分组对象groupby的用法类似,resample是针对时间序列的分组计算而设计的分组对象。例如,对上面的序列计算每10天的均值:8 u6 K$ }! I! e) t( T
s.resample('10D').mean().head()7 c* g2 N# L8 E0 }+ D
Out[117]: ; l% o+ g- {# y% }. U0 r" K
2020-01-01 -2.0000006 q: _. r) Q9 @& b
2020-01-11 -3.1666675 a6 l6 @$ ?8 h# z
2020-01-21 -3.625000
$ G# W1 [3 s9 y4 s7 \# G2020-01-31 -4.000000& n2 S9 [, F6 j. e; s: g* T& n
2020-02-10 -0.375000: W( t d# h5 C9 N ]
Freq: 10D, dtype: float64, w9 a! s( V7 Y8 x) T. O+ I. I
1
; x; D$ K. d F! a2 _8 y1 i+ E" `
3
7 }2 b q2 ^2 j/ p1 i4; Y8 G) A; Q+ X1 P( y5 ]# ?7 R
56 C1 s' t# E: v; Z4 v
65 f ^) {, x& f6 @1 x$ e1 R* D0 Y
7" y0 N9 ]" V. t: n$ G
8( i+ ^3 R$ D' e
可以通过apply方法自定义处理函数:
1 _, J- \& E- O! Q( F9 K5 \' ys.resample('10D').apply(lambda x:x.max()-x.min()).head() # 极差/ y, M8 z+ j9 {7 U; a/ j/ Y
7 O" I+ ?! f f# ]( Q# N
Out[118]:
5 P6 k$ B* m4 Y/ M i2020-01-01 31 v- ?: z8 V1 J& I. T% `# o
2020-01-11 4
0 t; _) Y5 L! ~" ~% n' j1 B2020-01-21 4$ z. s' E8 T8 U% h. }) D
2020-01-31 26 }$ `) f3 N9 f* {, F( W
2020-02-10 4
6 ^% q* N1 z3 x5 \* p* ]4 y9 mFreq: 10D, dtype: int32" \' R& o* U0 g9 f4 w- k5 I
1
+ F: ]7 {' _ C4 z) F/ m2. M2 J' o1 q2 }; {+ G
3! Y) t# n: A# J7 _/ [
4
, e3 V* g: d: O+ J [# @5( m+ C5 d7 I& b; [% ]) A3 p
61 L; t7 j) V: l9 ~8 D
7+ S: t: p; H& c3 f3 t( U7 Q
8
+ N: u7 u9 N5 m3 I9" h6 w) Q; C$ k
在resample中要特别注意组边界值的处理情况,默认情况下起始值的计算方法是从最小值时间戳对应日期的午夜00:00:00开始增加freq,直到不超过该最小时间戳的最大时间戳,由此对应的时间戳为起始值,然后每次累加freq参数作为分割结点进行分组,区间情况为左闭右开。下面构造一个不均匀的例子:2 T+ u) R+ O, p$ _) C6 x! \! A$ F7 R
S$ s) C* M! R/ y( M1 u0 @idx = pd.date_range('20200101 8:26:35', '20200101 9:31:58', freq='77s')( U; B: q" T7 }% a: g
data = np.random.randint(-1,2,len(idx)).cumsum()
0 N" L( E0 g# D1 `s = pd.Series(data,index=idx)( U3 y6 `+ e. A# @4 A3 T
s.head()
8 |3 d4 K' V( j3 {3 {( I+ K
. B, A( D4 {3 v! M# FOut[122]:
k2 d( h# \6 P3 [- V7 w2020-01-01 08:26:35 -1* b" Z/ m. k- X% P
2020-01-01 08:27:52 -18 z7 x9 T" w B
2020-01-01 08:29:09 -2 [. m, j8 h& r) Z. x. i, _
2020-01-01 08:30:26 -34 P, `% L" T2 L( d; ?& N
2020-01-01 08:31:43 -4
; G0 F+ D. o3 _4 k( w7 Z' W' DFreq: 77S, dtype: int320 Q% I) O5 @8 Z, X6 l8 j) `
1
; j' V# D* v* g7 Y6 ^: k; c2 h5 b$ e+ N- _* H2 ^
3
% K& X1 Y/ K; M3 u% f* n) v1 r4
$ _) _9 W3 N6 e- r! [) c2 n) Y- I5, V; b( G8 F( C/ D/ n
6
% t; a: F! x8 W" D7
7 c3 ?0 ]2 n2 @! G8' u* U( M. I. i8 `5 Y8 F2 `, w3 d9 P& q
9$ H2 x: `( z4 f5 o7 @! E5 Z
103 Z( M" a+ K0 w' H
11
B- q7 W- y& Y5 v7 y/ p124 V7 M$ m2 A. X. }) z f, j, S
下面对应的第一个组起始值为08:24:00,其是从当天0点增加72个freq=7 min得到的,如果再增加一个freq则超出了序列的最小时间戳08:26:35:/ _- E4 `0 l% L4 f; N0 r; f
; f" \- F0 u9 h3 r# \s.resample('7min').mean().head()
- S5 X6 a& O2 b8 FOut[123]: / T6 N+ O0 e) b% [; y7 R. S
2020-01-01 08:24:00 -1.750000 # 起始值,终点值包含最后一个值3 Q! V9 O. n1 u
2020-01-01 08:31:00 -2.600000
% b. q9 H A2 S2020-01-01 08:38:00 -2.166667
8 w( x4 ]9 r1 Z& q2020-01-01 08:45:00 0.200000
+ g& E6 M b# n0 e2020-01-01 08:52:00 2.833333
, Y& G7 r. {" e6 ?0 t* EFreq: 7T, dtype: float646 ?' l: `: G+ n$ h5 ]6 F
1$ g) c5 h' J/ ]( @$ d
2
& Z9 ]% K- v" ~) V: w32 ^9 T$ X0 {! a
4: Q1 e1 k! L' ~7 p
5
, o8 M, _5 P( l68 g7 _! A/ f! ?: V7 L- {8 M
7
/ H" x, i2 g' y8$ k& q' v- w# ` l7 k9 a6 Q
有时候,用户希望从序列的最小时间戳开始依次增加freq进行分组,此时可以指定origin参数为start:0 H- p7 }/ [/ |8 R+ l- x }
) n5 O9 _4 J% z4 D. T
s.resample('7min', origin='start').mean().head()2 H z- k. B- l" q8 p! S
Out[124]:
2 V! _& n! b: I( b2020-01-01 08:26:35 -2.333333
$ L6 W+ ^" J9 l4 a1 W2020-01-01 08:33:35 -2.400000 v5 Y/ I6 I* {6 b. C p
2020-01-01 08:40:35 -1.333333: I3 ~' k) G! P
2020-01-01 08:47:35 1.200000- J% o* i2 C3 h0 W. \0 {8 N9 G
2020-01-01 08:54:35 3.166667# \* e" ]# O& P: n
Freq: 7T, dtype: float64
% l; j5 ]9 u- @- d% b1# w+ B3 T1 w* C* \7 s% U
2: P& C* o' B3 e, m7 d
3
! s; ]- o8 n( l/ f4' d8 {5 @5 F# l- h0 }7 \/ w- i
5
' x( q) s s2 h( f w6
: y! e) j7 m- I! Y/ l' S7
6 a2 y7 |, t& R' h8
+ a V3 o/ E7 ~) R d7 I6 i/ Q 在返回值中,要注意索引一般是取组的第一个时间戳,但M, A, Q, BM, BA, BQ, W这七个是取对应区间的最后一个时间戳。如果想要得到正常索引,用’MS’就行。
6 J \7 Q0 P4 O/ ]: A4 ?2 t
; u% R g# n$ B5 \8 N1 G1 A; is = pd.Series(np.random.randint(2,size=366),
% X& H3 x# C" m& y- v/ L index=pd.date_range('2020-01-01'," O+ t( `9 a! [6 |! b6 U
'2020-12-31'))' n; U Q' z% M, M N
( L) M3 \, J* ~; u
1 k, s" e5 ]$ Xs.resample('M').mean().head()8 a( {: Y8 X' {6 P
Out[126]:
/ q) \7 W H# O9 t2020-01-31 0.451613
( v1 M) Q3 W; ]2 o( e) V5 v2020-02-29 0.448276. ]/ ~2 I: v8 L! G6 L& Z* J! b
2020-03-31 0.516129
+ p/ q2 |( U& S0 u% p2020-04-30 0.5666672 h8 g3 R, M+ |: A: K, n9 a& Y
2020-05-31 0.451613
& A, _5 T1 Z2 Y; d3 t& yFreq: M, dtype: float64
+ M0 M3 L, o$ P' p$ k# ~& ]7 `3 ?( U; a( R3 @
s.resample('MS').mean().head() # 结果一样,但索引是跟正常一样: a8 N9 a5 {5 c h& i
Out[127]: . [* Y2 m! K3 ]! z
2020-01-01 0.4516133 P; e4 O* J M$ Y, O% }1 l3 U
2020-02-01 0.4482766 t5 M& _3 h& U$ O/ _% R( \( X( `6 m3 u
2020-03-01 0.516129
" b/ |7 M5 m6 J3 `2020-04-01 0.566667
0 P8 e9 P8 N( U& b0 X$ i. C2020-05-01 0.4516135 m2 Q% g7 X+ P
Freq: MS, dtype: float64
* S: E& O4 ~! U) Q
( ^ U2 V N' t8 o3 L1
' o! m7 R8 W3 L2 n* o6 A. M2
. u$ `: h0 U+ G7 \! }# |9 {3- M; P- d# h% ~7 ]
4
8 b6 V4 B7 F5 s0 K% ] z5: ]& k+ M7 S: H* j7 ^
6
' b* \5 D, l+ D+ u7( o" y) \8 t; C$ e, l
81 j7 P4 a5 M. R6 F3 D
9
8 Q, _% t/ n* y! B& m; `. o% x10" k& d: a! y4 ?6 T9 C
116 S4 G# s4 i3 a; w$ l. N9 x
12, c2 v+ Z- ~/ t+ D
135 M! S7 R, @& |& q$ v3 R3 Y) m2 ]
14# D% b. o9 J( p7 n% C/ }
15
0 e% }% d) o6 a/ }1 U16
# [* F o% R) n17
) V' }& P& e9 D0 t0 K) \: T0 d18
8 ]0 ?: S8 i0 E# y4 o p196 z) D; H1 n3 O ^8 R
20
5 `6 ] Z3 O! c( [- ~ N, x21: D. ~7 g% u* Z l' p/ F
229 X5 @& R) c/ V8 f/ [1 P3 j
对于 DataFrame 对象,关键字 on 可用于指定列而不是索引以进行重采样:
# c6 x6 z% n! ^4 Jd = {'price': [10, 11, 9, 13, 14, 18, 17, 19],1 u# e8 ~/ ^2 M( p; g& b" R& S
'volume': [50, 60, 40, 100, 50, 100, 40, 50]}
" ` p; L7 i. v* @/ Y q: ~df = pd.DataFrame(d)7 b: f; m: r$ w4 y7 r. L6 C! z q
df['week_starting'] = pd.date_range('01/01/2018',
2 i' Q: q; @1 j: f periods=8,, a7 Z( M: v( [
freq='W')
0 L$ _, f2 _1 \4 M3 Hdf- ?& E; D8 B/ S7 E2 _* N5 ]
price volume week_starting
) [ x& z: q0 J' U4 L! e0 10 50 2018-01-07) Z' I% n, l, _0 }- D; V9 R. H
1 11 60 2018-01-14( x$ `0 U: G- d
2 9 40 2018-01-21 q+ x% c( N( P& I" @% f' V( b: n
3 13 100 2018-01-282 a! {! S7 r4 N7 q9 q) r4 }
4 14 50 2018-02-04
, h' S0 |9 ?. L" U* n0 f! |5 18 100 2018-02-11
1 I, i- V8 i$ d u6 17 40 2018-02-18* A% V5 c* l1 Y \
7 19 50 2018-02-25+ k( _, t0 n& P% [1 Q2 y) y }
df.resample('M', on='week_starting').mean()
8 p# ^8 T! H& \ price volume! x' M0 r# k m$ N) I5 N- d/ o
week_starting
! E y& N& d- b T1 |- F6 A& E2018-01-31 10.75 62.5
8 [2 ~4 u" n' \" L9 o2 }2 J6 w2018-02-28 17.00 60.0& M7 w5 Y9 ]% \, S& z6 P# q; S9 M
( H( \9 [5 `7 K& V1
/ _( _. g; ~& \' ^! d2 M2
6 H1 l$ c# L9 q% @/ `, }* R) C% t3
- H% d! b8 P, b. H2 W" k4
: A; G7 N7 U3 p K0 A. l5% p U% q& O4 }
6
" \, V) ? {$ E7 W4 [: C" B7
! [" F" \6 v4 s! }8
8 ]$ b' J( p2 Z- W1 g) u9
3 i+ q2 z$ ]# b$ [10 Y/ K+ h4 i6 b3 n/ r+ ~& q) T- ~
113 o% z3 _' {+ E: h4 E
12
* ^0 e! E" x) O3 j. K$ t13. ^4 G2 H5 _8 q/ a/ g
14
; x2 Y; e# M- Z* a157 D2 n: N/ A$ D! }- D& Y! y
16
- ^: x6 n; ` W! B, J1 b8 d7 }179 S) n: N2 w5 J! s5 B' ]2 P
187 w# w% V& l& }" u0 l% i
19
3 r l/ w' e: F- T$ `1 C' |* i* }20
* t% Y; z g! v: T21
2 E: A7 R( c. A对于具有 MultiIndex 的 DataFrame,关键字 level 可用于指定需要在哪个级别进行重采样。
9 T) z. m* y5 S4 x9 `8 _days = pd.date_range('1/1/2000', periods=4, freq='D'); L$ |3 G& d" M# C; r6 d* V9 K( [
d2 = {'price': [10, 11, 9, 13, 14, 18, 17, 19],5 z8 q6 k. y$ o' a- c
'volume': [50, 60, 40, 100, 50, 100, 40, 50]}3 g' q8 r0 V6 ^- }4 `+ R$ @% P
df2 = pd.DataFrame(7 @& w, Q6 m; O/ a
d2,
# `5 p6 N1 K4 Y' K" `- }& E8 C" g index=pd.MultiIndex.from_product(# }/ c' `3 [! ]
[days, ['morning', 'afternoon']]2 g+ Q( ^; E; M$ A6 \: G7 M
); `# C% p, p+ ]" F8 t. q0 l( r
)
# i2 V4 U" ^3 G4 E" d: ^# c3 K- c/ Idf2
7 x4 S1 x ]6 D price volume
$ b6 H/ O* l% i; g [5 s% ?2000-01-01 morning 10 505 R9 z% ^8 v% @' Z
afternoon 11 601 N' P k: D; J; I* |
2000-01-02 morning 9 40
! f" z. n3 D! k1 e5 ^, ?) F afternoon 13 100* S, x1 `2 |2 h8 n- n" t$ u
2000-01-03 morning 14 502 }# W$ R2 B9 I0 ^: a
afternoon 18 100
1 e$ w* r a2 i. N; o/ d2000-01-04 morning 17 402 S$ h* M4 g6 P
afternoon 19 50
2 l* @" s3 N/ n5 idf2.resample('D', level=0).sum()
- ?7 @$ m% H( M7 q' |! K- p price volume" D- I$ r" E$ w7 \% w
2000-01-01 21 110
S* c' n+ k" P4 T2000-01-02 22 140
. n$ I( j7 Z$ ~5 t% Q; v, X2000-01-03 32 150. x ^2 O4 f- O
2000-01-04 36 90- E2 T0 u- C x4 |: k/ l
9 v. C, o* ?$ q9 Z* i1
, S0 N V! q* T: Z% K' I0 r# O2
% H8 b, @- b9 B4 K3 V% `0 ^3# @3 E8 p' i; H+ @0 s) d
4" M- T' D/ X( D5 Q' o
5
; G2 o- B5 O$ X+ {0 O6
- C% I" D* q2 I8 x$ A7
/ D& w* ^5 E* F+ e) L" r8
4 {! [1 Z2 E5 q+ [% h91 a; g# P; [" {! ]
104 a* C% ]+ S8 L; G4 f
11
" R1 C1 D: i' \' V. U12/ T" t- @1 ?% Z3 H3 h
13. {' H+ h2 F0 L. C# [7 Y" H3 m
14
4 M5 U* n; d: v15' [4 T4 f9 y! V# z
16
& _5 R! n* M% O4 o" V17: m' u* p5 |3 o7 T% v* q! X
18+ l# m% d$ X4 z! Y. u
19% {% M5 E- q s) \+ g# Y
20
. z, F6 P4 F; P3 _4 R' Q3 T3 B21
# q$ d. X' s" E+ P5 _; ^! N8 F228 e/ u" [' z+ r9 Z; s5 J
23! x# S& u6 c$ w2 X% ~2 A+ X1 H% u, M3 V
24. d8 a$ H& e. j" Y& V9 q
25
+ T J. _/ N$ r$ w7 U根据固定时间戳调整 bin 的开始:0 L' c# c% E& U/ W1 g
start, end = '2000-10-01 23:30:00', '2000-10-02 00:30:00'
5 [$ t9 s' Q; Q4 p& r9 s. }rng = pd.date_range(start, end, freq='7min')
) z. C# G" |6 h# ]ts = pd.Series(np.arange(len(rng)) * 3, index=rng)
! H: @) V2 ^6 \7 i |9 J+ dts3 N* {/ i8 S2 N' M1 o& r7 P
2000-10-01 23:30:00 0) X7 Q; r$ I, x' u2 w' V
2000-10-01 23:37:00 3
9 e z" n' ^; u2000-10-01 23:44:00 6& m' H" I5 B6 k% d" G( }
2000-10-01 23:51:00 91 I# K+ H, |- Y* {2 r5 H% V
2000-10-01 23:58:00 127 M/ N8 d: Z1 V0 }( B% i- O
2000-10-02 00:05:00 15- U& C; p! }: q4 B3 X7 F1 J
2000-10-02 00:12:00 18
# I( w3 a) l" C2000-10-02 00:19:00 21
2 {( |# c; @' N2000-10-02 00:26:00 24
* w" O! P3 N: a; ^# S, d* z+ a* [' DFreq: 7T, dtype: int64
9 `& i" K, S! y+ i' R3 z% |/ q, \" y# I! |) V) _. y! T
ts.resample('17min').sum()
$ [; X% m- v7 Z1 i2000-10-01 23:14:00 0
8 ]$ }- c' D) O! a2000-10-01 23:31:00 9
% ^# S& j7 q: E! D. R2 T2000-10-01 23:48:00 21% A, a( H0 l5 i3 S# k
2000-10-02 00:05:00 54
7 d& U' d8 Y+ L$ m2 ~3 i2000-10-02 00:22:00 24
) K6 i% q+ _4 e+ F6 f cFreq: 17T, dtype: int64$ a/ ], u& c; U5 R0 D! |8 r
; t+ ^5 F) U" rts.resample('17min', origin='epoch').sum()
" V) {. R' B+ n+ o2000-10-01 23:18:00 0
# E0 R+ e" }0 p/ b2000-10-01 23:35:00 18
. ]$ u& W2 H5 M. F: i8 z. @2000-10-01 23:52:00 27) x( R# L3 C- [. V" V& _1 a
2000-10-02 00:09:00 39
. @6 G G7 e2 G% x. I& y2000-10-02 00:26:00 24- L1 S9 p- W) y5 q
Freq: 17T, dtype: int64: O5 \0 ~8 ^$ k9 z7 q; \1 D' r$ x
6 _0 x9 W- P* D4 z
ts.resample('17min', origin='2000-01-01').sum()
/ ?: a5 d5 r/ v/ k; V1 K2000-10-01 23:24:00 3
7 c) h- v$ l" ^# Q1 H# b2000-10-01 23:41:00 15
0 n. ]4 t- T; e; W: j! `% h8 K2000-10-01 23:58:00 45& `) W3 H$ T5 Z- y
2000-10-02 00:15:00 45
5 e, \3 p9 d3 `& d( E( ~Freq: 17T, dtype: int64
, R! V# n2 l! ]# u' n# [
1 `6 ?% S3 y- p O1
) a8 i) u0 U. [5 x2
( g- x& w7 o" x& `39 D5 k1 j8 I! F N/ u
4/ j! I/ |: l% ~* G! M) w8 j, y6 |
50 D# F. W$ H+ s2 y
6
: H1 @& O/ B7 ~- @& ]7 F+ |7# ^ y! t2 M P2 }% \% G0 T& B
8, h. i1 n" E9 k; [& x2 k1 K
99 e$ \3 w% ?" c. u; Z
102 s2 w9 k. [9 e6 Q
11 [, V7 s% L6 t h
120 D' y, d; E' O% @; U1 D
13" r8 h& E, o d$ z7 E w& ]3 c
14' y( `2 P1 p' q" b* w( p0 w4 l
15
$ m: c% b! w6 k! S16
4 x" b; F4 z0 C4 g+ j( F17
% [" [- o1 G0 u7 Z; f u% y187 I5 Z3 p# c! g
19+ G- U" `! S! s8 l7 K, V
20
) r& ^( s2 l$ E21
. o6 n1 T: a6 }$ n8 q22
: Z/ H4 T0 q8 E1 J, o23 y) R. ]5 O/ D& U
24
3 Q- W+ Z% a. _* \4 M25
& z& l$ l" R" m$ I26
: C) _* Z* l# f$ I n) X27
9 r9 o, t% u0 l. Z9 w28
# B) w2 g! t" o- y5 D29
$ v6 ~/ B: s* `" M) v30
# O! y. ~0 d+ ~7 ?4 u9 P5 R31
( b; ?$ ?) ]' Y+ y, V32
7 b, u- s! y y, G. s: L6 L! ~33
: z6 |3 ], z* V) c4 i R# S0 X7 \34
2 S& a! d7 ]& Q2 M# V35
. f6 ^- q: o1 Z% d363 R+ b, x$ f" j
37
) m8 t' Z: _) O0 ?) V% ~' u如果要使用偏移 Timedelta 调整 bin 的开始,则以下两行是等效的:
& }/ O D9 i; Hts.resample('17min', origin='start').sum()$ a# t: O; Z$ Q
ts.resample('17min', offset='23h30min').sum()
8 T- d# W9 q2 V2000-10-01 23:30:00 9. F" A9 L: d7 o* b& O0 x
2000-10-01 23:47:00 21/ A# O- f) L) ^" Y5 o
2000-10-02 00:04:00 54" H, v% c$ u3 N* V& E! g$ h+ `
2000-10-02 00:21:00 24; j$ B" ^6 r" W9 ]
Freq: 17T, dtype: int64
/ \3 B/ E X$ Y1
8 s; I% \/ R8 E" u( I, z2; N0 J5 M/ x6 Q3 c7 ~; c; }- S3 P. v
3
, @' u# T A. }4 o! c' _( \4
+ N3 F6 Q0 D+ ?& `9 T/ d- F) U5
& d. f0 L2 M) t, K$ `6' \8 z8 |! y" \( s
7
2 {* s% v( g( a0 W10.6 练习
3 D+ B1 w, D. v {$ d/ SEx1:太阳辐射数据集
! o, q: Z/ ^6 D3 _0 o4 `现有一份关于太阳辐射的数据集:
/ Q9 Y8 ^% [7 u2 U# W+ g6 Q" I/ B j3 d* e5 R- u: [5 Y( I$ |
df = pd.read_csv('../data/solar.csv', usecols=['Data','Time','Radiation','Temperature'])0 g$ d8 W% M8 D% T: r
df.head(3)
, |6 R& G% ]$ x6 x6 ~( Q/ s! Y) q% \" e2 C" H8 K
Out[129]: % L3 o" n2 Y4 I* j, w
Data Time Radiation Temperature
( C+ F5 b) D, F) I& j( _0 9/29/2016 12:00:00 AM 23:55:26 1.21 48( D5 H7 Z i/ M6 Z. a) Q4 O! Y: D7 \
1 9/29/2016 12:00:00 AM 23:50:23 1.21 48
% p5 m) o1 l7 F$ Z* @2 9/29/2016 12:00:00 AM 23:45:26 1.23 48* T9 r! |, O; y- w3 V6 f% e; ^
12 d$ m- y& T) ^: G, F4 }. P
2, b) M4 U% |- p% h& d- x& V9 s
3 W$ X$ b% H. f& W% q
4. \% W7 F0 X% H
5/ ^6 a9 K& P) c8 _: p: S3 P' s0 V2 V
6' r7 Y4 h9 B9 P0 b2 _
7
' V2 |( X4 S$ }4 |0 L% _( Z4 Z8
7 U( ]# _0 O3 c' q7 O& C7 @( p/ z将Datetime, Time合并为一个时间列Datetime,同时把它作为索引后排序。/ A4 ?4 d) P# j* V9 ]/ `; M
每条记录时间的间隔显然并不一致,请解决如下问题:( W2 u; N4 o# m$ e
找出间隔时间的前三个最大值所对应的三组时间戳。
/ ]; X! n l) x/ s& {# F是否存在一个大致的范围,使得绝大多数的间隔时间都落在这个区间中?如果存在,请对此范围内的样本间隔秒数画出柱状图,设置bins=50。
4 g' @6 v. m5 v% ^( C) |求如下指标对应的Series:3 M" s1 N8 D8 s% g/ |
温度与辐射量的6小时滑动相关系数! N# G8 [4 p9 o( u; ?8 J; h+ e
以三点、九点、十五点、二十一点为分割,该观测所在时间区间的温度均值序列
; {9 W0 c; a) O5 {/ K( V& e" {5 \7 `每个观测6小时前的辐射量(一般而言不会恰好取到,此时取最近时间戳对应的辐射量)9 u/ w. y) V5 f5 i# D
import numpy as np* }9 ?3 P" N& l9 ]7 ~
import pandas as pd
- W- ~/ s9 l+ h# u3 S0 Q9 a5 M# I1
8 C1 F1 j1 c; e6 y22 r* J6 Y7 G- |0 k9 s
将Datetime, Time合并为一个时间列Datetime,同时把它作为索引后排序。( c/ t( D$ U; j* ]/ U H2 k
data=pd.to_datetime(df.Data) # 本身是object对象,要先转为时间序列, W d; Y2 ?3 q1 L
times=pd.to_timedelta(df.Time)- E, H/ w# u. I, J7 O
df.Data=data+times
/ |- ?2 O* u; e+ }del df['Time']- N+ U8 q6 X8 K8 I4 ^ l
df=df.set_index('Data').sort_index() # 如果写的是set_index(df.Data),那么Data作为索引之外,这个列还另外保留/ H5 b5 |! ~" y: j! X' `' h# b
df& e& v. t4 a/ _. X- | u% B" o
Radiation Temperature
: o; M( ^0 C2 h% d) n8 [, X; B3 ZData $ x4 l) I9 c0 H7 U
2016-09-01 00:00:08 2.58 51
" b6 L+ ?/ y# x9 P) z1 U1 R( }+ }2016-09-01 00:05:10 2.83 51. ]3 |& `+ t: }& U1 V
2016-09-01 00:20:06 2.16 51, r- j/ k. \3 I5 F3 N8 j+ V$ P) q
2016-09-01 00:25:05 2.21 51
7 X/ r# b6 ?- V, Q' B/ T) B2016-09-01 00:30:09 2.25 51) C J# B* M% ^/ ~
... ... ...
: M9 l# T8 x( E6 k/ o2016-12-31 23:35:02 1.22 416 x1 J8 K# ?; a% d* |* T2 {: o
2016-12-31 23:40:01 1.21 41
: C7 g& @" N+ S3 ~6 A4 B- i2016-12-31 23:45:04 1.21 42- |* g- b8 V7 F9 Q, B/ B
2016-12-31 23:50:03 1.19 41
- t+ Q0 ~4 f, W7 f- C2016-12-31 23:55:01 1.21 41& d1 Y5 V7 x7 x" r6 @
( \# Q! u* l& P) S* F6 r
1) U2 Y/ g" N2 x9 w$ L
2' C9 n0 E# W5 _' l( t1 e
3
9 W( S0 O1 l. L$ y8 M0 e( o4( B( I' S3 o: [0 E; }+ j U
5
: K$ y. O# M( L: ]; N& g6
$ _) O8 b+ ]- v$ U7
1 g- J* x# v7 \" p3 \" U8% ^* ?& n0 ]! c2 v5 a& a+ U) l
9
" t+ d* U$ t: Z+ ~% X( E10
; `; \/ I8 H T2 q+ v# z( v% t11, C+ J7 U, K% P& M/ D* k8 N/ y
12
1 n2 Z6 ?* m7 v4 {13 d* S$ f0 A c/ W0 D3 i" W( I1 S5 w/ N
14* `% F n$ b/ K5 J
15! |9 l" M/ h6 d" R
16
. L8 Y3 H4 s$ X, E; s b& t0 v' ?17
7 n4 |7 t1 \0 [! K. l8 E$ d, H& |18
- G& ?: p* [: r0 d/ U4 l5 n) y7 q191 _- w$ y5 e# ]3 F2 K# D+ ?6 G- _* O
每条记录时间的间隔显然并不一致,请解决如下问题:
/ ~# s* B0 R* o# k" O$ a/ v找出间隔时间的前三个最大值所对应的三组时间戳。3 j0 K5 ?3 R/ a7 m5 g0 K
# 第一次做错了,不是找三组时间戳8 f& M4 a" k" R( L9 G" P. `
idxmax3=pd.Series(df.index).diff(1).sort_values(ascending=False).index[:3]! J9 N7 E) P1 {" w1 u
df.reset_index().Data[idxmax3,idxmax3-1], @/ X/ _, [3 Q3 D2 ^
6 e2 Z" l* j8 o% Z& Z
25923 2016-12-08 11:10:42
& ^- S9 V! @3 E) P24522 2016-12-01 00:00:02: J7 L/ R. p2 |& l+ E6 ~ B
7417 2016-10-01 00:00:190 z+ a; c: \ q3 w& R7 a
Name: Data, dtype: datetime64[ns], P) W% t! @3 u* k" Z: i( M
1
2 x) W8 d! b) \9 o26 \0 @# F/ K# c- J3 p
3
( g; a! ?5 ^: |) J8 o43 A' F" Q9 Y4 w: |
57 k5 ?, l0 M! o5 t% n; q! a& z" b0 C
6
6 d- B8 t* j, L% F9 U7
' _! ?/ Y. h( N# o, _; A88 J* ?7 D9 W F* B
idxmax3=pd.Series(df.index).diff(1).sort_values(ascending=False).index[:3]& \8 M$ J1 T9 _$ i1 L8 @8 C1 n) B. U
list(zip(df.reset_index().Data[idxmax3],df.reset_index().Data[idxmax3-1]))3 ?3 D0 z/ h$ ~3 F$ R
2 L3 t* a9 k: O+ T
[(Timestamp('2016-12-08 11:10:42'), Timestamp('2016-12-05 20:45:53')),
+ T, N2 ^) t# e$ B/ F (Timestamp('2016-12-01 00:00:02'), Timestamp('2016-11-29 19:05:02')),( R0 [8 J9 ?6 E+ R' ^: ?3 e" B* _2 _
(Timestamp('2016-10-01 00:00:19'), Timestamp('2016-09-29 23:55:26'))]2 j' i5 e6 A& ~8 P. B' k3 @2 k. f& @
1 k' X2 J, Q7 c7 C) s
2
3 d% x: _% @6 K6 q* {% b3
! d1 ~% @7 T1 k) t+ D" A4 \4
: W# D* X3 x1 d( c X! Q5
' S) V+ {" f- H# B6
5 m& @ d v N! b参考答案:0 K5 K% M5 c+ s
# a* K: O0 ] }: @
s = df.index.to_series().reset_index(drop=True).diff().dt.total_seconds()' B, G$ i" O9 l+ }
max_3 = s.nlargest(3).index: W5 X: w) z1 v9 K% Y+ N: ]
df.index[max_3.union(max_3-1)]
! D$ j/ W- j$ I8 B6 W8 t: r. n1 g
7 B& t% \7 x* I4 a( O1 ^Out[215]: 8 C) F* C' I, C4 y$ W- P7 s& W- x% E
DatetimeIndex(['2016-09-29 23:55:26', '2016-10-01 00:00:19',/ p2 B5 P% q# H: Z9 b* Q
'2016-11-29 19:05:02', '2016-12-01 00:00:02',
) z6 ~! a l r! P" Q) B '2016-12-05 20:45:53', '2016-12-08 11:10:42'],
! K" d6 S- X5 f. E6 D. T dtype='datetime64[ns]', name='Datetime', freq=None)# K: j+ P6 N4 [
1( O) K! c. o( m- B9 ~+ Q
2, |2 e4 ?' ^( J/ B) V: P( C" d6 d
3
0 L5 V4 ?$ M( Q4( N$ n( e" D, @* W) Y. u# v
5
5 }1 O; s3 T) ?" h6- t) h/ x. H0 ^$ S$ Q
7
! Y4 F0 V, A, S" F4 x8
7 Y0 L& ^4 F2 y% Q95 r; P/ k% K0 q- G2 d
是否存在一个大致的范围,使得绝大多数的间隔时间都落在这个区间中?如果存在,请对此范围内的样本间隔秒数画出柱状图,设置bins=50。5 l' o" I n/ N; t, D5 v/ A, i
# 将df的indexydiff做差,转为秒数后排序。再求几个分位数确定取值区间
1 f. a5 ~" L8 I' Ms=pd.Series(df.index).diff(1).dt.total_seconds().sort_values(ascending=False)0 R% ] d. K# [) [; K# J; _
s.quantile(0.9),s.quantile(0.95),s.quantile(0.99),s.quantile(0.01),s.quantile(0.03),s.quantile(0.05)
: u0 E$ a% z; l; Q- {) N: k! G2 b' U, c3 Z. `" ~
(304.0, 309.0, 337.15999999999985, 285.0, 290.0, 292.0)
7 G5 v2 d+ L1 ?# u; a3 f1
3 ~' a2 k; g. D8 u2
! ]) o: l2 Y( ?% T4 H7 W& b. [0 e35 O1 {% r+ N- F9 X7 G9 D
4
# g$ W" n/ d3 j8 u- W A55 S; \$ o% n/ h6 m1 h( J3 _! n
%pylab inline
# n& G& b3 O+ X3 q' j5 B" }, ^* C+ S_ = plt.hist(ss[(s.values<337)&(s.values>285)],bins=50)
3 C2 W* T, p1 t2 Xplt.xlabel(' Timedelta')* a. R2 } X7 m& J( [) f& @
plt.title(" Timedelta of solar")
( ^8 n$ |, R3 `4 s1
% C+ Z: z8 t& T- X" ]) t2
. F) l# \3 o4 o) o& @3
% r" J" C! b( D7 Z4 T5 S% |9 P40 `0 Q; }6 Q" u5 q
G) Y3 G" N; Y
. d4 h0 ?9 U% W. L- x0 M
求如下指标对应的Series:
4 z L1 t: B1 W+ t8 j温度与辐射量的6小时滑动相关系数; `, i) N! @! O% ~( N8 H$ F% z( s8 s
以三点、九点、十五点、二十一点为分割,该观测所在时间区间的温度均值序列7 n7 B' a/ j7 D9 l) y
每个观测6小时前的辐射量(一般而言不会恰好取到,此时取最近时间戳对应的辐射量)/ e. E* r6 R- V! }8 }* y
df.Radiation.rolling('6H').corr(df.Temperature).tail()
2 K7 M' c, x2 `9 ~+ N
' ]7 b3 u" P' U. w7 h1 cData
: A$ C4 \& ~6 H0 @2016-12-31 23:35:02 0.416187' ]% i* c% c: p
2016-12-31 23:40:01 0.416565
3 Y8 J$ m( X; W d) }2016-12-31 23:45:04 0.328574! Y8 I- I2 R! K/ u& q4 @
2016-12-31 23:50:03 0.261883# x9 [- x" `/ T
2016-12-31 23:55:01 0.2624066 b4 u I1 e2 }' Z
dtype: float64
8 q k( @; a+ z. ?- P; J1
9 z+ u8 A; C# P/ X4 r9 Q1 e2
0 I1 T, k3 [) k3( @5 s4 a% v4 D) ]3 u9 Y- R9 |- a2 L' y
4
. y2 J5 ^6 ?5 ?/ V5; j/ Q% W0 M0 ]6 p, g
6
, B% c$ O! r' ^" P- S0 c7; z0 F& p: H( w9 c* [; v5 J
85 M9 W0 H1 {7 k9 q) O
9$ W! { X$ Z$ C( _' y6 {
df['Temperature'].resample('6H',offset='3H').mean().head()
5 G3 x" W: H* o; s- x/ T
9 t0 @& t% q) ~0 s3 kData, F' u% x5 x* a
2016-08-31 21:00:00 51.218750
; x* [' m% P9 b, c7 B% k* y2016-09-01 03:00:00 50.0333330 ~; g) Y: B0 F* P
2016-09-01 09:00:00 59.379310
$ b! J3 H }* t* U2016-09-01 15:00:00 57.984375
, x2 \1 ?. I! M: Q2016-09-01 21:00:00 51.3939391 x \# r9 K( ~) W
Freq: 6H, Name: Temperature, dtype: float64% }4 J3 S8 {& ~6 s5 J, q
1
8 j4 m9 ]3 l8 k* E% Q1 r2- r" Z2 L/ z) C& r9 }' d( S0 J
3, N$ ?& a8 R/ ?8 L' }0 L
4
, i9 i; R, J( H5+ n% h! T- E, Z9 w h s
6
7 P! g4 `# i6 a6 m7
: E# e9 d7 l3 _/ E6 h: Z8 i, C& B% ]8
% q9 U, m$ ~2 l8 c9
1 G- R5 o3 I9 h3 Z* c C' k# |最后一题参考答案:
* x3 h5 V1 G. F$ b: C8 n/ d! f5 R4 t2 z# Z2 a$ p( ~7 T
# 非常慢
7 X, h' V" n( B- w$ Q/ |my_dt = df.index.shift(freq='-6H')
' d H( f* }6 O' h- l5 O7 W* x& Eint_loc = [df.index.get_indexer([i], method='nearest') for i in my_dt]) i) R+ F$ v! Z8 z
int_loc = np.array(int_loc).reshape(-1)8 T1 p' {: |# i0 i, A) T* A
res = df.Radiation.iloc[int_loc]6 |, J- z4 p* c5 {
res.index = df.index/ V! m8 L: v# c6 v' ] F4 t
res.tail(3)
4 T0 A9 z! k( x' M; |& T1- e" k9 O. x' x3 ]! J# `- _( m
2
" g( e' j. k& h* D8 A3
) x8 u1 j; `! m* d# ~" J* U47 p, ?& i- ~6 n y; n/ b. A
5& S1 k6 Y$ {1 ?
6) l2 c8 J" T1 _4 h" r4 h8 L
7: B; l c! {: j' ?" l! e
# 纸质版上介绍了merge_asof,性能差距可以达到3-4个数量级
- B& r& r: |; ptarget = pd.DataFrame(2 n8 w& x" G; s- t- u7 Z
{
4 J, g2 G& R7 ]4 Q' @* X "Time": df.index.shift(freq='-6H'),
+ \# d4 o" X4 v% R, D* w "Datetime": df.index,
9 h6 q9 i$ i( T9 b; W+ h5 y( w }- M$ ?2 w& \% a5 k
)2 n# W/ x9 }9 I' ? R! U# h
% f$ |4 y6 F: S+ ~$ \
res = pd.merge_asof(7 U, C# L! T k8 L7 l% a v
target,
9 s6 K3 V- k3 {. V* c df.reset_index().rename(columns={"Datetime": "Time"}),. J" r- P9 _2 p) P1 B8 k3 p
left_on="Time",
3 }! b% j4 V- o5 _ right_on="Time",
: c' R4 d; \! D direction="nearest"
2 h! N! t' S% L).set_index("Datetime").Radiation
( x' }+ D5 O9 A5 W! x" l! z7 R+ o9 g- t: x1 D
res.tail(3); t2 e/ U; A- f+ P9 r, A
Out[224]: $ r$ G w7 u8 |# f1 u7 R2 ~
Datetime) n% X- W. M! E* V6 h
2016-12-31 23:45:04 9.333 U* y* B/ s8 {* _0 B
2016-12-31 23:50:03 8.49
c0 ]! {+ a* ~( \2016-12-31 23:55:01 5.84
6 w* M6 W. C9 E) S. x3 i W$ F. vName: Radiation, dtype: float645 `7 k3 W$ m. V$ G( c
# S2 J6 F- E7 H' X i8 j9 l+ j5 P19 Z5 l1 a$ \: C" w0 d, H
23 B8 r# X2 ^, z; {& c
30 K8 U5 {# }* i8 y& B
41 a$ a; ~, j& p# P: _8 s. _+ \
5
9 F8 B9 u& ]( ?" g, G* J63 _# o' o& \$ S
7) f' Z$ L m7 r4 i8 J
8! `' f* O, _) D% i
9 ^/ E1 r ?& I9 \! O0 Q2 D, e
10 c8 q% ^) }1 O: S$ ?
11
. z. y" Y) a, O: ?12" K4 I- P) H' F) \; a4 c8 x( h
13
" Q9 w2 P( o* e/ o- u14
- U' e7 q1 `8 B- Z. x% r15
6 ]6 n6 `. _7 ~6 }; K* q. F- M) `16+ ]" Q: q) ?' D
17( L, Z# b$ b. H
182 ~9 M7 [" [0 {: X8 S1 {9 A( d
19+ N. u& `9 `% o- ]" M+ R
20
4 r' i2 i5 o1 f0 O! u21
0 C. I' ?% u' \7 a' [22
7 O1 _' b: {! R# j% f23
- v; C( u9 ^* ?. ]6 X: m2 b4 zEx2:水果销量数据集" F4 A. W3 d, S1 ?( y
现有一份2019年每日水果销量记录表:
# P9 i, k# g/ z
6 B+ k1 q2 ~2 ^* Kdf = pd.read_csv('../data/fruit.csv')$ O: d& w3 x0 \0 O. U' |3 e. C
df.head(3)
1 J7 ^8 q* A' v. ^
/ u# I M; n% VOut[131]: 3 k1 ]& Y! S! V9 W# t2 F
Date Fruit Sale
: o- Q9 @8 V# Z" i0 2019-04-18 Peach 152 `$ V, a2 m* d: H7 A
1 2019-12-29 Peach 15
* @) o+ Z9 n- z4 E4 Y2 2019-06-05 Peach 193 q: O+ w" V5 Y) Y/ q0 J
13 L% Y, k, q/ I' G: G# W0 t
2
! |4 ]. _" c- S: G y. q3) }# L, z' k2 E8 ]- o
4, [, }) [. \( |9 J% j6 v N' W1 I6 W
5
2 x: A- ?8 b( `1 P" o6
5 g; v7 N4 b$ \" K/ Q! ]" g7
: ~$ J# @1 ~ P/ Z8& }+ i9 k0 Z }* F+ [
统计如下指标:# n- R5 f6 G+ w/ N; W# m9 Q* R
每月上半月(15号及之前)与下半月葡萄销量的比值: c8 S! [' _. }' ?9 b; d9 C
每月最后一天的生梨销量总和- J7 B, r: `1 G% I) I
每月最后一天工作日的生梨销量总和
2 j6 b4 b$ t8 \; u每月最后五天的苹果销量均值
$ W9 e& G, [+ b! F5 @按月计算周一至周日各品种水果的平均记录条数,行索引外层为水果名称,内层为月份,列索引为星期。
9 f/ _2 D; e+ z. y. u$ j按天计算向前10个工作日窗口的苹果销量均值序列,非工作日的值用上一个工作日的结果填充。4 [& [7 a8 T# t6 z. S. e
import numpy as np
* `0 o4 U' j* \, M( Vimport pandas as pd
1 p7 n# K+ I, ^0 E4 I18 b/ m& h) K/ L0 j" G
28 D! ?2 a6 i3 ~/ ~4 s
统计如下指标:9 d) S: Y2 e0 o$ g+ t6 s8 c- X
每月上半月(15号及之前)与下半月葡萄销量的比值3 C( M* W, f2 n. i
每月最后一天的生梨销量总和
- |- N3 {7 \6 J3 ^; X每月最后一天工作日的生梨销量总和
" @3 i; E& T3 u+ {每月最后五天的苹果销量均值
. ?, a$ D" J8 s& y0 Q6 J# 每月上半月(15号及之前)与下半月葡萄销量的比值
* A2 h6 `; ? E; Q Ndf.Date=pd.to_datetime(df.Date)( k( u# N$ d% K# L+ t
sale=df.query('Fruit == "Grape"').groupby([df.Date.dt.month,df.Date.dt.day<=15])['Sale'].sum()
4 L9 h; f; {- Y& Ssale.columns=['Month','15Dayes','Sale'] # 为啥这么改没用啊& y. H6 Q+ y8 _- v' n2 G. y
sale=pd.DataFrame(sale)6 p7 o6 ]2 a: M4 k
sale=sale.unstack(1).rename_axis(index={'Date':'Month'},' \; p! ^4 [" Y8 y6 g. _2 ^
columns={'Date':'15Days'}).stack(1).reset_index() # unstack主要是两个索引都是Date无法直接重命名
& f7 h' r8 u4 g/ a, h$ Ksale.head() # 每个月上下半月的销量8 l9 S& w5 D- k/ P$ K+ [. F
- d) Y3 |' V: f6 d Month 15Days Sale
) }7 v! L8 P5 x4 Z/ \$ ~0 1 False 10503
1 R! T$ M+ R, g+ Q& `1 1 True 12341
7 M! A0 L+ ?7 G% U4 V& v( z2 2 False 10001) Z* y6 k+ k% H! M4 }
3 2 True 10106+ a$ H2 C3 [; L* {+ u
4 3 False 12814
6 u3 f; I7 x2 X0 E7 |1 J5 s$ B
0 t) f: O3 ?/ B# 使用自定义聚合函数,分组后每组就上半月和下半月两个值,根据索引位置判断求比值时的分子分母顺序7 K. R. F- j* Q
sale.groupby(sale['Month'])['Sale'].agg(
c3 J; S2 z% ] lambda x: x.max()/x.min() if x.idxmax()>x.idxmin() else x.min()/x.max())
2 ]' N/ ^. ^% \" v% Z- r
- q+ n+ l, S3 X& [7 n" uMonth9 P+ a. x3 F) L; u- M! T0 |
1 1.174998, C; W. d0 U; s1 ]
2 1.010499! \) |0 \5 l' g% o! p. B
3 0.7763389 D( ?& e0 A' l1 N$ }( Z' M7 h
4 1.0263455 D0 ]. R& W, T5 T8 [/ _1 m
5 0.9005347 s% N# ^, f! o Z7 R8 E
6 0.980136
3 s7 }4 ]9 H6 M0 o6 N7 1.350960
2 {6 S7 y* K' d& H( Z3 i8 O5 N8 1.0915847 F0 B5 H' T1 @! R
9 1.116508
} f m, H/ ]6 j N" r10 1.020784
" x' K* n Y; E$ K, C& p11 1.2759113 R+ M' w0 B- x- g+ i
12 0.989662
; l& t1 X- p/ V* O/ _& ]% j! EName: Sale, dtype: float64
: b- y7 ~( N0 e8 U. x4 ?; W3 B _- {0 B, @
1
, E9 }$ A8 W: l. B2 _( X# m6 j( ]2
, M! y1 Q4 [) m; A% _ k$ }3
6 f$ F: g$ [) c. K& K4
; c; M6 g" A/ N: N54 M+ t3 U: P/ C# `; J$ k
6! b% t4 ~) ?1 {
7+ |! G. ~ G0 l. I) s) Q2 H
8! q3 V, e# \% u$ z. _' h
9" Y! m( y# J5 Z4 N" M: W: F
100 Z3 I a) `' j0 s
11* h/ t9 r* `& X9 V0 e
12) ^& T5 N% A" W" S- e# E
135 w/ Y$ Z3 d2 E( _
14! z/ ~+ z5 s- U/ }" z; G* r
15
+ b, E0 n. p$ K! d/ q: O164 \; j4 W# D& l* n! ]7 ?8 q
17' ^1 A+ d5 A1 Q
187 F% r9 T0 a. P# M) b
19
2 [$ c6 ?- h: i" S. E c3 S4 d205 J. G. ~. v# e6 l
21
! S5 ~% _( C: H$ H4 @3 h22
* B3 i- |+ V1 |. N237 P# i: m# S* ~
24
. ~7 o; p1 X& Z) \& x25; L, Q& K* |$ m% a, k- G, S6 }
26, q8 s, |) a- \/ X
27
" ?; f7 O& n1 g0 D5 A$ D: ?28+ N2 G. [6 A- K, h( ~5 q
295 T( X" x) @# r
30: K+ V+ {4 d, l* S
310 M# [, r, s/ b% D3 A
32
7 j' w5 S7 R- @* k5 r33+ v# @: ^6 l5 p
34, ?: \+ N, k1 S+ @8 A/ ^( `$ {* u
# 每月最后一天的生梨销量总和
$ P$ u3 ]' g( C4 \3 C, p* w) fdf[df.Date.dt.is_month_end].loc[df.Fruit=='Pear'].groupby('Date')['Sale'].sum()
. T3 O/ ?4 t! z' M; [4 D# E: N' M* L) x& `7 i$ a# Z$ h& _
Date9 |% Q$ F+ o A. }# e: c* r
2019-01-31 847- j# I+ d% O% r; n
2019-02-28 774
m; p5 B; A* r6 m2 G) W$ `( Y( ^2019-03-31 761: c" W' Y4 w$ [, J' x$ `1 |# _. P1 f0 K
2019-04-30 648
r$ m x1 C' ] ?. ?2 b& J; n' N# x" X2019-05-31 616! D) u. e: ~9 y" ?( G
1
( O' k1 l! F4 |1 L; s* `26 Y4 u* f( p: e- d+ [0 f! T$ f( D
3 t4 K, S- \: Y# Y% @# i2 U
4
& U4 x! ^5 c+ z4 Y" Y8 m: {5, [5 U2 y# l' ]$ e4 r" E8 B' V
6$ }5 U9 F! L6 j& c: a; u( p) G+ M8 R4 P
7
) a1 d* C9 N$ p0 G; c& w3 t8
6 h- |: B. i& H+ u2 o+ q8 _9
, @3 m0 w. R( y2 Y% x# 每月最后一天工作日的生梨销量总和! M/ | \6 ^7 {! N
ls=df.Date+pd.offsets.BMonthEnd()2 \- t: K3 b/ D0 I- a$ _/ G
my_filter=pd.to_datetime(ls.unique())
' ]% H2 W# u. \. @0 ^3 s, F- ?; ~df[df.Date.isin(my_filter)].loc[df.Fruit=='Pear'].groupby('Date')['Sale'].sum()
3 s9 i8 m4 a/ F8 A6 Q: _. X+ B2 \) T2 ~6 l/ b
Date$ ^5 n4 b n2 p8 w+ f5 Q
2019-01-31 847
. e# ~5 U v F" }# F- c& n2019-02-28 774
2 S# [& t/ S7 a3 y0 O; u" [2019-03-29 5101 l7 l b3 L2 G! A7 b# x* n
2019-04-30 648
7 _+ J4 `+ r& z+ `, M# }2019-05-31 616) Q @2 `9 h, S" h f6 f0 \
1
( P( _1 s0 P! B' c2 t) E6 o) o& x4 s2! n7 d: o w) H# P: L- R9 N
3! T7 v6 M/ G j$ `: ?4 h( @" d3 b
4- W1 j& w9 [( X# T
51 |5 O7 K' i0 K
63 w+ A+ ?! y* d& G
7
- ^ O) [7 E: F$ Y+ d, H8( N7 @: t# P0 x
95 j5 z1 w' Y4 u& r, @# g8 j
10
# t) h2 K& v! C: T$ Y c5 D11# F1 O: R) Z) O# b9 i
# 每月最后五天的苹果销量均值, D6 a: n, z1 j: ^
start, end = '2019-01-01', '2019-12-31'& L& F+ z0 Y' i3 V* S
end = pd.date_range(start, end, freq='M')
% V4 @( Y0 ^5 mend=end.repeat(5) # 每月最后一天的日期列表,重复5次方便做差$ ~8 _/ ^# ?# X) o
- m6 O, x: y- A* t* t' m
td= pd.Series(pd.timedelta_range(start='0 days', periods=5),)
L! E3 c8 M( {7 ^td=pd.concat([td]*12) # 日期偏置,最后一天减去0-4天
, R7 |3 l# f# c' Tend5=(end-td).reset_index(drop=True) # 每个月最后5天的列表
9 M, e/ _6 E8 b4 [# ` t- h
# L! h4 S8 t q' fapple5=df[df.Date.isin(end5)].query("Fruit == 'Apple'") # 每月最后五天苹果销量
* P4 Y+ p) r. c5 z- H3 p& Gapple5.groupby(apple5.Date.dt.month)['Sale'].mean().head()
, `% \2 N8 D6 l. S- }8 R9 \, E* Q* S* R, Z. _
Date: A- T2 |% W! `, K
1 65.313725# c% N, @1 c! W$ V) z8 y6 W$ s
2 54.061538& P v+ B! v* D6 ]; F+ U% T" }0 D3 g
3 59.325581
' v& y+ C4 W% e( I2 Z6 `- z8 } i& ]; K4 65.795455. k9 T" S: W9 `8 e0 n8 J
5 57.465116
/ R4 ]. L: @8 C5 }2 O* }& n9 z* V2 j: v9 _' d# R
1* l, v m9 p8 P( P
2
+ }. ?# \5 R% Z6 k' o38 E0 E- Q6 ]/ |' K$ |
40 r' u0 {8 w. C7 H' x5 j
5
7 g% u1 s: c2 ~6
; ?! e4 f' `& X7
+ b! d: T+ B f8
# k$ W4 m4 }8 ~! j9& }8 a* l* J' O" S! o
10* W! H8 k0 N' Q7 Q! q' z& B ~+ c
11: i) J4 n. u! F/ U2 ^
12
8 x# O7 S2 u& N6 K13
% z. X, h( ?# W& d; p14
) X( f0 p$ X; H" H1 p15
; K" q; V/ _2 v8 m16. }. ]! y! G% [2 e* j! d* {0 ]' \ J
175 ~& b* g, Q4 {& q- G: t
183 m# l; G( y5 v$ @( I8 @7 }
# 参考答案:5 t, v. y$ i, e/ R$ O
target_dt = df.drop_duplicates().groupby(df.Date.drop_duplicates( `, X! H( B! `
).dt.month)['Date'].nlargest(5).reset_index(drop=True)
: I; H2 H' S* |. |4 X" n# B+ Y8 ~2 D
res = df.set_index('Date').loc[target_dt].reset_index() D7 w% @8 }& I. j& c/ u& M
).query("Fruit == 'Apple'"); |0 p7 r5 N+ ^
/ l. F) g2 [* c6 V0 {+ ]
res = res.groupby(res.Date.dt.month)['Sale'].mean(7 r ?. a& a* r
).rename_axis('Month')' e! R# w- Y8 C( F8 P* t0 t
( `0 o1 |- V3 b6 z% S" e% H! S3 o8 d. H6 l& ^! {; S9 f
res.head()
: L# j+ Y! A4 S& t3 j' {Out[236]: 1 }6 u. ?( U$ Y
Month" J) Z1 O, K" |$ N+ j8 w- u
1 65.313725
# `! e2 ?6 h5 |' W$ d2 54.061538
^7 W2 y! \' H3 59.325581
% g- n1 W8 J2 |0 S. H4 65.795455* m# \+ i! ^2 d% U! E8 I
5 57.465116* E( E% W. p6 B1 R/ u! ~
Name: Sale, dtype: float64- \! K! p ?/ Q2 T: h4 Y
4 Q9 }3 q) G) H1 |
1
& a; \, j7 C. {7 H$ S/ F" ?2+ \% G4 |2 F1 f
3
e, O: v7 U- ^: f" u: W! A4
3 }: i' z5 p g: P4 N1 i& j5" C& h) k- ]; c' B0 b# S! j0 G$ g
6
" k2 g. o/ d$ p/ q7 L7 L9 k1 ~4 J* m
8
. ], V( J/ S* S# r$ W9
7 ]( u& L2 v: i9 L! Q* e7 X" U5 R10
' ]: X0 ~9 i; t' t7 r' ~6 A: O11
8 z% {4 E3 W; D6 ~12 `* g1 ~8 G3 y7 ?. A9 G; v9 A
13
- R( K/ e, ]: y6 D1 F7 K4 V14& h( Q) ^5 ^! l5 Y' @0 ]
15
. T0 C$ b- Q' H3 U: r" W$ W16 D* \0 ~# R0 Q( [0 X* z
17
0 A! ^- i- C* L" _7 g) S1 X18
0 Y3 e3 A2 @: U0 t" d19
A" H6 q* B( O$ P6 P8 A2 B20
% S7 }6 S+ j, H- x& T! q按月计算周一至周日各品种水果的平均记录条数,行索引外层为水果名称,内层为月份,列索引为星期。
O+ M! [, K Lresult=pd.DataFrame(df.groupby([df.Date.dt.month,df.Date.
: w3 A( f4 b8 V9 ~: f dt.dayofweek,df.Fruit])['Sale'].count()) # 分组统计
" y+ K/ B: }3 x; ]8 S. [' V ! Q2 t/ p, u& x. d p: k
result=result.unstack(1).rename_axis(index={'Date':'Month'},8 V- ^; R' i; H( K* e3 f
columns={'Date':'Week'}) # 两个index名字都是Date,只能转一个到列,分开来改名字./ K6 f, h( U, g j8 Z) L
result=result.swaplevel(0,1,axis=0).droplevel(0,axis=1)' X' k4 {6 _+ G: h* f9 V
result.head() # 索引名有空再改吧
: R0 ]9 E: Y$ O+ o9 b7 T) [: e/ ]1 O2 |1 q; `! S; `% J8 _8 p
Week 0 1 2 3 4 5 62 k' ?/ L8 F6 |0 l" x" p* O! l1 u
Fruit Month
# ]' _5 W( V; E+ f1 ^3 WApple 1 46 50 50 45 32 42 23
6 O: |6 G( \& J. }2 y/ _4 yBanana 1 27 29 24 42 36 24 357 s/ f* j) {' [: u" X8 w; h: r
Grape 1 42 75 53 63 36 57 46
6 u* Q# Y" o" cPeach 1 67 78 73 88 59 49 72
% q' J& d% d; n/ t- O6 EPear 1 39 69 51 54 48 36 409 C3 Z7 m b% S. W$ W8 f6 ?
1
$ _& ^# |4 ?: f5 S c2 T# [0 P27 N- k/ ]" n: F* X& L" s; V: t
37 M; E7 o: ^! e
4
( S0 o' Z$ O) U1 \" o5- ] M/ ^( w0 [3 B( Q
68 _) s1 U' g' q+ _- F
7
& x2 l8 G2 y+ G2 N* K; L" f; y) D88 K. ^7 Y% x; g: Y1 C; a c
94 ?. k' n, f/ F0 E* a- m: R7 I
10
4 N; p6 u. L6 y1 O, O116 ^1 t5 ^( z3 E2 T) r
12
% U1 L6 L, k4 `. p9 v0 ^) ~13
; Z: Z" M1 c0 J' |# v2 H14( u* W0 q2 C; [! ?4 ~! q
15, I" G5 C3 e% M5 }0 D8 h$ Q
按天计算向前10个工作日窗口的苹果销量均值序列,非工作日的值用上一个工作日的结果填充。
& b1 M7 b! k. [# 工作日苹果销量按日期排序
7 O# ]! q. ^! M! Q! Q0 x1 R' rselect_bday=df[~df.Date.dt.dayofweek.isin([5,6])].query('Fruit=="Apple"').set_index('Date').sort_index()
% F0 q/ I+ Q: f. j/ b7 F V* ~select_bday=select_bday.groupby(select_bday.index)['Sale'].sum() # 每天的销量汇总& E4 |9 b0 o# X# @! @ ~
select_bday.head()4 O$ Z5 K4 s4 b# X
! s0 V' r# f$ @& ?- R, FDate
# s" F, O% V/ \3 s6 V, ?" f. Y4 f) O2019-01-01 189
: K# B0 D% G& a2 p- D- n' J2019-01-02 482* e0 G2 e! g$ B5 E K3 f
2019-01-03 890
9 H+ C7 X: |" X, g! w0 Q+ p2019-01-04 550
, b6 U( b2 @5 w6 u' U2019-01-07 494
0 H4 r2 U* J: Y9 y$ H" V; u
" o% Q5 i! C4 Y) ?- }# 此时已经是工作日,正常滑窗。结果重设索引,对周末进行向后填充。7 j* w$ k) P. _! {$ h
select_bday.rolling('10D').mean().reindex(df.Date.unique()).sort_index().ffill().head()7 P3 V! f' A) T% b1 `4 c
' I9 s7 P I$ S* M3 R* a
Date
, N: e& C" u- g2019-01-01 189.000000; i" f6 f6 X- ?5 b. d" u* L
2019-01-02 335.500000" a1 O0 Z3 x& `7 y! M* D' `
2019-01-03 520.333333
; C9 ^/ p& Q* D- S* M |2019-01-04 527.750000
1 R- T) V5 f6 A2019-01-05 527.750000
4 r2 W: H7 f. O2 M0 s0 ~; b( P2 C7 V$ A: i
————————————————& k8 h7 T/ ?% I4 f
版权声明:本文为CSDN博主「神洛华」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
# H R6 s0 l7 H原文链接:https://blog.csdn.net/qq_56591814/article/details/126633913
+ d/ Q0 k8 Y5 E$ D9 e
! t% j, d& ^* A: v, {* \- @( R7 q# t7 o6 g
|
zan
|