前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >数据科学的原理与技巧 三、处理表格数据

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

作者头像
ApacheCN_飞龙
发布2022-12-01 20:47:00
4.6K0
发布2022-12-01 20:47:00
举报
文章被收录于专栏:信数据得永生信数据得永生

三、处理表格数据

原文:DS-100/textbook/notebooks/ch03 译者:飞龙 协议:CC BY-NC-SA 4.0 自豪地采用谷歌翻译

索引、切片和排序

起步

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

代码语言:javascript
复制
# pd is a common shorthand for pandas
import pandas as pd

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

代码语言:javascript
复制
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,我们可以检查当前文件夹中的文件:

代码语言:javascript
复制
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切片语法。 第一个参数是行标签,第二个参数是列标签:

代码语言:javascript
复制
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 列

代码语言:javascript
复制
baby.loc[1, 'Name'] # Row labeled 1, Column labeled 'Name'
# 'Anna'

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

代码语言:javascript
复制
# 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中的单个列:

代码语言:javascript
复制
baby.loc[:, 'Year']
'''
0          1884
1          1884
2          1884
           ... 
1891891    1883
1891892    1883
1891893    1883
Name: Year, Length: 1891894, dtype: int64
'''

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

代码语言:javascript
复制
baby.loc[:, 'Year'] * 2
'''
0          3768
1          3768
2          3768
           ... 
1891891    3766
1891892    3766
1891893    3766
Name: Year, Length: 1891894, dtype: int64
'''

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

代码语言:javascript
复制
# 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 列

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

代码语言:javascript
复制
# 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
'''
代码语言:javascript
复制
# 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。 这很简单,因为序列上的数学和布尔运算符,应用于序列中的每个元素。

代码语言:javascript
复制
# Series of years
baby['Year']
'''
0          1884
1          1884
2          1884
           ... 
1891891    1883
1891892    1883
1891893    1883
Name: Year, Length: 1891894, dtype: int64
'''
代码语言:javascript
复制
# 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

代码语言:javascript
复制
# 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()函数。

代码语言:javascript
复制
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 的列表切片。

代码语言:javascript
复制
# 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()

分组和透视

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

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

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

代码语言:javascript
复制
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()方法。

代码语言:javascript
复制
baby.groupby('Year')
# <pandas.core.groupby.DataFrameGroupBy object at 0x1a14e21f60>

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

代码语言:javascript
复制
# 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函数,所以我们可以简化上面的代码。

代码语言:javascript
复制
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的每一列,从而产生冗余信息。 我们可以在分组之前使用切片限制输出列。

代码语言:javascript
复制
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分割出年份的子集:

代码语言:javascript
复制
# Every twentieth year starting at 1880
year_rows.loc[1880:2016:20, :]

Count

Year

1880

1900

1920

1940

1960

1980

2000

多个列的分组

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

代码语言:javascript
复制
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()。)

代码语言:javascript
复制
# 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接受行索引的元组,而不是单个值:

代码语言:javascript
复制
baby_pop.loc[(2000, 'F'), 'Name']
# 'Emily'

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

代码语言:javascript
复制
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()函数。

代码语言:javascript
复制
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索引成为了数据透视表的列。

代码语言:javascript
复制
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()

应用、字符串和绘图

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

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

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

代码语言:javascript
复制
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()方法,它接受一个函数并将其应用于序列中的每个值。

代码语言:javascript
复制
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()

代码语言:javascript
复制
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属性,提供字符串操作函数。

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

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

代码语言:javascript
复制
names.str[-1]
'''
0          y
1          a
2          a
          ..
1891891    a
1891892    e
1891893    p
Name: Name, Length: 1891894, dtype: object
'''

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

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

代码语言:javascript
复制
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分组。

代码语言:javascript
复制
# 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()之前选择所需的列。

代码语言:javascript
复制
# 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属性:

代码语言:javascript
复制
# 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表的透视版本将具有正确的格式。

代码语言:javascript
复制
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 列

代码语言:javascript
复制
letter_pivot.plot.barh(figsize=(10, 10))
# <matplotlib.axes._subplots.AxesSubplot at 0x1a17c36978>

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

代码语言:javascript
复制
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 列

代码语言:javascript
复制
(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()

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2018-06-05,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 三、处理表格数据
    • 索引、切片和排序
      • 起步
      • 索引、切片和排序
      • 对行排序
      • 总结
    • 分组和透视
      • 拆分问题
      • 多个列的分组
      • 总结
    • 应用、字符串和绘图
      • 拆解问题
      • 应用
      • 字符串操作
      • 分组
      • 绘图
      • 总结
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档