- 在线时间
- 1630 小时
- 最后登录
- 2024-1-29
- 注册时间
- 2017-5-16
- 听众数
- 82
- 收听数
- 1
- 能力
- 120 分
- 体力
- 558153 点
- 威望
- 12 点
- 阅读权限
- 255
- 积分
- 172819
- 相册
- 1
- 日志
- 0
- 记录
- 0
- 帖子
- 5313
- 主题
- 5273
- 精华
- 18
- 分享
- 0
- 好友
- 163
TA的每日心情 | 开心 2021-8-11 17:59 |
---|
签到天数: 17 天 [LV.4]偶尔看看III 网络挑战赛参赛者 网络挑战赛参赛者 - 自我介绍
- 本人女,毕业于内蒙古科技大学,担任文职专业,毕业专业英语。
 群组: 2018美赛大象算法课程 群组: 2018美赛护航培训课程 群组: 2019年 数学中国站长建 群组: 2019年数据分析师课程 群组: 2018年大象老师国赛优 |
% F6 [' {. `% ?/ n
9 X: X% `% F( d2 I3 R6 F2 n5 U, x+ e( Y1 s4 I7 D+ Z4 U
文章目录
, {0 F2 v7 m; B, `0 d+ O第八章 文本数据+ ] V7 j! C; w3 G' }3 P( V
8.1 str对象
8 R, O6 I- l1 i( |$ X( \8.1.1 str对象的设计意图; S$ q( p4 k& [9 \& Y
8.1.3 string类型
7 A6 X2 F& a! y+ J- h6 f8.2 正则表达式基础
% U! x. c* v. R. y. T, [8.2.1 . 一般字符的匹配" W, {* Z' p& e# e/ S
8.2.2 元字符基础
8 Q9 K# p2 k2 p% h( x7 ~8.2.3 简写字符集7 K0 B2 p. I3 ?3 {; H, C
8.3 文本处理的五类操作9 B0 d9 z$ v' X" z5 I
8.3.1 `str.split `拆分5 U8 u$ f: g$ V
8.3.2 `str.join` 或 `str.cat `合并) O& a/ w* v. V1 R
8.3.3 匹配; e$ l4 B. }+ b8 s' Q7 p5 \9 q) F
8.3.5 提取4 u7 G5 n* k, P: `/ W# b' a
8.4、常用字符串函数% z4 t6 E& ?( | d
8.4.1 字母型函数
/ H3 @ |# `, i& r! `8.4.2 数值型函数3 g5 F! a c5 p4 t2 y# |* P
8.4.3 统计型函数# K8 S+ K% I1 {: A
8.4.4 格式型函数9 A3 X- J, U$ a
8.5 练习% j& X# B [2 O f
Ex1:房屋信息数据集 ~+ V [; a; K' {
Ex2:《权力的游戏》剧本数据集$ X+ G, n+ u: L# @6 y# H5 ?
第九章 分类数据
( j1 i2 {- S, G' F' n% H9.1 cat对象! P3 t. R* X6 F# k8 U
9.1.1 cat对象的属性
, @6 W, r$ ~8 {# r ?9.1.2 类别的增加、删除和修改
1 W" B) m8 V$ u$ }( `9.2 有序分类6 u+ ]1 X' z# A* u4 c0 q* D& ?
9.2.1 序的建立; u: }1 y4 L( {7 E6 I7 g# a' F
9.2.2 排序和比较% E: u+ a5 l- H, W0 B6 B! x- ~
9.3 区间类别( x3 t$ t2 u8 ]5 M) `6 C; J. @
9.3.1 利用cut和qcut进行区间构造+ j; n0 H6 D: t! ?& {4 \' q( K! J
9.3.2 一般区间的构造) N8 c; z3 J) q+ \- N! j" I; m
9.3.3 区间的属性与方法
, \& c" T, E& j$ C+ y9.4 练习4 r S- H9 B, q$ h$ [4 a* C
Ex1: 统计未出现的类别
6 g+ i' c6 C( L$ F' u* |# p* cEx2: 钻石数据集& Q1 ]" @' A* F! R$ p8 C5 d5 N
第十章 时序数据
: R; z+ b" i! b8 F$ B1 C10.1 时序中的基本对象
; y# K# K7 d$ o10.2 时间戳+ a3 ~8 T+ v7 d8 }" G
10.2.1 Timestamp的构造与属性
: G* L! ^/ D; s( I10.2.2 Datetime序列的生成6 f+ r8 l9 A6 S% q
10.2.3 dt对象
' I% y2 |8 S2 h# F6 X6 q8 a10.2.4 时间戳的切片与索引* _. E8 K X; X' b/ |1 c0 ]! I+ {
10.3 时间差
- L; M' ?) h C$ J# K7 q10.3.1 Timedelta的生成; @6 ^% E0 I- b
10.2.2 Timedelta的运算
# G7 b8 q. r, m3 W/ |: N1 ]10.4 日期偏置8 L8 \9 e2 s" X% Y: ]
10.4.1 Offset对象3 c2 W( n, Z: C" C: X
10.4.2 偏置字符串1 n, |8 g% D" I5 u
10.5、时序中的滑窗与分组
( c# @, T A, T/ G# f' m( l10.5.1 滑动窗口
- g( q3 A+ b/ T3 @1 t* J. T10.5.2 重采样
$ J) R1 Z! N2 u% t* ?10.6 练习- N/ U) Q' a Q9 P
Ex1:太阳辐射数据集8 t% P: M% x5 P0 L" l6 N, u7 B! L% N
Ex2:水果销量数据集0 h9 J5 ]; q1 R7 f+ l/ G" x2 O
课程资料《pandas数据处理与分析》、github地址、讲解视频、习题参考答案 、pandas官网7 m) Y* o7 J; ?2 x
传送门:$ e, }, G2 w; D" @0 B& w
& K$ L/ V7 o' ^& }8 cdatawhale8月组队学习《pandas数据处理与分析》(上)(基础、索引、分组)
1 P2 [- J" [4 ]6 kdatawhale8月组队学习《pandas数据处理与分析》(中)(变形、连接、缺失数据)
" e# ~7 s! Q- G. `" q* X4 Y第八章 文本数据
& f( t: v' C, J. f8.1 str对象! J( z$ ~7 y$ t4 M% ?* w, J" G1 x) a
8.1.1 str对象的设计意图
* C8 H1 O9 a3 S8 c. `& w2 {5 c str 对象是定义在 Index 或 Series上的属性,专门用于处理每个元素的文本内容,其内部定义了大量方法,因此对一个序列进行文本处理,首先需要获取其 str 对象。在Python标准库中也有 str 模块,为了使用上的便利,在 pandas 的50个 str 对象方法中,有31个是和标准库中的 str 模块方法同名且功能一致,例如字母转为大写的操作:
! u+ U- [: i9 ]1 o8 V3 g& A6 Z* z3 Z+ l5 _
var = 'abcd'
4 d0 l0 p" v; a. P- W+ estr.upper(var) # Python内置str模块
p. l/ K& I$ }: }6 E$ MOut[4]: 'ABCD'! w& L9 }% C2 n! |+ T8 W
/ M8 s+ F6 K" R; D
s = pd.Series(['abcd', 'efg', 'hi'])
8 [, a0 b: ?; P! z
6 q3 E( p/ n- W! Z) rs.str
* D, N) A# {; q8 g* JOut[6]: <pandas.core.strings.accessor.StringMethods at 0x2b796892d60>
6 d* a& s% R5 C" s* A% X; Y1 W! E b
0 @4 E1 G3 O8 ]& M# i& v0 c2 Hs.str.upper() # pandas中str对象上的upper方法. w! Q) [3 V7 n- w' b$ @
Out[7]:
; x" H8 {! y( t1 i1 h) K5 b0 ABCD* F- U# P2 ]! B5 V+ ^% F
1 EFG
6 l8 e( N* q# p7 O% k/ w2 u. B2 HI; Z, W6 O3 N; v! v2 X( K
dtype: object
0 l1 [) a0 ?& Y+ `4 H1 z4 ^1 Q6 z1& j1 H' B8 [* S; L V8 A6 n; H
2
# l& ]+ c! y8 F; p& g* F9 h3
: Y! M6 \8 Y' R5 n( [4. m3 p% m& X+ B6 c4 s
54 ]$ B q/ j* D! U0 Q
6
3 |! k; ^6 f C T# D# Z77 R1 K) f, U# @' R
8$ }+ U% j( |3 X
9
6 w8 t+ f8 K( T: b4 y10, D8 y/ ]5 F6 p- y! h
11- v. g9 _6 `' j1 J" ^
12
& f4 S% h8 g' |134 c( v" z7 s9 L* ]4 z$ a
145 E) C" R; S U7 z/ Z
157 n9 v0 ?: J0 U- ^+ M5 F
8.1.2 []索引器
, @- `6 c0 n, E1 n3 B; T 对于 str 对象而言,可理解为其对字符串进行了序列化的操作,例如在一般的字符串中,通过 [] 可以取出某个位置的元素,同时也能通过切片得到子串。. X# K# @( I7 G! N- D9 _* x& Z
pandas中过对 str 对象使用 [] 索引器,可以完成完全一致的功能,并且如果超出范围则返回缺失值:
: J8 h: F6 _& d' I- e5 u7 t1 z3 J. B. c2 z+ x3 S5 b/ n
s.str[0]
- [9 S, |6 q5 EOut[10]:
0 V- p2 F. p# l2 ?# n5 P+ l* |0 X# {0 a
8 L1 `2 }! e$ [' @+ S1 e
+ q/ A) d9 c4 I8 N2 ]4 I2 h
; |+ g0 ]- e; A. A! X) a" n0 edtype: object
9 |0 x, x' @; |4 B* ~
0 P0 b5 k" ~- P( ^s.str[-1: 0: -2]
3 z$ P3 A' k% k# D$ T3 [2 EOut[11]:
, I8 ~" W+ M6 l' s( p1 H8 z0 db
. Y) X4 ^5 q+ [( T9 |% k8 ^1 g5 Z) \% L0 Y8 N) g% p/ T
2 i
2 ]( V* U3 ^% S. @; Ndtype: object- e& s6 Y# o: J" j+ }0 q: A% C: X
3 v2 {: [8 m& r9 o( H
s.str[2]
7 C2 X" F' n1 B5 ^5 W, y1 lOut[12]: 1 L7 C& A& f! I1 w, R1 E
0 c
9 u, l2 E. ] }1 g8 A2 Y( a7 s, {$ B* d5 A: ^
2 NaN" r! D6 ^- |# ]/ ?" |' W; r1 Q
dtype: object' ?. A( B% ^) X. m- g- `/ i% J9 d
; z2 W0 G2 {/ V+ F- w1
. ?1 b# j3 j2 A2* H7 Y- T0 h3 t" i. e; K
3
! n6 c4 p* R9 z4
2 H# E2 I3 m# A: U' r+ U5) |. C6 L( M9 o
62 a8 z! L' A3 p
7
$ h9 I& m' u. f0 z( z+ {6 q+ h8& u$ s, `" D' W. A
9
1 Y3 F( W5 k8 o' }# F0 S% P10
3 ]; N# P" k7 E1 \1 ] K117 ~! k' P8 J3 l( ]9 N( a1 J: S
12
: C4 O6 W' ?! ?9 `: L0 i13
; m Y) ^: \* O' m9 m14* `9 t# q9 T( A; J
15
7 u& p$ L/ u, ]163 |6 H8 P1 L \- j2 X: x
17
: S% \ o3 r6 p, S5 p: N180 p0 }4 g9 w8 i/ u
19! G- n2 }8 R# z& A4 ?
20
- G$ N5 U8 g) x+ f0 I2 v Cimport numpy as np% s- }* [# B" B8 J
import pandas as pd, J# Q" w3 [- U
n+ F' g1 [/ m
s = pd.Series(['abcd', 'efg', 'hi'])' @! k% o5 A9 |% @; R
s.str[0]$ v. J3 N- R9 W
1
6 W0 Z! `+ m0 h! \1 Y0 N2 L8 h- P3 M. O: b% ?' G
3
& \9 j5 ]+ ~$ r42 r- E9 x( ~0 I) @: A4 Z( U% ^
5
- p: h0 V( r( l0 z- c0 a
! C3 ?: B V! r$ z1 e
& m D5 \! Y+ w# A' z. A+ q: L; S( k* T2 h4 P- k3 |, e' l" a( h: ~& y
dtype: object: ]- d$ v8 C+ i* i* z4 m
1
" o Q' h0 |! t- v E# N2 Q2
J6 f z& _7 u0 R- N2 r& e/ o3
# p9 B* q( h& y2 d) T; |6 x, q4. k. b( R i. C- @+ x
8.1.3 string类型
9 |8 V/ f* f! G# \5 R% H 在上一章提到,从 pandas 的 1.0.0 版本开始,引入了 string 类型,其引入的动机在于:原来所有的字符串类型都会以 object 类型的 Series 进行存储,但 object 类型只应当存储混合类型,例如同时存储浮点、字符串、字典、列表、自定义类型等,因此字符串有必要同数值型或 category 一样,具有自己的数据存储类型,从而引入了 string 类型。
+ Q1 V- U" X' c2 \ 总体上说,绝大多数对于 object 和 string 类型的序列使用 str 对象方法产生的结果是一致,但是在下面提到的两点上有较大差异:- L% [/ Y" h$ E% |: h0 V. a
& K* Q, }6 w. h* I
二者对于某些对象的 str 序列化方法不同。
! H& d, B; e; I1 Q可迭代(Iterable)对象包括但不限于字符串、字典、列表。对于一个可迭代对象, string 类型和 object 类型对它们的序列化方式不同,序列化后str对象返回结果也可能不同。例如:
9 o0 q, q+ W* ]' vs = pd.Series([{1: 'temp_1', 2: 'temp_2'}, ['a', 'b'], 0.5, 'my_string'])
1 Z. R6 U" O9 Fs! V5 L" u. |/ Y- F2 a G
11 e' L3 R, J, R6 I' c6 ~0 d- D5 F6 z/ S
2. p3 [2 k) B. Q& H0 U
0 {1: 'temp_1', 2: 'temp_2'}
% ^6 q1 L, `$ m' r1 ]1 \* ^1 [a, b]; o1 t! p! o' @4 o4 E1 p
2 0.5
6 O5 z+ T& D3 J; g: [" k* c3 my_string
1 C t" x4 t9 q, ^2 l5 c% D: R, U: _dtype: object
* g* }, G. d7 E1
. d0 p5 T4 `- V3 c$ Q8 M) s9 R& N4 @2
, |6 I) p) {. I5 c3
) D3 I$ E4 e; N5 l* a; o4/ |+ f( a5 [5 _5 ?6 ?
5
8 r: J4 V* Q# b* K& u3 H# xs.str[1] # 对每个元素取[1]的操作4 f9 W5 V& L, ~1 i5 X3 n
1
; S# M' Q% J7 W& H2 z) m0 temp_1( S, q+ t% O4 H0 v6 Z
1 b0 Y6 O7 f3 n0 o5 c$ S8 ?: t
2 NaN# A- f: U1 u0 b) k% h
3 y
% U# i4 n! F/ H) _dtype: object9 I7 \/ t/ T l" D
1, D9 O" [: A( W" d& |0 p
25 a* Y2 ]* [0 p- J7 F
3
! e- U p4 @# C& b7 c) \4 j+ Z8 }4
# X' p1 p& ~6 a2 S3 e5
/ }; z4 ^" H. u% _; l' H6 gs.astype('string').str[1]9 }# E' ~" N$ V7 Z- D/ j; T
1; Z# {( w) l1 r
0 1
8 K! s( k* C" Y3 q, S. k1 '- ~ e' t% t: P/ H' S) Q8 c
2 .
+ E+ c* G: p/ J4 A0 o2 r# \' j! h3 y+ Q, l: ~' G6 u A% g" ^, u
dtype: string
+ ~, {+ G, S! V2 u4 y1# I7 n @7 t8 ~, F' e4 k8 [
2
$ j& d4 Y, D6 L6 [/ I) B3 L d% G3" \) u! s' p# T; K, _7 H$ R8 t3 u
4
e) G Y' S; t8 @ L' I" _5* ~" m, f0 {5 K+ h% t
除了最后一个字符串元素,前三个元素返回的值都不同,其原因在于:- R4 v% T8 `! k" i( S
7 F8 _# j4 D& v. l当序列类型为 object 时,是对于每一个元素进行 [] 索引,因此对于字典而言,返回temp_1字符串,对于列表则返回第二个值,而第三个为不可迭代对象,返回缺失值,第四个是对字符串进行 [] 索引。
8 K6 x- r+ n; d9 I4 cstring 类型的 str 对象先把整个元素转为字面意义的字符串,例如对于列表而言,第一个元素即 “{”,而对于最后一个字符串元素而言,恰好转化前后的表示方法一致,因此结果和 object 类型一致。
5 _) z/ \* d- c/ i; S( H T' n4 kstring 类型是 Nullable 类型,但 object 不是) y6 [/ T! `; K4 L7 F3 w" `8 u6 y8 J& w# K
这意味着 string 类型的序列,如果调用的 str 方法返回值为整数 Series 和布尔 Series 时,其分别对应的 dtype 是 Int 和 boolean 的 Nullable 类型,而 object 类型则会分别返回 int/float 和 bool/object ,不过这取决于缺失值的存在与否。3 @& \2 l0 L; i n r
同时,字符串的比较操作,也具有相似的特性, string 返回 Nullable 类型,但 object 不会。) f- ^* O, c5 f# x) m
s = pd.Series(['a'])
. N2 e; |2 v& Q2 H$ ^* h( S7 f. M7 {
s.str.len()# z+ G1 X+ C! J- O7 @
Out[17]: , o- T" ?7 ]7 G. f
0 1
/ s' I6 H% O/ o4 I! g# b/ E$ M$ Kdtype: int64/ t9 {" \; j x4 i
6 X5 a+ `8 K5 S3 m3 B9 I( c" r5 ^0 r% e
s.astype('string').str.len()% c/ U$ W0 ? S2 E1 s. h# W% C
Out[18]:
k1 ]" g& @5 _ D0 1
/ c% D5 o* H$ b& G9 @- F- Rdtype: Int64, h& c. G9 w3 W6 B0 g/ l7 @9 k
1 u0 c' N$ F; S' L5 qs == 'a'
) R, U! H6 A; X8 l1 ~Out[19]:
5 U7 _2 p- F/ ?, l0 True
' i& S( _$ c* a& f4 G: h1 |dtype: bool, ]2 _( K0 I( \6 e2 Y. _: ~- v8 V
. [6 m C+ H2 ]: ^2 z* [( u- Q, d
s.astype('string') == 'a'9 M2 D: r( N$ q
Out[20]:
3 o; P( Y B# O0 True
5 W( P8 ]) P1 \ sdtype: boolean
. T9 s$ i: K( y$ q9 b: G' i: T" S8 _6 z7 N6 h) o, S' `$ `0 A$ g
s = pd.Series(['a', np.nan]) # 带有缺失值
* O! C& k3 n! b: G7 @
1 q( ?6 ]2 v1 ps.str.len()
9 r, s5 t% `6 Y! d: a5 XOut[22]: G8 K& t! r/ }% Y
0 1.07 b. h4 p! {3 d% F, t
1 NaN
8 u. k4 Q& P2 y2 O6 Q ^6 [dtype: float64* Z7 X' n! ^( z8 O: k8 R
- V- s7 H! m: @7 q+ D
s.astype('string').str.len()
# m$ K/ X% h! ` W( Q2 zOut[23]: 0 `, z" e# r, z2 v' Q/ i/ w' J
0 1* s5 ]2 _) J8 }0 Y/ H
1 <NA>
; R' C: E- o: Z: n4 ?dtype: Int64
% ?0 [6 h& m9 y9 A# B* Y$ m7 ~8 N( U8 R; O
s == 'a'; ~4 s0 R$ [6 O8 ?" } W- j( I
Out[24]: * ~3 } z- ]/ b/ C
0 True" A7 {5 A3 k# E- h$ f7 h, ?
1 False
+ p8 }1 }! B2 {dtype: bool
4 C) I/ X, s0 Z3 b, \/ @, T. U
( p- K/ r' |" D! G# v, ws.astype('string') == 'a'
! c5 u0 @8 u) ]% u3 x3 |Out[25]:
" J$ S2 l; N4 g0 True* i! {- J, n; n, v# S" ^
1 <NA>8 o; ~3 y5 i+ S M$ {" t9 Q0 {
dtype: boolean
% k$ B; d3 U1 s% s3 D% X( s/ m5 p+ |7 _
14 U3 k* w$ ~3 @/ e: D/ S/ [
2
3 g& A! o. U& a* ~3# m4 H4 h0 A* y& p0 g5 C& p! \
49 t- \; a) U/ W* l; }3 M5 G5 g. s
51 b+ Y) H" x/ c1 e9 x; B# }( O
65 ~" i5 Y* ~& e* |5 o& Z
7
" Y9 }1 q3 j4 [% Q1 { @82 X* @. T6 v$ M0 q* [3 q
9 d- f6 T R( L) x# Q# r, f
10
: M* U4 u4 m& }" `5 Z) n11! D/ ~# T/ J A4 D# `' t) Z3 x
12
& |8 T5 S7 L' b% t `13
! C* S! R0 Z% X& F" f14 x4 x- A8 f! k6 R) W
15
( T8 z/ u4 K& D! N& o K2 U* U16
# ~) @, F. A# n- K; ~5 a) n R170 q3 l0 n; ~/ ]/ X" X+ l
18
3 b7 u$ a$ Y+ T7 w( R% h+ B7 E; e19* n: Z$ S' M# g- V
20 K1 t& U0 q1 z" I; y
21
* Z( x" D; X( E" U22* h6 J) A. {0 Z, O: ?
23
7 |7 ]% `9 Q+ f! d24/ x. k3 [6 x; ^8 G# O' o6 u# _
25- z. X8 g3 s9 }9 d
269 J& n7 B4 j* U% E! C. q/ [
27- _6 m. }% g! c8 k/ C1 {5 |% e
28
9 O3 L9 B7 c! J29
0 w/ `4 P6 @1 }1 g: a7 j" C' e" _307 ~+ A! l. A3 X
31: w- {! y! r! g1 w* z) M R
32) O) I+ c) \( s- p* R2 M! a
33
* T# m- L. w$ e34" i% K& h% Q* ?; v3 J
35) j+ Y( h3 E2 a# m4 h: }5 D! Q& G
36
3 g z# D; C7 F0 [$ b" U378 [! S2 V. {$ \) Z3 T
38; M8 a) \7 V0 N/ b5 q4 O
39% I" V# q2 p; h- Q$ i
409 L, T9 F6 S& c- E+ H0 K! n2 v
41& s3 l- q1 b+ i
42
9 k! {# m% W* [* \) D* ^43
+ J$ V& Q1 O) n" |" j448 j7 W2 f9 A F& K9 @7 o
45. [- _5 w2 ^( R/ |0 e5 z& `
46, J- a6 Y" C0 O# ]- B) ~9 O
47& c/ y8 `1 E: m; A& e. k
对于全体元素为数值类型的序列,即使其类型为 object 或者 category 也不允许直接使用 str 属性。如果需要把数字当成 string 类型处理,可以使用 astype 强制转换为 string 类型的 Series :3 t2 B$ |' Q2 j1 {7 I5 d5 N7 B" O
% }/ ~9 S, y; K* xs = pd.Series([12, 345, 6789])
( {3 X+ X1 ~$ D5 Y( u
# I& Y$ J- ?; Z( [* ]s.astype('string').str[1] p: w3 @* X8 u1 K2 u% Z t; G3 c; g
Out[27]: 6 v8 k) T8 W0 s$ m( L8 V
0 2
6 a8 z" L$ B& S+ w4 |1 45 T( M* v2 L4 o& {. G
2 7
/ o" v, L9 i; d) `- udtype: string' Z0 K, m& y" v& U" t. Q: E: F0 z$ C
1
. ^+ j6 w0 H2 @: G8 N! j2. r. }4 Y2 o: o/ P! j& J( P
35 U# q/ B5 B1 L+ _
4
: Y/ s& U. \9 C8 q7 m h$ w1 n5 P& |- R$ T6 n
6
" r; M4 L' N6 S3 P8 c; w7
7 y t3 z9 E: T, S; T) ~. i1 }8( m& P O3 d* z* u9 M4 w3 {" S* L1 C
8.2 正则表达式基础! H: c6 W6 H7 d
这一节的两个表格来自于 learn-regex-zh 这个关于正则表达式项目,其使用 MIT 开源许可协议。这里只是介绍正则表达式的基本用法,需要系统学习的读者可参考《Python3 正则表达式》,或者《 正则表达式必知必会 》这本书# O M+ @6 k, n0 U
# J+ O) d: Y0 b# @: G% S8.2.1 . 一般字符的匹配
! D7 ]5 C: r, g& d) x4 L1 s正则表达式是一种按照某种正则模式,从左到右匹配字符串中内容的一种工具。对于一般的字符而言,它可以找到其所在的位置,这里为了演示便利,使用了 python 中 re 模块的 findall 函数来匹配所有出现过但不重叠的模式,第一个参数是正则表达式,第二个参数是待匹配的字符串。例如,在下面的字符串中找出 apple :+ ?: a2 C4 T6 i3 C: y: D5 |
2 c; ]2 p) e6 d6 L+ m( s
import re
4 E* B) k2 i$ m5 v+ z
: C$ Z- Z' X! q! k8 Wre.findall(r'Apple', 'Apple! This Is an Apple!') # 字符串从左到右依次匹配. S+ R; a1 {5 i& Y
Out[29]: ['Apple', 'Apple']& @5 P7 z3 d# L+ r+ X$ t# w" @
1- w$ J# v% I: n/ m! f+ U
2
( d3 e) v: z) ~! p$ L( k3: Q6 R; T1 m \" N
49 A2 T# D P# ^/ r+ i$ w# M
8.2.2 元字符基础1 U( V( ]! [* b
元字符 描述" V6 Y, W* F( `6 J
. 匹配除换行符以外的任意字符
: q4 Z" W% y H& V/ l[ ] 字符类,匹配方括号中包含的任意字符
0 N7 j, W. r1 S7 I% m+ ?[^ ] 否定字符类,匹配方括号中不包含的任意字符
6 W2 g/ ? [, b8 `* Z* 匹配前面的子表达式零次或多次
& n7 J, M# g& c( y$ ]8 z) V+ 匹配前面的子表达式一次或多次。比如r’d+'就是匹配数字串,r’d’就是匹配单个数字- _9 q- T9 ?! \# l2 @
? 匹配前面的子表达式零次或一次,非贪婪方式
& g" {* \9 Z+ B) K3 m{n,m} 花括号,匹配 n 到 m 次由前面的正则表达式定义的片段,贪婪方式
% o5 I+ h9 i) z- [+ k(xyz) 字符组,按照确切的顺序匹配字符xyz' A) U- D$ R8 W+ ~% w* |
| 分支结构,匹配符号之前的字符或后面的字符- |% g( i d2 H8 I: P ~* E) v
\ 转义符,它可以还原元字符原来的含义% }1 c- E- ^, Y3 Z
^ 匹配行的开始$ u* B9 m% s8 z3 ~8 l2 h! H
$ 匹配行的结束
/ \2 y C8 T7 K# @( fimport re
( z' m7 ^" z l# ?, B% F, mre.findall(r'.', 'abc')
: g# g) M. d$ a9 c% x% T- }Out[30]: ['a', 'b', 'c']
; z) @- J& }6 d' j7 `3 t9 D& P$ F5 ~- G) f- {
re.findall(r'[ac]', 'abc') # []中有的子串都匹配; B. X! [8 U1 `( j- Z
Out[31]: ['a', 'c']
- v8 w" x: f! f% ^3 h1 Q+ S- o2 h$ L
re.findall(r'[^ac]', 'abc')
' r4 x' C$ W8 g3 oOut[32]: ['b']4 v, ], _: S, o+ l$ e
; W, {4 m* z; B
re.findall(r'[ab]{2}', 'aaaabbbb') # {n}指匹配n次7 I7 O* {- \6 g) u7 S$ m
Out[33]: ['aa', 'aa', 'bb', 'bb']* g: g4 k8 G5 S [3 \
% {! _. U2 I# k; }% v4 Q% I7 L
re.findall(r'aaa|bbc|ca', 'aacabbcbbc') # 匹配前面的或者后面的字符串
& C0 O* r: |+ F1 b J/ o% J: e% kOut[34]: ['ca', 'bbc', 'bbc']
% n' S: X: d+ k- A' R8 W! T2 H W
* {4 ?4 m5 }1 @4 X7 Z* L# 上面的元字符都有特殊含义,要匹配其本来的意思就得用\进行转义。
/ _+ s# ~# c+ u/ e" ` V. k5 d6 e5 X"""5 g; k. C' {* r" |9 q7 D, Y+ j
1. ?匹配的是前一个字符,即被转义的\,所以|前面的内容就是匹配a\或者a,但是结果里面没有a\,相当于只能匹配a。
! Y# M' {* Z# s8 ? {: o' V7 b2. |右边是a\*,转义之后匹配a*,对于竖线而言左边优先级高于右边* m5 Z6 h! c/ }0 I/ w3 T+ _0 b3 q. f
3. 然后看目标字符串aa?a*a,第一个a匹配左边,第二个a匹配左边,第三个a虽然后面有*,
: P6 j7 p% a0 u! ~4 H( F& j但是左边优先级高, 还是匹配左边,剩下一个a还是左边,所以结果是四个a" r; R' k9 W( q9 k9 |$ e1 X
"""
% n% Q. z0 P# \2 P7 `2 o
. a& J7 {7 r/ R, Z% Y& D! V- [re.findall(r'a\\?|a\*', 'aa?a*a') # 第二次先匹配到a,就不会匹配a?。a*同理。
/ h' R7 o0 c0 u8 I' xOut[35]: ['a', 'a', 'a', 'a']2 p! I0 k* {7 D: y4 x. C: s. A" ]
, O+ T" a' e& `! K* j1 c
# 这里匹配不到是因为目标串'aa\a*a'中,\a是python的转义字符(\a\b\t\n等),所以匹配不到。) L3 L3 K7 b4 x0 ]8 b. c/ h: @
# 如果是'aa\s*a'之内非python的转义字符,或者'aa\\s*a',或者r'aa\\s*a'就可以匹配到\字符。! _9 g7 r/ B9 k3 }5 T/ o2 e$ {" l
re.findall(r'\\', 'aa\a*a')
1 z v; b& B& i+ T. e. z; r[]! Z% v1 ^) `8 ^. V' F/ s' I
2 {8 C) N+ p& v( ^( `- p& n9 qre.findall(r'a?.', 'abaacadaae')7 K1 u( O' ^ R/ P& R
Out[36]: ['ab', 'aa', 'c', 'ad', 'aa', 'e']7 i* m8 ]' `$ s
+ y4 n, {7 i6 I. Are.findall(r'(\w+)=(\d+)', 'set width=20 and height=10') # 多个匹配模式,返回元组列表% h' g0 }0 |: R8 \" ~
[('width', '20'), ('height', '10')]9 |6 \8 a& P* V
9 a4 U. E( z( c9 n
1
' b) P, y }' K# L8 Y+ L! ?, ^- f2
f" y5 ] z6 a3
1 |6 K7 l9 j; E5 c, f _1 N8 l4
! F. F' }, C9 y5 J5
6 K s/ d6 Y3 Q% T" u1 L6
" F k" U. _) h9 N* u+ R9 N7
1 \: A. n: O3 L$ |8- }- b- a# U3 K8 j) W. \" l( W
9! o/ {/ z% ~, q' v
10
% \. z# A3 T3 s6 S( q; J# E11
3 a9 j3 }) e0 [* y12: A4 p* a' d8 ~* ^1 @( c# q
13) @5 y& X% r$ E; }
14
: d4 D. P8 D. Q6 n0 V. W* h15+ D: V3 ]* y7 X8 k/ ]1 g4 U
16+ b! a8 J7 A" }. ^
179 V& T) L" a$ F4 j# T2 E! c2 b
18
6 P; y1 ^0 e) B+ ^19
+ A" S F' i% u20
- g) p# D {8 N- J213 a8 ]" {& x; O
22
% A6 L5 a) d8 Q5 i/ V# ]' f23
! F) v3 l* T5 Z1 \* U1 D/ Q9 @24, a( z& k$ G% R; {$ I3 q4 i
25( {# {& h9 T5 o
26* T9 {" y5 w1 o! J% Y# A$ v/ y
274 J& h( w8 M( I0 A' W# |
285 a- D1 E' H. D/ Y/ w6 ?* h3 y* G
29
7 @9 p" G% S3 `! U( t1 j* t30
% V' m$ O% ]- e; R' C. ~31
# n6 U5 X8 A! K2 y% @32
# R* s+ u6 L( C& p) T7 y [33) Q. D8 s) ?3 P. O' h7 q# H
34
. f3 `4 J: q6 E5 C [" E( U# N35' g& Y9 h' @% a6 U" I' B+ a
36
8 H. m+ |7 u( z% z& {37
5 U7 `, X' ?9 _# T1 \9 U7 z5 \8.2.3 简写字符集
; p, C- K8 P1 e# Z! v则表达式中还有一类简写字符集,其等价于一组字符的集合:
6 U4 y9 J8 l! T, ]5 g. J
! E6 R! \$ `" ?! q. T简写 描述
6 r' E5 p+ ]5 A9 `\w 匹配所有字母、数字、下划线: [a-zA-Z0-9_]
; b9 ^# E3 |! J) ]+ Z2 W\W 匹配非字母和数字的字符: [^\w]/ c: V1 C4 W9 x
\d 匹配数字: [0-9]
; s3 j' W, E0 M5 g, f8 @, m- E* b$ w\D 匹配非数字: [^\d]1 w/ [' d2 n+ ~: a0 L
\s 匹配空格符: [\t\n\f\r\p{Z}]
" x- s, t( F6 A9 s" `! S\S 匹配非空格符: [^\s]
& Y& K4 L h% o' o3 i- N# f+ g+ r9 `\B 匹配非单词边界。‘er\B’ 能匹配 “verb” 中的 ‘er’,但不能匹配 “never” 中的 ‘er’。
( C, p4 H+ U3 x6 j$ Y/ m( U) Vre.findall(r'.s', 'Apple! This Is an Apple!')
* ~& c9 E+ \/ C5 y/ K. e/ UOut[37]: ['is', 'Is']
3 R0 z$ q% M* I$ B2 R7 h, X; ]& J# w
) i+ A5 I( |" L4 T% `" U: L6 t% ore.findall(r'\w{2}', '09 8? 7w c_ 9q p@') # 匹配任意数字字母下划线的组合,但必须是两次
- G. r- Q/ {6 ~0 u$ ZOut[38]: ['09', '7w', 'c_', '9q']
8 g3 x* ]# Q3 b* d' P& P4 M, F/ P6 Y' }0 n
re.findall(r'\w\W\B', '09 8? 7w c_ 9q p@') # 匹配的是两个字符串,前一个是任意数字字母下划线(\W),后一个不是(\W)0 D4 _8 u! o3 t7 |7 t
Out[39]: ['8?', 'p@']
- }9 G- O8 H; k q# _8 m7 t9 ~. q8 b% I0 d5 K
re.findall(r'.\s.', 'Constant dropping wears the stone.')
7 I$ Y& G; d) R2 q( U' j! V( Q7 iOut[40]: ['t d', 'g w', 's t', 'e s']
7 M7 i) l+ i: u
" k5 s( O, ~5 V6 I3 T8 [! vre.findall(r'上海市(.{2,3}区)(.{2,3}路)(\d+号)',5 J- ]4 C1 D5 S
'上海市黄浦区方浜中路249号 上海市宝山区密山路5号')1 S* U7 Q" p: X& m; J3 |/ n* p
7 n6 p/ G/ |: {( W6 e! W) e
Out[41]: [('黄浦区', '方浜中路', '249号'), ('宝山区', '密山路', '5号')]
" g$ p1 b' [; p9 {2 d" v# I; f8 _) h
1
, ~3 H' }7 S3 N4 T. O2) E& h1 l# s2 k2 L; S6 z( t
3; {$ f U5 R! u+ d/ t; M6 [' i
4$ Z ?. _: |8 R- B R9 I% u9 G
5) I, r; V. n# i& {
6# S0 F* d% c8 R6 k% S0 H
7
1 T. j8 |; b0 ]: |% B& C: F8& }( ~7 d: ]2 \7 @; u
9# C2 k( R# a3 A% a1 ?
10
9 t& c8 W% p; o A, g114 n+ m( Y! V+ k) l2 b1 k
12
Y* y8 m w4 H* X" c) h% {1 S: Z13$ R Q/ Z c. r4 u" m, j3 R+ n
14
3 B6 p2 q! V; i0 P' e7 g15& V) v+ J& \: K/ E% p- I. q* m
16& D4 o) I7 Y" m; m
8.3 文本处理的五类操作
3 H0 F7 o0 \8 o8.3.1 str.split 拆分
$ i- S) c* k6 Y! h6 z str.split 能够把字符串的列进行拆分,其中第一个参数为正则表达式,可选参数包括从左到右的最大拆分次数 n ,是否展开为多个列 expand 。! R6 m" W1 a5 q" C$ j- i% S) [( Z
/ b8 {- H4 g5 i( S4 }+ G; L
s = pd.Series(['上海市黄浦区方浜中路249号',/ G& q$ i9 Z0 |
'上海市宝山区密山路5号'])9 g+ p! I: M. p0 T! H
/ A: j0 D7 d6 y
7 v* P7 q+ G& g: G% j$ ss.str.split('[市区路]') # 每条结果为一行,相当于Series6 ?. N' I; [% J4 Z0 A8 h
Out[43]: ! t7 F% b) |) g" D1 J
0 [上海, 黄浦, 方浜中, 249号]% q c4 F) S: Q {: R) B! D
1 [上海, 宝山, 密山, 5号]; D1 \8 F& r' T+ C; J2 O X7 c
dtype: object ^4 n( l! R. {
- v% q8 C* L& O c) a4 M, O6 Q
s.str.split('[市区路]', n=2, expand=True) # 结果分成多个列展示,结果相当于DataFrame' W- ^3 W U O3 O" h# f! m% O
Out[44]:
+ k. C) L1 M1 f 0 1 2( o$ D3 p- s! ~
0 上海 黄浦 方浜中路249号2 Y2 M# k5 ^: v4 G- Q7 i
1 上海 宝山 密山路5号
/ R3 d$ g1 q. S& S1 x9 o! L1
! [5 b1 t0 {3 D3 }4 ]' o) v2
0 Q l- e2 U6 @& |33 g& |: g6 f7 x2 ]8 c
4
! A+ w, Z8 E8 l, u, o% N5
. V( x+ y ~' y$ V+ ?' ~ d( ~ _6
7 p; ?6 Z- V X5 O; C! p. S7
. z. x' o k; n8
, F: e, x- a9 \& j( k6 W! B9" u: `# R( S5 I+ G0 V3 }; R
10# v9 e. L2 ]2 m% E* ~2 b9 D
11' ~ \8 d- t2 J* s/ ?8 `
123 w( g9 r, ?& P5 V$ Q% w+ p9 e, z
137 Q1 n" |& Z/ U3 b
14
" u2 M [- H @15
" W' p5 t5 R9 ~& \# k+ i3 G7 ~/ A: X 类似的函数是 str.rsplit ,其区别在于使用 n 参数的时候是从右到左限制最大拆分次数。但是当前版本下 rsplit 因为 bug 而无法使用正则表达式进行分割:
1 D6 E' {9 e2 T% m, F. a
: N$ B8 j! E+ a, X. ds.str.rsplit('[市区路]', n=2, expand=True)0 J( i' M9 Z. _5 n- b4 e
Out[45]: * B3 i& ]/ Y+ f- h" S! j* s
0
) q& Y6 w: l/ D! B: m0 上海市黄浦区方浜中路249号
) S& R% W/ c2 U: @/ X1 上海市宝山区密山路5号+ x" R% ]5 j8 O+ D9 u
1! i4 g6 `+ g7 y3 i5 F
24 V7 d+ Z5 |- }' i6 O8 ]
3
/ ?/ e0 \) a$ }7 X0 W4
/ j7 U" ^9 ]! r8 o5
* I1 u* R, c1 W. f5 _8.3.2 str.join 或 str.cat 合并/ T* Y; K; Q% t; S8 F
str.join 表示用某个连接符把 Series 中的字符串列表连接起来,如果列表中出现了非字符串元素则返回缺失值。4 x' M5 R" ]5 {0 h/ g
str.cat 用于合并两个序列,主要参数为:9 v1 g9 k' `$ [' T! z1 s% I
sep:连接符、7 n" F, g3 U) D
join:连接形式默认为以索引为键的左连接
* O$ W# K5 D- C) l$ Q/ tna_rep:缺失值替代符号/ R8 U% J1 P( R& |, a- T
s = pd.Series([['a','b'], [1, 'a'], [['a', 'b'], 'c']])6 \+ z6 n `3 |" J" x" y# c- c1 b
s.str.join('-')9 O* f- y. R" I9 k2 G' ]! N* }* g& ?
Out[47]: * J- o% L- h. p% M- S0 h
0 a-b/ o4 Z3 j6 r% g3 r
1 NaN, C4 J& A! R1 i! K
2 NaN
6 C: L! N2 \; q/ Cdtype: object, t$ i- _8 ~* y6 v
1* x1 c. v, W" v' s( e" t: ^
2
3 G$ B! _: i8 |% w% z3 m2 S, P3
& v9 b& b/ C2 O4 O2 u4
/ M( f" \% X0 C# F5
; Z, A n0 e2 x# [6
6 I1 I% D* F( l1 n7
7 J$ m$ I0 v8 m9 Z' K$ ?s1 = pd.Series(['a','b'])) E O, q1 @# V4 N
s2 = pd.Series(['cat','dog'])
8 A: W) b; S$ Q& S6 ms1.str.cat(s2,sep='-')9 r8 M5 \. S; n* ]3 Y' d
Out[50]:
* {: o2 s/ z1 H7 t# k* G0 a-cat
: y; g, s2 o( B' V( w& I: [& W: q1 b-dog; q0 {: @5 D' }* }. i( v% J5 \4 q
dtype: object* g d5 o' O8 r
" }% N$ d5 U+ A/ x5 J# P% _
s2.index = [1, 2]
2 V* p6 k; N( V8 D2 |s1.str.cat(s2, sep='-', na_rep='?', join='outer')
: A+ h4 M9 x ZOut[52]:
: `9 `: Q: W* k% c" F& j4 }8 g7 Q* A0 a-?5 X9 u# a# P6 O' ^
1 b-cat
& A' {! Z9 N4 I7 c5 p2 ?-dog
5 d2 l, R. C/ l& R0 ~dtype: object0 m; M: f6 q8 b! a5 x3 N7 ~( ^
1
3 T8 }; O6 L) e% r2 `" j' c6 a2
$ s/ c0 a. K1 h9 i) x) W3) d0 o) u; J6 o% L, i% H b
4
& a# f( v6 u: h5( ?3 F% b/ {! B6 R- G
65 G: I$ }. S8 [8 k& M
7
) E9 ]* D/ o: U8 e, B; p8
" l; F" l2 d6 g9
- r2 ]* s* t% _" `105 L6 q( P! ]! o+ F( C* Z
11
/ F) e6 `3 S6 b) b12
5 `, L \3 a! p+ E9 S13
0 G; p8 Y5 i$ s0 K14& Q4 d; ^5 i; m# ?# b. L9 K/ ]6 |
150 Q, j: ~% O$ w: [* |4 c# d1 f
8.3.3 匹配
{( ]9 J" @9 Y& Kstr.contains返回了每个字符串是否包含正则模式的布尔序列:
0 s# o( ~; @" t3 c* M5 I: |8 Es = pd.Series(['my cat', 'he is fat', 'railway station'])
6 l5 o9 O( D: e/ `7 V2 qs.str.contains('\s\wat')- t: q" ^; u4 y( y, P- G
) b/ x' r5 f$ i/ Y6 k$ E1 I0 True2 j K, S$ d: _6 t/ o4 N
1 True0 R# }% `( i' H: M3 Z* H" a3 `4 Y
2 False6 F. \; x: h {8 `. S
dtype: bool# t, r: l* ~. E- t, W
1
$ z1 s% o5 f { j; J( `& `4 g2
; W7 B6 ~8 m+ S; U, U( d; J9 B/ M3$ @1 H% S" m- F' h
4
( ~. H8 G2 {0 t% Y* N H9 n+ q1 w' X5( D; M) M2 h1 P4 Z- G
69 u4 V) V6 J$ e/ G: ^8 U
7
; [6 h3 E* L" xstr.startswith和str.endswith返回了每个字符串以给定模式为开始和结束的布尔序列,它们都不支持正则表达式:0 j3 X% r! ~( i+ u1 M
s.str.startswith('my')
! q5 M9 j' h6 c; t* k' o5 @8 Y& M. _5 G \
0 True
$ i; a7 {- y9 [/ t; f$ R1 False2 J5 f0 z& U5 `8 f" D
2 False7 l' J! K; D0 X! T5 P1 l
dtype: bool" t: k: L# l* C
1" Q" F8 n* m5 u2 T8 j6 T
2- Y2 Y7 l% @7 e+ z- i' A# l
35 U8 H$ }! @: B& ^0 D
4
9 C; D$ ^% G: N* O0 r7 e* j5
* [5 h3 Y2 n+ D0 m* N4 X67 V% n& ?9 y/ _! @* P& j3 K: ^& O: e
s.str.endswith('t')# B Z {$ B9 ?2 _8 y! {
: O2 u+ r% j2 ~4 R4 a$ Y0 True& s b" i% k# e, d7 V& y
1 True( S2 n" F# J" Y0 Q$ }2 r
2 False
: k! ?: [( z+ W% R9 ?$ U- ?dtype: bool) b0 |! r" ~2 o
1- T: P2 R4 m8 n4 D+ y
29 z) e! E5 y+ M
3' [0 t9 s: T/ h$ k- V. m8 C* l$ {* ]
4 S* H0 o1 A9 a9 T% O* u
5: H) \7 k& d7 r8 o
6* t% P6 l O0 b, |* [
str.match可以用正则表达式来检测开始或结束字符串的模式,其返回了每个字符串起始处是否符合给定正则模式的布尔序列。当然,这些也能通过在str.contains的正则中使用^和$来实现。(貌似没有python里的search方法); N j( R! x3 F
s.str.match('m|h')
/ i- ?. S; b/ [ l( w1 [6 zs.str.contains('^[m|h]') # 二者等价1 F( d4 F/ e& B) e2 ?+ M7 [
; v: Y. w' ^3 U. |: c8 B1 Q
0 True" ]) X H+ i4 U# i; f- w
1 True
6 q2 {( [$ a6 o3 @% d2 False* J U1 q& z$ O2 ]4 m
dtype: bool
( y+ E1 q; l* v- p13 a+ F/ M9 x$ T4 L$ C+ ]1 p
2' \- R {3 H( J" e( V# V
3
+ L7 }9 X8 g; l$ x$ l4
# p J# |1 ?& J2 c% \0 R5
2 V* [$ v& h a2 u& H6# h, v! ^1 d& X" s" F
77 c$ {) e8 b- `* L$ y& X
s.str[::-1].str.match('ta[f|g]|n') # 反转后匹配% M, Y7 @" y4 x* a6 c
s.str.contains('[f|g]at|n$') # 二者等价
2 l$ F4 ?% C1 ]; N0 r' }
6 G; w! O, U7 Y/ M8 b) L- b; U! ?+ j0 False# k2 U2 X) D4 L4 N; p
1 True" ?+ G# ~/ ~9 O0 s% ?1 d1 N
2 True5 U4 m( r! r7 h
dtype: bool% z3 o. I) m/ N) y
1, E# r* x+ \$ W
2
% z- p* m! d2 l8 B' K3
6 a" C5 T7 i4 p/ Q; I, D3 H4, f* M% O3 E" m+ N1 C. z
5
7 {5 }1 k! F6 H! a# a6
' ~, r% H1 V: N* B* D, e) |7
2 P' c4 [( e( t0 ustr.find与str.rfind返回索引的匹配函数,其分别返回从左到右和从右到左第一次匹配的位置的索引,未找到则返回-1。需要注意的是这两个函数不支持正则匹配,只能用于字符子串的匹配:
+ t: V8 U( w) H3 |6 a& W- r/ Us = pd.Series(['This is an apple. That is not an apple.'])
5 \2 s- C `) d( L0 O' i0 a0 ]8 E, Q6 x, Z% |- b2 \! m
s.str.find('apple'). k6 ^) @2 b! u/ f; V
Out[62]:
5 ~' b/ o9 |# N& r0 11, [4 B# n8 k# Y, [( M: ?. {, o
dtype: int64$ f( e$ ?/ I: w, w
# t( ` D6 n- `( T3 m7 N: N/ Ls.str.rfind('apple')3 A* E! O8 c& t
Out[63]:
1 A+ ?9 s+ ^/ F1 i' s$ B0 33. Q% b7 h& o$ J9 _7 P: }; a
dtype: int64# G$ {8 l# ?7 K( n) p/ U- a
1
6 n+ C% R! \/ l6 _1 v2
, y- o2 U7 z5 n" }4 W4 x. ~3$ m, R6 i: V: H, X8 s1 }. r! R7 H
4
/ E0 |* a- Z/ |' {1 I& Q5: w( I6 F2 F' c D
6/ ?6 \5 F$ `: r- r3 _0 F
76 L' _2 g. Y! E- \9 f; }8 S
8! t( E) }! Z( S7 v% N
9
( y$ `7 J+ {' P% F10
7 j9 X% p" F! Z11
2 n6 p! m( x Z) ]1 X1 @/ `# F& l( b替换# t1 x# h7 m2 X
str.replace和replace并不是一个函数,在使用字符串替换时应当使用前者。
( u8 h, |& O4 M. cs = pd.Series(['a_1_b','c_?'])
1 L. X- G1 `3 ~# regex默认为True,表示是正则模式,否则第一个参数内容表示是单纯的字符串,也就是匹配字符串\d|\?
3 K/ G8 u; c+ @" P; r6 Ss.str.replace('\d|\?', 'new', regex=True)
1 v D; k4 l) ]) q P7 I# c5 D/ x% m2 R" R3 E
0 a_new_b/ _ @- m$ R: a) X% ?
1 c_new1 K. `% z% q: i9 W8 k1 \. M
dtype: object
' s9 ~9 v6 w; T# s" z6 E1$ D0 f8 D. P, s' Y1 s& U
2
8 A. h7 T+ i0 o" b3 m& L" g3% S: z8 P4 F; { T m
4+ C6 H+ d& H9 v# o+ U( t
50 ]) y2 [: i) I0 J
6- R( F2 d d: g
7( ^5 F; t0 n; G I2 c6 _5 p
当需要对不同部分进行有差别的替换时,可以利用子组的方法,并且此时可以通过传入自定义的替换函数来分别进行处理,注意group(k)代表匹配到的第k个子组(圆括号之间的内容):+ {' z" c5 m! x- e" m6 K
% b: F% M2 P9 I8 ]+ j
s = pd.Series(['上海市黄浦区方浜中路249号',
2 s% ^/ q0 R, @ '上海市宝山区密山路5号',
8 T- B, ~/ C4 C, m: }/ h) J '北京市昌平区北农路2号'])
/ b3 @$ q m1 wpat = '(\w+市)(\w+区)(\w+路)(\d+号)'. ]& y! x' G. ]
city = {'上海市': 'Shanghai', '北京市': 'Beijing'}3 ?2 s: z- e5 s, L [( O+ n8 p0 }; k
district = {'昌平区': 'CP District',1 A: G' t8 s; _0 n6 a$ D0 ^& {: _1 V
'黄浦区': 'HP District',3 L" O* V: g) N+ X; I
'宝山区': 'BS District'}' Y. x2 G4 g! q/ ]! Y
road = {'方浜中路': 'Mid Fangbin Road',
/ a/ Q2 ?9 S9 z& i) d '密山路': 'Mishan Road',1 U, W7 R" X5 E0 e! p1 G' }( I+ {
'北农路': 'Beinong Road'}
3 d$ J* V; \# j; |% O/ W5 qdef my_func(m): ]$ I- m. u1 r; N7 P
str_city = city[m.group(1)], B, p$ l( d, U+ h2 H
str_district = district[m.group(2)]8 Y0 L# G$ M) F3 Q" [+ O. O6 x
str_road = road[m.group(3)]
6 P0 v* i) S3 x6 u! W* z1 R$ k str_no = 'No. ' + m.group(4)[:-1]- b. B+ ^) e1 {; D/ W
return ' '.join([str_city,: G# {+ e; R% R$ M! u
str_district,
; s% U+ @/ V3 a$ i. {& s V, H, M str_road,
( t# _0 p0 h" C6 g) ~ str_no])
% |1 W- K5 q$ S+ bs.str.replace(pat, my_func, regex=True): a! ]; x% s- ~0 B
0 _6 |0 }# o8 x1
" A0 |: }1 F3 ^( @" X2, L% h3 b* \! e2 b3 @
3
/ k5 ]7 }2 V7 _8 X& R7 W J% b5 [. y, E4; Q1 _! C! h% e& F& k" L. j8 R
55 k1 ~$ ~, N" g9 |& X, }
6" m1 w6 _2 w1 f( ^1 R
7
& T1 P! k/ X! v! [8+ Q* g2 P. @4 @- r9 E# c
9
/ j$ H( I& O6 E) E6 {; }& @10( u9 k5 F& r5 l9 m
11 c+ |' J; y; A5 k4 j
12
4 d) r" H4 l, r5 D13: T& v1 d1 m( P: G- v9 n$ @
14
6 y8 r; `3 b4 p& k Y7 |. [1 g15# `; ?7 b6 W6 u
16% m! D* @' E! S8 x8 Q
17% l; `9 @( R6 s. }3 b
18
* `6 P6 ^/ \7 f19( c6 ?* t7 \8 E+ q3 d
20" \( h6 A3 e1 _9 @2 z
21
. c8 q$ D, \2 K( h- L7 X8 u5 g0 Shanghai HP District Mid Fangbin Road No. 249
- C8 P) p+ [& T6 B5 V- N1 Shanghai BS District Mishan Road No. 5
: `7 s7 S" R5 g4 M" W2 Beijing CP District Beinong Road No. 25 c4 F. b3 _; k9 r+ c7 v# p
dtype: object
& D- _! H$ L5 o" z1! f4 j' E! ^$ Z, E5 u
24 ^0 A& u2 m2 K5 r' Y
3
4 v1 M8 i$ o8 E" s8 G9 O4
9 p" G) J$ m8 h% d7 f% ?这里的数字标识并不直观,可以使用命名子组更加清晰地写出子组代表的含义:
4 P0 R& ^$ d( L9 D3 h
- k- y8 b& G$ R# 将各个子组进行命名
& N/ c0 {$ r5 g. Qpat = '(?P<市名>\w+市)(?P<区名>\w+区)(?P<路名>\w+路)(?P<编号>\d+号)'1 K8 J% g1 s4 o* n! E2 y
def my_func(m):
' x. b" D: q# X str_city = city[m.group('市名')]( Z; L1 B, I$ I
str_district = district[m.group('区名')]3 m$ h; a# g/ y( @1 l5 H# \2 ~+ V
str_road = road[m.group('路名')]
) m# e& R0 d S3 O8 e( E1 Y str_no = 'No. ' + m.group('编号')[:-1]
6 h# B6 i9 I) E9 ]: D' } return ' '.join([str_city,' p$ N6 Q) d8 E/ D2 g
str_district,$ E9 |: x4 z8 R! t
str_road,; _7 ^# G V/ ?2 ^! f/ c, `/ w0 R8 d
str_no])
* j R( \! Y0 ~; b Bs.str.replace(pat, my_func, regex=True)
' G4 M0 U' {# d0 o0 L1
& \% m/ Q( w8 ^# R m9 ?% r2 A5 t2
2 H T$ r6 g8 _- Z$ X* M( h3( q5 w( @1 T9 ^* Q' W" u
4
, v% F" z* L1 `+ J5& [, F1 O# K! Q
6
: e/ g: T" \, e" z; L9 ~+ J, F7
( ?* G& U8 T F! c8" w# T+ b! `3 F' V
9% F; o% Q4 S5 O" O* E2 N6 a* v& \
10* v+ L: y* ?0 l
11) P* M; t- n; y Y$ I0 { g
120 o, R2 c% }% `$ q
0 Shanghai HP District Mid Fangbin Road No. 249( y' C9 L! H3 ]6 L5 X) b
1 Shanghai BS District Mishan Road No. 5: g1 ~% _8 F7 z. |, d/ d
2 Beijing CP District Beinong Road No. 2
* Y; c f$ x% B g" ^1 d8 W1 \ }dtype: object
: E8 @4 U S0 F$ e! [/ ]0 p1
% ~- ~5 m( Y8 R2
- t+ A8 \ t) @3& ~+ m8 m& V1 z0 a8 G9 k
4
0 s" b! [" S7 c/ p 这里虽然看起来有些繁杂,但是实际数据处理中对应的替换,一般都会通过代码来获取数据从而构造字典映射,在具体写法上会简洁的多。4 i$ Q: Z3 e% m# A' ?" C" R
N0 K! M6 {6 c& Q/ c( ^
8.3.5 提取6 R6 d* J8 o7 |# ^
str.extract进行提取:提取既可以认为是一种返回具体元素值(而不是布尔值或元素对应的索引位置)的匹配操作,也可以认为是一种特殊的拆分操作。前面提到的str.split例子中会把分隔符去除,这并不是用户想要的效果,这时候就可以用str.extract进行提取:' J2 C2 C1 A1 K/ W7 h
s.str.split('[市区路]')! w4 K, b7 O/ r8 i
Out[43]:
2 ~- @! M+ ~$ X8 t* O1 f- A0 [上海, 黄浦, 方浜中, 249号]
! {, t% d1 G9 V1 [上海, 宝山, 密山, 5号]! j6 b$ n5 ?. H+ W; T: S1 C
dtype: object
. ~: O9 j- r! A
Y" ~3 n4 j! p/ ~! t" v; hpat = '(\w+市)(\w+区)(\w+路)(\d+号)'+ S& G; d" Z- j
s.str.extract(pat)
$ b% g# K. ]$ ?& WOut[78]:. `3 R; V c; b+ F' F8 K/ d
0 1 2 35 {1 z5 b! o1 L/ h) g
0 上海市 黄浦区 方浜中路 249号
E4 [$ i9 I% L: T% r" P4 P, E" e" @1 上海市 宝山区 密山路 5号
" f# @* ~. {9 ^) W2 北京市 昌平区 北农路 2号
6 v# O# N0 T4 P/ a( K* v( c1
. F, N }, k9 {0 n% E2
& `* y K: n; Q) X5 J3* a. b1 O" H9 \: K" R
4
% [3 D" p; l o3 s* [5
7 W; f4 _, j/ d2 t- W6, F% r+ o+ D% s; ~# d! q
7% [/ t. i* ^& L9 z/ J9 M( |7 y1 b
8
" a$ _) e+ ^& Y; ]. ~- R9
. n) B+ `6 N6 Y* v5 h104 F; F7 ^0 ~1 W6 n2 c! ?* a
11# d/ i/ c1 b% E' Y, ? E6 _% P
12
: L) k y' X' i! x% D13: Y% L1 h% Y" R5 k5 P
通过子组的命名,可以直接对新生成DataFrame的列命名:
2 [- J+ O# R$ C( o$ m, |* W; ~! Q7 s O! S! z6 x5 ?% c" b
pat = '(?P<市名>\w+市)(?P<区名>\w+区)(?P<路名>\w+路)(?P<编号>\d+号)'5 U9 a0 H" L) p [* x0 `
s.str.extract(pat)
6 T9 v7 K' G1 t% d& VOut[79]:
9 }3 ?- B* b; j1 s4 U3 R0 F 市名 区名 路名 编号
+ i- X/ t7 E4 j+ Q( T0 上海市 黄浦区 方浜中路 249号
* j3 u8 a R. I1 上海市 宝山区 密山路 5号
- l3 n# P- R6 Z2 K/ W7 U. R$ G _2 北京市 昌平区 北农路 2号0 E! m7 _9 W; R( G
1
5 d/ N- y- E! D' |) h+ v2/ d' R. X0 ?) m! F) Q& H: M- [
3
3 u; D, q! l, D# {* G4
: R9 e7 J+ @: v# |$ ~1 u- F( I& D5
$ S+ I$ f: d1 x& z- E. _" L* f( a' O" p68 u8 |) `7 k$ Q6 g4 l$ q
7
+ Q, S; b: x. Q3 b' h3 {" A9 rstr.extractall:不同于str.extract只匹配一次,它会把所有符合条件的模式全部匹配出来,如果存在多个结果,则以多级索引的方式存储:
( H! r1 l* \: k2 ns = pd.Series(['A135T15,A26S5','B674S2,B25T6'], index = ['my_A','my_B'])
) H7 l3 [9 w$ Q4 K* Q* |pat = '[A|B](\d+)[T|S](\d+)'
& P3 y6 o' D8 j9 ?1 P7 {s.str.extractall(pat)0 s" T0 L+ {+ v
Out[83]:& L& z4 T: n* m( N/ m' s* e7 N
0 1
, q+ n/ I0 Z6 C6 ]2 l: x. o( a match
7 b2 j; m& b7 r& N$ \4 i0 nmy_A 0 135 15: ^ u4 K7 j5 q
1 26 5
3 T( Z* [2 R# A# _my_B 0 674 2
+ t+ q' e4 S5 R1 F. s; L 1 25 6+ O- F+ n9 c8 j4 _ ]
1
. c1 ` P/ C' u; y# N# U/ [( S% V( a2
( g" w, E5 J b3+ z+ d S: h0 u' \+ K P
4
! }( P. r4 O( i# y$ k O5. }* h& E* q& M! ]$ `. N
6
# b2 t5 Z$ ]" V" |7 ~- D7
6 S# n( l) t4 t6 q! t7 X: m8 `. L0 ]% j% _) y$ Q
9
& C( \: v8 N) k: M/ ]! S$ \10
: p9 x5 Y" e. O( r* xpat_with_name = '[A|B](?P<name1>\d+)[T|S](?P<name2>\d+)'2 a) N; ^/ o; X0 Y) U$ p9 j
s.str.extractall(pat_with_name), v9 m) n% U& z! p- }/ {
Out[84]:
3 E1 `( n" D9 q! B4 F name1 name2. z# M, w- N' n X6 r3 K
match
/ B7 F. p- K2 ^- Xmy_A 0 135 15
, t( P+ @. Q3 r0 R( ?1 ^$ R 1 26 5* G4 g( G' s) v: ]
my_B 0 674 2
! L: O3 X5 M, Q; c4 p9 F, B1 D+ J6 S) T6 U 1 25 6
& n9 k! T4 U% u1) O& o3 P& ?* i) v* G
2
: i% t5 ~. B/ e( c& M3 g3( |9 G" e, A# |3 I- X: Y; l
4
+ g/ B, h& e: J' e- g% p5
: E$ B1 L2 f9 z9 @$ d( ~8 i6
# L* l4 C4 U% W7
- V% v! ^+ Z& ~2 Q0 c8
# x" c) U. s, T- N* W I9. c) K8 l9 b/ t. o
str.findall:功能类似于str.extractall,区别在于前者把结果存入列表中,而后者处理为多级索引,每个行只对应一组匹配,而不是把所有匹配组合构成列表。$ G6 M, d# h+ L% e2 o# C, X
s.str.findall(pat): M6 A1 O0 w3 V9 Q- r7 Z# t& U7 F
1
* W- v: v, W$ c( Z7 Jmy_A [(135, 15), (26, 5)]$ e# I! A( Z4 Q9 o6 }
my_B [(674, 2), (25, 6)]7 q0 t _% J2 T4 ^. ^& [) h
dtype: object) U+ e% p$ k. c! w& I
1) F7 A0 z* z7 P' r
2
9 L' R& z! }+ b( V3
/ W* X8 W: e6 X- F/ @- u _8.4、常用字符串函数
. g2 T; z2 T4 r& a" T( { 除了上述介绍的五类字符串操作有关的函数之外,str对象上还定义了一些实用的其他方法,在此进行介绍。
, p3 ~8 R* F. X: x$ ]8 G3 d# a- p! s- |/ K2 h H; W
8.4.1 字母型函数
; a- v8 f" f8 k! D* z upper, lower, title, capitalize, swapcase这五个函数主要用于字母的大小写转化,从下面的例子中就容易领会其功能:, u' N2 b, c9 R6 W% k
1 h- ~$ u$ o; q) N
s = pd.Series(['lower', 'CAPITALS', 'this is a sentence', 'SwApCaSe'])
/ L0 S n5 Q- o6 \# e
' Z" `" S" r! [; Ns.str.upper()4 a; `0 m+ W3 K* Z9 E3 P5 P
Out[87]:
* c) A, B. m+ \: @) m0 LOWER$ U* _- u3 H- o
1 CAPITALS
/ w6 h: H4 w \2 THIS IS A SENTENCE' S c4 ]+ D& R8 i Y3 \" A. A
3 SWAPCASE
+ c! @/ x" e2 e( odtype: object
! b$ w( _, q- i1 q1 q6 |- T
5 X* p7 C$ |0 ^4 Bs.str.lower()% H+ x, a. d7 j$ s
Out[88]: & {$ ^) p! Q# n" z
0 lower, D% m( C5 ?! b6 }3 ]) v
1 capitals: L5 o) d2 h+ V( y
2 this is a sentence' s- Z) l0 s3 c$ F5 k- [, r9 h9 W
3 swapcase d; u+ g; L& x, P
dtype: object
( B5 v+ c1 Y7 L0 T6 O
) n; e5 Q# u$ W9 d' ps.str.title() # 首字母大写$ @' D- O& ~8 a
Out[89]: 5 N2 y: E+ q: _7 t* f2 u
0 Lower& t+ D B& l5 ]/ k7 \+ ?' A2 Z+ _
1 Capitals4 _( ?7 Z( R, Z$ L4 _, K
2 This Is A Sentence
- [ z% I5 g* _% g* Z% s3 Swapcase
2 D8 z) [' ?* Q- |2 `: Odtype: object
! c; L% ~, N8 ~5 g; i
4 T5 P4 F/ _& ds.str.capitalize() # 句首大写# w7 N$ L% d1 w) t) P* C
Out[90]:
+ k: i% ?! f6 C( r6 ~& C: u9 L* N0 Lower) b" F, S( ~2 ~
1 Capitals8 S O1 r' L, s( m
2 This is a sentence2 c) M7 m1 G* s: w3 s( H- ~- {# ]/ D, B
3 Swapcase
9 p/ M/ N( B7 Ldtype: object
7 s8 u/ s1 n2 b! W8 l
# L# Q5 m: g: Rs.str.swapcase() # 将大写转换为小写,将小写转换为大写。
! d. u# P: N- h- q4 @* i& lOut[91]:
- ~2 M8 @: o* F- i, V7 y( G4 |0 LOWER/ F6 E5 _# |1 h+ b% C5 t
1 capitals
3 Q! K5 B# u, C0 R. Y/ I2 THIS IS A SENTENCE% @9 ?+ [; P1 j) @6 r4 s2 O
3 sWaPcAsE4 N1 `+ T5 E' z+ E7 Q
dtype: object
: i. k3 P0 }& F. L; P; y0 `" G" n* t7 \. Z4 H8 z
s.str.casefold() # 去除字符串中所有大小写区别% g i6 a* `# a! s, y
' X! i) F0 b# G5 ]
0 lower8 k- r- g( N& k; V' e3 a
1 capitals
6 A- v; g7 R7 w$ V: t6 R( j1 n2 this is a sentence+ ~7 O3 Z" f. [9 e* I- }4 B
3 swapcase7 }! \$ O# B4 s
. S0 N( P# `+ E: S; d) w
18 |& ] ` s; a9 @ O, V+ e
2
0 V+ u1 j$ E2 Y7 } O1 X3
* S; i( ~6 U7 _9 C& @ {9 t48 J D% o$ h/ b, ?/ g. X) {
5
! w4 o# y7 x } K: T- @7 ~) s6
7 k3 \( {: `4 J2 q2 g74 V) P' A8 e6 K, W% r# h7 q% k. ]
8' y; D- Q9 Y) I) D. y$ Q5 _
9/ p. i% ~: E# v( P
10 c& l" `" [+ o) t& F
11) j1 T* [% R1 [7 p% G6 G5 K# L
12
2 F4 y* U) I9 ^4 J* @% x13
?0 z! h; A' T/ r+ U% n14
9 Q8 t! S5 J3 b1 h, z15
& r& A7 q# f1 k1 f7 I, S7 P- R16
) Y" }8 M+ e7 y( c# \2 B# R# `17: s0 i+ Z. w. \% k
18
: @" K$ I$ Y4 f: I! O19
, X- l1 k R: u C! C* l8 A20
; ]( ?" e' {; a& M* H+ ?3 I21
5 e2 C8 X( X7 O- z: |4 i22
) K1 }2 ]& F" m# J9 @23$ Y* K( C* x6 @6 k6 h* q* r1 Z
24
/ n4 G( c8 [5 E4 Y) Z25% E* [4 F) X/ ^+ S7 \+ W
26# @% ~+ O7 i0 h
27; N( }0 H! t) q& Z6 z3 U
28; Q% O; v) C' r2 s4 z0 K
29
" R+ B8 n m4 ?8 z, s# D. q+ P q30& g5 |% [! D% R$ J+ v
31
- m8 m7 O/ L5 ?" N7 ]$ I& m32$ S+ C3 C0 E* H& |9 H
33; A1 Q8 s6 _1 a6 R2 Q
341 s5 U- N& P% v
35
4 ^7 n% `- X7 Q& W365 v9 G- }. j6 C* O
37
0 W' J$ ?: k4 r/ i: a8 A8 l38, H- [9 h5 ~8 E, z) v0 ], L- G
39 d( J( d4 M+ y( b7 [7 G
40
- N$ Y$ B0 Z# f41
3 L# k6 U& z3 X% b) `4 H42
6 g; b& I$ `* G432 R" g( w6 n' v+ o* J H4 X, f
44
- S2 S7 k* d' K# h2 r7 L" P% G453 O& y& }+ f% [
46
; O; P# Y' m$ [ ?9 G' C5 r47
* l ^$ \1 ]2 O; p" N) f1 J, r48- d: Z' k( R3 k8 d: K: [4 X
8.4.2 数值型函数0 J% t+ _$ b: `+ R) O1 m" f% r, o
这里着重需要介绍的是pd.to_numeric方法,它虽然不是str对象上的方法,但是能够对字符格式的数值进行快速转换和筛选。其主要参数包括:
5 d! s# P( ^& G5 o; s9 \ I- A t* a5 f/ K6 S# q# O3 x
errors:非数值的处理模式。对于不能转换为数值的有三种errors选项:) ~7 ?7 U4 l% f- ]
raise:直接报错,默认选项% W( f* Y# q _
coerce:设为缺失值8 }3 Y5 @1 u$ V$ r) V
ignore:保持原来的字符串。) ]1 [$ D; g9 ]
downcast:转换类型,转成 ‘integer’, ‘signed’, ‘unsigned’, 或 ‘float’的最小dtype。比如可以转成float32就不会转成float64。& F; T1 h/ O1 h/ ?! R5 ]2 R: O
s = pd.Series(['1', '2.2', '2e', '??', '-2.1', '0'])1 I& J8 s& \1 j1 Z& k8 {
2 C- q( g+ S0 j. m3 I) apd.to_numeric(s, errors='ignore')
5 n. ?( F+ [$ ]1 N' x9 ?Out[93]: - l3 M/ Q! `4 Y8 @+ [5 D- H* o
0 12 r6 }9 X O3 [6 P
1 2.20 g' n, e) i/ t" f0 E! _& e
2 2e
/ W0 p: I, t# P5 G B: O5 |3 ??: B! K( N+ q: N8 O3 G/ G
4 -2.1
( x9 }' ~" {7 i5 0" Q3 w' k7 d8 x; Z
dtype: object4 q7 X9 t/ {8 G3 q; j1 H5 V9 J7 W
( }& d5 s+ y, G0 lpd.to_numeric(s, errors='coerce'); k& q6 O6 X1 s& R
Out[94]:
n: q' k$ L2 _' s# `0 1.0
* y% g! U" c$ a' W. A& o1 2.27 H) Q$ ~# w' @. V, d7 t
2 NaN7 d' Q* i( g" P, P$ E
3 NaN1 R* e& I/ O& L& K
4 -2.1' c- H" I" y4 E' Y) R; b5 |
5 0.0
) G: n* G4 y0 U. b$ xdtype: float64
6 t3 y, }' Q: P. H* ~
4 y# s. O) D0 r5 ? C1 I f0 @1
( y1 k6 z2 x1 a2
% p( _# s7 t4 N2 K+ Z* W31 E& j/ X9 ?; [1 y- h, s9 ~, d F
43 K% w5 k5 R# c9 ~% C# i
5
9 W- e# i, H: ]' o+ k4 b6, _% K5 p- D3 F, Y4 N$ r: e; P9 T- ~
7! V: x2 ?8 j/ s0 _# S0 O0 f
84 G, a1 F; N/ Z; {
9
0 Y t q. P* w# }3 P10
- S# o% |) }% @2 z* ?11
4 U8 R( ^2 r: ]12
* h P+ _4 G1 t4 |9 C. }5 X' a13
$ T' W% E9 Z6 Z8 Q/ A5 t14$ H$ _9 \6 @4 U0 L: K
15
; \0 v6 Z5 G$ q9 O0 w, U* r169 h7 S/ G" H' A
170 E- z Z9 i1 G- X. O& q, t$ |
18- F7 |# c; e8 C" U
191 P5 J {( n7 c. \% |
20/ ~$ I! k* M: m! C- u8 ^) O
21
0 A* _* d4 ^' m, N% Y) [4 U 在数据清洗时,可以利用coerce的设定,快速查看非数值型的行:
: s; L' L% W7 N8 D; A5 D% O9 a0 |4 J4 h& ^) M
s[pd.to_numeric(s, errors='coerce').isna()]
0 X' f, P1 n" x! f# nOut[95]:
1 B! r( B' S2 P2 2e
0 F- ]5 }6 B7 I3 ??
& G' I1 R) _- w% a' ?dtype: object# G( \2 H% U! q& N
1- F8 e& U# C% _
2
& Z# v9 W7 N3 T8 s+ b! I3
* R' u1 W4 B/ f% ]4
/ ~' r; E) e) A( E$ n$ `5% [; Q/ J, s! D; ]
8.4.3 统计型函数! E- Q9 a; z8 I) l( H; b
count和len的作用分别是返回出现正则模式的次数和字符串的长度:% M! F) |1 m& z- T8 |# P
, ?" _( ~/ m) v3 D7 }s = pd.Series(['cat rat fat at', 'get feed sheet heat'])
9 j1 \3 v$ z8 r0 [
' E) y( b( R# Y u# qs.str.count('[r|f]at|ee') # |左右两种子串都匹配了两次
' O; w) q1 A& B, U ?Out[97]: & E' t8 j6 g3 U, r3 x$ k
0 2( J1 |2 a& P) X6 O# I" r# z, o
1 2
# D2 z% M) L3 W H' J* b# mdtype: int64
! K) v9 [( `6 X& t9 f. W, A% E& b) Z# E
s.str.len()
9 z1 s8 C ~& i3 A5 d$ D# W' HOut[98]: 2 q; X0 O9 \5 A9 j5 t
0 14
( c. l( I% y+ N1 191 R) N: Q# Q d. q9 b1 L
dtype: int64
( E# j2 N& X ]( @# T6 R$ g* O! \1
1 _& i8 F( P. @, n- A( |! K3 w2- v- g2 N+ H) I
35 ^+ ?* r& n0 O. ~3 O/ J
4
8 n7 W& \7 P$ Z9 ~3 B5
1 U$ x1 f( l% F8 @" h6* a- [2 W# v9 L8 y$ t! X6 F0 S
79 S1 Z4 E3 T( R& ]
8
* `, A$ y: N5 [# s2 |* L0 i2 e. M96 e+ r3 ]8 p9 @2 T
10
1 A" _% X. j1 c- R11
8 G, r1 A) u! o. j5 E+ |# Q12
7 J- D) o6 u( Q/ q% P" |136 X3 A1 g' |) k, a
8.4.4 格式型函数
2 U) I: w; R" C3 U. L 格式型函数主要分为两类,第一种是除空型,第二种是填充型。其中,第一类函数一共有三种,它们分别是strip, rstrip, lstrip,分别代表去除两侧空格、右侧空格和左侧空格。这些函数在数据清洗时是有用的,特别是列名含有非法空格的时候。
0 n& F3 R0 v' s& S& `
# P8 \- U! u3 F" Tmy_index = pd.Index([' col1', 'col2 ', ' col3 '])% o2 N' D2 M7 V+ Q# n% Q
, A% O Y7 l% y- G& O4 Fmy_index.str.strip().str.len()
/ H d. z0 H0 i# V1 SOut[100]: Int64Index([4, 4, 4], dtype='int64')( @" ` |1 y0 a5 y" x' L/ M2 |7 p
1 o- a. m* i y! ^7 N$ Hmy_index.str.rstrip().str.len()5 J' G1 l, Y4 Q) o
Out[101]: Int64Index([5, 4, 5], dtype='int64')
4 s- U n# @1 P% b
?8 k! O% w7 l' L. a: M: V2 \my_index.str.lstrip().str.len()
4 b4 N3 @" o) R' L- POut[102]: Int64Index([4, 5, 5], dtype='int64')7 {& N1 f: k/ r. t1 C7 _/ k
1" r% U, b7 Y/ E; |2 s0 B
2
# ?3 I9 c- y. ]( l2 h8 ~34 U/ n4 @- e4 E/ N! A3 u& ]
4% B1 j; P2 I) r3 X2 [9 S \0 g
5
7 H$ I4 P# P. F( ?7 S62 V* f% C4 ?% S
7
0 d( _! R) o7 n) r2 [8! T+ V6 m! e/ p( M3 h" u/ _
9
1 A, A' Q8 A, {' u10
( K- o; [" M, z3 Y, g4 ?: e( R 对于填充型函数而言,pad是最灵活的,它可以选定字符串长度、填充的方向和填充内容:
0 a% Z, n( v2 g4 v$ a/ n, K& w7 M/ X! c- x$ C: E p
s = pd.Series(['a','b','c'])5 e) e* V! T/ H; t: }: n/ e! n% |
' b8 ]9 u y/ n$ A+ ^0 Y2 r% b
s.str.pad(5,'left','*')
+ q2 W# t* o# g9 T0 V. zOut[104]:
3 H9 E& S: S# Z0 ****a
6 @1 u; X' b: m }1 ****b- F0 B+ u3 A7 g
2 ****c3 J+ s8 @% W) C) }% y q
dtype: object
7 g& j0 S0 ]! B/ m# u, j2 d" H c d* l' ]
s.str.pad(5,'right','*')
* p+ T( |9 G0 }7 JOut[105]: . \! _. \: k) y
0 a****
% R6 c/ U0 L- X O z1 b****, s* ]: ~# f5 B- h
2 c****! ], ]( m$ O# }- E# I
dtype: object. f" E; c" a4 E5 }: T
8 [' V6 V2 K# @' F" F) A
s.str.pad(5,'both','*')
) ~' h, Z% @! SOut[106]:
5 V1 u! O6 `) c9 u9 O1 z" q0 **a**
& L! Y9 r6 V4 b1 ~9 ]% A/ k" {1 **b**
5 ?- Z( l. o0 E( J& `- k9 n1 u2 **c**3 _" P: Q+ ^; M2 m8 n& W
dtype: object% X# N4 a8 D1 \; y: ^
& C" u% e& [4 Z1* m9 Q( a1 i6 L5 J
2: u1 v8 V) ]/ ?+ D: o9 @/ ]& `. C/ d" K
3! H0 o# S7 v% g. z# Y) {
42 H1 G8 A0 p' c# h( p
5' M. [, q* T% t4 V) K' |( y1 T
6
9 n( B0 X3 n- M- v7
8 ?" P6 ?* L4 I6 e+ w r3 l( Y8( q8 }# M f" t- `/ j
9" u( C4 f2 K" J. p! ^
107 j& [; |0 p, `1 R
11
5 z ]* e1 m) t3 m( y: A& j/ R: M+ P12) v B s7 G; G# `
13( u! p9 Z* x& Z7 ~" N
14
1 z) ~2 k9 @3 h5 @, I" t( z( _; Y8 D, N5 Y150 W1 t6 |1 k: d- m7 ]
16
! R: [; N+ D7 u# s" p1 [" F2 r6 l17$ h3 z4 v& D$ }; K
18
+ g: V6 M5 W1 ]0 k+ F/ Q9 u1 a19, J$ M6 S; U: ]' N: X
20
/ w2 t, C$ _3 D, u. l" P) Y' ]' G21$ x4 r3 H- A1 b C* U
229 Y A+ B" F8 ~1 B0 F2 w
上述的三种情况可以分别用rjust, ljust, center来等效完成,需要注意ljust是指右侧填充而不是左侧填充:
# P: V2 i( w! V: s% S/ J$ |, d0 t: C, p
s.str.rjust(5, '*')
8 p) C9 L D1 C9 L* ^( U4 [: ^Out[107]: ' u, R5 Y6 W2 {( O- \6 E
0 ****a' V- C* f& p9 W2 X& r; o2 h
1 ****b- p8 f$ V+ h7 s! S: P& K5 a
2 ****c
. T- n) V# V8 U+ n* S) Qdtype: object |/ v' L8 M- t. O4 ^2 a9 c
; V5 S& [5 n/ e3 [* X; ds.str.ljust(5, '*')
5 `* }1 g& [& `# }2 D6 m/ c; `Out[108]: ; d. O; I6 Z4 P$ \) Z
0 a****
* z1 B6 L+ y7 o' J3 }( G; Z1 b****8 C4 U/ V4 ~( d
2 c****
" T; r% ]; r- ~. |( ~+ P& wdtype: object
7 b3 V+ S. V9 ?1 ?+ X: R: }
3 j/ D* |' r1 r+ @/ Ps.str.center(5, '*')9 e7 d d4 j1 [9 k9 K& t: z6 j
Out[109]: 0 v; [4 b" ?) \" X
0 **a**
/ X) O- n3 ]" o2 t2 e4 G" N1 **b**
1 u# r. y# Q% a- r$ ]2 **c**0 a" s* R# F q' c$ ? F
dtype: object. F$ l/ F) P' J a9 B, C+ p. Z- b; U
. H. V! S: [" A& r) j
1; Q/ g3 a! f, G! j$ ^- h
23 c. [9 b# B5 q1 ]. x
3
) s3 d! j/ \" h4/ V, p+ ~- }* ^2 W+ J
5: K$ ~# d# u H
6
5 f+ w9 O* P: v0 o4 ]7# T! f4 [) C: I# C/ v5 s$ c
81 d7 Z4 V$ g2 A8 f2 H
91 A7 @" d* w& k0 x5 F: A- \
10
# P1 M: g- B: R B$ h6 K& @11
; o2 U0 O! C* j' Q12& F; A, }8 r% |0 _1 B6 ^9 M
13
" F6 p) J( u: X/ j& D: J- L14
& P9 v C! ~. a2 b5 E# w) H15
! N* _/ j+ {- M8 r6 K4 D16
9 n9 Y: F, S8 K8 j5 k3 L174 C, P' x% E& j0 l& e1 ]
18( c: U& u: s6 i3 c0 Y! x' L. {9 o
19& |. e1 }4 b* J3 R1 \0 S
20
: e, T% W- E" ^: y+ c6 s 在读取excel文件时,经常会出现数字前补0的需求,例如证券代码读入的时候会把"000007"作为数值7来处理,pandas中除了可以使用上面的左侧填充函数进行操作之外,还可用zfill来实现。
2 v2 L8 ], y; [' l k U* J5 ?1 n
s = pd.Series([7, 155, 303000]).astype('string')% O. L. W: y1 _6 J
- U6 J O6 q6 A# `: ?. W" @ H: T6 j2 [s.str.pad(6,'left','0')
7 l( o4 D( [0 L6 D6 ~0 z3 U5 G/ MOut[111]:
, u0 Y* _) I2 l* \1 L0 000007
4 r8 {5 j' C+ \: O+ T1 0001550 t; A7 T4 u; R, Z, b
2 303000+ I% y2 M- x0 I: l$ @6 ]6 t
dtype: string/ S4 z; `( u$ D Q& [% P; L
" T* L6 x" B) J0 I: a
s.str.rjust(6,'0')
. I- i/ L4 g! ]/ M7 G4 z' W8 nOut[112]: ) C3 ^4 p- \1 D3 K
0 000007 Y: z9 y/ T N d( P1 K. s
1 000155
' L* z. F2 s4 f; Z. M9 d* X1 _# i2 303000
/ k! e @% }& _" Z/ n) @dtype: string
! ?0 F9 ]/ C- \5 K: k8 h" Q: m
/ f) F" v. S+ L% ?5 `s.str.zfill(6)
, a8 D; O, E( j' EOut[113]: 5 Q# s& w/ `6 s7 m1 z. D
0 000007
. e$ S) D; F! L+ k9 e" q$ D4 ~7 m1 0001557 R5 R/ y# q4 D( f! o
2 3030001 G$ E1 I/ z6 P. b
dtype: string. c9 h& N+ H# P- H5 S1 Q, h
' L' p! g2 N( B8 |& q& \
1: f3 r- u, ^! c8 k
2
3 S) B; x. H9 m; U4 l/ ?& k3 n, C3
' s! l5 i. k, t* Y4
4 ]+ f+ L: t6 y$ Z/ o; W5. w2 Y+ ]/ }. k
60 M& d0 D( Q7 i4 ~1 j2 n# J
7
2 ?" ?! [' v" v8' n& T& ]2 K5 D$ \3 g
9
. P% l2 P8 U* \( i2 {$ J7 A8 z10, A, D/ i! G( Z! w$ l; B& @% I
11- U* c6 D0 D# D" ^! d% _% X
12 L w2 N& d; ^+ f
13, c5 ]2 {4 U4 b
14! I; i c& b4 G
152 I5 a# ^. Z: W. P' H7 y
16
. b& U w! n; S( e* B173 M a. h, a+ r5 G
18
" P3 y& Z/ r% o19
3 f, o8 f( C: F L/ n9 w! T& I20
3 q$ o4 E& W# I4 C. o9 T210 b( O" O% Y7 M4 g7 Q% g
226 u+ v9 f$ W* s' N: u' h1 w
8.5 练习! v- m5 V {9 m4 k1 |# d
Ex1:房屋信息数据集
" x/ R2 ~3 C+ u8 C现有一份房屋信息数据集如下:- A) [7 O; }" S# Z- u/ {
1 K/ i2 d* V- J( D# mdf = pd.read_excel('../data/house_info.xls', usecols=['floor','year','area','price']). x/ @0 ^" o3 ~8 l1 K& k
df.head(3)
# E* S1 C+ J7 P5 F4 JOut[115]: ( d4 f+ D2 {6 n
floor year area price
- ~7 v9 k, W/ ?' w. ?0 高层(共6层) 1986年建 58.23㎡ 155万! R6 b+ y, H8 W: g& b$ z( l
1 中层(共20层) 2020年建 88㎡ 155万
& M, P' U" }4 E5 Q2 o! ]2 低层(共28层) 2010年建 89.33㎡ 365万. c& s: r1 R/ j$ E/ N5 @3 r+ y
1
/ T# u6 t% P# i% M3 E b# Z2
7 C) B- w4 [0 W B* e. P9 D3
n) j+ j! k) N" }/ @( J" F1 j4
3 G: H: y. T* z6 V" w7 h1 o* ]5 q5 Z0 c, M8 d9 @, Z- d
6! D- c8 S! y# A: u# }/ p# c
7
1 Z5 r- M/ B% p将year列改为整数年份存储。
1 e$ Q) F% V/ l. y. j将floor列替换为Level, Highest两列,其中的元素分别为string类型的层类别(高层、中层、低层)与整数类型的最高层数。
& h* e) a+ k- Y; H* ]1 o, h计算房屋每平米的均价avg_price,以***元/平米的格式存储到表中,其中***为整数6 O3 I$ v& \9 k s# T, ]
将year列改为整数年份存储。
4 n) F, J$ }. B' g" |' @ h"""9 Z7 t9 s* ?) e4 S7 U# {1 C3 {
整个序列需要先转成Nullable类型的String类型,取出年份,再将年份转为Int64类型。0 y' A" A2 r3 r$ {: O
注意,转换的类型是Int64不是int,否则报错。即使astype加参数errors='ignore'跳过缺失值,
, e% d+ f9 }1 Z3 r9 v' a, y转成int后,序列还有缺失值所以,还是变成了object。* b( D! E1 t P
而整个序列转为Int,就还是Int类型,缺失值变成了 pd.NA 。
* Q7 J: c1 e1 B6 v9 k" a""", R# s4 W! c- L r
df = df.convert_dtypes()
) o) T6 g) A) X) f6 y" N! ldf['year']=df['year'].str.replace('\D','',regex=True).astype('Int64')
* g* z( f l% Ydf.loc[df.year.notna()]['year'].head() f$ N/ I1 e! U
& |$ R( @' W2 v% d+ i
0 1986 y/ G/ D8 g) Z% c( |
1 2020
$ l' w: I& Q6 W% X" a9 ]- E0 }2 2010
$ d! X: W; P g( q; b& d: m: w3 2014
2 B5 T" m$ c8 A4 b5 y4 20152 ? V( Y# \! W/ L- b
Name: year, Length: 12850, dtype: Int64
! \/ {; B- T5 N, L/ w+ U/ K" a7 N& e7 }
1
' u# S F6 o; N8 M ?! P4 r* L2- B( e4 [( C* ^
3" s. k+ k% ~7 m9 l2 i
4
1 j1 {$ \+ y1 N9 _ `" n$ |5+ ^& d. a8 d2 I, T3 L1 F
6
" e5 D* I# R5 z7
( B9 e) a; J: A* i# s. _8
5 c9 E. W; E( Z0 `3 m9, p+ I5 v. t, \' o% Z' m
10
% M7 j. R0 ?3 n8 F5 w$ t) P) _11
- A% h2 \: p3 G+ Z1 E" G, ?. }2 w12
& O1 g/ l( Z2 V8 n: s13- I4 h8 e+ Q* S7 F; _2 s8 c u
144 l! j% m" V0 o$ K/ X/ h! t
15
' x: [" ~0 F- V# K3 U+ {16
# Z3 d- H, e" x/ o- j) t% x: y* Y参考答案:: z1 [$ `' M, }) v% z$ `" c
' ?0 V: }7 p) J+ g' i# R不知道为啥pd.to_numeric(df.year.str[:-2],downcast="integer")类型为float32,不应该是整型么
* P3 l3 e6 n; e5 c* N- H" p" _$ G& H# l
df.year = pd.to_numeric(df.year.str[:-2]).astype('Int64') $ g% C( S. V/ g: p3 y$ P
df.loc[df.year.notna()]['year']
& S5 j! R( v4 W4 [1( ^# T: e& z: b+ d
25 {# l" f( K6 q; N5 j" H* H6 h* k
将floor列替换为Level, Highest两列,其中的元素分别为string类型的层类别(高层、中层、低层)与整数类型的最高层数。' }' @( L+ {" x" B9 W
pat = '(?P<Level>\w+层)(?P<Highest>\(\w+层)'
7 ]3 V$ U. ^" @- Y; Xdf2=df['floor'].str.extract(pat) # 拆分成两列,第二列还是(共6层得形式,所以还的替换一次; { x! s7 ]. }
df=pd.concat([df,df2],axis=1).convert_dtypes() # 新增列拼接在后面,再次转为Nullable类型* ?/ O$ e" B, m3 s+ n- f6 U
df['Highest']=df['Highest'].str.replace('\D+','',regex=True).astype('Int64') + {0 Q9 [& l: h3 v. [
df=df[['Level','Highest','year','area','price']]! K2 y3 N9 r( i9 _; X9 C5 u; V
df.head()! ~, w0 m& K! E t P, P, Z* A
9 p1 I. a6 l/ Y4 w Level Highest year area price7 X+ c2 ^% a: L6 q8 ~( ] ~
0 高层 6 1986 58.23㎡ 155万
& B+ a! l' [" ]$ w1 中层 20 2020 88㎡ 155万7 E' `! @6 ]! G1 M9 }
2 低层 28 2010 89.33㎡ 365万, b: b7 d6 N( B( c
3 低层 20 2014 82㎡ 308万# T$ I7 g$ d$ T, |8 |
4 高层 1 2015 98㎡ 117万
( h9 d2 ]% ^6 d/ I, `, H z! d" K1
9 T# W& Q6 o, A# S21 d9 n9 O7 o5 y6 e3 ?$ `! n( r
3
. P7 i, }. z" X$ T$ O4 T( N47 W0 v# p( |2 u% T
5
# W- T: T/ x+ D$ d: G6
' G2 |5 H# b9 A3 M w7. ~( q* U! ^. {
8- Q* X ^: @, D' ~8 p5 v
9
) \: h1 B' {5 P( U10
, _% W' L- Y$ t7 I/ y11
7 l1 Z: g5 E/ N2 W; y* d1 h3 D12; m' l6 S5 f: ~5 R% z
131 q( i# C4 O' S) j* X
# 参考答案。感觉是第二个字段加了中文的()可以准备匹配出数字,但是不好直接命令子组了
9 B& y" G( `- w$ A2 o0 y, M/ r% Spat = '(\w层)(共(\d+)层)'
. ^# O& q. ]6 }9 A: Tnew_cols = df.floor.str.extract(pat).rename(
- p) |) D5 d$ A6 q4 K columns={0:'Level', 1:'Highest'})
% t1 J4 d; Y: z Q* a; e7 q- ]# R7 i, e( ?- d6 G5 `
df = pd.concat([df.drop(columns=['floor']), new_cols], 1)
, b) ]; D9 w% r4 F. x5 {df.head(3)" w9 ~3 x9 {6 U- `/ h
2 d' t: N) n# `: R$ Z- Z, R3 q* a+ VOut[163]: * L) s& t8 k% z" N; u$ ?9 L
year area price Level Highest
; _7 \6 u" X- }0 1986 58.23㎡ 155万 高层 6
7 P, _: r6 ]0 R* c$ e) X1 2020 88㎡ 155万 中层 20) n- B. U" i( G" F# Q3 K" G4 ~
2 2010 89.33㎡ 365万 低层 28
% ^: U. x3 z6 U$ x) F4 }1
' l4 a' h' T' ^7 f4 P5 q5 d. r2
) l' z( p5 W4 @3
7 n5 f& r( R' V; ~49 \6 \ u. V7 i: W' l$ u) u% q
53 r: v/ C# u! f0 n7 U# r+ [) M
6: ~/ x9 D, V+ U$ e e
7
' Q% i: u7 H9 G5 o$ N4 t8
- c9 N3 [- j! N/ K6 X! g7 o8 }. D9' d# z1 \9 ~9 @ z. i' q; ]$ L6 r; B
10/ h& ~0 z6 h w. k
11
7 ~* [ o6 Y3 V( R+ P12
# G* b- t4 q4 F3 Y( x13/ u) g M9 d8 I7 h4 c3 Z1 U0 i
计算房屋每平米的均价avg_price,以***元/平米的格式存储到表中,其中***为整数。
0 f( s) V$ M Z6 m* |# {" }""". \7 P5 N( n4 C Z9 ^$ R
str.findall返回的结果都是列表,只能用apply取值去掉列表形式, u4 h- u% ~7 b% N8 C1 e; Y
参考答案用pd.to_numeric(df.area.str[:-1])更简洁
9 I% w; o! J2 s: B2 d由于area和price都没有缺失值,所以可以直接转类型
) ~+ f/ v! v; y, f6 p* [0 q3 ["""
# j8 \; b$ h M) ?8 jdf['new_area']=df['area'].str.findall(r'\d+.\d+|\d+').apply(lambda x:float(x[0]))3 ?# l; D. k T3 J$ N) B! z0 H4 {
df['new_price']=df['price'].str.replace('\D+','',regex=True).astype('int64'), F, l/ v: m) s w9 I# O* j) K3 M+ U
df.eval('avg_price=10000*new_price/new_area',inplace=True)
% a M: G& c% i4 v" \( p* S C# 最后均价这一列小数转整型直接用.astype('int')就行,我还准备.apply(lambda x:int(round(x,0)))
3 E7 [! n" y% g0 P# 最后数字+元/平米写法更简单2 G2 z- ?# ?2 p3 N
df['avg_price']=df['avg_price'].astype('int').astype('string')+'元/平米'
1 e- U! N0 ^4 G! l1 v; O& edel df['new_area'],df['new_price']
) I7 H2 O) s8 A% D* ]. R; m. Ldf.head()
5 S! V5 H) E' w! \8 l* s) R: x) a( Y6 r
Level Highest year area price avg_price
+ O4 x, v( T: c- X0 高层 6 1986 58.23㎡ 155万 26618元/平米: v" e+ r, ~% K8 n* N
1 中层 20 2020 88㎡ 155万 17613元/平米; Y3 s# F; g! o! d& B" r1 X
2 低层 28 2010 89.33㎡ 365万 40859元/平米, H1 T. B7 }1 m' C
3 低层 20 2014 82㎡ 308万 37560元/平米
: G8 {; ?9 U _' o4 高层 1 2015 98㎡ 117万 11938元/平米
$ K) N% X+ F$ L) a2 q4 G$ ^% Y
% T; h; x* ~6 G) C6 m' m8 r; T3 u; |1, p7 n% d5 m* A8 O5 O1 D
2
& |2 o. j* Q1 v8 u8 y* R3
% K |, M8 C' n& A( {45 a, H( N* z% @ b* J1 R! S) ?
54 ?2 B1 \3 U8 Q. c1 Y! S
6' R8 X* z; S- Q/ L" M/ `
7
. Y& q8 m1 P2 x* Q89 T- i4 f9 D1 C; K9 Q3 q
9( Q) m- I8 V4 x. v% L3 F8 i
10
9 n: Q1 r1 ~0 V" ^( k11
N# }5 c" _# x! u$ }12( Q* T( O* Z q T
133 N: T4 W/ g6 x+ x+ u0 Y' M6 g$ @2 f
14
. m' Z* A- L* m* B: V/ m8 o15 U3 x& c- Y' Q% s9 ~, ?
164 n3 d3 R$ D" s, l8 l
17; N, X( p! {$ C; Q9 T( c0 \, C' u
187 |! q! m2 [* q1 o5 L8 P; N0 X- d
192 n; X2 W6 H8 h3 a8 ?
20
- J C" o' I- J5 T4 u# 参考答案
. Y, A8 R4 D& gs_area = pd.to_numeric(df.area.str[:-1])5 w9 v/ s4 X* ], ~
s_price = pd.to_numeric(df.price.str[:-1])
8 l5 B6 B2 k2 l) edf['avg_price'] = ((s_price/s_area)*10000).astype(
& F# L2 f0 s t" |- [) M! z 'int').astype('string') + '元/平米'$ m2 [& ~* t! y2 u+ A- {& U
4 }5 o ^& r; o- T% r
df.head(3)
% N8 k' `: c9 lOut[167]: / E5 i5 M' G: B! b1 J% x" ]: r
year area price Level Highest avg_price
$ a9 P3 ~, n! ?0 1986 58.23㎡ 155万 高层 6 26618元/平米+ I+ F4 ?$ {* B% A: p
1 2020 88㎡ 155万 中层 20 17613元/平米6 ~! Z2 ]- u. Z* h
2 2010 89.33㎡ 365万 低层 28 40859元/平米
6 m6 B6 }' |9 ^0 v6 Z" C# d1! B. V- }# }9 Q
2. Q: v( _5 e( o% t! a% X; t
3
0 m6 A8 s5 g, @- Z" M7 o4' W, T4 m, s, R# u! n, }8 S; r3 n" R2 l
5
& d3 E" o7 q7 m+ N6 b6
5 `0 R x7 F5 a% d) C7; Z- S0 ^2 p( L t( M
8
5 W6 m2 _ o. O, Z) P0 X. |0 {9+ Y% V2 E# y e% q
10
" C* W% w# a/ q# c# p11
# G2 N2 b P+ n; J12; e2 r, Y; k2 z
Ex2:《权力的游戏》剧本数据集7 J2 E" [; s+ U, G! }) G
现有一份权力的游戏剧本数据集如下:0 ?' W1 ^4 V9 t* Y9 A6 i
: D; s+ q$ K6 N9 G8 qdf = pd.read_csv('../data/script.csv')
- y) ?3 F' d, ndf.head(3)" l8 P6 w. Y1 b
8 I0 [4 M- j G) a$ }% L2 oOut[115]: : C! N$ V4 E3 l' O$ a. ~
Out[117]: + J# ~( @; @ b8 ^
Release Date Season Episode Episode Title Name Sentence" i/ J6 j( W' m0 }
0 2011-04-17 Season 1 Episode 1 Winter is Coming waymar royce What do you expect? They're savages. One lot s...5 i& i$ R `+ ?% O/ J
1 2011-04-17 Season 1 Episode 1 Winter is Coming will I've never seen wildlings do a thing like this...6 D' p! n1 {' [: ^/ P
2 2011-04-17 Season 1 Episode 1 Winter is Coming waymar royce
! q3 x! m4 z' [4 V% l12 @, o, P' D: ]9 R- ?
2. i( Y @" r* T. j3 R$ n
3
: r. L" Q' @8 d* _3 ^46 L* v; w! k# B
54 B' r) z7 i" w' z5 t
64 U) T& i' o/ u
7
/ _: ?0 Y. K& z7 J8 M88 ^& m; J' k6 A( N9 S2 L5 \
9
0 q0 F5 M4 x( B8 [计算每一个Episode的台词条数。 V9 p$ P+ n2 X+ N5 R' x0 E' N
以空格为单词的分割符号,请求出单句台词平均单词量最多的前五个人。0 U! Q- K) u1 Y# T- ~6 N) E9 k
若某人的台词中含有问号,那么下一个说台词的人即为回答者。若上一人台词中含有 𝑛 个问号,则认为回答者回答了 𝑛 个问题,请求出回答最多问题的前五个人。
i' Z4 y6 J. R& H3 d计算每一个Episode的台词条数。6 A2 S% R/ z+ y1 h
df.columns =df.columns.str.strip() # 列名中有空格
% ]; X' _3 r+ N) Q9 ~! tdf.groupby(['Season','Episode'])['Sentence'].count().sort_values(ascending=False).head()2 ?: d9 q' t2 r1 z) H. [
9 r& f0 R2 j6 I1 V: P- c
season Episode
2 k. n2 S: A9 `$ \2 @7 R1 fSeason 7 Episode 5 505
' ~ e0 w8 V( j4 F$ OSeason 3 Episode 2 4801 o. f/ m( M* ~8 E2 h
Season 4 Episode 1 475
+ G2 b8 x1 m% w X0 l! fSeason 3 Episode 5 4401 ?9 L$ ?" ^8 S9 y3 g
Season 2 Episode 2 432
* K" T$ J2 \+ H& s8 |1
9 A/ @$ g- C1 q2 ?, [: l2
* G/ O% ^0 k4 b( H+ o3
( S. A! s% Z2 R7 S, t4: O' R8 Z& Y9 H8 V* B& Q
5- x' B. @( V, `- I
6
* A2 T$ S7 o. T- b3 y3 G5 y7: \% q1 q* w' y. Z5 F- f# s: S w
8
# `8 ]- G0 [& I4 ]/ h9
( j4 ]: l m7 m- P以空格为单词的分割符号,请求出单句台词平均单词量最多的前五个人。
. ]( c( Y' \, m8 T: d# str.count是可以计算每个字符串被正则匹配了多少次,+1就是单词数# d# Y. A A1 u! w! `& V
df['len_words']=df['Sentence'].str.count(r' ')+1
$ B, R, ^6 b2 _/ W8 y) T: O Fdf.groupby(['Name'])['len_words'].mean().sort_values(ascending=False).head() U( L) t% k5 _6 w! ^4 t1 T- L
+ \% `( _5 p& n+ v* f1 y( s' e$ k
Name
" y, a) v7 g% @0 rmale singer 109.000000) o# h& O( b! F* @0 M5 m
slave owner 77.000000
' A) w, A; X, Z/ qmanderly 62.000000
* Q$ P" H! L* ~8 qlollys stokeworth 62.000000( v; C8 R7 L4 o3 w3 O' a* Q6 X
dothraki matron 56.666667
Z [4 n7 D2 YName: len_words, dtype: float64
+ {' r0 h/ l4 s3 v w4 Z; M1% Q4 t1 W0 L2 ~
2 O5 ]" s0 Y# m! {5 R
3
2 p. Z# m: a6 w7 p4
9 f; Y; x3 r+ g& x( D58 K O6 j% P" b7 ^3 R
66 ^1 |% S! `% |- ]
7* s7 z( d. Z6 Z& |$ _
8
! k$ `7 B+ `0 K7 F' w8 _92 C, M6 y/ W9 C9 }
109 F% p$ W- [- b4 n# b8 h7 T
11. x* z \' b) r% J6 j! C1 B
若某人的台词中含有问号,那么下一个说台词的人即为回答者。若上一人台词中含有n nn个问号,则认为回答者回答了n nn个问题,请求出回答最多问题的前五个人。
; A# ^# m$ o2 o6 k. d* Cdf['Sentence'].str.count(r'\?') # 计算每人提问数8 b: H1 j1 M" R
ls=pd.concat([pd.Series(0),ls]).reset_index(drop=True)# 首行填0) e5 D. F4 @0 x
del ls[23911] # 末行删去
! _8 K7 [# f, ndf['len_questions']=ls
( K7 _3 z- l' o2 ^df.groupby(['Name'])['len_questions'].sum().sort_values(ascending=False).head()
, t7 p' H( B8 l" t: }* W
; a( T; a$ n! ^- r% ]2 S, H AName5 Z9 S6 r0 n: P; y( w* e
tyrion lannister 527- Z- v# A: C, E( ~7 }& g- N0 c4 v) D
jon snow 374
+ _( F+ @: w2 ^2 \jaime lannister 283
) g$ h0 ~3 @+ y, m+ \arya stark 265. ~- G, A3 E) O' M& Y
cersei lannister 246% C6 |% J0 V/ G& s
Name: len_questions, dtype: int64
6 L8 k+ t9 {) d% k# E0 D- G0 P2 C0 M0 L0 u& V
# 参考答案
; S4 k4 Z5 ? h; @/ Gs = pd.Series(df.Sentence.values, index=df.Name.shift(-1))
# z* C5 v, m& L6 ^+ f. B# ^s.str.count('\?').groupby('Name').sum().sort_values(ascending=False).head()
9 A0 B% E! O3 t ^! Z0 y, f. z' L6 D' ^" T2 a$ Z- j
1+ `9 H" d! S0 g
2% |4 ~' W' F. x
3
- f' _$ U" s7 u) h3 T0 G) t4! A8 M& Y, y, F. a9 Q9 ^% Q( {
55 B# W2 M# O9 }, j. x0 G6 X
6
+ G6 R; `* P% y6 I" F& Y* p7) |0 v- G; L d$ c- ^- a
8) \1 Q0 c5 p6 g2 Z; V/ t. Y/ U
9
: B/ `2 n. P$ i" N10( l H- ^( j" n' J: q2 B
11
' B3 v8 [/ m$ T, B: {* n) H+ U* t12. H3 O( @1 T/ U9 ~2 W H
13- T1 y& p& N* J& n' P" i
145 i' Z+ b- u9 Y4 k! @' I
15% s8 n) y6 s& |% u. q
16& O) u; A5 L, D- b- d; e
17
- F) Q* \. v; s/ w9 j! k( P8 z$ z第九章 分类数据
0 z2 E# D& C2 ~) M& wimport numpy as np M. b& M0 \- X0 ^9 Q) v/ w
import pandas as pd
4 _( T0 e9 s6 B6 {1
: u# g8 ~7 N# R6 m1 V2
2 @; w2 O: ?8 N9.1 cat对象* [5 J- a; P8 `) |2 m1 {
9.1.1 cat对象的属性% ~) u6 W7 j2 Q1 F
在pandas中提供了category类型,使用户能够处理分类类型的变量,将一个普通序列转换成分类变量可以使用astype方法。1 f8 i! d1 @) @3 I, E
/ q9 u, n' e! N5 @' @8 jdf = pd.read_csv('data/learn_pandas.csv',) V4 i1 M" h4 C4 G+ u9 Z0 E7 f
usecols = ['Grade', 'Name', 'Gender', 'Height', 'Weight'])( O! w" R6 _6 ~% R& A u9 u
s = df.Grade.astype('category')
% k1 |9 X9 K1 A i+ x4 u+ ^! ]) Z1 C' h. S* i
s.head()
+ y' M. v3 k% i0 W. M8 }Out[5]: * e. b/ J! j. [2 h8 [
0 Freshman1 r7 E- n5 S# w$ X
1 Freshman& [5 l6 k( p7 z
2 Senior
; I: X! T/ b( K% S# \3 W( M4 r, E3 Sophomore& V4 ^9 @! Z: W! m2 Q1 m8 k
4 Sophomore
0 s. O' R& n, q# @) W/ KName: Grade, dtype: category
1 A) @- Z2 y8 O4 ]7 u; [. FCategories (4, object): ['Freshman', 'Junior', 'Senior', 'Sophomore']7 f7 ?6 C5 {7 ^ h* W2 L: }
1
0 d% D. u2 E3 H/ m+ {# F: h$ R% C2: p& X+ g2 s: m# R8 z& p6 q" d. H
3
: @5 F/ ?# m! b4 g4( _1 X6 @+ d9 n @/ P- M9 ~
5
d' C8 g$ O7 S- Y6/ |/ J9 T1 J) F$ J+ j6 Y0 y
7- Y1 K3 f8 r7 Q# ~. G& Y+ b ^
8. H% C) F$ P& z3 D' O8 j7 Q
9
$ X2 C# _: d1 y/ C2 l10
8 }, ~- a% V6 j' I+ H11
6 l( P; G, i; k+ W12
( x* X' a- K) x) w' g( n/ G13
4 A' F" G- B5 f( `9 h6 Z 在一个分类类型的Series中定义了cat对象,它和上一章中介绍的str对象类似,定义了一些属性和方法来进行分类类别的操作。
& B' k3 h& D. F# r) R9 Q3 M
) G; K- u! Q+ ]% i zs.cat$ w2 A3 ^8 c. h0 v
Out[6]: <pandas.core.arrays.categorical.CategoricalAccessor object at 0x000002B7974C20A0>
s+ g; v _/ }2 Y& ~$ z0 [14 t+ A+ P# v5 @5 ?9 l7 ]2 M
2
3 L$ T" c1 i4 {: Gcat的属性: p- {: g& {$ _3 B* J7 a
( T+ V. Q# W1 X3 A5 Bcat.categories:查看类别的本身,它以Index类型存储
; d' n$ D5 l& S% Pcat.ordered:类别是否有序% B" l: `& z1 t( z
cat.codes:访问类别编号。每一个序列的类别会被赋予唯一的整数编号,它们的编号取决于cat.categories中的顺序# v4 g- \% D9 d0 _2 _! r9 v6 {8 e
s.cat.categories
6 }* M: I x7 c) t) H3 `8 \5 ROut[7]: Index(['Freshman', 'Junior', 'Senior', 'Sophomore'], dtype='object')5 |, `5 D) h& @; {' h0 d+ |4 ?- }+ @, ]
% n3 Z# E# [* f' L8 k) c) A- Qs.cat.ordered
7 f3 z& L* n' N# HOut[8]: False
6 |) Y: ]/ a1 q0 r9 M
- B n9 ]! A/ ps.cat.codes.head()0 s/ O* }! f; s- Z7 }+ J% D
Out[9]: & b- u6 l' y6 C0 m; D1 j
0 04 O6 @) K! P4 d5 d
1 0! y8 r' a; H+ @, I, d
2 2
( S- V; b% U& B3 36 t( q4 Z& r) Z" l& T) J4 o5 W
4 3
( Y9 j5 s& M# W K% Ddtype: int8/ N& N( H" l* Y8 \, G# d" I
1/ R* l& \/ j8 Q$ m' b+ o$ s) Z1 s
2' y8 ?7 q( q$ @3 G3 M9 O
3
, |# Y6 u( Y1 q3 i" [9 u4
# g- K0 u! i- C4 D( F A1 N59 H& ^; ]8 |# D
62 Q+ {8 W- R3 a0 k# {6 u ?
7
+ g( F5 Y( V( ]" A8+ }' n4 g* i5 R8 c4 |
9
' a4 o. A: C6 M: \5 p7 Z108 z! @8 F" Z! h$ T N; T$ a
11
/ S; p6 L4 b. w* K- ^+ j12
/ g8 q3 H8 f3 ^3 i3 M9 s6 Q3 d13: l, a* [+ h, b# i; L4 m
14
$ A- i/ S8 c- P9.1.2 类别的增加、删除和修改: M; p5 p4 I7 [: [$ @4 O, t& L3 j
通过cat对象的categories属性能够完成对类别的查询,那么应该如何进行“增改查删”的其他三个操作呢?
6 J$ {+ U! G1 k. X# u# z2 X1 t+ e( _% g* P* }
【NOTE】类别不得直接修改
; V& o3 e" ?% l) r在第三章中曾提到,索引 Index 类型是无法用 index_obj[0] = item 来修改的,而 categories 被存储在 Index 中,因此 pandas 在 cat 属性上定义了若干方法来达到相同的目的。" u0 I% g- |- ] I& J% G
& ~/ D. _% a. p2 p6 w+ D% kadd_categories:增加类别1 i# i) k( D" t
s = s.cat.add_categories('Graduate') # 增加一个毕业生类别# v3 ?( K% {1 |) a+ K0 E$ |$ v
s.cat.categories0 |. f0 |: M' O& {
$ `0 n( j; l p
Index(['Freshman', 'Junior', 'Senior', 'Sophomore', 'Graduate'], dtype='object'). R: j1 }& F* C
1
1 z+ a9 \8 v4 n/ P/ X. t7 f/ b4 o s2" p& M6 H% J- Y2 M; y' k6 P
3
9 c1 W4 Q; \5 j+ `( y# k4. j# [" X6 Y% k- _1 Z6 x
remove_categories:删除类别。同时所有原来序列中的该类会被设置为缺失。/ l& Z, C1 M/ |$ r- F
s = s.cat.remove_categories('Freshman')
1 R$ Y7 \% T, ?) {& s6 H! c
( [6 j- x$ S. j/ k8 s4 k2 Os.cat.categories
& n5 W. N% g/ N' p/ x8 R- F1 xOut[13]: Index(['Junior', 'Senior', 'Sophomore', 'Graduate'], dtype='object')# ]' w2 h5 I# W# C) F, u0 {6 N
+ R9 s) n& @3 t8 v k; }5 d' As.head()
' }4 E( I O) W, \; P8 ?. rOut[14]: ! s# L U6 ~5 t a; f0 ~
0 NaN+ ~5 ?+ V w) m" ` v' N
1 NaN& a( G4 w, n% X9 f) H5 h% x/ F
2 Senior; Z3 X F, L N. @ A
3 Sophomore
/ f$ b1 F. O. U, l" J2 g1 u0 g/ Y4 Sophomore$ m" H/ a7 z! k' l) z- a+ s
Name: Grade, dtype: category6 k ?: k% } P6 R, t
Categories (4, object): ['Junior', 'Senior', 'Sophomore', 'Graduate']
& M, E) A, m2 L' k3 G6 H- m1
. ?9 m$ Q( w3 e9 E" j# k9 w27 |$ b c/ J+ x/ @1 V! P$ H# ^
3
/ x- V% m& p9 V) D$ P4
; p! R0 \+ U6 g8 W7 \+ u4 O5 R7 J+ \5
/ k0 r( n6 t" Z- K; H( _4 J6
B6 e' G' }# H' {, w7' l& E L( R- f
8. k5 P9 {9 j0 s+ w% A0 O0 ^! |
9; C( _/ H9 o, [. r' J
10: D. g% N- }: g/ d5 b
11
5 o$ E1 D, n# V) b4 s1 i5 y* J12
' G# g7 t) B# k7 ~! j. P- m: W13# i. U# d. l: s" P5 R
14! M$ _; w5 v- c/ R: E) j
set_categories:直接设置序列的新类别,原来的类别中如果存在元素不属于新类别,那么会被设置为缺失。相当于索引重设。
& Y& i' A& z$ Q m: ms = s.cat.set_categories(['Sophomore','PhD']) # 新类别为大二学生和博士( r. D& W" A, |- t# }' s3 N6 a9 o
s.cat.categories
1 T3 p' `0 t4 A( |7 K( V' q+ _Out[16]: Index(['Sophomore', 'PhD'], dtype='object')
8 C w# \, F S d" r
+ u) S# c6 G8 Z) f8 d, X* ds.head()5 `: z. t: I4 a$ M6 U
Out[17]:
' U' d' L! |# Z' ?7 b% R0 NaN' A2 a3 x5 W+ {5 w! C$ N$ g
1 NaN
' c4 w) t4 ~! E3 v5 _3 ?3 `4 c1 Y2 NaN
! G" j) {5 D! G5 K+ y& J* s2 i3 Sophomore( S! m1 [3 I; q5 V0 i# ]' L; {' `
4 Sophomore, P6 L8 P1 n5 j' K5 K9 {4 K
Name: Grade, dtype: category, F" I) ?4 N |9 J' N( A
Categories (2, object): ['Sophomore', 'PhD'], k/ J" K$ m5 ^
17 W8 {# \: d$ P/ [9 \
2
4 ?1 ~6 P% u; K$ J, l7 P3
$ q3 L6 ^( k( y; `2 b, m45 T$ e v5 r! m
56 G9 _" f+ c+ O8 c7 z3 Y
6
: B8 |/ I' \9 I4 C8 g7
5 w( x8 L( z: t) S8& w% M, h8 g4 }* I1 f" A6 H
9
) V; B' w# c, w, _6 e10" D+ Y$ e, d9 ]/ s" M
118 l. B9 m/ w8 c/ e1 \3 G5 \
12
1 s9 l e6 O& k& V0 ?+ v+ b136 l9 `8 l2 x- h7 {" e
remove_unused_categories:删除未出现在序列中的类别
+ t+ w3 F' a( d/ y. u: bs = s.cat.remove_unused_categories() # 移除了未出现的博士生类别
( Z; m9 j3 }/ r; u. n7 Js.cat.categories
: ~. y% f2 a: Q- d; @8 w! }
( U* L7 u; @3 b! bIndex(['Sophomore'], dtype='object')
& E: t4 [7 Q, I/ U1
4 F$ ]5 P5 a6 w* Z5 j2
4 [4 ^, C: D* Q2 w* @4 P, [$ I) H3
9 f6 r* R3 p: {: {( m46 X7 ?" H* H+ Y! Z' v# d
rename_categories:修改序列的类别。注意,这个方法会对原序列的对应值也进行相应修改。例如,现在把Sophomore改成中文的本科二年级学生:
9 X7 V+ F7 u6 ^! Is = s.cat.rename_categories({'Sophomore':'本科二年级学生'})
+ s' V/ R/ Z" p9 w+ q5 Z bs.head(). [* h8 p4 Q9 T5 g
3 N$ V# W, B+ A- l7 t/ z/ X
0 NaN/ l5 J z# v- k7 k* y! e( ^
1 NaN5 S6 K% T2 w/ @. K8 e
2 NaN
+ ?( ?$ _4 L1 u) z( }3 本科二年级学生
6 c% O) |/ r2 [' F6 l% [4 本科二年级学生
; T! H. X1 [) r4 X3 E4 ~; nName: Grade, dtype: category9 _4 H8 L9 d4 w% A/ \" r2 v' {8 \
Categories (1, object): ['本科二年级学生']7 Z1 m/ c+ x) [, n: P; y v
18 o3 z3 j9 m4 p& ~
22 O* z) ?2 c: R5 X! U7 I! A
3
* y# N9 ?/ c0 r. |. P" W( [0 d1 n4
- E/ |4 ^$ Q4 R) X+ F5
8 q4 _0 x4 ]* h# n: H* S1 f, B2 d6
* K+ z/ @) @$ Y0 x7 I8 @: h1 h( ~4 I1 j3 l% j
87 e- A. {: I! k
92 r0 r, A8 a4 L0 `3 j
10
6 m S5 g7 q/ k, G' d9.2 有序分类
+ {* o8 f( a& k7 T2 {. U$ B9.2.1 序的建立
. |1 e' g. V: g% i 有序类别和无序类别可以通过as_unordered和reorder_categories互相转化。reorder_categories传入的参数必须是由当前序列的无序类别构成的列表,不能够新增或减少原先的类别,且必须指定参数ordered=True,否则方法无效。例如,对年级高低进行相对大小的类别划分,然后再恢复无序状态:
3 b8 _, v7 w q0 o+ E7 k
2 n( J6 t0 n; q0 g5 M: h# @s = df.Grade.astype('category')
7 y1 w8 |' y0 |s = s.cat.reorder_categories(['Freshman', 'Sophomore',9 O" M/ ]6 H# H- O0 X; B1 w8 g
'Junior', 'Senior'],ordered=True); n1 m( D: `, z1 i8 o
s.head()
$ n0 q7 u4 b7 C' U+ v7 POut[24]:
% @6 I" J; n! l% s0 Freshman3 _2 e* F4 l% o( I- M
1 Freshman
2 u- W) r6 q0 p6 c+ |2 Senior7 R. @ a$ f5 G7 e3 a6 `1 d1 X
3 Sophomore
1 F `. G5 I; s% X4 Sophomore
( P# z: ]' y7 \' Y# yName: Grade, dtype: category% Q6 E% P7 M p/ E X
Categories (4, object): ['Freshman' < 'Sophomore' < 'Junior' < 'Senior']! H3 n" [0 d* x K. m9 Z. E3 t
: a# r. y% z& `$ F( [, y9 W& Vs.cat.as_unordered().head()+ e7 T7 E+ I: F& z$ z5 d7 M- W, w
Out[25]: * _) d/ ^& S$ ]5 u
0 Freshman
" {; h" W3 b1 d1 j1 Freshman U+ e7 g) Z' m ^9 w
2 Senior: \- o; _) e: m" ]- l0 l( ?4 x7 E8 p
3 Sophomore- R* Y9 r0 l5 J3 |, s. x# a
4 Sophomore
) b/ S+ \8 r- U5 [) ~3 e WName: Grade, dtype: category
6 f" Z- g/ N5 W8 tCategories (4, object): ['Freshman', 'Sophomore', 'Junior', 'Senior']
+ J1 `( o9 g2 {/ s/ i: b" L
4 a. i) X% B, O( h1. Z, Y7 O5 R2 Y; [ @. j
2
; h+ j' q4 T' L ?5 ^* V) B7 E3. X+ ?2 N$ W8 ]+ I
4
- E# n/ }7 R; r, q3 r5
& m3 B9 `0 ]5 R. R6" z1 w2 p$ ?8 d3 c9 V/ O) C: |0 ]
75 G; P+ M5 C; ~* f& H
8
; ^; K2 }; n7 K$ q u& J& K. q5 \5 n/ g9. F' ~; I- @) D- u" L8 [
10
2 |4 ?- V) `/ }, ]5 [11& l0 U, A8 l. `6 I. s
12
. R2 {0 F* }6 E) |; v8 F9 W* I13! q# D! C# A# }
14
, U* ]8 l( N( P7 p15+ E2 X: r2 Q& p# V
16
3 d6 v7 C6 S. [17
# `/ {$ y' H+ `. G" W18- _& j3 L2 l% ]$ X& H- U* I
19
2 v, z# G% Q+ ^3 S" F0 M1 A7 G20
- D# K% s- a6 j+ d0 ~( t7 o21
! b' J( J+ o- {: @22' H4 h9 d, G' C, K
如果不想指定ordered=True参数,那么可以先用s.cat.as_ordered()转化为有序类别,再利用reorder_categories进行具体的相对大小调整。5 T7 N6 d7 [" T+ R
+ [; E! n' J3 M) e9.2.2 排序和比较
( B1 i+ e' l/ {0 M- `) u8 L在第二章中,曾提到了字符串和数值类型序列的排序。前者按照字母顺序排序,后者按照数值大小排序。
, Y$ m1 s' ]8 j' _( ]
+ \5 A- L {& f F& r* @ 分类变量排序,只需把列的类型修改为category后,再赋予相应的大小关系,就能正常地使用sort_index和sort_values。例如,对年级进行排序:
4 r. d0 j- q8 d/ @" ~! M7 ~$ p# T7 t7 y* [6 }9 N/ e
df.Grade = df.Grade.astype('category')- e8 c! k7 N' w4 Z* L- h% I
df.Grade = df.Grade.cat.reorder_categories(['Freshman', 'Sophomore', 'Junior', 'Senior'],ordered=True), T& F: w' h. z. u H1 Z& E
df.sort_values('Grade').head() # 值排序
# \' m0 n' \* j, N( ZOut[28]: i/ |1 v' Z" R3 O6 ^$ s
Grade Name Gender Height Weight+ y0 M \( Y7 z+ @0 F& E) F& ^( g
0 Freshman Gaopeng Yang Female 158.9 46.06 i& d4 s s6 h3 B5 P& y
105 Freshman Qiang Shi Female 164.5 52.0
: u ^2 }/ Q* s+ g96 Freshman Changmei Feng Female 163.8 56.0
1 [% G; B+ P2 J( p5 c88 Freshman Xiaopeng Han Female 164.1 53.01 n6 q2 U. o# F2 D) V! e3 V% E
81 Freshman Yanli Zhang Female 165.1 52.07 p# p, a/ f S+ J( m9 \8 K
" h0 L& A) E) K6 J* N, A
df.set_index('Grade').sort_index().head() # 索引排序# L, h, w0 d" g4 |7 Y6 ~
Out[29]:
2 n( |# {# v$ X( Y% x% L' u3 E Name Gender Height Weight! Y% h3 }* g' C- q$ R
Grade
3 E2 \; A1 ]) Y7 P; ?/ RFreshman Gaopeng Yang Female 158.9 46.0
8 ^8 E6 F0 S+ v# h9 P5 I" }Freshman Qiang Shi Female 164.5 52.0
* t+ H7 x2 a& \6 d* [" ^9 QFreshman Changmei Feng Female 163.8 56.0) D9 f2 t9 @$ p3 T& h# w4 c
Freshman Xiaopeng Han Female 164.1 53.0
* O+ W9 ?" S4 }1 N& t% kFreshman Yanli Zhang Female 165.1 52.0/ W1 B0 @$ d& v! W8 g9 V4 e4 _
: {, G/ Y7 D6 S$ Z
1& f7 Q" I: |6 {/ r
2; X* A7 b' a: I- ^
3
( o# V1 ~" W5 Q4
8 k3 a: m) d4 G6 \6 B2 `5
0 l Q1 j- j3 c6 l0 S6 o! q6
3 P2 o9 z. v+ i* f7# }1 ], i* W4 M7 w
8
7 c9 j5 t6 }& Z2 S9
" @* h: f, }7 s K: h6 x- k; J108 B( [0 s7 x+ q& C2 X
11
2 K1 @! \! j0 M) }! ~# K' I12
8 V& \7 c0 e; K7 J2 Q! v2 T138 }. j$ @: k- {+ V. b( C
14
6 S4 z2 T [/ f# \- O8 X* v15
( Q8 `/ x3 u0 z& L7 u- v! M164 S! Q6 x, N, I# ~8 |0 w
17
( }5 a; r0 O5 f+ f1 z; K1 {* M18- n+ M% a6 V9 k9 L" M
19
; K' T# F3 A" D/ k20, ^( K6 r E4 {% g. t n
由于序的建立,因此就可以进行比较操作,方便后续索引操作。分类变量的比较操作分为两类:
) e7 T6 d# _2 k8 R1 |9 H4 {! P9 S* [/ u) ~
==或!=关系的比较,比较的对象可以是标量或者同长度的Series(或list)。(无序时也可以比较)* b' M4 R5 ?" i0 }1 C
>,>=,<,<=四类大小关系的比较,比较的对象和第一种类似,但是所有参与比较的元素必须属于原序列的categories,同时要和原序列具有相同的索引。
% i5 a" y2 q4 R, W$ cres1 = df.Grade == 'Sophomore'
* ^/ ^9 G2 ~, a, ?# ^0 @
* E( U* w9 C* S) l0 D+ Y7 gres1.head(). l, E) f* N0 A0 y) L) r
Out[31]:
+ c) [0 u+ V4 Q0 False1 W/ l# f L* R2 L0 s
1 False
3 ^; T0 W- h% x9 h: ?2 False/ E3 t2 H Q% J
3 True( o( m7 d1 X# k6 w" ]1 M
4 True& U9 L$ E+ a$ l3 r V
Name: Grade, dtype: bool
1 v! S: T: K3 v& Y6 X- ~+ c5 R8 o! C l& R
res2 = df.Grade == ['PhD']*df.shape[0]/ R8 i( y2 }* \+ W0 ^4 R
( s: N/ Z( c! s- _; {
res2.head()) z S" L* Y! B7 Q& U9 ?# ?5 X
Out[33]: : ^8 _! a% O3 r/ y" m/ w8 V4 k
0 False
' }0 u" `& C& U/ L; Y+ ^, ]: g1 False
2 E" P {' ^8 `2 False+ H4 z$ Y2 Z- _) p
3 False$ @0 j1 P' B1 d7 o: D: s+ I' }
4 False! G% _) _8 s% O9 g* V
Name: Grade, dtype: bool9 K: [1 |9 R8 x. w! s6 m0 }
' b/ @- p* j" N6 j0 i2 n3 Gres3 = df.Grade <= 'Sophomore'0 c ]6 a3 I* x& L( I; W2 u" x
# ^$ o$ ~& V: [0 H1 k
res3.head()/ n6 W0 i% n3 n! ~" S! G* S6 t
Out[35]: 7 G# O- V4 O( x* B
0 True
% s7 v* Z! [% j, g1 True
0 i% S' ^# \- n2 False4 c6 f, M1 Y2 x8 L3 _: P
3 True
: ?/ {8 b! W4 M& ?4 True
2 X, ^4 Q( q# X, }* ^! K5 {Name: Grade, dtype: bool7 z) q: A1 K4 \! Z+ [3 z b, x
1 N g# y3 K, I6 T# d/ Y3 h8 U
# sample(frac=1)表示将序列随机打乱。打乱之后索引也是乱序的,直接比较会出错,必须重置索引。
3 N9 N+ Z! i2 Z4 Y2 d! Eres4 = df.Grade <= df.Grade.sample(frac=1).reset_index(drop=True)
# m7 H4 q/ `# J) o1 ]/ O' Q
5 H9 _: j U+ i9 yres4.head()+ [1 L9 b$ }5 m1 Z9 T3 B
Out[37]: ! A# N3 j# s$ `/ J' d
0 True
% Y- a" |. N& v; F/ k1 True( E1 k* u4 Z: m6 c. f
2 False) H0 m3 M$ M. ]8 e3 G! L$ h
3 True
: |+ I4 e/ U; h4 True% d# A5 P$ ]) ?# h
Name: Grade, dtype: bool
+ O( J& _: Q0 w$ [2 q
; Y1 b6 o! P9 U+ B+ v$ ?0 B1+ h! \# h3 c% A) b1 m1 U! [
2) B% j/ c, L# l, z9 h. u+ ^9 V8 C4 n
3' W5 ^0 C- ]; a6 _2 [
4
R# {& \3 c) x: W; \8 N% v( l! V. N5
' J3 S9 y1 i" [, Z1 s4 `9 q" L63 K# d9 Q; r1 L: O
7
& |0 ?) s# K& s2 e& m' {6 k8
! N% S3 v+ q* |5 z6 l+ Y8 p9
# K L! {/ c* b) K" _- e1 Q# T106 r- Q4 X9 C) i [0 S5 |
115 x8 I t5 D! Q8 F5 H
128 R8 X/ n; F7 @7 w! `' j% @
13
6 n: V: h; ?) e14
" k: e7 f; R- j) v3 m3 n+ A1 u- r157 h1 Z2 b- Z% H4 y( @( s
16
' t* V9 b& z- k! Z3 F d175 Y. k& n! I- V! w! e9 y
18# m1 G4 o% {) d5 O1 `# ^1 w
19. B+ ] b' W q1 y; V& V$ P
20/ F1 X0 G) o$ X! s+ N
21
; e6 Y: V# T4 ~, r3 b) ^22
$ ?( v2 g) x5 T2 s4 ~# \23: L4 X7 C& T) ]
24' b8 A- N5 w Z2 }" s
250 H8 J( L* M: L3 u" e) O
266 S: N# W v9 a: i
27
1 |! S# A" s; t& I( z+ A28( e7 v( p5 R7 ]# \: @* Z' s, K
29; V, \' q( p7 O/ I# L: E' G. W
30
[/ z d; d* o31
$ I/ A0 `" @' x. Z3 t, b32
* r3 ~# x# [7 w; f! c0 ~33
) p: H) \# @/ E0 M8 C* y" x34
3 P0 O- v5 C" L% k) O6 K35: k, w/ x6 v6 @; d6 ]+ T- A" p
36
$ R6 o% d2 h' t I) n8 R8 R) Q$ E37
+ m0 j+ C4 S( M: {4 p! P3 p38: D; A7 d; X: B
393 e# D* m9 m; V) ]6 i
401 w. u# a3 t, a- H
41
9 M, x& C( E& D0 h( l W, X' n9 F0 _42
: Q P& L: c2 ?2 B" h+ l# \43. B9 d; z ]( e. g
44
! F0 s$ ] r1 H1 R; K1 b* q9.3 区间类别/ E' w8 c6 c. M6 V1 l$ u% k' p
9.3.1 利用cut和qcut进行区间构造, q P* |' }, T8 n+ y
区间是一种特殊的类别,在实际数据分析中,区间序列往往是通过cut和qcut方法进行构造的,这两个函数能够把原序列的数值特征进行装箱,即用区间位置来代替原来的具体数值。
]3 j4 E0 U0 w: @5 w9 S# a4 k7 s# W3 K$ h8 S2 E7 C `2 E
cut函数常用参数有:
+ p/ _2 O9 {* l/ i( @2 R3 V! ]bins:最重要的参数。/ k% Y: p3 S' `
如果传入整数n,则表示把整个传入数组按照最大和最小值等间距地分为n段。默认right=True,即区间是左开右闭,需要在调整时把最小值包含进去。(在pandas中的解决方案是在值最小的区间左端点再减去0.001*(max-min)。)4 I# s" O, u8 F
也可以传入列表,表示按指定区间分割点分割。; a2 q) ?) Z. }2 y. g, B
如果对序列[1,2]划分为2个箱子时,第一个箱子的范围(0.999,1.5],第二个箱子的范围是(1.5,2]。
4 {2 s0 B1 f" n! r* U 如果需要指定区间为左闭右开,需要把right参数设置为False,相应的区间调整方法是在值最大的区间右端点再加上0.001*(max-min)。
+ V5 [! `. u4 G$ c' p) Z' l9 W" u1 }) w! k
s = pd.Series([1,2])
: R u i% m, s9 r8 F# bin传入整数$ R9 _0 o! {- z: Z4 R$ g K9 y4 w6 Q
' Y4 i# o+ f. D' R7 S6 gpd.cut(s, bins=2) P+ Q) S( z6 \, T* `# }
Out[39]: 1 `, q4 A% c3 M9 H
0 (0.999, 1.5]8 R3 k$ [% `; t% W/ h1 ~, y. Z
1 (1.5, 2.0]6 N* H9 R& d- x( p5 N) V" A
dtype: category- A" u, w5 h# X: P7 n0 d
Categories (2, interval[float64]): [(0.999, 1.5] < (1.5, 2.0]]; n- B% ]* {7 l
0 w8 H6 ]$ L$ V. Q: w2 S4 E1 K# g5 R
pd.cut(s, bins=2, right=False)# b( E* F4 B& [: W! i
Out[40]:
" ~# K+ R0 e3 k- x; _% M2 V- ?0 [1.0, 1.5)
& ~, {" m _* v3 I2 [+ M1 [1.5, 2.001)
c, c7 W. q, E! ^: G) c6 Jdtype: category
4 s) p9 K2 q0 }+ R3 n0 QCategories (2, interval[float64]): [[1.0, 1.5) < [1.5, 2.001)]
2 W0 S4 t l# `, J
2 U9 i+ b) g8 y, B) w8 Q7 G; a. [+ `
# bin传入分割点列表(使用`np.infty`可以表示无穷大):9 N( y4 u) W$ g$ O0 T1 [9 G. j0 Q* K
pd.cut(s, bins=[-np.infty, 1.2, 1.8, 2.2, np.infty])
! s8 N4 e5 n7 B8 u* ?Out[41]: 2 M+ J! v6 v8 I- b
0 (-inf, 1.2]
8 C! I* d9 c8 y, F1 (1.8, 2.2]+ d3 q2 O1 E/ n1 z7 ~& Z* c y- i+ t X! [
dtype: category; @, V$ j4 B' a6 n
Categories (4, interval[float64]): [(-inf, 1.2] < (1.2, 1.8] < (1.8, 2.2] < (2.2, inf]]+ M" v+ Q9 k# i2 }
! \. X" |* U8 u: k. n" T' \' A# x
1
- ^9 I# D& H' `) W6 M) R9 c" g20 P K1 @: [1 ?8 F
3
& Y* h9 D, ?7 x% |- T8 f4
) } z6 Q/ o* M7 c/ E; e+ ?5
, a2 w! C( [2 m* {3 C ?0 ~6 v6
. C A8 |) X( c4 E4 [0 M7
4 \6 X: I- a' N8: ~+ {/ a- _* {+ F% a
97 D( O5 q; f* K- l4 x/ O# p! l# z( E
10
- N6 ?4 w/ M4 n* I11" X! Y8 [! X5 Q" h$ T
12
* Q; ~8 l( V% B2 p+ F0 O* `& Z r135 l+ B4 B' o5 N- o4 W
14- Z1 q. E) L0 e& K' L
15
) b: Z5 l/ Q; m0 \. Y$ y1 w; a165 j, r- W8 X; @8 T% M, Q
17) W, ~% h) R& `9 C( m
18
/ B% [, f. {7 \% r4 R19
, d0 n' Y; Y1 w8 e& o h8 T20; B4 j0 u! b+ w7 s: O
214 [6 V* p4 K2 I- P B) w7 M
22
* O X4 U. S0 ~238 F! g ?: M! l+ u+ @+ B% W
24* E0 B. z: o: N- }
25* ?2 [- F+ w. v: b
labels:区间的名字6 j# o4 t" L* S! d7 d& m
retbins:是否返回分割点(默认不返回)8 o% n% u- K) O+ q/ N5 i& b6 M
默认retbins=Flase时,返回每个元素所属区间的列表3 O$ m) G8 i$ n% W# {* C
retbins=True时,返回的是元组,两个元素分别是元素所属区间和分割点。所属区间可再次用索引取值
6 z% M1 a& x; N
, [" G- c% @+ es = df.Weight
) T7 R& @3 H7 s( x) }res = pd.cut(s, bins=3, labels=['small', 'mid','big'],retbins=True)6 C' I9 [: I( x6 P5 t8 ?. z1 i$ V2 M
res[0][:2]
+ ?7 c5 l; z" d4 X( E
) A2 X3 q# E; s2 F$ @# I9 MOut[44]: 3 }0 C% B' _. f7 K; ^
0 small
+ @3 D2 \6 X; N1 \" D* R4 y: I1 c1 big |( r4 l; l' v% T5 X3 @3 E
dtype: category
: u- X/ g4 r" Y- jCategories (2, object): ['small' < 'big']0 T5 n+ t& o9 m
4 c. | e5 O9 o3 x7 z
res[1] # 该元素为返回的分割点: @; W: k2 P7 z: b. U5 ]
Out[45]: array([0.999, 1.5 , 2. ])* p) R1 G& H6 e. E6 m, f+ C% n
1
c6 e- t2 m2 i- m- Q) \6 E, t. y( a2& x% |4 A* l0 P( ?' S
3* q: V5 [% k, f7 `) k1 \) `: ^
4
4 m% ~) f' U& W, S$ n51 r( |$ h1 w6 [: S. |6 s! o
6
, }8 A' z8 @5 l7 b9 t' `7
1 i4 { o, H9 w8
6 e: V0 x$ I1 Q+ d! i9
9 u2 f5 l8 U6 C4 ]$ v' W. ~7 q+ V7 R10
7 n" S P2 t3 m6 `& P11, u! E. _( k& `9 t4 |& s" K
12& o& E4 N$ b! N4 \6 H2 a
qcut函数。其用法cut几乎没有差别,只是把bins参数变成q参数(quantile)。 q* Y3 D4 r6 a4 {5 z" g
q为整数n时,指按照n等分位数把数据分箱
* z+ [1 E6 Y7 n2 q6 p* jq为浮点列表时,表示相应的分位数分割点。
- D( N5 d) b9 o( I4 Ps = df.Weight
* Z* u3 W1 d z5 R, V
! g6 J# h' y) s3 `. upd.qcut(s, q=3).head()
7 g* k) @8 Y, I3 o/ e, W, ZOut[47]: . a% G; j' z' H, K- }1 n& X) q
0 (33.999, 48.0]1 l" B! S2 v: v5 A
1 (55.0, 89.0]+ c" P3 J) G X. [) \* h! |
2 (55.0, 89.0]
0 \/ v6 m) N* j" Q3 (33.999, 48.0]
0 P' x, I& t6 F- N( M# `) m( F4 (55.0, 89.0]8 v R# U0 L; y& ?' P- L$ T
Name: Weight, dtype: category
5 k# x: g' i0 j/ rCategories (3, interval[float64]): [(33.999, 48.0] < (48.0, 55.0] < (55.0, 89.0]]6 d" W% F5 b0 u% K. T4 ~9 U5 p
5 }8 S( H% x( V7 R/ T5 O0 i% ]pd.qcut(s, q=[0,0.2,0.8,1]).head()
2 r4 f) I6 V. I3 b. N1 Z* c y( D& iOut[48]: , h! `9 g" u. z5 X+ `6 _! g
0 (44.0, 69.4]
9 N% B. t( `! g2 E- ]; n1 (69.4, 89.0]
+ A6 G: ^8 O- B2 (69.4, 89.0]
7 s, g, W! O6 {4 \ r- t3 (33.999, 44.0]
* g* f' f0 z m4 (69.4, 89.0]- ~# y4 `0 |: ]5 f$ j* ]
Name: Weight, dtype: category
, N5 O s( U. A) ~; v uCategories (3, interval[float64]): [(33.999, 44.0] < (44.0, 69.4] < (69.4, 89.0]]2 ?1 I, g; B$ e9 d3 v
7 C$ x( g$ ]+ W( n1! v2 H! |& c' ?$ {0 ~ c
2( R" [: l8 t* W2 b4 y
3* X8 y- Q, [5 N; Q
4+ `: m# r# Y" r$ y/ P% U. z
5
* a8 Q0 q3 [" i6 e0 X8 z6
* F: P& K' p# n3 i# `) j7
& ~: Z( V, g! ?" D1 M. G$ _ m8
7 t/ k- Z) b% P5 ~% p9
& i9 E. Z2 s5 b$ R5 x: b104 T$ f8 }. r% `% S- w1 T
11
+ P% ^) r/ }* u; L12$ @$ b Z% H1 o! |! P3 Q
13
4 V8 a" O/ H* q4 `* u& l0 R) Z% m14
8 Q8 D6 U/ i# p! K! z& ~15/ d7 n: M# ?+ ]$ a0 v
16
' B* s5 D2 e( y+ C" O175 W7 Q4 ]" v/ p7 x: j3 w& [
18
4 ]! n, |0 @7 M3 T19
' k" @/ |+ s$ {9 r- O `$ T- R$ C20
: E5 I) G6 B D. O$ i21% |2 ?" C0 V" Z2 X/ Q1 A
9.3.2 一般区间的构造% d/ F: g- k" l& D1 G6 }' U
pandas的单个区间用Interval表示,对于某一个具体的区间而言,其具备三个要素,即左端点、右端点和端点的开闭状态。+ v% J5 I; H& j' a3 U1 |
- x& `7 W$ D# ]0 ?( |2 g/ X开闭状态:包含四种,即right(左开右闭), left(左闭右开), both(两边都闭), neither(两边都开)。, p1 G+ o# W( J! T! r
my_interval = pd.Interval(0, 1, 'right')
/ f/ A; ^, M+ B! _
1 u2 A4 h: j- G3 l; Vmy_interval4 u0 b- O- t( R8 E- e
Out[50]: Interval(0, 1, closed='right')
" R) O/ I, u6 \! l1 B1
/ W; |/ F* r. `8 m4 x8 k% ?( |2
4 W1 V0 X7 a9 E+ D3% \. L f4 o' m5 G9 M+ Y
4/ e* `* A, Q$ O) m9 W- H% E9 D
区间属性:包含left,mid,right,length,closed,,分别表示左中右端点、长度和开闭状态。6 l' i' O% T. V( |# P" l4 Z
使用in可以判断元素是否属于区间
+ M5 V/ C0 T# _- v2 o- U用overlaps可以判断两个区间是否有交集:
- F B# U2 {5 t2 E* K0.5 in my_interval. U+ S n( U* ~7 K0 P
& }- N9 j% |+ n" ?8 O# W& D4 Y6 j2 q
True
. U$ k+ B% J, b- G/ h1
, I: S- g8 s, ~& o20 X, H- q+ q$ J' M D* g: r a
3, w8 o! q6 i$ _2 K& G
my_interval_2 = pd.Interval(0.5, 1.5, 'left')
+ x0 S7 K9 Q: [' X6 |, Lmy_interval.overlaps(my_interval_2)( ]; l. t7 o, M! B1 h
7 z; V9 ~% g7 q2 S( N2 ~6 t/ QTrue
' w+ a1 }) }/ r5 ?- v1
0 Y% c: h9 k! U; S1 t2
& y1 x& P1 V: c. e: T3
$ R4 d) s, C& i# w; \46 F6 ~4 ` V0 Z6 v9 Z5 ?
pd.IntervalIndex对象有四类方法生成,分别是from_breaks, from_arrays, from_tuples, interval_range,它们分别应用于不同的情况:
/ i- N( d) }( b- d! Y
- L; W/ ?# ]+ `from_breaks:类似于cut或qcut函数,只不过后两个是通过计算得到的分割点,而前者是直接传入自定义的分割点:+ L2 `' b9 O8 D/ J% \9 a& m3 e
pd.IntervalIndex.from_breaks([1,3,6,10], closed='both')
% r9 u$ K9 G/ J- [1 A' k8 q: ]
) g/ L+ L$ m) W* N" N' ~IntervalIndex([[1, 3], [3, 6], [6, 10]],, [3 A3 `/ l1 b. }3 d% R+ m
closed='both',
& f3 f; Q6 ^# J \( Q* J, w dtype='interval[int64]')) b. @7 Z2 n p3 @
1
9 ?3 u/ N6 u: V3 ? l- ?7 M8 G% u' p2
) X: z& R" ?* e8 ^& }3- c* F+ y8 s# F
4. I1 T" O. j3 {0 B
5( ^0 ?! y3 U! @6 K7 J# |
from_arrays:分别传入左端点和右端点的列表,适用于有交集并且知道起点和终点的情况:! n6 X! E0 @ M: p; J" r
pd.IntervalIndex.from_arrays(left = [1,3,6,10], right = [5,4,9,11], closed = 'neither')/ V O& S3 v& m, u, O
# h6 x2 K4 K; k: c3 z* v6 s3 ZIntervalIndex([(1, 5), (3, 4), (6, 9), (10, 11)],
3 Z7 C$ U! a9 L+ B$ L closed='neither',3 _7 \# v* a) ]5 K) ^6 d( w x
dtype='interval[int64]') h3 c6 c) d, P3 n# ]
1( t, Q3 N, w2 Y+ A
26 X6 d5 _5 N& \* T
3
! Q9 M" e0 Q9 S0 ~. C3 [3 K$ x/ h3 e4
n! |: T& S' q; O5
7 S/ |7 |0 S. y( f9 d. dfrom_tuples:传入起点和终点元组构成的列表:3 }/ v* X0 g: K8 B8 y8 m
pd.IntervalIndex.from_tuples([(1,5),(3,4),(6,9),(10,11)], closed='neither')# j8 r: t9 _) R' |4 e; L: z1 ^) T" ]
9 {1 S- L2 p: j3 ]7 i4 }
IntervalIndex([(1, 5), (3, 4), (6, 9), (10, 11)],( b5 n8 O1 h% j
closed='neither',
. ^5 a2 R0 L: t$ I6 |3 s dtype='interval[int64]')3 f- f* y1 d% N: f. b- g! |
18 N* b4 q' x' X9 @
2
) `$ S6 P: O( p0 l3( ^+ s* c$ G; G- T* x
4
+ }% \( [3 G3 I R& [. s' o/ F5 g! N; R0 I1 o+ i4 ?( `) \
interval_range:生成等差区间。其参数有四个:start, end, periods, freq。分别表示等差区间的起点、终点、区间个数和区间长度。其中三个量确定的情况下,剩下一个量就确定了,从而就能构造出相应的区间:
* q! S2 M9 M; a* T+ p8 Jpd.interval_range(start=1,end=5,periods=8) # 启起点终点和区间个数
& [, M$ b3 u) E3 }& oOut[57]: 2 E% N9 z& m! P) p: n
IntervalIndex([(1.0, 1.5], (1.5, 2.0], (2.0, 2.5], (2.5, 3.0], (3.0, 3.5], (3.5, 4.0], (4.0, 4.5], (4.5, 5.0]],
, H: S' b8 ?$ w" j closed='right',; l5 |2 _* W6 M' A8 `8 }" `
dtype='interval[float64]')
4 m* U2 |2 a% C( b1 |
7 n3 ?' H3 P. D( Mpd.interval_range(end=5,periods=8,freq=0.5) # 启起点终点和区间长度4 \/ _* f+ c$ s* ^9 x
Out[58]:
7 r7 S. q/ {2 R2 {5 FIntervalIndex([(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]],* Q7 C5 [$ f& x; }3 z" f0 ~* w
closed='right',
. S+ i' P3 Q2 z. ] dtype='interval[float64]')- }4 [' Z, g4 g
1
0 b2 Q+ ]+ c/ O% b$ j4 S2
+ _. Z( ^% p B4 y$ S9 A3
1 D1 T; U1 c3 \2 \ q4 K4
( w0 \! ~, u9 V& v& D# \5
" v' | e. v6 K- W3 o# P, ~- t6
4 k* ]! \0 e6 I0 `7
1 w" y- x0 @7 Y4 Z9 b8 w+ d# R7 ?! r5 ^. x! D/ G8 L( s
9- g9 U: q& y, _: }5 \
10: V9 A7 T8 c/ ^- j$ S! {
11
# `- o4 e8 e$ J9 k, U【练一练】
2 _* L2 C. ]6 x( L: ? 无论是interval_range还是下一章时间序列中的date_range都是给定了等差序列中四要素中的三个,从而确定整个序列。请回顾等差数列中的首项、末项、项数和公差的联系,写出interval_range中四个参数之间的恒等关系。+ h8 c4 s8 D7 f4 f! S: M
7 J; |; r8 Y0 q! [ 除此之外,如果直接使用pd.IntervalIndex([...], closed=...),把Interval类型的列表组成传入其中转为区间索引,那么所有的区间会被强制转为指定的closed类型,因为pd.IntervalIndex只允许存放同一种开闭区间的Interval对象。
% {, t. K4 G' n$ H7 R9 u; R5 h! w! o6 F4 A* }4 o8 |: V
my_interval( w4 \ @( w7 o* w! i+ Z
Out[59]: Interval(0, 1, closed='right')
0 z, B1 @$ m/ T- R7 |* m" ~
3 V2 W- f: N! {/ `( S6 Vmy_interval_2
! a6 p( S2 K; s- cOut[60]: Interval(0.5, 1.5, closed='left'). J! E0 h. u- x( M! e! S7 W8 v" X
( |7 Q0 A/ e8 c0 y) L& _! Z
pd.IntervalIndex([my_interval, my_interval_2], closed='left')3 W" o8 [" ]* Y) X
Out[61]: + Q/ u# ?% ~3 I6 U+ T# }
IntervalIndex([[0.0, 1.0), [0.5, 1.5)],
, f o3 ], D. ~: H; {$ H# n closed='left',
9 L2 I: j* U% g9 B( N, { dtype='interval[float64]')% @+ s: c( z) m# n: l
1$ f4 M, j: E: w
20 n% Y0 Z! o# i, H* D/ t/ D
3% w9 d' v* O- i5 {: p1 C Y8 L
4
! N7 h: q9 s* }. v$ I& n- A% D5
1 B9 |' f$ ?3 Q0 B; A5 r6/ C6 p) n/ r; O/ r6 C9 T1 c' v7 U
7
% o' ?! g, n+ T \8) Q, D! l( @5 }6 y& q2 W# L
9
$ }$ r$ F2 v/ J- w9 q10
7 K/ ?8 i" q1 m" }: D' \/ o8 a116 ]# r. z) c( {) c' u
9.3.3 区间的属性与方法
/ j- @5 g+ m1 s+ j' ~ IntervalIndex上也定义了一些有用的属性和方法。同时,如果想要具体利用cut或者qcut的结果进行分析,那么需要先将其转为该种索引类型:6 R: V2 ]# z# T4 j1 S9 k. U8 X
4 q/ N7 Q% T! ~
s=df.Weight* A) M0 V* i4 j% u' w: L
id_interval = pd.IntervalIndex(pd.cut(s, 3)) # 返回的是每个元素所属区间,用具体数值(x,y]表示( m' a% K# q8 d
id_interval[:3]' C% R1 J/ ~" b H! S1 l1 F
; p4 ~2 J% O5 X; `& c# N. p
IntervalIndex([(33.945, 52.333], (52.333, 70.667], (70.667, 89.0]],
3 v+ L: K& O! s& i* i* } closed='right',
$ g2 Z3 l/ s" v5 s. B; _' H+ m name='Weight',! C _( L8 Q+ H3 M4 K! N$ K
dtype='interval[float64]')0 x# [! |) d7 Q1 }0 s; b) `! W: I
1
8 Y1 q, K( N4 @5 D: c4 h3 ~( p2- u9 @' n( S) H
3
; i. R p8 t9 e* Y$ g" a) [40 g( M: d0 M' B- l
5) q4 `* C. n0 f- q* Y, m+ Q
6
1 s: ^0 B- L m: C# F7' J. C$ x: W# t6 v
8' d: W$ ~# b* J1 n V1 z
与单个Interval类型相似,IntervalIndex有若干常用属性:left, right, mid, length,分别表示左右端点、两 点均值和区间长度。$ k! q p a5 d. O5 R
id_demo = id_interval[:5] # 选出前5个展示) y6 `( K& k' `$ [7 a) _% }
# d8 ~$ j' Q' U! H, d% ~. s
id_demo
?) H# r/ S' f3 IOut[64]:
0 z% t- ~. {3 o0 hIntervalIndex([(33.945, 52.333], (52.333, 70.667], (70.667, 89.0], (33.945, 52.333], (70.667, 89.0]],
$ L- L( B8 ^/ M7 S. q- e2 K closed='right',( R" P* K! S2 ^, T% f" E
name='Weight',% k' w! U/ v+ {, R
dtype='interval[float64]')
( s" l; G8 o5 j( J# l
! ^) ~1 @+ q" V& D) F4 M) M, i6 L" _id_demo.left # 获取这五个区间的左端点
) W2 ~. S; ~% Y. C6 XOut[65]: Float64Index([33.945, 52.333, 70.667, 33.945, 70.667], dtype='float64')3 M* A' z3 X) [' h. Z, l+ S
% m+ L s. C. G' s2 v1 o+ _, t2 Qid_demo.right # 获取这五个区间的右端点+ X8 T- @$ B) a8 F$ S& I/ n
Out[66]: Float64Index([52.333, 70.667, 89.0, 52.333, 89.0], dtype='float64')
9 `0 @8 s( R7 h; e& L- Q% s' T$ l3 |% A w$ u1 b. E# l# l
id_demo.mid
( ?8 ~( s" t. F, Q* w+ e9 wOut[67]: Float64Index([43.138999999999996, 61.5, 79.8335, 43.138999999999996, 79.8335], dtype='float64')9 ~; N6 ?: C0 A2 g- R: u
+ S; {' @4 {# J+ m v
id_demo.length
% y( t( e: Y9 F9 FOut[68]:
' c7 v9 z4 ?; S$ SFloat64Index([18.387999999999998, 18.334000000000003, 18.333,
. A6 w- Y$ j4 o 18.387999999999998, 18.333],
) O* F+ i7 h/ l3 z dtype='float64')
' E! H) F. p& J! j9 {
9 S) G1 I1 G- x* [1 C1 S1
3 m* ?# O- m0 k. P. J2
/ w3 y; C$ A4 k3
4 n; |3 Z7 n+ Q# h0 x: q4
# j- z- Z% ~. o# _, q4 S5* `3 C+ k: C. s- a* m1 q0 e, ^# e
6
h4 V+ \7 w9 p/ F% w$ H7& e4 `1 t# B* o0 }) Y
8/ |8 K, f7 g1 F1 H% k+ s. l
9
7 C* R& k% x. E10
2 z8 g/ w* D1 k8 }1 s# Y3 g11" [+ X8 L- k* t6 \% f6 T: m5 H- T
12( _. V$ }4 w. z/ J! p
13
# [, _2 c6 ?) l! z8 V- n+ z* ?141 f9 \2 b) ?# G7 J- ?# C+ [
15& e- x' A% x& i8 |% }) p
16: {( Q% C6 ]/ R/ m- w: Z
17( o% z& N+ i' O( x) T3 f8 O
18
2 b; \% ?$ ]% [; e. p* g19/ K3 s& J& ?# ~* E) J) E
206 l! m6 m- O6 G& O( f3 Q
214 b- \7 \7 A7 w
22
1 m5 N" W; B n: {0 `' A+ P23
: P! s W; h' K3 `5 CIntervalIndex还有两个常用方法:
" U$ C5 B/ A* B+ a: a4 f' U+ Y" N: Y) ocontains:逐个判断每个区间是否包含某元素3 w# {1 y) @, A$ `( H
overlaps:是否和一个pd.Interval对象有交集。! M6 N, \' N. W |& A& j
id_demo.contains(50)% i( @( X5 n" m7 n, G
Out[69]: array([ True, False, False, True, False])% N R% N- q8 o' S* e8 R& |
3 Y: f" P1 D3 A& y6 b, F3 R! mid_demo.overlaps(pd.Interval(40,60))7 l2 z2 E$ Q& e x, ?3 P
Out[70]: array([ True, True, False, True, False])
' l3 T G, G9 @9 F% ?5 V i1
$ K2 C5 s6 g8 r- B% K8 M2
* y* n1 L6 l8 Z; M0 Z& @& A) K; `3, Q8 {. J5 I; e' @5 w2 A. P$ C% v
4) g6 r1 F+ a K( g
5
$ V; Z+ Z( e9 s c% t0 v9.4 练习 c0 E2 g, w4 B# d: X
Ex1: 统计未出现的类别
! U$ N- `3 ?! Q 在第五章中介绍了crosstab函数,在默认参数下它能够对两个列的组合出现的频数进行统计汇总:
) E# z4 Y) y- Q* O1 ~
' U/ j0 w0 V% W: T# J0 e- rdf = pd.DataFrame({'A':['a','b','c','a'], 'B':['cat','cat','dog','cat']})
5 I; J4 I h( V4 j" dpd.crosstab(df.A, df.B)
6 k# i1 ^. ^1 V7 P$ ~2 [5 t
" t' x8 I; P- xOut[72]: g' o) H/ G- S6 [
B cat dog) f3 S1 p) t7 w m _; M' W3 {, M
A
9 Z, F2 U1 D5 ?5 F" A3 Y9 i! Va 2 0, ^% D. i- ~, b7 k1 W; t5 Y
b 1 0; z* Y" f$ t o9 s. B' z) o! r
c 0 1
; l2 F, k0 |. @% Y2 I6 [8 F+ S1
3 A8 _' S; l2 A, \( A; L2; f+ @7 @% z2 g9 L, @5 E7 u8 y
3
2 ~6 z8 e" v+ [( ~! N8 {% R4
5 Y, _; `7 I. Q: x, B' p+ L& E55 D" _2 j9 N$ d: p& u2 V! {
6
7 u& R. \" j( S7$ R, g9 ]( X) G9 {
8
h3 l7 F# W: M. U+ c$ i9: ] G' z& z" \+ h' q
但事实上有些列存储的是分类变量,列中并不一定包含所有的类别,此时如果想要对这些未出现的类别在crosstab结果中也进行汇总,则可以指定dropna参数为False:3 o5 k# c% ~2 t, W% R& q
* A0 y) y% b, B# M5 ^$ n5 }df.B = df.B.astype('category').cat.add_categories('sheep'), X* _7 H$ Q+ [6 {
pd.crosstab(df.A, df.B, dropna=False)6 H( k1 p. F# J# x# ]. l
8 d- U1 j4 ?2 C) B2 ^9 ]% a
Out[74]:
0 Y9 w2 A z& H$ G: @B cat dog sheep+ r/ _6 e# z7 i0 Y8 q
A % T9 f! G7 e4 c' j# T# H
a 2 0 0
/ T7 _! D; {* H: Y: Mb 1 0 03 U" T9 N ]4 n+ J4 T
c 0 1 0
9 {. O( E3 T+ z, X$ h) X1
& Z7 Q1 n. }+ y( y21 P! }) J7 b. V4 D0 B
3
X9 A" k' ?# I0 U0 W6 c x# W4
& F) L# p% H5 @: c- |' M5
! q. c( F y6 Y5 L6
: F/ g3 M: _2 h1 u0 i6 @7
' }7 b6 v' }' A. s8
3 p- {4 y' R4 _! H* c93 }1 |& L: s3 t# `6 G. g8 n x
请实现一个带有dropna参数的my_crosstab函数来完成上面的功能。
( A2 o$ u5 f) R O4 O% v( j& ?6 K
Ex2: 钻石数据集6 E! S, n* X) ?6 E) P( y
现有一份关于钻石的数据集,其中carat, cut, clarity, price分别表示克拉重量、切割质量、纯净度和价格,样例如下:
- O. y$ G& t# v1 S" i- @1 X; Y; V0 y% n8 l" b# l3 D
df = pd.read_csv('../data/diamonds.csv')
7 R5 g4 s) J5 Y- H6 R Cdf.head(3)
1 k( d: `) B1 {" I6 s4 w/ k9 t* D n6 }
Out[76]:
8 e& n/ t0 w' t7 N9 Z3 t+ Y carat cut clarity price7 M0 D4 `5 ]5 ~' b. a$ b+ Z
0 0.23 Ideal SI2 326
7 S2 C8 E( t5 a* N0 T1 0.21 Premium SI1 326
# n& B, N: M; c& g& b3 f- k( C6 Y2 0.23 Good VS1 327
' @& |$ k$ _; I* H& q' a1 _) ~1
6 P) K+ F, ]) F d C( K2
) o4 L0 c- P! \7 F5 ]32 F9 `0 s* \+ y
4/ f" S/ w& q" z4 \
5" ^# T8 o' l" _% f6 Q/ }6 C
6
7 h$ R$ _: S7 `/ F# l5 L7
6 u' X7 P7 a5 f- s0 t8
/ R0 T1 y7 O. ?1 i分别对df.cut在object类型和category类型下使用nunique函数,并比较它们的性能。
, v* [8 J: q2 e8 O6 [* g钻石的切割质量可以分为五个等级,由次到好分别是Fair, Good, Very Good, Premium, Ideal,纯净度有八个等级,由次到好分别是I1, SI2, SI1, VS2, VS1, VVS2, VVS1, IF,请对切割质量按照由好到次的顺序排序,相同切割质量的钻石,按照纯净度进行由次到好的排序。5 v* |1 V: F0 @3 e! ]: f6 ~! _% A
分别采用两种不同的方法,把cut, clarity这两列按照由好到次的顺序,映射到从0到n-1的整数,其中n表示类别的个数。
B, n% S+ {; [' i. q. g对每克拉的价格分别按照分位数(q=[0.2, 0.4, 0.6, 0.8])与[1000, 3500, 5500, 18000]割点进行分箱得到五个类别Very Low, Low, Mid, High, Very High,并把按这两种分箱方法得到的category序列依次添加到原表中。
! R4 H3 N8 h( X6 q' C第4问中按照整数分箱得到的序列中,是否出现了所有的类别?如果存在没有出现的类别请把该类别删除。
$ G- H% J4 M+ j对第4问中按照分位数分箱得到的序列,求每个样本对应所在区间的左右端点值和长度。
$ y$ h) v& ^6 E先看看数据结构:% s2 b; X" W( J% J0 }& ?0 L! c
3 j9 `* F/ M* e* Y* Fdf.info()
" I; |& B, w# Q" ~) R" I; Y+ JData columns (total 4 columns):$ ?0 _) F3 T$ b" X8 m
# Column Non-Null Count Dtype ( v4 c! {! v6 t9 W; g6 r2 r# m& }
--- ------ -------------- -----
2 i4 H% }1 K, Q, ~: B$ S 0 carat 53940 non-null float64
* L% l k& x U0 m4 } 1 cut 53940 non-null object
p2 h C1 f' j 2 clarity 53940 non-null object
7 k+ g: d" k% @) ?9 s0 \ 3 price 53940 non-null int64 6 L" G; |( o# W
dtypes: float64(1), int64(1), object(2)
& V* D, e/ t8 G8 W* f; _1+ z" X' x! W) h$ W" ]( e) n0 ]
2
. \) c. |" s! Z' j8 r. B3
- w: A) o# N' G2 |4! J* G6 _* k' k; @" d* t* i, L
5
. `7 F7 k3 I4 \: L2 v5 e3 n6
# M9 G" t# g# h; o& R% n$ K& d79 k. M" i s/ K
8# [1 x; t( W) `: Z
9" J; c, s7 }! p0 F$ \- S$ S4 u
比较两种操作的性能
: e7 I4 r$ N9 }$ C%time df.cut.unique()( x1 t# ~" R8 g, @# ^
3 q1 a5 ~% O( o
Wall time: 5.98 ms
9 O* B) x3 Z2 C: D- ~! uarray(['Ideal', 'Premium', 'Good', 'Very Good', 'Fair'], dtype=object) G; t, {4 u+ |: Y
1/ I5 K/ c, t3 X1 p
2( i+ R' F$ }# p L S$ t
3
& k% s3 M8 [9 M. C# T. L1 S4) e1 o# R8 k3 V
%time df.cut.astype('category').unique()2 A/ K; _) M' f7 R" p7 B
0 [& @8 W' V' @/ ]2 O- CWall time: 8.01 ms # 转换类型加统计类别,一共8ms
8 J- n9 r1 Y( o$ N' K! a5 T7 r4 {5 w['Ideal', 'Premium', 'Good', 'Very Good', 'Fair']
- Z; H; \; Y; ECategories (5, object): ['Ideal', 'Premium', 'Good', 'Very Good', 'Fair']
) R" a+ u- u3 q) Y% Y( H8 j& v1
5 l- T6 y2 l. v+ z- c/ j/ M26 i7 h4 }# M% b* o2 G
36 M- C" k; K2 n' ^& k+ y. O
4
2 I9 ?7 W% c( \- O& }5) x ~5 w9 u" \7 t9 e
df.cut=df.cut.astype('category')" U1 R5 N( Z( n( {
%time df.cut.unique() # 类别属性统计,2ms5 H- j( U7 @1 c( {3 U5 c2 [( \3 ~
: [+ q* w& r2 j4 q3 q
Wall time: 2 ms X3 |. `& ]+ m h' ]6 x" W
['Ideal', 'Premium', 'Good', 'Very Good', 'Fair']
) k5 R+ {9 S! ^" N$ z/ `: eCategories (5, object): ['Ideal', 'Premium', 'Good', 'Very Good', 'Fair']# B3 ]4 {8 N( O' t8 _
1
$ }/ L6 A7 d' r$ K7 `25 P* k; v [7 S+ d( U( c! p
3
" y( Y" ^& g+ J0 L4
( @6 t6 Y# k- F) w3 l54 N# L) d f. `. C7 a! t: Q
6
5 N" M! L9 @- n7 }1 E, ]对切割质量按照由好到次的顺序排序,相同切割质量的钻石,按照纯净度进行由次到好的排序。
: a( T( V) U0 |; m0 r/ Mls_cut=['Fair', 'Good', 'Very Good', 'Premium', 'Ideal']2 J) S1 e- _# e2 \9 I! n- p
ls_clarity=['I1','SI2', 'SI1', 'VS2', 'VS1', 'VVS2', 'VVS1', 'IF']
! e R6 V" m% T/ J: N( Z+ Zdf.cut=df.cut.astype('category').cat.reorder_categories(ls_cut,ordered=True) # 转换后还是得进行替换
0 y. F6 Y5 a2 i) }' Odf.clarity=df.clarity.astype('category').cat.reorder_categories(ls_clarity,ordered=True)
6 u2 A4 R: J& U5 K
$ `) B9 g! f: |# g4 qdf.sort_values(['cut','clarity'],ascending=[False,True]).head(3)
( n7 i; C2 d5 N) M( ?, N2 O8 v7 @* {
carat cut clarity price( F, J( {6 [# u& c. O; K
315 0.96 Ideal I1 2801' B: s# D7 a" n' G
535 0.96 Ideal I1 2826
( q- ?' G0 l7 F6 L* [551 0.97 Ideal I1 28305 S6 z0 u0 x' v. D; N( t
1 m/ {2 @: H; v. f5 H
2% F% f# F- R' O" g8 p
39 y& R( s* f5 e k2 j3 X D; j
4
2 ]) M1 t. \9 g2 W) r/ d+ n5. v5 q( `7 T& D
6, ~1 h$ n) }$ ]
77 {/ r5 t# B9 I6 T4 d' r6 g
8 _3 @) ^0 ]: d! B( \; g' X
9- I! m; U/ w& `: A+ @5 @0 }
10+ T( P3 \. N @$ w
118 Y8 m) Q( q9 T. A/ Q
分别采用两种不同的方法,把 cut, clarity 这两列按照 由好到次 的顺序,映射到从0到n-1的整数,其中n表示类别的个数。7 l; I- \- M+ `
# 第一种是将类别重命名为整数
8 K) h4 K, D/ r* r9 W, ydict1=dict(zip(ls_cut,[x for x in range (4,-1,-1)]))
O' I. B% m/ w L3 r/ d0 Pdict2=dict(zip(ls_clarity,[x for x in range (7,-1,-1)]))
# p& u6 A" @2 f) h& h$ s2 t5 g( L* s& ?8 m* C- c
df.cut=df.cut.cat.rename_categories(dict1)! {1 n+ T6 W, M+ p; z
df.clarity=df.clarity.cat.rename_categories(dict2)7 S6 i4 o! Q2 o1 `& A
df.head(3)
' D( [) ^( A8 m d$ Q" Q; H6 G, n) g- W1 Y' A
carat cut clarity price
; J4 U! z: i4 R D& ?- d0 0.23 0 6 326 E% l/ P+ q1 h. k
1 0.21 1 5 326# d& q& j I$ \+ ~- ~
2 0.23 3 3 327: c: M& V" a$ o4 ^6 e
1. d# u. I7 O) |' x; Z
2" _+ v; I8 b9 P' L% f$ a2 U' l7 j. v
3: ?+ s% N' m2 t4 _
42 P" e) b" S0 n
5
: x! X5 C: `/ u$ C2 p6; u0 N. u( d$ l" `
74 m' d" s$ {4 y
8. H; {9 I8 Y* r- V. p6 c- U2 b
9
8 A/ ]& m+ M) q; Y$ i' l }; {10. W! L! v' A& B# Q" d3 m
11
4 q9 C* n6 v9 h6 s12
3 ^# _6 }* B" i* S' d# 第二种应该是报错object属性,然后直接进行替换
0 y( z8 E: ~0 R) G* G( [6 V$ P4 cdf = pd.read_csv('data/diamonds.csv')- E9 N W0 \7 G1 f9 V3 q# s- f
for i,j in enumerate(ls_cut[::-1]):1 D4 u5 A8 B& Q4 t4 ^
df.loc[df.cut==j,'cut']=i
( F5 t) ^( ]/ O+ J& |0 G G
! O x8 @& z! q4 u* z n2 o+ @8 jfor k,l in enumerate(ls_clarity[::-1]):8 I5 Q, Y/ ]8 E+ o5 G y
df.loc[df.clarity==l,'clarity']=k
# y I4 K7 Y! tdf.head(3)5 I% _" i$ E, C; f
4 h' K1 o& f0 T( z& L2 ~
carat cut clarity price
- m% ~, N6 B3 r6 `6 Z/ x3 m/ j0 0.23 0 6 3261 E' X8 `* {4 S0 Q3 k) P9 \
1 0.21 1 5 3265 ?: T R2 m# Y0 l" G' S8 e
2 0.23 3 3 327! x5 y* E6 {3 u7 H& [
1" H- c7 `* C6 D- c3 G2 u& o
2, c s/ z, H1 t# l' Z9 w- {( B" S
3
# L/ Q7 S/ H/ q4 [. c4 P4 y3 X3 L+ E5 {, q) N' ~
5
" [9 a* G( c3 Z2 z' z% P @6' j7 P- W& m6 P# E4 @+ _
73 z* e$ P* _8 Q% R3 C6 h n7 I
83 d# `7 V$ k a9 E
9
2 Y" s% j. F% u10
" N& b! ^# w9 b$ N11
1 y. g& k" z: |4 V" G8 C4 {; `12! ^7 L! k) p0 h) ~0 f
137 [. a# f4 ~/ ^- @; _
对每克拉的价格分别按照分位数(q=[0.2, 0.4, 0.6, 0.8])与[1000, 3500, 5500, 18000]割点进行分箱得到五个类别Very Low, Low, Mid, High, Very High,并把按这两种分箱方法得到的category序列依次添加到原表中。 W5 X1 G3 W. J2 L1 g+ k
# retbins=True返回的是元组,第一个才是要的序列,第二个元素是分割点+ F; |8 G ?4 T# C' ^# i9 m
avg=df.price/df.carat
# ` ?" Y M4 b0 P8 ?. H
1 }* a1 f7 [" n6 I& \# H% f0 Fdf['price_quantile']=pd.qcut(avg, q=[0,0.2,0.4,0.6,0.8,1],4 d. K9 i# s' B, n/ i @
labels=['Very Low', 'Low', 'Mid', 'High', 'Very High'],retbins=True)[0]7 {9 V* s) N5 ~
: j9 U& A* p# }( h% U+ w
df['price_list']=pd.cut(avg, bins=[-np.infty,1000, 3500, 5500, 18000,np.infty],$ i+ l! |- k3 N- `( b) @( R7 i
labels=['Very Low', 'Low', 'Mid', 'High', 'Very High'],retbins=True)[0]7 k' j- Q3 ?! o, [
df.head()6 Y9 h" f' H" n- g1 N# E( ~$ N' l
) j( j3 q3 C: k0 g carat cut clarity price price_quantile price_list
% j& r* x! D, @8 o0 0.23 0 6 326 Very Low Low9 c, l! j0 h) _
1 0.21 1 5 326 Very Low Low8 c& |: ]/ s( c9 |
2 0.23 3 3 327 Very Low Low
- w5 r3 Y# H0 r; t2 g4 X; ^* g3 0.29 1 4 334 Very Low Low
_; [0 A8 Z' l4 P7 d$ j# E4 y0 D4 0.31 3 6 335 Very Low Low
/ z( i/ I9 [ W6 W+ I# B1 m3 J6 q4 {
1
; J/ J5 t; \ J4 ~6 z5 T ~2
% d4 X9 A- W5 n% @! r9 a3
% T. M! f1 I" D' Q+ q4
?" i9 f1 S3 d, K c9 S! q58 Z8 Q1 s8 W6 b: t! Q' ~; c6 {- \
6
' x: k) j1 J/ s: e73 d L% O% C* k. ^( ?; m1 [( R' F- M( d
8& x, r j' S$ K
9
S, O: E" Y7 M/ U' M10# e/ S% Z" m, W* J. H
11
; f, c" c$ i! C2 e& I- S12
6 u( a. ]+ B* ^, A13
6 J4 y& v4 }5 t6 U% s9 t3 B147 t( d: ~7 \* j3 z
15- d6 o2 K9 b& h. a, E/ V
16
- M6 X1 g3 e2 C% }3 A" J分割点分别是:
" _$ a3 ^7 o& b. G# S' O0 o3 r0 a4 A5 g |4 s
array([ 1051.16 , 2295. , 3073.29, 4031.68, 5456.34, 17828.84])7 G1 J" K5 T" K9 x
array([ -inf, 1000., 3500., 5500., 18000., inf])
# N' D3 ^, o5 W1# [2 b& M: R+ Q# k* H
2, m" \( t$ ]+ l% o% O$ ~- K9 j6 g
第4问中按照整数分箱得到的序列中,是否出现了所有的类别?如果存在没有出现的类别请把该类别删除。3 e6 h& N# K) i2 p
df['price_list'].cat.categories # 原先设定的类别数
# S5 C/ V4 y& ^% n! F& CIndex(['Very Low', 'Low', 'Mid', 'High', 'Very High'], dtype='object')
/ D5 k$ W8 W+ E# k9 I6 }0 d' m3 ]6 M$ O. ~% N: r" P3 l0 Z
df['price_list'].cat.remove_unused_categories().cat.categories # 移除未出现的类别
6 s" m9 p, z1 W6 d; \" m" ]Index(['Low', 'Mid', 'High'], dtype='object') # 首尾两个类别未出现
+ h2 F3 b8 _! W& Q1 g1
& H2 m6 H0 c8 y& {, }- _2
3 N% }' }- W* m0 m- w$ u$ b o9 w3" V* U3 \+ l/ Q* y
4
7 S) H& h$ m2 i5
$ G3 Y" r) j5 V4 ravg.sort_values() # 可见首尾区间确实是没有的
: j9 |; c: |7 s. Z/ n/ @9 s; S31962 1051.162791! m3 C/ R% V% n1 }$ L. c
15 1078.125000
% x" _- v2 ]6 t" _4 1080.645161
8 [0 X7 i4 V8 D' m/ ]# _% e28285 1109.090909
$ P9 w! |3 P6 G. ^5 q9 n; y, ?# x6 j13 1109.677419& n u" G$ J7 f& F) U1 {6 l
...
- Z' V+ e1 }/ Z0 }26998 16764.705882
' U! T! t; d; Z* }# l( H( t5 D27457 16928.971963: Z# r& i6 z) a) ~5 F
27226 17077.6699039 ~* t1 B& y6 z2 z7 H. ~3 B
27530 17083.177570
2 a0 V5 a+ O: C0 ]1 |/ x$ H27635 17828.846154
1 H1 g; |* {) `3 T1 g# r2 g; E! Y# z4 ^
2# X- o2 O* |9 h5 L* S0 b
3
2 Y: D7 `1 M0 Y8 t4
, o$ [. ]& J9 v4 b+ N8 X. u% i5
1 _* M f1 |3 Y4 U0 y" ^6# }- h$ x7 q+ B4 [& [) S
75 n) d7 z `3 V; g, }( q$ n
8
( `+ Y, z6 G0 z4 M/ i( c, F* R9
! ?4 b9 j8 z6 W10
8 I7 W& S. f5 x- X11
" U/ ?9 k% ~( u( }3 x7 v; ?12
2 m3 j: H, Q' h3 m) p对第4问中按照分位数分箱得到的序列,求每个样本对应所在区间的左右端点值和长度。/ U# v7 b8 m3 u; ~
# 分割时区间不能有命名,否则字符串传入错误。
3 D+ U! B% f, L6 W: i& Qid_interval=pd.IntervalIndex(
$ h. p" ^* f# ]7 d8 b' H4 Q pd.qcut(avg, q=[0,0.2,0.4,0.6,0.8,1],retbins=True)[0]1 [$ [$ W C; ^+ z, e1 f7 d6 }
) r, } G1 F" L3 Z1 g* L) u
id_interval.left- U! w2 x/ E& f- n- s; n
id_interval.right
% o3 ~ M+ O4 J4 t- V# A+ _: ~id_interval.length 3 k5 `/ F2 G8 Y# m4 C1 N
1
3 j/ j3 @+ u% K1 I p2% W: W6 i4 j/ w& T) ?+ Z7 ~
36 q: Z- H/ K: G8 G- ~% f% w: Q |
4$ ~5 T0 f% { U7 W* r7 c1 ?
55 M' o5 U. o' F% A( |$ g1 R4 q# A
6
6 | R5 R& [ K8 V7
$ a) t& E' @* S, r+ g& @第十章 时序数据 ]9 S) W s& z8 x
import numpy as np
6 n5 n6 l% K. Rimport pandas as pd
0 o) Q" d" u5 j3 G# K' b14 x* @/ g1 n5 F
2
t# Q7 @% R( w9 J* ? q/ Q4 x3 q9 x- w7 u' E2 \" u E
, q! A- v3 V4 t10.1 时序中的基本对象
* `; W3 P# |1 D: u+ E( d 时间序列的概念在日常生活中十分常见,但对于一个具体的时序事件而言,可以从多个时间对象的角度来描述。例如2020年9月7日周一早上8点整需要到教室上课,这个课会在当天早上10点结束,其中包含了哪些时间概念?. |5 {1 c* R6 A/ h- x1 u, P, r
6 Y. R, w( r) E5 ^会出现时间戳(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的简写。 @1 ~, e( Z9 `# a! \
& A% u! Y% n+ [* m+ {( @# o2 D' M' `
会出现时间差(Time deltas)的概念,即上课需要的时间,两个Timestamp做差就得到了时间差,pandas中利用Timedelta来表示。类似的,一系列的时间差就组成了TimedeltaIndex, 而将它放到Series中后,Series的类型就变为了timedelta64[ns]。
; E) V. R1 q b2 V; ~4 a
' R' i- N9 R4 x* j! O* G3 ^会出现时间段(Time spans)的概念,即在8点到10点这个区间都会持续地在上课,在pandas利用Period来表示。类似的,一系列的时间段就组成了PeriodIndex, 而将它放到Series中后,Series的类型就变为了Period。. Z) }* x; \) \, k3 L) r" a
! G |; p5 d/ W& Q+ G0 u( a" {
会出现日期偏置(Date offsets)的概念,假设你只知道9月的第一个周一早上8点要去上课,但不知道具体的日期,那么就需要一个类型来处理此类需求。再例如,想要知道2020年9月7日后的第30个工作日是哪一天,那么时间差就解决不了你的问题,从而pandas中的DateOffset就出现了。同时,pandas中没有为一列时间偏置专门设计存储类型,理由也很简单,因为需求比较奇怪,一般来说我们只需要对一批时间特征做一个统一的特殊日期偏置。
a3 b, w( s8 _! x+ g5 D0 S
# S9 w5 u2 ~2 o' S- B 通过这个简单的例子,就能够容易地总结出官方文档中的这个表格:% N; }; g) X; w
& J0 O% e: b8 N# \- O/ S概念 单元素类型 数组类型 pandas数据类型
4 H1 Q* L" V6 U, B8 H. l/ L. dDate times Timestamp DatetimeIndex datetime64[ns]- J# M* M- v' ?/ E/ \) Q
Time deltas Timedelta TimedeltaIndex timedelta64[ns]4 U! |1 R* b1 O6 v5 w4 ?
Time spans Period PeriodIndex period[freq]* R' x5 b' R2 k" k+ S
Date offsets DateOffset None None( ?' n$ m% d c) m5 ~
由于时间段对象Period/PeriodIndex的使用频率并不高,因此将不进行讲解,而只涉及时间戳序列、时间差序列和日期偏置的相关内容。
5 w4 v- l4 m. c9 \/ Q2 p; U3 t) s K
10.2 时间戳
% R2 z Q6 T1 H3 l: S1 k, m10.2.1 Timestamp的构造与属性
, n% q/ V. W" h+ @* l$ ]; t% @单个时间戳的生成利用pd.Timestamp实现,一般而言的常见日期格式都能被成功地转换:1 D$ Q3 ^* E* H
O! X" q5 S | o( gts = pd.Timestamp('2020/1/1')
2 G: c! m, r- i/ T. V* |9 v: ~0 l) G1 R4 z% U2 i
ts6 o/ M6 ? j6 K$ q
Out[4]: Timestamp('2020-01-01 00:00:00')* R7 R- G0 ]# R( B2 o; O1 i* G
9 e2 p7 t% ^/ H* D% E Dts = pd.Timestamp('2020-1-1 08:10:30')
. r# o9 L5 o9 Q% x9 p" J/ } z# A
: M m! i$ d! Q! }; j8 X- vts: w6 c7 @" c( E& O% A* O7 x
Out[6]: Timestamp('2020-01-01 08:10:30')
: P. A3 Q' Y/ m3 K1
5 n. a! u& @9 \3 u4 `5 s2& H- {# }# Z7 [4 t6 `. l6 R W
3: t: O7 C6 l: E8 t) C0 Z O# U
4
: V+ i* m$ M+ M' m+ U9 u* d! _5' O$ w) R- {; F; p: X/ L) j, @- V
6
' t7 x! v# S0 ?9 i7 p( N7 n0 k5 i% n( E! y g
87 |1 e$ L9 u; V
9( x; x& Z5 X. F6 S9 o5 r, \
通过year, month, day, hour, min, second可以获取具体的数值:2 \6 U. w! r1 i. j' B, f) m5 y
1 c) o% d! W+ e/ B" j6 s; b6 }6 c
ts.year8 k6 V p; M5 A c
Out[7]: 2020
7 p% {3 [1 H2 |4 M: T/ F7 a. ~7 E, L8 ~7 [% q# t& v/ A
ts.month: f! p; s1 R; `% j/ `* @1 w2 D
Out[8]: 1
6 |" e' i8 X+ b3 q- |' v7 f; y0 d' ?2 m& g6 d
ts.day, R: L$ s6 D% }5 B% L3 M8 y/ B
Out[9]: 1
4 |- D; w& m9 a* O1 q+ a, G. a9 \+ U1 ?5 ~4 o& J0 w2 |
ts.hour1 j5 F9 w6 E B: k4 k
Out[10]: 8
9 i1 S |+ a: a; f
6 s' Y' K) ^9 Q1 S2 W d' Sts.minute& p) u* z+ Z8 w9 I
Out[11]: 10" o# @5 y7 }# h' U5 ]5 a$ w. ?( y q
+ C( k: r, A8 \: qts.second: p6 V( E9 n5 S/ k) p
Out[12]: 300 k/ h/ U8 t0 G/ A
( G# H5 F, g9 V9 A1 k0 N: h$ G
1# }4 c+ N. E7 Q9 ]4 i: w: C0 h
2
9 ?- [2 H, q9 j5 F3% u, z1 c+ N7 C% H4 v$ M( `0 ]
4/ [, F- h5 D# w0 {+ f" Y+ O- H8 [
5
; {- K, q! C" \+ A3 C/ i3 T6
1 r% Y) W9 x! e( F. H& b7. t B5 {2 i9 x# I" y7 L) o
8
# S8 G0 \9 Y" p* v# b2 E& E9
: V2 p$ T, U" W) ~ A10
, C% c, T- d2 B p" b* M11
' X1 m1 |1 L) n& x4 ~12
- V1 j0 R/ |! P" l13
% E6 X' @5 s% t/ N14
/ Y) c+ G' |+ C+ q* y15' O2 T3 `& y4 J) g7 v
16
: k5 k% ]7 Q. @, T2 c" H17
8 |3 ^: W! r' |5 ~ [# 获取当前时间
. n) a2 b5 i+ t9 `+ t8 ?now=pd.Timestamp.now()
8 k4 e" N. w1 Y6 Z7 m, V! }9 c( L% ~1/ ?9 x, a. ~( u( s: [$ X1 j- _
2
% m7 J8 Y, X6 a) J" {在pandas中,时间戳的最小精度为纳秒ns,由于使用了64位存储,可以表示的时间范围大约可以如下计算:$ n& J- d. J+ f, ?- _$ Z
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)
0 @$ l# N9 `" U9 _3 cTimeRange=
7 v- c/ ]4 m2 |: k0 O, J( A7 m10 / t- ^6 l% d s) M$ w/ i3 Z
9
! R: b! }7 t" _) W ×60×60×24×365, d- k+ \& X$ ]7 v
2 7 n- u& H% v. S' |" b0 a
64
$ D. `$ v% E* }+ J2 }9 A$ A% _, P2 x2 ~& G" V9 Q' F
1 E) }0 a t, C! T P* W1 [ ≈585(Years)
( M: M0 V5 ^; W
& J) p5 X4 \- ~; d( t7 y8 l& J通过pd.Timestamp.max和pd.Timestamp.min可以获取时间戳表示的范围,可以看到确实表示的区间年数大小正如上述计算结果:
+ L0 u# S7 }* N
9 p9 j* _$ [( N0 t4 j: gpd.Timestamp.max
% D/ D8 v: n* \) G/ y* b* y) N. xOut[13]: Timestamp('2262-04-11 23:47:16.854775807')
x$ l6 |2 r9 p+ Y2 M4 t
. G' H! O6 \1 w( \! lpd.Timestamp.min
' h; t. m# e: }: N6 ~Out[14]: Timestamp('1677-09-21 00:12:43.145225')
/ e+ i, O. f8 |/ K0 G1 F$ @% v, [
pd.Timestamp.max.year - pd.Timestamp.min.year: s8 D) w! B% @. I! x1 p, C
Out[15]: 585! S4 g3 D* ^; b V0 B8 c4 c; j0 d
1
' `/ O4 L( k, r1 M$ y2
9 L: \4 X& k4 t& N: s( j3 ]3' C; H2 E2 Q4 N% G7 N% a) Q
4
Q, N# d: S7 J5 z5
/ i3 Y) h- L) R( f6
( ~/ q3 `8 S3 D1 S1 Z' O7
' x1 p; A: Q3 k+ [2 S% B85 ?5 L$ C+ o' n7 k
10.2.2 Datetime序列的生成
! D- ?" i7 K* B/ Epandas.to_datetime(arg, errors='raise', dayfirst=False, yearfirst=False, utc=None, format=None,& W6 ]" L" \0 ~
exact=True, unit=None, infer_datetime_format=False, origin='unix', cache=True)+ u. }( L$ Y" p3 l. v
1+ o( B9 Y& O: {& f' K
2 M. h9 V* x+ d" F
pandas.to_datetime将arg转换为日期时间。
- j9 b' \% }; W% ]) {. _. E: A4 t# Y8 u/ @5 v) l
arg:可以是argint、float、str、datetime、list、tuple、一维数组、Series、DataFrame/dict-like等要转换为日期时间的对象。如果提供了 DataFrame,则该方法至少需要以下列:“年”、“月”、“日”。5 s# o$ t4 t& C- |
errors:
: L8 K" [* E7 w1 e7 O- ‘raise’:默认值,无效解析将引发异常8 p! L# ~( a9 [0 a: Z- l1 z. q
- ‘raise’:无效解析将返回输入2 x- r& y, g U; p
- ‘coerce’:无效解析将被设置为NaT) e1 ^- I- u5 V6 c5 o
dayfirst:如果 arg 是 str 或类似列表时使用,表示日期解析顺序,默认为 False。如果为 True,则首先解析日期,例如“10/11/12”被解析为 2012-11-10。如果无法根据给定的 dayfirst 选项解析分隔日期字符串,会显示警告。
+ U, v7 w0 M+ v, j5 n) pyearfirst:如果 arg 是 str 或类似列表时使用,表示日期解析顺序,默认为 False。如果为 True,则首先解析年份,例如“10/11/12”被解析为2010-11-12。无法正确解析时会显示警告。(如果 dayfirst 和 yearfirst 都为 True,则 yearfirst 优先(与 dateutil 相同)。)! L8 u8 B, T, a* H: h
utcbool:默认None,控制时区相关的解析、本地化和转换。请参阅:pandas 有关时区转换和本地化的一般文档3 S$ W# O p5 R. x5 ^# b6 q
format:str格式,默认None。时间戳的格式不满足转换时,可以强制使用format进行匹配。
% n$ [# V" J. j3 p+ p( i( l! T$ R7 Junitstr:默认“ns”。它是arg (D,s,ms,us,ns) 的表示单位,可以是整数或浮点数。这将基于原点。例如,使用 unit=‘ms’ 和 origin=‘unix’ (默认值),这将计算到 unix 开始的毫秒数。7 }) c$ y, j, D2 k1 y, v# Q
to_datetime能够把一列时间戳格式的对象转换成为datetime64[ns]类型的时间序列:
* G& m0 q0 O" c( O) G v. ^ l2 ~pd.to_datetime(['2020-1-1', '2020-1-3', '2020-1-6'])& A: J% T' Y7 S7 L ~
9 n8 a. X, ^- c! d* r7 C! q- F6 E9 Z
DatetimeIndex(['2020-01-01', '2020-01-03', '2020-01-06'], dtype='datetime64[ns]', freq=None)$ C4 A& Q7 L; C; c2 ]4 @6 ]* ~
1- [9 V8 Y9 p( {2 `: m; C
2
( I4 h8 k0 s2 ~; c0 m( X* c! ~$ K6 @, ?38 n: d3 [% x6 E" v2 \
在极少数情况,时间戳的格式不满足转换时,可以强制使用format进行匹配:' f* v. X; k- a) b) z X: \# m
J# }0 [& _8 h$ k: G+ n' j' Atemp = pd.to_datetime(['2020\\1\\1','2020\\1\\3'],format='%Y\\%m\\%d')7 A) r0 Y* Y6 e+ ~3 g; M" A
temp3 j$ j. n6 q/ d6 \# t
( ]0 R/ `; ]* \0 @% A! eDatetimeIndex(['2020-01-01', '2020-01-03'], dtype='datetime64[ns]', freq=None)
+ q' W9 z! j8 |7 P8 F# D1 s. Q/ p0 \1
( {3 p1 F' f$ u3 z& l" A( y" f% o2& B1 ~* q% b, c# Y0 i
3
: m! U! \' H1 ^" t. ` ?0 f4
) ?, Y4 D) T- Z; N" `8 |' O* O 注意上面由于传入的是列表,而非pandas内部的Series,因此返回的是DatetimeIndex,如果想要转为datetime64[ns]的序列,需要显式用Series转化:1 X' ?+ k' [; q+ a) G
0 R- F3 N6 ~& q) c" d U2 S: ~pd.Series(temp).head()1 [. \" k* b/ I, t- V
! |( G# M3 {2 {( i0 2020-01-012 N3 q. X* g$ i1 v1 }. `
1 2020-01-03
0 `! S& H: i" z( zdtype: datetime64[ns]' M; y! w' a8 K! T" _
1
4 v3 ]9 @3 d I0 r) y D' E8 _0 |- @26 ^' X6 H) n' z1 q
3
! ~" P9 `% W! _3 ^4
8 Q2 o: N5 F, ^+ z, N5" I6 B4 ?2 A" U0 V4 g v. |
下面的序列本身就是Series,所以不需要再转化。7 o+ t V7 O) W* [$ t) X
$ |0 O2 u) |" d* f* T" Sdf = pd.read_csv('../data/learn_pandas.csv')
6 C: V. D: ` o0 S7 g0 Ks = pd.to_datetime(df.Test_Date), ?5 K. l1 F6 Z) g
s.head()& ?# h+ s& U% ?4 K9 k/ h3 U# W
9 z b. {" @0 E0 a, K- i
0 2019-10-05
0 b3 z( z! w* U1 2019-09-04
) _% V$ T; e3 l9 g( O b( T2 2019-09-12
( Z9 O4 B; _. i/ N1 d0 w% q1 u3 2020-01-032 T1 u/ B$ L# V. q `& e
4 2019-11-06- r& ?2 Y; q D5 u8 j9 r
Name: Test_Date, dtype: datetime64[ns]5 J9 Z' k; o- P7 L
1
; V w8 [3 A% B4 ]- g5 ?( a2
- Y. f2 u: r7 u- V7 B- g3
3 g) G# D5 d& T6 h( G( T6 X) f4
/ I) m! @. f. C. X3 A9 x' h' a3 ^56 O) u. q; j; e8 K
6/ c+ `3 J6 C7 [* R$ {3 L4 f# n, i7 Z
7
0 u$ K+ Z& C' {3 H8 C8
) n& G& D4 O! Q* f5 b# o0 J. \9
% D3 z6 S4 ?; D f' {5 C3 u10+ D3 P' k/ r3 i' ]& _, I! i! d
把表的多列时间属性拼接转为时间序列的to_datetime,此时的列名必须和以下给定的时间关键词列名一致:: q" s! G6 _# l; V8 k8 K6 J0 o
df_date_cols = pd.DataFrame({'year': [2020, 2020],0 u1 g. `! k; Y p4 ^) F' |+ A( w
'month': [1, 1],
1 }# T9 x0 s) Q. w" Y# p: G- l; C 'day': [1, 2],
" L: J! a( t2 ` 'hour': [10, 20],
4 `& O; ^. n" l 'minute': [30, 50],
2 |1 }" {& T6 F/ o; d8 s' J% @ 'second': [20, 40]})! c, g' i% P6 f% |7 s" z+ s* Q
pd.to_datetime(df_date_cols)2 q3 v: ~7 H S3 S) O
p6 @( ] |2 u, ^2 V7 k# Q
0 2020-01-01 10:30:20
; C" z5 M1 v/ P" R+ g3 B0 V, ?" E1 2020-01-02 20:50:40
# ^6 M% M+ v) F) T. tdtype: datetime64[ns]
6 F! w" G- @. C# c1" T' w9 Q# \& E0 d8 N! ?- ]
2
; k1 n! g- G2 H4 C. {' W3) r) a/ F& U' Q& T! L
45 B$ Q) \2 w% C4 \7 s2 n2 D" V) ~
5
4 V6 p% s- ?1 D5 M. J( h* g67 n- q. ~2 ]) \# {+ s7 A( ]- z
7; y$ Y3 b5 ?7 t! L) ]
88 M: w- d( l3 \ n6 O
98 R3 ^" P) `+ o* n& ~" z* e
10
/ I1 ^; L# j0 v' ~11+ D6 f* b2 W: l8 ]; w, t4 J- ?
date_range是一种生成连续间隔时间的一种方法,其重要的参数为start, end, freq, periods,它们分别表示开始时间,结束时间,时间间隔,时间戳个数。其中,四个中的三个参数决定了,那么剩下的一个就随之确定了。这里要注意,开始或结束日期如果作为端点则它会被包含:
8 t" U+ v* O! \7 E& J* }4 ?pd.date_range('2020-1-1','2020-1-21', freq='10D') # 包含9 Q( }$ A, |# \& S; N/ p7 Y- F
Out[25]: DatetimeIndex(['2020-01-01', '2020-01-11', '2020-01-21'], dtype='datetime64[ns]', freq='10D')
( I0 {# B7 O$ G: I: n; [- X6 h6 ^
pd.date_range('2020-1-1','2020-2-28', freq='10D')1 F' T2 n4 ?3 C7 r; {) }! s
Out[26]: , M, U4 z- J. Z+ i B1 @
DatetimeIndex(['2020-01-01', '2020-01-11', '2020-01-21', '2020-01-31',
% o: J, l3 D1 N+ `/ ?8 H% W '2020-02-10', '2020-02-20'],
! k& o9 U7 z' y. L' S dtype='datetime64[ns]', freq='10D')
5 f( G/ P: V& C7 t% A5 f* l+ Y5 f- v- A1 e# ^/ u0 p
pd.date_range('2020-1-1',
9 d% I/ b& i! k8 Y '2020-2-28', periods=6) # 由于结束日期无法取到,freq不为10天
9 T! Z. d0 `2 F9 s# A0 T( [% o& I! }1 L
Out[27]: 9 h ~6 F c3 _9 G5 u9 q
DatetimeIndex(['2020-01-01 00:00:00', '2020-01-12 14:24:00',
# s4 }* V8 A0 v2 W6 F& O '2020-01-24 04:48:00', '2020-02-04 19:12:00',, D* G$ v: P- O# e
'2020-02-16 09:36:00', '2020-02-28 00:00:00'],
' \+ R7 }" J# D i t dtype='datetime64[ns]', freq=None)" s A1 M( a, b2 o) W9 K2 [. _
; H: S2 ~! Z, E% \
1
) B. x& D" S" i" T0 a+ s. t, P2
6 }! N, |: X8 @0 d, x33 [0 J9 g+ A; I/ d" _
4
$ t; q; ?. \# r6 q5# q7 A) ~; x' y7 c2 F% s* B0 a
69 J }4 d" |7 \: n- m; N
7
6 A& r6 y, T9 n" m3 \1 ^& i8% d. k, {/ \0 \! c; u+ k1 N
9& k% u7 \$ a; C2 @
10 i R/ T1 `3 Z0 x, d; y6 Q& K! @
11' G: Q, g* @8 S& u, W
128 e( ~8 q8 s6 k7 K
13: @; [7 r/ ]. G1 J2 K
14
. ~7 c$ v! P) m, Q* [9 u150 i' k" s5 U m; [* T4 S: v1 o+ T6 N
16* [! B) Y. K$ a
17
) L" p! g e$ d# i3 _这里的freq参数与DateOffset对象紧密相关,将在第四节介绍其具体的用法。5 G4 Q a7 E, }3 V f3 w' J2 {
; K/ Z) g' ]& j; \6 b( i
【练一练】
) N6 i& X3 f$ KTimestamp上定义了一个value属性,其返回的整数值代表了从1970年1月1日零点到给定时间戳相差的纳秒数,请利用这个属性构造一个随机生成给定日期区间内日期序列的函数。
6 T) v0 n1 k2 a2 e+ L' U$ ?( x
1 }5 A# d# ]" q$ Als=['2020-01-01','2020-02-20']
, o& M; ?( H1 E Y- a! Hdef dates(ls,n):
8 s! m. e& B5 r7 w$ k/ X. C min=pd.Timestamp(ls[0]).value/10**9
; D" E: U: o3 E5 s6 a* } h& W max=pd.Timestamp(ls[1]).value/10**91 A' ^: A) E) I0 Z1 K
times=np.random.randint(min,max+1,n)
* r! P; @& N- J$ B! ?8 U, | return pd.to_datetime(times,unit='s')
* {. Y! i, u: D8 y* x$ K$ gdates(ls,10)
$ }: n5 M' ~0 b' k6 t$ ~& v$ Q, z( H* A8 N! }7 t' A4 t6 v
DatetimeIndex(['2020-02-16 09:25:30', '2020-01-29 07:00:04',4 a9 M; t2 `/ r" V- E2 ]
'2020-01-21 12:26:02', '2020-02-08 20:34:08',
( S3 h' t- }' ^: Y) W '2020-02-15 00:18:33', '2020-02-11 02:18:07',
/ K4 k/ l# k6 |3 v7 O q- t4 E+ w '2020-01-12 21:48:59', '2020-01-12 00:39:24',
9 k- j7 K2 Z4 i3 |+ L, R2 o! a. [ '2020-02-14 20:55:20', '2020-01-26 15:44:13'],2 x1 a# F- ^1 k2 H+ {8 j
dtype='datetime64[ns]', freq=None), I' e) X' U$ p* k- O" {! g9 b1 p! w6 h
12 Y: Z% ?: R$ V0 L, V
2( R$ N3 v: I5 i5 T
3
9 ^6 Z3 ?* b- ^: |3 b4
0 c) d7 V1 x$ J$ ?- K3 A4 c5; G/ W: V! d0 [( L, N
6
: A+ s: P/ r( v4 J7
, @0 c0 H6 [: }8: z4 j4 v* b4 q; w) n
9' {8 t V$ X" {3 a& U7 c Q; @ }# b
10
) A, ?" l9 b' y# @/ V5 J11& q' F' l) h" q* I
12/ {; T9 L5 u6 F# v2 D0 Q, A
137 v- z3 M c1 C; v
14
6 x* f0 {2 d" F5 jasfreq:改变序列采样频率的方法,能够根据给定的freq对序列进行类似于reindex的操作:" i$ r) I+ M8 h* {! g
s = pd.Series(np.random.rand(5),3 ^% h1 |6 @# c/ X" x
index=pd.to_datetime([
9 Q7 H' g3 w. ?$ H '2020-1-%d'%i for i in range(1,10,2)]))
/ l7 g/ _: F4 k6 \0 X3 J' m, } s" T* y, F
) q( ?$ k3 [4 _' N: T& g
s.head()
0 A9 M" h% y, @* ~, V, E6 y4 w3 kOut[29]:
- `* {. T! v" t. w5 @2020-01-01 0.836578' `" g m. {0 I7 H
2020-01-03 0.678419
3 R# Z( T/ t% P1 G( Z$ h2020-01-05 0.711897
' Y0 T6 i/ v# [% O8 B; R) z2020-01-07 0.487429
& |0 O; A* ^! p# H8 z7 Y$ X: d2020-01-09 0.604705# b; t. O! {# O7 s6 g0 @' H1 ?
dtype: float641 e, b9 v% R8 \- `- f4 S+ H. S& J
2 W4 _8 S/ J+ E1 N( D8 y! A& @
s.asfreq('D').head()/ M* ?- `6 _. D1 f8 w
Out[30]: ! | g1 P9 b$ l, ~/ _! n
2020-01-01 0.836578& I- ~: H5 z% a, c* H7 I
2020-01-02 NaN
, {5 I$ j# F. Z( x! ]: R5 {2020-01-03 0.6784199 q- J! `" V6 t9 D% g
2020-01-04 NaN
^* U. u( m; U3 `+ ]2020-01-05 0.711897
# t4 E5 g! s- Z) EFreq: D, dtype: float64
+ n- R% u7 Q) U$ X& Q3 _; R2 k: N0 L5 L- z4 K
s.asfreq('12H').head()
, g: g) l. d( ~Out[31]: : k# u* R, z1 O2 a1 {, o
2020-01-01 00:00:00 0.836578+ @. o) Q4 m; E" f, ?4 U) ~
2020-01-01 12:00:00 NaN
. u9 `( p7 z, P# ?% d' Y* A+ c2020-01-02 00:00:00 NaN
: G$ W0 g) K2 ] s0 E. X" f2020-01-02 12:00:00 NaN: n9 N r5 x3 N. p+ h0 i
2020-01-03 00:00:00 0.678419
. _7 K3 n/ B+ Y/ p0 G4 U8 \: tFreq: 12H, dtype: float64* ?* X/ D. ^+ i# n) `
% ^0 a/ ?& s: @8 V# E16 ~( |. u: f. q0 @; o, Z
2; Q1 v2 j1 j9 C4 ]) z
3: _; |* p! b0 @" a+ m/ i5 G5 @" }
47 w% J& I2 z7 |0 w1 p; G
5
3 c9 u1 p, a8 C6 ~# x64 z( ~% G4 Q: U& X" t
7
. p6 H% b, n: J% H$ P9 y* o2 l# ~/ K8
8 o9 [8 l4 q3 p1 t* J) I9
3 o) e" R. ?% u' N) _' d4 a10
1 d$ n7 o; I! k+ |% D; l11! [; T( k" N- T4 ]8 T" h4 N
129 }$ P* ~ w/ p, y, l, A
134 L9 ?2 {. ?. A5 c
14* b7 F2 l1 u# ^3 Y) ^
15
$ R7 i5 z8 Z/ S5 f9 m; p' Z16' j6 |/ i$ R; f' N; E, m% m
17
0 e l) [, o$ v H; ^18, u( t6 {, }4 N: ^+ s7 l- j
19
$ ]7 O, n! D& }0 {" N1 G% v20
! N2 b3 g' _9 \3 S: h, F21' p1 N0 V! J/ H
224 s+ @7 a' R/ r% a4 P0 ]
23
9 X9 U* G+ w" l6 }7 L24
/ I$ y4 Z1 v' H25 Y" p+ c1 V2 v5 n3 H, R4 [5 N
26
6 p! N- j1 h( t# k& V/ d27
, }* ?! Y5 z- s1 A$ |+ G1 A28! o n5 N, p7 T
29
' J4 H4 s. L- ]$ y30
; C' `* z4 h. Q5 G% ^1 j+ w) \31
$ ?4 n0 i8 t2 q【NOTE】datetime64[ns] 序列的极值与均值4 R+ q: t$ I+ Q) I5 a# `
前面提到了datetime64[ns]本质上可以理解为一个整数,即从1970年1月1日零点到给定时间戳相差的纳秒数。所以对于一个datetime64[ns]序列,可以使用max, min, mean,来取得最大时间戳、最小时间戳和“平均”时间戳。! N; ^0 P: J% q% B8 }: K, u$ L
1 g5 t7 E& @& D) l. H$ t4 {10.2.3 dt对象, N8 @, x" C' N8 Q
如同category, string的序列上定义了cat, str来完成分类数据和文本数据的操作,在时序类型的序列上定义了dt对象来完成许多时间序列的相关操作。这里对于datetime64[ns]类型而言,可以大致分为三类操作:取出时间相关的属性、判断时间戳是否满足条件、取整操作。
) ~. m1 ^$ v; \) O; v. P ?2 [2 g! ?; D) A( T& p
第一类操作的常用属性包括:date, time, year, month, day, hour, minute, second, microsecond, nanosecond, dayofweek, dayofyear, weekofyear, daysinmonth, quarter,其中daysinmonth, quarter分别表示该月一共有几天和季度。
, a( S+ M% j5 T5 ]/ p5 ^1 {& h/ V' E; \s = pd.Series(pd.date_range('2020-1-1','2020-1-3', freq='D'))3 C' v" \+ x. ?" e# Q5 B
( i& r5 E$ X8 T1 j
s.dt.date- f2 L4 J" _7 j q
Out[33]: 0 R% s, x) u( V( A" t
0 2020-01-017 @) U* j5 r0 f, C: i0 ]
1 2020-01-02% R4 ]( h1 K+ H+ x! l
2 2020-01-03
: s6 _8 q' T4 b" _& x) Zdtype: object6 T; G1 J! r; L/ B
( V7 j* J; \9 o' js.dt.time
+ i# ~5 k) n W6 ^+ sOut[34]: " Q2 Y5 c# Z I% c0 p. u
0 00:00:00
. C2 V% ?0 o6 Y+ E1 00:00:00. V: e( Z5 S2 `/ h e/ D- g/ P
2 00:00:00
' j4 n# j; p+ Q/ \- K4 \2 R2 h* B6 adtype: object& K! i) ^# a" B7 H% N8 r/ _2 S' v
2 M+ g' _; n9 u- d4 _" t5 ns.dt.day0 V, m8 e& y) U- Y; l4 B( u2 d# D
Out[35]: ) P' b8 ~7 u: y! ~
0 1
5 s N0 }: ^$ ~! r1 20 }' r% V, w" Y
2 3' V- N5 z; l5 o8 z1 Z
dtype: int64
3 q7 ^0 b1 L9 w/ Z0 j2 U
/ p6 H F4 [" D( s% L8 Q+ Q* ?s.dt.daysinmonth1 t* D' g3 n2 b" e
Out[36]:
/ I6 j5 ~9 v! n0 31. g! K- G" V( q" }9 {- Q4 L( p" p6 ]
1 31 H M p4 D: j% E
2 31
" b* C8 [0 b. Pdtype: int64
1 w& Z& v8 A2 K2 m
9 N4 {* A2 j, _ ~9 N6 X/ ~, _2 x* q1
! }4 d0 N; T p" Z9 x2 w' |5 U2
1 o, a- L9 c! \# m6 N4 u _3
% ~' Q5 o1 F$ |& E; ^4
9 I( k: p0 a" B+ S3 t) w5
0 f0 P7 [. N# l4 ]( w9 V1 o2 [62 J4 P m/ e' Z( _
77 N2 j6 J1 u% e
8+ g; d9 k7 G: E: ?9 f
9
9 T* L( c8 p) N3 V5 |. z10
5 R5 \* a! Z* B3 q5 M5 n% X: V11
* t$ \5 v% r5 I( K) u/ N% x12/ |# X) t& L3 m# g# c( A4 O
13
/ _$ F0 m/ ~- m! y4 U14
- y: z6 u$ k7 w: s/ w0 V) n15/ q) n. c# U# W2 [
162 x4 e" W3 L) l: ~. Y, H: U- ?- `$ f
17
2 _/ S/ I Y8 p18
3 K( Y3 R! I) j6 \198 g. j+ R8 k# O" ?6 o J1 O
20
4 j9 Q8 n* G( e3 d) ?7 ?215 s% ~* ~) I. w: I% o+ E0 f7 T' ~1 @' @
22
: X! m' q3 s$ S! T1 {23
) B8 S, f7 U0 V! s! D6 f24
! ]) a/ \7 h3 T* w! ]4 D' A9 `25
( X- _$ n8 V3 N26" |7 J4 s! h; O' K: d
27' F7 D; I5 ]9 @6 M& h
28
) O6 T. k# f$ h4 ?5 [* o7 I293 V, ?, m; }. q* v! [; R
在这些属性中,经常使用的是dayofweek,它返回了周中的星期情况,周一为0、周二为1,以此类推。此外,还可以通过month_name, day_name返回英文的月名和星期名,注意它们是方法而不是属性:
9 [: ~! f# @' \% y7 y2 q% d1 b$ \6 j, m2 b! o; T4 [) U
s.dt.dayofweek# ~# Q" o7 C/ i/ h2 l$ Q( K- l
Out[37]: 0 m# A! V9 \8 c
0 2
, u# b% t& A& Z- i1 3
4 y p$ U: A- W' ^3 B' r4 e1 z( ?# j2 4; L! w4 ]+ }, B
dtype: int64; Y/ M R# W, _/ W3 r
4 i# C( X" `' Zs.dt.month_name()
& d; {/ a1 T! F2 t/ O hOut[38]:
# h( l, o1 }' Y1 ^; C0 January0 _4 d- j7 ^+ ~* F: j
1 January1 {/ Y% a- z& P6 l% g* n5 A1 F9 X
2 January0 R1 u# h/ a! y3 c. Q3 e) O- G
dtype: object j* f/ M+ N, _, ~ k0 d0 x/ s
8 s# s3 e/ \# n+ k' T1 U3 Ss.dt.day_name()1 i9 q* a0 ]5 v8 O" b
Out[39]: % |+ f+ s' q3 W" J! }( o6 c/ A$ l$ }
0 Wednesday9 ]8 _: Y ]0 \$ J& T% [
1 Thursday& X9 N, K1 `, r0 `# @$ U I: a( R9 q
2 Friday, f" m1 M* p* ?8 p% K! P1 A
dtype: object+ r7 ^; x z. U8 l1 `
$ _. M% K; [9 L8 M1 i4 w
1
+ j. a5 M5 D, |6 ?& X21 L( I5 [! r1 x; o" T# ]
3/ H: [! e: V6 c
4
5 A8 O6 `1 G* }3 S. F K( M: M53 V A4 _6 S1 t8 Q; d+ X% j" E6 V; Y
6& i% e; b* t- t+ M
7* e& w% B: i- V* B' O! w0 d( L
8
& d; B: O' L; m2 I" E9 y, U0 w% F1 \" P8 d0 L# u
10) x/ s7 `# \# a+ n$ h# j. M2 G8 D
11
o* i) A5 r; O; G12
# F u6 i: }% S/ r0 s2 C13( D- u& A$ v4 @
14
. v) P$ l4 o6 |: d* O1 Y0 z15& t, B- t2 B% t8 ~9 r: U& J, C
16& |- |$ p9 `# I: v
17
3 @: I8 A2 d/ ?" H) W8 M4 }$ s18/ ^* x" m( M, Q8 y, C+ ^. k
192 k% K6 q6 |1 n6 c% l2 I0 L* C" e8 E
20
8 u% G# y0 |% i* E+ Q( W- G第二类判断操作主要用于测试是否为月/季/年的第一天或者最后一天:# v1 h9 z% `" ~. N' r
s.dt.is_year_start # 还可选 is_quarter/month_start
& u7 A# M) Z/ c. E- i& d a s2 |3 IOut[40]: 0 K7 L+ Y Q* A+ v3 c; ]
0 True
1 ^$ K: ]1 m6 z% d( L# M3 e1 False% H8 U( M. @! y4 T. H( \
2 False7 j5 J& _/ J% \* v& d9 z8 l, {
dtype: bool
2 y$ |( [% C2 j) Z* v, y% `* @3 {, g i" s% m
s.dt.is_year_end # 还可选 is_quarter/month_end
4 `- S) J+ P3 M+ v% jOut[41]: ) F5 p3 U; e1 N2 |
0 False* T. ~: C/ m2 f1 ^, i/ V
1 False% ~+ ^ ]: i! i1 b: l8 O( J
2 False
4 j* M0 ^& [! |6 edtype: bool# O. ]5 ?/ A5 m! H' P
16 ~. V9 S0 h! X+ H2 r" w1 [; @9 d% w
2
1 K, z* {! I8 e9 O8 V3
3 P. y1 t& C# ~, G4& |# T/ R8 P, d( F: T$ {# C
50 [$ u5 l1 `/ p+ w
6
$ {5 X) w% Z Y+ O9 B( K! y2 k7
. ~. { `: S/ S8 n4 A+ j8+ v! A) S) M. r) p0 T' C! S
9$ O/ T. @$ }: `" \+ v
102 v9 R d: o0 t$ X
11
" e, i) u' M" s. `12& ]# _; f' m. Y# I3 @
139 ^" w1 `3 j# w2 y
第三类的取整操作包含round, ceil, floor,它们的公共参数为freq,常用的包括H, min, S(小时、分钟、秒),所有可选的freq可参考此处。0 v% n6 v1 M; g R
s = pd.Series(pd.date_range('2020-1-1 20:35:00',
+ F' |" B, h& T! } '2020-1-1 22:35:00',! p+ `" E" M. X* R$ C# K
freq='45min'))' K# b; `0 N4 t; {8 P4 P# D4 W. p* v
, D7 g; Z' F8 B! {8 j1 u1 I
0 W5 W3 M2 m2 h* i$ v
s( v; ~; I3 }$ L4 j
Out[43]: ' B# J( o: @7 v& g& J, |% P0 c U
0 2020-01-01 20:35:001 ?/ R c3 F" M" c, v9 y# x+ k
1 2020-01-01 21:20:00
" M2 P/ K6 M/ t- M2 2020-01-01 22:05:00: h. o0 ~: I( ^: b3 c
dtype: datetime64[ns]# t; U0 C; I1 s, O) P$ M+ B
% u8 z: P! `$ H0 W
s.dt.round('1H')1 X- k1 ]: ]+ Y; \5 X& k
Out[44]: 3 P3 J% j7 h" S* \6 q% W
0 2020-01-01 21:00:00
, f. m5 Q8 K4 P9 S; l1 W/ X1 2020-01-01 21:00:00
4 Z1 I& P; k5 I; L2 2020-01-01 22:00:00
; l% [& q0 m6 o( T" V2 c: K. x/ Q) Odtype: datetime64[ns]* B6 [( X( ~6 k$ a2 z$ A! a9 i! t
& ?9 }& q" o4 b/ G6 Qs.dt.ceil('1H')& I) Z) D0 @9 \# ]) h# ~( h
Out[45]: ! H6 f, O4 X9 U- f" A$ k# a
0 2020-01-01 21:00:00 z0 C/ D) B2 C. q
1 2020-01-01 22:00:00
* f# A9 W9 R! \/ s# m! ^2 n s2 2020-01-01 23:00:00
( I9 X0 p% X, U, w" c4 pdtype: datetime64[ns]+ V' [- K* f4 M
2 Z" D" Q" {; j2 Y1 t B8 D- Fs.dt.floor('1H')
U* q9 ]% Y8 yOut[46]: * L+ o- [/ B7 ]& _2 y) H
0 2020-01-01 20:00:00( _& g% K& o" R4 |, H5 i
1 2020-01-01 21:00:004 ~. A9 ?0 y3 j S' Z9 ^ n- \
2 2020-01-01 22:00:00( q- Z% j* Y) [3 P. u
dtype: datetime64[ns]
6 E+ R5 h6 K$ E4 a3 I- F
5 i1 T9 [& a/ B; u1
" j0 d' |5 o. V5 `- m2' W. E' Z7 o! e2 g
3
5 L, K% v3 w$ Q. y' [3 h4* [4 `" {8 U+ p" s4 V
5) S6 J; m. D3 C {, C' X& b& ]
6
g0 C1 l5 n" l/ T1 A3 B" u7! l: N; P+ z8 [
8
+ K4 F' \( K5 I. }$ W99 G; }' E4 t' a( I0 j3 q* z
10" p" J b" b$ H+ C) s
11
' d& \6 F! D; `' L$ E12
1 Y6 L; h* l1 V! x13* I4 d( ?! t$ l A& B- k
14
: V3 | H3 I! B+ n9 q& c15
4 S/ S4 n& W, M M8 b& h16
+ R* z8 N* E& b7 f1 J174 A+ S# P4 `- u3 c
18
. R1 }0 g+ S: M: |# h19* |* K, |7 I- ^7 f4 W/ `# [8 ]
20
/ S+ x# e- i* X/ O, d* Y% t21
D, h" K4 T" ]: T221 q: }1 ], h+ d
23
$ h' q* Q) c! N' v) f24
7 s7 Q( q/ c& {, r25
3 x" S2 I( E1 ~/ ~4 N! X' q263 Q5 C1 }& H/ r( P
272 {0 e% @( G3 h! @2 {
28
1 n; X( R! {/ _. s% k296 c" s4 A7 D6 S6 @, i" B. a
30
0 r- r% Y, ~* p; e* s: v31: ]! w" E- g- M
32
: ~1 J4 Z/ B/ r- E! q* \( H$ I10.2.4 时间戳的切片与索引
& D% w$ N! s9 h$ o, j( r 一般而言,时间戳序列作为索引使用。如果想要选出某个子时间戳序列,有两种方法:
, A) f4 N. s6 z5 ?8 S) z6 G" k2 d' P7 S; U
利用dt对象和布尔条件联合使用+ r: Z$ O' X* R( T& c
利用切片,后者常用于连续时间戳。" r9 j7 I6 y) r' H. c$ X2 L
s = pd.Series(np.random.randint(2,size=366), index=pd.date_range('2020-01-01','2020-12-31'))9 Y4 Y+ m$ G, n' H0 y; _6 T: e7 ?
idx = pd.Series(s.index).dt% K; A6 U5 i# X- i
s.head()
2 {+ q, @' H# B9 _% X; t
' l; h- I0 m4 G5 {$ e2 F7 n) z* [2020-01-01 0
- H. a# R( Y4 A3 U, O# K/ {$ I2020-01-02 15 y8 X j- |. M% f
2020-01-03 19 R% f0 C# S+ x* S; T- B. l! h' C
2020-01-04 00 e, T4 x6 x$ l, M
2020-01-05 0$ {/ y( a( P, V4 a% I( v+ E0 }
Freq: D, dtype: int32
" O, z- T5 l1 w10 s! w" E7 i) p& {
2
* } A2 h0 i" g6 |& X8 q; }3* E; C* P3 i: I5 L
4
6 Y% {; @ D' e3 ^% v# i, I: k5$ Y3 ]8 ?/ J2 H6 g8 u% V, \
6
6 s1 m* O: S4 X' E7
: h! {6 z: k! @1 j- D8% g' V9 l1 u( J9 a' K
9
7 L/ `. I6 c! Q! V- Z) [- k6 ~10/ E i8 N) t: t/ |
Example1:每月的第一天或者最后一天
' ]+ F5 O: k3 B# g; y* A5 }6 h2 d2 \) E* K
s[(idx.is_month_start|idx.is_month_end).values].head() # 必须要写.values9 Q3 B/ |# o; K
Out[50]: ' U0 A/ o2 U" ]( d M9 H& D# q8 a
2020-01-01 1
* `3 ^* Z' r, X+ d: \0 i- b$ t, P2020-01-31 0 U+ n- }$ O) Z7 [6 O: W. F0 I
2020-02-01 1
9 E4 m! [- _7 ?. _2 ~$ J2020-02-29 17 P( g) C. `( X( }
2020-03-01 0$ y1 a# }6 _2 m3 E; T$ t
dtype: int32+ b8 ~8 R6 f2 f9 X
1# T- O# z. q4 J' G. ?* q* l, X
2
% J' d6 h3 e4 Q3 i! h3# d) t. X# l5 v9 r$ r$ v
4
$ V* i/ {) ?, h" a5; _3 m" j! C# q$ h9 `$ I7 i& M
6
* |0 _# a$ {9 Y+ A, y; F. f4 v% D% B7
$ ~1 u: s+ y8 ]86 h' h* Y* r' t- |7 K" R/ \% _( k
Example2:双休日# S2 b+ H" ?* l, N/ \
1 n& R! p ?. J: Z6 I- J0 P( rs[idx.dayofweek.isin([5,6]).values].head()% R- s& N9 p" N* F0 N0 s! V( `
Out[51]:
( t4 C# h- J' c) j2020-01-04 1# Z& O, I6 ?5 x0 V4 ^4 W" T
2020-01-05 0
) G6 @1 b# k6 t: v7 j2020-01-11 0/ Y0 c: Y$ \' g" I
2020-01-12 1* o% F: x5 a$ q% ]# M* @# t9 F8 Y
2020-01-18 1
; |" C2 y+ _$ M/ b% p& o; d0 Xdtype: int329 v8 n1 J; Z, K; U0 P# v; y2 v
1
4 a" ^. \3 ~! @2 d20 t2 h p/ `4 q0 T
3
& { m1 c2 t+ a1 S# `48 T) S! C/ e" j; w/ `
51 ~* I% U* w# H$ e# E% R! q7 b4 v; o
62 l7 z6 Q2 E# C
7! w# n2 i$ O" T+ i% l
8$ C7 D. ^9 y$ {( i3 w
Example3:取出单日值
2 [4 q; ^" J% [& q6 P+ q7 j6 l
0 `, ^% r2 P1 v8 ls['2020-01-01']9 }) n: O( Y3 S1 b% `: f
Out[52]: 1
j' u, T L$ }& z
f8 k: J8 F. p. E, |1 |s['20200101'] # 自动转换标准格式
. n: z4 [! |3 |Out[53]: 1& E- p1 g1 S+ u- c" l; I
1/ |! v* b, M4 Y7 }4 t5 h
2/ x2 G& V' K, Y* K+ H6 f/ m7 [
3/ ?# G+ J+ {% n5 M* `
4" ]$ f4 N0 A) `: H" j7 {) p
5
: Y7 D# _/ }# @) S; D. j( bExample4:取出七月
' k, I: f, d& y6 V( p, s! L5 r! ^) x9 I
s['2020-07'].head()* x% B8 o: X0 U8 [1 H0 _; f! F
Out[54]: 4 s4 J8 E/ v) H% f9 |
2020-07-01 0. [+ a/ ]) J+ S# C1 J
2020-07-02 1
2 c; f, t: }( l( _* |3 k2020-07-03 0
y' O) l* D6 e/ w4 F2 q" E2020-07-04 0) g2 j/ R4 c; H
2020-07-05 0
$ |. S3 K/ r: U7 R( p, {& xFreq: D, dtype: int32
K" I& Z- N. W1
" p- E4 ]7 }" t9 \- x+ `3 {2
2 { m6 M$ D# ]0 A5 `6 v z+ E/ Z3
+ r/ o! c/ v7 [0 k, ^2 s9 Q5 G+ z& Q& o4+ h' V6 p9 b: f( ?5 C
5
" B- L2 N. Y s8 @0 _6
" w! M* A7 s) \: K6 I3 u2 ~* m* M7; v" @0 \5 a+ i5 b' h
8
) K5 R3 Z) e+ e, Q8 e# w$ J/ @Example5:取出5月初至7月15日& `. [8 l9 W& X2 n# P9 ]
' ~5 w, z8 S1 w; L% j% X m3 n( is['2020-05':'2020-7-15'].head()6 Y) ^9 M' H: s6 ~+ U1 O2 G! p
Out[55]:
) a- T- F' V$ j# [4 J$ h2020-05-01 0
4 `$ {' j6 ?5 ^; h: Y& C5 P; E2020-05-02 1
: k3 w; t$ c5 T1 A2020-05-03 0
* E$ h8 A# @, C6 I2020-05-04 16 v8 i3 h- c( Z+ p6 p, v
2020-05-05 1
! K! \$ S9 p" i5 D6 X# L6 i" [Freq: D, dtype: int32
7 |3 \" O/ l$ V& A
( ^ g3 [0 D ?/ W+ {8 s7 ]: ]s['2020-05':'2020-7-15'].tail()
: j4 ?7 r7 H# [Out[56]:
0 C5 V) Q& H7 \7 D2020-07-11 0
: G' O) e8 g o( {: ~2020-07-12 0
, R, Q$ r0 f6 q* F2020-07-13 1
; A) x+ s. t7 a D. U$ C! D" b2020-07-14 0$ p7 W4 _ T' m2 F4 S+ u- i5 c7 ^
2020-07-15 1) X; V' _' L9 M' V8 G7 M6 x
Freq: D, dtype: int323 |8 \9 T7 |$ v# Q2 l' f+ g; h
& E7 X- ~$ X: U( Y& m' ^7 I) O1' x- {6 R v& s. ?; j4 v* f
2
2 |2 S# ?3 D% t( o4 A$ ~( b3
2 |1 d: z2 Z8 D4% Y2 o4 c: l" e: ~% W( a& ^
5
7 F0 b4 ^* i% ^/ `6
. ~+ }3 E1 B1 `$ L& q! a77 ^: x, o, f- X
8
* `4 d Y1 r( L! N# |+ a9 S7 z9
! s& ^7 l! E& D6 s10) M9 ~+ ]6 H( D/ u7 b. K5 Y2 J, o) N
117 e0 C2 z, ~% g& K) Z
126 u+ H" a& f! ?. _2 l2 c% V3 k
13
) n: G5 z6 u$ T7 B14& S+ W/ a* `$ b. Q
15) M) p+ v3 t2 p1 u4 z" w
16
0 m% z2 |6 a% F ~17" [6 j$ q( G# ^) u+ N4 A# _' c6 Z
10.3 时间差8 |: N1 E" Y. P$ q6 E' Y5 e
10.3.1 Timedelta的生成2 H" \: I% D, J( B& x5 Z1 \% N
pandas.Timedelta(value=<object object>, unit=None, **kwargs)
/ Q2 A) H5 q, w( s- G$ T, z unit:字符串格式,默认 ‘ns’。如果输入是整数,则表示输入的单位。
F& [8 \5 ^; N' W* T2 ? 可能的值有:
% x8 m7 q. u( Z4 u, C; {, H, S& [# p% J0 ~
‘W’, ‘D’, ‘T’, ‘S’, ‘L’, ‘U’, or ‘N’2 u4 Y; n& d( K G4 b) \' P3 |8 ~: D
‘days’ or ‘day’
+ ]" T ?. `6 A _) \‘hours’, ‘hour’, ‘hr’, or ‘h’) Z+ D7 v& C* Q/ b0 x/ n' p- }8 z; j
‘minutes’, ‘minute’, ‘min’, or ‘m’8 D2 }# H; W/ ^/ s& a- E
‘seconds’, ‘second’, or ‘sec’0 A' c* ?" D- v8 q! v
毫秒‘milliseconds’, ‘millisecond’, ‘millis’, or ‘milli’9 C k9 ]" \ ?7 ]# f6 h" R7 J3 B
微秒‘microseconds’, ‘microsecond’, ‘micros’, or ‘micro’( F* d/ x' e9 f! P" ]
纳秒 ‘nanoseconds’, ‘nanosecond’, ‘nanos’, ‘nano’, or ‘ns’.- n) t4 t: |, U8 X% E C' K7 M
时间差可以理解为两个时间戳的差,可以通过pd.Timedelta来构造:
" p4 `, @5 n j. ypd.Timestamp('20200102 08:00:00')-pd.Timestamp('20200101 07:35:00')
) C, |8 c- I! \$ {: p/ mOut[57]: Timedelta('1 days 00:25:00')
* x; h, E4 l! Y
) Y# ^3 g% t, ]% ~7 A* {' E5 Epd.Timedelta(days=1, minutes=25) # 需要注意加s. s: {! E% d! ^/ p$ X* b r& j0 G
Out[58]: Timedelta('1 days 00:25:00')
?9 N1 M8 G6 F! ~' l. p
3 T8 d3 i$ B4 |pd.Timedelta('1 days 25 minutes') # 字符串生成 }: C" `1 }1 q/ R c) a$ o
Out[59]: Timedelta('1 days 00:25:00')
o) W: m" i4 a9 j
+ \# q. C5 B; l) P/ c! q2 X8 `& mpd.Timedelta(1, "d")& c9 v4 q9 }. d8 X6 M! J
Out[58]: Timedelta('1 days 00:00:00')' I6 ~( O( {- T) n
1
. C: ^) B/ s( |4 ^! }, x% Z2
1 q- {2 [# m' J) F) q3
/ p5 k8 L% R9 e8 w. Y k4% g- ]+ V- ?1 {, P
5
9 n/ {0 {* L( G; z/ e6& C' n: ~/ ^6 F. C& v
7
8 q2 `/ _: @" z/ R: g/ P+ [8
3 }' J$ S/ w/ i$ S8 |3 L9) Y' }+ U/ d+ d4 _4 }
10- E( j% [- n8 N) @( n
11
- B; |' y& A l$ h生成时间差序列的主要方式是 pd.to_timedelta ,其类型为 timedelta64[ns] :
9 u) A! W/ u( ds = pd.to_timedelta(df.Time_Record)9 V( r% T, {# R' k
$ ]! N% j/ N/ v0 D3 D5 B9 o+ e
s.head()
$ B0 b, V2 r+ l" V' R9 COut[61]:
8 e; ?5 H! f4 k0 0 days 00:04:34
. F+ r! K8 _6 j7 `8 B; m0 r* E5 t1 0 days 00:04:209 X# [" V( i1 k; E# o/ e" ~9 D
2 0 days 00:05:22- B( A9 i. M, ~7 f& g
3 0 days 00:04:08- b3 g7 y9 c( K. W5 m
4 0 days 00:05:22
, e( b' v+ n6 L7 x8 T1 xName: Time_Record, dtype: timedelta64[ns]
& F3 e2 |9 K" A5 P1
" o n1 t' Z/ R' q+ C2" M( B& {9 w# @: K/ R. e- y5 q
3
* f3 p- }. R/ Z. m47 M2 {, A8 v0 M/ Y9 L I
5
% n; r2 g% z" e5 T: P6
$ @' p" e+ R( f4 g3 \7 `: g( G! @# ]* W
87 T _8 J$ c+ r% W5 b& O C
9
2 D5 U7 g$ w9 b10( B4 I9 ]/ f: z0 V4 ~
与date_range一样,时间差序列也可以用timedelta_range来生成,它们两者具有一致的参数:% ?8 P0 P% ^* n* a; B
pd.timedelta_range('0s', '1000s', freq='6min')
# Z2 B, `, v! GOut[62]: TimedeltaIndex(['0 days 00:00:00', '0 days 00:06:00', '0 days 00:12:00'], dtype='timedelta64[ns]', freq='6T')
7 W' E) R; l7 t9 j# n0 h4 }" \
0 }! \6 f' ]* {3 l- T$ Spd.timedelta_range('0s', '1000s', periods=3)
" S/ G& j4 G$ _& Y Z$ BOut[63]: TimedeltaIndex(['0 days 00:00:00', '0 days 00:08:20', '0 days 00:16:40'], dtype='timedelta64[ns]', freq=None)
. m6 A% w# y4 k) ?9 i( b$ v4 X1
1 U- D1 a f9 ~0 x9 H2
7 o% H% O3 r2 c* s6 O# ~3
* L2 o' d' X+ f' I2 C! r& o4
; u0 }; X) A; u/ X! L5) W5 \( a6 F+ t' x8 p2 M/ `
对于Timedelta序列,同样也定义了dt对象,上面主要定义了的属性包括days, seconds, mircroseconds(毫秒), nanoseconds(纳秒),它们分别返回了对应的时间差特征。需要注意的是,这里的seconds不是指单纯的秒,而是对天数取余后剩余的秒数:
# f4 p1 C G6 u# C# }( ~+ Y0 Hs.dt.seconds.head()* }5 M/ u" ~+ X2 ~! T y ~& h- H
Out[64]: + ~7 U0 u" c. j/ E
0 274
% B+ @- p0 E* k7 K: J% I( l, Y1 260
& e H. n1 ~: `$ S2 K2 322
3 C2 y2 X5 T: [+ S& v# v3 248
8 b2 f: D) S# f! ]4 3225 M7 H" k/ H/ L# z- @
Name: Time_Record, dtype: int64
1 W, `! F& x3 \- t8 g" w0 ^6 F4 t1# r* z$ T E* a9 h- v
2
7 M" V- @8 c' P# {. X4 U9 P& o3
1 q3 I6 N8 E& `! \$ b# N4
/ F! m! @1 o3 S4 ]5
) I5 T% u* x0 I- d) p% P6
' g2 H& i( J& _& U$ b8 ?2 h y7
3 ?* c) c/ `- {' U5 r9 B5 X# \8! O$ l: l$ N* i7 y5 \ U. v
如果不想对天数取余而直接对应秒数,可以使用total_seconds
- V& X* V5 i! P, C2 a K& h" k" Z; ^; ]8 V9 e
s.dt.total_seconds().head()
6 P" s, x) @9 t$ L a4 N8 u. XOut[65]:
$ P' W( q# x) R/ |& ?: |( s0 274.0/ }: U) |& T4 @" V% `4 M
1 260.0. z4 Y) g% ?) _" ]
2 322.0
/ m* {: s" @( y3 248.0- H! U2 ~( J9 m9 I
4 322.0
: x8 w* `3 Q- y/ l5 U$ c V5 t- xName: Time_Record, dtype: float646 F$ }: `8 b$ K/ a
1, r; d/ v+ h+ y" r2 q3 z: J: v& e
21 ]3 j; r- I- |# M" u# @
3/ S* ~3 H4 l9 l0 h+ X- h) ?
4 a; K5 f+ p' p S
53 Z# P& R. E# |- ?. R
64 | q% i4 c: T7 K6 e; X) R
7' X' D4 D3 y4 Z# b+ M& \' C3 i
8+ }- J1 N- X( \4 w5 ?
与时间戳序列类似,取整函数也是可以在dt对象上使用的:
2 s f. W6 t5 j( Q' r7 U) C5 N2 @' n
pd.to_timedelta(df.Time_Record).dt.round('min').head()9 B2 u/ P H; p/ a
Out[66]:
: `2 X* Y) P" L, V" l0 0 days 00:05:00
4 N5 [6 q" }: D. W: y# i1 0 days 00:04:001 n. C0 f+ m8 q8 X9 |- A
2 0 days 00:05:00+ @$ U7 t4 e8 _$ G
3 0 days 00:04:00; t3 r& o; o5 d7 D: w* [
4 0 days 00:05:00# K* c: I7 I+ L& ]0 |) e
Name: Time_Record, dtype: timedelta64[ns]2 V; i& ] T$ O4 K1 `
1# h. H' {: e9 x% R* \
26 Y/ n. {1 \( i N$ }
3
: P1 q& C, ^; {5 h& k4
( v' l( b+ p2 |5 m% {7 t5
/ I3 v2 g7 ]% C6 I& y66 G: ~: f5 X ]2 c1 u# e
77 B1 b1 @& |+ |" T
8: C* L+ k' T4 J$ f1 g
10.2.2 Timedelta的运算. }$ D6 X" r. M- e
单个时间差的常用运算,有三类:与标量的乘法运算、与时间戳的加减法运算、与时间差的加减法与除法运算:
% b- {: w$ q8 S6 A# Wtd1 = pd.Timedelta(days=1)) y4 \9 H" G7 j* p3 V3 \
td2 = pd.Timedelta(days=3)
# s+ I% W- w8 i0 Fts = pd.Timestamp('20200101')" D1 ]. S2 s6 g0 T. k2 e
5 `9 Z4 E* _* Z( L6 H( Z
td1 * 2# S: m4 h4 y [. O9 o2 [" s
Out[70]: Timedelta('2 days 00:00:00')) L$ Q8 A2 k$ ]+ K$ U, g
' {/ b: J* K, u/ Y+ v
td2 - td1
% n9 g0 l" _* I: a, R' G7 _Out[71]: Timedelta('2 days 00:00:00')
/ D8 }- d1 i0 t9 v, v$ F; }0 N3 W
6 n' b: v: E# T% q9 A) y* ?ts + td1
5 ~/ r: A" f9 G3 u7 d! Q* o! a! g. gOut[72]: Timestamp('2020-01-02 00:00:00')( C" R0 N) q: P$ ^* j
: T5 X+ p: q7 L/ ^
ts - td1
* e; P4 q0 G4 x) C2 rOut[73]: Timestamp('2019-12-31 00:00:00')
% a$ _9 h( T$ L# k1
7 R5 T) d, `( y+ Y2
+ h0 ]% q& d- X: @6 u3
+ R1 h9 W5 A# a; u9 S$ G48 C% C( ]% j7 ? ?" ?
5) o5 u- X0 A# x7 O! }: o y3 e
6. X: B8 l9 {5 |) d" w+ ^6 V6 L4 X4 b' M
7
7 e7 m E2 ]; {8
( r( u; s' M6 @( H9 ^+ J9- s. x5 g7 z) D& {$ V; i8 _
106 n/ G1 G( a) {0 J0 b4 n- o$ {
117 P! |: Q( I- U: A
121 j. A! E2 ]# e1 {4 `, E4 F" p
13# N+ m+ [! [' ^2 Z0 d1 l
14
, b3 A y% m A+ H15& g4 u& N1 H# k3 z# R
时间差的序列的运算,和上面方法相同:
) f3 c& x# y9 v' r1 ntd1 = pd.timedelta_range(start='1 days', periods=5)# J \+ T* R$ j4 P+ N# h& U
td2 = pd.timedelta_range(start='12 hours',4 G% L2 M2 ] f6 [
freq='2H',! M% S" u7 v# D
periods=5)
5 i& ]7 ]/ C8 Z- E5 U# ets = pd.date_range('20200101', '20200105')
; U/ [: f7 g2 _2 G0 ?td1,td2,ts
2 B% z5 ^2 a) D4 n* T! z
1 D3 \5 J2 q) j& _: XTimedeltaIndex(['1 days', '2 days', '3 days', '4 days', '5 days'], dtype='timedelta64[ns]', freq='D')
$ E8 {0 N1 Q# E, S) ]* QTimedeltaIndex(['0 days 12:00:00', '0 days 14:00:00', '0 days 16:00:00',
7 S7 T1 l+ }# G8 ]* I1 x* X '0 days 18:00:00', '0 days 20:00:00'], dtype='timedelta64[ns]', freq='2H')$ ?" s5 s# a- u6 H
DatetimeIndex(['2020-01-01', '2020-01-02', '2020-01-03', '2020-01-04',
, ^/ J N! y+ }( {* L: i# j% L '2020-01-05'],
2 j( Y, q- R8 j* C% K9 N dtype='datetime64[ns]', freq='D')' V7 Z0 O0 G4 w% m
1
) c; w0 D% |' w: p, e2
/ j0 \, p4 o" d3
% g6 B* M% l% n4
) O+ d. R3 {$ d7 V54 Q) U: b' a, q3 f
6" ?$ J8 v# t/ P; a0 e
72 _) F# M6 B& ?% I% V
8& K- ^4 c8 d5 R u
9
3 B* d2 |# [' U4 S: Y2 W10
4 F a/ k* F' w4 u11! _% u5 G6 j) P$ o* Z1 u+ X# G
12; B) }) t. j+ M1 t
13
4 y' j) X9 @+ f) Y8 T% Itd1 * 5
6 h! h0 v! @. q1 `Out[77]: TimedeltaIndex(['5 days', '10 days', '15 days', '20 days', '25 days'], dtype='timedelta64[ns]', freq='5D')" G4 {4 w2 E2 I% O2 }' m2 l
" o2 Y" F0 ~; D5 @# O1 I
td1 * pd.Series(list(range(5))) # 逐个相乘. T& M+ |; A. }4 l K$ b
Out[78]: , q4 _$ L: X* f! A) K
0 0 days/ e( J' F( R( D# w* s2 C: u/ ~
1 2 days) f- h+ m, F G+ i
2 6 days8 k0 p, d* K3 Q0 t
3 12 days
7 u3 k( V" {1 n3 B4 20 days, v2 K; a: V4 x( b
dtype: timedelta64[ns]
8 o# a* X$ ]- _) n( P- S7 O+ H6 ] A+ D9 D% Z
td1 - td27 K# M7 d; F; f4 g: \
Out[79]: 0 H# g2 G+ x- v+ z" c: i" a
TimedeltaIndex(['0 days 12:00:00', '1 days 10:00:00', '2 days 08:00:00',
# T5 w3 Y$ }8 }% X& q) R '3 days 06:00:00', '4 days 04:00:00'],
8 R0 F! w6 R& ]3 B dtype='timedelta64[ns]', freq=None)" i' J7 C) ?6 P# h7 W" @
$ T) I! H, h; J* G) a5 ~3 ^td1 + pd.Timestamp('20200101')$ P' h' l% ~7 w8 r! o/ i4 z$ i6 e
Out[80]:
) I3 \4 X$ P+ pDatetimeIndex(['2020-01-02', '2020-01-03', '2020-01-04', '2020-01-05',5 V2 S! o- _! W( x$ \1 ]& b4 U ?
'2020-01-06'],dtype='datetime64[ns]', freq='D')& ?6 z! `! K* K7 z! H. e/ i
. M' J n! d- H, o0 [td1 + ts # 逐个相加
, y" v6 ]: z: n) ]8 mOut[81]:
/ }1 O, Y- ^, D. f* u! M6 p$ m: Z. fDatetimeIndex(['2020-01-02', '2020-01-04', '2020-01-06', '2020-01-08',% d+ F0 b6 q9 r
'2020-01-10'],
, g p6 I. R/ M( Q& x dtype='datetime64[ns]', freq=None)2 g3 N1 s/ R! u' T1 y( x
8 b9 Z* R1 T, n6 p/ ~4 [1
- Y7 g, O" ^4 j, M, [: ?: k; h2 W. X3 O2 b0 ^7 w/ }* \, X. w u/ y
35 D& l0 |5 d8 U6 T
43 W( J% b1 z7 l5 W7 u2 O! e7 R
5- j$ C E9 e' h1 @+ w+ h8 Y
63 ?5 h4 b2 L+ j
7
) q1 j' ~) J1 d( t$ C# O) X3 ?8
+ Y( i) P9 M# W$ P* w1 C9
: Q0 B: j- L) s10% o6 o: E' }7 x* ~% j
11- V9 c9 _! [9 l0 N/ X# w. m
12" W: M$ B' X0 \; Z6 s, g! m
13# ~! n2 K4 m: E3 g5 K- @& l% E
140 P8 b6 H Q7 _; M3 O* J$ H# F/ H
15$ \$ _8 M" z' _" H9 h! _
16" I& c, D `' A. g$ r* ?
17
7 _# k, m* n$ J; v( [" |; F1 \2 a18
, X/ O$ @# @" t) m: P1 v: l6 r19
$ G p3 C! }3 t! L8 U20
7 G9 ]* a6 c: C7 T# d21; ~' L. B9 G1 z2 t) o
22
: g& c1 U) C7 d. P1 G3 P' E$ V2 A23+ F- e( |/ J4 k
24' J& L' K) p# I4 J, s) k
25
. M/ ?( U) K( E% s$ ~: H. _26
% r1 _* f1 p) W+ S3 N% u271 m' |" y( ^, @0 q" z! v3 D
283 r- D) Q0 w8 e l, D
10.4 日期偏置4 ]& K0 [8 A4 f, D" O
10.4.1 Offset对象
, }9 S7 C6 g5 {- Q* j' o 日期偏置是一种和日历相关的特殊时间差,例如回到第一节中的两个问题:如何求2020年9月第一个周一的日期,以及如何求2020年9月7日后的第30个工作日是哪一天。' [: U$ o0 j4 h: ?5 j) |" A/ {
) n7 N! v# X4 c& S; ]DateOffset 类有10个属性,假设s=pd.offsets.WeekOfMonth(week=0,weekday=0),则:
9 h, i! w) \& s) p$ ] k) L9 O
" g0 c3 u6 h C$ D/ y4 F! ys.base:<WeekOfMonth: week=0, weekday=0>,返回 n=1 且所有其他属性一样的副本4 n7 X" k& Y( P" b- P, W
s.kwds:{‘week’: 0, ‘weekday’: 0}
& B' p) A7 o! \; t" _" w* Zs.wek/s.weekday:顾名思义0 c$ g# i' M+ i0 ~
有14个方法,包括:5 E5 e9 x4 b' p, Q; J/ F
9 E0 H& c- }3 t# A3 ODateOffset.is_month_start、DateOffset.is_month_end、DateOffset.is_quarter_start、DateOffset.is_quarter_end、DateOffset.is_year_start、DateOffset.is_year_end等等。! `* ^' Z X' C9 N
pandas.tseries.offsets.WeekOfMonth(week,weekday):描述每月的日期,例如“每月第二周的星期二”。" v3 [% L) X$ V3 L
1 G! I( R4 I% D3 ~0 N& E7 R' I0 _, V2 i
有两个参数:
) y0 t3 X4 H+ q/ I- Lweek:整型,表示一个月的第几周。例如 0 是一个月的第 1 周,1 是第 2 周,以此类推。
! g" j% Y2 C3 Y* E& n" |) T; I* ]0 ^weekday:整型,取值为[0,1,…6],表示周一到周日,默认取值为0(星期一)
9 U4 ] s, m2 s3 \' ?pandas.tseries.offsets.BusinessDay(n):相当于pd.offsets.BDay(n),DateOffset 子类,表示可能的 n 个工作日。
& p+ T- V0 Y4 ^/ I' a8 }, ?1 u
8 V# |7 }8 L- S" p+ R0 s1 Apd.Timestamp('20200831') + pd.offsets.WeekOfMonth(week=0,weekday=0)
; S6 @3 ?) M0 }% g: h( ? {, COut[82]: Timestamp('2020-09-07 00:00:00')
% r4 y; }: C0 O) r0 v }7 o
* \7 }# g8 G! L4 d- Rpd.Timestamp('20200907') + pd.offsets.BDay(30)1 D% D* F6 |4 z3 M
Out[83]: Timestamp('2020-10-19 00:00:00')
6 X# w" z: P2 {/ b$ }4 w) ]1
9 ~* G- y) A0 Y6 q/ C" I+ a, Q- o2
' b7 V( f) o+ f/ _8 d* l9 U u3
; }. J: A; |4 c' ]; a) \- S46 M3 ^/ K7 v+ f0 O
5
/ f- C5 ?: o- v0 ] 从上面的例子中可以看到,Offset对象在pd.offsets中被定义。当使用+时获取离其最近的下一个日期,当使用-时获取离其最近的上一个日期:, u6 E/ d# K% x
- v9 o1 |# Y& W }2 K& m7 O' l @pd.Timestamp('20200831') - pd.offsets.WeekOfMonth(week=0,weekday=0)
, j# m; R2 I! E4 |( POut[84]: Timestamp('2020-08-03 00:00:00')
9 N( w4 F7 j# M# q, F
$ h. J* \* k; n! ypd.Timestamp('20200907') - pd.offsets.BDay(30)
* U4 S- m2 t) j d BOut[85]: Timestamp('2020-07-27 00:00:00')
" t! \' x- z6 X$ z0 X! \& q: c- g. N
pd.Timestamp('20200907') + pd.offsets.MonthEnd()) O& z7 y# b. I
Out[86]: Timestamp('2020-09-30 00:00:00'): o) y. A( H9 R* {# \7 y
1
4 W" q3 P5 m4 x5 w2
& @+ r* j% s' Y/ F- Q$ |4 X7 |3
2 t; T! P/ G5 R2 j4
6 b& }* `7 {. }, n; G+ a57 f0 m$ c ]. l5 H" l6 R" ?
6
- |1 T: ?4 }# f6 ^! k76 B5 A! [* f) _" r1 z* M: s: }! M# T
8( J! @8 @9 O- i; a
常用的日期偏置如下可以查阅这里的DateOffset 文档描述。在文档罗列的Offset中,需要介绍一个特殊的Offset对象CDay。CDay 或 CustomBusinessDay 类提供了一个参数化的 BusinessDay 类,可用于创建自定义的工作日日历,该日历说明当地假期和当地周末惯例。
. p: h+ s& r3 H q/ \: N 其中的holidays, weekmask参数能够分别对自定义的日期和星期进行过滤,前者传入了需要过滤的日期列表,后者传入的是三个字母的星期缩写构成的星期字符串,其作用是只保留字符串中出现的星期:
* t7 J$ X' ^% d7 `
$ \: \8 Q5 A1 T$ ?+ L# g( i4 lmy_filter = pd.offsets.CDay(n=1,weekmask='Wed Fri',holidays=['20200109'])
& F$ X% n, h0 v- ^ `: q8 ddr = pd.date_range('20200108', '20200111')# \% |3 R9 P! q D7 [' H6 X* [
# }$ j8 |% ^0 v# p' ]& p1 e- L
dr.to_series().dt.dayofweek. e v! ? k: g& c) i* P9 W
Out[89]:
- q2 Q$ K( t& X3 `8 d6 J; b2020-01-08 2
% E( ?8 T; }- ?3 u2 k2020-01-09 3- r% l: e9 b" b! l1 C
2020-01-10 4
7 Q9 ]0 Y6 J0 T( ?2020-01-11 5
' q* l* n5 j/ g: b. WFreq: D, dtype: int645 n$ m, S0 x) U" R" u
% I8 }$ O+ p# Q% w[i + my_filter for i in dr]; w9 A" E( i0 X% o0 A: q' O
Out[90]: : c# E5 G2 e$ \. ^
[Timestamp('2020-01-10 00:00:00'),
7 {( p: N. V2 _) u+ M' C Timestamp('2020-01-10 00:00:00'),, g5 K1 x* ]& F" n8 H6 d
Timestamp('2020-01-15 00:00:00'),
. _0 V* k1 R a/ }1 p Timestamp('2020-01-15 00:00:00')]5 \7 `4 S1 G4 Q" Z1 [8 N
% K+ E! U0 Q5 o+ U5 Q
1
5 k! ^# G$ }! \# ~' i4 u- a$ W7 L8 {2
: `$ W) i" G$ u: H# a1 n2 e) A35 ?& O& Q( X" c ^7 L5 [+ t1 H, _0 ?
4; G% D4 E0 K' e& r9 z
5
7 ^8 p. ^2 j" ~) h% L; t68 T- p! M) H. G) Z: r q W
77 E" S( _. X* h3 d+ `# `/ C
8
# t/ Q2 f0 ~' A7 S& ~97 p+ C6 p3 `. R( F8 `
10+ U2 M5 |/ [0 a9 d7 A4 {' y7 u
11 G- C: G3 z! V5 g; N
12% C7 C+ C }# D: m! J& ~
13
9 F2 S& y' u' h0 R% D14+ L( C3 {. u9 E1 ]- n6 ]2 V
15% q* W! H' _+ h3 [ m1 K
16
! ]: F# v- V8 s( P* G) H/ E3 O1 L2 C170 _& h7 o+ Q# d M. ~
上面的例子中,n表示增加一天CDay,dr中的第一天为20200108,但由于下一天20200109被排除了,并且20200110是合法的周五,因此转为20200110,其他后面的日期处理类似。
& S9 ]7 t/ P7 u& W/ M# r& y$ d+ o+ D! Z/ i( y8 j) _
【CAUTION】不要使用部分Offset2 P5 r& {. m. Y. s6 n/ s6 e9 I; v
在当前版本下由于一些 bug ,不要使用 Day 级别以下的 Offset 对象,比如 Hour, Second 等,请使用对应的 Timedelta 对象来代替。
& C1 c+ }" m, L3 z$ U+ m; f ]
- i5 ^( y5 O* |0 e$ j10.4.2 偏置字符串& P+ Q/ `. \ k& L* ^
前面提到了关于date_range的freq取值可用Offset对象,同时在pandas中几乎每一个Offset对象绑定了日期偏置字符串(frequencies strings/offset aliases),可以指定Offset对应的字符串来替代使用。下面举一些常见的例子。( t8 P5 O2 F' R- C' l. s2 O( t
% G! f+ E8 u! H$ l4 Z- T Offset aliases:pd.date_range函数中的freq参数,为常见时间序列频率提供了许多字符串别名。 也称为偏移别名Offset aliases。偏移别名列表点此参看(大概27个)。
( p2 ^, S* c; o* l2 q0 O6 q3 T, }7 N( O; E2 W
pd.date_range('20200101','20200331', freq='MS') # 月初 J" t; w B4 |. X! a1 R1 ^
Out[91]: DatetimeIndex(['2020-01-01', '2020-02-01', '2020-03-01'], dtype='datetime64[ns]', freq='MS'), N. N/ @5 |# i8 f( ]2 m
- }" {* T- V/ o/ k1 l
pd.date_range('20200101','20200331', freq='M') # 月末
) [: _: Q6 i8 {! O7 MOut[92]: DatetimeIndex(['2020-01-31', '2020-02-29', '2020-03-31'], dtype='datetime64[ns]', freq='M')0 F+ n3 {7 R1 r9 q; E
7 E- u6 }/ }, F
pd.date_range('20200101','20200110', freq='B') # 工作日
& \ V5 m& b1 F; z/ E+ Q6 {Out[93]: 4 L1 h5 p7 ]6 E; V) h3 o
DatetimeIndex(['2020-01-01', '2020-01-02', '2020-01-03', '2020-01-06',; n. ]. [7 i3 f* ~
'2020-01-07', '2020-01-08', '2020-01-09', '2020-01-10'],. X3 `, R' q% h. x7 a2 G. v: P7 T
dtype='datetime64[ns]', freq='B')
8 S2 E9 l, N2 ~( X# T
! d# y [+ o2 Q! {pd.date_range('20200101','20200201', freq='W-MON') # 周一
7 W) v4 p2 |6 A. ]Out[94]: DatetimeIndex(['2020-01-06', '2020-01-13', '2020-01-20', '2020-01-27'], dtype='datetime64[ns]', freq='W-MON')
9 E7 K8 f3 R' U! j' Q' M; L
# t( m. y( z1 a( dpd.date_range('20200101','20200201',+ R0 W+ m: |' s7 u! g
freq='WOM-1MON') # 每月第一个周一 y4 q" f- A$ p4 \/ v7 W
8 `! |8 h3 @1 }" t: q/ O
Out[95]: DatetimeIndex(['2020-01-06'], dtype='datetime64[ns]', freq='WOM-1MON')6 q1 X( V* q1 ^
( h6 S0 N' R' K' W
1
9 g) o5 d, n2 b8 Z( x& y' B2/ Q7 _- c# j% g6 X; [2 v
3
- z ~* {0 u; P- z* k. a4
8 O. D6 ~6 J4 a9 {0 _6 O, l5* b3 ` o/ _6 Z) _# a5 }- y
6
" {9 `7 B# d, c! F ^9 o7
/ t9 g; {" w1 b5 {7 e) Y8 O/ V9 T9 B, L( c1 E: `$ {
9
3 A+ T# D' n% j6 C10- V9 C9 Q1 c3 ?7 Q! L/ @
11/ ?/ U+ z/ w* b5 d) J0 F
12
( C+ T7 s2 x3 f' t138 B3 _/ o2 J& Q0 N0 n
14
: |$ E4 P( f8 w2 y& B0 N0 P9 i15
6 w [+ Y1 q+ V9 F16& e2 M8 ~5 o L2 Y; Y+ y
17" Y' I( V5 ?. ]$ n9 @; A' u/ C
18" T, f5 l, g% J( A9 _* f! Y$ P+ S
195 }: T0 q3 ?9 _( {1 ^8 B1 _& z
上面的这些字符串,等价于使用如下的 Offset 对象:
* H7 L6 U) u- P. H2 N. v9 V! x3 A: G: Y# R+ f. |* S6 m9 V
pd.date_range('20200101','20200331',7 x6 l7 G/ u; E/ }' {0 Q8 c, s
freq=pd.offsets.MonthBegin())
$ i0 k% ]4 ?: b. U% q
( c: c& Z; s7 F4 j+ c! KOut[96]: DatetimeIndex(['2020-01-01', '2020-02-01', '2020-03-01'], dtype='datetime64[ns]', freq='MS')( r) e/ v4 G; ]/ s( M
- y/ E$ U+ N, l" ? _pd.date_range('20200101','20200331',
% [, Y4 S) |: a8 x" K) K freq=pd.offsets.MonthEnd())
6 a: p% i- [8 U/ u; S3 m+ n1 t
& C; M; r3 Z" ]' X/ P3 Z0 y3 }Out[97]: DatetimeIndex(['2020-01-31', '2020-02-29', '2020-03-31'], dtype='datetime64[ns]', freq='M'). L( _ u9 g% w: J
/ z+ m7 z( G, wpd.date_range('20200101','20200110', freq=pd.offsets.BDay())
5 k! ~2 R2 X- c, }Out[98]:
) W! K- u2 N: Z V' KDatetimeIndex(['2020-01-01', '2020-01-02', '2020-01-03', '2020-01-06',
; _" P6 M4 i9 Q S6 u! a '2020-01-07', '2020-01-08', '2020-01-09', '2020-01-10'],( V: Y8 [+ [% S0 D: P+ k6 Z7 x
dtype='datetime64[ns]', freq='B')2 I" o8 a6 G8 a1 Y9 s
2 C% z: G$ W5 L+ z9 K" [: p+ n4 zpd.date_range('20200101','20200201',
D0 ?" z( _9 ~9 a: `- { freq=pd.offsets.CDay(weekmask='Mon'))) a& z$ Z$ B+ @8 Z- A/ ]
2 c0 }4 B5 N- E) B: w. dOut[99]: DatetimeIndex(['2020-01-06', '2020-01-13', '2020-01-20', '2020-01-27'], dtype='datetime64[ns]', freq='C')& u9 q) `0 W% w+ G1 A
6 ?& E8 m1 y7 B3 W6 {
pd.date_range('20200101','20200201',
+ E$ q/ l Z) _1 C1 @6 [: \; U freq=pd.offsets.WeekOfMonth(week=0,weekday=0))
, y! B+ h5 \/ Q* m1 I6 Y
& f: N' h2 M' T9 QOut[100]: DatetimeIndex(['2020-01-06'], dtype='datetime64[ns]', freq='WOM-1MON')9 F& D. N+ C5 I( t/ {0 I
2 f0 M- M' J% Q" p% X$ j
1/ C: }1 H% m1 W I8 {& `6 P
23 p9 x: J9 @8 v% P! C% `( P
3
! ]8 T; L' Y9 Y# v) H" E; B ~! {4" Z* \# w% O! h( _
56 h' c' l+ i8 W' q1 Y+ b. A
6
5 ]. R% D. o% X4 A( I; M. V79 R7 ~8 B8 o# K! O# r( @6 P
8
+ D5 e9 R, w" A' s1 ]1 j, X I3 ?/ B9
4 q& r% r( `' U, \10* |; v% _4 C* T& P
11' P' l- J$ q3 c& Y
12
% o: O0 c% r1 l, u% M13$ P% e) G6 O0 N( \
14
# H8 n. P0 |8 G15
2 r6 J0 o* ~8 Q+ h9 h$ A16* x) T1 d2 u4 G8 z8 `4 s$ `
17; k, r- G, R0 ~) ~3 s* z+ H: e
18" e1 O5 G# W8 s! Q4 H- e/ X M
19/ d* J0 x/ S5 u# i( T; {- n
20' |1 n: R& ?9 I4 h4 H
21
& t) e9 b# C( c: Z/ F7 I2 |5 O4 G22
) |) L6 I4 [7 }: t1 y23
) l W7 U4 v& x8 m0 w1 E243 f$ W6 y$ o+ @ r- ^+ b
25
+ Q) O# f J# h【CAUTION】关于时区问题的说明, g! |3 ]# U( s% i( |$ a/ U4 \
各类时间对象的开发,除了使用python内置的datetime模块,pandas还利用了dateutil模块,很大一部分是为了处理时区问题。总所周知,我国是没有夏令时调整时间一说的,但有些国家会有这种做法,导致了相对而言一天里可能会有23/24/25个小时,也就是relativedelta,这使得Offset对象和Timedelta对象有了对同一问题处理产生不同结果的现象,其中的规则也较为复杂,官方文档的写法存在部分描述错误,并且难以对描述做出统一修正,因为牵涉到了Offset相关的很多组件。因此,本教程完全不考虑时区处理,如果对时区处理的时间偏置有兴趣了解讨论,可以联系我或者参见这里的讨论。7 _" H1 G$ X$ j
( P- W% m. l3 U$ g( G10.5、时序中的滑窗与分组
8 h! d/ \' f# ~* H: d: N10.5.1 滑动窗口7 W2 n. e! O. B- t' U! z6 Q$ k
所谓时序的滑窗函数,即把滑动窗口windows用freq关键词代替,下面给出一个具体的应用案例:在股票市场中有一个指标为BOLL指标,它由中轨线、上轨线、下轨线这三根线构成,具体的计算方法分别是N日均值线、N日均值加两倍N日标准差线、N日均值减两倍N日标准差线。利用rolling对象计算N=30的BOLL指标可以如下写出:
/ Y- E% G$ J0 e8 u; y: o
; y i- C5 I# X; simport matplotlib.pyplot as plt
. `% u* A. U4 }& H( p8 Gidx = pd.date_range('20200101', '20201231', freq='B')& s; Q- q) t' F( R# Y
np.random.seed(2020)+ w& b+ H' x" G
9 C" C7 q, J ^% ?5 a9 v0 ]data = np.random.randint(-1,2,len(idx)).cumsum() # 随机游动构造模拟序列,cumsum表示累加
' z% Z: W# b/ M3 W5 ps = pd.Series(data,index=idx). h3 V7 A% P; E# J3 x6 d, D
s.head()
) x3 c2 t H6 ]! ?4 h( C' q0 O/ EOut[106]:
( e3 l6 H* O& H- }2020-01-01 -1# u- j0 s# V/ O4 W( O" Y/ ?8 h% m
2020-01-02 -2' g( R* b' t K6 S
2020-01-03 -16 `5 q( @3 U5 t% d2 L
2020-01-06 -1
0 p) U5 I6 o; T; J9 |% L2020-01-07 -2$ e" K9 ~( C( T, L9 t8 e1 o J
Freq: B, dtype: int32
E }2 B/ s5 u7 [r = s.rolling('30D')# rolling可以指定freq或者offset对象; _; D- s8 D, ^3 e: d9 P
+ o) s) N7 b# Lplt.plot(s) # 蓝色线: v4 ~, s$ y) w" A* z+ E
Out[108]: [<matplotlib.lines.Line2D at 0x2116d887eb0>]
& P3 l5 d3 d5 E, g3 Pplt.title('BOLL LINES')' x( c z' F/ }2 f9 l8 R! S
Out[109]: Text(0.5, 1.0, 'BOLL LINES')6 w9 Z0 s! o5 S
: ?' t) O4 S b" e
plt.plot(r.mean()) #橙色线
- i# p* A8 E) g) A: F3 WOut[110]: [<matplotlib.lines.Line2D at 0x2116d8eeb80>]$ F } b) w6 Z. p% J) q
- x$ C# n: N6 v0 _- S& h( d" _; p/ \plt.plot(r.mean()+r.std()*2) # 绿色线
; U; m& [2 N, E x; y; UOut[111]: [<matplotlib.lines.Line2D at 0x2116d87efa0>]$ o7 W( g' j! K; h; n( L. M
, q! t, }# ? q: V3 F6 mplt.plot(r.mean()-r.std()*2) # 红色线
7 C. ~# D$ V2 l8 LOut[112]: [<matplotlib.lines.Line2D at 0x2116d90d2e0>]
3 W! K2 a- W. o- R. d' c" e7 H+ n' x( i+ | b- N
18 \& x+ ?3 U- c6 b3 l
2
* @2 I! |8 t W( d2 C3' K! e) K- @' p% \- w" @8 H& b' n
48 y$ Y: {( j" j9 |1 O. _1 E
5
/ \8 U# T+ j. A F% C3 C( U7 }- m6
4 ]5 i1 Z2 u0 i6 a8 E$ j7/ a: L1 V9 y) d
84 Q* F+ v, c6 `9 b# c8 p
97 ]. M/ b6 u7 U' w
10( a5 p- x3 ^7 v
11+ e. k# `* v6 Q P. N
12& S% l7 B" Q$ s/ A( H" }
13% D2 j" m% ^3 W
142 c( ` w# Z( w! p1 k
15
6 u* `7 h5 ]" n16
, k/ y, I0 @, u17
o& b( P1 k3 v18
& A- P5 j# a6 `1 s19
- B% L& o' p* C4 `20
) R C4 W! S+ ^) w( O$ d g; r) ~21, d% q& r1 I$ p5 I& S2 K
22" d* X7 |, g6 ?; M5 F6 z1 t
23
- }2 U4 y) A. G: X24& ^8 X1 O: J2 x- K4 w
25% e" y4 f6 X3 d0 U% B/ X$ G
269 C$ d* p9 G0 k, Q7 r, I7 M: v3 b( l! @
27 B6 v' G$ v! B3 A9 R S, {
28
9 t% u* g6 x" k! F29
6 L; i& c4 X) P3 v$ \) ]
) u. ]- v9 [: r# i$ d 这里需要注意的是,pandas没有实现非固定采样频率的时间序列滑窗,及此时无法通过传入freq字段来得到滑窗结果。例如统计近7个工作日的交易总额。此时可以通过传入多个函数的组合来实现此功能。" o# r! p: S! k, |
首先选出所有工作日,接着用普通滑窗进行7日滑窗加和,最后用reindex()恢复索引,对于双休日使用前一个工作日的结果进行填充。
( o' ] b! S( i& k4 A. O5 b' t) h4 z2 q8 Y% n, v" p
select_bday=s[~s.index.to_series().dt.dayofweek.isin([5,6])]( |2 E- @ ^2 K5 t' b
bday_sum=select_bday.rolling(7,min_periods=1).sum()3 q. M5 C: ^$ Y1 }. s# S& I4 o
result=bday_sum.reindex().ffill()+ w* U: N/ d- I" w5 }& ~( E. U
result/ ]& u; ]1 w3 G9 i
8 m, p# W! T7 O
2020-01-01 -1.0
& h0 E; F& r4 V; ^2 b2020-01-02 -3.09 s& y( E: g$ I! T' S* U
2020-01-03 -4.05 {3 @1 y. G; g* a. P9 ^
2020-01-06 -5.0
/ H1 d. w$ h* o8 I, X2020-01-07 -7.0- I6 M1 i3 Y9 l7 P( I6 ^$ h
... $ }* e+ o/ G* b( c) l9 @. x9 X
2020-12-25 136.0' h6 }; z5 T& l1 D4 \6 C
2020-12-28 133.06 T s$ s, h- F2 r3 R; U+ t- e, b
2020-12-29 131.0
2 u% Y# {/ p+ J, e' Y; V; p% s2020-12-30 130.0
/ r: q# O# w @) R2020-12-31 128.0* v9 g4 _7 c4 {- z, ]. L& p1 t
Freq: B, Length: 262, dtype: float64
7 j& s7 [7 _+ v9 E1 C, s+ W9 O! K8 N! i9 D5 \
1" C( V: W9 ?" @$ B# [2 ^, h
2
5 G, d3 g# w$ L6 F4 B7 X4 l3
- ]/ K7 P( i. m, m: |4: B; F7 n: |( R! Y
5
; D P, J' w7 w& j64 J- i4 Y) ?. K! B5 v/ z
7; c4 D; l& o; o
83 H" W4 A! ]: O& \
97 C& V* n; T d1 b
10
4 N# i# r2 T+ t' _11
; H( q( f% Q3 r/ q* |12
4 ~# `$ r n w. p8 a* P; [, k0 j% r13
" @0 [/ ?* Y- i" a7 L$ l140 [& W8 U' O) x9 T( b
15- w2 Y; j- B1 s) Q: A- ]
16+ U I9 M4 l5 T6 C) v
17
0 `% W6 ~% R: b* q0 z6 W; ^7 D shift, diff, pct_change 是一组类滑窗函数,它们的公共参数为 periods=n ,默认为1,分别表示取向前第 n 个元素的值、与向前第 n 个元素做差(与 Numpy 中不同,后者表示 n 阶差分)、与向前第 n 个元素相比计算增长率。这里的 n 可以为负,表示反方向的类似操作。
- \) q- N) l9 G
7 H0 E+ a, ~ a7 ~: {/ o, x3 X/ o# Z 对于shift函数而言,作用在datetime64为索引(不是value)的序列上时,可以指定freq单位进行滑动:: T% n0 y0 y5 @: U- Z' H
% K- Y0 x/ q+ g! Ws.shift(freq='50D').head()5 V% k5 D3 b" e1 r* P8 |( b( m
Out[113]:
& E# l# l& L4 I- Y4 C7 w$ l( s% N2020-02-20 -1
/ A6 h, t8 i6 d; {8 b2 `% M2 Z" E2020-02-21 -2
# \: N2 @* H% N9 h# `9 ^2020-02-22 -1
% a' m: K4 U4 t. g2020-02-25 -14 v1 U m* Z. `; h
2020-02-26 -2
$ T+ V6 Z5 a4 rdtype: int32. t/ q+ R! W0 H
15 v H# c$ n5 m
2
9 V% `' w4 }% A7 J3
3 H' }5 \: O8 E# w( ]4
7 M1 o& Z6 d( `0 o# @5
- ^# q* I9 x' S. w9 ]7 ]: A7 c69 K) c3 s& f/ T& c9 R6 N& F2 C p) U* j, c
7
3 v, @7 q v& x' p& f9 \9 |% W8 |8' V; L* H4 G8 T7 |4 L. F R7 s
另外,datetime64[ns]的序列进行diff(前后做差)后就能够得到timedelta64[ns]的序列,这能够使用户方便地观察有序时间序列的间隔:/ f% y' P& o1 v
3 b3 [( q( Z: A* O, p$ U4 cmy_series = pd.Series(s.index)7 _1 v/ O' m+ u: q7 w
my_series.head()2 w1 V( B, e! A6 D/ o" N0 M
Out[115]:
1 d6 q7 ~2 f8 d" f8 r( j% F0 2020-01-018 a0 \' ^3 s4 Q. {
1 2020-01-02
* ~4 |; ?% i8 C3 n, z+ j: X9 k2 2020-01-037 E" G6 m+ j" F
3 2020-01-063 r7 E7 v% ]2 K7 F: m% g5 d0 }! N7 X
4 2020-01-07# d' [5 ~+ u9 ?9 L4 Z
dtype: datetime64[ns]
& w+ O9 n1 g8 w
4 ]9 l; G8 g H1 ?$ u1 cmy_series.diff(1).head()
& B5 [8 M. z0 J) S; fOut[116]: 0 @, {6 d9 k8 ~% U
0 NaT
( t2 J, Y4 r; N! u* t1 T3 U" a2 R1 1 days; K" ~8 l4 u/ \. a& K9 Y
2 1 days
" r. z( _6 G& z( |) s1 _3 3 days
% r5 Y) G( E& C4 e0 Q4 1 days
6 s# g$ n% k& x ~. @ Q! n2 Udtype: timedelta64[ns]3 i6 Z) K& n+ z
7 @, u8 p, I+ [: `! O3 k/ l2 @: Z2 |6 f
1
/ m s' W, f" _( K23 N5 r0 o) C9 l7 f
3% F7 R7 J! \" g# y
4
Y1 c; n1 i5 O) `8 c5# a; `/ L1 s$ { V4 o3 n
6$ {/ b" \8 \" B0 n- G8 g3 t
7
2 f8 s4 D8 Z0 m x: {8
8 J8 ?! V3 _; i9) {& F; W8 B3 s0 D0 q
103 _* d7 P/ h. h1 z+ A0 C( D- b
11
! z3 t/ x. w# N) ]- X5 w1 V12/ y7 M3 Z- a( N$ m9 @( g8 Z
13
& [8 Q9 T# G6 K8 C5 q' Z141 o8 A' G/ b) S+ V# I
15
1 Q' p& C' N; f* r5 w: k( S16 t2 g. G" a* Y( p! z% H
17$ q" z- N- C: D, O/ l
18# ]( [4 M5 I( D. s T4 _2 Y
10.5.2 重采样0 G. r1 M, r# Z
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)* a2 m) W7 k! c) |
常用参数有:
) a5 g! G5 k9 |2 b$ g9 N q% @- p) t* @ z. e5 ^7 Y: Q
rule:DateOffset, Timedelta or str类型。表示偏移量字符串或对象
1 n* F% V9 k( i) z6 F+ yaxis:{0 or ‘index’, 1 or ‘columns’}, default 0。使用哪个轴进行上采样或下采样
# ]0 s7 h3 R( d' rclosed:{‘right’, ‘left’},默认None。表示bin 区间的哪一侧是闭合的。所有offsets的默认值为“left”,但“M”、“A”、“Q”、“BM”、“BA”、“BQ”和“W”均默认为“right”。
; l# @/ ]5 U8 E" a& d- M# Qlabel:{‘right’, ‘left’}, 默认 None。hich bin edge label to label bucket with,所有offsets的默认值为“left”,但“M”、“A”、“Q”、“BM”、“BA”、“BQ”和“W”均默认为“right”。6 \2 n0 M' F: ]. X' V
convention{:‘start’, ‘end’, ‘s’, ‘e’}, default ‘start’。仅针对 PeriodIndex,控制是使用rule的开始还是结尾。
2 h" o, Y3 {! y% con:字符串类型,可选。对于 DataFrame,使用列而不是索引进行重采样。列必须类似于日期时间。' `% t I, C4 d2 G
level:str 或 int,可选表示多重索引MultiIndex的级别,这个级别的索引必须类似于日期时间。
5 G1 i( z8 K3 u( G' u5 porigin参数有5种取值:5 e! P' c7 p. K& l. o/ Q3 \
‘epoch’:从 1970-01-01开始算起
- F) h; M/ Q" q% d- E‘start’:原点是时间序列的第一个值; ~& @! f' F/ Z2 a# n
‘start_day’:默认值,表示原点是时间序列第一天的午夜。 n& E' |% h4 Z* F7 E. o/ g# n
'end':原点是时间序列的最后一个值(1.3.0版本才有)
& T8 ]. w3 _- W) |# }‘end_day’:原点是序列最后一天的午夜(1.3.0版本才有)
3 e: J- s {* N4 \offset:Timedelta 或 str,默认为 None,表示对时间原点的偏移量,很有用。. P8 o* j$ t& e0 ?' l
closed和计算有关,label和显示有关,closed才有开闭。
0 r- G+ G5 x' \/ r label指这个区间值算出来了,索引放区间的左端点还是右端点,closed是指算的时候左端点或右端点是不是包含。
% s/ R. u. |: x. I
9 z3 r2 U" S' q3 Q" o) {4 e重采样对象resample和第四章中分组对象groupby的用法类似,resample是针对时间序列的分组计算而设计的分组对象。例如,对上面的序列计算每10天的均值:
# S- W9 w' n9 k( _) X/ Q! ^ bs.resample('10D').mean().head()
( V$ v4 o! D4 OOut[117]: $ F; B5 \) e7 K# y* H& B/ w# l
2020-01-01 -2.000000
7 F4 F( z+ B# y/ G7 z2020-01-11 -3.1666679 I; f& c# n7 ^# s0 D4 b' M7 _* c. E: u
2020-01-21 -3.625000
: J) T- H7 A3 D1 Z$ R6 t2020-01-31 -4.000000
2 _2 ?2 n8 b2 n0 V# K2020-02-10 -0.375000
0 x* r* G2 T# Y0 t8 q: uFreq: 10D, dtype: float640 k; W8 [5 o- p6 l- ^# y& A
1: C! ^9 p A1 A9 V
2- P8 c6 g* _7 D% U
32 ^) W& }1 D! W- U$ v, b, L/ A
4! U: ?4 O" H1 U, u9 c5 d
5
$ q/ r, q$ ?4 a8 s6: b1 X6 A3 m1 i" s0 F" ^
7+ h, V% ~, L6 Z
8
6 C6 {' O+ h0 M3 w# g1 ~! M$ z! F K可以通过apply方法自定义处理函数:9 w6 ?% N Z2 x: K3 n+ w0 i, E
s.resample('10D').apply(lambda x:x.max()-x.min()).head() # 极差% T6 g. y+ z" K( x/ g& m+ N
, N# E+ v" U, g
Out[118]: 8 W' y* ?5 a+ V; G# q3 K
2020-01-01 3* B' k6 f& X: K* K9 W) `* R# K
2020-01-11 46 s. S2 D. K# O9 [1 x+ Z* e' ]
2020-01-21 4" S7 r' [+ z l" e" B! p5 Y/ Z( l" _
2020-01-31 2
' C4 g- c+ \5 t2 c2020-02-10 4
' I& o c0 G, g+ B% A% J6 ]! MFreq: 10D, dtype: int32
# j: y+ W" @; X" [4 B1
8 n3 M2 c% @6 z2 `2, ^4 `1 S7 h& Z: y) T
3. z) i8 G+ k5 Y4 O! g+ w
4- T0 a8 b& v+ B
5
* ^8 J) Y x: _+ B# D6
: L& \* J p5 _4 V" n+ F+ ~7
9 c- U. Q% K$ P m! P0 X3 N82 y: b5 A( o ?3 i
9
3 X& i! [3 C2 F/ F 在resample中要特别注意组边界值的处理情况,默认情况下起始值的计算方法是从最小值时间戳对应日期的午夜00:00:00开始增加freq,直到不超过该最小时间戳的最大时间戳,由此对应的时间戳为起始值,然后每次累加freq参数作为分割结点进行分组,区间情况为左闭右开。下面构造一个不均匀的例子:1 }! Z) F* k* B; ~/ i
]5 _- X$ R! d% O+ q3 F: Fidx = pd.date_range('20200101 8:26:35', '20200101 9:31:58', freq='77s')1 M) ^: ]+ r7 J8 v$ b5 q
data = np.random.randint(-1,2,len(idx)).cumsum()2 i( L+ R& `/ U& {& [0 [* T
s = pd.Series(data,index=idx)
) d& U( O! V# b1 as.head()( k: q) K% G$ _2 a ^# J- z9 \ A0 d7 [
+ y3 U5 {6 t2 e. y6 ?
Out[122]: % d5 E) o7 X: [2 i
2020-01-01 08:26:35 -1' |' l7 D, m/ ?# f p& I
2020-01-01 08:27:52 -11 ~4 N" K" h8 b9 l, K' W; c9 P
2020-01-01 08:29:09 -29 G& D8 r1 c& i/ K+ q
2020-01-01 08:30:26 -3
, f/ o: l3 J; e6 }2020-01-01 08:31:43 -4
6 F a5 h: y, a4 i$ N* C5 n0 sFreq: 77S, dtype: int326 \; j6 q- `2 u( e m2 D) m$ u( j6 M: {6 y
1
- i3 @& ^9 p9 |; d9 w( K2
- o) _! n8 [3 S& R3
4 [7 ?2 E' ?$ Q7 {' z! F4! Q( z5 U! Y. @5 ~- |9 T- ]
5
. y, I1 q+ g4 b/ X: j2 y1 l3 e5 T6
; h) d( C$ o- ?3 G* y7
3 p# L8 |' w, v- z' B81 E/ o k7 M. u6 ^# ^) `6 ~
92 I0 k O! f I. T% o
10" K. B ~, K* E
11% |6 l1 q R) H
12" D% T! f8 e2 j" Z
下面对应的第一个组起始值为08:24:00,其是从当天0点增加72个freq=7 min得到的,如果再增加一个freq则超出了序列的最小时间戳08:26:35:+ C' l9 @# t: P- ^" z9 s) }
+ b% L! }# e6 J) V. S1 t3 Y
s.resample('7min').mean().head()6 c3 y. l k6 B0 d2 G8 F+ M2 n
Out[123]:
' f; z7 w' u; j1 J2020-01-01 08:24:00 -1.750000 # 起始值,终点值包含最后一个值6 K( z. j, i' h @6 o
2020-01-01 08:31:00 -2.600000
4 h1 G' [1 c, ?2 Z# C4 _! Z8 j( Y0 e2020-01-01 08:38:00 -2.166667
% ^( y2 L+ u7 n) f7 z9 o2020-01-01 08:45:00 0.2000008 e7 I/ |6 a/ F
2020-01-01 08:52:00 2.8333333 u7 s* _8 y1 O+ f. H" [0 i+ E( L$ K
Freq: 7T, dtype: float64
# O* O9 u9 y( K) u4 g, a, c1
, E9 Q I6 L9 Q; _( H4 @0 a2$ ^( N- F: ~# K7 M5 E# z) X
3+ ?3 I% T3 I8 U [% V. u
4 I( L* z! A# Z5 ?7 Q
53 A" E2 C! C5 l6 Y! Q$ g
6
) J6 X, t( _/ A9 r4 l, N7
) g. p7 h" b: B8
4 V4 A6 g8 t' m7 Y9 P# }, m 有时候,用户希望从序列的最小时间戳开始依次增加freq进行分组,此时可以指定origin参数为start:
) O; y& n9 P4 N5 v$ G4 a9 h. n8 w, F
s.resample('7min', origin='start').mean().head()
* n& ^* w9 C8 b0 N1 I3 q% fOut[124]: * }' q& C" L* n) p# M+ g Q
2020-01-01 08:26:35 -2.333333/ S+ F8 n2 u! q; Z; ^
2020-01-01 08:33:35 -2.400000
9 \+ F) f. }4 x6 x. a' {: |. ]+ q2020-01-01 08:40:35 -1.333333
9 p- m( I$ d# f& h9 a. T: K2020-01-01 08:47:35 1.200000 q% _) j7 t9 D/ y3 A& b! d
2020-01-01 08:54:35 3.1666671 G5 M; M4 ?: Z, @' N
Freq: 7T, dtype: float64& [) i1 p! L; q4 @! ?
1# o; X3 C) B0 U* R% V J; @; l) ? [
2" ?+ W w& \# C! Y5 ^8 ^$ c
3
( L' i; ~- ^3 A% a4
1 L2 g; k8 d: `! }, r5& ~: `3 o$ s" q+ g$ t- i7 {
6
! @- @" j! k; @4 M/ k6 o7
1 Q7 j: {; [" v: B( R' g$ i8
, y4 u' q Z0 k' i: [ 在返回值中,要注意索引一般是取组的第一个时间戳,但M, A, Q, BM, BA, BQ, W这七个是取对应区间的最后一个时间戳。如果想要得到正常索引,用’MS’就行。
$ _- p- ^+ V4 ?! ]' x$ \" F0 d; @3 w0 t- }" {+ S
s = pd.Series(np.random.randint(2,size=366),+ D9 A6 H0 |# T# {4 a+ S* U
index=pd.date_range('2020-01-01',
$ u, U( I$ }/ x9 t" B) L7 ^ '2020-12-31'))7 r( A' ?8 d s( I; m" I! r! u
% j0 Y, B7 U1 u! F4 n4 u
! n+ J4 x/ F! ?6 \$ `- ^3 V/ E& B
s.resample('M').mean().head()
5 H" u" X0 N# [- ~0 g" o ~Out[126]: . @9 U1 t0 P! T3 s* ]0 H) D
2020-01-31 0.451613
) o+ }6 p) w& A! D- c2020-02-29 0.448276! O; K: K8 b' o
2020-03-31 0.516129" q4 w& Y; u; u8 z
2020-04-30 0.566667
+ y6 \5 }4 |9 t; W L6 [; Q2020-05-31 0.451613- a+ V6 }$ I- C) n Q9 v9 w& ^ S
Freq: M, dtype: float64& A) B8 H2 r$ F: ]- B) m) a
( a+ {# M. A: f* E/ u' bs.resample('MS').mean().head() # 结果一样,但索引是跟正常一样. R) } Y) T; O
Out[127]: {$ N+ Q" f8 B2 }) s
2020-01-01 0.451613
, v; C1 m* m# t& z9 U2020-02-01 0.448276
+ R9 g5 h$ m. E- S; X. w. {2 }5 S% j2020-03-01 0.516129
$ d/ a* j1 K$ [/ |/ Q2020-04-01 0.566667
; Y* n1 |7 z0 G4 K/ ^2020-05-01 0.451613- I# V. p- T' A4 l
Freq: MS, dtype: float64) [) T% U2 H7 o \5 f6 ^ k" e- {
7 X% l( a# ?/ S1& I/ t% r: b- n2 r
2% @% [/ K& |/ _3 Z V1 e4 ^! U
3
% I. [3 F4 p8 ~2 D9 t2 {4
8 b, j* o# f0 Z5
& j& G& I3 K7 L- b+ W64 d- h, m4 g3 s' q; o5 p. B2 V0 e9 X
7
, {( U i0 J/ @# J5 f q& i# j) V8
* A, d* o0 l/ t, E3 N$ a9
5 Q9 f) r! ^3 `* I104 k: l# [8 ]$ l! `& f
11
! _+ O* }' L2 s1 }3 z. z12
) X# A% x* t" f/ ]/ A: p p! ^+ P3 u13
7 M$ `* p' X: }! u9 ~, Z14( L; ]. I* B3 r" t" w
15& J$ N; s2 U; X5 n' E: K. }, n
169 H6 i+ @, e+ K4 @/ o! Q
17
$ \; r0 s i$ s18. j+ T+ W1 ]% y) f. N4 N+ n
19
: N. W j, P. ~: M. ^20
$ X2 o. D' ?& e+ i! G6 o21, k' o `1 E: Z$ _* r- i
220 W8 n# s5 ~1 `8 D. t9 s+ o' _
对于 DataFrame 对象,关键字 on 可用于指定列而不是索引以进行重采样:
2 s9 s, P ?1 ?" nd = {'price': [10, 11, 9, 13, 14, 18, 17, 19],
6 G( D d, O$ a4 G 'volume': [50, 60, 40, 100, 50, 100, 40, 50]}( q- w* A, t' ]0 j
df = pd.DataFrame(d)
3 E x- k5 T) L; j5 Rdf['week_starting'] = pd.date_range('01/01/2018',
; ~% H; b5 M* C; I, q9 c% z periods=8,7 \: E0 z) M) w9 e: g/ J) |
freq='W')
4 h2 |, I& B7 m6 u7 T$ v9 Adf
( k* q1 D& n' H8 s price volume week_starting" O, M4 \9 ^0 ?$ ^/ \& K. j
0 10 50 2018-01-07
& d5 Y/ y5 z' p* h1 11 60 2018-01-14) x' _0 U4 r6 G5 |+ Y1 l
2 9 40 2018-01-21) e+ [4 n( X% W' Z* w. t
3 13 100 2018-01-280 |# N! W' C2 v# M
4 14 50 2018-02-04
7 Z! G. r( \& P3 d5 18 100 2018-02-11
' A4 F' [. E8 z* x, l4 b* Y4 Y6 17 40 2018-02-183 @# c/ }3 q+ K; T; n$ l6 L D
7 19 50 2018-02-25' U; o! z) ?; A2 {. w" d: r& s
df.resample('M', on='week_starting').mean()
# u/ c, q: d$ v price volume
' G* a: Z& a3 Z( p+ A' J0 }week_starting. ]1 a1 P9 d) \' z" Z- j
2018-01-31 10.75 62.5) @" d9 U( g9 E5 [% i- d% ]
2018-02-28 17.00 60.0
% T0 z9 z. M2 t% `6 c, E5 c5 ?8 ^" d8 |
1
4 Z7 M7 X# q' D2 J2 B) E, [! w4 M2
& t& i% o. ^0 R& S8 W9 v3' E, c k r3 b8 q6 T- d
46 h2 E) F% g5 b' i$ y
5" K9 W7 H8 x4 G$ ~: j
6
% G* u0 I( V7 p9 a1 S4 d7
! L+ o! B8 C! t$ y0 P0 G- W8 M! a! ~ G; J4 v ~
91 ]% c9 U) \. I Q2 i% [4 p
10
% b4 T/ Y/ \+ a) {9 I# ?, {111 P" K) z* B. t6 A5 T. y
12
: [$ T- X0 a! Y4 m# v7 a9 m R130 v) Z9 S3 ]+ }! G5 E
14
( E2 _+ a' ?7 @$ v* X) W15
?8 T; L' z6 _" Z- C4 G16
7 W9 F1 k/ @. i1 c, |- H5 B( A. d$ i17
) k7 c# j: b8 v2 G5 p/ Y18
+ b* L' r* v i, a7 l. [19
/ j8 ^& d7 p- R. i: p20( z: S2 M8 A# K% s9 ^* x3 o; U# y' w
21
) u+ L$ C" z8 `对于具有 MultiIndex 的 DataFrame,关键字 level 可用于指定需要在哪个级别进行重采样。
: o5 h+ o) I' E: tdays = pd.date_range('1/1/2000', periods=4, freq='D')
7 N5 o7 [) b! S. B4 ad2 = {'price': [10, 11, 9, 13, 14, 18, 17, 19],1 t( u, g$ s( u0 c
'volume': [50, 60, 40, 100, 50, 100, 40, 50]}
8 D6 p6 t2 N$ s& ^- P/ L; Qdf2 = pd.DataFrame(; }% r5 M+ h, H/ A% [ C
d2,
. e, o- L" X3 T& |' f: i& x( v8 U& I index=pd.MultiIndex.from_product(# z( A9 l( N$ t( s% X+ d E+ B6 B
[days, ['morning', 'afternoon']]
M c) \9 k9 Y! v; B4 V )/ E5 h; ^/ a% P) K9 y8 Y
)
: d3 K; e9 T2 B/ n' @2 ~df22 \2 | }# x- t+ c9 y6 y
price volume9 e. U z4 j7 i) w7 E
2000-01-01 morning 10 50
3 T3 O, L: v+ _: d afternoon 11 60
) m9 R# W' |3 {# S2 W2000-01-02 morning 9 40
) l# m) j* ?) _2 P. Y! R2 ^+ m, o afternoon 13 100
+ w- l$ h8 S4 f$ U1 j; H2000-01-03 morning 14 50, z7 o# m2 b9 A
afternoon 18 100, ^8 R3 V# u* J% o
2000-01-04 morning 17 40
" I: @ `' z7 S! [/ A( x afternoon 19 50
: S B; e- @4 kdf2.resample('D', level=0).sum(); b& T% p+ R4 C$ y! x
price volume
8 \3 R, o8 O" f$ s# O2000-01-01 21 1103 j$ b) ]" U& b9 ?8 K; x% i. P
2000-01-02 22 140
6 T$ c( ?9 Y- c3 A Z1 @2000-01-03 32 150
. ?; {* f6 I) \/ m2000-01-04 36 90
7 r6 p! V# o5 K1 f3 P0 X) L3 j
1
. x( O, h# B* E; H& J2 n4 A4 V) c% L7 N" @! p9 F
3: X/ E: G& J7 |
4
& A: S3 }5 H' F/ W0 ]$ [2 ~5
- Y W1 e4 E* `2 Z" _6
% N. r7 B d# w6 Z( m5 o7* _; x# F5 p z, Q* s
8$ c) S- U0 a/ Q. Q. K
9" D& b+ F0 [' r
10& _) v" H$ {, x$ E: A* l
11' f) S# S9 e2 L: U! b; l1 [
12
: `5 E; V1 j: }8 m/ y3 B13
& I2 U' l5 n6 n& p3 W& e- ^14
: R9 ~' o- {+ @8 P15
# W% S5 O7 P* j/ D$ V3 }16
; J) `0 s4 N6 s17. G U5 j+ ^2 @( `
18
4 @' X$ r p. c- }3 S) a19' w8 o5 W2 y; H9 {' l
20+ X( k. {% p( Q& n* E7 n
21; F; H0 k0 m. g
22& Y2 \ f3 J6 c* r+ _" G
239 W3 c) Q+ A# k7 m L. a
24
, \' H0 b& Q) D8 G25
4 s2 U; u F, O& ]3 B/ o# l根据固定时间戳调整 bin 的开始:
, z3 H7 O9 M! B8 w) Rstart, end = '2000-10-01 23:30:00', '2000-10-02 00:30:00'
9 C ~, S. V# I( ?% orng = pd.date_range(start, end, freq='7min')( d3 \2 @, v9 s1 a: O, d k
ts = pd.Series(np.arange(len(rng)) * 3, index=rng)2 Y" \, Y( i& m& C6 H
ts
4 |# W% w+ f- s- q7 p( L4 }2000-10-01 23:30:00 0
, P/ Q- H/ D; ?4 w4 i2000-10-01 23:37:00 3
6 m# y0 j# y% V- |& z2000-10-01 23:44:00 60 j- q% D8 ^5 W" Q: O6 v
2000-10-01 23:51:00 9
/ U( Q' F% f& m }2000-10-01 23:58:00 12
- V6 S# i- |) @) ?8 |3 B- h2000-10-02 00:05:00 15; [+ j3 ?( q9 ~, i
2000-10-02 00:12:00 18
) j+ {7 j- \* V; J8 ]* B, B4 x3 G2000-10-02 00:19:00 21
# p5 R* p9 k3 i9 {2000-10-02 00:26:00 24, [6 e( D' W+ r5 R3 |
Freq: 7T, dtype: int648 g$ O* M3 U: x7 F \+ n2 O
/ ]# d2 ]! x, c7 [+ F* K0 a
ts.resample('17min').sum()$ t2 i/ I0 I) ^; J! N" O1 x
2000-10-01 23:14:00 0
+ w# P- z: U5 p1 m; ]: u7 J2000-10-01 23:31:00 9
) B9 @( R* [2 O4 U) G- _; P2000-10-01 23:48:00 21
+ r( Y, E% o2 J2 P2000-10-02 00:05:00 54
2 g1 U( E/ ^/ i2 h9 S' L" N1 [2000-10-02 00:22:00 24. t+ o( J7 X1 R0 h2 V8 m
Freq: 17T, dtype: int64
6 J" r4 D1 I/ d7 x \8 k% }1 {4 d4 \$ ]9 q
ts.resample('17min', origin='epoch').sum()7 Y2 o2 K5 k8 ?, p- p1 ^
2000-10-01 23:18:00 0
. g* e, r6 ~, e( R' [/ L2000-10-01 23:35:00 18
F4 v1 ?6 u b, ]- J- L8 L2000-10-01 23:52:00 27 E3 m% r1 x3 l- e# h, E
2000-10-02 00:09:00 39" _& g9 o! x8 ]
2000-10-02 00:26:00 243 S" l; }# C/ w# Q; u! s" o8 o1 y
Freq: 17T, dtype: int64$ ]! `. |: L. y0 `* A
& a4 t9 n: \" T# V B; ?0 S
ts.resample('17min', origin='2000-01-01').sum()6 u, D: _$ _4 o" B8 G) x& Z: w
2000-10-01 23:24:00 3
" d. Q2 r+ m2 Z1 F& E; @, R( M2000-10-01 23:41:00 15 e' D/ q' N' f1 h( u- f
2000-10-01 23:58:00 451 E+ P R. r' d. X4 Z! \
2000-10-02 00:15:00 45
- I# T+ [( f) X2 b& WFreq: 17T, dtype: int64; D" K; G0 J$ A8 S5 j! m2 ~4 \6 v
& s; w! V4 F- l7 ?; q; ~1
0 f/ l/ a! K( i* ^; Y$ E) [, ]+ ~2
1 e- b: o4 ~, D1 l3
4 L" P( r Z9 v5 s& U" g ?2 _4
3 ^1 ^; ~1 x% P$ Z* F& S5
* G0 ]0 Z( m# X- K4 c$ {4 W6. e ^! e+ B, M' x1 \; G% i4 Z
7
) g) D' a3 W# Z* ^* f* H81 h" b4 y. V% m* _
9; V A# {5 N9 j9 [8 z! G& G
10
" v7 t0 V3 ~* Z: x* i; s# f11
1 c3 u" `/ }9 z( p% c8 i$ M12
% X1 T' Y: Z. q" H4 G13! x1 b0 G# T* U2 c; m, [
14
* x5 H" b- l$ s) f, S: n* `0 y15" @( h2 ?0 @6 a+ q4 }2 f
164 h& ?2 j4 b/ l# N1 C$ z$ M# Y
176 f+ m& {. U' ]& J
181 R; X# V0 |9 I& V n3 U) F
199 \8 L2 u1 w. d3 G
20! T8 r7 w" W2 k+ n9 P' C
21
5 Q7 d% P6 L# ]4 s, R H22
2 x8 `& u! o9 \% n' o- C23; B/ |3 o, T% V
24
8 @ v) Q+ ]6 y% G0 K) r/ z/ I3 X25
" Y/ A1 r: r+ |* x) u" r26
4 {5 q, {" t9 N, J V27' x* D6 f4 t! l$ l- ?
28) g' I9 F, S0 o4 }
29
* Q- C4 g, C* ]4 a306 w: u( D# q- k6 O, _1 W }
31- W' G# C3 x3 g; u
32" ^8 p; t( m3 f/ a
33 h( A' W( \& h" f# p
34+ ]( h1 h( i" M q
35# ?4 M- y8 q8 I+ w% O5 e
36
" D$ C5 G+ w/ H7 g8 c1 d37$ m$ z: R! c" p$ ?% }
如果要使用偏移 Timedelta 调整 bin 的开始,则以下两行是等效的:" K w. k& ], o) T
ts.resample('17min', origin='start').sum()
. |) u) d& _8 h1 P0 Nts.resample('17min', offset='23h30min').sum()
, N5 U5 p4 i9 v3 w- W& Y2000-10-01 23:30:00 9! C6 i0 L. V: [; M' P
2000-10-01 23:47:00 21
! A/ c! m2 F! o( f2000-10-02 00:04:00 54
, e) O: ~" X2 N2000-10-02 00:21:00 24
4 M) d0 w- i% nFreq: 17T, dtype: int642 Y4 K8 U7 m J0 M; j. n2 n8 K
1. Z) p0 J3 W4 M. f; L; ~% h
2
6 Z& y2 [3 l; s9 R ~( |/ P3, N$ Q- A! ?, p) Z
40 K, O. Q6 Z: A/ |
52 T; E$ |$ F9 ~2 Y
6
, f, H' Z! S9 c$ ~ D' o3 A7( w _; i6 R* v, v
10.6 练习
% l% O1 p9 u! c/ [: [1 _Ex1:太阳辐射数据集; p) i" a, X8 z0 R: [+ P
现有一份关于太阳辐射的数据集:
! Q6 ?6 R0 Z& G( ?
, ^! J( R4 U; r; }! I; X" jdf = pd.read_csv('../data/solar.csv', usecols=['Data','Time','Radiation','Temperature'])) N. J! N' ~4 z2 K
df.head(3)
. @8 l! o, o+ W1 D5 z
$ |* S1 b( C/ J6 p/ uOut[129]: " y5 r4 D( h- S' ?$ _; W @* y
Data Time Radiation Temperature
" ]; m+ E, [. h, Y0 9/29/2016 12:00:00 AM 23:55:26 1.21 485 b, E# m6 V8 p& o! a
1 9/29/2016 12:00:00 AM 23:50:23 1.21 48& X# z% s% N( T' b6 }- T: C
2 9/29/2016 12:00:00 AM 23:45:26 1.23 488 M1 _3 a7 c- V \3 S: N9 M6 \3 O# Y
19 A' [0 e& `" x3 l+ w2 C
2
}+ {- K, U: ?% }3
/ I8 _4 A6 C/ h; i6 t& Q. {4
0 U( Z8 y3 P v: R1 t56 l+ ~; C/ ?6 B7 \& q) p( V
6* O8 Y! X$ H; i% }9 f
7: K0 O3 a8 ?4 r5 B
8( {2 {* J4 Y1 {* b+ ~7 ]* ]
将Datetime, Time合并为一个时间列Datetime,同时把它作为索引后排序。
; d9 t7 G5 ~- e2 P; r) z/ ~; b$ K/ b% @每条记录时间的间隔显然并不一致,请解决如下问题: B# g9 w6 c2 \: n8 S
找出间隔时间的前三个最大值所对应的三组时间戳。
+ V; w" ^$ \; K* S是否存在一个大致的范围,使得绝大多数的间隔时间都落在这个区间中?如果存在,请对此范围内的样本间隔秒数画出柱状图,设置bins=50。8 ^8 n. E: _: l/ `$ Z6 r" c0 m
求如下指标对应的Series:
j, g' F9 [; {* b5 V- I1 b% J温度与辐射量的6小时滑动相关系数
0 Q& x/ O9 J) N/ J6 J$ f以三点、九点、十五点、二十一点为分割,该观测所在时间区间的温度均值序列
& p' G" O$ t( o# i0 B6 X% i每个观测6小时前的辐射量(一般而言不会恰好取到,此时取最近时间戳对应的辐射量)$ w `9 ~3 I$ p+ ]5 y
import numpy as np1 I7 m! \; Z9 `$ q" Y. g
import pandas as pd+ @" l: S6 r: h" E
15 J, P3 w+ x* b9 ]; s. K: _2 I0 q
2
- r1 L4 Q) `/ D将Datetime, Time合并为一个时间列Datetime,同时把它作为索引后排序。
% R* J+ F4 x6 Bdata=pd.to_datetime(df.Data) # 本身是object对象,要先转为时间序列; w( E* q1 `8 m: Z# R' X* s
times=pd.to_timedelta(df.Time)
" @2 l `! q/ p) \8 Ldf.Data=data+times
! `; `- f- n4 C- o8 l, g( sdel df['Time']
' T- Q$ c3 z! }! X" ldf=df.set_index('Data').sort_index() # 如果写的是set_index(df.Data),那么Data作为索引之外,这个列还另外保留. ?, O0 |: S6 n8 g9 L" i4 ^% [
df/ Y6 g z5 X! q* |/ s4 u
Radiation Temperature
2 [& u O) q# h: e6 ?Data 3 W- d1 z; x0 }8 k- I
2016-09-01 00:00:08 2.58 51
- B8 T* a# g4 c$ G# U2016-09-01 00:05:10 2.83 51
; v7 P" {2 Q X( j. O' ]2016-09-01 00:20:06 2.16 51
/ p0 u3 ^- G" Y0 [2016-09-01 00:25:05 2.21 51
0 h; D" p( k1 a4 c4 o2016-09-01 00:30:09 2.25 51
' e. s+ _: D, K1 D) Y: K... ... ...( c, L+ M7 w. U/ f- Y5 S! V/ o; D
2016-12-31 23:35:02 1.22 41
! x D) y6 _1 i2016-12-31 23:40:01 1.21 41
$ R( ?1 C* b( c% I. v% y7 r2016-12-31 23:45:04 1.21 42( G- T; l6 L5 K! c( l
2016-12-31 23:50:03 1.19 41
+ [" w. m7 b _8 |2016-12-31 23:55:01 1.21 41% @2 i7 ]1 a* z1 N
a2 {4 ] ~$ b3 I7 V, v5 \1' w6 }$ e0 J, f$ W$ u8 T
2
6 N5 b0 o9 n- x: \3 O; }39 t) p1 ?8 X2 j3 q
4+ n5 i$ X3 f" F8 w& \( Y+ T
5 G" [0 f2 m. R# K+ C
6* d8 u" q- v4 e, p4 K, G$ P/ a* _
71 C2 M4 {$ \# d6 M
8
! Z7 q* z- N, r% m d( t9& h" y7 \$ V" J/ J/ }* I
10+ p) r$ |3 q5 s) u1 u d n
11
. _5 H. k4 _' ~9 w) N126 v/ g# p- L- [& ]
13. r( e: d6 k/ V A: I7 t6 C
14
, f9 W+ n8 n- s' @4 b- M( ?151 Q4 H7 l7 f% o& o
16
, T: d* V) Y( g) q( U1 W17
: Z& V8 q6 {+ y$ N+ a u6 t18
9 ]( l% l$ G* x1 ?( _19
+ m5 R. M+ S; V+ @3 f q+ U每条记录时间的间隔显然并不一致,请解决如下问题:
& H/ @# ^) T6 A! l, S' n找出间隔时间的前三个最大值所对应的三组时间戳。4 L ?9 G8 f% j
# 第一次做错了,不是找三组时间戳5 f5 G$ C& S8 ~9 h! `
idxmax3=pd.Series(df.index).diff(1).sort_values(ascending=False).index[:3]' A! R6 A1 Y$ w
df.reset_index().Data[idxmax3,idxmax3-1]
" V& j: L# N, G9 G1 Z7 W4 C% B/ S' q
25923 2016-12-08 11:10:42$ R' U5 g. ?$ {% Y5 g# d
24522 2016-12-01 00:00:02; u. L& I1 H3 y% U, _
7417 2016-10-01 00:00:19
- e$ {5 G; @0 P1 EName: Data, dtype: datetime64[ns]4 K; g- j. L. c9 ]4 B7 T
1$ \/ Z, Z* {& ?* e& j a" r
2' S3 \7 A; i% u$ |+ X& R
3* q* B# `3 t7 f8 Z; M
4+ M! |5 n7 D7 k3 ]( Q; ~/ J3 O
5& j! H$ L. C" ]7 H0 b( G: |0 {
6
1 o, F3 `5 N8 g" f7
3 O" x5 R0 ~5 x8 S) N* Y8
8 c5 z6 j2 N& s2 f7 ~idxmax3=pd.Series(df.index).diff(1).sort_values(ascending=False).index[:3]; |2 F" W1 \$ ^% {6 i9 J' ^
list(zip(df.reset_index().Data[idxmax3],df.reset_index().Data[idxmax3-1])); d. {" X) g; u9 g. z4 z# v
9 Z6 H# `0 ~. Z
[(Timestamp('2016-12-08 11:10:42'), Timestamp('2016-12-05 20:45:53')),# F9 E' U5 R! h* c/ |
(Timestamp('2016-12-01 00:00:02'), Timestamp('2016-11-29 19:05:02')),
1 {0 J4 N8 i9 q' e2 N1 ]# H) P! K( p( Y (Timestamp('2016-10-01 00:00:19'), Timestamp('2016-09-29 23:55:26'))]
2 l0 I! e6 A5 t. s4 p10 P# ~$ {" N* [* ?
2
4 ^+ h6 \$ n @3. U; E+ c" _% i% d
4
2 c, d O* v6 R$ J R% v5% i3 K6 I( s& M0 q( E
6
9 w. Y4 a" A& `* R& |& {参考答案:0 \. m5 l9 F% N6 Q# U# H
6 _0 X% ]( M) F# Ss = df.index.to_series().reset_index(drop=True).diff().dt.total_seconds()
; [3 |; Z2 B+ I0 K Nmax_3 = s.nlargest(3).index
$ D+ c! z4 z6 z' Z( Kdf.index[max_3.union(max_3-1)]
) s5 E* y: s1 b/ b3 d7 _
5 k9 Z2 c3 V* t. ~. AOut[215]: ( z$ D! v) E! V" l+ N
DatetimeIndex(['2016-09-29 23:55:26', '2016-10-01 00:00:19',
" y9 R: i( k& c* h '2016-11-29 19:05:02', '2016-12-01 00:00:02',7 C# j* w) W3 V- X2 b/ r
'2016-12-05 20:45:53', '2016-12-08 11:10:42'], S5 l- [8 V7 J" e: ?+ m/ {% T
dtype='datetime64[ns]', name='Datetime', freq=None)
( S* a& x1 t" d- ]* J9 p, w1
+ z' A# A2 i. W2 B6 J& X- ~, T2
2 L2 y$ O% e$ {! R3
: m. W! n# j# L' G7 i( |3 M+ z! v! W46 z g" h# P' Q I
5
# ^" S7 F5 x! Z, P8 x# z) B6
# |* H5 U! h! V6 O$ q77 s- \8 ]2 t2 T# B
8* M0 J3 R0 d% s6 u
9
& O: ^/ L0 |. W; C/ j+ Y是否存在一个大致的范围,使得绝大多数的间隔时间都落在这个区间中?如果存在,请对此范围内的样本间隔秒数画出柱状图,设置bins=50。
* @ r9 u' m6 [7 d8 z6 L9 J: R; j# 将df的indexydiff做差,转为秒数后排序。再求几个分位数确定取值区间6 B7 y' @% x$ Q9 x% p- a- R; `( J9 j, }
s=pd.Series(df.index).diff(1).dt.total_seconds().sort_values(ascending=False)
; w& |* m: F4 ?s.quantile(0.9),s.quantile(0.95),s.quantile(0.99),s.quantile(0.01),s.quantile(0.03),s.quantile(0.05)/ Q, I6 p/ \' V2 `
g/ c0 [' n, U1 Z% f2 N9 u/ L3 T(304.0, 309.0, 337.15999999999985, 285.0, 290.0, 292.0)
I& j, r$ h* u! @6 X1 k3 ]4 ^1* a& d( O0 m0 b/ I* r" U( O
2
/ Z& J' f$ u% v38 ` a# X! ^4 w- H
4
0 m8 }- r# R5 t: F7 T5) R6 `/ \; T7 v7 H% b v/ R0 G
%pylab inline
" O6 G" B' J: c2 t_ = plt.hist(ss[(s.values<337)&(s.values>285)],bins=50)
6 A+ F% h/ |0 H$ s( E9 \% L6 Nplt.xlabel(' Timedelta'), ^" a( S& [. T/ Y; Y$ D9 I
plt.title(" Timedelta of solar")
6 H! @9 i* M3 ^; a1 P( k1' b' A, \3 t, H9 ^. V% P
22 ~2 |( Y3 W; D7 u+ r
3$ l7 C+ p( C1 `3 V! P
4
- N: I/ q' S( d- w$ ?3 A; ?9 v5 O5 R! T0 O/ {3 S
- p7 }1 C" d/ k2 P+ t2 T求如下指标对应的Series:
) K: d7 V! Y( C6 P+ E( i4 j温度与辐射量的6小时滑动相关系数2 t% | ^7 D, ~% k2 k( a
以三点、九点、十五点、二十一点为分割,该观测所在时间区间的温度均值序列/ h X, r- W" A P4 d6 X
每个观测6小时前的辐射量(一般而言不会恰好取到,此时取最近时间戳对应的辐射量)
8 d0 o$ ]: H. z: ` G9 A+ `df.Radiation.rolling('6H').corr(df.Temperature).tail()
9 M" [. }& D) m3 [ ^+ H _0 @/ p6 \; v# v! S# h* a1 G
Data3 X6 L- N+ b0 J
2016-12-31 23:35:02 0.416187
, t9 M6 q% p9 p7 R7 G2016-12-31 23:40:01 0.416565: _+ @0 Z3 r a U7 f
2016-12-31 23:45:04 0.328574: p6 y3 |7 {. |1 G6 x. T: q
2016-12-31 23:50:03 0.261883/ Z# w0 w, f/ q$ e' I& N' P
2016-12-31 23:55:01 0.262406* `& o1 K% \; m
dtype: float64
/ u) q2 K7 `& O7 W* a+ D1. l/ C$ L" @' q1 X' R# @! c6 s
2 S3 [$ ?! R h; R' ~
3
: A. W7 L! W9 `8 @4
7 U! M: u6 H8 C0 J2 p- f7 J' m1 N5; P* R; y6 R& W9 O/ M
6; ^7 @1 u- {4 I) Q. y y1 A
7
$ P+ b D5 E: r+ p. p8
% c2 w/ x. h# {9 Q- r, F5 d% E9
, \7 O# x/ F$ W& D# Jdf['Temperature'].resample('6H',offset='3H').mean().head()
4 A5 N2 |; O. K5 X8 {! r4 C' J! @! r2 R. Q9 t6 d2 S# u) ]1 k
Data, a# m! e5 Q7 d, {) g% V W
2016-08-31 21:00:00 51.218750: _0 [( e6 D3 g) u+ _( S; i
2016-09-01 03:00:00 50.0333333 Y. |8 {9 O3 E' Y# Z: q' v, ~
2016-09-01 09:00:00 59.379310
% [7 T" H0 ]9 X2016-09-01 15:00:00 57.984375: Y) s) y* q2 r3 [7 }& r3 o( J* y
2016-09-01 21:00:00 51.393939' l: Y# I) M* _' E
Freq: 6H, Name: Temperature, dtype: float64
; H4 t0 [" c5 p% a- ^* P1 w3 L$ D6 r/ T- K6 `0 @4 v
2
* ^$ g, @4 B- Y7 T3
9 Q" j H, h. p) G0 v/ H4
$ ?6 E& ~3 t8 K' E- ?5
$ B0 t( [0 ]. ]; u9 }) |$ y* B/ P6
! \* ?7 W; t0 d e7
) i! i6 ]2 N; ?! o! h8
8 V$ T0 x$ N# n' B92 U* a' j1 N4 ~3 a1 U7 h3 w
最后一题参考答案:, C$ C) B0 Q% q+ b6 ^
1 n9 Z! {: Y( K# 非常慢
* y+ j! X1 k# M( d& mmy_dt = df.index.shift(freq='-6H')# p/ P% M! w$ M5 Q5 ?6 u* F/ |
int_loc = [df.index.get_indexer([i], method='nearest') for i in my_dt]! c6 Q$ M# J m' T R. ?0 C
int_loc = np.array(int_loc).reshape(-1), x. U! _" @* e- z# t8 A
res = df.Radiation.iloc[int_loc]* \$ W( Q: k5 O1 q- C. N3 h3 W
res.index = df.index
# S5 t) l. r& N) `/ ?. ires.tail(3)2 R0 f' r% q0 w) b
1
* l) D: v+ t0 p1 o/ N9 E' l2. y# {1 w8 [& |
36 Y6 o( t9 W" [/ o4 m
4
2 M5 J8 Q \ ^% l* {8 s* ^5
- l0 f) e4 a. [% G6
4 |! _6 p! a: k- M7
; g" v S# _+ o/ R5 b; ~9 v1 G; m# 纸质版上介绍了merge_asof,性能差距可以达到3-4个数量级
+ L- n9 u+ d% C' Itarget = pd.DataFrame(; Y! E9 _; `: B8 y& s7 r
{
5 t- ^4 L$ X. J: b2 C "Time": df.index.shift(freq='-6H'),
) M7 x. l# Q, ?' U) u& x8 z "Datetime": df.index,
8 m: c1 v/ a4 o& h0 e }
) U0 T! }6 h* k. Y)
. J. \: \& p8 p( F& \8 Q
- u, g# o+ D0 {3 F5 m' mres = pd.merge_asof(& x7 x9 S* K4 r
target,- a* E; d& R( ~6 a2 P1 [; @1 [% g
df.reset_index().rename(columns={"Datetime": "Time"}),5 `" t& A# }( L- t% _' H1 i$ T& w
left_on="Time",) E" i" l: a& @: Q! T5 j! J( h
right_on="Time",
" b0 l! Y0 f( F& {+ O: e direction="nearest"
3 y5 D& F1 q) O& t. c).set_index("Datetime").Radiation
* P; I3 ~2 I5 {, b- H+ V* O+ H4 c' G
res.tail(3)
) Y$ g+ e* O L8 ROut[224]: . r6 ?% m5 r' U" ~
Datetime
" {4 V: l7 E5 N! ~5 j/ P2016-12-31 23:45:04 9.33
! X5 B( A9 F. K; m3 V3 G( D2016-12-31 23:50:03 8.49
1 \8 @/ ~8 G0 I6 x/ b$ u; H2016-12-31 23:55:01 5.84& q5 l" M' k6 V
Name: Radiation, dtype: float64
4 ]7 h! T; u! p: \6 g3 V0 }% x& X" B6 [
1
9 Z* r& }- T" A; U1 j) e29 x2 q9 A0 X* d
3
5 V6 N% q2 e7 ^. s5 o% b4
5 d( r$ E0 Y6 Z; f5 I; @, @- I( H t' X
6
. {( ~4 V& G; M7
" b- R, P6 x! L1 }! [8; v/ f- _0 N, u8 v6 r
9) x1 q# q7 S% L4 u4 s
10
; h+ O1 q' J# I" a+ Y4 ?; G: Y/ J11
) y) {! B2 Y& [) r; o& _120 s; T0 h8 i, c3 T: O: K& W& q
13
& _, J7 h/ m3 A& j+ C14+ r2 ~6 ^! S( g5 V7 v
15
: U- p6 J3 ~& B2 M16$ d5 }: u/ S0 i" R, D
17
4 \( y1 Y5 I8 o3 r; B% T18
! t0 v+ Z* C) H0 c. a+ U19
7 n- W. N3 ?7 w4 m9 N20
. N7 |7 b5 l& q21
2 U3 l+ z# G. ^2 w' K22; ^8 f/ O1 C4 h2 {: m
23( o+ T' Y6 [9 Z" i7 Z
Ex2:水果销量数据集
* E# U" o- L. s# k* \! q7 P现有一份2019年每日水果销量记录表:
. s' I$ h; h4 z0 P
' E& j& l9 t0 N# Ldf = pd.read_csv('../data/fruit.csv')
$ u! g2 Y- }$ Kdf.head(3)7 [; C2 d0 b8 k2 d
. U i2 a F5 Q. EOut[131]: # M% a4 s6 j: H: `6 q! f' b* o9 B
Date Fruit Sale# k3 n5 U2 M5 z: k
0 2019-04-18 Peach 15- B( j) J: `/ `1 T, |6 B1 K
1 2019-12-29 Peach 15" Z4 M2 u) ?% S' P$ E
2 2019-06-05 Peach 19$ M, r3 T1 C6 E
1
( b% ~$ \& T |5 I7 W& y9 {24 E7 {5 h$ d- t, ?' `% s
3
* O+ d/ q% L, r; Y4
0 n N9 Z5 O7 p& ]0 D5 t5
! Y) y' I! S, l3 ?65 O; i4 @) u& G( ^. n
78 `# R5 _' c1 |/ }2 a
8
3 N5 @! p% o( H7 E- K% J5 N统计如下指标:. O$ d$ w$ b: s% y3 o' _. d1 f
每月上半月(15号及之前)与下半月葡萄销量的比值
1 y9 a8 D: U& z& d/ [每月最后一天的生梨销量总和* W6 p7 n. f$ `. d5 b. ~; D
每月最后一天工作日的生梨销量总和
0 W5 K+ S, ?" m" P1 v* o. \每月最后五天的苹果销量均值
- b: @' T6 k8 ]+ G5 i& Q; V# w按月计算周一至周日各品种水果的平均记录条数,行索引外层为水果名称,内层为月份,列索引为星期。
; y1 D4 q' |( z! A* E0 l按天计算向前10个工作日窗口的苹果销量均值序列,非工作日的值用上一个工作日的结果填充。
1 t: P# \* a* [4 ~; Pimport numpy as np. M& F; x6 Q, U0 k8 u( O& K
import pandas as pd/ |) G9 r% M5 P6 w/ o! _
1
% b6 A6 ~9 n: G2% {7 ~( G3 P+ y
统计如下指标:
+ X! V5 t# q2 @! F& T每月上半月(15号及之前)与下半月葡萄销量的比值* L5 C/ j8 B8 o- r
每月最后一天的生梨销量总和' j" M5 Q9 o& A( Q" X
每月最后一天工作日的生梨销量总和
8 m+ S4 ^: J+ e; h [" e5 ^每月最后五天的苹果销量均值
" F8 \" v! `9 F$ I# 每月上半月(15号及之前)与下半月葡萄销量的比值
* t+ d. M7 P2 F! E; |df.Date=pd.to_datetime(df.Date)
I2 ]1 X' `7 ]) x. I9 Vsale=df.query('Fruit == "Grape"').groupby([df.Date.dt.month,df.Date.dt.day<=15])['Sale'].sum()
" {1 S0 V" t/ r$ T9 rsale.columns=['Month','15Dayes','Sale'] # 为啥这么改没用啊
0 ^$ k9 G4 c# C5 H3 ]sale=pd.DataFrame(sale)
4 V) B/ |: y- V* @% \- @; d+ B3 Osale=sale.unstack(1).rename_axis(index={'Date':'Month'},1 E# q4 O8 W, O0 N9 a- L0 D3 u/ t& U( f
columns={'Date':'15Days'}).stack(1).reset_index() # unstack主要是两个索引都是Date无法直接重命名
8 _, y ]3 E4 Z: qsale.head() # 每个月上下半月的销量
" v, v' g2 a3 E' R8 w
! P( I$ `" b% X4 Q* l8 ^+ L8 u Month 15Days Sale# h/ ?1 x B% o9 k2 R
0 1 False 105031 [6 Z0 q2 a) S: n; t
1 1 True 123413 W( f" z4 t2 K; h1 C
2 2 False 10001
: e! L1 M2 W! t1 v3 2 True 10106
# l: C+ P3 a! ~+ f& S4 3 False 12814
4 h. K- z! C2 r! G( ^1 }* k( }4 J1 b
# 使用自定义聚合函数,分组后每组就上半月和下半月两个值,根据索引位置判断求比值时的分子分母顺序$ [ X, M4 K, b3 G& d9 Y
sale.groupby(sale['Month'])['Sale'].agg(
0 G7 H4 t: d2 ?3 ?' H3 Y lambda x: x.max()/x.min() if x.idxmax()>x.idxmin() else x.min()/x.max())) ~! W6 \; h, A6 E n
. x5 i4 `# W! a! @' \, c2 `
Month
# q* C, T, G+ o( E) ]# a& ~1 1.174998+ V4 G! U' u6 B9 _- r
2 1.010499
9 F$ [1 N3 d5 @; X/ `1 c0 l$ Z) @3 0.776338
4 h; Z$ P; x' Q& \4 1.0263458 ^9 A! ~# S d$ f, }7 k
5 0.900534. g0 L- I% U. J" o$ m# L ^5 ^
6 0.980136
- y0 P" W1 @6 K/ {, g$ f7 O7 1.350960
; {, S+ \; ?' r7 F' q1 Y$ A9 W7 u" d3 v8 1.091584: Q' P6 K5 a) x: @. e
9 1.116508, E" J9 f$ C/ J9 o- {$ l; x
10 1.0207848 v) Z5 A+ Y7 Z/ U+ a$ L e
11 1.2759111 a; w1 c( s! o; ]: f- L* u
12 0.9896622 x- i4 A+ m9 c6 O# |
Name: Sale, dtype: float64
( b" M4 L, d. U
' F" O" _0 p$ D1
: \: ^4 q- t2 J% y | ^+ L9 |" C2% U) q2 q# [1 M' }: [3 H$ K
34 J4 v* O% a- u p5 ?$ ^# [5 d
44 w9 E* W' F, \& a
5$ D; N' l i* J y& |: u
6; h- _6 s) L- o! Q$ C: T! ]
74 f O, |, q6 p, h
8/ U# b+ L# F2 S- B& g# {( }4 b/ Y2 Z
95 _" T/ `5 v1 q# ^) t& W! }
10% N W, z' O5 h* ^& G6 T3 F: P
11" C0 ^) l5 b* e9 [% v O2 c
12
2 h5 Z" _& p! f9 V- F& u5 l13
9 N0 @9 @$ w7 t: q7 B, c14
# i9 J/ g0 i- g7 y, ~15+ s: V, N; v3 W% x
16
s |. ?- U* F1 r17
4 G+ ~ r+ p7 ^5 n5 N* e$ O8 j18
/ A. v8 a4 X9 U# m' t c193 X' a" {* I" s+ d8 o- m9 ]
20) d5 ?' A- A$ b" h8 V# b
21
& O! d/ @8 o5 ^. s: q22
, {# s4 y+ v& n4 R% c) c23
0 I- O H" n9 {24
8 N; X& b7 l5 r: H. z) ]+ h25
" J( V4 E P# N$ ?26
# Y1 G) W# W8 Q. o$ Q) l27
" m; j. P: W/ P( m5 @ a5 {28) l7 R! n: A4 b$ q
299 V) p4 q0 ^, E6 M
30
# a5 O7 i: R+ ?: B, T( m) t- J8 C z31! I: \* ~8 @# D. D
32
0 D. s; B8 L5 u8 ~, @2 Z; H# }& s33
/ ]+ H+ W7 [' x348 l+ I8 N6 }# r8 q9 k j3 |
# 每月最后一天的生梨销量总和+ m! s: ~' ^% S3 b1 e
df[df.Date.dt.is_month_end].loc[df.Fruit=='Pear'].groupby('Date')['Sale'].sum()& U$ I7 _" A* h* E# P
, j& I. O" j$ G4 h
Date
) K2 c% s- W- w# e0 [2019-01-31 847
, H F* f: c) k2019-02-28 774- f# ~# d, _- V% X
2019-03-31 761" v$ O& f1 W- X3 C9 u% E. ]# Q) M
2019-04-30 648
; A* j Z' F9 l h7 _2019-05-31 616
5 w& ]& t' K, J% R6 m4 y8 W1
) b: l* {# J3 ?7 k22 P* L1 {# r, v; K
3
7 j* I3 ~5 F. J! p+ M4, ^/ |0 I, J8 O! L* q
5
6 }) A4 ~# r/ i+ x0 j( E5 |6 U6
' {: ]# O( T, W+ |/ I0 l, ]79 s% p7 Y: F7 c. @4 L
8
+ R3 O6 W1 y, E3 y: f9
+ N+ n6 T# m# \4 _# 每月最后一天工作日的生梨销量总和
5 D& K0 C' R" i" Q1 z2 u# Uls=df.Date+pd.offsets.BMonthEnd()9 A$ n& P, G8 ]
my_filter=pd.to_datetime(ls.unique())- k# }% [7 @8 u" {0 V
df[df.Date.isin(my_filter)].loc[df.Fruit=='Pear'].groupby('Date')['Sale'].sum()
* a3 V2 m4 K8 K3 r& t+ u4 y
1 ^$ U* G5 V) x) i: ^! [Date' `/ R5 I, J3 K
2019-01-31 847
) o+ b3 b3 Y+ B% p0 D, j2019-02-28 774$ P1 U5 t- K" D$ A& n/ @
2019-03-29 510
0 E& `4 ?* L9 ?9 t# A" k" _2 `; @7 N# z2019-04-30 648
: v! ?$ U7 \* C" O' K/ U. w# W2019-05-31 616+ p# @ N( k. k) {. s# R
12 j6 ~4 P [" s) S8 K. Z
2
6 v1 F$ ~( l3 }3 H- F3$ \* s# ]) y( z4 p- ~
4+ l' }8 Q$ M$ z! A5 \
5
( X( O. i. H- n- V6# F4 F* M' M6 J6 r) C1 ^# V/ X1 [+ x
75 Q4 E# K( u" p. V" {! H# @% G
88 l& D( K. _! k+ v5 t# n7 Y$ Y
9
7 I3 u) `$ `( E7 o10% M: m& y7 W; v( m
11
9 H; ^2 A8 Z! U |# 每月最后五天的苹果销量均值
, r% ?- }5 ?5 [0 K' W. Hstart, end = '2019-01-01', '2019-12-31'
& J1 A6 u l# Iend = pd.date_range(start, end, freq='M')
# C- ]$ v0 t# p& V. I" iend=end.repeat(5) # 每月最后一天的日期列表,重复5次方便做差5 Q* |/ i$ q' H6 ?
) ~+ ]8 n# p( o- {. x) C4 Ptd= pd.Series(pd.timedelta_range(start='0 days', periods=5),)
5 f$ o( k4 C: w9 Ctd=pd.concat([td]*12) # 日期偏置,最后一天减去0-4天
2 l* B7 q; H7 o' Y7 p+ pend5=(end-td).reset_index(drop=True) # 每个月最后5天的列表5 ~& ~3 U, k+ x. z" D
; }; B4 S/ s6 f% D! s8 |
apple5=df[df.Date.isin(end5)].query("Fruit == 'Apple'") # 每月最后五天苹果销量$ e2 O# R' L* H; w T
apple5.groupby(apple5.Date.dt.month)['Sale'].mean().head(): `7 Y$ V# n; |8 e4 `
0 ~7 f( g" P# L, `
Date
$ {9 a3 a* M! ]& }1 65.313725+ | m- y6 K/ k8 S
2 54.061538
+ I: V3 r2 I! G5 `3 59.325581
3 O* w* j" Z; o! I( W4 o4 65.795455
9 v$ l1 x. h8 G& N7 q5 57.465116; H$ C3 h6 s) l* Q8 B
- k9 J# K# [/ z) k& J6 u8 h1
" T" }! c% J- M3 I1 }. C( l2/ w, _. \$ N* U* P _& @
3
: N9 Q7 H, b$ ~1 B* u4! N; ?- \) R. e7 M2 }' R
5
! X# j* M( S9 ~* M) P4 O6
$ y7 }+ ?$ y- P7
5 }) w+ c* w8 g8) }$ u. V" I! h2 H" H3 C* q/ Z. @
9
6 A, a& J/ r$ n, g109 ~) _8 Q1 V u3 F* q' a
11' w* B. n9 ~4 E7 ?) A1 [- I
12) r) K+ b* U0 k! t1 c
13
, k% p9 G) ?3 A2 D+ O14+ g- r8 b3 h- r7 ~) @1 ^9 n
15
& q1 L- x# v; k3 A, c1 U16
8 u6 y4 t: g/ Q1 [9 z; j; s6 V17; X( U! C6 \ W1 X- \& `
18
[7 Z1 [# O; S% C# 参考答案:5 }# g Z' B! ^9 U3 e# r7 ~8 o
target_dt = df.drop_duplicates().groupby(df.Date.drop_duplicates(
. |; [0 D* `7 _0 i* [5 B" v ).dt.month)['Date'].nlargest(5).reset_index(drop=True)
( V6 ~7 ~& c9 K. C
# J( D) T& B7 s4 W& G9 L4 |9 Gres = df.set_index('Date').loc[target_dt].reset_index(# {" X Y2 v" P- R _
).query("Fruit == 'Apple'")9 W5 w. I0 g& P
5 g( j8 g6 Z0 u/ Q# lres = res.groupby(res.Date.dt.month)['Sale'].mean(& s3 [, D8 L) {7 j7 F0 J
).rename_axis('Month'): w( O: j9 M4 u
4 K$ g! o0 d0 w. H( h1 Y( Y. a. G: T2 e. B! C7 P7 \
res.head()
, [; p7 s) m$ {- _9 SOut[236]:
T) J1 D8 V6 o/ V: QMonth v2 u% F8 G6 R$ ?! |' e) a2 j
1 65.3137251 e: o/ b7 H$ [+ \3 \
2 54.061538
6 h) e1 p! a7 X3 59.325581 C) T: E# u: ~) z. y5 N
4 65.795455- i9 G6 d9 I3 q
5 57.465116. k" \+ y3 P4 p: W
Name: Sale, dtype: float649 h6 y5 f0 D% N1 e- W5 [2 q; @1 }' o
+ T: q1 H: Z9 W
1 z# N! Y; c5 E( N( T( _ V
2
) X5 q3 x0 O* w30 R$ g2 Y$ C% U% s' }9 v
4; ~9 z# |8 _6 u. p* ?
5% ^# {- i8 p: ?: j0 u. j
6
# Y7 g3 r+ c% e, a) [9 ?. O" T- ~7
2 O; c6 |" G9 [* s8 j I8
0 f9 l7 u0 \' C8 U, ?9
6 q5 L; q5 l* _ `1 |+ s3 o10
1 f0 m# Z2 o# x' d11
+ D' X) s7 {+ ~5 f9 R' J12; ^" ]/ y& _: J- p% V+ D' L
13
; ~' d* M* V( G! H& k' W14# M" q# V" S4 }. i7 J' E
15
$ `6 B, ^# A' P165 D1 r2 A6 l# J, o/ u" v s" i7 o% Z
17
+ E8 \# B) U5 `, J3 ^- T4 \' n5 h5 e18) Y q4 P% H1 A1 B
19
% z* w1 g8 \! E2 P20
) e1 W& m+ H1 ~, G按月计算周一至周日各品种水果的平均记录条数,行索引外层为水果名称,内层为月份,列索引为星期。
; Q( |. i6 z$ Z9 L/ ]/ I: y' M1 oresult=pd.DataFrame(df.groupby([df.Date.dt.month,df.Date.
2 m8 Y9 `& p% D' ]+ E6 P dt.dayofweek,df.Fruit])['Sale'].count()) # 分组统计
" [8 f' i- q* v1 _
6 W' y+ v+ P3 \: ?: \result=result.unstack(1).rename_axis(index={'Date':'Month'},
: m# {& ^ I1 q9 D5 E/ s( n columns={'Date':'Week'}) # 两个index名字都是Date,只能转一个到列,分开来改名字., @2 L6 z, w6 X0 V
result=result.swaplevel(0,1,axis=0).droplevel(0,axis=1)8 b3 N0 f7 W5 n8 j$ q' @, t( T
result.head() # 索引名有空再改吧9 I7 T7 t. Q6 D8 h+ O. S& Z$ f0 g
' _5 ~2 i/ w$ M: b
Week 0 1 2 3 4 5 6
7 ?7 k4 }; \; _8 z: p ~Fruit Month
, k7 E) l1 u9 w/ s8 KApple 1 46 50 50 45 32 42 23
/ i4 c3 c; m* o; Y. A3 {Banana 1 27 29 24 42 36 24 35
1 t% {! W# I! u6 k4 bGrape 1 42 75 53 63 36 57 46/ ` t- R; z5 [/ w: z# n. C @
Peach 1 67 78 73 88 59 49 72
1 y$ q; x, t; o7 OPear 1 39 69 51 54 48 36 40
7 S! |2 k' a) a$ G# W14 O3 A) G1 o2 s* S* ?* D0 x: ]4 p
24 H/ }: }5 M z! R }1 `% b
3- _9 v9 _7 Z9 Q) R( |
4
9 g& g' h$ E' V$ f% @) \2 K, Q59 @' k9 b# k9 u5 |2 v
6
+ @9 a3 l4 j- v: f# V- ~73 p! h; `+ d, ?2 @. `8 `/ h: R
8
6 ^8 i- N/ O8 h' n4 p# A9) a/ ^) @. A2 }7 i1 V) b
10
8 a0 B( M) Y/ r1 O) A5 ?6 m11, l4 B8 T4 ?4 q5 }3 a" \
12
[# j. d6 K- V, Z% p" f3 I139 m4 d. w5 p( e. p
143 t' H# ]' K3 K3 j& A
15; o1 |- r2 e* H4 j, n
按天计算向前10个工作日窗口的苹果销量均值序列,非工作日的值用上一个工作日的结果填充。
+ ]# Q6 R& ~' n# ?: m* v t# 工作日苹果销量按日期排序
$ ^' R/ H e* w$ n+ nselect_bday=df[~df.Date.dt.dayofweek.isin([5,6])].query('Fruit=="Apple"').set_index('Date').sort_index(). H0 v/ F/ P! x3 y/ O- U& X
select_bday=select_bday.groupby(select_bday.index)['Sale'].sum() # 每天的销量汇总+ D5 I. _+ t# R5 x% i- V
select_bday.head()$ X. }+ V5 l7 ?- _1 t
$ M9 M' o- Y1 F- p$ c! RDate1 ~6 T1 g1 G$ ~4 G4 r
2019-01-01 189
1 l; h4 s* }, ^6 q% V# z2019-01-02 482+ H( z6 y; b! x, f7 |
2019-01-03 890; w% z" D0 V# [2 y4 u6 Z
2019-01-04 550! z9 }& k8 |7 O
2019-01-07 494
1 @% {. F1 \! f6 K4 y9 w
4 Y' a. _% K8 Q6 t6 a$ d# 此时已经是工作日,正常滑窗。结果重设索引,对周末进行向后填充。
. c) Z+ p+ y( c3 m4 X- k! Iselect_bday.rolling('10D').mean().reindex(df.Date.unique()).sort_index().ffill().head()" i7 _0 g7 D8 O, ?
- V8 o4 ?/ z, ` y$ Z9 u4 c
Date A! R. q( K* e; ]* p- N8 B
2019-01-01 189.000000
! \1 L! L U) Q! E; E2019-01-02 335.500000
$ z) Z0 P( a' v6 y0 e: [2019-01-03 520.333333
) P, h3 q" {9 [2019-01-04 527.750000
" \, q: ^6 }- R0 P9 V9 ~2019-01-05 527.750000
- u& X( [& e& U5 V2 i4 t! S: V3 E+ \( w9 B
————————————————7 _0 S7 e+ x0 y' t d' Z
版权声明:本文为CSDN博主「神洛华」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
/ H8 i, @5 A4 f N6 f* {! j. I原文链接:https://blog.csdn.net/qq_56591814/article/details/126633913" x4 r8 [4 l& X, T
; W- ]. U& y1 Y7 O
+ S G. j/ @5 ]5 f7 t) D |
zan
|