QQ登录

只需要一步,快速开始

 注册地址  找回密码
查看: 2868|回复: 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

    ! V; ?5 a9 y. W9 a# X9 ^
    - u9 B& K% E6 J2 W
    , e7 X# [5 f9 c6 T4 I4 W$ w文章目录
    ' `2 y8 i/ z2 Y/ C+ Q( n第八章 文本数据
    ! G: s* D& S! D  [( J8.1 str对象
    , I: I- {/ n, m6 |8.1.1 str对象的设计意图$ F) O1 S4 F. n, f5 [; h
    8.1.3 string类型# U. y0 E) L1 A. j( M
    8.2 正则表达式基础( Q; a, G/ l+ ~! `7 {3 B
    8.2.1 . 一般字符的匹配
    5 C/ I6 N( c& \9 I) @) V8.2.2 元字符基础1 b( l' s7 E# r6 h. }
    8.2.3 简写字符集; ~. ]5 w8 X$ c9 s( q" [7 u# e8 x
    8.3 文本处理的五类操作
    / j; c7 v' A+ @( u8.3.1 `str.split `拆分% C! g5 k1 b1 {, z
    8.3.2 `str.join` 或 `str.cat `合并; V/ Q, h& n& h5 `: M/ r9 N
    8.3.3 匹配  _/ g3 k2 k! z( @8 B6 ?# L! y
    8.3.5 提取4 r$ J4 D( l! `/ x
    8.4、常用字符串函数- h& v9 W9 P0 ~
    8.4.1 字母型函数# U0 D& V  W  j; T$ U
    8.4.2 数值型函数6 ?; J- i1 a; g# p6 Z
    8.4.3 统计型函数
    8 p! ^3 @  O, z( F  ^2 f) c/ {/ {2 j! ~0 ?8.4.4 格式型函数  h; P: t. E8 E! B& _" \/ a
    8.5 练习& L) u$ V9 e0 z. C, ~% W
    Ex1:房屋信息数据集* p+ _! Y7 F, r" d4 T9 {
    Ex2:《权力的游戏》剧本数据集/ C( P6 w. k! u* X6 [; V. l1 E& P: z
    第九章 分类数据
    ( [, b* Y$ h- g4 e& c+ E, m9.1 cat对象4 ^& D/ S- L4 }- o* v1 S3 `0 u1 V7 r
    9.1.1 cat对象的属性: ^! T6 z7 o; d! I3 V+ K
    9.1.2 类别的增加、删除和修改$ T3 I2 @$ {! r' K) o7 ^
    9.2 有序分类
    & c' n2 E. C! P. w; l9.2.1 序的建立1 H) V8 i$ }* ]; O2 R0 j8 f
    9.2.2 排序和比较
    0 _( E* ?+ Z: f7 T) u. e8 ^9.3 区间类别+ Y) Q' c% J; Z
    9.3.1 利用cut和qcut进行区间构造
      c8 m4 C5 u" i3 ?$ V$ v6 q: }9.3.2 一般区间的构造
    # V8 d0 ]& S; I; ?9.3.3 区间的属性与方法+ m0 ]  h- z7 l9 L
    9.4 练习  }$ l5 F" Y+ J1 Y2 e8 N( M
    Ex1: 统计未出现的类别
    $ m# O$ Q, b, i+ hEx2: 钻石数据集
      m+ U8 F6 l1 g第十章 时序数据2 e1 K2 n) K; O; K5 R
    10.1 时序中的基本对象
    ; }9 E! z# j( {; n/ A# W* c' ?- O10.2 时间戳& S6 I8 J, c7 }' n$ Y! {) W
    10.2.1 Timestamp的构造与属性5 [0 s6 _. m6 p, E
    10.2.2 Datetime序列的生成, E! X  j+ p+ j1 ?* e
    10.2.3 dt对象3 I6 |  j$ C- |
    10.2.4 时间戳的切片与索引
    $ e% ^' M% q" \* k10.3 时间差) U7 q  e4 Y7 @2 Q
    10.3.1 Timedelta的生成3 k4 H2 x& b( u
    10.2.2 Timedelta的运算8 m* \- f- d. _6 o! |
    10.4 日期偏置9 r* M& f* O4 e5 i
    10.4.1 Offset对象
    ' `5 \& B' r" }6 C7 c* ]10.4.2 偏置字符串. G, s2 S' S/ ~; {
    10.5、时序中的滑窗与分组4 ]: Z4 i; @8 C& [0 r  S
    10.5.1 滑动窗口
    & i  k5 O. X5 g$ ?$ s10.5.2 重采样8 M+ U( n1 Q8 ]- Q6 |* F$ z
    10.6 练习) z/ r" j( v/ o' c! o' g
    Ex1:太阳辐射数据集( P6 f) B% r' i  E0 n
    Ex2:水果销量数据集
    : `' Q( ~# Y: O% {- ^  课程资料《pandas数据处理与分析》、github地址、讲解视频、习题参考答案 、pandas官网) J; p. G3 T, y: x+ J  k
    传送门:8 P1 H! Q; X5 V0 t( T6 f& C
    : A  a) J( s- l9 z0 B9 |0 c
    datawhale8月组队学习《pandas数据处理与分析》(上)(基础、索引、分组)
    5 ]( Z& C4 Z8 ^datawhale8月组队学习《pandas数据处理与分析》(中)(变形、连接、缺失数据)& ]+ q5 O* J% E8 K
    第八章 文本数据* {6 H/ {6 X, o+ \+ F
    8.1 str对象
    1 C; b0 }; a1 Y% p4 Q' h, @  V8.1.1 str对象的设计意图# r+ B, Q/ a8 p  U$ b
      str 对象是定义在 Index 或 Series上的属性,专门用于处理每个元素的文本内容,其内部定义了大量方法,因此对一个序列进行文本处理,首先需要获取其 str 对象。在Python标准库中也有 str 模块,为了使用上的便利,在 pandas 的50个 str 对象方法中,有31个是和标准库中的 str 模块方法同名且功能一致,例如字母转为大写的操作:& F- ~) ^2 `! Z  z+ ]+ ~

    1 f1 m: k: \. \0 V2 u1 o7 K; C9 jvar = 'abcd'
    ) |$ Q  O8 C9 b/ W% \) O+ Vstr.upper(var) # Python内置str模块% q4 i3 n, Q3 i# x; I
    Out[4]: 'ABCD'
    " u/ f7 n3 _1 s
    6 T3 D( e) ~, Q0 ?! us = pd.Series(['abcd', 'efg', 'hi'])& z. ?3 U0 k( V* Y$ ?
    6 w  n3 q9 Z1 q% U& [
    s.str' Q+ w. ]- n8 R, ~8 c& V( ?" p! v' d
    Out[6]: <pandas.core.strings.accessor.StringMethods at 0x2b796892d60>
    9 |1 G) h; Y  ?4 b8 u( \( H/ D9 z. l% @  }! |: O- k. N% w
    s.str.upper() # pandas中str对象上的upper方法
    ! H$ r" I) }0 ~6 nOut[7]:
    , b9 |$ O7 a+ c$ L+ _; U0    ABCD
    0 ~' Y  V' r# v: ]1     EFG
    ) j% _9 Y8 x. O  I* I+ q( g1 x; d3 z2      HI
    * l' s+ B0 d" W+ ~, A5 g7 [1 sdtype: object* @8 y& ?: s3 T: J' l
    11 a" t8 A3 A6 [
    2
    1 r9 _) \  d! b: W3
    - q9 f+ M; c) s) i$ W4
    ( e' E# p8 j% y! D5& _3 C" A5 n8 f& _% f  g
    6' f, k& q6 r+ \9 D0 V. G
    7
      c% p% |; w% ~4 L# Z6 Y% v2 H  f8
    4 U4 _0 j4 O& s5 ?3 m9' V% O, X+ ?& B# n5 g
    10
    / t+ g$ n( O9 t/ {( \8 m11
    $ L! }2 g* c. H12
    & t+ i# V' T' l3 q8 D* o13  |: b# E) A* O
    14
    2 m& }3 |$ S4 x2 Q15
    3 R) n1 j& b# \2 d" ?. `8.1.2 []索引器5 r8 K- e' n+ y% p
      对于 str 对象而言,可理解为其对字符串进行了序列化的操作,例如在一般的字符串中,通过 [] 可以取出某个位置的元素,同时也能通过切片得到子串。: X3 G. [* U3 S8 Y  f
      pandas中过对 str 对象使用 [] 索引器,可以完成完全一致的功能,并且如果超出范围则返回缺失值:
    , e  c; X/ e/ T/ q1 s( @& Z( {7 {! ^/ V
    s.str[0]1 N( k: Z0 P4 Z8 o/ G, Y; X2 F. V
    Out[10]:
    - y& ?- e( `0 ~, |# B0    a
    & M& E/ i0 g: V: {4 O# W8 j1    e$ x8 d7 L7 W1 A
    2    h  R9 A4 V* D* Z) `' B
    dtype: object9 L& d; M4 A( `* h+ G4 T

    + W; h3 M& y+ u3 y% z" ]- Us.str[-1: 0: -2]8 T0 m: c: N6 S0 C! Y; K: R
    Out[11]: 4 C) O! D2 a$ A7 ?8 M% H$ W# }3 ]- G
    0    db7 z. C8 ?1 Y8 \# E% z( ^
    1     g9 V# _* F. Z  L4 v5 u7 z$ R' H, u
    2     i1 E% Q6 Q3 I0 x  z
    dtype: object( W: i3 i9 D" x* P3 }

    ! q! N8 g/ ?7 o& u' T+ q, w) @4 y7 Is.str[2]
    6 _! \% h7 v& E' c- {6 a1 \Out[12]: ' v# E5 i4 ?! ~+ H, M
    0      c' f) |5 q  l! }' q# d0 Z
    1      g
    / [+ |7 }$ M( Q. I3 ^) _6 @2    NaN
      x* W% y- B2 f! Qdtype: object
    # I% }8 g  j" s7 d( U, d5 z
    + [, A0 Z% j  H& O% g# S! Y0 O; \3 V1
    1 S1 I- f: @* |* S1 M2$ P- \* ^: V# y; V  y; b
    3- x3 m$ G  r/ [2 G& Y
    4
    & |, _' z' @0 m: x0 }5
    2 `6 j  j- |6 H9 M6$ s3 I' y5 r8 G) L
    79 n3 S5 X) m% X& c) K7 L; H% c
    8
    ! E9 Q+ P4 ]; J6 \0 E9
    $ M; a  E& F( B3 O8 j$ d2 H: j10
      o0 h+ n) X" t- e5 {* I11
    4 I1 ~4 x9 ]+ z8 d3 K12
    ) q: e, i/ k2 {3 a3 Q% K( o/ f4 V13
    1 R- C( u; x3 S  r" ?14
    " N6 D+ _9 y9 I- p7 t; w15$ J0 S: G: p( |& C
    16
      m9 m; U' I7 ~' q# U' K* O3 h0 S17
    3 P/ k8 H5 q5 s% e5 b8 s& a& B189 T- a( u; D: t
    19
    ' j( f$ w4 R( x  S0 H20
    ) T" O, y' g4 g, J" q. nimport numpy as np% m' d- Z' \2 \( M. P" A) _" u  g
    import pandas as pd
    $ H1 I3 E) @* r3 A8 W
    / p& o- i7 b% Q1 qs = pd.Series(['abcd', 'efg', 'hi'])  _; C$ t( n, c. L/ I
    s.str[0]
    / g$ p' M7 a- C0 o12 r8 ~& S$ j6 P5 U
    2
    2 g+ G9 r" S& H2 R; y4 I3
    # c; ~6 z8 w0 |, F4
    . H+ q& n5 d! q58 M% c% `) o! \
    0    a: {  z: v4 `  u5 I/ ^4 }- G+ y9 u
    1    e5 S9 B) E/ w2 M( A
    2    h% o+ O8 n4 h, G
    dtype: object1 _( T' E3 ?& C( r/ K4 T
    1
    - B2 t) T* O# x; r( {2+ i- T2 ~7 n, a: r3 @8 L4 B
    3- }; j! R+ b5 R( @
    4) M9 W# `6 v6 _, c3 w
    8.1.3 string类型/ t$ L3 \1 v+ _- F
      在上一章提到,从 pandas 的 1.0.0 版本开始,引入了 string 类型,其引入的动机在于:原来所有的字符串类型都会以 object 类型的 Series 进行存储,但 object 类型只应当存储混合类型,例如同时存储浮点、字符串、字典、列表、自定义类型等,因此字符串有必要同数值型或 category 一样,具有自己的数据存储类型,从而引入了 string 类型。2 c" i) E+ c7 ~' U5 }0 o
      总体上说,绝大多数对于 object 和 string 类型的序列使用 str 对象方法产生的结果是一致,但是在下面提到的两点上有较大差异:
    ! u  X2 {1 W0 h+ l0 }$ h* B
    ) ]& j3 v5 E; ^9 J7 X二者对于某些对象的 str 序列化方法不同。
    ) Y* F5 v, N! Q. T3 r/ o3 Q0 X) u可迭代(Iterable)对象包括但不限于字符串、字典、列表。对于一个可迭代对象, string 类型和 object 类型对它们的序列化方式不同,序列化后str对象返回结果也可能不同。例如:) c/ p# x2 W1 f7 G( M
    s = pd.Series([{1: 'temp_1', 2: 'temp_2'}, ['a', 'b'], 0.5, 'my_string'])& D; C; ^4 e4 n, [- L
    s
    ( a, r+ e$ i; ]- N. E0 p3 [4 a1
    # C# j* q& l2 l: H. T" y2. x0 q8 @- A6 X! K& j
    0    {1: 'temp_1', 2: 'temp_2'}& V& L: }, g1 o* A
    1                        [a, b]
    ; z5 W, R; E2 s/ u0 n4 n/ J2                           0.5( g# M/ u& y/ R, o
    3                     my_string$ `* M" c, S( ^' k
    dtype: object
    : X  X# p# Z/ w2 G- Q9 W% A- D& E1+ @& B+ h( S, z; V5 L3 z$ ~# q
    2' |$ b* X" K9 F/ L/ A
    3
    1 c/ B4 \/ X9 U  n$ ]1 l: [& E4 Y4  r, t# x/ ~+ U; n' K& {
    5% q7 p  V8 @! K; {
    s.str[1] # 对每个元素取[1]的操作
    8 d& D, Q0 B2 E7 K3 R1
    ! e6 |6 d/ E5 R& D) v0    temp_1) D# U8 F) ]. i9 X7 W$ K1 Y
    1         b
    7 F* @! K) t3 C' O: S1 U3 Z  C2       NaN2 T% g: v# H5 t3 F: Z' G
    3         y
    + \* L7 a* Z; g* x0 k% rdtype: object* w: C" s* R& E8 H2 V
    1! f+ \' e  b" c# X
    2
    ( e( E; h5 n' h* t3 g, j$ f3
    6 T  `3 h: U& X; }41 c9 n- ?2 X% U  b* C; _$ m4 {
    5
    , ^6 ~1 r# Z! d7 g% ?' C2 Z2 t3 ss.astype('string').str[1]
    * u0 U5 }* _9 T6 U" U: b8 ]1: v1 ?+ A) e- k- |9 i) \6 g9 Y
    0    1
    8 y8 I1 I' v0 E' ^1 V1    '/ e$ l( O+ W" I! p  l# @( X
    2    .. l' n7 u$ R, v( f+ \
    3    y0 M( ~  t% A' G; Q" r
    dtype: string
    . I4 N. b! \" b1
    & C6 J3 U& D$ J" X' V2$ S! H" R2 ?7 C$ l' n
    3. _; q& M1 i# z/ b$ N( ]+ s& @
    44 b, V' Z- x0 V- Y9 A
    56 h: _7 r% _: ]1 P1 i, Q- B
    除了最后一个字符串元素,前三个元素返回的值都不同,其原因在于:
      x3 [% m" ?& ~" |* D! P) a, T' H- R- z" R1 o+ R" R  P$ Q
    当序列类型为 object 时,是对于每一个元素进行 [] 索引,因此对于字典而言,返回temp_1字符串,对于列表则返回第二个值,而第三个为不可迭代对象,返回缺失值,第四个是对字符串进行 [] 索引。
    ) O8 v) O+ ]5 q) {6 k0 Bstring 类型的 str 对象先把整个元素转为字面意义的字符串,例如对于列表而言,第一个元素即 “{”,而对于最后一个字符串元素而言,恰好转化前后的表示方法一致,因此结果和 object 类型一致。! }! L! x* @' ^5 M
    string 类型是 Nullable 类型,但 object 不是
    ( C1 d5 E7 P  a$ r( Y  这意味着 string 类型的序列,如果调用的 str 方法返回值为整数 Series 和布尔 Series 时,其分别对应的 dtype 是 Int 和 boolean 的 Nullable 类型,而 object 类型则会分别返回 int/float 和 bool/object ,不过这取决于缺失值的存在与否。3 O' `  S1 |$ n0 z
      同时,字符串的比较操作,也具有相似的特性, string 返回 Nullable 类型,但 object 不会。- [8 `* X7 D/ y+ L9 [
    s = pd.Series(['a'])
    8 {1 j$ b; q# K$ }
    8 W0 F$ l+ H& O  W" N- p& ]s.str.len()) R0 p5 G- C( q) a3 B9 b. @
    Out[17]:
    . U& `2 {2 q$ `' n; ^* D% Q% g+ R0    1
      p. m  s* o$ [2 G6 U" W% d% Ddtype: int642 `: `3 F- N. T& @$ d* b
    1 ^; M$ g# s  t: d% F
    s.astype('string').str.len()
    ; v* y# _+ w, b  }. r/ {Out[18]: 2 y6 r7 z2 N# c
    0    1
    6 w# A( m' ]0 m) R# K, w4 h7 q, y4 Rdtype: Int64
    6 |" M& D) J/ z; Z1 e6 R( t" J% F8 K& W) F$ v
    s == 'a'+ n* D, o* N* l0 h$ T* `
    Out[19]: 9 S* y: M. T  q5 q
    0    True" ~8 w% ~. n$ \0 v" s- o
    dtype: bool! P3 f- \- e9 R( |
    5 z( s4 U4 K. Z. Y+ r1 j
    s.astype('string') == 'a'
    : `9 V) ]% H+ T. H! z: ROut[20]:
    / ^. k" }5 e2 C0    True" W) ], B2 R: x+ L9 a
    dtype: boolean
    ! \: }2 i3 `1 F7 L. U$ T1 f+ V& x, {& z5 l1 K5 V
    s = pd.Series(['a', np.nan]) # 带有缺失值% h! {8 |& L, y  j- @: c

    : _6 _& r2 {/ w/ O9 Ys.str.len()4 h' y: H7 Q/ x( `8 ~( ^6 {. C
    Out[22]:
    ; L2 @/ ?. b6 c6 [& t/ [0    1.0
    " F: W: D) _0 D" T9 o% A1    NaN
    4 S, C" n  x1 |3 N3 X$ Q3 n/ ydtype: float64
    8 e6 |$ L- N8 k: M+ o7 j3 l- b, I
    s.astype('string').str.len()
    . ^" |- J% O! v$ N1 GOut[23]: : @6 y6 M. c, G* Y" ]* M4 @
    0       1
    ( p1 G9 p. R" y4 h, P3 |% y2 g1    <NA>& o" V7 Z* `  Y* g- u4 g
    dtype: Int641 j) j8 Q0 z) w- ~

    . ?8 L) y$ f6 l8 T& fs == 'a'
    * V, E# ^% T. H: r9 P6 J, uOut[24]:
    5 o4 O4 H8 P/ i7 ~0     True1 Z- H8 h$ m3 q( o
    1    False
    2 L: j+ p* G, }) xdtype: bool* V( `* P7 C" _  _0 {; `

    ) W. j0 g( o2 D2 k- y8 ~" ks.astype('string') == 'a'
    ) Z7 F/ c, n/ z. V5 ZOut[25]:   m! ?$ S5 K1 E& [$ H
    0    True
    , A! B& Y3 V1 Y) M- {7 G/ H1    <NA>
    - q5 H! o4 |3 I& J& hdtype: boolean' _! P; N' A8 P- b& d2 A' w

    ' u/ b: G1 M/ X1 Q+ r1, K; x9 Z7 E  \, _+ m7 t
    2
    ! }% N0 {% D( E, F0 C( r0 x3
    ( e! R7 U$ D6 U. u/ M0 F4
    / B! Q: G- _: z' B( z5
    1 f; i2 z6 Z  o61 E1 V2 c0 w5 j" ~
    7
    & Z+ U) n. n6 }5 `2 T85 _  z* a# Q+ {
    9
    5 j2 i& _0 |. H: r4 O* v- k10
    & s6 L" y0 ~  J1 R* q' l11
    , D8 U; I9 e6 w0 o& F- l( r12! J4 Z" [' R) t' S
    13& O$ y. I6 n7 b! K" D7 p
    14
    ! u. _0 P6 |- ]  ^$ C15  g+ L2 K) t% r3 s9 s8 G9 ~5 c! g* n
    16% w/ F$ {; e) o& n
    17
    , R' J; b- r3 ^5 k/ L7 ^* a18
    & y7 t: {- T4 Z% R% V) ^19( V/ S' a8 D2 B1 Y4 F+ \7 u
    20
    - H/ S# N1 d0 y' Q2 Q# ^) E216 b$ o: m. T) Q1 C
    22
    ) Q0 }6 k0 }, A* y* v23# y% \3 X2 a* W2 ]+ G  Y) ^( P
    24& o+ b$ T; r2 J% P# T
    25: a) ~8 {! W- a$ J
    26
      M( s3 E- n' g% G: q2 x! m27
    4 x% u/ P6 D0 D) f9 E) ^7 r28
    6 f3 D5 c* ?0 R8 H5 O; a' m29
    ( ?- r- o- F: s6 }  I+ ]7 K30: a9 x* k* X, F) X9 S6 M
    31
    / y: ~9 z9 A# Z32- }# m! X8 |0 c7 d5 |# g1 Y% C1 E
    33% I, d$ g: Q6 x( p
    34$ f& m0 _# \/ X, j6 S5 b
    35" N6 S: v3 p! o* C
    36
    . H6 @! |' i. f' X# x' J" a7 P37
    , ^1 m# c/ k1 A5 ?% l38
    $ n6 @  B& _% N# u39
    $ @5 d2 v% A: g) {5 u2 o40
    " w! a- l1 I6 m8 g) C/ x( F41& F/ ?  I9 g% R0 p- m1 o. R% c
    42* W2 Z' W$ Y2 L; K  U/ |( F2 T. u
    43
    7 t$ A- g$ c" I448 M4 K( {1 o+ o( V" K1 X
    45* V0 K; H/ p% l2 s2 m4 N" [
    463 T5 ^# Z7 Q/ I' q4 ]& Q! s
    47( M+ ~; E8 t6 u# {, ^  q) D
      对于全体元素为数值类型的序列,即使其类型为 object 或者 category 也不允许直接使用 str 属性。如果需要把数字当成 string 类型处理,可以使用 astype 强制转换为 string 类型的 Series :
    2 O. O% z! v' B) Z' {4 W5 P! ~
    , r! r! e; R8 {% s" u& ]- ls = pd.Series([12, 345, 6789])# ^' K+ n, ^1 f- \; `( P! U
    / @/ t* e& @" {3 c+ B0 J1 e
    s.astype('string').str[1]
    ) J/ J1 y( M& C* ~Out[27]:   h. ~2 W6 c0 O6 I$ _6 r( L  H
    0    24 ?3 m$ x& h( v3 X7 X" Z% \
    1    4+ Z: w6 d% ^& `
    2    7
    7 ~( L( v# O4 l, O" l9 Wdtype: string
    , p; E) X* v: [' D: w) v1* [  o% r7 Z: z" [+ l
    2
    & |/ r2 I1 \! e% p, x0 C3( c: G3 b$ ]$ x1 d9 E4 R
    4
    + p, y7 s, L( |: H+ {5$ |1 Q% Q, H- {' l1 `4 s; c/ R9 o. @# A+ K
    6
    ! y6 Q$ x& _; C- L7
    - B$ A% x( d" W# F  Q8
    4 K6 u. E# `) g" L6 k8.2 正则表达式基础* z6 m) O6 X3 q' e1 M2 w: I) O3 U
    这一节的两个表格来自于 learn-regex-zh 这个关于正则表达式项目,其使用 MIT 开源许可协议。这里只是介绍正则表达式的基本用法,需要系统学习的读者可参考《Python3 正则表达式》,或者《 正则表达式必知必会 》这本书
    5 F# ^( j5 Y; @5 ?: U. g: I" D3 w
    8.2.1 . 一般字符的匹配
    : j2 d* ~3 a; o! ?, i正则表达式是一种按照某种正则模式,从左到右匹配字符串中内容的一种工具。对于一般的字符而言,它可以找到其所在的位置,这里为了演示便利,使用了 python 中 re 模块的 findall 函数来匹配所有出现过但不重叠的模式,第一个参数是正则表达式,第二个参数是待匹配的字符串。例如,在下面的字符串中找出 apple :& Q  c# \+ r' V1 p( L7 |

    2 J' _; i2 Q8 y* {, bimport re1 h) v/ w4 f2 U4 k3 x
    ; @( x4 E; {6 |9 S# y) u" W* d
    re.findall(r'Apple', 'Apple! This Is an Apple!') # 字符串从左到右依次匹配  c' R1 C1 t3 R. ^+ D* e! |. `* T
    Out[29]: ['Apple', 'Apple']
    3 t: i7 T; f- ]9 q9 D2 Q3 v1
    ! m* R% e) r5 D! i1 s$ e( {2) f0 I( t9 c  C" k
    3
    . c$ Z3 I7 Q) k6 a/ ^  \4
    & c- C" f5 M. s% {$ q  a8.2.2 元字符基础
    * i: Z, F8 C2 w: D8 ^元字符        描述
    % Q/ D( M* }: h* l% u.        匹配除换行符以外的任意字符
    ; T# K, p5 H. x7 r: E[ ]        字符类,匹配方括号中包含的任意字符' }% j6 v% J4 A9 ]
    [^ ]        否定字符类,匹配方括号中不包含的任意字符5 K; J1 [! V/ d% `; ]9 S
    *        匹配前面的子表达式零次或多次* `# x3 @$ A( G  }
    +        匹配前面的子表达式一次或多次。比如r’d+'就是匹配数字串,r’d’就是匹配单个数字) w- [! S8 O* c' E* X' L8 e* b! O
    ?        匹配前面的子表达式零次或一次,非贪婪方式
    4 {, x! Q9 u: H3 T) U% c- }3 k+ u' o& F  Q{n,m}        花括号,匹配 n 到 m 次由前面的正则表达式定义的片段,贪婪方式
    : t1 N+ b6 J5 x3 x(xyz)        字符组,按照确切的顺序匹配字符xyz
    # {; |4 m% s( C% \9 |1 f7 ]|        分支结构,匹配符号之前的字符或后面的字符0 v6 J5 W/ T* a$ g) L
    \        转义符,它可以还原元字符原来的含义' _' [- O  u, A
    ^        匹配行的开始
    8 x: u) S5 e9 I5 ?$        匹配行的结束
    1 r% i* }. j+ C0 e( r/ }  u7 }9 yimport re
    0 q6 m( N3 K7 z* y$ ~re.findall(r'.', 'abc')
    4 p3 D- u0 r0 D8 @% \5 {5 u1 GOut[30]: ['a', 'b', 'c']
    8 ~( s3 Y7 P6 o2 W8 H) _
    " ]& I+ x* f% k. rre.findall(r'[ac]', 'abc') # []中有的子串都匹配
    4 `) O) ]' F6 A3 Q. C8 y; X5 ~Out[31]: ['a', 'c']
      P) p# {7 c& }0 ^& t8 u2 n6 f0 U- p6 Z9 m' a+ K% T
    re.findall(r'[^ac]', 'abc') % O% f" u; e. w7 i7 d
    Out[32]: ['b']
    6 L: I: ^  [2 z
    7 B0 \7 J; l6 a8 @* ~% V# V* {' Xre.findall(r'[ab]{2}', 'aaaabbbb') # {n}指匹配n次
    5 P2 Y) R& v5 S2 i$ [Out[33]: ['aa', 'aa', 'bb', 'bb']
    ' o+ `& r$ N8 C0 {- C# L. B" o' f9 s3 m  y5 b" b3 |. b5 ~7 s- Q/ E
    re.findall(r'aaa|bbc|ca', 'aacabbcbbc') # 匹配前面的或者后面的字符串
    # \- |+ O' Z0 {Out[34]: ['ca', 'bbc', 'bbc']) ?- A; e6 \5 r; l, y
    # C& j9 ]+ U! d- z" Y* [$ {% s, p
    # 上面的元字符都有特殊含义,要匹配其本来的意思就得用\进行转义。
    2 C8 z, j5 N$ {"""
    ) w- J# _7 B4 U# z* \, m1. ?匹配的是前一个字符,即被转义的\,所以|前面的内容就是匹配a\或者a,但是结果里面没有a\,相当于只能匹配a。: N5 B3 a9 [' F$ S" X2 J  g) D
    2. |右边是a\*,转义之后匹配a*,对于竖线而言左边优先级高于右边9 g2 y1 H5 n! u& V8 y$ j, K3 m
    3. 然后看目标字符串aa?a*a,第一个a匹配左边,第二个a匹配左边,第三个a虽然后面有*,5 W- V" P  c' X4 v# ]
    但是左边优先级高, 还是匹配左边,剩下一个a还是左边,所以结果是四个a
      P0 S+ v! X2 X. Q7 e- A2 M# V"""
    6 N9 P, ~" _3 u$ d
    , e$ C4 u9 C2 j. I  q) K2 kre.findall(r'a\\?|a\*', 'aa?a*a')   # 第二次先匹配到a,就不会匹配a?。a*同理。
    0 B, Y' ~  H* i1 @9 FOut[35]: ['a', 'a', 'a', 'a']
    9 T7 g4 S8 F. q( n$ x+ ^& b5 y7 w, F4 u# y% ^, X0 ]2 m& H
    # 这里匹配不到是因为目标串'aa\a*a'中,\a是python的转义字符(\a\b\t\n等),所以匹配不到。
    * L" F, U" G7 f6 @; p# 如果是'aa\s*a'之内非python的转义字符,或者'aa\\s*a',或者r'aa\\s*a'就可以匹配到\字符。  x$ E1 o3 n+ L
    re.findall(r'\\', 'aa\a*a')
    * I, m4 y3 O8 \2 ^7 u* {+ r5 ]7 Z[]; L  E8 B1 t) a! Y! d& }) x

    7 o1 E3 m7 g1 c4 ~. b% B$ ]re.findall(r'a?.', 'abaacadaae')
    ' C! b9 M' Z/ Y3 nOut[36]: ['ab', 'aa', 'c', 'ad', 'aa', 'e']* R. ?! s4 z: w9 P9 T% y

    / L. b" T* j( f" P- p7 p6 B) J* @6 Hre.findall(r'(\w+)=(\d+)', 'set width=20 and height=10') # 多个匹配模式,返回元组列表
    # E) C! w+ x" D5 M# U# m* D( ?[('width', '20'), ('height', '10')]
    # a9 P$ i! Y- Y8 k' u; w8 e
    & F) B( g0 X: i/ E" V- _11 i+ V! f- j7 _1 L" k5 x  |$ o9 a% _
    2
    ( M1 k! Y2 ^# a3/ A& k/ p/ V/ L# Y
    44 ?# p: s4 X. K' O; e6 V
    5
    5 K* i) G3 d* F5 C8 x1 _2 R6
    * |9 @6 ?) I, _6 }1 i7* w! U3 m; z6 _& p# Q
    8) G/ w" c  I0 Z, V: F) L9 L
    9
    # {5 x- n1 C/ I3 D10
    , N  O- b8 {  f5 L1 w% {$ K" B11- c" n6 i( _2 b! n' j( f
    12& x# b% Q1 c1 t% c* @9 T( t
    13
      o, H* k6 f; J! Y0 L! o% n14- M7 H! E; T0 @" I
    15
    & l& ^3 Z+ F) M* H16% W, H) ^  I' ~# M9 a
    17" ^+ l  {8 B3 ]5 ?% i
    18
    4 a6 `. @3 P7 t0 R8 V19
    ! n1 e- ?4 J1 s% i2 |; d20
      O* D  d. F3 k0 n' W8 C21
    8 d2 |8 ~2 }! H0 z0 y; B' }/ C7 Y22* x& }. O1 u# f6 j; x* j
    23
    " r+ o. A* [. C$ L24. X. t" U  @' I
    25, |. C. _- o# j4 y0 B/ f3 X
    26
    0 z8 L4 Z0 x0 u4 ?2 ]2 I1 e27
    , b/ Z" j# Y- z: H28
    8 H7 ?; ~! Z0 x8 ?! y! A! b290 `; n, A# h2 _) f% Y6 Q
    30
    - j7 P6 Q1 Z5 J. L' H; z, n31, d5 o9 E" ], M: C3 |( p8 h5 o
    32
    : V9 Q6 K8 y3 K  U( e5 o2 A3 I33
    ( Q! I, t- ~2 ?0 h$ v, c342 w/ M/ Y7 ~5 D4 z. l
    35
    0 n7 g. I' m# B36! W' w" ?- f! l$ z
    374 N! o& l: [# w( p* [  x
    8.2.3 简写字符集
    ; r0 o* A* ?: t8 L4 |则表达式中还有一类简写字符集,其等价于一组字符的集合:
    7 G9 e! C/ c; p4 S  b3 M* M% E% F2 K! o! J& B& i: n# D
    简写        描述
    2 X) T3 z6 Z- U3 V; P/ ^( M* x\w        匹配所有字母、数字、下划线: [a-zA-Z0-9_]/ w* c' n2 p' e
    \W        匹配非字母和数字的字符: [^\w]* f5 J: ^6 Q8 w! [. k* u
    \d        匹配数字: [0-9]% r9 B" H% L; s! _. k+ f/ U5 d' m
    \D        匹配非数字: [^\d], J" j, V& r9 E5 j. `1 r8 ?
    \s        匹配空格符: [\t\n\f\r\p{Z}]
    % }- A( q6 E( ~# c1 q+ G, O# |& z\S        匹配非空格符: [^\s]0 a6 e7 A% f. ^! s6 w- }
    \B        匹配非单词边界。‘er\B’ 能匹配 “verb” 中的 ‘er’,但不能匹配 “never” 中的 ‘er’。
    % ]8 K& Z, u9 Z$ y" Nre.findall(r'.s', 'Apple! This Is an Apple!')
    : X  Z4 V5 S4 ~. s$ }9 d0 E. ]Out[37]: ['is', 'Is']
    ( u2 R8 e& W1 }' l8 j* v9 n' [* H* Z5 @& `. C, D, v
    re.findall(r'\w{2}', '09 8? 7w c_ 9q p@') # 匹配任意数字字母下划线的组合,但必须是两次
    ' B6 }: V9 N8 }: a7 AOut[38]: ['09', '7w', 'c_', '9q']+ R9 `4 K  Y2 a4 Q* N! h
    8 Q% d3 Z! ?& I5 v
    re.findall(r'\w\W\B', '09 8? 7w c_ 9q p@') # 匹配的是两个字符串,前一个是任意数字字母下划线(\W),后一个不是(\W)
    7 [" n$ u& i% c2 o  b( ^Out[39]: ['8?', 'p@']
    6 c! d6 p0 @% F' s- e# e- }3 N4 m/ b% Y( q, J
    re.findall(r'.\s.', 'Constant dropping wears the stone.')" d, j& @( c# Y; f9 H5 F2 K" g
    Out[40]: ['t d', 'g w', 's t', 'e s']
    7 t% Q) L: q! y* Z
    & N" B7 q4 `$ T0 R2 k: Z5 O; vre.findall(r'上海市(.{2,3}区)(.{2,3}路)(\d+号)',; [* E4 f4 J3 U& |: T9 Y
               '上海市黄浦区方浜中路249号 上海市宝山区密山路5号'); t8 r, n9 H, `% y& {
    0 A! e0 U/ T6 v7 q- a" n2 Q
    Out[41]: [('黄浦区', '方浜中路', '249号'), ('宝山区', '密山路', '5号')], }( ^1 o/ t, ~' V- b% M
    " |! |, I* O' O0 N0 D( g. @9 B
    1
    7 d1 c% N" G7 @9 k) B# S2
      B7 ?  o1 p9 x8 L+ K3$ G% Y% h! [1 r! b7 ?) U
    4! z1 g) R$ H  U  q6 ?% R
    5
    4 v2 ?/ o2 ?: x: Y6( V+ w5 e8 B0 [( w8 S8 ~; E9 g) O, b
    7" R+ J+ M( T+ R' q
    8! N3 z$ s1 |) d7 V* B5 y: A8 l
    9% ]8 C: Y0 e3 F# Z! m
    105 C, }$ N/ O9 J+ Q. V2 c9 X+ ^1 d
    11
    - p7 M9 `* B5 o0 N8 ?" m129 X5 K/ }1 u4 D# h7 w
    13! b) I0 j7 t6 w* v$ l9 [7 ^
    14
    , K, [  |2 {/ F/ K$ X4 D2 H3 G( G157 [8 m3 }! d9 S0 J3 W+ N5 h
    16
    - W  `; n8 X2 Q5 a6 d: U) r$ w8.3 文本处理的五类操作% ~/ i( S/ x; f2 c/ ]% ]& k9 l1 F
    8.3.1 str.split 拆分, k/ p+ U+ O, O* Z# T0 j9 [  x
      str.split 能够把字符串的列进行拆分,其中第一个参数为正则表达式,可选参数包括从左到右的最大拆分次数 n ,是否展开为多个列 expand 。& q( a0 n: ?0 M* `; i3 l
    , w, ]% j  ^( s
    s = pd.Series(['上海市黄浦区方浜中路249号',
    : A7 t* Y) G, Y( B0 ?( ~9 u/ F            '上海市宝山区密山路5号'])
    3 ]& N: r4 }5 |$ C# d5 P; b3 _) S5 q7 t( o

    2 b2 P& Z. W) P$ V4 g" |7 w6 gs.str.split('[市区路]') # 每条结果为一行,相当于Series8 V# j! h3 u6 R, y
    Out[43]:
    1 O* t$ e; K' f* p$ v0    [上海, 黄浦, 方浜中, 249号]) B0 `' R+ w7 B3 V
    1       [上海, 宝山, 密山, 5号]8 c# O% b* H# W- |% R4 O2 O  K
    dtype: object
    ) t, n; v" M, R
    1 z! i+ ]" A8 u$ y$ rs.str.split('[市区路]', n=2, expand=True) # 结果分成多个列展示,结果相当于DataFrame  B1 g( L3 U8 k3 G! c  W1 L
    Out[44]:
    & s0 O# v) J% a  J    0   1         24 @% G, w  e* a- w/ a
    0  上海  黄浦  方浜中路249号, y' _( {' P5 n' I  F, j
    1  上海  宝山     密山路5号& u& y8 T5 \+ g* Y8 R1 G) C; A6 l4 E
    1# I% i$ ^. }/ C5 Q- ]& H- J, D
    22 d0 e0 d2 R$ b/ c- U4 U# F
    3
    . V  M/ x0 r% k: U0 Z1 I( t; }; O4 j43 o% u: h; {  v  T5 D
    5
    " w2 {, M; A# r. R6; l. x& D- m% f! ?7 I: J" K
    7
    : z. J( O6 q2 l, H87 _4 E# `8 K% W+ \$ F) ?
    9& A* R0 B7 C4 s& `" v* r
    10- z" _8 p# ], l7 \  E
    11
    1 a% F& M9 a/ ~4 B- v" T127 _3 z% }- c& ]& Q) _
    13$ O3 [0 c% F2 X! ~! B. E
    14
    # c# K( k3 S( c) P0 C9 D15$ ?9 `0 b- X1 k! F: y9 [" u
      类似的函数是 str.rsplit ,其区别在于使用 n 参数的时候是从右到左限制最大拆分次数。但是当前版本下 rsplit 因为 bug 而无法使用正则表达式进行分割:6 r% _6 p3 N, K5 z% @% D/ M' o
    ) F# K) p3 u% R, f: C
    s.str.rsplit('[市区路]', n=2, expand=True)8 J" i5 U( N+ o$ V& i
    Out[45]:
    , f/ M0 o- \0 P: E9 a4 n) T                0
    % P; C% z) O# ^% W+ i. I2 Y0  上海市黄浦区方浜中路249号
    3 X" p8 W+ ~- i/ j' U- v# a, L, G( A( t1     上海市宝山区密山路5号9 [1 T( M# P# |
    1
    0 q' o. ]2 g' g8 W0 P. X7 Q2$ P$ G' w+ K- o7 S3 ^
    31 ~' r5 W) H. F* x
    46 g: r( ]: g5 U6 Q( _
    5; i0 x/ ~; j( x5 x
    8.3.2 str.join 或 str.cat 合并+ z7 ?1 N5 j9 S: ?3 j
    str.join 表示用某个连接符把 Series 中的字符串列表连接起来,如果列表中出现了非字符串元素则返回缺失值。
    . Y, [. P' u7 v7 q* o2 Zstr.cat 用于合并两个序列,主要参数为:
    9 g9 X+ q, c# \- w: K; @7 {sep:连接符、4 G; U. L  W$ e! M$ x
    join:连接形式默认为以索引为键的左连接
    & ?5 [6 f4 Q, lna_rep:缺失值替代符号
    , O9 V8 m! u9 `' s& B. Hs = pd.Series([['a','b'], [1, 'a'], [['a', 'b'], 'c']])
    ' V- Z/ r+ ^( f* {1 js.str.join('-')) c4 k+ w  ?3 }9 y2 R4 e+ ~
    Out[47]:
    $ _# _! X( e' S* ~0    a-b# f2 b+ M" a( O$ n, X
    1    NaN
    , R" F" f& K4 W8 X0 y2    NaN
    ( O( X( g6 ^& N4 {/ Y; Bdtype: object& w6 W6 ?- z6 Z
    1" R7 D$ A) I1 O: {$ {# J" X4 C; C9 V
    2
    ) t0 ^" X* U. P! `: h3: P5 O! H" Y& F2 g. j0 n8 s+ w7 A& T
    4
    ) G/ P8 V( o: f" J1 Q% P7 N' ?6 r! g5
    3 y0 i4 h+ [" h4 l$ P' y" V6. b  g% G. V  h& Y: S7 y) W& p
    7
    6 x% g5 \1 V% C0 b" y) H4 t$ ms1 = pd.Series(['a','b'])
    & }$ D! f, Y. O( u0 bs2 = pd.Series(['cat','dog'])
    ( c6 n* ?  r+ s$ ]s1.str.cat(s2,sep='-')# c& v& P; g$ f, n
    Out[50]: 5 q2 Q0 k- {6 o; `' s" K) v
    0    a-cat
    8 Y' b3 b5 M1 a3 H2 m  ?1    b-dog; ]' y9 D( c1 `, z: {7 V
    dtype: object
    2 Y. `% s1 z; G$ I) Q4 [; Y. K- q0 G8 |! j# }. t
    s2.index = [1, 2]
    8 s3 D7 l" s3 t, z$ B) Gs1.str.cat(s2, sep='-', na_rep='?', join='outer')
    3 T4 `6 E- m/ O( |5 {* ?: S, N( kOut[52]:
      t. L: N, F% H4 w; C' z0      a-?1 m: Y& C! A/ w! P" `( i
    1    b-cat
    3 ~  h2 i# z: L5 d2    ?-dog
    9 \  B, C1 J& G0 Edtype: object
    8 S4 F0 B1 y+ l! r5 }- v( w1; R8 j1 N4 d: y4 e5 f
    2
    # L  g3 ~* W8 f$ @) \3 U3
    8 J& c% o5 D' {4 n- O# }4! N! a/ [2 P3 m6 g
    5- w: l2 k1 I6 _1 Q
    6& s+ v/ ~$ t- }/ f% ~  c5 }
    7
    % s8 r5 {' ~: b" ~( N8
    5 u: z2 W! g7 D+ Z: l! ~9
    1 J' Z. s8 }: C' o; H10. d, g$ i1 H* _7 i0 `1 [
    11  ]# U  K' k* a+ w  b
    12
    - z5 M& ?% V7 F8 O5 ?! h2 _135 t3 F) ]$ W7 m# B" P7 D
    14
    : R) R9 ^" _- S5 ?# [15
    & j% |$ N) ^! B6 t2 b5 r& g! g6 j8.3.3 匹配
    ( N: O  E+ S$ |% mstr.contains返回了每个字符串是否包含正则模式的布尔序列:9 c7 J7 x$ |9 z9 x! r8 W
    s = pd.Series(['my cat', 'he is fat', 'railway station'])* R) t' e4 z2 {' \% {
    s.str.contains('\s\wat')6 N+ j! r* P  i2 l8 t! V6 @4 O

    : z- V7 M' V  z) s) Y3 G0     True
    ; n$ O4 \9 n* b4 l: H4 R1     True
    * E9 R* k9 u9 L3 B# y2    False
    1 C' h3 V6 R5 R9 R" f+ Ldtype: bool
    ! }3 j5 V1 i) p1
    . t! k. N8 A$ i* l3 c" u2
    6 F% ]& j; A3 H1 h0 ]! h30 f. B- Z8 `; H; d, M1 |; j
    4
    ) Q* j. b; n& h8 Z5 u58 @% X; v4 p0 d8 ?' f! @, H
    6
    % f  u. V, m, N6 c) F/ t; S! M7
      Y+ z3 ~7 {. Q" A0 pstr.startswith和str.endswith返回了每个字符串以给定模式为开始和结束的布尔序列,它们都不支持正则表达式:* ~5 J) S. Q8 S: w. r6 y
    s.str.startswith('my'). V* q8 q0 t( [; d

    + U+ N& i0 D- I% U7 s; P0     True6 g6 h6 P! S2 a! e2 R! O2 K
    1    False: b; q. m* f! m
    2    False
    3 u/ i7 G8 d; n" H3 B/ ^8 R+ pdtype: bool" L# H7 Q" P- F$ x, @
    1
    . f2 R' k; {+ K4 ?" G) A- Q2
    ! @5 L5 l, D  }! B, _  M" L2 a3
    : E7 L4 l* O) X  N9 `+ m% @4
    5 F2 ~7 ]4 K; m) j5 u# j56 P% \5 F7 d% c% d. K8 Z. N$ w5 ?
    6
    - l9 e5 F8 ^8 z, C* Ps.str.endswith('t')0 ^1 k0 X4 Y3 s/ \
    - D+ e5 i6 r+ ]: C( ~8 \, V
    0     True
    0 e! i. L' T7 R2 ]4 e1     True
    : H( C, ]  G* B9 c1 P/ O# ~2    False
    ; y4 d/ N! u9 R( T2 fdtype: bool
    * [& o8 ?" w  _+ l1; z8 w* a/ Q) D/ n* G- {( o
    2- ^) v" C& Z0 |  X! r! }9 e
    3
    $ g& S! u8 @& x4" b  h+ u. w# B5 k
    5& I; W( U* ]) T3 x8 L$ }# \
    62 H8 A. p! R8 a  ^: V
    str.match可以用正则表达式来检测开始或结束字符串的模式,其返回了每个字符串起始处是否符合给定正则模式的布尔序列。当然,这些也能通过在str.contains的正则中使用^和$来实现。(貌似没有python里的search方法)
    5 N! A; {' A3 Is.str.match('m|h')
    # C0 N7 _, F9 s$ `, S( rs.str.contains('^[m|h]') # 二者等价
    , u' G$ p9 L( F' g2 g  r& ^9 v) F, e5 d: I5 T( ^1 o, g; q% I
    0     True
      m3 a4 }' v9 ?7 k1 K& B! k1     True* {$ H" r4 |7 H! b
    2    False
    ) J) |/ E, b9 B: ^9 `; Zdtype: bool
    1 P5 U: B1 P' Q4 |5 F) i1
    2 V2 K7 \1 y: i, n; n- S: C; g2
    0 _% `, ~0 `  g" [. k( g3) C  ]& e( ^% J/ w, u- u  A
    40 E* K; S; h! ?8 ~( j- o8 m" i  T
    5
    : R3 y! s* H+ o& X  @& \; g% d6
    . a5 Z- z9 H& @4 B; o7
    6 X  B! o, `, L, r* R! v# Q- es.str[::-1].str.match('ta[f|g]|n') # 反转后匹配
    ; S/ u+ A* S: t* R4 [6 G2 p7 Y5 Js.str.contains('[f|g]at|n$')       # 二者等价# o% `3 v% ~7 p$ t# k; M
    , l2 Q# D  }8 N  ^
    0    False5 D! O2 P0 t+ G, H$ c8 U) {1 N0 ~
    1     True8 d! b& v  M9 P8 ]( M
    2     True7 W& ]4 n8 M' K; \+ D" d$ f
    dtype: bool  v* K& x! R8 P0 f' E5 o3 H* G: C% B
    1) P! u, a2 Z* q
    2
    0 d+ V! \; q2 M! Q* G+ V3+ D/ E7 {3 C, t4 e( f2 s
    41 q) {$ A. Q, b! Y) B
    5
    - D, S" N8 G7 m/ T$ @6& d8 g9 S' M( a8 Q: c) V# \* [
    7
    ' A: _- c: _$ h. fstr.find与str.rfind返回索引的匹配函数,其分别返回从左到右和从右到左第一次匹配的位置的索引,未找到则返回-1。需要注意的是这两个函数不支持正则匹配,只能用于字符子串的匹配:2 G# G( a' {6 i3 C' V  w; M0 e
    s = pd.Series(['This is an apple. That is not an apple.'])7 f7 A! h# y8 }( L( f
    5 ?; |( m) B8 h' y, N
    s.str.find('apple')
    8 b+ Z* r& p( e8 y* G3 _' @1 eOut[62]: 5 E7 R; w, p6 q
    0    11/ s- j( Z) S) y, c" y
    dtype: int64: F4 U! M% o/ L$ o. c2 L
    ) X# F6 f$ ?* `: ~! K( b: U
    s.str.rfind('apple'): o) d/ i& ?7 _% Z6 c4 H$ G
    Out[63]: " r; A: \" Y) ]
    0    33
    ( V5 J: ?4 W) N6 }  [4 K# ]dtype: int64# }  _" S# e0 B. f
    1$ W3 Y# T3 b3 e
    2
    1 z9 b/ \3 j. y* b! i3 u3" a+ l# ]1 @" W1 t! l$ J" C& E0 {5 i
    49 T' e9 w$ ~- `- c) Y) h
    5
    ; u: J" r1 v0 m2 g; r5 ~+ d6) W* i; S3 Y- H& z1 J
    7
    3 {3 j# d3 V" f* z1 I. b8
    4 ~( Y4 a/ r0 j+ y, s9! C5 q/ Z7 W& n5 m
    10
    & n9 D! O1 D- U& t- Y11# K8 V) h9 Z- o( S8 h$ X5 \
    替换
    # }/ D- f  x+ x* Hstr.replace和replace并不是一个函数,在使用字符串替换时应当使用前者。
    : _& ^. ~# u9 e) k" B& q4 Vs = pd.Series(['a_1_b','c_?'])
    0 `( V5 a! ?0 h. |* Q- K! l8 _# regex默认为True,表示是正则模式,否则第一个参数内容表示是单纯的字符串,也就是匹配字符串\d|\?) x$ p# d9 B( m+ C  `) x' B
    s.str.replace('\d|\?', 'new', regex=True) 9 n8 s, ~9 p- j
    / I( _( x1 p, B$ q* f) ~
    0    a_new_b
    : Y5 ~& S) f2 ?" ^( Q4 \# x4 ^$ |. @1      c_new
    ' I) z  r& s  t! S( s5 v- Ydtype: object- s1 m0 Q0 A7 q% Q, T
    10 `- `, g, b2 `3 O
    2# o  F: g4 k* @5 @. V# N
    33 S9 e* |6 H- l% O
    49 j! b% D3 o+ Z2 b2 i8 J$ q5 [
    5: |5 R$ k6 `2 w( t
    6* ?/ j/ t- H6 k
    7+ h, D6 q# P4 I- J
      当需要对不同部分进行有差别的替换时,可以利用子组的方法,并且此时可以通过传入自定义的替换函数来分别进行处理,注意group(k)代表匹配到的第k个子组(圆括号之间的内容):
    / l4 @  f: E) n% h, @3 X6 ]6 ?2 x3 F9 ?
    s = pd.Series(['上海市黄浦区方浜中路249号',% A' O& L: J7 I) ]
                    '上海市宝山区密山路5号',
    4 P" R0 E+ Y; w. s* f                '北京市昌平区北农路2号'])
    ) l- E. R6 Z0 Y7 G3 opat = '(\w+市)(\w+区)(\w+路)(\d+号)'
    0 u+ A' m0 q7 `8 W) w, qcity = {'上海市': 'Shanghai', '北京市': 'Beijing'}
    " a8 w1 D$ w7 G4 ]/ \, q8 @1 _* ^6 Qdistrict = {'昌平区': 'CP District',
    0 v+ y  n4 P3 h" o1 w& b            '黄浦区': 'HP District',
    * \$ v' X3 q* e8 x" f4 A            '宝山区': 'BS District'}
    " D1 m" i2 r) Y* b" C/ [1 vroad = {'方浜中路': 'Mid Fangbin Road',
    ! I2 D; c1 ]. m, U        '密山路': 'Mishan Road',
    - C& N$ {2 u) d8 Q: G        '北农路': 'Beinong Road'}
    ; I9 ?$ G( t% B( B6 e  [def my_func(m):
    2 C9 m5 ?- @- z$ M    str_city = city[m.group(1)]! e! D1 X( h* q0 x" s( |  ?4 D2 R
        str_district = district[m.group(2)]
    ) }* d  x0 M" D9 d' P$ f" x+ k) I    str_road = road[m.group(3)]
    7 Q! x7 H" T7 [' @' k5 b+ x    str_no = 'No. ' + m.group(4)[:-1]( D% i3 E* E  I9 ]4 |, r
        return ' '.join([str_city,
      q  C9 X9 p" r3 {% i" y! p3 W) d. g                     str_district,( j- t9 ?7 y: Z' L
                         str_road,
    8 G& q' I7 g. S5 V; c                     str_no])& t# P- f' j" c! J6 L) L
    s.str.replace(pat, my_func, regex=True)( p2 G% K4 }2 }5 \

    5 L' ~' P" \0 Z6 V! h& s9 A1
    4 F  s; E0 j" g! `; E' |+ g3 d/ j8 t$ {2
    - k! b/ @* @9 f/ d# q6 l2 Q2 z$ ]3
    5 y" P* V. }( K" M8 k5 z43 \  {4 U# P& |& v$ {$ K
    5
    % G: T; O7 h! o! A5 X1 l  j6; y2 k' W$ P# F
    7
    % s0 j& R2 L& U  S; ]) P8
    % O& b1 i# n/ q/ L& x" w: H) \+ I9& h0 v& r* d7 {0 L+ }4 ]4 ]
    10' n5 X+ g4 [& ?1 R( @
    11
    ( X3 ^1 w" r7 v9 b: i& z( H; C0 _; \12+ s! M7 |7 b+ f) Z# A9 B
    13- F9 o7 D8 F( v( q. ^8 G. F
    147 ~0 \! j+ w, c9 i. \/ e5 _
    15
    & D- g( z5 }6 h16
    5 J* Y/ G9 b' W/ Z/ y" v, L. O17
    9 Q+ e1 y9 O) S3 ~- i7 L) C) X. h18
    + L: n3 |' i7 h, @9 V19
    ' L' L5 c; x' ~% v7 x  D* l20
    # y- G, U  L: x2 R21
    - _9 u5 q" V* l" t0    Shanghai HP District Mid Fangbin Road No. 249
    , t, d; u6 {/ _" D) n3 H/ _1           Shanghai BS District Mishan Road No. 5
    6 b/ p* j. P! P6 M4 f2           Beijing CP District Beinong Road No. 2+ S! f5 O6 N: u: \" P! O
    dtype: object
    6 @7 v% V1 L5 k9 V2 u  x1
    4 V9 K- J  t; v; A9 Z26 y9 k! ^0 t! ?
    3$ F: |6 _* m8 l/ n
    45 F$ a5 d) Z4 x0 @' i" L  h
    这里的数字标识并不直观,可以使用命名子组更加清晰地写出子组代表的含义:  @% t8 p/ n) C% `0 |( L
    ; H" m! H. V2 B
    # 将各个子组进行命名% _8 V+ j% I/ N9 C" o' @
    pat = '(?P<市名>\w+市)(?P<区名>\w+区)(?P<路名>\w+路)(?P<编号>\d+号)'; w  B3 }0 s% E: Y) n4 s
    def my_func(m):; ~; s. O) K2 O' `4 j* D4 q2 e
        str_city = city[m.group('市名')]
    : n& Q9 P% [: a( }. N3 l$ Z3 h+ d    str_district = district[m.group('区名')]$ ^2 z5 \; }6 _. r1 _* A8 G
        str_road = road[m.group('路名')]
    # g/ e; A9 t3 v    str_no = 'No. ' + m.group('编号')[:-1]" h( m9 [- j: P8 \& Y
        return ' '.join([str_city,4 U3 U9 w+ m$ Z
                         str_district,
    % Y) i/ P) f+ s8 j, e* J. s                     str_road,
    2 l4 H, K! T2 i7 e                     str_no])
    4 W, f; T/ t+ V: K2 Us.str.replace(pat, my_func, regex=True)8 k) x/ M' V% i/ ~0 S! Y
    11 R6 p; I  k+ O
    2
    - m) X# U. [5 S' E7 l$ G3
    ' a  x; }2 G  H8 n4  ^, j/ M2 f( A, v: G+ w
    51 d4 _. B' b6 h) d% A2 y
    6
    : ~; m! v. h3 c9 k9 q7
      R/ z% F. B' u% b8
    0 S: s7 R8 \7 C- I9+ Q8 f4 e8 \( V6 ]
    10; p! S: B/ L& S. h. W) t+ Q- y
    11
    4 O' p0 Y* I2 j; ~; A12
    2 U8 ^  N) L' C0    Shanghai HP District Mid Fangbin Road No. 249$ x9 X+ ]$ {4 }/ J5 N) {+ `- F
    1           Shanghai BS District Mishan Road No. 5+ W" S( P; y$ E; E7 m
    2           Beijing CP District Beinong Road No. 2+ c5 c/ R) ?1 d; c, y8 l1 o
    dtype: object9 q! e0 f/ i0 I1 F0 }3 p! t6 ]
    1
    3 T6 A0 z5 @  k8 x5 r9 ?' i2+ h" p' i% [( Q1 d* v
    3
    : r" H+ S1 M  S+ C+ t6 r$ X; A4: ]: f/ v5 k& i1 {3 R7 j, U9 [$ U1 ]: Y
      这里虽然看起来有些繁杂,但是实际数据处理中对应的替换,一般都会通过代码来获取数据从而构造字典映射,在具体写法上会简洁的多。
    ; N# E* K2 R" [& y+ n3 j& M6 n8 D! _  |' d( C0 Y# ?
    8.3.5 提取/ p' h7 k- n* j  O- ~4 k
    str.extract进行提取:提取既可以认为是一种返回具体元素值(而不是布尔值或元素对应的索引位置)的匹配操作,也可以认为是一种特殊的拆分操作。前面提到的str.split例子中会把分隔符去除,这并不是用户想要的效果,这时候就可以用str.extract进行提取:7 p  c, u( O: f7 r9 U9 i
    s.str.split('[市区路]')8 E  {) w+ H( F  U7 J# \! C
    Out[43]:
    ' `; E( F0 z7 E" `8 y0    [上海, 黄浦, 方浜中, 249号]9 k3 E0 I) o3 K4 {2 T
    1       [上海, 宝山, 密山, 5号]
    6 X( ]4 c3 O+ [5 g- |! zdtype: object9 q. I- K2 ?: n: K
    # U4 \( N+ s0 C# ]" t
    pat = '(\w+市)(\w+区)(\w+路)(\d+号)'% y  R0 x- Q- {, H4 ^" n: \, c
    s.str.extract(pat)
      J5 S6 {$ p: l5 k$ L. a- a: fOut[78]:
    8 x" s* \) r7 c% @    0    1     2     38 h5 i) k/ Q0 w" U6 s( `
    0  上海市  黄浦区  方浜中路  249号7 J0 V) z! B8 L) A4 w9 W; @2 Q
    1  上海市  宝山区   密山路    5号: i7 X2 m& I5 m3 }3 d
    2  北京市  昌平区   北农路    2号( B- Q+ ^9 n& T" D) |
    1
    : A0 e5 ?5 W1 v+ `1 |2
    " p( w( g$ T" g* {38 V! D2 ^3 h- |8 A) R* B5 x2 H
    4) ~# q) C7 ^! N( ?: q; q
    5
    % v+ M5 d4 K5 g4 j; C& ~( |4 L1 r6- S2 |" v- m* H/ H; B! p
    7, [2 W; f: [- {; L
    8
    % q8 M( y; d0 S$ W" J% l3 d9" I8 l. S8 v  o0 C5 c. |
    10! C4 A% Z3 q" p0 K# b; k4 Z( I
    115 u/ P+ j# n: M2 T( V" H# g! w/ J
    12
    % S2 \& o2 r: q$ S; G$ s, g139 V5 l( @9 L: h6 H( X9 m8 E2 x. _
    通过子组的命名,可以直接对新生成DataFrame的列命名:* s4 {$ C4 C0 t
    2 z9 L% q& G7 n: t+ a6 h+ C' I1 k+ X
    pat = '(?P<市名>\w+市)(?P<区名>\w+区)(?P<路名>\w+路)(?P<编号>\d+号)'
    $ b. _# r* ?3 e3 y. C1 Os.str.extract(pat)/ H- Q9 _! Q/ @4 |( d# O2 _0 e5 P/ V
    Out[79]:
    7 `- l$ M+ a# {    市名   区名    路名    编号; J+ h( a- _& |- E+ e
    0  上海市  黄浦区  方浜中路  249号
    % s6 R9 B3 y4 V0 g8 o  ]1  上海市  宝山区   密山路    5号: w# C  H. C' {7 o1 @; y
    2  北京市  昌平区   北农路    2号
    ' E' x% }# E& {7 i5 `8 w16 p, n& }( S3 v$ @/ j! d9 i+ X
    2
    ! ]8 N, U7 i" i5 }5 N3; v5 |2 B; n; N6 e/ V) `% k
    4# K( S6 |2 d: S
    51 _3 l% H! }! ^( f4 A) G3 i) ~/ d
    6& s0 M% H$ |  V0 ]
    77 ~$ u9 s0 U# Y6 @0 _2 s
    str.extractall:不同于str.extract只匹配一次,它会把所有符合条件的模式全部匹配出来,如果存在多个结果,则以多级索引的方式存储:
    * ^2 p3 q+ [1 l* y8 F: M, k/ Os = pd.Series(['A135T15,A26S5','B674S2,B25T6'], index = ['my_A','my_B'])
    & k" f) o* ?0 I9 ?) e$ Qpat = '[A|B](\d+)[T|S](\d+)'
    ; ]  F2 y8 y# x1 F/ t$ ?s.str.extractall(pat)
    % D/ y0 J+ f0 d# @( W$ \  iOut[83]:/ x. v. E# m! \* [1 ~
           0   16 k. d$ f  k3 O9 \0 ~
         match         
    , F6 z! S, i8 k/ \& {2 F- l  b' Mmy_A 0      135  152 _- O6 p) y. s8 H5 W' e; L
         1       26   5% E! k% a+ D- O- C2 Z
    my_B 0      674   2" |) ]6 Q6 P* k5 H3 o" C8 }
         1       25   6
    , x9 o2 V8 e  t6 ~) J* U7 _1
    0 D1 _2 K5 k& J* h$ k9 n( G2 z2 @2& t# c5 _4 |( E( X  T' I  R
    3
    7 L/ {5 Q9 e# T: c( |+ C4
    2 Z- r/ F$ x  f- M, m5
    . {/ _) s; q# y% M6, T5 x% p$ \5 D1 N
    71 J' _# W6 @- d
    8
    5 W6 K$ r* k& G, |, n/ ~9' h; \3 d. D+ V+ I5 ~& {
    10
    3 F3 v$ L- P5 H. P$ j: T% ]pat_with_name = '[A|B](?P<name1>\d+)[T|S](?P<name2>\d+)'
    ( k7 g; i' c7 T* @s.str.extractall(pat_with_name)) ]' B& D* P* j+ e
    Out[84]:
    . c8 W! X: `7 f" i5 F% c           name1 name21 D# F! q# j" W& Q  s
         match            4 Y0 \4 y' V& s$ X# r
    my_A 0       135    15) }' J7 J( _1 L3 b
         1        26     5
    + n! ^! T" P) F+ E9 S+ j& R+ }my_B 0       674     2
    + S0 A: b8 k3 m3 \) m     1        25     66 n( C+ y+ g' t$ N( i
    1
      V4 p3 H- f: K2 P7 W  j2
    : N9 d) r2 L  Z3+ W) C9 x  x( U" T9 k- W8 w8 j
    4- D. U& G8 W1 ^( k. p2 r+ ]) S
    5, w9 d% {' Z' r& d9 v
    6& [5 A* C3 n% J$ w3 |& U7 |' U* v
    7; ]/ r# Z' H3 @8 Z* m
    8  K/ m, y! Z- x2 ?$ Z
    9
    6 b# K- [8 c) }$ Ostr.findall:功能类似于str.extractall,区别在于前者把结果存入列表中,而后者处理为多级索引,每个行只对应一组匹配,而不是把所有匹配组合构成列表。
    * I4 w& I9 D0 w  hs.str.findall(pat): T# }4 D7 U, ]# A0 t
    1* H  f* u* Z, u) j6 v$ T2 s6 ~9 o
    my_A    [(135, 15), (26, 5)]  v& \; |' H2 F' l/ G
    my_B     [(674, 2), (25, 6)]
    : [, u+ ?- P) Y4 H9 b+ u  E! Gdtype: object
    , c+ u  N- l+ C( N3 H* G' ~1
    0 m) p  X1 h# ~2, z& F; l- Y) k( O# B# k% a" c
    3
    ' h6 G5 v# D- S* n3 b3 d: z8.4、常用字符串函数
    8 C( v6 N; A& T' V3 F6 @- K& \  除了上述介绍的五类字符串操作有关的函数之外,str对象上还定义了一些实用的其他方法,在此进行介绍。
    2 c( \. O8 s; ]. o; k3 T
    * p, D! r6 ^; D% i( }. M8.4.1 字母型函数
    : q) e3 N) z9 w; S+ h/ }/ q  upper, lower, title, capitalize, swapcase这五个函数主要用于字母的大小写转化,从下面的例子中就容易领会其功能:
    $ B% Q# P$ P  X6 z- f4 O7 f9 |. {5 h8 Z( U( W3 L
    s = pd.Series(['lower', 'CAPITALS', 'this is a sentence', 'SwApCaSe'])
    6 K9 |  E" C4 e" {8 `; h
    : z1 n) x- k. i/ H7 N4 Cs.str.upper()
    " q" P# ^8 H# j# v" p$ KOut[87]:   x# Y+ ]+ _$ L# [$ U
    0                 LOWER) W. t- g2 i3 w: M, v! j
    1              CAPITALS
    : U! O; a4 D  t3 Q. _2    THIS IS A SENTENCE
    " O% I+ V" p. d" c2 u3              SWAPCASE/ U% i% B0 k! ^" _7 F% g, ^! v7 e
    dtype: object/ f( ^4 y* ~+ t2 [$ Y& S' V. m& R! _

    8 E1 u1 Z" K' ]9 U  T" |s.str.lower()( @6 U+ V( D% Q. S
    Out[88]:
    5 }  V& e% H: s( v% J0                 lower0 I' J! m/ q: @! U
    1              capitals0 |# A& {  ?( s0 j2 r/ N% w
    2    this is a sentence$ C" d* N- E+ x- P& D
    3              swapcase$ Q7 k1 Z% o& p; R: h
    dtype: object
    4 k/ W3 P. o4 p! ?+ z  E
    7 u% ~) Y; q) os.str.title()  # 首字母大写
    $ M# @5 Y1 K" O3 J# A+ [Out[89]:
    ( l4 I7 a" V, P) C, j0                 Lower
    2 ?3 Z  l, Q& v& k$ @: N% o, k1              Capitals' D* {5 t' O- O- i1 ]* T/ H/ b1 d
    2    This Is A Sentence: E; k7 }3 \( [( @( C$ F) e+ j
    3              Swapcase
    ' H4 W3 k8 H3 N, t% ndtype: object
    - ?5 I7 ^0 g- v& U1 Q% V. q
    + u6 n6 I2 g6 F  u( Ds.str.capitalize()  # 句首大写& v( W+ W# |7 }  C
    Out[90]:
    5 O# O8 \3 P6 E3 y0                 Lower3 @8 X! R% Q/ z* h
    1              Capitals5 {# r6 y& T& n; q
    2    This is a sentence
    * I( n% e) I( F$ N: F3              Swapcase# f3 k  ]. w' A- p9 {& u$ ?
    dtype: object
    ) c3 K! d5 o! I5 G( J- ]$ ^3 x& B: E, M6 V( r
    s.str.swapcase() # 将大写转换为小写,将小写转换为大写。) Q1 l  I7 O& \7 `; D
    Out[91]:
    0 f0 u, _: a. H1 u! [/ Q( i0                 LOWER
    ! h& h3 L/ v; r% I. N1              capitals
    : k4 m1 i% F5 H/ s5 u3 _5 s2    THIS IS A SENTENCE
    - n) U' j6 |( t$ O, b/ {) o3              sWaPcAsE
    4 X. X+ C* d& s+ f4 d* v  f+ adtype: object" E7 G) l2 d( o; x

    * `/ O8 T& _8 J- Js.str.casefold()  # 去除字符串中所有大小写区别
    4 S8 i) U' n( x- r: s/ L0 f: @; F6 h" k( q8 s
    0                 lower: v% O# H/ j/ Y8 N; U
    1              capitals
    8 n: G. H7 u, w& X5 G0 o6 k2    this is a sentence% k! u  D! g. |1 J7 Q
    3              swapcase2 i3 V, K' L" P% Z0 x" i9 a" F' q

    ; b8 w" e2 _- I$ b# k* p7 H1
    / M( {+ \# }6 L1 O4 }2 w4 [27 _1 }' ~% E! Y
    3
    ' n4 b5 E5 b: e5 L: T4) @9 b9 M* D- p$ ^& ^; l
    5
    * J+ ], Z( R9 l5 P6
    6 r3 D+ D9 O! E& i/ u7
    # S: n8 z" A# r/ I8
    5 N7 I2 W+ n0 j1 I0 k5 T: z+ D0 T- k9
    ( L) H6 V" N$ F$ \% c102 I! f3 X6 W  n; c
    11
    " `5 J' G# K4 r/ ~" |5 S$ U- n121 N2 j6 y5 o, c4 ^. v) S+ }' d( v3 H
    13( v. p) a# z$ a! K" }
    148 m0 w  b/ P) x, q
    15
    8 @% |# H! L- J16
      \/ f( A0 \# T: J2 j17; d( L: ~" ^, J* G0 M8 f; T8 L
    18
    8 W2 g  n+ _' _. n1 c. ]: R' F19
    + Q5 n. o7 |, y! `20
    - r: v" E! Y) J0 d0 _  T# L( q: A21
    8 O5 T: i, r' g3 z22% [1 y+ ]( U) K5 l* M9 ]
    237 S2 l1 g5 M5 K# ~5 {) W/ H  b
    244 q6 Z( k% V" f' n) [
    25
    2 t$ b1 V. S8 j2 P2 L* S$ H. j$ Z26
    . p3 s. m/ y0 o: s9 R27
    1 k+ h# T! a& h. t+ ^  j8 e% y" D28
    3 G: W8 f! k# l# K( D292 |  M0 P) n( O% V/ p
    30
    7 {, n3 Z& l8 u% D31. g0 z. N- H! ?6 K+ K
    32# v) b  M- M( M# P. |
    336 c2 z$ s5 N; b, ^0 F0 Q: M
    34
    , G& L% z7 m- `( @+ c8 h35
    ( `& p; r0 I; c36
    . @4 Z" M3 H! a0 D" G, E, `) Z1 Z37; I: S! {4 `" {5 h+ b0 D
    380 S6 U1 V! k9 s) e0 R) P
    39
    $ b  B: t2 w& R' {40' G3 K3 m, c& d0 T, v
    41- H$ C: p! E- ]6 q0 P# Y
    42
    0 ~* R; ?( u  d" y0 o43
    9 S* g, d# e: y/ u0 J3 `. e44
    - O* r5 G7 C% v- j" z" O# B, d45
    ! n6 c: ]4 Y3 V46' B2 h' _& h% z$ Y" d
    47
    / Y+ D/ E( F6 S: a487 e+ [( n# N! w" W% _- O
    8.4.2 数值型函数
    0 Y! J$ q* v9 g* ^) \* ]  这里着重需要介绍的是pd.to_numeric方法,它虽然不是str对象上的方法,但是能够对字符格式的数值进行快速转换和筛选。其主要参数包括:9 y% e7 m7 N6 K) F3 S

    + W  {3 \8 `. w' ~9 M* Jerrors:非数值的处理模式。对于不能转换为数值的有三种errors选项:) e6 m, o0 G" n# ~. h
    raise:直接报错,默认选项% z5 d8 w% u- ~6 c7 A
    coerce:设为缺失值8 Z3 a: }: o) F$ V0 U9 I; X! k  z7 m  K# ]5 j
    ignore:保持原来的字符串。0 P& ~# {! b$ F" L+ V
    downcast:转换类型,转成 ‘integer’, ‘signed’, ‘unsigned’, 或 ‘float’的最小dtype。比如可以转成float32就不会转成float64。
    6 Y. r. [: _: W& U3 H& ms = pd.Series(['1', '2.2', '2e', '??', '-2.1', '0'])
    0 r$ c0 P, l& s, y( C5 n# a$ H! p+ J' @7 P+ u" {. \8 `& ~
    pd.to_numeric(s, errors='ignore'). H1 L) x5 P9 T. b
    Out[93]:
    - N8 {  |" g- O0       1: w( t; Y0 f- o, s: M4 U
    1     2.2
    & `# O" L% k# {% b0 G2      2e; W1 n' F) p$ ^
    3      ??, a+ m% x6 F+ ?* l1 u1 _
    4    -2.14 f, {4 K5 D6 n% ~# l9 H2 s% s
    5       0
    % y% o3 F+ F, B2 v3 Vdtype: object
    # O9 Q- \3 O7 q: S
    1 Y4 P. v2 u0 G  Vpd.to_numeric(s, errors='coerce')2 M( ]3 W7 z+ X1 z
    Out[94]: $ i5 c" a- k8 U- |' f" @1 k2 i  h
    0    1.0
    . X* E6 k5 f5 D7 X1    2.21 ~0 {8 L& P. ^6 t7 R
    2    NaN
      r  X7 u6 S( k/ S2 o& u3    NaN4 j! Z0 k4 t: k
    4   -2.1. d. [" u/ t2 G8 W! {4 r% c5 w
    5    0.0
      l+ n4 E' R- \  U; y: C" v0 sdtype: float64
    $ M5 {1 Z5 R, J/ S$ J
    4 x" Y- D! J8 s0 q1% p( P3 N) O. j$ s) g$ H/ T, u
    2  E0 A* A* }6 P" j( U7 B
    34 h# I6 h- o3 }' p$ [% g
    4
    : ]" o! d4 k! j! J. k# M3 `5& T5 j/ Z1 i* F2 v4 U
    6, a/ n* i1 l7 i2 J3 f5 E
    7
    * {* |9 j9 _$ c! A$ ]4 y+ s* C8
      P$ k( V5 P& P; A0 i9$ U& S( S! l( u1 S  k
    10
    . ?& Z& d; l+ n3 Q+ t$ Z  {( Y6 [11
    , }4 V, U. i6 I- `4 \' M  Z) ?2 u12
    - K6 L% j& m- }9 w( f5 l13
    ( P  h; ]3 X# P' Z/ h14
    * v5 J3 o( E0 X8 z& {+ I% Y5 W15
    ) k. p. D! t& }% Z( {* q16
      k* V! C8 x) u2 c& c$ Q! D17; F/ \0 }" h0 d8 }" D* }
    18
    $ E' a/ [( B! \! }+ j- v2 g19! u4 K5 Z# F. F6 U
    20
    5 V4 c/ P- M# ~* |21. n, r8 C3 Z  D0 c; N1 w8 t
      在数据清洗时,可以利用coerce的设定,快速查看非数值型的行:
    ) t6 K+ R: Y3 t+ \2 y
      l) J1 @0 \5 P6 a& E" Xs[pd.to_numeric(s, errors='coerce').isna()]$ @2 b. T4 P, j/ H5 }& n3 p; i
    Out[95]:
    5 s* f8 g; r, M5 X2 q; y2    2e4 z$ C8 O/ t1 K) y
    3    ??- n5 k% Q: b/ ]8 F
    dtype: object
    2 q9 F" u  C/ W+ e8 n: z18 K8 l/ {/ w/ S' V" F: U
    2
    - b/ x) J; r& {: z; n  K# X3
    ' c+ Y3 h! Y8 Q0 W) s( ]/ J% B4, q3 [2 P; ^, h& u4 G
    5
    % S5 F6 X8 k7 S0 ?: |# W* s8.4.3 统计型函数
    4 l& _4 D* H8 c  count和len的作用分别是返回出现正则模式的次数和字符串的长度:8 b( I( i- `* L

    . `" ?& h5 _9 t% p% Y% d9 Ds = pd.Series(['cat rat fat at', 'get feed sheet heat'])
    $ s, ]; U( T+ b; s/ F" m
    + p! G; N0 q  n8 C6 T. gs.str.count('[r|f]at|ee') # |左右两种子串都匹配了两次& Y9 k( v/ b8 S: P5 ~) ?: A
    Out[97]:
    ! B) D3 K6 ^6 o+ K* _0    2
    ! t; s9 w2 H' y+ N7 H. G1    2& x6 E* P7 L6 b2 f
    dtype: int64
    3 A: }' D' M% n/ d' V# ~$ I" n5 K& m4 S+ D9 ^. [
    s.str.len()9 `9 x* r# G1 a6 {1 H
    Out[98]:
    ' j2 h9 E5 `+ E$ r0    14
    # c2 c% |) B1 H" d  g$ {2 H1    19
    . i7 ?4 {9 S8 X* ?" p6 ]dtype: int64
    ; E/ e5 a! ^$ d' b6 J! P1
    1 d3 N4 o. T, v! {2
    # w- }# ?. {; s3' ^/ |1 L' H+ _
    4
    % ~5 `* c  I% l4 ]54 _& e$ T* @1 l# Y. i: i% h
    6
      e8 _- C+ _0 W3 ?; G6 O9 p; y76 [6 g' V, L4 ^, x" H, @6 S3 P! g8 f, e
    8& X. Y) q- B& p. \: {2 J- Q! |1 \
    9) o  _' {' q/ I) ?" z9 Y" l
    102 O  \: Z. I5 X" A
    118 m0 M' a9 c. [. T  g! c
    123 r' I" i/ s4 M( V) R& S9 K
    135 W3 ~% L7 @/ k4 K& C, I! T* u
    8.4.4 格式型函数0 h1 D3 w3 y% y' g
      格式型函数主要分为两类,第一种是除空型,第二种是填充型。其中,第一类函数一共有三种,它们分别是strip, rstrip, lstrip,分别代表去除两侧空格、右侧空格和左侧空格。这些函数在数据清洗时是有用的,特别是列名含有非法空格的时候。# a4 F. [/ m9 c/ _3 N
    ( P; C+ p; U) b1 K9 k7 W) |2 d. m6 i
    my_index = pd.Index([' col1', 'col2 ', ' col3 '])2 W5 ^4 d* B; }( ^

    ' z# _# A9 Z3 l6 @; D$ C* Qmy_index.str.strip().str.len()( ~6 p+ r9 Q' a2 l8 J, L$ k7 v1 [
    Out[100]: Int64Index([4, 4, 4], dtype='int64')
    0 K% t% k4 G* ]
    . N8 Y: X. S) C7 Q( dmy_index.str.rstrip().str.len()  n' A( U4 P$ Z( D3 }$ \3 u& S0 x
    Out[101]: Int64Index([5, 4, 5], dtype='int64')
    + ^/ J" s! e; K! p, e- V# o: W2 \5 i  s( R  I
    my_index.str.lstrip().str.len()- n5 N5 E- D$ c6 E' y
    Out[102]: Int64Index([4, 5, 5], dtype='int64')
    & ~. j; L( l0 o* f: b1
    . r" m0 Q8 j$ ], e* ~2# j2 @9 D, r5 S+ N! {
    3
    3 {+ p, A/ V. \( _& R5 c, T  @4
    / G' h3 l& V/ B' B52 ]7 S" v+ g8 m: C5 R, [5 U2 }
    6
    , \7 F) D6 _" {; G7 r, {- y. Y7( n4 s: q' y  N6 w# g5 ~
    83 D2 h& M$ C! y& U3 h. m- S
    98 |9 ^/ Y1 d+ P& r% E
    10
    $ v/ M' w  z' L( i4 @! N7 |  对于填充型函数而言,pad是最灵活的,它可以选定字符串长度、填充的方向和填充内容:
    3 _8 i7 \0 s4 D3 x( t( L* ~+ q7 b) E3 i6 p3 Z
    s = pd.Series(['a','b','c'])" K5 O$ M! [3 D( ?: J% M; p

    ( O" t4 k: A; Z) R* K$ ps.str.pad(5,'left','*')
    ; K* m# c( R( @) o( \# ^/ z8 ~Out[104]:
    + f8 Y- [  A) F4 k. n  R0    ****a
    4 O* t: W, o& ^3 l- k; {* M$ X, D7 k. Q* D1    ****b
      H" i# ~* U4 N7 n  j1 B2    ****c
    ) D# {/ v  y, G$ ydtype: object2 [. g" ]2 W% c2 I; Q& g2 B( v

    & R. G/ B0 T8 f) k+ {s.str.pad(5,'right','*')) K( E: p3 S" f3 K, k
    Out[105]: % I2 N5 U- S* Q# A, S1 L
    0    a****
    7 E; A+ o, Z8 I3 m; I3 P9 L2 F1    b****
    6 `8 Z+ ~; H) z- S7 z3 s# g! P8 s2    c****
    ; l, C% `( O2 w% Y: Adtype: object4 ?" f" h& ~' ?7 s" }5 @8 A

    / e) r1 w" _# o0 b, D6 g* @s.str.pad(5,'both','*')
    3 D' [% y- P! k# o7 MOut[106]:
    ) O: `4 e$ M  k" G1 y0    **a**7 r7 C+ M5 g. h) X
    1    **b**
    4 \( A$ N& X% L" ~9 Q2    **c**) d6 ^% ~+ b, ]& \% ~7 j
    dtype: object; k8 x' u, @$ v1 n1 W
    ) W' Z# C" _. b- ?" s( r9 Q
    16 B- o8 b: m2 c& u+ }; ~, \6 {
    2
    : X$ @" N5 M# U. k) U3
    2 x0 L3 L1 g/ g# K9 p' N( O4) P3 }* ?: x% [1 r5 p4 ]) ?5 F' h% h
    52 u. P1 e0 F8 c
    6
    0 d9 p# C) P& x. d& Z( A; f7
    $ k* t+ \4 G) M# T: _8/ r/ x( _: U. A5 O. |7 ^7 X& i2 A
    9
    # R) B$ I8 H: J  N- q* W6 d10# }% K) C0 i$ k: X& p
    11
    5 X  g8 ]1 @! O$ l/ V  g12
    % z  a0 ^- V( a- B$ l5 M4 S13
    " ]; z9 ^+ z+ j/ H. ?14
    * x% a/ t% p! Q" M/ i4 N, h$ M15
    ( g$ B% B# O" @* e6 ?16
    ) ?- A! Z! C5 D  z1 i* d) F17, n' G- R. e- T  w3 V
    184 Y2 X) |2 ?- ~; X# f& N. E
    19
    8 j1 R- P2 z5 i) v$ ]208 G' A# t9 m( v( H. K
    21( L' r& _1 n1 n% F6 B% H7 s
    22
    3 R) d0 C4 S8 t9 Z  F- i* h* n5 U  上述的三种情况可以分别用rjust, ljust, center来等效完成,需要注意ljust是指右侧填充而不是左侧填充:4 T' p  l" m# N9 l4 i* G) K6 c

    7 d9 J4 k; l2 p+ L: j( E8 \3 \s.str.rjust(5, '*')
    5 r5 u! _) l3 t% I& yOut[107]: 0 _; j1 B/ K9 Q* s6 P
    0    ****a: V5 F6 P- {  S& r7 Q
    1    ****b$ `- |& c* v0 n' a( Y; x$ O
    2    ****c
    - Q3 K- w/ S3 h8 C# `) H3 cdtype: object
    * l; D- T1 R, O  N; t
    & o8 X5 A" Y3 A& cs.str.ljust(5, '*')
    * u) _  c3 n$ ]5 zOut[108]: 0 V3 d2 z; |) v* x0 T
    0    a****
    ( Z  Q; y0 H7 i6 t1    b****
    $ S* {, q& s( p$ _; i2    c****
    6 C, W1 {+ S* I2 ]2 d3 zdtype: object" l7 x# e, m: o8 Y

    7 K+ [+ b2 [. S. G* P" _0 ]7 vs.str.center(5, '*')
    7 K7 n' \% C' }( w2 M0 D! m: tOut[109]: ) \0 O# }  \7 h' Y4 q* @* A& N. h
    0    **a**
    . L; p9 j/ v- S) U2 k7 g( A1    **b**
    $ d' X4 m3 w$ x4 d- j2    **c*** @' g0 ~1 `6 ~' T) r1 q2 ]3 Q- x4 O
    dtype: object  t! {3 P) L4 v4 L

    & W8 s3 R# r: W6 S$ {$ T- I1
    3 ?6 A3 R' B/ _& A2
    1 a$ z* Y! A7 x2 @3) R0 B- }3 g) I/ F4 d' |! Z+ w
    4
    ! W+ t- T: k) n5 H5
    ! N- c( K+ q; D- k3 ?: L6
    $ x# s7 q' O  B# D! e5 Q) M7$ }; M; r; _1 P0 c
    8  P: ~+ i2 n4 L4 x
    9
    / Y# J. L; M! t3 `3 g! I7 c, w; a10& M& g/ g( r, ^4 g+ U0 f% v
    11
    ; D" ^% W5 R* i8 J! Y. q6 P! q12, S- A8 w" O! g, Y; |" J
    13& r- p2 j+ Y  r" \$ j" G
    14
    ; ]$ B7 o9 \1 H# P* Z15/ g) n: I& x% u5 y% X
    161 ~3 a1 A* f8 r
    17
    2 D* ^9 u# v# \7 k7 W/ R; y182 I! [0 M+ e8 V; v: r
    19  i, O- C0 n6 X/ v' d
    20
    * M* \/ o. G4 P. f' I3 o  H# F) D  在读取excel文件时,经常会出现数字前补0的需求,例如证券代码读入的时候会把"000007"作为数值7来处理,pandas中除了可以使用上面的左侧填充函数进行操作之外,还可用zfill来实现。
    . o- w7 g! v( o
    " l- y! a2 @- ks = pd.Series([7, 155, 303000]).astype('string'): k/ ]! g) g, t6 w. Q0 a+ C) j1 x5 F

    + z/ ]6 K2 t7 G; d  Ps.str.pad(6,'left','0')! X; `0 m$ \& U' y" u
    Out[111]: # Y) Z( X$ {$ f* g% j0 ]9 b1 ?" {
    0    000007
    ( O, D0 D: N+ I7 o; E+ l* q1    000155
    ! P: d8 h4 Y+ ~. j, P2    3030000 y  R6 x4 h+ L
    dtype: string
    - a2 b; O; Y. U6 t. O/ v0 [$ ~$ L" f, C! J& w$ a
    s.str.rjust(6,'0')6 d" J4 `4 x+ J8 [, x
    Out[112]: . v0 A9 u& s; d2 p
    0    000007
    " y. I" Y% G, h: J3 R4 t1    000155
    5 _# h/ y% ^- w' {* a2    3030009 [) P& d; [& v8 h% p" ^' t
    dtype: string
    $ ]) l' w7 z5 V8 P* e1 W) Z. _( l$ Z
    s.str.zfill(6)
    . M( h2 A6 \" v7 y& OOut[113]: / X* b1 ?' B/ m* g3 v3 c2 E6 G
    0    000007
    - j7 Q7 T8 S# N! ^- U$ {1    000155
    3 R6 w- _# u- k/ S2 \/ n: r3 \  k; Z2    303000
    5 M3 {* V: H. \: Y( P1 Ydtype: string
    8 z  c. T3 r' o: |9 B: T+ N2 E
    ! |" A, M! q3 k2 c; \7 X1
    ; H4 t2 T  f3 h6 V26 u  j* a' K! l6 c0 J
    30 ?  F0 \" D) ?
    4: s4 D0 J* u! g
    5) }+ }4 s9 Q' p! L6 O/ r9 \
    6
    4 ~$ C  A" U2 U7
    . }3 A. W. w4 R: C" N8
    5 x* C/ E+ N0 ?4 i9
    8 Y0 Q8 v, O3 R  B* \' `6 L: v" Z4 }107 C* d. {! w8 K& {
    11* P8 X% ^* E8 }2 [0 @6 H
    122 _, m- I# d* [' A9 @0 l& U
    13; O. {: w# S2 Q' ^5 i1 V
    14
    + u3 a% u- X, f* S" t15! ?* b' g# f/ H
    16
    3 d/ [& Z# `5 K% `0 X17
    + L' V3 j$ `- k18; A$ S9 r& G. S
    19  d1 z  Y, w  O6 ^" b6 P) k
    20
    $ I& U; S& ]5 u21$ P3 n1 z4 y% n4 v: S' Y" p: U( C
    22# w1 Z( H8 u- u/ X& S: K5 j
    8.5 练习
    ( G% E" k. j5 E" g& P) W) |7 w5 N8 pEx1:房屋信息数据集: _7 j( d3 Y. G5 n) |
    现有一份房屋信息数据集如下:/ v$ y5 h6 i4 ^* V0 c4 y1 I9 s

    5 x8 V/ L  m0 C3 ]df = pd.read_excel('../data/house_info.xls', usecols=['floor','year','area','price'])0 J' L  _6 g, t3 b$ \
    df.head(3)
    3 }! H; f5 J! XOut[115]: 3 h7 {! ]: I6 m( a, |+ B+ j9 T
          floor    year    area price
    8 j7 @8 n/ y% l) s6 l& R0   高层(共6层)  1986年建  58.23㎡  155万
    # Y+ W" C* B" t# B1 c! v5 @- Z1  中层(共20层)  2020年建     88㎡  155万
    + e* Q0 `4 E! a) Y% g2  低层(共28层)  2010年建  89.33㎡  365万+ m) M) Q( ?- y  V
    1& ?8 H. q2 L6 }( e# C" I7 Z) s5 k  L
    2
    0 Q* T& T" [6 w; ^& `% R3  k) }: @1 |* @8 t9 V5 {' p( `2 R( y7 [0 n$ i
    4" E) c6 k4 v" M  p; D1 U& y4 `
    5- ]' x/ Q4 H1 q! m; h
    6
    # h7 @2 J+ z9 s9 F6 S' j7
    5 ~1 r, `3 j9 {/ R6 R: J将year列改为整数年份存储。6 [! n, T  H9 ~, q# v9 k# P' ?. E
    将floor列替换为Level, Highest两列,其中的元素分别为string类型的层类别(高层、中层、低层)与整数类型的最高层数。5 l! H2 c- g4 S  P0 b( [( K2 d
    计算房屋每平米的均价avg_price,以***元/平米的格式存储到表中,其中***为整数
    4 ~; d8 B  C$ a% ?' F3 R* p, N将year列改为整数年份存储。# U  d/ W7 `' T! }4 R
    """! M- S) \1 E/ F! j3 `6 ]
    整个序列需要先转成Nullable类型的String类型,取出年份,再将年份转为Int64类型。6 k6 E0 C$ Z+ w  ]& c/ c) E+ r8 Z
    注意,转换的类型是Int64不是int,否则报错。即使astype加参数errors='ignore'跳过缺失值,8 x8 {, x3 C* v1 p/ W1 G" m( a5 q
    转成int后,序列还有缺失值所以,还是变成了object。8 j: w" B, s8 o& ]. X
    而整个序列转为Int,就还是Int类型,缺失值变成了 pd.NA 。. p6 q2 m, V. f% q
    """( K7 C4 m+ H$ l, s- @( {
    df = df.convert_dtypes(); l7 D. p; T' D8 j; D
    df['year']=df['year'].str.replace('\D','',regex=True).astype('Int64')1 T! T' }) Q9 {1 f  j" |
    df.loc[df.year.notna()]['year'].head()
    ; K  f* e  f# W! B. m) o& }, k& w( X
    0        1986
    , l+ L! g0 I- i5 o% y! r1        20206 K% }" T  |# W8 l4 ~
    2        20105 f/ l' o- v' R
    3        2014
    % _) o. H& o3 u2 D5 v4        2015
    6 y8 E8 `! ]9 EName: year, Length: 12850, dtype: Int64; `% H8 A. N: @: W2 y

    : O; J7 w: P3 o, }1
    ! S; h* C8 k" u% E  f  y+ x2/ h- t7 {, N+ z: |
    3  p- X/ ^% ~# U
    48 d9 F, r. t) n! l* M+ p
    5
      Y3 I0 E4 W* H9 e( ^: i+ w6
    4 Q: N  q" W3 I8 @* c  C7
    8 h" A0 z+ S* l" ?5 n  X& X1 K: N% _& n2 ?8
    9 `5 v. r+ \6 E3 R& G" ^- o9) y$ z7 J+ y* M% N. R
    108 t% B/ B2 z* A+ u' S* v/ ]
    11
    $ w+ k! C; B8 K4 B7 ^12
    9 K4 T, v* Q! ]% ?  `4 b: w13( a' Z) u7 u7 S9 i; T$ ?$ F8 y
    14
    $ k6 c! m9 B' D0 Z9 |) s6 \  Y" Z' ~8 X15
    3 R/ Y" S1 S% @; T7 P# k16. P# {- m2 `8 i) F- x0 I
    参考答案:+ K- P/ F" L9 {9 s1 V9 [

    3 H" Z  g7 b, j2 g- T% z4 F不知道为啥pd.to_numeric(df.year.str[:-2],downcast="integer")类型为float32,不应该是整型么+ h! i6 U) d& j! L$ m! y6 |
    8 f# e- x: g8 A% h, q7 G
    df.year = pd.to_numeric(df.year.str[:-2]).astype('Int64') + I) h8 |4 H' e  S7 W2 W3 F
    df.loc[df.year.notna()]['year']* F( m) p) C7 e$ P; T; o
    1' `! j' v7 G# P$ l. i$ f6 v
    2
    $ O, P! g+ T, M8 L9 u) w& n将floor列替换为Level, Highest两列,其中的元素分别为string类型的层类别(高层、中层、低层)与整数类型的最高层数。
    ; J( H% q& g! o; P) dpat = '(?P<Level>\w+层)(?P<Highest>\(\w+层)'* j# r1 J1 ^& y/ u. ]
    df2=df['floor'].str.extract(pat)  # 拆分成两列,第二列还是(共6层得形式,所以还的替换一次
    0 x1 g" x$ |' Y; H3 h/ i: wdf=pd.concat([df,df2],axis=1).convert_dtypes()  # 新增列拼接在后面,再次转为Nullable类型+ L! n% D0 I' [. T8 F$ O( u( B1 R
    df['Highest']=df['Highest'].str.replace('\D+','',regex=True).astype('Int64')              ' Q* q' Q5 e/ _/ _; o( l
    df=df[['Level','Highest','year','area','price']]/ l5 ^7 }4 R- B+ u
    df.head()0 q- x3 H: r' i2 {$ ]
    : q% u) V7 G5 R2 m/ H0 d
       Level  Highest        year        area        price
    " Y1 A/ Y( O/ e8 g4 m) X% A0        高层                6                1986        58.23㎡        155万
    % Q6 w  r# f2 _1        中层                20                2020        88㎡        155万
    + u0 u: X8 z/ c! {& G  _2 p  h2        低层                28                2010        89.33㎡        365万1 P$ L. s/ y/ g. A$ Z
    3        低层                20                2014        82㎡        308万
    & l& l- p8 s3 \& J2 y6 u& k, Q4        高层                1                2015        98㎡        117万& z) }) c3 P, j/ M
    1
    7 y4 }3 f' ?9 `29 y0 X: O# N' x! o, L
    3
    8 G( T8 ^/ @0 \" k3 ~" d- T1 T8 S4
    5 @  S6 e5 v) U! E& A0 W( m53 E$ C: z/ _1 F
    6' R) Q- I2 o/ F9 E6 ?
    76 L# o) x# i( ^2 i' x
    8
    ( v7 O- D* [' O: X6 u9# M& q6 e4 u: |" A1 A3 [
    10) ?( l% @( {# X$ c
    11
    # a+ n9 [+ h; |3 b/ N125 s8 B' K( y; R4 B
    13& D2 W8 N0 K& X
    # 参考答案。感觉是第二个字段加了中文的()可以准备匹配出数字,但是不好直接命令子组了
    0 ], }4 D: ^5 @5 O* Q- Zpat = '(\w层)(共(\d+)层)'6 J& ]/ z6 F2 M
    new_cols = df.floor.str.extract(pat).rename(5 u8 ?! ], O7 m" [9 g8 s
                        columns={0:'Level', 1:'Highest'})- a" S5 w0 {3 n/ j; D  }. R- x
    , X, x8 _5 E8 r6 A9 x, B
    df = pd.concat([df.drop(columns=['floor']), new_cols], 1)0 [) d/ v+ h; I- K9 _' A2 i- H
    df.head(3)$ R5 A7 `' \  @. E# k, _7 w9 R, L  J% f

      l& N; n. l  i/ a) X' C8 R+ U& JOut[163]:
      F/ a+ B7 u6 q2 Y# N   year    area price    Level Highest
    " u+ J4 S% m, d! `4 s" o0  1986  58.23㎡  155万    高层       6' e4 l( r( Y. |0 d3 Z4 g0 R0 c
    1  2020     88㎡  155万    中层      20/ U# e8 @! v9 b% B3 h5 v7 [
    2  2010  89.33㎡  365万    低层      286 ]) m. U/ B3 r; T+ \
    17 U1 _- V/ [, \7 ?! o
    2* a/ M7 U2 i) o, c0 \8 N  Q
    3# j- z  i% M/ U7 \& a+ x9 `+ U- Q
    4
    9 x* S# s3 J. @& a% P5
    ) @( p6 I$ ?% t- W! }/ Z6
    1 v$ j, S3 Y' O. w+ r$ C70 K6 \# k  o! K9 J9 W
    8/ W9 S1 {, d4 U% C( M/ |
    9
    % J: N7 X' x& z4 C10
    # q( G/ N6 {% Y11; U8 Q1 R7 d; a0 K
    12
    % ]. O% E) @, i) t2 R1 _' _132 s1 c% x! j; H+ `
    计算房屋每平米的均价avg_price,以***元/平米的格式存储到表中,其中***为整数。
    , L& e# y; _# R+ b8 A6 j; C""". o: B8 |3 X0 U3 S# {
    str.findall返回的结果都是列表,只能用apply取值去掉列表形式
    # V. i0 L# s# N& [5 z" D& {参考答案用pd.to_numeric(df.area.str[:-1])更简洁( c* Z5 \: M! j1 p: p  e
    由于area和price都没有缺失值,所以可以直接转类型) o% |/ \* \) Q+ [2 m3 @9 i! ~  M
    """
    5 j: ?* ^- g( |* `' N3 @: idf['new_area']=df['area'].str.findall(r'\d+.\d+|\d+').apply(lambda x:float(x[0]))& G/ o8 L3 `# _, Y' |; J
    df['new_price']=df['price'].str.replace('\D+','',regex=True).astype('int64')' ~7 @) G- Z! d; A
    df.eval('avg_price=10000*new_price/new_area',inplace=True)
    : j1 x; ^% n3 B4 q( U+ w; \* w# 最后均价这一列小数转整型直接用.astype('int')就行,我还准备.apply(lambda x:int(round(x,0)))3 Q; G& R% w* z/ r
    # 最后数字+元/平米写法更简单( m2 u: z+ j. M' _7 ~0 {% E6 l
    df['avg_price']=df['avg_price'].astype('int').astype('string')+'元/平米'! C, y$ m0 v: ]/ D2 {! ]
    del df['new_area'],df['new_price']: M6 b& [" c3 X2 X% K! n
    df.head(). d1 o8 I: k9 t, D; Q, G
    ; I  L5 R6 F) O- I% F( z1 e
       Level        Highest        year        area        price        avg_price
    . u2 P1 {# j8 V! p& ~+ u0        高层                        6        1986        58.23㎡        155万        26618元/平米
    / U. S  u9 B0 i1        中层                        20        2020        88㎡        155万        17613元/平米
    , [4 U* X" r) j( s$ ~: I. ^5 {% }2        低层                        28        2010        89.33㎡        365万        40859元/平米4 c. K. u. H; g  v+ ]" y' l
    3        低层                        20        2014        82㎡        308万        37560元/平米
    # e9 h2 [) z' K7 w4        高层                        1        2015        98㎡        117万        11938元/平米' G3 {* w3 c3 P3 W

    " V6 B2 b  Y5 k1# c. p9 q- I& @  G
    2
    - J9 ]9 H  O0 |- T" g) Z3
    # \3 \: D( o+ s/ T: u) p4! D5 m# \. R" w
    5- X$ N/ V  C1 g1 T  ?0 O6 z
    6
    ; ^8 `4 R8 L- Z5 C4 O  x- b4 ]7. k7 U6 O9 v- G" `
    8
    ! u" H) o, o8 U" i& b4 L% z95 T( ~2 h7 s7 t2 X  r# T* W
    10
    % t6 V3 b9 l1 m8 i) B' c/ N& ?9 ^3 Z6 y11; Z! T& E1 \- l
    12
    9 _5 S1 w( i+ x. R: O: u& t% U# G13
    1 o# Q  W! r. Z' j14' P1 d9 ?# b2 G/ q) j7 B
    15
      H( i# A  f& x) V16
    & p6 n5 p8 z; d% `; O3 e172 G$ h0 n, f7 @+ c$ {
    18
    9 C. }% `, D' P* L7 g" f19$ h, U7 G$ @. E* p
    20: t9 |$ b* R% }1 ^/ k4 P
    # 参考答案5 `! Q  h& S& [0 T$ {7 x
    s_area = pd.to_numeric(df.area.str[:-1])
    2 r; D4 n  U) p6 L: o5 R* e: U2 ]/ k, ws_price = pd.to_numeric(df.price.str[:-1])
    ' d# j$ Q* T1 Qdf['avg_price'] = ((s_price/s_area)*10000).astype(
    8 ~3 T% R' w9 A/ _                    'int').astype('string') + '元/平米'6 w% @# @$ n/ ~7 A6 F0 R8 c/ S6 _

    $ s0 O+ h% Y+ y1 _) D+ q, mdf.head(3)/ `3 U* Y) D& b, `
    Out[167]:
    4 ?% |  X3 k2 {. H( ?0 k   year    area   price   Level Highest  avg_price% q8 y) v' I: X1 p7 l9 [  \2 q
    0  1986  58.23㎡  155万    高层     6          26618元/平米+ H/ c) S# f1 C% E5 O/ b
    1  2020     88㎡  155万    中层     20          17613元/平米% I! H" B) K/ u3 t& A
    2  2010  89.33㎡  365万    低层     28          40859元/平米+ b+ m8 Y4 K! x' n) ]
    1& R2 Z& ]* l. ^2 t) V* T" Q
    27 V' t1 S- F+ u; k: H' l
    3
    , y& E- R& s9 B( d( @4  @( A' k! _" o9 k" ^
    5, l/ x1 U# @/ h5 y# z3 U
    6
    ! z' H3 i* A+ X6 Z" h4 c7
    2 _2 O$ {& j/ N5 ^. H8, n6 A5 g: C1 S( B
    9" ]6 x; U2 t# _
    10
    : Z; Q. ]" y6 h1 A; Z. S1 K: ?11- [( b0 c  F9 e1 b7 ]
    12
    2 j  L0 q) P, _Ex2:《权力的游戏》剧本数据集& c$ c7 s4 e4 h& ]
    现有一份权力的游戏剧本数据集如下:0 c1 U) z$ v6 w: F8 n. B# X
    - Z- _/ p& Z7 v1 k* R
    df = pd.read_csv('../data/script.csv')* v1 H) P$ W0 o" _3 u& m0 v
    df.head(3). M' l+ S& h( R# ?1 |, p
    . f. [3 Y& h; ]2 J! M% u' v7 ^, `
    Out[115]: 8 P4 M0 k$ V# V& V9 [" a
    Out[117]:
    0 e' r8 I6 o5 m3 ?  Release Date    Season   Episode      Episode Title          Name                                           Sentence
    1 u* h" [3 [4 ]) y8 C" e/ M1 K0   2011-04-17  Season 1  Episode 1  Winter is Coming  waymar royce  What do you expect? They're savages. One lot s...
    # v: h; }9 N+ h1 A4 D$ i& p1   2011-04-17  Season 1  Episode 1  Winter is Coming          will  I've never seen wildlings do a thing like this..." t0 e# x) _5 Y: D5 c. L2 J9 n
    2   2011-04-17  Season 1  Episode 1  Winter is Coming  waymar royce
    * G7 N8 p' z4 @1
    1 X' {1 V; @! P; w/ Z; |% ?0 J5 k22 J# @+ p1 w' ^# J+ t
    3
    # q- q) A; {8 _. ?5 P2 q4
    " T- @( h9 `" b9 ~6 c' c; H5
    & [  S+ r% Q6 K0 i% s+ R0 h$ |% e& r6
    7 e! V: X$ ~8 Z/ d2 [7
    % U: M1 q" B! t8
    & p$ O( d: W/ ^: j9/ Q) B$ u2 s6 ]2 j  C  B
    计算每一个Episode的台词条数。3 r  E! W% F4 ?* a5 W! B
    以空格为单词的分割符号,请求出单句台词平均单词量最多的前五个人。0 b1 X9 A& z; Z3 ?/ {/ z
    若某人的台词中含有问号,那么下一个说台词的人即为回答者。若上一人台词中含有 &#119899; 个问号,则认为回答者回答了 &#119899; 个问题,请求出回答最多问题的前五个人。# ?: p: @, }# D4 y" Z: ], ~" O
    计算每一个Episode的台词条数。
    7 h) {. P8 @) jdf.columns =df.columns.str.strip() #  列名中有空格
    ' Z5 i' q9 L9 L6 h  t* hdf.groupby(['Season','Episode'])['Sentence'].count().sort_values(ascending=False).head(), O6 Y: N4 d1 V' @

    $ j$ [# w' b% x' X1 C- u) V1 ^" qseason    Episode  
    ( {5 m; u7 f  h) `7 }Season 7  Episode 5    505& w/ ^2 W$ h  {) e/ _
    Season 3  Episode 2    480
    6 i' `! R- e6 j$ `" U# t1 c5 k' OSeason 4  Episode 1    475
    + M2 k4 W5 h7 j) M- [" xSeason 3  Episode 5    440
    3 V" L, o! _: e9 }8 l5 A( @Season 2  Episode 2    432! R; v6 f9 d& t7 U. Q) Z0 F0 a
    18 O+ _0 ~% Q$ i2 q
    2" U% {6 D& v% R1 z, z4 {
    3. ~# Z, P0 e* ?8 i4 W
    4% z+ m: f% y. B1 U
    5& p/ F4 ]$ q! w2 o! W: V  u2 w( m
    6  g9 q2 T0 e8 O) m9 ^* \% I
    7
    % x& h( H0 E. R8& ^5 i/ l1 v/ p% X! J9 ?
    9
    7 {& O; s. D( K$ M  j# _以空格为单词的分割符号,请求出单句台词平均单词量最多的前五个人。  O; W/ f! ~$ M' Z
    # str.count是可以计算每个字符串被正则匹配了多少次,+1就是单词数9 x7 N' i' `3 r& r8 z+ ^
    df['len_words']=df['Sentence'].str.count(r' ')+1
    9 |: N5 j! I. T# @+ `  ddf.groupby(['Name'])['len_words'].mean().sort_values(ascending=False).head()
    ( y6 O/ C. U& P. n+ a( f: t1 T
    4 N: r: H. j5 U1 M4 R7 K2 vName
    $ m4 W: H# c8 @( l# H: k8 omale singer          109.000000
    ' Q$ n% m, e! P; W) islave owner           77.000000$ f8 @0 H4 O" @+ n
    manderly              62.000000
    ! o+ }: m/ y+ l; D: v, ylollys stokeworth     62.000000
    % K) n2 \1 W' cdothraki matron       56.6666673 f& t5 _# `0 F! d9 ~4 f# S  \
    Name: len_words, dtype: float64/ J# V, o) |; A' G* m
    1, v" g" {& ~$ a4 ^: i
    2: _' S' {# N% Q8 n1 Y; k
    3
    ! ~# u' F& C1 g/ d& M4
      _( z( o  _, }+ E3 K51 [1 j3 o7 K1 K7 w8 H
    6
    % d" T1 _4 k) i% k& q7$ n* ?( v6 a$ A5 m! a4 q
    8
    5 O5 c+ s2 f7 \* x! \9
    - y% d, U; \# d/ k7 b, z10
    0 |8 G3 u3 d8 _; v11" n9 _1 \* V! ~. r
    若某人的台词中含有问号,那么下一个说台词的人即为回答者。若上一人台词中含有n nn个问号,则认为回答者回答了n nn个问题,请求出回答最多问题的前五个人。+ _" X0 J" y0 J: ?4 N  |
    df['Sentence'].str.count(r'\?') #  计算每人提问数
    * l  r$ v% B5 ~1 }$ |$ ]ls=pd.concat([pd.Series(0),ls]).reset_index(drop=True)# 首行填0
      n. m! P6 B. o( _1 l% {5 b$ jdel ls[23911] # 末行删去2 _3 Z) n" R, T7 f! t
    df['len_questions']=ls, H" V- t5 x0 o& b0 X
    df.groupby(['Name'])['len_questions'].sum().sort_values(ascending=False).head()# o- f: P! Y+ O2 O: T
    . M8 V: x, D% @# ~- J5 }  w# M% g
    Name
    , V9 s$ L- A7 t+ _7 ~tyrion lannister    527+ t& V& ^! ]; L& L/ B/ k9 e4 \
    jon snow            3740 ], b, \2 D7 j" ^
    jaime lannister     283
    + ?! H6 c; l  }! t: k8 oarya stark          2656 m; e) G9 o* s0 s, y) e
    cersei lannister    246
    ; q3 q6 W, h( [Name: len_questions, dtype: int64
    " U2 a+ P. q% m5 ~  T% h0 c8 O) J+ k( {& X1 U( O# Y! q1 i
    # 参考答案
    : o. H2 e. _& ~7 Ss = pd.Series(df.Sentence.values, index=df.Name.shift(-1))
    ( K2 r, j9 W. Y3 |s.str.count('\?').groupby('Name').sum().sort_values(ascending=False).head()/ a* N8 n/ w# F& a# C0 D) [7 S7 I7 J
    : b/ y/ c# G# E
    1/ ^& }6 x6 S* Y& I  c- x. f! m3 ?
    2: d$ k! m& A6 P$ j
    3# V- R4 E; F5 f. ?: I
    40 t7 {! @9 L0 v0 J/ f
    5
    & J& B5 d3 C2 t+ }4 L6* r, z$ _  _8 L4 D/ b$ V
    7, I2 E* y5 G" W+ s' M: a
    8
    % F% J# w8 Q6 v0 j6 Q9
    6 l; u/ W! T! @* E  @. \$ R7 j10) Q3 h+ o! L4 y; T6 O$ r, P
    11$ x: m) W. v$ n4 _
    122 {& E' \( N, G# d& Q
    13. \9 R* J% j9 V/ A4 x& d
    14) x! C. |0 U8 m8 t* L' }$ K, T
    154 f. F& z" i: L1 _" ?8 Z
    166 T( @2 p/ e0 d
    179 I- ^7 _! d  A, Y; [
    第九章 分类数据0 a& P( t) X; A0 V& D
    import numpy as np7 D. a0 s* n3 t" U0 L4 y  y! d* o
    import pandas as pd: X3 D$ A; w+ p9 i
    1; q2 @; R9 e, q
    2
    ' i2 N! p8 p# y  Z. y9.1 cat对象# ]& I# f" z/ A9 a9 Z: C* ]
    9.1.1 cat对象的属性
    # F/ @2 H, ?5 u! H5 X% m8 U5 Z  在pandas中提供了category类型,使用户能够处理分类类型的变量,将一个普通序列转换成分类变量可以使用astype方法。2 j2 k9 v/ o0 ]

    + e; x4 P$ q. w( ydf = pd.read_csv('data/learn_pandas.csv',& x# ]: n) z# ]' o2 t, j5 h
         usecols = ['Grade', 'Name', 'Gender', 'Height', 'Weight'])
    # J) Q8 G) o( P8 R5 `. F' zs = df.Grade.astype('category')  D8 I$ S2 L" Q% t5 `# ?5 ]
    ) _/ R# g! [0 Y' @) G
    s.head()
      {% d( r) a1 \! LOut[5]: + ~3 m# [: N" w
    0     Freshman
    # v7 a" v/ }" d2 i9 E$ {6 g- a+ K7 f1     Freshman
    0 f1 Y. X/ z& E0 O" {2       Senior$ w0 [, q1 |" [+ q
    3    Sophomore2 d* i1 q+ ~: u7 p; H, S4 Y9 w. y
    4    Sophomore; k% f% N8 |: m. F+ {0 L% Y
    Name: Grade, dtype: category; h5 f/ K& G! P2 t9 G- M7 ]
    Categories (4, object): ['Freshman', 'Junior', 'Senior', 'Sophomore']
    7 D+ n$ F7 P! X  y10 Z4 S- y  o, D" D. d5 [2 @
    2
    & Y$ D- B$ N2 k  z3  h0 N2 z$ |: ^. d6 ~0 v( r: f0 @
    4
    , k6 ]5 ~# ~. V5% z& {$ C8 i0 A3 p$ J% Q
    6$ C7 {- i( ^- B" G# Z8 C
    7
    ' X) T( P* U% f8
    9 ?. p3 \) c: e8 B9: V$ D3 q2 O# f6 N1 L/ W; `
    104 u: M' P' k) n( J5 I
    11
    & A6 L$ L6 u1 I' u1 x) S8 O12
    0 g, y. |  d5 f  l13) Q; ^2 {- p( p+ j7 H, Z( e8 l
      在一个分类类型的Series中定义了cat对象,它和上一章中介绍的str对象类似,定义了一些属性和方法来进行分类类别的操作。
    ! k& W* N; O' n9 ?$ z4 {1 t" y
    0 L/ P9 J/ v8 N5 N: bs.cat& l* ]6 W# @- a4 K1 k" K
    Out[6]: <pandas.core.arrays.categorical.CategoricalAccessor object at 0x000002B7974C20A0>
    . ^4 M0 @  C: t& r2 n15 l3 @. N! o8 b% E) ^: ~& U
    23 Y5 p6 R' `6 W$ i$ p9 |  v
    cat的属性:8 _% L7 J" `* |6 z2 }
    % l1 z/ q: Q0 q2 c$ [
    cat.categories:查看类别的本身,它以Index类型存储" X$ _1 `- C$ @) x
    cat.ordered:类别是否有序
    9 |  i" w, B  \+ M2 Tcat.codes:访问类别编号。每一个序列的类别会被赋予唯一的整数编号,它们的编号取决于cat.categories中的顺序
    % N" o. ^( v' n$ j" Fs.cat.categories4 E4 S' p' E5 I% [$ ^/ V2 v
    Out[7]: Index(['Freshman', 'Junior', 'Senior', 'Sophomore'], dtype='object')$ [! ^' u: S' E' a' t7 r5 Z# @

    ( r/ d. s6 J2 E8 Q  |s.cat.ordered
    8 _- ]- S, _/ P6 Q3 K: dOut[8]: False. M" o  Q9 h0 r

    4 @) |0 a9 x2 M0 o6 N4 f: ?" ]s.cat.codes.head()" {& B. P  j" A
    Out[9]: * {; b  P3 B' k" @# \
    0    0
    9 m8 `4 n; p" {" x  a, ~% R1    0/ M2 f5 W5 b+ W9 P
    2    2
    : }$ H/ N6 e/ r+ ~4 X3    33 r/ u9 ~" \7 V+ f
    4    3
    ( p* `1 v6 Z2 M9 Adtype: int8
    4 z7 ~$ y2 x6 }8 o  A) z5 G1% a! F/ S% t' d$ G: z, X7 O
    27 D1 \: n9 M( C( m" G  d
    3
    3 @" E1 ]) N0 t* a9 A7 K4
    # C0 s1 K) T) @$ U* u5
    2 y7 l9 Z. b' y8 r6
    / n, y' s! [( Q4 w, R7
    ; h3 n' R! J( j. u8
    * D1 q6 x2 P$ t- R3 t99 k* p' \. U; d5 k6 c; h
    10* D* a2 z2 ^) y$ B& t2 d8 y0 c
    114 w$ Y# ?/ R, C9 h
    121 J; l$ i1 i6 R& {' u9 H/ H
    13
    8 W- v/ R: X8 k: K0 c! @  `4 x14
    0 _* N" l& o! u# s! E6 G9.1.2 类别的增加、删除和修改
    & e7 [8 Y" |2 ?7 u4 H  通过cat对象的categories属性能够完成对类别的查询,那么应该如何进行“增改查删”的其他三个操作呢?. a% o( w% D: ~% g

    2 Y/ e  q# U3 J1 z# G0 y【NOTE】类别不得直接修改
    9 [& j7 i, E5 h在第三章中曾提到,索引 Index 类型是无法用 index_obj[0] = item 来修改的,而 categories 被存储在 Index 中,因此 pandas 在 cat 属性上定义了若干方法来达到相同的目的。! u" g8 _, T% W* h7 A& a% c. x- S

    5 R* D2 [1 `0 i! f  tadd_categories:增加类别
    : m1 u9 n$ c& B& R/ J6 t3 Ms = s.cat.add_categories('Graduate') # 增加一个毕业生类别4 N8 z& h3 U+ f: m
    s.cat.categories
    % W8 u; w; t: Z' I& w
    2 o- ^8 V' i( c" I5 d  p+ H& HIndex(['Freshman', 'Junior', 'Senior', 'Sophomore', 'Graduate'], dtype='object')
    1 ^5 y& ?( G4 }0 d: @3 k, x17 b9 r5 Q1 N7 a' ?
    2
    7 ^$ _2 E. g: X$ z$ S2 k3  x8 ?* V: J; @
    4/ w! l) ^9 ?/ T' F# ?2 {
    remove_categories:删除类别。同时所有原来序列中的该类会被设置为缺失。: g3 l# ]8 L$ V" N! i+ r4 T* }- ]
    s = s.cat.remove_categories('Freshman')$ I) l  {1 `4 ~# }' u0 E* `
    / D- G' @8 S; N- v) f
    s.cat.categories. F/ `# ?1 Y. k* R7 t; ^% ~
    Out[13]: Index(['Junior', 'Senior', 'Sophomore', 'Graduate'], dtype='object')
    % p# k5 Q0 S. @6 h) u7 v) y( t3 \  S: j7 j. n) G+ b9 W3 t5 K
    s.head()# m, `% H, w+ _6 r: ?
    Out[14]:
    & q# r2 c& X8 v! m0          NaN# k2 z! L8 I1 B, M& m0 q& z/ ~$ F6 u3 ?
    1          NaN; t( G+ k, a# t" H- Q0 f
    2       Senior
    9 X1 m0 W& S9 U  g( I/ r3    Sophomore
    5 b' t6 I" C, _: q4    Sophomore
    ! T! D$ y$ m# r! BName: Grade, dtype: category5 q3 g7 v3 R: E4 s9 A" p% I0 N9 d
    Categories (4, object): ['Junior', 'Senior', 'Sophomore', 'Graduate']
    7 r* k  E2 T2 g# V* v1
      q, N0 T: x* [: U; F6 D0 G2
    / J$ P9 @! B8 `1 H* W3 t" F3: U6 `- h/ s! W1 f
    49 w, {/ S! M1 t# z* f4 C
    5
    ; |% Y$ `. C! \9 W6
    & y( v0 M% v+ R9 g' E( ]/ c7
    6 B7 R/ G/ k! {( g8
    . a3 T+ t2 t9 |+ P8 x; y  K9$ t8 |% a0 R) X3 v% E+ m" z
    10% L% n1 j6 A  b/ [$ M0 u
    11
    / P! u. {8 m5 r" _: j2 [2 C12
    ( |" K* O( v0 m4 [' W13% G2 D) U& }" Z$ ~
    14
    " p$ P5 n$ u0 t2 ]& O. Rset_categories:直接设置序列的新类别,原来的类别中如果存在元素不属于新类别,那么会被设置为缺失。相当于索引重设。  X) Y" Q$ w% K3 x+ T' Z: u
    s = s.cat.set_categories(['Sophomore','PhD']) # 新类别为大二学生和博士8 C5 D& |; l6 r
    s.cat.categories
    ! N1 [2 D0 i& b  H( _Out[16]: Index(['Sophomore', 'PhD'], dtype='object')' q/ T2 t2 x" o5 E  O  F0 K
    - `& u$ {9 b1 l# t8 Q1 y
    s.head()# T# _) C: N- P+ A6 |# A
    Out[17]: 0 o: T5 K) \7 m: i* {
    0          NaN
    . L) Z/ m5 P  R, D0 L+ G: R  H1          NaN
    , U9 Q# U$ H  ~4 Z( j0 W* E& E2          NaN
    / V: o  t0 \2 c# k3    Sophomore
    # C1 M" b  ^$ h3 P. o4    Sophomore7 k! k5 L; m# ]& l
    Name: Grade, dtype: category
    1 `2 G, D$ _; B6 ^Categories (2, object): ['Sophomore', 'PhD']8 e' a9 [% R$ @7 Z" d
    1
    + r% O' W: m+ O( X  `26 z, ?! `+ t% r* M
    3
    6 r6 \' i* |/ [6 \1 V/ b9 }2 [9 e. V4
    2 |  H3 i+ p( ^- Q5
    7 s* V- @* z% q6/ G+ D2 {( w( J
    71 D% _" S2 H' |* x. [4 Y' m9 [
    86 e, q  n8 _! T( Q
    9' C2 j1 H% r2 y
    10
    5 e+ x0 K: D6 s. J11
    - |4 g* {4 ?. R* W6 `% u6 ~# O% O% c124 X: i, j8 |7 z/ |8 s4 [
    13
    6 X/ S  E* z1 L$ |& o+ aremove_unused_categories:删除未出现在序列中的类别1 t( A. F2 ]' ^* [/ t0 q- e# b
    s = s.cat.remove_unused_categories() # 移除了未出现的博士生类别
    ; }! }+ ?& ?) bs.cat.categories1 T3 s( b7 ?0 ]  g# F! X! R

    / u' a! B! \) J! @% WIndex(['Sophomore'], dtype='object')
    % n( c2 M2 I8 {! B! f14 j' A7 U/ _+ o% e5 r; P, c6 y* P& _
    2& E8 _1 H7 B# K3 `8 W/ `
    3- a% |  u" M* n6 Q, k3 h" U
    4
    ) f/ D; [$ w/ q! b8 M" Zrename_categories:修改序列的类别。注意,这个方法会对原序列的对应值也进行相应修改。例如,现在把Sophomore改成中文的本科二年级学生:! I* I% z$ W! y* I# T
    s = s.cat.rename_categories({'Sophomore':'本科二年级学生'})
      k- G5 M- a" b# y' X9 h( Fs.head()& [* G0 E9 O' |* i

      S' ^: Q: P( s( M3 R; S. j0        NaN
    4 ?3 \  w& i3 x* J3 t1        NaN
    / M  p) n: D# O! w2        NaN1 j( w  s. ?, S* ?
    3    本科二年级学生
    7 }, f8 [% I) B0 m4    本科二年级学生4 V: G9 ~6 h, R3 T
    Name: Grade, dtype: category
    $ X0 L" X6 d, [9 I1 c/ P/ [5 pCategories (1, object): ['本科二年级学生']
    + }% R$ B- e! K, K1
    4 b) U+ I5 @* ~. ^$ ^0 B) P2
    : Z+ r, @  l( I1 e. w. O2 P30 {" ^% J( e/ U4 S% ]* K  d% v8 ~6 [: p
    4! x7 d0 f6 ?1 p
    5
    ! z0 W- E! [* g" I6
    ! C  P9 g  k+ F/ \' o7: ]) n/ d0 t: _8 o0 P9 E4 w
    8
    9 e0 w7 i0 B5 u9
    ; q( G4 K: r7 p: t10
    ( H1 x! R, s4 K9.2 有序分类
    6 I1 B$ S4 O  A# w9.2.1 序的建立; {3 o1 R! t, l9 b' y6 }6 Q
      有序类别和无序类别可以通过as_unordered和reorder_categories互相转化。reorder_categories传入的参数必须是由当前序列的无序类别构成的列表,不能够新增或减少原先的类别,且必须指定参数ordered=True,否则方法无效。例如,对年级高低进行相对大小的类别划分,然后再恢复无序状态:6 \& s# F1 z  _* [

    + w9 ?: b8 j. C, as = df.Grade.astype('category')# I( V& n# w  X8 n, I. e- ^# ~
    s = s.cat.reorder_categories(['Freshman', 'Sophomore',9 S; m$ {" ~, X; g, Y, ]
                                  'Junior', 'Senior'],ordered=True)
    ) \3 |1 P" Q; R' r& z1 |+ k/ es.head()
    : @8 ^' Y7 h: ^( u) ?/ uOut[24]:
    , C0 }( {  _: i8 H) r( o! b! X0     Freshman0 a3 K+ i9 q, l# J
    1     Freshman
    2 {' e$ J' q5 v4 s* h# J2       Senior
    . V* Z+ d. |& B- F. O& c3    Sophomore6 W) g, {7 \, ?' w  T/ u
    4    Sophomore  ~9 ~- z2 f. P- d  g
    Name: Grade, dtype: category
    ) R# n( P/ H# QCategories (4, object): ['Freshman' < 'Sophomore' < 'Junior' < 'Senior']0 @7 X8 M" J* T0 w1 R0 V2 s! w

    ' f6 ?: B8 u* M0 A- J. b$ R* vs.cat.as_unordered().head()* k- \8 d* q: G
    Out[25]: ; U' f# }0 a6 e# R! o! {
    0     Freshman
    ) P4 @' i% k2 ^, t2 G0 L; x6 w1     Freshman8 U2 U; P# W& w0 T
    2       Senior
    6 A( J/ y1 c4 }2 O3    Sophomore
    5 u2 C& m5 }! H4    Sophomore
    3 x9 p' _. d$ E  aName: Grade, dtype: category: @( w7 H# w6 H! ^; K. y1 h
    Categories (4, object): ['Freshman', 'Sophomore', 'Junior', 'Senior']9 Y  J" e) E( X8 |1 G
    + }; Z# r- J  z
    1
    5 G+ G, E% Q( N3 Z6 f2
    " ]5 h8 n7 s) C/ X" ~38 e* Y3 S* M/ w+ O) H
    49 t/ R1 |, b, S$ `) _
    51 H" _. F5 J* W) P
    64 Z) r# L8 _* u; U) A3 X
    7& I" c' F) y% C* }' y
    8
    0 z& G% v. q, f/ F' d5 [7 @9
    8 W3 ]' `0 j% `: Y/ @  |10- l2 Y4 O1 ^' D) B2 w! V  b: e
    11
    % Q: g# G9 K, a. n* f12
    ) S* V0 M2 h3 E) ~( m8 z13' ~7 z' H4 L9 g# Y1 \- g
    14
    & Y6 S& a+ Y( @150 M6 f4 P9 _' }6 `; Z' i
    16
    " w5 a" f! p( h3 k5 D+ M& ~  T17
    2 w% \+ r+ n1 O# b18. f+ E) m$ `2 P  ]+ F) {8 F
    19
      U8 k# u2 H( H- j; @20+ U9 A# p  C, v* T& L; r
    21
    7 M( K' C( g- [# a  \" N1 @3 a22
    ! j5 W  \3 S$ R! `3 ~' h  如果不想指定ordered=True参数,那么可以先用s.cat.as_ordered()转化为有序类别,再利用reorder_categories进行具体的相对大小调整。
    , v6 Z" n0 L6 L! c- z$ R( F3 M' g6 B3 |2 K" a& z  h1 A2 A9 `
    9.2.2 排序和比较
    # h( m% }7 T) l% Y; h在第二章中,曾提到了字符串和数值类型序列的排序。前者按照字母顺序排序,后者按照数值大小排序。
    ; n% \6 U" J3 q$ a! u  J3 j
    9 w# h- v8 j3 ~" U$ d7 u" S4 P$ I6 n  分类变量排序,只需把列的类型修改为category后,再赋予相应的大小关系,就能正常地使用sort_index和sort_values。例如,对年级进行排序:
    # P/ K$ B# y# D  i  c. \4 h8 y5 L
    ; D& h4 D0 W' cdf.Grade = df.Grade.astype('category'). @# F. A/ a1 W: T! b! D5 J
    df.Grade = df.Grade.cat.reorder_categories(['Freshman', 'Sophomore', 'Junior', 'Senior'],ordered=True)
    9 V  U; e! O! `9 udf.sort_values('Grade').head() # 值排序
    3 c0 b& u9 b) b, IOut[28]:
    6 H) v+ E, w- }: N+ I4 J& {9 R        Grade           Name  Gender  Height  Weight
    ' v: _+ Q  z3 ?" q1 h8 G* D2 o! W3 K0    Freshman   Gaopeng Yang  Female   158.9    46.0
    ) z6 Z" Q  c* R( r1 T105  Freshman      Qiang Shi  Female   164.5    52.0
    ) Q% k0 r# r7 O0 N96   Freshman  Changmei Feng  Female   163.8    56.0
    ( B4 q+ @/ E) J! Z/ {88   Freshman   Xiaopeng Han  Female   164.1    53.0* [$ J' ?% o& d. y( P# C+ Z
    81   Freshman    Yanli Zhang  Female   165.1    52.07 g: W# v" }, M  s! [; E  A

    ' r6 S( j* ]  b- e# W2 jdf.set_index('Grade').sort_index().head() # 索引排序
    # h5 ^: \( `4 G9 C6 t4 ?/ AOut[29]:
    . F; [1 U$ r  Z                   Name  Gender  Height  Weight, t/ l0 E9 C* B
    Grade                                          : @' ^/ }' \# y9 }  O4 O: {
    Freshman   Gaopeng Yang  Female   158.9    46.0/ T% p" M) |# b& B: {- M6 H6 j9 x- Z
    Freshman      Qiang Shi  Female   164.5    52.0( H) P& n1 a2 s( S5 n
    Freshman  Changmei Feng  Female   163.8    56.0+ P$ Q7 V2 a, D9 @0 Z
    Freshman   Xiaopeng Han  Female   164.1    53.0/ v9 O' U6 Y, c# T/ p0 |$ P7 \
    Freshman    Yanli Zhang  Female   165.1    52.0& u7 q$ O9 K- ~4 d, ^3 a
    ( S* b, c6 d, H
    1  G' X3 W. B8 L$ S' S% s
    2- \/ `7 d# I& q: h) {: T9 X
    3
    0 _/ P8 A" t) j42 w. _% \9 m9 O% h0 P8 f$ g! S
    5. S+ O& N7 Y; m; \
    61 f- N) z9 J. R+ [) U' Q+ ?5 N
    70 B, }. j& a* |
    8% Y3 R6 ^" h6 ~/ g9 k2 j
    9
    5 Y) u/ h$ U  ]# Z10
    " O! U; e0 X, i4 V7 I# e11
    & g' h$ g. J3 E% z% s2 U12/ ?# y5 L+ v* {
    139 J1 q5 r; ]9 z) d) t
    14' M# u* Z+ F- P. B! i) r& w
    15
    1 m* H# u( q; q9 y& ^3 W% X8 O* {16
    ! N$ u/ A$ V: a9 i5 J17! c# ]: C/ E* u
    18
    & |: C# ?. y) r19
    % S% N! N% ~: T20
    3 h+ M5 Q- r2 `0 [) Z0 }1 i  由于序的建立,因此就可以进行比较操作,方便后续索引操作。分类变量的比较操作分为两类:0 X+ J( J3 p) z( r$ O

    & i, ]( O$ l. G# x% B==或!=关系的比较,比较的对象可以是标量或者同长度的Series(或list)。(无序时也可以比较)! A! d3 N' F9 |# |
    >,>=,<,<=四类大小关系的比较,比较的对象和第一种类似,但是所有参与比较的元素必须属于原序列的categories,同时要和原序列具有相同的索引。
    ' x- O, ], f- l+ U* x% N( w: [res1 = df.Grade == 'Sophomore'
    * T5 _% T& x+ F  O6 I: [  A1 W
    # S+ A7 d/ }+ [) x4 ares1.head()6 F9 y) H: [: b5 A+ o
    Out[31]: 3 F) n9 q! B4 E! u+ M
    0    False
    $ p. H6 \" `# @& B. Q  [( {1    False' H: t  `$ Q0 B8 m7 ?" I! @. v0 j
    2    False
    5 P$ ]$ N; v. o3     True+ D6 @0 j: N* ]* T0 G+ X
    4     True
    0 [' T, u: }' \& y+ p) \Name: Grade, dtype: bool
    , l2 i3 v" x7 y6 v- ]$ o* R( K3 y: _/ N
    res2 = df.Grade == ['PhD']*df.shape[0]6 I/ m0 l$ g( D$ _/ P* E& C  A1 E- f
    0 I( u! n( e  d) K
    res2.head()
    4 r/ h* a; Y( C1 TOut[33]:
    5 x' R" E. h8 F  e0    False
    8 {) Z  u& a" H! j: d0 @1    False
    : p' f, b' C0 q* w9 z2 \2    False7 G6 k4 `9 t# X
    3    False3 H& P" }; B9 I) H
    4    False- H. t: m' B  @  ]! R  K
    Name: Grade, dtype: bool8 r3 b7 f# K8 V* |3 ]1 u  c; G
    ; l/ k3 ~3 ]1 p3 Y6 c
    res3 = df.Grade <= 'Sophomore'. V% n/ q& i8 M
    $ a! P, T. s0 ~5 O3 N! `2 W
    res3.head()+ {. p& h& O: w1 z9 {
    Out[35]: 0 M6 B1 d, V4 Y/ J8 r, T
    0     True" u; v  n- `+ ?( G  a
    1     True
    $ L/ l* N+ z. g$ }2    False
    # P* j3 N) g; `! A) Y) L* D8 {3     True
    4 N' k9 S6 a  A- Z; Z) O+ g5 _4     True
    % z* G# ^! R; b2 ^& \( c% m4 KName: Grade, dtype: bool
    5 I% P4 n" n. I/ K. S; \. e& @" S" d- u  A9 [% j
    # sample(frac=1)表示将序列随机打乱。打乱之后索引也是乱序的,直接比较会出错,必须重置索引。
    . \3 i( v6 R7 J/ t8 [0 v' _res4 = df.Grade <= df.Grade.sample(frac=1).reset_index(drop=True) 3 j- G7 f- {) d1 i
    0 X) A5 Z- {0 }1 G& x5 o
    res4.head()
    4 W+ ^* J6 I: zOut[37]: . ?- k: E* ^, j9 J- ^+ _
    0     True) a, G# h  J8 z4 E, ]& c% b
    1     True
    $ X0 x; A8 h- f) d3 e. N+ X/ t2    False7 Y4 R/ Q/ H) {! A+ Y8 c% d) L
    3     True
    % w0 ]& Y/ \1 I5 i8 {4     True4 {% _- B# O; o/ d# c9 S) P
    Name: Grade, dtype: bool8 H5 f% N5 G8 L- W) C' o

    # O" v* r8 H' p+ Z  E1 V( `11 ^$ \- [6 B0 m# s2 y
    28 y5 U3 }- h# }2 j! ]: n+ b) [
    3
    . O; a/ |" L: [% H9 C4
    ; }  |9 R" L$ N5- a& j4 ]5 {1 E, x# r
    6" N; D; N+ n1 g) Q' j4 F2 S
    7
    7 m5 O) p1 ~, g/ F9 y80 p0 w/ E$ G/ ]0 X9 a
    90 t+ p2 p0 E6 C" s' m9 [) P% k" }
    10" l  I, G* }6 p/ _: X8 O' O
    117 S, t% |  B0 Q1 X1 J1 h7 e
    125 X% n! v+ B% u4 U2 ?& |  C0 w3 r9 F  T( x3 g
    13: L! p9 S1 X- f) M6 z4 e  p( f
    14$ O, m$ K0 C( q7 Q
    15+ F5 w' X0 I; Z' Q* g
    16! o/ `% X9 ]& s+ P  n
    17
    " P% ?3 U2 C% O8 W9 p8 n18
    % g* S' y) F+ x( B- r4 ?) ~# O19
    # b0 u: z+ f* k0 z20' n" U6 H% e. V4 N
    21
    / k* n# u$ D( z+ m% t+ G22
    6 h' ]8 z8 f7 i0 j23
    ' E$ C1 F7 M) d" Y: k24
    3 d" e* F4 d6 |259 K" r  g) Z! @& j9 C
    26  l# B. {% [3 s0 V* n
    27
      K  \, I4 q; d( h28
    ( v. Y( ^' E; K29" ?0 `6 g. J) X7 w
    30* j4 U' x! F9 d  x& I
    31
    3 ]) f" G0 {, ^32& C+ K8 L. _) W0 T! o/ r
    33+ y; U# f; a9 w8 c% y0 Y
    347 a; z: z4 p1 q1 s) y
    35) i! X; Z+ K3 k* S4 |7 j! j
    36
    % p, ~: }0 e; G/ i6 w! k0 v, X37
    + e8 H/ B; p4 m% X385 [6 ]7 F: i* |& a- U& \1 x+ P, Y3 W
    39* s( W1 S1 @; R
    401 w5 N! Y! D. ]
    41
    6 W% D1 n( m$ V. B# Z42
    9 D. \4 z& z0 q8 ^43
    ( Q$ r, x* f  |3 x: E44
    % X* g  t0 m5 L1 N1 d. t9.3 区间类别. w0 U/ e$ P1 [! R" _
    9.3.1 利用cut和qcut进行区间构造" m5 A* [& a8 h2 H5 R
      区间是一种特殊的类别,在实际数据分析中,区间序列往往是通过cut和qcut方法进行构造的,这两个函数能够把原序列的数值特征进行装箱,即用区间位置来代替原来的具体数值。# C, y$ b2 F* x) g, u) c$ t# Y
    6 T4 h/ o) y' h
    cut函数常用参数有:, S( h0 Q' ~3 d! A: v' Y3 o
    bins:最重要的参数。
    - G9 K+ m. M. ^0 ]) b$ P3 I. B& s) ]; w如果传入整数n,则表示把整个传入数组按照最大和最小值等间距地分为n段。默认right=True,即区间是左开右闭,需要在调整时把最小值包含进去。(在pandas中的解决方案是在值最小的区间左端点再减去0.001*(max-min)。)0 F8 X% V8 d; l, C' a0 P% w
    也可以传入列表,表示按指定区间分割点分割。2 n8 H/ u  u  o
      如果对序列[1,2]划分为2个箱子时,第一个箱子的范围(0.999,1.5],第二个箱子的范围是(1.5,2]。
    2 P; `3 m  }; |, l  如果需要指定区间为左闭右开,需要把right参数设置为False,相应的区间调整方法是在值最大的区间右端点再加上0.001*(max-min)。5 j4 C% Q3 K! b4 x

    8 M3 N) V" v" d. N5 fs = pd.Series([1,2])
    " ?2 m, h. D: M# bin传入整数( J" S3 I+ @& \. b

    ; A) \& E# M6 f7 j, f$ P) Hpd.cut(s, bins=2)" Z5 T( [% r! G6 T/ m
    Out[39]: . Y  g8 ^% M6 S1 `0 j
    0    (0.999, 1.5]
    4 x  z; b& c/ p$ a/ b4 H1 x1      (1.5, 2.0]
    ; p0 I9 ~* ?) y/ s& U" f# ?dtype: category7 H- f3 U7 w3 L3 [' v! \5 x* y3 P
    Categories (2, interval[float64]): [(0.999, 1.5] < (1.5, 2.0]]" g1 J7 N! b6 H& ]3 A+ i

    3 a2 ?- n! v/ |4 b- L( q! M8 ppd.cut(s, bins=2, right=False)
    ' Y9 U# S1 T7 eOut[40]:
    4 c) ]% B; X- _' _" O0 T0 e+ m; m0      [1.0, 1.5)2 y+ ?, y) F2 B
    1    [1.5, 2.001)
    # Z) c. \! b+ I3 xdtype: category
    ' h* S( B8 H: t7 pCategories (2, interval[float64]): [[1.0, 1.5) < [1.5, 2.001)]
    3 ^% x1 e" l. N% @0 G! u3 |: p" {) A6 l2 C( O

    5 S1 X) r4 [( a+ q7 o# bin传入分割点列表(使用`np.infty`可以表示无穷大):  H& B9 U6 G5 F3 L4 m
    pd.cut(s, bins=[-np.infty, 1.2, 1.8, 2.2, np.infty])
    ! ^* Q. B* D, b9 {Out[41]: 3 O$ Y1 A" H  B; ]
    0    (-inf, 1.2]
    8 n2 k/ H( W, ]* S8 w1     (1.8, 2.2]5 Q4 J# F3 p8 k/ w
    dtype: category
    6 U6 b) k7 A7 p- x  ~) }. ?0 g2 _7 OCategories (4, interval[float64]): [(-inf, 1.2] < (1.2, 1.8] < (1.8, 2.2] < (2.2, inf]]$ P7 m2 S, a1 }0 }# t: K; ]; ?! \, K

    7 z+ t! c4 C- I( t5 X1
    " l6 f% \4 ]6 F4 S6 T+ v2
    # f' @4 V3 }" V3  w; w, X' j; O) x5 P: O6 c! K
    4
    $ Z! v# o( ]3 x5
    7 x6 {9 z6 z. ?8 i4 R  a( t% [6
    4 e$ R! `) C4 M, q# y# f7 R5 U* w7
    & v! s; N6 M, l8
    4 ]. Z3 l& R/ G, i2 V' ]( {; N* F9- |1 Z. M# Q3 T; [- Y4 a1 W
    10, P/ h) l+ M1 L, ]* T
    11
    & N+ L7 ~" q+ X2 `1 d12
    - a" ~; t5 w; t# D9 a) p" _13
    ; P+ r" F5 D6 H5 U149 ^/ A9 A3 v( U; A/ X8 P2 H
    15
    . w4 Q/ ]# V5 }5 {- m* \0 |! ]/ d16$ W, k+ i' q' D  N, p% q# G
    17
    ! I& P& l* \, g7 b' E5 P180 f! x7 s% H0 G# }
    19
    4 R! K) J+ t. L20' h7 r5 q0 o& F
    21& J3 _9 q! b# T& @6 T6 v9 B
    22& \  I# Q% J+ `1 k  X  r
    236 q9 W# |# N: T$ Y+ X4 x
    24! ?4 g4 ^% ?, _2 Y6 a9 e
    252 P* t. X5 Y+ M! P
    labels:区间的名字
    . ~0 |6 u$ H* {8 Yretbins:是否返回分割点(默认不返回)
    1 l* u' X7 g1 k* @% @1 R) T默认retbins=Flase时,返回每个元素所属区间的列表
    ( X9 ^# J! D, W( ^retbins=True时,返回的是元组,两个元素分别是元素所属区间和分割点。所属区间可再次用索引取值
    " c7 A* g) i1 n- f) o8 D9 \
    1 N% z- n3 \9 {3 g# C2 Qs = df.Weight" u) T* T" W4 M( g  j
    res = pd.cut(s, bins=3, labels=['small', 'mid','big'],retbins=True)0 P; D* S9 a5 D, t+ O7 q
    res[0][:2]
    # L; L% @$ m0 O6 ?0 H- O
    8 n! Z* R  s+ y- O8 VOut[44]:
    ) d; n1 f9 N/ r( y4 I0    small
    8 f( W3 q4 J3 t1      big
    ! Z$ T0 {3 E, u2 E5 Tdtype: category( Q1 ?9 |, Y; N# @
    Categories (2, object): ['small' < 'big']
    . x. I9 @7 ^4 v4 m" t! d0 R8 O4 I  I* t; o; b
    res[1] # 该元素为返回的分割点
    ; g0 O0 c8 _6 N4 _- eOut[45]: array([0.999, 1.5  , 2.   ])$ b! e4 R$ Y* i( i6 |* X
    1
    & [9 m/ B. W5 ?. H& ?+ i2
    ; n# r" H; d4 n( S3
    ; e7 n! A; P5 i- F" N2 ^: f; L0 Q  d5 w4
    ; u. g9 T4 m8 S/ R4 ~3 h- u+ V' b& Z5
    ; K: a3 Y! e& n6 d63 q0 ^! x( v1 g$ b, l$ i
    74 b1 |9 \: _4 G5 `( W  C
    8, n- @) ^8 K+ N! [
    9
    7 Q$ q" c9 B! I10. j3 T# P3 r3 [6 w: [
    11% }, A+ p) ?, c  U$ ~
    12  q2 g& V2 C; C
    qcut函数。其用法cut几乎没有差别,只是把bins参数变成q参数(quantile)。# G8 ]9 p6 P* m6 ~6 C# a
    q为整数n时,指按照n等分位数把数据分箱
    ( ?! y# V$ Y4 p' H( {q为浮点列表时,表示相应的分位数分割点。# ~# A7 z- S* s4 H8 c
    s = df.Weight. f$ N( ~* C  V# Z' Z" P
    ; P0 N  d: ]2 H6 _* D
    pd.qcut(s, q=3).head()
    2 ?3 B9 v3 s. u; SOut[47]:
    ! c; W: r6 X. j0    (33.999, 48.0]. v  a& X: Z: T2 \. R- T
    1      (55.0, 89.0]
    ; o" y$ C7 _8 I+ P2 w5 x' N* G" N2      (55.0, 89.0]& n2 M* O7 E$ u# \/ n- A8 W; ?. Q
    3    (33.999, 48.0]8 m3 d, j: m$ M& n6 b! f2 ^+ D
    4      (55.0, 89.0]
    # A% D4 v8 k" ?) h# j1 \6 JName: Weight, dtype: category
    # J1 \0 f/ ^' H* w( E: `Categories (3, interval[float64]): [(33.999, 48.0] < (48.0, 55.0] < (55.0, 89.0]]
    ! m2 i9 I* Z6 P9 \6 ~5 C1 u3 y3 P. H+ x0 n6 ]. e
    pd.qcut(s, q=[0,0.2,0.8,1]).head()
    " \/ h( ?2 k. y; m% \7 T! ^Out[48]:
    7 v5 L5 ]+ ?7 F! F. b4 l0      (44.0, 69.4]# T- h. ?, W. a; ^  U1 b
    1      (69.4, 89.0]6 A1 ]/ X6 |4 Y7 k
    2      (69.4, 89.0]$ l; O* h; Q5 I7 E
    3    (33.999, 44.0]# r5 F' _0 C& P7 Y2 M' ]1 n
    4      (69.4, 89.0]
    8 U% B/ g4 v( ?- sName: Weight, dtype: category1 D3 A" \) d' a8 U4 X
    Categories (3, interval[float64]): [(33.999, 44.0] < (44.0, 69.4] < (69.4, 89.0]]
    ( a# L$ H4 N/ [+ [9 v3 t
    " p2 O& B  Y- w1
    % W# e: l& i& j; C/ O2, o! E) f9 F6 F. N' }% B0 X
    3
    # L6 P: w8 f2 z0 N4. A2 r% ?7 _; r6 S
    5
    $ S+ N7 U1 f' a, h1 d* V  z; v6) p2 N& v  J5 Z; u! c( @1 I( t
    7
    ( C7 @1 i% U8 t. u# v8
    - H3 w! K1 g1 ^: r9 f5 j98 _; e8 ^+ |" i
    10
    , H; ]. V0 Z4 `% @, f11
    ' {+ _! a7 T/ H; g12
    : U+ q: w9 ^0 |6 O- H& [& N) b. [130 _  X  S+ m$ |$ V% v
    14
    6 ~* G+ C& o: o+ q- R, J. w15
    9 k' W9 ^. F) e  Y" q' k) Z16
    6 U# }6 k4 E+ k" |) \17; T$ U8 @, p! e1 X# k
    18
    8 g! r& {* `6 j/ q19
    7 \6 D7 J" T- _  A20
    " y* _, _/ `# x" B2 e9 s( E21
    - B; f  D8 `- o3 P3 [, Z/ n# _9.3.2 一般区间的构造
    4 c" n, j3 P" h. W% ~& A  pandas的单个区间用Interval表示,对于某一个具体的区间而言,其具备三个要素,即左端点、右端点和端点的开闭状态。
      ^; s! g( s. w3 y  t6 m% Z' e
    # @& [$ M, d1 D& r8 G  ^开闭状态:包含四种,即right(左开右闭), left(左闭右开), both(两边都闭), neither(两边都开)。
    2 g  \( f+ m7 m% A1 F/ u& smy_interval = pd.Interval(0, 1, 'right')9 O9 e5 y, v: \, u7 H
    ! |& a$ W- a. m+ U+ U; w" z2 q
    my_interval
    : S0 \% P& m3 M0 [6 @! v" ~! u7 uOut[50]: Interval(0, 1, closed='right')5 |3 h, q. X2 y( m7 _
    1, J5 ^9 v8 Y& D8 x! k' ]' {4 s
    2
    , q' M( l5 |8 `5 \6 C! @: f. ?7 G36 U4 z" F; y% @# B7 G
    4! L; b8 R+ @; f& \& L2 `
    区间属性:包含left,mid,right,length,closed,,分别表示左中右端点、长度和开闭状态。
    # b. M% `# {' F) o7 @+ I使用in可以判断元素是否属于区间
    & x) e+ x, H4 g4 p/ i用overlaps可以判断两个区间是否有交集:/ W6 N& z4 r! ]! d, n$ d: c
    0.5 in my_interval
    & f4 K" O) @& I6 @  W/ [; y7 l% X+ T$ p. _% @' _
    True
    ' p/ F% j$ m$ C" F1% j. P- N( O+ b4 e
    2; ~  h: N- Y  C& _6 E+ K
    3- M/ G1 B# p! [' _6 o! Z
    my_interval_2 = pd.Interval(0.5, 1.5, 'left')
    1 _, ]( ^; |+ M/ d* Hmy_interval.overlaps(my_interval_2)
    - `1 o0 C* v4 E& l, r  q
    ' _/ n1 [; D, b0 YTrue
    * |+ E9 d: j/ A8 z9 ?. t1
    ! e9 m+ Y) A* ?29 }  `7 o- @1 L6 ~3 }4 K" J& P
    3. S$ |9 Y+ [$ p( H/ q& P- X
    4# ~0 p5 d5 Q% w
      pd.IntervalIndex对象有四类方法生成,分别是from_breaks, from_arrays, from_tuples, interval_range,它们分别应用于不同的情况:
    % d, Q, m9 ^. Y
    4 F& W/ _5 d5 J( _( G3 G& Bfrom_breaks:类似于cut或qcut函数,只不过后两个是通过计算得到的分割点,而前者是直接传入自定义的分割点:9 x; J2 n( `, ^* ^
    pd.IntervalIndex.from_breaks([1,3,6,10], closed='both')$ p0 _. A' |4 {7 C( D% Y/ j% H
    ! }/ u: g7 X7 c0 G1 o" n9 t- j, x8 ^
    IntervalIndex([[1, 3], [3, 6], [6, 10]],
    : _, g) {+ d1 n3 F/ _               closed='both',. A  T& H/ f- w7 Z2 Z
                   dtype='interval[int64]')2 a" v6 z8 ]* ~2 l. s: m% B/ m/ z
    1
    % k7 E+ [/ |3 K; P7 m2
    $ {' e, X- s" t( z7 C38 r, N/ K7 C) v7 A0 m. A! \7 l
    4
    4 ^% m* u; |" M50 y: c4 ^; Z* a
    from_arrays:分别传入左端点和右端点的列表,适用于有交集并且知道起点和终点的情况:
    9 S, ]( f8 ]6 z9 i5 ppd.IntervalIndex.from_arrays(left = [1,3,6,10], right = [5,4,9,11], closed = 'neither')* w1 b$ h: j! G5 P
    2 N6 _" m% J6 H8 A3 G
    IntervalIndex([(1, 5), (3, 4), (6, 9), (10, 11)],
    1 z6 ^' L; ~' b                  closed='neither',! R" l& n& W9 H- @8 k8 h8 G, U7 Z
                      dtype='interval[int64]')( A9 _1 K# l  I: A7 E/ K1 k
    1
    - `4 i) b. H; d% f/ t' M  O28 c) y( L# I, r6 M
    3  A" W/ Z; k$ E6 w! E
    4# a, s1 X4 T) z/ ]6 f" `1 Y+ G
    5
      l* o5 `! y! s4 W/ i6 p; \6 `from_tuples:传入起点和终点元组构成的列表:- f0 J* o1 O, j, K3 Y: m6 m
    pd.IntervalIndex.from_tuples([(1,5),(3,4),(6,9),(10,11)], closed='neither')
    . t1 }  K# j& |- m: K% `3 y% K# N* K& S& K! h9 a
    IntervalIndex([(1, 5), (3, 4), (6, 9), (10, 11)],& E1 _/ T0 h3 B4 z
                  closed='neither',' F4 W  Q& |/ @/ E% H
                  dtype='interval[int64]')7 b1 z' o. W- M1 k
    1
    4 h$ z. u4 G3 R; L2* _3 ?. z* O' S9 S/ h) [. L
    3" |4 m2 q4 W6 V0 B2 V0 ]
    4: r: W  y3 u9 E
    5
    4 l0 J: r, _+ b4 Pinterval_range:生成等差区间。其参数有四个:start, end, periods, freq。分别表示等差区间的起点、终点、区间个数和区间长度。其中三个量确定的情况下,剩下一个量就确定了,从而就能构造出相应的区间:, p: u- T4 e3 c
    pd.interval_range(start=1,end=5,periods=8) # 启起点终点和区间个数; H" S2 Z6 k5 M# q: o, m
    Out[57]: " u% R0 p% k2 P" E& T* l( X2 j
    IntervalIndex([(1.0, 1.5], (1.5, 2.0], (2.0, 2.5], (2.5, 3.0], (3.0, 3.5], (3.5, 4.0], (4.0, 4.5], (4.5, 5.0]],* Q. o; A2 x) _3 _2 `6 u
                  closed='right',- |% W; _9 n2 q/ O( ^& `
                  dtype='interval[float64]')4 G! k2 d; T+ F8 l: _/ o9 v  y7 m
      g1 p  x& h1 M5 W6 i3 u5 p
    pd.interval_range(end=5,periods=8,freq=0.5) # 启起点终点和区间长度2 ~/ C+ @' q* b, m0 h  ^4 c' @
    Out[58]:
    $ M+ I4 [/ u. K  xIntervalIndex([(1.0, 1.5], (1.5, 2.0], (2.0, 2.5], (2.5, 3.0], (3.0, 3.5], (3.5, 4.0], (4.0, 4.5], (4.5, 5.0]],1 i# q: k) V0 [# x- A" p8 v
                  closed='right',, [. `, [3 w. f, q
                  dtype='interval[float64]')
    0 q! w5 ?, P  C; ~1
    - ]  ^( `; n# P' q) n2 k) X  i8 B24 o  O6 b" k, M! b, l/ [6 U
    3
    # h' R1 z  t( K0 G& z- B0 S. I! ^- U, C$ ~4
    6 l. L7 B' m$ Z. d2 n5# {  Q( ~# i( [% l& X
    6# J) p+ }2 q* S" D+ X9 v& A+ Q
    7
    . ?5 w+ y8 A, {8; K  P8 E7 L) ~  c" [! C7 D/ ?5 i8 i
    98 P, t" g9 t/ U! M
    10) I" m/ i5 x7 K7 n
    11
    : |+ m# H) ]' i4 W; S【练一练】
    : b5 b: K1 U  k6 o  无论是interval_range还是下一章时间序列中的date_range都是给定了等差序列中四要素中的三个,从而确定整个序列。请回顾等差数列中的首项、末项、项数和公差的联系,写出interval_range中四个参数之间的恒等关系。, O6 H  V6 T, P. v; L

    , ]: |- p. H- m; f  除此之外,如果直接使用pd.IntervalIndex([...], closed=...),把Interval类型的列表组成传入其中转为区间索引,那么所有的区间会被强制转为指定的closed类型,因为pd.IntervalIndex只允许存放同一种开闭区间的Interval对象。: d( N$ L6 e+ K3 f3 R) H  D8 h
    9 ^. N! E3 `" n8 l  V$ z
    my_interval4 L# a1 h+ z- c' E% ?, H# k
    Out[59]: Interval(0, 1, closed='right')
    - f% U( \* R; D' [9 J( b/ C  I# e: F2 N4 W2 E0 _! }- L
    my_interval_28 m- r1 I, a* \7 u# p
    Out[60]: Interval(0.5, 1.5, closed='left')  a5 Q. ]+ \& R$ [0 ^7 \
    ; x; B$ E7 m# @
    pd.IntervalIndex([my_interval, my_interval_2], closed='left')
    ) o$ }( e5 L3 L( @7 {& c9 zOut[61]: " A& W" d0 A- X5 e# S; C3 a
    IntervalIndex([[0.0, 1.0), [0.5, 1.5)],8 u8 y* J' X1 d. e: u
                  closed='left',
    0 x8 y4 u) J  ?- c. `$ V              dtype='interval[float64]')# x0 t* {& j, Y
    1* I* A9 m5 r) l+ P
    2' u! Z4 ^+ F$ K# `4 g6 n
    3
    / {/ O+ D3 H5 s; L% j0 g  }4: t$ }+ l9 x& X" G5 k, r- _
    5
    1 o" d' U% }  N; z+ O  A. I/ j6
    5 i( T6 n- Q! d1 \7* R# |! `. I4 K" s$ i' s
    8) f, B% `0 @8 Q& a+ R  p; J
    9/ s* ]- \9 L6 M! A* {; P& g: q
    108 J3 Z6 {4 b4 N0 y& Q
    11
    - K% A/ j. |8 i7 T0 f9.3.3 区间的属性与方法
    4 M5 p0 X& Z1 |1 g# K" ~  IntervalIndex上也定义了一些有用的属性和方法。同时,如果想要具体利用cut或者qcut的结果进行分析,那么需要先将其转为该种索引类型:
    6 m! k% i9 a, _9 h
    ) B: a: g8 I  ]' _7 J( T0 W* v9 Hs=df.Weight
    4 m3 l. ~, G# r# I7 z) Vid_interval = pd.IntervalIndex(pd.cut(s, 3)) # 返回的是每个元素所属区间,用具体数值(x,y]表示! k7 u& L# y4 \8 `6 h% `0 J
    id_interval[:3]  Q& n- r. e6 n

    0 I! O5 N8 A) Y( |5 DIntervalIndex([(33.945, 52.333], (52.333, 70.667], (70.667, 89.0]],
    * |. a5 v9 ?0 x" i: b- y                 closed='right',
    - ~! J+ F# [4 a                 name='Weight',/ g. p* U# v" b
                     dtype='interval[float64]')' W# Y, V9 ~, H' M! h. z
    17 s5 C& Z1 m/ S6 H* U
    2
    ; L" M7 X- Z% y3
    6 M+ P8 q4 `; a0 {5 W0 @8 n1 |; @4  B: g& s/ i" L& f; Q% \7 A- t
    5
    ! t+ I) H; P/ Y/ q& [3 U6# f, A# L9 J6 E* T+ T) o$ D
    7
    1 ~7 x3 x: M2 _2 r( x* x& P6 z8) }# P% j# U) n+ L
    与单个Interval类型相似,IntervalIndex有若干常用属性:left, right, mid, length,分别表示左右端点、两 点均值和区间长度。: X' H9 n5 B7 \; j: a" q
    id_demo = id_interval[:5] # 选出前5个展示0 [7 o6 n- D# M, o
    0 a4 Q3 u0 T8 @: q4 y# _0 f
    id_demo/ X! \2 k5 o7 A8 p! V# U
    Out[64]: + y, M, p: w- X* h% S0 Q
    IntervalIndex([(33.945, 52.333], (52.333, 70.667], (70.667, 89.0], (33.945, 52.333], (70.667, 89.0]],3 v, D6 Q/ a" E
                  closed='right',
    3 M2 S) m. ]( s              name='Weight',+ G) L% e" x/ y! K- |; P
                  dtype='interval[float64]')
    - A9 u, `; y# P% l" h
    7 `' Z% ~! [0 l5 h* ^& A) J, Qid_demo.left # 获取这五个区间的左端点3 @4 `/ D9 M' k# c1 a
    Out[65]: Float64Index([33.945, 52.333, 70.667, 33.945, 70.667], dtype='float64')
    6 q8 G9 k6 g$ w4 ]% _6 |! X  h1 a" E- C, x3 f0 i# {$ b1 G# O
    id_demo.right # 获取这五个区间的右端点7 v" B) d$ p+ R. U5 T" \
    Out[66]: Float64Index([52.333, 70.667, 89.0, 52.333, 89.0], dtype='float64')
    & l; {& q: z% L' n4 T: }; z  G% p9 f( q! @  Z( q) u0 s4 G
    id_demo.mid
    : |$ B6 j4 N( G" {( I. j! wOut[67]: Float64Index([43.138999999999996, 61.5, 79.8335, 43.138999999999996, 79.8335], dtype='float64')( M- i/ _, g/ C' Z" @" E0 a. l

    * Q: o5 w3 q! lid_demo.length
    1 W$ _, W/ P; F6 n2 m3 nOut[68]: ' Y" f+ t; G. w/ q# m  Y% e: l+ i% M
    Float64Index([18.387999999999998, 18.334000000000003, 18.333,
    ) Y% \: q3 F- f              18.387999999999998, 18.333],8 w( t3 b7 G7 P8 H8 @: {
                 dtype='float64')
    4 ~2 V/ ~/ t/ `# S5 G
    , K5 O3 H( u5 F. q1& e2 }8 d* r1 O# c& o
    2
    ( D* e4 C! y& B: }; s$ T7 K30 X8 B5 Y  ?% P0 f4 H
    44 c; J% e( ~/ k% a
    5
    9 E) L5 \4 b" D" W6
    8 _2 ]+ E6 Z& f# a: p7
    & Q- P+ [" C/ d% |9 L' F8 i8
    % m( v- J) C, H6 l1 G9 R: g9' j- Z! N- ?( ?. y" d
    105 N7 Z5 ~2 \2 [9 s
    113 k2 L9 I8 ~0 s9 e9 ^5 r
    12
    0 `: _+ x1 B' w! {* ^2 X; \( S4 ?13
    ' i( B$ V( ]/ h14' n# z& E5 e2 Q, n. d& x9 x$ q
    15
    2 I" q3 ]! x+ d16' g; x/ _1 w0 l" P5 U! `
    17
    ; z9 y9 ?; Q% y: ~185 m' A1 ^- Y3 E
    19/ d8 b) j6 G, }1 e
    20
    . E: |9 @8 R5 [! W# r& [) H' H210 B5 D' u" N2 |3 ]. O1 A
    22  S, R; |! |' n
    23  C6 o" ?2 l$ b9 ~% A* Y
    IntervalIndex还有两个常用方法:/ S( R, }; G! A4 U+ U. a' Y
    contains:逐个判断每个区间是否包含某元素6 A6 l! I% B) D1 d+ ~" u
    overlaps:是否和一个pd.Interval对象有交集。
    0 u' _- @/ @  j* `4 L5 yid_demo.contains(50)
    1 b' f' E: {; T* }, fOut[69]: array([ True, False, False,  True, False])- E1 \1 Y( B7 Y$ y7 [

      z8 h2 S6 V& Z5 `id_demo.overlaps(pd.Interval(40,60))% [* U* V* d; ~& U3 N
    Out[70]: array([ True,  True, False,  True, False])6 |3 A) F7 I" m
    1: a, u0 ^' `/ `! e0 o
    2- z1 z7 i2 z) L. x8 M$ O2 u; s; |4 k
    3% {5 M/ x/ y5 K# K4 D  y- r* Q
    4  u/ {9 o1 ^2 n/ k. l: B* f8 B. D$ G
    5
    ; a/ l% p% i5 _: m+ w  Y9.4 练习
    + v. \, g. H5 i7 P  X6 ]Ex1: 统计未出现的类别
    3 @8 Q2 y% ^1 `. r# F9 v  在第五章中介绍了crosstab函数,在默认参数下它能够对两个列的组合出现的频数进行统计汇总:- v. k" d& w+ d1 h: [0 U( E: N

    ) H* L: m1 O1 \* rdf = pd.DataFrame({'A':['a','b','c','a'], 'B':['cat','cat','dog','cat']})
      Z* `. S! v) _, C# T& r: b2 v1 Epd.crosstab(df.A, df.B)' q6 F) z4 l7 e

    ; r+ H$ t: M6 D1 {Out[72]:
    ( j0 J7 k& q% k7 x9 \1 iB  cat  dog
    % _0 n) w( C( O& f" ]. ]  lA          - @5 d# i: Z5 r
    a    2    0
    ' S; S7 t1 {& D3 Cb    1    04 w' _9 C* c/ T' N7 B- O
    c    0    1
    ( M+ T( |) v' }( G1
    ) u6 @2 E3 S4 N( Z. [6 Q; |% l2- b+ w% i% C6 v4 a
    3
    " k: M8 n5 S" U4
    8 g- K/ S8 }4 r5
    ! d. z2 Y6 J1 F' K7 u+ i3 e0 q6. {% U3 m5 P6 ~
    7
    8 n- r& g% N; Q& R7 X1 v8
    9 @- m2 n8 {5 A6 R99 p, w' ^9 [4 x, L- C; h: e! Z) e
      但事实上有些列存储的是分类变量,列中并不一定包含所有的类别,此时如果想要对这些未出现的类别在crosstab结果中也进行汇总,则可以指定dropna参数为False:
    - @0 e/ A0 H: Q0 g# k, L0 }  q& `
    * w6 C) o% Z% Odf.B = df.B.astype('category').cat.add_categories('sheep')
    2 g8 M2 d5 \/ ]" Gpd.crosstab(df.A, df.B, dropna=False)
    ! p  n& v1 e+ A" p$ w' @4 B1 R7 Z( [0 e) L
    Out[74]:
    : G, }% c, w+ s+ L- cB  cat  dog  sheep4 X/ O* R' ^$ E9 s! }8 v
    A                 & [4 P3 A& W' ^( l
    a    2    0      0; L; a+ l( W7 ?
    b    1    0      0
    - g( l$ I" i" S5 p8 s. u# @! L+ hc    0    1      0
    " }* @* q' y. v1
    7 G7 o. R3 _4 v; W2
    ' k7 i6 t9 K$ H# k4 Q34 u4 V' k- t8 i  J2 h
    4
    ! a( ]) M% |8 K2 t5
    ' h+ e9 c" e  s9 l68 T% ?+ s) F  g# r4 Q: O1 y& r
    7% [$ b) A- H: ^* d) U2 V
    8( e. R5 t! T7 J8 Q9 A
    9. N, Y/ F4 S3 `. v, B  C5 Q; L! W
    请实现一个带有dropna参数的my_crosstab函数来完成上面的功能。
    ) U! R7 x& O' }- g& n9 y$ B# Y. ]* P/ t! |- I. T9 A
    Ex2: 钻石数据集7 ^& D/ O( s" d' {: ^
      现有一份关于钻石的数据集,其中carat, cut, clarity, price分别表示克拉重量、切割质量、纯净度和价格,样例如下:
    2 {2 ~: R  u! [9 v/ M3 v3 W& R- E# o; ~& _; c. Z2 W2 B
    df = pd.read_csv('../data/diamonds.csv') " y, s% L8 l/ s, C2 q
    df.head(3)( t7 |% H4 W" K# N

    / S' T& ?$ O6 O, d# SOut[76]: 0 j  \1 m7 x$ g6 \8 s( o& c3 \
       carat      cut    clarity  price9 W/ B' M& M' A  \- g7 C
    0   0.23     Ideal     SI2     326
    * }- [* M+ W4 f) U1   0.21    Premium    SI1     326
    " M" w" [1 X& b$ W& R( Q0 [1 g" i2   0.23     Good      VS1     327- u* _+ }! O( x
    1# l' t- Z2 U/ d% g
    2
    / i5 {6 C3 t- R3 l4 y35 m, N2 a# @/ V8 K/ q
    4- O  E- p9 z4 z* ?3 M
    5
    + N' ?+ }" X, ^9 e6
    9 @6 ~3 Z2 n) C( y& B0 V+ s7
    1 P$ W7 w5 A- f85 `- [" O9 F" {( d) G( x
    分别对df.cut在object类型和category类型下使用nunique函数,并比较它们的性能。
    ) O5 H5 U' q& n# K* P7 T8 U+ E* @( \钻石的切割质量可以分为五个等级,由次到好分别是Fair, Good, Very Good, Premium, Ideal,纯净度有八个等级,由次到好分别是I1, SI2, SI1, VS2, VS1, VVS2, VVS1, IF,请对切割质量按照由好到次的顺序排序,相同切割质量的钻石,按照纯净度进行由次到好的排序。" v) [6 V- H8 b& R0 O" y$ O% X8 |
    分别采用两种不同的方法,把cut, clarity这两列按照由好到次的顺序,映射到从0到n-1的整数,其中n表示类别的个数。# q% F$ j' T6 s
    对每克拉的价格分别按照分位数(q=[0.2, 0.4, 0.6, 0.8])与[1000, 3500, 5500, 18000]割点进行分箱得到五个类别Very Low, Low, Mid, High, Very High,并把按这两种分箱方法得到的category序列依次添加到原表中。1 ^6 K, I+ ?+ T2 F- f2 c% R( P' z& j# w
    第4问中按照整数分箱得到的序列中,是否出现了所有的类别?如果存在没有出现的类别请把该类别删除。% Z4 a# S" n6 Q: |; ?0 V- _: j
    对第4问中按照分位数分箱得到的序列,求每个样本对应所在区间的左右端点值和长度。2 N! t! U& ]# }6 \* F! S. S. {$ f
    先看看数据结构:/ {9 t( }1 n2 k: A6 N9 ^" a: A
    ) d6 u3 T9 W8 u
    df.info()
    ) W  W# K' `4 j9 n; `! oData columns (total 4 columns):
      E+ y0 \6 I4 E5 `+ `9 R' Q #   Column   Non-Null Count  Dtype  
    8 G" d3 P( f' `' t2 r5 y5 Y---  ------   --------------  -----  
    - O3 e6 ^  ~" h: Q! t  n 0   carat    53940 non-null  float64
    ' @  X' H0 r: w* S 1   cut      53940 non-null  object
    3 P, p) W( M0 d" H( x 2   clarity  53940 non-null  object
    4 O/ q7 h& R4 c1 l; f- P+ U 3   price    53940 non-null  int64  
    ) r5 |6 p! f* t6 j. J  W' v& Y# C& Y- qdtypes: float64(1), int64(1), object(2)1 K' r$ Y& k2 G9 |
    1
    " F! k( v8 J, x' Y5 p5 n* a2
    . b, p  N: A1 C& j0 \* u3
    9 v, Z8 B' f& q4
    $ Q' [% ^* h" t4 y. C+ T. h! j5$ A* s" V% j$ \
    6" m+ K) X* _* \  G/ d
    7
    2 h. ?8 o& o( H- y8
    . z$ b1 y+ O- f9
    8 q4 s, I: T7 g0 x; {' Q! a: t( R! i比较两种操作的性能: A  Q( r; V4 k6 B0 Z/ u/ d
    %time df.cut.unique()
      y; z+ J5 L5 r8 E" m; {/ A/ A0 _, Z
    Wall time: 5.98 ms
    2 d6 q; w+ P* _, i* g  xarray(['Ideal', 'Premium', 'Good', 'Very Good', 'Fair'], dtype=object)
    9 e" H' L4 z) W1 |+ d& y/ Y1% u( R2 Z8 y7 n+ y( q
    27 H; O' b% Z" z% \# z9 L2 F- r2 B+ t
    3& D3 _. c4 V/ K# ]( l9 {, O
    43 k) p$ |& s. k9 L+ p6 I4 d: f
    %time df.cut.astype('category').unique()  \# j& g5 h% {

    " q. l( s) ^0 o4 X0 X9 D: |Wall time: 8.01 ms  # 转换类型加统计类别,一共8ms; ?0 }8 R0 g6 J: B( d
    ['Ideal', 'Premium', 'Good', 'Very Good', 'Fair']
    % I# }( e2 H2 r& F$ q" SCategories (5, object): ['Ideal', 'Premium', 'Good', 'Very Good', 'Fair']
    ; K4 @+ M. V4 e  I1
    , W3 \9 r- Q0 e( r20 b& G5 J* w7 }( X
    3
    ; S% h& E0 F( U9 m; \45 ?0 n1 K$ ~( E' Z$ q9 l$ f. W
    5
    ! _# P; q" Q6 i3 ?df.cut=df.cut.astype('category')) {$ R4 l+ Z: f0 S9 D) E6 t
    %time df.cut.unique() # 类别属性统计,2ms
    % L3 U* X1 D1 L( F* `: ]7 s9 `/ V1 z- a6 y5 e
    Wall time: 2 ms
    # E# M  `8 I, X; A, X['Ideal', 'Premium', 'Good', 'Very Good', 'Fair']$ b( k8 F: M$ K( \
    Categories (5, object): ['Ideal', 'Premium', 'Good', 'Very Good', 'Fair']
    $ n3 l# x% F6 \! V( N' @! ]19 }  E+ w8 ^* w  C3 m
    20 h- j; C. x8 b
    3! y) t) b/ u! ?& t4 U: U2 F- T$ X
    4$ C1 n0 B6 S5 R
    5
    # G/ K8 @: g* ^% ^6' Q$ h* c* l7 l& T( ?" x% Y, h
    对切割质量按照由好到次的顺序排序,相同切割质量的钻石,按照纯净度进行由次到好的排序。& C$ l7 l5 [% D6 y) q: L8 A' n
    ls_cut=['Fair', 'Good', 'Very Good', 'Premium', 'Ideal']
    # |: ^/ g) J+ Z" jls_clarity=['I1','SI2', 'SI1', 'VS2', 'VS1', 'VVS2', 'VVS1', 'IF']
    # W1 n; u/ B6 @df.cut=df.cut.astype('category').cat.reorder_categories(ls_cut,ordered=True)  # 转换后还是得进行替换# ^' M+ k: ~8 K
    df.clarity=df.clarity.astype('category').cat.reorder_categories(ls_clarity,ordered=True)" K5 ^. t; R! }( z2 S5 [2 E

    ' I7 }  @. ]9 xdf.sort_values(['cut','clarity'],ascending=[False,True]).head(3)
    " H1 }2 m4 m$ G# [1 A6 e. V: n( p1 \/ X$ }/ |
            carat         cut        clarity        price1 L/ {* ]! {- }2 p, W
    315        0.96        Ideal          I1        2801
    3 n9 C/ r$ S$ M% c3 I' [9 }7 s535        0.96        Ideal          I1        2826. z& q) Q; w+ r) [. T8 ?% {6 f
    551        0.97        Ideal          I1        2830
      g6 @" A# Y4 M4 }! G13 @8 v# r) E" ~. D: {/ m
    2  m/ D; U: ?( K5 ]" L' L
    3
    1 }9 s/ O5 G( h( W9 o* W- h! G6 w4% c1 N$ f4 y8 m6 ?8 \, F& e
    5
      x. ~. w7 Q, j+ M% r; g9 i" o6
    5 O5 M6 h+ h3 z- [* t( F% T/ T* ~8 j7
    ; \- N+ o% Y, p3 x3 k+ h8- N5 r% i  d1 ?7 l) _% ?' u9 o
    9
    ( _- f+ J5 z! {# Y* \8 Q5 C% `10
    7 F) h7 s) E; S+ |' r" ]& E110 _+ Z+ g( y. y% J8 x+ L; v
    分别采用两种不同的方法,把 cut, clarity 这两列按照 由好到次 的顺序,映射到从0到n-1的整数,其中n表示类别的个数。# T) d0 g2 G% n
    # 第一种是将类别重命名为整数
    4 V$ T3 P) H, m* `; I: i& |dict1=dict(zip(ls_cut,[x for x in range (4,-1,-1)]))& p2 E, N8 {4 O8 P, O
    dict2=dict(zip(ls_clarity,[x for x in range (7,-1,-1)]))) K* c$ H9 n2 N& y0 d" @
    0 h7 l/ N! @- c4 Y* k8 E
    df.cut=df.cut.cat.rename_categories(dict1), K" {; y6 z( N$ l9 q" m7 o4 Y
    df.clarity=df.clarity.cat.rename_categories(dict2)
    0 W$ P) p. c; t+ r2 _0 i  x  idf.head(3)6 ?* o; {: {# J
    2 j8 w6 x  q1 N; ]
            carat        cut        clarity        price
    5 C: x1 ]4 t9 Y, i$ Q0        0.23        0          6                326
    + |. B1 g* W; ^0 q) y, ?  `1        0.21        1          5                326& ?) z4 O' C$ s, a9 Q3 @) q
    2        0.23        3          3                327" [( Q1 _% O: }, q+ m' y: _
    1
    & x6 w5 W6 A7 r* ~# ~2: K8 g. ^- b& s# N3 j& h
    38 j) L! V  p+ Y( e) O8 d
    4
    8 ^1 k; m# x, s0 {8 b+ \9 V  p5
      p/ Q& x) O1 [. d' i0 W67 v3 |( c7 |. ~9 a1 I5 R+ @
    7, {: ]0 `3 q$ u
    87 Y. g- ^' Y" w
    9
    8 K5 f* x  O+ E1 J$ q4 k, Q10
    & a+ c6 {/ D! l# F11
    & X/ E0 r1 R9 I  S3 L6 V, d12/ }/ }' \% r9 Q; U, A: y
    # 第二种应该是报错object属性,然后直接进行替换: g8 N* d. B8 [
    df = pd.read_csv('data/diamonds.csv')
    " _* M! d' G& g# e5 Lfor i,j in enumerate(ls_cut[::-1]):$ D2 t6 Z; V6 f3 {3 ~5 w- L
        df.loc[df.cut==j,'cut']=i . B# z" A4 s9 `2 [2 e
    6 I  \) z/ Y0 R2 F- C
    for k,l in enumerate(ls_clarity[::-1]):
    5 o6 f$ ^% P$ W6 I0 g7 W    df.loc[df.clarity==l,'clarity']=k# t  Z1 U+ c5 n# [% ~, Q' a: Z, U
    df.head(3)+ p+ T/ \0 ~/ x) E! Z* k

    ! R# m8 T0 u8 ?, w* v# B        carat        cut        clarity        price
    * x% Z7 ~6 V' |& f$ D, w% m0        0.23        0          6                3260 H  e  I# K, J* @% |' k- X) M
    1        0.21        1          5                326
    1 v8 \6 ]7 s7 D1 J! C: S: d2        0.23        3          3                327
    8 v) ]: d* m$ w: W1/ Q( ?3 ?/ e: _- C
    2) n  k/ U* S& f: y
    3
    + V# [! \' R, T0 l& M5 r% R4: F- b( Q3 U7 o+ a) U' E
    5' f+ t" W! T9 X& g  y) B: c
    6
    3 u3 {1 K" u" M  T- S$ b7
    - c- ^- r6 y$ L* ~, y: O- m8 ], T. a2 m8
    - S# Z+ z4 D  F# g3 [+ \* f9' Q6 t- R; _+ _, t/ q
    102 u3 S* |$ b: f4 Q4 r7 b
    11
    : x1 x* R" M  [) y- n2 R9 H& p12+ Y" Z8 K. R, z1 u, f( O
    13
    ! e4 p8 o8 y" |0 _9 G: J对每克拉的价格分别按照分位数(q=[0.2, 0.4, 0.6, 0.8])与[1000, 3500, 5500, 18000]割点进行分箱得到五个类别Very Low, Low, Mid, High, Very High,并把按这两种分箱方法得到的category序列依次添加到原表中。# {5 X* g0 @5 Y0 Q
    # retbins=True返回的是元组,第一个才是要的序列,第二个元素是分割点) c4 V+ i4 R4 W
    avg=df.price/df.carat
    ' N- E( D9 L& @. m/ ^5 `- j! D
    6 |. s6 f% D0 h/ gdf['price_quantile']=pd.qcut(avg, q=[0,0.2,0.4,0.6,0.8,1],# I* }; t7 ^- C6 W
                                  labels=['Very Low', 'Low', 'Mid', 'High', 'Very High'],retbins=True)[0]
    $ _! v$ s8 B7 x) O, t. r& H! a- F7 N/ Y. D! @" S7 s
    df['price_list']=pd.cut(avg, bins=[-np.infty,1000, 3500, 5500, 18000,np.infty],
    * G# P; ~; ~( P7 x0 Y/ a                              labels=['Very Low', 'Low', 'Mid', 'High', 'Very High'],retbins=True)[0]9 B( O" _: n' l
    df.head()
    % e! h8 A6 E$ Y. v% g0 x, @' h' N+ |6 T1 m5 H& e
            carat        cut         clarity        price        price_quantile        price_list
    7 E7 T2 d7 D1 Z0        0.23        0                6                326                        Very Low                Low
    2 [- P2 A+ `, R1        0.21        1                5                326                        Very Low                Low
    4 V- }- E; a1 `5 ?3 e2        0.23        3                3                327                        Very Low                Low
    / P* t! E' k$ `: ]4 S3        0.29        1                4                334                        Very Low                Low4 L  e% E  |* q+ l
    4        0.31        3                6                335                        Very Low                Low                                       
    8 |9 ^$ h- _9 X/ E, E+ Q8 {/ g# @  H% m2 k' |% A! P- }
    1
    + @9 ^6 B/ ^) q) D* B, D2. X% \7 W, `! `" V; B
    3
    ' S3 y  t/ [3 d! R4
    ) v; h7 y. q; j3 {56 }* q$ ?7 r- y8 H6 r+ c
    6( V, H4 S# z$ v1 K+ t6 _
    7
    / f( I9 f* ~$ R8 _! e' o  G- D8. d: I; F+ }/ ?6 N& n) M
    9
      q" m6 |: \; J+ ^3 H" K10* V' l% @" Q& w  y6 ]
    11
    / P4 R0 l. C1 I6 _12$ s5 }4 f$ X, D/ m$ o0 V
    13
      p3 _& k- \. W14
    , C) |0 X+ d9 p% |# E2 o& B1 E) ?% i- H  l4 n156 f2 c! V8 m  ~+ j1 I
    16$ l+ e5 {) n6 Z  P+ F
    分割点分别是:
    8 ]3 ^. v) v$ u
    3 E3 R  \1 r+ y$ i( sarray([ 1051.16 , 2295. ,  3073.29,  4031.68, 5456.34, 17828.84])1 }( v5 p' {2 w
    array([  -inf,   1000.,    3500.,    5500.,   18000.,    inf])
    * e9 M! H1 u/ @. K* K7 r3 D1
    : m( V1 t! \) o% P* S2
    ) e- R$ O& Q. m1 p6 ~第4问中按照整数分箱得到的序列中,是否出现了所有的类别?如果存在没有出现的类别请把该类别删除。
    / ~/ U7 \7 x8 @5 @df['price_list'].cat.categories # 原先设定的类别数* O' D. ~: f  @+ s& o9 i
    Index(['Very Low', 'Low', 'Mid', 'High', 'Very High'], dtype='object')
    . [* C& R2 n, y  o1 `
    & F/ T8 i9 [# d+ n& P) |- idf['price_list'].cat.remove_unused_categories().cat.categories  # 移除未出现的类别
    ) _0 l: |0 K) P8 QIndex(['Low', 'Mid', 'High'], dtype='object')  # 首尾两个类别未出现
    % m9 e2 o* Q' v; m, g1
    . ?3 @2 ?: X$ t+ c4 N2
    - {- Q3 u' ?) N8 f' |6 ?3+ W* P" P) |0 F* J3 c3 N1 c  Y1 c
    4
    % s( T* C: F3 @3 p1 g" A51 K% s) w' E+ B* R& e
    avg.sort_values() # 可见首尾区间确实是没有的
    5 b2 B4 N5 z5 S2 u, Y" f31962     1051.162791
    8 f6 f0 q$ a2 b4 j( N6 D15        1078.125000* K4 O! I6 J9 C: G
    4         1080.6451614 k+ c/ i; }* j" T
    28285     1109.0909093 i- a- J; O" \$ ~
    13        1109.677419
    ( h# G6 V( ^. C( `7 j             ...     4 ~2 P- Y" c4 u3 @! ]% M
    26998    16764.705882' p  I8 D/ k3 k! z) `1 l
    27457    16928.971963
    ! G& S& \5 Z. I  e! Y; _+ E. ?27226    17077.669903
    ) Y0 J2 Q/ b8 ?8 a7 A27530    17083.177570
    ' |6 M9 m+ K' w5 U* c+ p) m' ?- u7 I27635    17828.846154: _, _) b4 H! s* P1 L
    1+ d/ p# E  v3 K# f9 D  x
    2! e6 w# E9 E9 _# j( d5 _, s: s
    3& J& g' l* p" U( `6 S5 `
    4$ W) a5 }/ Y! Y' Y' ~
    5: X# [$ U8 K+ V
    6
    5 K0 O" R7 V+ J7( W  I8 W3 ~4 Q
    81 Z. v. U: @# G( ^% j/ F* _, e5 a
    9
    7 W- a# ?2 I9 |/ |9 `; h10
    , a9 B" g9 U9 v8 G+ P11) g9 F: Z! |; R& g2 K* q1 R% U
    12
    4 z( Z( x$ i: V$ r对第4问中按照分位数分箱得到的序列,求每个样本对应所在区间的左右端点值和长度。
    6 c) {( d& _7 n5 c- m# 分割时区间不能有命名,否则字符串传入错误。
    - T6 L: w  ~( A& b; }id_interval=pd.IntervalIndex(; I, B  i- ^+ o) _
        pd.qcut(avg, q=[0,0.2,0.4,0.6,0.8,1],retbins=True)[0], X$ Q( ~( ?1 R- @2 n
                                )7 u  _1 {! C. x) ~# L5 Z+ c" s
    id_interval.left
    7 U% H" `0 R7 T7 u, g" u1 Xid_interval.right; B7 n6 C9 i1 B" F6 {9 p' }
    id_interval.length                           
    # _# f4 x5 g. f/ w1
    : a$ X7 o( y' N* U- P9 M# X& D2% I2 L# Y. d+ i: |/ X
    3
    2 d) }3 e+ I- Q( k( |4
    ) q2 z3 Y) |9 f& Q2 A54 M9 r, e2 W5 `) H9 d
    6
    ) K! A/ P+ u$ H$ T0 [$ i) w7
    ) L6 T$ i: o3 r% s* ~3 v第十章 时序数据% }/ R+ o9 c* j. f& n& \1 b4 n7 Z$ N
    import numpy as np" g  H5 _% G. [! V( p7 L
    import pandas as pd- X7 V% {9 Q) ~2 i  `6 N& h7 `
    1
    / @" w' z  H' T/ t. ]# ?' [4 h/ \; j2
    " a+ Y0 O. a3 C6 g5 V: f+ m+ B+ L# D/ S6 Y+ f

    9 P% h6 k7 a* O2 i1 v2 A8 }10.1 时序中的基本对象
    " ?' d# q! l9 H8 d  时间序列的概念在日常生活中十分常见,但对于一个具体的时序事件而言,可以从多个时间对象的角度来描述。例如2020年9月7日周一早上8点整需要到教室上课,这个课会在当天早上10点结束,其中包含了哪些时间概念?
    : b0 A' }: Y+ R: B8 ^0 A/ ]7 e: @' K( v  P( A5 X3 G; ~+ m
    会出现时间戳(Date times)的概念,即’2020-9-7 08:00:00’和’2020-9-7 10:00:00’这两个时间点分别代表了上课和下课的时刻,在pandas中称为Timestamp。同时,一系列的时间戳可以组成DatetimeIndex,而将它放到Series中后,Series的类型就变为了datetime64[ns],如果有涉及时区则为datetime64[ns, tz],其中tz是timezone的简写。8 X& j% \' }" T, W& h/ W  ^
    0 W& G6 n7 I! \- g4 K8 M3 d0 O
    会出现时间差(Time deltas)的概念,即上课需要的时间,两个Timestamp做差就得到了时间差,pandas中利用Timedelta来表示。类似的,一系列的时间差就组成了TimedeltaIndex, 而将它放到Series中后,Series的类型就变为了timedelta64[ns]。
    7 J- Y  e3 T& O7 B2 v  E8 s) ]2 U' ?. S
    3 e# C/ w, O( z& n- H  @/ [会出现时间段(Time spans)的概念,即在8点到10点这个区间都会持续地在上课,在pandas利用Period来表示。类似的,一系列的时间段就组成了PeriodIndex, 而将它放到Series中后,Series的类型就变为了Period。, r: x4 J" r8 ]+ p, M. K

    & Q! H( f! k1 X! a, {' s会出现日期偏置(Date offsets)的概念,假设你只知道9月的第一个周一早上8点要去上课,但不知道具体的日期,那么就需要一个类型来处理此类需求。再例如,想要知道2020年9月7日后的第30个工作日是哪一天,那么时间差就解决不了你的问题,从而pandas中的DateOffset就出现了。同时,pandas中没有为一列时间偏置专门设计存储类型,理由也很简单,因为需求比较奇怪,一般来说我们只需要对一批时间特征做一个统一的特殊日期偏置。
    6 j7 Z9 T) M& w2 \( u. i8 I- y5 I$ G1 g, ?, F, O6 R$ W- p
      通过这个简单的例子,就能够容易地总结出官方文档中的这个表格:9 e4 ~1 s  I* m* Y# Q3 g1 R6 u& A" |+ Z1 o
    4 y1 ]4 y" L* K) f, b, ^  l
    概念        单元素类型        数组类型        pandas数据类型
    ' r: e5 K9 h1 V* r. @+ zDate times        Timestamp        DatetimeIndex        datetime64[ns]
    . b5 T) |! E2 x7 |/ y0 [7 |Time deltas        Timedelta        TimedeltaIndex        timedelta64[ns]
    & n1 E: w( ]& r) rTime spans        Period        PeriodIndex        period[freq]6 s, |0 r( l" r: t
    Date offsets        DateOffset        None        None1 `/ a5 Q) A0 s
      由于时间段对象Period/PeriodIndex的使用频率并不高,因此将不进行讲解,而只涉及时间戳序列、时间差序列和日期偏置的相关内容。% W* L& ^- i; W8 z6 z9 v- {
    * T- ~  D( f' A
    10.2 时间戳
    1 x! n/ U3 N; O( u: f10.2.1 Timestamp的构造与属性: K* H5 C0 `9 \" s
    单个时间戳的生成利用pd.Timestamp实现,一般而言的常见日期格式都能被成功地转换:8 S* R2 R9 o1 G1 A& S" C

    ( W8 K" _3 Q$ M  k$ d7 `ts = pd.Timestamp('2020/1/1')
      U( B0 a  B9 R: W' G# c8 x1 k* B* ^5 ^; R( J9 }$ i7 Y6 R
    ts
    6 ]% d5 m1 b5 L# kOut[4]: Timestamp('2020-01-01 00:00:00')& E" U2 o: E' T) C& P$ U2 u
    : Z- S4 Q4 T1 C) M% P. R; a
    ts = pd.Timestamp('2020-1-1 08:10:30')) u8 h% ^9 s+ T9 j0 U
    . }4 E4 v8 Z# k- }. s, J
    ts
    $ O1 M& B; h) |; SOut[6]: Timestamp('2020-01-01 08:10:30')
    ( G& B* [, I7 w' }$ r1
    / ^& [/ J% r5 F0 h4 I27 n) k5 Q" N0 S4 m$ V% y# m
    38 t: B& r% g) D7 k0 t
    4
    ; k* C# m9 @2 }" u0 y5
    ( T) E0 o: y1 \" O; w. [" g6
    ! D" g8 A- ^0 |( s% o; @' J78 C  d9 T' p6 q3 u, J
    8
    $ C4 R% ~2 k9 Y  L5 c& y* ^  B# T9# C% r* [+ Y/ c, r. E2 Y7 d
    通过year, month, day, hour, min, second可以获取具体的数值:
    . ~( u/ {9 a7 A" Q  B+ C' ]8 C5 S" g
    * W- H$ \  q% bts.year
    3 s- h/ L: h6 H: GOut[7]: 2020# b9 ?2 ]4 G9 `7 u8 p$ ?
    5 B2 X  ~# A% K7 u: d4 P
    ts.month
    ; {9 G, s7 n. H- h* }Out[8]: 10 B8 Q- g3 V$ x% v5 @/ J0 Y

    4 G3 K5 P; _! G0 R. e/ N6 Nts.day
    & ~+ ~6 ?" O  p9 GOut[9]: 1
    ( i. ]. V) V% C6 o1 Z) L- Y  |7 L
    ' y* v6 _  g6 Y$ @3 o4 Y: f, Dts.hour
    & B9 J9 q: x8 k$ h5 G$ l: O7 r3 nOut[10]: 8
    4 s! S# S5 U3 `2 ~/ [9 J! N# b& q7 n3 r! A7 a& d' Z1 n9 ]
    ts.minute
    / U/ M: I# Y. kOut[11]: 10
    1 b( [8 \  s3 n, o, h5 h& D9 Q: S+ B
    ts.second/ c* E( R5 w& G  r+ z/ t
    Out[12]: 30" a9 M' m/ `! w5 R" _6 U

    , R$ i- T4 }9 S0 o* S12 U+ N+ |" L' s! K+ |8 K  j& R
    2
    , K9 b: H6 p- |" ?3
    # b; g* E7 F" N+ O( d$ {2 @4
    . |6 d* ]/ }# ^. s5
    7 A7 j2 o5 g$ I% j# F66 T. Y4 r# b3 A+ @
    72 U! W3 b1 U1 s
    8
    : A) Q' k% A6 \- l' Y# \9 Z9
    8 A5 [  T9 x+ b* E( P2 Z10
    4 i6 ?" n' Y, l) c11
    3 x2 n. x5 D4 f8 e12* N# w/ [, `) U6 O; V4 ^
    13& y& O  `9 _( q9 W0 E2 d( z5 O3 n
    141 [) w4 n  S0 ?7 V) G
    15/ Y/ F/ I# N1 C5 J2 \# n, V! V
    162 t+ w0 b5 ~, D9 t
    17
    ! x; L2 n+ C5 b! m% b# 获取当前时间0 x; s  N3 |; G+ f
    now=pd.Timestamp.now()2 [, P: B0 k  G3 `5 T, ^7 o8 {3 E
    1
    $ Q& U; b6 M) Q% N7 I' g: E2
    # m. Q3 ^- }2 i9 O( v( Q在pandas中,时间戳的最小精度为纳秒ns,由于使用了64位存储,可以表示的时间范围大约可以如下计算:
    ' u) J0 f9 i* A2 q; `. I* LT i m e   R a n g e = 2 64 1 0 9 × 60 × 60 × 24 × 365 ≈ 585 ( Y e a r s ) \rm Time\,Range = \frac{2^{64}}{10^9\times 60\times 60\times 24\times 365} \approx 585 (Years)
    : i/ N/ b: I9 O4 a! _1 W- `TimeRange= ; b1 W  ~/ }: J- j
    10
    ) F  Z  B6 s( f% ?0 x9  V, @: W2 h4 g$ N, y. }' M, I
    ×60×60×24×3652 j+ j, S4 r( H: h4 u; J
    2
    % P/ z0 j, E# S) b6 n641 d; m, M- L# Z/ O) K: U2 R

    ) w! |& d7 O: c  p- j/ Z0 o' G  [, d7 j4 f: U2 x6 Q9 Z* `
    ≈585(Years)
    ' X. ]: I& q( }+ f: _) B
    % g; N' Q0 C0 @通过pd.Timestamp.max和pd.Timestamp.min可以获取时间戳表示的范围,可以看到确实表示的区间年数大小正如上述计算结果:
    5 A+ V7 D- \2 \7 t: k3 K/ v3 B: W/ y
    pd.Timestamp.max
    8 l2 w& L# d' K& B" g3 OOut[13]: Timestamp('2262-04-11 23:47:16.854775807')$ j3 Q" p4 C& C4 _5 R2 \

    " Z# Q5 z$ d3 k8 X8 upd.Timestamp.min% \$ v, R# k& C1 R, C4 v
    Out[14]: Timestamp('1677-09-21 00:12:43.145225'): g% A* g4 y3 K: {

    6 A; b' W: X4 O; T; [( }' D& E8 k/ Wpd.Timestamp.max.year - pd.Timestamp.min.year9 c9 ~3 c- d  c$ R3 t; W. b  C
    Out[15]: 585
    . ?# L1 x$ Z$ L3 |! B. N1
    ) r  K" T5 V1 p$ ]$ E( h0 `: \9 O2( z: D% l4 a! n; y- j3 o
    34 T1 @( f! y8 h* q1 p/ U" e
    4! e% {2 R+ e# \2 H' n  ]
    5
    , X1 W8 l. B7 e6. R) j  W! W5 _2 Z( a
    7
    % R% d1 j5 `) b$ t% A: P  d% U8  g6 E- {9 K& J% }3 \# I! q
    10.2.2 Datetime序列的生成. ^2 H, v, T* a1 U
    pandas.to_datetime(arg, errors='raise', dayfirst=False, yearfirst=False, utc=None, format=None,) ~2 u1 i4 O0 v. G1 L6 W4 y
                                      exact=True, unit=None, infer_datetime_format=False, origin='unix', cache=True)
    + ^( D2 ?1 N( }1
    7 r0 A& ~  a% h" g2. k. Q: m* _% l) k- m( d; M
    pandas.to_datetime将arg转换为日期时间。0 O9 t9 U2 q  u& Y

    $ P' `6 N* p) T* t# Jarg:可以是argint、float、str、datetime、list、tuple、一维数组、Series、DataFrame/dict-like等要转换为日期时间的对象。如果提供了 DataFrame,则该方法至少需要以下列:“年”、“月”、“日”。) Y3 k; M% S4 M% y) F
    errors:
    1 m( y0 m. s; [, N; ]. x6 c9 w- ‘raise’:默认值,无效解析将引发异常, r3 Q" w6 J2 C9 W9 Y% B7 n5 @. W
    - ‘raise’:无效解析将返回输入2 `! l6 ^, p( Z  t7 a6 E
    - ‘coerce’:无效解析将被设置为NaT
    ) B6 \! `& h6 L6 N/ w! Adayfirst:如果 arg 是 str 或类似列表时使用,表示日期解析顺序,默认为 False。如果为 True,则首先解析日期,例如“10/11/12”被解析为 2012-11-10。如果无法根据给定的 dayfirst 选项解析分隔日期字符串,会显示警告。# x1 W. u/ N6 X
    yearfirst:如果 arg 是 str 或类似列表时使用,表示日期解析顺序,默认为 False。如果为 True,则首先解析年份,例如“10/11/12”被解析为2010-11-12。无法正确解析时会显示警告。(如果 dayfirst 和 yearfirst 都为 True,则 yearfirst 优先(与 dateutil 相同)。)
    & {/ ^- W2 K) h- o1 R8 _: b7 `5 uutcbool:默认None,控制时区相关的解析、本地化和转换。请参阅:pandas 有关时区转换和本地化的一般文档* W3 X5 {+ Z- T  y( {$ P* _
    format:str格式,默认None。时间戳的格式不满足转换时,可以强制使用format进行匹配。6 v+ o+ S  L3 ~+ D: N" k$ t
    unitstr:默认“ns”。它是arg (D,s,ms,us,ns) 的表示单位,可以是整数或浮点数。这将基于原点。例如,使用 unit=‘ms’ 和 origin=‘unix’ (默认值),这将计算到 unix 开始的毫秒数。% `2 n+ X1 b- G! a+ v
    to_datetime能够把一列时间戳格式的对象转换成为datetime64[ns]类型的时间序列:- L1 ?+ F; B; H6 G! K7 F
    pd.to_datetime(['2020-1-1', '2020-1-3', '2020-1-6'])) Q; F  M$ \$ \; x- B) j
    # F; H1 d* x! n8 y
    DatetimeIndex(['2020-01-01', '2020-01-03', '2020-01-06'], dtype='datetime64[ns]', freq=None)* u& Z- d% b9 _% ^% I
    1. u; j6 w2 K* `( P* U8 A7 s
    2
    6 i0 `2 S3 M& n3
    % o3 S) Y9 f  C* K0 P: f在极少数情况,时间戳的格式不满足转换时,可以强制使用format进行匹配:2 r, r& M( t8 \

    , E, M1 Z) O5 W! v: `* y: btemp = pd.to_datetime(['2020\\1\\1','2020\\1\\3'],format='%Y\\%m\\%d')
    * i4 y$ ^" n9 _) N4 t! D' N, r* ?temp$ b$ u6 F* i5 K+ ?% |1 e+ n$ U( k* }2 p
    9 a" y# }* h# c2 d! x7 {# m5 m( H
    DatetimeIndex(['2020-01-01', '2020-01-03'], dtype='datetime64[ns]', freq=None)3 |+ X: `/ U$ d( A. X  U
    1
    0 C! p' _0 Z( r/ C( m2
    8 ~) i$ }& V8 t! Y3) O+ ], k7 G- f' v7 e; T# Q
    4
    , Q9 H7 p6 q! y* ?  注意上面由于传入的是列表,而非pandas内部的Series,因此返回的是DatetimeIndex,如果想要转为datetime64[ns]的序列,需要显式用Series转化:# t. [) J- L: U

    ! t. z/ v4 q6 I& g! \pd.Series(temp).head()
    1 }, T& U$ C1 Y1 I8 c; n0 Q, `% A, j' q+ Q- K' H
    0   2020-01-01
    3 ]/ h+ y) I" N* X1   2020-01-03
    2 [2 p" R* z$ b4 ndtype: datetime64[ns]( S$ K* J; v6 L" p1 v$ [
    1
    # v9 ^9 a7 [, ~7 N* h" ^/ s2
    9 p2 K7 H0 _/ s3: X, Y5 W( n0 r
    4
    , m& {+ k$ @9 x1 g5. v" c* I1 Z9 i
    下面的序列本身就是Series,所以不需要再转化。
    , u# h. i' ^! M. R# d- I
    % E' Q* X( f9 u# l5 ?- W/ Ydf = pd.read_csv('../data/learn_pandas.csv')
    6 l3 \. a! ?9 k9 j+ W: ?s = pd.to_datetime(df.Test_Date)% p3 _6 R0 R& \2 [. [& X
    s.head()
    3 T) e5 m& z4 W& T2 M' P. |6 i( x( h
    0   2019-10-05
    , K( z' v9 H- s% }9 ?1   2019-09-04
    6 n% i, G  H: w1 W2   2019-09-12
    ' {5 d  v  e" H- v3   2020-01-03
    . W9 c$ b$ S+ F* O6 a4   2019-11-06# w3 L; }, {( s, L4 U+ u2 }7 j
    Name: Test_Date, dtype: datetime64[ns]7 T. u$ J3 j+ d  X9 P
    1
    . s7 M5 a, D0 y* U0 B/ v& Y# Z2
    # P, y8 Y8 r4 }! O3
    ) n* O* n2 ?3 D4 o4' a) l( y% C9 L/ e
    5! Y7 P7 S# @8 }
    64 }  e3 R/ y+ |6 U
    7  p+ q5 t( O6 t# Y
    8
    : f! D$ N1 _5 Q7 f/ l( L9  H( H. [8 Z' R) o9 s6 D
    10
    * T' g  o7 x( G$ ~把表的多列时间属性拼接转为时间序列的to_datetime,此时的列名必须和以下给定的时间关键词列名一致:
    4 X. D8 n. B" h! s" i* }df_date_cols = pd.DataFrame({'year': [2020, 2020],
    * M7 l& g+ l, h$ n6 I  Y                             'month': [1, 1],$ ?$ x' C: D4 X
                                 'day': [1, 2],' e' a+ U' D7 q' q$ k
                                 'hour': [10, 20],
    : n  i+ A3 }# g9 W) j; p5 [                             'minute': [30, 50],
    ( G$ q: x" U7 V+ g. i- V                             'second': [20, 40]})
    0 h: B3 S/ r$ f* w3 L' M+ hpd.to_datetime(df_date_cols)/ _0 y8 s4 x/ E6 }- D6 a+ g
    - d& ]1 j- z9 f+ ?
    0   2020-01-01 10:30:20& E7 M& `9 ~# H) |+ }% v5 V, u
    1   2020-01-02 20:50:40, S" E- V0 J4 a2 l
    dtype: datetime64[ns]3 O5 U' [, T& s% }4 a; p
    1
    1 i9 x' E( G$ a2
    0 c( `* e% V# X1 Q' w! d7 k$ R3
    ; P. Y" ^+ p  c7 Q9 m4$ A; t( q/ ^7 L9 _, b
    5  ^- [. b* a2 c$ G! A- d4 y1 i( x
    6
    " [' ]6 _" G, P1 m2 M( Z- V- E4 g, f7
    " ]+ F7 g$ G9 u0 t8
    & k. [* D: c" z* @) }9 q9
    7 R! V" y4 E) ^100 i$ y0 n' U9 j! _/ O
    115 @0 l3 ~% e7 s$ b, M5 O
    date_range是一种生成连续间隔时间的一种方法,其重要的参数为start, end, freq, periods,它们分别表示开始时间,结束时间,时间间隔,时间戳个数。其中,四个中的三个参数决定了,那么剩下的一个就随之确定了。这里要注意,开始或结束日期如果作为端点则它会被包含:5 a/ [7 r( ]7 w3 B- o2 `
    pd.date_range('2020-1-1','2020-1-21', freq='10D') # 包含0 o: I( {1 h! O
    Out[25]: DatetimeIndex(['2020-01-01', '2020-01-11', '2020-01-21'], dtype='datetime64[ns]', freq='10D')8 Y, D4 A5 q( z, |% {
    $ S% O7 X( z" O. B
    pd.date_range('2020-1-1','2020-2-28', freq='10D')8 u7 _0 S/ d3 W- y$ D2 ^8 X$ k, ~
    Out[26]: ' d7 Z! }8 E, x9 t# x
    DatetimeIndex(['2020-01-01', '2020-01-11', '2020-01-21', '2020-01-31',4 P; B  \4 V8 P+ `- b
                   '2020-02-10', '2020-02-20']," |' j. j- H% M+ c* y& X
                  dtype='datetime64[ns]', freq='10D')4 C' @/ `  Z; U1 P4 c$ I9 B2 K/ x: E
    0 G( H& ~# N- e# |) i6 y. i5 m; n4 F
    pd.date_range('2020-1-1',; ~1 s) U% n: |; O" k. G- Z
                  '2020-2-28', periods=6) # 由于结束日期无法取到,freq不为10天/ u. n$ j; ^6 E0 L

    ! T% H# d7 v7 B# c2 P2 _Out[27]: 1 ^5 ?7 d  z/ C4 {& U* d2 z
    DatetimeIndex(['2020-01-01 00:00:00', '2020-01-12 14:24:00',
    ( G2 b( c4 o. S& g2 M1 S* n0 K               '2020-01-24 04:48:00', '2020-02-04 19:12:00',! b% S* U; |# [: U% y, E" U
                   '2020-02-16 09:36:00', '2020-02-28 00:00:00'],6 J% P. Q1 i2 p; M* x
                  dtype='datetime64[ns]', freq=None)
    2 k2 s: b* @* L, f: L9 j5 I( f' j0 h# d  A# c
    1" R- ]# b5 d* J+ v  |# Z* T: n% T
    2
    : _0 ~( L! p0 J& P7 F7 D3" f: p2 C3 x$ g) G2 T% I
    4! _6 I* q- E9 q) D& M
    51 e8 g; W# g2 |+ q' G3 ?- I
    6, Z4 J5 {. c6 [
    7
    ; V% r! H1 Q( c) `% d8
    8 S( e2 I! P% Z3 e7 X4 k9
    $ @1 N" s/ g6 H) M. {( H' Z10$ Y- [$ z7 r0 _9 ]: o$ x
    11
    . Z1 M$ I) o/ l12- A& y* e8 Y# J# M6 B
    13$ Q: K5 Z$ P) v
    14
    6 q' Q2 q3 j: e- e9 B) ~15, s; d' |+ _6 H2 m7 _0 k0 _
    16
    + Z; ?- ]% Y0 _  r% T17
    ! w) _  o, `# J% F; O. C这里的freq参数与DateOffset对象紧密相关,将在第四节介绍其具体的用法。( ^+ f- q& p1 C+ f; [5 Y0 }; c4 A
    6 S( o  ^. O# E' A2 z4 y
    【练一练】
    % M/ g1 f2 |! V1 K+ z" {Timestamp上定义了一个value属性,其返回的整数值代表了从1970年1月1日零点到给定时间戳相差的纳秒数,请利用这个属性构造一个随机生成给定日期区间内日期序列的函数。
    ) l7 m/ a" s' r8 [5 l
    : |0 y8 A; E7 i7 _  H( ]ls=['2020-01-01','2020-02-20']0 Q0 V8 M! Q. a) z
    def dates(ls,n):1 x2 P! t7 E/ R, G% y& K1 Y* P, d
        min=pd.Timestamp(ls[0]).value/10**9( X' z  a( v+ k
        max=pd.Timestamp(ls[1]).value/10**9
    / w; }4 j  g/ n5 U5 [* B$ [    times=np.random.randint(min,max+1,n)6 C8 s' i# ~; E3 t& _
        return  pd.to_datetime(times,unit='s')" c9 T1 {+ I8 A" a; i$ g4 z8 ?( _
    dates(ls,10) 8 W! Y9 y' x% |

    5 ]) X- B0 X/ ~( C- x, d6 HDatetimeIndex(['2020-02-16 09:25:30', '2020-01-29 07:00:04',
    . Q* Z* ~2 k( ^% R' H               '2020-01-21 12:26:02', '2020-02-08 20:34:08',, {7 o  H! l4 A. z
                   '2020-02-15 00:18:33', '2020-02-11 02:18:07',, q) V/ n  E5 @  t+ @
                   '2020-01-12 21:48:59', '2020-01-12 00:39:24',
    ' H. D. C* v' ^9 C3 r               '2020-02-14 20:55:20', '2020-01-26 15:44:13'],
    % D0 n/ h/ d6 P& t$ R              dtype='datetime64[ns]', freq=None)6 y4 H; O1 |' p8 l, M0 S
    1
    9 y- {( s# C0 O' A2
      Z) p& S& z6 m0 e0 U  b) t, [% O5 `3  }9 @4 j. G$ r0 d8 J$ u
    4  i# C! m: e: V
    5
    4 _* O) m0 k4 g2 q& @69 i6 W( z2 A4 A- j- R  m
    7
    3 Z. ^6 y7 h1 x- @84 H1 T- p) @3 G; k+ E
    91 Z% t; {  ^: [3 w, r+ `3 U. T
    10
    : _0 L7 {1 |9 c( u/ R. i11( A% G  n9 F( w2 y+ m2 D
    12
    ) X* k& G1 X& |/ e5 h- |: i13
    * @6 t0 e( @8 S' @, b14& I+ X0 }! V3 a. f+ u
    asfreq:改变序列采样频率的方法,能够根据给定的freq对序列进行类似于reindex的操作:/ ?" j2 v( K: L2 E# \
    s = pd.Series(np.random.rand(5),) f. `& F5 S+ j2 S9 P5 Q
                index=pd.to_datetime([
    . Z7 V- `7 l& h) M                '2020-1-%d'%i for i in range(1,10,2)]))" ]5 y/ b: s) e  a5 t0 t
    & U8 \. T! l% j2 ^* ]8 G+ O
    6 |0 P# E: _, k0 T
    s.head(), z7 }# P) [" V* z) F$ f% y
    Out[29]: / u: m2 b$ X1 G1 W
    2020-01-01    0.836578
    5 L4 ^9 t, _: X- }8 C2020-01-03    0.678419
    * C2 X& g6 U; ^7 L2020-01-05    0.711897
    # l0 `7 G; @5 t! K5 E2020-01-07    0.487429/ G" W7 p1 a2 {; J' E& ?8 z  Y
    2020-01-09    0.6047050 c6 l4 R) H0 b: U8 g
    dtype: float64* U& V0 b0 B/ n# h+ n. R6 [% X
    : N: A' V3 H6 ^0 {/ j' n, k
    s.asfreq('D').head()5 B6 L% j5 B! b) u7 z$ K0 I
    Out[30]:
    . ~3 Z1 p# l0 r) c) o, G6 r2020-01-01    0.836578
    5 Y: x7 U( f/ t2 a9 r* N' |$ B" `1 E2020-01-02         NaN
    ; e1 q4 h, J" y# ]8 u9 |2020-01-03    0.678419. N- f2 n$ h( a
    2020-01-04         NaN& e3 p' o' X/ r* a% M* }
    2020-01-05    0.711897
    8 _; O  N+ ]$ i- JFreq: D, dtype: float64
      K1 J/ n, i8 J
    * J" E7 g4 E0 _" e3 Js.asfreq('12H').head()! `4 j3 x% m* J
    Out[31]: . g1 n) I  ?& Y% u, r% U( ^1 x" q
    2020-01-01 00:00:00    0.836578
    1 F# [9 `! m  f5 c2020-01-01 12:00:00         NaN
    . S0 n) V, n+ D+ q, ^1 d, k  s: I: ?2020-01-02 00:00:00         NaN- g  ]( i" ~) I( G4 n
    2020-01-02 12:00:00         NaN- B) ^8 A6 L* `) Z
    2020-01-03 00:00:00    0.678419. z: C5 G/ m% J2 w$ B5 o! }, W
    Freq: 12H, dtype: float64
    / Y/ e0 M/ \0 i4 B( g' @! g( y! G& [' y
    1
    ) G  Z' i% y6 m( x0 d/ p2
    : u$ M( ^2 A% C6 `( G38 k* s8 G4 v1 D/ e8 x
    4
    8 I( F7 n+ c. p& ^, v5" `8 y! F& Y" L3 @( ], p1 J/ f- Q0 ~
    6! Y" T3 X& w8 r9 e0 `
    7# L9 c" {5 D! b, B) t1 _4 u" j
    8; E1 F3 K' W  \2 }
    99 @: |7 K3 C) o2 I; A
    10' t4 t' @+ {2 w, v
    11
      p( }+ {: A9 _) a1 C+ [% D$ H12* {. A7 X3 m) [1 k$ w- G- R" L
    13
    7 n( i: t3 L* v2 l14) u; Y1 o8 J) L$ X$ X* i
    15
    . _" ^& ^2 ]1 m$ {1 ?; y0 g16/ A0 T! g% s; C& p0 ?
    17) W% a* X3 V1 e- Y, d
    18
    & B# S; p2 M- x% ~& U" l19; y! S, q. l5 ~# q" z
    20
    + ^6 L: L. {0 u. a4 V. ?5 r" \* \% u21) [; B3 c" U( ~2 w% U. p$ G! m
    22
    / ^3 Q, Y. O. i! }23
    + n% ~% C1 V( b$ G4 l24
    & V; c& C  t- d% M5 a0 b2 j/ X25
    9 @, X4 r# f% x4 b1 ?9 Y; O3 n264 l: }5 d( R6 H
    27, U8 H  Q; P5 @
    28$ N/ t% k0 g9 _/ |4 w
    29
    & j1 x' J; h2 u# W' d30
    / e* k+ c0 O) \9 j, P31
    ' z1 L. b5 {5 k- ]6 b【NOTE】datetime64[ns] 序列的极值与均值
    9 E  D1 Q, @- W  前面提到了datetime64[ns]本质上可以理解为一个整数,即从1970年1月1日零点到给定时间戳相差的纳秒数。所以对于一个datetime64[ns]序列,可以使用max, min, mean,来取得最大时间戳、最小时间戳和“平均”时间戳。; I! C, F/ R. I; P, c7 u5 Y( [! O

    % o: E) b( V! C$ O; p! U10.2.3 dt对象# i  M& ^0 P  r2 p! W4 I5 y' d9 k
      如同category, string的序列上定义了cat, str来完成分类数据和文本数据的操作,在时序类型的序列上定义了dt对象来完成许多时间序列的相关操作。这里对于datetime64[ns]类型而言,可以大致分为三类操作:取出时间相关的属性、判断时间戳是否满足条件、取整操作。
    9 l9 W* Y1 v* @6 `, ^7 a9 p& ]7 D- c. q+ r: P& p! F
    第一类操作的常用属性包括:date, time, year, month, day, hour, minute, second, microsecond, nanosecond, dayofweek, dayofyear, weekofyear, daysinmonth, quarter,其中daysinmonth, quarter分别表示该月一共有几天和季度。$ X% T1 G# i3 v- Z
    s = pd.Series(pd.date_range('2020-1-1','2020-1-3', freq='D'))
    & }2 R" n9 q9 O. q* k7 u& \! F  h( r: u
    s.dt.date
    - V$ [" t7 q( g7 ?; w- j  eOut[33]: 7 r- @8 R' D! }$ W
    0    2020-01-01
    , h) b$ M( `, O1    2020-01-02
    & C+ M" ~" ]( L$ L. y) F, ~" s8 S2    2020-01-03
    5 ^& X% |0 |: m0 q5 }5 Adtype: object! ^3 B% x6 J1 l

    4 A9 m2 R) M3 O, m' ~; m6 Xs.dt.time! f. o, b0 R% u" B3 c, F+ f
    Out[34]:
    ( _4 l6 u. ^0 K0 ?0    00:00:00
    . Z: x/ C. ~; F$ e9 b! {/ P1    00:00:00. ]) Z" a4 s  x/ S  D
    2    00:00:00
    : {- Y5 o, p9 g$ v3 Idtype: object' J7 G* [& D) B& d9 o

    * q7 P% u. @, z( j9 }  @s.dt.day
    ! y5 J( ]6 t, X& Q' aOut[35]:
    : G5 ]/ e* Z* {3 M; F0    1
    3 ~2 J3 s9 u. R$ g3 m. J1 L+ Y- D1    2
    6 `  Z. E8 \! J2    3( m0 ~' l5 l7 o5 H; [! F4 m8 K
    dtype: int64" s" l6 V9 }' `; ?
    4 J" f% g2 \) V0 s( P) E* _& j; r
    s.dt.daysinmonth
    , O  D" h7 Q( B, ~. f6 i; G9 DOut[36]:
    / w7 _# H5 `1 w: G0    31& t( ?; z! l% \7 U, |( r
    1    31
    $ o- t$ E8 U% }# Z& }2    31
    * H* k$ G/ X5 ]' j" Q$ F3 G# \dtype: int645 q7 w3 b7 V" @* ^+ @* B

    ) @6 {" ?+ z: h$ C9 w6 {# @1
    # z) q. J. E3 N' f7 |2
    - T1 D! ?0 G% [6 Q9 c2 R3
    ) f( ?& c  e& b" J3 z4
    5 R  V1 S' k' O  Y/ u$ x) q2 I- k; q5
    / Q+ H9 ?& P& C9 Y6
    % d+ ], ~3 b, _8 \/ Z" }8 h8 k- V72 F4 m' k9 [/ D5 D
    8
    ) |* f1 \- N: ~1 q9
    4 f3 e  i2 T! L! j9 Y) q& G, f* s' o10
    , ^3 z' }% }  b3 y1 B8 Z+ Y; T$ ?1 v11
    - A4 G5 `7 u* @5 Q( h/ D12, M: K/ d$ {! h2 z# c) l
    13, A2 {. [6 u! e7 f( _- i
    145 ^1 {5 [6 E* \
    15  m- i( n) W" _' ^& I1 k
    16
    4 u+ Q& ?8 K& }. Y17
    & P- P$ X. h6 X6 t0 x18# N( @+ x$ t% M# Q1 o0 K
    19$ A' Z2 [9 h- ~: ~+ O' g
    20; ~; f) X8 e2 a; }+ `# ?6 E) a' @- e3 s/ U
    21; N; O2 F3 y% G0 G+ a
    22
    & q$ S' u3 {0 @4 ?% b2 M23' G9 p+ I3 I( M4 c. f5 V
    24
    7 T1 R1 d7 m8 r4 @$ R252 |, v  X  T: a4 ~6 I
    26: J. i! `* f) P7 ]7 i! P; j
    27
    / K; \# a' n5 c$ p28" z/ H/ j9 O1 L# N% V* i
    298 c- I- H' t2 V/ I
      在这些属性中,经常使用的是dayofweek,它返回了周中的星期情况,周一为0、周二为1,以此类推。此外,还可以通过month_name, day_name返回英文的月名和星期名,注意它们是方法而不是属性:
    . F$ D; }& d5 ]! z. u
    ; A% ]  N5 J7 k2 ns.dt.dayofweek- a5 K" E2 A* W# k7 }
    Out[37]: . P& E* F7 i/ P- Q* ]9 a% o
    0    2
    ( F. C! O" I, Y  r" y1    3' g9 i) c# H0 `/ R; h' q8 F
    2    4
    , [! {" \- T) x% ^3 Ydtype: int64
    ! d  T0 U& _" D4 D9 E9 u1 y, {/ V: p0 j/ T3 K5 L1 s
    s.dt.month_name()  M7 u! J4 N+ r# ~( G( L3 N. T' y9 a
    Out[38]:
    6 c/ U2 Y( n, R0    January+ Y5 C: Y9 n5 U8 m
    1    January; r# r5 l; }2 R- l4 U/ h
    2    January1 M  D% m; k: _0 f% D, D6 H/ l8 C
    dtype: object* h4 M$ b$ t2 K% T9 E4 y9 r, ^: P9 S; [

    / u3 i9 r; v0 F+ |s.dt.day_name()2 k' X1 B3 V; D5 `
    Out[39]:
      O1 m& X+ g* }4 M. T0    Wednesday
    , V* t, |# s3 z" K: r4 i% ?1     Thursday
    ' I+ b0 Q. j+ E" M0 `2       Friday) H  N# K& u7 M) i
    dtype: object6 z) h  n2 i/ Q% t! I- v1 F

    ( U  ^& N' F& T% X$ W1
    ! T4 }* n. _8 t8 F. {2
    ( r& q' ?1 [3 j  ~( L5 H' b3
    " J5 e$ y4 Y  D* t& I. `( U44 F- _$ U, u) \) H3 B# u9 E) v$ O) ~3 A
    5
    0 Z: _2 r2 D' W" V' q: n# ^6+ r* B1 i4 Z2 a$ A3 Z6 z" ]
    7
    * N/ w; ^5 q5 h. E. x) ~8
    0 z& Z- [$ [$ J) T( j7 e92 p6 t9 V9 L7 n# V
    101 e5 p  |2 C! g6 I8 `- j/ W
    11
    ) p9 m! X+ e2 T3 P& J& B12
    # q2 S3 C9 d' S, G; ?13
    3 @1 Y! x! w& q( Z146 a8 h+ M+ R0 ^; s1 T6 W; M+ u
    153 I4 H2 b1 ?% L. w; D1 A0 k) C$ h: ?
    167 T9 ~1 B4 ~3 M
    17% G7 H7 Q/ p4 ?
    18
    / H% X# W+ l4 D8 S* ]19
    $ \! p2 {' m. r* |# y/ p9 ]20
    : {. E; M1 S  Z; v3 K9 n第二类判断操作主要用于测试是否为月/季/年的第一天或者最后一天:
    ) s6 Z/ p/ l+ a& C( z9 [* Ws.dt.is_year_start # 还可选 is_quarter/month_start; X% l9 P' G) w' X7 @5 T8 i" L
    Out[40]:
    5 C3 n' N: f% |2 ]9 ]0     True  e$ V& x: w: G. [& I/ K7 s% s: N
    1    False
      f3 _3 G% X& C; z2    False  t  y3 T# Y, W6 ]: g6 V' o
    dtype: bool. \& `6 I+ f7 a3 f

    6 |8 U+ c$ t4 T4 O: }3 Ks.dt.is_year_end # 还可选 is_quarter/month_end
    " t6 U) x% C# V9 I5 t" K7 VOut[41]: / _0 ~3 i$ M6 d6 i9 W/ e
    0    False, L, D: ?+ `1 T) m
    1    False3 u8 |) H/ Z1 W& u4 h( B
    2    False
    5 `) S. V' P0 K! p, i' ]0 }( wdtype: bool9 h9 E1 [: a/ d. o7 r
    16 p, b2 p( [- v" C0 B3 E$ k
    2+ c  J" {9 o7 L( ?* v  W
    3; i5 `$ t* Z4 f
    4
    : B/ p: L$ X* `5
    $ x/ d) n" a+ ?6, ]2 r, {' ]' x/ D2 k
    73 J$ r4 u/ P( N7 g+ Y
    8
    . }" h6 K; Q% e9
    : s) O4 _& f4 e. R8 h103 L+ Z2 B3 o/ j8 I* K7 U& H
    11( I/ q. Z% L# `
    12+ i4 ]/ x- d2 m# U
    13
    ) c8 ^4 E0 d" m% p/ s* L第三类的取整操作包含round, ceil, floor,它们的公共参数为freq,常用的包括H, min, S(小时、分钟、秒),所有可选的freq可参考此处。
    ' ], q" |% ^) Y. M& ^. us = pd.Series(pd.date_range('2020-1-1 20:35:00',0 m/ r: m+ q; R! b  @2 T
                                '2020-1-1 22:35:00',& _: M/ t" g# i; u1 S0 c
                                freq='45min'))
    $ N* d, N# s; g( t1 d7 _9 Z6 b, x) \2 f( z. y. W- |

    6 q" c1 |7 m! d4 e# Us
    ( P" P! q) k( a2 e/ X# oOut[43]: 0 P$ @' I# ~7 _/ ^
    0   2020-01-01 20:35:00
    & k/ b3 B1 |3 N2 A  W, L+ I1   2020-01-01 21:20:00
    / ^% }5 q, |9 w  Q! n2   2020-01-01 22:05:00  ~. Q1 z4 B9 [' O% r% t
    dtype: datetime64[ns]# g( R) X  ~% `
    9 j  U2 A. j, C7 Y* |
    s.dt.round('1H')
    3 i+ w: z4 M, T8 q) R! NOut[44]:
    : c7 Y2 q6 [9 r& _5 ?7 y3 c0   2020-01-01 21:00:00, I: z: Z$ v; p: L3 `/ S5 e
    1   2020-01-01 21:00:00
    4 V3 u5 q- @; S6 r! J2   2020-01-01 22:00:00
    / u; g' r6 B5 k6 D- A7 r" Vdtype: datetime64[ns]2 O& U) w4 E1 I
    ) Z' O2 e0 G2 N4 n
    s.dt.ceil('1H')
    0 v' {/ y, I- [1 rOut[45]:
    3 n8 [: d) o1 @$ K: b3 M0   2020-01-01 21:00:003 L2 X! x7 d& _3 G# p2 Q
    1   2020-01-01 22:00:00
    6 ~" V- d$ O  n! p( q) o2   2020-01-01 23:00:00, D( \  e4 D$ K
    dtype: datetime64[ns]
    7 M& f3 Y1 w, \" E" Y
    9 p" n. u6 A" @6 ~$ N; o" os.dt.floor('1H')/ j+ i$ C2 w3 u; k7 ~* c4 C4 j
    Out[46]:
    . g- g7 f7 V; C3 J0   2020-01-01 20:00:00
    / K, x2 _+ u/ ^1   2020-01-01 21:00:00
    0 _% x' U  R, P% ^" l; ?5 W1 e2   2020-01-01 22:00:00
    8 a' i# |3 H0 a3 Kdtype: datetime64[ns]% `2 t, L( L0 R. e# w8 J. v
    : L* [0 h6 x! ~/ f/ P4 @: \$ ]' {
    1
    7 F4 ^' o% r; h4 N9 H5 y, m2
    1 V/ x# ~. h7 T  L# }3, A( s( H" I" [2 T; F; k+ U5 Z0 V6 F
    4+ P# T- A1 w$ S6 a+ w" D& v4 Z
    58 W% s( r5 o- B& {
    6
    4 b9 k. [! F& P  U( \- d7
    # J1 x+ O  o* Y5 M+ l8  i' B$ H( n2 U' y5 o! ^
    9
    $ k# e; \. G6 a5 c# N; L. {108 v9 y* \" h* a
    11
    " m0 e! n3 P* k; B12
    0 Y3 m6 h/ ~  f: V. |13# Y5 ?2 K  f; D3 r# [6 i5 y
    14
    : i- [7 m+ ~* ]15
    9 z  q# n0 \) F) j) E) }16
    8 f! n1 e  }* c8 o, c) ?8 L! i170 c+ Q2 v' P. X( h& G
    18
    5 o# G; a& l. n5 C& n19: P8 q' C" w/ B3 `: ~+ X. r) v! R
    20
    ) f1 J/ s& H, [: b# u1 g21
    5 P/ U  |" k. f! x$ B22
    # `# {7 @2 @. C. A$ e% ^  n9 ~23
      @- T4 B# x4 q7 T+ Z24
    0 R1 e- c: S! W" ?& @7 `; t! P25
    " r% n0 O  r# b9 R) p) |2 M26
    " T2 r, ~' s' A. P% O( G( |27
    - L- }$ d- ~8 q. L7 O28' B3 n, v! w+ p6 J$ C4 @
    29, j* e& H" |! I" W9 T
    30) ~- g( \9 y' u
    31; m; m, ^/ X; \9 b4 ]
    32
    # N  |. m, X1 Z5 c( m10.2.4 时间戳的切片与索引0 c4 j5 e; W( `1 q" ~. P* C" g
      一般而言,时间戳序列作为索引使用。如果想要选出某个子时间戳序列,有两种方法:
      @1 ]2 n0 ^0 J8 @
    0 \: a& {, P* A利用dt对象和布尔条件联合使用
    5 P' s- G" C/ U8 d3 Z; w2 ?利用切片,后者常用于连续时间戳。& M" o8 o, f9 k: h% ~
    s = pd.Series(np.random.randint(2,size=366), index=pd.date_range('2020-01-01','2020-12-31')): Z- v! F% Z$ f% K
    idx = pd.Series(s.index).dt5 ^/ T  t; k3 |) e
    s.head()
    " ]# E* [6 d9 g
    ' s) B  m8 X' \1 F& W2020-01-01    0
    2 i! E, m# z7 R# `* [, k5 a0 g2020-01-02    1
    6 _- P8 q0 A  L! h2020-01-03    18 L. Z0 X+ R2 H8 O. @2 C/ e. F* P; N
    2020-01-04    0
    9 [( z6 v, Z" K# H  d2020-01-05    0; D6 A; V9 P3 f3 Y
    Freq: D, dtype: int32+ p' F  ~9 r, O1 F" X0 W
    1/ g! J$ W' s$ S; H% M
    2; |1 e2 _- W$ h7 u% z
    3
    & |+ ^8 ^; s; \1 G' Y5 U, v+ S4
    * Q* k/ {: X5 ~4 u. u4 q' \0 h, R5
    * S7 l/ ]* S! s) b7 @+ q6
    6 q7 `) }  ~3 w0 w" w% i) r" O78 D  c1 i# S: a6 l8 f
    8, b1 Y6 U0 H3 q) T# e" L9 J( O( `' U
    9
    0 `- H. N! n% q6 d10
    & W- }4 q, n& IExample1:每月的第一天或者最后一天
    5 k- a+ L2 m1 v! b/ W- `' j3 V: g
    * h# K' P) Y! ]9 c6 \1 vs[(idx.is_month_start|idx.is_month_end).values].head() # 必须要写.values
    1 _. W* G/ Y" H, X. a$ T) Z) kOut[50]:
    1 X  n/ h  l7 `9 K8 n, g2020-01-01    10 p8 Q/ J. B" e7 L' B: u: x8 }# \9 d6 u; ^
    2020-01-31    0+ E+ k! q! A) l: r6 {; H" N+ Q
    2020-02-01    1+ W3 k8 l3 [) q6 \. J4 M) A7 Q
    2020-02-29    13 Y3 B. g; L. S8 n
    2020-03-01    0
    7 m9 M4 }, d4 B0 ddtype: int32$ B0 v; z0 n9 h* L' B% j8 ]6 Q
    1
    2 \) p: Z: z1 M; q9 B2
    6 f, l5 G) E( E0 P3' ]! x( ^9 D& v& s  ~
    44 |1 V; c; [+ b" `  t% L
    5% k& @: x- m. U9 @: r$ \# {
    68 ^: r) P  x/ S- L
    7/ h6 p% Y( x* z4 S. y2 X  r
    8  V& z- y# C$ |! F9 C
    Example2:双休日
    : c5 E' Z+ p( Q" [- @
    2 Z! x; v. _- c6 k2 H# ys[idx.dayofweek.isin([5,6]).values].head()
      N% }( T9 w# L2 Z: ?9 _6 MOut[51]: 3 N4 F# m+ W( @! Z1 ?1 M+ K
    2020-01-04    1, L' f2 [7 Z/ b: s, g/ ^
    2020-01-05    06 p; n) Z  u; q
    2020-01-11    04 u7 r: I3 X6 D  {* E
    2020-01-12    1: n3 L( T1 J, T
    2020-01-18    17 b! H% f* z- r/ z
    dtype: int32
    ; o+ l9 a& r/ ?5 o) d3 a: O/ F1' Q* X" h# r% `% Z( t) t3 @
    2& e: S# B, |  l* D! b$ b, X& c
    3
    & A" [2 ~3 _' q5 {9 `: C4
    ! o  o1 _6 _1 n1 e/ _; s5
    5 y' Q3 X& |6 i6  C; l% h" B" K$ r
    7
    0 B4 `5 m8 G9 V3 N) _8
    ' k3 Y6 f* J  S$ sExample3:取出单日值
    8 |, J) J, Y5 W3 A) }6 y5 U2 h8 u$ h& ?/ ?1 E/ ^' a8 C# a
    s['2020-01-01']  v1 w* d$ ^% C+ w" T0 u
    Out[52]: 1
    0 G" z/ i9 M  ?$ R* L( t# i
    ; P; S% E* P) N2 K5 k0 ls['20200101'] # 自动转换标准格式1 w- v) H* ~- E
    Out[53]: 11 u$ I! |1 w; A+ g1 X: p4 o
    1
    7 ~4 V  v# L! O2" D- ]0 x4 b2 L
    3, O) m  @( K/ c' E. e; L1 P
    4" y& x9 Y) c2 d! U& U
    5/ q9 b' Q) U4 `# D
    Example4:取出七月
    3 Z0 ~3 A1 V; \; ?2 A* ~0 v) x5 ?( m( z0 J. ~) S7 V7 L5 g, c  p8 k
    s['2020-07'].head()4 q4 }3 L0 t7 D  |+ f9 {4 A. a
    Out[54]: # _/ M+ z! \' E+ f3 E* m
    2020-07-01    0
    3 X5 u- i7 F1 u/ ^$ S2020-07-02    1
    ; \+ a6 p: g3 L( Z2020-07-03    0
    : h( x5 J6 j' O3 x4 [2020-07-04    0% c* }- W+ O- x& _2 L9 ]
    2020-07-05    0
    6 g; D$ M8 Z6 U2 z2 ?+ {; Q; d. CFreq: D, dtype: int32) g- G5 O/ \6 m. @1 I- a
    1
    $ f0 W2 i9 N* [/ A5 @$ R2 I( ]4 o2
    $ P( M3 |  p5 j" b% S/ w33 h3 ?2 I8 W, E$ D9 U. s" y$ {. A
    40 C+ @1 D8 O& \. }+ n2 K& W7 D
    5. V' c! x* O/ L4 t4 @# S* Q
    6
    & I$ ?2 P9 I$ \7
    , x) Z* j) h' r+ H1 }  O8
    6 @* E1 H( [0 _( t8 b/ y; [+ OExample5:取出5月初至7月15日
    : P0 G9 {5 B; e& c$ m5 q) Y, j) g3 o- ]7 u- ?$ ?- S7 i
    s['2020-05':'2020-7-15'].head()4 _3 r2 m) W4 u9 e, d: v
    Out[55]: 2 @" J7 S1 |" Y8 B& g! s$ u! U
    2020-05-01    0
    1 ]" N) w$ R" F1 l2020-05-02    1
    5 n! q- F, q) h7 k2020-05-03    09 g" C- X+ M3 J0 W9 C+ b5 t
    2020-05-04    1
    ' k/ L7 X8 ], k! [! c2020-05-05    1
    . k* B: o. m# Q# p* Q6 x7 Q: m3 I/ WFreq: D, dtype: int32& f0 q7 g1 c; Q, ?! ]
    9 d6 y  D  D; e; R
    s['2020-05':'2020-7-15'].tail()
    , T0 O- F. n0 N) n) Z) _Out[56]: 4 s7 f. e$ l) @) z" ^
    2020-07-11    0
    $ D3 ]# T( B2 p5 i8 N4 M2020-07-12    04 K) L( L' p- j2 n
    2020-07-13    1  j7 e+ V) ]& D5 W8 w
    2020-07-14    0
    3 O/ i& h' ~2 M! b0 u2020-07-15    1; k7 y" y9 C+ c: R' ^% G' F
    Freq: D, dtype: int32- l1 h5 \- x) h: Q
    ( i+ c3 c4 }  X5 X9 D5 P  s
    1
    6 Y: |3 E- E1 ^5 u! x/ m6 x3 Z2
    & B2 l2 N) v: t& F5 L3
    6 m0 H7 c$ ]1 m& k4( W1 j5 j0 X! U, P( @; p7 z2 v
    5
      J! q1 ?' Y5 o$ ]# B9 f; T$ Y9 V' N6/ h8 T4 E* T; ~  o- I
    70 z5 r) \3 \4 j4 r% {3 m% O# k8 Q
    82 l  u% {* d; D  D6 D1 p% }
    9
    / j1 ~+ B8 B2 K. T% X; n  c10
      ~& J! ~( A$ q- l& @( [1 B& |11
    0 N  V- H- @* C7 Q12
    5 q  V3 k( O: N* _2 N13
    " @) M9 a7 ^# x0 q14" q+ D) B& X3 C; O
    15
    : i2 B0 ?; l/ r# |- [5 c16
    ( P* E+ T  s2 G/ o8 L# h: [: A17
    1 _: }* [( l* i1 h- a10.3 时间差
    ' E+ T$ @  w. f$ |* X& N* i10.3.1 Timedelta的生成
    9 B: d" ]3 B) ?' v/ g% k2 ~8 xpandas.Timedelta(value=<object object>, unit=None, **kwargs)
    . U: h' B6 R) F3 M1 P( ~  unit:字符串格式,默认 ‘ns’。如果输入是整数,则表示输入的单位。* g) {1 X) ^" @$ w
      可能的值有:* }) H1 p+ I$ V2 d$ m7 `
      P. n3 N* U4 \5 L, z
    ‘W’, ‘D’, ‘T’, ‘S’, ‘L’, ‘U’, or ‘N’3 U# |: q" p* }% [6 t
    ‘days’ or ‘day’
    ' Q* A8 p& y: Q. P1 n# K‘hours’, ‘hour’, ‘hr’, or ‘h’0 K( y# O8 ]! _* X( W
    ‘minutes’, ‘minute’, ‘min’, or ‘m’1 P# Q7 M" b/ e# p
    ‘seconds’, ‘second’, or ‘sec’. n) y8 ~  ^7 P6 b5 c
    毫秒‘milliseconds’, ‘millisecond’, ‘millis’, or ‘milli’+ g( f& ^" D- k) t! _& p1 E
    微秒‘microseconds’, ‘microsecond’, ‘micros’, or ‘micro’
    ! y3 P+ Y0 h% k" w1 d; D: H. |纳秒 ‘nanoseconds’, ‘nanosecond’, ‘nanos’, ‘nano’, or ‘ns’.6 S& C+ I& f" w! c+ M" _- E  w5 M
    时间差可以理解为两个时间戳的差,可以通过pd.Timedelta来构造:
    1 c. G1 G) q- I# ~& c$ S) O) k7 Hpd.Timestamp('20200102 08:00:00')-pd.Timestamp('20200101 07:35:00'), V( o8 x1 Y& ~0 ~
    Out[57]: Timedelta('1 days 00:25:00')% W5 Y+ d$ K+ f7 ?# k

    ( J% n2 N5 `% F8 Q+ `pd.Timedelta(days=1, minutes=25) # 需要注意加s
    - ~. W7 |0 V" `/ IOut[58]: Timedelta('1 days 00:25:00')
    2 a% F; \9 R" n) o) t  c
    5 F1 F6 d8 R; U, Z0 P4 R# qpd.Timedelta('1 days 25 minutes') # 字符串生成
    2 _. E6 a9 I& P+ I8 w; BOut[59]: Timedelta('1 days 00:25:00')3 _: S4 L! {. b

    5 n, v" b/ \5 q( I  y, n3 Rpd.Timedelta(1, "d")
    . h2 u/ f$ _3 G0 Y# ], s! }Out[58]: Timedelta('1 days 00:00:00')! I$ w) S4 {6 e4 Z6 L: c% }
    1( Q! U: y5 k. x+ ^6 b+ v+ @
    23 `8 ~$ R8 W. X5 T$ V+ x
    3
    " L0 O% r* R7 d6 T7 F4
    9 X- ?, K- j. `4 t) M6 O5& T/ o+ K" s" j
    6
    ' |6 p$ U% e1 l1 b$ y7 I2 F8 P( ^7
    ' g" Y0 N8 g2 B% Q% V8- r1 t, v% |6 `, @
    9/ H( U6 ^( s$ b) w
    10
    , J' f' l! U. U. z11
    " a! e8 d2 K1 s7 ^- J# E" q3 c生成时间差序列的主要方式是 pd.to_timedelta ,其类型为 timedelta64[ns] :
    ; U0 a9 R' p% Ns = pd.to_timedelta(df.Time_Record)
    + y7 @  j9 O7 `' _; e; [0 {. v' S
    s.head()
    : [/ K. Y2 |# S, Z8 m6 t/ ZOut[61]:
    8 S1 L7 v' f7 X# L7 F" H0 s0   0 days 00:04:34" ?% w5 N- g2 o
    1   0 days 00:04:20& B( L8 ?' g( G0 _3 {
    2   0 days 00:05:22
    3 _: E  Q0 k- n# B$ R3   0 days 00:04:08) Y" F0 g7 L6 B1 s9 a$ K
    4   0 days 00:05:22* p( K- R5 {1 N. H5 ^2 @
    Name: Time_Record, dtype: timedelta64[ns]$ d0 y2 c2 T8 K+ f. p
    1! g! I: v7 X  P% _! ~3 |% V, V& |
    2
    8 k- W& _7 F+ I$ s3' ~+ \& G# t" g
    4+ \9 Z2 |  z: k( R
    5
    $ A  ]# o8 z" [' ^+ X  Y6
    + Y: C7 A! O9 X" |7
      C- N& n2 j% r5 P# ~8
    " t) y! w5 q( [3 D9, |" k; l6 l' F4 e' a; a
    10+ d# \0 U: b( A, W7 M( e
    与date_range一样,时间差序列也可以用timedelta_range来生成,它们两者具有一致的参数:0 T" e$ S& i8 p+ I4 b8 e0 S
    pd.timedelta_range('0s', '1000s', freq='6min')( X. v# a, J- u5 W
    Out[62]: TimedeltaIndex(['0 days 00:00:00', '0 days 00:06:00', '0 days 00:12:00'], dtype='timedelta64[ns]', freq='6T')
    9 I/ l1 z7 Y  h, `- J7 R2 N  P* Z6 K4 x& ]* R
    pd.timedelta_range('0s', '1000s', periods=3)1 L' k( I, C" n
    Out[63]: TimedeltaIndex(['0 days 00:00:00', '0 days 00:08:20', '0 days 00:16:40'], dtype='timedelta64[ns]', freq=None)
    * V. A: N$ B' |3 X8 Z+ W: H1 i1! Y& Z$ t/ Q$ m' g( n+ Y8 Q' \
    2
    2 V' o! y6 V, `8 P2 P5 [& }6 R- @/ R37 T  s  c' F4 f, S4 |5 j
    4
    ' L/ {. X/ y6 \; M6 z( ], k8 _3 y55 _; B/ |* Z* [7 l! T# |
    对于Timedelta序列,同样也定义了dt对象,上面主要定义了的属性包括days, seconds, mircroseconds(毫秒), nanoseconds(纳秒),它们分别返回了对应的时间差特征。需要注意的是,这里的seconds不是指单纯的秒,而是对天数取余后剩余的秒数:
    : i0 R, d" s# O7 }s.dt.seconds.head()
    ) t3 F0 ?: i4 ?1 POut[64]:
    , c/ V9 n7 @0 f  j! G0    274$ q/ {3 J" |5 \0 Z- [( a" F
    1    260
    ) }. l8 r' {' o8 ^8 g2    3222 v, l6 l& a; q2 T
    3    248
    5 @/ g/ U* J7 J/ I, g4    322- [1 u# `1 N2 R- C8 w4 V: o8 }! a
    Name: Time_Record, dtype: int64
    ) ~! E, }/ F% e+ |1
    2 u  \" Z7 K2 A, E! M0 s# l1 O9 H; L2
    ! E# D9 h- V9 v0 w. j6 I3 e& P3
    3 e$ a- n7 E/ P  B( K. r, @! E& o( n! K4
    2 n5 H" e# `' f" j0 q, \5& {- c* v+ {7 x$ r' B( K4 |5 c. {
    6
    / I) x- E; u5 s) v7, h6 D. X" R& R, H, E7 z. s" ]' J& O
    8# Z3 Z: G9 H# U4 F- |
    如果不想对天数取余而直接对应秒数,可以使用total_seconds
    9 L1 D& r- _, @, n9 G  \1 H8 L
    ! D" l2 b- p: j6 w' \: J& q% es.dt.total_seconds().head()2 @6 ]! V% U) U# y
    Out[65]:
    1 z, s6 ~4 N$ c: S: H: J0    274.0
    , ]* X6 N. P+ }* w1    260.0
    1 w" ]# p% a" F- N* t* v  }; t2    322.0
    2 M  |& ]! j* D" m3 j) J3    248.0' N3 [2 a' }+ |1 ]5 v* w/ F5 M1 P" |
    4    322.0* C, Z0 `6 f# w4 u) t" M: p- F  F
    Name: Time_Record, dtype: float64
    2 M4 W* O5 M+ j% R1
    ! ?' I6 i# s* Z0 }$ k2
    7 [0 `4 N: Q" q8 R  o3' Z/ w2 h1 N8 q! k6 ~/ t1 I
    4
    * U- F- y, M1 g" k, N5 z( p5
    7 ]) O$ L# D9 M" Y9 R* y% y6$ W$ `. u$ D$ Q; @7 m! P
    7
    , \' a, Z; k9 r0 A; [86 |1 @- U3 l/ k/ T: U& [( d' N
    与时间戳序列类似,取整函数也是可以在dt对象上使用的:# k3 S5 |3 f$ k* x- ]8 W) F+ ^0 L

    7 P6 E  V  z( ]- g! f2 R/ Fpd.to_timedelta(df.Time_Record).dt.round('min').head()7 i* h, j3 H5 ]: L! S* s4 Q
    Out[66]: ) H8 _5 l: d1 F2 z& z8 H! _9 h) @' W
    0   0 days 00:05:00
    6 a; I% A1 X: G! v5 G7 V  u5 I1   0 days 00:04:00
    ) o  u! j0 H' s5 b5 ~2   0 days 00:05:00( o4 B8 s3 }$ x) Y
    3   0 days 00:04:00, v- j1 r: d) e
    4   0 days 00:05:00. @9 |7 ^' h, k. b
    Name: Time_Record, dtype: timedelta64[ns]
    6 n1 n% y: c1 T* Z$ R7 @1
    & {2 r+ L3 f" [- t7 T# y! L: V2. H& X& t( u; r, j) L8 `
    38 _& {7 v# P1 _% Q* w
    4* B, U5 }% o  o8 z" \* w5 k4 T
    5
    - D2 j# l* Y& u- _9 [  }+ d  W6
    ! K% j5 x2 W& B3 w' d* X7
    * W' e5 K& L' _8 p8- s( a. X* m. a) t! b8 ~
    10.2.2 Timedelta的运算
    3 o' w" Z) L8 |7 q5 |- C" _7 t单个时间差的常用运算,有三类:与标量的乘法运算、与时间戳的加减法运算、与时间差的加减法与除法运算:
    2 g2 }. Z7 E1 ^td1 = pd.Timedelta(days=1)
    + Q/ K( r; u8 P' Btd2 = pd.Timedelta(days=3)
    ! S- o& Y7 {0 e% Z5 N' F6 _5 jts = pd.Timestamp('20200101')! k  a2 n3 b2 M% M
    ! a3 x9 V: i7 {- u4 Z
    td1 * 20 T7 r/ I- f( L, M2 c  f
    Out[70]: Timedelta('2 days 00:00:00')
    ; O& _; Y9 P+ F" d1 @; q6 e
    * ]8 W+ a( }6 b% L6 Wtd2 - td13 D( u/ Z4 x; ?1 L! n& ]
    Out[71]: Timedelta('2 days 00:00:00'): v4 O% R. ?8 n% B( T7 @  Z( F3 Z
    ( d8 @2 |7 I2 C4 i. S" m
    ts + td11 z& Z& |% s% m' \( c; m. q8 q
    Out[72]: Timestamp('2020-01-02 00:00:00')
    ) h% W3 X. |3 s  u
    : R3 T/ n* v0 b9 w* Cts - td1
    . d+ {2 o7 {' j% E+ G# g5 NOut[73]: Timestamp('2019-12-31 00:00:00')
    ( [+ B% E: }5 J* i" C1
    ! W% [. C; [6 o4 D* }- Z2
      W6 d% \3 c4 D+ j37 e9 D& G. `. v
    42 i& s% Q1 |  x0 S( L4 S
    5
    ; s* q# A* [3 a4 O6
    * U7 Y* j9 y; [9 t9 q7
    + ^$ Q0 U5 ~  l9 s. H8& z! H6 o- K. s4 h( S& {0 ]+ b
    9
    * B( S5 \/ U9 t3 _! T+ O4 }9 H10
    ; [8 B' z! f# w! K7 b, T) ?3 j9 z11
    7 h8 X6 z, y5 a% R/ r* {12
    * z. g' c* z9 D% j) c8 E* g5 m13* k( l2 O% @  K: X2 Y. {3 h
    145 c' a8 }3 ^. q3 J- J; r# N- a
    159 _! H8 x5 D, s* n& Y
    时间差的序列的运算,和上面方法相同:' Q3 c, v8 N; c. {$ S% A' y
    td1 = pd.timedelta_range(start='1 days', periods=5)
    0 z" t- ^9 O0 U: |td2 = pd.timedelta_range(start='12 hours',
    0 @# Y, g- k" t, w  o! O                         freq='2H',
    9 d. o8 B; S4 Q                         periods=5)% h+ }5 R2 ^4 _" L- A3 m
    ts = pd.date_range('20200101', '20200105'). i* S% a* h6 u8 {  d% y6 F
    td1,td2,ts
    4 L5 G" t7 K% s! c! I4 v' m( Z; }( e+ W; ^* q. x
    TimedeltaIndex(['1 days', '2 days', '3 days', '4 days', '5 days'], dtype='timedelta64[ns]', freq='D')
    2 O) H! L( n2 J; N# QTimedeltaIndex(['0 days 12:00:00', '0 days 14:00:00', '0 days 16:00:00',& y0 A4 f" r+ T" h
                    '0 days 18:00:00', '0 days 20:00:00'], dtype='timedelta64[ns]', freq='2H')
    + f: |* ?" ~. k: v/ [6 a9 ODatetimeIndex(['2020-01-01', '2020-01-02', '2020-01-03', '2020-01-04',
    ! l  p0 `# w; K. }9 K) ^               '2020-01-05'],
    + Z5 T3 c1 J$ i, r( v              dtype='datetime64[ns]', freq='D')8 p: Q; G7 T! g& q  j) t
    16 `! \  y" R5 n- T
    2& K3 x$ i3 i/ m/ g  V: y
    3
    4 `3 z) `* K. r5 x6 m5 |% T/ O4# K& n* O& S( ]3 h0 _
    5
      G: ^: E/ ]; c. ?! j" V6
    0 ]0 |9 Z5 _. T! C) E7
    1 {! \' ]( g" }. \- F# d8
    . l: T! P3 L" `- n& V" X# _9
    / S% Q7 C2 I+ b( f4 L10
    & i) N$ N+ C5 Q& }* O( b6 t9 V  `  g0 F11
    ) J& @" s; o3 p/ ^6 _- o" i1 K6 a12! b3 _2 W( I  h4 b7 J. G
    13+ ]+ K% f8 H, g+ `; a, `& X
    td1 * 5
    ! r. e& X; G! p1 n( i9 Z. I$ rOut[77]: TimedeltaIndex(['5 days', '10 days', '15 days', '20 days', '25 days'], dtype='timedelta64[ns]', freq='5D')
    0 X; K, t, G8 ^7 D
    2 A+ z& n& u; q. w/ x& Etd1 * pd.Series(list(range(5))) # 逐个相乘
    ' l/ k$ f) L8 r2 c/ l2 {+ a- L9 eOut[78]:
    ) q1 E* R0 k5 G- Q% ?, L$ a( [0    0 days
    + r' U& J% T" H( a+ Q1    2 days
    4 T- K! Q5 h3 J4 B/ [' O2    6 days  p$ ~& A: \8 T4 @! \% n2 M
    3   12 days# l4 \' s3 J) c$ n; Y
    4   20 days
    9 Z  ~  @3 R( k( r; I2 Pdtype: timedelta64[ns]
      d$ A' F" F$ ?! {. d% d
    # M% T( z+ _' Q% Q, m: m# }td1 - td2/ V* Y5 s  I: o
    Out[79]: , `# Q. u, V- O0 w$ ?4 W8 \
    TimedeltaIndex(['0 days 12:00:00', '1 days 10:00:00', '2 days 08:00:00',
    * ^, w+ M: L1 ~  C/ [6 a. A8 ?                '3 days 06:00:00', '4 days 04:00:00'],% C& k( g1 R; u1 h( P, p
                   dtype='timedelta64[ns]', freq=None)/ v! j( {0 g2 i4 ?( W0 s
    4 g  f+ d1 w6 S
    td1 + pd.Timestamp('20200101')7 r" S, a! i: w. o0 o
    Out[80]:
    : ]6 X, F" d8 P/ L) {DatetimeIndex(['2020-01-02', '2020-01-03', '2020-01-04', '2020-01-05',* m! Z' {, J! z  U
                   '2020-01-06'],dtype='datetime64[ns]', freq='D')
    - _- N4 m! c1 |7 ]) S3 w' {$ l" k  N- {5 z" f  V9 a
    td1 + ts # 逐个相加* U4 N( a& z3 G8 F2 R0 O( ^9 h
    Out[81]:
    7 l) I: W4 h$ V+ B5 m7 ?( BDatetimeIndex(['2020-01-02', '2020-01-04', '2020-01-06', '2020-01-08',( c4 G% P+ s2 A0 ]% M$ E
                   '2020-01-10'],) ?7 k3 H; ^8 L. j+ Z- z. A7 s+ c
                  dtype='datetime64[ns]', freq=None)5 e( ^$ f2 Z+ x/ [$ B& {( L4 q

    3 W- R6 Z4 ?: h% x" w1
    0 h. n4 ]: H# V. n2
    1 _  N2 J  o/ B* i33 j+ @5 g( R5 E) a; Z& l! @
    4
    3 J* K0 Y  o* L% b5 N7 |+ L) L5
    ) L4 }2 {9 k2 }# a69 W4 j5 ?5 ~# G! V
    7
    ) n( j( A4 \+ |4 `8/ E9 x  _! X# N  e0 ~# K
    9* D$ v. J$ r" `/ U$ x; U
    10
    4 Z, P4 s7 [% G11
    , ~/ }. t1 z" s( U) ^8 u0 z  M# s12
    4 @2 M- b+ n2 z. ^: m13$ V* j( R# N4 H- ^9 ?
    14
    ' X) l- n* i7 A15
    3 W; Y. R( _9 h5 ]3 E16
    ( R7 V, i0 h5 R; |! l17
    , S& J1 V; V) P7 T- E; a18
    " R. D* E) q" F) g# v19- K5 X) t+ a7 q- r
    20
    % a- o8 M+ H( |' j3 h5 n: K# j21
    0 Q$ n" V2 r8 r2 p0 V" k22
    ! }0 _4 O' ~7 T- I- S23
    " ?3 N! r& u" c. o24& w" G! W# p, N1 V2 P
    25& `  l, G6 B. z7 H
    26
    3 ?+ `+ b5 K" u) V272 ]! i/ a* ]+ O% B3 C
    28: J- N. v* o; j1 v* @
    10.4 日期偏置( T! m  [+ N) {
    10.4.1 Offset对象* o) [% n' ^6 e: Z( G2 t7 v
      日期偏置是一种和日历相关的特殊时间差,例如回到第一节中的两个问题:如何求2020年9月第一个周一的日期,以及如何求2020年9月7日后的第30个工作日是哪一天。, w1 o# [( J* T9 Z! E& f( S
    $ U" y  L. W3 u) n$ w
    DateOffset 类有10个属性,假设s=pd.offsets.WeekOfMonth(week=0,weekday=0),则:
    + w2 j( k1 p" x$ P: S
    6 C, j$ q" _# g4 G0 Bs.base:<WeekOfMonth: week=0, weekday=0>,返回 n=1 且所有其他属性一样的副本1 L/ N& }2 c' G2 v( B
    s.kwds:{‘week’: 0, ‘weekday’: 0}! Q/ d5 N9 N5 ~7 K
    s.wek/s.weekday:顾名思义# |  E5 ~0 L$ k1 Z2 {
    有14个方法,包括:$ H& R4 T, r* @# c  B# M
    3 s' c7 E- n  T4 v9 o7 h4 z
    DateOffset.is_month_start、DateOffset.is_month_end、DateOffset.is_quarter_start、DateOffset.is_quarter_end、DateOffset.is_year_start、DateOffset.is_year_end等等。
    0 O2 V/ R. i' }* G0 Z2 lpandas.tseries.offsets.WeekOfMonth(week,weekday):描述每月的日期,例如“每月第二周的星期二”。- j* m, B: r  Y$ P
    1 ?& F4 A, X( I8 b
    有两个参数:, C2 L, N% Y" z0 f, u
    week:整型,表示一个月的第几周。例如 0 是一个月的第 1 周,1 是第 2 周,以此类推。/ ~2 U/ |+ n) Q3 ^8 t" v$ n
    weekday:整型,取值为[0,1,…6],表示周一到周日,默认取值为0(星期一)
    , q3 t" `* s% l5 Wpandas.tseries.offsets.BusinessDay(n):相当于pd.offsets.BDay(n),DateOffset 子类,表示可能的 n 个工作日。
    : g) ~6 x8 G- j
    4 [2 P" m$ _/ a+ a2 Z  X0 {pd.Timestamp('20200831') + pd.offsets.WeekOfMonth(week=0,weekday=0)
    4 T$ j: ~2 ]3 R; E! E9 ^' LOut[82]: Timestamp('2020-09-07 00:00:00'); @4 }- P; ?; ~! q

    ! S  `* @# n' K) _1 Tpd.Timestamp('20200907') + pd.offsets.BDay(30)# S9 }' W1 f. z  Z" a
    Out[83]: Timestamp('2020-10-19 00:00:00')1 B& L2 _" |& B1 h
    1
    # O% r" }# F6 h# `9 C9 V9 `2: X0 @& y5 K5 `) |1 Z" V
    3
    6 ]  x/ j. c! _; w' r& ]& P0 n8 C4  n; p7 o! q4 Y4 i6 Y+ Q" F# j
    5! N) M" L% `# m$ ^2 z/ U3 i9 i
      从上面的例子中可以看到,Offset对象在pd.offsets中被定义。当使用+时获取离其最近的下一个日期,当使用-时获取离其最近的上一个日期:
    2 \2 Q( T9 ?2 g& c( ?3 ]1 |/ [3 K4 t+ t3 e8 q5 D
    pd.Timestamp('20200831') - pd.offsets.WeekOfMonth(week=0,weekday=0)# T1 o7 ?- _1 f# ?# u2 ^! N
    Out[84]: Timestamp('2020-08-03 00:00:00')
    % D9 o7 t' r1 H& W; @9 @  D7 ^
    / t* ?  R  k& _7 V; Z' Apd.Timestamp('20200907') - pd.offsets.BDay(30)& z. w: T/ `9 R2 y+ E
    Out[85]: Timestamp('2020-07-27 00:00:00')
    & Y) e2 z* r9 T. k& v# D6 [
    . l/ S& ~% Y- ?9 z6 tpd.Timestamp('20200907') + pd.offsets.MonthEnd()* C0 h7 H) C2 ?4 l
    Out[86]: Timestamp('2020-09-30 00:00:00')! X2 T5 W5 o7 d' p
    1
    4 [! b  ]' @! L# E1 y9 N2* A: \7 a8 O5 z+ J' N
    3: K1 l# S. n* T2 y& H6 J9 k& D
    4( O( I4 e. R1 q8 T/ F8 a" B
    5
    6 ]' Z2 _& F4 S) p8 W: N6$ v+ d+ S' X; R7 J4 ^
    76 b6 }8 ^9 x. S- s6 g8 q! @
    8
    % M) |0 j* D( ]6 N! e$ P# d  常用的日期偏置如下可以查阅这里的DateOffset 文档描述。在文档罗列的Offset中,需要介绍一个特殊的Offset对象CDay。CDay 或 CustomBusinessDay 类提供了一个参数化的 BusinessDay 类,可用于创建自定义的工作日日历,该日历说明当地假期和当地周末惯例。) c  F" c5 T# i' S  r
      其中的holidays, weekmask参数能够分别对自定义的日期和星期进行过滤,前者传入了需要过滤的日期列表,后者传入的是三个字母的星期缩写构成的星期字符串,其作用是只保留字符串中出现的星期:
    . B* ?1 X5 K2 e) `2 V$ m5 ^  M- @
    5 O6 Y7 i$ @6 N9 U) b$ P% E7 `my_filter = pd.offsets.CDay(n=1,weekmask='Wed Fri',holidays=['20200109'])
    . i' U: W/ e9 Adr = pd.date_range('20200108', '20200111')
    " `& ^/ l. g; L, ^9 A6 ?# k; P! A9 n& F7 N# B
    dr.to_series().dt.dayofweek4 s: Q( m; @6 c$ Y+ D" g
    Out[89]:
    ) [/ {7 c% f- o2020-01-08    21 r, y4 c2 a7 z1 B$ m% U9 n! q* Z
    2020-01-09    3
    ! E7 h. m/ S+ F/ i4 L6 c2020-01-10    4
    4 t) k5 \3 H; B, u2020-01-11    5% X/ p  v1 ~& L' ~" Y
    Freq: D, dtype: int649 {, G( M0 |7 L
    8 Z: p0 G* h4 L4 m1 w. _9 b1 o1 R
    [i + my_filter for i in dr], c8 \  `  z% x- d) ]3 ^
    Out[90]:
    4 r3 o7 f) Q* g2 K[Timestamp('2020-01-10 00:00:00'),
    + k% d6 {  Z6 i Timestamp('2020-01-10 00:00:00'),; o! e# J) U1 c% j
    Timestamp('2020-01-15 00:00:00'),
    5 z4 h7 i/ ~% E! l' [) r Timestamp('2020-01-15 00:00:00')]' p6 L2 P, S9 \6 P6 o- [' D/ W

    ! p; Z; z: }+ S% N& ?- z" i1
    1 y4 ?  U& x- K5 c8 l2+ Z9 R& @3 a, E$ A
    3  B& i& A" L; ~. D0 S
    48 d' B" |# N* b, |: |& e% v
    5/ g5 K/ M) _2 `& z# U
    6, o# P4 a6 C, J% L# D
    7
    / [4 l. `& h5 G, I! y5 a% r8
    + u# a3 A& v0 |8 @/ l6 g% @9
    " x1 R7 N; _  q10" a5 w4 L5 \+ p) W- w' U. u
    11# u2 _/ V! z. N% f, F$ L8 z
    12
    / w: ?7 i: P0 k% Q1 n0 J2 B: b13( `; A. L9 t) S2 u6 g5 A" K
    14) ^% _8 \* o4 p% ^
    15
    * H8 n  z3 a5 m8 |3 m162 D! ]# j( U2 h5 @. a# n( G/ e
    17
    , |( H# o8 N9 D6 B  u8 X  上面的例子中,n表示增加一天CDay,dr中的第一天为20200108,但由于下一天20200109被排除了,并且20200110是合法的周五,因此转为20200110,其他后面的日期处理类似。
    $ L, c2 [9 q. L2 M+ f* k2 S& d2 }$ @; Q: H
    【CAUTION】不要使用部分Offset5 s) V5 x8 g3 [$ M8 I8 B
    在当前版本下由于一些 bug ,不要使用 Day 级别以下的 Offset 对象,比如 Hour, Second 等,请使用对应的 Timedelta 对象来代替。' F- y. }! @5 d; r7 Z+ P7 t6 f$ T$ [
    1 ?+ y0 l) U; D
    10.4.2 偏置字符串  O/ \3 O) D5 ~; L( r
      前面提到了关于date_range的freq取值可用Offset对象,同时在pandas中几乎每一个Offset对象绑定了日期偏置字符串(frequencies strings/offset aliases),可以指定Offset对应的字符串来替代使用。下面举一些常见的例子。" p% X9 B# |$ }% w3 n

    4 Q/ T7 E9 O/ K! \/ p3 |  Offset aliases:pd.date_range函数中的freq参数,为常见时间序列频率提供了许多字符串别名。 也称为偏移别名Offset aliases。偏移别名列表点此参看(大概27个)。% O5 C7 W" t, \
    1 R' N0 Q3 h: ~* d7 i/ a5 h+ I) L
    pd.date_range('20200101','20200331', freq='MS') # 月初
    ! m2 A" E& A, p9 ?3 HOut[91]: DatetimeIndex(['2020-01-01', '2020-02-01', '2020-03-01'], dtype='datetime64[ns]', freq='MS')! G8 \8 g- V7 z" [
    + h$ n& i6 U- G9 j9 b
    pd.date_range('20200101','20200331', freq='M') # 月末
    4 |" u9 I3 T% Z9 ]Out[92]: DatetimeIndex(['2020-01-31', '2020-02-29', '2020-03-31'], dtype='datetime64[ns]', freq='M')
    0 }; o# W$ E1 E; R8 r
    5 E1 S: q+ R( S( K' b; ppd.date_range('20200101','20200110', freq='B') # 工作日
    8 x- j, U) i6 d7 n8 F" i6 DOut[93]: ; _% h1 j4 @5 i& V' Q
    DatetimeIndex(['2020-01-01', '2020-01-02', '2020-01-03', '2020-01-06',, F( ^2 B% \& [7 f* f7 s
                   '2020-01-07', '2020-01-08', '2020-01-09', '2020-01-10'],
    # c/ j3 Y  y  r              dtype='datetime64[ns]', freq='B')" `0 _2 u* W/ m: s/ s

    " k! u8 z! ]* ~, Opd.date_range('20200101','20200201', freq='W-MON') # 周一* K. {  r/ H9 ?2 _
    Out[94]: DatetimeIndex(['2020-01-06', '2020-01-13', '2020-01-20', '2020-01-27'], dtype='datetime64[ns]', freq='W-MON')
    & G" d# l! z/ L. C4 [# Z! E  ?/ S$ F" H/ [" ]: Q. t
    pd.date_range('20200101','20200201',: X3 L8 G& O" Z! b7 V
                  freq='WOM-1MON') # 每月第一个周一" ~( \# W- P  j: J% }

    & x6 r- ?# V$ W* j6 xOut[95]: DatetimeIndex(['2020-01-06'], dtype='datetime64[ns]', freq='WOM-1MON')
    # g$ P8 ^+ `' v. l) T" L5 H
    ' C% J& g/ \# c) f% Q0 F1
    5 O6 i* v6 E" ^: n' @2
    ' _9 l1 F/ I6 d' w: S35 F$ j0 Z! ]+ W* D$ M1 N
    4! H+ P+ i- N% l
    5
    4 R  j0 t$ a# `- p( f7 r+ `1 W* `6
    6 O* q9 `; f# Y6 e& X7
    9 X; B; `+ u. U9 N( f8
    # z( Y4 q9 w0 p, ^& l9, U' M: ?3 b. L& D0 T/ `* s' ?3 o
    10
    6 o6 q3 A7 x$ R* b' R* a: J7 p, s2 }11
    1 a# Q$ v9 J6 Z+ m- x12; P! {/ ~$ c7 C, R$ R9 o# E# s' ?) ^
    13/ ~5 O/ j+ l9 \
    147 R4 \7 C. N: M) m8 N% W/ t' \) k
    15; S/ P" z( f3 w6 }4 b5 O- k
    163 e9 l8 b# O- r3 P3 P
    17. B2 C; {% M4 _6 j& O
    18! |9 k4 e# B8 g* p9 j
    190 t7 o. V0 G. l6 C
    上面的这些字符串,等价于使用如下的 Offset 对象:
    ! W' C5 J1 j+ t: O: C5 c  @2 s6 R) b) Y0 F9 c8 |
    pd.date_range('20200101','20200331',
    7 Z2 v( I9 b- [& u2 }. \( `              freq=pd.offsets.MonthBegin())0 G7 Z. {3 N$ j4 Q" b

    + [% n! L! `+ cOut[96]: DatetimeIndex(['2020-01-01', '2020-02-01', '2020-03-01'], dtype='datetime64[ns]', freq='MS')! d) z+ S4 f9 r/ W
    ( _3 u* K, V5 M8 g0 M
    pd.date_range('20200101','20200331',4 h$ [: E! E6 t, L" d& P
                  freq=pd.offsets.MonthEnd())4 l7 W5 d/ p0 s0 f7 h# }
    9 j3 R8 E( c9 |
    Out[97]: DatetimeIndex(['2020-01-31', '2020-02-29', '2020-03-31'], dtype='datetime64[ns]', freq='M')
    ; t: y. V- ~5 e
    % q& J; v, v$ c+ {pd.date_range('20200101','20200110', freq=pd.offsets.BDay())( @% {* r- h- M
    Out[98]: 4 t7 B. J. b( t6 h. C+ _+ p5 b
    DatetimeIndex(['2020-01-01', '2020-01-02', '2020-01-03', '2020-01-06',
    3 r5 `5 I9 [- P  g, f               '2020-01-07', '2020-01-08', '2020-01-09', '2020-01-10'],
    ; M* S) i, y  D8 ~' n, Z              dtype='datetime64[ns]', freq='B')
    - Z4 d6 h+ |  x& r5 F2 P2 P7 X
    # r( Y9 y/ |! z6 fpd.date_range('20200101','20200201',
    / L3 w! M+ O# d  o  l* W. h              freq=pd.offsets.CDay(weekmask='Mon'))1 e! ]1 q8 A8 a; m
    : [" ^' |4 M0 o7 t6 W0 `6 x
    Out[99]: DatetimeIndex(['2020-01-06', '2020-01-13', '2020-01-20', '2020-01-27'], dtype='datetime64[ns]', freq='C')
    * n  Q8 z1 j: o% a6 l/ j- C  q- C
    2 B7 I3 @. E. ~- \. ?9 apd.date_range('20200101','20200201',- i* J6 J+ |6 j! [9 {
                  freq=pd.offsets.WeekOfMonth(week=0,weekday=0))
    0 s0 A* U& y9 ?2 {! P6 K: @: L! c8 q, `' [% b5 T
    Out[100]: DatetimeIndex(['2020-01-06'], dtype='datetime64[ns]', freq='WOM-1MON')
    6 |; t% k, i% D4 x: f; E+ p, ~& e# t1 G
    1/ P9 v7 e  B0 {% z2 D/ ^
    2
    6 F' P- r( T5 }7 V4 W# K. C31 c" ~2 J2 _% v: e+ P8 r) V
    4. Q% `8 z# b4 _3 ~3 q
    5
      D) E; t2 j% ^) P6
      n( U# M% q) A7
      m$ W6 A8 P2 y5 o8
    $ \# u/ ]5 t) j5 J* w% U9* e, L# w* _% z( x4 p
    10* t2 g7 t1 W: `
    11: ~& p5 U& A; S  d7 l
    12
    - j  B/ x& M. k* _, y) V8 c13. D: T+ T1 T3 F# c# K
    14& ]. ^- \1 N- E
    15- b: s/ J8 H5 K8 @
    16
    % j4 b! H3 n+ e17
    ( W9 q/ U, q. Y18
    " m. m# M5 h) Q% y19; S% Q& O, U( y& C# ~8 s6 L
    20
    - P5 m6 c! `4 M, }$ W21% Z  m- b( o9 k% ^
    22
    3 i, C7 E7 H* W23
    7 _0 S8 F  r+ f% r24; N- O5 H8 n" {% r/ ]+ e
    25
    ) G! t; n" I* L8 @: H5 J0 F+ @4 R【CAUTION】关于时区问题的说明
    6 r9 x% F( ?: q  各类时间对象的开发,除了使用python内置的datetime模块,pandas还利用了dateutil模块,很大一部分是为了处理时区问题。总所周知,我国是没有夏令时调整时间一说的,但有些国家会有这种做法,导致了相对而言一天里可能会有23/24/25个小时,也就是relativedelta,这使得Offset对象和Timedelta对象有了对同一问题处理产生不同结果的现象,其中的规则也较为复杂,官方文档的写法存在部分描述错误,并且难以对描述做出统一修正,因为牵涉到了Offset相关的很多组件。因此,本教程完全不考虑时区处理,如果对时区处理的时间偏置有兴趣了解讨论,可以联系我或者参见这里的讨论。# y) Y3 K: }7 }
    % _0 @2 q+ K* X4 x  f' L% c
    10.5、时序中的滑窗与分组2 Q- Z* |& h+ t8 i, s
    10.5.1 滑动窗口: c) V. ?9 p; D$ U& z
      所谓时序的滑窗函数,即把滑动窗口windows用freq关键词代替,下面给出一个具体的应用案例:在股票市场中有一个指标为BOLL指标,它由中轨线、上轨线、下轨线这三根线构成,具体的计算方法分别是N日均值线、N日均值加两倍N日标准差线、N日均值减两倍N日标准差线。利用rolling对象计算N=30的BOLL指标可以如下写出:
    ( u6 T) s) t9 Y: M0 l- Z
    ( O2 q0 y) d/ n: U' V/ W/ nimport matplotlib.pyplot as plt
    " _$ r+ N" c' [! Midx = pd.date_range('20200101', '20201231', freq='B'), D) ?  ^6 @4 K; q
    np.random.seed(2020)
    3 q! n6 v- W: y. j. M! k- L. E; z/ }8 @/ {: M2 a
    data = np.random.randint(-1,2,len(idx)).cumsum() # 随机游动构造模拟序列,cumsum表示累加
    4 p, H8 s, u& s- j9 As = pd.Series(data,index=idx)# p, u( ~4 J  m8 G  G0 Z( o# R2 M
    s.head()
    4 j/ {/ ^9 H3 M# D' WOut[106]:
    . x* r; o  h$ w  x2020-01-01   -1% z  I( S( v6 Z/ A
    2020-01-02   -25 g5 P4 ^) |! a+ n: a, m
    2020-01-03   -1" N% U6 g- }$ k1 f: k! z+ R
    2020-01-06   -1) `# v& X1 q" j5 G+ a! T9 H
    2020-01-07   -2
    0 U( Z3 m, ~- g( o$ H/ [* h8 ZFreq: B, dtype: int329 \2 O1 Q* d. w/ Z, \2 u
    r = s.rolling('30D')# rolling可以指定freq或者offset对象. w, x$ ], \8 f# [: {

    , Q& W) N6 _9 V( l3 ^plt.plot(s) # 蓝色线) v4 {3 \* |. h8 u; w2 ]
    Out[108]: [<matplotlib.lines.Line2D at 0x2116d887eb0>]
    0 G. u' H2 Z3 K2 Rplt.title('BOLL LINES')
    # L% r( n! ]# VOut[109]: Text(0.5, 1.0, 'BOLL LINES')
    6 K* N0 v! z) H7 B! N2 U" A
    8 s/ h! d/ b. {* G# Vplt.plot(r.mean()) #橙色线3 b* f7 Z* i" y; B5 {' c
    Out[110]: [<matplotlib.lines.Line2D at 0x2116d8eeb80>]1 F$ b0 E% L8 J. ~

    & v) U/ _0 ]" [' n8 E7 w( Q0 rplt.plot(r.mean()+r.std()*2) # 绿色线8 Z# V1 Z7 I* k2 y8 l- ~
    Out[111]: [<matplotlib.lines.Line2D at 0x2116d87efa0>]( W# g2 N$ q7 O1 u* l* e% h

    + v' d8 z6 z1 d; X9 Fplt.plot(r.mean()-r.std()*2) # 红色线' G. q, C& \% Z; o' ~
    Out[112]: [<matplotlib.lines.Line2D at 0x2116d90d2e0>]) r, a$ C+ ]7 X  V. r0 ]

    9 e/ Y( q( S# _0 K5 Y* j. P1
    * M/ U# h6 w" t6 Y* z8 p2# G/ N9 {; |3 L/ f9 k
    35 {% v" k: G: c& {" c
    4  E6 B4 J' `" c3 y8 Y( r0 X9 ?
    5; q2 Y: Q  H8 h; V/ s& z
    6# f" @+ T* Q2 M
    7
    # r' H' j6 F' s$ L8* [: E7 }5 S/ c9 O
    97 p# o  Q5 Y0 t! v8 w
    10
    " t: `- U$ ], A3 h; I/ x11
    - o; }1 \5 T. q' X12" |/ N, a' p% C7 }& a6 W2 _
    13
    5 A- V1 L0 s1 e, s7 h14
    5 b% j) E! z& N. |# Q) f- t159 }7 @9 u3 K/ M8 p
    16( o* N% b6 t+ A8 ~
    17
    $ v8 m/ J/ P2 L. i$ |18
    2 a) \- C& m! z1 t; T5 Z; {19
    , S) h0 P9 C. y* O* }3 C20
    - J. ?* I# u' |, v* P$ O. @21
      `* R+ D: K5 I" R+ B; ~+ u22
    # W" ?) H& @- u23! Y. `0 V5 j5 F. S. \6 q& N( R
    24* ?- [/ [' t/ F9 N# T( Q# W& X
    25
    3 G5 C" f, ~5 B. @/ _  [9 A# g3 z266 Y, {7 U9 U/ t/ |& s
    27% s( d) i6 N4 G
    28
    " ~% B# C; L6 t; z* ]# J29; n4 {# ]5 R2 c: {6 U
    ' d( l# h9 [- y3 q
       这里需要注意的是,pandas没有实现非固定采样频率的时间序列滑窗,及此时无法通过传入freq字段来得到滑窗结果。例如统计近7个工作日的交易总额。此时可以通过传入多个函数的组合来实现此功能。
    . O7 b6 w9 z6 v3 t" d0 ?, k   首先选出所有工作日,接着用普通滑窗进行7日滑窗加和,最后用reindex()恢复索引,对于双休日使用前一个工作日的结果进行填充。
      ?3 f) J; ]; I
    9 K0 R& E2 ]* l1 Xselect_bday=s[~s.index.to_series().dt.dayofweek.isin([5,6])]0 n/ o' C, [9 ~/ I
    bday_sum=select_bday.rolling(7,min_periods=1).sum()7 z- H- v7 N& [. A; p* |) X1 S
    result=bday_sum.reindex().ffill(); t+ `4 m" P4 ^
    result
    : ]4 N- R" r% Q8 h
    - F5 }+ h, x1 L2020-01-01     -1.05 |. o+ B6 N! p# g- A/ a
    2020-01-02     -3.0" Y8 K) O, Z/ I* V) C
    2020-01-03     -4.0
    4 o4 u; Y# g: W9 d+ c2020-01-06     -5.0
    - F1 G3 T( b- P& e. t2020-01-07     -7.0
    # i' P* K8 [7 g4 V. ^7 s& J              ...  
    ) t- \( s% j& p2020-12-25    136.0
      R' K5 m, s1 {3 c! ?2020-12-28    133.0
    * v6 c: @: u% x# U! J2 w, P2020-12-29    131.0
    0 F; z+ M2 S3 ~1 A* j2020-12-30    130.0
    . p& ?$ h6 l& E% O( n& `4 j2020-12-31    128.0
    * c( w7 W3 u6 N  b+ m, G& JFreq: B, Length: 262, dtype: float64
    , E+ N1 I+ C3 g5 C& y+ B
    ( k9 {/ y! j4 D, s3 |% F  k5 j1' N4 c* b# s% M% @# u: q
    2
    + q2 a) j& y, ~* G* h30 r' I3 R: R! }9 [& V1 X! [
    4) X2 p) d, r% n1 \* P2 ]9 c6 O6 s& e
    5
    6 _7 Y% I! T& H+ B60 l6 V% s! \  G! I. E, W
    72 ]. |2 E0 G7 [' V5 |
    8
    4 S3 `% d$ r0 ?' i5 B5 ~9
    0 C1 q" _  z& o& J/ |10
    ) q8 X! o% c- ?7 G8 D4 }0 e11
    , k  x+ L# |7 y8 r12
    * z- y+ z/ q. {4 v; x13
    ( A- H* i# y4 t+ ^14
    6 s7 j/ k$ {& ?+ P3 X; Z( B15  c* Y. m0 ^4 l1 `9 r& A) n' y# R% u, W
    16
    . \0 y5 |# o- [8 U; L: Z17
    # ?$ H, B. Z( {  shift, diff, pct_change 是一组类滑窗函数,它们的公共参数为 periods=n ,默认为1,分别表示取向前第 n 个元素的值、与向前第 n 个元素做差(与 Numpy 中不同,后者表示 n 阶差分)、与向前第 n 个元素相比计算增长率。这里的 n 可以为负,表示反方向的类似操作。
    + D3 a3 v; s5 O, T" s# \  f: v7 O: _. `4 M2 J8 ~$ X8 O
      对于shift函数而言,作用在datetime64为索引(不是value)的序列上时,可以指定freq单位进行滑动:  Z9 ^/ \1 w7 V% [0 \$ a
    ( W4 v8 z- M* ]. G
    s.shift(freq='50D').head()
    0 i3 y( y% b7 b4 U: t+ mOut[113]:
    8 Q$ w$ z2 u/ N1 \! `2020-02-20   -1! g3 x4 ]% S% y
    2020-02-21   -2, j* E5 n6 [( u) ~
    2020-02-22   -1
    " _: D3 l3 E  T9 r* v8 q% F2020-02-25   -17 h' v+ F/ |+ f+ _+ E
    2020-02-26   -2
    * e) j) q1 ]6 Y! t9 E6 Idtype: int325 ^: M1 ^4 V( c
    1
    ( w9 {5 H) H" E2 l+ X2
    2 I8 T8 w) ]3 ~31 W) @9 I7 i& k9 k/ l5 D, v* ?# a
    4- e9 @& u) k& r% _0 z& Y# n
    5
    6 [' g* w' ]; I7 b9 y3 y/ A# i# O62 H& r% p% S) u0 h$ _- b0 }1 u4 T3 u
    7# m& ]3 k- l8 ]) \3 O3 S2 t7 H
    8
    : i; C9 t  F* o; d5 U  另外,datetime64[ns]的序列进行diff(前后做差)后就能够得到timedelta64[ns]的序列,这能够使用户方便地观察有序时间序列的间隔:4 }. p9 }! S$ I0 i/ O* O
    - P4 R0 h8 s8 L
    my_series = pd.Series(s.index)" W/ {7 A+ d  D1 c
    my_series.head()
    3 D+ R6 ^" J% i! _# wOut[115]:
    : M3 @1 }- s2 c  a+ Z+ K0   2020-01-01
    & G. f  h, A; h1   2020-01-02' N0 |: @$ B1 ?' w& e: W
    2   2020-01-038 i6 Y3 T5 k: b4 t+ a4 E6 K
    3   2020-01-061 G0 u! m" i3 b+ @, {) ^
    4   2020-01-07
    : x- ]3 i& o8 u+ X: Ldtype: datetime64[ns]8 p9 X5 r) l5 {$ B; ^  l4 u

    5 M" j  y3 D9 fmy_series.diff(1).head(): C) x- Y0 v* p6 K6 {
    Out[116]: 4 Z% H. @# v! c- V, C( ~
    0      NaT
    6 \- W( ^0 [) J, l9 m1   1 days8 G6 f# w& V  {7 a
    2   1 days
    * ~3 p9 a' B1 Q. O0 B2 \. S/ [  P3   3 days
    % i6 w# i0 ]2 A0 O+ ^4   1 days
    3 Q) ]/ f! N# a+ w; m4 @0 _dtype: timedelta64[ns]
    # T* ?+ U( O6 f: j, ?) d# M
    6 h+ Q+ C4 b1 y7 P" ~8 b17 @* C- R2 }* i* q2 D
    2) Y7 R; R" p  g9 @
    32 u3 R  |$ ~5 K8 R; ~) _
    40 s- P) J8 ~0 m; J5 `) t
    5
      H5 Q! m1 h5 U1 B# j- E. b6+ i" F6 {2 ]/ Q/ Q8 x
    7- L' I% Y- x1 J
    8; ]* \0 @3 F$ e4 I  l, B8 o  o, |
    9
      F! p0 E4 `; P: w* \10$ x( M9 S; k. o3 S% m
    11
    - E- b# V- {) x  @& ^12
    ) Q" f, }; M! i1 L" C6 c- ]4 m13( a4 K: R# ?: ^9 I
    14
    ! S0 a* A/ w6 v$ y8 K15! M9 G) P$ X, ^1 o3 f
    16
    $ _' O/ I% P9 r. S17
    7 u+ P; Q8 j2 n) L  t180 z8 H! K/ w" f; Y: _+ k" g
    10.5.2 重采样8 r* U! I; J: i# O  E1 @* N) d
      DataFrame.resample(rule, axis=0, closed=None, label=None, convention=‘start’, kind=None, loffset=None, base=None, on=None, level=None, origin=‘start_day’, offset=None)8 v5 @' X. C) r7 |7 A
    常用参数有:& H5 K0 l: g0 u3 ~9 r* s/ A

    , ?$ e4 m" p! ~% |+ o) l' j* Grule:DateOffset, Timedelta or str类型。表示偏移量字符串或对象, t, G7 S6 j. P( C
    axis:{0 or ‘index’, 1 or ‘columns’}, default 0。使用哪个轴进行上采样或下采样1 c2 p/ o" f5 k1 @
    closed:{‘right’, ‘left’},默认None。表示bin 区间的哪一侧是闭合的。所有offsets的默认值为“left”,但“M”、“A”、“Q”、“BM”、“BA”、“BQ”和“W”均默认为“right”。- A$ f9 O: w4 V8 i
    label:{‘right’, ‘left’}, 默认 None。hich bin edge label to label bucket with,所有offsets的默认值为“left”,但“M”、“A”、“Q”、“BM”、“BA”、“BQ”和“W”均默认为“right”。# W( d) w* @( v) k# q( d, J
    convention{:‘start’, ‘end’, ‘s’, ‘e’}, default ‘start’。仅针对 PeriodIndex,控制是使用rule的开始还是结尾。
    , ~9 o0 {' w9 V/ D$ N# ]on:字符串类型,可选。对于 DataFrame,使用列而不是索引进行重采样。列必须类似于日期时间。
    * \6 a. M3 \6 \level:str 或 int,可选表示多重索引MultiIndex的级别,这个级别的索引必须类似于日期时间。
    7 o; X/ j* ~5 q& T) u, Xorigin参数有5种取值:5 y. D& ~, U( C0 f2 G$ k" K
    ‘epoch’:从 1970-01-01开始算起
    # c: ~+ O/ B+ b1 X+ j2 U‘start’:原点是时间序列的第一个值
    " Q  q5 s+ _, K; R+ V‘start_day’:默认值,表示原点是时间序列第一天的午夜。/ z- p5 `* i0 K
    'end':原点是时间序列的最后一个值(1.3.0版本才有)( r" `! r2 r, {& [
    ‘end_day’:原点是序列最后一天的午夜(1.3.0版本才有)
    $ O, J% H# c8 ]- O- n9 T, yoffset:Timedelta 或 str,默认为 None,表示对时间原点的偏移量,很有用。
    # s* T, C+ S. n' ]) N  closed和计算有关,label和显示有关,closed才有开闭。
    5 {; f1 @( r) e  label指这个区间值算出来了,索引放区间的左端点还是右端点,closed是指算的时候左端点或右端点是不是包含。
    # z, {$ s& I8 D0 H
    3 }/ l$ W8 ?; j' \重采样对象resample和第四章中分组对象groupby的用法类似,resample是针对时间序列的分组计算而设计的分组对象。例如,对上面的序列计算每10天的均值:
    ( R9 e! C1 y) Q7 u% [8 y4 Ks.resample('10D').mean().head()! f! H  z0 H  {& j3 E2 ?5 y6 h
    Out[117]: 2 y3 m. x3 V" x- g/ ^( y" U
    2020-01-01   -2.000000
    3 v8 Z0 b* u. q/ ~+ {2020-01-11   -3.166667
    ( ^' l9 G" s( I. \6 z( r2020-01-21   -3.6250002 c, Y. N" }" U" x/ ~: q
    2020-01-31   -4.0000002 t! C3 j. Y" z
    2020-02-10   -0.375000
    . ^! T: k. r7 `0 qFreq: 10D, dtype: float640 s" M2 ?7 o4 c
    1) [' m9 H9 }- q0 R. W- g6 I8 ^
    2
    ( x3 Z- T" k! X4 M3
    " [5 H) [2 ^+ e- ^9 u8 ?4" o( s" I# [- R" V4 M  U) K1 I
    5
    $ O5 b+ O: a- K$ _8 Z5 F5 j# b6
    ! W( j# u: V7 _. C& H2 r7
    ' ?$ N: A$ Y- U) q8 z/ w* s# ^, i8) a; v* i! F/ M8 s; a3 ^
    可以通过apply方法自定义处理函数:
    6 q+ D) a8 I8 L4 J' E2 W3 ks.resample('10D').apply(lambda x:x.max()-x.min()).head() # 极差
    * x8 P! M, Y4 b) e5 G' L7 \5 ~( ]! h( j$ I+ l. P
    Out[118]:
    0 S* c3 j: I. r2020-01-01    3; `% G6 E0 k( n, Y; i6 l
    2020-01-11    4: `% O7 e, B- l; m: M1 n6 R& d
    2020-01-21    4
    & s. p/ E/ }+ z' I2020-01-31    2
    / K( c" ^! L4 [7 ]" y  z  y2020-02-10    4: c6 z3 U) P. @' Y$ S
    Freq: 10D, dtype: int327 m( ~2 [$ L3 o  ?' H, J  X
    1
    . A1 I8 R. F& S# z1 `( U3 |2! T1 q: K4 }, ^' T9 V
    3
    4 ]. D9 E7 M4 _% C5 [; m; ^4 P9 m4
    ( S0 b; r& j0 W56 l& H" |# S# T6 c6 g6 Z- v
    6+ c7 A+ `+ R" F' I5 h3 q
    7& U. e% i+ L! z  X/ H* _/ H4 G" k
    8/ d0 w) q7 b9 `9 m6 l$ O7 B
    9, U3 R# K: |* v
      在resample中要特别注意组边界值的处理情况,默认情况下起始值的计算方法是从最小值时间戳对应日期的午夜00:00:00开始增加freq,直到不超过该最小时间戳的最大时间戳,由此对应的时间戳为起始值,然后每次累加freq参数作为分割结点进行分组,区间情况为左闭右开。下面构造一个不均匀的例子:) n1 [$ K2 i, b& R/ \( ^

    - T3 s% u3 x+ g0 |1 w% t# G- Nidx = pd.date_range('20200101 8:26:35', '20200101 9:31:58', freq='77s')' m; s  ~9 v! z+ y6 A) Q( g
    data = np.random.randint(-1,2,len(idx)).cumsum()
    * C  _0 o; B% y& c, E* W6 D. }4 ps = pd.Series(data,index=idx)$ N+ l9 d6 ]( `2 K, I2 T6 P
    s.head()' ^, r5 g. `  _! V
    3 m* f( k$ S% I- g& U1 F
    Out[122]:
    % {" {+ @2 z% \+ ~1 M2020-01-01 08:26:35   -1* N) }  u" G( k
    2020-01-01 08:27:52   -1( R) {, i4 H* q, E1 u% u' m
    2020-01-01 08:29:09   -2
    * g8 T6 r7 I+ D% y* @2020-01-01 08:30:26   -3/ j; _1 c9 Z$ H# o$ h# D1 t6 [7 n
    2020-01-01 08:31:43   -4; N' }1 O( y0 g% M' K" M& g
    Freq: 77S, dtype: int32
    ( g; `* I3 u' k$ C1
    7 M* Q% ]' |/ t) O5 s9 X7 q2
    3 E1 I' @) ^4 ~; r3
    , `9 H1 m8 z" e5 W; f6 v  U4- |3 C5 ~5 S  P' g& ]- O+ Z( J9 j
    59 J/ d2 t9 p% Y0 u
    67 k2 N; M! i& M2 Q$ _+ O. \
    74 R" A6 w$ B# o! N+ `5 \
    8
    : X2 s" d: k" a+ [2 y0 U, l98 ^7 G) H( B# w; F
    10
    : `* @0 U/ c; Y! J& f! O( d% g11
    1 _& L8 p  K0 q  L$ k12
    5 K' s8 ~1 c& u& h7 D  下面对应的第一个组起始值为08:24:00,其是从当天0点增加72个freq=7 min得到的,如果再增加一个freq则超出了序列的最小时间戳08:26:35:
    ( d+ V9 M" r4 k+ s' M+ k
    9 g  c$ k0 G( g- g4 ^/ Gs.resample('7min').mean().head()% k* V. L) ~& _
    Out[123]: 1 M5 e) k% j2 h$ b9 B/ [) e5 `
    2020-01-01 08:24:00   -1.750000  # 起始值,终点值包含最后一个值( B- U& G0 ]$ L
    2020-01-01 08:31:00   -2.600000
    2 _; y$ U$ {3 T) \& \" x2020-01-01 08:38:00   -2.166667
    * T: y0 I& p$ e& e3 W; U3 T+ z2020-01-01 08:45:00    0.200000
    & n1 s' V# u0 z% f7 C* R( Y* w2020-01-01 08:52:00    2.833333% e4 |- C/ ^( V( j
    Freq: 7T, dtype: float64
    : g) z6 p2 y2 l& }/ u( P% m6 B1: t% }6 d8 F- @2 ~: o
    28 I: ~4 ?4 k6 c# r6 y5 o
    3
    0 L$ N% y* J% ^4
    $ u& r4 W/ ~; Y/ s; i. w" g5
    * Y# T* y3 e+ y! |, x7 H6
    9 X- S+ j7 ]: M75 T! q" A5 q$ p8 ]! Q' F
    8
    # y$ p) O$ W6 H% }% A4 ]* X- G  有时候,用户希望从序列的最小时间戳开始依次增加freq进行分组,此时可以指定origin参数为start:9 z, E8 F/ O6 t0 u: Z$ M/ L

    6 U. g5 C$ |) Ys.resample('7min', origin='start').mean().head()& F$ F0 P5 H* E
    Out[124]:
    - }  U0 n0 x: c3 N6 ?4 N- l! C2020-01-01 08:26:35   -2.333333
    ) X5 J# ^, b4 U$ v* K( \2020-01-01 08:33:35   -2.400000( O4 E' }* b% @2 z, P0 F
    2020-01-01 08:40:35   -1.333333
    4 D0 L# [: n) j- z2020-01-01 08:47:35    1.200000
    : X! R" r' O- _8 ?7 D2020-01-01 08:54:35    3.1666675 D. J0 F0 O' c# p1 d) p# r* f
    Freq: 7T, dtype: float64! k7 ^' C5 o0 k7 [. S% t" R
    1) ]( m: W! t+ e/ I
    2
    " l! m4 p3 {1 d/ e3. n$ o9 w/ I# t" p0 s+ i
    4& a; }1 E) h6 {0 _' e! O; x! r( z$ M
    5
    : i% D6 W' E, d+ t0 \6
      d( V" A( i! p- @76 v7 {, [. x& L/ m1 M
    84 l4 L5 R( Q: _  X1 m
      在返回值中,要注意索引一般是取组的第一个时间戳,但M, A, Q, BM, BA, BQ, W这七个是取对应区间的最后一个时间戳。如果想要得到正常索引,用’MS’就行。6 e% |- \, ?7 w$ Y8 ^' j$ a
    : z- V3 u. P9 s/ e2 v
    s = pd.Series(np.random.randint(2,size=366),
    - X; U5 H+ L5 {$ ]9 P              index=pd.date_range('2020-01-01',
    - k# Y. v4 u$ ]                                  '2020-12-31'))* w2 i/ z- ?, {& h  d7 t: N* g

    4 [; a# S5 ~$ ]4 p( Y/ y
    / t0 m5 X! Q" Z1 L4 C! B+ O) U. x! Xs.resample('M').mean().head()2 @9 o8 H  U, x( |( L
    Out[126]: 2 A8 Q( Y) ?. N, s; ~! o
    2020-01-31    0.451613
    * q& s5 k8 V, a2020-02-29    0.448276
    2 Q, ~" Y  {: }8 E$ F2020-03-31    0.516129- w0 D; c/ D% ]4 w  r
    2020-04-30    0.566667& S- ?' L  ~4 Y* R% H/ ~& M, C" Z
    2020-05-31    0.4516139 _: {; V/ m8 Q
    Freq: M, dtype: float64
    0 b! y5 q' X1 [, U/ j
    ! K" g6 \' P+ `& S  Us.resample('MS').mean().head() # 结果一样,但索引是跟正常一样
    : ]3 m- {  z3 ?2 C/ wOut[127]: - B7 V4 S: ~( r) A+ q- Z- F+ |& s- E
    2020-01-01    0.451613  Z3 W! U" y2 n3 ~9 ~
    2020-02-01    0.448276, W5 m2 x+ i5 n5 Z; g
    2020-03-01    0.516129. s0 D* d" I' C
    2020-04-01    0.566667& G: C! T) X6 E5 c7 h4 x3 N
    2020-05-01    0.451613
    8 u! j$ o6 n; i+ y* G9 t7 Y* S  pFreq: MS, dtype: float64
    1 [# W" ~& {# _, R. l; x8 ^$ @0 C; g" G2 O
    1
    $ o& J8 U3 n4 a/ M6 x2
    * x% G! z& A, }3 w* A+ z3; G7 t. y+ ~( H8 o
    42 H! R/ a& u, i# g
    53 B2 d0 A4 \" m1 b) l1 x2 ^
    6
    : \, z3 L9 m9 b- T" @* h7: A% N% D' ]- \' ?
    8
    / h# Y9 ?0 K& [( R9
    ! E. M5 ?3 |- t9 C0 Q/ }( s102 E- O0 d0 V, N5 `5 X
    11
    8 j2 D. R3 d' G1 X12
    9 e: B9 |; }* A; D13
    5 n) ^0 S# M" ~1 T! j6 b. H5 R9 a$ A14, E) t7 z! [$ I" l/ }7 o' |" @
    15  H3 ^. n' S9 z( x1 m1 E+ U+ W" g' u
    16. @, S! z: B, S9 M, Z  }
    17' ^9 r9 k' p0 H  N3 I
    181 O- p2 T& A4 y- f+ F" [
    19
    ( }8 _3 b1 b8 I" y20, E  S$ N$ e6 h4 o
    21
    , H3 a0 J& E$ Z6 R. O" [) g22
    $ B" v2 t/ X6 R* T3 U$ _8 b1 D; g对于 DataFrame 对象,关键字 on 可用于指定列而不是索引以进行重采样:! G4 D9 E5 o3 s0 _. o. n
    d = {'price': [10, 11, 9, 13, 14, 18, 17, 19],
    8 t8 y% q* {5 b8 t2 ~! Q3 ]     'volume': [50, 60, 40, 100, 50, 100, 40, 50]}
    - k, I: ^1 }* N$ s1 ^) Bdf = pd.DataFrame(d)' _& }# n* I4 n  I1 ~' i; M
    df['week_starting'] = pd.date_range('01/01/2018',( U9 E8 @' r. y6 [: H
                                        periods=8,7 Y; l/ b  v0 d5 w0 r% A. E
                                        freq='W')& O; E% y2 W2 a1 V" ^% D
    df
    , w6 e" s1 [4 `5 S; y( Q: Y& V   price  volume week_starting
    1 b" i  [+ Q) P& p0     10      50    2018-01-07* |  ~# M; l/ W; {1 O
    1     11      60    2018-01-14
    " A) `; e0 f, h* H9 o2      9      40    2018-01-21% n( ^, n4 z7 n3 w- i% C1 W" f
    3     13     100    2018-01-28
    $ e9 J9 O  n3 F5 M4     14      50    2018-02-04
    6 P' x2 M9 }7 O2 H& |. o& q5     18     100    2018-02-11
    " r, y! c8 L( \% h3 K9 q4 ^6     17      40    2018-02-18
    + G$ }/ L- p# K7     19      50    2018-02-25% S0 B% ], Y: f0 D5 e3 @, I# Q
    df.resample('M', on='week_starting').mean()
    0 U* W1 d% f9 c! F* Q* Z: V; Z1 k0 s" Y               price  volume
    . G, O1 J& X+ ]$ `week_starting
    & S9 G: x+ i. b& x. g! x2018-01-31     10.75    62.5" U( r+ E# |' Z( {1 [7 G
    2018-02-28     17.00    60.0  A3 m# ]$ K/ N* R

    * d5 K) o! W9 N) n! H' U1( ]& k3 W: Y. p) Q3 b- t
    21 E, n. l5 q+ s
    3* M" }) Q: m! ~* S+ {
    4* v8 J8 C# d2 `' n
    5
    6 `% o5 x1 u3 y0 K3 X- G3 m6
    7 l- K. g3 u+ h. p' Z1 d' T% ?7* d. |; r8 M" d0 ?; w
    8" x  r4 k' o. {2 [9 @7 D0 B# x( G" o
    9, |& I$ m+ a; r) F" l, g0 R% t
    106 C8 F- t# R) X* L5 G
    11
    , X' V1 \8 J" a12
    * V1 p$ H: u0 r% {$ U13/ u- o+ J% `$ r  P+ y9 r5 I
    140 L/ h9 M( r9 b4 K* W2 I, f( \
    15( I- q1 x# v7 ?/ \- ^6 @
    16
    / ?+ O! E/ {$ R2 T+ s3 m% n) Q( J17( n9 n9 @3 z% \' P8 F
    181 N) w' }7 K8 v7 _3 t5 \: Z* R
    19
    * r+ S0 t4 l9 u3 Y) O20
    5 n0 Q" p" U7 ^21
    & r, v! O! H& A) @5 a对于具有 MultiIndex 的 DataFrame,关键字 level 可用于指定需要在哪个级别进行重采样。
    , O( @% s# Z( d* G; ^days = pd.date_range('1/1/2000', periods=4, freq='D')6 K9 l: k( a5 I2 D6 u
    d2 = {'price': [10, 11, 9, 13, 14, 18, 17, 19],% A: j3 M5 b% ~- {
          'volume': [50, 60, 40, 100, 50, 100, 40, 50]}
    3 H* @3 t0 p: Z( F0 S) ddf2 = pd.DataFrame(
    2 X: i2 L7 H# t: s( m% q    d2,- w9 S; A0 C, b9 T- k
        index=pd.MultiIndex.from_product(
    ' l; P8 P* N, j' Y# w        [days, ['morning', 'afternoon']]& Y& t: t1 A1 V
        )
    ( n7 c3 C/ ]" L)
    : Z3 |+ |3 o: }$ Z! S1 Odf23 X# i& p1 d+ |7 G0 v6 G/ @
                          price  volume
    & V" q# ]3 X, F! W$ O* X9 Z2000-01-01 morning       10      50
    7 d1 ~* s8 E1 g' G           afternoon     11      60
    2 X% T5 Z0 @$ @+ K# J+ D2000-01-02 morning        9      40
    ' g6 ~/ w2 N& D, B  h! X) f           afternoon     13     1007 |( ^; U& ^1 u! z" \! `) k* b
    2000-01-03 morning       14      503 F/ t3 n' W5 i7 }7 c: K
               afternoon     18     100
    5 w2 E7 H: f& B: H' r' W2 L5 O$ C2000-01-04 morning       17      40
    6 r/ ], m& K* |7 y) I" x           afternoon     19      50" @4 \, T3 W- A$ G* G' {
    df2.resample('D', level=0).sum()' b) P$ l% u4 e5 x- [1 f4 ?$ |
                price  volume# J( k$ u. m/ f% x
    2000-01-01     21     110" v* N# u# v. R6 A5 n6 R
    2000-01-02     22     140
    - J5 w/ ]# n. Q/ _) i) o0 W2000-01-03     32     150
    & a! C% Z" ^' O8 l" u2000-01-04     36      90
    # N3 u: }0 R( I2 w9 z4 }0 S# d2 S6 z$ t& X
    1, y- L; |; N6 Q, V
    2
    % V: Z' ~: W2 g$ n( K% }3 K  f3
      P# t4 Y. Z- u# d4
    7 b. E! X7 f: d: Z0 D% B" P/ x: _5 \5
    - \/ n! x9 ]2 N5 Q6 W6
    5 y! A2 ?/ S/ T7 o1 R- ?- K7
    % c" J7 A* _! ^) O) x89 N' D/ {" Y. s# [% P% ?3 r
    94 w) m, x" ^) [3 J
    10+ M+ T& z: T5 J  ~
    11
    0 R1 `1 p* u3 d# H* m4 i12
    ( l" O  U1 x1 H+ t2 q  w3 m$ y! ]9 \  H13: ?! @! m# P9 j- l$ v+ I# k. r
    14% l" C! P3 f5 S( |' }. A5 i
    15
    6 _) f" q7 d; b16
    - s6 ?* G  d1 J2 T* }' o" t17
    9 A( u+ L+ K, b# [18! I# \' Q$ ]1 |! K. t
    19
    1 [  @$ |, R' B$ [$ z6 [/ ?20
    9 b9 W3 _* o; X  m  g0 ~21
    : x, Z% j1 v" E& `1 Q. x) f, ^( h22  @% |+ n9 ~8 Q" U0 {
    23
    * K8 T  z, V' J/ d1 C* N24
    3 y9 o+ l. E: {% Y0 J- n: n; s25
    1 V, m& I( c' g9 U( p2 k1 }根据固定时间戳调整 bin 的开始:; O! I. A9 E( ^* h0 t' q
    start, end = '2000-10-01 23:30:00', '2000-10-02 00:30:00'7 c! C  R- H# [1 }- [
    rng = pd.date_range(start, end, freq='7min')& d6 e6 r( u0 \4 d6 y# s0 j" `
    ts = pd.Series(np.arange(len(rng)) * 3, index=rng)
    + o3 ~6 C1 t3 Kts
    3 J$ h/ E. ?/ {6 Q$ Y2000-10-01 23:30:00     07 @) Y9 Z- h8 G. e
    2000-10-01 23:37:00     34 k) ]% A& j* m4 i, c
    2000-10-01 23:44:00     6
    ) _- Q2 u% l7 h- G% ?; c3 U2000-10-01 23:51:00     9
    7 z3 g0 f- D; f) @& ?4 o2000-10-01 23:58:00    12
    6 w4 g0 _6 K" K/ q6 P* m9 X) f+ H4 V- F2000-10-02 00:05:00    15% Y, J2 k- L  \" w. b
    2000-10-02 00:12:00    180 u+ z+ u3 U& E4 m% ?) U9 z
    2000-10-02 00:19:00    21
    ) _2 Y, d( q* P% \1 E( w2000-10-02 00:26:00    24# J8 k' g# s* `% d
    Freq: 7T, dtype: int644 S- t* q  ~6 l4 y: r

    " o; E! E2 z' Z4 L% A, Kts.resample('17min').sum(); H  w5 i+ V# U& K+ o4 {
    2000-10-01 23:14:00     0
    4 o  e8 `% ?9 W" A+ {1 O2000-10-01 23:31:00     9
    & N0 u( R3 C/ x4 D( r2000-10-01 23:48:00    21
    ! Q. w& S8 p- M' X  [2000-10-02 00:05:00    54
    % U' S% Q( U7 p% W+ l8 Q2000-10-02 00:22:00    24& e2 Z2 e2 K1 Z! Q  o
    Freq: 17T, dtype: int649 @& R9 N2 Q$ q0 I. i' j9 h6 ~
    " m; u; @8 u+ E% W& t' ^% D
    ts.resample('17min', origin='epoch').sum()0 x3 W1 A! T5 e( z' f
    2000-10-01 23:18:00     0: W4 B6 @2 U+ t8 H
    2000-10-01 23:35:00    18
    6 K( x- Y# h1 d: z( ~5 f5 g2000-10-01 23:52:00    27
    # [/ O6 B/ S2 F& s5 D" t( n# q9 V4 G2000-10-02 00:09:00    39
    ; f$ F7 d' o) b! U3 X2000-10-02 00:26:00    24
    6 f! U  r# z! I+ [2 b0 Q; mFreq: 17T, dtype: int64! {8 Q6 v4 b2 X" E* C" V
      F- z% `+ z" _5 T
    ts.resample('17min', origin='2000-01-01').sum()
    & v2 B- w4 A& @" o! p6 f2000-10-01 23:24:00     3* F- `% f  P; s2 O. C5 c2 L
    2000-10-01 23:41:00    15* }7 ^7 C5 W& U- w0 Z% Z
    2000-10-01 23:58:00    456 v4 c- R9 e9 @+ s5 P5 @* \
    2000-10-02 00:15:00    45
    ( ?! x7 Q, R1 I3 d/ xFreq: 17T, dtype: int64
    5 F$ {7 e" `. r2 Q* O- D0 [
    * t! Y! D7 s1 N$ E  F: I, x1
    " F8 d0 m& X- Z2/ V  B8 f" @5 \- r" W
    31 X$ c2 l: [+ y1 R8 e
    4
    ' ?5 K* Y- F4 {& O' n7 `5: g& s9 P1 W, x  C* e! d
    6
    ) J  m* y. [2 ?  f7
    ( N8 t/ F- u1 F  t7 }4 x8 j4 n86 P/ m! b. g( ~- d% P
    93 D' r7 g4 s/ E) t
    10( a. A3 u( B9 @9 e! m
    11
    5 B6 _# d" |5 B' y12, m" S- N# q; H( i# q1 V! B* p
    13
    ; a2 g9 j- ^: P  F1 {9 O14( i1 n, i) l/ j9 k  |
    15
    - c% c' Q; h9 d6 c: p0 R160 B& L1 d  C! i4 o9 N
    17
    . q. @/ f2 O  g1 ?18* E- ~5 c1 O$ N& }! t: y& W
    19
    # T& `) s' K8 Z; d0 ]* S2 A- P$ K20; a8 g+ g% K& B* W# v7 S0 ?. ?
    21
    . I* \/ i$ j+ b22
    # G3 a# ~! e: H6 D23
    4 @9 N  T' I6 G) Y245 U( x1 x& q/ `2 g- f$ A6 J
    25. |, T% T1 _$ a. y4 k9 d" @$ i
    26
    ) ^7 D4 y+ i. `6 c( n0 ~  L: A- ?; T272 E# Y/ m- W$ w
    28
      T" Y$ X( Z$ P+ M2 e4 q29
    ; t4 ]- o! P, N6 F7 t% V308 y/ A, G+ ~; e0 F+ v+ e
    31* T, M- o0 v3 |# Z) [/ x
    32, m3 e" {; w+ ~/ k" `+ f  V
    33/ @7 x% p3 k6 y& d
    34
    1 ?" X" Q' m* C3 y35
    - B) p1 y. c+ s+ G2 J36* C" A/ r; j1 B0 ~) S9 z
    378 @. @0 L0 Z  j. x7 o
    如果要使用偏移 Timedelta 调整 bin 的开始,则以下两行是等效的:
    , w) R- o" s  t) @ts.resample('17min', origin='start').sum()
    5 X5 J. A/ {8 |9 r6 |2 O5 `ts.resample('17min', offset='23h30min').sum()
    * ]% v9 _* D* K: ?/ C) P2000-10-01 23:30:00     93 ~$ Y+ X! d# }( }
    2000-10-01 23:47:00    21" ]; ]3 r3 Z- ^8 A( j, Z
    2000-10-02 00:04:00    54) u1 q/ m6 U. v' D' s4 S
    2000-10-02 00:21:00    24
    : x! z; |# R* p1 z6 SFreq: 17T, dtype: int64
    # l+ _0 [& H# v1
    : [* R% N. P2 j7 U  F9 i4 j  x4 @26 ]0 E2 g$ v- W) H! w6 u: @
    38 b8 `: p- @$ d
    4
    1 F( f& _4 I* v, F5 ^: j  T5# s* p' `5 j6 E$ M
    66 X1 L# z0 h3 U
    74 i, z) ]* M3 G% v6 o  \0 M+ ?9 s
    10.6 练习4 z/ D) }; Q1 S, v5 b2 x. M8 |
    Ex1:太阳辐射数据集; z! m1 s1 q' \
    现有一份关于太阳辐射的数据集:& L& K* g, k5 [0 r% a. h
    ; A% J: u9 I# _
    df = pd.read_csv('../data/solar.csv', usecols=['Data','Time','Radiation','Temperature'])+ D% d' F3 m$ C5 a. Q
    df.head(3)
    + z6 X6 ^3 K1 i$ l7 L, L0 Z9 v1 R  I, C( Y5 I' `8 [
    Out[129]:
    ) n. t, N0 Y- k) k                    Data      Time  Radiation  Temperature
    4 L' W0 D( ?: S! I, H0  9/29/2016 12:00:00 AM  23:55:26       1.21           48
    ; r* k  Y2 C! Z1  9/29/2016 12:00:00 AM  23:50:23       1.21           48
    6 U6 D( j' l3 B+ @2  9/29/2016 12:00:00 AM  23:45:26       1.23           48
    8 u8 q5 [3 m5 K! S10 Z8 w  x! t; K" ]1 e% j* J1 ~
    2' o. H5 C  J9 i6 d' J" N- f
    3& K- Y* V$ o0 W% ]) \" s% j
    4
    " p3 f- N/ a  F3 n) A. A7 E0 y* |5
    7 P. O2 H: {! P1 Q8 f0 g# i6
    5 E1 J0 e0 a" D( y& V7
    9 Y( r# e) ]1 d/ R- E8 \8
    5 k8 X: _5 m2 f. h' L, H3 v将Datetime, Time合并为一个时间列Datetime,同时把它作为索引后排序。: m6 r2 A8 U8 R+ l1 q$ l2 K6 H
    每条记录时间的间隔显然并不一致,请解决如下问题:
    & ~9 m" c8 h3 H  C. p0 K) ?找出间隔时间的前三个最大值所对应的三组时间戳。& N& t* |5 d$ }! i0 r
    是否存在一个大致的范围,使得绝大多数的间隔时间都落在这个区间中?如果存在,请对此范围内的样本间隔秒数画出柱状图,设置bins=50。& q4 K4 l) ]. C7 H- E5 Z2 I
    求如下指标对应的Series:7 ~+ H+ }) B) v5 X6 n9 t
    温度与辐射量的6小时滑动相关系数
    / o& H9 y5 Y8 l% C& D# x& q* T以三点、九点、十五点、二十一点为分割,该观测所在时间区间的温度均值序列$ i9 c7 v. s" Y9 z. U( q* a
    每个观测6小时前的辐射量(一般而言不会恰好取到,此时取最近时间戳对应的辐射量)0 Q* `" w) U* Z! A4 Q, j
    import numpy as np
    : }& ~6 l* s7 b5 Aimport pandas as pd5 r% h1 H2 \4 A) _! A
    1
    ' y8 Q  b, ^' l  A" F7 J6 e2
    ) n' i6 T% Y! E- F" Q将Datetime, Time合并为一个时间列Datetime,同时把它作为索引后排序。1 m" R! S- {* n$ y4 e/ A( u  \
    data=pd.to_datetime(df.Data) # 本身是object对象,要先转为时间序列
    % Q# y0 g. ?3 x% ~, Z0 J2 Btimes=pd.to_timedelta(df.Time)
    + g2 s) M* i! }* a& V3 A7 S5 |! Ndf.Data=data+times- J, m7 K9 U; r- @& X
    del df['Time']
    : s5 I8 I" b! }; r6 f' i, ^df=df.set_index('Data').sort_index() # 如果写的是set_index(df.Data),那么Data作为索引之外,这个列还另外保留
    1 k8 |) [0 T- d* y. B/ Q! j; d% Bdf$ X1 d/ A; V5 P
                                            Radiation        Temperature$ _. \  X. w0 l! y
    Data               
    - Q! Q) o) G2 {  S! D9 F2016-09-01 00:00:08                2.58                51% B2 d* [, R4 p# g: d- N* g
    2016-09-01 00:05:10                2.83                51
      S0 {5 W3 E+ ?/ Y2016-09-01 00:20:06                2.16                518 Q8 U2 b5 h7 ?) z9 I& t+ \
    2016-09-01 00:25:05                2.21                51
    0 b  u3 Z9 @4 s1 n# m2016-09-01 00:30:09                2.25                51
    : ^; [2 n8 l* H( ]* {% \% M...        ...        ...
    " `& d- R1 Z: i" u$ R2016-12-31 23:35:02                1.22                41
    : W8 b1 Q- L4 I3 i2016-12-31 23:40:01                1.21                41
    ' O- B  S# U# d! D! v2016-12-31 23:45:04                1.21                42
    5 e# ~' p4 W0 l: S- `2016-12-31 23:50:03                1.19                41
    5 U# G7 v6 [( z# Z5 d# b2016-12-31 23:55:01                1.21                41/ b8 `* F% i3 x. C) m$ e: J; P2 V

    # ]; |7 [1 r7 z; p+ o# J) j3 {1; L4 L' N; W6 ^- ]3 @9 ]
    2
    7 E: A5 L" j; Y6 x7 I: C3( b/ @/ ]$ [+ x, w' i5 z6 q2 d+ D
    4
    1 n5 D! K; h& N9 v) ~% o5
    * |6 d0 Q% z. a6 c6
    0 {8 @. `2 X7 g- t" J' w5 [# f% D76 p. U8 t7 \" z. z
    8
    1 y( }+ |0 d1 c5 v& p9
    3 u2 W( C$ W( `3 F0 [4 O, m7 F' ]10- C) G! m7 J6 @/ t
    11. \" E5 E' f0 Z* B
    12
    ; h. d8 G6 a( H; [13
    # S1 ]! ]8 y7 `' V  T/ U148 Z( Z# M! c: c. I
    150 `# E) v4 D7 h' S3 _% [
    16
    ! l6 q/ \* H  f17, Z  n. B, @; R8 L- ^
    18
    $ v  b& N" V; B- t) S19  J8 s6 G, D5 {* k; y7 A: C+ L
    每条记录时间的间隔显然并不一致,请解决如下问题:
    ( I4 @' j! U' o' X找出间隔时间的前三个最大值所对应的三组时间戳。9 Q! N, B. {  F  H3 z% q- l' N! K
    # 第一次做错了,不是找三组时间戳
    6 d! T5 T1 o% z5 c, ]/ \& c/ b6 iidxmax3=pd.Series(df.index).diff(1).sort_values(ascending=False).index[:3]* d) f1 {- t$ @+ U
    df.reset_index().Data[idxmax3,idxmax3-1]4 y% z$ b# B% k8 ^8 ^7 u4 `

    - \  }' \8 H2 D25923   2016-12-08 11:10:42
    ) F- C9 C' G) p  |24522   2016-12-01 00:00:02
    ( E) Z+ ]1 G( |; c- g4 C0 `- F7417    2016-10-01 00:00:19
    0 r( m1 m$ `0 E' p3 jName: Data, dtype: datetime64[ns]' l6 R3 U2 i5 S: `$ T
    1) x- \& M% V6 s' K1 o( K7 R, y: }2 U
    2+ f% n* Q! W5 S; V  U; W
    33 ^5 T! ~. e! u& ?3 S$ g& i
    4
    5 f, e( l7 l* [# o6 d1 R54 {2 z/ B3 e7 D) D/ {
    61 n7 i/ w8 W  }
    7& ^' I) q1 G2 {5 ]  t
    8; E, D3 r: O/ i0 R
    idxmax3=pd.Series(df.index).diff(1).sort_values(ascending=False).index[:3]
    ! \5 H' Y: [; e& flist(zip(df.reset_index().Data[idxmax3],df.reset_index().Data[idxmax3-1]))
    + R5 X: a8 \1 f/ \( ]% {, X
    5 O1 Z" u* \5 @/ @[(Timestamp('2016-12-08 11:10:42'), Timestamp('2016-12-05 20:45:53')),
    " P+ E) J7 T) d (Timestamp('2016-12-01 00:00:02'), Timestamp('2016-11-29 19:05:02')),: ]" M& J- i( t. X8 y! l) q
    (Timestamp('2016-10-01 00:00:19'), Timestamp('2016-09-29 23:55:26'))]
    7 h" z! q: K+ O6 [; L" M1+ v' v! |: }6 p% o- S, A
    2
    : O- j1 [4 e; j0 b2 ?  D: e30 a, G+ E0 R* b# f% P
    4
    * h0 ]  V! {5 w' p! y51 Z( \  V& t; ?- A, V
    6
    " [7 }+ E* [8 x8 L5 S- h7 Q参考答案:
    & `: ]3 B/ w% c# Y3 `' d( `, ^) i9 p) u2 D# Z. R# Z5 Z
    s = df.index.to_series().reset_index(drop=True).diff().dt.total_seconds()1 ~+ k- B7 Z4 j8 H7 t0 ?! z/ p
    max_3 = s.nlargest(3).index
      z% S; c4 u6 mdf.index[max_3.union(max_3-1)]. e5 \5 M4 J3 l, i
    2 c. }/ R! k$ [
    Out[215]:
    2 x' E. F" d  D# A8 w) VDatetimeIndex(['2016-09-29 23:55:26', '2016-10-01 00:00:19',5 I5 f4 j. r& b0 N3 k8 Z3 H+ t3 N
                   '2016-11-29 19:05:02', '2016-12-01 00:00:02',
    9 U6 d" c# L+ `' E) E6 f               '2016-12-05 20:45:53', '2016-12-08 11:10:42'],: L' f: T1 p( n5 w
                  dtype='datetime64[ns]', name='Datetime', freq=None); n( f* M, g# s* P
    18 C' D8 @% b6 r" u, O4 u# `
    2
    $ j4 e* g" H8 x2 s( J3
    8 q( R1 H+ C6 i5 p, S2 H4
    9 L( z. V8 I% m; f5
    1 N. m' O; U9 b; J$ O% I1 f/ S6
    ! n' D) {3 \; n: z$ I* o& x# ~7
    3 q! s" n- F8 P# T, C8 S3 x4 X8: {0 a- G& H2 C" W' H+ R
    9: r- k4 P* b  b/ Q9 w# |
    是否存在一个大致的范围,使得绝大多数的间隔时间都落在这个区间中?如果存在,请对此范围内的样本间隔秒数画出柱状图,设置bins=50。* Y, j2 f' X4 {/ d* ~
    # 将df的indexydiff做差,转为秒数后排序。再求几个分位数确定取值区间
    , y5 \) S: ~  T+ Y2 Z- Z" q' es=pd.Series(df.index).diff(1).dt.total_seconds().sort_values(ascending=False)
    5 z4 a  ~: Z! ~0 ?- qs.quantile(0.9),s.quantile(0.95),s.quantile(0.99),s.quantile(0.01),s.quantile(0.03),s.quantile(0.05)% ^0 K% B- V7 R/ Z% j

    : k+ W7 c" j+ L1 V& |(304.0, 309.0, 337.15999999999985, 285.0, 290.0, 292.0)
    ! p$ J% }( ~/ x. t8 i5 b5 j- G: E1
      s& d- r: }3 U22 C7 a1 Q/ c* T( ?' X6 w
    33 }$ a" @% b: Y& h
    4
    ) e  J" o0 b* n& a5  I7 J; G$ y# ?
    %pylab inline
    ! S3 b. e- i& I% h+ z_ = plt.hist(ss[(s.values<337)&(s.values>285)],bins=50)
    $ |& x# q6 r/ G5 bplt.xlabel(' Timedelta')
    1 |) ?5 L8 e5 u  `1 _  }: ]5 s" c  jplt.title(" Timedelta of solar")2 v6 P+ W  P- B9 M7 {# p9 v
    1. C6 Q4 U" k6 S4 @" B8 E& H! p
    2" v+ P/ `: E3 C% E
    3# b9 R$ H; }3 M' L: G8 g, ~
    4
    9 @. H1 \  i2 g9 X0 h1 b+ Y) D- n+ M3 w: o' G( m

    . E& \. R. }5 Q求如下指标对应的Series:: j7 b. u; |5 @
    温度与辐射量的6小时滑动相关系数
    & h: A. G( ^; {# Y以三点、九点、十五点、二十一点为分割,该观测所在时间区间的温度均值序列
    ; E7 L5 Q1 t, q/ Z1 A# S4 k; m( S' s每个观测6小时前的辐射量(一般而言不会恰好取到,此时取最近时间戳对应的辐射量)
    9 Z  x: ]; e2 O" [" Kdf.Radiation.rolling('6H').corr(df.Temperature).tail()
    - D" D. H# e5 I# b% K$ I( G4 c9 @! n
    Data
    2 T, T) c/ n& t2016-12-31 23:35:02    0.416187
    , z) G3 V5 |3 @7 m0 V2016-12-31 23:40:01    0.416565
    / k- f4 I: z" q% R  O/ Y2016-12-31 23:45:04    0.328574
    5 h5 r" f3 L( s7 S4 g2016-12-31 23:50:03    0.261883
    ) l# b8 x9 _6 }) t- j/ ~( Q, v2016-12-31 23:55:01    0.262406
    1 l! [7 o$ ]( _+ Gdtype: float64* \: b( B- N6 i
    11 @1 a1 }+ S" N+ z9 {0 C
    2
    * k& K5 X9 m0 O5 D4 ]" q! H3, `+ q* c5 X4 ?. f- T. ]% X4 {5 k
    4
    3 k1 F- v* T; _9 Q3 r$ }7 e5
    - o0 C- N, z* ?2 e* S- }& R! ]62 G1 ]+ ~4 ?8 C2 V+ Q
    7
    6 I- v! ]* h; k' S( D$ {' `3 M3 n8
    : I* B: G' j# E$ d$ X9
      Z) S  l+ `, S; cdf['Temperature'].resample('6H',offset='3H').mean().head()
    3 l/ m, H! J! E* ^* x
    . U/ E/ R, r  M; g8 b6 |Data
    3 a" }. q( _5 @2 b' D) @1 G# D2016-08-31 21:00:00    51.2187500 l8 B8 p  r$ B* z% [  ]5 P# i4 y
    2016-09-01 03:00:00    50.033333! P' T- e5 J2 G, z
    2016-09-01 09:00:00    59.3793107 K% o% J: \/ T! s1 b
    2016-09-01 15:00:00    57.9843759 H& B7 K$ W% \6 \
    2016-09-01 21:00:00    51.3939394 u7 d$ V( H: p+ N6 ~' G
    Freq: 6H, Name: Temperature, dtype: float640 u: l% i; a0 B3 a$ k  v
    1: F# }* ]) x# m$ C) ]8 @0 D) A
    2
    ' i* C3 a! r; q8 W! C3
    , c! G4 E5 O! p  s/ {; w4
    1 Q& u7 O* g7 Z9 X- b1 e* e5$ [- O4 ?# a2 ?4 G
    66 F( d$ J' s0 y! r( e4 J) N
    7! j( M& C# \. |
    8
    9 |, p: U/ n$ M% W4 z% z9, p% \7 t8 S) }9 O( x" ^/ M" ~
    最后一题参考答案:4 |- o4 h6 h0 B% r, r- t( z5 ?* J5 c
    $ y1 W: K7 e+ }2 b- z' g
    # 非常慢, y% Q! e& q! b% p+ v; e
    my_dt = df.index.shift(freq='-6H')
    ' F, I3 ^6 D# k  s+ ]4 b( U9 Qint_loc = [df.index.get_indexer([i], method='nearest') for i in my_dt]" ~0 }& u* ^8 v! J2 H; k$ F8 h0 r
    int_loc = np.array(int_loc).reshape(-1)4 c4 ^5 I& Z, s9 S7 D& X4 c
    res = df.Radiation.iloc[int_loc]& U# g1 o# H1 f. F3 n8 B9 c& x4 ?
    res.index = df.index  U9 n/ C! H1 c1 Z
    res.tail(3)& }4 e2 w; j- k* @
    1# G+ B$ f) ?8 x& z
    21 i9 I: i2 v: Q6 I
    3
      t" O! w, Y6 m4% C5 P  F8 Z& b5 K/ o
    5
    % i$ P5 O1 Y7 j) m1 t6( T3 z+ H% T8 ?8 _
    78 ?6 R- E: d1 U3 l
    # 纸质版上介绍了merge_asof,性能差距可以达到3-4个数量级
    6 m! m* _9 I0 q# j" Wtarget = pd.DataFrame(
      C3 p% _, Q- a* J; n' l# M    {. p" T7 t: c0 l3 J4 Q# D
            "Time": df.index.shift(freq='-6H'),+ L- n% M" s& j# m
            "Datetime": df.index,
    * l* d( }' k! l' f( b8 B+ g    }
    3 S: W& k7 e1 y- S)' m, F1 W9 L  L- {' @/ D. c4 j

    5 {' C- p# D5 p: `& [res = pd.merge_asof(! z7 M0 B: F$ P8 s" ^
        target,1 C/ `! y: t  h; i( y4 D: v- H1 D
        df.reset_index().rename(columns={"Datetime": "Time"}),6 o9 x- u/ B3 X8 p. y5 x3 E& {, \9 b
        left_on="Time",6 ^' p3 L, d9 y. Z% A8 Q
        right_on="Time",
    - t, i. M% N  r: c" E    direction="nearest"
    # O% z, E0 f) E' `- V).set_index("Datetime").Radiation
    ! K8 {9 @5 w. B6 O
    7 v, J4 U; _: Bres.tail(3)
    * r  ]# V+ {8 T8 x) \# q! o; hOut[224]:
    3 b' S) N4 v2 l' t3 I9 b' `Datetime9 m" e- D% R2 B, ~) L) H
    2016-12-31 23:45:04    9.33
    8 H, d9 [, n% V4 r. o2016-12-31 23:50:03    8.49
    ( ]. N/ t8 @! I( k2016-12-31 23:55:01    5.84
    6 F' K9 d2 B. zName: Radiation, dtype: float64
    ) W1 _% N0 @& P! C5 ^# I4 M! k' u, X
    1
    2 A4 _9 T4 q" ?( ?, a6 s! r26 |+ c; m6 v$ H" p9 u& Z8 J/ i0 ^
    3! H2 N9 w4 Y# X7 k) N! ^% B
    4: `1 C( s. t3 v, ]1 {, u7 e
    5" B) s9 F: r& D$ k
    6
    * J+ |$ C4 f5 }, i7
    ' q) }$ H6 ~6 U3 g8' p1 u7 _4 T  l9 P, X4 q
    9
    5 \% W3 `7 J# E! [% a10
    0 \- Q% K, @' u5 d" B2 Z2 U' c& D+ N11' s- M5 f* h( [2 X, P
    12
    ; {) f: C  [; m; X5 O% [/ W13
    ) V8 x. v- D4 V6 s) b2 P- @14( j- v1 }& F- {% ?9 W
    150 E1 j' e7 Z9 h# E
    16
    4 A, P, {, ^) ?) L1 Z17
    - J  ]' @( Y+ f- d6 i5 D18
    0 J4 L6 P9 U: Q. O) V0 |19
    + P) ^5 _' O: @: W% R$ j" G7 h% |7 V203 M2 P$ ~7 s0 I5 m1 K' A0 k
    21
    - S" F- K" N. [0 F$ g7 W9 R22
    - h4 Y& {' |4 O4 f% s: F23
    . C) O9 C1 h4 j. x% ?# mEx2:水果销量数据集4 w5 r, l+ U. K( [8 k% F
    现有一份2019年每日水果销量记录表:
    - f* y% U  m; p4 v8 i5 X( ^7 e7 k! b
    df = pd.read_csv('../data/fruit.csv')
    0 n; g  w" v% l1 E5 Bdf.head(3)1 @. f6 p2 y8 |) e; O  t

    . S+ j$ G5 w* v' nOut[131]:
    + u0 J9 `6 V7 Y  K2 Q. ~" s6 J         Date  Fruit  Sale% I; B) O/ h3 R0 I
    0  2019-04-18  Peach    15: O+ P7 U. V6 A" e% t; s
    1  2019-12-29  Peach    15
    1 K3 {$ R& y- z6 k  m2  2019-06-05  Peach    19" b; T; _, w; ~; f0 @
    1: s! B# _$ \1 u) a7 `! \
    27 j$ ^7 ]5 d- k4 {, f( {
    3
    5 S6 t" s9 W/ c8 w% n9 j4
    , ^2 b. ?1 L' O8 J5 t5
    / c6 B0 _, W: I' B6
    * v3 t% h, o4 A# j0 C& }7
    1 ?9 a( k# E% {* `2 R8! L5 b1 c5 w: T" z4 N
    统计如下指标:. }) c3 S0 R2 U
    每月上半月(15号及之前)与下半月葡萄销量的比值) y# Z8 a. y3 M
    每月最后一天的生梨销量总和0 q3 E, f- B+ t4 g: K
    每月最后一天工作日的生梨销量总和4 o& E' Y/ R/ @  }
    每月最后五天的苹果销量均值
    $ S" M+ }7 V7 y- ~) \- |! f按月计算周一至周日各品种水果的平均记录条数,行索引外层为水果名称,内层为月份,列索引为星期。/ Z9 p& \& b; I" J
    按天计算向前10个工作日窗口的苹果销量均值序列,非工作日的值用上一个工作日的结果填充。
    2 x+ m  h# T4 z8 ^2 qimport numpy as np) C9 U2 m, ]5 P, x7 Z3 K
    import pandas as pd2 H- x5 F4 ~" U7 Y
    1
    / ]2 M& ?. ^1 d' P" s% C+ Z; n20 W0 f- \8 j* M' a
    统计如下指标:
    9 y8 n- g1 O$ Q5 l1 D每月上半月(15号及之前)与下半月葡萄销量的比值- q- v6 O* b" [# Q2 G/ @: {
    每月最后一天的生梨销量总和2 J& h# u' L5 a$ [
    每月最后一天工作日的生梨销量总和
    " |: h' D% ~( Z每月最后五天的苹果销量均值6 [1 |" d( X- M+ ?, Z  b- G
    # 每月上半月(15号及之前)与下半月葡萄销量的比值
    * k) z  E) s4 D: d6 Tdf.Date=pd.to_datetime(df.Date)
    2 p" L0 g- R9 `* u! Z" dsale=df.query('Fruit == "Grape"').groupby([df.Date.dt.month,df.Date.dt.day<=15])['Sale'].sum()
    + h: z* K$ i5 ^; R" Y0 g3 ssale.columns=['Month','15Dayes','Sale'] # 为啥这么改没用啊/ ?/ k8 Y+ Y  X
    sale=pd.DataFrame(sale)7 n4 r* P5 ^; q: d: d
    sale=sale.unstack(1).rename_axis(index={'Date':'Month'},5 {6 k0 `% p) x! k4 _+ C
                     columns={'Date':'15Days'}).stack(1).reset_index() # unstack主要是两个索引都是Date无法直接重命名
    , l( @, S( V2 G( E0 tsale.head() # 每个月上下半月的销量
    # ]! n' d- s* b* B
    6 i: w0 }" x! h  Month        15Days        Sale1 [2 N% M" V' |* r4 {3 n5 y1 {
    0        1        False        10503& ]3 K6 `. ~! `" b. P( v
    1        1        True        12341$ o" h- e! D. J5 Z
    2        2        False        10001/ M) [' m3 c% _9 O
    3        2        True        10106) w1 S& ~% p8 h! l/ \
    4        3        False        12814
    $ C% t4 M9 ?1 X4 t! J; M& l% q! F
    # 使用自定义聚合函数,分组后每组就上半月和下半月两个值,根据索引位置判断求比值时的分子分母顺序4 i& K! H2 ]" h. d* ~' Q' o( u* e
    sale.groupby(sale['Month'])['Sale'].agg(
    * t: H6 ?1 _" C% |                lambda x: x.max()/x.min() if x.idxmax()>x.idxmin()  else x.min()/x.max())
    1 v3 s6 i+ r- c$ y( @) M$ }& a* E
    # x" ~0 `+ C! r0 _0 g: w: ?+ \; z& S# FMonth. Z6 E% }& N/ T% ]" t( a% L
    1     1.174998
    8 ?& M6 Z* P9 t1 T& d. G' b, d& @2     1.010499
    8 m! h+ M3 R% Z5 b( B% Y( @3     0.776338
    , n# G# R4 A5 e& L* a3 [4     1.026345
    2 d8 r6 T. u' k( O3 P, x; h5     0.900534
    1 ?$ ]7 L1 C0 v% T8 v9 k6     0.980136# A. V8 J' b2 n" K
    7     1.350960
    1 N% s& @- i  J8     1.091584
    $ ~: q3 Z3 Y; J# J) n+ F9     1.1165080 V6 \4 F  `9 E" {! H6 Y
    10    1.020784* ?6 C: w- Y3 Y% _: X' P' j2 j
    11    1.275911
    5 |# K! K4 ~- |$ X! r: }0 d12    0.9896624 U; B& _9 T' R8 Y& y9 E' f
    Name: Sale, dtype: float646 i/ d( s! f8 |6 |
    - Q  s+ J6 _6 b% D: S
    1% A4 {" J0 E8 {) I& I7 s' B
    27 o) U, A, [) t( x$ L
    3
    . x& F/ O, V  B* `1 Q4 u4, a  E/ e3 k) A3 G; H/ [
    5
    : i2 z  V' D  |) t6
    , ?8 l  \$ ]0 x7 O' T7
    / Z$ Z8 s% q8 l' ?6 {8/ Q! `6 l) z, ~0 n, s- W0 d! D* E
    9
    : k( P$ E2 b7 h# @10
    + Q* Y: Q+ ~* W, p- q* k5 |11# t- G0 U& f- x& R4 K' Q9 Q" b  ~- t
    12: g. `, L4 l8 `3 _
    13/ J  Z9 {1 A4 V, z0 ]. D
    14
    % I+ L! @/ `# t1 F" [0 O. J' ~156 R0 ]8 e4 ]' y/ `- r6 _# u
    16' P9 C% l! e; P. B% `* u- T
    17( x; v$ ~: O2 F7 {
    18
      N# t, m/ [! B. x3 T, l1 F: y! k19
    9 \; w( b4 X; S) j2 K, u' s! |20% y7 u2 e2 p  R
    217 P2 W6 J. O* x( G9 `6 j0 U1 f
    22" a. h" V1 K5 G/ O4 o" q3 i, w
    237 b: f3 q! o, t2 N
    24
      V1 g9 B5 ~" Y$ }6 h' E) R25- P2 R! s0 i) [6 z. w2 F. D8 W
    26: [* j) \( v$ V9 y, B
    27, |" h$ W  `0 O7 c' v- C- o
    284 t& J, p. p# E% q% t3 l2 y) O
    29
      G1 O, m( R- ]* D. B2 p/ Z307 F# j# d: i+ n- H8 N4 P
    31
    2 t  b/ e. e& U. E' p32" A  l* a: f- y4 X' Z  J
    33
    5 d$ F0 N' T) q. R34
      q3 Q1 {, ?7 v# 每月最后一天的生梨销量总和
      E; I5 Z. g) q- [! ddf[df.Date.dt.is_month_end].loc[df.Fruit=='Pear'].groupby('Date')['Sale'].sum()
    ) i' v7 a$ J3 }2 j8 C1 P2 H% W% o0 Q& F( ~
    Date
    * x% L, r1 Z! L3 Z- g2019-01-31    847
    ; x. f1 |- K: s' K2019-02-28    774
    . r" L9 H3 r% k8 m7 J# ~2019-03-31    761
    % i- {! w: t$ |2019-04-30    648
    - J& x0 k9 _. _  h/ I, c+ z2019-05-31    616
    3 U2 j  G* o1 U1
    " \4 a5 [5 x' X+ L. I; D2
    1 J0 J) n" i1 P* N1 P4 D38 X; a% N( G/ ?3 R& ~" t
    47 u7 z% r) _- `$ A
    5& Q, L/ ]( e+ Z7 Q
    6
    : O% k2 J" c% ]6 O- \7" s* H, w7 o$ k0 U; z" X
    8- ]5 r3 b3 L' M+ O* f$ ^3 C
    96 ^4 J: W5 m7 q( b+ A
    # 每月最后一天工作日的生梨销量总和8 k' `7 l8 \. ]& G) n; N5 u
    ls=df.Date+pd.offsets.BMonthEnd()( T' B( P8 Q2 }3 x
    my_filter=pd.to_datetime(ls.unique())
    4 z$ c5 Y9 C+ Pdf[df.Date.isin(my_filter)].loc[df.Fruit=='Pear'].groupby('Date')['Sale'].sum()
    8 l% h( v6 N, X
    * }0 t5 g: ~! K7 K' `0 v1 \9 V3 uDate
    $ u- `  t) K, D" l0 a* e4 l/ s2019-01-31     847
    9 x- {* e' b8 t! |" p6 W2019-02-28     7746 L9 y7 h0 }: ?' X! X4 h. m/ Q5 F
    2019-03-29     5102 T" @# k1 _4 t& z
    2019-04-30     648
    / r; }. x0 k4 U) \( w2019-05-31     616( _+ f9 Y5 J* s/ P
    15 F" w5 Y  u" T4 L9 m' v+ H, n
    2
    . b. e8 L' @, u; C( U. Z/ G3
    / r, {- o; B. c$ \6 k7 x5 H  A46 l8 f& J. {5 Q3 e/ W% }: |
    54 v: s9 T. B! _7 ?; ~6 ~
    6) V, R# v4 @4 G0 n
    7* \, \  U4 P3 Q0 A% T
    87 C- o! v- }, a" A# d( Q
    9
    3 o% j! z5 ?1 M# g1 u( p+ T10% g8 y% u; D7 l" }$ v& o7 d$ L6 \
    11
    ( g, f9 D. W7 B6 F( c$ N# 每月最后五天的苹果销量均值3 x9 R! [3 ^- I$ p$ m
    start, end = '2019-01-01', '2019-12-31'
    # C$ Z* l9 @' [1 l) d! }* |end = pd.date_range(start, end, freq='M')7 g7 F5 Z4 J8 h' @8 @
    end=end.repeat(5) # 每月最后一天的日期列表,重复5次方便做差+ t- f2 E0 Q+ G

    + o. n# O0 R* y# Etd= pd.Series(pd.timedelta_range(start='0 days', periods=5),)
    / i, c; `8 @0 u* w$ itd=pd.concat([td]*12) # 日期偏置,最后一天减去0-4天2 ~( q+ }# G5 _; F
    end5=(end-td).reset_index(drop=True) # 每个月最后5天的列表& Z& \2 J7 _7 f, b! ]

    % _- l+ ~2 n; e; Kapple5=df[df.Date.isin(end5)].query("Fruit == 'Apple'") # 每月最后五天苹果销量
    7 J! q: U5 G& P+ ]; D! ^- R8 k% Eapple5.groupby(apple5.Date.dt.month)['Sale'].mean().head()
    / k+ w) x4 ]  @8 c3 ^
    2 K% {, v; b$ c) C/ g$ d) ?Date
    ! C0 W0 T/ {6 T7 }7 o8 w" q/ Z" F1     65.3137252 t8 m) J5 F. g, [, l; J
    2     54.061538; _4 S3 S! |4 |* s6 F* `, k
    3     59.325581+ c) E2 T4 i. \8 E2 u+ I
    4     65.795455
    5 I5 ~9 a8 y$ p, Y' `; `3 P4 \5     57.4651161 C7 T9 j4 X, \8 s

    / _$ i# A( T" d! G9 I1
    ) ~  E4 ?7 d- N. o' I2
    6 I/ H* L7 O* q) A4 k8 q- k3
    ( U/ S; ~( c, K- b; u) U/ f& m1 N. b( i43 a4 D( k  v- g3 Q
    5
    / m7 @& n& P$ v" h3 ?5 `64 l& t: [* |! \# d+ ~
    7: `$ t( W9 b, O# S3 \7 z" v
    8
    . I: y% U9 J# \# W& x; G' \9- h6 ]7 o* s+ |( ?1 L5 h  j
    108 p9 Q/ B; _0 f$ J/ _! p
    11
    * C- n- q4 ?7 g5 ^" I: {4 i12: Y, k0 `6 ^* U7 k
    13  ]  E. V$ y0 L0 X4 O
    142 `4 z8 }, y( \' h) Q. [6 D
    153 \- |$ Z+ D0 O3 T  h3 F7 Q
    16& H  E; c$ [5 x/ r
    17: X4 C" V% H" a" _7 w# |
    18
    6 m/ |7 z5 \0 J4 I7 @0 N4 p  D# 参考答案:
    4 I2 s2 E* Q" |+ ]5 U* K2 b1 N6 ?target_dt = df.drop_duplicates().groupby(df.Date.drop_duplicates(
    & D5 n" V1 P$ M; e+ M            ).dt.month)['Date'].nlargest(5).reset_index(drop=True)- N$ V/ p6 ?. c+ X/ D2 w- l6 _" c
    ) H0 c; M3 j! b2 I( |, _! x+ j* K
    res = df.set_index('Date').loc[target_dt].reset_index(
    & C/ F. y- P7 L# w! F; j            ).query("Fruit == 'Apple'")8 m2 b0 M0 q$ l
    5 ~- E) F8 V/ H: G( l3 U1 n
    res = res.groupby(res.Date.dt.month)['Sale'].mean(* \( [) R! m: J/ w# m% P) y
                ).rename_axis('Month')
    $ H5 l3 L* P7 C2 g; V* {( A6 z0 k/ P3 R

    " |! ?( o& j# ]' g# P4 C0 rres.head()$ J. z9 J* q# a, a$ @  x
    Out[236]:
    . q# }/ B/ M6 B" ?Month
    * _0 S: z7 S4 ~- e# J1    65.313725
    + P* w- P' j8 H! n4 G2    54.061538
    5 I; S5 y2 b$ k) e/ A' G3    59.325581
    $ [8 A; U. S' \4    65.7954556 R* M( l) ^8 @
    5    57.465116
    # [9 E" e9 l1 F9 l8 \Name: Sale, dtype: float64. \6 w; r5 [: i6 F, A. T  @1 X
      X* N0 l3 D7 r
    13 g- d4 i& T3 @/ F2 p
    2: x; V+ V9 L& J# L
    31 y8 Z2 y( h/ m/ p* G
    4
    " x" V: C" U+ j' m5" b& x/ {* ^+ V" f' M1 n
    6" P0 @& T  e$ L
    7/ J( l6 k: \1 p3 O8 ]; Q3 i
    8+ o/ t! y+ u# o4 O6 J
    94 K* s4 \% a% Y8 p; `
    10' f. Y; y5 d  t
    119 M+ b4 V9 C8 B; O; H; z, q4 |
    12
    + E3 U/ n5 H) f$ r% z% b130 d- @: a3 K# t7 k: i6 b5 M' }
    14
      w: {* Y4 [6 L* a0 a6 A$ g15
    ; ~; w. r# l. f16! u& K3 T2 ?6 d; k$ c$ S
    17
    , j, X4 h2 F4 Y6 R' d( c18- A3 K- b" L) d3 [: a
    19
    % m& e) F0 U, l/ |. t20
    ! E) O( g- J, n2 l/ p按月计算周一至周日各品种水果的平均记录条数,行索引外层为水果名称,内层为月份,列索引为星期。* {9 g4 Y, y; M8 S7 c
    result=pd.DataFrame(df.groupby([df.Date.dt.month,df.Date., K/ E) e! m2 w$ e# w. e6 Q
                                            dt.dayofweek,df.Fruit])['Sale'].count()) # 分组统计 / f- x0 Q9 \$ `, Z1 d- @1 I; G
                                            , P1 L! c. `' F6 k
    result=result.unstack(1).rename_axis(index={'Date':'Month'},' y$ O' J0 [1 p
                     columns={'Date':'Week'})  # 两个index名字都是Date,只能转一个到列,分开来改名字.
    5 m1 L/ z) f; ~% \" E3 D7 dresult=result.swaplevel(0,1,axis=0).droplevel(0,axis=1)
    : x* }) B; ]' [* H. uresult.head() # 索引名有空再改吧
    # z( r! [; V) E. r6 h8 |5 ^* A2 T/ a% I% K# |0 L$ w
              Week        0        1        2        3        4        5        6
    3 j6 u# i3 l/ R+ O/ O  QFruit Month                                                       
    , {/ ]- I- [# _+ T* j; i( _4 |) j- ^Apple        1        46        50        50        45        32        42        23/ e4 H: }- v( O. B* l! e3 y1 w
    Banana        1        27        29        24        42        36        24        358 I7 A8 g$ A, a, A% h8 p* m
    Grape        1        42        75        53        63        36        57        46: ^) V5 B( k/ R( l* }
    Peach        1        67        78        73        88        59        49        72
    2 Y4 L- {) h1 s2 d6 W8 RPear        1        39        69        51        54        48        36        40, @" ^2 y$ U# d6 F) q7 L/ p
    1
    7 s6 k  f" e/ E2
    8 t+ t1 l( b& D4 ^" R) i3
    4 ^$ p+ O, ]7 k  s43 A& }$ ^) h$ d7 z/ b3 H" W
    5
    + K0 w3 r- X5 T) g% Z! ~% q6
    7 \3 s6 [5 O0 e" K7
    ) F- @7 N0 n- U8
    ' K7 O9 k: |2 A; [( D- }98 o6 t& D4 c6 i. h, w4 S
    103 i  r  Q+ L, Y# S
    11
    6 G- r/ N3 y( ?- B2 P' q: g* S12" @9 W# Y* E* o9 s/ C  T7 `
    13" T* @  x: o+ I. B
    140 [- g+ Z2 f5 l' e) s. k
    15# G  t4 d3 F# S' y" F% e
    按天计算向前10个工作日窗口的苹果销量均值序列,非工作日的值用上一个工作日的结果填充。
    / o0 ~; `% c, @: v  Q# 工作日苹果销量按日期排序
    ' D9 Y. ^1 H4 P+ r0 v" `: k0 L# Eselect_bday=df[~df.Date.dt.dayofweek.isin([5,6])].query('Fruit=="Apple"').set_index('Date').sort_index()  e. A( f# }0 u. c# j- @) C
    select_bday=select_bday.groupby(select_bday.index)['Sale'].sum() # 每天的销量汇总
    / y- w* l& c# H% E+ ~select_bday.head()3 e: W+ m1 o" G7 {% q7 j" \
    * |) d; H3 D) d6 B) t$ ]
    Date2 R" u, X7 W9 M" P" n0 x* t7 D
    2019-01-01    189
    ' Q0 v" [/ E4 g4 J* K2019-01-02    482
    " z2 K7 E% @, N7 K) H9 F! \2019-01-03    890- y+ d" P" ~( j6 G; K$ Z! K1 a
    2019-01-04    5501 f2 }5 T, w* b# p* N
    2019-01-07    494* R5 C# Q( ~: ~
    4 n/ X1 K* I( G( S0 D6 C
    # 此时已经是工作日,正常滑窗。结果重设索引,对周末进行向后填充。0 t& R5 ]7 y# P/ f# q4 r" o
    select_bday.rolling('10D').mean().reindex(df.Date.unique()).sort_index().ffill().head(); O+ U; Y) W' J) {+ E2 \) _
    & U; W( g4 c( n2 x2 Y2 v
    Date
    ( {+ h/ W( B% x! O! @2019-01-01    189.000000
    , A' v( A. a, \& G( e3 d9 ^2019-01-02    335.5000007 [" g  p+ F, \/ p
    2019-01-03    520.3333333 K5 y& H" K8 j5 x/ M
    2019-01-04    527.750000
    $ b6 f7 A3 O: ]. L) O' t' _2019-01-05    527.750000- f: k+ F/ K/ e* n# [% z1 u) D! C
    1 b1 g) g) g) \9 M/ a
    ————————————————
    . N# Q( u1 e# J9 M版权声明:本文为CSDN博主「神洛华」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。8 l0 d, X" L3 P; {  S
    原文链接:https://blog.csdn.net/qq_56591814/article/details/126633913
    $ n- Q, T( D/ h) P2 `! }
    . n1 @, c$ N& O  ~5 i  Q+ }
    ) w$ `0 h0 U$ E9 s
    zan
    转播转播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-9-13 06:49 , Processed in 1.230912 second(s), 51 queries .

    回顶部