QQ登录

只需要一步,快速开始

 注册地址  找回密码
查看: 2893|回复: 0
打印 上一主题 下一主题

[代码资源] datawhale8月组队学习《pandas数据处理与分析》(下)(文本、分类、时序数据)

[复制链接]
字体大小: 正常 放大
杨利霞        

5273

主题

82

听众

17万

积分

  • TA的每日心情
    开心
    2021-8-11 17:59
  • 签到天数: 17 天

    [LV.4]偶尔看看III

    网络挑战赛参赛者

    网络挑战赛参赛者

    自我介绍
    本人女,毕业于内蒙古科技大学,担任文职专业,毕业专业英语。

    群组: 2018美赛大象算法课程

    群组: 2018美赛护航培训课程

    群组: 2019年 数学中国站长建

    群组: 2019年数据分析师课程

    群组: 2018年大象老师国赛优

    跳转到指定楼层
    1#
    发表于 2022-9-5 16:11 |只看该作者 |倒序浏览
    |招呼Ta 关注Ta
    ( }5 B& o/ E& B2 s

    3 O4 |3 {6 S$ l  p# f8 m
    # S# g- O2 x0 G文章目录" H: \  A7 @! D& D( m
    第八章 文本数据
    3 f! a* V% Y; k. X- }' E, d$ F( V. c1 Q8.1 str对象% \/ a1 n( z4 e/ w5 }4 U1 j, k$ U
    8.1.1 str对象的设计意图
    . V+ }6 T1 D  _9 `4 C8.1.3 string类型* D4 T' [7 X2 g  i1 S/ b
    8.2 正则表达式基础
    8 g% w% d) M4 ?, _. I8.2.1 . 一般字符的匹配$ b& k. g  p, g
    8.2.2 元字符基础. K! k% a- H( ]
    8.2.3 简写字符集
    5 J5 L6 C. r+ H) R. p3 Y! l: b8.3 文本处理的五类操作
    ! S. e! Y) K( A5 E8.3.1 `str.split `拆分' J  }$ ]8 Y& x& I: A' N
    8.3.2 `str.join` 或 `str.cat `合并7 o& p; h! [; H
    8.3.3 匹配
    4 W1 U6 _# ?" r) q9 @& r8.3.5 提取2 w5 H( x4 Q) D
    8.4、常用字符串函数( T  d2 @; V  c) l' _4 L( i- s
    8.4.1 字母型函数
      Q* c" y+ X5 J( `2 B+ b8.4.2 数值型函数- m2 x  W3 K0 F: H2 R5 c% _
    8.4.3 统计型函数
    % D7 `6 `' E2 b) d3 ^% [8.4.4 格式型函数
    0 T0 y& g: f1 G1 M! ~! p$ k8.5 练习  P/ J0 f; B3 j3 c
    Ex1:房屋信息数据集
      ^$ [1 c1 `3 u0 o- GEx2:《权力的游戏》剧本数据集
    - B2 m1 ^6 m3 |0 _# `第九章 分类数据0 r' o( t9 G$ P0 N% p
    9.1 cat对象
    / ]" G) ]1 u- V" u" ^- U9.1.1 cat对象的属性
    : p% R$ L" r! u8 ^) \( Z9.1.2 类别的增加、删除和修改' r( p& i: a- Y, t- Z/ H6 c, @
    9.2 有序分类
    ) m+ Y$ C- X. C3 U4 W) k1 D& w9 Q9.2.1 序的建立
    . [* z9 Z, E  ?3 c9.2.2 排序和比较1 D- a1 f% a. V3 {
    9.3 区间类别+ [, O: ]. Z  D# M9 {1 t
    9.3.1 利用cut和qcut进行区间构造
    + {* }  q( |4 P9.3.2 一般区间的构造0 X' n1 B' p. ~; R) @
    9.3.3 区间的属性与方法
    0 L& O4 F7 A& Y- C9.4 练习% O6 j( m! a+ u, o5 u7 `: B
    Ex1: 统计未出现的类别7 d' E5 B% R1 h2 @  e- W
    Ex2: 钻石数据集
    2 ?$ l- Y+ f7 X4 \, Y7 `8 n第十章 时序数据
    3 ?- F% |7 Z* a* O8 K10.1 时序中的基本对象. q$ [1 f) H& [) m5 }, z6 e1 ^
    10.2 时间戳! t1 U# @4 H( O4 W: h
    10.2.1 Timestamp的构造与属性
    % W0 l% a  M# F1 q( Q1 K4 W10.2.2 Datetime序列的生成; Z0 i7 I% v1 s; {; m
    10.2.3 dt对象
    : z  \1 e; G) Z2 y10.2.4 时间戳的切片与索引5 B7 L3 v6 \1 D+ R% V# s) o
    10.3 时间差+ \+ q& s8 O( r& |$ b
    10.3.1 Timedelta的生成2 a4 f, U$ N5 k5 @) w( m
    10.2.2 Timedelta的运算( R2 l) `) M4 d2 |
    10.4 日期偏置
    1 H2 U* X2 q  c+ B4 t4 K10.4.1 Offset对象) B- l+ v$ u' o8 ?5 L
    10.4.2 偏置字符串* ]2 o  M: m1 ~; i' U
    10.5、时序中的滑窗与分组
      R, o5 C. l/ @10.5.1 滑动窗口( P, q' O! P5 a+ [' M, x: H7 E3 b
    10.5.2 重采样: r# M* Z! k9 q1 |
    10.6 练习; G5 k. G: G" J5 o
    Ex1:太阳辐射数据集5 j5 v  y5 H- Y! g8 X, Y
    Ex2:水果销量数据集
    . E2 L! `4 b( Y: d1 {  课程资料《pandas数据处理与分析》、github地址、讲解视频、习题参考答案 、pandas官网
    0 ~- R* _4 l6 |3 ~' o传送门:
    : c& i6 A7 p! o- Q2 p8 X
    / p5 N  X& ?$ L- Q# k& Bdatawhale8月组队学习《pandas数据处理与分析》(上)(基础、索引、分组)1 A, G' X5 O$ |" [$ C2 |
    datawhale8月组队学习《pandas数据处理与分析》(中)(变形、连接、缺失数据)- u( M% }9 U+ n' Z* ^. u
    第八章 文本数据3 Y3 `6 m* }! _) b! G1 {" g/ r" F0 _7 V& v
    8.1 str对象6 z# ?/ }* ^$ x2 ^
    8.1.1 str对象的设计意图
    * L; n/ Q0 }2 V. G( Q  str 对象是定义在 Index 或 Series上的属性,专门用于处理每个元素的文本内容,其内部定义了大量方法,因此对一个序列进行文本处理,首先需要获取其 str 对象。在Python标准库中也有 str 模块,为了使用上的便利,在 pandas 的50个 str 对象方法中,有31个是和标准库中的 str 模块方法同名且功能一致,例如字母转为大写的操作:
    # r' |6 W" }  |. b! V; `/ V+ D3 R4 d9 k0 B
    var = 'abcd'
    : J3 @) A4 Y4 Y' ~/ Sstr.upper(var) # Python内置str模块
    ) w  X0 n! R0 oOut[4]: 'ABCD'
    * j  M4 [( J* w% u. F  X! V& K) F
    ) I% Y: o9 n( d9 ^$ Bs = pd.Series(['abcd', 'efg', 'hi'])
    2 I5 J5 H( H9 k& P/ C+ j: N
    - b4 p( S2 d6 Us.str' k! z. I, y5 A8 X( m) Q6 Z
    Out[6]: <pandas.core.strings.accessor.StringMethods at 0x2b796892d60>" \% b7 U7 f. t9 ^1 W0 z/ D2 d

    : {# I0 w/ Q1 k& Rs.str.upper() # pandas中str对象上的upper方法
    8 P' C" Q* e- jOut[7]: 2 L/ t* i' A5 @* L2 B; a. f
    0    ABCD
    # I: I, V8 _8 s% y+ w, S7 r2 u1     EFG
    : @* s( I( {+ m+ f- j2      HI& V0 i7 R8 J% h+ p3 @5 S7 [
    dtype: object& O: M9 r8 n! W
    19 j1 s/ u+ t7 P1 O0 g: i; P
    2
    % T4 U. y5 N5 x$ s3
    + {4 O4 C" w4 _- K. q5 V% e4
    1 ~: T4 ^6 I0 Q+ U  z! F5
    " _7 o9 `8 v- x; u0 D& p6
    * k; K- s; _% N" s( \7
    & D7 k1 l# ^. i  H- ~! ^+ J8
    - m% s1 o$ |2 R1 E0 `- j% k97 E; v9 D" N5 M: I6 t2 E. O6 E
    10
    ! ]- s7 w& |% N: P; I' k11, U! @- i4 }, J
    12+ r0 M! \/ |. {. x
    13
    ' {8 `8 c4 H3 E14
    / h3 y' q5 c0 j& B15
    " Y) L/ D5 q4 [! ^) u5 w* B* T# U8.1.2 []索引器. \# S& g  N! I" v% n7 \5 W
      对于 str 对象而言,可理解为其对字符串进行了序列化的操作,例如在一般的字符串中,通过 [] 可以取出某个位置的元素,同时也能通过切片得到子串。
    6 I4 k/ o7 s' h. M, n: K; n  T  pandas中过对 str 对象使用 [] 索引器,可以完成完全一致的功能,并且如果超出范围则返回缺失值:
    ; v5 J# X  l* O2 w1 _9 }3 }* f9 Y8 d* m% e
    s.str[0]( W" j( o5 M" k6 t
    Out[10]:
    + }$ o9 k4 @$ T0    a3 S5 Y* k% ]: b. \
    1    e* l# z, O  ~; k. `) o/ F( q$ _
    2    h, B  K* e6 v2 J, [
    dtype: object: j1 v% i0 ]/ T6 ?
    : j% W* j; J! W4 x' F( o( k- ]2 j& x
    s.str[-1: 0: -2]
      j. z# ~9 u3 v6 D$ p0 w- ^+ eOut[11]:
    " ]( z* r9 e: q0 K3 v6 K" E# Y- O0    db
    2 Q% D6 L8 o6 `2 P: `1 G1     g
    ; p+ U2 x7 ^  ?2     i
    2 ]; P* K) L# x2 fdtype: object, v  e# J9 v( i' U2 M+ y% g; k

    % A/ G9 h; C! f4 ks.str[2]
    " Z! ^: F. D. H. VOut[12]:
    ) `" ^) Y- v3 G% {! x/ k0      c
    9 R; i* ~. q8 e! Z' b9 G1      g
    5 m% p; m' p, L0 w2 k2    NaN
    7 S& p/ V. m" l3 bdtype: object
    2 ^8 Y3 |7 ~- H+ @/ N: @% T- A) F. e' M7 ]
    1
      o( R! d" [! g) T! T' d7 S2
    3 J; D6 _% J* ]( w" h3& _1 \2 F/ R- x$ K$ S* ]& K: Q
    4
    0 w7 P0 A! g$ `6 d5
      _% u. `9 Z$ V, `7 u+ L% z) _. h9 D6 s6
    + E# B3 W7 Y, M( q% y, N7
    " V1 W/ B% p, q9 k4 G$ c87 H6 t) c+ ]% f
    97 {4 H# M7 I0 Q0 _' x
    106 i) s/ c/ B9 s, u* d/ I% [
    113 s6 e8 w# C9 V
    127 H3 x3 p& g. X" _
    13
    & U% @. x0 R, T0 \& F4 G' Q14
    . L) J1 g! t4 H5 S- l9 F9 y2 f; B5 a15+ I% l: w$ T4 |) U8 D
    16
    ) I" d. ^5 X& Q( U8 s" N1 n17
    ! [9 Y3 H- \1 [! Z186 v! O3 u5 x% [0 o- q
    197 U3 }* z2 X  }3 E
    201 C/ c( A" ]9 [. B- r
    import numpy as np
    7 E/ q# u# L; \1 T6 v6 cimport pandas as pd
    ; p7 f# ~2 r' d9 f9 A: k& V% e; r/ b! g8 ^: y
    s = pd.Series(['abcd', 'efg', 'hi'])
    / `2 y! S) e/ l3 _5 a  Fs.str[0]
    7 |7 o* E2 j( b6 V1
    ' @: o- V; e0 J) D5 @9 W$ Q22 G! `* Y: R6 g
    3
    # L! b6 t5 w; R4 A/ H) F/ V4
    * b2 B& j# t$ I: Z) {1 E5
    $ c; Y" V" D$ O% \/ k0    a* S) J: X3 f% F- z) l$ _& P1 B
    1    e  M/ `) C, H0 l# Q( w4 P3 p) [
    2    h
    % a4 _$ N& \0 x' D# n4 ydtype: object
    ( y9 t* J! N+ N- e' p" E' K1
    : r: ?7 y( \, N2
    8 f1 M) L2 @! K4 [: o30 n0 f' }9 U! ^0 z/ V3 W
    48 l: M. q% E" j3 f0 w$ L- f
    8.1.3 string类型# K: A$ D! `& U) M" |
      在上一章提到,从 pandas 的 1.0.0 版本开始,引入了 string 类型,其引入的动机在于:原来所有的字符串类型都会以 object 类型的 Series 进行存储,但 object 类型只应当存储混合类型,例如同时存储浮点、字符串、字典、列表、自定义类型等,因此字符串有必要同数值型或 category 一样,具有自己的数据存储类型,从而引入了 string 类型。9 g' b- F8 O! F1 g1 p
      总体上说,绝大多数对于 object 和 string 类型的序列使用 str 对象方法产生的结果是一致,但是在下面提到的两点上有较大差异:
      C) e3 ^; o1 t8 o% }- H
    3 z9 c6 |  |, N# a% j. Z$ ^& P/ }二者对于某些对象的 str 序列化方法不同。
    % A4 J. t/ X' V3 R2 D& B可迭代(Iterable)对象包括但不限于字符串、字典、列表。对于一个可迭代对象, string 类型和 object 类型对它们的序列化方式不同,序列化后str对象返回结果也可能不同。例如:) a) W0 E8 A6 O; Q% j9 n
    s = pd.Series([{1: 'temp_1', 2: 'temp_2'}, ['a', 'b'], 0.5, 'my_string'])
    & y8 ~& Z1 f$ F, ps
    $ G! m7 @3 K; e  v$ f* `- [, @, n9 e1* n0 }+ {. {1 x. a7 F
    20 D$ Q7 ^3 D# J# f; R
    0    {1: 'temp_1', 2: 'temp_2'}
    $ l8 l! M1 m# `. f: q" @1                        [a, b]& N1 ^4 D* ?1 y) ^( N
    2                           0.5) Q6 K# G; `1 W4 ]' n6 q
    3                     my_string
    + j/ y6 h1 Y, q/ r! R, r: Fdtype: object
    # J2 R1 ^4 `5 p5 g" Q& O$ y( b1% L9 e, _1 I& j
    28 ]9 B  z0 Q  j2 i# Z: B
    3
    4 T: P8 C3 s& R/ X8 i4
    ! Y7 U4 v4 b6 V6 J* z7 p& `5* }9 g# B2 G. {% q
    s.str[1] # 对每个元素取[1]的操作
    , ^1 E: L6 i! x" X1! A! p5 G8 j+ K( V: n) ~' k
    0    temp_18 k8 I$ l, v  W; J/ T. L! A" q  m/ o
    1         b+ M, L  q8 ]6 E/ p6 `- P
    2       NaN, Q! s, q* K0 D
    3         y
    ) @! b: U+ U( E+ s: O! gdtype: object
    , b7 f# I% x& m: D: @9 k19 D" B2 P6 a2 d3 k5 H
    2
    & r& L3 P/ r2 Y3 ]  \- B1 Y3
    5 Y# H# S8 W8 w& `  Z4
    ; F: j: \: }1 V0 U" l* M9 a4 D  [5# a' x' U# u9 m. V7 S5 k; o  w9 V
    s.astype('string').str[1]
    3 f/ y, o( s) m5 V% Y1: u( n% W6 j; \" n
    0    1- e- K; a3 B3 l0 C
    1    '8 g, h8 g( S: ?8 s! G" Q
    2    .
    : a, c! y1 w. o2 I9 z3    y) Q; H- g- c; G! q; V
    dtype: string6 ~& V! C  ~" y' w7 }" P$ I
    1; y8 F0 q/ o  j; z2 t5 r% Q& f
    29 @- }/ P( n0 Q. @4 q
    3
    % q( V2 r2 a8 B* a4
    / C% K1 c* ~# w5$ S+ u. J: i" i
    除了最后一个字符串元素,前三个元素返回的值都不同,其原因在于:
    0 ~% Q) @5 p* `# N8 X5 ]/ H8 R* h5 P, `
    当序列类型为 object 时,是对于每一个元素进行 [] 索引,因此对于字典而言,返回temp_1字符串,对于列表则返回第二个值,而第三个为不可迭代对象,返回缺失值,第四个是对字符串进行 [] 索引。# X) m0 Q8 G4 b! W6 }3 k: t
    string 类型的 str 对象先把整个元素转为字面意义的字符串,例如对于列表而言,第一个元素即 “{”,而对于最后一个字符串元素而言,恰好转化前后的表示方法一致,因此结果和 object 类型一致。0 [* J% w2 q( ^' V
    string 类型是 Nullable 类型,但 object 不是* i1 Q9 H7 M+ {/ o7 e1 L
      这意味着 string 类型的序列,如果调用的 str 方法返回值为整数 Series 和布尔 Series 时,其分别对应的 dtype 是 Int 和 boolean 的 Nullable 类型,而 object 类型则会分别返回 int/float 和 bool/object ,不过这取决于缺失值的存在与否。# x$ }  u* v, v
      同时,字符串的比较操作,也具有相似的特性, string 返回 Nullable 类型,但 object 不会。7 q4 q% i' g3 z/ E, G  l$ a
    s = pd.Series(['a'])
    * o, B$ Z7 Z3 f7 I
    ; K5 J5 ?* ?8 i: r$ E% J3 ]s.str.len()
    7 C: l$ j3 t9 x4 hOut[17]: ; n% T5 ~. P& T. `4 B
    0    1
    , Z6 Z  q: F, R9 o* gdtype: int64
    . h! u8 J9 v! Z2 @/ x; c) H) v( s; b- |& S3 v) {
    s.astype('string').str.len()
    + }  k! U9 M/ J/ z" bOut[18]: " g3 ?2 N/ c3 P$ P6 U3 J* k- ^
    0    1
    # J- s) J( j7 p- `* G, Kdtype: Int64
    0 W  L1 _" X; i9 I' `  B) D8 N9 x; m! n) a
    s == 'a'
    ; K5 y# `/ D! `: j* uOut[19]:
    % Q1 g* W" l, K+ |. L$ E0 _- b0    True$ P4 r/ n  B, [1 O5 ?
    dtype: bool
    & |6 R: Q$ ?7 `0 x5 E
    3 a5 T: g! B4 [  G8 i1 l  s% v0 Ls.astype('string') == 'a'9 H: T6 f5 O% ^5 Z0 E; k8 S
    Out[20]:
    . w7 `  T9 g+ d5 e* S0    True
    , q5 \+ f6 B! h6 Jdtype: boolean
    , w, K/ g. B& m0 Z$ s
    4 V( b% _4 o" ^9 ds = pd.Series(['a', np.nan]) # 带有缺失值5 H/ h6 X8 N5 E) ?
    8 F5 M/ ?3 L' @& O1 u( B
    s.str.len()1 ~7 S; S7 \$ H$ a4 L
    Out[22]:
    1 K, d" w4 q* y# a0    1.0, a- J3 `" k+ M1 [  V" ]$ D9 D  m: Y0 o5 l
    1    NaN
    - Z; @$ W7 n; Jdtype: float648 J  `  `8 c! b( C6 S
    $ o4 f5 J4 R* W* x( H2 b0 n
    s.astype('string').str.len()
    0 W; v, O1 k5 ]$ wOut[23]: / Q  [5 D, W5 q2 F. D
    0       1% |' |! c9 o! @
    1    <NA>
    5 o6 x8 R8 K  A9 X& B4 e( Ndtype: Int642 c0 G) D% N' x5 Y6 [5 }

    0 h! i% n4 T4 G' q& Z  D! {. n- ls == 'a'
    5 J  _& G3 t3 z: l  ^* NOut[24]: / `$ n6 j( ?5 q8 E( R. ?
    0     True4 F3 \+ {6 D, h* P0 B
    1    False
    ( [$ _0 \% o: S. t, f* zdtype: bool, C& \7 B9 t3 ]5 r

    5 c. G. y# F, X5 U# d  N+ ~s.astype('string') == 'a'/ t; g: k" {) f- h, R8 W/ [. {- j
    Out[25]:
    ) i8 H& X# m# f- Z8 n) m5 {$ ^0    True
    / F$ b1 G; N$ i4 v1    <NA>6 t- _5 w/ a% J
    dtype: boolean* @/ q% p- k4 N* X# W
    9 B* ^: g' X- ?+ B1 T' ~  H
    1
    6 j+ E' m9 b; J: D4 L/ Z2
    ' _$ M- {! w3 Y: t. Q3
    & a" m$ ?8 ?; e6 l3 L6 |4
    : _* O7 @1 Q9 L, K$ a8 T# J5
    9 N3 l8 Y4 {3 Z6) G" U& R' |5 B  R
    7
    : h& y9 `7 E0 |/ _3 c' v$ x. k' }! z8
    : g# D  l7 {$ W; {9 l9
    : R5 u, {5 L% t) @8 `, H4 w10
    " q4 R6 e* y  M; P2 P6 ?11
    : I+ a5 e2 ^2 N5 X# U$ k2 k12- s6 [/ A6 `' [- z8 O4 H1 c
    13" e" p' C+ w  r& g6 y
    14$ ~; b* }! c3 M3 i& q* l  H& u4 s  E, Z
    15
    ( }3 c/ I4 L& \4 d16$ C4 @# ~$ ~- a9 ]; a  B4 q
    17
    ' k- s+ ^( e$ x/ `186 Y8 N9 K2 {0 \4 Z
    19
    7 i. J% e' K6 ]' R$ E) A1 l20+ y/ ]& M" L5 U2 ?
    21
    & `/ h4 L- v- F8 X1 o, f+ Z- u22
    , ^! \+ P+ m" [( b' i/ C4 D233 t3 Z- O. t( B3 L' u
    24
    * u5 N# `- ?0 ?' M+ ^9 l# M) d! R257 L3 W1 h" k+ a7 ?
    268 u7 ?/ [5 X# z/ x& y; I, C4 s
    27
    3 d0 e9 V3 |2 T9 G, `2 o+ V/ H4 g$ w28/ @0 M, C: ^! c; {
    29  K' u; T7 \3 |. i9 O
    30
    0 }0 v# C* k6 V* w. t. Y31
    0 D7 n$ K* t$ E0 [5 c7 T7 |+ ?" q32
    / O8 J. }* e* h; C* H33
    . W. h+ |  t2 s# r/ H- |34: b: p) ?: W2 W- ~6 P  d0 ~; M
    352 Q6 v6 A7 d& M  V; S
    36
    / x/ g+ b; O5 J/ v% q$ ]/ |37
    ; ^8 `/ L3 J5 c, Q; n) c9 M38
    0 J# y6 W: {- c) C- S! B  {; h398 Q5 @0 P9 _3 M; ]
    40
    - X; b+ m7 z. u/ I! M41
    ' S2 P2 y. t# b0 e. p( x3 ^2 ]42
    $ r* a5 R6 t) {436 f& `& n! p2 A3 U+ m7 y" x
    44( K) O3 Z& a5 I
    45( X9 \& q# J, Z0 d; N1 H
    46
    ) L% K$ X) x) b! N* l; n4 W; x47
    # T: o2 B/ B% o0 y4 K  对于全体元素为数值类型的序列,即使其类型为 object 或者 category 也不允许直接使用 str 属性。如果需要把数字当成 string 类型处理,可以使用 astype 强制转换为 string 类型的 Series :  w- `! A* z2 Z" M" I; N
      M5 a! e2 R! s0 r& {& {% s; ?
    s = pd.Series([12, 345, 6789])  v1 P% r# r5 i  \
      b. S3 B% {- ]/ |
    s.astype('string').str[1]  @' k% q0 W) M* `% f, m
    Out[27]: $ W2 R4 I; ]" }1 \: f0 g
    0    2! r" t, D6 o- F' y& a, r. D" I
    1    4/ i$ s1 N% Z* X4 i/ }
    2    7" j% d  u  d7 J0 |7 E9 u: N
    dtype: string
    / ]3 k5 H& v& v! }9 ~1
    4 m" W" p$ S7 o8 w$ W23 G: f5 R, s6 b* l
    31 I/ ~6 h8 ^- B6 i& d( x
    4
    ) B+ P4 ~7 x/ P# \: L) B1 K3 E" [5
    0 H' |- C4 [; o2 _% g" X0 v4 |; x6, c; K& W! m0 O& \0 c
    7! O0 ?! r9 T( [) q) q
    8: D. k7 K9 x5 z6 T; V0 Z) |/ f" R9 C
    8.2 正则表达式基础8 U$ [1 ?* o( O8 W* `9 h
    这一节的两个表格来自于 learn-regex-zh 这个关于正则表达式项目,其使用 MIT 开源许可协议。这里只是介绍正则表达式的基本用法,需要系统学习的读者可参考《Python3 正则表达式》,或者《 正则表达式必知必会 》这本书$ V4 a( a: u/ q  R1 I* X" g2 o

    " g4 J: b' u- |8.2.1 . 一般字符的匹配3 S+ E9 b7 o- y( q
    正则表达式是一种按照某种正则模式,从左到右匹配字符串中内容的一种工具。对于一般的字符而言,它可以找到其所在的位置,这里为了演示便利,使用了 python 中 re 模块的 findall 函数来匹配所有出现过但不重叠的模式,第一个参数是正则表达式,第二个参数是待匹配的字符串。例如,在下面的字符串中找出 apple :/ o  A# t+ c- R7 h
      l3 i6 h) [: T( K
    import re
    ' R+ U- T% n0 g/ {
    & _$ t1 x, M. H* |1 are.findall(r'Apple', 'Apple! This Is an Apple!') # 字符串从左到右依次匹配" K; ]5 M4 _2 l$ C; t  i# i7 w2 Z) z
    Out[29]: ['Apple', 'Apple']
    3 |* O' n! {7 V+ _9 j3 I1
    # z  B. t9 t8 w2
    : S3 K" S4 X( I9 {, Q  E. U33 ~1 S  U+ Q2 z% z
    4* a; [# `9 T9 m. e: ^
    8.2.2 元字符基础% ^% R8 p7 E- n4 k. _+ t5 _) H( M$ q' P
    元字符        描述
    ) H4 e, S& H+ y1 g. {.        匹配除换行符以外的任意字符
    $ Q  H0 k. |4 h; ~; u* {, k3 v- X[ ]        字符类,匹配方括号中包含的任意字符
    * k1 j8 M7 I# \  g# N+ L; k/ ?[^ ]        否定字符类,匹配方括号中不包含的任意字符
    * v, q) [# g2 `! {: }: e5 T( l/ ~2 R*        匹配前面的子表达式零次或多次- M7 ~# M, k" A. J. C
    +        匹配前面的子表达式一次或多次。比如r’d+'就是匹配数字串,r’d’就是匹配单个数字
    7 ~& M9 A: c8 {4 D& Q0 {?        匹配前面的子表达式零次或一次,非贪婪方式
    ( ~/ E$ G2 h8 h5 O, q" a6 r9 s{n,m}        花括号,匹配 n 到 m 次由前面的正则表达式定义的片段,贪婪方式
    6 A4 p9 e( X) f# k- n' @: C$ `(xyz)        字符组,按照确切的顺序匹配字符xyz+ |( I. A% K3 P2 m
    |        分支结构,匹配符号之前的字符或后面的字符
    . w; e: Y! n6 I: R5 B- @3 C- ?\        转义符,它可以还原元字符原来的含义5 o' M) M7 E6 L* \* P! C
    ^        匹配行的开始' X  p$ T- {1 C9 E4 q
    $        匹配行的结束
    - b8 R; n2 C8 aimport re  p, N; i- J) [; N! i
    re.findall(r'.', 'abc')
    % r( w: I# _/ A9 c: u+ }+ [Out[30]: ['a', 'b', 'c']9 T, I! T  ]3 i9 H3 d& n

    ! n( ~  c" z' W; x% j  A, Dre.findall(r'[ac]', 'abc') # []中有的子串都匹配, S6 w: U+ x+ p
    Out[31]: ['a', 'c']
    & _0 u+ n0 j$ E) t
    $ b1 l7 ?, A" `6 fre.findall(r'[^ac]', 'abc')
    % o! d! w" h7 Z2 {$ v' _/ KOut[32]: ['b']
    4 `' l" P2 C" }4 A% m/ U+ g
    ) _' T, L4 Y  t9 d0 s& L* zre.findall(r'[ab]{2}', 'aaaabbbb') # {n}指匹配n次
    , _5 Q! }* x) x+ J6 u6 COut[33]: ['aa', 'aa', 'bb', 'bb']3 ]9 {. N3 B- \' z
    # I9 L( ^/ s0 n4 g" @8 W
    re.findall(r'aaa|bbc|ca', 'aacabbcbbc') # 匹配前面的或者后面的字符串
    6 z) W' j/ c4 Z$ ROut[34]: ['ca', 'bbc', 'bbc']3 ?+ h" H8 ~4 g' n
    5 X0 L' R7 ~$ k, J, p# x6 ~
    # 上面的元字符都有特殊含义,要匹配其本来的意思就得用\进行转义。
    4 A8 _% o; |# C" I"""1 X# m' Z5 E& f2 G3 w
    1. ?匹配的是前一个字符,即被转义的\,所以|前面的内容就是匹配a\或者a,但是结果里面没有a\,相当于只能匹配a。% T; g) s- j' K$ l. y
    2. |右边是a\*,转义之后匹配a*,对于竖线而言左边优先级高于右边
    ( l+ [3 ~( V: W  g: `; l+ m3. 然后看目标字符串aa?a*a,第一个a匹配左边,第二个a匹配左边,第三个a虽然后面有*,3 j' _1 o: B* Y! A) ?% w
    但是左边优先级高, 还是匹配左边,剩下一个a还是左边,所以结果是四个a6 Q! u; \3 ~2 J
    """
    / h' C' N8 h4 [7 g, ]: o3 R. |. f$ M! [3 t+ Y9 Z1 \
    re.findall(r'a\\?|a\*', 'aa?a*a')   # 第二次先匹配到a,就不会匹配a?。a*同理。
    9 u" |' W  S2 T* n4 r6 R: Z4 EOut[35]: ['a', 'a', 'a', 'a']
    ) w: [2 f; s# y: v# j1 Y, L3 \" o6 z3 N  x- t( O) @/ Y  t4 d2 q
    # 这里匹配不到是因为目标串'aa\a*a'中,\a是python的转义字符(\a\b\t\n等),所以匹配不到。: `; K1 t: r' @
    # 如果是'aa\s*a'之内非python的转义字符,或者'aa\\s*a',或者r'aa\\s*a'就可以匹配到\字符。
    ; T2 Z8 t: @" m7 b1 c/ Y- pre.findall(r'\\', 'aa\a*a')
    " m* t. O+ D1 a[]
    . W/ k: P/ ?1 \$ G
    , Z6 S, H7 n9 E: w- W! u! |! V) lre.findall(r'a?.', 'abaacadaae')/ q% h" F1 a: A5 ^) K2 A2 Z* l) p
    Out[36]: ['ab', 'aa', 'c', 'ad', 'aa', 'e']6 D3 O6 m( L+ ?0 ^, y& o- y

    8 c: S! d, {6 ]) N9 _- c7 z3 I! @3 rre.findall(r'(\w+)=(\d+)', 'set width=20 and height=10') # 多个匹配模式,返回元组列表
    & Y1 z; X$ W6 a9 o0 C[('width', '20'), ('height', '10')]$ C9 N' a2 I! i. ^! [& G# B# _1 q

    8 u; S. Q: t5 I$ F. g1$ U$ B" N( L" v! J6 j
    2
    % l: F0 c* E* k3
    2 V8 L0 \) M/ _" T% b5 y7 ~45 k8 X$ k: ]) a9 X$ ]8 I, p
    5
    + c* I5 r( d$ Y2 w- N6
    $ P5 r$ N8 V* |' ?- n, m% ^7
    5 U9 D8 @& K3 X$ x) ^5 ]% J! u+ }8
    # d  a) G- K% e! l9
    * f9 C' i7 k2 D10
    ) q+ q# _/ m  e11
    4 q3 }6 l& a1 \% C3 m12
    # {$ U: C, l7 U8 ?$ J# w. S13; Y% `/ L* W5 ]8 t! r& P( ]/ O
    14) G* Q! x2 R, {1 P$ E0 [
    15# b4 g) C% B0 I7 O8 P6 J
    16
    7 Y' x/ u: r  q; {17
    ! e8 A; w+ a: _' z18, s  A. {* S" R- @- n$ ^# O* D
    19
    - @+ e) q7 q: h4 `- J" I20' J; [  |) B" ^# E/ E
    21
    * ]$ J( a0 @+ H# l22
    & ]' \9 V8 o4 {& H6 D) z23
    2 m  i$ _. B( W* n/ l" _8 _24
    ( Z( p- g# y8 D25
    : l8 G' T- r6 M4 F. W26+ Y: L. q; V  h) e" L
    27
    0 ^; A. G- f0 X7 T: ]4 H28
    / x6 W$ Y3 w; f. |& H& ~8 O; f29
    2 c! `' F: ]( ^: C: l, }# ~30
    0 n, p2 x- Y$ |3 l  f: a31
    " f4 I  ?" K. M8 H: m5 g* j32
    # g: E- h2 b( `! z" h& |33
    6 j* K3 @- |/ E' k5 v) N8 o1 G348 e+ w* b% z# K3 D1 Z+ L
    35
    % |6 g  Q6 P! p; o6 Y36# Q, f" M, e5 l. j  [9 j' ]
    37
    ( z, t4 I  K) }0 r4 y7 V& H8.2.3 简写字符集
    ! J# o0 ], i7 D; w则表达式中还有一类简写字符集,其等价于一组字符的集合:
    ! ~! U5 V% ^# [8 i% y
    5 W7 x  `, y* v; D& p+ h! f/ V简写        描述# s6 H" Z4 g# X1 e* F0 P0 j5 e0 R2 {
    \w        匹配所有字母、数字、下划线: [a-zA-Z0-9_]
    8 M& Q  Z; f% p1 S2 x\W        匹配非字母和数字的字符: [^\w]' k2 b" B( J% z+ E) y; o* i
    \d        匹配数字: [0-9]
    3 m, q: r! R2 k. D) k, z$ I4 T9 ?\D        匹配非数字: [^\d]" m0 d$ h* ]& J0 J9 k! t
    \s        匹配空格符: [\t\n\f\r\p{Z}]! ]) F; |" ~" ?7 w! r
    \S        匹配非空格符: [^\s]
    2 ?6 m& K% K$ r9 Q8 J/ X. x. ]\B        匹配非单词边界。‘er\B’ 能匹配 “verb” 中的 ‘er’,但不能匹配 “never” 中的 ‘er’。6 i. Q) P) ^! a
    re.findall(r'.s', 'Apple! This Is an Apple!')
      M1 }9 h: `4 u& i& j( A2 cOut[37]: ['is', 'Is'], d8 X, \4 I8 M: e8 r" I3 e
    " w/ I( a/ o+ |9 W% y" I
    re.findall(r'\w{2}', '09 8? 7w c_ 9q p@') # 匹配任意数字字母下划线的组合,但必须是两次
    $ V6 ~3 _( x) mOut[38]: ['09', '7w', 'c_', '9q']
    1 v0 Q# x% w0 ~' [" b1 p5 {; J  ~: B
    8 ]# Y# T( ~$ g* x! k3 f' n' k) hre.findall(r'\w\W\B', '09 8? 7w c_ 9q p@') # 匹配的是两个字符串,前一个是任意数字字母下划线(\W),后一个不是(\W)6 y6 d  z+ Y6 x( j
    Out[39]: ['8?', 'p@']. ]1 t8 X! F6 F% ^* G
    0 i( C0 l( p0 s
    re.findall(r'.\s.', 'Constant dropping wears the stone.')
    ; a0 Q5 s( ]# ]% k/ E9 SOut[40]: ['t d', 'g w', 's t', 'e s']
    3 }( t' L0 z4 f1 y6 `' ]; I
    - \+ B9 Q( U2 m- n- Bre.findall(r'上海市(.{2,3}区)(.{2,3}路)(\d+号)',- H0 A& L( F. r+ T8 h
               '上海市黄浦区方浜中路249号 上海市宝山区密山路5号')
    - i8 `3 j" \" T. A! N% `- q, ?; j8 P- v2 `- T; K. c
    Out[41]: [('黄浦区', '方浜中路', '249号'), ('宝山区', '密山路', '5号')]' M8 c5 W2 i$ y% Z1 j" K
    % X& o: ~+ c; j6 x
    1' f$ h" T$ q- a3 I
    2
    7 v4 A9 J. w4 N; C6 m6 Y$ W4 x35 d' |. e# \- K" ]9 ]/ Z8 C/ |; a
    4
    ) R, E; [5 v  _58 [/ N6 j3 z1 @$ \- T
    61 w) n" V$ \/ G2 R; B
    7
    * t) |( q- \& j) q83 q5 B. w" x( g
    93 y) E, O4 ~1 n: u
    102 D: C" P1 U! Y# c/ h3 r2 ^) \
    11  A7 w% j$ Q/ B9 ]
    12" D( C! Z  F% [! i; n6 N% b* |
    13# o& k, D, y" t
    14
    9 ]& R. l' d$ j; s15% E2 e4 e$ o4 [, f: }
    167 o  r5 _9 z3 l
    8.3 文本处理的五类操作
    ( K6 L6 L" K( E( a6 ]3 o# l8.3.1 str.split 拆分- I' `/ [# V' }3 J! K2 q
      str.split 能够把字符串的列进行拆分,其中第一个参数为正则表达式,可选参数包括从左到右的最大拆分次数 n ,是否展开为多个列 expand 。
    ! `9 Y; u% f3 [; O0 b( M+ x# u5 A9 r/ C! y2 l8 v6 m% a
    s = pd.Series(['上海市黄浦区方浜中路249号',8 f/ u" B8 Z+ ?9 E0 {( \8 a+ R
                '上海市宝山区密山路5号'])9 h, ?) p  T6 R/ |

    . w7 E; Z$ q2 j/ p+ G2 o" j# X3 l$ k. O5 s3 b/ N
    s.str.split('[市区路]') # 每条结果为一行,相当于Series
    3 N  D  }  O6 ]: C4 _( ]& O% L3 EOut[43]:
    3 M% g0 W. t# M: W. v$ \0    [上海, 黄浦, 方浜中, 249号]
    $ Y9 `% `4 ~3 n1       [上海, 宝山, 密山, 5号]
    ( o2 n, w5 ?: a2 {: mdtype: object
    6 E% K. u; p' T% T  ?& ?7 k# M9 b& m  f3 |' z6 K
    s.str.split('[市区路]', n=2, expand=True) # 结果分成多个列展示,结果相当于DataFrame5 N$ E9 j% G1 W6 w! w# r$ I5 s
    Out[44]: ( Z& K! F( ^9 h. a& t- J8 Y% R
        0   1         2
    8 T& F9 {8 i* r) N0  上海  黄浦  方浜中路249号2 X) {# j3 P1 Z! j3 _
    1  上海  宝山     密山路5号+ k6 B( Y! X  H# N
    1
    ; D  ^; }' ?- G9 I. T4 Y23 r  {2 X  O1 y" P) M
    3" E! _* K' M: H- d0 {! V8 [
    4% y2 c4 Q6 y; i! e  _1 j/ O
    57 e  |4 ?8 Y/ P4 E, I( u- Q% {
    6
    2 W; [$ t' }, e4 O5 f7* U3 ?4 e5 S4 k6 l# _! h  x
    8
    # Z6 ~( x& e( B3 p. P9
    ; a" j2 m# P  b, }* A/ f9 n101 d( k8 `2 v. P- x& `4 V1 H: K
    11
    / Z! Z9 V( L+ t/ H' c8 v12, z2 a+ G0 k- h# ~
    13
    * k' l) U* o& T  ^7 u14" K( W( ?3 `7 R1 d
    15
    5 F* E  }* c# Q. I6 O6 |4 H8 V  类似的函数是 str.rsplit ,其区别在于使用 n 参数的时候是从右到左限制最大拆分次数。但是当前版本下 rsplit 因为 bug 而无法使用正则表达式进行分割:4 A2 f2 V6 W* i, l6 s3 U
    9 L& I3 }( L7 [3 `, d' W
    s.str.rsplit('[市区路]', n=2, expand=True)
    0 f) `" @! w3 u/ m  W2 \Out[45]:
    2 j& Z+ B, f7 e  p# K% s3 K                0
    % c, _0 ~( ?  {: H& f5 l- U( X, D, _0  上海市黄浦区方浜中路249号
    & C  C9 S3 ~6 F6 g1     上海市宝山区密山路5号
    8 a6 W" ]* V' Z1 P1, Y: \' s# W; D% J% X
    2* U& X7 P3 E+ Q3 G, ?
    3
    " R1 t$ G9 W2 f6 P4
    . G, M& ^4 w. A; `5
    # t' Q* w, d3 J8.3.2 str.join 或 str.cat 合并
    4 i# O- B) T  C/ C+ M4 V+ `str.join 表示用某个连接符把 Series 中的字符串列表连接起来,如果列表中出现了非字符串元素则返回缺失值。
    6 |/ b# m1 m- c, F( P5 ^/ Jstr.cat 用于合并两个序列,主要参数为:
    1 N4 q* Q+ @: r- asep:连接符、& A' u0 W+ T0 b1 S+ ]8 M2 D$ o
    join:连接形式默认为以索引为键的左连接
    & T/ @  m* [) h& Z- _7 F0 Lna_rep:缺失值替代符号
    . \" H/ J, ]5 g8 R' T1 b$ `s = pd.Series([['a','b'], [1, 'a'], [['a', 'b'], 'c']])
    9 \+ T) K% l! r5 f- x, Vs.str.join('-')3 s# Q) P( o" ?- S; G
    Out[47]:
    ; y- |; ~$ L% m9 ~0    a-b
    # n# q* d7 T- V# g, y1    NaN6 A* |3 Q6 T3 h, T: P- |; a
    2    NaN, P+ z$ v7 T% e
    dtype: object
    - W0 ?. P6 c& [- F( Z. K0 Y+ L% x: ~1
    7 J# Q* g- F$ I- I2! {* U3 R$ D- }% {, d) e) J. ~
    3
    ' u5 D  Q9 p0 |) _+ E9 E47 O* H) U& Y7 T- F6 n9 k: t
    5
    ( i* J0 I! M0 y1 \' I1 O60 e/ _8 d0 b8 I/ Z9 k$ Q( _
    7, O- l& D* N6 E3 R( n
    s1 = pd.Series(['a','b'])) G$ u* d$ v+ p4 l
    s2 = pd.Series(['cat','dog'])
    * K0 Z- D5 H4 F9 k) gs1.str.cat(s2,sep='-')
    # G/ S) X3 _7 t! {. mOut[50]:
    ! }2 a% v5 R# K+ S( E0    a-cat1 M  I0 K3 x1 _( n* }  d, w" h
    1    b-dog* d6 l, F/ |1 o; C+ _
    dtype: object; U7 S- {. @3 I
    % k1 e8 `, r6 N0 D, i6 H
    s2.index = [1, 2]
    . G# N) Q% y, c/ ds1.str.cat(s2, sep='-', na_rep='?', join='outer')/ F- |2 H; ^6 i9 R2 L
    Out[52]:
    8 A; r1 C/ }8 O) D4 J7 F# F0      a-?
    : B! _! z2 Y/ s+ v0 V" n0 V) i* P1    b-cat
    ; g' v) Z5 O$ K/ {2    ?-dog7 T: T5 \/ m* f6 a" e3 s
    dtype: object
    % {/ Y  a+ I' S  C1) l' A, i7 L4 x" J" s8 b6 d
    2( q2 p! z8 z* V; X6 {4 t4 J  p! h0 z
    3
    # s7 Q* A2 B% a6 s4
    " Y$ _! H; `' i. r- _6 m55 _$ r* ~9 ^$ A( I& O; x* [- G
    6
    ; W, m- |& B6 e+ e; m* m6 R7
      Y4 T- _5 Q5 R0 v* I8
    4 H2 M6 R% x" a2 F5 l, G9, m/ @4 y; G1 w8 f* L
    106 s8 ?" H: A% ?# F. v( M% ~
    11, k2 G( W# d! v4 Y6 Z
    12
    6 U4 O& \" X; ]4 [: K! x13
    0 H7 O7 J4 @! A8 q+ S14& Y7 g0 j) L% S4 H, q7 v& N8 ~  K; F
    15- d5 x+ R. n( W/ Q0 S
    8.3.3 匹配9 j+ A  v! R' \7 j  I7 V6 c
    str.contains返回了每个字符串是否包含正则模式的布尔序列:
    , r2 H( s) t1 Rs = pd.Series(['my cat', 'he is fat', 'railway station'])
    0 |- F3 C# c* q$ ys.str.contains('\s\wat')
    2 X7 ^% g$ J1 r: a: Z. Q* u1 R3 g1 T0 K, f) c9 _% f# w( B; e
    0     True
    & C( W9 v' p; R' [" J1 L1     True% H& O$ p7 V& ^& j) ]9 O9 R( E9 I. [
    2    False
    1 u3 Z1 [5 u6 ~, |, J) udtype: bool" v/ y! E. Q/ C, ~/ s* V
    10 L2 U* ^& l; P8 d
    2
    - q( B1 h' t/ f. N) s- ]8 P4 y6 x# S3
    8 L4 f. p4 R7 A+ ^' L4; W; X1 D* X$ s0 w+ |: j
    5
    * Q; Z- ?* O# S3 E. |63 P; B0 v% e6 o9 A) ?
    7
    4 [1 ~; z  X. Y) l4 Z+ \7 G3 Bstr.startswith和str.endswith返回了每个字符串以给定模式为开始和结束的布尔序列,它们都不支持正则表达式:- s& y4 ?- m& ?0 O# u; @
    s.str.startswith('my'). R5 Y; O# [, e4 y+ w

    $ S6 v, P/ X: q0     True" N+ `6 |& D% H) ^7 A
    1    False
    / R3 F! O% I. l2    False
    ) i6 [# J% g$ P6 h. ldtype: bool
    / G7 g% [# t$ n  t1
    + d$ P. X7 F+ Y. D. s2 {# F' {2
    ; N! U4 {3 {0 M% ]+ g) a! l4 |4 B3
    . H. I  C( x& @9 {44 [" r- {" ^7 p+ o
    5
    4 E8 I4 |1 m5 |& M# m( R6
    " t( G5 {" m3 O. E0 i  L1 Ls.str.endswith('t')
    : h! [& z! o6 X, W  w. @% M
    1 v- s( E* c$ ^2 y! [0 `4 M6 D& C8 x( Q0     True3 }. X  a. G4 ?& w# {$ w
    1     True9 z, A  F' D' Y4 z
    2    False: w- Y$ U& }6 j% y% e8 m
    dtype: bool
    3 _; J. U0 h6 ~- x1
    0 E2 S; \; m  Q# k  E2
    " y# j% k: D- @2 P3
    + q% d0 q: O! C1 P2 Y4
    5 k/ W5 u1 N+ L+ w" ]! t& A5
    0 V* A) [* @% g% u5 [6, T) Y0 }9 a7 d; a
    str.match可以用正则表达式来检测开始或结束字符串的模式,其返回了每个字符串起始处是否符合给定正则模式的布尔序列。当然,这些也能通过在str.contains的正则中使用^和$来实现。(貌似没有python里的search方法)" ]5 n7 y1 e  P+ h* X1 }" H/ M* M
    s.str.match('m|h')
    + k+ o2 s3 M. c0 E+ D0 Ts.str.contains('^[m|h]') # 二者等价& W5 _( I' m4 _& G

    6 |0 F7 D: }4 f! s0     True
    : X2 w5 S% M% B5 g) ]/ J1 A1     True
    " a* G4 t. q7 {4 b1 X1 V2    False
    / E7 `, y9 {, }: x5 g: gdtype: bool
    9 l3 N2 D1 ?  b9 A7 R8 J1  e! n$ `7 h$ M
    2$ p' N* X2 ~' ]. v7 P9 \  S9 [
    31 U& p+ ?3 ]% t+ {
    4
    . e- O1 {& N6 s0 u+ t, [" ^# Y; g5
    & @( @& j& e( L% O64 z" k, n: {; i, N1 W* E; T# o
    7
    : Q: F$ C4 K1 A: [2 Ps.str[::-1].str.match('ta[f|g]|n') # 反转后匹配- k" Z, {0 C- L. w/ ~
    s.str.contains('[f|g]at|n$')       # 二者等价! l4 r8 [2 V* N2 V( U! M; H4 J

    " |" f! M0 u* C& ?) m  W  U0    False
    / I4 ^4 l$ Q$ u/ r- @" R1     True
      p- G( h3 g" z- A$ c1 a2     True
    " z: J: y7 N7 ^+ L; B; c. Sdtype: bool' h! q6 a! W. c8 Z/ {1 _
    1
    ( c+ ?1 n& q) U, `22 ^4 }/ R# V- n
    3+ {) r8 c, V6 M" x- R
    40 o( F& u2 }9 x
    5$ s% n$ h" o- ?1 P7 m5 a# M
    67 O  `: j6 A7 Q  {8 W
    7
    9 F! Z" ]4 T/ V* }) v, \& pstr.find与str.rfind返回索引的匹配函数,其分别返回从左到右和从右到左第一次匹配的位置的索引,未找到则返回-1。需要注意的是这两个函数不支持正则匹配,只能用于字符子串的匹配:
    & D4 k7 }& P- T4 @s = pd.Series(['This is an apple. That is not an apple.'])
    # c+ L8 J+ O6 G3 h" ~! q7 v8 V3 h  ?% a
    s.str.find('apple')# Q5 I1 |1 a# s8 ?4 P
    Out[62]:
      n% q* n" _5 {; y0    11
    3 [$ f8 W! g6 Fdtype: int64
    0 T! Y) q2 N9 Y" x
    - E, f8 [, i+ bs.str.rfind('apple')
    . y8 q$ a2 e) F' H% a8 }( JOut[63]: 6 F! n' x) |8 D  |& u. V
    0    33
    $ i& Q! _* U- F; Ddtype: int64' V2 ?2 Y# V; v( z7 w0 c
    1
    ; f2 |$ c  [3 i  f2 _& y1 P2
    ) H' _: N% r1 `- q0 g' Z: W1 {( O3
    . R& q1 a" a; P8 O+ z: r) ]4# @" d5 a" R* [9 T+ J. z
    5
    . S' e/ Z4 T" X/ r( m# b+ |6; y) A) _5 c! P1 K. w' ^  b0 i, `" z
    7! `7 z  h2 E$ f
    8
    & d2 K- x/ X3 g0 J& l- N" \  L9
    , J5 k: j# z6 L10
    - J) V. X$ |9 ~11/ e* @/ a( w1 y; W
    替换" K2 Q/ L% `% j- V
    str.replace和replace并不是一个函数,在使用字符串替换时应当使用前者。
    : b$ U2 x. g+ p& o. V1 Ys = pd.Series(['a_1_b','c_?'])
    ) y" @1 u8 L- _# regex默认为True,表示是正则模式,否则第一个参数内容表示是单纯的字符串,也就是匹配字符串\d|\?& t' U5 C+ R- e8 A" K3 Z
    s.str.replace('\d|\?', 'new', regex=True) % i1 y9 @) n9 }' Z6 Q2 v2 _
    ) H" J5 W0 ]$ ~& O9 J; J6 J
    0    a_new_b
    8 V/ A2 f% J" w+ Q1      c_new
    3 |$ d) T' t/ _0 j& K3 W( Pdtype: object
    ; ^: q! D% E& n& l1 m/ P# ]1
    7 r( }3 z1 I$ i/ K% u0 h( N, S24 E* Z) X0 Z3 Q$ q
    3' e( `5 |  P4 v( }1 ^, @' o
    44 @' K0 E8 s/ N* d$ c
    50 e" h+ Q8 }# s
    6
    / e) v4 p: I+ x4 ^7
    ; e5 y5 }  a4 D+ Y8 u, A# F  当需要对不同部分进行有差别的替换时,可以利用子组的方法,并且此时可以通过传入自定义的替换函数来分别进行处理,注意group(k)代表匹配到的第k个子组(圆括号之间的内容):
    3 V9 M& o7 q# s: S  i- e5 D# R/ u3 i1 m- g3 a/ a/ E0 L( N/ I; Y; Y& S9 p
    s = pd.Series(['上海市黄浦区方浜中路249号',; U; i" d" x5 W+ e  h
                    '上海市宝山区密山路5号',+ z- |& ^7 I( V9 Z9 ^
                    '北京市昌平区北农路2号'])
    ; \% s! r8 P6 epat = '(\w+市)(\w+区)(\w+路)(\d+号)'2 |  ^2 G' b8 U- s0 s( \5 r& ^
    city = {'上海市': 'Shanghai', '北京市': 'Beijing'}
    7 p4 ~, W+ s1 o$ \7 _' T1 E6 @( ndistrict = {'昌平区': 'CP District',0 F- w* B  E) k9 M  S+ [2 T# I# f2 F
                '黄浦区': 'HP District',! d0 H/ @& c( M. {# m/ I1 I! z  n6 `
                '宝山区': 'BS District'}
    , R) k$ q6 L/ v1 R, q9 Qroad = {'方浜中路': 'Mid Fangbin Road',4 R6 h/ r5 o1 K, P# y( c
            '密山路': 'Mishan Road',
    # o. V- ~' ~. g) f5 @4 n        '北农路': 'Beinong Road'}5 j9 H# i3 q0 e9 B/ @4 e
    def my_func(m):! L. n: g! ^. I8 y3 K
        str_city = city[m.group(1)]
    : @9 w2 i% C( l6 V    str_district = district[m.group(2)]$ `, f) j3 Y. W2 S, K/ r
        str_road = road[m.group(3)]  V% ^' I+ I8 p3 }9 m. t
        str_no = 'No. ' + m.group(4)[:-1]! w" n8 w5 S& k' [- ?
        return ' '.join([str_city,: ]3 l# e4 E9 b
                         str_district,1 V: q/ L+ x3 O( i4 b2 C6 ?
                         str_road,) ~" @  D3 k5 A* j( D
                         str_no])8 o5 x- |! D1 H$ E1 Z; H4 X) D5 g
    s.str.replace(pat, my_func, regex=True)/ L" n2 _! o9 R1 u" z

    / d* x2 @1 B2 w* K6 Y5 y1/ R: s- f; o, Y8 |' Z* O* i! C
    2
    & V& x2 q" J- r$ }3
    9 E/ V3 X) E$ O0 e# V0 o4
    : P5 d0 w& [; B! f) E# \0 a/ N4 j5% @7 v' `: Y9 X
    6
    5 N+ x- P( h$ V+ t4 k78 U: u- Q! b2 `' h& U  g& d
    8
    . i4 J  E7 Z; R$ }- {1 O9
    . A7 W9 z* l. @3 r10
    : T* b' i/ j. A; m11
    + f, ]5 d" |1 R. L- u( B5 w- w/ S+ E12: P: V9 G) a" j, _" {3 y8 ^% N
    134 A. U3 J' o! ^) z' g/ \
    14
    ) B3 w% _0 D0 T8 f4 |15
    5 C$ x( p0 c: d167 s/ D" R' D. I1 U, J- I# t
    17
    , t  R) \- Z# a* N  O18& H9 M; t! L6 D; V: N* E( S$ D8 u
    19
    ! T9 [( z, J. e& Z- P3 b7 O20
    ' K) ^1 J8 A/ `( i21/ x3 K: X3 n, ?+ x+ \! G
    0    Shanghai HP District Mid Fangbin Road No. 249
    # n: Y& X- _) N1 o, [& C# f/ b4 L5 c1           Shanghai BS District Mishan Road No. 5
    6 \0 Y9 U3 d. ?" b) @, v2           Beijing CP District Beinong Road No. 22 }1 v7 D3 K) N( t7 g' @! z9 p
    dtype: object
    1 r! P* e2 @* O. @* c# z$ p: ?1 k1
    ; h( _# X. }) \4 ]' p( H, i2" _: o4 O! I7 r, L9 N: e0 R
    3
    $ `; I+ h9 f1 g" R1 }! G: Y4
    % r2 c; w; P1 O5 [) l6 q这里的数字标识并不直观,可以使用命名子组更加清晰地写出子组代表的含义:
    6 N0 U: J1 P) A. N6 N/ ~7 ~5 V0 |% s7 X# Y
    # 将各个子组进行命名1 a" e- U# `. d' r! }  H
    pat = '(?P<市名>\w+市)(?P<区名>\w+区)(?P<路名>\w+路)(?P<编号>\d+号)'
    ; p' s; Q$ R; x& P+ f( B, Ldef my_func(m):
    + l& X* J6 _$ b, i* Y    str_city = city[m.group('市名')]
    , ^9 x+ F! B, z4 ~! ?9 @, l    str_district = district[m.group('区名')]
    & D$ X& r( z; V    str_road = road[m.group('路名')]
    ! V0 ]* }3 D. ]) b( I    str_no = 'No. ' + m.group('编号')[:-1]/ ~6 {  o, }+ _! Z/ v) T4 e
        return ' '.join([str_city,( j1 u& |0 U" Q$ L1 E9 t8 s
                         str_district,& p5 e0 f) a* A  s# m' H# A
                         str_road,7 [* C8 N7 z* k$ ?; h' z
                         str_no])
    * S5 D" ]: A# O5 w; V7 J, Rs.str.replace(pat, my_func, regex=True)" D# Y* U: f6 Q) V1 P" {
    1# j7 n0 B1 p+ U/ k" {& M
    2$ k( i6 v- z: I( l/ j
    3
    # T& u- g% F' _* e) s2 T. u/ R$ s" _4
    7 ^$ t# w( r" J* |9 N6 ]3 k4 Y5
    & c8 a% S- P4 s& L! e: s2 B6. n! h/ n) @5 w% _& k$ U. ~
    72 c) H- Y8 W  I
    80 Q( J4 X! u. b( Y% E3 u* p1 ]
    9
    0 C! g2 K3 B# T2 u10
    0 x8 q2 U' @, i* w4 e7 }9 L. `: a11
    7 ]0 y7 `4 \6 J/ K) u9 o: V$ W12
    " w' B  I/ R3 \$ }0    Shanghai HP District Mid Fangbin Road No. 249- S: m% x2 f" i  G( j' q8 [3 B: z* J
    1           Shanghai BS District Mishan Road No. 5
    + J2 I  l* A( S: z3 M2           Beijing CP District Beinong Road No. 2, H2 e' e  j9 l# |
    dtype: object( l+ h% x, g8 N+ e" R- W
    1: o. ~& @2 |) X4 h
    2. v; e1 ^' c6 ?8 ?# s9 `
    3# N" m. U% k& w' q, b9 @- [
    4, f0 ]7 N: d5 [4 j2 X
      这里虽然看起来有些繁杂,但是实际数据处理中对应的替换,一般都会通过代码来获取数据从而构造字典映射,在具体写法上会简洁的多。
    ! o% N$ F+ V/ W( d* F4 `* c' d/ H3 B" j; x
    8.3.5 提取
    ( s6 ^, A- c$ e8 `. j' V) zstr.extract进行提取:提取既可以认为是一种返回具体元素值(而不是布尔值或元素对应的索引位置)的匹配操作,也可以认为是一种特殊的拆分操作。前面提到的str.split例子中会把分隔符去除,这并不是用户想要的效果,这时候就可以用str.extract进行提取:
    1 w% L' q" r! b! }) l9 ds.str.split('[市区路]')0 b$ V- t4 Y! Y/ z5 f. E
    Out[43]:
    4 |( ^. p& u) p- Z& D0    [上海, 黄浦, 方浜中, 249号]* m7 Y" n* n9 E' R, g
    1       [上海, 宝山, 密山, 5号]/ ^# B" ?. s4 R1 ?, T% n# ~
    dtype: object
    6 R+ r* P* s  i5 F; u% Z1 Q. x9 M! M# d/ |/ b2 \4 M
    pat = '(\w+市)(\w+区)(\w+路)(\d+号)'1 I# E$ X  j+ x" p
    s.str.extract(pat)/ n! F' A; L$ X4 R
    Out[78]:
    ' Z  n& z5 D5 E; d! J8 N    0    1     2     3
    - T* F/ I$ S9 f2 a8 J5 \0  上海市  黄浦区  方浜中路  249号0 T9 D1 t; `$ s' e7 O/ w  _* c; s$ `
    1  上海市  宝山区   密山路    5号
    ! s3 i7 g) m$ m6 \0 @2  北京市  昌平区   北农路    2号+ s# M6 O% q, k# a& Y& s' Z$ @
    1
    6 J. f3 b9 u( [/ s! X8 g  p4 R2" W- z* }# O8 }! a% L5 e' H8 R+ C1 a  l
    3
    1 v8 i3 o* G) s$ [% I4 C( y4
    1 Z9 @: k; r5 M- G1 G# L5; P! t6 ]* H8 f
    67 k/ d5 g, J& S! ?, f
    7; y- c8 Z" [# S3 H  F
    8
    9 T, i7 c& r$ t: [; P; K6 r96 E- ~+ U) l/ A4 C2 e6 x
    100 G4 ^7 G! B% d" N
    11
    % S0 N0 k7 F# V7 M12
    6 _* {/ [2 i/ Z+ |( @13. X1 f, T  |# `5 C7 ]
    通过子组的命名,可以直接对新生成DataFrame的列命名:
    3 \) {6 {* U1 i. Y% V$ f8 U+ Z* f2 s7 i8 c
    pat = '(?P<市名>\w+市)(?P<区名>\w+区)(?P<路名>\w+路)(?P<编号>\d+号)'( h- S' X7 q3 H" F
    s.str.extract(pat)- f0 o5 s1 l8 a2 k) R, o2 K
    Out[79]:
    ' }3 M" b) a, j+ b( t( H    市名   区名    路名    编号# R* t0 O, c+ }2 q3 L
    0  上海市  黄浦区  方浜中路  249号
    , d" h! A# H' }: M. ]  Y8 P; @6 A1  上海市  宝山区   密山路    5号
    6 q5 B0 J8 v6 x4 f5 {! T6 D2  北京市  昌平区   北农路    2号# B. p5 S0 U- h+ [' v; z* `  I. n) _
    1
    / D: I' E% p( L. K  A" ^1 Y- b. {24 y& A* J- r9 p) a9 T( t) {
    3; w" M  b- `) N; x/ Z$ N
    49 }4 B; T+ I% p
    5. P- ?5 L# \3 u! Z9 s
    6
    " Z/ K. P. Y* Y6 q78 k& N4 ?! s, d% j
    str.extractall:不同于str.extract只匹配一次,它会把所有符合条件的模式全部匹配出来,如果存在多个结果,则以多级索引的方式存储:
    ) \! r" i6 j1 W, A& Ms = pd.Series(['A135T15,A26S5','B674S2,B25T6'], index = ['my_A','my_B'])/ ~# X. f7 |1 }9 T) `3 R
    pat = '[A|B](\d+)[T|S](\d+)'
    ( t( ]' [) K1 t) us.str.extractall(pat)4 E  m, K, c# X; X
    Out[83]:3 w/ E4 B0 y8 n& l$ M
           0   18 M' R( ]+ R/ p' m
         match         
    8 W! V+ y* \/ F$ l6 d2 Nmy_A 0      135  15
    ! d; A6 l/ K7 W2 h, t0 ?3 U     1       26   57 D- L$ R& i/ I! B
    my_B 0      674   2: I: N  D" o/ E  \& W
         1       25   6
    ' m7 s. B( r4 ]. g- u1+ k. ]" O$ Y* ~9 K+ H
    24 x+ h( i9 U3 K3 M( {
    3
    6 z3 a& H5 n8 m9 _4( m" j: p" C( X
    5
    . Y; X, |+ Q% u: v6 O67 u/ ~0 [) r/ C! r7 w: e- U9 Z
    7% ?- ?! }$ l1 D' ~* K
    8
    . D+ F& s$ n& ]9
    8 f( [& c3 d' A4 j. N/ {6 a10
    / e( [3 @* `) a5 D" w# \3 \pat_with_name = '[A|B](?P<name1>\d+)[T|S](?P<name2>\d+)'& ^/ s8 _. o# g, k
    s.str.extractall(pat_with_name)0 T# d, T+ O* y/ c
    Out[84]: - N( Q( p8 a! w$ O, t
               name1 name2% @4 m( K9 y8 d
         match            
    1 [- Z7 ^. k5 _' B1 S! G  F( ]: r" imy_A 0       135    15
    0 M: Y. E0 m& l4 z     1        26     53 [4 \. G) P$ ]0 E; c6 z! H
    my_B 0       674     2; @( f" K) O  `: C/ S. H
         1        25     6
    ) x5 `" L5 t0 ~/ ~! l' w1+ ]6 x! u' \% z8 x$ [
    2
    & [" F* |- x$ s6 P. `( ?0 Z3
    4 D. g4 w% U: p# T4
    ; b: S6 v! p- D58 x. n0 y& v+ O3 ~; Q* Y
    6& S4 X. y0 p& A0 @0 p
    7
    1 @, _5 w6 O7 O7 r8
    $ U" M& Q( h$ j+ T9
    ( Y8 T& q$ E; P$ g) ostr.findall:功能类似于str.extractall,区别在于前者把结果存入列表中,而后者处理为多级索引,每个行只对应一组匹配,而不是把所有匹配组合构成列表。4 i' {/ o; W' L$ U- t/ S8 G6 W/ G
    s.str.findall(pat)
    . i( U5 J$ m2 W; E/ p1* b# `; P& r3 @4 e2 H
    my_A    [(135, 15), (26, 5)]' B3 \8 _) Z6 l
    my_B     [(674, 2), (25, 6)]
    ' k9 s# C2 ]+ `8 |- wdtype: object- \  o1 @9 _2 v* n$ z
    1) s7 w. `( `6 E' x5 P9 @
    24 T/ L. D( c$ q, X  w( g, w/ q
    3: w7 X; r# m6 g
    8.4、常用字符串函数
    # n  A$ i2 y2 c: o1 L& l" S  除了上述介绍的五类字符串操作有关的函数之外,str对象上还定义了一些实用的其他方法,在此进行介绍。
    / ^7 P6 }5 y$ s2 T* Q4 U
    / X. R* C5 S" H( U3 m  r1 R4 S8.4.1 字母型函数
    : s" j# q; d, O0 q# \% D. R  upper, lower, title, capitalize, swapcase这五个函数主要用于字母的大小写转化,从下面的例子中就容易领会其功能:: q! ~' B% A/ q' `* m4 l5 c
    8 l7 X! k* `; j! N* ~
    s = pd.Series(['lower', 'CAPITALS', 'this is a sentence', 'SwApCaSe'])
    # V& Y0 {. G9 P+ ]
    - U1 Q  D( `6 js.str.upper()2 R# I7 r6 z6 t$ E
    Out[87]: + q1 `1 L- H) N! T  u2 F- _. K, Z
    0                 LOWER
    6 e1 s$ h# T; @$ D6 `1              CAPITALS
    ; o+ C+ i) V4 P" t; R8 U: M; o2    THIS IS A SENTENCE
    / ?2 }$ I. U- i- @9 g1 @1 K- D3              SWAPCASE
    7 }! E8 Q" X! mdtype: object
    1 `% t8 t0 M; U( S+ Z1 ^+ Z- q9 D; @8 d# e% f$ b& N6 G6 H
    s.str.lower()
    2 H' M/ O0 L! R8 B" Q, dOut[88]:
      M2 J/ ^( w! c0 G8 p+ r, T0                 lower
    3 T7 K$ G, @" t1              capitals% G. e4 {. ?" h/ J5 l7 t
    2    this is a sentence
    3 E( D5 o7 C5 Y6 o0 _3              swapcase
    / E& {& A. |  m9 q; Fdtype: object
    ( }) J$ l7 R  Z0 f, U3 y6 i2 M
    % I) X$ q  z% \* F8 u# ss.str.title()  # 首字母大写
    5 B3 ?$ z+ l2 UOut[89]:
    " g% U5 S- [% m0                 Lower
    & G0 n0 C$ n! G( S& k  o; B0 \: Y) k1              Capitals% P& A& G9 U: L3 \) t# @0 p3 O
    2    This Is A Sentence
    6 n% {& d& g: P3              Swapcase
    7 K* _9 j4 [: w) B/ K% Edtype: object
    , U# ]- T, S+ B( B
    - j4 J8 S; A+ N0 ~0 D! b6 Bs.str.capitalize()  # 句首大写; C7 @2 {2 U/ D& [' ^* t" v
    Out[90]:
    . J) j) V2 V! K" o  n& ]* O5 Y0                 Lower
    3 A. b! B) d2 L) ]1              Capitals# d0 S5 |+ B- |1 U
    2    This is a sentence: l; Q$ |* L0 z3 c* V, A& F. t- Q4 E
    3              Swapcase
    % O! c! ]2 O2 k8 adtype: object  W5 C! z% \9 F/ n. U" l- ^3 m

    : m8 [6 S' P% `4 f- v% Ms.str.swapcase() # 将大写转换为小写,将小写转换为大写。: W7 k3 z* S! C! L
    Out[91]: % r9 a. h+ B! |* e
    0                 LOWER
    + E4 d0 B4 d- x' i) y4 Z, O: `- h1              capitals
    % S( I0 d+ r5 q8 V& D1 [9 t7 r2    THIS IS A SENTENCE
    # w( q* y. ?  g0 t* h$ u% Q- {0 z3              sWaPcAsE
      m$ O- I* Q4 _4 [* k9 @2 X0 |% Mdtype: object
    : O. Z! s2 P& }( B* R( f! }+ x2 B' b& f) \
    s.str.casefold()  # 去除字符串中所有大小写区别: X2 s6 G6 A0 B, \) F( f2 ^! I
    3 @* }2 d  J: q. v" `/ \; q3 ]
    0                 lower
    5 Q. {7 D  u- z' {: t1              capitals
    ; n7 W0 A# `8 n$ a7 q" ?" B2    this is a sentence
    1 x7 s1 M" q, y8 U  k3              swapcase: s4 @+ m7 P8 l4 h4 Q5 v

      J2 w, O& n  Z# x* B: Z1
    . d' a6 i3 a- S2+ {8 ~% y* S6 s, K0 [. o+ l& m, @
    3
    3 u! G9 w# t' g# d5 s& ^; [. p40 W; b& K  |4 r/ j0 d
    5
    ! z- `9 Q* u- O( f6 w4 Y. r3 d+ @9 ]6
    8 S' O! G3 m4 f+ F7
    . t) ~. z2 n$ n; ~. y3 [8
    ( a/ V2 f: K0 L9 Y9) V! ^5 [7 i) \3 ?, t
    10; g  Y5 t! K3 @. J4 L" d! f6 I
    113 S0 E* _! j* ~& b7 T7 z' a
    12
    ) U& _7 ^7 t' H% t5 y1 P; ^13
      E- J0 ~& c$ E4 F! P* Y8 ~14
    4 D0 R" m  Q0 U$ |5 b  {1 g15' C/ c: F2 L3 b9 t. g( j
    16( ^, H, c. l- {" D- |* e# c
    17
    ) R, _4 C2 e$ C8 {' W18/ f; @+ o# ?0 M* E$ S( G& I# N
    19! R; Z! C( y2 {* ~) \  ]
    20
    - _. C  P& z! B) E4 ~0 R/ j8 K- `21, l( g  D7 Z% g# W" y+ Z
    22
    ! w& o9 @) R+ b; k, U23
    - O) j; S: n8 {! z24( a5 j% L& E3 \4 O" m; Y  d
    25
    ) s1 B6 d6 E/ e1 B264 C& M* H" k! A6 {
    27
    & S" z: _) y0 ]9 `# k28
    ! ?$ o! i3 w1 w29; Y0 `5 p9 H' p
    304 f' S, f. A  z! \' c5 o
    312 [2 t" P7 c5 e( |( \' q6 o( b
    32$ C2 q6 [. V& ]7 k3 i; \
    33
    / O9 H, \0 N# f  v- _- C34# B9 N& u! ^9 k! W4 ?
    35
    9 Q# O6 B, _3 @6 i% R) _+ w" c; d; m36
    ! d. \' |3 ]# {377 w& i7 d# p4 B7 m# ]! L
    38' D: z; g+ r! w1 D" }
    39
    $ b) y$ h2 f0 M8 v3 o6 l  w. Y40$ C5 P. V( d, T8 G2 `
    41
    & H) q, e) V2 ?# z! ~42
    ; _- ^9 f" B: Z! L; F" f2 `4 q* Z1 ~43
    + n8 X: M  Z! Q# e/ p449 E0 t9 r7 @9 L' P+ \
    45
    6 g  e! u" D; ]$ ]# v468 r6 v" q- `. l/ g1 U
    47
    - g2 W& B+ w7 x! x48
    " O, q5 Q0 b) {8.4.2 数值型函数
    , ]9 ^) z* d# N, p' ~  这里着重需要介绍的是pd.to_numeric方法,它虽然不是str对象上的方法,但是能够对字符格式的数值进行快速转换和筛选。其主要参数包括:
    2 H$ K! Y+ \6 `; v, S9 |# f8 {4 c) I4 h* w! e( ?! `3 a1 e0 {0 s  g
    errors:非数值的处理模式。对于不能转换为数值的有三种errors选项:
    6 D. o; I. ?. X% ^# n; Praise:直接报错,默认选项
    / U, I  C* k- _. Kcoerce:设为缺失值2 w- p! ?) c1 H; P
    ignore:保持原来的字符串。
    3 `% g# t; x9 m4 N& o# Wdowncast:转换类型,转成 ‘integer’, ‘signed’, ‘unsigned’, 或 ‘float’的最小dtype。比如可以转成float32就不会转成float64。
    0 w) z6 N# }0 ~" I, Rs = pd.Series(['1', '2.2', '2e', '??', '-2.1', '0'])
    . r/ `3 b9 [$ P
    : T4 A* s1 e8 P+ i) ipd.to_numeric(s, errors='ignore')0 A/ ^; |5 G- I( e" `! A- i7 p. p
    Out[93]: " M0 Z. i9 N4 }2 w: \
    0       1
    3 ~; K5 F' \% U& n1     2.2
    ! j& ?$ M! R+ u& r0 F2      2e8 U# G: f0 C! J; s
    3      ??$ Y9 ]% ^' @: b9 y
    4    -2.1
    % e3 G" a8 {  u) J- f' w- f4 q, E( S5       0
    2 d7 r, P" i0 q; ?9 P6 `8 T# `dtype: object# \/ ~5 C- i* ^+ k2 G8 b- u

    ' Z7 i- P; [$ Q3 Ypd.to_numeric(s, errors='coerce')
    1 i2 G) t* P1 }7 l2 E2 JOut[94]: 2 R8 a6 Y5 q4 Q! x! o" E7 M. f& Q
    0    1.0
    ; q4 |" U1 |& ]- c- z1    2.2; u9 f8 j, J7 t8 ^5 U) C1 i. \0 C  N
    2    NaN5 }; l: a( \) b. K2 k0 z. E5 W$ ~
    3    NaN' E: I6 t4 o% D
    4   -2.14 n) p* ~' T* l  Q9 e9 J2 @# O
    5    0.0
    4 r' u6 u! j6 Q; {dtype: float64
    + V& i  K7 K' s; C& \% p3 o
    " r( I- f$ h" `4 i. [- U/ E1* O0 Z( V; p4 U) v6 N* n
    2
    5 G( U( a- D% S% a3
    ' }" w; C" ^# r8 P4
    ( F/ X0 b9 u5 v8 S4 ^( J. w5
    * W6 w+ T( z$ |6  M# d- s. {1 p) h6 ]) o; \
    7
    $ j+ X) H/ `8 f! v8
    7 h& s( ?, N, n95 r" c: e. e# {+ j, C5 j* C
    10
    7 N7 G) l/ a4 q11
    ) R$ R# N) Q  Z+ s12
    6 [7 Q# R- X0 i  r# Y13
    ( p# H! V; G: |$ W4 Y; ~145 P! l! a. G5 a" e  f3 u9 S
    15
    ; |2 ~8 Z+ ~/ Q+ U1 S* j- k16
    3 E  |+ F- Y0 v/ {9 d' u17
    6 U0 h! ?; U- Y3 E6 Z$ S18
    7 z& n# g0 A' F$ d$ N% e19" |+ m/ I$ j5 F. e2 @
    20! Q$ I9 [. _. q8 P: X
    212 |# z# c2 @; U/ T9 Z  r
      在数据清洗时,可以利用coerce的设定,快速查看非数值型的行:+ B5 D- O' P9 M% X) z( V7 \

    1 @+ L# @: Y0 [s[pd.to_numeric(s, errors='coerce').isna()]7 ?2 \/ U3 d: n. J3 ^9 L
    Out[95]:
    ' C! |6 N+ O' @' Z' T2    2e6 \9 X# c4 }% A. v. C, M; ?9 @
    3    ??
    , y& ^3 _. Z' odtype: object; h1 [/ Q& k# B) {* {3 S5 `( Q
    1/ V& x; l8 }: I( g) o
    2
    % B' b) U# m1 c8 L3' M" @0 x( M, H7 A; M: u& r0 R
    43 N  u# h- A1 k6 f
    5
    ) W, E( P. V) h/ C) j. G* Y3 a8.4.3 统计型函数. I" P* a( z- O, [9 G
      count和len的作用分别是返回出现正则模式的次数和字符串的长度:
    ! O: }" {) q5 F- R8 ~' V& E7 \3 I7 T/ ]8 w2 ^; W0 Q
    s = pd.Series(['cat rat fat at', 'get feed sheet heat'])* z  T+ u! b5 D: h* d

    . A0 \6 O" s$ G# I" C/ Hs.str.count('[r|f]at|ee') # |左右两种子串都匹配了两次
    6 c! |0 b9 A. t% ~2 eOut[97]:
    9 O/ Z" ?6 [# \' m7 M! c* |& s4 m4 ~0    2
    ( k# O( Z* {( Y9 f' d1    2. o* i7 m# D* P/ E* c# R+ `2 O
    dtype: int64' h" p4 A; O" I2 o/ ?
    2 F( H7 Z: q' |; g" s  B, c
    s.str.len()
    2 h4 U* F: A" e5 M. w" EOut[98]: % Q1 e' d" l' x7 N
    0    14: ]2 i$ F) Q! \8 r( i
    1    19
      w* \/ G4 V' l6 t; l# d, ldtype: int641 a9 |- t7 e# j  s
    1: K8 E' `! Y3 v, ^( _: r
    2) D$ n+ L) T9 N2 z/ J( R6 |
    3. ^+ I! s" I$ z7 e" b! O
    41 o* i/ @4 N" T7 m" U- g" B
    56 t" `+ e3 i8 j( a
    6
    5 T! y( p9 d5 u- o$ x7 E7
    - N' v5 x* ?# y# k8- e7 ~# m- g, H' _
    9
    1 y/ S+ {2 Y" T1 q10- V7 J9 ?% w2 K8 I
    119 W3 S- I! T( \/ Z0 H* C: e
    12
    * I) p0 {! B& ]# s13
    * v# U! Q! a$ R5 u; g- i$ Q* C8.4.4 格式型函数
    8 `5 ?' W. I. J  格式型函数主要分为两类,第一种是除空型,第二种是填充型。其中,第一类函数一共有三种,它们分别是strip, rstrip, lstrip,分别代表去除两侧空格、右侧空格和左侧空格。这些函数在数据清洗时是有用的,特别是列名含有非法空格的时候。5 `/ d& s+ C, y2 y
    1 v$ S0 @' J2 H% P& j
    my_index = pd.Index([' col1', 'col2 ', ' col3 '])0 r' b& J7 c- B: D  z
    / [8 d8 D9 |4 X7 o) }2 ?. u
    my_index.str.strip().str.len()' R: o: v/ v$ ?+ W; y! t
    Out[100]: Int64Index([4, 4, 4], dtype='int64')% z9 s7 M1 J  n+ o7 F7 z

    ' F+ U+ ?1 E! z! E6 [% Omy_index.str.rstrip().str.len()4 P+ g) {3 F& E$ T3 R& A
    Out[101]: Int64Index([5, 4, 5], dtype='int64'); }& l8 z1 `' N3 u. {) T
    1 U' D" Z* T6 s$ U$ N  D
    my_index.str.lstrip().str.len()) Y1 e, V9 T5 S* C2 [
    Out[102]: Int64Index([4, 5, 5], dtype='int64')) n. X/ {; O0 Q: n' f; W
    1
    ) D( s1 g2 V0 r* C  n8 G2% W% {7 @; w3 ]4 y3 i4 d" ^+ r
    3
    ( u- A" h- Z* C8 Q6 X4
    - ]( g" U( i. m. q0 S) Q" I: L5/ O2 t& A! X6 }
    6
    1 f- f- q; o5 K7
    " t8 B. m: F/ k6 v' ?8
    2 X/ T/ Q6 v  b6 m- K' n+ t+ ~9
    8 r% f" i6 [+ Y! I. ~4 @' c10! K5 p# Q/ ^7 @( k  Q! M* H8 Q
      对于填充型函数而言,pad是最灵活的,它可以选定字符串长度、填充的方向和填充内容:5 d  K. k5 p: l+ ?# e0 C

    % q6 N! J( g3 z) W4 s. ]s = pd.Series(['a','b','c'])
    0 x9 v6 ~' z# b, {, {5 O5 d+ b
    * x7 [6 w: f/ H1 `s.str.pad(5,'left','*')
    6 p- N* n9 |' Q* C% K" HOut[104]: : I) X+ E- Y3 E. T6 P% k  @
    0    ****a3 t* \2 H1 d& t3 _/ S4 i! ^
    1    ****b
    & ~0 ?! p; f8 T: r8 j2    ****c4 a8 u9 e& j, `1 e6 Y
    dtype: object; I: c; Z# R5 L$ C. N; D

    & s& |) Q+ g# i, c9 A( A/ [) Ks.str.pad(5,'right','*')/ m$ _2 a7 a8 x) H6 S
    Out[105]:
    % A# V; l/ z1 L0 z4 J& N( j0    a****
    ! w9 W/ q' k& o% M/ k1    b****
    % ^3 B0 k0 k( ?5 ?: N2    c****
    5 ^* H4 a- y4 }2 a! udtype: object2 e) r4 D; Z& D
    ' Z9 d7 m+ C) ^& ~: V
    s.str.pad(5,'both','*')% y1 |! k3 r) y/ @; q: D
    Out[106]:
      J) O6 G2 n4 z- u4 o' h# g0    **a**
    9 \& g8 j- q# e" S7 v  N$ W1    **b**- p" G2 b1 ~- e
    2    **c**
    , O+ |; O8 j) a; K0 ]+ O5 u% Ldtype: object
    ; l* g$ Z" k! k& C- `
    5 m% ]4 v0 d9 H% @" z/ J3 N1- G/ H% f7 a, {1 H
    2
    ( E' }! _- J5 J+ s# ]3
    9 r+ S4 ~1 a. V4 a5 @/ S+ C2 I42 }$ I7 A2 c. Z- E3 P& {
    55 n9 `/ N# q' W8 O7 l0 A
    6. K8 E! X# t" n  _1 U" d1 G9 M
    7
    + Z2 `' |/ \6 Y8 e1 L/ l7 G& P8
    1 t8 w8 K' B6 _) D+ U. w( y3 ?* U- y9/ q. @4 a7 x6 x! e% C1 D6 O. h
    108 J3 i1 X' R5 z9 b8 }
    11# y; T+ s) ^2 D
    12
    8 H! P. p/ u5 t4 |- o7 f1 e134 \+ L. V* n) o! t
    14$ n# {- r! w) W' ^- F5 F2 s. S
    157 a+ T: n! A4 m
    164 R. J) c) _+ x# \# {
    17  l" f8 u$ k" T5 E  W
    182 Q' w* v) S+ l" I
    19/ \4 ?" g$ e; Q& w2 T: e6 v
    20' C5 w3 E& _1 ^
    21" Y3 D$ B7 Y. v$ @" M% o5 P0 `
    22( B# R" n* v: Z, {
      上述的三种情况可以分别用rjust, ljust, center来等效完成,需要注意ljust是指右侧填充而不是左侧填充:
    + d$ |, u9 t% t0 g5 n4 l4 l* A* Z" n7 b& I6 l  b. H) k
    s.str.rjust(5, '*')
      n4 S+ I! w6 ~9 w2 AOut[107]:
    + H7 o6 B" \0 m8 t' u0    ****a
    ; p0 g1 D% m6 R/ {1    ****b& a; t1 o( m$ W4 B0 x
    2    ****c
    4 t* ~0 V( G5 M" N( W$ j) V7 {dtype: object* a" [: Q* ]/ m! l# U7 T
    5 g, L- o3 b9 B7 |( Y7 Y
    s.str.ljust(5, '*')2 N! X# h2 `' T  e0 d' E
    Out[108]:
    ) R- s2 S. W" V3 ]- D0    a****
    : o, E7 `! l0 `) D1    b****
    4 |3 s, \; s$ {# u3 L$ P2    c****5 |' Z5 ], L5 ]
    dtype: object1 Q6 B/ L7 C8 y. a+ {- q# _, a
    6 T6 X, N& s# P' B
    s.str.center(5, '*')
    , F4 @$ H% ^1 e& e& X  O, hOut[109]:
    " d9 x3 z) @/ I9 J1 K, P+ W; L- U0    **a**
      O+ `6 H" B; b7 b/ J1    **b**
    ; R$ m( `" q+ A/ N. z2    **c**% i+ ~7 n4 D0 C/ C! f8 Y7 |1 F0 k
    dtype: object
    + {9 g+ a7 Q% B: W" h+ n8 e
    4 x; w& e6 ]7 x3 g2 w1/ e% J, }  g( `  t  T
    2
    ) J+ J* z, P1 h$ j) {3
    0 E) T& v# v% o6 z' G4* j& E2 a( V) T( H
    5! v! W, Q6 x3 M) h/ S5 p& @3 M
    61 ?! C9 p" M( I
    7+ X7 P* {7 V7 k/ b( E4 s
    86 k# A6 [! w' B" p+ ?
    93 l( n" z& J6 g4 L$ d$ L" Z, b
    10
    " [0 V1 V) O; S5 k! x115 e. n3 F: d/ I, N
    12
    ( ?  Q) Q: {  T; O- b( {% Q  t13
    ' S; S9 _1 g6 `0 |1 I' ~/ F' o141 u* w" W, p1 A; J7 Q+ z; D
    15
    0 M' `, i5 W9 W: r16& i$ g* V8 {! @# a$ k3 O! {+ Z* U
    17! {7 h) J8 i) x4 e8 z+ T" m& S
    18
    9 `, f7 u. L1 _. _19, F/ J: ~/ e$ u  _5 U: I6 r
    209 z! {5 t3 K6 G4 o8 t& V" ~
      在读取excel文件时,经常会出现数字前补0的需求,例如证券代码读入的时候会把"000007"作为数值7来处理,pandas中除了可以使用上面的左侧填充函数进行操作之外,还可用zfill来实现。3 a) |- g0 C  t, t& G$ b

    - Z7 i% [' \0 u$ W8 qs = pd.Series([7, 155, 303000]).astype('string')
    8 P% c8 }' a' w) ?, T4 z% e) w; W2 Q: b- A5 ^7 h, B4 u" O
    s.str.pad(6,'left','0'); O5 B+ C% m% P" q# y8 i
    Out[111]:
    " m1 W$ `' X  V0    000007
    ; C/ N/ l4 ^! a, l7 m6 B1    0001559 H; m2 q- R& J. P
    2    303000/ W0 I3 O5 X) j2 a4 j  G2 s# E+ n
    dtype: string% P' ?4 J- m9 d% L$ }1 r# d  \

    3 w% L" \+ g% u, `s.str.rjust(6,'0')
    . b6 R) I+ r% dOut[112]: 0 u) _$ q" R$ a  e2 ]7 l
    0    0000076 V2 l" k8 R  o# X) d* i
    1    0001557 ~. V3 E8 F7 E) U1 h# h; r
    2    3030000 h4 p  d2 S9 \- C
    dtype: string
    & b4 k6 @$ D/ {. Q( d+ I0 Z' D, K
    0 ?. Z- Y9 A0 A" o, T- j, b& O& Fs.str.zfill(6)' n% Y. |& x5 G. q0 @& J
    Out[113]: . N% ~) L4 h" ^  q
    0    000007( Y9 a- X3 u& p% j
    1    000155" \& ~# R, g/ Y) o2 V* y; r5 g1 L
    2    303000  z) w6 C2 W# }, ]
    dtype: string8 {$ T% A9 u% V  H+ \1 G
    6 _$ V" v; T" Y7 b0 Y5 C
    1
      R4 c3 `* `( Y5 F# R' X* `2
    ! P) R5 \0 |7 ?3
    $ Y- b3 q3 Z' C' u* F/ s7 B9 l4
    ! P! @0 r( A8 D! D9 g% a9 [& Y% P5
    2 V0 X, t  _" [7 C6$ H6 y7 `6 Z  p$ @& n9 W
    7$ ^  [6 h, ?; E& C7 R( c8 c0 V
    8, A* o% Z2 W5 ?  H) }# B7 U
    9. L0 e  }5 y( P$ `  M
    10
    ' K! V& `8 u/ n7 L. u8 U11( ?) ]6 _, I: V* A* A
    123 i0 T' v5 `# o! a
    13
    ! d, e/ _" n" M* z8 G14
    . F" v$ e: t' H15
    $ s5 |+ g# ]1 ]( @165 J" u( o) I) L7 I
    17
    ; W/ i/ }- Z) O" [8 l18
    / j' R  M' |7 O6 j% i4 o19
      P" I3 ^* }7 u9 G2 e20. r% L3 H' Z, j$ k
    21, a: m  E: {& q# O
    22
      X& E. {' W% }$ M  a6 ~8.5 练习+ w2 @/ A" n7 f, b
    Ex1:房屋信息数据集
    " D8 C/ V1 j+ m现有一份房屋信息数据集如下:: q. K9 a# j" H, q5 ^3 F
    : k# N! J/ R, U$ @& J7 P1 ?2 d+ w) ]
    df = pd.read_excel('../data/house_info.xls', usecols=['floor','year','area','price'])' Y; j+ L% M+ ]- N8 E' b: x
    df.head(3)
    ( t$ [( B- K3 l7 N/ m! U- r' SOut[115]:
    1 f1 M* ^/ ^' k8 E4 v      floor    year    area price3 N( q9 [" w3 @2 B# E' b) }* ?" P
    0   高层(共6层)  1986年建  58.23㎡  155万
    , z+ k$ Z8 O& m" R4 r6 c1  中层(共20层)  2020年建     88㎡  155万- |' S8 d+ X# k' [! _5 @0 |( j
    2  低层(共28层)  2010年建  89.33㎡  365万6 N5 j6 F7 B9 p& u, }/ U
    1+ }4 t; T' b: x/ q2 P; |
    2
    # r& h  g% M! E3  O  H0 f* I/ A5 y7 ^4 v
    45 R8 b  g3 u1 U% s* r
    51 ~& u) N% `' r3 I# P" E
    65 T) G: M7 t$ Q0 `# Y5 b# {3 z
    7/ P4 r- q" b  ^4 S9 c
    将year列改为整数年份存储。
    5 L- A/ k9 J% q; p7 ^/ n将floor列替换为Level, Highest两列,其中的元素分别为string类型的层类别(高层、中层、低层)与整数类型的最高层数。- \6 h1 ^' v' S% w* K5 y7 \
    计算房屋每平米的均价avg_price,以***元/平米的格式存储到表中,其中***为整数9 Z4 Z0 K3 v4 f; w. ^+ W( S& _
    将year列改为整数年份存储。; ?; A" v* L4 V- }
    """
    , ?* z& d8 y. H整个序列需要先转成Nullable类型的String类型,取出年份,再将年份转为Int64类型。
    : ~6 [2 y: q! i* X注意,转换的类型是Int64不是int,否则报错。即使astype加参数errors='ignore'跳过缺失值,
    4 j& e# f$ W  C- A& Y$ Z转成int后,序列还有缺失值所以,还是变成了object。
    ; ~0 M  q3 A# S7 L2 S( Z而整个序列转为Int,就还是Int类型,缺失值变成了 pd.NA 。( Y2 h) t$ B( {3 Y8 x% ^
    """
    , V0 t" m/ l/ _- a. x7 _( Zdf = df.convert_dtypes()  Y( n6 e) E# L
    df['year']=df['year'].str.replace('\D','',regex=True).astype('Int64')# Y' h9 R6 I1 `4 Q
    df.loc[df.year.notna()]['year'].head()2 q8 b& l1 z! W7 O

    , e) c8 y6 c5 S. Z. u0        1986
    : j" x  X0 r5 I6 V1        2020
    3 N, W9 u- k9 G6 W' O' `. f+ F* a2 L2        2010
    % [1 q  H7 h6 o) t* Z7 g3        2014
    5 I' W3 C1 B1 h( d: ]4        2015! U; Z* S9 s1 P6 Y% l& ~2 o% d
    Name: year, Length: 12850, dtype: Int64- X: B# `/ q' }- k* ~$ D
    % w* w& {/ L( q2 L
    1" y6 I' k) ~* c0 b2 N* d& n5 G
    2
    % D( k. G1 e3 I5 [5 q6 k9 A* |3
    6 A: B2 V( u* U% A  j0 I4$ a6 X0 T4 V& \7 `; a8 o
    5
    9 {( O# F4 G0 m4 L0 U- V) ?1 _69 D. n) E4 t: V) w
    7, y( |' k0 G9 r% P+ S; z  O3 i
    8: f4 w6 \2 Y! w9 [0 }, s& e
    9
    " v" Z8 ~0 i! ?0 c0 x10& J3 B, `( i5 h4 N# \, s
    11; }# \) M) N0 z1 [& q, G3 V
    12
    - X5 a* x0 u7 j13
    , H& W: s; c. \. D3 o14
    - S/ j' W1 s: I- t. b% l8 r153 N$ V% q$ j) u0 n! v6 ?
    16
      e( A, Z& ~& w2 i参考答案:6 F. [8 `; s" K7 y% r+ j
    # s% B/ M5 p- {& G  U
    不知道为啥pd.to_numeric(df.year.str[:-2],downcast="integer")类型为float32,不应该是整型么
    2 |  N. L9 Q+ C2 x- |5 _( Z0 P0 V# [3 y
    df.year = pd.to_numeric(df.year.str[:-2]).astype('Int64')
    2 ^2 y3 Z' U! @# ]2 B3 V7 X" Zdf.loc[df.year.notna()]['year']
    + _1 c% g; o" W* T1
    , m/ A) u5 \& u8 Y! }29 u9 z8 w1 Z: Y, @
    将floor列替换为Level, Highest两列,其中的元素分别为string类型的层类别(高层、中层、低层)与整数类型的最高层数。
    " J! {# y( l1 b( j# x% Tpat = '(?P<Level>\w+层)(?P<Highest>\(\w+层)', K9 D9 n7 N' m- p
    df2=df['floor'].str.extract(pat)  # 拆分成两列,第二列还是(共6层得形式,所以还的替换一次* d4 E5 x) Z/ ~& X
    df=pd.concat([df,df2],axis=1).convert_dtypes()  # 新增列拼接在后面,再次转为Nullable类型
    ' k1 u4 H$ n9 q) U6 Z( Vdf['Highest']=df['Highest'].str.replace('\D+','',regex=True).astype('Int64')              
    " z9 [+ e- B+ c- \  R( udf=df[['Level','Highest','year','area','price']]
    1 J; b! s, v; o* ]% l. Edf.head(); C! U. p1 |" D3 R

    " I; B  d4 O9 C1 A8 G   Level  Highest        year        area        price
    ; |+ S, n! b  T& o, V7 C0        高层                6                1986        58.23㎡        155万
    4 B) ~3 p) ?( ~1 t$ F. e4 a1        中层                20                2020        88㎡        155万# G; i/ ~# [* x) m( ?/ h
    2        低层                28                2010        89.33㎡        365万
    # u* W/ B7 `" a2 [- c3        低层                20                2014        82㎡        308万
    9 R2 Y7 t5 H" ^; z& p4        高层                1                2015        98㎡        117万  w  n' @0 G1 Y$ V2 @, f6 k
    1
    ( T' y  F0 v& \+ G" P1 |$ `2
    3 k9 @5 {, V. n9 ?8 g+ M35 M# t- B3 A9 w2 I
    4
    : s! Y2 P5 x* E8 O) e* c5
    # ~) c1 q! e& z, F4 Q: M! o6
    7 ^1 r) Y" i1 O: j) A) D. V7+ f, \2 w& G, h5 A# f7 x
    81 X  |* c( z, ~/ Y; T
    9, F$ n+ }' }/ C. w6 U2 k, X% b0 n  _
    10" [1 r- g" }' I
    11
    6 v0 P& N5 F" |; |% D6 a. S. r12
    ) S& k7 X" }0 T2 x2 x$ q13
    : g- O3 Y: E3 a0 v3 p# 参考答案。感觉是第二个字段加了中文的()可以准备匹配出数字,但是不好直接命令子组了2 |7 ^' V! x7 i5 q" U$ [
    pat = '(\w层)(共(\d+)层)'" E: [8 }! {# W" L6 |* {
    new_cols = df.floor.str.extract(pat).rename(# l! r( Q$ o0 t1 O8 D6 y. J
                        columns={0:'Level', 1:'Highest'})
    # Z2 Z! g4 v: ]" m' n  Q6 G: K
    # H, |- B2 U5 Y  N4 d) fdf = pd.concat([df.drop(columns=['floor']), new_cols], 1)
    3 r  l! E3 t# R9 Y- L' xdf.head(3)
    8 H# I. r5 D& ~& h/ l6 V3 t
    6 K' b& V( j4 j) v6 Q# U# T) gOut[163]:
    2 a1 G( v( d  {; [$ @7 r& z   year    area price    Level Highest+ {+ o/ o9 F$ ]  y6 i
    0  1986  58.23㎡  155万    高层       6
    2 A( }7 \: q6 q( A( w- a1  2020     88㎡  155万    中层      20  B* ], M) x2 F: C
    2  2010  89.33㎡  365万    低层      28+ v/ f1 H& V8 P! A, f& J
    1
    - K) `8 B1 W+ b  L( c& a* n$ `2; y$ \; Z3 x# f& y/ O
    35 `; [( w# x6 b& L* x
    4
    $ H, l6 \. {2 B5/ s3 i' \7 s" E- ]& M! o2 O& h5 x
    61 ^: R0 D  }  V8 @+ Y+ n' g
    7
    + b* w3 J, y( x* F1 y  s# N87 G  e& p' `# E- O( z* t  Y  D
    9
    2 {9 [! ]9 |. k" m& _1 G$ C' R10
    0 M  J1 Q" k* F2 M7 I- l& B11" ^8 V2 s5 K  s: E
    12
    % c2 ^$ q% `% x5 r' _) U) T6 r' K  y' K13
    & u) d2 ?/ e* y$ v9 G4 ~" N) S计算房屋每平米的均价avg_price,以***元/平米的格式存储到表中,其中***为整数。
    7 m* {/ U! M9 f- H"""6 q' h1 M% m9 F9 e% d9 s9 }% T
    str.findall返回的结果都是列表,只能用apply取值去掉列表形式
    7 Y: A1 |' H/ g3 E9 Z! U参考答案用pd.to_numeric(df.area.str[:-1])更简洁
    + T) t/ F+ n& R% I+ B0 V% @! c7 j由于area和price都没有缺失值,所以可以直接转类型' v! ?4 `+ U# K3 d! x) d& j
    """
    : y: d" f' O/ y7 ^( \* g; D# Rdf['new_area']=df['area'].str.findall(r'\d+.\d+|\d+').apply(lambda x:float(x[0]))/ Z# t! z# r1 b% F
    df['new_price']=df['price'].str.replace('\D+','',regex=True).astype('int64')
    + N' b6 ~& L  W1 idf.eval('avg_price=10000*new_price/new_area',inplace=True)
    " q7 L- G8 o4 G# 最后均价这一列小数转整型直接用.astype('int')就行,我还准备.apply(lambda x:int(round(x,0)))
    7 y7 M6 q9 P9 v: C5 o( V" X8 f# 最后数字+元/平米写法更简单" H% m( \3 z0 L' G) J/ ~9 n- j) {2 d4 E
    df['avg_price']=df['avg_price'].astype('int').astype('string')+'元/平米'
    . Y3 i- x; }5 y$ s7 ]! L1 ]del df['new_area'],df['new_price']0 {) w3 u6 `) n
    df.head()% W5 |$ T  z; J. v! v8 {  @" _0 \7 {3 e$ r

    - t" B) a% G( r3 Q7 Q) N5 \5 e   Level        Highest        year        area        price        avg_price
    : y/ H- z: \: P( s: z2 l* A0        高层                        6        1986        58.23㎡        155万        26618元/平米) b9 X* H- j& \" ?: Q( h& Z9 `) B% ^. j0 f
    1        中层                        20        2020        88㎡        155万        17613元/平米& f! m5 _1 M" B! r
    2        低层                        28        2010        89.33㎡        365万        40859元/平米- a9 R1 E% g1 Z0 z. i  j
    3        低层                        20        2014        82㎡        308万        37560元/平米% k. {( _  _/ A
    4        高层                        1        2015        98㎡        117万        11938元/平米8 E; |* m, r; o' c

    4 U9 n8 [& i% ^- @' I' s, V& X1
    5 X3 H  x; f3 M: {. m0 A9 t2+ F) k7 [7 C( t: C
    3
    2 H) \4 J9 X; M& R0 g5 Q  W4; g+ n* z% `/ Q( x
    5
    ) j) C' u0 \5 O! G' @3 m7 v, f( b! q6
    6 k3 j" e* \# e* h7& L. @1 ^0 D/ d- q
    8
    " s: g1 w0 t# @* X9
    / ?) [& k+ W# z- W% C10
    " ], r7 r6 O# |( A. N- ?; E% x118 p% n0 B" [( x# V0 V) d7 ~8 k
    12/ W% `! n0 k5 w, V8 ]1 E. v8 q4 P
    13
    3 s% U4 N2 _6 ~- o* D! D14
    5 M+ ^7 v1 I- J" B( [2 B) O15& i' E3 B9 J: w+ t6 u; ?9 g" V
    16
    / O- i8 ~$ Y* y+ `- B  d0 w+ C17
    * c! M# p) d; X) N18/ U, c, T" P2 ~; M3 }( p6 w8 ?- g
    19$ T4 T, |! ?; C2 `, @  G
    20% B9 Z8 O6 N9 p* N9 g* [4 J9 S
    # 参考答案
    8 p; b4 K& I9 V* y5 G' ?s_area = pd.to_numeric(df.area.str[:-1])
    ) T7 T  H# \" C! m0 d% Rs_price = pd.to_numeric(df.price.str[:-1])& M% D2 U; Q" o' b. T: C' ^; V
    df['avg_price'] = ((s_price/s_area)*10000).astype(0 }8 Z; |0 }6 |4 l$ f) E
                        'int').astype('string') + '元/平米'+ m& U- G% k1 p) \" w
    . u& r, T5 T6 T+ G
    df.head(3): X5 B* u. [6 b& k& v' J
    Out[167]:
    9 c$ r9 d. z1 ?( s3 G8 z$ a   year    area   price   Level Highest  avg_price
    , W1 D9 Y6 L. Z+ p" |0  1986  58.23㎡  155万    高层     6          26618元/平米- D2 j$ w/ U+ M- e, Z. ~
    1  2020     88㎡  155万    中层     20          17613元/平米5 g3 g, `# v" S! a* i6 w4 }8 g$ B
    2  2010  89.33㎡  365万    低层     28          40859元/平米
    4 J3 ~9 m7 n: C7 y1
    2 D  h4 n8 c7 ^- T+ h24 r6 q( w0 a3 z/ u
    3
    ; g3 l. q4 Y! W8 D9 o8 ^4) n. ~& \  r# P* t% R: |  r$ Y' b! |
    51 S( }# x6 a  S* V
    68 m) P5 U& N" T. r) }
    7& c- t/ R( M1 @4 m& y# }& e
    8% q! W0 z9 l& L! l7 r
    90 ~) B! t! U1 R) |) X9 d
    10
    ; I/ f7 P1 M; O# M% M11+ I/ e+ I+ ?% P# j; T, Z
    12
    8 O5 T9 V3 H) b# gEx2:《权力的游戏》剧本数据集' l3 ?3 t' d( {
    现有一份权力的游戏剧本数据集如下:2 N/ ]0 K, r8 J5 c, z: Z
    & Y. {3 a- y( z" G; n/ m- W7 N
    df = pd.read_csv('../data/script.csv')
    5 b3 X6 ]$ U' Y8 |$ a# ddf.head(3)
    & s4 [. z, X1 [9 P' @7 Y/ o" v. Q1 X: K! c$ u5 z8 a5 J4 ?
    Out[115]: " C0 j# k3 o; {1 {3 q/ U4 ^: Q
    Out[117]: . q% K/ D; v1 T3 x7 d" e
      Release Date    Season   Episode      Episode Title          Name                                           Sentence7 j/ o: Z9 v2 k$ ]. Y. I( L4 B
    0   2011-04-17  Season 1  Episode 1  Winter is Coming  waymar royce  What do you expect? They're savages. One lot s...4 c3 {! N, j/ i6 C; {3 l4 i
    1   2011-04-17  Season 1  Episode 1  Winter is Coming          will  I've never seen wildlings do a thing like this...' W0 e9 {' K4 h& ?
    2   2011-04-17  Season 1  Episode 1  Winter is Coming  waymar royce
      E7 N( N- M. M1
    0 i. ?8 u# Z" ~; f* ~* I& t! P6 a2! q+ ]+ i7 D. M1 J% C4 k2 P2 @& @
    36 n. L( R  I) i  l% Y6 Y/ y/ o
    4
    # q5 |1 U+ V1 l5* N1 r4 z! m  y8 Z7 {
    6/ z1 {& d4 j7 {& a9 v  o% Q" X
    7
    9 D8 F: i2 n" ^  ^; S8  D$ i, D/ w* }" O. R1 t. h8 x
    93 |# M8 s; J3 A! O  u- D3 o* x
    计算每一个Episode的台词条数。) ]# M; o( C% T7 u7 y# r0 {. U, A
    以空格为单词的分割符号,请求出单句台词平均单词量最多的前五个人。) L7 M6 F( @* ~3 E* m
    若某人的台词中含有问号,那么下一个说台词的人即为回答者。若上一人台词中含有 &#119899; 个问号,则认为回答者回答了 &#119899; 个问题,请求出回答最多问题的前五个人。
    ' `$ T  N, c8 M2 h计算每一个Episode的台词条数。
    / }/ X5 k. k; [8 I& F4 ddf.columns =df.columns.str.strip() #  列名中有空格
    9 ~% Z4 [6 l5 v& D+ A# Z4 B( Q5 Jdf.groupby(['Season','Episode'])['Sentence'].count().sort_values(ascending=False).head()2 j  S" l- ~$ s2 H% f; j# Z& x$ j7 h

    6 P% u2 B; A, d( x0 o( x* a0 wseason    Episode  ) ]2 j+ ?( V. D4 b! O
    Season 7  Episode 5    5050 r: R& _2 I! m
    Season 3  Episode 2    480% o* ~8 L) O) v' t) s, N$ O
    Season 4  Episode 1    475
    ' s" k3 z. Y+ c$ Q7 X6 L* _/ gSeason 3  Episode 5    440% d. M0 @: h, R1 I
    Season 2  Episode 2    432
    ; p% C8 V- z4 P/ U1, }  {: ~  a( c5 k* M. o; v
    2
    7 g% ]; b+ @& b; Z$ C3$ [3 v1 R3 m! m3 N4 x4 h0 y+ ]
    4
    5 B& a5 y5 Y& R5
    , r4 s, `- j. v$ z9 I6
    7 E: ~  Q2 m/ X2 O8 t/ F79 }1 O% C  J; a% }
    8
    . T( L: z# }- N- |) `9
    , \& K, z* c1 W. X; K5 m1 S/ Z以空格为单词的分割符号,请求出单句台词平均单词量最多的前五个人。- t: [2 J$ K/ q4 R
    # str.count是可以计算每个字符串被正则匹配了多少次,+1就是单词数7 j6 h* J' ?" ], o9 S3 H( {
    df['len_words']=df['Sentence'].str.count(r' ')+1
    ' B5 j  o9 Z, E( A$ b' Fdf.groupby(['Name'])['len_words'].mean().sort_values(ascending=False).head()
    2 o* m1 Z  s  {! O" ^5 Q3 H2 b( C+ @; z' W8 P
    Name
    2 ^/ A. I' r3 {4 Y& Fmale singer          109.000000
    + M. U6 L7 {& ?' hslave owner           77.000000
    . X" e1 F8 E- {1 ]; y/ xmanderly              62.000000
    7 V& c3 d- s8 ]lollys stokeworth     62.000000
    ( Z* t7 S- G; y$ X( cdothraki matron       56.666667( d" s/ Q: n; h. W1 a- k0 O
    Name: len_words, dtype: float64* f$ Q8 ]6 j( L. E8 K- ^
    1
    & p' H! V7 w. N  f% H2$ d: {' x* l( o5 x2 C1 m
    3
    " v2 ]; v# y; o" T+ i4
    " K4 Q) B! F( ~7 I5' g; G# w. K. V* o8 U- h8 i# I' x
    66 g1 U, F3 t: e
    7& Z$ K* x7 Z/ S# R$ X
    8
    " g' @2 U, S' O3 F: x9
    ) }) w( R) ]/ N/ i7 g10
      _) u$ u8 W: ]( ]& f11- s. n5 ?7 M; v& T0 t: V) Y
    若某人的台词中含有问号,那么下一个说台词的人即为回答者。若上一人台词中含有n nn个问号,则认为回答者回答了n nn个问题,请求出回答最多问题的前五个人。
    4 u. I, B0 a; q# C# ydf['Sentence'].str.count(r'\?') #  计算每人提问数9 F2 {2 r6 T4 P8 |
    ls=pd.concat([pd.Series(0),ls]).reset_index(drop=True)# 首行填0
    " k& V% K" I4 I2 z! Tdel ls[23911] # 末行删去: }+ e3 G; v. y7 h2 e6 Y
    df['len_questions']=ls& d  S  @: @2 {% |; _
    df.groupby(['Name'])['len_questions'].sum().sort_values(ascending=False).head()
    3 [0 f% Z, D  D  v
      ~1 |8 @& S3 ]2 gName; G! ?# B; T8 l7 s: m) I
    tyrion lannister    527
    6 w9 e( x, h4 zjon snow            374
    7 m+ i) I' T5 t  |) U  o8 G+ \: s8 ]jaime lannister     283* j  i0 R: e7 N: n2 D
    arya stark          265
    / {- v' u9 u3 g1 a0 dcersei lannister    246+ k  R  X$ d& Q0 P3 p
    Name: len_questions, dtype: int64
    , ^" i2 ]" \1 x# }3 {3 [2 T. l9 \  B7 ]2 S
    # 参考答案
    , r" C& Z" S" J, |' Us = pd.Series(df.Sentence.values, index=df.Name.shift(-1)). \1 ]. P1 u/ i
    s.str.count('\?').groupby('Name').sum().sort_values(ascending=False).head()! u% J* m% r" M: o

    8 k0 P( y& W* |5 x$ L5 u1& o: D9 f3 J, x  G1 B  D
    2$ V- q* Y# F* ^* i
    3
    8 W* |" o/ \' a  a& R8 Q8 L48 n# S7 t8 c8 D2 x. w/ p- v7 L
    5" Z  F6 j" m: I- O% y
    6& S2 z# n/ @4 r9 ^, M& k" S8 n3 `
    7  ?- g, g6 c/ m
    8% p/ z* j1 M$ r/ W4 e. n
    96 X% j- e3 u3 Z
    10
    + j# g5 U% r. d6 Y+ n5 `9 }% ]" \11
    ( A& F+ {$ [- e( ?- u) B0 f9 n12/ ~" [6 D2 E' m$ W  F! K
    13
    - Z1 \* M) V% w2 M14* p( o1 p0 q9 u8 t% Y; e
    15
    6 ~  b1 @% N( v1 V/ y4 _16
    ! U  [+ A) F( ]  h$ t171 T$ b) s3 Q6 p
    第九章 分类数据/ S* }" |) y, k/ R
    import numpy as np
    & R, O- Q$ F" c/ Q$ R  O+ P& vimport pandas as pd( B: f9 H+ i# R% l* e6 I! c' b
    1# `* A) l& `+ I) k: V) R
    29 q- c" H# k3 c# y
    9.1 cat对象
    ; }; P& L% L, ]9 m9.1.1 cat对象的属性& z# C: f. E/ z- P  }$ {' i
      在pandas中提供了category类型,使用户能够处理分类类型的变量,将一个普通序列转换成分类变量可以使用astype方法。
    ; q& ?: |, I% ]0 j5 i1 C9 `, K+ _; m
    df = pd.read_csv('data/learn_pandas.csv',
    / V$ s& b! y. k! z; z4 C     usecols = ['Grade', 'Name', 'Gender', 'Height', 'Weight'])/ Q' s+ w; Z4 e/ [* ]: i6 [
    s = df.Grade.astype('category')
    # f, `- B9 ~* I# I; R$ l
    5 Q. \  f% L  s$ j$ Es.head()+ {# ?3 Q  b. o8 A3 B6 O3 N6 n1 I
    Out[5]: . G% R4 b. a2 f8 ?. _
    0     Freshman
    ) @9 g: A; \5 l: A* X1     Freshman8 w# R% e. ]  v8 ^# |; O7 U7 b! ]. x
    2       Senior& A  G# N- b! j" N9 ?- V5 i
    3    Sophomore
    ( \: O3 C# s6 [# w4    Sophomore
    $ c7 j. o6 V& W" a( L* RName: Grade, dtype: category
      G0 A% B' \# n6 HCategories (4, object): ['Freshman', 'Junior', 'Senior', 'Sophomore']
    ' |, q" I5 i1 @13 c4 }/ P4 E3 R- o6 b( N
    2
    4 j. ?* B* I- _# N( N3
    ( i: f4 N; C. h; O4 F$ X4
    . C" H/ u0 y* M0 e7 E, [6 K$ ]# D59 v  E( Q+ }& _
    6
    " ^" e. R- a. Q5 o8 Z7
    - f" r4 K) w. {: f; `+ a86 d& I! N5 V( p+ c
    9
    0 g: ~) V: Z4 ?/ m0 T4 Y10
    6 S2 Z  `. J6 X7 M- N11
    6 P. ]. Z. |$ k  G) q* T/ z12  H/ _; h" w6 W1 q3 t8 O
    136 B$ q! A, p2 Z* T1 v9 R7 B
      在一个分类类型的Series中定义了cat对象,它和上一章中介绍的str对象类似,定义了一些属性和方法来进行分类类别的操作。
    " B, @( Z3 U$ h& v9 Z8 B2 g9 ~0 g. h! U$ ~
    s.cat/ e8 H- }' x. T4 R5 F
    Out[6]: <pandas.core.arrays.categorical.CategoricalAccessor object at 0x000002B7974C20A0>
    8 e# @) \% g& }% M  k2 C1
    ! {6 U; ?$ a4 |; q- w8 j7 w: w' u/ ?2. n( P& e" M( I$ p1 p2 H- _& C
    cat的属性:# i. u: T. X$ @5 q% i7 A; m

    2 U3 n" Y+ _7 q# |4 m9 R  P7 Dcat.categories:查看类别的本身,它以Index类型存储
    : U( t1 X* ?. R7 j. vcat.ordered:类别是否有序
    & ^% j+ a" g3 {4 _& l- z5 @* jcat.codes:访问类别编号。每一个序列的类别会被赋予唯一的整数编号,它们的编号取决于cat.categories中的顺序6 K  Q7 }( j9 h, K
    s.cat.categories: S6 o* E( J6 [' ~& d
    Out[7]: Index(['Freshman', 'Junior', 'Senior', 'Sophomore'], dtype='object')
    5 \, x6 p8 V! g  p9 E: v3 d3 O3 n; H, l; c) g
    s.cat.ordered
    ! a( F0 g% o$ d3 AOut[8]: False
    $ u3 h$ ]/ a' [/ Q5 s0 k, x& g
    * L! ?$ p* B& O9 h: Vs.cat.codes.head()& e  k* g" H3 i) c
    Out[9]:
    # _0 w$ P6 d! g1 n0    0
    / a: r: Z, k# U0 [5 C, O5 {: V% A( g1    0
    5 ^! [6 ^+ N, s) c3 k2    2
    , Y" ~) o& X* T7 P3    3% i( x! X8 g7 G% C+ s0 R& [2 `
    4    3
    - q3 f3 l1 v6 G5 J  N! Z- w8 f6 Ldtype: int8
    1 Q- Q( F% d5 }/ N) T7 \: N1
    0 k4 j) {* }% B) E+ W2
    2 m$ V- y* M, f2 q/ I; O9 C& h4 ?3
    7 i4 s! P3 t& n/ t2 F- Q) q7 D4$ S8 V+ t# M2 U* O8 q
    5
    ) F4 j: j% I+ p. s& I% S6
    $ f, b8 B. Y- v' S7 q. K) Q% f# k$ [+ }7
    % r  a. c+ D% `$ ^6 a$ v# |8
      \0 B, T/ F" g: m# ?. d6 ^# C9
    + k/ o# f  J8 r* q10
    * ]0 Y- a* i1 |! [/ R11
    3 ~2 N. J/ y; Z12
    + E! L& ]' n4 a13
    - }7 |* b. ~/ l& ~14
    5 N3 E! l% U, U7 n) P/ b9.1.2 类别的增加、删除和修改4 ^+ L0 g$ j* S  h& O1 f2 w( n
      通过cat对象的categories属性能够完成对类别的查询,那么应该如何进行“增改查删”的其他三个操作呢?- p, z) C" H2 H: H

    6 c  a# ?8 q2 P( q2 m4 p2 e【NOTE】类别不得直接修改# g2 t; l  n$ M$ ]9 g" `- Z. O9 k3 C
    在第三章中曾提到,索引 Index 类型是无法用 index_obj[0] = item 来修改的,而 categories 被存储在 Index 中,因此 pandas 在 cat 属性上定义了若干方法来达到相同的目的。' @; j) A, ~" T, f- q
    4 A* o: T/ U! K9 h
    add_categories:增加类别: ^7 B/ m* _% E* ^% S' B
    s = s.cat.add_categories('Graduate') # 增加一个毕业生类别2 Y! o6 V: f1 ?' n. Q
    s.cat.categories
    5 B( @+ u0 {! U6 U
    ( D# h+ `( v# d/ pIndex(['Freshman', 'Junior', 'Senior', 'Sophomore', 'Graduate'], dtype='object')+ s0 q/ R9 g$ R/ P
    19 ?, l$ c; l* `9 A7 X
    2
    - k( l5 Q" V& T3, p0 c/ X% K  l- B5 T2 B
    4
    2 a/ s/ C0 a" dremove_categories:删除类别。同时所有原来序列中的该类会被设置为缺失。! L9 C* {; _% d% R1 k
    s = s.cat.remove_categories('Freshman')8 X6 c+ o; t( E, G  k5 ~: }

    - K! K5 `0 a) Q# p. c6 ]s.cat.categories. w! F& g+ \6 E! z0 {
    Out[13]: Index(['Junior', 'Senior', 'Sophomore', 'Graduate'], dtype='object')
    , i" I! g" \: {! i
    & Y' \8 c/ R. |, g# bs.head()" b( y, S2 T$ o$ k- l1 m3 o
    Out[14]:
    + \3 _4 E& i5 d# ~8 \0          NaN% f1 i4 m. h3 E# v5 T0 ?# X
    1          NaN
    3 ?7 K% V- l3 l# ~9 X* B# F$ z0 ?2       Senior7 ~/ }* C  S' E5 I% w' _
    3    Sophomore) F" `7 P" I6 U1 Y7 p
    4    Sophomore* U. j# \$ Q0 B) {
    Name: Grade, dtype: category: c6 a4 x9 h& W% T9 T' k( [: i1 L
    Categories (4, object): ['Junior', 'Senior', 'Sophomore', 'Graduate']8 @2 P# ^! Z( u# H; n
    1
    9 y5 z6 X, M' g2 [% L27 C8 R5 X2 f/ ?; n
    3
    / V: L; n. z9 [# U42 _6 I6 o$ f( E
    59 H+ h  y* L3 J. T2 Y3 l1 `
    6
    ; `6 D7 E* {2 u+ S8 ?7
    : D! `% e! ]" @+ X6 {5 T1 R8: E0 N. J0 v( W' ?/ t# H2 i( u, z/ t
    90 z; E1 {6 Q  r% l! L: \8 c
    10
    7 ?7 W3 ^8 I2 {* a11
    3 P3 R' h) @0 b- }) p12& \* p* F5 H& |) L. ~; o4 d% w1 h' b
    135 B$ T# I" q* ^1 j/ d5 H
    14; c+ S0 a: ?7 ^. ^6 X1 F+ r7 i# b5 B
    set_categories:直接设置序列的新类别,原来的类别中如果存在元素不属于新类别,那么会被设置为缺失。相当于索引重设。0 s4 o% f% G3 A7 Q$ A  z
    s = s.cat.set_categories(['Sophomore','PhD']) # 新类别为大二学生和博士4 A9 W( H/ Q+ v, ~6 T
    s.cat.categories( h7 O9 p! ~$ P6 }# s: W
    Out[16]: Index(['Sophomore', 'PhD'], dtype='object')
    / n9 y/ o) u! b; d) s3 a$ ~0 B# V+ N3 Y4 |, p4 f3 j2 [
    s.head()7 M: L) s& }9 Y5 _$ V
    Out[17]: 3 N1 n, y0 K( S' Y& I
    0          NaN
    9 x/ _. i- k/ [! D7 ^. o. Q: A5 S0 H* x1          NaN
      L/ A3 l; Z, h9 h* j4 z2          NaN1 t% Q4 d: |" J" D
    3    Sophomore
    ) R+ }3 N# W; f6 w4    Sophomore
    / l/ @2 B. ?  j+ s3 B# \1 ?Name: Grade, dtype: category
    3 o& ~' D- J' m" o4 tCategories (2, object): ['Sophomore', 'PhD']
    0 T0 q; J  N( z  ^% u) M; U1
    # v- H$ v# S% a2 c2
    & V2 D# @5 B0 f0 {* H* V$ T) X3
    8 r0 `  B: S0 b7 d4. X) a' I' ~. \" z' j2 b+ R
    57 {% c' V3 G( T2 t. B2 c
    6" i4 Z9 H6 |9 |" ^7 s
    7' q' w) m' M" ~9 \
    8: R4 B# f; P0 y7 R7 k% O
    9
    1 d. {' ^, ^3 ^2 \6 b10
    * K* \" q. n6 e4 ~2 \% B11- `/ S/ n9 N$ m( N
    12
    9 v/ R& x0 z6 w+ c( Z4 c2 U6 j1 `4 ]13
    8 [! m! ^  C' j/ aremove_unused_categories:删除未出现在序列中的类别
    1 M; D5 ]) u. S0 y1 Ys = s.cat.remove_unused_categories() # 移除了未出现的博士生类别
    ; P; T5 o; Q! u7 ~s.cat.categories. O- d$ O6 }. o  k
    ! h2 b, Q5 m' }5 ]8 S
    Index(['Sophomore'], dtype='object')
    ' ^" |5 _: |3 E6 g* y+ h1
    , N0 ?6 g& X# g# Q8 [2
    " ^( Z( v- l& J* W3
    # P1 ?  I( K9 K4* e/ Q5 _4 W1 ^" `$ C1 a
    rename_categories:修改序列的类别。注意,这个方法会对原序列的对应值也进行相应修改。例如,现在把Sophomore改成中文的本科二年级学生:! S0 V- K. t0 W+ a6 @! I
    s = s.cat.rename_categories({'Sophomore':'本科二年级学生'})! [$ [0 V% n% [$ Q6 }/ i' L
    s.head()
    ; H7 ~) G+ K( m/ w" R7 ?
    & X- P* Q# i0 B# ^- D/ z1 H0        NaN6 X, `3 v& C' Q3 t
    1        NaN
    & R" a! e/ W* W9 S2        NaN
    & l3 g+ G4 {2 T( q3    本科二年级学生
    ' [) h" m3 ]. l- K& P- _4    本科二年级学生' b  M7 h  k1 R' R& j
    Name: Grade, dtype: category$ P; l6 ?. T# h% x4 G" S7 q1 m
    Categories (1, object): ['本科二年级学生']
    3 B$ x1 ^9 @+ e. M& s1
    7 n/ M$ g5 G# ]( c6 c# I2 a20 J3 j" b' @6 ]2 M" E3 |/ l( _
    38 W/ n% i* a  Y( ?: v( W3 g' X
    4
    # s& g& W5 V6 e50 Z1 \6 L  _' m; v/ V
    6. C( U8 L5 F' D6 d1 U" D$ ^8 \
    72 |  B  X# U" t8 J
    8
    - Z/ W7 A4 C. p+ u) g9: c. T" w$ g+ }
    10+ c5 q; m) W' v1 s- f9 k; S
    9.2 有序分类# R, y8 T$ P. _" L- e
    9.2.1 序的建立$ c9 X; u* @  ~5 g
      有序类别和无序类别可以通过as_unordered和reorder_categories互相转化。reorder_categories传入的参数必须是由当前序列的无序类别构成的列表,不能够新增或减少原先的类别,且必须指定参数ordered=True,否则方法无效。例如,对年级高低进行相对大小的类别划分,然后再恢复无序状态:0 I  {% y( ^4 _9 ]( ]
    , H9 ~2 _4 q) J0 [# X: E
    s = df.Grade.astype('category')
    ; Z; N1 ?$ ~: @+ ]; f8 Ws = s.cat.reorder_categories(['Freshman', 'Sophomore',
    0 ]# u) O0 o$ S, Z9 _5 J# c4 {                              'Junior', 'Senior'],ordered=True); {5 `! a. t  ]0 A. M8 N5 n
    s.head()
    3 i: n: O! M8 ]Out[24]: % M9 V& S/ ~# }
    0     Freshman8 \- }- ^6 f4 p2 I: k
    1     Freshman
    0 g  i7 |# S" s( |2       Senior
    # l" u; @; I$ h6 |- ^, a( S6 r3    Sophomore0 P1 \8 S5 o7 y+ e+ ]  p  h) b' C
    4    Sophomore
    + ^0 C3 l, K) _5 `! B4 U- v# C) oName: Grade, dtype: category
    5 G; t# X- ?6 y/ wCategories (4, object): ['Freshman' < 'Sophomore' < 'Junior' < 'Senior']5 {" {+ P% ~. d6 H9 J1 \0 h# G. v
    # {6 t. G7 v' m# l( F7 l
    s.cat.as_unordered().head()/ q9 M) I* e  p
    Out[25]: ' }( y" L7 F9 w5 _
    0     Freshman( ^' ]' w" `" k0 }5 X. {
    1     Freshman
    1 f% w. ~# h8 W" X4 w! E8 z! V2       Senior
    * B* k4 [4 A' U. t5 v3    Sophomore
    ; R* D. ]# O+ K$ P8 B4    Sophomore  P& J  S7 K3 |5 Y4 J
    Name: Grade, dtype: category
    % q$ w) R8 U6 n: O0 [/ p/ j  SCategories (4, object): ['Freshman', 'Sophomore', 'Junior', 'Senior']
    9 y0 E7 \: m4 K* N' {7 O3 q1 B' u6 A% T9 p! j  T* b0 Q
    1
    . z" z* |3 W# j# S3 f2, x; X# ?9 r) m' t) K" c+ B
    3& f4 a9 Z7 ?5 k
    4( ^- i( d6 d$ f& m
    5
    # l7 k" m* y- }- c% g3 h) C. B6
    , E  h7 Z6 a! e7
    " y* J* j) ^! d. @8& m/ ~. g/ X, l, H. }/ F
    9
    6 q5 F# w) q7 M& k( j10, }- N$ i0 q5 N8 O; J" g" R7 d
    11* R3 r/ M  M' K
    126 w7 V5 \  n7 Y- j/ ?+ y! M1 m
    13& g  h  o* r# Z% [
    14
      f# Z, e) r1 X. I  Q9 J9 V15
    1 ^( l- d, f, V) X- f9 i7 c16
    1 Q* s4 _9 n- e+ P17
    $ m4 f) O' }" z4 @6 h4 b18
    / D2 r8 z0 V; ^& f6 N! x/ E19% G" K5 q1 ]3 L# ~7 l
    20
    $ s5 E; d; t) [21
    4 c0 s2 {% l0 V4 w; P! z' ?22
    - z* `9 y3 J% t; M% T  如果不想指定ordered=True参数,那么可以先用s.cat.as_ordered()转化为有序类别,再利用reorder_categories进行具体的相对大小调整。
    1 s9 `0 i; P- [% a: m# l4 [$ ^0 I6 K9 U$ n, X
    9.2.2 排序和比较, O/ a% ~6 q0 Q9 M$ ]# U& a" [
    在第二章中,曾提到了字符串和数值类型序列的排序。前者按照字母顺序排序,后者按照数值大小排序。) z" q! b3 R( }2 j* E
    . }" W# z  |) A6 F  k5 h* U
      分类变量排序,只需把列的类型修改为category后,再赋予相应的大小关系,就能正常地使用sort_index和sort_values。例如,对年级进行排序:7 J; ~9 T* G% n  i/ }6 d1 O0 u# I
    ( w" X+ A) ~9 ^. m
    df.Grade = df.Grade.astype('category')
    9 f: p2 j9 K- A0 a7 v) odf.Grade = df.Grade.cat.reorder_categories(['Freshman', 'Sophomore', 'Junior', 'Senior'],ordered=True)+ Q! A6 k* {- H0 b2 F# M, c: z# r
    df.sort_values('Grade').head() # 值排序
    / `8 `/ B+ D& h8 \6 A0 [Out[28]: 4 Z- Y- m8 R1 w. c5 e3 Y' d
            Grade           Name  Gender  Height  Weight
    1 Q; `# R1 ]) K+ F/ ~' L2 M0    Freshman   Gaopeng Yang  Female   158.9    46.04 |' }2 N4 {3 h) S- O" O
    105  Freshman      Qiang Shi  Female   164.5    52.06 h/ t7 n1 o5 E( [' K' [3 ~6 E
    96   Freshman  Changmei Feng  Female   163.8    56.0
    6 E. _, s5 S5 j5 G88   Freshman   Xiaopeng Han  Female   164.1    53.03 n! G! ]! _) |1 U; S
    81   Freshman    Yanli Zhang  Female   165.1    52.01 W3 B5 i# b- L: j  ]6 x
    8 _" q" x( ]6 _8 ]
    df.set_index('Grade').sort_index().head() # 索引排序
    9 Q# g( v4 P2 D: u8 Z! f, ^Out[29]:
    9 ]9 z' A1 V+ H6 O) i5 F                   Name  Gender  Height  Weight) O8 n4 _& g, ]' x% q/ c4 h
    Grade                                            f. M5 w2 p: z* f" G. ^
    Freshman   Gaopeng Yang  Female   158.9    46.0
    ( C4 B) |# U4 {8 [# [. ^9 g- OFreshman      Qiang Shi  Female   164.5    52.0
    % u& m- I1 G2 S2 @$ NFreshman  Changmei Feng  Female   163.8    56.0
    : Q" u1 P; e$ w# p% o+ B$ k3 w& P) m0 {Freshman   Xiaopeng Han  Female   164.1    53.00 G4 @' B4 d2 L* G. F8 u2 m; o
    Freshman    Yanli Zhang  Female   165.1    52.0% R2 A( h! Y( l6 ]

    , s: C0 S4 _* l' z' r  i( i  \5 ]: A14 l9 M2 L& [( O1 e" ^
    24 c% ^# V/ W$ R6 o% G
    3
    ; ^, Z9 Y/ B& m  |2 J9 i) G2 |2 y4
    $ c" d7 B& Y5 `5; h' |8 |1 i  D7 N. g
    6
    1 }' O$ K' q# {& q* J7
    4 K" h, l4 M. f7 l' k8
    ' ~! C9 w+ P' h  S1 V! u+ s& C1 G9  M( \. n* w+ _) D
    10
    + ^) _# z- V( U9 S; E- L11
    1 h+ j8 A! E0 P9 N( s! K; J12
    5 X) X* u. q2 G* a13* r  y1 M# ^) Z: }5 v9 o; f
    14. g5 F% _* o/ ~* Y
    15
    $ `: ^. D8 e" ?7 s. W1 c1 p+ N16
    1 P2 u8 V' I+ |17* e% [0 s7 ]. `: L& W! L
    18$ m: G& _5 P+ I& P6 C, G4 o% R
    19! {9 A6 f. P- h0 b. j& L
    207 O. G! M. i2 e- N1 V$ [
      由于序的建立,因此就可以进行比较操作,方便后续索引操作。分类变量的比较操作分为两类:- N) A$ F" Y4 a' C2 v+ x% E
    ; b& i8 w& o$ {* d* V5 A9 E
    ==或!=关系的比较,比较的对象可以是标量或者同长度的Series(或list)。(无序时也可以比较)
    : `, ]* g# W) V6 [>,>=,<,<=四类大小关系的比较,比较的对象和第一种类似,但是所有参与比较的元素必须属于原序列的categories,同时要和原序列具有相同的索引。
    ( U, R  s3 k" B) m% s# gres1 = df.Grade == 'Sophomore'
    0 i+ ]" Q, H$ D% x0 d3 G2 E2 x8 a6 v6 G1 y+ R& E9 n1 J
    res1.head()
    ' E" \2 p9 z- N' kOut[31]:
    6 o5 ?, j2 |" Q4 }0    False
    ' X/ j0 A  R  G" f  i1    False5 r* i0 d. S( _0 ^
    2    False9 ^  w  `& V5 |# H0 r5 |5 y+ a/ O- T
    3     True
    5 P& h  W" i1 W; P; Z9 o4     True
    5 w5 ]. X8 ?& P3 _5 X, dName: Grade, dtype: bool5 C: ?) f- g/ }% ?

    & O3 V% y& R# e+ bres2 = df.Grade == ['PhD']*df.shape[0]
    ' F# ?: A+ Q; Q8 R+ G5 Y- e/ |) _, m) q- D/ g
    res2.head()
    8 _1 Y( _8 [: [7 @' E5 mOut[33]:
    / b- a* V) F9 ]0 O/ ^0    False
    & L3 o: ^2 Y! j. K$ {# k7 y# o1    False, P/ }# N3 b9 ?: |% Q( i
    2    False
    + o$ Z, j0 M; k: n2 m! z, r  h3    False
      I4 X* H+ }/ l, I$ d# p' S# C4    False6 I" `$ [  m; X9 }) E
    Name: Grade, dtype: bool" t, L% r( J+ L% u+ j3 u
    $ W  }1 I  ?1 j8 h( H" {) B0 A& P
    res3 = df.Grade <= 'Sophomore'
    * \2 R$ h/ K0 l1 q
    & X+ W" }" d4 z: C' s, nres3.head()+ O+ `6 B" W5 q$ o& \
    Out[35]: 3 X3 f8 x* Z, v  o7 J
    0     True
    4 m* z4 q' c0 ]1     True$ B' L  E3 N0 I( M/ q6 Y4 n2 }
    2    False
    - j  f5 O2 y1 h: u3     True
    ; Q! y" c7 I/ d; E4     True
    ( m& o/ H2 Q( v0 c5 P- qName: Grade, dtype: bool- e1 u% w" K3 }" }
    2 [* i, B/ o9 n: j; M3 U8 ^
    # sample(frac=1)表示将序列随机打乱。打乱之后索引也是乱序的,直接比较会出错,必须重置索引。
    2 k. J- e3 t& z4 O. g) [; x4 d+ qres4 = df.Grade <= df.Grade.sample(frac=1).reset_index(drop=True)
    7 E) k8 m) j! W  Z! `+ a* \2 r" ~( F, f, @$ F2 e
    res4.head()
    ) e3 h: I, L- pOut[37]: 5 U8 @" O4 m/ x& V7 @" ]
    0     True
    2 ~8 G9 Q$ ?6 e/ O9 `1     True
    4 Z  t, l7 |5 m" X5 }' p4 c2    False
    : w9 Z6 d9 k) _' e3     True) P& p% P8 d+ C$ ^# u
    4     True- j* h( R# `5 R
    Name: Grade, dtype: bool. Y, D* g1 V2 u- d; k9 z4 _

      q  A& ?' o3 N4 x% S' l  m1 b1" I, O' K9 H" c4 M: g
    2
    7 G( d, y1 R% u9 M* Y# K3
    4 r; t! m9 X& J+ e( l+ @9 B: d4, y4 x0 {1 z/ X
    5
    ! X5 @- A1 `# q# |. B6
    % }0 X5 B" {% j4 K; t5 ]7% k* S) q" c3 z$ @
    8
    * Q+ H5 `6 l+ u$ s/ W9
    7 G& s- i/ o2 K8 j+ I10
    ) j! F4 C$ G! {5 U11
    4 [3 V1 ]8 N. W12' ?  r4 U& E8 D" a1 I
    13
    , T) v4 E1 r* c  I14
    ! n# T) j* d+ _: d5 x15& H, F; X$ p& I& m
    16$ f; ?8 H" b# x/ `. X
    17
    ) V0 O% j! L, j' M7 Q6 a18
    - ~8 F7 D, @' `. E$ B192 L2 X- C7 M+ \+ E$ A$ I
    20
    # r2 e$ [3 r8 g0 m9 e6 e0 S4 @3 M5 V6 z21. O3 j$ v: O* [: {1 p) h7 R/ U
    22
    4 J9 K$ a+ T" v" \, j2 t8 J23
    ! F( X* H6 Q8 Q* P: _24, ~6 w% y. t, z6 Q, h
    25! E4 z. V3 \# x0 v" _
    26
    / F# ^- g4 ?/ N: a: H27
    * F( f1 n0 Z& U, V1 P. o; q3 X28* t  _& }$ _9 k& o; h* @* B
    29' S6 l6 l" S3 ^- ?. Z) ~
    30$ W7 Y6 o* s/ f) k. t
    31" j& Z  |" W. o# m2 b0 I1 W' o
    326 F$ s& v  V( Q; Q5 a
    333 m' A6 g, O) s1 [7 Q" N6 {
    34
    7 d# l: c: [4 O" X5 A0 ?7 r35
    ; q" X$ e6 S5 S# }36$ ~$ D' L/ M- f8 O5 d. _
    37! v, L, T( D% e: F$ y
    38
    ( B; m$ n  |# R, d# k* E# j39  v. x1 I8 Q, q# j( f
    40
    3 N8 n% G/ c3 Q/ P+ C41
    1 `8 `# M$ M$ S2 K1 `2 U42- Y0 b) G1 f; M4 r" o
    43
    , P% s) h) z8 i6 }! a) `- K- y44
    7 ~. |! A+ J% \0 `, t7 B, L. X1 p! ~9.3 区间类别: t4 D8 }$ I, ?  P
    9.3.1 利用cut和qcut进行区间构造: p! W" ?  y! K  b
      区间是一种特殊的类别,在实际数据分析中,区间序列往往是通过cut和qcut方法进行构造的,这两个函数能够把原序列的数值特征进行装箱,即用区间位置来代替原来的具体数值。
    % q5 N- _, Z9 e
    ( O) v! {; g8 K9 C/ v/ ]cut函数常用参数有:
    - S$ {! g8 A- a& V: hbins:最重要的参数。/ w, v9 _. I6 T+ Y  h; v
    如果传入整数n,则表示把整个传入数组按照最大和最小值等间距地分为n段。默认right=True,即区间是左开右闭,需要在调整时把最小值包含进去。(在pandas中的解决方案是在值最小的区间左端点再减去0.001*(max-min)。)
    ! a; I$ X- \1 Y/ n9 ?! I& C也可以传入列表,表示按指定区间分割点分割。! X7 s3 N0 l0 R, h' R' i
      如果对序列[1,2]划分为2个箱子时,第一个箱子的范围(0.999,1.5],第二个箱子的范围是(1.5,2]。/ l1 ]4 ?+ C# b% C  |2 @
      如果需要指定区间为左闭右开,需要把right参数设置为False,相应的区间调整方法是在值最大的区间右端点再加上0.001*(max-min)。; x& W. \; A0 H' W- K
    / ]8 \+ O# W' }1 I1 p9 F, P- M
    s = pd.Series([1,2])6 Q6 J; D" b& e+ e
    # bin传入整数
    " I% @  b, c, D. B, \" d
    3 _0 ?1 N# q1 X7 Q( ipd.cut(s, bins=2)% k% D+ p  `1 n6 _
    Out[39]: * p3 h  Z9 t1 S! d! @
    0    (0.999, 1.5]
    - P. k7 v+ I& N" h: |+ Z# T9 s/ i9 {1      (1.5, 2.0]/ z1 o& Q0 }5 r2 V& i# }4 O' t
    dtype: category
    - U4 C: p: N) l0 u, ZCategories (2, interval[float64]): [(0.999, 1.5] < (1.5, 2.0]]1 H2 s& W3 K4 @" G! y
    . G* _3 Y, z" E7 w7 }9 h7 Q
    pd.cut(s, bins=2, right=False)( N* I# Z% u3 d. n6 {3 O
    Out[40]: $ o" j' q0 R4 e0 S3 h& s
    0      [1.0, 1.5)
    & D* b: a3 s- b8 O5 I1    [1.5, 2.001)! ~/ r' Q) U' s: Y- F
    dtype: category) K; C  l9 Z; R& r3 h  r$ E7 k0 R
    Categories (2, interval[float64]): [[1.0, 1.5) < [1.5, 2.001)]: i8 ]- o# N$ G  e, _0 D, D

    4 D% F& E3 C! N1 I  C
    # g2 w% p# B  n+ k* f' [2 _# bin传入分割点列表(使用`np.infty`可以表示无穷大):, W* o  y# D; u0 j
    pd.cut(s, bins=[-np.infty, 1.2, 1.8, 2.2, np.infty])4 d( B; U, v1 V( M3 ]
    Out[41]: ! A, s3 C& I5 ]
    0    (-inf, 1.2]
    3 e9 J7 K+ R' E! `7 X1     (1.8, 2.2]: {1 u7 K2 H2 G# l3 H
    dtype: category: W% b4 k) T( J: u( \
    Categories (4, interval[float64]): [(-inf, 1.2] < (1.2, 1.8] < (1.8, 2.2] < (2.2, inf]]. R, m, S' m+ b4 V1 W/ v& f* \5 v" A. H

      i8 c3 q$ S7 r( e: B  n8 r1
    / D+ t- V9 D3 c) h" d9 T& V. g2/ k) [: a$ I; f7 U3 D# e" s
    3
    % ]2 Q- s: L: z4
    . a* Y, N: G4 E7 k5 I2 Z59 l; F! h2 e  Q9 o
    6  ^6 G2 o; e9 ~- t& T! Y
    75 A1 J* E5 G! p. p& ^8 j
    8
    * m! R: Y, n* z8 k) ?# T2 \9( e8 P1 z- s2 Z) A1 q% d* U* F
    10* S: j8 v  E6 y) D' x
    11
    ) j, T) V; h' H12# [3 S% ?3 H4 @: d$ b+ k- j
    13& K( Z9 a' `  p  _  |/ Y
    14* r8 b  }; P4 V
    15( y4 }8 ?+ C5 c! ]
    160 ^/ ?. m3 g3 V2 O/ s9 S8 q
    17% a; R8 N/ w* d6 C- F* K
    18
    " H# @- S8 ^) j6 B' q19# m- ]9 R. ?2 S( Q; f& P% a  L) \" L
    20* O: C! y# g% C4 Z0 Y
    21" k: N, Y1 E9 p, U
    22
    ' _1 C& l% ~' Q( k! v234 ]7 B; O# l7 o* j
    240 P! ~. N( i8 k7 r# d* t5 M, r
    25
    ' B+ m# y, x+ @3 j6 [# H4 jlabels:区间的名字! w; c4 E* ^2 d; e
    retbins:是否返回分割点(默认不返回)
    ; d9 M% m0 Y. n- M& d默认retbins=Flase时,返回每个元素所属区间的列表
    ! Z( }7 B2 A: D1 h' yretbins=True时,返回的是元组,两个元素分别是元素所属区间和分割点。所属区间可再次用索引取值* h2 ^# z- K3 d

    $ K/ x. N  Y* @0 i' \. is = df.Weight0 S# h$ f! N6 G6 H) x+ F
    res = pd.cut(s, bins=3, labels=['small', 'mid','big'],retbins=True)
    + E/ ~5 ]% F8 f2 N: u' Wres[0][:2]
    & L  C) [5 e9 @; I6 Z
    6 t- ?# X/ b/ q# bOut[44]: / O  I8 a+ ^! T, H- G$ {
    0    small
    . [: u) ~. c- \4 N, x1      big
      |/ S% X" \# d6 Y1 u, Cdtype: category
    ) E. h- }2 D. I; i5 O3 O* |Categories (2, object): ['small' < 'big']
    . J' h' l6 @" p; V+ Z/ _/ @( x8 L4 E+ i/ u6 \1 B4 y8 `$ s
    res[1] # 该元素为返回的分割点
    4 ?6 {7 T- D" G, n. @Out[45]: array([0.999, 1.5  , 2.   ])" m6 L# R% A3 U: D  l0 T
    1
    1 I+ Y5 c4 y' Y% Q0 S* b2
    . {1 G# t! ?5 |/ s4 C34 C# \- K+ P8 I* e; h: Y
    4
    : z* g4 n! J& d4 g( B# O7 k( ^5
    ! }$ U8 }5 N6 O9 R60 I% {6 L$ G5 a
    7/ C, R3 g4 J8 ?" @7 d% k
    8
    4 K0 B. L1 H* P- V, W8 X9
    - ~* ~# E( q$ n( }0 ]/ f10
    8 g: ^# a4 T: g' q11) h! P% k- W! ^- Y9 I% h
    12
    / N; c  H/ U! Cqcut函数。其用法cut几乎没有差别,只是把bins参数变成q参数(quantile)。
    ' q8 y6 Q6 u1 B* w/ Z6 k- Xq为整数n时,指按照n等分位数把数据分箱
    8 [  x4 y( H; i! N5 t! A, ]q为浮点列表时,表示相应的分位数分割点。
    8 r3 @# ?3 `5 q% `s = df.Weight/ c( V/ @- G/ K% U5 Z

    9 y- q- R( o( w7 _7 o5 l) {: dpd.qcut(s, q=3).head()/ d6 d# n* ?5 _8 v
    Out[47]: ' r$ {3 G2 W0 a7 k: K; e4 q& \
    0    (33.999, 48.0]. A$ e: ~- x3 |7 b9 F7 L- D, m
    1      (55.0, 89.0]
    1 ^" _5 R' @) v0 K9 U- \. K2      (55.0, 89.0]; g; n/ L0 m$ ^' B
    3    (33.999, 48.0]/ O# l; I* D' C! Y) }* n% f( B
    4      (55.0, 89.0]5 [& B* W  a, a' M8 o
    Name: Weight, dtype: category+ Z/ T9 J: v/ c2 S, @: w! w
    Categories (3, interval[float64]): [(33.999, 48.0] < (48.0, 55.0] < (55.0, 89.0]]
    5 l; `$ v- m, Y" y# K1 F+ y
    + N4 s* b% Q0 fpd.qcut(s, q=[0,0.2,0.8,1]).head()2 J$ h9 Q% u8 s; M: R7 R
    Out[48]: ( @; w6 V- `  `+ o& R/ B% D
    0      (44.0, 69.4]( {0 W4 P) z1 I8 y* j2 k! j
    1      (69.4, 89.0]
    + P1 m3 _% Y; U, [- Z. ]0 r! D2      (69.4, 89.0]
    6 y9 B1 m# |. ?/ d% Q2 N) x5 t3    (33.999, 44.0]6 w& T5 s$ s& Z2 [
    4      (69.4, 89.0]
    * W0 z) c) w0 ?Name: Weight, dtype: category  o, R. s& Q4 S4 D0 B$ C6 U
    Categories (3, interval[float64]): [(33.999, 44.0] < (44.0, 69.4] < (69.4, 89.0]]
    % D/ X" K: H9 i" V  Z& H: Q5 b6 @
    1" K" D3 Z$ _: V8 h' z" p
    2, E- I' v. s! t1 g9 [
    3! u8 I6 U1 w' T; T  \/ {. n
    4  f) f! K/ V" ]8 `5 j/ {
    5: }% X: }2 Q9 @2 R4 I; ~
    63 W# L& {" h) Z1 p8 n3 a
    7
    1 T+ n% D) z! l8 b3 X# w8
    " b) k7 |, p2 v7 k/ t0 g( M/ B0 V% q9
    % e5 L9 A/ F9 K. J10
    , [6 e+ M5 {" G7 |( S11& Y* ?" k, G: `6 K: S8 G" Q
    124 Y8 o% A4 E) ^# O8 H
    13
    2 @; t+ a2 J% K+ }5 ]5 E14, C& c; D* h; ]4 X/ r: b  T
    15  H! R2 u# M! E
    16" X& x6 u$ N: s' ~5 W  I9 M- L; f
    17( L9 @0 N& T. k3 P0 J
    18
    6 O" ~% ^6 [. o  u) n19
    / F( G/ l9 O* ?7 I+ M20
    ' R9 N. }8 W7 Y, `; }7 f21, _- V( `: y- k4 U! v' U4 k2 s: b/ R
    9.3.2 一般区间的构造
    : A- l! [* ]: x, H  u) z) d  pandas的单个区间用Interval表示,对于某一个具体的区间而言,其具备三个要素,即左端点、右端点和端点的开闭状态。
    5 |/ Q& J+ b: \6 s2 G0 U* p
      u8 ]2 Q. z  w/ Z' Q1 x# a& V开闭状态:包含四种,即right(左开右闭), left(左闭右开), both(两边都闭), neither(两边都开)。
      u6 m2 p7 E) H: f. umy_interval = pd.Interval(0, 1, 'right')
    - w4 F9 ?5 v' U& ^2 H8 U1 a$ i* i  C
    my_interval! z8 P( e# A1 R# q
    Out[50]: Interval(0, 1, closed='right')
    $ A, Q& N& Z5 Z  ^( [. ?1
    1 T; Q8 H' V! A2 e/ _+ D' q2. l3 ~" I( y; \" [: O
    3
    8 Q* Q' a$ x8 j; i9 h5 S4
    8 u! Y* z" L; w! a; y$ Z$ ~区间属性:包含left,mid,right,length,closed,,分别表示左中右端点、长度和开闭状态。
    / E8 F# H& T7 K使用in可以判断元素是否属于区间
    + M& D* g* h$ T& z3 k' I* B, g用overlaps可以判断两个区间是否有交集:
    + d0 m) J! r* h1 J' O" `0.5 in my_interval' V) D4 q( E) @# b
    ) N2 M: d; q7 B$ A" K4 ~" l5 V
    True: d# C6 J9 ~8 p
    1
    ! R# l, ~! L# j2 T' m9 c2 D2
    7 I1 e  T/ T  R# G+ I3 F  s3
      x0 f8 q- P8 n" a6 C4 qmy_interval_2 = pd.Interval(0.5, 1.5, 'left')6 [* ^' O2 [: Y2 A$ R) ~* o
    my_interval.overlaps(my_interval_2)' {8 v  [% a" n
    8 f4 s9 M* w- d' P" \
    True) n+ w, x2 y' O0 E$ j
    1
    ( M! S+ S2 y0 y) o; w0 _' X23 t  E# l; v: y
    3
    / A- S9 g, I" T) t4
    ' K+ L' c% S8 c, g, i3 t2 B  pd.IntervalIndex对象有四类方法生成,分别是from_breaks, from_arrays, from_tuples, interval_range,它们分别应用于不同的情况:
    ! N7 c! s4 I9 \* N6 W9 P! X+ z# f, U* L" k0 e9 y5 Y0 ~
    from_breaks:类似于cut或qcut函数,只不过后两个是通过计算得到的分割点,而前者是直接传入自定义的分割点:
    ; h. z2 Q7 @, ~2 y4 b: `% n. Ipd.IntervalIndex.from_breaks([1,3,6,10], closed='both')
    ( j5 Z  J4 ]& h* A" b- }' K/ W/ ~; W# K, B* \4 U5 ^
    IntervalIndex([[1, 3], [3, 6], [6, 10]],  s) D7 I( g8 B! q6 [( r
                   closed='both',
    , Z2 a6 d1 |$ N- s: R) M               dtype='interval[int64]')
    7 k/ C% u- F7 h7 ^3 f0 v1
    2 u+ w" `, {' f2 g$ Q0 Y2
    / |7 E9 ^+ P# r* I- N' H: p3
    6 P/ {+ d( {$ ?2 ?5 v. R4$ x' E. y, ?2 f; Z! c
    5: B# M+ C/ r7 I) M
    from_arrays:分别传入左端点和右端点的列表,适用于有交集并且知道起点和终点的情况:
    % R$ C/ y! \7 K& L. h* Cpd.IntervalIndex.from_arrays(left = [1,3,6,10], right = [5,4,9,11], closed = 'neither')8 Y8 n' {3 N/ {" }2 b

    - U0 ?7 X) g% F* uIntervalIndex([(1, 5), (3, 4), (6, 9), (10, 11)],- u; G9 [% }. B) d) A" L- l
                      closed='neither',
    . V3 @! c, H: v& W                  dtype='interval[int64]')
    ) L  V4 L) W7 p- Z9 e/ p1
    & M8 [* R4 C# _& o/ ?' w2$ `4 X- O: a. v: }8 a: m+ |
    3* Q0 P) A# t% u1 r
    47 W; L. Z. s8 A( X$ m( P
    57 D& u; b& ~1 K, c
    from_tuples:传入起点和终点元组构成的列表:
    * ]1 N. c+ `% zpd.IntervalIndex.from_tuples([(1,5),(3,4),(6,9),(10,11)], closed='neither')3 w( O4 C) \2 F

    $ T* ~; @6 t0 S3 X% k- mIntervalIndex([(1, 5), (3, 4), (6, 9), (10, 11)],1 }; k3 i1 N/ b
                  closed='neither',
    6 E. [+ I) ]) G+ @+ A5 I              dtype='interval[int64]')2 u' x1 o' O: j* w& o/ \
    1
    6 Y! @* Z/ R' {# j' X21 C! V" V6 g/ V
    3
    " @; N( U5 S4 ~, ?# Z9 N! ]: ]- j42 o( X+ q4 F% Q$ O1 y( P
    5
    % p& ~& i+ d- |: b; u& K9 r0 xinterval_range:生成等差区间。其参数有四个:start, end, periods, freq。分别表示等差区间的起点、终点、区间个数和区间长度。其中三个量确定的情况下,剩下一个量就确定了,从而就能构造出相应的区间:- ?2 n' ]3 Z) Z  ~: ~
    pd.interval_range(start=1,end=5,periods=8) # 启起点终点和区间个数0 i2 @) t; q" j" g- Q: ^; R  F
    Out[57]:
    + v$ z# ?* `3 C3 XIntervalIndex([(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 V7 u. H4 D0 o4 L. \' W. t6 T* B2 B              closed='right',: Q0 t2 `9 @2 ]5 s4 G
                  dtype='interval[float64]')
    / B9 v9 B: L% D; u# L0 T9 z" P
    . s; t5 m8 m4 X& L9 |% G' gpd.interval_range(end=5,periods=8,freq=0.5) # 启起点终点和区间长度
    2 X8 s6 _1 e  YOut[58]: " W: c! L. W4 D+ g$ K+ q
    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]],+ f. p/ d* k9 L
                  closed='right',
    8 d3 `! c8 S6 X# }% W              dtype='interval[float64]')
    7 X4 _; k! v- r3 c) X$ O% E1
    5 o& u; N- j& j1 W+ H2
    5 R, u6 B7 j8 S9 L% a# ?7 W3
    7 C: Z" S0 n* k* L) d3 f4
    , @! Z5 z* d1 ^: @2 y+ h4 I5
    $ m& V6 m1 _, `2 U1 ]6 j6
    8 C8 y. G7 P9 ^# z. e* i( z4 U7" {0 R- p& p* W4 y# ]( [+ l
    8
    $ f" ~& }4 P) n: p6 {9
    ) Y1 W' P. l  z# a; r4 U$ J10
    ' n" x5 l2 v3 @3 P  h6 |/ ^4 B11# a7 o  }3 e0 j+ O! k9 R
    【练一练】
    : }' g. g7 J- m+ p/ e  无论是interval_range还是下一章时间序列中的date_range都是给定了等差序列中四要素中的三个,从而确定整个序列。请回顾等差数列中的首项、末项、项数和公差的联系,写出interval_range中四个参数之间的恒等关系。, z+ a' P: H5 ^4 @
    # s- E7 o$ ]9 G, `# n) g3 ]1 N
      除此之外,如果直接使用pd.IntervalIndex([...], closed=...),把Interval类型的列表组成传入其中转为区间索引,那么所有的区间会被强制转为指定的closed类型,因为pd.IntervalIndex只允许存放同一种开闭区间的Interval对象。
    / x5 h/ r2 G5 T# B! ?
    9 I- I+ n* |) D2 [* imy_interval
    $ `5 a8 I! N: a- E# bOut[59]: Interval(0, 1, closed='right')
    0 r- ?' b! O# z2 P9 ^) h+ ?# O+ z2 l- q
    my_interval_2
    9 h+ R6 P/ U7 e  a, W4 wOut[60]: Interval(0.5, 1.5, closed='left')
    % N$ z8 _: V9 e9 k0 J0 i% |9 X; I2 f# b. ~
    pd.IntervalIndex([my_interval, my_interval_2], closed='left')# [2 J  a2 K# ]% H# m3 f3 j
    Out[61]: / h7 u& n0 A3 r
    IntervalIndex([[0.0, 1.0), [0.5, 1.5)],
    , d/ u# F- R1 e  a' k              closed='left',5 O- [" X8 }' q9 R
                  dtype='interval[float64]')& {* N, l. @. @4 I( A/ Q4 v2 g+ k
    1& N+ G( j5 k  Z9 |. |
    26 P$ l! Y/ b4 s2 _5 H' q
    33 d3 W5 A7 T$ X# B, T) x* P
    4% q; E; F: r1 V% n- Z8 q; a
    5
    ; L% F0 W9 L% S% K. m" U, W6
    : x9 a+ e/ z0 y6 a! Y: d/ p$ Z8 Y7$ h' x5 x1 g7 o# p. B# J
    8
      P" l2 q) k9 C& c90 I9 m/ |7 P& w7 X0 \  E
    10  I  h- X- Q8 j, N5 H
    11
    : e) P4 h  G9 }# f- y9.3.3 区间的属性与方法- b1 |* t) Y  f
      IntervalIndex上也定义了一些有用的属性和方法。同时,如果想要具体利用cut或者qcut的结果进行分析,那么需要先将其转为该种索引类型:
    * ~: R; i2 w9 S) F6 K) f3 `( |2 ?* e) Y9 f) D6 J, \, H, P2 _
    s=df.Weight9 [  O+ o7 q! ?; {+ r
    id_interval = pd.IntervalIndex(pd.cut(s, 3)) # 返回的是每个元素所属区间,用具体数值(x,y]表示* Z' [3 _" p" x9 t( ~9 Q
    id_interval[:3]/ c6 H9 Z% ]% M; d+ C: y" i

    * M8 K: ]0 V5 k. p8 NIntervalIndex([(33.945, 52.333], (52.333, 70.667], (70.667, 89.0]],/ I) ]# M- T" l9 ^6 b. x  b
                     closed='right',
    ! w& o  |: y! x5 ~9 @" O9 k% D                 name='Weight',
    3 E1 G5 e4 h9 U' B1 B                 dtype='interval[float64]')% G/ w) Q' N! b: i
    1
    7 |0 o7 v2 l. n( e* _. |" h25 Z! K7 L8 F" l1 C# T  C6 q
    3
    - ~; ~; R2 G& t3 h47 l$ O  V; T- q. B& C5 ?
    5- b. N/ x9 U& V1 F- z5 j
    6
    : {- ~3 K& b; L7# x! v: x& u$ F
    8
    + `" K: l1 O7 u& h1 G$ |$ U与单个Interval类型相似,IntervalIndex有若干常用属性:left, right, mid, length,分别表示左右端点、两 点均值和区间长度。
    - r* W9 T5 ?( `) T. @( [id_demo = id_interval[:5] # 选出前5个展示
    . I0 Y0 e- B$ r( F( S, W$ R
    & z0 z7 D2 |; s' n, I9 cid_demo
    - @6 z( L* M% j; m9 LOut[64]:
    0 `; E( Y- E  d2 L" a7 nIntervalIndex([(33.945, 52.333], (52.333, 70.667], (70.667, 89.0], (33.945, 52.333], (70.667, 89.0]],! ~- o4 x/ V4 D% L- X
                  closed='right',
    ) V! ^+ \8 g6 r8 P" p7 U              name='Weight',  N/ e2 d) e! I
                  dtype='interval[float64]')% J! B4 b. V$ J% b- u

    : L4 \/ H  K0 G3 ~. u5 y8 iid_demo.left # 获取这五个区间的左端点  J  V& ^! H+ A
    Out[65]: Float64Index([33.945, 52.333, 70.667, 33.945, 70.667], dtype='float64')
    4 C. w) d* D& F+ u0 u+ [7 R) S* W% b8 D
    id_demo.right # 获取这五个区间的右端点5 ?3 @% O' D4 y4 J8 p
    Out[66]: Float64Index([52.333, 70.667, 89.0, 52.333, 89.0], dtype='float64')9 o) N& r5 v, }; n* E! K& |

    4 W9 h+ {: C+ @- s4 L3 i; w! Wid_demo.mid
    ( D" Z5 {; J* I1 oOut[67]: Float64Index([43.138999999999996, 61.5, 79.8335, 43.138999999999996, 79.8335], dtype='float64')
      Z; \/ y9 d  g: I
    4 j9 Q& X( G2 T0 T) X3 ?6 M6 ?id_demo.length  F3 j4 P) _. i6 g
    Out[68]: & e( g3 X+ l# ^  l6 j7 g" z$ B
    Float64Index([18.387999999999998, 18.334000000000003, 18.333,8 _, L4 M" J8 I: d. B: e
                  18.387999999999998, 18.333],
    7 U: \2 q3 j" N0 p2 J! U             dtype='float64')& q' H. h/ w- ?: |3 ]

    & F* h' y4 _5 j2 N17 x# b0 I" D- q8 `. T8 B
    28 E5 _/ \! P  q. R' h' m
    3' G' T: J7 d: N  i
    4
    . F; U/ x( R- d2 c2 V7 O5  I6 q2 C/ V, ]
    66 I5 Z! Y; g& E! l/ D( `; R
    76 O+ i! `, [, N% t  |
    8
    5 R7 r) `" Y4 X- ^5 a7 q* g& L8 X95 P; t3 k, C! _  P; s* M
    10
    : O' ]1 S, O0 m11
    6 J4 \8 e- ~# B  C( d7 c123 K; P2 `& g$ W2 U
    13
    # k; X" {# v5 N* y8 C14
      g, |4 {4 }5 e. U! x+ y' R15
      \' s6 i# }( u/ _16
    % T$ I/ |! }6 k( N' b1 e7 ^17/ M+ @, q4 [  f; d+ z4 T( O
    18
    ' N4 }7 x" m2 P: Q1 c' [1 J; Q3 b19
    1 H" s. f/ e3 u6 Y, ]% a20
    6 f  _3 }# E' E& Z" W. S. e; `/ y217 O6 D1 x: g" i& m# J
    22# [! l9 K( A6 A/ \' O4 S' ~
    23; P, X6 `3 m8 T6 m) M+ K
    IntervalIndex还有两个常用方法:
    - u* s+ x# a- a3 ]contains:逐个判断每个区间是否包含某元素* h% Z- F4 Q+ d9 A: `1 B
    overlaps:是否和一个pd.Interval对象有交集。
    & M, \9 L. k" j- l) kid_demo.contains(50)4 x  |2 ^3 {- }
    Out[69]: array([ True, False, False,  True, False])2 u/ [4 Y8 e6 |
    , b0 y8 Q& X$ n
    id_demo.overlaps(pd.Interval(40,60))
    * q8 ^4 Q4 E0 n, I. ?7 YOut[70]: array([ True,  True, False,  True, False])5 S, [! D9 T8 }: k
    1
      @/ g/ c" x  s8 }+ Q' T' O$ e2  Q6 ^9 w0 E! X# ]( g
    3  A0 h% a6 q) t8 `. ^! o
    42 @/ G- B; S! K) ^# Y. \" z
    5+ S  V9 g9 o" f; B4 N
    9.4 练习
    , D; r; W; _) S* v- ^Ex1: 统计未出现的类别% q: ~# B! O0 m9 k4 @" |
      在第五章中介绍了crosstab函数,在默认参数下它能够对两个列的组合出现的频数进行统计汇总:
    ( T9 q" u+ q1 z  W4 U! \0 `# q/ G- I6 I2 l
    df = pd.DataFrame({'A':['a','b','c','a'], 'B':['cat','cat','dog','cat']})! K5 ]3 B7 I$ R; g3 o3 Y
    pd.crosstab(df.A, df.B)
    : H6 W( h# d+ o* j& p4 y9 b$ _6 b$ P! u' ^0 i
    Out[72]: - e5 t* X0 I! d
    B  cat  dog
    ( h% h- ?3 p4 H, EA          ( I* z$ h, Q- F9 u8 K2 d; w& r% w
    a    2    0
    2 M5 P3 [6 h& S+ i$ s. |& {b    1    06 x' B. ?5 d7 S- H+ @
    c    0    1
    6 c) }1 A3 P/ |# Z16 y2 l3 b; T0 F+ M. Q$ A# l5 H( M' H# A
    2; K' C  P) s$ v0 c7 Y
    3
    : M& D. g8 n8 q1 X40 L9 T: S  \" N! B4 M, s
    5
    9 @, f  u( ?! w1 u  @, i2 S6
    # r+ h3 v/ H! _; q7
    - j0 K6 Y% I8 {7 e0 y/ {) V82 M3 o% t" A4 ]; e- X2 R, y
    9
    % |1 P# v1 |( V9 m: i1 n  但事实上有些列存储的是分类变量,列中并不一定包含所有的类别,此时如果想要对这些未出现的类别在crosstab结果中也进行汇总,则可以指定dropna参数为False:; A) D3 `6 X! Z  j( u; o+ N) ^

    ) {/ C% d9 d  ~* odf.B = df.B.astype('category').cat.add_categories('sheep')
    , `1 z) E3 Y1 a( p5 ^pd.crosstab(df.A, df.B, dropna=False)7 G8 h1 a1 Q( q2 h; f- V) J6 C0 Q
    4 q! h+ C6 B$ ^# i# ~  v( `: r
    Out[74]:
    . m5 h( T9 v# }# V8 J8 e* B' LB  cat  dog  sheep; J, {: P9 v4 m1 o6 J
    A                 
    7 O. D* o( r1 C- l$ ^+ `+ C$ Da    2    0      06 O( a4 ^" p$ ?2 _) M7 g& x- ~4 w
    b    1    0      0
    5 W. W6 ?) Q1 u# @/ U3 ?5 r. {. t3 Yc    0    1      0# @0 @3 @# J* t
    19 j- q7 ]! X5 ]) T1 |
    2$ n! a% T8 J* s. S& Z
    3- T3 ~* S+ ]! O
    4
    & A2 B4 B( ]$ e9 P53 p& h3 _1 f- ]: D0 s8 `& e
    6
    , G' o" H- J. m0 f7* B4 ?0 B- v1 g2 G/ v7 ]( u+ F
    8
    5 c% U2 ^, @8 F+ \. z9
    $ S. V# f& s. f请实现一个带有dropna参数的my_crosstab函数来完成上面的功能。  d& n' K7 W7 H

    * l8 H0 \% R+ k" y2 t+ n, y* D+ qEx2: 钻石数据集3 D" \; \+ M8 B' v2 [
      现有一份关于钻石的数据集,其中carat, cut, clarity, price分别表示克拉重量、切割质量、纯净度和价格,样例如下:
    : b/ ~5 s- f# f; ~* ^# Z) z3 \5 S4 V8 L5 k( s$ [: j* D* U+ R
    df = pd.read_csv('../data/diamonds.csv') 9 u# _# s! @6 B8 ^! }
    df.head(3)$ p9 k9 r5 R* [( z0 O
    0 ^# o2 N! `. J5 q. l0 U
    Out[76]: : b4 X& C- Z* o# \" E8 `
       carat      cut    clarity  price
    1 F7 x% Q8 b+ I9 H  ]0   0.23     Ideal     SI2     326; I" }# ~5 G$ f: |$ u8 F1 Y) \
    1   0.21    Premium    SI1     326
    % M$ Y; w# Q/ w+ Y$ F2   0.23     Good      VS1     327
    # ?: f. Q; w* \2 C7 F3 {+ F% i12 F  n5 m* w# a# W6 N
    2
    ; Y& g/ }/ H7 g; d, ]+ N' \& H39 i7 G$ w! I" Q$ l
    4; e4 w2 \2 v% U
    56 U( }0 m5 F# w, Q  ]& h/ v* f- ^
    6
    ! J6 y# e* H8 _$ A4 O7. k- m' J- v  M% X3 ^/ H2 a5 A* _
    8
    4 O8 a& T# q+ I: `3 F. ]分别对df.cut在object类型和category类型下使用nunique函数,并比较它们的性能。
    # q: F9 ~  h9 G1 n* m8 F+ ^钻石的切割质量可以分为五个等级,由次到好分别是Fair, Good, Very Good, Premium, Ideal,纯净度有八个等级,由次到好分别是I1, SI2, SI1, VS2, VS1, VVS2, VVS1, IF,请对切割质量按照由好到次的顺序排序,相同切割质量的钻石,按照纯净度进行由次到好的排序。, F0 D1 a8 W" b1 u3 P% N
    分别采用两种不同的方法,把cut, clarity这两列按照由好到次的顺序,映射到从0到n-1的整数,其中n表示类别的个数。
    $ b1 |) Y: u% @% `对每克拉的价格分别按照分位数(q=[0.2, 0.4, 0.6, 0.8])与[1000, 3500, 5500, 18000]割点进行分箱得到五个类别Very Low, Low, Mid, High, Very High,并把按这两种分箱方法得到的category序列依次添加到原表中。
    5 ^% F" P% ^$ u第4问中按照整数分箱得到的序列中,是否出现了所有的类别?如果存在没有出现的类别请把该类别删除。
    , C/ b4 r) y# x$ w- v/ \8 [6 M/ `对第4问中按照分位数分箱得到的序列,求每个样本对应所在区间的左右端点值和长度。7 o, m+ C3 Y# c7 s" |7 u' {
    先看看数据结构:1 |- u# n& I- A
    1 c! R, N5 }' ^1 a
    df.info()
    ' Y& Y9 a7 O! M" U  o1 f# IData columns (total 4 columns):
    / ^9 W$ N6 r& _+ V #   Column   Non-Null Count  Dtype  
    ) U9 ]6 ^  v* P" |: z---  ------   --------------  -----  
    2 }8 O* P5 H0 h" k 0   carat    53940 non-null  float64+ M) `2 o8 R5 @  Z3 p' i( ?
    1   cut      53940 non-null  object
    7 y4 ^" d$ A; @: W$ W9 `# b/ _2 ^) n* z 2   clarity  53940 non-null  object
    6 x. `4 ?& p  U, m( c8 N  l" a 3   price    53940 non-null  int64  
    # F4 g% K+ L9 @dtypes: float64(1), int64(1), object(2)# b; f+ V0 h" e" w9 H5 @' a
    1: }% n  {' v8 h$ C8 G" O- S
    2
    ( [6 t! p7 v# J; o5 _3
    ! C1 x8 f2 E/ T8 E" Q) _- k4* B( B9 O, H* ~3 ]! R) E) Y
    5
    0 }1 s, E0 X) L2 z6 r: m/ z9 D6
    1 F- P- y, Y  P. t0 X6 j* D7
    " y4 D- J7 k* U# e8 g0 G8  p8 l+ i) q6 B1 F; p7 B
    93 p  O" Y+ V) r+ f9 G, l
    比较两种操作的性能
    5 d* C5 ^" Q9 s9 U%time df.cut.unique()
    5 J$ e" {. F% K0 {" H) `3 r) E# a' j
    Wall time: 5.98 ms4 y  m& K# ^2 W: D; D0 C( W+ I
    array(['Ideal', 'Premium', 'Good', 'Very Good', 'Fair'], dtype=object)2 s8 ]; f$ i& \( f- o
    1
    2 X( E) M. X0 }- T  A& y27 }+ ]: x. T$ J
    3
    : D" j; e0 ^% Y1 c- l4 D6 @4
    * i7 p  u2 ~$ I! y4 `! D2 l3 s& _%time df.cut.astype('category').unique()
    9 ]) `8 X. k0 a4 B0 Z! n* r, w! ?6 f( F% C# X
    Wall time: 8.01 ms  # 转换类型加统计类别,一共8ms
    2 y$ N5 E  J& U7 n['Ideal', 'Premium', 'Good', 'Very Good', 'Fair']
    * W5 m8 R& |6 i$ s, zCategories (5, object): ['Ideal', 'Premium', 'Good', 'Very Good', 'Fair']" \; n9 ?9 P" k
    1
    ) x" Q9 r% U' D$ v3 F+ M2
      G2 C' l$ z7 C( b9 `) `35 T9 F$ g  ^9 q) O1 ^, m
    4
    % D, a/ Q4 z7 i54 J, e" B) e4 b0 Q, ^
    df.cut=df.cut.astype('category')
    . h. r3 Y9 y& B% m%time df.cut.unique() # 类别属性统计,2ms+ `5 d" _' P/ l. K' ^
      N+ h3 U" ]$ G
    Wall time: 2 ms) q; g: X7 l1 i* ?5 s' o( n- R
    ['Ideal', 'Premium', 'Good', 'Very Good', 'Fair']
    * p$ w7 u( a- r8 U( Y) TCategories (5, object): ['Ideal', 'Premium', 'Good', 'Very Good', 'Fair']; ~! `, A: f2 l$ {; E8 b1 R
    1# R2 Z  _# G# i, K( e$ w6 f
    2
    5 N0 R$ V. t. u4 |3. }( U( @) x7 j- Y; r+ D1 E
    4
      k$ |6 Z8 ^1 ]0 D9 b! X5
    ( F! k& \9 ?. q# M$ U/ _! ^7 |6- U' o6 y- @8 a% m
    对切割质量按照由好到次的顺序排序,相同切割质量的钻石,按照纯净度进行由次到好的排序。
    4 R. H. s1 f7 J. @! M& |: r8 _1 Qls_cut=['Fair', 'Good', 'Very Good', 'Premium', 'Ideal']2 p: t( o" R, v7 s( _7 w4 V
    ls_clarity=['I1','SI2', 'SI1', 'VS2', 'VS1', 'VVS2', 'VVS1', 'IF']- h' m' l) I/ K3 ?+ B, z
    df.cut=df.cut.astype('category').cat.reorder_categories(ls_cut,ordered=True)  # 转换后还是得进行替换# @7 R# ?3 o" p! _$ W/ |7 e8 }0 t
    df.clarity=df.clarity.astype('category').cat.reorder_categories(ls_clarity,ordered=True)
    - F# y4 Y: _, W. o5 J" F3 |- `/ [4 A3 a3 T
    df.sort_values(['cut','clarity'],ascending=[False,True]).head(3)
    * F  L5 G1 w" @, U. M
    $ U( p5 q% R, J2 P$ D        carat         cut        clarity        price
    " a: e! O8 o  @  k315        0.96        Ideal          I1        2801! [- o. e/ R* x2 ?6 j( v
    535        0.96        Ideal          I1        2826) R8 `" x- X0 I% c8 z& |% h
    551        0.97        Ideal          I1        2830& v' g& {: H5 h7 l
    1
    3 I  V: R  n5 V0 Q: i25 X$ @& |. d+ L
    38 i# Y0 B+ Z" @/ v
    43 o% Q" d% z9 ]+ _
    5* T4 e2 r% T( y$ [! y; @
    6
    3 p4 Q; O/ K; P7
    2 Y. v1 P) v% ~' {8/ u8 x9 b; G: i" i; X7 W0 }* k- _
    9
    ) r( E( [# D* o' Y10
    " j' F( @# K  G: j) P, ^& n11' |# B7 ?8 k, ^3 i0 t8 n
    分别采用两种不同的方法,把 cut, clarity 这两列按照 由好到次 的顺序,映射到从0到n-1的整数,其中n表示类别的个数。
    8 U1 t) d! x% ~. C6 [( b; |# 第一种是将类别重命名为整数
    9 t+ Y  j2 e- @1 z" I6 p- Zdict1=dict(zip(ls_cut,[x for x in range (4,-1,-1)]))
    % q' F6 a) m/ E: i( V: Odict2=dict(zip(ls_clarity,[x for x in range (7,-1,-1)]))- W" }- ?: B. T6 }! t$ ?6 `# Z
    - a& o& E" ], N( P$ O* ^# m
    df.cut=df.cut.cat.rename_categories(dict1)
    " o- g) o, }& s, s3 Bdf.clarity=df.clarity.cat.rename_categories(dict2)$ V2 H% I1 N% N" s( P
    df.head(3)5 q+ c; d/ l# W1 C7 n- B
    0 Q; `% a) e* w* C) D3 k
            carat        cut        clarity        price3 L+ r* i1 t$ G3 ?; v
    0        0.23        0          6                326
    9 q8 T2 e5 A6 R, n1        0.21        1          5                326
    1 D7 Z( \0 Q) N8 P9 F2        0.23        3          3                327
    ( T( n, D  s- {  Y5 f3 ~' c1
    3 ?2 w$ q$ h- V- G% U7 w! N1 g2
    * m. n% c+ C$ b# S' e& U2 K  B9 s3, G' ]) Q8 u" e
    48 l6 ?! X$ [# y3 T' f9 p
    5' j% e) |1 Q# {' @& U8 ~( ~
    6
    8 _) ]- T8 B( a7; X6 O! s& F, g* [+ k( x9 [, w
    8
      i# h. u, {" U, D  D5 H8 `9
    , r$ s$ l! t& A0 [$ B2 U+ h10
    2 m- D" E6 A$ m; }; v$ G/ l+ M* {11
    0 u; N/ K% K7 t. m& z& o" e: m7 b, q12
    & M; ~+ v. s8 h( c4 v# 第二种应该是报错object属性,然后直接进行替换% r2 K6 e# ]( J- J2 _! i8 p% ~% ]& `
    df = pd.read_csv('data/diamonds.csv')# M; L, Z/ k; a& u& Y5 p2 ^
    for i,j in enumerate(ls_cut[::-1]):2 O* h( v# x! _; e8 M$ v* N
        df.loc[df.cut==j,'cut']=i
    0 u- B2 F2 i/ I9 f% Z. o+ f( |* u5 S  [
    for k,l in enumerate(ls_clarity[::-1]):
    + z( x" G: C( T" L    df.loc[df.clarity==l,'clarity']=k* G9 W4 m/ S' Y' ^4 |" t' L. U$ ~
    df.head(3)
    5 G; h+ J2 ]$ d2 }: T
      d. v$ P- Q2 t5 G- l        carat        cut        clarity        price$ a. e1 I; E9 q9 D8 e5 m
    0        0.23        0          6                326/ z2 ]2 M) p6 ~& R8 B, G% l  m
    1        0.21        1          5                326* w" {! A1 s/ _6 A3 X  u- y
    2        0.23        3          3                327
    8 d: v: ^. b+ b: W2 U9 {4 m1
    ( o/ Y. h' v% g. S: o, q2
    # }  Y  A4 K( z9 F# P* B8 \3
    ) c" }) Z; |; R6 _/ V% z4! r5 p6 ?  o9 }6 g' K: K! I
    51 v$ w: A- z0 P1 j) |2 X) V8 Z" D
    6. o( `5 [  b" j. g* b% `
    7
    ' ]( c# K! s2 x- m! M8, f! D' f: g+ X% i! {7 h& u
    9/ v7 i3 N0 [% r3 \7 @1 E
    10" Y+ d0 A' S4 U: o8 u4 e! F
    11; [0 v: |+ p! P$ b
    128 x' H8 P6 E+ W$ p% F4 M1 {: u; Y' G
    13& j' W9 U$ h# h. u7 E
    对每克拉的价格分别按照分位数(q=[0.2, 0.4, 0.6, 0.8])与[1000, 3500, 5500, 18000]割点进行分箱得到五个类别Very Low, Low, Mid, High, Very High,并把按这两种分箱方法得到的category序列依次添加到原表中。
    1 _. f6 Q# T1 \: J4 x# retbins=True返回的是元组,第一个才是要的序列,第二个元素是分割点0 J& d# A. N# q0 T: T" d
    avg=df.price/df.carat0 c3 P; y* y: Y- {; Y7 R: y
    : ^, v; c4 S( v' T+ F
    df['price_quantile']=pd.qcut(avg, q=[0,0.2,0.4,0.6,0.8,1],
    ( d5 E; ~4 E# S0 k                              labels=['Very Low', 'Low', 'Mid', 'High', 'Very High'],retbins=True)[0]& t8 m9 A+ \/ j. A

    % Z9 I. d" u* ?' Edf['price_list']=pd.cut(avg, bins=[-np.infty,1000, 3500, 5500, 18000,np.infty],
    " {  {! Q( Z+ b# ?7 _: }7 |! ~, ^                              labels=['Very Low', 'Low', 'Mid', 'High', 'Very High'],retbins=True)[0]
    6 Y" p5 `! v1 p; [8 G3 P: v1 `df.head()
    ( R( o4 G) J- U- ]+ u  n# }5 ~- Y- r6 L% I3 G0 U
            carat        cut         clarity        price        price_quantile        price_list
    ) U4 M: t" k6 w/ b3 s0        0.23        0                6                326                        Very Low                Low
    . l% L# T- G3 z- o# U0 T( s1        0.21        1                5                326                        Very Low                Low* _, D, |; A" S4 a: l0 ?; ?
    2        0.23        3                3                327                        Very Low                Low
      a" C0 t4 K" B& Z6 z7 x" j3        0.29        1                4                334                        Very Low                Low
    : o/ R: r3 [$ u" [+ ~. h4        0.31        3                6                335                        Very Low                Low                                       
    * X+ a6 }7 U9 M" P8 A
    & c; w+ b4 K. E( Z4 r1& T# |2 |. A! c4 b: D* P
    2& m, ~, P0 ]  a  u1 |! Z% w& E! u
    3
    ' e" j0 [/ ?6 B2 s, a( H45 O" ]7 ?4 L- M5 |. }4 S4 K: d# _
    53 j7 g2 i" Z* u8 k& S9 d2 Y
    6
    ! O* t; `, X: f7% [& _3 V- P$ T
    85 K4 k  N; `6 M. q% e' N2 f9 R6 o! R
    9
    ) ^8 O1 r" d+ Z1 [, U/ c10
    ; f! w& d: W6 V! a) v$ o4 p11* y2 l) P# F; e3 b) \2 r6 A# {
    12! J  u! F" X; A2 q4 i7 C: G
    13, {9 F2 \3 o2 k# g
    14' _( x& Z/ n- N7 R, s; W; Z5 d
    15
    # ]3 T. F! _. V" C6 d16- J% W. m) ?$ ~3 |' N/ g
    分割点分别是:. ?, s) \% w1 J6 j4 ~
    ( }! u7 t. A7 i) j! d* i
    array([ 1051.16 , 2295. ,  3073.29,  4031.68, 5456.34, 17828.84])
    + q0 I. q$ _: o. e( q) D, Aarray([  -inf,   1000.,    3500.,    5500.,   18000.,    inf])* u4 G. [3 q* {4 `  i/ \; Y6 n
    1- s7 n  k9 ^9 e* A4 S" x5 ^" L0 s
    2. Q# x9 o$ ^0 U. ~9 p4 _
    第4问中按照整数分箱得到的序列中,是否出现了所有的类别?如果存在没有出现的类别请把该类别删除。" S! v( Q- E6 P6 e2 x
    df['price_list'].cat.categories # 原先设定的类别数
    + ~* i/ z& {5 M) C6 Y* TIndex(['Very Low', 'Low', 'Mid', 'High', 'Very High'], dtype='object')' p6 z/ P1 M+ I& {; k% Q

    2 u$ `3 Y! ^  B1 y, \! q( \df['price_list'].cat.remove_unused_categories().cat.categories  # 移除未出现的类别# m, q7 O6 D' O% J
    Index(['Low', 'Mid', 'High'], dtype='object')  # 首尾两个类别未出现
    2 N# d- W+ \! p: W1: v4 u7 A5 [6 m( F- q
    2% D& x8 s6 j" e* T/ s* t$ a) \
    3
    / o; ]/ n& [3 h3 B( N: }4
    ' _" p0 H) h/ k# f/ Z- y9 e5
    % Z, V2 i8 z8 \& a; n+ O7 }avg.sort_values() # 可见首尾区间确实是没有的- J( A* i% E$ j+ x' M
    31962     1051.162791
    8 @; a  E  w, A; F+ n15        1078.125000
    5 H; f8 O$ n, a8 H4         1080.645161
    : n0 V3 y/ y$ k28285     1109.090909
    9 I  Z0 N5 s* N& f" ^: @13        1109.677419
    5 u5 a& @& g/ o+ G# ~9 W# v6 f             ...     4 a4 J9 Q! V3 Z6 S
    26998    16764.705882
    $ y) W6 t, j+ @& A27457    16928.971963& P- Y$ h! o. N+ P5 ?: V8 J
    27226    17077.669903  t8 V5 {; X  Z5 H) O# ^0 v1 c
    27530    17083.177570
    ' W# V( ]* ]3 \27635    17828.846154  H9 t1 @8 C5 K1 U3 H+ ?: Y0 b
    1/ t5 K/ I3 a. r( e: [
    2
    - E/ T( b& M) x4 w7 g3
    + o: I2 ?2 ?. u- r$ ]7 ?43 L7 h$ M7 C" n
    5
    1 z1 K8 W/ N1 v) C) k/ ~% N$ _, Y63 a; U  k" l* H, N3 i7 v: W
    7
    ' S* l' m, O8 w; y+ f8, n  C1 [) Y7 }
    91 |* L% o0 @, _! n" D% O
    10
    3 l$ @+ s/ |7 r& W11
    # O8 Y# h, w) F$ e! d7 W; d8 L12
    4 G( b* M0 e  Y+ V# h- k1 {# G对第4问中按照分位数分箱得到的序列,求每个样本对应所在区间的左右端点值和长度。+ Z7 p" A, p( V$ N) X, I
    # 分割时区间不能有命名,否则字符串传入错误。
    5 j' t0 M' \  k/ z( ^. eid_interval=pd.IntervalIndex(0 ^6 Z; D4 X3 a
        pd.qcut(avg, q=[0,0.2,0.4,0.6,0.8,1],retbins=True)[0]5 V8 |/ ^1 Y& N0 ]. r; E% x' q
                                ), s! K6 P6 `" Y  c4 _+ y8 _( D
    id_interval.left
    6 P8 L$ V( ~; \  v/ Vid_interval.right
    9 `0 U+ ^8 H; Z7 c7 c3 e* J% fid_interval.length                            , c. ^8 `% z! q- \! M
    1
    " f$ v: Z0 [, [" R2, a% M2 k" i! O0 Q
    3
    ( p; B; B- d& B% x4" L& J2 a( T% j" n, b% V
    5
    " W& D# t4 a% n. h7 a6 }6
    0 O) t  U$ i: s5 x. j9 d6 u7
    # D. M8 B  h0 d' u+ G3 E2 k第十章 时序数据( d2 |; q$ d  b* R" w4 h$ \! r
    import numpy as np6 V  }  S/ _# X+ y5 r: c. i
    import pandas as pd; G/ U' P$ w* e9 M; {
    1! p: V# u+ S; G
    2( N! e; y) w  ^$ g( }
    , @! `9 m- i. n1 }
    " D* i! S/ a+ \6 S4 A
    10.1 时序中的基本对象$ U1 v2 k0 H, F5 y% _  n
      时间序列的概念在日常生活中十分常见,但对于一个具体的时序事件而言,可以从多个时间对象的角度来描述。例如2020年9月7日周一早上8点整需要到教室上课,这个课会在当天早上10点结束,其中包含了哪些时间概念?: W# j' P2 _7 n& U$ i7 q

      z) G$ N) Y" e0 T/ Y会出现时间戳(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的简写。
    $ \9 Z) R; [+ E  j0 K* X& x- y, l$ B+ N0 Z0 F
    会出现时间差(Time deltas)的概念,即上课需要的时间,两个Timestamp做差就得到了时间差,pandas中利用Timedelta来表示。类似的,一系列的时间差就组成了TimedeltaIndex, 而将它放到Series中后,Series的类型就变为了timedelta64[ns]。
    0 o4 B  X, m. ?& j. @9 L9 X/ o; a5 z
    会出现时间段(Time spans)的概念,即在8点到10点这个区间都会持续地在上课,在pandas利用Period来表示。类似的,一系列的时间段就组成了PeriodIndex, 而将它放到Series中后,Series的类型就变为了Period。" }( G0 P. F' `# p6 R  o
    ! ~0 K& \' y1 S, h% z1 m
    会出现日期偏置(Date offsets)的概念,假设你只知道9月的第一个周一早上8点要去上课,但不知道具体的日期,那么就需要一个类型来处理此类需求。再例如,想要知道2020年9月7日后的第30个工作日是哪一天,那么时间差就解决不了你的问题,从而pandas中的DateOffset就出现了。同时,pandas中没有为一列时间偏置专门设计存储类型,理由也很简单,因为需求比较奇怪,一般来说我们只需要对一批时间特征做一个统一的特殊日期偏置。
    ) Y' T- F$ `3 s: s% p9 r3 V
    3 z- k# T7 ~4 u5 {( M" P  通过这个简单的例子,就能够容易地总结出官方文档中的这个表格:
    " J- D  Z2 m2 E) \, {8 y3 b, q2 l. c1 C7 M: f8 L
    概念        单元素类型        数组类型        pandas数据类型
    8 k) a! h5 n2 P# V2 {Date times        Timestamp        DatetimeIndex        datetime64[ns]; ?% A# Q" {6 z7 Y1 ?# `2 N
    Time deltas        Timedelta        TimedeltaIndex        timedelta64[ns]- Q: P; R* f6 ]4 Z, z9 f( v4 S1 {
    Time spans        Period        PeriodIndex        period[freq]
    ! R0 ]( ?" }4 kDate offsets        DateOffset        None        None
    8 I, U) J- v( B, _* b; N) b  由于时间段对象Period/PeriodIndex的使用频率并不高,因此将不进行讲解,而只涉及时间戳序列、时间差序列和日期偏置的相关内容。
    . H7 i% F; G5 ?+ {- T, `4 p7 H# d& l1 b( v
    10.2 时间戳
    % c0 i) L6 |7 N9 h2 q" Y" y8 w10.2.1 Timestamp的构造与属性9 G+ v7 N( a& E! {% |
    单个时间戳的生成利用pd.Timestamp实现,一般而言的常见日期格式都能被成功地转换:+ V% _3 r# M! V& Z9 ?, ^
      F/ }% _" Z: b, g- U
    ts = pd.Timestamp('2020/1/1')( C( }# S7 t5 s( J/ l+ E

    # f% Z2 L( |8 B9 }  p) [ts
    4 A( H" j; Q' z# c: kOut[4]: Timestamp('2020-01-01 00:00:00'); l0 E1 O" r' V

    * @+ E7 u' [. K8 g7 {; x2 pts = pd.Timestamp('2020-1-1 08:10:30')
    / r0 M4 p, ~6 U+ \+ ?3 T1 y4 o$ @9 t2 X% R& F/ x
    ts
    0 l* y8 g2 u, R* q$ ^' A+ p5 POut[6]: Timestamp('2020-01-01 08:10:30')0 r) F$ a. P! W8 b. e1 \
    1
    - i. Z8 J, J- I$ O: a, d2( p: c/ R7 j  S5 {( s
    38 U5 l3 {* Q) V" c
    4$ D& C9 x6 f& W5 J* J, D
    5
    7 ^- y2 l( t3 c6
    0 E0 t4 p! Q4 `  a73 ]# b8 w! f5 @/ s* m+ C" D
    8
    , q) H: p/ L8 r( {9" t* v/ `7 P: h
    通过year, month, day, hour, min, second可以获取具体的数值:
    * h; E# C9 V0 p( A9 E  u; Z2 `
    % ~4 |$ N1 i/ I9 M, Y1 Q0 ots.year
    - x) W" q& O; A# A+ f* bOut[7]: 2020- v6 J; r: h! G4 z7 c2 e

    . _+ Y8 W7 g1 r6 o, m6 c7 xts.month
    & p$ i5 f( m4 w, ]: WOut[8]: 1! w7 e9 }7 f9 N  ?9 w; S' c1 b/ K: E2 m! {

    4 u6 s: L4 S$ G4 p! _ts.day2 a2 J9 C: O3 H. ]5 u4 r/ s, q: ]7 a
    Out[9]: 1
    0 y# K4 }, |+ o1 ?$ I; K. ^3 J+ u% o# ~4 z0 F7 |. W
    ts.hour
    * S8 F% }: O) E0 W0 c' GOut[10]: 8
    0 M5 i$ ~6 f, P1 d# \; @( N8 u0 p. A6 H4 v3 U" Z
    ts.minute/ i0 U% X+ u3 I# g- U' F+ I6 v/ G
    Out[11]: 10/ D4 _0 k7 K. v1 Z% t& {

    * g* ~1 z5 g0 u* g, ots.second, v/ u$ l. d1 z
    Out[12]: 30
    # O8 D4 k8 I: i0 x6 Y
    5 `6 z4 G( U1 \; z$ S3 V# ^1
    ( {# e# S6 T) Z* Q4 H$ ]2
    $ `- r8 n* {9 M2 j6 K- `3: ]6 L. v! O& }* O
    4
    " V" O, w  a& p0 I& ~53 o, }  l9 v# w0 ?; }" F
    6
    # ~" m+ ^. J# p. S6 Y; [7- q. K$ I0 L: W
    8- w/ R* {% U( }
    9
    , u& l4 q' N; \$ E& {4 N) j107 C4 _9 N6 v& R! x% G' ^: A
    11
    2 K' M7 v9 \2 L* Y  Z5 [12. z+ g/ M& a. R: c& v' L
    13! H( G& [# Y4 x8 j- K: Z
    14
    5 k' q2 B2 H) X8 m, {4 w" X15, d# Q. q6 h3 H( }* K0 R+ C) o
    16, u! X, L6 r9 m+ r
    174 ?* r1 l# A1 K; L* c
    # 获取当前时间' S# Y5 N# q# @
    now=pd.Timestamp.now()% X! `8 X! p. o( L  k5 u1 J& ]+ k
    15 J; S/ U' q: \( {  J
    2
    * D! d! D# g3 _+ N+ H在pandas中,时间戳的最小精度为纳秒ns,由于使用了64位存储,可以表示的时间范围大约可以如下计算:
    $ c1 r( G$ R: K  x( X. t- c  `  QT 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)6 }$ |4 w. Z3 q% J) O0 q4 ]+ N2 p# j
    TimeRange= 8 Z# k4 [9 E# [% q' K' {
    10 , Y- h" l5 e( ^2 z7 [
    98 I0 c" }- |6 L& o  T$ y+ T2 |5 U1 @
    ×60×60×24×365
    6 K& E$ C. y' B2 3 q" d! b) E9 D0 n# S
    643 h, C1 [, P) \, Y3 K; }

    ( V2 ]# T7 r9 L  z​
    # c) i7 P5 o2 [0 | ≈585(Years)
    ; D9 _+ _* ]0 t. y
    , U" x3 z! }. }4 j# C* s# h通过pd.Timestamp.max和pd.Timestamp.min可以获取时间戳表示的范围,可以看到确实表示的区间年数大小正如上述计算结果:
    8 d, n& W; P; z8 E3 N; A' j) d  f$ q5 h, Q+ P( V* ~
    pd.Timestamp.max
    - J9 P) {2 i1 W. @$ z+ ^2 T' V8 KOut[13]: Timestamp('2262-04-11 23:47:16.854775807')) T1 _. G# ]2 D3 [+ x1 l, {: Y
    * @  V. Y+ j: s
    pd.Timestamp.min6 a. `) e8 |1 W" H7 @
    Out[14]: Timestamp('1677-09-21 00:12:43.145225')+ z& s0 ]$ D  U' a

    1 A; m$ n. f- \8 `$ npd.Timestamp.max.year - pd.Timestamp.min.year7 u' a4 `# a4 _+ P5 _/ D
    Out[15]: 585
    2 K; Q3 T  Z, E) h1+ h' z3 h3 w4 H
    2
    % h5 T- x: W# P: \: ~  e+ v3
    ! g2 Z7 _* K' C% n/ E( ?4
    ( y+ h6 r9 o* Y3 r5 R2 |' h# j3 Q8 l5
    4 s, S0 k; r3 z4 P  {62 G2 |' ~8 x, ~8 h4 u3 D$ |$ E
    7* v& |5 Q  \& d9 D. {0 F8 C) K
    8
    ) G( s5 m* E0 g  O10.2.2 Datetime序列的生成
    # f  [0 C9 P9 b. vpandas.to_datetime(arg, errors='raise', dayfirst=False, yearfirst=False, utc=None, format=None,0 b+ b$ }% a; i$ R0 h8 r2 |$ F) O
                                      exact=True, unit=None, infer_datetime_format=False, origin='unix', cache=True)9 m. L" ~4 D8 d9 j5 i, t2 k3 P
    1
    4 h. F1 s# u4 o, `9 P) `2
    4 k& c! X5 v( \, d0 r  A( Opandas.to_datetime将arg转换为日期时间。
    : s! [$ O+ M9 p% P( S6 P
    1 n6 S% m0 ]! ~2 rarg:可以是argint、float、str、datetime、list、tuple、一维数组、Series、DataFrame/dict-like等要转换为日期时间的对象。如果提供了 DataFrame,则该方法至少需要以下列:“年”、“月”、“日”。7 A' S7 r7 q6 F
    errors:
    5 y7 ?$ _* @8 n- I. W, G- ‘raise’:默认值,无效解析将引发异常4 e" I/ n6 a- b0 N
    - ‘raise’:无效解析将返回输入9 e3 U1 ~5 |6 N) j
    - ‘coerce’:无效解析将被设置为NaT
    ' e4 U; M/ W' Q7 r: ^dayfirst:如果 arg 是 str 或类似列表时使用,表示日期解析顺序,默认为 False。如果为 True,则首先解析日期,例如“10/11/12”被解析为 2012-11-10。如果无法根据给定的 dayfirst 选项解析分隔日期字符串,会显示警告。
    ; e3 J* q% p, @  S- @4 Eyearfirst:如果 arg 是 str 或类似列表时使用,表示日期解析顺序,默认为 False。如果为 True,则首先解析年份,例如“10/11/12”被解析为2010-11-12。无法正确解析时会显示警告。(如果 dayfirst 和 yearfirst 都为 True,则 yearfirst 优先(与 dateutil 相同)。)
    ( y8 ?: z1 ]2 d/ J( i7 E2 rutcbool:默认None,控制时区相关的解析、本地化和转换。请参阅:pandas 有关时区转换和本地化的一般文档
    " ~  L  j+ L  H  P) C6 h- E/ ~format:str格式,默认None。时间戳的格式不满足转换时,可以强制使用format进行匹配。
    , n; Z5 y. Z7 G; V" Dunitstr:默认“ns”。它是arg (D,s,ms,us,ns) 的表示单位,可以是整数或浮点数。这将基于原点。例如,使用 unit=‘ms’ 和 origin=‘unix’ (默认值),这将计算到 unix 开始的毫秒数。
      d; o( z7 ~7 w2 q( @" l# Jto_datetime能够把一列时间戳格式的对象转换成为datetime64[ns]类型的时间序列:% X; K) k$ J9 `8 p! V
    pd.to_datetime(['2020-1-1', '2020-1-3', '2020-1-6'])
    ! f0 s/ v* g+ _$ ~) ^  H# @4 o  }; F& e7 T2 g% r3 ?
    DatetimeIndex(['2020-01-01', '2020-01-03', '2020-01-06'], dtype='datetime64[ns]', freq=None)  \: O: P) u6 K
    1: s+ S$ [# r1 j7 `: x
    2
    ' W5 K$ T( ]" ]: B4 f35 F+ I2 c. F3 e. x1 ?
    在极少数情况,时间戳的格式不满足转换时,可以强制使用format进行匹配:
    ) I( v, e% P7 T) ]1 R; ?3 a. U( @3 q6 R& W8 }+ D0 ?0 X
    temp = pd.to_datetime(['2020\\1\\1','2020\\1\\3'],format='%Y\\%m\\%d')! y; w% D( `7 k1 P
    temp
    & @6 y% Z$ [5 T) }- _/ [4 N4 V0 d, F2 k+ Z5 a) Z, H  G
    DatetimeIndex(['2020-01-01', '2020-01-03'], dtype='datetime64[ns]', freq=None)
    ) ~$ X' c7 Q' K1 Y3 Q3 \1
    ; z8 X5 u5 e- s4 r2
    9 k- |+ a* O" D5 |: H0 _3
    : X& E; x- ?9 ?, L; b; J4
    ( t5 A* A; D, C2 D* u; S6 f. @  注意上面由于传入的是列表,而非pandas内部的Series,因此返回的是DatetimeIndex,如果想要转为datetime64[ns]的序列,需要显式用Series转化:, b0 i3 v5 p3 x9 r% q! a! z; h% Q
    9 n  E7 M1 Q- ~7 r$ {; u& v, C' h
    pd.Series(temp).head()
    + n5 o( g+ m' ~6 J8 G! x* I( u# U8 b
    0   2020-01-01, \1 m, e" F5 Q1 ~2 e& B! x
    1   2020-01-032 b* [/ n% C  u2 S4 M- V* A* o4 m
    dtype: datetime64[ns]+ q/ |  f( \* a
    1
    4 S) B5 u: ~1 V' o$ S2
    : b. ^6 d1 V! u3
    6 {+ V- j/ i: |6 E/ B4
    6 `! c3 ~+ p" j  U5
    ) d) h4 ], i3 I' Z/ }; s下面的序列本身就是Series,所以不需要再转化。& S, K9 \% f; u. X

    9 M5 L/ k7 s: n! vdf = pd.read_csv('../data/learn_pandas.csv')+ |: h+ X5 Z: V5 j
    s = pd.to_datetime(df.Test_Date)
    3 [* [' ^! Q, s- j5 W- N* `1 [  [s.head()
    9 o) T9 z: `4 T( o" r
    # Y$ c" B$ R7 \3 ]5 L0   2019-10-05( x+ z4 u; c; c/ T0 U
    1   2019-09-04# V* O, j5 ?' O4 D5 D3 K# X
    2   2019-09-12
    7 P2 A4 g, B* X6 O. v0 _, n' Z3   2020-01-03
    ) s+ F0 \2 e( Q% T. |$ b' y4   2019-11-066 x$ W9 _6 Y- X" V
    Name: Test_Date, dtype: datetime64[ns]
    5 Y6 Q5 [9 g! d6 w" c- _% B0 ~1* U, r3 k$ [: A* a/ ]" O; |
    2
    ; [# k4 S# y7 q) h3
    # l" i% N8 W! N) u) j' q- j3 r4! m% h# e8 a5 ^7 z7 Y8 c
    51 \9 G, w; C# g6 P; P% W
    6' t9 B$ O: V, ]
    78 K/ k) s1 r* z3 K
    84 C' W" T1 c# l" F1 C6 ?" N2 [
    9
    $ ^0 a$ y" ?* \/ [7 y' c* f10$ w1 W( P5 H" A" F- s% h
    把表的多列时间属性拼接转为时间序列的to_datetime,此时的列名必须和以下给定的时间关键词列名一致:' t2 O( P$ @5 O
    df_date_cols = pd.DataFrame({'year': [2020, 2020],  ^) U  a4 j1 H
                                 'month': [1, 1],
    ; a( l5 t3 p$ i# V( Z                             'day': [1, 2],, j' z" E( B5 W) u3 t* m: y0 j8 p
                                 'hour': [10, 20],
    2 W4 V1 ^' M& S8 S9 x! g  }" T6 y& p                             'minute': [30, 50],# n7 k8 g, A3 S7 R
                                 'second': [20, 40]})5 a6 l6 x+ d% s8 v
    pd.to_datetime(df_date_cols)
    $ d& [' V/ c) ]5 m1 T4 Z1 s6 L, c$ u1 z9 e- b$ ^
    0   2020-01-01 10:30:20
    ) \1 n  @9 N, J, _5 ]0 s; o2 C0 z1   2020-01-02 20:50:40
    ) j$ J* M: M- u% w/ n# Y- idtype: datetime64[ns]7 Y# B( t8 ?% e0 m8 Z' C
    1( J& e* Y0 E: H4 D% m* X: l
    2! f6 B. `2 {9 C) F
    3$ Z* l2 K, Q! v$ ~
    4
    1 U  {' v# h$ O5
    ' ?- n4 B# z& c+ \61 Q! e" F5 e/ ~/ ~; R
    7: B4 o3 c. O5 p, B: D$ V. @
    8$ t5 j: \+ J0 h" H4 v% c
    9# \3 W! R2 p' }8 a& V* y
    10
    * D# V: T% T: v# E4 q11
    3 y% w  z- V0 O6 p1 Bdate_range是一种生成连续间隔时间的一种方法,其重要的参数为start, end, freq, periods,它们分别表示开始时间,结束时间,时间间隔,时间戳个数。其中,四个中的三个参数决定了,那么剩下的一个就随之确定了。这里要注意,开始或结束日期如果作为端点则它会被包含:
    ; k) S/ u, w( j) g9 Kpd.date_range('2020-1-1','2020-1-21', freq='10D') # 包含
    0 O7 R1 u/ Q8 V9 m/ Q# H% FOut[25]: DatetimeIndex(['2020-01-01', '2020-01-11', '2020-01-21'], dtype='datetime64[ns]', freq='10D')
    3 W# j( V; I) `8 u2 c! X3 I
    3 G, a0 B+ U8 ]2 f: Y1 M$ ~2 m, k( spd.date_range('2020-1-1','2020-2-28', freq='10D')" [5 Z- n4 l: i! M
    Out[26]:
    8 l6 X: P0 \$ u& {/ x) @DatetimeIndex(['2020-01-01', '2020-01-11', '2020-01-21', '2020-01-31',6 j/ u$ s( n, f; K, ]
                   '2020-02-10', '2020-02-20'],4 N1 |' T) O  ]8 V4 o1 e
                  dtype='datetime64[ns]', freq='10D')0 {- d# o( C. m* `* H( ~  Y

    4 I; v: e/ i4 q! U9 bpd.date_range('2020-1-1',6 N' F! F! F. O* [. H2 r$ |2 {
                  '2020-2-28', periods=6) # 由于结束日期无法取到,freq不为10天, `" G4 o! U+ N8 V

    : D4 s! b7 h9 v+ UOut[27]:
      [, r! A+ _* |0 Q  q) J* CDatetimeIndex(['2020-01-01 00:00:00', '2020-01-12 14:24:00',
    ! O4 P0 i+ [9 |9 I) _- t               '2020-01-24 04:48:00', '2020-02-04 19:12:00',' D; Y- d, P& e; N, n6 _7 {! \
                   '2020-02-16 09:36:00', '2020-02-28 00:00:00'],0 _6 y) a* r2 i
                  dtype='datetime64[ns]', freq=None). @$ q! Z2 I) `: |4 w
    0 A) ~0 a$ a1 I% x4 b' Q/ P
    1( F" W$ v4 U' p& e( T. Y& S
    2+ f( X4 N  k3 X: u  b2 u( _+ ?
    3. y7 _( t& p* s5 x( Y
    4
    % A0 a' a6 B/ J' O$ M5
    ! N: e/ Q. D0 }4 f! ~6$ X$ Q) r0 F6 ~( P6 x; v  ^
    7. Z, S0 l9 s( @4 \4 G! \1 ^
    8* N2 [3 \, h8 W; h+ r9 [7 _
    9% a8 x, E$ n: D4 ~
    10( O# [/ n0 X' i; @
    11
    " T. f8 K% F8 ?+ T5 V0 Q12
    3 S  @1 w$ \* n1 U" Z13
    1 X+ }6 V- U6 ?/ o" F4 n144 L2 N! |' s1 v4 G* [3 j$ r8 b' H8 G
    15
    # \8 {* d3 h& t4 k! g) K) W6 @* u16
    & q1 Y7 m) T# ?8 n+ k17
    2 b: D; {: J% v9 K这里的freq参数与DateOffset对象紧密相关,将在第四节介绍其具体的用法。3 i0 v# Z2 W# v8 k7 G. M" p8 ?/ P
    * b) y1 {* s6 N
    【练一练】
    % N7 _: ^3 H& k! t% D9 nTimestamp上定义了一个value属性,其返回的整数值代表了从1970年1月1日零点到给定时间戳相差的纳秒数,请利用这个属性构造一个随机生成给定日期区间内日期序列的函数。
    0 R# k% Z( q3 b7 R- d8 h) u
    . ]+ u* Y" X8 }) G; |ls=['2020-01-01','2020-02-20']
    ' u  O5 e) y* p6 n1 m' @def dates(ls,n):" L7 V8 R* B5 w& Z( D& S
        min=pd.Timestamp(ls[0]).value/10**9
    ; @* ?6 Y  U, S2 m8 ?& {8 O$ ^    max=pd.Timestamp(ls[1]).value/10**9
    ( R. W, u$ M% R( `- |    times=np.random.randint(min,max+1,n)
    1 O1 B) s) c; K1 }" y6 u& X" i) l( n    return  pd.to_datetime(times,unit='s')# s# W( V$ x. W+ E% V
    dates(ls,10)
    , d4 Q5 H8 F/ N8 k/ n( Y! F. Q' T9 o0 w1 r& F7 R- I& m+ y
    DatetimeIndex(['2020-02-16 09:25:30', '2020-01-29 07:00:04',
    7 d9 V) i) P( r2 n$ \               '2020-01-21 12:26:02', '2020-02-08 20:34:08',
    " G8 t+ X6 `" e* R3 Q               '2020-02-15 00:18:33', '2020-02-11 02:18:07',
    ; D: q9 Z$ ^2 U0 ]9 n               '2020-01-12 21:48:59', '2020-01-12 00:39:24',1 R2 r, w3 F8 f$ ?4 z9 R
                   '2020-02-14 20:55:20', '2020-01-26 15:44:13'],
    ' t$ l& w2 E$ `2 T2 m              dtype='datetime64[ns]', freq=None)! W- A6 r2 J. E& B
    1  ?9 z# I8 Y5 g7 s1 p; Q
    2
    ' q* l2 i- W. j! u( W& u* x4 H$ C3" P) C% ]$ j7 Y9 S- B7 H$ z
    4% ], j% y/ ?5 q& A5 e1 P
    5# M" p" U/ E4 i: v! W- |# B& e6 A, B
    6
    7 S1 a) z# e6 e7
    1 T& O2 [! A9 |1 p# }# R& C8- R9 ~% t, J* X
    9. x- ?% ?2 D. C6 x$ k
    10
    ) `1 @& \  H' U$ q/ }# t114 d, W) W& Y0 K: o) {$ ^" N1 d
    12
    9 b/ I" K6 B. w, Y0 Y134 O9 P+ U5 y/ q3 K1 r" W% R" H
    14
    ) r% X8 S, I" t1 M5 Oasfreq:改变序列采样频率的方法,能够根据给定的freq对序列进行类似于reindex的操作:
    % e, l# i- L$ p% ks = pd.Series(np.random.rand(5),
    ( [1 @! u6 |! @+ j            index=pd.to_datetime([* w- {% p  t. m' A2 }
                    '2020-1-%d'%i for i in range(1,10,2)]))
    5 U0 t8 m* m  m% t# U0 H& G5 }5 d" |; w1 l( O$ T& o
    ; X" m1 w0 `4 y8 W. Q
    s.head()
    ; ^2 ?4 J; f2 i- L3 x  zOut[29]:
    / t3 e9 B( N4 a# M2020-01-01    0.836578
    - |* W+ U# v4 n' v% a2020-01-03    0.678419
    ; l( M+ B8 ~& P2 m2020-01-05    0.711897! t7 G2 Q& p- `9 @
    2020-01-07    0.487429* {% ]/ V" ]! U
    2020-01-09    0.604705
    $ t& L: S9 t. X9 b% cdtype: float64% K5 m) ?3 i, I/ ~4 E# j5 M  Q& k  r
    + m+ Z: g7 y& u# R% y7 K
    s.asfreq('D').head()5 q% |) P+ O$ b/ t
    Out[30]:
      f7 H) y+ q- ?/ q8 c2020-01-01    0.836578
    ! f: V! J1 a) ~2020-01-02         NaN+ v7 N6 u( [; q  R7 d# K
    2020-01-03    0.678419
    ; \: F  J9 o: k& N2020-01-04         NaN
    , X7 J$ J4 O$ o( s0 Z0 n. h2020-01-05    0.711897
    8 A& V/ f1 X* n: u5 P2 JFreq: D, dtype: float64' C5 n. p8 u& F" b4 `" f

    & V' M7 \+ s& T. ^! I1 Ks.asfreq('12H').head()0 M" K0 @4 x1 j) e* g! Y7 g
    Out[31]:
    ) K& \6 ^3 b% B) h2020-01-01 00:00:00    0.836578/ r( G" O1 l1 v; s- B
    2020-01-01 12:00:00         NaN; v( P  h' f6 P  l
    2020-01-02 00:00:00         NaN
    . T6 |' Z! a5 J' g2020-01-02 12:00:00         NaN6 C/ J  o: f( H$ z, K' l; O
    2020-01-03 00:00:00    0.678419. G1 f* d) P. B2 p; U
    Freq: 12H, dtype: float64
    4 l2 n+ {6 q" V( x. j- \
    7 ?" O1 \' G. F2 Y, u1- I  w% w! y" c
    2
    2 Y! a# d' s# d! }3
    6 i' q# F' E0 p; c4 W) B. D9 S4
    5 m( o  P* f3 p/ K$ X/ P55 }, j3 p7 ]6 P8 k, k
    6
    9 C  D2 f; L- S7) O# d5 w- v: X- `4 \; |
    8
    ) M8 }7 S* o- O/ \2 K. Q2 U- n6 E9
    , a2 D2 k$ k7 F* j1 C& X1 l10
    1 q3 n9 w+ ]9 |) r- z8 e3 Z) J& ^11: o- e9 v4 G/ c" @+ m3 h6 H
    12& A0 a/ c: g3 k8 y* f0 ?3 o* |
    134 g& {5 `9 {; T, y. S' H
    14/ ^% T! r) O  W
    153 d8 q# p* B  E! U
    16& {" }( D, m* q
    175 L9 g1 L) j4 ~  @0 Z
    18* I: y. w7 \  n* S
    19
    9 y2 u- Q, H, \/ x3 D$ R20$ x. ~4 B+ w/ f4 ]1 W; p4 Q
    21( o$ N* d! _$ P# v* F7 a$ \' v
    22+ W" V+ R  e) L/ ]: o! t
    23
      C9 N) {) V2 k! I/ u24
    ! s& J5 }) D3 P* Z25- B5 M; @# i9 B9 ^& Q
    26$ |3 z! _% f# b" Z, u. F
    27
      D0 L0 ~3 k) m$ J! c28$ v9 t8 h: v7 l) ]' G0 l
    29' S# j: N/ g) g" m1 y+ k! |
    307 x. I6 S: E; s5 w
    31( B4 N4 V5 T) n8 p3 ~# P( X
    【NOTE】datetime64[ns] 序列的极值与均值+ H9 _7 I, F( N4 j$ y2 U
      前面提到了datetime64[ns]本质上可以理解为一个整数,即从1970年1月1日零点到给定时间戳相差的纳秒数。所以对于一个datetime64[ns]序列,可以使用max, min, mean,来取得最大时间戳、最小时间戳和“平均”时间戳。* N5 u% h! {/ {7 w

    5 A+ L5 o! M" B1 d. ?0 R9 u10.2.3 dt对象
    ) t  T' c0 n8 @0 F/ p8 f  如同category, string的序列上定义了cat, str来完成分类数据和文本数据的操作,在时序类型的序列上定义了dt对象来完成许多时间序列的相关操作。这里对于datetime64[ns]类型而言,可以大致分为三类操作:取出时间相关的属性、判断时间戳是否满足条件、取整操作。. B. D% o$ z# v* _% l' y: \# w' D2 p

    / L& J0 a! d" K: o" ~  n1 J; k第一类操作的常用属性包括:date, time, year, month, day, hour, minute, second, microsecond, nanosecond, dayofweek, dayofyear, weekofyear, daysinmonth, quarter,其中daysinmonth, quarter分别表示该月一共有几天和季度。
    : P3 z- l1 u2 n1 }s = pd.Series(pd.date_range('2020-1-1','2020-1-3', freq='D'))
    ) T5 b7 u  [2 P1 J2 k9 |* V" Y# j; B# i
    s.dt.date$ s" z) n$ t  f" E5 X9 _" _
    Out[33]: & W. X7 P% L2 |# v( V' b* c
    0    2020-01-01
    " I! l! \/ C9 X2 o1    2020-01-02
    - u, x' S( C$ t6 x5 X2    2020-01-03- [. O2 Y5 a- u7 ~7 }
    dtype: object7 d6 F" W1 j0 c0 p# S, P* b- @$ _

    ; ~/ @! b% Y9 E0 [$ ss.dt.time
    / b) R2 p! _; y5 bOut[34]: 0 d+ c1 d' W8 |! `
    0    00:00:00
    0 w, K- @+ d$ T6 a# o0 S& k1    00:00:00+ D: n. B) c0 ^7 h. C
    2    00:00:00! C3 G6 e0 \7 f
    dtype: object
    , u& o" _$ @8 C5 w; B
    9 g0 V5 t8 J  h! V" xs.dt.day6 W6 a! u7 s- T$ F7 c) H' V
    Out[35]: 2 u: Q2 q! y+ [/ c( B7 U
    0    1: ^8 C, R8 `6 x0 ~
    1    2
    4 E; W. t, t2 t2 E+ S! Q2    3
    " K# ?$ y  V! Q+ n3 O. F9 i+ R) c2 {dtype: int64
    0 }5 H1 s, s! U4 @: T# @4 G' l/ y
    s.dt.daysinmonth: J" S) B# I' V9 ~% [3 }
    Out[36]:
    $ a" t  N. s1 L+ u0    31
    7 a8 c; h7 w" c8 N. K1    31
    9 r- N) \/ h. ^2    31  X; W3 D! J% m% V2 y6 S* M' `5 X
    dtype: int644 d1 x9 |+ l' H1 g" H* Z  y
    6 G( N: J6 u) U/ [! C
    1% X- L5 E. O) G. n* j2 [: [
    2, M& d) |6 r5 C7 A
    3
    * a( e9 |# G  ]# |* y* {4
    6 g0 X8 i5 z' i6 h1 P! q8 y5
    ! \0 p7 i$ k! V0 b$ y63 @6 A3 y+ L$ U2 I, v3 s
    7
    . s. q- M7 n6 J( \$ z' w! K: d81 x' S  l% H& i* }# g
    9- m% X& Y6 `/ N* r- n/ R
    10
    % r7 n8 T* |( x/ G8 o# A11, _& |; Z) v$ i% x, h) K
    122 Z0 e# ]) D" s2 {3 c4 m, d
    13
    . G  w0 ^$ k: u8 ~7 o0 P143 [6 g; R+ X. Q1 U' ^9 \' X
    15
    3 L, k/ `) @" O* t0 f7 P16
    : H% f0 t3 L% K0 m17
    # c5 Q/ r: D, a. x18
    ! p+ w6 Q( J% n# l  A: n) L19
    / S5 G: Q* C! x9 I1 B; g! Y2 B200 |/ y+ x& w+ b- U, q
    21% D7 M5 u# @) v; m1 E+ Y) C9 h
    22- w5 m' x4 d* g
    23) s+ J+ x- r0 i% \2 z
    248 E  B8 y& p8 d% Q
    25
    0 l8 V- ?3 t: \" X8 D2 |. r26
    % R) Q3 g$ u2 k& l6 a27
    4 Z' k  q$ g# s3 x! {% N28& r2 q: D' O8 H; S9 b
    29# F, g8 y  M  w: M+ R( u9 ~. \% w
      在这些属性中,经常使用的是dayofweek,它返回了周中的星期情况,周一为0、周二为1,以此类推。此外,还可以通过month_name, day_name返回英文的月名和星期名,注意它们是方法而不是属性:2 w& g, h4 R1 W) D7 ]' V
    9 X  a# z! ]" g! J) H
    s.dt.dayofweek2 u. f1 w2 w/ i: S1 c0 O
    Out[37]:
    / @7 I7 P: C- D) l. [  T0    2
    % R1 y0 U8 Q% v8 D5 s% f$ S3 X* g1    3* ]7 }; M- x) ]2 u
    2    4' |  B8 b, }5 E& \" q
    dtype: int64# T, k- |1 V3 E: S0 ]" h

    2 [  I/ e" D5 M" is.dt.month_name()7 f5 g- d  H/ e2 h
    Out[38]:
    " ~! q: B8 d/ g  {1 q' Q0    January
    7 X9 W) z0 d1 l7 l' V' Y1    January
    0 k+ a; F; x4 U( l5 B8 I2    January
    / h3 a5 Y. Y$ `. S  l9 ydtype: object1 _, w' x3 k- a1 @: c7 M
    & e  A' }* t5 K0 [' N4 I4 M7 b
    s.dt.day_name()+ H% A, v9 j' i% |' r: L3 x
    Out[39]: . |3 u& N0 z' A
    0    Wednesday
    3 v$ H# v! w5 r6 c! O1     Thursday; _  ^# Z: b0 M) `3 d/ t" N
    2       Friday. o/ A: J* O" ^$ ^2 Y) W% I# P
    dtype: object
    ) k  ]1 T  R1 z! s- J) _
    6 h' |1 A+ p- t3 Q5 [, `8 c1) H6 L9 Z; P% {! X
    2
    4 o/ s: \1 Y: r$ J5 y& e. j3$ x6 a6 s1 n9 d! T: z( ~
    46 _5 N+ h" |6 f" l, v9 M
    5
    0 l$ y) I# C0 b6 l  }- T* {" U! N+ N6  U$ @- Z7 g% Q1 j
    7
    / R( f0 J+ `) R$ a1 V8/ K0 I1 M+ o$ k' G; c( c# `/ ~
    9( g+ v; \9 C; d
    10
    ( B7 S9 b7 t, [% ?11% J' P2 M, S, d4 I/ S* d
    12
    1 W- k( o$ B/ E5 b- a7 C6 E134 p% V. d% x  n, ~; \
    14" n$ _: b+ k& k8 q
    15' J9 n: }. b2 v" B* S; E' F1 o; w
    16
    - _, {+ h8 I  w: a17
    6 Q) t9 k+ |( ^' C18$ ]5 s. x. J4 U  T8 a9 |' \
    19
    * g7 S" C- j% v9 M' ?% o0 p( q20% j4 G; N/ D6 w
    第二类判断操作主要用于测试是否为月/季/年的第一天或者最后一天:9 s# R5 T4 A* k# \2 _. W) a" d
    s.dt.is_year_start # 还可选 is_quarter/month_start
    / K& d5 P* Q+ s1 {+ b0 j0 vOut[40]:
    ) u. J+ Z2 K1 j) N, W& a9 t# w0     True) V8 ^" i$ I. w8 O. k
    1    False
    / [! k5 N9 a* S. a3 o; i3 C2    False
    1 r5 j* [- k! ?% S0 ~3 Ldtype: bool
      t! Z/ Z2 k8 }8 l4 U! t1 k" {3 X( E& ^5 Q! N% K+ `) Z  Q$ j: ]
    s.dt.is_year_end # 还可选 is_quarter/month_end% f% _, d  ~! _2 ~
    Out[41]: 0 W3 U8 R1 C" q* J! l
    0    False
    4 V& A# {  Z, V) R  w. f: f9 }& Z1    False/ ~, _0 }  g. q2 |4 Y7 t
    2    False( ]2 ]7 k2 |2 P1 Y: X' R0 o
    dtype: bool; h/ n) C* S, ?: h( X% v  b4 l
    11 Y3 R, [; W* ~, c( J( s
    2# |" f. @8 `) K5 z
    3  E$ d0 f3 Z# ~1 ~% M# U$ b
    4$ X" Q+ C( ?! R, H
    51 s# ?% k8 b) _- P; Q2 m% u
    6
    0 k; c4 ^% V. k6 A' @/ t7 ^7
    ; j; l2 H3 T( G% ]% L7 M8
    8 _4 ^* J# O  Q, t- L9 s( T6 c95 T; m- r. r# C+ U, y# q; O0 ]
    10# J( ?0 I/ K& _) T0 Q$ w. E- L
    11" ?& C  j0 y4 ~* U0 h4 V
    128 Z; D1 D' m& b: E! H% w2 a- h6 z
    13
    " u7 W- D: i0 I& S第三类的取整操作包含round, ceil, floor,它们的公共参数为freq,常用的包括H, min, S(小时、分钟、秒),所有可选的freq可参考此处。
    ; `7 l8 e, @: Ls = pd.Series(pd.date_range('2020-1-1 20:35:00',
    , e& ^& R8 [" x  \; n                            '2020-1-1 22:35:00',
    " M& ~* c& r+ k" t0 m6 ~( d                            freq='45min'))
    ' e4 u" Z. h0 @% Z! n3 H3 R
    , @6 A5 I- B/ S
    . Q7 n( B' \0 is) Z2 F& i: b% S8 ]
    Out[43]: ' C! Q6 j8 w1 l& s. L5 B4 n
    0   2020-01-01 20:35:00
    4 A, T$ r+ [9 E" I1   2020-01-01 21:20:00
    ( c: k& U+ \3 e" L2   2020-01-01 22:05:00
    ( A' G5 _: e: j0 Edtype: datetime64[ns]
    - @$ J; `# T' X! H' d, V9 s5 U/ J2 K1 D' H& `+ ?5 ^
    s.dt.round('1H')3 v6 d, O; M3 ?
    Out[44]: 9 o3 o" x6 J6 ?# `3 k& ]6 d
    0   2020-01-01 21:00:00
    8 B3 D$ f" U& N+ W4 M1   2020-01-01 21:00:00
    # r% k* d; z9 N4 W1 S, ^& N" d2   2020-01-01 22:00:003 Q( X$ O4 o' g5 ~
    dtype: datetime64[ns]8 v; z$ p" y$ `" ?6 {: c" f' Z; h
    % Q5 H( J" k& t2 h* M& M" {/ [
    s.dt.ceil('1H')
    5 }& C( a# c3 f, k& `; f8 Q# ?Out[45]: . d/ @9 A' y  S) y: B; ]# K
    0   2020-01-01 21:00:00
    ) }) g2 a0 u( G( L, G: y2 t* s1   2020-01-01 22:00:00
    * _0 t! W; A* W# U% y  J2   2020-01-01 23:00:00# U& [; S0 U+ z# I8 G% F: N
    dtype: datetime64[ns]
    8 \# m6 B) ?; V) ?6 x7 h6 W5 {4 u% C% V
    s.dt.floor('1H')$ e1 X# _- F% h! u+ ~
    Out[46]: 6 k, p; A0 j* D. N! R5 S/ e
    0   2020-01-01 20:00:00
    # ]: i6 ?: k. X0 |- x2 j1   2020-01-01 21:00:00. v* [8 B1 a; K, q- |
    2   2020-01-01 22:00:00- x* d+ }8 d9 L# `* J
    dtype: datetime64[ns]0 n+ O& ~- X9 S

    1 N' N3 \, K; V1! O( D( i( W+ I7 ~
    2
    3 R0 T: Z- ^1 l. g3
    2 Z% P+ T. D  A45 c: w1 T0 \2 f- Y9 ]
    5
    8 @6 n! X& ~6 O2 `# g% |9 e9 o3 A) |6, e3 W9 s, o+ o4 J
    7+ @& h# L- c# b7 y0 J- D
    88 I' I& d  {. x/ D9 o) p' b
    9. v( k+ t5 @) t( `' _
    10
    , ~+ w/ ]" K0 q5 S# {11( {5 o  @; T7 K, D: [
    128 F1 {; z0 Q6 s0 d' c
    136 ~6 {) O4 F! F0 H8 W% e3 y6 r
    14
    4 M9 ]& M7 x% P* p6 Y  v15
    & j# c6 s% _; s, W+ [% ?  m" f16
    ) P1 n! G3 m% d17! ^& |: \* ~0 w9 j& o8 N2 L
    18
    * X0 c' i( d/ J. k6 i19% U0 j8 J# b2 a" ~
    20
    , A4 F. Y% V0 w, I& {. L21
    6 W9 T/ h* t+ ^0 p7 ~+ _22
    6 }$ x0 i4 I7 p. {* P23( N, V9 w7 K4 q$ d8 k3 r( \( r
    24; r4 m9 W+ R! k
    258 {; `- m0 ~& ]5 F. h
    267 x& ]5 n  D- V5 g) l1 d+ }- c
    27+ Q" D% R( G" o. @* F6 ~
    280 j2 C8 z. h' Q- f3 @7 ~0 \
    29% B# |( b! _1 E5 E. @
    30/ ^% r1 j6 b3 v3 W6 t3 E7 w
    315 A6 @/ a, y7 H/ X& s( Y
    32$ R2 J* y4 G! N, U5 f
    10.2.4 时间戳的切片与索引
    - l% b; r& d: R' S( U- e  一般而言,时间戳序列作为索引使用。如果想要选出某个子时间戳序列,有两种方法:, ^8 e& }# C0 s6 `+ O* `* j+ w
    " x: x) p, c' s  C
    利用dt对象和布尔条件联合使用
    / i- Y. w& o. f4 o7 u利用切片,后者常用于连续时间戳。
    6 {( R, U, m! [s = pd.Series(np.random.randint(2,size=366), index=pd.date_range('2020-01-01','2020-12-31'))
    & v# E% Y, w! @1 K/ Yidx = pd.Series(s.index).dt! v& `, q" A$ S) Z9 q
    s.head()
    4 \1 V0 c) r: ^" R6 u  M) `  `
    & j- V" O+ l) G9 V4 m/ t3 I* v7 a2020-01-01    0
    * v4 n0 X3 `& r5 N* `2020-01-02    1
    + y, \) ?8 b3 a$ L# _  o2020-01-03    1  {7 x, u2 q) v+ K$ Z% r' y7 y* B
    2020-01-04    0
    / j( w& J5 F  L2020-01-05    04 {, N6 G4 W& J0 e9 B
    Freq: D, dtype: int32
    7 T+ O6 U: D* D/ s0 }1% U) y% i2 k% u7 ?' M! U
    2! }# U3 k; o8 H, ]3 ^4 K
    3- @- B* K+ n" S  [2 N
    4  s% {( d" V$ c" ^4 R, c
    5# q3 t9 y/ N# {$ l1 {& s8 g% x
    6; P- ]( B! }3 s! N- Z
    7
    & `3 L* T  B1 |2 d0 C8
    / N7 k2 H$ h! A+ P0 \9
    ) ?5 a& r8 ?, M10  \/ U% a6 d% N
    Example1:每月的第一天或者最后一天# S% S( R# n6 u2 c0 `0 F& @
    & G% B3 Z2 k& p3 y, B" t
    s[(idx.is_month_start|idx.is_month_end).values].head() # 必须要写.values; m# E) W. g- e0 }! G3 T
    Out[50]: 9 R' p. ]- _7 S+ S2 J
    2020-01-01    1% k0 E0 C# }. g" x2 k: t2 ^
    2020-01-31    0
    / l& [1 H% ~! k2020-02-01    1- M/ M, }% S. @0 U7 b1 P1 |) @
    2020-02-29    18 b& l0 t* K8 x7 `& o. h9 g
    2020-03-01    0/ W. F& c7 r+ s% D: }) |
    dtype: int32+ D) T/ d( Q* V% `! j
    1
    % @1 Q/ Y# K* R; v- c2
    : c; X8 V4 Z1 q; H5 `/ H/ ^" r3
    " f: |  m: v4 Z4  i' D. ]5 @$ N& k7 k) e* m& @
    53 m: _' ^* \/ [8 k9 K
    61 b$ w. E* f# g/ E$ X$ P. C3 g
    7! F1 i" _" b- O, T9 V# p, J* w) P
    8
    $ `& b+ {1 Q7 V; T% mExample2:双休日; C: x! X% J! t9 k4 I: I- n
      L% i% T6 S' M* Q
    s[idx.dayofweek.isin([5,6]).values].head()
    5 o( W2 _3 t( i* W7 [4 J8 TOut[51]: % \# m: x/ A* s0 T0 o
    2020-01-04    1
    ; \, h. [8 j0 ^# `  c  v2020-01-05    0
    9 V0 e5 W# ~+ n% U1 ]7 s2020-01-11    0
    , i3 W& s" q! P$ q# u4 U; p- A6 D/ j2020-01-12    1
    & l3 k$ B; c) A3 n# z7 |# U2020-01-18    1, }/ W/ v( }7 U1 |
    dtype: int32, m, Z2 N4 U7 Q5 u; I
    1
    * x. P% M5 T* I# Z1 K2 Z# C28 P* w0 t: O1 e
    3
    5 Q1 J1 B& n: Z) z& Q% m* i3 c4
    ' V$ Z: U' T/ @5
    2 H9 h5 j; V5 }1 j5 \6 i" Q# J6
    8 k: |* ^) u- S8 W7; ^+ ^7 O% u/ V6 |: x
    8
    . M/ U& d! D. |5 fExample3:取出单日值
    : M3 G% j- u# Y3 {( |
      @) u. q/ q( z! Ys['2020-01-01']
    + Q, }7 n1 @: B: Z1 ?, v) M2 @Out[52]: 1
    + Q3 |/ m( {/ A! S" e+ \( f: d6 ^) D6 W3 J4 r
    s['20200101'] # 自动转换标准格式
    3 k, c- ?1 ~$ b8 c/ v  e+ h' dOut[53]: 1( X: I& ]9 {' i% ]
    1
      B' [* L- x* I5 r+ C2
    0 H' t( }. w. i" t' G( k) }' U3
    - n% \* e0 d8 e" A4. ~% B8 W8 ~6 r; _  A2 P, x. v
    5
    4 {( v6 q% N) {Example4:取出七月' U* i+ i) N. p5 \; m7 Y- `8 R

    % o7 H: s7 ?5 Q# L; Qs['2020-07'].head(): ]/ \4 G8 V# @
    Out[54]:
    % N$ Y0 S& ^+ O) q, Y  n  d$ M8 p2020-07-01    0
    " a: h8 ^) Q/ j# K* m0 p7 ~( H2020-07-02    1
    + x: A( N( y2 h3 W/ |2020-07-03    0# W+ R! G! P, S
    2020-07-04    03 Y; Y+ ~2 f0 b9 L
    2020-07-05    0
    " Y$ K2 j7 u& d. FFreq: D, dtype: int32
    * ], p& K: T' @, U6 z* ?( a* m6 D1$ u1 r! w& k* t9 G; k# m  G$ z8 {4 K+ v
    2
    $ [# O5 ?% `- y2 R& {" j! O3
    9 C2 A7 R; t, D! ^( T0 k+ v4
    6 t" ]- Q$ \: a5
      W' O5 ^& h2 n3 y3 q& \63 n. h! y* h+ a4 u8 t
    7, g  ]. v9 u9 J
    87 Y9 g  d, y2 f
    Example5:取出5月初至7月15日
    8 s  f" x$ O1 q, K9 p3 J( g7 |9 ?* H! m3 t
    s['2020-05':'2020-7-15'].head(): N" c/ h' N/ [- s4 U7 u: e; o
    Out[55]:
    4 b: W: L9 ^9 P" Z1 ^/ i! s3 M2020-05-01    0! I$ a2 T; [5 x; G4 W
    2020-05-02    1
    " ^! P$ X( h% ~6 F2020-05-03    07 J3 J; t2 C5 ~
    2020-05-04    17 e8 }5 D7 i- y# L
    2020-05-05    1
    7 Y" {; V4 }- T, U) W! h5 GFreq: D, dtype: int32
    " b, i# ?* J( L, y# P! s
    6 y; S( ^) x; _' V: L8 k2 U0 `s['2020-05':'2020-7-15'].tail()  r5 _( L7 l: G. N! q
    Out[56]: . Z. M. n2 Q0 j5 I5 h# u9 Y* u
    2020-07-11    0
    9 g  y, M* Y8 x0 F2020-07-12    0
    1 Z# d% B- a, Y+ k2020-07-13    1& f% v8 H5 t) I3 E
    2020-07-14    0" R! O- f( B* ?/ ]! m* ~
    2020-07-15    13 C0 c9 x$ a& N, d2 H  d* `
    Freq: D, dtype: int329 Q0 a7 `# F0 f1 U6 P8 S" D" t
    6 F& i5 E, K: k1 P. K' X
    1% G7 H) F8 M, |, `) G2 r# o
    2( b1 R6 g0 I7 H+ V
    3
    9 d) u( H9 Z2 j/ s8 `1 B4* D/ @$ [/ t9 }. ^  w4 V( v% q; Q
    5% v* x  Y  y3 ^9 T% v
    6' v9 V( X4 d5 L. \
    7: d9 B0 ~2 N' ~' t* y  g
    87 f: N$ @% j% P. t1 _9 ?
    9
    . N6 d) t) K' B, f7 R) A10
    ! T& ?8 y7 e, ~/ d9 P4 v11
    0 c9 n  a% r9 j* `$ J2 [1 N129 {) D2 H% M: g7 R
    13  l+ Q" \5 U& F4 b( {+ r
    14
    # C4 _+ z% {# j8 }) z% A15' M4 @3 w$ A" P" x& w+ a( V; ]
    16
    4 |0 }% c9 P. k5 ?* g" y17
    + E6 ]1 G' v# t3 C10.3 时间差. d! t. n3 F) V" U! d3 e, ^
    10.3.1 Timedelta的生成# ]/ G5 [1 H  H1 G5 ]; o" _8 b. E
    pandas.Timedelta(value=<object object>, unit=None, **kwargs)$ {4 x& |3 ]6 }& D  m
      unit:字符串格式,默认 ‘ns’。如果输入是整数,则表示输入的单位。! H& A% {) T2 H0 L( Y
      可能的值有:
    8 _- o2 T7 `6 F# y/ H
    - q) Q6 M& G6 L" e6 d‘W’, ‘D’, ‘T’, ‘S’, ‘L’, ‘U’, or ‘N’
    , d5 f" W! Z7 S+ k& s/ \‘days’ or ‘day’; M  ^/ _( i5 d4 M
    ‘hours’, ‘hour’, ‘hr’, or ‘h’
    3 f2 T! f: d  i) G7 e  g‘minutes’, ‘minute’, ‘min’, or ‘m’
    1 R& Y. h% D/ o‘seconds’, ‘second’, or ‘sec’
    ) L% K. v& W8 m0 [$ Y毫秒‘milliseconds’, ‘millisecond’, ‘millis’, or ‘milli’2 M% D3 V1 F- r2 s
    微秒‘microseconds’, ‘microsecond’, ‘micros’, or ‘micro’
    ) a, ]  J  ?* V6 _1 h) x, `纳秒 ‘nanoseconds’, ‘nanosecond’, ‘nanos’, ‘nano’, or ‘ns’.
    0 ^0 E* X! M( H* l! ^5 S6 ]时间差可以理解为两个时间戳的差,可以通过pd.Timedelta来构造:
    % T& P; A* ]5 A6 n0 e1 Z& h& npd.Timestamp('20200102 08:00:00')-pd.Timestamp('20200101 07:35:00')2 d: O+ a3 ^4 j) v8 n6 o7 n
    Out[57]: Timedelta('1 days 00:25:00')" c% j1 D3 G9 C- h' s" P
    , J/ S( i: _5 z0 _
    pd.Timedelta(days=1, minutes=25) # 需要注意加s2 o: ]9 r+ T5 j! q# `3 j" k
    Out[58]: Timedelta('1 days 00:25:00')6 m& s% _' A% Q6 ^, l( v" N) V* T# l

    ) W* p# C1 L) Q# ~1 Ypd.Timedelta('1 days 25 minutes') # 字符串生成
    6 e& X7 R, A( A$ {& x! n6 r  gOut[59]: Timedelta('1 days 00:25:00')
    ' Y' W; `. B5 q# s, s
    3 W! g% W: D/ J  F/ [' p8 hpd.Timedelta(1, "d")( Z) V. E8 Y. D# O. ~1 r
    Out[58]: Timedelta('1 days 00:00:00')
    7 ^6 A8 P8 s8 Y  Z1
    2 _6 }+ j1 M: x9 I1 Q2
    ; f, K- A) Q; b, j3" X2 h, D5 J1 o  J, v) I: L
    4& _1 H$ n0 _0 c5 e! k. B5 P
    5
    7 n( \% U& `$ Z( W" L6
    : n8 f% i" B4 W$ `7
    0 m- U& ^$ k) A- X2 ^  Q8 B+ q8
    4 m9 J& s  _# i' g; E* J+ k9, G9 p. U. U, u3 J. v, x/ w6 O
    10$ D: a2 h& H( k- L# p- Z
    11
    1 k3 j& v' r1 a0 h/ b生成时间差序列的主要方式是 pd.to_timedelta ,其类型为 timedelta64[ns] :
    ' @8 c! h7 b* A4 Ns = pd.to_timedelta(df.Time_Record)
    # w+ c1 P) m; A$ {/ U+ v8 ]8 V3 c* J* K4 R
    s.head()
      h2 r  @4 ?' i8 X: _; D0 ~5 }  H2 oOut[61]: 8 g* V/ P8 ]9 {; K0 T. E
    0   0 days 00:04:34
    " G) c( e+ X" B- W# Q& L1   0 days 00:04:20
    $ t' U! Q- e+ ]  U: ^+ {  W2   0 days 00:05:22/ W( m! i' T/ B+ T9 m
    3   0 days 00:04:08/ U6 x- U( q" f) n7 L4 g7 \
    4   0 days 00:05:22
    1 @4 f" U! k& R+ X) ?7 WName: Time_Record, dtype: timedelta64[ns]% A2 S/ ?. h) _0 X$ ]
    1; i4 A8 s! T* n4 O6 [1 ^
    2
    % c( ?& _1 v- \3" t; G& @) C; F& c! D! [
    4- U' K+ A& m$ ?6 A( ]
    5
    - `2 y, z% C  Y4 {! k/ A6+ C$ j  V/ d  B6 s
    76 q. i: l6 Z' @
    89 H) N) b/ `, O; L+ ]; y  K
    9
      Y2 n0 t, y, i- L: U10& Y  ^7 Q% N7 M; q2 h# c- R
    与date_range一样,时间差序列也可以用timedelta_range来生成,它们两者具有一致的参数:
    3 o4 g% H/ Y  ]2 @0 U" Qpd.timedelta_range('0s', '1000s', freq='6min')
    . S5 \! ?* f$ f9 lOut[62]: TimedeltaIndex(['0 days 00:00:00', '0 days 00:06:00', '0 days 00:12:00'], dtype='timedelta64[ns]', freq='6T'). W9 B5 o; N- g9 |; B' \

    " _9 A" y' D$ Y  Cpd.timedelta_range('0s', '1000s', periods=3)
    ) R  {9 |9 A% ]  O  z' _  lOut[63]: TimedeltaIndex(['0 days 00:00:00', '0 days 00:08:20', '0 days 00:16:40'], dtype='timedelta64[ns]', freq=None)' p9 _# c, O7 z! c0 }9 X
    16 |/ S5 k' b+ t0 K0 V6 D& x
    2
    # x' t5 b& j/ U1 c36 p) X+ H; @' [; a" c) u3 G
    44 Y6 J  v" G: g+ {; z2 M
    58 _4 l  W8 P' ~+ N/ u
    对于Timedelta序列,同样也定义了dt对象,上面主要定义了的属性包括days, seconds, mircroseconds(毫秒), nanoseconds(纳秒),它们分别返回了对应的时间差特征。需要注意的是,这里的seconds不是指单纯的秒,而是对天数取余后剩余的秒数:3 @: ~( |- H' q  N
    s.dt.seconds.head()
    * z% [0 [; Q; k: n3 oOut[64]:
    0 }- ~  X7 n* i$ Q0    274+ ^; q: F7 i: A4 g6 }
    1    260# }4 F* J. I& W( O+ f9 u
    2    322
    * P. X, ^5 j8 Y& ^# W9 P8 ~8 D3    248: p3 x" h' y/ K8 D
    4    322
    $ N6 ]5 H+ o% r9 K' K. _* NName: Time_Record, dtype: int64; \% K# z0 T) {! l
    1& M4 V' ], }  p. L8 i) ^
    2
    , j% N/ u; M; T# o- J6 y" U' o3) Z, q9 J  g7 T" P) p9 V
    4$ c: {' |3 [- s! r; k3 j8 z
    5
    7 x/ d& H. Z5 X: V6 C6
    4 A8 O+ O. C2 L7% h  Y7 D. u9 d- l( K: d
    8
    - m" v; `' q% W+ n9 T; n如果不想对天数取余而直接对应秒数,可以使用total_seconds( Z8 e  K0 j# K

    " x3 T' u' ~6 [  W4 {s.dt.total_seconds().head()
    " Q7 C. s. n* r% I9 [" k( \  pOut[65]: 5 Y7 p. }- k$ l9 S+ j1 P9 ~/ g
    0    274.03 p  Y# Y- t( Z$ ?
    1    260.0! u! s& ]" O, x
    2    322.0! i5 j& R: e1 j
    3    248.09 k0 r: p/ w! ?, ?  Q
    4    322.0
    0 y+ `% W3 r8 D+ U) N( dName: Time_Record, dtype: float64
    $ T4 n' ]7 U9 o) D' a, ^3 @+ M1 d10 C& O; e# }( k, _9 R2 I9 d
    2
    ) x" G) v. B! L) k6 o3
    $ P0 x5 p, _+ t8 ~$ H' ?5 l4
    # U3 V3 I2 i! a5 P- B; f5
    1 i2 e5 c( V6 z  s. |( M6' J( t: Y8 t5 @) o& q
    7: u5 I4 k3 g) q5 f5 T
    8
    6 _8 z5 Z3 [2 c" S: \; u% L1 d$ T2 y6 O与时间戳序列类似,取整函数也是可以在dt对象上使用的:  ?& o5 K$ m* r; t8 v+ d

    & y6 a( J. J! \$ r. m; Upd.to_timedelta(df.Time_Record).dt.round('min').head()% p5 }! r3 t: r- H8 ~. Z, V8 _1 B
    Out[66]: 7 E. O* w9 G  k6 c! e
    0   0 days 00:05:00) @3 M' P& q4 H' x
    1   0 days 00:04:00; P1 J8 b. A2 _5 i5 ]2 g, I* B
    2   0 days 00:05:00# \5 D% M2 V  z: t1 c- v
    3   0 days 00:04:00
    ' y" s, H$ k7 ~% J8 q8 q4   0 days 00:05:00
    ! r2 u+ E1 a% eName: Time_Record, dtype: timedelta64[ns]7 P; p( K9 d, ]# N
    16 q  K& N7 k% R- F
    2
    ) {0 E% W5 ~5 F- _$ j! ~34 ]' Z9 K% z. l% n* `" {( {8 Z
    41 O$ i$ Z; L/ V1 w9 e- |- F- R
    5  L. `* Z/ d& R9 \
    6
    ! s# S' Q& ~9 K, g1 g( t7
    2 X' X/ r) ]4 L  V- n9 Q+ B& b8
    : G% A* u5 r+ S( e! x3 {3 G10.2.2 Timedelta的运算; {: M' f" x- g3 F& ~4 S# N
    单个时间差的常用运算,有三类:与标量的乘法运算、与时间戳的加减法运算、与时间差的加减法与除法运算:
    # S* F0 V& ~3 T  X: k+ E: `$ htd1 = pd.Timedelta(days=1)
    " ^6 q, `' W# {2 a3 l6 O1 X- ?2 j( Wtd2 = pd.Timedelta(days=3)  q( W) y$ s6 P5 ?# u
    ts = pd.Timestamp('20200101')
    / ?( W: J; ]* k% I) f- b6 I  A3 u) J
    td1 * 2% C3 `$ a' @9 ?
    Out[70]: Timedelta('2 days 00:00:00')4 h+ ^. L/ l; j* {+ T2 \. w
    0 c( o6 _# M) U2 F6 [2 k# D' C) O
    td2 - td1
    % e" ]0 {8 d0 t. S  N6 KOut[71]: Timedelta('2 days 00:00:00')6 v1 f3 `! w# z: M( d7 {
    + l  ^; \6 x( _
    ts + td1
    % ?) `. I, U+ @; F$ KOut[72]: Timestamp('2020-01-02 00:00:00')
    , o/ `7 R0 {& d/ [
    6 B; x3 A: T0 O) P8 T; D4 `7 V( cts - td1+ l" [  C- w0 q" |; f! b( T5 K6 s
    Out[73]: Timestamp('2019-12-31 00:00:00')$ _2 V1 r. t# I# @" X
    1
    ! L# a  ^4 I, b2, w9 c5 }0 x' X; Y9 U  \' N6 n
    3
    4 D2 C0 a# h* w+ s" K- ~( Q' j4
    + Q; ?0 z3 B* ]9 z  E5# x; i1 Z7 Q% y! L0 q
    6
    8 G& F6 O3 y3 D9 e7" `0 M) P/ F3 X4 r
    8/ f' \2 U0 l) d8 V! U; @3 k
    93 N  O. F- m" a. [8 f* O+ D
    10
    " j  _* C- a7 f8 l, T110 H7 m- q' ?; x$ E: y* |+ f* O5 l
    12
    + ]7 N: w' c3 z& G' v$ ^13# f! {# l0 i, d$ T
    142 G% x7 n7 M% e
    15
    ; f6 A0 v6 I) _$ R2 r时间差的序列的运算,和上面方法相同:% K! W5 ?/ l5 i- `  Z8 k/ I  I" r
    td1 = pd.timedelta_range(start='1 days', periods=5)3 U' [+ w2 X1 h/ l: P
    td2 = pd.timedelta_range(start='12 hours',/ I  b1 j. L( S6 m, i
                             freq='2H',
    0 {( N* X+ ~' u" M( B' E                         periods=5)1 d+ ]: |5 {3 B% T5 H/ k
    ts = pd.date_range('20200101', '20200105')
    6 H! w: r! N8 E- @2 r4 n/ Atd1,td2,ts3 q! C$ x, u" @: X6 t0 y' {( f2 O
    + l1 @+ Y# i* C; y9 T+ w) B, i
    TimedeltaIndex(['1 days', '2 days', '3 days', '4 days', '5 days'], dtype='timedelta64[ns]', freq='D')8 K- S9 p2 P$ A8 Z$ H* G2 i
    TimedeltaIndex(['0 days 12:00:00', '0 days 14:00:00', '0 days 16:00:00',9 S  i& K% B$ t8 D" q1 F3 o
                    '0 days 18:00:00', '0 days 20:00:00'], dtype='timedelta64[ns]', freq='2H')$ b& V' v" s1 q
    DatetimeIndex(['2020-01-01', '2020-01-02', '2020-01-03', '2020-01-04',
      a' Q% K# G) y2 X, w               '2020-01-05'],# }0 z! U* }1 n3 W$ X. V7 g
                  dtype='datetime64[ns]', freq='D')
    . A4 @% T, z4 C' w. }  u19 \& n2 [/ x& ]
    2
    $ d" a( D9 ], ^9 o: y3- M3 m- a! G3 Z0 _: M: I) X8 _
    4. o& q% Y3 e: Y
    5
      X% d7 v2 ^- F! q, ], u6( j% X) Q- q+ I3 k  r
    7( t/ g6 m2 I$ v3 A
    8
    8 H& W  h- G9 s, J9
    ' O) W# n# d; ~. X104 A( G; e- ?+ }/ d
    118 j6 o2 z+ S* f- {1 a3 e
    12
    5 g0 g* @* _) l# s, N13
    + u! r# D6 I' ?! \  u( S! k1 vtd1 * 5
    " a8 c6 a! N: m+ x- O) e. ~Out[77]: TimedeltaIndex(['5 days', '10 days', '15 days', '20 days', '25 days'], dtype='timedelta64[ns]', freq='5D')! X3 e8 ?* c6 s7 G

    ' G, P* w+ m& x5 A% d3 G# u7 Dtd1 * pd.Series(list(range(5))) # 逐个相乘$ Q8 B% h3 F' h6 a! C# R) C
    Out[78]: : f1 {! c: R5 L- ?6 M
    0    0 days: q5 b4 |# X0 V1 D0 ^3 j9 C* \
    1    2 days
    ! G/ Q7 y4 Z4 c9 ?2    6 days$ |2 |5 P  u0 u" R
    3   12 days
    4 O; @% W0 c' I) |, c* E1 s  Z4   20 days
    2 }2 N* W; }9 _- A% Kdtype: timedelta64[ns]8 k1 k8 ?6 q+ r  R' G: W5 M
    5 ^0 C* z% u1 I- T
    td1 - td24 `) g5 y$ d# @# @: p/ N3 }
    Out[79]: , X3 J7 @% Y, f# ?, t
    TimedeltaIndex(['0 days 12:00:00', '1 days 10:00:00', '2 days 08:00:00',
    5 v# L" m, T+ q, F* l9 _                '3 days 06:00:00', '4 days 04:00:00'],7 m+ t0 Z3 Y; F) E$ W
                   dtype='timedelta64[ns]', freq=None)
    + L7 Z# Y! W+ E2 G/ S+ L  x1 s1 T
    , S4 j4 \3 C2 Q- A8 y) mtd1 + pd.Timestamp('20200101')
    5 u* I% a$ X3 {: H+ l' v8 i! ~' D5 \4 sOut[80]:
    2 J7 Q1 x+ W$ _DatetimeIndex(['2020-01-02', '2020-01-03', '2020-01-04', '2020-01-05',9 Z. e  `6 d2 U- S
                   '2020-01-06'],dtype='datetime64[ns]', freq='D')) Z" ^- t, t: U, j* b0 k

    , _( q4 a* _8 M: m$ n9 W8 J9 }td1 + ts # 逐个相加
    1 [% ~, g4 Q# a$ H+ b5 ROut[81]: 3 Y+ {; H& ]& t  F: ^! K
    DatetimeIndex(['2020-01-02', '2020-01-04', '2020-01-06', '2020-01-08',' D& C4 Y( }$ ^
                   '2020-01-10'],
    . d. K  ^# A/ A& t/ ?6 v/ f1 B              dtype='datetime64[ns]', freq=None)0 u. p8 A: B6 e- D4 b% ]

    $ f& A1 g+ O+ R2 O) x12 [+ ?( Y4 P7 E& `% w
    2+ k" F4 R$ u; y8 X6 `1 C
    37 E6 n& r. j- J& B$ b- k
    4
    - k7 k% [$ V  r! D: N7 _0 e5' o8 s) Z, }( D- L
    6& Q  i9 n" N( O+ o' c! ?1 o" f) Z) U
    7. s, h! w+ m" w, W) s# v6 X# V. e
    8  q- v( e/ F6 f% r1 O
    9, Y) k$ u4 g/ N
    10
    . S) d; H0 T- o3 v  _6 ]- W11
    % J* u$ j! s4 a" F122 g* \2 Y! T' \7 V1 W/ i
    132 L8 N. I* W. I9 Q
    14
    # ^% V: b: s/ u  {+ W15
    9 \" T4 K# k; U, Q  ^( F' u; q: Z16
    + b% ?+ @+ P; }5 {' ~$ q0 x% V4 Y17
    2 ~8 }& g2 u9 T3 A' [6 ?18  ^# ^0 k4 a1 q
    19
    ! U$ \8 B& N2 q: r' j1 q8 M, F; {20* v  i( a0 }+ x  W5 w
    217 L$ T! w# K8 Y7 T* B
    22
    & z  Q+ X* Q' R- D7 S1 d23% }  `) f% o8 u" ~
    24
    * M2 Y9 S6 d; o8 Y& ?254 H! h* u7 O6 N5 H% {
    26, a* K( K& h" f( q- l! V( Y
    27; X9 z/ |# E! T$ h+ E( O
    28
    ( E. e# i  L  B10.4 日期偏置
      k. D  p, q1 V$ D7 b# v' a& k; g, k10.4.1 Offset对象
    + D4 \0 y& P. R. j% X* o  日期偏置是一种和日历相关的特殊时间差,例如回到第一节中的两个问题:如何求2020年9月第一个周一的日期,以及如何求2020年9月7日后的第30个工作日是哪一天。! d, D" e! u, v' L- J
    1 S6 Y0 y8 }$ E; T) ?6 y% N
    DateOffset 类有10个属性,假设s=pd.offsets.WeekOfMonth(week=0,weekday=0),则:
    % W& w& }9 @4 d8 F6 H8 I' H4 v: l" C8 Z% G& K" G* V! E  K
    s.base:<WeekOfMonth: week=0, weekday=0>,返回 n=1 且所有其他属性一样的副本
    0 k7 i8 W/ c5 s/ W+ ]s.kwds:{‘week’: 0, ‘weekday’: 0}* Z! [8 s' }& D$ m2 H  g
    s.wek/s.weekday:顾名思义
    1 X- a- Q6 u, {9 x  }& e2 S有14个方法,包括:
    ! Z3 k, Y! t/ F, p) T
    3 C+ Y* A2 o& B; W8 Y# y% ADateOffset.is_month_start、DateOffset.is_month_end、DateOffset.is_quarter_start、DateOffset.is_quarter_end、DateOffset.is_year_start、DateOffset.is_year_end等等。
      D$ l4 v7 {+ ^pandas.tseries.offsets.WeekOfMonth(week,weekday):描述每月的日期,例如“每月第二周的星期二”。
    2 |5 m$ J+ b8 C. i$ V, f! v  C1 h- R( `
    有两个参数:
    ; m0 U7 l& N) @4 D$ Z* E3 wweek:整型,表示一个月的第几周。例如 0 是一个月的第 1 周,1 是第 2 周,以此类推。
    & W1 ]0 E: a% z& Hweekday:整型,取值为[0,1,…6],表示周一到周日,默认取值为0(星期一)
    9 m3 u% y$ C( N4 |2 ?pandas.tseries.offsets.BusinessDay(n):相当于pd.offsets.BDay(n),DateOffset 子类,表示可能的 n 个工作日。
    + W* ]8 Z8 W3 x. ]" G; _6 W0 g5 I6 c; D2 J: g  g0 ?
    pd.Timestamp('20200831') + pd.offsets.WeekOfMonth(week=0,weekday=0)9 |  {$ i) S) y
    Out[82]: Timestamp('2020-09-07 00:00:00'); n& k  G- d# \9 i( p

    ! h+ c& x" e6 s3 Tpd.Timestamp('20200907') + pd.offsets.BDay(30)* S' J6 W9 d5 l- ^. `% Z2 r
    Out[83]: Timestamp('2020-10-19 00:00:00')
    , _" P  ~- [& L' [: M5 s- _1" H' |, A8 U% X4 U; ~2 w) [, Q) S9 ?
    2
    3 P+ W+ z2 w, H  ^1 a8 C36 H8 d7 t/ f! X& |: H- g
    4, W) R4 J& g) L7 S' R8 n7 a
    5
    ; U$ H7 ^  m+ g+ P' m9 J' J  从上面的例子中可以看到,Offset对象在pd.offsets中被定义。当使用+时获取离其最近的下一个日期,当使用-时获取离其最近的上一个日期:& v1 ^0 X+ E; I: O
    - ?* |6 W7 N* u
    pd.Timestamp('20200831') - pd.offsets.WeekOfMonth(week=0,weekday=0)
    / N. `2 G. Y9 ~$ u' i3 vOut[84]: Timestamp('2020-08-03 00:00:00')8 r8 i, y, z) W! F; c$ q$ U
    & d3 W( J( e+ u
    pd.Timestamp('20200907') - pd.offsets.BDay(30)
    ; y; e4 s5 W- _1 \' v" LOut[85]: Timestamp('2020-07-27 00:00:00')
    * X8 e3 M3 {; ^- N& v+ a7 B7 J4 K7 ]6 k9 I/ k& q
    pd.Timestamp('20200907') + pd.offsets.MonthEnd()
    & v/ R  _" f' C/ p+ m) ^# NOut[86]: Timestamp('2020-09-30 00:00:00')9 a: g0 Y( H7 m+ \  a
    1& M( s7 l2 A$ M8 A' t3 f
    2
    ; Q+ x1 U4 a0 B5 u/ N2 f/ i3
    2 c$ t& n  \+ P( }8 x& i4
    4 }0 c& m7 @6 l" P8 x7 v5
    4 l" Y6 }# t. O, {0 N# l60 K/ x8 d/ P, g7 ?
    75 e, [/ m: u/ ]7 `  @* Q) r
    8
    % y0 l. p, j, z5 X3 b  常用的日期偏置如下可以查阅这里的DateOffset 文档描述。在文档罗列的Offset中,需要介绍一个特殊的Offset对象CDay。CDay 或 CustomBusinessDay 类提供了一个参数化的 BusinessDay 类,可用于创建自定义的工作日日历,该日历说明当地假期和当地周末惯例。
    - b) C5 L. I) {6 v  其中的holidays, weekmask参数能够分别对自定义的日期和星期进行过滤,前者传入了需要过滤的日期列表,后者传入的是三个字母的星期缩写构成的星期字符串,其作用是只保留字符串中出现的星期:# _6 C1 A! m6 `4 |3 T* G

    ! L9 ^2 T0 O7 M. s$ ?2 _my_filter = pd.offsets.CDay(n=1,weekmask='Wed Fri',holidays=['20200109'])
    ' K. f* v9 M! M5 z$ O- Ndr = pd.date_range('20200108', '20200111')  O% E: v6 Q. e& E

    0 x, t- C; H4 ?3 M# ~5 jdr.to_series().dt.dayofweek
    0 |/ X, V! q! q6 A% GOut[89]:
    8 W" _1 e6 I0 s5 H3 s2020-01-08    2
    : _) o% |7 M9 c" s- i0 q& l4 ]9 O2020-01-09    3
    : R( `/ T- U6 W/ m& G  a- w2020-01-10    4: y0 L$ e+ |1 C4 s2 z+ ]1 A6 E7 X% T
    2020-01-11    5+ R5 z* x/ @) v5 a
    Freq: D, dtype: int64
    ( S- i# ]0 V5 v, p- Y, h! @- [% o! ]
    [i + my_filter for i in dr]
    ! _9 N  ^! m1 h5 J8 t. A4 H& ?Out[90]:
    : F' S7 }* J, o$ A* [- M[Timestamp('2020-01-10 00:00:00'),# u# [4 \; f. C+ Y) @. F3 w$ x, D
    Timestamp('2020-01-10 00:00:00'),
    % y6 l/ a2 B; C( ]  {  n Timestamp('2020-01-15 00:00:00'),
    , g" h* y3 |5 T' ^  Y Timestamp('2020-01-15 00:00:00')]: H% x& b' B% i  c7 O

    ! h0 H  j, B4 C# b0 y1
    4 U: F6 m6 _2 |. ]4 v+ ?/ Z2
    ( g6 d: r2 o& Z' o# P37 J+ Q7 y* \( B  [: d  g
    4- r4 ?, H- r; J( L
    5: r5 w/ {* t& ^2 w
    62 h( q/ k6 J# [. R; q- \( k
    74 X& @3 C# d6 a- E
    84 M: Y$ A, `0 M
    9
    # o$ ]6 P' w% G" F( u6 m8 A9 n1 S; y10
    3 c7 Z8 M% Y0 K. f- y% N# M11" ?8 S, f( t* K
    12
    7 R4 ^* K9 J$ b: S& B13
    " \- X& L% h% Q% N14
    * w  c) ?( U, i4 h15
    4 P9 s- ^2 H' G# U16$ p6 a- {4 l/ u" X. k0 q
    17
    7 f0 {3 o: O/ t8 i4 x% P  上面的例子中,n表示增加一天CDay,dr中的第一天为20200108,但由于下一天20200109被排除了,并且20200110是合法的周五,因此转为20200110,其他后面的日期处理类似。9 l0 a4 E. n5 L6 V: U  S6 g
    % g7 j+ z6 m) u# ?
    【CAUTION】不要使用部分Offset
    $ @9 R- Q3 ^, y3 H, _在当前版本下由于一些 bug ,不要使用 Day 级别以下的 Offset 对象,比如 Hour, Second 等,请使用对应的 Timedelta 对象来代替。4 B% Z" s! S. K; P( k" H' U+ y2 s

    6 Z% U6 J- K3 G; `# M10.4.2 偏置字符串
    # x% C) h7 f' V. v5 f. ~  前面提到了关于date_range的freq取值可用Offset对象,同时在pandas中几乎每一个Offset对象绑定了日期偏置字符串(frequencies strings/offset aliases),可以指定Offset对应的字符串来替代使用。下面举一些常见的例子。% X0 W$ W  E# K" H/ r; }
    & S2 C% a. F5 m( R+ `+ |/ p
      Offset aliases:pd.date_range函数中的freq参数,为常见时间序列频率提供了许多字符串别名。 也称为偏移别名Offset aliases。偏移别名列表点此参看(大概27个)。$ h; U& k4 W, V4 |$ D
    / ?9 d  H3 Y! y9 {
    pd.date_range('20200101','20200331', freq='MS') # 月初
    ! F- M# x; c+ |: D8 \( [Out[91]: DatetimeIndex(['2020-01-01', '2020-02-01', '2020-03-01'], dtype='datetime64[ns]', freq='MS')
    9 D% E* j4 t9 M& M% n
    * Q1 J4 c# D. J3 X. U2 Qpd.date_range('20200101','20200331', freq='M') # 月末
    2 S6 i) o8 @; N% Q9 z1 C. L- u! kOut[92]: DatetimeIndex(['2020-01-31', '2020-02-29', '2020-03-31'], dtype='datetime64[ns]', freq='M')
    / q, t  j8 l) `: Z8 X5 R0 _& V3 W5 |. \7 h0 w9 K2 H; ^
    pd.date_range('20200101','20200110', freq='B') # 工作日% l9 U: _9 A9 u
    Out[93]: 2 q! u; D1 d4 [3 ?- h0 o' [
    DatetimeIndex(['2020-01-01', '2020-01-02', '2020-01-03', '2020-01-06',; v# ~- u" J5 G; ^+ P4 Q
                   '2020-01-07', '2020-01-08', '2020-01-09', '2020-01-10'],
    ; z+ Y. H; K- G& k, G/ A              dtype='datetime64[ns]', freq='B')& R' _1 V% C$ S+ B
    $ e5 c6 b6 B1 F( o
    pd.date_range('20200101','20200201', freq='W-MON') # 周一
    3 T# E, v9 N& X4 NOut[94]: DatetimeIndex(['2020-01-06', '2020-01-13', '2020-01-20', '2020-01-27'], dtype='datetime64[ns]', freq='W-MON')7 M" ~, e8 Q4 J. `( a; O/ A6 Z

    $ K( Y! |" g$ b2 |7 S# Wpd.date_range('20200101','20200201',* ~3 i* ]1 l# A0 }
                  freq='WOM-1MON') # 每月第一个周一6 |: U% v+ Y- h9 _! a2 P9 E4 R1 v

    8 ^9 i" h6 N/ ^5 ]Out[95]: DatetimeIndex(['2020-01-06'], dtype='datetime64[ns]', freq='WOM-1MON')
    8 z; _; D$ V( `8 A
    & t. ~' `4 x+ p2 s" {1
    4 N* Y( F+ r' h2 U2
    & t8 i5 F9 ^" X+ w2 C4 q! F39 B9 Z' Q0 L4 E
    4
    8 ?/ C% c& s2 b/ T5
    / F. d. X$ g0 {+ p' q/ e1 E" i6$ D$ h4 M! u* c
    7
    * d$ a) P  h1 N8
    : R* y" h8 l1 s: {( p! W2 [) B$ E97 O) ~: q& h* X& G5 N( P1 B/ b
    106 g- K" c! q1 p
    11
    1 ]0 o9 ~" ?/ o  j3 e6 v* v12& L' Q+ C/ _, w% B3 k. {1 g8 z9 h
    13. a5 d0 d9 f8 |; o
    14/ |" {; s) v0 X% y' t
    152 N; I" X3 T- v$ g
    166 S# W7 U) P* f# G; ~% Y
    17; D: J4 J  O* L2 |- w
    18- @7 k' s- V& J) @* b  A' q: @
    19
    * U1 P& a, n! i$ B/ x8 i& o5 `7 Z, ?上面的这些字符串,等价于使用如下的 Offset 对象:  t4 M5 t) M: h& E
    6 _6 T' ~, F& y& ]: a, |" Q" G
    pd.date_range('20200101','20200331',8 p* ]; @/ ?' v3 u$ |/ i( w
                  freq=pd.offsets.MonthBegin())
    8 C! {* m6 C0 O8 j( K  U% x: ~) g4 t$ ?. b, S
    Out[96]: DatetimeIndex(['2020-01-01', '2020-02-01', '2020-03-01'], dtype='datetime64[ns]', freq='MS')" g) [$ ]& i' u5 D4 G8 k+ D

    % V* Z- i' k7 O7 ^3 mpd.date_range('20200101','20200331',7 d! R  n6 `% z+ k
                  freq=pd.offsets.MonthEnd())6 u! O/ \, Z1 z% G( @0 M
    : |. g# y* ?! m) O" s. c& z% N# `
    Out[97]: DatetimeIndex(['2020-01-31', '2020-02-29', '2020-03-31'], dtype='datetime64[ns]', freq='M'); ]0 u9 `3 Y, Q1 o5 Q

    0 Q5 a9 R3 c) X/ ppd.date_range('20200101','20200110', freq=pd.offsets.BDay())
    - g4 f: H+ B' y$ @# _0 rOut[98]: ; j; @7 z; D& a- Q
    DatetimeIndex(['2020-01-01', '2020-01-02', '2020-01-03', '2020-01-06',
    9 M% X, S/ f  w+ F9 S' Q               '2020-01-07', '2020-01-08', '2020-01-09', '2020-01-10'],
    7 ~4 R8 C6 l- `" X  n$ S* A* d              dtype='datetime64[ns]', freq='B')
    0 l! T2 d! z6 m8 o3 D: n# s" n1 u5 B# p, X
    pd.date_range('20200101','20200201',
    $ s0 t! k5 q1 C  t" f              freq=pd.offsets.CDay(weekmask='Mon'))7 D* J$ J! w' \2 ]

    & D% N  r7 j: `9 F1 k2 oOut[99]: DatetimeIndex(['2020-01-06', '2020-01-13', '2020-01-20', '2020-01-27'], dtype='datetime64[ns]', freq='C'): `& |4 z! i1 \  Q' W% [4 h

      U' {9 y( \, I( a7 b* d" vpd.date_range('20200101','20200201',
    # T( `( E( _4 {              freq=pd.offsets.WeekOfMonth(week=0,weekday=0))# i4 e* W7 [, N# r

    0 B1 c! g) X0 qOut[100]: DatetimeIndex(['2020-01-06'], dtype='datetime64[ns]', freq='WOM-1MON')2 N  e' Z4 u- [4 V" o
    ) ?  _" |" @8 B+ a/ a: V
    1
    6 D$ P) \$ e! z8 Z3 s2
    9 V* F8 z1 ~* W( H6 Q1 c* K$ U3 ?3
    ' q9 y9 R4 t/ p6 e/ G4 V; R$ Z8 i4
    / B. ]4 F$ L# B0 A* d52 r5 O9 {9 J( g& `) f
    6
    ) r* d. b$ z' o( I0 I76 Q7 u  v2 n- r: [) ?$ g9 C+ j
    8
    . t/ P& F. \  K2 ]2 i" K9% n( j/ n1 k( Y6 Y7 _9 ^- w
    10
    3 k+ t2 T  Y6 A11
    * q  C* L2 U$ K" Y12+ q, o3 h* }3 M# N  D% r
    13. K: Y5 ?9 a7 [
    14/ j4 Y( ~' J' Y4 l8 ^2 M9 ^* ?. x
    15
    2 f6 e" [+ A( J8 b- s6 R& R/ s16
    2 i# r- o" m- Q! E2 @) E" s17
    / b( H" K; t' i0 @+ \18# i& T7 u6 U% R
    195 K+ h* Y* }( }% h/ w$ m: S
    20, B6 G/ B: Z! O
    21" ^( k8 [  B/ D6 i5 C4 m; ~
    22
    1 _; G  {6 z' b8 P# I230 L/ e) h. S) }* G% \6 }3 ~
    249 E7 [# s- P8 w/ B, v0 h9 h  ~- M
    25
    2 F) O; Q6 A: {7 H8 Z- x! @【CAUTION】关于时区问题的说明$ p* [* t2 R; f4 U; H
      各类时间对象的开发,除了使用python内置的datetime模块,pandas还利用了dateutil模块,很大一部分是为了处理时区问题。总所周知,我国是没有夏令时调整时间一说的,但有些国家会有这种做法,导致了相对而言一天里可能会有23/24/25个小时,也就是relativedelta,这使得Offset对象和Timedelta对象有了对同一问题处理产生不同结果的现象,其中的规则也较为复杂,官方文档的写法存在部分描述错误,并且难以对描述做出统一修正,因为牵涉到了Offset相关的很多组件。因此,本教程完全不考虑时区处理,如果对时区处理的时间偏置有兴趣了解讨论,可以联系我或者参见这里的讨论。5 u# T9 K: t: M4 R, e1 l

    3 l! q2 _$ F2 n10.5、时序中的滑窗与分组
    " ~$ _+ m1 Y0 }1 m10.5.1 滑动窗口
    1 r5 u) S5 y8 f3 l& a3 B" ]  所谓时序的滑窗函数,即把滑动窗口windows用freq关键词代替,下面给出一个具体的应用案例:在股票市场中有一个指标为BOLL指标,它由中轨线、上轨线、下轨线这三根线构成,具体的计算方法分别是N日均值线、N日均值加两倍N日标准差线、N日均值减两倍N日标准差线。利用rolling对象计算N=30的BOLL指标可以如下写出:9 ~& O& d$ U) y) b2 e

      ?: X8 _5 f8 h' y- G3 [8 D9 kimport matplotlib.pyplot as plt! c. ^- Y5 i' P
    idx = pd.date_range('20200101', '20201231', freq='B')
    : |% V- ^% D: X' _$ G' F" qnp.random.seed(2020)
    # `* g# }1 `) w+ H. R
    7 _* f2 ~5 G6 _5 p4 `" Y3 Mdata = np.random.randint(-1,2,len(idx)).cumsum() # 随机游动构造模拟序列,cumsum表示累加
    4 E& o$ ~% _/ A" B# Ks = pd.Series(data,index=idx)
    ( D8 w+ Q: s- os.head()
    ! }& X2 ]  ~4 G& T9 sOut[106]:
    * C" {" Y0 [/ ^- M" o4 O, h2020-01-01   -1
    6 a2 B1 k! T0 ~' R6 j2020-01-02   -2
    ( U7 y( B0 u% a( C% r2020-01-03   -1# ~$ [; j* N, g3 z% o2 Q+ @
    2020-01-06   -1
    2 |5 \' F$ x9 Z# {  K% U2020-01-07   -27 Q6 a+ H: @# `: h! V
    Freq: B, dtype: int32, A4 s2 j* f. X" i- t
    r = s.rolling('30D')# rolling可以指定freq或者offset对象
    $ L2 V; D; e- j  ?$ j, _' Y. o. N' _( A" c! y
    plt.plot(s) # 蓝色线+ [' }( |! L9 M; _" W3 w
    Out[108]: [<matplotlib.lines.Line2D at 0x2116d887eb0>]) m# x0 N  a- n# H
    plt.title('BOLL LINES')
    , I. a, a( e# bOut[109]: Text(0.5, 1.0, 'BOLL LINES')
    3 I! A; j" X1 [9 L* J, M8 `. I0 f8 ^4 R' b* \$ b" o
    plt.plot(r.mean()) #橙色线; M! C. C9 ]' A' C, Z( b4 n
    Out[110]: [<matplotlib.lines.Line2D at 0x2116d8eeb80>]. C9 @& }# y* P
    / I% W$ a! D+ Q
    plt.plot(r.mean()+r.std()*2) # 绿色线
    ' K" c) \5 Y: o9 \2 m1 G  bOut[111]: [<matplotlib.lines.Line2D at 0x2116d87efa0>]
    " `) U5 }, \8 L, \0 Z
    5 X' S5 T4 ]8 v. u8 L) \1 [$ ~plt.plot(r.mean()-r.std()*2) # 红色线- h+ x4 g* H3 }3 W
    Out[112]: [<matplotlib.lines.Line2D at 0x2116d90d2e0>]+ ?$ D$ `0 R' \) q  m
    0 x4 m3 V- p9 r" Z
    1
    $ p  i/ [" a/ ~# r4 }2
    ; N2 A; S+ c5 U( L% Z/ w( @3& n: @3 w7 Q/ R
    4! ]# V: g+ N- o2 J- Q
    5' \3 `& e, T; S( G$ G) Z
    6: V. q1 u% M( r4 p+ g
    7
    3 l" r' ~8 {, j, q8: R9 K  b& j$ K3 L
    9
    ' M# ^& }  ^2 d# i7 ^% ^6 P10+ @; S% k6 K% ?3 t) w
    11
    : F# B, U& [( X12
    % c$ g! s5 ]+ D13
    * k% V9 S2 S4 f14. M0 u) u9 ?" _( j
    15/ b, \/ p5 V! c; A
    16& C$ f4 Z0 c, H  w1 ]2 A
    17
    / L% v) C; A- ~( n: f; V( L18( q0 ~% W" W, ~# Z) K, X. A
    19
    0 O5 }6 g3 k) v/ X& ]( ]3 Z4 j& |20
    , j) O# n0 l# i  t- v* ^/ S21
    5 Q: D2 s0 H% z" ~# Q4 c22/ |" ~& Y0 b& i, y1 d
    23. v; X/ J& I" k# y6 d
    245 h$ a' j0 A1 a; d/ W+ G
    25
    2 N: J" b' l. _8 `26  }9 s8 T# a" U2 l3 Q4 `
    277 A: c1 @3 n1 e7 J
    28
    6 R6 o  I' F5 P8 O6 ~( @295 l. L+ S: E: u: O. M# S( b

    3 r5 y: ?# d/ u- c. o% Y) L+ O" p   这里需要注意的是,pandas没有实现非固定采样频率的时间序列滑窗,及此时无法通过传入freq字段来得到滑窗结果。例如统计近7个工作日的交易总额。此时可以通过传入多个函数的组合来实现此功能。
    % H# D* f" M0 ]) U! ~/ L   首先选出所有工作日,接着用普通滑窗进行7日滑窗加和,最后用reindex()恢复索引,对于双休日使用前一个工作日的结果进行填充。
    1 R4 S4 T  D3 o8 R3 O& a; D/ g9 ?3 j" s7 o9 ?% P7 p9 Q
    select_bday=s[~s.index.to_series().dt.dayofweek.isin([5,6])]
    # x: D6 i: x8 y6 o* J9 k7 d, gbday_sum=select_bday.rolling(7,min_periods=1).sum()& n% S4 J6 P8 h( Z
    result=bday_sum.reindex().ffill(). L% H. F5 t, w0 }# j# C/ z
    result+ k7 U: W- ^! h, P/ F
    / i; k0 ]9 x4 K) e  j( d8 O
    2020-01-01     -1.0
    . A  c3 Q  q8 Q! }, j4 q8 s) L2020-01-02     -3.0
    + Q7 G/ M; Y3 Z% }% ^2020-01-03     -4.0
    1 C' v1 H3 s6 r3 N2020-01-06     -5.00 F5 V, Q3 @! `$ A2 p7 d# e# V' S7 [
    2020-01-07     -7.06 M6 Y& e' [: p9 i5 K
                  ...  ( ]7 g' ]& n7 S* U$ A, }9 Z8 ^
    2020-12-25    136.0
    ' B! U( z5 t. D2020-12-28    133.0( p! b& A8 d6 f/ e; b0 n( w6 w
    2020-12-29    131.0- C  D7 h1 a$ w: L
    2020-12-30    130.0
    / G% @6 k# D( \0 E% `: Z4 T2020-12-31    128.02 s# {" j3 q4 |6 G; d* L0 i
    Freq: B, Length: 262, dtype: float64. O$ u: L- C' n* X) e* A% B/ G
      X* E2 ~' J3 q# L, }9 u; Z
    14 v  p. }0 j( T; r0 P3 R) b' E
    2
    0 n+ R8 O( A3 ~2 t$ v2 A& j3+ |+ J/ s( G4 r3 m/ N5 L
    4& ~5 {( `/ M8 K5 X8 z3 b9 \9 O
    53 I% n9 e; l# G) z8 `- X
    6% C7 W0 W; e! X- l
    79 B4 O) _! }6 N% h& y
    8
    3 T! M8 _5 f" Y3 F9 }. ?* e, g9, K. n; E2 J% Y
    10
    . K& m" q& @$ K4 c11, C8 V- {1 B7 U, o
    12% `5 Y/ S6 T( M' R
    13
    6 O! n: T: J8 c/ d5 ]1 ^146 J  j  K: B/ B, P- T: K  d+ h  r
    15
    % u! U" [% m/ A  y1 e16
    + L% M' u; r1 h175 G$ P1 U/ K, \2 n
      shift, diff, pct_change 是一组类滑窗函数,它们的公共参数为 periods=n ,默认为1,分别表示取向前第 n 个元素的值、与向前第 n 个元素做差(与 Numpy 中不同,后者表示 n 阶差分)、与向前第 n 个元素相比计算增长率。这里的 n 可以为负,表示反方向的类似操作。
    " e. {0 L+ o" H. o2 o
    7 l: j5 \- B* d  对于shift函数而言,作用在datetime64为索引(不是value)的序列上时,可以指定freq单位进行滑动:
    7 ^0 k1 x* p% [9 {8 ^4 b( ]0 P; z  N
    ) T# K) J5 w4 z  ?7 @s.shift(freq='50D').head()7 b- l6 b3 w% d
    Out[113]:
    6 V7 `/ T# W! C% r2020-02-20   -1
    ' z0 {% Y/ {) a; U9 `+ k2020-02-21   -2
    ( N: o4 S" [# q% A* f& B, }2020-02-22   -1
    ( n9 z" [# k  Y9 B  t2020-02-25   -1, D, E( L/ h5 d4 V  p6 S
    2020-02-26   -2
    / m; Y) \( c: N2 c4 P+ Y# p+ O& h' idtype: int32
    # ]2 t% _, S) ^( J/ W1. K3 L7 W" t' Y
    2) M' _) t6 V4 Z
    3
    . T- l( H- y2 d% `# t8 x4
    5 R, _! x& }- B- H$ w2 k. d2 j5
    ' L: n5 ?6 o. A8 r: N* P6
    % |3 y# _, k! Y; q7 }- v71 Q' y4 T- |1 E7 {! g& [) A* W
    8
    6 g. P; i% a4 X* g* x  另外,datetime64[ns]的序列进行diff(前后做差)后就能够得到timedelta64[ns]的序列,这能够使用户方便地观察有序时间序列的间隔:
    2 B) _' W) R3 Z( W6 y: O. o: E
    8 e$ ^# Y+ V( y  r$ x) g; a. d8 fmy_series = pd.Series(s.index)5 f& g' `+ \7 Q* Z; @) G7 s0 i1 ]6 x
    my_series.head()
    + a" M- l7 O) F( C: \- U6 I$ G7 O, iOut[115]:
    7 N4 W7 f! r3 M! `0   2020-01-01$ F: l+ d/ w$ a5 [
    1   2020-01-023 D% K2 q+ p6 U  R5 |% |. U% ^: ^
    2   2020-01-03
    * Z+ `  W/ d- N6 H1 J2 M0 Y4 u3 O3   2020-01-06* Z0 L( k9 B$ X; W3 w0 n
    4   2020-01-07/ ?# z4 p! M: i2 z
    dtype: datetime64[ns]
    5 y" d  `) L8 ^& |/ Z' s% M) h  r+ h7 h* A9 ]
    my_series.diff(1).head()  b7 Z5 |2 {$ b3 V
    Out[116]: " E# _3 i( q- h! K  ?  o! x' u
    0      NaT
    ) K0 z* c9 w* L1 C; w1   1 days! \. P7 ~9 Y5 E- K* ]: x
    2   1 days
    0 k5 I" V/ j* h7 g3   3 days
    - `, a! W5 l- J; G6 k4   1 days
    & s% t. D# |3 q* P* `) j$ Z& ]dtype: timedelta64[ns], R" Z, }6 W; L: I3 u6 q
    : r; N/ @$ R! f$ S# L% D% J
    1
    8 I3 Z! W+ C$ q( b4 z+ D2# L0 q/ E$ i3 Q8 m4 F- Q' u$ C
    3
    ) z/ u' Q. q+ V% x43 [3 e, ?# t1 U" I6 r
    5  E+ L& c. U( g" ^: o& z
    6
    . H4 V* p( A: w  n5 S2 B/ b7) O: |: T3 y2 `, ]2 x3 `+ f
    8! S3 s6 d: j' V2 ^$ t. I
    9) l2 h" W' H- ^: w
    10# i0 B: m& Q' T: F
    11
    $ L6 }' ]( u; g$ d% V, ?! Y123 M* ^9 e7 Q- s+ [
    130 U  _! s+ ~5 G: |; {; \; G9 [$ ~
    14
    6 `1 Z9 y8 i0 B' z5 v# F156 Y" I6 B1 u3 z' _- \4 R
    16) ]" p$ n$ r# h7 w
    17
    6 e6 c" A' N7 b$ n$ y6 f+ s6 i1 f/ n8 a18
    ! |7 `6 c, L- v10.5.2 重采样
    ' M" T' P# s( J  q  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)1 L: p- \. N2 ~* v6 y4 o+ t) a
    常用参数有:- c$ U0 _, p" A) m6 V

      k1 Q  O; [% X2 J+ P% P, prule:DateOffset, Timedelta or str类型。表示偏移量字符串或对象
    ' S- e- g9 d+ _' A9 K5 \8 y" ]axis:{0 or ‘index’, 1 or ‘columns’}, default 0。使用哪个轴进行上采样或下采样
    4 R9 K8 U" g& }% m) Q" Vclosed:{‘right’, ‘left’},默认None。表示bin 区间的哪一侧是闭合的。所有offsets的默认值为“left”,但“M”、“A”、“Q”、“BM”、“BA”、“BQ”和“W”均默认为“right”。" N/ q" n9 q6 S& w3 u2 ~6 [7 f4 G
    label:{‘right’, ‘left’}, 默认 None。hich bin edge label to label bucket with,所有offsets的默认值为“left”,但“M”、“A”、“Q”、“BM”、“BA”、“BQ”和“W”均默认为“right”。
    + q3 y% q% `# H8 v) s" Dconvention{:‘start’, ‘end’, ‘s’, ‘e’}, default ‘start’。仅针对 PeriodIndex,控制是使用rule的开始还是结尾。7 l! W) x; B, i, ~/ }) O1 v7 z
    on:字符串类型,可选。对于 DataFrame,使用列而不是索引进行重采样。列必须类似于日期时间。! E) \" D* n! y/ ?9 Y
    level:str 或 int,可选表示多重索引MultiIndex的级别,这个级别的索引必须类似于日期时间。
    % j0 J! o1 W. r+ e  \# ~' Dorigin参数有5种取值:. v! C. W) x& N+ ]  M/ [
    ‘epoch’:从 1970-01-01开始算起( \; b. B/ @2 M4 ?: [9 j
    ‘start’:原点是时间序列的第一个值" ~: e* @* ^, @' k5 o( [" |# D4 Y
    ‘start_day’:默认值,表示原点是时间序列第一天的午夜。
    ; ^1 A6 ]3 ?, ?) T% @0 e'end':原点是时间序列的最后一个值(1.3.0版本才有)- r, l) x% \' m; Z! N: n+ n
    ‘end_day’:原点是序列最后一天的午夜(1.3.0版本才有)
    , }8 J: Y: S+ l) hoffset:Timedelta 或 str,默认为 None,表示对时间原点的偏移量,很有用。6 ^' H) ?( J: ~- n7 k( \' e
      closed和计算有关,label和显示有关,closed才有开闭。! ^! O9 Y7 t7 `8 f; J3 v- L, K
      label指这个区间值算出来了,索引放区间的左端点还是右端点,closed是指算的时候左端点或右端点是不是包含。5 Z/ H+ ~9 b* e" ^! x- _

    ' j- I: L9 z" _0 A9 N& |6 J/ j1 E重采样对象resample和第四章中分组对象groupby的用法类似,resample是针对时间序列的分组计算而设计的分组对象。例如,对上面的序列计算每10天的均值:5 n# H& F0 m' K* l" y0 U4 Z% K/ L
    s.resample('10D').mean().head()
    ( i- L! t# X* ^  g8 h1 C4 VOut[117]:
    ! M  J5 w" f1 ?2020-01-01   -2.000000
    ; u, h/ |/ U$ i. c9 @  F7 ]  o6 @* j2020-01-11   -3.166667! ]+ m# E* w, d) d3 M! i  P
    2020-01-21   -3.625000
    ! X  V( |7 W# Z# X2020-01-31   -4.000000  J8 ]  S3 N; s& `; [
    2020-02-10   -0.375000) }$ q2 p2 k  B% I
    Freq: 10D, dtype: float64
    4 \( i4 B# b3 [, ?% }8 o14 @& T; N1 Y0 ?# ?% Z/ y
    2( M3 x+ G* ]9 P) ]- h
    37 q1 G; ^' ]( P5 B$ m$ [; M
    4
    % y# n1 r; V% x5 ?0 r3 S. [0 [5
    ) I0 m. }- ?. I6
    9 F1 U+ b. ^/ `/ b  b& P  S! L7
    : b/ ?0 Q/ H( t& l5 _- g4 Y- c8- M  H$ H$ w% g$ M. l
    可以通过apply方法自定义处理函数:) U: x! Z' G+ `8 ~  i$ ~! o$ ^3 J
    s.resample('10D').apply(lambda x:x.max()-x.min()).head() # 极差
    4 B# g8 `# y" a  L: I8 Y
    ' P% o: ~& {, W9 |6 q" iOut[118]: 8 Q. h( r# {) V8 b# e
    2020-01-01    3
    2 ]" [  r2 K* I: J& E2020-01-11    4
    5 r2 O* \: O) g- ~# V, l4 b& b2020-01-21    4/ M% G& H) \8 c9 x% K
    2020-01-31    25 K4 J$ ~$ d/ @7 N, l$ x# {
    2020-02-10    4# S0 ]% i5 L/ Q( k9 y
    Freq: 10D, dtype: int32
    3 |* H- m# o7 G6 u0 c1& q. o1 ?' P6 s0 L# C% [8 U, I4 k  e
    20 E! W0 K4 F: n% C7 B5 B5 X/ t6 g$ N2 Y
    3
      y' O0 e/ t1 X. B3 p4; y1 g$ ~% F3 H7 w$ S# D( F
    5
    ! b% O6 H" `3 \/ p0 A' f! U6
    8 R, B* d! l% G& w, s7
    7 V* i! W4 J! [% H$ ~8 H5 g8
    . {  O* L" J  B0 w& x: M98 C8 @/ k: Z" A7 G+ b2 c" c
      在resample中要特别注意组边界值的处理情况,默认情况下起始值的计算方法是从最小值时间戳对应日期的午夜00:00:00开始增加freq,直到不超过该最小时间戳的最大时间戳,由此对应的时间戳为起始值,然后每次累加freq参数作为分割结点进行分组,区间情况为左闭右开。下面构造一个不均匀的例子:
    : c! d$ J& ~0 a4 ]; y2 i9 i5 M- E; d) Q7 {. }. \! v
    idx = pd.date_range('20200101 8:26:35', '20200101 9:31:58', freq='77s'). p! ^* Z1 }/ k9 c0 _
    data = np.random.randint(-1,2,len(idx)).cumsum()7 S6 V! a' h. j6 ~1 S# Z/ r4 b
    s = pd.Series(data,index=idx)
    9 w$ c/ k5 r: W7 Y# I) as.head()
    7 z& L5 [- L% l
    ( D- b9 a- N  \; s: }% p; @) i" F; H4 COut[122]:
    * e8 _1 U1 Y% u3 m( o4 E! W, w2020-01-01 08:26:35   -1
    * x8 }, C8 f" ?, p2020-01-01 08:27:52   -1
    4 C9 Z! e. J% D4 V- n, j2020-01-01 08:29:09   -2
    3 }! \: ^9 a; M( J7 t- ~2020-01-01 08:30:26   -3- R) e( P( C& v+ R
    2020-01-01 08:31:43   -4$ g1 f! G2 M1 k4 M. |0 |
    Freq: 77S, dtype: int32
    & i& G% ?2 s0 e6 b7 G$ b% S12 b* r1 }4 i2 n" E' S7 \+ s$ X9 O% k
    2
    & l6 C1 i( B5 I; H3
    2 k; \  r$ k1 ^' g4
    / k6 Q- c; H6 s$ U: e  H% m- t0 h5
    4 X! O7 G6 Z$ c7 ]) H# h: n6$ n3 h4 z0 R7 ~) A( T
    75 \# S  D' B. C: z1 G* V5 I
    8, E" p: e: L: w6 u5 d
    97 R2 \, R) d( L6 @$ x
    10
    & M* P2 l0 C# t: M1 Z6 I11& X9 A. L) ]$ V/ P9 l* A
    12
      d2 n- B! t; ^  下面对应的第一个组起始值为08:24:00,其是从当天0点增加72个freq=7 min得到的,如果再增加一个freq则超出了序列的最小时间戳08:26:35:+ Y* J6 B  D6 t' D7 M5 ]) p: f
    & g9 E1 @4 \/ `9 `2 Y7 y( p8 X
    s.resample('7min').mean().head()) s! [, ~, r/ ^3 x( N) E
    Out[123]:
    " O0 d5 {* [( ~2020-01-01 08:24:00   -1.750000  # 起始值,终点值包含最后一个值
    9 b  U) V: a# b5 t2020-01-01 08:31:00   -2.600000) A9 P- U* _  b* w2 ~
    2020-01-01 08:38:00   -2.166667. X" Q. ~  Y3 E1 A
    2020-01-01 08:45:00    0.2000006 p1 b+ w& G) k& ~
    2020-01-01 08:52:00    2.833333
    & A4 M( I9 O2 I+ i8 c4 {Freq: 7T, dtype: float64
    0 a" g* P- H* L# R* Z$ m10 ]! `8 G3 u2 n, J1 M+ Z/ n1 y
    2' ~  u3 _, K6 e. u7 ^
    3
    9 e' M! |$ N4 x8 o2 @$ w4 k" m$ @45 N0 E4 s" R( x1 j0 P
    54 Q: Q0 Y, ^- [, W
    69 b8 y7 X& A$ G' `6 o! J6 T4 i$ Z* T* h
    7
    . e# d# l$ l% Y# n8. x4 C) _% N0 [; S1 c# b9 H( }
      有时候,用户希望从序列的最小时间戳开始依次增加freq进行分组,此时可以指定origin参数为start:% Y6 L5 M8 e- E0 U

    - \$ L5 i7 X( G0 ps.resample('7min', origin='start').mean().head()- m1 e& x  r0 r
    Out[124]:
    , ]; H; K) P; O/ C# o& h: Y3 B; I2020-01-01 08:26:35   -2.333333& K, n8 v* \. i
    2020-01-01 08:33:35   -2.400000
    & u! Q9 `) N9 _* Q. ^2 F, Z' q2020-01-01 08:40:35   -1.333333& t1 I5 M. J: a' \
    2020-01-01 08:47:35    1.200000' Q( h. M; Y. ?  Z- y4 M
    2020-01-01 08:54:35    3.166667
    ! r4 E1 j& _! V1 j! `Freq: 7T, dtype: float64: L- L0 B+ h) p" Y' [6 _9 ]
    1
    + s% \" w9 X$ u$ o1 G# J23 l+ [2 a9 J7 G7 O" s/ S0 I
    3. v8 j. Z( u1 Y* s+ w. [. a$ @2 ~( c/ c
    4
    6 a- B. B9 w( e4 Y1 B2 _52 m% t& F" z4 P. V  y. W" ?
    6
    8 y' B8 S9 @- k9 o+ J2 O# R7
    . I' l, H- m  @& ]) X  D9 E; F8
    2 W# J% M+ e+ C0 b3 U8 X; l- Q  在返回值中,要注意索引一般是取组的第一个时间戳,但M, A, Q, BM, BA, BQ, W这七个是取对应区间的最后一个时间戳。如果想要得到正常索引,用’MS’就行。
    + W( W4 X1 w0 a4 ^# l: o! F7 J: \  ~2 h$ M
    s = pd.Series(np.random.randint(2,size=366),
    ( p5 r, J3 g( S- E              index=pd.date_range('2020-01-01',
    6 N9 q+ V2 E8 t3 s* b& d' U- P5 B                                  '2020-12-31'))
    ! P: s: ]. t/ ^6 b
    2 ?5 K# s  O! J9 e: a: \; D+ o, n  u/ s& V
    s.resample('M').mean().head()
    / P- N$ b9 a: x. E! M$ F, z' XOut[126]:
    2 T3 F; b& {" H% k, g1 T) J2020-01-31    0.4516133 ]0 S2 H9 V( x4 G7 Q) t% ?" X, U5 S/ _2 ~
    2020-02-29    0.448276& I2 W3 c7 p3 k( E& j
    2020-03-31    0.516129
    5 h$ u# M0 o% g$ r& J- U7 `2020-04-30    0.5666676 `& A( w3 l# T/ ]% @/ [6 T+ r% n6 O
    2020-05-31    0.451613
    . }6 i. m/ r) o+ }( t0 N' CFreq: M, dtype: float64! A$ e# n9 X% A" B* l% L

    ' J" b" O+ l. \9 z+ B2 [s.resample('MS').mean().head() # 结果一样,但索引是跟正常一样( C" {6 O, ]$ I( W
    Out[127]: : N, H- j; g$ \4 A" g4 ~! F
    2020-01-01    0.4516132 g9 |' M6 ~2 ~9 }7 }
    2020-02-01    0.448276
    + s2 ]7 F& T7 F8 V2020-03-01    0.516129" |2 C% _; A. a* F; L7 [
    2020-04-01    0.566667
    - Z/ d6 T2 {8 |, B/ q4 s2020-05-01    0.451613, e7 N. F! }' e+ N
    Freq: MS, dtype: float64
    " p' m) g0 o9 j# D9 _! }% V: |) U2 ?% j, M0 @4 u$ }
    1
    ' _: f6 F9 X6 u% P) e3 W* z2 \1 E29 {6 Y: D- b5 U9 k; N
    3. D2 n9 F; `4 Z; T- g( ?" Y% B
    4
    8 A; ]8 y( y; B9 [' e55 d8 |# X: Z* H0 L/ l
    6
    ' I6 F3 L) m1 l, O- X7 E: Q8 K  g72 {# i) S' O' `( q( m; y
    8* i4 g0 _% A% s# `: [1 n; }: k
    99 i- P6 d# @' x0 {& ]2 d
    10& ~1 c( g7 i) @, h* W
    115 F6 g9 N' y8 O6 G8 ]0 H* d+ U& ?& h
    12& x# T. C8 m1 Q+ Y% \# ^* i
    13* t# V9 ~, L# K% G: Q
    14
    1 C1 z/ H, x; Y& y7 h15
    5 D9 F0 l2 d$ M; l' |16
    ! i- ~/ O8 _- b2 m( a17
    # K# ?3 d3 |6 R* z+ c  `18/ s! I5 f$ t. K! m
    19
    0 m7 S* x4 `$ l# U( h20
    1 h9 k) P" r: }& A  I1 h21
    / m7 }2 v4 ?* r; s8 e. ]22
    1 b4 F8 [, B) t对于 DataFrame 对象,关键字 on 可用于指定列而不是索引以进行重采样:
    9 C3 v8 }6 Z- |9 _  k  Pd = {'price': [10, 11, 9, 13, 14, 18, 17, 19],
    ! J/ F* _! L- z8 ~1 `     'volume': [50, 60, 40, 100, 50, 100, 40, 50]}- H" m2 v' @: y5 R9 z+ F
    df = pd.DataFrame(d)! S4 j: v  L' `$ R. P  @" K0 ~
    df['week_starting'] = pd.date_range('01/01/2018',
    - e# i2 e, |3 m3 i                                    periods=8,
    6 A5 P7 L* Y6 x' G: ^/ l% X! Y                                    freq='W')
    ; `4 Z4 J8 [1 B4 L9 odf- q" @5 i' G. r" I
       price  volume week_starting
    ' M3 p( _( A+ i+ p4 S0     10      50    2018-01-07( j% D7 {4 V0 x' K5 t& p
    1     11      60    2018-01-14
    , N6 n) W" d4 v2      9      40    2018-01-217 v/ m# c; ^+ ^, J/ G3 u
    3     13     100    2018-01-28
    : q3 ]4 |. ^1 Z5 Z: F4     14      50    2018-02-04
    + {! p  Z) N$ L) e* B) A4 U5     18     100    2018-02-11: v8 o( |7 W7 c0 b
    6     17      40    2018-02-18. U: j6 Q- J- W* O5 i" @7 G' o
    7     19      50    2018-02-25' _- ^- A1 R$ M1 Q: H: }; u
    df.resample('M', on='week_starting').mean()! k9 v4 J% o( O- J) L
                   price  volume0 P; R( Q4 h  U- k: M4 t
    week_starting- ^+ F( L/ a8 J% Z8 Y
    2018-01-31     10.75    62.5
    " Z* K5 Q' ~  ]/ ]) m/ a. ^1 X* P2018-02-28     17.00    60.0
    3 n: I% M; x5 V( T. Z8 P
    ( T7 u4 N8 b1 f1
    ' y, F8 q! e6 K, t2
    7 S2 i/ l+ H& s& J, g) h& V3
    % v; Q) u2 F- `3 h( O4
    * F7 p6 `1 r0 s( v5
    ; t; S) U$ Q9 U* P& _/ R' C6
    & v3 W, n& m( h- p8 p8 s7
    7 ?' |6 R+ l8 \- O6 r. F9 ]8
      r  i3 I) U+ ~( S/ A4 a  A9  v/ M" r& U% L4 `% N
    105 d/ r3 l4 N0 W0 l$ T
    11
    ; L* U  i1 x: r1 s) n' `12: v1 m7 I7 @' s; L4 [+ N
    13; F4 Z/ R% O$ e$ o  {7 A5 v+ G8 h
    14
    , `  |. @( r) H15
    ( t7 E( Y5 S/ q. w& I$ G3 |8 G) A3 p160 p3 V' t) S+ ~' ]) g* W
    17
    , O* {4 \# t  m" L18
    ' W% Q+ U5 ^  D8 D* x194 b, W" Z- f' A  ^$ P* r
    20
    ( N/ ~( d4 [: B: M) Y, ?# ?21' ?8 h' g6 ~4 k8 @
    对于具有 MultiIndex 的 DataFrame,关键字 level 可用于指定需要在哪个级别进行重采样。) Y5 M* Q5 g/ n5 F6 D4 }8 Z
    days = pd.date_range('1/1/2000', periods=4, freq='D')9 G6 [9 L# l8 Y) J' Z# w' z
    d2 = {'price': [10, 11, 9, 13, 14, 18, 17, 19],8 _& L6 ]5 q5 V* T- V6 h& S9 p
          'volume': [50, 60, 40, 100, 50, 100, 40, 50]}; K. n: _$ g0 {$ P' g
    df2 = pd.DataFrame(
    ' E1 d! ~8 ]' h- s  ^# \9 |, x    d2,0 J9 I  q) ~4 f4 c/ b  k  h; t
        index=pd.MultiIndex.from_product(
    9 {2 b7 J* T8 o7 |* H        [days, ['morning', 'afternoon']]
    : M) L4 ~' N3 x/ q8 ?    )
    ) T+ t! i, i8 ]! e/ N)
    ( y* u. j8 X- r1 b1 ldf20 f0 m: s8 C- C0 W- \/ V
                          price  volume& J4 P! P' g' ^6 P( P
    2000-01-01 morning       10      50
    / l+ j5 n( L7 F9 u& r           afternoon     11      603 }1 d, C/ C0 B1 a
    2000-01-02 morning        9      409 G. U9 }1 F- R. D1 O) X, h9 `
               afternoon     13     100& A  a5 _4 I! O5 l  b3 L8 k
    2000-01-03 morning       14      50+ j9 r+ J. `8 m0 a
               afternoon     18     1008 u; @: g5 {6 L( F6 P) K
    2000-01-04 morning       17      40
    & B7 W1 I' U. ^) f+ r           afternoon     19      503 ~/ \) Q6 [7 {
    df2.resample('D', level=0).sum()- M& v5 T; S5 O6 e' g+ e. U* Q
                price  volume
    2 c4 f4 i2 m1 N( z/ {$ S- q2000-01-01     21     1108 o( d+ Z' X- o7 M$ a0 Y
    2000-01-02     22     140- a8 o! z& H3 m* M
    2000-01-03     32     1508 E$ _5 c; i1 I) W  R
    2000-01-04     36      90
    6 [% U, E/ X, ^1 s0 z, ?) b, K' C9 Q1 g4 u* g4 a: ~
    16 Y5 ]( m/ ]( g3 i
    26 E7 ~; m- m6 j2 R: E% {1 s
    3/ X$ t( G7 K5 ^. y6 Y, n- D
    47 v  o3 A# R9 C6 ^
    5# q+ a9 O( c7 x9 w
    6
    1 G7 B' k, P, V8 S- F7, w) T/ d( A2 t2 q% }6 @
    8
    8 Q6 F' w$ m0 k. I0 f9 ~9
      G' N& l2 h& c( j  N10
    ; M3 t+ T6 B* L6 b11
    2 c; E% A; e6 A; b7 R. V! C12
    - G  T" N1 q/ L" b$ S; e3 s135 u* z# t* ^, \3 ~
    141 k2 v* c" T# a# v$ G
    15
    ) F( I% J+ U# k3 I% v' n16% P, W* c! s0 {
    17
    ( J- I: {6 I, b) H! B189 c  o/ S  I$ n6 ]: Q
    19
    ' L% n- v8 D. s+ ^% I$ H20; [  ~# q- P: H8 \  b' r
    21
    8 Q; p* F5 v# E/ z+ p# x0 L228 @5 Y0 S+ m' b) C
    23
    7 w2 D+ m; `4 K7 Z. @24- P- k& u' L+ `  k3 h! _
    25
    ( D! e* F7 [, c9 G根据固定时间戳调整 bin 的开始:8 g' e* `& `& \, s
    start, end = '2000-10-01 23:30:00', '2000-10-02 00:30:00'$ B4 R; r8 p1 N4 u8 O5 T
    rng = pd.date_range(start, end, freq='7min')( ?: c: s9 M$ D7 {3 I6 J
    ts = pd.Series(np.arange(len(rng)) * 3, index=rng)& [' {% T5 e$ Y" D1 R( G- K: r2 m4 |
    ts
    5 y6 E  h- O9 M+ v2000-10-01 23:30:00     0
    : _% U. `6 }1 a  v- k2000-10-01 23:37:00     3$ \; G$ d% k* k6 |* k; _6 P
    2000-10-01 23:44:00     6
    1 d6 h* z' V$ a+ m; Y1 x, p; v2000-10-01 23:51:00     9( h+ {: R$ W. X& [5 a2 t, }* d8 l
    2000-10-01 23:58:00    124 u1 w( V. F% z' V6 ?
    2000-10-02 00:05:00    15
    $ E3 m# i# T0 }$ G2000-10-02 00:12:00    18
    9 ]* B0 t5 B# }7 a5 Z" o  E2000-10-02 00:19:00    21
      J, d) B( i8 H* D6 [2000-10-02 00:26:00    24
    . G. ^( s6 g9 e6 I9 T" I. [Freq: 7T, dtype: int647 P, }. D- D. a
    3 i/ s5 f1 H7 A% ?
    ts.resample('17min').sum()- {6 V4 t2 f3 b8 S: d/ p8 q$ f
    2000-10-01 23:14:00     0+ ?% _6 T9 T% n
    2000-10-01 23:31:00     9
    8 \7 D+ a! ~+ Y/ G: e2000-10-01 23:48:00    21
    ! S) v! y2 W% K5 g7 B2000-10-02 00:05:00    54
    / ^- d+ I3 U4 P2000-10-02 00:22:00    24! ?: G, v9 O0 _; N* j2 X
    Freq: 17T, dtype: int64
    + C. `0 \, S& J" D6 q! ^5 s/ ^- D4 S/ t, Q& @& F+ w
    ts.resample('17min', origin='epoch').sum()! t; Z. s' ^( Q
    2000-10-01 23:18:00     0" V! {4 B" `, H8 V6 C
    2000-10-01 23:35:00    188 m( t2 b. [3 W
    2000-10-01 23:52:00    27
    2 O! c- C1 p8 u0 S" U2000-10-02 00:09:00    396 [  x8 v- |9 z- J$ t3 Q
    2000-10-02 00:26:00    24
    : K" l, r$ B+ B# d3 eFreq: 17T, dtype: int64+ e, y& V3 _+ e4 m- a/ a# K
    % u4 f" l' w2 X$ \4 ?
    ts.resample('17min', origin='2000-01-01').sum()
    , ^6 U# T/ I% l2000-10-01 23:24:00     3
    * q! q+ e9 h: {- {! s2000-10-01 23:41:00    15
    + s" ]9 V; Y4 Z& U' I- ]. V2000-10-01 23:58:00    45
    + I: D# ]2 X. U+ K# |2000-10-02 00:15:00    45
    2 D$ y( @0 N) t0 H6 E! d6 uFreq: 17T, dtype: int646 _% l; J, b! p( [, R& v) i
    8 I8 h: g1 Q" c$ b- u
    1
    $ g2 b$ C6 L" u9 o; R2: v/ h, j# V2 @# w! a/ V9 I8 q
    33 P! S1 F6 Y6 a* A7 Q
    4
    4 P2 O) e3 w: o- f5% i; B" _( `6 v2 L# Q2 ^. J
    6
    5 ~& i$ C: G9 u# v. y7* T7 ~! F: n6 Y1 ]5 y
    8& d7 K8 j7 J' |2 R! r3 y- k
    9
    / D& ]. |" c( h9 O4 z10
    0 O7 h: I) S# h11! a) i$ B1 F! {+ h
    12
    - A7 Q, l' q1 S, A' N13
    2 U9 Y) D4 O$ u% f: s) X3 u142 v" [; [+ l- y6 p$ [
    15
    ' X$ G; v/ b& }6 {, B16
    % ^2 R4 y0 y  J2 K8 u17
    * {4 d% J# h/ `  v) ^18, k+ x) P8 s/ }$ {0 o, Q
    197 R/ J) z  \8 H6 G/ a
    20! @$ n- R# q" Z. G5 n$ q
    21
    * d& q) ?6 u1 R# h+ m) {, Q22+ d) V& j  v; T0 h; k
    23
    # |( J; ~! W# e5 W3 H24: [$ |7 W1 w0 H  [8 p' c3 I
    25. r3 T* _+ M* @9 J# L0 h8 h$ P
    26% I0 D9 U9 d: `2 r; l/ Q
    27
    9 G+ q) X! X6 M) B28
    " L, ~, g% `) M7 T( O9 x297 ?2 |9 \4 z6 u& }  M
    30" O% c- P; ~  t. J$ x' _4 z
    31
    8 f' y, S  }1 G( H6 {32
    $ e- M% P2 F$ b3 e8 x+ g, G6 \33
    ' Y( Y7 K9 V2 H2 @34
    . E* u( E" e, l3 x1 X- k35
    , }6 e  B/ U; H$ Q7 y; m36- e  u; d+ O2 a
    376 H* |" X7 l* t( G
    如果要使用偏移 Timedelta 调整 bin 的开始,则以下两行是等效的:
    # c' p3 L5 i0 {8 H3 ets.resample('17min', origin='start').sum()
    - x+ ]& @8 r3 bts.resample('17min', offset='23h30min').sum()
    ' R' g- q: ^/ z5 `2000-10-01 23:30:00     9" U" U: A) c, y3 z+ b/ ]/ a
    2000-10-01 23:47:00    211 e6 n) O3 j% o0 p0 W4 k; ^
    2000-10-02 00:04:00    54
    / S% l% j+ u* p. o$ P( l1 z2000-10-02 00:21:00    24' A; t! ^4 h: m7 o
    Freq: 17T, dtype: int64
    3 ^9 }9 i; C4 j  M" B2 @( D1
      d4 c0 c8 ^' `( g' g: ]. \/ J2. \# Q+ f# d5 C: _+ H2 y
    3
    " P& |/ \, k6 p/ e- N4
    : F2 r. V2 o: s4 ~) }! G51 l& Q8 H# T5 j9 |: h
    6
    5 h' A. a2 j9 i1 ~7 V6 p& F4 w7
    * d! f6 H) l: ^) {  @8 d" o' @5 [( {10.6 练习
    4 b9 P# c! Y0 W- mEx1:太阳辐射数据集
    * u: E$ s% |; |& h$ q8 ~现有一份关于太阳辐射的数据集:
    & R0 F, P: X& ?' ?& D9 _1 ~# ?
    / P& Y* P& ?5 \df = pd.read_csv('../data/solar.csv', usecols=['Data','Time','Radiation','Temperature']): ^9 N' k! V4 p) o
    df.head(3)! U7 ?* L  B- h" {$ h, g; W
      @, |4 m9 a$ O2 {. A2 w
    Out[129]: 8 I8 ^, F( S& s8 G  N1 ?: K5 h  ]
                        Data      Time  Radiation  Temperature0 A4 u) |) h4 H+ _9 B
    0  9/29/2016 12:00:00 AM  23:55:26       1.21           489 s4 X8 D5 G( q; w0 g
    1  9/29/2016 12:00:00 AM  23:50:23       1.21           48$ t- O) d+ q6 Q
    2  9/29/2016 12:00:00 AM  23:45:26       1.23           487 k, U. O8 {* i% t+ S3 U9 a$ M& k
    1
      x. q, }( j  H2
    8 c! S" e: A# p1 p% {3
    " O: ^* n) Q( _8 k, |47 P7 k& K8 o  z( v$ s* G" Q+ E
    5
    ) E7 b/ j3 [$ s" u. G$ d60 K1 \$ {5 E! ?6 O' u, Y
    70 M3 B& b8 {8 O6 y1 L3 o( `9 S
    8
    ) C: s0 Y; e' U将Datetime, Time合并为一个时间列Datetime,同时把它作为索引后排序。
    9 u9 x4 ^. N7 b2 v: y- W9 @. B每条记录时间的间隔显然并不一致,请解决如下问题:8 g1 _8 X& H8 Y9 K: A- }+ P3 [# ^
    找出间隔时间的前三个最大值所对应的三组时间戳。+ w/ U! A* O$ M
    是否存在一个大致的范围,使得绝大多数的间隔时间都落在这个区间中?如果存在,请对此范围内的样本间隔秒数画出柱状图,设置bins=50。
    & N& }9 j3 ^7 q6 V求如下指标对应的Series:
    9 u( f1 S# L7 c" q% \5 ]" ?# c温度与辐射量的6小时滑动相关系数
    0 D+ {0 H. Z: E  f" n0 Y以三点、九点、十五点、二十一点为分割,该观测所在时间区间的温度均值序列
    * G) ^  C% S' I- M  F每个观测6小时前的辐射量(一般而言不会恰好取到,此时取最近时间戳对应的辐射量), T& |2 Z  y; S" T9 M
    import numpy as np$ i8 H' x/ [2 j# J6 F( w
    import pandas as pd
    4 O, b  q' K* K" q. ]* o$ l1
    ( ^- D* D8 N+ C! w! V. ]$ y2/ E! |$ B  C* l( I& M; u
    将Datetime, Time合并为一个时间列Datetime,同时把它作为索引后排序。* F1 e/ a5 f8 I% X1 N, M
    data=pd.to_datetime(df.Data) # 本身是object对象,要先转为时间序列2 W5 c+ k, n/ w* S. O
    times=pd.to_timedelta(df.Time)7 L; k& l* f1 z* q# \
    df.Data=data+times: b2 f/ z( j4 |  u9 Q
    del df['Time']9 W& u8 ~4 \+ F$ _
    df=df.set_index('Data').sort_index() # 如果写的是set_index(df.Data),那么Data作为索引之外,这个列还另外保留! V1 V* `$ s& L
    df
    1 l+ v* e6 ]. p, L                                        Radiation        Temperature
    # {& `5 M- e3 \0 P0 m. OData                # @2 X8 N' E# d% T4 Y- z
    2016-09-01 00:00:08                2.58                51
    % @( n5 r& e  q2016-09-01 00:05:10                2.83                51
    5 z4 u7 U% i( r. L2016-09-01 00:20:06                2.16                511 K. _4 z! f0 e7 M
    2016-09-01 00:25:05                2.21                51. F4 _# m6 ]5 t/ B/ Z. b. T8 G* v
    2016-09-01 00:30:09                2.25                511 v( f" }7 ]) P  q$ o/ l. w
    ...        ...        ...: t- O/ v& H. _  e
    2016-12-31 23:35:02                1.22                41# Z# j9 U0 @+ Z5 }8 y6 Q
    2016-12-31 23:40:01                1.21                41
    ' q# b5 K. J9 ~5 N$ N2016-12-31 23:45:04                1.21                42  [# S5 K# k$ A- v8 ~1 b
    2016-12-31 23:50:03                1.19                41- \& O  U7 {- X
    2016-12-31 23:55:01                1.21                41
    9 J) J5 k: V/ p& q, Q2 I: P% W; }3 i
    14 G) X8 U2 c6 f# v
    2* G; {" z1 Q5 ]" n& R, w0 G
    3( S; {4 `4 o) Y4 R  ?9 T0 `
    4+ P( m' }2 |7 J4 N2 o3 y8 g& a
    5: s2 V3 O( O# m. H. _! O
    6
    $ ^! Y* |  z+ ^! s7
    6 R+ f* q2 H- l8
    " }9 v* h8 Q9 ?7 I- [9 z8 e9" Y; o, g; e1 z. W# k
    10
    ! G! i3 t1 K8 v11; u7 F8 b/ l& q. B
    12
    " k5 j/ z5 D) u4 A- G) c8 N13
    ! S! E( }1 n9 ?$ M9 L+ d14
    5 O5 e- S+ ]! i" o15) N7 j4 W( u6 ?
    163 @: K4 W/ S4 t6 ?! a
    17
    $ b% S9 c- x2 i! A18# |  _# b, [2 o
    19+ w3 U+ A* n/ ^* a
    每条记录时间的间隔显然并不一致,请解决如下问题:
    $ ^# I$ T9 o( j: q8 W6 c找出间隔时间的前三个最大值所对应的三组时间戳。
    - z/ l% ?- d0 y/ V. Z; o. |. f# 第一次做错了,不是找三组时间戳( R4 A: ?6 L( n4 _
    idxmax3=pd.Series(df.index).diff(1).sort_values(ascending=False).index[:3], n" u! e3 _& E/ N: ]
    df.reset_index().Data[idxmax3,idxmax3-1]
    6 T- o- p4 q( h2 ~2 Q' e- _! l0 i0 r
    25923   2016-12-08 11:10:42" ~3 n! ^4 Y# Q# C: I
    24522   2016-12-01 00:00:02
    / [0 ?. m% X' t$ a9 H( M8 q7417    2016-10-01 00:00:19
    5 r! {2 K; m. E6 yName: Data, dtype: datetime64[ns]
    3 t" u! h& N0 o7 p1: }. j3 A( M$ r1 t- ?& A1 O% ~
    2
      \! K6 u& z( ], m2 w* q( T3
    2 p7 T% U/ Y0 a7 w5 m4
    ; U+ k; s% {- P2 p/ f* U5$ W. q7 n- ^$ Y7 |3 l. j
    6
      h9 A/ k% f& ?/ \77 }1 q! X  C' I8 R/ H4 c
    8
    8 B' t: m. Q# J6 n: F  v/ lidxmax3=pd.Series(df.index).diff(1).sort_values(ascending=False).index[:3]
    ! p0 [1 }4 e2 e- r) w" I0 Z) }list(zip(df.reset_index().Data[idxmax3],df.reset_index().Data[idxmax3-1]))* z4 b  C, ^! S; R7 u/ [7 X% i0 c
    , g7 A: S6 n, [' b+ X
    [(Timestamp('2016-12-08 11:10:42'), Timestamp('2016-12-05 20:45:53')),
    " {. M# p6 s1 [% C: ?) | (Timestamp('2016-12-01 00:00:02'), Timestamp('2016-11-29 19:05:02')),
    + @1 R  Z; _* R' h2 n: A (Timestamp('2016-10-01 00:00:19'), Timestamp('2016-09-29 23:55:26'))]
    1 c4 B% k' s* F7 f! w$ ~1
    2 T$ {" P6 x0 w, f8 b% ~7 i2
    ) `, t# J. o' K' `) X$ ]3
    & j, y3 I. ~$ @+ o7 U) `2 s* b4
    . |( N& e5 P0 E  P5
    2 D$ h6 U$ b3 Z6
    7 q3 E+ [! }  M  t参考答案:- `% q( y+ Y5 c/ t1 H7 ?
    8 d5 q2 @* I1 _9 o
    s = df.index.to_series().reset_index(drop=True).diff().dt.total_seconds()( O2 Z% l, @* _
    max_3 = s.nlargest(3).index$ ^' g/ m$ @9 d, f
    df.index[max_3.union(max_3-1)]2 J; V2 s. u; H1 D6 J

    & I1 Y) M. }3 F" b4 M# w# z7 XOut[215]: , b" _# L+ _9 ?
    DatetimeIndex(['2016-09-29 23:55:26', '2016-10-01 00:00:19',
    , Q2 ]5 M" ?, s& d4 y  A               '2016-11-29 19:05:02', '2016-12-01 00:00:02',  p) {8 ~+ G) X- ^0 U3 L
                   '2016-12-05 20:45:53', '2016-12-08 11:10:42'],. R; e& O0 S6 x* y$ l
                  dtype='datetime64[ns]', name='Datetime', freq=None)
    / Z! w7 F+ {9 C6 L* u) I1
    9 U7 T- g6 r' \# L, ?- v2" f. H4 k) _: F) q, l$ e% V
    3
    0 N" d: g  j5 {% p; U4
    6 E6 h2 q' i( Y; u50 I% f, {3 w, v0 O
    6) v1 w/ ^1 U+ U+ g* V
    7
    1 I0 l/ B8 j+ z) v& W8% t, b' b# d5 @
    9
    ' [+ C% i# U( x8 E& Y! p是否存在一个大致的范围,使得绝大多数的间隔时间都落在这个区间中?如果存在,请对此范围内的样本间隔秒数画出柱状图,设置bins=50。- z7 M- c% `; f. k- f$ m. r; l
    # 将df的indexydiff做差,转为秒数后排序。再求几个分位数确定取值区间
    0 v  P9 I4 w. Qs=pd.Series(df.index).diff(1).dt.total_seconds().sort_values(ascending=False)
    9 L: \- E" F- s) F8 y2 B: ]) `s.quantile(0.9),s.quantile(0.95),s.quantile(0.99),s.quantile(0.01),s.quantile(0.03),s.quantile(0.05)
    % k& t! J- [' D6 L5 g- U: B/ @: k" x2 k9 N* G/ y) ~5 h
    (304.0, 309.0, 337.15999999999985, 285.0, 290.0, 292.0)
    ( T, _8 B8 D& a1' V" ?" J8 ~, }7 N" a9 Y
    2
    ; b1 G' F) h6 f, ?& Z! z0 }3* w" `0 l6 U4 w" J
    46 D/ y- b. Y9 }" v6 P4 {
    5
    - m; C% \" E' G6 x: O5 G( l; e, U%pylab inline  C) T3 O: Q! s* Y
    _ = plt.hist(ss[(s.values<337)&(s.values>285)],bins=50)8 Z) j) T9 p; U6 h% c
    plt.xlabel(' Timedelta')
      Y9 X) P+ I  _8 S4 ^0 wplt.title(" Timedelta of solar")" ?6 Y, m  W  k1 O
    1+ D& C5 w! c$ D% s
    25 q) c  K# L0 J7 y
    3) ^7 O) c3 G9 T# c& @
    4
    " k) \7 g1 |9 H" P! Z, P
    ( C( T. C  v8 I7 O. K5 J$ V' U0 G: {# ?8 W* W1 L
    求如下指标对应的Series:
    9 ~/ `" i$ d6 _" I' a温度与辐射量的6小时滑动相关系数. D( l' G* e/ [% |  {# ?# e1 M" \
    以三点、九点、十五点、二十一点为分割,该观测所在时间区间的温度均值序列6 L9 Q! J2 u) \4 G1 z5 r
    每个观测6小时前的辐射量(一般而言不会恰好取到,此时取最近时间戳对应的辐射量)5 y' w6 q$ x: P. y
    df.Radiation.rolling('6H').corr(df.Temperature).tail()
    0 O, O. H, X" @
    0 @' T1 i6 E) IData" ~: a! Z% q" ]  p% H
    2016-12-31 23:35:02    0.416187
    3 u% U1 Y& ^, v4 S2016-12-31 23:40:01    0.416565
    5 z2 g0 ?! Q  b0 ^! B  I/ _) g2016-12-31 23:45:04    0.328574$ Q  Z! Q+ L: m' a
    2016-12-31 23:50:03    0.2618837 k. K2 B/ ~, Q5 W
    2016-12-31 23:55:01    0.262406
    6 h( d5 b& n9 t- @: Odtype: float64
    + I1 y' J" j/ V: P, L7 @' [) G  r1
    $ Z5 T3 {8 P( l  `; h' {2
    ) h. `9 A% m: F5 m4 X: f3% _" |  s# I6 ]- n; \- W0 ?
    4
    # r2 S4 E" i5 A' v# a: \! Y! c5
    : ~+ ?' A9 l, s) d$ v6
    ! L. Y$ f: R) ~2 Y! ^6 z. |73 x* G  r! Y( l& h+ V
    8
    4 F+ c) c% [0 z( a. a: D) f" q9+ k) c; T' B/ w6 r9 K! ~
    df['Temperature'].resample('6H',offset='3H').mean().head()# Y% I' E' t$ e7 E

    3 x0 ]6 Y. ^2 d: s. ?8 y# m7 n8 r+ M3 IData
    , m7 U  j' `) [- l9 c2016-08-31 21:00:00    51.218750& |, E) a7 F7 G! D$ ]7 h% S
    2016-09-01 03:00:00    50.033333
    % g5 _$ H; q2 Q' Y3 K2016-09-01 09:00:00    59.379310: f5 W0 @( e( F# ^% A5 k+ R$ I8 \
    2016-09-01 15:00:00    57.984375
    7 p" x, e' ~: `! y  H- c& a2016-09-01 21:00:00    51.393939: k8 ?7 z9 r- x9 F5 X
    Freq: 6H, Name: Temperature, dtype: float64& i0 S) q& R! p0 T7 Q
    1
    # L; }; [8 H. F% g' J0 D2- A# c1 Y* ^  N$ A9 ~2 R: _  E0 Q
    3
    ( ~. j5 F% L' Y6 d0 E/ Y" j( S4# S: l0 t) W; E7 G
    5) B+ G+ |# g/ B$ S/ t) Z$ \
    6* L6 p+ E' w0 @% S
    7  t0 ~( t9 D0 u% O! S: P# v: f
    82 Q7 ~! `, L* y5 s( @5 a
    9( P! T( c0 E& V% |
    最后一题参考答案:
    4 x( ?% l/ i) f* f
    ( U& z! |; T% e* j' M' [# 非常慢! [4 |9 U5 R) h
    my_dt = df.index.shift(freq='-6H')5 [1 |: ~9 G8 T
    int_loc = [df.index.get_indexer([i], method='nearest') for i in my_dt]+ J8 |  q% t2 [+ I: B& ~4 I
    int_loc = np.array(int_loc).reshape(-1)
    ) o% u$ s8 s1 r( G. ^res = df.Radiation.iloc[int_loc]
      p; f/ y, M; S/ R2 |; G5 Lres.index = df.index
    + N: @( L, l5 _! F4 s) \1 Rres.tail(3)3 l) N2 p3 M* |! D3 J9 d
    14 P0 J, Q4 z, U' j8 ]
    23 W) ]  V0 @, d8 }: s
    3
    6 h2 N' K% I9 \* `- f2 O. E+ F4
    ) K% Z, W7 V% s7 C7 Z4 Z3 L56 ~! ?/ w$ b5 l: E/ f# ~5 z
    6/ |. A/ |4 j! r  f6 }& }, N+ L! [: a
    78 a& P' J3 D, z/ S+ B/ E% k
    # 纸质版上介绍了merge_asof,性能差距可以达到3-4个数量级
    3 |+ K  P7 Y) P. `! ^target = pd.DataFrame(6 }+ @3 J. ?3 r5 G+ N- R
        {; b. H# c# B2 [1 M
            "Time": df.index.shift(freq='-6H'),% n4 K- a) t6 X* B9 I: V" j
            "Datetime": df.index,/ F2 Z+ I+ f5 w' m4 U4 m$ s
        }5 ?2 Y! s$ w. K, U& B! k
    )
    0 A0 @6 V: w9 T$ F6 s7 N. J5 e6 R) o. l. y% T& a: K0 ~' n
    res = pd.merge_asof(7 d" {! L+ W; W, g  U2 `
        target,
    1 U, F. S& i; A7 g: e$ K# V    df.reset_index().rename(columns={"Datetime": "Time"}),
    8 t. R4 K$ g- l- }, \3 t6 W    left_on="Time",# W: c. v" {0 e2 `9 ]
        right_on="Time",
    + D$ E* h+ X. {9 O/ C2 A5 b    direction="nearest"8 n) y( p$ E/ O5 h9 _5 Y
    ).set_index("Datetime").Radiation5 b; i4 \1 p4 a
    : `5 p7 q: D  r& q# R9 [2 s9 w
    res.tail(3)
    ' H4 U( \: |* g( rOut[224]:
    4 C8 @2 L* C+ y) ZDatetime7 Q- x, M2 R) r4 r. `9 F
    2016-12-31 23:45:04    9.33# G; n# l( ^/ ]. a
    2016-12-31 23:50:03    8.49
    6 H/ ]0 Z$ e, \7 b8 }5 s, \2016-12-31 23:55:01    5.84
    $ @4 z- ?4 D8 X! x( \' QName: Radiation, dtype: float64
    $ P" k# U: u  {- {& v7 R) ^& D- X0 m, }( T/ {
    11 k# t! ~9 g8 w1 w* h& o
    2' O( T1 `4 K9 V" x
    36 J7 f8 j' d0 s. G  b9 z& v
    4
    1 P3 G* I9 {& _; G% I  X: ^1 c7 A9 S, ?9 t5, g0 r$ Z; J3 b0 L2 ^2 C  p
    60 K9 \$ k3 ?6 ?4 f! b) p
    7$ n  S" B3 J$ `. g  H
    8
    * S% `+ t& y/ T+ ]% I9% a0 B: R% K  H! ^2 N
    10( U, U, p$ x3 b9 w# V, ?
    114 j- t; T- N8 x- n, ?
    12
    7 F% R; w0 m3 R; L7 G& A% f13
    : \! V0 b" v# w14& }5 X, ^/ m: b7 L
    15
    6 c* |) f/ h1 J16
    2 K' i/ O) b4 B- T8 d# _17
    / o) o1 d# a3 ~: Y7 e18
    $ s3 ~/ x$ ]/ E19# ]5 z7 \- V# G$ b- F1 [
    20
    , B% y+ a) Z& D/ b1 ~4 z& y21; d3 I0 _& e1 v; g( `8 M' k
    22' V3 {- ?# e# y! U# _
    23
    " r6 q9 ?  q( nEx2:水果销量数据集- {# s9 o' m- J5 Z$ H' z( t  g) }
    现有一份2019年每日水果销量记录表:
    ; |# ?5 K3 u( W. y# ~& r2 o
    # P$ S8 d6 `& @5 J7 [, ddf = pd.read_csv('../data/fruit.csv'), @  j! L; M7 g- @* K5 U
    df.head(3)
    . D+ q* J$ z; y# M3 j( w+ i: c" T8 a. i7 P8 Z
    Out[131]:
    # Y' M& v5 W  d. Y: n, \5 d' C         Date  Fruit  Sale
    ! H( B) H1 e: p( ?  _0  2019-04-18  Peach    15
    4 ~8 r! {$ w8 _7 {# r# r8 a1  2019-12-29  Peach    15( q4 {6 k/ Z6 v& u: Z
    2  2019-06-05  Peach    19" }; h1 K% i. M2 J! `/ A
    1. A* u4 O7 {: Q1 i$ I
    2! t" G. H( c) i( j/ n
    3) e: L' U/ S% z8 E
    4
    0 U; i' y" M, J/ K5 Q1 @7 l55 ~6 n6 \5 Z+ P, I1 |4 S' G
    6
    5 @/ N2 ^2 X6 O; O/ u- r7* r  s: u4 h. b) H. F# e! X; U# F
    84 ~" {# b0 _" [, B# x7 U. n
    统计如下指标:
    8 j$ ?8 ]# }$ ^( ~9 v& ^0 B8 |每月上半月(15号及之前)与下半月葡萄销量的比值, J6 Y! A7 @7 E! N/ u1 M" v
    每月最后一天的生梨销量总和
    7 E# z. j5 {" i" J2 I1 `每月最后一天工作日的生梨销量总和
    ' D: l9 P6 C+ r+ L0 z! i每月最后五天的苹果销量均值6 F4 T8 r% J. B0 r- Y8 \
    按月计算周一至周日各品种水果的平均记录条数,行索引外层为水果名称,内层为月份,列索引为星期。
    # q+ k) W. ^9 Q6 `按天计算向前10个工作日窗口的苹果销量均值序列,非工作日的值用上一个工作日的结果填充。& y- Q$ J8 E& {, `  E" O
    import numpy as np
    ; t- O7 P6 e& c( _/ dimport pandas as pd/ p  x# Z: n$ A4 p( C' h# Z
    1
    - n9 |* s& C! ~0 E  F2  u* S% T( x& x* P" K3 _$ C
    统计如下指标:
    ) t$ f' D+ v4 s9 J7 l每月上半月(15号及之前)与下半月葡萄销量的比值
    , k& \4 ?& j) p4 _每月最后一天的生梨销量总和
    + u" v. Z: t  c* o每月最后一天工作日的生梨销量总和7 W, ?" |; o' L) p0 \, A6 K
    每月最后五天的苹果销量均值, }. p: Q( o' D! x% w! d
    # 每月上半月(15号及之前)与下半月葡萄销量的比值# k' t( A, |1 P8 B
    df.Date=pd.to_datetime(df.Date)* w8 C6 c) l8 T& @# w' u6 @
    sale=df.query('Fruit == "Grape"').groupby([df.Date.dt.month,df.Date.dt.day<=15])['Sale'].sum()6 L3 N. {; {, N+ F( }' i
    sale.columns=['Month','15Dayes','Sale'] # 为啥这么改没用啊5 r3 J; P8 P7 m* u" w+ d
    sale=pd.DataFrame(sale)5 y2 ~  i+ i  y$ l/ f
    sale=sale.unstack(1).rename_axis(index={'Date':'Month'},
    ) f/ y; C5 }" H/ x5 x  r  n                 columns={'Date':'15Days'}).stack(1).reset_index() # unstack主要是两个索引都是Date无法直接重命名2 p" A3 }$ g' M( ^- m5 J! P
    sale.head() # 每个月上下半月的销量
    : }4 |0 |% R; B9 T1 g$ H. v$ a, L% @& }9 y4 O* q7 B
      Month        15Days        Sale
    5 n* ^2 |. \) r9 t* R5 D6 n  D3 w/ |, ^0        1        False        10503
    4 |% F' Z# h0 T% F- c6 L  c* v7 _1        1        True        12341
    $ Y  ^" {5 z" l' i$ D& ]2        2        False        10001
    2 N/ }: n7 b/ g% Q$ {3        2        True        10106. y; {; R8 T3 G" l. l+ B" L1 K( b
    4        3        False        12814
      Q; ?' x( g1 `$ E. ~4 a% x& m) ~6 k0 Q
    # 使用自定义聚合函数,分组后每组就上半月和下半月两个值,根据索引位置判断求比值时的分子分母顺序% }6 }9 {, [9 w. L1 \
    sale.groupby(sale['Month'])['Sale'].agg(
    # k& V, }* y: F( X7 |" j                lambda x: x.max()/x.min() if x.idxmax()>x.idxmin()  else x.min()/x.max())9 l6 P0 j9 P3 V1 U: k; Y

    * L# l6 L5 ?- {' E. ~! tMonth( H3 F' r5 B+ r4 d
    1     1.174998) H& q* Y1 s- \. k$ t3 D( d
    2     1.010499: @, n+ c4 |7 P  l: {
    3     0.776338
    ) i5 ]) g7 b+ H3 E: m1 z4     1.026345
    " Z9 L0 H: X1 ]/ b. f5 W/ @; _5     0.900534+ i% D+ o! [4 F( n3 J/ F
    6     0.980136% ~- M' F6 j/ W5 S3 a2 E( F
    7     1.350960
    7 v2 S0 Q3 g! R  M' n( z. q8     1.0915841 g7 V3 J! e3 z' O. h% v
    9     1.116508- \- w# u0 a1 f' O9 h
    10    1.020784
    - }! i2 d4 I  {1 |4 z1 d9 a* `" o11    1.275911
    3 J# e7 B- P# Q  R  d% V% z12    0.989662( i. a3 J) m7 Y  p  {. x. w
    Name: Sale, dtype: float64
      v* b& M  ?8 r& G2 Q9 }1 L6 P
    ' G. ]" f: q/ z1 y3 z+ s% i1
    . v2 e7 p& |8 u+ k2* Q+ S6 x8 L: h
    3! w, G4 _* X0 v  x6 Q4 @' z2 A
    4
    - X0 n, D( K/ q2 ~! Z7 N55 Y0 M, X+ M) d5 @% s( O. u6 y2 E
    6
    0 ]& t' p9 f+ i7
    7 T: H5 t, U# F+ v8
    1 O9 Z' l9 d/ `, b92 S! x, B2 ~- e9 D0 h) e
    10
    ( g* R  n3 S( h' b- n) ?( y7 A( _11
    5 t# Z4 B# l8 i1 @; B$ u- G12& F8 T% i/ f1 V; J) P
    13$ R! @5 p. E; B. y& |) k- a6 F* e
    14) |) t0 s# i9 h" |7 E
    15# Q6 ?# S; X, l$ ^
    16. y8 c3 X- l" A1 {7 X- J7 n$ v) }
    17" D1 ^/ g$ M8 Q% K/ j: u* x; q
    18( |$ ^+ I; l4 t. o4 V) `' U
    19' s$ Q% C$ q) W; G; \6 }  [0 `
    20
    . u! Q, F2 a& ~21
    8 |/ A/ r( W3 B5 ~& t. \$ M' ?22
    ( p! e9 L" [; U2 d0 n* e% j5 u& N1 v2 H23
    3 r/ F9 N" `3 a242 j) e& I+ M$ M$ t0 Q; N
    256 C. Y& _$ z9 W( H( t; Z2 I# v& k
    26
    ) `( i9 E( [& v( {, U/ U: p27
    & r$ m; H0 V: R$ @' J3 N) L' L28
    + y: {; x1 q0 Z" c& |! A1 a. i29
    6 w3 U8 n2 S+ V' U30
    6 L- ?+ x6 M5 H' Y$ m. m! O% V31
    : h; @+ O; D6 Y9 H7 C! [32" b) \' {3 ?) i: M% \+ g" Q( G
    332 |% p' _* }/ F
    34
    ' [' X5 C4 U7 g) q# 每月最后一天的生梨销量总和1 j5 X& O' R4 i' a9 G& l1 l$ [
    df[df.Date.dt.is_month_end].loc[df.Fruit=='Pear'].groupby('Date')['Sale'].sum(); E" F5 J) x( C, H' ~

    7 W$ R* R4 Z4 Q. D+ D; H4 i$ N: uDate' B* M6 }* X+ X
    2019-01-31    847
    7 i+ t5 b" ^( j6 A' x2019-02-28    774
    ' W1 Z$ S1 u; A2019-03-31    761
    8 n' C. }6 [. I: q/ P2019-04-30    648/ ^! Y/ o! p+ {+ c/ G- v( F
    2019-05-31    616
    / c4 h9 {, C0 K+ k3 p1
    , M' W1 W6 K; G6 h2
    3 n" [9 L0 [. J' ~7 ~3 j0 ^3
      C& J; [! f' v. f4
    3 a) Q3 {6 I; N! Y- t! R5
    $ |) P0 \5 [( ~5 ?7 v3 I5 S6
    8 D! e1 M; H& G3 T7
    6 C+ O7 K; a+ |& z0 V" ]* w8/ c9 E: f) I8 v! Z6 c0 X+ T
    9! h- v. c+ ~' l( }6 S+ ^
    # 每月最后一天工作日的生梨销量总和" Y1 I; F6 D' L  m: N& U, J
    ls=df.Date+pd.offsets.BMonthEnd()
    ) A" F8 [+ j  v7 l' m- Rmy_filter=pd.to_datetime(ls.unique())
    3 T" @. \* h+ v( {: S4 \df[df.Date.isin(my_filter)].loc[df.Fruit=='Pear'].groupby('Date')['Sale'].sum()
    : A; g1 N0 l+ ~/ H3 G- Z7 _. P/ m8 W/ P" A8 n6 }# |; L
    Date1 B6 G1 i2 [* M2 t
    2019-01-31     847+ b, ^/ k4 \, C, ^
    2019-02-28     774$ @0 {8 H. Q, M8 F
    2019-03-29     5108 q6 B5 l* Z. c* b1 y2 `% `
    2019-04-30     6487 N  f# ?6 E3 j: ~2 X9 y: y
    2019-05-31     616
    4 T) Z) m) C7 J! j* \6 c0 H* B) }7 r1
    ) d3 S8 {2 j6 F- ]$ n4 K  B0 J2- d3 }0 d1 d' n  H, |( ^0 h
    3
    & p/ E4 c: p; P2 ^4
    ! P& _$ }" s, Q7 T52 ^5 X; W/ `# r
    6
    # b; J3 X9 ?$ b+ v: A) U75 z$ s( _& Q# h* x, v
    8
    & H* t' {. A4 h. F9 c0 b. g9
    # y# N1 H% q! A7 e4 R( X7 Q& G2 u9 ^. N9 \10
    ! H6 F" ?% Q( w! d. X11
    " H4 Y; w$ G7 m8 I# |* v( D# 每月最后五天的苹果销量均值
    ) O; ~; N) P% G% V2 r  b+ Gstart, end = '2019-01-01', '2019-12-31'
    : c- n4 Q+ g4 cend = pd.date_range(start, end, freq='M')
    7 R( v, C7 `5 W7 `; T9 ?end=end.repeat(5) # 每月最后一天的日期列表,重复5次方便做差
    : W( `6 F4 q) i+ B% T
    # |0 o' p7 l4 P3 m4 a, C  otd= pd.Series(pd.timedelta_range(start='0 days', periods=5),)
    . |$ ?1 f9 O. R1 W! j2 U4 ^td=pd.concat([td]*12) # 日期偏置,最后一天减去0-4天
    7 {( i1 G8 m; u( o7 W; P$ H% t/ pend5=(end-td).reset_index(drop=True) # 每个月最后5天的列表
    / N- t, f' \$ v0 P  ]# h2 E; h% v. @; j5 g  k: D- J* j
    apple5=df[df.Date.isin(end5)].query("Fruit == 'Apple'") # 每月最后五天苹果销量
    3 I1 C1 W, e9 K/ ~apple5.groupby(apple5.Date.dt.month)['Sale'].mean().head()
    . H) W: J+ V$ N% y  o# y
      d  z4 e6 @3 A# @: y2 ?% E- L' `Date
    , K( R7 J( S' [/ ?1     65.3137257 ]- i& @4 e2 G& b) \$ B) l# v" s1 q
    2     54.061538
    ! E' ?& ~+ E4 l0 m1 Q% ?  r* ^3     59.3255817 r' B" t$ @$ A& Q. E; h9 `2 Z" T
    4     65.795455& d: o/ ^* U7 H/ ?" o0 S" d. j0 h
    5     57.465116  c. Z3 R/ w7 l6 F! H
    6 ^. ^" Z! F6 n/ G8 t
    1
    & }# n8 M0 `/ L5 w' Y3 f0 u2
    4 f8 G  }6 {0 y( k; c39 m3 z! }  W1 p! ]( a6 q- b1 }
    4+ \: d8 j  N; ]- E$ V6 J
    59 w* n+ c6 g9 R
    6
    9 N8 t3 Z8 v( J! C. W71 Q, K- s1 d' @. H
    8. v% S" B$ w" k, s" c; S: @' N
    9
    ) q" n% o! b, y10- l1 U9 s" W4 S! D/ I
    11
    $ N, f8 U. @/ `  T+ Y127 f# C# L, \+ U  T9 P$ I
    13. v* `: v0 C/ p$ x: S3 S/ \. @8 h
    14- q3 D. s. ^0 n2 c( B# y$ n6 H
    15- p+ r8 ?% \. X( j- k' ^6 y
    16
    2 i( J' O- b& \7 O0 k6 Y170 C6 R" c: A* L/ ^9 V
    180 _3 v' T" K  k% {5 E8 ]# _6 v" d
    # 参考答案:" n% T! W& W$ g* [
    target_dt = df.drop_duplicates().groupby(df.Date.drop_duplicates(
    2 K' Q' ~5 R' W, y  k; S, Y3 Y            ).dt.month)['Date'].nlargest(5).reset_index(drop=True)" @2 m2 Z2 f2 D
    ( V. P: V8 K4 v! q, r
    res = df.set_index('Date').loc[target_dt].reset_index(1 [7 U: g  U- k6 w$ E3 r7 Z/ q
                ).query("Fruit == 'Apple'"), e2 y5 N( l5 p3 O1 r  u

    5 v" g3 B0 t. s9 P0 q  Hres = res.groupby(res.Date.dt.month)['Sale'].mean(
    $ [, o  M) ?8 z- n: b5 H            ).rename_axis('Month')  ^5 u: h( |4 H/ T( U* B
    2 }3 M, s# [8 o1 t

    % }' \1 _$ T- _% p. `6 gres.head()
    8 h! K3 {, z0 n  n  ROut[236]:
    2 A/ g- [) h- y2 q9 r% I1 bMonth( |+ [; {1 c: k( D  F) s/ K
    1    65.313725
    4 Y- G. R( |) g4 M0 n( O) X2    54.061538
    # V  H) U4 [) [3    59.325581: b# t6 L# u* Y! n9 F5 M
    4    65.795455
    " A& d* s6 e* {% m5 W1 L3 E5    57.465116
    / N+ `& O$ c- z/ gName: Sale, dtype: float64
    5 [- j: n6 B( w/ m0 t  G4 q4 }" k! S, O+ Y& X
    1
    & A9 f- b7 Z6 G5 ]$ R+ r4 c) T4 l24 [0 `) F+ j9 z5 v" g, O
    3; S/ I' o3 a; C( \
    4
    / `! y; P. T, L& w4 y3 H  Y' C) x9 _5
    : V1 y' d" f: ^8 K6' |; i5 A2 E- g3 [& B( A- i
    7
    2 r7 H2 S) P7 K% j' X5 S8$ u6 S' d# i) M1 c, e! T' f
    9  i8 E8 \- B+ a7 i1 U9 u( w6 a
    10
    7 ~+ y) L# j- y/ m& \, ]5 @11. m' R  N7 m" D0 ?* {8 j/ r: [
    12
    * h! C0 |6 {1 P" Z13+ C! D9 R# O& V
    14
    * y9 w( S& n6 q  f156 h1 l0 q( G9 Y0 P4 o6 Q, j' W
    16. D4 T) l( [$ e7 B
    17
    6 f  d) T, u0 X184 g* R- l. f% d2 m- E# K9 h4 h
    19
    4 [$ u. ?' ^  m) j/ c: c9 q, [20
    ( F, M5 m+ H! T9 S按月计算周一至周日各品种水果的平均记录条数,行索引外层为水果名称,内层为月份,列索引为星期。
    8 }, q2 K! q( P. g4 X9 z. @: Oresult=pd.DataFrame(df.groupby([df.Date.dt.month,df.Date.
    , f( x$ S2 [' B: K+ E$ p0 w" Y( _& N                                        dt.dayofweek,df.Fruit])['Sale'].count()) # 分组统计 5 O6 L; R  p7 Y6 }0 X7 T
                                            ! o, k( z8 T1 G, P9 d: U7 p
    result=result.unstack(1).rename_axis(index={'Date':'Month'},
    : S' @2 ?: J: v& M4 f' S                 columns={'Date':'Week'})  # 两个index名字都是Date,只能转一个到列,分开来改名字." G+ v& m+ n- M' q
    result=result.swaplevel(0,1,axis=0).droplevel(0,axis=1)7 p- p# Q( h% S7 h5 s! ?% ~
    result.head() # 索引名有空再改吧) r1 C5 i; `7 q8 ~' r! B' `

    : L" o9 l  i' V          Week        0        1        2        3        4        5        64 x: `# ], b- G$ a
    Fruit Month                                                        ( a7 f5 i1 z" w3 s% b
    Apple        1        46        50        50        45        32        42        23; r( @# x, B* p* c5 [! q" F
    Banana        1        27        29        24        42        36        24        35
    6 P4 V" U/ H5 O; r. p. CGrape        1        42        75        53        63        36        57        46
    + T' A+ M; T7 m3 }& k" vPeach        1        67        78        73        88        59        49        727 A$ p0 ]' J, Z0 [" @
    Pear        1        39        69        51        54        48        36        40
    6 N/ v: q7 ]4 s# h/ y1. V5 |* k+ V9 W  n1 @
    24 ]/ Q7 L+ I4 c! }& V; ?
    3
    . T6 Y6 F1 ?- h9 ]8 a/ h4; |/ G1 L6 N. L8 h4 W8 ~3 g
    5* Z+ z* c$ O% H
    61 O+ O5 d0 g3 a/ ~0 ]
    76 y1 o; w& x7 B" D5 \0 x) `/ J
    8
    * C2 M+ |8 L& |5 r9; \5 E$ K/ u/ p4 e0 A- k- ]3 F! Y
    10/ j/ s3 o6 c5 I- m0 a
    11! H) I' a. s$ Q9 [. T
    12
    2 J& f: D+ d! s4 p132 C" ?  w9 O! F: k4 \9 I
    14! `- n9 m1 Z3 c* `$ _
    15
    . ?' C0 C' [$ H: ~2 E0 B  e按天计算向前10个工作日窗口的苹果销量均值序列,非工作日的值用上一个工作日的结果填充。5 |6 j: M; c* \3 Y% I8 W. V
    # 工作日苹果销量按日期排序; h- j0 E0 O7 t: M3 [  s
    select_bday=df[~df.Date.dt.dayofweek.isin([5,6])].query('Fruit=="Apple"').set_index('Date').sort_index()
    * n% d, b& b5 k4 ^! Iselect_bday=select_bday.groupby(select_bday.index)['Sale'].sum() # 每天的销量汇总
    8 J, {. L  ?0 f2 m- S; O, a2 Sselect_bday.head()0 C( T1 ~) e0 g) o. m9 H( I
    5 m! X! f7 R/ }5 _
    Date
    % V1 ]/ S( S, U% n; C2019-01-01    189, \7 w7 B5 J3 F5 A. r5 ?+ ?$ f
    2019-01-02    482( `" H( v. W( E2 P
    2019-01-03    890
    3 q& D% |' Z: V( n  ]2019-01-04    550
    ( G8 M/ B; _# ]0 o5 _" J2019-01-07    494/ ^# r; r) ?7 R

    7 q' s6 J1 u6 v/ p# 此时已经是工作日,正常滑窗。结果重设索引,对周末进行向后填充。3 P% }* B! G/ A! j# g
    select_bday.rolling('10D').mean().reindex(df.Date.unique()).sort_index().ffill().head()
    . j  B9 b: ~8 _) W' P& J, Y
    2 V# K: C  Y5 m, U8 Y* oDate; d& Z1 p# c1 g0 H7 d! d4 S
    2019-01-01    189.0000001 F+ K( ?* K- Y- p
    2019-01-02    335.500000- d! [8 L6 u  O
    2019-01-03    520.333333
    # r- f1 o# F, l/ k2 R2019-01-04    527.750000
    9 F& _* r' q  x) M8 y2019-01-05    527.7500001 O  I( {3 P( A7 ]: Y

    ( T* i6 {- i2 R+ m————————————————' [5 D. {1 P# l
    版权声明:本文为CSDN博主「神洛华」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
    ; t0 Z# r, k8 z+ r, w1 i原文链接:https://blog.csdn.net/qq_56591814/article/details/126633913
    ; u! J2 U7 L* k$ u" o% }; b
    % [' y/ [) f1 q$ Z; q  z& G6 n  y  _
    3 D$ c; {" p( M$ \$ T
    zan
    转播转播0 分享淘帖0 分享分享0 收藏收藏0 支持支持0 反对反对0 微信微信
    您需要登录后才可以回帖 登录 | 注册地址

    qq
    收缩
    • 电话咨询

    • 04714969085
    fastpost

    关于我们| 联系我们| 诚征英才| 对外合作| 产品服务| QQ

    手机版|Archiver| |繁體中文 手机客户端  

    蒙公网安备 15010502000194号

    Powered by Discuz! X2.5   © 2001-2013 数学建模网-数学中国 ( 蒙ICP备14002410号-3 蒙BBS备-0002号 )     论坛法律顾问:王兆丰

    GMT+8, 2026-10-8 09:45 , Processed in 2.277328 second(s), 51 queries .

    回顶部