QQ登录

只需要一步,快速开始

 注册地址  找回密码
查看: 2271|回复: 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
    # g; y5 B7 g- p1 [* c4 p0 Y# f" N

    ) r2 _( l7 g7 U. [  l
    ) ^; x$ [% d0 u9 g! {文章目录/ B) T! j6 Z. m9 n
    第八章 文本数据6 x$ E" Q3 b+ Y* H! x7 x
    8.1 str对象2 P& A% I, H6 ?& ]' q
    8.1.1 str对象的设计意图, v6 }% I+ P/ Q7 u6 X
    8.1.3 string类型: U8 C  Y9 I" h! {# U
    8.2 正则表达式基础
    5 V  d' l" \& Z; f) a8.2.1 . 一般字符的匹配, |7 _$ H' u5 `% N/ h  K
    8.2.2 元字符基础# x/ M' Y; a- G6 E* Y
    8.2.3 简写字符集
    ( O& s% ~% |2 {8.3 文本处理的五类操作2 b: R5 x" b$ ^) w7 ^) w6 @8 O, ~3 H
    8.3.1 `str.split `拆分
    # {/ R( }4 ~& z, j7 b' ]8.3.2 `str.join` 或 `str.cat `合并
    0 n6 a( U1 c  R/ I8.3.3 匹配
    + W! L' ~3 t0 w. a/ I, U8.3.5 提取7 |* t8 ^/ a+ N
    8.4、常用字符串函数
    + T' k2 _9 D! X; V8.4.1 字母型函数
    & f7 o! y3 B5 O& i$ b8.4.2 数值型函数
    5 C$ m* ^7 [9 K" v8.4.3 统计型函数% {5 V; N% E+ q! D: d0 C' M
    8.4.4 格式型函数: Q6 w0 w- u7 M
    8.5 练习7 A# d4 G3 z1 O
    Ex1:房屋信息数据集
    + E& f; t; ?) D8 d1 l3 HEx2:《权力的游戏》剧本数据集3 G0 P) ?7 K: S
    第九章 分类数据9 S7 d* {, H  I5 z- ?( L
    9.1 cat对象$ n0 V3 r0 x1 `$ P3 p+ [/ X# V+ u
    9.1.1 cat对象的属性* b. B: z- D+ u* D, C$ _
    9.1.2 类别的增加、删除和修改9 ]: R5 {7 n+ o+ j( R* l
    9.2 有序分类  r5 M  C) I, |6 p7 D
    9.2.1 序的建立
    4 o4 o1 h: J# S5 H+ Y9.2.2 排序和比较7 z& Q- g+ z: r' V" K
    9.3 区间类别, g7 a  [2 g: m; {+ i/ P2 L% [( p% |
    9.3.1 利用cut和qcut进行区间构造" `9 `# L  F6 D6 o/ e- S
    9.3.2 一般区间的构造9 g" D8 f# G( v$ |, o) `7 z
    9.3.3 区间的属性与方法- t$ g/ ?2 [" l6 v( x( J; p, B4 R
    9.4 练习- a3 j& ]5 Q: t! F
    Ex1: 统计未出现的类别
    " X1 O5 w) G( h/ z% u& v, q7 x. tEx2: 钻石数据集
    ; {3 L6 F) R: }7 m5 P第十章 时序数据/ V/ n. X' Q' e
    10.1 时序中的基本对象
    ) @  `, d4 ]+ `7 Q- U9 ]/ ~10.2 时间戳/ U% o) D1 e/ s) N
    10.2.1 Timestamp的构造与属性- p5 g5 V: k, M! `& o
    10.2.2 Datetime序列的生成
    " S* D' j  u7 ?10.2.3 dt对象6 h% \7 a$ N$ v0 o) A3 z) T
    10.2.4 时间戳的切片与索引
    - Y$ C' ^) B0 I10.3 时间差
    . z$ D0 y" z$ t; a' B9 M10.3.1 Timedelta的生成. A5 R; E. M+ I' Z% u
    10.2.2 Timedelta的运算/ P$ X, p, n% T; D% L
    10.4 日期偏置& t7 i; R. l) ]" P  v4 ]& |" y
    10.4.1 Offset对象% V; ?. y/ N5 W% u7 f
    10.4.2 偏置字符串
    3 ~0 U5 ?- x9 U; I) ^) H10.5、时序中的滑窗与分组
    0 H+ B. Q0 W+ g1 L10.5.1 滑动窗口
    6 K8 z* S$ y; T10.5.2 重采样3 x9 l" {( N: U6 L5 J& l* h
    10.6 练习
    ! O- h9 S5 L7 D  J+ p0 \Ex1:太阳辐射数据集
    - W3 x3 E+ a" H/ b# F; Z, bEx2:水果销量数据集( ]* o, t* \& e; w/ b
      课程资料《pandas数据处理与分析》、github地址、讲解视频、习题参考答案 、pandas官网
    ' H9 G2 [$ D: v4 u! M传送门:2 N; W+ m8 E2 |

    * w. n( W/ }1 i( s% T9 V) Mdatawhale8月组队学习《pandas数据处理与分析》(上)(基础、索引、分组)
    & }6 \6 ^# e% y4 v7 [) E5 p+ ~# zdatawhale8月组队学习《pandas数据处理与分析》(中)(变形、连接、缺失数据)
    - D9 \2 O/ o  |: }% M6 G第八章 文本数据5 Z: [& L" z6 ^/ Z
    8.1 str对象7 |# s% v$ a4 M6 Q0 ~- n# y8 d
    8.1.1 str对象的设计意图$ U9 n8 m# o! I; T( T7 r
      str 对象是定义在 Index 或 Series上的属性,专门用于处理每个元素的文本内容,其内部定义了大量方法,因此对一个序列进行文本处理,首先需要获取其 str 对象。在Python标准库中也有 str 模块,为了使用上的便利,在 pandas 的50个 str 对象方法中,有31个是和标准库中的 str 模块方法同名且功能一致,例如字母转为大写的操作:
    + L; u9 N3 r5 S8 w9 w; Q" R
    " i0 o+ q( d9 o, g" i# Wvar = 'abcd'1 ~/ ]* {9 Q3 B2 s2 U7 i+ a
    str.upper(var) # Python内置str模块
    - b1 u" j  t8 v% w9 T& I2 X- FOut[4]: 'ABCD'/ p9 T* W" {2 C8 H2 i
    # u  G$ Q' [8 L, w2 S& Z
    s = pd.Series(['abcd', 'efg', 'hi']); S" _& \  {0 G  F
    8 M1 W' ~( K9 N  g# `
    s.str* _- _  b4 _/ w; i( k  ~  H) y, U
    Out[6]: <pandas.core.strings.accessor.StringMethods at 0x2b796892d60>
    ' z  p5 Q" h0 [
    1 h& [: H6 A6 [s.str.upper() # pandas中str对象上的upper方法7 U: `; |! |( r' p
    Out[7]: - Q' I; B* X0 @5 N  i* h0 L
    0    ABCD5 X& z' ]1 |% w
    1     EFG* k. f7 ?* m( l, F7 |* W
    2      HI. y7 t* Y* U  q: V8 `
    dtype: object
    8 N, q* _2 \$ b# W3 x( {1 i  l1
    3 P! J5 e' t1 O2
    2 Q/ F- R1 q  X1 B2 _9 M3
    1 A& J: [" u+ _- D' ]4" L) m$ V# r3 |$ s' s( @
    53 g  F3 ?8 Z8 C& Y% g. h+ |
    69 `, V! S, t* W8 k* V7 `3 S
    7
    - b9 g* u- p& F& Y9 ~; @; i83 Y4 z+ ]0 G4 ~1 t/ z6 w( i0 R
    9( c2 s$ w# {, Q3 l: ?" c, O
    101 A3 a8 Z  s0 R# U! I0 U
    11$ s3 f9 B1 a0 o9 S' C. Q- B
    12
    . _, G% V3 f1 p: c/ D13
    , w. {) ]" C  z14
    & u1 b& q+ j8 J; Y" m15
    , Q5 N/ t, |2 N# Q1 I8.1.2 []索引器
    + B$ e* j  t# h2 u" w; u  对于 str 对象而言,可理解为其对字符串进行了序列化的操作,例如在一般的字符串中,通过 [] 可以取出某个位置的元素,同时也能通过切片得到子串。5 t& j* j4 u7 y2 A! [$ l4 a
      pandas中过对 str 对象使用 [] 索引器,可以完成完全一致的功能,并且如果超出范围则返回缺失值:
    5 t) C8 o& I0 P9 Z- S! J3 K/ z0 p7 b+ O3 n
    s.str[0]6 r0 {$ i0 g/ f( a. O
    Out[10]:
    ' U! p8 b$ }2 P/ s: A* o  \0    a
    9 F$ v0 [4 B( ~' @! E& |& f5 P5 e8 R1    e9 k1 Q% Q6 d8 m
    2    h
    . D! l  |& b7 j/ Jdtype: object
    : y, {$ E: u1 ?4 c" F6 y2 ^/ f+ i3 E1 m) }' {
    s.str[-1: 0: -2]- o) l* x+ Q. U$ ~6 J! \9 Y) I
    Out[11]:
    / ?; k& s7 N& j/ S0    db
    & q- }0 d) ]0 Y0 Y9 o2 d1     g4 c) c' Q# J: b" O4 P2 \* m
    2     i: [1 j5 W- X8 t0 a
    dtype: object3 ^/ e0 e5 I$ f% }1 j4 f1 d

    $ g# ^9 p: b0 t9 E! ?s.str[2]
    & u: r( |% i- A2 ?. DOut[12]:
    : n8 u6 \% s/ v1 c0      c
    1 G: t  b! c+ v6 d7 I1 f$ s5 x1 s1      g
    2 ~; x, n6 i- A; b* L2    NaN
    + E: ^' @' c3 \2 |0 Y( M8 ^9 _$ Odtype: object
    ( p7 \' k( ~/ {% c
    # V9 j: U) f) T8 [! o1, o  m5 B' b2 t9 v. C2 m1 |4 V$ F  l
    2: y% a- p% H" n8 v3 H
    3; ?5 m5 |, i7 N3 M
    4  Q  W3 e; r' R
    5
    : @0 m& q" e! u/ Y7 }; E5 g$ d2 U6
    ( ?' A! ?( r* a" H- a7
    ; Z4 Q* U) d+ S3 F( N8& h2 Y6 ^8 ]; D+ L* k% f6 m; e
    9/ i9 E6 F+ e5 }
    10
    $ o# ]- g( G3 P2 D7 t% T3 R" L) u8 ^11& X0 r6 o" p/ r! K! Q
    12
    # n$ r" t& w9 P13, R9 v+ C  g+ ?! E7 x+ ~2 B1 q
    142 H' C/ l/ C# H# B4 Q4 w
    15. D8 x& \' L/ F5 k  x3 ]. A
    16
    ! s! J. }6 o* N4 @1 B) i17
    # b- z, J5 _3 V, `/ ~) u# j4 w18
    & z9 I/ Z1 [9 B" G5 m19
    7 |  @8 `: P: _  o: ~. @20
    0 C, s( l# o8 X, Zimport numpy as np1 b6 o4 [  ^- ^' F1 H% ~
    import pandas as pd+ _3 i6 i: s, g- h6 T* Z
    & m% \6 l# g0 Q% B0 Q
    s = pd.Series(['abcd', 'efg', 'hi'])- Y" _5 L9 @* @5 a- L: d( L" l
    s.str[0]
    " w8 p. L- r: i/ C* l' j1, `! l8 d( H" I& e3 y4 `  m" I
    2
    $ @2 O2 `) j7 o! x, k3
    6 q, r7 F( i# A  K" ~0 K: M4 T4
    $ e& c2 `, ]6 ]$ D! D51 l+ X" z' l+ W# x/ I: @- Q' v
    0    a/ d  S8 J+ y0 U; C
    1    e
    " }% ]  n' c/ i5 e( m) o* t2    h: p, y2 L. B% c. C# r! A( ^
    dtype: object3 N  }8 K% c. y# e3 h$ c
    1
    3 P7 W# a- Y) [) K/ o4 @7 U2
    : p$ u# G- n( D+ t% l: O9 p3
    9 ]6 E, X9 u/ R  c& A4
    7 n4 d* j# f1 n  u" Q- H8.1.3 string类型
    4 X& P# {$ q8 [  在上一章提到,从 pandas 的 1.0.0 版本开始,引入了 string 类型,其引入的动机在于:原来所有的字符串类型都会以 object 类型的 Series 进行存储,但 object 类型只应当存储混合类型,例如同时存储浮点、字符串、字典、列表、自定义类型等,因此字符串有必要同数值型或 category 一样,具有自己的数据存储类型,从而引入了 string 类型。
    # q9 d4 @9 ?4 ?! t  总体上说,绝大多数对于 object 和 string 类型的序列使用 str 对象方法产生的结果是一致,但是在下面提到的两点上有较大差异:# h1 n5 d7 t6 z) `+ u
    ' U: V# H- Y& z2 k/ m; h6 ?
    二者对于某些对象的 str 序列化方法不同。; J' Q' t) Y3 K) h" ~+ A
    可迭代(Iterable)对象包括但不限于字符串、字典、列表。对于一个可迭代对象, string 类型和 object 类型对它们的序列化方式不同,序列化后str对象返回结果也可能不同。例如:7 R5 Z6 {4 V5 F$ p6 F( L
    s = pd.Series([{1: 'temp_1', 2: 'temp_2'}, ['a', 'b'], 0.5, 'my_string'])
    . |8 l3 U5 u) h. k% b) Qs
      D2 U# _( k& W' @, O! [) K3 L0 z4 d1& ~2 T: S' a* G" [6 t- c
    2) d; e4 h$ H4 E/ o4 h+ y4 B
    0    {1: 'temp_1', 2: 'temp_2'}: I- j% C4 [5 M. b6 j- D, M
    1                        [a, b]
    1 m5 c0 z$ n5 _) B( `2                           0.51 M1 X+ o$ o% f7 z
    3                     my_string
    9 W8 E2 i# t8 N/ h% @3 z1 \8 |) kdtype: object0 K9 d/ L* E- i8 @/ m. @
    1
    & p  v% W% y0 C2 k7 h: |  |7 ^6 w. p' W2
    $ i, L; a0 a8 t- [. |- X3
    1 l2 q& u  N; {$ A0 n* b$ i: U4
    . V. B) T8 {' e) F9 q' ^5
    - J. m/ R5 b* e. e* Fs.str[1] # 对每个元素取[1]的操作% B% V8 j: P: R3 W* y- ~* k/ d8 H
    1
    , @' o2 ~( q5 |2 K2 E0 c0    temp_1
    8 G9 J0 U+ T+ b# o1         b
      u4 Y/ A  n( N: k/ |; d2       NaN' {* O0 W7 p( ]8 h
    3         y
    ; A/ K2 r1 T% f) N& ~( Sdtype: object
    : s' X* n3 y" U( {) m' w9 x14 }, r; U/ H2 p4 O! k1 }3 M
    2, o9 P5 |: j& X; k; K
    3, m" K7 i# r) C6 B6 Q9 J
    4( l) e8 [. _8 }1 L
    5
    5 c% Q1 i# L6 T  G2 T' ]s.astype('string').str[1]
    5 v, n8 {4 ?; |8 r8 C* H1
    & a* g6 M* }3 r* l* t8 M1 J' h! N0    19 {% U4 Q% h( H
    1    ': T4 l' f+ n- W- T8 F
    2    .
    # \; w" b+ ?5 \* t7 s" \1 S& M9 R) `3    y# `$ M: @- x% I* U+ X7 Y8 x0 g* P* M
    dtype: string
    6 j8 q: o0 G( u7 u# Z+ t1
    ; Y! F4 ?$ T% y) H2
    # ~( H( E8 f4 F9 A3  t$ e% i% \+ G5 `
    4
    - |" H" U5 I7 D2 {9 R4 s5
    4 g* m* t; ^; u& T5 o  x除了最后一个字符串元素,前三个元素返回的值都不同,其原因在于:& U- n7 T  h& E8 k  L" W

    # g' S, b6 z# {; ?* g: @: R( e当序列类型为 object 时,是对于每一个元素进行 [] 索引,因此对于字典而言,返回temp_1字符串,对于列表则返回第二个值,而第三个为不可迭代对象,返回缺失值,第四个是对字符串进行 [] 索引。
    ( c5 M% D6 [9 h$ _string 类型的 str 对象先把整个元素转为字面意义的字符串,例如对于列表而言,第一个元素即 “{”,而对于最后一个字符串元素而言,恰好转化前后的表示方法一致,因此结果和 object 类型一致。
    " {! L- e- q$ C" B& y* Istring 类型是 Nullable 类型,但 object 不是; \3 k# W0 K# F% ?7 a1 ~' _
      这意味着 string 类型的序列,如果调用的 str 方法返回值为整数 Series 和布尔 Series 时,其分别对应的 dtype 是 Int 和 boolean 的 Nullable 类型,而 object 类型则会分别返回 int/float 和 bool/object ,不过这取决于缺失值的存在与否。
    - U% P# x4 J) A0 E0 |  同时,字符串的比较操作,也具有相似的特性, string 返回 Nullable 类型,但 object 不会。
    ) C7 |6 x3 R5 E: M$ Q: _2 Ks = pd.Series(['a'])
    - L$ {2 q* @; F- x' ]) h  A: B4 [7 e: ]0 Z+ B5 Y" m
    s.str.len()
    ' m/ I0 M; S, G# \5 QOut[17]: - f; }. i* q5 t$ |0 @* m
    0    1
    " I$ }0 G0 o2 Z8 n7 `0 E% R1 Ddtype: int64
    4 e+ G! I. l( y
    ( t; }7 E7 z  g: \! N; N. z7 B: ms.astype('string').str.len()  b/ w) x8 G' `/ ?+ i. a& q
    Out[18]:
    & n5 B. A# {- F1 L! V: K8 `0 d. e0    13 d. N; T9 L  h( Z" T: i
    dtype: Int64/ i  i7 j+ ~  Z/ C% w
      G4 x8 C" L, ~
    s == 'a'5 @  U# ~- W9 r# A
    Out[19]: 2 o' z9 I! h4 h
    0    True: h7 d2 B8 f0 j: ^7 S3 M
    dtype: bool
    # a) W8 M& Y2 u
    $ a+ K" [5 @) l- p) n* as.astype('string') == 'a'
    8 [5 |( |! j' \6 SOut[20]: 2 v" H6 i' o) H, w
    0    True4 s( y$ @5 B* q( \) n
    dtype: boolean! J/ R" ^9 K+ q- b' G8 v

    . W' \0 e& `3 j8 K* E% H2 bs = pd.Series(['a', np.nan]) # 带有缺失值
    : C" E. r6 J5 J: p; g+ ]5 t
    % a6 b# R, ?, l4 g; L4 g7 ]2 {, As.str.len()9 n- b: C: M. v( n7 ?% g( S9 d6 d
    Out[22]:
    ) B, B) n# l. E0    1.0
    - H4 l4 N9 B( v- H1    NaN8 I; H6 Y2 Q) M- Z8 c: v! |
    dtype: float640 C4 J; G+ t# K% K# h  `0 Y" c# Y% M
    - t* i; s& q) k3 ~1 ]
    s.astype('string').str.len()
    ) s8 E: f( m) w4 aOut[23]:
    ; w( [& e. f- M( z" S0       1  c" T6 `5 U4 ^0 G) [
    1    <NA>
    9 Y; b, q! T: M+ l( N* N' ?) ]dtype: Int64
    1 R" M( V9 @2 T' S0 @" T, l: X7 F! H5 G4 ~+ @
    s == 'a'
    ' @" [! `2 j! i9 OOut[24]: / `# t0 u2 i* ]6 ~! p
    0     True8 |( v' K  H7 a" l2 e& e/ t
    1    False
    5 y5 U8 C3 z2 E1 C1 ]dtype: bool
    , i  s3 c! H4 M6 }( l6 s5 [' c6 ]: w
    s.astype('string') == 'a'
    " n# I) x  k6 ~- e0 C. p8 sOut[25]: 3 I4 M! B( F8 H0 W: U2 S
    0    True- a4 J- Y! ?. J. U& r! a
    1    <NA>
    ! x$ {' u; V, g$ z3 i8 P0 m' Mdtype: boolean
    $ B$ F& K7 _3 q: E7 Y2 t9 a7 {$ z. \" U- }, h4 m% }
    1
    8 X. l* O: L/ w6 e- m- X- Z3 g2. P% Y+ T' ?  @" p) _  B/ W
    3
    , V* y3 F  r3 F6 v3 Q" @$ |4
    3 S7 c0 e! x" J1 {5
    # c8 i+ l, q7 K2 T1 R% F% G# F6% Q' @9 X. ^- `
    7
    7 l: B5 b$ p8 W* Z- O: n8' \  k0 J6 G) Y( [- l, v( _5 ~
    9
    ( L$ f3 B% b  O" k2 ^* l107 H6 I+ i: N! B* W( i
    11
    9 M; W" l& F) h& Z1 }$ O12% f8 d0 ?3 T$ A2 N2 F
    13
    - q1 Z# G$ ~* ^& F: f& E' e# x14# f2 n$ S2 E3 D
    15
    " W: N  Q% A  i5 T  r( R16
    % I4 t, M# a. S* l  I! U17' F% r+ G# Y) h
    18' M4 x# p3 G. z  v/ P) I8 E
    19
    5 C& Z/ |2 ^2 w& M" B7 |20
    5 O- o& ?  {% i- o  G214 T6 `( u6 s3 a; `8 V. e
    22! P0 L0 Q2 w/ U) `
    23( V5 ]) `7 {4 Q4 |4 A3 `
    24
    4 \3 q/ E( [2 j6 x( b25% P9 ^1 u0 X8 G/ e
    26' n; }* \3 B5 O( L  L- T( v
    27
    3 f* [4 S$ Q; P28
    " [, V, R5 v( D29+ k8 [: l/ R8 x5 L# D" k
    30& W" G  t7 K$ W5 K5 a
    31
    : [' [9 z5 V! [. ?5 S( K  M0 v32
    0 o# v0 L. [' ~33
    5 X1 M$ b% m) f! A5 A* I34
    ) e/ H" Y: ~: A; Q3 }' r0 M35, S) [2 g) F1 x
    36* D9 ^# h7 Y6 M- ~2 y
    37
    / W' ?! D9 ^2 @2 P1 w7 u38/ K* c1 M+ R) B4 ?" t
    39. Z6 O7 Z, h7 r2 W  o
    40  p' i* G; |- z) [
    41
    % K# Z  S+ ]8 ?$ R, I42
    . [# @3 R9 I& r6 N5 B& n43* H% D, w6 j0 W. ~
    44
    3 w# p; U  }) J& @! O, s453 a& R! m* q, }" R" ~, K8 r2 Q' ~) x2 L
    46
    : y) v( S; e4 {) f& x# N47
    / @4 ?; {5 l0 X5 u  对于全体元素为数值类型的序列,即使其类型为 object 或者 category 也不允许直接使用 str 属性。如果需要把数字当成 string 类型处理,可以使用 astype 强制转换为 string 类型的 Series :
    2 V" C2 W& c. y9 p/ a. F+ _3 j9 @6 h# n: U
    s = pd.Series([12, 345, 6789])5 ^  b! Y( M( u. G( {4 P& @3 P% `) D$ H

    1 D- d$ W3 C5 G  v, Ls.astype('string').str[1]/ r2 X  u! _) x5 J' w/ h
    Out[27]: " {/ H; y) O/ M; {/ z
    0    27 B, p: b4 }' A# q8 f1 k
    1    4
    & z/ |# c; ]$ v- W* i6 i; g2    7, h7 @+ X' m0 F/ C: [5 \: M
    dtype: string$ V& v: k# d3 d) G5 [& e$ O& f
    1% t5 w* C' A6 q4 ^& t
    2% K8 Y3 Z3 ?9 ]. m9 M/ f  k8 V7 W5 B
    3
    ; X. s3 u/ M0 ]1 Y1 |" _4
    9 X* J9 t  Y$ q1 \8 z2 |0 A' Y8 \5
      }6 b% a5 B9 q+ c6) ?6 }0 r  Y1 R2 W* e; D
    7
    ! m7 P. A, {+ u' G/ }1 Y# Y7 M) E8% m' d: k8 |( U& I/ V& C; w$ E) l2 V
    8.2 正则表达式基础
    ' R2 M% |' |# ^% L' O这一节的两个表格来自于 learn-regex-zh 这个关于正则表达式项目,其使用 MIT 开源许可协议。这里只是介绍正则表达式的基本用法,需要系统学习的读者可参考《Python3 正则表达式》,或者《 正则表达式必知必会 》这本书! Y9 p" r* J2 q. s. F) w. X
    1 N- k& c5 a9 }+ o
    8.2.1 . 一般字符的匹配. e+ |  L8 \4 I3 R2 h" |+ F: o! t
    正则表达式是一种按照某种正则模式,从左到右匹配字符串中内容的一种工具。对于一般的字符而言,它可以找到其所在的位置,这里为了演示便利,使用了 python 中 re 模块的 findall 函数来匹配所有出现过但不重叠的模式,第一个参数是正则表达式,第二个参数是待匹配的字符串。例如,在下面的字符串中找出 apple :& S8 }, }1 [* |  Q
    ! O6 |( v. B; ]/ O" A& A1 _! e) u. J
    import re6 z9 V' H, g' Y0 Y  k' [

    2 }, c6 I7 V6 v( y0 v; Y( J1 Ire.findall(r'Apple', 'Apple! This Is an Apple!') # 字符串从左到右依次匹配5 @' f5 H! n! [
    Out[29]: ['Apple', 'Apple']
    1 q" m* }0 F- ?1
    # j8 R, F9 }8 |5 K% Q2
    8 M1 l" e" ?6 O8 l& [: ^) m3
    5 N' e$ i. g/ ^% E* \4
    . H( D  C" T: {8.2.2 元字符基础
    + Z5 i: g+ G+ ]" I. P" Y元字符        描述
    9 u$ t2 P/ I3 u, q8 e; p/ ^.        匹配除换行符以外的任意字符
    " R- {+ e4 U" z3 f+ n9 w' |[ ]        字符类,匹配方括号中包含的任意字符
    " `1 P% q6 E2 C( n$ O+ v[^ ]        否定字符类,匹配方括号中不包含的任意字符
    6 z; Z* D6 L" x% R# t3 t*        匹配前面的子表达式零次或多次6 ?7 V/ G8 }0 S3 p5 f
    +        匹配前面的子表达式一次或多次。比如r’d+'就是匹配数字串,r’d’就是匹配单个数字
    5 X* R1 ]+ j/ S?        匹配前面的子表达式零次或一次,非贪婪方式( C- M. `" b9 O( f- {
    {n,m}        花括号,匹配 n 到 m 次由前面的正则表达式定义的片段,贪婪方式5 S" |) k( z: t7 C- \: D# ]; D
    (xyz)        字符组,按照确切的顺序匹配字符xyz
    * g6 l& {/ b/ {1 ~|        分支结构,匹配符号之前的字符或后面的字符, x4 \! r( b: |0 R& @3 U2 X4 j
    \        转义符,它可以还原元字符原来的含义
    7 _2 }$ m% ]) z5 c  Y1 S^        匹配行的开始7 `& q# ]# ~: z" ]4 {
    $        匹配行的结束
    ! Q, M6 J. i2 U0 a& Himport re
    9 s, h# z& k0 ^re.findall(r'.', 'abc')( c" Q# X1 Q/ {7 J
    Out[30]: ['a', 'b', 'c']" q1 k" X( N" D; J

    * o: a, O1 K- ~* v7 Kre.findall(r'[ac]', 'abc') # []中有的子串都匹配
    1 Z. _' M6 v) t# g7 d; U. ]/ NOut[31]: ['a', 'c']
    / }8 _# M7 C+ K) |+ W
    3 O2 e1 E' t! X. p' t" Kre.findall(r'[^ac]', 'abc')
    7 u( K; A) S; SOut[32]: ['b']
    $ O1 [. @6 W3 g/ A# f2 u9 X! x' f' @0 H/ V7 D8 `
    re.findall(r'[ab]{2}', 'aaaabbbb') # {n}指匹配n次
    9 l0 Y  E, D* t! u/ y! a6 ROut[33]: ['aa', 'aa', 'bb', 'bb']
    * R. |  \* K$ U& \# G' {- t$ b# l
    3 J/ _6 k; s+ |( e5 m2 y/ w% S+ M% E" Vre.findall(r'aaa|bbc|ca', 'aacabbcbbc') # 匹配前面的或者后面的字符串
    1 }' @/ c) [( L0 @7 K! I4 o. F) kOut[34]: ['ca', 'bbc', 'bbc']
    7 F* c/ E9 ]9 U& w! J7 o! G: Q+ T, s$ g# i. @7 O
    # 上面的元字符都有特殊含义,要匹配其本来的意思就得用\进行转义。
    : I  n6 Z% A- g& @"""4 g, \/ V' |% J& L8 h2 z
    1. ?匹配的是前一个字符,即被转义的\,所以|前面的内容就是匹配a\或者a,但是结果里面没有a\,相当于只能匹配a。% |0 A$ ~8 u1 W3 V$ @9 E( @! K
    2. |右边是a\*,转义之后匹配a*,对于竖线而言左边优先级高于右边
    4 M2 c) t4 \* T3. 然后看目标字符串aa?a*a,第一个a匹配左边,第二个a匹配左边,第三个a虽然后面有*,6 j5 b1 I( Q! q: A% Z9 V3 c0 J% @
    但是左边优先级高, 还是匹配左边,剩下一个a还是左边,所以结果是四个a
    , [% {& S1 i- B  r! k"""
    " r% i; h& d, G6 Z4 E# j  A4 a( c3 I
    re.findall(r'a\\?|a\*', 'aa?a*a')   # 第二次先匹配到a,就不会匹配a?。a*同理。: \4 z; O  U- h. B: L1 F3 Y1 e
    Out[35]: ['a', 'a', 'a', 'a']
    - D9 n1 |5 a' g6 F, m3 L: ~
    . e- l* H) p3 G# `+ J# 这里匹配不到是因为目标串'aa\a*a'中,\a是python的转义字符(\a\b\t\n等),所以匹配不到。
    ! G6 A  u" V0 {6 w# 如果是'aa\s*a'之内非python的转义字符,或者'aa\\s*a',或者r'aa\\s*a'就可以匹配到\字符。
    . `. k1 W( x+ ~1 R( S, B; f5 Ore.findall(r'\\', 'aa\a*a') 7 n) z. }8 I4 |0 `  A$ a
    []
    ! y* l  U3 T* v3 Z/ }; w( g7 k6 @3 d# `  ]$ ?/ z( @
    re.findall(r'a?.', 'abaacadaae')
    ! ?4 A! _+ t- x# j2 l2 GOut[36]: ['ab', 'aa', 'c', 'ad', 'aa', 'e']  N$ j  y6 t9 V- r) K7 C% h+ I, U' ?

    + C% Q' s0 n; t" I/ U# U# qre.findall(r'(\w+)=(\d+)', 'set width=20 and height=10') # 多个匹配模式,返回元组列表6 e/ Y! Z5 g2 `' F+ R+ u7 i
    [('width', '20'), ('height', '10')]
    & G* d; O4 `$ A* X, D2 `: M6 ^
    . v; m, R% n" x1 M7 E1% A! n. S* x- R) r. m
    2
    9 z0 C: i' h5 B/ z+ f2 A  M3, c; s6 E6 j  G
    4
    6 c, M* y5 Y  O/ m) f5
    % `1 U5 |) q$ T6( Q# w' `1 L- k' L6 ]5 u6 ?
    7
    0 Y/ e) H; X8 q8/ _* L1 @/ ^9 F& r. E3 F" V
    9
    8 d% I' L( l* m2 ~+ \10
    5 G; p7 G9 `0 O$ r11
    ' F8 L/ m0 M9 t4 L12
    7 M( {0 p, k/ A* Q0 ^3 ?13
    % T5 ?$ y- @; D- I" ^9 l3 @14
    ; C% z5 x6 M4 U/ @) b: J15  B2 }0 V8 r" g
    16
    2 z" h, P# o% \+ a17
    ( h3 x5 H+ t9 w1 p4 n2 ^6 h( I18
    . @! B% O, m$ J, H1 K19
      C( W* b; ^  P1 W; y7 r20
    , D/ e/ m3 ~9 T2 ?  L! ]7 t: @21
    0 N5 W: w8 w+ a: x) C7 {; X22
    % y; ~9 K: ]. H# h) E+ m( V4 b! x23
    ' A0 Q' o# u' W3 f3 K6 O3 e: \24
    + U( e$ s: U6 c25) U' S' k( V: c; @& R% \
    26
    ) p1 d" t- y( C, a( j, F3 e277 T$ F# i' g$ h: e) g; z+ ~
    28
    6 z. }1 w) J9 r0 J29
    - i* o: o9 o% x302 S0 |3 ~' p% A9 X  {2 Q: l2 Y, N8 B
    31
    + i5 S5 Q- M: u3 P32
    $ R4 |2 {! n; J0 i9 v33
    3 H& f% O2 k# e% m3 K: F. s34% @: l( _" r2 ^
    353 P$ u+ \- [9 n2 j
    36* U; E: V/ M. P. f$ u/ m( C0 i
    372 |0 \4 t4 |# ?- t
    8.2.3 简写字符集; z: r' `3 m& ^
    则表达式中还有一类简写字符集,其等价于一组字符的集合:1 Y, |2 J! G1 a8 M7 U, a  b

    0 R* M! T" I0 |1 p0 N5 v简写        描述
    7 K- g0 V/ M. R. Y- A/ |\w        匹配所有字母、数字、下划线: [a-zA-Z0-9_]" ^& C- i0 p5 b1 ^
    \W        匹配非字母和数字的字符: [^\w], z, h$ }; E! f' E; w3 ~9 d: W
    \d        匹配数字: [0-9]4 b. W2 @0 ^4 w  B( n; R
    \D        匹配非数字: [^\d]
    3 K2 H  }6 E/ K- E\s        匹配空格符: [\t\n\f\r\p{Z}]
    9 _- m$ A* o- I* ?) e7 E# f& L\S        匹配非空格符: [^\s]
    ) X2 i3 X, {! D, W! i: b1 _. d\B        匹配非单词边界。‘er\B’ 能匹配 “verb” 中的 ‘er’,但不能匹配 “never” 中的 ‘er’。6 y- j2 _% b2 ~; J- P
    re.findall(r'.s', 'Apple! This Is an Apple!')) v8 D1 U, G+ N, S6 f+ C
    Out[37]: ['is', 'Is']
    $ r* w  z! ]1 }# x" i" ]1 I) {& K) K2 Z4 {7 L) Q+ M9 E, [( }5 [
    re.findall(r'\w{2}', '09 8? 7w c_ 9q p@') # 匹配任意数字字母下划线的组合,但必须是两次5 l% L- _8 u' S
    Out[38]: ['09', '7w', 'c_', '9q']; y$ f: w9 `9 q3 v% S

    1 m, ]7 F* P* s$ t- {re.findall(r'\w\W\B', '09 8? 7w c_ 9q p@') # 匹配的是两个字符串,前一个是任意数字字母下划线(\W),后一个不是(\W)
    6 M* l0 }' r* c3 d, ?, |Out[39]: ['8?', 'p@']6 `% T5 Y  Y# A
    , v9 b/ `7 G  g% N+ J
    re.findall(r'.\s.', 'Constant dropping wears the stone.')
    ( }+ W3 p4 f7 c5 P& l4 l( F( gOut[40]: ['t d', 'g w', 's t', 'e s']
    4 _1 ^  t, G8 Z* A3 [. K7 l" ?* P# `2 D! n) `, Q
    re.findall(r'上海市(.{2,3}区)(.{2,3}路)(\d+号)',
    ) {% K. y" p' c           '上海市黄浦区方浜中路249号 上海市宝山区密山路5号'): s/ I3 B. F- w" k& Y% M

    5 Q. @& n- A% f; c: z! N3 DOut[41]: [('黄浦区', '方浜中路', '249号'), ('宝山区', '密山路', '5号')]1 E, i4 `8 z  t& v8 {! E+ @
    % m/ {- j) q* E8 q/ Q
    1) M$ {+ @% z% J+ N) K
    2" r. E1 z8 E) o, E2 r
    31 `0 w2 x, g9 U/ O" V
    4% F2 L; H5 E7 t: N( ^5 \
    5$ c- `& _" p  `% I- e: Q  E, {
    6* ^. P& e7 w, J
    77 A! k; E& m/ ]
    8
    : e( Y3 T* }* M9, ~8 b' Z3 B, S+ K  j# H- _. g
    108 m0 F8 N, r! ^" b/ g( W4 f
    11
    5 L9 V) E% @2 ~) l. v* F3 ?3 }12
    * m6 |+ _+ R! {# B13% a# }. \0 [; P& v
    14
    ( `4 T# T5 s. }7 x; H/ h! y* E% g15% v$ i7 e; W% w4 R$ Y  \
    169 a1 B0 o5 j/ n  j0 W1 A! g1 n1 O! v
    8.3 文本处理的五类操作- x- d8 \& k. N( f+ P' @9 H- H( c
    8.3.1 str.split 拆分% ~3 |/ K0 \5 E9 r( D0 ?3 a7 h2 m
      str.split 能够把字符串的列进行拆分,其中第一个参数为正则表达式,可选参数包括从左到右的最大拆分次数 n ,是否展开为多个列 expand 。. W; |8 O2 m; Z. ~

    8 ?+ U% O8 R: x7 B" C' t7 Y: Ds = pd.Series(['上海市黄浦区方浜中路249号',
    4 H* c& Q% d4 f& O: D. G9 O            '上海市宝山区密山路5号'])0 \& q, @" H7 b% e  g6 D' g
    3 B+ m) m' J) d% ]8 e+ D
    , O1 l; ?5 c; W, Z
    s.str.split('[市区路]') # 每条结果为一行,相当于Series9 c2 e/ z% u' R
    Out[43]: ! ?' K2 L8 [: n$ T2 t; H
    0    [上海, 黄浦, 方浜中, 249号]) H" t0 _0 G8 u0 l$ r( p! ?: D
    1       [上海, 宝山, 密山, 5号]0 a/ I% L( j4 v3 V- T+ F- N
    dtype: object
    2 t) k! f4 A& p% G  p# C9 y6 [3 L- n( k
    s.str.split('[市区路]', n=2, expand=True) # 结果分成多个列展示,结果相当于DataFrame
    4 M8 R2 r$ G! jOut[44]:
    ) V7 j% F; D" L( P# q5 N1 I4 \    0   1         2
    8 s7 f, c( J; b+ ?+ A. D/ z( K0  上海  黄浦  方浜中路249号% V9 ]  \1 z  u/ A6 K7 M! Y
    1  上海  宝山     密山路5号
    % X5 h% l/ H% W" `1* d( ?& z/ o- t* q4 _
    2. d- m0 }) K; {' H( Q; i% }0 V
    3
    2 p3 W0 F7 ~! L3 M5 [4
    : G% C+ w! g/ b; ]58 O1 h# r/ T- w+ @- E- [$ u$ A
    6
    + F9 d& D0 l! w' ^/ C9 q7: Q# w- |: @! @3 m
    8
    . A& q( s: K& @1 A; O# q& b9
    8 @$ {+ D/ c) u10
    ' R2 f, T+ q; W/ o& {# D11: q. T% C) ?" M4 C
    12
    4 B* Y% T5 A# U& f9 b! u13, e& U( U+ Z8 A' A+ [
    141 u9 w+ h! w" j) I  Y' b
    15( j" n+ a& `$ l9 ~
      类似的函数是 str.rsplit ,其区别在于使用 n 参数的时候是从右到左限制最大拆分次数。但是当前版本下 rsplit 因为 bug 而无法使用正则表达式进行分割:
    2 h- @% r2 }, o% F
    * B  N9 U$ Y7 a! F) T0 b  t* y& x2 Ss.str.rsplit('[市区路]', n=2, expand=True)% L: ^3 @5 x- [
    Out[45]: % w( P' h4 @+ t9 D
                    0
    2 e% O4 [; T; o- U2 }0  上海市黄浦区方浜中路249号  Y6 H. r+ w: ^. `+ t& T$ q8 e
    1     上海市宝山区密山路5号
    6 `- |# f8 e$ D8 K' J1
    ) b! M: d' `9 c; `$ n26 Q, `& J# i; S1 I1 E! v, ~& j
    3
    " w0 g, U7 M2 K0 w4 P  K% j4/ c' Y# F' U8 e, y
    5# l; Y7 b# f. v
    8.3.2 str.join 或 str.cat 合并! z- ]+ o" F1 n* N% R. I2 ]
    str.join 表示用某个连接符把 Series 中的字符串列表连接起来,如果列表中出现了非字符串元素则返回缺失值。
    6 K! j( y# {* u3 S3 x$ N. L( u" Ustr.cat 用于合并两个序列,主要参数为:
    * z+ h+ m! `/ [+ p3 c6 Ssep:连接符、5 L3 y9 Z- O0 _9 V0 J
    join:连接形式默认为以索引为键的左连接
    ( [! A6 t& l2 I$ c. g6 vna_rep:缺失值替代符号
    + Y/ s9 v# M! o+ C' a, js = pd.Series([['a','b'], [1, 'a'], [['a', 'b'], 'c']])- {* P+ G+ I. _6 ~& g
    s.str.join('-')
    2 G( D$ e7 U3 Z: QOut[47]:
    ( V' w7 a( k* I) l9 W9 W0    a-b
    3 C5 i0 K+ |( n$ g& _4 x6 c  j% T; c1    NaN
    2 p* x0 T. ~$ B3 {7 \% I2    NaN0 }' s9 d4 p' U: D2 H4 P
    dtype: object
    2 L, K* X; m$ t% Q1  w* j, _/ c' Y7 F" p  B: Z
    2! m5 e( Y  E0 U3 U3 i' Q
    3
    9 Z7 ]: j9 l+ I: ?6 \, o, z40 r- c+ B4 O8 H; f
    5
    6 e; N; ?4 L( @  ^, ?  g! S8 ?6( [" I. F/ Z, o! f4 o8 H7 s/ M
    7. ?8 g0 ^6 w: y, K- \; n) `
    s1 = pd.Series(['a','b'])* B7 a. W4 Q5 a; o
    s2 = pd.Series(['cat','dog'])) g6 b4 m) u! y9 Y8 i7 `: J1 P
    s1.str.cat(s2,sep='-')( M$ o% t& o6 u( M4 ~7 |0 i
    Out[50]:
    . I. L6 V  W: A! n  z0    a-cat9 G8 T+ F& ?! T2 ~. P
    1    b-dog  Z9 e; S. m8 t8 s; X0 M9 F1 T
    dtype: object
    / S0 e3 r! ^, g0 i- r+ M
    / E& V4 W4 N; C. `$ F1 |) b5 Z, Ys2.index = [1, 2]5 f: U" K9 |6 I& l% I
    s1.str.cat(s2, sep='-', na_rep='?', join='outer'); ?' ]- {! z0 f# h3 O
    Out[52]: 8 B; y: a* y2 v+ M8 K
    0      a-?4 a: y1 M4 ]9 m$ l% ^4 A
    1    b-cat9 i, J( c- v. V1 ^
    2    ?-dog2 m8 u1 Y3 n0 g1 r9 H
    dtype: object" H1 c: W! j5 O# J2 z$ \2 `
    1  T) J3 F6 R- w% H
    2
    % t) Y4 K# p% U7 }7 v& ?) l3
    ! [* g: Z  [2 E" _0 v8 b2 g4
    " P6 `' o% O8 _7 @" ?# F, U2 o7 Q5
    / D2 O4 d5 N& T% z  t6 U6 P' b6: n" m1 z6 a( n& i+ ^% F2 @
    7
    5 H% l) s( E; s3 R& n8
    % m5 n1 [7 V- i4 n  @  R) Y9& q) z1 O  ~. R4 `
    10+ U3 e* R- e; `% l7 ]. s1 q4 r' C7 S$ H
    11( y5 K6 c+ ?! o' |5 {
    12
    # z' X9 h. f, R2 D13
    7 J/ a3 Y, N: F& U. u6 Y2 O. r! \( O2 c14) \0 X* H) P  B2 F9 ]) ?8 o
    15
    $ `( A( \, v8 m# h" |. S7 h% g8.3.3 匹配
    + o$ }# L4 ~$ X. ^5 h! sstr.contains返回了每个字符串是否包含正则模式的布尔序列:
    2 h0 ^8 B$ f. Us = pd.Series(['my cat', 'he is fat', 'railway station'])0 k/ F5 M0 f& c0 R9 r- O
    s.str.contains('\s\wat')
    8 v2 V: Q4 T4 \1 u9 y$ b+ }1 a; I5 x9 Z4 W* }- z+ V; t
    0     True
    ; D0 O' w. V3 T& r1     True+ Z) W' V6 t4 ^5 ?! F  ?% v9 G
    2    False. M+ }' T$ `% ?. ^# R1 _
    dtype: bool
    ( b6 U( ~. o: O. `6 X& i2 h1
    ! T) R! @: X+ [; J8 G( k2' K2 E3 D, y; `* L6 v5 U: K7 Q8 z6 G
    3
    / X8 I% N1 n! Q& S% h! ]- ?4
    " y  {# ^7 w( e: A$ g6 b! I1 b- H. m. |51 O7 T4 s2 X6 ^& C
    6
    8 T% {# ^+ u: `% Z7( x: ]7 u, T8 l) o; Y
    str.startswith和str.endswith返回了每个字符串以给定模式为开始和结束的布尔序列,它们都不支持正则表达式:, y9 s2 V: A. b6 _; e0 ~! @, r
    s.str.startswith('my')$ F. {& e( y- W& Z8 b: J

    5 t+ H; S+ ?8 t8 S+ V5 U5 e0     True
    ' G: o$ A4 J7 ~5 O4 V6 j1    False) e1 J% R* Z7 X, ^) _% {
    2    False/ E9 k! o) \+ Q
    dtype: bool
    ' C& y3 d" q, W9 \1+ N1 {9 P: {9 U  ?& b
    2
      S4 F5 ^' W+ b; z  {* t# o3; N# Q8 [  b2 f
    4
    + ^! R1 C# ]; t5
    3 Z2 A8 M6 U1 w0 y! _1 Q3 C" _5 n69 o1 b9 F6 ?( ?! p
    s.str.endswith('t')
    0 j+ P9 M" Q. ]) U  x8 t1 p1 Q& B9 k; Q/ E& A& s: m7 C. a, g
    0     True$ t. v- G- ]2 x/ c& ^
    1     True
    / @, t1 a. h9 `0 [" C2    False) x4 x- m' O# E* c6 S: k
    dtype: bool: f4 U: i3 `7 p9 P# U# o
    1
    6 k5 R0 K5 ]# y7 p$ w. b2
    8 p- q$ W7 M+ |7 C3
    * y) M+ b% D# D' n4& L: i9 r# Z- ]7 N* a- e
    5! ]$ Z9 m4 P& S! w: m
    6
    / E' v2 g: k/ w9 ?8 b1 c6 ]4 ^str.match可以用正则表达式来检测开始或结束字符串的模式,其返回了每个字符串起始处是否符合给定正则模式的布尔序列。当然,这些也能通过在str.contains的正则中使用^和$来实现。(貌似没有python里的search方法)
    9 Y( q* n3 \( T# z- e) Ws.str.match('m|h')
    4 r! k7 Y6 S% f' ?3 As.str.contains('^[m|h]') # 二者等价
    : t- M) C. ^7 ]
    + p4 O  |8 P6 b- E& `$ U0     True
    " k2 E" p8 R& e) D1     True
    8 D; t/ T1 I; `3 E3 B$ L2    False
    8 U5 B7 s, ~( D6 ndtype: bool
    7 B, u# Z( G5 U1 ^2 Y+ ?1: D4 W' Y& `6 m0 k' P
    2
    8 p0 O: P: o1 J3/ l4 g0 v3 W. }6 R; p2 Y1 z
    4
    5 `4 }( M7 G) O( l9 ]% J' p56 x6 X: Q9 p. i8 M! S, g* D
    6
    9 I, ]  x0 m# c$ t  Q7' o$ b3 E+ A- m( U
    s.str[::-1].str.match('ta[f|g]|n') # 反转后匹配: r6 m9 B; T+ V8 V; F
    s.str.contains('[f|g]at|n$')       # 二者等价& C  |/ t) S1 L3 O' P
    7 v0 |. m( S/ i0 Z! g
    0    False3 J! x. L4 l# h5 g3 e6 Z7 r
    1     True
    ; R+ V" L/ {3 E" P2     True, R9 s: V* d+ L8 L
    dtype: bool
    , q- p7 f- r6 I8 \9 k: o1
    9 o- ?& M) B8 ~/ o2- l' f' J" n* {3 J7 X
    3# s5 V% A( V# A( H+ @# A5 P
    4; v3 X0 K3 I1 U0 ?
    50 T; p  q& `- a) M9 }
    6
    $ _$ z7 k  a, l6 M$ [2 b9 o+ p7
    6 b" P# j- D, Q4 _* |: jstr.find与str.rfind返回索引的匹配函数,其分别返回从左到右和从右到左第一次匹配的位置的索引,未找到则返回-1。需要注意的是这两个函数不支持正则匹配,只能用于字符子串的匹配:( ^% b$ C9 M9 ^7 h1 z6 O
    s = pd.Series(['This is an apple. That is not an apple.']), V. r$ D# S9 r) r2 _$ \

    0 `8 }8 h6 [. X) Ys.str.find('apple')' K2 i- m! C! u* F6 A/ M1 j* h; }
    Out[62]: ; }  E* I! C  P: B, U; V
    0    11
    9 V& B  }7 N  m7 O8 }dtype: int64
    9 L! `- k0 [  R9 V8 L
    6 b  p4 E1 y! R4 n1 W; Xs.str.rfind('apple')
    . s! J% Q% l2 J; WOut[63]:
    6 k. g: e* i* E$ J) U0    33  p2 {/ S  a. Z+ Y5 A
    dtype: int64) U) f) B, ?$ e
    1
    7 G9 p4 b5 i1 @4 S2
    8 E) n( h7 F6 W1 P3( k( e) T4 T% A2 M0 N2 v8 }
    49 G/ M* [3 i2 L1 G: I# w
    5
    5 R8 B; h' g* u6 X( R6( H% E- O* Z2 i, y1 L, ?1 G% N- m
    74 e; p0 D" D% x" x0 g
    8
    , E. P! c4 K  Z/ c& t( h' c, d9! t8 k9 O$ _7 x* P* D" @
    10* n" ^+ o8 F& b. w4 T* `
    11% ]" L0 k: P: H4 j
    替换$ Q4 y  s' Q4 C0 p% G
    str.replace和replace并不是一个函数,在使用字符串替换时应当使用前者。
    3 Q8 W' ~5 O% d; m1 \0 p" js = pd.Series(['a_1_b','c_?'])
    6 z8 t; `8 c+ c& d  z' E; j# regex默认为True,表示是正则模式,否则第一个参数内容表示是单纯的字符串,也就是匹配字符串\d|\?
    " j# l! p1 u0 I+ p+ y% ss.str.replace('\d|\?', 'new', regex=True)
    * R$ G! _3 J& F0 [) E7 p1 I& O2 g4 g, Z+ {1 e' B( T6 G7 x
    0    a_new_b
    & M0 T7 k, F: R% e$ B# W' s  h0 d" r1      c_new
    8 c8 }% r$ }) y$ Z+ Kdtype: object
      u9 M' z' o& s9 M. C& s9 J1& F) ~6 f, q! k4 p! P. t( P! [' }
    2
    ) m# v5 S" \5 i  Z( `30 G6 Z" m8 h: ~+ M7 r: C
    4
    3 j8 k7 T# j+ O$ _0 [5# n, Z# y) Q+ V0 |
    6
    4 r4 ~7 w* L" f! Y, k) m7
    2 ?( q% z3 b3 r% K- X* o  当需要对不同部分进行有差别的替换时,可以利用子组的方法,并且此时可以通过传入自定义的替换函数来分别进行处理,注意group(k)代表匹配到的第k个子组(圆括号之间的内容):
    # V$ D7 u- U# ^3 w, W' f' \* k# K& [' ?" j
    s = pd.Series(['上海市黄浦区方浜中路249号',
    , j3 q# g% g# U3 V7 g$ o                '上海市宝山区密山路5号',2 M1 z% i% u% a2 p% x
                    '北京市昌平区北农路2号'])
    - u4 x4 E; s3 t$ L7 ~- mpat = '(\w+市)(\w+区)(\w+路)(\d+号)'
    ' Y9 c3 J: a2 t' k) T3 {city = {'上海市': 'Shanghai', '北京市': 'Beijing'}: I! U9 k' y! T! c( m) o, P
    district = {'昌平区': 'CP District',. L1 P: a( E4 \! ?# G2 x
                '黄浦区': 'HP District',
    0 X! Z8 B! V. `9 y2 M9 V            '宝山区': 'BS District'}1 I" ~7 ~4 B) R) \* b
    road = {'方浜中路': 'Mid Fangbin Road',. W' ]& L* Y& m# C# M
            '密山路': 'Mishan Road',5 c9 m# a# ~# ]* G4 {
            '北农路': 'Beinong Road'}, m3 L: x) \+ q! T- B, ~
    def my_func(m):5 Q+ T! I: t& [8 y+ \1 D5 z8 A
        str_city = city[m.group(1)]
    7 H% R# X" ~- H) a7 _    str_district = district[m.group(2)]- x( i# i' p, I1 W+ @
        str_road = road[m.group(3)]) K2 A' f# O% d" x
        str_no = 'No. ' + m.group(4)[:-1]
      b0 m  r( h6 N) @* g% v/ \    return ' '.join([str_city,# G* @0 T: |  Z4 U4 G# {7 b! V
                         str_district,
    # X4 Q' I& k) D) V+ @                     str_road,; T" i/ D' B5 e* v7 W2 T, I
                         str_no])
    6 S3 U# [- v6 y, S! y. Qs.str.replace(pat, my_func, regex=True): a9 c$ ]) R' e# l, R, k  q
    2 z* R$ s( O0 s& e; x. q
    1
    2 ?' L' Y/ g7 ~9 A2 k4 n! @2. e' I) j% I, Q% C" m* ?
    31 T" t7 R0 w0 B! I  M4 z
    4
    & N8 p9 a6 q0 F0 \. ^7 q59 e  G% ?2 g0 `
    6
    ! `% V% ~+ ?0 o0 M5 J7
    ) i2 \: C+ p, G5 o% S' I7 [" ~$ j2 N84 Z5 q5 H! ]& T$ K# A
    9
    " P( k' i0 b2 \4 r* ^8 f! I10, K' ~2 s6 `  Y0 [
    11) q% F- O0 P( b! o
    12
    # }1 j* {8 x2 x- W2 }# j. m4 p13) {. D8 c, K% c9 D/ v4 k! a' H
    14
    * J7 A, X5 {' A, P, ^# P6 O15& K' A% b. _- z" ]# F
    16, X) P5 I6 S. ?7 l" Q% R
    17
    , b  j: l+ K2 W$ k2 b1 h18
    + d9 r( r- ?7 a* z0 B) g19
    $ T6 ]) H; L. d20- G+ L( H6 \$ _/ M
    21
    ' q. Q9 r0 q# S8 B! j5 R6 a% D0    Shanghai HP District Mid Fangbin Road No. 249
    1 f3 N( S1 [. E$ t4 R! n9 D1           Shanghai BS District Mishan Road No. 5
    ( C+ M; v4 E0 c: f0 N' N5 r* `2           Beijing CP District Beinong Road No. 2/ b# `2 t0 i8 q" G
    dtype: object
    1 g6 B! X0 ^+ |% X( a9 x5 m1 c1
    # R- T/ e+ D7 u2 A" G% l3 j, {& h2* _8 m2 v" }5 Y' }7 X8 F* q
    3" n( A) s- S1 O1 o9 t
    4
    8 S9 C  z! O6 N2 f- t这里的数字标识并不直观,可以使用命名子组更加清晰地写出子组代表的含义:4 q. |" A% n, }; k9 O

    " z' B$ p7 H, C* |+ r% x$ n0 q: @! ~: j# 将各个子组进行命名
    . [* D3 O4 t: }$ W* |% N/ E) @. spat = '(?P<市名>\w+市)(?P<区名>\w+区)(?P<路名>\w+路)(?P<编号>\d+号)'4 M! q' J* y8 _. W8 V: O" |
    def my_func(m):
    ' y4 W3 C1 O0 P    str_city = city[m.group('市名')]
    ( ]  j0 h) b# m- ]( i2 ~    str_district = district[m.group('区名')]
    8 P0 b5 \$ x6 n* H- Y- ~4 v6 x    str_road = road[m.group('路名')]- R& c$ K: v, g/ _( P+ A
        str_no = 'No. ' + m.group('编号')[:-1]
    ' L; F3 \; V4 F* m& C    return ' '.join([str_city,
    1 X% v  o8 a8 _- e% W                     str_district,
    4 C1 Z; ~( Z/ ~* R# k                     str_road,
    3 [$ A7 ~2 O0 A/ N  ^  N6 e9 p                     str_no])$ B7 e% ~5 M' K" n6 Z5 ]: d
    s.str.replace(pat, my_func, regex=True)
    ) n% m8 ~6 @" z) y4 S4 a  T2 G1
    * @# I& v+ ?8 \- W) L( w21 V/ _1 x: j9 L) u; L5 e
    3
    7 I7 z1 j4 ]" m4 m0 X8 K4' S5 U2 c7 F2 N. h) w. w
    5
    . m& @3 h/ q, z1 e8 V+ U6- x5 c  ?" Q6 {
    7" n" z+ E3 a$ ]$ O, S
    8# B1 P0 _1 R6 N% V
    9
    ( Z; P, I% I/ u4 m# {7 O2 b$ N10
    ' z! ^8 R& x- a# |* p( n11
    ! S- f" N9 E* N, X; f! u  h12
    + T1 p. v$ p' H# n! X3 {  j0    Shanghai HP District Mid Fangbin Road No. 2495 x( K. s4 z) ~1 v- W0 h
    1           Shanghai BS District Mishan Road No. 50 }) d: ~$ B, B  }  u
    2           Beijing CP District Beinong Road No. 2$ s& O: O/ g2 P/ A8 s* X7 F1 |6 h
    dtype: object
    6 y/ o8 c; L: p. U# l4 A: U& j10 ]% U5 w* f* R- k
    2
    ; M; q: c) w% |5 W3
    # I- n) M8 K! c5 O1 t$ r4
    , S2 m, f3 r% u4 H  这里虽然看起来有些繁杂,但是实际数据处理中对应的替换,一般都会通过代码来获取数据从而构造字典映射,在具体写法上会简洁的多。
    / p# B% f& L$ t0 r# U/ Z7 T* i1 A1 {! b; C& @& I
    8.3.5 提取1 N% A; Q1 |. V% m& l
    str.extract进行提取:提取既可以认为是一种返回具体元素值(而不是布尔值或元素对应的索引位置)的匹配操作,也可以认为是一种特殊的拆分操作。前面提到的str.split例子中会把分隔符去除,这并不是用户想要的效果,这时候就可以用str.extract进行提取:0 Y3 I2 ^; @' |1 L) V  }4 T
    s.str.split('[市区路]')0 g7 z9 A4 f6 m7 n2 q
    Out[43]: 2 |7 [) {/ _, B9 w" \- m3 Y
    0    [上海, 黄浦, 方浜中, 249号]
    + a+ d+ q4 e4 D0 W+ [1       [上海, 宝山, 密山, 5号]- u0 z2 D# j+ m# D! W* ~" ?+ d
    dtype: object
    4 u6 h3 z6 [4 u; Q  _4 [, z9 n7 e& `- ]6 W! T
    pat = '(\w+市)(\w+区)(\w+路)(\d+号)'
    . v2 ~, b; f4 W5 as.str.extract(pat)& A' |$ a# f: F8 e, X8 k
    Out[78]:4 _# G" r$ E3 T6 D# _- _5 o3 t% z3 c& ]
        0    1     2     3! A" t. A2 _& L5 V$ H# X
    0  上海市  黄浦区  方浜中路  249号
    2 p2 `" E$ Q- {+ w7 @6 t9 J0 f1  上海市  宝山区   密山路    5号
    : c/ h; d8 p, s4 T+ K" c7 S5 K1 D4 |2  北京市  昌平区   北农路    2号
    # H) I$ J' M( v! E, A  @$ \5 t; w1
    * p' L1 y$ }( K$ E8 i2
    8 J( j2 I* |$ p+ Z3
    8 i1 N" f, m* F; k  a2 ^0 W$ k$ U4
      E1 G' ~- O6 ]* V6 a" U7 M, [' g5
    * T' n, F1 [1 ?9 E4 N, W  m) h6
    + E- [; i& Z4 Q8 Y/ d7* t' X& f- c: Q, T% \5 N
    8: f/ S: L) }0 O: I; }* P, K. i7 w
    9% A$ H' C, y* A5 M- b# B4 u$ V
    10
    ) A/ O+ ?4 V5 f: a  e- |11
    * U3 X0 h( U# T" H; _129 Q* y, @: w2 ^
    13& l, I2 f  N/ S, F8 Z+ K9 }
    通过子组的命名,可以直接对新生成DataFrame的列命名:
    * @' @3 ^3 X* Q) K; ~; U/ ~3 L, T& X" [6 J! f+ \
    pat = '(?P<市名>\w+市)(?P<区名>\w+区)(?P<路名>\w+路)(?P<编号>\d+号)'8 B! H4 m7 Z$ V0 V3 h* m
    s.str.extract(pat)' m0 A/ }& o( B' K0 h
    Out[79]: 5 s! r" _9 [  o( Y) e5 K
        市名   区名    路名    编号
    1 W1 D) m9 X# h% ]* g5 a0  上海市  黄浦区  方浜中路  249号) j; T5 d+ f& e2 z- Z
    1  上海市  宝山区   密山路    5号
    ' I- Z) Q! G/ U' C8 [  p3 [2 O2  北京市  昌平区   北农路    2号
    8 |# H6 w5 f. T, @1* B' Z: W+ `! I5 }  v# g5 k2 F
    2
    + t' a9 M; E" y6 u39 I  B. D+ b7 z: J8 @; `- Z% T
    4
    * {5 o6 }! }  j% W* W) q! f' {5
    7 C1 m$ Q* T0 A/ }' w6
    6 D6 v4 k( F0 i% B0 y7
    " }# ~  b* u% `" B  Mstr.extractall:不同于str.extract只匹配一次,它会把所有符合条件的模式全部匹配出来,如果存在多个结果,则以多级索引的方式存储:, i/ o) R1 J0 m4 l
    s = pd.Series(['A135T15,A26S5','B674S2,B25T6'], index = ['my_A','my_B']): l# D5 w9 \% Y" k
    pat = '[A|B](\d+)[T|S](\d+)'8 r3 v9 w# w! c" W) m
    s.str.extractall(pat)8 l- ~. `8 v5 O- p6 N3 G1 V, M
    Out[83]:
    $ F+ F% F" g# h/ s$ X; Y       0   1
    - p9 r' r# A9 n8 X! W     match         ' j$ |, j% p* b- e- y2 A3 c6 g
    my_A 0      135  15
    $ m. x$ B' X4 u, t# ]4 r     1       26   5
    " H  u% l1 b: K: X( jmy_B 0      674   2
    5 A: p0 Q& N( U     1       25   6
    6 B8 R7 A1 M/ I  S1: v$ h3 ^, l/ q- i
    2
    9 V8 _: [4 b+ n3
    & u9 V+ c- t/ {& k) N4
    , T7 o. N# c' A. d5" j" C9 B% R( e, ?; d/ Y+ o, D( D
    69 \! h, w  Z8 M  c
    70 g" X$ w% @; A) I" |# ]2 c
    8
    - y) p0 }+ V. _9+ |3 x. f6 N8 b6 J2 g
    10' Z. t* p3 ]/ W* y0 S
    pat_with_name = '[A|B](?P<name1>\d+)[T|S](?P<name2>\d+)'
    - m" W% W( d+ |0 Q( }# is.str.extractall(pat_with_name)& q8 R6 g' C) A7 [+ f% S
    Out[84]:
    1 u) [: |# K  y3 N, \           name1 name29 \: U1 B* }  a1 i2 ]7 R
         match            
      U$ ^0 ^% a" [) Jmy_A 0       135    155 p# h* o2 U* y, l* t3 N5 d6 C
         1        26     5
    3 |. c- K1 k2 }& Y  i  k" E$ dmy_B 0       674     29 m5 G0 J# K" D: m
         1        25     6
    4 D) S: u5 p" g( @1 a( z5 t18 O& [; D' S$ Y# d
    2/ c/ @! S( s- F( n/ m1 k/ z
    3
    ! _* j6 s9 f/ R( o3 K4* v3 ^/ ?. O; V, J
    5' |) u* P; R. W+ U  d& @
    6) j6 w& b% q" W  Q$ ^
    7$ C+ w) j8 f; I! Q7 Q
    8" q' d9 M, q$ s9 d, d
    9
    ! R% T" |3 E" zstr.findall:功能类似于str.extractall,区别在于前者把结果存入列表中,而后者处理为多级索引,每个行只对应一组匹配,而不是把所有匹配组合构成列表。
    4 e+ x% W2 D: ]- a9 U8 u% ys.str.findall(pat). d$ m- Z- B' M" I
    1
    ! A, o2 p$ ^. B# P9 N% ?my_A    [(135, 15), (26, 5)]
    9 o- F' s. X. a4 C( W& Q# W, U2 qmy_B     [(674, 2), (25, 6)]
    " `- @* D4 W+ I. G, C9 ]* Udtype: object
    , }. U3 T& b. W17 T( A. T: ?2 _3 {6 ^  V1 g. `! s) a
    2+ ~5 B5 W6 g0 A% O# Y8 S
    33 L% d; x2 T6 d  m  G$ `+ A
    8.4、常用字符串函数, O) U! u3 r; M( G
      除了上述介绍的五类字符串操作有关的函数之外,str对象上还定义了一些实用的其他方法,在此进行介绍。
    : U; }; N" D2 [  {1 i- j+ i! I1 }) m2 @- _/ u( r, z0 t! q4 ]
    8.4.1 字母型函数# E4 {: D) J; ~8 q. q. X0 m
      upper, lower, title, capitalize, swapcase这五个函数主要用于字母的大小写转化,从下面的例子中就容易领会其功能:
    ' ?: p+ |. [" Z2 d6 j9 Q& z$ A- W/ z, v
    s = pd.Series(['lower', 'CAPITALS', 'this is a sentence', 'SwApCaSe'])2 }8 u! j- _; [+ p* X

    / A( d  [2 d* K- z4 \; M1 fs.str.upper(), h3 p# ?& b$ {4 _
    Out[87]:
    , Q4 @% p/ \; N0                 LOWER
    ; p7 A) S8 ?5 \% {; @* S& [8 w1              CAPITALS
    5 ?9 \) x6 l7 \5 K& j1 J9 y  F2    THIS IS A SENTENCE
    " C+ r* v' L" e3              SWAPCASE2 l! A- o5 Q. A1 |0 U
    dtype: object
    ! ~& |( {7 K" y  k, f& @) m) U# z: ]) M5 V
    s.str.lower()& U. ?& P5 r; ]  C" r
    Out[88]:
    / g0 W+ C. s1 N0 C. b; |, x0                 lower
    6 v* ]" P( I4 j9 A1              capitals
    1 S+ S) z& M: v- S0 y  e9 T9 _3 ~: `3 ]2    this is a sentence
    ! @3 T7 O3 Q* ^7 n: E0 N, p3              swapcase) b% l, L7 ^0 j; G
    dtype: object' A- |8 |$ S* j/ {, W! P& L
    ( ]5 Z0 A" h( s6 S) e* X* N8 @
    s.str.title()  # 首字母大写
    8 f# w, w+ J3 M& XOut[89]: ! b: C. E/ O' `; ?3 A: j
    0                 Lower6 o& d+ P( O  E$ X  {. A8 f
    1              Capitals
    ' C9 e9 {2 \4 c9 Q2    This Is A Sentence
    : k3 d. }% y6 z) l/ D& ?3              Swapcase
    , a4 G4 F. M9 ndtype: object
    9 a4 q4 Q) q. B/ X3 ?4 t
    * m( w! P, K6 Z" [6 P1 m2 Vs.str.capitalize()  # 句首大写
    8 z  L& T* L5 \  f1 x# JOut[90]: 0 R9 u6 {9 ~0 q, d9 L2 I% e
    0                 Lower8 Q/ t$ O$ V. |2 l9 B# |
    1              Capitals
    1 \$ D6 g% B/ D. t) u2    This is a sentence
    0 ~+ q3 b3 g% T" p3              Swapcase: L  D  Z4 O9 L7 D( W: M6 s! `/ a
    dtype: object
    ' S) V& p0 O2 @
    " _" I/ d# F& x, Ds.str.swapcase() # 将大写转换为小写,将小写转换为大写。
    & n* N; d0 n+ T" GOut[91]:
    + F! w4 ~# d3 K0 Z; j$ O/ |0                 LOWER
    0 {: D- x! t# {) e- y1              capitals3 ~, b4 C9 p( h6 }4 N% F
    2    THIS IS A SENTENCE
    / H9 v' O" v, M) v* K3              sWaPcAsE
    ; V) ?* B1 w! ?/ c1 _, V8 }dtype: object) w) r0 q7 H* \0 X
    8 Q, P# j! w7 h1 e! e5 v/ Q& ]
    s.str.casefold()  # 去除字符串中所有大小写区别
    " r+ J" U) P% j* o* l  d
    " M' Q8 O/ A% {+ N0                 lower
    ! j. h3 m" g& N, r# H, a! H2 ]1              capitals
    : ?6 v5 z& C& _9 U2    this is a sentence' M( Y7 h: t, j) |9 @- S6 c
    3              swapcase' N! A( D" Y* r) Z( L
    ) r& G) {. v9 V( |/ F
    1, K* R, t8 \1 C& T1 u
    2
    4 l/ C+ Y# D" P1 U2 m39 o# a9 \+ @' M4 R; Y% z
    4
    ! y1 }0 ?5 r* X; V7 ~0 X58 I- {- k/ n+ c
    6
    1 q& |2 n( b1 a" H; a0 x7
    6 Z1 g* }  x( [8 Q) P! }! ~, p8" A# q+ {6 d7 s! h. _# z. u
    9
    : s9 \8 m. w  l( W10. _( Z: ?6 h6 _& {$ z
    11
      t( m; s! t" ^1 E+ @5 d12
    $ T' d; C3 e. n7 v13
    / k' H$ L" z/ b& f7 q141 {- O& Z) k# l
    155 C) r4 s* r' D* l1 ]6 x  A
    16, j' Q$ \5 v2 m/ p* i
    17
    # s, c4 v8 K5 x  w8 U18
    0 w- l- R- u/ t' Z5 j/ V% R& t19* P& C5 B' l% e% `! M
    20
    + Q$ l& @5 x9 o' Q; L* v( K+ Q21
    8 l5 o: a$ a$ p. X, h! x$ L8 K22
    4 M* t. o( v8 ^239 U4 ^  V# I5 E+ C
    24
    9 d" T: e2 ?1 K1 v8 c6 q; t' }258 z/ ]2 I: C- J: ~6 Y$ Z
    263 P, @+ x# Z) A6 Y- X
    27" |" l' a. G! D' D  x  r3 \6 {$ _
    28' ^% }5 S' {7 S
    29: T+ j5 j6 o% X/ n; V9 u9 z' S
    30
    # ]* L! N  e% {31
    ' Z& _  r2 p1 z- |( x. q+ s) D32  _: a6 H' i, _$ r2 T' H- O
    33
    3 V3 |4 l4 W* R2 P. m34
    ! k9 j2 G' s# P# `35
    + j0 U. z. Q3 `; e+ _36# L2 j' J( v) \
    37
    2 y, z' F' g0 E* ]  F* n38: D# z# G: z- w7 r5 ?! ?" e
    39
    ' `  t0 g& N1 I0 |7 v+ G$ Y# r: m40
    3 l: x- e) p# x4 ?' D41
    ' P8 o; @. n" a42. X( G7 w; d, X, J- ]
    43
    6 i* e6 t! D. Q- M44( U- d# L1 s, u2 ~) X- I1 ^
    456 L( O( h7 K( p
    46. s% Y) l# v- L; b
    47- p3 t5 k# _( G! z9 @" i) S
    48- O+ B- H2 ]2 K" Q# [0 A
    8.4.2 数值型函数" f9 j  M5 m6 A$ F3 v5 n  s9 u
      这里着重需要介绍的是pd.to_numeric方法,它虽然不是str对象上的方法,但是能够对字符格式的数值进行快速转换和筛选。其主要参数包括:) \7 K% n  [- U6 f/ l' E

    + b0 l5 w# K1 Lerrors:非数值的处理模式。对于不能转换为数值的有三种errors选项:
    8 K+ E# e: q3 }raise:直接报错,默认选项/ d5 j$ C4 G. S" T: L. ^
    coerce:设为缺失值0 ~& u3 M$ K" [( H3 m, i
    ignore:保持原来的字符串。
    . t: [. X/ S' v% \- k, Kdowncast:转换类型,转成 ‘integer’, ‘signed’, ‘unsigned’, 或 ‘float’的最小dtype。比如可以转成float32就不会转成float64。0 e5 z/ ]7 R" G& T7 U9 }8 N: L
    s = pd.Series(['1', '2.2', '2e', '??', '-2.1', '0'])
    6 n4 \9 |2 }6 ?# Z$ v& i3 D, G0 i: i0 B; a* W: e8 D0 n, e, G
    pd.to_numeric(s, errors='ignore')7 X8 T/ ^* k/ q/ m: V. v
    Out[93]:
    4 Q) v0 m1 Y/ Q1 E  K- k0       1; n+ }7 R. ^6 r5 {
    1     2.26 b+ ]( [. k, D& h) z
    2      2e
    & Z1 E) d5 O4 H7 w8 g3      ??
    , Q# s+ x* t8 I/ |8 d5 v3 G. }$ c2 v4    -2.1
    " N+ M1 |& e( c; F- o4 H5       0
    ( ~4 e1 `% ^( ~( xdtype: object5 ?5 z, L$ e' f: y2 s  J

    , B# ^& J& y6 m, Fpd.to_numeric(s, errors='coerce')/ C! p4 R& P" [- k
    Out[94]: ; P5 \: Q# f" C/ t1 M
    0    1.0
    7 k- ?4 ^( |; N$ B1    2.2
    3 M2 i( @: p2 v4 D/ R" _2    NaN" q0 v- s5 o3 S2 d
    3    NaN2 d6 ^7 ^  q4 J
    4   -2.1; p8 @0 b: ?. Y4 H) J3 W
    5    0.0, |7 E$ Z: E- ^. A- E" ^
    dtype: float64
    7 |+ |% S# D0 |0 S$ @% b% v5 E6 X3 J" S2 Z  {( Q
    14 Y. ?  }' A1 T$ }# K3 f0 c
    2% m% U& L( T, m! k
    3* V3 X5 b& y5 p6 l, M+ X* e
    4/ a; d! _8 E; r7 P1 S
    5
    9 H* Q" }1 B' y& G9 k6
    2 q4 k' V( ~. a4 P/ n. W, q7
    # N* ^2 V# \( Q. \8/ D% ^5 H, F. i' m. c/ O+ x
    9
    6 L) o& g# W( h% A; t8 ]10
    6 [" t, N& t- f; U8 a  g) d11
    / C; s3 O$ h: @) w8 K12
    ' C6 _3 L9 c+ a9 L13" W: `" c9 x" x3 X9 b
    14
    & w* i+ }* E; C7 u" X! ^1 T9 g' T15
    8 V- k6 N4 N" m$ n  g7 |: W163 S6 j# J, X$ }$ h8 Y  `
    17/ d% D/ Z  V# r
    18/ S- I3 ~" \8 }" A+ q, b) G/ n
    19
    , F: U+ E8 g! L20. F9 v: R  O4 q( K3 l
    21# W8 @& Q7 h6 ~1 ]
      在数据清洗时,可以利用coerce的设定,快速查看非数值型的行:
    3 e2 d7 x6 {8 t# b/ G9 @# F- R
      v/ S4 S$ Q  _' y! xs[pd.to_numeric(s, errors='coerce').isna()]9 O0 ^# V$ T+ P+ n
    Out[95]:
    " [0 Y5 F; c: V2    2e
    ! K3 M( \, a4 m" Y; k3    ??
    / x- a9 e( }" h9 g& t& `6 E, ddtype: object% }! `" Q4 L$ k  y6 p
    1& \; d/ ?9 {4 x: X) Z
    2
    * e! ~4 t& ?" d9 L& }  w3
    , X  r( G6 {& _* q) ?# p& b4
    0 [/ n3 Y/ A3 Q, i5
    8 q9 |/ L' O" ?/ B8.4.3 统计型函数& Y1 U) z+ s( B5 q6 y
      count和len的作用分别是返回出现正则模式的次数和字符串的长度:! o2 d, x% c5 A) t$ M

    % j! G+ T. I6 ~: p- @8 _s = pd.Series(['cat rat fat at', 'get feed sheet heat'])
    - B1 U; Z& P! X2 ^( r  ~! @# P2 A: s6 L  K' S: W2 I" i
    s.str.count('[r|f]at|ee') # |左右两种子串都匹配了两次
    ) H$ |& H- V/ A$ rOut[97]:
    1 F! E+ F3 d$ Q- g6 d4 v# \0    2
    - T% L: O" z, O; s8 P1    2
    ( R2 }+ S, k) O9 n: ydtype: int64
    . ?+ y- p) ~4 h7 Q7 C: Z' W: |# t# T; i
    s.str.len(). z3 R8 E) T5 H9 R) h
    Out[98]: + g) w$ |6 ^6 U  j5 I$ z
    0    14
    3 S  v( G. s, R/ W9 @1    19, \5 k% l9 G; T$ B) o
    dtype: int641 w! c% {& W% v( h! N% x
    1. q- t* ^  {+ r  a
    2
    5 v5 J2 i% p, j3) J( l: H( M: G  W
    4( t' Q9 @) ?! O. r2 v& j) Y/ A; D% R$ b
    5
      G. n  [1 O- Z- w/ ~' R6# V& w1 ?$ |) _; ^
    7! [( ~# \! R; V9 }. x2 W& ]
    8& X& i/ N) s# U0 V; s7 y
    9
    3 n7 M: V0 p, l% [" p  \* y10) \+ m; A  A1 j* `! E8 N% b
    114 |- q4 h' d3 d5 ?
    129 Z/ V8 y. S8 e4 }& Z
    13* V5 V3 n6 w  H, L- F: a
    8.4.4 格式型函数
    8 `7 Z6 E1 A7 S2 W$ c) D8 S& l3 }  格式型函数主要分为两类,第一种是除空型,第二种是填充型。其中,第一类函数一共有三种,它们分别是strip, rstrip, lstrip,分别代表去除两侧空格、右侧空格和左侧空格。这些函数在数据清洗时是有用的,特别是列名含有非法空格的时候。3 n- I! d1 I7 H6 {3 l4 o7 N* O, X
    * x  X; Q1 @; t2 I
    my_index = pd.Index([' col1', 'col2 ', ' col3 '])
    0 F" G. y8 J  k! ?, X$ \" y  R) _; O. v) f- S9 e$ j- |( C2 C
    my_index.str.strip().str.len()+ Y& o6 {3 U+ H1 M8 Z, |; L
    Out[100]: Int64Index([4, 4, 4], dtype='int64'). V4 f; y8 ^: u: P
    9 v, K1 X! S+ @- m6 M. \* c
    my_index.str.rstrip().str.len()
    ; O% J9 l2 O  NOut[101]: Int64Index([5, 4, 5], dtype='int64')
    7 Q- O  R8 _+ H; P; X2 k' T8 I9 k" [  a+ P; K# s: E: r8 I
    my_index.str.lstrip().str.len()- i/ a7 j9 U1 f8 i# ]( p
    Out[102]: Int64Index([4, 5, 5], dtype='int64')
    ! t) C# m/ _& j1 v+ t0 O1+ i1 Q8 _* ^  S, F/ `
    2: s8 ^9 m% _% A7 _; Q
    34 S! d9 e% F( J/ j/ U9 Q' d: o
    4
    4 R4 c0 M1 d( J) i5
    2 k3 D( k: i; ]1 s, t* F0 d  i6
    3 d; J% T9 E) v+ t# S* t7' h' X! d7 u& ~
    8% K/ H9 H; N: \2 G
    92 @( M* T$ E' p) {
    10
    " ], z' B( s; J8 b1 Z% v7 h. q  对于填充型函数而言,pad是最灵活的,它可以选定字符串长度、填充的方向和填充内容:- y( y: T- O, V. Y- g

    : J/ p; P" S7 F4 a5 D/ E& l- ns = pd.Series(['a','b','c'])
    ! n0 ]6 X( e+ ]% v  n
    ! e: d- w% i" n% w7 n& d4 t, {s.str.pad(5,'left','*')
    7 W6 L6 V5 j7 K' t6 E" P8 XOut[104]:
    ! O! s* h* S% j0 v4 ?3 z% j  z0    ****a
    5 `  B, z% x6 ~1 h1    ****b' {4 v' H3 X* [/ u# `0 J' |
    2    ****c
    3 h1 w+ w: N% @0 ddtype: object; V( Q* H: G( s3 j: x- K
    5 r; H/ \' t8 ~+ [- ^3 I- n
    s.str.pad(5,'right','*')
    2 D0 ]% E$ C9 `+ Q: |& ?Out[105]:
    - K$ U- N* F" J6 w$ I$ H0    a****& e) K: N* ~% P3 n9 g
    1    b****
    . ^* d1 n! u, V$ A6 l2    c****
    9 v6 q  z% M, e* w* n6 Tdtype: object% D( Q8 k+ Z- S  ]
    8 Y, c* `. _* x! R
    s.str.pad(5,'both','*')
    6 R' o! w$ B- X3 N: B3 a& uOut[106]: ( [5 w- M3 G+ |5 ]+ o, _6 C- B% S7 ~
    0    **a**
    $ e; B& j+ S# M' ?3 T1    **b**
    4 Z8 Q/ |* o, b4 o2    **c*** ^/ E0 d& ]0 S, i
    dtype: object
    : K; s' n4 B( l) w7 q
    4 V$ R7 l3 Z+ X, r' X+ H- n1
    3 p; o. J2 F! j# n& Y2/ `+ m0 l3 K* g  |+ \) V. o( r
    3
    4 y. [" ?2 |. N, W4
    2 a/ R9 ^4 \0 [5 y58 ^7 l9 }9 N2 F0 j; l
    69 ~/ j: J5 _& A2 d
    7
    , [! c6 X; U. ]. d8
    3 v8 W0 [6 K+ h# }7 |# r2 I99 x( F  m0 M+ M3 Q" V6 \" g3 |
    10( ~: s9 |) }6 L; k. y
    110 t! E( n$ A- ?$ E
    12. E; g% Z+ P, ^0 T+ P! Y
    13* q( E2 C" E) }, E8 l0 U
    14* K6 \" l6 }6 C; U) z3 r
    15. a$ L# i" c2 c/ p. [. J9 K
    16- c- ~: B. ]7 |
    171 t4 q, [2 t- B! G# |" @: M* x% b
    18
    2 D0 R0 I! b, k! G* |/ u/ J1 J4 w19
    + Q, c' g, v9 g2 W8 w3 T208 y$ V2 U; C8 U
    21
    4 ]/ @: n7 Q2 _  g8 I( ^( C4 n, Z7 z22& j. K: N. q* T
      上述的三种情况可以分别用rjust, ljust, center来等效完成,需要注意ljust是指右侧填充而不是左侧填充:
    9 d5 J: ]0 ?5 p" D- ^, T  X* T2 e6 Y1 D5 Y
    s.str.rjust(5, '*')
    ( r7 L' K% V! o# U# p' s) lOut[107]: 5 r8 @& p5 W4 @7 _. x6 K
    0    ****a  m2 Z  |$ Y, Y+ B
    1    ****b
    " q5 S# i  F# C* X; L5 X$ X2    ****c+ k6 x& `  }& W& c/ d' ?' U
    dtype: object: e+ `5 A7 F% X5 ]
    # v  x3 a0 y# f8 T1 ?/ [, k9 E" ]
    s.str.ljust(5, '*')+ X8 ~& v; S7 O: r1 ^" I) q
    Out[108]:
    1 i" m  D4 X/ {: u+ A0    a****
    / x6 T$ j- v( W: p# `7 t. X- @# p1    b****; T- D1 w' X% n$ \) B+ `7 P* B
    2    c****# h2 G9 t  w; L, c, c
    dtype: object" Q5 f8 s+ a# P3 e$ w: f, I: ?$ ~* @1 T* P
    1 d/ t2 t- x% e* C9 q% j
    s.str.center(5, '*')
    / L9 a7 w3 I9 m; d; j! D* ~Out[109]: 9 ^4 V4 u1 M: ?
    0    **a**
    2 `; a& Y3 s6 e2 q/ \7 \( v3 o0 R3 l# k1    **b**& X* Y/ _# p1 F5 u8 O) W
    2    **c**
    $ e- Y9 B5 X. ^: G2 V5 V5 M& Adtype: object
    . N7 N- B2 Y) Z' R' p/ Q3 r8 `, K/ R* k4 u
    1
    ; [7 f& M. P& ~" r: u+ [$ N! a' ^25 Z, G$ T& S2 p/ @
    3/ x, q; W. P9 E- z; D
    4+ c; x9 B+ B: G0 h
    5! n, Z  m( r% L* r
    60 L2 s' K0 q5 w- t) Z% ?( P& S. X
    7. r# h$ _% E, a" z) f/ V/ B
    8
    2 o8 x3 l1 V6 q; b" R- S2 t9
    , e9 J9 C2 N, Q+ s2 F9 z10- [; p4 P7 [% Z. Q6 b" ^
    11
    ' [' a$ A: G& ]: c0 r127 N) p/ ]3 K) P# Z4 M1 J
    13
    0 ~# I/ i& ]0 r% T6 w( d0 [14
    1 |  X6 `! Q4 u0 r; {4 I& i15$ g( h# |( \# R
    16
      U: m3 Q. R: Y* s5 y' r17. B! `' s7 |" v. R
    18
    ' I7 }1 D1 H: h* b9 d& E" e* M19: ^( \3 _2 N' y6 d* r
    20
    5 s0 x0 j* U/ P; R) f  h  在读取excel文件时,经常会出现数字前补0的需求,例如证券代码读入的时候会把"000007"作为数值7来处理,pandas中除了可以使用上面的左侧填充函数进行操作之外,还可用zfill来实现。; h4 E/ ]7 ~2 {% t3 }
    ' u" w! f; }+ V( C% ]: O2 f
    s = pd.Series([7, 155, 303000]).astype('string'). k' T* ^. z0 V4 K+ R+ m( \
    ; j/ Q1 ~" Y! g9 R; M" O! }
    s.str.pad(6,'left','0')
    % {3 v& A/ L- EOut[111]: # w! V8 M3 |0 B5 |( k  R$ S" j9 D5 L
    0    000007! V5 l) G; C7 s' A
    1    000155
    9 {6 R) O- v( p8 D# B  c8 V2    303000
    4 a: G, j1 ~! V9 w1 S7 z  ydtype: string" K) C0 `( `: F8 _

    2 ~/ d3 C, G+ M) a( }4 p! @" G- ds.str.rjust(6,'0')
    5 W9 Q7 `8 z0 @7 e  W0 @Out[112]:
    7 j& P4 `7 e" t4 U& `' O8 J0    0000071 {# i# z1 ?3 n
    1    000155
    # l7 ~8 ]: h8 u* c6 i2    303000
    1 _8 I2 i; f- kdtype: string* R6 G1 M+ r6 ]  W8 Q2 c
    4 h0 ~, |0 c% h: f" p
    s.str.zfill(6)
    1 J' R, N1 f7 A! r; C, W' eOut[113]: - b% p7 @/ m! R0 |: c
    0    000007
    ! T+ T; N7 F) n6 B- u: I5 L- a1 B1    000155
    1 V0 d0 Z8 B$ B9 \6 V2    303000' m4 S, N% U4 U6 Q: M
    dtype: string
    3 v2 T. Q, A, t' E' V, C, l; T
    / O. f, u- x) _: o1
    0 H0 s" \1 B8 M2 t- M2" Q, R7 b$ [2 h) m! h6 K- G
    3
    + B. C: l( H% l- ~4! N& ^+ g8 P- Z5 I, G
    5
    ( \% `9 |  L2 Z- ]; A6
    ) C& R) F' F% d5 }4 {) A$ I  S, D7
    2 I1 c' K- ?# @  G8& q3 |( J/ [) A; k
    9
    - f& {1 v1 Q; s% \0 \& E100 V( k" M" X) [. @' |/ {6 K
    11
    # _/ O7 N9 s( K12+ p$ a7 Q6 L" a7 B- u$ m
    13
    5 \8 W3 f, ^! u) R14
    , v8 R4 ?6 o+ |: B7 \. t  I15) Z& q. H& H  c9 a2 }9 r
    16
    ) I8 J. ?3 z0 |' p1 C! q17" V' W1 I- B6 n# r9 P1 f- C
    18
    3 f( n  z  I* E1 q- O- f19
    0 |1 ]4 Q( E  b9 C/ E# U8 O4 I20! m6 L6 H5 h$ M
    21" T9 z0 l7 l; S' h1 x& z/ U
    22% x  h% ]. ^; T0 L
    8.5 练习
    * ]7 o" i% I0 A) u9 e" A' r' gEx1:房屋信息数据集/ c+ g, Q; g9 Z5 d6 f! _
    现有一份房屋信息数据集如下:
    ! }/ ~) k2 M" y8 k3 I& C
    1 A/ l5 C6 _% f0 \/ sdf = pd.read_excel('../data/house_info.xls', usecols=['floor','year','area','price'])/ s. k8 k" p! @$ P6 e) n  o' J4 d0 l
    df.head(3)
    - @/ z+ z) a" N; P# {$ ~5 p* x. zOut[115]: 3 l0 W, V* q* F  f1 T+ n9 h5 w
          floor    year    area price
    ! P* f9 a. N: l+ [% R3 C, w. j8 O0   高层(共6层)  1986年建  58.23㎡  155万
    0 h4 O, I. I* a1  中层(共20层)  2020年建     88㎡  155万6 e# k. V! Y7 G
    2  低层(共28层)  2010年建  89.33㎡  365万7 E% i9 l; _. k, f# G% N
    1
    * J; i5 y' z8 F7 B2& f* m, a7 x1 h% w' a2 d0 [- o
    3* {! L% H+ O0 p) U8 t3 I
    4
      y2 O" i! H4 f5 v4 P" l  W8 B) B. C5! ]9 _5 ~( P) \
    6
    4 D7 N! H3 g7 r% s7' V' {: i9 E. p  S% i; D8 e
    将year列改为整数年份存储。4 j6 y/ D& T( z# n# O; o
    将floor列替换为Level, Highest两列,其中的元素分别为string类型的层类别(高层、中层、低层)与整数类型的最高层数。" g9 U/ k  |  N2 d
    计算房屋每平米的均价avg_price,以***元/平米的格式存储到表中,其中***为整数& ^, u/ [& P2 S7 m% o0 {" s
    将year列改为整数年份存储。
    - N6 A  b' [5 G4 P( \9 p' o"""
    ' {+ l1 E* m4 V: l整个序列需要先转成Nullable类型的String类型,取出年份,再将年份转为Int64类型。6 H: M1 s$ c% E* Z. n4 E4 y
    注意,转换的类型是Int64不是int,否则报错。即使astype加参数errors='ignore'跳过缺失值,9 N* z2 G# R9 x" ]: ^: n
    转成int后,序列还有缺失值所以,还是变成了object。3 D5 S' N( b. l# m0 v
    而整个序列转为Int,就还是Int类型,缺失值变成了 pd.NA 。! ?" J2 v; ?9 R
    """1 c, M% E4 U  W0 K
    df = df.convert_dtypes()
    - v/ c* t& b! K8 L7 Pdf['year']=df['year'].str.replace('\D','',regex=True).astype('Int64'). q6 o* p; N% i$ f8 W: s1 R' L
    df.loc[df.year.notna()]['year'].head()
    % t' o. o: u3 f1 N* J
    4 k: {* H9 U' {/ {0        1986
    ( o: C8 S! \. o1        2020
    1 ~7 K/ m6 F9 v9 [0 l$ ?2        20105 N6 K. ?( b! v! V8 x7 h* Z
    3        2014; w, T6 n( |6 F
    4        2015
    % x3 k+ x' M8 l' IName: year, Length: 12850, dtype: Int64, s7 i" B$ X9 X: u& }1 U
    ' d; d. }/ N1 N# t
    1/ U. f5 p% C$ k# `7 C
    2
      V( ]' U2 F9 J3 E3/ r4 z, T0 B1 W& k4 P5 n4 r, B
    4
    # @3 U3 e" v3 y* P4 k  U5% m3 r5 I0 D7 i2 K; t! _
    6; Y+ c- O$ `5 v# }4 y. ]/ u+ D4 s
    7
    + E6 K* W# {& V$ L# ], x% e6 u8
      X1 g+ y2 s2 d' `! c6 n( C( U5 A9
    ! A" F# g& x8 s- L- W101 Y4 m% T! T& W% U* Z2 c0 I& Q
    11
    / h6 D, Y, W. _+ @' @3 v$ n6 \120 w% z3 {; u0 k9 \  y
    131 s& z, \3 s+ U; s4 R1 `) G9 m
    14
    : I! B. a+ ^. j/ N: ~. q5 j15, p4 d  ^! h& j6 \4 q# \  h. t6 O! M% L
    16
    ! C2 v4 }1 t) B参考答案:
    ) H7 j5 ], B$ T! F' U3 l" R% J) d% f. m) d) S! P
    不知道为啥pd.to_numeric(df.year.str[:-2],downcast="integer")类型为float32,不应该是整型么4 E. t7 `2 N0 h4 E( m) S) C

    0 D# h6 i% c0 odf.year = pd.to_numeric(df.year.str[:-2]).astype('Int64')
    1 w* `! f! G$ xdf.loc[df.year.notna()]['year']. f/ c! \" d( S7 r8 A' t% \8 N$ {
    1- y3 v% U7 K+ h1 e7 |& T; O) K
    2( R( p8 U* x  m! p! t  Q# `
    将floor列替换为Level, Highest两列,其中的元素分别为string类型的层类别(高层、中层、低层)与整数类型的最高层数。7 @: i$ Z2 [( j$ t" s
    pat = '(?P<Level>\w+层)(?P<Highest>\(\w+层)'
    : j; ^' I2 C: _4 K- sdf2=df['floor'].str.extract(pat)  # 拆分成两列,第二列还是(共6层得形式,所以还的替换一次9 I) g: ^, y5 O6 w
    df=pd.concat([df,df2],axis=1).convert_dtypes()  # 新增列拼接在后面,再次转为Nullable类型2 b/ M# v; |7 C
    df['Highest']=df['Highest'].str.replace('\D+','',regex=True).astype('Int64')              " p7 A5 n) t) ?% Y0 B" R2 ^$ p  g/ C
    df=df[['Level','Highest','year','area','price']]
    2 x$ [( E) o1 A8 j+ V, v5 k2 ~2 Z' @df.head()
    " ~& i7 M; W( b# T
    5 ^/ S' j+ f6 k# H, x& \9 K$ B, a$ p7 J   Level  Highest        year        area        price
    8 R5 l% X1 N: I$ e1 \. [- T( r% N0        高层                6                1986        58.23㎡        155万
    2 b9 P# c6 u' M$ O4 c6 b1        中层                20                2020        88㎡        155万9 d+ e# D) _( |' z0 C6 D; v3 L- a
    2        低层                28                2010        89.33㎡        365万
    * T* l& t0 k6 k' E4 G+ ]9 B- m3        低层                20                2014        82㎡        308万# g7 }9 n0 v3 `; D& u2 ]
    4        高层                1                2015        98㎡        117万" G$ C; |* G/ y, @
    1& x$ \( w- C' z% U" U
    25 ^9 c4 P4 p5 h. U; ], V" ~, s
    3
    ; N) e7 i( ^- @$ i$ Y4* W/ W' Q& _! C. X6 W) U+ J) m# }
    55 Z- @* ?, f" ]& [% ?5 J
    69 U9 S3 ^) ^& I, H
    79 l% N% ^8 K5 m+ W& E
    8
    6 F# d9 k5 a( \9
    # L, Y( f/ n7 H% ~$ K- C) F10# Q. L* A4 V( B6 Y0 j* q* l4 \! r: {
    11
    + Q/ U: x7 k" ^8 l120 {, Y1 k! z3 }
    13, D, }- z* A' ?# a4 E: v$ D0 K
    # 参考答案。感觉是第二个字段加了中文的()可以准备匹配出数字,但是不好直接命令子组了
    ! q$ p# e* N9 o& T8 m( Ypat = '(\w层)(共(\d+)层)'$ w6 j2 o: T' ]' Y+ @
    new_cols = df.floor.str.extract(pat).rename(& b+ o8 U7 C0 _& R. l3 z' c
                        columns={0:'Level', 1:'Highest'})
    1 i" ^$ w! _  N7 J7 D
    2 Y8 K9 B4 u$ d) _  d6 C, R* U( {/ {5 Udf = pd.concat([df.drop(columns=['floor']), new_cols], 1)0 `0 d2 X; V9 e+ K
    df.head(3)
    . B! s3 K" U2 I
    + }  @, E* [! [& b5 D0 v+ Y) v( qOut[163]:
    ' C" K: @; u  ?( s! d   year    area price    Level Highest
    * M/ i; [# Z5 p$ u7 ?0  1986  58.23㎡  155万    高层       6
    % S% Z6 t9 p1 D( s- y" t1  2020     88㎡  155万    中层      20
    . c& Y4 p0 y8 M2  2010  89.33㎡  365万    低层      28; N$ `* }  Q( S! P$ ?
    17 R  s9 _8 i7 D8 r+ c8 _* }
    2: W9 W( Q! `1 K9 z: _4 t
    3
    4 {- L( |9 }3 s3 U$ r' a/ }4
    " [, j0 C( e' ~# }+ d58 c5 w$ O  f8 E/ n: _+ G
    6
    " i2 ~1 Q0 u. E" H7  n- S8 _$ Z4 `& V& N7 J
    8
    7 D' \' C: X* t& e91 t8 N/ J/ s  U$ }" l! z# e% Z
    107 l4 I) Y6 S  j
    11( A& H2 G3 d( u; t: q! A
    12
    5 L/ G# N" g$ z, ~2 C0 J13
    - }: C/ K5 P1 t# f: a' d计算房屋每平米的均价avg_price,以***元/平米的格式存储到表中,其中***为整数。
    7 z4 `7 J7 [- m+ [: |* u3 i8 u5 z"""3 B( A8 b0 f- Z* Z# R
    str.findall返回的结果都是列表,只能用apply取值去掉列表形式8 K5 G" ^: W% M) |; z
    参考答案用pd.to_numeric(df.area.str[:-1])更简洁* z( ~/ m  N* e; ?1 V
    由于area和price都没有缺失值,所以可以直接转类型
    ) l# X7 P/ {* f. X"""
    ! M) b, k" P* ]# z" A0 rdf['new_area']=df['area'].str.findall(r'\d+.\d+|\d+').apply(lambda x:float(x[0]))  f1 {6 i0 a2 j0 M( S" A+ x
    df['new_price']=df['price'].str.replace('\D+','',regex=True).astype('int64')9 q) f- j; c. o( `& H1 s
    df.eval('avg_price=10000*new_price/new_area',inplace=True)
    / u, c" @: k. v) N# 最后均价这一列小数转整型直接用.astype('int')就行,我还准备.apply(lambda x:int(round(x,0)))# ~5 C/ ]% Q7 `, U6 v* I
    # 最后数字+元/平米写法更简单
    8 C; z/ [5 B* E/ k5 @$ y" \, t# Kdf['avg_price']=df['avg_price'].astype('int').astype('string')+'元/平米'2 R' y0 D/ t1 a) i" P0 H/ E! O
    del df['new_area'],df['new_price']$ }7 D# d  s9 W2 f, D! E' L4 X
    df.head()' F" [) X3 W) @! Y: I! ?/ t3 u$ X$ M
    6 W' @) X; h  l: u- C; J5 z/ M
       Level        Highest        year        area        price        avg_price8 L9 m4 H* U% l! q
    0        高层                        6        1986        58.23㎡        155万        26618元/平米
    , L: D8 p/ a' p* s- M1        中层                        20        2020        88㎡        155万        17613元/平米! S. n' n0 k; |0 `$ o" a# }$ q
    2        低层                        28        2010        89.33㎡        365万        40859元/平米, ]( M9 b/ S# I! s# d% k
    3        低层                        20        2014        82㎡        308万        37560元/平米1 W  ^0 U" S" P: s( t) |) n" x
    4        高层                        1        2015        98㎡        117万        11938元/平米
    " {8 e: _) v  T) R* u
    7 f. Q6 P2 E* U( W1
    3 q0 C4 e! Y! O% c, n# R5 ~2, z" V' T; J7 c, r
    3
    6 U& X7 n8 c' ]. l% Y: t4
    0 O  e- I" o9 ~! X5
      u0 z# w- \, h  K: B6
    5 S4 D2 ^+ c# v7
    # y+ O) U8 d7 [* z8
    ) q* _% b; |4 |8 j! Z1 }95 x7 A+ R0 v4 I/ Y- l2 s6 c# w
    10/ W, d1 J' E( b% D6 T
    11
    ( Y' P, K! {2 A/ \) k3 }12
    - ]  m5 b" e: b9 u, C+ I13
    / M8 U* u; `) W3 D14
    8 \; M( |' g* y. W  ~  P2 }15
    2 X$ I# j. C4 C+ ?" ]% d16
    & O9 N% b& }( ~170 g4 W2 u! ~1 G
    18, _& B! a3 H3 u
    198 N6 C- t( s. E+ k8 }
    20' i7 B, @- \& O( V9 o/ l
    # 参考答案
    8 }' H) N% c( @, T' I6 k4 Ls_area = pd.to_numeric(df.area.str[:-1])' U9 L" z  ?' x. B5 ~' x
    s_price = pd.to_numeric(df.price.str[:-1])4 y5 |! ~6 L6 c0 e" c* F; y! w
    df['avg_price'] = ((s_price/s_area)*10000).astype(
    7 Y8 ]0 M4 }, g" S% H5 g                    'int').astype('string') + '元/平米'
    & T0 Q. R! a. H6 _- R  X1 |4 u% t# _. h! y3 O& [1 P0 [& Z' v
    df.head(3)7 o9 s' X% u3 G  }- q
    Out[167]: 7 ]7 ?' }; f) m# E/ k& D/ M
       year    area   price   Level Highest  avg_price
    6 a& D% o" [+ H% l/ ]+ m* x0  1986  58.23㎡  155万    高层     6          26618元/平米
    7 N' e# m, d3 S7 y+ l1  2020     88㎡  155万    中层     20          17613元/平米& [8 }: |" K- N4 X* a  `
    2  2010  89.33㎡  365万    低层     28          40859元/平米, f& m8 s, ^+ [. q: J( j( k
    1. s1 I* j* [7 |( R% V1 `
    2
    % U9 ]7 S) }8 A; h* a% h3
    : ^6 G% d3 P& d' \6 x+ s7 W4
    + m- g& e5 ~* d5 o5* s, G' |% n% I5 T6 F' ]! j& x
    6# Z% K7 I, t$ C
    7
    9 L6 f  a8 S: Q- h  e8
    0 A5 {: J: ~5 y+ Y97 b, D& b# o7 c2 b) m; K2 F  O
    10% l3 k& `! w' ?' D0 T. U. j% U: G
    11
    % u; r' R2 F6 ?0 j" D12
    ( H0 |) O; J! y$ }Ex2:《权力的游戏》剧本数据集
    5 U' @0 J, p  c' X现有一份权力的游戏剧本数据集如下:
    - ~* M9 e! D5 T! Z0 {7 J  b$ j. y" a0 Z
    df = pd.read_csv('../data/script.csv')0 E8 d& y9 G9 H, ]" K. [2 f
    df.head(3)
    1 G! r4 H) V9 Z3 U  E1 F
    , I) r, [: x9 G1 I' W& v* dOut[115]: : \9 f+ P+ j1 e+ v" p8 u
    Out[117]:
    ( M6 O1 `  z5 m% {0 e- d5 P1 Y  Release Date    Season   Episode      Episode Title          Name                                           Sentence& D) S# s" }$ X7 f$ k" p
    0   2011-04-17  Season 1  Episode 1  Winter is Coming  waymar royce  What do you expect? They're savages. One lot s...( \5 E' W3 |8 B* C# {, ^
    1   2011-04-17  Season 1  Episode 1  Winter is Coming          will  I've never seen wildlings do a thing like this...; f6 g3 y* a7 h7 t
    2   2011-04-17  Season 1  Episode 1  Winter is Coming  waymar royce - m" E: U0 I( b8 K1 O# G  R! x
    1
    $ w8 s6 O! \8 A: f$ U2" Z3 ?5 p- x; [0 w
    3
    * \1 d% M5 ~4 ?6 u4( x; W  r3 [8 f' W# M! i1 z0 p6 Q
    5# p. R/ C8 v) c% n2 i% N
    6. ~2 m! v" b% s1 @
    7- C% F  B% x9 A' U/ L% g' W
    8% {/ P# Q2 o9 ~: J- X! c2 M% k! f* l
    94 U9 I. }+ k6 j) K& j# X$ i  L
    计算每一个Episode的台词条数。
    ; O1 K. p& o  P1 }6 E" W以空格为单词的分割符号,请求出单句台词平均单词量最多的前五个人。
    % @3 h' z7 J+ L3 _! J1 u5 c  \4 X若某人的台词中含有问号,那么下一个说台词的人即为回答者。若上一人台词中含有 &#119899; 个问号,则认为回答者回答了 &#119899; 个问题,请求出回答最多问题的前五个人。7 |2 ?& V# h) }& K  l
    计算每一个Episode的台词条数。
    8 d7 b2 K; z( n. k' Odf.columns =df.columns.str.strip() #  列名中有空格; t+ L8 D+ s' T3 Y
    df.groupby(['Season','Episode'])['Sentence'].count().sort_values(ascending=False).head()
    # o8 n; a/ R" D* V( Q/ h7 N  p* V4 M( ~, l
    season    Episode  - I1 _7 C; C% Y- ]- j
    Season 7  Episode 5    5053 T- {' Y6 I; A0 Q, [
    Season 3  Episode 2    480( Y7 h5 l2 j5 j" m) g) h3 D
    Season 4  Episode 1    4757 |) c, z# N) H8 _& K
    Season 3  Episode 5    440
    - t  o  `0 I  C/ hSeason 2  Episode 2    432) ~$ t2 J2 y% V+ t7 H4 j
    15 b9 Q9 Q4 [( K
    2' U+ \  S# D5 u9 R- _3 @% ~# \
    3' {1 [1 P: \* l) F
    4
    5 s0 K7 X; z& v$ m5
    ' N  a' c; p1 }/ {% c6# t( T( P  o$ q8 c+ @- \$ k! M/ ~; ~
    7
    ! n! @5 w: r+ `; ]5 n88 ^, {+ i) x* m. i8 l4 [2 N; W% K9 _
    9" J) N6 P, Z8 a- z1 m
    以空格为单词的分割符号,请求出单句台词平均单词量最多的前五个人。
    2 S2 Z- V6 Y4 p# str.count是可以计算每个字符串被正则匹配了多少次,+1就是单词数5 m5 j0 y$ K( u+ h; R$ O
    df['len_words']=df['Sentence'].str.count(r' ')+1
    5 M# M/ \# R) y- ]) v- ^df.groupby(['Name'])['len_words'].mean().sort_values(ascending=False).head()
    3 {: g! V. h8 b, `. w! _, W( \# b& f7 M+ e; |. C8 B' v
    Name
    $ i* g7 m* M7 |+ }2 K5 J. `male singer          109.000000' H% [% }. N3 ~2 O& T2 c( _" @
    slave owner           77.000000* z) d2 B/ G) w! f7 G( |# t# H, B$ [
    manderly              62.0000009 c- q4 \6 R5 i" v
    lollys stokeworth     62.000000
    / V* n- @: _! k) k8 V' Jdothraki matron       56.6666670 V7 W! z8 E; @$ S
    Name: len_words, dtype: float64
    1 T* a) D. W  T: Z, O1% x0 F( `7 i7 J
    29 q  P& p0 G; S% Z, U
    3  x# _. A9 F0 S2 U5 [1 p( n
    4
    ! P8 x2 _4 C) {5
    . t0 {( Q6 p9 i  k( M9 l1 z7 F+ H6
    + g# ]% L8 h: m) m+ f  U  k7
    . ?# Q/ W. v# Z( \) V) @1 y# m- U8
    ( G- O9 t4 }+ w/ \9 U9
    2 z0 N- g9 K! l  }/ p/ g  W! Z10$ O$ o6 m1 L6 U) a& J
    11; Z% ^0 ^1 \4 [  n; m3 [) W* {; s
    若某人的台词中含有问号,那么下一个说台词的人即为回答者。若上一人台词中含有n nn个问号,则认为回答者回答了n nn个问题,请求出回答最多问题的前五个人。
    $ Z% o% B+ I- G* A% Ndf['Sentence'].str.count(r'\?') #  计算每人提问数
    $ F& b$ I3 j- fls=pd.concat([pd.Series(0),ls]).reset_index(drop=True)# 首行填09 q) F: y9 m$ w1 G! H
    del ls[23911] # 末行删去( V) m- A9 J: x& @, q
    df['len_questions']=ls
    & o! A6 H) ^, H  @df.groupby(['Name'])['len_questions'].sum().sort_values(ascending=False).head()
    5 k$ N+ \8 P! s. k. ?* `3 k$ ~7 F7 B5 U6 k# x
    Name
    $ U4 g8 }$ V( o) m, e3 R' [: `tyrion lannister    527
    8 x/ N+ e! ^7 k% o, d% F' ~. Djon snow            374
    ; x) b+ e. `, {# [jaime lannister     2839 v0 h# c- G4 o5 s0 y$ v0 U
    arya stark          265, O, q) @2 F8 \% ^9 x. T! N$ t
    cersei lannister    2467 F$ l3 U* x  o4 s3 d" ?* X
    Name: len_questions, dtype: int642 e. I5 V0 c9 V: G  W$ k

    % H+ u: D& P: _3 O3 e+ V* I# 参考答案# l5 M1 ]5 U% a6 `. N! ]
    s = pd.Series(df.Sentence.values, index=df.Name.shift(-1))
    8 w0 M, N: n( |5 o3 Ns.str.count('\?').groupby('Name').sum().sort_values(ascending=False).head()8 J/ B* ^( {2 d

    # i& N6 S$ i. w- \" l1
    : N' R8 b. `2 ^6 R  t26 J. Y+ E8 w% C8 B* e
    3
    1 R6 K( L, ]; H9 S& d6 t, D4 }" h4" e' J4 o, C! V$ _! B: J9 d/ f
    53 O, m& m, D3 z, i
    6) H2 N8 W, }' B- A0 g; P: ?0 X' \
    75 G; H; }2 w* u0 ~) R- i: @, R0 P# l0 a
    8
    ( ^. j+ \4 k) e& M$ P7 ^9
    % E7 b% n  Z1 |) u0 r' V2 Y8 C10
    ) L& P/ B& `" U- u( r; n, I118 @5 S* V% b3 a
    120 H1 R/ p# v& f9 S
    131 {& B4 ?( Z/ i: Z  j% ~3 O
    14
    2 T- X. ]# B- |: c15
    & q( j4 e+ A7 @) F" a) p  o, M  W168 V% o3 y4 R+ K* k; J1 K
    17
    & @. [9 x& {& F$ k: ]" q/ h第九章 分类数据
    2 B+ i9 V  D# @& H, ~, F, Yimport numpy as np
    ) _' Q4 e9 Q, x/ _; i5 vimport pandas as pd) }7 _: _, }/ ^9 W$ ~0 i
    1' C! H+ ~: A2 @2 E7 K$ o
    2" s/ E3 S! \! O! F4 O. g
    9.1 cat对象
    : w" `' L8 _+ V9.1.1 cat对象的属性
    . M+ G4 h' j. K" j  在pandas中提供了category类型,使用户能够处理分类类型的变量,将一个普通序列转换成分类变量可以使用astype方法。" ]" `* `' e, U
    ! }3 ~9 f& g! X) t
    df = pd.read_csv('data/learn_pandas.csv',3 t1 m2 X/ M: X
         usecols = ['Grade', 'Name', 'Gender', 'Height', 'Weight'])
    5 Q9 W* A# y- }6 r/ os = df.Grade.astype('category')  Y- h! o! f  y% y% e5 D( d& C
    6 \* a/ j; Q+ @: C" `" O; F
    s.head()
    ; U0 t# p' ^0 E0 R/ DOut[5]: : O4 [: c0 X- i( y+ T1 E" i
    0     Freshman
    * y3 ?, y/ H9 T- M: N1     Freshman# M' `9 C# C$ e; m, [+ \$ m$ a0 `0 B' U
    2       Senior
    " j$ {% h- S( F4 {3    Sophomore( \& @, q+ O% f. Y1 q- x
    4    Sophomore
    8 j4 j+ N& z/ G# ?Name: Grade, dtype: category; f6 M; ?( \( i- {9 o% ]  O
    Categories (4, object): ['Freshman', 'Junior', 'Senior', 'Sophomore']- M+ a4 }" a( U; U) q6 x' T8 _5 k0 |4 V) m
    1+ o& Q, |5 S6 O, O, Y) m  T
    2
    7 ]/ N9 b+ J& {! w/ @$ h- f& f3" {2 ]5 R% E: ^0 Y
    4% h- f8 M/ f& X7 Q
    5) e7 q/ |% w$ K' O" J
    6
    - O9 C8 [4 g1 _$ W6 _5 |7 }7* z6 P# ?4 n, {1 y" d- k. n
    8- q6 l$ c' j' @% Y
    9
    5 {8 [" r# j2 {$ \" r' n4 L. y10
    & A. \& }6 U$ _1 L) A/ l# r) @11
    & ]( I! m- J9 [3 ^! l, k4 d$ U12
    : n( U; q6 J; G$ [# @7 j4 ^13
    ! X$ s. H4 @9 w. Z2 H4 X- y, k) ^" s3 g  在一个分类类型的Series中定义了cat对象,它和上一章中介绍的str对象类似,定义了一些属性和方法来进行分类类别的操作。0 u; u! N. A8 F1 x6 S; e& D: d( H

    ) ?' l2 b) E# v- ~( F! is.cat  C' ]; j1 Y" p. Y6 c$ s
    Out[6]: <pandas.core.arrays.categorical.CategoricalAccessor object at 0x000002B7974C20A0>
    + `  R3 i, G  B, S0 P' }1- g6 m2 e. U" @% s
    27 k: L" H5 C+ P: c; V
    cat的属性:
    6 T  R! H7 y8 t" g; Y1 n8 `2 _3 @7 m: }" U' t
    cat.categories:查看类别的本身,它以Index类型存储: `! F9 e/ {6 @$ p& t1 ?3 r
    cat.ordered:类别是否有序2 e8 P* r  P. ~. D% @
    cat.codes:访问类别编号。每一个序列的类别会被赋予唯一的整数编号,它们的编号取决于cat.categories中的顺序
    ! X2 K! Q% {5 Ds.cat.categories
    ! e7 T2 T8 l) I% F1 ^4 U* VOut[7]: Index(['Freshman', 'Junior', 'Senior', 'Sophomore'], dtype='object')" c( @2 U% ^( @" ~/ V

    7 R, n4 N# A7 f: ^3 ks.cat.ordered
    - r3 z7 [% m. ?8 f* WOut[8]: False3 k/ @/ n- s+ d1 y& ^
    $ ]  W7 B4 o1 a$ b3 @5 D3 n
    s.cat.codes.head(); I9 d; ~8 S( ^* C
    Out[9]: $ J, g  I( k9 I7 D: K+ V$ G$ ^
    0    0! ?( a- S6 O2 E% X0 A5 C" e
    1    0; J7 i/ v# \3 L% m
    2    2
    ; e! w, C3 f& t3 G: L7 n/ s3    3
    1 ^3 x' B5 h- L% D' m7 I  }4    3) p- U, F8 c2 [2 ^5 u( [
    dtype: int84 m/ R% d% f* m( {! \1 \
    1
    1 z8 t) K/ i4 D% D8 k# F2$ n9 U; L" ?( q, n/ A7 t: d. P
    3) \% n8 S% G% G, Y( s
    4
    3 w4 n( O% \+ m% ]$ I8 M5
    3 N" h5 T4 Y+ Y3 |5 x9 V2 P; p6
    * l$ E2 u$ |5 q% |72 i( t: g& {' F5 e- |/ K
    8# ^  d; b: d, s) ?: ~1 h
    9
    # l9 f- j1 R, x10  s" v6 l* n6 c& e* `3 r) Z* H
    11
    2 [6 k( w0 o2 l12$ T- ?! w6 [5 V
    13  Y! e2 p' ?; |# z1 S4 ~
    14
    3 Z$ g% r0 @0 \3 N$ L9.1.2 类别的增加、删除和修改
    4 H6 i" B" h8 j: O1 R  B  通过cat对象的categories属性能够完成对类别的查询,那么应该如何进行“增改查删”的其他三个操作呢?" r& q1 u& c3 Y8 g) H7 D! k

    / J' s3 j+ ^2 A) e: d3 `. }【NOTE】类别不得直接修改( Y% s& `' [0 T; \/ l
    在第三章中曾提到,索引 Index 类型是无法用 index_obj[0] = item 来修改的,而 categories 被存储在 Index 中,因此 pandas 在 cat 属性上定义了若干方法来达到相同的目的。
    9 G$ m$ r. {3 H, ~  [1 y
    0 A+ B1 M0 e* f( Badd_categories:增加类别. t4 i  n  X7 z; m* ?# j" d
    s = s.cat.add_categories('Graduate') # 增加一个毕业生类别* L3 u# @- t5 i
    s.cat.categories3 |# ]2 V  N( I

    + B. I4 R, {4 B( ?; i) @+ AIndex(['Freshman', 'Junior', 'Senior', 'Sophomore', 'Graduate'], dtype='object')
    + \: N( P3 f# {8 J) R! {6 Z1
    - n% ]4 h: j' m! l2
    $ q) E) L5 n" c8 x$ d% L3% a4 l1 x. T8 A3 R. I. ]
    4
    2 U. d; U2 a1 }9 z* |remove_categories:删除类别。同时所有原来序列中的该类会被设置为缺失。( d8 ], S2 W: H7 N5 i
    s = s.cat.remove_categories('Freshman')7 b6 H* M" A) p5 U; T) L7 K

    1 K* |  {* x$ K9 xs.cat.categories
    5 L& @3 \" g* u- QOut[13]: Index(['Junior', 'Senior', 'Sophomore', 'Graduate'], dtype='object')8 h0 X1 y! r- n; Q% U9 W- e$ _% |

    9 y5 h# B  s) x9 s4 `; R% Cs.head()$ d+ p+ T* @2 o2 i$ H* t
    Out[14]: + Q7 `% ~8 y" s! J( j3 q1 @
    0          NaN
    # w* _% Q# R# z/ d8 T1          NaN
    , z7 d7 D( {6 s: P8 X) [  n4 b2       Senior  |4 ]% c) B: ?: ]6 m  v% B
    3    Sophomore; L9 u- v/ Z4 m
    4    Sophomore5 l: O+ u2 v# A6 K4 i. p" y& ]
    Name: Grade, dtype: category
    9 c0 _0 M% }. t0 ?2 g: n1 j  MCategories (4, object): ['Junior', 'Senior', 'Sophomore', 'Graduate']
    ( |9 C$ R3 ~; q" b  t* C6 j1
    ! C4 M1 ]( f! W1 w) p+ M* ^+ j25 Q0 x1 I+ P8 `/ L1 h9 M; u+ _% S
    38 A5 V! q0 p. K  }" U* U
    4
    0 T: d( o/ c+ ^, B1 W% w- u5
    ! X9 Z+ d# K$ r* Z( ?, l9 T- {6
    8 s0 z- v6 q* e' k, |3 M, q7& N; ^( a! d4 D6 O$ |
    8: n$ p# ]1 k* J7 q, d
    9
    5 l/ e* l  u- l7 g10
    7 m" W2 s4 X5 r4 R) q# H% U) m11
    ( ^" E$ q7 i. ~' D: \12
    . w) l4 @% R8 I) X: C133 e. X# B# c% |; ^! V9 O9 o8 F
    146 _+ x; ~  a2 b, \4 y2 U5 M; ?" U, U
    set_categories:直接设置序列的新类别,原来的类别中如果存在元素不属于新类别,那么会被设置为缺失。相当于索引重设。9 b+ p& f" e0 W  }
    s = s.cat.set_categories(['Sophomore','PhD']) # 新类别为大二学生和博士- T5 D2 A' ]0 J* n4 L8 b
    s.cat.categories
    7 ]) X0 @0 |1 T5 J7 d, |Out[16]: Index(['Sophomore', 'PhD'], dtype='object')6 F+ U5 ?- S4 C% V; r* e

    5 I: A- I, s: Z5 M0 V7 Z1 ]s.head()
    ' i9 ]! Q" e% A5 r; m9 pOut[17]:
    / l9 g, r7 o! M$ T7 y  X" Y0          NaN* }3 }3 k  R1 O2 C& v) v7 S
    1          NaN& \6 w: a9 Q7 L; d7 `- m- F
    2          NaN% T# p2 H. l" K# K- ^
    3    Sophomore
    8 o0 i2 f- T4 H) r3 f4    Sophomore! g" N8 w6 E9 [8 j; F5 I" t- U
    Name: Grade, dtype: category
    ) X4 }- ^/ R% X, _' b6 U9 _Categories (2, object): ['Sophomore', 'PhD']
    9 Q! b: l9 D& t  _  S1
    # ~% ~  h& \1 s, E6 b, o2/ I, }1 V+ i: o/ c, s
    3+ ?" ]" x) }# u) N! b/ E
    4
    7 M- Q' i9 k1 \, M5  c0 G# X& O# ?- q6 y, _1 r' @* a; m1 _
    6" F  z1 k- ~3 a) W! t1 j! u
    7
    ; E+ k8 r5 y0 O8) |8 u# p5 B' M5 B$ t
    9  a1 K& D- K  z1 A6 K  s" f
    10. F* Q  y3 v3 A3 i( O9 F
    11# y" A+ U" O1 Y) o( }# m' n* H! A! A
    12$ [) X; G9 g3 R6 b" a* }
    13
    / {  j" M* f" _. x6 Fremove_unused_categories:删除未出现在序列中的类别0 i: t& s/ }; D. n1 O
    s = s.cat.remove_unused_categories() # 移除了未出现的博士生类别& H* {. G1 m1 a2 Q
    s.cat.categories+ a1 T# {2 i) ?5 O1 @! O

    / N- u5 j, X: L9 hIndex(['Sophomore'], dtype='object')
    ! t# |- q! R6 X& J1 Z% N1
    # o8 K; K- a, j* A25 _+ _* p$ l" m# }9 }& K
    3" g  Y4 O: x! _6 q# E( z8 _
    4
    ' C7 O# J6 W. ]) h2 Jrename_categories:修改序列的类别。注意,这个方法会对原序列的对应值也进行相应修改。例如,现在把Sophomore改成中文的本科二年级学生:3 K" a6 X& j$ _& q& s' L+ N
    s = s.cat.rename_categories({'Sophomore':'本科二年级学生'})
    & H$ Z: D+ R/ {+ ~0 |  l8 W5 As.head()
    4 B0 ^8 L1 W  K( M8 W
    0 Z5 a! ~+ {7 w! ?3 w( @0        NaN1 L; }# M7 E! D0 h6 O) I
    1        NaN! |: J# @* S/ Q4 G
    2        NaN# V& E2 c+ k* S
    3    本科二年级学生
    + W3 d4 x& `. w1 Z. s) g4    本科二年级学生
    & b6 _( \- e* y* w; \Name: Grade, dtype: category. O  _3 S) q. C# I, S
    Categories (1, object): ['本科二年级学生']0 C* t2 S) `% g- _' y
    1; g1 _- o+ v7 a! I1 Z/ T0 E
    2
    - [+ E& B6 H% D3# t1 [4 s5 H6 _
    4% g! {0 V/ t) j5 F" A
    57 [% Y: y/ e0 a0 T: D. N- u
    6: h6 c& B) h! o+ N8 W% N
    7% c0 {2 u4 V8 a3 ]# i
    8$ z+ O: X* f$ C  }
    9
    ' ~  D* A0 c; h2 L10
    2 Z0 a& H5 j8 J9.2 有序分类
    / Q: g) [! t( [4 J% e1 w. I8 d  }9.2.1 序的建立( ?( I7 C8 f( f; h; Z6 Z" e; @# q
      有序类别和无序类别可以通过as_unordered和reorder_categories互相转化。reorder_categories传入的参数必须是由当前序列的无序类别构成的列表,不能够新增或减少原先的类别,且必须指定参数ordered=True,否则方法无效。例如,对年级高低进行相对大小的类别划分,然后再恢复无序状态:
    ) S7 L5 e  \: _8 `/ e4 _8 o" |5 c
    1 C1 _' ?: O; }4 q8 j4 ks = df.Grade.astype('category')7 Z' C0 y2 m/ H
    s = s.cat.reorder_categories(['Freshman', 'Sophomore'," ?, Y, _% ?( s% d) G4 W4 [, `& D
                                  'Junior', 'Senior'],ordered=True)
    5 h) w) e) k- S: u8 v' js.head()& r, B- _: L1 x) ~6 B( i3 o
    Out[24]:
    1 _+ C0 x, B" q0     Freshman
    : L% Y0 {/ w) J3 e) P% i1     Freshman
    ) V0 v7 L& t) \7 o8 ?" C2       Senior
    ! d4 s7 D* n) o! n- E0 l/ I3    Sophomore
    9 j$ u: ~; {* p$ O6 k0 A4 |+ O4    Sophomore  t  z  j  W$ H& @2 q$ E
    Name: Grade, dtype: category" e$ ~) k$ q# [* N( `% @& }: b
    Categories (4, object): ['Freshman' < 'Sophomore' < 'Junior' < 'Senior']9 [% @5 b2 |* H6 }9 [

    4 x& L2 ?3 `7 Ms.cat.as_unordered().head()
    9 Q  s4 S+ r) P- s% M, G1 F8 w" Y, dOut[25]: ( S/ X9 E2 e& d8 A9 P7 w
    0     Freshman. V. G" {5 i0 M' u9 C& Y
    1     Freshman
    9 ]8 H! O/ h: d& L7 m5 g2       Senior
    % s: J* }( S0 w. [! ^0 \3    Sophomore- P  J) c  W+ j, s
    4    Sophomore$ d: |( s$ O3 d& J; s: W" }4 Z* Y
    Name: Grade, dtype: category
      c4 g5 Q1 i! I$ n1 }# r. r" wCategories (4, object): ['Freshman', 'Sophomore', 'Junior', 'Senior']
    1 n- n6 B* R" c& a8 m7 L2 ~
    - f1 K" e2 i- M. N6 o1
    # h( m' X5 i0 ]! ]2 Y2
    " h5 y6 r! X. @1 v: T3- e8 O' g; G9 T7 i) Y) g- O
    43 L9 T$ o/ h+ j# v$ m! W5 u
    5
    $ r% g# F' S* g: e0 q6 K4 L6
    % F* d: n* r. q9 e3 \0 n; q# D# U7" x2 ]- o( U: |8 z
    8
    : \, x- A- D' I( _& {) W! R2 H91 S; j1 E  h  ]
    10
    ' R1 j9 W2 K5 c11
    9 a, M" G  e) k, _3 t12. m& {' J- c" o" ~) u5 N, F
    13
    + P1 a& I* ^* Z; [149 ~  I1 t: `9 ~2 Z) C
    15, O4 Q( q/ V% }# B' i3 L/ ^
    16* }; I5 l" e5 H. F) `" x
    17
    9 i, D( y& v" I7 G) L18
    $ b9 ]' |+ l5 R! U& f* G& E19+ I8 V) T4 o: R
    20
    ) E# ?; D, [4 A8 n3 B( `- C# M21
    $ K3 g# O& u: e8 r22
    6 Q/ M/ _9 d" M  如果不想指定ordered=True参数,那么可以先用s.cat.as_ordered()转化为有序类别,再利用reorder_categories进行具体的相对大小调整。+ G/ e9 r, n/ c7 c- ^

    - [% @# |) i% R5 L6 |9.2.2 排序和比较
    . o9 W4 Y" t/ L6 N$ g6 q在第二章中,曾提到了字符串和数值类型序列的排序。前者按照字母顺序排序,后者按照数值大小排序。  Q  w  v( t+ x' ~7 Z3 u1 X

    ' n- f. A; B+ T3 u( Y9 \  分类变量排序,只需把列的类型修改为category后,再赋予相应的大小关系,就能正常地使用sort_index和sort_values。例如,对年级进行排序:
    % l7 n7 a: N9 }0 Z+ ?$ R' M  _1 W: ]! ~8 @- s, c, Z9 J
    df.Grade = df.Grade.astype('category')2 u4 W* Q& S; `
    df.Grade = df.Grade.cat.reorder_categories(['Freshman', 'Sophomore', 'Junior', 'Senior'],ordered=True)
    & R; B& Z5 d" _( e2 r6 C( Cdf.sort_values('Grade').head() # 值排序
    3 q% p! l( G! M# v; U3 DOut[28]: 8 x5 L$ Y3 k. l6 _; v
            Grade           Name  Gender  Height  Weight
    % B* Q) t) T( Y0    Freshman   Gaopeng Yang  Female   158.9    46.0! x/ _1 a9 q7 h0 }9 c7 D- [$ e
    105  Freshman      Qiang Shi  Female   164.5    52.0' i: i- t% k4 F3 Z( @6 R
    96   Freshman  Changmei Feng  Female   163.8    56.0( F$ J$ f+ l0 H7 v
    88   Freshman   Xiaopeng Han  Female   164.1    53.07 I. ^  f$ ?2 Y& R8 o7 d2 G: @
    81   Freshman    Yanli Zhang  Female   165.1    52.08 w- n$ K! u) K% Y& p: p

    3 _5 W* T, p/ \6 @: W2 {4 f6 Y, ^' mdf.set_index('Grade').sort_index().head() # 索引排序' u: p6 h3 W6 h2 D
    Out[29]: + U& r& f/ [' E
                       Name  Gender  Height  Weight, Y- }7 t% B& P$ U7 ?; m8 E
    Grade                                          
    7 Z" y. j  v5 FFreshman   Gaopeng Yang  Female   158.9    46.0# }" Y1 w/ v9 Y
    Freshman      Qiang Shi  Female   164.5    52.0* h; R( p/ }  C
    Freshman  Changmei Feng  Female   163.8    56.0( m7 U$ q5 V0 G9 g: Q  b
    Freshman   Xiaopeng Han  Female   164.1    53.0! ~& M  i- L( |5 t8 Z" o
    Freshman    Yanli Zhang  Female   165.1    52.0" e) ~5 B8 \. G% Y; V

    8 j; d1 ^2 Q+ J! M% N9 r2 g5 F  f& z1
    $ s4 n0 D; P  [1 J$ d8 I2
    ) x* q- n2 E/ z6 u3# E/ ]+ J7 D# S5 Z, ~
    4
    4 u% H( h! X' e5% p8 f; t. N1 ?3 j" `5 e4 h/ v
    6
    % R* [1 L1 ]/ b# u5 r7% t$ z& g1 c4 p$ H
    8
    ! k% Z$ O& R, ^; p$ _) l7 I9 z90 f  n& O. k& b1 W; L6 ~9 z
    106 @3 I4 F0 _1 ~! j# P* {  s7 o1 P
    11
      C& e/ ~) _5 \* X, L" O% H12
    7 |* N) S4 b$ T13" N8 q/ O& Q  B; S
    144 v; U/ {4 i9 D9 C4 _8 x, V
    15
    & \( b1 d4 K& F; x16' |! f6 K* H3 d+ m* C) B* o+ N
    17
    3 a/ A' D; B' S6 n18
    : P. I0 |6 i* h% [, R% |: h19! ?5 j8 M9 {$ w& H" W( _
    20
    0 o" ?; N9 ~9 `7 W  由于序的建立,因此就可以进行比较操作,方便后续索引操作。分类变量的比较操作分为两类:
    * S) ?2 F, ?! N9 R3 u$ f8 }: {% Z: b2 H
    ==或!=关系的比较,比较的对象可以是标量或者同长度的Series(或list)。(无序时也可以比较)9 A9 R0 Y4 I& N" _" x
    >,>=,<,<=四类大小关系的比较,比较的对象和第一种类似,但是所有参与比较的元素必须属于原序列的categories,同时要和原序列具有相同的索引。
    . a+ O$ L9 E7 n  N0 R' V, z* X, Sres1 = df.Grade == 'Sophomore'; N  Y9 y# Z' ^, ~. k
    ) h$ B4 Q2 e1 ?* N# ~
    res1.head(): Z$ c, x( M3 H) L8 A- F) }) \) u
    Out[31]:
    * Q  }% V: s' I, e6 q3 `0    False
    5 S9 n- @5 v2 w1    False
    2 q8 U; M! |/ R$ d+ f) e2    False
      G1 a. W  X& t5 ^( q3     True  R# ]2 K9 l3 a: f3 V# o5 c
    4     True) v0 ?' {% l5 ^# z& l0 R$ v
    Name: Grade, dtype: bool( y+ c1 B' G, F/ o$ D5 B! W

    ) t# y2 }! R6 A9 P6 |res2 = df.Grade == ['PhD']*df.shape[0], \$ o# r1 W- K

    1 c- M2 v; R  I8 Ures2.head()( _0 w! ^( {# u" T) m
    Out[33]: , b: M  w6 b. ]1 h( [& `' I
    0    False. q6 A% X& P- W2 T3 {6 b5 {
    1    False
    5 B8 e: [5 D1 T; S' c2    False
    0 H$ w' P  {/ A3 J3    False; r$ \2 N: m( \% Z$ G3 L
    4    False
    9 }$ N2 i( Z6 D: j$ U# P- ]Name: Grade, dtype: bool8 f+ v- z' E# X; Y9 i

    ) Z7 ~* E6 I( j( xres3 = df.Grade <= 'Sophomore': V2 h: ~, C# V% p& E

    2 q9 }% L4 N1 D" u0 u. [; Pres3.head()- `# t9 R  I( Y
    Out[35]: # `- N1 v" `# u% ?4 ]; R
    0     True/ u9 u1 [( o% c5 G5 i
    1     True
    & M0 {( _1 X" V: l5 X" @2    False
    ) B1 B7 t$ M3 E( p( Q) y3     True) k0 _1 a$ u& [  a
    4     True3 G* |/ Q) j- f! {' x- L
    Name: Grade, dtype: bool
    * s* p; g! R5 V0 M9 s
    : z  |4 c9 U9 N" `# ~4 r7 ^# sample(frac=1)表示将序列随机打乱。打乱之后索引也是乱序的,直接比较会出错,必须重置索引。0 |7 H3 G$ X1 v
    res4 = df.Grade <= df.Grade.sample(frac=1).reset_index(drop=True) / T/ @5 [( \' C3 D% S, r

    ! z' X& O$ z. ^2 M; E! Nres4.head()& M" x' q. v( n7 r8 V/ f0 _; W9 {  G
    Out[37]: 6 O$ Y# }# {" |
    0     True
    " {$ F  g' L1 [) {; {/ p7 J1     True
    - H2 I7 a/ i3 ?/ t2 d: R  ?+ X2    False9 P$ Y4 G, t+ n; m4 M
    3     True( t8 E" E7 y$ u
    4     True; ?: ?9 s! `) J9 b- \
    Name: Grade, dtype: bool
    $ Q% [( I  k2 H1 k
    + U5 ?& x6 E9 S9 P9 I- E1
    * B2 ]( i9 [. p- e& g5 h. d5 n28 y. Q. |" R& x: r0 t, C
    3
    & G6 H* t8 F9 R$ f4
    ! T9 ]& F/ w4 T* Z: Z55 ^3 {& m/ f7 V' N& l; `* k- E! M
    69 B- B0 n, i; a% k+ p
    7* c& i8 R5 p! }1 c0 v$ `- P7 o& h
    8
    & {) t/ b8 y; H& v, p1 Q; P92 N6 U  i6 I+ f, A) O" f1 p
    10# B' e) G/ q! H9 ]% i) ]
    11/ R* k8 Q5 ^6 H2 Y
    123 y2 a; Q0 u5 p  B2 C) Z0 E% J* }
    13
    & U( W# O8 u+ [14
    , x" o1 c1 W6 S4 K& `0 D+ q157 p- d: C! h9 a* D# E- p# @
    16
    # n$ h6 E0 ^  g. q) W17' z% G- d  a& o' f' K( _* h8 A9 i
    18+ Y! H% V) h  O) E2 Y, l( J
    19
    * |! {: ^: Y1 r" t9 |20
    5 \! x5 C2 G) p* L3 k21
    8 V2 K9 o  v. B6 ?8 N22
      S1 z% c1 t/ ~4 V: ?23
    ; H3 m: s3 z" Y3 R- V24
    % c( d1 B3 N$ \: W$ b25
    7 C/ r8 d  ]8 N0 O260 U- ?" G# I: B/ a* a3 ^
    27; N7 f, T; f( O% H: H& [4 ]9 g! ]7 d
    28
    ' y3 z1 u" y5 v29
    ! h, T1 X* j" J  p. G30
    : B- l# }* W3 c; l1 D31& L( s" T5 A- K/ ?) [
    32
    4 K2 H2 y/ u) G0 l; E) P, |" D33
    8 W& [. e/ K% Q/ l349 J5 g. R: {) _' f' o/ U
    35
    / m" |2 K! i+ v2 y3 g8 C368 M$ T7 U3 H, H+ E
    37$ `  m; K2 o1 p% x, p! U
    38. ]9 X; W9 o2 T* S9 w
    39- ?/ b" Q. z2 Y9 I0 S
    40$ X: z  c5 ~$ W1 |# \
    41! B$ W% u, F& y; W
    42+ i: Q$ h% r" M. K
    43
    4 r, H8 E, G( s448 O5 C6 \; ]0 O4 |) M1 z9 _
    9.3 区间类别
    0 `+ t/ \; l' Z! W" t* b8 K9.3.1 利用cut和qcut进行区间构造6 x+ z5 L3 D$ {! M  _4 x, F
      区间是一种特殊的类别,在实际数据分析中,区间序列往往是通过cut和qcut方法进行构造的,这两个函数能够把原序列的数值特征进行装箱,即用区间位置来代替原来的具体数值。. ]4 M2 V! }. |& g% W: T- k

    ( S% ^% B, ]3 h; ^cut函数常用参数有:$ e/ i6 i$ \& K5 ], C# O  I% e
    bins:最重要的参数。
    / X0 b  M6 `6 }. {+ G$ Z4 U如果传入整数n,则表示把整个传入数组按照最大和最小值等间距地分为n段。默认right=True,即区间是左开右闭,需要在调整时把最小值包含进去。(在pandas中的解决方案是在值最小的区间左端点再减去0.001*(max-min)。)
    ( p: I& J$ ~* u0 \9 F9 F( x. l也可以传入列表,表示按指定区间分割点分割。
    - |. y( H* Q3 {. P# O% c  如果对序列[1,2]划分为2个箱子时,第一个箱子的范围(0.999,1.5],第二个箱子的范围是(1.5,2]。% F. L9 L; I6 ?
      如果需要指定区间为左闭右开,需要把right参数设置为False,相应的区间调整方法是在值最大的区间右端点再加上0.001*(max-min)。
    % l: ~) G6 `) t. p. D: j% a$ }* O/ H7 x% U* t8 k
    s = pd.Series([1,2])
    , A* s0 r7 h! V' i# bin传入整数
    - K/ X: U" N: e6 _0 n# a; z1 r: C- K* l% c# k
    pd.cut(s, bins=2)& I& r$ p: s% i' S/ q' }
    Out[39]: 2 f+ |# O* w* e7 o. }. [/ i
    0    (0.999, 1.5]
    $ @+ A1 w" ]9 F4 {1      (1.5, 2.0]
    9 P  m. ^2 m* w! B& mdtype: category* Y& d: f% Q: H6 W9 @2 U: [$ `0 O
    Categories (2, interval[float64]): [(0.999, 1.5] < (1.5, 2.0]]: J# D7 T$ V1 }4 D! c3 e8 x* }9 i
    5 \# }7 V' T3 T0 ?5 @% V
    pd.cut(s, bins=2, right=False)
    4 z) F8 g! c' T7 HOut[40]: 2 b' t& X3 O; b# I
    0      [1.0, 1.5)
    0 {* G! B3 D8 \' l1    [1.5, 2.001)
    % Z$ M/ P2 A2 P3 ^0 `: z: B3 ^: _& R& cdtype: category
    4 ?6 B/ C. ^1 C# ?Categories (2, interval[float64]): [[1.0, 1.5) < [1.5, 2.001)]
    ( m4 m; b8 f: m% F
    2 f* |8 g2 y- Z1 N+ }$ ~' ^. s- e+ q& v0 @
    # bin传入分割点列表(使用`np.infty`可以表示无穷大):
    * j7 w  @$ O& Z# u3 `# f' k# E: mpd.cut(s, bins=[-np.infty, 1.2, 1.8, 2.2, np.infty])+ J0 _3 d' I: f$ D6 F" z' d
    Out[41]:
      C# B6 ?" @! @. c0    (-inf, 1.2], O  U6 T% P* }/ }' N- ?  G
    1     (1.8, 2.2]( Z4 L- L; Y& V1 ~, Z9 A" K
    dtype: category
    . D$ @% `+ J- `7 {. zCategories (4, interval[float64]): [(-inf, 1.2] < (1.2, 1.8] < (1.8, 2.2] < (2.2, inf]]4 K" b* {9 w% o

    ! t- m# E  k- V  w/ P5 \18 ~9 |5 u4 W1 H5 t" \
    2/ ]% T2 t/ i2 f- {" J! t1 Q
    3
    9 [! K1 n2 L  G% j- r  v4
    ' {' }* K0 ~: X4 t$ N5
    ( p$ `1 r' e3 ]* g  S6# Y. ]9 z1 X( `3 x) V2 x. Z+ c* A7 y
    7
    ) F- s9 c4 P, ~+ z* l6 b* G, b8/ i& [( a, ?1 a
    9
    + ]% k" _  G% B- G10
    : m4 I1 ?) z9 A2 @, H/ I11
    ; {4 b7 y# U4 L. H" I. x  R12, W$ Q2 r1 A& b, ^4 ]: |  E+ d: I
    13
    & D1 S! U# m( _5 b4 l9 k14
    : A8 {9 {$ Y( |0 ?! V15
    $ A" f2 i& {1 E$ _3 Q: n16( D: p* l' k( @8 C4 g2 X/ n3 k* w
    171 o) K6 t& Y+ B  b+ j* i
    18
      I7 L2 A0 o8 ?4 `3 I8 B/ r19
    ; v6 Y$ D- M# \0 K" R208 G0 Y- Z( C5 a4 Z
    21) a6 P2 T! J+ ^
    22
    . A/ }) H- X7 i23# R7 ]5 x% b# X4 N
    24
      B! \7 p6 k+ Y% O' H: ?+ c$ L/ j25
    ) a6 q7 l) ~  M8 s- _( H3 l0 T  q" G& Jlabels:区间的名字6 ~+ P8 R, m8 s3 t& ^7 a
    retbins:是否返回分割点(默认不返回)
    % H8 ^- y# |# V% d. {默认retbins=Flase时,返回每个元素所属区间的列表
    - @3 A. b) s2 f9 k7 j+ y# b: eretbins=True时,返回的是元组,两个元素分别是元素所属区间和分割点。所属区间可再次用索引取值
    : ^9 g: A" r! Q6 R5 R. g/ [# I& K$ c& n/ {
    s = df.Weight
    " d) |9 v) E. y9 q6 x, H% ]6 x2 eres = pd.cut(s, bins=3, labels=['small', 'mid','big'],retbins=True)6 o0 [, w: o& J* C- X" T2 E
    res[0][:2]' u- [  R. a# G' i
    / L/ E9 K1 g; i
    Out[44]: 3 j: m1 L3 ]) k* O1 E* U0 b
    0    small, y& b1 n$ {+ `- w# q
    1      big" H" Y6 ^/ x5 L9 \
    dtype: category1 k7 `5 I& v9 @5 u3 b# w
    Categories (2, object): ['small' < 'big'], A; v" {; `+ l

    7 z0 b& _6 p* G( X0 R% Ores[1] # 该元素为返回的分割点' i7 e  K; u- P5 k& [, w* h
    Out[45]: array([0.999, 1.5  , 2.   ])& X% d! B4 `$ @$ r
    1# G4 P! T. Y" ~/ S0 Z
    2' `( x; y" ~& x/ L7 ]' m( ^
    3
    ; l2 `* u' k" t  e49 h9 ]3 p7 ]) d' ?, J% z
    5
    ( _# R. I4 _' d0 O$ v# g63 t, A( o  R7 ?" H
    7
    4 g3 Q6 ~- C2 t8) G0 i7 {" w0 y2 P+ c) ]2 j
    9( v% ]5 d$ K  I7 ~0 B; o
    10
    8 k6 _9 F5 i) U9 j, H; L11; p# O9 X* b* ?' X4 Y- a) s$ L. D
    12
    . u! f5 u# N  ]# t5 e; P7 ]qcut函数。其用法cut几乎没有差别,只是把bins参数变成q参数(quantile)。
    6 d" U# R: E. A* Zq为整数n时,指按照n等分位数把数据分箱
    : o" @. I$ O8 i3 ]. p. b/ `; n7 y! }q为浮点列表时,表示相应的分位数分割点。
    - j4 j7 u, r. U! b# [) n1 rs = df.Weight$ J( N9 i1 f( V6 ~( p$ ]6 _
    - u/ Q! ~- k8 E6 _. u
    pd.qcut(s, q=3).head()
    " u9 J1 i# `4 l) E+ `Out[47]:
    ' G" x2 X4 z5 h7 Q+ V, w5 X0    (33.999, 48.0]: ?# W7 c5 F1 Z- N% f
    1      (55.0, 89.0]: T8 o$ a0 `: Y8 n
    2      (55.0, 89.0]
    8 i. ~$ ]" ?% n6 c0 d. b+ [3    (33.999, 48.0]
    ; [  a# s5 L% n4      (55.0, 89.0]7 _7 u( S! |+ k! W8 K
    Name: Weight, dtype: category
    . B- v7 p4 y! B* N: E7 `, o" \; X/ b2 HCategories (3, interval[float64]): [(33.999, 48.0] < (48.0, 55.0] < (55.0, 89.0]]
    0 o  ]5 \3 N; s4 a- P2 j2 f7 e! L" P* }( T2 g1 `/ s( ?/ r
    pd.qcut(s, q=[0,0.2,0.8,1]).head()! N5 S, K- R* L- X: R5 X
    Out[48]: - u& r% ^" S8 ?" e: V7 K
    0      (44.0, 69.4]
    $ }: q, W7 L2 N& E( W1      (69.4, 89.0]: k, h9 B/ C$ S; I! t7 F9 u1 a( ]
    2      (69.4, 89.0]5 m8 L- p2 K) H% N
    3    (33.999, 44.0]) g, l9 n8 z6 Y( r7 }: l
    4      (69.4, 89.0]
    ' Q9 M, K  E/ M0 XName: Weight, dtype: category; U2 [7 j! @# ~2 w- B1 ^- o1 r- r
    Categories (3, interval[float64]): [(33.999, 44.0] < (44.0, 69.4] < (69.4, 89.0]]/ j6 g. H: T! C* Q4 z
    & T  K( ?8 h+ \, q8 L) L1 B
    1* y' f( A3 v* N. h
    2
    ! t  S- [( B5 w( o" _4 o: X3& H5 M: u- y& c  a
    4
    # H) h- ~6 L$ i0 V( r3 C5% \8 S8 y# U9 g7 O" Q5 I
    6& ?, K5 v3 j* O6 K8 S
    7# {( G3 S. l& p( w
    8
    # P3 N7 T6 r$ n9
    9 p; x( W  m; j0 n5 m0 p  L108 x3 q" A- D/ O/ z- b1 l
    11, D/ Z# e! H# W, K0 L
    12
    $ k" m! t' Z# N13, Q; `% G- `' S" l( b
    140 ~! ~7 F  H* e% m( q; `: ]
    15
    , X$ `0 p( }3 x161 v1 N, W) P  {6 Y. C9 p, |$ o
    17  ~8 [% y. l6 \3 E. Z/ T
    181 R0 u  u# o- S0 w; T. h+ U4 d
    19
    6 z9 }, r: F8 N: @; h' q20
    4 g1 w! J. @% k) O21
    6 g/ S; C- E9 K* ]# w9.3.2 一般区间的构造. P- j2 j# A& g1 [# x
      pandas的单个区间用Interval表示,对于某一个具体的区间而言,其具备三个要素,即左端点、右端点和端点的开闭状态。
    ; a* D- }' S) @1 F0 v% }( ^: Y  }3 n
    开闭状态:包含四种,即right(左开右闭), left(左闭右开), both(两边都闭), neither(两边都开)。
      v% k% t; ~9 u$ Rmy_interval = pd.Interval(0, 1, 'right')
    8 K) f7 H; z5 x* v( H9 o) ]( p8 w$ ]: |% j+ D7 P6 ^# c
    my_interval  N  R9 l1 N" W
    Out[50]: Interval(0, 1, closed='right')
    : Y8 ~* F( p0 }) T" \1 b& K9 C1
    , ^) f6 n& W9 r: K5 V2. z5 K$ J/ f! Z$ }. b- M
    3. s6 k0 l) [8 f% G' K+ x
    4
    & t0 `9 U* _1 m) b区间属性:包含left,mid,right,length,closed,,分别表示左中右端点、长度和开闭状态。
    % ^- p3 D' F6 s2 A: {6 c5 v8 K0 z/ g使用in可以判断元素是否属于区间/ \7 o* j9 o" S6 Q6 H
    用overlaps可以判断两个区间是否有交集:
    9 d" G& O$ S3 O5 b$ P2 C6 F0.5 in my_interval
    ) [! \' z% e* l# O4 _
    ; o% ~) W; Y# i) E0 @+ sTrue
    , y  u9 L" X: [4 C8 ^" `6 G; T% x+ O1
    4 Y; s2 B! Z" e! Q2 w2
    4 I- w$ g8 H; i: E; f! n: ]/ O& D, g( b3' m& V$ ^. ^$ O3 `  |7 U
    my_interval_2 = pd.Interval(0.5, 1.5, 'left')/ x; r- _8 T( }
    my_interval.overlaps(my_interval_2)" s6 N: c7 \) @4 x" D) f/ f

    $ i9 d4 `% m. A% s2 V  yTrue
    ) d# G# f* X8 L- R" I7 K" C5 z$ t- J1 J1' C# j* B2 Y9 x+ J  m4 b
    2
    5 G8 J% s5 r8 _& K, d) V3
    ' @) G2 J" `8 _3 y7 q( H2 [4- q2 j1 L7 N2 o- {1 Z: r$ L
      pd.IntervalIndex对象有四类方法生成,分别是from_breaks, from_arrays, from_tuples, interval_range,它们分别应用于不同的情况:& L4 ^7 {8 l" \- ^4 n+ X8 C9 A& d
    7 N  K* L/ r8 e6 d$ Y
    from_breaks:类似于cut或qcut函数,只不过后两个是通过计算得到的分割点,而前者是直接传入自定义的分割点:! ~5 w3 z& j+ m( q
    pd.IntervalIndex.from_breaks([1,3,6,10], closed='both')
      K7 ~  V+ ^2 }1 G1 m+ e( S" l" {
    - P) p5 @* l* n  g5 I, QIntervalIndex([[1, 3], [3, 6], [6, 10]],3 G' {0 g6 Z2 r  t
                   closed='both',
    * L$ F( O' [/ B               dtype='interval[int64]')
    8 t3 a4 |# C9 T3 I' y; M4 K( R% \15 W. J/ N! [9 Z$ }) S
    2) d, R; P; t$ j3 A0 ]) A3 m0 q
    30 w6 }6 p$ v3 o* j* q% J
    4$ U/ N% P) T% b. U8 |3 G6 j
    5
    7 d5 c, V* T& ~  Y  a' N/ X/ @from_arrays:分别传入左端点和右端点的列表,适用于有交集并且知道起点和终点的情况:
    * A2 n. y. ^8 v/ V3 ?- wpd.IntervalIndex.from_arrays(left = [1,3,6,10], right = [5,4,9,11], closed = 'neither')4 c4 o, r+ z* _# A8 k

    % A* B- x( ~' x5 H, I/ ^IntervalIndex([(1, 5), (3, 4), (6, 9), (10, 11)],
    # C8 |& u7 Z. B! t+ |8 s1 C- E                  closed='neither',5 C5 z* |& X7 v, T
                      dtype='interval[int64]')
    7 n( x( ^; b: [/ ~1
    ; i  X0 H/ }. a5 @; y2
      M( p6 s- Z2 P) c& H0 E3
    8 X$ c7 A3 R$ }. `, ]0 D0 K4- `) _1 T! O- I7 @
    5
    + P- ~* d: n, N; f+ I) Q* b  cfrom_tuples:传入起点和终点元组构成的列表:
    " j( f1 N" i' U, h5 T$ f/ cpd.IntervalIndex.from_tuples([(1,5),(3,4),(6,9),(10,11)], closed='neither')
    ; m* l8 o4 h+ u8 M* s) P
    9 ?1 {1 ^, [7 H! h* X5 R9 [+ mIntervalIndex([(1, 5), (3, 4), (6, 9), (10, 11)],4 p" s3 t2 x" ~& O8 L4 ~
                  closed='neither',. _- N: C1 ?2 z8 p3 J
                  dtype='interval[int64]')
    # u- V7 h# i$ G5 E- O; N; s1
    2 a! z/ n( P8 y: l# Z! K# ^2
    / `! i" |& k3 m/ _; @8 }( Y3
    ; l( c) n+ l; \0 o; c$ q# L& g+ E* F4
    ! b" k3 g0 Q& J2 |/ K' l" e5
    + L: v6 ?0 l1 K/ yinterval_range:生成等差区间。其参数有四个:start, end, periods, freq。分别表示等差区间的起点、终点、区间个数和区间长度。其中三个量确定的情况下,剩下一个量就确定了,从而就能构造出相应的区间:: C# g$ v, }; X5 }5 z& T4 }
    pd.interval_range(start=1,end=5,periods=8) # 启起点终点和区间个数: P+ @) @/ a* `# R. _' G. P& c4 i
    Out[57]:
    0 ?1 W4 F; J" qIntervalIndex([(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]],: q9 s' g4 p; u& U" ^( R2 `
                  closed='right',
    # y  u& I1 {. N4 W9 q- V8 {0 ^              dtype='interval[float64]')3 ~: m) O; k" R" L2 H
    / o1 p. c3 _# h( e7 i# Q6 B
    pd.interval_range(end=5,periods=8,freq=0.5) # 启起点终点和区间长度$ c: R4 x6 ~- ]5 f
    Out[58]:
    " ~, Q, `  l1 p9 VIntervalIndex([(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]],
    - Z8 ^# k- d- t7 i8 s              closed='right',
    ) Q1 |5 j# A, V" a4 H/ [! x              dtype='interval[float64]')& |+ p% j& t7 b. r
    1- v! H1 p# ]! N( ~: ^/ d
    29 Y0 r6 u) l. L3 _2 _! t
    3$ a3 c4 U5 ^/ [/ d
    4  E# ?; m& F) G7 M
    5: H+ G* v, ~$ B) I
    6; W+ Y& L# G0 a# n  d
    7( d! J1 E0 D: e7 Z
    8: x% z: G7 f8 o, R
    9
    * G3 O2 t1 c8 d10
    1 p% q1 ]& X1 I" |' U" a11
    ; h0 s) m! S! \" N+ S$ m6 w' Q. S- h: d【练一练】, o8 J9 y/ x, g4 ~4 M% d
      无论是interval_range还是下一章时间序列中的date_range都是给定了等差序列中四要素中的三个,从而确定整个序列。请回顾等差数列中的首项、末项、项数和公差的联系,写出interval_range中四个参数之间的恒等关系。/ u4 {. f. x- K# f1 {

    ; p3 h* |- x8 ]5 A  `# Y  除此之外,如果直接使用pd.IntervalIndex([...], closed=...),把Interval类型的列表组成传入其中转为区间索引,那么所有的区间会被强制转为指定的closed类型,因为pd.IntervalIndex只允许存放同一种开闭区间的Interval对象。
    7 N; l+ W  _) |9 Z4 v. O' V% y0 @4 r6 ]' c- v4 Y) Y
    my_interval
    # L/ |# A$ l- c0 r5 k8 |Out[59]: Interval(0, 1, closed='right')
    0 j; K9 p% x9 {7 P! {+ H; U$ T5 B4 k, t& R
    my_interval_2+ ?- T6 v8 _7 E. S0 s$ S6 L0 N
    Out[60]: Interval(0.5, 1.5, closed='left')
    / f  r  P4 ~1 c( n1 X' |0 t; B$ I6 T
      F. w/ t/ h3 X$ e' ?& hpd.IntervalIndex([my_interval, my_interval_2], closed='left')
    - \. h  X( d% I2 t# }) {/ J- lOut[61]:
    $ l) V6 J, }0 z5 X# `$ @IntervalIndex([[0.0, 1.0), [0.5, 1.5)],
    , s; `: O0 ?' d, V7 E3 k              closed='left'," l7 m. ~7 O1 i2 u
                  dtype='interval[float64]')
    3 s# t/ G1 ^0 ^4 o% l1
    # \- D3 o9 j9 g0 z2
    ! j8 G! E' j& y3, t0 R( h5 s4 ^+ g
    4
    1 M2 B, `( U& E( c  }5: x1 k# a; g4 l2 ~9 D! t& g
    6
    . o) i+ b: C! p7  _; w# x! a- m: P
    8
    , J  N: {" J$ G; A9" u8 r) l7 W, K
    10* O3 W; V" H+ @  B+ K
    116 c* _8 p9 T$ R% [1 k8 N
    9.3.3 区间的属性与方法: ]- R" H& U& T* |9 f: o) G
      IntervalIndex上也定义了一些有用的属性和方法。同时,如果想要具体利用cut或者qcut的结果进行分析,那么需要先将其转为该种索引类型:3 i' V( z+ {( y( ^9 }

    7 [% N3 q. Q, N1 Z+ s' c4 as=df.Weight
    ( D8 g8 p) \4 U( ~5 c) Sid_interval = pd.IntervalIndex(pd.cut(s, 3)) # 返回的是每个元素所属区间,用具体数值(x,y]表示4 C' D" @0 B8 B2 w
    id_interval[:3]; w, a  C- f. |- `5 s

    3 h7 v+ D2 L+ E0 bIntervalIndex([(33.945, 52.333], (52.333, 70.667], (70.667, 89.0]],: l5 Z4 c, C; j: ], g; ~  y2 Q1 }6 _
                     closed='right',0 n- I# C& \  ]; L2 S0 a( {
                     name='Weight',& y7 t) @, O) V
                     dtype='interval[float64]')
    . b; U& y+ g, f1
    . F2 T" Y) K& r2
    7 b6 b0 h5 Z- |  t' I9 n: l3
    $ K* ?5 r: W' O% X7 N7 @5 w! F4( }  n7 }- p: h, [7 r
    5
    7 ?, S( [4 |, K# x  |: m) ~* \6- g: [* T- ~4 D, A- Z2 [  V
    7" I, ~  p# h& I/ W8 y+ S
    8. U+ B/ p* T( _: `0 ]9 J
    与单个Interval类型相似,IntervalIndex有若干常用属性:left, right, mid, length,分别表示左右端点、两 点均值和区间长度。
    ) ~7 p. x! k- Y. f5 Cid_demo = id_interval[:5] # 选出前5个展示
    - }3 r5 z) A( o$ b" S
    . X' z3 e- A5 X5 Z2 Aid_demo( w* e3 ?8 I& K: A; J
    Out[64]:
    . R9 x1 q- l0 r6 M4 oIntervalIndex([(33.945, 52.333], (52.333, 70.667], (70.667, 89.0], (33.945, 52.333], (70.667, 89.0]],9 o; o1 W! `; t6 T, {1 X) J
                  closed='right',
    - z$ R7 C! D. j/ H! ?* R              name='Weight',
    $ P3 _; x5 X% u7 u$ d              dtype='interval[float64]'), ~8 W# p* w0 P
    4 b1 e  e+ l2 |8 a; A9 |7 ?' G
    id_demo.left # 获取这五个区间的左端点
    + ?& z2 Q/ i$ s' D' z' T* O8 xOut[65]: Float64Index([33.945, 52.333, 70.667, 33.945, 70.667], dtype='float64'): N) q( I' a2 A& u/ N6 H- U2 A
    6 ?4 j4 C) a0 F1 Q
    id_demo.right # 获取这五个区间的右端点: S6 A( g# @+ T5 P
    Out[66]: Float64Index([52.333, 70.667, 89.0, 52.333, 89.0], dtype='float64')! P2 X" f7 P+ @4 c
    1 g0 l8 T1 ~& L  J5 T
    id_demo.mid
    9 A! D7 G" b) w* A: t$ UOut[67]: Float64Index([43.138999999999996, 61.5, 79.8335, 43.138999999999996, 79.8335], dtype='float64')% A8 _% Q; l! v5 {1 P: V/ y

    / C! ?3 t; d, E" g6 h1 s' Gid_demo.length
    - g$ A. Y+ q, vOut[68]:
    & m4 J9 b2 a: Y" B& I) `" M3 R9 KFloat64Index([18.387999999999998, 18.334000000000003, 18.333,
    : u+ E% h4 A4 ^: L7 F              18.387999999999998, 18.333],
    7 ~. Z) q$ c5 I) R             dtype='float64')! p/ P* J* y. q- g

    - Y5 \# k( w; I' I! K9 K! u% e1
    4 ^6 V1 z) e& c$ D, T: @6 w  ^4 E2" Q5 U0 p5 Q" K4 W$ x
    39 v1 ?" q( {7 k, B% D# H0 r
    46 [! R- ^4 }& b, U. z
    5
    - P( ^% ~$ i% K; M% e* O6
    7 @9 Z. K3 r9 A0 V: [$ A& J; E7
    / v+ Q: E8 W) i, e6 E7 W! f- Y8
    $ P& L( |$ g2 A98 w: \+ l+ F6 V  {8 B
    10& A3 y) y" R* V/ `. z# ]- I
    11% {, Y- ]& N0 S+ g
    12
    $ X; y6 F$ t) r( Z+ u6 Q2 \13# j. a, x& `2 `9 P4 U
    14
    7 H+ S! `$ ]( c) N7 b! i7 Y) M15; }2 A4 I0 v. `; j
    16( o9 F4 F6 k) c: ?
    172 d. j) a) w2 p: k) D( t* T
    18
    + ^! T7 |, \3 y$ G9 i! g190 Z" ?1 b$ s' a( Z5 H9 T( N
    20
    5 b  D9 c8 Y3 J3 ]& d  Q3 D21& J3 T$ y$ r3 \; P+ A
    22
    8 G# B' a$ Y0 W% I+ V% w9 b231 M/ a9 S! S0 Q# V2 o
    IntervalIndex还有两个常用方法:3 ~) `! x1 N0 K: ~" r
    contains:逐个判断每个区间是否包含某元素- k% S9 h( p# C2 `* P
    overlaps:是否和一个pd.Interval对象有交集。
    " |1 B% W5 o* b( I" c+ I. hid_demo.contains(50)  |) Q( |' g: a. S* ?
    Out[69]: array([ True, False, False,  True, False])
    0 W9 }, R7 I& w' Y4 b  T( i9 v4 W: B& S) h
    id_demo.overlaps(pd.Interval(40,60))0 N7 p7 ~6 p! c2 o2 k
    Out[70]: array([ True,  True, False,  True, False]): A" b) D9 g7 Y- W. b
    1
    3 \/ b: b1 u! z2 y2
    1 C& z' i/ w  H0 }2 u0 C3( O7 w) ?4 i+ `# Z9 p
    4
    8 Z: V/ c% O. x; W; C; ]55 F" \0 M7 ~- ?
    9.4 练习
    ) d6 W* q8 Y) O7 \3 g# @1 JEx1: 统计未出现的类别
    " r& n( ^: |& Q3 A& K2 @0 T- N  在第五章中介绍了crosstab函数,在默认参数下它能够对两个列的组合出现的频数进行统计汇总:, t1 F' O7 q, S2 H
    3 F0 B# p1 M% t: q& t" B. \
    df = pd.DataFrame({'A':['a','b','c','a'], 'B':['cat','cat','dog','cat']})/ W9 ~- K, ~0 i" t  r, p& u1 r
    pd.crosstab(df.A, df.B)
    / k5 @, S5 g5 k, C1 J/ H) `# ^5 w0 z- Z5 x+ ?: Q9 `& B. U9 K  Y
    Out[72]:
    4 z" l! F- e  L. SB  cat  dog
    . D1 E" G& A: O' ]4 Q$ Y. ?A         
    6 ~. l2 z! S9 F1 L# }  z4 ca    2    0( ?/ O$ F5 I6 ?( V; [* @, {
    b    1    0* o) d- F! I9 V- D- k
    c    0    1
    * v: t1 N5 M5 H: R0 f1* Z2 K) R; s/ Z2 O  C( k
    2
      C# p* E! U: ~2 p4 U8 ]& c; n36 z& @9 t) o: p! u8 m% S
    4+ X- E4 o! {. J  y
    5) }+ n# M' B( O1 s1 }, t, r
    6  p7 g' O3 p' L1 I
    7
    : S& c6 x6 @6 \4 m! d& E8+ z; L5 E0 ~4 x  j4 b! M
    92 c8 t' @! L+ b) `
      但事实上有些列存储的是分类变量,列中并不一定包含所有的类别,此时如果想要对这些未出现的类别在crosstab结果中也进行汇总,则可以指定dropna参数为False:( G0 ~4 t2 y# W

    6 C; D$ ^( ?5 C: v, ~8 @7 ?df.B = df.B.astype('category').cat.add_categories('sheep')& v) Z6 }- I0 S) T
    pd.crosstab(df.A, df.B, dropna=False)* J! l4 P+ V6 G' r) g+ T3 a2 K
    . ^& g3 S- U# I- s9 C
    Out[74]:
    # B, N4 s7 c! w0 n3 ^# ~0 HB  cat  dog  sheep1 R: Z: X' [4 }! j& x% T8 B
    A                 " Y+ @! ?# z% q2 U" J! I  y
    a    2    0      0
    - x' I$ j8 ?) r9 U6 B/ a/ @# Ib    1    0      02 D' H. A  H% F+ d9 b
    c    0    1      0& z& X6 I4 ?. z( n
    11 u, _& x% m: m) R
    2
    . A% a. k8 s; E# D2 p% D3
    . ^9 F8 D' K! t0 u4
    % D1 C- V% ]8 [5
    7 e& W4 S7 ~  z9 y( p0 }6  B# a  ?# ?5 E3 p8 \7 ^8 y
    7" l$ p5 N" D% M3 u4 v8 P# P
    8
    3 [% }% o5 C  F& u& O9 w; g8 j! \9; U2 \* T: b) H- y
    请实现一个带有dropna参数的my_crosstab函数来完成上面的功能。* _  s% q7 g& R3 n0 @
    $ |; T3 c5 X; [% `' }6 L6 Z- N
    Ex2: 钻石数据集- e  {- c4 C% y# `
      现有一份关于钻石的数据集,其中carat, cut, clarity, price分别表示克拉重量、切割质量、纯净度和价格,样例如下:- E. `4 C* [" Z; b" y
    1 z: a% C7 K- ~, o: e, a5 n/ w# v
    df = pd.read_csv('../data/diamonds.csv')
    6 c: f+ w# g" E  y; Q& mdf.head(3)
    4 I$ ?, B3 r4 S2 I# t1 b
    6 b  K# }2 M! O2 V8 j6 F4 xOut[76]:
    / d7 u. e: o$ [/ f) N   carat      cut    clarity  price
    ; r9 V5 k( b* G: g0   0.23     Ideal     SI2     326! B& B: j; i- l' V% j  y4 Z6 g
    1   0.21    Premium    SI1     326+ C+ `& a4 H2 l! \% m0 w
    2   0.23     Good      VS1     327
    ) f- X8 {4 m  W13 z% C0 S( O( U& C8 w. {4 g
    2
    5 h  C( X) [, }$ g9 f9 B* F- o3' M" r! R8 y. c3 l9 O" {- A
    4/ j; Y6 Z$ ?2 l) O% c* N, ?" f7 T
    5, a8 {$ R+ c# V5 o: v: [6 M6 E
    6- W+ g. K/ G# l% M
    7
    6 v& f9 x" B3 z/ `) u2 B0 E/ Z8# y7 ?& e, @% W4 Y. E
    分别对df.cut在object类型和category类型下使用nunique函数,并比较它们的性能。
    5 j- }7 H  A, s' O, s钻石的切割质量可以分为五个等级,由次到好分别是Fair, Good, Very Good, Premium, Ideal,纯净度有八个等级,由次到好分别是I1, SI2, SI1, VS2, VS1, VVS2, VVS1, IF,请对切割质量按照由好到次的顺序排序,相同切割质量的钻石,按照纯净度进行由次到好的排序。
    , |$ o. Z3 G8 A* Y分别采用两种不同的方法,把cut, clarity这两列按照由好到次的顺序,映射到从0到n-1的整数,其中n表示类别的个数。
    : E# p' X/ D1 n对每克拉的价格分别按照分位数(q=[0.2, 0.4, 0.6, 0.8])与[1000, 3500, 5500, 18000]割点进行分箱得到五个类别Very Low, Low, Mid, High, Very High,并把按这两种分箱方法得到的category序列依次添加到原表中。
    : E) O7 j" ~$ x/ d第4问中按照整数分箱得到的序列中,是否出现了所有的类别?如果存在没有出现的类别请把该类别删除。
    - {! b8 s% \7 V/ Q' x对第4问中按照分位数分箱得到的序列,求每个样本对应所在区间的左右端点值和长度。
    ( X1 d7 }+ N: S2 j: s: r6 v2 {先看看数据结构:
    1 \0 {/ n9 t+ B, {/ F  x3 d: Z2 f2 |+ N5 T2 K5 T. U
    df.info(): e/ G9 P& ^2 _4 i1 o7 g
    Data columns (total 4 columns):
    ' H* ?' j; l$ z1 c1 s8 E #   Column   Non-Null Count  Dtype  
    3 ^& y9 s: S4 C; j1 A---  ------   --------------  -----  * N) k% _; W! J
    0   carat    53940 non-null  float642 g1 h- X" w  n. v- B8 Z
    1   cut      53940 non-null  object
    ) w- Q, w4 _, P2 \- u  x0 v$ a 2   clarity  53940 non-null  object
    : {9 t3 I8 h5 g+ P! p. g 3   price    53940 non-null  int64  0 E% y; e# c1 }
    dtypes: float64(1), int64(1), object(2)
    : |9 y/ Q) J, v+ T; C* E12 g1 Q) l4 \/ C) P  ]: X
    2- Y# F7 a8 ~& x0 p
    3$ N# Z) L& O  v
    4
    / _. e- P/ b, g" \. o9 L5; C8 m( Z' P; \+ }* u4 k) [
    6. b& H! ^* {5 ~% a
    75 r6 P! L9 j9 L, [1 I* Q9 L
    87 W; q- y6 A$ a5 ]
    9* u: `0 F! H' S- ]5 I# N$ [
    比较两种操作的性能
    $ `. H! |+ k. f8 [, ^5 b%time df.cut.unique()' k3 S3 Z0 R/ P, W7 ]
    5 d( v* n5 _! ]# p
    Wall time: 5.98 ms- e8 C+ P' B: B# E! C  f
    array(['Ideal', 'Premium', 'Good', 'Very Good', 'Fair'], dtype=object)  }7 W* @1 l1 T
    1
    " ~" @# l7 G+ |2' i' z- N* K1 c
    3  u; t$ ^' C9 \0 X  U8 F0 H) B0 \
    47 i1 l0 j* F- N2 U0 `, o
    %time df.cut.astype('category').unique()
    / ~4 |% Q% Y6 w6 K
    $ T+ }3 k9 k  pWall time: 8.01 ms  # 转换类型加统计类别,一共8ms* O$ D4 y& s' Z2 d9 i3 D
    ['Ideal', 'Premium', 'Good', 'Very Good', 'Fair']
    ; v  P8 A" ^' U5 a; o/ c' N. z, @Categories (5, object): ['Ideal', 'Premium', 'Good', 'Very Good', 'Fair']
    # w# {+ H7 \' C/ i! L+ o$ w$ O/ I1
    3 J6 w9 d  J4 N3 g2
    * O5 S' G5 L* e* b. \  b  c' Z37 E2 m6 Y% P0 ~: J8 q5 f$ g: K
    4
    ( I2 q3 ~: V& o; R+ W: M6 C5- J) x, v  P& H
    df.cut=df.cut.astype('category')
    2 U: Q) u1 {/ b%time df.cut.unique() # 类别属性统计,2ms( R# F1 k1 Z0 f$ c. f- e
    , m: E9 l% ^# R8 D7 z3 r
    Wall time: 2 ms
    " G) N: X9 o; S2 l['Ideal', 'Premium', 'Good', 'Very Good', 'Fair']
    6 l3 o  c" E" MCategories (5, object): ['Ideal', 'Premium', 'Good', 'Very Good', 'Fair']1 c  {4 U2 Z9 M: K8 L
    1
    5 a8 [+ U7 x: q1 E1 i, j+ K26 i5 m: u" Q" F" ?
    3
    % C3 I/ E- W# g0 p' U( G& D4
    - K' ^9 m- @1 J' p+ U! J0 j5
    / c1 m- {! O" S3 e: O$ h6% f/ t+ k3 x9 z# P) I7 O
    对切割质量按照由好到次的顺序排序,相同切割质量的钻石,按照纯净度进行由次到好的排序。
    4 c5 L5 S' c: Dls_cut=['Fair', 'Good', 'Very Good', 'Premium', 'Ideal']$ P: B* ~- `# H" E9 U) ~
    ls_clarity=['I1','SI2', 'SI1', 'VS2', 'VS1', 'VVS2', 'VVS1', 'IF']
    ! C5 l4 }# ~- s: ddf.cut=df.cut.astype('category').cat.reorder_categories(ls_cut,ordered=True)  # 转换后还是得进行替换
    / g  i7 ]- {% D& M8 l6 Ddf.clarity=df.clarity.astype('category').cat.reorder_categories(ls_clarity,ordered=True)
    7 ^; K* ?: L1 A+ y* X. V% b3 A( q8 E9 B
    df.sort_values(['cut','clarity'],ascending=[False,True]).head(3)
    $ `) W3 q9 N' y; Z9 a# Z2 J# F4 l2 }
            carat         cut        clarity        price! A( f0 M. B% W
    315        0.96        Ideal          I1        2801
    / }* a. r. Z! t! K: z' t3 b535        0.96        Ideal          I1        2826
    - D3 x. i- i& [7 c7 I551        0.97        Ideal          I1        2830
    ! V3 A+ x" f7 z- |- z6 i. ^1( p) E& Z( ?+ ~
    2
    & {0 A/ |$ \5 q7 w3
    5 o  b; a* [( ~4 y4
    ) V3 K' E, S& u# T* j- I58 Q. m! {6 @* F/ t
    61 _: s  I$ l) s  \6 U! \
    7: n" e6 r1 \( Q2 |1 {% w! e
    8
    0 f8 }  C: k3 w; A' P# Z( i9$ W$ C5 D! t) D, N# o8 W+ b
    10
    ( I+ c* J: m5 A2 p$ h( n& X4 O% h11! T9 X% x( ~+ R+ z: k7 l0 r7 B
    分别采用两种不同的方法,把 cut, clarity 这两列按照 由好到次 的顺序,映射到从0到n-1的整数,其中n表示类别的个数。0 u+ N2 O  a) Q: W" L; }
    # 第一种是将类别重命名为整数
    : Q) U2 S  V- a3 U1 Rdict1=dict(zip(ls_cut,[x for x in range (4,-1,-1)]))
    . {  K! o1 l7 h# tdict2=dict(zip(ls_clarity,[x for x in range (7,-1,-1)]))+ j7 S; x# u9 r$ \! E& V

    ) D: {4 S0 d1 a% ]& }( ?df.cut=df.cut.cat.rename_categories(dict1)
    / l3 k  n2 [4 \4 {; wdf.clarity=df.clarity.cat.rename_categories(dict2)6 _! g, t1 b) n& ^( P
    df.head(3)/ \" q  r6 E- d' Y- w0 H

    - m( |4 R4 a- m        carat        cut        clarity        price, |  @+ [$ m' _8 L. t) V4 X
    0        0.23        0          6                326
    # _. A% X0 m2 Y$ {! J3 {4 |; o1        0.21        1          5                326! P+ h+ L2 N& }5 l7 O
    2        0.23        3          3                327! ?# l1 s  r2 Z, n* V/ ^
    1! `5 @( x3 B5 K6 }5 B9 ?' N9 f4 l
    2
    / B8 u$ {5 H' h; E. J! }% y/ H* q5 M5 i3
    3 n; a4 M2 A( k9 p3 b% [3 K45 l( j4 t' J* G
    50 L! F  z, @, D' N( S
    6
    5 T' D' R$ j4 n( G* y* I7
    ; B- e" h9 h) R- ^% m4 K8
    1 o5 W$ _9 l( G* I, U4 [; F! Z9; g# H+ y# p7 ^$ C
    10
    & H& x1 Y* i4 Z1 f+ n  A11
    3 ]) C  l7 j" L: G6 z- ~. Y: _12& g. i$ M0 e; d0 a6 M, P% E1 S
    # 第二种应该是报错object属性,然后直接进行替换
    + J/ w. R% H6 P; Mdf = pd.read_csv('data/diamonds.csv')- L5 l+ G4 i' ^# d$ P6 \2 A9 I
    for i,j in enumerate(ls_cut[::-1]):
    " ^$ @% C" i# [) F9 V/ U    df.loc[df.cut==j,'cut']=i   M* Z" D$ X* t. z' O
    7 C  C, Z9 e/ Q0 e# {5 [( p
    for k,l in enumerate(ls_clarity[::-1]):0 F& x' M  ?" S3 D; P
        df.loc[df.clarity==l,'clarity']=k2 t4 k, B6 R/ C/ x# b+ F: D
    df.head(3)& U* c6 l& G( ~7 q' h- P
    2 D4 y9 D# ?! _% D
            carat        cut        clarity        price
    # h# s' q* q7 w; y$ M; ~/ G. W7 k$ t0        0.23        0          6                326
    7 Y( b7 h8 n5 _9 n9 }$ b, J1        0.21        1          5                326+ X# x# @" w+ l8 x; P2 e. R
    2        0.23        3          3                327
    . z3 L' K5 u+ R  e  y. q$ U1
    + C% g, ?3 U8 a" _0 K2% y% u* I3 c' V8 u0 j
    3
    1 X" P$ W$ {# n: z* U+ r& a) L4 p) s4
    ; A$ P  e) _6 |3 c5
    ' e% d  O- z; |# ?% b6
    ) I' N' E4 t% @; n' u9 ^0 f7$ D4 i2 }+ Y: n* j& k1 x9 t
    8
    & X4 I# p5 @; M9! ~" z5 i5 s) c. B0 ^% c
    10
    : z) N1 s+ [0 T+ P! Z- T9 j11
    : j" i" B1 O; d% L/ P  ]: Y3 J8 Z7 q12
    8 y/ q2 Q7 P2 z/ o# e7 N" a13- @8 H, `% |3 o" }- ]7 r
    对每克拉的价格分别按照分位数(q=[0.2, 0.4, 0.6, 0.8])与[1000, 3500, 5500, 18000]割点进行分箱得到五个类别Very Low, Low, Mid, High, Very High,并把按这两种分箱方法得到的category序列依次添加到原表中。7 R7 O) O" j1 O0 a9 O' ?
    # retbins=True返回的是元组,第一个才是要的序列,第二个元素是分割点4 y) w7 j% f: e" W& y
    avg=df.price/df.carat. V$ r0 C7 D; G% T2 f2 {3 I
    1 t  O; \$ b& q) ]' u3 N) Y
    df['price_quantile']=pd.qcut(avg, q=[0,0.2,0.4,0.6,0.8,1],' H7 s) h4 c  W
                                  labels=['Very Low', 'Low', 'Mid', 'High', 'Very High'],retbins=True)[0]
    ( Q$ i3 h' Y$ i4 `: @; J% ?
    : O: D' q* `! jdf['price_list']=pd.cut(avg, bins=[-np.infty,1000, 3500, 5500, 18000,np.infty],; B( T* g7 Q" u; `8 N9 y# g' I; V
                                  labels=['Very Low', 'Low', 'Mid', 'High', 'Very High'],retbins=True)[0], O: {, C. P6 i2 J. t6 C2 \
    df.head()% u8 Z. Z& L5 T
    * R: h# f7 j0 O
            carat        cut         clarity        price        price_quantile        price_list
    & V/ `+ u$ @/ y3 F. N0        0.23        0                6                326                        Very Low                Low/ ?+ D, [/ s- o3 g9 l. ~/ e
    1        0.21        1                5                326                        Very Low                Low1 ?& M% p. J6 [
    2        0.23        3                3                327                        Very Low                Low
    , c( O1 m# O5 ]1 a3        0.29        1                4                334                        Very Low                Low
      N5 a3 [* b  X: d+ b) |. W4        0.31        3                6                335                        Very Low                Low                                       0 k: g# m1 Q/ T: E  S
    * R3 C4 F" L# i
    1
    9 h$ p/ }$ q( u8 c7 z% |2" {! {2 t$ I" k. u) ^
    3- _2 s3 a% c( U8 E
    4
    - P" `1 X  A" O  q/ b5$ }, X7 j0 g4 P0 O7 Z, A
    67 k7 D3 \7 E5 A+ s  ^! u  q9 U! x
    76 \' T6 \5 A9 a; k
    82 }8 z, H2 v9 U; z2 p; R/ x' T
    91 K* b8 r, ~9 r" X/ x4 q
    108 G7 ?" a3 j. ?" Z9 _" Y
    115 ]! r- ], G, k4 g+ l' w2 h. c
    12
    ) J% ~% n& M: O, R: b" q1 W131 U7 |7 U+ I" J" d1 @: ]
    14* d& p2 V4 E" E
    15" E7 W; i( `* ^
    16
    . c+ {$ f1 j" |分割点分别是:
    8 C% A+ K) @8 t, V+ _0 t. o4 P' a4 s) ~4 u4 I
    array([ 1051.16 , 2295. ,  3073.29,  4031.68, 5456.34, 17828.84])
    / h9 G6 w4 }$ Z+ Marray([  -inf,   1000.,    3500.,    5500.,   18000.,    inf])
    0 V* _) W* g3 u# b6 B; y' N1' s+ e+ g0 X6 r+ h  Z' D8 G
    2
    ' z% M# R9 K0 Z7 B) j$ l! P3 n第4问中按照整数分箱得到的序列中,是否出现了所有的类别?如果存在没有出现的类别请把该类别删除。
    ( Z' d. D* n0 g7 P" \6 bdf['price_list'].cat.categories # 原先设定的类别数; A; p' s  q0 `9 I. g' o4 d" E
    Index(['Very Low', 'Low', 'Mid', 'High', 'Very High'], dtype='object'). ]  q. S5 O3 u4 a5 T( k/ @( R4 x

    9 [% S0 O! b- m" g3 c7 Pdf['price_list'].cat.remove_unused_categories().cat.categories  # 移除未出现的类别2 C, V5 a3 V/ K# e7 U
    Index(['Low', 'Mid', 'High'], dtype='object')  # 首尾两个类别未出现! s4 R+ k: Z( x1 q
    1
    " o, \) u0 D8 l& Y( g28 ?: i) q3 }7 j" n
    3
    ) ]2 r! m; o. q' r! E' q% m4
    , y$ J. y6 h+ k1 O* E. |  e52 g# ]2 t% V( K3 {, s
    avg.sort_values() # 可见首尾区间确实是没有的! d! z9 r, j2 ?& b4 g# [! b# |& _8 V# p
    31962     1051.162791
    3 u" ~1 Q2 R  ?15        1078.125000
    : S( s: Y" e7 a& f* D4         1080.645161
    + ]- Q" `6 {6 z, i3 V- O6 j/ r28285     1109.090909
      R" D8 V! m( v5 C8 R4 D13        1109.677419
    ) q+ A# O7 L7 W- m: j             ...     
    8 H/ _5 G( c; ~  z; C* \26998    16764.705882
    , x/ ^$ T" x2 S5 F27457    16928.971963+ u  o( |- J( g: `6 e# T
    27226    17077.6699033 @3 F1 i8 y, S9 q7 W4 Z
    27530    17083.177570
    ! _3 `6 F0 N9 i" q) R1 U1 i27635    17828.846154
    5 ^! d& m' Y8 f1' Z' L& ^% M. x7 L5 d6 t7 \
    2  q; B. W1 q  F* D# B
    3
    % {) ^+ f! ?. L4, ?; V/ v" z. T) ]/ U& ~" f$ X
    5
    $ ^2 h1 I2 u+ ]/ W6( q1 _) v3 q3 U, ]* E( S" q# y
    7
    ! i0 u$ h1 @. N2 _0 h" S! m4 X85 [7 f( @+ e5 d3 U* e$ _# X( h
    9
    ; C9 M0 K9 v0 N& M" {10+ e# {; |6 D6 w: S6 S
    11' M6 E- `) L, b( h: g
    12
    9 y$ f, J1 @; E4 I7 Y对第4问中按照分位数分箱得到的序列,求每个样本对应所在区间的左右端点值和长度。
    1 f2 t/ X$ K1 u9 f# 分割时区间不能有命名,否则字符串传入错误。# @" \; v4 R# _0 V) G
    id_interval=pd.IntervalIndex(1 e) o9 P6 O& Y! P
        pd.qcut(avg, q=[0,0.2,0.4,0.6,0.8,1],retbins=True)[0]3 K2 r& F. V# B; F
                                )
    ' v" F* a) Y& g( Bid_interval.left
    ! J' X' I6 ^9 ]$ @id_interval.right
    " A4 Z2 x6 Z8 q7 |) {id_interval.length                            , J6 m+ j' r" p8 B* |5 p( u$ a
    1& x* |1 H7 A$ }8 h
    2, e+ _7 a, j  m
    3
    ' u" ~3 c# M6 @8 @7 x4
    6 V# O- A, C' x; y3 \" ]# M/ ~5
    $ S: V( C) Y) [4 j/ [+ e6
    3 d( B2 Q# i7 J0 w7 u7 x7
    3 D+ f$ P% A; E7 j! u( t9 h; J第十章 时序数据2 w2 ^" B$ P2 d$ H, }9 s) A. g
    import numpy as np' ^% R. v) y$ P1 X, x% J6 B
    import pandas as pd
    " {1 W5 o- M, [1 a$ N- a( O1+ C! E+ j  ?& Z+ W8 Y9 k6 C
    2
    : F4 v9 @0 `0 D9 _7 Y8 h
    ' t/ j9 G: {4 P2 L
    1 h. Q5 \0 u5 B* O4 \10.1 时序中的基本对象( r+ n  G9 _2 R
      时间序列的概念在日常生活中十分常见,但对于一个具体的时序事件而言,可以从多个时间对象的角度来描述。例如2020年9月7日周一早上8点整需要到教室上课,这个课会在当天早上10点结束,其中包含了哪些时间概念?
    ) W) D* _; C- y3 U1 z+ @
    ! k5 w7 Q3 ^" H8 S$ Y0 x会出现时间戳(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的简写。! Q4 p; o- h% Q: @+ h
    6 `% |5 ]6 }. _1 U
    会出现时间差(Time deltas)的概念,即上课需要的时间,两个Timestamp做差就得到了时间差,pandas中利用Timedelta来表示。类似的,一系列的时间差就组成了TimedeltaIndex, 而将它放到Series中后,Series的类型就变为了timedelta64[ns]。9 n: i! s; z3 A/ a( _. j2 Z8 P
    $ w$ r$ p+ U/ o( n# Y
    会出现时间段(Time spans)的概念,即在8点到10点这个区间都会持续地在上课,在pandas利用Period来表示。类似的,一系列的时间段就组成了PeriodIndex, 而将它放到Series中后,Series的类型就变为了Period。) B- ?1 v! ?* }, ?9 Q8 }1 F
    5 [% `, O3 h" z
    会出现日期偏置(Date offsets)的概念,假设你只知道9月的第一个周一早上8点要去上课,但不知道具体的日期,那么就需要一个类型来处理此类需求。再例如,想要知道2020年9月7日后的第30个工作日是哪一天,那么时间差就解决不了你的问题,从而pandas中的DateOffset就出现了。同时,pandas中没有为一列时间偏置专门设计存储类型,理由也很简单,因为需求比较奇怪,一般来说我们只需要对一批时间特征做一个统一的特殊日期偏置。$ ]$ F2 a% v% Y$ B

    6 r; f# f6 {, w5 z% G  通过这个简单的例子,就能够容易地总结出官方文档中的这个表格:
    4 h9 r; G9 j) j
    % q8 g' L1 J, z0 s0 k7 |* Q. q! Q概念        单元素类型        数组类型        pandas数据类型( M2 L+ w0 p; m5 O- v  v
    Date times        Timestamp        DatetimeIndex        datetime64[ns]- i$ N4 q$ R1 Q8 v/ e
    Time deltas        Timedelta        TimedeltaIndex        timedelta64[ns]
    2 ?! A% A# Y9 i! s% W9 B* aTime spans        Period        PeriodIndex        period[freq]# k1 ?  r1 D1 `/ \
    Date offsets        DateOffset        None        None  Z6 e5 k. t7 b+ R! b1 ^
      由于时间段对象Period/PeriodIndex的使用频率并不高,因此将不进行讲解,而只涉及时间戳序列、时间差序列和日期偏置的相关内容。% |: N( p9 ]5 \. c* g: G

    " X  s* ?6 t4 i( V: h1 K3 S10.2 时间戳5 ^6 G' `+ V; U6 \" T
    10.2.1 Timestamp的构造与属性' n4 W+ d$ G3 ~5 u5 }  N0 Z1 T6 |5 w
    单个时间戳的生成利用pd.Timestamp实现,一般而言的常见日期格式都能被成功地转换:
    $ _9 z( j( T6 x0 C1 o, x; F
    # d! }, ~0 p9 Q4 A0 Jts = pd.Timestamp('2020/1/1')& X- n- Q! b6 j& t
      p2 Z5 K0 J- M7 v
    ts
      S$ O# M3 B: y. d3 \% DOut[4]: Timestamp('2020-01-01 00:00:00')2 G# o  D# x, o4 Y. o; ~
    4 U: @/ \; J9 u' M  @
    ts = pd.Timestamp('2020-1-1 08:10:30')! b" _4 J+ s3 ]& X% j3 k
    6 {4 s/ w% w4 O# `4 D
    ts( Z3 F( z6 u* G2 D/ Q' {
    Out[6]: Timestamp('2020-01-01 08:10:30')+ l5 H! ?9 T) j3 s3 T, p+ {0 W
    1
    3 F& P) y# @7 ~4 q2
    & p/ `* A( ~1 E1 i- J" n3
    3 R* d* T: l# u40 x# E. }2 s' I
    5% F, _& M- T8 O
    6
    ) g/ l3 F; _/ c1 G- W6 r6 T7
    $ E/ t, Z8 y5 I7 ^8
    0 D0 b8 ^$ K) B9" \4 B5 t! a' x: t: x; ^& j) a
    通过year, month, day, hour, min, second可以获取具体的数值:4 V8 V1 x) p" [3 c; _7 O: u

    ( M$ k; e: ^6 ~  l4 z0 @- rts.year
    ) s# E- ^; _4 l$ J' ?Out[7]: 2020
    3 @  B, f, L. A0 i5 c9 d
    : w. Y' p' Y6 `ts.month
    % T$ K/ D. p, g1 V* d: BOut[8]: 1& n& W0 P. E) z5 F) X3 ]6 ~

    - }- w+ D' s7 C( z7 Lts.day
    0 s& I% T6 p* gOut[9]: 1
    6 Y  Z* t$ S& z4 Z2 P9 g1 ]( T5 n% T
    ts.hour; L: D  f. x0 v3 n! o8 {3 b
    Out[10]: 8* J, ]" q8 d. s0 A9 h5 A' c9 _' j
    4 I# I5 b; I7 m6 a
    ts.minute+ }& [2 K/ ^/ q$ f; r
    Out[11]: 10. ~% ]! ~6 @0 I* W8 Y& Y, ~  U

    1 N- J5 x0 E( r* H$ Ets.second5 F; J. T. c6 a' U4 V$ A$ F
    Out[12]: 30
    3 N+ x9 j. u8 k  [2 Z4 `
    & |7 F& N' a5 L) H/ z, G. @- C5 ~1/ s. y: ~: G) v& o
    2
    / r9 z- R: u0 W6 z" J3
    ) C! Z, i. `0 U3 |1 j: d4 `4
    8 G: j0 k2 g/ L4 e% Y5
    ( h- d+ _( b9 i2 q4 E6
    ) }: K4 U& s7 i7 H7' B, u. K% ~: K, Y/ l7 z, j
    8
    4 L) ?# E9 N. |  G$ O$ }" {9; @* v  ~  \! T+ [8 T8 @6 A$ M
    10) t7 f/ C& \6 N! C3 x' {& \  G" A
    116 m, C! x( e: l" c
    12  u/ y& W; }9 ~) ]9 h
    13
    ! g7 |; N2 W, h  n% o14* Q& A% F) E% b' t
    15
    9 p/ w; W: }! @7 L  F162 L- X- R  d" |% q
    17
    7 V+ m+ l+ D8 }/ c" Z% ]( W/ y# 获取当前时间1 w& r5 U" B% w% l3 \& D6 w! w
    now=pd.Timestamp.now()
    : q  a" q- Q- o4 o# r1 j; W1* g4 y7 d* ?8 M7 O1 Y: x- ?
    29 @$ L# m1 M  U9 U$ r6 E) G7 Y# a1 L
    在pandas中,时间戳的最小精度为纳秒ns,由于使用了64位存储,可以表示的时间范围大约可以如下计算:) \3 J$ w# r. {- ^/ Z: C: m6 r
    T i m e   R a n g e = 2 64 1 0 9 × 60 × 60 × 24 × 365 ≈ 585 ( Y e a r s ) \rm Time\,Range = \frac{2^{64}}{10^9\times 60\times 60\times 24\times 365} \approx 585 (Years)% e* D2 s7 C& |! W- O- c6 I
    TimeRange= , X. Q- O% f# P. t
    10
    6 }& z3 O: Q2 d5 }' t9. V1 g8 g% n- @5 y1 i
    ×60×60×24×365
    4 w4 c8 f+ p( k- Y+ j# t+ f% K2 - A$ h' p8 _6 w/ Y( t* J$ n/ u: k+ J
    64" M8 V* ]+ z! H+ `* k9 O
    ) t. S4 r  N) @# j' i. F+ @) R
    1 \) E$ v  M* H. u# L) L' O
    ≈585(Years)6 h, L. }; ^9 K; L
    ; s- x; Z. I7 s& {. u+ A
    通过pd.Timestamp.max和pd.Timestamp.min可以获取时间戳表示的范围,可以看到确实表示的区间年数大小正如上述计算结果:: @4 j( w1 P7 H7 J! ?9 F
    8 Z+ |, q- A6 A; q( [/ L8 E: B0 S
    pd.Timestamp.max
    % L8 f/ I9 g0 lOut[13]: Timestamp('2262-04-11 23:47:16.854775807')/ T) }; c0 `) {& Z/ U: B
    : B5 n5 b" g3 y. H  e$ O
    pd.Timestamp.min7 F$ r) f, `" K
    Out[14]: Timestamp('1677-09-21 00:12:43.145225')" |" H/ G* f; y0 u

    8 K( |! @6 C' ipd.Timestamp.max.year - pd.Timestamp.min.year
    7 u) |3 |+ U+ |: W) J% [Out[15]: 585$ A2 U: u5 \+ I9 u- Q5 }6 B
    1% s) Y6 h" `2 q1 }  r# c
    2
    & X; |' K7 J+ j! e, P! @32 t1 Z7 G. a2 [, Z. t
    4! ]; X4 w% z0 j# x3 g" [" I
    59 E! S6 x1 q% B8 H0 R
    6
    - ]  x8 d0 V5 ^8 z7
    # \6 k$ W6 p; ?6 y' f8% d' g, n" k, Z/ h
    10.2.2 Datetime序列的生成
    / _6 c. w* Z! d; E2 D7 T, opandas.to_datetime(arg, errors='raise', dayfirst=False, yearfirst=False, utc=None, format=None,
    2 q" ?5 G  ~6 Y7 h                                  exact=True, unit=None, infer_datetime_format=False, origin='unix', cache=True)
    , E$ K( W; v& b0 [- _# J# v$ l19 R' i; J3 Z3 G) Q- r
    29 J8 {& W% S* M1 e
    pandas.to_datetime将arg转换为日期时间。# j6 V- {* K3 v  _

    * e, c$ I# {. J9 T, Y; Earg:可以是argint、float、str、datetime、list、tuple、一维数组、Series、DataFrame/dict-like等要转换为日期时间的对象。如果提供了 DataFrame,则该方法至少需要以下列:“年”、“月”、“日”。
    4 K  l2 [+ V5 `errors:$ v% c% D0 Z/ o' e- \2 a' |
    - ‘raise’:默认值,无效解析将引发异常6 q9 m, q( E" n/ G- O6 w' i$ J
    - ‘raise’:无效解析将返回输入
    7 X, D: m% ]$ i, Z- ‘coerce’:无效解析将被设置为NaT
    0 k2 P% Q, J  \& y! |4 ^dayfirst:如果 arg 是 str 或类似列表时使用,表示日期解析顺序,默认为 False。如果为 True,则首先解析日期,例如“10/11/12”被解析为 2012-11-10。如果无法根据给定的 dayfirst 选项解析分隔日期字符串,会显示警告。$ `8 W+ S7 X. z- [+ J
    yearfirst:如果 arg 是 str 或类似列表时使用,表示日期解析顺序,默认为 False。如果为 True,则首先解析年份,例如“10/11/12”被解析为2010-11-12。无法正确解析时会显示警告。(如果 dayfirst 和 yearfirst 都为 True,则 yearfirst 优先(与 dateutil 相同)。)9 b7 d# G- M( O' q6 [) S
    utcbool:默认None,控制时区相关的解析、本地化和转换。请参阅:pandas 有关时区转换和本地化的一般文档
    " Y6 P) J2 ?9 }) v  l1 g# [8 Iformat:str格式,默认None。时间戳的格式不满足转换时,可以强制使用format进行匹配。% l' u% D7 I% A0 S# S* U- l
    unitstr:默认“ns”。它是arg (D,s,ms,us,ns) 的表示单位,可以是整数或浮点数。这将基于原点。例如,使用 unit=‘ms’ 和 origin=‘unix’ (默认值),这将计算到 unix 开始的毫秒数。
    1 V. K4 P8 ]0 n9 \# gto_datetime能够把一列时间戳格式的对象转换成为datetime64[ns]类型的时间序列:/ ]- H5 {2 p" t$ I" n) O! h6 s
    pd.to_datetime(['2020-1-1', '2020-1-3', '2020-1-6'])
    5 A! C4 T: k4 S6 s8 x9 \' [: E3 t$ t  K' b3 K: W6 T0 f
    DatetimeIndex(['2020-01-01', '2020-01-03', '2020-01-06'], dtype='datetime64[ns]', freq=None)! |; u" `' K6 X; Z8 s$ w
    19 |% ^' K+ [) y* x1 o6 {( @: B
    2
    * X8 g- g. Z+ [; b7 G" {, b3
    6 }. u! H0 V# S- |2 l! G在极少数情况,时间戳的格式不满足转换时,可以强制使用format进行匹配:
    2 ~5 ?, S8 r$ b: @8 n* H4 O2 L1 @7 v6 f9 @% B$ J/ Z# F
    temp = pd.to_datetime(['2020\\1\\1','2020\\1\\3'],format='%Y\\%m\\%d')
    & p! ?( [: q6 mtemp
    7 }7 K6 g  H7 Z* K& j& p7 N$ \% _& G2 \' `* {. S! m5 o
    DatetimeIndex(['2020-01-01', '2020-01-03'], dtype='datetime64[ns]', freq=None)* ^1 x- {: g! i& A
    1
    . S0 F. K$ n0 R2 `2) \4 U4 Y( s# O$ Y
    30 N( q$ x9 R+ ]$ ?
    49 f+ W" W% v; F8 h2 i9 E9 D
      注意上面由于传入的是列表,而非pandas内部的Series,因此返回的是DatetimeIndex,如果想要转为datetime64[ns]的序列,需要显式用Series转化:5 C$ M5 @: ~/ M; r; J
    ) f3 b( R, v. e7 j' I( z
    pd.Series(temp).head()) Y  x: V9 q- V8 v0 x/ f- q
    + E  q; G4 E7 J2 K
    0   2020-01-01
    9 U7 }4 }& _3 e( L9 f4 @- p1   2020-01-03# S& i) m4 V9 y7 ?
    dtype: datetime64[ns]
    $ m7 @/ J9 F$ q0 o$ \1 ~5 i; E1% A4 A7 _0 r6 k: d7 Q
    2
    ( {1 e8 i& X# @( S$ F/ \5 y" w3+ [8 X! I% S2 k  k6 h
    4
    * O1 O! j+ Z0 W7 _9 C* y57 [! G& a3 n7 G- L3 |) A; W
    下面的序列本身就是Series,所以不需要再转化。6 R0 v) \3 F# k9 x3 i7 E
    & a. S* H/ r( ?1 u9 b' K. m* t
    df = pd.read_csv('../data/learn_pandas.csv')
    # S( k, R4 \$ p. p& P# ys = pd.to_datetime(df.Test_Date)) J& @9 }" C- V+ A$ V
    s.head()
    9 N/ C! P& K9 W+ s5 |
    , j, E: T/ v% y0   2019-10-051 T4 S9 H) }" |4 C1 G: T& Z" q
    1   2019-09-04
    - q1 y- G$ M: ?. {7 d2 E3 s2   2019-09-123 s+ k: T0 u# r4 _( _$ B
    3   2020-01-03
    + b7 m- n' D3 V2 X4   2019-11-06
    % o2 c: p- _" x: ]/ VName: Test_Date, dtype: datetime64[ns]+ \0 b! w& }6 E. k/ t
    15 }$ l4 ]& y( i  R
    2
    8 F$ a! d6 s- ?/ z3, u, z7 L. g- \% x' p8 A5 I0 k2 x$ `, i4 R
    49 q  Q: I' d2 V8 n
    5
      a# S, ]  n, r  l; Y6
    ' B% W0 [$ E7 V2 V. Z+ g9 U6 h7. d. }! }8 I0 p: N% @
    85 V3 G; ~( {* H% T
    9# r) a4 b" J6 _# {( m, \5 @
    10: N; l2 s9 @8 O7 g3 @4 m0 M% @0 n; V: L
    把表的多列时间属性拼接转为时间序列的to_datetime,此时的列名必须和以下给定的时间关键词列名一致:- `( h  K5 A* O+ x6 j* n6 N
    df_date_cols = pd.DataFrame({'year': [2020, 2020],) n/ t! O7 N$ A' N; ]
                                 'month': [1, 1],
    # \$ R0 C5 t7 F+ Y* d! o9 x4 P                             'day': [1, 2],$ @6 {, R9 L4 I/ D
                                 'hour': [10, 20],3 @# F/ E5 r. i+ }/ e$ u3 J
                                 'minute': [30, 50],  A9 U" h3 e8 Z+ [4 u+ X6 n* |
                                 'second': [20, 40]})
    % {+ H- R# j& L/ l3 x+ w0 lpd.to_datetime(df_date_cols)
    5 J1 e7 `6 Y( v  G2 u# K
    ( Z1 `: M& B* h9 y/ E" I. m0   2020-01-01 10:30:20
    - ?* i( C8 M3 i/ _& y/ E0 J1   2020-01-02 20:50:40
    - l  l" z) K* c0 L7 z2 K9 v0 adtype: datetime64[ns]
    ) h' N3 U3 l# O7 o0 N0 d% r' n1
    5 }8 l& g' m) h- P2
    4 e3 ~# d( N) }' ~! M( E0 r3# `. R. |1 r% E3 f, [$ X1 a6 e9 O* m
    48 H" j+ C: S8 h! v- I2 p6 B1 O
    5
    * ]7 b+ k. Y0 j& ^& {6" h8 d/ q& t5 i) S% g4 H9 o- N1 _4 a
    7
    / ^* m' t) N0 k. j+ ^) R8
    - j" c  }' j9 A0 }( }9. A% C( `3 }- z
    10
    : q$ E( P! }# m5 c( Y; b/ l$ r11
    3 A4 ^8 U" D( {! d/ L" Z7 Odate_range是一种生成连续间隔时间的一种方法,其重要的参数为start, end, freq, periods,它们分别表示开始时间,结束时间,时间间隔,时间戳个数。其中,四个中的三个参数决定了,那么剩下的一个就随之确定了。这里要注意,开始或结束日期如果作为端点则它会被包含:3 @. r+ w4 b9 r( l4 J* z
    pd.date_range('2020-1-1','2020-1-21', freq='10D') # 包含
    . O! Y9 ?5 O/ F- l  FOut[25]: DatetimeIndex(['2020-01-01', '2020-01-11', '2020-01-21'], dtype='datetime64[ns]', freq='10D')$ T" J  c+ x; y" P( E
    + o; c& d% z% @6 \9 }; U
    pd.date_range('2020-1-1','2020-2-28', freq='10D')
    ; `0 G0 ]6 k: \1 r. y9 ]Out[26]:   D) O5 i4 m" \# ?
    DatetimeIndex(['2020-01-01', '2020-01-11', '2020-01-21', '2020-01-31',, ~$ ~" k4 R) i8 t9 i5 ^, J
                   '2020-02-10', '2020-02-20'],: g2 K. b& ]3 n
                  dtype='datetime64[ns]', freq='10D')
    9 P* O- @7 f( f( @
    % u% p  z7 z/ X' a) i# @; g3 Y0 Zpd.date_range('2020-1-1',1 j/ ~# S9 P, Q& H9 H! p8 ~5 D
                  '2020-2-28', periods=6) # 由于结束日期无法取到,freq不为10天
    * n5 J* k' E& a1 l) L. w" _1 ]) R1 n  D3 r" G  s  r$ {
    Out[27]: . J/ j& F$ I/ e
    DatetimeIndex(['2020-01-01 00:00:00', '2020-01-12 14:24:00',
    " ~  }5 x, W- `: n) ?7 s               '2020-01-24 04:48:00', '2020-02-04 19:12:00',
    . A: g! a* b6 |5 w               '2020-02-16 09:36:00', '2020-02-28 00:00:00'],
    3 v, `6 G# W* ~# y. U9 s7 F# }& l) ^2 y0 q              dtype='datetime64[ns]', freq=None)
    + O% `, e7 z/ n& I- \5 z' W  n
    ! r, w- X+ b5 E5 x" r# \" O3 F$ p1
    : [7 F% q- N) V  F, `2 |" K! F  f2
    8 P( E7 h0 H. u1 L- n7 u3' }# R1 I. w- n, P$ F
    4. }; z/ O5 B2 z$ @1 J
    5
    . ~7 x) `  L% r2 f61 G: X# I' j1 S0 v9 U$ _8 u
    7
      Q% d9 q3 S$ p* n  |; O: d8
    0 e% c9 Y8 p# S) a9& s. N6 q5 Y* A) F, Q
    10( U7 X9 o; }/ o# c8 P  t
    111 q7 {( Y( ?6 {' y9 T2 ?/ V  ^
    12+ y5 J' c8 q6 {# F
    13  z+ S( D% X% s# x' A0 ^
    14
    / b$ U9 m0 b' n5 t1 }15
    " }& W% X+ [& i+ j16
    9 _9 e/ p. ~! P( k# y+ o% w. _17. h& m  @# s, ~- D! A
    这里的freq参数与DateOffset对象紧密相关,将在第四节介绍其具体的用法。& T! T5 x* z" o
    - q$ C5 |( \% W1 l3 D5 ^; {2 }
    【练一练】3 g) F% w; K0 b( p& }, y; e
    Timestamp上定义了一个value属性,其返回的整数值代表了从1970年1月1日零点到给定时间戳相差的纳秒数,请利用这个属性构造一个随机生成给定日期区间内日期序列的函数。7 A8 L$ ^2 e4 l3 b

    4 G/ J+ {2 q+ }/ ?7 [6 M: }ls=['2020-01-01','2020-02-20']  E! x* L* j. b( p3 u
    def dates(ls,n):/ L1 |/ Y# j7 ]# L, g! f
        min=pd.Timestamp(ls[0]).value/10**9* S) X0 F- P( O# b: d8 h+ Y
        max=pd.Timestamp(ls[1]).value/10**9
    $ Q" X7 ~. O+ s! b" v2 x    times=np.random.randint(min,max+1,n)
    $ T# i: b# c/ C- m    return  pd.to_datetime(times,unit='s')2 l& |' I" D8 g
    dates(ls,10) : J% `3 ^0 y/ j. Z8 Z/ f5 k0 |) T
      R! a8 X- a1 h4 t6 T* k  L# t9 |
    DatetimeIndex(['2020-02-16 09:25:30', '2020-01-29 07:00:04',
    5 f# M8 s' c' h( x5 k               '2020-01-21 12:26:02', '2020-02-08 20:34:08',& Y6 V+ Z% M; X
                   '2020-02-15 00:18:33', '2020-02-11 02:18:07',
    9 e3 @; L4 o* I) m2 o# j5 l               '2020-01-12 21:48:59', '2020-01-12 00:39:24',- B7 E3 _4 N: @% c9 R7 I: r
                   '2020-02-14 20:55:20', '2020-01-26 15:44:13'],
    ( d8 E0 ]8 S1 O              dtype='datetime64[ns]', freq=None)5 o, i7 |/ [0 X! o
    1
    & d2 N% ]& \5 o- ^. a' U- z22 d3 l* }: w) O6 p6 ]
    31 P# S2 k6 H) n4 i  U7 S3 V
    4
      j3 l0 B4 t5 A% O  y& |6 P5
    8 I" F' z; M* }3 Y- B. F6
    % {, W3 {/ Y2 e$ D) d! C79 v" p+ p; I1 F( N- n3 `% ]
    8
      H5 {# r& ?1 r3 p9. |: v1 J7 y( O9 x
    101 [7 E, ~1 A% ~, S* W2 J/ C
    11
    9 q. y/ I  `3 M+ I5 H12
    0 _- f" p  v' }8 q7 |5 W: {13
    : K/ ~1 w3 T3 G" s5 H6 ~147 L6 y# A/ x( z& ?; U; }2 P
    asfreq:改变序列采样频率的方法,能够根据给定的freq对序列进行类似于reindex的操作:
    * R' ~( x$ [; j6 k2 Gs = pd.Series(np.random.rand(5),
    ; c! q7 W' e1 d            index=pd.to_datetime([
    + e& @+ T/ E+ G: t) w; O                '2020-1-%d'%i for i in range(1,10,2)]))7 O9 k9 Q3 a) j9 o+ I
    + X9 y, b( C9 s  n4 G

    ) B( L3 S* J( _+ x9 ~( Vs.head()5 ?9 K2 c" I. R8 \0 d, d+ G
    Out[29]:
    1 a6 H5 x0 ~: o! s' i& w2020-01-01    0.836578
    $ R7 \4 J& m' {6 K( U2020-01-03    0.6784196 L, R  Q8 p, j1 V
    2020-01-05    0.711897' `: }* l( t, r: Y% e6 w& `# h: |* a
    2020-01-07    0.4874293 B  ]" r7 v. L" ?
    2020-01-09    0.604705
    $ k, E1 t9 y" ?& o+ cdtype: float64* T& ~4 b. Y( T' r6 Z

    : |1 m% Q) C. w9 `% hs.asfreq('D').head()2 L; V& v" @# o3 R, z
    Out[30]: : S. z- o% ?. D1 S  v
    2020-01-01    0.836578
    9 m: N2 F( I* `) v# |" B2020-01-02         NaN+ u6 ?( R* [2 Z: h8 B
    2020-01-03    0.678419
    / u& \/ C1 ?- _2020-01-04         NaN
    : ]& S9 s0 e4 r  _/ q* Q$ x2020-01-05    0.711897
    6 S1 |0 o: m/ q1 [, JFreq: D, dtype: float64
      G7 o7 \0 N; n& S4 F, q4 r/ ?1 z3 J. P2 T! V2 P3 D8 ?9 R2 v+ @
    s.asfreq('12H').head()
    " `2 j6 W- v7 u8 f3 YOut[31]: ; d- U4 I! g5 h' ^
    2020-01-01 00:00:00    0.836578
    8 ?& N+ B  m, z  T. Y% u2020-01-01 12:00:00         NaN7 z+ ~, o( I5 Z; W6 X; c" ?2 U$ {9 f
    2020-01-02 00:00:00         NaN* f* s3 c; Z$ n$ Y% J6 Y
    2020-01-02 12:00:00         NaN( L& w. _9 r* L8 m- }* ]
    2020-01-03 00:00:00    0.6784197 n, m1 R! b5 |) L' S
    Freq: 12H, dtype: float64
    ( m& V) r1 \- ~# d
    / u7 j  b. g3 e; O0 ^1
    5 {3 ^; R( t" k( w& c2
    * _6 N" I8 i4 y3
    % e5 `7 U2 m$ _0 B- ]1 E4! r; n# J+ }( M) `1 N6 c: I/ Q6 g
    5
    + P7 d# Z( i" o% F$ r# T6" u" O- o( ~. {# P
    7
    / f7 {  k9 c& F# _8
    - P4 _; }! {. \, z& l4 T90 C6 W4 k. K4 s- ]2 M
    10# i, l  }, |+ C. J& e% E8 ~
    11
    + s5 K/ l1 {8 ^( h12, s  v  q7 ?, e" k
    13
    ( j& w9 e0 l: d- I14- v. B5 _# |2 F3 |/ J4 F4 a
    15
    * v; q0 e0 m/ D1 |/ J4 R) T/ |+ `1 ?16* p0 ~  l( h) _2 d
    17
    0 a8 s& z# ^: `' k18
    ) I* k% x2 a8 E: x! G3 w: P19
    / `7 y# [4 f( u3 h# D20
    $ n1 l% _8 U  h/ x$ c; m, p. j214 I8 z$ f( S  \; w9 J
    22
    * @" n* ?7 E- N5 Z. _  K5 c239 T5 H1 ], O$ _- Y
    24+ {1 W! F* c3 O% r& p
    253 |  Y8 z# A, D: o
    26
    ( T( X! @; O; P8 d- S7 y! p273 M% b0 {$ U" i
    28$ w% {0 y+ c2 C. G. U
    29* d* }. g9 I) h4 d2 x; ~
    30
    ( ~1 e4 |" A3 M5 j+ N31# u/ f  d' m* W
    【NOTE】datetime64[ns] 序列的极值与均值
    1 p: P& c0 P. @& t  前面提到了datetime64[ns]本质上可以理解为一个整数,即从1970年1月1日零点到给定时间戳相差的纳秒数。所以对于一个datetime64[ns]序列,可以使用max, min, mean,来取得最大时间戳、最小时间戳和“平均”时间戳。9 b+ P+ b* T& t0 T0 n

    + `0 R* u) X6 M7 H10.2.3 dt对象$ ?7 w! J" t3 g8 E- |
      如同category, string的序列上定义了cat, str来完成分类数据和文本数据的操作,在时序类型的序列上定义了dt对象来完成许多时间序列的相关操作。这里对于datetime64[ns]类型而言,可以大致分为三类操作:取出时间相关的属性、判断时间戳是否满足条件、取整操作。
    % ^3 X+ i' B7 g0 L+ K! j: E9 i( T  J
    % ]7 A' U- S0 h+ p- y, h( i第一类操作的常用属性包括:date, time, year, month, day, hour, minute, second, microsecond, nanosecond, dayofweek, dayofyear, weekofyear, daysinmonth, quarter,其中daysinmonth, quarter分别表示该月一共有几天和季度。
    : B3 x1 }7 R+ C& x: M9 fs = pd.Series(pd.date_range('2020-1-1','2020-1-3', freq='D'))8 A, y5 m7 G: b& ]  j% g/ t
    1 [* h" V2 e. X3 F! M
    s.dt.date
    : y% @* h  `9 v$ |9 Q, r! LOut[33]: ! y; u5 \) v, F1 |
    0    2020-01-01
    : O) l" x8 ?% O! k0 R) H# D1    2020-01-02
    0 E9 F/ r8 R: W6 n! L2    2020-01-03" Y& {) E9 C; h; o6 K; Y
    dtype: object( q8 i  u- w; \) j2 y4 [
    7 R4 Y* _  _- c& O7 Q
    s.dt.time
    - F6 E' [0 A+ h; H9 G8 @Out[34]: / o+ f( c+ t6 h0 ]/ x- y
    0    00:00:00
    ' l0 }4 t3 Q+ s) X1    00:00:00
    ( C: W/ V; n! k0 b  j2    00:00:00
    6 F, l, G- d$ E, z. Jdtype: object
    # u# F0 A! K/ B1 r+ F6 e6 b! K  p4 J
    s.dt.day
    . x$ s  G& L; l0 ~3 r+ HOut[35]: 5 o) X  O- ^5 `, F+ q
    0    1% K! e5 b) b, S
    1    2# @; A* K* J* a
    2    3, ^; o6 L3 `; C. D
    dtype: int649 P4 f1 n- _0 _* I! @/ F
    , v! |3 y: m0 r. h
    s.dt.daysinmonth6 e! K! O" s( d9 l! n: N& |
    Out[36]: ' X, `2 `5 Y3 n8 M- ?9 S1 d, [
    0    31
    , h9 W1 ~9 n* u* B# x  e1    31/ s* o) N, \9 q& L7 E  U
    2    31
      n4 c( p# S' p9 Tdtype: int64
    % o8 t4 g/ d5 P, ^3 a; y3 f2 ]0 H5 g! C, I3 S+ P% X3 t8 @
    15 f7 z/ l2 z3 G  P- U
    2
    % v1 L* j4 T( k& e3 r2 L  Q3
    - _+ V' L. k0 Z% t1 `4, ~+ n$ B4 p* \& [% b7 i
    5
    5 u3 p6 Y0 ?$ d+ y* a6
    ) K+ o) s2 `$ M: _* Y! M& _73 z( o/ ?/ D0 x' h" P( C
    8
    : a  T2 P, f; N, I6 \6 [. x9
    - A& r1 R( \  x1 W10
    3 B' e) H/ x& c) R; d% {+ F7 ]11
    & K/ E4 {9 V; D9 w3 ]# b12
    4 ?6 I/ e2 V; N% u. Y13
    & {) Q  `4 _2 ^0 G14+ B: E( D. w5 ^/ @, E; I# z
    15
    6 z/ L$ I% t& V, M. `. z16* k2 M6 m1 K' g( ^2 g3 D1 f. b
    17) t( f$ R+ k* h7 r# s" B$ X& a5 I
    181 n8 r$ I5 s( H# f  y
    19# N; F' C) E$ Z! n( {- @
    206 i. j; n. T: B- \& G
    21
      t  x8 K+ O" g9 t22" Z. H+ Y/ s- ~
    23- c8 U7 W# Y' B' F$ X$ h
    24, B) s( S4 c9 n$ q) r
    25
    , j+ z5 H% |4 J8 M. s0 M% v26: J& D( U. r1 H$ @0 C  `: n
    276 m$ r( Y  E6 L$ B) |
    28
    + d' z+ `# i# }. M/ i2 i: w# }29
    1 Q# z, |  e* L8 F9 {( B0 i6 S; V$ {3 N  在这些属性中,经常使用的是dayofweek,它返回了周中的星期情况,周一为0、周二为1,以此类推。此外,还可以通过month_name, day_name返回英文的月名和星期名,注意它们是方法而不是属性:
    8 W4 L5 ^& [* x# f7 [; m$ ~% g; l# u+ A) S$ V1 Y* N, G& U% Z
    s.dt.dayofweek- W, V4 M2 W% I$ ]8 f
    Out[37]: + }' V7 H0 M% C+ D9 G9 g
    0    2- @/ j% m# R, {( G8 ]( ^, e
    1    3! Q, s/ C' ]5 X
    2    4
    5 q4 `3 ]: u) S4 jdtype: int647 T( F8 L( c1 b; P
    3 W. B  m+ ^: N# n( u% H
    s.dt.month_name()
    0 S# S9 e8 Y! E8 I9 O( u, UOut[38]:
    - ^5 S2 x1 K* E8 _0    January
    1 e5 s1 e- I7 U3 X' ]. z% l7 B/ l) b1    January
    . {5 q5 M0 J" y+ ^: U2    January2 K0 j* D% ]& a( |0 Z# s
    dtype: object
    1 X% T+ \  Z- u! R  g  q$ ^1 Y
    " Q8 c0 N$ Y) Z( g! |& N+ ?4 ]s.dt.day_name()
    ! ?# W) U" M  f0 q; a4 U, r' [: xOut[39]:
    5 f2 e1 t/ e; Z0    Wednesday
    0 V1 B. e, x' h9 o" ?1     Thursday  e. u$ _' a0 V! A
    2       Friday
    5 h- V, Y* n0 c' b5 `: @dtype: object
    # L+ T5 ~& g  D# G
    $ x' w# U5 l$ @5 }+ W3 H$ _* M1" N2 h3 V/ @# E9 y
    2
    ( {/ n' d# f: D+ H3, _+ K1 \' h5 q% f% S0 r
    4
    ( |7 L% W5 a/ M/ \6 C2 o5/ ^6 Q  b0 r) U$ {
    6
    " N4 b) V. g! f5 `) \7: d7 j; a% k& |" e
    8
    ! h$ g& `# a, O, c90 b- C. d  }4 L7 J8 P" k, _
    10" I9 c" T  ?; E  C- k& `+ z
    11
    / R/ q; I& ?7 A* ]; ?# I12- u, r- X& c. h- e8 C1 P* X
    13
    5 T" v: _# @8 I1 I1 F9 c14
    & _1 \7 r( Y* A% l$ G! G$ k15( l3 }0 V8 Y/ X* W4 j2 [$ L3 ]
    16
    9 R$ g' j# R; c4 L17
    : q1 t% N- q9 w. S. ^) C: e18
    . i1 M, W( y% \. \  M. V" h9 \  {19
    0 h0 X( D3 Y, v! |- e20, H* Q/ u* o* ~0 b7 v. v& d
    第二类判断操作主要用于测试是否为月/季/年的第一天或者最后一天:
    7 e% F% k) l$ ^5 hs.dt.is_year_start # 还可选 is_quarter/month_start
    0 c, {" n; I) A) \' w1 I, C# g5 wOut[40]:
    / i$ x# Q8 ]& ~# Q0 a0     True
    ' D7 T' @; \, Y  D& i1    False
    / f* ^+ @! O8 Q8 Q& L1 z1 P2    False3 A5 b9 x5 P; ~  ?# m( C7 k
    dtype: bool
    2 x; A; l8 d( D7 ~9 b4 b- i" C8 M* \1 J0 A1 u. n
    s.dt.is_year_end # 还可选 is_quarter/month_end
    8 O7 }2 T0 h8 s% ~8 W! h( _7 fOut[41]:
    ' }& d) X; Z! ?/ i5 K) ?8 Q8 A0    False
    ) I1 J( C9 T( {) m9 q1    False7 U' q  j" P+ ?
    2    False' N( T0 V8 K% e9 e
    dtype: bool  p! P! h5 o( _
    1
    ' o. K. i6 i; V2
    ( c( z$ x2 h. J/ T2 H# F' U3
    $ @# S& y1 z) I4% u" f9 U( I- K1 R; O
    5
    # V; ~7 h' q# {! n6: I; B; u4 z* ~: g
    7- S( i  B8 V6 t- V0 F7 I
    8
    0 R6 A' F, E! H% e3 J9) I" F/ q0 A! y, l; J! E0 Q6 F5 F# _
    10/ D" Y# p" t7 O0 Z
    11! T. g' U# E0 p" B1 F4 F3 V2 r( l) C
    12
    / n( c/ b. c, V9 h+ Q5 R5 g13
    * m' |5 f$ ]# z# }! f/ @% U第三类的取整操作包含round, ceil, floor,它们的公共参数为freq,常用的包括H, min, S(小时、分钟、秒),所有可选的freq可参考此处。' U; ~' t: Y/ f/ X& m
    s = pd.Series(pd.date_range('2020-1-1 20:35:00',' y0 t# F  X5 J
                                '2020-1-1 22:35:00',# j: Q0 t- |! e9 ?6 r: o: M
                                freq='45min'))
    4 x" O( U8 S1 l" n
    # i+ [& `6 a" g8 |+ K: x# g3 s, R" M  C  f; e7 Y. |
    s
    6 d7 ]; n. i6 v9 z+ a; LOut[43]:
    , [# B8 K' Y- t; @! I! k" |8 O8 p7 V0   2020-01-01 20:35:00* I2 a0 Q5 U+ m; r
    1   2020-01-01 21:20:00; f+ h' }$ C5 j3 }% x! f6 e, h
    2   2020-01-01 22:05:00
    2 K7 ]$ g) g4 U7 C7 `+ E. Hdtype: datetime64[ns]
    / e1 x% l5 S3 n+ m# C/ n- l, J1 ?! T
    s.dt.round('1H')
    ! S: x' e; m/ A  T$ N/ aOut[44]:
    9 J7 Q& G; w+ }7 u- P- L0   2020-01-01 21:00:00
    ) o8 `$ m6 p6 A+ d5 z/ S1   2020-01-01 21:00:00
    + _% q9 H  |, S1 x2   2020-01-01 22:00:00
    / [* p% [* x: q: D7 idtype: datetime64[ns]
    , t1 y- I7 ^5 D" T" b7 ~0 @! [7 \& [) p) F" R# q
    s.dt.ceil('1H'). B/ w! Q, d9 c  l- j
    Out[45]:
    . n( D. S* M& P0 ^+ e  E2 {' `0   2020-01-01 21:00:00! }$ `- B* m6 n% _% C8 h* i
    1   2020-01-01 22:00:007 a" u3 d, D* B/ j
    2   2020-01-01 23:00:007 k- r7 Q& U4 j
    dtype: datetime64[ns]
    . }4 v+ X' x/ T* M7 Z
    - F2 e7 i2 C7 T) n1 Bs.dt.floor('1H')
    ( X- j9 T+ c# u4 Y# VOut[46]:
    ( h& ?" H3 a( y9 y" G0   2020-01-01 20:00:009 L" L, X# H2 U+ j
    1   2020-01-01 21:00:008 w! H# t4 `8 A- L: j0 V& b
    2   2020-01-01 22:00:00
    % ], V7 V* h# D0 Vdtype: datetime64[ns]$ n7 R+ d, h5 H/ d6 g
    6 X, ?: K5 V/ z; M& H$ Y3 `
    1
    " {4 V6 G4 |  g2# o3 Q4 i0 K$ Z' p: R' @
    3( H; @7 [+ L7 v: x8 }; [  t
    45 z  S; X( W  g
    5' C* ?% j: x9 ?, n) A6 d/ Y% r
    6
    " o+ E4 M$ {$ {* S: \79 J$ J! i5 T, `; {2 N* U
    88 J: n! l% V% j" |) g
    9, z6 {+ {. n7 y% B
    102 X/ x- e0 n8 z6 k0 m  t0 {
    11
    " J2 f9 J/ \" V+ d12
    2 `1 R3 U% Z* }) ]3 H! D7 `13- {& \7 q: J0 x7 ]% J2 }
    143 Q1 R8 W1 Q* ~  x, h3 _
    15
    . O5 c$ q9 j$ n9 j16
    $ a4 {; r; H- I2 p, y: Q. t17( ?, b. G- l4 O# L
    18
    ( R! F) x2 O! Y) I% q! z/ r* M19  u" Q; \0 Y9 X1 D# X; a$ i
    20
    0 d& W5 v4 r. ~) U$ ~) x* z21
    ! y' I, C9 S8 l5 m; [225 s  [, D8 ~' ?  @& {. {! b" l
    23
    ' A; B1 q6 ^0 M/ D- @! j, F24; r* b" H* R$ D: t( N: V
    25
    / l5 i4 `% F1 \! Q' z26/ {3 h0 a, O; S( U$ i- @
    27/ {8 \, x: }( C" K
    28
    ; o6 @$ t* K5 C& O$ P5 ^/ t3 Q* x292 G8 ]1 g' {2 f4 d9 n4 ~
    30
    & O- G" [9 Y, K# w' H31
    5 i# y8 q( Z( E1 f8 N. Z327 r1 _- d/ [( e7 d
    10.2.4 时间戳的切片与索引
    ! t$ ~8 q/ S0 I. m4 Y6 T1 n% N& A  一般而言,时间戳序列作为索引使用。如果想要选出某个子时间戳序列,有两种方法:
    , D7 t/ J6 O0 J9 I) p( ]  k6 b
    9 l0 |, ^& R  ^利用dt对象和布尔条件联合使用
    8 N1 \( z4 U/ F2 G5 C利用切片,后者常用于连续时间戳。& ?4 W' B" x! q- ~1 m9 M. S9 L4 M
    s = pd.Series(np.random.randint(2,size=366), index=pd.date_range('2020-01-01','2020-12-31'))
    " S1 ?1 X& V. oidx = pd.Series(s.index).dt
    ; g* R+ {5 P; w4 J" F6 g+ Q& T$ `  ss.head()2 j4 }1 c: N1 x

    6 G+ u  Y' s; @& [2020-01-01    0! B, R) {7 H# ~( P$ ?& |" h5 `; k
    2020-01-02    1, h0 a5 r- W  u0 V+ R% |/ ]
    2020-01-03    1
    + g- R" S4 o+ e) c0 {$ Y1 E4 {2020-01-04    0
    3 N# T5 s& j- ?5 ]6 k. x9 b2020-01-05    08 Q& v. T/ K4 }9 U7 N$ l
    Freq: D, dtype: int32
    6 J! a- @7 G3 o% m% s( q12 M4 Y. d+ Q% \
    28 i0 }1 _( T' W" O( \
    3
    ) U* O  U' M! Q49 r5 Y9 Y1 `0 ]0 E, a) b( v' l
    5
    # c1 B4 K, V0 K0 i  F) d6
    $ h/ q* k& `6 h# p7
    * W' g( _7 D4 L' i+ B88 x0 b, s$ ^6 C, n$ M
    96 V, W& E2 T! M9 b+ T2 b2 i
    10
    ' @4 c. i' ^0 P( j, YExample1:每月的第一天或者最后一天. y9 A4 e1 y- K! e8 z) F( l8 D
    " @4 f: P; Y# _% }6 A! x# ]4 V! G
    s[(idx.is_month_start|idx.is_month_end).values].head() # 必须要写.values, Y) r" {- ]3 z4 n* V; u
    Out[50]:
    7 }* d7 @; w5 {0 D2020-01-01    1) |+ R5 h$ s- S0 Y
    2020-01-31    0
    # D$ ?! j* ]+ V* X2 w. k  s2020-02-01    19 `  O/ W% R" Q
    2020-02-29    1
    1 @; e3 W. _: x6 D1 F# w& u9 c2020-03-01    03 K( v3 j7 ^6 \) V& J- Y9 F9 i& K  P  n
    dtype: int32
    - U( }, M! O5 f( S# M1
    * |9 t" p- p, L" E5 a9 u2% l" ~3 K! e; b+ g
    3
    6 p1 {  Q) I" v0 d/ Z3 m40 D- a2 s; U% e% J' r5 h3 I
    5+ P$ v7 M4 \* }% g
    6" i: U; ^& U7 P  N: `4 }$ u
    7
    6 J6 \4 `' w# q- B; L87 _! R( |) L  f" {. w8 j6 k
    Example2:双休日
    : ^) q" y& I* ^2 K% O& m% S3 G1 t1 a# H, ?
    s[idx.dayofweek.isin([5,6]).values].head()
    & R) B# B8 `  C" N) q& t" ROut[51]:
    9 P  @. g1 ]$ @8 {  r2020-01-04    1
    & ~: R  T0 \' v2020-01-05    0& m  C6 o8 ^; o/ I
    2020-01-11    0' G2 E. S" t. L( _, {7 |; h/ p
    2020-01-12    18 B* @9 q: w% p
    2020-01-18    16 I0 U4 W( B; X' m! P, P
    dtype: int32
    $ V" X9 V7 _) D0 k( J. x12 _( s+ b6 I/ R
    2! p' T/ {! Z6 V# {6 x9 B
    3
    , v* z# U  k# |4
    6 D  ?$ F' @! V7 K5
    - Z' c; J9 R* V. T9 |$ i6
    % M9 Z, @* S7 ^/ `) D7
    ) K% |/ }/ q2 V. e8/ N( V, ~5 g7 ]& w' u4 N
    Example3:取出单日值
    - Z, p, S1 [6 u$ M0 m/ u
    ; F' a% |! v; @+ p5 Js['2020-01-01']- o. s! \8 T0 A' X
    Out[52]: 1
    7 y- @6 G$ u+ ^' W' J
    4 W9 }& A% E! u- V  i( xs['20200101'] # 自动转换标准格式; {' S+ \/ J, |! N1 U$ @/ K$ S* |5 q
    Out[53]: 1
    + |" T1 x6 n0 g1$ i6 E5 `/ H' y$ X6 n* m
    2
    ) P( O% [: v6 o$ N* k3 x) V34 ]: R- e1 K- S7 Y
    4
    . }; `$ C2 m" w# a4 h7 A; S& @' z5
    . H& P7 c7 j6 J8 e- lExample4:取出七月) l/ t7 l# k8 K2 U( s: J

    * E/ [& K% x( fs['2020-07'].head()# K# I# X& H7 H' N
    Out[54]: + p' q. k! i0 [' _
    2020-07-01    0
    % L! O, G) y$ l1 R7 r7 f, \2020-07-02    1
    & ^- `6 b7 P. S2020-07-03    04 k9 F5 V% V- J* P( b0 C
    2020-07-04    0! h( ~- N# b* U5 d3 L
    2020-07-05    0
    4 l8 \1 k3 E8 O. j5 j, i" YFreq: D, dtype: int32
    . ?3 F% A; x# H4 Q1* b. g  L2 G  o- Z3 Q- F9 s  ~
    2' o6 @0 }9 T6 A. Q9 b/ F4 y% o
    3& x# a0 k( p' P4 f
    4) ]  i8 i; i' ]' S: |
    5
    8 ]0 ?; b" b5 Z+ A9 q6
    ( o# N$ N! u8 Z4 ^1 K7
    - `4 v. W7 R$ e& _, E- F83 c' C% D# D1 g7 g4 O! ~1 I
    Example5:取出5月初至7月15日
    0 E% z- H- t' ], V: X
    8 d  A! @; @, M" os['2020-05':'2020-7-15'].head()
    $ B$ u& @) C8 ^Out[55]: 4 T+ Q* ~& k7 }7 I" d( m/ n6 N
    2020-05-01    0  `9 \( T, f) t
    2020-05-02    1
    6 X3 I: P' k! X0 ]4 }6 w) x2020-05-03    0
    6 p4 U. i# b1 w- M6 n1 K, O1 I/ P7 n2020-05-04    1
      j0 M1 n0 m( W* V7 P9 e2020-05-05    1+ ^# i3 |3 c% l8 Z% M1 l# X
    Freq: D, dtype: int328 e8 {- {* m; u8 j! Y% p
    % N; G" ~8 Y0 R5 ^' Y2 i
    s['2020-05':'2020-7-15'].tail()
    ( S3 K# G) [/ x- u* A8 R/ l- [& @/ OOut[56]: . z, [( y  i" T9 w
    2020-07-11    0
    ! w2 t% |8 {( I: O( g! {% a$ p2020-07-12    0
    / b8 {. e8 ^6 o# x$ W2020-07-13    16 \* r4 ?1 E! ^! L+ u( N2 P( J
    2020-07-14    05 u  O* [# _# b* H$ n: B
    2020-07-15    12 S! i, \. z$ z  _) m. L* }9 V
    Freq: D, dtype: int32
    5 _8 d# U! D* i( U" g' e( p' q5 {7 b9 ~/ s. @( Q1 ]5 r
    1* h) }7 _- U* I9 C& }  k% Q  G
    2
    " i4 P8 B* O3 Q$ U0 N' K- O0 H3 L3
    ) e. g. F) W8 C* W46 W) }% @/ P3 e* P
    56 ~* k. h  x- s* K
    6
    7 N$ b% D) G/ D! @; i) Z; J( \7 D7
    ! ~: v7 C6 d- z8 f4 _; }( f/ h8& L* l2 M7 {2 E9 \- _: r- p9 f
    9
    9 K% a# \  t$ d. d! X6 X. y% s10# r6 G- `. l6 g: m5 j) e
    11
    + B  Q1 }9 B, ]6 A# `1 A12
    8 w  K* L6 P+ K6 L+ M; y13
    4 n. Y% T& X- V4 v0 ]7 m( u14
    + l" s* z3 Z2 {  ]7 B' v15* k3 e6 ?$ v/ f% p
    16
    " k4 l/ z4 {: Q/ i9 x17
    $ i8 ]; `8 _$ R2 g" L  H+ c10.3 时间差8 G7 w3 _: k0 z, {
    10.3.1 Timedelta的生成$ C4 N5 D# r8 V! E7 `# c
    pandas.Timedelta(value=<object object>, unit=None, **kwargs)
    8 ]8 `. ~% C1 ?1 j  unit:字符串格式,默认 ‘ns’。如果输入是整数,则表示输入的单位。2 _/ R* \( h, h# T% H3 \/ {6 n
      可能的值有:8 f( @7 X6 V& ]* _' e4 l7 [
    ; i+ E  Y) D! O. e4 o5 B
    ‘W’, ‘D’, ‘T’, ‘S’, ‘L’, ‘U’, or ‘N’& J9 ^2 x* {: |' H
    ‘days’ or ‘day’
    9 B! b: [% P$ N- [+ {) z, w‘hours’, ‘hour’, ‘hr’, or ‘h’& W6 p2 O( R8 U- q! U; y( Q
    ‘minutes’, ‘minute’, ‘min’, or ‘m’
    4 ]. W+ h. |  T; @+ `‘seconds’, ‘second’, or ‘sec’2 I% N9 ~: H* [/ m
    毫秒‘milliseconds’, ‘millisecond’, ‘millis’, or ‘milli’
    4 K3 O8 g* `" h" o( T1 P; P微秒‘microseconds’, ‘microsecond’, ‘micros’, or ‘micro’$ ]% X0 @' z. k! J+ T3 ^
    纳秒 ‘nanoseconds’, ‘nanosecond’, ‘nanos’, ‘nano’, or ‘ns’./ W1 l) W' W! g
    时间差可以理解为两个时间戳的差,可以通过pd.Timedelta来构造:
    ( Z. O; h% X2 j# U. wpd.Timestamp('20200102 08:00:00')-pd.Timestamp('20200101 07:35:00')
    ; O! A# ]: D& Z, \0 gOut[57]: Timedelta('1 days 00:25:00')
    6 W0 K1 s! q! g/ w4 K/ M5 x( e; q' |1 o1 E$ A
    pd.Timedelta(days=1, minutes=25) # 需要注意加s: C& q5 c- X3 U
    Out[58]: Timedelta('1 days 00:25:00')
    & u: S5 F  i9 J' b  t( l5 Y/ b/ U! h) |$ l
    pd.Timedelta('1 days 25 minutes') # 字符串生成
    ) Z6 `* t- z4 C9 D8 H, T8 YOut[59]: Timedelta('1 days 00:25:00')3 h; _* _* ^7 J: r3 Z/ P( x
    ! e- T# e7 G$ C# S. |, S
    pd.Timedelta(1, "d")
    9 U; j. D. k$ X' ]: d* V. bOut[58]: Timedelta('1 days 00:00:00')& i7 ^- H0 ^- ?
    1
    : T* z, r! ]5 D3 r) n6 r: n: D% z. Z2# N! L4 P* @  g" [/ L1 R: [
    3
    ) }1 T# D! j" T! P+ \3 K- j4
    0 x3 ]7 I) ^4 c7 c6 X55 M3 v  W6 P* n
    6
    : \) E( C: A: h3 r, ?7
    ) [( ]: f+ G3 i, b8 ?, C8
    3 z% ?- D! T9 c8 h9 K" ~  P; o9' s% `8 V$ Y8 S6 k* Z2 D
    10/ p5 p2 b2 Q6 z( w+ S! ^
    11
    8 f' I/ w4 X' D3 {  k+ @生成时间差序列的主要方式是 pd.to_timedelta ,其类型为 timedelta64[ns] :; Z7 U. {9 T: F- O/ u. @8 }
    s = pd.to_timedelta(df.Time_Record)0 c3 L+ }& x: f0 }' w" I# E

    " X; Q  i  F. k, {$ k9 es.head()- Q' b  h' c( W1 M
    Out[61]:
    * L# S% h8 V, p' @0 J0   0 days 00:04:34
    " p" F, P/ K( D1 n1   0 days 00:04:20
    5 V% p6 R+ }& o: p* \; m/ a, b2   0 days 00:05:22
    . t5 t) m- a$ J3   0 days 00:04:08
    6 @# R' [0 q2 A/ |/ ?4   0 days 00:05:22
    , p5 _. \0 `9 D0 ]Name: Time_Record, dtype: timedelta64[ns]) W2 c8 ^8 D# F6 x1 \
    1
    ; ^; k3 Y0 w( _$ E. Y$ P7 h2
    5 `/ s$ m( Y1 r0 E( t3
    0 ]$ k& V- j" O& E% B0 J+ X! A4" e1 c) y6 E9 X; `' F  {- ^8 X
    5
    1 h! {+ l% {: d  `- _" B9 e# _1 a6! C3 H9 C* V! M# J
    7) O) M* j3 }2 l4 U4 g3 h
    8# `- ~& e* F. S3 u0 E3 P
    9
    5 B- N) `2 P- n" ^" J: X& E# R10& S) N) V9 Q0 j
    与date_range一样,时间差序列也可以用timedelta_range来生成,它们两者具有一致的参数:' U2 P) R! m/ a2 k) T* `5 H0 X2 r
    pd.timedelta_range('0s', '1000s', freq='6min')4 i( a$ H8 d' T/ I2 g5 A( ?' k
    Out[62]: TimedeltaIndex(['0 days 00:00:00', '0 days 00:06:00', '0 days 00:12:00'], dtype='timedelta64[ns]', freq='6T')# C5 ~5 P; P' x" v3 Z
    & A5 S( V* w2 T/ F0 Y; O
    pd.timedelta_range('0s', '1000s', periods=3)
    6 g' \1 I" O8 N/ J8 {7 @Out[63]: TimedeltaIndex(['0 days 00:00:00', '0 days 00:08:20', '0 days 00:16:40'], dtype='timedelta64[ns]', freq=None)8 R. @* G3 V- Z& p( q
    1& G: }! Y, p4 C1 @4 x  @& k6 }
    21 r! c5 u- t0 f) x/ Z
    3
    4 \% `, X" H0 r1 x4  y$ T/ n: U: D% r4 }- L4 ~
    5
    % K+ d& s$ f! t- |7 X对于Timedelta序列,同样也定义了dt对象,上面主要定义了的属性包括days, seconds, mircroseconds(毫秒), nanoseconds(纳秒),它们分别返回了对应的时间差特征。需要注意的是,这里的seconds不是指单纯的秒,而是对天数取余后剩余的秒数:$ O3 B* e4 F4 z0 w& D8 I
    s.dt.seconds.head()
    3 t5 T% R4 `9 ZOut[64]: 5 Y6 s2 ]: F0 D# q. n
    0    274
    3 H  k& G( K) D2 A/ h9 E' E1    260
    5 u& z* J( V* j7 O" S2    322! p# r9 U. a* F3 O3 N  x5 a
    3    248( A- V; Y  B! n# j
    4    322
    4 U5 J" T  b5 M- W9 g$ e6 J- Q  _Name: Time_Record, dtype: int64: i! ?0 m# j) C; Y; l
    1
    4 j7 T: Y1 O1 i2+ b! a) U/ V8 |1 g  e5 l$ C
    3
    " c# R0 K4 l- J. g* b4
    $ u3 G9 L) N0 e5. h0 V/ B: e9 G+ Q
    6. M$ v+ |3 ~  ~. d
    7
    * p% [2 u  D9 Y/ t9 u8% L  J, u$ ~+ g+ G3 K
    如果不想对天数取余而直接对应秒数,可以使用total_seconds
    . T$ J, w+ [% n  G' `( `
    / P) u' r" d! g, _9 Y8 P" o( as.dt.total_seconds().head()
    1 }4 L* d: `* H9 D. E' x9 b: \Out[65]:
    ( _' U; a9 J) Y# b0    274.0
    9 V5 d, m- X( N1    260.0
    # z* y9 A& q$ ?' o: h' S9 y2    322.0
    # @/ [* v; g; E3    248.0
    # `% j2 F, Z7 o5 C4    322.0* v9 J$ W% ~$ ^1 ]& \
    Name: Time_Record, dtype: float64
    ' O  Q' S+ E: v6 {1" A4 T# @9 _# ?: C
    23 P/ v: x, M7 J; n
    3
    ) V+ b  i1 W8 Q/ b* `* J4
    * i# K) z& b5 ~5  n* Y; |3 {# N0 g) z
    6. s/ `2 t- F* m
    71 e! d% ]6 F  s1 L) N$ |- ?& ?  h
    8
    ! V& G9 s. g7 R与时间戳序列类似,取整函数也是可以在dt对象上使用的:+ T. f7 H9 ?; t. j4 K" E

    + p$ {( j2 H6 b4 ~8 C" J7 W6 i+ |pd.to_timedelta(df.Time_Record).dt.round('min').head()
    5 g3 }9 E2 e1 {/ l& u: IOut[66]:
    , f& w  z8 T' E: |0   0 days 00:05:009 Y. F! B9 h% Q1 c2 ]3 s- ]
    1   0 days 00:04:00+ ?1 [  U" I' c  W" }% \: N
    2   0 days 00:05:00
    ; b* [0 [8 ~2 V3 I' z$ _6 T. k4 V3   0 days 00:04:008 g  x* d9 Q9 ?! h* E& Y
    4   0 days 00:05:00
    9 L& @8 u, i1 B5 vName: Time_Record, dtype: timedelta64[ns]. B+ ?+ e1 r  S" _: |2 Z
    11 M) j+ G0 f* ^7 W8 H( V
    2
    & k! j  I: E) Q9 ]- `& G3
    5 I1 h( d% \, P% [7 b% _4$ i- A7 W$ D/ x' Q. d7 |! }
    5+ V: `: q) }) p3 o' h0 q, x7 p9 a& Q
    6
    & l+ R  r+ a5 A8 u7
    ; J& r+ ~5 F* ?; q" F, b: @84 p- E9 ]! b( s# ]$ E
    10.2.2 Timedelta的运算
    * o/ s+ l0 l# ]  O; h/ S单个时间差的常用运算,有三类:与标量的乘法运算、与时间戳的加减法运算、与时间差的加减法与除法运算:
    0 x5 r: [# T/ j5 X) z, Std1 = pd.Timedelta(days=1)! s; O) d3 U" }6 r
    td2 = pd.Timedelta(days=3)
    . S. g4 I- c) d7 z8 zts = pd.Timestamp('20200101')3 |5 z7 q5 X) g- y, R1 t: K

      v, d* D' U/ l8 U; Q, z; }td1 * 2
    1 @: G5 h8 q! ^2 n3 Z+ D" l( DOut[70]: Timedelta('2 days 00:00:00'): b/ U% w; G! D) T2 K! D/ N
    7 z# T9 N4 ?7 H
    td2 - td1
    / Y# l  b4 Y% x4 fOut[71]: Timedelta('2 days 00:00:00')7 v" x% [0 {* U7 \

    3 |0 ]6 s, G: l8 g& n( O$ Ots + td1
    9 F' S0 I# f0 \2 G0 \) mOut[72]: Timestamp('2020-01-02 00:00:00')2 t" b# X, T& b4 ~0 @( Z
    $ {$ r+ s- i0 ]1 t( u) N' a0 I
    ts - td1
    ' A" s- ~: M5 y' oOut[73]: Timestamp('2019-12-31 00:00:00'): Q& T8 `9 `# G! K. p
    1
    ) M8 j/ |: V4 q  d, o28 X. q$ V6 R: Z% C9 _0 ~7 k+ e! _
    3
    9 w- F9 g8 u) U' n& `* c4
      J( r1 V% V2 A$ f5
    2 [! v' r% o4 ~$ V62 w. d, h1 A4 M% y  g& {0 j
    7
    9 U/ g6 H$ ]& q- u89 G+ @9 F5 C! `) _: D6 B' G+ U: P4 c
    9
    . t9 P- q' X6 a7 D105 J4 \6 W4 A+ L1 i4 S. x, U) M
    11
    $ _8 [$ Q: f/ e2 |6 Y& H$ c3 _12$ G1 D! l3 P  J
    13
    + A3 p1 d; \. P6 b+ D3 r) k* {2 D" n147 X7 W/ z. v" ]# h( y+ G
    157 V/ A4 f9 e% V5 I
    时间差的序列的运算,和上面方法相同:
    ( K% N3 U& d: B" [td1 = pd.timedelta_range(start='1 days', periods=5)
    ( S+ P" _2 t5 f/ Ltd2 = pd.timedelta_range(start='12 hours',: a/ a' }9 \9 |% N) X9 j- f+ s0 P, r& w
                             freq='2H',5 c+ p" p9 y( J
                             periods=5)4 f+ P+ h- C. }/ H3 u/ b$ |
    ts = pd.date_range('20200101', '20200105')0 _% \4 ~5 o) f
    td1,td2,ts0 v8 o9 Q3 ?& l0 h9 d) g
    9 i0 Y. i; J: X2 ]5 @8 w+ e
    TimedeltaIndex(['1 days', '2 days', '3 days', '4 days', '5 days'], dtype='timedelta64[ns]', freq='D')
    ) C! x! B' v# U  U% ETimedeltaIndex(['0 days 12:00:00', '0 days 14:00:00', '0 days 16:00:00',
    & p) l8 W" n+ P; g( P, R                '0 days 18:00:00', '0 days 20:00:00'], dtype='timedelta64[ns]', freq='2H')
    4 p4 T% ^) g/ \) s7 k0 s% d; YDatetimeIndex(['2020-01-01', '2020-01-02', '2020-01-03', '2020-01-04',7 |5 u6 j5 p2 H9 f, x1 d
                   '2020-01-05'],
    8 }$ c0 M) r9 R9 K% Z+ A9 T              dtype='datetime64[ns]', freq='D')9 U& {3 b! e1 `# T- d- j# g# ?' |
    1
    # ]* [/ U3 |& G) G' O22 Q6 J0 R5 w, ~/ U1 j5 c# n4 X
    3  B+ \, C! b' }% Z, t7 T6 u
    4; c/ Q/ p$ p5 G9 F; O0 r9 }. Q
    5) W+ x! i6 @- S
    6
    2 L8 J4 z5 n0 k5 b9 i  }7
    6 e0 `! o9 R5 e8 c5 |1 F8, B. P( C  A3 }3 z
    9
    % m% h0 Z: z% n# u" E8 U/ @10
    1 n7 O1 L- D0 T, s11
    # A7 j% e: ?) i# W4 [( j' G0 E12
    % ^( H7 {# [/ g6 u  J13
    ! s" N9 E# r, f! |3 Ktd1 * 5: d+ N$ F! V* n3 P2 n
    Out[77]: TimedeltaIndex(['5 days', '10 days', '15 days', '20 days', '25 days'], dtype='timedelta64[ns]', freq='5D')6 @9 S4 a/ z. u# ?# Q, F
    6 W. g. A/ o- l+ }1 B9 F
    td1 * pd.Series(list(range(5))) # 逐个相乘
    5 }# z; P' y+ V* Q6 rOut[78]:
    , p$ Q7 u/ n6 q3 v- g0    0 days
    & j& K% k/ @5 A/ k1    2 days
    0 l( m* z" F- [6 x2    6 days
    / R( ]- `3 w) X% @3   12 days9 H/ y  e+ |3 o# i
    4   20 days
    . J+ f* G$ [8 s, x- L' a3 _, Idtype: timedelta64[ns]
    2 d$ ?* ?- N' Q3 Y. O' j! K/ J# ?8 d$ p0 Y) [/ i0 V7 w
    td1 - td2
    5 {8 w) q! p: i: @1 UOut[79]:
    4 B, {% T4 q8 @5 D3 p* k2 o9 ZTimedeltaIndex(['0 days 12:00:00', '1 days 10:00:00', '2 days 08:00:00',
    $ H& E5 [2 z) s+ U! j8 [                '3 days 06:00:00', '4 days 04:00:00'],! t' o- i; J' Y* Y! F
                   dtype='timedelta64[ns]', freq=None), _5 q0 S3 L; c9 l

    7 s, K" |" ?; f* k' atd1 + pd.Timestamp('20200101')- o, G& b+ U, z5 ?4 o
    Out[80]:
    # ]( S, J" t& k8 Z' p1 @DatetimeIndex(['2020-01-02', '2020-01-03', '2020-01-04', '2020-01-05',
    + J7 ^3 \3 n5 F) E- i               '2020-01-06'],dtype='datetime64[ns]', freq='D')( Q# z4 [3 \: @  n
    ( ~4 n7 U" |& u5 d" ~
    td1 + ts # 逐个相加
    8 L3 s& C8 g& O. E3 GOut[81]: - A7 v" I6 b8 ~6 W; _4 ]. x% W' `
    DatetimeIndex(['2020-01-02', '2020-01-04', '2020-01-06', '2020-01-08',
    0 s5 \! H( N1 C2 `6 A: ~               '2020-01-10'],. F9 Z" S: n/ G7 q& p
                  dtype='datetime64[ns]', freq=None)) ~: \' y0 g  z% o: O

    - N6 d; ^# i2 u/ f, Q11 j8 R. @! T- T
    2
    , p+ {4 H, Q1 a. I8 l) v31 q$ S: p" K7 j
    4# q+ Z: a8 t$ {* s9 i# a$ M
    5
    ! i6 @1 H/ {* _" `6 w/ [6
    # W) r2 Q% \3 S; W) ^: q7
    * x$ |! i4 m& |% [' p2 w+ ?$ x8
    1 g/ J6 H( [4 z: y* K+ f0 z7 ^9
    - @1 Q. j6 U* o9 ^- y' n# g10( b, i" i" i4 E( d1 U$ f! G
    11
    % e1 I+ ?# L. _& A6 L3 B0 b12! f; v* v' e( q1 E
    13
    , R; ~& p$ H1 ~1 [, b1 a14, U3 ^) s0 c* Y1 o. ?7 U$ I
    15
    1 \5 D9 u: X+ }/ C# |' |16: t7 J( N& _" Q9 i
    17
    + e! c+ Z7 g* x8 `  @, f6 Z5 W0 j18
    # |. H1 U7 U2 |7 @2 U19
    ( H5 r2 y" \& ]+ _& _20
    ; ^, k% Q" |3 [! g; L) [, L6 l, z21- ^( M- k: }) w* ]9 p
    22! t; t# u  j1 U$ Q# }( D2 M. l
    23
    6 u' J& t, h6 @: H$ W) @, [24% z9 k  T; d1 K6 b8 s# R
    25
    $ S' v& a- u! T, R8 _' f4 ^" u26) ?. Q2 K- h4 R
    27
    ; `$ ?+ B  e4 V* V28
      `6 K2 G9 g0 W: B/ I10.4 日期偏置
    . G' z" I& o# }5 R, Y  Q* f* F7 I10.4.1 Offset对象! N# L! a- r% r, N2 F
      日期偏置是一种和日历相关的特殊时间差,例如回到第一节中的两个问题:如何求2020年9月第一个周一的日期,以及如何求2020年9月7日后的第30个工作日是哪一天。, W; Y$ |& p# W! i9 }# x
    , T/ p3 G* N& ?* _; U6 [
    DateOffset 类有10个属性,假设s=pd.offsets.WeekOfMonth(week=0,weekday=0),则:
    " h6 L6 ]* z; x- ^: e3 T1 J
    + r$ {9 @4 U: s5 z. fs.base:<WeekOfMonth: week=0, weekday=0>,返回 n=1 且所有其他属性一样的副本1 S* y' v7 R3 C6 {& y
    s.kwds:{‘week’: 0, ‘weekday’: 0}, o7 q; Z' R$ ]1 y' |& M: L! [
    s.wek/s.weekday:顾名思义
    8 n" ]* W; ~8 K% O  i有14个方法,包括:
    . O. `; b9 X7 E  r7 j
    ! g, x4 G% `3 x, [5 u( L% S4 o% }DateOffset.is_month_start、DateOffset.is_month_end、DateOffset.is_quarter_start、DateOffset.is_quarter_end、DateOffset.is_year_start、DateOffset.is_year_end等等。8 l; T& M% ?) c, p2 y3 I
    pandas.tseries.offsets.WeekOfMonth(week,weekday):描述每月的日期,例如“每月第二周的星期二”。
    . ]3 M: U1 E- d4 i$ V5 b- b
    ( O5 ]& ]. g9 v有两个参数:
    1 t& Y6 X% V2 y0 B- I* Z2 ]week:整型,表示一个月的第几周。例如 0 是一个月的第 1 周,1 是第 2 周,以此类推。. G1 t8 ~- f: f
    weekday:整型,取值为[0,1,…6],表示周一到周日,默认取值为0(星期一)
    - y$ ~" m& C- n; e# A  g) Kpandas.tseries.offsets.BusinessDay(n):相当于pd.offsets.BDay(n),DateOffset 子类,表示可能的 n 个工作日。
    6 x( _& y  F4 x3 L" |
    4 \2 f( m6 v1 ?pd.Timestamp('20200831') + pd.offsets.WeekOfMonth(week=0,weekday=0)6 W& [! T5 S8 ?
    Out[82]: Timestamp('2020-09-07 00:00:00')" {. I( P- h* ^) A6 G. r  a& j

    : @; `6 j" B+ v! a3 Dpd.Timestamp('20200907') + pd.offsets.BDay(30)- D! ~( y/ h9 q! v: ^  x
    Out[83]: Timestamp('2020-10-19 00:00:00')# S2 N+ u$ B. g4 F6 \! K* K% \
    19 T: W+ d$ j( y
    2  d, v8 ], W- A
    3$ q9 ^  q. y- P) {# p# ^0 N1 v
    40 X  E# q5 \" ^6 A! a2 B$ O
    5; F1 [- w/ Q0 e0 f8 }' M1 \' C8 {
      从上面的例子中可以看到,Offset对象在pd.offsets中被定义。当使用+时获取离其最近的下一个日期,当使用-时获取离其最近的上一个日期:" _- q, e* m% }- o2 p( W; n5 _" f
    5 A% z2 U, j' c- O
    pd.Timestamp('20200831') - pd.offsets.WeekOfMonth(week=0,weekday=0)
    9 J. j. Y! J1 z* XOut[84]: Timestamp('2020-08-03 00:00:00')1 w2 e$ d/ V+ b2 m" ]# |
    , W) t% F9 W* e  i' c$ D1 B
    pd.Timestamp('20200907') - pd.offsets.BDay(30)
    6 `' g% l% |7 F$ gOut[85]: Timestamp('2020-07-27 00:00:00')
    # s/ F8 C7 u. Q7 a3 @# q
    ) e9 P- F5 v/ q3 R" }. C$ }  a* Lpd.Timestamp('20200907') + pd.offsets.MonthEnd()
    : N+ X6 r" S6 [* v2 S2 ~Out[86]: Timestamp('2020-09-30 00:00:00')
    7 q' s2 {% M: [$ O3 X5 Y7 R1
    " O4 Y9 q" o4 `  w1 ?; ]2* ]4 P1 }% g- E0 y  {. ~' ~& B
    3' b) _. h; r% X1 k7 v2 [- e1 `9 U) f
    4
    + R. B7 D% b. T/ w5! Z0 X) Q4 _- u/ X* \6 e7 X; x
    6
    . p& X( H+ w' x6 K( P( {7
    ) G/ Q. J: t0 @* ^7 G6 s, r5 k! {8; p, M# `; C6 u7 \! ~
      常用的日期偏置如下可以查阅这里的DateOffset 文档描述。在文档罗列的Offset中,需要介绍一个特殊的Offset对象CDay。CDay 或 CustomBusinessDay 类提供了一个参数化的 BusinessDay 类,可用于创建自定义的工作日日历,该日历说明当地假期和当地周末惯例。
    4 O. h# q) ?8 h) ]) R8 o  其中的holidays, weekmask参数能够分别对自定义的日期和星期进行过滤,前者传入了需要过滤的日期列表,后者传入的是三个字母的星期缩写构成的星期字符串,其作用是只保留字符串中出现的星期:% Q7 m7 a) Z6 X9 p
    - y  S) F; l4 n3 X9 T. w! U7 [
    my_filter = pd.offsets.CDay(n=1,weekmask='Wed Fri',holidays=['20200109'])( p( o$ i% v/ R! f
    dr = pd.date_range('20200108', '20200111')
    + x  I8 Z! [: ^( A. ~6 R2 e; y
    dr.to_series().dt.dayofweek+ \7 |% J" Z  }( _
    Out[89]:
    1 D" `+ M8 I0 Q2020-01-08    2
    ' B, l! J. W' Y! X0 r) [, [2020-01-09    3" s" _$ S. H* K; f( O& u
    2020-01-10    48 p  N% C0 _3 ?- N% r
    2020-01-11    5
    ' ?( @0 @) X4 T4 lFreq: D, dtype: int64- K5 i# F1 `7 R+ b: K# p' Z. `

    ) E# R9 m% m  l$ F$ c/ ~2 Z[i + my_filter for i in dr]4 u9 Y+ k$ \3 s3 ^* ~/ E  g
    Out[90]:
    4 X  V2 u8 W; G$ X" C$ Z  C& \[Timestamp('2020-01-10 00:00:00'),3 @) d  i2 \. p" X
    Timestamp('2020-01-10 00:00:00'),
    $ i- O+ t1 c+ _( m/ {7 e  m2 L Timestamp('2020-01-15 00:00:00'),) g: y& I- O( q  A
    Timestamp('2020-01-15 00:00:00')]
    & L; \5 X: f+ N: P  [- E7 X0 f& @9 I( B9 n( s. p+ A
    1
    ) Y5 C% d1 r" S* D20 h/ f8 l7 f/ r* A' H6 Q! `/ W
    3
    ' Y! J! C/ r* u3 U44 `3 v& F, [, g: M3 z
    56 C3 G! |# |; y- [7 x& y- X1 A
    6
    # A3 A" Y" T" |* z% \& h7
    * k( [9 w" t5 q! ^. G2 x8
    ' Y8 D  X& ~" d( |9
    ) C: U  h2 Y" {& {/ C10
    6 F* Z% g: {8 j# B119 I) x% d- B3 M0 p8 a. r7 ?- G
    12
    % Y! H! @7 z' i! s" K4 e; o13- A! N  P. P$ @! u# v* ^! ?+ _
    14
    1 @% L! o! B4 _) I15
    3 B7 l4 i) S! J% Q165 J8 G" Q+ H8 O' k, \  v' o! ~3 t
    17. N8 \$ a; i. ^0 ?$ g
      上面的例子中,n表示增加一天CDay,dr中的第一天为20200108,但由于下一天20200109被排除了,并且20200110是合法的周五,因此转为20200110,其他后面的日期处理类似。% R8 c  c' b( ]
    9 h; p0 ~4 y/ H. k7 D4 G. ~
    【CAUTION】不要使用部分Offset
    : f5 W4 G( K! \5 e! P7 g在当前版本下由于一些 bug ,不要使用 Day 级别以下的 Offset 对象,比如 Hour, Second 等,请使用对应的 Timedelta 对象来代替。! ]' f, M# z* b

    # i% R3 T1 x7 T  |/ i10.4.2 偏置字符串
    4 h" @0 \$ {5 e. [1 Y  前面提到了关于date_range的freq取值可用Offset对象,同时在pandas中几乎每一个Offset对象绑定了日期偏置字符串(frequencies strings/offset aliases),可以指定Offset对应的字符串来替代使用。下面举一些常见的例子。
    - X7 `: U  P7 N6 L& X
    $ S) n. a7 ?0 E  Offset aliases:pd.date_range函数中的freq参数,为常见时间序列频率提供了许多字符串别名。 也称为偏移别名Offset aliases。偏移别名列表点此参看(大概27个)。/ N3 R+ ?' e1 E

    . l: C. M* b! ?: T* q* M& j5 Ipd.date_range('20200101','20200331', freq='MS') # 月初/ F1 }5 y, L  g! |7 Z/ B$ c
    Out[91]: DatetimeIndex(['2020-01-01', '2020-02-01', '2020-03-01'], dtype='datetime64[ns]', freq='MS')
    2 y) [% ~* @, |' P# E2 {8 Y- h$ b3 A5 T+ S2 f) K
    pd.date_range('20200101','20200331', freq='M') # 月末/ o; n# [1 z. B) q, N. }1 d: ]
    Out[92]: DatetimeIndex(['2020-01-31', '2020-02-29', '2020-03-31'], dtype='datetime64[ns]', freq='M')6 F! b, _& O! Q
      \% x# F1 Z: E: o, s# j4 k% u  x- ]* N
    pd.date_range('20200101','20200110', freq='B') # 工作日8 `. a4 @6 S; ^. @) F( ?
    Out[93]:
    6 r' K: ~+ d/ m6 ?8 R) Z7 y9 pDatetimeIndex(['2020-01-01', '2020-01-02', '2020-01-03', '2020-01-06',* E: p- I# M. w
                   '2020-01-07', '2020-01-08', '2020-01-09', '2020-01-10'],, \  m9 x7 `4 a' m! n
                  dtype='datetime64[ns]', freq='B')
    9 a8 M5 R5 L4 ]3 }  r4 \3 I) L5 m* ]2 }( ~6 ^5 M  W  R
    pd.date_range('20200101','20200201', freq='W-MON') # 周一
    + O; V0 w: @! ~4 a8 p$ cOut[94]: DatetimeIndex(['2020-01-06', '2020-01-13', '2020-01-20', '2020-01-27'], dtype='datetime64[ns]', freq='W-MON'). c7 G, H4 f, M. T5 y

    ) |* p0 ?7 l' Z8 q3 N' |# _1 O% ?pd.date_range('20200101','20200201',
    2 N9 O( r) l" n$ ^7 E% u2 K! s2 X              freq='WOM-1MON') # 每月第一个周一7 c* r  J  H0 N/ p6 R- [

      M/ g! e5 p8 g' VOut[95]: DatetimeIndex(['2020-01-06'], dtype='datetime64[ns]', freq='WOM-1MON')* J3 G' U4 ]" y' [2 r

    ( L0 f1 v, \) U$ H/ U+ U' r1
    ' n  t" p0 L) y5 e, c# f# }3 w27 q) \1 L4 @9 p+ j
    3
    ' V; T  A; T# t+ D. P. V9 k* y4( q, {  g1 c0 z8 W# v1 V+ F
    5
    6 y0 q9 J& {5 \/ Y- L& X6
    $ ^# q! _3 L9 D0 O7% @- e3 H8 j* S
    8
    * p4 ?3 ^( x9 ~+ U9) \% N" F7 F4 _2 t- V
    106 t6 n+ g0 q: c8 _+ V8 I) {3 P% K
    11
    & F' j! P2 }7 i  C. n: P3 n12
      {2 w- x* N5 f5 A& @( N8 X13% u) j7 s. z+ n: p
    14
    $ x/ y2 k/ j; N# c) l3 d. M2 D" O15/ }) k% A6 d5 R4 A& g$ `1 ^
    16
    4 C6 ^  ?  X. z4 }' ?17- o$ F/ S) l/ C! j  I- t
    18, S' y* p2 l: o% z9 H2 O  `, j3 _
    19
    7 z8 s9 [' V2 p/ r. y+ N% u, k; V上面的这些字符串,等价于使用如下的 Offset 对象:
    1 V2 u( @- o  S; }
      _5 {% r& h* d# K5 a: e0 {pd.date_range('20200101','20200331',. W: I8 b3 w( O% B& h4 }: R
                  freq=pd.offsets.MonthBegin())2 x. N+ F  T( ^1 u9 e# e% y* w

    5 z' Z2 ?; D. c3 G( K. jOut[96]: DatetimeIndex(['2020-01-01', '2020-02-01', '2020-03-01'], dtype='datetime64[ns]', freq='MS'). S- y4 x2 Y5 \( R* q  V* M4 e
    : u: F, e5 `2 ?$ H! s0 ^
    pd.date_range('20200101','20200331',4 \# b7 C% N( W  I2 K
                  freq=pd.offsets.MonthEnd())1 R4 x" G5 [4 F* A! P
      I% Z7 d7 N+ ]9 A  U) v2 r$ T  E, Q
    Out[97]: DatetimeIndex(['2020-01-31', '2020-02-29', '2020-03-31'], dtype='datetime64[ns]', freq='M')
    5 ^4 P) U( C. H( g+ K3 h: ?  y: `2 q7 Z# `
    pd.date_range('20200101','20200110', freq=pd.offsets.BDay())
    8 U' S/ ~6 S/ J8 }& w! IOut[98]: ' I+ R0 p) q1 U% u7 H
    DatetimeIndex(['2020-01-01', '2020-01-02', '2020-01-03', '2020-01-06',
    ' Z$ S/ D% R- a! E9 a6 m               '2020-01-07', '2020-01-08', '2020-01-09', '2020-01-10'],
    , F$ ^9 Z) u& Z, S  Z              dtype='datetime64[ns]', freq='B')
    9 ?* E7 r9 E' S7 r3 C2 l/ J+ t
    % P8 V! F" l3 y  G7 }) |" ^$ R, Cpd.date_range('20200101','20200201',
    0 X3 X* b2 D5 y1 {6 z, T3 n& A              freq=pd.offsets.CDay(weekmask='Mon'))
    ; V8 W" ]; l" N5 f8 D6 Q* s
    ) m0 T7 d  c8 \) C* `( g! J5 ]( QOut[99]: DatetimeIndex(['2020-01-06', '2020-01-13', '2020-01-20', '2020-01-27'], dtype='datetime64[ns]', freq='C')
    ' B. h3 o2 `* r% Z4 I
    * i* V! q( z+ Xpd.date_range('20200101','20200201',' r. \, v& \, Z) w4 J4 T
                  freq=pd.offsets.WeekOfMonth(week=0,weekday=0))1 Z$ v' r/ v2 W5 H& l( t/ A' o& _
    9 f; ^8 @1 G5 F  s+ B
    Out[100]: DatetimeIndex(['2020-01-06'], dtype='datetime64[ns]', freq='WOM-1MON')! k6 N$ w/ w. h5 K  W: s# R: p- v
    / A" k3 b; j3 S. r% B0 u1 S
    1
    - T2 F: d$ o9 `2  }) J( X6 N2 D
    3
    ; G2 i0 [0 P5 Y, L4  |4 F9 h0 K( q+ K
    5
    / e4 z( M2 z. T# S* W6
    7 H) ?4 j* ^& b" g" r2 n$ d7
    1 n6 G5 }3 H( p; O* h; ]3 i0 `2 K8+ [$ \* G$ x7 ~: {* s
    9
    2 s! r0 i/ D0 i10
    ; l+ v3 k1 G. n: f3 V" K" P11
    ) l5 L9 I6 ~) ^. N; @12) n0 T1 q) d$ I" k
    13
    0 k8 J$ B) m3 t2 m9 l4 h& n14
      x( r% I" o  V. N6 m% K( E15
    * Z: {; R; n% c6 w8 G161 Z' j9 k  T* O
    171 Y7 Z8 x9 w) _. ?' A9 T* u, s2 ~
    18+ B6 e  v1 {5 u. B) Q7 d, e! z
    19" t" l+ u9 n) O3 e1 S1 S# C. ^
    20: K: G3 ^( |; _$ Y3 q
    21( T. o# Q) |5 m
    22/ \  B5 e- k8 y! Z" _
    23
    0 `) V0 }; L* z- u# h+ Y242 h6 |3 D+ V* ~. U2 S. O4 Z# a! ?
    25* D& d- O" j$ t# S8 ?  N& _7 K! I
    【CAUTION】关于时区问题的说明
    / S" ~" ?! I2 n  各类时间对象的开发,除了使用python内置的datetime模块,pandas还利用了dateutil模块,很大一部分是为了处理时区问题。总所周知,我国是没有夏令时调整时间一说的,但有些国家会有这种做法,导致了相对而言一天里可能会有23/24/25个小时,也就是relativedelta,这使得Offset对象和Timedelta对象有了对同一问题处理产生不同结果的现象,其中的规则也较为复杂,官方文档的写法存在部分描述错误,并且难以对描述做出统一修正,因为牵涉到了Offset相关的很多组件。因此,本教程完全不考虑时区处理,如果对时区处理的时间偏置有兴趣了解讨论,可以联系我或者参见这里的讨论。
    9 X) H3 X6 f( i( X
    9 j6 s* v. L! E9 u; p- g2 G10.5、时序中的滑窗与分组
    0 Q7 y, n6 ~+ z' E; N10.5.1 滑动窗口: C' c1 \3 @- k# _8 x
      所谓时序的滑窗函数,即把滑动窗口windows用freq关键词代替,下面给出一个具体的应用案例:在股票市场中有一个指标为BOLL指标,它由中轨线、上轨线、下轨线这三根线构成,具体的计算方法分别是N日均值线、N日均值加两倍N日标准差线、N日均值减两倍N日标准差线。利用rolling对象计算N=30的BOLL指标可以如下写出:' o) Y0 P# e7 P* L& X" `
    ( m" c8 t# r) [
    import matplotlib.pyplot as plt2 \9 G* D- O, `) C8 N
    idx = pd.date_range('20200101', '20201231', freq='B')  v9 n' ^! {! t. s! B
    np.random.seed(2020)1 g+ x& D4 d% f# \( _

    - \1 k* ?% v1 w; ^data = np.random.randint(-1,2,len(idx)).cumsum() # 随机游动构造模拟序列,cumsum表示累加3 Z" A  y+ K( c" T  i
    s = pd.Series(data,index=idx)
    9 q, Q1 C  z. O$ zs.head()5 p* S( F- ]1 V# @- N, g3 z
    Out[106]: 3 _0 e0 U8 ^& a. C
    2020-01-01   -1' f, ?, x3 I, r. D4 t: g3 h
    2020-01-02   -2
    0 g8 Z2 ~$ A1 H. O( D3 B2020-01-03   -1
    3 m  L- {4 `9 c7 [8 [  _2020-01-06   -1! U" C8 t% N) j( R0 }8 u% ?
    2020-01-07   -23 A7 c) Q7 e! ]( ~9 E4 V7 C
    Freq: B, dtype: int32+ N* Q# `3 [# N( u) ]9 h1 A
    r = s.rolling('30D')# rolling可以指定freq或者offset对象4 V: x# K7 h# D1 ]( L9 y

    1 Y8 n7 ^2 U# r* W$ X  Jplt.plot(s) # 蓝色线
    ( c1 {2 v. [# f; C8 Y8 N+ QOut[108]: [<matplotlib.lines.Line2D at 0x2116d887eb0>]
    ! Q2 C. ^2 ~$ y; H% j7 x3 N: a" ]: jplt.title('BOLL LINES')0 I+ ?  Y. b8 ?! s% ~
    Out[109]: Text(0.5, 1.0, 'BOLL LINES')
    5 G6 I  B$ x# Z2 }+ H( q. P( C
    8 ]9 X' b( z. w+ p2 x' }; R* [plt.plot(r.mean()) #橙色线
    2 R! F3 k) C2 ?2 q  p( \Out[110]: [<matplotlib.lines.Line2D at 0x2116d8eeb80>]
    5 f5 B) ~. G; n0 N  {. q9 r- K' l
    & Z  k; z- e% Q( B( ]: Iplt.plot(r.mean()+r.std()*2) # 绿色线
    " z& g$ {8 Q* k3 tOut[111]: [<matplotlib.lines.Line2D at 0x2116d87efa0>]) p' ^9 E( ]& ^) C# ]& F
    1 y% ~  r% d2 ~( o
    plt.plot(r.mean()-r.std()*2) # 红色线
    # V+ G9 v3 V  Y) n0 e4 }* nOut[112]: [<matplotlib.lines.Line2D at 0x2116d90d2e0>]3 s8 N) v4 M2 ^/ h6 g; w( v8 D9 S

      d" l. w% {3 z# @, ]1 S% k1
    3 x( r- ]# r- y! N+ J2
    5 T) ^7 @4 V. t! q38 v4 }  Q, B; s
    4
    ( q+ Q; f9 ?7 H& v, s5
    & D* @! ]' S  ~0 V$ |$ d6
    0 A2 R; i: K7 F9 q7
    2 [/ E& t) w" ~9 L0 _0 _  \* I8
    * N" g5 ~) m+ T5 P4 ~! `9
    " z: s* l: f4 r% |; Z10
    1 P3 c; z8 P$ r3 B  J* s4 @3 |11
    ( ?) j: s9 {+ A- e" B7 g122 y: t2 X+ Y$ s; Y8 i
    134 k& q9 n) E3 F) W
    14
    3 x* f5 z, a5 q% F15' p: Q) C8 a0 o
    16
    * ?' S1 k1 e; m7 m17# a% r8 {4 F) q9 I+ Y) m: R0 [
    18/ A. @) W- K& S/ k# H8 G
    19- ~) g8 H) W: t+ w' X9 I, V9 f( m: y
    202 m) p% u! r3 L, \
    21  X  `8 u- Q1 ~( d2 b1 U
    22
    * i% o) O5 d$ x. m7 |23
    , Q5 A. n% k: D) O241 i$ D" U  S5 G2 @, {6 _, x
    25% o1 i& @: S8 ]( X! Q
    26: I  y! a1 d! P, j0 w  r
    27
    - K) r. i+ i4 R2 i+ C8 O28) x! d# b3 k" O* V6 [
    29; \( _8 w( z8 Z$ ?& H
    ( W; `$ z8 O. }% Z! X
       这里需要注意的是,pandas没有实现非固定采样频率的时间序列滑窗,及此时无法通过传入freq字段来得到滑窗结果。例如统计近7个工作日的交易总额。此时可以通过传入多个函数的组合来实现此功能。
    - i/ k& ^; b& F+ ?   首先选出所有工作日,接着用普通滑窗进行7日滑窗加和,最后用reindex()恢复索引,对于双休日使用前一个工作日的结果进行填充。
    , f5 ~4 X: J0 I0 o1 r" p' ~/ v/ O4 R( B( N& g8 U' `# U" J2 y
    select_bday=s[~s.index.to_series().dt.dayofweek.isin([5,6])]0 ]/ S8 p( Y9 K9 \! @: f
    bday_sum=select_bday.rolling(7,min_periods=1).sum()3 j0 x$ n. R3 D# X9 X+ f( x
    result=bday_sum.reindex().ffill()
    3 p" c, i( E* p( Y6 xresult
    + F+ H: @( _8 e+ ?9 T3 k4 C/ i7 o& ]5 H* b2 m3 a/ i! Z  x
    2020-01-01     -1.03 Z* C- W: X& X- \
    2020-01-02     -3.0; m& H1 y* s, o# ]
    2020-01-03     -4.0
    $ D/ u$ m: f( f/ u: f( s, t2020-01-06     -5.0
    & H1 H9 S1 m  L6 T9 D$ C" O7 J# V# E: ~2020-01-07     -7.0- I8 ~1 _% J4 N: ~: j$ p9 t  C+ s
                  ...  
    . S. a3 z; R, x5 C: ^. i2020-12-25    136.0
    2 y% q6 f* d2 p+ F2020-12-28    133.0) M; V3 e' ]! P. |
    2020-12-29    131.0+ E# T' U: G+ j" g) b1 N) O
    2020-12-30    130.0
    ) f7 u* c6 c5 u/ y# ~2020-12-31    128.0
    9 U% q/ D+ C/ y) {Freq: B, Length: 262, dtype: float64
    ; N, Y: ?- x+ L3 ~1 T2 N$ E  v$ N* C% i; ?' z  }* G" D+ B
    1
    " I. r9 o6 _& b2
    4 L+ H" Q9 ]! _2 J* d2 b2 p1 |" |" |3* P2 n1 u7 w$ _& P; T
    4
    % ?3 S+ I7 k' f5
    8 @  c0 e7 D% G1 I+ @6, K/ c0 l# u& I& D& T
    7
      T) i8 b( B6 t. j* ?6 w% C9 d8
    * d' P+ q( Z7 P# |7 e4 @: `96 S6 d! \1 w! z: r& F% A, i
    104 w* G8 C& E3 u( t9 N& u
    119 k  U$ Q. ^) r8 W; p
    120 B  D) s. W6 Q) ~$ i+ J! P3 A
    135 P' {& y& l! A7 p
    14
    / B/ B" s3 I9 m7 X" H( z15- \4 |. {! b, P! G0 f6 |
    16& R  N  V) {0 Z( f1 M! ~4 M
    17
    * n! B, k$ \# j/ j  shift, diff, pct_change 是一组类滑窗函数,它们的公共参数为 periods=n ,默认为1,分别表示取向前第 n 个元素的值、与向前第 n 个元素做差(与 Numpy 中不同,后者表示 n 阶差分)、与向前第 n 个元素相比计算增长率。这里的 n 可以为负,表示反方向的类似操作。
    , u( a" q3 u2 t& R5 O$ V( F& C8 F- r/ \- T- H
      对于shift函数而言,作用在datetime64为索引(不是value)的序列上时,可以指定freq单位进行滑动:
    2 y; s2 \3 q+ I/ }* t$ q: f) f/ K' y3 z% t
    s.shift(freq='50D').head()3 E  r7 t. g+ V) k2 q- o- d1 m
    Out[113]:
    # e) g( o8 T6 f4 q9 s2020-02-20   -1
    . `/ }3 a* A9 a8 |+ Q2020-02-21   -2
    " q( }' \3 b& U' T7 P2020-02-22   -1
    3 E! }9 G* P% W/ ?$ D2020-02-25   -1
    % ^' f4 z3 L0 }4 H0 q3 x/ c. {2020-02-26   -2% |) I5 a. V+ c9 S( O$ Z
    dtype: int32
    + x9 c$ }) Z8 H1 a- f+ B1
      s% h& J! }3 u, H20 r( Z' L/ L8 J) Z' r2 q. P* k
    3
    + e2 ^% f* l* l" k+ G4
    ( J4 G( l% k# u1 S3 v' @0 m5
    + e% L' q  D, s  u! [7 z( R6/ j5 r& K& n, U7 N8 H
    7( h5 [$ r7 J/ A* U1 m- b
    8$ E# J8 \8 _. i" e2 g% u- [
      另外,datetime64[ns]的序列进行diff(前后做差)后就能够得到timedelta64[ns]的序列,这能够使用户方便地观察有序时间序列的间隔:
    ; e3 ^  F  n+ \  F
    ) O6 K1 O" m+ C$ Y. l9 j2 F9 Hmy_series = pd.Series(s.index)
    7 b7 K( L( ?# {, P8 V& A3 u! O/ f$ Zmy_series.head()5 ~. |6 ?! l" o4 c5 b9 h
    Out[115]:   R1 l% C% p0 |' w: c
    0   2020-01-01& j3 z1 A$ K1 Z( ^  i
    1   2020-01-02
    + {* y7 j7 O1 v$ k. w+ k% A2   2020-01-03+ [3 Z1 Z" |/ b5 P; Y) f
    3   2020-01-06
    ' _1 L" c: ]! i9 M* B( e0 R7 U4   2020-01-07) h/ ~/ i7 r4 Q$ `( D  E$ l
    dtype: datetime64[ns]
    % ~/ e% b8 v$ f* |; B; A' B
    4 Y3 }, c# G: `' E6 ymy_series.diff(1).head()3 G$ o1 w* T& p+ x: A1 J# W1 m
    Out[116]: , Z1 Z; Z3 l* u. _  r
    0      NaT
    - _6 c- R, d7 j, y1   1 days
    - c, [5 t4 e' }+ ~' u2   1 days/ C2 h6 Z3 W; {, v6 u: L
    3   3 days  z4 c: v  M: i1 ]1 U, q( U; U' O, W  r
    4   1 days& E6 W8 p% z; R! @
    dtype: timedelta64[ns]
    * I9 P; ^8 i# E$ ?: A; d) j0 L: x7 ^  ?  h5 g2 q  ~- Y
    1* K  B& B) Y. w7 R* f: O) G
    2% ^5 W& F8 C% C8 z- d5 |' i
    3# s$ B7 Y0 v, ^$ V% T1 u) C; \
    4+ V! p+ s) W. v6 _9 f
    5
    / W; W- e/ K" m* I6
    : W) H6 h# m3 D! F7
    ! I. Z% O$ w% i' H1 x+ t* U8  z$ j2 l9 q, s9 [; v) ?7 k! q
    9
    ' }1 R, k1 J0 ?( u3 g0 T10
    9 i$ \, g+ i% L! y* d  C112 x: d- v4 b( F6 B: J; c) y
    12
    % i3 f% V" b. S/ S! ?3 {0 j. H135 i2 q, z% ^  k- j0 O$ |3 D2 Y0 o
    14- |" a; G) Y- [& h! C" S, J
    15
    ' l% Q* t# s8 }6 R16
    8 z. u4 D% z2 [2 Z8 T* u( p' p3 z17
    : ~" X5 s8 b+ I7 B4 a- U18- f$ ?  d7 i4 l8 h. Q# @4 D: Q3 [
    10.5.2 重采样
    + T$ B0 _! X1 X1 Y% M2 r! @5 Y  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)
    9 W$ X% ^" F9 d& K常用参数有:
    6 F6 `' w. H& M: ^3 V' f: h
    1 I7 s9 J$ t* srule:DateOffset, Timedelta or str类型。表示偏移量字符串或对象  t/ E; t" W- g8 y7 l: H, M
    axis:{0 or ‘index’, 1 or ‘columns’}, default 0。使用哪个轴进行上采样或下采样
    ' Q7 I% G! \& [2 W2 c, W" G: K% `closed:{‘right’, ‘left’},默认None。表示bin 区间的哪一侧是闭合的。所有offsets的默认值为“left”,但“M”、“A”、“Q”、“BM”、“BA”、“BQ”和“W”均默认为“right”。
    + u9 u: D0 [( n4 |0 ulabel:{‘right’, ‘left’}, 默认 None。hich bin edge label to label bucket with,所有offsets的默认值为“left”,但“M”、“A”、“Q”、“BM”、“BA”、“BQ”和“W”均默认为“right”。: t; h1 {5 y# f* j
    convention{:‘start’, ‘end’, ‘s’, ‘e’}, default ‘start’。仅针对 PeriodIndex,控制是使用rule的开始还是结尾。
    $ _2 L) T& O. I! T( T" W. {on:字符串类型,可选。对于 DataFrame,使用列而不是索引进行重采样。列必须类似于日期时间。$ Y! `+ w+ `9 k8 b' m: C
    level:str 或 int,可选表示多重索引MultiIndex的级别,这个级别的索引必须类似于日期时间。
    0 V, G% u4 _# G: porigin参数有5种取值:" ?% d% s* S7 v* s) t
    ‘epoch’:从 1970-01-01开始算起
    4 t! d1 C! ]% [; c. ?6 S6 U‘start’:原点是时间序列的第一个值
    8 _2 {* b4 L9 J. \' g1 A* y7 m! e9 O" M‘start_day’:默认值,表示原点是时间序列第一天的午夜。$ d. p3 @) H8 e- ]3 v
    'end':原点是时间序列的最后一个值(1.3.0版本才有). [9 K7 Z9 s0 G, s! k3 I
    ‘end_day’:原点是序列最后一天的午夜(1.3.0版本才有). Q' H* v, R0 F5 w* Z) Z# U& q+ K
    offset:Timedelta 或 str,默认为 None,表示对时间原点的偏移量,很有用。
    # e, y' e5 V, M! G4 D+ p& T  a  closed和计算有关,label和显示有关,closed才有开闭。
    4 m3 {$ W1 ^" W4 I( X. N  label指这个区间值算出来了,索引放区间的左端点还是右端点,closed是指算的时候左端点或右端点是不是包含。
    ) y. L" `+ I6 g( e( u% R1 O; P2 O
    重采样对象resample和第四章中分组对象groupby的用法类似,resample是针对时间序列的分组计算而设计的分组对象。例如,对上面的序列计算每10天的均值:9 T* ^( N, t8 k3 [/ Q6 V  \# Y
    s.resample('10D').mean().head()2 l6 @3 R* D' a3 a7 G/ _
    Out[117]: - V6 c) Y4 c3 I
    2020-01-01   -2.000000
    5 `8 D) x: N5 C& {3 J- Z# _4 {2020-01-11   -3.166667
    2 D" |0 p7 ~( a( m2020-01-21   -3.625000) Y& _/ r: ?. E8 ~& _8 f
    2020-01-31   -4.0000009 C) E$ L0 Q! a1 s
    2020-02-10   -0.375000
    1 A: B6 k, v; v- C/ f  E, @Freq: 10D, dtype: float64( a7 l- x% v' n; R- w
    12 Y! \1 U7 T, B
    2' N4 v0 I1 P- m& c- L4 u
    3* c# x& X6 K( M5 ]- H+ @
    4* k3 Y2 ]) Z% X4 _
    5. B$ B) X: j5 x- A) r+ J. h
    6
    2 h3 g% B* {( |) d  k  e* N7 r7
    6 j7 g2 Y1 B! m; M+ n8! o! I, d8 C' f' B% i$ B- H+ e
    可以通过apply方法自定义处理函数:/ m# o7 t# |' B+ h
    s.resample('10D').apply(lambda x:x.max()-x.min()).head() # 极差/ a8 D% _% a/ \7 B
    6 b' Q4 A* X7 }+ @) u2 y
    Out[118]: & e. J2 x& L" `$ f
    2020-01-01    3# X( y6 A+ u' L: c6 a/ }4 w
    2020-01-11    4/ N  Q9 r+ a: C/ U0 x
    2020-01-21    4
    6 m/ e3 r5 V2 f; @+ y2020-01-31    2
    8 n7 K) T4 L, ?# w2020-02-10    4
    ' ^4 {% [, j, y3 L* v+ w; L( MFreq: 10D, dtype: int32" N- `' g* ]' R# {
    1
    0 r+ w6 Z* m2 k2 V3 n2# I5 n. Q; X# I
    3
    6 a5 X/ }) A  o* b4
    ( f* }! ?0 q8 U9 ?' \/ |) @' A$ d5
    ' G, L2 f: T6 ]+ M6 w  L' {6
    0 t7 j9 @. f$ M5 ]4 ~5 t7" j6 ?) {& \& D: L' s- D
    80 q: c, r( ^2 ?/ A+ l
    96 @8 `! U# G, v; Y1 P9 U8 A
      在resample中要特别注意组边界值的处理情况,默认情况下起始值的计算方法是从最小值时间戳对应日期的午夜00:00:00开始增加freq,直到不超过该最小时间戳的最大时间戳,由此对应的时间戳为起始值,然后每次累加freq参数作为分割结点进行分组,区间情况为左闭右开。下面构造一个不均匀的例子:
    / ?" n; d  }: @$ A2 z/ ?* E& v+ m5 N7 L5 {
    idx = pd.date_range('20200101 8:26:35', '20200101 9:31:58', freq='77s')) N3 ?! R) v. w( P. a. o
    data = np.random.randint(-1,2,len(idx)).cumsum()  b0 m/ ?0 B1 {6 B/ W$ j* T' D
    s = pd.Series(data,index=idx), W4 r# Q" |; E/ I
    s.head()
    4 K. ^  i' I, Q, i8 u3 J0 k! L- b/ }
    Out[122]: # B2 q% B$ r0 T( v3 D. h
    2020-01-01 08:26:35   -1
    # ~' J% Q: A+ e) ^7 \1 F2020-01-01 08:27:52   -1* b4 p% W- I2 X0 q
    2020-01-01 08:29:09   -2
    ; z2 u7 F+ ]3 J8 J8 D. [% G# R% m2020-01-01 08:30:26   -3
    6 }9 F- I/ G$ ~. y! l3 G& [% |2020-01-01 08:31:43   -4
    : a2 T) e. Z/ }5 n9 pFreq: 77S, dtype: int320 i: [! y- l' V; G1 K: w; R
    1
    & H7 [7 H4 {3 A6 w( [2# l+ G8 V9 x: \; e0 V$ |' j' z
    3& O9 Y2 q, O& t, u$ R  z3 S0 L$ F
    4
    4 l& X0 I0 b3 K$ _) d; P& r: R; i5( m$ I5 u  n5 c3 ~& z: F4 [$ T* S( L
    6
    ' K- i% r: ]; B' N  W! r7
    ! C( l: C* b: r5 \5 C6 G  `8
    9 o0 r* g6 X' m* _: N% }9( S& f/ y1 j3 s- q
    10
    ) M3 @6 @. p( Y+ Z& O7 h+ ^5 ]6 G11& G- t# z) a! G$ `3 k9 j+ v' M
    128 R, y8 |1 c0 M4 }+ b& f
      下面对应的第一个组起始值为08:24:00,其是从当天0点增加72个freq=7 min得到的,如果再增加一个freq则超出了序列的最小时间戳08:26:35:. T, {6 K& n  Y! p9 G- W
    ( _1 C! x, |: V6 j! Z$ @# Z
    s.resample('7min').mean().head()
    . _2 v# |. L# x+ J6 s' F$ bOut[123]:
    : O& g! |( y; ?: J, C* J2020-01-01 08:24:00   -1.750000  # 起始值,终点值包含最后一个值
      [8 [- K. Y# E% F2020-01-01 08:31:00   -2.600000
    4 E: U6 j3 q9 E- {' t. E5 a2020-01-01 08:38:00   -2.1666673 ^2 N5 i* e% ]( s$ s  e5 `$ D
    2020-01-01 08:45:00    0.200000. v$ U! g) [6 D: U0 j9 W
    2020-01-01 08:52:00    2.833333
    / _) c% X) I, f5 U+ `6 V' _Freq: 7T, dtype: float642 p4 B2 Q' V2 l5 {8 B
    1
    ) s# w3 _, J$ l2 |# V2 _$ K2
    & f& d/ c; P- J  X$ ?! J3 H3
    ; R) y+ e2 a& ?+ \. b/ }/ x; G, }2 ~4
    ) G  l5 @6 T- g# y7 o5
    0 D/ C/ S' n# b% [6
    / G' `/ Z1 U/ R5 G8 |: n$ ~$ F7" [2 N# l+ l0 K: {+ N
    8' i& w' x+ c6 ~% M* m) b. [
      有时候,用户希望从序列的最小时间戳开始依次增加freq进行分组,此时可以指定origin参数为start:
    4 \9 D& z2 {  q( U0 k/ i
    ! z4 H- b' b$ y9 Ls.resample('7min', origin='start').mean().head()
    . O: o! f3 K0 t) I" HOut[124]:
    . H  x. i2 u5 m! J$ r, {2020-01-01 08:26:35   -2.333333: k, h" r3 J5 `( D$ D8 O, N. |3 q
    2020-01-01 08:33:35   -2.4000008 T; v5 g, I0 ]6 J0 K3 q
    2020-01-01 08:40:35   -1.333333: I1 s+ B& e1 _. w  f
    2020-01-01 08:47:35    1.200000: D% D, }+ ?! p
    2020-01-01 08:54:35    3.166667
    $ E* H: E* c) [" U- x& i( IFreq: 7T, dtype: float64/ h( l7 R5 i) a2 E) p- E
    1
    6 s/ x$ t& M% U* q2! o; Q0 p5 _+ b" C6 O6 L
    39 V8 }0 ^0 Y& h; U& e
    44 d9 `7 D5 S& j, g! S1 t
    5' T/ R7 d  q% n  ]/ q5 f4 ]
    6
    : o2 `  L* D* x0 d' i: w+ Q7
    # _  q2 @0 c# v0 R2 X6 ^85 h; I$ e% d  M) g: F+ E
      在返回值中,要注意索引一般是取组的第一个时间戳,但M, A, Q, BM, BA, BQ, W这七个是取对应区间的最后一个时间戳。如果想要得到正常索引,用’MS’就行。5 }3 p4 F; U, |% ?0 r
    $ S& u, L, E' E) D- N
    s = pd.Series(np.random.randint(2,size=366),
    ' C: F% r4 k4 `9 X              index=pd.date_range('2020-01-01',
    * V" p) s: V' C                                  '2020-12-31'))
    # [& v( f$ t- V' `7 L% Z
    ) k  ?1 g/ g4 b' |' @' n
    ! f  c4 |' B! \s.resample('M').mean().head()0 x6 v- X) d0 |: w
    Out[126]:
    ) n, G/ [- g3 ?% j* v" _2020-01-31    0.4516135 S  T: K5 g7 ~& ?$ T, ]/ T
    2020-02-29    0.448276& c4 L& }7 L" H3 N  {8 I/ @
    2020-03-31    0.516129, }  X; n# \6 y3 t/ x8 B/ S4 v; }
    2020-04-30    0.566667
    2 G6 q; ~' [5 V4 I4 Z! K2020-05-31    0.451613" x- K1 u; T; L) M/ s( [
    Freq: M, dtype: float64
    + e, H4 C7 G' z6 Y6 C9 }
    * O) ?  `  K* O. |7 R/ {+ C- Bs.resample('MS').mean().head() # 结果一样,但索引是跟正常一样8 G3 r2 a7 n2 l: x8 \9 R
    Out[127]: + e: ~# z9 |# C* L: S+ [  d
    2020-01-01    0.4516136 T4 r% n  {1 w! D
    2020-02-01    0.448276, Q! `3 t! m  M7 ?+ W" S
    2020-03-01    0.516129/ @# B$ x1 K( ?" V7 r) w+ t* c6 V
    2020-04-01    0.566667
    4 B8 T! H. T' V% U2020-05-01    0.4516132 @% T; B! g' b3 a% {! H
    Freq: MS, dtype: float640 L2 b) T6 W" n: ?+ v
    5 l0 U& w& n. z
    1
    ( o) S8 _$ s" U/ K0 L2  s" m, J3 h. x7 M; i6 m
    31 Q6 B/ v" n4 t  s% y
    4
      }& n7 g+ j8 C% _: j# G5
    $ A5 s4 m& C0 ~# R$ @6
    - G, Y; V$ ?2 C7 W7& O/ ?# r: q4 v% \4 ~( J, x8 M
    81 ~7 x! s" M. G
    90 O0 l  b! K5 @/ U5 w2 S! S
    10: @: u* q" Q0 _) a
    11: N) K  k. H' h
    12
    ) l/ t- l5 E: z) @1 N8 B# T& _: m13
    8 _0 ]; Y3 m3 R3 \# A7 Y144 M' x6 ^9 [, X6 A1 `. z: _/ c# a
    157 q4 [" J/ i$ R/ k
    16: U4 O! Y; |. t- ]6 r
    17
    : l+ J, l7 f% O, a1 Z$ t/ X18
    6 S8 i3 {. q  J) B8 w. S19
    4 ]8 E, g1 b" t; U2 ~* l20
    5 d  p" X( Q! i( [21
    2 U4 `7 k! T9 t7 z22
    " u2 g+ r  T% `! x2 j6 j对于 DataFrame 对象,关键字 on 可用于指定列而不是索引以进行重采样:/ Y" \. I' F; _( {9 W1 u
    d = {'price': [10, 11, 9, 13, 14, 18, 17, 19],1 g+ X. X0 e0 |. T2 A+ \- O
         'volume': [50, 60, 40, 100, 50, 100, 40, 50]}
    / w+ E+ [9 k6 v$ M0 c2 F% B, f% udf = pd.DataFrame(d)
    ) w: N. C7 Y( n) c; k) _$ L9 Ydf['week_starting'] = pd.date_range('01/01/2018',
    1 t) P9 L: l1 L. S' E: q                                    periods=8,
    ) Q: [- r7 Y2 ~' f% f                                    freq='W')
    6 _2 ]9 k) \, Udf8 R. D' S, R* q8 }
       price  volume week_starting
    # D5 Z$ h' r- k$ {' r( ^, ]0     10      50    2018-01-07
    " m$ W7 M- Z0 ]  r5 }1     11      60    2018-01-14( Q3 J$ G8 k" q2 S3 ]( f% I/ t# d
    2      9      40    2018-01-21. @) d! o: x( N) O4 [  l/ A& b1 D
    3     13     100    2018-01-282 Q4 _0 z+ |# N& q
    4     14      50    2018-02-049 H; C6 o: E: y& i. u
    5     18     100    2018-02-11
    ! f# t0 p* T: y) ?4 v6     17      40    2018-02-18
    5 N2 g. n. B! n; x* |- c/ l2 H7     19      50    2018-02-25- o, U! E$ P4 n8 [" C
    df.resample('M', on='week_starting').mean()
    % f9 {$ }" w( S% {. U               price  volume
    ' R- T: ]3 Y- ~week_starting
    ! C8 r  o: ~0 S9 f2 X5 d2018-01-31     10.75    62.5
    , g4 v! Y# K* a) z9 C2018-02-28     17.00    60.0
    ( u0 ~) W1 |. Z" \! N8 S$ X/ z/ K4 B
    11 m4 C) U5 |: c6 z5 `: b0 B
    24 w& O! y+ Z& d, [4 a
    3
    % m4 ?# F6 [: H* w& V1 r% A% P4& {# v* p: ]; P" n" f
    5
    % |, ]+ k6 @1 z# T. B* B6
    ! B: O+ J) I- M3 z& y7
    ' q, d$ C# N: ^; Z8
    $ W! ?0 K0 j' m  y, |0 K9( R. l2 ~6 J' o7 g0 |3 b+ l5 O* D" x( e
    10$ I. ^( r. j' f/ k+ Q7 n
    116 ^: X  z4 a6 M) \1 u% s6 n
    12& j% I$ h* R5 k& b. W5 z& X$ T/ N
    13) @; ^6 ^+ u# w/ n
    14
    # F& P: M7 A* o15
    4 Q: L# A3 j) c- |16
    , F: R( x+ `. I17. t0 M, p1 k' Y; y
    18  }9 I0 \6 t* v7 c) V: B1 V: T
    19
    1 X$ M4 Z1 Q: I9 ?& J1 s203 g  X4 q; ~4 S
    21
    ' P* I  C; h6 a4 |, @对于具有 MultiIndex 的 DataFrame,关键字 level 可用于指定需要在哪个级别进行重采样。1 x2 o& j3 u$ d9 b1 [$ j
    days = pd.date_range('1/1/2000', periods=4, freq='D')
    4 N5 f1 P5 ~7 c7 f' L# L: E2 qd2 = {'price': [10, 11, 9, 13, 14, 18, 17, 19],
    $ x* t% C4 H6 A5 S8 f      'volume': [50, 60, 40, 100, 50, 100, 40, 50]}, q6 c: j& J: ~4 C
    df2 = pd.DataFrame(& g8 L/ C$ O* j9 x9 J8 J
        d2,
    1 E6 A- V' i1 ]1 V' B' [    index=pd.MultiIndex.from_product(
    ) \! Z8 ]& e* V        [days, ['morning', 'afternoon']]0 Y# a- }! k# j+ O( R
        )2 \8 G, k( m" C3 z6 v7 D  Z2 t/ F
    )6 q- j2 T; u: k
    df2
    ( |0 J* Y' o. T                      price  volume
    2 K, Z$ A' y3 w7 X! C2000-01-01 morning       10      50
    3 s5 _, F2 M6 {. x& Q* c, ^           afternoon     11      60
    ( H- R# P% p; k" B$ S5 A2000-01-02 morning        9      401 M8 M5 f) c, b& [0 D# l9 s
               afternoon     13     1004 }" ^6 u7 |+ N0 v4 `1 k
    2000-01-03 morning       14      50
    0 w# N; D+ `4 G8 D* _0 X; z           afternoon     18     100
    / A6 v1 r( L" O! K& `% E! |( }2000-01-04 morning       17      407 P+ p  ~" _  d1 u5 o4 `
               afternoon     19      500 K4 S7 n4 B! k
    df2.resample('D', level=0).sum()
    % \; ?- Y  G0 S* e            price  volume* k1 T% m1 K; F5 E0 I
    2000-01-01     21     110
    % q  x& c' X9 N: k2000-01-02     22     1409 S/ i& q% V( X0 U  k
    2000-01-03     32     150, _7 x1 C; ]# l$ H' y1 Q, F# Z' u
    2000-01-04     36      90. }3 e3 Y) k+ u5 |. i, C
    1 p5 t% g6 C) A& ?/ i
    1
      f( L$ y: Z, n$ n  L$ J2
    % o2 y& d6 j. T0 R* @/ R39 \9 j. {4 s, r2 s  b
    4, \) s2 f0 j8 z) Y  a3 F5 P1 l/ s
    5
    3 I' `. B) j, d! y) `6# Q5 W+ K3 `; a$ D# O6 {
    7. r/ @! I# ^! Z3 A. N/ Q/ s' s4 H& l
    8
    & ?2 B* O' l* R$ s9& _7 D$ [3 C+ O* u
    10
    & J5 [  b0 N2 D! N11: l7 Z) [0 F' r4 L
    12
    - _. B9 I; p1 m4 Q: l% m: P; p; @' @13
    , X7 @6 l! M& A! _% b14( Q. _( }" e5 X$ U
    15
    ) E  i  q6 C  W" E169 S$ U5 J! P" v0 A3 F6 X3 Y* n
    17
    5 o' u# S$ d, P' [2 I7 [7 n4 R18) b3 M3 l: [+ t% V- R
    19/ d$ ~8 }* G: k+ [
    20
    1 [2 d( i' u$ A+ [  }# |21
    5 y; Q+ D3 g6 o8 b: Z4 C22
    # v( j2 e0 u( t237 e; m3 `2 R; ]0 Z
    24; i/ u2 d- C; o8 x% w
    25
    9 n4 c3 w, j7 [根据固定时间戳调整 bin 的开始:& ]: G* X  I8 c9 W. ]$ c) U
    start, end = '2000-10-01 23:30:00', '2000-10-02 00:30:00'
    * \9 ?: D$ R0 ^rng = pd.date_range(start, end, freq='7min')
    , `- O: v) n, @! dts = pd.Series(np.arange(len(rng)) * 3, index=rng)( ]% o7 J2 W8 R# F
    ts
    & `; h- g7 J, z! {0 I9 b2000-10-01 23:30:00     06 G2 {  b0 g$ H% O' I# i: a) F
    2000-10-01 23:37:00     3
    , x8 q. I9 _; ]3 h) h2000-10-01 23:44:00     6
    & }6 u7 l2 R7 k% i; x- ~9 ?0 z; [2000-10-01 23:51:00     9
    * r" W! l  p2 ?1 [2000-10-01 23:58:00    120 d9 Y9 N0 c' t: c& V9 }3 w2 D
    2000-10-02 00:05:00    15* C' e  u$ ]4 H- K! S
    2000-10-02 00:12:00    18
    " N8 F3 o) w! i9 y$ p6 b2000-10-02 00:19:00    21
    1 x7 C. }2 X6 N9 A& ?' R# [2000-10-02 00:26:00    24! t* f/ x  T# [2 ]9 ]4 h# ~
    Freq: 7T, dtype: int64
    ! f% ]. v7 W% |% V. D. k; |/ v# U4 @6 ~5 q; n- y7 [! s3 Y  Q9 V
    ts.resample('17min').sum()
    : V( m6 ?+ L) C, z: q1 d  c2000-10-01 23:14:00     0
    0 ?. ^! p/ p# W2000-10-01 23:31:00     9  x& M0 o; V7 x
    2000-10-01 23:48:00    212 e& h9 G2 ]/ F; _/ s
    2000-10-02 00:05:00    541 t' P" T9 ]% L0 d& X  J1 i
    2000-10-02 00:22:00    24
    1 W& ~& Q; D% y7 ]& f! GFreq: 17T, dtype: int641 t; m* C/ X/ `
    6 J$ d0 n1 m4 K! R$ }
    ts.resample('17min', origin='epoch').sum(). @7 \9 W$ T5 N+ `! q8 z5 F8 Q
    2000-10-01 23:18:00     0# [% s4 W' P/ D; @2 `! A7 P# l
    2000-10-01 23:35:00    18
    ( {" `+ D4 Q; n- D2000-10-01 23:52:00    27$ P  D7 G# t& ]
    2000-10-02 00:09:00    39
    : x3 \& h0 A  A# X! _2000-10-02 00:26:00    242 t# v* |( Y# P
    Freq: 17T, dtype: int64
    6 r5 Z: d& ]8 p* H. i7 \
    ; b" |& ]& K2 Its.resample('17min', origin='2000-01-01').sum()
    0 i  J- X. B* G7 U' |4 |% u2000-10-01 23:24:00     3
    + |6 n4 s' V& ]  k2 h+ |9 F0 b7 ?3 ^* a2000-10-01 23:41:00    15/ i, n" D: T1 u$ x  \$ W4 H$ _
    2000-10-01 23:58:00    45
    . o. c, R" {7 I/ X8 U, e4 y2000-10-02 00:15:00    45
    1 a) I4 m: u) H! d. E9 EFreq: 17T, dtype: int64( C  `4 t+ m' X' x2 F7 G

    2 x$ y$ c! q6 M6 @: U! L19 l, x0 W3 `  F$ @# T
    2
    ; L) G0 J  Q. \9 X7 L3) I. ?: Q* `, D- s
    4
    , t3 m! C7 z4 Y: l' {5$ b! J0 k: C7 w* c
    66 O' c- E1 ?4 R  `- N: e! P. Q/ Z
    7
    / Y, f4 z0 c3 [0 x: ^8 R, |8* U( c) _7 D8 ^
    9- y# K. P! g, ]/ c% F
    10
    $ F* A- M: ^  [# k* l1 Z5 g' W% c11
    ) e9 a1 G5 L, O4 H  t$ V; f12  o3 g% W: d5 a# x8 C
    13% @4 V; {/ C3 l; c
    149 l& Z! E, z6 c6 t' v
    15
    4 ?! }4 j. [' f16
    ' m  F7 s4 V; P) P# U) I17
    * ]2 n5 a, K* [3 A8 }- R0 i18# F6 o( b; P+ u" }
    19
    ; U5 q! U, O1 a3 K/ ~& W203 i% c% |, I* u; K) o, E& {& a$ x# K
    21* b  V3 @  @! K0 w. B3 O5 e5 X
    22
    $ g5 K9 E( S* x; J# Q23
    4 {/ s0 {2 b9 |24, E* C- L0 E% W6 M5 i* t
    25# s2 c: [4 X% p; Y8 W% e1 _
    26
    $ u) ?! V% P. i1 o) E& C27" E; p/ q. c0 b: f' q- d9 M
    28$ c; I3 y( X. B' }; C
    29
    ) ^! w5 K: y( g$ R8 Z% k30
    % y) C5 L. e7 o1 b31, `" K2 \+ B4 ]; V' E! s) j
    32& Q4 N7 S' e- B! V5 X" \
    33
    ! n, T, s! |  q! S34
    6 z: a( |7 p: d) D1 Z1 q35
    - s2 S+ u) B* N$ k36
    7 F- G5 _0 @* r37
    9 W9 p9 _" ~/ C/ Q# Y如果要使用偏移 Timedelta 调整 bin 的开始,则以下两行是等效的:# Z5 G0 A- D& p, o3 Y7 o
    ts.resample('17min', origin='start').sum()  }7 l& u- i. R  y# w' V
    ts.resample('17min', offset='23h30min').sum()/ g5 _# I$ Y, `4 p9 i
    2000-10-01 23:30:00     9
    * K: n) p8 [, |  t3 s- u7 [2000-10-01 23:47:00    21
    . g& H1 u: @9 V: c! t( h2000-10-02 00:04:00    54
    2 e' {" v' B: e' {3 [* A% E2000-10-02 00:21:00    24
    % Z0 v0 D3 p" D. a% B4 ^Freq: 17T, dtype: int649 V( z, T# A9 o9 x7 V
    1+ u1 l8 l3 t; x. {5 y
    27 [: R2 [* H8 F2 ~
    3
    " K! e+ i) K/ Q5 @" Z: T4
    ) h1 U5 T# I5 v2 s& }& H5
    # |/ w0 x4 x- }8 [1 e5 g6
    8 ^1 y' a( [$ v" M/ X! U/ D9 L7+ _/ f* t' ~2 X4 R% S. [) c
    10.6 练习/ B( q0 T1 S) ^, X' W
    Ex1:太阳辐射数据集
    * l' b( J0 w' F! x现有一份关于太阳辐射的数据集:: [5 g* k) C& y( q6 W: G, v
    ( V( Z. `, v; \
    df = pd.read_csv('../data/solar.csv', usecols=['Data','Time','Radiation','Temperature'])7 P# h1 S; z# m
    df.head(3)$ m' ~* ^/ l2 D; o$ P& a0 ?, ~, P
    2 i% D( F% r5 y. s
    Out[129]:
    2 ], E8 I- B$ m% R, K                    Data      Time  Radiation  Temperature
    ) O$ z6 }8 K8 W+ t, P0  9/29/2016 12:00:00 AM  23:55:26       1.21           480 R9 l+ x, f2 `* U
    1  9/29/2016 12:00:00 AM  23:50:23       1.21           48
    % [1 R1 q& y1 N* G8 C8 d1 c2  9/29/2016 12:00:00 AM  23:45:26       1.23           487 b) y. A5 X3 V
    1% q8 C5 S2 I! [4 @$ |
    2
    ; N) U: `$ C! k3 R8 A7 U9 x3 `3
    0 d/ z0 L8 ~: G- Q  @' s48 {# p' }% \& e+ A! |/ y
    5* [& P! d* ^7 a
    6
    : k# Z- c" X, _( s, A0 P, t- c* r* V7! o  h5 T. g; U' d; r& a9 S7 y
    80 O, B0 m8 g5 q
    将Datetime, Time合并为一个时间列Datetime,同时把它作为索引后排序。, F6 p& ]5 B$ \& ]) ?1 N
    每条记录时间的间隔显然并不一致,请解决如下问题:
    6 u! p- J6 ]/ L- H找出间隔时间的前三个最大值所对应的三组时间戳。
    7 @5 \! f) A" q' [3 L3 L是否存在一个大致的范围,使得绝大多数的间隔时间都落在这个区间中?如果存在,请对此范围内的样本间隔秒数画出柱状图,设置bins=50。
    ; E7 o$ T1 p9 ^0 }0 R0 T3 c/ N) t求如下指标对应的Series:8 F' x# S$ \, v# `- ~% T  a
    温度与辐射量的6小时滑动相关系数
    # J+ n  ?1 C  }1 Z以三点、九点、十五点、二十一点为分割,该观测所在时间区间的温度均值序列
    1 _! e( B& S# c/ I: V; `- `: |每个观测6小时前的辐射量(一般而言不会恰好取到,此时取最近时间戳对应的辐射量)
    ; N) u3 ], ~" zimport numpy as np6 @' t5 r3 m" H$ O) V8 P! j; U. R
    import pandas as pd/ |( b! y+ Z* g* E9 d
    12 `- ?: K' g9 D4 h  P* j3 V, v$ t1 m
    2
      N0 F5 q& }4 n6 S6 j7 p% W将Datetime, Time合并为一个时间列Datetime,同时把它作为索引后排序。  ^1 G- X' h) A' C3 u0 \, Y9 M
    data=pd.to_datetime(df.Data) # 本身是object对象,要先转为时间序列5 H0 `( c' a7 Y! x
    times=pd.to_timedelta(df.Time)1 E( n& x/ D/ d! Q* l7 U
    df.Data=data+times
    3 j9 C$ n7 B" }" N1 r) bdel df['Time']
    * Z, m. O! F; p- o& Zdf=df.set_index('Data').sort_index() # 如果写的是set_index(df.Data),那么Data作为索引之外,这个列还另外保留
    * C8 n: l% y, c6 J& idf4 z: E3 O, [6 d" g: u2 @* V. f
                                            Radiation        Temperature- s5 C2 m6 v/ M
    Data               
    " R/ ?0 |3 k, y' B2 Z2016-09-01 00:00:08                2.58                511 G" x7 l' Q6 }9 Q
    2016-09-01 00:05:10                2.83                51/ ]( R1 E: t3 J8 C: l# r& _8 J
    2016-09-01 00:20:06                2.16                51- J. s; [% ~3 ~  ]. w2 ^
    2016-09-01 00:25:05                2.21                51  @% Z' ^6 A/ U& m& G
    2016-09-01 00:30:09                2.25                51
    : ]6 @" P' b; m: p  D...        ...        ...
    ' q, j, k. Q3 N; j+ d1 i, w2016-12-31 23:35:02                1.22                41- f* A7 w3 m6 R1 D3 V2 Y4 b6 q  d
    2016-12-31 23:40:01                1.21                41
    $ U6 _- M8 W) Q( U( P, R2016-12-31 23:45:04                1.21                42! ~1 p% }7 m, p3 g1 l
    2016-12-31 23:50:03                1.19                41
    : _. w$ D7 q" c$ T( y6 `2016-12-31 23:55:01                1.21                41* w9 C5 j) g0 E8 @! A0 q# m
    ; {/ x+ \! X- B/ U
    1
    ) A. U' h9 @- b+ F. D2 W2# x# {7 [$ t2 S4 b
    3
    6 X- B. ?. H" X( q% }; s4
    & i2 h! k7 n* d2 h5
    0 W& {( R5 u& S9 _+ U/ u6( i* V$ x( ^4 m6 S3 B; E! n
    74 Q3 t0 a0 b8 F2 l. c) k$ \
    8) D" t' D- [+ s, E2 K
    98 ~5 v  w4 L: V* H. F. c* U, p5 p& K
    10
    6 Q" k7 J8 {- ^$ ~3 h, N4 D11
    + a" x5 h( C4 ^! R% M8 k& R3 [12
    + P8 ~4 [; W! Y; X- W9 h2 L133 v) G: c/ a$ H( n) P$ A! j
    14
    - o; E# S7 q1 o- n15) K# t' U( C+ Q: Z+ F! q
    16
    ! r( T0 Y3 L' R$ i$ T- `17
    $ S, ~4 \" p2 s18# g+ v8 J" H9 [# I- \0 m4 b) b+ g
    19/ T0 }7 S8 s/ K. ?  {, z+ d
    每条记录时间的间隔显然并不一致,请解决如下问题:
    : A. c- A1 i  `5 Y找出间隔时间的前三个最大值所对应的三组时间戳。3 j, K, L3 H6 S2 N& M
    # 第一次做错了,不是找三组时间戳4 B: R3 B& f& Y; E5 d+ k$ n
    idxmax3=pd.Series(df.index).diff(1).sort_values(ascending=False).index[:3]
    9 m: U. n6 r7 |  r4 X9 Cdf.reset_index().Data[idxmax3,idxmax3-1]0 W- P/ S; J6 n  v# |( I1 \  Z; e

    1 w2 S, l6 `6 U) O0 K, l0 |& E6 W+ b. B7 R  b25923   2016-12-08 11:10:428 T: F2 B+ Q% P, ^) G
    24522   2016-12-01 00:00:02/ a* z; ?8 N7 [; e6 j* p) R$ r
    7417    2016-10-01 00:00:193 @: z$ x* s$ c1 v' c( h
    Name: Data, dtype: datetime64[ns]
    ) S/ s6 u2 [+ W0 V0 r15 V, _1 I) H  s! H8 I
    2
    . Q- ?. r4 }; w0 E3& m% _" D+ Y* P
    4
    . x, ?+ W4 P/ W4 y8 ?0 E. x5
    . z* c8 V. @& b; b9 r# Q6
    ) \6 Q2 x0 m$ G3 N; o( P6 P7
    4 v- s3 e4 Q; e) U2 N8
    % \! T8 ~9 ?& a5 F6 p& \- Kidxmax3=pd.Series(df.index).diff(1).sort_values(ascending=False).index[:3]# j! ^9 c6 g$ a: j; J' e# ^
    list(zip(df.reset_index().Data[idxmax3],df.reset_index().Data[idxmax3-1]))
    6 Z- k  O* T3 B* U& n. s6 [- e% Z/ m
    / Y! P+ n( l# P1 V; n[(Timestamp('2016-12-08 11:10:42'), Timestamp('2016-12-05 20:45:53')),
    ' X0 E/ h& ~8 ?8 B2 w, P (Timestamp('2016-12-01 00:00:02'), Timestamp('2016-11-29 19:05:02')),8 H# a6 k3 z% h& b) j/ a) F
    (Timestamp('2016-10-01 00:00:19'), Timestamp('2016-09-29 23:55:26'))]; G- p$ v' }& i  B  y9 I
    1
    " k( b' m. ~: Y% W9 x+ h2
    * _, }1 W; W6 l. c3
    ; C' R0 H8 a6 v4
    / b' i: @; ]+ P) M% m5
    % Y* B/ K0 `  }) g6
    9 ]  Y& M1 P- m: \" P+ s参考答案:# Q- t/ V4 p- l
    ! ]( x/ F: p4 ]0 L# s. a
    s = df.index.to_series().reset_index(drop=True).diff().dt.total_seconds()5 x7 w6 x# }8 C/ j! t
    max_3 = s.nlargest(3).index
    8 N+ [/ [; ~0 S7 [/ ydf.index[max_3.union(max_3-1)]
    3 k, r! k2 K1 n  u4 u! s$ j/ f/ a$ U, h
    Out[215]:
    ; j. {' ?3 O5 E" ?$ ^" m* [DatetimeIndex(['2016-09-29 23:55:26', '2016-10-01 00:00:19',5 }! a2 S7 C& T6 t& t/ c5 t' e
                   '2016-11-29 19:05:02', '2016-12-01 00:00:02',* D; }( N, ]( {+ v5 y  ~
                   '2016-12-05 20:45:53', '2016-12-08 11:10:42'],  l2 k$ J! Z7 U5 t5 B! v
                  dtype='datetime64[ns]', name='Datetime', freq=None)# Y6 x" B- e& }! q: n- g; w
    11 N; \5 U5 Y7 x- s! m' ]" Z/ K
    2
    % V2 b5 J# O( C% e2 U; _3$ C- u6 V/ s) q  \) x; r
    4
    % k* Q. w2 y8 a5% L7 x. {  w+ I/ K
    60 Z6 U- e$ ^6 |# A
    7
    7 ]9 L% N' ~2 Y1 b* F8: O& s/ K0 ~+ b- U' W
    90 t( E4 [. |3 C: _, _
    是否存在一个大致的范围,使得绝大多数的间隔时间都落在这个区间中?如果存在,请对此范围内的样本间隔秒数画出柱状图,设置bins=50。. s0 M# V5 W! S, \6 n" g
    # 将df的indexydiff做差,转为秒数后排序。再求几个分位数确定取值区间
    4 d2 [. ~( T$ o- g5 Z7 W% i" ~s=pd.Series(df.index).diff(1).dt.total_seconds().sort_values(ascending=False)
    % V0 G. C* \: p9 W6 L$ e% t) Us.quantile(0.9),s.quantile(0.95),s.quantile(0.99),s.quantile(0.01),s.quantile(0.03),s.quantile(0.05)* [0 L& V+ ]) t4 ?- U- K9 B* s9 C
    # c! ^2 D  _; ?2 ]! ?& i  G) k
    (304.0, 309.0, 337.15999999999985, 285.0, 290.0, 292.0)) G5 y; H' |: H( L+ i$ Z, r
    1
    . U& [# M6 [$ d4 y3 ^23 V* f% H) P) h% ?
    3+ @0 u9 [7 L4 Q: K
    4" @8 w( t7 F+ d/ g$ X" ?" f5 z
    5
    7 n& Z0 x7 I* k* d( Q; s%pylab inline
    , E3 l6 C" W* U6 q" d_ = plt.hist(ss[(s.values<337)&(s.values>285)],bins=50)
    & n8 \5 `# [2 Jplt.xlabel(' Timedelta')
    8 q3 Q- a  C3 N% Dplt.title(" Timedelta of solar"); O8 H! H/ r  u/ k% O
    1
    ' s- Z" S/ _4 i5 R1 T. m22 P9 r; u. s! R5 O/ z9 O6 A
    35 ]+ d  i: Z: T
    4# G7 G" R" u4 S. N0 @+ c

    $ p2 C" Q& t3 A2 P4 @5 S9 P) N1 b; h7 ^9 t0 g4 }% r
    求如下指标对应的Series:, W# }3 D- ~# `' \
    温度与辐射量的6小时滑动相关系数
    : F" ]2 q: N, l+ ^' Q以三点、九点、十五点、二十一点为分割,该观测所在时间区间的温度均值序列
    6 y: q- t3 b8 o* y每个观测6小时前的辐射量(一般而言不会恰好取到,此时取最近时间戳对应的辐射量)
    & _; w$ ^( M0 o; p7 }: l# x2 c* _df.Radiation.rolling('6H').corr(df.Temperature).tail()2 X: S: u& m6 q
    & W+ x7 K# O+ D) o! N% \
    Data
    3 H. N4 }/ E6 v$ w1 H2016-12-31 23:35:02    0.416187
    & ~' _/ F5 }9 v+ U2016-12-31 23:40:01    0.416565
    6 A8 N' U) m, t& E  M8 h2016-12-31 23:45:04    0.3285749 C) w& o* n8 C( `7 |% x" R. q6 ^# y
    2016-12-31 23:50:03    0.2618837 q9 a+ f% m7 B* Z
    2016-12-31 23:55:01    0.262406. s. ^5 z! W3 O) a. c* [, a4 ^
    dtype: float646 `5 w8 J2 ^- t5 o1 Y/ m# b
    1
    4 U! P4 |3 n( h24 a8 W5 h4 V- |
    3- Q( ~! p, F) m( T9 v, \
    47 M$ s1 T. ?! t0 D! T7 k: v$ H
    5+ c4 z, z, ~- `* e8 O# {
    6% `& `# B8 j5 Y0 }9 L& n
    7! u, w6 _4 z- A
    8
    9 X7 I4 m6 g0 C  I  P) E; g9
    , Q1 _. w$ k, ~4 m* U, f8 _9 hdf['Temperature'].resample('6H',offset='3H').mean().head()4 q) ^+ J; j; s8 ~6 K# p
    % E$ W2 Y/ z( u1 _; ?$ ]7 o
    Data
    * d! R* A: A' F8 ?. D2016-08-31 21:00:00    51.218750
    $ W6 k( y5 W5 x$ _* f% }2016-09-01 03:00:00    50.0333336 x6 V% C/ R. y
    2016-09-01 09:00:00    59.379310
    1 i  c% `8 I% c6 @5 l2016-09-01 15:00:00    57.984375
    ' s) G1 \. T6 Z- T$ q2016-09-01 21:00:00    51.393939
    ) V- W5 h- i8 _% [, {Freq: 6H, Name: Temperature, dtype: float64
    ! N# p0 c" m8 x+ z3 w- i( [1 N1
    : w# n; F, h8 H) I8 j, W2" W0 f5 Y; K% B# d; Y, x
    3
    5 I' D/ C8 t. U) W4
    ( Q2 a- ]% Y: b) c$ E' F5 L5
    5 V1 P, [2 Y* ?4 h9 h  o6! ?! \* T' X$ b( o+ n
    7
      n4 ~7 I8 o- _/ f( Q' y4 D8* H5 |, I- o' `( D
    9
    ; h; G7 f8 a0 Z  f最后一题参考答案:" j0 ?  w4 j* V; h

      p* `1 ~$ U& Q9 ~* {+ o0 G5 q0 ^# 非常慢1 g6 I1 m, t/ j
    my_dt = df.index.shift(freq='-6H')
    2 \1 ^( I) `; }" c% M" pint_loc = [df.index.get_indexer([i], method='nearest') for i in my_dt]
    0 m  t( `! X$ Wint_loc = np.array(int_loc).reshape(-1)
    $ }5 w8 P+ o1 {res = df.Radiation.iloc[int_loc]
    ' s" a  X; G1 L, F- w1 [( @res.index = df.index2 j9 [8 Z5 C! D0 e2 Y
    res.tail(3)
    : e4 a" x8 f$ P3 i1
    : N1 [  d0 P% ?: J0 A- J2# q6 K0 _6 G# ~$ P
    3  d$ x$ L5 L8 q# W) h
    40 q. C5 a7 j) s- t
    5
    0 T% @* \& [& T) g( J' n6& M, ^5 M* c! S& L1 e$ u0 p
    7
    ' G3 @# k3 l2 F; D8 b# 纸质版上介绍了merge_asof,性能差距可以达到3-4个数量级
    / b# D6 ~% S+ e1 u# P' Wtarget = pd.DataFrame(1 z; ?$ ^* Z8 a- F% B% d0 V
        {( E3 }* L5 G0 t. E
            "Time": df.index.shift(freq='-6H'),
    ( J) [- |% T4 p2 T        "Datetime": df.index,
    ! i& T" S) Y: F3 D6 W% H& ^% U    }+ [; p! p2 ~  S' U. c3 u$ l6 y
    )
    & K, h" |; s& \* V/ p# y2 q7 t
    + I! T" b  z- dres = pd.merge_asof(6 K. I& q( d/ `" k* G
        target,
    8 W( R+ T) m+ I: J    df.reset_index().rename(columns={"Datetime": "Time"}),9 O  }: y7 z$ N3 g
        left_on="Time",* H3 s- y7 H9 S* j
        right_on="Time",& _: ]5 [9 e+ @5 n
        direction="nearest"4 y6 z. i8 @6 i% N
    ).set_index("Datetime").Radiation, h0 r4 I( L0 R0 h
    - g, B; N' m+ p! f$ `. P$ `
    res.tail(3)
    7 _  t4 h4 @; s8 \Out[224]: / G/ t' ~0 F3 w
    Datetime# u4 @! ]) b; o  w9 l6 I7 y) j0 R0 i! L
    2016-12-31 23:45:04    9.33/ S6 p: Y' e0 ]* B* i% b
    2016-12-31 23:50:03    8.49' i! s- m8 P/ i" f
    2016-12-31 23:55:01    5.84: I' _: ?/ I% _) m5 \+ r2 P6 a7 V
    Name: Radiation, dtype: float64
    , t4 P) T# C8 c$ K* v+ K) k9 L( [+ A& z
    18 F/ m! i1 x" c1 g2 o: h4 |6 e9 l
    2+ K; f+ H& B2 Y0 Y
    3* z/ A! S" j: [' l7 a3 ^
    4  d2 m) e& {1 K3 n$ I
    59 o, s  O( o% h( J
    6/ D% e& e" S6 L* H0 [; C
    7
    - b- i3 D+ D2 d. Q80 l- c8 |8 F, A1 _1 V6 J: A3 K
    94 Z# U3 {+ H8 y# h& Z' T" _
    10
    ! n% F- D: E# E8 Z3 }11
    5 Z4 l. A! Z7 C# |6 _12
    - P) ], z! `) q13: ]; @2 r* ]  h* g
    14. p# q- I* k. l% L$ o1 o. v
    15% q" |; p3 _3 ^& A0 ]) A5 A
    16$ h  J2 D. H( G) d! _$ H
    17' b$ k7 N+ ]8 Q! j
    18
    * _/ s. }5 A6 q# G  w/ i) r% |/ i2 f! i19
      M, m1 n. P2 Y9 `208 e8 q6 M0 u0 f9 W
    21
    . Z" p* t7 O% B  p; y22
    . W5 J( Y# |& j, u1 y* O3 s23
    5 }5 ?+ o+ M- R! D. t' R. DEx2:水果销量数据集- s" D$ E- F2 p, j
    现有一份2019年每日水果销量记录表:
    & |# ]$ }- n5 ~% |, M' W( }4 A  }4 m2 h# _: }3 J" ?
    df = pd.read_csv('../data/fruit.csv')  m, \4 d; r9 b5 I) D
    df.head(3)5 p9 ]. p7 G0 w9 y

    - ?7 Q' k2 Y7 X. i' ROut[131]: 4 @3 v  t! X" C8 ?6 M
             Date  Fruit  Sale1 v: m6 L% ^/ O
    0  2019-04-18  Peach    15
    3 \5 O" P5 Y& j. M1  2019-12-29  Peach    155 b7 y) A/ `, l
    2  2019-06-05  Peach    195 E% P) J  a* x" I4 B1 o
    1
    " D# d/ ~0 G8 ?+ i0 P' b25 q* B, J- v2 n2 b' {! j
    3* d+ \2 e6 i& p* d# \: T  \3 Z
    4
    % E, S5 u$ i3 \, J  _( ^& |5
    2 F5 H' W! B9 W: H" A6% O7 e( n2 l' b, `% Z  q# J
    7# R$ U1 v$ |, H+ @1 U
    87 c  ~5 m9 i2 w% T
    统计如下指标:: ^* I/ G; W: n3 G4 P# d5 y4 n+ h
    每月上半月(15号及之前)与下半月葡萄销量的比值
    & Z# |! x* G* ~6 t) H" o* ~每月最后一天的生梨销量总和6 r, z: j- v7 i& D$ F- o
    每月最后一天工作日的生梨销量总和
    ) }3 }/ n, {9 ^! F每月最后五天的苹果销量均值
    ; H7 t0 v- a- e0 @$ z按月计算周一至周日各品种水果的平均记录条数,行索引外层为水果名称,内层为月份,列索引为星期。# o3 ]$ t. h( j9 [+ T
    按天计算向前10个工作日窗口的苹果销量均值序列,非工作日的值用上一个工作日的结果填充。) y1 U" S. ~4 |. R3 Z
    import numpy as np6 ?8 k+ e$ Z3 N: `6 q2 G' e; x$ ]
    import pandas as pd) |1 s) X: C+ M  g0 ?/ W# C
    1
    0 `' U* n2 G8 ]3 ^1 f1 M4 |" ^* ~2
    1 G' D$ \5 H6 N0 s统计如下指标:
    3 R" f0 f8 {( x每月上半月(15号及之前)与下半月葡萄销量的比值
    / d, T1 {: i0 Q/ i: \' Y4 ?每月最后一天的生梨销量总和' ^& ?/ n, U5 _. U' i1 W2 x' W
    每月最后一天工作日的生梨销量总和
    4 O+ }2 m- h1 l, |4 W( g+ M# W8 W每月最后五天的苹果销量均值
    / m- P/ p7 J7 u# R4 s# L# t# 每月上半月(15号及之前)与下半月葡萄销量的比值
    & {* ~' Y) |: j5 U. f' d* ydf.Date=pd.to_datetime(df.Date)
    + `  F% {9 o. Z' `8 d! `% Psale=df.query('Fruit == "Grape"').groupby([df.Date.dt.month,df.Date.dt.day<=15])['Sale'].sum()+ p4 W) o. Y2 u5 l+ v  |& ]
    sale.columns=['Month','15Dayes','Sale'] # 为啥这么改没用啊6 s7 F( _+ O& c+ \7 d
    sale=pd.DataFrame(sale)$ ?) r7 m, m4 r; c
    sale=sale.unstack(1).rename_axis(index={'Date':'Month'},: [- e) b+ ^( N' d
                     columns={'Date':'15Days'}).stack(1).reset_index() # unstack主要是两个索引都是Date无法直接重命名5 H: C' u5 Z  }' k
    sale.head() # 每个月上下半月的销量
    " o+ [5 S# Y9 i) _/ T! R4 u4 A1 X  a7 q7 O0 C
      Month        15Days        Sale
    ) W! Z) w- v2 P2 p4 ?" X) T8 d0        1        False        10503
    % m, d! E2 c- C- v1        1        True        12341# Z! I  U8 `% Q/ Q
    2        2        False        10001- q+ X4 _1 \8 {, Q; T# l7 G
    3        2        True        10106" u; v8 U7 w3 ?
    4        3        False        12814
    - Z, X& ?0 I1 G2 g8 [( l8 V
    7 H" H+ j6 T: k7 Q/ p& r# 使用自定义聚合函数,分组后每组就上半月和下半月两个值,根据索引位置判断求比值时的分子分母顺序; I) N7 o6 N! z
    sale.groupby(sale['Month'])['Sale'].agg(3 d" k' F% b- ?: ?5 w$ w  \; `
                    lambda x: x.max()/x.min() if x.idxmax()>x.idxmin()  else x.min()/x.max())
    - g2 j$ p/ D8 c& f: l! i# |- U
    ( e( r6 f5 ^+ t! v% gMonth
    # L' i. |5 u, ]- a6 c1     1.1749986 ]' ]0 K- o9 m5 \
    2     1.010499
    2 t8 C, ~! D" {% i/ z0 t) Z8 J3     0.776338
    : T" E9 s4 Q" Y/ u. T5 p3 V. P4     1.026345- [0 c3 P& B! d3 C8 \- U. P. t
    5     0.900534: t" @+ i5 K/ V& ~* e& w' Z
    6     0.980136
    0 G% u+ o( }( |  S- z7     1.350960/ p/ U1 g5 z) Y; [
    8     1.091584
    6 d3 {, R5 R7 ]: D# T" I9     1.116508
    ; z: d/ O& G& M/ {7 l2 |10    1.020784
    0 t/ R+ K) B& N2 F; o; D- Z- L11    1.275911
    2 O0 d# A: s8 T, f6 i12    0.989662
    , K: H. u1 ~/ d+ c" ~7 RName: Sale, dtype: float64
    ) ^+ Q5 t5 Z4 X! E& Y  J4 S% B/ p2 j9 Z& F: |0 e9 h9 g
    1! a2 \) I3 k8 V* p. u: H- ]
    2; H5 a* g0 U) z" U9 G
    3
    7 A$ Q8 z9 P: N7 N/ `* o7 u! z4: R/ R: s+ g. |8 x* S
    5
    5 c3 h' H0 ?7 V4 M' @* X6' B' {2 ?$ a5 w) ^
    7
    . w4 H5 P8 M4 _$ C- C7 a, _1 g- {83 q7 \/ j& z4 Z; j' f- Q3 z
    9
    ' b# R$ @. h% G. T- G# D10
    2 @6 M0 L3 w' Q% u$ P% x11
    - L% j! u) a# k$ E0 ~: c12
    ! F! y) ?+ r$ A, s- f/ c# t13
      C$ s5 v& Q& K6 e( Y14
    * V9 p  j; ~2 F5 L151 C" m& k. Q0 L# `
    161 o" O2 J4 d+ I$ a- ?
    17
    & ]: E: v- W# `& T; G) F) G18
    - g+ I% Y; _% d. R19
    " {& C( ?$ ~0 S- z2 k- z20
    # w* o+ Z' q$ O7 L21
    : K; @# o# J" q7 I3 Q7 n, K; Y22. l% M1 B8 _" S$ K5 d% _
    23! b; i6 z* ]; [( k
    24
    + ~, C& R* z8 C25, i7 c; `) a6 g/ R' {
    26
    4 m. \1 r  u9 L' h27
    ' g; c. H( s8 L288 H( D$ M  m4 T3 d0 ?* D4 C. k
    29# p7 l0 C! T- F9 k% I+ X0 V
    30# Q/ J: ^8 }. E! q2 r4 h
    31* t- z3 V" `* g& h/ I
    32* Q# b: |9 E& m# Q. H
    33
    " g/ [; W% D9 N5 d4 A34# _: s5 F: {* A! a, Z& y! k
    # 每月最后一天的生梨销量总和- J' [2 f. i. R  S
    df[df.Date.dt.is_month_end].loc[df.Fruit=='Pear'].groupby('Date')['Sale'].sum()
    ) f# m9 ?/ ~  P/ p3 o# [& P+ z1 }+ x' \
    Date2 T% t7 O4 P9 V" A. H9 [# j
    2019-01-31    847
    1 s  ~7 h" @5 {  `4 r$ F4 o2019-02-28    774
    6 d# H4 `7 p( s. X" y' t4 ?2019-03-31    761# C& m& S" `0 q+ A; v7 D( Q
    2019-04-30    648
    $ U4 h4 j! E. ], V7 A2019-05-31    616
    9 [! `9 `1 Z2 _* r# s1
      {6 @+ u6 W, X- m; }- f2
    6 n3 j4 f8 F) i  P  U1 y  h3' E( j* X' Z2 O8 j* F
    4
    1 z- t" j) _" A: \, M/ O5
    % A* j3 t# ~* C' S0 ]2 ~1 V6! k7 {& b) F, Q
    7" }. ^" Y* J2 h! ~! n
    84 j5 b; o! A$ w- Q
    90 {5 }. c. M/ p. w9 `
    # 每月最后一天工作日的生梨销量总和
    ) F( Z! x: O, G/ X* i. Els=df.Date+pd.offsets.BMonthEnd()0 U& V6 {. F0 }# g6 n! [
    my_filter=pd.to_datetime(ls.unique())4 p' `, @* D) S1 y5 {4 b
    df[df.Date.isin(my_filter)].loc[df.Fruit=='Pear'].groupby('Date')['Sale'].sum()
    / }  Z' Y( B$ U1 H8 U( ]! V' e" Y6 L# z
    Date
    5 o  a& ?& o0 j& x$ F3 `# k2019-01-31     847: f: D2 P- [. ^3 B' p" b" o
    2019-02-28     774
    5 q, ^! q  C: I2019-03-29     510
      g3 [2 ?4 |7 c2 a2 b: e2019-04-30     648
    $ {2 X7 ]  [  p, V( M' j1 U0 O2019-05-31     6162 Y1 ~& r3 A% c: A; u+ e
    1
    ' B/ r/ U0 c2 K7 H0 V5 _9 u$ J2
    # f; t& d% W) ^: q  l3
    & ^. r3 m% P  i) `0 Z4
    ! v, d/ A( t9 y# w; \4 @8 h5# F$ E9 m4 Y0 p+ o/ _
    6% n4 V/ s9 i$ \5 S% y3 |
    79 f( O4 S+ T/ k: D- t
    8* Q9 Q7 j5 Z+ R: `. C9 z
    9
    ) U; @; G. c. J10. u1 D5 F9 A: _; e& c
    11! Z( ~4 D4 S: ?" Y; J8 z" R
    # 每月最后五天的苹果销量均值. \3 K' f( A: v' }0 Q/ e1 S: K9 ?
    start, end = '2019-01-01', '2019-12-31'
    5 L# l' d* I# kend = pd.date_range(start, end, freq='M'): W" _* U* G* f1 R4 N( n
    end=end.repeat(5) # 每月最后一天的日期列表,重复5次方便做差0 g. f- U1 i, L/ A; q% ~

    1 }; c5 S3 F8 \td= pd.Series(pd.timedelta_range(start='0 days', periods=5),), C+ ?. \' u, e
    td=pd.concat([td]*12) # 日期偏置,最后一天减去0-4天
    % Y( \1 D1 X. V+ ]% R9 Q6 C. [end5=(end-td).reset_index(drop=True) # 每个月最后5天的列表
    9 U# c1 G4 ?( M$ ~# h4 ~1 Z4 }) {
    7 W6 [4 m8 ~8 a) f0 M* \; n/ lapple5=df[df.Date.isin(end5)].query("Fruit == 'Apple'") # 每月最后五天苹果销量
    5 Z5 b& S0 ^$ Fapple5.groupby(apple5.Date.dt.month)['Sale'].mean().head()0 i% M' D3 p1 r& [1 E7 r
    4 p2 ]' f8 N  Q: X( z3 ~8 o
    Date1 N& h  r. ]6 X/ r6 E* W! m
    1     65.313725
    ) m( |- ?( O; H3 l2     54.061538
    $ f/ p/ {* N+ p) C; s9 k# f, A3     59.325581
    % I3 ]  `- t7 r0 ]6 x4     65.795455
    4 F+ M. Q, i/ Q5 _5     57.465116
    0 F  z! B) V) ?+ e' i# r/ N
      s: E' K# J4 ^- b% C# A( A' c! t0 P1* E2 M! {; C6 b, p- |
    2" X  }% J/ ~. R) T- |3 @
    3% x' d/ q. v. p! Z
    4+ E: n$ R/ x4 s
    5/ }  B7 a& {3 r6 S
    6
    ) N! [0 l3 @/ e7
    ; S) z0 i4 P9 y4 S4 i8
    * U: Z$ M& Z/ U9
    ( a* k* F9 @: m8 r10
    4 C: Z& k) y+ M- ~" S2 M7 E115 D, f7 k1 L8 C' {: L& D- A
    12* k; Q4 a8 I, e+ m5 G- J, ?
    130 c$ M8 O. N# p$ M
    14$ M" j% b' n2 F5 d8 [
    15
    9 f6 v+ |# o* i1 M( s' g/ _16% i) }( z, w" a; v  ~
    17
    & l  z0 g& I7 N+ m- q189 A7 R! w: l( l$ x
    # 参考答案:
    ! t8 y; H" b' x/ q; N" Atarget_dt = df.drop_duplicates().groupby(df.Date.drop_duplicates(
    + o5 P! h2 \+ \  [            ).dt.month)['Date'].nlargest(5).reset_index(drop=True)" M  f/ j7 r+ j: I+ ~0 G3 j/ ~; u

    6 Q2 u+ y, b# mres = df.set_index('Date').loc[target_dt].reset_index(
    7 Y% D& S1 q9 \            ).query("Fruit == 'Apple'")$ p" |9 ?7 {) F) _

    $ _1 t7 O+ {7 s5 ?- Kres = res.groupby(res.Date.dt.month)['Sale'].mean(; G9 p' k+ E0 g- Z' s
                ).rename_axis('Month')
    3 Y) C6 @" Y9 }( r
    5 s4 G5 _. C# {0 G+ E6 N7 P1 L; q% k" b$ E
    res.head()3 h* U0 i- m" h7 \7 [# a' x6 ^% S
    Out[236]: , G+ M0 u8 S# W, M( P( {
    Month2 \3 @8 U' {: c6 }+ K" W9 O
    1    65.313725
    + O% A- z+ g; y9 n4 }6 r2    54.0615382 l! a+ P- l; Y/ @
    3    59.325581
    6 G# W% v* z+ w4 Z3 I* T; J' W4    65.795455
    8 M4 t1 Y0 i" r5    57.465116& s! G3 P" q( b( c% P
    Name: Sale, dtype: float64
    0 J6 B2 F- \8 l. r. s3 Z# K7 X! W* x# {3 }$ W5 c2 F! L
    1
    4 p  A! ]7 e1 m3 L25 v9 I0 M9 W  x% s9 ?
    3
    ! y, O. Z( A% c2 j$ L4; e) R/ W9 o" k- G
    5
    : R1 c% _) @' y5 ~2 M5 j6
    . j% N+ F) r! i. m* {: O& `7) y, c0 S% I# V/ v8 S7 ?
    8
    ( ^4 Z, h9 H1 q( ?" [: o3 ^3 }. T9+ B' _' D& k8 K# y  r+ c- ], z" X, Q8 Z% k
    104 N& b" o5 A5 \6 q) }. i/ Z
    110 R  U( D3 Q: w
    12/ Q$ E2 p4 m$ R" N
    13
    * i  k* e. B  ^. l' t14( P* u( N- J5 Q: A8 t+ e
    15& I+ y, E1 R3 a; o  t
    16
    : |: _  p: z/ ]& ~( d0 R175 T9 i5 f/ k$ A6 n/ n9 `: P
    18
    " T7 X9 y; E5 N) [1 l/ J/ Y) {19
    ' R: ?" `7 u3 [) a8 p200 W3 z& v; z. J4 c, ^7 B6 o4 D! o
    按月计算周一至周日各品种水果的平均记录条数,行索引外层为水果名称,内层为月份,列索引为星期。
    # w" j/ ?* }# i% Uresult=pd.DataFrame(df.groupby([df.Date.dt.month,df.Date.3 u& ^2 ^+ B3 M
                                            dt.dayofweek,df.Fruit])['Sale'].count()) # 分组统计 : J6 }/ G. L( Y0 [) q
                                            5 k, I- y. O" u! ]
    result=result.unstack(1).rename_axis(index={'Date':'Month'},
    ' F/ [0 P# `8 f' t                 columns={'Date':'Week'})  # 两个index名字都是Date,只能转一个到列,分开来改名字.
    + j- o8 N* S/ B# Yresult=result.swaplevel(0,1,axis=0).droplevel(0,axis=1)
    % i& g  ^1 B0 q; V! s2 d  T3 U0 Presult.head() # 索引名有空再改吧
    4 F8 E) O' }0 ^# M; V  a/ V  ~+ N
    ; G: E/ t: g6 Z# k7 ]; ]( _          Week        0        1        2        3        4        5        6
    + Q, o- Z0 p- vFruit Month                                                       
    5 k3 `9 ~# g% @2 ~" FApple        1        46        50        50        45        32        42        233 q9 H! ^  Q. o
    Banana        1        27        29        24        42        36        24        35
    $ o% J( f% q( _  l. F& p7 U. B" WGrape        1        42        75        53        63        36        57        46
    " {2 K9 g* D& h# H# gPeach        1        67        78        73        88        59        49        72
    . |( X/ ~" N. @8 F) W& A. E/ l$ S- RPear        1        39        69        51        54        48        36        40
    $ F6 y  @. U; n& ^1 K' ]/ k1
    2 a- O+ E. Z% ~* M5 z1 h& d+ G1 r2& V( k$ c3 I2 o" j
    3
    0 ]4 {' W8 A+ x+ l/ J# r/ [4
    2 ?% i8 r+ Y# S) A7 ?% y5
    1 O1 j# M+ ~- w6
    6 b& v9 F' K, |, T7
    0 V- N' Z8 l8 v+ ]% C8 r  m8
    ' i7 G4 ]. c& `! R0 @4 b9
    ' Z5 F. O2 i9 M: b7 s10
    , U, H, i$ E  q( ?11
    9 p* i! Y5 _; p/ j4 G12
    : s0 v5 r  X* h+ A0 M" Y13" d" J  E) `( U% F* s; b
    142 A7 Z1 I& c1 L! l2 q* M% G" z
    155 e- V  {9 k; Q' x  p0 z
    按天计算向前10个工作日窗口的苹果销量均值序列,非工作日的值用上一个工作日的结果填充。
    ' \, Q, m5 L/ w( y# V; S0 l. ]9 x# 工作日苹果销量按日期排序5 Z2 b  l' j( {4 H, Y+ ]" A5 j* k, f2 S
    select_bday=df[~df.Date.dt.dayofweek.isin([5,6])].query('Fruit=="Apple"').set_index('Date').sort_index()
    / \+ @1 L+ T+ q) b1 c  Tselect_bday=select_bday.groupby(select_bday.index)['Sale'].sum() # 每天的销量汇总- \% t  m% X" W- l( t% j
    select_bday.head()
    $ w# F& w7 a, |' G* t/ L& {( H1 D, F6 a& l6 `1 ]8 \
    Date
    8 W2 ~3 t$ u6 [2019-01-01    189
    . K# t; m$ b1 z1 G2 V' B2019-01-02    482
    7 F0 u% b4 ~% {* a2019-01-03    890+ {4 j5 ^4 A$ M& _3 U! C
    2019-01-04    550
    , l0 p2 q6 \# s2019-01-07    494+ o; T7 y: ?* ^/ \9 ~

    " [) K7 m! B- M1 H# 此时已经是工作日,正常滑窗。结果重设索引,对周末进行向后填充。
    ! y2 K9 S3 ~! k! ^& k5 Fselect_bday.rolling('10D').mean().reindex(df.Date.unique()).sort_index().ffill().head()# Z( m" d8 ^! y, y" k

    ; v1 r( {( k! h5 ^Date
    ) o: X0 E2 T/ \  }) M  C& t% r( u2019-01-01    189.0000007 }/ A/ g) b% ]7 Y9 A* A4 ^* B
    2019-01-02    335.5000003 n7 d+ ~- z5 M) b6 Z: E
    2019-01-03    520.333333; e' @1 ^8 V! b5 m, K
    2019-01-04    527.750000
    ; f3 m& U* W9 O2 k* M' P2019-01-05    527.750000
    + M; ?! {4 Y) ~0 V% C% M* D3 k! p( }4 u! p6 Z2 U) T
    ————————————————
    8 [( t% k. q; f: k) Q* f: A2 L( [. j7 W# e版权声明:本文为CSDN博主「神洛华」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。# [  [( c) s0 W* u8 G; r
    原文链接:https://blog.csdn.net/qq_56591814/article/details/126633913
    / Y5 T3 J* N  i$ U$ V" t! X4 ~+ z* L; j' K0 C# g9 L- S+ X! O( {
      l- P) q( _& [8 g+ o/ A
    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, 2025-8-8 20:16 , Processed in 0.611405 second(s), 51 queries .

    回顶部