数据科学的原理与技巧 三、处理表格数据

  1. 云栖社区>
  2. 博客>
  3. 正文

数据科学的原理与技巧 三、处理表格数据

apachecn_飞龙 2018-06-05 17:33:05 浏览1001
展开阅读全文

三、处理表格数据

原文:DS-100/textbook/notebooks/ch03

译者:飞龙

协议:CC BY-NC-SA 4.0

自豪地采用谷歌翻译

索引、切片和排序

起步

在本章的每一节中,我们将使用第一章中的婴儿名称数据集。我们将提出一个问题,将问题分解为大体步骤,然后使用pandas DataFrame将每个步骤转换为 Python 代码。 我们从导入pandas开始:

# pd is a common shorthand for pandas
import pandas as pd

现在我们可以使用pd.read_csv读取数据。

baby = pd.read_csv('babynames.csv')
baby
Name Sex Count Year
0 Mary F 9217
1 Anna F 3860
2 Emma F 2587
1891891 Verna M 5
1891892 Winnie M 5
1891893 Winthrop M 5

1891894 行 × 4 列

请注意,为了使上述代码正常工作,babynames.csv文件必须位于这个笔记本的相同目录中。 通过在笔记本单元格中运行ls,我们可以检查当前文件夹中的文件:

ls
# babynames.csv                  indexes_slicing_sorting.ipynb

当我们使用熊猫来读取数据时,我们得到一个DataFrameDataFrame是一个表格数据结构,其中每列都有标签(这里是'Name', 'Sex', 'Count', 'Year'),并且每一行都有标签(这里是0,1,2, ..., 1891893)。 然而,Data8 中引入的表格仅包含列标签。

DataFrame的标签称为DataFrame的索引,并使许多数据操作更容易。

索引、切片和排序

让我们使用pandas来回答以下问题:

2016 年的五个最受欢迎的婴儿名字是?

拆分问题

我们可以将这个问题分解成以下更简单的表格操作:

  • 分割出 2016 年的行。
  • 按照计数对行降序排序。

现在,我们可以在pandas中表达这些步骤。

使用.loc切片

为了选择DataFrame的子集,我们使用.loc切片语法。 第一个参数是行标签,第二个参数是列标签:

baby
Name Sex Count Year
0 Mary F 9217
1 Anna F 3860
2 Emma F 2587
1891891 Verna M 5
1891892 Winnie M 5
1891893 Winthrop M 5

1891894 行 × 4 列

baby.loc[1, 'Name'] # Row labeled 1, Column labeled 'Name'
# 'Anna'

要分割出多行或多列,我们可以使用:。 请注意.loc切片是包容性的,与 Python 的切片不同。

# Get rows 1 through 5, columns Name through Count inclusive
baby.loc[1:5, 'Name':'Count']
Name Sex Count
1 Anna F
2 Emma F
3 Elizabeth F
4 Minnie F
5 Margaret F

我们通常需要DataFrame中的单个列:

baby.loc[:, 'Year']
'''
0          1884
1          1884
2          1884
           ... 
1891891    1883
1891892    1883
1891893    1883
Name: Year, Length: 1891894, dtype: int64
'''

请注意,当我们选择一列时,我们会得到一个pandas序列。 序列就像一维 NumPy 数组,因为我们可以一次在所有元素上执行算术运算。

baby.loc[:, 'Year'] * 2
'''
0          3768
1          3768
2          3768
           ... 
1891891    3766
1891892    3766
1891893    3766
Name: Year, Length: 1891894, dtype: int64
'''

为了选择特定的列,我们可以将列表传递给.loc切片:

# This is a DataFrame again
baby.loc[:, ['Name', 'Year']]
Name Year
0 Mary
1 Anna
2 Emma
1891891 Verna
1891892 Winnie
1891893 Winthrop

1891894 行 × 2 列

选择列很常见,所以存在简写。

# Shorthand for baby.loc[:, 'Name']
baby['Name']
'''
0              Mary
1              Anna
2              Emma
             ...   
1891891       Verna
1891892      Winnie
1891893    Winthrop
Name: Name, Length: 1891894, dtype: object
'''
# Shorthand for baby.loc[:, ['Name', 'Count']]
baby[['Name', 'Count']]
Name Count
0 Mary
1 Anna
2 Emma
1891891 Verna
1891892 Winnie
1891893 Winthrop

1891894 行 × 2 列

使用谓词对行切片

为了分割出 2016 年的行,我们将首先创建一个序列,其中每个想要保留的行为True,每个想要删除的行为False。 这很简单,因为序列上的数学和布尔运算符,应用于序列中的每个元素。

# Series of years
baby['Year']
'''
0          1884
1          1884
2          1884
           ... 
1891891    1883
1891892    1883
1891893    1883
Name: Year, Length: 1891894, dtype: int64
'''
# Compare each year with 2016
baby['Year'] == 2016
'''
0          False
1          False
2          False
           ...  
1891891    False
1891892    False
1891893    False
Name: Year, Length: 1891894, dtype: bool
'''

一旦我们有了这个TrueFalse的序列,我们就可以将它传递给.loc

# We are slicing rows, so the boolean Series goes in the first
# argument to .loc
baby_2016 = baby.loc[baby['Year'] == 2016, :]
baby_2016
Name Sex Count Year
1850880 Emma F 19414
1850881 Olivia F 19246
1850882 Ava F 16237
1883745 Zyahir M 5
1883746 Zyel M 5
1883747 Zylyn M 5

32868 行 × 4 列

对行排序

下一步是按'Count'对行降序排序。 我们可以使用sort_values()函数。

sorted_2016 = baby_2016.sort_values('Count', ascending=False)
sorted_2016
Name Sex Count Year
1850880 Emma F 19414
1850881 Olivia F 19246
1869637 Noah M 19015
1868752 Mikaelyn F 5
1868751 Miette F 5
1883747 Zylyn M 5

32868 行 × 4 列

最后,我们将使用.iloc分割出DataFrame的前五行。 .iloc的工作方式类似.loc,但接受数字索引而不是标签。 它的切片中没有包含右边界,就像 Python 的列表切片。

# Get the value in the zeroth row, zeroth column
sorted_2016.iloc[0, 0]
# Get the first five rows
sorted_2016.iloc[0:5]
Name Sex Count Year
1850880 Emma F 19414
1850881 Olivia F 19246
1869637 Noah M 19015
1869638 Liam M 18138
1850882 Ava F 16237

总结

我们现在拥有了 2016 年的五个最受欢迎的婴儿名称,并且学会了在pandas中表达以下操作:

操作 pandas
读取 CSV 文件 pd.read_csv()
使用标签或索引来切片 .loc.iloc
使用谓词对行切片 .loc中使用布尔值的序列
对行排序 .sort_values()

分组和透视

在本节中,我们将回答这个问题:

每年最受欢迎的男性和女性名称是什么?

这里再次展示了婴儿名称数据集:

baby = pd.read_csv('babynames.csv')
baby.head()
# the .head() method outputs the first five rows of the DataFrame
Name Sex Count Year
0 Mary F 9217
1 Anna F 3860
2 Emma F 2587
3 Elizabeth F 2549
4 Minnie F 2243

拆分问题

我们应该首先注意到,上一节中的问题与这个问题有相似之处;上一节中的问题将名称限制为 2016 年出生的婴儿,而这个问题要求所有年份的名称。

我们再次将这个问题分解成更简单的表格操作。

  • baby表按'Year''Sex'分组。
  • 对于每一组,计算最流行的名称。

认识到每个问题需要哪种操作,有时很棘手。通常,一系列复杂的步骤会告诉你,可能有更简单的方式来表达你想要的东西。例如,如果我们没有立即意识到需要分组,我们可能会编写如下步骤:

  • 遍历每个特定的年份。
  • 对于每一年,遍历每个特定的性别。
  • 对于每一个特定年份和性别,找到最常见的名字。

几乎总是有一种更好的替代方法,用于遍历pandas DataFrame。特别是,遍历DataFrame的特定值,通常应该替换为分组。

分组

为了在pandas中进行分组。 我们使用.groupby()方法。

baby.groupby('Year')
# <pandas.core.groupby.DataFrameGroupBy object at 0x1a14e21f60>

.groupby()返回一个奇怪的DataFrameGroupBy对象。 我们可以使用聚合函数,在该对象上调用.agg()来获得熟悉的输出:

# The aggregation function takes in a series of values for each group
# and outputs a single value
def length(series):
    return len(series)

# Count up number of values for each year. This is equivalent to
# counting the number of rows where each year appears.
baby.groupby('Year').agg(length)
Name Sex Count
Year
1880 2000 2000
1881 1935 1935
1882 2127 2127
2014 33206 33206
2015 33063 33063
2016 32868 32868

137 行 × 3 列

你可能会注意到length函数只是简单调用了len函数,所以我们可以简化上面的代码。

baby.groupby('Year').agg(len)
Name Sex Count
Year
1880 2000 2000
1881 1935 1935
1882 2127 2127
2014 33206 33206
2015 33063 33063
2016 32868 32868

137 行 × 3 列

聚合应用于DataFrame的每一列,从而产生冗余信息。 我们可以在分组之前使用切片限制输出列。

year_rows = baby[['Year', 'Count']].groupby('Year').agg(len)
year_rows

# A further shorthand to accomplish the same result:
#
# year_counts = baby[['Year', 'Count']].groupby('Year').count()
#
# pandas has shorthands for common aggregation functions, including
# count, sum, and mean.
Count
Year
1880
1881
1882
2014
2015
2016

137 行 × 1 列

请注意,生成的DataFrame的索引现在包含特定年份,因此我们可以像以前一样,使用.loc分割出年份的子集:

# Every twentieth year starting at 1880
year_rows.loc[1880:2016:20, :]
Count
Year
1880
1900
1920
1940
1960
1980
2000

多个列的分组

我们在 Data8 中看到,我们可以按照多个列分组,基于唯一值来获取分组。 为此,请将列标签列表传递到.groupby()

grouped_counts = baby.groupby(['Year', 'Sex']).sum()
grouped_counts
Count
Year Sex
1880 F
M 110491
1881 F
2015 M
2016 F
M 1880674

274 行 × 1 列

上面的代码计算每年每个性别出生的婴儿总数。 现在让我们使用多列分组,来计算每年和每个性别的最流行的名称。 由于数据已按照年和性别的递减顺序排序,因此我们可以定义一个聚合函数,该函数返回每个序列中的第一个值。 (如果数据没有排序,我们可以先调用sort_values()。)

# The most popular name is simply the first one that appears in the series
def most_popular(series):
    return series.iloc[0]

baby_pop = baby.groupby(['Year', 'Sex']).agg(most_popular)
baby_pop
Name Count
Year Sex
1880 F Mary
M John 9655
1881 F Mary
2015 M Noah
2016 F Emma
M Noah 19015

274 行 × 2 列

注意,多列分组会导致每行有多个标签。 这被称为“多级索引”,并且很难处理。 需要知道的重要事情是,.loc接受行索引的元组,而不是单个值:

baby_pop.loc[(2000, 'F'), 'Name']
# 'Emily'

.iloc的行为与往常一样,因为它使用索引而不是标签:

baby_pop.iloc[10:15, :]
Name Count
Year Sex
1885 F Mary
M John 8756
1886 F Mary
M John 9026
1887 F Mary

透视

如果按两列分组,则通常可以使用数据透视表,以更方便的格式显示数据。 数据透视表可以使用一组分组标签,作为结果表的列。

为了透视,使用pd.pivot_table()函数。

pd.pivot_table(baby,
               index='Year',         # Index for rows
               columns='Sex',        # Columns
               values='Name',        # Values in table
               aggfunc=most_popular) # Aggregation function
Sex F M
Year
1880 Mary John
1881 Mary John
1882 Mary John
2014 Emma Noah
2015 Emma Noah
2016 Emma Noah

137 行 × 2 列

将此结果与我们使用.groupby()计算的baby_pop表进行比较。 我们可以看到baby_pop中的Sex索引成为了数据透视表的列。

baby_pop
Name Count
Year Sex
1880 F Mary
M John 9655
1881 F Mary
2015 M Noah
2016 F Emma
M Noah 19015

274 行 × 2 列

总结

我们现在有了数据集中每个性别和年份的最受欢迎的婴儿名称,并学会了在pandas中表达以下操作:

操作 pandas
分组 df.groupby(label)
多列分组 df.groupby([label1, label2])
分组和聚合 df.groupby(label).agg(func)
透视 pd.pivot_table()

应用、字符串和绘图

在本节中,我们将回答这个问题:

我们可以用名字的最后一个字母来预测婴儿的性别吗?

这里再次展示了婴儿名称数据集:

baby = pd.read_csv('babynames.csv')
baby.head()
# the .head() method outputs the first five rows of the DataFrame
Name Sex Count Year
0 Mary F 9217
1 Anna F 3860
2 Emma F 2587
3 Elizabeth F 2549
4 Minnie F 2243

拆解问题

虽然有很多方法可以预测是否可能,但我们将在本节中使用绘图。 我们可以将这个问题分解为两个步骤:

  • 计算每个名称的最后一个字母。
  • 按照最后一个字母和性别分组,使用计数来聚合。
  • 绘制每个性别和字母的计数。

应用

pandas序列包含.apply()方法,它接受一个函数并将其应用于序列中的每个值。

names = baby['Name']
names.apply(len)
'''
0          4
1          4
2          4
          ..
1891891    5
1891892    6
1891893    8
Name: Name, Length: 1891894, dtype: int64
'''

为了提取每个名字的最后一个字母,我们可以定义我们自己的函数来传入.apply()

def last_letter(string):
    return string[-1]

names.apply(last_letter)
'''
0          y
1          a
2          a
          ..
1891891    a
1891892    e
1891893    p
Name: Name, Length: 1891894, dtype: object
'''

字符串操作

虽然.apply()是灵活的,但在处理文本数据时,在使用pandas内置的字符串操作函数通常会更快。

pandas通过序列的.str属性,提供字符串操作函数。

names = baby['Name']
names.str.len()
'''
0          4
1          4
2          4
          ..
1891891    5
1891892    6
1891893    8
Name: Name, Length: 1891894, dtype: int64
'''

我们可以用类似的方式,直接分离出每个名字的最后一个字母。

names.str[-1]
'''
0          y
1          a
2          a
          ..
1891891    a
1891892    e
1891893    p
Name: Name, Length: 1891894, dtype: object
'''

我们建议查看文档来获取字符串方法的完整列表

我们现在可以将最后一个字母的这一列添加到我们的婴儿数据帧中。

baby['Last'] = names.str[-1]
baby
Name Sex Count Year Last
0 Mary F 9217 1884
1 Anna F 3860 1884
2 Emma F 2587 1884
1891891 Verna M 5 1883
1891892 Winnie M 5 1883
1891893 Winthrop M 5 1883

1891894 行 × 5 列

分组

为了计算每个最后一个字母的性别分布,我们需要按LastSex分组。

# Shorthand for baby.groupby(['Last', 'Sex']).agg(np.sum)
baby.groupby(['Last', 'Sex']).sum()
Count Year
Last Sex
a F 58079486
M 1931630 53566324
b F 17376
y M 18569388
z F 142023
M 120123 9649274

52 行 × 2 列

请注意,因为每个没有用于分组的列都传递到聚合函数中,所以也求和了年份。 为避免这种情况,我们可以在调用.groupby()之前选择所需的列。

# When lines get long, you can wrap the entire expression in parentheses
# and insert newlines before each method call
letter_dist = (
    baby[['Last', 'Sex', 'Count']]
    .groupby(['Last', 'Sex'])
    .sum()
)
letter_dist
Count
Last Sex
a F
M 1931630
b F
y M
z F
M 120123

52 行 × 1 列

绘图

pandas为大多数基本绘图提供了内置的绘图函数,包括条形图,直方图,折线图和散点图。 为了从DataFrame中绘制图形,请使用.plot属性:

# We use the figsize option to make the plot larger
letter_dist.plot.barh(figsize=(10, 10))
# <matplotlib.axes._subplots.AxesSubplot at 0x1a17af4780>

虽然这个绘图显示了字母和性别的分布,但是男性和女性的条形很难分开。 通过在pandas文档中查看绘图,我们了解到pandasDataFrame的一行中的列绘制为一组条形,并将每列显示为不同颜色的条形。 这意味着letter_dist表的透视版本将具有正确的格式。

letter_pivot = pd.pivot_table(
    baby, index='Last', columns='Sex', values='Count', aggfunc='sum'
)
letter_pivot
Sex F M
Last
a 58079486 1931630
b 17376 1435939
c 30262 1672407
x 37381 644092
y 24877638 18569388
z 142023 120123

26 行 × 2 列

letter_pivot.plot.barh(figsize=(10, 10))
# <matplotlib.axes._subplots.AxesSubplot at 0x1a17c36978>

请注意,pandas为我们生成了图例,这很方便 但是,这仍然难以解释。 我们为每个字母和性别绘制了计数,这些计数会导致一些条形看起来很长,而另一些几乎看不见。 相反,我们应该绘制每个最后一个字母的男性和女性的比例。

total_for_each_letter = letter_pivot['F'] + letter_pivot['M']

letter_pivot['F prop'] = letter_pivot['F'] / total_for_each_letter
letter_pivot['M prop'] = letter_pivot['M'] / total_for_each_letter
letter_pivot
Sex F M F prop M prop
Last
a 58079486 1931630 0.967812 0.032188
b 17376 1435939 0.011956 0.988044
c 30262 1672407 0.017773 0.982227
x 37381 644092 0.054853 0.945147
y 24877638 18569388 0.572597 0.427403
z 142023 120123 0.541771 0.458229

26 行 × 4 列

(letter_pivot[['F prop', 'M prop']]
 .sort_values('M prop') # Sorting orders the plotted bars
 .plot.barh(figsize=(10, 10))
)
# <matplotlib.axes._subplots.AxesSubplot at 0x1a18194b70>

总结

我们可以看到几乎所有以'p'结尾的名字都是男性,以'a'结尾的名字都是女性! 一般来说,许多字母的条形长度之间的差异意味着,如果我们只知道他们的名字的最后一个字母,我们往往可以准确猜测一个人的性别。

我们已经学会在pandas中表达以下操作:

操作 pandas
逐元素应用函数 series.apply(func)
字符串操作 series.str.func()
绘图 df.plot.func()

网友评论

登录后评论
0/500
评论
apachecn_飞龙
+ 关注