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