数学建模社区-数学中国

标题: datawhale8月组队学习《pandas数据处理与分析》(下)(文本、分类、时序数据) [打印本页]

作者: 杨利霞    时间: 2022-9-5 16:11
标题: datawhale8月组队学习《pandas数据处理与分析》(下)(文本、分类、时序数据)

" F! p3 I" m5 w0 N3 O- ]# \- s+ d% Z( E  P. {7 H

& C* m9 W) g* W+ B6 j文章目录
3 x: ?5 d: I) ?, c第八章 文本数据  j( B; ~. a5 _2 g7 M) C0 Z! [
8.1 str对象4 b; n) }8 P4 Z  Y4 l" T+ m
8.1.1 str对象的设计意图
9 g8 d4 N' X* O' T; p' g  v8.1.3 string类型& I) x( c# E% L8 \. Q5 v' D
8.2 正则表达式基础
0 i) r. T/ r3 T; w8.2.1 . 一般字符的匹配
1 n4 J+ B9 q$ \0 m2 n8.2.2 元字符基础+ C; p# B6 o1 ~6 C' M# L
8.2.3 简写字符集
2 T6 p8 A9 V3 D$ k8.3 文本处理的五类操作& b# @3 l' H: @/ V9 q! W
8.3.1 `str.split `拆分: ~" l  ^- A6 M9 h: M
8.3.2 `str.join` 或 `str.cat `合并
8 r( i) `$ Z0 n7 f- a8.3.3 匹配
$ g" j9 f4 U2 L5 `" F8.3.5 提取7 k- Y3 O6 Q. f) {4 Y8 n, A
8.4、常用字符串函数
4 l( s! v- a% V. u5 F8.4.1 字母型函数% U+ ^6 r; G' H
8.4.2 数值型函数
/ j) M. ]) `9 b8.4.3 统计型函数5 b1 d7 s6 g, O, z% U" E+ V
8.4.4 格式型函数$ S$ E/ B. B4 c& M& m
8.5 练习' X2 Y5 F9 D4 H
Ex1:房屋信息数据集
  K) r7 b2 M% vEx2:《权力的游戏》剧本数据集4 q$ a; E3 }+ u' |8 U& A' h
第九章 分类数据
5 U/ V) z. c) g0 K2 o9.1 cat对象) U/ p) {; H5 J2 W2 t+ J$ N
9.1.1 cat对象的属性
1 i3 O; {' u( m# {' F9.1.2 类别的增加、删除和修改
% \& ^/ b# @0 X9.2 有序分类" {# i! E* ?  }. T
9.2.1 序的建立1 i. Z% j9 }8 f1 l
9.2.2 排序和比较3 _! }, s" E$ Q" i
9.3 区间类别
) [4 w6 \2 d- w2 P9.3.1 利用cut和qcut进行区间构造3 B" e& W# c+ w* m. M! ~
9.3.2 一般区间的构造
4 W0 o0 \1 r, G! K/ s" B  \3 L9.3.3 区间的属性与方法8 Y* K8 T, J$ n1 J
9.4 练习# t4 t( H1 _. \; z: Z
Ex1: 统计未出现的类别
/ h: z1 t- c4 q6 dEx2: 钻石数据集# M7 M+ Y' f1 w$ }6 U7 @* |. _
第十章 时序数据
4 W. d, y5 r  ]10.1 时序中的基本对象
& B, G3 e$ F" s0 h/ N# J  H- u" J10.2 时间戳, y" q; P+ Z) ~- U( A. B- q7 M" V
10.2.1 Timestamp的构造与属性. N6 Y# Z' z/ _3 ]  X: u5 Q
10.2.2 Datetime序列的生成
% L5 t+ o/ o6 f, ?10.2.3 dt对象
( l# {) i# w& `& a5 r1 q5 u10.2.4 时间戳的切片与索引/ j) b$ v, x; R5 C" }
10.3 时间差
: @- A6 s, P* h% u) V' y. E10.3.1 Timedelta的生成2 W; N( Z% i- S, W9 }$ r: X( S8 w
10.2.2 Timedelta的运算; L, n' g3 X" ~1 z( \& k
10.4 日期偏置* ~! `& x3 w& t: {+ S) d! y) u
10.4.1 Offset对象4 m- j9 A# `8 q, i
10.4.2 偏置字符串
% @  C/ ~6 n% `: U  Q10.5、时序中的滑窗与分组& n7 G2 A. V1 R  j# q& ^. G* ]
10.5.1 滑动窗口, k* {( r: L, g8 k) ~$ W
10.5.2 重采样
; i9 [( j% l3 e- U6 T" H" M( d10.6 练习( t& k: a0 c$ i' R' [
Ex1:太阳辐射数据集
* B4 B9 c6 n2 SEx2:水果销量数据集
2 y+ ^2 h1 ?* p) E; G! a( m! I  课程资料《pandas数据处理与分析》、github地址、讲解视频、习题参考答案 、pandas官网
3 j5 F' T+ ?6 d) U$ j; y# i传送门:
) X2 Z: u0 p. n% y
4 g( E* m" @' W4 H" i6 C8 X. Adatawhale8月组队学习《pandas数据处理与分析》(上)(基础、索引、分组)& X% u9 P; T7 a' t- f
datawhale8月组队学习《pandas数据处理与分析》(中)(变形、连接、缺失数据)
& b, d0 x# u% G第八章 文本数据
, Y" M4 q; L- v5 ]- E8.1 str对象
1 R9 h# h5 F6 j8.1.1 str对象的设计意图
8 P! d7 D1 B8 V5 E% d  }- ?3 Q) h  str 对象是定义在 Index 或 Series上的属性,专门用于处理每个元素的文本内容,其内部定义了大量方法,因此对一个序列进行文本处理,首先需要获取其 str 对象。在Python标准库中也有 str 模块,为了使用上的便利,在 pandas 的50个 str 对象方法中,有31个是和标准库中的 str 模块方法同名且功能一致,例如字母转为大写的操作:
4 `  `1 f6 w7 R% }$ R
* Q8 n( P( f3 h7 X; Yvar = 'abcd'
, N; Y+ T3 O& r* Y3 y1 k1 ?str.upper(var) # Python内置str模块- U  o# Z- u) e  H) ~
Out[4]: 'ABCD'; P# U- I2 D  W5 ~2 n

3 }7 w. Q1 W4 \$ M, es = pd.Series(['abcd', 'efg', 'hi']); x7 R1 {+ \  K* o( g: u0 |

( `2 ]1 P: G) t6 D7 ms.str
* y/ t, p: Z* S) tOut[6]: <pandas.core.strings.accessor.StringMethods at 0x2b796892d60>! b7 A; }7 H  c9 t3 N
) }. [( |+ \/ E. B
s.str.upper() # pandas中str对象上的upper方法0 O, g2 _0 k- D1 L" u: n
Out[7]:
' o  f! c" u7 E9 v  l5 i4 K0    ABCD
$ J; a0 H- A# M% V5 F/ o0 G0 y: W1     EFG  W# y& ~4 D/ U& v/ F% c
2      HI3 o- `( J% [, u; ]
dtype: object
( J" w% q+ E; F1, @' E( r  c5 e) e+ c8 j% U
2
/ F7 K, L( A* F  g1 N$ W( F3 N- Y( W3
1 ~% A  _: X8 }# c! j6 \4/ C" u- X2 E$ |: `
5
1 w2 q" G$ w: @6 n6: O6 w8 U. }- d$ ?' H' O
7! q# R$ h) \4 L8 O) i  J) x
8
3 L! m7 z' l8 j6 m9
" R' R$ S+ ~9 t, v# f4 F! S102 T; ]$ g+ I4 J, s
11- X) G2 f: V1 v" a
12
9 [0 j3 Y- ^1 q# R# f5 E13/ y# ]$ s' Z7 ?! b) f) @: ?- y
14
* c* v$ [& x0 O# U3 R: D157 R( T* O5 `, [& e0 F8 z& \
8.1.2 []索引器5 H9 e8 D! _, t
  对于 str 对象而言,可理解为其对字符串进行了序列化的操作,例如在一般的字符串中,通过 [] 可以取出某个位置的元素,同时也能通过切片得到子串。# g1 s7 e# o% x9 `+ i* Q  z5 @
  pandas中过对 str 对象使用 [] 索引器,可以完成完全一致的功能,并且如果超出范围则返回缺失值:8 }% c2 D6 Y' H! G) {
2 P" e) ^: v% O( \$ s" m
s.str[0]
( R# I& k1 G/ y8 ?2 qOut[10]:
, \2 T0 M/ E% W0 P  }; v7 y0    a
( u9 {8 o2 H: {" z3 V1    e
% u/ x, g! A& {" ^2    h
0 D+ V! E! m: Y8 b: _& Y6 Z2 qdtype: object
. ~' m: a+ s9 y$ ?. t7 |3 r* o0 r0 }' K
9 R) I6 f; t, Z+ `% ks.str[-1: 0: -2]
$ R. {2 i; y$ ^1 _' m/ h& xOut[11]:
- l; ^5 [1 q) R: n7 w: u4 r0    db
8 B6 z5 Q4 G  S4 A  T- _2 {, v1     g; m, o1 k! m6 q8 V( O0 ?
2     i# F; ?' X. \: H+ W0 s: z
dtype: object
: e3 ^: {" l/ j
7 I$ U; ?5 t  u5 [s.str[2]
! v: y3 y8 e' ?* [& MOut[12]: $ i, S% _% d3 Q% A% o# ~% q1 S; N
0      c5 R/ K: c' F$ z: f- s' o
1      g
# r7 s+ a1 e' e$ g4 ]/ A2    NaN
% V. t( _& }8 W% ]dtype: object
& G7 E- A/ b/ B4 r. O/ }8 m- a3 R, `/ W' y% x
16 d8 H; }  B- \5 [# t) [# R
22 g& _7 ?8 m- j
31 l& ?/ a" F* A* E6 k
4  x' o' c8 x( U% E. r4 L
51 y1 o$ y8 X* h% s' N- X
6
9 ~# x' g0 y% R7 l79 o1 B% x, J3 V& N# Y" q
8
. f: t; m+ v4 \( }1 _9
. C9 M% U0 Y( R$ Y0 w4 x# J10
* A! x' u) P' p, H2 p118 s( Z) Q8 }' \) ~
12
4 ^5 Z' f. O' C13
/ M8 V& W/ v+ R5 I149 Q! Z( H4 V( o! @! Q! H& \0 v
15
6 S1 H7 L3 l# u3 X169 g5 a- b$ Y; C0 z* C7 [
17  J, a4 u, j% i# A7 W
18, e7 W; Y" E9 A% Q* T% X
19
2 g5 ?1 P" W1 @- R) w6 V5 V20
$ w# C  M) Z0 Kimport numpy as np
2 D7 C* Q* W: Y- Q6 c" limport pandas as pd
$ J0 \+ @, |" `9 F+ L$ [: X3 r  Y6 B0 e  }
s = pd.Series(['abcd', 'efg', 'hi'])
$ {* a- j+ s3 a& M7 bs.str[0]1 z! m' ^% A- \9 J
13 V3 ]& n  q+ u- z
2
, G0 E0 J: W! e# G' O! ^) j39 o$ e" A! ~# M/ H
4* Q4 ^, }. f% ?1 ~2 r) J
5& c2 f' Z6 p0 B% n0 [
0    a
8 |% g6 ]4 \& \0 P0 u# J5 ~. d( z1    e% @7 F+ X' I" E1 [$ d3 L% Y
2    h. c$ I( U; t0 d1 m1 O
dtype: object
9 g% q3 t2 I  p( S( f1
- y  \! N' M" I) g+ j21 R% t* O' a6 Z9 G
3
% b: D+ C* o/ U. u4
; \3 a/ [! `9 q' [$ x8 \8.1.3 string类型
2 l3 [+ k( s; h' y  在上一章提到,从 pandas 的 1.0.0 版本开始,引入了 string 类型,其引入的动机在于:原来所有的字符串类型都会以 object 类型的 Series 进行存储,但 object 类型只应当存储混合类型,例如同时存储浮点、字符串、字典、列表、自定义类型等,因此字符串有必要同数值型或 category 一样,具有自己的数据存储类型,从而引入了 string 类型。6 ~4 ^$ q: M/ e& N, x
  总体上说,绝大多数对于 object 和 string 类型的序列使用 str 对象方法产生的结果是一致,但是在下面提到的两点上有较大差异:: {/ m* Y' Y4 C& G% N- z8 t' W* L
5 k, a6 ~5 b* r. C7 H
二者对于某些对象的 str 序列化方法不同。
; T) c* j  W! R6 s5 L' q0 F可迭代(Iterable)对象包括但不限于字符串、字典、列表。对于一个可迭代对象, string 类型和 object 类型对它们的序列化方式不同,序列化后str对象返回结果也可能不同。例如:
1 T0 a) L7 a8 g, R$ G+ v& K# b9 ts = pd.Series([{1: 'temp_1', 2: 'temp_2'}, ['a', 'b'], 0.5, 'my_string'])
/ P" h4 Y: [- Zs5 e/ ]. o" }3 ^  o
1* u0 i; p' N/ L2 z# n
2/ {5 ?# O7 k. _
0    {1: 'temp_1', 2: 'temp_2'}5 P( l1 u6 ~! Y$ @% m" q: Q
1                        [a, b]
: W; c6 n4 X, F& g! S* D7 ^2                           0.5; k1 f) V$ H* N3 a3 h! p  v
3                     my_string% o# m8 B+ n/ O4 T5 L' W" Y
dtype: object
9 U% Q: B. M& @1 i# m5 M1
. b$ p, r8 V+ @4 `: u7 ]2" ]( ^5 ^9 `: t5 o1 m
31 ~4 ?6 A. Y$ v4 t- S
4
9 O: ]! B# D  @+ W% q- Z8 |2 O53 E4 ^/ y# |# J* G
s.str[1] # 对每个元素取[1]的操作
5 B% ?* X6 Y: U5 ?6 m17 X( o# m) R1 N  W) m
0    temp_1  f; K. n, h3 e: j
1         b
: X4 C, ]/ K4 F4 B: G/ j( W2       NaN% N; a% a0 s$ w6 e, E
3         y
( }! x5 V+ x; Z6 B+ Pdtype: object
2 L4 s- @! i; h$ i1& N$ ?1 n8 _+ f
2% w! y  X7 x8 W5 m7 c2 u" }
38 m- G  Q5 V* r/ n" i
4
9 c& o6 X) D+ |  i5
3 C- Q0 }: W7 [+ G$ ]. K, n; c; bs.astype('string').str[1]$ I* ^0 O1 f, c- k6 f  `3 [1 e
1
, L: {/ _  a$ f2 W: S# ?1 R) Y0    1
- U$ \! w, m0 S- r( v8 N- N% o0 w1    '* Q4 @, I$ ?, V) D+ k
2    .2 t+ c$ A: c% c9 t  V/ i4 g* A
3    y
5 C0 R4 z1 v* l+ ~. b& z, odtype: string
  U7 P/ _6 |9 l, R( \: Z1
- O6 v& d4 I  i  k  N3 j2/ L3 e0 d; K1 R/ z4 M( Q
3
, x. e( h9 B# P' M/ B1 g. F9 c43 \* J3 p. [4 h7 T
5
0 o- H/ P3 f3 L8 S2 j) S8 T0 Q除了最后一个字符串元素,前三个元素返回的值都不同,其原因在于:
. L% X. t) |( ]# w  n8 n5 Z
- z/ _, t# A6 D; H当序列类型为 object 时,是对于每一个元素进行 [] 索引,因此对于字典而言,返回temp_1字符串,对于列表则返回第二个值,而第三个为不可迭代对象,返回缺失值,第四个是对字符串进行 [] 索引。
5 ]0 ]9 \; X  bstring 类型的 str 对象先把整个元素转为字面意义的字符串,例如对于列表而言,第一个元素即 “{”,而对于最后一个字符串元素而言,恰好转化前后的表示方法一致,因此结果和 object 类型一致。
( t/ i" p( S! u" z8 S6 ?, g9 r+ Astring 类型是 Nullable 类型,但 object 不是0 O0 P! D! Q* d8 b  u
  这意味着 string 类型的序列,如果调用的 str 方法返回值为整数 Series 和布尔 Series 时,其分别对应的 dtype 是 Int 和 boolean 的 Nullable 类型,而 object 类型则会分别返回 int/float 和 bool/object ,不过这取决于缺失值的存在与否。
% D1 R% o* b' ^8 K7 `* v9 O/ U  同时,字符串的比较操作,也具有相似的特性, string 返回 Nullable 类型,但 object 不会。: n  @: Y; G2 ^3 N; H
s = pd.Series(['a'])9 y( F( T. |. Q0 d( u  Q

& \) a5 {- J7 y8 q" ^s.str.len()  ?3 C" f. z+ ~2 e! M: J9 \
Out[17]:
, F6 g8 r1 q& ?& P' k. g0    1& ~# B9 D2 ~" ?6 s8 O! ?
dtype: int640 V- T* u) `) ?$ Y& ^5 O4 H

2 K0 P3 R3 T- d" w: Ys.astype('string').str.len()
* B9 N6 {' _# ]Out[18]:
" H0 L8 s# E: M$ u$ Y" ~* X0    1
: _( t  s# G0 E, |3 Z  X1 Cdtype: Int645 j4 ^/ r! U+ i  M! H, [
" _# y# f9 r, Y7 y  ]$ J
s == 'a'
% z, Q. b7 j) q9 n) F% j) p5 KOut[19]: ( ?. c; i; `) b) s$ ?
0    True
+ c% F* ?- H6 p- i0 C2 T7 [+ h- Pdtype: bool  c9 Z4 @0 i) H3 Z" L

4 K# q! D" W6 U+ xs.astype('string') == 'a'& p* d0 K1 `9 Q% U& o8 w* Y
Out[20]: / R, l' Y0 N) ]. U- e
0    True
; g; H1 ?% P1 l1 L6 }8 Wdtype: boolean- L' O1 }' _* ^: r# Y+ t
( h( @: m& D: P: D1 Y& E
s = pd.Series(['a', np.nan]) # 带有缺失值
, K4 R  ]& H2 K, p! q& ^' e# G* L* h) V$ ?" m5 _
s.str.len(); _3 V; z4 h5 i! l3 s
Out[22]: 5 W/ T# _6 x5 O: [. O- J3 a
0    1.0
. F8 }( V2 X, e3 |$ i1    NaN8 A7 I  f( z; v5 y7 z0 D
dtype: float64
) b: P6 p$ b1 D% r8 O" z1 z- H8 m( i( m- [; R) C- z7 {
s.astype('string').str.len()( w. M0 `  C: h9 q" N- u
Out[23]:
  s  b( w+ o! q( i& {4 u0       1
0 Y* S7 c' f( H0 a. b4 w, g1    <NA>( O( X* F2 V1 v! Z
dtype: Int64" E# S+ b$ C4 M8 H- ^( h( s7 S7 j5 h

4 c+ u3 B! p! @3 B1 W2 _" I! o  [s == 'a'
  p0 |0 }1 h/ @+ NOut[24]:
# ^! q8 Y" c' X0 Q0 n0     True8 i) f3 l, A/ Z0 M2 I& N& h
1    False* f% I+ Z9 Y4 k  W! M; T1 A
dtype: bool
. G# q; \4 g, x! k- `3 T
+ C( O7 R+ U: n4 ]) z# f$ Ys.astype('string') == 'a'
  C0 g- l4 \- z. c% Z+ WOut[25]: 5 q2 c: y6 F, P; A0 j. m; e& W  E+ x) o
0    True
- e0 v+ M- y( r2 C- D/ p  p$ w- F1    <NA>
# c1 g* ~: o1 y- Pdtype: boolean
: L: p& m/ f! t$ z# r9 I% c5 D( x# W, H. t! b& W4 r
1/ I! E# P# M8 C6 `
2: c) Q, V! F7 M
3
3 L) R3 |  r) o7 f0 g& O% }3 U" l4
: D( o, j) X/ ~+ C6 W9 s58 D! m+ {6 E1 j8 _
6
& ]0 [6 q" k( M. m" v7
6 R0 d( d' ^7 g1 c% n) ]1 \8
+ R) p$ l  v& c8 f5 D2 Q9% ^4 _/ L9 @4 H- b% Y
10
# |- i# s& o" w- t/ @" s6 t4 S: U+ v11" y0 j5 s9 o$ ~/ Y" Y7 d, E/ n% c
120 _( x" s2 \" l
13* W# `# L# Z& M0 s  P; r0 m$ p
14$ h  L9 m% A! w+ _$ M; ^
15. e9 W* A# v1 m+ I5 m% {
16
! f8 a- I% `' d* u17
1 ~" G0 s0 ~& B+ J3 A+ g187 P" D8 H1 s5 g  ^# M. `5 e! ^" ?
19
9 P  r0 D) W8 q# r2 v8 S) D* p  ^20
, V/ |$ o. z) u& `! [21
& U0 _/ Z8 n6 X22  o, {3 E9 ?- t+ e: b; D
231 o  c; L$ i8 e
24; g2 O8 T- l/ A/ C) j- V
259 u( o6 Q5 d: C" N6 ~
26
4 T: T# u7 K/ r# g& q9 h27
) r# q$ }; Q- Y  }2 p6 Z/ G28) S. R/ N4 d' Z" Y7 U
29
5 G5 b7 \& E! B9 K6 U1 [& Z. C30
3 k: q% ^8 F9 L; |/ h- C31
1 ]3 Z( S  T# P! a, l+ _) }0 a3 e. ~32
1 o+ C$ ]1 s: o1 O33! ?( C& y) w; t1 L
34  q8 L/ e* d) W- |) X" Q2 w* V
35* n( [9 s8 E$ ]9 f* l; N
364 Z5 C$ D( v0 }/ J
37; P& i* Y1 g2 g6 @% b* O7 f
38" W  F- ?% b1 D" l! ]3 f, `
39
4 F5 _3 e# E3 D% [401 R; n" m& N$ L/ }# h/ T
41
9 }5 T' v  y0 k/ r42- b2 {4 A7 \7 y; \
432 Y! B( L# l3 U1 d3 ]/ A; W
44
2 J5 v. w) M' [45
" K0 Q" A0 D& t46" y" s/ N: l' X3 ~- X- j
47
1 z+ g% _9 l0 `  对于全体元素为数值类型的序列,即使其类型为 object 或者 category 也不允许直接使用 str 属性。如果需要把数字当成 string 类型处理,可以使用 astype 强制转换为 string 类型的 Series :8 ^& M7 M$ ^$ }4 X; J) w. |( h
5 `6 |) k# e6 ~3 F
s = pd.Series([12, 345, 6789])
( E3 h- B. ]# o8 q* [+ Y) |, I' L6 P/ T' N3 x- O. n) i  {
s.astype('string').str[1]3 F5 e# g; g5 ?. F% O
Out[27]:
- c% [& T$ G0 ?0    2) p9 R# c  |8 u
1    40 j9 ^1 Q* Y/ j6 @" z6 z3 t
2    7
+ p# i8 j" X/ O% x% hdtype: string  {8 J" U8 B& o. H) H3 W
1
" i- z! i" [) }( g/ g7 c* r2. \$ N3 W; `! Z
3
: J9 I! @3 h' J. o4
1 i+ w3 e7 `/ g) Z- I: |  D, C5- E1 c) k9 M% ]
6' U+ S- n$ R( r' Z" a
7: q9 u! F4 I1 M6 A: J
87 }* ^( S; j; Z/ K9 H1 C+ W* m
8.2 正则表达式基础& h/ ~" A0 _: m  D$ [- e& X$ B
这一节的两个表格来自于 learn-regex-zh 这个关于正则表达式项目,其使用 MIT 开源许可协议。这里只是介绍正则表达式的基本用法,需要系统学习的读者可参考《Python3 正则表达式》,或者《 正则表达式必知必会 》这本书( p7 O" \# j* b
9 A8 P  u  d: R* d
8.2.1 . 一般字符的匹配/ [3 F! ^* l& I  Q& Z# ]
正则表达式是一种按照某种正则模式,从左到右匹配字符串中内容的一种工具。对于一般的字符而言,它可以找到其所在的位置,这里为了演示便利,使用了 python 中 re 模块的 findall 函数来匹配所有出现过但不重叠的模式,第一个参数是正则表达式,第二个参数是待匹配的字符串。例如,在下面的字符串中找出 apple :# o) V* J- d% E* Q+ A

$ U% I: q7 ^5 F2 iimport re
8 V" @6 p: A/ V
/ K: ~+ j$ ]9 t/ {( \' k. O: @' M1 Fre.findall(r'Apple', 'Apple! This Is an Apple!') # 字符串从左到右依次匹配
. L7 P, ]- }+ J1 F2 P2 B( POut[29]: ['Apple', 'Apple']
+ R2 ~% ~( ]9 g4 V. h1
3 m# T) c/ }& }1 y24 z  t; ?1 `  _/ O3 ^  O
3. b! {/ y1 ?5 I" W/ d; ~- `
4
% o9 {8 U, F+ N; a& C8.2.2 元字符基础
. ^1 O4 W4 t: F" }: k元字符        描述) ]0 k  K/ n/ a2 O0 q5 ?& s
.        匹配除换行符以外的任意字符
" C: l, e6 }9 d+ ~1 ?6 _[ ]        字符类,匹配方括号中包含的任意字符+ D8 d* c" ~+ k/ [/ x% ?3 f
[^ ]        否定字符类,匹配方括号中不包含的任意字符& W! G! r  S3 M/ F
*        匹配前面的子表达式零次或多次  d6 V5 K  \2 D; Y9 L" y
+        匹配前面的子表达式一次或多次。比如r’d+'就是匹配数字串,r’d’就是匹配单个数字
9 y5 Z; ]6 B1 R- d1 L?        匹配前面的子表达式零次或一次,非贪婪方式
, f9 P* n7 K1 \: J! |- `2 k/ B* ?{n,m}        花括号,匹配 n 到 m 次由前面的正则表达式定义的片段,贪婪方式4 K, J. @4 T8 l
(xyz)        字符组,按照确切的顺序匹配字符xyz  o* J: z8 v' ]) `: ^
|        分支结构,匹配符号之前的字符或后面的字符( J7 c% f: s: Z; }0 D: f# e
\        转义符,它可以还原元字符原来的含义8 g* ]# Y) b9 U% h! w' A
^        匹配行的开始  X: g' y) ?6 L5 n. `; x/ y
$        匹配行的结束- N6 m6 ~5 o) R# Q8 |/ [
import re* p+ |) O9 C6 e' X& P
re.findall(r'.', 'abc')! \5 m6 q6 D, G; q5 K
Out[30]: ['a', 'b', 'c']8 L3 `) z" w# s8 Z. v4 I

: P: C) x. Q' hre.findall(r'[ac]', 'abc') # []中有的子串都匹配
9 n0 |. K2 I7 j! Y8 Q2 ^Out[31]: ['a', 'c']
5 S3 a- @" w* F5 ]3 |+ G4 v- Y: f" D$ W5 @2 x. L
re.findall(r'[^ac]', 'abc')
6 Y8 b) A" `  Y# w0 X) LOut[32]: ['b']& ~# U' P) @8 J7 e: Y1 [# u

4 J3 M/ s. f2 R5 P: @: cre.findall(r'[ab]{2}', 'aaaabbbb') # {n}指匹配n次
9 r) S9 O% \1 V$ T2 b2 s7 B8 q) H5 J9 v  ?Out[33]: ['aa', 'aa', 'bb', 'bb']! g5 |5 h5 v" K% G1 {

/ |6 z( C4 f  i8 F' [1 i+ xre.findall(r'aaa|bbc|ca', 'aacabbcbbc') # 匹配前面的或者后面的字符串5 A  [, a% a+ n
Out[34]: ['ca', 'bbc', 'bbc']
6 X3 f' F$ Y4 Q9 P: D, ^
3 n! Z. x& d! h7 r9 G! ?  M# 上面的元字符都有特殊含义,要匹配其本来的意思就得用\进行转义。
/ j) ^. d% a- N6 h' r6 S5 r""", Z( N; d7 }& }; g" c7 v
1. ?匹配的是前一个字符,即被转义的\,所以|前面的内容就是匹配a\或者a,但是结果里面没有a\,相当于只能匹配a。: p# b% k: d  H  H) X
2. |右边是a\*,转义之后匹配a*,对于竖线而言左边优先级高于右边
' z8 Y7 J# \$ e4 J8 f) W* Y3. 然后看目标字符串aa?a*a,第一个a匹配左边,第二个a匹配左边,第三个a虽然后面有*,
- ]# N- F$ [0 R2 X但是左边优先级高, 还是匹配左边,剩下一个a还是左边,所以结果是四个a
% ?- ]; @% H2 X6 ^9 E6 X; @+ c2 F"""
; u- S# {% T  k- `, e0 L. {8 t# A
. D3 j9 d0 F$ K, T1 f) yre.findall(r'a\\?|a\*', 'aa?a*a')   # 第二次先匹配到a,就不会匹配a?。a*同理。$ ~6 c* S7 U' ~" i
Out[35]: ['a', 'a', 'a', 'a']
- @1 U0 b3 g6 C' P7 p4 e  j( `% ]% s1 d" r  R( N9 a
# 这里匹配不到是因为目标串'aa\a*a'中,\a是python的转义字符(\a\b\t\n等),所以匹配不到。
+ ^/ f) N/ t3 t3 C- B# 如果是'aa\s*a'之内非python的转义字符,或者'aa\\s*a',或者r'aa\\s*a'就可以匹配到\字符。
0 i. Z: _) Q( z5 Gre.findall(r'\\', 'aa\a*a')
! V9 R7 d% _) E[]/ ]; F  _$ @! x& x
+ Y4 `' z6 u- O8 ~6 P% d
re.findall(r'a?.', 'abaacadaae')3 l, T" F5 x$ ?+ q5 Y
Out[36]: ['ab', 'aa', 'c', 'ad', 'aa', 'e']
" e+ u" ^0 |( A( H, ]3 |
8 c% L% I. \% x8 o9 G- ure.findall(r'(\w+)=(\d+)', 'set width=20 and height=10') # 多个匹配模式,返回元组列表
# u; z  f5 f1 A* x. V9 b; r[('width', '20'), ('height', '10')]' B" R8 M* X. J+ B
5 S  A: e. |" S( w5 x
1$ h2 O, C& `/ ^- _3 i1 X3 v" P; d
2
* R; I" L7 |  d1 i/ Z# b% Y; ^3
) d( d, F0 Z/ V0 K8 {42 O! Q' L4 p" ]9 p3 P% q7 `  o
5
0 U7 H$ ^6 J$ y! ?6
9 ^, p1 w4 N. v7
* @4 F3 Y8 F& D! A% e8
  T# M' L8 g( R/ P/ m" n0 m9
; q# K9 p2 ]) A8 n3 j/ F8 y10
2 I5 m2 f( e9 @" K) T8 o- y8 r1 P11  ~) |4 p$ g$ U3 y8 M+ Q+ l
12% B$ S: F+ C- a7 ?1 m
13; a' f+ p1 p, ^- r" P9 S/ {! g
145 @7 M2 D% d3 B& ]* d  ]( }
15
' G3 ?! p* M0 o: s# {  b16
. s, H; h% j: C17
$ y6 G# T/ {* q1 @* Q18- |( c: a( }- r  p1 B! E  ^
19; T7 J! `# T4 g: @; ]
20. n, Z! C! f0 L5 B. ~' |" Z  L
21" `- Q8 \( q  z  v. K
22( u) ?7 p! B' _# E8 q' L
23' ^) t5 A1 k* ?, }4 \9 x4 v9 I1 T
243 V2 L; J1 M! e; g
25
9 C) s# P9 I8 v) p6 ^, a0 O: K! `26+ ^! v& b$ R( B4 G# @
27
4 F, \& c/ F( [2 ], K28
# S% Z3 l: H4 {29/ H( s2 t7 k" K" R4 d& ?
306 f  r0 j) H! q+ [, N' j
31, g+ W0 s) g7 r& e) o" k0 q6 {
324 _% _! d8 }" J* ~) \9 [* B. t
33
/ y2 w2 ?) K' V+ c4 p34
) @/ V1 y- Z- n4 u7 l35/ n% \, P- O" b/ Y; k; d; c
36
! w% d. n( s, y: S37) m& F0 a3 O; b# D1 V3 X
8.2.3 简写字符集' Y/ e; T' m/ `
则表达式中还有一类简写字符集,其等价于一组字符的集合:3 `( x% o6 |7 U7 {  z

( @. l" t  w3 ?& e" h, v简写        描述- `& A6 j& @' h
\w        匹配所有字母、数字、下划线: [a-zA-Z0-9_]- K& ^) c6 b+ R4 o8 _1 k
\W        匹配非字母和数字的字符: [^\w]7 t/ t# T  G$ |3 h; G: }
\d        匹配数字: [0-9]8 k; B3 ~+ O# [% A# t* [
\D        匹配非数字: [^\d]. I, p  y: }# _/ {, l- ]
\s        匹配空格符: [\t\n\f\r\p{Z}]
$ D* S; v' v; b3 f) G( K\S        匹配非空格符: [^\s]
" p9 q4 ]7 i- t  M( W! C2 @\B        匹配非单词边界。‘er\B’ 能匹配 “verb” 中的 ‘er’,但不能匹配 “never” 中的 ‘er’。; ^  k$ m2 A5 P. {5 h* B
re.findall(r'.s', 'Apple! This Is an Apple!')
  K0 D2 `" a; l3 [7 M6 ]0 x) LOut[37]: ['is', 'Is']3 f- g: R+ V/ @

) r/ F7 t9 h# m3 {& v* t$ ?! jre.findall(r'\w{2}', '09 8? 7w c_ 9q p@') # 匹配任意数字字母下划线的组合,但必须是两次
) v7 L2 u* S. }" H/ aOut[38]: ['09', '7w', 'c_', '9q']
3 _' z/ R. f4 e1 D5 d, ]- y
. E& M- E  g* H: l2 Jre.findall(r'\w\W\B', '09 8? 7w c_ 9q p@') # 匹配的是两个字符串,前一个是任意数字字母下划线(\W),后一个不是(\W); T# ]1 M- r  v7 ~. |3 G! x% R- k/ m
Out[39]: ['8?', 'p@']: J. `+ y3 f3 v  Y9 Z

, v, J6 o+ ~- ?; p2 l+ q; g- }re.findall(r'.\s.', 'Constant dropping wears the stone.')- u6 A% y( X. o. l  i7 _
Out[40]: ['t d', 'g w', 's t', 'e s']* _: t( |. @' x" g' Z1 x/ }/ t1 R6 ^
7 h7 k+ _" q' U; p' w8 ]8 t& a
re.findall(r'上海市(.{2,3}区)(.{2,3}路)(\d+号)'," b) `  ]1 I- Z! J
           '上海市黄浦区方浜中路249号 上海市宝山区密山路5号')
4 X% K! r7 o& {- `. s+ T, D- \) e5 {5 g( o5 ]4 B4 e/ h
Out[41]: [('黄浦区', '方浜中路', '249号'), ('宝山区', '密山路', '5号')]
2 }0 c  f5 ^& o6 p8 c
3 a: H6 `( j+ Z1' O* T4 o  t  N' [/ O
2
  r& {  Z, q( _2 ?; |% I3% }; R6 ]  t/ p5 ?( J, M
4
9 F- j& t+ F- x- J& f# ]5  U# _# S# [, r! z
6. `0 v# {" z  [8 D- t0 K
7+ h8 u0 P' ~5 Q
8
' o6 ^( n. X( ]6 z8 a" ~3 M9
' o7 F2 J" \! T4 S. u10
# @# T+ a" L" P0 a6 R# i11
. G$ w% l" z9 c- O/ |' o12
( b/ o0 M: t% s$ u4 M3 l! q3 m13
( n% y8 a0 `) R7 o1 t14. o. F# ~- Q! h9 l
15
9 d+ Q& d$ C* k. S5 T6 i16
; ?  o$ |, F* ]0 l8.3 文本处理的五类操作
6 t. b# R3 w/ [8 ?2 Z* d  e8.3.1 str.split 拆分
& ?1 F. ]. m* Z6 ]% ~, v- j) e  str.split 能够把字符串的列进行拆分,其中第一个参数为正则表达式,可选参数包括从左到右的最大拆分次数 n ,是否展开为多个列 expand 。
4 V6 @: A6 [. k' }  M  v3 O. _- n: g) a( T4 h* F7 z
s = pd.Series(['上海市黄浦区方浜中路249号',
: Q# R: ]7 o( e            '上海市宝山区密山路5号'])
/ w6 \" r. G0 Z+ M7 r1 Q8 n
# A- g0 G4 ^6 r0 Y, T! v1 j  W& n, |, G3 R4 w- f6 ^
s.str.split('[市区路]') # 每条结果为一行,相当于Series' ^" z! p; s+ s# r) h
Out[43]:
2 l" x$ @! F1 H; s2 N0    [上海, 黄浦, 方浜中, 249号]
3 S- v5 a3 k0 Q$ ]$ N% m1 M1       [上海, 宝山, 密山, 5号]; Z0 g' ?0 {' s
dtype: object
% [! g0 _# r$ O2 u( f7 v/ K6 O) _& x5 a7 N2 `* _8 K1 w! |& l
s.str.split('[市区路]', n=2, expand=True) # 结果分成多个列展示,结果相当于DataFrame
+ L; G, [, ]3 |  \6 {4 TOut[44]: 4 I$ }; X- b2 ^* W8 {
    0   1         2* P9 ~4 T" P. m; Y
0  上海  黄浦  方浜中路249号5 {$ t5 ]# d! k) E. M
1  上海  宝山     密山路5号7 W' v1 P0 e/ [' b8 s2 L) p
16 f5 J- W8 ^% s8 j* E1 D
2
; g1 g3 `3 {8 j; v/ O* r/ Z36 H5 w( ?" e# i8 [; L! U" F# x
41 v" w" Q5 A0 l. E3 a2 B
5% {% \2 V: d: G! ^" K! a
6
4 q/ i" x2 A0 [79 C2 c3 _  h: q  o% K5 b
8
* G6 X. T! @. z; O+ ^) H9
, k! _# ^) r! Z: x103 {# C$ \4 u( r  S+ x( H1 r
11
) Q1 u1 \( A. ?8 L121 q% n) d: v; E( [" |8 y& J) Y* A0 ]
13
" d& l3 q, w% Y5 U' S+ W14
" o$ ^: x# c6 r9 A  g: ^15- x( U$ _2 }; X4 U
  类似的函数是 str.rsplit ,其区别在于使用 n 参数的时候是从右到左限制最大拆分次数。但是当前版本下 rsplit 因为 bug 而无法使用正则表达式进行分割:
& S9 k( t8 v" E; }0 s
' F7 k* `/ U* m/ x% f- n) h+ ns.str.rsplit('[市区路]', n=2, expand=True)
. G' H; P5 Y+ W& s1 dOut[45]:   [6 P, [+ d, q. u. W( b- E4 z6 x" i
                0
. m* w6 j4 |1 R8 o- ?3 a, \, [0  上海市黄浦区方浜中路249号
) L! o$ R3 u- `- W( `& D0 {) n1     上海市宝山区密山路5号
! _9 j1 ]' l  y1# ]+ c' K, T# i1 _! B- l+ k6 d. n* I
21 Y( w; d; k- L+ m9 ?: ^6 ~) G  k
3
0 n5 M/ ~- @0 T- C0 W3 ?4 |$ L4
- ]: b$ e' b* f* Y- D5
, ?0 f3 ]! j: }$ a$ D: |9 f8.3.2 str.join 或 str.cat 合并& [; p8 Z5 x/ p# m) Z
str.join 表示用某个连接符把 Series 中的字符串列表连接起来,如果列表中出现了非字符串元素则返回缺失值。
/ `5 B* ?9 V! t1 ~6 E- t2 d, vstr.cat 用于合并两个序列,主要参数为:/ O4 L7 V& v: S2 i) ?2 f
sep:连接符、
, D$ X- i1 G2 X, e. R1 O) T1 o6 n+ O% a  Bjoin:连接形式默认为以索引为键的左连接
5 N" x( y: `* D# G  z: F1 }6 \na_rep:缺失值替代符号
4 g- O# v1 N. Vs = pd.Series([['a','b'], [1, 'a'], [['a', 'b'], 'c']])6 u6 n. i/ r; c: z: |" A/ d9 l
s.str.join('-')
$ A2 C- U/ ^0 Z( s9 |Out[47]: 3 l7 b8 D6 w% n" r. z6 f
0    a-b
% a1 w+ T- z2 Q6 T4 ~, a9 ~3 [* L1    NaN5 e) V. g- }/ [1 z
2    NaN
6 Z' M2 I1 i7 f/ D; I  `' K9 Qdtype: object- t$ c& R" N+ }. o+ I  S/ B
1
$ F( _' P8 O; |2. H8 V. G6 D1 h  I0 X+ r) i' i- [
3
- X; ?9 _  A: A* x4
- s1 f, u, p6 X8 J& V) x- }5
  z% V4 I" B2 s1 c1 A1 M4 M$ U0 }6
2 Z/ O: Q0 A! d; N. H5 M$ b7
! f7 f4 n8 J2 `( z$ Qs1 = pd.Series(['a','b'])2 T2 x9 A/ W/ a! o! ?6 E  F5 R
s2 = pd.Series(['cat','dog'])+ n4 Z" g& W* }; A5 s" V% \
s1.str.cat(s2,sep='-')
2 D/ e6 [  V/ }5 ZOut[50]: 8 C7 d6 [. [% {  M; q
0    a-cat
4 t$ ~) e( P& p+ C+ Y1    b-dog
* T% J; O$ M1 ^% V, Pdtype: object
. E9 d. [: j$ K2 h% y8 v$ n- d: l# N$ V/ x* ^; }$ N5 m3 D! z
s2.index = [1, 2]
" x/ T0 q% k8 L9 }% _8 Ss1.str.cat(s2, sep='-', na_rep='?', join='outer')' y$ m* T4 B+ S% k1 `
Out[52]: ) S1 Q% b& H/ z2 q
0      a-?
0 h0 s8 `  x7 _* d) ~5 A2 T1    b-cat& e- ]- i4 m3 `, j5 ?! _; |. _
2    ?-dog" M4 o4 p2 J* E
dtype: object
. j1 k3 e/ k+ n1 ~- A9 l  ~1! {9 R0 b6 S+ h% u7 p: A
2
  \0 [( M7 [4 Z/ |) ~3
1 Y, j; {' A" \5 ^4 D4
2 O. L* L# O: i2 y. R2 i+ U5& }6 X, z# a( d4 c1 S5 V6 l+ l
66 m8 }, k4 D% T$ B) E5 j3 M
7
$ A1 V4 D+ l) `6 k9 A- }5 o1 X: Z2 F# m8
% H- `& P! J1 R) Q  _0 u* K9
6 H2 x! O- k# r' w5 T4 m% H6 S10: a: |9 _. N4 _4 \% `
115 M3 A8 L: k7 C3 w9 v
12
+ X2 [' B) g4 K' \133 `, O  C7 x# S' B1 a7 K
14
; l% t. \1 I3 h( ^15
1 m' a9 Z' w6 J  U% D" v8 `8.3.3 匹配( v3 q; U4 o- U0 {: T
str.contains返回了每个字符串是否包含正则模式的布尔序列:
8 b+ M  p! ~. H& _% E) U, c9 `- ?s = pd.Series(['my cat', 'he is fat', 'railway station'])! \2 H3 m4 Q0 r. f( t: o
s.str.contains('\s\wat')1 k9 g1 k% P' G

: B# W  b, a' g; L3 ]; i0     True0 x. Q- A" e" }2 z  v6 ^0 c
1     True3 q" d7 _0 K, Q% p1 E$ }
2    False
5 Y/ F; ~( s/ E1 J# W6 Udtype: bool! T, o; Y' U$ ?
1
# H8 j( @4 w- e$ w; J2
8 s  k. T- [) k, o5 I4 h6 H30 X5 ^7 z, ]2 x- d# A7 g" A
4
. n& \4 I& f) |' h7 }* n53 `0 x1 @" A6 l& m
6
7 P% I, m6 _% Z' B& S7
( e6 G# O# X- L& mstr.startswith和str.endswith返回了每个字符串以给定模式为开始和结束的布尔序列,它们都不支持正则表达式:8 M/ f2 U, N3 L9 @4 ~3 T
s.str.startswith('my')" n; C, |3 l$ ], G" G

, j7 s9 c, ~9 B2 x( J, m' E" [) @0     True: R% K3 _2 R: ^$ A
1    False
# p! t& X: t- S; X: Q% s& k5 a2    False/ B) D  ~% ]! c1 ~1 H; f* x0 F+ c
dtype: bool1 m: t9 w4 h8 [* M5 W
1
) K) w( Y! k8 x2
- k. I( e5 _# ^  X5 ~$ N6 [33 o" V8 q& A6 c# V" f7 d
4( G5 {- V8 U" G- k: ^7 j; r
5
1 R" J, v' J: w% @# g; G+ A6
4 z: Q* @' z7 ]) b1 Y+ e) M4 a9 X! bs.str.endswith('t')% j  n. i& P' O; A* d2 m

4 N# C9 e: G$ z% a# W0     True- y; Y! o" u& t* p
1     True& V9 }) H5 ~: z% |" S8 a
2    False
& y2 `, d1 \5 Ydtype: bool
* q1 V/ p7 K) h6 A  H1
& c; V' }/ J2 A; t+ m5 d2% K* r( a7 Y% \; E9 \
3$ o, T0 r! F  M* f; O4 |  c. L
40 `8 N; p& Z- o0 ?% e
5
/ D: L$ u0 X& C& g$ |8 T1 s6
& w7 z. }. r# Dstr.match可以用正则表达式来检测开始或结束字符串的模式,其返回了每个字符串起始处是否符合给定正则模式的布尔序列。当然,这些也能通过在str.contains的正则中使用^和$来实现。(貌似没有python里的search方法)
9 d; f# W- [8 y4 _" @: l; P5 os.str.match('m|h')4 f9 j4 I2 [5 n: l
s.str.contains('^[m|h]') # 二者等价' S6 I  Q5 o& f: q7 v
  m  m1 C9 U2 ^  |7 d. D
0     True
9 o: z" a3 m/ h: e2 G  w% P) w2 ^6 U1     True
; v) @7 f0 F; q# L1 I" x& a* g2    False9 ]6 A# w+ z1 n1 ~" B
dtype: bool
9 k: }9 D; u" Z8 b4 k1
6 b& j; C' w3 N* t2
/ e; E" K7 h2 C* a* n3
* G! R$ S5 i& E8 v+ H. n, w4
/ g7 c7 A# ]+ V55 G8 P( q6 V, x5 E$ B4 b
64 r& F, K+ H1 b, T' @# p
7: x) C1 [1 a( s: n7 x: ~
s.str[::-1].str.match('ta[f|g]|n') # 反转后匹配  ?% I  J6 D5 T' H/ U+ S- @
s.str.contains('[f|g]at|n$')       # 二者等价8 W- o2 R+ N( Q2 k9 B/ M- X  m
' @/ {* j+ U0 L; W7 m2 f: i9 Y' f
0    False- f8 d* Y  l, m* r5 P# g
1     True
$ b" a: O3 \5 L2     True* y; i/ h, ^) Z/ @7 \" R6 A% L" [
dtype: bool
  y; R1 W- r. o: A1
; R* H3 x7 r* F9 Y: y+ L2
' m& D; ^7 C) g# h+ V9 K39 _. b$ M' _$ B; V
43 E9 F1 L% y" a; i6 ~* n
5% G" ]' o3 Q- P* z- v
6
% [* w+ m& ?. {( T6 y! x  }- G7
# X+ L# w& b1 E! \5 G; H; Hstr.find与str.rfind返回索引的匹配函数,其分别返回从左到右和从右到左第一次匹配的位置的索引,未找到则返回-1。需要注意的是这两个函数不支持正则匹配,只能用于字符子串的匹配:
+ |, m7 N: o) r) b; zs = pd.Series(['This is an apple. That is not an apple.'])
- [" f5 `  T# U; I& l3 ?* V
) ]8 g" H. R: e4 f- ?: s, as.str.find('apple')7 q% C; i* `- ^& v; u
Out[62]:
  h- C% J3 b0 R0    11
* a' T2 N! a0 i# Sdtype: int64% E6 @; Y9 Q5 ^" u9 N. {' {% L

' T, j+ \6 `; j1 u" x' [- `s.str.rfind('apple')
+ K3 j# |* x8 W5 g* cOut[63]:
/ ^6 M% e0 j: e$ v7 I+ @6 I0    33
" P: Q& X: x* P' v: K" \& O. E: _dtype: int64. s4 P- b! d4 e
1
3 [' [0 ?8 @5 k: @" m$ f& N; _6 |2! L3 m, q6 w+ A- Z  M
3
+ ]$ A" l' ^+ J& g4
1 g, P- L3 P8 U- @# w3 J' R: m5
0 o( H" Z  ]7 c5 F. i6
& s, g+ R3 L2 @, `7
5 g6 Z" ~9 l+ y* k6 M8
# G; H  J0 M: |! x) Y8 f91 N& U, G; d; j% _' ?( i
10
+ g4 Q+ f. S' t! O6 X11
8 z! e4 \" U3 k' t9 b0 @3 k替换
7 f7 E$ h2 {4 V: Pstr.replace和replace并不是一个函数,在使用字符串替换时应当使用前者。5 Q5 _' G" T$ O( z0 K
s = pd.Series(['a_1_b','c_?'])
/ d; {* p; w& _5 ^; N/ T# regex默认为True,表示是正则模式,否则第一个参数内容表示是单纯的字符串,也就是匹配字符串\d|\?3 M2 O5 p7 E% g+ A$ L% `
s.str.replace('\d|\?', 'new', regex=True)
* O8 Z' c  `+ k& o2 T% `2 }. {
  _, v+ D8 k2 Q  D/ l& z) h0    a_new_b2 Y- n4 O4 ^. r8 N& r9 H
1      c_new0 R; ~% m* }' E, X$ ^$ J$ o
dtype: object5 ~1 S+ ], [2 S& x0 p# i8 B
1( E" u3 y( _# I. O' ~3 ^
2
; P7 Z& w$ @4 ^. m9 S( }) e. I1 w6 a3- W& x* p- S- ?' F) ?0 H2 Q
4
) w8 d# P4 L4 ~7 b# l: {1 l( z. \$ s5
' x( K, r! t4 q4 }6 g2 s  f, c5 N1 Q6
  Q* o8 m, ?( V4 X7
; q" o+ u2 _; ^. u7 u* G7 _2 _) n  当需要对不同部分进行有差别的替换时,可以利用子组的方法,并且此时可以通过传入自定义的替换函数来分别进行处理,注意group(k)代表匹配到的第k个子组(圆括号之间的内容):! x; D# x9 d; D7 w- U) x. E3 C
% `, I& `. M$ O2 U1 H, B
s = pd.Series(['上海市黄浦区方浜中路249号',) \2 E: s- |+ h  s8 E
                '上海市宝山区密山路5号',' P5 U& }, X1 W9 E" w" k$ p" X0 n
                '北京市昌平区北农路2号'])
9 ]; g! J! ~$ I' Wpat = '(\w+市)(\w+区)(\w+路)(\d+号)'
8 p) G- r3 N4 l, t& c7 A0 jcity = {'上海市': 'Shanghai', '北京市': 'Beijing'}
) P& ~* I) F& F$ a1 g3 K" Idistrict = {'昌平区': 'CP District',5 a( H; j  K* P0 S
            '黄浦区': 'HP District',
. l$ q0 D( G1 {$ P            '宝山区': 'BS District'}
( Y" Z1 @* v2 V2 w; H9 iroad = {'方浜中路': 'Mid Fangbin Road',
$ H$ l# r+ u4 C4 k        '密山路': 'Mishan Road',9 S- k2 N" z) h( i1 B
        '北农路': 'Beinong Road'}
4 E* z8 s5 k& M; {4 P  Mdef my_func(m):
! l- H, x4 n+ E9 q    str_city = city[m.group(1)]
9 Y' X4 W" n+ H* r: v    str_district = district[m.group(2)]8 d: ?" c' F6 |
    str_road = road[m.group(3)]
: ?) T( u7 O0 S5 W  Y    str_no = 'No. ' + m.group(4)[:-1]
2 X' m) ?( {, `4 p" U    return ' '.join([str_city,
# {, h1 B/ v3 b! y5 q/ \, T                     str_district," h; `3 o4 J. ~/ M# Q( n' z
                     str_road,9 \2 i) R# n3 e! W% T  |3 v" q
                     str_no])
/ e, Z- T* n/ `& b, S5 s4 ?8 Q) As.str.replace(pat, my_func, regex=True)$ v- b3 O7 y8 z# O8 N$ J$ H# {

, U/ d  p, p0 p2 M( n11 H( M2 L& }7 b  r. S) f5 q, x
28 u& Q0 o3 J% U: a
3* u5 n0 \9 w0 d  Z
4/ B# t/ Z( H2 |
5
# A+ Q4 G# _$ }5 @% R8 A6
. u8 n5 `' `$ `% N& P& \70 i1 }! c% w* g/ ~/ G
8( n' t8 n" b/ S. O  A6 P
9# M! o3 w1 Y. M$ E& I; N+ [
10
) K3 v& H0 {1 p# x& t' K  k/ P! Y) S11; K; j; B* {3 k0 \& B
12# w- n' ^. f) A- L* S( p) a
13
. k2 R+ T5 Y" r144 z1 Z( k# P8 X  }% r" u
15, c, {& B) \9 Y  ^- i
16
, {2 C( v7 Q7 {) t$ x2 t8 V: [, m172 E" ~  ]9 p4 N7 c0 H4 y9 h4 i
18
0 ?; T9 C3 H( P3 i8 G# ~) T; C19
7 K: n& D9 r! u. n' ^1 M20) k4 g' w4 j' p
21
' l& X* B4 y* v. i2 C4 x0    Shanghai HP District Mid Fangbin Road No. 249
% ]& _2 C6 I4 m6 J0 o( v# V# B" r& y1           Shanghai BS District Mishan Road No. 5  Q% T3 s; L, ?' g  k
2           Beijing CP District Beinong Road No. 22 H( i8 c3 {! L4 X7 N
dtype: object
* b5 s# h9 v" `/ P/ C( y' X1" {* V; P2 G( H" m8 W6 h
2
2 l% w* Z0 o* }2 |: A3
! d/ k( {7 j; N, ~3 p  p* H8 K7 h4
% j/ u$ L9 e7 o这里的数字标识并不直观,可以使用命名子组更加清晰地写出子组代表的含义:, R: r7 ~7 z/ p: d* r, K

: f8 B9 p4 W; {- H) S# 将各个子组进行命名3 j' e& E/ ^3 N& [3 w8 E: [
pat = '(?P<市名>\w+市)(?P<区名>\w+区)(?P<路名>\w+路)(?P<编号>\d+号)'
. e* t* {' V6 I7 Z: |+ O" adef my_func(m):- h  G, i0 ^5 B: X  k
    str_city = city[m.group('市名')]# t1 B+ W% P8 }
    str_district = district[m.group('区名')]
# w, X& f# V; {: r8 [" M- _1 f    str_road = road[m.group('路名')]9 _$ [: E: y2 \+ b
    str_no = 'No. ' + m.group('编号')[:-1]) O* C' A# H' y: h  ?6 P
    return ' '.join([str_city,/ Z7 G  s% I) s. g/ D8 L
                     str_district,0 M$ \7 J5 y1 N5 W$ _3 h
                     str_road,+ i9 e$ ~& m" h- d7 R' n
                     str_no])
* {" Y, l7 R* P; g5 C6 Cs.str.replace(pat, my_func, regex=True)
. }) k; m2 S2 l" j( C+ H5 }( Z1
2 L$ h1 A2 b& ?+ k) N: R. D) g/ d2
7 @# [# f1 g1 d$ w5 @& v3
9 v9 S: z7 |4 @6 ]: ~# @4
" v3 m, S: ?- S* B! t) J  R5
5 R( v0 T# s; B" Z8 z6
4 {/ y1 K6 v& A# p" [7
' d5 A# x, P% B86 w( s) T1 m% L- ?* u5 U- [
9* m) x, T( P2 {; d* i# P" Y
10
  z7 E$ ?5 `) r5 ~  Z7 b, \# s9 l11
1 ~0 d) c# h, k6 x4 b12; e# r6 y# A6 c) h
0    Shanghai HP District Mid Fangbin Road No. 249
7 o) m5 k, ~+ ^! i: Z1 o1           Shanghai BS District Mishan Road No. 55 a1 O$ Q, i. P( |7 o3 d
2           Beijing CP District Beinong Road No. 2
' d. X! l6 W+ [$ Y# g8 Udtype: object& D6 O! a+ S2 \/ i4 U. \, e& [
13 {; f1 I% A# h7 r1 A% A
2" c7 m* E! v! H! d/ }* y$ \1 N, R
3
, `' U- G& w2 N/ K! \7 u, i! ^7 e4
) v0 m* |5 p* Z* I: s) X  这里虽然看起来有些繁杂,但是实际数据处理中对应的替换,一般都会通过代码来获取数据从而构造字典映射,在具体写法上会简洁的多。/ [$ J0 q- c8 e" L8 A! P1 E

* U0 [! S: x6 Y, j' }* {8.3.5 提取
# u' W- C1 h, q; L( F" O3 V/ wstr.extract进行提取:提取既可以认为是一种返回具体元素值(而不是布尔值或元素对应的索引位置)的匹配操作,也可以认为是一种特殊的拆分操作。前面提到的str.split例子中会把分隔符去除,这并不是用户想要的效果,这时候就可以用str.extract进行提取:# b' G1 L0 d8 e! j
s.str.split('[市区路]')
8 p7 a" h2 e- r' rOut[43]: ) u4 s% q& W' M) d  w6 Z7 K# _7 I
0    [上海, 黄浦, 方浜中, 249号]
+ V4 L; J3 y( X5 G. ~1       [上海, 宝山, 密山, 5号]" X  e7 Q# F, J% N
dtype: object
6 l- t; W" m* O+ k& [6 d. M/ B) s- p
pat = '(\w+市)(\w+区)(\w+路)(\d+号)'. ^1 k4 P' w/ @" y$ b
s.str.extract(pat)
5 U, \; J& @' s# p+ SOut[78]:  Y8 P1 q: {8 [! c( y
    0    1     2     3  N5 `4 h' Q' n" ~* h
0  上海市  黄浦区  方浜中路  249号
) l, O' w9 l# N( a1  上海市  宝山区   密山路    5号) S# O& z. I/ X5 ]0 Y. i
2  北京市  昌平区   北农路    2号
6 p2 T" d7 e, B" X4 P/ m1
6 j' \. K8 t5 E21 P3 u( A5 [' M9 L' ?
3
* V- R( ~: y3 F/ h; l4
; N$ q. ~2 }. p2 c- h. f5 p5
$ t# F* \( t  @$ v" A+ R. F( L3 r6
# E( I9 w+ B3 q  }74 X9 `8 d8 C* ]! I( D
84 J3 G  i0 |# Z) N. Z4 Y
9
5 |- c- ^2 C- e/ C; W! _' {10
1 ?3 k2 P2 u7 C% O11
( d9 V; B: a- O! @3 U+ r2 d. I12
4 c) {3 M8 F5 U) r: n* e$ [13
2 p7 ]+ d0 l8 Z! }( H; `- M通过子组的命名,可以直接对新生成DataFrame的列命名:
0 n5 B  k! n& a! P- x% H
! U: ?' y0 Q9 G. B$ t/ p4 s! g) J! zpat = '(?P<市名>\w+市)(?P<区名>\w+区)(?P<路名>\w+路)(?P<编号>\d+号)'2 H3 ]. d6 N. g% |
s.str.extract(pat)+ t0 k% _* ~1 s* U
Out[79]:
- Q6 l+ r6 h3 \+ d1 h0 O+ U    市名   区名    路名    编号
8 \' n/ q2 |8 f  h0  上海市  黄浦区  方浜中路  249号
- n( B( H- Z' J1  上海市  宝山区   密山路    5号9 Y- g7 [7 P. I% H: t/ O
2  北京市  昌平区   北农路    2号  h5 c! l% m+ V" T3 e$ Y$ i
1$ [6 z& x+ L" L- a# a
2  b3 Y% `( J8 ~0 T3 u( |( K
3# i3 O# a# W$ h: |9 N1 n' T
4: k8 T9 {# @# b0 g
5
# U* j( g, Q4 r5 t. U" p6 ?; @+ G6
' C" K3 v4 A. T7 h1 S9 _75 c9 {# g' U$ H% \5 \, k3 \3 y. W' k
str.extractall:不同于str.extract只匹配一次,它会把所有符合条件的模式全部匹配出来,如果存在多个结果,则以多级索引的方式存储:, r' b1 R& n, f
s = pd.Series(['A135T15,A26S5','B674S2,B25T6'], index = ['my_A','my_B'])
. L% N6 X3 ^+ S/ |pat = '[A|B](\d+)[T|S](\d+)'" m, F. w% u7 s0 R
s.str.extractall(pat)
4 O) b% ~5 s" g/ F$ W4 \- {7 H, OOut[83]:
# A, m3 U7 V4 ?. ^+ j! c. V0 C& u       0   1( T# k# E0 B) q" f+ V
     match         
1 c' {$ }- K+ l8 F* ?% jmy_A 0      135  156 t. t4 W# {6 d1 q5 x  J) q
     1       26   5
  s' P( g2 M0 s( h0 qmy_B 0      674   2! U' ^1 V$ C5 G
     1       25   6
- H# u+ `) e/ a# {' Q" c1
) Z, f1 S$ D& n2
# Q( ~7 O" i% x3' E/ @& h, k, _( l
4
/ H% [# |; F  ]2 X6 b4 D51 v: ?  o  R1 x: T* v
67 O6 {+ L! z1 B2 K
7
* n9 L+ a, |9 Z83 \, w3 m5 k1 q. a- R
9, A; A0 V5 g5 l- E: z
10
& X. D+ s( p1 }$ u0 {pat_with_name = '[A|B](?P<name1>\d+)[T|S](?P<name2>\d+)'
7 x( ~$ S5 g% D) s' c& V$ ]6 fs.str.extractall(pat_with_name)
0 J, k7 q6 I( h  n/ S  ^2 {, KOut[84]: # c& C" E6 t, w8 V! o2 k
           name1 name2
2 W: A4 v+ {* a$ S: |" `     match            
" Q' C' x. a% m2 b2 |my_A 0       135    15
+ E; A- l) z! y$ W4 ?     1        26     5
4 S9 M, [3 G: D1 `; E, kmy_B 0       674     2, T8 l5 x; I* n% ~4 f4 K
     1        25     6
- P% F$ \% |% p! N1 o1
& `* J: i9 }) x- A3 f6 Y2
* ?/ l1 h" O8 Y: U3
$ G, i# [/ l: [# _3 Q' {% G4
5 C4 X  ?" j* @, L7 `  Q5
+ e7 P! t" U4 H: ], V' @6 D6
3 D: a" J7 h( Y0 g7# ?+ t7 V, `. x% g+ X- u* `
8
  n2 t4 J" U, n  f  I2 r9
6 k, V+ j8 ?- T+ }+ }3 D& f& Ostr.findall:功能类似于str.extractall,区别在于前者把结果存入列表中,而后者处理为多级索引,每个行只对应一组匹配,而不是把所有匹配组合构成列表。
# u/ S2 i  B4 g6 d! c* Ts.str.findall(pat)
8 x8 ^6 ^' d: W& \1: A/ A( V) A% t  h2 c' [
my_A    [(135, 15), (26, 5)]) H$ Z# b. c1 |! j' k. J# P/ @
my_B     [(674, 2), (25, 6)]
$ Z. `) ?) i+ ?3 y; Ldtype: object
$ t9 U, ?9 @7 k. K; H1& |. i2 O: g! w& z2 O, n& E
2
$ y* b9 M2 @  |4 z  s- N* K3
" u/ i5 [4 b- r+ C3 M0 n8.4、常用字符串函数( y: V5 v# \/ B2 H
  除了上述介绍的五类字符串操作有关的函数之外,str对象上还定义了一些实用的其他方法,在此进行介绍。) v- y6 F% U- O; ^( G2 {1 h  V6 m

3 n+ R, o" F* @' W6 X/ V* A9 K8.4.1 字母型函数
3 L- H+ X0 G( n9 _& A$ m  upper, lower, title, capitalize, swapcase这五个函数主要用于字母的大小写转化,从下面的例子中就容易领会其功能:
) ^& I$ r* X; d, E5 n1 {4 u  U5 x+ x, ], Y3 L
s = pd.Series(['lower', 'CAPITALS', 'this is a sentence', 'SwApCaSe'])
% Q+ A1 w9 ^5 c& ^4 {: i) i' }5 m0 ?8 w4 H' t! Q' ]
s.str.upper()5 t. ^4 d4 V5 D: K( u
Out[87]:
- c6 ^) L! h  R. P/ A" |0                 LOWER3 @: f' \9 c3 W
1              CAPITALS
" b' }# u7 J' M' {0 \2    THIS IS A SENTENCE1 }* m9 J* Q, S, \3 {/ e
3              SWAPCASE6 R) U. j3 U5 B9 y8 v& v
dtype: object
( j) w! n+ }" V3 y0 @3 F, C1 \+ W4 q+ u3 r8 a
s.str.lower()
9 B. m, c/ V' s8 I; xOut[88]:
. t, x8 B* u) P0                 lower3 |* S  N# i9 j, I5 h
1              capitals4 d" F6 G' w, d% _6 s
2    this is a sentence0 }  V- m* U0 o* _1 A, V6 |3 f! _
3              swapcase
* z0 O" C# |- g/ R$ v5 Odtype: object
0 ]  |2 Z7 r; w. B! M& M% V/ A
7 Q5 D; O/ p7 Ws.str.title()  # 首字母大写
2 G+ E( b* }% j: ~! G: w  X/ {Out[89]: ; Y1 ]. b4 p4 w! P8 y5 b" u. ?
0                 Lower
9 k  b* P. y* h1 l1              Capitals! m4 ^6 E' W0 |$ N; I8 O
2    This Is A Sentence
" h% ?2 [) [' c3              Swapcase$ k( ^+ E# m$ b, m+ w% K
dtype: object) w( Z. W; u3 t) `0 P# E9 r
" f) p( S3 F! e1 A+ f1 C
s.str.capitalize()  # 句首大写1 V. p& k8 g, L. j
Out[90]:
/ u/ [) ^% x( l0                 Lower' u$ R  R) ~+ P6 a) a9 B
1              Capitals  }8 J+ c5 g6 H! h2 z
2    This is a sentence9 d  u; k# V* t  F4 R' B
3              Swapcase$ O# `8 {$ X: z# Z
dtype: object
* S0 d) L& e$ i. V* s0 h4 Z% Q. v1 g2 m" Z) I
s.str.swapcase() # 将大写转换为小写,将小写转换为大写。* ]- s/ y- P# T0 U* v. r
Out[91]: ' q! |$ X3 F$ {  A  v7 i# W
0                 LOWER" Y; L& _- a0 b. x  G9 U. W
1              capitals
' z/ q4 q- H$ M* A2    THIS IS A SENTENCE
/ o" \2 ]7 V4 y7 [: _3              sWaPcAsE1 N9 [  X, J) A+ x$ c/ E
dtype: object9 s) S! G  z) N0 w: \8 z
/ S2 N$ ^% g% S. Q
s.str.casefold()  # 去除字符串中所有大小写区别& m( q. E: x) Y6 l5 a

* r; U: E4 E. R! b. B0                 lower: X, Z9 o. m7 a1 o2 ~4 G9 B) Y
1              capitals. O3 H/ j* m- ^" I" G
2    this is a sentence: L* L7 D1 u* D4 T3 S
3              swapcase
: R4 l, Y: Z( n
8 X. ]2 W, `( o1 q2 \! T# b* s1
* c1 ?, h' m( ?+ A) }2
% x* I# S' {$ }9 s34 a7 ^/ U$ [- u! ^
4+ h# J1 Z) z4 z4 j4 [0 j5 J2 {4 U
5. S* L" {1 g9 S  p2 L
6
% @4 \8 F, z* g; g% W. U4 F) s71 j5 b  p9 p, b- l) f9 x: m; ^/ c
8
& G/ i2 J+ T2 C$ n4 U9
, v: u3 F) d% \+ W$ ^  w8 ^6 X104 x! H2 |( Q) E+ t
11
/ \! e+ {( ]1 K2 B  e12
- W! l; U8 m! L6 ~- P13
; O* Z% k- ?0 I: g; }7 c( K6 l14
/ _1 f( j7 M, N6 @  `' U15
( Z6 V2 [( U, [6 `* V8 x16
. p; s8 P: F. Y" j5 U% L17. Y2 N+ p1 h1 B1 k
18/ g5 S: p2 i5 }4 [; b7 D( A+ [
199 N/ u: i/ W( R* y, d! R# O7 U( S* [
20
0 y5 k0 q0 N3 G0 @3 E6 F21% K/ T# g# ^* h/ p  e  S* `/ N
223 H" f; V) d  j3 N$ Y( j( h
23
" _' v. N4 y4 Q8 `5 ~24
+ ]+ A  d' L) V# Z" {25
/ ?8 U) W& G) q; ~& J, z4 F5 e2 O26( q$ P' g) w2 H# X0 h
27  Q* p- P4 P, O* e* h& _
28
; H! l0 s4 V$ `5 ~+ D& X- S6 ]/ G" [29
/ J0 h, d( u, I: i) D30, ^% }8 L& ~; z0 z/ K
319 I6 a* P* ~0 L
32
) N( u' k+ [& t7 z7 q! M3 d33; g; e. A1 w$ Q: S) W$ a; U
34
2 `  s- n1 b: J9 F0 d9 Y: f4 [0 E( j356 K0 }" l" ?5 U; O* N" @/ F' F* W
36
/ ]% `+ x( x; Q  [6 r: \3 X2 ~37" A- E, b" H7 X" ]( I
38, {. Z9 Z% I) g, y0 {* j
39( `: x3 u4 }' X4 X
40
1 X; `, P. X5 n# Q" Z414 @# u# D! Q# }9 G, Z
42
; g3 K! r& b8 l9 `& Z9 ~' H- K43. M4 y+ H( Q  V, |1 q" F6 u
44- c  }3 w( P, i" i( ^. o& {
45
7 t. R# n* f% O& |2 s461 g8 L! I& e7 s4 }
47; U3 k. R- a1 J* U+ Q$ O/ f9 I
48& ~  d+ X9 G  K/ J0 [* G
8.4.2 数值型函数. F/ M1 e  K+ O6 `
  这里着重需要介绍的是pd.to_numeric方法,它虽然不是str对象上的方法,但是能够对字符格式的数值进行快速转换和筛选。其主要参数包括:
' O6 ^6 x9 i  v, S" R" R
. Y; w; c1 _1 k' ~1 g! V0 jerrors:非数值的处理模式。对于不能转换为数值的有三种errors选项:0 `* X, b/ l! k, S( M( t- V
raise:直接报错,默认选项; y+ w  L4 S, j  O' O' O9 Z
coerce:设为缺失值
  G3 Q- n2 ^' Q; z& N9 f5 }+ ^; Aignore:保持原来的字符串。
. v$ D# Y- c. X8 \. t& ~downcast:转换类型,转成 ‘integer’, ‘signed’, ‘unsigned’, 或 ‘float’的最小dtype。比如可以转成float32就不会转成float64。2 D; `0 M% {; `7 u9 `
s = pd.Series(['1', '2.2', '2e', '??', '-2.1', '0'])+ w7 V8 q+ E5 o
4 D& d# i  H& M
pd.to_numeric(s, errors='ignore'); `/ |, J0 |" k0 n+ g3 e
Out[93]:
# T+ [2 x: e1 k# J* v6 b0       17 c( b8 L( F% k  m
1     2.2
- v" A! @: `1 r0 I: n8 A2      2e$ f: S: Z' s5 _7 A
3      ??
2 h; t+ W/ q% L) Q* C2 ?4    -2.1. R4 p" o8 n7 F% p/ V; D
5       0
  x( C$ ^* @# q& N1 `+ cdtype: object% _# G' I- t1 O$ ?+ |0 X- R( ^* i

; @3 U: P6 m. T" v( {' qpd.to_numeric(s, errors='coerce')5 h- N  q+ I' \. o
Out[94]:
4 o  n8 m0 w6 S0 @4 v) w6 l0    1.0" c" k1 E7 `1 F% j4 z" |3 v
1    2.2
$ T# }5 T; C. f7 ?3 J( O0 D2    NaN
3 g; t4 k# g4 O" z* @" c% n3    NaN
6 ]2 ]* k0 h6 n" d- s; d$ N4   -2.1% J- Y' F8 r4 I; K0 n% `, m
5    0.08 f, l. N$ p# ]4 E- g* R
dtype: float64  ^, @* q1 N/ m2 z, a4 O

( E7 y$ h* B4 ]; {1
, g) S5 u3 N5 N6 X2
& b6 E* v7 {6 n# G( j/ v* ?38 r+ s0 D( c6 G# Y+ w; Z/ u% M
4# _, q8 x9 G  g/ q) f3 V: `; j$ |
51 |- k& `) T$ P
61 y! y7 E, W  t" C
7
/ K( O% L' w, |9 s4 }' C8
  n1 @( V& I1 t9
+ [- N) \. n, a! N& o. P+ @- Q10: {6 l8 B3 M; {5 X. G$ |: w- `1 {
113 F9 D1 a$ b' e6 D4 Z. O
12
- ~7 }! a6 b: y/ \: g# h7 D- \13# |5 u; I1 T3 R9 u! T& e9 G, G
14
( o4 Z. E) |/ _15
; M' E# {) M. b) E* E3 m16$ F. Z  y5 f8 S9 k
17
7 p% `9 g, h+ }5 n# U% K180 s# p2 ~- n* K) [3 W
198 ^$ d5 A  i$ n+ e( s( x+ Z: j
20
8 z/ j- ]1 g2 u& M21
% f+ U7 o" ~4 B/ H* E& U+ e  在数据清洗时,可以利用coerce的设定,快速查看非数值型的行:
) H& C6 K! C/ O) P) ^/ a, b
6 D9 `% U: S. r# s& Es[pd.to_numeric(s, errors='coerce').isna()]- M" e. X" y. K+ m6 U; v1 R
Out[95]: ' L$ V( E; d, ], _
2    2e; i8 [8 `. R. z5 [
3    ??
" Z: I, v5 |) b' }* q% j. i$ g+ ydtype: object) T2 B% l- p1 o! \5 \0 a0 h0 S
1
4 c% I- |+ q) p% K3 F0 b! z2
. R7 R" F2 }# ?/ X) R& T3
8 N! x8 g. Y) W9 h/ g5 E$ \4# }6 ?# e- u' Q; r4 i9 X. ~- T+ [
5
8 f& @5 s: i6 N. q8.4.3 统计型函数  {% w0 P2 E: h$ j2 C
  count和len的作用分别是返回出现正则模式的次数和字符串的长度:
+ {. g6 i1 I# `! l' k
! q6 v" X" X' n5 j$ {' n9 Is = pd.Series(['cat rat fat at', 'get feed sheet heat'])% y# n3 A% o: C% o

& S) N" l; O+ g% d. zs.str.count('[r|f]at|ee') # |左右两种子串都匹配了两次
. u; s" b  D! V  _2 O" MOut[97]: 1 J) ?) @" ?% Y7 Z
0    2; `2 g( }) ]1 W5 T
1    2
" _( V- j! d( B( O- i7 mdtype: int649 H& x$ l5 q% e, O$ A3 D
2 \6 y* G. q5 t+ ]; f$ Q5 \# U
s.str.len()7 }3 J+ G# W2 }, C: @+ X
Out[98]:
; f7 N, m/ W* Q  i9 z0    14: r1 m, ^' z" n" Z% q# H
1    199 p# z/ ]; t5 B) f( G& T8 }& f: y: Z
dtype: int645 K9 U9 a% K8 E% v. n, ]
1
8 b0 {& H+ S7 Q6 M( S1 C/ Z8 d# m! h& w8 [2; F6 w4 |4 f7 @: B& U8 Z- Y( i, g& c
3' A. w- L) H% J5 C3 y5 S
4
+ ~' @) S. _. @: K7 j9 R& _0 \5
8 M0 ~+ g1 L& ?% ?/ s  N6
8 j* X8 Q( m3 l1 w3 d% p7) T; v$ D7 i, S9 s( S
8
8 y* n0 {& k2 w$ ^+ U1 u$ W2 {9
) j1 N! K0 x8 N& |0 I10' B2 ?, \& U7 k2 E
11
8 y4 z7 X# t2 G- U) M5 p, Q" C12& p. }3 i& s4 H$ s* D. p0 a
13+ t5 D( W' |" G% t% h
8.4.4 格式型函数
) Y6 f8 Y  e! l5 p, m3 i  格式型函数主要分为两类,第一种是除空型,第二种是填充型。其中,第一类函数一共有三种,它们分别是strip, rstrip, lstrip,分别代表去除两侧空格、右侧空格和左侧空格。这些函数在数据清洗时是有用的,特别是列名含有非法空格的时候。% I, b0 _0 }( E) s4 [# e& S6 ]
7 J/ F& u0 j0 T4 c1 `
my_index = pd.Index([' col1', 'col2 ', ' col3 '])
. l  x$ V) d# L# h* Y( z8 ?' v6 _6 J9 F0 S2 o
my_index.str.strip().str.len()7 u5 L; Y1 V6 t: n  |3 S
Out[100]: Int64Index([4, 4, 4], dtype='int64')
' d& H) m) Y" p' D% P* P/ N
6 B2 ?3 e( ]* s1 s# ]: Umy_index.str.rstrip().str.len()
$ ]2 N. h0 s# ?Out[101]: Int64Index([5, 4, 5], dtype='int64')
& b$ {! A0 d+ g/ U5 L4 N# d
& f/ \, Q7 \3 m, Q* W3 pmy_index.str.lstrip().str.len()$ S" Z8 @* }* J7 Y2 c
Out[102]: Int64Index([4, 5, 5], dtype='int64')
2 u; {3 b! m( Z0 u# ?8 S. m, W3 ?& N1
; S" T; }" V: x# T2
1 S6 Z8 S. c. L; K1 M36 `& ~; H' E; f) W& k
4+ t$ r% O+ _5 S4 B3 R
5* G5 v6 h2 H: @
6" q. f  o; P; b. }# y& d" r
7
1 z- [8 _( s8 S9 ~: c9 S$ [2 R8% ~9 i( |" X0 }
9
0 I4 |; C6 M+ D) v+ e2 t10
0 v1 M0 r+ Z( [  对于填充型函数而言,pad是最灵活的,它可以选定字符串长度、填充的方向和填充内容:
) E* k( j3 f5 N+ k& F* \' `( ~1 r" q
5 i9 l& Q: r2 ]# Q& ?: |s = pd.Series(['a','b','c'])
* R' ~, [& o! `/ b6 W3 `4 D! B9 x2 e$ |5 J4 E3 p
s.str.pad(5,'left','*')' ^# U. I3 m. ^6 G( E
Out[104]: 7 R/ O1 A; y) s0 ]2 O1 ]& y# q
0    ****a
( `2 L. A7 `4 }3 t1    ****b
. f+ p& ]7 o4 u' m9 z2 K& F2    ****c; K+ H0 I( P7 k# r* }
dtype: object
' T$ ~2 u% x. m( }' o4 m/ K9 M+ S4 k+ O+ p1 |, a
s.str.pad(5,'right','*')% q$ X, h6 z' O
Out[105]: 0 H* |: P( c8 m8 z: Q
0    a****/ N+ x/ I6 p1 r: X) R
1    b****
, V9 o! p0 Z; w2 y! B/ s2    c****- \+ `0 k/ e* m1 T) Q
dtype: object
9 e( k5 y" H7 [7 h" |% M
" X( \! T+ t& s& @) i' `1 Ps.str.pad(5,'both','*')# c; a$ L! O" g/ p( e% r# j
Out[106]: 0 k# ?; G; S* G$ }7 x
0    **a**
9 u% q# Y  S& d" H1    **b**
4 X% w" T/ v" }( j* N2    **c**2 G+ Y5 [& `5 R  N' D
dtype: object
5 T! T; U1 N5 D( {8 n+ G7 e
( _5 Y: e: s# s5 y1 x1
: |2 J$ O9 Z$ V2; ~- k, F0 r% C  K/ P
3; _" t- R$ v9 s! l# \
4( d5 G+ R) n; c2 ?3 z% A
5: N* X5 D$ T0 ]" n
6
, L. R7 S* ^  O7 Y9 Y7
7 \0 q8 u7 @$ C8 k3 j9 Z' Z0 Y9 w8
# h8 x7 T  r( k7 @" U' K9
+ W# k1 t4 G$ f0 i) J3 w10
" \$ t5 a) @1 G$ F# ^115 b1 h8 f0 G; p- G9 j* z
12; D% _( ?0 Y: {
13
) b* F% o8 s$ R* i- a14: K1 ?% S4 {" c3 F7 [2 x) z
15
/ W# C3 ~. @/ N+ O169 \9 Y$ `# {8 v7 O% R6 ]! d
17
* C. B0 B- W- ]2 p* x7 C18
! t* k) i) I' x* \; f198 {" ]4 @. M. c0 K) N6 ~
20
% i% l2 @, [/ z* Y21
! `4 v: s( D, ?- J4 q) X22/ W9 E- Y6 z; J! |1 y! e- s. V* C
  上述的三种情况可以分别用rjust, ljust, center来等效完成,需要注意ljust是指右侧填充而不是左侧填充:
  }) u4 @3 v. O  @+ V  a
1 v4 y. Z/ b( ps.str.rjust(5, '*')$ P: S+ p# X% e
Out[107]: 6 l1 M% k/ f& H0 P/ y' x7 s, v
0    ****a1 E8 ~$ r; _$ p  O/ x" M- h
1    ****b# Y4 q, t6 u5 t0 H9 M3 U. Y7 @1 C
2    ****c
. m! Z5 L6 |3 e. q! `6 Vdtype: object
+ `# l6 b8 U* P( l& D; v3 ]) a5 S, J7 E
s.str.ljust(5, '*')
  p) Q+ H4 \8 ]Out[108]:
6 K. m9 O; h8 s( J6 F3 t  B0    a****
+ ]1 ]. L3 S0 e/ j' h1    b****5 D5 V' ], ]9 L3 R& J! s
2    c****8 q2 E; P4 l! J' r
dtype: object! }3 g$ b, L' E

6 e4 [) X. ^1 @: Ps.str.center(5, '*')) _, f8 I3 F5 X9 z' n
Out[109]: ! ^+ D8 W/ Y' J. T
0    **a**, R. H) D% r0 M7 o: x; p
1    **b**
$ r5 z' f7 P7 C- o2    **c**
! N: V% J7 ]6 I# F# V* sdtype: object
6 ~- ]/ a: K5 K
2 _6 N$ T* W" D/ c/ M) Z( h# E1
- d8 x- W0 k; f! O' u. \: c  C' L24 Q" N( D& Z! |! b0 ]' @
3
; Y, O( u' n$ h4
: d7 Z( T& V9 z+ ^5 Y59 o4 L3 A* X+ ^9 I
6
9 s' {# A% c* u  |9 A6 H75 }% W4 C, Q4 M1 H% s$ t
8, E9 }; @* h* L& F5 Q/ L! @
9
: g& o8 _, P6 j' c4 L, @10) q4 m- {! ?  k6 G( E9 ^$ X
11
7 v8 r1 D2 m  x2 b6 i3 X12
' Y# C- s1 p* I13
% e% [/ D; Q/ D: V3 r- ?+ G& b14
. _# f. Q* S/ o3 t15
) [/ s- u8 }( {4 _16
8 s9 f5 a+ m. o" w+ {8 B: t17
9 P4 o2 O$ Z" E+ s2 @18+ E- Y- q3 ?2 M4 e" u2 e2 R: S
19
$ C/ B- v' G4 U9 Y( v  v20+ [: O" c: w$ o% L3 S  i+ C
  在读取excel文件时,经常会出现数字前补0的需求,例如证券代码读入的时候会把"000007"作为数值7来处理,pandas中除了可以使用上面的左侧填充函数进行操作之外,还可用zfill来实现。7 e& K/ s1 U7 @1 S3 Y
! z4 c5 K$ t1 b3 U: t' P6 Q0 S8 {
s = pd.Series([7, 155, 303000]).astype('string')/ I! s( c7 U' o* L
  o( R0 ^& G: a& x, d" F# `5 A
s.str.pad(6,'left','0')8 k: v) t0 p/ M) N
Out[111]: $ w6 A) g, F. ~: y1 Q& N
0    0000076 ?0 W+ i" L: y+ d2 f) e
1    000155: D4 R  e6 L6 {6 W7 z9 [
2    303000- E- p) ~; C! h" d) b
dtype: string
( N; I% {; w# ]5 n( p0 ]' e
$ z( F2 ]/ e7 H0 ys.str.rjust(6,'0')$ l6 C0 s8 p+ n: G# o+ `
Out[112]:
+ W- g1 q6 J! X& k  i4 S6 P3 \' }0    000007
+ s: \; `( h. ?' S" s9 B" _1    000155
4 {' c# d8 s( b* ]& ?7 ?8 n! s2    303000% m! C# W$ B' T0 R/ p0 r
dtype: string4 K: E) t6 J) o) w

& ]6 V" j" P2 v; @s.str.zfill(6)$ j3 M3 u/ b% O0 A4 _* `9 p# K% \7 A
Out[113]: % V5 z; G2 m0 o' m
0    000007
# B: @" U- E. U% ^' E6 c1    000155
, |! J4 w, n: S( P+ g- Y2    303000$ F; l# t( U! Q$ ~& s) o" v
dtype: string
$ E7 t$ w6 Q" |3 y8 b7 Y. u) j) f& S
( Q' {% v! M7 h" J. H# E1
8 r3 C' V2 U+ L+ C2) I+ ^# I! @6 T( w
3: ]& k% n7 Z0 x. ~3 P& U; G1 A' b
4
6 a  i" T0 B* G. U" c5 Z' B56 @, \2 X7 K- N- d# S! h* i" O
6
: g! n0 P) _: ^3 `' a3 ]7: H7 Q. V8 |5 I0 s& }$ y
8  Z1 C- l; S! w" L/ i% c! E# Q& |
99 A$ B6 [; `. V  a! F
10
, Q9 q& L! R: i3 q/ Q11
' \0 K4 h3 U0 L8 Y  X, x  R, \12
  o5 {  R% S1 F2 `+ H! A& J) l13
9 d1 J; x2 I; w, c) }1 m  Q- _! _14
. z/ p$ F7 H  j7 w! n! ~: z15
3 H( N) h# J6 h+ H# p16- l" V. g6 \5 X* T2 r- b
17
. n% U2 M5 k7 v! @/ |4 A18! {8 ~( o, |; S6 \: v
196 ?* a2 K# w5 q  ^8 O6 c; l
20
& n+ U& [$ }; H* |' L/ @21/ ~% s$ l4 `4 n0 I0 J0 H) _! D/ k
22
5 T; P  B0 U/ _' v4 k8.5 练习% O0 w$ n' X; y  R4 |( `
Ex1:房屋信息数据集, h6 T% e7 q! {, ]& M- o2 L
现有一份房屋信息数据集如下:
7 W/ |2 c" g9 S4 {* r3 b( w" t$ m4 z; q1 y
df = pd.read_excel('../data/house_info.xls', usecols=['floor','year','area','price'])# s' t* g4 ?9 v& p/ w
df.head(3)) Z1 q, l3 ?, e# Q9 v; F
Out[115]:
: t0 ~' j6 P5 J( V/ }      floor    year    area price! z; u- r1 y6 X( a2 G( c
0   高层(共6层)  1986年建  58.23㎡  155万( a+ P3 J' }( F! W4 G/ ~. g1 ^
1  中层(共20层)  2020年建     88㎡  155万
  z! Z# l) f, o. |* z* S2  低层(共28层)  2010年建  89.33㎡  365万9 M' p2 U% X( }& Q: k5 H) a3 K
1
7 E. n( R; ~" r+ l6 b; c* o8 R- z2
2 o" A. \& I  W8 s3
$ O7 E# x& h$ k( z4
4 x1 S+ a  w2 P- F! F) _58 T& V* `# L/ @" Z( y% N) h) M5 e
65 v  T1 d% f0 A5 x  J% c
7% j% ?  t" x6 \1 J9 x, V: P
将year列改为整数年份存储。
! u2 y* D7 X% L+ m, _# A3 D, r9 q6 }将floor列替换为Level, Highest两列,其中的元素分别为string类型的层类别(高层、中层、低层)与整数类型的最高层数。& O) ~  H- ]* @- k) S' u* j
计算房屋每平米的均价avg_price,以***元/平米的格式存储到表中,其中***为整数
: I, t* X2 g! [3 j将year列改为整数年份存储。
0 c( I5 i: l# |1 [8 K"""/ U# O8 x. r+ E* a& t/ x7 g; I
整个序列需要先转成Nullable类型的String类型,取出年份,再将年份转为Int64类型。  D8 D! Y! h8 \; u9 r# d
注意,转换的类型是Int64不是int,否则报错。即使astype加参数errors='ignore'跳过缺失值,6 U  V8 O  m' T( y4 J: E+ Y
转成int后,序列还有缺失值所以,还是变成了object。. l  X. j+ E, T9 [9 [0 T  s' c
而整个序列转为Int,就还是Int类型,缺失值变成了 pd.NA 。7 j# K7 ]. K% L2 t& _
"""
' c9 r; p9 p; p' rdf = df.convert_dtypes()
3 V& j$ a/ R. J: g' J5 S! Qdf['year']=df['year'].str.replace('\D','',regex=True).astype('Int64')* P& O* s: E# X5 w) ^. ~
df.loc[df.year.notna()]['year'].head()8 c+ @; e- i) o7 B

' P! t$ f8 [2 F% d3 P! q* e" n; O& u0        1986) D* q1 C" M% `/ C
1        20203 N# j) f7 d1 H, G8 O- x
2        2010
; M- ^3 l+ P( O9 I( P3        2014; X0 ~& ^* C( @' U
4        2015  A# Z  l5 L* U& N
Name: year, Length: 12850, dtype: Int64
; v  p; e3 j" l% ], E1 D- a' ~5 [
7 s/ o# U/ {2 Z: V1
. U( o- ?6 w) |; `7 Q) A3 I2
) C# n. z4 I- i7 @: p$ a  h3
, H- _2 C# m; `3 u1 O1 u4
- I9 s& c& L4 g+ P- L7 |; ?5
( O+ B- L9 e( a/ X, A8 R% E8 R9 L7 o69 g/ N' q4 _5 j. }4 W- j+ }* Z8 m
7# Z; ?, G0 J7 u- y9 Z
8
5 s1 E) ~) T* T, H9: e. |2 g! o" S! ?
10
2 h7 d$ Y; G! ~( n11/ x! X7 b0 X; b* ?# O5 g' b
12
8 s" n, A- c7 B( m( A/ @13
- Y  d. s% e! s' D14
* S4 T% ^( |" g  ^154 {' h) K0 p& R
16" l9 {! T* [5 v2 S* Z
参考答案:* F" C' }5 g% S$ f6 U

, W, W2 g0 R* X% [  S不知道为啥pd.to_numeric(df.year.str[:-2],downcast="integer")类型为float32,不应该是整型么/ M/ G( `9 I5 n) n- N
5 R7 l2 Y8 s0 @
df.year = pd.to_numeric(df.year.str[:-2]).astype('Int64')
  }+ A5 T4 S4 D6 b$ wdf.loc[df.year.notna()]['year']
& t# r. R, O  D" C! I1
7 I- V9 a$ F4 W+ v/ l2
: Q! P9 y1 U- e2 p将floor列替换为Level, Highest两列,其中的元素分别为string类型的层类别(高层、中层、低层)与整数类型的最高层数。
9 n9 v8 g* i, d9 c2 W% w6 r6 spat = '(?P<Level>\w+层)(?P<Highest>\(\w+层)'
7 p6 Q: i* P) U; F, gdf2=df['floor'].str.extract(pat)  # 拆分成两列,第二列还是(共6层得形式,所以还的替换一次+ b4 [  X, W; S" N" k4 q1 Z
df=pd.concat([df,df2],axis=1).convert_dtypes()  # 新增列拼接在后面,再次转为Nullable类型/ o/ v2 j2 I2 I2 ]
df['Highest']=df['Highest'].str.replace('\D+','',regex=True).astype('Int64')              7 a$ e0 c) U8 b2 H+ w5 Q
df=df[['Level','Highest','year','area','price']]
8 R& N: m7 t6 R2 w, ~4 h% h- Odf.head()  y3 K( m# [. ?( K. @& m' d: ^
* s$ r& [# @1 [; V) W
   Level  Highest        year        area        price
1 _: r8 V* B. Q+ C# z0        高层                6                1986        58.23㎡        155万
7 f9 x& n/ L* I1 N. }& K" E3 h1        中层                20                2020        88㎡        155万2 v1 E/ K. A+ V! N
2        低层                28                2010        89.33㎡        365万. V( K, {0 d' C( \+ b2 {8 s
3        低层                20                2014        82㎡        308万
/ m; T# K  C- p- h' K" l0 A1 P) g4        高层                1                2015        98㎡        117万
4 s0 N$ N- s" W1
: z7 ^% p- X+ C8 X3 N. L2
7 }% S- s, _: f8 f  {. k; w3
$ ?) x& Y/ ?% ^1 }4
( R" T6 F) r$ [) ^) E/ p7 S/ U5
+ B- Q& h3 H, q; R6
8 B1 d' u1 A& C* q) Q& t71 p* ?. `1 G- H7 {, E8 P6 s
8
# N! f7 M$ d/ f/ K& p7 I9/ q8 S7 v' T  [7 m- \& ^- ^) m  @
10
4 ~4 `/ f6 M& f' U. {+ J  q11
4 W* D1 w, l: [/ A8 G( ^6 [12
' W4 j/ p; q2 ^9 d13
6 z) k/ T2 e8 n0 v  H5 U2 Q* i# N$ ~# 参考答案。感觉是第二个字段加了中文的()可以准备匹配出数字,但是不好直接命令子组了3 w4 J7 I2 H+ f  j/ ]  b
pat = '(\w层)(共(\d+)层)'
! q4 W# ?4 u" wnew_cols = df.floor.str.extract(pat).rename(
/ Q5 W2 C' W3 b, a1 z                    columns={0:'Level', 1:'Highest'})+ }, c- i. C, x0 _" s9 w
/ r% d. F- B5 L
df = pd.concat([df.drop(columns=['floor']), new_cols], 1)
* f$ I2 P* M, B3 i% c6 H5 Bdf.head(3)
. g; e5 Q! W5 |+ W- g( n' \4 i1 i, U" P9 Z
Out[163]: 3 X7 S/ J% `) |9 y, w) Y
   year    area price    Level Highest
1 }+ Z5 c+ K' H4 d- \7 o$ `  J: g0  1986  58.23㎡  155万    高层       6
) l9 B- W1 s" J, J4 d: t1  2020     88㎡  155万    中层      20
! `) z* c' ~- I) }& B- @6 r5 e2 K% b* F2  2010  89.33㎡  365万    低层      28
6 j9 k  q/ M4 ?) \6 @2 D1" u. ~1 B4 E4 L/ x
2
7 G' ?3 l% p" ^) h3
7 q7 g( p2 @7 |5 G! K2 e8 E4
4 J- r7 K/ z0 G  ]) s1 M- ^5
4 h' t$ Z; s( J9 f7 P6" g2 }0 E5 @$ t/ _. P# s5 V$ h- t
75 @% {8 }! z: s  s
8$ j; |' f$ z' \9 U# S' [# r
92 S% l: B+ x# {2 V! R! F2 z4 k* U
101 L# X6 c5 A& ~
11" L7 ]1 R, x) v9 t
12+ I1 @1 ^+ i' T4 d$ I! }  |
13, V: f9 n9 M4 q$ q, K7 U
计算房屋每平米的均价avg_price,以***元/平米的格式存储到表中,其中***为整数。& O4 z! \" i6 x+ ]( Y3 s) @# t
"""* D- a2 w3 o2 R: l
str.findall返回的结果都是列表,只能用apply取值去掉列表形式- D! Z9 y+ x3 C% F
参考答案用pd.to_numeric(df.area.str[:-1])更简洁: _& f) D1 M/ L# ^$ P2 ~
由于area和price都没有缺失值,所以可以直接转类型. h4 o* @0 B  n+ b- d5 A: r4 X6 H8 h
"""
$ j7 [' [0 p" _' g. ?# wdf['new_area']=df['area'].str.findall(r'\d+.\d+|\d+').apply(lambda x:float(x[0]))' W& s7 o9 s7 E" x" L
df['new_price']=df['price'].str.replace('\D+','',regex=True).astype('int64')# Z: \- s/ u8 o9 i
df.eval('avg_price=10000*new_price/new_area',inplace=True)- f3 i/ k  Q9 S
# 最后均价这一列小数转整型直接用.astype('int')就行,我还准备.apply(lambda x:int(round(x,0)))7 A6 K# O9 {# W  ^
# 最后数字+元/平米写法更简单
: u( G0 v0 J4 K$ q: z; H1 ~- `) Pdf['avg_price']=df['avg_price'].astype('int').astype('string')+'元/平米'
* c4 _2 M1 E$ U- J1 c2 _8 X) hdel df['new_area'],df['new_price'], [5 F8 k  E5 l8 ?6 D, u: o5 `! s
df.head()
: z0 O" \1 |7 d1 T* ~  [- r! V( V+ F2 z, ]% v2 `* g
   Level        Highest        year        area        price        avg_price$ @8 m: a; L9 T2 o# x+ x2 b
0        高层                        6        1986        58.23㎡        155万        26618元/平米6 s/ e. Z# r+ Y) u0 |8 d
1        中层                        20        2020        88㎡        155万        17613元/平米
9 m% G. f; e8 p# c$ {. f& `2        低层                        28        2010        89.33㎡        365万        40859元/平米
; `" |" e9 b  `& q3        低层                        20        2014        82㎡        308万        37560元/平米2 j) h; x& k( }5 y8 {+ x: U& Z2 }5 u
4        高层                        1        2015        98㎡        117万        11938元/平米/ C! b9 d6 v4 a7 o& o
% L# c# U7 V0 p. i) O
1
! q& j1 g) v2 n; L2
' T: \2 ?+ L4 [9 \. X2 O2 g, R36 K+ y/ w$ B2 a7 x% b6 K  z
4' }8 l5 f5 M  `- G) H$ ^
5
& x1 t& x& s9 M" ]' K. _1 B' b6
5 L% l8 y4 a5 ^5 d7
6 f- a; f) k- b& M86 e) X- W4 z- b2 T$ a( e; k
99 y2 d+ X# L+ R0 j4 ~
10, b8 j1 ]6 N- q. k# F
11* j8 X# M7 V+ K6 H. F
12
1 y/ o$ i8 c3 u  v" W' \) n/ W13
0 S5 c- A  @3 [. Q4 x3 V14
; o6 D8 `* H" h15
) Z0 n% r8 k9 L1 n- D  }0 d16
. K% P; t% n: k- \. _0 `) d+ v6 G17
$ U, U3 S7 ^) w4 P186 v* _* k7 _, q5 k; E( x& Q8 ^9 \
19
3 r+ Q0 b  ]4 y; _) ?# S& E  n) ^20. o" S9 f" [1 m/ y- n) m- N- p; J
# 参考答案4 U3 d# h5 F- ^( z  l
s_area = pd.to_numeric(df.area.str[:-1])" f5 Z9 ?7 `8 o3 W$ Y3 ?
s_price = pd.to_numeric(df.price.str[:-1])
$ o- Y1 `8 ~% ]. ]$ z6 r6 v8 kdf['avg_price'] = ((s_price/s_area)*10000).astype(
  @: U% v1 m0 Q. b3 ]                    'int').astype('string') + '元/平米'
( O! }, |3 Q# p$ w  W& _2 e8 M! f+ J3 f1 e7 x* Y% y0 ?9 s
df.head(3)
/ u9 X  {/ t( _$ dOut[167]: 9 Q# J" |- ~2 M4 q
   year    area   price   Level Highest  avg_price
4 r2 B- d6 x& [: S: {6 T0  1986  58.23㎡  155万    高层     6          26618元/平米+ h0 x8 n) @  w$ @4 [
1  2020     88㎡  155万    中层     20          17613元/平米
. d" g% t2 S7 n( \  Z8 n2  2010  89.33㎡  365万    低层     28          40859元/平米
0 U! K- V! b3 _2 e# v10 m1 C6 U' n, ~( `. Q5 m  b/ N3 w
20 v& X/ M3 x# z
3
$ X8 ^* A9 ^' i5 u. Q; [% C4
0 u. J% A3 }2 V. H+ r" d5
) _: Y3 q) v+ p5 |, s67 \4 ~- Q& `% V* J1 s1 |) ~2 V5 i
7
+ ^+ A1 r; O& B9 B: w8) C# k& e1 o/ C4 e
9( O% ]; X4 ?7 p. [/ Q4 j
103 Y# z( G  `3 S9 C. W3 w# y+ C
11
! z9 k* M3 n! }( H12
% o6 ^$ B$ M8 j6 QEx2:《权力的游戏》剧本数据集  }# i! f  w# C2 Q4 ~2 `& e
现有一份权力的游戏剧本数据集如下:
6 [) ?) ~4 j. d0 r; u" W! w/ n" a( D
7 v( j/ y  ?  e% j0 f: M5 gdf = pd.read_csv('../data/script.csv')
0 h6 L! M- Y9 P. v4 E' h* Y" odf.head(3)7 L- U7 U! v8 |8 z+ {+ v

  c& y9 _3 _+ f4 FOut[115]:
3 H, `" F8 S1 r4 x" E: N+ SOut[117]: $ S) N; y5 g* H5 x3 h  x
  Release Date    Season   Episode      Episode Title          Name                                           Sentence& L/ n& Q) c, b" s6 E8 {; x( [! p1 E
0   2011-04-17  Season 1  Episode 1  Winter is Coming  waymar royce  What do you expect? They're savages. One lot s.... a( V( [: K  D: p
1   2011-04-17  Season 1  Episode 1  Winter is Coming          will  I've never seen wildlings do a thing like this..." P. Z) B5 {; S. k( ^3 K6 D! s
2   2011-04-17  Season 1  Episode 1  Winter is Coming  waymar royce
- d# d% E+ t) T4 Z2 L2 ^* p13 V+ q8 Y; |' @+ ?6 e; Q% I
2/ Y; X( z/ n& e% c6 v% V& U+ J3 `. f
34 g, M" u0 }/ `6 }% W! g0 J
4
0 Y3 n1 c$ C7 u* S2 Y2 [& ?# J9 D; `5  Y' E) ?* b' ~3 V  C4 J9 v
69 y8 R/ Z1 c3 b- i0 a
73 J8 j; Q& I' P2 C$ s4 b9 c
8
+ ]2 D- v9 z/ l4 X% k. R/ f0 g9
" O+ G6 C  \5 a; g/ {' W计算每一个Episode的台词条数。, W% f; d/ u4 P  g$ J% V
以空格为单词的分割符号,请求出单句台词平均单词量最多的前五个人。
! V' ], J" @6 L6 N若某人的台词中含有问号,那么下一个说台词的人即为回答者。若上一人台词中含有 &#119899; 个问号,则认为回答者回答了 &#119899; 个问题,请求出回答最多问题的前五个人。8 b- D8 t: N& x. A- B* N! t9 q6 g
计算每一个Episode的台词条数。
3 u+ [9 O3 g) H; Ndf.columns =df.columns.str.strip() #  列名中有空格
% z: o5 d- j* l  M2 U2 Xdf.groupby(['Season','Episode'])['Sentence'].count().sort_values(ascending=False).head()  M8 h$ c4 X0 ?7 L& ?) w; i

9 q( j- \  T4 G* Zseason    Episode  
7 e, K4 O* s0 m: K! y. oSeason 7  Episode 5    505  H: T. V: _& o" h
Season 3  Episode 2    480$ u& b- R7 O- U  ?& ^
Season 4  Episode 1    475
' W+ O, _% v+ a& SSeason 3  Episode 5    4407 k; B4 [; c8 N
Season 2  Episode 2    432  F  ~  Z$ s0 |- `4 ]; Q
1/ k- u# G- C# r; h: y4 j
2
& Z3 _* e9 v. w1 \$ c3
: M3 l2 e% K* |5 g5 w4 ~0 W+ x4! }2 o- N0 c, r, A" m
5
+ E2 K8 K& M9 [  w5 n6" \# S& z/ g9 {3 S
7
. _9 X  _+ L5 @: ?) o8
9 m8 }+ Y% U( x2 c9. l; ^( r; q; @! o* K% B
以空格为单词的分割符号,请求出单句台词平均单词量最多的前五个人。% y, A; ]2 W( L/ m
# str.count是可以计算每个字符串被正则匹配了多少次,+1就是单词数/ u$ S: }2 {: n/ Z$ d2 d  `
df['len_words']=df['Sentence'].str.count(r' ')+1. d! Z; d- Y4 I' V; G
df.groupby(['Name'])['len_words'].mean().sort_values(ascending=False).head()9 P% w% M7 g& B4 M! ^

# ?* ?" ?+ b" m( m$ N+ L$ {Name
6 `' S3 Q- Z/ X3 pmale singer          109.000000
( g) X- c* q8 U3 u3 U% C% Eslave owner           77.000000& p- @1 H2 ]2 W# u+ \3 K# C
manderly              62.000000
0 y1 m6 D. m; J4 ?+ s$ ]lollys stokeworth     62.000000" b/ `, ^1 R6 W8 z
dothraki matron       56.666667
: m. C* ]" A( D+ t' MName: len_words, dtype: float64/ V3 w7 w3 G# b( \1 n
1: A8 r/ Z4 u! M5 u& i+ d1 r
2% l4 @* j+ _0 U4 X& b% r9 h; `9 _
3
! s" Q" F4 ^7 h1 K4 ~! Y% }4
- R1 G, ~/ `, `" s4 o5
8 m3 ^  k: r. F5 e/ p6
  }# Z( `( V5 {0 G5 N7
$ T( U+ V: g8 }. J5 Y9 J& V8
* j$ d: ]9 k$ T' _9
; q* [8 D# v% l+ N6 V10' s+ x3 M$ n- Q- B
11! G0 Y. T+ i( Z1 J
若某人的台词中含有问号,那么下一个说台词的人即为回答者。若上一人台词中含有n nn个问号,则认为回答者回答了n nn个问题,请求出回答最多问题的前五个人。+ A% C9 A; u* [; N" n- h" b& y
df['Sentence'].str.count(r'\?') #  计算每人提问数
0 d  v& O% C) `' e7 X* Vls=pd.concat([pd.Series(0),ls]).reset_index(drop=True)# 首行填0
; k* k7 V: d4 Vdel ls[23911] # 末行删去
! t0 o  H5 ^3 u7 u2 pdf['len_questions']=ls
4 q2 n- ?! p8 b: f) o! z( Odf.groupby(['Name'])['len_questions'].sum().sort_values(ascending=False).head()
) E1 L( c2 F: u. X2 p! J$ C+ `1 U
. w3 k- O5 q  _4 w/ q0 RName
. |! {3 R; P2 jtyrion lannister    527
1 ^3 z$ {5 Z  @( M5 `jon snow            3740 L1 y; _( u$ p! H3 }/ e9 H
jaime lannister     283/ p& ^: M. w7 i: V6 c0 U
arya stark          265/ w( H5 Q& v( E) L
cersei lannister    246, |8 m- {( i6 g# C( @& X$ R
Name: len_questions, dtype: int64
7 e; F! W& r9 c' n6 A/ S% X
* N5 Z" z! [7 T* H' s) t# 参考答案
$ [" J  F: t0 ]  q; r, }s = pd.Series(df.Sentence.values, index=df.Name.shift(-1))0 |. f: J% i  X; e
s.str.count('\?').groupby('Name').sum().sort_values(ascending=False).head()
1 r: @) D& @. h4 W# E" L& S) ?4 B9 I$ q9 O5 I
1' ], O" l- F0 u2 I  z9 R* c3 U
2# R6 V( j8 H% V6 t) P& Y+ `
3' h7 o- X' z' ^2 Q& v. Z
4
; r4 P2 n9 H/ Q" i. s5
1 L! y- x, f8 V" j6
5 t( M: ?' i- i; y9 O7) ~7 s9 A8 U. J2 w& ?- L3 ^' \
85 R+ w2 ]% M+ u; g
9
% v% m+ b% C$ i/ A- E- `" y103 }3 _. |0 F/ a# o
11
8 @+ l. d+ ^0 y. {0 n! S! o1 W+ S0 p  C12
7 e8 i4 w) T- L13
' M& a$ F) A# T, a" \3 t14
. r+ e' l+ x% {/ O: _) _15  J1 l7 a% U5 l
165 M, H. Z) p& G2 e. e1 {9 O
174 V5 |$ y+ n0 [" _6 I
第九章 分类数据
+ ^9 `4 c$ J2 Fimport numpy as np
% g2 [8 c0 v  G% A. l: h& Y% q2 Yimport pandas as pd" _7 P! P) `4 N$ U; E
1
0 _0 ^" n( r4 `2 }2
. K8 z  k) }% K8 q9.1 cat对象) U7 X0 J1 X& ~  N
9.1.1 cat对象的属性
9 W8 j  Y5 r9 ]: c% P2 F! v5 m  在pandas中提供了category类型,使用户能够处理分类类型的变量,将一个普通序列转换成分类变量可以使用astype方法。
: K$ \1 V& O' `5 ~! e9 q6 F
) B. I' s/ J* `' |df = pd.read_csv('data/learn_pandas.csv',
# s( @( [5 n7 \5 s* E     usecols = ['Grade', 'Name', 'Gender', 'Height', 'Weight'])8 [7 I% d% z" X, t0 x8 u
s = df.Grade.astype('category')
4 e4 z9 @% ~5 e, m' A) l  s) L: `, s: ^/ u" o& N
s.head()+ `0 Q2 P1 I) P: C4 }1 I
Out[5]:
7 k& n( G, v  t2 f, [6 C( N0     Freshman! b7 J5 K! k" ~3 f) I
1     Freshman0 S5 ]6 G# D" D8 C$ I/ G9 h
2       Senior
9 o) I& g# R3 Y: F3    Sophomore3 {, o  l6 |1 i) o. H' m
4    Sophomore
; g5 t2 q/ L& d" B7 ^/ E' V) hName: Grade, dtype: category
7 f/ s6 J8 k( Q& mCategories (4, object): ['Freshman', 'Junior', 'Senior', 'Sophomore']# [9 b+ Z: K5 ~& w6 c, F, {1 a7 S
18 _4 O$ \' k9 x8 A; y
2% L# @- z! J" L5 d9 n5 I: `
3
9 @# R2 E1 h* z8 d( e0 h4
" K1 D. M. B" k" Z' u5
8 e' s2 y$ F! P% X1 f6
. K# W# g: A" ]# j# |+ |: E79 j) \0 D" y/ y, y
8
, ]( \! T- V7 C7 s8 ^8 |9' w- K8 ?( M9 u& ~# Y+ o. _/ k
10
+ w$ {+ f, ~: f* o2 C11
/ z0 Z  y  W; |8 n! ?; e4 g- z12
% r; ^1 J8 _! \- A/ K  E  g: m13
1 \7 I; z& ~: |1 n8 g  k& H  在一个分类类型的Series中定义了cat对象,它和上一章中介绍的str对象类似,定义了一些属性和方法来进行分类类别的操作。
7 I: t/ C% e* X0 G* j" w8 M% K! p* w' I  ~2 P# u) m, Q7 q
s.cat
/ Q5 t0 ^" V) Q& \Out[6]: <pandas.core.arrays.categorical.CategoricalAccessor object at 0x000002B7974C20A0>
$ D5 A+ q* Z4 o! j  M8 z1' n3 ^1 K. o; w
21 e( ^$ X2 C( p. K- P. e# s' P
cat的属性:
; K- k) a3 V0 ?
4 j3 B# U% r/ P+ K  \cat.categories:查看类别的本身,它以Index类型存储# {5 `) l* {9 h4 Z
cat.ordered:类别是否有序3 C1 P9 A1 x! I$ E4 x$ W) S" p+ Z/ A
cat.codes:访问类别编号。每一个序列的类别会被赋予唯一的整数编号,它们的编号取决于cat.categories中的顺序
( h3 H" Z. k5 \s.cat.categories
, b) ?/ x, \3 _% g5 t* k/ \- m+ oOut[7]: Index(['Freshman', 'Junior', 'Senior', 'Sophomore'], dtype='object')- l+ D7 u3 [& b
- x# t; o6 R) a. g
s.cat.ordered; y( L- x% d( x! Y+ _  A7 O
Out[8]: False+ H4 ~% }$ [, X" C+ K  I9 C
5 e! c( N3 y: G1 g9 K6 N
s.cat.codes.head()# d+ w! b2 B% M$ m3 L
Out[9]:
! i8 L1 h( s+ K; Q9 w0    0
+ X. O/ a3 a  t' i1    0
, M9 w% a; T; H2 G5 c2    27 Q0 U2 ]+ Q1 a0 r7 c
3    3: \5 b! k4 @$ ^1 k+ q
4    3) f5 c# _, A  P) n  r, L% x
dtype: int8
/ L- p3 L( ~5 |. u, Y7 S1
. ^( o0 k1 Q% P3 Q+ K, U2
7 s# n) k$ e4 {4 @$ \5 [' H6 W2 k$ ]8 {3
" b+ b/ E  K: c: Z- R( ?4# l, K9 A3 }! \- _* g; L4 C
5
% ?& `( Z/ Y% Z! t6
0 I2 N# H' M1 _! B0 x& Z74 j( U; Y3 i# C
8) ?! g0 c) u- W/ c- X' ?1 w0 ?
9
7 ~/ F! o1 X: s  v: T$ g1 w3 w10
& i' ]' m7 Q" X11
4 j( l# P5 p7 M! ~' V( G12
1 x, T  X) G, N1 J0 N13; V  O) ~% _2 u' e2 t! r& I' T. [
143 O: X8 M" [9 B5 P  y$ ?  y4 K0 Q% s
9.1.2 类别的增加、删除和修改9 q1 h- N; |  X8 t% G. I" P8 v( _$ v
  通过cat对象的categories属性能够完成对类别的查询,那么应该如何进行“增改查删”的其他三个操作呢?
3 _5 E- Z5 N1 R4 J9 }, p- [6 C- g/ y8 D$ |+ X1 I
【NOTE】类别不得直接修改3 ?# n# {9 t+ F1 y' E
在第三章中曾提到,索引 Index 类型是无法用 index_obj[0] = item 来修改的,而 categories 被存储在 Index 中,因此 pandas 在 cat 属性上定义了若干方法来达到相同的目的。* P  ?% x5 H3 B9 [0 @2 |/ P

7 A9 ^0 h& R2 I$ {* u9 Zadd_categories:增加类别2 d) W$ G  ~' e  n# M$ w; R! e
s = s.cat.add_categories('Graduate') # 增加一个毕业生类别
, X+ @$ x' q5 t9 i( G" X! Vs.cat.categories) C1 d/ a6 x# z, e
. e2 z! ]/ p- n% X6 z4 e3 a
Index(['Freshman', 'Junior', 'Senior', 'Sophomore', 'Graduate'], dtype='object')
4 ^2 Y! i/ Y0 Y( A4 v! X0 B11 z, t6 @+ m( V6 |2 h# z
2
# ]! \1 u/ T/ e8 A4 n- X. k% N3
8 ]5 `2 _! k  H/ Z( Y4
# n6 V( I4 i( \+ X8 y! q9 eremove_categories:删除类别。同时所有原来序列中的该类会被设置为缺失。7 T7 Y3 J( H+ J5 S; T- Q
s = s.cat.remove_categories('Freshman')
: M& r% |4 M2 W7 N4 Y) _4 e" n& ]/ X0 s
s.cat.categories
; B5 Y2 F3 q. ^1 s" z; OOut[13]: Index(['Junior', 'Senior', 'Sophomore', 'Graduate'], dtype='object')2 W$ W& u- j! e) P: L

; I: T9 R8 r( w+ us.head(), N) w# H6 l" d6 v! c- _
Out[14]: / t$ K: {; D& T
0          NaN: Q$ Z: L. U0 F' X
1          NaN
6 I1 _" S% m  ?# `8 ~2       Senior
8 w# k9 ^& U% R5 U+ e. n+ [1 w3    Sophomore
- w3 e4 E* b* A( V6 d, p4 F4    Sophomore4 S' F/ t3 d/ a
Name: Grade, dtype: category
; [1 X3 Q8 r/ ]* t1 WCategories (4, object): ['Junior', 'Senior', 'Sophomore', 'Graduate']( y5 m) \6 V4 u4 J
1) S, j: t& a# V- J4 T
2
3 z) r8 Z" r4 ?8 U34 ^4 m- h% L% T+ e: n+ v) o
4
6 A, N, I/ d# C: V# F5
6 k6 ~2 K. c( `9 a0 P6
" X1 I# }8 J( m+ ~# ~7
$ e6 b: y1 ]+ y7 e% E& ~. O87 _% i( @- U  z2 Z' k# \7 ^" k
9
) j6 n# f/ v- v" o2 h9 X, ~10
  }2 |' k3 P& v) w2 i11! y5 |5 C0 O* R0 H
12
7 F4 ^( b2 M/ t& P13
% Q1 K  O, [) k- K14+ D$ o; p3 t) S6 ^9 x* i
set_categories:直接设置序列的新类别,原来的类别中如果存在元素不属于新类别,那么会被设置为缺失。相当于索引重设。
3 {3 V  _# Y! ls = s.cat.set_categories(['Sophomore','PhD']) # 新类别为大二学生和博士
+ p" `6 S0 z" O& I* o* Z0 b) Ws.cat.categories
8 ^: q. ^* p! T0 hOut[16]: Index(['Sophomore', 'PhD'], dtype='object')
- \& o! {/ q- z0 y8 ?/ Q9 F* D9 k; l$ i! G% h9 i1 \
s.head()
8 r* A' d$ e# P- h% U5 uOut[17]: 0 O  R; f3 V6 t& k$ \- I
0          NaN% s* p' v# _* I9 _% s1 T
1          NaN: S" E7 o9 G4 i5 x; l: ]! `+ o
2          NaN
# ]- i8 B/ [- h. l) `6 ]3    Sophomore+ n0 I1 \  t3 w, k) e* i) v3 k( q
4    Sophomore* G, W, N7 {7 P; x
Name: Grade, dtype: category% Y  M* }; W9 _
Categories (2, object): ['Sophomore', 'PhD']- t" w6 F* Z' V/ y: t
1/ o$ q5 x7 m5 A  N' T
2
& {* o6 Y( ~6 P' C0 s* ^) t3) L. h! g) g; Z/ w/ j7 s3 s
4; |- ^4 w3 s8 z; q) R1 g2 |9 x
5
' B6 W: j* g" z1 @4 b5 I6& b. H8 h$ o. e) G' i
7
6 b, R' l  C, e: P7 v* x  y) y$ h- C8
% G4 t3 E% P' j  d9) V7 l8 b7 D3 j# S# f& R) ^
10
, P- a+ x6 [5 P, n' l; Y* Q  Q11
6 u8 ]$ {$ i8 L- c12
, z; O& i1 }7 z13! I" n+ [. n# p6 \
remove_unused_categories:删除未出现在序列中的类别3 y1 T& o$ X9 J* _8 k& n& R
s = s.cat.remove_unused_categories() # 移除了未出现的博士生类别5 ^/ v0 p) q' v. _3 i' W9 Z. S: j
s.cat.categories; `  O1 E4 ?! g8 ~6 b0 V9 \

4 i  e+ x. f6 Z* U* F$ u) A2 xIndex(['Sophomore'], dtype='object')8 ~+ g, L/ V0 ~' F9 \: e
16 L6 A. C* r: G( ?9 J& c; A
2& i& L* E+ d/ M: r/ _
3
1 O$ r: X" r. I1 {42 m) ^2 p7 M. z/ X1 T
rename_categories:修改序列的类别。注意,这个方法会对原序列的对应值也进行相应修改。例如,现在把Sophomore改成中文的本科二年级学生:
1 L7 V- g: q: g$ |' \s = s.cat.rename_categories({'Sophomore':'本科二年级学生'})
' E1 j$ F% `3 R& o2 `: L- D; Y$ r/ cs.head()
$ h5 u: z0 N. m& {; Z2 ^# K4 c! `7 ^# F, q9 p8 s, C
0        NaN
) V4 K# _$ [9 [& C1        NaN: m% E* w) H' N# E; r
2        NaN5 x0 i; X; ]: ~* L) n8 m7 A
3    本科二年级学生
0 A1 q. o# D. O" P4    本科二年级学生
* u2 p& r1 N, S2 r8 z( yName: Grade, dtype: category6 `1 N& \6 N4 D; {- |3 z; j- z
Categories (1, object): ['本科二年级学生']9 U6 s6 i  n0 T# z( _
10 U9 Z( Q  T5 r! ?2 c; }& o( R, K
2
+ N2 N+ {: k5 k. n' o0 k3
" K( T/ G' e2 ]; i$ y; H; Z4
/ J; O# I1 T6 ~' ?+ j56 I" ?2 `" A* |* L* {
62 ~8 r( f, U& t4 J7 P
7' N3 b3 V" t3 p/ D) `. V
85 f, O& D7 J: c7 G- M  D& A& ?
9: U8 b+ @; E8 }8 s1 H* m8 f# ?3 U* |
10
8 ~8 g) V- I2 F0 |/ x' b9.2 有序分类
' ~' w; y( n% \7 T* x+ f9.2.1 序的建立
4 ~9 ]( G" O+ L3 a  有序类别和无序类别可以通过as_unordered和reorder_categories互相转化。reorder_categories传入的参数必须是由当前序列的无序类别构成的列表,不能够新增或减少原先的类别,且必须指定参数ordered=True,否则方法无效。例如,对年级高低进行相对大小的类别划分,然后再恢复无序状态:7 N. m7 D5 f' I) W4 f. u

( I1 w) X" P0 P7 v+ I. qs = df.Grade.astype('category')
( H) ]4 n6 R! t' |s = s.cat.reorder_categories(['Freshman', 'Sophomore',
* o. D1 [3 J7 K* U( w( O; R                              'Junior', 'Senior'],ordered=True)7 d2 y; [5 ^. b' N" U0 `1 k
s.head()9 y( o) s% a6 G, T" I  b9 Q
Out[24]:
5 D9 \3 ~. |3 h8 j% A; x5 E0     Freshman
- _4 r+ u2 }* \( f9 y1     Freshman# {! v( K3 B, e
2       Senior
) D4 \3 a1 ]$ a0 w+ O9 j, r3    Sophomore( F2 Q" Y% m! k4 n5 m& T! H- }
4    Sophomore& p) R# N( K' R9 k. m9 E
Name: Grade, dtype: category0 r: d/ z3 [( T1 V0 e
Categories (4, object): ['Freshman' < 'Sophomore' < 'Junior' < 'Senior']
  _, q& \% r- u( U7 G% D% ~
2 h7 m7 l( V, P( }$ y( f% Us.cat.as_unordered().head()
; b* [) A6 X' K3 ~, Q3 YOut[25]: : D3 H. e+ F/ H6 W" B" W
0     Freshman
( P5 L, w4 d2 o2 U% j1 C/ s1     Freshman
0 t  F5 u4 q4 J8 ~5 [2 U2       Senior
4 f2 s" Y* v5 Z$ \3    Sophomore: ]( [  R; P( `: c/ V
4    Sophomore: d' i3 Q; {# a! v( A
Name: Grade, dtype: category
1 o$ i7 Z! j0 UCategories (4, object): ['Freshman', 'Sophomore', 'Junior', 'Senior']
4 C6 f) }! L# h) d5 h  T4 j* X0 j9 i( x& O9 x6 w- A" k
1
( r9 S! {) X! f" S! p0 i- ?2
9 a1 m* ~2 g2 \! W: L: r3
0 T$ f. M0 L* A( o* e8 e4 I4
% E& x) D5 {' e9 P. j5( W' r6 S( l* B2 ~
6! n. D2 n9 p1 H) x, t5 }* Y
7
$ q3 w( }! {# i( F8* ~' M0 c/ ~/ S. {
9
' {/ u3 W4 l: ?10; Q! H# N+ \! R/ ~- F6 G
11
( V4 P5 I0 ~) {( k2 G12
) W4 g8 S2 R  {5 _6 }; t137 @" F. a4 m$ s- U0 S1 r0 Z
14) e) o& x# t  |0 w: u+ Z6 r- d
15- Y7 G# C, @& T; z6 S; _$ O
16
& }) k5 }% c( K5 {& M% Y* i17+ x* P( }) S& s0 k! l# d8 l, z  h
18
  }: H' w4 h* R9 O0 |( @4 o3 N19
4 o0 H9 b1 \/ E! U( r20) c% {& Z6 s8 c8 w2 P! _
21
' \+ ]$ p  m6 d% }22
' L  x9 Y' L0 L/ j  如果不想指定ordered=True参数,那么可以先用s.cat.as_ordered()转化为有序类别,再利用reorder_categories进行具体的相对大小调整。+ ^8 K  q# {* h! P0 v* L

$ s0 E- \. C3 ?- n9.2.2 排序和比较
9 j* a; G1 R  G/ e, n9 e9 D9 a! }1 n在第二章中,曾提到了字符串和数值类型序列的排序。前者按照字母顺序排序,后者按照数值大小排序。9 t  A- {# x4 G  N4 ~" ~
( f% B3 v# n8 H: A  j- R
  分类变量排序,只需把列的类型修改为category后,再赋予相应的大小关系,就能正常地使用sort_index和sort_values。例如,对年级进行排序:
: \0 R& K0 n  P. g) g
# c/ n' J5 o% ]3 Qdf.Grade = df.Grade.astype('category')
) v& K4 Z9 Q( g% n3 y) a- v9 Edf.Grade = df.Grade.cat.reorder_categories(['Freshman', 'Sophomore', 'Junior', 'Senior'],ordered=True)
% o/ H# s: F, H* g% n) Wdf.sort_values('Grade').head() # 值排序1 B/ i4 j! Z$ D" D+ @$ n8 `3 S
Out[28]:
3 f( b2 y6 X. b7 s9 K) D4 K        Grade           Name  Gender  Height  Weight
  E( Q- u' o* a' S7 H6 z  t0    Freshman   Gaopeng Yang  Female   158.9    46.0
0 m, @8 g0 z: r! r7 l% d3 [) V/ S105  Freshman      Qiang Shi  Female   164.5    52.0* s; q& ^6 n3 @/ Z0 J
96   Freshman  Changmei Feng  Female   163.8    56.0$ Z) s/ b; U6 \& A( ]" Z; J
88   Freshman   Xiaopeng Han  Female   164.1    53.00 }1 k/ I9 j3 I9 I8 T4 }# W
81   Freshman    Yanli Zhang  Female   165.1    52.0! e) F9 q; O' g9 F
  U9 C- [0 X- x* p5 m8 L& |& ~
df.set_index('Grade').sort_index().head() # 索引排序
: c7 I: C8 v, V0 eOut[29]:
% ?& {1 Q7 Y6 V# ?4 G1 z. z+ h. t                   Name  Gender  Height  Weight  R( n7 ^* s( ~% E' d& e
Grade                                          
- _7 @; }* g7 {: m" \, [7 XFreshman   Gaopeng Yang  Female   158.9    46.0
: ]+ M1 G% w: O; `& a2 h5 TFreshman      Qiang Shi  Female   164.5    52.0) ^- Q' ]& b$ g, P
Freshman  Changmei Feng  Female   163.8    56.0, A4 T" {8 P( {$ b  e9 G
Freshman   Xiaopeng Han  Female   164.1    53.0
; o- j4 G; Y& \( U% UFreshman    Yanli Zhang  Female   165.1    52.08 \# n) e# O" g0 f

' Y  F4 e( b3 O" s" T1& \: J3 L, f. @2 J1 M% w- J# K
22 v7 X6 ^! w7 x) H. d9 v* N9 Q
3
; v4 n" z0 i: L" a4
) i: H1 ^7 ]" k/ M; m& f5, L! B0 H+ ]4 f( u
6  g4 E" R9 T5 j  e/ O
7
- a1 m# ?( s- u$ {1 ?/ N7 X3 p' i8/ ?/ o1 D  `. A$ ]4 ~; R
9) p( u' ^1 B. S6 s% u4 d3 ?  i
10
  ^+ ~2 p! i  i0 O9 s8 N3 E11
9 A: K( K& s; r' P% H" m12, @  y' N$ [# {
13
$ ^7 p" [: {, x, o9 x7 i& D14) Q6 z. `- g! S
154 }3 V6 o  K' S# g4 s" a& D0 B
16& n( n3 M7 C1 T2 Y7 k
17
3 k+ J* K. H; }1 ^' v& m) r18
4 J5 G2 {2 ?9 o19
+ Y  ^- f$ G- y0 m20
, ^/ O, G/ l8 f% j1 Z  由于序的建立,因此就可以进行比较操作,方便后续索引操作。分类变量的比较操作分为两类:9 L4 d4 p2 u0 G
2 v# N* P; K8 Z9 m2 F# e; W
==或!=关系的比较,比较的对象可以是标量或者同长度的Series(或list)。(无序时也可以比较)2 I# \9 n2 n, j% K6 v
>,>=,<,<=四类大小关系的比较,比较的对象和第一种类似,但是所有参与比较的元素必须属于原序列的categories,同时要和原序列具有相同的索引。1 s( @( v  Z. p; W
res1 = df.Grade == 'Sophomore'- Y  H3 _! e' `# J
! H& y- v6 y  N" ^' Y" B0 l
res1.head()
0 E. w0 t4 J4 ~2 ~: f; |! gOut[31]:
/ a) E" H: f4 x: A. i) W- M5 \; P6 Z0    False
; c- N* Y" F) ~8 P; K$ E, d1    False
& O, L1 c/ }2 Q7 y- I& W2    False/ ?8 z& C* c# `% s, \% f
3     True
9 N8 @6 o& @+ ^/ O' U( q4     True; `( Q% j, l1 H2 Q/ U! Z8 |5 A+ \
Name: Grade, dtype: bool
/ }' o, Y. V0 ^! w
. V" s9 c" Q. }  O5 Sres2 = df.Grade == ['PhD']*df.shape[0]" ^, i3 ?. x" w* G0 Z
) N7 r4 l  T5 }& F% _' i
res2.head()
0 R! Q3 ]% s/ P/ Z) c) BOut[33]: 1 I; @$ E* w8 i
0    False- ]9 |' m, V. T  e  Q3 G& i
1    False1 b9 {9 U1 W. a: z: q
2    False% {% d* z6 p! J# R! y
3    False' Z0 a5 ?1 K4 m) C5 O
4    False
& c5 }+ |4 e/ _! _Name: Grade, dtype: bool
( K- G1 ]( u9 B. R# M9 H. Q
* b+ X% ~* T9 B$ q' a1 eres3 = df.Grade <= 'Sophomore'! S# y/ \8 ^+ \; Q

) j: o- R! s) h4 B  x1 i, Rres3.head()
5 B3 H/ o  u* jOut[35]: % w# D/ c1 N! Q1 d$ J' B  s8 v+ i
0     True
- X. v, x: k. R/ l  @1     True0 h1 N) p4 c* W- [
2    False0 M$ R# K' a- v" b, ~
3     True
+ V. S: N  f/ R. u; I8 I, u4     True+ X" s8 @& B! \
Name: Grade, dtype: bool
7 F: P# ]4 C" C# h
2 R1 B1 t; x; i# sample(frac=1)表示将序列随机打乱。打乱之后索引也是乱序的,直接比较会出错,必须重置索引。# v; t5 R2 }: m/ \$ [8 ?
res4 = df.Grade <= df.Grade.sample(frac=1).reset_index(drop=True)
  N) t* j+ Z. |2 x' J% n% ~; M; p* H# \( F$ y9 I& |) C. c
res4.head()( @0 X) r! D! v  i
Out[37]: % Y- o, E( d0 s  I/ A" n; n
0     True3 }& J4 O: L+ j) H+ X
1     True1 A( m2 X; V/ C1 j
2    False7 z, G; }3 F! z) N" ]7 e' ]( X
3     True) \1 p! ^# z8 Q0 L- X: r
4     True
2 U( H, Z* S" d/ V5 n7 TName: Grade, dtype: bool$ S8 @1 \  _5 e! H6 T
4 }! [# M" _7 u+ L# ^8 v
1
  S+ @, X4 F$ ~# k, E2
% B) b5 ^- G& C- u3 U( a. F3
5 r7 d8 Z+ E/ |0 f46 [, X2 G. i( v& X
5. z6 F: r$ u5 L
6
8 ]" U0 O" a/ T71 }  v* ?- ^/ ~3 ^* ^& I  j! \
8
% }3 E  S; [2 h90 `8 \; i' {' w; z$ m4 P
10
$ k, ^, @1 q+ \11
  f( J) k: \5 W% A+ p3 g125 v4 f4 v7 f# ?1 O1 D. a; W! w! x: x/ ]
137 w8 B! j0 ?6 D
14
$ s; {& a5 K- V& F6 I15( X" \  R3 _- W; m
16
- Y. ?! B$ B8 h3 o% l17. e8 b$ D/ B5 U  ]6 V  q5 I
18
- l( @( `( c3 K( [! @; P19
+ b" N2 |: B0 I+ a% X. h20& _; W4 B# z% [) K# Y
21
6 A/ ?6 N& g" @7 U9 B22- i, c" J" y# S( l8 i$ X
23
% L$ m5 R8 I' G243 N1 U. M( S2 m7 o' c: I9 _( r
25
# t6 J7 n, V& X1 R8 |267 H9 H% Z% X' Z7 k1 M" w) ^: p
27
2 k) k, B- Z7 x4 m28: K! H# D% Y: J0 K
29
& E; i# [# s% n2 k% X30
. a# N" P1 X8 x  d31
" p, Z8 W$ q" N  _$ X32( P" R9 P8 [: _6 d8 I
33
8 `6 u; Z- E* O5 M" x8 V342 ^8 k2 C# v) \) t- E
35/ p. Q. _6 M! L/ k8 S5 s3 ?- D  K
36
- G# P  k! I# i0 C+ V5 u( _+ t37
# O) o6 H; {* F38
6 u/ q9 ]9 V. o' ?& }; ~39- t$ I+ [1 U0 A* g1 u) A0 F% y
400 l9 N! F& b8 q2 y8 y
41  S8 L$ V% X7 V4 l
42
) n1 h( s2 `( O( a" b2 m43
% n4 \5 g, `* T/ `& a! T44
/ q6 K9 B: W- l6 Y( h0 i/ }+ b& W9.3 区间类别" Z  _1 w* P# |
9.3.1 利用cut和qcut进行区间构造
6 N( v& E  s2 ^$ \/ }' J  区间是一种特殊的类别,在实际数据分析中,区间序列往往是通过cut和qcut方法进行构造的,这两个函数能够把原序列的数值特征进行装箱,即用区间位置来代替原来的具体数值。  K5 ^9 g' S' J

- i: o8 u8 i% c; X* Bcut函数常用参数有:; Q+ n+ Z" U8 Q8 O, q( g* a4 C8 ~" U
bins:最重要的参数。
- J0 F1 R' r$ j如果传入整数n,则表示把整个传入数组按照最大和最小值等间距地分为n段。默认right=True,即区间是左开右闭,需要在调整时把最小值包含进去。(在pandas中的解决方案是在值最小的区间左端点再减去0.001*(max-min)。)$ P& Y; T  z, n9 V6 D. [
也可以传入列表,表示按指定区间分割点分割。
/ X& k$ y  _: p  如果对序列[1,2]划分为2个箱子时,第一个箱子的范围(0.999,1.5],第二个箱子的范围是(1.5,2]。
; B* e2 X% ~( N% `+ S  如果需要指定区间为左闭右开,需要把right参数设置为False,相应的区间调整方法是在值最大的区间右端点再加上0.001*(max-min)。
4 _9 h: S7 j& b* p# S0 L  Y& W8 O3 N, {6 y4 J3 T
s = pd.Series([1,2])
3 w; B7 h+ J7 @# bin传入整数7 \4 H% I/ }# ^7 b8 F
3 |, {9 X& t/ g* G7 L- D
pd.cut(s, bins=2)5 q" g# D( t" U$ e! V
Out[39]:
* |+ h5 i) A7 V) J( r2 [0    (0.999, 1.5]
: ]; g* `+ ^" M+ M1 l# H1 R' Q1      (1.5, 2.0]0 U8 Z( G$ F1 _% ?
dtype: category
+ O+ D+ O" v$ x5 `Categories (2, interval[float64]): [(0.999, 1.5] < (1.5, 2.0]]
2 D% F! W  d, D; A
; p# d$ D$ @7 e6 v5 A  J7 `! l* ?. Y/ \pd.cut(s, bins=2, right=False)5 r$ x. {. ]- v
Out[40]:
( h3 S- z5 }' D/ C* v/ X7 G' c1 p0      [1.0, 1.5)
5 u' P) {$ N- X' j0 i. A8 |1    [1.5, 2.001)
  F* I& a) N2 Adtype: category$ `- }* Q, {- V
Categories (2, interval[float64]): [[1.0, 1.5) < [1.5, 2.001)]6 V) R# F9 d( c- {2 N" S

: y4 Q2 a5 j% ]1 W: y: o0 o4 J' G4 y9 o3 v
# bin传入分割点列表(使用`np.infty`可以表示无穷大):8 f" t, \( v% l& f# ~$ e% ^
pd.cut(s, bins=[-np.infty, 1.2, 1.8, 2.2, np.infty])
+ |/ l' {. u2 @% }4 hOut[41]: + |& J# [+ L8 h. y3 j
0    (-inf, 1.2]
4 `. Q4 C) T: C5 d7 w1     (1.8, 2.2]' {) A1 p) d$ y- G6 I, ]
dtype: category
: J  Y# f3 A" B6 e0 n  M  h4 e3 F5 RCategories (4, interval[float64]): [(-inf, 1.2] < (1.2, 1.8] < (1.8, 2.2] < (2.2, inf]]
% x7 R( E; r( _% @; N  _' Z8 @0 ]5 `# D5 T5 q
1
5 q- y% [' O: `7 g: N3 V6 c; b27 z( A, D7 S- ]5 }
32 [) E% Z/ c0 N
4
4 W8 v2 A  M' U. H* i9 \5
8 M6 b% t- V, |( J. t, u6, ~8 u8 |1 A  c. g+ Q2 t
7% {6 H5 j+ p: h! K! Z4 E
8
; l  d: r/ Q  o9
; f( D9 W& h& S  n# s10" f4 J! Z/ H7 f  z8 A
11: U, e/ j8 [* w9 g9 L' ^& Y
12' @/ m7 t8 o( `! ], [# B1 i
13; I; y. u7 @2 q* J3 s
142 @7 k2 O1 v! {0 S& H+ a1 f! w
15* n. D. `* z' \- x, U+ u9 Z
16! C& @; h$ f2 _
17  R) a. T, Q0 j- h* F, w
18
# U2 r4 l; t/ H195 O( L# M, |- c- g/ U
20
, C" P0 X, C7 R8 S% H21
( o" ?  m% Q$ }& u' `0 G& r22
1 Q; }0 ^/ {( L, Q! \& i- p23
$ a" P' E3 f" l  Q7 K7 \$ ~24
0 v" U, u; r% g- s$ s257 r# X, q' x; P+ l2 C  q; F( z9 e
labels:区间的名字
5 ~3 Y, i5 n% E5 a' _retbins:是否返回分割点(默认不返回)9 N# Y8 @/ `+ |
默认retbins=Flase时,返回每个元素所属区间的列表% E$ W! V: a' P
retbins=True时,返回的是元组,两个元素分别是元素所属区间和分割点。所属区间可再次用索引取值
7 _4 N1 m2 y5 E! [/ M( o, \7 ~7 i- |+ s
s = df.Weight
! I* ~% Y5 w: ^% \  C8 _7 B  Pres = pd.cut(s, bins=3, labels=['small', 'mid','big'],retbins=True); |' X# U9 c$ }6 ?# x
res[0][:2]  _/ t6 D1 x2 V& X  n# T3 G9 n' @7 n
3 A3 r' O( ^2 d+ o+ U9 l6 q) j
Out[44]:
/ Y  ?! Z. Y& v0    small
' {4 Z  }1 V/ a% n$ V; o1      big
8 t  x( m# `$ V- N8 |dtype: category* C- u2 q! J7 J  g3 u
Categories (2, object): ['small' < 'big']8 Q# f( C( w% v8 V8 V/ ?
+ D* d# I* ^. z6 g
res[1] # 该元素为返回的分割点0 Z- B8 `! M) B, c
Out[45]: array([0.999, 1.5  , 2.   ])  `# V0 Z& s9 z; d
1, t; }( @( W6 o# D, t# \3 \
22 c5 o, G/ B! `+ C6 ]
3
8 k2 ]1 P* m( p4 k& C4  E& Z6 U; N" ], g$ B$ ^
5
8 B3 g1 n$ U1 ~5 [2 o5 p4 Z, {/ V6
& ^) m4 j: A5 ?$ l* X72 Y4 M8 P! c7 y' y% R
8
- W3 J, j. A: a' ~: I9
8 P+ s. R- l8 T& \& J10% e+ K7 s2 L/ V+ l/ G5 k6 g
11, ?# ?7 t  F% F: C+ ?
12: C$ b3 n" a0 M: v' q; @* b
qcut函数。其用法cut几乎没有差别,只是把bins参数变成q参数(quantile)。0 P2 o( x, z( \6 H$ T8 i
q为整数n时,指按照n等分位数把数据分箱! M% r1 H6 B/ Y) J1 |+ t
q为浮点列表时,表示相应的分位数分割点。* r2 ~4 s+ W: e
s = df.Weight
9 u8 R, `# W0 {" g- Y% B, @. C8 f5 L( [9 n
pd.qcut(s, q=3).head()/ o; x* }6 w0 I' _& b, J$ ~) \+ i
Out[47]: / ?" i% r( y$ z7 J" U& k( R+ C7 a% o
0    (33.999, 48.0]
3 x0 R, L% K8 W+ s+ N4 M1      (55.0, 89.0]3 o: o, Y1 m* m) D. x
2      (55.0, 89.0]
' J. Z  m/ \9 h9 u* x3 ^3    (33.999, 48.0]
( [* s, ]# p% M# C4      (55.0, 89.0]
* h* I$ t- A- t3 _- eName: Weight, dtype: category
* [& x/ Z& t" r( A% PCategories (3, interval[float64]): [(33.999, 48.0] < (48.0, 55.0] < (55.0, 89.0]]
, n) f. F, ]0 z5 H8 x9 J9 _/ x& _" y" o+ l$ D7 ]" G; T- l9 ?
pd.qcut(s, q=[0,0.2,0.8,1]).head()
0 ]- C4 a& s5 B3 G, y" LOut[48]: 2 k$ ?% {4 _$ b6 |/ B8 U
0      (44.0, 69.4]1 v; B5 a/ l; L: x" \2 S' T
1      (69.4, 89.0]; _% `2 |7 e, h* c/ y  B3 X' r3 q
2      (69.4, 89.0]
8 S' q. n$ e* ?$ E' |! b+ h2 l3    (33.999, 44.0]
  c6 X! r. O6 R) S9 G' a4      (69.4, 89.0]
( C, Y4 e6 g$ p$ p- b# y/ Q/ @" p. wName: Weight, dtype: category" u6 M: r9 V+ w1 c8 Q
Categories (3, interval[float64]): [(33.999, 44.0] < (44.0, 69.4] < (69.4, 89.0]]+ z, Q1 w* @4 d; W6 ~3 q
  R. W; K7 _( ^& H
1/ U/ w1 r/ @( Y& R' P1 Q/ X% U; A
2/ W  q; P0 S0 @, |
3
6 M7 [9 T, ~% g, j% ?, z4# s& Q) Z& V' z, _  N( o
5
4 A( _' o3 U6 W" J9 J* m& L3 a  \: [6
; e7 A( g5 O" B. r; u+ o6 E7
5 O3 J' C1 _6 b7 u! |86 d: Z6 B1 a: I% ?( b* X2 m/ p
9" s) P6 d* W5 r6 ^" Z
10' O" y( k, U4 Y( }) F
113 s! Y# j( ?" G# g3 y. _% P
12
+ x# m3 A( y4 w  S$ ]) |$ E13
; p! @$ t7 t- S! X4 U1 t3 G% x9 W6 J149 h1 ~# c( j! W( M9 w0 ~8 e
15& R2 V8 u% d* l! \
16
* S% }1 f7 K! F2 T17
5 G1 t& e- r6 m- M/ `6 V$ Y18
# `* S% R. I0 N& L; z19
, \6 N& }- G, u1 ?20* R" h: Y* [. a1 n2 q
21" s  C: }1 a. [9 l8 J
9.3.2 一般区间的构造" E" m  G+ s' l% Y- @" F
  pandas的单个区间用Interval表示,对于某一个具体的区间而言,其具备三个要素,即左端点、右端点和端点的开闭状态。; ]% l5 R9 M- j1 M) e
. e% S- N# Y. @- r  k9 w5 M
开闭状态:包含四种,即right(左开右闭), left(左闭右开), both(两边都闭), neither(两边都开)。8 F; `/ E( }/ ]. q
my_interval = pd.Interval(0, 1, 'right')3 l6 R: @# F( y
/ s/ M" I7 Q+ K5 K/ z) ?/ h
my_interval3 e& ?( b/ M6 \9 J
Out[50]: Interval(0, 1, closed='right')3 t7 i% u! {  ^0 X$ O+ E
1
) G$ i" c  ^, r2- l, D/ @# k: @( d
3! p9 u. t5 _% U+ @5 h3 y3 a  _7 g
4; m2 \; U8 A- W% Y6 ~1 I, I; w
区间属性:包含left,mid,right,length,closed,,分别表示左中右端点、长度和开闭状态。' r* Y4 ]! D: y, _! W0 Z
使用in可以判断元素是否属于区间
0 j& a5 U* a9 p; P) X用overlaps可以判断两个区间是否有交集:- j4 @: D6 z5 O5 D$ l
0.5 in my_interval. e  v; D# t" u- s6 E

+ n& I6 f6 L1 \( ETrue
: k. M5 `1 V- ]* l  g1
3 u2 m7 a) \; _1 J' X. `2
& e6 y2 b: m' ?% f2 F3
9 r: _% h& w5 T; R/ \my_interval_2 = pd.Interval(0.5, 1.5, 'left'). p  [7 P6 q& c; `+ Z- j
my_interval.overlaps(my_interval_2)7 x, l6 |8 R/ e& `. Z: j

8 u* j/ S: P5 X$ z6 yTrue5 Z4 ?, |6 Q5 x+ |+ I5 X4 R
15 U* l3 {! g' p8 }( h; U+ q, d
2
. q( _0 ]& v% k3. K) t1 P# x' N' h
4
1 u" U" {& _9 O  pd.IntervalIndex对象有四类方法生成,分别是from_breaks, from_arrays, from_tuples, interval_range,它们分别应用于不同的情况:: b6 v9 h" }% ~: q1 i6 ]" U. g2 ]

& ~; Q7 m0 R4 K/ w. w& t* }, _from_breaks:类似于cut或qcut函数,只不过后两个是通过计算得到的分割点,而前者是直接传入自定义的分割点:3 ?5 O4 S8 T. h) E( ~$ {5 J* y
pd.IntervalIndex.from_breaks([1,3,6,10], closed='both')
$ D! i6 F) N5 Y9 s+ g# W1 n- E# N: V, |
IntervalIndex([[1, 3], [3, 6], [6, 10]],$ ^, w4 L* u- ]8 A8 O" g
               closed='both',2 J+ u2 w* W* }9 s" f4 L6 _. j
               dtype='interval[int64]')% Z/ e! b# n) H" m9 y5 g
1
' ~5 U0 Q7 y, S4 n2$ X$ g& s; D/ O% {
3( F5 Z8 [4 n, @% d$ h8 W0 q
4
7 O; f3 b& h/ j" L9 v3 M1 E5
2 n# i4 _- A9 ?9 _& Afrom_arrays:分别传入左端点和右端点的列表,适用于有交集并且知道起点和终点的情况:$ s7 Z( x1 R% A: J7 u8 ?" c
pd.IntervalIndex.from_arrays(left = [1,3,6,10], right = [5,4,9,11], closed = 'neither')& [0 t* ]3 d7 Y/ a1 [' ^# H

5 e! P* x" g6 U! `* i; {IntervalIndex([(1, 5), (3, 4), (6, 9), (10, 11)],( ]- r0 o- v1 {; |! w% O( U
                  closed='neither',
! B5 m' E3 e  ~  R& q* ~+ n+ N                  dtype='interval[int64]')
# ~( Q: u6 E  n9 R; E4 `8 g1. p# m  `# v2 v
2
+ }; s) F3 m  F  y- a9 |3& z7 e: n( _0 `. M' |/ s7 H+ \
4% I! N  \! ?5 s% P; s
5! U  p2 \9 {' }7 Z: Q3 i, R
from_tuples:传入起点和终点元组构成的列表:
5 I) D2 j0 J- G2 j" _$ I  I1 opd.IntervalIndex.from_tuples([(1,5),(3,4),(6,9),(10,11)], closed='neither')
) G1 ~0 D7 y9 U, u
/ X6 s/ l) N+ L& `IntervalIndex([(1, 5), (3, 4), (6, 9), (10, 11)],
$ m( y' s* s! L2 X7 a              closed='neither',
; B" Q  w( f% \- t) z6 v              dtype='interval[int64]')( m  q1 N7 @( Y( A4 L1 V
1$ D/ ]& U0 V4 k/ c8 }: M
2* _' N. x+ d* S* }) }  y/ ]
3! m% P) T0 J( H. ]" s. @
4
# ]# {9 o6 r3 \: m+ a  G5/ G% S3 f3 N% u( _8 p2 m: _- W: _
interval_range:生成等差区间。其参数有四个:start, end, periods, freq。分别表示等差区间的起点、终点、区间个数和区间长度。其中三个量确定的情况下,剩下一个量就确定了,从而就能构造出相应的区间:* T% ^2 U8 v, n8 i
pd.interval_range(start=1,end=5,periods=8) # 启起点终点和区间个数0 K: w; k  J* \
Out[57]: * R2 m3 C! z4 T7 A* k* W
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]],2 ?3 S* I, a' i( l, o& M; V0 i
              closed='right',
* |0 H+ m! X1 _$ T              dtype='interval[float64]'). x5 l9 a7 J+ Z, [
4 K# ?6 S% Z4 }, A5 c7 v
pd.interval_range(end=5,periods=8,freq=0.5) # 启起点终点和区间长度- t% J1 L2 w* f$ w
Out[58]:
8 S2 F) ~* t4 D! |. @1 Z, L( EIntervalIndex([(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]],
& A" F+ w6 g1 n1 n              closed='right',; t- ?9 c1 w+ C
              dtype='interval[float64]'); I) z+ T6 Z- d" F+ v( M2 x# [4 h4 g
1
5 u2 J* D2 I% p) Y: h3 z; L2
1 R/ h1 G( R3 S- B" E: z2 V3
* o/ b  o. E; w& J8 n4
2 |  t: M2 ?' D& k3 L51 m9 Y: \9 s. o4 ^: b. m9 d7 S2 j
6/ ~. N1 H1 d  u& [7 f" S
7
! J$ a5 {9 _/ \/ F- ~8
1 n5 z/ b0 O% H& ?6 S) d9/ P2 V) L4 j  a2 ^  i7 B$ @
10
) u& w. B, Y4 g4 i8 |9 ?  F& o% K119 U/ u! a8 T) K# k4 y- N1 J4 f  N# d; ~& p
【练一练】3 [' V" Y2 a( H  g0 ^! ?$ X9 ]# S0 m
  无论是interval_range还是下一章时间序列中的date_range都是给定了等差序列中四要素中的三个,从而确定整个序列。请回顾等差数列中的首项、末项、项数和公差的联系,写出interval_range中四个参数之间的恒等关系。
; G. C: b  l+ l8 |1 z; D, d: @
: f; C$ w: U4 ^2 [3 m8 F! e. O  除此之外,如果直接使用pd.IntervalIndex([...], closed=...),把Interval类型的列表组成传入其中转为区间索引,那么所有的区间会被强制转为指定的closed类型,因为pd.IntervalIndex只允许存放同一种开闭区间的Interval对象。
0 H8 I& P9 X7 }; u# C9 H3 l! W5 J6 y" ~1 H+ E8 R: q6 x
my_interval5 Y+ E- Y/ h: h# ^! A
Out[59]: Interval(0, 1, closed='right')
/ Q, g* V4 \  B$ Z( y% N& d; T) R( {, f: P+ y# g% m# b
my_interval_2
4 Z* r" k: s1 G: {Out[60]: Interval(0.5, 1.5, closed='left')
* ?' J. p6 U" P# m4 g' n5 i! s# e8 F7 \! t/ l
pd.IntervalIndex([my_interval, my_interval_2], closed='left')
6 z7 p6 p1 N/ ?3 C) k$ x8 ^Out[61]: ' s- h6 I* F1 ~
IntervalIndex([[0.0, 1.0), [0.5, 1.5)],
5 z# ?3 _4 [- X5 W- w7 Z0 {              closed='left',6 ^. q# r$ H# \+ x6 I! w7 A& G
              dtype='interval[float64]')  `8 N7 i) t3 G3 u4 r8 j
1! w. c2 ?9 F: X4 {
2
" ], ^' [; `+ e, i3  |7 ?2 i1 U# W: d8 d
4' S6 J! g3 R6 ]" e- j" f/ Z4 M
5/ _' w" z) n; n) K
6& E9 `* m* J7 |# l
7' T  C$ S* A- `$ S0 f: {/ d, F
8
* O5 @$ U- ^  ^8 i& i* l9  ]  k% s: c% o9 H+ s5 b) P6 h
10
* m5 g2 }1 L6 d. Y& k+ S11
& \# G; S# ?8 n6 @1 x. O9.3.3 区间的属性与方法, V- z5 l& W& w6 S% Z$ o3 o
  IntervalIndex上也定义了一些有用的属性和方法。同时,如果想要具体利用cut或者qcut的结果进行分析,那么需要先将其转为该种索引类型:
) i  i: S* n( Q4 H6 k4 Y
+ f0 N, G! s$ |5 `% Ps=df.Weight
, Q  C; m- o. O( v) {. `* H" d3 Vid_interval = pd.IntervalIndex(pd.cut(s, 3)) # 返回的是每个元素所属区间,用具体数值(x,y]表示
4 A/ W. |  |9 P5 a' X( F4 vid_interval[:3]
0 }9 d& C/ J/ n" _" ]+ i8 R" C. k& F; T% d# E9 g, w% y9 B
IntervalIndex([(33.945, 52.333], (52.333, 70.667], (70.667, 89.0]],+ d1 h5 b9 b2 }4 n
                 closed='right',
. O# L' |; Y' u6 D5 R( S                 name='Weight',# U% s( }0 P! Z" J$ S2 V& p
                 dtype='interval[float64]')
* n- p( _& S; g4 u7 A% t1* [8 F/ s2 J% J
2
1 [9 ]0 Z0 m6 Z1 Z3; ?9 f/ V( u7 {
46 c# @% p# Y( X3 ~" B
5
. C. b* o9 Y' H6) r5 `2 Q( A% y  g
7$ }5 I9 O0 g# y3 V: k. x, G5 Y0 }
8
7 E9 Q* v( ~# |* K# r3 x1 J% O与单个Interval类型相似,IntervalIndex有若干常用属性:left, right, mid, length,分别表示左右端点、两 点均值和区间长度。/ A: K# f; X1 ~% ]$ o* L( I
id_demo = id_interval[:5] # 选出前5个展示( {- {& V2 ]7 K2 ?; k  X

1 W0 D, [+ E/ qid_demo+ {4 B" I$ L& D$ Z3 M" P% i
Out[64]: 3 r0 G  Y( q# {
IntervalIndex([(33.945, 52.333], (52.333, 70.667], (70.667, 89.0], (33.945, 52.333], (70.667, 89.0]],3 N- h9 e' r. u+ P9 z8 J3 K1 Y
              closed='right',, ^0 R! z7 v; C; D6 @
              name='Weight',
  e7 g4 c* ]% }9 j  l1 J              dtype='interval[float64]')
# x8 _) C6 }: u2 h/ r; B3 g0 R. M2 X4 s0 q# J
id_demo.left # 获取这五个区间的左端点
0 z8 J: B; G; v* `! i. dOut[65]: Float64Index([33.945, 52.333, 70.667, 33.945, 70.667], dtype='float64')
6 l' Q  t) [3 B) A0 p8 G
' |+ \9 j6 W+ t2 a+ ^id_demo.right # 获取这五个区间的右端点0 C  u, G3 |2 {2 @
Out[66]: Float64Index([52.333, 70.667, 89.0, 52.333, 89.0], dtype='float64')
) K* x2 p, r) C  d4 b/ Z! Z
5 N. a" H% f+ }4 ]* ]7 a6 _& g5 L* Vid_demo.mid9 K7 O2 N5 s7 ?* ~* i
Out[67]: Float64Index([43.138999999999996, 61.5, 79.8335, 43.138999999999996, 79.8335], dtype='float64')
& G! e, O1 G6 G- d( L8 B+ p3 g8 q) |3 Z: {; S3 J4 m( A% [* t
id_demo.length; O3 P+ a& T$ ]2 O4 h8 K# z, y# n6 v
Out[68]: 5 [# c$ b/ t- \( T, t/ Q/ a2 d6 f: m
Float64Index([18.387999999999998, 18.334000000000003, 18.333,
3 j/ _  u' y5 n1 I% O              18.387999999999998, 18.333],8 T' d# o; Q' q/ W
             dtype='float64'). z1 e' d( C4 G4 K$ E6 |' g- p

2 H9 W6 T. {. P$ s; a1
5 K( e7 q% |+ Z# O) _2 Q2+ \% v0 {- M5 w6 r
35 o# U- X( a8 Y6 s6 _
43 i' g( p8 H  C6 T
50 P5 f5 C: a: c2 [) V: j
66 D& y* x# ~* `$ H
77 Z% l. V: z5 L5 I
8
4 T+ v, \8 T# ?# I$ a90 q2 p1 Q- p+ R! _4 J
10
! |; i" n* [" n  T2 v/ i11
& i. B/ a- E5 d# K12
% Q3 q8 P" a) s7 n& U+ {3 P13
) I, Z6 m% [& v9 s1 h8 S14
# a2 q# N" W# ?% e+ S. d151 G5 p% H" z/ T1 R! j" c2 @6 y' K) O
165 i; m% ^- s9 k" C
17% l' t) B) L) `% @; B# p; u/ k4 G
18/ S% L; E5 o4 J' }6 D
19# k- I2 t3 ?! T  j+ o& q
20% B$ `8 W$ q2 H- Z& ?& ]% t
21
0 I4 i! e) J# c22
+ g& J8 ^" K) W. f+ k% M- B+ a23% g- _1 f9 z1 `( r  P9 d
IntervalIndex还有两个常用方法:
/ R+ _7 @0 H- X: E% d  [9 }contains:逐个判断每个区间是否包含某元素
2 M, @6 V( \- q. y( b  W4 e# ioverlaps:是否和一个pd.Interval对象有交集。
2 c; D$ f5 O9 Q4 Kid_demo.contains(50)
* _! z% v* V& R" v' N& z, ZOut[69]: array([ True, False, False,  True, False])
4 l* t, A( B1 B, w( Q
2 O- D% e- s; |6 Jid_demo.overlaps(pd.Interval(40,60))
9 u" t# J2 e2 T# j3 I- X, gOut[70]: array([ True,  True, False,  True, False])
9 h! N! @  g/ d1
9 ^1 i) j6 O3 R4 @& `( e2
2 O1 M! @; H1 K$ [9 @0 t9 y3
) q5 |1 z+ n4 T+ R: R45 w$ t6 W( r% K% ]. d. Y( P
5
  y* g2 n. N$ u. H8 v9.4 练习" O# B  G' n/ j1 m' S
Ex1: 统计未出现的类别+ Z# V5 k4 j  L, s' k
  在第五章中介绍了crosstab函数,在默认参数下它能够对两个列的组合出现的频数进行统计汇总:
, q+ p+ \' N) v3 l
% K: k: I& o( q% m  ?9 ~df = pd.DataFrame({'A':['a','b','c','a'], 'B':['cat','cat','dog','cat']})( \; ]6 a( s; U' y
pd.crosstab(df.A, df.B)
9 ^3 \! T4 H1 D( V! S3 C
$ X  C, Y; k) `7 r: d) LOut[72]: % I% i( V8 v* Y, G. M# T
B  cat  dog
6 @6 R  N' d' q7 {0 [# K5 AA         
- y# E$ j5 U5 R0 D* ~a    2    0
  i) T% L1 c4 `- X: K% hb    1    0% y" f1 m& s' r* N% l: k2 |
c    0    12 o. k% Z$ [/ @' v3 n
1
( u1 f& l, ~3 G# f! x# j1 Z: y2/ @( E9 c: b/ l1 S* Y( K1 i" Q
3
3 x0 Y* m- Y# [4
9 L$ m& n" L8 e- @( I* `2 U- s# \5/ ^4 o6 E* i& r8 F  @3 T
6
5 @8 o8 p& ^: k9 i( _2 r70 t& _8 ~+ R3 g% U& n, `7 f' e
85 ^3 x- N  T7 S* j5 _
9
8 S/ q, E# D& U: ^4 Z  但事实上有些列存储的是分类变量,列中并不一定包含所有的类别,此时如果想要对这些未出现的类别在crosstab结果中也进行汇总,则可以指定dropna参数为False:' e7 h4 e& O7 g: Q

( g" m; K6 m/ Z1 Cdf.B = df.B.astype('category').cat.add_categories('sheep')5 s, ?; e' C% i
pd.crosstab(df.A, df.B, dropna=False)
9 f6 H# ~4 H1 s" M5 ^( ^
7 L5 z2 a; `3 i# hOut[74]:
4 k) k2 ?- `- e+ X/ oB  cat  dog  sheep
3 b* o( ]  i, d" Y! H7 W0 C+ QA                 3 j- p3 @: v. F, h8 T# o  k* U
a    2    0      07 a9 P: k, V: Y! ?" ^* a8 ^
b    1    0      0! Y9 ]4 |# Z# u& q
c    0    1      0& L- G( g# e: _
1! N3 Z- Q4 r' d) ~( u3 \- o
25 Y1 J  ?+ X3 c$ m& [% U+ h9 B
3
  r, s7 @/ Y. K8 _4% D; v! O1 O5 n. O& f0 @' F' r
5
6 p- Y7 \$ T$ H6( c6 I3 A6 o3 o3 P) _' ]/ m
7
6 d$ j1 O+ g5 f' q& o83 i, J! Y$ g8 [; R- h7 h
9' |5 I0 D/ T6 ]2 s2 F
请实现一个带有dropna参数的my_crosstab函数来完成上面的功能。* j& u1 u2 {. c) P( q# C" ]
6 R, q3 X1 Q" r/ j- [# P7 B
Ex2: 钻石数据集
" R7 j, E6 j; d3 D  现有一份关于钻石的数据集,其中carat, cut, clarity, price分别表示克拉重量、切割质量、纯净度和价格,样例如下:
9 d3 [8 b# r* P1 j8 i1 L3 P6 a: V1 q* m: K- G
df = pd.read_csv('../data/diamonds.csv')
7 x6 |0 ]3 D; z% J% V7 ]df.head(3)
" C% g! V! s2 T& w0 R$ y: x9 Q$ J/ m$ z1 N' n
Out[76]: % W  u! X/ X( B( \" ]4 r
   carat      cut    clarity  price
+ f! B0 y3 u7 D6 T% f& T0   0.23     Ideal     SI2     326
9 g$ m3 ]7 [/ P  g; n* V) W1   0.21    Premium    SI1     326
; n% n/ C: l  t& L2 L! x0 b2   0.23     Good      VS1     327
+ x5 k6 |) u) x1 Q) O& D16 X* c" o9 X* @' b0 @/ U1 ~! K
2
! P+ D0 J. ~. E9 `37 k' |- q4 ]1 q) A- y6 P! s
4
) I' P. g( W2 ?1 w5( Z7 K# j& e6 R0 f2 e+ @+ ~
62 W( K6 w: x7 M# K0 i
7
. Y; M" `( q: m' |- `$ ?2 g83 |" j: G4 N4 G- O$ c0 R
分别对df.cut在object类型和category类型下使用nunique函数,并比较它们的性能。& p* `( b, U3 W: M1 Y
钻石的切割质量可以分为五个等级,由次到好分别是Fair, Good, Very Good, Premium, Ideal,纯净度有八个等级,由次到好分别是I1, SI2, SI1, VS2, VS1, VVS2, VVS1, IF,请对切割质量按照由好到次的顺序排序,相同切割质量的钻石,按照纯净度进行由次到好的排序。
' e# y5 W$ O4 h分别采用两种不同的方法,把cut, clarity这两列按照由好到次的顺序,映射到从0到n-1的整数,其中n表示类别的个数。
, B8 T0 K) k7 l9 Q对每克拉的价格分别按照分位数(q=[0.2, 0.4, 0.6, 0.8])与[1000, 3500, 5500, 18000]割点进行分箱得到五个类别Very Low, Low, Mid, High, Very High,并把按这两种分箱方法得到的category序列依次添加到原表中。9 d* s& x' `7 @
第4问中按照整数分箱得到的序列中,是否出现了所有的类别?如果存在没有出现的类别请把该类别删除。8 ]- J0 U) `  h: E
对第4问中按照分位数分箱得到的序列,求每个样本对应所在区间的左右端点值和长度。) P7 _1 O1 O7 T5 L3 Q( |$ Q
先看看数据结构:3 p% g1 f& w1 D+ ~1 j5 x% L/ f
6 Z" v5 g& F0 h1 C& e1 V5 l
df.info()7 n: G9 i/ O1 L4 \" W
Data columns (total 4 columns):0 T( l2 d% c* `/ [. p! }
#   Column   Non-Null Count  Dtype  
' [' D; k# @  U  }5 Z" k---  ------   --------------  -----  
. ^2 g- {  ]% E- L) g3 f 0   carat    53940 non-null  float64! Q$ l& G, ~$ C1 z+ O
1   cut      53940 non-null  object * O8 r* y- L3 g. q# w3 J
2   clarity  53940 non-null  object 8 b0 w. L  K( W- A7 M# [* L
3   price    53940 non-null  int64  ! `+ z& R2 g7 I; u
dtypes: float64(1), int64(1), object(2)- _5 ~' D; D+ t2 u/ H/ x1 k" b
1. Y, ^+ C$ z0 a7 F4 s, V$ _
2
$ G& t; f) Z6 g& M  H4 b3' r$ i2 m& Z, [1 P, W5 @
4
  e0 b* T- R8 x1 i5. [4 H2 H2 g6 h0 P- a/ U' M
6
1 [; o8 U3 r( @6 [) Y/ s3 m7" x/ B' I1 l& j; J
8
# R4 J6 t" Y. i  F7 z  y/ F/ L2 g9
/ q. z* g1 Z% K: D比较两种操作的性能
. ^7 @; h5 E2 w3 [. W3 z1 ^1 g$ K%time df.cut.unique()7 S# b. `" ~: n( S6 C

: D! b+ n1 T( N9 G" ?Wall time: 5.98 ms- [5 W# k; g9 n" s
array(['Ideal', 'Premium', 'Good', 'Very Good', 'Fair'], dtype=object)
( c9 B) g. L: D- e4 e$ A* l1
" T7 q" ], Q! _2
* p2 e5 \- }, w& T9 I36 ~. l) Y/ z  W6 c% A2 b6 O! y
4
/ @: I+ B0 B2 |- O" T%time df.cut.astype('category').unique(); i9 p( u* B% V/ R% X
" B/ G( v$ Q& x* r/ u+ L7 p" o9 D
Wall time: 8.01 ms  # 转换类型加统计类别,一共8ms" L% |; l" ?* x! m0 |
['Ideal', 'Premium', 'Good', 'Very Good', 'Fair']* P) D; h% A: Y1 M: F* K
Categories (5, object): ['Ideal', 'Premium', 'Good', 'Very Good', 'Fair']; n* P* H( v, b0 g0 Q
1
8 [& m; M- Y. h/ \- J2+ L8 ]% ?+ G4 a  u& l3 j
3- e5 U! R" `( R1 @& m- w( R
42 {' U7 q8 M8 |2 Y
5
7 ^6 L" t" C7 [$ J9 H8 O, Odf.cut=df.cut.astype('category')
, H) s6 S  B/ i- V( M2 v% o" u& n  h( {%time df.cut.unique() # 类别属性统计,2ms, u( a6 i' o$ h  {/ ~1 I: n

7 @( Z! o1 M' pWall time: 2 ms
5 H3 m$ x3 r  J' @6 V/ T& ~['Ideal', 'Premium', 'Good', 'Very Good', 'Fair']
* N" R, s3 p6 f# u+ mCategories (5, object): ['Ideal', 'Premium', 'Good', 'Very Good', 'Fair']
! ~- ?/ k( v7 Q, r3 ?. O1
' j6 b4 V" d+ B. |2
4 x- ?0 b7 k; x7 z4 Q# d36 U8 `$ {$ c9 R# x) b5 l
4
5 f% _* f5 A- _3 d" B5' j0 R; c; z: l9 X$ u9 w
6
% e  X; P7 Y: K( H) q/ P! z对切割质量按照由好到次的顺序排序,相同切割质量的钻石,按照纯净度进行由次到好的排序。3 _7 F7 [' X. A3 F) F+ x" N& m
ls_cut=['Fair', 'Good', 'Very Good', 'Premium', 'Ideal']
. s0 o; o0 a4 p% yls_clarity=['I1','SI2', 'SI1', 'VS2', 'VS1', 'VVS2', 'VVS1', 'IF']' ]. |. u) Q/ X
df.cut=df.cut.astype('category').cat.reorder_categories(ls_cut,ordered=True)  # 转换后还是得进行替换
7 Z' a3 W. x" m# ]! O  G- Bdf.clarity=df.clarity.astype('category').cat.reorder_categories(ls_clarity,ordered=True)
+ z7 }$ f5 y. D# T! c' r/ M% o. K5 [8 {1 [& A0 o+ ~1 l
df.sort_values(['cut','clarity'],ascending=[False,True]).head(3)5 N. T# K. I9 J1 W% c( q

: B1 j) @- Y' D+ G3 ~        carat         cut        clarity        price
- P# ?9 n0 R4 I" k) N) h315        0.96        Ideal          I1        2801
. }. T7 l1 X) s535        0.96        Ideal          I1        2826
# u$ x( I" R& P- O+ Y" D- J551        0.97        Ideal          I1        28302 W% u2 s* V7 M& Q+ Y; n
1
! v: a# u7 T4 C: D2 G! o2- d' U0 L/ }% v+ e
3  e' D" i) f) |4 k
44 H7 V) F$ a3 S5 ^3 K# Q3 O
51 G4 R" m7 W! ^$ R7 u/ S1 X! p+ p
60 `8 e7 k8 E* ^, k& s1 V! b" q8 S
7
8 l: x, u+ A& V) H' s. u8
8 j; r, u) [- s; f  k1 `9( J8 k) W% u# e
10' O) ~& d6 _" D- O0 o$ q# l! H
11( i$ i# E8 }' X
分别采用两种不同的方法,把 cut, clarity 这两列按照 由好到次 的顺序,映射到从0到n-1的整数,其中n表示类别的个数。  `2 i+ G& N# e
# 第一种是将类别重命名为整数
$ p  W( B, ^. ]/ J, i. Wdict1=dict(zip(ls_cut,[x for x in range (4,-1,-1)]))& x4 D2 d* B' j& q
dict2=dict(zip(ls_clarity,[x for x in range (7,-1,-1)]))
. L* `9 g2 |# d* l) v
# X' R/ M6 M/ X( F. P: Kdf.cut=df.cut.cat.rename_categories(dict1)* \7 w6 @1 V7 O- t1 x
df.clarity=df.clarity.cat.rename_categories(dict2)" r/ C; {# Z( t: P2 n* X
df.head(3)- X' o6 j1 u4 i' L5 u0 O# e/ Q2 y

0 _, b3 a* V8 [% @        carat        cut        clarity        price  ]6 x2 c" m9 I7 c) J
0        0.23        0          6                3266 r6 r$ p' f8 A% [
1        0.21        1          5                326: H6 E8 s7 t7 _8 H0 r# P, R
2        0.23        3          3                327! A5 p& J' t/ ]# F" x& Y
1
$ C: e$ y* [& e" v2 p21 e: e0 O* B4 G9 O! }8 x
3
0 B* C- Q9 Q& I4 g  i4
; ?" U* V) [. L, T) w5
! w* }2 q2 v4 ^6 I' P' B9 P- y# F6
2 _0 K7 `# [/ }: R7( J! J3 l- x2 R, Y. s
8
( \5 i" X% ~3 ]& `. R6 G9
  p; F) h0 V' u0 U10% D: L" M3 e0 D; d2 U; p; T6 z
11
2 b* I! q. e. N4 g12+ h# t, D+ V, O* ~
# 第二种应该是报错object属性,然后直接进行替换
9 `( E, L* i# T; v1 pdf = pd.read_csv('data/diamonds.csv')
% m0 M0 j) Q( H. P- q# Cfor i,j in enumerate(ls_cut[::-1]):' i2 u1 f! j' ^6 `8 y+ b7 k+ r/ }
    df.loc[df.cut==j,'cut']=i " r9 P. _( M/ L% r3 e
0 Q5 e7 H; \. ]
for k,l in enumerate(ls_clarity[::-1]):/ H: q/ ^8 j: X# N: ~6 F* y
    df.loc[df.clarity==l,'clarity']=k
5 K+ Z. U% ^/ D$ J: r2 ddf.head(3)
' Z. s& `2 A2 a7 }# H( G9 q* b5 n, u( |: {  n: w
        carat        cut        clarity        price
/ c2 J, u7 E/ R+ k( G2 }0        0.23        0          6                3265 ^. `- \6 q4 C+ o3 V
1        0.21        1          5                326
% b$ p9 b: `( S! t2        0.23        3          3                327# ?4 e3 L  }1 p  R9 g0 y
1% \; I4 K' i1 j
2  w( ]+ U' \* a! C
3
7 i. q. r9 w0 H4
) {6 r  j# }1 a2 X53 Q! |0 ]* l+ ~+ g+ ?5 |. y
6
1 S0 l  i$ Q! T7
. o8 |! O6 O/ L  E7 p8 V% W4 }8
+ p8 V) G* d  G+ |& @) z4 S% w( o9
+ Q1 U( m9 c/ ?10) k+ X1 [( Q' O4 T
11
$ B' t0 O. [2 l# \6 l12
+ J3 |! h9 O( p& g3 Q% f2 X13
; G; l: ^& A+ B( g( I- f对每克拉的价格分别按照分位数(q=[0.2, 0.4, 0.6, 0.8])与[1000, 3500, 5500, 18000]割点进行分箱得到五个类别Very Low, Low, Mid, High, Very High,并把按这两种分箱方法得到的category序列依次添加到原表中。/ h1 r. |( e* D0 m
# retbins=True返回的是元组,第一个才是要的序列,第二个元素是分割点
7 H2 W/ {; E2 E- v: ?# Pavg=df.price/df.carat* u: r( \# X  ?, p

- ?6 @9 R# i. _9 P% k3 ~1 bdf['price_quantile']=pd.qcut(avg, q=[0,0.2,0.4,0.6,0.8,1],- ?7 S/ r  A) g4 r' ?
                              labels=['Very Low', 'Low', 'Mid', 'High', 'Very High'],retbins=True)[0]! m) y+ n. m& K& j( O
0 a, Z+ B+ F- @  x  T
df['price_list']=pd.cut(avg, bins=[-np.infty,1000, 3500, 5500, 18000,np.infty],
0 q  w7 O$ g! _                              labels=['Very Low', 'Low', 'Mid', 'High', 'Very High'],retbins=True)[0]
) q& Z! [( T2 `/ C! t6 Wdf.head()
, |# f* w* l% `& J/ x& m/ @4 q  u1 z9 _! b5 x7 H7 j5 P
        carat        cut         clarity        price        price_quantile        price_list9 I5 j. p  \1 y* e3 \9 i5 h
0        0.23        0                6                326                        Very Low                Low
2 m( A6 X7 |. G8 [5 h. u! o3 D5 r4 `: J1        0.21        1                5                326                        Very Low                Low3 S8 e; s0 r" i
2        0.23        3                3                327                        Very Low                Low! T# g$ R. a  N: ?& ~: k& {
3        0.29        1                4                334                        Very Low                Low% z! D5 z/ X2 P6 V9 s  c3 j
4        0.31        3                6                335                        Very Low                Low                                       1 x" t3 R8 B. Z# ]* r

3 a5 U0 r" C7 [$ d# d* {" O1! X$ ~( ]6 P) z: n
2
- v, D5 W+ s0 O# w  H36 c4 t& i# Y5 x) F6 A1 S0 v* N
47 k' m+ ]0 ]& S: a
5
8 Z! N, g6 _. F1 X, [, m6
+ L( f2 a6 `2 [: z8 @8 o2 Z2 v7
1 d; H; ]2 s- n5 _0 ~& V1 @+ W% J81 `1 r% z  N; F% ~: U
9% j- Q% F" a3 t
10
4 ?) J# @; M5 |, B# I4 m& v7 x11
" y2 R. z' @, \/ t9 H4 a/ K/ Y12) Q. w+ h( U, H
13; h( {, s: s$ F( \
14
& ?7 I0 ~+ {5 ]1 u( a  m4 q- k15
! o5 a1 M1 q0 t! I# x+ I$ m9 u16; h& p' t0 Z) @
分割点分别是:! A1 o8 Y" _6 ?& D$ N
/ d  y. g, |9 A4 k4 w2 n
array([ 1051.16 , 2295. ,  3073.29,  4031.68, 5456.34, 17828.84])
8 z2 G8 G+ ~9 d3 narray([  -inf,   1000.,    3500.,    5500.,   18000.,    inf])
2 @! h/ O  S$ D8 P3 C/ V* U1/ K& `4 _2 @9 y& U/ ]5 w4 F0 E  M3 s
28 t0 T2 l0 I9 U" N& ~
第4问中按照整数分箱得到的序列中,是否出现了所有的类别?如果存在没有出现的类别请把该类别删除。
* J$ {; H$ K3 F$ C+ }df['price_list'].cat.categories # 原先设定的类别数
" o0 C; J- c" Z6 t) yIndex(['Very Low', 'Low', 'Mid', 'High', 'Very High'], dtype='object')1 Z' m9 K6 M$ x  X! v1 l( w

: e* {6 N; O0 k% o; n# hdf['price_list'].cat.remove_unused_categories().cat.categories  # 移除未出现的类别
' D. c) J8 I6 Q* ~3 R- ZIndex(['Low', 'Mid', 'High'], dtype='object')  # 首尾两个类别未出现$ l0 l( a1 ~* e0 A; \  ~
1
/ N* ^3 m* W0 \2
( i1 @3 X/ o6 [0 p# Y! U! i9 L3# R1 W& {& f8 e% F5 Z5 p' X' s
4/ w/ j: t% m) R& E- O7 ^8 }- B
5
  j) ?5 ~1 h( b6 \2 `avg.sort_values() # 可见首尾区间确实是没有的
: [% v3 O8 ^: \4 M; p9 P4 G31962     1051.1627915 B' k. \% H3 w  ^& ]# c
15        1078.125000+ `' ]8 Q/ c- g: P- e
4         1080.645161
" x( h5 K% \, L% V$ p! z" o28285     1109.0909094 m/ g/ j9 m; z& q( }0 F
13        1109.677419
2 h# x3 f5 L* u3 w  A, K             ...     4 l- V% m1 k) w' y3 A/ G
26998    16764.7058822 {/ E4 q$ u7 M8 F3 g
27457    16928.9719637 h6 Q( e0 s1 K" M1 e: O
27226    17077.669903- j5 l. G! {) L, P3 U7 I
27530    17083.177570
1 L) N& W: D1 h9 s9 M27635    17828.846154, Z2 l" O. [# w1 s7 _- I, h
1' s" w6 X7 W- i0 g
2/ q: \1 K3 r& ?! r6 F6 Z
3# b' q# q5 A# u6 ?
4) b1 Z* L$ e5 E6 N. D
5" _! z; C0 {3 C! @8 H6 _0 a1 f
63 P/ c& o: m) K, |; Y$ @
7
* v0 c3 n( i; ~6 S- t8
' ?7 h1 v- F2 E6 S" ]: u" G9
( r% e9 \2 d( s7 O10
' [% R& L# ^; M. U' G( d5 [11
7 P( c& ^% ~& q8 x) k6 I$ T12  p, ~! W- e4 s  _$ }
对第4问中按照分位数分箱得到的序列,求每个样本对应所在区间的左右端点值和长度。
6 _5 N1 k6 s3 D2 J; ~! W# 分割时区间不能有命名,否则字符串传入错误。! `1 S9 h4 F& j% ~0 {1 J2 p
id_interval=pd.IntervalIndex(
* |$ E! j( v7 V% ]    pd.qcut(avg, q=[0,0.2,0.4,0.6,0.8,1],retbins=True)[0]
; y+ Z* {* }) M0 I$ Q                            )) \' C; b$ Q* v3 w1 }+ a! h4 N
id_interval.left5 }, C! ~+ D3 t5 ?+ {; U4 I
id_interval.right
# L; z  }2 N4 B7 p) A6 ?% Y, M  qid_interval.length                           
& C8 \8 X2 P  {: V# C17 W6 {" A/ C' _) U& F
2
/ t0 b; x" g3 b0 ?3- c% P3 n$ W, P0 A( o
4( P% W* H. c2 P* }, ]* G' q* h
5
5 U* F) n8 W! \4 [+ Z+ V67 B' [+ M, y3 W' {1 x
7
3 i3 F  t3 W, n; Q& M! K& g/ o第十章 时序数据
. p  d5 `+ S5 o. R0 ^; cimport numpy as np1 o- }$ w# _+ Y9 \8 z' `: p/ {1 D
import pandas as pd- E6 u7 \2 y2 s8 c3 e' k
1
8 n% n6 |$ Y9 C0 F9 q) s4 U2
4 ~0 B. x8 |  V" i: I0 ]
1 K6 M: n7 @$ h" a+ j! i; ^3 S# E) x5 g, n9 ?+ c
10.1 时序中的基本对象1 w, \5 ^9 g4 {! E+ k
  时间序列的概念在日常生活中十分常见,但对于一个具体的时序事件而言,可以从多个时间对象的角度来描述。例如2020年9月7日周一早上8点整需要到教室上课,这个课会在当天早上10点结束,其中包含了哪些时间概念?
7 h9 j0 I- G: p$ t) B, M
$ r+ y2 h4 H# h5 N会出现时间戳(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的简写。  Q# N$ L5 j6 V/ D

9 K- e; m& H* b会出现时间差(Time deltas)的概念,即上课需要的时间,两个Timestamp做差就得到了时间差,pandas中利用Timedelta来表示。类似的,一系列的时间差就组成了TimedeltaIndex, 而将它放到Series中后,Series的类型就变为了timedelta64[ns]。
% T2 f: D( Z  K& Y# s
. d' l7 b% [* l9 @$ E会出现时间段(Time spans)的概念,即在8点到10点这个区间都会持续地在上课,在pandas利用Period来表示。类似的,一系列的时间段就组成了PeriodIndex, 而将它放到Series中后,Series的类型就变为了Period。
& [" g7 D( Y( q) O; q
! u" i% g! x1 l4 N会出现日期偏置(Date offsets)的概念,假设你只知道9月的第一个周一早上8点要去上课,但不知道具体的日期,那么就需要一个类型来处理此类需求。再例如,想要知道2020年9月7日后的第30个工作日是哪一天,那么时间差就解决不了你的问题,从而pandas中的DateOffset就出现了。同时,pandas中没有为一列时间偏置专门设计存储类型,理由也很简单,因为需求比较奇怪,一般来说我们只需要对一批时间特征做一个统一的特殊日期偏置。
6 S( \9 n0 F* }% P
+ |  I$ J7 |) Z+ l  通过这个简单的例子,就能够容易地总结出官方文档中的这个表格:
4 c6 S' U6 }6 S' {. N
" n4 r" r3 O) Q8 W; L% `! W概念        单元素类型        数组类型        pandas数据类型
; e# z! m! @5 N) WDate times        Timestamp        DatetimeIndex        datetime64[ns]1 F: Q9 `( D# Y( v  j
Time deltas        Timedelta        TimedeltaIndex        timedelta64[ns]
- R7 H' H0 y% v; I" q! J/ rTime spans        Period        PeriodIndex        period[freq]" m' F; z- {8 E8 R' B+ }. \
Date offsets        DateOffset        None        None
% f# y4 g3 n2 t* f1 {1 ]  w) m  m  由于时间段对象Period/PeriodIndex的使用频率并不高,因此将不进行讲解,而只涉及时间戳序列、时间差序列和日期偏置的相关内容。
2 O1 e) b* E0 X2 X0 k
2 |, ?0 K$ u2 L0 a3 a) l10.2 时间戳7 D( T6 I5 @% o0 V3 n2 y/ _7 J
10.2.1 Timestamp的构造与属性
+ @/ x8 I0 Q/ \# u% j单个时间戳的生成利用pd.Timestamp实现,一般而言的常见日期格式都能被成功地转换:6 t% k0 R" U+ B0 `3 H
/ {1 |3 u5 H- A6 r( h. j
ts = pd.Timestamp('2020/1/1')9 w6 m/ e8 w' E. i" L
) K" j0 x& Y8 x& i' J
ts
1 }# L1 G" t# VOut[4]: Timestamp('2020-01-01 00:00:00')) O8 t/ J9 N" l

5 n- q" m0 O2 o* C- Nts = pd.Timestamp('2020-1-1 08:10:30')
1 X* S" x! n) ^0 @  J& n$ P8 T3 h) T( q6 I9 r1 i2 V
ts
' J% x+ ~; l1 n% c( H' P! [$ IOut[6]: Timestamp('2020-01-01 08:10:30')
; G4 v! l" R; S9 g/ I3 P" M# Q2 b19 g. h% B$ c6 i' R; z
2
- M) t1 J9 V3 s- Q' v2 }' f3
# X6 r7 E9 V  i) ]6 Z4
2 `- ~# I! ]7 q, t: C' W( W5
! R: L& I/ H* D$ R: H; G) q% T6
+ z  N  |) _  J% a. L; Z74 ?4 B& S8 X8 M6 \, W
89 Z7 H4 v) L; M! j  U0 y( ]. h
9$ y$ m$ z9 `( P3 E3 x" n
通过year, month, day, hour, min, second可以获取具体的数值:9 E+ H& r; `  N3 L$ }
& w2 N, c! g' c8 V
ts.year
. X! T& z9 H. t3 BOut[7]: 20203 {9 {- v6 e! b9 P  V$ T
2 t6 A# p' }8 o: {# \
ts.month/ M* j9 x7 D: `! q* z, Q
Out[8]: 1
$ |$ Z6 p5 c8 _3 h1 C8 C0 P: Y+ z3 ~+ E' d
ts.day
7 u. b" h$ N2 Q- G. G3 zOut[9]: 1
* E1 H- Q  T. V/ W3 j7 h
, e  ]% n' B; m' J1 q+ dts.hour3 x; I0 E& s: [0 A4 R; j
Out[10]: 8
# a. b6 D6 V2 ~8 I! y  u9 u' j9 [. v3 v8 }1 }) D. F- `' w: R
ts.minute4 x1 u# _4 [5 v$ P
Out[11]: 10
* v2 V) G7 r3 T% s. ]+ I, g( t* k& G- n$ v' M# e
ts.second' ?  O4 Y% Y8 K$ W% y0 [( v; X
Out[12]: 305 h+ z# W6 E9 {2 L
2 ~: {7 `5 v2 V  y* x! [* s! ?+ a
1
( \3 \9 T5 y- b* ^2
$ d. S5 h5 r% H8 m3
7 h2 t9 j' z9 z- y( r4 c( ]4) f( h7 J; D+ f3 E5 Q8 [
52 i* h$ G" Y2 v: v. c
67 I4 y7 V# M. d8 x
77 l( d: m+ T; k- Y
85 G  N, Z$ L7 e! u% h5 W
9  j2 w; p$ G3 J6 w, U" T0 F2 I/ I
10' q# O& u$ E* {/ D
11
. g, s1 i( Y2 h9 O, G128 k6 ^( t" ?5 s( Y$ v0 ^  [: I; Q
13
; `+ h; k  ^& \# C; |. j. Y9 r14
' C) C% R1 Q1 k8 B15
$ i& G. s9 L. |3 E: V( z( j16
$ A- X+ ^6 t& N+ F/ {- y2 _% {17
  ^8 x3 i) K6 |  w, H& g# 获取当前时间
' D( S& c2 k" i  c- [now=pd.Timestamp.now()! D# G7 P: A" V! g0 G$ e+ L6 R
1
# \: H  U: S5 @! k25 l7 T: S$ o3 G3 t8 J9 M7 r
在pandas中,时间戳的最小精度为纳秒ns,由于使用了64位存储,可以表示的时间范围大约可以如下计算:
6 b* J6 P& p' ?( m' ET 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)
& n7 E& t% p. @3 I6 ]TimeRange= # l, q( Y3 B" n8 z# |4 n" `9 ~
10
; X, H: {( i( l% H! ~" ]6 W98 a3 P; v! z, g- ^% I! \' H6 F
×60×60×24×365
' a3 c  w& G& ^- c# X3 x. t  X9 \2 : h' z& S' B; r5 x9 s
64# a  f6 C( `3 \* O) y
3 R. ^5 u0 K; P5 c5 H5 g4 x( A% w
( _, L6 O2 T( S  k' F
≈585(Years)" E% r7 R( e& E* C& s  [8 |
' `( T% P6 s; {! L. E
通过pd.Timestamp.max和pd.Timestamp.min可以获取时间戳表示的范围,可以看到确实表示的区间年数大小正如上述计算结果:
5 I8 ~; |+ L3 y$ a) r! b% z
5 Y& _! F  u1 x) Q  s6 }pd.Timestamp.max
  C  X+ d& a) e/ k1 T- z; V) O" HOut[13]: Timestamp('2262-04-11 23:47:16.854775807')$ L+ M$ p. K; w8 X

2 l$ e! ~3 A- k) L3 Opd.Timestamp.min, P2 c3 y1 B5 P( i7 z! Z
Out[14]: Timestamp('1677-09-21 00:12:43.145225')
/ N8 h# t/ D& E0 Y1 u3 v" z! K& w6 {# C6 U* v8 L
pd.Timestamp.max.year - pd.Timestamp.min.year
$ e( y' K; [9 \; MOut[15]: 585
( o3 Z8 p$ ~' y: C+ P, Q4 e1
  w; I3 |4 o" m1 Q2
/ L+ V7 v/ O# S4 m, V3 z36 N- v, G! I0 p2 ^# t3 m( r1 Y
4
+ W0 @! z" [1 |5
1 S6 S) X$ b5 k' g6
; J1 }: z, x8 y4 o) f7& k9 m  z0 c% n) Z! ^* B+ N
8
: v& U: R1 @9 s% \10.2.2 Datetime序列的生成! n/ g  h0 v; k8 p7 b+ _- I1 e
pandas.to_datetime(arg, errors='raise', dayfirst=False, yearfirst=False, utc=None, format=None,. v, a: D. M: P
                                  exact=True, unit=None, infer_datetime_format=False, origin='unix', cache=True)& c+ c" z- z' X
1' y. ?1 c0 O$ b7 |
2
$ q# H' j2 C4 S, X$ Upandas.to_datetime将arg转换为日期时间。
2 n$ |/ l. {5 i0 r# m7 t( ~7 D' i/ U: [! m, O! I1 {8 a( Q( q% b
arg:可以是argint、float、str、datetime、list、tuple、一维数组、Series、DataFrame/dict-like等要转换为日期时间的对象。如果提供了 DataFrame,则该方法至少需要以下列:“年”、“月”、“日”。
4 ?0 `2 Y$ P8 I0 b1 eerrors:
8 h& t  {# Z8 t$ a$ Q- ‘raise’:默认值,无效解析将引发异常
  y( }' G( y, C, q/ z' g: I3 {- ‘raise’:无效解析将返回输入/ q5 Y8 p( o, Y" ]9 Z; p
- ‘coerce’:无效解析将被设置为NaT1 C4 |, |' ]. e( V" ^$ j! B- A2 C
dayfirst:如果 arg 是 str 或类似列表时使用,表示日期解析顺序,默认为 False。如果为 True,则首先解析日期,例如“10/11/12”被解析为 2012-11-10。如果无法根据给定的 dayfirst 选项解析分隔日期字符串,会显示警告。
+ u; O4 x( s& T. \& d! ryearfirst:如果 arg 是 str 或类似列表时使用,表示日期解析顺序,默认为 False。如果为 True,则首先解析年份,例如“10/11/12”被解析为2010-11-12。无法正确解析时会显示警告。(如果 dayfirst 和 yearfirst 都为 True,则 yearfirst 优先(与 dateutil 相同)。)
$ o; \' K$ M; ?' g2 A3 N6 Gutcbool:默认None,控制时区相关的解析、本地化和转换。请参阅:pandas 有关时区转换和本地化的一般文档# p$ G* S/ h$ u/ W) E# O
format:str格式,默认None。时间戳的格式不满足转换时,可以强制使用format进行匹配。6 |0 f* q; |! E4 X
unitstr:默认“ns”。它是arg (D,s,ms,us,ns) 的表示单位,可以是整数或浮点数。这将基于原点。例如,使用 unit=‘ms’ 和 origin=‘unix’ (默认值),这将计算到 unix 开始的毫秒数。7 @1 S3 C/ L, F' }
to_datetime能够把一列时间戳格式的对象转换成为datetime64[ns]类型的时间序列:; \2 V: C/ I$ N; w  f4 V. ^. X- E
pd.to_datetime(['2020-1-1', '2020-1-3', '2020-1-6'])0 m: \( z) M8 v/ ^

: Q1 f! W& m3 S* nDatetimeIndex(['2020-01-01', '2020-01-03', '2020-01-06'], dtype='datetime64[ns]', freq=None)
8 X2 ^: l' q1 ^' `) v3 D1
9 o$ ~* b1 M8 R: B, T2
0 B$ j9 A! T# o: N! a) i$ {3
4 i9 b7 |: U! d2 D在极少数情况,时间戳的格式不满足转换时,可以强制使用format进行匹配:
5 d7 ?' m3 N, G
. D& ?3 O$ B! |* w* qtemp = pd.to_datetime(['2020\\1\\1','2020\\1\\3'],format='%Y\\%m\\%d')+ D$ T! N7 L/ W: ~
temp; }$ h7 q; M$ s0 Y. `

* C7 A/ S' l* g5 Z9 W. cDatetimeIndex(['2020-01-01', '2020-01-03'], dtype='datetime64[ns]', freq=None)7 [( e& v* `8 F% V# X6 a
17 [" `$ Q0 U" w
2, ~0 U7 e: A: ?0 _& q
3) P5 W( Z9 a  _* G# }
4/ l! A# B4 R1 x; q# F: _
  注意上面由于传入的是列表,而非pandas内部的Series,因此返回的是DatetimeIndex,如果想要转为datetime64[ns]的序列,需要显式用Series转化:2 {3 e) p6 R$ D# W2 X: v
4 j2 x7 R0 |8 q
pd.Series(temp).head()
& B$ C2 m  w: g: |2 L, I3 O0 C5 \; X" l
0   2020-01-01
% x7 U6 R- o9 I* G( y1   2020-01-03
/ @: [/ b3 U  T2 p* Zdtype: datetime64[ns]
3 {! N: A5 `- S/ e9 U1
" q( C+ |- c3 O, s/ O( O23 f( P# @& g( E4 P6 Y! B
3! z" w9 }% I# F# Z8 r
41 V/ b* s7 \, i( y2 o2 \" X- R9 e: ^
5
" J: C1 A# g/ K! S% Z下面的序列本身就是Series,所以不需要再转化。
. V% d6 N  D% q( I5 }% Q( y" K( `6 f5 d- b4 }0 C! K/ i. k
df = pd.read_csv('../data/learn_pandas.csv')
% F+ M2 n' E# A5 `/ o3 i: Os = pd.to_datetime(df.Test_Date)
5 L% B% S+ Y8 h# D+ A" u' Ps.head()
. A) u- o: [8 _) M; {+ Z
0 z. }& a0 B% h3 Y5 B+ W1 ~3 \0   2019-10-05* N2 ^5 s5 y" B$ Q* N3 q  t
1   2019-09-04& h" ]. H2 P2 }, ^+ i: T
2   2019-09-12
; ]! @0 r' ^- h  i2 o3   2020-01-03
7 B& c  Y% y, l2 L* T; A% R0 v4   2019-11-064 w& M* T) d6 R+ l6 \
Name: Test_Date, dtype: datetime64[ns]- h& h- H9 t, J8 Q, C  G  W: J
1
6 r& ?8 _0 k4 w- B2
) a7 Q# _5 D+ k/ ^) ~* L3
6 Y/ g( u) Q# j4
& p8 x! C. v- q9 N3 `8 X, S3 ?% S1 l59 b, I: e; f8 |: w& E; ~; Y
6- c: }  I: D' g, }; t
7
; K2 @. ^( L) [7 K$ Q; t! T8
4 Z( L; X1 |% t% B: T9
; F4 K3 Y. k) K  T10
. K. n- D7 K9 J8 |4 z5 b( a把表的多列时间属性拼接转为时间序列的to_datetime,此时的列名必须和以下给定的时间关键词列名一致:
' K' T" K3 P3 {. u8 g# g, Ydf_date_cols = pd.DataFrame({'year': [2020, 2020],
9 j: Y8 Y' q4 z6 V9 u                             'month': [1, 1],
: l4 q+ u/ f! V8 @) h- \/ t' Q                             'day': [1, 2],# K! I9 i9 A" y& }" c
                             'hour': [10, 20],) H$ @4 W) |+ s6 c: y- @; c
                             'minute': [30, 50],
& p4 u" l5 y* ?& B3 d% k& i                             'second': [20, 40]})
' z, t" K! G  b6 g: V3 u# _: ?' qpd.to_datetime(df_date_cols)% u9 T/ F( h) e* C/ c
6 H$ j, H; w& c; j/ H8 f
0   2020-01-01 10:30:209 u' I* o$ l; v: A! w7 \+ `1 `! z
1   2020-01-02 20:50:40
1 n" ^+ F; q. F4 E, B: R: O: gdtype: datetime64[ns]
9 Q6 f2 ^/ o& d/ s3 c* N, l, p1+ F2 C* K2 E8 b
20 C1 b  s2 D6 A) y* L( y! E
3' }: c5 ~& W' s* Y) V# {
4
, W3 h. v- k; p  G) }1 x5; N  e5 m) T- }/ |) m! ~
6
) b  Z8 H4 ]1 z+ K7
! i, @7 Z" u  H' t, L8
! r1 h. F! f9 Y9
- ]9 S4 [% U' l: Q10
" ?  B! `) L( ^116 b) J4 Y) R$ }1 J' U* d% o
date_range是一种生成连续间隔时间的一种方法,其重要的参数为start, end, freq, periods,它们分别表示开始时间,结束时间,时间间隔,时间戳个数。其中,四个中的三个参数决定了,那么剩下的一个就随之确定了。这里要注意,开始或结束日期如果作为端点则它会被包含:& V, |! G5 o, m- r' s6 O& V
pd.date_range('2020-1-1','2020-1-21', freq='10D') # 包含5 f; E  D- ]3 K" L8 G0 O
Out[25]: DatetimeIndex(['2020-01-01', '2020-01-11', '2020-01-21'], dtype='datetime64[ns]', freq='10D')
8 J6 N; S5 u& \; j4 @- I- R* {' q3 K1 n2 u& q
pd.date_range('2020-1-1','2020-2-28', freq='10D')- }+ [# ]) C2 Q7 V. B$ N
Out[26]: * R) V( V7 V& P4 F7 f& T8 Q
DatetimeIndex(['2020-01-01', '2020-01-11', '2020-01-21', '2020-01-31',3 `2 K0 |1 {) Y+ }7 w! O5 Q+ v
               '2020-02-10', '2020-02-20'],
  p! D& Q+ |/ x1 f              dtype='datetime64[ns]', freq='10D')' p/ x7 Z. n# y! a: {0 L

! u2 Z( f! c/ {pd.date_range('2020-1-1',
3 \4 e; O$ G$ A              '2020-2-28', periods=6) # 由于结束日期无法取到,freq不为10天
; f, ?6 R% E( \: X/ W& e! k. z  I6 A! U2 q$ U( {' L2 X
Out[27]:
' K% O2 ^) m0 k* _2 y( _( P* wDatetimeIndex(['2020-01-01 00:00:00', '2020-01-12 14:24:00',1 x+ {% T/ p" U. D
               '2020-01-24 04:48:00', '2020-02-04 19:12:00',
; l+ ~+ Z8 R$ [: p7 \! t               '2020-02-16 09:36:00', '2020-02-28 00:00:00'],- U/ N% t% M# k* L% [
              dtype='datetime64[ns]', freq=None)
$ j0 f- f' U6 u3 y, I/ F
9 P6 ?$ ?; w9 z1
: H# N4 f0 Y/ k: c- B20 B! v) x1 D8 @* U
3
: T- J% [& T# n# U' y6 J& X+ W4
' {5 U  _) C& t# g5# k2 J( U' M* @1 v1 t# r! D
6
: t# U0 F/ L: u0 [' I) M" R9 U7
! \! [. H5 F: t8
6 l2 J8 H, y' T9 p% \9 R; G; t9  h: X: n% J, X% m8 }
10
. {' _! v) d. h) o11$ R  E* x3 \1 S. V! F
12
6 f5 p# r6 H0 M" R3 {  z# x8 L137 u$ g  u4 _$ ^3 c0 c' V, F+ B
14) e/ k' `  g8 Y
15& M9 v0 R/ m# y0 ~7 ~2 G  Y
162 j1 w6 }- p/ D  B: p0 k
17
: k! z% D7 b( p3 R这里的freq参数与DateOffset对象紧密相关,将在第四节介绍其具体的用法。
& P$ U  s" c# ~+ }( t  ^5 D& k# c' P+ y! i; m4 {( r- o
【练一练】9 R; Y( F7 h* M% \# S! L
Timestamp上定义了一个value属性,其返回的整数值代表了从1970年1月1日零点到给定时间戳相差的纳秒数,请利用这个属性构造一个随机生成给定日期区间内日期序列的函数。* X8 M) H" E) B6 ?$ a( }

" c1 _8 d+ q3 ?" C' sls=['2020-01-01','2020-02-20']
+ m6 t  Q4 J! L2 q& M  t/ gdef dates(ls,n):( b! `9 X8 H% N! X
    min=pd.Timestamp(ls[0]).value/10**9
0 v0 y  d2 S* v' ]! M- @" u    max=pd.Timestamp(ls[1]).value/10**9
' |% n& H* ?! R/ `$ ]6 H    times=np.random.randint(min,max+1,n)' K6 [1 {1 j$ k
    return  pd.to_datetime(times,unit='s')% ~" `1 A$ x. Z, y( o
dates(ls,10) 2 V% P0 l- s  p
; M2 y+ M4 |, _9 c1 A  s
DatetimeIndex(['2020-02-16 09:25:30', '2020-01-29 07:00:04',
: g2 M- n. w* D. S) b& c               '2020-01-21 12:26:02', '2020-02-08 20:34:08',
, j8 o2 Q6 c3 b3 [0 p3 [9 R               '2020-02-15 00:18:33', '2020-02-11 02:18:07',; z5 g& ?, j! o5 d1 D0 b  `! o5 Q1 `
               '2020-01-12 21:48:59', '2020-01-12 00:39:24',
$ u1 a' ]  U6 `2 Q               '2020-02-14 20:55:20', '2020-01-26 15:44:13'],
1 d9 g8 J& H4 @$ o$ e! E              dtype='datetime64[ns]', freq=None)3 x4 b- N6 n+ q
1& e! b4 p7 Z3 u' S; C, }7 j
2- ?* \8 K0 N) e$ c( v4 j9 j
3
! d* |8 r) g4 O. A! X6 A4
( l8 G' O) o. c* G) H& h/ @- d# [+ J5 c3 P3 L5
1 J+ T* \, O' B2 F$ m) Z6
# i" _" I* t$ O, [7 O7$ h" e4 g' n+ A/ S0 J7 N
89 }3 Y0 H% Y1 t1 Z
9
' o  W  G# o. ~2 O- U4 h6 F- z10
' M  `, q2 P% C, r% t& ^% O11
& ]- \9 E' x9 U6 {+ @! |12
6 i8 _2 ^6 _( ~13, ?/ ?9 Z- Z+ \3 V  B
14
# u2 v) e- Z  o$ ?: f, tasfreq:改变序列采样频率的方法,能够根据给定的freq对序列进行类似于reindex的操作:, d7 Z  i4 B5 I% }& l0 z" w1 H3 @
s = pd.Series(np.random.rand(5),' ^0 H" H' x5 Y* t& c$ Q" w
            index=pd.to_datetime([' k4 |3 L5 P0 k; J: ^
                '2020-1-%d'%i for i in range(1,10,2)]))# c! H5 k# x# [4 l" u- J
( Z% P% s9 \9 x1 i  p& s

0 N  }+ m7 P& \5 ts.head()
! y5 ~' h$ b3 V: _7 F; @: S8 dOut[29]: ' E0 \  s0 A+ ]" q( i! B: O& @# I
2020-01-01    0.8365788 c4 w, @0 y3 \! M; f# u
2020-01-03    0.678419
; I& [( |2 I# J' o6 B2020-01-05    0.711897$ \7 {  r0 j  S. S7 g, O
2020-01-07    0.487429$ c3 C4 A, G* f% ^( p
2020-01-09    0.6047051 {0 S2 ^1 ~4 I7 L( j0 [& {
dtype: float64; R% Q; W/ _" N$ y
7 P8 o' t& M( I% _7 j
s.asfreq('D').head()  X( ]/ Q0 S; D  ~& W4 }6 E' ?- s
Out[30]: " V2 y6 g1 ^- e* A3 @* B
2020-01-01    0.8365789 a0 z. j6 n, |: Y! V
2020-01-02         NaN+ E9 M. _5 t6 w  ~, ]$ D8 f
2020-01-03    0.678419- g9 Q' ]9 C0 r2 N9 V' q) [
2020-01-04         NaN" ]8 ?+ `& ?7 e* K" P1 A+ Q  c+ s
2020-01-05    0.7118973 e2 F/ J" [5 n' j
Freq: D, dtype: float64
8 o. ~) `3 t1 y- h* I9 \7 K5 Y; A" j8 |5 b8 b# g- W$ q, `( i
s.asfreq('12H').head()
5 V; P- H/ x" G! ?+ h2 QOut[31]:
5 l' ~4 x* X8 b* `2020-01-01 00:00:00    0.836578: R! h+ U  Q3 D  O
2020-01-01 12:00:00         NaN
- y$ v5 X- A/ V' V( R+ o2020-01-02 00:00:00         NaN1 r/ V, s! n4 i' y
2020-01-02 12:00:00         NaN
" m. u7 [7 G* ?! a5 \: C! N2020-01-03 00:00:00    0.6784191 |  N0 ^, P- u5 j- K
Freq: 12H, dtype: float64
( P( f2 d! q. D  j4 L  m3 |$ W
# h- F' M4 S2 Z$ B/ a1) j, l' B; F/ T
2
$ f, ~& G2 s2 t, _5 ^( q" x- m30 C0 |. z  h3 }
4) Q; D; E% O- a4 z
5
' _3 j2 M- I) c3 ^2 f* H8 u6 ?+ z* \6
9 }# C  H4 E' t8 N9 k0 e# F6 W7
6 s. i$ ]) q3 B& d; R9 x" F( _$ j8# ]) M* O% v8 B; a, T/ k. K
9. O% C+ `2 |- N
10
2 D* k) ~$ V! u11
) V/ j8 f9 g% l125 N( B: [* f2 r. ]7 ?& g% x
13# m) c( I4 P0 w: r0 V: g
14
. O. H2 _# |  G. p  g! J) q/ ^3 e15
7 I" s- s1 }# e4 F* j/ s; q16( F1 k, Z2 k6 e
17- G- x9 k9 M- y) s3 }
189 A9 U- P$ i$ x
19
4 e% |* R- z' q# |8 ]4 Z20) P& I6 I- |# o0 B5 {; [
21
4 g, U. Q8 |& l6 \7 r22
9 N' J) w) d6 I( d* ^23& W% Y7 f) V5 y# _7 O+ J
24; ^5 ^8 B4 U1 N4 W
25
) l! b; g  L8 |% a8 C0 o* y26
1 `. T! @& B* G) U27
4 X9 b* Y% E5 Z, L6 c+ T' T289 O/ ~% P7 K& V. ^% ]0 Y: O3 q( n
29
) ^  H1 U$ {* w+ n30
7 ^$ P$ [* ~% _! K: ?31- Y; O: Z6 T5 _+ C* C
【NOTE】datetime64[ns] 序列的极值与均值
) w& o6 c5 N4 [, k0 D  前面提到了datetime64[ns]本质上可以理解为一个整数,即从1970年1月1日零点到给定时间戳相差的纳秒数。所以对于一个datetime64[ns]序列,可以使用max, min, mean,来取得最大时间戳、最小时间戳和“平均”时间戳。  ?' O# |+ k- ~) }" I

" Z9 w% X5 r7 q) E% i, }10.2.3 dt对象
) S" O* R1 ~: [8 D  如同category, string的序列上定义了cat, str来完成分类数据和文本数据的操作,在时序类型的序列上定义了dt对象来完成许多时间序列的相关操作。这里对于datetime64[ns]类型而言,可以大致分为三类操作:取出时间相关的属性、判断时间戳是否满足条件、取整操作。. ]) k4 _: M8 X+ k' l; [! h& p2 q9 R
* G3 @1 H, h& m% f
第一类操作的常用属性包括:date, time, year, month, day, hour, minute, second, microsecond, nanosecond, dayofweek, dayofyear, weekofyear, daysinmonth, quarter,其中daysinmonth, quarter分别表示该月一共有几天和季度。9 h7 {9 G& {; @6 r' E
s = pd.Series(pd.date_range('2020-1-1','2020-1-3', freq='D'))
3 F4 a  T# h+ l/ A7 M' Q3 n7 H  h6 t/ i) m
2 d" m. V$ \( `  _0 Js.dt.date
6 O% {$ ]' E/ B5 ~# u2 a/ IOut[33]:
9 {9 D8 I7 {, i0 R5 l1 ^9 d' h0    2020-01-01
8 O! t( H" `' E0 [6 u$ F/ n1    2020-01-02
" f, |, S. i- p* y- ~4 N2 C; _2    2020-01-035 g4 x. [8 k- b7 O7 j. c
dtype: object
% p3 b4 r+ _, d- t+ I( K4 K5 l" O1 L
/ i. v* F: [4 |) Q' Bs.dt.time
+ c" g0 D8 {* Z! H" |* A  MOut[34]: 1 [, B* T( l8 a/ ^
0    00:00:00
2 P5 q& z+ t5 Z( J2 @2 R$ S1    00:00:00# ^3 i( K# w6 ^3 k  f- K. p8 n1 V
2    00:00:00
! k& w9 H4 Z; y# K6 `7 w, Vdtype: object+ }* L3 T# U& h  ?3 C4 a

. [3 v+ ?& |2 G7 F4 r( B5 i! o) rs.dt.day: I# f9 F2 M& Q5 N8 q% E2 C/ n
Out[35]: 2 q$ l; V6 E) q$ \' }
0    15 b* I, v; S/ {) ^5 }" a2 |
1    2$ G1 t0 l: r, I! m$ @3 t. v0 b: ^
2    3& R1 l" Z. r# ~- M. b
dtype: int64
8 K% Q; f8 c# t( U: d4 M' f7 ~
* E1 Q6 H  ^5 f! }s.dt.daysinmonth$ F4 Z/ E  j2 c. Y. T; i, x! C. s3 ]! y
Out[36]: ' x5 |: k7 G' \8 O
0    31
' @+ r0 v6 N' w- s; Z0 W1 c1    31, r3 V$ @, T7 g3 T8 g$ [
2    316 P: @8 `" S& b1 }) K" k
dtype: int64
' T) s3 s, p( [' ^" j6 g; f- `% T# d, o7 J' W+ W7 Y% N' I
17 m/ x# K" Q7 H$ Z
2- Q1 P1 F6 _% ~3 p
3* Z& H1 c! u+ ^8 `, V! P* ?
4
2 X, q1 a, C* k6 r! r5$ |6 {1 H/ i9 b: m5 G
6
* g! f0 V0 v: p3 S* [: Y2 t% C7
& b' }8 j) q0 U& a5 a4 a8; ?: y! P: j  s
95 l# k0 x5 ?; \) `2 l$ n
10& T' i# u. W  q: W
11
; i0 W4 Y8 K: W; D12% o" w+ ?6 x7 A3 c
13
) K: q( h& R* S  [14  o! ]% g+ s2 t" O
15
8 V0 n( U' Q: Y' T& ]16
+ Z. e5 L8 `& @- ]/ K% e. e178 n- P6 r! w8 V, J
18
2 s: {  u* R7 x; W8 w19# N$ y8 k( F' F4 ?, D' `
20
6 Y' o- k9 a5 d; r21
% ~5 F9 u) h# ?  @5 e8 g3 h22  a7 d5 ?3 s# O/ F/ O. B* ^
23
- G4 P, {& w6 u) I) }* |, N" @5 j24
$ j! N5 a2 N0 @9 P# O# E  ]2 E1 k25
5 R( Y. Q  }" C% ^: G/ C% o3 O26( z5 d' V; V. N
27
5 g8 X9 F( t6 J6 {+ U& T, E! H28
5 X( V1 R# R. R1 Z# ~+ I299 Q; ?/ q7 x( D, s
  在这些属性中,经常使用的是dayofweek,它返回了周中的星期情况,周一为0、周二为1,以此类推。此外,还可以通过month_name, day_name返回英文的月名和星期名,注意它们是方法而不是属性:
" U6 ]# i. F( ~' ?
9 O& S8 r$ A, [9 S4 V. Ns.dt.dayofweek7 Y, u$ Y% m7 v
Out[37]: * L& [5 [( ?6 P9 J6 a' j% f0 V% |
0    2
  i4 C$ c# d5 J' e2 n6 X4 d3 H' p! ~1    3
" r" z1 x2 `$ ]  A% q! o" Y2    4
  W2 t1 @$ F, U* V1 b& n$ r, ndtype: int64
; h! J" a" \2 b+ |, }
7 s3 g9 C6 m& E, \4 T5 M( Vs.dt.month_name()
$ N& c+ G: @& g5 `& {6 E: [Out[38]: 9 O3 x6 }4 w& c" H9 z- V1 R  k5 k
0    January7 O3 M1 Z) v& h3 d! r
1    January# d  n1 }  d- q; E4 Y, \# \9 K
2    January
, [4 c+ ^3 S% x8 M; e$ T7 r9 o. Mdtype: object$ v' e2 n; F! s  ^( ^. K" t7 P* Y8 A  R

7 a# C! ?- t+ C' ys.dt.day_name()
% `( [2 ]  E' y( pOut[39]: 0 Q" {& m9 O) E/ X- z6 E0 K; S) @" U
0    Wednesday
# Z0 u# s6 v; B/ _/ c+ g' m; @1     Thursday1 ?4 O& o2 x' @4 M* J
2       Friday4 ]: [) T" T6 n
dtype: object  y  O& W3 `+ P- \

- {& e$ ?; U  B# ]1
: j/ _" U9 x: r. y! H# A, G! o) ^' h25 O/ @! |" D# p' r" a2 Q: B8 o
3% Q9 T+ {& O4 ^" j9 q
4
& y6 q! I: ^! e$ ?6 A( l# m7 F51 t5 h* |5 x; j$ H$ ]
6  t0 r1 J% L2 T" A. G
7
& n/ X- W2 R- W  n& ^" `9 z83 B& t8 G9 |0 j9 O5 p
9, I- {7 T# g5 g" N$ J; @: g6 Y
108 r8 H, g8 [& J; G6 g' p7 h
11+ }1 {& |( i) \: |2 L
12
2 h% t* ?/ [6 R% k1 ]2 _13
" z4 f1 d( c) U14
. N. I1 a# |' w* W# `% x2 K152 p% J$ U, t; K
16
% @: c& w: [" b% x' ?0 O: l17
: k. L2 a' h$ B8 `- |18+ H" C' k0 E9 {8 ~: M
19% ^" k4 r  {9 J. m/ o5 Q
20' H: @$ |9 p# P4 h3 C
第二类判断操作主要用于测试是否为月/季/年的第一天或者最后一天:
6 J; ^+ f4 W* I+ S# S  }- U% ps.dt.is_year_start # 还可选 is_quarter/month_start0 }2 Z( V8 {3 [" j" K
Out[40]: ( q/ A" }/ A8 X% ?; ?! t
0     True
" V: O2 [- n6 y$ u/ q1    False
: n8 }9 U$ ?; d% b2    False, X& Z* D# H' q) w# `! @
dtype: bool* b6 Z4 x" I' @9 r0 U5 e" X
5 T" Y- C8 @* @) |- ^
s.dt.is_year_end # 还可选 is_quarter/month_end. k. t7 d( u1 r& |% ?9 P
Out[41]: & T- f; J) O& I0 b- r
0    False
& b) T3 Y4 F, ~& O& y+ K: a5 v% l% [1    False
. L4 r! A, n$ b8 J6 y1 e( W% T% {. D7 ?2    False9 @: z' E; }: E' K; V* v
dtype: bool
$ t2 S' ^- ~- s; @7 |1
* R: o" ^+ M& A5 R9 W  P2
$ U  ?! k3 j( W3 m" C: C3% v" ^6 b- q/ }+ A$ v; h
4
4 h8 F& Z' g& a5 @5
1 e) B$ L, O# \0 n6) A2 w4 ^* |2 o
70 _( z- a6 D3 D" g0 [" i
8
2 ?0 i  w: ]" I% n  a0 ^. u9; ~* l* |0 n  C  \( T4 l
10
; P/ i2 H: B5 Y1 r  F5 N: n11
" F: J8 s+ T. X121 x1 \& y8 S( k. S$ b3 g% v7 C
13
+ |8 B% @# _) F! q& p3 {第三类的取整操作包含round, ceil, floor,它们的公共参数为freq,常用的包括H, min, S(小时、分钟、秒),所有可选的freq可参考此处。
5 \. O9 L  j2 {( _7 L) O  p& As = pd.Series(pd.date_range('2020-1-1 20:35:00',& j6 E7 j& Y+ E; U; _2 e2 A
                            '2020-1-1 22:35:00',$ Q" b+ l  t- E
                            freq='45min'))5 o- K# @$ B9 F5 c& ^: o! y* i

6 L+ K' H5 o+ b& O' u% h& C3 P2 ^0 r! Y+ d
s
+ u. `7 `( |' w2 o4 sOut[43]: 0 D+ Z, _% g$ m3 L7 m
0   2020-01-01 20:35:00! \$ T, g0 v2 v3 a+ w! v" p
1   2020-01-01 21:20:00
5 ~$ ^" t4 I9 o" d" z: v( H% ]* b- O2   2020-01-01 22:05:00; ]! E; Q7 m1 C: G" D+ |
dtype: datetime64[ns]5 U& S6 g0 s* b+ }
- x. m. T4 p9 F$ I6 ^8 M; E8 Q1 L
s.dt.round('1H')
" t$ P/ R9 x# F0 g: jOut[44]:
  F/ p( a+ i, _) |6 a" h( v/ H6 f0   2020-01-01 21:00:00
9 `) l& f: e7 N1 @% O! P1   2020-01-01 21:00:007 H' o  ]: Q+ ^+ l
2   2020-01-01 22:00:00; x' W2 K. k( n
dtype: datetime64[ns]
" R" d7 p1 P( `6 g% h, C  ~3 \8 [4 W
s.dt.ceil('1H'), Q( h$ |5 y5 [$ R2 U' s+ r4 l! f
Out[45]:
8 ~# t5 g" @- C* L  I1 d0   2020-01-01 21:00:00
; V- @9 v% y6 _5 Z: R. @1   2020-01-01 22:00:00
: g8 Q$ _( G+ k9 R, c2   2020-01-01 23:00:00
/ I, C# T& n; m! wdtype: datetime64[ns]: O1 l( K: u: W0 F6 [

0 Z: t$ O- j8 m. b& i# Ps.dt.floor('1H')
3 ?  {3 {+ N3 Z$ H% L. o7 i: p) j4 ZOut[46]: $ b9 q  x6 H# H, i, i5 d# {
0   2020-01-01 20:00:00
1 i6 @% v" J8 T1   2020-01-01 21:00:00
( k% |5 ~5 _9 Q5 R" X2   2020-01-01 22:00:00
; a8 N8 U) A! ~: e: tdtype: datetime64[ns]
3 w7 N$ r7 v* w( E
8 ]( l7 P2 a/ \. t+ A; X; G1
; `6 N% \$ q! t. l) G4 j: Q% m$ Y2$ g1 F9 z! F* o" C) {) c; s' B
3
: g  C5 I( ~8 {( Z6 f% X! t) D44 D1 q. X. S5 a0 t
5! n9 Q" V. V8 V
66 r. g; ]. ?. ~2 h# U# j# ~
7
2 A/ S# j, n/ e: p; N2 W8
4 h* U9 d8 |8 S) Q  y! q9
4 q) k$ A3 e+ U9 `" W102 V2 T) L7 n2 X/ V
11" g* P7 c& l6 L5 L( d
12; j7 h( ?( @2 V" x+ h
13
8 e; F: B+ ]& L( d# E0 j14
4 C8 H& M! u1 o) V; _* z" j6 Q15
" }5 F0 O: q, U( {2 R16; ]7 K6 d3 N9 T6 |) |
17
1 B+ O, Z0 j# u& H0 b+ U, p! j18
! @) ~  N0 J" k2 @$ S" N# k195 D. ]% {) m3 a' Q% Q0 U
20# `( M  z6 E8 m) M) }% U4 g: \
21! n2 h* I4 z7 t3 A1 Q% @. ~
22, ]. `3 N+ g- F
235 n1 Q- n: p5 y- E5 }- i& t
24% [/ L! s5 H9 K& d
25. f7 N1 L4 C+ f" W6 {
26
8 W/ K0 O) i: ]( k  L7 C27* O& |% y+ ?! U8 d% u
28
/ A7 R6 R# S  Y29
$ K) ~$ M  ?) g5 N/ Y* y' o30
" b. [- R8 ?, I( p) P) ^1 ?. V31$ s" i- V) H- U, }6 }
323 x6 B* v; o1 d2 ~. n! j
10.2.4 时间戳的切片与索引- x  o2 _- B' f5 J! P* ]( n( R( N
  一般而言,时间戳序列作为索引使用。如果想要选出某个子时间戳序列,有两种方法:% h* Y" G1 Q1 v( i( s# ~  W' z  v2 a
. N- t+ ~4 }+ x& i6 e. b/ `
利用dt对象和布尔条件联合使用0 p: }) R8 l) Z) h# x$ S, O8 _
利用切片,后者常用于连续时间戳。% f+ |7 n: x/ W! e4 Y
s = pd.Series(np.random.randint(2,size=366), index=pd.date_range('2020-01-01','2020-12-31'))2 v- W! z" q$ {# o: D
idx = pd.Series(s.index).dt' s* b& I# U# s* e% e  [" m) C6 O
s.head()
1 P5 O; R% C& l- Y" k  S3 L& y' s% ?' w% R' D# v0 p, X6 X
2020-01-01    0; ^% |" m& z: c1 W# F
2020-01-02    1
& k2 ~+ d* p  d2020-01-03    19 z% d" V1 o' Q
2020-01-04    0
: ?# _# q4 F8 N3 E2020-01-05    0  X6 T* p# P9 j" H- {7 P8 M0 |
Freq: D, dtype: int32
% c6 w( S: P: T# u+ G. A9 R1" x% H) N2 \4 g0 ^5 k" C
2
2 r& u7 @( L0 g2 n0 G! E3  f5 h8 S) X" k: Y- m: B
43 g& o- L7 \' ?! i
57 x8 L+ `, Z6 ~5 ?4 J/ j
64 d3 b/ O3 w$ B! w
7, S9 y5 E( r2 U' _3 k1 Z6 j
8
' M& }7 O8 U+ s  W1 M9
! H5 n9 w2 G4 n/ q# I+ _" \; @- F4 S10
- v+ H2 _$ \$ G1 |; z: kExample1:每月的第一天或者最后一天
# \* H) ~- W/ [& L6 U6 B9 }2 H
8 J6 f# |% m# r+ Hs[(idx.is_month_start|idx.is_month_end).values].head() # 必须要写.values
% k! J2 L8 Z( i; y2 ~3 Y" P; ?Out[50]: : ~+ A- G% N7 T9 Y) j6 n: @
2020-01-01    1  z% i# {. v9 s& q# K
2020-01-31    0* q3 G1 F5 u2 T; w6 V
2020-02-01    1( I- V, ?+ K& ^8 d0 }# ~
2020-02-29    1. L1 b# U- X5 _8 S* ?
2020-03-01    0% I; O2 f% k( @- I1 J5 p- O6 }( ?, O$ \
dtype: int32' R% q& u& o/ Z: l7 C
1
4 y( r1 z0 K& K4 [' L, n27 J% N; x7 u* m+ N1 r6 K
3
9 E6 m  Q+ ?9 Q( C& v% G# X: D% h49 b. I0 B# o. |  t& x4 w7 u0 v
5( s* `1 V$ y6 t+ J* l4 |5 d
6
7 ^% |2 U7 J8 n& c4 |7& W* C1 @. v  ~5 q0 P
8# Y4 A! L, n4 I9 C# d
Example2:双休日
: k+ z! c' X1 x2 {$ g+ i3 @" Q8 e% K5 z8 s9 \0 \4 O
s[idx.dayofweek.isin([5,6]).values].head()- H& h6 R4 W0 G7 E. V5 b
Out[51]: & Z8 `  D( i) E- Y
2020-01-04    1
$ s$ R% N( R  K3 O, O3 p1 ~2 k8 d2020-01-05    0
- k& }! [  o2 B5 r* t& ~2020-01-11    08 n+ a) N' w0 o- H' N+ Z5 S* B
2020-01-12    1+ |$ o# @+ F9 i- [4 ?6 G: c# f
2020-01-18    15 ^: r# {" g7 O7 ^- \; V
dtype: int32
* Q9 N8 W0 Y* _* Z5 {1) j' b* @$ n" e( x1 K
25 w$ R" k% C6 u( k
3
/ c2 q1 @2 f0 M2 J0 z3 O43 `( n& m7 C9 D2 A- j
5  i- J5 }, _/ g; C5 B$ F3 X7 [
6: k3 L, V& A/ K$ w- s
7
& z- I& K9 }; @& e! U6 t7 I! z85 G6 T  x  R0 d9 \' f# d
Example3:取出单日值: A: X- B, S: P7 G

+ `; m; p7 H+ R0 h- V( Is['2020-01-01']. R7 e7 u- O2 x0 u# s: M
Out[52]: 1. p' J0 n0 ^! N" D5 x
4 h7 o& i7 T# m# x. i) y
s['20200101'] # 自动转换标准格式
. b8 M- o; Q: y5 eOut[53]: 1
8 B" w: O2 B# E% j2 c1% a, {& G: ?9 i9 ]
2
5 h1 S" a/ p) v9 A3& B3 ~2 C" z( n( }. s1 v
4$ |+ z$ \4 O; i) W
5. T) G# N4 f4 z7 k) y* U8 ^: s4 x, i4 Z
Example4:取出七月" L6 m2 Q" p0 z* `5 q

# Z! f4 z9 T# x5 I9 Q4 d1 Vs['2020-07'].head()
7 j8 [3 U* L& h0 C/ VOut[54]:
/ T2 [4 n# y0 q( p2020-07-01    0
" x5 P- P4 T2 ?6 Y& _2020-07-02    15 C" h- c& [6 E3 o' O* H
2020-07-03    0
) b" X1 y  [  P/ O. e- y2020-07-04    07 s. W5 w" ^, ^& S- A7 j# k
2020-07-05    08 H) B, x6 U0 c3 m
Freq: D, dtype: int32$ U/ N3 I+ [9 U$ r% e
19 n6 K) m# c$ H4 c
2- ~3 a8 T5 Q4 g# ~/ X( R5 l
3
( j6 _) ]5 K4 {9 t' O4
' C( c# i6 s5 r1 m8 R% u5
3 \' ]& Z6 g; r' R3 u( g8 f, f1 G6) q+ b+ J4 v6 X/ t# m' |
7) ^6 Z+ {3 x( n' B0 D/ K) n! k
8
' v6 |0 r% k( _) A' dExample5:取出5月初至7月15日
9 @6 L7 I/ m4 j' m- A: B% a( S1 t. U8 W3 n- N6 `+ I# R0 d
s['2020-05':'2020-7-15'].head()
8 k, Q' X( s5 F* @% {9 z/ WOut[55]:
' B+ \! i1 p! y, d5 H& ?7 u9 w% D2020-05-01    0: `4 ^0 d3 k' \) n8 m: {
2020-05-02    1
% m* q5 T6 P# [/ }# |& r( k2020-05-03    0
8 w* I8 [6 h7 x# C2020-05-04    1
# }. s; Y! R7 b% p0 w. k2020-05-05    1- g7 E) z/ V8 j; o  o! c7 W- r! J
Freq: D, dtype: int32( u8 \5 @. z' A7 I
8 B- ]2 L8 N: c" v7 e1 t0 \2 M
s['2020-05':'2020-7-15'].tail(). w) U9 Y7 x1 O5 p$ m
Out[56]: - h0 t  [/ W( \7 H9 C
2020-07-11    0" z7 V* K, _1 C- B" ]6 b
2020-07-12    0
' o% p# D/ E! {& j; J2020-07-13    1
' T  M; C. o5 y9 l3 q$ p2 Q2020-07-14    0
. X9 ]9 I. k# H5 L/ }$ [) U# u/ r$ E2020-07-15    1
. Q& E4 ?) o$ K0 bFreq: D, dtype: int32
  |( Z) \2 b6 g: U% X1 [* G  c$ d& b! z6 r% ]
1
. J. A5 X% J2 G8 g2
! \" b  X  M) t3
% f' I3 x- N( g, e0 b0 \4, a- p: g8 ]  x! H/ w4 u% ^( \& {
55 x! o, M7 \, ?+ ]) x
6
5 n2 u6 q. Y7 b7
: ?5 r# B1 P9 ?87 M8 v+ U. _$ v3 y
99 n. D! b- o9 t
10
$ L' a' x0 Z2 x* e5 q. _1 b118 d' e( [2 C& Y: O) {* }
12
; j' h( U# |( V134 B0 }2 `" g6 @; p
14
+ O& a' y/ G+ d- c15
2 Z, r5 s* Y& t: d; B1 K7 @16) A& u- l& g- ~
17
4 B8 ~+ w: ?% O  C- g! w% F10.3 时间差
: R; O' Y, e- K: ^8 m10.3.1 Timedelta的生成
1 A% `" h+ u5 t8 T1 U( N7 Vpandas.Timedelta(value=<object object>, unit=None, **kwargs)/ B7 J0 z, c9 C& t# l, I0 k' j. j7 e
  unit:字符串格式,默认 ‘ns’。如果输入是整数,则表示输入的单位。
! G: w) E: w3 |- G+ O  可能的值有:
3 p" J! D8 v/ H' A, g. O1 ^+ l
/ h' H: U/ l: D: Z‘W’, ‘D’, ‘T’, ‘S’, ‘L’, ‘U’, or ‘N’+ g$ B  X% s% k9 t
‘days’ or ‘day’
+ f8 p+ K5 Q- M) }‘hours’, ‘hour’, ‘hr’, or ‘h’, h" l4 A& X' j' b9 j1 H
‘minutes’, ‘minute’, ‘min’, or ‘m’
: T$ g: e  Y3 m. C2 \‘seconds’, ‘second’, or ‘sec’4 [, F9 v$ l7 l9 q( r
毫秒‘milliseconds’, ‘millisecond’, ‘millis’, or ‘milli’
8 T. F# @% P7 y# ?微秒‘microseconds’, ‘microsecond’, ‘micros’, or ‘micro’
7 r" O$ U+ i* \8 k$ C纳秒 ‘nanoseconds’, ‘nanosecond’, ‘nanos’, ‘nano’, or ‘ns’.9 E% f8 ~+ f4 H* O; y0 e5 n: {1 }/ |4 f
时间差可以理解为两个时间戳的差,可以通过pd.Timedelta来构造:# f- Y+ Q; v8 n; A$ d. t5 B
pd.Timestamp('20200102 08:00:00')-pd.Timestamp('20200101 07:35:00')3 M; x7 _9 s- I. V  ^
Out[57]: Timedelta('1 days 00:25:00')
8 I' P# a9 ?" ?9 J4 z% P
/ z& h6 x* S8 A) a. Upd.Timedelta(days=1, minutes=25) # 需要注意加s
0 e$ C: ^  b( V" R, G5 qOut[58]: Timedelta('1 days 00:25:00'); E, H4 r6 Q3 \, ?$ H, T

. r' X9 D# T& o- ^4 spd.Timedelta('1 days 25 minutes') # 字符串生成
  ?2 a* o/ ^; eOut[59]: Timedelta('1 days 00:25:00')
5 ^7 a* g3 a; m/ A2 o! `
+ H' t( ^, e( e3 o0 ypd.Timedelta(1, "d"), Q) O+ A8 R5 B
Out[58]: Timedelta('1 days 00:00:00'), {& h" B1 e! L$ }' R' R
1% q& `3 c% @7 u$ r* F/ `. v/ F7 P
2: f8 _; u* S( r5 _8 C$ a8 S
3: v; C/ C! p) x# N( ?7 R$ V
47 n# `; ]! l  z
56 d0 H! I6 X+ J; B- H' T4 Y0 W" q
6
( i4 |! y, X$ {- D2 @6 G3 m7
1 c: B; e$ L  I  C8
8 {( G2 b% I- c7 q, v8 ]) R92 O- Z- ?. P9 ~# s+ [2 Y8 Y+ l. |
10
; @) ]4 y/ l6 T! J; i118 s9 y4 b* R8 x3 r) e! r3 f
生成时间差序列的主要方式是 pd.to_timedelta ,其类型为 timedelta64[ns] :' L' ]8 o" \) B& v
s = pd.to_timedelta(df.Time_Record)9 y" y$ h2 v  l$ I  B4 T
( Q2 L8 c2 p* P+ U
s.head()
9 Z" g9 G* Z" q5 k1 @Out[61]:
; C7 J8 [' d" `: ]0   0 days 00:04:34+ j% s0 d( p6 ~% G0 Z1 C8 T
1   0 days 00:04:20$ S0 x4 G6 x9 p4 D3 B- b
2   0 days 00:05:22, w) e" N6 o7 k# l$ O  P
3   0 days 00:04:08
# A+ w5 k' I- K6 H/ v2 B" V4   0 days 00:05:22# R# l) @( u/ a; Y& y
Name: Time_Record, dtype: timedelta64[ns]2 k) `% A7 C" H+ G8 C0 e
1
$ Y( p, l2 a7 t$ J: w2: m! P3 Z8 o$ {$ R5 r1 h
3
1 E( q9 {$ J$ d: G) y9 J4: c* }7 N1 I2 b. e" s& l$ Y
5
& N5 v0 A* H4 ?( }61 K- r, y$ `0 o& F' Q
7
% Q% @6 c+ U* x1 [9 s. D( i2 L8
7 D+ w2 H* ?/ `3 z9
+ v1 g5 r. h: ^% z) E, \' B. y10
2 r; U4 i1 W. K' {+ e1 ?与date_range一样,时间差序列也可以用timedelta_range来生成,它们两者具有一致的参数:7 d; |; B7 t5 D* x5 f4 Y
pd.timedelta_range('0s', '1000s', freq='6min')
7 m- k; m- v! w7 a, lOut[62]: TimedeltaIndex(['0 days 00:00:00', '0 days 00:06:00', '0 days 00:12:00'], dtype='timedelta64[ns]', freq='6T')
% m/ W' p6 v1 @4 X1 t
7 P3 k3 n( u) P! [pd.timedelta_range('0s', '1000s', periods=3)
7 M% l9 r, K5 p+ O: N1 N% XOut[63]: TimedeltaIndex(['0 days 00:00:00', '0 days 00:08:20', '0 days 00:16:40'], dtype='timedelta64[ns]', freq=None)7 p. \, ?! u! j9 v6 p/ z
1
* X% @, ?# V. A( i/ a4 R2' T( K5 {8 r" _% J8 a" ?8 _6 M) e
3
! q/ A! |% ~; [2 n) P+ d4+ K2 z, w6 X4 D. n! ~, y
5% U7 G; R% m* p, t, W0 X" M
对于Timedelta序列,同样也定义了dt对象,上面主要定义了的属性包括days, seconds, mircroseconds(毫秒), nanoseconds(纳秒),它们分别返回了对应的时间差特征。需要注意的是,这里的seconds不是指单纯的秒,而是对天数取余后剩余的秒数:
9 }% Y3 a) G* as.dt.seconds.head()
1 {. ^/ ~: F; L( YOut[64]: ; o& e$ W6 I3 U4 V9 @$ d2 C' g8 l- r
0    274
. X7 U( n+ g; X7 H1    260
! }+ g6 ?* G  T3 @6 U5 b8 M2    322$ b" X# z% p2 |* U6 m
3    248, e% d+ t9 H3 h) C) g
4    322
! l% f  u8 {* I( z+ X; _Name: Time_Record, dtype: int642 a* J, @# O; c' y: I5 U+ {
1( B- Y2 z' y0 z  D/ G
2& i8 ~/ b( s+ g# ~
3
0 [/ ~) K  t6 r; L4, _. P8 C7 R) v  ]3 a, u( d& S5 S
5# ]0 K5 q. z7 [$ @7 {
66 x; R9 x& B& P
79 q* ]5 f9 s+ \* T( x& f4 r( ?
8# j, L0 c- H2 [$ X
如果不想对天数取余而直接对应秒数,可以使用total_seconds
$ H7 O: \  W; m3 q* I3 z, \" M$ @6 l5 K8 p/ R. _$ e
s.dt.total_seconds().head()
1 X6 p4 `  W- i( _( j& ~4 @( HOut[65]: 5 e# ^9 a8 s9 U
0    274.0/ k. B1 f$ Z( k! E2 p( j. E
1    260.0+ E- n* V" a" V) ]8 \
2    322.0
1 H5 J/ |9 Z6 D: [2 {% |) T7 a5 l7 \- }3    248.0
% }/ n3 j. P, Z% l# Y( e4    322.00 t7 H4 H# G& Z1 Q: m
Name: Time_Record, dtype: float64
, E: D; E2 [- E) f17 k$ b9 {( m2 ~( |
2
) e* T" O4 v: C( |9 G. J  a3
3 Z' |' S8 X9 e' S/ l4
6 t# e5 o4 f; r. g2 b: |  d. a5
4 u( ?. i$ Z( S# C! U$ }. ?6
- m$ W* W0 H. k# M8 T" h' R7
/ Q; g* W  P2 i1 l8) r; E* ?( l$ J/ \  L3 Q- G* Y
与时间戳序列类似,取整函数也是可以在dt对象上使用的:
- s) S4 ^/ t, l$ k9 B& s7 [% }, H4 D& @0 z
pd.to_timedelta(df.Time_Record).dt.round('min').head()" v; v9 p/ R3 ]$ F* [3 @, j
Out[66]:
4 ~: m+ v$ |- f6 H6 o0   0 days 00:05:00( Q2 U# m3 s0 F+ c: U) A
1   0 days 00:04:00
& d* T7 l5 ]: n5 r2 c2   0 days 00:05:00
6 w( L, m7 a8 S7 Y3   0 days 00:04:009 _; y% K! A& U$ p3 b9 ]7 D
4   0 days 00:05:00
9 T) y. K6 H% [2 a- C4 sName: Time_Record, dtype: timedelta64[ns]6 k! o5 P' O! t
1
3 L3 l8 |; q0 B1 o+ E2( _, I; r; v+ z& ~0 D; E
3; F# K& Q* o+ c8 ?6 w) F3 y0 k6 ^
4  V# G# o* i$ {/ t+ f' G( @8 \" V
5. Z: f# X$ q5 ]* j) U" Q, n2 `
6
) r$ V4 r; L0 H0 y+ O" o9 E7
! r* Z$ E% o! d3 o4 F% x: l5 t0 w86 ?  ^* ~* O1 u6 X
10.2.2 Timedelta的运算. H/ k* C& c4 `" q. R
单个时间差的常用运算,有三类:与标量的乘法运算、与时间戳的加减法运算、与时间差的加减法与除法运算:
1 i6 Q8 f, V9 Ftd1 = pd.Timedelta(days=1)
# n- `0 `3 X7 Z3 B4 k$ }td2 = pd.Timedelta(days=3)4 V9 E9 v& s9 ]9 R5 C/ G1 ^$ z& Q, Q
ts = pd.Timestamp('20200101')( l0 w6 w$ z( b& L

5 l$ ~) B7 U  y- k1 m5 j7 U2 ttd1 * 2
5 L* }% A7 B& Y6 r5 cOut[70]: Timedelta('2 days 00:00:00')
* t& h9 d; C+ J. N& @' H& q
7 Z' ?3 N% ]5 ~td2 - td1+ n+ N. r1 ]/ t4 V1 g' q& U
Out[71]: Timedelta('2 days 00:00:00')! `8 f9 |: C) E( [8 I

) M$ W$ s7 V/ A: v5 }( J% Zts + td10 F! X+ v3 c$ ?
Out[72]: Timestamp('2020-01-02 00:00:00')
* K! F) y$ [6 E  \) j* M+ U# x
! o; V, n  Y% Y& @! dts - td1/ u3 n- }3 t; o: b3 [: y: _
Out[73]: Timestamp('2019-12-31 00:00:00')
+ o; |1 K+ V( n; q1
7 e8 n% I4 m9 A0 C; S# A5 [2
# a! {6 z# ^; b+ X! J  i3 f: k3
& `9 J. U; Z3 C+ c. X4
% D( i. O# q5 L2 c: q( S5
% m: a, T3 G% \& @% m7 d7 z5 O61 V# I5 T4 s, E; z
7
4 F* ?& T- ?! }9 e+ U" o4 y# P8
( x9 M) i1 s( ~2 T9
& U8 y# o+ I* Q, C. @106 |" Y7 ^+ s& q" `) V/ o$ z. P- K
11
) x1 ^- W* ?5 ]2 l/ e12
2 e- _" G1 `1 z0 Z9 Y13- ~- w- x! I1 j: W  }1 y& F; f7 n1 B
14: \, r; n- @. m1 A# P0 \" ^
15: I4 @. |9 ]" e! w9 m3 h6 S
时间差的序列的运算,和上面方法相同:  C8 b9 h$ F. k3 y
td1 = pd.timedelta_range(start='1 days', periods=5)! x- R# z" Q( V; H
td2 = pd.timedelta_range(start='12 hours',2 k# {3 l- L" ?( n- z- m7 s- G
                         freq='2H',- R: t) T" {/ i
                         periods=5)) @/ b, M0 i/ I5 C
ts = pd.date_range('20200101', '20200105')2 d) U1 l# G4 c7 K  \* p
td1,td2,ts4 x+ Q: f& P# N: I0 j! X* ]: y

8 u) m# C' c( K" ]% wTimedeltaIndex(['1 days', '2 days', '3 days', '4 days', '5 days'], dtype='timedelta64[ns]', freq='D')
, [; Q: z  u. Q- [+ BTimedeltaIndex(['0 days 12:00:00', '0 days 14:00:00', '0 days 16:00:00',
2 r! q7 r6 q: h' P                '0 days 18:00:00', '0 days 20:00:00'], dtype='timedelta64[ns]', freq='2H')
9 e# v$ e; Z2 i; ?" NDatetimeIndex(['2020-01-01', '2020-01-02', '2020-01-03', '2020-01-04',9 z/ r2 Q8 k8 w% m$ g# x: o
               '2020-01-05'],
4 o+ |  S/ }9 d              dtype='datetime64[ns]', freq='D')( T, T# r) N' X4 r9 r. D
1
& @/ [  l# \0 C9 M2$ m8 J" n0 L2 r) n$ R( O' N
3
, F4 @* b' W' M: X! [4
* R0 Q/ }- N, \2 [0 t5
# w, `& S' Z1 b4 T7 z6
/ U8 H3 o0 t1 v2 \2 r) |7! p9 d6 _: Z5 W, F2 M/ g
8, X1 b- i5 D7 Q% \3 B1 }$ ~  t4 t
9
5 o9 l. v5 v4 @: e9 k106 |0 n" C0 V! g, B1 c0 V1 I8 i
11& e( [$ n# I8 Q  Q( M$ ]( K
12
: H/ U  {3 j- x7 F, \% |+ a13& r% C+ [2 Q0 \( b# u# S
td1 * 5
  L  j0 P. `; J5 UOut[77]: TimedeltaIndex(['5 days', '10 days', '15 days', '20 days', '25 days'], dtype='timedelta64[ns]', freq='5D')
% h( [6 ?6 Z1 M2 Q7 K, \* m5 ^- z4 }& J
td1 * pd.Series(list(range(5))) # 逐个相乘
+ M1 y# B# z5 l2 Z( Q4 E9 OOut[78]:
+ O; u2 F' g4 x8 K9 L% ]- A& a0    0 days
: V5 R# w5 A$ B9 P* l" V8 h1    2 days( j7 z7 m: J% a. X- ]- \
2    6 days
% S0 [8 u" B5 W8 W3   12 days
/ N8 F6 X% C7 b1 Q- l2 W4   20 days
& T7 A# e) g1 `1 O; Kdtype: timedelta64[ns]/ M& s$ l8 G- [: C- B4 y

1 Q  v. ?- A# V" }, c/ ?td1 - td2- l/ L  b6 a( X, F1 W8 P9 ~* m
Out[79]:
/ @" b5 K* P4 K# G" f$ JTimedeltaIndex(['0 days 12:00:00', '1 days 10:00:00', '2 days 08:00:00',( U) x% q- p7 n5 _8 H5 z
                '3 days 06:00:00', '4 days 04:00:00'],, a+ B. A+ D" b7 N4 N# S
               dtype='timedelta64[ns]', freq=None)! c3 e8 d7 g  R" t0 a

" C' ]9 \* h( d* f' E; m3 S& I6 mtd1 + pd.Timestamp('20200101'), Z, G% ]9 j! p4 f9 g$ t2 C
Out[80]:
7 |  g4 p/ w8 r* O# G2 U8 P1 uDatetimeIndex(['2020-01-02', '2020-01-03', '2020-01-04', '2020-01-05',
9 b, Y" ^$ ^" m/ R9 U0 D0 h+ u               '2020-01-06'],dtype='datetime64[ns]', freq='D'): ?/ m3 n0 m: F6 @
5 b  m$ n5 j8 a' e9 v0 r
td1 + ts # 逐个相加+ d6 \- ?( s1 H0 v9 V( F
Out[81]:
3 \7 ]0 I5 s6 p" n. s# a, I5 R% zDatetimeIndex(['2020-01-02', '2020-01-04', '2020-01-06', '2020-01-08',
3 ]9 n- J, J" e' T, i               '2020-01-10'],- Y3 v% t8 U6 K; `1 F, M# W) |
              dtype='datetime64[ns]', freq=None)
7 a$ C( @. w( [, k
' ^/ H! x* o% b$ T5 p/ |1
% g" W+ z7 e4 z9 `2 f" Z2 b25 k1 d8 z: h- B7 a
3# t: y; N$ K! q0 w+ S
4+ \- H6 B6 Z1 Y+ q8 t* ]7 `$ e
5
) U; X2 H$ U" r9 Y64 {( E+ v, T1 _6 K  q  H5 Z) t  w
7( g8 H6 r; y/ [5 X
84 c( e7 {2 N) H* v
92 l% D; ?6 E0 S! x( ~9 B- ~
106 D' Q& B5 U2 A3 v& H* D! j; J
11/ e2 c5 a- l& n
124 N4 H5 ?% X3 i: Z% L
13
3 w+ q" t  v- p8 s. k' n. z, Y14' p6 ~5 q6 z4 B, C+ N
15
0 I9 D/ }$ G% x! X16! \/ B. K, C8 {& `( T, x2 u* w
17
' y# g* x3 ?* B! U" Q18; |/ M$ l+ ?; U' ?
19
/ \. q, F( m8 x; j4 d20$ q# m( C+ @/ D. |8 B/ u: m3 H
21
+ G/ {# ^+ W' M  {! V3 u221 C- A  w! ~1 l$ g, G1 x
23. o3 C  D# n7 C# l( F
24( {; M) k0 z$ d: a
25* e. B2 Q( m$ e4 l) s" `3 q) \
26' R  u! Q! a" `2 K( C
271 c6 D) K  N: g; x  o! [. T  D/ @% Q
287 n/ b% U4 B1 e, f* L0 ~0 O
10.4 日期偏置9 x3 a+ @2 Z7 m& u6 z" w
10.4.1 Offset对象
% b5 }, |( w; ~& c2 Z+ X0 c  日期偏置是一种和日历相关的特殊时间差,例如回到第一节中的两个问题:如何求2020年9月第一个周一的日期,以及如何求2020年9月7日后的第30个工作日是哪一天。& X: i+ @8 ?8 P
3 V! u+ `! w; Z2 m( K5 S; z8 [
DateOffset 类有10个属性,假设s=pd.offsets.WeekOfMonth(week=0,weekday=0),则:
1 G6 U' L0 _: M+ k! B% {
  b: \7 y8 R9 M- p/ f4 |6 w# W  v2 zs.base:<WeekOfMonth: week=0, weekday=0>,返回 n=1 且所有其他属性一样的副本! ^- n% H  A6 D/ b
s.kwds:{‘week’: 0, ‘weekday’: 0}
6 P3 l+ i* V, Os.wek/s.weekday:顾名思义, r* j% `4 l; X# |8 e
有14个方法,包括:
5 r  o. d2 h& p: _+ _, U7 Y2 J8 w' N' i' Y
DateOffset.is_month_start、DateOffset.is_month_end、DateOffset.is_quarter_start、DateOffset.is_quarter_end、DateOffset.is_year_start、DateOffset.is_year_end等等。+ b- G6 H) m- g2 g
pandas.tseries.offsets.WeekOfMonth(week,weekday):描述每月的日期,例如“每月第二周的星期二”。
+ S' o- O$ i9 ?5 I
9 m" }! ]7 i8 F有两个参数:$ t2 H" J& @# f. z, w+ x
week:整型,表示一个月的第几周。例如 0 是一个月的第 1 周,1 是第 2 周,以此类推。4 x7 k0 E$ @. f; i: O5 L  ^  [! c
weekday:整型,取值为[0,1,…6],表示周一到周日,默认取值为0(星期一)
& v7 _% P7 E* [" w1 K+ K$ wpandas.tseries.offsets.BusinessDay(n):相当于pd.offsets.BDay(n),DateOffset 子类,表示可能的 n 个工作日。
9 [' v5 D: J3 W
4 o  X& s! a0 u) Q! g1 zpd.Timestamp('20200831') + pd.offsets.WeekOfMonth(week=0,weekday=0)
/ [# ^" h) t3 T- i. r1 ^1 z1 A* ZOut[82]: Timestamp('2020-09-07 00:00:00')
! F6 D" a# D0 {
/ i; Q$ J. Q, A6 hpd.Timestamp('20200907') + pd.offsets.BDay(30)
+ M8 G; P  [9 ~: H. a* zOut[83]: Timestamp('2020-10-19 00:00:00'): H' Q( x; o6 ?& Z3 v6 k
1
- _7 V: U5 Q9 n6 I# ?4 u2
+ e& E* G: F' S2 I' h3
# ^7 I& s- b1 d/ c% s' M; p4
4 G3 w7 q- x( X% W5 O+ `5
) x: L4 m$ u2 \% y3 m! S4 Q- {  从上面的例子中可以看到,Offset对象在pd.offsets中被定义。当使用+时获取离其最近的下一个日期,当使用-时获取离其最近的上一个日期:8 o/ a/ ~& q9 d6 Y! b% \5 Y4 c

8 S6 B# j* I$ Lpd.Timestamp('20200831') - pd.offsets.WeekOfMonth(week=0,weekday=0)/ Y! D5 I" ^" C& ]# b
Out[84]: Timestamp('2020-08-03 00:00:00')
! {& J' K" I. Z: ^8 I
' k2 p2 F' ^% U$ B  Mpd.Timestamp('20200907') - pd.offsets.BDay(30)$ a* J5 s% a+ @2 b
Out[85]: Timestamp('2020-07-27 00:00:00')
! U# Q' C8 O+ G9 m8 M( G) L4 [/ G- I" B7 S* H$ }
pd.Timestamp('20200907') + pd.offsets.MonthEnd()
7 r2 z) q2 r( r4 n  K6 _8 SOut[86]: Timestamp('2020-09-30 00:00:00')
  C, x* y( p* y8 S6 B1
3 r( _: w& M5 s& J4 b2
% W  M8 u, t* h4 g; ~' R4 M3/ }) k3 v+ i( M# l' t4 F
4* e/ j7 Z. `% R4 c* P
5
  O7 S, a# I  e1 m- {2 L, p/ H5 F# o64 Q8 C9 s+ D$ R' h
7+ d" k; z, @3 \4 `8 p
8- t3 r( G  X5 p& m: r2 A
  常用的日期偏置如下可以查阅这里的DateOffset 文档描述。在文档罗列的Offset中,需要介绍一个特殊的Offset对象CDay。CDay 或 CustomBusinessDay 类提供了一个参数化的 BusinessDay 类,可用于创建自定义的工作日日历,该日历说明当地假期和当地周末惯例。
' u7 D9 ^' u( W  其中的holidays, weekmask参数能够分别对自定义的日期和星期进行过滤,前者传入了需要过滤的日期列表,后者传入的是三个字母的星期缩写构成的星期字符串,其作用是只保留字符串中出现的星期:! \  g8 K7 [( X. R

4 c3 ]% U6 f3 F, imy_filter = pd.offsets.CDay(n=1,weekmask='Wed Fri',holidays=['20200109'])
$ H- G* L4 m0 jdr = pd.date_range('20200108', '20200111')1 U) U5 j9 S+ L8 u( t9 c# s9 @1 K5 I

' b. g$ O" W) q) e; [dr.to_series().dt.dayofweek2 w( n7 r8 G7 D9 J
Out[89]:
) e5 g+ A- O; R2020-01-08    2( U9 J5 Z, J  H% Q; k* U* v& \
2020-01-09    3
6 n% `5 N# \- q8 [! l8 t$ w2020-01-10    4% Y1 A3 \! j2 R: z$ i6 M# E1 N
2020-01-11    5- l7 G' P# W' I3 e9 {: X
Freq: D, dtype: int64
, W% _0 m  s$ L- P" j, F! f
- e" R8 [& i' {7 W  E[i + my_filter for i in dr]
/ P( p* ~; Q2 T& G$ o, yOut[90]:
8 U# n* v* v4 U1 y  R- F[Timestamp('2020-01-10 00:00:00'),! }+ l, i4 a" v
Timestamp('2020-01-10 00:00:00'),
- v6 i7 n' |4 s$ U7 k Timestamp('2020-01-15 00:00:00'),3 \4 f, E$ c" P, C1 m
Timestamp('2020-01-15 00:00:00')]
2 E' n9 O6 ^9 N1 s+ s  A! D6 t# \- g! L+ t6 m( {
1
( _" W, M" s4 x; x; C) A2
; Z* n, O$ X$ ?8 S, ]% R9 a+ C3$ I7 Y- g! [% z5 W3 S' k
4; c. ]% A. K+ u# y. Y$ m
5
, F- `' \5 W6 W% {8 m, C6
) K9 D/ g5 Z" F, S+ H74 \& j) c/ u" I7 O7 A. H% S
81 M7 _1 j& ^7 B2 Z! z  J: @. B
9) m' M, v/ I& B
108 B# u& G  R& }7 H% e
117 x2 v" w: s6 Z9 @( M
12) e& j3 h+ S, p4 R7 q
13, O7 r3 b/ C; b( I: l5 ^4 g
146 v7 S6 i5 w( ^; D; g" Z  _  x3 S
15) }  A7 o4 H9 j: n) a
16- U, l# |/ T/ n4 j5 \
17# W1 o. V/ @" B7 g
  上面的例子中,n表示增加一天CDay,dr中的第一天为20200108,但由于下一天20200109被排除了,并且20200110是合法的周五,因此转为20200110,其他后面的日期处理类似。
) x# L$ l# J* j# }
* u- V  H' @4 k$ m【CAUTION】不要使用部分Offset; k( d6 z( [# c$ Z
在当前版本下由于一些 bug ,不要使用 Day 级别以下的 Offset 对象,比如 Hour, Second 等,请使用对应的 Timedelta 对象来代替。8 @5 H& x6 k1 J6 K
& \/ B: \' k5 o; K! _
10.4.2 偏置字符串; t" G5 h$ \& Z1 `- \
  前面提到了关于date_range的freq取值可用Offset对象,同时在pandas中几乎每一个Offset对象绑定了日期偏置字符串(frequencies strings/offset aliases),可以指定Offset对应的字符串来替代使用。下面举一些常见的例子。! `% x0 Y$ f! N9 z
% P3 O, \0 v& p0 q) L* r3 B0 V
  Offset aliases:pd.date_range函数中的freq参数,为常见时间序列频率提供了许多字符串别名。 也称为偏移别名Offset aliases。偏移别名列表点此参看(大概27个)。8 c0 c, r9 e2 w

, H( ?6 v; w! b, ypd.date_range('20200101','20200331', freq='MS') # 月初3 ?/ B9 J8 L3 v1 [3 C
Out[91]: DatetimeIndex(['2020-01-01', '2020-02-01', '2020-03-01'], dtype='datetime64[ns]', freq='MS'). O' `0 A- b" [# h0 P
. S# W' }2 f( ?1 ]! L% R$ t
pd.date_range('20200101','20200331', freq='M') # 月末
+ G% H( A3 D% M) w( [Out[92]: DatetimeIndex(['2020-01-31', '2020-02-29', '2020-03-31'], dtype='datetime64[ns]', freq='M')0 ~, Q' y$ y0 p/ }$ Q
: \; G" K  \+ P6 ?, B1 a' C. @  w' @
pd.date_range('20200101','20200110', freq='B') # 工作日* o3 \' j7 Q. t  i: {! {
Out[93]:
4 E( H8 `5 I) H& I" r1 `0 a( FDatetimeIndex(['2020-01-01', '2020-01-02', '2020-01-03', '2020-01-06',/ F# C7 n) Q4 u7 b
               '2020-01-07', '2020-01-08', '2020-01-09', '2020-01-10'],8 g# W; J4 \" l6 h1 A! I3 B% i; R8 B
              dtype='datetime64[ns]', freq='B')* [; o3 I( E4 x0 n
% v, E3 }2 [7 j# R' y
pd.date_range('20200101','20200201', freq='W-MON') # 周一; i( w7 d$ E" Y' e4 Q6 C; m
Out[94]: DatetimeIndex(['2020-01-06', '2020-01-13', '2020-01-20', '2020-01-27'], dtype='datetime64[ns]', freq='W-MON')
( O0 @+ C# D% g9 d
4 S+ p* @4 S4 R: x/ x- W8 R( }pd.date_range('20200101','20200201',7 v9 U9 C. n- ?& Z# o
              freq='WOM-1MON') # 每月第一个周一# j4 O& d* O3 K* A
) p7 H+ g/ k7 T( z
Out[95]: DatetimeIndex(['2020-01-06'], dtype='datetime64[ns]', freq='WOM-1MON')5 p. k7 H1 u' U, |! K2 ^9 \$ d# R
  p* l  K8 u, _# M2 s
1
; f7 B" m" V3 Z1 W2* `" W/ I0 ]2 n6 Y# S& p9 ^# m6 c
3
; J1 S) S; y( |8 Y6 r4
: y- m8 ~. R- p2 o* m5
. Q( ]* K" w7 }/ z, F# j9 X% L6
8 ^# E$ b" k: ^& p7, L! h  ~; t/ h6 g7 _. K7 o6 W
8
$ S$ a3 O$ R( P) Q0 L: o9
0 b  m: }1 ]" A0 z! u10' ~3 v* R+ i& f  s& Z  B0 o! [
11
! E2 O3 m3 _% \+ {8 C3 j125 ]5 `3 s; a9 I8 b
13
2 C6 H: |9 I" @! n5 ?/ F3 Z147 c* _8 P# o) F" O. s; q
15( P( Q8 J" s) J3 f7 Q* R# e
16
; M% b; d! K* [  Q- Q17
7 }# \$ k2 V) M4 K9 q# K- r18; ]* Y; `: t8 x2 s( G9 X) O
19
% y0 ^2 U- h, j上面的这些字符串,等价于使用如下的 Offset 对象:
- o/ ]* W5 q% d! X+ H
  r- N2 E7 y4 w1 l) ]pd.date_range('20200101','20200331',
1 u  A. U' L; I6 G9 I              freq=pd.offsets.MonthBegin())
2 a( P, e* @% A# x
9 Q9 _( B7 T# P8 b8 v( GOut[96]: DatetimeIndex(['2020-01-01', '2020-02-01', '2020-03-01'], dtype='datetime64[ns]', freq='MS')
' r, G) L' R0 o# I1 G& x
# A$ v6 y" b6 R8 h, cpd.date_range('20200101','20200331',
& T: @& L7 w7 j              freq=pd.offsets.MonthEnd())
" X* J+ h4 E: M7 \9 H7 U: d$ E. [% i6 N& _, P
Out[97]: DatetimeIndex(['2020-01-31', '2020-02-29', '2020-03-31'], dtype='datetime64[ns]', freq='M')
! i% a* C5 t( K' p; C
# ^3 y) n  I- f- O# ^4 C; e) `& tpd.date_range('20200101','20200110', freq=pd.offsets.BDay())
5 x$ L, b9 I1 E8 X, K( DOut[98]: ) C# w; F& s/ R. |/ W8 ~3 T+ A* \
DatetimeIndex(['2020-01-01', '2020-01-02', '2020-01-03', '2020-01-06',1 b. [7 k! q1 ~! @7 t9 T" x
               '2020-01-07', '2020-01-08', '2020-01-09', '2020-01-10'],! n3 P* b" R; [/ M" L% e0 J
              dtype='datetime64[ns]', freq='B')2 m3 w* G) j" D! U, a

" d8 ~: m8 b$ T* Dpd.date_range('20200101','20200201',7 R+ l( ^4 [* Z- p4 b
              freq=pd.offsets.CDay(weekmask='Mon'))
( ?% f) h+ y3 u* S+ p" G3 Y0 {0 G% B& J
Out[99]: DatetimeIndex(['2020-01-06', '2020-01-13', '2020-01-20', '2020-01-27'], dtype='datetime64[ns]', freq='C')
  _- m6 A+ V9 c% _( f, t& X2 s8 h. }2 o, L! T% }: A1 @$ F  N; @
pd.date_range('20200101','20200201',
* S% B  m6 l: d! u              freq=pd.offsets.WeekOfMonth(week=0,weekday=0))% _+ a3 p2 P: y+ M; `0 W+ z
0 T& P) R$ K4 N- y, a( i
Out[100]: DatetimeIndex(['2020-01-06'], dtype='datetime64[ns]', freq='WOM-1MON')! ^" n- t4 ?1 o* w* Z$ j" X
+ r3 G$ f  r1 n2 F; V
13 f0 w8 D" z# }
2; w# ^5 n4 W# |  Y- N" F. P5 }
30 `2 j9 e" c8 t1 x3 c6 D; f
49 n8 E* ?+ x/ ~
5( v: k' `9 [3 S5 X2 h
6
7 h& F5 F" X/ T4 ~7
3 A$ n0 @/ g  U/ L8
9 l+ v9 s3 |' j4 }) s5 x* ]9
) q/ w! B7 o9 W. K8 \4 v, N; A10
) M6 R% @3 m( h" B11
. s! J1 c/ ^5 x7 w. q* S: m122 l! W! E) a0 c
13
: j" e; y( x9 [2 u9 {8 \( m8 P14
  [6 ~3 C1 m9 B3 J2 b  ?! y155 L6 j4 o! D  g- g( G
161 [! O! ~( Q* a* P+ I
17
* O) ?4 O" m& }% \2 W+ d; G18
- i. H7 x+ l- o# z198 {& ^& W+ v7 P
20
, _7 b; E1 X0 P) E7 _5 t# m  K4 g21
; q" m: d! @" u+ ~! ^5 o22$ Q5 x9 b+ h3 b; Y
23# F" H- n; H& a1 ]- U5 ]) @) X( o
24
6 A0 @4 ]) P5 i& y25
2 c4 v4 d: d' p: A, c% E1 r【CAUTION】关于时区问题的说明
+ m& o' x/ v, S- `+ b+ p3 t9 @$ f  各类时间对象的开发,除了使用python内置的datetime模块,pandas还利用了dateutil模块,很大一部分是为了处理时区问题。总所周知,我国是没有夏令时调整时间一说的,但有些国家会有这种做法,导致了相对而言一天里可能会有23/24/25个小时,也就是relativedelta,这使得Offset对象和Timedelta对象有了对同一问题处理产生不同结果的现象,其中的规则也较为复杂,官方文档的写法存在部分描述错误,并且难以对描述做出统一修正,因为牵涉到了Offset相关的很多组件。因此,本教程完全不考虑时区处理,如果对时区处理的时间偏置有兴趣了解讨论,可以联系我或者参见这里的讨论。2 Q! e% ?' H- K* F; x3 \
9 _" D, I) h3 H' t! c: P" R5 P$ G
10.5、时序中的滑窗与分组# h5 W% K2 f" }9 S# M6 f0 p
10.5.1 滑动窗口
2 D' f. A2 r: @; z  所谓时序的滑窗函数,即把滑动窗口windows用freq关键词代替,下面给出一个具体的应用案例:在股票市场中有一个指标为BOLL指标,它由中轨线、上轨线、下轨线这三根线构成,具体的计算方法分别是N日均值线、N日均值加两倍N日标准差线、N日均值减两倍N日标准差线。利用rolling对象计算N=30的BOLL指标可以如下写出:; q& r; ^- {8 z9 ?- \
9 ^/ F9 ?- v3 Q' w) D' q4 w. v! d
import matplotlib.pyplot as plt
! \/ z! P6 K0 j, Widx = pd.date_range('20200101', '20201231', freq='B')( {; r2 B! c: ^" s* I1 Q! ~. t8 `
np.random.seed(2020)* W: c) P$ K' A: J

. p/ i6 [& [6 G# S; E# C+ Ydata = np.random.randint(-1,2,len(idx)).cumsum() # 随机游动构造模拟序列,cumsum表示累加
- i9 F4 X3 O1 `2 L# hs = pd.Series(data,index=idx)
' f) j2 b8 a$ h5 u% L; i- k- S% cs.head()
' l' l) C# v7 Q7 y2 b7 L* ZOut[106]:
& b- k1 @2 Z* S1 u2020-01-01   -1
2 @- c  x0 Y' R2 d0 b2020-01-02   -2  L: }3 X& B, Y$ x
2020-01-03   -1% ~& S8 g. E; n1 M9 L' j  {
2020-01-06   -1
& G" @! Y8 c: o3 f: e: Q$ ?2020-01-07   -2* t' q- m; D8 D2 w" p9 R0 M
Freq: B, dtype: int32
9 T9 z2 L' b! _0 Y6 J( Ur = s.rolling('30D')# rolling可以指定freq或者offset对象
' d8 m! n8 D, R! y8 h9 D! d4 M  `
plt.plot(s) # 蓝色线! _4 B& O, B2 M5 `
Out[108]: [<matplotlib.lines.Line2D at 0x2116d887eb0>]$ g, t4 j0 E( f3 E  {& _
plt.title('BOLL LINES')3 n  ~% _2 U9 a) L
Out[109]: Text(0.5, 1.0, 'BOLL LINES')* b0 i" x6 x% b$ x' q8 q

  ^! a! t" \5 W0 i: |5 mplt.plot(r.mean()) #橙色线
. r& k( e0 Q: _. k6 SOut[110]: [<matplotlib.lines.Line2D at 0x2116d8eeb80>]! n( [) O; K0 |, Z' i

4 t" n6 F2 Z3 E$ s+ Dplt.plot(r.mean()+r.std()*2) # 绿色线/ i( p# [, r# O3 p& V( s
Out[111]: [<matplotlib.lines.Line2D at 0x2116d87efa0>]
- I9 a4 ?$ i8 w9 b/ c# }4 i6 z' P
) _* j' d1 u7 a4 t2 h/ J1 vplt.plot(r.mean()-r.std()*2) # 红色线
* C( e9 ~" w: TOut[112]: [<matplotlib.lines.Line2D at 0x2116d90d2e0>]
6 ]( q2 j. y; G, \" m% K8 B7 k) S' |- |+ E/ J# H1 r# J  t
15 p7 @- g: c, F! I6 a1 x  @
2
0 h& G1 w! R) Q4 o' v3
% h$ |. r: @/ l0 y& L4( o% i' e* `) L+ z6 a+ P
5
2 _4 D) {. w7 s% e" F7 y* x6- j- i! A) l, L2 G
7
$ W4 ]  w7 T4 \3 |, Z7 [" z8: ?2 Q! }# n; S5 j
92 H1 M3 P  A+ C
10" q3 m) T8 a+ N/ y5 A6 s( ^1 `
11
6 L+ ^) K/ y1 R; s& r( b9 i12- n( T/ ?9 @: l7 N: R0 V7 X4 U- Q: V
137 x8 @8 U- e- p
14
0 l; Y% o' t/ J159 `3 L; ^& Y. L% ?, j0 X+ q
16; Q4 K# U4 |* h; ?, ?8 g
17( c! N) q$ H2 G% k' w. y
18% Y: M  `& m- p( t1 f' }* Q) z& n
19
# D# g" t1 E7 ]8 e) p* O0 T20
; M$ G& ]9 P# T. C21% o; C  a( ~. ~9 }
22
0 y! W' X. _0 d4 Q" U+ e! |23
! I5 t5 R0 |! t7 V243 {2 h" [8 b7 ~( T2 n
25
4 A" M  K0 W1 Z0 F' M+ ^266 H, H1 r+ T1 M3 n- j. E, o, Y
278 X" }8 j- h4 t+ B
28; y: g: n8 A8 J/ S( F
29
9 @9 G: i& J" P2 ^4 Z8 S8 c& a* F6 I5 k4 _1 z" E9 a) B- q
   这里需要注意的是,pandas没有实现非固定采样频率的时间序列滑窗,及此时无法通过传入freq字段来得到滑窗结果。例如统计近7个工作日的交易总额。此时可以通过传入多个函数的组合来实现此功能。4 O3 t* w8 x7 j; c( g
   首先选出所有工作日,接着用普通滑窗进行7日滑窗加和,最后用reindex()恢复索引,对于双休日使用前一个工作日的结果进行填充。3 o% K, p# I2 y3 T# O1 T
& G6 g1 w9 [& v; |: N( v2 Y
select_bday=s[~s.index.to_series().dt.dayofweek.isin([5,6])]
. W7 f0 d% c) t# l, `* K4 ~bday_sum=select_bday.rolling(7,min_periods=1).sum()
# a7 X) [+ X% e$ {result=bday_sum.reindex().ffill()
$ L: B: _( V8 E2 ]4 K7 Tresult$ n8 W. w, h3 P3 H
. ^+ H. B! N6 Z, r& a8 W
2020-01-01     -1.0
; t& C: t6 M  I: B2020-01-02     -3.00 e2 P4 p! U7 V1 o
2020-01-03     -4.0" j' o6 V3 B% h0 e( e4 m* \9 u& _
2020-01-06     -5.0$ h- ^$ H3 E5 _7 {
2020-01-07     -7.0$ @$ w" Y- `4 n
              ...  
& W- O4 y2 i5 s2 q# ?4 P2020-12-25    136.0
" |: g, j% z; Y. m2020-12-28    133.0
+ f+ D' X. \# N6 {# C6 V: T2020-12-29    131.0* y" E: ]& F1 x3 M! J$ O: d; H4 \0 S
2020-12-30    130.0$ E! d; W5 J0 z0 w* y9 m- L) e$ \
2020-12-31    128.0
7 S, d0 H" M& E0 J2 rFreq: B, Length: 262, dtype: float645 y8 P- c0 f- p$ z( A' y: p( H
4 |6 R# @  H) X6 y8 g5 U
1# Q  [, E7 ^) I8 X
28 q5 ]( b  l+ G6 ^4 B4 B1 B
37 K( u. Z& Q" `
4& A6 L$ L& B  X: |
5
' b" B9 X) g* G* }1 V6& v& n& U7 t$ v
72 ]$ D! P4 c# }" u1 x, n6 }
80 @- B& M6 C% x# a7 v
9, x# Y9 p3 j$ H8 ~
10
. X* L( S8 S2 G& f" S9 V11% |" `2 j9 l! |
12
% y- d% b: u/ b1 W# A4 ~3 J13" N8 I' f  k5 c0 k$ Y6 ?8 `% p
141 |# X, }4 D. v- h5 A  I
15
  L$ v" k$ T4 N6 N% w7 X& m) b161 c8 V; u3 U, p, {
17
2 q4 A  |( M% @2 _/ c* P& F. }  shift, diff, pct_change 是一组类滑窗函数,它们的公共参数为 periods=n ,默认为1,分别表示取向前第 n 个元素的值、与向前第 n 个元素做差(与 Numpy 中不同,后者表示 n 阶差分)、与向前第 n 个元素相比计算增长率。这里的 n 可以为负,表示反方向的类似操作。1 @$ J8 |: y& \) h+ I/ N& w

6 @  N/ X6 p! S- T6 H# Z" V9 h3 c  对于shift函数而言,作用在datetime64为索引(不是value)的序列上时,可以指定freq单位进行滑动:$ C$ I5 H) t  d. }5 I1 h4 Q4 U

6 b! j" w8 R) I6 M8 m3 p7 ls.shift(freq='50D').head()' h9 l2 A4 c4 N2 s/ b" ^# V; U$ g1 y5 {
Out[113]:   H) A3 L3 M1 w2 E) v
2020-02-20   -1
; o: U! _* l; H9 o. h. p2020-02-21   -23 |/ T. j0 p0 L* O. e: B7 t
2020-02-22   -1
- P% ~) k# }; [+ g) e8 Z- c2020-02-25   -1
/ R; @1 @' r4 R- f$ N/ S$ w$ U2020-02-26   -22 ?7 x8 G: a& z' J
dtype: int32: f0 t  _5 C) _6 T# W- _7 ?
18 X: W0 x3 P) H  w8 ?1 ]; q
2
4 n, {1 m; |* W$ o' \1 R3
: s8 K! P) v/ n43 r7 L% V2 w! A2 X. x
5
9 }4 B6 O1 u& k6
$ f; A3 z1 H. b8 c  H( G7
" G: }' r3 n; _7 `7 X+ Q8
% g( C: R, y% c3 y  另外,datetime64[ns]的序列进行diff(前后做差)后就能够得到timedelta64[ns]的序列,这能够使用户方便地观察有序时间序列的间隔:
# W6 M2 w0 L& F, i0 ^1 n
/ H) A( X3 G/ f1 W& ^+ I0 Q' bmy_series = pd.Series(s.index), \6 I7 u- y. `3 f7 Y$ |, a& i
my_series.head()! g9 X. {3 z0 T/ k
Out[115]:
& k8 Y- h, Y6 v# d0 G4 J0   2020-01-01* @+ `, W1 K, U; G
1   2020-01-02$ V8 T. z5 [7 J6 i+ L
2   2020-01-03! x5 T" d6 S3 V& K8 f" N& \
3   2020-01-06
6 `$ |4 q4 M0 j! c4 [+ [4   2020-01-07% `& }, g! U+ E- g5 b# v; B& [) H% v
dtype: datetime64[ns]4 H4 J$ R0 n2 a# L

# ^6 |1 Y5 Z4 b8 A6 P; D! c1 zmy_series.diff(1).head()
: A/ _; v. ]& O9 \0 LOut[116]: 1 X- x( r7 A& H1 l
0      NaT" n' p+ L; j8 c' n5 s/ K
1   1 days
4 |8 W) N8 P8 c: P0 K% J  T2   1 days
& R1 r3 w* I/ L% I3   3 days! P/ ~9 B: j6 H* }- W% k! C1 b5 w
4   1 days
3 p1 j6 V5 F/ ^dtype: timedelta64[ns]( e; O: `" l+ @* i& |; @
$ h( d2 f5 j5 L
1
2 t' W% O3 m% y& T2
. D* d7 e" z$ p3
, r5 d6 F& U" M% c4
6 x4 Q! _2 H; d; F6 l& N5
6 m2 y5 Y- C" {5 y: e' x6
* K2 P. c, }. b5 b; v7
6 D( Y6 [: \; I& v# t3 c8
3 K  v( B. Z, r, S. h5 C& H, W/ z1 w9
2 ?. A8 B# i1 y! `: R6 ~% E10
% U/ {7 U, `8 E# m+ [* R  u11
9 Y  O# x4 Y5 e" j0 x. j/ h7 L12
% x$ U0 x0 a) W% A( n8 @13
. d, j* F7 `) t, X( l14
1 f( F1 W4 m$ A' q* H; I; Y15' c2 w5 i2 N) Q- b4 J2 @( D( N
161 u% R8 B, Y: l" d7 i) v
17
! h0 T( ]3 B( L# u% S, N18
+ ]# i' q5 v% q* h10.5.2 重采样- l% s" d& |0 O# p% t
  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)! L% \- v4 U' Z
常用参数有:
) H. [4 ^( y3 g2 |) k/ d) W" q6 ]3 i: d0 r  M9 `* |
rule:DateOffset, Timedelta or str类型。表示偏移量字符串或对象
* i4 C0 `. k' p" r4 c) saxis:{0 or ‘index’, 1 or ‘columns’}, default 0。使用哪个轴进行上采样或下采样
% f( L$ V- j7 u0 lclosed:{‘right’, ‘left’},默认None。表示bin 区间的哪一侧是闭合的。所有offsets的默认值为“left”,但“M”、“A”、“Q”、“BM”、“BA”、“BQ”和“W”均默认为“right”。
9 U- {6 [% b$ `; q3 ylabel:{‘right’, ‘left’}, 默认 None。hich bin edge label to label bucket with,所有offsets的默认值为“left”,但“M”、“A”、“Q”、“BM”、“BA”、“BQ”和“W”均默认为“right”。
5 }, h+ w' i1 O9 f5 v, M% }convention{:‘start’, ‘end’, ‘s’, ‘e’}, default ‘start’。仅针对 PeriodIndex,控制是使用rule的开始还是结尾。
6 d: b, g+ N+ c  s0 Xon:字符串类型,可选。对于 DataFrame,使用列而不是索引进行重采样。列必须类似于日期时间。
5 C2 r. O( d, a+ V- z" b- |1 plevel:str 或 int,可选表示多重索引MultiIndex的级别,这个级别的索引必须类似于日期时间。
, V' D& G, U: O" p$ e$ Xorigin参数有5种取值:
' I9 z+ m- _4 y# O‘epoch’:从 1970-01-01开始算起) z4 k/ p/ q2 e" _& ~/ p
‘start’:原点是时间序列的第一个值
, K, l+ o, V& l6 L& d2 ]/ T- B2 e5 e/ H‘start_day’:默认值,表示原点是时间序列第一天的午夜。
6 w& n  F& I7 N4 a& o'end':原点是时间序列的最后一个值(1.3.0版本才有)* X, D$ k, f! U7 s. L: T/ X5 k
‘end_day’:原点是序列最后一天的午夜(1.3.0版本才有)
4 }9 o0 ~* e+ K% b; Hoffset:Timedelta 或 str,默认为 None,表示对时间原点的偏移量,很有用。
3 V7 X) ?7 {+ B  closed和计算有关,label和显示有关,closed才有开闭。$ `+ \( J) ^% z" x: g8 g
  label指这个区间值算出来了,索引放区间的左端点还是右端点,closed是指算的时候左端点或右端点是不是包含。% n, W- ]2 E2 _5 \- n
, A: d0 B" R# E% X
重采样对象resample和第四章中分组对象groupby的用法类似,resample是针对时间序列的分组计算而设计的分组对象。例如,对上面的序列计算每10天的均值:/ d' i  u8 b* D+ K" q7 g" r2 l
s.resample('10D').mean().head()
* s6 s+ g( C' |8 S% O; T8 FOut[117]:
" J9 }& x8 K  a! O2020-01-01   -2.000000. V% O/ L4 w, K  i6 F
2020-01-11   -3.166667
$ N/ {2 n# u; g, F9 K2020-01-21   -3.625000) d1 N+ Y, `7 n, P3 q
2020-01-31   -4.000000
" p6 n* [. [7 D( v( H5 _2020-02-10   -0.375000% [$ ^3 u" b  p; S  E- R% F
Freq: 10D, dtype: float64
3 l1 O- c3 I8 {1
8 c5 @% s3 ]! c' R4 m! i2( t' o6 U  c+ b( j$ d
3
6 O. p$ u  m& E! k; \* W8 D1 l5 l( U4
0 z* F( [. y5 B# l. o- |4 @9 a: x51 e2 x" M7 Z- w5 {) Q# j' B8 Y
6
3 D5 E6 n9 m4 A1 Z3 b3 w* V78 N- M% \$ Q& v/ ^' H4 a  S5 U+ Q
8
  F8 }% g/ N& e可以通过apply方法自定义处理函数:
4 d% D* l' n; {4 ]  o. F6 Ms.resample('10D').apply(lambda x:x.max()-x.min()).head() # 极差- J) d. W0 l5 F' D/ c/ K

: `! o' A7 ~5 T; l- n( ~, \Out[118]:
) \8 a& k" U" j2 F  R2020-01-01    3( {. {4 H" k4 _) c% {9 ~
2020-01-11    4
: h1 s6 M2 T7 g8 \. a7 X2020-01-21    4
* s0 A. h7 j5 M2 S. P! b( d2020-01-31    2
* d7 _3 Q: w, c: J" S$ Q2020-02-10    4
! n" Z& D* _5 T5 s- Y% c9 s9 NFreq: 10D, dtype: int32! Y& p# i; M+ A2 D& M7 j/ ?
1
1 i$ T  R: V, b! `2$ _# e- O" N; ~
3
* a) v6 H; o7 R4 B+ o3 `47 ]( K5 \7 p9 c, ^. M3 A+ m
5/ G& A, J3 o# X  a' e! p+ I. i
6
- p% N9 E9 b' `7
% P! ]; n0 x( |1 b" `* A. ?8
: b- M7 ~/ ?6 p) M+ y) j9
& E  `' {) K' ?1 p0 d- ~9 q  在resample中要特别注意组边界值的处理情况,默认情况下起始值的计算方法是从最小值时间戳对应日期的午夜00:00:00开始增加freq,直到不超过该最小时间戳的最大时间戳,由此对应的时间戳为起始值,然后每次累加freq参数作为分割结点进行分组,区间情况为左闭右开。下面构造一个不均匀的例子:" c" p' a2 ]: d: U! h
( W( S1 J3 K7 v% d% U
idx = pd.date_range('20200101 8:26:35', '20200101 9:31:58', freq='77s'): c) y* u2 T% o
data = np.random.randint(-1,2,len(idx)).cumsum()& W( C# n" j* E; P+ n2 N+ e1 o4 x
s = pd.Series(data,index=idx)# Q( i4 x9 F* M; [
s.head(), i) j$ u7 M7 [3 ]7 y1 u

- M" S. {2 f0 R! A, uOut[122]:
3 `9 \0 _7 h# s  ^( M. S2020-01-01 08:26:35   -1, K) H. R& q+ T
2020-01-01 08:27:52   -10 U3 G8 C! Y, r4 G
2020-01-01 08:29:09   -2
. _2 e! B/ A" v2020-01-01 08:30:26   -3; W& [, b/ [7 }% ?
2020-01-01 08:31:43   -4$ l3 E" e' {3 Z0 k3 n
Freq: 77S, dtype: int32
) d" g! k% \, t5 R& \; t0 i1
9 X" v9 ~1 l6 G2* o- H" E- G* Z
3
8 V4 J% x# c7 N6 G, x4/ }- n0 w4 G2 Q9 X
5
1 ]0 a8 U  |! E* q2 W# f# g6 N$ P62 u/ H8 z3 y& ]4 ]
7" u6 V: T! A/ W/ ?) ^1 Q. P
87 N) d  K: t3 N+ f' x
9
% w  s1 B) P" k  c  G( ^104 @4 i4 C1 O6 X) G7 ?
11, t- F/ j# f' N8 x
12
7 W/ Q+ N. U- u; u& Y! @  下面对应的第一个组起始值为08:24:00,其是从当天0点增加72个freq=7 min得到的,如果再增加一个freq则超出了序列的最小时间戳08:26:35:
4 k# ]1 Y/ t( }: @- x6 m, }* G; M- M' x9 R2 e, ~1 C
s.resample('7min').mean().head()8 }: y% U' h- e& Y9 h
Out[123]:
( z* `& n  @! ]$ l! f0 p7 m6 g* }2020-01-01 08:24:00   -1.750000  # 起始值,终点值包含最后一个值
5 M) d& [7 `# X0 F2020-01-01 08:31:00   -2.600000! d/ e5 u& p0 i% L( O8 p  `
2020-01-01 08:38:00   -2.166667
) C: u1 t! H8 C/ a  i; T, r2020-01-01 08:45:00    0.200000
' ^2 U/ y& m* g$ u( z5 }2020-01-01 08:52:00    2.833333
; Z) }4 M/ }- Y/ h# ]4 Z. QFreq: 7T, dtype: float64
9 W4 Z/ p, \5 z% s5 ?' c" A1
. W4 P( X/ R1 {" Y8 l2
9 d) |% C) h" Z5 x4 ]: o. ^  O3# Z/ r# L$ B2 L0 p+ @, n: E
4
. G. J: d8 F; L1 x5' l9 \* t# R2 w9 A
63 ~- t- D% o7 I. c3 X
7) t  j0 P3 I$ @' s
8) t3 c$ U, f( U3 w3 S( ^" f4 b4 Z/ r+ X' O2 |
  有时候,用户希望从序列的最小时间戳开始依次增加freq进行分组,此时可以指定origin参数为start:
/ ~2 `$ E( c* Y2 c) o
. V; w1 s  z  T! ~6 O6 ~) Os.resample('7min', origin='start').mean().head()4 Z% B$ c' Y8 c1 }2 k$ v/ j: U
Out[124]: : t: a! M7 x# I$ H( ?' A) c
2020-01-01 08:26:35   -2.333333
2 w* t! p. m. o3 O+ L3 h+ z+ g2020-01-01 08:33:35   -2.400000, @8 U. g+ W0 u( Q
2020-01-01 08:40:35   -1.333333
8 Z$ h4 V3 }. r  V. [8 `2020-01-01 08:47:35    1.200000
# C0 m4 P" y; ~% l6 X3 r% l2020-01-01 08:54:35    3.166667: j( f7 J) ^% X( M! _9 w
Freq: 7T, dtype: float64
; J- _8 S1 N. Y$ g' y% T1
5 c3 E- l, _! W: s2/ h! H3 @0 m+ q6 y
3, @. }! V* J/ ]7 ~# c2 w) G7 }$ m5 m
4; L# ~$ ^1 c0 p; d
5
. y( k- @& k8 X9 T7 O7 r0 C6
5 K. y, k1 j- i73 T1 T- a8 ~5 ~6 K/ [6 n  H6 M; l
8% @0 o7 v" |4 ]6 i4 k
  在返回值中,要注意索引一般是取组的第一个时间戳,但M, A, Q, BM, BA, BQ, W这七个是取对应区间的最后一个时间戳。如果想要得到正常索引,用’MS’就行。) z% |# u' n: L

8 d% b) M* t' k' Ps = pd.Series(np.random.randint(2,size=366),
2 m( E% H# e8 m' M! A              index=pd.date_range('2020-01-01',) O  T. A  j( O2 L0 L; }% h' ]/ J. O
                                  '2020-12-31'))- D# J, ?  P% ^! Z5 K7 p

, j: E* ?/ O( }& L% Q7 B1 E0 q4 |6 y, g
s.resample('M').mean().head()3 v) n) j- ^( N+ Q8 X* c+ ~
Out[126]:
9 b" ^8 z: b" c* i2020-01-31    0.451613* R3 x7 G6 U; o% ]6 l: J. b
2020-02-29    0.4482764 K# B, k4 x; T9 F+ g
2020-03-31    0.5161295 Z! t' j3 X6 W2 C
2020-04-30    0.566667
. \4 d$ G7 ?: A( u2020-05-31    0.451613: ?5 T, b% s- l& B8 |, l
Freq: M, dtype: float64
* l% {3 v9 e) f3 G1 h
6 P/ y, l3 r" js.resample('MS').mean().head() # 结果一样,但索引是跟正常一样
$ X! i; r: \2 h  U6 DOut[127]: + v. D( T6 Q+ }$ }: \
2020-01-01    0.451613
, K7 l7 a. a5 P( P) V2 M2020-02-01    0.448276) c) i3 ^) J' O" u; p
2020-03-01    0.516129* o- W% }/ a" p+ a- X% @
2020-04-01    0.566667
' p' a: r' H8 ~/ G2020-05-01    0.4516133 ^2 r4 [. r0 Q2 z) x9 q% S: ?
Freq: MS, dtype: float64
5 y8 ?; S' b& w, l1 {1 i1 [  m0 w
2 G2 p- Y  S& |1
- P/ u- n1 \' U% G1 N6 w$ p2
8 S+ [- [2 e6 D3 K. e6 V6 Z& U3' N6 s- H8 K8 ?5 x* @
4
  l9 M9 p5 Y# p3 T5
! u/ t8 l. X3 q5 x* S/ B! `9 i6
5 U1 ~+ N$ g: ]- N7: O8 N8 A5 V, H; h, v& h7 B& l* b
8: b. n. U9 Y- m7 b' O2 c1 f
9
' }" R7 a" Z5 d/ w( V. P+ l10
) P8 x8 S; Y" W. _9 |4 d110 i5 G4 S# a; j
125 @- b( R% X- E
13
2 J$ p+ X. J2 d) b5 |' o14! c5 P! M) r& p/ g% v/ e5 ~/ K- L
156 e5 J6 n6 S' V& \
16; U" j* E' T' {' Y3 U( [
176 n/ Y% R- C$ f( |2 \# ^# ]" v
18
2 V+ z) ]: x* t198 e& u4 t5 Y. j
20( H1 s) w6 d: j1 X& C; J
21
, D; |- V% b/ G& L7 f229 q6 f! U3 s2 H0 z7 c; V
对于 DataFrame 对象,关键字 on 可用于指定列而不是索引以进行重采样:
) P! f; o2 }8 d, I* @d = {'price': [10, 11, 9, 13, 14, 18, 17, 19],- [4 C: n7 S. l# |  w$ d
     'volume': [50, 60, 40, 100, 50, 100, 40, 50]}
- d% L: @8 D8 f& K0 Jdf = pd.DataFrame(d); `7 i8 s+ E" ]5 L/ c
df['week_starting'] = pd.date_range('01/01/2018',1 x2 c: |2 t- h4 g; F
                                    periods=8," F, t. z( H6 Y' J6 P
                                    freq='W')$ R9 f6 \6 h6 _# V6 L
df1 @# u0 D4 y' U9 x
   price  volume week_starting
7 d  E# H. c5 U: Z0     10      50    2018-01-07  v+ P) ?6 R$ R5 ?: d* }" q
1     11      60    2018-01-144 Q# T! z6 D" {$ k6 Y  L# I1 ~
2      9      40    2018-01-21
  F/ n* ?/ y9 S1 {& k3     13     100    2018-01-28
- [1 S" r5 }9 W/ W  B$ b4     14      50    2018-02-04
! t6 B) |/ A' E' J! s' I5     18     100    2018-02-11
3 B# a) ]5 \' J6     17      40    2018-02-18
6 A  r+ U9 l% `) @: T7     19      50    2018-02-25+ R" w, `2 W& @) a, I$ O
df.resample('M', on='week_starting').mean()8 N! h0 g( k( D0 r7 h9 U, K
               price  volume
. x) ?  U! ^- R2 X( uweek_starting* v. G* r0 f' t  B8 ^1 j
2018-01-31     10.75    62.5
2 Y* |3 @/ ^8 \5 M9 @4 D2018-02-28     17.00    60.0( u& K- i# T. d7 L6 J0 q4 L! a

$ N' u1 M3 Z7 Z& ~) ^" b( v& T1! ?- o& T( [5 ?4 z! s. L
2' w  Z9 L. M1 h2 \7 Z$ ~- ?
3
" i3 N+ ^% K8 Y6 s4
: q. v  z, |' z- n; V2 n/ U5 T5
8 x3 H& C% X, P2 `# _9 Z% i1 k3 {6
; {! |) [- [/ v1 `72 G' ]7 E. p; W
89 {" {# k  n/ C
9
! g: Q7 c- P/ c, j1 x10
; u3 G2 W3 q0 S0 Y, P11  }  _% _- s  D; d5 p
127 N* c' I# u8 F8 O
132 c0 U. R  K. P: ~) Q1 `
14
' K7 G/ T/ h0 a9 ^9 K+ G  W155 I7 I1 j5 Q3 x
16: [2 \6 w* `+ c
17
+ v9 e0 o* i' u' Q' t% v18) h5 P- f0 ~) u! z
19
) W) P& V/ d! e8 p20
! V9 d8 C" D" k' O21
( L8 V6 a% p' o6 E; y( G9 @# G对于具有 MultiIndex 的 DataFrame,关键字 level 可用于指定需要在哪个级别进行重采样。
  m1 g( e" R' ~# ^+ F; S! odays = pd.date_range('1/1/2000', periods=4, freq='D')4 ^. v' [0 i/ C9 `  z
d2 = {'price': [10, 11, 9, 13, 14, 18, 17, 19],
0 C. B/ q, f) b9 H      'volume': [50, 60, 40, 100, 50, 100, 40, 50]}- ^/ e$ H: e; U. W. k0 A
df2 = pd.DataFrame(
  i- z: w8 P' z    d2,
8 S5 O' {/ G7 O! I5 a$ J    index=pd.MultiIndex.from_product(; o) o  k7 E$ Z9 z- W
        [days, ['morning', 'afternoon']]
' I# {7 ^  \/ \) `    )
( E# F. l8 I& e7 r' z)
' o2 J- I4 a" Xdf2
) B& B5 i( [7 a' N6 }5 T8 n3 ~                      price  volume
) n, E$ y. n! ~2000-01-01 morning       10      50
% \) Z# q9 K! D" N           afternoon     11      60' M4 b7 ?4 E6 w3 g
2000-01-02 morning        9      40& V/ E& w2 d  ]  V) c5 x9 k* o
           afternoon     13     100: U! K7 _: z7 X* C, u3 B3 D
2000-01-03 morning       14      50
" O# q+ ?. \" b% u& O& W, t           afternoon     18     100
' o' v9 D/ k; j! {( A2000-01-04 morning       17      40
# u- [5 A& ]6 z           afternoon     19      50: R2 ?" ~' p1 e8 n5 Y
df2.resample('D', level=0).sum()
) g6 y4 ?* z# i0 ?0 q+ g+ O            price  volume
# K9 n& E0 T7 Q7 H: H: l# D2000-01-01     21     110# U- C; U( v7 M3 y7 q& _% f
2000-01-02     22     140
( W% b  D6 c/ u2 F# T8 S# y2000-01-03     32     150
* v/ h/ F" {4 ?  p2000-01-04     36      904 R3 N8 R& K9 R$ e# [; Y% d: U: v; s* G

" |; O/ Z+ o- l! B- l$ X1
1 b4 V* F/ i1 ^' A8 h* E7 A2* o- G4 S, o: @9 ~$ w: ]. ]! B& B
3
) M$ d; g- ~. V40 O8 U! K5 B$ ?7 e3 @
5% v/ A. P- Y( z3 l0 T7 ~. b- q
6
/ N1 K' S$ r& I$ ]) E5 @8 U7* e+ C9 Y! k: Q) `0 Y
8: \4 a  f2 M6 w2 \
9
7 L. ~2 ^7 ?$ _# B  j& i10
3 q/ k* T" s4 s; k9 x% f- @11
* ~" R5 A; I0 w' e0 s7 t12, \& Z3 D" p4 A( h
13/ Z0 T  [7 l9 h4 D
14! o& w0 H7 m3 G" P% e( v
15
: l1 a2 M$ p4 ~. f161 P4 X3 [4 d) C& E# [1 d9 N9 |2 B
17% ]* d& c+ @' G4 Y: z" @/ x; _
18  w5 o; O& }& h% H) _, D( X
19
  L( D8 w. f. w( j& G20( y( J! z4 O. X- ?
21. f" p: k4 N# i! |
223 C. C; \; [6 O, _! E
23
- e$ ~% G2 G8 y0 ~. j7 M# u24
8 _6 V' c. }; e  M/ H3 J1 X( [9 A$ t25
' p) n5 z5 R  L; l% w% Y% K根据固定时间戳调整 bin 的开始:% q" [+ h; \% W4 t
start, end = '2000-10-01 23:30:00', '2000-10-02 00:30:00'
) z. H9 |! @  b8 ]% T$ D' ?2 p2 trng = pd.date_range(start, end, freq='7min')% M, r# i- }$ {5 ]0 A
ts = pd.Series(np.arange(len(rng)) * 3, index=rng)( ], k. ~; e2 q% k5 {) }
ts
3 V! Z! n* z4 i: Q2000-10-01 23:30:00     0
1 {+ g: V' ^& Y/ z3 B1 V2000-10-01 23:37:00     3
7 P* l$ K* [1 P1 e6 i/ X# J6 _2000-10-01 23:44:00     6
7 U* U+ o' b- z- b2000-10-01 23:51:00     9
8 q9 w' ]3 w9 U; X2000-10-01 23:58:00    120 K  J- ?8 \2 |* `, j
2000-10-02 00:05:00    15
" @% i4 I+ J( k2000-10-02 00:12:00    182 ?0 h7 c. \: `2 K0 r
2000-10-02 00:19:00    21
, F2 M" Z! C3 n5 p0 y$ f2000-10-02 00:26:00    24
3 A* U- Y- d5 M* [, M/ c+ [1 j, [Freq: 7T, dtype: int64
! p  f- @  x/ X# n0 [% N% z: y- P! ~0 u4 M$ d8 p7 w, m" K2 J6 n
ts.resample('17min').sum()# A6 D/ `' y+ h8 h# n1 c
2000-10-01 23:14:00     0
% p  C7 |* w% M6 n+ {2000-10-01 23:31:00     9) m- @  {7 }8 o0 ?8 `
2000-10-01 23:48:00    21
3 l1 }. n. ~# u2 \8 Z" z/ [2000-10-02 00:05:00    54
6 d% O- ~+ Y! z" l# `+ g% B2000-10-02 00:22:00    243 u& h' }5 U1 G! u( V' K
Freq: 17T, dtype: int64
9 x! g9 d& a/ ^$ j$ K7 Y+ y2 C* b, m1 h0 [& ]2 d% T5 o. n  y9 f7 X. j5 Z
ts.resample('17min', origin='epoch').sum()$ b& a. R2 g# b" f  g
2000-10-01 23:18:00     0# d( S. s( k$ a3 G& v9 ~4 I
2000-10-01 23:35:00    186 Z( K* t% @0 c8 k/ p' N
2000-10-01 23:52:00    27* s+ o  W2 K$ L1 x- Y7 O5 g0 ^% R
2000-10-02 00:09:00    39$ m. z9 w& K6 c6 j
2000-10-02 00:26:00    24
! _" T, l  {8 Y* E  r$ iFreq: 17T, dtype: int64+ @( X: l' ~. g4 Q2 S4 `

/ P, ?0 e2 ^, W6 h* p8 mts.resample('17min', origin='2000-01-01').sum()
6 d& W6 m( B4 a2000-10-01 23:24:00     3
3 }: J9 g& n# W& ]8 ?* \2000-10-01 23:41:00    15
& y! J1 _- w! w" w2 G$ r2000-10-01 23:58:00    45& y: k( I1 c# c
2000-10-02 00:15:00    452 N0 t- F5 H  `( b0 c( t
Freq: 17T, dtype: int64; `8 L. f  k5 S) \7 Z& w1 j4 Z

1 j- u; F+ S: x! E# r5 K5 w) h6 z1
7 ^. |1 f, I( Y% g) K3 o) \9 P: f) J2
$ O9 p. h0 P7 U  F35 R. o3 H8 Q) B, y
4( _6 ?% O& Z  Q2 y* y* v
5( r% P* B" x5 @- I, m! k8 T
6: r$ @# o' \5 L% q% q
78 {, g* Y$ C' {! Y
8: t  e: `, w) ^% X: o* C+ a
92 f% O  R2 Q" d6 o0 C! _
102 y' X. o8 v% m" y: ?& b
11# _; _& U; L' p/ W$ X
12- ]4 H9 T1 R9 t8 ]6 H. h/ |3 c3 e
13
4 \2 ~; N9 d0 L* J: P3 j# _% F14
" F0 W, h' ~4 I) l15
/ }: z- G) r- J6 M16
, H4 B) ?' W" u# a$ V3 g& J1 [17
3 Z' b" O8 A  E18
2 ~" i/ A9 y- A19" d' ^- @! Q% L# \- A, [2 ?
20+ s3 J0 O# i! N* ]
21
5 Y8 Q5 H1 D$ _6 W9 i22
# e9 B3 D  y9 A$ n+ t: o  j3 M235 N8 A. e0 \' S5 y/ t0 R
24
" ]: l0 h! O3 U2 B0 z6 e252 u4 |5 ?) h% c
26# g. b+ L6 p7 [9 Z  l, W
27
' X6 j) r; b% |# E& g28
. @2 V8 G# s$ }2 i5 D$ T2 Q29
  I7 A& m! c; T8 X7 D30* D5 ~. h4 T2 p3 b" ?
31- w/ t3 `; y& f9 V( e
32, Z3 H" y0 C; r9 Q  b* }
33  X  C" ^: s. u8 ~6 T
34) z2 \: d+ F$ m" p, i0 u0 o8 X- `
35" ]- t; U% P2 \% f9 u( Z) ?
36
) d- @" [% u% L  w+ F6 J9 g! o379 H: h( G7 }/ _9 M: S6 D) R
如果要使用偏移 Timedelta 调整 bin 的开始,则以下两行是等效的:
4 ?1 Y, F4 @7 L' i6 d3 Jts.resample('17min', origin='start').sum()
* A1 s+ P$ R. V% `1 ots.resample('17min', offset='23h30min').sum()
: g' I% Q9 G9 K" q; \2 E/ f2000-10-01 23:30:00     9$ E5 _6 p5 r- c, @
2000-10-01 23:47:00    217 |& h3 L* ^; Y. a; j
2000-10-02 00:04:00    54. @) \" i* ?/ {* }- Q2 m- S
2000-10-02 00:21:00    24
; g, x4 y5 I+ A- d2 [; R; i: y7 E: j8 NFreq: 17T, dtype: int646 W  V$ [  s5 F! Q. @2 L, d' c. j& [
1
, Z1 G% h) v8 W! n# j8 e$ a2! X4 s# A. t9 Y
3
' W2 J! u) `; u$ [! |. A* I40 F& R8 X/ Q0 l
5
3 L5 |4 D- O( Q1 K0 `% D" T6
5 [" U, n! g5 {+ E. X2 d7
& U( J8 }. ]1 I10.6 练习* ?  P8 F( C* `7 J4 @$ N, K3 b2 I, S
Ex1:太阳辐射数据集  R# f! n/ d( v/ g' t8 {
现有一份关于太阳辐射的数据集:0 H. a% G$ l6 A  I! H  T. k
8 K1 ~( I( j) ?2 Q1 q
df = pd.read_csv('../data/solar.csv', usecols=['Data','Time','Radiation','Temperature'])
) ?  i; D$ w5 Q: w* hdf.head(3)
4 L% E8 F0 _4 I7 `
8 J1 m# U) i; UOut[129]: 4 i+ I0 I1 s* r3 X! K
                    Data      Time  Radiation  Temperature
4 P9 ]  l$ Q$ a3 r- g6 S0  9/29/2016 12:00:00 AM  23:55:26       1.21           48
' W7 |1 o4 `" H0 f1  9/29/2016 12:00:00 AM  23:50:23       1.21           48
) h7 g' a, L, N- d0 X2  9/29/2016 12:00:00 AM  23:45:26       1.23           484 R3 a- U0 z2 _
1& |; [- e+ @( T
2
8 r( q# z0 L, [3/ [; D/ O+ b/ p9 c0 T. }5 o
40 G- |: v% t1 t/ a" @
5# Y! \' R5 d/ ?9 [9 g. T: Y
6
* H" {2 G$ F( s; K1 a$ h7# ]' m" ~+ e. Q3 M+ X
8
* d5 t$ M: Q8 H! O将Datetime, Time合并为一个时间列Datetime,同时把它作为索引后排序。# l6 o) T- \+ t+ a( F8 q: x% z
每条记录时间的间隔显然并不一致,请解决如下问题:
# u5 a! t* O* G: x2 s0 U: I找出间隔时间的前三个最大值所对应的三组时间戳。$ V1 J; [6 C  V* s5 J: V1 T& f
是否存在一个大致的范围,使得绝大多数的间隔时间都落在这个区间中?如果存在,请对此范围内的样本间隔秒数画出柱状图,设置bins=50。9 {/ U) w5 e1 Z. h8 `
求如下指标对应的Series:. b  Z* W% d5 g' y. Q+ Z
温度与辐射量的6小时滑动相关系数
+ X% [, G% p- ^& N% y6 \& w# R以三点、九点、十五点、二十一点为分割,该观测所在时间区间的温度均值序列, {: O7 V( t# Y7 H( ?
每个观测6小时前的辐射量(一般而言不会恰好取到,此时取最近时间戳对应的辐射量)
5 ]' h1 \  ?  Q7 B% _import numpy as np& q, [  T% ?" h$ f( [8 k
import pandas as pd0 I: A6 F+ ]. P3 ~" N) P% i1 a
1
0 p6 e6 p3 T* U23 u: ?- m( d5 V6 l: ^0 T! l0 k
将Datetime, Time合并为一个时间列Datetime,同时把它作为索引后排序。% v5 K4 a( N2 N
data=pd.to_datetime(df.Data) # 本身是object对象,要先转为时间序列
# H& X7 A- q2 d0 Ttimes=pd.to_timedelta(df.Time)
( S9 w* `( H* Sdf.Data=data+times
( {7 ~5 x" b; c, zdel df['Time']
. h. w; m) Z7 ?4 rdf=df.set_index('Data').sort_index() # 如果写的是set_index(df.Data),那么Data作为索引之外,这个列还另外保留
4 o1 d4 g+ D/ m" Ldf6 A8 E; i8 a0 S0 v  ]) N
                                        Radiation        Temperature
9 @$ D3 S5 B# i* ^9 m, eData                9 q7 W6 M, L9 i7 `$ Z
2016-09-01 00:00:08                2.58                51  ?6 b0 j0 h5 T$ N8 i
2016-09-01 00:05:10                2.83                51, n6 R0 j$ n& e, n4 X& v- M
2016-09-01 00:20:06                2.16                51; H" F- y  Z% s7 V8 K0 E
2016-09-01 00:25:05                2.21                519 W4 q# F# ?0 m& I. @# @, l, g
2016-09-01 00:30:09                2.25                51
( I0 ~: E" c8 `8 b+ j' H7 Z( L0 F...        ...        ...
  B1 {% Y. I& b; n5 B! @: h2016-12-31 23:35:02                1.22                41
6 {- L6 e  ^( y; j2016-12-31 23:40:01                1.21                41% M$ \  V% y( k" D
2016-12-31 23:45:04                1.21                42
- U; j8 u. t- ]8 S2016-12-31 23:50:03                1.19                41: J0 u4 N1 O" C
2016-12-31 23:55:01                1.21                41
, ~) R% B! O& O# q/ V$ m6 f& O2 V5 S- w( ]' r$ @7 x
1
+ ~$ Z* H9 S. _. k2: z  F$ B7 [$ ^7 |: o$ K9 d
3* _1 B! c. q3 R7 \
4% o) g/ V0 r( z
5
- q$ ], W+ o: z; E& v69 W7 l5 X1 _: K% g4 ~6 |
7$ i  E8 @8 D& R' O! |
86 W( d2 D% K+ a" A
9! J4 y' q+ r6 d( [9 I
10
) E$ y- O) c3 o7 i* K11: s; a. y4 g- i
12
' m- T; C* z+ O/ N( O, ^13, r% X- @0 P5 t, u( [0 {+ i+ y0 H
14
/ q2 Y& d- v, T0 _* n* v5 w15
3 `; F8 L' e, ?  `; ]( ~+ _* i1 X168 I( G1 ~6 K) ^; `" J6 `6 W4 g( s
17
- q5 }3 g0 `3 K0 M3 ]! q$ K5 u18. ~7 `# Q0 g/ [% G4 b% m0 Q
19, j# v( n$ P% F; }6 K- p4 ^
每条记录时间的间隔显然并不一致,请解决如下问题:
% p; z0 a$ u' {8 W% ]' y找出间隔时间的前三个最大值所对应的三组时间戳。
# j; l4 P: [; L; x8 g# y3 Y# 第一次做错了,不是找三组时间戳
+ }! g2 N6 t1 Y' Q# Q4 w; M0 W7 l2 T5 Tidxmax3=pd.Series(df.index).diff(1).sort_values(ascending=False).index[:3]* ]3 P1 `' N* u, `/ V$ l6 K
df.reset_index().Data[idxmax3,idxmax3-1]
8 @$ J+ O# l: X
5 _9 f/ f. P/ g8 T: `1 ^/ c25923   2016-12-08 11:10:42% a- d/ U9 ^' D  w5 N, F/ q( o; {
24522   2016-12-01 00:00:02
3 \2 Z% w0 }9 B7417    2016-10-01 00:00:19
7 D/ g7 A  w$ E1 _4 |) y2 KName: Data, dtype: datetime64[ns]
2 p( w/ P2 t  U6 D( `1
, y% M, Z) K. C2
+ c! M' V1 r# G3! v7 A9 q$ f$ L+ r8 m& h/ z
4
. w8 p" v: p2 X. v2 ]7 H6 J. Y3 L55 C2 h$ u, C7 X7 y7 n- I$ D6 L
67 a9 E; y( O/ [1 i$ L5 ~
7
( W3 a* Y0 a: u! [- x- {8
$ i' g. R7 b$ Eidxmax3=pd.Series(df.index).diff(1).sort_values(ascending=False).index[:3]
0 ~' G4 s) m2 j+ q0 qlist(zip(df.reset_index().Data[idxmax3],df.reset_index().Data[idxmax3-1]))
; ~/ a6 j, S, M' {9 g+ L9 O+ ^$ m# j% o
[(Timestamp('2016-12-08 11:10:42'), Timestamp('2016-12-05 20:45:53')),8 X" C8 d0 y3 g8 [
(Timestamp('2016-12-01 00:00:02'), Timestamp('2016-11-29 19:05:02')),
6 ^9 b3 T. ^, ~3 M) N3 | (Timestamp('2016-10-01 00:00:19'), Timestamp('2016-09-29 23:55:26'))]( H9 ?3 R0 i/ }; y$ q
14 V1 E; G/ W, d8 k  F' I
2
2 Y. u. G% v1 y1 E4 P3
) o, A5 Y: Q2 H3 M: T+ R6 h+ L; @49 C+ U$ H" W1 ^; l0 g1 B3 A
56 O( o5 |! J* D/ m+ a$ o* m5 w
6- s/ T9 [' A4 O3 j3 ?% U, w
参考答案:% q8 o! U% A6 J. u; v$ ]+ n
  ~2 f. Y! a, j1 O" |
s = df.index.to_series().reset_index(drop=True).diff().dt.total_seconds()5 G9 E, E3 J' ~7 \+ l( h: l: q9 d
max_3 = s.nlargest(3).index
* o/ Q( p2 A) w# A3 xdf.index[max_3.union(max_3-1)]2 \4 R/ E6 S) t! B

) p. t# d- C2 s% J: t) eOut[215]:
/ R8 @& ^% |7 o6 d1 {. ODatetimeIndex(['2016-09-29 23:55:26', '2016-10-01 00:00:19',, N2 k# Q, L& F. ^" u: P
               '2016-11-29 19:05:02', '2016-12-01 00:00:02',( d3 k# k) `) r2 M) U% x/ S0 ?* ?
               '2016-12-05 20:45:53', '2016-12-08 11:10:42'],
% M5 c, q+ D& \) |              dtype='datetime64[ns]', name='Datetime', freq=None)8 T8 c% e" c' ~" c- z# X7 @
1' X. Q* [" ?6 m* a* B
2# ?: g( C% d# u0 l; ~- k6 e
3! K6 G/ T! R3 z1 ?# T- z* Q
4
7 @' |) ~9 b2 l. |) }2 M5
- t0 [  ]( k, x6
6 K# u1 R/ g( e5 k7 R4 ~. p7
$ P, r3 B6 P; e1 J9 p6 E8
6 m) r0 G* _2 M+ W9% w9 i: c8 e$ @) a9 E8 q8 c4 r
是否存在一个大致的范围,使得绝大多数的间隔时间都落在这个区间中?如果存在,请对此范围内的样本间隔秒数画出柱状图,设置bins=50。* z, |. R$ ^  `% q" G
# 将df的indexydiff做差,转为秒数后排序。再求几个分位数确定取值区间, @  j2 N1 h: g- e# _8 ^* F% G! a4 \
s=pd.Series(df.index).diff(1).dt.total_seconds().sort_values(ascending=False)
2 b# I$ o4 n: v" `! ss.quantile(0.9),s.quantile(0.95),s.quantile(0.99),s.quantile(0.01),s.quantile(0.03),s.quantile(0.05)- W! S. M1 f2 t  B# @7 _: P3 t
8 b2 Q' y: L# {  N# F! P6 |
(304.0, 309.0, 337.15999999999985, 285.0, 290.0, 292.0)
* J5 j% }! I  C, L9 z5 ]0 O' e9 h11 s/ L/ ^3 o6 j/ {7 g7 H
2
2 J7 p9 F" o" E3
4 ]# R7 r3 v6 x# m. H- S4
9 b: q/ r+ b! C4 g: i0 f5
$ _1 z' n3 p; Y4 W%pylab inline
. I- R* w! e2 j& C7 z_ = plt.hist(ss[(s.values<337)&(s.values>285)],bins=50)  a9 P/ X6 E5 S& U
plt.xlabel(' Timedelta')
9 ^5 X$ H1 h, |6 B# x- e# wplt.title(" Timedelta of solar")+ W5 E& p) W) n9 d, O7 s. e
1
8 f6 G% \9 N0 N( c) o  _21 y9 r/ C( T- h  |2 k* C" {) N) F
3: g* {* P8 p. k: w) V# ~0 j4 s' |
4
1 Y6 @6 h8 s# B+ Z# M  b
# H$ x& `5 b$ Y& @! j2 _0 x
' a; p! N" t4 X, j7 I; }求如下指标对应的Series:; _% }& i. t, F% _: o  Z5 z3 F
温度与辐射量的6小时滑动相关系数+ q& x) Y. w, C' y9 w
以三点、九点、十五点、二十一点为分割,该观测所在时间区间的温度均值序列# ^. a7 R% M3 A
每个观测6小时前的辐射量(一般而言不会恰好取到,此时取最近时间戳对应的辐射量)
8 h1 {0 T+ I9 ydf.Radiation.rolling('6H').corr(df.Temperature).tail()
6 U7 g  D7 Q; z3 V0 \
( p5 f$ @1 E6 ZData
- q' L4 d# U9 R2016-12-31 23:35:02    0.416187
# a8 d. |, v6 [, O2 b. U* Z( k2016-12-31 23:40:01    0.416565% }' x0 [. J! G3 b& c$ E
2016-12-31 23:45:04    0.328574
' |7 P9 b6 \; E3 r! l2016-12-31 23:50:03    0.2618839 c- C3 o) `7 M+ f
2016-12-31 23:55:01    0.262406
. q: v9 t3 N" E3 a' Q0 P5 a& cdtype: float64, @: A  z6 V8 p
1: B, E5 f6 w7 ^1 p2 m$ e$ U; \) _: g
2
/ V( d; v( D9 J0 c; ~* M: E3
; j' z; e0 b% K, |0 E% _4
5 Z6 p1 J, Q1 k/ _4 ~/ V2 H5" |8 \! C* n2 e
6
7 d2 ^+ P3 [* ]3 Y7 s, R; S7
* n$ H# c" u6 c8
7 U. o2 P1 z" _6 E/ g2 g$ v9! G$ l8 L- n# c7 j* F! k# b- v) A
df['Temperature'].resample('6H',offset='3H').mean().head()
5 y# V1 V- u* P, V
4 W; W, \0 l$ _1 oData
4 y2 l- t7 ~% X/ h- t2016-08-31 21:00:00    51.218750- ~! A) [' f. j5 r
2016-09-01 03:00:00    50.033333
* e. V: c, T1 z2016-09-01 09:00:00    59.379310
$ q# c  W- N5 R6 j5 a# W' L2016-09-01 15:00:00    57.984375
7 i' o4 p  M& D/ Q' h2 D& G2016-09-01 21:00:00    51.393939
3 ^3 w( R7 x# p6 zFreq: 6H, Name: Temperature, dtype: float64. u) t5 e6 {+ X" |
1% ~# O. A% k% [& G2 U
2' o' d  _& C8 I: _3 r8 l
3( U, k$ t3 M' k2 Q
4
/ x7 A6 v6 J4 }5
5 X# X# {1 v4 j: ~, U6( C5 I6 ~) x2 Z3 c: D2 R
7- x; U3 E( _# z. v( P0 y% K9 W7 c
8, ]) p& c: {% ^% E6 o( i
9
* T( K; o9 _- T: n) H; [% a0 a最后一题参考答案:
6 S9 W& F) T! m6 J% ~& B! t2 T/ X& |0 n% }& ^4 X
# 非常慢! B4 H' S+ W. |
my_dt = df.index.shift(freq='-6H')0 i! q, u9 J# i
int_loc = [df.index.get_indexer([i], method='nearest') for i in my_dt]
: ^3 A/ [! E3 f% Gint_loc = np.array(int_loc).reshape(-1)
5 s# e  ]7 k# Z" pres = df.Radiation.iloc[int_loc]) w7 b7 E$ k  c0 Z( J/ Y3 |
res.index = df.index4 X2 `: x- P3 U$ C( B  {
res.tail(3)% D0 K' o- R0 N- J! z
19 ^2 M9 a7 M# m. M. Y+ t
2
1 i& l% f( w8 W6 q/ [4 m3. J5 r$ P# P7 b
40 v6 |( u0 G, I( B* _
5
, P  ~3 P: d: t0 V' e  j% a2 n6
: k. a) o4 Y4 ?8 Q* J7
4 r8 v5 ^, [! D( e# 纸质版上介绍了merge_asof,性能差距可以达到3-4个数量级' _* f: r2 R3 h- B& S* I) K
target = pd.DataFrame(+ U" f. |: i7 O9 S
    {
0 D( I1 C& v. y3 H( u        "Time": df.index.shift(freq='-6H'),2 }9 l1 @* K; W
        "Datetime": df.index,- h2 @6 d# t0 a. o! K+ r9 q
    }
' C4 k1 c1 e* |) |( c" x. k)$ g5 t' E5 `; Y/ q
( K  V4 d  M, T4 A* K
res = pd.merge_asof(
% }% T, b2 I. ~0 M# O; f. a    target,
% }9 T  L; C# F! }8 w    df.reset_index().rename(columns={"Datetime": "Time"}),
- P4 @% e  m/ R% ^9 Y    left_on="Time",
  W) E. T3 F4 K/ A    right_on="Time",
; F5 t" q* G0 y% o6 s8 N' F    direction="nearest"9 X! Q7 h& U8 C4 f& p7 x4 u& L$ R
).set_index("Datetime").Radiation
0 x: ~5 s: _# T. s: r: B1 L; F5 t% s4 H6 f% e
res.tail(3)
9 P& P" a, I4 ?  }Out[224]: . g4 T3 F6 V/ s' O0 k( J$ w$ ?: J
Datetime
9 b% v: L# j# J* y1 b( e2016-12-31 23:45:04    9.33# q! r1 }' T8 d$ G
2016-12-31 23:50:03    8.49
- r& q% T, U7 A( K' ^7 r$ n' V5 w+ j2016-12-31 23:55:01    5.84
9 ~5 \9 G# x9 t% Q2 Z2 ~8 E- eName: Radiation, dtype: float64
: v6 k7 }  L; f# _5 ~( H. C. e7 g4 [( M: ?4 k
1
" k% V6 i, ]- ]9 z28 H! a) w! P' w' w9 o4 V
3: m& e% U4 R! ?  Z
40 \' F( [6 Y5 n! q* T
5
4 _, b( q8 Y5 h# J0 u6 h68 v. K7 \, a& w7 {& ?" ~, h
72 [- \  v! N3 X# i; Y/ E2 X: u  Q
86 K: B8 d$ |2 p5 y6 r( f; f
9% r8 [* e1 q  |, {- f- x6 [
10
- a/ P& U$ P! W2 B- F! E0 q11, m* h$ w4 i1 l5 V
12/ v  ^) B" E( q$ ~6 L% S
13; b4 Q- q/ S: }4 x  B
14
5 a2 f) _* T2 y9 c  D" Y+ n15' v: H. T2 j; y# ^% P% i
161 P6 b& w  i1 c  E  N9 D
176 I$ L# \; A. k0 ^! m. G
18
5 ^6 J3 [. X' f& I5 ^19" j& K  ^/ L, e
20! O- k. Q! u! [$ H+ O+ M
21
/ O4 n& C& e  B. P- U22( H. B4 u: s5 S
23
8 j4 L8 R0 A9 _3 m, CEx2:水果销量数据集
% ^6 F8 ]4 d0 b3 M现有一份2019年每日水果销量记录表:
; D; _! h' K. k( E* m2 C5 h
+ B# E1 J/ s5 mdf = pd.read_csv('../data/fruit.csv')
9 x- c+ P- F0 X( E  s- g0 R3 N& }df.head(3)
  X" d  b- _7 g! c$ g5 q
: B  X/ I% }: n- G( jOut[131]:
- l& G! A3 j& l* r) Q4 b9 r6 X. R         Date  Fruit  Sale1 J" e  x( |- m- @& @5 G$ {4 K* ?
0  2019-04-18  Peach    152 t* k* h, Q# T% K$ @
1  2019-12-29  Peach    15
9 K6 V, h3 v- ^0 U! Q7 i2  2019-06-05  Peach    19
: C: `9 f$ X( m# C; t2 \0 {1
( e5 W* k( P: h8 R$ I2
+ J1 i0 z/ h" \4 u9 o3. _. S$ k" ]/ C! I- Q
4" h) R+ @, c( ~; ?; I
5* Q2 {% Q9 F& k4 `
6; G5 D8 m% x9 K% t
78 {) i1 U& U8 q. e/ j- A
82 k1 l) E% w( U2 L! F4 A
统计如下指标:9 X/ k8 T8 G  P7 h& u; Z# @
每月上半月(15号及之前)与下半月葡萄销量的比值
0 h, q% a& M; ?5 S/ \每月最后一天的生梨销量总和
! u: i; J+ p( _/ [+ C. a每月最后一天工作日的生梨销量总和. U# E/ v( N0 |( n$ i( D3 h( F
每月最后五天的苹果销量均值
) a& p& J3 ]% v6 f按月计算周一至周日各品种水果的平均记录条数,行索引外层为水果名称,内层为月份,列索引为星期。' [1 K/ r) @1 s4 X
按天计算向前10个工作日窗口的苹果销量均值序列,非工作日的值用上一个工作日的结果填充。7 F% A. H8 F& q# G9 W5 B
import numpy as np
1 |0 N- t% o, _import pandas as pd% ?* J3 X- B, H
1
3 x- d& a3 Z; [26 R3 ?# i) A9 m+ @. `
统计如下指标:
# x( O; A  P* u* z' r: d0 X每月上半月(15号及之前)与下半月葡萄销量的比值8 d1 N; a% I+ @- J, u, R
每月最后一天的生梨销量总和
1 k. i* l3 x( v) L2 x8 Q0 n每月最后一天工作日的生梨销量总和
+ M# D/ P) m2 J每月最后五天的苹果销量均值# m- d& ^3 v( h  u$ K. z' \4 j
# 每月上半月(15号及之前)与下半月葡萄销量的比值
8 N8 A5 a! E; ]" sdf.Date=pd.to_datetime(df.Date)
7 ^8 q1 ~1 e* T4 h- M3 d& Asale=df.query('Fruit == "Grape"').groupby([df.Date.dt.month,df.Date.dt.day<=15])['Sale'].sum()2 h+ U  \6 u- p) t! W4 ]) V
sale.columns=['Month','15Dayes','Sale'] # 为啥这么改没用啊
* u" f4 a4 {+ U. \# u9 F; Bsale=pd.DataFrame(sale)
, ^5 l3 S  q( ^4 k/ R1 |sale=sale.unstack(1).rename_axis(index={'Date':'Month'},
, _& T! l% P  |3 b6 L" g% @                 columns={'Date':'15Days'}).stack(1).reset_index() # unstack主要是两个索引都是Date无法直接重命名
7 h! |2 ~( ^3 Asale.head() # 每个月上下半月的销量: Y" [/ E* E- `3 ?) y  O9 I
8 K+ D) P1 y' J9 n- G7 R9 V) N
  Month        15Days        Sale
( f. M3 N& |7 _6 K! N8 d& e0        1        False        10503/ q4 k  v  _+ T5 r1 _! O
1        1        True        12341
$ X! y% E$ v7 S* S6 R7 l& O2        2        False        10001
# S2 E; {" A+ a, b/ P8 |3        2        True        101068 P$ N0 o% L  `5 V' |, E! Q
4        3        False        12814
% `: C) r5 O+ M3 ~7 B! z7 S) ^7 d
# 使用自定义聚合函数,分组后每组就上半月和下半月两个值,根据索引位置判断求比值时的分子分母顺序1 u/ h5 \$ |! |" O
sale.groupby(sale['Month'])['Sale'].agg(
3 J  S+ ^: H& F% @* h8 }# ?5 H1 t, A; |                lambda x: x.max()/x.min() if x.idxmax()>x.idxmin()  else x.min()/x.max())0 ^0 r2 X* O1 X% Q
3 i0 y7 \1 X- k4 ~
Month5 U" S$ y2 k4 D- Q! f
1     1.174998
+ e) u- U1 i9 \# R5 W* ~$ q  s8 b* M2     1.010499! U6 {# \' i- b( _
3     0.776338
3 J; d4 b: B% j# c5 B) p4     1.026345
& G: i& O& D; M3 L4 H# c8 _3 A5     0.900534" O! M: Y, w0 j
6     0.980136
' T' g' p9 T) Z7     1.350960
$ A& C# {" ?7 {8     1.0915841 e- x8 j7 V) @/ i2 w$ i  z% C2 C- t
9     1.116508. P+ X0 z1 \/ L4 _3 r( Z
10    1.020784
; V% ^" U5 q& }; Z" j3 y: Y, j11    1.275911
! T% y8 w% B- p12    0.989662
' h# k6 i# u+ a* A- ?1 j+ q$ ]' oName: Sale, dtype: float64
4 l4 ~# a2 U; ~, q- B
5 Y+ V7 `4 k5 |+ z1
9 ]) T9 V7 }4 ?' ]! s! o1 Z3 a2" f$ h* \: s: e$ l( Y0 T1 u" [
3: V+ s& m3 J( H, X0 t
4
, d" Z; L& A6 n5/ M; F1 z" E$ F) u3 d
6
! j6 q, n1 Q' E: }2 y, a7# h8 `1 J9 F0 L
8
" T; ^2 ~1 H* j, H9/ \, d. L: N7 `+ b
10
5 e0 G* P" Q4 q" j. i$ x11
: ^0 \6 f7 o  L/ x/ O5 L12
" V& m) V. {0 D13# e+ ~- w1 z% ]2 @+ Y; Q) [
14: `# W2 y0 ^" |1 h/ Y8 B
15
9 T( N+ f$ J/ n3 B& O9 }16
- @5 `+ i5 Q: W17
# Q$ t- U% x$ ^' W1 A" v7 _18; y. B" L9 W- P1 ^8 R4 P0 L
19, T6 r: f! \) F8 h: P/ j, c  Y% G1 ?/ t
20
2 N+ H1 O  R( b& Z" p21& y5 q0 s0 z) f  e, q: s$ u
22
. v. h) q" K% Q23
$ |# O1 _; y' q0 C/ f24% n0 ^3 F% O8 c0 u" N
25( P' k# |: ~& S( [
26
* o* d+ L6 ^6 a% O; G27
. o1 M* e" \- b$ s% Y) Y288 T2 R; s, f. {' b, q
29  H$ z0 S) ^2 {/ w$ b
30- u, y' A8 I' I: T2 Q* E
31- F% D  I) w( M
32
' t% e4 s5 q9 v! i! E33- d3 |. ^% p+ `3 v2 b) X8 ~
345 H3 X6 h* ~" h+ T* _
# 每月最后一天的生梨销量总和
% y6 D3 B4 |* Y, o. \) s% L& {df[df.Date.dt.is_month_end].loc[df.Fruit=='Pear'].groupby('Date')['Sale'].sum()
, r# y/ P4 x5 y  o! @$ _
6 o4 X* s; u0 R- IDate
2 P+ Q+ r, s" F& n2019-01-31    8471 m+ @& g) U/ ~) f
2019-02-28    774, Q% K( [  [( _- ]5 J! K
2019-03-31    761
  ?8 y% n: T  T2 H- O2019-04-30    648$ n# s, K% }4 f/ S3 y& Z
2019-05-31    616" v/ e/ V" D$ q" }' X' h! b1 C9 Y4 T  ?% H
1$ ]. i$ T9 o* @: z; j) h0 e' E" g( _
23 }, G) }& [2 y9 @
3
( V% c- @2 {3 _( N- g* r& G+ a" l40 J- y0 v0 s" B* \' c# L
53 t( {1 R1 b. }1 V
6
& z; j" U: P  y' v7
/ _  C9 M, N8 h+ u8 i7 {8* I7 Y% p& y4 {
9" D! A+ q0 T0 G
# 每月最后一天工作日的生梨销量总和
4 ^. K1 I9 o1 vls=df.Date+pd.offsets.BMonthEnd()0 V. K+ J" N1 K+ K+ v
my_filter=pd.to_datetime(ls.unique())/ C, C3 i" B! ~; o3 t4 g0 J8 H
df[df.Date.isin(my_filter)].loc[df.Fruit=='Pear'].groupby('Date')['Sale'].sum()
3 n+ U, V1 K7 a( u; A& U6 ]
, l4 b8 N5 @3 d* O/ SDate
6 Y! b, ^  D! f2019-01-31     847/ c- E6 A0 a! N9 P. Z
2019-02-28     774: @$ H4 h/ K- ]; e9 J. n
2019-03-29     510. G! N0 T: c+ G; p+ ^+ w7 X
2019-04-30     648
& s& ?; V0 h( y$ L7 C2019-05-31     616
6 N, s4 a3 K8 t/ D' r3 k1
9 ~* j# b: H& G2
0 K7 ?0 m/ o2 k+ F/ _/ t5 T2 L3
( N  R% g2 r7 _4' m) Y" ]( P# \# M' P7 @; [2 d! k
50 X8 ~& ^5 m- m' ?- b
6
0 X, c4 X4 u8 x$ T7 {. }+ T77 I2 J3 N0 x7 T- {* W" g4 ?! s
8
* w5 c, h8 d, ?8 g+ ?90 W, ?- p, c! p: ]; S7 H
10( w( U2 ]3 z/ x) Z- x2 n, U. d$ }$ j
11) [/ [; L& J3 {$ M% o" X8 I8 X9 b9 ]9 ~
# 每月最后五天的苹果销量均值
  u1 G6 S! y3 t* pstart, end = '2019-01-01', '2019-12-31'
. ^& \1 ]+ I: Uend = pd.date_range(start, end, freq='M')2 i5 A, c8 h% g; D3 y" N3 x/ T
end=end.repeat(5) # 每月最后一天的日期列表,重复5次方便做差: \- A6 w, u- [' g/ B
5 J5 ^6 g# F3 _2 n9 Y
td= pd.Series(pd.timedelta_range(start='0 days', periods=5),)! D  y, @% S6 C: Q! G* M
td=pd.concat([td]*12) # 日期偏置,最后一天减去0-4天+ q  C( c0 b% s5 X1 F' Y
end5=(end-td).reset_index(drop=True) # 每个月最后5天的列表' {7 a# z8 [: t; V

* d& ~8 B, l5 J, T: ^: R# Zapple5=df[df.Date.isin(end5)].query("Fruit == 'Apple'") # 每月最后五天苹果销量6 R* Q! V9 u: W8 x5 r$ N
apple5.groupby(apple5.Date.dt.month)['Sale'].mean().head()
& A2 |4 p$ T5 t8 e+ G& A- {3 t2 ~
Date
$ N( `' ]6 ^9 J& z7 A; T) g1     65.313725
& i8 }- Y# J1 ~9 \( y7 G0 V1 w2     54.061538
: }/ d: c1 e, }8 V+ v2 F$ v/ O3     59.325581( k, t: d  _6 M) n9 X2 O& K# m
4     65.795455! \; {$ _5 G6 j
5     57.465116
6 {6 Z- j1 V2 I- m% O
( y8 U8 q- q4 i% ~( P& V8 ~' |1
9 q0 W' U! z# e6 R3 a2
  I* `! @; B' O* i) }! B3
4 o& Q8 s# b1 J4 |! S. g4/ E: p% g" ?, m2 R! y
52 ~* l9 i% I- g. b$ u" {1 k, J
6
5 o5 s1 t4 [2 S4 m6 s3 r7
/ k8 d: Z3 e& R9 I0 o2 |* Q/ H) ~8; \# }7 a/ X( \( c
97 |+ \! v% m$ N
10
0 I$ ?6 j& w1 a11
0 S% P- v9 U7 L) Y- n& a7 @12
' d0 }( l" v9 X: ?4 i13
3 |8 L3 G% j& F/ u( y14, J- ?; N( l0 G
15
" \- r5 E/ G% c/ ~% j2 s+ `16
1 v8 y7 |; n: z0 ^) `, |17
* l5 f" W) r( X184 \( d( ]& g  H; m* U
# 参考答案:* R" v& `. x! D, |% E
target_dt = df.drop_duplicates().groupby(df.Date.drop_duplicates(: i( {) h# N  w( e
            ).dt.month)['Date'].nlargest(5).reset_index(drop=True)
% j* a& e5 Q, Q1 h, C
1 [$ K6 P1 i( ures = df.set_index('Date').loc[target_dt].reset_index(0 U0 |2 q! s, I9 I  ]
            ).query("Fruit == 'Apple'")# _8 Z5 ^6 r& U, F; A5 y4 Z4 V
/ a; d. J" i* L" x
res = res.groupby(res.Date.dt.month)['Sale'].mean(% E4 k7 [  }: W! r
            ).rename_axis('Month')
4 |5 }0 X9 q0 m' M
- Q7 X3 p, A% v% ~% V# K3 P9 t6 O# i* i1 \! u
res.head()
: X3 Y- t! r$ n3 h1 W- `' BOut[236]:
$ X; x1 |! {7 M+ x+ WMonth
3 L3 h% ^4 w6 d1    65.313725
# u2 d3 u/ H5 Q5 y9 V2    54.0615380 N. P1 R, r/ N' i; N
3    59.325581' ~  \* y! F- \$ o* E8 Y+ J2 x
4    65.795455
' }% K3 f; U) R7 D( n5 g! V* [% x5    57.465116
( d3 I2 U# c' p. h7 F# ~Name: Sale, dtype: float64
/ N: T7 ?8 W/ s6 \
, s4 M1 Y4 U9 C1* ~7 v7 u" l5 C# H, a
2
& V% i$ `# j; |* j; Z5 {35 x' H: S, ~  D! f/ B
4
& h, k6 T/ ]+ ^) K1 O/ T( i9 L5
8 g8 K+ B, T! c5 h6' S$ a+ }, R3 O
75 e8 l+ |9 f# X4 [8 ]; d
8
5 q. J/ @. X4 [" s! q% v6 p1 ]9
. i3 y, e  l: z: i  G3 A6 i$ x10
3 L; t4 R1 `! _  K2 h) y/ n11; \+ E: K( o9 n+ j
12  l4 |/ b, i% O7 u$ [+ d) K1 W" f
13
+ N: J6 W* H& y& @9 V14
- N% A. f' \. n: L; S154 M, _' M, b. A; n+ ~0 h
16  Y1 T8 E& Z' S7 y: S4 Z: Q
17
: x. a$ S. k! g, ^- I& A, S18! b( v) m& a% z  L8 V
19
0 ]% a  t6 v  p: O( V5 G! ^, a$ z9 a20! N: p  N% M1 H* L% ~
按月计算周一至周日各品种水果的平均记录条数,行索引外层为水果名称,内层为月份,列索引为星期。
+ c, w$ s. d( ~! a# I0 ]result=pd.DataFrame(df.groupby([df.Date.dt.month,df.Date.
% Y/ s  f0 W- c0 w9 j; K$ w( c  y% M                                        dt.dayofweek,df.Fruit])['Sale'].count()) # 分组统计 " r1 D1 Z6 m. u5 U
                                       
% I' i+ G3 P2 }& n( vresult=result.unstack(1).rename_axis(index={'Date':'Month'},
' G2 Z# G9 E0 V% b                 columns={'Date':'Week'})  # 两个index名字都是Date,只能转一个到列,分开来改名字.# K4 o' [# ^5 @. R, b6 @
result=result.swaplevel(0,1,axis=0).droplevel(0,axis=1): s! ?- X' Y5 r7 E( n, ~, d
result.head() # 索引名有空再改吧) e2 b8 f1 b, ^* i, M* p
; m) G! d7 b/ |$ }7 A0 \
          Week        0        1        2        3        4        5        6
6 p7 @- F9 c. E7 Q+ vFruit Month                                                        # I5 O  C0 G+ k/ n: y  b+ k( y/ b1 `
Apple        1        46        50        50        45        32        42        23
, }2 r: N6 E& G) q1 OBanana        1        27        29        24        42        36        24        35( p7 \% Y6 c# ~, {. d
Grape        1        42        75        53        63        36        57        46
5 l+ D, u- S8 v1 ^7 p8 I6 \4 s; mPeach        1        67        78        73        88        59        49        72
, g$ N) `+ T2 E9 L4 b" E9 lPear        1        39        69        51        54        48        36        407 Q1 p6 V$ ~. u: F
1
3 t+ s  g9 k5 p9 _" O( r1 M2( {/ O6 \3 m+ ]$ ~7 q# [
3
: J. K2 @. A$ r7 k' {4
' Q  q3 E, `* v2 k6 h5 T5# v; v/ k! w  N+ u
6
. w$ y3 {/ w( ?. s! g4 l3 y7
% ?8 Y4 Z$ d& E8* w9 O8 ]' k6 z% H6 E' v
9
. F/ m4 S+ o* s# Q10
( l0 f( D- y- t9 l8 Q112 D. n- v/ K" p$ l+ K4 }2 z
12
" l7 p! v) V6 X13
* k0 r5 n0 k( g" E0 a' {14
+ W9 u9 u/ g, D3 b( ~* f159 F) g( a7 n, z" M& n3 \$ D
按天计算向前10个工作日窗口的苹果销量均值序列,非工作日的值用上一个工作日的结果填充。
3 U% d1 f+ N8 X) q8 R# 工作日苹果销量按日期排序
+ ~. O. O; A- N& V, _select_bday=df[~df.Date.dt.dayofweek.isin([5,6])].query('Fruit=="Apple"').set_index('Date').sort_index()9 ~! x. a1 \# M8 v# X. D
select_bday=select_bday.groupby(select_bday.index)['Sale'].sum() # 每天的销量汇总+ F5 a9 I6 T$ `, b3 [) s+ q7 ^9 {
select_bday.head()
! W% l# _' j# @6 L* }. K
. J2 L4 a$ I) R1 r$ ]Date
9 O7 p  ]# L+ J2 P* v/ q5 Z2019-01-01    1897 Y. K, p3 a2 x% U# G1 r/ k+ e
2019-01-02    482
2 f  q$ k$ e% d' [) E6 A2019-01-03    890# n/ l8 V& i- k) o! P2 M5 V. M
2019-01-04    550+ Y" ^* N: q* p- m" K
2019-01-07    494
8 M/ G2 j4 @5 ]! ]% O! n& y' t' {2 E6 i) G
# 此时已经是工作日,正常滑窗。结果重设索引,对周末进行向后填充。7 X  g; \9 H) u+ e: ^6 Y
select_bday.rolling('10D').mean().reindex(df.Date.unique()).sort_index().ffill().head()
, j* Y% H1 U/ F1 l! H, R  p& [: U1 [1 i3 C( T& D
Date) m' B. o6 O7 U6 h9 h
2019-01-01    189.000000
. ^& \+ b, A4 v% H2019-01-02    335.500000
6 A# u4 u4 L( |1 ?2019-01-03    520.333333
5 k3 n; L/ J0 A2 X! P( R7 N$ r2019-01-04    527.750000' S1 r3 O" H  [. A
2019-01-05    527.750000
) Y& v  j/ G: O" O" F6 ]$ E' f
8 ?5 s  q4 }# P1 p  V————————————————8 N7 I. K8 H. c! z4 a  ~
版权声明:本文为CSDN博主「神洛华」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。' e( U0 P/ F1 E/ T
原文链接:https://blog.csdn.net/qq_56591814/article/details/126633913& Y' W/ X! p, H4 q, Z

. _2 y4 p" L& N: i$ L* Z! E9 W# u, G+ X( p6 H  ]/ r  L





欢迎光临 数学建模社区-数学中国 (http://www.madio.net/) Powered by Discuz! X2.5