QQ登录

只需要一步,快速开始

 注册地址  找回密码
查看: 2760|回复: 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
    6 V8 i$ B! v1 f: E* e( `
    2 o' n/ Q8 R6 t) w3 Z) n

    - z* Y$ F, J. q$ y0 O文章目录! X3 A3 {. ]' L8 u2 ^) ~. }
    第八章 文本数据
    9 B$ v! \! Y" O; g$ Y8.1 str对象- @! s, t6 F7 L" o- Y7 P( I
    8.1.1 str对象的设计意图# f5 d: N. t) z4 [, m6 p
    8.1.3 string类型
    ! v1 }9 O$ p8 [1 f" x0 e: R+ W8.2 正则表达式基础
    * N" X# O: K) B* W# c, u8.2.1 . 一般字符的匹配
    - P) K( e  ?. f4 a! ]( H8.2.2 元字符基础& r  u* |' ~5 g/ J8 |, C( y6 K! @
    8.2.3 简写字符集
    + C0 i+ j3 `! W1 q1 {8.3 文本处理的五类操作
    8 B, O& [7 y' a6 d5 ?7 H! m8 G: y8.3.1 `str.split `拆分2 F) {5 r, C6 U! P, R7 R$ W9 C
    8.3.2 `str.join` 或 `str.cat `合并0 A3 D' [& Z% M( K
    8.3.3 匹配$ v) E& ?$ i' B
    8.3.5 提取
    0 `0 n1 w' d3 @9 S) C% p8.4、常用字符串函数
    ) \  \6 S- @4 L1 s" v; e& B8.4.1 字母型函数
    . j- v5 Q" D& S% o+ o4 ?/ q3 i8.4.2 数值型函数
    9 t6 K+ f; p' J' z1 J8.4.3 统计型函数
    3 i" P# L" M4 ?$ [6 A( W, x7 [8.4.4 格式型函数! H! m4 F! [8 E4 n. Q
    8.5 练习
    & u6 M, r; ]0 @- C; L$ YEx1:房屋信息数据集
    4 ?! A) s/ X! QEx2:《权力的游戏》剧本数据集# U4 Q  ~; k  T4 r) q! p
    第九章 分类数据
    7 a* O% h/ y- a# H- I( Y0 r. }& H9.1 cat对象7 B' W9 v, {5 @/ l# E: ^0 ?! S
    9.1.1 cat对象的属性
    " V1 H! W8 I; o# M! T9.1.2 类别的增加、删除和修改
    - r3 J4 ]* `7 S. r+ y" d. }6 U2 m9.2 有序分类
    3 J+ x) U/ L8 p! m9.2.1 序的建立2 \" P# t2 |6 F5 u- n" _
    9.2.2 排序和比较
    ( T: H; _& e1 Z+ ~. a6 B& D9.3 区间类别! d# S9 V9 h1 m6 e
    9.3.1 利用cut和qcut进行区间构造$ D9 E& m' t+ r* f. P6 K' q
    9.3.2 一般区间的构造. h* e/ Y" O7 j8 w6 t* G
    9.3.3 区间的属性与方法
      K/ Z9 o! B2 r/ u9.4 练习
    4 Y% G9 I) I; J; l6 iEx1: 统计未出现的类别! g9 V4 C; o' b- O% V3 _. g" N' c
    Ex2: 钻石数据集
    $ f) x. I! _! `& j1 c第十章 时序数据
    3 [; w  l; S$ O! E# p10.1 时序中的基本对象! Q' N% a4 R) i. `* k$ c
    10.2 时间戳
    6 Q$ w- }( |" @- c10.2.1 Timestamp的构造与属性+ V/ o% q) Y) |
    10.2.2 Datetime序列的生成. r- a0 e; |% d8 q+ A
    10.2.3 dt对象
    # K3 N5 u0 M& D  n. o10.2.4 时间戳的切片与索引
    * B6 W. a7 n( Q' Y10.3 时间差. J: v6 e4 T. o6 l, Y
    10.3.1 Timedelta的生成6 p0 c+ U: v; ]7 e
    10.2.2 Timedelta的运算. A/ A; Q: x1 M7 K% L7 W
    10.4 日期偏置- E& `( k6 M. G0 }. l
    10.4.1 Offset对象6 }3 N9 c" u8 ~4 S% J4 B2 p1 v$ f
    10.4.2 偏置字符串) U# W' k' _" v9 U. G, Z/ c8 {
    10.5、时序中的滑窗与分组
    3 Y$ P( ?' N! u* A8 o% y10.5.1 滑动窗口
    * ^& m$ J" a9 V; v10.5.2 重采样4 T) R0 L; K2 o: D) k+ I
    10.6 练习3 W, @* a" b$ m9 w" V8 p
    Ex1:太阳辐射数据集
    3 i' h- H* p( K5 J6 Q0 F& cEx2:水果销量数据集
    . g6 E& z& ]) m  课程资料《pandas数据处理与分析》、github地址、讲解视频、习题参考答案 、pandas官网, c# N3 R. ^% `2 w
    传送门:
    - v- a6 h: [; K) g9 e* |' h, r9 E
    0 F& o+ v( h2 m5 Q! tdatawhale8月组队学习《pandas数据处理与分析》(上)(基础、索引、分组)
    & t; i/ L0 C$ X5 ]& ndatawhale8月组队学习《pandas数据处理与分析》(中)(变形、连接、缺失数据)* Z8 ]: n7 D( O8 O5 m1 A% j
    第八章 文本数据
    % N4 U1 E5 `& H- G/ E8.1 str对象
    " b- d: d+ f" |/ K* w: Y8.1.1 str对象的设计意图# ]: }' B: ~8 \3 q' F# |# a
      str 对象是定义在 Index 或 Series上的属性,专门用于处理每个元素的文本内容,其内部定义了大量方法,因此对一个序列进行文本处理,首先需要获取其 str 对象。在Python标准库中也有 str 模块,为了使用上的便利,在 pandas 的50个 str 对象方法中,有31个是和标准库中的 str 模块方法同名且功能一致,例如字母转为大写的操作:
    . D! t1 y' u! B) ?; n$ c$ D6 ~# X) ]  ^7 |0 `
    var = 'abcd'
    : [  h, h- M" `, d( R& W% ~. E2 `" gstr.upper(var) # Python内置str模块
    $ y& N! n  u7 X; _( oOut[4]: 'ABCD'" R4 k& i! A5 {3 @2 z9 h/ w5 `2 G

    8 j+ J3 {: k% |% l( r! Y3 ns = pd.Series(['abcd', 'efg', 'hi'])
    9 ~/ n% W, k* A$ y8 k! E/ |7 B) ]4 V+ ?5 x3 K1 d( [) N
    s.str; R' C4 d3 V: E7 X! n6 t6 x" P5 l
    Out[6]: <pandas.core.strings.accessor.StringMethods at 0x2b796892d60>
    : @9 I8 i- B) `9 K3 l
    1 `: \! `: u: f  q. |s.str.upper() # pandas中str对象上的upper方法* p) I* g7 q1 j* K  K; k
    Out[7]: % l7 S2 B4 X1 {; ?( `- o; I
    0    ABCD
    # c' `+ t0 r1 d2 t: |' |, |  c1     EFG3 n9 r0 h* E( R1 C7 H+ v
    2      HI' x" ]" W/ r) h3 a8 M5 J
    dtype: object& ~: G* _6 l4 i- e4 l' M
    1  A: {' W! A0 b( C* z: p$ m
    2
    6 A$ i/ m/ K7 z- r! H; C8 q38 F4 I9 K8 c1 y2 B6 O
    47 u* K( U* b# y& U
    56 t% w7 s8 B) a+ F7 ]
    6
    , w" ?  a) ?' T, `! ?0 q7
    4 c# p7 R6 P3 w+ G+ j+ X8
    % A- C* j7 Z2 ~# g2 y9
    7 t' c/ _! z8 i# u5 x10
    - u, u. f; k( z, w11
    ' W9 w" ^. o  J+ B, \" Q; [12
    0 a7 r: P. d: n! \( J5 b* U# j3 Y5 M6 ^13
    6 J' Y( f( s) q143 g, |/ c7 t0 E( U8 F
    15/ _8 e( v" c2 W
    8.1.2 []索引器  h. e* D" H; q3 y# S
      对于 str 对象而言,可理解为其对字符串进行了序列化的操作,例如在一般的字符串中,通过 [] 可以取出某个位置的元素,同时也能通过切片得到子串。4 O' a; r" P' s, j( I1 x
      pandas中过对 str 对象使用 [] 索引器,可以完成完全一致的功能,并且如果超出范围则返回缺失值:
    5 R' S& M" E& V0 k0 Q/ ^. J, ~+ f) ]8 j
    s.str[0]
    9 V1 T3 d5 C' O; r. KOut[10]:
    . {+ u8 [5 F/ j0    a
    0 B/ e4 N$ [* g, @' q, g6 g( L1    e3 |! T6 t9 I4 T
    2    h; U0 V& s( m7 `& d# a' l
    dtype: object2 R: }& n# I- `1 k* F6 j  `8 s
    9 ~! h7 x) c7 ~" H1 d3 t. H$ ]
    s.str[-1: 0: -2]
    2 d4 i$ g. i- z/ aOut[11]: 4 Z& D" ~: _  s* v9 r, B% x
    0    db
      ~- `% i2 q3 q2 T+ E9 t6 F4 w1     g- t4 D$ D; R& d! x
    2     i$ s) g# e  o, U, T  O
    dtype: object
    ; m! e2 X3 B2 a0 A# B4 |7 T+ r( ?: `0 c9 U1 M& l% x& w4 Q0 c
    s.str[2]
    5 a8 D  D' ?9 ~& ^$ O# x: WOut[12]: - |8 Y7 B* c: Y. y
    0      c
    8 S0 x' o" n; E; s& V1      g) J+ S$ o+ N7 C$ q
    2    NaN
    , L6 f. L, y* u( [' w  \+ L) jdtype: object
    & N! D7 X( l) ~: T
    ! v4 M% Z, d  s1 X% N% H  `1 M1: ?  w% Y- q( v/ h! T( C/ o0 k
    2
      ~) [: R6 ~( Q/ P. E3
    $ F4 D- c/ B+ Q  ~3 V7 |1 ]47 S- R, W& W: S  a. ?& ^
    5( k) T. ^1 R0 e8 _
    6
    ( b# h" y- F7 x* i2 u4 k+ g( q7" \4 X# ?' }% x6 N5 j
    8' h4 j1 _+ e& v$ i7 k% _
    95 B2 N. i. z9 O6 I# J
    10
    ! u4 ^: N& H9 {9 F  Z" b# ~11) E4 K9 J" X" K3 n/ v
    123 P0 v5 s1 f: Z% [
    13
    " k5 p+ K( l/ e* S' j14
    " |  F, E! d9 x/ i9 r157 X; p$ r+ U8 N  `3 j
    16) J& s5 X: e/ u" a# b
    17
    " ^/ {+ ~" e5 S/ K185 x# g$ A2 A3 ]9 K4 W6 }# N
    19
    - |. P( ~; ^- R6 k$ B20
    * z+ I; \' E0 u) t' M5 r  c2 oimport numpy as np" m1 t8 t- |) s- Y' e' ~( d
    import pandas as pd
    + n7 S& }5 p7 A0 V6 V/ N3 V
    $ S; I9 M2 y$ h( [s = pd.Series(['abcd', 'efg', 'hi'])
    3 M9 }) y0 O  q. us.str[0]
    2 {( Q% ]+ M9 D# ^9 v6 @18 }# Q7 [/ v. @: X) B
    2% L. g6 e# M' k- B. E4 x& A
    3
    ' d' h* z! F3 J  }! M- @, Z4
    3 \% i0 f  B8 y7 d  L5
      W: l9 c$ V# u" X# t7 U6 }$ d0    a
    " d1 D- Y- l% d1    e2 O5 R8 A2 k. [5 Y3 [
    2    h
    9 y. M: d& q" N8 v/ r8 }3 p" ?3 ldtype: object
    , ?' l5 R# B9 }+ q6 V3 c) }1. P1 x! N& ~# L( w, \, {
    2% p6 w8 U/ ?3 }5 b: H5 o# r
    37 d6 d5 D$ `1 J6 D1 g- }" O
    4
    ' i8 J8 {7 r6 c2 X1 w8.1.3 string类型- o5 r% a( d& g% q
      在上一章提到,从 pandas 的 1.0.0 版本开始,引入了 string 类型,其引入的动机在于:原来所有的字符串类型都会以 object 类型的 Series 进行存储,但 object 类型只应当存储混合类型,例如同时存储浮点、字符串、字典、列表、自定义类型等,因此字符串有必要同数值型或 category 一样,具有自己的数据存储类型,从而引入了 string 类型。
    1 L$ V) ^/ W9 b$ A. i7 Y" \+ I  总体上说,绝大多数对于 object 和 string 类型的序列使用 str 对象方法产生的结果是一致,但是在下面提到的两点上有较大差异:5 p, ^# M9 ^' [" ?
    3 {1 A5 a$ }, U7 A6 m2 l( z8 N
    二者对于某些对象的 str 序列化方法不同。' t% F( Y. J4 ~8 R2 t
    可迭代(Iterable)对象包括但不限于字符串、字典、列表。对于一个可迭代对象, string 类型和 object 类型对它们的序列化方式不同,序列化后str对象返回结果也可能不同。例如:& [' q% {5 U* b  [# T
    s = pd.Series([{1: 'temp_1', 2: 'temp_2'}, ['a', 'b'], 0.5, 'my_string'])0 A9 ?/ h' `) y$ }+ D4 E* k
    s
    ! h6 Z  C9 h, \: A3 w5 o1' Z" r( }" ~% A5 z+ u+ h
    2* y, v3 R+ u# {# V
    0    {1: 'temp_1', 2: 'temp_2'}
    , J* s& s  ~0 Y$ S* |" p1                        [a, b]
    5 t* ]8 {$ x6 N2                           0.5. R' \/ l/ `" H, K9 ^
    3                     my_string. `9 q. S) q$ o
    dtype: object$ h0 V; m8 ~% u% J
    15 s- @* ]4 K. q% W
    2
    " v& [# M* k) R) T" u9 U6 ~3
      Z& q1 t3 {0 p4
    . y( c$ \! c9 Y6 j: `5
    - N" y& k; K, M( P& @+ O3 e9 _0 K* Gs.str[1] # 对每个元素取[1]的操作
    8 S4 v3 W- n$ u9 `1
    2 S+ b: q+ F, {: a+ i0    temp_1/ C; V, E' [0 E( [& M* v, X. j  U
    1         b* \* v/ T' J- A6 p: a
    2       NaN; \1 \  {( w7 Q4 A% M! G+ |
    3         y
    . f" Q- h+ Z7 r& U9 [! R$ j$ V; adtype: object
    ' n8 V" [  A  _% S7 m1
    ! m7 U+ e0 p0 ~+ L* r& g. {# s+ U: D2% {" F2 @" f: }. F7 n$ o
    3
    ) J# ?& z2 K& E) a: `4
    % c. h( F+ ?% J0 z0 g2 l- W58 d7 `; J4 y7 E8 _- f2 P# {6 |+ `% G
    s.astype('string').str[1]# G  j+ s/ g. i
    1
    ' T; e. }  u* W+ }$ H0    1: P( v9 j, R( P# L( X1 G% C! P* ?+ B
    1    '
    # l% K! V2 D+ E2    .6 Y2 r6 Q, P$ Q8 i" B/ e" ~8 ^. H
    3    y
    / I+ g$ L4 b7 V2 E3 F; O  Qdtype: string; h6 o7 x% s3 A5 U
    18 M1 R/ \3 ?4 F  u* u0 z% b
    2+ C0 N9 u% l2 Q* Z
    3. [: T0 I7 J4 `! z
    4
    ; N7 n5 ~1 \) K% |! y  `) H5
    2 R' @  C* ^8 g* G* J, z! z除了最后一个字符串元素,前三个元素返回的值都不同,其原因在于:
    ( [: N% u1 |7 ]
    8 M1 I, r9 G' {) X当序列类型为 object 时,是对于每一个元素进行 [] 索引,因此对于字典而言,返回temp_1字符串,对于列表则返回第二个值,而第三个为不可迭代对象,返回缺失值,第四个是对字符串进行 [] 索引。
    3 N  H6 {( g+ U- j  Mstring 类型的 str 对象先把整个元素转为字面意义的字符串,例如对于列表而言,第一个元素即 “{”,而对于最后一个字符串元素而言,恰好转化前后的表示方法一致,因此结果和 object 类型一致。
    2 z7 V+ b' S6 c$ b7 |: ?. Hstring 类型是 Nullable 类型,但 object 不是
    3 c$ E/ o% a0 s" G2 R3 X  这意味着 string 类型的序列,如果调用的 str 方法返回值为整数 Series 和布尔 Series 时,其分别对应的 dtype 是 Int 和 boolean 的 Nullable 类型,而 object 类型则会分别返回 int/float 和 bool/object ,不过这取决于缺失值的存在与否。
    3 e+ F- `! B6 b& U- g  同时,字符串的比较操作,也具有相似的特性, string 返回 Nullable 类型,但 object 不会。, ]( `3 l( G9 M. C8 M
    s = pd.Series(['a']): r; v( h0 k. ?" f( Z
    4 s2 X# e! L8 h/ Q! U' A8 ~
    s.str.len()
    7 c$ i, J/ W8 S( r9 FOut[17]:
    6 z& \5 f6 b, v) \! s0    1
    9 F4 I+ e2 {+ G2 j- Hdtype: int645 d0 J0 w' C* G" O9 f  }

      x& z- V5 g' @: N& ss.astype('string').str.len()( g( ~, ]( ~3 M0 c4 |3 Z3 O
    Out[18]:
    " F2 Z. W1 ]: T* ?0    1
    6 t# z: ]; f1 b( Idtype: Int64; o, U2 O' [* ?7 ~( p, i
    . v+ ]# R  k* h- o
    s == 'a'
    8 t: J( ]* D5 O$ K* t+ uOut[19]: 4 T2 {$ x" q0 v! |" ^; B! }# h9 Q
    0    True% U$ ]2 a0 C* j; ]1 ~: R1 I0 r1 f0 s
    dtype: bool! I6 z9 _+ d7 j( c7 j

    - G, _& y# v3 G, C/ _3 F4 ~s.astype('string') == 'a'
    $ o9 Q$ j  X4 a( v6 V2 ZOut[20]:
    " O* |( @" _6 |3 e2 F7 A0    True
    0 a% |  [( w' f  C+ A, j$ Mdtype: boolean9 c" U5 y3 V- y+ {/ s  \
    : W" S) i& H8 k" M9 y+ B
    s = pd.Series(['a', np.nan]) # 带有缺失值: v8 i& M9 b" q: Z' v7 ^

    # V9 I1 c8 s  H( ?/ Bs.str.len()" Q7 \+ J" q. e! j
    Out[22]:
    ; z& S& s6 N. ?# J0    1.0; V; Y2 d1 {/ }( a; v' f! K
    1    NaN  G0 n' |* A$ P4 ^0 t
    dtype: float649 F7 w. D7 \$ F/ e

    - ]7 K4 g* P/ J2 k* q4 h0 Ss.astype('string').str.len()
    ! w+ R7 Z8 n* @7 T1 Z7 Z- v% [Out[23]: 2 A, e: r+ t% \5 b$ E* n
    0       1( z4 I  a' q  q: R2 {( A
    1    <NA>
    " R0 G7 I4 v4 A5 X' \dtype: Int64- c+ z0 s3 s: C" ?

    ( S: W# N' ]* _7 B' Y6 y8 U9 ds == 'a'' m5 s) }+ d' h" ?0 p: P/ n
    Out[24]:
    4 Y2 ]& e+ S& i+ {2 E. I0     True# O- s% ^/ l& ^5 j
    1    False( x7 R/ L# I1 T/ b( R
    dtype: bool. q: K2 ]. u) ]4 D+ @' p0 W
      U1 H/ V$ H3 `: f5 Q# D- ~# G
    s.astype('string') == 'a'
    ! v0 A, Y* u; p6 zOut[25]: # K$ S5 N9 d0 w9 Z* ~
    0    True
    ; D$ W" F) R0 g( X/ G8 ]' [1    <NA>
    3 r' ]! |5 o- n, J1 Hdtype: boolean* \  Q) h  m& X& S0 L

    7 V* `! @. C" L. ~5 f1 {/ Y" `0 G1$ _" i4 r. k, @  ?* C* f
    2
    2 n" T7 W  ^0 Z( U6 n. R0 V, ^1 h3
    3 ]; [( Y8 e2 L0 \# J, H1 I6 E40 M* p( u3 e, ]8 N* C
    5
    , o5 P! R0 s& ^: c% @6
    9 K( j0 G- w: l7
    1 O, p. M' n: @9 b6 Q84 ]2 K( K9 S2 \" o' ~) ?
    93 q9 F1 p! _9 L
    10
    ; f6 r& D: p0 v11: x  C+ J/ @5 W4 [! j
    12: L8 M* F& O5 G! z4 x
    136 t# j7 ~+ o1 f: x+ N9 h+ [
    14
    * X2 P2 L& i2 c8 |2 D) g( f8 w# L15
    3 E& K1 k* Q$ i" n$ h+ R2 @16
    ) I4 M/ i, H* T+ l! k5 Z& m& C17
    7 {) s" M7 d7 [18" ]5 ^% _% M0 V1 i
    19* i8 U' r; h! C; r' q' R
    20
    : b5 q5 P- p, ^0 G, v- n1 |21
    3 u& W: \% g! H- \22
    " P" p) ~* G3 ]' {5 i6 p% ~231 A; H4 L" U, C9 A* [" r
    24- l8 F0 `. |( _5 }
    259 u9 y9 d2 k$ [( z4 a. H
    261 ]3 e9 d, G$ K
    27+ C2 C+ W1 @  S2 P! i: o( j
    28+ u4 r0 X: \+ S, T
    29
    - C8 q% j/ d' X; ]' ?30
    ) D% H- |. ^' Q$ Z( w% }1 u9 U31
    % N( W* s8 v) E$ m9 j" C% ~32; b" D9 u, n9 F6 o' l* Q
    33
    ) D8 m5 z9 v' ]% F8 x1 z5 j34+ T* z; J: b6 @9 i, p0 f
    35" W9 o( u5 \, E! R) `: \1 i$ N9 i
    36
      d( K7 j# [7 J+ s37
    + }1 B6 Z7 H" O3 G. E) l6 d38
    ! D/ k  c; z3 G39; R# r  ~9 @- U- o9 C4 b
    40: H' A1 L8 f8 a% V) ^# B
    41
    3 a+ \3 u% ]3 }8 F: L" E- O421 Y( q7 H3 i5 B
    43
    , A$ X, h$ g2 u) ~44
    ' e! X4 L; X; n0 p/ U45
    . _' G3 r( X2 S% g46
    $ q- q8 n+ z! O; W2 e47& t/ T  h2 Q$ F5 W+ d: d  z( A( |+ `  d
      对于全体元素为数值类型的序列,即使其类型为 object 或者 category 也不允许直接使用 str 属性。如果需要把数字当成 string 类型处理,可以使用 astype 强制转换为 string 类型的 Series :8 p* o% O) `9 ]# E. K2 C. i

      N8 @* ^2 v; I* r/ _* G) As = pd.Series([12, 345, 6789])$ O+ T  c. N" Y( O' j) b" m, ~& ?
    , e  ?% v& G( N1 T! p2 Z- j9 F, t
    s.astype('string').str[1]
    , j; q1 M4 Y/ T9 b! [Out[27]: ; G- I" @5 A4 v$ N
    0    2$ U# G$ K) }, R( l
    1    4
    & [$ [* `5 w. U# C/ b5 K2    7
    / |5 O3 G% u$ N7 ^6 A/ pdtype: string% K5 z- ~8 }# m( C! Z. O* O" t' K
    1/ D5 i' D5 q% g, F$ s) k: _
    2
    8 B. N9 U$ R3 x( O8 y( n$ w! A3/ W$ M- ^1 R) {
    4
    ) L) C) p7 u  H. f; n5 ]) Z5
    : h( }" I' J5 J0 ?2 L0 `6
    - r# P# }1 J) M7 p4 O7
    1 e9 T3 ~) U$ C" d* @0 ~1 O0 X, f/ M8% ]/ I" n! p4 G0 [/ G
    8.2 正则表达式基础4 Y, K7 X6 }& e' U- A% {  V
    这一节的两个表格来自于 learn-regex-zh 这个关于正则表达式项目,其使用 MIT 开源许可协议。这里只是介绍正则表达式的基本用法,需要系统学习的读者可参考《Python3 正则表达式》,或者《 正则表达式必知必会 》这本书
    & y7 R5 a% H0 B$ ?; A
    - D; U) q: E0 }" ?8.2.1 . 一般字符的匹配; i6 A5 X: X3 w, ~1 h' Q/ {5 B9 A( P
    正则表达式是一种按照某种正则模式,从左到右匹配字符串中内容的一种工具。对于一般的字符而言,它可以找到其所在的位置,这里为了演示便利,使用了 python 中 re 模块的 findall 函数来匹配所有出现过但不重叠的模式,第一个参数是正则表达式,第二个参数是待匹配的字符串。例如,在下面的字符串中找出 apple :
    - `7 d9 e7 i: w) {# f6 _6 J
    0 f% R- T# A( c& V( T" F, kimport re0 ?5 Y$ u& N" K. U: [/ U8 I" z) _

    4 m  h4 b+ r8 w* Y7 Z8 o) mre.findall(r'Apple', 'Apple! This Is an Apple!') # 字符串从左到右依次匹配, y8 n" D% ]* `
    Out[29]: ['Apple', 'Apple']
    - @! H0 T, W7 O' r1* h1 f0 P! U6 d' n7 u* ]7 `" e
    29 a$ g! L4 n' `+ X' {  A
    3" t# J( X. {! i, m1 W0 t1 [
    4
    0 x) V8 E4 @6 r8.2.2 元字符基础
    % C: ~2 _9 |) i2 E9 E# b8 K9 {元字符        描述
    ; w; o! u4 x; S$ X0 [2 P.        匹配除换行符以外的任意字符
    9 t6 ~! t% R7 M7 N( K[ ]        字符类,匹配方括号中包含的任意字符
    / `/ q2 h) L1 v2 a, w' S  V8 g' d[^ ]        否定字符类,匹配方括号中不包含的任意字符
    ! p; {6 Y! t7 L6 c( e6 Q1 Y' L7 R*        匹配前面的子表达式零次或多次1 k: |% z. N2 d* Q2 d5 I* Q
    +        匹配前面的子表达式一次或多次。比如r’d+'就是匹配数字串,r’d’就是匹配单个数字- F; W( X2 B1 [( o  `
    ?        匹配前面的子表达式零次或一次,非贪婪方式
    7 g% ]& X$ }9 B2 g{n,m}        花括号,匹配 n 到 m 次由前面的正则表达式定义的片段,贪婪方式
    6 d) \* |% Z$ K! T0 ?8 Z; u(xyz)        字符组,按照确切的顺序匹配字符xyz
    * p6 F$ a, S/ E) Y; I* b; l|        分支结构,匹配符号之前的字符或后面的字符
    + g1 M7 y4 v) s$ J; w6 y: N\        转义符,它可以还原元字符原来的含义
    ' c0 |6 ^+ w( ]3 z9 P+ L, O^        匹配行的开始6 ]% y* o7 b4 f' B) O, z
    $        匹配行的结束1 Y, f: |. E0 A+ a5 @' ^. `; E
    import re  S* U- v1 F! v
    re.findall(r'.', 'abc')/ p, {3 ]" _( V
    Out[30]: ['a', 'b', 'c']$ S/ t+ a8 Z/ N: E/ z
    % a( l0 Y) o6 J3 f' f
    re.findall(r'[ac]', 'abc') # []中有的子串都匹配
    ( @8 i5 R5 V9 `- l* YOut[31]: ['a', 'c']- o2 X+ b" g' l! \
    4 m# v( g: C2 f' k% ~: }
    re.findall(r'[^ac]', 'abc')
    " v+ Q- Z/ s7 a) p$ P  l( _Out[32]: ['b']6 T/ H2 n" i6 p! d

    + a, w! r/ @$ Q/ t3 o; f. bre.findall(r'[ab]{2}', 'aaaabbbb') # {n}指匹配n次: B3 j+ T- o! S5 u. D
    Out[33]: ['aa', 'aa', 'bb', 'bb']
    4 a9 L+ Z( V# ]0 Y7 z- q% Z3 ?9 O' x. J0 |' x
    re.findall(r'aaa|bbc|ca', 'aacabbcbbc') # 匹配前面的或者后面的字符串
      F, ^! D2 }* h6 YOut[34]: ['ca', 'bbc', 'bbc']
    * d1 D$ w7 G6 [" @  N$ P
    % o8 H4 ~5 n, i. _9 J: M5 ~# 上面的元字符都有特殊含义,要匹配其本来的意思就得用\进行转义。0 g9 v6 c- v, {
    """) P3 Q2 V* s2 i2 m4 Z% P
    1. ?匹配的是前一个字符,即被转义的\,所以|前面的内容就是匹配a\或者a,但是结果里面没有a\,相当于只能匹配a。' K5 m7 V" }# `* \9 ?
    2. |右边是a\*,转义之后匹配a*,对于竖线而言左边优先级高于右边) D# m/ D2 q* Y3 k6 ]+ R0 D4 s/ G
    3. 然后看目标字符串aa?a*a,第一个a匹配左边,第二个a匹配左边,第三个a虽然后面有*,
    $ e, q4 M* R0 o& z' }% `但是左边优先级高, 还是匹配左边,剩下一个a还是左边,所以结果是四个a
    ! P3 z& Y: D2 V  \0 \8 g5 U"""9 B5 G9 L' {' K: w$ i
    * C% {4 n1 X. R# N
    re.findall(r'a\\?|a\*', 'aa?a*a')   # 第二次先匹配到a,就不会匹配a?。a*同理。
    1 X. z5 a8 b* E& a( E2 x( H$ H3 rOut[35]: ['a', 'a', 'a', 'a']
    8 t. T& ^2 }: D6 B- [: A( U; E9 e. }5 D! \6 U. I
    # 这里匹配不到是因为目标串'aa\a*a'中,\a是python的转义字符(\a\b\t\n等),所以匹配不到。7 U, u; c6 P( E9 \% m
    # 如果是'aa\s*a'之内非python的转义字符,或者'aa\\s*a',或者r'aa\\s*a'就可以匹配到\字符。  _! K' \6 X6 s; l
    re.findall(r'\\', 'aa\a*a') 1 F2 k& @% `2 ~: J
    []1 X7 x0 q2 ?" |: [

    " W/ X3 r; \' ?/ M' \" {. Ore.findall(r'a?.', 'abaacadaae')
    . P! h4 E3 M2 E/ q0 _7 k5 D% nOut[36]: ['ab', 'aa', 'c', 'ad', 'aa', 'e']  u. T" l1 T$ ~( U- P$ r! u% J9 g
    0 D/ P( D* C% H
    re.findall(r'(\w+)=(\d+)', 'set width=20 and height=10') # 多个匹配模式,返回元组列表! M( y4 a- Q" ~# }
    [('width', '20'), ('height', '10')]
    2 p7 ?; J. \$ E. d: _
      }& I/ S) `) e: y* c0 f1; d  h! Y! F0 j: Q! t3 p* y
    2
    ' g; z0 y; c5 ~" q& p, |3
    - B' Q+ e" s% p, r4
    7 h* X0 g, f0 P) X+ |53 o- |1 n$ F( ?
    6
    3 E, z6 ]7 [' d3 H/ }' L' a) Q7# M1 Z3 [4 L2 U" }# {& C2 c
    8; H+ B5 c. C5 |6 d- a: I+ y
    96 y7 }! \+ r' X
    10
    9 s; ~  R& C! F4 ]2 n/ K  H11! i  F+ i; V1 Y* u. ^: m( U
    12% T" E; U+ h4 f7 P
    13" u  _8 {0 J* ~$ j9 ~
    14
    4 ^0 ^! y/ g+ D3 A15
    0 ^+ R" q1 L6 J! ?7 Q16
    5 ^+ k* O" b2 ?0 p4 l3 v) P17
    : P1 R  @5 i: C0 H6 ^! O* g18
    1 t/ x) V1 t- m  l  b19
    6 r# k5 V  }1 `& r: H# w20
    7 Z5 j& K) d+ y) E21. L% \) j) a: L$ Y1 o- [, R) W
    22
    * b: y, {/ c3 a" q" X23
    9 T& C/ @5 ~* A" n  g7 Z. k& f. G24
    3 `2 W! @1 ^9 s  n" q25
    6 _$ W; @& D& S7 ?, O: n26
    # w" E6 h, P, n1 ~1 U* ?# K279 B5 |# |8 R$ ?
    28
    + s3 p% Y) T/ l0 ^1 X6 V& t" Z29; w4 U4 J! Y; M5 j1 q
    30( r3 v1 P1 A7 \; q9 B- z& d8 F
    31# y# A- o9 f! z4 z
    32
    " ]2 e% B* i' Z9 h8 d33% {5 \7 ^% t2 L  I0 Q' Z- V2 @
    34
    6 ~7 a7 O* q  s, G# r35
    ) W" c0 P6 G) V  c4 S' \, @! U) W36
    # g! h- ?; f4 E: @0 b37' _2 ^3 C, h: E% a8 i2 ?* @9 c/ ^
    8.2.3 简写字符集
    $ r& `% K$ G, B; w  X) O则表达式中还有一类简写字符集,其等价于一组字符的集合:
    & n$ h5 {! y+ G4 g
    3 o0 T& L1 F$ j简写        描述
    % c+ u% e! n& g4 g1 Q4 W& j\w        匹配所有字母、数字、下划线: [a-zA-Z0-9_]4 }" o& W1 G* ^8 G: Y
    \W        匹配非字母和数字的字符: [^\w]
    % h$ W5 g) ]' Y" L* I\d        匹配数字: [0-9]
    8 _& A5 D2 V( O) r& w\D        匹配非数字: [^\d]4 Q0 R7 s# M/ s( J9 m' K1 J
    \s        匹配空格符: [\t\n\f\r\p{Z}]4 D+ g2 s9 C' I
    \S        匹配非空格符: [^\s]  P( M) d0 l/ z1 D4 u5 I
    \B        匹配非单词边界。‘er\B’ 能匹配 “verb” 中的 ‘er’,但不能匹配 “never” 中的 ‘er’。
    ! L. n' e+ `2 u# }9 nre.findall(r'.s', 'Apple! This Is an Apple!')
    # U+ x5 y5 u  _0 ^" X+ J5 n9 Z( hOut[37]: ['is', 'Is']
    + _& s: k" m' A0 {! X4 e, t) G3 X+ k& Q1 Z, L5 C# p) z
    re.findall(r'\w{2}', '09 8? 7w c_ 9q p@') # 匹配任意数字字母下划线的组合,但必须是两次
    - |! P  _2 |1 F) V- z* vOut[38]: ['09', '7w', 'c_', '9q']6 O. o8 y* S" s; I) [

    $ L7 N3 W6 L3 h$ g& Ire.findall(r'\w\W\B', '09 8? 7w c_ 9q p@') # 匹配的是两个字符串,前一个是任意数字字母下划线(\W),后一个不是(\W)
    # P7 Y0 p. a" _) r& l9 A9 rOut[39]: ['8?', 'p@']
    * E- b' B1 Y- K) i9 j* d
    5 {. ~5 x! \* ?- d8 ~re.findall(r'.\s.', 'Constant dropping wears the stone.')
    ' h* ~( P# i. Q) L9 E& a0 j5 s# p# mOut[40]: ['t d', 'g w', 's t', 'e s']' m1 c  W8 H% R* R! u
    , e+ w# T. |/ |5 ^! \
    re.findall(r'上海市(.{2,3}区)(.{2,3}路)(\d+号)',1 {) z. i/ A/ u
               '上海市黄浦区方浜中路249号 上海市宝山区密山路5号')
    6 O9 }) M# m+ o2 I
    , a- ?/ m- R- u3 c% WOut[41]: [('黄浦区', '方浜中路', '249号'), ('宝山区', '密山路', '5号')]$ Z: y" _. Z; q: G' V. R
    / {8 |: L, i. k3 ^1 o( N5 g$ r, J
    1
    " S+ Z* J: m- l# K  s7 N2
    1 `9 M; v( F3 |, \3
    0 e5 x' ?" M1 j! C8 i40 ~/ _8 x! S% Q! F+ T& V
    5" N( |5 m% K* A1 v& }  d
    61 K8 P' d" Q  J0 u3 ^
    7
    9 u2 v- z' g5 Q  E0 ?" L7 j8
    $ b% D9 _& @* _& Z9
    4 C: n) f# q; \2 ^2 Y& ?0 ^' k& |10( L3 K" u' H  Z
    11
    $ R; ]2 b7 N5 ?( ?: }12+ N/ _& B( e% W' q& p5 e' M* o
    13# Z2 x( a# `& |* u
    140 e5 I6 S, ]  |6 |) G  b. n+ A( Q
    15
    ) p! |! t8 {! k9 q16
    % }" L% [! D4 e8.3 文本处理的五类操作
    / H5 h) h. y8 o! `; `8.3.1 str.split 拆分
    3 S! [, ^0 t: Y8 O+ n  str.split 能够把字符串的列进行拆分,其中第一个参数为正则表达式,可选参数包括从左到右的最大拆分次数 n ,是否展开为多个列 expand 。
    ; b& q% \4 w& e- o  B
    6 X: K. ]" Z- Y9 ^+ W0 s; y% Ws = pd.Series(['上海市黄浦区方浜中路249号',
    3 G9 m) [4 p- C" i& P. Q            '上海市宝山区密山路5号']): ]. m5 R+ Q: b

    ! a& \6 S0 i0 t- y) ~6 m, J) q  s5 j2 @' V0 ~' O! q% M: q4 ^
    s.str.split('[市区路]') # 每条结果为一行,相当于Series- J# T: u' M0 L5 X
    Out[43]:
    4 Q3 {' C) {1 z- C' F0    [上海, 黄浦, 方浜中, 249号]
    / t" a0 ?/ d( T$ \/ D1       [上海, 宝山, 密山, 5号]
    . G/ [+ P- K7 Adtype: object
    2 S( Z* ]# g) x9 [
    ; t$ p. m% I; u( d/ E% Xs.str.split('[市区路]', n=2, expand=True) # 结果分成多个列展示,结果相当于DataFrame
    ) X: C. p- Q; L0 iOut[44]: 4 G' Q+ w7 G) D8 g" W/ C
        0   1         2$ A1 n  y3 O* O) ?5 I
    0  上海  黄浦  方浜中路249号) X/ T: d6 Y4 X0 z1 L" Y" |
    1  上海  宝山     密山路5号
    & B( G8 [& m2 L* ]- U0 H1
    $ p$ l2 P3 t0 @0 g6 E- b/ |# j* U2( h+ A7 g7 F. [
    3
    & Z. G2 S2 f: c3 O4 P+ {45 l3 i, ]1 X4 @3 f8 l6 }% P9 J, q
    5
    * u7 e# J& r6 ?9 l% v# q6
    $ @: r$ D3 }' t- p, c* u. ?4 q7
    3 v0 u( y! h& E  |9 Q; S% ~# u# ^4 o8) W$ |& J7 s4 \" c9 h" x
    90 u) M* G7 O- ^/ M7 \2 z
    10
    * I. q% U- F9 V  x/ W% D) o# a11& A* n: {0 L' x& q6 L0 b- O1 Q) S( G- p* t
    122 f& R* T# N* r" v0 ~' t5 G9 ^5 X
    131 A) N% j% U% y5 |; q1 e
    148 L6 I1 H; J8 w- N
    15
    7 a7 Y# U! F7 O1 i* L; g! U' A9 y. F  类似的函数是 str.rsplit ,其区别在于使用 n 参数的时候是从右到左限制最大拆分次数。但是当前版本下 rsplit 因为 bug 而无法使用正则表达式进行分割:5 X6 [& x9 q9 ?3 k
    0 c4 O* v; U% V% c* N, Y
    s.str.rsplit('[市区路]', n=2, expand=True)7 ?5 N  |1 m  z# B
    Out[45]: " J% l7 y% `% u2 |& a
                    0
    ) z0 ~/ O7 J5 X0  上海市黄浦区方浜中路249号/ W( r5 u# Z! W. {0 i6 a8 K' U
    1     上海市宝山区密山路5号7 w- I7 l& [# T* a. l. S  W. Q- T
    1
    $ l- k6 r4 |9 F7 y2
    ' I6 z+ \. b, T3
    ( i0 ^4 P' Z' j# ^" }4& G) }* ^: I+ p& l* L# {$ B& M4 c
    5$ b  G0 P: N7 R1 _9 N* E
    8.3.2 str.join 或 str.cat 合并' k7 v) d2 s! o/ H0 r& ]2 }# Z
    str.join 表示用某个连接符把 Series 中的字符串列表连接起来,如果列表中出现了非字符串元素则返回缺失值。
    1 }# }$ B8 l$ {% d8 v# pstr.cat 用于合并两个序列,主要参数为:1 [% W" Z7 @: [0 }6 o
    sep:连接符、
    6 {3 ^3 I3 C' S7 t, ?join:连接形式默认为以索引为键的左连接* E# h5 H" F  S8 S
    na_rep:缺失值替代符号
    9 C  c+ `; b) w* _* n/ As = pd.Series([['a','b'], [1, 'a'], [['a', 'b'], 'c']])
    ( D% L, I# i. D0 u+ m$ e  os.str.join('-')
    ; q8 `! Q, f; n6 e, i9 `1 [Out[47]:
    ( ~5 @4 V6 B! q, v0    a-b
    5 v0 M0 x0 K8 k9 ^9 s1    NaN
    7 o; p8 C) S: Q" r- ^2    NaN
    ! K6 r# I0 j. D8 R0 B2 R$ x; C, xdtype: object
    ) A( v% @* P1 ?4 M8 C/ v" c1
    : F2 F  J0 ~6 U! l# m  R2
    - c0 X) |& O5 ~! `% y" c" |* p3
    & a: R7 [' ^: n8 C2 e0 E/ _% G4
    3 J8 \* @1 e9 U4 P( M' j! G* V53 j% Q8 o2 d& T1 F3 O# \
    6& d; q8 |" b/ }! D  _) {' n
    7. m5 h+ J4 a1 J, D0 @
    s1 = pd.Series(['a','b'])+ a! ^) j  x: I- M
    s2 = pd.Series(['cat','dog'])
    # g: P! I. |+ a! Ls1.str.cat(s2,sep='-')
    1 Y$ ]3 D* Z6 j3 {* S2 H( wOut[50]:
    # a: a$ A6 ?$ s5 {2 h" T$ e0    a-cat9 u: z7 g3 i& E( t
    1    b-dog8 G" b9 X9 l6 ~3 e1 f
    dtype: object  b2 `4 k6 [+ r

    4 }$ a6 d$ ]3 I) S! zs2.index = [1, 2]; e# m/ i, T0 d" w- U7 T
    s1.str.cat(s2, sep='-', na_rep='?', join='outer')9 P) J* @8 i: k. z+ ?4 r+ F& r
    Out[52]:
    / u2 Z  |) m3 I0 p: C0      a-?
    2 D2 p7 m: a  F+ M' D1    b-cat
    & o: P, c1 N" b. m2    ?-dog
    $ F, |3 O6 O, q  Z/ T% }: b" p2 c  Pdtype: object$ N* n- u) B. U$ |9 B( w1 Z8 g2 s/ P7 Z
    1
    8 U- M" ^3 J4 a$ t' o" `# r2
    : n0 E/ t# [: L$ r8 y$ N) I3" p- T  ~; t& u3 M6 |6 N# @5 _
    4! Q6 O! D6 u9 |/ C; Z
    54 A! U( i7 l- _# `. g1 a  D, B- D
    6
    ) F: @5 j5 X$ S3 y1 G9 w7! t4 [% ~/ R4 ^7 x
    8% \+ P- E1 ]0 @- t1 F% E5 ]
    91 G% @. x9 e1 l7 k+ B6 g  V$ R3 a
    10# x8 S2 t: j4 v( @2 o
    11
    ( O( I+ W( A  @/ P6 S12
      ?6 ?, a, P) ^' b3 N13
    ; ~) C1 L4 o' j# w* m* m) `140 \8 w/ `; J8 p3 y
    15
    3 U! V# G& D- h* l7 n8.3.3 匹配* w$ |' ?* ]1 b
    str.contains返回了每个字符串是否包含正则模式的布尔序列:! C' a  F5 g1 h+ I' ]# M
    s = pd.Series(['my cat', 'he is fat', 'railway station'])( s( Z8 }. {$ q' T
    s.str.contains('\s\wat')& d; n) d) P. _9 ]$ d7 V
    + h9 i9 u. D9 J$ _  G6 u" {% D+ B
    0     True
    ) C# N. P! S: F8 R5 _* }/ G4 Q9 z1     True
    2 k, ^8 b1 a; Q2    False
    5 U' j, y* l8 s: Zdtype: bool
    4 \, R6 R* I: M+ `# C1
    4 f$ h+ Y5 t! Q# e2; q- |+ j  _2 `0 {
    32 H5 t8 W) ]4 M# l1 [
    4
    2 z( ~: _0 V( F5
    : f) F# d+ @4 t* K$ a63 E! L1 j, l7 O! E
    7  `% W5 e- M* a) X, P( ?
    str.startswith和str.endswith返回了每个字符串以给定模式为开始和结束的布尔序列,它们都不支持正则表达式:
      ?1 W0 _/ |( h5 c6 Ws.str.startswith('my')5 v# \$ ^4 K) H
    5 ^, _) s& m2 x& C
    0     True2 c. i/ K4 f9 `
    1    False  T( V5 Z/ L3 C& w1 x5 {
    2    False
    2 L5 r& J0 \) v0 N# O- Z: i/ @+ hdtype: bool/ e" S& w' `# {- M; m+ r
    1# p  F# _8 I' X5 z  c5 S
    2  t0 u/ M& U0 N% V8 p% V
    3
    : Q  V3 C$ _2 u$ E" [% z6 |4 `& f4
    : v  _( r( c4 f5 l3 o5
    0 q" K  O8 V8 p" ^1 V+ K  X9 U6$ Y. i. Z' e8 v
    s.str.endswith('t')" E# G6 J' w* }: f
    : V- k! F5 o" t
    0     True
    / W( W6 y! z, x# M6 z1     True
    5 u1 C7 C+ s. W8 H7 O, L2    False
    8 S0 L4 P% j; X' e' P" }dtype: bool1 h0 x: g) Q% F7 H+ R
    1
    % C5 k% W* e  e+ a" c' Q2% S% D6 d! ~" A6 M% K8 R+ o0 b
    3
    6 r3 t+ r& u/ d% ]1 u. H2 z4
    ) [: n" ?; j8 L2 [# T9 E% g% t6 L5
    7 q/ i% G/ ~/ `" B! m6
    ; o( ~# I% Y' L7 `2 z; Qstr.match可以用正则表达式来检测开始或结束字符串的模式,其返回了每个字符串起始处是否符合给定正则模式的布尔序列。当然,这些也能通过在str.contains的正则中使用^和$来实现。(貌似没有python里的search方法)
    2 z6 n' |% Y0 e6 Qs.str.match('m|h')8 W+ Y$ u* {3 g4 |
    s.str.contains('^[m|h]') # 二者等价
    4 B  d) A  ?, g
    7 v/ K. a8 y1 k# H( \% [0     True
    " F. G* i4 `6 J! n" ~1 Y  Y4 N: n1     True
    / |8 `& l7 N9 G2    False- q0 D8 R3 p/ ^3 Z
    dtype: bool- H& ~# V) U- f6 v' n  `
    1
    ( Z' D/ X( \$ {; c2
    8 K5 R+ I, p/ J' U# C3
    4 H& Y6 }; P& S% i/ A2 I1 |4
    1 b( y* `5 [4 Z5
    2 E5 F# X5 H& }9 e7 [8 Y, b6
    ) B% _/ G$ [. Z: \, m  c7 S2 e7& N# w& w" r3 u- c& B  q2 b+ D$ Z; g
    s.str[::-1].str.match('ta[f|g]|n') # 反转后匹配
    7 t) l0 c4 X( n" J) R" L' us.str.contains('[f|g]at|n$')       # 二者等价3 u$ @3 Y' z" U7 z0 T8 B  X

    / e) x! A5 `) a# s0    False5 {, J& L* D$ }+ i8 X& f: @$ o
    1     True: x$ i/ s2 A9 r4 L2 {0 E. q
    2     True$ K5 E" y) ]. @) g; o) X
    dtype: bool
    , d& ?: A, ?5 s1
      p0 R! c5 q6 K  a7 c2
    1 y- x/ k; q& X! q9 d5 q38 A, J2 E  ]- E3 M0 \
    4
    , J9 U$ ?7 y! l2 E% M( v5( r. B. J6 H1 |# ^
    6
    - x& T* i$ Z  g$ _& @6 C78 A) I/ k& L- `! s# b- {+ M3 D# j" v
    str.find与str.rfind返回索引的匹配函数,其分别返回从左到右和从右到左第一次匹配的位置的索引,未找到则返回-1。需要注意的是这两个函数不支持正则匹配,只能用于字符子串的匹配:
    $ z/ [+ n, B& ks = pd.Series(['This is an apple. That is not an apple.'])# \. n. R4 M# m" @

    ' t& v% H0 _. W- Ws.str.find('apple')2 f# j7 o) i+ g* H
    Out[62]: & X, f' C  S4 ?: p' s
    0    11' t3 D; y2 i& W! n. v8 X1 W6 a7 T  y* C
    dtype: int64
    5 {6 K. [- c; r/ W. k) \* g, z$ O7 h+ V2 a. g- s* R* f
    s.str.rfind('apple')6 s. f' w6 O( b( S: R1 ]7 b8 u& ]
    Out[63]:
    - B7 t3 I! B$ Q) V0    33/ G# X- `- Z3 }
    dtype: int64
    " V6 s7 M! Y0 q" r- S9 F1
    5 v; a3 ^$ n  o: q* c  u1 P8 B& Z2
    8 h9 O7 g$ I% j4 O$ x" u3
    " A; h% [, l2 b& I6 b" b4" y' w+ }' L9 m& [& p" b
    5! r3 G/ g. L' l; v; [
    6
    6 V/ r$ W* Q: X& F0 o3 W6 y2 ]7
    , w9 ^) \9 S- [! _4 ]% g% S+ a( n- N8
    6 _3 i: O( Y8 c9
    " q4 v9 E. G5 Z10* Y( I: e# O4 w1 E: i6 F. a! @
    11
    1 C' {' q9 k# J替换
    . M4 i) g6 k7 v( p$ S0 L+ dstr.replace和replace并不是一个函数,在使用字符串替换时应当使用前者。
    5 y8 P3 x" B5 B% t0 \  \0 Os = pd.Series(['a_1_b','c_?'])- m9 b0 M/ ]' R2 i' [
    # regex默认为True,表示是正则模式,否则第一个参数内容表示是单纯的字符串,也就是匹配字符串\d|\?
    / B$ X% @2 F! z/ Cs.str.replace('\d|\?', 'new', regex=True) 4 @& a) @8 z0 o" ^9 u7 x: A7 Z

      v* \# }$ M. b: F1 n0    a_new_b0 q9 x' H1 i! H  T9 u2 Q, {
    1      c_new
    - r1 B. O7 s! l9 b; i5 jdtype: object
    : e. ?1 E$ h* l1 g# s1
    2 `2 J5 v: C+ T# G' u) Z' `3 A( C29 r" K0 J& s; F+ }! ?# o7 u9 F) u
    31 A4 x7 g) H; c$ o
    4
    9 P& b7 a; m3 F2 i5
    " }# ^; V2 ]# D1 c* j6 {6 p6  ~: G# M: \0 C
    7- D* z* d# H+ x* m1 f) _
      当需要对不同部分进行有差别的替换时,可以利用子组的方法,并且此时可以通过传入自定义的替换函数来分别进行处理,注意group(k)代表匹配到的第k个子组(圆括号之间的内容):9 h; i7 ]7 a; [

    : k( `- {: q# S, W9 @) k9 _+ Ks = pd.Series(['上海市黄浦区方浜中路249号',
    # e; c0 t# |8 b2 K                '上海市宝山区密山路5号',
    ; J$ i# ?, }2 O* t+ A" H( {                '北京市昌平区北农路2号'])
    $ A, B3 H- L* J; q0 M" ?9 @% Tpat = '(\w+市)(\w+区)(\w+路)(\d+号)'0 K, _* l; f7 _' g
    city = {'上海市': 'Shanghai', '北京市': 'Beijing'}
    2 k, i5 f7 ]2 j3 Fdistrict = {'昌平区': 'CP District',
      ]  w3 ~# f0 R            '黄浦区': 'HP District',
    ( X$ S. E1 t. {: e/ X$ w7 b8 S* x            '宝山区': 'BS District'}
    % G7 Z6 E: M* d$ ~" s0 I3 `# w; {road = {'方浜中路': 'Mid Fangbin Road',( ]0 c$ l. V0 {7 S( f- Z& t
            '密山路': 'Mishan Road',* g# x( s* p" h  X' B# p( ?
            '北农路': 'Beinong Road'}3 R# T$ I  o9 O$ z6 I( j# m
    def my_func(m):
    " T5 g" H; ?3 [1 f    str_city = city[m.group(1)]
    # Q0 S- k6 [; T" w7 ^' l( C    str_district = district[m.group(2)]
      `. |1 {& {# b    str_road = road[m.group(3)]) y5 a8 p  V* Z0 P- L
        str_no = 'No. ' + m.group(4)[:-1]5 l0 n* @# Y9 D/ H
        return ' '.join([str_city,
    5 o1 O0 @& d( ?9 u, U" k# e2 P                     str_district,
    ! i  X: W. T8 N2 B! d$ r                     str_road," T5 V  X# j/ D  l
                         str_no])
    6 F! F+ F  H5 }; F, V% |s.str.replace(pat, my_func, regex=True)7 ^' j3 s- g  v2 t; g
    % j% M4 `, z) B. g3 P
    1- Q9 O# a3 o+ ~& j
    2
    ) \" A6 K; K/ g+ {3 l! t3
    0 l' w5 [& {, v( M5 V" O4/ m! F' Y' z7 [% {
    5( A4 t% w+ k5 s! `& C9 i  l
    6. r6 a' d2 p8 P+ @3 A. N4 e
    7. m/ f- O) I1 D& d2 p- Z$ Q) L& {$ A
    84 A4 F  @3 Q8 d( p6 e" z6 ~
    9
    - W7 Y  k% T7 L) C" D& z# \4 ]10
    ) S5 b; |2 O/ H' H1 ~. ^; g! S11
    ; P  F3 Y1 M( }7 }+ @' A4 A" B12
    ' h7 W% j6 F  e5 K9 @4 s" b13
    $ h$ u$ k( G) Z# z5 V6 F5 e  e14
    # @# A& h; K, E6 C: D& K- e+ |7 t15* \( C) G8 s. K* @
    16
    ; D) b1 f, h: n, K* V17
    : }7 K* I/ _# I. i7 Z9 O) E* ?  I180 @; t0 n, M8 R. B9 G, Y0 @+ g( X, U9 y
    199 J# Z9 D% S1 O$ H$ }# m
    20
    & ]# G) w! [& Z5 S21" y+ j. ~) S2 @& H1 o5 @  @0 `+ Z
    0    Shanghai HP District Mid Fangbin Road No. 249
    " F* ~; ^# V, {) F/ m7 [" O  y  R" L1           Shanghai BS District Mishan Road No. 56 @! g3 q. B7 H0 ^; ]8 N, P
    2           Beijing CP District Beinong Road No. 2
    6 h3 X4 }! n- ]! U; R; c* Vdtype: object8 W, ~. W4 s2 S% q1 Z: `
    19 I' K3 s( {) Z0 E, s* I% y# k% c
    2
    % V1 m- A# f) P, z3
    1 Z- ]3 `7 F( B( u. V% O; z4
    , K! V9 f$ Q6 M: @; A/ U2 J* l/ h! B这里的数字标识并不直观,可以使用命名子组更加清晰地写出子组代表的含义:
    ) ]  e3 \! s- f2 f$ l7 q, \8 C" C( K1 h! R# u! F
    # 将各个子组进行命名
    ' N9 j% R6 t' t) E$ ~. u* qpat = '(?P<市名>\w+市)(?P<区名>\w+区)(?P<路名>\w+路)(?P<编号>\d+号)'" U, J8 K9 r. `5 G3 w+ d
    def my_func(m):. q+ w& d( f# g
        str_city = city[m.group('市名')]) A* {+ d& y! L) t# Q0 C- R
        str_district = district[m.group('区名')]" h* B( T( y! ~3 T
        str_road = road[m.group('路名')]. \9 r9 G$ Y% j& a3 f5 r" S) X4 Y
        str_no = 'No. ' + m.group('编号')[:-1]
    ( h- M. a1 K0 [7 t7 z) u; ^    return ' '.join([str_city,. [5 Q! p6 G* \( x# Y$ s9 {# x# k
                         str_district,
      W8 B! M7 O* O0 e                     str_road,% v" t" J' o) ^9 s- T; _
                         str_no])1 c# V6 H" ?: l# Q" e
    s.str.replace(pat, my_func, regex=True), Z4 a" G* t- y4 m' V6 s1 R
    1
    5 B5 i0 n7 r! N) A2
    7 N% `7 b# U, R3 S- _3, X* W; X% k  F8 u2 r+ t0 T1 w9 P
    4
    6 a) }6 ~. Z7 l+ L/ k5
    3 N4 B  D/ {/ z, g  t6
    : f; ~) J- W' \4 e73 u; S! G* f; H' Z, p- C
    8# ^& Q+ {* ~% }6 Y% p: a. A( v9 ^
    98 d2 u" H2 n9 |3 A3 F& P: W
    103 @, K' e, |+ t9 a6 s
    11, q: C5 K3 N, I9 ]
    12
    8 S8 N$ K7 N! Q  b1 d, Q/ T0    Shanghai HP District Mid Fangbin Road No. 249
    % h0 \  Q2 Z& G" x" ]1           Shanghai BS District Mishan Road No. 5+ B" q0 `2 ~* b9 F+ ?) w
    2           Beijing CP District Beinong Road No. 2
    ; }6 s: z5 N! j' w0 e2 G- R3 fdtype: object
    8 T1 l( F& t6 {; T8 n( c4 F15 T8 C4 ^  |, `' _/ R- r
    27 G- o3 R9 p  g5 Y! O, I, L
    3* n/ f( L$ x' N2 q  J
    4
    $ d* L' h. R3 b2 s: k. U  这里虽然看起来有些繁杂,但是实际数据处理中对应的替换,一般都会通过代码来获取数据从而构造字典映射,在具体写法上会简洁的多。# C- ~6 K+ u5 Q4 B
    ' w  R1 ]& ~* t
    8.3.5 提取8 G. P) K3 e: @0 S6 C% F) ~
    str.extract进行提取:提取既可以认为是一种返回具体元素值(而不是布尔值或元素对应的索引位置)的匹配操作,也可以认为是一种特殊的拆分操作。前面提到的str.split例子中会把分隔符去除,这并不是用户想要的效果,这时候就可以用str.extract进行提取:
    6 I! S' i( c* ?- V' h6 M# _s.str.split('[市区路]')
    0 \0 J6 ?% P; }# XOut[43]:
    / {, h3 m) ]" H3 q' P3 X* H  K0    [上海, 黄浦, 方浜中, 249号]
    " \0 j6 N6 |9 N6 z$ f. [) J5 s1       [上海, 宝山, 密山, 5号]
    ; \! ^; w) ?2 C# n6 {6 t5 @8 Ddtype: object4 k  L/ f! K2 h% l
    : y) W- s: V! E5 A
    pat = '(\w+市)(\w+区)(\w+路)(\d+号)'# C2 w, S! M) D# P% @
    s.str.extract(pat)
    4 ?% T5 w; A# I6 r/ x% tOut[78]:
    9 p* X0 d$ K6 B6 l* ^0 C+ d8 {4 G+ ~    0    1     2     3" z4 g; C( Y# G. X( U* r( M
    0  上海市  黄浦区  方浜中路  249号
    5 U5 F% v; \6 {7 z1  上海市  宝山区   密山路    5号
    $ g% y1 c: n, S8 d8 _* ~% C2  北京市  昌平区   北农路    2号1 T: X1 v; d0 l/ G2 K5 G
    1
    ' ^9 J  P6 A0 F9 b3 o2  o7 a* n$ j% v9 V1 r& `
    3
    , J0 h% P4 Z' ~5 f( T! [0 `4
    . w5 F# R  I( J# V2 x! d5* T  k( b0 A( M
    6
    ' a" X& l+ O/ Z% P; N) U7. c) _) ~4 }/ g4 Z8 A4 @
    8
    0 n/ W) C* u4 e7 q, P' {) |2 [9
    - U2 J' `( f3 g! M: B& u6 H10; Q% ^# z, K; m( k. ~
    11. Z* b/ I# o  \5 [. i$ d2 Z* ^
    12
    ! {( V9 h, V9 a+ r) \13) h% F2 ^; |" s. C" ~3 N% e
    通过子组的命名,可以直接对新生成DataFrame的列命名:
    5 D5 P' ]% e8 u# l, b9 ]
    8 n7 p& W. {1 _- }! B+ M2 bpat = '(?P<市名>\w+市)(?P<区名>\w+区)(?P<路名>\w+路)(?P<编号>\d+号)'
    . z; M+ S4 z; V: e9 z9 X" [s.str.extract(pat)1 K2 N& B' ?/ U3 }) ?
    Out[79]:
    + X2 w4 X* ]" F; b/ \    市名   区名    路名    编号
    " e9 }: B' I) O3 M* |3 Z0 l" s. O& J0  上海市  黄浦区  方浜中路  249号, }# [8 G9 k1 F; s
    1  上海市  宝山区   密山路    5号
    % a2 U6 X8 W, t4 g! s/ O7 j/ D# C# M2  北京市  昌平区   北农路    2号
    7 z3 {2 y0 R$ U2 k# J* G" w- o' V$ |1
    & |* I3 [3 d8 H2
    8 r# b; j0 S+ O9 P" W$ T% S8 S6 f- K35 N7 X- {" K  M6 P4 g1 u
    4
    1 k" B* ?6 S, r5
    * l! {3 k' o, E: A) w) ]% {. B6
    : L, p/ Y7 p0 t8 [  O7$ j4 ?$ y# i0 [) l  G
    str.extractall:不同于str.extract只匹配一次,它会把所有符合条件的模式全部匹配出来,如果存在多个结果,则以多级索引的方式存储:
    " B/ i+ C8 j' A. Zs = pd.Series(['A135T15,A26S5','B674S2,B25T6'], index = ['my_A','my_B'])
    9 h) f" ^( M. D& zpat = '[A|B](\d+)[T|S](\d+)'( Z0 ~. T; }# M: B+ ]
    s.str.extractall(pat)
    , @) l9 M" H' b+ U8 Y% K+ n" NOut[83]:/ U/ i) H6 ~+ ], E( Z# C
           0   1
      G, j, ]+ y) u+ |4 L     match         7 u' N  j% ?* N6 `: C
    my_A 0      135  151 s" q" T5 W3 v( B: V4 ]
         1       26   5
    % Z$ {: |- k3 _# Ymy_B 0      674   2
    5 a( c1 A3 s# M# F! [     1       25   6
    3 r8 e% S4 t$ m$ X' L1
    # N, W/ ~, k( `- ?: f29 K# ]. r5 v3 F9 _/ I0 A4 Q3 V
    3' n/ V% k0 y6 P, L$ ]
    4
    6 b) T6 B; x; R2 G; \- ~* n2 e1 |5' r( x+ |: s. [! E$ s2 o% B
    6# u/ u* N  G5 D6 L
    7! l5 L# N1 l& {+ _% u3 P
    8) p* f% ~6 V" R# [' h) W
    9$ H' M7 K6 ~# w
    10: B$ V( R! ~- {! Z
    pat_with_name = '[A|B](?P<name1>\d+)[T|S](?P<name2>\d+)'
    6 W/ A( Q) b4 J( vs.str.extractall(pat_with_name)* X3 Q. {( y, y
    Out[84]: $ z  m3 [8 _: D; @% v0 Q
               name1 name2, L1 {. F9 u4 {' p" B
         match            * n% m) w4 Y: f+ b- S# F' U  @( Q
    my_A 0       135    15
    % n: c5 x9 `& s: n- W" o6 A; g8 j! L     1        26     5
    . M* t% x3 Y( J5 s# y& bmy_B 0       674     2
    $ A- \6 g4 L6 p1 ^     1        25     67 A' _# o. V6 o' m1 j. y! p
    1( C5 t1 I5 q/ h; [, ~
    2# y, _* o+ l9 m0 A' |0 ]
    3% `8 l0 B3 \4 h& _. r
    4& D  _8 U& _* q3 J
    5
    , H& l6 @- S, z- A) v% k6
    $ W+ G9 Z) p* H73 l/ _7 P7 Y* |5 W, ?9 l
    8
    , m) I8 A( g' }1 W8 x6 L9( G7 f# U" W! W8 I$ k+ q
    str.findall:功能类似于str.extractall,区别在于前者把结果存入列表中,而后者处理为多级索引,每个行只对应一组匹配,而不是把所有匹配组合构成列表。% [, Y) |6 P5 `3 f& N
    s.str.findall(pat)
    6 B1 x# P, f/ R1
    * a' \  Q& O5 a* F$ f- T: bmy_A    [(135, 15), (26, 5)], @0 y+ P' H& l* W8 [+ `3 c- B* G' G
    my_B     [(674, 2), (25, 6)]
    5 Q0 E8 |2 o; j7 _dtype: object4 r  O, o- w/ \4 P; T
    13 J  h# c& w7 H" I
    2
    & a; I8 T& F$ v) j9 N. u. O: }! }36 u7 C6 q% @$ z
    8.4、常用字符串函数
    % `; _9 J$ ~1 J4 A  除了上述介绍的五类字符串操作有关的函数之外,str对象上还定义了一些实用的其他方法,在此进行介绍。; G" W5 A6 o. n8 W

    2 L- t6 T  T' q6 F+ d8.4.1 字母型函数
    / M1 |( H/ f5 Q  upper, lower, title, capitalize, swapcase这五个函数主要用于字母的大小写转化,从下面的例子中就容易领会其功能:- m* o1 E% u$ `" ~3 C4 e# X( U! @
    6 |, @3 z0 ?0 W: I
    s = pd.Series(['lower', 'CAPITALS', 'this is a sentence', 'SwApCaSe'])
    5 D6 u, }( u: z# t; p: V9 u2 ]; ?# a
    s.str.upper()3 L% s% X3 u- M. P
    Out[87]: 8 d/ o# s7 @. V1 k5 C) ~5 L
    0                 LOWER
    6 K# \+ x' L1 K6 {( U, g/ l+ C1              CAPITALS" ~8 ~6 h0 U; T" {4 |% I* L0 ?3 _
    2    THIS IS A SENTENCE! ]* B9 M/ z5 i2 f
    3              SWAPCASE; _4 s/ E' @! F& y, L* p
    dtype: object
    2 r% t' T8 [& {3 M$ M9 k4 ~0 C& L; }2 g% N( ^9 }; }
    s.str.lower()" g, z! W. Y8 T1 }7 |
    Out[88]: 8 i( e& v1 d8 U  t* h
    0                 lower* {7 |6 h( J- X7 a
    1              capitals
    4 C+ {8 s! j: I/ O2    this is a sentence
    $ _3 m! K# D# H* ^, x2 X3              swapcase- d: U3 O; C+ H% Q6 r: B
    dtype: object$ M$ X9 l  S9 R% F
    ( q; F) b9 a  K7 D  A! b& p
    s.str.title()  # 首字母大写$ n& G. R( p$ w0 Z
    Out[89]: & M* G9 _0 _. k% V6 r
    0                 Lower
    6 @: S# E. Q$ o1              Capitals
    1 c+ E7 R" q! d: r0 Y- p2    This Is A Sentence+ U8 C% d" `% J( M
    3              Swapcase
    1 A% m9 v- p: u- ]# Q8 Ddtype: object# i4 b& Y, q1 A- z, M8 _) E& V

    ) q, U: b; @% B' ?7 D' Cs.str.capitalize()  # 句首大写
    % }0 Y, }3 r# x4 yOut[90]:
    ) _% d9 s! U# T* S& j2 d& ~0                 Lower
      X* f4 ]: }( A: [- P# v1              Capitals
      J" b; ^% x5 I+ C% @5 d3 V: P2    This is a sentence+ h: h8 n+ @3 S
    3              Swapcase; S& l* y9 j1 i
    dtype: object! i& a3 n& Q) N2 B+ W& ]
    : M+ }# U2 H9 Q0 c; B. h
    s.str.swapcase() # 将大写转换为小写,将小写转换为大写。$ S3 a1 \5 o6 |( {2 p; E& R
    Out[91]: 7 C2 v- i0 _: m8 `6 E
    0                 LOWER
    6 d' n( G" Q2 x' z1              capitals5 F' {- j2 E! S  s  s2 M
    2    THIS IS A SENTENCE
    $ m* D. r* t, f  Y3 i3              sWaPcAsE
    # |7 J- F, k+ k6 C; pdtype: object
    2 v) e4 ?" l. M; \: V; v* T& V0 x' P' z7 }: {" g0 ~/ ^4 {  S  W' [
    s.str.casefold()  # 去除字符串中所有大小写区别
    3 p# N2 I6 I+ X9 E& l+ X$ N( k$ r0 U7 {# u
    0                 lower& U9 b# _% ]4 G1 x
    1              capitals
    & b6 D: e1 ?6 J0 O- Z$ a2    this is a sentence
    + K7 \& V( ^7 ^# Q  g) k9 z3              swapcase
    1 l+ ?! D. T) q1 u. u# C) P: l+ m
    1& C; a) z# ^, Z- L* z' T8 D! T* a* f" H
    2
    , W9 x$ x% i; }$ o, D3# X3 F4 m3 p& w# ~) Y. F  S
    45 ?" o6 d+ h4 R7 H2 j3 \
    55 |: Y% ?! g$ h& G$ {) F6 k0 w* u
    6* K1 ?' A, a4 l0 z$ ~
    7+ m* S9 p* U% h) X* y9 K. q7 ?
    8
    8 v( P! Y$ k9 k9
    # U/ T. d0 w3 k, j' [10; d3 c7 S7 o2 a! b
    11+ ?1 l4 M3 _2 F: p' C
    12+ Y- g: I1 E* I" q1 b3 H
    13
    + h' ^: X* m, E" [14" v: |' V  P' {
    15
    2 Q+ b7 V& I; @' L; ~. I16
    7 P4 @7 D; `  X% W17
    4 {/ M- |6 O  t1 X, e182 i" ?  L0 ?! e& G
    19
    ) O% i/ @# d' s+ B/ L# [8 X20
    3 c2 y& _' I# e21
    : a  Y! C/ ]( [) w* M22
    ; ^* {* f* D+ v23
      N4 J, |5 U9 q% A/ r/ H1 h24
    5 k! q4 e9 ]  ^; ?: f# L% }2 x: v  t25
    # x) A0 c! m. D5 v26; _4 C/ Y: ]5 f5 `" }& Y( }
    27
    ' e$ _5 r8 ]" ?1 y28
    * Y, A3 c( J. ]- |29
    9 {6 n: j2 u: M, V# i  k3 Z" P8 O% V8 z30
    ' Z, {1 `* @0 D8 d6 M: P) M31
    ! y8 y. Z! p8 S' V$ l32
    2 C8 {, t( O1 j33
    1 O4 E( u; V7 X  f8 ]6 T346 C+ w) i, l3 I4 ]" w
    354 l3 H1 r2 b: r3 v
    368 t1 F# x9 H0 t9 O' z7 n- C+ Q
    37# X2 x; R8 _5 `8 ]8 |) U0 j  T. ^
    38+ P) r& Q; u% K+ {+ ?& P0 a
    39; W. r; q1 H3 G
    402 j1 H* I; f6 @1 ?# h
    41
    0 K, R' s* t0 R. m& f42
    & F3 ~' X# n- _% a7 T# H9 N' R9 ^+ d43- H* Y$ l" @. O: T
    44* g$ D: H& z& Y7 u
    45
    " m# D3 G" K* ?6 d' u: W46
    ) Z2 ~& ~+ A5 f47
    3 \( f0 z1 f8 B" C48) m% N* `- \" z; t
    8.4.2 数值型函数* I. b; B0 L; P
      这里着重需要介绍的是pd.to_numeric方法,它虽然不是str对象上的方法,但是能够对字符格式的数值进行快速转换和筛选。其主要参数包括:
    . D( L# Y. X* J$ z7 g; k
    * K2 E0 f; M: }# Q+ K' O- {errors:非数值的处理模式。对于不能转换为数值的有三种errors选项:
    * I" d! n  n. Z7 y6 r& Fraise:直接报错,默认选项6 N1 ^, d2 q5 L4 G0 z; ~
    coerce:设为缺失值
    5 J; m8 Y( i: \9 G7 S) E3 vignore:保持原来的字符串。
    : D  \5 K' X) o% U1 g1 F5 tdowncast:转换类型,转成 ‘integer’, ‘signed’, ‘unsigned’, 或 ‘float’的最小dtype。比如可以转成float32就不会转成float64。( l0 s9 x5 V+ C" Y3 |. w
    s = pd.Series(['1', '2.2', '2e', '??', '-2.1', '0'])9 R5 t6 b" o! G8 E

    5 R9 A! u* M! Fpd.to_numeric(s, errors='ignore')
    0 k' f. w: p9 M; jOut[93]:
    ' Y, H9 }# v. t5 K1 q5 z0       1- m4 W; K0 H0 v+ s# L$ _
    1     2.24 O5 Z! L, w% z* y- [" N2 c4 Y9 F
    2      2e0 w2 O9 D" U3 T7 T0 B
    3      ??
    + h# H. Q+ E! t4 i' B" v8 P4    -2.1/ m! p* L0 q" _* ]* O3 i
    5       0- e: Q$ h2 M' b/ g
    dtype: object( G* h" b0 T3 A) ]& J6 L4 P

    / T% o7 x, q3 E5 c. Hpd.to_numeric(s, errors='coerce')
    ( L6 z' o' j$ B  W7 OOut[94]: % D& D# q1 a' C9 U1 X
    0    1.0; |! f; G- k- U4 |
    1    2.2
    / A# P: f& o: i- g3 t' p2    NaN
    7 Y9 t1 F. x  e( T8 V. R  u+ j3    NaN6 g2 P: l8 @' H5 Z4 Z* O  B
    4   -2.1
    - o8 ], N& ]* Z+ A6 m- n5    0.0
      ]0 }2 r' t6 Pdtype: float640 @4 b% u' {& Z+ H
    6 @% U0 ?% Y# V' R1 S
    1
      K4 ^, d# d7 U5 r# X4 u3 N) ~25 O9 l3 l- O& Q. Z# C8 {! w1 v& E
    30 S/ ?$ l1 m3 a8 C2 X; T
    42 y$ E: P0 f3 _) _+ J
    5
    - G  V/ p5 k& Q5 }, C2 \' a- w/ _6, X6 ]5 y6 ^: E: F
    7
    $ m& V  Q8 j. n9 O8
    7 s* q3 S8 t* h" `; I9
    9 j4 j" e! w3 \: R& l101 l' E+ }7 c! F) w) W
    11
    6 k4 S3 V' L0 R4 ]! e) [& _; X125 P6 Y( O! h' P' Z! _$ ~6 p
    13
    6 P( a" T* m3 E* x1 u2 i14
    ; a8 h! j5 E2 U15
    9 ^: D" y+ D$ J7 u164 _7 M# \6 a  y( L
    17
    ' o3 Z! b- k8 z+ g2 Q$ S4 H2 m; G182 Y" l6 B6 m1 K5 q$ H
    19
    : h) n( m, `/ k  }% E3 w20
    ' S7 {$ O% I+ e! D+ r8 Z21
    1 M) Q. [0 Z* h% H0 z2 @  在数据清洗时,可以利用coerce的设定,快速查看非数值型的行:+ F7 s& u+ w3 {4 Q6 i4 W" L/ y  l5 _
    , k$ y& V, I7 s& o
    s[pd.to_numeric(s, errors='coerce').isna()]
    7 d9 L4 V6 K3 p& Y2 e7 |7 OOut[95]:
    / }4 E9 ~0 K4 O8 E5 ~2    2e" f# H: @- S2 ^( W9 s
    3    ??
    5 f7 K$ r  l+ [2 o5 b: {9 G6 D  Idtype: object
      V# Q* z9 C" @11 u3 X+ i" M: A% G0 {
    2
    2 K! q* j/ {1 q9 b+ H34 a% b: i: e/ B. p( d% \
    4
    5 Z  N$ H2 Y# [5 n9 \5
    8 i/ [! y! H& \: N8.4.3 统计型函数$ F# r0 y  X6 [$ f1 g& j5 @
      count和len的作用分别是返回出现正则模式的次数和字符串的长度:
    % A  q8 G: X% }  ?0 y8 y3 {
    $ o! |3 ]$ w/ v1 a0 Y& P2 S% [s = pd.Series(['cat rat fat at', 'get feed sheet heat'])  t) m" ~; D7 d- y
    9 u5 Q- X- M. y
    s.str.count('[r|f]at|ee') # |左右两种子串都匹配了两次
    8 i+ ~6 {5 C1 f5 j" h+ dOut[97]:
    7 p" ]. f+ \( r* z7 p0    2
    % F# a! i9 A$ G+ r1    2: t5 O* w8 j! E
    dtype: int64" |! {& h1 n% s

    0 ?6 ~: i: w' bs.str.len()
    / G+ L! b; i) a# l) N3 e0 WOut[98]: & m9 H& C/ {3 ?7 W4 ]  x) K9 o
    0    140 Q7 ?# H/ ~8 g% k5 W' h, N6 u' w
    1    198 f. E9 s. e! e7 C! h1 s7 i
    dtype: int648 n7 }" r% k, W# L: m# R
    1
    0 N- ^' r; R2 ]/ b; y7 \29 i, I' ]& Z- M- y" p& R
    3% C2 v3 z( O4 v+ b
    4* C7 R  b) I2 |. W/ i. j7 H: {
    5' c$ q. B% M/ {
    6' w4 Z% \4 V" @  a) B
    79 u; e2 ]& l4 _# q0 D
    8
    1 B: N) t2 ~6 ^* t% W: Z9; L( p" _3 W# T2 J7 O  ^
    10
    ' F8 R+ m" d/ X11
    % S- j7 |" V5 b  {: m12
    # b' U/ y, r# {1 \, s% K8 e138 |5 J0 u7 Z& B2 H9 `0 M  x- X- F
    8.4.4 格式型函数: t8 R% L# Q, a! z6 \
      格式型函数主要分为两类,第一种是除空型,第二种是填充型。其中,第一类函数一共有三种,它们分别是strip, rstrip, lstrip,分别代表去除两侧空格、右侧空格和左侧空格。这些函数在数据清洗时是有用的,特别是列名含有非法空格的时候。) O6 z! f6 ?5 |; J) {+ Z- @( Z. K
    ; U5 o/ ?9 {  X
    my_index = pd.Index([' col1', 'col2 ', ' col3 '])
    ( G8 w# b- }; v" _0 u6 Z) L$ q( X* |  N" u1 _8 T$ M
    my_index.str.strip().str.len()# v0 {+ p0 U7 v3 ]  T. i
    Out[100]: Int64Index([4, 4, 4], dtype='int64')
    # k- F& @$ c0 h  k; r* w4 Z+ {: e2 R. y0 G/ S% y& O; R7 R9 x
    my_index.str.rstrip().str.len()& F9 y2 B5 f5 F6 j
    Out[101]: Int64Index([5, 4, 5], dtype='int64')
    2 k/ J# D6 q/ k" A7 q# ]" n, y1 z, @. c0 ?# q5 T+ m
    my_index.str.lstrip().str.len()6 n( t% S  }6 B" B5 o
    Out[102]: Int64Index([4, 5, 5], dtype='int64')( s6 t5 \; j; Y2 I
    1/ I6 G* ^( J/ ^8 u* [% _/ e
    2
    # V! g# Z5 ~" M0 n3
    ! q' Q, ?" l; O  \- r4
    5 \7 U# g- i. ^% L4 ?* c) x0 Q2 ]- X5
    9 k4 h) |  [) R- o! ?: P6
    : A; O+ B0 d# z. }* @5 Z. ]* V7; a3 B  s* |) q0 t; `( Y
    8
    2 W# z) e5 C# |* r$ V9  q) k8 @) D5 T% {
    10
    / V% C$ w6 x2 L. g) S  对于填充型函数而言,pad是最灵活的,它可以选定字符串长度、填充的方向和填充内容:7 N: R5 ]* K$ a! r5 J$ E

    ' X' H) v$ ]( {- Ws = pd.Series(['a','b','c'])9 F2 B3 x- b  c! L# D  x
    0 u. J- H3 p* W) N" Z/ Q
    s.str.pad(5,'left','*')
    4 z+ O: a; R4 W  S/ KOut[104]:
    5 `; Z% y. o# t9 c2 n0    ****a$ T) j4 `9 X7 v9 b% r9 p
    1    ****b. P% e+ q5 p1 |6 w
    2    ****c
    $ _8 N$ b* T: H, d/ ~. Vdtype: object
    . k0 N, K& H% l! d) D# _& {1 b# t2 m( o
    s.str.pad(5,'right','*')
    # h' i1 k" V- O5 Q: p( i; xOut[105]: 8 k2 @  Q+ Z+ j4 e, u
    0    a****0 [8 H. w' \/ i
    1    b****
    * O+ t) z( t2 |! d2    c****' C# k6 o& ~. J! p0 {2 \
    dtype: object' J7 @0 y7 B4 Z' U
    # y8 c* C( Z! [
    s.str.pad(5,'both','*')
    * W/ Q$ m3 @% d+ MOut[106]:
    1 u2 x3 S& o7 [+ V0    **a**/ x9 A8 S; a8 q; O
    1    **b**
    5 @( c  v& \% U% h# u( C2    **c**
    ; Q0 _% W& p0 W/ B9 |' }4 mdtype: object3 E, C( i* [0 ?& R: f3 Y0 l
    1 a; A: W# ^3 U* n6 k) k3 w0 l
    1% i- s3 `" u3 E) V( f
    2
    ) i1 d: S- E" a2 S$ Y/ [, v3) q5 u5 k2 ^" n' ]
    4
    # m5 _" x8 }. r. f4 f9 I/ j5 }5
    6 `/ S& a! T3 I: W5 k4 S+ }6/ G% h# L, ]- U7 N1 l
    7& u  \' j% J# z
    8
    9 T0 I6 l/ c  u  Y9
    + g" @% _* `3 ~10, {# i3 s7 s' ~
    11/ a# o2 q5 P. u: T( d2 b6 S
    12) l2 t4 [3 z' w% A3 v
    13
    / j' {% ^' p& |1 m& V5 W14
    . ?1 ~* O( V6 d& k) I" c  ]15, U9 i) ^* @; r/ b- j) q
    16
    2 J) N% i; G5 x2 U: R17
    7 Y1 k: r+ C  f1 r7 V18- h  `1 U$ u; g% b' P* @+ i
    19
    4 f7 k6 r5 M: u/ H8 y20: u- n5 h  \: V0 `
    219 a: @7 v1 N5 C" F
    22
    / C- k% Z1 a5 h# c% t6 J0 E; Y  上述的三种情况可以分别用rjust, ljust, center来等效完成,需要注意ljust是指右侧填充而不是左侧填充:
    ; T) O& \7 O$ y3 K  F6 ^6 R
    6 ^/ r9 \$ n4 O5 M/ b) k/ h- fs.str.rjust(5, '*'): g/ ~4 A2 D) K  N3 m8 w" ~
    Out[107]:
    . f* j) g% Q+ ?" q0    ****a
    2 C* ?3 o4 J  q. T1    ****b, Z. g6 d' c- D6 e) _/ Q' V
    2    ****c# ]: v2 K8 p9 M& F* x4 T: ^) ^" k( U
    dtype: object* o! R" e1 A" ^( o/ _
    ( }, \, |% n+ w% w- D$ y* v& M
    s.str.ljust(5, '*'). q- Q: i9 q2 y# K+ w
    Out[108]:
    7 o8 ]( ^" q. s3 t2 f0    a****, D% ?; o1 j0 X+ w+ F0 `$ K# M
    1    b****
    4 l; j, z  m* q7 k2    c****, _' h" L5 T! v5 a  L
    dtype: object
    ( F# a6 f2 Q8 p0 P* U5 B; L  z2 f9 l7 F: h4 F# f! Y
    s.str.center(5, '*')
    & F& t& C3 `) P$ M8 Q. Y. x$ yOut[109]:
    / B  q- x0 X# _$ E0    **a**
    * ?5 `: }/ H8 G% v" s1    **b**
    9 j, L) m- u6 K% _4 H9 X: K2    **c**
    5 B0 m- o5 e1 xdtype: object
    ! C9 i# j2 D# V: ?* H8 Q' K/ [  e/ X- i, {6 R+ [
    17 G$ ~7 n* n6 F+ N2 w* @$ L+ ?
    24 k- R: u$ d; h. ?5 ~9 k
    36 I& W9 {3 E' R' @4 q
    4
    ; S) V9 _2 Y3 ~9 [3 g5 ~+ b- f5; ?3 A7 z; M# T$ L
    6
    % X0 T7 t; @6 S# v4 u: b! I7
    / y$ n0 [7 [7 b* O* n8
    7 O$ ~5 g; y; Q$ W+ u9 z# I* E; C9
      F# J) O0 c3 c( m# s0 J8 \7 U10" O5 e+ F. l! E6 a( G
    11) r2 t- h, _$ }- [3 m
    12( J2 e) y! F+ l  f: @
    137 J+ f4 G: y! e, W: A- }7 o
    14
    & n- G  ?4 Y! z6 i' G- z/ _15& W9 V. v2 b. |3 B" P
    16
    5 \- B- b- {9 y; ]3 j17
    ) L# u3 \6 A. }% j$ `% _8 f18
    , h' q1 c0 t) J+ o& V# W7 a! s8 v19
    . I& c4 x' W( y2 z7 X203 |( D* v% @* x6 z
      在读取excel文件时,经常会出现数字前补0的需求,例如证券代码读入的时候会把"000007"作为数值7来处理,pandas中除了可以使用上面的左侧填充函数进行操作之外,还可用zfill来实现。
      i; X3 _+ `7 I7 V( W  M0 N
    5 z2 v) R1 b4 t7 _- Fs = pd.Series([7, 155, 303000]).astype('string')7 K" c6 @% S( F
    . ]! D# M. L( C* ^2 n+ Z' W
    s.str.pad(6,'left','0')& O* g" D* l' e2 s9 B$ g* S
    Out[111]:
    " r1 l# }. c# {. Q8 ~( A. g0    0000071 i+ B) J8 s6 v2 N
    1    000155
    . X  }) ~! _7 b4 y% o: T- V$ |1 r2    303000
    ' G7 ?5 q& ^1 p% E) w& vdtype: string
    ) ]' s# z( l9 S1 N! X$ d$ R" O* e% x
    s.str.rjust(6,'0')
    7 r+ e% c/ _7 C  m+ z+ a2 i" U- `9 U' DOut[112]: : T1 N8 c; U- l' A. n
    0    000007
    0 `( A' q" m( f8 \" F  z0 z0 S' H1    000155
    - ?, `% c9 A: k! k5 E2 i* Z2    303000
    # r2 c2 e' I+ `" ldtype: string
    $ e- H, F/ E8 z4 Q, x& }4 n; ^. z0 p( K
    s.str.zfill(6)2 V, Z/ G5 T6 ^2 m/ B+ w" S
    Out[113]: * z# i( i7 g- t1 S
    0    000007
    # N/ `) [1 M  Q/ j1    000155; `6 e9 q- p5 t, u
    2    3030005 ]* {- S( n9 n3 Y
    dtype: string$ G2 g# }9 U% {; J+ V
    + _% W7 p; [7 M8 E
    1! K+ ~3 _; F0 q; ]) F+ w( g1 a
    2! d1 _$ v0 Y2 k- Z1 d
    37 F- n* A4 ~! ]; E
    4- ^- l0 z+ b- G; _2 N
    56 k9 w! |3 O" S% @
    6
    - k/ h& u1 [9 J6 S$ g9 o9 Z' u7
    3 k  M5 @+ x- o" x, _8; K$ L5 \0 x! L# e
    98 s8 T: R* Z9 F2 P5 W. L
    10. F. D* f2 s+ H$ h# w6 ]3 x
    11/ a- e7 G  [% s) _* ~3 e
    125 j5 V, G* l  T! F- C6 A
    137 |; I7 H+ E. U# @
    14/ N$ m* Z( w6 f. ~. ]1 Q
    15
      v% t; Y0 f+ d16
    " S) b6 [  \4 o. ]0 C* h/ c% J177 j2 y! o/ E8 c! c+ w" N' M
    18
    2 \! V5 U5 e6 L3 N4 E, K5 b8 ^19% \0 h" t' x. G/ x+ S; x  [
    20
    - w- m0 C8 ~, K; s5 j21
    1 e! j1 O' i+ A- X: [222 e( r, a, N" \! A' ~7 q" C- f: w
    8.5 练习- S" X! O8 {; G0 x/ ~4 a& d
    Ex1:房屋信息数据集
    ) C+ C' }' N4 C- S现有一份房屋信息数据集如下:% l  k3 q+ _" B
    3 `5 A( X) N6 }- s$ U
    df = pd.read_excel('../data/house_info.xls', usecols=['floor','year','area','price'])
    ! d# M5 [  Y2 A' J. R& Bdf.head(3)0 |! I6 B4 E. z9 ^' @
    Out[115]:
    # ]! @. i1 r  l3 S" _2 \+ \; }      floor    year    area price; D! s2 ]2 P# y' C) c5 Q. x; |6 p
    0   高层(共6层)  1986年建  58.23㎡  155万
    7 f" ]% C- s1 g5 g! D1  中层(共20层)  2020年建     88㎡  155万0 v5 l- \. e+ M. \- T; I
    2  低层(共28层)  2010年建  89.33㎡  365万# v8 y6 `% C4 b  }# `9 t4 d
    1# m% M: H/ L8 l' t& |
    20 y# }0 h8 X+ _% z5 w
    3
    ' X7 Z& E  |3 E/ t1 k* B8 K4
    3 H) y. S$ U3 U' j2 ?8 D54 @  {9 _$ E9 V$ Y
    6
    % o5 ^  s, }6 [1 X4 D, N4 [7
    ) N0 y: {! }- z( z1 m将year列改为整数年份存储。
    + ]1 T% i- T8 r2 g; c6 Y2 R将floor列替换为Level, Highest两列,其中的元素分别为string类型的层类别(高层、中层、低层)与整数类型的最高层数。) R) E7 M2 P! H5 A# f
    计算房屋每平米的均价avg_price,以***元/平米的格式存储到表中,其中***为整数
    ; }& h1 Z9 N. n) m/ ]将year列改为整数年份存储。
    % ?4 a9 ?# E* p0 X) e  G% K, @""", c. C  }& N5 q& v( |
    整个序列需要先转成Nullable类型的String类型,取出年份,再将年份转为Int64类型。
    3 _+ R; J0 V2 B1 q- D  b, S4 w注意,转换的类型是Int64不是int,否则报错。即使astype加参数errors='ignore'跳过缺失值,0 n$ u8 r( Q; C! s! c  [8 A- B
    转成int后,序列还有缺失值所以,还是变成了object。
    2 ~; q$ o' T' ^. p& O而整个序列转为Int,就还是Int类型,缺失值变成了 pd.NA 。( q0 V0 o7 s! @6 P. l) n9 g; V
    """
    & H3 O3 M- O2 \3 F2 e; S# v$ hdf = df.convert_dtypes()/ ]  h$ }( C1 e' v" [  Z
    df['year']=df['year'].str.replace('\D','',regex=True).astype('Int64')' _) Z9 u9 [# b" I0 z3 g9 n  K
    df.loc[df.year.notna()]['year'].head()
    # w% b9 Q3 U' U  p: ]8 E: @; c$ {
    3 p0 S7 K+ y5 q1 G& [! k0        1986/ y6 [3 ^- ]( c8 Q
    1        20203 t' M# ^* ?9 ?% c
    2        2010* C- R9 v/ y9 n+ t3 A
    3        2014
    # F9 S( A; D0 X, U4        2015# O* F) C; e% H- Q
    Name: year, Length: 12850, dtype: Int64
    . t$ C, m% {  x' K, l# z  f% ~4 Q* z* f& U/ v2 @+ V$ r$ [# r
    1& \3 b! {" C- I' W& z
    2
    3 z$ D* U& t0 H) n# _1 K% K3
    # Q" X8 Q, ^) u0 d4
    , n2 i$ i- \0 J& p; H, H/ e5! m7 {5 D) I! Q, X
    6# L. e* M, P2 r' M
    7- w3 k- m% j8 m: ~! [0 D3 I( w: C; H
    8
    3 o) h( e: @" G$ _9
    " o/ G% j, D6 ^10
    - T1 n, h' e& Z0 V9 V11' T& j* b! p3 o
    12
    4 x0 ]' U: a0 z+ k5 C13% Q8 Z8 R9 ^4 ~5 ]# e
    14
    3 W7 s8 N$ b+ T5 H, Y7 K" E" X- [15
    * `2 J& s8 t2 F- }% T1 b16) s6 I# B' H# _. i4 s7 c; I& ^
    参考答案:+ U# Y( i. l7 p/ c
    5 V# y. `8 k& ~1 \$ e, m% e
    不知道为啥pd.to_numeric(df.year.str[:-2],downcast="integer")类型为float32,不应该是整型么+ _3 o6 j% e) R
    ; |% Q" f. C3 Q
    df.year = pd.to_numeric(df.year.str[:-2]).astype('Int64') - M3 i/ h; \$ x# S' P' x  J
    df.loc[df.year.notna()]['year']
    9 o% K& f/ i1 J( s- {$ f1
    + k3 I6 P/ r% t: }2
    . f4 S% k# S, {+ k. J5 s, V将floor列替换为Level, Highest两列,其中的元素分别为string类型的层类别(高层、中层、低层)与整数类型的最高层数。
    7 W) G0 D6 a) k% vpat = '(?P<Level>\w+层)(?P<Highest>\(\w+层)'( K4 p1 c* f+ }" x: X3 n6 |" L# P
    df2=df['floor'].str.extract(pat)  # 拆分成两列,第二列还是(共6层得形式,所以还的替换一次
    4 a% b( }6 R- ~4 |0 a$ Qdf=pd.concat([df,df2],axis=1).convert_dtypes()  # 新增列拼接在后面,再次转为Nullable类型
    : `5 a0 p6 m" W$ q* n) Mdf['Highest']=df['Highest'].str.replace('\D+','',regex=True).astype('Int64')              
    ! Y' @2 Y2 i; w3 ^. z9 u" s2 ydf=df[['Level','Highest','year','area','price']]
    + O# }7 d  Y  K" Z9 C4 z) Idf.head(); U4 Y2 m; n* g. ?; ~/ a& v# ]2 e# \9 U

    7 r3 U& N: d: u   Level  Highest        year        area        price6 W( p$ ~- r8 W8 }7 U2 T
    0        高层                6                1986        58.23㎡        155万
    7 Y; ~- \6 n9 x' n9 ^% _1        中层                20                2020        88㎡        155万! g% T8 h. {! g+ a5 A: o( S
    2        低层                28                2010        89.33㎡        365万( M) e( h& _+ D+ W$ `
    3        低层                20                2014        82㎡        308万2 H& Q1 N; H! ~" k$ l- Y( x3 Q
    4        高层                1                2015        98㎡        117万6 n9 L- g( u5 z0 a* n! W
    1
    9 h; I# h8 I8 v3 k- m2
    & q# K, O/ j! l9 }3
    4 T6 Q. X9 S0 K  v* q% i4
    ' [. m4 U2 |2 O2 [* f52 R6 Y, s; L% A9 A$ |$ C  f
    6
    6 P! |7 F3 G; r3 |7* ]! F# m+ l2 I/ ~8 J9 G
    8
    + t: I, Z4 B9 O. g( A) n0 `( I3 y9& V' P: N9 ~& ?+ K( O
    10
    ' U5 d! e, F% q. ^3 G+ t11% w; ^7 J3 q* w. U( V( g
    12* F# H: a% |9 ^) [
    13
    - Q  T% K' i( z  {9 p* Y0 R, o# 参考答案。感觉是第二个字段加了中文的()可以准备匹配出数字,但是不好直接命令子组了% z( W) y, t. S' M( g& _6 D
    pat = '(\w层)(共(\d+)层)'/ b$ U$ G  I* O; b# b2 T+ I, P
    new_cols = df.floor.str.extract(pat).rename(  W4 O6 G* a8 J( [  n1 Q& }- a
                        columns={0:'Level', 1:'Highest'})& [4 O2 L) R1 j$ j0 H; b
    6 l) X: n  T! ?* Q
    df = pd.concat([df.drop(columns=['floor']), new_cols], 1)
    & I' s- J; l  f( x4 b9 r1 @df.head(3)7 n" L' J( ~- \8 ?. x

    ' J8 c8 ~% z, }* \7 [Out[163]: 8 V* H( |) c- J' ]! Z
       year    area price    Level Highest) p. h( k+ r2 z
    0  1986  58.23㎡  155万    高层       65 M+ |2 a6 N' s) [# S; _7 n7 p: i
    1  2020     88㎡  155万    中层      206 D* ~" e& Y5 l
    2  2010  89.33㎡  365万    低层      28
      ]( O! E  N% d! p1 a6 G/ y, r' D# X) j' g1+ E+ o: W- P* J% B
    23 ?% v! J( a7 P- P: ~
    3$ R" [7 T7 _5 b. m, v
    4* |+ z7 x9 E# \2 x. B
    5
    0 M: y6 m* a  \( I0 ~6+ N9 i$ O' V" O
    7) b3 S2 t: G, i/ S1 a1 i: i
    8
    * @, q- {2 ^  K/ Z5 t( m9; k8 j0 l$ i" n& |0 \! O
    100 T1 Y6 v) y# C( k1 |
    11
    ) |9 K& }7 M9 g4 j1 {, o12: f7 ?  k$ m) l) A( K; @
    13
    ) V9 b7 y& d9 e6 c& I5 |2 s  |* S, D计算房屋每平米的均价avg_price,以***元/平米的格式存储到表中,其中***为整数。' _4 ]* U4 ~- z
    """4 H' B  u! f6 Z+ z, p2 F: G* m* x
    str.findall返回的结果都是列表,只能用apply取值去掉列表形式
    5 h4 Q  O7 @3 |7 u9 g3 z参考答案用pd.to_numeric(df.area.str[:-1])更简洁) _( w% K4 y) O; w4 h6 M3 G
    由于area和price都没有缺失值,所以可以直接转类型
    ) {- ~0 b5 m1 `0 L"""9 y: o3 i+ @# A( ?/ Y  ~. D
    df['new_area']=df['area'].str.findall(r'\d+.\d+|\d+').apply(lambda x:float(x[0]))/ O. T7 s, u/ d
    df['new_price']=df['price'].str.replace('\D+','',regex=True).astype('int64')
    / d0 Q* H1 A0 }. a( hdf.eval('avg_price=10000*new_price/new_area',inplace=True)! |, O, ]# }& P4 [/ a
    # 最后均价这一列小数转整型直接用.astype('int')就行,我还准备.apply(lambda x:int(round(x,0)))0 \/ M7 u% K9 G/ ]. K0 |& y6 T$ x$ J
    # 最后数字+元/平米写法更简单
    9 T0 R8 ]# K+ E/ B  B  U  vdf['avg_price']=df['avg_price'].astype('int').astype('string')+'元/平米'
    ! K- E2 d) o- B- `0 K4 y6 ydel df['new_area'],df['new_price']' d, n$ r2 A) [
    df.head()5 u6 M+ O# V* ]3 j
    4 D% U" Q! Y4 Z+ S' n3 J
       Level        Highest        year        area        price        avg_price8 g2 I" m9 A6 B! t" |) m, M; G  @
    0        高层                        6        1986        58.23㎡        155万        26618元/平米# ^) k9 c% l  I  m) c
    1        中层                        20        2020        88㎡        155万        17613元/平米
    . Z3 l; ^! U9 W+ j  N2        低层                        28        2010        89.33㎡        365万        40859元/平米0 \2 R) R  x* \
    3        低层                        20        2014        82㎡        308万        37560元/平米
    7 j; e, Y( ~! q- J6 ]4        高层                        1        2015        98㎡        117万        11938元/平米
    8 l( r% g, @6 V# E% ?1 ]6 m9 \) w2 o4 ~( a& Z3 i3 D3 R, {7 y
    1+ l# M5 H2 H: J* Q7 j
    2
    * o: z' t( K- D) O9 M3
    4 m: a; d& P# J3 |  Z4  x4 _4 I, C! o3 X" P# B
    5; a' r# p/ I, d" E( \4 B# N5 e, U2 z
    6
    $ ]0 k: [1 @3 U( p# U7" k4 }  h8 x' }- l' [- u
    89 j5 h( p) m8 v' ]. r! m6 q
    9
    ; _/ R' j4 a) ^1 }105 F# z6 w4 ~/ h* A
    11
    6 h/ x& d2 F/ c. y4 j( n12! K4 V: ?9 a0 H$ O5 y& I: x7 b
    13" p* t  U* v6 B: i1 D# q
    14
    & o0 X* _" n. I4 v15
    9 J  B& I+ r; _7 W0 U% P$ q* W160 R2 M+ N5 R9 R
    17
    ; X' u4 b- E5 l7 r8 Q7 `187 l( q% n! m# |5 W. U, p
    19
    ; Z" n# F  |4 }2 K- e; o* C20+ Q. Y  D" C; T# G' v" R$ d
    # 参考答案7 Z( _. ]9 C1 G! W2 ~( r1 X& u  z
    s_area = pd.to_numeric(df.area.str[:-1])
    * i$ @  N2 \* i3 Us_price = pd.to_numeric(df.price.str[:-1])
    4 O! U- [5 a% h5 K4 Mdf['avg_price'] = ((s_price/s_area)*10000).astype(
    ; q3 [/ I* R0 N& t                    'int').astype('string') + '元/平米'% L, J2 ^+ y* W* V

    $ l# @3 D# w4 M( x- Hdf.head(3)
    1 r7 O  E( E- ^! ^Out[167]: 9 t4 [& G" W4 \% N0 b
       year    area   price   Level Highest  avg_price
    ! L/ W" }0 v$ k0 _  w/ t0  1986  58.23㎡  155万    高层     6          26618元/平米
    ; q& x! e$ y5 F9 K- |. T1  2020     88㎡  155万    中层     20          17613元/平米
    , W4 k0 @/ g$ H6 l2  2010  89.33㎡  365万    低层     28          40859元/平米
    # }" X$ H5 x: q8 w% ~1
    7 D* u: o4 ]+ o0 o4 X6 p9 [2
    * S" Z# N6 B0 R) W1 b6 n9 p3
    0 N  T7 }, {' j% f9 r+ I! Z3 L8 e4
    2 m) H, Z( J3 v" I2 T9 U5! _0 P& y; [5 q6 g7 I
    6) n  I0 l3 Q/ `1 M) G
    73 F$ C8 Q+ \0 `0 Q2 ~
    8+ T7 K0 u8 K1 @& O. x4 A& l* g! O
    9
    $ b9 G6 h: }' i* L( @) h2 m) g2 W109 m2 U2 Q6 y- o+ x9 N5 q4 ]
    11
    1 V$ z7 Q0 ?2 d9 P% W! F12, p, L5 b! n$ R, I5 t- g
    Ex2:《权力的游戏》剧本数据集
    ; u% w# W$ f+ n1 k; m现有一份权力的游戏剧本数据集如下:
    ) t( s& ^1 h- f
    ! p! H$ P1 [8 Sdf = pd.read_csv('../data/script.csv')
    * V8 ~) m& S: ^+ j& tdf.head(3)
      q& L7 Z$ I( g" c2 Z
    : [$ \; d: P; \! O/ g, hOut[115]:
    9 X9 v. [9 d/ a& r) E1 kOut[117]: # v/ [, v' ^$ u! W, _8 H/ y
      Release Date    Season   Episode      Episode Title          Name                                           Sentence( z8 T  n! ~" t1 X3 b
    0   2011-04-17  Season 1  Episode 1  Winter is Coming  waymar royce  What do you expect? They're savages. One lot s...
    , g  T" {3 z- h8 C; t# s1   2011-04-17  Season 1  Episode 1  Winter is Coming          will  I've never seen wildlings do a thing like this...- q5 r) N5 ?! H. r$ F" b
    2   2011-04-17  Season 1  Episode 1  Winter is Coming  waymar royce
    6 E7 B1 D+ }3 E. r; ~. Q1
    ' J- R$ p1 H) N1 p) \7 d$ m2' e$ W. [; H! C! J  k
    3
    ( f9 i9 P" u5 w" ^; k) y4
    2 Q+ W* ~2 b5 o% G- i3 u5
    . \3 g8 Q3 Q; z' ?) N0 S6
    " Z" X, p- X$ e: R  W7$ R2 {" W: u+ F  \; T. S
    84 C6 j" c, \1 N6 Z6 N' |9 u
    9
    ) d  k; x  v& B0 F; G计算每一个Episode的台词条数。
    5 S- |9 W2 x  D4 u, I* u以空格为单词的分割符号,请求出单句台词平均单词量最多的前五个人。. x( Q1 T6 A' Z& m
    若某人的台词中含有问号,那么下一个说台词的人即为回答者。若上一人台词中含有 &#119899; 个问号,则认为回答者回答了 &#119899; 个问题,请求出回答最多问题的前五个人。
    0 M! R! Q6 a' O2 v计算每一个Episode的台词条数。
    0 J6 c* B$ h* U5 T- o  Ddf.columns =df.columns.str.strip() #  列名中有空格, z. c# R" r3 h# D1 _3 Y
    df.groupby(['Season','Episode'])['Sentence'].count().sort_values(ascending=False).head()
    / T. b' M" L+ _  N& H: k+ }* a: H9 b  H4 T1 r/ [
    season    Episode  % M6 G7 k! M, B& H' P4 {
    Season 7  Episode 5    505+ g- M) c! o, S/ C! F
    Season 3  Episode 2    480& x0 ^: ?; U! j* ?  j( [
    Season 4  Episode 1    475
    + I0 H, W/ ]6 t; SSeason 3  Episode 5    440# e  u6 b" J5 E+ J0 G( g
    Season 2  Episode 2    432
    + M/ N* c( r/ k) z1( _$ q; k: f; p( ~, A2 Q) d
    2
    " G$ O; C! R9 I; C  `3% t. ^" v; E, F
    4
    : _- b* l% \0 r# d9 J5
    2 x/ K4 X$ ?# r" P0 E; `6
    , {$ H: k1 \7 ^* \: I7
    8 @. d. Z) t& t+ Q+ {: g8
    " q) h# a5 X, i' a2 b9
    - W8 y1 h2 Q9 n2 X以空格为单词的分割符号,请求出单句台词平均单词量最多的前五个人。7 c- _4 O9 T- z7 F0 G# c. ~
    # str.count是可以计算每个字符串被正则匹配了多少次,+1就是单词数8 K$ B% W  W- r( w
    df['len_words']=df['Sentence'].str.count(r' ')+1
    " x0 }/ H! H6 h: Zdf.groupby(['Name'])['len_words'].mean().sort_values(ascending=False).head()8 r' U: G$ Q5 D# X5 J8 j" g  K5 Q
    0 W( q& D7 t$ x- k/ n& O
    Name0 a. z# x; _% b5 Y$ u5 ~6 o
    male singer          109.000000
    ) k. F5 x& O8 ~/ ?" t7 islave owner           77.000000
    + [3 h4 o; z0 l3 M2 l+ vmanderly              62.000000
    % e" ^! C( z  C( ]8 c1 _5 Nlollys stokeworth     62.000000* U% l. f+ n  I( l) d1 v8 ?& E
    dothraki matron       56.666667) }7 p/ N( e0 ]% B; ]" F4 Z
    Name: len_words, dtype: float64
    / T" ?: D! {" T1
    * O( ^& j. U( s# f2
    # q$ C0 e$ j8 n( O& s: X3
    ) C4 N" v6 O0 j1 W4% D" ]7 W9 U* j7 u
    5' a+ q6 E6 t# S8 s! k0 m& M
    6
    * u; [) H' o& s$ v8 U2 t% ]' [  ?7
    / _8 ^% `0 d5 `8! o, J: m2 G& e9 P) x
    9' Q8 i8 \  t! j3 N* A, }; x
    10# o7 j# F, l; ]' d
    11
    4 h4 j$ Y9 n, q8 R0 j5 D' x若某人的台词中含有问号,那么下一个说台词的人即为回答者。若上一人台词中含有n nn个问号,则认为回答者回答了n nn个问题,请求出回答最多问题的前五个人。8 [# Z& l2 I$ J4 n
    df['Sentence'].str.count(r'\?') #  计算每人提问数
    9 W% v$ @/ B+ P0 w1 n7 [3 Ils=pd.concat([pd.Series(0),ls]).reset_index(drop=True)# 首行填02 ]9 G; t5 V3 N  a1 [1 P
    del ls[23911] # 末行删去* Q4 \2 o( P; C* @0 G# I
    df['len_questions']=ls
    3 P+ `; K+ P2 q) j- idf.groupby(['Name'])['len_questions'].sum().sort_values(ascending=False).head()/ M+ C6 `- s9 l
    + n/ K+ o9 J- K6 b) G" d" _
    Name
    ( r  |3 r! u( _2 h2 t! _& utyrion lannister    527
    # B8 R  C" c/ g' X' `) f4 _& Cjon snow            374
    2 ]7 d9 H+ y9 Z+ O8 zjaime lannister     283
    % R, z7 D9 w4 ^. \* g3 Y4 Z7 oarya stark          265
    : S2 O" K8 q3 E1 I+ pcersei lannister    2468 U7 p1 {4 d& ~/ k$ R
    Name: len_questions, dtype: int643 v; j0 ?2 @$ v/ e1 y) H

    4 b1 I7 q4 m6 L# 参考答案
    ( `" S/ T" U2 }) g( Ts = pd.Series(df.Sentence.values, index=df.Name.shift(-1))7 h8 r- T% |2 R; ~7 u: j
    s.str.count('\?').groupby('Name').sum().sort_values(ascending=False).head()8 _! B8 Z: w& x  }0 g) K. d- Y3 W
    & A9 ^7 j5 ?# E2 K! K
    1
      W! k8 I( R4 x, }2
    ) p* B" V- `$ f* Z, J3
    6 m7 z) t3 u. U" _7 t46 d9 v+ X- k7 s: p1 i, @
    51 p, p( j4 \/ W& p
    6/ ?2 a: V2 b- _. u
    7. l- `6 @% j( h2 r$ P3 h( d, X
    8
    1 g1 Y* }& y9 n$ @9% P0 W" f8 H# ]
    10% b, S% ~: H9 Z3 z
    11
    / r, C& F+ M: g* n4 W12! N" j9 w" s* r6 f( P! y, }6 @0 H
    132 A/ a$ i- M9 x8 L5 S
    14
    2 l2 a* X) b/ d157 e8 g# @7 C/ B& g
    160 v6 ?% [( I  L, S+ ?4 q
    17
    ; w2 Z5 m' v8 [1 j第九章 分类数据
    2 V; ^& Q0 f$ b. s1 A2 vimport numpy as np
    + m2 m( _& }) j& m; ]import pandas as pd; ?2 I, M- R7 m- g" K! @5 c2 j+ E: T
    1
      ^+ a. o0 n$ x$ Z  V+ B! _" c; I2/ D; G% [7 f' H" K- z  R2 L+ G0 ~% a
    9.1 cat对象
    / y! O- y: P$ e6 o2 g7 V' _# l3 R9.1.1 cat对象的属性
    # N1 o% p# Q4 A# e0 v- |, s  在pandas中提供了category类型,使用户能够处理分类类型的变量,将一个普通序列转换成分类变量可以使用astype方法。
    4 ?- A% K& q$ M0 q7 }7 X# K/ p4 L( w5 S; L4 O* m, W
    df = pd.read_csv('data/learn_pandas.csv',$ Q9 P! X( i: ?- A4 ~' z7 }$ e
         usecols = ['Grade', 'Name', 'Gender', 'Height', 'Weight'])! {9 L5 c7 C  |
    s = df.Grade.astype('category')
    , D8 D0 |6 b" O/ i- v) D( C& A, {6 n
      ]. C2 W, S6 R, \' Q0 rs.head()/ g; u7 s# Y/ J& ^" m! X4 F
    Out[5]: ; G" m. ?$ C$ j  A6 t0 h7 s# Y+ j' U. I
    0     Freshman. L& N, i% X6 Q# o! O- W8 G
    1     Freshman
    9 W5 O* T" R1 h3 v/ U0 K2       Senior
    4 @) Z  i: X  _; ~$ ~7 K3    Sophomore! Q6 g) C5 L0 b/ I2 a5 o! }
    4    Sophomore
    . L! H  c7 {0 g6 [- f+ v$ FName: Grade, dtype: category
    6 m, m- M3 c/ c! {: y8 z* ]( ^Categories (4, object): ['Freshman', 'Junior', 'Senior', 'Sophomore']
      z. e' A& s2 I8 ]' _, P" O1
    5 f" g) f: e  t' k24 d4 W$ G3 e. Y2 l
    3
    , }4 G8 V4 l8 a9 {4% L2 I/ C7 m; q4 J2 d# f2 K+ b
    5' p* k+ t9 U" Y( V; g; h+ @
    6
    : ?# w% B# ]% r7
    - g+ C; Y) _1 h& H0 t" R: F* o8/ s& [! D# P( S# k2 e! `) f
    9; u2 y0 V8 \" N, u
    10
    ; V& D: Z3 s; u0 @! {9 ^11/ s4 V: H) n6 ]& {( q7 m
    12/ R, L! `& k3 L* L, r3 j: B
    130 I% ^% Q0 L( i2 A4 M
      在一个分类类型的Series中定义了cat对象,它和上一章中介绍的str对象类似,定义了一些属性和方法来进行分类类别的操作。1 O+ q$ J: E& r/ X

    9 i- K+ J  k& ]  fs.cat
    * u; s* M3 x% s( HOut[6]: <pandas.core.arrays.categorical.CategoricalAccessor object at 0x000002B7974C20A0>
    $ ~. j% F1 ]( R, y" k/ P* i1
    " o( F0 @+ k9 @! v2
    5 c0 N0 j. V0 m. t5 N# p3 e  ucat的属性:
    ( n' h3 J  [5 i/ V" d& M- ~" Q1 \) w  G
    cat.categories:查看类别的本身,它以Index类型存储
    5 Z- m" S1 Q: y, G% a. `9 W+ c6 zcat.ordered:类别是否有序* `1 O8 {  m$ t9 K- E& U( [' ]: ^
    cat.codes:访问类别编号。每一个序列的类别会被赋予唯一的整数编号,它们的编号取决于cat.categories中的顺序
    2 {& w3 C$ F, S$ G1 cs.cat.categories3 Q7 Q/ k: O! m' s& g9 g
    Out[7]: Index(['Freshman', 'Junior', 'Senior', 'Sophomore'], dtype='object')6 |4 W5 n; g" @# R& f: `4 R+ g

    . y6 t: g! P, R3 {s.cat.ordered
    + Q* C% B8 u4 C3 qOut[8]: False
      F! N# W3 I! W7 W4 A4 m; l: R5 K% \6 N
    s.cat.codes.head()/ v* |7 i4 E2 b* t  R) Q# z
    Out[9]:
      R; D" x" B2 |) L) S6 y( b" s0    0
    ' w" d6 M7 i1 E! G2 v1    0
    2 R! G/ f, n% L0 U1 l, R0 G- H2    2
    ' W5 y5 w) s$ w. k5 P3    3# c, c9 m, B- X
    4    31 A0 E+ g# K& [8 S
    dtype: int8
    5 d( n( K  i4 u6 }7 k: e1
    ( `# J" p, X4 ^. R6 k9 `2
    + a. @% m3 I0 V& y9 k' `39 J) f4 H3 [. w$ D$ r
    4: w! I; H4 D( m( N! I& E2 f* t
    59 b- Z/ D" j: _
    6! x$ l1 A8 T% p  ~# q; z) R
    7
    + C7 A' ^, K, R% }( a3 _1 Z2 C82 G* |8 ^' e, d. L
    9, B% {( r$ ~% z4 ?1 T0 w! B) I
    106 I  A& l9 F2 r
    11
    ! U( {$ Q* N4 o8 Y9 h12
    $ T: A  k+ V. o. K+ \13
    % ^  M0 L) \& @0 ?' r" f( M14* B9 P: Q5 i! ]
    9.1.2 类别的增加、删除和修改* t( D% t8 X! I/ J( u" U3 d9 t$ G
      通过cat对象的categories属性能够完成对类别的查询,那么应该如何进行“增改查删”的其他三个操作呢?
    6 o) d- J# d0 e5 q8 v# D1 Z
    ; z7 R$ l+ `5 }, u/ k% @( c' p【NOTE】类别不得直接修改
    2 V: f" B; n# \  P! J* c在第三章中曾提到,索引 Index 类型是无法用 index_obj[0] = item 来修改的,而 categories 被存储在 Index 中,因此 pandas 在 cat 属性上定义了若干方法来达到相同的目的。; ~: R, z2 J7 E

    " \. o: ~) u8 j1 _+ o! nadd_categories:增加类别
      n4 Y% m% |2 _& A1 q7 \: Zs = s.cat.add_categories('Graduate') # 增加一个毕业生类别
    / P  Z' }$ z5 v8 vs.cat.categories; ]# c. |1 r) I4 Y6 p

    ' M5 k2 y% G2 s6 k2 TIndex(['Freshman', 'Junior', 'Senior', 'Sophomore', 'Graduate'], dtype='object')8 P  Q# N/ v  X, K  Y' _+ V
    1: G7 x7 G- M' M0 a/ ]. \0 O
    2
    - P8 F8 h6 l9 V" y  s* S& o7 l3
    . U/ ~% X2 X- h7 @6 b2 c2 q$ R42 c) {2 n; y0 l) ]0 k( Z$ }7 C
    remove_categories:删除类别。同时所有原来序列中的该类会被设置为缺失。" _% Z: A, W" N# r1 f: c) P
    s = s.cat.remove_categories('Freshman')6 h& d+ }2 ?$ u$ X

    / V# s  K2 ~) W( l" I( V6 Ys.cat.categories
    ! v8 f  p, h7 b+ [3 ]+ s) eOut[13]: Index(['Junior', 'Senior', 'Sophomore', 'Graduate'], dtype='object')
    + q/ O$ g% m' A8 v& t# y9 E
    $ D$ P3 r7 I4 }0 S" U" _% t" q+ Gs.head()
    2 a* a% K' b7 C9 AOut[14]: 9 I2 g0 |9 K% N5 v0 f
    0          NaN7 g9 _7 A# [9 z0 l
    1          NaN
    8 T/ l* r  r! T' t3 t  p, H2       Senior
    8 J7 U7 ^+ f1 V+ L( M4 x! C  p2 {9 H3    Sophomore1 v: I* I, G# W$ k0 }7 b
    4    Sophomore% F3 g1 L, t" H6 T# C5 Z2 h8 ^
    Name: Grade, dtype: category  c/ P" ^+ x$ o- B
    Categories (4, object): ['Junior', 'Senior', 'Sophomore', 'Graduate']
    1 c! U4 a9 g# u  s* i1
    ( m% n. J* ~0 W) A2* ]& l# O6 f7 q# \! K5 K
    37 J* g, C* F  s1 u
    4
    / h% `# v9 S6 ~6 X' W% v6 a57 E. H5 B2 J* Q3 n0 L$ ~
    64 ?- _" ^% y0 y& t& G" p) ~
    7
    1 ]0 k9 G0 w" H' [3 x9 ^9 Y1 ~8
    / L0 Y; W% y) t5 |) G9
      |% @1 ~" m& F: e: {; u( `107 z, v# _0 x5 v* I+ p. Y! c+ T
    11
    # y4 F3 n- d3 P; Z( Y12
    ' d. x5 W  s/ [9 b. y, b13, E  ~3 ]! a& Q/ s0 Q
    14' Z, Y4 W% W7 t) t: |- m( x' E
    set_categories:直接设置序列的新类别,原来的类别中如果存在元素不属于新类别,那么会被设置为缺失。相当于索引重设。
    * I9 w2 i: g. w5 |. h8 cs = s.cat.set_categories(['Sophomore','PhD']) # 新类别为大二学生和博士" r3 z: N* |$ l& y
    s.cat.categories: n- ], i9 u0 B2 f
    Out[16]: Index(['Sophomore', 'PhD'], dtype='object')1 n2 ^# }6 z' |6 }" I4 h* Q3 Q
    % `) }8 Z& q) _0 ^# s; B5 U2 I
    s.head()
    9 ~. @+ H  f0 M9 L1 g: IOut[17]: 8 B8 o$ S' H# {, d
    0          NaN6 p' A- j' K8 N7 [0 z- F3 t
    1          NaN
    , F- b7 Z9 \! {2          NaN, m( i. B  l. I. f
    3    Sophomore8 u0 l# ?4 h1 \* y$ @% |' I1 M
    4    Sophomore
    # P% e3 o2 n, V4 X' d2 w( @- T9 VName: Grade, dtype: category
    3 Q# N# a/ ?- d. l0 eCategories (2, object): ['Sophomore', 'PhD']) E- u, O% ^5 }- |3 v% ?) F
    1+ K- d" \! ?4 t0 v3 C% B4 l
    20 L0 f- H( W. o  z
    3
    ! X; _  g1 `' Q, q, R40 D" c1 E1 z/ v: Q1 ]
    50 ?8 X1 z7 q* M6 `
    6
    5 H- Z+ @( ?/ s4 G% B75 E& a! V7 b- y4 Q
    8
    % o9 `+ c* Q: c1 O$ w9 V9
    8 p1 h* {2 e5 L% p) R6 p! u10
    7 k8 _0 x: u/ k+ ^" H11
      l. T, T( g" r% W1 `! u12
    . |8 u: H2 g. E$ i7 Z9 E  k137 d+ n0 f/ o  W, B
    remove_unused_categories:删除未出现在序列中的类别1 P/ E8 c& {' W* v7 o" _: M
    s = s.cat.remove_unused_categories() # 移除了未出现的博士生类别) m; Q' V* T" R" K5 ?% ^
    s.cat.categories
    7 n$ z& |7 I8 ?2 Y
    5 B- B4 Q) L* r  V; c5 M3 ~Index(['Sophomore'], dtype='object')5 r* ]3 Q0 b' w8 x- T. h% K# a
    1
    , O+ ^5 j. m/ V( Y% V- L. i$ v2, L) n3 t( s) u2 @' ^* R
    3( M* q) n0 u' ~: D
    4
    8 h( a% Q$ b& r- M5 e' [; P& W$ Grename_categories:修改序列的类别。注意,这个方法会对原序列的对应值也进行相应修改。例如,现在把Sophomore改成中文的本科二年级学生:
    , b2 d, n) g0 }0 S) M# e5 Hs = s.cat.rename_categories({'Sophomore':'本科二年级学生'})5 \2 C$ ~: {0 _0 c8 m% n
    s.head()" O$ D& D2 |7 o. ]* u4 K' w
    ( P9 n& [8 S' \, I
    0        NaN! K( Z- ~! q/ _4 H5 Z
    1        NaN- _8 j) U2 ?2 g/ a
    2        NaN1 L% Q* [4 N- h+ F1 g- r, k/ E
    3    本科二年级学生
    2 O' C, l3 b- v/ ]4 f% S4    本科二年级学生; {- a$ a0 @8 `' m& U6 f1 q  K. f
    Name: Grade, dtype: category) ]7 h- f( K. }1 w
    Categories (1, object): ['本科二年级学生']
    ) _! B# r% A: G! Q/ J" N1
    " X/ w' V  f) A# Z( V" J* b  W2
    2 p: E1 N! w8 o* W+ _& e! H# F3+ Q- R0 |  `; e# B7 \4 i
    4% U! T6 [) w9 {' H! d& S# Z9 k0 Z. L
    5
    * T* G2 J+ L' }+ E* ?8 x: H) q6
    0 C8 i3 e+ X2 V, Z) n, z7
    " X5 Y7 Z7 @, f  S3 q, U8
    & F# y, I/ z* v/ j+ R96 z+ ]0 U& `. I! Q: h
    10
    # _8 ~, Y7 W! f; Q9.2 有序分类
    1 U% Y- O' d& E  G: f! U9.2.1 序的建立4 d, J& Y9 `7 B( A4 `
      有序类别和无序类别可以通过as_unordered和reorder_categories互相转化。reorder_categories传入的参数必须是由当前序列的无序类别构成的列表,不能够新增或减少原先的类别,且必须指定参数ordered=True,否则方法无效。例如,对年级高低进行相对大小的类别划分,然后再恢复无序状态:
    ; B* G) y1 J! z# U- q- e) j+ `: p' Y& ?1 C7 ?8 F2 j
    s = df.Grade.astype('category')
    3 r4 s6 B% w, i- ^5 q! y; u$ ws = s.cat.reorder_categories(['Freshman', 'Sophomore',; q2 J/ I  n. U5 }! t8 o
                                  'Junior', 'Senior'],ordered=True); j2 e4 a6 ]3 Z
    s.head()7 T. G! o( a0 j& ~$ a
    Out[24]:
    / Y$ ?: A4 ]- l0     Freshman
    0 @) ]1 O+ I2 j: u) J* H8 f1     Freshman
    , u/ K6 R' n  ], {: a4 b( C2       Senior
    + e6 [' s6 O3 E, @3    Sophomore) n. S- n* Y" N: g, F
    4    Sophomore
    ' R5 ^8 z' j1 B3 n7 Z" ~Name: Grade, dtype: category7 [! R# b; F" c) f" n# m! C
    Categories (4, object): ['Freshman' < 'Sophomore' < 'Junior' < 'Senior']
    - Q- y( N+ s7 N2 b( x8 f/ j6 r  g# e# K) P! ]; Q; p, L
    s.cat.as_unordered().head()
    / N- V: l1 z/ h- A# J) MOut[25]:
    ! z7 R. T8 F. [' J3 F* y  `7 Q0     Freshman
    0 D! R$ S0 _$ b5 C5 r1     Freshman1 I6 T% r! D7 w0 y( j% d( T  {8 p7 }
    2       Senior
      ~; F+ p$ L/ H  e- |8 W3    Sophomore
    : {  i* t. |% \0 O, d3 k% N4    Sophomore8 s5 p, E5 e3 }% y5 ?! V1 p* T
    Name: Grade, dtype: category
    6 Q5 u# @9 H3 e2 ?Categories (4, object): ['Freshman', 'Sophomore', 'Junior', 'Senior']
    " C( E  s# Q2 @# t$ B3 T* ~  U2 X' z. {' k/ Q7 M  t( z
    1
    8 Y  S3 P8 n  K8 h! i2
    " m; ^* B2 D. l% e4 I32 w9 D; W8 z8 T: k2 Q+ J$ M/ Y4 ^9 f
    4. J( h- d( g/ s4 C
    5
    % I3 H$ [# q  F% {- K2 ]60 l$ e5 C2 A$ G! d7 k3 x
    7
    : k4 d! C9 j. M3 O% o8. `( ]/ j' J% c% Y4 S, G  b
    9  r- p! F" `3 r" K: U
    10. [8 l. `6 i* e+ a
    11
    ( X6 z* n9 e  [6 k12
    : f# S! x5 m; u2 y+ i* Q13
    * I: ?+ d' I2 q- v* @/ y! I14* v" t5 g! r; j" v, X/ \& l& a( ~
    15
    7 \" \6 ^. o/ D, L16
    / e7 W/ e. h  D& a6 i& V. L17! z) P9 c* g# s* ]1 k6 i
    18
    & T( a7 s) z0 y* S19# m) s# h% B# H
    20
    0 D. }; v& o/ d. Z  c' N' m5 p21
    + O' F' g9 q4 a' Q7 m$ U22! ?' Q1 X' a& m' {, t1 l
      如果不想指定ordered=True参数,那么可以先用s.cat.as_ordered()转化为有序类别,再利用reorder_categories进行具体的相对大小调整。
      A  h; l1 h1 \+ c/ \! s8 R4 w- I' o- e! m) S$ p: q
    9.2.2 排序和比较
    5 n" C* `& W/ x在第二章中,曾提到了字符串和数值类型序列的排序。前者按照字母顺序排序,后者按照数值大小排序。6 Q! M+ R) [# s5 u9 }, l8 ?

    " }* [7 m3 c; J+ W  分类变量排序,只需把列的类型修改为category后,再赋予相应的大小关系,就能正常地使用sort_index和sort_values。例如,对年级进行排序:
    ' ~( G3 e0 i3 z! ^9 e' f& o; T% N0 z# O
    df.Grade = df.Grade.astype('category')$ i; i5 @. ~2 }8 _$ _' ^( Q0 I' v
    df.Grade = df.Grade.cat.reorder_categories(['Freshman', 'Sophomore', 'Junior', 'Senior'],ordered=True)
    : d8 E( ^, e( @8 C5 ~df.sort_values('Grade').head() # 值排序
    ' A5 I3 p5 B# DOut[28]:
    . ]5 L- m2 y2 _; e4 e' K3 n        Grade           Name  Gender  Height  Weight2 H3 }7 S3 F% \- {$ P3 N- \4 `
    0    Freshman   Gaopeng Yang  Female   158.9    46.0+ K3 E6 U* ^& A8 ^
    105  Freshman      Qiang Shi  Female   164.5    52.0
    8 V2 a/ m* C( Q+ e$ w2 q96   Freshman  Changmei Feng  Female   163.8    56.09 `' w5 a3 A, I5 w
    88   Freshman   Xiaopeng Han  Female   164.1    53.09 |0 x7 h2 }! V7 q) i) [. v
    81   Freshman    Yanli Zhang  Female   165.1    52.0
    - n" V, i: R; d1 l2 j$ [% J$ C* m' B. h6 \- ]+ A
    df.set_index('Grade').sort_index().head() # 索引排序7 R, B# M/ D# v  H! L$ e
    Out[29]: $ n( X- A* t* ~; L
                       Name  Gender  Height  Weight
    $ t4 S8 u! t$ tGrade                                          1 O5 M- k& r4 {4 F2 Z
    Freshman   Gaopeng Yang  Female   158.9    46.0
    ( o. S- g) W: d& vFreshman      Qiang Shi  Female   164.5    52.0% P( |7 _# G) Q/ ^9 O. J  S
    Freshman  Changmei Feng  Female   163.8    56.0
    4 o* K6 Y4 h. w# }1 }' FFreshman   Xiaopeng Han  Female   164.1    53.0; P' K: Y4 z2 b
    Freshman    Yanli Zhang  Female   165.1    52.0  a0 h  y& |1 p# Y' D5 y5 f

    % g, ], u: h7 b/ Y+ R2 ^; B3 }) c1
    ! }' R7 H# y* C* x7 i" _2+ y  ]1 b7 s/ k, F5 f
    3$ \' ?$ R  I6 }) ^) b- {4 [2 ^
    42 T# s$ _8 ]. t: H3 B
    58 I) ~, x( w8 `5 ]7 t' _
    6
    ) _6 G4 I0 B( H8 {& M1 [2 x4 k+ `7) x! _$ z* o" D6 s4 T4 P& H4 u! L& b; A
    8
    # T( B- B9 j+ l9 f, _$ M98 s& r8 w: e0 Y
    10
    , t4 ^' |( e& l6 e6 A3 e112 f) G5 q' r: ?/ d! M- R& g. k9 l  _
    12
    " n- g3 e0 ]6 |13
    6 E9 j- K* L2 b- |8 F3 X6 O14" m$ o. j1 t1 f3 ?
    15
    - @0 d; Q8 S7 a: H6 Q16
    0 Y- H: ~" @6 l7 e* v; w171 E* H: m' o. F
    18
    . f9 V" u+ [7 E8 `9 ^8 a19& W# j) \! t1 t; h. z3 Z% y
    20
    ( ?% R: E% J5 D. J( |" }' z9 D  由于序的建立,因此就可以进行比较操作,方便后续索引操作。分类变量的比较操作分为两类:4 C  g9 r9 }3 |7 y1 d3 U' ^" J# H3 X
    9 x2 X$ t' k! W9 K( w, ~. I9 l
    ==或!=关系的比较,比较的对象可以是标量或者同长度的Series(或list)。(无序时也可以比较)5 v% T0 V! h- G- _& x( g1 G6 I
    >,>=,<,<=四类大小关系的比较,比较的对象和第一种类似,但是所有参与比较的元素必须属于原序列的categories,同时要和原序列具有相同的索引。
    8 S: N% Q8 k$ ~) t3 ?0 cres1 = df.Grade == 'Sophomore'
    9 [  p0 l1 `) _
    6 `: ]. U/ F' |& Dres1.head(); i! v8 O( L# T2 E4 `' |
    Out[31]:
    7 G  s7 U5 N! C8 x  ^0    False
    2 p  }3 {5 X1 t1    False; Y( u9 S- m" R9 Y$ t2 [) j  V2 _
    2    False8 D4 X; u8 \0 v
    3     True
    " \: F# S. A9 a- c" }4     True
    ; t6 H* m2 a3 Z0 Y* v  ]% rName: Grade, dtype: bool4 }: j* E( |3 |- e

    ; I: O9 o" Y, z# R! _% j+ sres2 = df.Grade == ['PhD']*df.shape[0]) t5 W( n+ q: k6 F7 s4 I1 e

    ' o; A. `! J( p( Z0 c& C& |$ Ires2.head()
    $ v; a) a) z/ z: }Out[33]: + s  S. m5 ^( n3 L: I
    0    False
    0 B& z" E8 _" h8 t/ z6 k/ A' j1    False  K- P6 Y; s: V7 u9 G* H
    2    False
    3 X* \0 z* S; Z% ~6 }3    False) h; @1 u1 K. L/ x+ u8 u( l0 T
    4    False5 B  n. W) H6 G7 @% I& {
    Name: Grade, dtype: bool6 A) O  S$ l& d3 K& f9 u
    3 L$ s  `* H/ v, w
    res3 = df.Grade <= 'Sophomore'
    $ s, \3 m9 p: C, u
    4 E# c# p8 |2 K, e% k) }' C& v- |$ Fres3.head()5 j, h+ s, Q' k2 \; q- p* M" _7 `
    Out[35]: " o* v5 r0 e' `/ k* v: z
    0     True% Z  G; }; b( \1 _  ~) a" r5 y
    1     True
    5 k. P" o: B- Z: P. G7 }* r$ X% s2    False
    * O! ~1 V2 z, D4 J& X$ ]1 r* p9 b3     True
    3 h( k, H" H% }+ }8 j+ G4     True* N* T5 x0 Z! o' S& r- u
    Name: Grade, dtype: bool
    1 L) ?. ]" S/ V4 ]9 r
      q  [; P. O) U  A/ p" m% v# sample(frac=1)表示将序列随机打乱。打乱之后索引也是乱序的,直接比较会出错,必须重置索引。* P9 Y' q! \2 Z" I
    res4 = df.Grade <= df.Grade.sample(frac=1).reset_index(drop=True) 5 ^# k% }% O1 m/ K0 m

    . G- P6 @- m1 b$ _6 K1 L5 Xres4.head()8 D  z4 q2 t$ W6 ?4 q( v$ T$ E1 P( W
    Out[37]: 1 m% J6 q* w6 h! I* k
    0     True
    2 B( l1 Y$ p* e" c. J. k: F8 }1 z1     True
    0 u4 m# n/ C7 g5 a: |3 F% }2    False6 _* J9 M/ O# X! H! `2 Y
    3     True- P) L1 e$ u. Q
    4     True6 z/ {& P, E% s$ h0 X* U6 E, K
    Name: Grade, dtype: bool
    6 w, V5 ]: P  [" g7 O* Z0 a' @: i4 e" h+ H
    1& W  C( s5 l/ O, l' S; y
    2
    & r6 O+ T/ M8 {% l- j/ \3* e. y- e7 D3 c. x; j  t" K
    43 Y; V+ ?  f: X) ~9 Z
    5
    3 L* @7 c2 N- C2 y, W8 ~4 k66 y# v+ X9 j, }/ D8 @5 Y0 y+ x+ [6 k5 o
    7# j$ P; f  Y) S4 Q& x
    8
    : P4 V8 ?; e; }6 m' E9; F! ~" R, i/ P4 f- v1 g
    104 L& d7 l( H0 B* W- q
    11
      G* J+ t. J8 O  A: Y* ~+ [12
    & s# R1 _5 y: B, Q+ [; c2 s7 \3 Z13
    / o- D& P) e5 _+ \# M14
    1 W& V% _. ], N, N# L, W. j6 x152 C- S* \1 t- z# _' h$ |
    16
    & A' G& y' z! |) F* ^# A17
    ; E/ `7 _& c* r3 F18. H! T+ Z. P8 Z  \" W' t( R
    19
    3 B4 C2 n( g. X. [$ H209 A, w/ Z4 x  L1 A2 U0 C, g
    21
    * K$ H  i2 o7 |# F) Q) @" c% x22
    / M0 K  d; \  h7 ?. ~23$ q- X  B0 y' {+ C, E
    244 U  P( u$ j0 }4 U$ L9 U8 ~1 w2 A# j
    25
    & l$ e$ L7 A' n1 h26  J& S& k' s! k7 q
    27
      o+ l" Z7 P* K, L1 N: B' e28
    0 q$ f2 l5 x+ p29
      w* s& v# }  t: W1 D  F0 K30
    / z/ M5 f9 W' }. v. E. L1 V+ g31
    1 V# k8 }8 T* c: x" F32
    ( w$ n9 Z+ P8 Q! [* ~331 R; n, \/ P, T5 v- u
    344 [+ c! O* f3 S$ M
    35" {: e# G4 k* n6 v
    36
    2 [% O2 a' U4 ?& o" |  p8 |37" T0 E: i+ ~$ c7 i! N) x
    38  a% \; m2 B1 o
    39
    5 @& O/ o# d) ^6 n40
    4 X7 C* [" t9 [  |  L  H41
    . _. I  @+ T. n$ q, u9 W3 M, r9 Z* x42- D1 z1 D" f# c* k
    439 o( r5 B, U& P2 N( W9 K% ~
    44
    1 I8 W- I; h) j! W  D9.3 区间类别* _; t3 h( c% F
    9.3.1 利用cut和qcut进行区间构造
    ! J3 F8 G3 Q" d/ i  {6 y# [& `1 I  区间是一种特殊的类别,在实际数据分析中,区间序列往往是通过cut和qcut方法进行构造的,这两个函数能够把原序列的数值特征进行装箱,即用区间位置来代替原来的具体数值。. z" j/ u. Z* n  q, B  D& D

      x- R8 k/ H; ]cut函数常用参数有:
    4 D9 d. T! r1 X; K1 Z7 tbins:最重要的参数。8 q# t( [' t: l2 D: a3 P1 c) M4 I
    如果传入整数n,则表示把整个传入数组按照最大和最小值等间距地分为n段。默认right=True,即区间是左开右闭,需要在调整时把最小值包含进去。(在pandas中的解决方案是在值最小的区间左端点再减去0.001*(max-min)。)8 m: k8 P9 @$ H0 z8 P6 E
    也可以传入列表,表示按指定区间分割点分割。7 g: n/ F/ _$ c4 K5 E. _
      如果对序列[1,2]划分为2个箱子时,第一个箱子的范围(0.999,1.5],第二个箱子的范围是(1.5,2]。: `2 K5 e* P' e6 o' ?7 ~
      如果需要指定区间为左闭右开,需要把right参数设置为False,相应的区间调整方法是在值最大的区间右端点再加上0.001*(max-min)。. f- w9 u* `2 X! S; ~  @
    8 ]4 f) k5 ]; V/ L: ^  r" [0 y$ S9 G# C
    s = pd.Series([1,2])( Y! f# C. S3 L2 S& e0 {! {
    # bin传入整数
    4 s; h0 B: [# }/ Y" d
    & j( X6 p' t: C" ~, v( Gpd.cut(s, bins=2)4 w7 c# e6 `6 b; m, K# |# u
    Out[39]: ) @3 Z3 i! _9 ^: @4 O2 K' s& ?, _
    0    (0.999, 1.5]
    4 x( F+ s2 u6 T  V1      (1.5, 2.0]
    8 e+ @9 b7 x1 E: O  x) [! H) Mdtype: category
    0 L- e1 a% n5 R( i' ECategories (2, interval[float64]): [(0.999, 1.5] < (1.5, 2.0]]
    ) s* b, s; f' l; J* s; O
    4 a4 w, E+ t- I. W$ [* k( P% Spd.cut(s, bins=2, right=False)& \2 q: E+ ?( R% \1 X
    Out[40]: 7 s: t7 q0 g9 K( L% {
    0      [1.0, 1.5)3 J0 U5 I, U& W3 O  [& ?
    1    [1.5, 2.001)
    $ p8 J# y: B$ y/ K4 S' A0 M( Pdtype: category
    3 c' \" ~& }6 j9 xCategories (2, interval[float64]): [[1.0, 1.5) < [1.5, 2.001)]
    " O# U" l  ?; R2 ~8 r
    ; p  o/ M7 F& t/ Q" f* R: @& y/ L( L1 D8 ]; j* T
    # bin传入分割点列表(使用`np.infty`可以表示无穷大):: G+ {3 p. V# {% O! B# u3 @$ e2 l
    pd.cut(s, bins=[-np.infty, 1.2, 1.8, 2.2, np.infty])
    3 n% W. }6 @1 Z: a. \9 O% w: HOut[41]: ' d) R5 Z3 d5 R1 {0 P1 U$ ]( O
    0    (-inf, 1.2]- k1 {( ^8 b: O: G- a; L; }
    1     (1.8, 2.2]. ]$ h" z# d6 B
    dtype: category. O. ^5 W! m& w$ S4 F* L
    Categories (4, interval[float64]): [(-inf, 1.2] < (1.2, 1.8] < (1.8, 2.2] < (2.2, inf]]- Y$ X7 D8 x! E# d0 h0 {3 ^; G
    4 d% Z1 `* k: o8 @( g# B
    1
    ' D  d$ }9 {9 K1 C& x2' I& _" B& a, z; h1 v: J; b
    3
    ( i1 `( Y  @' A6 c& W# u4
    : l, Y6 e0 f5 p# v56 H" ?9 h3 o+ t# Y* a5 J: e) Z- M. A/ }
    6" K6 u% W1 f+ V7 U
    72 r! q( d/ x5 N+ f3 K( {
    8
    : i2 T% |6 x/ A: t5 E+ X) B4 `* X9/ s0 q! Y7 m% B( D. G
    10
    ; n& q/ t! w: H: X/ |  r11
    4 U* j- m8 |% T, Z124 s4 }/ q5 w$ ]* |& z3 i, m, ^& F
    13
    8 X6 v" b! r! U) ]% P146 Q( n7 I; A/ m. Z' ]3 @  g0 v# f* F
    15' n" S" [- l1 t
    16  T9 I( Q3 [, Y% E
    17' e+ `$ l: l# @! f9 z- d
    18
    2 z* w/ S4 g0 V  n7 u19' ]. F! {9 V; {
    20
    7 F+ R9 V# O" R' z* J$ A21* N, H" {4 u0 T3 G1 ^9 I, i
    22' N$ ]: \+ I0 Y- ^* l
    23
    . ?$ z8 o3 c. @24
    9 T4 o% _+ e) H. e8 x, Z# m' V2 z4 u257 _6 Q0 u3 s  m( Y) F/ ^
    labels:区间的名字; j$ e/ w$ Z+ D  |) r% F
    retbins:是否返回分割点(默认不返回)2 _& A% w! ?4 U; ^- T& k& V
    默认retbins=Flase时,返回每个元素所属区间的列表( \/ N" N$ E  C3 L: C- W
    retbins=True时,返回的是元组,两个元素分别是元素所属区间和分割点。所属区间可再次用索引取值
    $ y: k, B: Y' }3 {9 ]7 X! u  O
      k9 P6 v# b" Z2 Y3 I% c; }- Ns = df.Weight& u* \# d7 {5 y8 @5 _1 Z: i+ h: z" r
    res = pd.cut(s, bins=3, labels=['small', 'mid','big'],retbins=True)! D  Y% W- k5 x  c& Y) j5 D
    res[0][:2]- P7 }/ [$ ^& t

    + P  G+ D* h4 w/ ]- [' j4 O0 pOut[44]: 1 W) w# R. {# j% u* l) f1 D2 I9 K
    0    small9 f+ N, {9 x7 |7 \* @
    1      big
    0 n, z* q: m1 F0 K+ Hdtype: category3 E7 H6 A/ [) J, o: q' V
    Categories (2, object): ['small' < 'big']
    , D/ s; N. a' g. e& }' o& Y% P- u( u7 [6 v' k$ T: o
    res[1] # 该元素为返回的分割点' ], O8 e$ A: m  N- J4 O) v
    Out[45]: array([0.999, 1.5  , 2.   ])
    4 O4 V& `. z3 H( G4 ^* E1) Q; d$ }7 A5 c! U6 _* v
    2
    9 F, e; |* I6 ?) M3) b! B0 Q) m7 J
    4
    1 D! b6 D1 C; s* o/ U5
    , E1 E" L- p/ g, h& R' P6
    ! {: B2 s- e2 {+ U/ k! @7
    , ?, O8 o1 n' Q; i% t' y8
    : A4 ^; x8 d0 R9
      R1 H, I' G* p; I104 e9 ~( n3 u6 U6 P$ w: v2 M  H3 G
    11
    4 `, `( z' N" v12
    " j& l) ]6 h3 |' [; xqcut函数。其用法cut几乎没有差别,只是把bins参数变成q参数(quantile)。  p0 d# q8 }# C+ y
    q为整数n时,指按照n等分位数把数据分箱; Y! C$ G/ g. K" ^% M0 W0 y  q9 J
    q为浮点列表时,表示相应的分位数分割点。
    9 u. y( Q; j& D0 Z* d6 _" os = df.Weight7 Z/ }* x% B) o, D" I* {

      p! r  B7 G4 {% C1 apd.qcut(s, q=3).head()7 L/ |7 H8 J! f3 R& M+ Y) h
    Out[47]: + }2 u0 O  g& X& s- s% N' n* |# U
    0    (33.999, 48.0]/ D$ `! q# n/ A. l# [
    1      (55.0, 89.0], ?7 m3 Y& D- z4 [; U6 F0 Z3 y
    2      (55.0, 89.0]
    5 n- V, H) }) ^8 I- O3    (33.999, 48.0]3 N, t4 }3 ?  n. ~
    4      (55.0, 89.0]+ D$ O( y  V5 N* E4 F) x* c
    Name: Weight, dtype: category# r5 P" z6 h1 W5 {4 D  \, `, d
    Categories (3, interval[float64]): [(33.999, 48.0] < (48.0, 55.0] < (55.0, 89.0]]2 D1 @2 ]/ j* l

    - _$ M) P3 D4 n* _4 @1 {( o% i9 npd.qcut(s, q=[0,0.2,0.8,1]).head()1 a- {7 |) J5 Y; b0 q3 g
    Out[48]:
    $ h2 n( z. B& _6 d1 p0      (44.0, 69.4]
    ( b; T3 }  _$ u$ F8 P: c1      (69.4, 89.0]4 f6 Q' V5 M/ M) L
    2      (69.4, 89.0]: n. v  e5 Y" t/ L3 Y$ G  s* C
    3    (33.999, 44.0]
      w5 T, h7 p- [* n4      (69.4, 89.0]* L3 h7 i( O" p. O3 B; M
    Name: Weight, dtype: category% ^; p* }4 e6 B6 c+ j2 H" n
    Categories (3, interval[float64]): [(33.999, 44.0] < (44.0, 69.4] < (69.4, 89.0]]( t) @/ a8 i, o3 b

    1 h/ Y, q: B+ U* n1 S1
    - ?% I' N* D5 c5 B; B7 ~2
    0 c* A+ K7 ~# w, i3
    8 M' h# P/ H2 H! I* s4
    . j# }% Q6 a, e- k4 `: M9 v4 e3 ~5
    ( R4 M  s( f1 `" g67 _) X0 J5 Z  i9 b0 b
    7
    $ n9 [* Z) b% m8' o, I# ~# ~/ V
    91 X& ]  Y- o" {* R
    10
    ( C8 t9 w  }' O; F) h2 |11- x9 J# X) A4 c6 p" a: o2 Y" p2 u4 O4 G
    12
    ! W, y, r5 K6 m; S13
    # q+ |7 \% ]; d! K14$ O3 T, V" K7 O8 y. I6 [, u
    15
    - {2 g8 Q  ~, [# y16
    + q4 K: |4 e2 [! R+ E' Q17
    2 w+ X7 m" G' w# T7 y0 y18% z  b* u# g# \2 W9 k$ @! `( Q( l
    19
    . Y% ]/ }4 @& q* N20
    $ \) @+ O- }. _  \( a21
    : U( A6 z6 n$ u3 `' z9.3.2 一般区间的构造
    + e7 m7 N3 O9 ~: }) u0 V4 A0 g  pandas的单个区间用Interval表示,对于某一个具体的区间而言,其具备三个要素,即左端点、右端点和端点的开闭状态。
    - ~( C  M  F9 M* Z! M* N; u+ @* D( p* [9 J( ]0 f7 C0 K; T' Q
    开闭状态:包含四种,即right(左开右闭), left(左闭右开), both(两边都闭), neither(两边都开)。" `1 f0 ~3 c) {
    my_interval = pd.Interval(0, 1, 'right')
    % b+ ?, d7 ?7 l7 |! D0 x/ G- d  \3 W/ L/ D1 ?) c
    my_interval. m/ ~3 c4 l5 m' U' {+ q% l
    Out[50]: Interval(0, 1, closed='right')+ r( L% R7 u* v: p, e
    1
    ' v3 b' [2 N: h4 G5 H- I0 E2# Q! Z1 P3 S7 H, h7 u- d- \! M" z
    3& E9 Y0 B0 \% q" p" z
    4) u! e6 I- I, }
    区间属性:包含left,mid,right,length,closed,,分别表示左中右端点、长度和开闭状态。- C: v3 }, M; Z! o) @
    使用in可以判断元素是否属于区间( f! l$ ^! k: [- _  c( G0 e
    用overlaps可以判断两个区间是否有交集:5 e6 K; ~: m* ^) W
    0.5 in my_interval) P% |, J/ ?% r  ^
    : ~1 |. c7 j/ ~5 S
    True/ s. j- ]! S2 f: Z7 Q
    1
    , O' o: L2 t3 l( i3 V* H8 H2
    2 V* L) v0 P' O3 f4 c36 |3 r/ y$ f  ]5 n" R
    my_interval_2 = pd.Interval(0.5, 1.5, 'left')
    / o; R& w2 f* i- F- Lmy_interval.overlaps(my_interval_2)$ R; k, M& u1 `% P/ ~4 ]* o+ g+ n
    * \+ Q4 v& Y* V% d; w4 a
    True
    8 k+ p* l+ U! Z# A! k5 y1  ]6 T2 g9 _' f
    2
    ( @$ Y/ N# o; q3
    5 ]9 @  h* [0 Z1 M% j$ _4& G: P& B4 ]3 z7 f
      pd.IntervalIndex对象有四类方法生成,分别是from_breaks, from_arrays, from_tuples, interval_range,它们分别应用于不同的情况:# O: e8 s% Q* }- `! t( z- W9 ?6 F- S
      A( F, y$ @) `3 x  j' S  `* r" k  _% i) m
    from_breaks:类似于cut或qcut函数,只不过后两个是通过计算得到的分割点,而前者是直接传入自定义的分割点:
    4 H0 V" z5 u+ Q! G# mpd.IntervalIndex.from_breaks([1,3,6,10], closed='both')6 L# t3 e. O& o: c0 g) _

    * {7 p5 P7 r7 aIntervalIndex([[1, 3], [3, 6], [6, 10]],' e5 {+ r3 R( V1 X/ T  U
                   closed='both',# B0 F1 u# S& q! ?1 Y3 p
                   dtype='interval[int64]')# o' Z5 b( K' ?) e- o
    1. N3 p6 F% Z# z0 D& o; ~/ m
    2
    9 \1 L$ Z1 q3 b, Y5 p# E' t3" P' O( d$ ^% V( o
    4
    * w. M$ _3 R* g% g- \7 I' ]5
    5 g: @6 L( M& v* B6 o9 Q9 dfrom_arrays:分别传入左端点和右端点的列表,适用于有交集并且知道起点和终点的情况:
    ( C( d3 h1 q# G$ dpd.IntervalIndex.from_arrays(left = [1,3,6,10], right = [5,4,9,11], closed = 'neither')
    ) ~) Q) s  B8 B5 I' i
    / R1 _0 {# U- Y# |IntervalIndex([(1, 5), (3, 4), (6, 9), (10, 11)],
    4 X1 l3 t/ n: q0 s% E& c1 ~2 j                  closed='neither',
    8 K: }6 j' \. X                  dtype='interval[int64]')% X/ q$ g5 z- O' i  Q* d9 l
    1
    6 V! F% u% G( c- D. F) Z7 K7 e2
    ! _7 X  _' u& ?) [# P. J- N; \, ~$ W3
    ) B! z& r" Y8 a) ]: h4
    / I! i% q# Y( \9 n  a. A5/ ]2 m0 X6 _3 l! j$ Y& P  K0 F
    from_tuples:传入起点和终点元组构成的列表:6 h3 j1 d: d( n. L) i5 i
    pd.IntervalIndex.from_tuples([(1,5),(3,4),(6,9),(10,11)], closed='neither')
    5 e5 v0 Q' H* C& P/ r) h! A( _. c+ W6 o/ r
    IntervalIndex([(1, 5), (3, 4), (6, 9), (10, 11)],' k4 ~, I8 v* h' Y1 Z# x8 M
                  closed='neither',
    * R3 }% |3 z) l, M3 T; _              dtype='interval[int64]')
    7 C7 {4 J* h9 R; o: `; V1
      i2 n) q( ^' x2 Q( ]% f" c6 I/ p2
    . _5 e' {1 `2 Q8 d: O: \/ o7 \3: c3 o4 d- g/ v# e3 ?# ?% P
    4
    3 E) k' J" \1 \# i$ R9 R9 e5
    / X$ N+ u4 M" n& b' q8 h- i4 F$ p8 Finterval_range:生成等差区间。其参数有四个:start, end, periods, freq。分别表示等差区间的起点、终点、区间个数和区间长度。其中三个量确定的情况下,剩下一个量就确定了,从而就能构造出相应的区间:8 n- U! T' a7 b4 F8 I$ f6 }
    pd.interval_range(start=1,end=5,periods=8) # 启起点终点和区间个数
    1 v3 H' i' |; D" E/ N1 Z* H- ]. |Out[57]:
    ) k. A) D* G( M0 jIntervalIndex([(1.0, 1.5], (1.5, 2.0], (2.0, 2.5], (2.5, 3.0], (3.0, 3.5], (3.5, 4.0], (4.0, 4.5], (4.5, 5.0]],9 ^- }3 a6 b) h
                  closed='right',3 n  f0 K) }% n, x6 }) d
                  dtype='interval[float64]'): l( J: R8 j- u- Y+ p
    4 K/ e5 l& x" V+ }; a7 ]$ V  s
    pd.interval_range(end=5,periods=8,freq=0.5) # 启起点终点和区间长度
    ; b7 _2 L$ Q  H* |Out[58]: 5 P$ f. A3 U/ M) ]/ [* J
    IntervalIndex([(1.0, 1.5], (1.5, 2.0], (2.0, 2.5], (2.5, 3.0], (3.0, 3.5], (3.5, 4.0], (4.0, 4.5], (4.5, 5.0]],
    ) d; c% S8 Y0 ?! }7 h/ q( e: X              closed='right',/ U( J$ C; f# T- [
                  dtype='interval[float64]')
    $ d& w9 P# g% N4 C6 ]1
    1 `' V$ X% g' a: Z* l2; _( v- o( x2 X2 g& J9 C4 g8 J& L
    34 m$ V' D& y& [. l( s) V
    4, S0 F3 d' a1 h2 e- f
    5- R" I# _8 `1 a2 U
    68 l/ I; c1 y9 x1 G6 U4 T, H
    7* V1 D! ~$ l6 w4 [4 s
    8
    - L5 g9 C/ g" Q, U9
    : G% G7 ~0 H* r, U5 v% U$ _' _10! A* y9 M1 A. `/ ^( v# }7 }4 R: D
    11
      \6 d" g! r2 \$ N. n# y  w: A9 v【练一练】  a2 K: X3 l! I6 r& O
      无论是interval_range还是下一章时间序列中的date_range都是给定了等差序列中四要素中的三个,从而确定整个序列。请回顾等差数列中的首项、末项、项数和公差的联系,写出interval_range中四个参数之间的恒等关系。
    3 [& @2 F& }; K/ m6 u' l$ s: C+ |$ {
    : n- m3 @0 w% p+ V+ y% T  除此之外,如果直接使用pd.IntervalIndex([...], closed=...),把Interval类型的列表组成传入其中转为区间索引,那么所有的区间会被强制转为指定的closed类型,因为pd.IntervalIndex只允许存放同一种开闭区间的Interval对象。- c9 g8 \/ {  \  r4 i/ g
    # ]( o, W+ K' ^9 |
    my_interval
    7 B6 ], A& c5 F8 L) o7 {0 e9 L0 ROut[59]: Interval(0, 1, closed='right')1 P) x8 A: }; Z/ m. C: A( z
    3 ^: K0 [) ?; g' C
    my_interval_2' O+ O: j3 _: E3 x( z" n1 `
    Out[60]: Interval(0.5, 1.5, closed='left')' Z' U$ B0 c8 F. `3 G
    1 X- _9 T- p. t3 q0 z
    pd.IntervalIndex([my_interval, my_interval_2], closed='left')
    . A; b* a0 ^0 H4 S3 F0 SOut[61]: " X7 M! t' J6 t  a3 \- E8 V& i
    IntervalIndex([[0.0, 1.0), [0.5, 1.5)],
    + n: s7 l4 F3 L. H' B              closed='left',
    / K) M6 Z6 f7 |2 V7 t# V( I1 z              dtype='interval[float64]')
    ' ^5 M/ A7 W6 \1
    - k6 L' l% A% Q  Q- @! @2
    8 F/ T* b; {5 j3 D3
    ( c1 [+ B5 ~* n) a4& d! x$ Z  k) p( s3 e
    5
    3 F. W( F; |, I5 A6
    * S' _" \" ^! P% r7
    ( K% U/ O( U$ o6 W/ c8' R5 {- q5 D3 Z; q
    9. e- T/ w) P) T6 `; {$ r- R
    10; K, K6 ]/ `: v, {/ e; `
    11
    % V  y; W& t4 }9 p9.3.3 区间的属性与方法
    % ~7 v6 G  M# Q! r3 b! R. d* I+ s, z  T: Q  IntervalIndex上也定义了一些有用的属性和方法。同时,如果想要具体利用cut或者qcut的结果进行分析,那么需要先将其转为该种索引类型:
    - Y5 w0 M$ g& H& ~$ a/ ]
    9 A$ H1 \7 o" E& hs=df.Weight
    1 M' z! _( U( z* f5 q/ rid_interval = pd.IntervalIndex(pd.cut(s, 3)) # 返回的是每个元素所属区间,用具体数值(x,y]表示$ M  B+ R0 ^  N$ i- E
    id_interval[:3]
    ! ?* h% q; B/ R) Y+ u! A. p/ r, m. V/ K
    IntervalIndex([(33.945, 52.333], (52.333, 70.667], (70.667, 89.0]],6 C, l6 e. a# ^& p3 t  [- ~" w
                     closed='right',; S2 y- i  r6 Z' h% [  [4 x( o
                     name='Weight',0 B: e2 N$ t/ V2 N4 K
                     dtype='interval[float64]')5 g8 p$ w# m- s8 c- v( E
    1: Z$ F% L, H$ L2 I( V
    24 Q& o7 y& t* E6 t/ G8 z5 D
    3
    - H  U7 C3 }3 g43 x0 l. {9 A1 g& F" U6 u
    5
    2 z/ P$ S& u, _0 j! `1 P( m7 [6
    % q. x- ^+ J+ H2 @. `7
    # V$ P" Z, @+ M1 b8
    1 k% Z7 W6 g/ o" T与单个Interval类型相似,IntervalIndex有若干常用属性:left, right, mid, length,分别表示左右端点、两 点均值和区间长度。  h+ D5 e' F' f
    id_demo = id_interval[:5] # 选出前5个展示5 C: F- i1 o& z8 }! ~! U

    3 A# {) Y& R: V/ Q7 E# i, X( s, Wid_demo: s- d1 H* S( ?9 E
    Out[64]: & u, p/ p: n& z
    IntervalIndex([(33.945, 52.333], (52.333, 70.667], (70.667, 89.0], (33.945, 52.333], (70.667, 89.0]],
    6 A8 T2 \$ j& i& J! C0 ?& |              closed='right',
    $ _2 }( l) o2 q4 |# o              name='Weight',' f$ F0 @+ n" S7 Z3 A2 b! Y/ ?
                  dtype='interval[float64]')5 Z# J: x8 [. A4 m5 N

    ) O' }# i( T; N1 f2 Sid_demo.left # 获取这五个区间的左端点
    " K6 y* F4 A; ~: y6 ^Out[65]: Float64Index([33.945, 52.333, 70.667, 33.945, 70.667], dtype='float64')
    " P9 x+ a( _8 C9 N6 L  p! {& z0 n8 K+ R2 A2 e1 w! h
    id_demo.right # 获取这五个区间的右端点6 S# ?3 Z1 I6 p1 L
    Out[66]: Float64Index([52.333, 70.667, 89.0, 52.333, 89.0], dtype='float64')% ~% p/ a: W0 U
    $ C. l4 `" D5 H8 s5 c8 K
    id_demo.mid
    ; m/ O+ Y( w% O4 sOut[67]: Float64Index([43.138999999999996, 61.5, 79.8335, 43.138999999999996, 79.8335], dtype='float64')
    4 d6 s3 }5 M( H& k) {3 X
    ! G7 I3 w6 H/ g) B& Bid_demo.length8 s) `9 y% e# _" @8 C* N  @
    Out[68]: ; V' S! {" L+ \9 r
    Float64Index([18.387999999999998, 18.334000000000003, 18.333,
      h9 r! {4 u6 X* N( V" V              18.387999999999998, 18.333],
    3 f) v3 |; q1 j( _             dtype='float64')% o! ~9 r1 v1 v7 d7 p- S) A
    ' u# k+ `& ^. R4 f2 ^
    1
    ) J# f$ [* Q8 ?1 F  U2
      \/ u, Q0 w! Q: ]# Z/ r$ Z34 c) a( d  R3 a  F& C) f
    4
    % }' o9 R7 k# @$ x  n6 t( p- F5& \& j# z% C5 o: }. o7 b
    6
    7 i, V* K5 |) r0 ~7. C0 H" z" b* R. I5 F6 w' |
    8
      d: I3 ~$ L( r" g: j! U2 N9+ T: y, |4 l) m1 i
    10
    5 E" c# Y! l4 @$ v$ p4 m  L11
    5 u: ?% [3 Y' v' }126 a+ K' L+ I8 ^* F; U5 p
    13
    4 I, _+ j. [0 n( `14) n+ y0 G- S7 f5 N+ Z4 m
    15& m3 E$ j2 G  G
    16; r% n3 a* C: q# |3 N6 j6 H+ @
    17
    ; l2 N) X# V5 C" _8 @. X18& z- B3 \6 g! j$ D1 v" f
    19
    - ?! V1 j9 f& x20( b7 E# A& S9 R; k) v/ j3 O1 c
    219 d0 Y! G1 }7 p* O
    22  G  s4 M5 b9 L" R
    23
    - }7 }4 Q' Z" ?5 U  GIntervalIndex还有两个常用方法:
    6 W) J; Z1 U1 M5 ^contains:逐个判断每个区间是否包含某元素* z  V% i, o$ x8 a. z1 f
    overlaps:是否和一个pd.Interval对象有交集。
    - b' K$ W2 ~& V7 O8 r( nid_demo.contains(50)
    & n& N# U( r$ T8 q7 SOut[69]: array([ True, False, False,  True, False])
    7 ^! B5 j2 W9 @
    ; k# a6 |8 q& ^; S! N- Pid_demo.overlaps(pd.Interval(40,60))
    9 Q1 p+ _! a; V- KOut[70]: array([ True,  True, False,  True, False])
    ( I" R. s  U0 Y9 a+ J0 Z1' J$ X& D- @6 `, W
    2# N1 q% Y$ P' q5 u* b
    37 `$ A2 g' P: L* D+ k- t5 l
    4
    / H$ x4 T' r& R( ?5% [$ ^3 \# n% l- F4 |' {* W" j. h
    9.4 练习
    + [: f# H$ t5 v4 X3 _2 aEx1: 统计未出现的类别
    : o6 Y# J: n5 K( L  在第五章中介绍了crosstab函数,在默认参数下它能够对两个列的组合出现的频数进行统计汇总:* s/ \# _! c  a1 E+ D. b' `) i

    ' x5 Q* ?/ H7 j: ]5 Tdf = pd.DataFrame({'A':['a','b','c','a'], 'B':['cat','cat','dog','cat']})
    * Z1 c# r7 i  m0 e- F8 `pd.crosstab(df.A, df.B)& x6 y2 L& z3 I7 L, c( \( W
    ) S/ T: D$ x! e+ m2 d9 r
    Out[72]: - N) q0 E. T* `# `
    B  cat  dog
    . r1 q2 x: Y- t* N7 c3 F+ oA         
    - b- a" M* q) }: Y: E5 j9 ha    2    03 \" `+ D2 _* Z
    b    1    0
    ( j5 u; v! ]0 t9 Kc    0    1+ m; ~) Y! j7 E; d1 o- ~
    13 m& m1 ?4 q) P8 S! E( j. q
    21 c4 a3 D% Q" ]
    3
    4 \4 L# T5 ]) D- H$ p4
    0 A2 z4 t$ |- t8 Q4 k5 y7 F5: I* \! A+ n% }
    6: S- ~2 D- s/ E7 Y) @" h
    7
    & d& e* f" j# r8* q2 g9 f1 q8 }' C8 p1 t
    9
    . H9 e- `$ l) z1 q2 W0 D5 Y  但事实上有些列存储的是分类变量,列中并不一定包含所有的类别,此时如果想要对这些未出现的类别在crosstab结果中也进行汇总,则可以指定dropna参数为False:+ L2 O: a) x+ }

    3 T1 n1 A0 y* r5 X% e2 s8 Cdf.B = df.B.astype('category').cat.add_categories('sheep')
    5 I9 X! z8 \# }0 q6 t  k" A* a  Tpd.crosstab(df.A, df.B, dropna=False)
    2 r* o' `* a* V0 k  {& f7 e8 J
    - B$ D' }5 C9 ]5 Q4 mOut[74]: " P) s; r& R+ M( ~: q  C1 m/ j
    B  cat  dog  sheep$ T3 T5 V+ Z* c0 l- N/ u9 y
    A                 2 b! U. p: x5 p! Y
    a    2    0      0
    $ s+ p9 e; R: F0 y) {4 ?b    1    0      0
    * w' S8 Z0 T# lc    0    1      0' Y" D! O) L, a
    1
    / e: \. L& T, O: N( ~5 p2% L1 W8 z" x% ^' Z
    3
    : O! }4 N& ~0 N: J# R& `/ \$ ]4
    7 J+ k9 c0 F' W" J59 t: ]4 I4 K) P4 R
    68 y: _; Z  u& K! s9 M9 i3 i
    7" ]$ K, {# W2 Z5 k
    89 L5 h( J0 [0 n* H
    9
    0 Z; Y8 G; t* C4 j/ l6 S请实现一个带有dropna参数的my_crosstab函数来完成上面的功能。  {) D) W$ t* _. T; K* D% O

    ! G% p0 \% ^4 X5 U: [0 B, [0 gEx2: 钻石数据集
    ) A) i3 U. x0 z# K1 C  现有一份关于钻石的数据集,其中carat, cut, clarity, price分别表示克拉重量、切割质量、纯净度和价格,样例如下:
    . L( w$ r# U2 Z# s1 _8 e. N# b3 X7 e. ?$ K7 ]) O& J: S: ^
    df = pd.read_csv('../data/diamonds.csv')
    & y" v3 }! O+ u0 Rdf.head(3)
    7 A) m; l# v' @7 L6 F
    2 o2 q  g# p1 Q! l" t6 dOut[76]: 4 g1 o) S0 \- m% ^5 k2 ~- T
       carat      cut    clarity  price: D6 e5 a- G4 F
    0   0.23     Ideal     SI2     3265 u( D& B- j% T* A/ _/ U, n# ?1 _
    1   0.21    Premium    SI1     326
    5 v4 a: ~" R1 J. O% i8 {+ y# g2   0.23     Good      VS1     327  J% v- ]& g/ p3 I8 Z0 D
    1
      U! p$ o. I+ d) m$ r6 g20 E: w( I" \; D0 G1 {
    30 d- b/ L" W, I/ ~
    40 U( n) Y3 @* D, @( `
    5. y$ c4 F9 _2 F3 c* x: x
    6' s% G" ?# k  k
    7
    6 R9 O7 p. w* h8 F9 q* c8. z+ @* m& z* q- U7 {/ S& e/ V
    分别对df.cut在object类型和category类型下使用nunique函数,并比较它们的性能。
    $ f1 R& [( L# |. A) c$ s4 U钻石的切割质量可以分为五个等级,由次到好分别是Fair, Good, Very Good, Premium, Ideal,纯净度有八个等级,由次到好分别是I1, SI2, SI1, VS2, VS1, VVS2, VVS1, IF,请对切割质量按照由好到次的顺序排序,相同切割质量的钻石,按照纯净度进行由次到好的排序。6 b% H/ m+ k6 i, j& P+ }
    分别采用两种不同的方法,把cut, clarity这两列按照由好到次的顺序,映射到从0到n-1的整数,其中n表示类别的个数。$ R6 v2 d, H0 r, o+ _
    对每克拉的价格分别按照分位数(q=[0.2, 0.4, 0.6, 0.8])与[1000, 3500, 5500, 18000]割点进行分箱得到五个类别Very Low, Low, Mid, High, Very High,并把按这两种分箱方法得到的category序列依次添加到原表中。: V  t  R; H1 g3 a
    第4问中按照整数分箱得到的序列中,是否出现了所有的类别?如果存在没有出现的类别请把该类别删除。
    0 U7 I8 b+ m. k: s. I& [8 h  F/ x! z( ^对第4问中按照分位数分箱得到的序列,求每个样本对应所在区间的左右端点值和长度。
    8 A8 X1 Q! p7 d5 x5 j8 h先看看数据结构:
    : f* _2 O  h6 k7 e/ f
    / n. d3 s* `/ O' edf.info()3 W4 f/ \! D" a+ r7 ~8 H
    Data columns (total 4 columns):
    + b( C' a1 _8 ~ #   Column   Non-Null Count  Dtype  
    & q& K) S6 t4 y$ y+ l( \---  ------   --------------  -----  
    ' _, X; ]$ c* w/ ] 0   carat    53940 non-null  float64; D9 h0 g% H' V$ J* g
    1   cut      53940 non-null  object 1 p$ r  J* B( X$ q2 E5 _( m
    2   clarity  53940 non-null  object   Q9 ], q! G5 x+ d, V
    3   price    53940 non-null  int64  
    % z- y/ a- O; P: Odtypes: float64(1), int64(1), object(2)4 T2 k- G' j9 R3 o+ c
    1
    % a8 k" }4 O6 a) Y2
    8 d3 O5 r2 t9 t" t7 Q; h32 c6 n/ O2 e. U# q- N# R
    4% D; J+ R6 J* M$ u: B2 w
    5
    ( ]! Z$ T' F$ r* x7 o# W5 h62 [( k/ T* x, }
    7* d& c: p% Y" V( m& D) A5 P. W
    8
    / z& `. x8 y+ ?5 j7 H9 y98 j+ X" p; p5 z3 H3 S8 q" `0 @8 u
    比较两种操作的性能; \$ i8 ^* t6 O4 A3 S1 l/ Q
    %time df.cut.unique()
      J, x* D) e/ ^5 D7 t5 @; }( H2 R% Q" h- F
    Wall time: 5.98 ms: Y4 P+ ^& `& ~  M) W% V2 z
    array(['Ideal', 'Premium', 'Good', 'Very Good', 'Fair'], dtype=object)
    4 y, a3 B: {. S: L4 f4 A12 ^: h. I8 e# }9 T/ D: a0 o6 z
    2& O( n# k6 K0 n% U! M; M
    3" k' g8 I( X! t% d
    4
    ! G/ b" K% ^, q* J" {) i2 P1 D%time df.cut.astype('category').unique()
    , b: u6 P+ u" d: K7 c" I4 ^" P, R4 `; h: F+ `
    Wall time: 8.01 ms  # 转换类型加统计类别,一共8ms8 D4 ]) a. R3 Z& \9 b" O3 z
    ['Ideal', 'Premium', 'Good', 'Very Good', 'Fair']5 j1 W6 i. Q' D. s  g7 S
    Categories (5, object): ['Ideal', 'Premium', 'Good', 'Very Good', 'Fair']
    - m8 V) a& F  |3 S1: ^$ r! u& L4 i3 f  q
    2
    ; I3 w; _, I" a1 y3
    7 ]' Q2 h& x$ S5 d3 ]4
    6 A3 {4 W* \$ R% q# T7 [, N55 i  _( B4 g+ ]0 a! F
    df.cut=df.cut.astype('category')
    . Q% R) X! x3 v8 B6 z5 d9 C%time df.cut.unique() # 类别属性统计,2ms
    9 }% Z9 z' C( h1 W! u- z7 a$ O. M' \0 X. m3 i
    Wall time: 2 ms" }9 \9 f, M3 Q" ~
    ['Ideal', 'Premium', 'Good', 'Very Good', 'Fair']6 ~8 {' ?$ N. I1 I1 @- [
    Categories (5, object): ['Ideal', 'Premium', 'Good', 'Very Good', 'Fair']- w' M- _  i5 G. ~1 [( K7 o
    1
    + \9 t* [3 p% p! z6 y8 D2& g+ ]( E% f1 N- f7 K+ H
    3
    0 Q' S' G. o$ ^! A4
    $ \6 `" P9 B6 g" t54 z0 X+ m, A( D, N" [' C2 k
    6
    * J+ H: w7 S7 G- r对切割质量按照由好到次的顺序排序,相同切割质量的钻石,按照纯净度进行由次到好的排序。
    4 h4 j, k( w: A2 vls_cut=['Fair', 'Good', 'Very Good', 'Premium', 'Ideal']) }# Y" h6 G* @+ K3 i
    ls_clarity=['I1','SI2', 'SI1', 'VS2', 'VS1', 'VVS2', 'VVS1', 'IF']3 ~  m" F/ O& y& U
    df.cut=df.cut.astype('category').cat.reorder_categories(ls_cut,ordered=True)  # 转换后还是得进行替换
    7 O4 z% C+ a; D0 q. Ndf.clarity=df.clarity.astype('category').cat.reorder_categories(ls_clarity,ordered=True)
    . s  I/ s9 d( S& |. q6 j: O+ x& m% D$ s
    df.sort_values(['cut','clarity'],ascending=[False,True]).head(3)
    " `; `# c6 ^! [9 }& v7 j/ N# p. B6 _4 l
            carat         cut        clarity        price- m7 I  k5 k/ Q% s. g$ d# P- H0 c
    315        0.96        Ideal          I1        2801, i2 s% i, w+ K- n( R
    535        0.96        Ideal          I1        28265 ]5 {! V$ J/ m9 x  j# X) x
    551        0.97        Ideal          I1        2830/ e6 \( s) w! f- Y5 p( z
    1. |& r: r4 w, M) B
    2
    8 j% u% i) z: r, E/ ~# [6 ?" v3+ T4 L$ P0 S8 z# f8 ^7 N: S9 v0 U
    4
    3 \' x1 I9 v# R5
    ( `/ k, P% X6 `  p, O& P8 z6
    % m, G% V# x$ G. N8 d1 t4 K- e78 C( O. w; S; \, Y! Y4 [# g
    8! m" b) _: q/ w: f% A, a8 A# E
    9. R$ I6 b# F6 Z+ J8 B9 k
    10
    $ `2 [* l; ^* D2 I! k( Y11# w2 Q' J/ \1 a; S9 W# B9 o1 f" f6 E
    分别采用两种不同的方法,把 cut, clarity 这两列按照 由好到次 的顺序,映射到从0到n-1的整数,其中n表示类别的个数。; h1 ~# F4 Y, T" q9 `: A; S/ U
    # 第一种是将类别重命名为整数
      d! E. c4 q- _% V" M5 Z$ \4 wdict1=dict(zip(ls_cut,[x for x in range (4,-1,-1)]))
    ) I) B9 o, `0 G- j+ X9 V0 Y% Tdict2=dict(zip(ls_clarity,[x for x in range (7,-1,-1)]))
    5 Q$ g# _8 E; r' D0 S
    ) }, r2 k& }+ \1 Y5 jdf.cut=df.cut.cat.rename_categories(dict1)
    7 ~6 a4 P8 k" l. H9 \" m8 R- Qdf.clarity=df.clarity.cat.rename_categories(dict2)* P5 ]: R3 A% t$ ]* ~
    df.head(3)
    4 v5 N7 ^2 ^% I* v6 }8 v" @/ s' T% ]: q
            carat        cut        clarity        price) L! O* n; t3 s8 [8 ?
    0        0.23        0          6                326) k/ X% v# ?& w6 I4 M1 w6 A  j
    1        0.21        1          5                326
    " k* u1 ~4 O  S% {2 {2        0.23        3          3                327
    ( ^9 ^+ p. Z" H  I  n/ b" v1
    ! |6 B9 d$ s. e# K9 q2% c% ?& X5 q1 N+ V
    3
    & b8 V& P% g1 K# V9 e4
    ! G9 N1 ?# B, K- L6 R5& P0 X. N5 c5 x: ^3 C8 C# t* M! B
    6$ P7 s1 Y' D; K2 }- o2 t; g
    7
    ( a) e% O1 {/ t! k0 Z8/ Q) q6 ?! e: }4 k9 b0 O
    9
    + A6 k6 z9 V  G10
    6 i# {6 z+ C. B" a5 P% T/ r* Q5 ?11* s  M2 k6 B9 `7 S, _0 s8 Z8 _
    12
    3 a% J" {2 c0 w' H: {: B! \: D# 第二种应该是报错object属性,然后直接进行替换) J  x& P, f) y
    df = pd.read_csv('data/diamonds.csv')5 y7 c' R+ d. O" j# o: m7 O
    for i,j in enumerate(ls_cut[::-1]):4 b0 X) ?2 o' S' G* M
        df.loc[df.cut==j,'cut']=i ; l, C! @8 @& Q2 i; v! B( S
    9 I0 M' ?) h" L
    for k,l in enumerate(ls_clarity[::-1]):8 o- V' N; P8 e& o- T# _
        df.loc[df.clarity==l,'clarity']=k
    - A6 h% O9 o8 T! d$ kdf.head(3)
    ( L- _$ \* W: u) M! @' ?" L' X# D" A5 q1 {9 j- _$ \2 Y
            carat        cut        clarity        price
    8 ~7 I/ J" ^. p0 {8 N. Z0        0.23        0          6                326
    , \' f: s/ Y' ~/ P7 Y" `3 n3 H1 \1        0.21        1          5                326
    - ~* _4 C  e( A5 E  R2        0.23        3          3                327
    $ q9 h* t+ b+ Z2 k4 ]; q1
    / i; ~) f( B' o0 s/ a  w* |2
    3 e& ]! V# V# R! N" t# |$ I" W3& X% V$ `$ A; n9 J8 P" O
    4
    ) w2 {+ W- n, R) J7 V4 _7 m7 G5
    2 N6 ~& `3 |4 d. k9 D$ B' n65 h6 U: P- d, k2 U+ q
    7
    1 B$ z+ N" O/ N+ S9 x( |6 q4 F2 ?8" x* M/ S0 Q! M, S! m
    9* ?0 u- J: H2 d! R6 p1 ^
    104 P7 H( T  w( ^* O6 R
    11
    4 v/ D  P& {# J/ B3 q' S, i7 D12
    / H' {0 T. Q7 G0 X- G# b13- K0 ~4 z& \5 Q  p
    对每克拉的价格分别按照分位数(q=[0.2, 0.4, 0.6, 0.8])与[1000, 3500, 5500, 18000]割点进行分箱得到五个类别Very Low, Low, Mid, High, Very High,并把按这两种分箱方法得到的category序列依次添加到原表中。
    ! q! g. n- r" @% ~( t$ l# retbins=True返回的是元组,第一个才是要的序列,第二个元素是分割点& s6 b" N1 i" @; S! P; ]" o
    avg=df.price/df.carat2 w6 w+ |5 c3 B. e
    " j: T6 K/ K7 b# |6 E" l
    df['price_quantile']=pd.qcut(avg, q=[0,0.2,0.4,0.6,0.8,1],
    6 @9 d2 ^9 G& Y# v                              labels=['Very Low', 'Low', 'Mid', 'High', 'Very High'],retbins=True)[0]
    ; A5 s0 t5 Y- k& b$ i1 O5 w- u4 l2 V; O7 d* c. g* J
    df['price_list']=pd.cut(avg, bins=[-np.infty,1000, 3500, 5500, 18000,np.infty],
    - H- I* {7 f; Z( u                              labels=['Very Low', 'Low', 'Mid', 'High', 'Very High'],retbins=True)[0]
    ) O, E3 o2 \' b7 tdf.head()
    1 }3 w5 A% q. k9 p# a0 A" @
    4 d$ q% d4 z8 }        carat        cut         clarity        price        price_quantile        price_list0 v, P" U5 n( k7 G# R
    0        0.23        0                6                326                        Very Low                Low! ]6 w' E# ]+ [' C
    1        0.21        1                5                326                        Very Low                Low5 y2 s1 @- T, ~' w' O
    2        0.23        3                3                327                        Very Low                Low7 M& w  R6 O+ z6 Q- w
    3        0.29        1                4                334                        Very Low                Low; R- \7 E3 d+ i8 w) v3 B+ g8 ?
    4        0.31        3                6                335                        Very Low                Low                                       : |2 w; C6 }1 ~: g4 r4 B
    * g) n- M1 Q7 W6 g/ Y
    1
    8 i7 ?( \8 D( D/ I0 v2
    ; y% h( T9 q( t4 K7 P35 C5 z* j) p( `! M: h
    4
      @2 [. K5 Y; ^7 c2 b7 H$ O5
    ' `% K0 r4 p- d5 x: i" p65 I! A, \; o" j7 O# x8 k) j
    7# l* x% v4 u! V% M4 n& ?( V
    8
    5 N( T$ r) x( P4 N5 R90 E& x0 K) t5 O5 Q5 R
    10) d( b6 R: b; b) U" o
    11* d: X, H0 p9 ^
    12. W+ n7 t! F2 E' u
    135 g9 A( e% m) u
    14, y  H8 Q. K6 \0 U; j
    15
    ! Y, r5 y% c6 K" S166 S5 Q! _7 j* ]3 m
    分割点分别是:' v* l! V- L) W  v" s& Q) U8 f

    & F! h2 d3 N: f7 Varray([ 1051.16 , 2295. ,  3073.29,  4031.68, 5456.34, 17828.84])
    2 x) r4 y- b0 \3 U6 K5 W6 C8 c% Garray([  -inf,   1000.,    3500.,    5500.,   18000.,    inf])
    5 U# N0 |( w' c* }" U1
    , L" P; D$ j% X) x' ]/ _2' y6 x. r+ q- m* F
    第4问中按照整数分箱得到的序列中,是否出现了所有的类别?如果存在没有出现的类别请把该类别删除。
    0 A* a9 B% T) [: S9 q# Y( T$ B/ [- Cdf['price_list'].cat.categories # 原先设定的类别数
    3 l3 I; m( [' r8 j; `Index(['Very Low', 'Low', 'Mid', 'High', 'Very High'], dtype='object')' H2 ?# z/ i9 R, @( ~8 o; p$ o. @
    # D- |; |) O, p% ?2 |/ W6 B8 ?
    df['price_list'].cat.remove_unused_categories().cat.categories  # 移除未出现的类别
    # w1 }6 I; i1 [7 @- r) A* q: c7 rIndex(['Low', 'Mid', 'High'], dtype='object')  # 首尾两个类别未出现9 S6 E. d" {  E2 C. a2 e# G& F
    1
    0 r; D/ S2 l0 e# q; {- o, `* n2! [* ~: Y5 z3 r7 q; L) j* x
    37 ~  o4 T7 F6 x! b( g2 R% r
    4
      P; e3 o" V; l& K4 I8 b: y3 k5: n8 @+ M/ Y. O3 D& t
    avg.sort_values() # 可见首尾区间确实是没有的) @, F/ F1 {6 @. n& W% a6 |
    31962     1051.162791
    ; E- G8 j4 I8 A2 V! t15        1078.1250003 _6 O5 p7 X7 J# ~9 J5 b
    4         1080.645161
    : p# i" w# h9 }! g1 _9 U28285     1109.090909' M3 Y3 W' G2 K# k
    13        1109.677419
    2 ^0 h8 {) W7 x4 k8 S* M  i3 ~             ...     % l& S. m/ W8 ?# K& X
    26998    16764.705882% @5 ]% D' Y, N' |+ c% E+ b
    27457    16928.971963
    7 L8 y# h) Y* R; g- t27226    17077.669903' M2 V" g7 N7 F4 H8 S
    27530    17083.177570
    ( |7 ~: H2 X1 M27635    17828.846154* R5 g+ P( q+ L
    1' ^! l( S0 a3 f2 f3 `
    2
    $ w' `( F& v% t: I% V* X' G3: {. F6 r# B6 }" k, ~% ?1 c
    4
    1 ?: }& ]' O& h9 H3 q3 b5 R3 w5
    , w$ ]( ~1 V. L4 O6
    : [% t) ?1 ~: u' E7  w6 F4 G- Q3 ^% T) R* y" D; G
    8
    9 m/ n- ?+ z/ d3 ]9, e5 e8 W: L: I; M! a, ?
    10
    6 D+ l- ]9 i% c" e/ \9 m' E11) ^, ]# V: `1 m  a" H$ H5 V
    12
    8 m, X' u& s) Q对第4问中按照分位数分箱得到的序列,求每个样本对应所在区间的左右端点值和长度。
    6 l% d5 o- y; w0 H' N# 分割时区间不能有命名,否则字符串传入错误。
    ! ^. O8 P4 c+ v5 C$ U+ n. Bid_interval=pd.IntervalIndex(
    : B3 e- r+ c; u2 B4 f9 ]9 c! i& U2 ^    pd.qcut(avg, q=[0,0.2,0.4,0.6,0.8,1],retbins=True)[0]
    " x0 Q$ `5 }+ L+ s: q                            )
    4 }. V0 w. {! }) X1 C2 B8 @id_interval.left
    : v, J: y6 ?( ?9 r. Z$ Sid_interval.right+ |/ u- W# X$ d! o* T
    id_interval.length                            $ D) e2 z2 u0 k+ g4 j
    1
    1 z4 x- q9 r& {: u" f; f2
    + o) b8 @2 f9 P% O' i3, H% e  C1 n- T* u" ?9 U0 e  O
    42 `% I( o, d4 Z8 d
    5
    9 X9 i# ?3 a# T  a1 j) M7 E6, S$ q* w& ]" S! s
    7
    7 T  o$ _$ d- }- ]. I, \第十章 时序数据
    1 e7 H5 e+ g* r- `7 v. fimport numpy as np
    3 i' }: ^; {# z  B$ P8 ximport pandas as pd
      W; _3 O4 s  a/ c% _1
    3 }% `& f& S1 _* b  U( X4 n1 ?7 _% _2# f( |, B5 p, P3 Z1 B2 f
    3 F2 r4 v+ W# o7 T

    3 e( H& ^0 L1 r5 b10.1 时序中的基本对象
    ' _! C; E5 r9 E) ?$ o! n  时间序列的概念在日常生活中十分常见,但对于一个具体的时序事件而言,可以从多个时间对象的角度来描述。例如2020年9月7日周一早上8点整需要到教室上课,这个课会在当天早上10点结束,其中包含了哪些时间概念?
    + z+ l9 K' z- [6 [9 X1 V! l' e, ?" l& U
    会出现时间戳(Date times)的概念,即’2020-9-7 08:00:00’和’2020-9-7 10:00:00’这两个时间点分别代表了上课和下课的时刻,在pandas中称为Timestamp。同时,一系列的时间戳可以组成DatetimeIndex,而将它放到Series中后,Series的类型就变为了datetime64[ns],如果有涉及时区则为datetime64[ns, tz],其中tz是timezone的简写。( O# I* U) E% y" F. X# p# X, Q8 C; I

    % `' b: q9 H/ r会出现时间差(Time deltas)的概念,即上课需要的时间,两个Timestamp做差就得到了时间差,pandas中利用Timedelta来表示。类似的,一系列的时间差就组成了TimedeltaIndex, 而将它放到Series中后,Series的类型就变为了timedelta64[ns]。
    , W( o0 N* j+ G
    6 e0 ^; v0 W/ {; T( c会出现时间段(Time spans)的概念,即在8点到10点这个区间都会持续地在上课,在pandas利用Period来表示。类似的,一系列的时间段就组成了PeriodIndex, 而将它放到Series中后,Series的类型就变为了Period。* f% E3 f: _% ^' k  p: Y

    1 i8 J" `2 v8 S# \会出现日期偏置(Date offsets)的概念,假设你只知道9月的第一个周一早上8点要去上课,但不知道具体的日期,那么就需要一个类型来处理此类需求。再例如,想要知道2020年9月7日后的第30个工作日是哪一天,那么时间差就解决不了你的问题,从而pandas中的DateOffset就出现了。同时,pandas中没有为一列时间偏置专门设计存储类型,理由也很简单,因为需求比较奇怪,一般来说我们只需要对一批时间特征做一个统一的特殊日期偏置。% O. @( E- N/ i9 t
    ; }# `! e8 f$ H
      通过这个简单的例子,就能够容易地总结出官方文档中的这个表格:
    9 r' S  `9 a( j& h& V. X. S4 R
    % S1 L  S% f# k1 D  H( U1 p  R概念        单元素类型        数组类型        pandas数据类型
    # o- ]7 x6 F: g  T" P$ Q# tDate times        Timestamp        DatetimeIndex        datetime64[ns]
    4 E* f/ ~! R5 ITime deltas        Timedelta        TimedeltaIndex        timedelta64[ns]/ ?! u6 B  M( W) ~- d
    Time spans        Period        PeriodIndex        period[freq]- o4 ~7 w+ ]) l3 `3 }
    Date offsets        DateOffset        None        None% Y* H% N+ }# Z5 P- W& k9 W
      由于时间段对象Period/PeriodIndex的使用频率并不高,因此将不进行讲解,而只涉及时间戳序列、时间差序列和日期偏置的相关内容。
    6 `$ [; Z, s3 t1 E' |! K! e( W& O% n. B4 V8 @
    10.2 时间戳
    2 ~3 c  X! d) _4 E% s' P% f- F) I10.2.1 Timestamp的构造与属性
    - @/ R/ f& K) y/ U单个时间戳的生成利用pd.Timestamp实现,一般而言的常见日期格式都能被成功地转换:
    5 Q& V$ W7 }8 M+ O9 U; _5 L
    , d# [9 _6 P7 V3 K2 A% sts = pd.Timestamp('2020/1/1')
    : B  \' `1 v- O- I% F
    5 l6 ~" i5 O/ L% W# u9 w; D( }ts8 c6 Y! i( x. h& Y
    Out[4]: Timestamp('2020-01-01 00:00:00')
    ( N  q# Q; i7 \, _8 y
      B' N: |/ B1 c5 V* L& Q8 T5 hts = pd.Timestamp('2020-1-1 08:10:30')/ [* g+ t& [# I
    % B. G0 T$ f4 u+ y$ k' j
    ts
    ; g. K: w* F' T5 K% B3 h: B# \Out[6]: Timestamp('2020-01-01 08:10:30')/ ]: p  e; `* K& j3 {' r
    1" Y/ c' ~! e. P2 c
    2
    ) F, G+ j6 F7 j) h8 p/ I7 W" d3
    0 k8 i) _1 a1 e0 ?2 I. B4+ s1 {) h& m( y
    5
    , B; W  Z* Z8 O. N  K" E6
    3 L6 d  s0 t* o* K/ n3 U. C3 q7! n* L; H0 V$ |1 x7 X  d" n
    8
    4 p0 z7 r7 E6 U6 [# q9
    , O" O! `# M$ l" F- }0 n  \通过year, month, day, hour, min, second可以获取具体的数值:" T* f. m$ K- V6 K  W2 E: u

    ; D/ W0 [) p5 c7 W; t' K) ~) Pts.year' v: u( }4 O8 T4 L
    Out[7]: 2020
    ! F' ?% y2 g. O9 R' w7 o1 J& x( q. V, T9 C
    ts.month
      E" L/ V0 }# w  ]* cOut[8]: 1: L0 e- m% G* a. Q8 H( ^4 S: Y5 ?2 K

    - |  _+ W5 G, ?1 `1 i8 yts.day
    ! K* s. K! m5 VOut[9]: 1& w0 F; L, \$ K% N, l
    - p. f* r8 j& }+ w# q% [8 o0 K
    ts.hour
    + b1 Z5 k4 A% EOut[10]: 81 r/ e8 Y4 y3 `& ]0 E) D
    ' W5 E, [8 }1 [. r
    ts.minute
    & T( b# X% W7 \3 s- o7 C) dOut[11]: 10
    ) o' ^* I" V, n! S- T. k" Z& m, K7 b( R* ]
    ts.second
    ; d( {( m) M. x8 nOut[12]: 30
    1 \* h, p2 j  p8 ?) ~+ u
    " K7 `% \  p' c3 P1- v+ V) a% A! v4 ~
    2% S" @3 v9 d5 i3 P
    3
    2 c3 q& i! @5 h+ k4* ]6 C# I5 p5 c+ d
    5/ h$ [7 }) i$ `* `- v0 t9 C) j
    6$ V, }4 u: \0 c1 Y" I+ N
    7
    : f, L- I4 \7 t$ X7 M9 u8
    / o. C& p8 V6 I! y, |/ \4 B99 q5 r7 Z- H5 a7 V7 m9 k
    10
    8 G7 ?# n) g8 p2 R11
      F0 f, k9 [% V* C12
    % w0 t: J# |/ w3 a13
    + g: B5 u3 g2 A. h( i149 ~& G( T- J  O
    15( A$ e# d7 @4 p- t3 K" |
    16  u2 z# u5 T$ A* m
    17+ s: N% V# G0 ]0 O* m: v! K" y
    # 获取当前时间
    % F: n* E% f+ _9 pnow=pd.Timestamp.now()1 W$ M: P( R1 \
    15 P1 A/ k+ v9 z  v5 W( @% h( @2 _
    2. d4 q. O* X9 T  w8 S
    在pandas中,时间戳的最小精度为纳秒ns,由于使用了64位存储,可以表示的时间范围大约可以如下计算:
    + _/ {! g, i* a. @! dT i m e   R a n g e = 2 64 1 0 9 × 60 × 60 × 24 × 365 ≈ 585 ( Y e a r s ) \rm Time\,Range = \frac{2^{64}}{10^9\times 60\times 60\times 24\times 365} \approx 585 (Years)" I1 t* F5 @! M% n& \
    TimeRange= 9 ~' \7 `/ U* a! @
    10 & R8 B0 B. C/ R
    9
    + X  \6 Z0 T: x- H5 s, r% J ×60×60×24×365
    3 j; q& ^1 _1 z) g2 ' ^( o0 K, H3 y, ]$ }
    647 e) F. {. @1 i! [1 y

    + v' ~. z+ D0 D: r6 |8 P( }
    / X$ {0 K1 [1 s4 S ≈585(Years)2 f2 Y3 P' j$ T. A+ B
    ! n) f5 p" z( t& g, t/ g8 u
    通过pd.Timestamp.max和pd.Timestamp.min可以获取时间戳表示的范围,可以看到确实表示的区间年数大小正如上述计算结果:* G( [4 z7 I! C$ G
    3 T) H- s# ?& F/ t6 @3 r
    pd.Timestamp.max/ c# i, h# W8 {. |
    Out[13]: Timestamp('2262-04-11 23:47:16.854775807')
    ( R1 }5 y. f  V8 D4 v
    % Z6 d5 e' d" w3 jpd.Timestamp.min
    2 ]: V, O% R% _4 r4 V4 lOut[14]: Timestamp('1677-09-21 00:12:43.145225')
    - m1 Q4 u" [3 Q% l. K0 K
    0 b+ _; P% v4 A7 ?, Y6 X: T) Gpd.Timestamp.max.year - pd.Timestamp.min.year2 O; k# L8 |# J+ `
    Out[15]: 585
    & z1 A1 m3 m! x) @, G4 |8 y/ E( }1: f! a0 ]% g) v
    2
    + x" B! ~' Y% {% |. O$ k31 c0 V1 ~$ O+ q* A# W8 K- g
    4: O0 h) m8 [& S' k. S' K7 T$ a' ?
    5
    3 v$ h& j9 q- b( f64 C7 F  `' M% N; {) b4 I' H
    7
    * ^4 D6 X+ \4 J3 @' x; K% w8% {. M7 T+ N& w9 W9 Q
    10.2.2 Datetime序列的生成7 Q/ b2 m7 ]; H5 Z9 b6 Q5 D/ ~6 D
    pandas.to_datetime(arg, errors='raise', dayfirst=False, yearfirst=False, utc=None, format=None,4 l4 X# c6 r) J
                                      exact=True, unit=None, infer_datetime_format=False, origin='unix', cache=True)4 w5 B% X- ]( t8 s; G! _& S( t
    1" \9 P4 ]& W6 v
    2
    * X2 X4 a% p$ }* c9 ?3 J" v4 Qpandas.to_datetime将arg转换为日期时间。
      @, B1 G' C( i% |& I0 H+ D: @6 @0 v7 _7 B
    arg:可以是argint、float、str、datetime、list、tuple、一维数组、Series、DataFrame/dict-like等要转换为日期时间的对象。如果提供了 DataFrame,则该方法至少需要以下列:“年”、“月”、“日”。2 a/ ?4 V3 Q% }! A. ~0 p  z% ~5 r
    errors:7 y" {' b* x! g3 d' M; Z2 M2 n
    - ‘raise’:默认值,无效解析将引发异常
    , y, q; T; x: K" F( n- ‘raise’:无效解析将返回输入
    + `' ^0 Z1 q7 `7 ]9 w$ x- ‘coerce’:无效解析将被设置为NaT) G2 S! E$ G; ^! r2 @+ c
    dayfirst:如果 arg 是 str 或类似列表时使用,表示日期解析顺序,默认为 False。如果为 True,则首先解析日期,例如“10/11/12”被解析为 2012-11-10。如果无法根据给定的 dayfirst 选项解析分隔日期字符串,会显示警告。; [1 R& y6 ~+ F: _$ |2 L; N
    yearfirst:如果 arg 是 str 或类似列表时使用,表示日期解析顺序,默认为 False。如果为 True,则首先解析年份,例如“10/11/12”被解析为2010-11-12。无法正确解析时会显示警告。(如果 dayfirst 和 yearfirst 都为 True,则 yearfirst 优先(与 dateutil 相同)。)# J2 L! H. ~; A0 }7 `, I
    utcbool:默认None,控制时区相关的解析、本地化和转换。请参阅:pandas 有关时区转换和本地化的一般文档
    : D  e5 B& F6 Q, L2 x% C5 bformat:str格式,默认None。时间戳的格式不满足转换时,可以强制使用format进行匹配。0 S" X8 G, C8 h* n
    unitstr:默认“ns”。它是arg (D,s,ms,us,ns) 的表示单位,可以是整数或浮点数。这将基于原点。例如,使用 unit=‘ms’ 和 origin=‘unix’ (默认值),这将计算到 unix 开始的毫秒数。4 ?* ^- [1 w( l0 N  H% W
    to_datetime能够把一列时间戳格式的对象转换成为datetime64[ns]类型的时间序列:4 r, l9 W% _( S( \7 }6 s. A8 U
    pd.to_datetime(['2020-1-1', '2020-1-3', '2020-1-6'])2 O2 e% E. i6 Y

    $ d0 U' |, b7 H& l6 ^* J  jDatetimeIndex(['2020-01-01', '2020-01-03', '2020-01-06'], dtype='datetime64[ns]', freq=None)/ @7 m* A) p- L8 B, j
    1
    ) J8 L$ |1 o) Q6 R, o( l# R3 x* K2
    8 }8 s: R6 s2 |3 e* S- q+ d37 u5 _* B/ U3 d
    在极少数情况,时间戳的格式不满足转换时,可以强制使用format进行匹配:
    4 x# h' T4 g, k% [# |8 s6 m
    , _+ M/ d, b2 V( [9 M+ C! Z' Ztemp = pd.to_datetime(['2020\\1\\1','2020\\1\\3'],format='%Y\\%m\\%d')6 f9 B1 {% B& q( s3 }8 O& e" L
    temp) U+ y6 M5 N3 X9 h

    6 X  G2 M* e' h# n. YDatetimeIndex(['2020-01-01', '2020-01-03'], dtype='datetime64[ns]', freq=None)
    2 R# O; M" `2 D9 G14 J; X, Y$ g7 v& w; w
    2& ]9 m: L; V: s
    3
    ) e; T( k  d' o( [) ~4" ?2 Z- t' [8 J7 x% J- f4 V
      注意上面由于传入的是列表,而非pandas内部的Series,因此返回的是DatetimeIndex,如果想要转为datetime64[ns]的序列,需要显式用Series转化:
    6 }4 p9 }9 K1 w. k" r
    3 d! m. q3 g5 f# ?pd.Series(temp).head(), l; t$ L3 `: E1 `8 y+ _- H

    2 S8 G7 [& b* w1 s# |0   2020-01-01
    * Z7 v3 J7 V) L4 p5 A) X: d' `2 A1   2020-01-03# m; b& K& g$ P, G3 K
    dtype: datetime64[ns]
    3 O5 ~) {+ [# ^- @3 S1$ t- m* o: G/ ]; |, [0 o2 |
    27 }& N5 P- F0 ~
    3$ [! w  C0 E6 L% L8 V" M. X2 I
    40 W! h6 a) f/ X
    5
    ' I3 Z+ V# g, p+ w) E) V下面的序列本身就是Series,所以不需要再转化。
    . X2 ]+ r- m( Q
    6 V1 p8 I8 _! J( P  @- d1 Ldf = pd.read_csv('../data/learn_pandas.csv')7 f- |  e7 L4 j& A) e+ g7 `0 b
    s = pd.to_datetime(df.Test_Date)/ l- t# Y/ ]3 }
    s.head()
      B4 [! \* G& i+ ]8 s" H8 s- z* @/ C  J/ K2 Y. X1 X6 g
    0   2019-10-05  T6 n7 E3 @. f5 b2 j2 Y  L
    1   2019-09-04; Y0 F3 j# f; ], \
    2   2019-09-124 F  }: P7 m5 i4 L5 |' r
    3   2020-01-03$ {2 y7 w9 m) _* s' h$ o6 K" h& H
    4   2019-11-06
    : [/ q: P( r9 O; ]. NName: Test_Date, dtype: datetime64[ns]
    8 {* V0 S# P7 s* R1. n- w% x4 q$ ~  Q: V! D
    2
    & v8 ?4 M* l4 V1 F2 ~) G8 b3
    2 F2 V' y( C1 b* Q& z4 A49 a/ b+ ?) q1 ~' r! ]& i
    5
    ) O+ L6 C0 U0 u% Q3 {- o) G3 h8 `6. H- T5 W2 K$ ?- s4 I$ H
    7
    0 L" |% N3 @1 c0 d# ]; Z0 S4 D8
    1 f; [5 l5 d5 H% C9, A: N- ~- S. c7 d# \0 X( T
    10) C0 k: s+ J7 N& t' @
    把表的多列时间属性拼接转为时间序列的to_datetime,此时的列名必须和以下给定的时间关键词列名一致:6 ]1 R1 ^0 P2 j, T7 Q- Q
    df_date_cols = pd.DataFrame({'year': [2020, 2020],0 E# R  f  C& [! h. G
                                 'month': [1, 1],
    , [, S6 L0 `; {$ _1 B& P* T                             'day': [1, 2],# w5 U9 s8 n% r2 f; y2 Q
                                 'hour': [10, 20],
      b! u, R; d# o% U) H2 s                             'minute': [30, 50],
    - p0 E0 a8 O- f, n) I8 a                             'second': [20, 40]})( Z5 Q6 d2 O8 o$ D& x: f2 k% a
    pd.to_datetime(df_date_cols)) _2 x7 V8 {5 `  p5 n
    + z$ g) p3 I7 ]( `
    0   2020-01-01 10:30:20
    2 ?3 r* D! F7 G3 z' a1   2020-01-02 20:50:40' s. q9 q' `6 K! m$ E0 u2 i) T" z
    dtype: datetime64[ns]
    % U* n: y; U6 X12 K1 S9 i/ n1 N) S: P
    2% a. [+ Y9 H! [' v; t$ ]5 W
    3( ~9 ~* g( j) y3 {" ~
    4
    & G7 ?5 G" Z  U  q5
    / s  {& z) r, T% f0 K% u7 A6 U6
    ; `  y+ i# x5 A" u3 c8 t7& |$ q. u" S- u: N/ s( X
    8
      J; f( X6 {- r( V' ]0 d* y0 }92 I0 A$ k9 ~1 W* p
    10
    % s( ~7 u( |& r- G  |* j11! _8 \" h) C% _
    date_range是一种生成连续间隔时间的一种方法,其重要的参数为start, end, freq, periods,它们分别表示开始时间,结束时间,时间间隔,时间戳个数。其中,四个中的三个参数决定了,那么剩下的一个就随之确定了。这里要注意,开始或结束日期如果作为端点则它会被包含:
    # {2 t& ~) S+ ?) Z) l& ~+ Rpd.date_range('2020-1-1','2020-1-21', freq='10D') # 包含
    0 m5 D$ a2 t0 ~4 ~! g4 WOut[25]: DatetimeIndex(['2020-01-01', '2020-01-11', '2020-01-21'], dtype='datetime64[ns]', freq='10D')* Z2 t2 C0 {8 {, |& o+ @/ U
    % y! k% q8 I, z1 s
    pd.date_range('2020-1-1','2020-2-28', freq='10D')
    5 \6 Y, S5 X- t* [0 a$ AOut[26]:
    ' i4 K; _8 }% W. r3 vDatetimeIndex(['2020-01-01', '2020-01-11', '2020-01-21', '2020-01-31',5 B$ U, k* ~# i) I4 C4 q! J
                   '2020-02-10', '2020-02-20'],4 Q- W9 m2 s: b: l5 t' C: \2 R; W8 B
                  dtype='datetime64[ns]', freq='10D')! t# U5 p& C9 O9 V. v* E! B7 m0 V
    ' Z5 A& \! B* e5 U, B# x$ f
    pd.date_range('2020-1-1',
    2 K+ \8 C& Q5 T7 D; ^              '2020-2-28', periods=6) # 由于结束日期无法取到,freq不为10天
    1 _2 p1 {3 S  ~! F# j
    1 b1 ~  w0 Z7 ]# ]  EOut[27]:
      i1 p8 Q9 r2 o- F+ Z' FDatetimeIndex(['2020-01-01 00:00:00', '2020-01-12 14:24:00',, B; p" _. l( q! g9 t
                   '2020-01-24 04:48:00', '2020-02-04 19:12:00',, M' s" J) e+ P  p0 f* D
                   '2020-02-16 09:36:00', '2020-02-28 00:00:00'],
    4 `% X& {# Z$ }6 l, l- g0 E0 j- S              dtype='datetime64[ns]', freq=None)& C, f" l2 {0 _8 v/ G1 N4 r/ K- f

    , L2 y. u( |3 A: _; n1
    + n$ a3 g) g" G  f) E2
    . H* J1 f/ g7 p; S; D1 a35 @3 s$ Z/ i% W& x7 J) b! H8 w
    4) F# {- s9 @' ?0 N
    5; `! A  u3 S& B0 J4 }5 I4 Y. X9 |0 T! o
    6
    1 g- U, I' y4 Z" L. |8 B) _79 l  R+ t7 v( f  v
    8
      _, Q% D: k0 k& _6 [9
    " `. n, z5 Y4 H105 ~: Q2 A) {! ?. R5 O: P9 A
    11" ^2 a+ h% S8 Y
    12
    & g2 F0 n$ j% W* J/ }139 m5 D  r0 N: T4 S5 I! g6 G
    14  P7 k7 [- b7 N# T  y" e: S
    15, P7 [* ]$ O8 d0 z7 a; f! M) U6 N
    16
    & ]8 W) m% A$ R) U9 q1 \% J17
    ) _* t- K2 X4 z% z  r这里的freq参数与DateOffset对象紧密相关,将在第四节介绍其具体的用法。9 o5 F9 Z/ i3 D: D: F/ o
    4 F  `- T$ F6 B, y
    【练一练】
    1 w: s/ T3 H0 f  |7 OTimestamp上定义了一个value属性,其返回的整数值代表了从1970年1月1日零点到给定时间戳相差的纳秒数,请利用这个属性构造一个随机生成给定日期区间内日期序列的函数。4 A3 u( R. }! t+ I7 C* b

    & E$ n( Q. c* _. G" A8 T. [5 Qls=['2020-01-01','2020-02-20']: O# v! g* w+ F
    def dates(ls,n):
    ) Z( @+ m5 E0 C8 l. w    min=pd.Timestamp(ls[0]).value/10**9& C, _. _' ^: L  I8 }0 r3 B" S+ I7 |
        max=pd.Timestamp(ls[1]).value/10**9
    # _) A- \) N, q7 j' B    times=np.random.randint(min,max+1,n), u+ Z( J) D% S5 I" H4 m' e
        return  pd.to_datetime(times,unit='s')
    , [) y5 ~4 I! n, J( f% [  p! Vdates(ls,10)
    * ^) [/ [8 {' v+ j1 S! m( Q( m$ j# j5 `# S8 h  z/ o) }$ D8 }
    DatetimeIndex(['2020-02-16 09:25:30', '2020-01-29 07:00:04',
    0 O5 ?3 g& k* |               '2020-01-21 12:26:02', '2020-02-08 20:34:08',3 o. [5 n. @3 J- `- G% \
                   '2020-02-15 00:18:33', '2020-02-11 02:18:07',. r# ~* {: z( g  t( u3 G; z
                   '2020-01-12 21:48:59', '2020-01-12 00:39:24',
    ! V+ o% e' ]! }  s$ z               '2020-02-14 20:55:20', '2020-01-26 15:44:13'],7 N, R+ Z9 K7 Z. u% B
                  dtype='datetime64[ns]', freq=None)  B8 P$ I" A1 W7 V* _
    1
      k1 T( p, M" S4 ^2
    ' ]& ?" _" W0 g: U. o# B3
    1 O# j2 j  a% }* f3 K40 e+ ~% B# h1 |- }9 T
    5
    $ _1 T4 U, ^. ?, F5 W1 ~( t6
    * m( T6 s- \/ P" q7$ S% e+ ?# S8 s2 Z8 M) u3 l
    8
    2 E/ n" m% y# f) L4 g$ P9 j0 r9
    6 f/ {' i; \7 e. u  x8 P; H10
    2 ?# |9 _+ |% z6 m! `; s11" Z/ b& }) r* U8 D- S* l
    12
    5 s4 m# d5 V/ T6 ~! |4 E13% ^0 t9 w5 y' Q7 L. [; N2 f! M+ l
    14  m- C( S6 p! F0 g' y0 z4 l2 @
    asfreq:改变序列采样频率的方法,能够根据给定的freq对序列进行类似于reindex的操作:) T2 M: i) e8 L, Q- X7 v
    s = pd.Series(np.random.rand(5),
    / v7 c7 x  P/ P* u7 ~1 i            index=pd.to_datetime([7 j; c' S8 ~) R5 h; T+ X
                    '2020-1-%d'%i for i in range(1,10,2)]))  ]: E! N# s# t
    , m5 e. |; j1 i( D$ b3 ~
    ( ^" X  H* q8 S
    s.head()
    $ E8 z  n+ ]$ o9 _) i9 R0 _+ QOut[29]: ) W! W0 l6 ^. a$ d
    2020-01-01    0.836578
    ; b5 l: p) r# m% \( E2020-01-03    0.678419
    ' p, u3 m. D: E2 ~: a2020-01-05    0.711897% B3 y, W1 U2 g* b# v# N
    2020-01-07    0.487429  J4 v) e" F& r  J- B/ d+ N' b7 b
    2020-01-09    0.604705
    1 c. |- Q- l6 g  r# Hdtype: float64$ A( R- E0 M; o: A) H4 V1 q4 |
    2 Q# G* u8 l3 Q! [* W3 Q. V
    s.asfreq('D').head()
    ) i7 Z7 Y' C. `7 MOut[30]:
    + N4 \5 X& {7 t. d2020-01-01    0.836578
    $ {7 S9 d: s; u% l& z' @8 A8 Q2020-01-02         NaN
    ; [# O) e' A: k* O/ p( [& h2020-01-03    0.678419
    % `7 ?% ], d, z) i: q3 F7 E2020-01-04         NaN3 l: d# \# _+ j6 V! H
    2020-01-05    0.711897
    ) G; N* O" n, M1 a4 K2 @% zFreq: D, dtype: float64. N9 Z: e2 s9 R) r# F1 J( E
    * I  ?9 n- b8 y6 e1 `/ u8 Z( H
    s.asfreq('12H').head()
      }7 v: r( x/ c. [! ]Out[31]:
    * V2 t7 A0 b! p2020-01-01 00:00:00    0.836578: L& ~5 w: \- M7 ~; B5 M
    2020-01-01 12:00:00         NaN# [  i6 u1 B$ o! ~4 ~' U# L
    2020-01-02 00:00:00         NaN
    - T2 q5 T- c9 x% N2 o0 _2020-01-02 12:00:00         NaN) \) l) K% v2 v3 o. v1 T( v
    2020-01-03 00:00:00    0.678419% j4 c; r% A. [  P* d! b) _1 P
    Freq: 12H, dtype: float64; |/ c( ?- l% D# }9 n' L
    & J- @. y3 U4 q0 x5 q
    1; J( W& M: E+ \3 z( p: v% m/ x
    2
    8 R6 \2 x# v/ @) L6 K3
    6 @0 M% r, z' T" b# p& E! u! V4 L4! n. ?0 v7 ]1 |' g
    5
    0 m4 a5 b/ ~6 [' [  l* D6
    $ k$ ^5 @4 K  W6 W7- p( p4 K3 z3 @
    8; H$ N! G3 G( j- F
    9. S2 B+ y2 j- b  H; I" D
    103 B7 U, B7 d' ?
    11! L3 {7 r1 Y) F
    12
    8 k# a( s( \- f" O137 {- Y9 \1 A( c9 p& X2 W
    14
    - C4 q9 w, w2 Q4 G: F151 K  Z$ y3 B6 @* x6 Z
    16! S- L, L6 Z- w
    17$ Z, S3 H" G: L% |
    187 T( c/ Q# U0 [9 F: p
    19
    0 p/ L# M9 x0 u20. i/ {# Y3 q  H) c9 A
    21
    # o8 N; e; ]8 y22
    3 d, O" m3 s" U. g23
      H( i" v; x3 a# Q248 X9 {% e* l- V, W' u
    25
    7 h; @# b; i0 h& x7 w26
    5 [; S& \+ w1 G27
    & i& I' l* H4 E8 X1 o5 q28
    1 N- n9 O! v8 K# U* z- w29
    9 B, U' N/ N: \4 V- Y7 E! S9 f1 D. a4 F30
    ) i! a1 p" z4 f) z; b310 D9 R, _; y, b; @# Q
    【NOTE】datetime64[ns] 序列的极值与均值# S; X4 ^# r# w0 j' B
      前面提到了datetime64[ns]本质上可以理解为一个整数,即从1970年1月1日零点到给定时间戳相差的纳秒数。所以对于一个datetime64[ns]序列,可以使用max, min, mean,来取得最大时间戳、最小时间戳和“平均”时间戳。
    ; y& r* J) y' u% ^7 [3 U+ g- o! K2 d. W/ f2 O3 ?' v
    10.2.3 dt对象6 _; m3 v& H  H+ u
      如同category, string的序列上定义了cat, str来完成分类数据和文本数据的操作,在时序类型的序列上定义了dt对象来完成许多时间序列的相关操作。这里对于datetime64[ns]类型而言,可以大致分为三类操作:取出时间相关的属性、判断时间戳是否满足条件、取整操作。: ?/ |7 K: [$ G; o/ b
    1 T5 F/ D4 v6 S
    第一类操作的常用属性包括:date, time, year, month, day, hour, minute, second, microsecond, nanosecond, dayofweek, dayofyear, weekofyear, daysinmonth, quarter,其中daysinmonth, quarter分别表示该月一共有几天和季度。8 W0 u5 V) G' x" f: t
    s = pd.Series(pd.date_range('2020-1-1','2020-1-3', freq='D'))( @$ H1 Y+ H) S3 H! Z

    3 J- D/ q; w3 H, r9 Ds.dt.date2 r7 f* q- B8 Q2 |, B% T
    Out[33]: 9 w" Z# Z+ n: \
    0    2020-01-011 Z: V/ c. Q+ K6 V4 J+ s2 z
    1    2020-01-02* E! U7 I# ?/ d8 ]8 u- ~
    2    2020-01-031 h, W! m9 w& n& v( u
    dtype: object
    ) [% A: Y' ]& m* q3 a( u2 ~" d6 L+ W+ G0 c
    s.dt.time6 F- F% |1 p+ Y2 E0 _; w' h
    Out[34]: " e# J# @1 V& A: I* i. U
    0    00:00:00
    % l- l2 J! r0 j$ c# X4 x  `1    00:00:00
    " t* M. u" P7 g1 d2    00:00:00
    # u. D5 {3 G; v( Mdtype: object& \& h# ]6 @$ _9 {! Z

    ( q+ B6 z$ u* K5 Z8 Gs.dt.day
    8 \! O$ p+ S4 f& O3 @Out[35]: ! p& q) C; w- w$ ?6 H: i2 K9 }
    0    1
    * S/ ~9 I% ?( v2 |0 X  u1    2
    * X  K3 x5 e  Z8 [) l$ Q2    3
    ' g3 |" z/ t$ [& I. adtype: int64
    ) D, g0 D  g; G2 n9 Y9 E% x6 u  J$ d8 h. g( U9 I2 {1 p( x
    s.dt.daysinmonth& C, V$ y0 D$ Z/ T4 Y
    Out[36]:
    / O3 q$ R1 S5 {$ K  ^0    31' e- s6 `2 B8 J1 R
    1    31( a; m9 i$ R1 s( r1 `  {& O
    2    31
    " K% K1 J, W! p  m, N1 Wdtype: int64) x5 J* F$ C) {7 v+ `4 y8 h* u

    ' ~) R5 O9 u$ N8 O+ b0 e! T& }( B1; F& S+ `/ a) j: M% n6 O8 s- [
    2
    9 o% u+ P4 I# u0 I) x; A/ }2 i3
    / W. E) A1 B. f% g, p+ ~2 c4+ `6 Q7 ^+ X( \8 ?" T  R( J
    5
    + C; b4 {5 ^; c! i5 C0 ^6
    , A3 Z; W' f+ U) G- w4 {7# N: m( b; E) W5 ~% U
    85 T5 a' I3 j9 i6 `
    9/ o! Y5 Y5 M" t/ A
    10
    - a2 n0 E1 \7 p8 {1 w. A118 ?# @8 y# ~; a" H. x; |3 N
    12
    $ {( }* N6 N" s& \8 E- u* l4 P13& i% s/ s: z9 f5 U( S/ Y4 h* d
    14
    ( L' u" u9 |3 r! m15
    4 @! \  J; S% x. Y5 L& V2 P16
    ' Y8 R" F* x* w) {, V' I17% L% {8 D$ \% i. k1 P7 `2 r2 x
    18
    9 M2 Y7 Q& s0 t* Q/ `' w19: r; m0 l* \0 R/ b
    200 I! R, L1 [  c
    21
    9 ^1 f1 Q% H$ B22# C+ D7 Z0 @# C' ?/ ]
    239 S# M' X+ D" u3 S9 i
    24
    $ w0 r6 d3 [' d25
    5 ^2 C9 e+ c6 B$ T% N26
    ; x6 o& e$ e; X; m27) _) e2 x% X. T& E& E+ L3 \
    288 M$ V2 k% t. ?+ _3 |
    29
    - o2 P. o' i% ]# h' |: u9 A  在这些属性中,经常使用的是dayofweek,它返回了周中的星期情况,周一为0、周二为1,以此类推。此外,还可以通过month_name, day_name返回英文的月名和星期名,注意它们是方法而不是属性:# @- z' \. X- ]- \
    + u' D  e3 a4 }2 d8 P
    s.dt.dayofweek" |/ T# E, N0 f6 v- d
    Out[37]:
    . o/ M+ O! ?4 I0    2
    ( }# t: Z8 k& o" n8 r4 q1    3% @" H. X+ e+ g! U
    2    4
    - N& U6 ~* ^- P, b8 [dtype: int641 V) N; r0 G6 i
    9 R" m6 I' j# o' w  u1 k  t
    s.dt.month_name()* j1 Y  @) E$ L
    Out[38]: 5 {/ w" ^) `* d8 G
    0    January/ n$ _5 V8 ?: o% j
    1    January% K6 z3 U$ S4 c2 u6 u& r7 I! S
    2    January
    + c  d6 d  Y: Y- ^0 q: Edtype: object
    5 E) h2 {7 a2 y8 q) Q
    4 b6 |# _) B; U. Os.dt.day_name(): a! \! P# ~  S
    Out[39]:
    ! K2 t  o7 R  _* Z  {) X0    Wednesday) v/ y, x/ O' q: ]+ y
    1     Thursday8 S( H4 Z- n3 U2 j, L' \
    2       Friday; m' b- z, q# m+ _( J2 }
    dtype: object
    & X& L+ i0 b: J7 [: q- q
    " z6 T3 w" A% Q/ b1: a) K# ?6 O& Z4 P0 _- M6 p
    29 |2 F9 L3 ]1 j
    3
    6 }* n- h  y" I" ]; E4
    - ~5 ~) o" M/ r# y. C8 K- L5" Y) O( E, ?( }2 I! ]  C
    6/ I$ w$ j( A5 \
    7
    ' [$ z9 ~7 F2 ~, U: ^5 Y* ~8
    $ Y5 w4 [% v4 Z9
    % ~$ u! l( a+ E2 v) [10; g, k8 W# F' D
    11
    : x8 T/ P" ~8 Z) |- R9 g; ?" T12
    3 f% K5 `+ E# D/ c13
    ) P* S" Q, P3 h  L0 R14/ B' n% x' j! X- [7 V* P" o
    15
    , A, `! b1 b) ^16
    ) u* A$ l# j4 ?178 J+ M0 B. I6 u8 ^3 p4 j; q+ C9 Z
    18& a8 b. M  p1 F4 R/ |" b
    19+ p0 W* i6 _' l! ]
    20
    ( @4 s( r8 |8 h. b" m- @第二类判断操作主要用于测试是否为月/季/年的第一天或者最后一天:# D5 O3 ~/ a# O
    s.dt.is_year_start # 还可选 is_quarter/month_start( @4 n! n0 P/ O; }% ]' |
    Out[40]:
    / s- s, p% O' U$ ^3 P* O# k0     True
    8 F& O9 w9 x6 f& F& y4 A4 f6 a4 h1    False
    * x% L1 g5 M- J6 u, K5 O2    False( M0 S' r; b4 S  a/ D8 a! A
    dtype: bool
    ) x! h" [, K  y
    4 t4 t- a- h" _3 rs.dt.is_year_end # 还可选 is_quarter/month_end# u1 m9 D2 h2 T1 D" Q, X
    Out[41]:
    5 u" O& s5 ^* u# Z- H0    False
    . B/ I* v! Y- f8 h1    False
    ) g, A8 X( T/ z% z  J& P2    False
    " Q3 i+ A  N3 `' X5 T$ Y9 {dtype: bool( |1 q6 Y- ^$ [3 q* x
    1; Q! q' {6 b( k5 l0 _9 z
    2
    : m1 m4 _* c- ^8 x' Y3/ j4 I- ^" T: D& q
    4
    , R: A: S7 ]( v# F  R: T3 y5$ G1 i# F4 B1 z3 F- L
    6
    , F- I" W" Y: V  e7
    ) T% k6 ^* g8 Q5 l4 Z" e8 b8
    8 G: v) U; r, P$ y$ I- i+ K) i; ?, {9
    6 R2 x# O1 z9 E5 O5 b& m- U10
    8 G+ M* V- W! ~5 e11
    ' F  R. U+ ]9 o( @: E121 @, H% \' S0 V5 ^3 \
    132 f& X0 Y1 s, J: m
    第三类的取整操作包含round, ceil, floor,它们的公共参数为freq,常用的包括H, min, S(小时、分钟、秒),所有可选的freq可参考此处。+ g! r( x0 r) A8 z! c
    s = pd.Series(pd.date_range('2020-1-1 20:35:00',
    ' `, M: K7 L8 K6 D3 T                            '2020-1-1 22:35:00',
    5 {8 C$ U3 V1 ?. i                            freq='45min'))1 D! T2 R! d9 G7 f
      l1 I) A& ]/ E! n! r0 e
    2 J7 d# P; T0 [# s+ u/ R4 l
    s
    ! T+ p3 y" g* o% i5 Y5 a* p1 ^Out[43]:
    $ V6 o0 ]  {" J! `* ]0   2020-01-01 20:35:00
      B. j+ U0 F* u7 E$ e+ U1   2020-01-01 21:20:00
    3 Z0 C) T: Q$ C2 ~: y- l/ a* R2   2020-01-01 22:05:00
    ' p& y% Z* B! F) O+ sdtype: datetime64[ns]
    7 h- \6 i5 p6 R; X' |+ n5 z$ L0 S3 t% e/ ^& {. ^
    s.dt.round('1H'), ^9 \8 ^! g6 J/ q2 O; s8 @
    Out[44]: : b& O% t* \1 R6 t6 ?
    0   2020-01-01 21:00:004 t8 T. j) T6 X* N  g
    1   2020-01-01 21:00:00: Q3 J/ a5 M9 c
    2   2020-01-01 22:00:00
    7 S- G( h' V' ]% t2 _  I% Sdtype: datetime64[ns]
    6 E" {. W( X9 ]9 z1 J- E/ ]" D  P* T5 `
    s.dt.ceil('1H')# Y( T8 G/ J" H1 a
    Out[45]:
    # e# V! ~  x+ l' o0   2020-01-01 21:00:00
    # Z* p( f, K6 m7 B  |( K2 z' v1   2020-01-01 22:00:001 G  K+ ~- o% K) d) ]
    2   2020-01-01 23:00:003 ?4 b: O7 h' V
    dtype: datetime64[ns]6 M7 a; `3 h* L

    ( E3 u5 k9 h( qs.dt.floor('1H')
      f2 x/ X' s+ hOut[46]:
    # a. }$ F, ^9 R6 C5 k" l/ H8 K- l0   2020-01-01 20:00:00( s6 ?% o, f$ @, a
    1   2020-01-01 21:00:00; L% |2 [! y" T1 k
    2   2020-01-01 22:00:00( K2 P" V( ?& ?# b
    dtype: datetime64[ns]
      t; ?. J5 S8 d/ S
      t( }3 A8 V& w$ k& p& B1$ |3 h$ _+ e- _/ B: R8 j# |
    28 R8 B, P* B' Y7 c# i
    3
    + B( o* a% ?" d) D4
    ! m$ O, U; b* A; Q7 M50 s" ~# z4 t( |7 L( R% B
    61 c- V4 r' b; i/ h( k
    7
    - S0 O8 o6 A' O5 y86 m  K4 k% I. X9 Q, W
    9
    / d) o; _& |* P) g107 I' Q; f8 P  @& ^7 w( J
    110 p7 O4 B8 l! M: W9 R9 S# w# F( G
    12# T0 _+ w$ d8 N) W/ t
    13, r, }5 `$ V4 k) p- X0 w# o; I" g
    140 j: V: z3 |/ d5 b/ b
    15- e$ G  ~# o( n, D9 v+ e
    16* k! _! ]- O1 y0 v
    17: l& f; l- Y: r' c' J& i. i
    18' [6 [5 T! w! K; j" ^, ~  Y4 e' v
    19
    * m  _9 y0 H3 `8 Z% Y20
    2 B" I$ @6 W$ U% i8 [( I' v% ?1 S) z+ d21
    . \9 j8 {8 v9 _# \224 h7 r- Z( G' }: Q( ]
    23
    ! `. C; R: s: j1 g8 N! p24
    " a/ Q, N9 l1 z, u: K25
    $ d) J1 X3 ?6 T1 H; v/ j" z26. N& V( m/ ]9 b& R: P, N; N
    271 b9 M: H8 F- I" h9 o
    28  Q( t2 b9 B( P
    29* z; @9 u- N/ i  `9 T" r1 {4 D& a3 C
    30" O: `( @* Y7 L% @  ~
    31
    + [: M7 C- Y& r2 `5 u32
    ! w8 v3 ~* G8 [9 ]10.2.4 时间戳的切片与索引
    7 B5 }- b& _8 @0 u; X! t. q2 I2 j  一般而言,时间戳序列作为索引使用。如果想要选出某个子时间戳序列,有两种方法:
    6 r0 {7 \, I- M" p6 U) x8 Z
    + b/ G) {1 S4 w  C( ?. ]6 n: \5 e利用dt对象和布尔条件联合使用0 h9 l, e6 E5 B% w; }8 l; _
    利用切片,后者常用于连续时间戳。% o' g4 n( r' q7 z  A+ K
    s = pd.Series(np.random.randint(2,size=366), index=pd.date_range('2020-01-01','2020-12-31'))' g) @5 N% q- o: A1 g* ]+ P$ n
    idx = pd.Series(s.index).dt* m7 t8 u3 r: x
    s.head()- J; U- S  o, Z" E( e& t

    1 i: ~) I% Y; p6 e" {0 ~0 S  l- g2020-01-01    0
    4 G3 E; Q* r( K/ u. ^2020-01-02    1+ F4 }! S& v8 S- E  q, o
    2020-01-03    1& O3 E% D% o( R* C' w" H
    2020-01-04    0
    ' O% e0 p* C: R  l2020-01-05    04 G' M! l; ~/ n3 W) ]0 B+ [+ d) d
    Freq: D, dtype: int32! C. z& b# A5 h% X, v6 [
    1
    4 p8 B7 I: g$ K2
    & k9 m' n: O- q; o) V3
    6 d. _* n& M3 ?; h* h+ Q4
    0 J2 p$ l/ _: C  e9 R5
    3 p. [) y9 M9 e6
    ( n6 _3 I" [' |- ~& f* q4 I5 G7
    6 D( F% O8 T# [1 r) k( d: ^3 C# X8; J: B/ [% `3 U2 {: W9 N1 w
    91 m  r: ]7 X0 e) K" w
    106 r6 X- }" Z2 [  }
    Example1:每月的第一天或者最后一天
    8 Q& e$ t- o# m" o" {
    - b0 s* ]8 Q, ~9 r+ n% ns[(idx.is_month_start|idx.is_month_end).values].head() # 必须要写.values# |# N. H9 N) E/ m2 O
    Out[50]:
    " R7 A% H( V! u8 I0 Z/ n2020-01-01    1
    $ {5 {, {. N! Q2020-01-31    07 S) R+ s( I" n" j( G4 `- l$ c
    2020-02-01    15 a  p# S8 I# s! d* B5 E
    2020-02-29    1  S: S4 a- U6 H. D$ z5 T1 ?
    2020-03-01    0
    & G; T+ s) D0 }dtype: int321 V6 x1 t. o6 J7 y6 ]) l
    1# o. S) d; Q% q- Z
    2
    6 j: v2 J* H5 T3 l3
    4 K0 B/ }: @6 P4
    - ?& m4 z4 L1 M4 E, p! D5* K4 e6 q( I1 l" u5 P& D( u
    6
    5 i) ?' u' L8 q2 `% H6 _, g7
    * K& u( w9 F7 d* {5 J( D6 k8+ S" ^. Q+ Y$ |* V4 S$ T$ V
    Example2:双休日
    ! q- n! @; a7 l2 R/ r/ n" M: ^% M& e$ J( U0 e7 G
    s[idx.dayofweek.isin([5,6]).values].head()0 ?& d2 F; ~' c" {
    Out[51]: 1 q' j% m; V* r5 I6 ]
    2020-01-04    1
    % o. ^; d5 G4 g3 ]5 F; h2 j$ Q- }2020-01-05    0
    - l9 F( t# t0 x7 b" r2020-01-11    0
    % H8 ~# B4 j' l1 l4 M2020-01-12    1
    % T0 {( ^9 b$ Y, s- m  q2020-01-18    10 y: t4 q' L4 K* x3 ]: M% ]9 R
    dtype: int32! o$ ~# d! X8 n% `
    17 |3 Z0 o0 T1 ]
    2; |7 d" I" Z. S5 ?; I0 Q1 }6 j
    3" m9 h4 x! a% A( Y! e( J' g1 l
    4) a- R- I4 \* f0 w% @6 k- v: M
    5
    6 F( g# {) y! r/ B  P2 f; q6
    $ z6 n& E7 _! Q5 f1 ^* z& z71 u- F# l) E" I
    8: ^- c6 D4 B: F& T
    Example3:取出单日值
    ' t# J- \7 G5 @, A# t) ]2 Q3 H6 A- u, V
    s['2020-01-01']
    1 U/ u' J# }5 q4 K- ~, ZOut[52]: 1
    ) c7 O' G5 u9 g- F6 O6 C& s: c  ^- y$ i
    s['20200101'] # 自动转换标准格式1 `& C, u5 l0 {- f. ^
    Out[53]: 1
    3 A6 ~- `* m+ Y) O1 m$ I( A: n+ F10 \# B0 w! s! }- e* d5 K4 O
    2
    4 w3 }* o% R3 k% u: D6 [9 r/ }3+ k4 [2 u2 O5 N* ~
    4
    % N+ ~6 \# `9 s5
    - K5 C6 a3 q; Y# k3 Z8 i7 NExample4:取出七月) j( P. Q7 W7 _- R$ v! d$ p! D, g; ?
    ! Q4 o& L: v5 v  D) e
    s['2020-07'].head()1 F$ }6 c( m0 Z9 b# A7 B7 I- H
    Out[54]: 7 D8 f/ H5 D- M% s" v" }& \) I
    2020-07-01    01 k6 }5 b5 B$ z4 v7 c4 @; A) X+ J- l  G" v
    2020-07-02    1: B* m7 l! j1 t/ z
    2020-07-03    0& U  z' e' z# w/ P2 k- F
    2020-07-04    0
    7 ]2 |+ V; j) J# e2020-07-05    0
    3 l: U7 D; o; H) O' vFreq: D, dtype: int32
    - m6 Q! `* f6 p. r1: P! Z: O* H- y5 l3 s
    2
    2 k4 J. U$ m+ n. F: K  F: j; K* d3
    ! R. g& h; ?9 V( P6 }$ h" h47 \8 e# W5 X: Y& M; W
    51 p6 K5 M5 `' Y9 ]% s, A- ?
    6
    5 X  |+ _# [$ m$ x0 S. M: l- i7* ?# D7 V; X1 T$ K/ @
    8' D$ T8 x5 [9 e0 d; Z7 g
    Example5:取出5月初至7月15日. i; g! m; ?9 @; ^( H% U! g

    8 y! \4 Z1 N8 {6 ~3 h  es['2020-05':'2020-7-15'].head()
    1 P% W" _' w/ L6 |( L$ s- b2 VOut[55]: : C  c; x2 Z1 @, a3 P0 Z9 g, y
    2020-05-01    0
    * T. k. p2 O& ^2020-05-02    1
    + ~# u, x7 P/ v& Q( m2020-05-03    0( }& E9 ?. E6 _$ q2 `. x
    2020-05-04    1
    ; o! a9 @" c, e& q5 @3 ?7 Z* x2020-05-05    10 i* e$ S) B- D* g: X' f
    Freq: D, dtype: int32
    & G1 F% R" d6 X2 N" P7 B7 ]
    6 k1 k1 g1 o# Y! R  l0 ts['2020-05':'2020-7-15'].tail()
    + o) t; z8 j; a: a9 e. L4 ROut[56]: % C1 B9 s6 U9 j2 Y( X3 s% \1 k4 X
    2020-07-11    0
    4 l6 E5 l+ E& K0 z& r2020-07-12    0
    9 Q4 d2 `$ R4 l/ z% f7 X; s2020-07-13    1
    $ G- t2 S1 }& I# C/ Y2020-07-14    0, S9 C$ h6 z- K- c: J7 t
    2020-07-15    1
    5 ?* R( E- |9 ^) V# a* u1 FFreq: D, dtype: int32" H, F3 B7 Y. p
    ! W# E( R, Y* G  T+ E
    1
    9 x7 m! S2 O& S29 Y, z4 U/ o% e* V2 ~3 B7 R
    3) Z6 K4 F6 s# Q5 y
    4
    . m6 s+ i4 w4 \: C1 O  `5/ d5 ?. A3 v+ i* P5 j8 g
    67 d1 m) A1 L; h" L9 P- w
    78 Q5 ?: j  i- t0 u0 R
    8
    5 G8 d0 U- g! ]9 u5 D5 ]9
    , {# Z/ Q; x6 |0 \8 j! Q) p2 V10% y2 D+ L) z8 N: l
    11% j) z: x0 ?( Q1 z% t" H. |: H
    12
    ' s5 u' L8 n- M$ A  N2 f13
    1 s% w) y" G8 \9 H14
    ) I/ E' m) g, L( {/ V155 o. q; v7 F# i, a$ ]# w2 C( [
    160 Q$ }" O1 b1 c% ^( N
    17) W: L/ D* _) a% f
    10.3 时间差( l& I( U4 `& [
    10.3.1 Timedelta的生成
    + Z- A5 _; S" H: Kpandas.Timedelta(value=<object object>, unit=None, **kwargs)
    + v2 Q/ w! c% ?4 H  unit:字符串格式,默认 ‘ns’。如果输入是整数,则表示输入的单位。
    7 ?$ Z( l# _. |1 \) r  可能的值有:
    , @/ [9 q3 L4 y# U
    ( `) U+ [* F+ k. p- g‘W’, ‘D’, ‘T’, ‘S’, ‘L’, ‘U’, or ‘N’0 I+ q$ _: q2 u0 @- E2 G$ s
    ‘days’ or ‘day’5 r; v+ P" F: A! d
    ‘hours’, ‘hour’, ‘hr’, or ‘h’& d6 s! T1 B  C# w* c
    ‘minutes’, ‘minute’, ‘min’, or ‘m’
    ( J% |9 d6 B2 R' ]‘seconds’, ‘second’, or ‘sec’
    ( y8 H4 U: D/ t) Z5 l  k毫秒‘milliseconds’, ‘millisecond’, ‘millis’, or ‘milli’
    5 ]+ b# O, C5 m; @3 K' R微秒‘microseconds’, ‘microsecond’, ‘micros’, or ‘micro’8 l) ~) q) p7 [: I" f
    纳秒 ‘nanoseconds’, ‘nanosecond’, ‘nanos’, ‘nano’, or ‘ns’.
    $ k2 [; r  W. D3 Q1 S时间差可以理解为两个时间戳的差,可以通过pd.Timedelta来构造:
    # J0 x$ S3 L! J9 v9 G5 ppd.Timestamp('20200102 08:00:00')-pd.Timestamp('20200101 07:35:00')
    " D( ^; T- d2 |  a! u: eOut[57]: Timedelta('1 days 00:25:00')2 }* m$ ]: a( v7 l
    $ ?0 F, O# b: Z/ ]8 J
    pd.Timedelta(days=1, minutes=25) # 需要注意加s% ^) m! j# q( }2 F( G& o- W: s
    Out[58]: Timedelta('1 days 00:25:00')" d7 j3 ^! z3 B& s3 Y  E
    ( m" O4 G  N, a" d8 p3 ]
    pd.Timedelta('1 days 25 minutes') # 字符串生成7 P- `. S6 c9 i4 \" s9 ]
    Out[59]: Timedelta('1 days 00:25:00')" s  q4 O& A1 ]# P" g" _9 J
    , I; @. d% N, X) O' y9 C- A
    pd.Timedelta(1, "d")2 r4 ^, Y( |. A" A6 i
    Out[58]: Timedelta('1 days 00:00:00')
    ; e7 j  \1 n+ R9 q7 j' J6 `# m1
      l" m3 c, Y8 P+ K3 D2. T6 a* x! V, L( h
    31 M4 {7 Q. ]; C! b. s$ A# H
    4
    7 B/ l  Y- G5 e5 Y7 H3 Q5 q  a5( U3 I6 c; I1 }' S/ K+ N4 V* r
    6, Z( V3 s! }! L
    7
    8 n0 ~; M/ k' o: j4 b* a/ @80 {$ A7 ~9 q/ X; _
    9, [" p( ~5 X" w4 I) \' k# X. g
    10  J; E# r  `% }9 w' \  a8 C
    11
    * W* n  u: \2 c3 Q7 L3 A生成时间差序列的主要方式是 pd.to_timedelta ,其类型为 timedelta64[ns] :
    9 r: [5 H) Z) z5 p( E/ j* _" E6 vs = pd.to_timedelta(df.Time_Record)
    + f# x5 |3 @2 d0 j  \! {' Y3 @
    % e1 {3 D) \% u) Xs.head()
    0 N* _4 Y( s* V$ S( z% eOut[61]: 8 C) r  f9 f. @  d5 T$ h- V+ k8 P
    0   0 days 00:04:34; p, v. W/ Z0 ?3 j
    1   0 days 00:04:20
    + S) K6 b1 C! P& `: o0 O/ S0 V2   0 days 00:05:22
    $ z4 K6 G1 [: ~/ Y% `3   0 days 00:04:08  k- C' M, \- @" R
    4   0 days 00:05:22
    7 Z6 s9 Y, V3 qName: Time_Record, dtype: timedelta64[ns]- t: q- _5 Z% o  S/ S, W
    1
    0 M+ V9 H  }4 |. p2
    ! n8 B' o( W1 E7 f3
    6 L% H" `5 e4 ~$ l, ]4 Z4- l) O$ ]6 M7 j2 N% T% w
    5
    * h% I( u) o* m) B6: w% c! n, E2 f9 f
    7
      ?  p; _& z2 ^2 q8 g" L8
    ; K$ U) R4 v; d( x9; @: g1 ~% t2 z% B
    10
      j! k- x+ l$ J+ z% ~4 z与date_range一样,时间差序列也可以用timedelta_range来生成,它们两者具有一致的参数:
    + S2 Z6 z! A) M4 r; N9 }pd.timedelta_range('0s', '1000s', freq='6min')" ]0 X9 Y% {7 q7 _; o" i8 u
    Out[62]: TimedeltaIndex(['0 days 00:00:00', '0 days 00:06:00', '0 days 00:12:00'], dtype='timedelta64[ns]', freq='6T')4 m, Z( [  R5 m' X$ o% o
    % s$ a' f" C% k) Y
    pd.timedelta_range('0s', '1000s', periods=3)
    & \/ Z: M# D4 k0 U) I, rOut[63]: TimedeltaIndex(['0 days 00:00:00', '0 days 00:08:20', '0 days 00:16:40'], dtype='timedelta64[ns]', freq=None)
    0 V1 i/ U/ j; w# q( p3 ~/ B1 O1( Q/ N! |1 Y: h) r
    2
    . a( w7 J% m' F  F; w; E3, Q" p$ M/ ?7 v+ k# ]& T
    4+ s$ R  J* E5 m7 R8 b* W
    5
    $ U9 d+ L& b9 x6 C# e对于Timedelta序列,同样也定义了dt对象,上面主要定义了的属性包括days, seconds, mircroseconds(毫秒), nanoseconds(纳秒),它们分别返回了对应的时间差特征。需要注意的是,这里的seconds不是指单纯的秒,而是对天数取余后剩余的秒数:
    2 u- a2 \$ b8 M8 d! H; f1 P! {s.dt.seconds.head()
    * M: `$ P+ v8 @' g0 f6 M+ nOut[64]:
    * d+ p9 c2 C% P# `0    274
    0 X! X/ t! K& d% @5 R& Q1    2603 h% U% H. c: F; ?6 h  G
    2    322
    ' D; g- n# U5 l% L. o- v6 V3    248: E4 ?0 T: q0 a" s6 }" d
    4    3222 L5 a6 z4 e3 v3 {* a6 y
    Name: Time_Record, dtype: int643 ], ]+ T& c' S& J2 _! Y% b
    1
    % }5 n/ }! ]+ z8 m  d( D9 C2  x1 ?' R/ d' K
    3
    ; o$ ^% J9 S  z4 _6 L4
    2 q+ v3 s0 E4 S; w6 b5
    , V9 S- V/ T$ l" Q( x3 ?* C% Z68 |; i! [. U6 f) G- D
    7
    + ?0 W7 y/ d& V! K8
    : J5 y9 l# y+ y8 c$ ^$ I如果不想对天数取余而直接对应秒数,可以使用total_seconds
    / D  `0 A( x5 b* y- M: b3 D" k
    s.dt.total_seconds().head()
    - l/ X) m3 ?7 ?; A9 QOut[65]:
    % n/ T0 `. Y0 A2 U" U/ C9 c$ j0    274.0
    0 h& @7 i$ h7 Q+ t/ H, x1    260.0
    4 H& s2 u" H  e8 M; u) W2 I$ z2    322.0/ Q3 |$ z( |& G$ F
    3    248.0
      x8 ^9 G  e- t8 f9 L4    322.0, t& g/ B% i; b) _/ g- ^: ^
    Name: Time_Record, dtype: float64) I5 o4 @1 ~: f( x# i/ o' k
    13 C. ^1 c( P8 C3 G
    2
    2 R& A  X3 |" e  [7 X! E3
    , ?' r' [0 ]2 c% E: M' W48 f4 w) r) B: O* O) _+ ~6 v  x
    5) m1 o3 k* x* X. y
    6
    " X$ _8 L! w. _( z. }: J2 L7
    3 |9 P& U4 {6 O2 J, |2 Y" G5 o$ |84 Q! ~, _) X" `# U9 x4 _; C
    与时间戳序列类似,取整函数也是可以在dt对象上使用的:
    * \: l, m* w( w& W/ v2 u# `0 N: J( x+ A* E
    pd.to_timedelta(df.Time_Record).dt.round('min').head()/ p" E! o9 K, A1 ~- i
    Out[66]:
    , e) L) W! w& b) R* n% _0   0 days 00:05:00: B$ r( {3 q. `3 `: r
    1   0 days 00:04:00
    ; ^" \- @- d1 ^7 z& c. J1 E2   0 days 00:05:00$ S: _/ n7 a" z( X5 Z: b8 ?
    3   0 days 00:04:00% f4 d7 }9 P1 o( ]! @
    4   0 days 00:05:00
      W+ U% {, u/ c3 V& aName: Time_Record, dtype: timedelta64[ns]7 J/ a" q. H- E+ Q
    1% o" y' @6 |& r: r, b! E8 _
    2
    " K2 t. {  W; o+ Q3
    % \& e( b6 H/ R46 X6 v# @! S8 H
    5
    3 x8 [& Z( n: K- W) j3 x6
    4 y+ a" @* F1 {3 w  A70 d: I0 q6 z8 h1 y$ D6 E4 _
    8
    / p/ _" U8 w3 G* U10.2.2 Timedelta的运算
    3 y) h2 _- b6 X  I! p# I, U单个时间差的常用运算,有三类:与标量的乘法运算、与时间戳的加减法运算、与时间差的加减法与除法运算:
    + C9 d+ y( K+ r4 t0 Ztd1 = pd.Timedelta(days=1)
    + z6 G4 M7 O- u4 ^7 ]5 P& X5 gtd2 = pd.Timedelta(days=3)
    1 |* d% d" x$ K3 t" S* R  Gts = pd.Timestamp('20200101')% Q! d9 U# }! A# Z3 e3 P* ^0 y

    7 @* Y0 D" z& Q9 N1 C% B6 [td1 * 2* c  _4 l' Y# I$ B5 A3 \1 D8 G. ^- Y
    Out[70]: Timedelta('2 days 00:00:00'), r! f, F% d) ^& M2 N2 m

    6 S3 {  w* b( @% p! F; ~& c' Gtd2 - td1
    : ^0 {% F  B3 FOut[71]: Timedelta('2 days 00:00:00')! {% @/ q( g8 [; W* m

    * Y/ A. _5 @8 B$ Uts + td1
    3 f6 @. j: ~+ nOut[72]: Timestamp('2020-01-02 00:00:00')
      I3 ~1 f5 e- g7 K
    6 c4 |8 J9 d2 a4 m+ Zts - td1' r$ Y& ^! b) y: @! n! m* h; D& m
    Out[73]: Timestamp('2019-12-31 00:00:00')" q6 \4 v8 K- |5 p' X" W
    18 B% d: G+ k" I
    2
    . R( T9 G# ]% R+ _7 |1 J! s1 B3' R: \0 g; e: \. T$ U
    40 N0 a6 I% O! j8 Z: z
    5
    $ n' G# E0 |& K( k: Q4 N7 D3 S' J6+ T% E7 [! |) s& X* t3 ?  `
    74 q" t. h  E5 J4 F5 b5 }
    8
    # \7 V5 H8 ]) o  C  Z  a3 i6 u. p) O98 o6 O8 h- b5 l5 t( a+ ]
    10
    . @( c/ R  s4 R( x+ F11
    $ Z6 K3 X4 Q& a- J) w12
    2 U+ R) X. D% q8 x" |13
    9 A( V3 f9 l5 H; T7 Z14/ t8 f- k. V  {  r- F4 T
    15
    2 ~6 c* V5 z0 [# s: w时间差的序列的运算,和上面方法相同:
    9 x& U# ~- Y. R8 ?: C  btd1 = pd.timedelta_range(start='1 days', periods=5)5 x+ t% {( a7 m8 O
    td2 = pd.timedelta_range(start='12 hours',
    , w( w' Z) N" Q7 {2 N# D                         freq='2H',  a" b9 F; K! D* L, r
                             periods=5)
    . p) p. N( e; Y5 L' L9 A+ W% Q2 \, n, Kts = pd.date_range('20200101', '20200105')- f4 A( T5 C5 \; k) A0 t
    td1,td2,ts( [5 `; S& v* S  |' b( O( V. [( C
    * V9 d, k  P& q: |# J# G9 H, j- G8 a
    TimedeltaIndex(['1 days', '2 days', '3 days', '4 days', '5 days'], dtype='timedelta64[ns]', freq='D')
    0 `! n! y/ m# S$ ]! P1 S$ {3 L6 tTimedeltaIndex(['0 days 12:00:00', '0 days 14:00:00', '0 days 16:00:00',
    % [' s1 f7 f9 ?8 n# b                '0 days 18:00:00', '0 days 20:00:00'], dtype='timedelta64[ns]', freq='2H')
    0 \$ _% z  D& U, N$ K: Z$ SDatetimeIndex(['2020-01-01', '2020-01-02', '2020-01-03', '2020-01-04',% b, I/ E( L/ ]$ \* e
                   '2020-01-05'],
    ; z! O- H3 w  ~3 ^              dtype='datetime64[ns]', freq='D')& p( A7 k! O& I
    1
    * X; e# n3 u: a2
    2 `" P% a/ o4 I: r3 {5 O1 K3
    # p- U& U5 y& ^) ?' G+ N4  V) t! m; ~; S2 F5 h( N, {
    5: Q  W& n9 h7 m- U' c
    6* b4 L" ?& r2 c3 _* f. l8 Y
    70 v8 k* i  G/ D' C0 L
    8
    . \6 ~. i3 N+ l0 e91 ~1 k0 ]$ m/ v, ^
    10
    ; Y. K  i! T" `4 o: J9 C' [11
    4 O! E& `, g5 A12
    1 A! N1 g5 {4 H" F7 Y9 t; }) D13
    ! j; t+ R- F1 a: ktd1 * 5
      j9 j8 L1 B$ X* I0 `Out[77]: TimedeltaIndex(['5 days', '10 days', '15 days', '20 days', '25 days'], dtype='timedelta64[ns]', freq='5D')
    . p) Z2 i- n& ^5 W9 r2 j
    & F& k- [5 c! y$ O' H- Utd1 * pd.Series(list(range(5))) # 逐个相乘$ g, p4 B' E# f* R
    Out[78]: - y- z9 o  \1 Z
    0    0 days
    : J+ S0 o6 \& u* W: Q) g2 l4 n9 X1    2 days
    7 ~8 l4 `1 F/ L  x- i# S$ a2 D2    6 days
    ' w6 V5 M  @$ R5 t9 U8 S* }$ U! x, O3   12 days6 P$ C, T; u7 K2 S, G" z* y
    4   20 days
    8 c3 T- C& G. c. T0 Tdtype: timedelta64[ns]
    6 r: o. `: T: ?7 _% N" _7 S# v9 K$ x! B7 v
    td1 - td2
    ) h- k. F8 D, m  w7 Q1 {Out[79]:
    ( g" B; l; @1 X2 y8 ]6 ATimedeltaIndex(['0 days 12:00:00', '1 days 10:00:00', '2 days 08:00:00',3 g, E) d! n3 \! x
                    '3 days 06:00:00', '4 days 04:00:00'],5 R  h0 \7 d" l0 L! Q/ v/ I
                   dtype='timedelta64[ns]', freq=None)
    1 f0 o. y9 ~5 q6 M$ l  U, s5 i* ~3 N( Z/ _
    td1 + pd.Timestamp('20200101')3 ~+ ^3 C7 ~2 U2 E+ g
    Out[80]: : b0 _1 k7 U, x/ M# z
    DatetimeIndex(['2020-01-02', '2020-01-03', '2020-01-04', '2020-01-05',% C. f+ }9 K$ N/ `
                   '2020-01-06'],dtype='datetime64[ns]', freq='D')$ I6 ]2 M5 s# T2 o# o) a2 t+ s
    1 e; l7 z# x; P. d
    td1 + ts # 逐个相加: g& J! Q0 w1 k2 J
    Out[81]: $ u8 C" w  G0 K
    DatetimeIndex(['2020-01-02', '2020-01-04', '2020-01-06', '2020-01-08'," b- R+ O7 R7 i# g" q, i' Y
                   '2020-01-10']," K. z" [  ^. v6 c. E; B, U$ e
                  dtype='datetime64[ns]', freq=None)" J2 L6 m8 A- p" r. f6 y

    " C4 G# H3 v, T0 X4 y: I1
    - _8 F8 {1 S# r" Z$ L; P) p22 |3 @; ~1 R- i% ?. g
    3; h, k: o$ d1 S9 Q! Z. ^% e
    49 G( ~2 \$ C3 A- N3 R) o$ j% p
    5
    3 h+ d  r9 u  c! \3 j6
    2 h# g& `. w- k  q7
    * K4 {& N9 B$ K- @! L  H: b8' ^2 i0 Z7 l" _7 u
    9, w* b) N" _+ v/ A3 R: ]
    10
    1 K1 l5 e" b. ?118 \, d" n0 T% d1 z" k
    12
    , p) _2 Z, d* t' u. |/ }8 N13
    + A3 r  w  Q/ X  L& @14: ~2 Q8 p- z- A) |7 n9 S* M
    15
    ( n8 ^% v4 q( R1 b" y; m1 C16
    ( [* M* {- j% M8 f* ^; T5 Q17' g2 c  g: X( m+ E/ x4 Z+ B" ^4 M+ U
    18
    " b; s; ?) n. C3 [, J19
    6 _( Z1 N! o& D. W8 c. z20
    1 c! h% y; ^- u21
    8 K6 G( J: P2 S22+ g# N. w5 s* D3 J1 m- p
    23
    * Q, |, j* v; [4 Q24
    6 q  }3 Q+ M7 I9 h3 ]259 ~9 M; B9 M& P4 t
    268 o) Z* w! e! J1 O1 x4 z
    27" {1 H1 C' O5 V+ V6 c5 C4 E( e
    28; u# E) ]! d) k8 T. T
    10.4 日期偏置3 ]! |3 t1 [" Y% r) o
    10.4.1 Offset对象
    " s4 |& l+ a+ l: ], C( ^! Q3 ?  日期偏置是一种和日历相关的特殊时间差,例如回到第一节中的两个问题:如何求2020年9月第一个周一的日期,以及如何求2020年9月7日后的第30个工作日是哪一天。
    ' Q. \- h! q3 Q7 I# f6 \; T
    5 y& W' X( \7 j9 XDateOffset 类有10个属性,假设s=pd.offsets.WeekOfMonth(week=0,weekday=0),则:. w  t5 t! O1 w/ ^  f
    8 T& {1 w) {" x- |. c$ [. t/ v9 A
    s.base:<WeekOfMonth: week=0, weekday=0>,返回 n=1 且所有其他属性一样的副本! R5 _6 Z) k/ B+ m
    s.kwds:{‘week’: 0, ‘weekday’: 0}1 y% A5 q! b8 O7 W$ j: Q2 R
    s.wek/s.weekday:顾名思义
    & c9 L% h8 }2 q有14个方法,包括:
    2 D) ?2 K. W0 ^3 i$ |
    3 `. w' E! C+ G8 n4 g# TDateOffset.is_month_start、DateOffset.is_month_end、DateOffset.is_quarter_start、DateOffset.is_quarter_end、DateOffset.is_year_start、DateOffset.is_year_end等等。
    % I4 ?& W6 H$ q3 @pandas.tseries.offsets.WeekOfMonth(week,weekday):描述每月的日期,例如“每月第二周的星期二”。  m! ~; K  T$ ~/ o3 z

    " H; B: X7 @1 R; C$ o; s3 `$ h有两个参数:
    - m/ W' _0 a3 g7 ?week:整型,表示一个月的第几周。例如 0 是一个月的第 1 周,1 是第 2 周,以此类推。7 \1 |5 h; ?3 |7 B1 Q
    weekday:整型,取值为[0,1,…6],表示周一到周日,默认取值为0(星期一)5 G8 x; M2 n1 w2 J5 o" U
    pandas.tseries.offsets.BusinessDay(n):相当于pd.offsets.BDay(n),DateOffset 子类,表示可能的 n 个工作日。
    " i% z& b3 D/ N0 f2 w. J0 S7 M/ O+ @( n6 _
    pd.Timestamp('20200831') + pd.offsets.WeekOfMonth(week=0,weekday=0)
    * i6 R$ Z5 u7 o* }- d9 a) ^3 w, EOut[82]: Timestamp('2020-09-07 00:00:00')
    ) Y- L9 e- O- x
    ' |$ D0 v2 K& p) ^$ |& Mpd.Timestamp('20200907') + pd.offsets.BDay(30)
    0 E: g' e- ?1 t! s7 COut[83]: Timestamp('2020-10-19 00:00:00')
    2 w! q% \" F# R. @+ n. {1
    ! p* N/ |) z1 q/ ^( b% {2
    % i& c/ E7 _$ H4 }3
    ; h9 `% A* U, C- f8 f! f4
    3 i1 V/ W: E5 y; h* Y' u8 t5% U. E( b3 ?" y: [
      从上面的例子中可以看到,Offset对象在pd.offsets中被定义。当使用+时获取离其最近的下一个日期,当使用-时获取离其最近的上一个日期:
    4 F, a% _& z) P/ c" U' n2 [
    ' J& H# z/ k$ G, d  zpd.Timestamp('20200831') - pd.offsets.WeekOfMonth(week=0,weekday=0). t. U5 V0 y9 d) W/ l  M
    Out[84]: Timestamp('2020-08-03 00:00:00')- C/ i+ T# s1 u4 d# o1 E

    ( t0 o4 n5 a  Q" `pd.Timestamp('20200907') - pd.offsets.BDay(30)$ ^  b% e* Z: R' [
    Out[85]: Timestamp('2020-07-27 00:00:00')% u4 b2 Z7 E4 b% e( D- u

    $ }/ z2 @% q' f8 Spd.Timestamp('20200907') + pd.offsets.MonthEnd()9 O6 O4 V2 @7 L# T
    Out[86]: Timestamp('2020-09-30 00:00:00'). Q4 l0 Y3 ~8 s4 j' x2 }
    1- n5 [$ t3 ?( k- e3 A$ F" q4 \
    2" A  `! [! i5 l
    3- n' G/ S7 O, H3 `1 F" \2 `3 G
    4
    9 ]5 X8 ?+ x$ |) [+ ^& [5
    5 D  I- s6 Q/ T  F& ]1 l$ K" P( n& O( |67 C2 [5 h! S/ L
    7' Z! m0 [# r# U2 q% ^
    8, m( P6 v' h6 L  I) j
      常用的日期偏置如下可以查阅这里的DateOffset 文档描述。在文档罗列的Offset中,需要介绍一个特殊的Offset对象CDay。CDay 或 CustomBusinessDay 类提供了一个参数化的 BusinessDay 类,可用于创建自定义的工作日日历,该日历说明当地假期和当地周末惯例。- P2 T7 [' G. z7 N! k3 k2 T
      其中的holidays, weekmask参数能够分别对自定义的日期和星期进行过滤,前者传入了需要过滤的日期列表,后者传入的是三个字母的星期缩写构成的星期字符串,其作用是只保留字符串中出现的星期:. N" p/ x8 u4 j* e6 s
    * w  d6 P! E$ b3 }7 N. c; R3 J
    my_filter = pd.offsets.CDay(n=1,weekmask='Wed Fri',holidays=['20200109'])3 T' o1 C! Z9 O1 K4 h' g7 _
    dr = pd.date_range('20200108', '20200111')
    0 W# Y0 G2 s# g! y9 N) p7 K  Z) ^! u6 o; b1 C/ V8 F& N+ {$ `
    dr.to_series().dt.dayofweek
    ( O. c% k: l, z; L0 S" ?Out[89]: ) B  z* V) I1 T8 u
    2020-01-08    2$ \$ U6 Y- V2 ]. D6 f
    2020-01-09    3" x# p3 H' Y; Z5 j5 [* f
    2020-01-10    4
    2 Q8 E' J9 E: Q7 H2020-01-11    58 D7 Y- E+ e+ V+ b% _
    Freq: D, dtype: int64
    * }5 T5 k1 v% x5 ?: C% m
    2 s: k& S8 g- z/ \[i + my_filter for i in dr]% ^$ A( E/ J. T- x8 }0 k
    Out[90]:
    5 |( S  j# c- ]/ Z& z3 Q: t[Timestamp('2020-01-10 00:00:00'),
    5 F0 G1 m3 c6 x$ t Timestamp('2020-01-10 00:00:00'),7 c5 K3 F- g8 \+ O) Q9 ^
    Timestamp('2020-01-15 00:00:00'),
    & R0 P! z- {$ }9 M Timestamp('2020-01-15 00:00:00')]
    % h" e, u: u6 }9 V; @
    ! v, V1 \5 J3 i" i1( A3 I% T7 y+ J- K& o7 p8 ]
    20 O: p) x$ x. R, P' v4 u
    3
    * A; [- H$ i- E: u4
    $ [9 ~8 i$ {7 d5
    3 Z% n5 ], c7 Q) ~# X6
    9 Y# G3 K- v) s- c0 N" s8 y2 `8 ]5 R72 ?" ]" H5 A. D3 M
    8$ s2 Z, |" G7 m" A2 W; y9 b
    98 N; z& d' u7 q0 M' y' C2 k2 D: ~8 C
    10
    / U. g+ I. G+ j3 @; V11
    ' D' ?' ^+ g# g126 w7 a, s- l( o$ N& a% [
    13
    5 Y4 ]6 _/ Z- ?, H% T144 b# }0 k6 h# U8 p9 y) K& O/ k' Q
    15
    , [% O% a) i7 t  b1 F/ P. Z# ], p16
    " a2 a, o! }9 Q17+ a0 D7 r6 i0 c
      上面的例子中,n表示增加一天CDay,dr中的第一天为20200108,但由于下一天20200109被排除了,并且20200110是合法的周五,因此转为20200110,其他后面的日期处理类似。
    - K" N- T+ S+ c: n% a3 V& u0 d
    4 D8 g' P) b7 o' k3 e9 \' i【CAUTION】不要使用部分Offset
    - s' v; n4 E& }# H在当前版本下由于一些 bug ,不要使用 Day 级别以下的 Offset 对象,比如 Hour, Second 等,请使用对应的 Timedelta 对象来代替。8 S' p& L' H6 f0 r( F

    8 G( u6 F( ^, K) v" F; S6 \  e( G10.4.2 偏置字符串
    * {4 f. r$ W. Z  前面提到了关于date_range的freq取值可用Offset对象,同时在pandas中几乎每一个Offset对象绑定了日期偏置字符串(frequencies strings/offset aliases),可以指定Offset对应的字符串来替代使用。下面举一些常见的例子。7 G) E: p0 A6 }* h2 L4 y" r. s

    " b( p' N) A: H: F  Offset aliases:pd.date_range函数中的freq参数,为常见时间序列频率提供了许多字符串别名。 也称为偏移别名Offset aliases。偏移别名列表点此参看(大概27个)。5 Y  ]1 g( {4 s; {) O0 [$ r; h5 _
    * u: _, o4 I7 E
    pd.date_range('20200101','20200331', freq='MS') # 月初: n  N3 t; v0 H) Z' C
    Out[91]: DatetimeIndex(['2020-01-01', '2020-02-01', '2020-03-01'], dtype='datetime64[ns]', freq='MS'), A' H$ T& p% N% ]/ u5 A
    & @  P  k8 [2 ?/ z! y
    pd.date_range('20200101','20200331', freq='M') # 月末0 _7 _3 |5 k) @1 u4 C$ g6 R5 X- |
    Out[92]: DatetimeIndex(['2020-01-31', '2020-02-29', '2020-03-31'], dtype='datetime64[ns]', freq='M'); b5 F$ D$ O/ @1 X+ u

    - _  D: ]' t0 g. j  apd.date_range('20200101','20200110', freq='B') # 工作日! M$ l: Q9 F7 O6 Q
    Out[93]:
    ' e" x* `( j. j  z( u- ?4 ^DatetimeIndex(['2020-01-01', '2020-01-02', '2020-01-03', '2020-01-06',
    3 y1 B/ I" c2 F/ c: I               '2020-01-07', '2020-01-08', '2020-01-09', '2020-01-10'],: m& [: @9 y/ L! v  ~
                  dtype='datetime64[ns]', freq='B')" ]0 ^4 K! y. C; p# P5 _) l
    - k( M7 q3 v' _" e2 q) P& N0 \
    pd.date_range('20200101','20200201', freq='W-MON') # 周一1 W7 Q% V5 l3 ]& ~; `+ S
    Out[94]: DatetimeIndex(['2020-01-06', '2020-01-13', '2020-01-20', '2020-01-27'], dtype='datetime64[ns]', freq='W-MON')
    ; O. F! `% @8 M7 `# o) v2 _5 C  T/ f( N
    pd.date_range('20200101','20200201',. Q5 N# A, T! n; G6 ^$ V
                  freq='WOM-1MON') # 每月第一个周一
    - t6 e1 R3 c+ M6 I! B6 J1 O* ?% D- z7 M$ H% X" Y
    Out[95]: DatetimeIndex(['2020-01-06'], dtype='datetime64[ns]', freq='WOM-1MON')& q! |& v, Y% [* K

    ' U! c4 d, \% ?) x* E9 I1
    ( B$ j  p' v/ t# I, p2
    6 g) i* M& i* z2 @: T* d' J: o; \3
    / l+ G3 [8 ~+ i# d0 f4! ~1 I8 y+ K( |1 G! N& t2 V
    5
    ! h% D8 W9 d, ^$ ~# r6
    - e; O* ?+ y7 j5 y; r; i7
    5 g% o: ]( B5 \2 E+ v, b8
    $ G) L* b/ c9 t9
    . E# @% ~+ o: }$ \5 ~! ?105 J' p3 @! x0 \% P, B+ Q% i
    11
    4 w, U# v* b, \6 Y! @4 x* D' L126 i2 V5 u6 y8 e/ c! }
    13
    6 J( n& [! f! U14# ^: H7 z! V% M+ B
    15
    7 @$ T5 ]; }  y; j% B3 Z) J# _6 U166 Y- E1 }" m) W5 M& T  o0 `4 S0 }4 y' U
    17
    6 P. J- V2 |4 m$ Y6 v- H2 w. A; |3 I18
    + C0 I  z9 Q4 w1 ~19
    0 ?9 u7 z/ H& Y7 X& @- c6 y上面的这些字符串,等价于使用如下的 Offset 对象:! v5 T3 R: ?1 O4 d8 ^" M) n
    ' q- O1 A6 S5 N4 w- u$ a5 H
    pd.date_range('20200101','20200331',
    4 d- |( {5 J  t0 u' r              freq=pd.offsets.MonthBegin())& p! B2 _0 G2 R. Z: i* j
    # k4 Z  O8 m) Q6 h
    Out[96]: DatetimeIndex(['2020-01-01', '2020-02-01', '2020-03-01'], dtype='datetime64[ns]', freq='MS')! I" E* L5 |' z+ g( @

    ' c0 |( U. o0 E+ Opd.date_range('20200101','20200331',
    0 O" i4 N; Z+ X; r: X* q              freq=pd.offsets.MonthEnd())
    / Q' ?3 s) ~4 d7 b/ L$ e- k6 p- f# Y5 D- V5 [. [: ^& w( w  a& n- o
    Out[97]: DatetimeIndex(['2020-01-31', '2020-02-29', '2020-03-31'], dtype='datetime64[ns]', freq='M')
    : S6 \2 P0 ?) X: t- ~
    & X, u% [, [; Y- ?7 ]2 v( i) wpd.date_range('20200101','20200110', freq=pd.offsets.BDay())
    , \$ S3 o. K9 t: a2 @, u" \/ n0 oOut[98]: . s+ @$ B3 t  _/ U! {& G
    DatetimeIndex(['2020-01-01', '2020-01-02', '2020-01-03', '2020-01-06',
    ; F4 l7 B2 @# L               '2020-01-07', '2020-01-08', '2020-01-09', '2020-01-10'],4 m% ?" |) c$ [' u3 T
                  dtype='datetime64[ns]', freq='B')
    # Z0 E3 r& a. n9 Z. V7 u. @
    " ~0 U7 ]# M9 U# e, H" epd.date_range('20200101','20200201',
    * N' ]8 U. f. F8 D              freq=pd.offsets.CDay(weekmask='Mon'))
    ' d2 u& Q3 v& t$ C* o7 W9 O
    # F! r7 @: Q: H( L  GOut[99]: DatetimeIndex(['2020-01-06', '2020-01-13', '2020-01-20', '2020-01-27'], dtype='datetime64[ns]', freq='C')
    . }6 a; _) `/ o4 m; u- x4 E' X+ L# i  P
    pd.date_range('20200101','20200201',1 g0 I$ O# ^+ |6 a
                  freq=pd.offsets.WeekOfMonth(week=0,weekday=0))9 P) s2 S$ C. F0 {
    ! |4 d# p/ _" I: U6 m; x( x" R7 R
    Out[100]: DatetimeIndex(['2020-01-06'], dtype='datetime64[ns]', freq='WOM-1MON')+ n. w) w2 [' ?1 H. G+ Z6 U

    3 c- `) J. [4 H$ D) l1
    ( D- _- L1 E! `0 F3 `2. }  ~4 g2 ]. _: Y
    3, ], l" N9 I$ T' ?. G1 A/ O
    4
    3 U7 ]5 U* }. Y4 j' B, g5$ T7 j9 }6 c6 S. M0 q
    6
    ; E) ]' V0 A& d0 A7 b7
    0 y/ P! k! W3 n% [8 g: L1 R+ @8& W, a% D) |* L) |/ |1 h7 b/ c3 W8 M2 Y
    9
    / o2 |. [$ O) n8 P10
    2 ^  O) R) \# \. Z11
    + N5 Y$ m* s2 P- @' h12
    4 o0 Z& A% z  C3 l: {9 z2 E+ u13
    ' ^6 c' z/ B# a. y/ F14' {6 q$ y, k0 w- U4 t1 q& @
    15
    & y2 e4 C! n8 D0 h7 R$ M16
    6 _! M! e& C6 c5 U# R17% z; t! H9 ?# K  a: H5 t+ w
    18
    8 `2 i! l" f4 [# M1 Z3 s19  {% X( N# G9 Y7 A
    20! j/ Y; e* `/ O, U/ o/ x
    21, Y' T$ J3 z* F1 p* B* j' v- d
    22
    0 [' S/ q6 I& U" H9 [9 }23
    8 ~- w+ U' z# {+ A24
    . o* B- r7 |9 G; T8 _) |25( m/ S5 N- M( x  d6 l
    【CAUTION】关于时区问题的说明4 z; H2 b3 s; d
      各类时间对象的开发,除了使用python内置的datetime模块,pandas还利用了dateutil模块,很大一部分是为了处理时区问题。总所周知,我国是没有夏令时调整时间一说的,但有些国家会有这种做法,导致了相对而言一天里可能会有23/24/25个小时,也就是relativedelta,这使得Offset对象和Timedelta对象有了对同一问题处理产生不同结果的现象,其中的规则也较为复杂,官方文档的写法存在部分描述错误,并且难以对描述做出统一修正,因为牵涉到了Offset相关的很多组件。因此,本教程完全不考虑时区处理,如果对时区处理的时间偏置有兴趣了解讨论,可以联系我或者参见这里的讨论。3 d! q& o2 \; p) L
    ( q  \) N  G3 o9 n) d" q& i% e" o
    10.5、时序中的滑窗与分组
    ; F" J% n+ U5 i3 n/ h& r; O  `10.5.1 滑动窗口& o' U1 M. {8 H4 n- V6 A
      所谓时序的滑窗函数,即把滑动窗口windows用freq关键词代替,下面给出一个具体的应用案例:在股票市场中有一个指标为BOLL指标,它由中轨线、上轨线、下轨线这三根线构成,具体的计算方法分别是N日均值线、N日均值加两倍N日标准差线、N日均值减两倍N日标准差线。利用rolling对象计算N=30的BOLL指标可以如下写出:6 C3 k4 t; E+ g

    * i- d$ X, x1 x" }# X% d# e; p4 ]5 N" _import matplotlib.pyplot as plt
    1 r0 l  [2 |! c( O/ Tidx = pd.date_range('20200101', '20201231', freq='B')
    1 O2 v: H- W3 Vnp.random.seed(2020)2 Z! P. d5 w$ W! Y  _
    0 T+ B' g6 A& [- m# l( Y! T1 _% o2 }
    data = np.random.randint(-1,2,len(idx)).cumsum() # 随机游动构造模拟序列,cumsum表示累加- q6 n  X5 T& j$ Z
    s = pd.Series(data,index=idx)$ [! ~  N; Z8 s4 j( g& L' \
    s.head()5 Q  o; M, S, z* w7 Z5 F1 \
    Out[106]: 5 ]. D! L, u  e$ C: m
    2020-01-01   -18 L9 V) K5 ^( J4 Q, {, \, h9 |
    2020-01-02   -21 P! K- D6 D) h
    2020-01-03   -1  Q  l4 w* `2 X( V6 Z
    2020-01-06   -1
    6 s/ q4 q( y+ {- ^8 `7 f- [2020-01-07   -2" m9 v( c0 I/ v' F' r4 L
    Freq: B, dtype: int32
    1 M5 V# T3 V0 ^. er = s.rolling('30D')# rolling可以指定freq或者offset对象
    $ @% Z- J& ^/ K" |
      s- y5 j3 ]5 o0 V  k7 S8 rplt.plot(s) # 蓝色线
    6 {2 q" X, L  e' cOut[108]: [<matplotlib.lines.Line2D at 0x2116d887eb0>]! c4 e8 {( n. a1 C
    plt.title('BOLL LINES')! j, a. Y( V! k
    Out[109]: Text(0.5, 1.0, 'BOLL LINES')7 O  u, A; w- L4 ]0 r  B3 s3 S- B

      q' x( Z* J6 O2 T" Wplt.plot(r.mean()) #橙色线' O5 N  m4 C) S. h1 Q  {7 R* E
    Out[110]: [<matplotlib.lines.Line2D at 0x2116d8eeb80>]
    0 n* N4 a% u0 n9 ^- Q! J
    0 G+ W: y# R; D* fplt.plot(r.mean()+r.std()*2) # 绿色线
    2 D$ T$ C- g: JOut[111]: [<matplotlib.lines.Line2D at 0x2116d87efa0>]$ l. [, S; ^6 s8 Z
    1 G9 N6 O7 ]4 Z( l5 G$ d
    plt.plot(r.mean()-r.std()*2) # 红色线
      L/ |+ D$ e! ?  @Out[112]: [<matplotlib.lines.Line2D at 0x2116d90d2e0>]
    1 E3 W+ ^+ j& d! F: [* F& d" ?2 G3 W7 n; g& c+ p- H, |/ C
    1
    6 b7 E5 x2 v" R2
    & D& ^* f1 B7 P% ~4 @+ s3 j3  d8 h! Y1 I( R. a9 s6 Y; P- Z- C8 P/ B
    47 ~. H* j/ J- _" g# r7 ^  E0 i
    5; c3 N, i9 H  Y# C( q6 v: T
    60 i7 h! m: k/ Y# S  L9 F* H/ u& R
    7" V$ s+ b/ B  l, l; V4 A( N7 y
    8
    6 ?; ^0 X& `# l* H$ ]2 L$ h. h3 R( B9- }. y. n3 L8 ^5 n
    10+ c  `! H' u( _* K- R3 ?
    11
    ; D0 _# g) n7 ]; I8 [! a0 R, C12
    " }5 B9 }. [  m0 F+ o7 d13! G4 F; D+ M5 I0 i2 `7 }) [
    14
    3 z" w8 W5 ~; E# }/ q. l15
    * _2 U3 L3 j( \2 y8 m9 h16
    , N, Q: J" ]$ v' t- k9 E17
    % R/ X, B: L0 m1 d+ x# p18' I( A! s: K1 ^% H2 v, O( f
    19
    + `3 }6 z% P, k$ C8 t20! f& @: T+ i: ~% x: r: W
    215 l; Q# e! D" W; k( M4 i
    22
    / R! \$ m8 S$ I  _239 j& H( Y' ^& {* ^% u- U
    24" v+ u2 ]% z! Y; t/ g2 A% N7 u
    25/ g1 I( F% \1 |8 P
    26
    # X2 y( ?- @& n27% {7 \+ U. ~( a) x! k* ^+ n3 I+ ~* i
    28/ T# u: J; Z4 q7 w% @) f* S# |
    290 a: B  H% G) U, t, F7 @* F
    , g7 l$ r) s0 u/ ~8 c5 O  F5 d
       这里需要注意的是,pandas没有实现非固定采样频率的时间序列滑窗,及此时无法通过传入freq字段来得到滑窗结果。例如统计近7个工作日的交易总额。此时可以通过传入多个函数的组合来实现此功能。% G+ Q) |# }. {- T
       首先选出所有工作日,接着用普通滑窗进行7日滑窗加和,最后用reindex()恢复索引,对于双休日使用前一个工作日的结果进行填充。
    9 Z  M2 i" d# r+ Z5 P& D+ P
    & j; m6 g1 O) b9 k" j4 \+ ?select_bday=s[~s.index.to_series().dt.dayofweek.isin([5,6])]
    * D. j  n: [' S# ?1 Mbday_sum=select_bday.rolling(7,min_periods=1).sum()) H% M9 n* [5 |% e7 j* {
    result=bday_sum.reindex().ffill()
    9 l; S1 l0 L6 S- E: ?  I! y5 Oresult! ?& _' K/ `9 B, p' _$ z

    : g( @2 Q) w7 I0 |" \5 T  r2020-01-01     -1.0+ @9 l" r4 R  ]4 M# ]# g
    2020-01-02     -3.0  Q& l3 c  V9 a: n1 m" L
    2020-01-03     -4.0
    $ g, \, r. }7 F7 W2 m8 E/ {( O2020-01-06     -5.0
    6 B/ d/ P5 @* Q/ j9 s% R2020-01-07     -7.0
      b8 H+ f- f# m) O+ I7 m              ...  
    9 Z6 o1 b6 Y* ~0 V- W2020-12-25    136.08 J4 N  s% L8 c2 O9 i9 F$ M
    2020-12-28    133.0
    ) ?1 r% J- c, }+ F% m* P+ v& _2020-12-29    131.0
    . e( v5 ^5 [1 X" |& h+ s2020-12-30    130.0
    8 V+ @; R. {6 _2020-12-31    128.0, v0 k% R; A2 d. C* K
    Freq: B, Length: 262, dtype: float64
    ! R" P6 b: }: |
      a2 S& {; x# W: T3 R1% E4 {5 g3 V2 A3 B5 Q
    2: Y+ A/ d  w$ O8 U0 f9 ], a
    31 d7 ~9 ~3 F( A8 \9 P4 m- v
    4' |/ y3 s. p6 e/ A( T7 _
    5, u( C! e" }8 F; a
    63 h! h( f# a6 R; F
    7. J' x4 a: l5 q( G. e) ~
    84 z7 Y2 [& W* o6 D/ v* E
    9
    # R" f6 ~; g  n/ H1 v6 d10
    * |( ]7 l7 X6 T- X- @9 T$ R/ D11( q3 \5 P) c+ T8 q1 a7 M4 w
    12* g7 z+ U& p: V; G4 [3 P
    13% F  V0 u5 U, I2 j- b
    14; u4 b$ k+ z8 ^
    15- q! {+ R# _8 j1 V' P
    16
    " n+ S' s1 l! \" D1 @) ~2 ?17
    9 l. m! R) @, b* ?$ ^( W  shift, diff, pct_change 是一组类滑窗函数,它们的公共参数为 periods=n ,默认为1,分别表示取向前第 n 个元素的值、与向前第 n 个元素做差(与 Numpy 中不同,后者表示 n 阶差分)、与向前第 n 个元素相比计算增长率。这里的 n 可以为负,表示反方向的类似操作。
    , b6 a/ G2 I& p  o" G, E
    ; i- c: I/ r- O* Z" J  对于shift函数而言,作用在datetime64为索引(不是value)的序列上时,可以指定freq单位进行滑动:
    ; i/ `) P( V* F: {. s* i8 T( [6 z
    + n0 E& d: \+ x* P. ]s.shift(freq='50D').head()2 A4 w, U# h8 i. `
    Out[113]:
    6 ~1 `, Y- Q2 m+ M. D6 w: `2020-02-20   -16 c7 D  \5 }) _. e/ W
    2020-02-21   -28 _+ ?1 R  M" R2 H; S: j; ~7 y
    2020-02-22   -1
    ' S2 I9 O) N2 N( k0 U9 I" r2020-02-25   -17 D+ _8 r5 w6 Q2 C: e" G
    2020-02-26   -2- ]0 g2 _- T2 S  O
    dtype: int32$ ^" o! O: Z$ J/ d, ~7 W" G& g
    1
    ! A! o& d( ?% N) X# {* Y0 A2 l2
    % r  R% k. u: }6 Q3
    " M) C2 k6 j% ?9 I# I9 Y6 o47 _( d' j  [9 F# I2 s
    5
    % k- c7 Z9 |! J  P6 P) ]6 b( L% r  A6' ~+ N: ~3 g6 W7 b9 u$ o6 f& E
    73 s' ~5 l& K1 w+ |1 g
    8
    . \- z. z; ]" w6 R- H  另外,datetime64[ns]的序列进行diff(前后做差)后就能够得到timedelta64[ns]的序列,这能够使用户方便地观察有序时间序列的间隔:
    0 d' n% A  J- y
    , q  Z2 _% f  C+ U  E% nmy_series = pd.Series(s.index)9 D: u+ Y; z' P6 |+ p
    my_series.head()0 j8 g" D; V9 Q! J. P& A; v
    Out[115]: 2 p; w+ _. B0 o1 T# P$ S
    0   2020-01-01
    0 Y9 r; H' n2 T- F1 q! v1   2020-01-02! A  j" r0 O" r0 ^+ H0 x+ @7 V: A! _3 c
    2   2020-01-037 ~9 X+ b; k. d
    3   2020-01-06
    0 H% j  v, Y3 O8 g6 y4   2020-01-07; G$ [" i6 O  C. [
    dtype: datetime64[ns]
    * z( I4 t. j9 ^% x8 b" ~
    ; a: m6 J0 x; [) e- C9 S$ Ymy_series.diff(1).head()
    4 O- }& y; X, E) o1 n+ kOut[116]: : s7 y; {$ i. T: u) o  J( j3 c
    0      NaT
    : R! G5 }7 O  S. {1   1 days) E) i$ v$ A0 Q, ?( E
    2   1 days
    $ z$ Q: c& Z7 P/ Q* H) v! j; \3   3 days
    3 q, b! E: x. ]- h5 x0 u4   1 days2 g1 t) y9 ~& K: I) |
    dtype: timedelta64[ns]9 ]* G  N4 g% A% J: `6 P
    / w& u9 r4 H, \- I& k
    13 q$ Q: Y# `% m4 O6 S# Q3 F- O; G
    23 j  `7 ]1 e( l" M* Z  ~: c
    3
    " {1 [0 P$ {# b. U% m6 A4
    $ \1 V  _- J$ K) Z7 E5* ~' \$ S: U! z4 N; E3 P
    6
    , \9 M8 \; `) L! v5 g7
    * G& f5 S9 E# ~) \* |8- r3 G% w( S% a2 a
    9% I% R% O0 c6 K* C7 ]* p
    10
    / m" q. r6 M8 B; M% X4 V11) m' Z' l6 _$ ^" e' b
    12
    / ?+ o& n% v7 J, V' |, N13
    0 g% S/ _. p; x$ V% q14  G* m# `& Q& ]
    15
    $ N. v! D4 s; t, M16( \# }: v( P8 b2 x( }. O
    17
    % u3 C; [* x$ l8 j- z" z1 q* i18+ H( V9 z, s# L4 z; q" t( \
    10.5.2 重采样
    3 V4 @3 Y! `+ C9 z. I  DataFrame.resample(rule, axis=0, closed=None, label=None, convention=‘start’, kind=None, loffset=None, base=None, on=None, level=None, origin=‘start_day’, offset=None)8 g6 L. K* L& ^- S; F
    常用参数有:* s& `9 f- R+ f" r& r

    # H! \- c* G- j9 e: v5 q& wrule:DateOffset, Timedelta or str类型。表示偏移量字符串或对象
    ; M" w& j7 s% T9 |5 haxis:{0 or ‘index’, 1 or ‘columns’}, default 0。使用哪个轴进行上采样或下采样
    8 Y! U/ I9 }/ Y+ v! fclosed:{‘right’, ‘left’},默认None。表示bin 区间的哪一侧是闭合的。所有offsets的默认值为“left”,但“M”、“A”、“Q”、“BM”、“BA”、“BQ”和“W”均默认为“right”。
    ' V2 R, Z  `. V6 t% Dlabel:{‘right’, ‘left’}, 默认 None。hich bin edge label to label bucket with,所有offsets的默认值为“left”,但“M”、“A”、“Q”、“BM”、“BA”、“BQ”和“W”均默认为“right”。5 B2 I, N+ U5 i' ~/ p
    convention{:‘start’, ‘end’, ‘s’, ‘e’}, default ‘start’。仅针对 PeriodIndex,控制是使用rule的开始还是结尾。
    # c  s+ g1 ~0 e% j, M1 m  {9 won:字符串类型,可选。对于 DataFrame,使用列而不是索引进行重采样。列必须类似于日期时间。
    " c( [/ c9 ?% V. I% ~# C; c5 E; vlevel:str 或 int,可选表示多重索引MultiIndex的级别,这个级别的索引必须类似于日期时间。* R5 Z& C2 S- d7 p2 P; {8 D
    origin参数有5种取值:
    5 g: d/ l- T- f4 i; {3 y3 b0 B2 V$ N‘epoch’:从 1970-01-01开始算起- D# |( |9 r0 H8 x+ l) M* Q
    ‘start’:原点是时间序列的第一个值: ~8 x! t) Q2 G: F7 E# W2 D! R
    ‘start_day’:默认值,表示原点是时间序列第一天的午夜。+ n; g! b8 [5 G0 E1 I! R% C
    'end':原点是时间序列的最后一个值(1.3.0版本才有)
    5 t+ S. @! l  r% S4 ^‘end_day’:原点是序列最后一天的午夜(1.3.0版本才有)
    7 T' N- U( R1 c8 v4 j1 K& l6 p6 A" roffset:Timedelta 或 str,默认为 None,表示对时间原点的偏移量,很有用。5 G2 n9 }( R4 D* F5 S
      closed和计算有关,label和显示有关,closed才有开闭。
    : L3 k% @  p4 b. D! L  label指这个区间值算出来了,索引放区间的左端点还是右端点,closed是指算的时候左端点或右端点是不是包含。
    - a3 j' e! r* Y0 R: q) a2 y" b
    " |5 j9 k- N6 E; `0 f9 O+ o重采样对象resample和第四章中分组对象groupby的用法类似,resample是针对时间序列的分组计算而设计的分组对象。例如,对上面的序列计算每10天的均值:% `7 S" P2 P$ Z! T2 @
    s.resample('10D').mean().head(). `; X6 X5 U/ l- Z" E* T. S
    Out[117]:
    2 G4 r* U9 H$ ~9 u; e6 f& g2020-01-01   -2.000000
    3 g5 l+ C  t! k, U. a7 ?5 N2020-01-11   -3.166667
    3 c) x3 o3 ~" H( f1 t2020-01-21   -3.625000
    $ d, R- w* x7 y  m' R8 d5 `8 G5 i1 X2020-01-31   -4.000000
    % ?9 P5 f8 i5 h6 F! U3 }$ T# N2020-02-10   -0.3750008 ]4 \& y' l: v
    Freq: 10D, dtype: float64! ~, S" W3 j: b6 j
    1
    7 c1 L: f5 U0 {9 \( H$ i5 F2& |: Q2 p' z( `
    3! J: H- e/ b  L! _+ O
    4
    * t, F0 m; o- H5 W& o% P! A5
    0 G+ h" D- U+ N2 P6" R3 \! ^% {* v8 _) J; s
    7
    * s+ }! k  ^( m$ q: Y, x8
    ) _/ T: W$ {4 h$ _6 X可以通过apply方法自定义处理函数:
    + y+ k- F3 x& bs.resample('10D').apply(lambda x:x.max()-x.min()).head() # 极差$ P4 F3 C. `- Z- u/ K/ v1 Z6 j4 B7 n+ s

    # R3 Q9 H0 D! eOut[118]:
    . L! o- z4 r8 N  n" N- V2020-01-01    3
    4 a6 d- O# U% i+ ?9 k& j2 x! G, A2020-01-11    4; Z2 y# Z( y$ P& [4 W* a9 Y
    2020-01-21    42 J8 m) |: C! B9 ]. j1 z' W
    2020-01-31    2
    ) g0 n5 _& E0 L' |* s) I0 f; \2020-02-10    4/ }, ~1 Y; N, X; S5 v& I, u; p
    Freq: 10D, dtype: int32( @! C6 {; I# V! O9 y; X, ?
    1# w5 ^9 F, ~! X0 {8 i
    2! {, [- z/ }3 G' F. ?" `! G( |
    3
    ( Y, z1 t4 [( i- i" t/ m, f! X( T46 l$ c8 }4 r7 X) w- w# b
    5
    : g. H+ y. `/ Z7 U2 i* [2 U( `9 K6, W2 g$ m" ^1 V+ }3 P6 `; E" X/ u, n5 `
    7
    % t- w" ~  {, i. D: u4 _8/ K7 q( l( w' e% v1 W& K/ t
    9
    6 L2 [* E+ s  y8 z: D  在resample中要特别注意组边界值的处理情况,默认情况下起始值的计算方法是从最小值时间戳对应日期的午夜00:00:00开始增加freq,直到不超过该最小时间戳的最大时间戳,由此对应的时间戳为起始值,然后每次累加freq参数作为分割结点进行分组,区间情况为左闭右开。下面构造一个不均匀的例子:
    & Y  G: u% R! n% ~3 F1 ?7 G3 n; z0 O0 Q6 M/ `7 J/ |
    idx = pd.date_range('20200101 8:26:35', '20200101 9:31:58', freq='77s')
    9 x$ Q: \- H& n( e2 U' T$ w/ a4 cdata = np.random.randint(-1,2,len(idx)).cumsum()
    % ?/ c, b8 L- y( C0 [# fs = pd.Series(data,index=idx)+ v/ ?7 O# @8 F
    s.head()# k" L- M, `- q! u% J# z" ^
    " o$ a' }/ B$ |! n$ h" Q
    Out[122]:   s: c4 l1 M0 Y. D% O, _) l3 i  A
    2020-01-01 08:26:35   -1
    7 \# T% C; c# j# s( z2020-01-01 08:27:52   -1
    8 r% J! l& O3 f7 A2020-01-01 08:29:09   -2# G2 ?5 }$ }  C" |
    2020-01-01 08:30:26   -3
    2 `  S; H, q& h( F4 u! I- B2020-01-01 08:31:43   -4
    7 D# g" e: I( `+ q2 {2 VFreq: 77S, dtype: int32
    " ^+ ?; G# Q& M6 U- w  L( A% o1) F- Z8 I# v6 [  Y9 i/ U
    2' K- b4 j8 F! T; Q' v: E( F
    31 e6 S+ u5 @$ |8 S7 J( r
    4
    0 p3 N3 S* a4 h2 j% G5
    " j' r7 ^6 T6 A3 R4 z, b2 q6: u( Z8 _8 v, U( ^" {6 z
    7
    6 U. `$ G( ]% N6 D$ c9 U8$ f5 Y$ s0 l6 J% F3 z' z
    9
    ) Y0 ]9 S  N3 E  _( G. ]! q10/ j( T4 p" m% K9 x
    11  V- J8 I1 _' b5 S) m. x! [0 k2 Z4 |
    125 N5 u* x7 r- x: {4 R. c* U; I
      下面对应的第一个组起始值为08:24:00,其是从当天0点增加72个freq=7 min得到的,如果再增加一个freq则超出了序列的最小时间戳08:26:35:9 @7 h6 j7 v% v

    6 j& U5 B5 ?0 s3 r6 l/ ns.resample('7min').mean().head()
    / G; a! V" O! _. w* |2 r% gOut[123]:
    % ]) Q( B7 q( I7 \2020-01-01 08:24:00   -1.750000  # 起始值,终点值包含最后一个值4 L, ?7 d, y# g7 }: T
    2020-01-01 08:31:00   -2.600000
    ) e+ f. X& W) k" L% _2020-01-01 08:38:00   -2.166667+ I$ e6 R' q- N" q3 M
    2020-01-01 08:45:00    0.200000! p) j5 R* g6 o$ m, V
    2020-01-01 08:52:00    2.833333
    0 j& g) d. s! A% aFreq: 7T, dtype: float64
    $ A4 d) f3 h! K/ R3 f" U1 I1' }  l2 F% i+ m% R$ E
    2
    . j# h0 F3 e/ U/ Y% q; z3
    ( [" L( @( i8 D% e8 B4
    ! F1 e( H; B% b# S3 W+ {5
      B, }/ u7 V/ b# b  H% ]8 J8 q6
    7 _& U( o3 H' G7 _2 B2 v' p" J$ C8 d70 E5 [; b5 l/ A) v! G
    8, |6 ?# t4 L* n; ~6 \
      有时候,用户希望从序列的最小时间戳开始依次增加freq进行分组,此时可以指定origin参数为start:
    9 h+ S2 c+ M) w. O# v" Z  a5 B- q
    s.resample('7min', origin='start').mean().head()
    ! Y' G2 ?6 }7 h' i5 K. rOut[124]: # R: w; t& d( u- J! g
    2020-01-01 08:26:35   -2.333333- }% P3 z, U7 \( ?4 `2 X: v
    2020-01-01 08:33:35   -2.400000$ \6 N& G% L2 T) W
    2020-01-01 08:40:35   -1.333333
    ) t8 j5 T6 g& l( J1 m2020-01-01 08:47:35    1.200000% Z- I7 @' c* ~; V& a6 U+ I
    2020-01-01 08:54:35    3.166667  Q1 [, o; x" f
    Freq: 7T, dtype: float64
    % H' y; k7 g" B6 D+ @1! V4 `1 p( U/ c
    2) g& k/ y; I# n
    3
    7 R$ M6 s6 e7 G& f' ^  G43 b& E, ~' f' k! d5 p2 M
    5% k" c( m! Z6 P
    68 |6 N# c# P5 I: D* d
    7
    , M3 d$ Z- w3 j. N- Z% S3 z% s8# Y5 X/ n; j" D3 z' b
      在返回值中,要注意索引一般是取组的第一个时间戳,但M, A, Q, BM, BA, BQ, W这七个是取对应区间的最后一个时间戳。如果想要得到正常索引,用’MS’就行。8 U' o1 O, F% ^- ]+ S: N
    ) M3 _) b+ \- g
    s = pd.Series(np.random.randint(2,size=366),4 {- B+ I7 }5 F0 R
                  index=pd.date_range('2020-01-01',
    . E# S- J* k$ w' B+ d; Z  C6 A9 G! k                                  '2020-12-31'))% ~2 J% p" O7 h. t$ g2 r, ~

    + c' Q# q3 {6 a$ {
    ' e1 t: Y& E2 R) U) B- t0 C" ?s.resample('M').mean().head()
    : j% M: l8 t1 iOut[126]:
    % m5 J6 b& d2 H4 b2020-01-31    0.4516137 H0 _2 L8 C/ i( h6 g3 m$ N
    2020-02-29    0.448276; K  w  ?8 F) n; |" O; t. P8 {
    2020-03-31    0.516129) a4 o( ]1 o* W  c9 A1 R& i$ s8 H3 }
    2020-04-30    0.566667
    ; D8 M% n# k% j2020-05-31    0.451613. n* R" I* d0 O/ t6 G
    Freq: M, dtype: float64# H9 l7 |% w8 M& l* ~) v8 b* B
    5 h5 L& z& D* U0 Y0 K2 N
    s.resample('MS').mean().head() # 结果一样,但索引是跟正常一样8 v! ?$ y9 M6 w: I" p4 H# |
    Out[127]:
    0 Z4 D8 _' x& }. ^: m1 {) u2020-01-01    0.451613' M: c) H$ i+ o& d
    2020-02-01    0.448276. Y4 p* E( f- T9 j
    2020-03-01    0.516129. Z% u! y0 n! X) v3 c; X$ P  `
    2020-04-01    0.566667
    - G1 n  u1 N7 g; m9 z2020-05-01    0.451613
    % P7 ~: t* k# d& i, B4 nFreq: MS, dtype: float64( O' G! o6 n# M9 Z

    " S& q& M/ `  H; V1( X" m5 A( j1 G* z: {/ |
    28 v9 H& J/ J$ y7 ^  }5 E/ R" Q
    3
    3 E; s+ L# |* {$ D+ O% D3 S4* ~$ T$ ^7 W/ I3 [
    50 a, P7 j8 F) T
    63 S& N! Z9 U5 I8 H
    7
    3 w3 a/ N' d6 i) p2 W3 @86 B& x8 J$ d0 z8 f0 Q& X/ H1 |2 g
    9
    & [# `' Z5 A/ o: U, y  ?/ O10
    , n( M( H3 x7 Q, e11; I. u$ B3 G4 L! \; H) N) L" F8 i% C
    12
    ( X% d% w4 D9 t3 j- i1 N130 S" W, g4 Q$ }- r2 v, {
    14
    $ |  Y1 ?$ d" \! S15
    # ]* m  O; ?- Y, `: J16/ j; G3 T7 w" ^3 _$ b) Y
    17
    , W' U, @7 q0 [7 Q18* E1 e3 K/ G/ _) L
    19
    + g, Y- D4 J! O3 G/ _  }! Q2 M20+ h& U. ]5 E4 m) h0 {( G) u: A7 m
    21
    0 x. h6 C3 o2 T+ \  T  C6 ]. P22' b! ^# B& {: [) C! M$ m1 t% o
    对于 DataFrame 对象,关键字 on 可用于指定列而不是索引以进行重采样:- `/ M/ O7 d7 a- I, |1 T" D, _
    d = {'price': [10, 11, 9, 13, 14, 18, 17, 19],
    " p5 x$ e& r& M; e% t     'volume': [50, 60, 40, 100, 50, 100, 40, 50]}1 O" t; {0 u; d: _; T+ \
    df = pd.DataFrame(d)9 U7 Z& F7 o: q
    df['week_starting'] = pd.date_range('01/01/2018',
    ' x1 N2 g' u+ r- S                                    periods=8,8 t: o+ L- t7 O
                                        freq='W')2 [" \6 v7 u3 t5 u0 B; i" A
    df" W3 ^+ K2 m/ R
       price  volume week_starting
    0 ?* b5 Z) J5 h, `0     10      50    2018-01-07
    . q* T+ p& p( ]! j1     11      60    2018-01-14
    , X+ V' R6 t7 n- U( B6 {4 P2      9      40    2018-01-21
    ! g6 `$ v2 b8 V) b3     13     100    2018-01-285 J! ~' E2 ~' n+ m
    4     14      50    2018-02-04
    * H. Q2 X* h) ^% N0 y% X8 z5     18     100    2018-02-111 @9 O, `  N6 ~4 a+ n$ \+ P
    6     17      40    2018-02-18. ?* B& o' O( l% `0 c/ b
    7     19      50    2018-02-25
    3 b0 b( d- |; tdf.resample('M', on='week_starting').mean()7 v" U# p& `. d3 k4 L
                   price  volume8 |2 q% G2 h+ @: `# ]
    week_starting# y8 E7 S) b/ c, w# c: t# N8 s& |
    2018-01-31     10.75    62.5
    # ?; z+ @% D2 }( K' m2018-02-28     17.00    60.0
    8 Q/ x4 z& g& J" }; K
    1 d2 `9 f3 r7 M) }# ^8 e5 f4 _: A4 X& m1- W# R+ Z. g6 J# @, l4 S
    2" O& ]! Y* E( ^+ z/ `: B
    3- f( K) B) v2 A2 E, f( l
    4
    . F* g8 T! @6 \+ L4 J5: b# H+ s1 C: _
    6
      M$ s- [' `2 R4 t% q% g7
    ) n, s+ B5 Z) X( s/ c$ |4 Z, |# H8% T: k; p% Z3 t' }2 s
    92 R& g, J1 y! Q) n: l3 q. `" \0 R
    101 `: B# f8 I0 v9 s& T- q* ?7 i
    11( r& `& |  Z2 S( I& d* w3 B$ x
    12! e- S3 y6 @; B; Z/ k# N2 u" g3 C
    13
    * W) g% A3 v2 x2 M) r" y, y14
    2 C9 w: `+ P% o! F4 X. h  ?15
    ; e, Y1 e! u3 X; Q, g16
    % W, x# j: J; R: k5 f/ R17# a5 s$ T; Q1 U6 n
    18
    ' j( [& G2 Z, N" Z2 c' L# |- |19
    " O1 E6 V2 C+ y3 D5 e20' H8 P, ^# E' @7 n+ ?0 S
    21# U6 Y* c7 L% \# Z+ w5 ^) z
    对于具有 MultiIndex 的 DataFrame,关键字 level 可用于指定需要在哪个级别进行重采样。2 Y4 Q+ Q3 P# A
    days = pd.date_range('1/1/2000', periods=4, freq='D'), c* S9 Z  q+ K2 a* W
    d2 = {'price': [10, 11, 9, 13, 14, 18, 17, 19],
    ( N, }2 f2 o2 V& n" L1 Z0 @      'volume': [50, 60, 40, 100, 50, 100, 40, 50]}
    ' J7 i3 w7 l  J2 H" Zdf2 = pd.DataFrame(
    6 D, o1 U0 ~  W- M, `2 l    d2,
    7 h4 R2 R% D3 T0 a    index=pd.MultiIndex.from_product(
    * b" `- S/ p; i4 T" M3 h2 w        [days, ['morning', 'afternoon']]
    " T$ {3 Z, ^( @9 {5 d7 f    )5 D0 T& u8 n0 r, r0 r4 l8 C
    )3 n7 w/ J' V' P$ ^$ ~  H' T9 |
    df2- g% u) S5 q+ Q+ q( ~* u! }( S
                          price  volume
    7 H% j9 G& ^) ^+ V) S( Y2000-01-01 morning       10      50
    / ?1 b% n& ~& A1 e+ y! t# u! X           afternoon     11      60. M9 V3 b! p9 `8 c: d
    2000-01-02 morning        9      40" |6 G* ~: |7 Y2 u+ K  u# ~9 D) K4 ?, s
               afternoon     13     1005 r& T9 }9 y7 q9 Q0 I6 A2 g$ e! q( _
    2000-01-03 morning       14      50( l) F$ z8 r* C  W- y
               afternoon     18     100
    + V+ J6 F$ r- l% m+ n( G9 Z6 n0 c2000-01-04 morning       17      40
    ; H4 [! r) V: }7 E% f7 d7 W           afternoon     19      50" E8 T, ?3 l4 T" v( ?" l
    df2.resample('D', level=0).sum()1 ?9 ]+ ]9 J- y
                price  volume
    * d; o/ n6 I! S( L" T; t. @2000-01-01     21     110
    2 u6 S% }$ n: @  _& r2000-01-02     22     1408 ~* Y! Y6 I( e9 i0 d
    2000-01-03     32     150, z- D. l9 n+ s, H; ]1 ~+ }7 B+ _
    2000-01-04     36      90* _/ T8 X6 H+ f. q- @  x

    6 Y+ ?- I9 i: j* y9 k1+ u/ z5 \) }: @: y: q+ R8 o
    2
    , F4 Y6 l" c% A* }1 Y3
    ; f4 j# O3 C% m, P4
    $ w8 D% ?1 d+ r: L5 O! }! z3 |5. O. \* {& }$ a; Q. V
    6
    ) R. \0 c- Q, r: g9 A5 F4 R" i7
    ; y9 S) e6 z: `0 U- R/ [) i8
    " r* W. e% g9 B, p. h9
    : z  O" p+ `0 [9 c5 C9 M109 Y" |; W7 B4 \+ D% f$ O4 Z9 N
    11
    ( i: ]) X; T; y: F12
    + ~1 _; Q( ?* e132 c  c( G7 Q* f
    14
    / Y, d+ d! D1 U1 d  M0 u' s15
    ; E  h6 y6 U0 v- h16
    ; b3 z' r# e8 }17( x# {% w; i4 ?
    18) A5 q2 ^& r  h
    19
    - N: e2 m5 @* z8 `2 J20
    # F* N8 ~" _" p21( q8 z) M/ R- T
    222 ~# N, I; j0 [9 R) [( d
    23
    - `! {8 H# `( B# `24* n7 @) W0 b- z! h8 X1 H% m3 q, M2 b
    25' V4 K$ H, _1 c6 P2 ]
    根据固定时间戳调整 bin 的开始:
    0 m! r4 Y3 z" ^, L( \8 ?% W" d: T$ fstart, end = '2000-10-01 23:30:00', '2000-10-02 00:30:00'
    5 w5 W& o7 k! S- |rng = pd.date_range(start, end, freq='7min')
    . D0 n# D+ B4 u( fts = pd.Series(np.arange(len(rng)) * 3, index=rng)
    . t6 c4 D% F$ m  r  nts
    ( N) X- [1 q8 n& g& @+ z2000-10-01 23:30:00     0
    ) @) L& e) X0 m( w. ^$ w  @: ~2000-10-01 23:37:00     3
    - D2 a/ D' Z/ \' W3 Z2000-10-01 23:44:00     60 L1 A! z3 {1 A  ?  ^. Q& B) u
    2000-10-01 23:51:00     9
    * \) W; s& `; G( V$ s6 x2000-10-01 23:58:00    12
    & |& g4 Q7 l% ]- }$ Y! J, d' E) P2000-10-02 00:05:00    15
    ) B: u2 B7 B" N! ]2000-10-02 00:12:00    186 F! P  z" H. T2 c/ h( t( i
    2000-10-02 00:19:00    21
    3 ^, g% V3 x! i: u6 P) g2000-10-02 00:26:00    247 n" {3 P' a* T+ Y) B9 `" U1 ]
    Freq: 7T, dtype: int64; P# ~+ }* |9 k* D) J. B

    . l# i  Q1 i) Y# cts.resample('17min').sum()3 H0 j5 C* U& t5 u9 H
    2000-10-01 23:14:00     0  H% U* T0 n; \" |0 R, p8 H6 X0 Y4 U
    2000-10-01 23:31:00     9
    & X1 [& [/ _: k7 ~2000-10-01 23:48:00    21
    - C7 R) P- S+ S4 z2000-10-02 00:05:00    54  _! c3 f& }& O
    2000-10-02 00:22:00    24
    / w0 _) v5 ^. p! p! @! i$ q! [6 w# CFreq: 17T, dtype: int64
    8 a! q5 \# U* w# `3 H$ }
    ( ]8 B" ^9 c# G! j2 wts.resample('17min', origin='epoch').sum()
    : L! X8 Z$ L" e; O1 r2000-10-01 23:18:00     0
    6 i2 }: ]4 f, s0 b4 y2000-10-01 23:35:00    18- Q& \7 S$ C- L
    2000-10-01 23:52:00    27: i& h/ B* ?" P, q) u. w% D; \# V
    2000-10-02 00:09:00    39
    : a1 V- L' D5 |, {8 q+ z, _9 E, |2000-10-02 00:26:00    245 U; A: L4 R" B# y7 R, b+ q( @
    Freq: 17T, dtype: int64
    * B% x: A' Q* q# m) \2 g/ D
    # L4 }  H; ?9 F7 s& m  jts.resample('17min', origin='2000-01-01').sum()' `% k8 C' F* {- G7 P% {' [
    2000-10-01 23:24:00     3
    / f' S* k! ^' Q2000-10-01 23:41:00    15, x' f, ?) C3 M. }  V1 {& g* R: a
    2000-10-01 23:58:00    45
    8 A! B0 O! [) h2 J6 E8 L$ p- i2000-10-02 00:15:00    45
    ) h, z8 @" W1 a; M1 iFreq: 17T, dtype: int649 }' r. J, ~8 q. ?3 n* ^

      S  b6 \7 n4 v, C* x% N1
    - U7 p/ z6 c% b, R2( o7 a, ~" x$ R/ n+ |
    3
    7 [9 j* T  ]/ j4
    + I0 n* O% t7 b5) K* Q# v$ E4 X6 D! {
    6* L% A& g7 m1 B& S# I9 v- j
    7# ?- _( x" {% v
    8( t" d. B; H. u
    95 z+ Q$ W1 [# ?6 E- ]( Z" o
    107 @, M$ v) J( P: @
    11
      R' s9 y/ J4 g& T12# a/ W! N* m$ v$ T6 p/ k2 s
    136 p7 f* _  K4 K% q$ }' f; z
    14: I0 Y9 m/ ]/ ]2 N
    15
    - Y& I, L. L4 ~* L& D* _16# Q& S6 k5 |2 [. j( ?& B
    172 q. Y; {& l& w6 u1 X  }
    180 q' z( {+ U& c( W
    19
    8 {3 u. C! j: ^# B202 ]- n; c/ c6 A2 g! [
    21
    $ f% k& Y6 c9 Q  V! H4 {/ `22- Z5 j: u/ X1 J9 r0 }+ o
    23
    9 G) Y2 F+ m) C- b. H3 h) C244 ^( |. f0 r3 J$ U* I- N6 w& k
    25: j1 M7 O: Q/ J- ?5 Y
    268 U7 D3 M! ~# K5 A
    27
    ' J6 _2 _* Y* r1 b28
    - E7 ^% T) X9 D  |3 ^0 G29
    ( n) {$ ~- t9 b; f( }, C- C30
    - I  V- G) |+ N3 U; l319 {1 B9 Z  ^# p& W
    32
    ' Q( R  K; E) l& D: Y; V8 \339 Q1 [" @9 w- f2 X, w4 \6 P
    34& @; G# ]! C& z5 Z# y3 a
    358 W2 {$ m6 j/ u& v
    36
    . z* }2 C2 e4 H# v  ]4 S37
    + z; z8 p/ S) W/ c7 ]* b2 @; `$ Z如果要使用偏移 Timedelta 调整 bin 的开始,则以下两行是等效的:! Q! e/ D9 o& z5 S1 V6 g" i
    ts.resample('17min', origin='start').sum()& Y5 e3 _7 r3 i  N8 @
    ts.resample('17min', offset='23h30min').sum()
    - |4 `) A2 k8 y4 G- E5 ~3 ]" V2000-10-01 23:30:00     9
    ; ]- S- {" A3 l* R5 x2000-10-01 23:47:00    21$ }# q$ y% p8 l$ N( i6 l- x. J
    2000-10-02 00:04:00    54' [0 T' E% i# G$ b& b2 L
    2000-10-02 00:21:00    24
    7 }, l% n- ~- t9 h1 p% `  c3 gFreq: 17T, dtype: int64! w3 w" r8 U& M2 H  B' x/ K
    1
    ! Y/ g/ I7 P0 B: Y) l1 ^5 k/ V0 C0 Q2* T% N5 t8 B3 i$ ^+ y7 o4 u$ e. E
    3  B" F- |1 t0 `+ Z$ @* O1 h& ?
    4
    9 o+ V0 z, G3 N5# L! j9 K5 x) E
    6# n& g$ j- F! d( z$ R3 Z
    7
    2 y4 ~0 z' x4 O: H10.6 练习( a$ `# @4 p* z' o6 M8 \. [- {3 y  H
    Ex1:太阳辐射数据集
    1 a6 U! w( s1 `: w. V* F现有一份关于太阳辐射的数据集:
    % q( j' X5 K$ G* h: u5 s# ]/ t& U. T
    ; K+ R9 o8 w2 A  _8 X( m  Jdf = pd.read_csv('../data/solar.csv', usecols=['Data','Time','Radiation','Temperature'])
    3 x& P; S/ s  G( [3 j6 S4 Y- fdf.head(3)4 Z4 x4 |( X# H4 G" o5 G6 x
    : a$ _  L2 U/ R" A% v" T
    Out[129]:
    / w/ y& M7 Y( ~+ {5 e                    Data      Time  Radiation  Temperature  u8 C1 ^. U, H6 H8 m3 w
    0  9/29/2016 12:00:00 AM  23:55:26       1.21           48
    ) I$ M* Y' x5 D9 L. I# y! W& m1  9/29/2016 12:00:00 AM  23:50:23       1.21           48
    % K7 ?0 `/ Q6 {9 J0 G2  9/29/2016 12:00:00 AM  23:45:26       1.23           48
    7 \6 F% c/ a$ i" {+ P12 P- K9 [% r4 S% F" B( \/ {: [
    2: ?9 t& ~, V7 c/ f
    39 i8 _$ l$ a4 c! W3 D$ G
    4
    9 d  ~% h1 ]: ~4 ^# o) U( s5 W5' k$ ~7 g; |! Y: @; N% l, i
    6% C* B3 J6 Q* |
    7- ^+ f) e1 m2 S2 e, K9 W
    8
    1 U0 g6 `+ A3 y8 E0 A将Datetime, Time合并为一个时间列Datetime,同时把它作为索引后排序。
    / s# E0 v  d( h$ T' q每条记录时间的间隔显然并不一致,请解决如下问题:! j* q0 m) ?& W5 X
    找出间隔时间的前三个最大值所对应的三组时间戳。
    1 ^7 k/ y4 |5 @+ k7 ]+ ~是否存在一个大致的范围,使得绝大多数的间隔时间都落在这个区间中?如果存在,请对此范围内的样本间隔秒数画出柱状图,设置bins=50。
      H5 T& g" b" _5 y- m求如下指标对应的Series:" L8 T1 H% k6 P
    温度与辐射量的6小时滑动相关系数/ D7 I( r1 W9 q8 _8 v
    以三点、九点、十五点、二十一点为分割,该观测所在时间区间的温度均值序列
    ! C+ a! p" J- U) t7 E每个观测6小时前的辐射量(一般而言不会恰好取到,此时取最近时间戳对应的辐射量)
    - q3 l: O. N. himport numpy as np
    7 e; ^5 U9 ]: h: r: z: Iimport pandas as pd! }: U; g  V( b9 n' ~0 p# S6 S1 {
    1% u: a' Z1 E( G
    2
    5 o/ e7 g* [4 t" O3 d* d" f9 ~: w将Datetime, Time合并为一个时间列Datetime,同时把它作为索引后排序。* j" t3 T. v! ^
    data=pd.to_datetime(df.Data) # 本身是object对象,要先转为时间序列$ k$ N- {2 i2 F4 l# x
    times=pd.to_timedelta(df.Time)
    - l( ^% g& U" \df.Data=data+times9 ~0 j- S0 V; C# P
    del df['Time']. @. }" G' R) L( w8 F7 Q& q
    df=df.set_index('Data').sort_index() # 如果写的是set_index(df.Data),那么Data作为索引之外,这个列还另外保留+ y. ^* K3 W! X; i5 u- Q  l
    df$ K, m; R3 e  v2 e6 R" t
                                            Radiation        Temperature0 s4 M5 w$ ~" m) F
    Data               
    & ~2 w6 }  h) }6 v. X7 y2016-09-01 00:00:08                2.58                51! t+ d- `  v8 E- `4 c4 O
    2016-09-01 00:05:10                2.83                51+ w2 i2 o. ~: {; J
    2016-09-01 00:20:06                2.16                51
    " n- J: ]  \' |' q9 K1 V2016-09-01 00:25:05                2.21                51* e3 Y  l2 N: R; _. m0 x
    2016-09-01 00:30:09                2.25                51" I" ^7 Z0 u7 [3 c
    ...        ...        ...
    : z6 P  Q- u( h# x8 z2016-12-31 23:35:02                1.22                41
    : w7 u' k# t  r1 v9 g! m% x- m# d2016-12-31 23:40:01                1.21                418 n6 M, I! ^! ]# [; }& K
    2016-12-31 23:45:04                1.21                42
    & O! Y8 Z( ?! ?3 U/ F: I/ a2016-12-31 23:50:03                1.19                41
    ' `3 K! m9 r" E. p8 {2016-12-31 23:55:01                1.21                41
    8 b3 `% R  v/ x2 A# y. }, K
    - u5 M" o7 J! F- z( I) l8 i: Q1: f4 D1 O5 z5 @* S1 a0 E
    26 {( [2 S) @1 Q2 c9 i2 [
    37 W- \2 ?" L  ~! x1 j7 \5 Y
    4
    " d8 V9 V, p* j0 t9 l5$ H$ ], b& [9 B4 p. A7 E* ^
    6
    " |) E8 i' g3 _" i7& f$ ]) l4 {# p- |0 B
    8" l/ z: [9 w4 L8 ?% |6 ]* u" Q
    9
    ; R8 V9 Y' |8 M0 D2 A8 H7 t  S10' ^2 g7 U; v) R
    11
    % D' ?. h1 Y0 h12
    5 ?# M' N5 k1 n' R5 [' `13, Q8 u+ B. `3 O" C  y; b2 E
    146 N; N, v, ~) Y$ X* y
    157 T$ c& _: o. G7 l) d" u9 O
    16
    + g4 w2 K3 }* j( s171 F9 m# B" O. G1 i& h8 f# W
    18
    / ~" H* T; O! u* G4 {19
    4 S. k  q: g& W- m2 w/ z# m* N每条记录时间的间隔显然并不一致,请解决如下问题:
    0 r! Z$ j& s; f# [找出间隔时间的前三个最大值所对应的三组时间戳。3 u  J; r8 B, \2 t: W) G
    # 第一次做错了,不是找三组时间戳% v' Z+ v% w) J
    idxmax3=pd.Series(df.index).diff(1).sort_values(ascending=False).index[:3]* {0 K( G0 L! [$ c( ?0 g
    df.reset_index().Data[idxmax3,idxmax3-1]" {9 m  r$ p* N
    1 d. q& j4 c6 X' q3 k2 D
    25923   2016-12-08 11:10:42, b, Z( {- H/ S8 `8 L
    24522   2016-12-01 00:00:02
    % @" E) T+ O- G* ?- f7417    2016-10-01 00:00:198 F* |# N6 b+ e- S6 d% o. L
    Name: Data, dtype: datetime64[ns]3 \; w5 e2 h! Y
    1' ]; M" C4 G8 q" C2 y7 n% A, y2 W
    2
    7 ]! U. I" P* R+ n1 k3' o4 q7 \9 j! R2 ^" D# v1 R: u2 j, c" J
    45 G# p& i3 Y4 Z8 W6 V3 g) |
    5, U7 y3 @) ]; a* B' M0 f& J' O+ ~
    6
    : h# z* T1 U& @& x8 t7
    + @$ @  j6 m& i; m8 K  s8/ s- c: W; O4 U( O! L( j
    idxmax3=pd.Series(df.index).diff(1).sort_values(ascending=False).index[:3]7 u/ T2 K6 B8 Y8 r4 w! e0 r# i
    list(zip(df.reset_index().Data[idxmax3],df.reset_index().Data[idxmax3-1]))
    6 s4 b+ H! k# v" W
    & K; d$ b9 C; S8 x9 s! G! E' T7 N" k1 a[(Timestamp('2016-12-08 11:10:42'), Timestamp('2016-12-05 20:45:53')),
    - R, ?/ C& s3 |( l/ G (Timestamp('2016-12-01 00:00:02'), Timestamp('2016-11-29 19:05:02')),
    * A% f' N7 u, F+ q: ~ (Timestamp('2016-10-01 00:00:19'), Timestamp('2016-09-29 23:55:26'))]
    ; O" G6 u) I; z% @! J/ p1
    7 `8 j3 }  H8 J0 v: Q2
    6 o( q% V( Q6 S0 T: E1 }, X( N39 }; S, [: F! G( G& ]0 T
    4
    # ]& U2 W4 \# y2 {& ]5
    0 q* U; w* I# m- I- m" _/ U. ]! g6, g5 o$ a8 |; _+ w$ G7 n; \" x
    参考答案:$ G' b7 q& R0 V9 j/ t! N
    . m, V8 Y7 w' l5 `6 @
    s = df.index.to_series().reset_index(drop=True).diff().dt.total_seconds()
    - ?, z+ P; n4 F5 ~8 e/ d& lmax_3 = s.nlargest(3).index
    7 y# e2 U! v- U+ ^* R4 x: O6 adf.index[max_3.union(max_3-1)]
    3 F  y" b1 n7 x4 G) {1 u1 d; O% P8 {7 Y9 ^- P
    Out[215]:
    ) w5 {8 y2 v/ z4 N9 F  m% BDatetimeIndex(['2016-09-29 23:55:26', '2016-10-01 00:00:19',( K7 C& {$ Y1 s/ p3 x
                   '2016-11-29 19:05:02', '2016-12-01 00:00:02',
    + v0 v" s# |* q. M! V* {               '2016-12-05 20:45:53', '2016-12-08 11:10:42'],# E# S0 N: N# A  M! K+ h
                  dtype='datetime64[ns]', name='Datetime', freq=None)6 Y* J! B/ Q1 g) W
    1: v7 ~+ y- ~1 Z% B9 k+ s
    2
    8 t" P) V1 G1 R, |) ]3
    " o, P( @9 K# y+ q9 F42 x% W; H- r0 z5 T9 v
    5
    - R: q# d& m! a% e1 Q6
    & V! W- B5 Y  H& o* w% Y# F* ?7
    - B+ |3 q, l0 f- n, M7 ~8+ r) D" l& {1 b; g1 b
    9
    . r  C0 X0 s9 e/ o3 W" E6 a是否存在一个大致的范围,使得绝大多数的间隔时间都落在这个区间中?如果存在,请对此范围内的样本间隔秒数画出柱状图,设置bins=50。
    $ N1 {0 p% A# y0 v! M/ b# 将df的indexydiff做差,转为秒数后排序。再求几个分位数确定取值区间2 k4 Z( N# \' Y% ^5 E; w8 S
    s=pd.Series(df.index).diff(1).dt.total_seconds().sort_values(ascending=False)
    $ G" U4 H6 B! G  L. O6 X0 ?s.quantile(0.9),s.quantile(0.95),s.quantile(0.99),s.quantile(0.01),s.quantile(0.03),s.quantile(0.05)
    : A/ n) y7 {" i9 s* N# s3 w0 c
    / X/ m( L' I6 A(304.0, 309.0, 337.15999999999985, 285.0, 290.0, 292.0)% K% c. L" a' w% Z0 g% O
    1" [) ~+ D7 E  d
    2
    7 a6 S2 Z# |0 {3; x' B) a% ~$ r& ?( U; s* M
    4. x7 k8 I; a7 Y+ r$ W  l
    50 k8 Z  N' l) `1 {0 n1 m/ Q  v* ]0 K$ H
    %pylab inline* a# `7 T, D" S8 E9 L! U3 U
    _ = plt.hist(ss[(s.values<337)&(s.values>285)],bins=50)
    6 S/ w& N% L  p. Fplt.xlabel(' Timedelta')
    ! U" q( W" C7 n  b' ~plt.title(" Timedelta of solar")
    % L, b  T( l/ [8 X* X1
    5 Z9 I7 |# n8 j* `2
    # ]8 s) n; r' l2 K+ C0 n. |. g4 E39 k9 I. s/ B- [3 l8 K! L" K
    48 e  q& l. Q9 \( }( A
    + M- Z* k  e# _+ P3 p6 W% p
    - s9 u8 C# W  i% S) r. f" u
    求如下指标对应的Series:( G0 L- e# Q0 s. v  c& A' Z" X
    温度与辐射量的6小时滑动相关系数
    5 Y2 ]. ^6 D* Y& L' m以三点、九点、十五点、二十一点为分割,该观测所在时间区间的温度均值序列7 g( a7 t' ]8 ]
    每个观测6小时前的辐射量(一般而言不会恰好取到,此时取最近时间戳对应的辐射量)
    9 \9 c( g! F6 E: L; zdf.Radiation.rolling('6H').corr(df.Temperature).tail()
      ^: Y0 S! J$ R& y, c0 B7 W# H( s8 z$ \) f* N1 t- G  i+ m
    Data# l# J/ ^* i1 ^
    2016-12-31 23:35:02    0.416187
    3 n4 b+ A$ O" D2016-12-31 23:40:01    0.416565
    $ N  L  O0 ]9 q( g2 V2016-12-31 23:45:04    0.328574
    ) |& Y, a) X+ T9 t! B2016-12-31 23:50:03    0.261883
    ; ?! h1 }8 Q( |2016-12-31 23:55:01    0.262406
    8 v; u& `' M7 q) t0 b2 Edtype: float64+ A* {( u+ {6 _4 F# i& g3 u
    1$ K# h* T4 F7 ^3 \0 W5 g
    2
    ( b* }! f& P: Y6 E$ k) H0 r3: b+ w3 J. o( H' d2 h
    4
    & d0 \. x) `# G: J59 I4 @2 L; `( n& u
    6
    6 n6 X8 N* e) U7
    $ ~" `  H& a1 `7 Y- r9 Q, g( E/ ]8
    1 t2 S* E7 v1 B3 `& V& f9
    8 B0 o; L* _6 h7 |' zdf['Temperature'].resample('6H',offset='3H').mean().head()  a* H8 x5 X1 h) W
    2 d; k: l- Y3 ?" |) _9 a
    Data
    , \2 E6 f& Y6 A, n% u2016-08-31 21:00:00    51.218750# V1 C. _) U& A
    2016-09-01 03:00:00    50.033333. k& r9 W" N, V, q+ K
    2016-09-01 09:00:00    59.379310& [- m8 a! R& x7 g+ s
    2016-09-01 15:00:00    57.984375. u. X9 _3 v8 Q1 j, v+ F$ G
    2016-09-01 21:00:00    51.393939
    ; g- Y0 O- y1 [8 Q0 c0 u# _# XFreq: 6H, Name: Temperature, dtype: float648 M; t, x1 V7 W$ ~; T& c: P" _; A- L
    1
    6 G. X) I1 u& u( t, q2
    - r1 `9 q" Z5 N% y3% u' p) l0 W3 s% w, @5 T) T; l
    4
    " p, x  E3 ^% s; y5& `3 [  ?2 D, ]% L- i  r
    6% p* P* k0 F! A/ M( X. [1 @
    7
    5 e+ X2 `( V  O( {8- C% K* x1 Y& \' p7 H
    9$ I* g  ~/ ]1 d8 c% }3 k4 I+ g+ `
    最后一题参考答案:
    " n' T' e$ `, i5 s& P! R" V6 l/ k$ g& P& W, T
    # 非常慢: H! s, }& M8 l2 m
    my_dt = df.index.shift(freq='-6H')
    7 h3 \4 L% j' a0 S3 g% x4 o9 gint_loc = [df.index.get_indexer([i], method='nearest') for i in my_dt]
    # Z9 W$ G7 T2 }$ qint_loc = np.array(int_loc).reshape(-1)4 f6 @) H8 ^0 o
    res = df.Radiation.iloc[int_loc]
    ! q; S9 `' s4 K. H. p0 e9 ?res.index = df.index$ L$ P& w# E0 q1 {8 V7 k1 ]+ Y
    res.tail(3)
    2 s+ N( e. z3 ^4 H1! {9 W3 W% M4 p3 O# I5 ?+ E
    2+ c& O4 u' U: r( B
    3
    # y, @  P+ n7 U, R4
    9 r6 s  c2 M, _8 s( z- o4 B7 o5$ u. _$ D5 C# K* ~7 q% k
    63 }% I4 O. c& E! M5 q! V2 l) U
    7) x% i1 {* L* y- f% l
    # 纸质版上介绍了merge_asof,性能差距可以达到3-4个数量级
    ; Y3 ^0 F# G. |0 Btarget = pd.DataFrame(5 R$ N" R" o/ U! T2 [
        {% T: ~' N+ O* t; m) O
            "Time": df.index.shift(freq='-6H'),& K" q% [7 o/ r1 Z/ ^
            "Datetime": df.index,
    $ X  _( m' n* X4 f' o0 x8 a0 t    }9 f2 B) V, e/ |; J
    )8 ]3 @9 Q3 Y' Z. |  t  h7 B2 ]2 E. r
    , Y2 }: e0 J) h* X& f/ g
    res = pd.merge_asof(
    / p) n0 E5 a: t# X/ I    target,
    ; M/ t1 a, f- [7 f# x/ a    df.reset_index().rename(columns={"Datetime": "Time"}),
    & I6 u- G& y  t/ a& `    left_on="Time",: b5 P/ h  m( N8 [
        right_on="Time",) Q. R; z7 a6 Q
        direction="nearest"# M* \! J7 F8 Q; ~' K
    ).set_index("Datetime").Radiation9 \) m3 V( Y" e, [
    $ z+ f  \2 W1 Q- V" O8 g
    res.tail(3)
    8 D7 g" e/ T2 VOut[224]:
    . [0 N. i5 h1 z4 N  JDatetime; I: S2 i4 W/ J9 K6 Q6 L: I4 R! E
    2016-12-31 23:45:04    9.33
    ; ~: J1 J5 }/ h5 q, f2016-12-31 23:50:03    8.49
    ) _  a6 ?5 ~' T8 o9 `' s* L. V2016-12-31 23:55:01    5.84+ e9 i5 p: @: G5 L1 w7 R
    Name: Radiation, dtype: float64
    5 |- o! K0 K) J( l# p& g8 o; |/ R. p; Q* i2 ~
    12 u. Y/ Q, @/ a
    2  k" q, C9 C. v: }/ M. R: Y4 P6 |
    32 V: r# _& c7 K, B. R7 s
    4+ h0 X! u8 U: w8 V0 h/ H1 a
    5
    ! S0 L% ?/ }6 F( p+ O* m6$ i8 [6 u4 r; h) B$ [0 B6 t" E5 ^
    7
    1 n, Y: N- ~; H! F, [  e8
    2 X1 X! w4 Z4 t1 L- x1 D0 X9' ?' W3 i/ t4 {
    10
    ' V' C+ e' Y2 T$ _3 ^11" |- b; T2 ^' H3 T1 J' C
    12
    , c8 D: W, P+ Q  V) ~5 n: w3 O13
    $ I4 l4 x9 C  @$ N/ T146 g6 E8 ]; r: ?7 Y! b/ _
    15
    . E1 X8 {$ k3 e) N- m" f6 R16
    + d% M3 v8 w% O& Q  a17) N4 I. D& A" o; d* J. k
    18
    0 G" I( L) ^! [# u4 n. x19
    " H5 O0 B: a! M! L20
    % P$ _: o6 ]0 T! C- q21
    $ m1 D" [4 g% |  l22: j: j- a0 M1 g( a" H% {& v
    23
    $ u7 o$ [  n+ _/ n0 oEx2:水果销量数据集
    + g/ G% a* a3 N; y' V- B现有一份2019年每日水果销量记录表:. a' p0 w  o7 j/ J
    : u- c7 c% L5 l% {/ t
    df = pd.read_csv('../data/fruit.csv')8 I, j; Y6 N2 h9 r" Z* a. j
    df.head(3)
    0 ^# _: |! s2 I' r( {1 _$ l" E; ]
      n! G8 q9 p5 L2 z' vOut[131]: $ h8 y( D% T, k0 ?% m  D
             Date  Fruit  Sale7 A2 Q% C3 h! s& ~' x  I- ?
    0  2019-04-18  Peach    15  o' c/ H! L8 O7 F/ a8 i
    1  2019-12-29  Peach    159 L! }4 t: n* n3 L# K% g
    2  2019-06-05  Peach    19
    7 ?5 a8 P* v% }* @1! j# I/ i# d: S5 z; F9 K6 t2 o
    20 B0 {8 k3 G9 L# t$ a3 E" U  D
    3
    3 y4 `4 _( N; ^: B4
    % i% g7 h; Y1 t4 |5
    ! V# ^( [: }+ k* b& s6
    % @/ a: r4 a" e" D+ N7 ]7. \# u0 l  V* N2 P
    8# ]; e; H9 O0 m
    统计如下指标:# R; M2 _: O7 ?1 _1 r+ M  B
    每月上半月(15号及之前)与下半月葡萄销量的比值# ]( B4 I& I1 h7 H' V' c
    每月最后一天的生梨销量总和& I; D/ c7 {& m0 C8 a* b
    每月最后一天工作日的生梨销量总和
    * p( g! h  p+ O* v) i" }) P每月最后五天的苹果销量均值. R4 ~5 T2 |/ }& ]' g8 \
    按月计算周一至周日各品种水果的平均记录条数,行索引外层为水果名称,内层为月份,列索引为星期。, k; x1 M2 U& B0 W% L# M' p- t4 s- }; a
    按天计算向前10个工作日窗口的苹果销量均值序列,非工作日的值用上一个工作日的结果填充。
    $ Q  n% c5 P5 Uimport numpy as np  c+ z. ^, a3 w& I! ~
    import pandas as pd
    & g+ Z. l4 k: B, s0 \1) P8 ]4 P% R. y$ t! x) D% O0 n2 `
    2
    1 ]  M( E$ q& w* ~统计如下指标:
    7 O: d/ b% e* c7 i每月上半月(15号及之前)与下半月葡萄销量的比值- i& }/ ~5 q. Y0 j; L  w7 ?
    每月最后一天的生梨销量总和- F6 ?/ ^1 p5 M, a  B6 d+ i
    每月最后一天工作日的生梨销量总和
    , `1 i5 ^. l- ]每月最后五天的苹果销量均值; b% N. m2 _% {: R3 ]- `/ a
    # 每月上半月(15号及之前)与下半月葡萄销量的比值
    / G1 L8 E+ \5 }, x/ F9 \df.Date=pd.to_datetime(df.Date)
    : |- W2 ^, `4 z+ m0 z% F& fsale=df.query('Fruit == "Grape"').groupby([df.Date.dt.month,df.Date.dt.day<=15])['Sale'].sum()
    " y3 M' o' X3 \3 V! y0 w0 C: Zsale.columns=['Month','15Dayes','Sale'] # 为啥这么改没用啊
    8 b  y7 y0 b5 s% ~+ Msale=pd.DataFrame(sale), ?0 g+ s" V( }
    sale=sale.unstack(1).rename_axis(index={'Date':'Month'},
    / K& K# t" `6 k: e$ Y& h# j9 l                 columns={'Date':'15Days'}).stack(1).reset_index() # unstack主要是两个索引都是Date无法直接重命名- ^* [, C- y; |9 \% X4 v
    sale.head() # 每个月上下半月的销量: x( V3 Y5 F) |) Y/ u

    ( V" h7 r1 `1 H! j1 X  Month        15Days        Sale$ k* `  z  V6 |2 j- b$ |
    0        1        False        10503$ m+ h# N4 m1 i% h' V7 E4 G5 L
    1        1        True        12341
    1 L9 V% u9 U4 U9 Y9 S% n2        2        False        10001
    ; C0 I* r& A2 g* x3        2        True        10106
    1 x& m- i2 E- W( ~- D4        3        False        128146 W- {, k6 s- w$ Q

    6 ~( A5 @' ~& Y$ a" _( y  |4 a# 使用自定义聚合函数,分组后每组就上半月和下半月两个值,根据索引位置判断求比值时的分子分母顺序& Y/ |# R( X+ M! p! x
    sale.groupby(sale['Month'])['Sale'].agg(
    # ]: o( g+ |' j0 A- [6 h8 Z! e                lambda x: x.max()/x.min() if x.idxmax()>x.idxmin()  else x.min()/x.max())7 n# n1 F: Z, V/ `
    # z8 l6 g5 s. ?" G' u' f
    Month
    + Q+ j4 l3 C! X6 ?1     1.174998
    5 L; h4 L6 k: _2     1.010499
    " [6 S# V. G/ ~, D2 Y$ I3     0.776338
    ! O( q+ }9 P5 a4     1.0263458 u. \0 d7 L" q2 V
    5     0.900534
    : [6 b) ]3 T, b7 E* w' p6     0.9801368 E0 ~! f% _1 j
    7     1.3509603 P8 `; A  k# J& A" E0 J
    8     1.091584
    * H6 v& U* N9 ]7 \9     1.116508- l2 D5 d  \" F
    10    1.020784& T" l$ t3 ?: Z9 P
    11    1.275911
    4 E" p8 o* N2 M1 _8 |, _12    0.989662+ |. i% J  O' _+ `7 N) ?- x5 K( }
    Name: Sale, dtype: float64
    & `" @4 V+ S+ K( v: f3 b
    , _9 s4 k/ z0 k1 P) u1
    9 V6 Q- \. _& ~+ O5 E2
    / l0 ^. _: e1 e6 F3
    ; ]$ p5 J( z9 F. B1 }4
    $ M: f+ ]+ L" |( M1 d' B# d( O5
    6 I# J6 w: ~# a9 v3 ]% u6
    5 n  P1 s# E4 G- F, o7, J* I" F/ B' M3 ^# d, k8 g$ X
    8& \' Q. Y1 s" ?+ y2 W' h
    9
    2 \  ]% j5 ^/ I/ M: M+ }10
    6 n9 w$ Q  I6 l' n9 t5 s11
    : Z+ o! z: R, C12
    + a! X, M/ ?" U) c% e8 O, {8 I13. ^- U. z3 L# B7 B/ ~7 r/ w& y
    14
    . s1 @* s: k+ u( @8 x- h3 D159 h5 d, Y- u4 z+ \4 p7 [
    16
    8 s( o8 p/ Z* }8 u17; _  O, Q& [: J& O
    18
    ; E  H7 v6 z- y  J6 D4 P* T: U& g19! _& A8 P+ X, T2 c, k% w6 n8 L
    20
    " V1 R$ M2 r: b8 |, ?( a" v217 l- h' x6 ]. s( |1 P
    22; w. g4 M6 A( k' b
    23! x8 f, F1 Z* m. g7 ^- x" R) T
    24
    . d0 n& K/ _7 p/ C9 b25% N1 v) G4 n( E+ P4 j) t. N
    26' U7 b8 s" }6 v$ s' \9 r
    279 ~4 j9 h; f* ]3 y+ B* l2 B8 M+ z
    285 ]" o$ N* O, i
    29# Q+ x( w7 O, w0 O6 ?
    30% ]/ V$ E: Z0 i, h) f
    31
    + u$ w4 U0 h# _" x- ^( m# }32( X4 l+ q4 {  p+ h: E& _
    33& a4 j' k& z* P' }
    34
    & p" U: F  H$ T# n8 q# 每月最后一天的生梨销量总和+ ?1 c. o4 A9 f2 O. N% N' h
    df[df.Date.dt.is_month_end].loc[df.Fruit=='Pear'].groupby('Date')['Sale'].sum()  V: B  w; H8 L6 z8 ?0 c

    8 f! r" o- d) s7 L" l# DDate) A: P! C. V6 ?" M! Z. N
    2019-01-31    847
    ' d0 ~( [, W; n) J3 c; Z2019-02-28    774
    7 q4 P( p$ L4 ^6 c+ a2019-03-31    7617 j( g+ f* ~* w2 e* ?/ o8 o2 K# q
    2019-04-30    6484 v& L% B, B& r/ G5 Q  M9 z
    2019-05-31    6163 `. E2 ~1 n* A* a3 ?3 w
    14 K2 }" j3 L6 j: `; d  C: b% a" l+ x! r
    20 r3 r; J* R: _' ~8 v, R; h9 _
    36 }1 T" S! \( |- s4 G4 K
    49 R* t5 A: \' n8 S
    5
    % j3 G. W8 B+ @6  c1 }9 I/ ^; t) n+ ?9 B
    7
    . w1 M/ h8 W; T  e8& \+ x) j& R8 ~2 f" Z" f9 ^
    9
    - K2 E/ M8 H! V. S0 Q0 X# 每月最后一天工作日的生梨销量总和$ ~5 [# S0 H: S2 U% j9 d0 e
    ls=df.Date+pd.offsets.BMonthEnd()5 C1 K- E( z$ [: o9 M. b3 y
    my_filter=pd.to_datetime(ls.unique())
    0 K- |" s4 p/ f1 K/ Gdf[df.Date.isin(my_filter)].loc[df.Fruit=='Pear'].groupby('Date')['Sale'].sum()& M1 Y% X2 ~0 [; {2 y
    . C& S; P! t5 P/ |2 C4 Y
    Date' p/ F9 g. m2 @7 u: P, F7 i/ O
    2019-01-31     847
    ; y+ a4 ?6 t8 m5 j0 z( S1 s, X2019-02-28     774
    # Z9 P5 I5 _' k7 Q% X2019-03-29     510
    & u( f) k+ `$ z8 L% o3 S2019-04-30     648
    ! L8 Y% f) v6 f  h7 Q0 m2019-05-31     616
    * Y% F" }: F& x4 y( A1) z6 l( K# ?% M+ |4 J
    2
    1 J* Q2 o  @& U37 D1 D9 H4 u* y' ~. ]
    4( j1 R  X( D( [0 l8 E8 u
    5
    3 h5 k0 X: B5 s3 Z6
    ! T, N( v8 W4 `# J4 g. G7: d" b0 ^, O. h% b# v  t
    86 G6 P$ {: s4 `9 @; C
    91 d" L* [( a" F' R* z
    10, H  \# ^& f+ y' F' v  d3 x/ |- x
    116 g2 u) t+ p3 O  A' C! D/ @: t: U
    # 每月最后五天的苹果销量均值3 e4 L0 Z# T, t" e. e
    start, end = '2019-01-01', '2019-12-31'. a$ t  ]8 w2 U/ G, [
    end = pd.date_range(start, end, freq='M')
    % I/ R, X8 Z9 rend=end.repeat(5) # 每月最后一天的日期列表,重复5次方便做差
    . F% `. m, F' S% s  o# a# [" M+ q' s$ p: ~9 e5 L! E) X9 W
    td= pd.Series(pd.timedelta_range(start='0 days', periods=5),)* Y4 s5 K" i% `+ d
    td=pd.concat([td]*12) # 日期偏置,最后一天减去0-4天0 x9 b) \8 K+ l% H
    end5=(end-td).reset_index(drop=True) # 每个月最后5天的列表( b$ @( q& e7 |
    ' @. n. N9 E. }% Y' k; P, P
    apple5=df[df.Date.isin(end5)].query("Fruit == 'Apple'") # 每月最后五天苹果销量
    & ?$ Q' Q: u3 Q5 e* ?apple5.groupby(apple5.Date.dt.month)['Sale'].mean().head()
    $ J  H6 ]- o' L
    2 o" F& H; X7 F# b1 FDate: N8 Z; C" N4 X; {0 _
    1     65.313725
    / y8 V: E; K6 X; L2     54.061538; i# i2 c) D, a5 @) w
    3     59.325581) Q4 O/ R, V: }# m, ?$ X3 W
    4     65.795455
    # K8 j  M5 O8 C6 _$ L5     57.465116
    0 ]1 n) B5 \/ W' ?$ L- A" \" r  ^/ B6 v$ P# N# g, ]+ Q0 D, D; f8 w
    1
    ; u+ {/ N& |3 K( ]( c3 |4 D2$ w% O/ z8 w+ u  W4 _
    3
    . {7 a  c' @: p47 S* S3 d; k% ]; L
    5- `; y% D0 D! g
    6; R  }7 q% G/ q6 j1 |
    7' }6 O+ m* _3 G
    8
    * c/ k% D! P: h; ]0 f/ T0 A0 w& [  l9
    4 a/ x' Y3 L2 O$ e. t10
    ; h# w6 y7 w- f* c11
    5 w) z% v( ~* T* E: u12  p: j0 ~) |: H9 B
    13: C: h8 p3 y5 H
    14
    * T6 b; e4 _  j. y% t0 }15! x' x" X" o  K
    16$ S4 X0 k- ~. K, {  ^
    17! D6 W1 p  x7 y, f1 P3 v5 D$ l& e& i
    18' N6 `/ g% V6 ?8 P# z
    # 参考答案:  h8 p, C8 C: U  y
    target_dt = df.drop_duplicates().groupby(df.Date.drop_duplicates(: y& f4 D1 T$ O* o9 q6 w. O
                ).dt.month)['Date'].nlargest(5).reset_index(drop=True)$ c/ S4 n/ u; B+ z2 {

    4 F1 f  l. D' fres = df.set_index('Date').loc[target_dt].reset_index(% N# U1 }, d4 P# S
                ).query("Fruit == 'Apple'"). g6 G+ b3 ?+ x3 N7 d7 @- {7 V

    ( }2 q+ @" W. B: v! Sres = res.groupby(res.Date.dt.month)['Sale'].mean(
    ; l" b# n  v: Y& b0 ^            ).rename_axis('Month')
    ; s  H. \. k9 K
    ( M6 @: _. J& H4 B0 a0 f# [! \9 k( a' }; V  S3 `- m
    res.head(). y7 c4 c, |. R
    Out[236]: 5 m; t7 ^, E6 E0 T# l9 D3 g
    Month8 [( r5 ], n" O
    1    65.313725* A' A5 q2 k! @. j* ]) |$ B
    2    54.061538
    $ Z2 s) j% u- p3 J* l8 B3    59.325581
    % y; V. r7 _) j" y9 l& j; u4    65.795455
    # H- q2 c* s, B! X+ _- L; k5    57.465116
    4 {8 M1 J% d9 ~) H  t: U7 kName: Sale, dtype: float64
    : ~# f$ E5 t  w- c% [9 u, i/ C: d8 R4 B/ ^
    1
    / ^7 }8 [7 p8 s! U2
    6 }3 `) c- p2 r. A3
    " H5 d! u0 M. X% M  h  w7 n4
    . H) P: k' O; t1 Y4 F0 z5
    * U% F/ q! V  e! ?" f3 _  ^6" S/ `; v2 a  A: d, ~3 ~7 ^
    7% S7 K) w( }7 F1 H% B# D8 {9 t
    83 V9 C5 @2 b* x+ I- Z
    9; n0 D: `+ m# `  E5 `; Q
    10
    0 Q9 H. u! O0 P3 i. W11
    $ n/ e9 q2 k0 d& e  F9 `126 Z' g( ]2 K# i2 B$ ~
    13
    - P. y3 J6 P8 x+ L. X( S14
    9 r+ c  b& V* a4 {6 E* e15
    % [2 Q- w! v. A3 O5 C. m- ?16+ q1 w9 P8 P: I7 D
    17
    " v. w* v. F; e6 D9 P18& }6 b2 `4 \9 p4 |5 C" \
    19
    $ L, U; W; o; b+ Y& p; b20/ K7 ?" m8 `* f" ~
    按月计算周一至周日各品种水果的平均记录条数,行索引外层为水果名称,内层为月份,列索引为星期。* X' {& u+ p1 t/ I4 j/ V
    result=pd.DataFrame(df.groupby([df.Date.dt.month,df.Date.+ ^, W1 X$ ?9 f9 q+ x
                                            dt.dayofweek,df.Fruit])['Sale'].count()) # 分组统计 ) |9 o  M  Q# m! i; Y3 L) k
                                           
    / v( ^0 M1 K& U7 K1 Tresult=result.unstack(1).rename_axis(index={'Date':'Month'},
    4 |1 o4 @3 }; g, U# s                 columns={'Date':'Week'})  # 两个index名字都是Date,只能转一个到列,分开来改名字.9 L" O6 e8 ~) Y; K5 k: M4 w4 ~8 S
    result=result.swaplevel(0,1,axis=0).droplevel(0,axis=1)
    - ?# D( c9 ?0 A( F- r1 _; j( {result.head() # 索引名有空再改吧" r& p! }4 f5 s" ^! {
    % V7 l8 w$ M9 z
              Week        0        1        2        3        4        5        6; W4 F, T; x/ s" P
    Fruit Month                                                        ' L- L4 B+ H  B  s: E, i* i* J
    Apple        1        46        50        50        45        32        42        23# g0 l' P, b4 ]5 L) x8 V$ u: M3 G
    Banana        1        27        29        24        42        36        24        35
    ' f" y' F# @" y  e9 V, e' zGrape        1        42        75        53        63        36        57        46
    . B0 Y% k2 Y. Q! A( ~Peach        1        67        78        73        88        59        49        72- b6 \+ X: T" Y, d7 K' J8 D
    Pear        1        39        69        51        54        48        36        405 v  d, R8 Y; k1 w' ^
    1. u# ]* e( h' z; Q9 T8 k0 t$ S: p
    2
    ; G6 Z- U& I4 `, `' g; H3
    3 ?7 G; I' B% U  d& Q0 v  u0 t4' o/ M' W, s0 D
    59 K* K6 b% }0 `6 C; |
    6
    ( R. i4 n$ }' d& U$ h7
      m/ T* i' N; V8
    6 H% O3 C! U4 I4 Q7 s9
    2 b  F# I" A4 \106 a9 [2 u9 B" l' Y, B( p
    11* \/ G/ y, t$ C" O% v6 @( r" ^
    12- X2 m9 k( y$ n9 W  t$ e7 }( q
    13
    0 N. ~, o' |% x8 U8 t0 v8 L14
    / J# ~# N7 Q% O2 ^0 I( X15' V6 B: B, K& _& V. f7 @# j8 h: a
    按天计算向前10个工作日窗口的苹果销量均值序列,非工作日的值用上一个工作日的结果填充。
    * a6 @1 q$ N1 a; y- @# 工作日苹果销量按日期排序: H% h. G9 F& @7 s- Y2 e7 W8 H
    select_bday=df[~df.Date.dt.dayofweek.isin([5,6])].query('Fruit=="Apple"').set_index('Date').sort_index()' T+ ~; z% H9 T8 @
    select_bday=select_bday.groupby(select_bday.index)['Sale'].sum() # 每天的销量汇总9 u7 k* z& ]/ ^$ H
    select_bday.head()4 k% I0 n1 v5 h7 y# H3 \5 ?- y( |

    $ b7 j6 f, J7 w! ?6 s3 K7 i, z6 SDate
    9 E6 C3 K. G; D; q; G9 r2 O8 d( w5 f2019-01-01    189
    3 w0 L- [$ u$ v( H6 {/ [2019-01-02    4826 ?- k  i1 k+ g9 E+ T% h
    2019-01-03    8906 k, J( ^& }* o: U
    2019-01-04    5502 D% u! e3 @! K7 s/ O
    2019-01-07    494$ J6 n! z: v, _  p" M1 c

    : k' s" V, a) M" L% P# 此时已经是工作日,正常滑窗。结果重设索引,对周末进行向后填充。
    5 K, ^/ _  ]; T$ Vselect_bday.rolling('10D').mean().reindex(df.Date.unique()).sort_index().ffill().head()
    3 X6 R2 P5 j% ?7 m6 X' |( |- i/ @0 C: v$ k$ t; p7 ?2 m
    Date! i7 n1 d. g. L# O7 N2 w; m
    2019-01-01    189.000000
    - c* H8 T* _3 x% \& k: z2019-01-02    335.500000
    9 T8 w* ?/ L2 h: W  n2019-01-03    520.333333
    : N: Y0 c- t; L  F2019-01-04    527.750000$ |# ?" R; {8 v! O
    2019-01-05    527.750000+ j: m) S$ b$ S9 y  v& B
    4 [: t" W& M$ C. P% B& M
    ————————————————
    8 s% n7 y1 O! b8 X版权声明:本文为CSDN博主「神洛华」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
    2 Z7 i1 k  k) u2 ^% J原文链接:https://blog.csdn.net/qq_56591814/article/details/1266339134 k5 R6 y6 @, K- g
    1 D3 [. S+ K. v% i! r, t
    . I! ?4 O7 ~5 C  ~9 N8 L
    zan
    转播转播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-4-10 10:48 , Processed in 0.574689 second(s), 51 queries .

    回顶部