- 在线时间
- 1630 小时
- 最后登录
- 2024-1-29
- 注册时间
- 2017-5-16
- 听众数
- 82
- 收听数
- 1
- 能力
- 120 分
- 体力
- 563253 点
- 威望
- 12 点
- 阅读权限
- 255
- 积分
- 174199
- 相册
- 1
- 日志
- 0
- 记录
- 0
- 帖子
- 5313
- 主题
- 5273
- 精华
- 3
- 分享
- 0
- 好友
- 163
TA的每日心情 | 开心 2021-8-11 17:59 |
|---|
签到天数: 17 天 [LV.4]偶尔看看III 网络挑战赛参赛者 网络挑战赛参赛者 - 自我介绍
- 本人女,毕业于内蒙古科技大学,担任文职专业,毕业专业英语。
 群组: 2018美赛大象算法课程 群组: 2018美赛护航培训课程 群组: 2019年 数学中国站长建 群组: 2019年数据分析师课程 群组: 2018年大象老师国赛优 |
6 V8 i$ B! v1 f: E* e( `
2 o' n/ Q8 R6 t) w3 Z) n
- z* Y$ F, J. q$ y0 O文章目录! X3 A3 {. ]' L8 u2 ^) ~. }
第八章 文本数据
9 B$ v! \! Y" O; g$ Y8.1 str对象- @! s, t6 F7 L" o- Y7 P( I
8.1.1 str对象的设计意图# f5 d: N. t) z4 [, m6 p
8.1.3 string类型
! v1 }9 O$ p8 [1 f" x0 e: R+ W8.2 正则表达式基础
* N" X# O: K) B* W# c, u8.2.1 . 一般字符的匹配
- P) K( e ?. f4 a! ]( H8.2.2 元字符基础& r u* |' ~5 g/ J8 |, C( y6 K! @
8.2.3 简写字符集
+ C0 i+ j3 `! W1 q1 {8.3 文本处理的五类操作
8 B, O& [7 y' a6 d5 ?7 H! m8 G: y8.3.1 `str.split `拆分2 F) {5 r, C6 U! P, R7 R$ W9 C
8.3.2 `str.join` 或 `str.cat `合并0 A3 D' [& Z% M( K
8.3.3 匹配$ v) E& ?$ i' B
8.3.5 提取
0 `0 n1 w' d3 @9 S) C% p8.4、常用字符串函数
) \ \6 S- @4 L1 s" v; e& B8.4.1 字母型函数
. j- v5 Q" D& S% o+ o4 ?/ q3 i8.4.2 数值型函数
9 t6 K+ f; p' J' z1 J8.4.3 统计型函数
3 i" P# L" M4 ?$ [6 A( W, x7 [8.4.4 格式型函数! H! m4 F! [8 E4 n. Q
8.5 练习
& u6 M, r; ]0 @- C; L$ YEx1:房屋信息数据集
4 ?! A) s/ X! QEx2:《权力的游戏》剧本数据集# U4 Q ~; k T4 r) q! p
第九章 分类数据
7 a* O% h/ y- a# H- I( Y0 r. }& H9.1 cat对象7 B' W9 v, {5 @/ l# E: ^0 ?! S
9.1.1 cat对象的属性
" V1 H! W8 I; o# M! T9.1.2 类别的增加、删除和修改
- r3 J4 ]* `7 S. r+ y" d. }6 U2 m9.2 有序分类
3 J+ x) U/ L8 p! m9.2.1 序的建立2 \" P# t2 |6 F5 u- n" _
9.2.2 排序和比较
( T: H; _& e1 Z+ ~. a6 B& D9.3 区间类别! d# S9 V9 h1 m6 e
9.3.1 利用cut和qcut进行区间构造$ D9 E& m' t+ r* f. P6 K' q
9.3.2 一般区间的构造. h* e/ Y" O7 j8 w6 t* G
9.3.3 区间的属性与方法
K/ Z9 o! B2 r/ u9.4 练习
4 Y% G9 I) I; J; l6 iEx1: 统计未出现的类别! g9 V4 C; o' b- O% V3 _. g" N' c
Ex2: 钻石数据集
$ f) x. I! _! `& j1 c第十章 时序数据
3 [; w l; S$ O! E# p10.1 时序中的基本对象! Q' N% a4 R) i. `* k$ c
10.2 时间戳
6 Q$ w- }( |" @- c10.2.1 Timestamp的构造与属性+ V/ o% q) Y) |
10.2.2 Datetime序列的生成. r- a0 e; |% d8 q+ A
10.2.3 dt对象
# K3 N5 u0 M& D n. o10.2.4 时间戳的切片与索引
* B6 W. a7 n( Q' Y10.3 时间差. J: v6 e4 T. o6 l, Y
10.3.1 Timedelta的生成6 p0 c+ U: v; ]7 e
10.2.2 Timedelta的运算. A/ A; Q: x1 M7 K% L7 W
10.4 日期偏置- E& `( k6 M. G0 }. l
10.4.1 Offset对象6 }3 N9 c" u8 ~4 S% J4 B2 p1 v$ f
10.4.2 偏置字符串) U# W' k' _" v9 U. G, Z/ c8 {
10.5、时序中的滑窗与分组
3 Y$ P( ?' N! u* A8 o% y10.5.1 滑动窗口
* ^& m$ J" a9 V; v10.5.2 重采样4 T) R0 L; K2 o: D) k+ I
10.6 练习3 W, @* a" b$ m9 w" V8 p
Ex1:太阳辐射数据集
3 i' h- H* p( K5 J6 Q0 F& cEx2:水果销量数据集
. g6 E& z& ]) m 课程资料《pandas数据处理与分析》、github地址、讲解视频、习题参考答案 、pandas官网, c# N3 R. ^% `2 w
传送门:
- v- a6 h: [; K) g9 e* |' h, r9 E
0 F& o+ v( h2 m5 Q! tdatawhale8月组队学习《pandas数据处理与分析》(上)(基础、索引、分组)
& t; i/ L0 C$ X5 ]& ndatawhale8月组队学习《pandas数据处理与分析》(中)(变形、连接、缺失数据)* Z8 ]: n7 D( O8 O5 m1 A% j
第八章 文本数据
% N4 U1 E5 `& H- G/ E8.1 str对象
" b- d: d+ f" |/ K* w: Y8.1.1 str对象的设计意图# ]: }' B: ~8 \3 q' F# |# a
str 对象是定义在 Index 或 Series上的属性,专门用于处理每个元素的文本内容,其内部定义了大量方法,因此对一个序列进行文本处理,首先需要获取其 str 对象。在Python标准库中也有 str 模块,为了使用上的便利,在 pandas 的50个 str 对象方法中,有31个是和标准库中的 str 模块方法同名且功能一致,例如字母转为大写的操作:
. D! t1 y' u! B) ?; n$ c$ D6 ~# X) ] ^7 |0 `
var = 'abcd'
: [ h, h- M" `, d( R& W% ~. E2 `" gstr.upper(var) # Python内置str模块
$ y& N! n u7 X; _( oOut[4]: 'ABCD'" R4 k& i! A5 {3 @2 z9 h/ w5 `2 G
8 j+ J3 {: k% |% l( r! Y3 ns = pd.Series(['abcd', 'efg', 'hi'])
9 ~/ n% W, k* A$ y8 k! E/ |7 B) ]4 V+ ?5 x3 K1 d( [) N
s.str; R' C4 d3 V: E7 X! n6 t6 x" P5 l
Out[6]: <pandas.core.strings.accessor.StringMethods at 0x2b796892d60>
: @9 I8 i- B) `9 K3 l
1 `: \! `: u: f q. |s.str.upper() # pandas中str对象上的upper方法* p) I* g7 q1 j* K K; k
Out[7]: % l7 S2 B4 X1 {; ?( `- o; I
0 ABCD
# c' `+ t0 r1 d2 t: |' |, | c1 EFG3 n9 r0 h* E( R1 C7 H+ v
2 HI' x" ]" W/ r) h3 a8 M5 J
dtype: object& ~: G* _6 l4 i- e4 l' M
1 A: {' W! A0 b( C* z: p$ m
2
6 A$ i/ m/ K7 z- r! H; C8 q38 F4 I9 K8 c1 y2 B6 O
47 u* K( U* b# y& U
56 t% w7 s8 B) a+ F7 ]
6
, w" ? a) ?' T, `! ?0 q7
4 c# p7 R6 P3 w+ G+ j+ X8
% A- C* j7 Z2 ~# g2 y9
7 t' c/ _! z8 i# u5 x10
- u, u. f; k( z, w11
' W9 w" ^. o J+ B, \" Q; [12
0 a7 r: P. d: n! \( J5 b* U# j3 Y5 M6 ^13
6 J' Y( f( s) q143 g, |/ c7 t0 E( U8 F
15/ _8 e( v" c2 W
8.1.2 []索引器 h. e* D" H; q3 y# S
对于 str 对象而言,可理解为其对字符串进行了序列化的操作,例如在一般的字符串中,通过 [] 可以取出某个位置的元素,同时也能通过切片得到子串。4 O' a; r" P' s, j( I1 x
pandas中过对 str 对象使用 [] 索引器,可以完成完全一致的功能,并且如果超出范围则返回缺失值:
5 R' S& M" E& V0 k0 Q/ ^. J, ~+ f) ]8 j
s.str[0]
9 V1 T3 d5 C' O; r. KOut[10]:
. {+ u8 [5 F/ j0 a
0 B/ e4 N$ [* g, @' q, g6 g( L1 e3 |! T6 t9 I4 T
2 h; U0 V& s( m7 `& d# a' l
dtype: object2 R: }& n# I- `1 k* F6 j `8 s
9 ~! h7 x) c7 ~" H1 d3 t. H$ ]
s.str[-1: 0: -2]
2 d4 i$ g. i- z/ aOut[11]: 4 Z& D" ~: _ s* v9 r, B% x
0 db
~- `% i2 q3 q2 T+ E9 t6 F4 w1 g- t4 D$ D; R& d! x
2 i$ s) g# e o, U, T O
dtype: object
; m! e2 X3 B2 a0 A# B4 |7 T+ r( ?: `0 c9 U1 M& l% x& w4 Q0 c
s.str[2]
5 a8 D D' ?9 ~& ^$ O# x: WOut[12]: - |8 Y7 B* c: Y. y
0 c
8 S0 x' o" n; E; s& V1 g) J+ S$ o+ N7 C$ q
2 NaN
, L6 f. L, y* u( [' w \+ L) jdtype: object
& N! D7 X( l) ~: T
! v4 M% Z, d s1 X% N% H `1 M1: ? w% Y- q( v/ h! T( C/ o0 k
2
~) [: R6 ~( Q/ P. E3
$ F4 D- c/ B+ Q ~3 V7 |1 ]47 S- R, W& W: S a. ?& ^
5( k) T. ^1 R0 e8 _
6
( b# h" y- F7 x* i2 u4 k+ g( q7" \4 X# ?' }% x6 N5 j
8' h4 j1 _+ e& v$ i7 k% _
95 B2 N. i. z9 O6 I# J
10
! u4 ^: N& H9 {9 F Z" b# ~11) E4 K9 J" X" K3 n/ v
123 P0 v5 s1 f: Z% [
13
" k5 p+ K( l/ e* S' j14
" | F, E! d9 x/ i9 r157 X; p$ r+ U8 N `3 j
16) J& s5 X: e/ u" a# b
17
" ^/ {+ ~" e5 S/ K185 x# g$ A2 A3 ]9 K4 W6 }# N
19
- |. P( ~; ^- R6 k$ B20
* z+ I; \' E0 u) t' M5 r c2 oimport numpy as np" m1 t8 t- |) s- Y' e' ~( d
import pandas as pd
+ n7 S& }5 p7 A0 V6 V/ N3 V
$ S; I9 M2 y$ h( [s = pd.Series(['abcd', 'efg', 'hi'])
3 M9 }) y0 O q. us.str[0]
2 {( Q% ]+ M9 D# ^9 v6 @18 }# Q7 [/ v. @: X) B
2% L. g6 e# M' k- B. E4 x& A
3
' d' h* z! F3 J }! M- @, Z4
3 \% i0 f B8 y7 d L5
W: l9 c$ V# u" X# t7 U6 }$ d0 a
" d1 D- Y- l% d1 e2 O5 R8 A2 k. [5 Y3 [
2 h
9 y. M: d& q" N8 v/ r8 }3 p" ?3 ldtype: object
, ?' l5 R# B9 }+ q6 V3 c) }1. P1 x! N& ~# L( w, \, {
2% p6 w8 U/ ?3 }5 b: H5 o# r
37 d6 d5 D$ `1 J6 D1 g- }" O
4
' i8 J8 {7 r6 c2 X1 w8.1.3 string类型- o5 r% a( d& g% q
在上一章提到,从 pandas 的 1.0.0 版本开始,引入了 string 类型,其引入的动机在于:原来所有的字符串类型都会以 object 类型的 Series 进行存储,但 object 类型只应当存储混合类型,例如同时存储浮点、字符串、字典、列表、自定义类型等,因此字符串有必要同数值型或 category 一样,具有自己的数据存储类型,从而引入了 string 类型。
1 L$ V) ^/ W9 b$ A. i7 Y" \+ I 总体上说,绝大多数对于 object 和 string 类型的序列使用 str 对象方法产生的结果是一致,但是在下面提到的两点上有较大差异:5 p, ^# M9 ^' [" ?
3 {1 A5 a$ }, U7 A6 m2 l( z8 N
二者对于某些对象的 str 序列化方法不同。' t% F( Y. J4 ~8 R2 t
可迭代(Iterable)对象包括但不限于字符串、字典、列表。对于一个可迭代对象, string 类型和 object 类型对它们的序列化方式不同,序列化后str对象返回结果也可能不同。例如:& [' q% {5 U* b [# T
s = pd.Series([{1: 'temp_1', 2: 'temp_2'}, ['a', 'b'], 0.5, 'my_string'])0 A9 ?/ h' `) y$ }+ D4 E* k
s
! h6 Z C9 h, \: A3 w5 o1' Z" r( }" ~% A5 z+ u+ h
2* y, v3 R+ u# {# V
0 {1: 'temp_1', 2: 'temp_2'}
, J* s& s ~0 Y$ S* |" p1 [a, b]
5 t* ]8 {$ x6 N2 0.5. R' \/ l/ `" H, K9 ^
3 my_string. `9 q. S) q$ o
dtype: object$ h0 V; m8 ~% u% J
15 s- @* ]4 K. q% W
2
" v& [# M* k) R) T" u9 U6 ~3
Z& q1 t3 {0 p4
. y( c$ \! c9 Y6 j: `5
- N" y& k; K, M( P& @+ O3 e9 _0 K* Gs.str[1] # 对每个元素取[1]的操作
8 S4 v3 W- n$ u9 `1
2 S+ b: q+ F, {: a+ i0 temp_1/ C; V, E' [0 E( [& M* v, X. j U
1 b* \* v/ T' J- A6 p: a
2 NaN; \1 \ {( w7 Q4 A% M! G+ |
3 y
. f" Q- h+ Z7 r& U9 [! R$ j$ V; adtype: object
' n8 V" [ A _% S7 m1
! m7 U+ e0 p0 ~+ L* r& g. {# s+ U: D2% {" F2 @" f: }. F7 n$ o
3
) J# ?& z2 K& E) a: `4
% c. h( F+ ?% J0 z0 g2 l- W58 d7 `; J4 y7 E8 _- f2 P# {6 |+ `% G
s.astype('string').str[1]# G j+ s/ g. i
1
' T; e. } u* W+ }$ H0 1: P( v9 j, R( P# L( X1 G% C! P* ?+ B
1 '
# l% K! V2 D+ E2 .6 Y2 r6 Q, P$ Q8 i" B/ e" ~8 ^. H
3 y
/ I+ g$ L4 b7 V2 E3 F; O Qdtype: string; h6 o7 x% s3 A5 U
18 M1 R/ \3 ?4 F u* u0 z% b
2+ C0 N9 u% l2 Q* Z
3. [: T0 I7 J4 `! z
4
; N7 n5 ~1 \) K% |! y `) H5
2 R' @ C* ^8 g* G* J, z! z除了最后一个字符串元素,前三个元素返回的值都不同,其原因在于:
( [: N% u1 |7 ]
8 M1 I, r9 G' {) X当序列类型为 object 时,是对于每一个元素进行 [] 索引,因此对于字典而言,返回temp_1字符串,对于列表则返回第二个值,而第三个为不可迭代对象,返回缺失值,第四个是对字符串进行 [] 索引。
3 N H6 {( g+ U- j Mstring 类型的 str 对象先把整个元素转为字面意义的字符串,例如对于列表而言,第一个元素即 “{”,而对于最后一个字符串元素而言,恰好转化前后的表示方法一致,因此结果和 object 类型一致。
2 z7 V+ b' S6 c$ b7 |: ?. Hstring 类型是 Nullable 类型,但 object 不是
3 c$ E/ o% a0 s" G2 R3 X 这意味着 string 类型的序列,如果调用的 str 方法返回值为整数 Series 和布尔 Series 时,其分别对应的 dtype 是 Int 和 boolean 的 Nullable 类型,而 object 类型则会分别返回 int/float 和 bool/object ,不过这取决于缺失值的存在与否。
3 e+ F- `! B6 b& U- g 同时,字符串的比较操作,也具有相似的特性, string 返回 Nullable 类型,但 object 不会。, ]( `3 l( G9 M. C8 M
s = pd.Series(['a']): r; v( h0 k. ?" f( Z
4 s2 X# e! L8 h/ Q! U' A8 ~
s.str.len()
7 c$ i, J/ W8 S( r9 FOut[17]:
6 z& \5 f6 b, v) \! s0 1
9 F4 I+ e2 {+ G2 j- Hdtype: int645 d0 J0 w' C* G" O9 f }
x& z- V5 g' @: N& ss.astype('string').str.len()( g( ~, ]( ~3 M0 c4 |3 Z3 O
Out[18]:
" F2 Z. W1 ]: T* ?0 1
6 t# z: ]; f1 b( Idtype: Int64; o, U2 O' [* ?7 ~( p, i
. v+ ]# R k* h- o
s == 'a'
8 t: J( ]* D5 O$ K* t+ uOut[19]: 4 T2 {$ x" q0 v! |" ^; B! }# h9 Q
0 True% U$ ]2 a0 C* j; ]1 ~: R1 I0 r1 f0 s
dtype: bool! I6 z9 _+ d7 j( c7 j
- G, _& y# v3 G, C/ _3 F4 ~s.astype('string') == 'a'
$ o9 Q$ j X4 a( v6 V2 ZOut[20]:
" O* |( @" _6 |3 e2 F7 A0 True
0 a% | [( w' f C+ A, j$ Mdtype: boolean9 c" U5 y3 V- y+ {/ s \
: W" S) i& H8 k" M9 y+ B
s = pd.Series(['a', np.nan]) # 带有缺失值: v8 i& M9 b" q: Z' v7 ^
# V9 I1 c8 s H( ?/ Bs.str.len()" Q7 \+ J" q. e! j
Out[22]:
; z& S& s6 N. ?# J0 1.0; V; Y2 d1 {/ }( a; v' f! K
1 NaN G0 n' |* A$ P4 ^0 t
dtype: float649 F7 w. D7 \$ F/ e
- ]7 K4 g* P/ J2 k* q4 h0 Ss.astype('string').str.len()
! w+ R7 Z8 n* @7 T1 Z7 Z- v% [Out[23]: 2 A, e: r+ t% \5 b$ E* n
0 1( z4 I a' q q: R2 {( A
1 <NA>
" R0 G7 I4 v4 A5 X' \dtype: Int64- c+ z0 s3 s: C" ?
( S: W# N' ]* _7 B' Y6 y8 U9 ds == 'a'' m5 s) }+ d' h" ?0 p: P/ n
Out[24]:
4 Y2 ]& e+ S& i+ {2 E. I0 True# O- s% ^/ l& ^5 j
1 False( x7 R/ L# I1 T/ b( R
dtype: bool. q: K2 ]. u) ]4 D+ @' p0 W
U1 H/ V$ H3 `: f5 Q# D- ~# G
s.astype('string') == 'a'
! v0 A, Y* u; p6 zOut[25]: # K$ S5 N9 d0 w9 Z* ~
0 True
; D$ W" F) R0 g( X/ G8 ]' [1 <NA>
3 r' ]! |5 o- n, J1 Hdtype: boolean* \ Q) h m& X& S0 L
7 V* `! @. C" L. ~5 f1 {/ Y" `0 G1$ _" i4 r. k, @ ?* C* f
2
2 n" T7 W ^0 Z( U6 n. R0 V, ^1 h3
3 ]; [( Y8 e2 L0 \# J, H1 I6 E40 M* p( u3 e, ]8 N* C
5
, o5 P! R0 s& ^: c% @6
9 K( j0 G- w: l7
1 O, p. M' n: @9 b6 Q84 ]2 K( K9 S2 \" o' ~) ?
93 q9 F1 p! _9 L
10
; f6 r& D: p0 v11: x C+ J/ @5 W4 [! j
12: L8 M* F& O5 G! z4 x
136 t# j7 ~+ o1 f: x+ N9 h+ [
14
* X2 P2 L& i2 c8 |2 D) g( f8 w# L15
3 E& K1 k* Q$ i" n$ h+ R2 @16
) I4 M/ i, H* T+ l! k5 Z& m& C17
7 {) s" M7 d7 [18" ]5 ^% _% M0 V1 i
19* i8 U' r; h! C; r' q' R
20
: b5 q5 P- p, ^0 G, v- n1 |21
3 u& W: \% g! H- \22
" P" p) ~* G3 ]' {5 i6 p% ~231 A; H4 L" U, C9 A* [" r
24- l8 F0 `. |( _5 }
259 u9 y9 d2 k$ [( z4 a. H
261 ]3 e9 d, G$ K
27+ C2 C+ W1 @ S2 P! i: o( j
28+ u4 r0 X: \+ S, T
29
- C8 q% j/ d' X; ]' ?30
) D% H- |. ^' Q$ Z( w% }1 u9 U31
% N( W* s8 v) E$ m9 j" C% ~32; b" D9 u, n9 F6 o' l* Q
33
) D8 m5 z9 v' ]% F8 x1 z5 j34+ T* z; J: b6 @9 i, p0 f
35" W9 o( u5 \, E! R) `: \1 i$ N9 i
36
d( K7 j# [7 J+ s37
+ }1 B6 Z7 H" O3 G. E) l6 d38
! D/ k c; z3 G39; R# r ~9 @- U- o9 C4 b
40: H' A1 L8 f8 a% V) ^# B
41
3 a+ \3 u% ]3 }8 F: L" E- O421 Y( q7 H3 i5 B
43
, A$ X, h$ g2 u) ~44
' e! X4 L; X; n0 p/ U45
. _' G3 r( X2 S% g46
$ q- q8 n+ z! O; W2 e47& t/ T h2 Q$ F5 W+ d: d z( A( |+ ` d
对于全体元素为数值类型的序列,即使其类型为 object 或者 category 也不允许直接使用 str 属性。如果需要把数字当成 string 类型处理,可以使用 astype 强制转换为 string 类型的 Series :8 p* o% O) `9 ]# E. K2 C. i
N8 @* ^2 v; I* r/ _* G) As = pd.Series([12, 345, 6789])$ O+ T c. N" Y( O' j) b" m, ~& ?
, e ?% v& G( N1 T! p2 Z- j9 F, t
s.astype('string').str[1]
, j; q1 M4 Y/ T9 b! [Out[27]: ; G- I" @5 A4 v$ N
0 2$ U# G$ K) }, R( l
1 4
& [$ [* `5 w. U# C/ b5 K2 7
/ |5 O3 G% u$ N7 ^6 A/ pdtype: string% K5 z- ~8 }# m( C! Z. O* O" t' K
1/ D5 i' D5 q% g, F$ s) k: _
2
8 B. N9 U$ R3 x( O8 y( n$ w! A3/ W$ M- ^1 R) {
4
) L) C) p7 u H. f; n5 ]) Z5
: h( }" I' J5 J0 ?2 L0 `6
- r# P# }1 J) M7 p4 O7
1 e9 T3 ~) U$ C" d* @0 ~1 O0 X, f/ M8% ]/ I" n! p4 G0 [/ G
8.2 正则表达式基础4 Y, K7 X6 }& e' U- A% { V
这一节的两个表格来自于 learn-regex-zh 这个关于正则表达式项目,其使用 MIT 开源许可协议。这里只是介绍正则表达式的基本用法,需要系统学习的读者可参考《Python3 正则表达式》,或者《 正则表达式必知必会 》这本书
& y7 R5 a% H0 B$ ?; A
- D; U) q: E0 }" ?8.2.1 . 一般字符的匹配; i6 A5 X: X3 w, ~1 h' Q/ {5 B9 A( P
正则表达式是一种按照某种正则模式,从左到右匹配字符串中内容的一种工具。对于一般的字符而言,它可以找到其所在的位置,这里为了演示便利,使用了 python 中 re 模块的 findall 函数来匹配所有出现过但不重叠的模式,第一个参数是正则表达式,第二个参数是待匹配的字符串。例如,在下面的字符串中找出 apple :
- `7 d9 e7 i: w) {# f6 _6 J
0 f% R- T# A( c& V( T" F, kimport re0 ?5 Y$ u& N" K. U: [/ U8 I" z) _
4 m h4 b+ r8 w* Y7 Z8 o) mre.findall(r'Apple', 'Apple! This Is an Apple!') # 字符串从左到右依次匹配, y8 n" D% ]* `
Out[29]: ['Apple', 'Apple']
- @! H0 T, W7 O' r1* h1 f0 P! U6 d' n7 u* ]7 `" e
29 a$ g! L4 n' `+ X' { A
3" t# J( X. {! i, m1 W0 t1 [
4
0 x) V8 E4 @6 r8.2.2 元字符基础
% C: ~2 _9 |) i2 E9 E# b8 K9 {元字符 描述
; w; o! u4 x; S$ X0 [2 P. 匹配除换行符以外的任意字符
9 t6 ~! t% R7 M7 N( K[ ] 字符类,匹配方括号中包含的任意字符
/ `/ q2 h) L1 v2 a, w' S V8 g' d[^ ] 否定字符类,匹配方括号中不包含的任意字符
! p; {6 Y! t7 L6 c( e6 Q1 Y' L7 R* 匹配前面的子表达式零次或多次1 k: |% z. N2 d* Q2 d5 I* Q
+ 匹配前面的子表达式一次或多次。比如r’d+'就是匹配数字串,r’d’就是匹配单个数字- F; W( X2 B1 [( o `
? 匹配前面的子表达式零次或一次,非贪婪方式
7 g% ]& X$ }9 B2 g{n,m} 花括号,匹配 n 到 m 次由前面的正则表达式定义的片段,贪婪方式
6 d) \* |% Z$ K! T0 ?8 Z; u(xyz) 字符组,按照确切的顺序匹配字符xyz
* p6 F$ a, S/ E) Y; I* b; l| 分支结构,匹配符号之前的字符或后面的字符
+ g1 M7 y4 v) s$ J; w6 y: N\ 转义符,它可以还原元字符原来的含义
' c0 |6 ^+ w( ]3 z9 P+ L, O^ 匹配行的开始6 ]% y* o7 b4 f' B) O, z
$ 匹配行的结束1 Y, f: |. E0 A+ a5 @' ^. `; E
import re S* U- v1 F! v
re.findall(r'.', 'abc')/ p, {3 ]" _( V
Out[30]: ['a', 'b', 'c']$ S/ t+ a8 Z/ N: E/ z
% a( l0 Y) o6 J3 f' f
re.findall(r'[ac]', 'abc') # []中有的子串都匹配
( @8 i5 R5 V9 `- l* YOut[31]: ['a', 'c']- o2 X+ b" g' l! \
4 m# v( g: C2 f' k% ~: }
re.findall(r'[^ac]', 'abc')
" v+ Q- Z/ s7 a) p$ P l( _Out[32]: ['b']6 T/ H2 n" i6 p! d
+ a, w! r/ @$ Q/ t3 o; f. bre.findall(r'[ab]{2}', 'aaaabbbb') # {n}指匹配n次: B3 j+ T- o! S5 u. D
Out[33]: ['aa', 'aa', 'bb', 'bb']
4 a9 L+ Z( V# ]0 Y7 z- q% Z3 ?9 O' x. J0 |' x
re.findall(r'aaa|bbc|ca', 'aacabbcbbc') # 匹配前面的或者后面的字符串
F, ^! D2 }* h6 YOut[34]: ['ca', 'bbc', 'bbc']
* d1 D$ w7 G6 [" @ N$ P
% o8 H4 ~5 n, i. _9 J: M5 ~# 上面的元字符都有特殊含义,要匹配其本来的意思就得用\进行转义。0 g9 v6 c- v, {
""") P3 Q2 V* s2 i2 m4 Z% P
1. ?匹配的是前一个字符,即被转义的\,所以|前面的内容就是匹配a\或者a,但是结果里面没有a\,相当于只能匹配a。' K5 m7 V" }# `* \9 ?
2. |右边是a\*,转义之后匹配a*,对于竖线而言左边优先级高于右边) D# m/ D2 q* Y3 k6 ]+ R0 D4 s/ G
3. 然后看目标字符串aa?a*a,第一个a匹配左边,第二个a匹配左边,第三个a虽然后面有*,
$ e, q4 M* R0 o& z' }% `但是左边优先级高, 还是匹配左边,剩下一个a还是左边,所以结果是四个a
! P3 z& Y: D2 V \0 \8 g5 U"""9 B5 G9 L' {' K: w$ i
* C% {4 n1 X. R# N
re.findall(r'a\\?|a\*', 'aa?a*a') # 第二次先匹配到a,就不会匹配a?。a*同理。
1 X. z5 a8 b* E& a( E2 x( H$ H3 rOut[35]: ['a', 'a', 'a', 'a']
8 t. T& ^2 }: D6 B- [: A( U; E9 e. }5 D! \6 U. I
# 这里匹配不到是因为目标串'aa\a*a'中,\a是python的转义字符(\a\b\t\n等),所以匹配不到。7 U, u; c6 P( E9 \% m
# 如果是'aa\s*a'之内非python的转义字符,或者'aa\\s*a',或者r'aa\\s*a'就可以匹配到\字符。 _! K' \6 X6 s; l
re.findall(r'\\', 'aa\a*a') 1 F2 k& @% `2 ~: J
[]1 X7 x0 q2 ?" |: [
" W/ X3 r; \' ?/ M' \" {. Ore.findall(r'a?.', 'abaacadaae')
. P! h4 E3 M2 E/ q0 _7 k5 D% nOut[36]: ['ab', 'aa', 'c', 'ad', 'aa', 'e'] u. T" l1 T$ ~( U- P$ r! u% J9 g
0 D/ P( D* C% H
re.findall(r'(\w+)=(\d+)', 'set width=20 and height=10') # 多个匹配模式,返回元组列表! M( y4 a- Q" ~# }
[('width', '20'), ('height', '10')]
2 p7 ?; J. \$ E. d: _
}& I/ S) `) e: y* c0 f1; d h! Y! F0 j: Q! t3 p* y
2
' g; z0 y; c5 ~" q& p, |3
- B' Q+ e" s% p, r4
7 h* X0 g, f0 P) X+ |53 o- |1 n$ F( ?
6
3 E, z6 ]7 [' d3 H/ }' L' a) Q7# M1 Z3 [4 L2 U" }# {& C2 c
8; H+ B5 c. C5 |6 d- a: I+ y
96 y7 }! \+ r' X
10
9 s; ~ R& C! F4 ]2 n/ K H11! i F+ i; V1 Y* u. ^: m( U
12% T" E; U+ h4 f7 P
13" u _8 {0 J* ~$ j9 ~
14
4 ^0 ^! y/ g+ D3 A15
0 ^+ R" q1 L6 J! ?7 Q16
5 ^+ k* O" b2 ?0 p4 l3 v) P17
: P1 R @5 i: C0 H6 ^! O* g18
1 t/ x) V1 t- m l b19
6 r# k5 V }1 `& r: H# w20
7 Z5 j& K) d+ y) E21. L% \) j) a: L$ Y1 o- [, R) W
22
* b: y, {/ c3 a" q" X23
9 T& C/ @5 ~* A" n g7 Z. k& f. G24
3 `2 W! @1 ^9 s n" q25
6 _$ W; @& D& S7 ?, O: n26
# w" E6 h, P, n1 ~1 U* ?# K279 B5 |# |8 R$ ?
28
+ s3 p% Y) T/ l0 ^1 X6 V& t" Z29; w4 U4 J! Y; M5 j1 q
30( r3 v1 P1 A7 \; q9 B- z& d8 F
31# y# A- o9 f! z4 z
32
" ]2 e% B* i' Z9 h8 d33% {5 \7 ^% t2 L I0 Q' Z- V2 @
34
6 ~7 a7 O* q s, G# r35
) W" c0 P6 G) V c4 S' \, @! U) W36
# g! h- ?; f4 E: @0 b37' _2 ^3 C, h: E% a8 i2 ?* @9 c/ ^
8.2.3 简写字符集
$ r& `% K$ G, B; w X) O则表达式中还有一类简写字符集,其等价于一组字符的集合:
& n$ h5 {! y+ G4 g
3 o0 T& L1 F$ j简写 描述
% c+ u% e! n& g4 g1 Q4 W& j\w 匹配所有字母、数字、下划线: [a-zA-Z0-9_]4 }" o& W1 G* ^8 G: Y
\W 匹配非字母和数字的字符: [^\w]
% h$ W5 g) ]' Y" L* I\d 匹配数字: [0-9]
8 _& A5 D2 V( O) r& w\D 匹配非数字: [^\d]4 Q0 R7 s# M/ s( J9 m' K1 J
\s 匹配空格符: [\t\n\f\r\p{Z}]4 D+ g2 s9 C' I
\S 匹配非空格符: [^\s] P( M) d0 l/ z1 D4 u5 I
\B 匹配非单词边界。‘er\B’ 能匹配 “verb” 中的 ‘er’,但不能匹配 “never” 中的 ‘er’。
! L. n' e+ `2 u# }9 nre.findall(r'.s', 'Apple! This Is an Apple!')
# U+ x5 y5 u _0 ^" X+ J5 n9 Z( hOut[37]: ['is', 'Is']
+ _& s: k" m' A0 {! X4 e, t) G3 X+ k& Q1 Z, L5 C# p) z
re.findall(r'\w{2}', '09 8? 7w c_ 9q p@') # 匹配任意数字字母下划线的组合,但必须是两次
- |! P _2 |1 F) V- z* vOut[38]: ['09', '7w', 'c_', '9q']6 O. o8 y* S" s; I) [
$ L7 N3 W6 L3 h$ g& Ire.findall(r'\w\W\B', '09 8? 7w c_ 9q p@') # 匹配的是两个字符串,前一个是任意数字字母下划线(\W),后一个不是(\W)
# P7 Y0 p. a" _) r& l9 A9 rOut[39]: ['8?', 'p@']
* E- b' B1 Y- K) i9 j* d
5 {. ~5 x! \* ?- d8 ~re.findall(r'.\s.', 'Constant dropping wears the stone.')
' h* ~( P# i. Q) L9 E& a0 j5 s# p# mOut[40]: ['t d', 'g w', 's t', 'e s']' m1 c W8 H% R* R! u
, e+ w# T. |/ |5 ^! \
re.findall(r'上海市(.{2,3}区)(.{2,3}路)(\d+号)',1 {) z. i/ A/ u
'上海市黄浦区方浜中路249号 上海市宝山区密山路5号')
6 O9 }) M# m+ o2 I
, a- ?/ m- R- u3 c% WOut[41]: [('黄浦区', '方浜中路', '249号'), ('宝山区', '密山路', '5号')]$ Z: y" _. Z; q: G' V. R
/ {8 |: L, i. k3 ^1 o( N5 g$ r, J
1
" S+ Z* J: m- l# K s7 N2
1 `9 M; v( F3 |, \3
0 e5 x' ?" M1 j! C8 i40 ~/ _8 x! S% Q! F+ T& V
5" N( |5 m% K* A1 v& } d
61 K8 P' d" Q J0 u3 ^
7
9 u2 v- z' g5 Q E0 ?" L7 j8
$ b% D9 _& @* _& Z9
4 C: n) f# q; \2 ^2 Y& ?0 ^' k& |10( L3 K" u' H Z
11
$ R; ]2 b7 N5 ?( ?: }12+ N/ _& B( e% W' q& p5 e' M* o
13# Z2 x( a# `& |* u
140 e5 I6 S, ] |6 |) G b. n+ A( Q
15
) p! |! t8 {! k9 q16
% }" L% [! D4 e8.3 文本处理的五类操作
/ H5 h) h. y8 o! `; `8.3.1 str.split 拆分
3 S! [, ^0 t: Y8 O+ n str.split 能够把字符串的列进行拆分,其中第一个参数为正则表达式,可选参数包括从左到右的最大拆分次数 n ,是否展开为多个列 expand 。
; b& q% \4 w& e- o B
6 X: K. ]" Z- Y9 ^+ W0 s; y% Ws = pd.Series(['上海市黄浦区方浜中路249号',
3 G9 m) [4 p- C" i& P. Q '上海市宝山区密山路5号']): ]. m5 R+ Q: b
! a& \6 S0 i0 t- y) ~6 m, J) q s5 j2 @' V0 ~' O! q% M: q4 ^
s.str.split('[市区路]') # 每条结果为一行,相当于Series- J# T: u' M0 L5 X
Out[43]:
4 Q3 {' C) {1 z- C' F0 [上海, 黄浦, 方浜中, 249号]
/ t" a0 ?/ d( T$ \/ D1 [上海, 宝山, 密山, 5号]
. G/ [+ P- K7 Adtype: object
2 S( Z* ]# g) x9 [
; t$ p. m% I; u( d/ E% Xs.str.split('[市区路]', n=2, expand=True) # 结果分成多个列展示,结果相当于DataFrame
) X: C. p- Q; L0 iOut[44]: 4 G' Q+ w7 G) D8 g" W/ C
0 1 2$ A1 n y3 O* O) ?5 I
0 上海 黄浦 方浜中路249号) X/ T: d6 Y4 X0 z1 L" Y" |
1 上海 宝山 密山路5号
& B( G8 [& m2 L* ]- U0 H1
$ p$ l2 P3 t0 @0 g6 E- b/ |# j* U2( h+ A7 g7 F. [
3
& Z. G2 S2 f: c3 O4 P+ {45 l3 i, ]1 X4 @3 f8 l6 }% P9 J, q
5
* u7 e# J& r6 ?9 l% v# q6
$ @: r$ D3 }' t- p, c* u. ?4 q7
3 v0 u( y! h& E |9 Q; S% ~# u# ^4 o8) W$ |& J7 s4 \" c9 h" x
90 u) M* G7 O- ^/ M7 \2 z
10
* I. q% U- F9 V x/ W% D) o# a11& A* n: {0 L' x& q6 L0 b- O1 Q) S( G- p* t
122 f& R* T# N* r" v0 ~' t5 G9 ^5 X
131 A) N% j% U% y5 |; q1 e
148 L6 I1 H; J8 w- N
15
7 a7 Y# U! F7 O1 i* L; g! U' A9 y. F 类似的函数是 str.rsplit ,其区别在于使用 n 参数的时候是从右到左限制最大拆分次数。但是当前版本下 rsplit 因为 bug 而无法使用正则表达式进行分割:5 X6 [& x9 q9 ?3 k
0 c4 O* v; U% V% c* N, Y
s.str.rsplit('[市区路]', n=2, expand=True)7 ?5 N |1 m z# B
Out[45]: " J% l7 y% `% u2 |& a
0
) z0 ~/ O7 J5 X0 上海市黄浦区方浜中路249号/ W( r5 u# Z! W. {0 i6 a8 K' U
1 上海市宝山区密山路5号7 w- I7 l& [# T* a. l. S W. Q- T
1
$ l- k6 r4 |9 F7 y2
' I6 z+ \. b, T3
( i0 ^4 P' Z' j# ^" }4& G) }* ^: I+ p& l* L# {$ B& M4 c
5$ b G0 P: N7 R1 _9 N* E
8.3.2 str.join 或 str.cat 合并' k7 v) d2 s! o/ H0 r& ]2 }# Z
str.join 表示用某个连接符把 Series 中的字符串列表连接起来,如果列表中出现了非字符串元素则返回缺失值。
1 }# }$ B8 l$ {% d8 v# pstr.cat 用于合并两个序列,主要参数为:1 [% W" Z7 @: [0 }6 o
sep:连接符、
6 {3 ^3 I3 C' S7 t, ?join:连接形式默认为以索引为键的左连接* E# h5 H" F S8 S
na_rep:缺失值替代符号
9 C c+ `; b) w* _* n/ As = pd.Series([['a','b'], [1, 'a'], [['a', 'b'], 'c']])
( D% L, I# i. D0 u+ m$ e os.str.join('-')
; q8 `! Q, f; n6 e, i9 `1 [Out[47]:
( ~5 @4 V6 B! q, v0 a-b
5 v0 M0 x0 K8 k9 ^9 s1 NaN
7 o; p8 C) S: Q" r- ^2 NaN
! K6 r# I0 j. D8 R0 B2 R$ x; C, xdtype: object
) A( v% @* P1 ?4 M8 C/ v" c1
: F2 F J0 ~6 U! l# m R2
- c0 X) |& O5 ~! `% y" c" |* p3
& a: R7 [' ^: n8 C2 e0 E/ _% G4
3 J8 \* @1 e9 U4 P( M' j! G* V53 j% Q8 o2 d& T1 F3 O# \
6& d; q8 |" b/ }! D _) {' n
7. m5 h+ J4 a1 J, D0 @
s1 = pd.Series(['a','b'])+ a! ^) j x: I- M
s2 = pd.Series(['cat','dog'])
# g: P! I. |+ a! Ls1.str.cat(s2,sep='-')
1 Y$ ]3 D* Z6 j3 {* S2 H( wOut[50]:
# a: a$ A6 ?$ s5 {2 h" T$ e0 a-cat9 u: z7 g3 i& E( t
1 b-dog8 G" b9 X9 l6 ~3 e1 f
dtype: object b2 `4 k6 [+ r
4 }$ a6 d$ ]3 I) S! zs2.index = [1, 2]; e# m/ i, T0 d" w- U7 T
s1.str.cat(s2, sep='-', na_rep='?', join='outer')9 P) J* @8 i: k. z+ ?4 r+ F& r
Out[52]:
/ u2 Z |) m3 I0 p: C0 a-?
2 D2 p7 m: a F+ M' D1 b-cat
& o: P, c1 N" b. m2 ?-dog
$ F, |3 O6 O, q Z/ T% }: b" p2 c Pdtype: object$ N* n- u) B. U$ |9 B( w1 Z8 g2 s/ P7 Z
1
8 U- M" ^3 J4 a$ t' o" `# r2
: n0 E/ t# [: L$ r8 y$ N) I3" p- T ~; t& u3 M6 |6 N# @5 _
4! Q6 O! D6 u9 |/ C; Z
54 A! U( i7 l- _# `. g1 a D, B- D
6
) F: @5 j5 X$ S3 y1 G9 w7! t4 [% ~/ R4 ^7 x
8% \+ P- E1 ]0 @- t1 F% E5 ]
91 G% @. x9 e1 l7 k+ B6 g V$ R3 a
10# x8 S2 t: j4 v( @2 o
11
( O( I+ W( A @/ P6 S12
?6 ?, a, P) ^' b3 N13
; ~) C1 L4 o' j# w* m* m) `140 \8 w/ `; J8 p3 y
15
3 U! V# G& D- h* l7 n8.3.3 匹配* w$ |' ?* ]1 b
str.contains返回了每个字符串是否包含正则模式的布尔序列:! C' a F5 g1 h+ I' ]# M
s = pd.Series(['my cat', 'he is fat', 'railway station'])( s( Z8 }. {$ q' T
s.str.contains('\s\wat')& d; n) d) P. _9 ]$ d7 V
+ h9 i9 u. D9 J$ _ G6 u" {% D+ B
0 True
) C# N. P! S: F8 R5 _* }/ G4 Q9 z1 True
2 k, ^8 b1 a; Q2 False
5 U' j, y* l8 s: Zdtype: bool
4 \, R6 R* I: M+ `# C1
4 f$ h+ Y5 t! Q# e2; q- |+ j _2 `0 {
32 H5 t8 W) ]4 M# l1 [
4
2 z( ~: _0 V( F5
: f) F# d+ @4 t* K$ a63 E! L1 j, l7 O! E
7 `% W5 e- M* a) X, P( ?
str.startswith和str.endswith返回了每个字符串以给定模式为开始和结束的布尔序列,它们都不支持正则表达式:
?1 W0 _/ |( h5 c6 Ws.str.startswith('my')5 v# \$ ^4 K) H
5 ^, _) s& m2 x& C
0 True2 c. i/ K4 f9 `
1 False T( V5 Z/ L3 C& w1 x5 {
2 False
2 L5 r& J0 \) v0 N# O- Z: i/ @+ hdtype: bool/ e" S& w' `# {- M; m+ r
1# p F# _8 I' X5 z c5 S
2 t0 u/ M& U0 N% V8 p% V
3
: Q V3 C$ _2 u$ E" [% z6 |4 `& f4
: v _( r( c4 f5 l3 o5
0 q" K O8 V8 p" ^1 V+ K X9 U6$ Y. i. Z' e8 v
s.str.endswith('t')" E# G6 J' w* }: f
: V- k! F5 o" t
0 True
/ W( W6 y! z, x# M6 z1 True
5 u1 C7 C+ s. W8 H7 O, L2 False
8 S0 L4 P% j; X' e' P" }dtype: bool1 h0 x: g) Q% F7 H+ R
1
% C5 k% W* e e+ a" c' Q2% S% D6 d! ~" A6 M% K8 R+ o0 b
3
6 r3 t+ r& u/ d% ]1 u. H2 z4
) [: n" ?; j8 L2 [# T9 E% g% t6 L5
7 q/ i% G/ ~/ `" B! m6
; o( ~# I% Y' L7 `2 z; Qstr.match可以用正则表达式来检测开始或结束字符串的模式,其返回了每个字符串起始处是否符合给定正则模式的布尔序列。当然,这些也能通过在str.contains的正则中使用^和$来实现。(貌似没有python里的search方法)
2 z6 n' |% Y0 e6 Qs.str.match('m|h')8 W+ Y$ u* {3 g4 |
s.str.contains('^[m|h]') # 二者等价
4 B d) A ?, g
7 v/ K. a8 y1 k# H( \% [0 True
" F. G* i4 `6 J! n" ~1 Y Y4 N: n1 True
/ |8 `& l7 N9 G2 False- q0 D8 R3 p/ ^3 Z
dtype: bool- H& ~# V) U- f6 v' n `
1
( Z' D/ X( \$ {; c2
8 K5 R+ I, p/ J' U# C3
4 H& Y6 }; P& S% i/ A2 I1 |4
1 b( y* `5 [4 Z5
2 E5 F# X5 H& }9 e7 [8 Y, b6
) B% _/ G$ [. Z: \, m c7 S2 e7& N# w& w" r3 u- c& B q2 b+ D$ Z; g
s.str[::-1].str.match('ta[f|g]|n') # 反转后匹配
7 t) l0 c4 X( n" J) R" L' us.str.contains('[f|g]at|n$') # 二者等价3 u$ @3 Y' z" U7 z0 T8 B X
/ e) x! A5 `) a# s0 False5 {, J& L* D$ }+ i8 X& f: @$ o
1 True: x$ i/ s2 A9 r4 L2 {0 E. q
2 True$ K5 E" y) ]. @) g; o) X
dtype: bool
, d& ?: A, ?5 s1
p0 R! c5 q6 K a7 c2
1 y- x/ k; q& X! q9 d5 q38 A, J2 E ]- E3 M0 \
4
, J9 U$ ?7 y! l2 E% M( v5( r. B. J6 H1 |# ^
6
- x& T* i$ Z g$ _& @6 C78 A) I/ k& L- `! s# b- {+ M3 D# j" v
str.find与str.rfind返回索引的匹配函数,其分别返回从左到右和从右到左第一次匹配的位置的索引,未找到则返回-1。需要注意的是这两个函数不支持正则匹配,只能用于字符子串的匹配:
$ z/ [+ n, B& ks = pd.Series(['This is an apple. That is not an apple.'])# \. n. R4 M# m" @
' t& v% H0 _. W- Ws.str.find('apple')2 f# j7 o) i+ g* H
Out[62]: & X, f' C S4 ?: p' s
0 11' t3 D; y2 i& W! n. v8 X1 W6 a7 T y* C
dtype: int64
5 {6 K. [- c; r/ W. k) \* g, z$ O7 h+ V2 a. g- s* R* f
s.str.rfind('apple')6 s. f' w6 O( b( S: R1 ]7 b8 u& ]
Out[63]:
- B7 t3 I! B$ Q) V0 33/ G# X- `- Z3 }
dtype: int64
" V6 s7 M! Y0 q" r- S9 F1
5 v; a3 ^$ n o: q* c u1 P8 B& Z2
8 h9 O7 g$ I% j4 O$ x" u3
" A; h% [, l2 b& I6 b" b4" y' w+ }' L9 m& [& p" b
5! r3 G/ g. L' l; v; [
6
6 V/ r$ W* Q: X& F0 o3 W6 y2 ]7
, w9 ^) \9 S- [! _4 ]% g% S+ a( n- N8
6 _3 i: O( Y8 c9
" q4 v9 E. G5 Z10* Y( I: e# O4 w1 E: i6 F. a! @
11
1 C' {' q9 k# J替换
. M4 i) g6 k7 v( p$ S0 L+ dstr.replace和replace并不是一个函数,在使用字符串替换时应当使用前者。
5 y8 P3 x" B5 B% t0 \ \0 Os = pd.Series(['a_1_b','c_?'])- m9 b0 M/ ]' R2 i' [
# regex默认为True,表示是正则模式,否则第一个参数内容表示是单纯的字符串,也就是匹配字符串\d|\?
/ B$ X% @2 F! z/ Cs.str.replace('\d|\?', 'new', regex=True) 4 @& a) @8 z0 o" ^9 u7 x: A7 Z
v* \# }$ M. b: F1 n0 a_new_b0 q9 x' H1 i! H T9 u2 Q, {
1 c_new
- r1 B. O7 s! l9 b; i5 jdtype: object
: e. ?1 E$ h* l1 g# s1
2 `2 J5 v: C+ T# G' u) Z' `3 A( C29 r" K0 J& s; F+ }! ?# o7 u9 F) u
31 A4 x7 g) H; c$ o
4
9 P& b7 a; m3 F2 i5
" }# ^; V2 ]# D1 c* j6 {6 p6 ~: G# M: \0 C
7- D* z* d# H+ x* m1 f) _
当需要对不同部分进行有差别的替换时,可以利用子组的方法,并且此时可以通过传入自定义的替换函数来分别进行处理,注意group(k)代表匹配到的第k个子组(圆括号之间的内容):9 h; i7 ]7 a; [
: k( `- {: q# S, W9 @) k9 _+ Ks = pd.Series(['上海市黄浦区方浜中路249号',
# e; c0 t# |8 b2 K '上海市宝山区密山路5号',
; J$ i# ?, }2 O* t+ A" H( { '北京市昌平区北农路2号'])
$ A, B3 H- L* J; q0 M" ?9 @% Tpat = '(\w+市)(\w+区)(\w+路)(\d+号)'0 K, _* l; f7 _' g
city = {'上海市': 'Shanghai', '北京市': 'Beijing'}
2 k, i5 f7 ]2 j3 Fdistrict = {'昌平区': 'CP District',
] w3 ~# f0 R '黄浦区': 'HP District',
( X$ S. E1 t. {: e/ X$ w7 b8 S* x '宝山区': 'BS District'}
% G7 Z6 E: M* d$ ~" s0 I3 `# w; {road = {'方浜中路': 'Mid Fangbin Road',( ]0 c$ l. V0 {7 S( f- Z& t
'密山路': 'Mishan Road',* g# x( s* p" h X' B# p( ?
'北农路': 'Beinong Road'}3 R# T$ I o9 O$ z6 I( j# m
def my_func(m):
" T5 g" H; ?3 [1 f str_city = city[m.group(1)]
# Q0 S- k6 [; T" w7 ^' l( C str_district = district[m.group(2)]
`. |1 {& {# b str_road = road[m.group(3)]) y5 a8 p V* Z0 P- L
str_no = 'No. ' + m.group(4)[:-1]5 l0 n* @# Y9 D/ H
return ' '.join([str_city,
5 o1 O0 @& d( ?9 u, U" k# e2 P str_district,
! i X: W. T8 N2 B! d$ r str_road," T5 V X# j/ D l
str_no])
6 F! F+ F H5 }; F, V% |s.str.replace(pat, my_func, regex=True)7 ^' j3 s- g v2 t; g
% j% M4 `, z) B. g3 P
1- Q9 O# a3 o+ ~& j
2
) \" A6 K; K/ g+ {3 l! t3
0 l' w5 [& {, v( M5 V" O4/ m! F' Y' z7 [% {
5( A4 t% w+ k5 s! `& C9 i l
6. r6 a' d2 p8 P+ @3 A. N4 e
7. m/ f- O) I1 D& d2 p- Z$ Q) L& {$ A
84 A4 F @3 Q8 d( p6 e" z6 ~
9
- W7 Y k% T7 L) C" D& z# \4 ]10
) S5 b; |2 O/ H' H1 ~. ^; g! S11
; P F3 Y1 M( }7 }+ @' A4 A" B12
' h7 W% j6 F e5 K9 @4 s" b13
$ h$ u$ k( G) Z# z5 V6 F5 e e14
# @# A& h; K, E6 C: D& K- e+ |7 t15* \( C) G8 s. K* @
16
; D) b1 f, h: n, K* V17
: }7 K* I/ _# I. i7 Z9 O) E* ? I180 @; t0 n, M8 R. B9 G, Y0 @+ g( X, U9 y
199 J# Z9 D% S1 O$ H$ }# m
20
& ]# G) w! [& Z5 S21" y+ j. ~) S2 @& H1 o5 @ @0 `+ Z
0 Shanghai HP District Mid Fangbin Road No. 249
" F* ~; ^# V, {) F/ m7 [" O y R" L1 Shanghai BS District Mishan Road No. 56 @! g3 q. B7 H0 ^; ]8 N, P
2 Beijing CP District Beinong Road No. 2
6 h3 X4 }! n- ]! U; R; c* Vdtype: object8 W, ~. W4 s2 S% q1 Z: `
19 I' K3 s( {) Z0 E, s* I% y# k% c
2
% V1 m- A# f) P, z3
1 Z- ]3 `7 F( B( u. V% O; z4
, K! V9 f$ Q6 M: @; A/ U2 J* l/ h! B这里的数字标识并不直观,可以使用命名子组更加清晰地写出子组代表的含义:
) ] e3 \! s- f2 f$ l7 q, \8 C" C( K1 h! R# u! F
# 将各个子组进行命名
' N9 j% R6 t' t) E$ ~. u* qpat = '(?P<市名>\w+市)(?P<区名>\w+区)(?P<路名>\w+路)(?P<编号>\d+号)'" U, J8 K9 r. `5 G3 w+ d
def my_func(m):. q+ w& d( f# g
str_city = city[m.group('市名')]) A* {+ d& y! L) t# Q0 C- R
str_district = district[m.group('区名')]" h* B( T( y! ~3 T
str_road = road[m.group('路名')]. \9 r9 G$ Y% j& a3 f5 r" S) X4 Y
str_no = 'No. ' + m.group('编号')[:-1]
( h- M. a1 K0 [7 t7 z) u; ^ return ' '.join([str_city,. [5 Q! p6 G* \( x# Y$ s9 {# x# k
str_district,
W8 B! M7 O* O0 e str_road,% v" t" J' o) ^9 s- T; _
str_no])1 c# V6 H" ?: l# Q" e
s.str.replace(pat, my_func, regex=True), Z4 a" G* t- y4 m' V6 s1 R
1
5 B5 i0 n7 r! N) A2
7 N% `7 b# U, R3 S- _3, X* W; X% k F8 u2 r+ t0 T1 w9 P
4
6 a) }6 ~. Z7 l+ L/ k5
3 N4 B D/ {/ z, g t6
: f; ~) J- W' \4 e73 u; S! G* f; H' Z, p- C
8# ^& Q+ {* ~% }6 Y% p: a. A( v9 ^
98 d2 u" H2 n9 |3 A3 F& P: W
103 @, K' e, |+ t9 a6 s
11, q: C5 K3 N, I9 ]
12
8 S8 N$ K7 N! Q b1 d, Q/ T0 Shanghai HP District Mid Fangbin Road No. 249
% h0 \ Q2 Z& G" x" ]1 Shanghai BS District Mishan Road No. 5+ B" q0 `2 ~* b9 F+ ?) w
2 Beijing CP District Beinong Road No. 2
; }6 s: z5 N! j' w0 e2 G- R3 fdtype: object
8 T1 l( F& t6 {; T8 n( c4 F15 T8 C4 ^ |, `' _/ R- r
27 G- o3 R9 p g5 Y! O, I, L
3* n/ f( L$ x' N2 q J
4
$ d* L' h. R3 b2 s: k. U 这里虽然看起来有些繁杂,但是实际数据处理中对应的替换,一般都会通过代码来获取数据从而构造字典映射,在具体写法上会简洁的多。# C- ~6 K+ u5 Q4 B
' w R1 ]& ~* t
8.3.5 提取8 G. P) K3 e: @0 S6 C% F) ~
str.extract进行提取:提取既可以认为是一种返回具体元素值(而不是布尔值或元素对应的索引位置)的匹配操作,也可以认为是一种特殊的拆分操作。前面提到的str.split例子中会把分隔符去除,这并不是用户想要的效果,这时候就可以用str.extract进行提取:
6 I! S' i( c* ?- V' h6 M# _s.str.split('[市区路]')
0 \0 J6 ?% P; }# XOut[43]:
/ {, h3 m) ]" H3 q' P3 X* H K0 [上海, 黄浦, 方浜中, 249号]
" \0 j6 N6 |9 N6 z$ f. [) J5 s1 [上海, 宝山, 密山, 5号]
; \! ^; w) ?2 C# n6 {6 t5 @8 Ddtype: object4 k L/ f! K2 h% l
: y) W- s: V! E5 A
pat = '(\w+市)(\w+区)(\w+路)(\d+号)'# C2 w, S! M) D# P% @
s.str.extract(pat)
4 ?% T5 w; A# I6 r/ x% tOut[78]:
9 p* X0 d$ K6 B6 l* ^0 C+ d8 {4 G+ ~ 0 1 2 3" z4 g; C( Y# G. X( U* r( M
0 上海市 黄浦区 方浜中路 249号
5 U5 F% v; \6 {7 z1 上海市 宝山区 密山路 5号
$ g% y1 c: n, S8 d8 _* ~% C2 北京市 昌平区 北农路 2号1 T: X1 v; d0 l/ G2 K5 G
1
' ^9 J P6 A0 F9 b3 o2 o7 a* n$ j% v9 V1 r& `
3
, J0 h% P4 Z' ~5 f( T! [0 `4
. w5 F# R I( J# V2 x! d5* T k( b0 A( M
6
' a" X& l+ O/ Z% P; N) U7. c) _) ~4 }/ g4 Z8 A4 @
8
0 n/ W) C* u4 e7 q, P' {) |2 [9
- U2 J' `( f3 g! M: B& u6 H10; Q% ^# z, K; m( k. ~
11. Z* b/ I# o \5 [. i$ d2 Z* ^
12
! {( V9 h, V9 a+ r) \13) h% F2 ^; |" s. C" ~3 N% e
通过子组的命名,可以直接对新生成DataFrame的列命名:
5 D5 P' ]% e8 u# l, b9 ]
8 n7 p& W. {1 _- }! B+ M2 bpat = '(?P<市名>\w+市)(?P<区名>\w+区)(?P<路名>\w+路)(?P<编号>\d+号)'
. z; M+ S4 z; V: e9 z9 X" [s.str.extract(pat)1 K2 N& B' ?/ U3 }) ?
Out[79]:
+ X2 w4 X* ]" F; b/ \ 市名 区名 路名 编号
" e9 }: B' I) O3 M* |3 Z0 l" s. O& J0 上海市 黄浦区 方浜中路 249号, }# [8 G9 k1 F; s
1 上海市 宝山区 密山路 5号
% a2 U6 X8 W, t4 g! s/ O7 j/ D# C# M2 北京市 昌平区 北农路 2号
7 z3 {2 y0 R$ U2 k# J* G" w- o' V$ |1
& |* I3 [3 d8 H2
8 r# b; j0 S+ O9 P" W$ T% S8 S6 f- K35 N7 X- {" K M6 P4 g1 u
4
1 k" B* ?6 S, r5
* l! {3 k' o, E: A) w) ]% {. B6
: L, p/ Y7 p0 t8 [ O7$ j4 ?$ y# i0 [) l G
str.extractall:不同于str.extract只匹配一次,它会把所有符合条件的模式全部匹配出来,如果存在多个结果,则以多级索引的方式存储:
" B/ i+ C8 j' A. Zs = pd.Series(['A135T15,A26S5','B674S2,B25T6'], index = ['my_A','my_B'])
9 h) f" ^( M. D& zpat = '[A|B](\d+)[T|S](\d+)'( Z0 ~. T; }# M: B+ ]
s.str.extractall(pat)
, @) l9 M" H' b+ U8 Y% K+ n" NOut[83]:/ U/ i) H6 ~+ ], E( Z# C
0 1
G, j, ]+ y) u+ |4 L match 7 u' N j% ?* N6 `: C
my_A 0 135 151 s" q" T5 W3 v( B: V4 ]
1 26 5
% Z$ {: |- k3 _# Ymy_B 0 674 2
5 a( c1 A3 s# M# F! [ 1 25 6
3 r8 e% S4 t$ m$ X' L1
# N, W/ ~, k( `- ?: f29 K# ]. r5 v3 F9 _/ I0 A4 Q3 V
3' n/ V% k0 y6 P, L$ ]
4
6 b) T6 B; x; R2 G; \- ~* n2 e1 |5' r( x+ |: s. [! E$ s2 o% B
6# u/ u* N G5 D6 L
7! l5 L# N1 l& {+ _% u3 P
8) p* f% ~6 V" R# [' h) W
9$ H' M7 K6 ~# w
10: B$ V( R! ~- {! Z
pat_with_name = '[A|B](?P<name1>\d+)[T|S](?P<name2>\d+)'
6 W/ A( Q) b4 J( vs.str.extractall(pat_with_name)* X3 Q. {( y, y
Out[84]: $ z m3 [8 _: D; @% v0 Q
name1 name2, L1 {. F9 u4 {' p" B
match * n% m) w4 Y: f+ b- S# F' U @( Q
my_A 0 135 15
% n: c5 x9 `& s: n- W" o6 A; g8 j! L 1 26 5
. M* t% x3 Y( J5 s# y& bmy_B 0 674 2
$ A- \6 g4 L6 p1 ^ 1 25 67 A' _# o. V6 o' m1 j. y! p
1( C5 t1 I5 q/ h; [, ~
2# y, _* o+ l9 m0 A' |0 ]
3% `8 l0 B3 \4 h& _. r
4& D _8 U& _* q3 J
5
, H& l6 @- S, z- A) v% k6
$ W+ G9 Z) p* H73 l/ _7 P7 Y* |5 W, ?9 l
8
, m) I8 A( g' }1 W8 x6 L9( G7 f# U" W! W8 I$ k+ q
str.findall:功能类似于str.extractall,区别在于前者把结果存入列表中,而后者处理为多级索引,每个行只对应一组匹配,而不是把所有匹配组合构成列表。% [, Y) |6 P5 `3 f& N
s.str.findall(pat)
6 B1 x# P, f/ R1
* a' \ Q& O5 a* F$ f- T: bmy_A [(135, 15), (26, 5)], @0 y+ P' H& l* W8 [+ `3 c- B* G' G
my_B [(674, 2), (25, 6)]
5 Q0 E8 |2 o; j7 _dtype: object4 r O, o- w/ \4 P; T
13 J h# c& w7 H" I
2
& a; I8 T& F$ v) j9 N. u. O: }! }36 u7 C6 q% @$ z
8.4、常用字符串函数
% `; _9 J$ ~1 J4 A 除了上述介绍的五类字符串操作有关的函数之外,str对象上还定义了一些实用的其他方法,在此进行介绍。; G" W5 A6 o. n8 W
2 L- t6 T T' q6 F+ d8.4.1 字母型函数
/ M1 |( H/ f5 Q upper, lower, title, capitalize, swapcase这五个函数主要用于字母的大小写转化,从下面的例子中就容易领会其功能:- m* o1 E% u$ `" ~3 C4 e# X( U! @
6 |, @3 z0 ?0 W: I
s = pd.Series(['lower', 'CAPITALS', 'this is a sentence', 'SwApCaSe'])
5 D6 u, }( u: z# t; p: V9 u2 ]; ?# a
s.str.upper()3 L% s% X3 u- M. P
Out[87]: 8 d/ o# s7 @. V1 k5 C) ~5 L
0 LOWER
6 K# \+ x' L1 K6 {( U, g/ l+ C1 CAPITALS" ~8 ~6 h0 U; T" {4 |% I* L0 ?3 _
2 THIS IS A SENTENCE! ]* B9 M/ z5 i2 f
3 SWAPCASE; _4 s/ E' @! F& y, L* p
dtype: object
2 r% t' T8 [& {3 M$ M9 k4 ~0 C& L; }2 g% N( ^9 }; }
s.str.lower()" g, z! W. Y8 T1 }7 |
Out[88]: 8 i( e& v1 d8 U t* h
0 lower* {7 |6 h( J- X7 a
1 capitals
4 C+ {8 s! j: I/ O2 this is a sentence
$ _3 m! K# D# H* ^, x2 X3 swapcase- d: U3 O; C+ H% Q6 r: B
dtype: object$ M$ X9 l S9 R% F
( q; F) b9 a K7 D A! b& p
s.str.title() # 首字母大写$ n& G. R( p$ w0 Z
Out[89]: & M* G9 _0 _. k% V6 r
0 Lower
6 @: S# E. Q$ o1 Capitals
1 c+ E7 R" q! d: r0 Y- p2 This Is A Sentence+ U8 C% d" `% J( M
3 Swapcase
1 A% m9 v- p: u- ]# Q8 Ddtype: object# i4 b& Y, q1 A- z, M8 _) E& V
) q, U: b; @% B' ?7 D' Cs.str.capitalize() # 句首大写
% }0 Y, }3 r# x4 yOut[90]:
) _% d9 s! U# T* S& j2 d& ~0 Lower
X* f4 ]: }( A: [- P# v1 Capitals
J" b; ^% x5 I+ C% @5 d3 V: P2 This is a sentence+ h: h8 n+ @3 S
3 Swapcase; S& l* y9 j1 i
dtype: object! i& a3 n& Q) N2 B+ W& ]
: M+ }# U2 H9 Q0 c; B. h
s.str.swapcase() # 将大写转换为小写,将小写转换为大写。$ S3 a1 \5 o6 |( {2 p; E& R
Out[91]: 7 C2 v- i0 _: m8 `6 E
0 LOWER
6 d' n( G" Q2 x' z1 capitals5 F' {- j2 E! S s s2 M
2 THIS IS A SENTENCE
$ m* D. r* t, f Y3 i3 sWaPcAsE
# |7 J- F, k+ k6 C; pdtype: object
2 v) e4 ?" l. M; \: V; v* T& V0 x' P' z7 }: {" g0 ~/ ^4 { S W' [
s.str.casefold() # 去除字符串中所有大小写区别
3 p# N2 I6 I+ X9 E& l+ X$ N( k$ r0 U7 {# u
0 lower& U9 b# _% ]4 G1 x
1 capitals
& b6 D: e1 ?6 J0 O- Z$ a2 this is a sentence
+ K7 \& V( ^7 ^# Q g) k9 z3 swapcase
1 l+ ?! D. T) q1 u. u# C) P: l+ m
1& C; a) z# ^, Z- L* z' T8 D! T* a* f" H
2
, W9 x$ x% i; }$ o, D3# X3 F4 m3 p& w# ~) Y. F S
45 ?" o6 d+ h4 R7 H2 j3 \
55 |: Y% ?! g$ h& G$ {) F6 k0 w* u
6* K1 ?' A, a4 l0 z$ ~
7+ m* S9 p* U% h) X* y9 K. q7 ?
8
8 v( P! Y$ k9 k9
# U/ T. d0 w3 k, j' [10; d3 c7 S7 o2 a! b
11+ ?1 l4 M3 _2 F: p' C
12+ Y- g: I1 E* I" q1 b3 H
13
+ h' ^: X* m, E" [14" v: |' V P' {
15
2 Q+ b7 V& I; @' L; ~. I16
7 P4 @7 D; ` X% W17
4 {/ M- |6 O t1 X, e182 i" ? L0 ?! e& G
19
) O% i/ @# d' s+ B/ L# [8 X20
3 c2 y& _' I# e21
: a Y! C/ ]( [) w* M22
; ^* {* f* D+ v23
N4 J, |5 U9 q% A/ r/ H1 h24
5 k! q4 e9 ] ^; ?: f# L% }2 x: v t25
# x) A0 c! m. D5 v26; _4 C/ Y: ]5 f5 `" }& Y( }
27
' e$ _5 r8 ]" ?1 y28
* Y, A3 c( J. ]- |29
9 {6 n: j2 u: M, V# i k3 Z" P8 O% V8 z30
' Z, {1 `* @0 D8 d6 M: P) M31
! y8 y. Z! p8 S' V$ l32
2 C8 {, t( O1 j33
1 O4 E( u; V7 X f8 ]6 T346 C+ w) i, l3 I4 ]" w
354 l3 H1 r2 b: r3 v
368 t1 F# x9 H0 t9 O' z7 n- C+ Q
37# X2 x; R8 _5 `8 ]8 |) U0 j T. ^
38+ P) r& Q; u% K+ {+ ?& P0 a
39; W. r; q1 H3 G
402 j1 H* I; f6 @1 ?# h
41
0 K, R' s* t0 R. m& f42
& F3 ~' X# n- _% a7 T# H9 N' R9 ^+ d43- H* Y$ l" @. O: T
44* g$ D: H& z& Y7 u
45
" m# D3 G" K* ?6 d' u: W46
) Z2 ~& ~+ A5 f47
3 \( f0 z1 f8 B" C48) m% N* `- \" z; t
8.4.2 数值型函数* I. b; B0 L; P
这里着重需要介绍的是pd.to_numeric方法,它虽然不是str对象上的方法,但是能够对字符格式的数值进行快速转换和筛选。其主要参数包括:
. D( L# Y. X* J$ z7 g; k
* K2 E0 f; M: }# Q+ K' O- {errors:非数值的处理模式。对于不能转换为数值的有三种errors选项:
* I" d! n n. Z7 y6 r& Fraise:直接报错,默认选项6 N1 ^, d2 q5 L4 G0 z; ~
coerce:设为缺失值
5 J; m8 Y( i: \9 G7 S) E3 vignore:保持原来的字符串。
: D \5 K' X) o% U1 g1 F5 tdowncast:转换类型,转成 ‘integer’, ‘signed’, ‘unsigned’, 或 ‘float’的最小dtype。比如可以转成float32就不会转成float64。( l0 s9 x5 V+ C" Y3 |. w
s = pd.Series(['1', '2.2', '2e', '??', '-2.1', '0'])9 R5 t6 b" o! G8 E
5 R9 A! u* M! Fpd.to_numeric(s, errors='ignore')
0 k' f. w: p9 M; jOut[93]:
' Y, H9 }# v. t5 K1 q5 z0 1- m4 W; K0 H0 v+ s# L$ _
1 2.24 O5 Z! L, w% z* y- [" N2 c4 Y9 F
2 2e0 w2 O9 D" U3 T7 T0 B
3 ??
+ h# H. Q+ E! t4 i' B" v8 P4 -2.1/ m! p* L0 q" _* ]* O3 i
5 0- e: Q$ h2 M' b/ g
dtype: object( G* h" b0 T3 A) ]& J6 L4 P
/ T% o7 x, q3 E5 c. Hpd.to_numeric(s, errors='coerce')
( L6 z' o' j$ B W7 OOut[94]: % D& D# q1 a' C9 U1 X
0 1.0; |! f; G- k- U4 |
1 2.2
/ A# P: f& o: i- g3 t' p2 NaN
7 Y9 t1 F. x e( T8 V. R u+ j3 NaN6 g2 P: l8 @' H5 Z4 Z* O B
4 -2.1
- o8 ], N& ]* Z+ A6 m- n5 0.0
]0 }2 r' t6 Pdtype: float640 @4 b% u' {& Z+ H
6 @% U0 ?% Y# V' R1 S
1
K4 ^, d# d7 U5 r# X4 u3 N) ~25 O9 l3 l- O& Q. Z# C8 {! w1 v& E
30 S/ ?$ l1 m3 a8 C2 X; T
42 y$ E: P0 f3 _) _+ J
5
- G V/ p5 k& Q5 }, C2 \' a- w/ _6, X6 ]5 y6 ^: E: F
7
$ m& V Q8 j. n9 O8
7 s* q3 S8 t* h" `; I9
9 j4 j" e! w3 \: R& l101 l' E+ }7 c! F) w) W
11
6 k4 S3 V' L0 R4 ]! e) [& _; X125 P6 Y( O! h' P' Z! _$ ~6 p
13
6 P( a" T* m3 E* x1 u2 i14
; a8 h! j5 E2 U15
9 ^: D" y+ D$ J7 u164 _7 M# \6 a y( L
17
' o3 Z! b- k8 z+ g2 Q$ S4 H2 m; G182 Y" l6 B6 m1 K5 q$ H
19
: h) n( m, `/ k }% E3 w20
' S7 {$ O% I+ e! D+ r8 Z21
1 M) Q. [0 Z* h% H0 z2 @ 在数据清洗时,可以利用coerce的设定,快速查看非数值型的行:+ F7 s& u+ w3 {4 Q6 i4 W" L/ y l5 _
, k$ y& V, I7 s& o
s[pd.to_numeric(s, errors='coerce').isna()]
7 d9 L4 V6 K3 p& Y2 e7 |7 OOut[95]:
/ }4 E9 ~0 K4 O8 E5 ~2 2e" f# H: @- S2 ^( W9 s
3 ??
5 f7 K$ r l+ [2 o5 b: {9 G6 D Idtype: object
V# Q* z9 C" @11 u3 X+ i" M: A% G0 {
2
2 K! q* j/ {1 q9 b+ H34 a% b: i: e/ B. p( d% \
4
5 Z N$ H2 Y# [5 n9 \5
8 i/ [! y! H& \: N8.4.3 统计型函数$ F# r0 y X6 [$ f1 g& j5 @
count和len的作用分别是返回出现正则模式的次数和字符串的长度:
% A q8 G: X% } ?0 y8 y3 {
$ o! |3 ]$ w/ v1 a0 Y& P2 S% [s = pd.Series(['cat rat fat at', 'get feed sheet heat']) t) m" ~; D7 d- y
9 u5 Q- X- M. y
s.str.count('[r|f]at|ee') # |左右两种子串都匹配了两次
8 i+ ~6 {5 C1 f5 j" h+ dOut[97]:
7 p" ]. f+ \( r* z7 p0 2
% F# a! i9 A$ G+ r1 2: t5 O* w8 j! E
dtype: int64" |! {& h1 n% s
0 ?6 ~: i: w' bs.str.len()
/ G+ L! b; i) a# l) N3 e0 WOut[98]: & m9 H& C/ {3 ?7 W4 ] x) K9 o
0 140 Q7 ?# H/ ~8 g% k5 W' h, N6 u' w
1 198 f. E9 s. e! e7 C! h1 s7 i
dtype: int648 n7 }" r% k, W# L: m# R
1
0 N- ^' r; R2 ]/ b; y7 \29 i, I' ]& Z- M- y" p& R
3% C2 v3 z( O4 v+ b
4* C7 R b) I2 |. W/ i. j7 H: {
5' c$ q. B% M/ {
6' w4 Z% \4 V" @ a) B
79 u; e2 ]& l4 _# q0 D
8
1 B: N) t2 ~6 ^* t% W: Z9; L( p" _3 W# T2 J7 O ^
10
' F8 R+ m" d/ X11
% S- j7 |" V5 b {: m12
# b' U/ y, r# {1 \, s% K8 e138 |5 J0 u7 Z& B2 H9 `0 M x- X- F
8.4.4 格式型函数: t8 R% L# Q, a! z6 \
格式型函数主要分为两类,第一种是除空型,第二种是填充型。其中,第一类函数一共有三种,它们分别是strip, rstrip, lstrip,分别代表去除两侧空格、右侧空格和左侧空格。这些函数在数据清洗时是有用的,特别是列名含有非法空格的时候。) O6 z! f6 ?5 |; J) {+ Z- @( Z. K
; U5 o/ ?9 { X
my_index = pd.Index([' col1', 'col2 ', ' col3 '])
( G8 w# b- }; v" _0 u6 Z) L$ q( X* | N" u1 _8 T$ M
my_index.str.strip().str.len()# v0 {+ p0 U7 v3 ] T. i
Out[100]: Int64Index([4, 4, 4], dtype='int64')
# k- F& @$ c0 h k; r* w4 Z+ {: e2 R. y0 G/ S% y& O; R7 R9 x
my_index.str.rstrip().str.len()& F9 y2 B5 f5 F6 j
Out[101]: Int64Index([5, 4, 5], dtype='int64')
2 k/ J# D6 q/ k" A7 q# ]" n, y1 z, @. c0 ?# q5 T+ m
my_index.str.lstrip().str.len()6 n( t% S }6 B" B5 o
Out[102]: Int64Index([4, 5, 5], dtype='int64')( s6 t5 \; j; Y2 I
1/ I6 G* ^( J/ ^8 u* [% _/ e
2
# V! g# Z5 ~" M0 n3
! q' Q, ?" l; O \- r4
5 \7 U# g- i. ^% L4 ?* c) x0 Q2 ]- X5
9 k4 h) | [) R- o! ?: P6
: A; O+ B0 d# z. }* @5 Z. ]* V7; a3 B s* |) q0 t; `( Y
8
2 W# z) e5 C# |* r$ V9 q) k8 @) D5 T% {
10
/ V% C$ w6 x2 L. g) S 对于填充型函数而言,pad是最灵活的,它可以选定字符串长度、填充的方向和填充内容:7 N: R5 ]* K$ a! r5 J$ E
' X' H) v$ ]( {- Ws = pd.Series(['a','b','c'])9 F2 B3 x- b c! L# D x
0 u. J- H3 p* W) N" Z/ Q
s.str.pad(5,'left','*')
4 z+ O: a; R4 W S/ KOut[104]:
5 `; Z% y. o# t9 c2 n0 ****a$ T) j4 `9 X7 v9 b% r9 p
1 ****b. P% e+ q5 p1 |6 w
2 ****c
$ _8 N$ b* T: H, d/ ~. Vdtype: object
. k0 N, K& H% l! d) D# _& {1 b# t2 m( o
s.str.pad(5,'right','*')
# h' i1 k" V- O5 Q: p( i; xOut[105]: 8 k2 @ Q+ Z+ j4 e, u
0 a****0 [8 H. w' \/ i
1 b****
* O+ t) z( t2 |! d2 c****' C# k6 o& ~. J! p0 {2 \
dtype: object' J7 @0 y7 B4 Z' U
# y8 c* C( Z! [
s.str.pad(5,'both','*')
* W/ Q$ m3 @% d+ MOut[106]:
1 u2 x3 S& o7 [+ V0 **a**/ x9 A8 S; a8 q; O
1 **b**
5 @( c v& \% U% h# u( C2 **c**
; Q0 _% W& p0 W/ B9 |' }4 mdtype: object3 E, C( i* [0 ?& R: f3 Y0 l
1 a; A: W# ^3 U* n6 k) k3 w0 l
1% i- s3 `" u3 E) V( f
2
) i1 d: S- E" a2 S$ Y/ [, v3) q5 u5 k2 ^" n' ]
4
# m5 _" x8 }. r. f4 f9 I/ j5 }5
6 `/ S& a! T3 I: W5 k4 S+ }6/ G% h# L, ]- U7 N1 l
7& u \' j% J# z
8
9 T0 I6 l/ c u Y9
+ g" @% _* `3 ~10, {# i3 s7 s' ~
11/ a# o2 q5 P. u: T( d2 b6 S
12) l2 t4 [3 z' w% A3 v
13
/ j' {% ^' p& |1 m& V5 W14
. ?1 ~* O( V6 d& k) I" c ]15, U9 i) ^* @; r/ b- j) q
16
2 J) N% i; G5 x2 U: R17
7 Y1 k: r+ C f1 r7 V18- h `1 U$ u; g% b' P* @+ i
19
4 f7 k6 r5 M: u/ H8 y20: u- n5 h \: V0 `
219 a: @7 v1 N5 C" F
22
/ C- k% Z1 a5 h# c% t6 J0 E; Y 上述的三种情况可以分别用rjust, ljust, center来等效完成,需要注意ljust是指右侧填充而不是左侧填充:
; T) O& \7 O$ y3 K F6 ^6 R
6 ^/ r9 \$ n4 O5 M/ b) k/ h- fs.str.rjust(5, '*'): g/ ~4 A2 D) K N3 m8 w" ~
Out[107]:
. f* j) g% Q+ ?" q0 ****a
2 C* ?3 o4 J q. T1 ****b, Z. g6 d' c- D6 e) _/ Q' V
2 ****c# ]: v2 K8 p9 M& F* x4 T: ^) ^" k( U
dtype: object* o! R" e1 A" ^( o/ _
( }, \, |% n+ w% w- D$ y* v& M
s.str.ljust(5, '*'). q- Q: i9 q2 y# K+ w
Out[108]:
7 o8 ]( ^" q. s3 t2 f0 a****, D% ?; o1 j0 X+ w+ F0 `$ K# M
1 b****
4 l; j, z m* q7 k2 c****, _' h" L5 T! v5 a L
dtype: object
( F# a6 f2 Q8 p0 P* U5 B; L z2 f9 l7 F: h4 F# f! Y
s.str.center(5, '*')
& F& t& C3 `) P$ M8 Q. Y. x$ yOut[109]:
/ B q- x0 X# _$ E0 **a**
* ?5 `: }/ H8 G% v" s1 **b**
9 j, L) m- u6 K% _4 H9 X: K2 **c**
5 B0 m- o5 e1 xdtype: object
! C9 i# j2 D# V: ?* H8 Q' K/ [ e/ X- i, {6 R+ [
17 G$ ~7 n* n6 F+ N2 w* @$ L+ ?
24 k- R: u$ d; h. ?5 ~9 k
36 I& W9 {3 E' R' @4 q
4
; S) V9 _2 Y3 ~9 [3 g5 ~+ b- f5; ?3 A7 z; M# T$ L
6
% X0 T7 t; @6 S# v4 u: b! I7
/ y$ n0 [7 [7 b* O* n8
7 O$ ~5 g; y; Q$ W+ u9 z# I* E; C9
F# J) O0 c3 c( m# s0 J8 \7 U10" O5 e+ F. l! E6 a( G
11) r2 t- h, _$ }- [3 m
12( J2 e) y! F+ l f: @
137 J+ f4 G: y! e, W: A- }7 o
14
& n- G ?4 Y! z6 i' G- z/ _15& W9 V. v2 b. |3 B" P
16
5 \- B- b- {9 y; ]3 j17
) L# u3 \6 A. }% j$ `% _8 f18
, h' q1 c0 t) J+ o& V# W7 a! s8 v19
. I& c4 x' W( y2 z7 X203 |( D* v% @* x6 z
在读取excel文件时,经常会出现数字前补0的需求,例如证券代码读入的时候会把"000007"作为数值7来处理,pandas中除了可以使用上面的左侧填充函数进行操作之外,还可用zfill来实现。
i; X3 _+ `7 I7 V( W M0 N
5 z2 v) R1 b4 t7 _- Fs = pd.Series([7, 155, 303000]).astype('string')7 K" c6 @% S( F
. ]! D# M. L( C* ^2 n+ Z' W
s.str.pad(6,'left','0')& O* g" D* l' e2 s9 B$ g* S
Out[111]:
" r1 l# }. c# {. Q8 ~( A. g0 0000071 i+ B) J8 s6 v2 N
1 000155
. X }) ~! _7 b4 y% o: T- V$ |1 r2 303000
' G7 ?5 q& ^1 p% E) w& vdtype: string
) ]' s# z( l9 S1 N! X$ d$ R" O* e% x
s.str.rjust(6,'0')
7 r+ e% c/ _7 C m+ z+ a2 i" U- `9 U' DOut[112]: : T1 N8 c; U- l' A. n
0 000007
0 `( A' q" m( f8 \" F z0 z0 S' H1 000155
- ?, `% c9 A: k! k5 E2 i* Z2 303000
# r2 c2 e' I+ `" ldtype: string
$ e- H, F/ E8 z4 Q, x& }4 n; ^. z0 p( K
s.str.zfill(6)2 V, Z/ G5 T6 ^2 m/ B+ w" S
Out[113]: * z# i( i7 g- t1 S
0 000007
# N/ `) [1 M Q/ j1 000155; `6 e9 q- p5 t, u
2 3030005 ]* {- S( n9 n3 Y
dtype: string$ G2 g# }9 U% {; J+ V
+ _% W7 p; [7 M8 E
1! K+ ~3 _; F0 q; ]) F+ w( g1 a
2! d1 _$ v0 Y2 k- Z1 d
37 F- n* A4 ~! ]; E
4- ^- l0 z+ b- G; _2 N
56 k9 w! |3 O" S% @
6
- k/ h& u1 [9 J6 S$ g9 o9 Z' u7
3 k M5 @+ x- o" x, _8; K$ L5 \0 x! L# e
98 s8 T: R* Z9 F2 P5 W. L
10. F. D* f2 s+ H$ h# w6 ]3 x
11/ a- e7 G [% s) _* ~3 e
125 j5 V, G* l T! F- C6 A
137 |; I7 H+ E. U# @
14/ N$ m* Z( w6 f. ~. ]1 Q
15
v% t; Y0 f+ d16
" S) b6 [ \4 o. ]0 C* h/ c% J177 j2 y! o/ E8 c! c+ w" N' M
18
2 \! V5 U5 e6 L3 N4 E, K5 b8 ^19% \0 h" t' x. G/ x+ S; x [
20
- w- m0 C8 ~, K; s5 j21
1 e! j1 O' i+ A- X: [222 e( r, a, N" \! A' ~7 q" C- f: w
8.5 练习- S" X! O8 {; G0 x/ ~4 a& d
Ex1:房屋信息数据集
) C+ C' }' N4 C- S现有一份房屋信息数据集如下:% l k3 q+ _" B
3 `5 A( X) N6 }- s$ U
df = pd.read_excel('../data/house_info.xls', usecols=['floor','year','area','price'])
! d# M5 [ Y2 A' J. R& Bdf.head(3)0 |! I6 B4 E. z9 ^' @
Out[115]:
# ]! @. i1 r l3 S" _2 \+ \; } floor year area price; D! s2 ]2 P# y' C) c5 Q. x; |6 p
0 高层(共6层) 1986年建 58.23㎡ 155万
7 f" ]% C- s1 g5 g! D1 中层(共20层) 2020年建 88㎡ 155万0 v5 l- \. e+ M. \- T; I
2 低层(共28层) 2010年建 89.33㎡ 365万# v8 y6 `% C4 b }# `9 t4 d
1# m% M: H/ L8 l' t& |
20 y# }0 h8 X+ _% z5 w
3
' X7 Z& E |3 E/ t1 k* B8 K4
3 H) y. S$ U3 U' j2 ?8 D54 @ {9 _$ E9 V$ Y
6
% o5 ^ s, }6 [1 X4 D, N4 [7
) N0 y: {! }- z( z1 m将year列改为整数年份存储。
+ ]1 T% i- T8 r2 g; c6 Y2 R将floor列替换为Level, Highest两列,其中的元素分别为string类型的层类别(高层、中层、低层)与整数类型的最高层数。) R) E7 M2 P! H5 A# f
计算房屋每平米的均价avg_price,以***元/平米的格式存储到表中,其中***为整数
; }& h1 Z9 N. n) m/ ]将year列改为整数年份存储。
% ?4 a9 ?# E* p0 X) e G% K, @""", c. C }& N5 q& v( |
整个序列需要先转成Nullable类型的String类型,取出年份,再将年份转为Int64类型。
3 _+ R; J0 V2 B1 q- D b, S4 w注意,转换的类型是Int64不是int,否则报错。即使astype加参数errors='ignore'跳过缺失值,0 n$ u8 r( Q; C! s! c [8 A- B
转成int后,序列还有缺失值所以,还是变成了object。
2 ~; q$ o' T' ^. p& O而整个序列转为Int,就还是Int类型,缺失值变成了 pd.NA 。( q0 V0 o7 s! @6 P. l) n9 g; V
"""
& H3 O3 M- O2 \3 F2 e; S# v$ hdf = df.convert_dtypes()/ ] h$ }( C1 e' v" [ Z
df['year']=df['year'].str.replace('\D','',regex=True).astype('Int64')' _) Z9 u9 [# b" I0 z3 g9 n K
df.loc[df.year.notna()]['year'].head()
# w% b9 Q3 U' U p: ]8 E: @; c$ {
3 p0 S7 K+ y5 q1 G& [! k0 1986/ y6 [3 ^- ]( c8 Q
1 20203 t' M# ^* ?9 ?% c
2 2010* C- R9 v/ y9 n+ t3 A
3 2014
# F9 S( A; D0 X, U4 2015# O* F) C; e% H- Q
Name: year, Length: 12850, dtype: Int64
. t$ C, m% { x' K, l# z f% ~4 Q* z* f& U/ v2 @+ V$ r$ [# r
1& \3 b! {" C- I' W& z
2
3 z$ D* U& t0 H) n# _1 K% K3
# Q" X8 Q, ^) u0 d4
, n2 i$ i- \0 J& p; H, H/ e5! m7 {5 D) I! Q, X
6# L. e* M, P2 r' M
7- w3 k- m% j8 m: ~! [0 D3 I( w: C; H
8
3 o) h( e: @" G$ _9
" o/ G% j, D6 ^10
- T1 n, h' e& Z0 V9 V11' T& j* b! p3 o
12
4 x0 ]' U: a0 z+ k5 C13% Q8 Z8 R9 ^4 ~5 ]# e
14
3 W7 s8 N$ b+ T5 H, Y7 K" E" X- [15
* `2 J& s8 t2 F- }% T1 b16) s6 I# B' H# _. i4 s7 c; I& ^
参考答案:+ U# Y( i. l7 p/ c
5 V# y. `8 k& ~1 \$ e, m% e
不知道为啥pd.to_numeric(df.year.str[:-2],downcast="integer")类型为float32,不应该是整型么+ _3 o6 j% e) R
; |% Q" f. C3 Q
df.year = pd.to_numeric(df.year.str[:-2]).astype('Int64') - M3 i/ h; \$ x# S' P' x J
df.loc[df.year.notna()]['year']
9 o% K& f/ i1 J( s- {$ f1
+ k3 I6 P/ r% t: }2
. f4 S% k# S, {+ k. J5 s, V将floor列替换为Level, Highest两列,其中的元素分别为string类型的层类别(高层、中层、低层)与整数类型的最高层数。
7 W) G0 D6 a) k% vpat = '(?P<Level>\w+层)(?P<Highest>\(\w+层)'( K4 p1 c* f+ }" x: X3 n6 |" L# P
df2=df['floor'].str.extract(pat) # 拆分成两列,第二列还是(共6层得形式,所以还的替换一次
4 a% b( }6 R- ~4 |0 a$ Qdf=pd.concat([df,df2],axis=1).convert_dtypes() # 新增列拼接在后面,再次转为Nullable类型
: `5 a0 p6 m" W$ q* n) Mdf['Highest']=df['Highest'].str.replace('\D+','',regex=True).astype('Int64')
! Y' @2 Y2 i; w3 ^. z9 u" s2 ydf=df[['Level','Highest','year','area','price']]
+ O# }7 d Y K" Z9 C4 z) Idf.head(); U4 Y2 m; n* g. ?; ~/ a& v# ]2 e# \9 U
7 r3 U& N: d: u Level Highest year area price6 W( p$ ~- r8 W8 }7 U2 T
0 高层 6 1986 58.23㎡ 155万
7 Y; ~- \6 n9 x' n9 ^% _1 中层 20 2020 88㎡ 155万! g% T8 h. {! g+ a5 A: o( S
2 低层 28 2010 89.33㎡ 365万( M) e( h& _+ D+ W$ `
3 低层 20 2014 82㎡ 308万2 H& Q1 N; H! ~" k$ l- Y( x3 Q
4 高层 1 2015 98㎡ 117万6 n9 L- g( u5 z0 a* n! W
1
9 h; I# h8 I8 v3 k- m2
& q# K, O/ j! l9 }3
4 T6 Q. X9 S0 K v* q% i4
' [. m4 U2 |2 O2 [* f52 R6 Y, s; L% A9 A$ |$ C f
6
6 P! |7 F3 G; r3 |7* ]! F# m+ l2 I/ ~8 J9 G
8
+ t: I, Z4 B9 O. g( A) n0 `( I3 y9& V' P: N9 ~& ?+ K( O
10
' U5 d! e, F% q. ^3 G+ t11% w; ^7 J3 q* w. U( V( g
12* F# H: a% |9 ^) [
13
- Q T% K' i( z {9 p* Y0 R, o# 参考答案。感觉是第二个字段加了中文的()可以准备匹配出数字,但是不好直接命令子组了% z( W) y, t. S' M( g& _6 D
pat = '(\w层)(共(\d+)层)'/ b$ U$ G I* O; b# b2 T+ I, P
new_cols = df.floor.str.extract(pat).rename( W4 O6 G* a8 J( [ n1 Q& }- a
columns={0:'Level', 1:'Highest'})& [4 O2 L) R1 j$ j0 H; b
6 l) X: n T! ?* Q
df = pd.concat([df.drop(columns=['floor']), new_cols], 1)
& I' s- J; l f( x4 b9 r1 @df.head(3)7 n" L' J( ~- \8 ?. x
' J8 c8 ~% z, }* \7 [Out[163]: 8 V* H( |) c- J' ]! Z
year area price Level Highest) p. h( k+ r2 z
0 1986 58.23㎡ 155万 高层 65 M+ |2 a6 N' s) [# S; _7 n7 p: i
1 2020 88㎡ 155万 中层 206 D* ~" e& Y5 l
2 2010 89.33㎡ 365万 低层 28
]( O! E N% d! p1 a6 G/ y, r' D# X) j' g1+ E+ o: W- P* J% B
23 ?% v! J( a7 P- P: ~
3$ R" [7 T7 _5 b. m, v
4* |+ z7 x9 E# \2 x. B
5
0 M: y6 m* a \( I0 ~6+ N9 i$ O' V" O
7) b3 S2 t: G, i/ S1 a1 i: i
8
* @, q- {2 ^ K/ Z5 t( m9; k8 j0 l$ i" n& |0 \! O
100 T1 Y6 v) y# C( k1 |
11
) |9 K& }7 M9 g4 j1 {, o12: f7 ? k$ m) l) A( K; @
13
) V9 b7 y& d9 e6 c& I5 |2 s |* S, D计算房屋每平米的均价avg_price,以***元/平米的格式存储到表中,其中***为整数。' _4 ]* U4 ~- z
"""4 H' B u! f6 Z+ z, p2 F: G* m* x
str.findall返回的结果都是列表,只能用apply取值去掉列表形式
5 h4 Q O7 @3 |7 u9 g3 z参考答案用pd.to_numeric(df.area.str[:-1])更简洁) _( w% K4 y) O; w4 h6 M3 G
由于area和price都没有缺失值,所以可以直接转类型
) {- ~0 b5 m1 `0 L"""9 y: o3 i+ @# A( ?/ Y ~. D
df['new_area']=df['area'].str.findall(r'\d+.\d+|\d+').apply(lambda x:float(x[0]))/ O. T7 s, u/ d
df['new_price']=df['price'].str.replace('\D+','',regex=True).astype('int64')
/ d0 Q* H1 A0 }. a( hdf.eval('avg_price=10000*new_price/new_area',inplace=True)! |, O, ]# }& P4 [/ a
# 最后均价这一列小数转整型直接用.astype('int')就行,我还准备.apply(lambda x:int(round(x,0)))0 \/ M7 u% K9 G/ ]. K0 |& y6 T$ x$ J
# 最后数字+元/平米写法更简单
9 T0 R8 ]# K+ E/ B B U vdf['avg_price']=df['avg_price'].astype('int').astype('string')+'元/平米'
! K- E2 d) o- B- `0 K4 y6 ydel df['new_area'],df['new_price']' d, n$ r2 A) [
df.head()5 u6 M+ O# V* ]3 j
4 D% U" Q! Y4 Z+ S' n3 J
Level Highest year area price avg_price8 g2 I" m9 A6 B! t" |) m, M; G @
0 高层 6 1986 58.23㎡ 155万 26618元/平米# ^) k9 c% l I m) c
1 中层 20 2020 88㎡ 155万 17613元/平米
. Z3 l; ^! U9 W+ j N2 低层 28 2010 89.33㎡ 365万 40859元/平米0 \2 R) R x* \
3 低层 20 2014 82㎡ 308万 37560元/平米
7 j; e, Y( ~! q- J6 ]4 高层 1 2015 98㎡ 117万 11938元/平米
8 l( r% g, @6 V# E% ?1 ]6 m9 \) w2 o4 ~( a& Z3 i3 D3 R, {7 y
1+ l# M5 H2 H: J* Q7 j
2
* o: z' t( K- D) O9 M3
4 m: a; d& P# J3 | Z4 x4 _4 I, C! o3 X" P# B
5; a' r# p/ I, d" E( \4 B# N5 e, U2 z
6
$ ]0 k: [1 @3 U( p# U7" k4 } h8 x' }- l' [- u
89 j5 h( p) m8 v' ]. r! m6 q
9
; _/ R' j4 a) ^1 }105 F# z6 w4 ~/ h* A
11
6 h/ x& d2 F/ c. y4 j( n12! K4 V: ?9 a0 H$ O5 y& I: x7 b
13" p* t U* v6 B: i1 D# q
14
& o0 X* _" n. I4 v15
9 J B& I+ r; _7 W0 U% P$ q* W160 R2 M+ N5 R9 R
17
; X' u4 b- E5 l7 r8 Q7 `187 l( q% n! m# |5 W. U, p
19
; Z" n# F |4 }2 K- e; o* C20+ Q. Y D" C; T# G' v" R$ d
# 参考答案7 Z( _. ]9 C1 G! W2 ~( r1 X& u z
s_area = pd.to_numeric(df.area.str[:-1])
* i$ @ N2 \* i3 Us_price = pd.to_numeric(df.price.str[:-1])
4 O! U- [5 a% h5 K4 Mdf['avg_price'] = ((s_price/s_area)*10000).astype(
; q3 [/ I* R0 N& t 'int').astype('string') + '元/平米'% L, J2 ^+ y* W* V
$ l# @3 D# w4 M( x- Hdf.head(3)
1 r7 O E( E- ^! ^Out[167]: 9 t4 [& G" W4 \% N0 b
year area price Level Highest avg_price
! L/ W" }0 v$ k0 _ w/ t0 1986 58.23㎡ 155万 高层 6 26618元/平米
; q& x! e$ y5 F9 K- |. T1 2020 88㎡ 155万 中层 20 17613元/平米
, W4 k0 @/ g$ H6 l2 2010 89.33㎡ 365万 低层 28 40859元/平米
# }" X$ H5 x: q8 w% ~1
7 D* u: o4 ]+ o0 o4 X6 p9 [2
* S" Z# N6 B0 R) W1 b6 n9 p3
0 N T7 }, {' j% f9 r+ I! Z3 L8 e4
2 m) H, Z( J3 v" I2 T9 U5! _0 P& y; [5 q6 g7 I
6) n I0 l3 Q/ `1 M) G
73 F$ C8 Q+ \0 `0 Q2 ~
8+ T7 K0 u8 K1 @& O. x4 A& l* g! O
9
$ b9 G6 h: }' i* L( @) h2 m) g2 W109 m2 U2 Q6 y- o+ x9 N5 q4 ]
11
1 V$ z7 Q0 ?2 d9 P% W! F12, p, L5 b! n$ R, I5 t- g
Ex2:《权力的游戏》剧本数据集
; u% w# W$ f+ n1 k; m现有一份权力的游戏剧本数据集如下:
) t( s& ^1 h- f
! p! H$ P1 [8 Sdf = pd.read_csv('../data/script.csv')
* V8 ~) m& S: ^+ j& tdf.head(3)
q& L7 Z$ I( g" c2 Z
: [$ \; d: P; \! O/ g, hOut[115]:
9 X9 v. [9 d/ a& r) E1 kOut[117]: # v/ [, v' ^$ u! W, _8 H/ y
Release Date Season Episode Episode Title Name Sentence( z8 T n! ~" t1 X3 b
0 2011-04-17 Season 1 Episode 1 Winter is Coming waymar royce What do you expect? They're savages. One lot s...
, g T" {3 z- h8 C; t# s1 2011-04-17 Season 1 Episode 1 Winter is Coming will I've never seen wildlings do a thing like this...- q5 r) N5 ?! H. r$ F" b
2 2011-04-17 Season 1 Episode 1 Winter is Coming waymar royce
6 E7 B1 D+ }3 E. r; ~. Q1
' J- R$ p1 H) N1 p) \7 d$ m2' e$ W. [; H! C! J k
3
( f9 i9 P" u5 w" ^; k) y4
2 Q+ W* ~2 b5 o% G- i3 u5
. \3 g8 Q3 Q; z' ?) N0 S6
" Z" X, p- X$ e: R W7$ R2 {" W: u+ F \; T. S
84 C6 j" c, \1 N6 Z6 N' |9 u
9
) d k; x v& B0 F; G计算每一个Episode的台词条数。
5 S- |9 W2 x D4 u, I* u以空格为单词的分割符号,请求出单句台词平均单词量最多的前五个人。. x( Q1 T6 A' Z& m
若某人的台词中含有问号,那么下一个说台词的人即为回答者。若上一人台词中含有 𝑛 个问号,则认为回答者回答了 𝑛 个问题,请求出回答最多问题的前五个人。
0 M! R! Q6 a' O2 v计算每一个Episode的台词条数。
0 J6 c* B$ h* U5 T- o Ddf.columns =df.columns.str.strip() # 列名中有空格, z. c# R" r3 h# D1 _3 Y
df.groupby(['Season','Episode'])['Sentence'].count().sort_values(ascending=False).head()
/ T. b' M" L+ _ N& H: k+ }* a: H9 b H4 T1 r/ [
season Episode % M6 G7 k! M, B& H' P4 {
Season 7 Episode 5 505+ g- M) c! o, S/ C! F
Season 3 Episode 2 480& x0 ^: ?; U! j* ? j( [
Season 4 Episode 1 475
+ I0 H, W/ ]6 t; SSeason 3 Episode 5 440# e u6 b" J5 E+ J0 G( g
Season 2 Episode 2 432
+ M/ N* c( r/ k) z1( _$ q; k: f; p( ~, A2 Q) d
2
" G$ O; C! R9 I; C `3% t. ^" v; E, F
4
: _- b* l% \0 r# d9 J5
2 x/ K4 X$ ?# r" P0 E; `6
, {$ H: k1 \7 ^* \: I7
8 @. d. Z) t& t+ Q+ {: g8
" q) h# a5 X, i' a2 b9
- W8 y1 h2 Q9 n2 X以空格为单词的分割符号,请求出单句台词平均单词量最多的前五个人。7 c- _4 O9 T- z7 F0 G# c. ~
# str.count是可以计算每个字符串被正则匹配了多少次,+1就是单词数8 K$ B% W W- r( w
df['len_words']=df['Sentence'].str.count(r' ')+1
" x0 }/ H! H6 h: Zdf.groupby(['Name'])['len_words'].mean().sort_values(ascending=False).head()8 r' U: G$ Q5 D# X5 J8 j" g K5 Q
0 W( q& D7 t$ x- k/ n& O
Name0 a. z# x; _% b5 Y$ u5 ~6 o
male singer 109.000000
) k. F5 x& O8 ~/ ?" t7 islave owner 77.000000
+ [3 h4 o; z0 l3 M2 l+ vmanderly 62.000000
% e" ^! C( z C( ]8 c1 _5 Nlollys stokeworth 62.000000* U% l. f+ n I( l) d1 v8 ?& E
dothraki matron 56.666667) }7 p/ N( e0 ]% B; ]" F4 Z
Name: len_words, dtype: float64
/ T" ?: D! {" T1
* O( ^& j. U( s# f2
# q$ C0 e$ j8 n( O& s: X3
) C4 N" v6 O0 j1 W4% D" ]7 W9 U* j7 u
5' a+ q6 E6 t# S8 s! k0 m& M
6
* u; [) H' o& s$ v8 U2 t% ]' [ ?7
/ _8 ^% `0 d5 `8! o, J: m2 G& e9 P) x
9' Q8 i8 \ t! j3 N* A, }; x
10# o7 j# F, l; ]' d
11
4 h4 j$ Y9 n, q8 R0 j5 D' x若某人的台词中含有问号,那么下一个说台词的人即为回答者。若上一人台词中含有n nn个问号,则认为回答者回答了n nn个问题,请求出回答最多问题的前五个人。8 [# Z& l2 I$ J4 n
df['Sentence'].str.count(r'\?') # 计算每人提问数
9 W% v$ @/ B+ P0 w1 n7 [3 Ils=pd.concat([pd.Series(0),ls]).reset_index(drop=True)# 首行填02 ]9 G; t5 V3 N a1 [1 P
del ls[23911] # 末行删去* Q4 \2 o( P; C* @0 G# I
df['len_questions']=ls
3 P+ `; K+ P2 q) j- idf.groupby(['Name'])['len_questions'].sum().sort_values(ascending=False).head()/ M+ C6 `- s9 l
+ n/ K+ o9 J- K6 b) G" d" _
Name
( r |3 r! u( _2 h2 t! _& utyrion lannister 527
# B8 R C" c/ g' X' `) f4 _& Cjon snow 374
2 ]7 d9 H+ y9 Z+ O8 zjaime lannister 283
% R, z7 D9 w4 ^. \* g3 Y4 Z7 oarya stark 265
: S2 O" K8 q3 E1 I+ pcersei lannister 2468 U7 p1 {4 d& ~/ k$ R
Name: len_questions, dtype: int643 v; j0 ?2 @$ v/ e1 y) H
4 b1 I7 q4 m6 L# 参考答案
( `" S/ T" U2 }) g( Ts = pd.Series(df.Sentence.values, index=df.Name.shift(-1))7 h8 r- T% |2 R; ~7 u: j
s.str.count('\?').groupby('Name').sum().sort_values(ascending=False).head()8 _! B8 Z: w& x }0 g) K. d- Y3 W
& A9 ^7 j5 ?# E2 K! K
1
W! k8 I( R4 x, }2
) p* B" V- `$ f* Z, J3
6 m7 z) t3 u. U" _7 t46 d9 v+ X- k7 s: p1 i, @
51 p, p( j4 \/ W& p
6/ ?2 a: V2 b- _. u
7. l- `6 @% j( h2 r$ P3 h( d, X
8
1 g1 Y* }& y9 n$ @9% P0 W" f8 H# ]
10% b, S% ~: H9 Z3 z
11
/ r, C& F+ M: g* n4 W12! N" j9 w" s* r6 f( P! y, }6 @0 H
132 A/ a$ i- M9 x8 L5 S
14
2 l2 a* X) b/ d157 e8 g# @7 C/ B& g
160 v6 ?% [( I L, S+ ?4 q
17
; w2 Z5 m' v8 [1 j第九章 分类数据
2 V; ^& Q0 f$ b. s1 A2 vimport numpy as np
+ m2 m( _& }) j& m; ]import pandas as pd; ?2 I, M- R7 m- g" K! @5 c2 j+ E: T
1
^+ a. o0 n$ x$ Z V+ B! _" c; I2/ D; G% [7 f' H" K- z R2 L+ G0 ~% a
9.1 cat对象
/ y! O- y: P$ e6 o2 g7 V' _# l3 R9.1.1 cat对象的属性
# N1 o% p# Q4 A# e0 v- |, s 在pandas中提供了category类型,使用户能够处理分类类型的变量,将一个普通序列转换成分类变量可以使用astype方法。
4 ?- A% K& q$ M0 q7 }7 X# K/ p4 L( w5 S; L4 O* m, W
df = pd.read_csv('data/learn_pandas.csv',$ Q9 P! X( i: ?- A4 ~' z7 }$ e
usecols = ['Grade', 'Name', 'Gender', 'Height', 'Weight'])! {9 L5 c7 C |
s = df.Grade.astype('category')
, D8 D0 |6 b" O/ i- v) D( C& A, {6 n
]. C2 W, S6 R, \' Q0 rs.head()/ g; u7 s# Y/ J& ^" m! X4 F
Out[5]: ; G" m. ?$ C$ j A6 t0 h7 s# Y+ j' U. I
0 Freshman. L& N, i% X6 Q# o! O- W8 G
1 Freshman
9 W5 O* T" R1 h3 v/ U0 K2 Senior
4 @) Z i: X _; ~$ ~7 K3 Sophomore! Q6 g) C5 L0 b/ I2 a5 o! }
4 Sophomore
. L! H c7 {0 g6 [- f+ v$ FName: Grade, dtype: category
6 m, m- M3 c/ c! {: y8 z* ]( ^Categories (4, object): ['Freshman', 'Junior', 'Senior', 'Sophomore']
z. e' A& s2 I8 ]' _, P" O1
5 f" g) f: e t' k24 d4 W$ G3 e. Y2 l
3
, }4 G8 V4 l8 a9 {4% L2 I/ C7 m; q4 J2 d# f2 K+ b
5' p* k+ t9 U" Y( V; g; h+ @
6
: ?# w% B# ]% r7
- g+ C; Y) _1 h& H0 t" R: F* o8/ s& [! D# P( S# k2 e! `) f
9; u2 y0 V8 \" N, u
10
; V& D: Z3 s; u0 @! {9 ^11/ s4 V: H) n6 ]& {( q7 m
12/ R, L! `& k3 L* L, r3 j: B
130 I% ^% Q0 L( i2 A4 M
在一个分类类型的Series中定义了cat对象,它和上一章中介绍的str对象类似,定义了一些属性和方法来进行分类类别的操作。1 O+ q$ J: E& r/ X
9 i- K+ J k& ] fs.cat
* u; s* M3 x% s( HOut[6]: <pandas.core.arrays.categorical.CategoricalAccessor object at 0x000002B7974C20A0>
$ ~. j% F1 ]( R, y" k/ P* i1
" o( F0 @+ k9 @! v2
5 c0 N0 j. V0 m. t5 N# p3 e ucat的属性:
( n' h3 J [5 i/ V" d& M- ~" Q1 \) w G
cat.categories:查看类别的本身,它以Index类型存储
5 Z- m" S1 Q: y, G% a. `9 W+ c6 zcat.ordered:类别是否有序* `1 O8 { m$ t9 K- E& U( [' ]: ^
cat.codes:访问类别编号。每一个序列的类别会被赋予唯一的整数编号,它们的编号取决于cat.categories中的顺序
2 {& w3 C$ F, S$ G1 cs.cat.categories3 Q7 Q/ k: O! m' s& g9 g
Out[7]: Index(['Freshman', 'Junior', 'Senior', 'Sophomore'], dtype='object')6 |4 W5 n; g" @# R& f: `4 R+ g
. y6 t: g! P, R3 {s.cat.ordered
+ Q* C% B8 u4 C3 qOut[8]: False
F! N# W3 I! W7 W4 A4 m; l: R5 K% \6 N
s.cat.codes.head()/ v* |7 i4 E2 b* t R) Q# z
Out[9]:
R; D" x" B2 |) L) S6 y( b" s0 0
' w" d6 M7 i1 E! G2 v1 0
2 R! G/ f, n% L0 U1 l, R0 G- H2 2
' W5 y5 w) s$ w. k5 P3 3# c, c9 m, B- X
4 31 A0 E+ g# K& [8 S
dtype: int8
5 d( n( K i4 u6 }7 k: e1
( `# J" p, X4 ^. R6 k9 `2
+ a. @% m3 I0 V& y9 k' `39 J) f4 H3 [. w$ D$ r
4: w! I; H4 D( m( N! I& E2 f* t
59 b- Z/ D" j: _
6! x$ l1 A8 T% p ~# q; z) R
7
+ C7 A' ^, K, R% }( a3 _1 Z2 C82 G* |8 ^' e, d. L
9, B% {( r$ ~% z4 ?1 T0 w! B) I
106 I A& l9 F2 r
11
! U( {$ Q* N4 o8 Y9 h12
$ T: A k+ V. o. K+ \13
% ^ M0 L) \& @0 ?' r" f( M14* B9 P: Q5 i! ]
9.1.2 类别的增加、删除和修改* t( D% t8 X! I/ J( u" U3 d9 t$ G
通过cat对象的categories属性能够完成对类别的查询,那么应该如何进行“增改查删”的其他三个操作呢?
6 o) d- J# d0 e5 q8 v# D1 Z
; z7 R$ l+ `5 }, u/ k% @( c' p【NOTE】类别不得直接修改
2 V: f" B; n# \ P! J* c在第三章中曾提到,索引 Index 类型是无法用 index_obj[0] = item 来修改的,而 categories 被存储在 Index 中,因此 pandas 在 cat 属性上定义了若干方法来达到相同的目的。; ~: R, z2 J7 E
" \. o: ~) u8 j1 _+ o! nadd_categories:增加类别
n4 Y% m% |2 _& A1 q7 \: Zs = s.cat.add_categories('Graduate') # 增加一个毕业生类别
/ P Z' }$ z5 v8 vs.cat.categories; ]# c. |1 r) I4 Y6 p
' M5 k2 y% G2 s6 k2 TIndex(['Freshman', 'Junior', 'Senior', 'Sophomore', 'Graduate'], dtype='object')8 P Q# N/ v X, K Y' _+ V
1: G7 x7 G- M' M0 a/ ]. \0 O
2
- P8 F8 h6 l9 V" y s* S& o7 l3
. U/ ~% X2 X- h7 @6 b2 c2 q$ R42 c) {2 n; y0 l) ]0 k( Z$ }7 C
remove_categories:删除类别。同时所有原来序列中的该类会被设置为缺失。" _% Z: A, W" N# r1 f: c) P
s = s.cat.remove_categories('Freshman')6 h& d+ }2 ?$ u$ X
/ V# s K2 ~) W( l" I( V6 Ys.cat.categories
! v8 f p, h7 b+ [3 ]+ s) eOut[13]: Index(['Junior', 'Senior', 'Sophomore', 'Graduate'], dtype='object')
+ q/ O$ g% m' A8 v& t# y9 E
$ D$ P3 r7 I4 }0 S" U" _% t" q+ Gs.head()
2 a* a% K' b7 C9 AOut[14]: 9 I2 g0 |9 K% N5 v0 f
0 NaN7 g9 _7 A# [9 z0 l
1 NaN
8 T/ l* r r! T' t3 t p, H2 Senior
8 J7 U7 ^+ f1 V+ L( M4 x! C p2 {9 H3 Sophomore1 v: I* I, G# W$ k0 }7 b
4 Sophomore% F3 g1 L, t" H6 T# C5 Z2 h8 ^
Name: Grade, dtype: category c/ P" ^+ x$ o- B
Categories (4, object): ['Junior', 'Senior', 'Sophomore', 'Graduate']
1 c! U4 a9 g# u s* i1
( m% n. J* ~0 W) A2* ]& l# O6 f7 q# \! K5 K
37 J* g, C* F s1 u
4
/ h% `# v9 S6 ~6 X' W% v6 a57 E. H5 B2 J* Q3 n0 L$ ~
64 ?- _" ^% y0 y& t& G" p) ~
7
1 ]0 k9 G0 w" H' [3 x9 ^9 Y1 ~8
/ L0 Y; W% y) t5 |) G9
|% @1 ~" m& F: e: {; u( `107 z, v# _0 x5 v* I+ p. Y! c+ T
11
# y4 F3 n- d3 P; Z( Y12
' d. x5 W s/ [9 b. y, b13, E ~3 ]! a& Q/ s0 Q
14' Z, Y4 W% W7 t) t: |- m( x' E
set_categories:直接设置序列的新类别,原来的类别中如果存在元素不属于新类别,那么会被设置为缺失。相当于索引重设。
* I9 w2 i: g. w5 |. h8 cs = s.cat.set_categories(['Sophomore','PhD']) # 新类别为大二学生和博士" r3 z: N* |$ l& y
s.cat.categories: n- ], i9 u0 B2 f
Out[16]: Index(['Sophomore', 'PhD'], dtype='object')1 n2 ^# }6 z' |6 }" I4 h* Q3 Q
% `) }8 Z& q) _0 ^# s; B5 U2 I
s.head()
9 ~. @+ H f0 M9 L1 g: IOut[17]: 8 B8 o$ S' H# {, d
0 NaN6 p' A- j' K8 N7 [0 z- F3 t
1 NaN
, F- b7 Z9 \! {2 NaN, m( i. B l. I. f
3 Sophomore8 u0 l# ?4 h1 \* y$ @% |' I1 M
4 Sophomore
# P% e3 o2 n, V4 X' d2 w( @- T9 VName: Grade, dtype: category
3 Q# N# a/ ?- d. l0 eCategories (2, object): ['Sophomore', 'PhD']) E- u, O% ^5 }- |3 v% ?) F
1+ K- d" \! ?4 t0 v3 C% B4 l
20 L0 f- H( W. o z
3
! X; _ g1 `' Q, q, R40 D" c1 E1 z/ v: Q1 ]
50 ?8 X1 z7 q* M6 `
6
5 H- Z+ @( ?/ s4 G% B75 E& a! V7 b- y4 Q
8
% o9 `+ c* Q: c1 O$ w9 V9
8 p1 h* {2 e5 L% p) R6 p! u10
7 k8 _0 x: u/ k+ ^" H11
l. T, T( g" r% W1 `! u12
. |8 u: H2 g. E$ i7 Z9 E k137 d+ n0 f/ o W, B
remove_unused_categories:删除未出现在序列中的类别1 P/ E8 c& {' W* v7 o" _: M
s = s.cat.remove_unused_categories() # 移除了未出现的博士生类别) m; Q' V* T" R" K5 ?% ^
s.cat.categories
7 n$ z& |7 I8 ?2 Y
5 B- B4 Q) L* r V; c5 M3 ~Index(['Sophomore'], dtype='object')5 r* ]3 Q0 b' w8 x- T. h% K# a
1
, O+ ^5 j. m/ V( Y% V- L. i$ v2, L) n3 t( s) u2 @' ^* R
3( M* q) n0 u' ~: D
4
8 h( a% Q$ b& r- M5 e' [; P& W$ Grename_categories:修改序列的类别。注意,这个方法会对原序列的对应值也进行相应修改。例如,现在把Sophomore改成中文的本科二年级学生:
, b2 d, n) g0 }0 S) M# e5 Hs = s.cat.rename_categories({'Sophomore':'本科二年级学生'})5 \2 C$ ~: {0 _0 c8 m% n
s.head()" O$ D& D2 |7 o. ]* u4 K' w
( P9 n& [8 S' \, I
0 NaN! K( Z- ~! q/ _4 H5 Z
1 NaN- _8 j) U2 ?2 g/ a
2 NaN1 L% Q* [4 N- h+ F1 g- r, k/ E
3 本科二年级学生
2 O' C, l3 b- v/ ]4 f% S4 本科二年级学生; {- a$ a0 @8 `' m& U6 f1 q K. f
Name: Grade, dtype: category) ]7 h- f( K. }1 w
Categories (1, object): ['本科二年级学生']
) _! B# r% A: G! Q/ J" N1
" X/ w' V f) A# Z( V" J* b W2
2 p: E1 N! w8 o* W+ _& e! H# F3+ Q- R0 | `; e# B7 \4 i
4% U! T6 [) w9 {' H! d& S# Z9 k0 Z. L
5
* T* G2 J+ L' }+ E* ?8 x: H) q6
0 C8 i3 e+ X2 V, Z) n, z7
" X5 Y7 Z7 @, f S3 q, U8
& F# y, I/ z* v/ j+ R96 z+ ]0 U& `. I! Q: h
10
# _8 ~, Y7 W! f; Q9.2 有序分类
1 U% Y- O' d& E G: f! U9.2.1 序的建立4 d, J& Y9 `7 B( A4 `
有序类别和无序类别可以通过as_unordered和reorder_categories互相转化。reorder_categories传入的参数必须是由当前序列的无序类别构成的列表,不能够新增或减少原先的类别,且必须指定参数ordered=True,否则方法无效。例如,对年级高低进行相对大小的类别划分,然后再恢复无序状态:
; B* G) y1 J! z# U- q- e) j+ `: p' Y& ?1 C7 ?8 F2 j
s = df.Grade.astype('category')
3 r4 s6 B% w, i- ^5 q! y; u$ ws = s.cat.reorder_categories(['Freshman', 'Sophomore',; q2 J/ I n. U5 }! t8 o
'Junior', 'Senior'],ordered=True); j2 e4 a6 ]3 Z
s.head()7 T. G! o( a0 j& ~$ a
Out[24]:
/ Y$ ?: A4 ]- l0 Freshman
0 @) ]1 O+ I2 j: u) J* H8 f1 Freshman
, u/ K6 R' n ], {: a4 b( C2 Senior
+ e6 [' s6 O3 E, @3 Sophomore) n. S- n* Y" N: g, F
4 Sophomore
' R5 ^8 z' j1 B3 n7 Z" ~Name: Grade, dtype: category7 [! R# b; F" c) f" n# m! C
Categories (4, object): ['Freshman' < 'Sophomore' < 'Junior' < 'Senior']
- Q- y( N+ s7 N2 b( x8 f/ j6 r g# e# K) P! ]; Q; p, L
s.cat.as_unordered().head()
/ N- V: l1 z/ h- A# J) MOut[25]:
! z7 R. T8 F. [' J3 F* y `7 Q0 Freshman
0 D! R$ S0 _$ b5 C5 r1 Freshman1 I6 T% r! D7 w0 y( j% d( T {8 p7 }
2 Senior
~; F+ p$ L/ H e- |8 W3 Sophomore
: { i* t. |% \0 O, d3 k% N4 Sophomore8 s5 p, E5 e3 }% y5 ?! V1 p* T
Name: Grade, dtype: category
6 Q5 u# @9 H3 e2 ?Categories (4, object): ['Freshman', 'Sophomore', 'Junior', 'Senior']
" C( E s# Q2 @# t$ B3 T* ~ U2 X' z. {' k/ Q7 M t( z
1
8 Y S3 P8 n K8 h! i2
" m; ^* B2 D. l% e4 I32 w9 D; W8 z8 T: k2 Q+ J$ M/ Y4 ^9 f
4. J( h- d( g/ s4 C
5
% I3 H$ [# q F% {- K2 ]60 l$ e5 C2 A$ G! d7 k3 x
7
: k4 d! C9 j. M3 O% o8. `( ]/ j' J% c% Y4 S, G b
9 r- p! F" `3 r" K: U
10. [8 l. `6 i* e+ a
11
( X6 z* n9 e [6 k12
: f# S! x5 m; u2 y+ i* Q13
* I: ?+ d' I2 q- v* @/ y! I14* v" t5 g! r; j" v, X/ \& l& a( ~
15
7 \" \6 ^. o/ D, L16
/ e7 W/ e. h D& a6 i& V. L17! z) P9 c* g# s* ]1 k6 i
18
& T( a7 s) z0 y* S19# m) s# h% B# H
20
0 D. }; v& o/ d. Z c' N' m5 p21
+ O' F' g9 q4 a' Q7 m$ U22! ?' Q1 X' a& m' {, t1 l
如果不想指定ordered=True参数,那么可以先用s.cat.as_ordered()转化为有序类别,再利用reorder_categories进行具体的相对大小调整。
A h; l1 h1 \+ c/ \! s8 R4 w- I' o- e! m) S$ p: q
9.2.2 排序和比较
5 n" C* `& W/ x在第二章中,曾提到了字符串和数值类型序列的排序。前者按照字母顺序排序,后者按照数值大小排序。6 Q! M+ R) [# s5 u9 }, l8 ?
" }* [7 m3 c; J+ W 分类变量排序,只需把列的类型修改为category后,再赋予相应的大小关系,就能正常地使用sort_index和sort_values。例如,对年级进行排序:
' ~( G3 e0 i3 z! ^9 e' f& o; T% N0 z# O
df.Grade = df.Grade.astype('category')$ i; i5 @. ~2 }8 _$ _' ^( Q0 I' v
df.Grade = df.Grade.cat.reorder_categories(['Freshman', 'Sophomore', 'Junior', 'Senior'],ordered=True)
: d8 E( ^, e( @8 C5 ~df.sort_values('Grade').head() # 值排序
' A5 I3 p5 B# DOut[28]:
. ]5 L- m2 y2 _; e4 e' K3 n Grade Name Gender Height Weight2 H3 }7 S3 F% \- {$ P3 N- \4 `
0 Freshman Gaopeng Yang Female 158.9 46.0+ K3 E6 U* ^& A8 ^
105 Freshman Qiang Shi Female 164.5 52.0
8 V2 a/ m* C( Q+ e$ w2 q96 Freshman Changmei Feng Female 163.8 56.09 `' w5 a3 A, I5 w
88 Freshman Xiaopeng Han Female 164.1 53.09 |0 x7 h2 }! V7 q) i) [. v
81 Freshman Yanli Zhang Female 165.1 52.0
- n" V, i: R; d1 l2 j$ [% J$ C* m' B. h6 \- ]+ A
df.set_index('Grade').sort_index().head() # 索引排序7 R, B# M/ D# v H! L$ e
Out[29]: $ n( X- A* t* ~; L
Name Gender Height Weight
$ t4 S8 u! t$ tGrade 1 O5 M- k& r4 {4 F2 Z
Freshman Gaopeng Yang Female 158.9 46.0
( o. S- g) W: d& vFreshman Qiang Shi Female 164.5 52.0% P( |7 _# G) Q/ ^9 O. J S
Freshman Changmei Feng Female 163.8 56.0
4 o* K6 Y4 h. w# }1 }' FFreshman Xiaopeng Han Female 164.1 53.0; P' K: Y4 z2 b
Freshman Yanli Zhang Female 165.1 52.0 a0 h y& |1 p# Y' D5 y5 f
% g, ], u: h7 b/ Y+ R2 ^; B3 }) c1
! }' R7 H# y* C* x7 i" _2+ y ]1 b7 s/ k, F5 f
3$ \' ?$ R I6 }) ^) b- {4 [2 ^
42 T# s$ _8 ]. t: H3 B
58 I) ~, x( w8 `5 ]7 t' _
6
) _6 G4 I0 B( H8 {& M1 [2 x4 k+ `7) x! _$ z* o" D6 s4 T4 P& H4 u! L& b; A
8
# T( B- B9 j+ l9 f, _$ M98 s& r8 w: e0 Y
10
, t4 ^' |( e& l6 e6 A3 e112 f) G5 q' r: ?/ d! M- R& g. k9 l _
12
" n- g3 e0 ]6 |13
6 E9 j- K* L2 b- |8 F3 X6 O14" m$ o. j1 t1 f3 ?
15
- @0 d; Q8 S7 a: H6 Q16
0 Y- H: ~" @6 l7 e* v; w171 E* H: m' o. F
18
. f9 V" u+ [7 E8 `9 ^8 a19& W# j) \! t1 t; h. z3 Z% y
20
( ?% R: E% J5 D. J( |" }' z9 D 由于序的建立,因此就可以进行比较操作,方便后续索引操作。分类变量的比较操作分为两类:4 C g9 r9 }3 |7 y1 d3 U' ^" J# H3 X
9 x2 X$ t' k! W9 K( w, ~. I9 l
==或!=关系的比较,比较的对象可以是标量或者同长度的Series(或list)。(无序时也可以比较)5 v% T0 V! h- G- _& x( g1 G6 I
>,>=,<,<=四类大小关系的比较,比较的对象和第一种类似,但是所有参与比较的元素必须属于原序列的categories,同时要和原序列具有相同的索引。
8 S: N% Q8 k$ ~) t3 ?0 cres1 = df.Grade == 'Sophomore'
9 [ p0 l1 `) _
6 `: ]. U/ F' |& Dres1.head(); i! v8 O( L# T2 E4 `' |
Out[31]:
7 G s7 U5 N! C8 x ^0 False
2 p }3 {5 X1 t1 False; Y( u9 S- m" R9 Y$ t2 [) j V2 _
2 False8 D4 X; u8 \0 v
3 True
" \: F# S. A9 a- c" }4 True
; t6 H* m2 a3 Z0 Y* v ]% rName: Grade, dtype: bool4 }: j* E( |3 |- e
; I: O9 o" Y, z# R! _% j+ sres2 = df.Grade == ['PhD']*df.shape[0]) t5 W( n+ q: k6 F7 s4 I1 e
' o; A. `! J( p( Z0 c& C& |$ Ires2.head()
$ v; a) a) z/ z: }Out[33]: + s S. m5 ^( n3 L: I
0 False
0 B& z" E8 _" h8 t/ z6 k/ A' j1 False K- P6 Y; s: V7 u9 G* H
2 False
3 X* \0 z* S; Z% ~6 }3 False) h; @1 u1 K. L/ x+ u8 u( l0 T
4 False5 B n. W) H6 G7 @% I& {
Name: Grade, dtype: bool6 A) O S$ l& d3 K& f9 u
3 L$ s `* H/ v, w
res3 = df.Grade <= 'Sophomore'
$ s, \3 m9 p: C, u
4 E# c# p8 |2 K, e% k) }' C& v- |$ Fres3.head()5 j, h+ s, Q' k2 \; q- p* M" _7 `
Out[35]: " o* v5 r0 e' `/ k* v: z
0 True% Z G; }; b( \1 _ ~) a" r5 y
1 True
5 k. P" o: B- Z: P. G7 }* r$ X% s2 False
* O! ~1 V2 z, D4 J& X$ ]1 r* p9 b3 True
3 h( k, H" H% }+ }8 j+ G4 True* N* T5 x0 Z! o' S& r- u
Name: Grade, dtype: bool
1 L) ?. ]" S/ V4 ]9 r
q [; P. O) U A/ p" m% v# sample(frac=1)表示将序列随机打乱。打乱之后索引也是乱序的,直接比较会出错,必须重置索引。* P9 Y' q! \2 Z" I
res4 = df.Grade <= df.Grade.sample(frac=1).reset_index(drop=True) 5 ^# k% }% O1 m/ K0 m
. G- P6 @- m1 b$ _6 K1 L5 Xres4.head()8 D z4 q2 t$ W6 ?4 q( v$ T$ E1 P( W
Out[37]: 1 m% J6 q* w6 h! I* k
0 True
2 B( l1 Y$ p* e" c. J. k: F8 }1 z1 True
0 u4 m# n/ C7 g5 a: |3 F% }2 False6 _* J9 M/ O# X! H! `2 Y
3 True- P) L1 e$ u. Q
4 True6 z/ {& P, E% s$ h0 X* U6 E, K
Name: Grade, dtype: bool
6 w, V5 ]: P [" g7 O* Z0 a' @: i4 e" h+ H
1& W C( s5 l/ O, l' S; y
2
& r6 O+ T/ M8 {% l- j/ \3* e. y- e7 D3 c. x; j t" K
43 Y; V+ ? f: X) ~9 Z
5
3 L* @7 c2 N- C2 y, W8 ~4 k66 y# v+ X9 j, }/ D8 @5 Y0 y+ x+ [6 k5 o
7# j$ P; f Y) S4 Q& x
8
: P4 V8 ?; e; }6 m' E9; F! ~" R, i/ P4 f- v1 g
104 L& d7 l( H0 B* W- q
11
G* J+ t. J8 O A: Y* ~+ [12
& s# R1 _5 y: B, Q+ [; c2 s7 \3 Z13
/ o- D& P) e5 _+ \# M14
1 W& V% _. ], N, N# L, W. j6 x152 C- S* \1 t- z# _' h$ |
16
& A' G& y' z! |) F* ^# A17
; E/ `7 _& c* r3 F18. H! T+ Z. P8 Z \" W' t( R
19
3 B4 C2 n( g. X. [$ H209 A, w/ Z4 x L1 A2 U0 C, g
21
* K$ H i2 o7 |# F) Q) @" c% x22
/ M0 K d; \ h7 ?. ~23$ q- X B0 y' {+ C, E
244 U P( u$ j0 }4 U$ L9 U8 ~1 w2 A# j
25
& l$ e$ L7 A' n1 h26 J& S& k' s! k7 q
27
o+ l" Z7 P* K, L1 N: B' e28
0 q$ f2 l5 x+ p29
w* s& v# } t: W1 D F0 K30
/ z/ M5 f9 W' }. v. E. L1 V+ g31
1 V# k8 }8 T* c: x" F32
( w$ n9 Z+ P8 Q! [* ~331 R; n, \/ P, T5 v- u
344 [+ c! O* f3 S$ M
35" {: e# G4 k* n6 v
36
2 [% O2 a' U4 ?& o" | p8 |37" T0 E: i+ ~$ c7 i! N) x
38 a% \; m2 B1 o
39
5 @& O/ o# d) ^6 n40
4 X7 C* [" t9 [ | L H41
. _. I @+ T. n$ q, u9 W3 M, r9 Z* x42- D1 z1 D" f# c* k
439 o( r5 B, U& P2 N( W9 K% ~
44
1 I8 W- I; h) j! W D9.3 区间类别* _; t3 h( c% F
9.3.1 利用cut和qcut进行区间构造
! J3 F8 G3 Q" d/ i {6 y# [& `1 I 区间是一种特殊的类别,在实际数据分析中,区间序列往往是通过cut和qcut方法进行构造的,这两个函数能够把原序列的数值特征进行装箱,即用区间位置来代替原来的具体数值。. z" j/ u. Z* n q, B D& D
x- R8 k/ H; ]cut函数常用参数有:
4 D9 d. T! r1 X; K1 Z7 tbins:最重要的参数。8 q# t( [' t: l2 D: a3 P1 c) M4 I
如果传入整数n,则表示把整个传入数组按照最大和最小值等间距地分为n段。默认right=True,即区间是左开右闭,需要在调整时把最小值包含进去。(在pandas中的解决方案是在值最小的区间左端点再减去0.001*(max-min)。)8 m: k8 P9 @$ H0 z8 P6 E
也可以传入列表,表示按指定区间分割点分割。7 g: n/ F/ _$ c4 K5 E. _
如果对序列[1,2]划分为2个箱子时,第一个箱子的范围(0.999,1.5],第二个箱子的范围是(1.5,2]。: `2 K5 e* P' e6 o' ?7 ~
如果需要指定区间为左闭右开,需要把right参数设置为False,相应的区间调整方法是在值最大的区间右端点再加上0.001*(max-min)。. f- w9 u* `2 X! S; ~ @
8 ]4 f) k5 ]; V/ L: ^ r" [0 y$ S9 G# C
s = pd.Series([1,2])( Y! f# C. S3 L2 S& e0 {! {
# bin传入整数
4 s; h0 B: [# }/ Y" d
& j( X6 p' t: C" ~, v( Gpd.cut(s, bins=2)4 w7 c# e6 `6 b; m, K# |# u
Out[39]: ) @3 Z3 i! _9 ^: @4 O2 K' s& ?, _
0 (0.999, 1.5]
4 x( F+ s2 u6 T V1 (1.5, 2.0]
8 e+ @9 b7 x1 E: O x) [! H) Mdtype: category
0 L- e1 a% n5 R( i' ECategories (2, interval[float64]): [(0.999, 1.5] < (1.5, 2.0]]
) s* b, s; f' l; J* s; O
4 a4 w, E+ t- I. W$ [* k( P% Spd.cut(s, bins=2, right=False)& \2 q: E+ ?( R% \1 X
Out[40]: 7 s: t7 q0 g9 K( L% {
0 [1.0, 1.5)3 J0 U5 I, U& W3 O [& ?
1 [1.5, 2.001)
$ p8 J# y: B$ y/ K4 S' A0 M( Pdtype: category
3 c' \" ~& }6 j9 xCategories (2, interval[float64]): [[1.0, 1.5) < [1.5, 2.001)]
" O# U" l ?; R2 ~8 r
; p o/ M7 F& t/ Q" f* R: @& y/ L( L1 D8 ]; j* T
# bin传入分割点列表(使用`np.infty`可以表示无穷大):: G+ {3 p. V# {% O! B# u3 @$ e2 l
pd.cut(s, bins=[-np.infty, 1.2, 1.8, 2.2, np.infty])
3 n% W. }6 @1 Z: a. \9 O% w: HOut[41]: ' d) R5 Z3 d5 R1 {0 P1 U$ ]( O
0 (-inf, 1.2]- k1 {( ^8 b: O: G- a; L; }
1 (1.8, 2.2]. ]$ h" z# d6 B
dtype: category. O. ^5 W! m& w$ S4 F* L
Categories (4, interval[float64]): [(-inf, 1.2] < (1.2, 1.8] < (1.8, 2.2] < (2.2, inf]]- Y$ X7 D8 x! E# d0 h0 {3 ^; G
4 d% Z1 `* k: o8 @( g# B
1
' D d$ }9 {9 K1 C& x2' I& _" B& a, z; h1 v: J; b
3
( i1 `( Y @' A6 c& W# u4
: l, Y6 e0 f5 p# v56 H" ?9 h3 o+ t# Y* a5 J: e) Z- M. A/ }
6" K6 u% W1 f+ V7 U
72 r! q( d/ x5 N+ f3 K( {
8
: i2 T% |6 x/ A: t5 E+ X) B4 `* X9/ s0 q! Y7 m% B( D. G
10
; n& q/ t! w: H: X/ | r11
4 U* j- m8 |% T, Z124 s4 }/ q5 w$ ]* |& z3 i, m, ^& F
13
8 X6 v" b! r! U) ]% P146 Q( n7 I; A/ m. Z' ]3 @ g0 v# f* F
15' n" S" [- l1 t
16 T9 I( Q3 [, Y% E
17' e+ `$ l: l# @! f9 z- d
18
2 z* w/ S4 g0 V n7 u19' ]. F! {9 V; {
20
7 F+ R9 V# O" R' z* J$ A21* N, H" {4 u0 T3 G1 ^9 I, i
22' N$ ]: \+ I0 Y- ^* l
23
. ?$ z8 o3 c. @24
9 T4 o% _+ e) H. e8 x, Z# m' V2 z4 u257 _6 Q0 u3 s m( Y) F/ ^
labels:区间的名字; j$ e/ w$ Z+ D |) r% F
retbins:是否返回分割点(默认不返回)2 _& A% w! ?4 U; ^- T& k& V
默认retbins=Flase时,返回每个元素所属区间的列表( \/ N" N$ E C3 L: C- W
retbins=True时,返回的是元组,两个元素分别是元素所属区间和分割点。所属区间可再次用索引取值
$ y: k, B: Y' }3 {9 ]7 X! u O
k9 P6 v# b" Z2 Y3 I% c; }- Ns = df.Weight& u* \# d7 {5 y8 @5 _1 Z: i+ h: z" r
res = pd.cut(s, bins=3, labels=['small', 'mid','big'],retbins=True)! D Y% W- k5 x c& Y) j5 D
res[0][:2]- P7 }/ [$ ^& t
+ P G+ D* h4 w/ ]- [' j4 O0 pOut[44]: 1 W) w# R. {# j% u* l) f1 D2 I9 K
0 small9 f+ N, {9 x7 |7 \* @
1 big
0 n, z* q: m1 F0 K+ Hdtype: category3 E7 H6 A/ [) J, o: q' V
Categories (2, object): ['small' < 'big']
, D/ s; N. a' g. e& }' o& Y% P- u( u7 [6 v' k$ T: o
res[1] # 该元素为返回的分割点' ], O8 e$ A: m N- J4 O) v
Out[45]: array([0.999, 1.5 , 2. ])
4 O4 V& `. z3 H( G4 ^* E1) Q; d$ }7 A5 c! U6 _* v
2
9 F, e; |* I6 ?) M3) b! B0 Q) m7 J
4
1 D! b6 D1 C; s* o/ U5
, E1 E" L- p/ g, h& R' P6
! {: B2 s- e2 {+ U/ k! @7
, ?, O8 o1 n' Q; i% t' y8
: A4 ^; x8 d0 R9
R1 H, I' G* p; I104 e9 ~( n3 u6 U6 P$ w: v2 M H3 G
11
4 `, `( z' N" v12
" j& l) ]6 h3 |' [; xqcut函数。其用法cut几乎没有差别,只是把bins参数变成q参数(quantile)。 p0 d# q8 }# C+ y
q为整数n时,指按照n等分位数把数据分箱; Y! C$ G/ g. K" ^% M0 W0 y q9 J
q为浮点列表时,表示相应的分位数分割点。
9 u. y( Q; j& D0 Z* d6 _" os = df.Weight7 Z/ }* x% B) o, D" I* {
p! r B7 G4 {% C1 apd.qcut(s, q=3).head()7 L/ |7 H8 J! f3 R& M+ Y) h
Out[47]: + }2 u0 O g& X& s- s% N' n* |# U
0 (33.999, 48.0]/ D$ `! q# n/ A. l# [
1 (55.0, 89.0], ?7 m3 Y& D- z4 [; U6 F0 Z3 y
2 (55.0, 89.0]
5 n- V, H) }) ^8 I- O3 (33.999, 48.0]3 N, t4 }3 ? n. ~
4 (55.0, 89.0]+ D$ O( y V5 N* E4 F) x* c
Name: Weight, dtype: category# r5 P" z6 h1 W5 {4 D \, `, d
Categories (3, interval[float64]): [(33.999, 48.0] < (48.0, 55.0] < (55.0, 89.0]]2 D1 @2 ]/ j* l
- _$ M) P3 D4 n* _4 @1 {( o% i9 npd.qcut(s, q=[0,0.2,0.8,1]).head()1 a- {7 |) J5 Y; b0 q3 g
Out[48]:
$ h2 n( z. B& _6 d1 p0 (44.0, 69.4]
( b; T3 } _$ u$ F8 P: c1 (69.4, 89.0]4 f6 Q' V5 M/ M) L
2 (69.4, 89.0]: n. v e5 Y" t/ L3 Y$ G s* C
3 (33.999, 44.0]
w5 T, h7 p- [* n4 (69.4, 89.0]* L3 h7 i( O" p. O3 B; M
Name: Weight, dtype: category% ^; p* }4 e6 B6 c+ j2 H" n
Categories (3, interval[float64]): [(33.999, 44.0] < (44.0, 69.4] < (69.4, 89.0]]( t) @/ a8 i, o3 b
1 h/ Y, q: B+ U* n1 S1
- ?% I' N* D5 c5 B; B7 ~2
0 c* A+ K7 ~# w, i3
8 M' h# P/ H2 H! I* s4
. j# }% Q6 a, e- k4 `: M9 v4 e3 ~5
( R4 M s( f1 `" g67 _) X0 J5 Z i9 b0 b
7
$ n9 [* Z) b% m8' o, I# ~# ~/ V
91 X& ] Y- o" {* R
10
( C8 t9 w }' O; F) h2 |11- x9 J# X) A4 c6 p" a: o2 Y" p2 u4 O4 G
12
! W, y, r5 K6 m; S13
# q+ |7 \% ]; d! K14$ O3 T, V" K7 O8 y. I6 [, u
15
- {2 g8 Q ~, [# y16
+ q4 K: |4 e2 [! R+ E' Q17
2 w+ X7 m" G' w# T7 y0 y18% z b* u# g# \2 W9 k$ @! `( Q( l
19
. Y% ]/ }4 @& q* N20
$ \) @+ O- }. _ \( a21
: U( A6 z6 n$ u3 `' z9.3.2 一般区间的构造
+ e7 m7 N3 O9 ~: }) u0 V4 A0 g pandas的单个区间用Interval表示,对于某一个具体的区间而言,其具备三个要素,即左端点、右端点和端点的开闭状态。
- ~( C M F9 M* Z! M* N; u+ @* D( p* [9 J( ]0 f7 C0 K; T' Q
开闭状态:包含四种,即right(左开右闭), left(左闭右开), both(两边都闭), neither(两边都开)。" `1 f0 ~3 c) {
my_interval = pd.Interval(0, 1, 'right')
% b+ ?, d7 ?7 l7 |! D0 x/ G- d \3 W/ L/ D1 ?) c
my_interval. m/ ~3 c4 l5 m' U' {+ q% l
Out[50]: Interval(0, 1, closed='right')+ r( L% R7 u* v: p, e
1
' v3 b' [2 N: h4 G5 H- I0 E2# Q! Z1 P3 S7 H, h7 u- d- \! M" z
3& E9 Y0 B0 \% q" p" z
4) u! e6 I- I, }
区间属性:包含left,mid,right,length,closed,,分别表示左中右端点、长度和开闭状态。- C: v3 }, M; Z! o) @
使用in可以判断元素是否属于区间( f! l$ ^! k: [- _ c( G0 e
用overlaps可以判断两个区间是否有交集:5 e6 K; ~: m* ^) W
0.5 in my_interval) P% |, J/ ?% r ^
: ~1 |. c7 j/ ~5 S
True/ s. j- ]! S2 f: Z7 Q
1
, O' o: L2 t3 l( i3 V* H8 H2
2 V* L) v0 P' O3 f4 c36 |3 r/ y$ f ]5 n" R
my_interval_2 = pd.Interval(0.5, 1.5, 'left')
/ o; R& w2 f* i- F- Lmy_interval.overlaps(my_interval_2)$ R; k, M& u1 `% P/ ~4 ]* o+ g+ n
* \+ Q4 v& Y* V% d; w4 a
True
8 k+ p* l+ U! Z# A! k5 y1 ]6 T2 g9 _' f
2
( @$ Y/ N# o; q3
5 ]9 @ h* [0 Z1 M% j$ _4& G: P& B4 ]3 z7 f
pd.IntervalIndex对象有四类方法生成,分别是from_breaks, from_arrays, from_tuples, interval_range,它们分别应用于不同的情况:# O: e8 s% Q* }- `! t( z- W9 ?6 F- S
A( F, y$ @) `3 x j' S `* r" k _% i) m
from_breaks:类似于cut或qcut函数,只不过后两个是通过计算得到的分割点,而前者是直接传入自定义的分割点:
4 H0 V" z5 u+ Q! G# mpd.IntervalIndex.from_breaks([1,3,6,10], closed='both')6 L# t3 e. O& o: c0 g) _
* {7 p5 P7 r7 aIntervalIndex([[1, 3], [3, 6], [6, 10]],' e5 {+ r3 R( V1 X/ T U
closed='both',# B0 F1 u# S& q! ?1 Y3 p
dtype='interval[int64]')# o' Z5 b( K' ?) e- o
1. N3 p6 F% Z# z0 D& o; ~/ m
2
9 \1 L$ Z1 q3 b, Y5 p# E' t3" P' O( d$ ^% V( o
4
* w. M$ _3 R* g% g- \7 I' ]5
5 g: @6 L( M& v* B6 o9 Q9 dfrom_arrays:分别传入左端点和右端点的列表,适用于有交集并且知道起点和终点的情况:
( C( d3 h1 q# G$ dpd.IntervalIndex.from_arrays(left = [1,3,6,10], right = [5,4,9,11], closed = 'neither')
) ~) Q) s B8 B5 I' i
/ R1 _0 {# U- Y# |IntervalIndex([(1, 5), (3, 4), (6, 9), (10, 11)],
4 X1 l3 t/ n: q0 s% E& c1 ~2 j closed='neither',
8 K: }6 j' \. X dtype='interval[int64]')% X/ q$ g5 z- O' i Q* d9 l
1
6 V! F% u% G( c- D. F) Z7 K7 e2
! _7 X _' u& ?) [# P. J- N; \, ~$ W3
) B! z& r" Y8 a) ]: h4
/ I! i% q# Y( \9 n a. A5/ ]2 m0 X6 _3 l! j$ Y& P K0 F
from_tuples:传入起点和终点元组构成的列表:6 h3 j1 d: d( n. L) i5 i
pd.IntervalIndex.from_tuples([(1,5),(3,4),(6,9),(10,11)], closed='neither')
5 e5 v0 Q' H* C& P/ r) h! A( _. c+ W6 o/ r
IntervalIndex([(1, 5), (3, 4), (6, 9), (10, 11)],' k4 ~, I8 v* h' Y1 Z# x8 M
closed='neither',
* R3 }% |3 z) l, M3 T; _ dtype='interval[int64]')
7 C7 {4 J* h9 R; o: `; V1
i2 n) q( ^' x2 Q( ]% f" c6 I/ p2
. _5 e' {1 `2 Q8 d: O: \/ o7 \3: c3 o4 d- g/ v# e3 ?# ?% P
4
3 E) k' J" \1 \# i$ R9 R9 e5
/ X$ N+ u4 M" n& b' q8 h- i4 F$ p8 Finterval_range:生成等差区间。其参数有四个:start, end, periods, freq。分别表示等差区间的起点、终点、区间个数和区间长度。其中三个量确定的情况下,剩下一个量就确定了,从而就能构造出相应的区间:8 n- U! T' a7 b4 F8 I$ f6 }
pd.interval_range(start=1,end=5,periods=8) # 启起点终点和区间个数
1 v3 H' i' |; D" E/ N1 Z* H- ]. |Out[57]:
) k. A) D* G( M0 jIntervalIndex([(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]],9 ^- }3 a6 b) h
closed='right',3 n f0 K) }% n, x6 }) d
dtype='interval[float64]'): l( J: R8 j- u- Y+ p
4 K/ e5 l& x" V+ }; a7 ]$ V s
pd.interval_range(end=5,periods=8,freq=0.5) # 启起点终点和区间长度
; b7 _2 L$ Q H* |Out[58]: 5 P$ f. A3 U/ M) ]/ [* J
IntervalIndex([(1.0, 1.5], (1.5, 2.0], (2.0, 2.5], (2.5, 3.0], (3.0, 3.5], (3.5, 4.0], (4.0, 4.5], (4.5, 5.0]],
) d; c% S8 Y0 ?! }7 h/ q( e: X closed='right',/ U( J$ C; f# T- [
dtype='interval[float64]')
$ d& w9 P# g% N4 C6 ]1
1 `' V$ X% g' a: Z* l2; _( v- o( x2 X2 g& J9 C4 g8 J& L
34 m$ V' D& y& [. l( s) V
4, S0 F3 d' a1 h2 e- f
5- R" I# _8 `1 a2 U
68 l/ I; c1 y9 x1 G6 U4 T, H
7* V1 D! ~$ l6 w4 [4 s
8
- L5 g9 C/ g" Q, U9
: G% G7 ~0 H* r, U5 v% U$ _' _10! A* y9 M1 A. `/ ^( v# }7 }4 R: D
11
\6 d" g! r2 \$ N. n# y w: A9 v【练一练】 a2 K: X3 l! I6 r& O
无论是interval_range还是下一章时间序列中的date_range都是给定了等差序列中四要素中的三个,从而确定整个序列。请回顾等差数列中的首项、末项、项数和公差的联系,写出interval_range中四个参数之间的恒等关系。
3 [& @2 F& }; K/ m6 u' l$ s: C+ |$ {
: n- m3 @0 w% p+ V+ y% T 除此之外,如果直接使用pd.IntervalIndex([...], closed=...),把Interval类型的列表组成传入其中转为区间索引,那么所有的区间会被强制转为指定的closed类型,因为pd.IntervalIndex只允许存放同一种开闭区间的Interval对象。- c9 g8 \/ { \ r4 i/ g
# ]( o, W+ K' ^9 |
my_interval
7 B6 ], A& c5 F8 L) o7 {0 e9 L0 ROut[59]: Interval(0, 1, closed='right')1 P) x8 A: }; Z/ m. C: A( z
3 ^: K0 [) ?; g' C
my_interval_2' O+ O: j3 _: E3 x( z" n1 `
Out[60]: Interval(0.5, 1.5, closed='left')' Z' U$ B0 c8 F. `3 G
1 X- _9 T- p. t3 q0 z
pd.IntervalIndex([my_interval, my_interval_2], closed='left')
. A; b* a0 ^0 H4 S3 F0 SOut[61]: " X7 M! t' J6 t a3 \- E8 V& i
IntervalIndex([[0.0, 1.0), [0.5, 1.5)],
+ n: s7 l4 F3 L. H' B closed='left',
/ K) M6 Z6 f7 |2 V7 t# V( I1 z dtype='interval[float64]')
' ^5 M/ A7 W6 \1
- k6 L' l% A% Q Q- @! @2
8 F/ T* b; {5 j3 D3
( c1 [+ B5 ~* n) a4& d! x$ Z k) p( s3 e
5
3 F. W( F; |, I5 A6
* S' _" \" ^! P% r7
( K% U/ O( U$ o6 W/ c8' R5 {- q5 D3 Z; q
9. e- T/ w) P) T6 `; {$ r- R
10; K, K6 ]/ `: v, {/ e; `
11
% V y; W& t4 }9 p9.3.3 区间的属性与方法
% ~7 v6 G M# Q! r3 b! R. d* I+ s, z T: Q IntervalIndex上也定义了一些有用的属性和方法。同时,如果想要具体利用cut或者qcut的结果进行分析,那么需要先将其转为该种索引类型:
- Y5 w0 M$ g& H& ~$ a/ ]
9 A$ H1 \7 o" E& hs=df.Weight
1 M' z! _( U( z* f5 q/ rid_interval = pd.IntervalIndex(pd.cut(s, 3)) # 返回的是每个元素所属区间,用具体数值(x,y]表示$ M B+ R0 ^ N$ i- E
id_interval[:3]
! ?* h% q; B/ R) Y+ u! A. p/ r, m. V/ K
IntervalIndex([(33.945, 52.333], (52.333, 70.667], (70.667, 89.0]],6 C, l6 e. a# ^& p3 t [- ~" w
closed='right',; S2 y- i r6 Z' h% [ [4 x( o
name='Weight',0 B: e2 N$ t/ V2 N4 K
dtype='interval[float64]')5 g8 p$ w# m- s8 c- v( E
1: Z$ F% L, H$ L2 I( V
24 Q& o7 y& t* E6 t/ G8 z5 D
3
- H U7 C3 }3 g43 x0 l. {9 A1 g& F" U6 u
5
2 z/ P$ S& u, _0 j! `1 P( m7 [6
% q. x- ^+ J+ H2 @. `7
# V$ P" Z, @+ M1 b8
1 k% Z7 W6 g/ o" T与单个Interval类型相似,IntervalIndex有若干常用属性:left, right, mid, length,分别表示左右端点、两 点均值和区间长度。 h+ D5 e' F' f
id_demo = id_interval[:5] # 选出前5个展示5 C: F- i1 o& z8 }! ~! U
3 A# {) Y& R: V/ Q7 E# i, X( s, Wid_demo: s- d1 H* S( ?9 E
Out[64]: & u, p/ p: n& z
IntervalIndex([(33.945, 52.333], (52.333, 70.667], (70.667, 89.0], (33.945, 52.333], (70.667, 89.0]],
6 A8 T2 \$ j& i& J! C0 ?& | closed='right',
$ _2 }( l) o2 q4 |# o name='Weight',' f$ F0 @+ n" S7 Z3 A2 b! Y/ ?
dtype='interval[float64]')5 Z# J: x8 [. A4 m5 N
) O' }# i( T; N1 f2 Sid_demo.left # 获取这五个区间的左端点
" K6 y* F4 A; ~: y6 ^Out[65]: Float64Index([33.945, 52.333, 70.667, 33.945, 70.667], dtype='float64')
" P9 x+ a( _8 C9 N6 L p! {& z0 n8 K+ R2 A2 e1 w! h
id_demo.right # 获取这五个区间的右端点6 S# ?3 Z1 I6 p1 L
Out[66]: Float64Index([52.333, 70.667, 89.0, 52.333, 89.0], dtype='float64')% ~% p/ a: W0 U
$ C. l4 `" D5 H8 s5 c8 K
id_demo.mid
; m/ O+ Y( w% O4 sOut[67]: Float64Index([43.138999999999996, 61.5, 79.8335, 43.138999999999996, 79.8335], dtype='float64')
4 d6 s3 }5 M( H& k) {3 X
! G7 I3 w6 H/ g) B& Bid_demo.length8 s) `9 y% e# _" @8 C* N @
Out[68]: ; V' S! {" L+ \9 r
Float64Index([18.387999999999998, 18.334000000000003, 18.333,
h9 r! {4 u6 X* N( V" V 18.387999999999998, 18.333],
3 f) v3 |; q1 j( _ dtype='float64')% o! ~9 r1 v1 v7 d7 p- S) A
' u# k+ `& ^. R4 f2 ^
1
) J# f$ [* Q8 ?1 F U2
\/ u, Q0 w! Q: ]# Z/ r$ Z34 c) a( d R3 a F& C) f
4
% }' o9 R7 k# @$ x n6 t( p- F5& \& j# z% C5 o: }. o7 b
6
7 i, V* K5 |) r0 ~7. C0 H" z" b* R. I5 F6 w' |
8
d: I3 ~$ L( r" g: j! U2 N9+ T: y, |4 l) m1 i
10
5 E" c# Y! l4 @$ v$ p4 m L11
5 u: ?% [3 Y' v' }126 a+ K' L+ I8 ^* F; U5 p
13
4 I, _+ j. [0 n( `14) n+ y0 G- S7 f5 N+ Z4 m
15& m3 E$ j2 G G
16; r% n3 a* C: q# |3 N6 j6 H+ @
17
; l2 N) X# V5 C" _8 @. X18& z- B3 \6 g! j$ D1 v" f
19
- ?! V1 j9 f& x20( b7 E# A& S9 R; k) v/ j3 O1 c
219 d0 Y! G1 }7 p* O
22 G s4 M5 b9 L" R
23
- }7 }4 Q' Z" ?5 U GIntervalIndex还有两个常用方法:
6 W) J; Z1 U1 M5 ^contains:逐个判断每个区间是否包含某元素* z V% i, o$ x8 a. z1 f
overlaps:是否和一个pd.Interval对象有交集。
- b' K$ W2 ~& V7 O8 r( nid_demo.contains(50)
& n& N# U( r$ T8 q7 SOut[69]: array([ True, False, False, True, False])
7 ^! B5 j2 W9 @
; k# a6 |8 q& ^; S! N- Pid_demo.overlaps(pd.Interval(40,60))
9 Q1 p+ _! a; V- KOut[70]: array([ True, True, False, True, False])
( I" R. s U0 Y9 a+ J0 Z1' J$ X& D- @6 `, W
2# N1 q% Y$ P' q5 u* b
37 `$ A2 g' P: L* D+ k- t5 l
4
/ H$ x4 T' r& R( ?5% [$ ^3 \# n% l- F4 |' {* W" j. h
9.4 练习
+ [: f# H$ t5 v4 X3 _2 aEx1: 统计未出现的类别
: o6 Y# J: n5 K( L 在第五章中介绍了crosstab函数,在默认参数下它能够对两个列的组合出现的频数进行统计汇总:* s/ \# _! c a1 E+ D. b' `) i
' x5 Q* ?/ H7 j: ]5 Tdf = pd.DataFrame({'A':['a','b','c','a'], 'B':['cat','cat','dog','cat']})
* Z1 c# r7 i m0 e- F8 `pd.crosstab(df.A, df.B)& x6 y2 L& z3 I7 L, c( \( W
) S/ T: D$ x! e+ m2 d9 r
Out[72]: - N) q0 E. T* `# `
B cat dog
. r1 q2 x: Y- t* N7 c3 F+ oA
- b- a" M* q) }: Y: E5 j9 ha 2 03 \" `+ D2 _* Z
b 1 0
( j5 u; v! ]0 t9 Kc 0 1+ m; ~) Y! j7 E; d1 o- ~
13 m& m1 ?4 q) P8 S! E( j. q
21 c4 a3 D% Q" ]
3
4 \4 L# T5 ]) D- H$ p4
0 A2 z4 t$ |- t8 Q4 k5 y7 F5: I* \! A+ n% }
6: S- ~2 D- s/ E7 Y) @" h
7
& d& e* f" j# r8* q2 g9 f1 q8 }' C8 p1 t
9
. H9 e- `$ l) z1 q2 W0 D5 Y 但事实上有些列存储的是分类变量,列中并不一定包含所有的类别,此时如果想要对这些未出现的类别在crosstab结果中也进行汇总,则可以指定dropna参数为False:+ L2 O: a) x+ }
3 T1 n1 A0 y* r5 X% e2 s8 Cdf.B = df.B.astype('category').cat.add_categories('sheep')
5 I9 X! z8 \# }0 q6 t k" A* a Tpd.crosstab(df.A, df.B, dropna=False)
2 r* o' `* a* V0 k {& f7 e8 J
- B$ D' }5 C9 ]5 Q4 mOut[74]: " P) s; r& R+ M( ~: q C1 m/ j
B cat dog sheep$ T3 T5 V+ Z* c0 l- N/ u9 y
A 2 b! U. p: x5 p! Y
a 2 0 0
$ s+ p9 e; R: F0 y) {4 ?b 1 0 0
* w' S8 Z0 T# lc 0 1 0' Y" D! O) L, a
1
/ e: \. L& T, O: N( ~5 p2% L1 W8 z" x% ^' Z
3
: O! }4 N& ~0 N: J# R& `/ \$ ]4
7 J+ k9 c0 F' W" J59 t: ]4 I4 K) P4 R
68 y: _; Z u& K! s9 M9 i3 i
7" ]$ K, {# W2 Z5 k
89 L5 h( J0 [0 n* H
9
0 Z; Y8 G; t* C4 j/ l6 S请实现一个带有dropna参数的my_crosstab函数来完成上面的功能。 {) D) W$ t* _. T; K* D% O
! G% p0 \% ^4 X5 U: [0 B, [0 gEx2: 钻石数据集
) A) i3 U. x0 z# K1 C 现有一份关于钻石的数据集,其中carat, cut, clarity, price分别表示克拉重量、切割质量、纯净度和价格,样例如下:
. L( w$ r# U2 Z# s1 _8 e. N# b3 X7 e. ?$ K7 ]) O& J: S: ^
df = pd.read_csv('../data/diamonds.csv')
& y" v3 }! O+ u0 Rdf.head(3)
7 A) m; l# v' @7 L6 F
2 o2 q g# p1 Q! l" t6 dOut[76]: 4 g1 o) S0 \- m% ^5 k2 ~- T
carat cut clarity price: D6 e5 a- G4 F
0 0.23 Ideal SI2 3265 u( D& B- j% T* A/ _/ U, n# ?1 _
1 0.21 Premium SI1 326
5 v4 a: ~" R1 J. O% i8 {+ y# g2 0.23 Good VS1 327 J% v- ]& g/ p3 I8 Z0 D
1
U! p$ o. I+ d) m$ r6 g20 E: w( I" \; D0 G1 {
30 d- b/ L" W, I/ ~
40 U( n) Y3 @* D, @( `
5. y$ c4 F9 _2 F3 c* x: x
6' s% G" ?# k k
7
6 R9 O7 p. w* h8 F9 q* c8. z+ @* m& z* q- U7 {/ S& e/ V
分别对df.cut在object类型和category类型下使用nunique函数,并比较它们的性能。
$ f1 R& [( L# |. A) c$ s4 U钻石的切割质量可以分为五个等级,由次到好分别是Fair, Good, Very Good, Premium, Ideal,纯净度有八个等级,由次到好分别是I1, SI2, SI1, VS2, VS1, VVS2, VVS1, IF,请对切割质量按照由好到次的顺序排序,相同切割质量的钻石,按照纯净度进行由次到好的排序。6 b% H/ m+ k6 i, j& P+ }
分别采用两种不同的方法,把cut, clarity这两列按照由好到次的顺序,映射到从0到n-1的整数,其中n表示类别的个数。$ R6 v2 d, H0 r, o+ _
对每克拉的价格分别按照分位数(q=[0.2, 0.4, 0.6, 0.8])与[1000, 3500, 5500, 18000]割点进行分箱得到五个类别Very Low, Low, Mid, High, Very High,并把按这两种分箱方法得到的category序列依次添加到原表中。: V t R; H1 g3 a
第4问中按照整数分箱得到的序列中,是否出现了所有的类别?如果存在没有出现的类别请把该类别删除。
0 U7 I8 b+ m. k: s. I& [8 h F/ x! z( ^对第4问中按照分位数分箱得到的序列,求每个样本对应所在区间的左右端点值和长度。
8 A8 X1 Q! p7 d5 x5 j8 h先看看数据结构:
: f* _2 O h6 k7 e/ f
/ n. d3 s* `/ O' edf.info()3 W4 f/ \! D" a+ r7 ~8 H
Data columns (total 4 columns):
+ b( C' a1 _8 ~ # Column Non-Null Count Dtype
& q& K) S6 t4 y$ y+ l( \--- ------ -------------- -----
' _, X; ]$ c* w/ ] 0 carat 53940 non-null float64; D9 h0 g% H' V$ J* g
1 cut 53940 non-null object 1 p$ r J* B( X$ q2 E5 _( m
2 clarity 53940 non-null object Q9 ], q! G5 x+ d, V
3 price 53940 non-null int64
% z- y/ a- O; P: Odtypes: float64(1), int64(1), object(2)4 T2 k- G' j9 R3 o+ c
1
% a8 k" }4 O6 a) Y2
8 d3 O5 r2 t9 t" t7 Q; h32 c6 n/ O2 e. U# q- N# R
4% D; J+ R6 J* M$ u: B2 w
5
( ]! Z$ T' F$ r* x7 o# W5 h62 [( k/ T* x, }
7* d& c: p% Y" V( m& D) A5 P. W
8
/ z& `. x8 y+ ?5 j7 H9 y98 j+ X" p; p5 z3 H3 S8 q" `0 @8 u
比较两种操作的性能; \$ i8 ^* t6 O4 A3 S1 l/ Q
%time df.cut.unique()
J, x* D) e/ ^5 D7 t5 @; }( H2 R% Q" h- F
Wall time: 5.98 ms: Y4 P+ ^& `& ~ M) W% V2 z
array(['Ideal', 'Premium', 'Good', 'Very Good', 'Fair'], dtype=object)
4 y, a3 B: {. S: L4 f4 A12 ^: h. I8 e# }9 T/ D: a0 o6 z
2& O( n# k6 K0 n% U! M; M
3" k' g8 I( X! t% d
4
! G/ b" K% ^, q* J" {) i2 P1 D%time df.cut.astype('category').unique()
, b: u6 P+ u" d: K7 c" I4 ^" P, R4 `; h: F+ `
Wall time: 8.01 ms # 转换类型加统计类别,一共8ms8 D4 ]) a. R3 Z& \9 b" O3 z
['Ideal', 'Premium', 'Good', 'Very Good', 'Fair']5 j1 W6 i. Q' D. s g7 S
Categories (5, object): ['Ideal', 'Premium', 'Good', 'Very Good', 'Fair']
- m8 V) a& F |3 S1: ^$ r! u& L4 i3 f q
2
; I3 w; _, I" a1 y3
7 ]' Q2 h& x$ S5 d3 ]4
6 A3 {4 W* \$ R% q# T7 [, N55 i _( B4 g+ ]0 a! F
df.cut=df.cut.astype('category')
. Q% R) X! x3 v8 B6 z5 d9 C%time df.cut.unique() # 类别属性统计,2ms
9 }% Z9 z' C( h1 W! u- z7 a$ O. M' \0 X. m3 i
Wall time: 2 ms" }9 \9 f, M3 Q" ~
['Ideal', 'Premium', 'Good', 'Very Good', 'Fair']6 ~8 {' ?$ N. I1 I1 @- [
Categories (5, object): ['Ideal', 'Premium', 'Good', 'Very Good', 'Fair']- w' M- _ i5 G. ~1 [( K7 o
1
+ \9 t* [3 p% p! z6 y8 D2& g+ ]( E% f1 N- f7 K+ H
3
0 Q' S' G. o$ ^! A4
$ \6 `" P9 B6 g" t54 z0 X+ m, A( D, N" [' C2 k
6
* J+ H: w7 S7 G- r对切割质量按照由好到次的顺序排序,相同切割质量的钻石,按照纯净度进行由次到好的排序。
4 h4 j, k( w: A2 vls_cut=['Fair', 'Good', 'Very Good', 'Premium', 'Ideal']) }# Y" h6 G* @+ K3 i
ls_clarity=['I1','SI2', 'SI1', 'VS2', 'VS1', 'VVS2', 'VVS1', 'IF']3 ~ m" F/ O& y& U
df.cut=df.cut.astype('category').cat.reorder_categories(ls_cut,ordered=True) # 转换后还是得进行替换
7 O4 z% C+ a; D0 q. Ndf.clarity=df.clarity.astype('category').cat.reorder_categories(ls_clarity,ordered=True)
. s I/ s9 d( S& |. q6 j: O+ x& m% D$ s
df.sort_values(['cut','clarity'],ascending=[False,True]).head(3)
" `; `# c6 ^! [9 }& v7 j/ N# p. B6 _4 l
carat cut clarity price- m7 I k5 k/ Q% s. g$ d# P- H0 c
315 0.96 Ideal I1 2801, i2 s% i, w+ K- n( R
535 0.96 Ideal I1 28265 ]5 {! V$ J/ m9 x j# X) x
551 0.97 Ideal I1 2830/ e6 \( s) w! f- Y5 p( z
1. |& r: r4 w, M) B
2
8 j% u% i) z: r, E/ ~# [6 ?" v3+ T4 L$ P0 S8 z# f8 ^7 N: S9 v0 U
4
3 \' x1 I9 v# R5
( `/ k, P% X6 ` p, O& P8 z6
% m, G% V# x$ G. N8 d1 t4 K- e78 C( O. w; S; \, Y! Y4 [# g
8! m" b) _: q/ w: f% A, a8 A# E
9. R$ I6 b# F6 Z+ J8 B9 k
10
$ `2 [* l; ^* D2 I! k( Y11# w2 Q' J/ \1 a; S9 W# B9 o1 f" f6 E
分别采用两种不同的方法,把 cut, clarity 这两列按照 由好到次 的顺序,映射到从0到n-1的整数,其中n表示类别的个数。; h1 ~# F4 Y, T" q9 `: A; S/ U
# 第一种是将类别重命名为整数
d! E. c4 q- _% V" M5 Z$ \4 wdict1=dict(zip(ls_cut,[x for x in range (4,-1,-1)]))
) I) B9 o, `0 G- j+ X9 V0 Y% Tdict2=dict(zip(ls_clarity,[x for x in range (7,-1,-1)]))
5 Q$ g# _8 E; r' D0 S
) }, r2 k& }+ \1 Y5 jdf.cut=df.cut.cat.rename_categories(dict1)
7 ~6 a4 P8 k" l. H9 \" m8 R- Qdf.clarity=df.clarity.cat.rename_categories(dict2)* P5 ]: R3 A% t$ ]* ~
df.head(3)
4 v5 N7 ^2 ^% I* v6 }8 v" @/ s' T% ]: q
carat cut clarity price) L! O* n; t3 s8 [8 ?
0 0.23 0 6 326) k/ X% v# ?& w6 I4 M1 w6 A j
1 0.21 1 5 326
" k* u1 ~4 O S% {2 {2 0.23 3 3 327
( ^9 ^+ p. Z" H I n/ b" v1
! |6 B9 d$ s. e# K9 q2% c% ?& X5 q1 N+ V
3
& b8 V& P% g1 K# V9 e4
! G9 N1 ?# B, K- L6 R5& P0 X. N5 c5 x: ^3 C8 C# t* M! B
6$ P7 s1 Y' D; K2 }- o2 t; g
7
( a) e% O1 {/ t! k0 Z8/ Q) q6 ?! e: }4 k9 b0 O
9
+ A6 k6 z9 V G10
6 i# {6 z+ C. B" a5 P% T/ r* Q5 ?11* s M2 k6 B9 `7 S, _0 s8 Z8 _
12
3 a% J" {2 c0 w' H: {: B! \: D# 第二种应该是报错object属性,然后直接进行替换) J x& P, f) y
df = pd.read_csv('data/diamonds.csv')5 y7 c' R+ d. O" j# o: m7 O
for i,j in enumerate(ls_cut[::-1]):4 b0 X) ?2 o' S' G* M
df.loc[df.cut==j,'cut']=i ; l, C! @8 @& Q2 i; v! B( S
9 I0 M' ?) h" L
for k,l in enumerate(ls_clarity[::-1]):8 o- V' N; P8 e& o- T# _
df.loc[df.clarity==l,'clarity']=k
- A6 h% O9 o8 T! d$ kdf.head(3)
( L- _$ \* W: u) M! @' ?" L' X# D" A5 q1 {9 j- _$ \2 Y
carat cut clarity price
8 ~7 I/ J" ^. p0 {8 N. Z0 0.23 0 6 326
, \' f: s/ Y' ~/ P7 Y" `3 n3 H1 \1 0.21 1 5 326
- ~* _4 C e( A5 E R2 0.23 3 3 327
$ q9 h* t+ b+ Z2 k4 ]; q1
/ i; ~) f( B' o0 s/ a w* |2
3 e& ]! V# V# R! N" t# |$ I" W3& X% V$ `$ A; n9 J8 P" O
4
) w2 {+ W- n, R) J7 V4 _7 m7 G5
2 N6 ~& `3 |4 d. k9 D$ B' n65 h6 U: P- d, k2 U+ q
7
1 B$ z+ N" O/ N+ S9 x( |6 q4 F2 ?8" x* M/ S0 Q! M, S! m
9* ?0 u- J: H2 d! R6 p1 ^
104 P7 H( T w( ^* O6 R
11
4 v/ D P& {# J/ B3 q' S, i7 D12
/ H' {0 T. Q7 G0 X- G# b13- K0 ~4 z& \5 Q p
对每克拉的价格分别按照分位数(q=[0.2, 0.4, 0.6, 0.8])与[1000, 3500, 5500, 18000]割点进行分箱得到五个类别Very Low, Low, Mid, High, Very High,并把按这两种分箱方法得到的category序列依次添加到原表中。
! q! g. n- r" @% ~( t$ l# retbins=True返回的是元组,第一个才是要的序列,第二个元素是分割点& s6 b" N1 i" @; S! P; ]" o
avg=df.price/df.carat2 w6 w+ |5 c3 B. e
" j: T6 K/ K7 b# |6 E" l
df['price_quantile']=pd.qcut(avg, q=[0,0.2,0.4,0.6,0.8,1],
6 @9 d2 ^9 G& Y# v labels=['Very Low', 'Low', 'Mid', 'High', 'Very High'],retbins=True)[0]
; A5 s0 t5 Y- k& b$ i1 O5 w- u4 l2 V; O7 d* c. g* J
df['price_list']=pd.cut(avg, bins=[-np.infty,1000, 3500, 5500, 18000,np.infty],
- H- I* {7 f; Z( u labels=['Very Low', 'Low', 'Mid', 'High', 'Very High'],retbins=True)[0]
) O, E3 o2 \' b7 tdf.head()
1 }3 w5 A% q. k9 p# a0 A" @
4 d$ q% d4 z8 } carat cut clarity price price_quantile price_list0 v, P" U5 n( k7 G# R
0 0.23 0 6 326 Very Low Low! ]6 w' E# ]+ [' C
1 0.21 1 5 326 Very Low Low5 y2 s1 @- T, ~' w' O
2 0.23 3 3 327 Very Low Low7 M& w R6 O+ z6 Q- w
3 0.29 1 4 334 Very Low Low; R- \7 E3 d+ i8 w) v3 B+ g8 ?
4 0.31 3 6 335 Very Low Low : |2 w; C6 }1 ~: g4 r4 B
* g) n- M1 Q7 W6 g/ Y
1
8 i7 ?( \8 D( D/ I0 v2
; y% h( T9 q( t4 K7 P35 C5 z* j) p( `! M: h
4
@2 [. K5 Y; ^7 c2 b7 H$ O5
' `% K0 r4 p- d5 x: i" p65 I! A, \; o" j7 O# x8 k) j
7# l* x% v4 u! V% M4 n& ?( V
8
5 N( T$ r) x( P4 N5 R90 E& x0 K) t5 O5 Q5 R
10) d( b6 R: b; b) U" o
11* d: X, H0 p9 ^
12. W+ n7 t! F2 E' u
135 g9 A( e% m) u
14, y H8 Q. K6 \0 U; j
15
! Y, r5 y% c6 K" S166 S5 Q! _7 j* ]3 m
分割点分别是:' v* l! V- L) W v" s& Q) U8 f
& F! h2 d3 N: f7 Varray([ 1051.16 , 2295. , 3073.29, 4031.68, 5456.34, 17828.84])
2 x) r4 y- b0 \3 U6 K5 W6 C8 c% Garray([ -inf, 1000., 3500., 5500., 18000., inf])
5 U# N0 |( w' c* }" U1
, L" P; D$ j% X) x' ]/ _2' y6 x. r+ q- m* F
第4问中按照整数分箱得到的序列中,是否出现了所有的类别?如果存在没有出现的类别请把该类别删除。
0 A* a9 B% T) [: S9 q# Y( T$ B/ [- Cdf['price_list'].cat.categories # 原先设定的类别数
3 l3 I; m( [' r8 j; `Index(['Very Low', 'Low', 'Mid', 'High', 'Very High'], dtype='object')' H2 ?# z/ i9 R, @( ~8 o; p$ o. @
# D- |; |) O, p% ?2 |/ W6 B8 ?
df['price_list'].cat.remove_unused_categories().cat.categories # 移除未出现的类别
# w1 }6 I; i1 [7 @- r) A* q: c7 rIndex(['Low', 'Mid', 'High'], dtype='object') # 首尾两个类别未出现9 S6 E. d" { E2 C. a2 e# G& F
1
0 r; D/ S2 l0 e# q; {- o, `* n2! [* ~: Y5 z3 r7 q; L) j* x
37 ~ o4 T7 F6 x! b( g2 R% r
4
P; e3 o" V; l& K4 I8 b: y3 k5: n8 @+ M/ Y. O3 D& t
avg.sort_values() # 可见首尾区间确实是没有的) @, F/ F1 {6 @. n& W% a6 |
31962 1051.162791
; E- G8 j4 I8 A2 V! t15 1078.1250003 _6 O5 p7 X7 J# ~9 J5 b
4 1080.645161
: p# i" w# h9 }! g1 _9 U28285 1109.090909' M3 Y3 W' G2 K# k
13 1109.677419
2 ^0 h8 {) W7 x4 k8 S* M i3 ~ ... % l& S. m/ W8 ?# K& X
26998 16764.705882% @5 ]% D' Y, N' |+ c% E+ b
27457 16928.971963
7 L8 y# h) Y* R; g- t27226 17077.669903' M2 V" g7 N7 F4 H8 S
27530 17083.177570
( |7 ~: H2 X1 M27635 17828.846154* R5 g+ P( q+ L
1' ^! l( S0 a3 f2 f3 `
2
$ w' `( F& v% t: I% V* X' G3: {. F6 r# B6 }" k, ~% ?1 c
4
1 ?: }& ]' O& h9 H3 q3 b5 R3 w5
, w$ ]( ~1 V. L4 O6
: [% t) ?1 ~: u' E7 w6 F4 G- Q3 ^% T) R* y" D; G
8
9 m/ n- ?+ z/ d3 ]9, e5 e8 W: L: I; M! a, ?
10
6 D+ l- ]9 i% c" e/ \9 m' E11) ^, ]# V: `1 m a" H$ H5 V
12
8 m, X' u& s) Q对第4问中按照分位数分箱得到的序列,求每个样本对应所在区间的左右端点值和长度。
6 l% d5 o- y; w0 H' N# 分割时区间不能有命名,否则字符串传入错误。
! ^. O8 P4 c+ v5 C$ U+ n. Bid_interval=pd.IntervalIndex(
: B3 e- r+ c; u2 B4 f9 ]9 c! i& U2 ^ pd.qcut(avg, q=[0,0.2,0.4,0.6,0.8,1],retbins=True)[0]
" x0 Q$ `5 }+ L+ s: q )
4 }. V0 w. {! }) X1 C2 B8 @id_interval.left
: v, J: y6 ?( ?9 r. Z$ Sid_interval.right+ |/ u- W# X$ d! o* T
id_interval.length $ D) e2 z2 u0 k+ g4 j
1
1 z4 x- q9 r& {: u" f; f2
+ o) b8 @2 f9 P% O' i3, H% e C1 n- T* u" ?9 U0 e O
42 `% I( o, d4 Z8 d
5
9 X9 i# ?3 a# T a1 j) M7 E6, S$ q* w& ]" S! s
7
7 T o$ _$ d- }- ]. I, \第十章 时序数据
1 e7 H5 e+ g* r- `7 v. fimport numpy as np
3 i' }: ^; {# z B$ P8 ximport pandas as pd
W; _3 O4 s a/ c% _1
3 }% `& f& S1 _* b U( X4 n1 ?7 _% _2# f( |, B5 p, P3 Z1 B2 f
3 F2 r4 v+ W# o7 T
3 e( H& ^0 L1 r5 b10.1 时序中的基本对象
' _! C; E5 r9 E) ?$ o! n 时间序列的概念在日常生活中十分常见,但对于一个具体的时序事件而言,可以从多个时间对象的角度来描述。例如2020年9月7日周一早上8点整需要到教室上课,这个课会在当天早上10点结束,其中包含了哪些时间概念?
+ z+ l9 K' z- [6 [9 X1 V! l' e, ?" l& U
会出现时间戳(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的简写。( O# I* U) E% y" F. X# p# X, Q8 C; I
% `' b: q9 H/ r会出现时间差(Time deltas)的概念,即上课需要的时间,两个Timestamp做差就得到了时间差,pandas中利用Timedelta来表示。类似的,一系列的时间差就组成了TimedeltaIndex, 而将它放到Series中后,Series的类型就变为了timedelta64[ns]。
, W( o0 N* j+ G
6 e0 ^; v0 W/ {; T( c会出现时间段(Time spans)的概念,即在8点到10点这个区间都会持续地在上课,在pandas利用Period来表示。类似的,一系列的时间段就组成了PeriodIndex, 而将它放到Series中后,Series的类型就变为了Period。* f% E3 f: _% ^' k p: Y
1 i8 J" `2 v8 S# \会出现日期偏置(Date offsets)的概念,假设你只知道9月的第一个周一早上8点要去上课,但不知道具体的日期,那么就需要一个类型来处理此类需求。再例如,想要知道2020年9月7日后的第30个工作日是哪一天,那么时间差就解决不了你的问题,从而pandas中的DateOffset就出现了。同时,pandas中没有为一列时间偏置专门设计存储类型,理由也很简单,因为需求比较奇怪,一般来说我们只需要对一批时间特征做一个统一的特殊日期偏置。% O. @( E- N/ i9 t
; }# `! e8 f$ H
通过这个简单的例子,就能够容易地总结出官方文档中的这个表格:
9 r' S `9 a( j& h& V. X. S4 R
% S1 L S% f# k1 D H( U1 p R概念 单元素类型 数组类型 pandas数据类型
# o- ]7 x6 F: g T" P$ Q# tDate times Timestamp DatetimeIndex datetime64[ns]
4 E* f/ ~! R5 ITime deltas Timedelta TimedeltaIndex timedelta64[ns]/ ?! u6 B M( W) ~- d
Time spans Period PeriodIndex period[freq]- o4 ~7 w+ ]) l3 `3 }
Date offsets DateOffset None None% Y* H% N+ }# Z5 P- W& k9 W
由于时间段对象Period/PeriodIndex的使用频率并不高,因此将不进行讲解,而只涉及时间戳序列、时间差序列和日期偏置的相关内容。
6 `$ [; Z, s3 t1 E' |! K! e( W& O% n. B4 V8 @
10.2 时间戳
2 ~3 c X! d) _4 E% s' P% f- F) I10.2.1 Timestamp的构造与属性
- @/ R/ f& K) y/ U单个时间戳的生成利用pd.Timestamp实现,一般而言的常见日期格式都能被成功地转换:
5 Q& V$ W7 }8 M+ O9 U; _5 L
, d# [9 _6 P7 V3 K2 A% sts = pd.Timestamp('2020/1/1')
: B \' `1 v- O- I% F
5 l6 ~" i5 O/ L% W# u9 w; D( }ts8 c6 Y! i( x. h& Y
Out[4]: Timestamp('2020-01-01 00:00:00')
( N q# Q; i7 \, _8 y
B' N: |/ B1 c5 V* L& Q8 T5 hts = pd.Timestamp('2020-1-1 08:10:30')/ [* g+ t& [# I
% B. G0 T$ f4 u+ y$ k' j
ts
; g. K: w* F' T5 K% B3 h: B# \Out[6]: Timestamp('2020-01-01 08:10:30')/ ]: p e; `* K& j3 {' r
1" Y/ c' ~! e. P2 c
2
) F, G+ j6 F7 j) h8 p/ I7 W" d3
0 k8 i) _1 a1 e0 ?2 I. B4+ s1 {) h& m( y
5
, B; W Z* Z8 O. N K" E6
3 L6 d s0 t* o* K/ n3 U. C3 q7! n* L; H0 V$ |1 x7 X d" n
8
4 p0 z7 r7 E6 U6 [# q9
, O" O! `# M$ l" F- }0 n \通过year, month, day, hour, min, second可以获取具体的数值:" T* f. m$ K- V6 K W2 E: u
; D/ W0 [) p5 c7 W; t' K) ~) Pts.year' v: u( }4 O8 T4 L
Out[7]: 2020
! F' ?% y2 g. O9 R' w7 o1 J& x( q. V, T9 C
ts.month
E" L/ V0 }# w ]* cOut[8]: 1: L0 e- m% G* a. Q8 H( ^4 S: Y5 ?2 K
- | _+ W5 G, ?1 `1 i8 yts.day
! K* s. K! m5 VOut[9]: 1& w0 F; L, \$ K% N, l
- p. f* r8 j& }+ w# q% [8 o0 K
ts.hour
+ b1 Z5 k4 A% EOut[10]: 81 r/ e8 Y4 y3 `& ]0 E) D
' W5 E, [8 }1 [. r
ts.minute
& T( b# X% W7 \3 s- o7 C) dOut[11]: 10
) o' ^* I" V, n! S- T. k" Z& m, K7 b( R* ]
ts.second
; d( {( m) M. x8 nOut[12]: 30
1 \* h, p2 j p8 ?) ~+ u
" K7 `% \ p' c3 P1- v+ V) a% A! v4 ~
2% S" @3 v9 d5 i3 P
3
2 c3 q& i! @5 h+ k4* ]6 C# I5 p5 c+ d
5/ h$ [7 }) i$ `* `- v0 t9 C) j
6$ V, }4 u: \0 c1 Y" I+ N
7
: f, L- I4 \7 t$ X7 M9 u8
/ o. C& p8 V6 I! y, |/ \4 B99 q5 r7 Z- H5 a7 V7 m9 k
10
8 G7 ?# n) g8 p2 R11
F0 f, k9 [% V* C12
% w0 t: J# |/ w3 a13
+ g: B5 u3 g2 A. h( i149 ~& G( T- J O
15( A$ e# d7 @4 p- t3 K" |
16 u2 z# u5 T$ A* m
17+ s: N% V# G0 ]0 O* m: v! K" y
# 获取当前时间
% F: n* E% f+ _9 pnow=pd.Timestamp.now()1 W$ M: P( R1 \
15 P1 A/ k+ v9 z v5 W( @% h( @2 _
2. d4 q. O* X9 T w8 S
在pandas中,时间戳的最小精度为纳秒ns,由于使用了64位存储,可以表示的时间范围大约可以如下计算:
+ _/ {! g, i* a. @! dT 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)" I1 t* F5 @! M% n& \
TimeRange= 9 ~' \7 `/ U* a! @
10 & R8 B0 B. C/ R
9
+ X \6 Z0 T: x- H5 s, r% J ×60×60×24×365
3 j; q& ^1 _1 z) g2 ' ^( o0 K, H3 y, ]$ }
647 e) F. {. @1 i! [1 y
+ v' ~. z+ D0 D: r6 |8 P( }
/ X$ {0 K1 [1 s4 S ≈585(Years)2 f2 Y3 P' j$ T. A+ B
! n) f5 p" z( t& g, t/ g8 u
通过pd.Timestamp.max和pd.Timestamp.min可以获取时间戳表示的范围,可以看到确实表示的区间年数大小正如上述计算结果:* G( [4 z7 I! C$ G
3 T) H- s# ?& F/ t6 @3 r
pd.Timestamp.max/ c# i, h# W8 {. |
Out[13]: Timestamp('2262-04-11 23:47:16.854775807')
( R1 }5 y. f V8 D4 v
% Z6 d5 e' d" w3 jpd.Timestamp.min
2 ]: V, O% R% _4 r4 V4 lOut[14]: Timestamp('1677-09-21 00:12:43.145225')
- m1 Q4 u" [3 Q% l. K0 K
0 b+ _; P% v4 A7 ?, Y6 X: T) Gpd.Timestamp.max.year - pd.Timestamp.min.year2 O; k# L8 |# J+ `
Out[15]: 585
& z1 A1 m3 m! x) @, G4 |8 y/ E( }1: f! a0 ]% g) v
2
+ x" B! ~' Y% {% |. O$ k31 c0 V1 ~$ O+ q* A# W8 K- g
4: O0 h) m8 [& S' k. S' K7 T$ a' ?
5
3 v$ h& j9 q- b( f64 C7 F `' M% N; {) b4 I' H
7
* ^4 D6 X+ \4 J3 @' x; K% w8% {. M7 T+ N& w9 W9 Q
10.2.2 Datetime序列的生成7 Q/ b2 m7 ]; H5 Z9 b6 Q5 D/ ~6 D
pandas.to_datetime(arg, errors='raise', dayfirst=False, yearfirst=False, utc=None, format=None,4 l4 X# c6 r) J
exact=True, unit=None, infer_datetime_format=False, origin='unix', cache=True)4 w5 B% X- ]( t8 s; G! _& S( t
1" \9 P4 ]& W6 v
2
* X2 X4 a% p$ }* c9 ?3 J" v4 Qpandas.to_datetime将arg转换为日期时间。
@, B1 G' C( i% |& I0 H+ D: @6 @0 v7 _7 B
arg:可以是argint、float、str、datetime、list、tuple、一维数组、Series、DataFrame/dict-like等要转换为日期时间的对象。如果提供了 DataFrame,则该方法至少需要以下列:“年”、“月”、“日”。2 a/ ?4 V3 Q% }! A. ~0 p z% ~5 r
errors:7 y" {' b* x! g3 d' M; Z2 M2 n
- ‘raise’:默认值,无效解析将引发异常
, y, q; T; x: K" F( n- ‘raise’:无效解析将返回输入
+ `' ^0 Z1 q7 `7 ]9 w$ x- ‘coerce’:无效解析将被设置为NaT) G2 S! E$ G; ^! r2 @+ c
dayfirst:如果 arg 是 str 或类似列表时使用,表示日期解析顺序,默认为 False。如果为 True,则首先解析日期,例如“10/11/12”被解析为 2012-11-10。如果无法根据给定的 dayfirst 选项解析分隔日期字符串,会显示警告。; [1 R& y6 ~+ F: _$ |2 L; N
yearfirst:如果 arg 是 str 或类似列表时使用,表示日期解析顺序,默认为 False。如果为 True,则首先解析年份,例如“10/11/12”被解析为2010-11-12。无法正确解析时会显示警告。(如果 dayfirst 和 yearfirst 都为 True,则 yearfirst 优先(与 dateutil 相同)。)# J2 L! H. ~; A0 }7 `, I
utcbool:默认None,控制时区相关的解析、本地化和转换。请参阅:pandas 有关时区转换和本地化的一般文档
: D e5 B& F6 Q, L2 x% C5 bformat:str格式,默认None。时间戳的格式不满足转换时,可以强制使用format进行匹配。0 S" X8 G, C8 h* n
unitstr:默认“ns”。它是arg (D,s,ms,us,ns) 的表示单位,可以是整数或浮点数。这将基于原点。例如,使用 unit=‘ms’ 和 origin=‘unix’ (默认值),这将计算到 unix 开始的毫秒数。4 ?* ^- [1 w( l0 N H% W
to_datetime能够把一列时间戳格式的对象转换成为datetime64[ns]类型的时间序列:4 r, l9 W% _( S( \7 }6 s. A8 U
pd.to_datetime(['2020-1-1', '2020-1-3', '2020-1-6'])2 O2 e% E. i6 Y
$ d0 U' |, b7 H& l6 ^* J jDatetimeIndex(['2020-01-01', '2020-01-03', '2020-01-06'], dtype='datetime64[ns]', freq=None)/ @7 m* A) p- L8 B, j
1
) J8 L$ |1 o) Q6 R, o( l# R3 x* K2
8 }8 s: R6 s2 |3 e* S- q+ d37 u5 _* B/ U3 d
在极少数情况,时间戳的格式不满足转换时,可以强制使用format进行匹配:
4 x# h' T4 g, k% [# |8 s6 m
, _+ M/ d, b2 V( [9 M+ C! Z' Ztemp = pd.to_datetime(['2020\\1\\1','2020\\1\\3'],format='%Y\\%m\\%d')6 f9 B1 {% B& q( s3 }8 O& e" L
temp) U+ y6 M5 N3 X9 h
6 X G2 M* e' h# n. YDatetimeIndex(['2020-01-01', '2020-01-03'], dtype='datetime64[ns]', freq=None)
2 R# O; M" `2 D9 G14 J; X, Y$ g7 v& w; w
2& ]9 m: L; V: s
3
) e; T( k d' o( [) ~4" ?2 Z- t' [8 J7 x% J- f4 V
注意上面由于传入的是列表,而非pandas内部的Series,因此返回的是DatetimeIndex,如果想要转为datetime64[ns]的序列,需要显式用Series转化:
6 }4 p9 }9 K1 w. k" r
3 d! m. q3 g5 f# ?pd.Series(temp).head(), l; t$ L3 `: E1 `8 y+ _- H
2 S8 G7 [& b* w1 s# |0 2020-01-01
* Z7 v3 J7 V) L4 p5 A) X: d' `2 A1 2020-01-03# m; b& K& g$ P, G3 K
dtype: datetime64[ns]
3 O5 ~) {+ [# ^- @3 S1$ t- m* o: G/ ]; |, [0 o2 |
27 }& N5 P- F0 ~
3$ [! w C0 E6 L% L8 V" M. X2 I
40 W! h6 a) f/ X
5
' I3 Z+ V# g, p+ w) E) V下面的序列本身就是Series,所以不需要再转化。
. X2 ]+ r- m( Q
6 V1 p8 I8 _! J( P @- d1 Ldf = pd.read_csv('../data/learn_pandas.csv')7 f- | e7 L4 j& A) e+ g7 `0 b
s = pd.to_datetime(df.Test_Date)/ l- t# Y/ ]3 }
s.head()
B4 [! \* G& i+ ]8 s" H8 s- z* @/ C J/ K2 Y. X1 X6 g
0 2019-10-05 T6 n7 E3 @. f5 b2 j2 Y L
1 2019-09-04; Y0 F3 j# f; ], \
2 2019-09-124 F }: P7 m5 i4 L5 |' r
3 2020-01-03$ {2 y7 w9 m) _* s' h$ o6 K" h& H
4 2019-11-06
: [/ q: P( r9 O; ]. NName: Test_Date, dtype: datetime64[ns]
8 {* V0 S# P7 s* R1. n- w% x4 q$ ~ Q: V! D
2
& v8 ?4 M* l4 V1 F2 ~) G8 b3
2 F2 V' y( C1 b* Q& z4 A49 a/ b+ ?) q1 ~' r! ]& i
5
) O+ L6 C0 U0 u% Q3 {- o) G3 h8 `6. H- T5 W2 K$ ?- s4 I$ H
7
0 L" |% N3 @1 c0 d# ]; Z0 S4 D8
1 f; [5 l5 d5 H% C9, A: N- ~- S. c7 d# \0 X( T
10) C0 k: s+ J7 N& t' @
把表的多列时间属性拼接转为时间序列的to_datetime,此时的列名必须和以下给定的时间关键词列名一致:6 ]1 R1 ^0 P2 j, T7 Q- Q
df_date_cols = pd.DataFrame({'year': [2020, 2020],0 E# R f C& [! h. G
'month': [1, 1],
, [, S6 L0 `; {$ _1 B& P* T 'day': [1, 2],# w5 U9 s8 n% r2 f; y2 Q
'hour': [10, 20],
b! u, R; d# o% U) H2 s 'minute': [30, 50],
- p0 E0 a8 O- f, n) I8 a 'second': [20, 40]})( Z5 Q6 d2 O8 o$ D& x: f2 k% a
pd.to_datetime(df_date_cols)) _2 x7 V8 {5 ` p5 n
+ z$ g) p3 I7 ]( `
0 2020-01-01 10:30:20
2 ?3 r* D! F7 G3 z' a1 2020-01-02 20:50:40' s. q9 q' `6 K! m$ E0 u2 i) T" z
dtype: datetime64[ns]
% U* n: y; U6 X12 K1 S9 i/ n1 N) S: P
2% a. [+ Y9 H! [' v; t$ ]5 W
3( ~9 ~* g( j) y3 {" ~
4
& G7 ?5 G" Z U q5
/ s {& z) r, T% f0 K% u7 A6 U6
; ` y+ i# x5 A" u3 c8 t7& |$ q. u" S- u: N/ s( X
8
J; f( X6 {- r( V' ]0 d* y0 }92 I0 A$ k9 ~1 W* p
10
% s( ~7 u( |& r- G |* j11! _8 \" h) C% _
date_range是一种生成连续间隔时间的一种方法,其重要的参数为start, end, freq, periods,它们分别表示开始时间,结束时间,时间间隔,时间戳个数。其中,四个中的三个参数决定了,那么剩下的一个就随之确定了。这里要注意,开始或结束日期如果作为端点则它会被包含:
# {2 t& ~) S+ ?) Z) l& ~+ Rpd.date_range('2020-1-1','2020-1-21', freq='10D') # 包含
0 m5 D$ a2 t0 ~4 ~! g4 WOut[25]: DatetimeIndex(['2020-01-01', '2020-01-11', '2020-01-21'], dtype='datetime64[ns]', freq='10D')* Z2 t2 C0 {8 {, |& o+ @/ U
% y! k% q8 I, z1 s
pd.date_range('2020-1-1','2020-2-28', freq='10D')
5 \6 Y, S5 X- t* [0 a$ AOut[26]:
' i4 K; _8 }% W. r3 vDatetimeIndex(['2020-01-01', '2020-01-11', '2020-01-21', '2020-01-31',5 B$ U, k* ~# i) I4 C4 q! J
'2020-02-10', '2020-02-20'],4 Q- W9 m2 s: b: l5 t' C: \2 R; W8 B
dtype='datetime64[ns]', freq='10D')! t# U5 p& C9 O9 V. v* E! B7 m0 V
' Z5 A& \! B* e5 U, B# x$ f
pd.date_range('2020-1-1',
2 K+ \8 C& Q5 T7 D; ^ '2020-2-28', periods=6) # 由于结束日期无法取到,freq不为10天
1 _2 p1 {3 S ~! F# j
1 b1 ~ w0 Z7 ]# ] EOut[27]:
i1 p8 Q9 r2 o- F+ Z' FDatetimeIndex(['2020-01-01 00:00:00', '2020-01-12 14:24:00',, B; p" _. l( q! g9 t
'2020-01-24 04:48:00', '2020-02-04 19:12:00',, M' s" J) e+ P p0 f* D
'2020-02-16 09:36:00', '2020-02-28 00:00:00'],
4 `% X& {# Z$ }6 l, l- g0 E0 j- S dtype='datetime64[ns]', freq=None)& C, f" l2 {0 _8 v/ G1 N4 r/ K- f
, L2 y. u( |3 A: _; n1
+ n$ a3 g) g" G f) E2
. H* J1 f/ g7 p; S; D1 a35 @3 s$ Z/ i% W& x7 J) b! H8 w
4) F# {- s9 @' ?0 N
5; `! A u3 S& B0 J4 }5 I4 Y. X9 |0 T! o
6
1 g- U, I' y4 Z" L. |8 B) _79 l R+ t7 v( f v
8
_, Q% D: k0 k& _6 [9
" `. n, z5 Y4 H105 ~: Q2 A) {! ?. R5 O: P9 A
11" ^2 a+ h% S8 Y
12
& g2 F0 n$ j% W* J/ }139 m5 D r0 N: T4 S5 I! g6 G
14 P7 k7 [- b7 N# T y" e: S
15, P7 [* ]$ O8 d0 z7 a; f! M) U6 N
16
& ]8 W) m% A$ R) U9 q1 \% J17
) _* t- K2 X4 z% z r这里的freq参数与DateOffset对象紧密相关,将在第四节介绍其具体的用法。9 o5 F9 Z/ i3 D: D: F/ o
4 F `- T$ F6 B, y
【练一练】
1 w: s/ T3 H0 f |7 OTimestamp上定义了一个value属性,其返回的整数值代表了从1970年1月1日零点到给定时间戳相差的纳秒数,请利用这个属性构造一个随机生成给定日期区间内日期序列的函数。4 A3 u( R. }! t+ I7 C* b
& E$ n( Q. c* _. G" A8 T. [5 Qls=['2020-01-01','2020-02-20']: O# v! g* w+ F
def dates(ls,n):
) Z( @+ m5 E0 C8 l. w min=pd.Timestamp(ls[0]).value/10**9& C, _. _' ^: L I8 }0 r3 B" S+ I7 |
max=pd.Timestamp(ls[1]).value/10**9
# _) A- \) N, q7 j' B times=np.random.randint(min,max+1,n), u+ Z( J) D% S5 I" H4 m' e
return pd.to_datetime(times,unit='s')
, [) y5 ~4 I! n, J( f% [ p! Vdates(ls,10)
* ^) [/ [8 {' v+ j1 S! m( Q( m$ j# j5 `# S8 h z/ o) }$ D8 }
DatetimeIndex(['2020-02-16 09:25:30', '2020-01-29 07:00:04',
0 O5 ?3 g& k* | '2020-01-21 12:26:02', '2020-02-08 20:34:08',3 o. [5 n. @3 J- `- G% \
'2020-02-15 00:18:33', '2020-02-11 02:18:07',. r# ~* {: z( g t( u3 G; z
'2020-01-12 21:48:59', '2020-01-12 00:39:24',
! V+ o% e' ]! } s$ z '2020-02-14 20:55:20', '2020-01-26 15:44:13'],7 N, R+ Z9 K7 Z. u% B
dtype='datetime64[ns]', freq=None) B8 P$ I" A1 W7 V* _
1
k1 T( p, M" S4 ^2
' ]& ?" _" W0 g: U. o# B3
1 O# j2 j a% }* f3 K40 e+ ~% B# h1 |- }9 T
5
$ _1 T4 U, ^. ?, F5 W1 ~( t6
* m( T6 s- \/ P" q7$ S% e+ ?# S8 s2 Z8 M) u3 l
8
2 E/ n" m% y# f) L4 g$ P9 j0 r9
6 f/ {' i; \7 e. u x8 P; H10
2 ?# |9 _+ |% z6 m! `; s11" Z/ b& }) r* U8 D- S* l
12
5 s4 m# d5 V/ T6 ~! |4 E13% ^0 t9 w5 y' Q7 L. [; N2 f! M+ l
14 m- C( S6 p! F0 g' y0 z4 l2 @
asfreq:改变序列采样频率的方法,能够根据给定的freq对序列进行类似于reindex的操作:) T2 M: i) e8 L, Q- X7 v
s = pd.Series(np.random.rand(5),
/ v7 c7 x P/ P* u7 ~1 i index=pd.to_datetime([7 j; c' S8 ~) R5 h; T+ X
'2020-1-%d'%i for i in range(1,10,2)])) ]: E! N# s# t
, m5 e. |; j1 i( D$ b3 ~
( ^" X H* q8 S
s.head()
$ E8 z n+ ]$ o9 _) i9 R0 _+ QOut[29]: ) W! W0 l6 ^. a$ d
2020-01-01 0.836578
; b5 l: p) r# m% \( E2020-01-03 0.678419
' p, u3 m. D: E2 ~: a2020-01-05 0.711897% B3 y, W1 U2 g* b# v# N
2020-01-07 0.487429 J4 v) e" F& r J- B/ d+ N' b7 b
2020-01-09 0.604705
1 c. |- Q- l6 g r# Hdtype: float64$ A( R- E0 M; o: A) H4 V1 q4 |
2 Q# G* u8 l3 Q! [* W3 Q. V
s.asfreq('D').head()
) i7 Z7 Y' C. `7 MOut[30]:
+ N4 \5 X& {7 t. d2020-01-01 0.836578
$ {7 S9 d: s; u% l& z' @8 A8 Q2020-01-02 NaN
; [# O) e' A: k* O/ p( [& h2020-01-03 0.678419
% `7 ?% ], d, z) i: q3 F7 E2020-01-04 NaN3 l: d# \# _+ j6 V! H
2020-01-05 0.711897
) G; N* O" n, M1 a4 K2 @% zFreq: D, dtype: float64. N9 Z: e2 s9 R) r# F1 J( E
* I ?9 n- b8 y6 e1 `/ u8 Z( H
s.asfreq('12H').head()
}7 v: r( x/ c. [! ]Out[31]:
* V2 t7 A0 b! p2020-01-01 00:00:00 0.836578: L& ~5 w: \- M7 ~; B5 M
2020-01-01 12:00:00 NaN# [ i6 u1 B$ o! ~4 ~' U# L
2020-01-02 00:00:00 NaN
- T2 q5 T- c9 x% N2 o0 _2020-01-02 12:00:00 NaN) \) l) K% v2 v3 o. v1 T( v
2020-01-03 00:00:00 0.678419% j4 c; r% A. [ P* d! b) _1 P
Freq: 12H, dtype: float64; |/ c( ?- l% D# }9 n' L
& J- @. y3 U4 q0 x5 q
1; J( W& M: E+ \3 z( p: v% m/ x
2
8 R6 \2 x# v/ @) L6 K3
6 @0 M% r, z' T" b# p& E! u! V4 L4! n. ?0 v7 ]1 |' g
5
0 m4 a5 b/ ~6 [' [ l* D6
$ k$ ^5 @4 K W6 W7- p( p4 K3 z3 @
8; H$ N! G3 G( j- F
9. S2 B+ y2 j- b H; I" D
103 B7 U, B7 d' ?
11! L3 {7 r1 Y) F
12
8 k# a( s( \- f" O137 {- Y9 \1 A( c9 p& X2 W
14
- C4 q9 w, w2 Q4 G: F151 K Z$ y3 B6 @* x6 Z
16! S- L, L6 Z- w
17$ Z, S3 H" G: L% |
187 T( c/ Q# U0 [9 F: p
19
0 p/ L# M9 x0 u20. i/ {# Y3 q H) c9 A
21
# o8 N; e; ]8 y22
3 d, O" m3 s" U. g23
H( i" v; x3 a# Q248 X9 {% e* l- V, W' u
25
7 h; @# b; i0 h& x7 w26
5 [; S& \+ w1 G27
& i& I' l* H4 E8 X1 o5 q28
1 N- n9 O! v8 K# U* z- w29
9 B, U' N/ N: \4 V- Y7 E! S9 f1 D. a4 F30
) i! a1 p" z4 f) z; b310 D9 R, _; y, b; @# Q
【NOTE】datetime64[ns] 序列的极值与均值# S; X4 ^# r# w0 j' B
前面提到了datetime64[ns]本质上可以理解为一个整数,即从1970年1月1日零点到给定时间戳相差的纳秒数。所以对于一个datetime64[ns]序列,可以使用max, min, mean,来取得最大时间戳、最小时间戳和“平均”时间戳。
; y& r* J) y' u% ^7 [3 U+ g- o! K2 d. W/ f2 O3 ?' v
10.2.3 dt对象6 _; m3 v& H H+ u
如同category, string的序列上定义了cat, str来完成分类数据和文本数据的操作,在时序类型的序列上定义了dt对象来完成许多时间序列的相关操作。这里对于datetime64[ns]类型而言,可以大致分为三类操作:取出时间相关的属性、判断时间戳是否满足条件、取整操作。: ?/ |7 K: [$ G; o/ b
1 T5 F/ D4 v6 S
第一类操作的常用属性包括:date, time, year, month, day, hour, minute, second, microsecond, nanosecond, dayofweek, dayofyear, weekofyear, daysinmonth, quarter,其中daysinmonth, quarter分别表示该月一共有几天和季度。8 W0 u5 V) G' x" f: t
s = pd.Series(pd.date_range('2020-1-1','2020-1-3', freq='D'))( @$ H1 Y+ H) S3 H! Z
3 J- D/ q; w3 H, r9 Ds.dt.date2 r7 f* q- B8 Q2 |, B% T
Out[33]: 9 w" Z# Z+ n: \
0 2020-01-011 Z: V/ c. Q+ K6 V4 J+ s2 z
1 2020-01-02* E! U7 I# ?/ d8 ]8 u- ~
2 2020-01-031 h, W! m9 w& n& v( u
dtype: object
) [% A: Y' ]& m* q3 a( u2 ~" d6 L+ W+ G0 c
s.dt.time6 F- F% |1 p+ Y2 E0 _; w' h
Out[34]: " e# J# @1 V& A: I* i. U
0 00:00:00
% l- l2 J! r0 j$ c# X4 x `1 00:00:00
" t* M. u" P7 g1 d2 00:00:00
# u. D5 {3 G; v( Mdtype: object& \& h# ]6 @$ _9 {! Z
( q+ B6 z$ u* K5 Z8 Gs.dt.day
8 \! O$ p+ S4 f& O3 @Out[35]: ! p& q) C; w- w$ ?6 H: i2 K9 }
0 1
* S/ ~9 I% ?( v2 |0 X u1 2
* X K3 x5 e Z8 [) l$ Q2 3
' g3 |" z/ t$ [& I. adtype: int64
) D, g0 D g; G2 n9 Y9 E% x6 u J$ d8 h. g( U9 I2 {1 p( x
s.dt.daysinmonth& C, V$ y0 D$ Z/ T4 Y
Out[36]:
/ O3 q$ R1 S5 {$ K ^0 31' e- s6 `2 B8 J1 R
1 31( a; m9 i$ R1 s( r1 ` {& O
2 31
" K% K1 J, W! p m, N1 Wdtype: int64) x5 J* F$ C) {7 v+ `4 y8 h* u
' ~) R5 O9 u$ N8 O+ b0 e! T& }( B1; F& S+ `/ a) j: M% n6 O8 s- [
2
9 o% u+ P4 I# u0 I) x; A/ }2 i3
/ W. E) A1 B. f% g, p+ ~2 c4+ `6 Q7 ^+ X( \8 ?" T R( J
5
+ C; b4 {5 ^; c! i5 C0 ^6
, A3 Z; W' f+ U) G- w4 {7# N: m( b; E) W5 ~% U
85 T5 a' I3 j9 i6 `
9/ o! Y5 Y5 M" t/ A
10
- a2 n0 E1 \7 p8 {1 w. A118 ?# @8 y# ~; a" H. x; |3 N
12
$ {( }* N6 N" s& \8 E- u* l4 P13& i% s/ s: z9 f5 U( S/ Y4 h* d
14
( L' u" u9 |3 r! m15
4 @! \ J; S% x. Y5 L& V2 P16
' Y8 R" F* x* w) {, V' I17% L% {8 D$ \% i. k1 P7 `2 r2 x
18
9 M2 Y7 Q& s0 t* Q/ `' w19: r; m0 l* \0 R/ b
200 I! R, L1 [ c
21
9 ^1 f1 Q% H$ B22# C+ D7 Z0 @# C' ?/ ]
239 S# M' X+ D" u3 S9 i
24
$ w0 r6 d3 [' d25
5 ^2 C9 e+ c6 B$ T% N26
; x6 o& e$ e; X; m27) _) e2 x% X. T& E& E+ L3 \
288 M$ V2 k% t. ?+ _3 |
29
- o2 P. o' i% ]# h' |: u9 A 在这些属性中,经常使用的是dayofweek,它返回了周中的星期情况,周一为0、周二为1,以此类推。此外,还可以通过month_name, day_name返回英文的月名和星期名,注意它们是方法而不是属性:# @- z' \. X- ]- \
+ u' D e3 a4 }2 d8 P
s.dt.dayofweek" |/ T# E, N0 f6 v- d
Out[37]:
. o/ M+ O! ?4 I0 2
( }# t: Z8 k& o" n8 r4 q1 3% @" H. X+ e+ g! U
2 4
- N& U6 ~* ^- P, b8 [dtype: int641 V) N; r0 G6 i
9 R" m6 I' j# o' w u1 k t
s.dt.month_name()* j1 Y @) E$ L
Out[38]: 5 {/ w" ^) `* d8 G
0 January/ n$ _5 V8 ?: o% j
1 January% K6 z3 U$ S4 c2 u6 u& r7 I! S
2 January
+ c d6 d Y: Y- ^0 q: Edtype: object
5 E) h2 {7 a2 y8 q) Q
4 b6 |# _) B; U. Os.dt.day_name(): a! \! P# ~ S
Out[39]:
! K2 t o7 R _* Z {) X0 Wednesday) v/ y, x/ O' q: ]+ y
1 Thursday8 S( H4 Z- n3 U2 j, L' \
2 Friday; m' b- z, q# m+ _( J2 }
dtype: object
& X& L+ i0 b: J7 [: q- q
" z6 T3 w" A% Q/ b1: a) K# ?6 O& Z4 P0 _- M6 p
29 |2 F9 L3 ]1 j
3
6 }* n- h y" I" ]; E4
- ~5 ~) o" M/ r# y. C8 K- L5" Y) O( E, ?( }2 I! ] C
6/ I$ w$ j( A5 \
7
' [$ z9 ~7 F2 ~, U: ^5 Y* ~8
$ Y5 w4 [% v4 Z9
% ~$ u! l( a+ E2 v) [10; g, k8 W# F' D
11
: x8 T/ P" ~8 Z) |- R9 g; ?" T12
3 f% K5 `+ E# D/ c13
) P* S" Q, P3 h L0 R14/ B' n% x' j! X- [7 V* P" o
15
, A, `! b1 b) ^16
) u* A$ l# j4 ?178 J+ M0 B. I6 u8 ^3 p4 j; q+ C9 Z
18& a8 b. M p1 F4 R/ |" b
19+ p0 W* i6 _' l! ]
20
( @4 s( r8 |8 h. b" m- @第二类判断操作主要用于测试是否为月/季/年的第一天或者最后一天:# D5 O3 ~/ a# O
s.dt.is_year_start # 还可选 is_quarter/month_start( @4 n! n0 P/ O; }% ]' |
Out[40]:
/ s- s, p% O' U$ ^3 P* O# k0 True
8 F& O9 w9 x6 f& F& y4 A4 f6 a4 h1 False
* x% L1 g5 M- J6 u, K5 O2 False( M0 S' r; b4 S a/ D8 a! A
dtype: bool
) x! h" [, K y
4 t4 t- a- h" _3 rs.dt.is_year_end # 还可选 is_quarter/month_end# u1 m9 D2 h2 T1 D" Q, X
Out[41]:
5 u" O& s5 ^* u# Z- H0 False
. B/ I* v! Y- f8 h1 False
) g, A8 X( T/ z% z J& P2 False
" Q3 i+ A N3 `' X5 T$ Y9 {dtype: bool( |1 q6 Y- ^$ [3 q* x
1; Q! q' {6 b( k5 l0 _9 z
2
: m1 m4 _* c- ^8 x' Y3/ j4 I- ^" T: D& q
4
, R: A: S7 ]( v# F R: T3 y5$ G1 i# F4 B1 z3 F- L
6
, F- I" W" Y: V e7
) T% k6 ^* g8 Q5 l4 Z" e8 b8
8 G: v) U; r, P$ y$ I- i+ K) i; ?, {9
6 R2 x# O1 z9 E5 O5 b& m- U10
8 G+ M* V- W! ~5 e11
' F R. U+ ]9 o( @: E121 @, H% \' S0 V5 ^3 \
132 f& X0 Y1 s, J: m
第三类的取整操作包含round, ceil, floor,它们的公共参数为freq,常用的包括H, min, S(小时、分钟、秒),所有可选的freq可参考此处。+ g! r( x0 r) A8 z! c
s = pd.Series(pd.date_range('2020-1-1 20:35:00',
' `, M: K7 L8 K6 D3 T '2020-1-1 22:35:00',
5 {8 C$ U3 V1 ?. i freq='45min'))1 D! T2 R! d9 G7 f
l1 I) A& ]/ E! n! r0 e
2 J7 d# P; T0 [# s+ u/ R4 l
s
! T+ p3 y" g* o% i5 Y5 a* p1 ^Out[43]:
$ V6 o0 ] {" J! `* ]0 2020-01-01 20:35:00
B. j+ U0 F* u7 E$ e+ U1 2020-01-01 21:20:00
3 Z0 C) T: Q$ C2 ~: y- l/ a* R2 2020-01-01 22:05:00
' p& y% Z* B! F) O+ sdtype: datetime64[ns]
7 h- \6 i5 p6 R; X' |+ n5 z$ L0 S3 t% e/ ^& {. ^
s.dt.round('1H'), ^9 \8 ^! g6 J/ q2 O; s8 @
Out[44]: : b& O% t* \1 R6 t6 ?
0 2020-01-01 21:00:004 t8 T. j) T6 X* N g
1 2020-01-01 21:00:00: Q3 J/ a5 M9 c
2 2020-01-01 22:00:00
7 S- G( h' V' ]% t2 _ I% Sdtype: datetime64[ns]
6 E" {. W( X9 ]9 z1 J- E/ ]" D P* T5 `
s.dt.ceil('1H')# Y( T8 G/ J" H1 a
Out[45]:
# e# V! ~ x+ l' o0 2020-01-01 21:00:00
# Z* p( f, K6 m7 B |( K2 z' v1 2020-01-01 22:00:001 G K+ ~- o% K) d) ]
2 2020-01-01 23:00:003 ?4 b: O7 h' V
dtype: datetime64[ns]6 M7 a; `3 h* L
( E3 u5 k9 h( qs.dt.floor('1H')
f2 x/ X' s+ hOut[46]:
# a. }$ F, ^9 R6 C5 k" l/ H8 K- l0 2020-01-01 20:00:00( s6 ?% o, f$ @, a
1 2020-01-01 21:00:00; L% |2 [! y" T1 k
2 2020-01-01 22:00:00( K2 P" V( ?& ?# b
dtype: datetime64[ns]
t; ?. J5 S8 d/ S
t( }3 A8 V& w$ k& p& B1$ |3 h$ _+ e- _/ B: R8 j# |
28 R8 B, P* B' Y7 c# i
3
+ B( o* a% ?" d) D4
! m$ O, U; b* A; Q7 M50 s" ~# z4 t( |7 L( R% B
61 c- V4 r' b; i/ h( k
7
- S0 O8 o6 A' O5 y86 m K4 k% I. X9 Q, W
9
/ d) o; _& |* P) g107 I' Q; f8 P @& ^7 w( J
110 p7 O4 B8 l! M: W9 R9 S# w# F( G
12# T0 _+ w$ d8 N) W/ t
13, r, }5 `$ V4 k) p- X0 w# o; I" g
140 j: V: z3 |/ d5 b/ b
15- e$ G ~# o( n, D9 v+ e
16* k! _! ]- O1 y0 v
17: l& f; l- Y: r' c' J& i. i
18' [6 [5 T! w! K; j" ^, ~ Y4 e' v
19
* m _9 y0 H3 `8 Z% Y20
2 B" I$ @6 W$ U% i8 [( I' v% ?1 S) z+ d21
. \9 j8 {8 v9 _# \224 h7 r- Z( G' }: Q( ]
23
! `. C; R: s: j1 g8 N! p24
" a/ Q, N9 l1 z, u: K25
$ d) J1 X3 ?6 T1 H; v/ j" z26. N& V( m/ ]9 b& R: P, N; N
271 b9 M: H8 F- I" h9 o
28 Q( t2 b9 B( P
29* z; @9 u- N/ i `9 T" r1 {4 D& a3 C
30" O: `( @* Y7 L% @ ~
31
+ [: M7 C- Y& r2 `5 u32
! w8 v3 ~* G8 [9 ]10.2.4 时间戳的切片与索引
7 B5 }- b& _8 @0 u; X! t. q2 I2 j 一般而言,时间戳序列作为索引使用。如果想要选出某个子时间戳序列,有两种方法:
6 r0 {7 \, I- M" p6 U) x8 Z
+ b/ G) {1 S4 w C( ?. ]6 n: \5 e利用dt对象和布尔条件联合使用0 h9 l, e6 E5 B% w; }8 l; _
利用切片,后者常用于连续时间戳。% o' g4 n( r' q7 z A+ K
s = pd.Series(np.random.randint(2,size=366), index=pd.date_range('2020-01-01','2020-12-31'))' g) @5 N% q- o: A1 g* ]+ P$ n
idx = pd.Series(s.index).dt* m7 t8 u3 r: x
s.head()- J; U- S o, Z" E( e& t
1 i: ~) I% Y; p6 e" {0 ~0 S l- g2020-01-01 0
4 G3 E; Q* r( K/ u. ^2020-01-02 1+ F4 }! S& v8 S- E q, o
2020-01-03 1& O3 E% D% o( R* C' w" H
2020-01-04 0
' O% e0 p* C: R l2020-01-05 04 G' M! l; ~/ n3 W) ]0 B+ [+ d) d
Freq: D, dtype: int32! C. z& b# A5 h% X, v6 [
1
4 p8 B7 I: g$ K2
& k9 m' n: O- q; o) V3
6 d. _* n& M3 ?; h* h+ Q4
0 J2 p$ l/ _: C e9 R5
3 p. [) y9 M9 e6
( n6 _3 I" [' |- ~& f* q4 I5 G7
6 D( F% O8 T# [1 r) k( d: ^3 C# X8; J: B/ [% `3 U2 {: W9 N1 w
91 m r: ]7 X0 e) K" w
106 r6 X- }" Z2 [ }
Example1:每月的第一天或者最后一天
8 Q& e$ t- o# m" o" {
- b0 s* ]8 Q, ~9 r+ n% ns[(idx.is_month_start|idx.is_month_end).values].head() # 必须要写.values# |# N. H9 N) E/ m2 O
Out[50]:
" R7 A% H( V! u8 I0 Z/ n2020-01-01 1
$ {5 {, {. N! Q2020-01-31 07 S) R+ s( I" n" j( G4 `- l$ c
2020-02-01 15 a p# S8 I# s! d* B5 E
2020-02-29 1 S: S4 a- U6 H. D$ z5 T1 ?
2020-03-01 0
& G; T+ s) D0 }dtype: int321 V6 x1 t. o6 J7 y6 ]) l
1# o. S) d; Q% q- Z
2
6 j: v2 J* H5 T3 l3
4 K0 B/ }: @6 P4
- ?& m4 z4 L1 M4 E, p! D5* K4 e6 q( I1 l" u5 P& D( u
6
5 i) ?' u' L8 q2 `% H6 _, g7
* K& u( w9 F7 d* {5 J( D6 k8+ S" ^. Q+ Y$ |* V4 S$ T$ V
Example2:双休日
! q- n! @; a7 l2 R/ r/ n" M: ^% M& e$ J( U0 e7 G
s[idx.dayofweek.isin([5,6]).values].head()0 ?& d2 F; ~' c" {
Out[51]: 1 q' j% m; V* r5 I6 ]
2020-01-04 1
% o. ^; d5 G4 g3 ]5 F; h2 j$ Q- }2020-01-05 0
- l9 F( t# t0 x7 b" r2020-01-11 0
% H8 ~# B4 j' l1 l4 M2020-01-12 1
% T0 {( ^9 b$ Y, s- m q2020-01-18 10 y: t4 q' L4 K* x3 ]: M% ]9 R
dtype: int32! o$ ~# d! X8 n% `
17 |3 Z0 o0 T1 ]
2; |7 d" I" Z. S5 ?; I0 Q1 }6 j
3" m9 h4 x! a% A( Y! e( J' g1 l
4) a- R- I4 \* f0 w% @6 k- v: M
5
6 F( g# {) y! r/ B P2 f; q6
$ z6 n& E7 _! Q5 f1 ^* z& z71 u- F# l) E" I
8: ^- c6 D4 B: F& T
Example3:取出单日值
' t# J- \7 G5 @, A# t) ]2 Q3 H6 A- u, V
s['2020-01-01']
1 U/ u' J# }5 q4 K- ~, ZOut[52]: 1
) c7 O' G5 u9 g- F6 O6 C& s: c ^- y$ i
s['20200101'] # 自动转换标准格式1 `& C, u5 l0 {- f. ^
Out[53]: 1
3 A6 ~- `* m+ Y) O1 m$ I( A: n+ F10 \# B0 w! s! }- e* d5 K4 O
2
4 w3 }* o% R3 k% u: D6 [9 r/ }3+ k4 [2 u2 O5 N* ~
4
% N+ ~6 \# `9 s5
- K5 C6 a3 q; Y# k3 Z8 i7 NExample4:取出七月) j( P. Q7 W7 _- R$ v! d$ p! D, g; ?
! Q4 o& L: v5 v D) e
s['2020-07'].head()1 F$ }6 c( m0 Z9 b# A7 B7 I- H
Out[54]: 7 D8 f/ H5 D- M% s" v" }& \) I
2020-07-01 01 k6 }5 b5 B$ z4 v7 c4 @; A) X+ J- l G" v
2020-07-02 1: B* m7 l! j1 t/ z
2020-07-03 0& U z' e' z# w/ P2 k- F
2020-07-04 0
7 ]2 |+ V; j) J# e2020-07-05 0
3 l: U7 D; o; H) O' vFreq: D, dtype: int32
- m6 Q! `* f6 p. r1: P! Z: O* H- y5 l3 s
2
2 k4 J. U$ m+ n. F: K F: j; K* d3
! R. g& h; ?9 V( P6 }$ h" h47 \8 e# W5 X: Y& M; W
51 p6 K5 M5 `' Y9 ]% s, A- ?
6
5 X |+ _# [$ m$ x0 S. M: l- i7* ?# D7 V; X1 T$ K/ @
8' D$ T8 x5 [9 e0 d; Z7 g
Example5:取出5月初至7月15日. i; g! m; ?9 @; ^( H% U! g
8 y! \4 Z1 N8 {6 ~3 h es['2020-05':'2020-7-15'].head()
1 P% W" _' w/ L6 |( L$ s- b2 VOut[55]: : C c; x2 Z1 @, a3 P0 Z9 g, y
2020-05-01 0
* T. k. p2 O& ^2020-05-02 1
+ ~# u, x7 P/ v& Q( m2020-05-03 0( }& E9 ?. E6 _$ q2 `. x
2020-05-04 1
; o! a9 @" c, e& q5 @3 ?7 Z* x2020-05-05 10 i* e$ S) B- D* g: X' f
Freq: D, dtype: int32
& G1 F% R" d6 X2 N" P7 B7 ]
6 k1 k1 g1 o# Y! R l0 ts['2020-05':'2020-7-15'].tail()
+ o) t; z8 j; a: a9 e. L4 ROut[56]: % C1 B9 s6 U9 j2 Y( X3 s% \1 k4 X
2020-07-11 0
4 l6 E5 l+ E& K0 z& r2020-07-12 0
9 Q4 d2 `$ R4 l/ z% f7 X; s2020-07-13 1
$ G- t2 S1 }& I# C/ Y2020-07-14 0, S9 C$ h6 z- K- c: J7 t
2020-07-15 1
5 ?* R( E- |9 ^) V# a* u1 FFreq: D, dtype: int32" H, F3 B7 Y. p
! W# E( R, Y* G T+ E
1
9 x7 m! S2 O& S29 Y, z4 U/ o% e* V2 ~3 B7 R
3) Z6 K4 F6 s# Q5 y
4
. m6 s+ i4 w4 \: C1 O `5/ d5 ?. A3 v+ i* P5 j8 g
67 d1 m) A1 L; h" L9 P- w
78 Q5 ?: j i- t0 u0 R
8
5 G8 d0 U- g! ]9 u5 D5 ]9
, {# Z/ Q; x6 |0 \8 j! Q) p2 V10% y2 D+ L) z8 N: l
11% j) z: x0 ?( Q1 z% t" H. |: H
12
' s5 u' L8 n- M$ A N2 f13
1 s% w) y" G8 \9 H14
) I/ E' m) g, L( {/ V155 o. q; v7 F# i, a$ ]# w2 C( [
160 Q$ }" O1 b1 c% ^( N
17) W: L/ D* _) a% f
10.3 时间差( l& I( U4 `& [
10.3.1 Timedelta的生成
+ Z- A5 _; S" H: Kpandas.Timedelta(value=<object object>, unit=None, **kwargs)
+ v2 Q/ w! c% ?4 H unit:字符串格式,默认 ‘ns’。如果输入是整数,则表示输入的单位。
7 ?$ Z( l# _. |1 \) r 可能的值有:
, @/ [9 q3 L4 y# U
( `) U+ [* F+ k. p- g‘W’, ‘D’, ‘T’, ‘S’, ‘L’, ‘U’, or ‘N’0 I+ q$ _: q2 u0 @- E2 G$ s
‘days’ or ‘day’5 r; v+ P" F: A! d
‘hours’, ‘hour’, ‘hr’, or ‘h’& d6 s! T1 B C# w* c
‘minutes’, ‘minute’, ‘min’, or ‘m’
( J% |9 d6 B2 R' ]‘seconds’, ‘second’, or ‘sec’
( y8 H4 U: D/ t) Z5 l k毫秒‘milliseconds’, ‘millisecond’, ‘millis’, or ‘milli’
5 ]+ b# O, C5 m; @3 K' R微秒‘microseconds’, ‘microsecond’, ‘micros’, or ‘micro’8 l) ~) q) p7 [: I" f
纳秒 ‘nanoseconds’, ‘nanosecond’, ‘nanos’, ‘nano’, or ‘ns’.
$ k2 [; r W. D3 Q1 S时间差可以理解为两个时间戳的差,可以通过pd.Timedelta来构造:
# J0 x$ S3 L! J9 v9 G5 ppd.Timestamp('20200102 08:00:00')-pd.Timestamp('20200101 07:35:00')
" D( ^; T- d2 | a! u: eOut[57]: Timedelta('1 days 00:25:00')2 }* m$ ]: a( v7 l
$ ?0 F, O# b: Z/ ]8 J
pd.Timedelta(days=1, minutes=25) # 需要注意加s% ^) m! j# q( }2 F( G& o- W: s
Out[58]: Timedelta('1 days 00:25:00')" d7 j3 ^! z3 B& s3 Y E
( m" O4 G N, a" d8 p3 ]
pd.Timedelta('1 days 25 minutes') # 字符串生成7 P- `. S6 c9 i4 \" s9 ]
Out[59]: Timedelta('1 days 00:25:00')" s q4 O& A1 ]# P" g" _9 J
, I; @. d% N, X) O' y9 C- A
pd.Timedelta(1, "d")2 r4 ^, Y( |. A" A6 i
Out[58]: Timedelta('1 days 00:00:00')
; e7 j \1 n+ R9 q7 j' J6 `# m1
l" m3 c, Y8 P+ K3 D2. T6 a* x! V, L( h
31 M4 {7 Q. ]; C! b. s$ A# H
4
7 B/ l Y- G5 e5 Y7 H3 Q5 q a5( U3 I6 c; I1 }' S/ K+ N4 V* r
6, Z( V3 s! }! L
7
8 n0 ~; M/ k' o: j4 b* a/ @80 {$ A7 ~9 q/ X; _
9, [" p( ~5 X" w4 I) \' k# X. g
10 J; E# r `% }9 w' \ a8 C
11
* W* n u: \2 c3 Q7 L3 A生成时间差序列的主要方式是 pd.to_timedelta ,其类型为 timedelta64[ns] :
9 r: [5 H) Z) z5 p( E/ j* _" E6 vs = pd.to_timedelta(df.Time_Record)
+ f# x5 |3 @2 d0 j \! {' Y3 @
% e1 {3 D) \% u) Xs.head()
0 N* _4 Y( s* V$ S( z% eOut[61]: 8 C) r f9 f. @ d5 T$ h- V+ k8 P
0 0 days 00:04:34; p, v. W/ Z0 ?3 j
1 0 days 00:04:20
+ S) K6 b1 C! P& `: o0 O/ S0 V2 0 days 00:05:22
$ z4 K6 G1 [: ~/ Y% `3 0 days 00:04:08 k- C' M, \- @" R
4 0 days 00:05:22
7 Z6 s9 Y, V3 qName: Time_Record, dtype: timedelta64[ns]- t: q- _5 Z% o S/ S, W
1
0 M+ V9 H }4 |. p2
! n8 B' o( W1 E7 f3
6 L% H" `5 e4 ~$ l, ]4 Z4- l) O$ ]6 M7 j2 N% T% w
5
* h% I( u) o* m) B6: w% c! n, E2 f9 f
7
? p; _& z2 ^2 q8 g" L8
; K$ U) R4 v; d( x9; @: g1 ~% t2 z% B
10
j! k- x+ l$ J+ z% ~4 z与date_range一样,时间差序列也可以用timedelta_range来生成,它们两者具有一致的参数:
+ S2 Z6 z! A) M4 r; N9 }pd.timedelta_range('0s', '1000s', freq='6min')" ]0 X9 Y% {7 q7 _; o" i8 u
Out[62]: TimedeltaIndex(['0 days 00:00:00', '0 days 00:06:00', '0 days 00:12:00'], dtype='timedelta64[ns]', freq='6T')4 m, Z( [ R5 m' X$ o% o
% s$ a' f" C% k) Y
pd.timedelta_range('0s', '1000s', periods=3)
& \/ Z: M# D4 k0 U) I, rOut[63]: TimedeltaIndex(['0 days 00:00:00', '0 days 00:08:20', '0 days 00:16:40'], dtype='timedelta64[ns]', freq=None)
0 V1 i/ U/ j; w# q( p3 ~/ B1 O1( Q/ N! |1 Y: h) r
2
. a( w7 J% m' F F; w; E3, Q" p$ M/ ?7 v+ k# ]& T
4+ s$ R J* E5 m7 R8 b* W
5
$ U9 d+ L& b9 x6 C# e对于Timedelta序列,同样也定义了dt对象,上面主要定义了的属性包括days, seconds, mircroseconds(毫秒), nanoseconds(纳秒),它们分别返回了对应的时间差特征。需要注意的是,这里的seconds不是指单纯的秒,而是对天数取余后剩余的秒数:
2 u- a2 \$ b8 M8 d! H; f1 P! {s.dt.seconds.head()
* M: `$ P+ v8 @' g0 f6 M+ nOut[64]:
* d+ p9 c2 C% P# `0 274
0 X! X/ t! K& d% @5 R& Q1 2603 h% U% H. c: F; ?6 h G
2 322
' D; g- n# U5 l% L. o- v6 V3 248: E4 ?0 T: q0 a" s6 }" d
4 3222 L5 a6 z4 e3 v3 {* a6 y
Name: Time_Record, dtype: int643 ], ]+ T& c' S& J2 _! Y% b
1
% }5 n/ }! ]+ z8 m d( D9 C2 x1 ?' R/ d' K
3
; o$ ^% J9 S z4 _6 L4
2 q+ v3 s0 E4 S; w6 b5
, V9 S- V/ T$ l" Q( x3 ?* C% Z68 |; i! [. U6 f) G- D
7
+ ?0 W7 y/ d& V! K8
: J5 y9 l# y+ y8 c$ ^$ I如果不想对天数取余而直接对应秒数,可以使用total_seconds
/ D `0 A( x5 b* y- M: b3 D" k
s.dt.total_seconds().head()
- l/ X) m3 ?7 ?; A9 QOut[65]:
% n/ T0 `. Y0 A2 U" U/ C9 c$ j0 274.0
0 h& @7 i$ h7 Q+ t/ H, x1 260.0
4 H& s2 u" H e8 M; u) W2 I$ z2 322.0/ Q3 |$ z( |& G$ F
3 248.0
x8 ^9 G e- t8 f9 L4 322.0, t& g/ B% i; b) _/ g- ^: ^
Name: Time_Record, dtype: float64) I5 o4 @1 ~: f( x# i/ o' k
13 C. ^1 c( P8 C3 G
2
2 R& A X3 |" e [7 X! E3
, ?' r' [0 ]2 c% E: M' W48 f4 w) r) B: O* O) _+ ~6 v x
5) m1 o3 k* x* X. y
6
" X$ _8 L! w. _( z. }: J2 L7
3 |9 P& U4 {6 O2 J, |2 Y" G5 o$ |84 Q! ~, _) X" `# U9 x4 _; C
与时间戳序列类似,取整函数也是可以在dt对象上使用的:
* \: l, m* w( w& W/ v2 u# `0 N: J( x+ A* E
pd.to_timedelta(df.Time_Record).dt.round('min').head()/ p" E! o9 K, A1 ~- i
Out[66]:
, e) L) W! w& b) R* n% _0 0 days 00:05:00: B$ r( {3 q. `3 `: r
1 0 days 00:04:00
; ^" \- @- d1 ^7 z& c. J1 E2 0 days 00:05:00$ S: _/ n7 a" z( X5 Z: b8 ?
3 0 days 00:04:00% f4 d7 }9 P1 o( ]! @
4 0 days 00:05:00
W+ U% {, u/ c3 V& aName: Time_Record, dtype: timedelta64[ns]7 J/ a" q. H- E+ Q
1% o" y' @6 |& r: r, b! E8 _
2
" K2 t. { W; o+ Q3
% \& e( b6 H/ R46 X6 v# @! S8 H
5
3 x8 [& Z( n: K- W) j3 x6
4 y+ a" @* F1 {3 w A70 d: I0 q6 z8 h1 y$ D6 E4 _
8
/ p/ _" U8 w3 G* U10.2.2 Timedelta的运算
3 y) h2 _- b6 X I! p# I, U单个时间差的常用运算,有三类:与标量的乘法运算、与时间戳的加减法运算、与时间差的加减法与除法运算:
+ C9 d+ y( K+ r4 t0 Ztd1 = pd.Timedelta(days=1)
+ z6 G4 M7 O- u4 ^7 ]5 P& X5 gtd2 = pd.Timedelta(days=3)
1 |* d% d" x$ K3 t" S* R Gts = pd.Timestamp('20200101')% Q! d9 U# }! A# Z3 e3 P* ^0 y
7 @* Y0 D" z& Q9 N1 C% B6 [td1 * 2* c _4 l' Y# I$ B5 A3 \1 D8 G. ^- Y
Out[70]: Timedelta('2 days 00:00:00'), r! f, F% d) ^& M2 N2 m
6 S3 { w* b( @% p! F; ~& c' Gtd2 - td1
: ^0 {% F B3 FOut[71]: Timedelta('2 days 00:00:00')! {% @/ q( g8 [; W* m
* Y/ A. _5 @8 B$ Uts + td1
3 f6 @. j: ~+ nOut[72]: Timestamp('2020-01-02 00:00:00')
I3 ~1 f5 e- g7 K
6 c4 |8 J9 d2 a4 m+ Zts - td1' r$ Y& ^! b) y: @! n! m* h; D& m
Out[73]: Timestamp('2019-12-31 00:00:00')" q6 \4 v8 K- |5 p' X" W
18 B% d: G+ k" I
2
. R( T9 G# ]% R+ _7 |1 J! s1 B3' R: \0 g; e: \. T$ U
40 N0 a6 I% O! j8 Z: z
5
$ n' G# E0 |& K( k: Q4 N7 D3 S' J6+ T% E7 [! |) s& X* t3 ? `
74 q" t. h E5 J4 F5 b5 }
8
# \7 V5 H8 ]) o C Z a3 i6 u. p) O98 o6 O8 h- b5 l5 t( a+ ]
10
. @( c/ R s4 R( x+ F11
$ Z6 K3 X4 Q& a- J) w12
2 U+ R) X. D% q8 x" |13
9 A( V3 f9 l5 H; T7 Z14/ t8 f- k. V { r- F4 T
15
2 ~6 c* V5 z0 [# s: w时间差的序列的运算,和上面方法相同:
9 x& U# ~- Y. R8 ?: C btd1 = pd.timedelta_range(start='1 days', periods=5)5 x+ t% {( a7 m8 O
td2 = pd.timedelta_range(start='12 hours',
, w( w' Z) N" Q7 {2 N# D freq='2H', a" b9 F; K! D* L, r
periods=5)
. p) p. N( e; Y5 L' L9 A+ W% Q2 \, n, Kts = pd.date_range('20200101', '20200105')- f4 A( T5 C5 \; k) A0 t
td1,td2,ts( [5 `; S& v* S |' b( O( V. [( C
* V9 d, k P& q: |# J# G9 H, j- G8 a
TimedeltaIndex(['1 days', '2 days', '3 days', '4 days', '5 days'], dtype='timedelta64[ns]', freq='D')
0 `! n! y/ m# S$ ]! P1 S$ {3 L6 tTimedeltaIndex(['0 days 12:00:00', '0 days 14:00:00', '0 days 16:00:00',
% [' s1 f7 f9 ?8 n# b '0 days 18:00:00', '0 days 20:00:00'], dtype='timedelta64[ns]', freq='2H')
0 \$ _% z D& U, N$ K: Z$ SDatetimeIndex(['2020-01-01', '2020-01-02', '2020-01-03', '2020-01-04',% b, I/ E( L/ ]$ \* e
'2020-01-05'],
; z! O- H3 w ~3 ^ dtype='datetime64[ns]', freq='D')& p( A7 k! O& I
1
* X; e# n3 u: a2
2 `" P% a/ o4 I: r3 {5 O1 K3
# p- U& U5 y& ^) ?' G+ N4 V) t! m; ~; S2 F5 h( N, {
5: Q W& n9 h7 m- U' c
6* b4 L" ?& r2 c3 _* f. l8 Y
70 v8 k* i G/ D' C0 L
8
. \6 ~. i3 N+ l0 e91 ~1 k0 ]$ m/ v, ^
10
; Y. K i! T" `4 o: J9 C' [11
4 O! E& `, g5 A12
1 A! N1 g5 {4 H" F7 Y9 t; }) D13
! j; t+ R- F1 a: ktd1 * 5
j9 j8 L1 B$ X* I0 `Out[77]: TimedeltaIndex(['5 days', '10 days', '15 days', '20 days', '25 days'], dtype='timedelta64[ns]', freq='5D')
. p) Z2 i- n& ^5 W9 r2 j
& F& k- [5 c! y$ O' H- Utd1 * pd.Series(list(range(5))) # 逐个相乘$ g, p4 B' E# f* R
Out[78]: - y- z9 o \1 Z
0 0 days
: J+ S0 o6 \& u* W: Q) g2 l4 n9 X1 2 days
7 ~8 l4 `1 F/ L x- i# S$ a2 D2 6 days
' w6 V5 M @$ R5 t9 U8 S* }$ U! x, O3 12 days6 P$ C, T; u7 K2 S, G" z* y
4 20 days
8 c3 T- C& G. c. T0 Tdtype: timedelta64[ns]
6 r: o. `: T: ?7 _% N" _7 S# v9 K$ x! B7 v
td1 - td2
) h- k. F8 D, m w7 Q1 {Out[79]:
( g" B; l; @1 X2 y8 ]6 ATimedeltaIndex(['0 days 12:00:00', '1 days 10:00:00', '2 days 08:00:00',3 g, E) d! n3 \! x
'3 days 06:00:00', '4 days 04:00:00'],5 R h0 \7 d" l0 L! Q/ v/ I
dtype='timedelta64[ns]', freq=None)
1 f0 o. y9 ~5 q6 M$ l U, s5 i* ~3 N( Z/ _
td1 + pd.Timestamp('20200101')3 ~+ ^3 C7 ~2 U2 E+ g
Out[80]: : b0 _1 k7 U, x/ M# z
DatetimeIndex(['2020-01-02', '2020-01-03', '2020-01-04', '2020-01-05',% C. f+ }9 K$ N/ `
'2020-01-06'],dtype='datetime64[ns]', freq='D')$ I6 ]2 M5 s# T2 o# o) a2 t+ s
1 e; l7 z# x; P. d
td1 + ts # 逐个相加: g& J! Q0 w1 k2 J
Out[81]: $ u8 C" w G0 K
DatetimeIndex(['2020-01-02', '2020-01-04', '2020-01-06', '2020-01-08'," b- R+ O7 R7 i# g" q, i' Y
'2020-01-10']," K. z" [ ^. v6 c. E; B, U$ e
dtype='datetime64[ns]', freq=None)" J2 L6 m8 A- p" r. f6 y
" C4 G# H3 v, T0 X4 y: I1
- _8 F8 {1 S# r" Z$ L; P) p22 |3 @; ~1 R- i% ?. g
3; h, k: o$ d1 S9 Q! Z. ^% e
49 G( ~2 \$ C3 A- N3 R) o$ j% p
5
3 h+ d r9 u c! \3 j6
2 h# g& `. w- k q7
* K4 {& N9 B$ K- @! L H: b8' ^2 i0 Z7 l" _7 u
9, w* b) N" _+ v/ A3 R: ]
10
1 K1 l5 e" b. ?118 \, d" n0 T% d1 z" k
12
, p) _2 Z, d* t' u. |/ }8 N13
+ A3 r w Q/ X L& @14: ~2 Q8 p- z- A) |7 n9 S* M
15
( n8 ^% v4 q( R1 b" y; m1 C16
( [* M* {- j% M8 f* ^; T5 Q17' g2 c g: X( m+ E/ x4 Z+ B" ^4 M+ U
18
" b; s; ?) n. C3 [, J19
6 _( Z1 N! o& D. W8 c. z20
1 c! h% y; ^- u21
8 K6 G( J: P2 S22+ g# N. w5 s* D3 J1 m- p
23
* Q, |, j* v; [4 Q24
6 q }3 Q+ M7 I9 h3 ]259 ~9 M; B9 M& P4 t
268 o) Z* w! e! J1 O1 x4 z
27" {1 H1 C' O5 V+ V6 c5 C4 E( e
28; u# E) ]! d) k8 T. T
10.4 日期偏置3 ]! |3 t1 [" Y% r) o
10.4.1 Offset对象
" s4 |& l+ a+ l: ], C( ^! Q3 ? 日期偏置是一种和日历相关的特殊时间差,例如回到第一节中的两个问题:如何求2020年9月第一个周一的日期,以及如何求2020年9月7日后的第30个工作日是哪一天。
' Q. \- h! q3 Q7 I# f6 \; T
5 y& W' X( \7 j9 XDateOffset 类有10个属性,假设s=pd.offsets.WeekOfMonth(week=0,weekday=0),则:. w t5 t! O1 w/ ^ f
8 T& {1 w) {" x- |. c$ [. t/ v9 A
s.base:<WeekOfMonth: week=0, weekday=0>,返回 n=1 且所有其他属性一样的副本! R5 _6 Z) k/ B+ m
s.kwds:{‘week’: 0, ‘weekday’: 0}1 y% A5 q! b8 O7 W$ j: Q2 R
s.wek/s.weekday:顾名思义
& c9 L% h8 }2 q有14个方法,包括:
2 D) ?2 K. W0 ^3 i$ |
3 `. w' E! C+ G8 n4 g# TDateOffset.is_month_start、DateOffset.is_month_end、DateOffset.is_quarter_start、DateOffset.is_quarter_end、DateOffset.is_year_start、DateOffset.is_year_end等等。
% I4 ?& W6 H$ q3 @pandas.tseries.offsets.WeekOfMonth(week,weekday):描述每月的日期,例如“每月第二周的星期二”。 m! ~; K T$ ~/ o3 z
" H; B: X7 @1 R; C$ o; s3 `$ h有两个参数:
- m/ W' _0 a3 g7 ?week:整型,表示一个月的第几周。例如 0 是一个月的第 1 周,1 是第 2 周,以此类推。7 \1 |5 h; ?3 |7 B1 Q
weekday:整型,取值为[0,1,…6],表示周一到周日,默认取值为0(星期一)5 G8 x; M2 n1 w2 J5 o" U
pandas.tseries.offsets.BusinessDay(n):相当于pd.offsets.BDay(n),DateOffset 子类,表示可能的 n 个工作日。
" i% z& b3 D/ N0 f2 w. J0 S7 M/ O+ @( n6 _
pd.Timestamp('20200831') + pd.offsets.WeekOfMonth(week=0,weekday=0)
* i6 R$ Z5 u7 o* }- d9 a) ^3 w, EOut[82]: Timestamp('2020-09-07 00:00:00')
) Y- L9 e- O- x
' |$ D0 v2 K& p) ^$ |& Mpd.Timestamp('20200907') + pd.offsets.BDay(30)
0 E: g' e- ?1 t! s7 COut[83]: Timestamp('2020-10-19 00:00:00')
2 w! q% \" F# R. @+ n. {1
! p* N/ |) z1 q/ ^( b% {2
% i& c/ E7 _$ H4 }3
; h9 `% A* U, C- f8 f! f4
3 i1 V/ W: E5 y; h* Y' u8 t5% U. E( b3 ?" y: [
从上面的例子中可以看到,Offset对象在pd.offsets中被定义。当使用+时获取离其最近的下一个日期,当使用-时获取离其最近的上一个日期:
4 F, a% _& z) P/ c" U' n2 [
' J& H# z/ k$ G, d zpd.Timestamp('20200831') - pd.offsets.WeekOfMonth(week=0,weekday=0). t. U5 V0 y9 d) W/ l M
Out[84]: Timestamp('2020-08-03 00:00:00')- C/ i+ T# s1 u4 d# o1 E
( t0 o4 n5 a Q" `pd.Timestamp('20200907') - pd.offsets.BDay(30)$ ^ b% e* Z: R' [
Out[85]: Timestamp('2020-07-27 00:00:00')% u4 b2 Z7 E4 b% e( D- u
$ }/ z2 @% q' f8 Spd.Timestamp('20200907') + pd.offsets.MonthEnd()9 O6 O4 V2 @7 L# T
Out[86]: Timestamp('2020-09-30 00:00:00'). Q4 l0 Y3 ~8 s4 j' x2 }
1- n5 [$ t3 ?( k- e3 A$ F" q4 \
2" A `! [! i5 l
3- n' G/ S7 O, H3 `1 F" \2 `3 G
4
9 ]5 X8 ?+ x$ |) [+ ^& [5
5 D I- s6 Q/ T F& ]1 l$ K" P( n& O( |67 C2 [5 h! S/ L
7' Z! m0 [# r# U2 q% ^
8, m( P6 v' h6 L I) j
常用的日期偏置如下可以查阅这里的DateOffset 文档描述。在文档罗列的Offset中,需要介绍一个特殊的Offset对象CDay。CDay 或 CustomBusinessDay 类提供了一个参数化的 BusinessDay 类,可用于创建自定义的工作日日历,该日历说明当地假期和当地周末惯例。- P2 T7 [' G. z7 N! k3 k2 T
其中的holidays, weekmask参数能够分别对自定义的日期和星期进行过滤,前者传入了需要过滤的日期列表,后者传入的是三个字母的星期缩写构成的星期字符串,其作用是只保留字符串中出现的星期:. N" p/ x8 u4 j* e6 s
* w d6 P! E$ b3 }7 N. c; R3 J
my_filter = pd.offsets.CDay(n=1,weekmask='Wed Fri',holidays=['20200109'])3 T' o1 C! Z9 O1 K4 h' g7 _
dr = pd.date_range('20200108', '20200111')
0 W# Y0 G2 s# g! y9 N) p7 K Z) ^! u6 o; b1 C/ V8 F& N+ {$ `
dr.to_series().dt.dayofweek
( O. c% k: l, z; L0 S" ?Out[89]: ) B z* V) I1 T8 u
2020-01-08 2$ \$ U6 Y- V2 ]. D6 f
2020-01-09 3" x# p3 H' Y; Z5 j5 [* f
2020-01-10 4
2 Q8 E' J9 E: Q7 H2020-01-11 58 D7 Y- E+ e+ V+ b% _
Freq: D, dtype: int64
* }5 T5 k1 v% x5 ?: C% m
2 s: k& S8 g- z/ \[i + my_filter for i in dr]% ^$ A( E/ J. T- x8 }0 k
Out[90]:
5 |( S j# c- ]/ Z& z3 Q: t[Timestamp('2020-01-10 00:00:00'),
5 F0 G1 m3 c6 x$ t Timestamp('2020-01-10 00:00:00'),7 c5 K3 F- g8 \+ O) Q9 ^
Timestamp('2020-01-15 00:00:00'),
& R0 P! z- {$ }9 M Timestamp('2020-01-15 00:00:00')]
% h" e, u: u6 }9 V; @
! v, V1 \5 J3 i" i1( A3 I% T7 y+ J- K& o7 p8 ]
20 O: p) x$ x. R, P' v4 u
3
* A; [- H$ i- E: u4
$ [9 ~8 i$ {7 d5
3 Z% n5 ], c7 Q) ~# X6
9 Y# G3 K- v) s- c0 N" s8 y2 `8 ]5 R72 ?" ]" H5 A. D3 M
8$ s2 Z, |" G7 m" A2 W; y9 b
98 N; z& d' u7 q0 M' y' C2 k2 D: ~8 C
10
/ U. g+ I. G+ j3 @; V11
' D' ?' ^+ g# g126 w7 a, s- l( o$ N& a% [
13
5 Y4 ]6 _/ Z- ?, H% T144 b# }0 k6 h# U8 p9 y) K& O/ k' Q
15
, [% O% a) i7 t b1 F/ P. Z# ], p16
" a2 a, o! }9 Q17+ a0 D7 r6 i0 c
上面的例子中,n表示增加一天CDay,dr中的第一天为20200108,但由于下一天20200109被排除了,并且20200110是合法的周五,因此转为20200110,其他后面的日期处理类似。
- K" N- T+ S+ c: n% a3 V& u0 d
4 D8 g' P) b7 o' k3 e9 \' i【CAUTION】不要使用部分Offset
- s' v; n4 E& }# H在当前版本下由于一些 bug ,不要使用 Day 级别以下的 Offset 对象,比如 Hour, Second 等,请使用对应的 Timedelta 对象来代替。8 S' p& L' H6 f0 r( F
8 G( u6 F( ^, K) v" F; S6 \ e( G10.4.2 偏置字符串
* {4 f. r$ W. Z 前面提到了关于date_range的freq取值可用Offset对象,同时在pandas中几乎每一个Offset对象绑定了日期偏置字符串(frequencies strings/offset aliases),可以指定Offset对应的字符串来替代使用。下面举一些常见的例子。7 G) E: p0 A6 }* h2 L4 y" r. s
" b( p' N) A: H: F Offset aliases:pd.date_range函数中的freq参数,为常见时间序列频率提供了许多字符串别名。 也称为偏移别名Offset aliases。偏移别名列表点此参看(大概27个)。5 Y ]1 g( {4 s; {) O0 [$ r; h5 _
* u: _, o4 I7 E
pd.date_range('20200101','20200331', freq='MS') # 月初: n N3 t; v0 H) Z' C
Out[91]: DatetimeIndex(['2020-01-01', '2020-02-01', '2020-03-01'], dtype='datetime64[ns]', freq='MS'), A' H$ T& p% N% ]/ u5 A
& @ P k8 [2 ?/ z! y
pd.date_range('20200101','20200331', freq='M') # 月末0 _7 _3 |5 k) @1 u4 C$ g6 R5 X- |
Out[92]: DatetimeIndex(['2020-01-31', '2020-02-29', '2020-03-31'], dtype='datetime64[ns]', freq='M'); b5 F$ D$ O/ @1 X+ u
- _ D: ]' t0 g. j apd.date_range('20200101','20200110', freq='B') # 工作日! M$ l: Q9 F7 O6 Q
Out[93]:
' e" x* `( j. j z( u- ?4 ^DatetimeIndex(['2020-01-01', '2020-01-02', '2020-01-03', '2020-01-06',
3 y1 B/ I" c2 F/ c: I '2020-01-07', '2020-01-08', '2020-01-09', '2020-01-10'],: m& [: @9 y/ L! v ~
dtype='datetime64[ns]', freq='B')" ]0 ^4 K! y. C; p# P5 _) l
- k( M7 q3 v' _" e2 q) P& N0 \
pd.date_range('20200101','20200201', freq='W-MON') # 周一1 W7 Q% V5 l3 ]& ~; `+ S
Out[94]: DatetimeIndex(['2020-01-06', '2020-01-13', '2020-01-20', '2020-01-27'], dtype='datetime64[ns]', freq='W-MON')
; O. F! `% @8 M7 `# o) v2 _5 C T/ f( N
pd.date_range('20200101','20200201',. Q5 N# A, T! n; G6 ^$ V
freq='WOM-1MON') # 每月第一个周一
- t6 e1 R3 c+ M6 I! B6 J1 O* ?% D- z7 M$ H% X" Y
Out[95]: DatetimeIndex(['2020-01-06'], dtype='datetime64[ns]', freq='WOM-1MON')& q! |& v, Y% [* K
' U! c4 d, \% ?) x* E9 I1
( B$ j p' v/ t# I, p2
6 g) i* M& i* z2 @: T* d' J: o; \3
/ l+ G3 [8 ~+ i# d0 f4! ~1 I8 y+ K( |1 G! N& t2 V
5
! h% D8 W9 d, ^$ ~# r6
- e; O* ?+ y7 j5 y; r; i7
5 g% o: ]( B5 \2 E+ v, b8
$ G) L* b/ c9 t9
. E# @% ~+ o: }$ \5 ~! ?105 J' p3 @! x0 \% P, B+ Q% i
11
4 w, U# v* b, \6 Y! @4 x* D' L126 i2 V5 u6 y8 e/ c! }
13
6 J( n& [! f! U14# ^: H7 z! V% M+ B
15
7 @$ T5 ]; } y; j% B3 Z) J# _6 U166 Y- E1 }" m) W5 M& T o0 `4 S0 }4 y' U
17
6 P. J- V2 |4 m$ Y6 v- H2 w. A; |3 I18
+ C0 I z9 Q4 w1 ~19
0 ?9 u7 z/ H& Y7 X& @- c6 y上面的这些字符串,等价于使用如下的 Offset 对象:! v5 T3 R: ?1 O4 d8 ^" M) n
' q- O1 A6 S5 N4 w- u$ a5 H
pd.date_range('20200101','20200331',
4 d- |( {5 J t0 u' r freq=pd.offsets.MonthBegin())& p! B2 _0 G2 R. Z: i* j
# k4 Z O8 m) Q6 h
Out[96]: DatetimeIndex(['2020-01-01', '2020-02-01', '2020-03-01'], dtype='datetime64[ns]', freq='MS')! I" E* L5 |' z+ g( @
' c0 |( U. o0 E+ Opd.date_range('20200101','20200331',
0 O" i4 N; Z+ X; r: X* q freq=pd.offsets.MonthEnd())
/ Q' ?3 s) ~4 d7 b/ L$ e- k6 p- f# Y5 D- V5 [. [: ^& w( w a& n- o
Out[97]: DatetimeIndex(['2020-01-31', '2020-02-29', '2020-03-31'], dtype='datetime64[ns]', freq='M')
: S6 \2 P0 ?) X: t- ~
& X, u% [, [; Y- ?7 ]2 v( i) wpd.date_range('20200101','20200110', freq=pd.offsets.BDay())
, \$ S3 o. K9 t: a2 @, u" \/ n0 oOut[98]: . s+ @$ B3 t _/ U! {& G
DatetimeIndex(['2020-01-01', '2020-01-02', '2020-01-03', '2020-01-06',
; F4 l7 B2 @# L '2020-01-07', '2020-01-08', '2020-01-09', '2020-01-10'],4 m% ?" |) c$ [' u3 T
dtype='datetime64[ns]', freq='B')
# Z0 E3 r& a. n9 Z. V7 u. @
" ~0 U7 ]# M9 U# e, H" epd.date_range('20200101','20200201',
* N' ]8 U. f. F8 D freq=pd.offsets.CDay(weekmask='Mon'))
' d2 u& Q3 v& t$ C* o7 W9 O
# F! r7 @: Q: H( L GOut[99]: DatetimeIndex(['2020-01-06', '2020-01-13', '2020-01-20', '2020-01-27'], dtype='datetime64[ns]', freq='C')
. }6 a; _) `/ o4 m; u- x4 E' X+ L# i P
pd.date_range('20200101','20200201',1 g0 I$ O# ^+ |6 a
freq=pd.offsets.WeekOfMonth(week=0,weekday=0))9 P) s2 S$ C. F0 {
! |4 d# p/ _" I: U6 m; x( x" R7 R
Out[100]: DatetimeIndex(['2020-01-06'], dtype='datetime64[ns]', freq='WOM-1MON')+ n. w) w2 [' ?1 H. G+ Z6 U
3 c- `) J. [4 H$ D) l1
( D- _- L1 E! `0 F3 `2. } ~4 g2 ]. _: Y
3, ], l" N9 I$ T' ?. G1 A/ O
4
3 U7 ]5 U* }. Y4 j' B, g5$ T7 j9 }6 c6 S. M0 q
6
; E) ]' V0 A& d0 A7 b7
0 y/ P! k! W3 n% [8 g: L1 R+ @8& W, a% D) |* L) |/ |1 h7 b/ c3 W8 M2 Y
9
/ o2 |. [$ O) n8 P10
2 ^ O) R) \# \. Z11
+ N5 Y$ m* s2 P- @' h12
4 o0 Z& A% z C3 l: {9 z2 E+ u13
' ^6 c' z/ B# a. y/ F14' {6 q$ y, k0 w- U4 t1 q& @
15
& y2 e4 C! n8 D0 h7 R$ M16
6 _! M! e& C6 c5 U# R17% z; t! H9 ?# K a: H5 t+ w
18
8 `2 i! l" f4 [# M1 Z3 s19 {% X( N# G9 Y7 A
20! j/ Y; e* `/ O, U/ o/ x
21, Y' T$ J3 z* F1 p* B* j' v- d
22
0 [' S/ q6 I& U" H9 [9 }23
8 ~- w+ U' z# {+ A24
. o* B- r7 |9 G; T8 _) |25( m/ S5 N- M( x d6 l
【CAUTION】关于时区问题的说明4 z; H2 b3 s; d
各类时间对象的开发,除了使用python内置的datetime模块,pandas还利用了dateutil模块,很大一部分是为了处理时区问题。总所周知,我国是没有夏令时调整时间一说的,但有些国家会有这种做法,导致了相对而言一天里可能会有23/24/25个小时,也就是relativedelta,这使得Offset对象和Timedelta对象有了对同一问题处理产生不同结果的现象,其中的规则也较为复杂,官方文档的写法存在部分描述错误,并且难以对描述做出统一修正,因为牵涉到了Offset相关的很多组件。因此,本教程完全不考虑时区处理,如果对时区处理的时间偏置有兴趣了解讨论,可以联系我或者参见这里的讨论。3 d! q& o2 \; p) L
( q \) N G3 o9 n) d" q& i% e" o
10.5、时序中的滑窗与分组
; F" J% n+ U5 i3 n/ h& r; O `10.5.1 滑动窗口& o' U1 M. {8 H4 n- V6 A
所谓时序的滑窗函数,即把滑动窗口windows用freq关键词代替,下面给出一个具体的应用案例:在股票市场中有一个指标为BOLL指标,它由中轨线、上轨线、下轨线这三根线构成,具体的计算方法分别是N日均值线、N日均值加两倍N日标准差线、N日均值减两倍N日标准差线。利用rolling对象计算N=30的BOLL指标可以如下写出:6 C3 k4 t; E+ g
* i- d$ X, x1 x" }# X% d# e; p4 ]5 N" _import matplotlib.pyplot as plt
1 r0 l [2 |! c( O/ Tidx = pd.date_range('20200101', '20201231', freq='B')
1 O2 v: H- W3 Vnp.random.seed(2020)2 Z! P. d5 w$ W! Y _
0 T+ B' g6 A& [- m# l( Y! T1 _% o2 }
data = np.random.randint(-1,2,len(idx)).cumsum() # 随机游动构造模拟序列,cumsum表示累加- q6 n X5 T& j$ Z
s = pd.Series(data,index=idx)$ [! ~ N; Z8 s4 j( g& L' \
s.head()5 Q o; M, S, z* w7 Z5 F1 \
Out[106]: 5 ]. D! L, u e$ C: m
2020-01-01 -18 L9 V) K5 ^( J4 Q, {, \, h9 |
2020-01-02 -21 P! K- D6 D) h
2020-01-03 -1 Q l4 w* `2 X( V6 Z
2020-01-06 -1
6 s/ q4 q( y+ {- ^8 `7 f- [2020-01-07 -2" m9 v( c0 I/ v' F' r4 L
Freq: B, dtype: int32
1 M5 V# T3 V0 ^. er = s.rolling('30D')# rolling可以指定freq或者offset对象
$ @% Z- J& ^/ K" |
s- y5 j3 ]5 o0 V k7 S8 rplt.plot(s) # 蓝色线
6 {2 q" X, L e' cOut[108]: [<matplotlib.lines.Line2D at 0x2116d887eb0>]! c4 e8 {( n. a1 C
plt.title('BOLL LINES')! j, a. Y( V! k
Out[109]: Text(0.5, 1.0, 'BOLL LINES')7 O u, A; w- L4 ]0 r B3 s3 S- B
q' x( Z* J6 O2 T" Wplt.plot(r.mean()) #橙色线' O5 N m4 C) S. h1 Q {7 R* E
Out[110]: [<matplotlib.lines.Line2D at 0x2116d8eeb80>]
0 n* N4 a% u0 n9 ^- Q! J
0 G+ W: y# R; D* fplt.plot(r.mean()+r.std()*2) # 绿色线
2 D$ T$ C- g: JOut[111]: [<matplotlib.lines.Line2D at 0x2116d87efa0>]$ l. [, S; ^6 s8 Z
1 G9 N6 O7 ]4 Z( l5 G$ d
plt.plot(r.mean()-r.std()*2) # 红色线
L/ |+ D$ e! ? @Out[112]: [<matplotlib.lines.Line2D at 0x2116d90d2e0>]
1 E3 W+ ^+ j& d! F: [* F& d" ?2 G3 W7 n; g& c+ p- H, |/ C
1
6 b7 E5 x2 v" R2
& D& ^* f1 B7 P% ~4 @+ s3 j3 d8 h! Y1 I( R. a9 s6 Y; P- Z- C8 P/ B
47 ~. H* j/ J- _" g# r7 ^ E0 i
5; c3 N, i9 H Y# C( q6 v: T
60 i7 h! m: k/ Y# S L9 F* H/ u& R
7" V$ s+ b/ B l, l; V4 A( N7 y
8
6 ?; ^0 X& `# l* H$ ]2 L$ h. h3 R( B9- }. y. n3 L8 ^5 n
10+ c `! H' u( _* K- R3 ?
11
; D0 _# g) n7 ]; I8 [! a0 R, C12
" }5 B9 }. [ m0 F+ o7 d13! G4 F; D+ M5 I0 i2 `7 }) [
14
3 z" w8 W5 ~; E# }/ q. l15
* _2 U3 L3 j( \2 y8 m9 h16
, N, Q: J" ]$ v' t- k9 E17
% R/ X, B: L0 m1 d+ x# p18' I( A! s: K1 ^% H2 v, O( f
19
+ `3 }6 z% P, k$ C8 t20! f& @: T+ i: ~% x: r: W
215 l; Q# e! D" W; k( M4 i
22
/ R! \$ m8 S$ I _239 j& H( Y' ^& {* ^% u- U
24" v+ u2 ]% z! Y; t/ g2 A% N7 u
25/ g1 I( F% \1 |8 P
26
# X2 y( ?- @& n27% {7 \+ U. ~( a) x! k* ^+ n3 I+ ~* i
28/ T# u: J; Z4 q7 w% @) f* S# |
290 a: B H% G) U, t, F7 @* F
, g7 l$ r) s0 u/ ~8 c5 O F5 d
这里需要注意的是,pandas没有实现非固定采样频率的时间序列滑窗,及此时无法通过传入freq字段来得到滑窗结果。例如统计近7个工作日的交易总额。此时可以通过传入多个函数的组合来实现此功能。% G+ Q) |# }. {- T
首先选出所有工作日,接着用普通滑窗进行7日滑窗加和,最后用reindex()恢复索引,对于双休日使用前一个工作日的结果进行填充。
9 Z M2 i" d# r+ Z5 P& D+ P
& j; m6 g1 O) b9 k" j4 \+ ?select_bday=s[~s.index.to_series().dt.dayofweek.isin([5,6])]
* D. j n: [' S# ?1 Mbday_sum=select_bday.rolling(7,min_periods=1).sum()) H% M9 n* [5 |% e7 j* {
result=bday_sum.reindex().ffill()
9 l; S1 l0 L6 S- E: ? I! y5 Oresult! ?& _' K/ `9 B, p' _$ z
: g( @2 Q) w7 I0 |" \5 T r2020-01-01 -1.0+ @9 l" r4 R ]4 M# ]# g
2020-01-02 -3.0 Q& l3 c V9 a: n1 m" L
2020-01-03 -4.0
$ g, \, r. }7 F7 W2 m8 E/ {( O2020-01-06 -5.0
6 B/ d/ P5 @* Q/ j9 s% R2020-01-07 -7.0
b8 H+ f- f# m) O+ I7 m ...
9 Z6 o1 b6 Y* ~0 V- W2020-12-25 136.08 J4 N s% L8 c2 O9 i9 F$ M
2020-12-28 133.0
) ?1 r% J- c, }+ F% m* P+ v& _2020-12-29 131.0
. e( v5 ^5 [1 X" |& h+ s2020-12-30 130.0
8 V+ @; R. {6 _2020-12-31 128.0, v0 k% R; A2 d. C* K
Freq: B, Length: 262, dtype: float64
! R" P6 b: }: |
a2 S& {; x# W: T3 R1% E4 {5 g3 V2 A3 B5 Q
2: Y+ A/ d w$ O8 U0 f9 ], a
31 d7 ~9 ~3 F( A8 \9 P4 m- v
4' |/ y3 s. p6 e/ A( T7 _
5, u( C! e" }8 F; a
63 h! h( f# a6 R; F
7. J' x4 a: l5 q( G. e) ~
84 z7 Y2 [& W* o6 D/ v* E
9
# R" f6 ~; g n/ H1 v6 d10
* |( ]7 l7 X6 T- X- @9 T$ R/ D11( q3 \5 P) c+ T8 q1 a7 M4 w
12* g7 z+ U& p: V; G4 [3 P
13% F V0 u5 U, I2 j- b
14; u4 b$ k+ z8 ^
15- q! {+ R# _8 j1 V' P
16
" n+ S' s1 l! \" D1 @) ~2 ?17
9 l. m! R) @, b* ?$ ^( W shift, diff, pct_change 是一组类滑窗函数,它们的公共参数为 periods=n ,默认为1,分别表示取向前第 n 个元素的值、与向前第 n 个元素做差(与 Numpy 中不同,后者表示 n 阶差分)、与向前第 n 个元素相比计算增长率。这里的 n 可以为负,表示反方向的类似操作。
, b6 a/ G2 I& p o" G, E
; i- c: I/ r- O* Z" J 对于shift函数而言,作用在datetime64为索引(不是value)的序列上时,可以指定freq单位进行滑动:
; i/ `) P( V* F: {. s* i8 T( [6 z
+ n0 E& d: \+ x* P. ]s.shift(freq='50D').head()2 A4 w, U# h8 i. `
Out[113]:
6 ~1 `, Y- Q2 m+ M. D6 w: `2020-02-20 -16 c7 D \5 }) _. e/ W
2020-02-21 -28 _+ ?1 R M" R2 H; S: j; ~7 y
2020-02-22 -1
' S2 I9 O) N2 N( k0 U9 I" r2020-02-25 -17 D+ _8 r5 w6 Q2 C: e" G
2020-02-26 -2- ]0 g2 _- T2 S O
dtype: int32$ ^" o! O: Z$ J/ d, ~7 W" G& g
1
! A! o& d( ?% N) X# {* Y0 A2 l2
% r R% k. u: }6 Q3
" M) C2 k6 j% ?9 I# I9 Y6 o47 _( d' j [9 F# I2 s
5
% k- c7 Z9 |! J P6 P) ]6 b( L% r A6' ~+ N: ~3 g6 W7 b9 u$ o6 f& E
73 s' ~5 l& K1 w+ |1 g
8
. \- z. z; ]" w6 R- H 另外,datetime64[ns]的序列进行diff(前后做差)后就能够得到timedelta64[ns]的序列,这能够使用户方便地观察有序时间序列的间隔:
0 d' n% A J- y
, q Z2 _% f C+ U E% nmy_series = pd.Series(s.index)9 D: u+ Y; z' P6 |+ p
my_series.head()0 j8 g" D; V9 Q! J. P& A; v
Out[115]: 2 p; w+ _. B0 o1 T# P$ S
0 2020-01-01
0 Y9 r; H' n2 T- F1 q! v1 2020-01-02! A j" r0 O" r0 ^+ H0 x+ @7 V: A! _3 c
2 2020-01-037 ~9 X+ b; k. d
3 2020-01-06
0 H% j v, Y3 O8 g6 y4 2020-01-07; G$ [" i6 O C. [
dtype: datetime64[ns]
* z( I4 t. j9 ^% x8 b" ~
; a: m6 J0 x; [) e- C9 S$ Ymy_series.diff(1).head()
4 O- }& y; X, E) o1 n+ kOut[116]: : s7 y; {$ i. T: u) o J( j3 c
0 NaT
: R! G5 }7 O S. {1 1 days) E) i$ v$ A0 Q, ?( E
2 1 days
$ z$ Q: c& Z7 P/ Q* H) v! j; \3 3 days
3 q, b! E: x. ]- h5 x0 u4 1 days2 g1 t) y9 ~& K: I) |
dtype: timedelta64[ns]9 ]* G N4 g% A% J: `6 P
/ w& u9 r4 H, \- I& k
13 q$ Q: Y# `% m4 O6 S# Q3 F- O; G
23 j `7 ]1 e( l" M* Z ~: c
3
" {1 [0 P$ {# b. U% m6 A4
$ \1 V _- J$ K) Z7 E5* ~' \$ S: U! z4 N; E3 P
6
, \9 M8 \; `) L! v5 g7
* G& f5 S9 E# ~) \* |8- r3 G% w( S% a2 a
9% I% R% O0 c6 K* C7 ]* p
10
/ m" q. r6 M8 B; M% X4 V11) m' Z' l6 _$ ^" e' b
12
/ ?+ o& n% v7 J, V' |, N13
0 g% S/ _. p; x$ V% q14 G* m# `& Q& ]
15
$ N. v! D4 s; t, M16( \# }: v( P8 b2 x( }. O
17
% u3 C; [* x$ l8 j- z" z1 q* i18+ H( V9 z, s# L4 z; q" t( \
10.5.2 重采样
3 V4 @3 Y! `+ C9 z. I DataFrame.resample(rule, axis=0, closed=None, label=None, convention=‘start’, kind=None, loffset=None, base=None, on=None, level=None, origin=‘start_day’, offset=None)8 g6 L. K* L& ^- S; F
常用参数有:* s& `9 f- R+ f" r& r
# H! \- c* G- j9 e: v5 q& wrule:DateOffset, Timedelta or str类型。表示偏移量字符串或对象
; M" w& j7 s% T9 |5 haxis:{0 or ‘index’, 1 or ‘columns’}, default 0。使用哪个轴进行上采样或下采样
8 Y! U/ I9 }/ Y+ v! fclosed:{‘right’, ‘left’},默认None。表示bin 区间的哪一侧是闭合的。所有offsets的默认值为“left”,但“M”、“A”、“Q”、“BM”、“BA”、“BQ”和“W”均默认为“right”。
' V2 R, Z `. V6 t% Dlabel:{‘right’, ‘left’}, 默认 None。hich bin edge label to label bucket with,所有offsets的默认值为“left”,但“M”、“A”、“Q”、“BM”、“BA”、“BQ”和“W”均默认为“right”。5 B2 I, N+ U5 i' ~/ p
convention{:‘start’, ‘end’, ‘s’, ‘e’}, default ‘start’。仅针对 PeriodIndex,控制是使用rule的开始还是结尾。
# c s+ g1 ~0 e% j, M1 m {9 won:字符串类型,可选。对于 DataFrame,使用列而不是索引进行重采样。列必须类似于日期时间。
" c( [/ c9 ?% V. I% ~# C; c5 E; vlevel:str 或 int,可选表示多重索引MultiIndex的级别,这个级别的索引必须类似于日期时间。* R5 Z& C2 S- d7 p2 P; {8 D
origin参数有5种取值:
5 g: d/ l- T- f4 i; {3 y3 b0 B2 V$ N‘epoch’:从 1970-01-01开始算起- D# |( |9 r0 H8 x+ l) M* Q
‘start’:原点是时间序列的第一个值: ~8 x! t) Q2 G: F7 E# W2 D! R
‘start_day’:默认值,表示原点是时间序列第一天的午夜。+ n; g! b8 [5 G0 E1 I! R% C
'end':原点是时间序列的最后一个值(1.3.0版本才有)
5 t+ S. @! l r% S4 ^‘end_day’:原点是序列最后一天的午夜(1.3.0版本才有)
7 T' N- U( R1 c8 v4 j1 K& l6 p6 A" roffset:Timedelta 或 str,默认为 None,表示对时间原点的偏移量,很有用。5 G2 n9 }( R4 D* F5 S
closed和计算有关,label和显示有关,closed才有开闭。
: L3 k% @ p4 b. D! L label指这个区间值算出来了,索引放区间的左端点还是右端点,closed是指算的时候左端点或右端点是不是包含。
- a3 j' e! r* Y0 R: q) a2 y" b
" |5 j9 k- N6 E; `0 f9 O+ o重采样对象resample和第四章中分组对象groupby的用法类似,resample是针对时间序列的分组计算而设计的分组对象。例如,对上面的序列计算每10天的均值:% `7 S" P2 P$ Z! T2 @
s.resample('10D').mean().head(). `; X6 X5 U/ l- Z" E* T. S
Out[117]:
2 G4 r* U9 H$ ~9 u; e6 f& g2020-01-01 -2.000000
3 g5 l+ C t! k, U. a7 ?5 N2020-01-11 -3.166667
3 c) x3 o3 ~" H( f1 t2020-01-21 -3.625000
$ d, R- w* x7 y m' R8 d5 `8 G5 i1 X2020-01-31 -4.000000
% ?9 P5 f8 i5 h6 F! U3 }$ T# N2020-02-10 -0.3750008 ]4 \& y' l: v
Freq: 10D, dtype: float64! ~, S" W3 j: b6 j
1
7 c1 L: f5 U0 {9 \( H$ i5 F2& |: Q2 p' z( `
3! J: H- e/ b L! _+ O
4
* t, F0 m; o- H5 W& o% P! A5
0 G+ h" D- U+ N2 P6" R3 \! ^% {* v8 _) J; s
7
* s+ }! k ^( m$ q: Y, x8
) _/ T: W$ {4 h$ _6 X可以通过apply方法自定义处理函数:
+ y+ k- F3 x& bs.resample('10D').apply(lambda x:x.max()-x.min()).head() # 极差$ P4 F3 C. `- Z- u/ K/ v1 Z6 j4 B7 n+ s
# R3 Q9 H0 D! eOut[118]:
. L! o- z4 r8 N n" N- V2020-01-01 3
4 a6 d- O# U% i+ ?9 k& j2 x! G, A2020-01-11 4; Z2 y# Z( y$ P& [4 W* a9 Y
2020-01-21 42 J8 m) |: C! B9 ]. j1 z' W
2020-01-31 2
) g0 n5 _& E0 L' |* s) I0 f; \2020-02-10 4/ }, ~1 Y; N, X; S5 v& I, u; p
Freq: 10D, dtype: int32( @! C6 {; I# V! O9 y; X, ?
1# w5 ^9 F, ~! X0 {8 i
2! {, [- z/ }3 G' F. ?" `! G( |
3
( Y, z1 t4 [( i- i" t/ m, f! X( T46 l$ c8 }4 r7 X) w- w# b
5
: g. H+ y. `/ Z7 U2 i* [2 U( `9 K6, W2 g$ m" ^1 V+ }3 P6 `; E" X/ u, n5 `
7
% t- w" ~ {, i. D: u4 _8/ K7 q( l( w' e% v1 W& K/ t
9
6 L2 [* E+ s y8 z: D 在resample中要特别注意组边界值的处理情况,默认情况下起始值的计算方法是从最小值时间戳对应日期的午夜00:00:00开始增加freq,直到不超过该最小时间戳的最大时间戳,由此对应的时间戳为起始值,然后每次累加freq参数作为分割结点进行分组,区间情况为左闭右开。下面构造一个不均匀的例子:
& Y G: u% R! n% ~3 F1 ?7 G3 n; z0 O0 Q6 M/ `7 J/ |
idx = pd.date_range('20200101 8:26:35', '20200101 9:31:58', freq='77s')
9 x$ Q: \- H& n( e2 U' T$ w/ a4 cdata = np.random.randint(-1,2,len(idx)).cumsum()
% ?/ c, b8 L- y( C0 [# fs = pd.Series(data,index=idx)+ v/ ?7 O# @8 F
s.head()# k" L- M, `- q! u% J# z" ^
" o$ a' }/ B$ |! n$ h" Q
Out[122]: s: c4 l1 M0 Y. D% O, _) l3 i A
2020-01-01 08:26:35 -1
7 \# T% C; c# j# s( z2020-01-01 08:27:52 -1
8 r% J! l& O3 f7 A2020-01-01 08:29:09 -2# G2 ?5 }$ } C" |
2020-01-01 08:30:26 -3
2 ` S; H, q& h( F4 u! I- B2020-01-01 08:31:43 -4
7 D# g" e: I( `+ q2 {2 VFreq: 77S, dtype: int32
" ^+ ?; G# Q& M6 U- w L( A% o1) F- Z8 I# v6 [ Y9 i/ U
2' K- b4 j8 F! T; Q' v: E( F
31 e6 S+ u5 @$ |8 S7 J( r
4
0 p3 N3 S* a4 h2 j% G5
" j' r7 ^6 T6 A3 R4 z, b2 q6: u( Z8 _8 v, U( ^" {6 z
7
6 U. `$ G( ]% N6 D$ c9 U8$ f5 Y$ s0 l6 J% F3 z' z
9
) Y0 ]9 S N3 E _( G. ]! q10/ j( T4 p" m% K9 x
11 V- J8 I1 _' b5 S) m. x! [0 k2 Z4 |
125 N5 u* x7 r- x: {4 R. c* U; I
下面对应的第一个组起始值为08:24:00,其是从当天0点增加72个freq=7 min得到的,如果再增加一个freq则超出了序列的最小时间戳08:26:35:9 @7 h6 j7 v% v
6 j& U5 B5 ?0 s3 r6 l/ ns.resample('7min').mean().head()
/ G; a! V" O! _. w* |2 r% gOut[123]:
% ]) Q( B7 q( I7 \2020-01-01 08:24:00 -1.750000 # 起始值,终点值包含最后一个值4 L, ?7 d, y# g7 }: T
2020-01-01 08:31:00 -2.600000
) e+ f. X& W) k" L% _2020-01-01 08:38:00 -2.166667+ I$ e6 R' q- N" q3 M
2020-01-01 08:45:00 0.200000! p) j5 R* g6 o$ m, V
2020-01-01 08:52:00 2.833333
0 j& g) d. s! A% aFreq: 7T, dtype: float64
$ A4 d) f3 h! K/ R3 f" U1 I1' } l2 F% i+ m% R$ E
2
. j# h0 F3 e/ U/ Y% q; z3
( [" L( @( i8 D% e8 B4
! F1 e( H; B% b# S3 W+ {5
B, }/ u7 V/ b# b H% ]8 J8 q6
7 _& U( o3 H' G7 _2 B2 v' p" J$ C8 d70 E5 [; b5 l/ A) v! G
8, |6 ?# t4 L* n; ~6 \
有时候,用户希望从序列的最小时间戳开始依次增加freq进行分组,此时可以指定origin参数为start:
9 h+ S2 c+ M) w. O# v" Z a5 B- q
s.resample('7min', origin='start').mean().head()
! Y' G2 ?6 }7 h' i5 K. rOut[124]: # R: w; t& d( u- J! g
2020-01-01 08:26:35 -2.333333- }% P3 z, U7 \( ?4 `2 X: v
2020-01-01 08:33:35 -2.400000$ \6 N& G% L2 T) W
2020-01-01 08:40:35 -1.333333
) t8 j5 T6 g& l( J1 m2020-01-01 08:47:35 1.200000% Z- I7 @' c* ~; V& a6 U+ I
2020-01-01 08:54:35 3.166667 Q1 [, o; x" f
Freq: 7T, dtype: float64
% H' y; k7 g" B6 D+ @1! V4 `1 p( U/ c
2) g& k/ y; I# n
3
7 R$ M6 s6 e7 G& f' ^ G43 b& E, ~' f' k! d5 p2 M
5% k" c( m! Z6 P
68 |6 N# c# P5 I: D* d
7
, M3 d$ Z- w3 j. N- Z% S3 z% s8# Y5 X/ n; j" D3 z' b
在返回值中,要注意索引一般是取组的第一个时间戳,但M, A, Q, BM, BA, BQ, W这七个是取对应区间的最后一个时间戳。如果想要得到正常索引,用’MS’就行。8 U' o1 O, F% ^- ]+ S: N
) M3 _) b+ \- g
s = pd.Series(np.random.randint(2,size=366),4 {- B+ I7 }5 F0 R
index=pd.date_range('2020-01-01',
. E# S- J* k$ w' B+ d; Z C6 A9 G! k '2020-12-31'))% ~2 J% p" O7 h. t$ g2 r, ~
+ c' Q# q3 {6 a$ {
' e1 t: Y& E2 R) U) B- t0 C" ?s.resample('M').mean().head()
: j% M: l8 t1 iOut[126]:
% m5 J6 b& d2 H4 b2020-01-31 0.4516137 H0 _2 L8 C/ i( h6 g3 m$ N
2020-02-29 0.448276; K w ?8 F) n; |" O; t. P8 {
2020-03-31 0.516129) a4 o( ]1 o* W c9 A1 R& i$ s8 H3 }
2020-04-30 0.566667
; D8 M% n# k% j2020-05-31 0.451613. n* R" I* d0 O/ t6 G
Freq: M, dtype: float64# H9 l7 |% w8 M& l* ~) v8 b* B
5 h5 L& z& D* U0 Y0 K2 N
s.resample('MS').mean().head() # 结果一样,但索引是跟正常一样8 v! ?$ y9 M6 w: I" p4 H# |
Out[127]:
0 Z4 D8 _' x& }. ^: m1 {) u2020-01-01 0.451613' M: c) H$ i+ o& d
2020-02-01 0.448276. Y4 p* E( f- T9 j
2020-03-01 0.516129. Z% u! y0 n! X) v3 c; X$ P `
2020-04-01 0.566667
- G1 n u1 N7 g; m9 z2020-05-01 0.451613
% P7 ~: t* k# d& i, B4 nFreq: MS, dtype: float64( O' G! o6 n# M9 Z
" S& q& M/ ` H; V1( X" m5 A( j1 G* z: {/ |
28 v9 H& J/ J$ y7 ^ }5 E/ R" Q
3
3 E; s+ L# |* {$ D+ O% D3 S4* ~$ T$ ^7 W/ I3 [
50 a, P7 j8 F) T
63 S& N! Z9 U5 I8 H
7
3 w3 a/ N' d6 i) p2 W3 @86 B& x8 J$ d0 z8 f0 Q& X/ H1 |2 g
9
& [# `' Z5 A/ o: U, y ?/ O10
, n( M( H3 x7 Q, e11; I. u$ B3 G4 L! \; H) N) L" F8 i% C
12
( X% d% w4 D9 t3 j- i1 N130 S" W, g4 Q$ }- r2 v, {
14
$ | Y1 ?$ d" \! S15
# ]* m O; ?- Y, `: J16/ j; G3 T7 w" ^3 _$ b) Y
17
, W' U, @7 q0 [7 Q18* E1 e3 K/ G/ _) L
19
+ g, Y- D4 J! O3 G/ _ }! Q2 M20+ h& U. ]5 E4 m) h0 {( G) u: A7 m
21
0 x. h6 C3 o2 T+ \ T C6 ]. P22' b! ^# B& {: [) C! M$ m1 t% o
对于 DataFrame 对象,关键字 on 可用于指定列而不是索引以进行重采样:- `/ M/ O7 d7 a- I, |1 T" D, _
d = {'price': [10, 11, 9, 13, 14, 18, 17, 19],
" p5 x$ e& r& M; e% t 'volume': [50, 60, 40, 100, 50, 100, 40, 50]}1 O" t; {0 u; d: _; T+ \
df = pd.DataFrame(d)9 U7 Z& F7 o: q
df['week_starting'] = pd.date_range('01/01/2018',
' x1 N2 g' u+ r- S periods=8,8 t: o+ L- t7 O
freq='W')2 [" \6 v7 u3 t5 u0 B; i" A
df" W3 ^+ K2 m/ R
price volume week_starting
0 ?* b5 Z) J5 h, `0 10 50 2018-01-07
. q* T+ p& p( ]! j1 11 60 2018-01-14
, X+ V' R6 t7 n- U( B6 {4 P2 9 40 2018-01-21
! g6 `$ v2 b8 V) b3 13 100 2018-01-285 J! ~' E2 ~' n+ m
4 14 50 2018-02-04
* H. Q2 X* h) ^% N0 y% X8 z5 18 100 2018-02-111 @9 O, ` N6 ~4 a+ n$ \+ P
6 17 40 2018-02-18. ?* B& o' O( l% `0 c/ b
7 19 50 2018-02-25
3 b0 b( d- |; tdf.resample('M', on='week_starting').mean()7 v" U# p& `. d3 k4 L
price volume8 |2 q% G2 h+ @: `# ]
week_starting# y8 E7 S) b/ c, w# c: t# N8 s& |
2018-01-31 10.75 62.5
# ?; z+ @% D2 }( K' m2018-02-28 17.00 60.0
8 Q/ x4 z& g& J" }; K
1 d2 `9 f3 r7 M) }# ^8 e5 f4 _: A4 X& m1- W# R+ Z. g6 J# @, l4 S
2" O& ]! Y* E( ^+ z/ `: B
3- f( K) B) v2 A2 E, f( l
4
. F* g8 T! @6 \+ L4 J5: b# H+ s1 C: _
6
M$ s- [' `2 R4 t% q% g7
) n, s+ B5 Z) X( s/ c$ |4 Z, |# H8% T: k; p% Z3 t' }2 s
92 R& g, J1 y! Q) n: l3 q. `" \0 R
101 `: B# f8 I0 v9 s& T- q* ?7 i
11( r& `& | Z2 S( I& d* w3 B$ x
12! e- S3 y6 @; B; Z/ k# N2 u" g3 C
13
* W) g% A3 v2 x2 M) r" y, y14
2 C9 w: `+ P% o! F4 X. h ?15
; e, Y1 e! u3 X; Q, g16
% W, x# j: J; R: k5 f/ R17# a5 s$ T; Q1 U6 n
18
' j( [& G2 Z, N" Z2 c' L# |- |19
" O1 E6 V2 C+ y3 D5 e20' H8 P, ^# E' @7 n+ ?0 S
21# U6 Y* c7 L% \# Z+ w5 ^) z
对于具有 MultiIndex 的 DataFrame,关键字 level 可用于指定需要在哪个级别进行重采样。2 Y4 Q+ Q3 P# A
days = pd.date_range('1/1/2000', periods=4, freq='D'), c* S9 Z q+ K2 a* W
d2 = {'price': [10, 11, 9, 13, 14, 18, 17, 19],
( N, }2 f2 o2 V& n" L1 Z0 @ 'volume': [50, 60, 40, 100, 50, 100, 40, 50]}
' J7 i3 w7 l J2 H" Zdf2 = pd.DataFrame(
6 D, o1 U0 ~ W- M, `2 l d2,
7 h4 R2 R% D3 T0 a index=pd.MultiIndex.from_product(
* b" `- S/ p; i4 T" M3 h2 w [days, ['morning', 'afternoon']]
" T$ {3 Z, ^( @9 {5 d7 f )5 D0 T& u8 n0 r, r0 r4 l8 C
)3 n7 w/ J' V' P$ ^$ ~ H' T9 |
df2- g% u) S5 q+ Q+ q( ~* u! }( S
price volume
7 H% j9 G& ^) ^+ V) S( Y2000-01-01 morning 10 50
/ ?1 b% n& ~& A1 e+ y! t# u! X afternoon 11 60. M9 V3 b! p9 `8 c: d
2000-01-02 morning 9 40" |6 G* ~: |7 Y2 u+ K u# ~9 D) K4 ?, s
afternoon 13 1005 r& T9 }9 y7 q9 Q0 I6 A2 g$ e! q( _
2000-01-03 morning 14 50( l) F$ z8 r* C W- y
afternoon 18 100
+ V+ J6 F$ r- l% m+ n( G9 Z6 n0 c2000-01-04 morning 17 40
; H4 [! r) V: }7 E% f7 d7 W afternoon 19 50" E8 T, ?3 l4 T" v( ?" l
df2.resample('D', level=0).sum()1 ?9 ]+ ]9 J- y
price volume
* d; o/ n6 I! S( L" T; t. @2000-01-01 21 110
2 u6 S% }$ n: @ _& r2000-01-02 22 1408 ~* Y! Y6 I( e9 i0 d
2000-01-03 32 150, z- D. l9 n+ s, H; ]1 ~+ }7 B+ _
2000-01-04 36 90* _/ T8 X6 H+ f. q- @ x
6 Y+ ?- I9 i: j* y9 k1+ u/ z5 \) }: @: y: q+ R8 o
2
, F4 Y6 l" c% A* }1 Y3
; f4 j# O3 C% m, P4
$ w8 D% ?1 d+ r: L5 O! }! z3 |5. O. \* {& }$ a; Q. V
6
) R. \0 c- Q, r: g9 A5 F4 R" i7
; y9 S) e6 z: `0 U- R/ [) i8
" r* W. e% g9 B, p. h9
: z O" p+ `0 [9 c5 C9 M109 Y" |; W7 B4 \+ D% f$ O4 Z9 N
11
( i: ]) X; T; y: F12
+ ~1 _; Q( ?* e132 c c( G7 Q* f
14
/ Y, d+ d! D1 U1 d M0 u' s15
; E h6 y6 U0 v- h16
; b3 z' r# e8 }17( x# {% w; i4 ?
18) A5 q2 ^& r h
19
- N: e2 m5 @* z8 `2 J20
# F* N8 ~" _" p21( q8 z) M/ R- T
222 ~# N, I; j0 [9 R) [( d
23
- `! {8 H# `( B# `24* n7 @) W0 b- z! h8 X1 H% m3 q, M2 b
25' V4 K$ H, _1 c6 P2 ]
根据固定时间戳调整 bin 的开始:
0 m! r4 Y3 z" ^, L( \8 ?% W" d: T$ fstart, end = '2000-10-01 23:30:00', '2000-10-02 00:30:00'
5 w5 W& o7 k! S- |rng = pd.date_range(start, end, freq='7min')
. D0 n# D+ B4 u( fts = pd.Series(np.arange(len(rng)) * 3, index=rng)
. t6 c4 D% F$ m r nts
( N) X- [1 q8 n& g& @+ z2000-10-01 23:30:00 0
) @) L& e) X0 m( w. ^$ w @: ~2000-10-01 23:37:00 3
- D2 a/ D' Z/ \' W3 Z2000-10-01 23:44:00 60 L1 A! z3 {1 A ? ^. Q& B) u
2000-10-01 23:51:00 9
* \) W; s& `; G( V$ s6 x2000-10-01 23:58:00 12
& |& g4 Q7 l% ]- }$ Y! J, d' E) P2000-10-02 00:05:00 15
) B: u2 B7 B" N! ]2000-10-02 00:12:00 186 F! P z" H. T2 c/ h( t( i
2000-10-02 00:19:00 21
3 ^, g% V3 x! i: u6 P) g2000-10-02 00:26:00 247 n" {3 P' a* T+ Y) B9 `" U1 ]
Freq: 7T, dtype: int64; P# ~+ }* |9 k* D) J. B
. l# i Q1 i) Y# cts.resample('17min').sum()3 H0 j5 C* U& t5 u9 H
2000-10-01 23:14:00 0 H% U* T0 n; \" |0 R, p8 H6 X0 Y4 U
2000-10-01 23:31:00 9
& X1 [& [/ _: k7 ~2000-10-01 23:48:00 21
- C7 R) P- S+ S4 z2000-10-02 00:05:00 54 _! c3 f& }& O
2000-10-02 00:22:00 24
/ w0 _) v5 ^. p! p! @! i$ q! [6 w# CFreq: 17T, dtype: int64
8 a! q5 \# U* w# `3 H$ }
( ]8 B" ^9 c# G! j2 wts.resample('17min', origin='epoch').sum()
: L! X8 Z$ L" e; O1 r2000-10-01 23:18:00 0
6 i2 }: ]4 f, s0 b4 y2000-10-01 23:35:00 18- Q& \7 S$ C- L
2000-10-01 23:52:00 27: i& h/ B* ?" P, q) u. w% D; \# V
2000-10-02 00:09:00 39
: a1 V- L' D5 |, {8 q+ z, _9 E, |2000-10-02 00:26:00 245 U; A: L4 R" B# y7 R, b+ q( @
Freq: 17T, dtype: int64
* B% x: A' Q* q# m) \2 g/ D
# L4 } H; ?9 F7 s& m jts.resample('17min', origin='2000-01-01').sum()' `% k8 C' F* {- G7 P% {' [
2000-10-01 23:24:00 3
/ f' S* k! ^' Q2000-10-01 23:41:00 15, x' f, ?) C3 M. } V1 {& g* R: a
2000-10-01 23:58:00 45
8 A! B0 O! [) h2 J6 E8 L$ p- i2000-10-02 00:15:00 45
) h, z8 @" W1 a; M1 iFreq: 17T, dtype: int649 }' r. J, ~8 q. ?3 n* ^
S b6 \7 n4 v, C* x% N1
- U7 p/ z6 c% b, R2( o7 a, ~" x$ R/ n+ |
3
7 [9 j* T ]/ j4
+ I0 n* O% t7 b5) K* Q# v$ E4 X6 D! {
6* L% A& g7 m1 B& S# I9 v- j
7# ?- _( x" {% v
8( t" d. B; H. u
95 z+ Q$ W1 [# ?6 E- ]( Z" o
107 @, M$ v) J( P: @
11
R' s9 y/ J4 g& T12# a/ W! N* m$ v$ T6 p/ k2 s
136 p7 f* _ K4 K% q$ }' f; z
14: I0 Y9 m/ ]/ ]2 N
15
- Y& I, L. L4 ~* L& D* _16# Q& S6 k5 |2 [. j( ?& B
172 q. Y; {& l& w6 u1 X }
180 q' z( {+ U& c( W
19
8 {3 u. C! j: ^# B202 ]- n; c/ c6 A2 g! [
21
$ f% k& Y6 c9 Q V! H4 {/ `22- Z5 j: u/ X1 J9 r0 }+ o
23
9 G) Y2 F+ m) C- b. H3 h) C244 ^( |. f0 r3 J$ U* I- N6 w& k
25: j1 M7 O: Q/ J- ?5 Y
268 U7 D3 M! ~# K5 A
27
' J6 _2 _* Y* r1 b28
- E7 ^% T) X9 D |3 ^0 G29
( n) {$ ~- t9 b; f( }, C- C30
- I V- G) |+ N3 U; l319 {1 B9 Z ^# p& W
32
' Q( R K; E) l& D: Y; V8 \339 Q1 [" @9 w- f2 X, w4 \6 P
34& @; G# ]! C& z5 Z# y3 a
358 W2 {$ m6 j/ u& v
36
. z* }2 C2 e4 H# v ]4 S37
+ z; z8 p/ S) W/ c7 ]* b2 @; `$ Z如果要使用偏移 Timedelta 调整 bin 的开始,则以下两行是等效的:! Q! e/ D9 o& z5 S1 V6 g" i
ts.resample('17min', origin='start').sum()& Y5 e3 _7 r3 i N8 @
ts.resample('17min', offset='23h30min').sum()
- |4 `) A2 k8 y4 G- E5 ~3 ]" V2000-10-01 23:30:00 9
; ]- S- {" A3 l* R5 x2000-10-01 23:47:00 21$ }# q$ y% p8 l$ N( i6 l- x. J
2000-10-02 00:04:00 54' [0 T' E% i# G$ b& b2 L
2000-10-02 00:21:00 24
7 }, l% n- ~- t9 h1 p% ` c3 gFreq: 17T, dtype: int64! w3 w" r8 U& M2 H B' x/ K
1
! Y/ g/ I7 P0 B: Y) l1 ^5 k/ V0 C0 Q2* T% N5 t8 B3 i$ ^+ y7 o4 u$ e. E
3 B" F- |1 t0 `+ Z$ @* O1 h& ?
4
9 o+ V0 z, G3 N5# L! j9 K5 x) E
6# n& g$ j- F! d( z$ R3 Z
7
2 y4 ~0 z' x4 O: H10.6 练习( a$ `# @4 p* z' o6 M8 \. [- {3 y H
Ex1:太阳辐射数据集
1 a6 U! w( s1 `: w. V* F现有一份关于太阳辐射的数据集:
% q( j' X5 K$ G* h: u5 s# ]/ t& U. T
; K+ R9 o8 w2 A _8 X( m Jdf = pd.read_csv('../data/solar.csv', usecols=['Data','Time','Radiation','Temperature'])
3 x& P; S/ s G( [3 j6 S4 Y- fdf.head(3)4 Z4 x4 |( X# H4 G" o5 G6 x
: a$ _ L2 U/ R" A% v" T
Out[129]:
/ w/ y& M7 Y( ~+ {5 e Data Time Radiation Temperature u8 C1 ^. U, H6 H8 m3 w
0 9/29/2016 12:00:00 AM 23:55:26 1.21 48
) I$ M* Y' x5 D9 L. I# y! W& m1 9/29/2016 12:00:00 AM 23:50:23 1.21 48
% K7 ?0 `/ Q6 {9 J0 G2 9/29/2016 12:00:00 AM 23:45:26 1.23 48
7 \6 F% c/ a$ i" {+ P12 P- K9 [% r4 S% F" B( \/ {: [
2: ?9 t& ~, V7 c/ f
39 i8 _$ l$ a4 c! W3 D$ G
4
9 d ~% h1 ]: ~4 ^# o) U( s5 W5' k$ ~7 g; |! Y: @; N% l, i
6% C* B3 J6 Q* |
7- ^+ f) e1 m2 S2 e, K9 W
8
1 U0 g6 `+ A3 y8 E0 A将Datetime, Time合并为一个时间列Datetime,同时把它作为索引后排序。
/ s# E0 v d( h$ T' q每条记录时间的间隔显然并不一致,请解决如下问题:! j* q0 m) ?& W5 X
找出间隔时间的前三个最大值所对应的三组时间戳。
1 ^7 k/ y4 |5 @+ k7 ]+ ~是否存在一个大致的范围,使得绝大多数的间隔时间都落在这个区间中?如果存在,请对此范围内的样本间隔秒数画出柱状图,设置bins=50。
H5 T& g" b" _5 y- m求如下指标对应的Series:" L8 T1 H% k6 P
温度与辐射量的6小时滑动相关系数/ D7 I( r1 W9 q8 _8 v
以三点、九点、十五点、二十一点为分割,该观测所在时间区间的温度均值序列
! C+ a! p" J- U) t7 E每个观测6小时前的辐射量(一般而言不会恰好取到,此时取最近时间戳对应的辐射量)
- q3 l: O. N. himport numpy as np
7 e; ^5 U9 ]: h: r: z: Iimport pandas as pd! }: U; g V( b9 n' ~0 p# S6 S1 {
1% u: a' Z1 E( G
2
5 o/ e7 g* [4 t" O3 d* d" f9 ~: w将Datetime, Time合并为一个时间列Datetime,同时把它作为索引后排序。* j" t3 T. v! ^
data=pd.to_datetime(df.Data) # 本身是object对象,要先转为时间序列$ k$ N- {2 i2 F4 l# x
times=pd.to_timedelta(df.Time)
- l( ^% g& U" \df.Data=data+times9 ~0 j- S0 V; C# P
del df['Time']. @. }" G' R) L( w8 F7 Q& q
df=df.set_index('Data').sort_index() # 如果写的是set_index(df.Data),那么Data作为索引之外,这个列还另外保留+ y. ^* K3 W! X; i5 u- Q l
df$ K, m; R3 e v2 e6 R" t
Radiation Temperature0 s4 M5 w$ ~" m) F
Data
& ~2 w6 } h) }6 v. X7 y2016-09-01 00:00:08 2.58 51! t+ d- ` v8 E- `4 c4 O
2016-09-01 00:05:10 2.83 51+ w2 i2 o. ~: {; J
2016-09-01 00:20:06 2.16 51
" n- J: ] \' |' q9 K1 V2016-09-01 00:25:05 2.21 51* e3 Y l2 N: R; _. m0 x
2016-09-01 00:30:09 2.25 51" I" ^7 Z0 u7 [3 c
... ... ...
: z6 P Q- u( h# x8 z2016-12-31 23:35:02 1.22 41
: w7 u' k# t r1 v9 g! m% x- m# d2016-12-31 23:40:01 1.21 418 n6 M, I! ^! ]# [; }& K
2016-12-31 23:45:04 1.21 42
& O! Y8 Z( ?! ?3 U/ F: I/ a2016-12-31 23:50:03 1.19 41
' `3 K! m9 r" E. p8 {2016-12-31 23:55:01 1.21 41
8 b3 `% R v/ x2 A# y. }, K
- u5 M" o7 J! F- z( I) l8 i: Q1: f4 D1 O5 z5 @* S1 a0 E
26 {( [2 S) @1 Q2 c9 i2 [
37 W- \2 ?" L ~! x1 j7 \5 Y
4
" d8 V9 V, p* j0 t9 l5$ H$ ], b& [9 B4 p. A7 E* ^
6
" |) E8 i' g3 _" i7& f$ ]) l4 {# p- |0 B
8" l/ z: [9 w4 L8 ?% |6 ]* u" Q
9
; R8 V9 Y' |8 M0 D2 A8 H7 t S10' ^2 g7 U; v) R
11
% D' ?. h1 Y0 h12
5 ?# M' N5 k1 n' R5 [' `13, Q8 u+ B. `3 O" C y; b2 E
146 N; N, v, ~) Y$ X* y
157 T$ c& _: o. G7 l) d" u9 O
16
+ g4 w2 K3 }* j( s171 F9 m# B" O. G1 i& h8 f# W
18
/ ~" H* T; O! u* G4 {19
4 S. k q: g& W- m2 w/ z# m* N每条记录时间的间隔显然并不一致,请解决如下问题:
0 r! Z$ j& s; f# [找出间隔时间的前三个最大值所对应的三组时间戳。3 u J; r8 B, \2 t: W) G
# 第一次做错了,不是找三组时间戳% v' Z+ v% w) J
idxmax3=pd.Series(df.index).diff(1).sort_values(ascending=False).index[:3]* {0 K( G0 L! [$ c( ?0 g
df.reset_index().Data[idxmax3,idxmax3-1]" {9 m r$ p* N
1 d. q& j4 c6 X' q3 k2 D
25923 2016-12-08 11:10:42, b, Z( {- H/ S8 `8 L
24522 2016-12-01 00:00:02
% @" E) T+ O- G* ?- f7417 2016-10-01 00:00:198 F* |# N6 b+ e- S6 d% o. L
Name: Data, dtype: datetime64[ns]3 \; w5 e2 h! Y
1' ]; M" C4 G8 q" C2 y7 n% A, y2 W
2
7 ]! U. I" P* R+ n1 k3' o4 q7 \9 j! R2 ^" D# v1 R: u2 j, c" J
45 G# p& i3 Y4 Z8 W6 V3 g) |
5, U7 y3 @) ]; a* B' M0 f& J' O+ ~
6
: h# z* T1 U& @& x8 t7
+ @$ @ j6 m& i; m8 K s8/ s- c: W; O4 U( O! L( j
idxmax3=pd.Series(df.index).diff(1).sort_values(ascending=False).index[:3]7 u/ T2 K6 B8 Y8 r4 w! e0 r# i
list(zip(df.reset_index().Data[idxmax3],df.reset_index().Data[idxmax3-1]))
6 s4 b+ H! k# v" W
& K; d$ b9 C; S8 x9 s! G! E' T7 N" k1 a[(Timestamp('2016-12-08 11:10:42'), Timestamp('2016-12-05 20:45:53')),
- R, ?/ C& s3 |( l/ G (Timestamp('2016-12-01 00:00:02'), Timestamp('2016-11-29 19:05:02')),
* A% f' N7 u, F+ q: ~ (Timestamp('2016-10-01 00:00:19'), Timestamp('2016-09-29 23:55:26'))]
; O" G6 u) I; z% @! J/ p1
7 `8 j3 } H8 J0 v: Q2
6 o( q% V( Q6 S0 T: E1 }, X( N39 }; S, [: F! G( G& ]0 T
4
# ]& U2 W4 \# y2 {& ]5
0 q* U; w* I# m- I- m" _/ U. ]! g6, g5 o$ a8 |; _+ w$ G7 n; \" x
参考答案:$ G' b7 q& R0 V9 j/ t! N
. m, V8 Y7 w' l5 `6 @
s = df.index.to_series().reset_index(drop=True).diff().dt.total_seconds()
- ?, z+ P; n4 F5 ~8 e/ d& lmax_3 = s.nlargest(3).index
7 y# e2 U! v- U+ ^* R4 x: O6 adf.index[max_3.union(max_3-1)]
3 F y" b1 n7 x4 G) {1 u1 d; O% P8 {7 Y9 ^- P
Out[215]:
) w5 {8 y2 v/ z4 N9 F m% BDatetimeIndex(['2016-09-29 23:55:26', '2016-10-01 00:00:19',( K7 C& {$ Y1 s/ p3 x
'2016-11-29 19:05:02', '2016-12-01 00:00:02',
+ v0 v" s# |* q. M! V* { '2016-12-05 20:45:53', '2016-12-08 11:10:42'],# E# S0 N: N# A M! K+ h
dtype='datetime64[ns]', name='Datetime', freq=None)6 Y* J! B/ Q1 g) W
1: v7 ~+ y- ~1 Z% B9 k+ s
2
8 t" P) V1 G1 R, |) ]3
" o, P( @9 K# y+ q9 F42 x% W; H- r0 z5 T9 v
5
- R: q# d& m! a% e1 Q6
& V! W- B5 Y H& o* w% Y# F* ?7
- B+ |3 q, l0 f- n, M7 ~8+ r) D" l& {1 b; g1 b
9
. r C0 X0 s9 e/ o3 W" E6 a是否存在一个大致的范围,使得绝大多数的间隔时间都落在这个区间中?如果存在,请对此范围内的样本间隔秒数画出柱状图,设置bins=50。
$ N1 {0 p% A# y0 v! M/ b# 将df的indexydiff做差,转为秒数后排序。再求几个分位数确定取值区间2 k4 Z( N# \' Y% ^5 E; w8 S
s=pd.Series(df.index).diff(1).dt.total_seconds().sort_values(ascending=False)
$ G" U4 H6 B! G L. O6 X0 ?s.quantile(0.9),s.quantile(0.95),s.quantile(0.99),s.quantile(0.01),s.quantile(0.03),s.quantile(0.05)
: A/ n) y7 {" i9 s* N# s3 w0 c
/ X/ m( L' I6 A(304.0, 309.0, 337.15999999999985, 285.0, 290.0, 292.0)% K% c. L" a' w% Z0 g% O
1" [) ~+ D7 E d
2
7 a6 S2 Z# |0 {3; x' B) a% ~$ r& ?( U; s* M
4. x7 k8 I; a7 Y+ r$ W l
50 k8 Z N' l) `1 {0 n1 m/ Q v* ]0 K$ H
%pylab inline* a# `7 T, D" S8 E9 L! U3 U
_ = plt.hist(ss[(s.values<337)&(s.values>285)],bins=50)
6 S/ w& N% L p. Fplt.xlabel(' Timedelta')
! U" q( W" C7 n b' ~plt.title(" Timedelta of solar")
% L, b T( l/ [8 X* X1
5 Z9 I7 |# n8 j* `2
# ]8 s) n; r' l2 K+ C0 n. |. g4 E39 k9 I. s/ B- [3 l8 K! L" K
48 e q& l. Q9 \( }( A
+ M- Z* k e# _+ P3 p6 W% p
- s9 u8 C# W i% S) r. f" u
求如下指标对应的Series:( G0 L- e# Q0 s. v c& A' Z" X
温度与辐射量的6小时滑动相关系数
5 Y2 ]. ^6 D* Y& L' m以三点、九点、十五点、二十一点为分割,该观测所在时间区间的温度均值序列7 g( a7 t' ]8 ]
每个观测6小时前的辐射量(一般而言不会恰好取到,此时取最近时间戳对应的辐射量)
9 \9 c( g! F6 E: L; zdf.Radiation.rolling('6H').corr(df.Temperature).tail()
^: Y0 S! J$ R& y, c0 B7 W# H( s8 z$ \) f* N1 t- G i+ m
Data# l# J/ ^* i1 ^
2016-12-31 23:35:02 0.416187
3 n4 b+ A$ O" D2016-12-31 23:40:01 0.416565
$ N L O0 ]9 q( g2 V2016-12-31 23:45:04 0.328574
) |& Y, a) X+ T9 t! B2016-12-31 23:50:03 0.261883
; ?! h1 }8 Q( |2016-12-31 23:55:01 0.262406
8 v; u& `' M7 q) t0 b2 Edtype: float64+ A* {( u+ {6 _4 F# i& g3 u
1$ K# h* T4 F7 ^3 \0 W5 g
2
( b* }! f& P: Y6 E$ k) H0 r3: b+ w3 J. o( H' d2 h
4
& d0 \. x) `# G: J59 I4 @2 L; `( n& u
6
6 n6 X8 N* e) U7
$ ~" ` H& a1 `7 Y- r9 Q, g( E/ ]8
1 t2 S* E7 v1 B3 `& V& f9
8 B0 o; L* _6 h7 |' zdf['Temperature'].resample('6H',offset='3H').mean().head() a* H8 x5 X1 h) W
2 d; k: l- Y3 ?" |) _9 a
Data
, \2 E6 f& Y6 A, n% u2016-08-31 21:00:00 51.218750# V1 C. _) U& A
2016-09-01 03:00:00 50.033333. k& r9 W" N, V, q+ K
2016-09-01 09:00:00 59.379310& [- m8 a! R& x7 g+ s
2016-09-01 15:00:00 57.984375. u. X9 _3 v8 Q1 j, v+ F$ G
2016-09-01 21:00:00 51.393939
; g- Y0 O- y1 [8 Q0 c0 u# _# XFreq: 6H, Name: Temperature, dtype: float648 M; t, x1 V7 W$ ~; T& c: P" _; A- L
1
6 G. X) I1 u& u( t, q2
- r1 `9 q" Z5 N% y3% u' p) l0 W3 s% w, @5 T) T; l
4
" p, x E3 ^% s; y5& `3 [ ?2 D, ]% L- i r
6% p* P* k0 F! A/ M( X. [1 @
7
5 e+ X2 `( V O( {8- C% K* x1 Y& \' p7 H
9$ I* g ~/ ]1 d8 c% }3 k4 I+ g+ `
最后一题参考答案:
" n' T' e$ `, i5 s& P! R" V6 l/ k$ g& P& W, T
# 非常慢: H! s, }& M8 l2 m
my_dt = df.index.shift(freq='-6H')
7 h3 \4 L% j' a0 S3 g% x4 o9 gint_loc = [df.index.get_indexer([i], method='nearest') for i in my_dt]
# Z9 W$ G7 T2 }$ qint_loc = np.array(int_loc).reshape(-1)4 f6 @) H8 ^0 o
res = df.Radiation.iloc[int_loc]
! q; S9 `' s4 K. H. p0 e9 ?res.index = df.index$ L$ P& w# E0 q1 {8 V7 k1 ]+ Y
res.tail(3)
2 s+ N( e. z3 ^4 H1! {9 W3 W% M4 p3 O# I5 ?+ E
2+ c& O4 u' U: r( B
3
# y, @ P+ n7 U, R4
9 r6 s c2 M, _8 s( z- o4 B7 o5$ u. _$ D5 C# K* ~7 q% k
63 }% I4 O. c& E! M5 q! V2 l) U
7) x% i1 {* L* y- f% l
# 纸质版上介绍了merge_asof,性能差距可以达到3-4个数量级
; Y3 ^0 F# G. |0 Btarget = pd.DataFrame(5 R$ N" R" o/ U! T2 [
{% T: ~' N+ O* t; m) O
"Time": df.index.shift(freq='-6H'),& K" q% [7 o/ r1 Z/ ^
"Datetime": df.index,
$ X _( m' n* X4 f' o0 x8 a0 t }9 f2 B) V, e/ |; J
)8 ]3 @9 Q3 Y' Z. | t h7 B2 ]2 E. r
, Y2 }: e0 J) h* X& f/ g
res = pd.merge_asof(
/ p) n0 E5 a: t# X/ I target,
; M/ t1 a, f- [7 f# x/ a df.reset_index().rename(columns={"Datetime": "Time"}),
& I6 u- G& y t/ a& ` left_on="Time",: b5 P/ h m( N8 [
right_on="Time",) Q. R; z7 a6 Q
direction="nearest"# M* \! J7 F8 Q; ~' K
).set_index("Datetime").Radiation9 \) m3 V( Y" e, [
$ z+ f \2 W1 Q- V" O8 g
res.tail(3)
8 D7 g" e/ T2 VOut[224]:
. [0 N. i5 h1 z4 N JDatetime; I: S2 i4 W/ J9 K6 Q6 L: I4 R! E
2016-12-31 23:45:04 9.33
; ~: J1 J5 }/ h5 q, f2016-12-31 23:50:03 8.49
) _ a6 ?5 ~' T8 o9 `' s* L. V2016-12-31 23:55:01 5.84+ e9 i5 p: @: G5 L1 w7 R
Name: Radiation, dtype: float64
5 |- o! K0 K) J( l# p& g8 o; |/ R. p; Q* i2 ~
12 u. Y/ Q, @/ a
2 k" q, C9 C. v: }/ M. R: Y4 P6 |
32 V: r# _& c7 K, B. R7 s
4+ h0 X! u8 U: w8 V0 h/ H1 a
5
! S0 L% ?/ }6 F( p+ O* m6$ i8 [6 u4 r; h) B$ [0 B6 t" E5 ^
7
1 n, Y: N- ~; H! F, [ e8
2 X1 X! w4 Z4 t1 L- x1 D0 X9' ?' W3 i/ t4 {
10
' V' C+ e' Y2 T$ _3 ^11" |- b; T2 ^' H3 T1 J' C
12
, c8 D: W, P+ Q V) ~5 n: w3 O13
$ I4 l4 x9 C @$ N/ T146 g6 E8 ]; r: ?7 Y! b/ _
15
. E1 X8 {$ k3 e) N- m" f6 R16
+ d% M3 v8 w% O& Q a17) N4 I. D& A" o; d* J. k
18
0 G" I( L) ^! [# u4 n. x19
" H5 O0 B: a! M! L20
% P$ _: o6 ]0 T! C- q21
$ m1 D" [4 g% | l22: j: j- a0 M1 g( a" H% {& v
23
$ u7 o$ [ n+ _/ n0 oEx2:水果销量数据集
+ g/ G% a* a3 N; y' V- B现有一份2019年每日水果销量记录表:. a' p0 w o7 j/ J
: u- c7 c% L5 l% {/ t
df = pd.read_csv('../data/fruit.csv')8 I, j; Y6 N2 h9 r" Z* a. j
df.head(3)
0 ^# _: |! s2 I' r( {1 _$ l" E; ]
n! G8 q9 p5 L2 z' vOut[131]: $ h8 y( D% T, k0 ?% m D
Date Fruit Sale7 A2 Q% C3 h! s& ~' x I- ?
0 2019-04-18 Peach 15 o' c/ H! L8 O7 F/ a8 i
1 2019-12-29 Peach 159 L! }4 t: n* n3 L# K% g
2 2019-06-05 Peach 19
7 ?5 a8 P* v% }* @1! j# I/ i# d: S5 z; F9 K6 t2 o
20 B0 {8 k3 G9 L# t$ a3 E" U D
3
3 y4 `4 _( N; ^: B4
% i% g7 h; Y1 t4 |5
! V# ^( [: }+ k* b& s6
% @/ a: r4 a" e" D+ N7 ]7. \# u0 l V* N2 P
8# ]; e; H9 O0 m
统计如下指标:# R; M2 _: O7 ?1 _1 r+ M B
每月上半月(15号及之前)与下半月葡萄销量的比值# ]( B4 I& I1 h7 H' V' c
每月最后一天的生梨销量总和& I; D/ c7 {& m0 C8 a* b
每月最后一天工作日的生梨销量总和
* p( g! h p+ O* v) i" }) P每月最后五天的苹果销量均值. R4 ~5 T2 |/ }& ]' g8 \
按月计算周一至周日各品种水果的平均记录条数,行索引外层为水果名称,内层为月份,列索引为星期。, k; x1 M2 U& B0 W% L# M' p- t4 s- }; a
按天计算向前10个工作日窗口的苹果销量均值序列,非工作日的值用上一个工作日的结果填充。
$ Q n% c5 P5 Uimport numpy as np c+ z. ^, a3 w& I! ~
import pandas as pd
& g+ Z. l4 k: B, s0 \1) P8 ]4 P% R. y$ t! x) D% O0 n2 `
2
1 ] M( E$ q& w* ~统计如下指标:
7 O: d/ b% e* c7 i每月上半月(15号及之前)与下半月葡萄销量的比值- i& }/ ~5 q. Y0 j; L w7 ?
每月最后一天的生梨销量总和- F6 ?/ ^1 p5 M, a B6 d+ i
每月最后一天工作日的生梨销量总和
, `1 i5 ^. l- ]每月最后五天的苹果销量均值; b% N. m2 _% {: R3 ]- `/ a
# 每月上半月(15号及之前)与下半月葡萄销量的比值
/ G1 L8 E+ \5 }, x/ F9 \df.Date=pd.to_datetime(df.Date)
: |- W2 ^, `4 z+ m0 z% F& fsale=df.query('Fruit == "Grape"').groupby([df.Date.dt.month,df.Date.dt.day<=15])['Sale'].sum()
" y3 M' o' X3 \3 V! y0 w0 C: Zsale.columns=['Month','15Dayes','Sale'] # 为啥这么改没用啊
8 b y7 y0 b5 s% ~+ Msale=pd.DataFrame(sale), ?0 g+ s" V( }
sale=sale.unstack(1).rename_axis(index={'Date':'Month'},
/ K& K# t" `6 k: e$ Y& h# j9 l columns={'Date':'15Days'}).stack(1).reset_index() # unstack主要是两个索引都是Date无法直接重命名- ^* [, C- y; |9 \% X4 v
sale.head() # 每个月上下半月的销量: x( V3 Y5 F) |) Y/ u
( V" h7 r1 `1 H! j1 X Month 15Days Sale$ k* ` z V6 |2 j- b$ |
0 1 False 10503$ m+ h# N4 m1 i% h' V7 E4 G5 L
1 1 True 12341
1 L9 V% u9 U4 U9 Y9 S% n2 2 False 10001
; C0 I* r& A2 g* x3 2 True 10106
1 x& m- i2 E- W( ~- D4 3 False 128146 W- {, k6 s- w$ Q
6 ~( A5 @' ~& Y$ a" _( y |4 a# 使用自定义聚合函数,分组后每组就上半月和下半月两个值,根据索引位置判断求比值时的分子分母顺序& Y/ |# R( X+ M! p! x
sale.groupby(sale['Month'])['Sale'].agg(
# ]: o( g+ |' j0 A- [6 h8 Z! e lambda x: x.max()/x.min() if x.idxmax()>x.idxmin() else x.min()/x.max())7 n# n1 F: Z, V/ `
# z8 l6 g5 s. ?" G' u' f
Month
+ Q+ j4 l3 C! X6 ?1 1.174998
5 L; h4 L6 k: _2 1.010499
" [6 S# V. G/ ~, D2 Y$ I3 0.776338
! O( q+ }9 P5 a4 1.0263458 u. \0 d7 L" q2 V
5 0.900534
: [6 b) ]3 T, b7 E* w' p6 0.9801368 E0 ~! f% _1 j
7 1.3509603 P8 `; A k# J& A" E0 J
8 1.091584
* H6 v& U* N9 ]7 \9 1.116508- l2 D5 d \" F
10 1.020784& T" l$ t3 ?: Z9 P
11 1.275911
4 E" p8 o* N2 M1 _8 |, _12 0.989662+ |. i% J O' _+ `7 N) ?- x5 K( }
Name: Sale, dtype: float64
& `" @4 V+ S+ K( v: f3 b
, _9 s4 k/ z0 k1 P) u1
9 V6 Q- \. _& ~+ O5 E2
/ l0 ^. _: e1 e6 F3
; ]$ p5 J( z9 F. B1 }4
$ M: f+ ]+ L" |( M1 d' B# d( O5
6 I# J6 w: ~# a9 v3 ]% u6
5 n P1 s# E4 G- F, o7, J* I" F/ B' M3 ^# d, k8 g$ X
8& \' Q. Y1 s" ?+ y2 W' h
9
2 \ ]% j5 ^/ I/ M: M+ }10
6 n9 w$ Q I6 l' n9 t5 s11
: Z+ o! z: R, C12
+ a! X, M/ ?" U) c% e8 O, {8 I13. ^- U. z3 L# B7 B/ ~7 r/ w& y
14
. s1 @* s: k+ u( @8 x- h3 D159 h5 d, Y- u4 z+ \4 p7 [
16
8 s( o8 p/ Z* }8 u17; _ O, Q& [: J& O
18
; E H7 v6 z- y J6 D4 P* T: U& g19! _& A8 P+ X, T2 c, k% w6 n8 L
20
" V1 R$ M2 r: b8 |, ?( a" v217 l- h' x6 ]. s( |1 P
22; w. g4 M6 A( k' b
23! x8 f, F1 Z* m. g7 ^- x" R) T
24
. d0 n& K/ _7 p/ C9 b25% N1 v) G4 n( E+ P4 j) t. N
26' U7 b8 s" }6 v$ s' \9 r
279 ~4 j9 h; f* ]3 y+ B* l2 B8 M+ z
285 ]" o$ N* O, i
29# Q+ x( w7 O, w0 O6 ?
30% ]/ V$ E: Z0 i, h) f
31
+ u$ w4 U0 h# _" x- ^( m# }32( X4 l+ q4 { p+ h: E& _
33& a4 j' k& z* P' }
34
& p" U: F H$ T# n8 q# 每月最后一天的生梨销量总和+ ?1 c. o4 A9 f2 O. N% N' h
df[df.Date.dt.is_month_end].loc[df.Fruit=='Pear'].groupby('Date')['Sale'].sum() V: B w; H8 L6 z8 ?0 c
8 f! r" o- d) s7 L" l# DDate) A: P! C. V6 ?" M! Z. N
2019-01-31 847
' d0 ~( [, W; n) J3 c; Z2019-02-28 774
7 q4 P( p$ L4 ^6 c+ a2019-03-31 7617 j( g+ f* ~* w2 e* ?/ o8 o2 K# q
2019-04-30 6484 v& L% B, B& r/ G5 Q M9 z
2019-05-31 6163 `. E2 ~1 n* A* a3 ?3 w
14 K2 }" j3 L6 j: `; d C: b% a" l+ x! r
20 r3 r; J* R: _' ~8 v, R; h9 _
36 }1 T" S! \( |- s4 G4 K
49 R* t5 A: \' n8 S
5
% j3 G. W8 B+ @6 c1 }9 I/ ^; t) n+ ?9 B
7
. w1 M/ h8 W; T e8& \+ x) j& R8 ~2 f" Z" f9 ^
9
- K2 E/ M8 H! V. S0 Q0 X# 每月最后一天工作日的生梨销量总和$ ~5 [# S0 H: S2 U% j9 d0 e
ls=df.Date+pd.offsets.BMonthEnd()5 C1 K- E( z$ [: o9 M. b3 y
my_filter=pd.to_datetime(ls.unique())
0 K- |" s4 p/ f1 K/ Gdf[df.Date.isin(my_filter)].loc[df.Fruit=='Pear'].groupby('Date')['Sale'].sum()& M1 Y% X2 ~0 [; {2 y
. C& S; P! t5 P/ |2 C4 Y
Date' p/ F9 g. m2 @7 u: P, F7 i/ O
2019-01-31 847
; y+ a4 ?6 t8 m5 j0 z( S1 s, X2019-02-28 774
# Z9 P5 I5 _' k7 Q% X2019-03-29 510
& u( f) k+ `$ z8 L% o3 S2019-04-30 648
! L8 Y% f) v6 f h7 Q0 m2019-05-31 616
* Y% F" }: F& x4 y( A1) z6 l( K# ?% M+ |4 J
2
1 J* Q2 o @& U37 D1 D9 H4 u* y' ~. ]
4( j1 R X( D( [0 l8 E8 u
5
3 h5 k0 X: B5 s3 Z6
! T, N( v8 W4 `# J4 g. G7: d" b0 ^, O. h% b# v t
86 G6 P$ {: s4 `9 @; C
91 d" L* [( a" F' R* z
10, H \# ^& f+ y' F' v d3 x/ |- x
116 g2 u) t+ p3 O A' C! D/ @: t: U
# 每月最后五天的苹果销量均值3 e4 L0 Z# T, t" e. e
start, end = '2019-01-01', '2019-12-31'. a$ t ]8 w2 U/ G, [
end = pd.date_range(start, end, freq='M')
% I/ R, X8 Z9 rend=end.repeat(5) # 每月最后一天的日期列表,重复5次方便做差
. F% `. m, F' S% s o# a# [" M+ q' s$ p: ~9 e5 L! E) X9 W
td= pd.Series(pd.timedelta_range(start='0 days', periods=5),)* Y4 s5 K" i% `+ d
td=pd.concat([td]*12) # 日期偏置,最后一天减去0-4天0 x9 b) \8 K+ l% H
end5=(end-td).reset_index(drop=True) # 每个月最后5天的列表( b$ @( q& e7 |
' @. n. N9 E. }% Y' k; P, P
apple5=df[df.Date.isin(end5)].query("Fruit == 'Apple'") # 每月最后五天苹果销量
& ?$ Q' Q: u3 Q5 e* ?apple5.groupby(apple5.Date.dt.month)['Sale'].mean().head()
$ J H6 ]- o' L
2 o" F& H; X7 F# b1 FDate: N8 Z; C" N4 X; {0 _
1 65.313725
/ y8 V: E; K6 X; L2 54.061538; i# i2 c) D, a5 @) w
3 59.325581) Q4 O/ R, V: }# m, ?$ X3 W
4 65.795455
# K8 j M5 O8 C6 _$ L5 57.465116
0 ]1 n) B5 \/ W' ?$ L- A" \" r ^/ B6 v$ P# N# g, ]+ Q0 D, D; f8 w
1
; u+ {/ N& |3 K( ]( c3 |4 D2$ w% O/ z8 w+ u W4 _
3
. {7 a c' @: p47 S* S3 d; k% ]; L
5- `; y% D0 D! g
6; R }7 q% G/ q6 j1 |
7' }6 O+ m* _3 G
8
* c/ k% D! P: h; ]0 f/ T0 A0 w& [ l9
4 a/ x' Y3 L2 O$ e. t10
; h# w6 y7 w- f* c11
5 w) z% v( ~* T* E: u12 p: j0 ~) |: H9 B
13: C: h8 p3 y5 H
14
* T6 b; e4 _ j. y% t0 }15! x' x" X" o K
16$ S4 X0 k- ~. K, { ^
17! D6 W1 p x7 y, f1 P3 v5 D$ l& e& i
18' N6 `/ g% V6 ?8 P# z
# 参考答案: h8 p, C8 C: U y
target_dt = df.drop_duplicates().groupby(df.Date.drop_duplicates(: y& f4 D1 T$ O* o9 q6 w. O
).dt.month)['Date'].nlargest(5).reset_index(drop=True)$ c/ S4 n/ u; B+ z2 {
4 F1 f l. D' fres = df.set_index('Date').loc[target_dt].reset_index(% N# U1 }, d4 P# S
).query("Fruit == 'Apple'"). g6 G+ b3 ?+ x3 N7 d7 @- {7 V
( }2 q+ @" W. B: v! Sres = res.groupby(res.Date.dt.month)['Sale'].mean(
; l" b# n v: Y& b0 ^ ).rename_axis('Month')
; s H. \. k9 K
( M6 @: _. J& H4 B0 a0 f# [! \9 k( a' }; V S3 `- m
res.head(). y7 c4 c, |. R
Out[236]: 5 m; t7 ^, E6 E0 T# l9 D3 g
Month8 [( r5 ], n" O
1 65.313725* A' A5 q2 k! @. j* ]) |$ B
2 54.061538
$ Z2 s) j% u- p3 J* l8 B3 59.325581
% y; V. r7 _) j" y9 l& j; u4 65.795455
# H- q2 c* s, B! X+ _- L; k5 57.465116
4 {8 M1 J% d9 ~) H t: U7 kName: Sale, dtype: float64
: ~# f$ E5 t w- c% [9 u, i/ C: d8 R4 B/ ^
1
/ ^7 }8 [7 p8 s! U2
6 }3 `) c- p2 r. A3
" H5 d! u0 M. X% M h w7 n4
. H) P: k' O; t1 Y4 F0 z5
* U% F/ q! V e! ?" f3 _ ^6" S/ `; v2 a A: d, ~3 ~7 ^
7% S7 K) w( }7 F1 H% B# D8 {9 t
83 V9 C5 @2 b* x+ I- Z
9; n0 D: `+ m# ` E5 `; Q
10
0 Q9 H. u! O0 P3 i. W11
$ n/ e9 q2 k0 d& e F9 `126 Z' g( ]2 K# i2 B$ ~
13
- P. y3 J6 P8 x+ L. X( S14
9 r+ c b& V* a4 {6 E* e15
% [2 Q- w! v. A3 O5 C. m- ?16+ q1 w9 P8 P: I7 D
17
" v. w* v. F; e6 D9 P18& }6 b2 `4 \9 p4 |5 C" \
19
$ L, U; W; o; b+ Y& p; b20/ K7 ?" m8 `* f" ~
按月计算周一至周日各品种水果的平均记录条数,行索引外层为水果名称,内层为月份,列索引为星期。* X' {& u+ p1 t/ I4 j/ V
result=pd.DataFrame(df.groupby([df.Date.dt.month,df.Date.+ ^, W1 X$ ?9 f9 q+ x
dt.dayofweek,df.Fruit])['Sale'].count()) # 分组统计 ) |9 o M Q# m! i; Y3 L) k
/ v( ^0 M1 K& U7 K1 Tresult=result.unstack(1).rename_axis(index={'Date':'Month'},
4 |1 o4 @3 }; g, U# s columns={'Date':'Week'}) # 两个index名字都是Date,只能转一个到列,分开来改名字.9 L" O6 e8 ~) Y; K5 k: M4 w4 ~8 S
result=result.swaplevel(0,1,axis=0).droplevel(0,axis=1)
- ?# D( c9 ?0 A( F- r1 _; j( {result.head() # 索引名有空再改吧" r& p! }4 f5 s" ^! {
% V7 l8 w$ M9 z
Week 0 1 2 3 4 5 6; W4 F, T; x/ s" P
Fruit Month ' L- L4 B+ H B s: E, i* i* J
Apple 1 46 50 50 45 32 42 23# g0 l' P, b4 ]5 L) x8 V$ u: M3 G
Banana 1 27 29 24 42 36 24 35
' f" y' F# @" y e9 V, e' zGrape 1 42 75 53 63 36 57 46
. B0 Y% k2 Y. Q! A( ~Peach 1 67 78 73 88 59 49 72- b6 \+ X: T" Y, d7 K' J8 D
Pear 1 39 69 51 54 48 36 405 v d, R8 Y; k1 w' ^
1. u# ]* e( h' z; Q9 T8 k0 t$ S: p
2
; G6 Z- U& I4 `, `' g; H3
3 ?7 G; I' B% U d& Q0 v u0 t4' o/ M' W, s0 D
59 K* K6 b% }0 `6 C; |
6
( R. i4 n$ }' d& U$ h7
m/ T* i' N; V8
6 H% O3 C! U4 I4 Q7 s9
2 b F# I" A4 \106 a9 [2 u9 B" l' Y, B( p
11* \/ G/ y, t$ C" O% v6 @( r" ^
12- X2 m9 k( y$ n9 W t$ e7 }( q
13
0 N. ~, o' |% x8 U8 t0 v8 L14
/ J# ~# N7 Q% O2 ^0 I( X15' V6 B: B, K& _& V. f7 @# j8 h: a
按天计算向前10个工作日窗口的苹果销量均值序列,非工作日的值用上一个工作日的结果填充。
* a6 @1 q$ N1 a; y- @# 工作日苹果销量按日期排序: H% h. G9 F& @7 s- Y2 e7 W8 H
select_bday=df[~df.Date.dt.dayofweek.isin([5,6])].query('Fruit=="Apple"').set_index('Date').sort_index()' T+ ~; z% H9 T8 @
select_bday=select_bday.groupby(select_bday.index)['Sale'].sum() # 每天的销量汇总9 u7 k* z& ]/ ^$ H
select_bday.head()4 k% I0 n1 v5 h7 y# H3 \5 ?- y( |
$ b7 j6 f, J7 w! ?6 s3 K7 i, z6 SDate
9 E6 C3 K. G; D; q; G9 r2 O8 d( w5 f2019-01-01 189
3 w0 L- [$ u$ v( H6 {/ [2019-01-02 4826 ?- k i1 k+ g9 E+ T% h
2019-01-03 8906 k, J( ^& }* o: U
2019-01-04 5502 D% u! e3 @! K7 s/ O
2019-01-07 494$ J6 n! z: v, _ p" M1 c
: k' s" V, a) M" L% P# 此时已经是工作日,正常滑窗。结果重设索引,对周末进行向后填充。
5 K, ^/ _ ]; T$ Vselect_bday.rolling('10D').mean().reindex(df.Date.unique()).sort_index().ffill().head()
3 X6 R2 P5 j% ?7 m6 X' |( |- i/ @0 C: v$ k$ t; p7 ?2 m
Date! i7 n1 d. g. L# O7 N2 w; m
2019-01-01 189.000000
- c* H8 T* _3 x% \& k: z2019-01-02 335.500000
9 T8 w* ?/ L2 h: W n2019-01-03 520.333333
: N: Y0 c- t; L F2019-01-04 527.750000$ |# ?" R; {8 v! O
2019-01-05 527.750000+ j: m) S$ b$ S9 y v& B
4 [: t" W& M$ C. P% B& M
————————————————
8 s% n7 y1 O! b8 X版权声明:本文为CSDN博主「神洛华」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
2 Z7 i1 k k) u2 ^% J原文链接:https://blog.csdn.net/qq_56591814/article/details/1266339134 k5 R6 y6 @, K- g
1 D3 [. S+ K. v% i! r, t
. I! ?4 O7 ~5 C ~9 N8 L
|
zan
|