【读书笔记】《利用Python进行数据分析》第2版_第七章 数据清洗与准备

讨论用于缺失值、重复值、字符串操作和其他分析数据转换的工具

7.1 处理缺失值

pandas对象的所有描述性统计信息默认情况下是排除缺失值的

  • 对于数值型数据,pandas使用浮点值NaN(Not a Number来表示缺失值)

    • NA(not available,不可用)
    • Python内建的None值在对象数组中也被当作NA处理
  • NA处理方法

    epub_22739904_91

过滤缺失值

  • 过滤缺失值方法

    • 使用pandas.isnull和布尔值索引

    • 使用dropna

      • 在Series上使用,会返回Series中所有的非空数据及其索引值

        import pandas as pd
        from numpy import nan as NA
        data = pd.Series([1,NA,3.5,NA,7])
        data.dropna()
        

        等价表示:data[data.notnull()]

      • 处理DataFrame对象时,dropna默认删除包含缺失值的

        • 传入how='all’时,将删除所有值均为NA的行

        • 删除列,传入参数axis=1

          data.dropna(axis=1,how='all')
          
        • 使用thresh参数保留一定数量的观察值的行

          df.dropna(thresh=2)
          

补全缺失值

  • 使用fillna方法来补全缺失值

    • 可以使用一个常数来代替缺失值:df.fillna(0)
    • 调用时使用字典来为不同设定不同的填充值:df.fillna({1:0,2:0})
    • fillna返回一个新对象,也可以修改意见存在的对象:df.fillna(0,inplace=True)
    • 用于重建索引的相同的插值方法也可以用于fillna:df.fillna(method=‘ffill’,limit=2)
    • 使用fillna完成创造性填充:data.fillna(data.mean())
  • fillna函数参数

    img

7.2 数据转换

删除重复值

  • 有些数据会出现重复行

    • DataFrame的duplicated方法返回一个布尔值的Series,反映每一行是否与之前出现过相同的行。

      data = pd.DataFrame({'k1': ['one', 'two'] * 3 + ['two'],
                           'k2': [1, 1, 2, 3, 3, 4, 4]})
      data
      """
      	k1	k2
      0	one	1
      1	two	1
      2	one	2
      3	two	3
      4	one	3
      5	two	4
      6	two	4
      """
      data.duplicated()
      """
      0    False
      1    False
      2    False
      3    False
      4    False
      5    False
      6     True
      dtype: bool
      """
      
    • drop_duplicates返回duplicated返回数组中为False的部分,格式为DataFrame

      data.drop_duplicates()
      """
      	k1	k2
      0	one	1
      1	two	1
      2	one	2
      3	two	3
      4	one	3
      5	two	4
      """
      
    • 方法默认针对,可以指定数据的子集来检测是否有重复

      # 假设有一个额外的列,并想基于’k1’列去除重复值
      data['v1'] = range(7)
      data.drop_duplicates(['k1'])
      """
      k1	k2	v1
      0	one	1	0
      1	two	1	1
      """
      
    • 【注意】:uplicated和drop_duplicates默认都是保留第一个观测到的值。传入参数keep='last’将会返回最后一个

使用函数或映射进行数据转换

基于DataFrame中的数组、列或列中的数值进行一些转换

map是一种可以便捷执行按元素转换及其他清洗相关操作的方法

收集到的基于肉类的假设数据

data = pd.DataFrame({'food': ['bacon', 'pulled pork', 'bacon',
                              'Pastrami', 'corned beef', 'Bacon',
                              'pastrami', 'honey ham', 'nova lox'],
                     'ounces': [4, 3, 12, 6, 7.5, 8, 3, 5, 6]})
image-20220329161532043
  • 添加一列用于表明每种食物的动物肉类型,先写下一个食物和肉类的映射

    meat_to_animal = {
      'bacon': 'pig',
      'pulled pork': 'pig',
      'pastrami': 'cow',
      'corned beef': 'cow',
      'honey ham': 'pig',
      'nova lox': 'salmon'
    }
    
  • 使用Series的str.lower方法将每个值都转换为小写

    lowercased = data['food'].str.lower()
    
  • Series的map方法接收一个函数一个包含映射关系字典型对象

    data['animal'] = lowercased.map(meat_to_animal)
    data
    

总结:传入一个能够完成所有工作的函数

data['food'].map(lambda x: meat_to_animal[x.lower()])

替代值

  • 使用replace方法更灵活地替代缺失值:data.replace(-999,np.nan)
  • 一次替代多个值,传入一个列表和一个替代值:data.replace([-999,-1000],np.nan)
  • 将不同的值替换为不同的值,可以传入替代值的列表:data.replace([-999, -1000], [np.nan, 0]);也可通过字典传递:data.replace({-999: np.nan, -1000: 0})

data.replace方法与data.str.replace方法是不同的

data.str. replace是对字符串进行按元素替代的

重命名轴索引

数据

image-20220329163415871
  • 修改轴

    transform = lambda x : x[:4].upper() 
    data.index.map(transform)
    # Index(['OHIO', 'COLO', 'NEW '], dtype='object')
    # 赋值给index,修改DataFrame
    data.index = data.index.map(transform)
    data
    
    image-20220329163828079
  • rename:创建数据集转换后的版本,并且不修改原有的数据集

    data.rename(index=str.title,columns=str.upper)
    # rename可以结合字典型对象使用,为轴标签的子集提供新的值
    data.rename(index={'OHIO':'INDIANA'},
                columns={'three':'peekaboo'})
    

    想要修改原有的数据,传入inplace=True

离散化和分箱

连续值经常需要离散化,或者分离成”箱子“进行分析

假设研究一组人群的数据,将他们进行分组,放入离散的年龄框中

ages = [20, 22, 25, 27, 21, 23, 37, 31, 61, 45, 41, 32]
  • 使用pandas中的cut将这些年龄分为18~25、26~35、36~60以及61及以上等若干组

    bins = [18,25,35,60,100]
    cats = pd.cut(ages,bins)
    cats # 特殊的Categorical对象
    

    返回值:一个特殊的Categorical对象,可当作表示箱名字的字符串数组,在内部包含一个categories(类别)数组,指定了不同的类别名称以及codes属性的ages(年龄)数据标签

    cats.codes
    # array([0, 0, 0, 1, 0, 0, 2, 1, 3, 2, 2, 1], dtype=int8)
    cats.categories
    # IntervalIndex([(18, 25], (25, 35], (35, 60], (60, 100]], dtype='interval[int64, right]')
    pd.value_counts(cats) # 对pandas.cut的结果中的箱数量的计数
    
  • 小括号表示边开放,中括号表示封闭;通过传递right=False来改变哪一边是封闭的

    pd.cut(ages,[18,26,36,61,100],right=False)
    
  • 向labels选项传递一个列表或数组来传入自定义的箱名

    group_names = ['Youth', 'YoungAdult', 'MiddleAged', 'Senior']
    pd.cut(ages, bins, labels=group_names)
    """
    [[18, 26), [18, 26), [18, 26), [26, 36), [18, 26), ..., [26, 36), [61, 100), [36, 61), [36, 61), [26, 36)]
    Length: 12
    Categories (4, interval[int64, left]): [[18, 26) < [26, 36) < [36, 61) < [61, 100)]
    """
    
  • 传给cut整数个的箱来代替显示的箱边,自动根据数据最值计算等长的箱

    # 均匀分布的数据被切成四份
    data = np.random.rand(20)
    pd.cut(data,4,precision=2) # precision=2将十进制精度限制在两位
    
  • qcut基于样本分位数进行分箱,可以使用qcut获得等长的箱

    data = np.random.randn(1000)  # Normally distributed
    cats = pd.qcut(data, 4)  # Cut into quartiles
    cats
    pd.value_counts(cats) # 每个箱数量都是250
    

    可以传入自定义的分位数(0和1之间的数据,包括边)

    pd.qcut(data, [0, 0.1, 0.5, 0.9, 1.])
    

检测和过滤异常值

  • 举例

    # 具有正态分布数据的DataFrame
    data = pd.DataFrame(np.random.randn(1000,4))
    # 假设你想要找出一列中绝对值大于三的值
    col = data[2]
    col[np.abs(col) > 3]
    # 选出所有值大于3或小于-3的行,对布尔值DataFrame使用any方法
    data[(np.abs(data)>3).any(1)]
    # 限制-3到3之间的数值
    data[np.abs(data)>3] = np.sign(data) * 3
    data.describe()
    
  • np.sign(data)根据数据中的值的正负分别生成1和-1的数值:np.sign(data).head()

    image-20220329185902275

置换和随机抽样

使用numpy.random.permutation对DataFrame中的Series或行进行置换(随机重排序)是非常方便的

  • 调用permutation时可根据轴长度产生一个新顺序的整数数组

    df = pd.DataFrame(np.arange(5*4).reshape((5,4)))
    sampler = np.random.permutation(5)
    sampler # array([1, 4, 3, 2, 0])
    # 整数数组可以用在基于iloc的索引或等价的take函数中
    df.take(sampler)
    
  • 选出不含有替代值的随机子集,可以使用sample方法

    df.sample(n=3)
    # 生成带有替代值的样本(允许有重复选择),将replace=True传入sample方法
    choices = pd.Series([5,7,-1,6,4])
    draws = choices.sample(n=10,replace=True)
    draws
    

计算指标、虚拟变量

将分类变量转换为“虚拟”或“指标”矩阵

  • 如果DataFrame中的一列有k个不同的值,则可以衍生一个k列的值为1和0的矩阵或DataFrame(pandas有一个get_dummies函数用于实现该功能)

    df = pd.DataFrame({'key': ['b', 'b', 'a', 'c', 'a', 'b'],
                       'data1': range(6)})
    pd.get_dummies(df['key'])
    
    # 在指标DataFrame的列上加入前缀,然后与其他数据合并
    dummies = pd.get_dummies(df['key'], prefix='key')
    df_with_dummy = df[['data1']].join(dummies)
    df_with_dummy
    

    image-20220329194222196image-20220329194345505

  • 多成员构建指标变量

    • DataFrame中的一行属于多个类别,则情况略为复杂

      • 每个电影流派添加指标变量

        image-20220329202000815
      • 取出所有不同的流派的列表

        all_genres = []
        for x in movies.genres:
            all_genres.extend(x.split('|'))
        genres = pd.unique(all_genres)
        genres
        
      • 用全0的DataFrame构建指标

        zero_matrix = np.zeros((len(movies),len(genres)))
        dummies = pd.DataFrame(zero_matrix,columns=genres)
        # 遍历每一部电影,将dummies每一行的条目设置为1
        # 使用dummies.columns来计算每一个流派的列指标
        gen = movies.genres[0]
        gen.split('|')
        dummies.columns.get_indexer(gen.split('|'))
        # array([0, 1, 2], dtype=int64)
        for i,gen in enumerate(movies.genres):
            indices = dummies.columns.get_indexer(gen.split('|'))
            dummies.iloc[i,indices] = 1
        # 将结果与movies进行联合
        movies_windic = movies.join(dummies.add_prefix('Genre_'))
        movies_windic.iloc[0]
        
        image-20220329203834522
    • 对于大数据的快速方法

      # 直接将数据写为NumPy数组的底层函数,然后将结果封装进DataFrame
      # get_dummies与cut等离散化函数结合使用
      np.random.seed(12345) # 置随机种子以确保示例的确定性
      values = np.random.rand(10)
      values
      # array([0.92961609, 0.31637555, 0.18391881, 0.20456028, 0.56772503,
      #       0.5955447 , 0.96451452, 0.6531771 , 0.74890664, 0.65356987])
      bins = [0, 0.2, 0.4, 0.6, 0.8, 1]
      pd.get_dummies(pd.cut(values, bins))
      
      image-20220329195456052

7.3 字符串操作

pandas允许你将字符串和正则表达式简洁地应用到整个数组上

还能处理缺失值带来的困扰

字符串对象方法

  • 一个逗号分隔的字符串可以使用split方法拆分成多块

    val = 'a,b, guido'
    val.split(',') #val = 'a,b, guido'a
    

    split常和strip一起使用,用于清除空格(包括换行)

    val = 'a,b, guido'
    val.split(',') #val = 'a,b, guido'a
    pieces = [x.strip() for x in val.split(',')]
    pieces # ['a', 'b', 'guido']
    # 子字符串可以使用加法与两个冒号分隔符连接在一起
    first ,second , third=pieces
    first+'::'+second+'::'+third # 'a::b::guido'
    # 更通用方法
    '::'.join(pieces)
    
  • 尽管index和find也可,Python的in关键字是检测子字符串的最佳方法

    index在字符串没有找到时会抛出一个异常(而find是返回-1)

    'guido' in val # True
    val.index(',') # 1
    val.find(':') # -1
    
  • count返回子字符串出现的次数:val.count(‘,’)

  • replace将用一种模式替代另一种模式。它通常也用于传入空字符串来删除某个模式

    val.replace(',','::') # 'a::b:: guido'
    val.replace(',',' ')  # 'a b  guido'
    
  • Python内建字符串方法

    img

正则表达式

一种在文本中灵活查找或匹配(通常更为复杂的)字符串模式的方法。单个表达式通常被称为regex,是根据正则表达式语言形成的字符串.

  • Python的re模块是将正则表达式应用到字符串上的库

    • re模块的三个主题

      • 模式匹配
      • 替代
      • 拆分
    • 描述一个或多个空白字符的正则表达式是**\s+**

      # # 将含有多种空白字符(制表符、空格、换行符)的字符串拆分开
      import re
      text = "foo    bar\t baz  \tqux"
      re.split('\s+', text) # ['foo', 'bar', 'baz', 'qux']
      
    • re.compile自行编译,形成一个可复用的正则表达式对象

      regex = re.compile('\s+')
      regex.split(text) # ['foo', 'bar', 'baz', 'qux']
      
    • 使用findall获得所有匹配正则表达式模式的列表

      regex.findall(text)
      # ['    ', '\t ', '  \t']
      
  • 在正则表达式中避免转义符\的影响,可以使用原生字符串语法,比如r'C:\x'或者用等价的’C:\\x'

  • 使用re.compile创建一个正则表达式对象,可以将相同的表达式应用到多个字符串上,这样有利于节约CPU周期

  • match、search、findall

    • findall返回字符串中所有匹配项

    • search仅返回第一个匹配项

    • match更为严格,只在字符串的起始位置进行匹配

    • 例子:识别大部分电子邮件地址的正则表达式

      text = """Dave dave@google.com
      Steve steve@gmail.com
      Rob rob@gmail.com
      Ryan ryan@yahoo.com
      """
      pattern = r'[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,4}'
      
      # re.IGNORECASE makes the regex case-insensitive
      regex = re.compile(pattern, flags=re.IGNORECASE)
      
      # 使用findall会生成一个电子邮件地址的列表
      regex.findall(text)
      # ['dave@google.com', 'steve@gmail.com', 'rob@gmail.com', 'ryan@yahoo.com']
      
      # search返回第一个匹配到的电子邮件地址
      m =regex.search(text)
      m # <re.Match object; span=(5, 20), match='dave@google.com'>
      # 匹配对象只能告诉我们模式在字符串中起始和结束的位置
      text[m.start():m.end()] # 'dave@google.com'
      
      # regex.match只在模式出现于字符串起始位置时进行匹配,如果没有匹配到,返回None
      print(regex.match(text)) # None
      
  • sub会返回一个新的字符串,原字符串会被新的字符串替代

    print(regex.sub('REDACTED',text))
    """
    Dave REDACTED
    Steve REDACTED
    Rob REDACTED
    Ryan REDACTED
    """
    
  • 将电子邮件地址分为三个部分:用户名、域名和域名后缀,可以用括号将模式包起来

    pattern = r'([A-Z0-9._%+-]+)@([A-Z0-9.-]+)\.([A-Z]{2,4})'
    regex = re.compile(pattern, flags=re.IGNORECASE)
    # 使用修改后的对象的groups方法,返回的是模式组件的元组
    m = regex.match('wesm@bright.net')
    m.groups() # ('wesm', 'bright', 'net')
    
  • 模式可以分组时,findall返回的是包含元组的列表

    regex.findall(text)
    """
    [('dave', 'google', 'com'),
     ('steve', 'gmail', 'com'),
     ('rob', 'gmail', 'com'),
     ('ryan', 'yahoo', 'com')]
    """
    
  • sub也可以使用特殊符号,如\1和\2,访问每个匹配对象中的分组

    print(regex.sub(r'Username: \1,Domain: \2,Suffix: \3',text))
    """
    Dave Username: dave,Domain: google,Suffix: com
    Steve Username: steve,Domain: gmail,Suffix: com
    Rob Username: rob,Domain: gmail,Suffix: com
    Ryan Username: ryan,Domain: yahoo,Suffix: com
    """
    
  • 正则表达式方法

    image-20220329225833993

pandas中的向量化字符串函数

  • 包含字符串的列有时会含有缺失数据

    image-20220329224259419

    可以使用data.map将字符串和有效的正则表达式方法(以lambda或其他函数的方式传递)应用到每个值上,但是在NA(null)值上会失败

  • Series有面向数组的方法用于跳过NA值的字符串操作。这些方法通过Series的str属性进行调用

    • str.contains来检查每个电子邮件地址是否含有’gmail’:data.str.contains(‘gmail’)
  • 正则表达式也可以结合任意的re模块选项使用,例如IGNORECASE

    pattern
    data.str.findall(pattern, flags=re.IGNORECASE)
    """
    Dave     [(dave, google, com)]
    Steve    [(steve, gmail, com)]
    Rob        [(rob, gmail, com)]
    Wes                        NaN
    dtype: object
    """
    
  • 进行向量化元素检查

    • 使用str.get或在str属性内部索引

      matches = data.str.match(pattern,flags=re.IGNORECASE)
      
    • 要访问嵌入式列表中的元素,将索引传递给这些函数中的任意一个

      matches.str.get(1)
      matches.str[0]
      
    • 使用字符串切片的类似语法进行向量化切片

      data.str[5]
      """
      Dave     dave@
      Steve    steve
      Rob      rob@g
      Wes        NaN
      dtype: object
      """
      
  • 部分向量化字符串方法列表

    img
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值