DataQuest-博客中文翻译-六-

龙哥盟 / 2025-02-21 / 原文

DataQuest 博客中文翻译(六)

原文:DataQuest Blog

协议:CC BY-NC-SA 4.0

教程:如何创建和使用熊猫数据框架

原文:https://www.dataquest.io/blog/tutorial-how-to-create-and-use-a-pandas-dataframe/

January 31, 2022How to Create and Use a Pandas DataFrame

当使用 Python 探索数据时,数据帧使得分析和操作数据变得容易。本文将探讨使用数据帧的一些细节。

在处理数据时,Python 是一个强大的工具。它的可扩展性和各种用于数据分析和数据科学应用的库等特性使其用途广泛。然而,Python 经常被低估但却非常有价值的一点是,我们可以用灵活的数据结构轻松地操纵数据。这些结构之一是数据帧。

什么是数据帧?

首先,重要的是要知道数据可以采用各种不同的结构。在大多数情况下,大多数数据都是以表格形式(即,数据被组织成代表单个条目的行)。如果您曾经使用过 Excel 电子表格或 SQL 表,您可能对此已经很熟悉了。代表给定数据条目的这些行中的每一行及其属性的集合被形成为二维结构,其中标题列由相同属性的值组成。

这些结构有几个独特的品质:

  1. 行—表示单一的数据输入点
  2. 列-对应于与每个给定数据点的单一质量相关的分组,通常有标题
  3. 索引—每个数据条目的唯一标识符

Create Pandas DataFrame

尽管根据所使用的编程语言或应用工具的不同,这些结构可以有不同的名称,但在 Python 中,我们称这些结构为 DataFrames 。用于处理这些结构的主要库是熊猫。

你如何制作一个数据框架?

在创建数据帧时,您可以从外部文件导入,也可以用 Python 自己创建。

方法 1-从文件导入数据

在现实世界中,数据集通常通过管理它的外部源读入 Python。我们可以在多种类型的文件中找到这些数据集,但我们最常见的是以逗号分隔值文件(CSV)的形式找到它们。幸运的是,在 Pandas 库中,它有一个功能可以将这种格式的数据转换成名为 pandas.read_csv() 的数据帧。它需要的唯一主要参数是一个路径,该路径概述了文件存在的位置。

一种途径可能来自 web(即来自 API 或 GitHub 库)

import pandas as pd
import numpy as np 
import datetime as dt
# While not necessarily the case, you’ll often need to load the numpy library when working with the pandas library 
url = 'https://raw.githubusercontent.com/Vibe1990/Netflix-Project/main/netflix_title.csv'
netflix = pd.read_csv(url)
#  When providing the URL pathway, this will need to be in the form of a raw string, otherwise it will result in an error
netflix
显示 id 类型 标题 主管 国家 日期 _ 已添加 发布年份 等级 期间 列出 _ 在 描述
Zero s1 电视节目 3% 圆盘烤饼 朱奥·米格尔,比安卡·孔帕拉托,米歇尔戈麦斯,R… 巴西 8 月 14 日至 20 日 Two thousand and twenty TV-MA 四季 国际电视节目、电视剧、电视科幻片&… 在未来,精英居住在一个岛上…
one s2 电影 nineteen past seven 豪尔赫·米歇尔·格劳 德米安·毕希、赫克托·博尼利亚、奥斯卡·塞拉诺… 墨西哥 2016 年 12 月 23 日 Two thousand and sixteen TV-MA 93 分钟 戏剧、国际电影 一场毁灭性的地震袭击了墨西哥城…
Two s3 电影 one to twelve p.m. 吉尔伯特·陈 陈泰德,钟欣桐,许亮宇,劳伦斯… 新加坡 2018 年 12 月 20 日 Two thousand and eleven 稀有 78 分钟 恐怖电影,国际电影 当一名新兵被发现死亡时,他的同伴…
three 第四心音 电影 nine 申·阿克 伊利亚·伍德,约翰 C·瑞里,詹妮弗·康纳利… 美国 11 月 16 日至 17 日 Two thousand and nine PG-13 80 分钟 动作和冒险,独立电影,科幻… 在后启示录世界,布娃娃机器人嗨…
four 表面抗原-5 电影 Twenty-one 罗伯特·路克蒂克 吉姆·斯特吉斯,凯文·史派西,凯特·波茨沃斯,Aar… 美国 2010 年 1 月 1 日 Two thousand and eight PG-13 123 分钟 戏剧 一群出色的学生成为了纸牌玩家

或者,如果一个文件存储在您的计算机上的工作目录中,那么路径会相应地调整。在这个过程中,我们可以使用相对路径或完整路径来指定获取给定文件的路径,因为该函数可以毫无问题地解释两者之间的差异。

# Assuming you've set up your notebook to have the desired working directory set

netflix = pd.read_csv(“netflix_title.csv”)
netflix
显示 id 类型 标题 主管 国家 日期 _ 已添加 发布年份 等级 期间 列出 _ 在 描述
Zero s1 电视节目 3% 圆盘烤饼 朱奥·米格尔,比安卡·孔帕拉托,米歇尔戈麦斯,R… 巴西 8 月 14 日至 20 日 Two thousand and twenty TV-MA 四季 国际电视节目、电视剧、电视科幻片&… 在未来,精英居住在一个岛上…
one s2 电影 nineteen past seven 豪尔赫·米歇尔·格劳 德米安·毕希、赫克托·博尼利亚、奥斯卡·塞拉诺… 墨西哥 2016 年 12 月 23 日 Two thousand and sixteen TV-MA 93 分钟 戏剧、国际电影 一场毁灭性的地震袭击了墨西哥城…
Two s3 电影 one to twelve p.m. 吉尔伯特·陈 陈泰德,钟欣桐,许亮宇,劳伦斯… 新加坡 2018 年 12 月 20 日 Two thousand and eleven 稀有 78 分钟 恐怖电影,国际电影 当一名新兵被发现死亡时,他的同伴…
three 第四心音 电影 nine 申·阿克 伊利亚·伍德,约翰 C·瑞里,詹妮弗·康纳利… 美国 11 月 16 日至 17 日 Two thousand and nine 宜在家长指导下观看的 80 分钟 动作和冒险,独立电影,科幻… 在后启示录世界,布娃娃机器人嗨…
four 表面抗原-5 电影 Twenty-one 罗伯特·路克蒂克 吉姆·斯特吉斯,凯文·史派西,凯特·波茨沃斯,Aar… 美国 2010 年 1 月 1 日 Two thousand and eight 宜在家长指导下观看的 123 分钟 戏剧 一群出色的学生成为了纸牌玩家
Seven thousand seven hundred and eighty-two s7783 电影 佐佐 约瑟夫·法里斯 伊马德克雷迪,安托瓦内特土耳其,埃利亚斯格尔吉,汽车… 瑞典、捷克共和国、英国、丹麦… 10 月 19 日至 20 日 Two thousand and five TV-MA 99 分钟 戏剧、国际电影 当黎巴嫩内战剥夺了佐佐的…
Seven thousand seven hundred and eighty-three s7784 电影 祖班 莫兹·辛格 维姬·考沙尔,萨拉·简·迪亚斯,拉格哈夫·查南… 印度 2019 年 3 月 2 日 Two thousand and fifteen 电视-14 111 分钟 戏剧、国际电影、音乐和音乐剧 一个好斗但贫穷的男孩爬进了一个小村庄…
Seven thousand seven hundred and eighty-four s7785 电影 日本的祖鲁族人 圆盘烤饼 讨厌的 C 圆盘烤饼 9 月 25 日至 20 日 Two thousand and nineteen TV-MA 44 分钟 纪录片,国际电影,音乐和 M… 在这部纪录片中,南非说唱歌手纳斯特…
Seven thousand seven hundred and eighty-five s7786 电视节目 尊宝只是甜点 圆盘烤饼 雷切尔·库·阿德里亚诺·宗博 澳大利亚 10 月 31 日至 20 日 Two thousand and nineteen 电视-PG 一季 国际电视节目,真人秀 甜品师阿德里亚诺·宗博寻找新的…
Seven thousand seven hundred and eighty-six s7787 电影 来自德克萨斯的小老乐队 山姆·邓恩 圆盘烤饼 英国、加拿大、美国 2010 年 3 月 1 日 Two thousand and nineteen TV-MA 90 分钟 纪录片、音乐和音乐剧 这部纪录片深入探讨了神秘的背后…

7787 行× 12 列

netflix.head(5)
显示 id 类型 标题 主管 国家 日期 _ 已添加 发布年份 等级 期间 列出 _ 在 描述
Zero s1 电视节目 3% 圆盘烤饼 朱奥·米格尔,比安卡·孔帕拉托,米歇尔戈麦斯,R… 巴西 8 月 14 日至 20 日 Two thousand and twenty TV-MA 四季 国际电视节目、电视剧、电视科幻片&… 在未来,精英居住在一个岛上…
one s2 电影 nineteen past seven 豪尔赫·米歇尔·格劳 德米安·毕希、赫克托·博尼利亚、奥斯卡·塞拉诺… 墨西哥 2016 年 12 月 23 日 Two thousand and sixteen TV-MA 93 分钟 戏剧、国际电影 一场毁灭性的地震袭击了墨西哥城…
Two s3 电影 one to twelve p.m. 吉尔伯特·陈 陈泰德,钟欣桐,许亮宇,劳伦斯… 新加坡 2018 年 12 月 20 日 Two thousand and eleven 稀有 78 分钟 恐怖电影,国际电影 当一名新兵被发现死亡时,他的同伴…
three 第四心音 电影 nine 申·阿克 伊利亚·伍德,约翰 C·瑞里,詹妮弗·康纳利… 美国 11 月 16 日至 17 日 Two thousand and nine PG-13 80 分钟 动作和冒险,独立电影,科幻… 在后启示录世界,布娃娃机器人嗨…
four 表面抗原-5 电影 Twenty-one 罗伯特·路克蒂克 吉姆·斯特吉斯,凯文·史派西,凯特·波茨沃斯,Aar… 美国 2010 年 1 月 1 日 Two thousand and eight PG-13 123 分钟 戏剧 一群出色的学生成为了纸牌玩家

尽管 CSV 文件是最常见的,但是在 Pandas 中有许多不同的函数可以将各种类型的文件读入数据帧,该数据帧以相同的一般过程运行:

| 文件类型 | 在熊猫中的作用 |
| 杰森 | read_json() |
| HTML | read_html() |
| XML | read_xml() |
| SQL | read_sql() |
| Excel | read_excel() |

方法 2–自己创建数据框

虽然这不是创建数据框的最常用方法,但您当然可以通过输入数据自己创建数据框。我们可以通过熊猫来实现这一目标。DataFrame() 函数,它接受数据输入参数并将其转换成 DataFrame。pandas.DataFrame函数非常健壮,因为它可以接受各种不同的数据输入:

  • 无-这将生成一个空的数据帧,您可以在稍后用数据填充它

    # Creating an empty DataFrame
    data = pd.DataFrame()
    print(data)
    
    Empty DataFrame
    Columns: []
    Index: []
    
  • A dictionary of ndarrays / Lists

    # DataFrame for Pawnee City Hall
    
    Pawnee_city_hall = {
        "Personnel": ["Leslie Knope", "Ron Swanson", "Ann Perkins", "Tom Haverford", "Mark Brendanawicz", "April Ludgate", "Andy Dwyer", "Ben Wyatt", "Chris Traeger","Jerry Gergich", "Donna Meagle", "Craig Middlebrooks"],
        "Position":["Deputy Director", "Director", "Health Representative", "Administrator", "City Planner", "Assistant - Director", "Assistant - Deputy Director", "Deputy City Manager", "City Manger", "Administrator", "Office Manger", "Assistant Office Manager"]
    }
    
    Pawnee_city_hall = pd.DataFrame(Pawnee_city_hall)
    Pawnee_city_hall
    

    20] Leslie Knope T67] Eight [T95 】 Ten

    Position
    Zero Deputy Director
    One Ron swanson
    Two [ 36] Ann Perkins Health Representative
    Three Tom Haverford
    52] Mark Bredanavich Urban Planner
    Five April Ludgate
    Assistant-Deputy Director of Andy dwyer
    Seven Ben Wyatt
    Nine Jerry Grdjic
    Eleven Craig Middlebrooks
  • A dictionary of series (a one-dimensional array of data with an axis label)

    # Another way to create a DataFrame for Pawnee City Hall
    personnel = pd.Series(["Leslie Knope", "Ron Swanson", "Ann Perkins", "Tom Haverford", "Mark Brendanawicz", "April Ludgate", "Andy Dwyer", "Ben Wyatt", "Chris Traeger","Jerry Gergich", "Donna Meagle", "Craig Middlebrooks"])
    position = pd.Series(["Deputy Director", "Director", "Health Representative", "Administrator", "City Planner", "Assistant - Director", "Assistant - Deputy Director", "Deputy City Manager", "City Manger", "Administrator", "Office Manger", "Assistant Office Manager"])
    
    Pawnee_city_hall = pd.DataFrame({"Names":personnel, "Job":position})
    Pawnee_city_hall
    
    Name Position
    Zero Leslie Knope [T11 Deputy Director
    One Ron swanson Director
    Two Ann Pope 】 Health Representative
    Three Tom Haverford Manager
    Four Mark Brendan Assistant Director of Urban Planner
    Five April Ludgate
    Six [T66 67] Assistant-Deputy Director
    Seven Ben Wyatt Deputy City Manager
    Traege CEO
    Nine Jerry Grdjic Manager
    Miguel Business Manager
    Eleven Craig Middlebrooks Assistant Office Manager
  • A list of lists

    # Another way to create a DataFrame for Pawnee City Hall
    
    data = [
        ["Leslie Knope", "Deputy Director"], 
        ["Ron Swanson", "Director"], 
        ["Tom Haverford", "Administrator"], 
        ["April Ludgate", "Assistant-Director"], 
        ["Donna Meagle", "Office Manager"],
        ["Andy Dwyer", "Assistant - Deputy Director"], 
        ['Jerry Gergich', "Administrator"]]
    
    Pawnee_city_hall = pd.DataFrame(data, columns = ["Names", "Position"])
    Pawnee_city_hall
    

    Ford 8] Donna Miguel 4] Jerry Grdjic

    Name Location
    Zero Les Li E Knope Deputy Director
    One Ron swanson Director
    Two Manager
    Three April Ludgate Assistant Director
    Four Business Manager
    Five Andy dwyer Assistant-Deputy Director
    Manager
  • A list of dictionaries

    # DATAFRAME FOR RAPTORS
    
    raptors = [{"Player": "Pascal Siakim", "PPG": 23.7, "College":"University of New Mexico", "is_starting_five": True},
               {"Player": "Fred VanVleet", "PPG": 20.1, "College":"Wichita State", "is_starting_five": True},
               {"Player": "Scottie Barnes", "PPG": 15.1, "College":"Florida State", "is_starting_five": True},
               {"Player": "Chris Boucher", "PPG": 8.7, "College":"Oregon", "is_starting_five": False}
              ]
    
    raptors = pd.DataFrame(raptors, index = ['Power Forward', "Point Guard", "Small Forward", "Center"])
    raptors
    
    运动员 PPG 大学 从五点开始 从五点开始
    大前锋 帕斯卡尔·西亚金 Twenty-three point seven 新墨西哥大学 真实的 真实的
    控球后卫 弗瑞德疯了 Twenty point one 威奇托州 真实的 真实的
    小前锋 斯科蒂·巴恩斯 Fifteen point one 佛罗里达州 真实的 真实的
    中心 克里斯·鲍彻 Eight point seven 俄勒冈州 错误的 错误的

    注意:pandas.DataFrame函数也有 index 和 column 参数,分别用于命名行索引和列标题。

虽然在上面的例子中使用了相同的数据类型(字符串),但数据帧可以由各种不同的数据类型组成,如整数、浮点、列表、日期时间、布尔、列表等。

# DATAFRAME FOR LAKERS

lakers = {
"player" : ['Lebron James', 
              'Russell Westbrook', 'Anthony Davis', 'Dwight Howard', 'Avery Bradley', 'DeAndre Jordan', 
              'Carmelo Anthony', 'Austin Reaves', 'Kent Bazemore', 'Malik Monk', 'Stanley Johnson', 
              'Trevor Ariza', 'Wayne Ellington', 'Talen Horton-Tucker'],
"PPG": [28.7, 19.3, 23.3, 5, 6.9, 4.5, 13.4, 5.4, 4.1, 12.1, 6.2, 3.5, 6.4, 10.9],
"in_starting_lineup": [True, True, True, False, True, False, False, False, False, True, False, False, False, False]
}

lakers = pd.DataFrame(lakers)
lakers
运动员 PPG 首发阵容中
Zero 勒布朗·詹姆斯 Twenty-eight point seven 真实的
one 拉塞尔·维斯特布鲁克 Nineteen point three 真实的
Two 安东尼·戴维斯 Twenty-three point three 真实的
three 德怀特·霍华德 Five 错误的
four 艾弗里·布拉德利 Six point nine 真实的
five 迪安卓·乔丹 Four point five 错误的
six 卡梅隆·安东尼 Thirteen point four 错误的
seven 奥斯汀·雷夫斯 Five point four 错误的
eight 肯特·巴兹摩尔 Four point one 错误的
nine Malik Monk Twelve point one 真实的
Ten 斯坦利·约翰逊 Six point two 错误的
Eleven 特雷沃·阿里扎 Three point five 错误的
Twelve 韦恩·艾灵顿 Six point four 错误的
Thirteen 塔伦·霍顿-塔克 Ten point nine 错误的

探索数据框架

因为 Python 是一种面向对象的编程语言,所以创建 DataFrame 意味着创建 DataFrame 类的对象。这也意味着我们可以探索许多不同的属性和方法来应用于数据帧。虽然我们在不熟悉数据集的情况下(比如从某个地方导入数据集)更经常使用它们,但它们仍然是有用的。

每当数据集作为数据帧加载到 Python 中时,最好查看它的结构。有许多不同的属性可以提供该信息:

  • DataFrame.shape —返回指示数据帧的行数和列数的元组

  • DataFrame.size —返回数据集中数据点数的整数值

    print(netflix.shape)
    print(netflix.size)
    
    (7787, 12)
    93444
    

如果您要探索数据帧的轴,您可以通过让数组经由 DataFrame.columns 和 DataFrame.index 返回列出的列和索引来实现。另一方面,查看组成数据集的不同类型的数据可能是有用的。在这些情况下,使用 DataFrame.dtypes 。

print(netflix.columns)
print("")
print(netflix.index)
print("")
print(netflix.dtypes)
Index(['show_id', 'type', 'title', 'director', 'cast', 'country', 'date_added',
       'release_year', 'rating', 'duration', 'listed_in', 'description'],
      dtype='object')
RangeIndex(start=0, stop=7787, step=1)
显示 id 目标
类型 目标
标题 目标
主管 目标
目标
国家 目标
日期 _ 已添加 目标
发布年份 int64
等级 目标
期间 目标
列出 _ 在 目标
描述 目标
dtype:对象

操作数据帧

现在我们知道了 DataFrame 是什么,是时候做一些真正的工作了!原则上,这包括在实际分析之前,将它作为数据清理和数据争论过程的一部分进行操作。现在,每个人都应该掌握一些基本操作,首先是能够访问和隔离数据帧的给定部分。

为了分割数据帧,我们使用 DataFrame.loc 属性或 DataFrame.iloc 属性,其中输入指示提取哪些行或列([rows: columns])。如果一个列需要被隔离,那么这个过程将会是使用方括号和给定列的名称。

print(netflix['title'])
print("")
print(netflix.loc[0])
Zero 3%
one nineteen past seven
Two one to twelve p.m.
three nine
four Twenty-one
Seven thousand seven hundred and eighty-two 佐佐
Seven thousand seven hundred and eighty-three 祖班
Seven thousand seven hundred and eighty-four 日本的祖鲁族人
Seven thousand seven hundred and eighty-five 尊宝只是甜点
Seven thousand seven hundred and eighty-six 来自德克萨斯的小老乐队
名称:标题,长度:7787,数据类型:对象
显示 id s1
类型 电视节目
标题 3%
主管 圆盘烤饼
朱奥·米格尔,比安卡·孔帕拉托,米歇尔戈麦斯,R…
国家 巴西
日期 _ 已添加 8 月 14 日至 20 日
发布年份 Two thousand and twenty
等级 TV-MA
期间 四季
列出 _ 在 国际电视节目、电视剧、电视科幻片&…
描述 在未来,精英居住在一个岛上…
名称:0,数据类型:对象

在我们需要提取多行或多列的情况下,我们使用切片方法,这涉及到使用“:”来指示一个连续的范围,该范围的结束范围是排他的(即不包括在内),或者通过在方括号内输入标准,其方式类似于使用带有 NumPy 的布尔的索引。

# Select the first two row of the raptors DataFrame 
raptors.iloc[0:2]
运动员 PPG 大学 从五点开始
大前锋 帕斯卡尔·西亚金 Twenty-three point seven 新墨西哥大学 真实的
控球后卫 弗瑞德疯了 Twenty point one 威奇托州 真实的
# Select the last three columns of the raptors DataFrame
raptors.iloc[:, 1:4]
PPG 大学 从五点开始
大前锋 Twenty-three point seven 新墨西哥大学 真实的
控球后卫 Twenty point one 威奇托州 真实的
小前锋 Fifteen point one 佛罗里达州 真实的
中心 Eight point seven 俄勒冈州 错误的
# Select players that averaged more than 15 PPG on the Laker DataFrame
lakers[lakers["PPG"] > 15]
运动员 PPG 首发阵容中
Zero 勒布朗·詹姆斯 Twenty-eight point seven 真实的
one 拉塞尔·维斯特布鲁克 Nineteen point three 真实的
Two 安东尼·戴维斯 Twenty-three point three 真实的

虽然上面的例子很简单,但是通过使用 AND (&)、OR (|)、NOT EQUAL TO(!idspnonenote)等运算符,可以使它变得更加强大和复杂。=)或等于(==)。为此,让我们创建一个包含当前 UFC 冠军信息的新数据框架:

# DATAFRAME FOR UFC CHAMPS

ufc_champs = {
    "names" : ['Francis Ngannou', "Glover Teixeira", 
             "Israel Adesanya", "Kamaru Usman", 
             "Charles Oliveira", "Alex Volkanowski", 
             'Aljamain Sterling', "Brandon Morano", 
             "Julianna Pena", "Valentina Shevchenko", "Rose Namajunas"],
    "nicknames" : ['The Predator', None, "The Last Stylebender", 
                 "The Nigerian Nightmare", "Da Bronx", "The Great",
                "Funk Master", "The Assassin Baby", "The Venezulean Vixen",
                "The Bullet", "Thug"],
    "wins" : [16,33,21,19,31,23,20,19,11,22,11],
    "losses" : [3,7,1,1,8,1,3,5,4,3,4],
    "weightclass" : ['Heavyweight', "Light Heavyweight", "Middleweight",
                   'Welterweight', "Lightweight", "Featherweight", 
                   'Bantamweight', "Flyweight", "Bantamweight", "Flyweight", "Strawweight"]
}

ufc_champs = pd.DataFrame(ufc_champs)
ufc_champs
名称 绰号 视窗网际网路名称服务 损耗 失重的 国家
Zero 弗兰西斯甘努 掠夺者 Sixteen three 要人 喀麦隆
one 格洛弗·特谢拉 没有人 Thirty-three seven 轻量级 巴西
Two 以色列阿德桑亚 最后的御术师 Twenty-one one 中量级 新西兰
three Kamaru Usman 尼日利亚的噩梦 Nineteen one 次中量级 美利坚合众国
four 查尔斯·奥利维拉 达布朗克斯 Thirty-one eight 轻量级选手 巴西
five 亚历克斯·弗莱根斯基 伟大的 Twenty-three one 轻量级 澳大利亚
six 阿尔贾曼·斯特林 恐惧大师 Twenty three 轻量级 美利坚合众国
seven 布兰登·莫拉诺 刺客宝贝 Nineteen five 轻量级 墨西哥
eight 朱莉安娜·佩纳 委内瑞拉雌狐 Eleven four 轻量级 美利坚合众国
nine 瓦伦蒂娜·舍甫琴科 子弹 Twenty-two three 轻量级 吉尔吉斯斯坦
Ten 罗斯·纳马朱纳斯 暴徒 Eleven four 秸秆重量 美利坚合众国
# Select champion that came from Brazil + is the Light Heavyweight Champ
ufc_champs[(ufc_champs['country'] == "Brazil") & (ufc_champs['weightclass'] == "Light Heavyweight")]
名称 绰号 视窗网际网路名称服务 损耗 失重的 国家
one 格洛弗·特谢拉 没有人 Thirty-three seven 轻量级 巴西
# Select champion(s) if home country is Brazil OR has at Less than 2 losses 
ufc_champs[(ufc_champs['country'] == "Brazil") | (ufc_champs['losses'] < 2)]
名称 绰号 视窗网际网路名称服务 损耗 失重的 国家
one 格洛弗·特谢拉 没有人 Thirty-three seven 轻量级 巴西
Two 以色列阿德桑亚 最后的御术师 Twenty-one one 中量级 新西兰
three Kamaru Usman 尼日利亚的噩梦 Nineteen one 次中量级 美利坚合众国
four 查尔斯·奥利维拉 达布朗克斯 Thirty-one eight 轻量级选手 巴西
five 亚历克斯·弗莱根斯基 伟大的 Twenty-three one 轻量级 澳大利亚
# Select chamption with less than 20 wins AND home country is not USA
print(ufc_champs[(ufc_champs['wins'] < 20) & (ufc_champs["country"] != "USA")])
名称 绰号 视窗网际网路名称服务 损耗 失重的 国家
Zero 弗兰西斯甘努 掠夺者 Sixteen three 要人 喀麦隆
seven 布兰登·莫拉诺 刺客宝贝 Nineteen five 轻量级 墨西哥

除了过滤掉数据帧或将其分段,还可以使用DataFrame.iloc()DataFrame.loc()属性来更改特定的值。让我们看一个例子,看看我们如何用真实数据做到这一点。假设湖人改变了首发五人阵容,用迪安卓·乔丹替换安东尼·戴维斯。我们可以用DataFrame.iloc()这样做:

# The Lakers made a change in the starting 5 lineup where we replace Anthony Davis with DeAndre Jordan

lakers.iloc[2,2] = False
lakers.loc[5,"in_starting_lineup"] = True
lakers
运动员 PPG 首发阵容中
Zero 勒布朗·詹姆斯 Twenty-eight point seven 真实的
one 拉塞尔·维斯特布鲁克 Nineteen point three 真实的
Two 安东尼·戴维斯 Twenty-three point three 错误的
three 德怀特·霍华德 Five 错误的
four 艾弗里·布拉德利 Six point nine 真实的
five 迪安卓·乔丹 Four point five 真实的
six 卡梅隆·安东尼 Thirteen point four 错误的
seven 奥斯汀·雷夫斯 Five point four 错误的
eight 肯特·巴兹摩尔 Four point one 错误的
nine Malik Monk Twelve point one 真实的
Ten 斯坦利·约翰逊 Six point two 错误的
Eleven 特雷沃·阿里扎 Three point five 错误的
Twelve 韦恩·艾灵顿 Six point four 错误的
Thirteen 塔伦·霍顿-塔克 Ten point nine 错误的

在某些情况下,可能需要在数据帧中插入或删除数据。我们可以使用 append()drop() 方法插入或删除一行。对于 append 方法,您将使用一个 panda。Series 对象,匹配作为函数参数的数据帧的维度。对于 drop 方法,我们需要声明的只是需要删除的数据帧中的索引/列标签。这两种方法都包含 axis 参数,该参数指定是添加还是删除行或列。

回想一下,我们创建了一个由 UFC 冠军组成的数据框架,这些冠军在 2021 年底获得冠军,并拥有他们的绰号和输赢记录:

# Say we create a DataFrame consisting of UFC champions that held the title at the end of 2021 with their monikers and win-loss record 

ufc_champs = {
    "names": ['Francis Ngannou', "Glover Teixeira", 
             "Israel Adesanya", "Kamaru Usman", 
             "Charles Oliveira", "Alex Volkanowski", 
             'Aljamain Sterling', "Brandon Morano", 
             "Julianna Pena", "Valentina Shevchenko", "Rose Namajunas"],
    "nicknames": ['The Predator', None, "The Last Stylebender", 
                 "The Nigerian Nightmare", "Da Bronx", "The Great",
                "Funk Master", "The Assassin Baby", "The Venezulean Vixen",
                "The Bullet", "Thug"],
    "wins": [16,33,21,19,31,23,20,19,11,22,11],
    "losses": [3,7,1,1,8,1,3,5,4,3,4],
    "weightclass": ['Heavyweight', "Light Heavyweight", "Middleweight",
                   'Welterweight', "Lightweight", "Featherweight", 
                   'Bantamweight', "Flyweight", "Bantamweight", "Flyweight", "Strawweight"]
}

ufc_champs = pd.DataFrame(ufc_champs)
ufc_champs

# In making this DataFrame, we forgot to include the Women’s Featherweight champ 
ufc_champs.append(pd.Series(data = ["Amanda Nunes", "Lioness", 21, 5, "Featherweight"], index = ufc_champs.columns, name = 17))
名称 绰号 视窗网际网路名称服务 损耗 重量级
Zero 弗兰西斯甘努 掠夺者 Sixteen three 要人
one 格洛弗·特谢拉 没有人 Thirty-three seven 轻量级
Two 以色列阿德桑亚 最后的御术师 Twenty-one one 中量级
three Kamaru Usman 尼日利亚的噩梦 Nineteen one 次中量级
four 查尔斯·奥利维拉 达布朗克斯 Thirty-one eight 轻量级选手
five 亚历克斯·弗莱根斯基 伟大的 Twenty-three one 轻量级
six 阿尔贾曼·斯特林 恐惧大师 Twenty three 轻量级
seven 布兰登·莫拉诺 刺客宝贝 Nineteen five 轻量级
eight 朱莉安娜·佩纳 委内瑞拉雌狐 Eleven four 轻量级
nine 瓦伦蒂娜·舍甫琴科 子弹 Twenty-two three 轻量级
Ten 罗斯·纳马朱纳斯 暴徒 Eleven four 秸秆重量
Seventeen 阿曼达纽内斯 母狮子 Twenty-one five 轻量级
ufc_champs.drop([0], axis = 0) # drops first row
名称 绰号 视窗网际网路名称服务 损耗 失重的
one 格洛弗·特谢拉 没有人 Thirty-three seven 轻量级
Two 以色列阿德桑亚 最后的御术师 Twenty-one one 中量级
three Kamaru Usman 尼日利亚的噩梦 Nineteen one 次中量级
four 查尔斯·奥利维拉 达布朗克斯 Thirty-one eight 轻量级选手
five 亚历克斯·弗莱根斯基 伟大的 Twenty-three one 轻量级
six 阿尔贾曼·斯特林 恐惧大师 Twenty three 轻量级
seven 布兰登·莫拉诺 刺客宝贝 Nineteen five 轻量级
eight 朱莉安娜·佩纳 委内瑞拉雌狐 Eleven four 轻量级
nine 瓦伦蒂娜·舍甫琴科 子弹 Twenty-two three 轻量级
Ten 罗斯·纳马朱纳斯 暴徒 Eleven four 秸秆重量

在添加列的情况下,这个过程类似于将一个条目添加到字典中。

weight_limit = [265,205,185,170,155,145,135,125,135,125,115]
ufc_champs['weight_limit'] = weight_limit
ufc_champs
名称 绰号 视窗网际网路名称服务 损耗 失重的 重量限制
Zero 弗兰西斯甘努 掠夺者 Sixteen three 要人 Two hundred and sixty-five
one 格洛弗·特谢拉 没有人 Thirty-three seven 轻量级 Two hundred and five
Two 以色列阿德桑亚 最后的御术师 Twenty-one one 中量级 One hundred and eighty-five
three Kamaru Usman 尼日利亚的噩梦 Nineteen one 次中量级 One hundred and seventy
four 查尔斯·奥利维拉 达布朗克斯 Thirty-one eight 轻量级选手 One hundred and fifty-five
five 亚历克斯·弗莱根斯基 伟大的 Twenty-three one 轻量级 One hundred and forty-five
six 阿尔贾曼·斯特林 恐惧大师 Twenty three 轻量级 One hundred and thirty-five
seven 布兰登·莫拉诺 刺客宝贝 Nineteen five 轻量级 One hundred and twenty-five
eight 朱莉安娜·佩纳 委内瑞拉雌狐 Eleven four 轻量级 One hundred and thirty-five
nine 瓦伦蒂娜·舍甫琴科 子弹 Twenty-two three 轻量级 One hundred and twenty-five
Ten 罗斯·纳马朱纳斯 暴徒 Eleven four 秸秆重量 One hundred and fifteen

有时对于数据集,用于标识列的标签可能无法准确描述其属性。要更改这些标签,我们可以使用[DataFrame.rename()](https://pandas.pydata.org/docs/reference/api/pandas.DataFrame.rename.html#pandas.DataFrame.rename)方法,该方法接受 index 参数(以类似字典的格式指定索引的标签)、columns 参数(以类似字典的格式指定列的标签)以及确定是否返回新数据帧的就地参数。

# Let’s say we need to rename the columns of the UFC champ DataFrame by capitalizing the labels

ufc_champs.rename(columns = {'names': 'Names', 
                             'nicknames': 'Nicknames', 
                             'wins': 'Wins',  
                             'losses': 'Losses', 
                             'weightclass': 'Weight Class', 
                             'weight_limit': 'Weight Limit'})
名称 绰号 视窗网际网路名称服务 损耗 重量级 重量限度
Zero 弗兰西斯甘努 掠夺者 Sixteen three 要人 Two hundred and sixty-five
one 格洛弗·特谢拉 没有人 Thirty-three seven 轻量级 Two hundred and five
Two 以色列阿德桑亚 最后的御术师 Twenty-one one 中量级 One hundred and eighty-five
three Kamaru Usman 尼日利亚的噩梦 Nineteen one 次中量级 One hundred and seventy
four 查尔斯·奥利维拉 达布朗克斯 Thirty-one eight 轻量级选手 One hundred and fifty-five
five 亚历克斯·弗莱根斯基 伟大的 Twenty-three one 轻量级 One hundred and forty-five
six 阿尔贾曼·斯特林 恐惧大师 Twenty three 轻量级 One hundred and thirty-five
seven 布兰登·莫拉诺 刺客宝贝 Nineteen five 轻量级 One hundred and twenty-five
eight 朱莉安娜·佩纳 委内瑞拉雌狐 Eleven four 轻量级 One hundred and thirty-five
nine 瓦伦蒂娜·舍甫琴科 子弹 Twenty-two three 轻量级 One hundred and twenty-five
Ten 罗斯·纳马朱纳斯 暴徒 Eleven four 秸秆重量 One hundred and fifteen

最后,在某些情况下,我们可能需要重塑数据集的当前构成,以使其适合数据分析。虽然手动重新制作另一个数据帧肯定是可能的,但转换它会更容易。在 Pandas 中,我们可以使用三种不同的转换函数来重塑数据帧:

方法 1 —旋转

这种转换实质上是将一个较长格式的数据帧变得更宽。这通常是因为每个后续条目的唯一标识符在多行中重复。导出新格式化的数据帧的一种方法是使用 DataFrame.pivot 。这种方法需要定义哪些数据列将用作新的索引和索引以及数据帧的值。

# Say we are creating a DataFrame that maps out the voting for the season MVP of the 2021-2022 NBA season from 5 different sports journalist/reporter/pundits in sports media

mvp_vote_2022 = pd.DataFrame({
    "Voter": ['Kenny Smith', 'Kenny Smith', 'Kenny Smith', 'Kenny Smith', 'Kenny Smith',
               "Charles Barkley", "Charles Barkley", "Charles Barkley", "Charles Barkley", "Charles Barkley",
               "Ernie Johnson", "Ernie Johnson", "Ernie Johnson", "Ernie Johnson", "Ernie Johnson",
               "Michael Wilbon", "Michael Wilbon", "Michael Wilbon", "Michael Wilbon", "Michael Wilbon",
               "Doris Burke", "Doris Burke", "Doris Burke", "Doris Burke", "Doris Burke"
              ], 
    "Player": ['Steph Curry', 'Lebron James', 'Chris Paul', 'Kevin Durant', 'Giannis Antetokounmpo',
                'Steph Curry', 'Nikola Jokic', 'Giannis Antetokounmpo', 'Chris Paul', 'DeMar DeRozan',
                'Steph Curry', 'Giannis Antetokounmpo', 'Kevin Durant', 'Nikola Jokic', 'Joel Embid',
                'Kevin Durant', 'Giannis Antetokounmpo', 'Nikola Jokic', 'Steph Curry', 'DeMar DeRozan',
                'Kevin Durant', 'Giannis Antetokounmpo', 'Steph Curry', 'Nikola Jokic', 'DeMar DeRozan',],

    "Placing": [1,2,3,4,5,1,2,3,4,5,1,2,3,4,5,1,2,3,4,5,1,2,3,4,5] 
})

# Say if we need to switch the index to indicate voters as the index values and columns with the MVP placings from each voter

mvp_vote_2022.pivot(index = 'Voter', columns = 'Placing')
运动员
放置
投票人
查尔斯·巴克利
多丽丝·伯克
厄尼·约翰逊
肯尼·史密斯
迈克尔·威尔本

对于给定的列,DataFrame.pivot方法不允许有重复值的行。如果不能保证这种唯一性,另一种方法是使用 DataFrame.pivot_table() 函数。这种方法需要用于指定索引、列和值的参数。独特的是,这个函数还有一个额外的参数“agg func”(默认为 numpy.mean ),它传递一个函数来聚合 DataFrame 的值。

# Create a new DataFrame that shows the median placing for NBA MVP 
mvp_vote_2022.pivot_table(index = 'Player', values = 'Placing', aggfunc = np.median).sort_values(by= 'Placing') 

# the .sort_values() method is used to arrange the DataFrame by some existing variable in either ascending or descending order
名次
运动员
斯蒂芬·库里
扬尼斯·阿德托昆博
凯文·杜兰特
勒布朗·詹姆斯
克里斯·保罗
尼古拉·约基奇
德马尔·德罗赞
Joel Embid

方法 2——堆叠/拆分

有时一个数据帧可能有多个索引,如下所示:

array = [["Month 1",  "Month 2", "Month  3", "Month 4"], 
        ['Squat', "Squat", "Squat", "Squat"]]

array2 = [['Arnold', "Arnold","Larry", "Larry"], 
          ["Before", "After", "Before", "After"]]

values = [[225, 245, 275, 315], [335, 355, 365, 405], [315, 365, 435, 455], [495, 405, 545, 585]]

values = np.array(values)

effect_of_ped = pd.DataFrame(data = values, index = array2, columns = array)
effect_of_ped
第一个月 第二个月 第三个月 第 4 个月
下蹲 下蹲 下蹲 下蹲
阿诺德 之前 Two hundred and twenty-five Two hundred and forty-five Two hundred and seventy-five Three hundred and fifteen
之后 Three hundred and thirty-five Three hundred and fifty-five Three hundred and sixty-five Four hundred and five
拉里 之前 Three hundred and fifteen Three hundred and sixty-five Four hundred and thirty-five Four hundred and fifty-five
之后 Four hundred and ninety-five Four hundred and five Five hundred and forty-five Five hundred and eighty-five

通常很难理解数据的含义或对其进行分析。因此,诸如 stack()unstack() 之类的功能可以分别使其更长或更宽。

effect_of_ped.stack()
第三个月 第一个月 第二个月 第 4 个月
阿诺德 之前 下蹲 Two hundred and seventy-five Two hundred and twenty-five Two hundred and forty-five Three hundred and fifteen
之后 下蹲 Three hundred and sixty-five Three hundred and thirty-five Three hundred and fifty-five Four hundred and five
拉里 之前 下蹲 Four hundred and thirty-five Three hundred and fifteen Three hundred and sixty-five Four hundred and fifty-five
之后 下蹲 Five hundred and forty-five Four hundred and ninety-five Four hundred and five Five hundred and eighty-five
effect_of_ped.unstack()
第一个月 第二个月 第三个月 第 4 个月
下蹲 下蹲 下蹲 下蹲
之后 之前 之后 之前
阿诺德 Three hundred and thirty-five Two hundred and twenty-five Three hundred and fifty-five Two hundred and forty-five
拉里 Four hundred and ninety-five Three hundred and fifteen Four hundred and five Three hundred and sixty-five

方法 3 —熔化

这被称为 unpivoting a DataFrame,其工作原理是将宽格式数据帧转换为长格式数据帧。这通常发生在多个列作为给定分析的标识符时。为了将数据帧转换成更长的格式,我们需要使用 DataFrame.melt() 函数,该函数需要确定哪些列将被用作标识符变量,哪些列将被“取消透视”以对应于所述标识符的值。

data = pd.DataFrame({"Name": ["Pankaj", "Lisa", "David"], "ID": [1, 2, 3], "Role": ["CEO", "Editor", "Author"]})
data

# Let's say the identifier is ID and the values would be Name + Role

pd.melt(data, id_vars = ["ID"], value_vars = ['Name', 'Role'])
身份证明 可变的 价值
Zero one 名字 Pankaj
one Two 名字 elizabeth 的昵称
Two three 名字 大卫
three one 作用 首席执行官
four Two 作用 编者ˌ编辑
five three 作用 作者

到目前为止,我们只是触及了数据框架的表面。Python 中有更多的函数和方法可以对这些数据结构进行操作,从而更深入地了解数据。你可以在熊猫数据框架参考指南中找到这些。然而,Dataquest 上的熊猫和 NumPy 基础课程是一个很好的起点。一旦你了解了基础知识,就可以在数据分析师职业道路的其他课程中继续学习使用 Python 处理数据。

教程:如何在熊猫中使用应用方法

原文:https://www.dataquest.io/blog/tutorial-how-to-use-the-apply-method-in-pandas/

February 18, 2022

apply()方法是最常用的数据预处理方法之一。它简化了对熊猫系列中的每个元素以及熊猫数据帧中的每一行或每一列应用函数。在本教程中,我们将学习如何在 pandas 中使用apply()方法——您需要了解 Python 和 lambda 函数的基础知识。如果你不熟悉这些或者需要提高你的 Python 技能,你可能想试试我们的免费 Python 基础课程。

让我们开始吧。

在熊猫系列上应用函数

系列构成了熊猫的基础。它们本质上是一维数组,带有称为索引的轴标签。

创建一个系列对象有不同的方法(例如,我们可以用列表或字典初始化一个系列)。让我们定义一个包含两个列表的 Series 对象,这两个列表包含学生姓名作为索引,以厘米为单位的身高作为数据:

import pandas as pd
import numpy as np
from IPython.display import display

students = pd.Series(data=[180, 175, 168, 190], 
                     index=['Vik', 'Mehdi', 'Bella', 'Chriss'])
display(students)
print(type(students))
Vik       180
Mehdi     175
Bella     168
Chriss    190
dtype: int64

<class>

上面的代码返回了students对象的内容及其数据类型。

students对象的数据类型是系列,因此我们可以使用apply()方法对其数据应用任何函数。让我们看看如何将学生的身高从厘米转换为英尺:

def cm_to_feet(h):
    return np.round(h/30.48, 2)

print(students.apply(cm_to_feet))
Vik       5.91
Mehdi     5.74
Bella     5.51
Chriss    6.23
dtype: float64

学生身高换算成英尺,带两位小数。为此,我们首先定义一个进行转换的函数,然后将不带括号的函数名传递给apply()方法。apply()方法获取序列中的每个元素,并对其应用cm_to_feet()函数。

在熊猫数据帧上应用函数

在这一节中,我们将学习如何使用apply()方法来操作数据帧中的列和行。

首先,让我们使用下面的代码片段创建一个包含公司员工个人详细信息的虚拟数据帧:

data = pd.DataFrame({'EmployeeName': ['Callen Dunkley', 'Sarah Rayner', 'Jeanette Sloan', 'Kaycee Acosta', 'Henri Conroy', 'Emma Peralta', 'Martin Butt', 'Alex Jensen', 'Kim Howarth', 'Jane Burnett'],
                    'Department': ['Accounting', 'Engineering', 'Engineering', 'HR', 'HR', 'HR', 'Data Science', 'Data Science', 'Accounting', 'Data Science'],
                    'HireDate': [2010, 2018, 2012, 2014, 2014, 2018, 2020, 2018, 2020, 2012],
                    'Sex': ['M', 'F', 'F', 'F', 'M', 'F', 'M', 'M', 'M', 'F'],
                    'Birthdate': ['04/09/1982', '14/04/1981', '06/05/1997', '08/01/1986', '10/10/1988', '12/11/1992', '10/04/1991', '16/07/1995', '08/10/1992', '11/10/1979'],
                    'Weight': [78, 80, 66, 67, 90, 57, 115, 87, 95, 57],
                    'Height': [176, 160, 169, 157, 185, 164, 195, 180, 174, 165],
                    'Kids': [2, 1, 0, 1, 1, 0, 2, 0, 3, 1]
                    })
display(data)
员工姓名 部门 你在说什么 出生年月日 重量 高度 小孩子
Zero 卡伦·邓克利 会计 Two thousand and ten M 04/09/1982 seventy-eight One hundred and seventy-six Two
one 莎拉·雷纳 工程 Two thousand and eighteen F 14/04/1981 Eighty One hundred and sixty one
Two 珍妮特·斯隆 工程 Two thousand and twelve F 06/05/1997 Sixty-six One hundred and sixty-nine Zero
three 凯茜·阿科斯塔 人力资源(部) Two thousand and fourteen F 08/01/1986 Sixty-seven One hundred and fifty-seven one
four 亨利康罗伊 人力资源(部) Two thousand and fourteen M 10/10/1988 Ninety One hundred and eighty-five one
five 艾玛·佩拉尔塔 人力资源(部) Two thousand and eighteen F 12/11/1992 Fifty-seven One hundred and sixty-four Zero
six 马丁·巴特 数据科学 Two thousand and twenty M 10/04/1991 One hundred and fifteen One hundred and ninety-five Two
seven 艾利克斯詹森 数据科学 Two thousand and eighteen M 16/07/1995 Eighty-seven one hundred and eighty Zero
eight 金·豪沃思 会计 Two thousand and twenty M 08/10/1992 Ninety-five One hundred and seventy-four three
nine 简·伯内特 数据科学 Two thousand and twelve F 11/10/1979 Fifty-seven One hundred and sixty-five one

在这一部分,我们将处理由公司人力资源团队发起的虚拟请求。我们将通过不同的场景来学习如何使用apply()方法。我们将在每个场景中探索一个新的用例,并使用apply()方法解决它。


场景 1

让我们假设人力资源团队想要发送一封邀请电子邮件,以对所有员工的友好问候开始(例如,嘿,莎拉!)。他们要求您创建两列来分别存储雇员的名和姓,以便于引用雇员的名。为此,我们可以使用一个 lambda 函数,该函数在用指定的分隔符将一个字符串拆分成一个列表之后,将它拆分成一个列表;split()方法的默认分隔符是任何空格。让我们看看代码:

data['FirstName'] = data['EmployeeName'].apply(lambda x : x.split()[0])
data['LastName'] = data['EmployeeName'].apply(lambda x : x.split()[1])
display(data)
员工姓名 部门 你在说什么 出生年月日 重量 高度 小孩子 西方人名的第一个字
Zero 卡伦·邓克利 会计 Two thousand and ten M 04/09/1982 seventy-eight One hundred and seventy-six Two 卡伦 邓克利
one 莎拉·雷纳 工程 Two thousand and eighteen F 14/04/1981 Eighty One hundred and sixty one 撒拉 雷纳
Two 珍妮特·斯隆 工程 Two thousand and twelve F 06/05/1997 Sixty-six One hundred and sixty-nine Zero 细斜纹布 斯隆
three 凯茜·阿科斯塔 人力资源(部) Two thousand and fourteen F 08/01/1986 Sixty-seven One hundred and fifty-seven one 凯茜 阿科斯塔
four 亨利康罗伊 人力资源(部) Two thousand and fourteen M 10/10/1988 Ninety One hundred and eighty-five one 亨利 康罗伊
five 艾玛·佩拉尔塔 人力资源(部) Two thousand and eighteen F 12/11/1992 Fifty-seven One hundred and sixty-four Zero 女子名 佩拉尔塔
six 马丁·巴特 数据科学 Two thousand and twenty M 10/04/1991 One hundred and fifteen One hundred and ninety-five Two 马丁 屁股
seven 艾利克斯詹森 数据科学 Two thousand and eighteen M 16/07/1995 Eighty-seven one hundred and eighty Zero 亚历克斯 詹森
eight 金·豪沃思 会计 Two thousand and twenty M 08/10/1992 Ninety-five One hundred and seventy-four three 金姆(人名) 霍沃斯
nine 简·伯内特 数据科学 Two thousand and twelve F 11/10/1979 Fifty-seven One hundred and sixty-five one 简(女子名) 伯内特

在上面的代码中,我们对EmployeeName列应用了 lambda 函数,从技术上讲,这是一个 Series 对象。lambda 函数将雇员的全名分为名和姓。因此,代码创建了另外两列,包含雇员的名字和姓氏。

场景 2

现在,让我们假设人力资源团队想要知道每个员工的年龄和员工的*均年龄,因为他们想要确定员工的年龄是否影响工作满意度和工作参与度。

要完成这项工作,第一步是定义一个函数,获取雇员的出生日期并返回他们的年龄:

from datetime import datetime, date

def calculate_age(birthdate):
    birthdate = datetime.strptime(birthdate, '%d/%m/%Y').date()
    today = date.today()
    return today.year - birthdate.year - (today.month < birthdate.month)

calculate_age()函数以适当的格式获取一个人的出生日期,并在对其进行简单计算后,返回其年龄。

下一步是使用apply()方法对数据帧的Birthdate列应用函数,如下所示:

data['Age'] = data['Birthdate'].apply(calculate_age)
display(data)
员工姓名 部门 你在说什么 出生年月日 重量 高度 小孩子 西方人名的第一个字 年龄
Zero 卡伦·邓克利 会计 Two thousand and ten M 04/09/1982 seventy-eight One hundred and seventy-six Two 卡伦 邓克利 Thirty-nine
one 莎拉·雷纳 工程 Two thousand and eighteen F 14/04/1981 Eighty One hundred and sixty one 撒拉 雷纳 Forty
Two 珍妮特·斯隆 工程 Two thousand and twelve F 06/05/1997 Sixty-six One hundred and sixty-nine Zero 细斜纹布 斯隆 Twenty-four
three 凯茜·阿科斯塔 人力资源(部) Two thousand and fourteen F 08/01/1986 Sixty-seven One hundred and fifty-seven one 凯茜 阿科斯塔 Thirty-six
four 亨利康罗伊 人力资源(部) Two thousand and fourteen M 10/10/1988 Ninety One hundred and eighty-five one 亨利 康罗伊 Thirty-three
five 艾玛·佩拉尔塔 人力资源(部) Two thousand and eighteen F 12/11/1992 Fifty-seven One hundred and sixty-four Zero 女子名 佩拉尔塔 Twenty-nine
six 马丁·巴特 数据科学 Two thousand and twenty M 10/04/1991 One hundred and fifteen One hundred and ninety-five Two 马丁 屁股 Thirty
seven 艾利克斯詹森 数据科学 Two thousand and eighteen M 16/07/1995 Eighty-seven one hundred and eighty Zero 亚历克斯 詹森 Twenty-six
eight 金·豪沃思 会计 Two thousand and twenty M 08/10/1992 Ninety-five One hundred and seventy-four three 金姆(人名) 霍沃斯 Twenty-nine
nine 简·伯内特 数据科学 Two thousand and twelve F 11/10/1979 Fifty-seven One hundred and sixty-five one 简(女子名) 伯内特 forty-two

上面的单行语句对Birthdate列的每个元素应用了calculate_age()函数,并将返回值存储在Age列中。

最后一步是计算雇员的*均年龄,如下所示:

print(data['Age'].mean())
32.8

场景 3

该公司的人力资源经理正在探索为所有员工提供医疗保险的方案。潜在的提供商需要关于雇员的信息。由于数据框架包含每个员工的体重和身高,我们假设人力资源经理要求您提供每个员工的体重指数(身体质量指数),以便她可以从潜在的医疗保健提供商那里获得报价。

为了完成这项任务,首先,我们需要定义一个计算身体质量指数(身体质量指数)的函数。身体质量指数的公式是以千克为单位的重量除以以米为单位的高度的*方。因为员工的身高是以厘米为单位的,所以我们需要将身高除以 100 来获得以米为单位的身高。让我们实现这个函数:

def calc_bmi(weight, height):
    return np.round(weight/(height/100)**2, 2)

下一步是在数据帧上应用函数:

data['BMI'] = data.apply(lambda x: calc_bmi(x['Weight'], x['Height']), axis=1)

lambda 函数获取每一行的体重和身高值,然后对它们应用calc_bmi()函数来计算它们的 BMI。axis=1参数意味着迭代数据帧中的行。

display(data)
员工姓名 部门 你在说什么 出生年月日 重量 高度 小孩子 西方人名的第一个字 年龄 身体质量指数
Zero 卡伦·邓克利 会计 Two thousand and ten M 04/09/1982 seventy-eight One hundred and seventy-six Two 卡伦 邓克利 Thirty-nine Twenty-five point one eight
one 莎拉·雷纳 工程 Two thousand and eighteen F 14/04/1981 Eighty One hundred and sixty one 撒拉 雷纳 Forty Thirty-one point two five
Two 珍妮特·斯隆 工程 Two thousand and twelve F 06/05/1997 Sixty-six One hundred and sixty-nine Zero 细斜纹布 斯隆 Twenty-four Twenty-three point one one
three 凯茜·阿科斯塔 人力资源(部) Two thousand and fourteen F 08/01/1986 Sixty-seven One hundred and fifty-seven one 凯茜 阿科斯塔 Thirty-six Twenty-seven point one eight
four 亨利康罗伊 人力资源(部) Two thousand and fourteen M 10/10/1988 Ninety One hundred and eighty-five one 亨利 康罗伊 Thirty-three Twenty-six point three
five 艾玛·佩拉尔塔 人力资源(部) Two thousand and eighteen F 12/11/1992 Fifty-seven One hundred and sixty-four Zero 女子名 佩拉尔塔 Twenty-nine Twenty-one point one nine
six 马丁·巴特 数据科学 Two thousand and twenty M 10/04/1991 One hundred and fifteen One hundred and ninety-five Two 马丁 屁股 Thirty Thirty point two four
seven 艾利克斯詹森 数据科学 Two thousand and eighteen M 16/07/1995 Eighty-seven one hundred and eighty Zero 亚历克斯 詹森 Twenty-six Twenty-six point eight five
eight 金·豪沃思 会计 Two thousand and twenty M 08/10/1992 Ninety-five One hundred and seventy-four three 金姆(人名) 霍沃斯 Twenty-nine Thirty-one point three eight
nine 简·伯内特 数据科学 Two thousand and twelve F 11/10/1979 Fifty-seven One hundred and sixty-five one 简(女子名) 伯内特 forty-two Twenty point nine four

最后一步是根据身体质量指数度量对员工进行分类。低于 18.5 的身体质量指数是第一组,在 18.5 和 24.9 之间是第二组,在 25 和 29.9 之间是第三组,超过 30 是第四组。为了实现该解决方案,我们将定义一个返回各种身体质量指数指标的函数,然后将其应用于数据帧的BMI列,以查看每个员工属于哪个类别:

def indicator(bmi):
    if (bmi < 18.5):
        return 'Group One'
    elif (18.5 <= bmi < 25):
        return 'Group Two'
    elif (25 <= bmi < 30):
        return 'Group Three'
    else:
        return 'Group Four'

data['BMI_Indicator'] = data['BMI'].apply(indicator)
display(data)
员工姓名 部门 你在说什么 告发 重量 高度 小孩子 西方人名的第一个字 年龄 身体质量指数 身体质量指数指标
Zero 卡伦·邓克利 会计 Two thousand and ten M 04/09/1982 seventy-eight One hundred and seventy-six Two 卡伦 邓克利 Thirty-nine Twenty-five point one eight 第三组
one 莎拉·雷纳 工程 Two thousand and eighteen F 14/04/1981 Eighty One hundred and sixty one 撒拉 雷纳 Forty Thirty-one point two five 第四组
Two 珍妮特·斯隆 工程 Two thousand and twelve F 06/05/1997 Sixty-six One hundred and sixty-nine Zero 细斜纹布 斯隆 Twenty-four Twenty-three point one one 第二组
three 凯茜·阿科斯塔 人力资源(部) Two thousand and fourteen F 08/01/1986 Sixty-seven One hundred and fifty-seven one 凯茜 阿科斯塔 Thirty-six Twenty-seven point one eight 第三组
four 亨利康罗伊 人力资源(部) Two thousand and fourteen M 10/10/1988 Ninety One hundred and eighty-five one 亨利 康罗伊 Thirty-three Twenty-six point three 第三组
five 艾玛·佩拉尔塔 人力资源(部) Two thousand and eighteen F 12/11/1992 Fifty-seven One hundred and sixty-four Zero 女子名 佩拉尔塔 Twenty-nine Twenty-one point one nine 第二组
six 马丁·巴特 数据科学 Two thousand and twenty M 10/04/1991 One hundred and fifteen One hundred and ninety-five Two 马丁 屁股 Thirty Thirty point two four 第四组
seven 艾利克斯詹森 数据科学 Two thousand and eighteen M 16/07/1995 Eighty-seven one hundred and eighty Zero 亚历克斯 詹森 Twenty-six Twenty-six point eight five 第三组
eight 金·豪沃思 会计 Two thousand and twenty M 08/10/1992 Ninety-five One hundred and seventy-four three 金姆(人名) 霍沃斯 Twenty-nine Thirty-one point three eight 第四组
nine 简·伯内特 数据科学 Two thousand and twelve F 11/10/1979 Fifty-seven One hundred and sixty-five one 简(女子名) 伯内特 forty-two Twenty point nine four 第二组

场景 4

让我们假设新的一年即将来临,公司管理层已经宣布,那些有十年以上工作经验的员工将获得额外的奖金。人力资源经理想知道谁有资格获得奖金。

为了准备请求的信息,您需要对HireDate列应用下面的 lambda 函数,如果当前年份和雇佣年份之间的差值大于或等于十年,则返回True,否则返回False

mask = data['HireDate'].apply(lambda x: date.today().year - x >= 10)
print(mask)
0     True
1    False
2     True
3    False
4    False
5    False
6    False
7    False
8    False
9     True
Name: HireDate, dtype: bool

运行上面的代码创建一个包含TrueFalse值的 pandas 系列,称为布尔掩码。

为了显示合格的雇员,我们使用布尔掩码来过滤数据帧行。让我们运行下面的语句,看看结果:

display(data[mask])
员工姓名 部门 你在说什么 告发 重量 高度 小孩子 西方人名的第一个字 年龄 身体质量指数
Zero 卡伦·邓克利 会计 Two thousand and ten M 04/09/1982 seventy-eight One hundred and seventy-six Two 卡伦 邓克利 Thirty-nine Twenty-five point one eight
Two 珍妮特·斯隆 工程 Two thousand and twelve F 06/05/1997 Sixty-six One hundred and sixty-nine Zero 细斜纹布 斯隆 Twenty-four Twenty-three point one one
nine 简·伯内特 数据科学 Two thousand and twelve F 11/10/1979 Fifty-seven One hundred and sixty-five one 简(女子名) 伯内特 forty-two Twenty point nine four

场景 5

假设明天是母亲节,公司为所有有孩子的女员工策划了一份母亲节礼物。人力资源团队要求您准备一份有资格获得礼品的员工名单。为了完成这项任务,我们需要编写一个简单的 lambda 函数,它考虑到了SexKids列,以提供所需的结果,如下所示:

data[data.apply(lambda x: True if x ['Gender'] == 'F' and x['Kids'] > 0 else False, axis=1)]
员工姓名 部门 你在说什么 出生年月日 重量 高度 小孩子 西方人名的第一个字 年龄 身体质量指数
one 莎拉·雷纳 工程 Two thousand and eighteen F 14/04/1981 Eighty One hundred and sixty one 撒拉 雷纳 Forty Thirty-one point two five
three 凯茜·阿科斯塔 人力资源(部) Two thousand and fourteen F 08/01/1986 Sixty-seven One hundred and fifty-seven one 凯茜 阿科斯塔 Thirty-six Twenty-seven point one eight
nine 简·伯内特 数据科学 Two thousand and twelve F 11/10/1979 Fifty-seven One hundred and sixty-five one 简(女子名) 伯内特 forty-two Twenty point nine four

运行上面的代码会返回将收到礼物的员工列表。

如果女性雇员至少有一个孩子,lambda 函数返回True;否则返回False。对数据帧应用 lambda 函数的结果是一个布尔掩码,我们直接用它来过滤数据帧的行。

结论

在本教程中,我们通过不同的例子学习了apply()方法做什么以及如何使用它。apply()方法是一种强大而有效的方法,可以在 pandas 中对一个系列或数据帧的每个值应用函数。因为apply()方法使用 Python 的 C 扩展,所以当遍历 pandas 数据帧的所有行时,它执行得更快。但是,这并不是一个普遍的规则,因为当通过一列执行相同的操作时,它会变慢。

教程:如何用 Python 编写 For 循环

原文:https://www.dataquest.io/blog/tutorial-how-to-write-a-for-loop-in-python/

January 14, 2022How to Write a For Loop in Python

我们在日常生活中遇到的大多数任务都是重复的。虽然这些任务对人类来说可能会变得无聊,但计算机可以快速有效地处理重复的任务。我们将在本教程中学习如何操作。

本教程是为 Python 初学者编写的,但是如果你以前从未写过一行代码,你可能想要完成我们免费的 Python 基础课程,因为我们在这里不会涉及基本语法。
为了理解计算机是如何帮助我们完成重复性任务的,让我们通过一个场景将一万人按年龄分组。让我们假设这个过程需要两步:

  1. 查一下这个人的年龄
  2. 在适当的年龄组中记录

为了成功地将人口中的每个成员登记到他们的年龄组中,我们将完成一万次两步过程。当我们成功地完成这个过程一次,我们就迭代了一次。当我们对整个群体完成这个过程时,我们已经迭代了 10,000 次。

迭代是指多次执行这个两步过程。一次迭代之后,我们从头开始重复这个过程,形成一个循环。因此,循环是实现迭代的设置。

有两种类型的迭代:确定的和不确定的。使用确定的迭代,你可以遍历一个固定大小的对象。这意味着您从一开始就知道程序将执行的迭代次数。如果你从一开始就不知道迭代的次数,你需要某种形式的条件逻辑来终止循环,这就是一个不定迭代。

Python 中的 For 循环是什么?

在 Python 中,我们使用 for 循环来遍历可迭代对象和迭代器。这是一个明确迭代的示例,简单 Python for 循环操作的语法如下所示:

for item in iterable:
statement

并非所有数据类型都支持这种操作。我们称支持循环操作的数据类型为 iterables。可重复项包括字符串、列表、集合、冷冻集、字典、元组、数据帧、范围和数组。Iterables 是可以实现__iter__()__next()__方法的对象。

迭代器也支持循环操作。迭代器是在 iterable 上调用__iter__()方法时形成的数据类型。因此,所有的可迭代对象都有它们的迭代器。然而,有些数据类型在默认情况下是迭代器。示例包括 zip、枚举和生成器。迭代器只能实现__next__()方法。

需要多个 for 循环的操作嵌套在 for 循环操作中。带有两个 for 循环的操作的语法如下所示:

for item_one in iterable_one:
    for item_two in iterable_two:
        statement

for 循环有一个可选的 else 块,在 for 循环操作结束时执行。带有两个 for 循环操作的 for-else 语法如下所示:

for item_one in iterable_one:
    for item_two in iterable_two:
        statement:
    else:
        statement
else:
    statement

要将 iterable 转换为 iterator 对象,我们可以调用__iter__()方法或iter()函数。这两种操作的语法略有不同,但它们实现了相同的结果:

# Using the __iter__() method
iterable.__iter__()

# Using the iter() function
iter(iterable)

iter()函数调用__iter__()方法来执行这个转换。

当我们有一个迭代器对象时,我们可以用 __next__()方法或next()函数返回迭代器对象中的项目。该操作的语法如下所示:

# Using the __next__() method
iterator.__next__()

# Using the next() function
next(iterator, default)

next()函数调用__next__()方法返回迭代器中的下一项。当你穷尽了迭代器中的所有条目后,__next__()方法会抛出 StopIteration 异常,让你知道迭代器现在是空的。如果使用默认值调用next()函数,则不会引发该异常。而是返回默认值。

Python 中的 for 循环操作类似于一次或多次调用__iter__()方法和__next__()方法,直到迭代器中的项目用尽,我们将在下面几节中看到

遍历一个字符串

字符串是不可变数据类型的例子。一旦创建,其值就不能更新。我们发现,对于一个大小确定的对象,可以使用 for 循环。我们使用len()函数来获取字符串的大小。

string = "greatness"
len(string)  # 9

我们还声称字符串是可迭代的,它们可以实现 __iter__()__next__()方法。下面的代码片段显示了这些方法的实现:

string = "greatness"
string_iter = string.__iter__()
print(string_iter.__next__())
print(string_iter.__next__())
print(string_iter.__next__()) 

输出

g
r
e

__iter__()方法将数据类型转换为字符串迭代器,__next__()方法一个接一个地返回字符串中的项。如果您使用__next__()方法足够多次,您将返回字符串中的所有项目。

我们可以使用 for 循环执行类似的操作。有两种方法可以做到这一点。在第一种方法中,您遍历字符串:

# Looping through a string
for item in string:
    print(item)

输出

g
r
e
a
t
n
e
s
s

在第二种方法中,循环遍历字符串的索引,并使用索引值访问字符串中的项。我们用 range 函数从字符串的大小得到它的索引。

输出

g
r
e
a
t
n
e
s
s

遍历列表

与字符串不同,列表是可变的。像字符串一样,它们是可重复的。因此,列表实现了__len__()__iter__()__next__()方法:

list_string = ['programming', 'marathon', 'not', 'sprint']

print(list_string.__len__())

list_iter = list_string.__iter__()
print(list_iter.__next__())
print(list_iter.__next__())
print(list_iter.__next__())
print(list_iter.__next__())

输出

4
编程
马拉松

短跑

__iter__()方法将列表对象转换为列表迭代器,__next__()方法一个接一个地返回迭代器中的项目。我们可以使用 for 循环对列表执行类似的操作:

for item in list_string:
    print(item)

输出

编程
马拉松
不是
短跑

遍历一个元组

元组是由逗号分隔的对象的集合。它的操作非常类似于列表,但是元组是不可变的。元组是可迭代的,它实现了__len__()__iter__()__next__()方法:

tuple_string = 'programming', 'marathon', 'not', 'sprint'

print(tuple_string.__len__())

tuple_iter = tuple_string.__iter__()
print(tuple_iter.__next__())
print(tuple_iter.__next__())
print(tuple_iter.__next__())
print(tuple_iter.__next__())

输出

4
编程
马拉松

短跑

iter()函数将元组对象转换为元组迭代器,next()函数一个接一个地返回迭代器中的项。我们可以用 for 循环执行类似的操作:

for item in tuple_string:
    print(item)

输出

编程
马拉松
不是
短跑

遍历 Set 和 Frozenset

在 Python 中,set 是一个可变对象。Frozenset 与 set 具有相似的属性,但它是不可变的。Set 和 frozenset 有确定的大小,它们是可迭代的。因此,在 set 和 frozenset 对象上使用next()iter()函数的组合会产生类似于 for 循环的结果:

set_object = set(['programming', 'marathon', 'not', 'sprint'])

set_iter = iter(set_object)
print(next(set_iter))
print(next(set_iter))
print(next(set_iter))
print(next(set_iter))

frozenset_object = frozenset(['programming', 'marathon', 'not', 'sprint'])

frozenset_iter = iter(frozenset_object)
print(next(frozenset_iter))
print(next(frozenset_iter))
print(next(frozenset_iter))
print(next(frozenset_iter))

for item in set_object:
    print(item)

for item in frozenset_object:
    print(item)

输出

不是
编程
冲刺
马拉松

iter()函数将 set 和 frozenset 对象转换为集合迭代器,而next()函数一个接一个地返回集合迭代器中的项。

遍历字典

Python 字典是将数据存储为键值对的对象。字典的键必须是不可变的数据类型。因此,字符串、整数和元组是合适的字典键。字典中的条目可以用它们的关键字来评估。像之前讨论的 iterables 一样,字典有一个确定的大小,并实现了iter()next()函数:

dict_score = {'Kate': [85, 90, 95], 'Maria':[92, 90, 94], 'Ben':[92, 85, 91]}

print(len(dict_score))

dict_iter = iter(dict_score)
print(next(dict_iter))
print(next(dict_iter))
print(next(dict_iter))

输出

凯特玛利亚 T2 本

函数将 dictionary 对象转换成 dictionary_keyiterator。从新对象的名称和输出中可以注意到,下一个操作迭代了字典键。我们可以使用 for 循环执行类似的操作:

for key in dict_score:
    print(key)

输出

凯特玛丽亚本

还可以使用以下语法迭代字典值:

for value in dict_score.values():
    print(value)

输出

[85,90,95]
【92,90,94】
【92,85,91】

上述代码片段的输出是一个 Python 列表,它是一个可迭代的列表。当我们有两个 iterables 时,我们可以应用嵌套的 for 循环语法:

for value in dict_score.values():
    for item in value:
        print(item)

输出

85
90
95
92
90
94
92
85
91

我们已经看到了如何分别迭代字典的键和值的例子。但是,我们可以使用以下语法同时完成这两项操作:

for key, value in dict_score.items():
    print(key)
    for item in value:
        print(item)

输出

凯特
85
90
95
玛利亚
92
90
94

92
85
91

遍历 Zip

我们使用zip()函数进行并行迭代。这意味着您可以同时迭代两个或更多的 iterables。它返回一个元组,其中包含通过它传递的 iterables 的并行项:

import string

letters = string.ascii_lowercase[:5]  # abcde
index = range(len(letters))  # 01234

for tup in zip(index, letters):
    print(tup)

输出

(0,' a')
(1,' b')
(2,' c')
(3,' d')
(4,' e ')

与我们看到的用iter()函数将可迭代对象转换成迭代器的其他可迭代对象不同,zip()函数不需要这一步,因为它是迭代器。它使用next()函数以元组的形式一个接一个地返回项目

zip_iter = zip(index, letters)
print(next(zip_iter))
print(next(zip_iter))
print(next(zip_iter))

输出

(0,' a')
(1,' b')
(2,' c ')

我们还可以使用zip()函数对不同大小的迭代进行并行迭代:

letters = ['a', 'b', 'c']  # length equals 3
numbers = [1, 2, 3, 4]  # length equals 4

for tup in zip(letters, numbers):
    print(tup)

输出

(' a ',1)
('b ',2)
('c ',3)

结果的大小等于最小的 iterable 的大小,如上面的代码片段所示。

遍历数据帧

pandas 数据帧是可变的二维表格数据结构。它是可迭代的,实现了len()iter()next()函数:

import pandas as pd

df = pd.DataFrame({
    'Physics': [90, 92, 89, 94],
    'Math': [100, 98, 100, 99]}, 
    index=['Oliver', 'Angela', 'Coleman', 'Agatha'])

df_iter = iter(df)
print(next(df_iter))
print(next(df_iter))

输出

物理
数学

函数将一个数据帧转换成一个 map 对象,它是一个迭代器。next()函数返回列名。这类似于使用 for 循环:

for col in df:
    print(col)

输出

物理
数学

我们可能想要迭代数据帧的行,而不是它们的列名。我们可以用df.iterrows()方法来实现。它从索引开始返回每行的元组:

for row in df.iterrows():
    print(row)

输出

('奥利弗',物理 90
数学 100
姓名:奥利弗,dtype: int64)
('安吉拉',物理 92
数学 98
姓名:安吉拉,dtype: int64)
('科尔曼',物理 89
数学 100
姓名:科尔曼,dtype: int64)
('阿加莎',物理 94
数学 99
姓名:阿加莎,dtype: int64)

遍历枚举

zip()函数一样,enumerate()函数也是一个迭代器。因此,它不需要iter()功能来操作。我们用它来迭代一个 iterable。它返回一个包含索引和与该索引相关联的值的元组。我们使用 start 属性来设置起始索引值

list_string = ['programming', 'marathon', 'not', 'sprint']

list_enumerate = enumerate(list_string, start=5)
print(next(list_enumerate))
print(next(list_enumerate))
print(next(list_enumerate))
print(next(list_enumerate))

输出

(5,'编程')
(6,'马拉松')
(7,'非')
(8,'冲刺')

我们可以使用 for 循环得到类似的结果:

for item in enumerate(list_string, start=5):
    print(item)

输出

(5,'编程')
(6,'马拉松')
(7,'非')
(8,'冲刺')

遍历生成器

在 Python 中,生成器是一个特殊的函数,它使用 yield 而不是 return 语句。我们称之为懒惰迭代器,因为它不会将所有内容都存储在内存中;当调用next()函数时,它一个接一个地返回它们:

def square_generator(n):
    for i in range(1, n+1):
        yield i**2

squares = square_generator(4)
print(next(squares))
print(next(squares))
print(next(squares))
print(next(squares))

输出

1
4
9
16
我们可以用 for 循环实现相同的结果:

for item in square_generator(4):
    print(item)

输出

1
4
9
16

对于循环和列表的理解

列表理解是一种从任何 iterable 创建列表的方法。这是一种简化以 append 语句结束的 for 循环的有趣方法。

import string

string_iter = string.ascii_lowercase[:5]

ord_list = []
for item in string_iter:
    ord_list.append(ord(item))

print(ord_list)

输出

[97, 98, 99, 100, 101]

上面的代码片段写为一个列表理解看起来像这样:

ord_list = [ord(item) for item in string_iter]
print(ord_list)

输出

[97, 98, 99, 100, 101]

我们使用列表理解来简化嵌套的 for 循环,如下所示:

multiples_two = [2, 4, 6, 8]
multiples_three = [3, 6, 9, 12]

result = []
for first in multiples_two:
    for second in multiples_three:
        result.append(first*second)     
print(result)

输出

[6, 12, 18, 24, 12, 24, 36, 48, 18, 36, 54, 72, 24, 48, 72, 96]

根据列表理解,我们使用以下代码片段:

result = [first * second for first in multiples_two for second in multiples_three]
print(result)

输出

[6, 12, 18, 24, 12, 24, 36, 48, 18, 36, 54, 72, 24, 48, 72, 96]

集合理解和列表理解非常相似。输出数据类型是集合而不是列表。表示为集合理解的嵌套 for 循环示例是:

result = {first * second for first in multiples_two for second in multiples_three}
print(result)

输出

{6, 12, 18, 24, 36, 48, 54, 72, 96}

集合理解的结果比列表理解的结果小;这是因为器械包中的物品不会重复。

For 循环中的 Break 和 Continue 关键字

有时,我们希望在满足某个条件时终止循环的执行。我们使用 break 关键字来中断循环。例如:

result = [6, 12, 18, 24, 12, 24, 36, 48, 18, 36, 54, 72, 24, 48, 72, 96]

for item in result:
    if item > 40:
        break
    print(item)

输出

6
12
18
24
12
24
36

break 关键字停止 for 循环的执行,但有时我们可能希望跳过满足特定条件的项,继续我们的迭代。这就是 continue key 工作的作用。

for item in result:
    if item > 40:
        continue
    print(item)

输出

6
12
18
24
12
24
36
18
36
24

For-Else 循环

for 循环有一个可选的 else 语句。如果 for 循环在执行过程中没有遇到 break 关键字,else 块就会执行。例如:

result = [6, 12, 18, 24, 12, 24, 36, 48, 18, 36, 54, 72, 24, 48, 72, 96]

for item in result:
    if item > 40:
        break
    print(item)
else:
    print('For loop does not encounter break keyword')

输出

6
12
18
24
12
24
36

在上面的代码片段中,for 循环遇到了 break 关键字,并终止了循环的执行。因此,else 块不会执行。如果没有 break 关键字,else 块将在 for 循环之后执行:

for item in result:
    if item > 40:
        continue
    print(item)
else:
    print('For loop does not encounter break keyword')

输出

6
12
18
24
12
24
36
18
36
24

For 循环没有遇到 break 关键字

结论

我们使用 for 循环来遍历一个大小确定的 iterable。__int__()方法将 iterable 转换成它的迭代器,而__next__()方法一个接一个地返回 iterable 中的项目。for 循环操作类似于在 iterable 上调用__iter__()方法来获取迭代器,以及一次或多次调用__next__()方法直到迭代器为空。我们讨论了简单的和嵌套的 for 循环,for 循环如何用于不同的数据类型,以及 break 和 continue 关键字如何在循环中工作。

如果您想继续探索 Python 中的循环,请查看另一个 Dataquest 初学者教程。要更深入地了解 Python 中的 For 循环,请查看这个 Dataquest 教程。

教程:在 Pandas 中索引数据帧

原文:https://www.dataquest.io/blog/tutorial-indexing-dataframes-in-pandas/

February 15, 2022

在本教程中,我们将讨论索引熊猫数据帧意味着什么,为什么我们需要它,存在什么样的数据帧索引,以及应该使用什么语法来选择不同的子集。

什么是熊猫的索引数据帧?

索引熊猫数据帧意味着从该数据帧中选择特定的数据子集(如行、列、单个单元格)。Pandas 数据帧具有由行和列表示的固有表格结构,其中每一行和列在数据帧内具有唯一的标签(名称)和位置编号(类似于坐标),并且每个数据点通过其在特定行和列的交叉点处的位置来表征。

称为数据帧索引的行标签可以是整数或字符串值,称为列名的列标签通常是字符串。由于数据帧索引和列名只包含唯一的值,我们可以使用这些标签来引用数据帧的特定行、列或数据点。另一方面,我们可以通过每个行、列或数据点在数据帧结构中的位置来描述它们。位置编号是整数,从第一行或第一列的 0 开始,随后每一行/列增加 1,因此它们也可以用作特定数据帧元素(行、列或数据点)的唯一坐标。这种通过标签或位置号引用数据帧元素的能力正是使数据帧索引成为可能的原因。

pandas 数据帧索引的一个具体(实际上也是最常见的)例子是切片。它用于访问数据帧元素的序列,而不是单个数据帧元素

Pandas 数据帧索引可用于各种任务:根据预定义的标准提取数据子集、重新组织数据、获取数据样本、数据操作、修改数据点的值等。

为了从数据帧中选择一个子集,我们使用了索引操作符[]、属性操作符.,以及熊猫数据帧索引的适当方法,例如locilocatiat以及其他一些方法。

本质上,有两种主要的方法来索引熊猫数据帧:基于标签的基于位置的(又名基于位置的基于整数的)。此外,可以根据预定义的条件应用布尔数据帧索引,甚至混合不同类型的数据帧索引。让我们详细考虑一下所有这些方法。

为了进一步的实验,我们将创建一个假的数据帧:

import pandas as pd

df = pd.DataFrame({'col_1': list(range(1, 11)), 'col_2': list(range(11, 21)), 'col_3': list(range(21, 31)),
                   'col_4': list(range(31, 41)), 'col_5': list(range(41, 51)), 'col_6': list(range(51, 61)),
                   'col_7': list(range(61, 71)), 'col_8': list(range(71, 81)), 'col_9': list(range(81, 91))})
df
第一栏 第二栏 第三栏 col_4 第五栏 col_6 col_7 第八栏 col_9
Zero one Eleven Twenty-one Thirty-one Forty-one Fifty-one Sixty-one Seventy-one Eighty-one
one Two Twelve Twenty-two Thirty-two forty-two fifty-two Sixty-two seventy-two Eighty-two
Two three Thirteen Twenty-three Thirty-three Forty-three Fifty-three Sixty-three Seventy-three Eighty-three
three four Fourteen Twenty-four Thirty-four forty-four Fifty-four Sixty-four Seventy-four Eighty-four
four five Fifteen Twenty-five Thirty-five Forty-five Fifty-five Sixty-five Seventy-five eighty-five
five six Sixteen Twenty-six Thirty-six Forty-six fifty-six Sixty-six Seventy-six Eighty-six
six seven Seventeen Twenty-seven Thirty-seven Forty-seven Fifty-seven Sixty-seven Seventy-seven Eighty-seven
seven eight Eighteen Twenty-eight Thirty-eight Forty-eight Fifty-eight sixty-eight seventy-eight Eighty-eight
eight nine Nineteen Twenty-nine Thirty-nine forty-nine Fifty-nine sixty-nine Seventy-nine eighty-nine
nine Ten Twenty Thirty Forty Fifty Sixty Seventy Eighty Ninety

基于标签的数据帧索引

顾名思义,这种方法意味着根据行和列标签选择数据帧子集。让我们探索四种基于标签的数据帧索引方法:使用索引操作符[]、属性操作符.loc索引器和at索引器。

使用索引运算符

如果我们需要从 pandas 数据帧的一列或多列中选择所有数据,我们可以简单地使用索引操作符[]。为了从单个列中选择所有数据,我们传递该列的名称:

df['col_2']
0    11
1    12
2    13
3    14
4    15
5    16
6    17
7    18
8    19
9    20
Name: col_2, dtype: int64

最终的对象是一个熊猫系列。相反,如果我们想要一个单列数据帧作为输出,我们需要包含第二对方括号[[]]:

print(df[['col_2']])
type(df[['col_2']])
 col_2
0     11
1     12
2     13
3     14
4     15
5     16
6     17
7     18
8     19
9     20

pandas.core.frame.DataFrame

也可以从 pandas 数据帧中选择多个列,并将列名列表传递给索引操作符。此操作的结果将始终是一个熊猫数据帧:

df[['col_5', 'col_1', 'col_8']]
第五栏 第一栏 第八栏
Zero Forty-one one Seventy-one
one forty-two Two seventy-two
Two Forty-three three Seventy-three
three forty-four four Seventy-four
four Forty-five five Seventy-five
five Forty-six six Seventy-six
six Forty-seven seven Seventy-seven
seven Forty-eight eight seventy-eight
eight forty-nine nine Seventy-nine
nine Fifty Ten Eighty

正如我们所看到的,列在输出数据帧中出现的顺序与列表中的顺序相同。当我们想要重新组织原始数据时,这是很有帮助的。

如果所提供的列表中至少有一个列名不在数据帧中,将抛出KeyError:

df[['col_5', 'col_1', 'col_8', 'col_100']]
---------------------------------------------------------------------------

KeyError                                  Traceback (most recent call last)

~\AppData\Local\Temp/ipykernel_2100/3615225278.py in <module>
----> 1 df[['col_5', 'col_1', 'col_8', 'col_100']]

~\anaconda3\lib\site-packages\pandas\core\frame.py in __getitem__(self, key)
   3462             if is_iterator(key):
   3463                 key = list(key)
-> 3464             indexer = self.loc._get_listlike_indexer(key, axis=1)[1]
   3465 
   3466         # take() does not accept boolean indexers

~\anaconda3\lib\site-packages\pandas\core\indexing.py in _get_listlike_indexer(self, key, axis)
   1312             keyarr, indexer, new_indexer = ax._reindex_non_unique(keyarr)
   1313 
-> 1314         self._validate_read_indexer(keyarr, indexer, axis)
   1315 
   1316         if needs_i8_conversion(ax.dtype) or isinstance(

~\anaconda3\lib\site-packages\pandas\core\indexing.py in _validate_read_indexer(self, key, indexer, axis)
   1375 
   1376             not_found = list(ensure_index(key)[missing_mask.nonzero()[0]].unique())
-> 1377             raise KeyError(f"{not_found} not in index")
   1378 
   1379 

KeyError: "['col_100'] not in index"

使用属性运算符

要仅选择数据帧中的一列,我们可以通过其名称作为属性直接访问它:

df.col_3
0    21
1    22
2    23
3    24
4    25
5    26
6    27
7    28
8    29
9    30
Name: col_3, dtype: int64

上面这段代码相当于df['col_3']:

df['col_3']
0    21
1    22
2    23
3    24
4    25
5    26
6    27
7    28
8    29
9    30
Name: col_3, dtype: int64

然而,将列作为属性访问的方法有很多缺点。它不适用于以下情况:

  • 如果列名包含空格或标点符号(下划线_除外),
  • 如果列名与 pandas 方法名一致(例如,“max”、“first”、“sum”),
  • 如果列名不是字符串类型(尽管使用这样的列名通常不是一个好的做法),
  • 为了选择多列,
  • 用于创建新列(在这种情况下,尝试使用属性访问只会创建新属性而不是新列)。

让我们暂时在数据帧的列名中引入一些混乱,看看如果我们尝试使用属性 access 会发生什么:

df.columns = ['col_1', 'col_2', 'col_3', 'col_4', 'col_5', 'col 6', 'col-7', 8, 'last']
df.columns
Index(['col_1', 'col_2', 'col_3', 'col_4', 'col_5', 'col 6', 'col-7', 8,
       'last'],
      dtype='object')

上面,我们更改了最后四列的名称。现在,让我们看看如何在这些列上使用属性操作符:

# The column name contains a white space
df.col 6
 File "C:\Users\Utente\AppData\Local\Temp/ipykernel_2100/3654995016.py", line 2
    df.col 6
           ^
SyntaxError: invalid syntax
# The column name contains a punctuation mark (except for the underscore)
df.col-7
---------------------------------------------------------------------------

AttributeError                            Traceback (most recent call last)

~\AppData\Local\Temp/ipykernel_2100/1932640420.py in <module>
      1 # The column name contains a punctuation mark
----> 2 df.col-7

~\anaconda3\lib\site-packages\pandas\core\generic.py in __getattr__(self, name)
   5485         ):
   5486             return self[name]
-> 5487         return object.__getattribute__(self, name)
   5488 
   5489     def __setattr__(self, name: str, value) -> None:

AttributeError: 'DataFrame' object has no attribute 'col'
# The column name coincides with a pandas method name
df.last
 <bound method NDFrame.last of    col_1  col_2  col_3  col_4  col_5  col 6  col-7   8  last
    0      1     11     21     31     41     51     61  71    81
    1      2     12     22     32     42     52     62  72    82
    2      3     13     23     33     43     53     63  73    83
    3      4     14     24     34     44     54     64  74    84
    4      5     15     25     35     45     55     65  75    85
    5      6     16     26     36     46     56     66  76    86
    6      7     17     27     37     47     57     67  77    87
    7      8     18     28     38     48     58     68  78    88
    8      9     19     29     39     49     59     69  79    89
    9     10     20     30     40     50     60     70  80    90>
# The column name is not a string 
df.8
 File "C:\Users\Utente\AppData\Local\Temp/ipykernel_2100/2774159673.py", line 2
    df.8
      ^
SyntaxError: invalid syntax
# An attempt to create a new column using the attribute access
df.new = 0
print(df.new)     # an attribute was created
print(df['new'])  # a column was not created
0

---------------------------------------------------------------------------

KeyError                                  Traceback (most recent call last)

~\anaconda3\lib\site-packages\pandas\core\indexes\base.py in get_loc(self, key, method, tolerance)
   3360             try:
-> 3361                 return self._engine.get_loc(casted_key)
   3362             except KeyError as err:

~\anaconda3\lib\site-packages\pandas\_libs\index.pyx in pandas._libs.index.IndexEngine.get_loc()

~\anaconda3\lib\site-packages\pandas\_libs\index.pyx in pandas._libs.index.IndexEngine.get_loc()

pandas\_libs\hashtable_class_helper.pxi in pandas._libs.hashtable.PyObjectHashTable.get_item()

pandas\_libs\hashtable_class_helper.pxi in pandas._libs.hashtable.PyObjectHashTable.get_item()

KeyError: 'new'

The above exception was the direct cause of the following exception:

KeyError                                  Traceback (most recent call last)

~\AppData\Local\Temp/ipykernel_2100/2886494677.py in <module>
      2 df.new = 0
      3 print(df.new)     # an attribute was created
----> 4 print(df['new'])  # a column was not created

~\anaconda3\lib\site-packages\pandas\core\frame.py in __getitem__(self, key)
   3456             if self.columns.nlevels > 1:
   3457                 return self._getitem_multilevel(key)
-> 3458             indexer = self.columns.get_loc(key)
   3459             if is_integer(indexer):
   3460                 indexer = [indexer]

~\anaconda3\lib\site-packages\pandas\core\indexes\base.py in get_loc(self, key, method, tolerance)
   3361                 return self._engine.get_loc(casted_key)
   3362             except KeyError as err:
-> 3363                 raise KeyError(key) from err
   3364 
   3365         if is_scalar(key) and isna(key) and not self.hasnans:

KeyError: 'new'

注意,在上述所有情况下,语法df[column_name]都可以完美地工作。此外,在整个项目中使用相同的编码风格,包括数据帧索引的方式,提高了整体代码的可读性,因此保持一致并坚持更通用的风格是有意义的(在我们的例子中,使用方括号[])。

让我们恢复原来的列名,并继续下一种基于标签的数据帧索引方法:

df.columns = ['col_1', 'col_2', 'col_3', 'col_4', 'col_5', 'col_6', 'col_7', 'col_8', 'col_9']
df
第一栏 第二栏 第三栏 col_4 第五栏 col_6 col_7 第八栏 col_9
Zero one Eleven Twenty-one Thirty-one Forty-one Fifty-one Sixty-one Seventy-one Eighty-one
one Two Twelve Twenty-two Thirty-two forty-two fifty-two Sixty-two seventy-two Eighty-two
Two three Thirteen Twenty-three Thirty-three Forty-three Fifty-three Sixty-three Seventy-three Eighty-three
three four Fourteen Twenty-four Thirty-four forty-four Fifty-four Sixty-four Seventy-four Eighty-four
four five Fifteen Twenty-five Thirty-five Forty-five Fifty-five Sixty-five Seventy-five eighty-five
five six Sixteen Twenty-six Thirty-six Forty-six fifty-six Sixty-six Seventy-six Eighty-six
six seven Seventeen Twenty-seven Thirty-seven Forty-seven Fifty-seven Sixty-seven Seventy-seven Eighty-seven
seven eight Eighteen Twenty-eight Thirty-eight Forty-eight Fifty-eight sixty-eight seventy-eight Eighty-eight
eight nine Nineteen Twenty-nine Thirty-nine forty-nine Fifty-nine sixty-nine Seventy-nine eighty-nine
nine Ten Twenty Thirty Forty Fifty Sixty Seventy Eighty Ninety

使用loc步进器

如果我们不仅需要从数据帧中选择列,还需要选择行(或者只选择行),我们可以使用loc方法,也称为loc索引器。这种方法也意味着使用索引操作符[]。这是通过标签访问数据帧行和列的最常见方式。

在继续之前,让我们看看数据帧的当前标签是什么。为此,我们将使用属性columnsindex:

print(df.columns)
print(df.index)
Index(['col_1', 'col_2', 'col_3', 'col_4', 'col_5', 'col_6', 'col_7', 'col_8',
       'col_9'],
      dtype='object')
RangeIndex(start=0, stop=10, step=1)

注意,数据帧行的标签由一种特定类型的对象表示,在我们的例子中,它由从 0 到 9 的有序整数组成。这些整数是有效的行标签,它们可以在应用loc索引器时使用。例如,要提取带有标签 0 的行,这也是我们的数据帧的第一行,我们可以使用以下语法:

df.loc[0]
col_1     1
col_2    11
col_3    21
col_4    31
col_5    41
col_6    51
col_7    61
col_8    71
col_9    81
Name: 0, dtype: int64

一般来说,语法df.loc[row_label]用于从数据帧中提取特定的行作为熊猫系列对象。

然而,为了进一步实验loc索引器,让我们将行标签重命名为更有意义的字符串数据类型:

df.index = ['row_1', 'row_2', 'row_3', 'row_4', 'row_5', 'row_6', 'row_7', 'row_8', 'row_9', 'row_10']
df
第一栏 第二栏 第三栏 col_4 第五栏 col_6 col_7 第八栏 col_9
第 1 行 one Eleven Twenty-one Thirty-one Forty-one Fifty-one Sixty-one Seventy-one Eighty-one
第 2 行 Two Twelve Twenty-two Thirty-two forty-two fifty-two Sixty-two seventy-two Eighty-two
第三行 three Thirteen Twenty-three Thirty-three Forty-three Fifty-three Sixty-three Seventy-three Eighty-three
第 4 行 four Fourteen Twenty-four Thirty-four forty-four Fifty-four Sixty-four Seventy-four Eighty-four
第 5 行 five Fifteen Twenty-five Thirty-five Forty-five Fifty-five Sixty-five Seventy-five eighty-five
第 6 行 six Sixteen Twenty-six Thirty-six Forty-six fifty-six Sixty-six Seventy-six Eighty-six
第 7 行 seven Seventeen Twenty-seven Thirty-seven Forty-seven Fifty-seven Sixty-seven Seventy-seven Eighty-seven
第 8 行 eight Eighteen Twenty-eight Thirty-eight Forty-eight Fifty-eight sixty-eight seventy-eight Eighty-eight
第 9 行 nine Nineteen Twenty-nine Thirty-nine forty-nine Fifty-nine sixty-nine Seventy-nine eighty-nine
第 10 行 Ten Twenty Thirty Forty Fifty Sixty Seventy Eighty Ninety

让我们使用loc索引器和新的行标签再次提取数据帧第一行的值:

df.loc['row_1']
col_1     1
col_2    11
col_3    21
col_4    31
col_5    41
col_6    51
col_7    61
col_8    71
col_9    81
Name: row_1, dtype: int64

如果我们想要访问多个不同的行,不一定按照原始数据帧中的顺序,我们必须传递一个行标签列表:

df.loc[['row_6', 'row_2', 'row_9']]
第一栏 第二栏 第三栏 col_4 第五栏 col_6 col_7 第八栏 col_9
第 6 行 six Sixteen Twenty-six Thirty-six Forty-six fifty-six Sixty-six Seventy-six Eighty-six
第 2 行 Two Twelve Twenty-two Thirty-two forty-two fifty-two Sixty-two seventy-two Eighty-two
第 9 行 nine Nineteen Twenty-nine Thirty-nine forty-nine Fifty-nine sixty-nine Seventy-nine eighty-nine

结果数据帧中的行以与列表中相同的顺序出现。

如果提供的列表中至少有一个标签不在数据帧中,将抛出KeyError:

df.loc[['row_6', 'row_2', 'row_100']]
---------------------------------------------------------------------------

KeyError                                  Traceback (most recent call last)

~\AppData\Local\Temp/ipykernel_2100/3587512526.py in <module>
----> 1 df.loc[['row_6', 'row_2', 'row_100']]

~\anaconda3\lib\site-packages\pandas\core\indexing.py in __getitem__(self, key)
    929 
    930             maybe_callable = com.apply_if_callable(key, self.obj)
--> 931             return self._getitem_axis(maybe_callable, axis=axis)
    932 
    933     def _is_scalar_access(self, key: tuple):

~\anaconda3\lib\site-packages\pandas\core\indexing.py in _getitem_axis(self, key, axis)
   1151                     raise ValueError("Cannot index with multidimensional key")
   1152 
-> 1153                 return self._getitem_iterable(key, axis=axis)
   1154 
   1155             # nested tuple slicing

~\anaconda3\lib\site-packages\pandas\core\indexing.py in _getitem_iterable(self, key, axis)
   1091 
   1092         # A collection of keys
-> 1093         keyarr, indexer = self._get_listlike_indexer(key, axis)
   1094         return self.obj._reindex_with_indexers(
   1095             {axis: [keyarr, indexer]}, copy=True, allow_dups=True

~\anaconda3\lib\site-packages\pandas\core\indexing.py in _get_listlike_indexer(self, key, axis)
   1312             keyarr, indexer, new_indexer = ax._reindex_non_unique(keyarr)
   1313 
-> 1314         self._validate_read_indexer(keyarr, indexer, axis)
   1315 
   1316         if needs_i8_conversion(ax.dtype) or isinstance(

~\anaconda3\lib\site-packages\pandas\core\indexing.py in _validate_read_indexer(self, key, indexer, axis)
   1375 
   1376             not_found = list(ensure_index(key)[missing_mask.nonzero()[0]].unique())
-> 1377             raise KeyError(f"{not_found} not in index")
   1378 
   1379 

KeyError: "['row_100'] not in index"

我们可能需要从原始数据帧中选择多个连续的行,而不是选择不同的行。在这种情况下,我们可以应用切片,即指定由冒号分隔的开始和结束行标签:

df.loc['row_7':'row_9']
第一栏 第二栏 第三栏 col_4 第五栏 col_6 col_7 第八栏 col_9
第 7 行 seven Seventeen Twenty-seven Thirty-seven Forty-seven Fifty-seven Sixty-seven Seventy-seven Eighty-seven
第 8 行 eight Eighteen Twenty-eight Thirty-eight Forty-eight Fifty-eight sixty-eight seventy-eight Eighty-eight
第 9 行 nine Nineteen Twenty-nine Thirty-nine forty-nine Fifty-nine sixty-nine Seventy-nine eighty-nine

注意,对于loc索引器,的开始和停止边界都是包含的,这不是 Python 中常见的切片风格,通常停止边界是唯一的。

如果我们需要所有的行,包括特定行的(例如df.loc[:'row_4']),或者从特定行开始,直到末端(例如df.loc['row_4':]),可以将其中一个切片末端打开:

# Selecting all the rows up to and including 'row_4'
df.loc[:'row_4']
第一栏 第二栏 第三栏 col_4 第五栏 col_6 col_7 第八栏 col_9
第 1 行 one Eleven Twenty-one Thirty-one Forty-one Fifty-one Sixty-one Seventy-one Eighty-one
第 2 行 Two Twelve Twenty-two Thirty-two forty-two fifty-two Sixty-two seventy-two Eighty-two
第三行 three Thirteen Twenty-three Thirty-three Forty-three Fifty-three Sixty-three Seventy-three Eighty-three
第 4 行 four Fourteen Twenty-four Thirty-four forty-four Fifty-four Sixty-four Seventy-four Eighty-four

为了选择多行和多列,实际上意味着这些行和列的交叉点处的数据帧的数据点的子集,我们向loc索引器传递两个由逗号分隔的参数:必要的行标签和列标签。对于行标签和列标签,它可以是一个标签列表、一个标签片(或一个开放标签片)或一个字符串形式的单个标签。一些例子:

df.loc[['row_4', 'row_2'], ['col_5', 'col_2', 'col_9']]
第五栏 第二栏 col_9
第 4 行 forty-four Fourteen Eighty-four
第 2 行 forty-two Twelve Eighty-two
df.loc[['row_4', 'row_2'], 'col_5':'col_7']
第五栏 col_6 col_7
第 4 行 forty-four Fifty-four Sixty-four
第 2 行 forty-two fifty-two Sixty-two
df.loc['row_4':'row_6', 'col_5':]
第五栏 col_6 col_7 第八栏 col_9
第 4 行 forty-four Fifty-four Sixty-four Seventy-four Eighty-four
第 5 行 Forty-five Fifty-five Sixty-five Seventy-five eighty-five
第 6 行 Forty-six fifty-six Sixty-six Seventy-six Eighty-six
df.loc[:'row_4', 'col_5']
row_1    41
row_2    42
row_3    43
row_4    44
Name: col_5, dtype: int64

一个特殊的情况是当我们需要从一个数据帧中显式地获取一个值时。为此,我们将必要的行和列标签作为由逗号分隔的两个参数进行传递:

df.loc['row_6', 'col_3']
26

使用at步进器

对于上一节的最后一种情况,即从数据帧中只选择一个值,有一种更快的方法——使用at索引器。语法与loc索引器的语法相同,只是这里我们总是使用由逗号分隔的两个标签(对于行和列):

df.at['row_6', 'col_3']
26

基于位置的数据帧索引

使用这种方法,也称为基于位置或基于整数的方法,每个 dataframe 元素(行、列或数据点)通过其位置编号而不是标签来引用。位置号是整数,从第一行或第一列的 0 开始(典型的基于 Python 0 的索引),随后的每一行/列增加 1。

基于标签和基于位置的数据帧索引方法之间的关键区别在于数据帧切片的方式:对于基于位置的索引,它是纯 Python 风格的,即范围的开始界限是包含性的,而停止界限是排他性的。对于基于标签的索引,停止界限是包含

使用索引运算符

要从 pandas 数据帧的多个连续行中检索所有数据,我们可以简单地使用索引操作符[]和一系列必要的行位置(可以是一个开放的范围):

df[3:6]
第一栏 第二栏 第三栏 col_4 第五栏 col_6 col_7 第八栏 col_9
第 4 行 four Fourteen Twenty-four Thirty-four forty-four Fifty-four Sixty-four Seventy-four Eighty-four
第 5 行 five Fifteen Twenty-five Thirty-five Forty-five Fifty-five Sixty-five Seventy-five eighty-five
第 6 行 six Sixteen Twenty-six Thirty-six Forty-six fifty-six Sixty-six Seventy-six Eighty-six
df[:3]
第一栏 第二栏 第三栏 col_4 第五栏 col_6 col_7 第八栏 col_9
第 1 行 one Eleven Twenty-one Thirty-one Forty-one Fifty-one Sixty-one Seventy-one Eighty-one
第 2 行 Two Twelve Twenty-two Thirty-two forty-two fifty-two Sixty-two seventy-two Eighty-two
第三行 three Thirteen Twenty-three Thirty-three Forty-three Fifty-three Sixty-three Seventy-three Eighty-three

请注意,索引操作符不适用于选择单个行。

使用iloc步进器

这是通过位置编号选择数据帧行和列的最常见方式。这个方法的语法非常类似于loc索引器的语法,也意味着使用索引操作符[]

为了访问单行多行,我们将一个参数传递给iloc索引器,该索引器表示相应的行位置、行位置列表(如果行是不同的)或行位置范围(如果行是连续的):

# Selecting one row 
df.iloc[3]
col_1     4
col_2    14
col_3    24
col_4    34
col_5    44
col_6    54
col_7    64
col_8    74
col_9    84
Name: row_4, dtype: int64
# Selecting disparate rows in the necessary order
df.iloc[[9, 8, 7]]
第一栏 第二栏 第三栏 col_4 第五栏 col_6 col_7 第八栏 col_9
第 10 行 Ten Twenty Thirty Forty Fifty Sixty Seventy Eighty Ninety
第 9 行 nine Nineteen Twenty-nine Thirty-nine forty-nine Fifty-nine sixty-nine Seventy-nine eighty-nine
第 8 行 eight Eighteen Twenty-eight Thirty-eight Forty-eight Fifty-eight sixty-eight seventy-eight Eighty-eight
# Selecting a slice of sequential rows
df.iloc[3:6]
第一栏 第二栏 第三栏 col_4 第五栏 col_6 col_7 第八栏 col_9
第 4 行 four Fourteen Twenty-four Thirty-four forty-four Fifty-four Sixty-four Seventy-four Eighty-four
第 5 行 five Fifteen Twenty-five Thirty-five Forty-five Fifty-five Sixty-five Seventy-five eighty-five
第 6 行 six Sixteen Twenty-six Thirty-six Forty-six fifty-six Sixty-six Seventy-six Eighty-six
# Selecting a slice of sequential rows (an open-ending range)
df.iloc[:3]
第一栏 第二栏 第三栏 col_4 第五栏 col_6 col_7 第八栏 col_9
第 1 行 one Eleven Twenty-one Thirty-one Forty-one Fifty-one Sixty-one Seventy-one Eighty-one
第 2 行 Two Twelve Twenty-two Thirty-two forty-two fifty-two Sixty-two seventy-two Eighty-two
第三行 three Thirteen Twenty-three Thirty-three Forty-three Fifty-three Sixty-three Seventy-three Eighty-three

注意,在最后两段代码中,不包括范围的停止边界,如前所述。

在所有其他情况下,要选择任意大小的数据子集(一列多列多行多列在一起单个数据点),我们将两个参数传递给iloc索引器:必要的行位置号和列位置号。对于这两个参数,值的潜在类型可以是:

  • 整数(选择单行/列),
  • 整数列表(多个不同的行/列),
  • 整数范围(多个连续的行/列),
  • 整数的开放式范围(多个连续的行/列,直到并且,不包括特定位置编号(例如df.iloc[:4, 1]),或者从特定编号开始直到结束(例如df.loc[4:, 1]),
  • 冒号(选择所有行/列)。

一些例子:

# Selecting an individual data point
df.iloc[0, 0]
1
# Selecting one row and multiple disparate columns 
df.iloc[0, [2, 8, 3]]
col_3    21
col_9    81
col_4    31
Name: row_1, dtype: int64
# Selecting multiple disparate rows and multiple sequential columns 
df.iloc[[8, 1, 9], 5:9]
col_6 col_7 第八栏 col_9
第 9 行 Fifty-nine sixty-nine Seventy-nine eighty-nine
第 2 行 fifty-two Sixty-two seventy-two Eighty-two
第 10 行 Sixty Seventy Eighty Ninety
# Selecting multiple sequential rows and multiple sequential columns (with open-ending ranges)
df.iloc[:3, 6:]
col_7 第八栏 col_9
第 1 行 Sixty-one Seventy-one Eighty-one
第 2 行 Sixty-two seventy-two Eighty-two
第三行 Sixty-three Seventy-three Eighty-three
# Selecting all rows and multiple disparate columns 
df.iloc[:, [1, 3, 7]]
第二栏 col_4 第八栏
第 1 行 Eleven Thirty-one Seventy-one
第 2 行 Twelve Thirty-two seventy-two
第三行 Thirteen Thirty-three Seventy-three
第 4 行 Fourteen Thirty-four Seventy-four
第 5 行 Fifteen Thirty-five Seventy-five
第 6 行 Sixteen Thirty-six Seventy-six
第 7 行 Seventeen Thirty-seven Seventy-seven
第 8 行 Eighteen Thirty-eight seventy-eight
第 9 行 Nineteen Thirty-nine Seventy-nine
第 10 行 Twenty Forty Eighty

注意,如果我们向iloc索引器传递至少一个越界的位置号(无论是整行/列的单个整数还是列表中的一个整数),将会抛出一个IndexError:

df.iloc[[1, 3, 100]]
---------------------------------------------------------------------------

IndexError                                Traceback (most recent call last)

~\anaconda3\lib\site-packages\pandas\core\indexing.py in _get_list_axis(self, key, axis)
   1529         try:
-> 1530             return self.obj._take_with_is_copy(key, axis=axis)
   1531         except IndexError as err:

~\anaconda3\lib\site-packages\pandas\core\generic.py in _take_with_is_copy(self, indices, axis)
   3627         """
-> 3628         result = self.take(indices=indices, axis=axis)
   3629         # Maybe set copy if we didn't actually change the index.

~\anaconda3\lib\site-packages\pandas\core\generic.py in take(self, indices, axis, is_copy, **kwargs)
   3614 
-> 3615         new_data = self._mgr.take(
   3616             indices, axis=self._get_block_manager_axis(axis), verify=True

~\anaconda3\lib\site-packages\pandas\core\internals\managers.py in take(self, indexer, axis, verify)
    861         n = self.shape[axis]
--> 862         indexer = maybe_convert_indices(indexer, n, verify=verify)
    863 

~\anaconda3\lib\site-packages\pandas\core\indexers.py in maybe_convert_indices(indices, n, verify)
    291         if mask.any():
--> 292             raise IndexError("indices are out-of-bounds")
    293     return indices

IndexError: indices are out-of-bounds

The above exception was the direct cause of the following exception:

IndexError                                Traceback (most recent call last)

~\AppData\Local\Temp/ipykernel_2100/47693281.py in <module>
----> 1 df.iloc[[1, 3, 100]]

~\anaconda3\lib\site-packages\pandas\core\indexing.py in __getitem__(self, key)
    929 
    930             maybe_callable = com.apply_if_callable(key, self.obj)
--> 931             return self._getitem_axis(maybe_callable, axis=axis)
    932 
    933     def _is_scalar_access(self, key: tuple):

~\anaconda3\lib\site-packages\pandas\core\indexing.py in _getitem_axis(self, key, axis)
   1555         # a list of integers
   1556         elif is_list_like_indexer(key):
-> 1557             return self._get_list_axis(key, axis=axis)
   1558 
   1559         # a single integer

~\anaconda3\lib\site-packages\pandas\core\indexing.py in _get_list_axis(self, key, axis)
   1531         except IndexError as err:
   1532             # re-raise with different error message
-> 1533             raise IndexError("positional indexers are out-of-bounds") from err
   1534 
   1535     def _getitem_axis(self, key, axis: int):

IndexError: positional indexers are out-of-bounds

但是,对于整数范围(一部分连续的行或列),允许超出边界的位置数:

df.iloc[1, 5:100]
col_6    52
col_7    62
col_8    72
col_9    82
Name: row_2, dtype: int64

这也适用于上一节中讨论的索引运算符:

df[7:1000]
第一栏 第二栏 第三栏 col_4 第五栏 col_6 col_7 第八栏 col_9
第 8 行 eight Eighteen Twenty-eight Thirty-eight Forty-eight Fifty-eight sixty-eight seventy-eight Eighty-eight
第 9 行 nine Nineteen Twenty-nine Thirty-nine forty-nine Fifty-nine sixty-nine Seventy-nine eighty-nine
第 10 行 Ten Twenty Thirty Forty Fifty Sixty Seventy Eighty Ninety

使用iat步进器

要从数据帧中只选择一个值,我们可以使用iat索引器,它比iloc执行得更快。语法与iloc索引器相同,只是这里我们总是使用两个整数(行号和列号):

df.iat[1, 2]
22

布尔数据帧索引

除了基于标签或基于位置的 pandas 数据帧索引之外,还可以根据特定条件从数据帧中选择一个子集:

df[df['col_2'] > 15]
第一栏 第二栏 第三栏 col_4 第五栏 col_6 col_7 第八栏 col_9
第 6 行 six Sixteen Twenty-six Thirty-six Forty-six fifty-six Sixty-six Seventy-six Eighty-six
第 7 行 seven Seventeen Twenty-seven Thirty-seven Forty-seven Fifty-seven Sixty-seven Seventy-seven Eighty-seven
第 8 行 eight Eighteen Twenty-eight Thirty-eight Forty-eight Fifty-eight sixty-eight seventy-eight Eighty-eight
第 9 行 nine Nineteen Twenty-nine Thirty-nine forty-nine Fifty-nine sixty-nine Seventy-nine eighty-nine
第 10 行 Ten Twenty Thirty Forty Fifty Sixty Seventy Eighty Ninety

上面这段代码返回数据帧中所有列的值,其中列col_2的值大于 15。所应用的条件(在我们的例子中是–df['col_2'] > 15)是一个布尔向量,其长度与数据帧索引相同,并检查数据帧的每一行是否满足定义的标准。

我们还可以使用任何其他比较运算符:

  • ==等于,
  • !=不等于,
  • >大于,
  • <小于,
  • >=大于或等于,
  • <=小于或等于。

也可以为字符串列定义布尔条件(比较运算符==!=在这种情况下有意义)。

此外,我们可以在同一列或多列上定义几个标准。用于此目的的运算符有& ( )、| ( )、~ ( 而非)。每个条件必须放在单独的一对括号中:

# Selecting all the rows of the dataframe where the value of `col_2` is greater than 15 but not equal to 19
df[(df['col_2'] > 15) & (df['col_2'] != 19)]
第一栏 第二栏 第三栏 col_4 第五栏 col_6 col_7 第八栏 col_9
第 6 行 six Sixteen Twenty-six Thirty-six Forty-six fifty-six Sixty-six Seventy-six Eighty-six
第 7 行 seven Seventeen Twenty-seven Thirty-seven Forty-seven Fifty-seven Sixty-seven Seventy-seven Eighty-seven
第 8 行 eight Eighteen Twenty-eight Thirty-eight Forty-eight Fifty-eight sixty-eight seventy-eight Eighty-eight
第 10 行 Ten Twenty Thirty Forty Fifty Sixty Seventy Eighty Ninety
# Selecting all the rows of the dataframe where the value of `col_2` is greater than 15 
# or the value of `col_5` is equal to 42
df[(df['col_2'] > 15) | (df['col_5'] == 42)]
第一栏 第二栏 第三栏 col_4 第五栏 col_6 col_7 第八栏 col_9
第 2 行 Two Twelve Twenty-two Thirty-two forty-two fifty-two Sixty-two seventy-two Eighty-two
第 6 行 six Sixteen Twenty-six Thirty-six Forty-six fifty-six Sixty-six Seventy-six Eighty-six
第 7 行 seven Seventeen Twenty-seven Thirty-seven Forty-seven Fifty-seven Sixty-seven Seventy-seven Eighty-seven
第 8 行 eight Eighteen Twenty-eight Thirty-eight Forty-eight Fifty-eight sixty-eight seventy-eight Eighty-eight
第 9 行 nine Nineteen Twenty-nine Thirty-nine forty-nine Fifty-nine sixty-nine Seventy-nine eighty-nine
第 10 行 Ten Twenty Thirty Forty Fifty Sixty Seventy Eighty Ninety
# Selecting all the rows of the dataframe where the value of `col_2` is NOT greater than 15 
df[~(df['col_2'] > 15)]
第一栏 第二栏 第三栏 col_4 第五栏 col_6 col_7 第八栏 col_9
第 1 行 one Eleven Twenty-one Thirty-one Forty-one Fifty-one Sixty-one Seventy-one Eighty-one
第 2 行 Two Twelve Twenty-two Thirty-two forty-two fifty-two Sixty-two seventy-two Eighty-two
第三行 three Thirteen Twenty-three Thirty-three Forty-three Fifty-three Sixty-three Seventy-three Eighty-three
第 4 行 four Fourteen Twenty-four Thirty-four forty-four Fifty-four Sixty-four Seventy-four Eighty-four
第 5 行 five Fifteen Twenty-five Thirty-five Forty-five Fifty-five Sixty-five Seventy-five eighty-five

数据帧索引方法的组合

最后,我们可以以各种方式组合基于标签、基于位置和布尔数据帧的索引方法。为此,我们应该再次应用loc索引器,并使用行的index属性和列的columns属性来访问位置号:

df.loc[df.index[[3, 4]], ['col_3', 'col_7']]
第三栏 col_7
第 4 行 Twenty-four Sixty-four
第 5 行 Twenty-five Sixty-five
df.loc['row_3':'row_6', df.columns[[0, 5]]]
第一栏 col_6
第三行 three Fifty-three
第 4 行 four Fifty-four
第 5 行 five Fifty-five
第 6 行 six fifty-six
df.loc[df['col_4'] > 35, 'col_4':'col_7']
col_4 第五栏 col_6 col_7
第 6 行 Thirty-six Forty-six fifty-six Sixty-six
第 7 行 Thirty-seven Forty-seven Fifty-seven Sixty-seven
第 8 行 Thirty-eight Forty-eight Fifty-eight sixty-eight
第 9 行 Thirty-nine forty-nine Fifty-nine sixty-nine
第 10 行 Forty Fifty Sixty Seventy

结论

总之,在本教程中,我们深入探讨了在 pandas 中索引数据帧。我们学到了很多东西:

  • 什么是熊猫数据帧索引
  • 数据帧索引的目的
  • 什么是数据帧切片
  • 什么是行标签和列标签
  • 如何检查数据帧的当前行和列标签
  • 什么是行列位置号
  • 熊猫数据帧索引的主要方法
  • 基于标签和基于位置的数据帧索引的关键区别
  • 为什么使用属性 access 来选择数据帧列并不可取
  • 布尔数据帧索引的工作原理
  • 最常见的方法涉及每种数据帧索引方法、用于选择不同类型子集的语法以及它们的局限性
  • 如何组合不同的索引方法

教程:使用 Linux 的 Windows 子系统在 Windows 上安装 Linux

原文:https://www.dataquest.io/blog/tutorial-install-linux-on-windows-wsl/

August 1, 2019install-linux-on-windows

对于所有类型的程序员和数据科学家来说,学习 UNIX 命令行是一项重要的技能,但是如果你是 Windows 用户,在你的机器上使用你的命令行技能意味着你需要在 Windows 上安装 Linux。在本教程中,我们将带您在 Windows 机器上安装 Linux 的 Windows 子系统,以便您可以充分利用您的 UNIX 命令行技能。

(在 Dataquest,我们发布了一个关于使用命令行的互动课程,以及一个关于使用命令行处理文本的互动课程。任何系统的用户都可以在我们的交互式网络*台上练习。但是,如果您想在自己的机器上练习这些命令行技能,并且您使用的是 Windows,那么您需要在访问 Unix 命令行之前做一些设置,因为 Windows 不是基于 UNIX 的。)

在本教程中,我们将安装 Windows Subsystem for Linux (WSL)和一个使用 WSL 的 Linux 发行版,这将使您能够使用 UNIX 命令行界面。

安装 WSL 相当简单,但是不可能在每个版本的 Windows 上安装,所以第一步是确保你的机器是兼容的。

步骤 1:确保兼容性

要安装 WSL,您的计算机必须安装 64 位版本的 Windows 10(或更高版本)。如果你不知道你有哪个版本,你可以去Settings > System > About版本系统类型字段。

check-system-compatibility

如果您有 64 位 Windows 10,但您的版本低于 1607,您必须在继续安装之前更新 Windows。要更新 Windows,请遵循这些说明。

如果您没有 64 位体系结构,或者如果您有早期版本的 Windows,您将无法运行 WSL。但是不用担心!通过安装 Cygwin,您仍然可以在您的机器上使用 UNIX 命令行。你可以在这里找到安装 Cygwin 的说明。如果您的机器支持它,那么最好安装 WSL,所以让我们进入下一步!

步骤 2:启用 WSL 可选组件

用于 Linux 的 Windows 子系统是 Windows 10 的内置功能,但必须手动启用。因为默认情况下它是禁用的,所以我们需要做的第一件事就是启用它。

方法是导航到Control Panel > Programs > Turn Windows Features On or Off,然后在弹出窗口中向下滚动,直到我们看到 Linux 的 Windows 子系统。我们将勾选旁边的框,然后单击“确定”启用该功能。然后,当 Windows 搜索文件并应用更改时,在短暂的进度条等待后,我们将被提示重新启动计算机,以便更改生效。

enable-wsl-windows-10

作为一种捷径,我们也可以在 Windows 10 搜索栏中搜索“turn w”。点击此搜索的顶部结果将直接将我们带到Control Panel > Programs > Turn Windows Features On or Off,之后我们可以按照上述相同的步骤来启用 Linux 的 Windows 子系统。

请记住,在进行下一步之前,我们必须重新启动机器以使更改生效。

步骤 3:在 Windows 上安装 Linux

一旦机器重新启动,我们的下一步将是安装 Linux 发行版。我们将打开 Microsoft Store —在 Windows 搜索栏中搜索“Microsoft Store”或在“开始”菜单中导航到它。在商店应用中,搜索“Linux”,然后点击“获取应用”以查看可用的 Linux 发行版(或者直接点击您的首选发行版,如果您在下面看到它)。点击这里了解更多关于每个 Linux 发行版的信息,但是如果你不确定你需要什么,Ubuntu 是一个常见的选择。

出于本教程的目的,我们将选择 Ubuntu,但是安装任何其他 Linux 发行版的过程都是一样的。在 Ubuntu 页面,点击“获取”,然后在弹出的消息中选择“不,谢谢”。Ubuntu 会自动开始下载,完成后会出现启动按钮。

downloading-ubuntu

第 4 步:启动并创建您的帐户

Ubuntu app 下载完成后,点击Launch等待安装。它将打开一个新窗口,当提示我们创建一个用户帐户时,我们将知道它已完成安装。

installing-ubuntu-windows-command-line

准备就绪后,输入您的首选用户名和密码。这可以是你喜欢的任何东西;它不需要与您的 Windows 用户名或密码相同。请注意,系统会提示您输入密码两次,并且在您键入密码时,密码不会显示在屏幕上。

install-ubuntu-bash-shell

一旦你创建了你的账户,你可以使用pwd命令显示当前的工作目录来测试一切是否正常。您会注意到,默认目录可能不便于处理 Windows 文件系统中的文件,因此可选的快速第五步是更改默认目录,这样我们就不必在每次启动命令行时导航到不同的目录。

步骤 5(可选):更改您的默认工作目录

要改变默认的工作目录,我们可以编辑.bashrc.bashrc是一个包含所有设置和别名的文件,当我们启动 shell 时,shell 会关注这些设置和别名。我们要做的是在这个文件中插入一个命令,该命令将在每次启动 shell 时运行,以将工作目录更改为我们首选的默认目录,这样我们就不必手动执行此操作。

出于本教程的目的,我们将设置默认目录为 Windows 文件路径C:/Users/Charlie/Desktop,但是您可以用任何指向您想要设置为默认目录的文件路径来替换它。

首先,在命令行中输入edit ~/.bashrc。该命令将打开.bashrc

当它打开时,一直滚动到底部并按下i进入插入模式。插入模式将允许我们“插入”对.bashrc的更改。

在插入模式下,键入cd,后跟您想要更改的目录。注意,在 Ubuntu 中,你的 Windows C:驱动器位于/mnt/文件夹中,所以你应该输入cd /mnt/c/your_file_path。在上面的文件路径示例中,我们希望将cd /mnt/c/Users/Charlie/Desktop输入到我们的.bashrc中。

.bashrc添加一条注释,用一条简短的消息解释我们的新命令正在做什么,这也是一个好主意。我们将使用#添加一个注释来解释这个命令改变了默认目录。

完成后,按Esc退出插入模式,然后键入:wq保存更改并退出命令行。从命令行,我们可以输入cat ~/.bashrc来查看编辑过的bashrc文件,并确认我们的更改已保存。然后,我们可以输入source ~/.bashrc来重启命令行,它会以我们新的默认目录作为工作目录重启。我们只需要做这一次——从现在开始,每次我们打开命令行时,我们刚刚添加的默认目录将是我们的工作目录。

它看起来是这样的:

editing-bashrc-file

恭喜你!现在,您已经在您的机器上安装了 Linux,您已经准备好在您的本地 Windows 机器上展示您的 UNIX 命令行技能了。此外,您知道如何在 Windows 机器上安装 Linux,这对各种任务都很有用,比如帮助同事为数据科学工作做好准备。

获取免费的数据科学资源

免费注册获取我们的每周时事通讯,包括数据科学、 PythonRSQL 资源链接。此外,您还可以访问我们免费的交互式在线课程内容!

SIGN UP

教程:Git 和 Github 简介

原文:https://www.dataquest.io/blog/tutorial-introduction-learn-git-github/

March 21, 2019learn-git-github

如果你对数据科学或软件开发感兴趣,版本控制的工作知识是绝对必要的。但是什么是版本控制,它如何帮助我们完成日常的数据科学任务?

版本控制是集成和记录来自多个合作者的代码和其他文件的更改的过程。出于以下几个原因,您对版本控制感到满意是很重要的:

  • 资格:雇主正在寻找理解版本控制并有使用它进行协作的经验的候选人。当你进行专业编程时,你不太可能独自处理代码。
  • 经验:通过获得基本的版本控制技能,你使自己有能力为数百万开源项目中的任何一个做出贡献。这是获得编程经验的一种极好的方式。
  • 社区:TensorFlow、scikit-learn、Hadoop、Spark 等流行的数据科学工具都是开源的。如果你会使用 Git 和 GitHub,你就可以成为开源数据科学社区的积极参与者。

在本文中,我们将介绍最流行的版本控制解决方案 Git 和 GitHub 的基础知识。

Git 是一个版本控制工具,它提供了所有的基本功能。

GitHub 是一个构建在 Git 之上的*台,为团队提供了另一个层次的项目组织,包括问题跟踪、代码审查等等。

工作流程

版本控制可能看起来像时髦的命令行魔术,但它实际上只是一种将变更集成到项目中的方法。记住这个简单的目标,你就不会有任何麻烦。在我们深入研究具体细节之前,我们应该抽象地讨论一下协作工作流。让我们假设我们是加入一个已建立的项目的开发人员。

问题

通常,开发工作流始于项目的问题跟踪器。问题跟踪器是项目中需要完成的所有工作的列表。问题可以是需要修复的 bug,需要实现的特性,或者需要进行的任何其他种类的更改。作为一个项目的贡献者,你可能会选择一个问题或被分配到一个问题。当您开始处理一个问题时,您将创建一个与该问题相关联的新分支。你所有的工作都将在这个分支上进行。

分支

分支是项目的*行版本。通过将开发任务划分为分支,我们可以使项目更有组织性。通常,有一个特殊的分支叫做“master ”,它保存着项目的“live”版本。分支允许开发人员在将他们的变更与主分支结合或“合并”之前,独立地测试他们的变更。

如果我们想在其他修复完成之前看到它们发生了什么,我们可以“检查”不同的分支来观察不同任务的进度。

承诺

提交只是一个“原子”变更的记录——也就是说,一个不包含多个较小变更的变更。提交在分支上进行,它们打破了与该分支相关的变更的时间线。

例如,假设我们正在实现一个相当复杂的特性。提交背后的想法是记录构成特性实现的单个变更。每一次我们做出改变,我们就做了一次承诺。这样,当有人审查我们的代码时,他可以看到一系列的提交,这些提交详细描述了我们实现该特性的过程。​

拉取请求

拉请求是开发工作流的一个重要部分。拉请求是将我们的分支机构与主分支机构合并的请求。换句话说,这是一个让我们的工作成为活项目的一部分的请求,我们的工作到目前为止一直发生在它的孤立的分支上。

当我们提交拉取请求时,我们应该详细描述我们所做的更改以及做出这些更改的原因。如果我们的更改正在修复问题跟踪器上的一个问题,我们也会希望包括该信息。对于每个 pull 请求,GitHub 都会提供一个窗口,突出显示我们的分支和主分支之间的所有差异。通常,项目的另一个成员会审查我们所有的变更。几乎总是会有我们必须采取行动的反馈。在一两个审阅者看来一切都很好之后,我们的更改将被合并。

操作

我们了解工作流程,现在是工作的时候了。从安装 Git 开始。

让我们假设我们正在研究张量流。我们要做的第一件事是将代码库下载到我们的机器上,这样我们就可以使用它了。

在 GitHub 上的资源库页面中,点击右上角的 Fork。派生存储库反映了我们帐户上的存储库——因此它位于 tensorflow/tensorflow,也位于我们的用户名/tensorflow。

但是我们仍然没有下载任何东西,所以让我们回到那个。单击克隆或下载,然后单击剪贴板图标,将 URL 复制到剪贴板。现在,打开命令提示符,用 cd filepath 导航到项目的目录。然后键入 git clone 并粘贴 URL,并按 enter 键。这将下载存储库,并使其在当前目录的子文件夹中可用。

git-github-screen-1

接下来要做的是告诉 Git 项目来自哪里。返回 tensorflow/tensorflow 存储库,单击克隆或下载,然后单击剪贴板。然后,在命令提示符下,键入 git remote add upstream,粘贴 URL,并按 enter 键。这告诉 Git 这个目录是一个项目,它需要与我们刚刚给它的 URL 所指定的存储库进行对话。

一般来说,在开始工作之前,我们应该使用 git pull 从上游检索最新的更改。然而,由于我们几分钟前刚刚克隆了存储库,所以现在没有必要这样做。

在这一点上,我们准备开始做出改变。让我们假设我们被分配到问题#0001:非常非常糟糕的错误。我们应该从创建一个新的分支开始发展。键入 git check out-b fix-really-really-bad-bug。checkout 命令实际上是切换到不同分支的命令,但是通过使用标志-b,我们告诉 Git 我们要切换到的分支需要被创建。​

(如果我们想单独创建一个新分支或切换到一个现有分支,我们可以使用 git branch {new-branch-name}创建一个新分支,或者使用 git checkout {branch-name}切换到一个现有分支。)

既然我们已经建立了自己的分支,我们就可以开始编码了。

当我们工作时,我们应该在每次执行一个基本的改变时提交。如果问题相对较小,我们可能只需要一次提交。对于这个非常非常糟糕的 Bug,我们可能需要提交一些。我们分解提交的方式主要取决于开发人员。一种方法是每次我们要测试我们的变更时提交一次。

所有提交都包含一条提交消息,描述与该提交相关联的更改。要进行提交,我们必须首先存放我们想要提交的更改。如果我们只修改了一个文件,我们只需在命令提示符下输入 git add {filepath}。这一步是必要的,因为有时我们会修改几个文件,但我们只想提交一个或两个文件中的更改。接下来,输入 git commit -m“提交消息”。提交消息应该是描述与该提交相关的更改的几个词。请注意,引号是必需的。或者,如果没有我们不想提交的已修改文件,我们可以跳过暂存步骤,只使用 git commit -a -m "{commit message} "来暂存和提交所有文件。

当我们修复了一个非常非常糟糕的 Bug,对它进行了彻底的测试,并做出了最后的提交,我们就可以提交我们的修改了。我们可以在命令提示符下键入 git push origin master fix-really-really-bad-bug,然后按 enter 键。这个命令告诉 git 将本地分支上的更改发送到原始存储库主分支。

如果我们在这样做之后去 GitHub 上的仓库,我们应该会看到一个黄色的通知,这意味着仓库收到了我们的推送。点击“比较和提取请求”。如果您看不到此通知,只需点击“新建拉动请求”,然后在拉动请求屏幕上选择开发分支。在这里,我们可以填写我们的拉取请求描述,并提交以供审查。

我们还应该单击“文件已更改”选项卡,以验证我们更改的所有内容都是有意的。

我们并不完美,所以评审员几乎肯定会要求对我们的实现进行一些修改。没问题——只需做出改变,再次提交,再次推送。新的提交将在同一个拉请求中自动可见。

git-github-screen-2 git-github-screen-3

结论

我们找到了。这是最基本的。我们应该拥有开始为一些开源项目做贡献或加入开发团队所需的一切。

不过,版本控制不仅仅是基础,还有很多东西,所以一定要查看 Dataquest 课程以获得更深入的实际操作版本控制课程。

教程:Python 中的 Lambda 函数

原文:https://www.dataquest.io/blog/tutorial-lambda-functions-in-python/

March 9, 2022Lambda Functions in Python

在本教程中,我们将在 Python 中定义 lambda 函数,并探讨使用它们的优势和局限性。

Python 中的 Lambda 函数是什么?

lambda 函数是一个匿名函数(即定义时没有名字),它可以接受任意数量的参数,但与普通函数不同,它只计算并返回一个表达式。

Python 中的 lambda 函数具有以下语法:

λ参数:表达式

lambda 函数的结构包括三个元素:

  • 关键字lambda——正常功能中def的模拟
  • 参数 —支持传递位置和关键字
    参数,就像普通函数一样
  • 主体 —使用 lambda 函数评估给定参数的表达式

注意,与普通函数不同,我们不用括号将 lambda 函数的参数括起来。如果一个 lambda 函数有两个或更多的参数,我们用逗号把它们列出来。

我们使用 lambda 函数只计算一个短表达式(理想情况下是一行),并且只计算一次,这意味着我们以后不会应用这个函数。通常,我们将一个 lambda 函数作为参数传递给一个更高阶的函数(接受其他函数作为参数的函数),比如 Python 内置函数,如filter()map()reduce()

Python 中 Lambda 函数的工作原理

让我们看一个 lambda 函数的简单例子:

 lambda x: x + 1
 <function __main__.<lambda>(x)>

上面的 lambda 函数接受一个参数,将其递增 1,然后返回结果。这是下面带有关键字defreturn的普通函数的简单版本:

 def increment_by_one(x):
        return x + 1

然而现在,我们的 lambda 函数lambda x: x + 1只创建一个函数对象,不返回任何东西。我们预料到了这一点:我们没有为它的参数x提供任何值(一个参数)。让我们先赋一个变量,把它传递给 lambda 函数,看看这次我们得到了什么:

 a = 2
    print(lambda x: a + 1)
 <function <lambda> at 0x00000250CB0A5820>

正如我们所料,我们的 lambda 函数没有返回3,而是返回了函数对象本身及其内存位置。事实上,这不是调用 lambda 函数的正确方式。要将参数传递给 lambda 函数,执行它并返回结果,我们应该使用以下语法:

 (lambda x: x + 1)(2)
 3

请注意,虽然我们的 lambda 函数的参数没有用括号括起来,但当我们调用它时,我们会在 lambda 函数的整个构造和传递给它的参数周围添加括号。

上面代码中需要注意的另一点是,使用 lambda 函数,我们可以在函数创建后立即执行它并接收结果。这就是所谓的立即调用函数执行(或)。

我们可以创建一个带有多个参数的 lambda 函数。在这种情况下,我们用逗号分隔函数定义中的参数。当我们执行这样一个 lambda 函数时,我们以相同的顺序列出相应的参数,并用逗号分隔它们:

 (lambda x, y, z: x + y + z)(3, 8, 1)
 12

也可以使用 lambda 函数来执行条件运算。下面是一个简单的 if-else 函数的 lambda 模拟:

 print((lambda x: x if(x > 10) else 10)(5))
 print((lambda x: x if(x > 10) else 10)(12))
 10
    12

如果存在多个条件( if-elif-…-else ),我们必须将它们嵌套:

 (lambda x: x * 10 if x > 10 else (x * 5 if x < 5 else x))(11)
 110

这种方法的问题是,已经有了一个嵌套条件,代码变得难以阅读,正如我们在上面看到的。在这种情况下,带有一组条件的普通函数比 lambda 函数更好。事实上,我们可以用下面的方式编写上面例子中的 lambda 函数:

 def check_conditions(x):
        if x > 10:
            return x * 10
        elif x < 5:
            return x * 5
        else:
            return x

    check_conditions(11)
 110

尽管上面的函数比相应的 lambda 函数跨越了更多的行,但它更容易阅读。

我们可以将 lambda 函数赋给一个变量,然后将该变量作为普通函数调用:

 increment = lambda x: x + 1
    increment(2)
 3

然而,根据 Python 代码的 PEP 8 风格指南,这是一种不好的做法:

赋值语句的使用消除了 lambda 表达式可以提供的优于显式 def 语句的唯一好处(即,它可以嵌入到更大的表达式中)。

所以,如果我们真的需要存储一个函数以备后用,与其给一个变量赋一个 lambda 函数,不如定义一个等价的 normal 函数。

Lambda 函数在 Python 中的应用

带有filter()功能的λ

我们使用 Python 中的filter()函数从
一个 iterable(比如列表、集合、元组、序列等)中选择某些项目。)基于预定义的
标准。它需要两个参数:

  • 定义过滤标准的函数
  • 运行函数的可迭代对象

作为该操作的结果,我们得到一个过滤器对象:

 lst = [33, 3, 22, 2, 11, 1]
    filter(lambda x: x > 10, lst)
 <filter at 0x250cb090520>

为了从 filter 对象中获得一个新的 iterable,其中包含原始 iterable 中满足预定义标准的所有项,我们需要将 filter 对象传递给 Python 标准库的相应函数:list()tuple()set()frozenset()sorted()(返回一个排序列表)。

让我们筛选一个数字列表,只选择大于 10 的数字,并返回一个按升序排序的列表:

 lst = [33, 3, 22, 2, 11, 1]
    sorted(filter(lambda x: x > 10, lst))
 [11, 22, 33]

我们不必创建一个新的与原对象类型相同的可迭代对象。此外,我们可以将该操作的结果存储在一个变量中:

 lst = [33, 3, 22, 2, 11, 1]
    tpl = tuple(filter(lambda x: x > 10, lst))
    tpl
 (33, 22, 11)

带有map()功能的λ

我们使用 Python 中的map()函数对 iterable 的每一项执行特定的操作。它的语法与filter()相同:要执行的函数和该函数适用的 iterable。map()函数返回一个 map 对象,通过将这个对象传递给相应的 Python 函数,我们可以得到一个新的 iterable:list()tuple()set()frozenset()sorted()

filter()函数一样,我们可以从一个 map 对象中提取一个不同于原始类型的 iterable,并将其赋给一个变量。下面是一个使用map()函数将列表中的每一项乘以 10 并输出映射值作为赋给变量tpl的元组的例子:

 lst = [1, 2, 3, 4, 5]
    print(map(lambda x: x * 10, lst))
    tpl = tuple(map(lambda x: x * 10, lst))
    tpl
 <map object at 0x00000250CB0D5F40>

    (10, 20, 30, 40, 50)

map()filter()函数的一个重要区别是,第一个函数总是返回一个与原始函数长度相同的迭代函数。因此,由于熊猫系列对象也是可迭代的,我们可以在 DataFrame 列上应用map()函数来创建一个新列:

 import pandas as pd
    df = pd.DataFrame({'col1': [1, 2, 3, 4, 5], 'col2': [0, 0, 0, 0, 0]})
    print(df)
    df['col3'] = df['col1'].map(lambda x: x * 10)
    df
 col1  col2
0     1     0
1     2     0
2     3     0
3     4     0
4     5     0

   col1  col2  col3
0     1     0    10
1     2     0    20
2     3     0    30
3     4     0    40
4     5     0    50

注意,为了在上述情况下获得相同的结果,也可以使用apply()函数:

 df['col3'] = df['col1'].apply(lambda x: x * 10)
    df
 col1  col2  col3
0     1     0    10
1     2     0    20
2     3     0    30
3     4     0    40
4     5     0    50

我们还可以根据另一列的某些条件创建一个新的 DataFrame 列。同样,对于下面的代码,我们可以互换使用map()apply()函数:

 df['col4'] = df['col3'].map(lambda x: 30 if x < 30 else x)
    df
 col1  col2  col3  col4
0     1     0    10    30
1     2     0    20    30
2     3     0    30    30
3     4     0    40    40
4     5     0    50    50

带有reduce()功能的λ

reduce()函数与 functools Python 模块相关,其工作方式如下:

  1. 对 iterable 的前两项进行操作并保存结果
  2. 对保存的结果和 iterable 的下一项进行操作
  3. 以这种方式处理成对的值,直到使用完 iterable 的所有项

这个函数具有与前两个函数相同的两个参数:一个函数和一个 iterable。但是,与前面的函数不同,这个函数不需要传递给任何其他函数,而是直接返回结果标量值:

 from functools import reduce
    lst = [1, 2, 3, 4, 5]
    reduce(lambda x, y: x + y, lst)
 15

上面的代码展示了当我们使用reduce()函数来计算一个列表的总和时,它的作用(尽管对于这样一个简单的操作,我们会使用一个更好的解决方案:sum(lst))。

请注意,reduce()函数总是需要一个正好有两个参数的 lambda 函数,而且我们必须首先从 functools Python 模块中导入它。

Python 中 Lambda 函数的利与弊

赞成的意见

  • 这是评估单个表达式的理想选择,该表达式应该只评估一次。
  • 它一旦被定义就可以被调用。
  • 与相应的普通
    函数相比,它的语法更加简洁。
  • 它可以作为参数传递给更高阶的函数,比如
    filter()map()reduce()

骗局

  • 它不能执行多个表达式。
  • 它很容易变得繁琐,例如当它
    包含一个 if-elif-…-else 循环时。
  • 它不能包含任何变量赋值(例如,lambda x: x=0
    会抛出一个SyntaxError)。
  • 我们不能为 lambda 函数提供 docstring。

结论

总而言之,我们已经详细讨论了在 Python 中定义和使用 lambda 函数的许多方面:

  • lambda 函数与普通 Python 函数的区别
  • Python 中 lambda 函数的语法和剖析
  • 何时使用 lambda 函数
  • lambda 函数的工作原理
  • 如何调用 lambda 函数
  • 被调用函数执行的定义(IIFE)
  • 如何用 lambda 函数执行条件运算,如何
    嵌套多个条件,以及为什么我们应该避免它
  • 为什么我们应该避免将 lambda 函数赋给变量
  • 如何将 lambda 函数与filter()函数一起使用
  • 如何将 lambda 函数与map()函数一起使用
  • 我们如何使用传递了 lambda 函数的
    map()函数在 pandas 数据帧中创建新列,以及在这种情况下使用的
    替代函数
  • 如何将 lambda 函数与reduce()函数一起使用
  • 使用 lambda 函数优于普通 Python
    函数的利弊

希望本教程有助于让 Python 中 lambda 函数这个看似吓人的概念变得更清晰、更易于应用!

教程:R 中的泊松回归

原文:https://www.dataquest.io/blog/tutorial-poisson-regression-in-r/

February 27, 2019

如果你知道如何以及何时使用泊松回归,它会是一个非常有用的工具。在本教程中,我们将深入了解泊松回归,它是什么,以及 R 程序员如何在现实世界中使用它。

具体来说,我们将涵盖:

  • 泊松回归实际上是什么,我们什么时候应该使用它
  • 泊松分布,以及它与正态分布的区别
  • 基于广义最小二乘法的泊松回归建模
  • 计数数据的泊松回归建模
  • 使用 jtools 可视化来自模型的发现
  • 费率数据的泊松回归建模

install.packages("Dataquest ")

从我们的R 课程简介开始学习 R——不需要信用卡!

SIGN UP

什么是泊松回归模型?

泊松回归模型最适用于对结果为计数的事件进行建模。或者,更具体地说,计数数据:具有非负整数值的离散数据,用于计数,比如某个事件在给定时间段内发生的次数或者在杂货店排队的人数。

计数数据也可以表示为速率数据,因为事件在一个时间范围内发生的次数可以表示为原始计数(即“一天中,我们吃三顿饭”)或速率(我们以每小时 0.125 顿饭的速率吃饭)。

泊松回归允许我们确定哪些解释变量(X 值)对给定的响应变量(Y 值、计数或比率)有影响,从而帮助我们分析计数数据和比率数据。例如,杂货店可以应用泊松回归来更好地理解和预测排队人数。

什么是泊松分布?

泊松分布是以法国数学家西蒙·丹尼斯·泊松命名的统计理论。假设 y 的发生不受 y 之前发生的时间的影响,它对特定时间范围内发生的一个或多个事件 y 的概率进行建模。这可以用下面的公式进行数学表达:

poisson distribution formula

这里, μ (在某些教科书中你可能会看到 λ 而不是 μ )是单位曝光下一个事件可能发生的*均次数。也叫泊松分布的参数曝光可能是时间、空间、人口规模、距离或面积,但往往是时间,用 t 表示。如果未给出曝光值,则假定其等于 1

让我们通过为不同的 μ 值创建一个泊松分布图来形象化这一点。

首先,我们将创建一个 6 色矢量:

 # vector of colors
colors <- c("Red", "Blue", "Gold", "Black", "Pink", "Green") 

接下来,我们将为分布创建一个列表,它将具有不同的 μ 值:

 # declare a list to hold distribution values
poisson.dist < - list() 

然后,我们将为 μ 创建一个值向量,并对来自 μ 的值进行循环,每个值的分位数范围为 0-20,将结果存储在一个列表中:

 a < - c(1, 2, 3, 4, 5, 6) # A vector for values of u
for (i in 1:6) {
    poisson.dist[[i]] <- c(dpois(0:20, i)) # Store distribution vector for each corresponding value of u
} 

最后,我们将使用plot()来绘制这些点。plot()是 R 中的基本图形函数。在 R 中绘制数据的另一种常用方法是使用流行的ggplot2包;这包括在 Dataquest 的 R 课程中。但是对于本教程,我们将坚持基 R 函数。

 # plot each vector in the list using the colors vectors to represent each value for u
plot(unlist(poisson.dist[1]), type = "o", xlab="y", ylab = "P(y)", 
col = colors[i])
for (i in 1:6) {
    lines(unlist(poisson.dist[i]), type = "o", col = colors[i])
}
# Adds legend to the graph plotted
legend("topright", legend = a, inset = 0.08, cex = 1.0, fill = colors, title = "Values of u") 

Poisson-Distribution-1

注意,我们使用dpois(sequence,lambda)来绘制泊松分布中的概率密度函数(PDF)。在概率论中,概率密度函数是描述连续随机变量(其可能值是随机事件的连续结果的变量)具有给定值的相对可能性的函数。(在统计学中,“随机”变量只是一个其结果是随机事件的结果的变量。)

泊松分布和正态分布有什么不同?

泊松分布最常用于计算给定时间间隔内事件发生的概率。因为我们讨论的是一个计数,泊松分布的结果必须是 0 或更高——一个事件不可能发生负次数。另一方面,正态分布是连续变量的连续分布,它可能导致正值或负值:

泊松分布 正态分布
用于计数数据或比率数据 用于连续变量
取决于λ值的偏斜。 围绕*均值对称的钟形曲线。
方差=*均值 方差和均值是不同的参数;均值、中值和众数相等

我们可以生成 R 的正态分布,如下所示:

 # create a sequence -3 to +3 with .05 increments 
xseq < - seq(-3, 3, .05) 

# generate a Probability Density Function
densities <- dnorm(xseq, 0, 1) 

# plot the graph
plot(xseq, densities, col = "blue", xlab = "", ylab = "Density", type = "l", lwd = 2) 
# col: changes the color of line
# 'xlab' and 'ylab' are labels for x and y axis respectively
# type: defines the type of plot. 'l' gives a line graph
# lwd: defines line width 

poisson-regression-unnamed-chunk-1-1

在 R 中,dnorm(sequence, mean, std.dev)用于绘制正态分布的概率密度函数(PDF)。

为了理解泊松分布,考虑来自 Chi Yau 的 R 教程教科书的以下问题:

如果*均每分钟有 12 辆车过桥,那么在任何给定的一分钟内有 17 辆或更多的车过桥的概率是多少?

这里,*均每分钟过桥的车辆数量是 μ = 12 辆。

ppois(q, u, lower.tail = TRUE)是一个 R 函数,给出一个随机变量低于或等于一个值的概率。

我们必须找出拥有十七辆或多汽车的概率,所以我们将使用lower.trail = FALSE并将 q 设为 16:

 ppois(16, 12, lower.tail = FALSE)
# lower.tail = logical; if TRUE (default) then probabilities are P[X < = x], otherwise, P[X > x]. 
 ## [1] 0.101291 

要得到一个百分比,我们只需将这个输出乘以 100。现在我们有了问题的答案:在任何特定的一分钟内,有 17 辆或更多的汽车通过这座桥的概率是 10.1%

泊松回归模型和广义最小二乘法

广义线性模型是响应变量遵循非正态分布的模型。这与线性回归模型形成对比,在线性回归模型中,响应变量遵循正态分布。这是因为广义线性模型的响应变量是绝对的,如是、否;或者 A 组、B 组,因此不在-∞到+∞的范围内。因此,反应和预测变量之间的关系可能不是线性的。在 GLM:

[【I】]=+【β】[+【p】]【I】+) n****

响应变量y[I由预测变量和一些误差项的线性函数建模。*]

泊松回归模型是一种广义线性模型(GLM) ,用于对计数数据和列联表进行建模。输出 Y (计数)是一个遵循泊松分布的值。它假设期望值(均值)的对数,可以通过一些未知参数建模成线性形式。

注: 在统计学中,列联表(例)是依赖于多个变量的频率矩阵。

为了将非线性关系转换成线性形式,使用了一个链接函数,它是泊松回归的对数。因此,泊松回归模型也被称为对数线性模型。泊松回归模型的一般数学形式是:

log(y)=α + β[1]x[1] + β[2]x[2] + … + β[p]x[p]

在哪里,

  • y :响应变量
  • αβ :数值系数, α 为截距,有时 α 也用 β [0] 表示,同样如此
  • x 是预测值/解释变量

使用最大似然估计(MLE)或最大似然等方法计算系数。

考虑一个包含一个预测变量和一个响应变量的方程:

log(y)=α + β(x)

**这相当于:

y = exp(α + β(x)) = exp(α) + exp(β * x)

**注意:在泊松回归模型中,预测变量或解释变量可以是数值或分类值的混合。

泊松分布和泊松回归最重要的特征之一是等离差,这意味着分布的均值和方差相等。

方差衡量数据的分布。它是“*均值的*方差的*均值”。如果所有值都相同,方差(Var)等于 0。值之间的差异越大,方差越大。Mean 是数据集值的*均值。*均值是值的总和除以值的个数。

假设*均值( μ )由 E ( X )表示

E ( X )= μ

对于泊松回归,均值和方差的关系如下:

var(X)=σ2E(X)

其中σT2 2 为色散参数。由于var(X)=E(X)(方差=均值)必须成立,泊松模型才能完全拟合, σ² 必须等于 1。

当方差大于均值时,称为过分散,且大于 1。如果它小于 1,则称为欠分散

使用计数数据的泊松回归建模

在 R 中,glm()命令用于建模广义线性模型。下面是glm()的大致结构:

 glm(formula, family = familytype(link = ""), data,...) 

在本教程中,我们将使用这三个参数。要了解更多细节,我们可以查阅 R 文档,但让我们快速浏览一下每个文档所指的内容:

参数 描述
公式 该公式是如何拟合模型的符号表示
家庭 家族告诉方差和连接函数的选择。家庭有几种选择,包括泊松和逻辑斯蒂
数据 数据是要使用的数据集

glm()通过以下默认链接功能为系列提供八种选择:

家庭的 默认链接功能
二项式 (link = "logit ")
高斯的 (link = "身份")
微克 (link = "反向")
逆高斯 (链接= \(frac{1}{mu^2}\))
泊松 (link = "log ")
类似的 (link = "恒等式",variance = "常数")
拟二项式 (link = "logit ")
准泊松 (link = "log ")

让我们开始建模吧!

我们将建立与织造过程中纱线断裂频率相关的泊松回归模型。这些数据可以在 R 中的datasets包中找到,所以我们需要做的第一件事是使用install.package("datasets")安装包,并用library(datasets)加载库:

 # install.packages("datasets")
library(datasets) # include library datasets after installation 

datasets包包括大量的数据集,所以我们需要专门选择我们的纱线数据。查阅包文档,我们可以看到它被称为 warpbreaks ,所以我们把它作为一个对象存储。

 data < - warpbreaks 

我们来看看数据:

 columns < - names(data) # Extract column names from dataframe
columns # show columns 

输出:[1] "breaks" "wool" "tension"

我们的数据里有什么?

该数据集显示了每台织机上不同类型的织机在每根固定长度的纱线上发生了多少次经纱断头。我们可以在文档这里中阅读关于该数据集的更多详细信息,但这里是我们将查看的三列以及每一列所指的内容:

圆柱 类型 描述
休息 数字的 休息的次数
羊毛 因素 羊毛的种类(A 或 B)
紧张 因素 张力水*(L,M,H)

在 9 台织机上测量了 6 种经纱中的每一种,数据集中总共有 54 个条目。

让我们看看如何使用ls.str()命令构建数据:

 ls.str(warpbreaks) 

输出:

breaks : num [1:54] 26 30 54 25 70 52 51 26 67 18 ...
tension : Factor w/ 3 levels "L","M","H": 1 1 1 1 1 1 1 1 1 2 ...
wool : Factor w/ 2 levels "A","B": 1 1 1 1 1 1 1 1 1 1 ... 

从上面,我们可以看到数据中存在的类型和级别。阅读这个来学习更多关于 r 中的因子。

现在我们将使用data数据帧。请记住,使用泊松分布模型,我们试图弄清楚一些预测变量如何影响响应变量。这里,breaks是响应变量,wooltension是预测变量。

我们可以通过创建直方图来查看因变量breaks的数据连续性:

 hist(data$breaks) 

histogram-poisson-regression

显然,这些数据并不像正态分布那样呈钟形曲线。

让我们检查因变量的mean()var():

 mean(data$breaks) # calculate mean 

输出:[1] 28.14815

 var(data$breaks) # calculate variance 

输出:[1] 174.2041

方差远大于*均值,这表明我们将在模型中出现过度分散。

让我们使用glm()命令来拟合泊松模型。

 # model poisson regression using glm()
poisson.model < - glm(breaks ~ wool + tension, data, family = poisson(link = "log"))
summary(poisson.model) 

summary()是一个通用函数,用于生成各种模型拟合函数的结果汇总。

输出:

Call: glm(formula = breaks ~ wool + tension, family = poisson(link = "log"), data = data)

Deviance Residuals: 
Min 1Q Median 3Q Max 
-3.6871 -1.6503 -0.4269 1.1902 4.2616 

Coefficients:
Estimate Std. Error z value Pr(>|z|) 
(Intercept) 3.69196 0.04541 81.302 < 2e-16 ***
woolB -0.20599 0.05157 -3.994 6.49e-05 ***
tensionM -0.32132 0.06027 -5.332 9.73e-08 ***
tensionH -0.51849 0.06396 -8.107 5.21e-16 ***
---
Signif. codes: 0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1
(Dispersion parameter for poisson family taken to be 1)
Null deviance: 297.37 on 53 degrees of freedom
Residual deviance: 210.39 on 50 degrees of freedom
AIC: 493.06Number of Fisher Scoring iterations: 4
 

解读泊松模型

我们刚刚得到了许多信息,现在我们需要解释这些信息。名为Estimate的第一列是 α (截距) β [1] 等的系数值。以下是对参数估计的解释:

  • 当 X = 0 时, e x p ( α )=对*均值的影响 μ
  • eXp(β)= X 每增加一个单位,预测变量对 Y 的均值有 e x p ( β )的乘法效应,即 μ
  • 如果 β = 0,那么 exp( β ) = 1,期望计数为 e x p ( α )并且,Y 和 X 不相关。
  • 如果 β > 0,那么 exp( β ) > 1,期望计数是 X = 0 时的 exp( β )倍
  • 如果 β < 0,那么 exp( β ) < 1,期望计数比 X = 0 时的 exp( β )小一倍

如果family = poisson保存在glm()中,则使用最大似然估计 MLE 计算这些参数。

r 将分类变量视为哑变量。分类变量,也称为指标变量,通过将变量中的级别指定为某种数字表示形式而转换为虚拟变量。一般规则是,如果一个因子变量中有 k 个类别,glm()的输出将有k1 个类别,剩余的 1 个作为基本类别。

我们可以在上面的总结中看到,对于羊毛来说,“A”是基础,没有在总结中显示。同样,对于张力来说,“L”是基本范畴。

为了了解哪些解释变量对响应变量有影响,我们将查看 p 值。如果 p 小于 0.05 ,则该变量对响应变量有影响。在上面的总结中,我们可以看到所有的 p 值都小于 0.05,因此,两个解释变量(羊毛和张力)对断裂都有显著影响。注意 R 输出是如何在每个变量的末尾使用***的。星星的数量象征着意义。

在开始解释结果之前,让我们检查一下这个模型是过度分散还是分散不足。如果剩余偏差大于自由度,则存在过度分散。这意味着估计值是正确的,但是标准误差(标准差)是错误的,并且没有被模型解释。

零偏差显示了模型对响应变量的预测有多好,该模型仅包括截距(大*均值),而残差包括独立变量。上面,我们可以看到,3 (53-50 =3)个独立变量的加入使偏离度从 297.37 下降到 210.39。价值观差异越大意味着不适合。

因此,为了获得更准确的标准误差,我们可以使用准泊松模型:

 poisson.model2 < - glm(breaks ~ wool + tension, data = data, family = quasipoisson(link = "log"))
summary(poisson.model2) 

输出:

Call: 
glm(formula = breaks ~ wool + tension, family = poisson(link = "log"), data = data)

Deviance Residuals: 
Min 1Q Median 3Q Max 
-3.6871 -1.6503 -0.4269 1.1902 4.2616 

Coefficients:
Estimate Std. Error z value Pr(>|z|) 
(Intercept) 3.69196 0.04541 81.302 < 2e-16 ***
woolB -0.20599 0.05157 -3.994 6.49e-05 ***
tensionM -0.32132 0.06027 -5.332 9.73e-08 ***
tensionH -0.51849 0.06396 -8.107 5.21e-16 ***
---
Signif. codes: 0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1
(Dispersion parameter for poisson family taken to be 1)
Null deviance: 297.37 on 53 degrees of freedom
Residual deviance: 210.39 on 50 degrees of freedom
AIC: 493.06
Number of Fisher Scoring iterations: 4
 

比较模型:

既然我们有了两种不同的型号,让我们来比较一下哪一种更好。首先,我们将安装arm库,因为它包含了我们需要的一个函数:

 # install.packages("arm")

# load library arm that contains the function se.coef()
library(arm) 

现在我们将使用se.coef()函数从每个模型中提取系数,然后使用cbind()将这些提取的值合并到一个数据帧中,这样我们就可以对它们进行比较。

 # extract coefficients from first model using 'coef()'
coef1 = coef(poisson.model) 

# extract coefficients from second model
coef2 = coef(poisson.model2) 

# extract standard errors from first model using 'se.coef()'
se.coef1 = se.coef(poisson.model) 

# extract standard errors from second model
se.coef2 = se.coef(poisson.model2)

# use 'cbind()' to combine values into one dataframe
models.both < - cbind(coef1, se.coef1, coef2, se.coef2, exponent = exp(coef1)) 

# show dataframe
models.both 

输出:

coef1 se.coef1 coef2 se.coef2 exponent
(Intercept) 3.6919631 0.04541069 3.6919631 0.09374352 40.1235380
woolB -0.2059884 0.05157117 -0.2059884 0.10646089 0.8138425
tensionM -0.3213204 0.06026580 -0.3213204 0.12440965 0.7251908
tensionH -0.5184885 0.06395944 -0.5184885 0.13203462 0.5954198 

在上面的输出中,我们可以看到系数是相同的,但是标准误差是不同的。

记住这几点,让我们看看羊毛的估价。其值为 -0.2059884-0.2059884 的指数为 0.8138425

1-0.8138425 

输出:[1] 0.1861575

这表明从 A 型羊毛换成 B 型羊毛导致断裂减少0.8138425 倍截距,因为估计值-0.2059884 为负。另一种说法是,假设所有其他变量都相同,如果我们将羊毛类型从 A 改为 B,断裂次数将下降 18.6%。

从模型中预测

一旦建立了模型,我们就可以使用predict(model, data, type)来预测结果,使用新的数据框架包含除训练数据之外的数据。让我们看一个例子。

 # make a dataframe with new data
newdata = data.frame(wool = "B", tension = "M")

# use 'predict()' to run model on new data
predict(poisson.model2, newdata = newdata, type = "response") 

输出:[1] 23.68056

我们的模型预测,对于羊毛类型 B 和张力级别 m,将有大约 24 个断裂。

使用 jtools 可视化结果

当你与他人分享你的分析时,表格通常不是吸引人们注意力的最佳方式。图表帮助人们更快地掌握你的发现。在 R 中可视化数据最流行的方法可能是ggplot2(在 Dataquest 的数据可视化课程中讲授),我们还将使用一个名为 jtools 的出色的 R 包,它包括专门用于汇总和可视化回归模型的工具。让我们用jtools来形象化poisson.model2

 # Install the package jtools if not already installed
install.packages("jtools")

# you may be asked to install 'broom' and 'ggstance' packages as well
install.packages("broom")
install.packages("ggstance") 

jtools提供了plot_summs()plot_coefs()来可视化模型的概要,也允许我们用ggplot2来比较不同的模型。

 # Include jtools library
library(jtools)

# plot regression coefficients for poisson.model2
plot_summs(poisson.model2, scale = TRUE, exp = TRUE) 

poisson regression unnamed-chunk-15-1

 # plot regression coefficients for poisson.model2 and poisson.model
plot_summs(poisson.model, poisson.model2, scale = TRUE, exp = TRUE) 

输出:

poisson-regression-unnamed-chunk-15-2

在上面的代码中,plot_summs(poisson.model2, scale = TRUE, exp = TRUE)使用glm中的准泊松家族绘制了第二个模型。

  • plot_summs()中的第一个参数是要使用的回归模型,可以是一个或多个。
  • scale有助于解决变量尺度不同的问题。
  • exp被设置为TRUE,因为对于泊松回归,我们更可能对估计值的指数值感兴趣,而不是线性值。

你可以在文档中找到关于jtoolsplot_summs() 的更多细节。

我们还可以将预测变量之间的相互作用可视化。jtools为不同类型的变量提供不同的函数。例如,如果所有的变量都是分类的,我们可以使用cat_plot()来更好地理解它们之间的相互作用。对于连续变量,使用interact_plot()

warpbreaks 数据中,我们有分类预测变量,所以我们将使用cat_plot()来可视化它们之间的相互作用,通过给它参数来指定我们想要使用的模型、我们正在查看的预测变量以及它与之组合以产生结果的其他预测变量。

 cat_plot(poisson.model2, pred = wool, modx = tension)
# argument 1: regression model
# pred: The categorical variable that will appear on x-axis
# modx: Moderator variable that has an effect in combination to pred on outcome 

输出:

poisson-regression-unnamed-chunk-16-1

我们可以做同样的事情来看看tension:

 # using cat_plot. Pass poisson.model2 and we want to see effect of tension type so we set pred=tension
cat_plot(poisson.model2, pred = tension, modx = wool) 

输出:

poisson-regression-unnamed-chunk-17-1

上面,我们看到了三种不同类型的张力(L、M 和 H)如何影响每种羊毛类型的断裂。例如,低张力和 A 型羊毛的断裂倾向最高。

我们还可以使用geom参数定义由cat_plot()创建的绘图类型。该参数增强了对图的解释。我们可以这样使用它,将geom作为附加参数传递给cat_plot:

 cat_plot(poisson.model2, pred = tension, modx = wool, geom = "line") 

输出:

poisson-regression-unnamed-chunk-18-1

我们也可以通过添加plot.points = TRUE来将观察结果包含在绘图中:

 cat_plot(poisson.model2, pred = tension, modx = wool, geom = "line", plot.points = TRUE) 

输出:

poisson-regression-unnamed-chunk-19-1

有很多其他的设计选项,包括线条样式、颜色等,这将允许我们定制这些可视化的外观。具体的,参考jtools文档这里。

使用速率数据的泊松回归建模

到目前为止,在本教程中,我们已经对计数数据进行了建模,但我们也可以对预测一段时间或分组内的计数数量的速率数据进行建模。模拟速率数据的公式如下:

log(x/n=β+

这相当于:(应用对数公式)

g(x)─l(n=**

g(x=l(n【t11 })+

因此,速率数据可以通过包括系数为 1 的 log(n) 项来建模。这被称为偏移**。该偏移用 r 中的offset()建模。

让我们使用来自 ISwR 包的另一个名为eba1977的数据集来为比率数据建立泊松回归模型。首先,我们将安装软件包:

 # install.packages("ISwR")library(ISwR) 
## Warning: package 'ISwR' was built under R version 3.4.4 

现在,让我们来看看关于数据的一些细节,并打印前十行来了解数据集包含的内容。

 data(eba1977)
cancer.data = eba1977
cancer.data[1:10, ]
# Description
# Lung cancer incidence in four Danish cities 1968-1971
# Description:
# This data set contains counts of incident lung cancer cases and
# population size in four neighbouring Danish cities by age group.
# Format:
# A data frame with 24 observations on the following 4 variables: 
# city a factor with levels Fredericia, Horsens, Kolding, and Vejle.
# age a factor with levels 40-54, 55-59, 60-64, 65-69,70-74, and 75+.
# pop a numeric vector, number of inhabitants.
# cases a numeric vector, number of lung cancer cases. 

输出:

city age pop cases
1 Fredericia 40-54 3059 11
2 Horsens 40-54 2879 13
3 Kolding 40-54 3142 4
4 Vejle 40-54 2520 5
5 Fredericia 55-59 800 11
6 Horsens 55-59 1083 6
7 Kolding 55-59 1050 8
8 Vejle 55-59 878 7
9 Fredericia 60-64 710 11
10 Horsens 60-64 923 15 

为了对速率数据建模,我们使用 X/n ,其中 X 是要发生的事件,而 n 是分组。在本例中,X =病例(事件为癌症病例) n=pop (人群为分组)。

如上式所示,比率数据由 log ( n )计算,在该数据中 n 是人口,因此我们将首先找到人口的 log。我们可以为病例/人群建模如下:

 # find the log(n) of each value in 'pop' column. It is the third column
logpop = log(cancer.data[ ,3])

# add the log values to the dataframe using 'cbind()'
new.cancer.data = cbind(cancer.data, logpop)

# display new dataframe
new.cancer.data 

输出:

city age pop cases logpop
1 Fredericia 40-54 3059 11 8.025843
2 Horsens 40-54 2879 13 7.965198
3 Kolding 40-54 3142 4 8.052615
4 Vejle 40-54 2520 5 7.832014
5 Fredericia 55-59 800 11 6.684612
6 Horsens 55-59 1083 6 6.987490
7 Kolding 55-59 1050 8 6.956545
8 Vejle 55-59 878 7 6.777647
9 Fredericia 60-64 710 11 6.565265
10 Horsens 60-64 923 15 6.827629
11 Kolding 60-64 895 7 6.796824
12 Vejle 60-64 839 10 6.732211
13 Fredericia 65-69 581 10 6.364751
14 Horsens 65-69 834 10 6.726233
15 Kolding 65-69 702 11 6.553933
16 Vejle 65-69 631 14 6.447306
17 Fredericia 70-74 509 11 6.232448
18 Horsens 70-74 634 12 6.452049
19 Kolding 70-74 535 9 6.282267
20 Vejle 70-74 539 8 6.289716
21 Fredericia 75+ 605 10 6.405228
22 Horsens 75+ 782 2 6.661855
23 Kolding 75+ 659 12 6.490724
24 Vejle 75+ 619 7 6.428105 

现在,让我们用offset()对速率数据进行建模。

 poisson.model.rate < - glm(cases ~ city + age+ offset(logpop), family = poisson(link = "log"), data = cancer.data)

#display summary
summary(poisson.model.rate) 

输出:

Call:
glm(formula = cases ~ city + age + offset(logpop), family = poisson(link = "log"), data = cancer.data)

Deviance Residuals: 
Min 1Q Median 3Q Max 
-2.63573 -0.67296 -0.03436 0.37258 1.85267 

Coefficients:
Estimate Std. Error z value Pr(>|z|) 
(Intercept) -5.6321 0.2003 -28.125 < 2e-16 ***
cityHorsens -0.3301 0.1815 -1.818 0.0690 . 
cityKolding -0.3715 0.1878 -1.978 0.0479 * 
cityVejle -0.2723 0.1879 -1.450 0.1472 
age55-59 1.1010 0.2483 4.434 9.23e-06 ***
age60-64 1.5186 0.2316 6.556 5.53e-11 ***
age65-69 1.7677 0.2294 7.704 1.31e-14 ***
age70-74 1.8569 0.2353 7.891 3.00e-15 ***
age75+ 1.4197 0.2503 5.672 1.41e-08 ***
---
Signif. codes: 0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1
(Dispersion parameter for poisson family taken to be 1)Null deviance: 129.908 on 23 degrees of freedom

Residual deviance: 23.447 on 15 degrees of freedom
AIC: 137.84
Number of Fisher Scoring iterations: 5
 

在这个数据集中,我们可以看到剩余偏差接*自由度,离散参数为 1.5 (23.447/15) 很小,因此模型是一个很好的拟合。

我们使用fitted(model)返回模型拟合的值。它使用模型所基于的训练数据返回结果。让我们试一试:

 fitted(poisson.model.rate) 
1 2 3 4 5 6 7 8 
10.954812 7.411803 7.760169 6.873215 8.615485 8.384458 7.798635 7.201421 
9 10 11 12 13 14 15 16 
11.609373 10.849479 10.092831 10.448316 12.187276 12.576313 10.155638 10.080773 
17 18 19 20 21 22 23 24 
11.672630 10.451942 8.461440 9.413988 8.960422 8.326004 6.731286 6.982287 

使用此模型,我们可以使用predict()函数预测新数据集每 1000 人的病例数,就像我们之前对计数数据模型所做的那样:

 # create a test dataframe containing new values of variables
test.data = data.frame(city = "Kolding", age = "40-54", pop = 1000, logpop = log(1000)) 

# predict outcomes (responses) using 'predict()' 
predicted.value < - predict(poisson.model.rate, test.data, type = "response") 

# show predicted value
predicted.value 

输出:[1] 2.469818

因此,对于科灵市 40-54 岁年龄组的人来说,我们可以预计每 1000 人中大约有 2 或 3 例肺癌

与计数数据一样,我们也可以使用准泊松来获得比率数据的更正确的标准误差,但是出于本教程的目的,我们不会重复该过程。

结论

泊松回归模型在计量经济学和现实世界预测中具有重要意义。在本教程中,我们学习了泊松分布、广义线性模型和泊松回归模型。

我们还学习了如何使用glm()为 R 中的计数和比率数据实现泊松回归模型,以及如何将数据拟合到模型中以预测新的数据集。此外,我们研究了如何使用准泊松glm()中获得更精确的标准误差,并看到了一些使用jtools进行可视化的可能性。

[thrive _ leads id = ' 21203 ']

如果你想了解更多关于这个话题的信息,可以去看看 Dataquest 的数据分析师 R 网站,这将帮助你在大约 6 个月内做好工作准备。

参考

  1. https://stat . ethz . ch/R-manual/R-devel/library/stats/html/poisson . html
  2. https://www . the analysis factor . com/generalized-linear-models-in-r-part-6-poisson-regression-count-variables/
  3. https://stats.idre.ucla.edu/r/dae/poisson-regression/
  4. https://www . rdocumentation . org/packages/base/versions/3 . 5 . 2/topics/summary

准备好提升你的 R 技能了吗?

我们 R path 的数据分析师涵盖了你找到工作所需的所有技能,包括:

  • 使用 ggplot2 进行数据可视化
  • 使用 tidyverse 软件包的高级数据清理技能
  • R 用户的重要 SQL 技能
  • 统计和概率的基础知识
  • ...还有多得多的

没有要安装的东西,没有先决条件,也没有时间表。

Start learning for free!*********

教程:使用 Python 脚本和命令行转换数据

原文:https://www.dataquest.io/blog/tutorial-python-scripts-command-line/

October 1, 2019python-scripts-command-line-hackernews

在本教程中,我们将深入探讨如何使用 Python 脚本和命令行来转换数据。

但是首先,有必要问一个你可能会想的问题:“Python 如何适应命令行,当我知道我可以使用 IPython 笔记本或 Jupyter lab 完成所有数据科学工作时,我为什么还要使用命令行与 Python 交互?”

笔记本是快速数据可视化和探索的好工具,但是 Python 脚本是将我们学到的任何东西投入生产的方法。假设你想做一个网站,帮助人们用理想的标题和 sublesson 时间制作黑客新闻帖子。为此,您需要脚本。

本教程假设对函数有基本的了解,一点命令行经验也不会有什么坏处。如果你以前没有使用过 Python,请随意查看我们的课程,包括 Python 函数的基础知识,或者深入学习我们的 T2 数据科学课程。最*,我们发布了两个新的交互式命令行课程:命令行元素和命令行中的文本处理,所以如果你想更深入地了解命令行,我们也推荐这些课程

也就是说,不要太担心先决条件!我们会边走边解释我们正在做的一切,所以让我们开始吧!

熟悉数据

黑客新闻是一个网站,用户可以提交来自互联网的文章(通常是关于技术和创业公司的),其他人可以“投票支持”这些文章,表明他们喜欢这些文章。一个分包商获得的支持票越多,在社区里就越受欢迎。热门文章会出现在黑客新闻的“首页”,在那里它们更有可能被其他人看到。

这就引出了一个问题:是什么让一篇黑客新闻文章获得成功?我们能找到什么样的模式最有可能被投票支持吗?

我们将使用的数据集是 Arnaud Drizard 使用黑客新闻 API 编译的,可以在这里找到。我们从数据中随机抽取了 10000 行,并删除了所有无关的列。我们的数据集只有四列:

  • sublesson_time —故事提交的时间。
  • upvotes —分包商获得的支持票数。
  • url —分包商的基本域。
  • headline——子标题。用户可以编辑它,并且它不必与原始文章的标题相匹配。

我们将编写脚本来回答三个关键问题:

  • 什么词最常出现在标题中?
  • 黑客新闻最常提交哪些域名?
  • 大多数文章是在什么时候提交的?

记住:对于编程来说,完成一项任务有多种方法。在本教程中,我们将通过一种方法来解决这些问题,但肯定还有其他方法可能同样有效,所以请随意尝试,并尝试提出自己的方法!

使用命令行和 Python 脚本读入数据

首先,让我们在桌面上创建一个名为Transforming_Data_with_Python的文件夹。要使用命令行创建文件夹,您可以使用mkdir命令,后跟文件夹的名称。例如,如果你想创建一个名为test的文件夹,你可以导航到桌面目录,然后输入mkdir test

稍后我们将讨论为什么创建一个文件夹,但是现在,让我们使用cd命令导航到创建的文件夹。cd命令允许我们使用命令行改变目录。

虽然使用命令行创建文件有多种方法,但是我们可以利用一种称为管道和重定向输出的技术来同时完成两件事:将来自 stdout 的输出(命令行生成的标准输出)重定向到一个文件中,并创建一个新文件!换句话说,我们可以让它创建一个新文件,并让它输出该文件的内容,而不是让命令行只打印它的输出。

为此,我们可以使用>>>,这取决于我们想要对文件做什么。如果文件不存在,两者都会创建一个文件;然而,>会用重定向的输出覆盖文件中已经存在的文本,而>>会将任何重定向的输出附加到文件中。

我们希望将数据读入这个文件,并创建一个描述性的文件名和函数名,所以我们将创建一个名为load_data()的函数,并将它放在名为read.py的文件中。让我们使用读入数据的命令行来创建我们的函数。为此,我们将使用printf函数。(我们将使用printf,因为它允许我们打印换行符和制表符,我们希望使用它们来使我们的脚本对我们自己和他人来说更具可读性)。

为此,我们可以在命令行中键入以下内容

 printf "import pandas as pd\n\ndef load_data():\n\thn_stories = pd.read_csv('hn_stories.csv')\n\thn_stories.colummns = ['sublesson_time', 'upvotes', 'url', 'headline']\n\treturn(hn_stores)\n" > read.py 

检查上面的代码,有很多事情正在发生。让我们一点一点地分解它。在函数中,我们是:

  • 记住,我们想让我们的脚本可读,我们使用printf命令来生成一些输出,使用命令行来保存我们生成输出时的格式。
  • 进口熊猫。
  • 将我们的数据集(hn_stories.csv)读入熊猫数据帧。
  • 使用df.columns将列名添加到我们的数据框架中。
  • 创建一个名为load_data()的函数,它包含读入和处理数据集的代码。
  • 利用换行符(\n)和制表符(\t)来保存格式,以便 Python 可以读取脚本。
  • 使用>操作符将printf的输出重定向到一个名为read.py的文件。因为read.py还不存在,所以创建了这个文件。

在我们运行上面的代码之后,我们可以在命令行中键入cat read.py并执行命令来检查read.py的内容。如果一切运行正常,我们的read.py文件将如下所示:

 import pandas as pd

def load_data():
    hn_stories = pd.read_csv("hn_stories.csv")
    hn_stories.columns = ['sublesson_time', 'upvotes', 'url', 'headline']
    return(hn_stories) 

创建这个文件后,我们的目录结构应该如下所示

Transforming_Data_with_Python
 | read.py
 | 
 | 
 |
 |
___ 

正在创建__init__.py

对于这个项目的其余部分,我们将创建更多的脚本来回答我们的问题,并使用load_data()函数。虽然我们可以将这个函数粘贴到每个使用它的文件中,但是如果我们正在处理的项目很大,这可能会变得很麻烦。

为了解决这个问题,我们可以创建一个名为__init__.py的文件。本质上,__init__.py允许文件夹将它们的目录文件视为包。最简单的形式,__init__.py可以是一个空文件。它必须存在,目录文件才能被视为包。你可以在 Python 文档中找到更多关于包和模块的信息。

因为load_data()read.py中的一个函数,我们可以像导入包一样导入那个函数:from read import load_data()

还记得使用命令行创建文件有多种方法吗?我们可以使用另一个命令来创建__init__.py,这一次,我们将使用touch命令来创建文件。touch是一个命令,当您运行该命令时,它会为您创建一个空文件:

 touch __init__.py 

创建这个文件后,目录结构将如下所示

Transforming_Data_with_Python
 | __init__.py
 | read.py
 | 
 |
 |
___ 

探索标题中的单词

现在我们已经创建了一个脚本来读入和处理数据,并创建了__init__.py,我们可以开始分析数据了!我们首先要探究的是出现在标题中的独特词汇。为此,我们希望做到以下几点:

  • 使用命令行创建一个名为count.py的文件。
  • read.py导入load_data,调用函数读入数据集。
  • 将所有标题组合成一个长字符串。当你组合标题时,我们希望在每个标题之间留有空间。对于这一步,我们将使用 Series.str.cat 来加入字符串。
  • 把那一长串拆分成单词。
  • 使用计数器类来计算每个单词在字符串中出现的次数。
  • 使用.most_common()方法将 100 个最常用的单词存储到wordCount

如果您使用命令行创建该文件,它看起来是这样的:

 printf "from read import load_data\nfrom collections import Counter\n\nstories = load_data()\nheadlines = stories['headline'].str.cat(sep = ' ').lower()\nwordCount = Counter(headlines.split(' ')).most_common(100)\nprint(wordCount)\n" > count.py 

运行上面的代码后,可以在命令行中键入cat count.py并执行命令来检查count.py的内容。如果一切运行正常,您的count.py文件将如下所示:

 from read import load_data
from collections import Counter

stories = load_data()
headlines = stories["headline"].str.cat(sep = ' ').lower()
wordCount = Counter(headlines.split(' ')).most_common(100)
print(wordCount) 

该目录现在应该是这样的:

Transforming_Data_with_Python
 | __init__.py
 | read.py
 | count.py
 |
 |
___ 

现在我们已经创建了 Python 脚本,我们可以从命令行运行我们的脚本来获得一百个最常见单词的列表。为了运行这个脚本,我们从命令行输入命令python count.py

脚本运行后,您将看到打印的结果:

 [('the', 2045), ('to', 1641), ('a', 1276), ('of', 1170), ('for', 1140), ('in', 1036), ('and', 936), ('', 733), ('is', 620), ('on', 568), ('hn:', 537), ('with', 537), ('how', 526), ('-', 487), ('your', 480), ('you', 392), ('ask', 371), ('from', 310), ('new', 304), ('google', 303), ('why', 262), ('what', 258), ('an', 243), ('are', 223), ('by', 219), ('at', 213), ('show', 205), ('web', 192), ('it', 192), ('–', 184), ('do', 183), ('app', 178), ('i', 173), ('as', 161), ('not', 160), ('that', 160), ('data', 157), ('about', 154), ('be', 154), ('facebook', 150), ('startup', 147), ('my', 131), ('|', 127), ('using', 125), ('free', 125), ('online', 123), ('apple', 123), ('get', 122), ('can', 115), ('open', 114), ('will', 112), ('android', 110), ('this', 110), ('out', 109), ('we', 106), ('its', 102), ('now', 101), ('best', 101), ('up', 100), ('code', 98), ('have', 97), ('or', 96), ('one', 95), ('more', 93), ('first', 93), ('all', 93), ('software', 93), ('make', 92), ('iphone', 91), ('twitter', 91), ('should', 91), ('video', 90), ('social', 89), ('&', 88), ('internet', 88), ('us', 88), ('mobile', 88), ('use', 86), ('has', 84), ('just', 80), ('world', 79), ('design', 79), ('business', 79), ('5', 78), ('apps', 77), ('source', 77), ('cloud', 76), ('into', 76), ('api', 75), ('top', 74), ('tech', 73), ('javascript', 73), ('like', 72), ('programming', 72), ('windows', 72), ('when', 71), ('ios', 70), ('live', 69), ('future', 69), ('most', 68)] 

在我们的网站上滚动浏览它们有点笨拙,但你可能会注意到最常见的单词如thetoafor等。这些词被称为停用词——对人类语言有用但对数据分析没有任何帮助的词。你可以在我们关于空间的教程中找到更多关于停用词的信息。如果您想扩展这个项目,从我们的分析中删除停用词将是一个有趣的下一步。

尽管包含了停用词,我们还是可以发现一些趋势。除了停用词,这些词中的绝大多数都是与科技和创业相关的术语。鉴于 HackerNews 专注于科技初创公司,这并不令人惊讶,但我们可以看到一些有趣的具体趋势。例如,谷歌是这个数据集中最常被提及的品牌。脸书,苹果,推特是其他品牌的热门话题。

探索域子层次

现在,我们已经探索了不同的标题并显示了前 100 个最常用的单词,我们现在可以探索域的子部分了!为此,我们可以执行以下操作:

  • 使用命令行创建一个名为domains.py的文件。
  • read.py导入load_data,调用函数读入数据集。
  • 在 pandas 中使用value_counts()方法来计算一列中每个值出现的次数。
  • 遍历序列并打印索引值及其相关的总计。

下面是它在命令行中的样子:

 printf "from read import load_data\n\nstories = load_data()\ndomains = stories['url'].value_counts()\nfor name, row in domains.items():\n\tprint('{0}: {1}'.format(name, row))\n" > domains.py 

同样,如果我们在命令行中键入cat domains.py来检查domains.py,我们应该会看到:

 from read import load_data

stories = load_data()
domains = stories['url'].value_counts()
for name, row in domains.items():
    print('{0}: {1}'.format(name, row)) 

创建该文件后,我们的目录如下所示:

Transforming_Data_with_Python
 | __init__.py
 | read.py
 | count.py
 | domains.py
 |
___ 

探索 Sublesson 时代

我们想知道大多数文章是什么时候提交的。一个简单的方法就是看看文章是在什么时候提交的。为了解决这个问题,我们需要使用sublesson_time列。

sublesson_time列包含如下所示的时间戳:2011-11-09T21:56:22Z。这些时间用 UTC 表示,这是大多数软件为保持一致性而使用的通用时区(想象一个数据库中的时间都有不同的时区;工作起来会很痛苦)。

为了从时间戳中获取小时,我们可以使用dateutil库。dateutil中的parser模块包含解析函数,该函数可以接收时间戳,并返回一个datetime对象。这里是文档的链接。解析时间戳后,产生的 date 对象的hour属性将告诉您文章提交的时间。

为此,我们可以执行以下操作:

  • 使用命令行创建一个名为times.py的文件。
  • 写一个函数从时间戳中提取小时。这个函数应该首先使用dateutil.parser.parse解析时间戳,然后从结果datetime对象中提取小时,然后使用.hour返回小时。
  • 使用 pandas apply()方法制作一列子小时数。
  • 在 pandas 中使用value_counts()方法来计算每小时出现的次数。
  • 打印出结果。

下面是我们在命令行中的操作方法:

 printf "from dateutil.parser import parse\nfrom read import load_data\n\n\ndef extract_hour(timestamp):\n\tdatetime = parse(timestamp)\n\thour = datetime.hour\n\treturn hour\n\nstories = load_data()\nstories['hour'] = stories['sublesson_time'].apply(extract_hour)\ntime = stories['hour'].value_counts()\nprint(time)" > times.py 

下面是它作为一个单独的.py文件的样子(如上所述,您可以通过从命令行运行cat times.py检查该文件来确认):

 from dateutil.parser import parse
from read import load_data

def extract_hour(timestamp):
    datetime = parse(timestamp)
    hour = datetime.hour
    return hour

nstories = load_data()
stories['hour'] = stories['sublesson_time'].apply(extract_hour)
time = stories['hour'].value_counts()
print(time) 

让我们再一次更新我们的目录:

Transforming_Data_with_Python
 | __init__.py
 | read.py
 | count.py
 | domains.py
 | times.py
___ 

现在我们已经创建了 Python 脚本,我们可以从命令行运行我们的脚本来获取某个小时内发布的文章数量的列表。为此,您可以从命令行键入命令python times.py。运行该脚本,您将看到以下结果:

 17    646
16    627
15    618
14    602
18    575
19    563
20    538
13    531
21    497
12    398
23    394
22    386
11    347
10    324
7     320
0     317
1     314
2     298
9     298
3     296
4     282
6     279
5     275
8     274 

你会注意到大多数转租是在下午公布的。但是,请记住,这些时间是用 UTC 表示的。如果您对扩展这个项目感兴趣,可以尝试在脚本中添加一个部分,将 UTC 的输出转换为您当地的时区。

后续步骤

在本教程中,我们探索了数据并构建了一个简短脚本的目录,这些脚本相互协作以提供我们想要的答案。这是构建我们的数据分析项目的生产版本的第一步。

当然,这仅仅是开始!在本教程中,我们没有使用任何 upvotes 数据,因此这将是扩展您的分析的一个很好的下一步:

  • 什么样的标题长度能获得最多的支持?
  • 什么时候投票最多?
  • 随着时间的推移,向上投票的总数是如何变化的?

我们鼓励您思考自己的问题,并在继续探索该数据集时发挥创造力!

这个教程有帮助吗?

选择你的道路,不断学习有价值的数据技能。

arrow down leftarrow right downPython Tutorials

通过我们的免费教程练习您的 Python 编程技能。

Data science courses

通过我们的交互式浏览器数据科学课程,投入到 Python、R、SQL 等语言的学习中。

教程:在 Pandas 中重置索引

原文:https://www.dataquest.io/blog/tutorial-reset-index-in-pandas/

February 25, 2022

在本教程中,我们将讨论reset_index() pandas 方法,为什么我们可能需要在 pandas 中重置数据帧的索引,以及我们如何应用和调优该方法。我们还将考虑一个在丢失值后重置数据帧索引的小用例。

为了练习数据帧索引重置,我们将在动物收容所分析上使用一个 Kaggle 数据集的样本。

熊猫中的Reset_Index()是什么?

如果我们使用read_csv() pandas 方法读取一个 csv 文件,而没有指定任何索引,那么得到的数据帧将有一个默认的基于整数的索引,从第一行的 0 开始,随后每一行增加 1:

import pandas as pd
import numpy as np

df = pd.read_csv('Austin_Animal_Center_Intakes.csv').head()
df
动物 ID 名字 日期时间 月份年份 找到位置 摄入类型 摄入条件 动物类型 摄入时的性别 摄入时的年龄 类型 颜色
Zero A786884 *布洛克 2019 年 01 月 03 日下午 04 时 19 分 2019 年 01 月 03 日下午 04 时 19 分 德克萨斯州奥斯汀市马金牧场大道 2501 号 走失的家畜 常态 绝育男性 2 年 比格犬混合 三色
one A706918 美女 2015 年 5 月 7 日下午 12 时 59 分 2015 年 5 月 7 日下午 12 时 59 分 德克萨斯州奥斯汀市兰草大道 9409 号 走失的家畜 常态 阉割过的雌性 8 年 英国史宾格猎犬 白色/肝色
Two A724273 巨大的 2016 年 4 月 14 日下午 06 时 43 分 2016 年 4 月 14 日下午 06 时 43 分 德克萨斯州奥斯汀市帕洛米诺路 2818 号 走失的家畜 常态 完整的男性 11 个月 巴森吉混合 沙子/白色
three A665644 圆盘烤饼 2013 年 10 月 21 日上午 07 时 59 分 2013 年 10 月 21 日上午 07 时 59 分 奥斯汀(德克萨斯州) 走失的家畜 生病的 完整的女性 4 周 国内短毛混合品种 白棉布
four A682524 里约 2014 年 6 月 29 日上午 10 时 38 分 2014 年 6 月 29 日上午 10 时 38 分 德克萨斯州奥斯汀市格罗夫大道 800 号 走失的家畜 常态 绝育男性 4 年 杜宾犬/澳大利亚牧牛犬 褐色/灰色

在某些情况下,我们可能希望有更有意义的行标签,所以我们将选择数据帧中的一列作为数据帧索引。当使用index_col参数应用read_csv() pandas 方法时,我们可以直接这样做:

df = pd.read_csv('Austin_Animal_Center_Intakes.csv', index_col='Animal ID').head()
df
动物 ID 名字 日期时间 月份年份 找到位置 摄入类型 摄入条件 动物类型 摄入时的性别 摄入时的年龄 类型 颜色
--- --- --- --- --- --- --- --- --- --- --- ---
A786884 *布洛克 2019 年 01 月 03 日下午 04 时 19 分 2019 年 01 月 03 日下午 04 时 19 分 德克萨斯州奥斯汀市马金牧场大道 2501 号 走失的家畜 常态 绝育男性 2 年 比格犬混合 三色
A706918 美女 2015 年 5 月 7 日下午 12 时 59 分 2015 年 5 月 7 日下午 12 时 59 分 德克萨斯州奥斯汀市兰草大道 9409 号 走失的家畜 常态 阉割过的雌性 8 年 英国史宾格猎犬 白色/肝色
A724273 巨大的 2016 年 4 月 14 日下午 06 时 43 分 2016 年 4 月 14 日下午 06 时 43 分 德克萨斯州奥斯汀市帕洛米诺路 2818 号 走失的家畜 常态 完整的男性 11 个月 巴森吉混合 沙子/白色
A665644 圆盘烤饼 2013 年 10 月 21 日上午 07 时 59 分 2013 年 10 月 21 日上午 07 时 59 分 奥斯汀(德克萨斯州) 走失的家畜 生病的 完整的女性 4 周 国内短毛混合品种 白棉布
A682524 里约 2014 年 6 月 29 日上午 10 时 38 分 2014 年 6 月 29 日上午 10 时 38 分 德克萨斯州奥斯汀市格罗夫大道 800 号 走失的家畜 常态 绝育男性 4 年 杜宾犬/澳大利亚牧牛犬 褐色/灰色

或者,我们可以使用set_index()方法将数据帧的任何列设置为数据帧索引:

df = pd.read_csv('Austin_Animal_Center_Intakes.csv').head()
df.set_index('Animal ID', inplace=True)
df
动物 ID 名字 日期时间 月份年份 找到位置 摄入类型 摄入条件 动物类型 摄入时的性别 摄入时的年龄 类型 颜色
--- --- --- --- --- --- --- --- --- --- --- ---
A786884 *布洛克 2019 年 01 月 03 日下午 04 时 19 分 2019 年 01 月 03 日下午 04 时 19 分 德克萨斯州奥斯汀市马金牧场大道 2501 号 走失的家畜 常态 绝育男性 2 年 比格犬混合 三色
A706918 美女 2015 年 5 月 7 日下午 12 时 59 分 2015 年 5 月 7 日下午 12 时 59 分 德克萨斯州奥斯汀市兰草大道 9409 号 走失的家畜 常态 阉割过的雌性 8 年 英国史宾格猎犬 白色/肝色
A724273 巨大的 2016 年 4 月 14 日下午 06 时 43 分 2016 年 4 月 14 日下午 06 时 43 分 德克萨斯州奥斯汀市帕洛米诺路 2818 号 走失的家畜 常态 完整的男性 11 个月 巴森吉混合 沙子/白色
A665644 圆盘烤饼 2013 年 10 月 21 日上午 07 时 59 分 2013 年 10 月 21 日上午 07 时 59 分 奥斯汀(德克萨斯州) 走失的家畜 生病的 完整的女性 4 周 国内短毛混合品种 白棉布
A682524 里约 2014 年 6 月 29 日上午 10 时 38 分 2014 年 6 月 29 日上午 10 时 38 分 德克萨斯州奥斯汀市格罗夫大道 800 号 走失的家畜 常态 绝育男性 4 年 杜宾犬/澳大利亚牧牛犬 褐色/灰色

如果在某个时候,我们需要恢复默认的数字索引,该怎么办?这就是熊猫方法的用武之地:

df.reset_index()
动物 ID 名字 日期时间 月份年份 找到位置 摄入类型 摄入条件 动物类型 摄入时的性别 摄入时的年龄 类型 颜色
Zero A786884 *布洛克 2019 年 01 月 03 日下午 04 时 19 分 2019 年 01 月 03 日下午 04 时 19 分 德克萨斯州奥斯汀市马金牧场大道 2501 号 走失的家畜 常态 绝育男性 2 年 比格犬混合 三色
one A706918 美女 2015 年 5 月 7 日下午 12 时 59 分 2015 年 5 月 7 日下午 12 时 59 分 德克萨斯州奥斯汀市兰草大道 9409 号 走失的家畜 常态 阉割过的雌性 8 年 英国史宾格猎犬 白色/肝色
Two A724273 巨大的 2016 年 4 月 14 日下午 06 时 43 分 2016 年 4 月 14 日下午 06 时 43 分 德克萨斯州奥斯汀市帕洛米诺路 2818 号 走失的家畜 常态 完整的男性 11 个月 巴森吉混合 沙子/白色
three A665644 圆盘烤饼 2013 年 10 月 21 日上午 07 时 59 分 2013 年 10 月 21 日上午 07 时 59 分 奥斯汀(德克萨斯州) 走失的家畜 生病的 完整的女性 4 周 国内短毛混合品种 白棉布
four A682524 里约 2014 年 6 月 29 日上午 10 时 38 分 2014 年 6 月 29 日上午 10 时 38 分 德克萨斯州奥斯汀市格罗夫大道 800 号 走失的家畜 常态 绝育男性 4 年 杜宾犬/澳大利亚牧牛犬 褐色/灰色

该方法的默认行为包括用默认的基于整数的索引替换现有的 DataFrame 索引,并将旧索引转换为与旧索引同名的新列(如果没有任何名称,则使用名称index)。另外,默认情况下,reset_index()方法从一个MultiIndex中移除所有级别(当情况如此时,我们将在后面看到),并且不影响原始数据帧的创建,而是创建一个新的。

何时使用Reset_Index()方法

reset_index() pandas 方法将 DataFrame 索引重置为默认的数字索引,它在以下情况下特别有用:

  • 当执行数据争论时——特别是预处理操作,如过滤数据或删除丢失的值,导致带有不再连续的数字索引的较小的数据帧(我们将在本教程的结尾探索一个用例)
  • 当索引应该被视为公共数据帧列时
  • 当索引标签没有提供任何有价值的数据信息时

如何调整Reset_Index()方法

前面,我们看到了当我们不向 pandas 方法传递任何参数时,它是如何工作的。如果有必要,我们可以通过调整该方法的各种参数来改变这种默认行为。让我们看看最有用的:leveldropinplace

level

此参数将整数、字符串、元组或列表作为可能的数据类型,并且仅适用于带有MultiIndex的数据帧,如下所示:

df_multiindex = pd.read_csv('Austin_Animal_Center_Intakes.csv', index_col=['Animal ID', 'Name']).head()
df_multiindex
动物 ID 名字 日期时间 月份年份 找到位置 摄入类型 摄入条件 动物类型 摄入时的性别 摄入时的年龄 类型 颜色
--- --- --- --- --- --- --- --- --- --- --- ---
A786884 *布洛克 2019 年 01 月 03 日下午 04 时 19 分 2019 年 01 月 03 日下午 04 时 19 分 德克萨斯州奥斯汀市马金牧场大道 2501 号 走失的家畜 常态 绝育男性 2 年 比格犬混合 三色
A706918 美女 2015 年 5 月 7 日下午 12 时 59 分 2015 年 5 月 7 日下午 12 时 59 分 德克萨斯州奥斯汀市兰草大道 9409 号 走失的家畜 常态 阉割过的雌性 8 年 英国史宾格猎犬 白色/肝色
A724273 巨大的 2016 年 4 月 14 日下午 06 时 43 分 2016 年 4 月 14 日下午 06 时 43 分 德克萨斯州奥斯汀市帕洛米诺路 2818 号 走失的家畜 常态 完整的男性 11 个月 巴森吉混合 沙子/白色
A665644 圆盘烤饼 2013 年 10 月 21 日上午 07 时 59 分 2013 年 10 月 21 日上午 07 时 59 分 奥斯汀(德克萨斯州) 走失的家畜 生病的 完整的女性 4 周 国内短毛混合品种 白棉布
A682524 里约 2014 年 6 月 29 日上午 10 时 38 分 2014 年 6 月 29 日上午 10 时 38 分 德克萨斯州奥斯汀市格罗夫大道 800 号 走失的家畜 常态 绝育男性 4 年 杜宾犬/澳大利亚牧牛犬 褐色/灰色

事实上,如果我们现在检查上面数据帧的索引,我们会看到它不是一个普通的数据帧索引,而是一个MultiIndex对象:

df_multiindex.index
MultiIndex([('A786884',  '*Brock'),
            ('A706918',   'Belle'),
            ('A724273', 'Runster'),
            ('A665644',       nan),
            ('A682524',     'Rio')],
           names=['Animal ID', 'Name'])

默认情况下,reset_index() pandas 方法(level=None)的参数level会删除一个MultiIndex的所有级别:

df_multiindex.reset_index()
动物 ID 名字 日期时间 月份年份 找到位置 摄入类型 摄入条件 动物类型 摄入时的性别 摄入时的年龄 类型 颜色
Zero A786884 *布洛克 2019 年 01 月 03 日下午 04 时 19 分 2019 年 01 月 03 日下午 04 时 19 分 德克萨斯州奥斯汀市马金牧场大道 2501 号 走失的家畜 常态 绝育男性 2 年 比格犬混合 三色
one A706918 美女 2015 年 5 月 7 日下午 12 时 59 分 2015 年 5 月 7 日下午 12 时 59 分 德克萨斯州奥斯汀市兰草大道 9409 号 走失的家畜 常态 阉割过的雌性 8 年 英国史宾格猎犬 白色/肝色
Two A724273 巨大的 2016 年 4 月 14 日下午 06 时 43 分 2016 年 4 月 14 日下午 06 时 43 分 德克萨斯州奥斯汀市帕洛米诺路 2818 号 走失的家畜 常态 完整的男性 11 个月 巴森吉混合 沙子/白色
three A665644 圆盘烤饼 2013 年 10 月 21 日上午 07 时 59 分 2013 年 10 月 21 日上午 07 时 59 分 奥斯汀(德克萨斯州) 走失的家畜 生病的 完整的女性 4 周 国内短毛混合品种 白棉布
four A682524 里约 2014 年 6 月 29 日上午 10 时 38 分 2014 年 6 月 29 日上午 10 时 38 分 德克萨斯州奥斯汀市格罗夫大道 800 号 走失的家畜 常态 绝育男性 4 年 杜宾犬/澳大利亚牧牛犬 褐色/灰色

我们看到,我们的数据帧的两个索引都被转换为公共数据帧列,而索引被重置为默认的基于整数的索引。

相反,如果我们显式传递level的值,该参数将从数据帧索引中删除所选级别,并将它们作为公共数据帧列返回(除非我们选择使用drop参数从数据帧中完全删除该信息)。比较以下操作:

df_multiindex.reset_index(level='Animal ID')
名字 动物 ID 日期时间 月份年份 找到位置 摄入类型 摄入条件 动物类型 摄入时的性别 摄入时的年龄 类型 颜色
--- --- --- --- --- --- --- --- --- --- --- ---
*布洛克 A786884 2019 年 01 月 03 日下午 04 时 19 分 2019 年 01 月 03 日下午 04 时 19 分 德克萨斯州奥斯汀市马金牧场大道 2501 号 走失的家畜 常态 绝育男性 2 年 比格犬混合 三色
美女 A706918 2015 年 5 月 7 日下午 12 时 59 分 2015 年 5 月 7 日下午 12 时 59 分 德克萨斯州奥斯汀市兰草大道 9409 号 走失的家畜 常态 阉割过的雌性 8 年 英国史宾格猎犬 白色/肝色
巨大的 A724273 2016 年 4 月 14 日下午 06 时 43 分 2016 年 4 月 14 日下午 06 时 43 分 德克萨斯州奥斯汀市帕洛米诺路 2818 号 走失的家畜 常态 完整的男性 11 个月 巴森吉混合 沙子/白色
圆盘烤饼 A665644 2013 年 10 月 21 日上午 07 时 59 分 2013 年 10 月 21 日上午 07 时 59 分 奥斯汀(德克萨斯州) 走失的家畜 生病的 完整的女性 4 周 国内短毛混合品种 白棉布
里约 A682524 2014 年 6 月 29 日上午 10 时 38 分 2014 年 6 月 29 日上午 10 时 38 分 德克萨斯州奥斯汀市格罗夫大道 800 号 走失的家畜 常态 绝育男性 4 年 杜宾犬/澳大利亚牧牛犬 褐色/灰色

最初,Animal ID是数据帧的索引之一。在设置了level参数之后,它被从索引中删除,并作为一个名为Animal ID的公共列插入。

df_multiindex.reset_index(level='Name')
动物 ID 名字 日期时间 月份年份 找到位置 摄入类型 摄入条件 动物类型 摄入时的性别 摄入时的年龄 类型 颜色
--- --- --- --- --- --- --- --- --- --- --- ---
A786884 *布洛克 2019 年 01 月 03 日下午 04 时 19 分 2019 年 01 月 03 日下午 04 时 19 分 德克萨斯州奥斯汀市马金牧场大道 2501 号 走失的家畜 常态 绝育男性 2 年 比格犬混合 三色
A706918 美女 2015 年 5 月 7 日下午 12 时 59 分 2015 年 5 月 7 日下午 12 时 59 分 德克萨斯州奥斯汀市兰草大道 9409 号 走失的家畜 常态 阉割过的雌性 8 年 英国史宾格猎犬 白色/肝色
A724273 巨大的 2016 年 4 月 14 日下午 06 时 43 分 2016 年 4 月 14 日下午 06 时 43 分 德克萨斯州奥斯汀市帕洛米诺路 2818 号 走失的家畜 常态 完整的男性 11 个月 巴森吉混合 沙子/白色
A665644 圆盘烤饼 2013 年 10 月 21 日上午 07 时 59 分 2013 年 10 月 21 日上午 07 时 59 分 奥斯汀(德克萨斯州) 走失的家畜 生病的 完整的女性 4 周 国内短毛混合品种 白棉布
A682524 里约 2014 年 6 月 29 日上午 10 时 38 分 2014 年 6 月 29 日上午 10 时 38 分 德克萨斯州奥斯汀市格罗夫大道 800 号 走失的家畜 常态 绝育男性 4 年 杜宾犬/澳大利亚牧牛犬 褐色/灰色

这里,Name最初是数据帧的索引之一。在设置了level参数后,它变成了一个名为Name的普通列。

drop

此参数确定在索引重置后是将旧索引保留为公共数据帧列,还是将其从数据帧中完全删除。默认情况下(drop=False)保留它,正如我们在前面所有例子中看到的。否则,如果我们不想将旧索引保留为一列,我们可以在索引重置后将其从 DataFrame 中完全删除(drop=True):

df
动物 ID 名字 日期时间 月份年份 找到位置 摄入类型 摄入条件 动物类型 摄入时的性别 摄入时的年龄 类型 颜色
--- --- --- --- --- --- --- --- --- --- --- ---
A786884 *布洛克 2019 年 01 月 03 日下午 04 时 19 分 2019 年 01 月 03 日下午 04 时 19 分 德克萨斯州奥斯汀市马金牧场大道 2501 号 走失的家畜 常态 绝育男性 2 年 比格犬混合 三色
A706918 美女 2015 年 5 月 7 日下午 12 时 59 分 2015 年 5 月 7 日下午 12 时 59 分 德克萨斯州奥斯汀市兰草大道 9409 号 走失的家畜 常态 阉割过的雌性 8 年 英国史宾格猎犬 白色/肝色
A724273 巨大的 2016 年 4 月 14 日下午 06 时 43 分 2016 年 4 月 14 日下午 06 时 43 分 德克萨斯州奥斯汀市帕洛米诺路 2818 号 走失的家畜 常态 完整的男性 11 个月 巴森吉混合 沙子/白色
A665644 圆盘烤饼 2013 年 10 月 21 日上午 07 时 59 分 2013 年 10 月 21 日上午 07 时 59 分 奥斯汀(德克萨斯州) 走失的家畜 生病的 完整的女性 4 周 国内短毛混合品种 白棉布
A682524 里约 2014 年 6 月 29 日上午 10 时 38 分 2014 年 6 月 29 日上午 10 时 38 分 德克萨斯州奥斯汀市格罗夫大道 800 号 走失的家畜 常态 绝育男性 4 年 杜宾犬/澳大利亚牧牛犬 褐色/灰色
df.reset_index(drop=True)
名字 日期时间 月份年份 找到位置 摄入类型 摄入条件 动物类型 摄入时的性别 摄入时的年龄 类型 颜色
Zero *布洛克 2019 年 01 月 03 日下午 04 时 19 分 2019 年 01 月 03 日下午 04 时 19 分 德克萨斯州奥斯汀市马金牧场大道 2501 号 走失的家畜 常态 绝育男性 2 年 比格犬混合 三色
one 美女 2015 年 5 月 7 日下午 12 时 59 分 2015 年 5 月 7 日下午 12 时 59 分 德克萨斯州奥斯汀市兰草大道 9409 号 走失的家畜 常态 阉割过的雌性 8 年 英国史宾格猎犬 白色/肝色
Two 巨大的 2016 年 4 月 14 日下午 06 时 43 分 2016 年 4 月 14 日下午 06 时 43 分 德克萨斯州奥斯汀市帕洛米诺路 2818 号 走失的家畜 常态 完整的男性 11 个月 巴森吉混合 沙子/白色
three 圆盘烤饼 2013 年 10 月 21 日上午 07 时 59 分 2013 年 10 月 21 日上午 07 时 59 分 奥斯汀(德克萨斯州) 走失的家畜 生病的 完整的女性 4 周 国内短毛混合品种 白棉布
four 里约 2014 年 6 月 29 日上午 10 时 38 分 2014 年 6 月 29 日上午 10 时 38 分 德克萨斯州奥斯汀市格罗夫大道 800 号 走失的家畜 常态 绝育男性 4 年 杜宾犬/澳大利亚牧牛犬 褐色/灰色

在上面的数据帧中,旧索引中包含的信息已从数据帧中完全删除。

drop参数也适用于带有MultiIndex的数据帧,就像我们之前创建的那个:

df_multiindex
动物 ID 名字 日期时间 月份年份 找到位置 摄入类型 摄入条件 动物类型 摄入时的性别 摄入时的年龄 类型 颜色
--- --- --- --- --- --- --- --- --- --- --- ---
A786884 *布洛克 2019 年 01 月 03 日下午 04 时 19 分 2019 年 01 月 03 日下午 04 时 19 分 德克萨斯州奥斯汀市马金牧场大道 2501 号 走失的家畜 常态 绝育男性 2 年 比格犬混合 三色
A706918 美女 2015 年 5 月 7 日下午 12 时 59 分 2015 年 5 月 7 日下午 12 时 59 分 德克萨斯州奥斯汀市兰草大道 9409 号 走失的家畜 常态 阉割过的雌性 8 年 英国史宾格猎犬 白色/肝色
A724273 巨大的 2016 年 4 月 14 日下午 06 时 43 分 2016 年 4 月 14 日下午 06 时 43 分 德克萨斯州奥斯汀市帕洛米诺路 2818 号 走失的家畜 常态 完整的男性 11 个月 巴森吉混合 沙子/白色
A665644 圆盘烤饼 2013 年 10 月 21 日上午 07 时 59 分 2013 年 10 月 21 日上午 07 时 59 分 奥斯汀(德克萨斯州) 走失的家畜 生病的 完整的女性 4 周 国内短毛混合品种 白棉布
A682524 里约 2014 年 6 月 29 日上午 10 时 38 分 2014 年 6 月 29 日上午 10 时 38 分 德克萨斯州奥斯汀市格罗夫大道 800 号 走失的家畜 常态 绝育男性 4 年 杜宾犬/澳大利亚牧牛犬 褐色/灰色
df_multiindex.reset_index(drop=True)
日期时间 月份年份 找到位置 摄入类型 摄入条件 动物类型 摄入时的性别 摄入时的年龄 类型 颜色
Zero 2019 年 01 月 03 日下午 04 时 19 分 2019 年 01 月 03 日下午 04 时 19 分 德克萨斯州奥斯汀市马金牧场大道 2501 号 走失的家畜 常态 绝育男性 2 年 比格犬混合 三色
one 2015 年 5 月 7 日下午 12 时 59 分 2015 年 5 月 7 日下午 12 时 59 分 德克萨斯州奥斯汀市兰草大道 9409 号 走失的家畜 常态 阉割过的雌性 8 年 英国史宾格猎犬 白色/肝色
Two 2016 年 4 月 14 日下午 06 时 43 分 2016 年 4 月 14 日下午 06 时 43 分 德克萨斯州奥斯汀市帕洛米诺路 2818 号 走失的家畜 常态 完整的男性 11 个月 巴森吉混合 沙子/白色
three 2013 年 10 月 21 日上午 07 时 59 分 2013 年 10 月 21 日上午 07 时 59 分 奥斯汀(德克萨斯州) 走失的家畜 生病的 完整的女性 4 周 国内短毛混合品种 白棉布
four 2014 年 6 月 29 日上午 10 时 38 分 2014 年 6 月 29 日上午 10 时 38 分 德克萨斯州奥斯汀市格罗夫大道 800 号 走失的家畜 常态 绝育男性 4 年 杜宾犬/澳大利亚牧牛犬 褐色/灰色

两个旧索引都从数据帧中完全删除,并且索引被重置为默认值。

当然,我们可以组合droplevel参数,指定要从数据帧中完全删除的旧索引:

df_multiindex.reset_index(level='Animal ID', drop=True)
日期时间 月份年份 找到位置 摄入类型 摄入条件 动物类型 摄入时的性别 摄入时的年龄 类型 颜色
名字
--- --- --- --- --- --- --- --- --- --- ---
*布洛克 2019 年 01 月 03 日下午 04 时 19 分 2019 年 01 月 03 日下午 04 时 19 分 德克萨斯州奥斯汀市马金牧场大道 2501 号 走失的家畜 常态 绝育男性 2 年 比格犬混合 三色
美女 2015 年 5 月 7 日下午 12 时 59 分 2015 年 5 月 7 日下午 12 时 59 分 德克萨斯州奥斯汀市兰草大道 9409 号 走失的家畜 常态 阉割过的雌性 8 年 英国史宾格猎犬 白色/肝色
巨大的 2016 年 4 月 14 日下午 06 时 43 分 2016 年 4 月 14 日下午 06 时 43 分 德克萨斯州奥斯汀市帕洛米诺路 2818 号 走失的家畜 常态 完整的男性 11 个月 巴森吉混合 沙子/白色
圆盘烤饼 2013 年 10 月 21 日上午 07 时 59 分 2013 年 10 月 21 日上午 07 时 59 分 奥斯汀(德克萨斯州) 走失的家畜 生病的 完整的女性 4 周 国内短毛混合品种 白棉布
里约 2014 年 6 月 29 日上午 10 时 38 分 2014 年 6 月 29 日上午 10 时 38 分 德克萨斯州奥斯汀市格罗夫大道 800 号 走失的家畜 常态 绝育男性 4 年 杜宾犬/澳大利亚牧牛犬 褐色/灰色

旧索引Animal ID已从索引和数据帧本身中删除。另一个索引Name被保留为数据帧的当前索引。

inplace

该参数确定是直接修改原始数据帧还是创建新的数据帧对象。默认情况下,它用新的索引(inplace=False)创建一个新的数据帧,并保持原来的数据帧不变。实际上,让我们使用默认参数再次运行reset_index()方法,然后将结果与原始数据帧进行比较:

df.reset_index()
动物 ID 名字 日期时间 月份年份 找到位置 摄入类型 摄入条件 动物类型 摄入时的性别 摄入时的年龄 类型 颜色
Zero A786884 *布洛克 2019 年 01 月 03 日下午 04 时 19 分 2019 年 01 月 03 日下午 04 时 19 分 德克萨斯州奥斯汀市马金牧场大道 2501 号 走失的家畜 常态 绝育男性 2 年 比格犬混合 三色
one A706918 美女 2015 年 5 月 7 日下午 12 时 59 分 2015 年 5 月 7 日下午 12 时 59 分 德克萨斯州奥斯汀市兰草大道 9409 号 走失的家畜 常态 阉割过的雌性 8 年 英国史宾格猎犬 白色/肝色
Two A724273 巨大的 2016 年 4 月 14 日下午 06 时 43 分 2016 年 4 月 14 日下午 06 时 43 分 德克萨斯州奥斯汀市帕洛米诺路 2818 号 走失的家畜 常态 完整的男性 11 个月 巴森吉混合 沙子/白色
three A665644 圆盘烤饼 2013 年 10 月 21 日上午 07 时 59 分 2013 年 10 月 21 日上午 07 时 59 分 奥斯汀(德克萨斯州) 走失的家畜 生病的 完整的女性 4 周 国内短毛混合品种 白棉布
four A682524 里约 2014 年 6 月 29 日上午 10 时 38 分 2014 年 6 月 29 日上午 10 时 38 分 德克萨斯州奥斯汀市格罗夫大道 800 号 走失的家畜 常态 绝育男性 4 年 杜宾犬/澳大利亚牧牛犬 褐色/灰色
df
动物 ID 名字 日期时间 月份年份 找到位置 摄入类型 摄入条件 动物类型 摄入时的性别 摄入时的年龄 类型 颜色
--- --- --- --- --- --- --- --- --- --- --- ---
A786884 *布洛克 2019 年 01 月 03 日下午 04 时 19 分 2019 年 01 月 03 日下午 04 时 19 分 德克萨斯州奥斯汀市马金牧场大道 2501 号 走失的家畜 常态 绝育男性 2 年 比格犬混合 三色
A706918 美女 2015 年 5 月 7 日下午 12 时 59 分 2015 年 5 月 7 日下午 12 时 59 分 德克萨斯州奥斯汀市兰草大道 9409 号 走失的家畜 常态 阉割过的雌性 8 年 英国史宾格猎犬 白色/肝色
A724273 巨大的 2016 年 4 月 14 日下午 06 时 43 分 2016 年 4 月 14 日下午 06 时 43 分 德克萨斯州奥斯汀市帕洛米诺路 2818 号 走失的家畜 常态 完整的男性 11 个月 巴森吉混合 沙子/白色
A665644 圆盘烤饼 2013 年 10 月 21 日上午 07 时 59 分 2013 年 10 月 21 日上午 07 时 59 分 奥斯汀(德克萨斯州) 走失的家畜 生病的 完整的女性 4 周 国内短毛混合品种 白棉布
A682524 里约 2014 年 6 月 29 日上午 10 时 38 分 2014 年 6 月 29 日上午 10 时 38 分 德克萨斯州奥斯汀市格罗夫大道 800 号 走失的家畜 常态 绝育男性 4 年 杜宾犬/澳大利亚牧牛犬 褐色/灰色

即使我们在运行第一段代码时将索引重置为默认的数字索引,原始数据帧df仍然保持不变。如果我们需要将原始数据帧重新分配给对其应用reset_index()方法的结果,我们可以直接重新分配(df = df.reset_index())或将参数inplace=True传递给该方法:

df.reset_index(inplace=True)
df
动物 ID 名字 日期时间 月份年份 找到位置 摄入类型 摄入条件 动物类型 摄入时的性别 摄入时的年龄 类型 颜色
Zero A786884 *布洛克 2019 年 01 月 03 日下午 04 时 19 分 2019 年 01 月 03 日下午 04 时 19 分 德克萨斯州奥斯汀市马金牧场大道 2501 号 走失的家畜 常态 绝育男性 2 年 比格犬混合 三色
one A706918 美女 2015 年 5 月 7 日下午 12 时 59 分 2015 年 5 月 7 日下午 12 时 59 分 德克萨斯州奥斯汀市兰草大道 9409 号 走失的家畜 常态 阉割过的雌性 8 年 英国史宾格猎犬 白色/肝色
Two A724273 巨大的 2016 年 4 月 14 日下午 06 时 43 分 2016 年 4 月 14 日下午 06 时 43 分 德克萨斯州奥斯汀市帕洛米诺路 2818 号 走失的家畜 常态 完整的男性 11 个月 巴森吉混合 沙子/白色
three A665644 圆盘烤饼 2013 年 10 月 21 日上午 07 时 59 分 2013 年 10 月 21 日上午 07 时 59 分 奥斯汀(德克萨斯州) 走失的家畜 生病的 完整的女性 4 周 国内短毛混合品种 白棉布
four A682524 里约 2014 年 6 月 29 日上午 10 时 38 分 2014 年 6 月 29 日上午 10 时 38 分 德克萨斯州奥斯汀市格罗夫大道 800 号 走失的家畜 常态 绝育男性 4 年 杜宾犬/澳大利亚牧牛犬 褐色/灰色

我们看到,现在更改已经直接应用于原始数据帧。

用例:删除缺失值后重置索引

让我们将目前所讨论的一切付诸实践,看看当我们从数据帧中删除丢失的值时,重置数据帧索引是如何有用的。

首先,让我们恢复我们在本教程开始时创建的第一个数据帧,它具有默认的数字索引:

df = pd.read_csv('Austin_Animal_Center_Intakes.csv').head()
df
动物 ID 名字 日期时间 月份年份 找到位置 摄入类型 摄入条件 动物类型 摄入时的性别 摄入时的年龄 类型 颜色
Zero A786884 *布洛克 2019 年 01 月 03 日下午 04 时 19 分 2019 年 01 月 03 日下午 04 时 19 分 德克萨斯州奥斯汀市马金牧场大道 2501 号 走失的家畜 常态 绝育男性 2 年 比格犬混合 三色
one A706918 美女 2015 年 5 月 7 日下午 12 时 59 分 2015 年 5 月 7 日下午 12 时 59 分 德克萨斯州奥斯汀市兰草大道 9409 号 走失的家畜 常态 阉割过的雌性 8 年 英国史宾格猎犬 白色/肝色
Two A724273 巨大的 2016 年 4 月 14 日下午 06 时 43 分 2016 年 4 月 14 日下午 06 时 43 分 德克萨斯州奥斯汀市帕洛米诺路 2818 号 走失的家畜 常态 完整的男性 11 个月 巴森吉混合 沙子/白色
three A665644 圆盘烤饼 2013 年 10 月 21 日上午 07 时 59 分 2013 年 10 月 21 日上午 07 时 59 分 奥斯汀(德克萨斯州) 走失的家畜 生病的 完整的女性 4 周 国内短毛混合品种 白棉布
four A682524 里约 2014 年 6 月 29 日上午 10 时 38 分 2014 年 6 月 29 日上午 10 时 38 分 德克萨斯州奥斯汀市格罗夫大道 800 号 走失的家畜 常态 绝育男性 4 年 杜宾犬/澳大利亚牧牛犬 褐色/灰色

我们发现数据帧中缺少一个值。让我们使用dropna()方法删除缺少值的整行:

df.dropna(inplace=True)
df
动物 ID 名字 日期时间 月份年份 找到位置 摄入类型 摄入条件 动物类型 摄入时的性别 摄入时的年龄 类型 颜色
Zero A786884 *布洛克 2019 年 01 月 03 日下午 04 时 19 分 2019 年 01 月 03 日下午 04 时 19 分 德克萨斯州奥斯汀市马金牧场大道 2501 号 走失的家畜 常态 绝育男性 2 年 比格犬混合 三色
one A706918 美女 2015 年 5 月 7 日下午 12 时 59 分 2015 年 5 月 7 日下午 12 时 59 分 德克萨斯州奥斯汀市兰草大道 9409 号 走失的家畜 常态 阉割过的雌性 8 年 英国史宾格猎犬 白色/肝色
Two A724273 巨大的 2016 年 4 月 14 日下午 06 时 43 分 2016 年 4 月 14 日下午 06 时 43 分 德克萨斯州奥斯汀市帕洛米诺路 2818 号 走失的家畜 常态 完整的男性 11 个月 巴森吉混合 沙子/白色
four A682524 里约 2014 年 6 月 29 日上午 10 时 38 分 2014 年 6 月 29 日上午 10 时 38 分 德克萨斯州奥斯汀市格罗夫大道 800 号 走失的家畜 常态 绝育男性 4 年 杜宾犬/澳大利亚牧牛犬 褐色/灰色

该行已从数据框架中删除。但是,索引不再是连续的:0,1,2,4。让我们重置它:

df.reset_index()
指数 动物 ID 名字 日期时间 月份年份 找到位置 摄入类型 摄入条件 动物类型 摄入时的性别 摄入时的年龄 类型 颜色
Zero Zero A786884 *布洛克 2019 年 01 月 03 日下午 04 时 19 分 2019 年 01 月 03 日下午 04 时 19 分 德克萨斯州奥斯汀市马金牧场大道 2501 号 走失的家畜 常态 绝育男性 2 年 比格犬混合 三色
one one A706918 美女 2015 年 5 月 7 日下午 12 时 59 分 2015 年 5 月 7 日下午 12 时 59 分 德克萨斯州奥斯汀市兰草大道 9409 号 走失的家畜 常态 阉割过的雌性 8 年 英国史宾格猎犬 白色/肝色
Two Two A724273 巨大的 2016 年 4 月 14 日下午 06 时 43 分 2016 年 4 月 14 日下午 06 时 43 分 德克萨斯州奥斯汀市帕洛米诺路 2818 号 走失的家畜 常态 完整的男性 11 个月 巴森吉混合 沙子/白色
three four A682524 里约 2014 年 6 月 29 日上午 10 时 38 分 2014 年 6 月 29 日上午 10 时 38 分 德克萨斯州奥斯汀市格罗夫大道 800 号 走失的家畜 常态 绝育男性 4 年 杜宾犬/澳大利亚牧牛犬 褐色/灰色

现在指数是连续的;然而,由于我们没有显式地传递参数drop,旧的索引被转换成一个列,默认名称为index。让我们从数据帧中完全删除旧索引:

df.reset_index(drop=True)
动物 ID 名字 日期时间 月份年份 找到位置 摄入类型 摄入条件 动物类型 摄入时的性别 摄入时的年龄 类型 颜色
Zero A786884 *布洛克 2019 年 01 月 03 日下午 04 时 19 分 2019 年 01 月 03 日下午 04 时 19 分 德克萨斯州奥斯汀市马金牧场大道 2501 号 走失的家畜 常态 绝育男性 2 年 比格犬混合 三色
one A706918 美女 2015 年 5 月 7 日下午 12 时 59 分 2015 年 5 月 7 日下午 12 时 59 分 德克萨斯州奥斯汀市兰草大道 9409 号 走失的家畜 常态 阉割过的雌性 8 年 英国史宾格猎犬 白色/肝色
Two A724273 巨大的 2016 年 4 月 14 日下午 06 时 43 分 2016 年 4 月 14 日下午 06 时 43 分 德克萨斯州奥斯汀市帕洛米诺路 2818 号 走失的家畜 常态 完整的男性 11 个月 巴森吉混合 沙子/白色
three A682524 里约 2014 年 6 月 29 日上午 10 时 38 分 2014 年 6 月 29 日上午 10 时 38 分 德克萨斯州奥斯汀市格罗夫大道 800 号 走失的家畜 常态 绝育男性 4 年 杜宾犬/澳大利亚牧牛犬 褐色/灰色

我们彻底摆脱了无意义的旧指数,现在的指数是连续的。最后一步是使用inplace参数将这些修改保存到我们的原始数据帧:

df.reset_index(drop=True, inplace=True)
df
动物 ID 名字 日期时间 月份年份 找到位置 摄入类型 摄入条件 动物类型 摄入时的性别 摄入时的年龄 类型 颜色
Zero A786884 *布洛克 2019 年 01 月 03 日下午 04 时 19 分 2019 年 01 月 03 日下午 04 时 19 分 德克萨斯州奥斯汀市马金牧场大道 2501 号 走失的家畜 常态 绝育男性 2 年 比格犬混合 三色
one A706918 美女 2015 年 5 月 7 日下午 12 时 59 分 2015 年 5 月 7 日下午 12 时 59 分 德克萨斯州奥斯汀市兰草大道 9409 号 走失的家畜 常态 阉割过的雌性 8 年 英国史宾格猎犬 白色/肝色
Two A724273 巨大的 2016 年 4 月 14 日下午 06 时 43 分 2016 年 4 月 14 日下午 06 时 43 分 德克萨斯州奥斯汀市帕洛米诺路 2818 号 走失的家畜 常态 完整的男性 11 个月 巴森吉混合 沙子/白色
three A682524 里约 2014 年 6 月 29 日上午 10 时 38 分 2014 年 6 月 29 日上午 10 时 38 分 德克萨斯州奥斯汀市格罗夫大道 800 号 走失的家畜 常态 绝育男性 4 年 杜宾犬/澳大利亚牧牛犬 褐色/灰色

结论

总之,我们从多方面考虑了熊猫法。我们了解到以下情况:

  • reset_index() pandas 方法的默认行为
  • 如何恢复数据帧的默认数字索引
  • 什么时候使用reset_index()熊猫法
  • 该方法最重要的参数
  • 如何使用MultiIndex
  • 如何从数据帧中完全删除旧索引
  • 如何将修改直接保存到原始数据帧

此外,我们探索了在丢失值后重置数据帧索引的用例。

教程:用 Python 进行文本分析以测试假设

原文:https://www.dataquest.io/blog/tutorial-text-analysis-python-test-hypothesis/

May 16, 2019

人们经常抱怨新闻对重要话题报道太少。其中一个主题是气候变化。科学界的共识是,这是一个重要的问题,显而易见,越多的人意识到这个问题,我们解决这个问题的机会就越大。但是我们如何评估各种媒体对气候变化的报道有多广泛呢?我们可以用 Python 做一些文本分析!

具体来说,在本帖中,我们将尝试回答一些关于哪些新闻媒体对气候变化报道最多的问题。同时,我们将学习一些在 Python 中分析文本数据和测试与该数据相关的假设所需的编程技巧。

本教程假设您非常熟悉 Python 和流行的数据科学包 pandas。如果你想温习熊猫,请查看这篇帖子,如果你需要建立一个更全面的基础, Dataquest 的数据科学课程更深入地涵盖了 Python 和熊猫的所有基础知识。

寻找和探索我们的数据集

在这篇文章中,我们将使用 Andrew Thompson 提供的来自 Kaggle 的新闻数据集(无关系)。该数据集包含来自 15 个来源的超过 142,000 篇文章,主要来自 2016 年和 2017 年,并被分成三个不同的 csv 文件。以下是 Andrew 在 Kaggle 概览页面上显示的文章数量:

Text_Hyp_Test-Updated_2_0

稍后我们将致力于复制我们自己的版本。但是,值得关注的一件事是,这些新闻媒体的特征与它们发表的气候变化相关文章的比例之间是否存在关联。

我们可以关注的一些有趣的特征包括所有权(独立、非营利或公司)和政治倾向(如果有的话)。下面,我做了一些初步的研究,从维基百科和提供商自己的网页上收集信息。

我还发现了两个网站,allsides.com 和 mediabiasfactcheck.com,对出版物的自由和保守倾向进行评级,所以我从那里收集了一些关于政治倾向的信息。

  • 大西洋:
    • 业主:大西洋传媒;多数股权最*卖给了艾默生集体公司,这是一家由史蒂夫·乔布斯的遗孀鲍威尔·乔布斯创立的非盈利组织
    • 向左倾斜
  • 布莱巴特:
    • 所有者:Breitbart 新闻网络有限责任公司
    • 由一位保守派评论员创立
    • 对吧
  • 商业内幕:
    • 所有者:Alex Springer SE(欧洲出版社)
    • 居中/左居中
  • Buzzfeed 新闻:
    • 私人,Jonah Peretti 首席执行官兼执行主席 Kenneth Lerer(后者也是《赫芬顿邮报》的联合创始人)
    • 向左倾斜
  • CNN:
    • 特纳广播系统,大众媒体
    • TBS 本身为时代华纳所有
    • 向左倾斜
  • 福克斯新闻频道:
    • 福克斯娱乐集团,大众传媒
    • 向右/向右倾斜
  • 卫报:
    • 卫报媒体集团(英国),大众传媒
    • 归斯科特信托有限公司所有
    • 向左倾斜
  • 国家评论:
    • 国家评论协会,一个非盈利机构
    • 由小威廉·F·巴克利创建
    • 对吧
  • 纽约邮报:
    • 新闻集团,大众媒体
    • 右/右居中
  • 纽约时报:
    • 纽约时报公司
    • 向左倾斜
  • NPR:
    • 非营利组织
    • 居中/左居中
  • 路透社:
    • 汤森路透公司(加拿大跨国大众媒体)
    • 中心
  • 谈话要点备忘录:
    • 乔希·马歇尔,独立报
    • 左边的
  • 华盛顿邮报:
    • 纳什控股有限责任公司,由 j .贝佐斯控制
    • 向左倾斜
  • Vox:
    • Vox 媒体,跨国公司
    • 向左/向左倾斜

回顾这一点,我们可能会假设,例如,右倾的布莱巴特,与气候相关的文章比例会低于 NPR。

我们可以把它变成一个正式的假设陈述,我们会在后面的文章中这样做。但首先,让我们更深入地研究数据。术语注释:在计算语言学和 NLP 社区中,像这样的文本集合被称为语料库,所以在讨论我们的文本数据集时,我们将在这里使用该术语。

探索性数据分析(EDA)是任何数据科学项目的重要组成部分。它通常涉及以各种方式分析和可视化数据,以在进行更深入的分析之前寻找模式。不过,在这种情况下,我们处理的是文本数据而不是数字数据,这就有点不同了。

例如,在数值探索性数据分析中,我们经常想要查看数据特征的*均值。但是在文本数据库中没有所谓的“*均”单词,这使得我们的任务有点复杂。然而,仍然有定量和定性的探索,我们可以执行健全检查我们的语料库的完整性。

首先,让我们复制上面的图表,以确保我们没有遗漏任何数据,然后按文章数量排序。我们将从覆盖所有的导入开始,读取数据集,并检查其三个部分的长度。

# set up and load data, checking we've gotten it all

import pandas as pd
import numpy as np
import string
import re
from collections import Counter
from nltk.corpus import stopwords

pt1= pd.read_csv('data/articles1.csv.zip',
compression='zip', index_col=0)

pt1.head()
身份证明(identification) 标题 出版 作者 日期 全球资源定位器(Uniform Resource Locator) 内容
Zero Seventeen thousand two hundred and eighty-three 众议院共和党人担心赢得他们的头… 纽约时报 卡尔·赫尔斯 2016-12-31 Two thousand and sixteen Twelve 圆盘烤饼 华盛顿——国会共和党人已经…
one Seventeen thousand two hundred and eighty-four 警官和居民之间的裂痕就像杀戮… 纽约时报 本杰明·穆勒和艾尔·贝克 2017-06-19 Two thousand and seventeen Six 圆盘烤饼 子弹壳数完之后,血…
Two Seventeen thousand two hundred and eighty-five 泰勒斯黄,'小鹿斑比'艺术家受阻于种族… 纽约时报 玛格丽特·福克斯 2017-01-06 Two thousand and seventeen One 圆盘烤饼 当华特·迪士尼的《斑比》在 1942 年上映时,克里…
three Seventeen thousand two hundred and eighty-six 在 2016 年的死亡人数中,流行音乐死亡人数最多… 纽约时报 威廉·麦克唐纳 2017-04-10 Two thousand and seventeen Four 圆盘烤饼 死亡可能是最好的均衡器,但它不是…
four Seventeen thousand two hundred and eighty-seven 金正恩称朝鲜正准备进行核试验 纽约时报 让他转到匈牙利 2017-01-02 Two thousand and seventeen One 圆盘烤饼 韩国首尔——朝鲜领导人……
len(pt1)
50000
pt2 = pd.read_csv('data/articles2.csv.zip',compression='zip',index_col=0)
len(pt2)
49999
pt3 = pd.read_csv('data/articles3.csv.zip',compression='zip',index_col=0)
len(pt3)
42571

不过,处理三个独立的数据集并不方便。我们将把所有三个数据帧合并成一个,这样我们可以更容易地处理整个语料库:

articles = pd.concat([pt1,pt2,pt3])
len(articles)
142570

接下来,我们将确保我们拥有与原始数据集中相同的出版物名称,并检查文章的最早和最新年份。

articles.publication.unique()
array(['New York Times', 'Breitbart', 'CNN', 'Business Insider',
       'Atlantic', 'Fox News', 'Talking Points Memo', 'Buzzfeed News',
       'National Review', 'New York Post', 'Guardian', 'NPR', 'Reuters',
       'Vox', 'Washington Post'], dtype=object)
print(articles['year'].min())
articles['year'].max()
2000.0
2017.0

像我们上面看到的那样将日期存储为浮点数是不常见的,但这就是它们在 CSV 文件中的存储方式。我们不打算在任何太重要的事情上使用日期,所以出于本教程的目的,我们将把它们作为浮点数。但是,如果我们正在进行不同的分析,我们可能希望将它们转换成不同的格式。

先来快速看一下我们的文章是从什么时候开始使用熊猫的value_counts()功能的。

articles['year'].value_counts()
2016.0    85405
2017.0    50404
2015.0     3705
2013.0      228
2014.0      125
2012.0       34
2011.0        8
2010.0        6
2009.0        3
2008.0        3
2005.0        2
2004.0        2
2003.0        2
2007.0        1
2000.0        1
Name: year, dtype: int64

我们可以看到大部分是最*几年的,但也包括一些旧的文章。这正好符合我们的目的,因为我们最关心的是过去几年的报道。

现在,让我们按名称对出版物进行排序,以再现来自 Kaggle 的原始情节。

ax = articles['publication'].value_counts().sort_index().plot(kind='bar', fontsize=14, figsize=(12,10))
ax.set_title('Article Count\n', fontsize=20)
ax.set_xlabel('Publication', fontsize=18)
ax.set_ylabel('Count', fontsize=18);

Text_Hyp_Test-Updated_21_0

如果你想快速找到一个特定的出口,这种图的顺序是很有帮助的,但是对我们来说,按文章数量排序可能更有帮助,这样我们可以更好地了解我们的数据来自哪里。

ax = articles['publication'].value_counts().plot(kind='bar', fontsize=14, figsize=(12,10))
ax.set_title('Article Count - most to least\n', fontsize=20)
ax.set_xlabel('Publication', fontsize=18)
ax.set_ylabel('Count', fontsize=18);

Text_Hyp_Test-Updated_21_0

我们想要检查*均文章长度,但是同样重要的是这些单词的多样性。让我们两个都看看。

我们将从定义一个函数开始,该函数删除标点并将所有文本转换为小写。(我们没有做任何复杂的句法分析,所以我们不需要保留句子结构或大写)。

def clean_text(article):
    clean1 = re.sub(r'['+string.punctuation + '’—”'+']', "", article.lower())
    return re.sub(r'\W+', ' ', clean1)

现在,我们将在 dataframe 中创建一个新列,其中包含已清理的文本。

articles['tokenized'] = articles['content'].map(lambda x: clean_text(x))
articles['tokenized'].head()
0    washington congressional republicans have a ne...
1    after the bullet shells get counted the blood ...
2    when walt disneys bambi opened in 1942 critics...
3    death may be the great equalizer but it isnt n...
4    seoul south korea north koreas leader kim said...
Name: tokenized, dtype: object

上面,我们可以看到,我们已经成功地从语料库中删除了大写和标点符号,这将使我们更容易识别和统计独特的单词。

让我们看看每篇文章的*均字数,以及数据集中最长和最短的文章。

articles['num_wds'] = articles['tokenized'].apply(lambda x: len(x.split()))
articles['num_wds'].mean()
732.36012485095046
articles['num_wds'].max()
articles['num_wds'].min()
49902
0

一篇没有单词的文章对我们没有任何用处,所以让我们看看有多少单词。我们希望从数据集中删除没有单词的文章。

len(articles[articles['num_wds']==0])
97

让我们去掉那些空文章,然后看看这对我们数据集中每篇文章的*均字数有什么影响,以及我们新的最小字数是多少。

articles = articles[articles['num_wds']>0]
articles['num_wds'].mean()
articles['num_wds'].min()
732.85873814687693
1

在这一点上,它可能有助于我们可视化文章字数的分布,以了解异常值对我们的*均值的影响程度。让我们生成另一个图来看看:

ax=articles['num_wds'].plot(kind='hist', bins=50, fontsize=14, figsize=(12,10))
ax.set_title('Article Length in Words\n', fontsize=20)
ax.set_ylabel('Frequency', fontsize=18)
ax.set_xlabel('Number of Words', fontsize=18);

Text_Hyp_Test-Updated_37_0

Python 文本分析的下一步:探索文章的多样性。我们将使用每篇文章中独特单词的数量作为开始。为了计算这个值,我们需要从文章中的单词中创建一个集合,而不是一个列表。我们可以认为集合有点像列表,但是集合会忽略重复的条目。

在官方文档中有更多关于集合和它们如何工作的信息,但是让我们先来看一个创建集合如何工作的基本例子。请注意,虽然我们从两个b条目开始,但是在我们创建的集合中只有一个条目:

set('b ac b'.split())
{'ac', 'b'}

接下来,我们要立刻做几件事:

对我们之前创建的来自tokenized列的系列进行操作,我们将从string库中调用split函数。然后我们将从我们的系列中获取集合以消除重复的单词,然后用len()测量集合的大小。

最后,我们将把结果添加为一个新列,其中包含每篇文章中唯一单词的数量。

articles['uniq_wds'] = articles['tokenized'].str.split().apply(lambda x: len(set(x)))
articles['uniq_wds'].head()
0     389
    1    1403
    2     920
    3    1037
    4     307
    Name: uniq_wds, dtype: int64

我们还想看看每篇文章的*均独立字数,以及最小和最大独立字数。

articles['uniq_wds'].mean()
articles['uniq_wds'].min()
articles['uniq_wds'].max()
336.49826282874648
1
4692

当我们将其绘制成图表时,我们可以看到,虽然唯一单词的分布仍然是倾斜的,但它看起来比我们之前生成的基于总字数的分布更像正态(高斯)分布。

ax=articles['uniq_wds'].plot(kind='hist', bins=50, fontsize=14, figsize=(12,10))
ax.set_title('Unique Words Per Article\n', fontsize=20)
ax.set_ylabel('Frequency', fontsize=18)
ax.set_xlabel('Number of Unique Words', fontsize=18);

Text_Hyp_Test-Updated_48_0

让我们来看看这两种衡量文章长度的方法在不同的出版物上有什么不同。

为此,我们将使用 pandas 的groupby功能。关于这个强大函数的完整文档可以在这里找到,但是为了我们的目的,我们只需要知道它允许我们aggregate,或者以不同的方式,通过另一列的值合计不同的指标。

在这种情况下,该列是publication。第一个图通过在len上聚合,仅使用每个组中的对象数量。我们可以在下面的代码中使用除了title之外的任何其他列。

art_grps = articles.groupby('publication')

ax=art_grps['title'].aggregate(len).plot(kind='bar', fontsize=14, figsize=(12,10))
ax.set_title('Articles per Publication (repeated)\n', fontsize=20)
ax.set_ylabel('Number of Articles', fontsize=18)
ax.set_xlabel('Publication', fontsize=18);

Text_Hyp_Test-Updated_52_0

现在,我们将分别合计*均单词数和唯一单词数。

ax=art_grps['num_wds'].aggregate(np.mean).plot(kind='bar', fontsize=14, figsize=(12,10))
ax.set_title('Mean Number of Words per Article\n', fontsize=20)
ax.set_ylabel('Mean Number of Words', fontsize=18)
ax.set_xlabel('Publication', fontsize=18);

Text_Hyp_Test-Updated_54_0

ax=art_grps['uniq_wds'].aggregate(np.mean).plot(kind='bar', fontsize=14, figsize=(12,10))
ax.set_title('Mean Number of Unique Words per Article\n', fontsize=20)
ax.set_ylabel('Mean Number of Unique Words', fontsize=18)
ax.set_xlabel('Publication', fontsize=18);

Text_Hyp_Test-Updated_55_0

最后,让我们看看整个语料库中最常见的单词。

我们将使用 Python Counter,它是一种特殊的字典,为每个键值假定整数类型。这里,我们使用文章的标记化版本遍历所有文章。

wd_counts = Counter()
for i, row in articles.iterrows():
    wd_counts.update(row['tokenized'].split())

然而,当我们统计最常见的单词时,我们不想把所有的单词都包括在内。在英语书面语中有许多非常常见的单词,在任何分析中它们都可能成为最常见的单词。对它们进行计数不会告诉我们关于文章内容的任何信息。在自然语言处理和文本处理中,这些词被称为“停用词”常见的英语停用词列表包括“and”、“or”和“The”等词

还记得我们在这个项目开始的时候从nltk.corpus中导入了模块stopwords,所以现在让我们来看看这个预先做好的stopwords列表中包含了哪些单词:

wd_counts = Counter()
for i, row in articles.iterrows():
    wd_counts.update(row['tokenized'].split())
['i',
     'me',
     'my',
     'myself',
     'we',
     'our',
     'ours',
     'ourselves',
     'you',
     "you're",
     "you've",
     "you'll",
     "you'd",
     'your',
     'yours',
     'yourself',
     'yourselves',
     'he',
     'him',
     'his',
     'himself',
     'she',
     "she's",
     'her',
     'hers',
     'herself',
     'it',
     "it's",
     'its',
     'itself',
     'they',
     'them',
     'their',
     'theirs',
     'themselves',
     'what',
     'which',
     'who',
     'whom',
     'this',
     'that',
     "that'll",
     'these',
     'those',
     'am',
     'is',
     'are',
     'was',
     'were',
     'be',
     'been',
     'being',
     'have',
     'has',
     'had',
     'having',
     'do',
     'does',
     'did',
     'doing',
     'a',
     'an',
     'the',
     'and',
     'but',
     'if',
     'or',
     'because',
     'as',
     'until',
     'while',
     'of',
     'at',
     'by',
     'for',
     'with',
     'about',
     'against',
     'between',
     'into',
     'through',
     'during',
     'before',
     'after',
     'above',
     'below',
     'to',
     'from',
     'up',
     'down',
     'in',
     'out',
     'on',
     'off',
     'over',
     'under',
     'again',
     'further',
     'then',
     'once',
     'here',
     'there',
     'when',
     'where',
     'why',
     'how',
     'all',
     'any',
     'both',
     'each',
     'few',
     'more',
     'most',
     'other',
     'some',
     'such',
     'no',
     'nor',
     'not',
     'only',
     'own',
     'same',
     'so',
     'than',
     'too',
     'very',
     's',
     't',
     'can',
     'will',
     'just',
     'don',
     "don't",
     'should',
     "should've",
     'now',
     'd',
     'll',
     'm',
     'o',
     're',
     've',
     'y',
     'ain',
     'aren',
     "aren't",
     'couldn',
     "couldn't",
     'didn',
     "didn't",
     'doesn',
     "doesn't",
     'hadn',
     "hadn't",
     'hasn',
     "hasn't",
     'haven',
     "haven't",
     'isn',
     "isn't",
     'ma',
     'mightn',
     "mightn't",
     'mustn',
     "mustn't",
     'needn',
     "needn't",
     'shan',
     "shan't",
     'shouldn',
     "shouldn't",
     'wasn',
     "wasn't",
     'weren',
     "weren't",
     'won',
     "won't",
     'wouldn',
     "wouldn't"]

正如我们所看到的,这是一个相当长的列表,但这些单词中没有一个能真正告诉我们一篇文章的意思。让我们使用这个列表来删除Counter中的停用词。

for sw in stopwords.words('english'):
    del wd_counts[sw]

为了进一步筛选出有用的信息,Counter有一个方便的most_common方法,我们可以用它来查看找到的最常用的单词。使用这个函数,我们可以指定想要看到的结果的数量。在这里,我们将要求它列出前 20 个最常用的单词。

wd_counts.most_common(20)
[('said', 571476),
 ('trump', 359436),
 ('would', 263184),
 ('one', 260552),
 ('people', 246748),
 ('new', 205187),
 ('also', 181491),
 ('like', 178532),
 ('president', 161541),
 ('time', 144047),
 ('could', 143626),
 ('first', 132971),
 ('years', 131219),
 ('two', 126745),
 ('even', 124510),
 ('says', 123381),
 ('state', 118926),
 ('many', 116965),
 ('u', 116602),
 ('last', 115748)]

上面,我们可以看到一些相当容易预测的单词,但也有点令人惊讶:单词 u 显然是最常见的。这可能看起来很奇怪,但它来自于这样一个事实,即像“美国”和“联合国”这样的缩写词在这些文章中频繁使用。

这有点奇怪,但请记住,目前我们只是在探索数据。我们想要测试的实际假设是,气候变化报道可能与媒体的某些方面相关,如其所有权或政治倾向。在我们的语料库中, u 作为一个单词的存在根本不可能影响这个分析,所以我们可以让它保持原样。

我们还可以在其他领域对这个数据集进行更多的清理和提炼,但这可能是不必要的。相反,让我们进入下一步:测试我们最初的假设是否正确。

文本分析:检验我们的假设

我们如何检验我们的假设?首先,我们必须确定哪些文章在谈论气候变化,然后我们必须比较不同类型文章的覆盖范围。

我们如何判断一篇文章是否在谈论气候变化?有几种方法可以做到这一点。我们可以使用高级文本分析技术来识别概念,比如聚类或主题建模。但是为了本文的目的,让我们保持简单:让我们只识别可能与主题相关的关键字,并在文章中搜索它们。只要头脑风暴一些感兴趣的单词和短语就可以了。

当我们列出这些短语时,我们必须小心避免诸如“环境”或“可持续发展”等含糊不清的词语。这些可能与环保主义有关,但也可能与政治环境或商业可持续性有关。甚至“气候”也可能不是一个有意义的关键词,除非我们能确定它与“变化”密切相关。

我们需要做的是创建一个函数来确定一篇文章是否包含我们感兴趣的单词。为此,我们将使用正则表达式。如果你需要复习的话,这篇博客文章中会更详细地介绍 Python 中的正则表达式。除了这个正则表达式之外,我们还将搜索在cc_wds参数中定义的其他几个短语的精确匹配。

在寻找有关气候变化的信息时,我们必须小心一点。我们不能用“改变”这个词,因为那样会排除像“改变”这样的相关词。

所以我们要这样过滤它:我们希望字符串chang后跟 1 到 5 个单词内的字符串climate(在正则表达式中,\w+匹配一个或多个单词字符,\W+匹配一个或多个非单词字符)。

我们可以用| is 来表示一个逻辑或者,所以我们也可以在 1 到 5 个单词内匹配字符串climate后跟字符串chang。1 到 5 个单词的部分是正则表达式的一部分,看起来像这样:(?:\w+\W+){1,5}?

总之,搜索这两个字符串应该有助于我们识别任何提到气候变化、气候变化等的文章。

def find_cc_wds(content, cc_wds=['climate change','global warming', 'extreme weather', 'greenhouse gas'
                                 'clean energy', 'clean tech', 'renewable energy']
):
    found = False
    for w in cc_wds:
        if w in content:
            found = True
            break

    if not found:
        disj = re.compile(r'(chang\w+\W+(?:\w+\W+){1,5}?climate) | (climate\W+(?:\w+\W+){1,5}?chang)')
        if disj.match(content):
            found = True
    return found

下面我们来仔细看看这个函数的各个部分是如何工作的:

disj = re.compile(r'(chang\w+\W+(?:\w+\W+){1,5}?climate)|(climate\W+(?:\w+\W+){1,5}?chang)')
disj.match('climate is changing')
<_sre.SRE_Match object; span=(0, 16), match='climate is chang'>
disj.match('change in extreme  climate')
<_sre.SRE_Match object; span=(0, 26), match='change in extreme  climate'>
disj.match('nothing changing here except the weather')

正如我们所看到的,这正如预期的那样起作用——它与气候变化的真实参考相匹配,而不是被其他上下文中使用的术语“变化”所抛弃。

现在,让我们使用我们的函数创建一个新的布尔字段,指示我们是否找到了相关的单词,然后查看在我们的数据集的前五篇文章中是否有任何关于气候变化的内容:

articles['cc_wds'] = articles['tokenized'].apply(find_cc_wds)
articles['cc_wds'].head()
0    False
1    False
2    False
3    False
4    False
Name: cc_wds, dtype: bool

我们数据集中的前五篇文章没有提到气候变化,但我们知道我们的功能正在按照我们之前测试的意图工作,所以现在我们可以开始对新闻报道进行一些分析。

回到我们比较不同来源的气候变化主题的最初目标,我们可能会考虑统计每个来源发表的气候相关文章的数量,并比较不同来源。但是,当我们这样做时,我们需要考虑文章总数的差异。来自一个渠道的大量气候相关文章可能仅仅是因为总体上发表了大量的文章。

我们需要做的是统计气候相关文章的相对比例。我们可以对布尔字段(如cc_wds)使用sum函数来计算真值的数量,然后除以发表的文章总数来得到我们的比例。

让我们先来看看所有来源的总比例,给我们自己一个比较每个渠道的基线:

articles['cc_wds'].sum() / len(articles)
0.030826893516666315

我们看到气候报道占所有文章的比例是 3.1%,这是相当低的,但从统计学的角度来看没有问题。

接下来我们要计算每组的相对比例。让我们通过查看每个出版物来源的比例来说明这是如何工作的。我们将再次使用 groupby 对象和sum,但是这一次我们想要每组的文章数,这是从count函数中获得的:

art_grps['cc_wds'].sum()
publication
    Atlantic               366.0
    Breitbart              471.0
    Business Insider       107.0
    Buzzfeed News          128.0
    CNN                    274.0
    Fox News                58.0
    Guardian               417.0
    NPR                    383.0
    National Review        245.0
    New York Post          124.0
    New York Times         339.0
    Reuters                573.0
    Talking Points Memo     76.0
    Vox                    394.0
    Washington Post        437.0
    Name: cc_wds, dtype: float64
art_grps.count()
身份证明(identification) 标题 作者 日期 全球资源定位器(Uniform Resource Locator) 内容 符号化 S7-1200 可编程控制器 unique _ WDS 函数 cc_wds
出版
--- --- --- --- --- --- --- --- --- --- --- --- ---
大西洋的 Seven thousand one hundred and seventy-eight Seven thousand one hundred and seventy-eight Six thousand one hundred and ninety-eight Seven thousand one hundred and seventy-eight Seven thousand one hundred and seventy-eight Seven thousand one hundred and seventy-eight Zero Seven thousand one hundred and seventy-eight Seven thousand one hundred and seventy-eight Seven thousand one hundred and seventy-eight Seven thousand one hundred and seventy-eight Seven thousand one hundred and seventy-eight
布莱巴特 Twenty-three thousand seven hundred and eighty-one Twenty-three thousand seven hundred and eighty-one Twenty-three thousand seven hundred and eighty-one Twenty-three thousand seven hundred and eighty-one Twenty-three thousand seven hundred and eighty-one Twenty-three thousand seven hundred and eighty-one Zero Twenty-three thousand seven hundred and eighty-one Twenty-three thousand seven hundred and eighty-one Twenty-three thousand seven hundred and eighty-one Twenty-three thousand seven hundred and eighty-one Twenty-three thousand seven hundred and eighty-one
商业内幕 Six thousand six hundred and ninety-five Six thousand six hundred and ninety-five Four thousand nine hundred and twenty-six Six thousand six hundred and ninety-five Six thousand six hundred and ninety-five Six thousand six hundred and ninety-five Zero Six thousand six hundred and ninety-five Six thousand six hundred and ninety-five Six thousand six hundred and ninety-five Six thousand six hundred and ninety-five Six thousand six hundred and ninety-five
Buzzfeed 新闻 Four thousand eight hundred and thirty-five Four thousand eight hundred and thirty-five Four thousand eight hundred and thirty-four Four thousand eight hundred and thirty-five Four thousand eight hundred and thirty-five Four thousand eight hundred and thirty-five Four thousand eight hundred and thirty-five Four thousand eight hundred and thirty-five Four thousand eight hundred and thirty-five Four thousand eight hundred and thirty-five Four thousand eight hundred and thirty-five Four thousand eight hundred and thirty-five
美国有线新闻网;卷积神经网络 Eleven thousand four hundred and eighty-five Eleven thousand four hundred and eighty-five Seven thousand and twenty-four Eleven thousand four hundred and eighty-five Eleven thousand four hundred and eighty-five Eleven thousand four hundred and eighty-five Zero Eleven thousand four hundred and eighty-five Eleven thousand four hundred and eighty-five Eleven thousand four hundred and eighty-five Eleven thousand four hundred and eighty-five Eleven thousand four hundred and eighty-five
福克斯新闻频道 Four thousand three hundred and fifty-one Four thousand three hundred and fifty-one One thousand one hundred and seventeen Four thousand three hundred and forty-nine Four thousand three hundred and forty-nine Four thousand three hundred and forty-nine Four thousand three hundred and forty-eight Four thousand three hundred and fifty-one Four thousand three hundred and fifty-one Four thousand three hundred and fifty-one Four thousand three hundred and fifty-one Four thousand three hundred and fifty-one
监护人 Eight thousand six hundred and eighty Eight thousand six hundred and eighty Seven thousand two hundred and forty-nine Eight thousand six hundred and forty Eight thousand six hundred and forty Eight thousand six hundred and forty Eight thousand six hundred and eighty Eight thousand six hundred and eighty Eight thousand six hundred and eighty Eight thousand six hundred and eighty Eight thousand six hundred and eighty Eight thousand six hundred and eighty
噪声功率比(noise power ratio) Eleven thousand nine hundred and ninety-two Eleven thousand nine hundred and ninety-two Eleven thousand six hundred and fifty-four Eleven thousand nine hundred and ninety-two Eleven thousand nine hundred and ninety-two Eleven thousand nine hundred and ninety-two Eleven thousand nine hundred and ninety-two Eleven thousand nine hundred and ninety-two Eleven thousand nine hundred and ninety-two Eleven thousand nine hundred and ninety-two Eleven thousand nine hundred and ninety-two Eleven thousand nine hundred and ninety-two
国家评论 Six thousand one hundred and ninety-five Six thousand one hundred and ninety-five Six thousand one hundred and ninety-five Six thousand one hundred and ninety-five Six thousand one hundred and ninety-five Six thousand one hundred and ninety-five Six thousand one hundred and ninety-five Six thousand one hundred and ninety-five Six thousand one hundred and ninety-five Six thousand one hundred and ninety-five Six thousand one hundred and ninety-five Six thousand one hundred and ninety-five
纽约邮报 Seventeen thousand four hundred and ninety-three Seventeen thousand four hundred and ninety-three Seventeen thousand four hundred and eighty-five Seventeen thousand four hundred and ninety-three Seventeen thousand four hundred and ninety-three Seventeen thousand four hundred and ninety-three Seventeen thousand four hundred and ninety-three Seventeen thousand four hundred and ninety-three Seventeen thousand four hundred and ninety-three Seventeen thousand four hundred and ninety-three Seventeen thousand four hundred and ninety-three Seventeen thousand four hundred and ninety-three
纽约时报 Seven thousand eight hundred and three Seven thousand eight hundred and three Seven thousand seven hundred and sixty-seven Seven thousand eight hundred and three Seven thousand eight hundred and three Seven thousand eight hundred and three Zero Seven thousand eight hundred and three Seven thousand eight hundred and three Seven thousand eight hundred and three Seven thousand eight hundred and three Seven thousand eight hundred and three
路透社 Ten thousand seven hundred and ten Ten thousand seven hundred and nine Ten thousand seven hundred and ten Ten thousand seven hundred and ten Ten thousand seven hundred and ten Ten thousand seven hundred and ten Ten thousand seven hundred and ten Ten thousand seven hundred and ten Ten thousand seven hundred and ten Ten thousand seven hundred and ten Ten thousand seven hundred and ten Ten thousand seven hundred and ten
谈话要点备忘录 Five thousand two hundred and fourteen Five thousand two hundred and thirteen One thousand six hundred and seventy-six Two thousand six hundred and fifteen Two thousand six hundred and fifteen Two thousand six hundred and fifteen Five thousand two hundred and fourteen Five thousand two hundred and fourteen Five thousand two hundred and fourteen Five thousand two hundred and fourteen Five thousand two hundred and fourteen Five thousand two hundred and fourteen
声音 Four thousand nine hundred and forty-seven Four thousand nine hundred and forty-seven Four thousand nine hundred and forty-seven Four thousand nine hundred and forty-seven Four thousand nine hundred and forty-seven Four thousand nine hundred and forty-seven Four thousand nine hundred and forty-seven Four thousand nine hundred and forty-seven Four thousand nine hundred and forty-seven Four thousand nine hundred and forty-seven Four thousand nine hundred and forty-seven Four thousand nine hundred and forty-seven
华盛顿邮报 Eleven thousand one hundred and fourteen Eleven thousand one hundred and fourteen Eleven thousand and seventy-seven Eleven thousand one hundred and fourteen Eleven thousand one hundred and fourteen Eleven thousand one hundred and fourteen Eleven thousand one hundred and fourteen Eleven thousand one hundred and fourteen Eleven thousand one hundred and fourteen Eleven thousand one hundred and fourteen Eleven thousand one hundred and fourteen Eleven thousand one hundred and fourteen

现在,让我们把它分成几个部分,并对列表进行排序,这样我们就可以快速地一目了然哪些媒体对气候变化的报道最多:

proportions = art_grps['cc_wds'].sum() / art_grps['cc_wds'].count()
proportions.sort_values(ascending=True)
proportions
publication
    New York Post          0.007089
    Fox News               0.013330
    Talking Points Memo    0.014576
    Business Insider       0.015982
    Breitbart              0.019806
    CNN                    0.023857
    Buzzfeed News          0.026474
    NPR                    0.031938
    Washington Post        0.039320
    National Review        0.039548
    New York Times         0.043445
    Guardian               0.048041
    Atlantic               0.050989
    Reuters                0.053501
    Vox                    0.079644
    Name: cc_wds, dtype: float64

比例从《纽约邮报》的 0.7%到 Vox 的 8%不等。让我们绘制这个图,先按出版物名称排序,然后再按值排序。

ax=proportions.plot(kind='bar', fontsize=14, figsize=(12,10))
ax.set_title('Mean Proportion of Climate Change Related Articles per Publication\n', fontsize=20)
ax.set_ylabel('Mean Proportion', fontsize=18)
ax.set_xlabel('Publication', fontsize=18);

Text_Hyp_Test-Updated_81_0

ax=proportions.sort_values(ascending=False).plot(kind='bar', fontsize=14, figsize=(12,10))
ax.set_title('Mean Proportion of Climate Change Related Articles per Publication (Sorted)\n', fontsize=20)
ax.set_ylabel('Mean Proportion', fontsize=18)
ax.set_xlabel('Publication', fontsize=18);

Text_Hyp_Test-Updated_82_0

我们可以在这里做各种其他的探索性数据分析,但是让我们暂时把它放在一边,继续我们的目标,测试关于我们的语料库的假设。

检验假设

在这篇文章中,我们不会对假设检验及其微妙之处做一个完整的概述;关于 Python 中概率的概述,请访问这篇文章,关于统计假设检验的细节,维基百科是个不错的地方。

我们将在这里举例说明假设检验的一种形式。

回想一下,我们一开始非正式地假设,出版物的特征可能与它们发表的气候相关文章的优势相关。这些特征包括政治倾向和所有权。例如,我们的与政治倾向相关的零假设非正式地说,当比较具有不同政治倾向的文章时,在气候变化提及方面没有差异。让我们更正式一点。

如果我们观察出版物的左与右的政治倾向,并将左倾的出版物组称为“左”,将右倾的出版物组称为“右”,我们的零假设是左的人口气候变化文章比例等于右的人口气候变化文章比例。我们的另一个假设是两个人口比例不相等。我们可以用其他人群分组和陈述类似的假设来代替其他政治倾向比较或其他出版物特征。

让我们从政治倾向开始。你可以重温这篇文章的顶部,提醒自己我们是如何收集关于媒体政治倾向的信息的。下面的代码使用一个字典,根据我们收集的信息为每个出版物名称分配leftrightcenter值。

#liberal, conservative, and center
bias_assigns = {'Atlantic': 'left', 'Breitbart': 'right', 'Business Insider': 'left', 'Buzzfeed News': 'left', 'CNN': 'left', 'Fox News': 'right',
                'Guardian': 'left', 'National Review': 'right', 'New York Post': 'right', 'New York Times': 'left',
                'NPR': 'left', 'Reuters': 'center', 'Talking Points Memo': 'left', 'Washington Post': 'left', 'Vox': 'left'}
articles['bias'] = articles['publication'].apply(lambda x: bias_assigns[x])
articles.head()
身份证明(identification) 标题 出版 作者 日期 全球资源定位器(Uniform Resource Locator) 内容 标记器 S7-1200 可编程控制器 unique _ WDS 函数 cc_wds 偏见
Zero Seventeen thousand two hundred and eighty-three 众议院共和党人担心赢得他们的头… 纽约时报 卡尔·赫尔斯 2016-12-31 Two thousand and sixteen Twelve 圆盘烤饼 华盛顿——国会共和党人已经… 华盛顿国会共和党人有一个新的… Eight hundred and seventy-six Three hundred and eighty-nine 错误的 左边的
one Seventeen thousand two hundred and eighty-four 警官和居民之间的裂痕就像杀戮… 纽约时报 本杰明·穆勒和艾尔·贝克 2017-06-19 Two thousand and seventeen Six 圆盘烤饼 子弹壳数完之后,血… 子弹壳数完之后,血… Four thousand seven hundred and forty-three One thousand four hundred and three 错误的 左边的
Two Seventeen thousand two hundred and eighty-five 泰勒斯黄,'小鹿斑比'艺术家受阻于种族… 纽约时报 玛格丽特·福克斯 2017-01-06 Two thousand and seventeen One 圆盘烤饼 当华特·迪士尼的《斑比》在 1942 年上映时,克里… 当沃尔特·迪斯尼的《斑比》在 1942 年上映时,评论家们… Two thousand three hundred and fifty Nine hundred and twenty 错误的 左边的
three Seventeen thousand two hundred and eighty-six 在 2016 年的死亡人数中,流行音乐死亡人数最多… 纽约时报 威廉·麦克唐纳 2017-04-10 Two thousand and seventeen Four 圆盘烤饼 死亡可能是最好的均衡器,但它不是… 死亡可能是最好的均衡器,但它不是… Two thousand one hundred and four One thousand and thirty-seven 错误的 左边的
four Seventeen thousand two hundred and eighty-seven 金正恩称朝鲜正准备进行核试验 纽约时报 让他转到匈牙利 2017-01-02 Two thousand and seventeen One 圆盘烤饼 韩国首尔——朝鲜领导人…… 韩国首尔朝鲜领导人金说… Six hundred and ninety Three hundred and seven 错误的 左边的

我们再次使用groupby()来找出每个政治团体中气候变化文章的比例。

bias_groups = articles.groupby('bias')
bias_proportions = bias_groups['cc_wds'].sum() / bias_groups['cc_wds'].count()

让我们看看每组有多少篇文章,并用图表表示出来:

bias_groups['cc_wds'].count()
bias
    center    10710
    left      79943
    right     51820
    Name: cc_wds, dtype: int64
ax=bias_proportions.plot(kind='bar', fontsize=14, figsize=(12,10))
ax.set_title('Proportion of climate change articles by Political Bias\n', fontsize=20)
ax.set_xlabel('Bias', fontsize=18)
ax.set_ylabel('Proportion', fontsize=18);

Text_Hyp_Test-Updated_89_0

从上面的图表来看,很明显,不同的政治倾向群体的气候变化相关文章的比例是不同的,但是让我们正式检验一下我们的假设。为此,对于给定的一对文章分组,我们提出零假设,即假设气候相关文章的总体比例没有差异。让我们也为我们的测试建立一个 95%的置信度。

一旦我们收集了统计数据,我们就可以使用 P 值或置信区间来确定我们的结果是否具有统计学意义。这里我们将使用置信区间,因为我们感兴趣的是差值的范围可能反映人口比例的差异。我们假设检验中感兴趣的统计数据是两个样本中气候变化文章比例的差异。回想一下,置信区间和显著性检验之间有密切的关系。具体来说,如果统计值在 0.05 水*显著不同于零,则 95%置信区间将不包含 0。

换句话说,如果零在我们计算的置信区间内,那么我们不会拒绝零假设。但如果不是,我们可以说相关文章比例的差异在统计学上是显著的。我想借此机会指出置信区间中的一个常见误解:95%的区间给出了一个区域,如果我们重新进行抽样,那么在 95%的时间里,区间将包含真实的(总体)比例差异。不是说 95%的样本都在区间内。

为了计算置信区间,我们需要一个点估计和一个误差幅度;后者由临界值乘以标准误差组成。对于比例差异,我们对差异的点估计是 p[1]——p[2],其中 p [1] 和 p [2] 是我们的两个样本比例。对于 95%的置信区间,1.96 是我们的临界值。接下来,我们的标准误差是:

最后,置信区间为(点估计临界值 X 标准误差),或者:

让我们用一些辅助函数将我们的数字代入这些公式。

def standard_err(p1, n1, p2, n2):
    return np.sqrt((p1* (1-p1) / n1) + (p2 * (1-p2) / n2))
def ci_range(diff, std_err, cv=1.96):
    return (diff - cv * std_err, diff + cv * std_err)

最后,calc_ci_range函数将所有东西放在一起。

def calc_ci_range(p1, n1, p2, n2):
    std_err = standard_err(p1, n1, p2, n2)
    diff = p1-p2
    return ci_range(diff, std_err)

让我们计算我们倾向组的置信区间,首先看左边和右边。

center = bias_groups.get_group('center')
left = bias_groups.get_group('left')
right = bias_groups.get_group('right')
calc_ci_range(bias_proportions['left'], len(left), bias_proportions['right'], len(right))
(0.017490570656831184, 0.02092806371626154)

观察左与右出版物的比例差异,我们的置信区间在 1.8%到 2.1%之间。这是一个相当窄的范围,相对于比例差异的总体范围而言,远远不是零。所以拒绝零假设是显而易见的。类似地,中心对左侧的范围是 1.3%到 2.1%:

calc_ci_range(bias_proportions['center'], len(center), bias_proportions['left'], len(left))
(0.012506913377622272, 0.021418820332295894)

因为将出版物归入 bias slant 有些主观,这里有另一个变体,将 Business Insider、NY Post 和 NPR 归入center

bias_assigns = {'Atlantic': 'left', 'Breitbart': 'right', 'Business Insider': 'center', 'Buzzfeed News': 'left', 'CNN': 'left', 'Fox News': 'right',
                'Guardian': 'left', 'National Review': 'right', 'New York Post': 'center', 'New York Times': 'left',
                'NPR': 'center', 'Reuters': 'center', 'Talking Points Memo': 'left', 'Washington Post': 'left', 'Vox': 'left'}
articles['bias'] = articles['publication'].apply(lambda x: bias_assigns[x])
bias_groups = articles.groupby('bias')
bias_proportions = bias_groups['cc_wds'].sum() / bias_groups['cc_wds'].count()
ax=bias_proportions.plot(kind='bar', fontsize=14, figsize=(12,10))
ax.set_title('Proportion of climate change articles by Political Bias\n', fontsize=20)
ax.set_xlabel('Bias', fontsize=18)
ax.set_ylabel('Proportion', fontsize=18);

Text_Hyp_Test-Updated_102_0

center = bias_groups.get_group('center')
left = bias_groups.get_group('left')
right = bias_groups.get_group('right')
calc_ci_range(bias_proportions['left'], len(left), bias_proportions['right'], len(right))
(0.014934299280171939, 0.019341820093654233)
calc_ci_range(bias_proportions['left'], len(left), bias_proportions['center'], len(center))
(0.012270972859506818, 0.016471711767773518)
calc_ci_range(bias_proportions['center'], len(center), bias_proportions['right'], len(right))
(0.0006482405387969359, 0.0048851942077489004)

接下来,我们可以使用相同的方法来查看发布所有权。我们将我们的人口分为四组,有限责任公司,公司,非营利组织和私人。

own_assigns = {'Atlantic': 'non-profit', 'Breitbart': 'LLC', 'Business Insider': 'corp', 'Buzzfeed News': 'private',
               'CNN': 'corp', 'Fox News': 'corp',
                'Guardian': 'LLC', 'National Review': 'non-profit', 'New York Post': 'corp', 'New York Times': 'corp',
                'NPR': 'non-profit', 'Reuters': 'corp', 'Talking Points Memo': 'private', 'Washington Post': 'LLC', 'Vox': 'private'}
articles['ownership'] = articles['publication'].apply(lambda x: own_assigns[x])
owner_groups = articles.groupby('ownership')
owner_proportions = owner_groups['cc_wds'].sum() / owner_groups['cc_wds'].count()

现在让我们绘制这些数据,看看不同类型的公司是否以不同的比例报道气候变化。

ax=owner_proportions.plot(kind='bar', fontsize=14, figsize=(12,10))
ax.set_title('Proportion of climate change articles by Ownership Group\n', fontsize=20)
ax.set_xlabel('Ownership', fontsize=18)
ax.set_ylabel('Proportion', fontsize=18);

Text_Hyp_Test-Updated_109_0

或许不出所料,看起来私营公司和非营利组织比公司和有限责任公司更关注气候变化。但是让我们更仔细地看看前两者,有限责任公司和股份有限公司之间的比例差异:

llc = owner_groups.get_group('LLC')
corp = owner_groups.get_group('corp')
non_profit = owner_groups.get_group('non-profit')
private = owner_groups.get_group('private')

calc_ci_range(owner_proportions['LLC'], len(llc), owner_proportions['corp'], len(corp))
(0.0031574852345019415, 0.0072617257208337279)

这里,置信区间是 0.3%到 0.7%,比我们之前的差异更接*于零,但仍然不包括零。我们预计非营利到有限责任区间也不包括零:

calc_ci_range(owner_proportions['non-profit'], len(non_profit), owner_proportions['LLC'], len(llc))
(0.0058992390642172241, 0.011661788182388525)

非营利组织对有限责任公司的置信区间为 0.6%至 1.2%。最后,看看私人与非营利组织,我们发现一个-0.3%到 0.5%的置信区间:

calc_ci_range(owner_proportions['private'], len(private), owner_proportions['non-profit'], len(non_profit))
(-0.003248922257497777, 0.004627808917174475)

因此,在这种情况下,我们可以得出结论,与我们比较过的其他人群不同,这两个人群中与气候变化相关的文章比例没有显著差异。

概要:测试假设的文本分析

在本文中,我们对大量新闻文章进行了文本分析,并测试了一些关于内容差异的假设。具体来说,使用 95%的置信区间,我们估计了不同新闻来源群体在气候变化讨论中的差异。

我们发现了一些有趣的差异,这些差异也具有统计学意义,包括右倾新闻来源对气候变化的报道较少,公司和有限责任公司对气候变化的报道往往少于非营利和私营机构。

然而,就使用这些语料库而言,我们还仅仅触及了冰山一角。您可以尝试用这些数据进行许多有趣的分析,所以为自己从 Kaggle 下载数据,并开始编写自己的文本分析项目!

延伸阅读:

在线新闻和社交媒体中事件报道的比较:气候变化案例。第九届国际网络和社交媒体 AAAI 会议论文集。2015.

这个教程有帮助吗?

选择你的道路,不断学习有价值的数据技能。

arrow down leftarrow right downPython Tutorials

在我们的免费教程中练习 Python 编程技能。

Data science courses

通过我们的交互式浏览器数据科学课程,投入到 Python、R、SQL 等语言的学习中。

教程:使用 spaCy 在 Python 中进行文本分类

原文:https://www.dataquest.io/blog/tutorial-text-classification-in-python-using-spacy/

April 16, 2019text-classification-python-spacy-linear-regression

文本是极其丰富的信息来源。每分钟,人们向发送数以亿计的新邮件和短信。有一个名副其实的文本数据山等待挖掘的洞察力。但是,想要从所有这些文本数据中收集意义的数据科学家面临着一个挑战:因为文本数据以非结构化的形式存在,所以很难分析和处理。

在本教程中,我们将看看如何使用有用的 Python 包spaCy ( 文档)将所有非结构化文本数据转换成对分析和自然语言处理更有用的东西。

一旦我们做到了这一点,我们将能够从文本数据中获得有意义的模式和主题。这在各种数据科学应用中非常有用:垃圾邮件过滤、支持票、社交媒体分析、上下文广告、查看客户反馈等等。

具体来说,我们将对自然语言处理(NLP)进行高级研究。然后,我们将使用spaCy完成一些清理和分析文本数据的重要基本操作。然后我们将深入到文本分类,具体是逻辑回归分类,使用一些真实世界的数据(亚马逊的 Alexa 智能家居音箱的文本评论)。

什么是自然语言处理?

自然语言处理(NLP)是机器学习的一个分支,它处理、分析,有时生成人类语音(“自然语言”)。

毫无疑问,在确定一串文本的含义方面,人类仍然比机器好得多。但是在数据科学中,我们经常会遇到数据集太大,人类无法在合理的时间内进行分析。我们还可能遇到没有人可以分析和响应一段文本输入的情况。在这些情况下,我们可以使用自然语言处理技术来帮助机器理解文本的含义(如果有必要,做出相应的响应)。

例如,自然语言处理被广泛用于情感分析,因为分析师经常试图从大量文本数据中确定整体情感,这对人类来说是耗时的。它也用于广告匹配——确定文本主体的主题,并自动分配相关的广告。它被用于聊天机器人、语音助手和其他应用程序,在这些应用程序中,机器需要理解并快速响应以自然人类语言形式出现的输入。

spaCy分析和处理文本

spaCy是一个针对 Python 的开源自然语言处理库。它是专门为生产使用而设计的,它可以帮助我们构建高效处理大量文本的应用程序。首先,让我们看看spaCy可以处理的一些基本分析任务。

安装spaCy

在继续下一步之前,我们需要安装spaCy和它的英语模型。我们可以使用以下命令行命令来实现这一点:

pip install spacy

python -m spacy download en

我们也可以在 Juypter 笔记本中使用spaCy。不过,它不是 Jupyter 默认包含的预安装库之一,所以我们需要从笔记本上运行这些命令,将spaCy安装到正确的 Anaconda 目录中。请注意,我们在每个命令前面使用了!,以让 Jupyter 笔记本知道它应该作为命令行命令来阅读。

!pip install spacy

!python -m spacy download en

对文本进行标记

标记化是将文本分成称为标记的片段,并忽略标点符号(,)等字符的过程。“’”和空格。spaCy的记号赋予器接受 unicode 文本形式的输入,并输出一系列记号对象。

让我们看一个简单的例子。假设我们有下面的文本,我们想把它标记出来:

学习数据科学的时候,不要气馁。

挑战和挫折不是失败,它们只是旅程的一部分。

有几种不同的方法可以解决这个问题。第一种叫做单词标记化,意思是把文本分解成单个的单词。对于许多语言处理应用程序来说,这是一个关键步骤,因为它们通常需要以单个单词的形式输入,而不是较长的文本字符串。

在下面的代码中,我们将导入spaCy及其英语语言模型,并告诉它我们将使用该模型进行自然语言处理。然后我们将我们的文本字符串分配给text。使用nlp(text),我们将处理spaCy中的文本,并将结果赋给一个名为my_doc的变量。

此时,我们的文本已经被标记化了,但是spaCy将标记化的文本存储为一个文档,我们希望以列表的形式查看它,所以我们将创建一个for循环来遍历我们的文档,将它在我们的文本字符串中找到的每个单词标记添加到一个名为token_list的列表中,这样我们可以更好地了解单词是如何被标记化的。

# Word tokenization
from spacy.lang.en import English

# Load English tokenizer, tagger, parser, NER and word vectors
nlp = English()

text = """When learning data science, you shouldn't get discouraged!
Challenges and setbacks aren't failures, they're just part of the journey. You've got this!"""

#  "nlp" Object is used to create documents with linguistic annotations.
my_doc = nlp(text)

# Create list of word tokens
token_list = []
for token in my_doc:
    token_list.append(token.text)
print(token_list) 
['When', 'learning', 'data', 'science', ',', 'you', 'should', "n't", 'get', 'discouraged', '!', '\n', 'Challenges', 'and', 'setbacks', 'are', "n't", 'failures', ',', 'they', "'re", 'just', 'part', 'of', 'the', 'journey', '.', 'You', "'ve", 'got', 'this', '!'] 

正如我们所见,spaCy生成了一个列表,其中包含了作为单独项目的每个令牌。请注意,它已经认识到像这样的缩写不应该是实际上代表两个不同的单词,因此它将它们分解成两个不同的标记。

首先,我们需要加载语言词典,在一个简单的例子中,我们使用 english()类加载英语词典并创建 nlp nlp 对象。“nlp”对象用于创建带有语言注释和各种 nlp 属性的文档。创建文档后,我们正在创建令牌列表。

如果我们愿意,我们也可以把文章分成句子而不是单词。这叫做句子标记化。在执行句子标记化时,标记化器会查找位于句子之间的特定字符,如句点、感叹号和换行符。对于句子标记化,我们将使用预处理管道,因为使用spaCy的句子预处理包括一个标记器、一个标记器、一个解析器和一个实体识别器,我们需要访问它们来正确识别什么是句子,什么不是。

在下面的代码中,spaCy标记文本并创建一个 Doc 对象。这个 Doc 对象使用我们的预处理管道的组件标记器、解析器和实体识别器将文本分解成组件。从这个管道中,我们可以提取任何组件,但是这里我们将使用sentencizer组件来访问句子标记。

# sentence tokenization

# Load English tokenizer, tagger, parser, NER and word vectors
nlp = English()

# Create the pipeline 'sentencizer' component
sbd = nlp.create_pipe('sentencizer')   

# Add the component to the pipeline
nlp.add_pipe(sbd)

text = """When learning data science, you shouldn't get discouraged!
Challenges and setbacks aren't failures, they're just part of the journey. You've got this!"""

#  "nlp" Object is used to create documents with linguistic annotations.
doc = nlp(text)

# create list of sentence tokens
sents_list = []
for sent in doc.sents:
    sents_list.append(sent.text)
print(sents_list) 
["When learning data science, you shouldn't get discouraged!", "\nChallenges and setbacks aren't failures, they're just part of the journey.", "You've got this!"] 

同样,spaCy已经正确地将文本解析成我们想要的格式,这次输出了在源文本中找到的句子列表。

清除文本数据:删除停用字词

我们处理的大多数文本数据都会包含很多对我们实际上没用的单词。这些词被称为停用词,在人类的言语中很有用,但对数据分析没有太大贡献。删除停用词有助于我们消除文本数据中的噪音和干扰,还可以加快分析时间(因为需要处理的单词更少)。

让我们看看默认情况下包含的停用词spaCy。我们将导入spaCy并将它的英语语言模型中的停用词赋给一个名为spacy_stopwords的变量,这样我们就可以看一看了。

#Stop words
#importing stop words from English language.
import spacy
spacy_stopwords = spacy.lang.en.stop_words.STOP_WORDS

#Printing the total number of stop words:
print('Number of stop words: %d' % len(spacy_stopwords))

#Printing first ten stop words:
print('First ten stop words: %s' % list(spacy_stopwords)[:20]) 
Number of stop words: 312
First ten stop words: ['was', 'various', 'fifty', "'s", 'used', 'once', 'because', 'himself', 'can', 'name', 'many', 'seems', 'others', 'something', 'anyhow', 'nowhere', 'serious', 'forty', 'he', 'now'] 

我们可以看到,spaCy的默认停用词列表总共包括 312 个条目,每个条目都是一个单词。我们也可以看到为什么这些词对数据分析没有用。然而,像这样的过渡词,例如,对于理解一个句子的基本意思并不是必要的。另外,像某人这样的词太模糊了,对自然语言处理任务没有多大用处。

如果我们愿意,我们也可以创建自己的自定义停用词列表。但是对于我们在本教程中的目的来说,spaCy提供的默认列表就可以了。

从数据中删除停用词

现在我们已经有了停用词列表,让我们用它从上一节中处理的文本字符串中删除停用词。我们的文本已经存储在变量text中,所以我们不需要再次定义它。

相反,我们将创建一个名为filtered_sent的空列表,然后遍历我们的doc变量,查看源文本中的每个标记化单词。spaCy包括一堆有用的令牌属性,我们将使用其中一个名为is_stop的属性来识别不在停用词表中的单词,然后将它们添加到我们的filtered_sent列表中。

from spacy.lang.en.stop_words import STOP_WORDS

#Implementation of stop words:
filtered_sent=[]

#  "nlp" Object is used to create documents with linguistic annotations.
doc = nlp(text)

# filtering stop words
for word in doc:
    if word.is_stop==False:
        filtered_sent.append(word)
print("Filtered Sentence:",filtered_sent) 
Filtered Sentence: [learning, data, science, ,, discouraged, !,
, Challenges, setbacks, failures, ,, journey, ., got, !] 

不难看出为什么停用词会有帮助。删除它们将我们的原文浓缩为几个词,让我们很好地了解句子在讨论什么:学习数据科学,以及在这个过程中阻止挑战和挫折。

词汇规范化

词典规范化是文本数据清理过程中的另一个步骤。总的来说,归一化将高维特征转换成适合任何机器学习模型的低维特征。出于我们的目的,我们只看一下词汇化,这是一种处理单词的方法,它将单词简化为它们的根。

词汇化

词汇化是一种处理这样一个事实的方式,即像连接连接连接连接等等。并不完全相同,都有相同的本质意义:连接。拼写上的差异在口语中有语法功能,但对于机器处理来说,这些差异可能会令人困惑,所以我们需要一种方法来将单词 connect形式的所有单词都变成单词 connect 本身。

一种方法叫做词干。词干包括简单地删除容易识别的前缀和后缀,以产生一个词的最简单版本。例如,连接将去掉 -ion 后缀,并正确地简化为连接。这种简单的词干处理通常是所需要的,但是词汇化(lemmatization)——实际上是查看字典中描述的单词及其词根(称为lemma)——更精确(只要单词存在于字典中)。

由于spaCy包含了一种将单词分解成其词条的内置方式,我们可以简单地使用它来进行词条化。在下面这个非常简单的例子中,我们将使用.lemma_来产生我们正在分析的每个单词的词条。

# Implementing lemmatization
lem = nlp("run runs running runner")
# finding lemma for each word
for word in lem:
    print(word.text,word.lemma_) 
run run
runs run
running run
runner runner 

词性标注

一个单词的词性定义了它在句子中的功能。例如,一个名词表示一个物体。形容词描述一个物体。动词描述动作。在句子的上下文中识别和标注每个单词的词性称为词性标注,或称词性标注。

让我们用spaCy来尝试一些词性标注吧!我们需要导入它的en_core_web_sm模型,因为它包含进行分析所需的字典和语法信息。然后我们需要做的就是用.load()加载这个模型,并遍历我们新的docs变量,使用.pos_识别每个单词的词性。

(注意u"All is well that ends well."中的 u 表示该字符串是 Unicode 字符串。)

# POS tagging

# importing the model en_core_web_sm of English for vocabluary, syntax & entities
import en_core_web_sm

# load en_core_web_sm of English for vocabluary, syntax & entities
nlp = en_core_web_sm.load()

#  "nlp" Objectis used to create documents with linguistic annotations.
docs = nlp(u"All is well that ends well.")

for word in docs:
    print(word.text,word.pos_) 
All DET
is VERB
well ADV
that DET
ends VERB
well ADV
. PUNCT 

万岁!已经正确识别了这个句子中每个单词的词性。能够识别词性在各种 NLP 相关的上下文中是有用的,因为它有助于更准确地理解输入句子和更准确地构造输出响应。

实体检测

实体检测,也称为实体识别,是一种更高级的语言处理形式,可以识别输入文本字符串中的重要元素,如地点、人员、组织和语言。这对于从文本中快速提取信息非常有帮助,因为你可以快速挑选出重要的主题或识别文本的关键部分。

让我们使用华盛顿邮报最*的文章中的几段来尝试一些实体检测。我们将使用.label为文本中检测到的每个实体获取一个标签,然后我们将使用spaCydisplaCy可视化工具以更直观的格式查看这些实体。

#for visualization of Entity detection importing displacy from spacy:

from spacy import displacy

nytimes= nlp(u"""New York City on Tuesday declared a public health emergency and ordered mandatory measles vaccinations amid an outbreak, becoming the latest national flash point over refusals to inoculate against dangerous diseases.

At least 285 people have contracted measles in the city since September, mostly in Brooklyn’s Williamsburg neighborhood. The order covers four Zip codes there, Mayor Bill de Blasio (D) said Tuesday.

The mandate orders all unvaccinated people in the area, including a concentration of Orthodox Jews, to receive inoculations, including for children as young as 6 months old. Anyone who resists could be fined up to $1,000.""")

entities=[(i, i.label_, i.label) for i in nytimes.ents]
entities 
[(New York City, 'GPE', 384),
 (Tuesday, 'DATE', 391),
 (At least 285, 'CARDINAL', 397),
 (September, 'DATE', 391),
 (Brooklyn, 'GPE', 384),
 (Williamsburg, 'GPE', 384),
 (four, 'CARDINAL', 397),
 (Bill de Blasio, 'PERSON', 380),
 (Tuesday, 'DATE', 391),
 (Orthodox Jews, 'NORP', 381),
 (6 months old, 'DATE', 391),
 (up to $1,000, 'MONEY', 394)] 

使用这种技术,我们可以识别文本中的各种实体。spaCy文档为提供了支持的实体类型的完整列表,从上面的简短示例中我们可以看到,它能够识别各种不同的实体类型,包括特定位置(GPE)、日期相关的单词(DATE)、重要数字(CARDINAL)、特定个人(PERSON)等。

使用displaCy,我们还可以可视化我们的输入文本,用颜色突出显示每个被识别的实体并贴上标签。我们将使用style = "ent"告诉displaCy我们想要在这里可视化实体。

displacy.render(nytimes, style = "ent",jupyter = True) 

New York City GPE on Tuesday DATE declared a public health emergency and ordered mandatory measles vaccinations amid an outbreak, becoming the latest national flash point over refusals to inoculate against dangerous diseases. At least 285 CARDINAL people have contracted measles in the city since September DATE , mostly in Brooklyn GPE ’s Williamsburg GPE neighborhood. The order covers four CARDINAL Zip codes there, Mayor Bill de Blasio PERSON (D) said Tuesday DATE .The mandate orders all unvaccinated people in the area, including a concentration of Orthodox Jews NORP , to receive inoculations, including for children as young as 6 months old DATE . Anyone who resists could be fined up to $1,000 MONEY .

依存句法分析

依存解析是一种语言处理技术,它允许我们通过分析句子的结构来确定各个单词之间的关系,从而更好地确定句子的意思。

例如,考虑句子“比尔扔球”我们有两个名词(比尔和球)和一个动词(投掷)。但是不能单个看这几个字,不然最后可能会以为球在扔比尔!为了正确理解句子,我们需要看词序和句子结构,而不仅仅是单词及其词性。

这样做相当复杂,但谢天谢地spaCy会替我们处理这项工作!下面,我们再给spaCy一个从新闻标题中拉出来的短句。然后我们将使用另一个名为noun_chunksspaCy,它将输入分解为名词和描述它们的单词,并遍历我们的源文本中的每个块,识别单词、其词根、其依存标识以及它属于哪个块。

docp = nlp (" In pursuit of a wall, President Trump ran into one.")

for chunk in docp.noun_chunks:
   print(chunk.text, chunk.root.text, chunk.root.dep_,
          chunk.root.head.text) 
pursuit pursuit pobj In
a wall wall pobj of
President Trump Trump nsubj ran 

这个输出可能有点难以理解,但是因为我们已经导入了displaCy可视化工具,我们可以用它来查看使用style = "dep"的依赖关系图,这样更容易理解:

displacy.render(docp, style="dep", jupyter= True) 

text-expressions-python-chart

Click to expand

当然,我们也可以查看spaCy的文档中关于依存解析的内容,以便更好地理解根据每个句子的解释,可能会应用到我们文本中的不同标签。

词向量表示法

当我们单独看单词时,机器很难理解人类能够立即理解的联系。例如,引擎汽车看起来似乎有明显的联系(汽车使用引擎运行),但这种联系对计算机来说并不明显。

令人欣慰的是,我们有一种方法可以表达更多的这类联系。一个单词向量是一个单词的数字表示,它表示该单词与其他单词的关系。

每个单词都被解释为一个唯一的长数组。你可以把这些数字想象成类似 GPS 坐标的东西。GPS 坐标由两个数字组成(纬度和经度),如果我们看到两组在数字上彼此接*的 GPS 坐标(如 43,-70 和 44,-70),我们就会知道这两个位置相对接*。单词向量的工作方式类似,尽管每个单词有两个以上的坐标,所以人类很难看到它们。

使用spaCyen_core_web_sm模型,让我们看看一个单词的向量的长度,以及使用.vector.shape的向量是什么样子。

import en_core_web_sm
nlp = en_core_web_sm.load()
mango = nlp(u'mango')
print(mango.vector.shape)
print(mango.vector) 
(96,)
[ 1.0466383  -1.5323697  -0.72177905 -2.4700649  -0.2715162   1.1589639
  1.7113379  -0.31615403 -2.0978343   1.837553    1.4681302   2.728043
 -2.3457408  -5.17184    -4.6110015  -0.21236466 -0.3029521   4.220028
 -0.6813917   2.4016762  -1.9546705  -0.85086954  1.2456163   1.5107994
  0.4684736   3.1612053   0.15542296  2.0598564   3.780035    4.6110964
  0.6375268  -1.078107   -0.96647096 -1.3939928  -0.56914186  0.51434743
  2.3150034  -0.93199825 -2.7970662  -0.8540115  -3.4250052   4.2857723
  2.5058174  -2.2150877   0.7860181   3.496335   -0.62606215 -2.0213525
 -4.47421     1.6821622  -6.0789204   0.22800982 -0.36950028 -4.5340714
 -1.7978683  -2.080299    4.125556    3.1852438  -3.286446    1.0892276
  1.017115    1.2736416  -0.10613725  3.5102775   1.1902348   0.05483437
 -0.06298041  0.8280688   0.05514218  0.94817173 -0.49377063  1.1512338
 -0.81374085 -1.6104267   1.8233354  -2.278403   -2.1321895   0.3029334
 -1.4510616  -1.0584296  -3.5698352  -0.13046083 -0.2668339   1.7826645
  0.4639858  -0.8389523  -0.02689964  2.316218    5.8155413  -0.45935947
  4.368636    1.6603007  -3.1823301  -1.4959551  -0.5229269   1.3637555 ] 

人类不可能看到那个数组并识别出它的意思是“mango”,但这种表示单词的方式对机器来说很好,因为它允许我们使用数组中的坐标来表示单词的意思及其与其他类似单词的“接*度”。

文本分类

既然我们已经看了一些spaCy可以做的很酷的事情,让我们看看这些自然语言处理技术的一个更大的真实世界的应用:文本分类。很多时候,我们可能会发现自己有一组文本数据,我们希望根据一些参数(例如,可能是每个片段的主题)进行分类,文本分类将帮助我们做到这一点。

下图展示了我们在对文本进行分类时想要做的事情。首先,我们从源文本(以及它附带的任何标签或元数据)中提取我们想要的特征,然后我们将清理后的数据输入机器学习算法,该算法为我们进行分类。

text-classification-python-spacy

导入库

我们将从导入这个任务所需的库开始。我们已经导入了spaCy,但是我们还需要pandasscikit-learn来帮助我们的分析。

import pandas as pd
from sklearn.feature_extraction.text import CountVectorizer,TfidfVectorizer
from sklearn.base import TransformerMixin
from sklearn.pipeline import Pipeline 

加载数据

上面,我们已经看了一些用spaCy进行文本分析的简单例子,但是现在我们将使用scikit-learn进行一些逻辑回归分类。为了更加真实,我们将使用真实世界的数据集— 这组亚马逊 Alexa 产品评论。

这个数据集以制表符分隔文件的形式出现。tsv)。它有五列:ratingdatevariationverified_reviewsfeedback

rating表示每个用户给 Alexa 的评分(满分 5 分)。date表示评审日期,variation描述用户评审的模型。verified_reviews包含每条评论的文字,feedback包含一个情绪标签,1 表示正面情绪(用户喜欢),0 表示负面情绪(用户不喜欢)。

该数据集包含消费者对亚马逊 Alexa 产品的评论,如 Echos、Echo Dots、Alexa Firesticks 等。我们要做的是开发一个分类模型,它查看评论文本并预测评论是正面还是负面。由于这个数据集已经在feedback列中包含了评论是正面还是负面,我们可以使用这些答案来训练和测试我们的模型。我们的目标是生成一个准确的模型,然后我们可以用它来处理新的用户评论,并快速确定它们是正面的还是负面的。

让我们首先将数据读入一个pandas数据帧,然后使用 pandas 的内置函数来帮助我们更仔细地查看我们的数据。

# Loading TSV file
df_amazon = pd.read_csv ("datasets/amazon_alexa.tsv", sep="\t") 
# Top 5 records
df_amazon.head() 
等级 日期 变化 已验证 _ 评论 反馈
Zero five 2018 年 7 月 31 日 木炭织物 爱我的回声! one
one five 2018 年 7 月 31 日 木炭织物 爱死了。 one
Two four 2018 年 7 月 31 日 胡桃木饰面 有时候在玩游戏的时候,你可以回答… one
three five 2018 年 7 月 31 日 木炭织物 我从这件事情中获得了很多乐趣。我的 4 … one
four five 2018 年 7 月 31 日 木炭织物 音乐 one
# shape of dataframe
df_amazon.shape 
(3150, 5) 
# View data information
df_amazon.info() 
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 3150 entries, 0 to 3149
Data columns (total 5 columns):
rating              3150 non-null int64
date                3150 non-null object
variation           3150 non-null object
verified_reviews    3150 non-null object
feedback            3150 non-null int64
dtypes: int64(2), object(3)
memory usage: 123.1+ KB 
# Feedback Value count
df_amazon.feedback.value_counts() 
1    2893
0     257
Name: feedback, dtype: int64 

spaCy标记数据

现在我们知道了我们在做什么,让我们使用spaCy创建一个定制的记号赋予器函数。我们将使用这个功能从每个评论中自动删除我们不需要的信息,比如停用词和标点符号。

我们将首先从spaCy导入我们需要的英文模型,以及 Python 的string模块,它包含了我们可以在string.punctuation中使用的所有标点符号的有用列表。我们将创建包含我们想要删除的标点符号和停用词的变量,以及一个通过spaCy的英语模块运行输入的解析器。

然后,我们将创建一个spacy_tokenizer()函数,它接受一个句子作为输入,并将该句子处理成记号,执行词汇化、小写和删除停用词。这类似于我们在本教程前面的例子中所做的,但是现在我们把它们放在一个函数中,用于预处理我们正在分析的每个用户评论。

import string
from spacy.lang.en.stop_words import STOP_WORDS
from spacy.lang.en import English

# Create our list of punctuation marks
punctuations = string.punctuation

# Create our list of stopwords
nlp = spacy.load('en')
stop_words = spacy.lang.en.stop_words.STOP_WORDS

# Load English tokenizer, tagger, parser, NER and word vectors
parser = English()

# Creating our tokenizer function
def spacy_tokenizer(sentence):
    # Creating our token object, which is used to create documents with linguistic annotations.
    mytokens = parser(sentence)

    # Lemmatizing each token and converting each token into lowercase
    mytokens = [ word.lemma_.lower().strip() if word.lemma_ != "-PRON-" else word.lower_ for word in mytokens ]

    # Removing stop words
    mytokens = [ word for word in mytokens if word not in stop_words and word not in punctuations ]

    # return preprocessed list of tokens
    return mytokens 

定义自定义转换器

为了进一步清理我们的文本数据,我们还想创建一个自定义的转换器来删除起始和结束空格,并将文本转换成小写。这里,我们将创建一个自定义的predictors类,它继承了 TransformerMixin 类。该类覆盖 transform、fit 和 get_parrams 方法。我们还将创建一个clean_text()函数,删除空格并将文本转换成小写。

# Custom transformer using spaCy
class predictors(TransformerMixin):
    def transform(self, X, **transform_params):
        # Cleaning Text
        return [clean_text(text) for text in X]

    def fit(self, X, y=None, **fit_params):
        return self

    def get_params(self, deep=True):
        return {}

# Basic function to clean the text
def clean_text(text):     
    # Removing spaces and converting text into lowercase
    return text.strip().lower() 

矢量化特征工程(TF-IDF)

当我们对文本进行分类时,我们最终会得到与各自标签相匹配的文本片段。但是我们不能简单地在我们的机器学习模型中使用文本字符串;我们需要一种方法将我们的文本转换成可以用数字表示的东西,就像标签一样(1 代表积极,0 代表消极)。将文本分为正面和负面标签称为情感分析。所以我们需要一种用数字表示文本的方法。

我们可以使用的一个工具叫做单词包。BoW 将文本转换成给定文档中单词出现的矩阵。它关注给定的单词是否出现在文档中,并生成一个矩阵,我们可能会看到它被称为 BoW 矩阵或文档术语矩阵。

我们可以通过使用scikit-learn的计数矢量器为我们的文本数据生成一个弓形矩阵。在下面的代码中,我们告诉CountVectorizer使用我们构建的自定义spacy_tokenizer函数作为它的标记器,并定义我们想要的 ngram 范围。

n 元语法是给定文本中相邻单词的组合,其中 n 是记号中包含的单词数。比如在句子“2022 年足球世界杯谁会赢?” unigrams 是一系列单词,比如“谁”、“威尔”、“赢”等等。二元模型将是两个连续单词的序列,例如“谁将会”、“将会赢”等等。因此,我们将在下面的代码中使用的ngram_range参数设置了我们的 ngrams 的下限和上限(我们将使用 unigrams)。然后我们将把 ngrams 分配给bow_vector

bow_vector = CountVectorizer(tokenizer = spacy_tokenizer, ngram_range=(1,1)) 

我们还想看看我们的术语的 TF-IDF(术语频率-逆文档频率)。这听起来很复杂,但这只是通过将每个单词的频率与文档频率进行比较来标准化我们的单词包(BoW)的一种方式。换句话说,这是一种表示特定术语在给定文档的上下文中有多重要的方式,基于该术语出现的次数以及该术语在其他文档中出现的次数。TF-IDF 越高,该术语对该文档越重要。

我们可以用下面的数学公式来表示:

text-classification-python-spacy-math

当然,我们不必手动计算!我们可以使用scikit-learn的 TfidfVectorizer 自动生成 TF-IDF。同样,我们将告诉它使用我们用spaCy构建的自定义标记器,然后我们将结果赋给变量tfidf_vector

tfidf_vector = TfidfVectorizer(tokenizer = spacy_tokenizer) 

将数据分为训练集和测试集

我们试图建立一个分类模型,但我们需要一种方法来了解它实际上是如何执行的。将数据集分为训练集和测试集是一种行之有效的方法。我们将使用一半的数据集作为我们的训练集,其中将包括正确的答案。然后,我们将使用数据集的另一半来测试我们的模型,而不需要给出答案,看看它的表现有多准确。

为了方便起见,scikit-learn为我们提供了一个内置函数:train_test_split()。我们只需要告诉它我们希望它分割的特性集(X)、我们希望它测试的标签(ylabels)以及我们希望用于测试集的大小(以十进制形式表示为百分比)。

from sklearn.model_selection import train_test_split

X = df_amazon['verified_reviews'] # the features we want to analyze
ylabels = df_amazon['feedback'] # the labels, or answers, we want to test against

X_train, X_test, y_train, y_test = train_test_split(X, ylabels, test_size=0.3) 

创建管线并生成模型

现在我们都设置好了,是时候实际构建我们的模型了!我们将从导入LogisticRegression模块并创建一个 LogisticRegression 分类器对象开始。

然后,我们将创建一个包含三个组件的管道:一个清理器、一个矢量器和一个分类器。清理器使用我们的predictors类对象来清理和预处理文本。矢量器使用 countvector 对象为我们的文本创建单词包矩阵。分类器是执行逻辑回归以分类情感的对象。

一旦这个管道建立起来,我们将使用fit()来安装管道组件。

# Logistic Regression Classifier
from sklearn.linear_model import LogisticRegression
classifier = LogisticRegression()

# Create pipeline using Bag of Words
pipe = Pipeline([("cleaner", predictors()),
                 ('vectorizer', bow_vector),
                 ('classifier', classifier)])

# model generation
pipe.fit(X_train,y_train) 
Pipeline(memory=None,
     steps=[('cleaner', <__main__.predictors object at 0x00000254DA6F8940>), ('vectorizer', CountVectorizer(analyzer='word', binary=False, decode_error='strict',
        dtype=<class 'numpy.int64'>, encoding='utf-8', input='content',
        lowercase=True, max_df=1.0, max_features=None, min_df=1,
      ...ty='l2', random_state=None, solver='liblinear', tol=0.0001,
          verbose=0, warm_start=False))]) 

评估模型

让我们来看看我们的模型实际表现如何!我们可以使用来自scikit-learnmetrics模块来完成这项工作。既然我们已经训练了我们的模型,我们将通过管道输入我们的测试数据,以得出预测。然后,我们将使用metrics模块的各种功能来查看我们的模型的准确度、精确度和召回率。

  • 准确性是指我们的模型做出的完全正确的总预测的百分比。
  • Precision 描述了我们预测中真阳性与真阳性加假阳性的比率。
  • Recall 描述了我们预测中的真阳性与真阳性加假阴性的比率。

上面的文档链接提供了每个术语的更多细节和更精确的定义,但底线是所有三个指标都是从 0 到 1 测量的,其中 1 表示预测完全正确。因此,我们的模型的分数越接* 1 越好。

from sklearn import metrics
# Predicting with a test dataset
predicted = pipe.predict(X_test)

# Model Accuracy
print("Logistic Regression Accuracy:",metrics.accuracy_score(y_test, predicted))
print("Logistic Regression Precision:",metrics.precision_score(y_test, predicted))
print("Logistic Regression Recall:",metrics.recall_score(y_test, predicted)) 
Logistic Regression Accuracy: 0.9417989417989417
Logistic Regression Precision: 0.9528508771929824
Logistic Regression Recall: 0.9863791146424518 

换句话说,总的来说,我们的模型在 94.1%的时候正确识别了评论的情感。当它预测一个评论是正面的时候,那个评论实际上 95%的时候都是正面的。当被给予正面评价时,我们的模型在 98.6%的情况下将其识别为正面的

资源和后续步骤

在本教程的过程中,我们已经从使用spaCy执行一些非常简单的文本分析操作,到使用scikit-learn构建我们自己的机器学习模型。当然,这仅仅是开始,还有很多东西spaCyscikit-learn可以提供给 Python 数据科学家。

以下是一些有用资源的链接:

  • Dataquest 关于 Python 中线性回归的机器学习课程;许多其他机器学习课程也可以在我们的数据科学家之路中找到。

用正确的方法学习 Python。

从第一天开始,就在你的浏览器窗口中通过编写 Python 代码来学习 Python。这是学习 Python 的最佳方式——亲自看看我们 60 多门免费课程中的一门。

astronaut floating over code

尝试数据任务

教程:熊猫的时间序列分析

原文:https://www.dataquest.io/blog/tutorial-time-series-analysis-with-pandas/

January 10, 2019

在本教程中,我们将了解熊猫图书馆中强大的时间序列工具。我们将学习制作像这样的酷酷的图表!

最初是为金融时间序列(如每日股票市场价格)开发的,pandas 中健壮而灵活的数据结构可以应用于任何领域的时间序列数据,包括商业、科学、工程、公共卫生和许多其他领域。借助这些工具,您可以轻松地以任何粒度级别组织、转换、分析和可视化您的数据—检查感兴趣的特定时间段内的详细信息,并缩小以探索不同时间尺度上的变化,如每月或每年的汇总、重复模式和长期趋势。

从最广泛的定义来看,时间序列是在不同时间点测量值的任何数据集。许多时间序列以特定的频率均匀分布,例如,每小时的天气测量、每天的网站访问量或每月的销售总额。时间序列也可以是不规则间隔和零星的,例如,计算机系统的事件日志中的时间戳数据或 911 紧急呼叫的历史记录。Pandas 时间序列工具同样适用于任何一种时间序列。

本教程将主要关注时间序列分析的数据争论和可视化方面。通过研究能源数据的时间序列,我们将了解基于时间的索引、重采样和滚动窗口等技术如何帮助我们探索电力需求和可再生能源供应随时间的变化。我们将讨论以下主题:

  • 数据集:开放电力系统数据
  • 时间序列数据结构
  • 基于时间的索引
  • 可视化时间序列数据
  • 季节性
  • 频率
  • 重采样
  • 滚动窗户
  • 趋势

我们将使用 Python 3.6、pandas、matplotlib 和 seaborn。为了充分利用本教程,您需要熟悉 pandas 和 matplotlib 的基础知识。

还没到那一步?通过我们的Python for Data Science:Fundamentals和中级课程,构建您的 Python 基础技能。

数据集:开放电力系统数据

在本教程中,我们将使用德国开放电力系统数据(OPSD) 的每日时间序列,德国*年来一直在快速扩大其可再生能源生产。该数据集包括 2006-2017 年全国电力消费、风力发电和太阳能发电总量。你可以在这里下载数据。

电力生产和消费以千兆瓦时(GWh)的日总量报告。数据文件的列有:

  • Date —日期( yyyy-mm-dd 格式)
  • Consumption —用电量,单位为吉瓦时
  • Wind——以 GWh 为单位的风力发电量
  • Solar —太阳能发电量,单位 GWh
  • Wind+Solar——风能和太阳能发电量之和,单位 GWh

我们将探索德国的电力消费和生产如何随着时间的推移而变化,使用 pandas 时间序列工具来回答以下问题:

  • 电力消耗通常在什么时候最高和最低?
  • 风力和太阳能发电如何随一年的季节变化?
  • 电力消耗、太阳能和风力发电的长期趋势是什么?
  • 风能和太阳能发电与电力消耗相比如何,这一比例随着时间的推移如何变化?

时间序列数据结构

在我们深入研究 OPSD 数据之前,让我们简要介绍一下用于处理日期和时间的主要 pandas 数据结构。在熊猫中,单个时间点被表示为一个时间戳。我们可以使用to_datetime()函数从各种日期/时间格式的字符串中创建时间戳。让我们导入 pandas 并将一些日期和时间转换成时间戳。

import pandas as pd
pd.to_datetime('2018-01-15 3:45pm') 
 Timestamp('2018-01-15 15:45:00') 
 pd.to_datetime('7/8/1952') 
 Timestamp('1952-07-08 00:00:00') 

正如我们所见,to_datetime()根据输入自动推断日期/时间格式。在上面的例子中,不明确的日期'7/8/1952'被假定为月/日/年,并被解释为 1952 年 7 月 8 日。或者,我们可以使用dayfirst参数告诉熊猫将日期解释为 1952 年 8 月 7 日。

pd.to_datetime('7/8/1952, dayfirst=True) 
 Timestamp('1952-08-07 00:00:00')

如果我们提供一个字符串列表或数组作为对to_datetime()的输入,它将在一个 DatetimeIndex 对象中返回一系列日期/时间值,这是一个核心数据结构,为 pandas 的大部分时间序列功能提供支持。

 pd.to_datetime(['2018-01-05', '7/8/1952', 'Oct 10, 1995']) 
 DatetimeIndex(['2018-01-05', '1952-07-08', '1995-10-10'], dtype='datetime64[ns]', freq=None)

在上面的 DatetimeIndex 中,数据类型datetime64[ns]表示底层数据存储为64位整数,单位为纳秒(ns)。这种数据结构允许 pandas 紧凑地存储大型日期/时间值序列,并使用 NumPy datetime64 数组有效地执行矢量化运算。

如果我们正在处理一系列日期/时间格式相同的字符串,我们可以用format参数显式地指定它。对于非常大的数据集,与默认行为相比,这可以大大提高to_datetime()的性能,在默认行为中,格式是为每个单独的字符串单独推断的。可以使用 Python 内置 datetime 模块中的strftime()strptime()函数中的任意格式代码。下面的示例使用格式代码%m(数字月份)、%d(月份中的某一天)和%y(两位数的年份)来指定格式。

pd.to_datetime(['2/25/10', '8/6/17', '12/15/12'], format='%m/%d/%y') 
 DatetimeIndex(['2010-02-25', '2017-08-06', '2012-12-15'], dtype='datetime64[ns]', freq=None) 

除了表示各个时间点的 Timestamp 和 DatetimeIndex 对象,pandas 还包括表示持续时间(例如,125 秒)和周期(例如,2018 年 11 月)的数据结构。关于这些数据结构的更多信息,这里有一个很好的总结。在本教程中,我们将使用 DatetimeIndexes,熊猫时间序列最常见的数据结构。

创建时间序列数据框架

为了在 pandas 中处理时间序列数据,我们使用 DatetimeIndex 作为数据帧(或序列)的索引。让我们看看如何用我们的 OPSD 数据集做到这一点。首先,我们使用read_csv()函数将数据读入 DataFrame,然后显示它的形状。

 opsd_daily = pd.read_csv('opsd_germany_daily.csv')
opsd_daily.shape 
 (4383, 5) 

DataFrame 有 4383 行,涵盖从 2006 年 1 月 1 日到 2017 年 12 月 31 日的时间段。为了查看数据的样子,让我们使用head()tail()方法来显示前三行和后三行。

opsd_daily.head(3) 
日期 消费 太阳的 风能+太阳能
Zero 2006-01-01 One thousand and sixty-nine point one eight four 圆盘烤饼 圆盘烤饼 圆盘烤饼
one 2006-01-02 One thousand three hundred and eighty point five two one 圆盘烤饼 圆盘烤饼 圆盘烤饼
Two 2006-01-03 One thousand four hundred and forty-two point five three three 圆盘烤饼 圆盘烤饼 圆盘烤饼
opsd_daily.tail(3) 
日期 消费 太阳的 风能+太阳能
Four thousand three hundred and eighty 2017-12-29 1295.08753 Five hundred and eighty-four point two seven seven Twenty-nine point eight five four Six hundred and fourteen point one three one
Four thousand three hundred and eighty-one 2017-12-30 1215.44897 Seven hundred and twenty-one point two four seven Seven point four six seven Seven hundred and twenty-eight point seven one four
Four thousand three hundred and eighty-two 2017-12-31 1107.11488 Seven hundred and twenty-one point one seven six Nineteen point nine eight Seven hundred and forty-one point one five six

接下来,让我们检查每一列的数据类型。

 opsd_daily.dtypes 
 Date datetime64[ns]
Consumption float64
Wind float64
Solar float64
Wind+Solar float64
dtype: object 

既然Date列是正确的数据类型,让我们将其设置为数据帧的索引。

 opsd_daily = opsd_daily.set_index('Date')
opsd_daily.head(3) 
消费 太阳的 风能+太阳能
日期
--- --- --- --- ---
2006-01-01 One thousand and sixty-nine point one eight four 圆盘烤饼 圆盘烤饼 圆盘烤饼
2006-01-02 One thousand three hundred and eighty point five two one 圆盘烤饼 圆盘烤饼 圆盘烤饼
2006-01-03 One thousand four hundred and forty-two point five three three 圆盘烤饼 圆盘烤饼 圆盘烤饼
 opsd_daily.index 
 DatetimeIndex(['2006-01-01', '2006-01-02', '2006-01-03', '2006-01-04',
'2006-01-05', '2006-01-06', '2006-01-07', '2006-01-08',
'2006-01-09', '2006-01-10',
...
'2017-12-22', '2017-12-23', '2017-12-24', '2017-12-25',
'2017-12-26', '2017-12-27', '2017-12-28', '2017-12-29',
'2017-12-30', '2017-12-31'],
dtype='datetime64[ns]', name='Date', length=4383, freq=None) 

或者,我们可以使用read_csv()函数的index_colparse_dates参数将上述步骤合并成一行。这通常是一种有用的捷径。

 opsd_daily = pd.read_csv('opsd_germany_daily.csv', index_col=0, parse_dates=True) 

现在我们的 DataFrame 的索引是 DatetimeIndex,我们可以使用 pandas 强大的基于时间的索引来争论和分析我们的数据,正如我们将在下面的部分中看到的。

DatetimeIndex 的另一个有用的方面是,的各个日期/时间组件都可以作为属性使用,例如yearmonthday等等。让我们给opsd_daily再添加几列,包含年、月和工作日名称。

 # Add columns with year, month, and weekday name
opsd_daily['Year'] = opsd_daily.index.year
opsd_daily['Month'] = opsd_daily.index.month
opsd_daily['Weekday Name'] = opsd_daily.index.weekday_name
# Display a random sampling of 5 rows
opsd_daily.sample(5, random_state=0) 
消费 太阳的 风能+太阳能 工作日名称
日期
--- --- --- --- --- --- --- ---
2008-08-23 One thousand one hundred and fifty-two point zero one one 圆盘烤饼 圆盘烤饼 圆盘烤饼 Two thousand and eight eight 星期六
2013-08-08 One thousand two hundred and ninety-one point nine eight four Seventy-nine point six six six Ninety-three point three seven one One hundred and seventy-three point zero three seven Two thousand and thirteen eight 星期四
2009-08-27 One thousand two hundred and eighty-one point zero five seven 圆盘烤饼 圆盘烤饼 圆盘烤饼 Two thousand and nine eight 星期四
2015-10-02 One thousand three hundred and ninety-one point zero five Eighty-one point two two nine One hundred and sixty point six four one Two hundred and forty-one point eight seven Two thousand and fifteen Ten 星期五
2009-06-02 One thousand two hundred and one point five two two 圆盘烤饼 圆盘烤饼 圆盘烤饼 Two thousand and nine six 星期二

基于时间的索引

熊猫时间序列最强大和方便的特性之一是基于时间的索引——使用日期和时间来直观地组织和访问我们的数据。通过基于时间的索引,我们可以使用日期/时间格式的字符串,通过loc访问器选择数据帧中的数据。索引的工作方式类似于使用loc的标准的基于标签的索引,但是有一些额外的特性。

例如,我们可以使用诸如'2017-08-10'这样的字符串来选择某一天的数据。

 opsd_daily.loc['2017-08-10'] 
 Consumption 1351.49
Wind 100.274
Solar 71.16
Wind+Solar 171.434
Year 2017
Month 8
Weekday Name Thursday
Name: 2017-08-10 00:00:00, dtype: object 

我们也可以选择一段日子,比如'2014-01-20':'2014-01-22'。与使用loc的常规基于标签的索引一样,切片包含两个端点。

 opsd_daily.loc['2014-01-20':'2014-01-22'] 
消费 太阳的 风能+太阳能 工作日名称
日期
--- --- --- --- --- --- --- ---
2014-01-20 One thousand five hundred and ninety point six eight seven Seventy-eight point six four seven Six point three seven one Eighty-five point zero one eight Two thousand and fourteen one 星期一
2014-01-21 One thousand six hundred and twenty-four point eight zero six Fifteen point six four three Five point eight three five Twenty-one point four seven eight Two thousand and fourteen one 星期二
2014-01-22 One thousand six hundred and twenty-five point one five five Sixty point two five nine Eleven point nine nine two Seventy-two point two five one Two thousand and fourteen one 星期三

pandas 时间序列的另一个非常方便的特性是部分字符串索引,在这里我们可以选择与给定字符串部分匹配的所有日期/时间。例如,我们可以用opsd_daily.loc['2006']选择整个 2006 年,或者用opsd_daily.loc['2012-02']选择整个 2012 年 2 月。

 opsd_daily.loc['2012-02'] 
消费 太阳的 风能+太阳能 工作日名称
日期
--- --- --- --- --- --- --- ---
2012-02-01 One thousand five hundred and eleven point eight six six One hundred and ninety-nine point six zero seven Forty-three point five zero two Two hundred and forty-three point one zero nine Two thousand and twelve Two 星期三
2012-02-02 One thousand five hundred and sixty-three point four zero seven Seventy-three point four six nine Forty-four point six seven five One hundred and eighteen point one four four Two thousand and twelve Two 星期四
2012-02-03 One thousand five hundred and sixty-three point six three one Thirty-six point three five two Forty-six point five one Eighty-two point eight six two Two thousand and twelve Two 星期五
2012-02-04 One thousand three hundred and seventy-two point six one four Twenty point five five one Forty-five point two two five Sixty-five point seven seven six Two thousand and twelve Two 星期六
2012-02-05 One thousand two hundred and seventy-nine point four three two Fifty-five point five two two Fifty-four point five seven two One hundred and ten point zero nine four Two thousand and twelve Two 星期日
2012-02-06 One thousand five hundred and seventy-four point seven six six Thirty-four point eight nine six Fifty-five point three eight nine Ninety point two eight five Two thousand and twelve Two 星期一
2012-02-07 One thousand six hundred and fifteen point zero seven eight One hundred point three one two Nineteen point eight six seven One hundred and twenty point one seven nine Two thousand and twelve Two 星期二
2012-02-08 One thousand six hundred and thirteen point seven seven four Ninety-three point seven six three Thirty-six point nine three One hundred and thirty point six nine three Two thousand and twelve Two 星期三
2012-02-09 One thousand five hundred and ninety-one point five three two One hundred and thirty-two point two one nine Nineteen point zero four two One hundred and fifty-one point two six one Two thousand and twelve Two 星期四
2012-02-10 One thousand five hundred and eighty-one point two eight seven Fifty-two point one two two Thirty-four point eight seven three Eighty-six point nine nine five Two thousand and twelve Two 星期五
2012-02-11 One thousand three hundred and seventy-seven point four zero four Thirty-two point three seven five Forty-four point six two nine Seventy-seven point zero zero four Two thousand and twelve Two 星期六
2012-02-12 One thousand two hundred and sixty-four point two five four Sixty-two point six five nine Forty-five point one seven six One hundred and seven point eight three five Two thousand and twelve Two 星期日
2012-02-13 One thousand five hundred and sixty-one point nine eight seven Twenty-five point nine eight four Eleven point two eight seven Thirty-seven point two seven one Two thousand and twelve Two 星期一
2012-02-14 One thousand five hundred and fifty point three six six One hundred and forty-six point four nine five Nine point six one One hundred and fifty-six point one zero five Two thousand and twelve Two 星期二
2012-02-15 One thousand four hundred and seventy-six point zero three seven Four hundred and thirteen point three six seven Eighteen point eight seven seven Four hundred and thirty-two point two four four Two thousand and twelve Two 星期三
2012-02-16 One thousand five hundred and four point one one nine One hundred and thirty point two four seven Thirty-eight point one seven six One hundred and sixty-eight point four two three Two thousand and twelve Two 星期四
2012-02-17 One thousand four hundred and thirty-eight point eight five seven One hundred and ninety-six point five one five Seventeen point three two eight Two hundred and thirteen point eight four three Two thousand and twelve Two 星期五
2012-02-18 One thousand two hundred and thirty-six point zero six nine Two hundred and thirty-seven point eight eight nine Twenty-six point two four eight Two hundred and sixty-four point one three seven Two thousand and twelve Two 星期六
2012-02-19 One thousand one hundred and seven point four three one Two hundred and seventy-two point six five five Thirty point three eight two Three hundred and three point zero three seven Two thousand and twelve Two 星期日
2012-02-20 One thousand four hundred and one point eight seven three One hundred and sixty point three one five Fifty-three point seven nine four Two hundred and fourteen point one zero nine Two thousand and twelve Two 星期一
2012-02-21 One thousand four hundred and thirty-four point five three three Two hundred and eighty-one point nine zero nine Fifty-seven point nine eight four Three hundred and thirty-nine point eight nine three Two thousand and twelve Two 星期二
2012-02-22 One thousand four hundred and fifty-three point five zero seven Two hundred and eighty-seven point six three five Seventy-four point nine zero four Three hundred and sixty-two point five three nine Two thousand and twelve Two 星期三
2012-02-23 One thousand four hundred and twenty-seven point four zero two Three hundred and fifty-three point five one Eighteen point nine two seven Three hundred and seventy-two point four three seven Two thousand and twelve Two 星期四
2012-02-24 One thousand three hundred and seventy-three point eight Three hundred and eighty-two point seven seven seven Twenty-nine point two eight one Four hundred and twelve point zero five eight Two thousand and twelve Two 星期五
2012-02-25 One thousand one hundred and thirty-three point one eight four Three hundred and two point one zero two Forty-two point six six seven Three hundred and forty-four point seven six nine Two thousand and twelve Two 星期六
2012-02-26 One thousand and eighty-six point seven four three Ninety-five point two three four Thirty-seven point two one four One hundred and thirty-two point four four eight Two thousand and twelve Two 星期日
2012-02-27 One thousand four hundred and thirty-six point zero nine five Eighty-six point nine five six Forty-three point zero nine nine One hundred and thirty point zero five five Two thousand and twelve Two 星期一
2012-02-28 One thousand four hundred and eight point two one one Two hundred and thirty-one point nine two three Sixteen point one nine Two hundred and forty-eight point one one three Two thousand and twelve Two 星期二
2012-02-29 One thousand four hundred and thirty-four point zero six two Seventy-seven point zero two four Thirty point three six One hundred and seven point three eight four Two thousand and twelve Two 星期三

可视化时间序列数据

通过 pandas 和 matplotlib,我们可以轻松地可视化我们的时间序列数据。在这一节中,我们将介绍几个例子和一些对我们的时间序列图有用的定制。首先,我们导入 matplotlib。

 import matplotlib.pyplot as plt
# Display figures inline in Jupyter notebook 

我们将为我们的图使用 seaborn 样式,让我们将默认的图形大小调整为适合时间序列图的形状。

 import seaborn as sns
# Use seaborn style defaults and set the default figure size
sns.set(rc={'figure.figsize':(11, 4)}) 

让我们使用 DataFrame 的plot()方法,创建一个德国日常用电量全时间序列的线图。

 opsd_daily['Consumption'].plot(linewidth=0.5); 

time-series-pandas_36_0.png

我们可以看到,plot()方法为 x 轴选择了非常好的刻度位置(每两年)和标签(年份),这很有帮助。但是,这么多的数据点,线图很拥挤,很难读懂。让我们将数据绘制成点,同时看看SolarWind时间序列。

 cols_plot = ['Consumption', 'Solar', 'Wind']
axes = opsd_daily[cols_plot].plot(marker='.', alpha=0.5, linestyle='None', figsize=(11, 9), subplots=True)
for ax in axes:
    ax.set_ylabel('Daily Totals (GWh)') 

time-series-pandas_38_0.png

我们已经可以看到一些有趣的模式出现了:

  • 冬季耗电量最高,可能是由于电加热和照明用电的增加,夏季耗电量最低。
  • 电力消耗似乎分成两个集群——一个以大约 1400 GWh 为中心的振荡,另一个以大约 1150 GWh 为中心的数据点越来越少,越来越分散。我们可能会猜测这些聚类对应于工作日和周末,我们将很快对此进行进一步的研究。
  • 太阳能发电量在阳光最充足的夏季最高,冬季最低。
  • 风力发电量在冬季最高,可能是因为风力更强,风暴更频繁,而在夏季最低。
  • *年来,风力发电似乎呈现出强劲的增长趋势。

所有三个时间序列都明显表现出周期性——在时间序列分析中通常被称为季节性——其中一个模式以固定的时间间隔一次又一次地重复。ConsumptionSolarWind时间序列在每年的高值和低值之间振荡,与一年中天气的季节性变化相对应。然而,季节性通常不一定与气象季节相一致。例如,零售销售数据通常表现出年度季节性,在十一月和十二月销售增加,导致假期。

季节性也可能发生在其他时间尺度上。上图表明,德国的电力消耗可能存在一定的周季节性,与工作日和周末相对应。让我们画出一年的时间序列来进一步研究。

 ax = opsd_daily.loc['2017', 'Consumption'].plot()
ax.set_ylabel('Daily Consumption (GWh)'); 

time-series-pandas_40_0.png

现在我们可以清楚地看到周线振荡。另一个在这个粒度级别变得明显的有趣特性是,在 1 月初和 12 月底的假期期间,用电量会急剧下降。

让我们进一步放大,只看一月和二月。

 ax = opsd_daily.loc['2017-01':'2017-02', 'Consumption'].plot(marker='o', linestyle='-')
ax.set_ylabel('Daily Consumption (GWh)'); 

time-series-pandas_42_0.png

正如我们所怀疑的,消费在工作日最高,周末最低。

自定义时间序列图

为了更好地显示上图中电力消耗的每周季节性,最好在每周时间刻度上(而不是在每月的第一天)显示垂直网格线。我们可以用 matplotlib.dates 定制我们的绘图,所以让我们导入那个模块。

 import matplotlib.dates as mdates 

因为与 DataFrame 的plot()方法相比,matplotlib.dates 对日期/时间刻度的处理稍有不同,所以让我们直接在 matplotlib 中创建绘图。然后我们使用mdates.WeekdayLocator()mdates.MONDAY将 x 轴刻度设置为每周的第一个星期一。我们还使用mdates.DateFormatter()来改进刻度标签的格式,使用我们之前看到的格式代码。

fig, ax = plt.subplots()
ax.plot(opsd_daily.loc['2017-01':'2017-02', 'Consumption'], marker='o', linestyle='-')
ax.set_ylabel('Daily Consumption (GWh)')
ax.set_title('Jan-Feb 2017 Electricity Consumption')
# Set x-axis major ticks to weekly interval, on Mondays
ax.xaxis.set_major_locator(mdates.WeekdayLocator(byweekday=mdates.MONDAY))
# Format x-tick labels as 3-letter month name and day number
ax.xaxis.set_major_formatter(mdates.DateFormatter('%b %d')); 

time-series-pandas_46_0.png

现在,我们在每个星期一都有垂直网格线和格式良好的刻度标签,所以我们可以很容易地分辨出哪一天是工作日和周末。

还有许多其他方法来可视化时间序列,这取决于您尝试探索的模式—散点图、热图、直方图等等。在接下来的部分中,我们将看到其他可视化示例,包括以某种方式转换的时间序列数据的可视化,例如聚合或*滑数据。

季节性

接下来,让我们用箱线图进一步探索我们数据的季节性,使用 seaborn 的boxplot()函数按不同时间段对数据进行分组,并显示每组的分布。我们将首先按月对数据进行分组,以显示每年的季节性。

 fig, axes = plt.subplots(3, 1, figsize=(11, 10), sharex=True)
for name, ax in zip(['Consumption', 'Solar', 'Wind'], axes):
sns.boxplot(data=opsd_daily, x='Month', y=name, ax=ax)
ax.set_ylabel('GWh')
ax.set_title(name)
# Remove the automatic x-axis label from all but the bottom subplot
if ax != axes[-1]:
    ax.set_xlabel('') 

time-series-pandas_48_0.png

这些箱线图证实了我们在早期图中看到的年度季节性,并提供了一些额外的见解:
*虽然电力消耗通常在冬季较高,在夏季较低,但与 11 月和 2 月相比,12 月和 1 月的中间值和较低的两个四分位数较低,这可能是由于假期期间企业关闭。我们在 2017 年的时间序列中看到了这一点,箱线图证实了这是多年来的一致模式。
*虽然太阳能和风力发电都表现出年度季节性,但风力发电分布有更多异常值,反映了与风暴和其他瞬态天气条件相关的偶然极端风速的影响。

接下来,让我们按一周中的每一天对电力消耗时间序列进行分组,以探究每周的季节性。

 sns.boxplot(data=opsd_daily, x='Weekday Name', y='Consumption'); 

time-series-pandas_50_0.png

不出所料,工作日的用电量明显高于周末。工作日的低异常值大概是在假期。

本节简要介绍了时间序列的季节性。正如我们将在后面看到的,对数据应用滚动窗口也有助于在不同的时间尺度上可视化季节性。分析季节性的其他技术包括自相关图,它绘制了时间序列在不同时滞下与其自身的相关系数。

具有强季节性的时间序列通常可以用将信号分解为季节性和长期趋势的模型来很好地表示,并且这些模型可以用于预测时间序列的未来值。这种模型的一个简单例子是经典季节分解,如本教程所示。一个更复杂的例子是脸书的先知模型,它使用曲线拟合来分解时间序列,考虑多个时间尺度上的季节性、假日效应、突变点和长期趋势,如本教程中的所示。

频率

当时间序列的数据点在时间上均匀间隔时(例如,每小时、每天、每月等)。),时间序列可以与熊猫的频率联系起来。例如,让我们使用date_range()函数以每天的频率创建一个从1998-03-101998-03-15的等间距日期序列。

 pd.date_range('1998-03-10', '1998-03-15', freq='D') 
 DatetimeIndex(['1998-03-10', '1998-03-11', '1998-03-12', '1998-03-13',
'1998-03-14', '1998-03-15'],
dtype='datetime64[ns]', freq='D') 

得到的 DatetimeIndex 有一个值为'D'的属性freq,表示每天的频率。熊猫的可用频率包括每小时一次('H'、日历每天一次('D')、商业每天一次('B')、每周一次('W')、每月一次('M')、每季度一次('Q')、每年一次('A')以及许多其他的。频率也可以指定为任何基本频率的倍数,例如每五天一次'5D'

作为另一个例子,让我们以每小时的频率创建一个日期范围,指定开始日期和周期数,而不是开始日期和结束日期。

 pd.date_range('2004-09-20', periods=8, freq='H') 
 DatetimeIndex(['2004-09-20 00:00:00', '2004-09-20 01:00:00',
'2004-09-20 02:00:00', '2004-09-20 03:00:00',
'2004-09-20 04:00:00', '2004-09-20 05:00:00',
'2004-09-20 06:00:00', '2004-09-20 07:00:00'],
dtype='datetime64[ns]', freq='H') 

现在让我们再来看看我们的opsd_daily时间序列的 DatetimeIndex。

 opsd_daily.index 
 DatetimeIndex(['2006-01-01', '2006-01-02', '2006-01-03', '2006-01-04',
'2006-01-05', '2006-01-06', '2006-01-07', '2006-01-08',
'2006-01-09', '2006-01-10',
...
'2017-12-22', '2017-12-23', '2017-12-24', '2017-12-25',
'2017-12-26', '2017-12-27', '2017-12-28', '2017-12-29',
'2017-12-30', '2017-12-31'],
dtype='datetime64[ns]', name='Date', length=4383, freq=None) 

我们可以看到它没有频率(freq=None)。这是有意义的,因为索引是根据 CSV 文件中的日期序列创建的,没有明确指定时间序列的任何频率。

如果我们知道我们的数据应该在一个特定的频率,我们可以使用 DataFrame 的asfreq()方法来分配一个频率。如果数据中缺少任何日期/时间,将为这些日期/时间添加新行,这些行或者为空(NaN),或者根据指定的数据填充方法(如向前填充或插值)进行填充。

为了了解这是如何工作的,让我们创建一个新的 DataFrame,它只包含 2013 年 2 月 3 日、6 日和 8 日的Consumption数据。

 # To select an arbitrary sequence of date/time values from a pandas time series,
# we need to use a DatetimeIndex, rather than simply a list of date/time strings
times_sample = pd.to_datetime(['2013-02-03', '2013-02-06', '2013-02-08'])
# Select the specified dates and just the Consumption column
consum_sample = opsd_daily.loc[times_sample, ['Consumption']].copy()
consum_sample 
消费
2013-02-03 One thousand one hundred and nine point six three nine
2013-02-06 One thousand four hundred and fifty-one point four four nine
2013-02-08 One thousand four hundred and thirty-three point zero nine eight

现在我们使用asfreq()方法将数据帧转换为日频率,一列为未填充的数据,一列为向前填充的数据。

 # Convert the data to daily frequency, without filling any missings
consum_freq = consum_sample.asfreq('D')
# Create a column with missings forward filled
consum_freq['Consumption - Forward Fill'] = consum_sample.asfreq('D', method='ffill')
consum_freq 
消费 消耗-向前填充
2013-02-03 One thousand one hundred and nine point six three nine One thousand one hundred and nine point six three nine
2013-02-04 圆盘烤饼 One thousand one hundred and nine point six three nine
2013-02-05 圆盘烤饼 One thousand one hundred and nine point six three nine
2013-02-06 One thousand four hundred and fifty-one point four four nine One thousand four hundred and fifty-one point four four nine
2013-02-07 圆盘烤饼 One thousand four hundred and fifty-one point four four nine
2013-02-08 One thousand four hundred and thirty-three point zero nine eight One thousand four hundred and thirty-three point zero nine eight

Consumption列中,我们有原始数据,对于我们的consum_sample数据框架中缺失的任何日期,值为NaN。在Consumption - Forward Fill列中,缺失值已被向前填充,这意味着最后一个值在缺失行中重复,直到下一个非缺失值出现。

如果您正在进行任何时间序列分析,需要均匀间隔的数据,没有任何遗漏,您将希望使用asfreq()将您的时间序列转换为指定的频率,并用适当的方法填充任何遗漏。

重采样

将我们的时间序列数据重新采样到更低或更高的频率通常是有用的。重新采样到一个较低的频率(下采样)通常涉及一个聚合操作——例如,从每天的数据计算每月的总销售额。我们在本教程中使用的每日 OPSD 数据是从最初的每小时时间序列向下采样的。重新采样到更高的频率(上采样)不太常见,通常涉及插值或其他数据填充方法——例如,将每小时的天气数据插值到 10 分钟的间隔,以输入到科学模型中。

我们将在这里关注下采样,探索它如何帮助我们分析不同时间尺度上的 OPSD 数据。我们使用 DataFrame 的resample()方法,该方法将 DatetimeIndex 分割成时间仓,并按时间仓对数据进行分组。resample()方法返回一个重采样器对象,类似于 pandas GroupBy 对象。然后,我们可以应用聚合方法,如mean()median()sum()等。,添加到每个时间仓的数据组。

例如,让我们将数据重新采样为每周*均时间序列。

 # Specify the data columns we want to include (i.e. exclude Year, Month, Weekday Name)
data_columns = ['Consumption', 'Wind', 'Solar', 'Wind+Solar']
# Resample to weekly frequency, aggregating with mean
opsd_weekly_mean = opsd_daily[data_columns].resample('W').mean()
opsd_weekly_mean.head(3) 
消费 太阳的 风能+太阳能
日期
--- --- --- --- ---
2006-01-01 1069.184000 圆盘烤饼 圆盘烤饼 圆盘烤饼
2006-01-08 1381.300143 圆盘烤饼 圆盘烤饼 圆盘烤饼
2006-01-15 1486.730286 圆盘烤饼 圆盘烤饼 圆盘烤饼

上面标记为2006-01-01的第一行包含时间仓2006-01-012006-01-07中包含的所有数据的*均值。第二行标记为2006-01-08,包含从2006-01-082006-01-14时间仓的*均数据,依此类推。默认情况下,缩减采样时间序列的每一行都用时间条的右边缘进行标记。

通过构建,我们的每周时间序列的数据点是每日时间序列的 1/7。我们可以通过比较两个数据帧的行数来证实这一点。

 print(opsd_daily.shape[0])
print(opsd_weekly_mean.shape[0]) 
 4383
627 

让我们一起绘制一个六个月期间的每日和每周时间序列来比较它们。

 # Start and end of the date range to extract
start, end = '2017-01', '2017-06'
# Plot daily and weekly resampled time series together
fig, ax = plt.subplots()
ax.plot(opsd_daily.loc[start:end, 'Solar'],
marker='.', linestyle='-', linewidth=0.5, label='Daily')
ax.plot(opsd_weekly_mean.loc[start:end, 'Solar'],
marker='o', markersize=8, linestyle='-', label='Weekly Mean Resample')
ax.set_ylabel('Solar Production (GWh)')
ax.legend(); 

time-series-pandas_66_0.png

我们可以看到,周*均时间序列比日*均时间序列更*滑,因为较高的频率可变性在重采样中被*均掉了。

现在,让我们按月频率对数据进行重新采样,用总和而不是*均值进行合计。与使用mean()聚合不同,聚合会将所有缺失数据的任意时段的输出设置为NaN,sum()的默认行为将返回0的输出作为缺失数据的总和。我们使用min_count参数来改变这种行为。

 # Compute the monthly sums, setting the value to NaN for any month which has
# fewer than 28 days of data
opsd_monthly = opsd_daily[data_columns].resample('M').sum(min_count=28)
opsd_monthly.head(3) 
消费 太阳的 风能+太阳能
日期
--- --- --- --- ---
2006-01-31 Forty-five thousand three hundred and four point seven zero four 圆盘烤饼 圆盘烤饼 圆盘烤饼
2006-02-28 Forty-one thousand and seventy-eight point nine nine three 圆盘烤饼 圆盘烤饼 圆盘烤饼
2006-03-31 Forty-three thousand nine hundred and seventy-eight point one two four 圆盘烤饼 圆盘烤饼 圆盘烤饼

您可能会注意到,每月重新采样的数据被标记为每个月的月末(条柱的右边),而每周重新采样的数据被标记为条柱的左边。默认情况下,对于月度、季度和年度频率,重新采样的数据标记为条柱右边缘,对于所有其他频率,标记为条柱左边缘。该行为和各种其他选项可以使用resample()文档中列出的参数进行调整。

现在,让我们通过将耗电量绘制为线形图,将风能和太阳能发电量一起绘制为堆积面积图,来探索月度时间序列。

 fig, ax = plt.subplots()
ax.plot(opsd_monthly['Consumption'], color='black', label='Consumption')
opsd_monthly[['Wind', 'Solar']].plot.area(ax=ax, linewidth=0)
ax.xaxis.set_major_locator(mdates.YearLocator())
ax.legend()
ax.set_ylabel('Monthly Total (GWh)'); 

time-series-pandas_70_0.png

在这个月时间尺度上,我们可以清楚地看到每个时间序列中的年度季节性,并且很明显,电力消费随着时间的推移一直相当稳定,而风力发电一直在稳步增长,风力+太阳能发电在电力消费中所占的份额越来越大。

让我们通过对年频率进行重新采样并计算每年的Wind+SolarConsumption之比来进一步探究这一点。

 # Compute the annual sums, setting the value to NaN for any year which has
# fewer than 360 days of data
opsd_annual = opsd_daily[data_columns].resample('A').sum(min_count=360)
# The default index of the resampled DataFrame is the last day of each year,
# ('2006-12-31', '2007-12-31', etc.) so to make life easier, set the index
# to the year component
opsd_annual = opsd_annual.set_index(opsd_annual.index.year)
opsd_annual.index.name = 'Year'
# Compute the ratio of Wind+Solar to Consumption
opsd_annual['Wind+Solar/Consumption'] = opsd_annual['Wind+Solar'] / opsd_annual['Consumption']
opsd_annual.tail(3) 
消费 太阳的 风能+太阳能 风能+太阳能/消耗
--- --- --- --- --- ---
Two thousand and fifteen 505264.56300 Seventy-seven thousand four hundred and sixty-eight point nine nine four Thirty-four thousand nine hundred and seven point one three eight One hundred and twelve thousand three hundred and seventy-six point one three two 0.222410
Two thousand and sixteen 505927.35400 Seventy-seven thousand and eight point one two six Thirty-four thousand five hundred and sixty-two point eight two four One hundred and eleven thousand five hundred and seventy point nine five 0.220528
Two thousand and seventeen 504736.36939 One hundred and two thousand six hundred and sixty-seven point three six five Thirty-five thousand eight hundred and eighty-two point six four three One hundred and thirty-eight thousand five hundred and fifty point zero zero eight 0.274500

最后,让我们把风能+太阳能在年用电量中所占的份额绘制成柱状图。

 # Plot from 2012 onwards, because there is no solar production data in earlier years
ax = opsd_annual.loc[2012:, 'Wind+Solar/Consumption'].plot.bar(color='C0')
ax.set_ylabel('Fraction')
ax.set_ylim(0, 0.3)
ax.set_title('Wind + Solar Share of Annual Electricity Consumption')
plt.xticks(rotation=0); 

time-series-pandas_74_0.png

我们可以看到,风能+太阳能发电量占年用电量的比例已从 2012 年的约 15%上升至 2017 年的约 27%。

滚动窗户

滚动窗口操作是时间序列数据的另一个重要变换。类似于下采样,滚动窗口将数据分成时间窗口和,并且每个窗口中的数据用诸如mean()median()sum()等函数聚集。但是,与时间窗不重叠且输出频率低于输入频率的下采样不同,滚动窗口重叠并以与数据相同的频率“滚动”,因此转换后的时间序列与原始时间序列具有相同的频率。

默认情况下,一个窗口内的所有数据点在聚合中的权重相等,但这可以通过指定窗口类型来更改,如高斯、三角和其他。我们将坚持使用标准的等权重窗口。

让我们使用rolling()方法来计算我们每日数据的 7 天滚动*均值。我们使用center=True参数来标记每个窗口的中点,因此滚动窗口是:

  • 2006-01-012006-01-07 —标注为2006-01-04
  • 2006-01-022006-01-08 —标注为2006-01-05
  • 2006-01-032006-01-09 —标注为2006-01-06
  • 诸如此类…
 # Compute the centered 7-day rolling mean
opsd_7d = opsd_daily[data_columns].rolling(7, center=True).mean()
opsd_7d.head(10) 
消费 太阳的 风能+太阳能
日期
--- --- --- --- ---
2006-01-01 圆盘烤饼 圆盘烤饼 圆盘烤饼 圆盘烤饼
2006-01-02 圆盘烤饼 圆盘烤饼 圆盘烤饼 圆盘烤饼
2006-01-03 圆盘烤饼 圆盘烤饼 圆盘烤饼 圆盘烤饼
2006-01-04 1361.471429 圆盘烤饼 圆盘烤饼 圆盘烤饼
2006-01-05 1381.300143 圆盘烤饼 圆盘烤饼 圆盘烤饼
2006-01-06 1402.557571 圆盘烤饼 圆盘烤饼 圆盘烤饼
2006-01-07 1421.754429 圆盘烤饼 圆盘烤饼 圆盘烤饼
2006-01-08 1438.891429 圆盘烤饼 圆盘烤饼 圆盘烤饼
2006-01-09 1449.769857 圆盘烤饼 圆盘烤饼 圆盘烤饼
2006-01-10 1469.994857 圆盘烤饼 圆盘烤饼 圆盘烤饼

我们可以看到第一个非缺失滚动*均值在2006-01-04上,因为这是第一个滚动窗口的中点。

为了直观显示滚动*均值和重采样之间的差异,让我们更新我们之前的 2017 年 1 月至 6 月太阳能发电量图,以包括 7 天的滚动*均值以及每周*均重采样时间序列和原始每日数据。

 # Start and end of the date range to extract
start, end = '2017-01', '2017-06'
# Plot daily, weekly resampled, and 7-day rolling mean time series together
fig, ax = plt.subplots()
ax.plot(opsd_daily.loc[start:end, 'Solar'],
marker='.', linestyle='-', linewidth=0.5, label='Daily')
ax.plot(opsd_weekly_mean.loc[start:end, 'Solar'],
marker='o', markersize=8, linestyle='-', label='Weekly Mean Resample')
ax.plot(opsd_7d.loc[start:end, 'Solar'],
marker='.', linestyle='-', label='7-d Rolling Mean')
ax.set_ylabel('Solar Production (GWh)')
ax.legend(); 

time-series-pandas_78_0.png

我们可以看到,滚动*均时间序列中的数据点与每日数据具有相同的间距,但曲线更*滑,因为更高的频率可变性已被*均掉。在滚动*均时间序列中,波峰和波谷往往与每日时间序列的波峰和波谷紧密对齐。相比之下,每周重采样时间序列中的波峰和波谷与每日时间序列不太一致,因为重采样时间序列的粒度更粗。

趋势

除了较高频率的可变性,如季节性和噪声,时间序列数据通常表现出一些缓慢的、渐进的可变性。可视化这些趋势的一个简单方法是使用不同时间尺度的滚动方法。

滚动*均往往通过*均频率远高于窗口大小的变化和*均时间尺度等于窗口大小的季节性来*滑时间序列。这允许探索数据中较低频率的变化。由于我们的电力消费时间序列具有每周和每年的季节性,让我们看看这两个时间尺度上的滚动*均值。

我们已经计算了 7 天的滚动*均值,现在让我们来计算 OPSD 数据的 365 天的滚动*均值。

 # The min_periods=360 argument accounts for a few isolated missing days in the
# wind and solar production time series
opsd_365d = opsd_daily[data_columns].rolling(window=365, center=True, min_periods=360).mean() 

让我们绘制 7 天和 365 天的滚动*均用电量,以及每日时间序列。

 # Plot daily, 7-day rolling mean, and 365-day rolling mean time series
fig, ax = plt.subplots()
ax.plot(opsd_daily['Consumption'], marker='.', markersize=2, color='0.6',
linestyle='None', label='Daily')
ax.plot(opsd_7d['Consumption'], linewidth=2, label='7-d Rolling Mean')
ax.plot(opsd_365d['Consumption'], color='0.2', linewidth=3,
label='Trend (365-d Rolling Mean)')
# Set x-ticks to yearly interval and add legend and labels
ax.xaxis.set_major_locator(mdates.YearLocator())
ax.legend()
ax.set_xlabel('Year')
ax.set_ylabel('Consumption (GWh)')
ax.set_title('Trends in Electricity Consumption'); 

time-series-pandas_82_0.png

我们可以看到,7 天滚动*均消除了所有的每周季节性,同时保留了每年的季节性。7 天滚动*均值显示,虽然用电量通常在冬季较高,夏季较低,但在每个冬季的 12 月底和 1 月初的几个星期里,在假期期间,用电量会急剧下降。

观察 365 天的滚动*均时间序列,我们可以看到电力消费的长期趋势非常*缓,在 2009 年和 2012-2013 年左右有几个时期的消费量异常低。

现在让我们看看风能和太阳能生产的趋势。

 # Plot 365-day rolling mean time series of wind and solar power
fig, ax = plt.subplots()
for nm in ['Wind', 'Solar', 'Wind+Solar']:
    ax.plot(opsd_365d[nm], label=nm)
    # Set x-ticks to yearly interval, adjust y-axis limits, add legend and labels
    ax.xaxis.set_major_locator(mdates.YearLocator())
    ax.set_ylim(0, 400)
    ax.legend()
    ax.set_ylabel('Production (GWh)')
    ax.set_title('Trends in Electricity Production (365-d Rolling Means)'); 

time-series-pandas_84_0.png

随着德国继续扩大其在这些领域的产能,我们可以看到太阳能发电的小幅增长趋势和风力发电的大幅增长趋势。

总结和进一步阅读

我们已经学会了如何在 pandas 中使用基于时间的索引、重采样和滚动窗口等技术来争论、分析和可视化我们的时间序列数据。将这些技术应用于我们的 OPSD 数据集,我们获得了关于德国电力消费和生产的季节性、趋势和其他有趣特征的见解。

我们还没有涉及的其他潜在有用的主题包括时区处理和时移。如果你想了解更多关于在 pandas 中处理时间序列数据的信息,你可以查看 Python 数据科学手册的这一部分,这篇博文,当然还有官方文档。如果您对时间序列数据的预测和机器学习感兴趣,我们将在未来的博客文章中讨论这些主题,敬请关注!

如果你想了解更多关于这个话题的信息,请查看 Dataquest 的交互式 Pandas 和 NumPy Fundamentals 课程,以及我们的Python 数据分析师和Python 数据科学家路径,它们将帮助你在大约 6 个月内做好工作准备。

YouTube video player for 6a5jbnUNE2E

https://www.youtube.com/embed/6a5jbnUNE2E?rel=0

*提升您的数据技能。

查看计划*

教程:在 Python 中使用 If 语句

原文:https://www.dataquest.io/blog/tutorial-using-if-statements-in-python/

March 3, 2022

我们的生活充满了各种情况,即使我们大多数时候没有注意到它们。让我们看几个例子:

  • 如果明天不下雨,我会和朋友去公园。否则,我会呆在家里喝一杯热茶,看电视。
  • 如果明天天气不太热,我将去海边,但如果天气太热,我将去森林里散步。然而,如果下雨,我会呆在家里。

你明白了。让我们看看计算机中的条件是如何工作的。你可能已经知道 Python 中的程序是逐行执行的。然而,有时,我们需要跳过一些代码,只有在满足某些条件的情况下才执行其中的一部分。这就是控制结构变得有用的地方。Python 中的条件语句就是建立在这些控制结构之上的。它们将指导计算机执行程序。

在本教程中,您将学习如何使用条件语句。本指南是为 Python 初学者准备的,但是你需要了解一些 Python 编码的基础知识。如果你不知道,那就去看看这个免费的 Python 基础课程。

基本的 if 语句

在 Python 中,if语句是实现条件的起点。让我们看一个最简单的例子:

if <condition>:
    <expression>

当 Python 对<condition>求值时,它会变成TrueFalse(布尔值)。因此,如果条件为True(即满足条件),将执行<expression>,但是如果<condition>False(即不满足条件),将不执行<expression>

我们可以自由决定条件和表达式,因为 Python 非常灵活。

我们来看一个具体的例子。

# Basic if statement
x = 3
y = 10

if x < y:
    print("x is smaller than y.")
x is smaller than y.

首先我们定义两个变量,xy。然后我们说如果变量x小于变量y,打印出x is smaller than y。实际上,如果我们执行这段代码,我们会打印出这个输出,因为 3 小于 10。

输出:x is smaller than y.

让我们看一个更复杂的例子。

# A slightly more complex example
x = 3
y = 10
z = None

if x < y:
    z = 13
print(f"Variable z is now {z}.")
Variable z is now 13.

在这种情况下,如果满足条件,那么值 13 将被赋给变量z。然后Variable z is now 13.会被打印出来(注意print语句在if语句内外都可以使用)。

如您所见,我们在选择要执行的表达式时不受限制。您现在可以通过编写更复杂的代码来进行更多的练习。

让我们看看如果我们执行下面的代码会发生什么:

# What happens here?
x = 3
y = 10

if x > y:
    print("x is greater than y.")

这里我们改变了比较符号的方向(原来是小于,现在是大于)。你能猜出产量吗?

不会有输出!发生这种情况是因为条件没有得到满足。3 不大于 10,所以条件评估为False,表达式没有执行。我们如何解决这个问题?用else语句。

else 语句

如果我们想在条件不满足的情况下执行一些代码呢?我们在if语句下面添加一个else语句。让我们看一个例子。

# else statement
x = 3
y = 10

if x > y:
    print("x is greater than y.")
else:
    print("x is smaller than y.")
x is smaller than y.

输出:x is smaller than y.

这里,Python 首先执行 if 条件并检查它是否是True。因为 3 不大于 10,所以不满足条件,所以我们不打印出“x 大于 y”。然后我们说在所有其他情况下,我们应该执行 else 语句下的代码:x is smaller than y.

让我们回到条件语句的第一个例子:

如果明天不下雨,我会和朋友去公园。否则,我会呆在家里喝一杯热茶,看电视。

这里的 else 语句是“否则”

如果满足条件会怎么样?

# What if the condition is met?
x = 3
y = 10

if x < y:
    print("x is smaller than y.")
else:
    print("x is greater than y.")
x is smaller than y.

在这种情况下,Python 只是像以前一样打印出第一句话。

输出:x is smaller than y.

如果x等于y呢?

# x is equal to y
x = 3
y = 3

if x < y:
    print("x is smaller than y.")
else:
    print("x is greater than y.")
x is greater than y.

输出明显是错误的,因为 3 等于 3!在大于或小于比较符号之外,我们还有另一个条件;因此,我们必须使用elif语句。

elif 声明

让我们重写上面的例子,添加一个elif语句。

# x is equal to y with elif statement
x = 3
y = 3

if x < y:
    print("x is smaller than y.")
elif x == y:
    print("x is equal to y.")
else:
    print("x is greater than y.")
x is equal to y.

输出:x is equal to y

Python 首先检查条件x < y是否满足。它不是,所以它继续到第二个条件,在 Python 中,我们写为elif,这是 else if 的缩写。如果第一个条件不满足,检查第二个条件,如果满足,执行表达式。别的,干点别的。输出是“x 等于 y。”

现在让我们回到条件语句的第一个例子:

如果明天天气不太热,我将去海边,但如果天气太热,我将去森林里散步。然而,如果下雨,我会呆在家里。

在这里,我们的首要条件是明天天气不要太热(if语句)。如果这个条件不满足,那么我们去森林里散步(elif语句)。最后,如果两个条件都不满足,我们就呆在家里(else语句)。

现在我们把这句话翻译成 Python。

在这个例子中,我们将使用字符串而不是整数来演示 Python 中的if条件的灵活性。

# elif condition
tomorrow = "warm"

if tomorrow == "warm":
    print("I'll go to the sea.")
elif tomorrow == "very hot":
    print("I'll go to the forest.")
else:
    print("I'll stay home.")
I'll go to the sea.

Python 首先检查变量tomorrow是否等于“warm ”,如果是,则打印出I'll go to the sea.,并停止执行。如果第一个条件没有满足会怎么样?

# Tomorrow is very hot
tomorrow = "very hot"

if tomorrow == "warm":
    print("I'll go to the sea.")
elif tomorrow == "very hot":
    print("I'll go to the forest.")
else:
    print("I'll stay home.")
I'll go to the forest.

在这种情况下,Python 将第一个条件评估为False,并转到第二个条件。这个条件是True,所以打印出I'll go to the forest.并停止执行。

如果两个条件都不满足,那么它将打印出I’ll stay home.

当然,您可以使用任意数量的elif语句。让我们添加更多的条件,并将else语句下输出的内容改为Weather not recognized.(例如,如果明天是“f”,我们不知道它的含义)。

# Several elif conditions
tomorrow = "snowy"

if tomorrow == "warm":
    print("I'll go to the sea.")
elif tomorrow == "very hot":
    print("I'll go to the forest.")
elif tomorrow == "snowy":
    print("I'll build a snowman.")
elif tomorrow == "rainy":
    print("I'll stay home.")
else:
    print("Weather not recognized.")
I'll build a snowman.

猜猜打印出来的是什么?

多重条件

现在让我们增加一些复杂性。如果我们想让在一个if语句中满足多个条件呢?

假设我们想要基于两个气候测量值来预测一个生物群落(即沙漠或热带森林):温度和湿度。例如,如果天气炎热干燥,那么它是一个炎热的沙漠,但如果天气寒冷干燥,那么它是一个北极沙漠。你可以看到,我们不能只根据湿度对这两个生物群落进行分类(它们都是干燥的),所以我们还必须添加温度测量。

在 Python 中,我们可以使用逻辑运算符(即 and、or)在同一个if语句中使用多个条件。

看看下面的代码。

# Biome prediction with and logical operator
humidity = "low"
temperature = "high"

if humidity == "low" and temperature == "high":
    print("It's a hot desert.")
elif humidity == "low" and temperature == "low":
    print("It's an arctic desert.")
elif humidity == "high" and temperature == "high":
    print("It's a tropical forest.")
else:
    print("I don't know!")
It's a hot desert.

输出将是It's a hot desert.,因为只有当湿度低而温度高时,组合条件才是True。仅仅有一个条件成为True是不够的。

形式上,Python 检查第一个条件湿度是否是True(确实如此),然后检查第二个条件温度是否是True(确实如此),只有在这种情况下组合条件才是True。如果不满足这些条件中的至少一个,那么组合条件评估为False

如果我们希望两个(或更多)条件中的任何一个得到满足呢?在这种情况下,我们应该使用or逻辑运算符。

让我们看一个例子。假设您有一个从 1 到 14(包括 1 和 14)的数字列表,您想要提取所有小于 3 或大于或等于 10 的数字。您可以使用一个or操作符来获得结果!

# or logical operator
nums = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14]
nums_less_3_greater_equal_10 = []

for num in nums:
    if num < 3 or num >= 10:
        nums_less_3_greater_equal_10.append(num)

print(nums_less_3_greater_equal_10)
[1, 2, 10, 11, 12, 13, 14]

输出:[1, 2, 10, 11, 12, 13, 14]

这里 Python 检查了for循环中的当前数是否小于 3,如果是True,那么组合的if语句的计算结果为True。如果当前数字等于或大于 10,也会发生同样的情况。如果组合的if语句是True,那么表达式被执行,当前数字被附加到列表nums_less_3_greater_equal_10

为了实验方便,我们把or改成and

# Change or to and
nums = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14]
nums_less_3_greater_equal_10 = []

for num in nums:
    if num < 3 and num >= 10:
        nums_less_3_greater_equal_10.append(num)

print(nums_less_3_greater_equal_10)
[]

输出:[]

在这种情况下,当前数字应该同时小于 3 并且大于或等于 10,这显然是不可能的,因此组合的if语句计算为False,并且不执行表达式。

为了让事情更清楚,看看这个print声明。

print(False or True)
True

输出:True

这里 Python 计算了FalseTrue的组合,因为我们有了or逻辑操作符,所以这些布尔中至少有一个是True就足够了,可以计算组合到True的语句。

现在,如果我们把or改成and会发生什么?

print(False and True)
False

输出:False

两个布尔值都应该是True,以将组合条件求值为True。既然其中一个是False,那么组合条件也是False。这就是数字示例中发生的情况。

您甚至可以在一个表达式中组合多个逻辑运算符。让我们使用相同的数字列表,但是现在,我们想要找到所有小于 3 或大于或等于 10 并且同时是偶数的数字。

我们将使用%操作符来判断数字是否是偶数。表达式number % another_number将得出number除以another_number的余数。如果我们想知道一个数是否是偶数,那么这个数除以 2 的余数应该是 0。

# More complex logical statements
nums = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14]
nums_less_3_greater_equal_10_multiple_2 = []

for num in nums:
    if (num < 3 or num >= 10) and num % 2 == 0:
        nums_less_3_greater_equal_10_multiple_2.append(num)

print(nums_less_3_greater_equal_10_multiple_2)
[2, 10, 12, 14]

输出:[2, 10, 12, 14]

为什么输出的第一个数字是 2?在第二个for循环中,在括号中的第一个条件中计算 2。小于 3,所以括号里的组合条件是True。2 也能被余数为 0 的 2 整除,所以第二个条件也是True。两个条件都是True,所以这个数字被追加到列表中。

我们为什么要用括号?是因为 Python 中的运算符优先级。如果我们移除它们呢?

# More complex logical statements without parentheses
nums = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14]
nums_less_3_greater_equal_10_multiple_2 = []

for num in nums:
    if num < 3 or num >= 10 and num % 2 == 0:
        nums_less_3_greater_equal_10_multiple_2.append(num)

print(nums_less_3_greater_equal_10_multiple_2)
[1, 2, 10, 12, 14]

输出:[1, 2, 10, 12, 14]

我们在列表中有 1 个!在 Python 中,所有操作符都按照精确的顺序进行计算。例如,and操作符优先于or操作符。但是如果我们将or操作符放在括号中,它将优先于and操作符。

首先我们评估and操作符两边的条件(它有优先权)。1 既不大于 10,除以 2 也不会得到 0,所以组合条件是False。我们只剩下条件if num < 3 or False。1 小于 3,所以第一个条件是True。条件变成了True or False。我们有一个or操作符,所以组合条件的计算结果是True,1 被追加到列表中。通过检查其他数字发生了什么来练习。

最后,看看这个真值表来理解逻辑运算符是如何工作的。这里,我们将只描述andor逻辑操作符,但是在 Python 中,我们也有not操作符。我们邀请您更多地了解它,并在if语句中练习使用它。

输入 A 输入 B 运筹学
错误的 错误的 错误的 错误的
真实的 错误的 错误的 真实的
错误的 真实的 错误的 真实的
真实的 真实的 真实的 真实的

我们有两个输入,A 和 B,可以是TrueFalse。比如第二排,A 是True,B 是False;因此,A AND B评估为False,而A OR B评估为True。表格的其余部分以同样的方式阅读。花一分钟去理解它告诉你什么。

嵌套的 if 语句

Python 是一种非常灵活的编程语言,它允许你在其他 if 语句中使用 if 语句,即所谓的嵌套if语句。让我们看一个例子。

# Nested if statements
mark =  85

if mark >= 60 and mark <= 100:
    if mark >= 90:
        print("You are the best!")
    elif mark >= 80:
        print("Well done!")
    elif mark >= 70:
        print("You can do better.")
    else:
        print("Pass.")
elif mark > 100:
    print("This mark is too high.")
elif mark < 0:
    print("This mark is too low.")
else:
    print("Failed.")
Well done!

输出:Well done!

这里,如果标记在 60 和 100 之间,则执行if语句下的表达式。但是我们还有其他条件也要评估。所以,我们的分数是 85,介于 60 和 100 之间。但是,85 小于 90,所以第一个嵌套的if条件是False,并且第一个嵌套的表达式没有被执行。但是 85 高于 80,所以执行第二个表达式并“做得好!”是打印出来的。

当然,在第一个if语句下面的表达式之外,我们还有elif语句。例如,什么if的分数高于 100 分?如果第一个条件(60 到 100 之间的数字)是False,那么我们直接进入elif语句mark > 100并打印出This mark is too low.

试着给mark变量分配不同的数字来理解这段代码的逻辑。

Python 3.10 中的模式匹配

2021 年 10 月发布的 Python 3.10 中加入了模式匹配。简而言之,可以看出if..elif语句的不同语法。让我们看一个例子,用模式匹配重写前面的例子。

# Previous example
tomorrow = "snowy"

if tomorrow == "warm":
    print("I'll go to the sea.")
elif tomorrow == "very hot":
    print("I'll go to the forest.")
elif tomorrow == "snowy":
    print("I'll build a snowman.")
elif tomorrow == "rainy":
    print("I'll stay home.")
else:
    print("Weather not recognized.")
I'll build a snowman.
# Pattern matching with match..case syntax
tomorrow = "snowy"

match tomorrow:
    case "warm":
        print("I'll go to the sea.")
    case "very hot":
        print("I'll go to the forest.")
    case "snowy":
        print("I'll build a snowman.")
    case "rainy":
         print("I'll stay home.")
    case _:
        print("Weather not recognized.")
I'll build a snowman.

我们可以看到使用if..elif语句和match..case语法之间的相似之处。首先,我们定义什么变量是我们想要的match,以及我们何时定义用例(或者这个变量可以取的值)。代码的其余部分是类似的。如果匹配了一个案例(相当于一个双等号),那么执行print表达式。

注意最后一个case语句,是_案例,相当于else:如果没有匹配到案例,那么我们print Weather not recognized

通过语句

当您开始编写更复杂的代码时,您可能会发现自己不得不使用一个占位符来代替您稍后想要实现的代码。pass语句就是这个占位符。让我们看一个有和没有pass语句的例子。

# Without pass
num = 3
if num == 3:

print("I'll write this code later.")
 Input In [24]
    print("I'll write this code later.")
    ^
IndentationError: expected an indented block after 'if' statement on line 3

输出:

File "<ipython-input-26-4af22b0ed55d>", line 4
    print("I'll write this code later.")
    ^
IndentationError: expected an indented block

Python 需要一些“if”语句下的代码,但是你还没有实现它!你可以在那里写“通过”来解决这个问题。

# With pass
num = 3
if num == 3:
    pass

print("I'll write this code later.")
I'll write this code later.

输出:`我以后再写这段代码. '

相反,如果你在“If”语句中放置“pass ”, Python 不会抛出任何错误,而是传递给“if”语句下的任何代码。即使在第一个“if”语句下面有其他条件,这也是有效的。

# With pass
num = 4
if num == 3:
    pass
elif num == 4:
    print("The variable num is 4.")
else:
    print("The variable num is neither 3 nor 4.")
The variable num is 4.

输出:The variable num is 4.

结论

在 Python 中,if语句无时无刻不在使用,你会发现自己在构建的任何项目或脚本中都在使用它们,所以理解它们背后的逻辑是很重要的。在本文中,我们已经介绍了 Python 中if条件最重要的方面:

  • 创建基本的if语句
  • 通过使用elseelif语句增加复杂性
  • 使用逻辑运算符(orand)在一个if语句中组合多个条件
  • 使用嵌套的if语句
  • 使用pass语句作为占位符

有了这些知识,您现在可以开始使用 Python 中的条件语句了。

请随时在 LinkedIn 和 GitHub 上联系我。编码快乐!

教程:理解 Python 中的回归误差度量

原文:https://www.dataquest.io/blog/understanding-regression-error-metrics/

September 26, 2018

人类大脑的构造是为了识别我们周围世界的模式。例如,我们观察到,如果我们每天练习编程,我们的相关技能就会提高。但是我们如何准确地向他人描述这种关系呢?我们如何描述这种关系有多强?幸运的是,我们可以用被称为回归的正式数学估计来描述现象之间的关系,比如实践和技能。

回归是数据科学家工具箱中最常用的工具之一。当你学习 Python 或 R 时,你会获得用单行代码创建回归的能力,而不必处理底层的数学理论。但是这种轻松会导致我们忘记评估我们的回归,以确保它们足够充分地代表我们的数据。我们可以将数据插回到回归方程中,看看预测的输出是否与数据中看到的相应观察值相匹配。

回归模型的质量是其预测与实际值的匹配程度,但是我们实际上如何评估质量呢?幸运的是,聪明的统计学家已经开发了误差度量来判断模型的质量,并使我们能够将回归与其他具有不同参数的回归进行比较。这些指标是对我们数据质量的简短而有用的总结。本文将深入探讨四个常见的回归指标,并讨论它们的用例。回归有很多种类型,但是本文将专门关注与线性回归相关的度量。

线性回归是研究和商业中最常用的模型,也是最容易理解的,所以开始发展你对如何评估它们的直觉是有意义的。我们将在这里讨论的许多度量背后的直觉扩展到其他类型的模型和它们各自的度量。如果你想快速复习一下线性回归,你可以参考这篇精彩的博客文章或者线性回归维基页面。

线性回归入门

在回归的上下文中,模型是指用于描述两个变量之间关系的数学方程。一般来说,这些模型处理我们的数据输出中感兴趣的值的预测估计。模型将查看我们认为会影响输出的被称为输入的数据的其他方面,并使用它们来生成估计输出。

这些输入和输出有许多您可能以前听过的名字。输入也可以称为自变量或预测值,而输出也称为响应或因变量。简单地说,模型就是输出是输入的某个函数的函数。线性回归的线性部分指的是这样一个事实,一个线性回归模型以如下形式进行数学描述:Linear Regression Anatomy如果这看起来太数学化,那么线性思维是特别直观的。如果你听说过“熟能生巧”,那么你知道更多的练习意味着更好的技能;实践和完美之间有某种线性关系。线性回归的回归部分并不是指某种程度上回到更差的状态。回归在这里简单地指估计我们的输入和输出之间的关系的行为。特别是,回归处理与离散状态(想一想:类别)相对的连续值(想一想:数字)的建模。

综上所述,线性回归创建了一个假设输入和输出之间存在线性关系的模型。输入越高,输出也越高(或者更低,如果关系是负的话)。调整这种关系有多强的和这种关系的方向的是我们的系数。第一个没有输入的系数被称为截距,当所有输入都为 0 时,它调整模型预测的内容。我们将不深究如何计算这些系数,但知道存在一种方法来计算最优系数,给定我们想要用来预测输出的输入。

给定系数,如果我们插入输入值,线性回归将给我们一个输出值的估计值。正如我们将看到的,这些输出不会总是完美的。除非我们的数据是一条完美的直线,否则我们的模型不会精确地触及所有的数据点。其中一个原因是ϵ(名为“ε”)项。这个术语代表来自我们控制之外的来源的误差,导致数据稍微偏离它们的真实位置。我们的误差度量将能够判断预测值和实际值之间的差异,但是我们无法知道误差在多大程度上造成了差异。虽然我们不能完全消除ε,但在线性模型中保留一个术语是有用的。

将模型预测与现实进行比较

由于给定任何输入或一组输入,我们的模型都会产生一个输出,因此我们可以对照我们试图预测的实际值来检查这些估计的输出。我们将实际值和模型估计值之间的差异称为残差。我们可以计算数据集中每个点的残差,这些残差中的每一个都将在评估中有用。这些残差将在判断模型的有用性方面发挥重要作用。

如果我们的残差集合很小,这意味着产生它们的模型在预测我们感兴趣的输出方面做得很好。相反,如果这些残差通常很大,这意味着模型是一个很差的估计量。从技术上来说,我们可以检查所有的残差来判断模型的准确性,但不出所料,如果我们有数千或数百万个数据点,这是不可行的。因此,统计学家开发了汇总测量,将我们收集的残差压缩成一个代表我们模型预测能力的单个值。有许多这样的汇总统计数据,每一个都有自己的优点和缺陷。对于每一个,我们将讨论每个统计数据代表什么,它们的直觉和典型用例。我们将涵盖:

  • 绝对*均误差
  • 均方误差
  • *均绝对百分比误差
  • *均百分比误差

注意:即使你在这里看到了单词 error,它也不是指上面的 epsilon 项!这些指标中描述的误差指的是残差

扎根于真实数据

在讨论这些误差指标时,很容易被用来描述它们的各种缩略语和方程式所困扰。为了让我们自己脚踏实地,我们将使用我用 Kaggle 的视频游戏销售数据集创建的模型。我创建的模型的细节如下所示。Imgur我的回归模型有两个输入(评论家得分和用户得分),所以它是一个多变量线性回归。模型接受了我的数据,发现 0.039 和-0.099 是输入的最佳系数。

在我的模型中,我选择截距为零,因为我想假设分数为零时销售额为零。因此,截距项被划掉了。最后,误差项被划掉,因为我们不知道它在实际中的真实值。我展示它是因为它更详细地描述了线性回归方程中编码的信息。

模型背后的基本原理

假设我是一个游戏开发者,刚刚创作了一个新游戏,我想知道我会赚多少钱。我不想等待,所以我开发了一个模型,根据专家评论家对游戏的判断和一般玩家的判断(我的输入),预测全球总销量(我的输出)。如果评论家和玩家都热爱这款游戏,那么我应该赚更多的钱…对吗?当我真的得到评论家和用户对我游戏的评论时,我可以预测我会赚多少钱。目前,我不知道我的模型是否准确,所以我需要计算我的误差指标,以检查我是否应该包括更多的输入,或者我的模型是否有任何好处!

绝对*均误差

*均绝对误差 (MAE)是最容易理解的回归误差指标。我们将计算每个数据点的残差,只取每个数据点的绝对值,这样正负残差就不会相互抵消。然后我们取所有这些残差的*均值。实际上,MAE 描述了残差的典型大小。如果你对*均数不熟悉,你可以回头参考这篇关于描述统计学的文章。形式方程如下:MAE Equation下图是对 MAE 的图形化描述。绿线代表我们模型的预测,蓝点代表我们的数据。MAE

MAE 也是最直观的指标,因为我们只是查看数据和模型预测之间的绝对差异。因为我们使用残差的绝对值,MAE 并不表示模型的表现不佳表现过度(无论模型是否低于或超过实际数据)。每个残差对误差总量的贡献是成比例的,这意味着较大的误差对总误差的贡献是线性的。就像我们上面说过的,一个小的 MAE 表明模型在预测方面很棒,而一个大的 MAE 表明你的模型在某些方面可能有问题。MAE 为 0 意味着你的模型是输出的完美预测器(但这几乎不会发生)。

虽然 MAE 很容易解释,但使用残差的绝对值通常不如*方这个差值更可取。根据您希望您的模型如何处理数据中的异常值或极值,您可能希望更多地关注这些异常值或淡化它们。离群值的问题在您使用的误差度量中起着重要的作用。

根据我们的模型计算 MAE

在 Python 中计算 MAE 相对简单。在下面的代码中,sales包含所有销售数字的列表,X包含大小为 2 的元组列表。每个元组包含与同一索引中的销售相对应的评论家分数和用户分数。lm包含一个来自 scikit-learn 的LinearRegression对象,我用它来创建模型本身。这个对象也包含系数。predict方法接受输入,并基于这些输入给出实际预测。

# Perform the intial fitting to get the LinearRegression object
from sklearn import linear_model
lm = linear_model.LinearRegression()
lm.fit(X, sales)

mae_sum = 0
for sale, x in zip(sales, X):
    prediction = lm.predict(x)
    mae_sum += abs(sale - prediction)
mae = mae_sum / len(sales)

print(mae)
>>> [ 0.7602603 ]

我们模型的 MAE 是 0.760,考虑到我们数据的销售范围从 0.01 到大约 83(以百万计),这是相当小的。

均方误差

均方误差 (MSE)就像 MAE 一样,但是在将它们全部相加之前对差进行*方,而不是使用绝对值。我们可以在下面的等式中看到这种差异。MSE Equation

*方项的结果

因为我们在求差值的*方,所以 MSE 几乎总是比 MAE 大。由于这个原因,我们不能直接比较*均寿命和*均寿命。我们只能将我们的模型的误差指标与竞争模型的误差指标进行比较。MSE 方程中*方项的影响在我们的数据中存在异常值时最为明显。虽然 MAE 中的每个残差对总误差成比例地贡献,但误差以 MSE 的形式二次增长。这最终意味着我们数据中的异常值将导致 MSE 中比 MAE 中更高的总误差。同样,如果我们的模型做出的预测与相应的实际值相差很大,它将受到更多的惩罚。这就是说,实际和预测之间的巨大差异在 MSE 中比在 MAE 中受到更多的惩罚。下图形象地展示了 MSE 中的单个残差可能的样子。离群值将产生这些指数级的更大差异,我们的工作就是判断我们应该如何接*它们。****

****## 离群值问题

对于试图创建模型的数据科学家来说,数据中的异常值是一个持续的讨论来源。我们是在模型创建中包含离群值,还是忽略它们?这个问题的答案取决于研究领域、手头的数据集以及一开始就出现错误的后果。例如,我知道一些视频游戏获得了超级明星的地位,因此有着不成比例的高收入。因此,忽视这些离群游戏是愚蠢的,因为它们代表了数据集中的真实现象。我想使用 MSE 来确保我的模型更多地考虑这些异常值。

如果我想淡化它们的重要性,我会使用 MAE,因为异常残差对总误差的贡献不会像 MSE 那样大。最终,在 MSE 和 MAE 之间的选择是特定于应用程序的,取决于您希望如何处理大的错误。两者都仍然是可行的误差度量,但将描述模型预测误差的不同细微差别。

关于 MSE 和*亲的一个注记

你可能遇到的另一个误差指标是均方根误差 (RMSE)。顾名思义,就是 MSE 的*方根。因为 MSE 是*方的,所以它的单位与原始输出的单位不匹配。研究人员经常使用 RMSE 将误差度量转换回类似的单位,使解释更容易。由于均方误差和 RMSE 都是残差的*方,它们同样会受到异常值的影响。RMSE 类似于标准差(MSE 对方差),是对残差展开程度的一种度量。MAE 和 MSE 的范围都可以从 0 到正无穷大,所以随着这两个度量变得更高,解释模型的表现就变得更难了。我们可以总结我们的残差集合的另一种方法是使用百分比,以便每个预测都与它应该估计的值成比例。

根据我们的模型计算 MSE

像 MAE 一样,我们将计算模型的 MSE。谢天谢地,计算就像 MAE 一样简单。

mse_sum = 0
for sale, x in zip(sales, X):
    prediction = lm.predict(x)
    mse_sum += (sale - prediction)**2
mse = mse_sum / len(sales)

print(mse)
>>> [ 3.53926581 ]

对于 MSE,由于异常值的影响,我们预计它会比 MAE 大得多。我们发现情况是这样的:MSE 比 MAE 高一个数量级。相应的 RMSE 约为 1.88,表明我们的模型与实际销售额相差约 180 万美元

*均绝对百分比误差

*均绝对百分比误差 (MAPE)是 MAE 的百分比当量。这个等式看起来就像 MAE 的等式,但是做了一些调整,将所有内容转换为百分比。MAPE Equation正如 MAE 是模型产生的*均误差大小一样,MAPE 是模型的预测与相应输出的*均偏差程度。像梅一样,MAPE 也有一个清晰的解释,因为人们更容易将百分比概念化。由于绝对值的使用,MAPE 和 MAE 对异常值的影响都是稳健的。MAPE

然而,尽管 MAPE 有很多优点,但我们在使用它时比 MAE 更受限制。MAPE 的许多弱点实际上源于使用除法运算。现在,我们必须用实际值来缩放一切,对于值为 0 的数据点,MAPE 是未定义的。同样,如果实际值本身非常小,MAPE 可能会变得出乎意料地大。最后,MAPE 偏向于系统性地小于实际值的预测。也就是说,与高出相同数量的预测相比,当预测低于实际时,MAPE 将会更低。下面的快速计算证明了这一点。MAPE Bad

我们有一个类似于 MAPE 的度量标准,即*均百分比误差。虽然 MAPE 中的绝对值消除了任何负值,但*均百分比误差将正负误差纳入其计算中。

根据我们的模型计算 MAPE

mape_sum = 0
for sale, x in zip(sales, X):
    prediction = lm.predict(x)
    mape_sum += (abs((sale - prediction))/sale)
mape = mape_sum/len(sales)

print(mape)
>>> [ 5.68377867 ]

我们肯定知道没有零销售额的数据点,所以我们使用 MAPE 是安全的。记住,我们必须用百分点来解释它。MAPE 表示,我们模型的预测值*均与实际值相差 5.6%。

*均百分比误差

*均百分比误差(MPE)方程与 MAPE 方程完全一样。唯一不同的是它缺少绝对值运算。

MPE Equation

尽管 MPE 缺少绝对值运算,但实际上正是由于缺少绝对值运算,MPE 才变得有用。由于正负误差会相互抵消,我们无法对模型预测的总体表现做出任何陈述。然而,如果有更多的负或正误差,这种偏差将在 MPE 中出现。与梅和 MAPE 不同,MPE 对我们很有用,因为它让我们可以看到我们的模型是否系统地低估了(更大的负误差)或高估了(正误差)。MPE

如果你要使用相对误差度量标准如 MAPE 或 MPE,而不是绝对误差度量标准如 MAE 或 MSE,你最有可能使用 MAPE。MAPE 具有易于解释的优势,但您必须警惕会对计算产生不利影响的数据(即零)。你不能像 MAPE 那样使用 MPE,但是它可以告诉你你的模型所产生的系统误差。

根据我们的模型计算 MPE

mpe_sum = 0
for sale, x in zip(sales, X):
    prediction = lm.predict(x)
    mpe_sum += ((sale - prediction)/sale)
mpe = mpe_sum/len(sales)

print(mpe)
>>> [-4.77081497]

所有其他误差指标都向我们表明,一般来说,该模型在根据评论家和用户评分预测销售方面做得不错。然而,MPE 向我们表明,它实际上系统地低估了销售额。了解我们模型的这一方面对我们很有帮助,因为它允许我们回顾数据并重申哪些输入可以改进我们的指标。总的来说,我会说我预测销售的假设是一个好的开始。误差度量揭示了否则会不清楚或看不到的趋势。

结论

我们已经用四个汇总统计数据覆盖了很多领域,但是正确地记住它们可能会令人困惑。下表将给出缩略语及其基本特征的快速总结。

首字母缩略词 全名 残留操作? 对异常值稳健?
*均绝对误差 绝对*均误差 绝对值
均方误差(mean square error) 均方误差 *方
均方根误差 均方根误差 *方
multidimensional assessment of philosophy of education 教育哲学的多维评价 *均绝对百分比误差 绝对值
MasterofPhysicalEducation 体育硕士 *均百分比误差 不适用的

所有上述措施直接处理我们的模型产生的残差。对于它们中的每一个,我们使用度量的大小来决定模型是否表现良好。小的误差度量值表明良好的预测能力,而大的值则相反。也就是说,在选择要呈现的指标时,考虑数据集的性质很重要。异常值可能会改变您的度量选择,这取决于您是否愿意赋予它们在总误差中更大的重要性。一些字段可能更容易出现异常值,而另一些字段可能不太容易出现异常值。

然而,在任何领域,清楚地知道哪些指标对你有用总是很重要的。我们已经介绍了一些最常用的误差指标,但是还有其他一些指标也很有用。我们讨论的指标使用残差的*均值,但是中值残差也有用处。当您学习其他类型的数据模型时,请记住我们在度量标准背后开发的直觉,并根据需要应用它们。

更多资源

如果你想更深入地探索线性回归,Dataquest 提供了一个关于它的使用和应用的极好的课程!我们在本文中使用了scikit-learn来应用错误度量,因此您可以阅读文档来更好地了解如何使用它们!

  • Dataquest 的线性回归课程
  • sci kit-学习和回归误差指标
  • Scikit-learn 关于线性回归对象的文档
  • 线性回归对象的使用示例

用正确的方法学习 Python。

从第一天开始,就在你的浏览器窗口中通过编写 Python 代码来学习 Python。这是学习 Python 的最佳方式——亲自看看我们 60 多门免费课程中的一门。

astronaut floating over code

尝试 Dataquest****

Python 单元测试初学者指南(2022)

原文:https://www.dataquest.io/blog/unit-tests-python/

October 6, 2022unit-tests-python

单元测试是为了测试其他代码而编写的代码段,通常是一个函数或方法,我们称之为单元。它们是软件开发过程中非常重要的一部分,因为它们有助于确保代码按预期工作,并在早期捕捉到错误。此外,测试是一种最佳实践,通过在问题导致重大问题之前发现并修复问题,可以节省时间和金钱。

在本文中,我们将介绍用 Python 编写单元测试,从理解assert语句到使用专门为此类任务设计的框架——并遵循 Python 单元测试的最佳实践。

Python 有两个主要框架来简化单元测试:unittestPyTest。第一个是从 Python 2.1 开始的 Python 标准库的一部分,也是我们在本文中关注的一个。

按照单元测试教程,您不需要任何高级知识,但是我们希望您对 Python 函数和类的工作原理有一个基本的了解。

assert声明

assert语句是 Python 中的内置语句,顾名思义,用于断言给定条件是否为真。如果条件为真,什么都不会发生,但是如果条件不为真,就会产生一个错误。尽管最初看起来像是tryexcept子句,但它们是完全不同的,并且assert不应该用于错误处理,而是用于调试和测试。

例如,下面一行中的条件为真,因此它不输出或返回任何内容:

assert 1 > 0

然而,如果我们改变这个条件,使它成为假,我们得到一个AssertionError:

assert 1 < 0
---------------------------------------------------------------------------
AssertionError                            Traceback (most recent call last)
<ipython-input-2-2d19dbe67b58> in <module>
----> 1 assert 1 < 0

AssertionError: 

注意,在错误消息的最后一行中,在AssertionError:之后没有实际的消息。那是因为用户应该传递这个消息。方法如下:

n = 0
assert 1 < n, 'The Condition is False'
---------------------------------------------------------------------------
AssertionError                            Traceback (most recent call last)
<ipython-input-3-e335e3eb84ff> in <module>
      1 n = 0
----> 2 assert 1 < n, 'The Condition is False'

AssertionError: The Condition is False

因此,使用assert的基本语法如下:

assert <condition being tested>, <error message to be displayed>

assert使用起来非常简单。理解它对于测试来说是至关重要的,我们将在下面的章节中看到。

unittest模块

unittest模块是一个框架,旨在让我们测试代码的工作变得更容易。该模块基于一些重要的面向对象概念工作,这就是为什么您需要理解 Python 中的类和方法的基础。

一个测试用例被认为是一个单一的测试单元,它由TestCase类来表示。在unittest提供的众多允许我们测试代码的工具中,这个类是最重要的工具之一。它被用作基类来创建我们自己的测试用例,使我们能够一次运行多个测试。

虽然我们在上一节已经看到了 Python assert语句的重要性,但是这里不会用到它。这是因为TestCase类也提供了几个自己的断言方法,它们的工作方式就像assert语句一样,只是针对特定类型的断言。

例如,assertEqual获取两个元素并测试它们是否相等,而assertNotEqual测试元素是否不同。同样,assertTrue方法接受一个元素并测试它是否为真,而assertFalse测试它是否为假。

下面是官方文档提供的TestCase类中最常用的断言方法列表:

方法 检查一下
assertEqual(a, b) a == b
assertNotEqual(a, b) a != b
assertTrue(x) bool(x) is True
assertFalse(x) bool(x) is False
assertIs(a, b) a is b
assertIsNot(a, b) a is not b
assertIsNone(x) x is None
assertIsNotNone(x) x is not None
assertIn(a, b) a in b
assertNotIn(a, b) a not in b
assertIsInstance(a, b) isinstance(a, b)
assertNotIsInstance(a, b) not isinstance(a, b)

很重要的一点是,TestCase类中的所有 assert 方法也接受一个msg参数,该参数在测试失败时用作错误消息。

实现单元测试

所以让我们实现一组简单的单元测试。首先,我们需要有一些代码来测试。为此,让我们考虑下面的Calculations类,它位于tests目录下的my_calculations.py文件中:

# project/code/my_calculations.py

class Calculations:
    def __init__(self, a, b):
        self.a = a
        self.b = b

    def get_sum(self):
        return self.a + self.b

    def get_difference(self):
        return self.a - self.b

    def get_product(self):
        return self.a * self.b

    def get_quotient(self):
        return self.a / self.b

这是一个非常简单的类,接受两个数字,有四个方法将第一个数字与第二个数字相加、相减、相乘和相除,并返回结果。

所以现在我们想测试这个类中的方法。为此,我们需要基于TestCase类创建一个类,这个类将包含执行测试的方法。

假设我们有以下文件夹结构:

project/
│
├── code/
│   ├── __initII.py
│   └── my_calculations
│
└── tests.py
# project/test.py

import unittest
from code.my_calculations import Calculations

class TestCalculations(unittest.TestCase):

    def test_sum(self):
        calculation = Calculations(8, 2)
        self.assertEqual(calculation.get_sum(), 10, 'The sum is wrong.')

if __name__ == '__main__':
    unittest.main()

上面的代码测试了Calculations类的get_sum方法。为此,我们必须做到以下几点:

  1. 导入unittestsCalculations
  2. 实例化一个对象如果Calculations
  3. 创建TestCalculations类和其中的test_sum方法

注意,我们使用assertEqual来断言get_sum的输出是否等于 10。我们还为失败的情况设置了消息。最后,当我们运行这个脚本时,unittest.main()运行测试。这是我们得到的输出:

.
----------------------------------------------------------------------
Ran 1 test in 0.000s
OK

例如,如果我们将期望值从 10 更改为 11,测试将会失败,并且我们会得到以下输出:

----------------------------------------------------------------------
Traceback (most recent call last):
  File ".\my_test.py", line 9, in test_sum
    self.assertEqual(calculation.get_sum(), 11, 'The sum is wrong.')
AssertionError: 10 != 11 : The sum is wrong.
----------------------------------------------------------------------
Ran 1 test in 0.001s

注意到The sum is wrong.消息如预期的那样出现了。

按照同样的逻辑,我们有下面的代码来测试Calculations类中的所有四个方法:

import unittest
from code.my_calculations import Calculations

class TestCalculations(unittest.TestCase):

    def test_sum(self):
        calculation = Calculations(8, 2)
        self.assertEqual(calculation.get_sum(), 10, 'The sum is wrong.')

    def test_diff(self):
        calculation = Calculations(8, 2)
        self.assertEqual(calculation.get_difference(), 6, 'The difference is wrong.')

    def test_product(self):
        calculation = Calculations(8, 2)
        self.assertEqual(calculation.get_product(), 16, 'The product is wrong.')

    def test_quotient(self):
        calculation = Calculations(8, 2)
        self.assertEqual(calculation.get_quotient(), 4, 'The quotient is wrong.')

if __name__ == '__main__':
    unittest.main()

所有的测试都运行了:

....
----------------------------------------------------------------------
Ran 4 tests in 0.001s
OK

顺便说一下,所有方法的名称都以单词test开头并不是偶然的。这是我们使用的惯例,以便unittest能够识别它应该运行的测试。例如,下面的代码只运行三个测试:

import unittest
from code.my_calculations import Calculations

class TestCalculations(unittest.TestCase):

    def not_a_test_sum(self):
        calculation = Calculations(8, 2)
        self.assertEqual(calculation.get_sum(), 10, 'The sum is wrong.')

    def test_diff(self):
        calculation = Calculations(8, 2)
        self.assertEqual(calculation.get_difference(), 6, 'The difference is wrong.')

    def test_product(self):
        calculation = Calculations(8, 2)
        self.assertEqual(calculation.get_product(), 16, 'The product is wrong.')

    def test_quotient(self):
        calculation = Calculations(8, 2)
        self.assertEqual(calculation.get_quotient(), 4, 'The quotient is wrong.')

if __name__ == '__main__':
    unittest.main()
...
----------------------------------------------------------------------
Ran 3 tests in 0.001s
OK

输出显示运行了三个测试。注意,第一个方法叫做not_a_test_sum,这就是为什么它没有被执行。

setUp

现在我们已经理解了使用unittest模块进行单元测试的基础,让我们稍微优化一下我们的代码。您可能已经注意到,在每个测试中,我们初始化了一个Calculations类的对象,它将被测试。然而,我们可以通过创建一个setUp方法来避免这种情况。

TestCase类已经有一个在每次测试前运行的setUp方法。所以当我们创建一个新的方法时,实际上是用我们自己的方法覆盖默认方法。这是实现了这个新方法的代码:

import unittest
from code.my_calculations import Calculations

class TestCalculations(unittest.TestCase):

    def setUp(self):
        self.calculation = Calculations(8, 2)

    def test_sum(self):
        self.assertEqual(self.calculation.get_sum(), 10, 'The sum is wrong.')

    def test_diff(self):
        self.assertEqual(self.calculation.get_difference(), 6, 'The difference is wrong.')

    def test_product(self):
        self.assertEqual(self.calculation.get_product(), 16, 'The product is wrong.')

    def test_quotient(self):
        self.assertEqual(self.calculation.get_quotient(), 4, 'The quotient is wrong.')

if __name__ == '__main__':
    unittest.main()

这意味着calculations对象将在每次测试运行之前被初始化。另一种选择是使用setUpClass来代替。这个想法是一样的,唯一的区别是这个方法将只运行一次,而不是在每次测试之前。这是该方法的实现方式:

@classmethod
def setUpClass(self):
    self.calculation = Calculations(8, 2)

从命令行运行测试

在上一节中,我们看到了用.py文件中的unittest.main()运行测试是可能的。然而,另一个非常有用的运行测试的方法是直接从命令行调用unittest

使用命令行界面运行单元测试可以提高您的工作效率,因为它允许您一次运行多个文件:

>>>pyhon -m unittest

上面的行将运行unittest中的发现模式,该模式将在当前目录中查找测试。

然而,为了运行测试,我们必须遵循一些命名惯例:包含测试的每个文件的名称必须以test开头,并且所有的测试必须是基于TestCase类的方法。正如我们前面所说的,所有这些方法的名字都必须以test这个词开头。最后,目录必须是一个可导入的模块,这意味着它应该包含一个init.py文件。

假设我们有下面的tests目录:

tests/
├── init.py
├── test.py
└── test_str.py

test_str.py文件包含以下测试,这些测试取自 unittest 文档中的一个示例:

import unittest

class TestStringMethods(unittest.TestCase):

    def test_upper(self):
        self.assertEqual('foo'.upper(), 'FOO')

    def test_isupper(self):
        self.assertTrue('FOO'.isupper())
        self.assertFalse('Foo'.isupper())

    def test_split(self):
        s = 'hello world'
        self.assertEqual(s.split(), ['hello', 'world'])
        # check that s.split fails when the separator is not a string
        with self.assertRaises(TypeError):
            s.split(2)

if __name__ == '__main__':
    unittest.main()

如果我们想在两个文件中运行测试,我们可以使用下面一行:

>>>python -m unittest -v

-v使输出更加详细,这在同时运行几个测试时非常有用:

test_diff (teste.test.TestCalculations) ... ok
test_product (teste.test.TestCalculations) ... ok
test_quotient (teste.test.TestCalculations) ... ok
test_sum (teste.test.TestCalculations) ... ok
test_isupper (teste.test_str.TestStringMethods) ... ok
test_split (teste.test_str.TestStringMethods) ... ok
test_upper (teste.test_str.TestStringMethods) ... ok
----------------------------------------------------------------------
Ran 7 tests in 0.002s
OK

我们还可以指定要运行的单个文件:

>>>python -m unittest -v tests.test

在上一行中,tests.test确保只有 tests.py 文件会运行。使用相同的逻辑,我们指定测试类,甚至我们想要运行的单个方法:

>>>python -m unittest -v tests.test.TestCalculations.test_diff

上面的行将只运行test_diff方法,正如我们在输出中看到的:

test_diff (teste.test.TestCalculations) ... ok
----------------------------------------------------------------------
Ran 1 test in 0.000s
OK

结论

单元测试对于任何开发人员来说都是一项非常重要的技能。在本教程中,我们探索了基本概念以及如何使用强大的 Python 框架实现一些单元测试及其最佳实践。

然而,我们在这篇文章中看到的仍然只是对unittest能为您做什么的介绍。这是一个非常强大的工具,现在您已经了解了基础知识,还有更多的东西需要学习。

无监督机器学习的简单指南(2023)

原文:https://www.dataquest.io/blog/unsupervised-machine-learning/

December 19, 2022Introduction to machine learning

当开始机器学习时,通常要花一些时间来预测值。这些值可能是信用卡交易是否是欺诈性的,基于客户的行为模式,客户赚了多少,等等。在这样的场景中,我们正在使用有监督的机器学习。

在监督机器学习中,数据集包含我们试图预测的目标变量。顾名思义,我们可以监督模型的性能,因为可以客观地验证其输出是否正确。

当使用无监督算法时,我们有一个未标记的数据集,这意味着我们没有试图预测的目标变量。事实上,我们的目标不是预测任何事情,而是在数据中发现模式。

因为没有目标变量,我们不能通过客观地确定输出是否正确来监督算法。因此,由数据科学家来分析输出并理解算法在数据中找到的模式。

下图说明了这种差异:

image+1.png

最常见的无监督机器学习类型包括以下几种:

*聚类:根据数据中发现的模式将数据集划分成组的过程,例如,用于划分客户和产品。

  • 关联:目标是发现变量之间的模式,而不是条目——例如,经常用于购物篮分析。

  • 异常检测:这种算法试图识别特定数据点何时完全脱离数据集模式的其余部分,通常用于欺诈检测。

使聚集

聚类算法根据每个数据点的特征将数据集分成多个组。

例如,假设我们有一个包含成千上万客户数据的数据库。我们可以使用聚类将这些客户分成不同的类别,以便为每个群体应用不同的营销策略。

KMeans 算法

K-means 算法是一种迭代算法,设计用于在给定用户设置的多个聚类的情况下,为数据集寻找分裂。集群的数量称为 k。

在 K-means 中,该算法随机选择 K 个点作为聚类的中心。这些点被称为星团的质心。k 由用户设置。然后,迭代过程开始,其中每次迭代是将数据点分配到最*的质心并重新计算质心作为聚类中的点的*均值的过程。这个过程一直持续下去,直到每个质心位于其聚类的*均值。

下图说明了这一过程:

image+2.gif

现在让我们来看一个 K-means 算法的例子。我们有一个客户的数据集,我们将根据他们的年收入和支出分数对这些客户进行细分。下面是代表这两个变量的曲线图:

import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns

# Reading the data
sns.set_style('whitegrid')
df = pd.read_csv('mall_customers.csv')

# Plotting the data
fig, ax = plt.subplots(figsize=(12, 6))

sns.scatterplot('Annual Income', 'Spending Score', data=df, ax=ax)

plt.tight_layout()
plt.show()
/usr/local/lib/python3.7/dist-packages/seaborn/_decorators.py:43: FutureWarning: Pass the following variables as keyword args: x, y. From version 0.12, the only valid positional argument will be `data`, and passing other arguments without an explicit keyword will result in an error or misinterpretation.
  FutureWarning

image+3.gif

然后,我们将数据集分成五个集群:

# Using KMeans
from sklearn.cluster import KMeans

model = KMeans(n_clusters=5)
y = model.fit_predict(df[['Annual Income', 'Spending Score']])

df['CLUSTER'] = y

现在,我们有了按刚刚创建的集群分组的相同图:

# Plotting the clustered data
fig, ax = plt.subplots(figsize=(12, 6))

sns.scatterplot('Annual Income', 'Spending Score', data=df, hue='CLUSTER', ax=ax, cmap='tab10')

plt.tight_layout()
plt.show()
/usr/local/lib/python3.7/dist-packages/seaborn/_decorators.py:43: FutureWarning: Pass the following variables as keyword args: x, y. From version 0.12, the only valid positional argument will be `data`, and passing other arguments without an explicit keyword will result in an error or misinterpretation.
  FutureWarning

image+4.png

你可以在这里查看 KMeans 文档。

分层聚类

分层聚类是一种基于在聚类之间建立层次结构的聚类技术。这种技术细分为两种主要方法:

  • 凝聚聚类
  • 分裂聚类

聚集或自下而上的方法包括将每个数据点指定为单个聚类,然后通过迭代过程将这些聚类组合在一起。

比方说,我们有一个 100 行的数据集。首先,自下而上的方法将有 100 个集群。然后,每个点被分组到最*的一个点,创建更大的集群。这些新的聚类不断与最*的聚类组合在一起,直到我们有一个包含所有点的聚类。

分裂的,或自上而下的方法以相反的方式工作。首先,所有的数据点被分组到一个聚类中。然后将最远的点从主簇中取出,一遍又一遍,直到每个点都成为自己的簇。

自底向上的方法远比自顶向下的方法更常见,所以让我们来看一个使用它的例子。我们使用 K-means 示例中使用的同一数据集的样本。下图被称为树状图,这是我们可视化层次聚类的方式:

# Creating a dendogram with scipy
import scipy.cluster.hierarchy as shc

sample = df.sample(30)
plt.figure(figsize=(12,6))
dend = shc.dendrogram(shc.linkage(sample[['Annual Income', 'Spending Score']], method='ward'))
plt.grid(False)

image+5.png

请注意,每个点在开始时都是一个集群,然后它们被分组,直到出现一个集群。

此外,我们将方法设置为 Ward 。这是执行分层聚类的一种常见方式。该方法通过计算聚类之间的*方距离之和,在聚类过程的每一步进行聚类合并。有多种其他方法可以使用最大、最小和*均距离,等等。你可以在文档中查到。

我们现在需要设置一个截止点。没有正确的方法可以做到这一点,决定聚类的数量可能是任何聚类过程中最棘手的一步。有几个工具可以帮助你确定这个数字,比如剪影和肘方法。在我们的例子中,我们创建了两个集群,树状图如下所示:

import scipy.cluster.hierarchy as shc

plt.figure(figsize=(12,6))
dend = shc.dendrogram(shc.linkage(sample[['Annual Income', 'Spending Score']], method='ward'))
plt.axhline(y=150, color='black', linestyle='--')

plt.grid(False)

image+6.png

从下面的分界点开始,我们剩下两个集群。

联合

关联是一种无监督的学习技术,用于发现数据中的“隐藏”规则和模式。它的经典用例被称为市场篮子分析。

购物篮分析包括发现彼此高度相关的项目。换句话说,我们使用大量购买的数据来确定哪些商品经常一起购买,以便在网上商店向客户提供建议,或者确定在实体店展示产品的最佳方式。

我们有一个数据集,包含在杂货店购买的数千次商品的信息。在每一行中,我们都有一个属于购买的项目。它看起来是这样的:

# Reading the initial data
df = pd.read_csv('Groceries data.csv')
df.sort_values(['Member_number', 'Date']).head()
成员编号 日期 项目说明 星期几
Thirteen thousand three hundred and thirty-one One thousand 2014-06-24 全脂奶 Two thousand and fourteen six Twenty-four one
Twenty-nine thousand four hundred and eighty One thousand 2014-06-24 面粉糕饼 Two thousand and fourteen six Twenty-four one
Thirty-two thousand eight hundred and fifty-one One thousand 2014-06-24 咸点心 Two thousand and fourteen six Twenty-four one
Four thousand eight hundred and forty-three One thousand 2015-03-15 香肠 Two thousand and fifteen three Fifteen six
Eight thousand three hundred and ninety-five One thousand 2015-03-15 全脂奶 Two thousand and fifteen three Fifteen six

前三行是同一客户在同一天购买的商品。我们假设这是一个单一的交易。

然后,我们以这样一种方式操作这个数据集(这不是本文的重点),即每一行都变成一个完整的事务。对于数据集中被视为TrueFalse的每个唯一项目,我们都有一列,这取决于它是否是该行中表示的事务的一部分:

# Reading data after manipulation
transactions = pd.read_csv('transactions.csv')
transactions = transactions.astype(bool)
transactions.head()
速食食品 超高温牛奶 磨料清洁剂 阿提夫。好处 婴儿化妆品 发酵粉 浴室清洁剂 牛肉 浆果 火鸡 闲聊 生奶油/酸奶油 威士忌酒 白面包 白葡萄酒 全脂奶 酸奶 洋葱卷
Zero 真实的 错误的 错误的 错误的 错误的 错误的 错误的 错误的 错误的 错误的 错误的 错误的 错误的 真实的 错误的 错误的 错误的 错误的 错误的 错误的
one 真实的 错误的 错误的 错误的 错误的 错误的 错误的 错误的 错误的 错误的 错误的 错误的 错误的 错误的 错误的 错误的 错误的 错误的 错误的 错误的
Two 真实的 错误的 错误的 错误的 错误的 错误的 错误的 错误的 错误的 错误的 错误的 错误的 错误的 错误的 错误的 错误的 错误的 错误的 错误的 错误的
three 真实的 错误的 错误的 错误的 错误的 错误的 错误的 错误的 错误的 错误的 错误的 错误的 错误的 错误的 错误的 错误的 错误的 真实的 错误的 错误的
four 真实的 错误的 错误的 错误的 错误的 错误的 错误的 错误的 错误的 错误的 错误的 错误的 错误的 错误的 错误的 错误的 错误的 错误的 错误的 错误的

5 行× 167 列

你可以在这里访问这个修改过的数据集。

有了这些数据,我们将使用先验算法。例如,这是一个著名的算法,用于识别频繁出现在一个购物篮中的多组商品。这些集合从一个项目到我们设定的所有项目。在我们的案例中,我们不希望器械包包含三件以上的物品:

# Using the apriori algorithm
from mlxtend.frequent_patterns import apriori

frequent_itemsets = apriori(transactions, min_support=0.001, use_colnames=True, max_len=3)
frequent_itemsets
支持 项目集
Zero 0.028612 (即食食品)
one 0.016691 (超高温牛奶)
Two 0.001431 (磨料清洁剂)
three 0.001431 (artif。甜味剂)
four 0.008107 (发酵粉)
One thousand eight hundred and ninety-nine 0.001431 (全脂牛奶、香肠、zwieback)
One thousand nine hundred 0.001431 (苏打水、酸奶、热带水果)
One thousand nine hundred and one 0.001431 (特色巧克力、全脂牛奶、酸奶)
One thousand nine hundred and two 0.001431 (鲜奶油/酸奶油、酸奶、热带水果)
One thousand nine hundred and three 0.001907 (全脂牛奶、酸奶、热带水果)

1904 行× 2 列

我们看到的支持度量对应于特定项目集出现的概率。例如,假设我们有一个 100 个篮子的数据集,其中牛奶和咖啡的组合出现在 20 个篮子中。这意味着这种组合发生在 20%的篮子里;所以支撑是 0.20。

我们现在将使用一个函数来查找数据中的规则。该函数将检查 apriori 算法创建的集合,以识别经常一起购买的产品,并确定它们是否在同一个集合中。

# Using association rules
from mlxtend.frequent_patterns import association_rules

rules = association_rules(frequent_itemsets, metric="lift",  min_threshold=1.5)

display(rules.head())
print(f"Number of rules: {len(rules)}")
祖先 结果 先行支持 后续支持 支持 信心 电梯 杠杆作用 定罪
Zero (瓶装啤酒) (超高温牛奶) 0.041965 0.016691 0.001907 0.045455 2.723377 0.001207 1.030134
one (超高温牛奶) (瓶装啤酒) 0.016691 0.041965 0.001907 0.114286 2.723377 0.001207 1.081653
Two (法兰克福) (超高温牛奶) 0.031950 0.016691 0.001431 0.044776 2.682729 0.000897 1.029402
three (超高温牛奶) (法兰克福) 0.016691 0.031950 0.001431 0.085714 2.682729 0.000897 1.058804
four (人造黄油) (超高温牛奶) 0.032427 0.016691 0.001431 0.044118 2.643277 0.000889 1.028693
Number of rules: 2388

除了支持,我们还应该了解另外两个重要指标。

信心是一个完整的交易发生的概率,假设他们的第一个项目已经在篮子里。换句话说,假设一个人既买牛奶又买咖啡的概率(支持度)是 0.5,一个人只买咖啡的概率(支持度)是 0.10。置信度为 0.10/0.05,等于 0.5。从数学角度来说:

\[信心(牛奶→咖啡)= \ frac {支持(牛奶→咖啡)} {支持(牛奶)} \]

Lift 指标表示规则出现的频率比我们预期的要高多少。如果 lift 等于 1,则意味着这些项目在统计上相互独立,无法从中得出任何规则。所以,升力越高,我们对规则就越有信心。请注意,在代码中,我们选择根据提升对规则进行排序,并将最小阈值设置为 1.5。

从数学上讲,这种提升是对第二项的信心超过支持:

\[lift(牛奶→咖啡)= \frac{confidence(牛奶→咖啡)} {support(牛奶→咖啡)} \]

还有一些其他指标。检查文档以查看所有内容。

异常检测

异常检测是在数据集中发现异常数据点的过程。换句话说,我们正在寻找完全脱离数据集模式的异常值和点。

这种机器学习通常用于检测欺诈性信用卡交易或设备或机器的故障或即将发生的故障。

虽然我们将异常检测作为无监督的机器学习过程来处理,但它也可以作为有监督的算法来执行。然而,要做到这一点,我们需要一个带标签的数据集,我们知道每个数据点是否是异常的,以便训练一个模型来执行分类任务。

但是我们并不总是拥有我们想要的所有数据。如果我们没有那个标记的数据集,那么它就变成了一个无监督的学习问题,异常检测算法可以帮助我们。

当我们谈论异常检测时,我们不是在谈论一种单一的算法。同一任务有多种不同的算法,每种算法使用不同的策略,得到不同的解决方案。

Scikit-learn 为异常检测实现了一些不同的算法。在这里,我们将提供其中三个的快速介绍和比较,但是请随意查看伟大的 Scikit-learn 的文档以获得更多信息。

隔离森林

隔离林是一种基于树的算法,用于异常检测。该算法通过随机选择一个特征和该特征中的分割值来工作。这个过程一直持续到我们将数据点隔离为树中的节点。

这一过程的随机性使得异常数据点比其他数据点需要更少的分裂来隔离。因为整棵树是多次创建的,所以我们称之为“森林”当同一个观察结果被快速隔离多次时,算法认为这是一个异常是安全的。

为了使用 Scikit-learn 的 Isolation Forest 实现创建一个快速示例,我们将使用与集群部分相同的客户数据集,考虑相同的两个变量:年收入和支出分数。

下面的代码使用算法在数据集中创建一个新列,其中-1 表示异常,1 表示非异常。散点图让我们可以看到这些类是如何分布的:

# using the Isolation Forest algorithm
from sklearn.ensemble import IsolationForest

model = IsolationForest()
y_pred = model.fit_predict(df[['Annual Income', 'Spending Score']])
df['class'] = y_pred

# Plotting the data
fig, ax = plt.subplots(figsize=(12, 6))

sns.scatterplot('Annual Income', 'Spending Score', data=df, hue='class', ax=ax, cmap='tab10', alpha=0.8)

plt.tight_layout()
plt.show()
/usr/local/lib/python3.7/dist-packages/seaborn/_decorators.py:43: FutureWarning: Pass the following variables as keyword args: x, y. From version 0.12, the only valid positional argument will be `data`, and passing other arguments without an explicit keyword will result in an error or misinterpretation.
  FutureWarning

image+7.png

DBSCAN

DBSCAN 是含噪声应用的基于密度的空间聚类的简称。这不仅是一个异常检测算法,也是一个聚类算法。它的工作原理是将观察结果分成三大类。

基础类由在确定半径的圆内具有一定数量的邻居的观测值构成。邻居的数量和圆的半径是由用户设置的超参数。

边界观测值的邻居数量少于既定数量,但仍至少有一个,而完全没有邻居的观测值被视为噪声类。

该算法迭代地选择随机点,并且基于超参数,确定它们属于哪一类。这种情况一直持续到所有的观察结果都被分配到一个类中。

如前所述,DBSCAN 可以产生多个集群,因为我们有可能拥有比半径超参数相距最远的基本观测值。

让我们在运行隔离林的同一数据集中运行 DBSCAN。例如,我们将半径设置为 12,邻居数量设置为 5:

# Using DBSCAN
from sklearn.cluster import DBSCAN

model = DBSCAN(eps=12, min_samples=5) 
y_pred = model.fit_predict(df[['Annual Income', 'Spending Score']])
df['class'] = y_pred

# Plotting the data
fig, ax = plt.subplots(figsize=(12, 6))

sns.scatterplot('Annual Income', 'Spending Score', data=df, hue='class', ax=ax, cmap='tab10')

plt.tight_layout()
plt.show()
/usr/local/lib/python3.7/dist-packages/seaborn/_decorators.py:43: FutureWarning: Pass the following variables as keyword args: x, y. From version 0.12, the only valid positional argument will be `data`, and passing other arguments without an explicit keyword will result in an error or misinterpretation.
  FutureWarning

image+8.png

请注意,与隔离林相比,我们有了更少的异常。

局部异常因素

局部异常值因子- LOF 的工作方式类似于 DBSCAN 算法。它们都是基于寻找不属于任何局部观察组的数据点。主要区别在于,局部异常值因子发布另一种算法,K-最*邻–KNN,以确定局部密度。

的数量 K 由用户设置,并传递给 KNN,以确定每个数据点的相邻组。当我们将一个观测值的局部密度与其相邻观测值的局部密度进行比较时,可以识别出密度相似的区域和密度明显低于其相邻观测值的区域,这些区域被视为异常值或异常值。

使用 Scikit-learn 的实现并创建散点图,我们可以看到异常不仅位于图的边缘,而且分布在整个图中。这是因为我们根据它们的位置和与*邻的距离来决定它们是否是异常。

# Usinsg Local Outlier Factor
from sklearn.neighbors import LocalOutlierFactor
model = LocalOutlierFactor(n_neighbors=2)
y_pred = model.fit_predict(df[['Annual Income', 'Spending Score']])
df['class'] = y_pred

# Plotting the data
fig, ax = plt.subplots(figsize=(12, 6))

sns.scatterplot('Annual Income', 'Spending Score', data=df, hue='class', ax=ax, cmap='tab10')

plt.tight_layout()
plt.show()
/usr/local/lib/python3.7/dist-packages/seaborn/_decorators.py:43: FutureWarning: Pass the following variables as keyword args: x, y. From version 0.12, the only valid positional argument will be `data`, and passing other arguments without an explicit keyword will result in an error or misinterpretation.
  FutureWarning

image+9.png

如我们所料,不同的异常检测方法产生了不同的结果。如果我们测试了更多的算法,同样的事情也会发生。如前所述,每种算法使用不同的方法来识别异常值,理解这些算法对于获得更好的结果至关重要。

最后

本文旨在快速介绍无监督机器学习的有用性和强大功能。我们探讨了有监督的和无监督的机器学习之间的区别,并且我们还浏览了一些重要的用例、算法和无监督学习的例子。

如果你有兴趣了解关于这个主题的更多信息, Dataquest 的数据科学家学习路径是深入监督和非监督机器学习的一个很好的选择。一定要去看看!

Helena Tan 如何从投资银行转向数据科学

原文:https://www.dataquest.io/blog/user-story-helena-tan/

December 30, 2015To highlight how Dataquest has changed people’s lives, we’ve started a new blog series called User Stories where we interview our users to learn more about their personal journeys.In this post, we interview Helena Tan, Data Analyst at Fitbit. Helena worked in a variety of roles in private equity and investment banking after graduating from the University of Waterloo. She wanted to become more technical, and work on more challenging problems. She started learning Python and data analysis using Dataquest. Her progress has been incredible, and she landed a job at Fitbit soon after she started learning. She’s been continuing to learn on Dataquest, and is continually improving her skillset. She’s very passionate about sharing what she’s learned, and recently presented at the Grace Hopper Conference.

你以前在投资银行工作。是什么让你对数据科学感兴趣?

我一直热衷于用数据来讲述一个好故事。回到我的金融职业生涯,我喜欢根据行业趋势和公司具体的财务数据提出投资建议。2013 年,我搬到了硅谷,在那里,我有机会在一家初创公司担任商业智能分析师,与一些最优秀的数据科学家和机器学习专家一起工作。同时,我有机会见证了该公司如何应用机器学习为其客户带来巨大价值。结果,我变得很受启发,用数据来构建伟大的产品,解决问题,给人们的生活带来改变!

你是如何开始学习数据科学的?

基于我的理解,数据科学有三大方面:编程技能、统计知识、产品感/领域知识。幸运的是,我有统计学学位,所以我不必从零开始统计部分。然而,在我受到使用数据来构建产品的启发后,我很快意识到,如果不学习 Python 这样的编程语言,我就无法在追求自己的激情方面走得很远。这就是学习之旅的开始,也是我发现 Dataquest 的时候!

当你在找工作时,你认为帮助你获得面试机会的最重要的一件事是什么?

如果非要我总结成一个词,那就是“坚持”。我不记得我在硅谷找到第一份工作之前申请了多少份工作,但我知道很多!每个数据科学工作都是独一无二的。因团队、公司、行业而异。在个人技能、职业目标和角色/团队的要求之间找到一个好的匹配需要时间。

你在 Fitbit 做了几个月的数据分析师。你是如何把自己的技能提高到可以被雇佣的程度的?

当我开始学习创建数据产品所必需的编程技能时,Fitbit 的机会来了。我还记得我和 Fitbit 的第一轮技术面试。这是我的第一次 Python 编程测试,发生在我开始 Dataquest 的 Python 课程后仅 3 周!幸运的是,我能够在面试中应用我在 Dataquest 学到的东西,并最终得到了这份工作。就技能的最低水*而言,我没有明确的答案。这要看团队、公司和一个人过去的经历。对于那些对科技行业完全陌生的人,我建议他们从学习 SQL 开始,并确保在面试中展示强大的定量分析技能。

数据科学工作的技术面试是什么样的?

根据团队的不同,数据科学工作的技术面试可能会有不同的形式。常见的形式包括实时编码、案例研究和带回家的项目。对于实时编码和案例研究,通常会给候选人一些简单的数据集和假设场景,然后要求他们在面试中使用自己喜欢的编码语言解决问题。带回家的项目通常更全面。候选人会得到一组更复杂的数据,他们通常会被要求发现见解,并根据这些数据建立预测模型。对于那些试图在这个领域闯出一片天地的人来说,最初的几次技术面试可能会很难,但是随着大量的练习,他们会变得更容易!

如果你可以重新开始学习数据科学,你会在这个过程中改变什么吗?

我还在学习过程中。在这个领域有很多有趣的东西可以探索。对于我自己的过程,我喜欢边做边学。它让我保持动力。我有一个让我感到兴奋的产品创意列表,所以我去找学习资料,学习制作它们所需的技术。我发现用这种方式学习非常有趣和愉快。然而,有时我需要切换回更结构化的学习过程,以确保我理解应用程序背后的理论概念。

学习数据科学和找工作最大的误区是什么?

数据科学已经成为一个非常通用的术语。数据科学角色可以从数据工程师、机器学习工程师到业务分析师不等。因此,现在的候选人需要花更多的时间来理解他们想要解决什么类型的问题,而不是关注一个职位。

2016 年你最兴奋的学习是什么?

我很高兴能提高我的技能来做两件事:处理和分析更多来自网络的开放数据,并以互动的方式可视化结果。我想 Dataquest 掩护了我!🙂

帕特里克·肯尼迪如何利用 Dataquest 向数据科学转型

原文:https://www.dataquest.io/blog/user-story-patrick-kennedy/

February 18, 2016

为了突出 Dataquest 如何改变人们的生活,我们已经开始了一个名为用户故事的新博客系列,在这里我们采访我们的用户,以了解他们的个人旅程以及我们如何帮助他们达到他们需要的目的。

在本帖中,我们采访了顾问、作家兼企业家帕特里克·肯尼迪。帕特里克曾在多家房地产公司担任高管职务。他现在正在学习数据科学,因为他想探索它对现有行业的影响。对于他正在从事的数据科学公司和项目,他有很多想法。

patrick

Dataquest 对您的学习过程有什么帮助?

很难用语言表达。这是非凡的。这正是我需要的,来弥合我以前作为一名实验心理学家的生活和我现在作为一名专注于建立企业的人的生活之间的差距。我知道许多统计数据和一些编码,但对 Sci-kit learn 中的模型构建技术却一无所知。我知道更仔细地查看数据会产生很好的结果,但我仍然手动构建应用程序,将我们的财务报告与我们的销售渠道、我们的个人销售人员目标、我们的预算以及我们上一年的业绩联系起来。那是一只熊。现在,想到我可以用这些类型的模型生产出什么东西,我感到非常兴奋。然而更重要的是,它向我展示了一种思考开发智力的新方法。

是什么让你决定开始使用 Dataquest?

我是从一个叫“激励”的招聘人员那里了解到 DataQuest 的。我是在与另一家在奥斯汀交易的大型商业房地产公司的一次会议上了解到这个项目的。当我听说了 gentile 的产品后,我知道我的未来在于以一种我在以前的职位上没有经历过的方式将数据与业务联系起来。glazing 的招聘人员让我看了一个 quora 帖子,是由 glazing 的 CTO 发的,他提到 DataQuest 是一个很棒的工具。我制定了一个计划,尽可能快地浏览尽可能多的 DataQuest 内容。这是一场斗争,因为你们都制作了新的内容!

你正在学习一门大会数据科学课程,很快就会身临其境。你如何对比训练营和在线学习的体验?

我把它看作一个垫脚石。从我读到的一切来看,数据科学沉浸式将会让我大吃一惊。然而,我用 DQ 弄湿了我的脚,所以我能知道我在做什么。我可以探索各种不同的主题,我可以在结构化的环境中这样做。这是最好的学习方法。随着引导式项目的引入,DQ 将培训的车轮移开了一些,但仍然提供了如何在数据科学过程中组织你的思想的基本轮廓。大会提供了少一点的结构和多一点的责任,让你自己去解决问题,你可以自己挑选、调查和展示一个顶点项目。GA 以实践的方式提供数据科学各方面的帮助和教学,使人们能够成功地开发他们的项目,但最终还是取决于个人。激励似乎是把个人扔进火里,然后看另一头出来的是什么。通过 DQ 和 GA 之前的充分准备,我认为这是一个完美的 1-2-3。

您一般的数据科学学习流程和计划是什么?

我的计划是双重的。首先,我在探索各种不同的商业想法,看看会推出什么,每一个都有数据科学的一个方面,有些比其他的更多。卖掉最后一家公司后,我很幸运有了自由去打探消息。第二,我知道让一家企业运转起来很难,所以我正在利用“激励”的结果援助来为我确定一些好的地方。当一个团体根据合同有义务安置你的时候总是很好的。最后,我想我不会只做一件事——我太容易厌倦了。我甚至可能只是再写一本关于学习数据科学过程的书。

你在心理学、房地产和管理方面有着丰富的职业和教育经历。这些是如何将你引向数据科学的?

到目前为止,这是一条有趣的道路——我希望我可以说这一切都是有预谋的。但对数据的真正感受来自于在哥伦比亚大学攻读实验心理学博士学位。这是我第一次真正深入研究具体的工具来分析数据,并为实验构建软件应用程序,并以一种使后端数据收集更容易的方式构建它们。这迫使我以一种全面的方式思考数据。此外,和一群追求相同目标的人在一起,让我找到了不同的做事方式。我的好朋友 Dave 教我关于分层混合模型,Baruch 教我如何以有意义的方式分析小数据集。对我来说,问题是一旦我们有了见解,目标就是发表、推广并继续下一个研究。没有任何一部分涉及到帮助他人。我的研究是关于我在电子游戏设计、最佳表现和员工敬业度或倦怠方面的感觉。一次又一次,有人告诉我,我的重点应该是研究,而不是应用研究。在与一名顾问(现已离职)讨论了区内研究可能涉及的所有有趣应用后,她告诉我,她对商业一无所知,也许我不适合做研究。所以我接受了她的建议,在整理我的论文提案的过程中离开了。

对我来说不幸的是,我把婴儿和洗澡水一起扔了出去,并试图不惜一切代价避免严格的数据分析,认为它被归入那些象牙塔。相反,我进入了创业世界,创办了几个自己的公司(一个变成了僵尸,另一个很快就被烧掉了),拿到了商学学位,在一家科技孵化器工作。在孵化器里,我不断看到这些公司,我会帮助他们完善一个推介*台(由于我在哥伦比亚大学的多年教学,这个*台变得很容易),然后筹集大量资金并毕业。但我想要的不仅仅是引领一家公司进入青春期。我想加入其中一家公司。我尽力推脱,但被告知我要么做销售,要么做工程。我不认为自己是一名工程师,销售让我觉得很酸,所以我做了任何非销售、非工程人员都会做的事情:咨询。

我咨询过的一家公司最终聘请我加入公司,担任运营总监。那是一家商业房地产公司——对此我只有一个模糊的概念。他们需要的似乎很简单:帮助找出谁做了什么。令我困惑的是,这甚至是一个问题,但当你试图在没有运营骨干的情况下快速增长时,你会错过其中的一些结构组件。所以我走进去,立刻看到了一份申请,是关于我几年前在这个区域进行的所有研究的。我可以用我对员工敬业度的研究来培养一种文化,看看这将如何影响未来的收入(提示:它影响很大)。

所以在这里,我用我的旧研究,它的工作。这让我想到了一个问题——在这里我还能做什么?我疯狂地开发各种工具和程序,这导致了我的第一本书的出版,我被提升到首席运营官,并最终将公司卖给了一家全球公司。太棒了。压力很大,但是很棒。这是通过对各种数据的调查得出的。构建实时销售漏斗需要数据收集、管理、分析和展示技术。大部分工作我都是手工完成的,但是我一直在寻找新的和改进的方法来实现自动化。然而,大约在这个时候,事情开始变得更加摩擦。

我越是尝试开发可扩展的技术,以达到既能预测潜在客户与我们签署意向书的可能性,又能预测哪家经纪人最有可能最大化这种可能性的最终目标,我就越感到阻力重重。当我负责三个办公室和大约 80 人的销售团队时,没有人真正关心收入最大化。这件事很奇怪,我过了一会儿才明白过来。简而言之,经纪人通常能从一堂销售课中赚取 50%以上的利润,这使得一个 3-5 岁的经纪人每年能赚 50 万美元。考虑到经纪公司在给经纪人一大笔钱后通常不会剩下多少钱,这导致很少或没有管理,或由经纪人管理。人们可以很容易地假设,如果一个经纪人/经理可以通过经纪业务每年赚取数百万美元,他们会花时间做些什么。

我在打一场必败的仗。当经纪人一周能打 4 天高尔夫,一年能赚 50 万的时候,他们试图推动经纪人赚更多的钱。为什么他们想工作而不是打高尔夫?当然,他们都认识到这很重要,但是其他人参与到这个系统中也很重要。不是他们。与许多想法一样,我的挫折让我再次意识到数据的力量。但是数据需要一个繁荣的环境。我生成、处理和展示的数据几乎没有任何效果。我需要一个开关。

任何销售组织都涉及到大量的数据,从最初陌生电话中的语气、情绪和用词,到管道管理的可预测性,再到对场地和办公空间位置的建议。见鬼,在研究谈判技巧方面有很大的空间。我决定,与其在我周围为这些更老的学校经纪人展开新的一页而斗争,我想尽可能快地了解我所能了解的一切,以调查所有这些潜在的数据来源。我想知道如何收集,如何操作,如何让数据以一种有意义的方式与我交流。

这就是我进入数据科学领域的原因。是的,这是一个超长的答案。

你在商业运作和高层次思考商业方面有着丰富的经验。数据科学如何帮助你做到这一点?

数据科学几乎可以在企业的任何层面运作。它甚至可以经营企业,但这只是我受到了我刚刚读完的这本书的影响。具体来说,对于运营来说,它是巨大的。无论你是在经营一家供应链管理繁重的企业,还是像我一样经营一家以销售为主的机构,数据科学都是至关重要的一部分。部分原因是数据科学这个术语包含的内容太多,还因为数据无处不在。一项数据科学技术的每一次成功应用都应该带来至少两次以上的应用机会。例如,我们使用 CRM 来管理我们所有的数据。但是在花了比我愿意承认的更多的钱之后,我们不得不雇人来管理这个数据库,因为没有人想直接使用它,而且 99%的时间它仅仅被用作一个数字通讯录。RelateIQ 在使用自然语言处理技术从各种不同的来源(日历、电子邮件、VoIP 电话等)收集数据方面做了大量工作,从而消除了 CRM 的数据输入方面。还有另一个工具(我现在忘记了它的名字),让你有可能根据你的电子邮件链达成交易。现在 LinkedIn 有了他们的销售导航工具,使他们的整个*台成为 CRM。

在内部运营中,销售是一个不用动脑筋的事情,但管理层有大量的机会来应用数据科学。现在,目标设定在组织中非常重要。与标准的绩效评估(当你试图解雇某人时,它实际上只是一份书面记录)不同,目标设定让经理能够与员工建立融洽的关系,并鼓励他们沿着自己的道路前进。组织中的大多数目标设定“程序”都保存在 excel 文件中,信息处于休眠状态。相反,这些信息可以输入到应用程序中,如果员工被困在一个特定的项目中,有一段时间没有更新他们的状态,这些应用程序可以提供帮助;或者,如果其他人在他们不知道的情况下正在使用类似的应用程序,这些应用程序可以建议与其他人合作。管理层的大部分工作是激励员工和促进对话。这两个角色都可以在一定程度上实现自动化。

关于学习数据科学和找工作,你看到的最大误解是什么?

首先,这是一种时尚。我在奥斯汀采访过的许多人在听到“数据科学”这个术语时都会翻白眼。我明白了。这是一个热门的新事物,公司说他们需要,即使他们不知道为什么。当然这很烦人。统计学家、计算机科学家和工程师了解这些技术已经有一段时间了。当我了解到许多数据科学模型的构建都是通过回归来完成时,我感到很惊讶,因为回归是我在大多数实验中使用的模型!我只是从来不知道实验环境中的模型构建和商业或工程环境中的模型构建之间的关系。但这背后的基础是,数据科学本身并不是一种时尚。它是各种不同学科的重组。这就是进步!能够把有些不相关的领域放在一起是很棒的。当然,这种热情可能会在未来几年内稍微*静下来,但该领域所做工作的重要性不会。

第二个是难学。有些人说他们没有得到代码。还有人说自己不是数学人。当然,如果你想成为有史以来最好的数据科学家,你可能会想了解一点代码和数学。但你不一定要成为最好的。你只要足够优秀,就能实现你的目标。因此,对于那些真正感兴趣,但对你将要学习的新语言或做数学题感到害怕的人,想想你想做什么。你想建立一种新型的电影推荐服务?开始阅读关于服务的内容,推荐是由什么产生的,以及这些推荐服务是如何编码的。把它分成几块。不要从拿起一本关于 Java 或 Python 之类的书开始。你会很快失去兴趣,因为没有目标在推动你前进。我在大学当吉他导师的时候犯了一个错误。我在辅导一个 9 岁左右的男孩。他想演奏猫王的歌曲。相反,我告诉他,他需要先学习他的音阶,所以我们花了前三节 30 分钟的时间复习音阶。他妈妈在第四次治疗前给我打电话说,对不起,他已经不感兴趣了。我感到遗憾的是,他可能因为我而对一种乐器失去了兴趣,但至少这给我上了宝贵的一课:让目标驱动表现。

2016 年你最兴奋的学习是什么?

坦率地说,我想学习如何赢得纸牌比赛。我现在正处于其中,并把我有限的知识投入到如何建立一个越来越好的模型中。但是唷,有一些好的人在做这些比赛!我很难停止竞争…我想赢🙂

除此之外,对我来说,数据科学最有趣的方面是自然语言处理。我们如何感受、思考和解释他人,在很大程度上取决于语言。我的一个朋友在哈佛获得了临床心理学学位,他的研究表明移动治疗干预有助于缓解焦虑和抑郁情绪。你能想象 Siri 是你的治疗师吗?有很多好事要做,我认为我们正处于伟大事业的尖端。

Dataquest 如何帮助 Health Catalyst 的企业分析副总裁 Patrick Nelli

原文:https://www.dataquest.io/blog/user-story-patrick-nelli/

October 5, 2015

在本系列的第一篇文章中,我们采访了 Health Catalyst 公司的企业分析副总裁 Patrick Nelli。Health Catalyst 是一家位于犹他州盐湖城的数据仓库和分析公司,其经验是帮助医疗机构“使用分析、最佳实践和采用服务来改善结果”。Health Catalyst 与斯坦福医疗保健公司、凯撒医疗保健公司和德克萨斯儿童医院等知名机构合作。

你的故事是什么,你是如何走到今天的?

自大学以来,我一直对通过技术和创新改善医疗保健行业感兴趣。作为一名专注于生物物理学和生物化学的物理学专业学生,我被医疗保健领域吸引,特别是从实验室研究和接触早期生物技术公司开始。学术研究需要很长时间才能影响患者,这让我感到不快,所以我想更接*这个行业的“商业”方面。我在医疗保健投资银行和私募股权行业呆了几年,借此机会了解了医疗保健行业的商业方面。

从投资的角度研究医疗保健行业时,我对医疗保健数据和分析领域产生了浓厚的兴趣。医疗保健提供商正在采用电子交易系统(最显著的是电子病历),这为通过收集越来越多的电子数据来实现洞察和改进提供了机会。在对该行业有了更多了解并会见了该领域的一些投资者和公司后,我有幸结识并加入了健康催化剂,这是一家位于盐湖城的医疗保健数据和分析初创公司。

我在 Health Catalyst 工作了几年,负责管理我们的内部分析团队。我们使用自己的数据仓库和分析产品来汇总公司内部数据(即来自营销、销售、运营、产品开发、财务等部门的数据)。)并根据数据推动内部运营改进。

是什么促使你对学习数据科学感兴趣?

由于 Health Catalyst 的实际使用案例和基于医疗保健领域发展方向的个人兴趣,我对数据科学的兴趣有所增长。

实际上,我们需要在 Health Catalyst 内部使用数据科学方法。当 Health Catalyst 开始收集和分析我们的内部数据集时,我们主要进行探索性分析。这是合乎逻辑的第一步,这些分析会产生很多价值。随着 Health Catalyst 的发展,我们已经在内部聚合了更大、更多样化的数据集,我们意识到,我们可以通过对数据进行更严格的统计分析来获得更多见解。Health Catalyst 面向客户的产品整合了预测分析模型,我们希望在我们的内部数据集上使用类似的技术。

就我个人而言,我相信医疗保健领域将在未来几十年受益于数据科学。虽然大多数提供商刚刚开始建立数据分析能力,但成熟范围内的卫生系统正在建立根植于护理流程的预测模型。我想亲自了解如何开发这些模型,并能够使用这些知识来开发其他预测模型用例。

您是如何听说 Dataquest 的?在 Dataquest 之前,您使用了哪些资源来学习数据科学?

我利用网上所有令人惊叹的免费内容开始了数据科学学习之旅。我已经研究了无数的 MOOCs 和关于这个主题的 O'Reilly 书籍。在利用所有这些内容大约一年之后,我想更深入地利用 python 来执行数据科学。这让我想到了大会的数据科学课程。我在学习本课程时听说了 Dataquest。

Dataquest 对您目前的工作有何帮助?

Dataquest 最好的部分是理论和实用主义之间的*衡。在展示更简单的 scikit-learn 方法来执行这些分析之前,这些课程似乎一步一步地介绍了如何执行特定的分析。

这帮助了我

  • 理解分析背后的基本理论
  • 利用如何执行分析的实用代码示例。

很难找到这种理论和编码示例相结合的资源,这就是为什么当我被介绍给 Dataquest 时我如此兴奋。虽然我仍处于学习过程的早期,但 Dataquest 让我接触到了各种模型。这有助于我对这些分析的用例进行头脑风暴,并为我提供了启动项目的示例代码。

数据科学在医疗领域有哪些有趣的应用方式?

探索性数据分析开始在整个医疗保健行业使用。在提供者方面,卫生系统正在探索运营、临床和财务数据,以确定需要改进的领域。预测分析也开始应用于这些数据集(尽管它们还处于采用的早期阶段)。具体示例包括预测临床事件(如心力衰竭复课)、运营事件(医院部门数量)和财务事件(基于临床概况的患者支出)。事实上,我们有几份关于主题为 T1 的免费白皮书。

你在很多不同的行业工作过,从金融到医疗保健。在数据方面,你经常看到不同行业的人犯哪些错误?

数据不会自动、完美地生成,而是基于人工设计的流程生成的。甚至机器日志文件也是基于特别定义的参数生成的。无论身处哪个行业,深入了解数据是如何生成的都是至关重要的。这一次的投资将在 2010 年获得丰厚回报

  • 初始数据分析流程(例如,了解为什么会有缺失或脏数据),
  • 当试图根据数据得出结论时,以及
  • 当试图根据结果实施干预时。

分析的结果是理想的行动或干预。如果不知道数据是如何生成的,就很难准确理解如何实现操作。

Health Catalyst 在招人吗?你们在找什么样的人?

我们是!查看我们的职位空缺。

编者按:想开始自己的旅程?现在就开始学习。

Rob Hipps 如何使用 Dataquest 获得数据分析工作

原文:https://www.dataquest.io/blog/user-story-rob-hipps/

February 19, 2016To highlight how Dataquest has changed people’s lives, we publish student stories where we interview our students to learn more about their personal journey and how we’ve helped them get where they needed to. In this post, we interview Rob Hipps, Data Analyst at 3M. Rob went through a Master’s program in Data Science, and has an interesting take on how in-person and online learning intersect. He later went through a data science internship at Verisk before landing his first fulltime data science role at 3M.

你获得了工商管理学士学位,并从事商业战略工作。你为什么想进入数据科学领域?

它几乎是有机发生的。我会看到一组数据,并为提出的问题提供答案。我发现自己想更深入地研究这些数据,我想学习能让我做到这一点的技能。

是什么让你决定开始使用 Dataquest?

在我努力获取更多数据科学知识的过程中,我发现我所学的知识中存在一些明显的漏洞。我寻找资源来帮助我。我找到了 Dataquest。对我来说,我需要能够以动手的方式与数据互动,Dataquest 提供了这种学习方式。大约一两个小时后,我注册了高级会员。

Dataquest 对您的学习过程有什么帮助?

Dataquest 帮助我成为了一名更全面的数据分析师,并帮助我理解了如何从头到尾完成一个项目。课程设置的方式让我们很容易看到真实数据问题应该如何解决。此外,如果我在项目中遇到问题,我几乎总能找到快速模块来帮助解决问题。

你是如何在 Verisk Analytics 获得第一份数据科学实习的?

我申请了实习,被邀请去现场面试。我们讨论了许多分析领域,以及我将如何融入他们的团队。

祝贺您在 3M 健康信息系统公司获得全职数据科学职位!什么技能对面试最重要?

谢谢!这是一个相当长的面试过程,涵盖了许多技能。对他们来说,最重要的是 SQL 和使用数据分析工具(如 R 和 Python)的能力。他们对如何利用这些技能有一些有趣的想法,我很高兴能把我学到的东西带给团队。

实习有助于你找到一份全职工作吗?

实习对获得这份工作至关重要。我将为 3M 做的事情与我在 Verisk 实习时的角色有很多相似之处。

除了在线学习,你还在攻读数据科学硕士学位。你如何对比这两种经历?

我发现这两种经历是相辅相成的,我依靠这两种经历获得了我一直在寻找的技能,从而加深了我对数据科学的理解。例如,在我的硕士课程中,R 比 Python 教得更重。我想变得更加*衡,所以我寻找一种方法来加强我的 Python 技能。Dataquest 帮助我学习 Python,甚至加强了我的 r 技能。Dataquest 的独特之处在于它能够选择您想学习的内容和时间,而不是为迎合更多受众而对材料设定严格的截止日期。

关于学习数据科学和找工作,你看到的最大误解是什么?

我认为人们认为编程对他们来说太难学了。编程就像其他事情一样,如果你投入时间,你就会看到结果。我会挑战那些认为编程超出他们能力的人,让他们尝试 Dataquest。

接下来学什么最让你兴奋?

我对你们继续推出的新内容感到非常兴奋!

在 Python 中使用类

原文:https://www.dataquest.io/blog/using-classes-in-python/

January 26, 2022Using Classes in Python

在本教程中,我们将学习如何创建和使用 Python 类。特别是,我们将讨论什么是 Python 类,我们为什么使用它们,存在什么类型的类,如何在 Python 中定义类和声明/调整类对象,以及许多其他事情。

Python 中有哪些类?

因为 Python 是面向对象编程(OOP) 语言,里面的一切都代表一个对象。列出了,元组,集合,字典,函数等。都是 Python 对象的例子。一般来说,Python 对象是数据项和描述这些项的行为的方法的实体。

相比之下,Python 类是一个模板或构造函数,用于创建 Python 对象。我们可以把它想象成一份烹饪食谱,里面列出了所有的配料和它们的数量,包括它们可能的范围,并一步一步地描述了烹饪的整个过程。在这种情况下,我们可以将蛋糕食谱比作一个类,将按照该食谱烹饪的蛋糕比作一个对象(即该类的一个实例)。使用相同的配方(类),我们可以创建许多蛋糕(对象)。这正是在 Python 中创建类的范围:定义数据元素和建立这些元素如何交互和改变它们状态的规则——然后使用这个原型以预定义的方式构建各种对象(类的实例),而不是每次都从头开始创建它们。

Python 中的类类型

从 Python 3 开始,Python 原生对象的术语类和类型是相同的,这意味着对于任何对象x,x.**class**type(x)的值都是相等的。让我们确认一下:

a = 5
b = 'Hello World'
c = [1, 2, 3]

for var in [a, b, c]:
    print(type(var)==var.__class__)
True
True
True

Python 中有几个内置类(数据类型):整型、浮点型、字符串型、布尔型、列表型、范围型、元组型、集合型、冷冻集型、字典型,以及其他一些很少使用的类型:

a = 5.2
b = 'Hello World'
c = [1, 2, 3]
d = False
e = range(4)
f = (1, 2, 3)
g = complex(1, -1)

for var in [a, b, c, d, e, f, g]:
    print(var.__class__)
<class 'float'>
<class 'str'>
<class 'list'>
<class 'bool'>
<class 'range'>
<class 'tuple'>
<class 'complex'>

内置和用户定义的函数有自己的类/类型:

def my_func():
    pass

print(my_func.__class__)
print(min.__class__)
<class 'function'>
<class 'builtin_function_or_method'>

当我们使用额外的 Python 库时,比如 pandas 或 NumPy ,我们可以创建只与那些库相关的类/类型的对象:

import pandas as pd
import numpy as np

s = pd.Series({'a': 1, 'b': 2})
df = pd.DataFrame(s)
arr = np.array([1, 2, 3])

print(s.__class__)
print(df.__class__)
print(arr.__class__)
<class 'pandas.core.series.Series'>
<class 'pandas.core.frame.DataFrame'>
<class 'numpy.ndarray'>

然而,Python 中类的真正好处是我们可以创建自己的类,并用它们来解决特定的任务。让我们看看怎么做。

在 Python 中定义类

要定义一个 Python 类,使用class关键字,后跟新类的名称和冒号。让我们创建一个非常简单的空类:

class MyClass:
    pass

注意 Python 对类的命名约定:使用 camel case 样式,其中每个单词都以大写字母开头,单词写在一起,没有任何分隔符。另一个强烈推荐的约定是添加一个 docstring :简单描述类用途的类构造函数中的第一个字符串。让我们给我们的类添加一个 docstring:

class MyClass:
    '''This is a new class'''
    pass

上面的代码创建了一个名为MyClass的新类对象。有趣的是,因为 Python 中的每个对象都与某个类(类型)相关,所以我们的新类(以及所有其他 Python 类,包括内置类)的类/类型是 type:

print(MyClass.__class__)
print(type(MyClass))
<class 'type'>
<class 'type'>

不过,让我们回到主题上来。一般来说,当定义一个新类时,我们还会创建一个新的class object,它包含与该类相关的所有属性(变量和方法)。还有一些具有特殊意义的属性,那些以双下划线开始和结束的属性,并且与所有的类相关。例如,doc__属性返回该类的文档字符串,或者如果没有添加文档字符串,返回None

**要访问类属性,请使用点(.)运算符:

class TrafficLight:
    '''This is a traffic light class'''
    color = 'green'

    def action(self):
        print('Go')

print(TrafficLight.__doc__)
print(TrafficLight.__class__)
print(TrafficLight.color)
print(TrafficLight.action)

这是一堂交通灯课

<class 'type'>
green
<function TrafficLight.action at 0x00000131CD046160>

上面的TrafficLight类中定义的函数action()是一个类方法的例子,而变量color是一个类变量。

用 Python 声明类对象

我们可以将一个类对象赋给一个变量,创建该类的一个新对象(实例)。我们称这个过程为实例化。分配给变量的类对象是该类的副本,具有将该对象与同一类中的其他对象区分开的实值。回到我们的烹饪例子,这是一个由 370 克面粉(当食谱中的范围是 350-400)制成的蛋糕,上面有蜜饯水果装饰,而不是巧克力。

类对象具有以下属性:

  • 状态–对象的数据(变量)
  • 行为–对象的方法
  • 身份–对象的唯一名称

让我们将TrafficLight类的一个类对象赋给一个变量traffic:

class TrafficLight:
    '''This is a traffic light class'''
    color = 'green'

    def action(self):
        print('Go')

traffic = TrafficLight()

print(traffic.__doc__)
print(traffic.__class__)
print(traffic.color)
print(traffic.action)
print(traffic.action())

这是一堂交通灯课

<class '__main__.TrafficLight'>
green
<bound method TrafficLight.action of <__main__.TrafficLight object at 0x00000131CD0313D0>>
Go
None

我们创建了一个名为trafficTrafficLight类的新对象实例,并使用对象名(identity)访问其属性。注意新对象的类不同于类本身的类(**main**.TrafficLight而不是type。另外,action属性为类实例和类本身返回不同的值:第一种情况是方法对象,第二种情况是函数对象。调用 traffic对象上的action()方法(通过指定 traffic.action())给出“Go”,这是目前 TrafficLight类的这个方法唯一可能的输出。

在上面的代码中需要注意的另一件重要的事情是,我们在类内部的函数定义中使用了self 参数。然而,当我们在 traffic对象上调用action()方法时,我们没有添加任何参数。这就是self参数的工作方式:每当一个类实例调用它的一个方法时,对象本身作为第一个参数被传递。换句话说,traffic.action()相当于TrafficLight.action(traffic)。更一般地说,当我们用几个参数定义一个 Python 类方法,创建这个类的一个对象,并在这个对象上调用那个方法时,Python 根据下面的方案在幕后转换它:

就是使用一个适合使用这样一个电子游戏描述的网站来处理

因此,每个 Python 类方法都必须有这个额外的第一个参数来表示对象本身,即使它没有任何其他参数。根据 Python 的命名惯例,建议将其命名为self

让我们使我们的TrafficLight类更有意义,同时引入一个新的特殊方法:

class TrafficLight:
    '''This is an updated traffic light class'''
    def __init__(self, color):
        self.color = color

    def action(self):
        if self.color=='red':
            print('Stop & wait')
        elif self.color=='yellow':
            print('Prepare to stop')
        elif self.color=='green':
            print('Go')
        else:
            print('Stop drinking 😉')

yellow = TrafficLight('yellow')
yellow.action()
```py

Prepare to stop


我们使用`****init**()**` **方法**(又名`class constructor`)来初始化对象的状态(即在对象实例化的时候分配所有的类变量)。在这种情况下,我们创建了一个新的 Python 类`TrafficLight`,它有两个方法:`**init**()`初始化交通灯的颜色,`action()`根据交通灯的颜色建议相应的动作。

## 在 Python 中创建和删除对象属性

在实例化之后,可以向类对象添加新的属性:

green = TrafficLight('green')
yellow = TrafficLight('yellow')

green.next_color = 'red'

print(green.next_color)
print(yellow.next_color)


red



AttributeError Traceback (most recent call last)
~\AppData\Local\Temp/ipykernel_3860/40540793.py in
5
6 print(green.next_color)
----> 7 print(yellow.next_color)

AttributeError: 'TrafficLight' object has no attribute 'next_color'


在上面的代码中,我们为`green`对象创建了一个新属性`next_color`,并将其赋给“red”由于这个属性不在`TrafficLightclass`定义中,当我们试图检查另一个对象(`yellow`)时,我们得到一个`AttributeError`。

我们可以使用`del`语句删除一个类对象的任何属性:

print(yellow.color)
del yellow.color
print(yellow.color)


yellow



AttributeError Traceback (most recent call last)
~\AppData\Local\Temp/ipykernel_3860/1130729614.py in
1 print(yellow.color)
2 del yellow.color
----> 3 print(yellow.color)

AttributeError: 'TrafficLight' object has no attribute 'color'


<main.TrafficLight object at 0x00000131CAD50610>



NameError Traceback (most recent call last)
~\AppData\Local\Temp/ipykernel_3860/808519506.py in
1 print(yellow)
2 del yellow
----> 3 print(yellow)

NameError: name 'yellow' is not defined


## 类和实例变量

类和实例变量之间的区别在于它们的值在类定义中的赋值位置,这也决定了变量的范围。

*   **类变量:**在类声明中赋值,但在任何方法定义之外(包括类构造函数)。它们与该类的所有对象实例相关。
*   **实例变量:**在类方法或构造函数定义内部赋值。它们对于该类的每个对象实例都是唯一的。

class TrafficLight:
'''This is an updated traffic light class'''

# Class variable
traffic_light_address = 'NYC_Cranberry_Hicks'

def __init__(self, color):

    # Instance variable assigned inside the class constructor
    self.color = color

def action(self):
    if self.color=='red':

        # Instance variable assigned inside a class method
        self.next_color = 'yellow'
        print('Stop & wait')
    elif self.color=='yellow':
        self.next_color = 'green'
        print('Prepare to stop')
    elif self.color=='green':
        self.next_color = 'red'
        print('Go')
    else:
        self.next_color = 'Brandy'
        print('Stop drinking 😉')

Creating class objects

for c in ['red', 'yellow', 'green', 'fuchsia']:
c = TrafficLight(c)
print(c.traffic_light_address)
print(c.color)
c.action()
print(c.next_color)
print('\n')


NYC_Cranberry_Hicks
red
Stop & wait
yellow

NYC_Cranberry_Hicks
yellow
Prepare to stop
green

NYC_Cranberry_Hicks
green
Go
red

NYC_Cranberry_Hicks
fuchsia
Stop drinking 😉
Brandy


对于上面所有的类实例,我们有相同的`traffic_light_address`变量的值,这是一个类变量。在类构造函数内部分配了`color`变量,在`action()`类方法内部根据每个类实例的`color`值计算了`next_color`变量。这两个是实例变量。

## 结论

总而言之,我们已经讨论了创建和使用 Python 类的许多方面:

*   什么是 Python 类以及何时使用它们
*   不同类型的 Python 内置类
*   Python 对象的术语*类*和*类型*之间的关系
*   如何在 Python 中定义一个新类
*   类的 Python 命名约定
*   什么是类属性以及如何访问它们
*   如何实例化一个类对象
*   一个类对象的属性
*   什么是`self`参数及其工作原理
*   类构造函数的属性
*   如何访问、创建或删除类对象属性
*   类变量和实例变量的区别

现在您有了一个完整的工具包,可以开始使用 Python 中的类了!**

# 使用机器学习和自然语言处理工具进行文本分析

> 原文:<https://www.dataquest.io/blog/using-machine-learning-and-natural-language-processing-tools-for-text-analysis/>

February 28, 2022![](https://github.com/OpenDocCN/geekdoc-ds-zh/raw/master/dataquest-blog/img/ca7bd0308cc1e6822b1d947812edc0a1.png)

这是关于引导项目反馈分析主题的第三篇文章。该主题的主要思想是分析学习者在论坛页面上收到的回复。Dataquest 鼓励其学习者在其论坛上发布他们的指导项目,在发布后,其他学习者或工作人员可以分享他们对该项目的意见。我们对这些观点的内容感兴趣。

在我们的[上一篇文章](https://www.dataquest.io/blog/how-to-clean-and-prepare-your-data-for-analysis/)中,我们已经对数字数据进行了基本的数据分析,并深入分析了反馈文章的文本数据。

在本文中,我们将尝试使用多个包来增强我们的文本分析。我们不会为一项任务设定目标,而是会尝试各种工具,这些工具使用自然语言处理和/或机器学习来提供输出。

下面是我们将在本文中尝试的一些好东西:

*   情感分析
*   关键词提取
*   主题建模
*   k 均值聚类
*   一致
*   查询回答模型

使用机器学习进行自然语言处理是一个非常广泛的话题,不可能包含在一篇文章中。您可能会发现,从您的角度来看,本文中描述的工具并不重要。或者它们被错误地使用,它们中的大多数没有被调整,我们只是使用了开箱即用的参数。请记住,这是对用于增强反馈数据分析的软件包、工具和模型的主观选择。但是你也许能找到更好的工具或不同的方法。我们鼓励您这样做并分享您的发现。

# ML 与 NLP

机器学习和自然语言处理是两个非常宽泛的术语,可以涵盖文本分析和处理领域。我们不会试图在这两个术语之间设定一条固定的界限,我们会把它留给哲学家。如果你有兴趣探究它们之间的差异,请看这里。

# 情感分析

情感分析是 ML power 的一个非常流行的应用。通过分析每个文本的内容,我们可以评估句子或整个文本的权重是积极还是消极。如果你想过滤掉对你产品的负面评价或者只展示好的评价,这将会有巨大的价值。

网上有大量的 python 情感分析模型和工具。我们将关注最简单的一个:执行一个基本的情感分析需要两行代码:

```py
# import the package:
from pattern.en import sentiment
# perform the analysis:
x = 'project looks amazing, great job'
sentiment(x)

输出:

(0.7000000000000001, 0.825)

如上所示,导入包后,我们只需调用情感函数,为它提供一个字符串值,瞧!输出是一个元组,第一个值是“极性”(句子在从-1 到 1 的范围内有多积极)。第二个值是主观性,它告诉我们算法对其第一个值的评估有多确定,这次标度从 0 开始,到 1 结束。让我们再看几个例子:

y = 'plot looks terrible, spines are too small'
sentiment(y)

输出:

(-0.625, 0.7)

我们得到的是一个相当“低”的第一值,算法对其极性评估仍然很有信心。让我们试试更难的:

z = 'improve the comment, first line looks bad'
sentiment(z)

输出:

(-0.22499999999999992, 0.5)

我们可以注意到模型在这一点上更加犹豫。字符串有一个否定词,但它不再那么确定了。

让我们将情感函数应用于我们的反馈内容:

df['sentiment'] = df['feedback_clean2_lem'].apply(lambda x: sentiment(x))
df['polarity'] = df['sentiment'].str[0]
df['subjectivity'] = df['sentiment'].str[1]
df = df.drop(columns='sentiment')

现在,我们已经衡量了每个反馈帖子的极性和主观性,让我们看看每个主题有何不同:

top15 = df['short_title'].value_counts()[:15].index
df[df['short_title'].isin(top15)].groupby('short_title')[['polarity','subjectivity']].mean().sort_values('subjectivity')
简短标题 极性 主观性
sql 使用 0.227535 0.441175
中情局实况报道 0.232072 0.460941
汽车价格 0.206395 0.476976
易趣汽车 0.251605 0.498947
交通拥挤 0.219977 0.504832
星球大战 0.250845 0.50871
新闻黑客 0.288198 0.509783
离职员工 0.269066 0.512406
科普 0.276232 0.514718
app 盈利 0.281144 0.514833
纽约高中 0.288988 0.519288
fandango 收视率 0.265831 0.524607
性别差距 0.285667 0.534309
大学可视化 0.279269 0.547273
市场广告 0.279195 0.572073

不幸的是,结果非常相似。这并不奇怪,大多数反馈帖子都有非常相似的结构。开始时,他们通常会包含一两句对项目表示祝贺的话。这种正面的内容后面通常会跟着一些批评性的备注(通常被视为带有负极性的内容)。

帖子通常以一些对未来编码有益的信息结尾。本质上,这是一堆积极和消极情绪交织在一起的信息。不像产品评论那么简单,我们经常会遇到一个开心的客户或一个非常不开心的客户。大多数时候,对他们的评论进行分类并不困难。不幸的是,我们的内容更加复杂。但是我们不会这么轻易放弃。

关键词

从给定的字符串中提取关键字是另一个重要的技巧,可以改进我们的分析。

Rake 包提供了从文本中提取的所有 n 元文法及其权重的列表。该值越高,所考虑的 n-gram 就越重要。解析完文本后,我们可以只过滤掉具有最高值的 n 元语法。

请注意,该模型使用停用词来评估句子中哪些词是重要的。如果我们给这个模型输入清除了停用词的文本,我们不会得到任何结果。

from rake_nltk import Rake
# set the parameteres (length of keyword phrase):
r = Rake(include_repeated_phrases=False, min_length=1, max_length=3)
text_to_rake = df['feedback'][31]
r.extract_keywords_from_text(text_to_rake)
# filter out only the top keywords:
words_ranks = [keyword for keyword in r.get_ranked_phrases_with_scores() if keyword[0] > 5]
words_ranks

输出:

[(9.0, '“ professional ”'),
 (9.0, 'avoiding colloquial language'),
 (8.0, 'nicely structured project'),
 (8.0, 'also included antarctica'),
 (8.0, 'add full stops')]

在这个例子中,Rake 认为“专业”或“避免口语化语言”是输入文本中最重要的关键词。为了进一步分析,我们不会对关键字的数值感兴趣。我们只想收到每个帖子的几个热门关键词。我们将设计一个简单的函数,只提取顶部的关键字,并将其应用于“反馈”列:

def rake_it(text):
    r.extract_keywords_from_text(text)
    r.get_ranked_phrases()
    keyword_rank = [keyword for keyword in r.get_ranked_phrases_with_scores() if keyword[0] > 5]
    # select only the keywords and return them:
    keyword_list = [keyword[1] for keyword in keyword_rank]
    return keyword_list

df['rake_words'] = df['feedback'].apply(lambda x: rake_it(x))

把每个帖子的关键词都提取出来了,我们来看看哪些是最受欢迎的!记住它们是作为一个列表存储在一个单元格中的,所以我们必须处理这个障碍:

# function from: towardsdatascience.com/dealing-with-list-values-in-pandas-dataframes-a177e534f173
def to_1D(series):
    return pd.Series([x for _list in series for x in _list])

to_1D(df['rake_words']).value_counts()[:10]

输出:

jupyter file menu        24
guided project use       24
happy coding :)          22
everything looks nice    20
sql style guide          16
first guided project     16
everything looks good    15
new topic button         15
jupyter notebook file    14
first code cell          13
dtype: int64

主题建模

主题建模可以让我们快速洞察文本的内容。与从文本中提取关键词不同,主题建模是一种更高级的工具,可以根据我们的需要进行调整。

我们不打算冒险深入设计和实现这个模型,它本身可以填充几篇文章。我们将针对每个反馈内容快速运行该模型的基本版本。我们的目标是为每个帖子提取指定数量的主题。

我们将从一个反馈帖子开始。让我们导入必要的包,编译文本并创建所需的字典和矩阵(这是机器学习,因此每个模型都需要特定的输入):

# Importing:
import gensim
from gensim import corpora
import re

# compile documents, let's try with 1 post:
doc_complete = df['feedback_clean2_lem'][0]
docs = word_tokenize(doc_complete)
docs_out = []
docs_out.append(docs)

# Creating the term dictionary of our courpus, where every unique term is assigned an index. dictionary = corpora.Dictionary(doc_clean)
dictionary = corpora.Dictionary(docs_out)

# Converting a list of documents (corpus) into Document Term Matrix using dictionary prepared above.
doc_term_matrix = [dictionary.doc2bow(doc) for doc in docs_out]

做了所有的准备工作之后,就该训练我们的模型并提取结果了。您可能已经注意到,我们可以手动设置许多参数。举几个例子:话题的数量,每个话题我们用了多少单词。但是清单还在继续,就像许多 ML 模型一样,你可以花很多时间调整这些参数来完善你的模型。

# Running and Trainign LDA model on the document term matrix.
Lda = gensim.models.ldamodel.LdaModel
ldamodel = Lda(doc_term_matrix, num_topics=5, id2word = dictionary, passes=50, random_state=4)

# Getting results:
ldamodel.print_topics(num_topics=5, num_words=4)

输出:

[(0, '0.032*"notebook" + 0.032*"look" + 0.032*"process" + 0.032*"month"'),
 (1, '0.032*"notebook" + 0.032*"look" + 0.032*"process" + 0.032*"month"'),
 (2, '0.032*"important" + 0.032*"stay" + 0.032*"datasets" + 0.032*"process"'),
 (3,
  '0.032*"httpswww1nycgovsitetlcabouttlctriprecorddatapage" + 0.032*"process" + 0.032*"larger" + 0.032*"clean"'),
 (4, '0.113*"function" + 0.069*"inside" + 0.048*"memory" + 0.048*"ram"')]

我们可以注意到,我们的模型为我们提供了一个元组列表,每个元组包含单词及其权重。数字越高,单词越重要(根据我们的模型)。如果我们想深入研究,我们可以提取某个值以上的所有单词…只是一个想法🙂

让我们继续将该模型应用于每个反馈帖子。为了简化我们的生活,我们将只提取主题词,而不是“权重”值。这样,我们可以轻松地对提取的主题执行 value_counts,并查看哪些主题是最受欢迎的(根据模型)。为了对列中的每个单元格进行主题建模,我们将设计一个函数。作为输入值,我们将使用单元格的内容(文本)和主题的字数:

def get_topic(x,n):
    """
    extract list of topics given text(x) and number(n) of words for topic
    """
    docs = word_tokenize(x)
    docs_out = []
    docs_out.append(docs)
    dictionary = corpora.Dictionary(docs_out)
    doc_term_matrix = [dictionary.doc2bow(doc) for doc in docs_out]
    Lda = gensim.models.ldamodel.LdaModel
    # Running and Trainign LDA model on the document term matrix.
    ldamodel = Lda(doc_term_matrix, num_topics=5, id2word = dictionary, passes=50, random_state=1)
    topics = ldamodel.print_topics(num_topics=2, num_words=n)
    topics_list = []
    for elm in topics:
        content = elm[1]
        no_digits = ''.join([i for i in content if not i.isdigit()])
        topics_list.append(re.findall(r'\w+', no_digits, flags=re.IGNORECASE))
    return topics_list 

让我们看看最大长度为 4 个单词的主题是什么样子的:

df['topic_4'] = df['feedback_clean2_lem'].apply(lambda x: get_topic(x,4))
to_1D(df['topic_4']).value_counts()[:10]

输出:

[keep, nice]                                               8
[nice, perform]                                            4
[nice]                                                     4
[nan]                                                      4
[sale, take, look, finish]                                 2
[learning, keep, saw, best]                                2
[library, timezones, learning, httpspypiorgprojectpytz]    2
[job, graph, please, aesthetically]                        2
[thanks, think, nice, learning]                            2
[especially, job, like, nice]                              2
dtype: int64

现在,让我们检查最多 3 个词的主题:

df['topic_3'] = df['feedback_clean2_lem'].apply(lambda x: get_topic(x,3))
to_1D(df['topic_3']).value_counts()[:10]

输出:

[keep, nice]                       8
[share, thank, please]             4
[nan]                              4
[nice]                             4
[nice, perform]                    4
[guide, documentation, project]    3
[]                                 3
[cell]                             3
[guide, project, documentation]    3
[plot]                             3
dtype: int64

我们的结果不是很好,但是记住我们只是触及了这个工具的表面。谈到 lda 模型,有很大的潜力。我鼓励您至少阅读下面的一篇文章,以扩大您对这个库的了解:

  • 机械瓶颈
  • 走向数据科学

k 均值聚类

Kmeans 模型可以基于各种输入对数据进行聚类,这可能是最流行的无监督机器学习模型。只需选择您希望数据分配到多少个集群,基于什么功能和瞧。作为一个 ML 模型,我们不能只给它提供原始文本,我们必须对文本数据进行矢量化,然后给模型提供原始文本。本质上,我们将文本数据转换成数字数据。我们如何做取决于我们自己,有许多方法可以对数据进行矢量化,让我们试试 TfidfVectorizer:

# imports:
from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.cluster import KMeans

# vectorize text:
tfidfconverter = TfidfVectorizer(max_features=5000, min_df=0.1, max_df=0.7, stop_words=stopwords.words('english'))  
X = tfidfconverter.fit_transform(df['feedback']).toarray()

# fit and label:
Kmean = KMeans(n_clusters=8, random_state=2)
Kmean.fit(X)
df['label'] = Kmean.labels_

现在,让我们看看不同的集群是否有很大的极性差异:

df.groupby('label')['polarity'].mean()

输出:

label
0    0.405397
1    0.312328
2    0.224152
3    0.210876
4    0.143431
5    0.340016
6    0.242555
7    0.241244
Name: polarity, dtype: float64

实际上,让我们根据我们的模型分配的集群编号检查一些其他的编号:

 #group data:
polar = df.groupby('label')['polarity'].mean()
subj = df.groupby('label')['subjectivity'].mean()
level = df.groupby('label')['level'].mean()
val_cnt = df['label'].value_counts()
length = df.groupby('label')['len'].mean()

#create a df and rename some of the columns, index:
cluster_df = pd.concat([val_cnt,polar,subj,level, length], axis=1)
cluster_df = cluster_df.rename(columns={'label':'count'})
cluster_df.index.name = 'label'
cluster_df
标签 数数 极性 主观性 水* 低输入联网(low-entry networking 的缩写)
Zero Eighty-seven 0.405397 0.635069 1.49425 Three hundred and fourteen point two eight seven
one One hundred and fifty 0.312328 0.536363 1.55333 Seven hundred and forty-two point two five three
Two Sixty 0.224152 0.469265 One point five Five hundred and ninety-four point two six seven
three One hundred and thirty-six 0.210876 0.513048 1.46324 One thousand four hundred and twenty-nine point one
four Sixty-six 0.143431 0.34258 1.4697 Two hundred and fifty-one point two two seven
five One hundred and eighteen 0.340016 0.581554 1.29661 Nine hundred and three point one one
six Three hundred and two 0.242555 0.495008 1.45033 Seven hundred and twenty-four point two zero nine
seven Ninety-two 0.241244 0.431905 1.52174 Three hundred and ninety-eight point two two eight

我们可以在该表中注意到一些有趣的趋势,例如,0 号聚类具有相当积极的内容(高极性*均值),此外,我们之前在该聚类中使用的情感模型对其建模相当确定(高主观性值)。这可能是由该簇(314)中非常短的*均文本长度引起的。看着上面的表格,你有没有发现其他有趣的事实?

请记住,我们已经向 Kmeans 模型提供了使用 Tfidf 矢量化的数据,在将文本数据提供给模型之前,有多种方式对文本数据进行矢量化。你应该试试它们,看看它们对结果有什么影响。

句子聚类

如前所述:每个反馈帖子的内容是赞美和建设性批评的相当复杂的混合。这就是为什么我们的聚类模型在被要求对帖子进行聚类时表现不佳。但是,如果我们将所有的帖子分成句子,并要求模型将句子聚集起来,我们应该会改善我们的结果。

from nltk.corpus import stopwords
stop = set(stopwords.words('english'))
from nltk import sent_tokenize
from nltk import pos_tag
import string
from nltk.stem import WordNetLemmatizer
lemmatizer = WordNetLemmatizer()

# set up for cleaning and sentence tokenizing:
exclude = set(string.punctuation)
def clean(doc):
    stop_free = " ".join([i for i in doc.lower().split() if i not in stop])
    punc_free = ''.join(ch for ch in stop_free if ch not in exclude)
    return punc_free

# remember this function from article no 2?
def lemmatize_it(sent):
    empty = []
    for word, tag in pos_tag(word_tokenize(sent)):
        wntag = tag[0].lower()
        wntag = wntag if wntag in ['a', 'r', 'n', 'v'] else None
        if not wntag:
            lemma = word
            empty.append(lemma)
        else:
            lemma = lemmatizer.lemmatize(word, wntag)
            empty.append(lemma)
    return ' '.join(empty)

# tokenize sentences and clean them:
doc_complete = sent_tokenize(df['feedback'].sum())
doc_clean = [clean(doc) for doc in doc_complete] 
doc_clean_lemmed = [lemmatize_it(doc) for doc in doc_clean] 

# create and fill a dataframe with sentences:
sentences = pd.DataFrame(doc_clean_lemmed)
sentences.columns = ['sent']
sentences['orig'] = doc_complete
sentences['keywords'] = sentences['orig'].apply(lambda x: rake_it(x))

# vectorize text:
tfidfconverter = TfidfVectorizer(max_features=10, min_df=0.1, max_df=0.9, stop_words=stopwords.words('english'))  
X = tfidfconverter.fit_transform(sentences['sent']).toarray()

# fit and label:
Kmean = KMeans(n_clusters=8)
Kmean.fit(X)
sentences['label'] = Kmean.labels_

现在让我们来看看每个标签最受欢迎的关键词:

to_1D(sentences[sentences['label'] == 0]['keywords']).value_counts()[:10]

输出:

everything looks nice     15
everything looks good     13
sql style guide           10
print () function          8
social love attention      7
data cleaning process      7
looks pretty good          6
screen shot 2020           5
everything looks great     5
jupyter notebook file      5
dtype: int64
to_1D(sentences[sentences['label'] == 2]['keywords']).value_counts()[:10]

输出:

first code cell            8
avoid code repetition      7
items (): spine            4
might appear complex       4
may appear complex         4
might consider creating    3
1st code cell              3
print () function          3
little suggestion would    3
one code cell              3
dtype: int64

聚类 n 元文法

类似于对帖子和句子进行聚类,我们可以对 n 元语法进行聚类:

from nltk.util import ngrams 
import collections

# extract n-grams and put them in a dataframe:
tokenized = word_tokenize(sentences['sent'].sum())
trigrams = ngrams(tokenized, 3)
trigrams_freq = collections.Counter(trigrams)
trigram_df = pd.DataFrame(trigrams_freq.most_common())
trigram_df.columns = ['trigram','count']
trigram_df['tgram'] = trigram_df['trigram'].str[0]+' '+trigram_df['trigram'].str[1]+' '+trigram_df['trigram'].str[2]

# vectorize text:
tfidfconverter = TfidfVectorizer(max_features=100, min_df=0.01, max_df=0.9, stop_words=stopwords.words('english'))  
X = tfidfconverter.fit_transform(trigram_df['tgram']).toarray()

# fit and label:
Kmean = KMeans(n_clusters=8, random_state=1)
Kmean.fit(X)
trigram_df['label'] = Kmean.labels_

一致

https://avidml . WordPress . com/2017/08/05/natural-language-processing-concordance/

如果我们想检查一个特定的单词在文本中是如何使用的呢?我们想看看这个特定单词前后的单词。在 concordance 的一点帮助下,我们可以很快地看一看:

from nltk.text import Text 
text = nltk.Text(word_tokenize(df['feedback'].sum()))
text.concordance("plot", lines=10)

输出:

Displaying 10 of 150 matches:
ive precision . it is better to make plot titles bigger . about the interactiv
 '' ] what is the point of your last plot ? does it confirm your hypothesis th
ou use very similar code to create a plot – there is an opportunity to reduce 
er plots ” try to comment after each plot and not at the end so the reader doe
ur case saleprice , and after it you plot correlation only between remaining f
tation then possible and correlation plot may be have different colors and val
tting the format of the grid on your plot setting the ylabel as ‘ average traf
 line_data = series that you want to plot # start_hour = beginning hour of the
#start_hour = beginning hour of the plot **in 24hr format** # end_hour = end 
ormat** # end_hour = end hour of the plot **in 24hr format** def plot_traffic_

查询回答模型

老实说,从一个简单的查询回答模型开始,你不需要了解太多关于模型内部发生的巫术的具体机制。有必要了解一些基本知识:

  • 你必须把文本转换成向量和数组
  • 该模型比较该数字输入,并找到与该输入最相似的内容
  • 就这样,在您成功运行了一个基本模型之后,您应该开始尝试不同的参数和矢量化方法
# imports:
from sklearn.metrics import pairwise_distances
from sklearn.decomposition import TruncatedSVD
from sklearn.feature_extraction.text import CountVectorizer
from nltk.corpus import stopwords

stopword_list = stopwords.words('english')

# create a model:
dtm = CountVectorizer(max_df=0.7, min_df=5, token_pattern="[a-z']+", 
                      stop_words=stopword_list, max_features=6000)

dtm.fit(df['feedback_clean2_lem'])
dtm_mat = dtm.transform(df['feedback_clean2_lem'])
tsvd = TruncatedSVD(n_components=200)
tsvd.fit(dtm_mat)
tsvd_mat = tsvd.transform(dtm_mat)

# let's look for "slow function solution"
query = "slow function solution"
query_mat = tsvd.transform(dtm.transform([query]))

# calculate distances:
dist = pairwise_distances(X=tsvd_mat, Y=query_mat, metric='cosine')
# return the post with the smallest distance:
df['feedback'][np.argmin(dist.flatten())]

输出:

' processing data inside a function saves memory (the variables you create stay inside the function and are not stored in memory, when you are done with the function) it is important when you are working with larger datasets - if you are interested with experimenting: https://www1.nyc.gov/site/tlc/about/tlc-trip-record-data.page try cleaning 1 month of this dataset on kaggle notebook (and look at your ram usage) outside the function and inside the function, compare the ram usage in both examples '

请记住,我们正在解析以寻找答案的数据集相当小,因此我们不能期待令人兴奋的答案。

最后一句话

这并不是用于文本分析的一长串工具的结尾。我们几乎没有触及表面,我们使用的工具还没有得到最有效的利用。你应该继续寻找更好的方法,调整模型,使用不同的矢量器,收集更多的数据。

自然语言处理和机器学习的疯狂混合,是一个可以研究几十年的永无止境的话题。就在过去的 20 年里,这些工具给我们带来了惊人的应用,你还记得谷歌之前的世界吗?在网上搜索内容和看黄页非常相似。用我们的智能手机助手怎么样?这些工具不断变得更有效,值得你关注它们如何变得更好地理解我们的语言。

有什么问题吗?

随便伸手问我什么:
Dataquest , LinkedIn , GitHub

维克内什:“Dataquest 帮助我从一名科学教师转变为一名数据科学家”

原文:https://www.dataquest.io/blog/vicknesh-mano/

December 28, 2016

Vicknesh Mano 在新加坡当化学老师。获得化学信息学学位后,他意识到学术界不是他想从事的。他转行教书了。

“我是一名科学老师,但我想做得更多。我喜欢教书,但我想体验一下商业世界,把手弄脏。”

“我开始寻找在线学习的方式。我尝试了 Codecademy,谷歌了很多,然后我找到了 Dataquest。”

“它很好地*衡了覆盖不同范围的领域,但仍然很深入。我能够在一个地方学习熊猫、SQL、数据可视化和机器学习。”

维克内什开始申请工作,并获得了埃森哲的面试机会。正是在这里,Dataquest 教授算法的深入方法获得了回报。

没有 Dataquest 我不会有今天。

“他们让我解释 k-means 聚类算法背后的逻辑。这不是一个我经常使用的算法,但我记得用 Dataquest 学过它。在学习 scikit-learn 语法之前,我已经构建了一个基本版本的算法。”

"从我能记得的,我走了一遍算法是如何工作的."

维克尼斯给面试官留下了深刻的印象,并得到了这份工作。“他告诉我,大多数人无法回答算法是如何工作的,只能回答如何使用它们。”

“我认为埃森哲持开放态度是有帮助的。尽管我的学术背景并不完全一致,但当我在网上学到技能时,他们还是愿意面试我。”

维克尼什最初是一名商业情报分析师。“在我最初的几个项目中,我了解到 SQL 有多重要。它渗透到这里的每一件事——每一件事都使用 SQL 或 SQL 的衍生物。”

“刚开始的时候,我没有意识到数据分析师和数据科学家的区别。随着我了解的越来越多,我意识到我肯定更适合数据科学。”

Vicknesh 参加了埃森哲的内部数据科学竞赛。“我们有橄榄球比赛的数据。我们必须建立一个能做出最佳预测的模型,就像迷你卡格尔比赛一样。我们与埃森哲全球员工竞争,其中大多数是数据科学家。”他的团队在所有团队中排名前 25%。

“这让我很兴奋,并帮助我认识到我所知道的是有价值的和独特的。我意识到我可以从分析开始,然后成为一名数据科学家。”

Vicknesh 最*从他的分析师职位晋升到数据科学团队。“我能够与数据科学团队建立联系,并向他们展示我以前的工作。”

Vicknesh 对那些想过渡到数据科学的人有一些建议。“对您从 Dataquest 中学到的东西充满信心。如果你理解了他们教给你的东西,你将成为世界上少数几个能做你所做的事情的人之一。你属于数据科学家的世界。”

“如果没有 Dataquest,我就不会有今天。”

在线学习数据科学,视频好还是文字好?

原文:https://www.dataquest.io/blog/video-text-learn-data-science-online/

February 22, 2019

当谈到在线学习数据科学时,学生有各种各样的选择,但每个学生都必须做出的一个最基本的选择是,他们更喜欢通过阅读文本还是通过观看视频来学习。

大多数mooc,因为它们是基于传统的大学讲座形式,通过视频讲座展示他们的信息。即使在不隶属于大学的教育*台上,视频授课也是一种常见的方式。例如,Udemy 的顶级数据科学课程包括总共 41 小时的视频讲座

其他流行的在线数据科学和编码*台的教学方式不同,使用最少的视频,专注于基于文本的教学。例如,在 Dataquest,我们不使用任何*视频,而是通过你在下面看到的并排文本和交互式编码界面来展示我们所有的课程。

data cleaning live coding

显然,观看视频和阅读文本是有区别的,但是这些方法中哪一种更适合学习数据科学呢?

视频与文本

2018 年,作为麻省理工学院综合学习计划(MITili)的一部分,进行了一项关于这一主题的最*有趣的实验。研究人员要求学生报名参加学习课程,然后将他们分成小组,一组通过视频观看课程,另一组阅读经过编辑的视频文本,其中包括一些解释性图表。

会议结束后的第二天早上,两组都参加了同样的评估来测试理解和记忆,并且两组都接受了关于他们更喜欢的学习方法的调查。

研究人员假设学生们更喜欢看视频而不是阅读,而且看视频的人在课后评估中也会得分更高。但是他们发现恰恰相反。相比视频,更多的学生更喜欢阅读(30%对 20%,另外 50%喜欢其他形式的学习,如现场讲座和实践学习)。在课后评估中,通过阅读来阅读材料的学生比看视频的学生表现稍好一些,尽管这项研究的人数很少,这种差异在统计上并不显著。

这是一个在许多更大更科学的研究中得到证实的结果。通过视频学习和通过阅读学习之间的区别,就其本身而言,似乎对学生理解或保留材料的程度没有统计学影响。

那么,这是否意味着通过阅读或观看来学习数据科学并不重要?不。当我们具体谈论数据科学时,媒体格式本身并不是基于视频和基于文本的数据科学学习选项之间的唯一区别。

其他数据科学学习*台的巨大差异

在下图中,看看 Coursera 的约翰霍普金斯大学数据科学课程的用户界面与 Dataquest 的用户界面的对比。显然,这两个*台之间有一个超越视频的巨大差异:交互性。在约翰霍普金斯大学的课堂上(左),学生们可能会选择在右边空白处记笔记,但他们不会在任何地方写任何代码。在 Dataquest 屏幕(右)中,学生可以在左侧阅读,但在进入下一部分之前,他们还需要应用他们在屏幕右侧的编码框中所学的内容。

这种表述上的差异是可以理解的。大多数学生在观看视频时会努力编写代码,这将导致令人沮丧的用户体验,需要多次暂停和倒带。此外,Coursera 和其他普通教育*台并不支持现场编码或答案检查,实现这些功能将是困难和昂贵的。这样做的最终结果是,他们倾向于以视频形式呈现所有信息,在视频之后进行一个小测验,并希望学生在自己的时间里应用他们所学的知识。

Dataquest 和其他基于文本的*台通常采取相反的方法。我们要求学生动手操作,并在学习后直接应用他们所学的知识。由于在编程中总是有正确的答案(要么代码正确运行,要么不正确),这种动手实践的方法还允许学生获得关于他们理解和应用概念的程度的即时反馈。

这很重要,因为当谈到动手和被动学习时,科学非常清楚:动手学习更好。

衡量实践学习的价值

这是我们早就知道的事情。例如,在 20 世纪 80 年代,一项大规模的元研究观察了 57 项不同的学习研究,涵盖了总共 1000 多个教室和 13000 名学生,并得出结论,实践学习者的表现比通过更传统的、基于讲座的方法学习的学生*均好 20%

这些结果在最*的研究中也得到了相当一致的证实。一项 2014 年的元研究查看了 225 项先前的研究,发现在 STEM 课程中,通过讲座被动学习的学生失败的可能性是 t 2 的 1.5 倍。

当然,从基于视频的*台学习并不排除应用你的知识。你可以随时观看视频,然后在笔记本电脑上打开一个编码 IDE,开始应用你所学到的东西,每当你遇到困难时,就回头参考视频中的相关位置。但是这也有一些缺点:

  • 你不太可能真的去做,因为这不是强制性的,需要额外的步骤。
  • 如果你的代码运行但是产生了一个不正确的答案,你可能没有意识到你已经出错了,因为没有答案检查。
  • 当你陷入困境时,你必须在视频中寻找答案,这可能会令人沮丧和困难。
  • 你必须自己解决遇到的任何技术问题。

这些都不是不可逾越的障碍,但当您选择数据科学*台进行学习时,它们肯定是值得考虑的。如果你能够快速方便地应用你所学的东西,你的进步将会更有效率,你也不会试图跳过应用你所学的东西,因为这是一个额外的麻烦。

适合您的*台

你学习数据科学的效率取决于你的个人方法,而不是你选择的*台。没有一个*台能够为每个人提供学习数据科学的正确方法。

我们确实认为 Dataquest 对大多数人来说是最好的数据科学学习*台,因为我们的内容呈现得非常清楚,而且我们可以非常快速地让您用真实世界的数据集亲自动手编写代码。但是无论你最终在哪里学习,确保你有机会经常应用你所学的东西。从这方面的科学来看,很明显,如果你花更少的时间听,花更多的时间,你就更有可能成功实现你的学习目标。

您可以在 Dataquest 上免费学习数据科学编程的基础知识,无需信用卡!

今天就开始学习吧!*

可视化女性游行:第一部分

原文:https://www.dataquest.io/blog/visualizing-womens-marches-part-1/

March 26, 2018

为了庆祝妇女历史月,我想更好地了解 2017 年 1 月发生的妇女游行的规模。游行结束后不久,Vox 发布了一张地图,直观显示了全国的预计投票人数。

Vox Visualization

这张地图很好地展示了:

  • 相对道岔最高的位置
  • 游行发生的中心和聚集地

我立即有了许多后续问题,所以我进一步深入数据集,寻找其他潜在的数据源并创建自己的可视化。在这一系列的文章中,我将回顾我的探索,只关注发生在美国的游行。

在这篇文章中,我将回顾数据收集和清理。在第 2 部分中,我们将研究数据丰富和可视化。

我在这个系列中的主要目标是:

  • 强调大多数数据科学实际上是数据收集和数据清理
  • 使用数字和可视化来欣赏这些游行的规模
  • 能够问更多的问题并给出*似的答案

如果您想继续并重新创建这个过程,您需要熟悉 Python(特别是 pandas 和 matplotlib 库)和电子表格工具(Microsoft Excel 或 Google Sheets)。我还建议在你跟随的时候,打开我们的熊猫备忘单。

数据收集

我在这篇博文中使用的两个数据集都是由志愿者众包的。幸运的是,大部分的数据都有链接。不幸的是,这些源链接中的一些不再存在或可能不可靠(自我报告的计数)。这意味着我们应该避免得出实质性的结论,而是利用这些结论来提供一些总体趋势和见解。

社交媒体众包数据集

上面的地图是由教授艾丽卡·切诺维特和杰瑞米·普雷斯曼领导的众包数据工作生成的。

电子表格包含一些工作表,这些工作表大多是同一数据的不同视图。似乎第一个工作表( By Town/City )中关于美国游行的行数最多,所以我们将使用那个。

因为电子表格是公共文件,所以它被锁定以供编辑。让我们将感兴趣的行和列复制并粘贴到一个新的电子表格中,并下载为 CSV 文件。对于包含公式的列,在复制后重写公式很重要。

仅保留以下各列:

spreadsheet_preview

我们将在后面的数据清理小节中再次讨论这个数据集。

维基百科众包数据集

我发现的另一个很好的资源是一个维基百科页面,列出了所有的游行,由维基志愿者社区众包。

wiki_preview

下载这个数据集要痛苦得多,需要更多的实验。以下是我尝试过的两种不同的技巧,中途我放弃了:

  • 使用请求和 BeautifulSoup 获取并解析页面。
    • 问题:表格的 HTML 格式不一致,在提交大量 HTML 代码之前,我想尝试一些其他方法。
  • 使用 pandas.read_html() 函数读取整个网页,并尝试将相关表格解析为 DataFrame 对象。
    • 问题:同样的 HTML 格式不一致的问题,特别是关于每行的列数。产生的数据帧将需要大量的手工清理(几乎达到逐行转录的水*)。

试图从 HTML 代码生成数据帧的挑战在于格式必须完美。每一行都需要有相同的列数,否则产生的数据帧可能会出错。

另一方面,Wikipedia 将底层 HTML 代码呈现在一个很好的表格中,便于我们进行交互。我们可以执行手动文本选择来选择格式良好的行,并将其复制粘贴到其他地方。

这是我最终使用的工作流程:

  • 按照状态列对表格进行排序,以强制每一行包含相同数量的列(熊猫的要求),除了华盛顿州 DC 的行。wikipedia_sort
  • 用鼠标选择并复制上面的行、华盛顿 DC 行,包括带有列名的标题行。然后,我使用 pandas.read_clipboard() 将我的选择读入数据帧。这个函数是由 pandas 开发人员专门为复制小表格而实现的:pandas_read_clipboard
  • 用鼠标选择并复制位于华盛顿 DC 行下面的行。不幸的是,当我这次试图解析我的剪贴板时,Pandas 返回了一个错误,因为列数不一致。而是用免费工具 HTML 表格生成器生成一个 HTML 表格(文件>粘贴表格数据),用编辑器快速清理数据,导出 HTML。html_table_generator
  • 然后,我从这个清理后的表中生成新的 HTML 代码,将其复制到 Python string 对象中,然后使用pandas.read_html()将字符串读入 DataFrame。pandas_string_html
  • 最后,我在第一个数据帧中手动添加了一行,包含华盛顿 DC 的数据。

虽然这个工作流肯定是拼凑起来的,并不是真正可扩展的,但它让我很快获得了我需要的数据,而不必做任何不成熟的优化。此外,这更像是一个随意的项目,所以一个简单的方法就可以了!最终,更严格的分析需要建立强大的数据管道、维护数据质量的严格程序以及更可靠的数据集。

数据清理

现在是有趣的部分,清理数据集!

社交媒体众包数据集

因为这个数据集很小(只包含几百行和六列),所以使用像 Microsoft Excel 或 Google Sheets 这样的直接操作电子表格工具来执行一些数据清理真的很方便。当您滚动电子表格时,您会注意到数据中的一些问题:

  • 一些行缺少道岔编号值。
  • State栏的几个问题:
    • 两行的State列有--
    • 一行的 state 列有CA/NV
  • 一些行缺少州和国家的信息。
  • City_state列中的某些值的格式不符合样式:<city>, <state>
  • 一些行同时缺少StateCountry列的值。
  • 两行描述了不同日期发生在同一城市的游行(Salt Lake City, UT (1/20)Salt Lake City, UT (1/23))。

我们可以先用电子表格工具处理最后五个问题。

  • 删除State/Country列中带有--的行,因为它们似乎没有发生在美国的州或地区。
  • 对于State/Country列带有South Lake Tahoe, NV的行,将其编辑为South Lake Tahoe, NV,编辑为State列的NV
  • 对于City_state列中缺少城市的值(例如Cambridge,使用State值将其添加回来。
  • 对于逗号后没有州值的City_state列中的值,手动查找并修复(例如Christiansted, St. Croix应为维尔京群岛的Christiansted, VI)。
  • 对于包含额外信息的City_state列中的值,缩短(如Encinitas, CA (Seacrest)Encinitas, CA)。
  • 对于在StateCountry列中缺少值的行,手动将其添加回去。
  • 将两行合并为Salt Lake City, UT (1/20)Salt Lake City, UT (1/23)

这里有一个链接指向最终的电子表格。将其下载为 CSV 文件并加载到 pandas 数据帧中,以处理包含道岔编号缺失值的行的第一个问题。

import pandas as pd
all_marches = pd.read_csv("FinalSocialMedia.csv")
all_marches

| | 城市州 | 状态 | 国家 | 低的 | *均的 | 高的 |
| Zero | 德克萨斯州阿比林 | 谢谢 | 美国 | Two hundred | Two hundred | Two hundred |
| one | 事故,医学博士 | 医学博士 | 美国 | Fifty-four | Fifty-four | Fifty-four |
| Two | Adak, AK | 阿拉斯加 | 美国 | Ten | Ten | Ten |
| three | 密歇根州阿德里安 | 大调音阶的第三音 | 美国 | One hundred and fifty | One hundred and fifty | One hundred and fifty |
| four | 亚利桑那州阿霍 | 阿塞拜疆(Azerbaijan 的缩写) | 美国 | Two hundred and fifty | Two hundred and fifty | Two hundred and fifty |
| … | … | … | … | … | … | … |
| Six hundred and sixty-seven | 俄亥俄州黄泉市 | 俄亥俄州 | 美国 | Two hundred and fifty | Two hundred and fifty | Two hundred and fifty |
| Six hundred and sixty-eight | ypsilanti,我 | 大调音阶的第三音 | 美国 | One thousand two hundred | One thousand two hundred | One thousand two hundred |
| Six hundred and sixty-nine | 加利福尼亚州尤卡谷 | 加拿大 | 美国 | Seventy-six | One hundred and thirty-eight | Two hundred |
| Six hundred and seventy | 亚利桑那州尤马 | 阿塞拜疆(Azerbaijan 的缩写) | 美国 | Ten | Ten | Ten |
| Six hundred and seventy-one | 西布伦,走 | 通用航空 | 美国 | Thirty-five | Thirty-five | Thirty-five |

让我们计算每列缺少值的行数:

all_marches.isnull().sum()
 City_State     0
State          2
Country        1
Low           44
Average        0
High          44
dtype: int64

然后,调查State列的两行缺失值:

 all_marches[all_marches['State'].isnull()] 

| | 城市州 | 状态 | 国家 | 低的 | *均的 | 高的 |
| One hundred and forty-nine | 残疾(在线) | 圆盘烤饼 | 美国 | 圆盘烤饼 | Zero | 圆盘烤饼 |
| Six hundred and sixty-five | 马萨诸塞州伍斯特 | 圆盘烤饼 | 圆盘烤饼 | 圆盘烤饼 | Zero | 圆盘烤饼 |

看起来我们还发现了Country列缺少值的行。让我们删除这两行,因为一行是遗漏的投票数,另一行是在线发生的(这限制了我们在地图上可视化)。

 all_marches = all_marches.drop([149, 665], axis=0)
all_marches.isnull().sum()
 City_State     0
State          0
Country        0
Low           42
Average        0
High          42
dtype: int64

现在我们只需要处理包含LowHigh列缺失值的行。包含一列缺失值的行很可能也包含另一列的缺失值。尝试删除Low列中所有缺少值的行,看看是否能处理那些缺少High值的行:

 low_null = all_marches[~all_marches['Low'].isnull()]
low_null.isnull().sum()
 City_State    0
State         0
Country       0
Low           0
Average       0
High          0
dtype: int64

看来我们的假设是正确的!我们可以删除主数据帧的那些行,并将结果分配给新的数据帧。

sm_marches = all_marches[~all_marches['Low'].isnull()]

维基百科众包数据集

让我们从将两个数据帧导出到单独的 CSV 文件above_dc.csvbelow_dc.csv开始。然后,将它们导入电子表格工具。浏览两个电子表格,开始寻找数据质量问题。以下是我们需要解决的一些问题:

  • 包含照片的行溢出到两三行。wiki_spillover
  • CitiesApproximate Attendance列中为许多值([6])添加了引用链接的额外文本。numerous_issues
  • 在 pandas 中,许多无法解析为数字的范围实例。
    • 5,000 - 10,000
    • 70+
    • 100s (hundreds)
  • 某些State值中的重复或额外信息(如Georgia (U.S State) Georgia)。

让我们使用电子表格工具来解决所有这些问题(大约需要 30 分钟):

  • 找到所有分成两到三行的进行曲,手动设置它们的格式。
  • 使用以下规则替换所有格式错误的数值:
    • 用*均值替换像5,000 - 10,0000这样的范围。通过创建 2 列(一列为低,一列为高)来保留 rangaes 是很好的,但是这增加了我们手动清理的时间。
    • 仅用70替换像70+这样的值。
    • 仅用100替换像100s (hundreds)这样的值。
    • 对于任何无关紧要的小问题,使用你最好的判断。

最后,将两个电子表格合并成一个电子表格,并导出为 CSV 文件。这里有一个最终版本的链接: Google Sheets 链接

后续步骤

经过几个小时的数据收集和清理,我们有两个干净的数据集可以使用。在下一篇博文中,我们将重点关注用更多信息(例如地理数据)丰富数据集,计算一些汇总统计数据,并创建一些简单的可视化。

可视化女性游行:第二部分

原文:https://www.dataquest.io/blog/visualizing-womens-marches-part-2/

March 30, 2018This post is the second in a series on visualizing the Women’s Marches from January 2017. In the first post, we explored the intensive data collection and data cleaning process necessary to produce clean pandas dataframes.

数据丰富

因为我们最终希望能够构建可视化游行的地图,所以我们需要纬度和经度坐标。如果我们预览这两个数据帧,您会注意到我们遗漏了这两个数据帧的信息:

sm_marches.head()

| | 城市州 | 状态 | 国家 | 低的 | *均的 | 高的 |
| Zero | 德克萨斯州阿比林 | 谢谢 | 美国 | Two hundred | Two hundred | Two hundred |
| one | 事故,医学博士 | 医学博士 | 美国 | Fifty-four | Fifty-four | Fifty-four |
| Two | Adak, AK | 阿拉斯加 | 美国 | Ten | Ten | Ten |
| three | 密歇根州阿德里安 | 大调音阶的第三音 | 美国 | One hundred and fifty | One hundred and fifty | One hundred and fifty |
| four | 亚利桑那州阿霍 | 阿塞拜疆(Azerbaijan 的缩写) | 美国 | Two hundred and fifty | Two hundred and fifty | Two hundred and fifty |

wiki_marches.head()

| | 状态 | 城市 | 出动 |
| Zero | 得克萨斯州 | 阿比林 | Two hundred |
| one | 马里兰州 | 事故 | Fifty-four |
| Two | 阿拉斯加 | 数据,数据 | Ten |
| three | 密歇根 | 艾德里安(男子名) | One hundred and forty |
| four | 科罗拉多州 | 阿拉莫萨 | Three hundred and fifty |

另一个问题是一个数据帧使用状态代码(如

TX)而另一个使用完整的状态名(如Texas)。幸运的是,我们可以使用地理编码服务来解决缺失地理数据和状态表示的问题。地理编码是将地址转换成一对具体的经纬度坐标的过程。使用地理编码服务来改善数据集是数据丰富的一个例子。数据丰富在数据科学项目中非常常见,因为原始数据很少包含我们感兴趣的所有信息。虽然我们没有游行发生地点的具体地址,但地理编码服务将接受城市和州名的组合(例如,TXTexas的州市)。有许多地理编码服务,但我们将使用geocodeio,因为它是我熟悉的一种服务,并且有 2500 个免费查找层。geocodio_one-2我们可以通过上传电子表格文件或他们的 API 来使用 Geocodio。由于我们只有两个小的电子表格,我们将使用他们的上传工具。请注意,使用地理编码需要您创建一个帐户。以下是一些其他地理编码服务的列表,如果你不想使用 geocodeio:https://geo Services . tamu . edu/Services/Geocode/other geocoders/让我们使用 DataFrame.to_csv()sm_marches导出为 CSV 文件:

sm_marches.to_csv("sm_marches.csv", index=False)

上传两者

sm_marches.csvFinalWiki.csv到 Geocodio。该服务将要求您指定哪些列代表地址、州、国家、邮政编码等(您不需要拥有所有的字段)。然后,Geocodio 将在新列中添加该服务对每行的最佳猜测,并让您将新的 CSV 文件下载回您的计算机。最后,将这些文件重命名为FinalWiki_geocodio.csvsm_csv_geocodio.csv。让我们将这两个文件读回到 dataframes 中,并研究添加的列。

sm_gc = pd.read_csv("sm_csv_geocodio.csv")
wiki_gc = pd.read_csv("FinalWiki_geocodio.csv")
sm_gc.head()

| | 城市州 | 状态 | 国家 | 低的 | *均的 | 高的 | 纬度 | 经度 | 准确度分数 | 准确度类型 | 数字 | 街道 | 城市 | 国家. 1 | 县 | 活力 | 国家. 1 | 来源 |
| Zero | 德克萨斯州阿比林 | 谢谢 | 美国 | Two hundred | Two hundred | Two hundred | 32.576489 | -99.665323 | One | 地方 | 圆盘烤饼 | 圆盘烤饼 | 阿比林 | 谢谢 | 泰勒县 | Seventy-nine thousand six hundred and one | 美国 | 来自美国人口普查局的老虎/线数据集 |
| one | 事故,医学博士 | 医学博士 | 美国 | Fifty-four | Fifty-four | Fifty-four | 39.628700 | -79.319760 | One | 地方 | 圆盘烤饼 | 圆盘烤饼 | 事故 | 医学博士 | 加勒特县 | Twenty-one thousand five hundred and twenty | 美国 | 来自美国人口普查局的老虎/线数据集 |
| Two | Adak, AK | 阿拉斯加 | 美国 | Ten | Ten | Ten | 51.829438 | -176.629994 | One | 地方 | 圆盘烤饼 | 圆盘烤饼 | 数据,数据 | 阿拉斯加 | 阿留申群岛西部人口普查区 | Ninety-nine thousand five hundred and forty-six | 美国 | 来自美国人口普查局的老虎/线数据集 |
| three | 密歇根州阿德里安 | 大调音阶的第三音 | 美国 | One hundred and fifty | One hundred and fifty | One hundred and fifty | 41.889943 | -84.065892 | One | 地方 | 圆盘烤饼 | 圆盘烤饼 | 艾德里安(男子名) | 大调音阶的第三音 | Lenawee County | Forty-nine thousand two hundred and twenty-one | 美国 | 来自美国人口普查局的老虎/线数据集 |
| four | 亚利桑那州阿霍 | 阿塞拜疆(Azerbaijan 的缩写) | 美国 | Two hundred and fifty | Two hundred and fifty | Two hundred and fifty | 32.384890 | -112.890110 | One | 地方 | 圆盘烤饼 | 圆盘烤饼 | Ajo | 阿塞拜疆(Azerbaijan 的缩写) | 皮马县 | Eighty-five thousand three hundred and twenty-one | 美国 | 来自美国人口普查局的老虎/线数据集 |

wiki_gc.head()

| | 状态 | 城市 | 出动 | 纬度 | 经度 | 准确度分数 | 准确度类型 | 数字 | 街道 | 城市. 1 | 国家. 1 | 县 | 活力 | 国家 | 来源 |
| Zero | 得克萨斯州 | 阿比林 | Two hundred | 32.576489 | -99.665323 | One | 地方 | 圆盘烤饼 | 圆盘烤饼 | 阿比林 | 谢谢 | 泰勒县 | Seventy-nine thousand six hundred and one | 美国 | 来自美国人口普查局的老虎/线数据集 |
| one | 马里兰州 | 事故 | Fifty-four | 39.628700 | -79.319760 | One | 地方 | 圆盘烤饼 | 圆盘烤饼 | 事故 | 医学博士 | 加勒特县 | Twenty-one thousand five hundred and twenty | 美国 | 来自美国人口普查局的老虎/线数据集 |
| Two | 阿拉斯加 | 数据,数据 | Ten | 51.829438 | -176.629994 | One | 地方 | 圆盘烤饼 | 圆盘烤饼 | 数据,数据 | 阿拉斯加 | 阿留申群岛西部人口普查区 | Ninety-nine thousand five hundred and forty-six | 美国 | 来自美国人口普查局的老虎/线数据集 |
| three | 密歇根 | 艾德里安(男子名) | One hundred and forty | 41.889943 | -84.065892 | One | 地方 | 圆盘烤饼 | 圆盘烤饼 | 艾德里安(男子名) | 大调音阶的第三音 | Lenawee County | Forty-nine thousand two hundred and twenty-one | 美国 | 来自美国人口普查局的老虎/线数据集 |
| four | 科罗拉多州 | 阿拉莫萨 | Three hundred and fifty | 37.479933 | -105.790967 | One | 地方 | 圆盘烤饼 | 圆盘烤饼 | 阿拉莫萨 | 指挥官(commanding officer) | 阿拉莫萨县 | Eighty-one thousand one hundred and one | 美国 | 来自美国人口普查局的老虎/线数据集 |

Geocodio 增加了几个额外的列:

  • Latitude
  • Longitude
  • Accuracy Score
  • Accuracy Type
  • Number
  • Street
  • City(在一个数据帧中呈现为City.1,因为City已经存在)
  • State(在两个数据帧中呈现为State.1,因为State已经存在)
  • County(在一个数据帧中呈现为Country.1,因为Country已经存在)
  • Zip
  • Country
  • Source

让我们来确定哪些行具有 Geocodio 的高置信度得分,哪些行 Geocodio 根本无法进行地理编码。让我们计算置信度得分小于的行数

1(注意列名的细微差别):

sm_count = len(sm_gc[sm_gc['Accuracy Score'] < 1.00][['City_State', 'City', 'State.1', 'Latitude', 'Longitude']])
wiki_count = len(wiki_gc[wiki_gc['Accuracy Score'] < 1.00][['State', 'City', 'City.1', 'State.1', 'Latitude', 'Longitude']])
print(sm_count)
print(wiki_count)
32
16

由于没有太多的行,我们可以手动检查 Geocodio 不确定的每一行。让我们增加熊猫在 jupyter 笔记本中显示的行数,以便更容易验证每一行。

pd.options.display.max_rows = 50

这里有一个我们可以使用的工作流程:

  • 将原始城市和州的值与 geo codeo 返回的值进行比较,看它们是否匹配(或者 geo codeo 是否找到了匹配)。
    • 如果它们不匹配或完全丢失,请使用谷歌地图搜索位置,单击并按住轮廓区域的中心以放置一个点,然后复制纬度和经度坐标。创建一个字典,将行索引值添加到包含坐标:sm_replace_dict = { 15: [30.6266, 81.4609], ... }的列表中
    • 对于没有明确位置的游行(例如Hospital Ward, CA),将索引值存储在一个单独的列表中,我们将一次性删除这些行。

这是转换的结果代码

sm_gc:

 sm_replace_dict = {
    15: [30.6266, 81.4609],
    70: [37.191807, -108.078634],
    78: [42.914664, -78.864897],
    97: [37.555616, -76.304590],
    159: [35.047669, -108.323769],
    234: [48.199181, -120.773236],
    242: [39.245103, -76.914071],
    326: [39.956147, -74.922647],
    331: [41.379763, -70.649143],
    376: [30.013898, -90.013031],
    475: [17.716221, -64.831193],
    513: [46.486260, -84.355558],
    528: [32.459338, -93.769839],
    539: [38.978815, -119.932676],
    573: [44.224584, -74.462409],
    581: [43.095693, -75.229880]}
sm_drop = [238]

for k,v in sm_replace_dict.items():
    sm_gc.at[k, 'Latitude'] = v[0]
    sm_gc.at[k, 'Longitude'] = v[1]

for i in sm_drop:
    sm_gc = sm_gc.drop(i, axis=0)

这是转换的代码

wiki_gc:

 for k,v in wiki_replace_dict.items():
    wiki_gc.at[k, 'Latitude'] = v[0]
    wiki_gc.at[k, 'Longitude'] = v[1]

for i in wiki_drop:
    wiki_gc = wiki_gc.drop(i, axis=0)

让我们只选择要用于分析和可视化的列,并将它们分配给新的数据框架:

 sm_keep_cols = ['City_State', 'State', 'Average', 'Latitude', 'Longitude']
wiki_keep_cols = ['City', 'State', 'Turnout', 'Latitude', 'Longitude']

sm_ds = sm_gc[sm_keep_cols]
wiki_ds = wiki_gc[wiki_keep_cols]

最后,让我们将描述投票人数的两列都转换成数字列。

 wiki_ds['Turnout'] = pd.to_numeric(wiki_ds['Turnout'])
sm_ds['Average'] = pd.to_numeric(sm_ds['Average'].str.replace(",", ""))

现在我们准备好进行一些可视化了!

数据可视化

我们已经进行了多轮数据清理,所以让我们首先了解每个数据集中代表的游行次数:

 print(len(sm_ds))
print(len(wiki_ds))
627
506

可视化最受欢迎的游行

我们将从两个数据集中投票率最高的游行开始。让我们从导入 matplotlib 开始,并将样式设置为

五三八风格。虽然 fivethirtyeight matplotlib 风格提供了美学基线,但它并没有完全复制他们使用的情节。如果你有兴趣学习如何完全复制他们的风格,你应该看看如何用 Python 生成 FiveThirtyEight 图形。

%matplotlib inline
import matplotlib.pyplot as plt
import matplotlib.style as style
style.use('fivethirtyeight')

我们将使用

DataFrame.plot() 方法生成横条图,将 matplotlib Axes 对象赋给一个变量,然后使用方法修改每个图。

 colors = [[0,158/255,115/255]]sm_top_10_plot = sm_ds.sort_values(by='Average', ascending=False)[0:10].plot(x='City_State', y='Average', kind='bar', figsize = (10,6), fontsize=16, color=colors)
sm_top_10_plot.legend_.remove()

sm_top_10_plot.set_title("Top 10 Marches Crowdsourced From Social Media")
sm_top_10_plot.set_xlabel('City', fontsize=20)
sm_top_10_plot.set_ylabel('Turnout', fontsize=20) 

sm_top_10_plot-2让我们对来自维基百科的数据集进行复制:

 wiki_top_10_plot = wiki_ds.sort_values(by='Turnout', ascending=False)[0:10].plot(x='City', y='Turnout', kind='barh', figsize = (10,6), fontsize=16, color=colors)
wiki_top_10_plot.legend_.remove()

wiki_top_10_plot.set_title("Top 10 Marches Crowdsourced From Wikipedia")
wiki_top_10_plot.set_ylabel('City', fontsize=18, labelpad=20)
wiki_top_10_plot.set_xlabel('Turnout', fontsize=18, labelpad=20)

看起来,洛杉矶、华盛顿、DC、纽约和芝加哥的投票人数(均超过 20,000 人)非常突出。为了使这些条形图更容易比较,我们将:

  • 将状态名添加到wiki_ds(目前只包含状态码)并将状态码添加到sm_ds(目前只包含状态名)。
  • wiki_ds数据框中的CityState列合并成一个City_State列。
state_codes = ["AL", "AK", "AZ", "AR", "CA", "CO", "CT", "DC", "DE", "FL", "GA", 
          "HI", "ID", "IL", "IN", "IA", "KS", "KY", "LA", "ME", "MD", 
          "MA", "MI", "MN", "MS", "MO", "MT", "NE", "NV", "NH", "NJ", 
          "NM", "NY", "NC", "ND", "OH", "OK", "OR", "PA", "RI", "SC", 
          "SD", "TN", "TX", "UT", "VT", "VA", "WA", "WV", "WI", "WY"]

state_names = ["Alabama","Alaska","Arizona","Arkansas","California","Colorado",
  "Connecticut","District Of Columbia", "Delaware","Florida","Georgia","Hawaii","Idaho","Illinois",
  "Indiana","Iowa","Kansas","Kentucky","Louisiana","Maine","Maryland",
  "Massachusetts","Michigan","Minnesota","Mississippi","Missouri","Montana",
  "Nebraska","Nevada","New Hampshire","New Jersey","New Mexico","New York",
  "North Carolina","North Dakota","Ohio","Oklahoma","Oregon","Pennsylvania",
  "Rhode Island","South Carolina","South Dakota","Tennessee","Texas","Utah",
  "Vermont","Virginia","Washington","West Virginia","Wisconsin","Wyoming"]

# Which state codes aren't in the list?
sm_ds[~sm_ds['State'].isin(states)] 

| | 城市州 | 状态 | *均的 | 纬度 | 经度 |
| One hundred and two | 克里斯汀的家,我们 | 我们吗 | Four hundred and fifty | 17.734211 | -64.734694 |
| One hundred and thirty-one | VI .克鲁兹湾 | 我们吗 | Two hundred | 18.331343 | -64.793750 |
| One hundred and sixty-eight | 希望,博士 | 一对 | Three hundred and twenty-five | 18.097187 | -65.470714 |
| Two hundred and nineteen | 关岛哈加特那 | 克 | Two hundred | 13.444257 | 144.786297 |
| Four hundred and seventy-five | 圣克罗伊,六世 | 我们吗 | Two hundred and fifty | 17.716221 | -64.831193 |
| Four hundred and seventy-seven | 圣约翰六世 | 我们吗 | Sixty | 18.328160 | -64.740737 |
| Five hundred | 密苏里州圣胡安 | 一对 | Three hundred | 18.465901 | -66.103568 |
| Five hundred and ninety-one | 波多黎各别克斯 | 一对 | Two hundred | 18.123347 | -65.460356 |

现在让我们弄清楚哪些州名不在 50 个州名的列表中:

wiki_ds[~wiki_ds['State'].isin(state_names)]

| | 城市 | 状态 | 出动 | 纬度 | 经度 | 城市州 |
| Two hundred and sixty-three | 马亚圭斯 | 波多黎各 | Zero | 18.181938 | -67.133802 | 波多黎各马亚圭斯 |
| Three hundred and ninety | 圣胡安 | 波多黎各 | Zero | 18.465901 | -66.103568 | 波多黎各圣胡安 |
| Four hundred and one | 桑特尔塞 | 波多黎各 | Zero | 18.452679 | -66.078113 | 波多黎各圣乌尔茨 |
| Four hundred and thirty | 圣克罗伊 | 美属维尔京群岛 | Five hundred | 17.718837 | -64.807850 | 美属维尔京群岛圣克罗伊 |
| Four hundred and thirty-one | 圣约翰 | 美属维尔京群岛 | Two hundred | 18.337598 | -64.735584 | 美属维尔京群岛圣约翰 |
| Four hundred and thirty-eight | 圣托马斯 | 美属维尔京群岛 | Three hundred | 18.349667 | -64.930083 | 美属维尔京群岛圣托马斯 |
| Four hundred and seventy-four | 别克斯岛 | 波多黎各 | Two hundred | 18.123347 | -65.460356 | 波多黎各别克斯岛 |
| Four hundred and seventy-nine | 华盛顿哥伦比亚特区 | 华盛顿哥伦比亚特区 | Five hundred thousand | 38.886988 | -77.013516 | 华盛顿 DC,华盛顿 DC |

让我们删除描述不在美国大陆的游行的所有行(除了华盛顿特区之外的所有行),并手动处理描述华盛顿 DC 游行的行(因为这是一个边缘情况)。

 sm_ds = sm_ds.drop([102, 131, 168, 219, 475, 477, 500, 591])
wiki_ds = wiki_ds.drop([263, 390, 401, 430, 431, 438, 474])

wiki_ds.at[479, "City"] = "Washington"
wiki_ds.at[479, "State"] = "District Of Columbia"

现在,让我们添加一个

State_Codes和一个用于两个数据帧的State_Names列。

 sm_ds['State_Codes'] = sm_ds['State']
sm_ds['State_Names'] = ""

for key, row in sm_ds.iterrows():
    list_index = 0
    current_code = row['State_Codes']
    for counter, name in enumerate(state_codes):
        if name == current_code:
            list_index = counter
    sm_ds.at[key, 'State_Names'] = state_names[list_index]

wiki_ds['State_Names'] = wiki_ds['State']
wiki_ds['State_Codes'] = ""

for key, row in wiki_ds.iterrows():
    list_index = 0
    current_name = row['State_Names']
    for counter, name in enumerate(state_names):
        if name == current_name:
            list_index = counter
    wiki_ds.at[key, 'State_Codes'] = state_codes[list_index] 

这是什么

sm_ds看起来像是经过了这样的转变:

| | 城市州 | 状态 | *均的 | 纬度 | 经度 | 州代码 | 州名 |
| Zero | 德克萨斯州阿比林 | 谢谢 | Two hundred | 32.576489 | -99.665323 | 谢谢 | 得克萨斯州 |
| one | 事故,医学博士 | 医学博士 | Fifty-four | 39.628700 | -79.319760 | 医学博士 | 马里兰州 |
| … | … | … | … | … | … | … | … |
| Six hundred and twenty-six | 亚利桑那州尤马 | 阿塞拜疆(Azerbaijan 的缩写) | Ten | 32.701461 | -114.657232 | 阿塞拜疆(Azerbaijan 的缩写) | 亚利桑那州 |
| Six hundred and twenty-seven | 西布伦,走 | 通用航空 | Thirty-five | 33.077371 | -84.321736 | 通用航空 | 格鲁吉亚 |

在…期间

sm_ds包含一个友好的列,用于在我们的绘图中显示城市和州代码(City_State),wiki_ds数据框架没有。我们将通过连接两列来添加它:

wiki_ds['City_State'] = wiki_ds ['City'] + ', ' + wiki_ds['State_Codes']

最后,让我们创建一个 matplotlib 子图网格,2 行 1 列,并显示两个条形图。

fig = plt.figure(figsize=(8,12))
ax1 = fig.add_subplot(2,1,1)
ax2 = fig.add_subplot(2,1,2)

sm_top_10_plot = sm_ds.sort_values(by='Average', ascending=False)[0:10].plot(x='City_State', y='Average', kind='barh', ax=ax1, color=colors)
wiki_ds.sort_values(by='Turnout', ascending=False)[0:10].plot(x='City_State', y='Turnout', kind='barh', ax=ax2, color=colors)

ax1.legend_.remove()
ax1.set_title("Top 10 Marches Crowdsourced From Social Media \n")

ax2.legend_.remove()
ax2.set_title("Top 10 Marches Crowdsourced From Wikipedia \n")

combined_top_10_plot-1

可视化每个州的游行数量

现在让我们把每个州发生的游行数量形象化为柱状图。默认情况下,y 轴刻度标签使用浮点值。我们可以获取这些刻度标签,将它们转换为整数值,然后将它们设置回来。让我们从社交媒体数据集开始。

 sm_state_hist = sm_ds['State_Names'].value_counts().plot(kind='hist', figsize=(8,6), bins=15, title="Distribution Of Marches Per State")
sm_state_hist.set_xlabel("# Marches")
sm_state_hist.set_ylabel("# States")

y_labels = [int(item) for item in sm_state_hist.get_yticks().tolist()]
sm_state_hist.set_yticklabels(y_labels)

plt.savefig("sm_state_hist.png", bbox_inches="tight")

超过一半的州有大约 12 次或更少的游行。还有一个明显的异常状态,大约有 72 次游行。如果我们绘制维基百科数据集,我们观察到一个非常相似的直方图。

 wiki_state_hist = wiki_ds['State_Names'].value_counts().plot(kind='hist', figsize=(8,6), bins=15, title="Distribution Of Marches Per State. Source: Wikipedia")
wiki_state_hist.set_xlabel("# Marches")

wiki_state_hist.set_ylabel("# States")y_labels = [int(item) for item in wiki_state_hist.get_yticks().tolist()]
wiki_state_hist.set_yticklabels(y_labels) 

wiki_state_hist-1哪个状态是离群值?

print("State with the most # of marches:", sm_ds['State_Names'].value_counts().index[0])
California

虽然每个州的游行人数很有趣,但我们也可以深入了解每个州游行的总人数。这可能

有可能成为这些州集体力量和运动精神的更好指标。

可视化每个州的总投票率

让我们利用熊猫吧

groupby 和 aggregation 能够按州对两个数据框架中的所有游行进行分组,然后按每个州的总投票人数进行排序。

sm_ds.groupby('State_Names').agg(['count', 'sum'])['Average'].sort_values('count', ascending=False)[0:15]

| | 数数 | 总和 |
| 州名 | | |
| 加利福尼亚 | Seventy-seven | Nine hundred and forty-eight thousand six hundred and sixty-one |
| 哥伦比亚特区 | one | Seven hundred and fifty thousand |
| 纽约 | Twenty-eight | Five hundred and two thousand four hundred and thirty-six |
| 伊利诺伊 | nine | Two hundred and sixty-two thousand one hundred and forty-five |
| 马萨诸塞州 | Fifteen | One hundred and eighty-three thousand five hundred and ninety-six |
| 华盛顿 | Thirty-three | One hundred and seventy-seven thousand four hundred and thirty-nine |
| 科罗拉多州 | Twenty-five | One hundred and sixty-seven thousand four hundred and seventy |
| 俄勒冈州 | Twenty-five | One hundred and eighteen thousand eight hundred and twenty |
| 明尼苏达州 | Twelve | Ninety-eight thousand four hundred and ten |
| 得克萨斯州 | Twenty | Ninety-five thousand four hundred and fifteen |
| 佛罗里达州 | Twenty-one | Ninety-three thousand four hundred and forty-one |
| 威斯康星州 | Seventeen | Ninety-one thousand and ninety-eight |
| 宾夕法尼亚州 | Twenty-four | Eighty-seven thousand and eighteen |
| 格鲁吉亚 | six | Sixty-six thousand one hundred and eighty-five |
| 北卡罗来纳州 | Sixteen | Sixty-one thousand seven hundred and seventy-eight |

哥伦比亚特区、伊利诺伊州和佐治亚州脱颖而出是因为

很少有游行会导致如此高的投票率。维基百科数据集也是如此:

wiki_ds.groupby('State_Names').agg(['count', 'sum'])['Turnout'].sort_values('sum', ascending=False)[0:15]

| | 数数 | 总和 |
| 州名 | | |
| 加利福尼亚 | Sixty-two | One million one hundred and eighty-four thousand nine hundred and thirty |
| 哥伦比亚特区 | one | Five hundred thousand |
| 纽约 | Twenty-five | Four hundred and fifty-two thousand nine hundred and sixteen |
| 伊利诺伊 | nine | Two hundred and sixty thousand five hundred and ninety-five |
| 华盛顿 | Twenty-seven | Two hundred and thirteen thousand eight hundred and ninety-three |
| 马萨诸塞州 | Ten | One hundred and sixty-nine thousand five hundred and fifty-three |
| 科罗拉多州 | Sixteen | One hundred and sixty-two thousand three hundred and forty-nine |
| 俄勒冈州 | Twenty-four | One hundred and twenty-seven thousand eight hundred and fifty-six |
| 明尼苏达州 | Twelve | Ninety-eight thousand two hundred and seventy-four |
| 得克萨斯州 | Twenty | Eighty-six thousand three hundred and thirty-five |
| 宾夕法尼亚州 | Seventeen | Eighty-three thousand five hundred and forty |
| 佛罗里达州 | Twenty | Seventy-eight thousand three hundred and seventy |
| 格鲁吉亚 | six | Sixty-two thousand five hundred and thirty-five |
| 北卡罗来纳州 | Eleven | Fifty-nine thousand two hundred and seventy |
| 亚利桑那州 | six | Thirty-eight thousand four hundred and fifty |

在地图上可视化

我们将通过在美国地图上可视化游行来结束这篇文章

follow包,它提供了一个用于生成交互式地图(以 HTML、CSS 和 JavaScript 呈现)的 Python API。不幸的是,我们的博客目前不支持嵌入式 javascript,所以我们将在这里包含截图。要在 lyum 中创建地图,我们首先需要导入相关模块,然后创建一个folium.folium.Map对象:

 from folium.plugins import MarkerCluster
from folium import plugins
from folium.plugins import HeatMap

# Map obj
map_clusters = folium.Map()

然后,我们从数据帧中提取纬度和经度值,将其转换为 numpy 对象,将这些对象传递到

MarkerCluster()构造函数,然后使用MarkerCluster.add_to()方法将标记集群链接到我们创建的地图对象。

 data = sm_ds[['Latitude', 'Longitude']].values
MarkerCluster(data).add_to(map_clusters)

map_clusters

这是地图放大两倍后的样子,其中只显示了游行的集群:

随着我们进一步放大,单个的游行会显示为标记,而不是群集。map_two-1如果我们再放大一个项目,只关注美国大陆(不包括阿拉斯加、维京群岛和夏威夷),我们可以观察到活动的奇妙分布。map_three-1这张地图让我们直观地了解游行的范围:gradient-1我们强烈建议您自己制作这些互动地图,这样您可以更有效地探索不同的游行。

https://s3.amazonaws.com/dq-content/blog/folium_zoom.mp4

后续步骤

从视觉化的角度来看,你还可以探索更多的东西。以下是后续步骤的一些想法:

  • 通过道岔值缩放地图上的聚类和标记的半径(就像在原始的 Vox 可视化中一样)。
  • 创建分组条形图,显示来自数据集的计数。
  • 创建一个融合了来自两个数据集的数据的地图。
  • 将县级选举结果数据添加到地图中以增强游行。
  • 在 Vox 这里和这里重新创建可视化效果。

教程:网页抓取和美化

原文:https://www.dataquest.io/blog/web-scraping-beautifulsoup/

June 29, 2017html-web-scraping-beautifulsoup

为了获取数据科学项目的数据,你通常会依赖于 SQL 和 NoSQL 数据库、API或现成的 CSV 数据集。

问题是你不能总是找到关于你的主题的数据集,数据库不能保持最新,API 要么很贵,要么有使用限制。

然而,如果你要找的数据在网页上,那么所有这些问题的解决方案就是网络抓取

在本教程中,我们将学习使用 BeautifulSoup 和请求用 Python 抓取多个网页。然后我们将使用熊猫和 matplotlib 执行一些简单的分析。

你应该已经对 HTML 有了一些基本的了解,对 Python 的基础有了很好的掌握,对什么是 web 抓取有了一个大概的概念。如果你对这些不放心,我推荐这本初学网页抓取教程。

收集 2000 多部电影的数据

我们想分析一下 IMDB 和 Metacritic 电影评分的分布,看看我们是否能发现什么有趣的东西。为此,我们将首先收集 2000 多部电影的数据。

从一开始就确定我们刮擦的目标是至关重要的。编写一个抓取脚本会花费很多时间,尤其是当我们想要抓取多个网页的时候。我们希望避免花费数小时编写脚本来抓取我们实际上并不需要的数据。

计算要刮哪一页

一旦我们确立了目标,我们就需要确定一组有效的页面来抓取。

我们希望找到一个需要相对较少请求的页面组合。一个请求是每当我们访问一个网页时发生的事情。我们从服务器“请求”页面的内容。我们发出的请求越多,脚本运行的时间就越长,服务器的压力就越大。

获取我们需要的所有数据的一种方法是编译一个电影名称列表,并使用它来访问 IMDB 和 Metacritic 网站上每部电影的网页。

option1

由于我们希望从 IMDB 和 Metacritic 获得超过 2000 个评级,我们必须发出至少 4000 个请求。如果我们每秒发出一个请求,我们的脚本将需要一个多小时来发出 4000 个请求。正因为如此,有必要尝试找出更有效的方法来获取我们的数据。

如果我们浏览 IMDB 网站,我们可以发现将请求数量减半的方法。IMDB movie 页面上显示了 Metacritic 评分,因此我们可以通过一个请求获得两个评分:

如果我们进一步研究 IMDB 站点,我们可以发现如下所示的页面。它包含了我们需要的 50 部电影的所有数据。考虑到我们的目标,这意味着我们只需要做大约 40 个请求,比我们的第一个选项少 100 倍。让我们进一步探讨最后一个选项。

识别 URL 结构

我们现在面临的挑战是,当我们想要抓取的页面发生变化时,要确保我们理解 URL 的逻辑。如果我们不能充分理解这个逻辑,那么我们就可以用代码来实现它,然后我们就会走进死胡同。

如果你上 IMDB 的高级搜索页面,你可以按年份浏览电影:

我们按 2017 年来浏览,第一页的电影按票数排序,然后切换到下一页。我们将到达这个网页,它有这个 URL:

在上图中,您可以看到 URL 在问号后面有几个参数:

  • release_date —仅显示特定年份上映的电影。
  • sort —对页面上的电影进行排序。sort=num_votes,desc翻译成按票数降序排序
  • page —指定页码。
  • ref_ —将我们带到下一页或上一页。参考是我们当前所在的页面。adv_nxtadv_prv是两个可能的值。它们分别翻译为前进到下一页,和前进到上一页

如果浏览这些页面并观察 URL,您会注意到只有参数值发生了变化。这意味着我们可以编写一个脚本来匹配变化的逻辑,并发出更少的请求来抓取我们的数据。

让我们通过请求这个单个网页的内容来开始编写脚本:[https://www.imdb.com/search/title?release_date=2017&sort=num_votes,desc&page=1](https://www.imdb.com/search/title?release_date=2017&amp;sort=num_votes,desc&amp;page=1)。在下面的代码单元中,我们将:

  • requests模块导入get()函数。
  • 将网页的地址分配给一个名为url的变量。
  • 使用get()向服务器请求网页内容,并将服务器的响应存储在变量response中。
  • 通过访问.text属性打印response的一小部分内容(response现在是一个Response对象)。
from requests import get
url = 'https://www.imdb.com/search/title?release_date=2017&sort=num_votes,desc&page=1'
response = get(url)
print(response.text[:500])
<!DOCTYPE html><
htmlxmlns:og="https://ogp.me/ns#"xmlns:fb="https://www.facebook.com/2008/fbml">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="apple-itunes-app" content="app-id=342792525, app-argument=imdb:///?src=mdot">
<script type="text/javascript">
var ue_t0=window.ue_t0||+new Date();</script>
<script type="text/javascript">
var ue_mid = "A1EVAM02EL8SFB";

理解单个页面的 HTML 结构

response.text的第一行可以看到,服务器给我们发来了一个 HTML 文档。该文档描述了该网页的整体结构,以及它的特定内容(这是该特定网页的独特之处)。

我们要抓取的所有页面都有相同的整体结构。这意味着它们也有相同的整体 HTML 结构。因此,要编写我们的脚本,只需要理解一个页面的 HTML 结构就足够了。为此,我们将使用浏览器的开发工具

如果你使用 Chrome ,右击你感兴趣的网页元素,然后点击 Inspect 。这将把您带到对应于该元素的 HTML 行:

右键点击电影名称,然后左键检查。以灰色突出显示的 HTML 行对应于用户在网页上看到的电影名称。

你也可以同时使用 Firefox 和 Safari DevTools 来完成这项工作。

注意,每部电影的所有信息,包括海报,都包含在一个div标签中。

每个div标签中都嵌套了许多 HTML 行。您可以通过点击对应于每个div的 HTML 行左边的灰色小箭头来探索它们。在这些嵌套的标签中,我们将找到我们需要的信息,比如电影的分级。

每页显示 50 部电影,所以每个电影都应该有一个div容器。让我们通过解析前面请求中的 HTML 文档来提取所有这 50 个容器。

使用 BeautifulSoup 解析 HTML 内容

为了解析我们的 HTML 文档并提取 50 个div容器,我们将使用一个名为 BeautifulSoup 的 Python 模块,这是 Python 最常见的 web 抓取模块。

在下面的代码单元中,我们将:

  • 从包bs4中导入BeautifulSoup类创建器。
  • 通过创建一个BeautifulSoup对象来解析response.text,并将这个对象分配给html_soup'html.parser'参数表明我们想要使用 Python 的内置 HTML 解析器来进行解析。
from bs4 import BeautifulSoup
html_soup = BeautifulSoup(response.text, 'html.parser')
type(html_soup)
bs4.BeautifulSoup

在提取 50 个div容器之前,我们需要找出它们与页面上其他div元素的区别。通常,与众不同的标志存在于class 属性中。如果您检查感兴趣的容器的 HTML 行,您会注意到class属性有两个值:lister-itemmode-advanced。这种组合是这些div容器所独有的。我们可以通过快速搜索(Ctrl + F)来了解这一点。我们有 50 个这样的容器,所以我们预计只看到 50 个匹配:

现在让我们使用find_all() 方法提取所有具有lister-item mode-advancedclass属性的div容器:

movie_containers = html_soup.find_all('div', class_ = 'lister-item mode-advanced')
print(type(movie_containers))
print(len(movie_containers))
<class 'bs4.element.ResultSet'>
50

find_all()返回一个ResultSet对象,它是一个包含我们感兴趣的所有 50 个divs的列表。

现在我们将只选择第一个容器,并依次提取每个感兴趣的项目:

  • 电影的名字。
  • 发行年份。
  • IMDB 评级。
  • Metascore。
  • 票数。

提取单部电影的数据

我们可以通过在movie_containers上使用列表符号来访问第一个容器,它包含关于一部电影的信息。

first_movie = movie_containers[0]
first_movie
<div class="lister-item mode-advanced">
<div class="lister-top-right">
<div class="ribbonize" data-caller="filmosearch" data-tconst="tt3315342"></div>
</div>
<div class="lister-item-image float-left">
<a href="/title/tt3315342/?ref_=adv_li_i"> <img alt="Logan" class="loadlate" data-tconst="tt3315342" height="98" loadlate="https://images-na.ssl-images-amazon.cimg/M/[[email protected]](/cdn-cgi/l/email-protection)_V1_UX67_CR0,0,67,98_AL_.jpg" src="https://ia.media-imdb.cimg/G/01/imimg/nopicture/large/film-184890147._CB522736516_.png" width="67"/>
</a> </div>
<div class="lister-item-content">
<h3 class="lister-item-header">
<span class="lister-item-index unbold text-primary">1.</span>
<a href="/title/tt3315342/?ref_=adv_li_tt">Logan</a>
<span class="lister-item-year text-muted unbold">(2017)</span>
</h3>
<p class="text-muted ">
<span class="certificate">R</span>
<span class="ghost">|</span>
<span class="runtime">137 min</span>
<span class="ghost">|</span>
<span class="genre">
Action, Drama, Sci-Fi </span>
</p>
<div class="ratings-bar">
<div class="inline-block ratings-imdb-rating" data-value="8.3" name="ir">
<span class="global-sprite rating-star imdb-rating"></span>
<strong>8.3</strong>
</div>
<div class="inline-block ratings-user-rating">
<span class="userRatingValue" data-tconst="tt3315342" id="urv_tt3315342">
<span class="global-sprite rating-star no-rating"></span>
<span class="rate" data-no-rating="Rate this" data-value="0" name="ur">Rate this</span>
</span>
<div class="starBarWidget" id="sb_tt3315342">
<div class="rating rating-list" data-auth="" data-ga-identifier="" data-starbar-class="rating-list" data-user="" id="tt3315342|imdb|8.3|8.3|||search|title" itemprop="aggregateRating" itemscope="" itemtype="https://schema.org/AggregateRating" title="Users rated this 8.3/10 (320,428 votes) - click stars to rate">
<meta content="8.3" itemprop="ratingValue"/>
<meta content="10" itemprop="bestRating"/>
<meta content="320428" itemprop="ratingCount"/>
<span class="rating-bg"> </span>
<span class="rating-imdb " style="width: 116.2px"> </span>
<span class="rating-stars">
<a href="/register/login?why=vote&ref_=tt_ov_rt" rel="nofollow" title="Register or login to rate this title"><span>1</span></a>
<a href="/register/login?why=vote&ref_=tt_ov_rt" rel="nofollow" title="Register or login to rate this title"><span>2</span></a>
<a href="/register/login?why=vote&ref_=tt_ov_rt" rel="nofollow" title="Register or login to rate this title"><span>3</span></a>
<a href="/register/login?why=vote&ref_=tt_ov_rt" rel="nofollow" title="Register or login to rate this title"><span>4</span></a>
<a href="/register/login?why=vote&ref_=tt_ov_rt" rel="nofollow" title="Register or login to rate this title"><span>5</span></a>
<a href="/register/login?why=vote&ref_=tt_ov_rt" rel="nofollow" title="Register or login to rate this title"><span>6</span></a>
<a href="/register/login?why=vote&ref_=tt_ov_rt" rel="nofollow" title="Register or login to rate this title"><span>7</span></a>
<a href="/register/login?why=vote&ref_=tt_ov_rt" rel="nofollow" title="Register or login to rate this title"><span>8</span></a>
<a href="/register/login?why=vote&ref_=tt_ov_rt" rel="nofollow" title="Register or login to rate this title"><span>9</span></a>
<a href="/register/login?why=vote&ref_=tt_ov_rt" rel="nofollow" title="Register or login to rate this title"><span>10</span></a>
</span>
<span class="rating-rating "><span class="value">8.3</span><span class="grey">/</span><span class="grey">10</span></span>
<span class="rating-cancel "><a href="/title/tt3315342/vote?v=X;k=" rel="nofollow" title="Delete"><span>X</span></a></span>
</div>
</div>
</div>
<div class="inline-block ratings-metascore">
<span class="metascore favorable">77 </span>
Metascore
</div>
</div>
<p class="text-muted">
In the near future, a weary Logan cares for an ailing Professor X somewhere on the Mexican border. However, Logan's attempts to hide from the world and his legacy are upended when a young mutant arrives, pursued by dark forces.</p>
<p class="">
Director:
<a href="/name/nm0003506/?ref_=adv_li_dr_0">James Mangold</a>
<span class="ghost">|</span>
Stars:
<a href="/name/nm0413168/?ref_=adv_li_st_0">Hugh Jackman</a>,
<a href="/name/nm0001772/?ref_=adv_li_st_1">Patrick Stewart</a>,
<a href="/name/nm6748436/?ref_=adv_li_st_2">Dafne Keen</a>,
<a href="/name/nm2933542/?ref_=adv_li_st_3">Boyd Holbrook</a>
</p>
<p class="sort-num_votes-visible">
<span class="text-muted">Votes:</span>
<span data-value="320428" name="nv">320,428</span>
<span class="ghost">|</span> <span class="text-muted">Gross:</span>
<span data-value="226,264,245" name="nv">$226.26M</span>
</p>
</div>
</div>

如您所见,一个容器的 HTML 内容非常长。为了找出特定于每个数据点的 HTML 行,我们将再次使用 DevTools。

电影的名字

我们从电影的名字开始,通过使用 DevTools 找到它对应的 HTML 行。您可以看到该名称包含在锚标记(<a>)中。这个标签嵌套在一个 header 标签中(<h3>)。<h3>标签嵌套在<div>标签中。这个<div>是嵌套在第一部电影的容器中的第三个divs。我们将这个容器的内容存储在first_movie变量中。

first_movie是一个Tag 对象,其中的各种 HTML 标签被存储为其属性。我们可以像访问 Python 对象的任何属性一样访问它们。但是,使用标签名称作为属性只会选择该名称的第一个标签。如果我们运行first_movie.div,我们只得到第一个div标签的内容:

first_movie.div
<div class="lister-top-right">
<div class="ribbonize" data-caller="filmosearch" data-tconst="tt3315342"></div></div>

访问第一个锚标签(<a>)不会将我们带到电影的名称。第一个<a>在第二个div的某个地方:

first_movie.a
<a href="/title/tt3315342/?ref_=adv_li_i"> <img alt="Logan" class="loadlate" data-tconst="tt3315342" height="98" loadlate="https://images-na.ssl-images-amazon.cimg/M/[[email protected]](/cdn-cgi/l/email-protection)_V1_UX67_CR0,0,67,98_AL_.jpg" src="https://ia.media-imdb.cimg/G/01/imimg/nopicture/large/film-184890147._CB522736516_.png" width="67"/></a>

然而,访问第一个<h3>标签使我们非常接*:

first_movie.h3
<h3 class="lister-item-header">
<span class="lister-item-index unbold text-primary">1.</span>
<a href="/title/tt3315342/?ref_=adv_li_tt">Logan</a
><span class="lister-item-year text-muted unbold">(2017)</span>
</h3>

从这里开始,我们可以使用属性符号来访问<h3>标签中的第一个<a>:

first_movie.h3.a
<a href="/title/tt3315342/?ref_=adv_li_tt">Logan</a>

现在只需要从那个<a>标签中访问文本:

first_name = first_movie.h3.a.text
first_name
'Logan'

电影上映的年份

我们继续提取年份。该数据存储在包含名称的<a>下面的<span>标记中。

点符号将只访问第一个span元素。我们将通过第二个<span>的特殊标记进行搜索。我们将使用find() 方法,它与find_all()几乎相同,只是它只返回第一个匹配。事实上,find()相当于find_all(limit = 1)limit 参数将输出限制为第一个匹配。

区别标记由分配给class属性的值lister-item-year text-muted unbold组成。因此,我们在<h3>标记中寻找具有这些值的第一个<span>:

first_year = first_movie.h3.find('span', class_ = 'lister-item-year text-muted unbold')
first_year
<span class="lister-item-year text-muted unbold">(2017)</span>

从这里开始,我们只需使用属性符号来访问文本:

first_year = first_year.text
first_year
'(2017)'

我们可以很容易地清理输出并将其转换为整数。但是如果你浏览更多的页面,你会注意到,对于一些电影来说,年份是不可预测的值,比如(2017)(I)或(2015)(V)。刮完后再做清洁会更有效率,因为那时我们会知道所有的年值。

IMDB 评级

我们现在集中于提取第一部电影的 IMDB 评级。

有几种方法可以做到这一点,但我们将首先尝试最简单的一种。如果您使用 DevTools 检查 IMDB 评级,您会注意到评级包含在标签 T4 中。

让我们使用属性符号,希望第一个<strong>也是包含评级的那个。

first_movie.strong
<strong>8.3</strong>

太好了!我们将访问文本,将其转换为float类型,并将其赋给变量first_imdb:

first_imdb = float(first_movie.strong.text)
first_imdb
8.3

金属核心

如果我们使用 DevTools 检查 Metascore,我们会注意到我们可以在一个span标签中找到它。

属性符号显然不是一个解决方案。在那之前还有很多<span>标签。您可以在<strong>标签的正上方看到一个。我们最好使用class属性的独特值(metascore favorable)。

注意,如果你从 DevTools 的标签中复制粘贴这些值,在metascorefavorable之间会有两个空格。确保在将值作为参数传递给class_参数时,只有一个空白字符。否则,find()什么也找不到。

first_mscore = first_movie.find('span', class_ = 'metascore favorable')
first_mscore = int(first_mscore.text)
print(first_mscore)
77

favorable值表示高 Metascore,并将评级的背景色设置为绿色。另外两个可能的值是unfavorablemixed。然而,所有 Metascore 评级所特有的只是metascore值。当我们为整个页面编写脚本时,我们将使用这个脚本。

票数

投票数包含在一个<span>标签中。它与众不同的标志是一个值为nvname属性。

name属性不同于class属性。使用 BeautifulSoup,我们可以通过任何属性访问元素。find()find_all()函数有一个名为attrs的参数。为此,我们可以传递我们作为字典搜索的属性和值:

first_votes = first_movie.find('span', attrs = {'name':'nv'})
first_votes
<span data-value="320428" name="nv">320,428</span>

我们可以使用.text符号来访问<span>标签的内容。如果我们访问data-value属性的值会更好。这样,我们可以将提取的数据点转换成一个int,而不必去掉逗号。

你可以像对待字典一样对待一个Tag对象。HTML 属性是字典的关键字。HTML 属性的值就是字典的键值。这就是我们如何访问data-value属性的值:

first_votes['data-value']
'320428'

让我们将该值转换为一个整数,并将其赋给first_votes:

first_votes = int(first_votes['data-value'])

就是这样!我们现在可以轻松地编写一个脚本来抓取单个页面。

单页的脚本

在将我们到目前为止所做的事情拼凑在一起之前,我们必须确保我们将只从具有 Metascore 的容器中提取数据。

我们需要添加一个条件来跳过没有 Metascore 的电影。

再次使用 DevTools,我们看到 Metascore 部分包含在一个<div>标记中。class属性有两个值:inline-blockratings-metascore。与众不同的显然是ratings-metascore

我们可以使用find()在每个电影容器中搜索具有该独特标记的div。当find()没有找到任何东西时,它返回一个None对象。我们可以在if语句中使用这个结果来控制一部电影是否被删除。

让我们在网页上搜索一个没有 Metascore 的电影容器,看看find()返回什么。

重要提示:当我运行下面的代码时,第八个容器没有 Metascore。然而,这是一个移动的目标,因为每部电影的投票数不断变化。为了获得与我在下一个演示代码单元中所做的相同的输出,您应该在运行代码时搜索一个没有 Metascore 的容器。

eighth_movie_mscore = movie_containers[7].find('div', class_ = 'ratings-metascore')
type(eighth_movie_mscore)
NoneType

现在让我们把上面的代码放在一起,并尽可能地压缩它,但只是在它仍然易于阅读的范围内。在下一个代码块中,我们:

  • 声明一些list变量来存储提取的数据。
  • 遍历movie_containers(包含所有 50 个电影容器的变量)中的每个容器。
  • 仅当容器具有元得分时,才提取感兴趣的数据点。
# Lists to store the scraped data in
names = []
years = []
imdb_ratings = []
metascores = []
votes = []
# Extract data from individual movie container
for container in movie_containers:
# If the movie has Metascore, then extract:
    if container.find('div', class_ = 'ratings-metascore') is not None:
# The name
    name = container.h3.a.text
    names.append(name)
# The year
    year = container.h3.find('span', class_ = 'lister-item-year').text
    years.append(year)
# The IMDB rating
    imdb = float(container.strong.text)
    imdb_ratings.append(imdb)
# The Metascore
    m_score = container.find('span', class_ = 'metascore').text
    metascores.append(int(m_score))
# The number of votes
    vote = container.find('span', attrs = {'name':'nv'})['data-value']
    votes.append(int(vote))

让我们检查一下目前收集的数据。熊猫让我们很容易看到我们是否成功地收集了数据。

import pandas as pd
test_df = pd.DataFrame({'movie': names,
'year': years,
'imdb': imdb_ratings,
'metascore': metascores,
'votes': votes
})
print(test_df.info())
test_df
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 32 entries, 0 to 31
Data columns (total 5 columns):
imdb 32 non-null float64
metascore 32 non-null int64
movie 32 non-null object
votes 32 non-null int64
year 32 non-null object
dtypes: float64(1), int64(2), object(2)
memory usage: 1.3+ KB
None
imdb 中 金属芯 电影 投票
Zero Eight point three Seventy-seven 洛根 Three hundred and twenty thousand four hundred and twenty-eight (2017)
one Eight point one Sixty-seven 银河守护者第二卷 One hundred and seventy-five thousand four hundred and forty-three (2017)
Two Eight point one Seventy-six 《神奇女侠》 One hundred and fifty-two thousand and sixty-seven (2017)
three Seven point seven Seventy-five 约翰·威克:第二章 One hundred and forty thousand seven hundred and eighty-four (2017)
four Seven point five Sixty-five 《美女与野兽》 One hundred and thirty-seven thousand seven hundred and thirteen (2017)
five Seven point eight Eighty-four 滚出去 One hundred and thirty-six thousand four hundred and thirty-five ㈠(2017 年)
six Six point eight Sixty-two 孔:骷髅岛 One hundred and twelve thousand and twelve (2017)
seven Seven fifty-six 狂怒者的命运 Ninety-seven thousand six hundred and ninety (2017)
eight Six point eight Sixty-five 异形:契约 Eighty-eight thousand six hundred and ninety-seven (2017)
nine Six point seven Fifty-four 生活 Eighty thousand eight hundred and ninety-seven ㈠(2017 年)
Ten Seven Thirty-nine 加勒比海盗:死无对证 Seventy-seven thousand two hundred and sixty-eight (2017)
Eleven Six point six fifty-two 壳里的幽灵 Sixty-eight thousand five hundred and twenty-one (2017)
Twelve Seven point four Seventy-five 乐高蝙蝠侠电影 Sixty-one thousand two hundred and sixty-three (2017)
Thirteen Five point two forty-two xXx:史云光·凯奇的回归 Fifty thousand six hundred and ninety-seven (2017)
Fourteen Four point six Thirty-three 黑暗五十度 Fifty thousand and twenty-two (2017)
Fifteen Seven point four Sixty-seven T2 猜火车 Forty-eight thousand one hundred and thirty-four (2017)
Sixteen Six point three forty-four 力量游骑兵 Forty-four thousand seven hundred and thirty-three (2017)
Seventeen Five point eight Thirty-four 木乃伊 Thirty-four thousand one hundred and seventy-one (2017)
Eighteen Six point four Fifty 老板宝贝 Thirty-two thousand nine hundred and seventy-six (2017)
Nineteen Six point six Forty-three 狗的目的 Twenty-nine thousand five hundred and twenty-eight (2017)
Twenty Four point five Twenty-five 戒指 Twenty thousand nine hundred and thirteen (2017)
Twenty-one Five point eight Thirty-seven 海湾观察 Twenty thousand one hundred and forty-seven (2017)
Twenty-two Six point four Thirty-three 我们之间的距离 Nineteen thousand and forty-four ㈠(2017 年)
Twenty-three Five point three Twenty-eight 变形金刚 5:最后的骑士 Seventeen thousand seven hundred and twenty-seven (2017)
Twenty-four Six point one fifty-six 战争机器 Sixteen thousand seven hundred and forty (2017)
Twenty-five Five point seven Thirty-seven 拳赛 Sixteen thousand four hundred and forty-five (2017)
Twenty-six Seven point seven Sixty 有才华的 Fourteen thousand eight hundred and nineteen (2017)
Twenty-seven Seven Seventy-five 在这个世界上我再也没有家的感觉了 Fourteen thousand two hundred and eighty-one (2017)
Twenty-eight Five point five Thirty-four 警觉的 Thirteen thousand seven hundred and seventy-six ㈢(2017 年)
Twenty-nine Six point three Fifty-five 这个发现 Thirteen thousand two hundred and seven (2017)
Thirty Six point four Fifty-eight 在我倒下之前 Thirteen thousand and sixteen (2017)
Thirty-one Eight point five Twenty-six 奥斯曼上尉 Twelve thousand eight hundred and sixty-eight (2017)

一切都如预期的那样进行!

顺便提一下,如果你在一个英语不是主要语言的国家运行代码,很有可能你会把一些电影名翻译成那个国家的主要语言。

很有可能,这是因为服务器从你的 IP 地址推断出你的位置。即使你所在的国家以英语为主要语言,你仍然可以获得翻译的内容。如果您在发出GET请求时使用 VPN,可能会发生这种情况。

如果您遇到这个问题,请将以下值传递给get()函数的headers参数:

headers = {"Accept-Language": "en-US, en;q=0.5"}

这将向服务器传达类似“我想要美式英语(en-US)的语言内容。如果 en-US 不可用,那么其他类型的英语(en)也可以(但没有 en-US 那么多)。”。q参数表示我们对某种语言的偏好程度。如果没有指定,那么默认情况下这些值被设置为1,就像 en-US 的情况一样。你可以在这里阅读更多关于这个的内容。

现在让我们开始为我们想要抓取的所有页面构建脚本。

多个页面的脚本

抓取多个页面更具挑战性。我们将在单页脚本的基础上再做三件事:

  1. 在循环中发出所有我们想要的请求。
  2. 控制循环的速率,以避免用请求轰炸服务器。
  3. 在循环运行时对其进行监控。

我们将从 2000 年到 2017 年这段时间里,每年的前 4 页。18 年中每年 4 页,总共 72 页。每页有 50 部电影,所以我们最多只能抓取 3600 部电影的数据。但并不是所有的电影都有 Metascore,所以数量会比那个低。即便如此,我们仍然很有可能获得超过 2000 部电影的数据。

更改 URL 的参数

如前所述,随着网页的变化,URL 遵循一定的逻辑。

当我们发出请求时,我们只需要改变 URL 的两个参数的值:参数release_datepage。让我们准备下一个循环需要的值。在下一个代码单元中,我们将:

  • 创建一个名为pages的列表,用对应于前 4 页的字符串填充它。
  • 创建一个名为years_url的列表,并用与 2000-2017 年相对应的字符串填充它。
pages = [str(i) for i in range(1,5)]
years_url = [str(i) for i in range(2000,2018)]

控制爬行速率

控制抓取的速率对我们是有利的,对我们在抓取的网站也是有利的。如果我们避免每秒钟数十个请求冲击服务器,那么我们就不太可能被禁止使用我们的 IP 地址。我们还通过允许服务器响应其他用户的请求来避免中断我们抓取的网站的活动。

我们将通过使用 Python 的time 模块中的sleep() 函数来控制循环的速率。sleep()将暂停循环执行指定的秒数。

为了模仿人类行为,我们将通过使用 Python 的random 模块中的randint() 函数来改变请求之间的等待时间。randint()在指定的区间内随机生成整数。

sleep_new

现在,让我们只导入这两个函数来防止包含主 sleep from 循环的代码单元过度拥挤

from time import sleep
from random
import randint

当它还在运行时,监视循环

假设我们抓取了 72 页,如果我们能找到一种方法来监控抓取过程,那就太好了。这个特性绝对是可选的,但是它在测试和调试过程中非常有帮助。此外,页面数量越多,监控就越有帮助。如果你打算在一次代码运行中抓取成百上千的网页,我会说这个特性是必须的。

对于我们的脚本,我们将利用这个特性,并监控以下参数:

  • 请求的频率(速度),所以我们确保我们的程序没有使服务器过载。
  • 请求数,因此我们可以在超出预期请求数的情况下停止循环。
  • 我们请求的 状态码 ,因此我们确保服务器发回正确的响应。

为了得到一个频率值,我们将请求的数量除以自第一次请求以来经过的时间。这类似于计算汽车的速度——我们用行驶距离除以行驶时间。让我们先在小范围内试验一下这种监控技术。在下面的代码单元中,我们将:

  • 使用time 模块中的time() 函数设置开始时间,并将值分配给start_time
  • 将 0 赋给变量requests,我们将使用它来计算请求的数量。
  • 开始一个循环,然后每次迭代:
    • 模拟请求。
    • 将请求数增加 1。
    • 将循环暂停 8 至 15 秒。
    • 计算自第一次请求以来经过的时间,并将该值分配给elapsed_time
    • 打印请求的数量和频率。
from time import timestart_time = time()
requests = 0
for _ in range(5):
# A request would go here
    requests += 1
    sleep(randint(1,3))
    elapsed_time = time() - start_time
    print('Request: {}; Frequency: {} requests/s'.format(requests, requests/elapsed_time))
Request: 1; Frequency: 0.49947650463238624 requests/s
Request: 2; Frequency: 0.4996998027377252 requests/s
Request: 3; Frequency: 0.5995400143227362 requests/s
Request: 4; Frequency: 0.4997272043465967 requests/s
Request: 5; Frequency: 0.4543451628627026 requests/s

由于我们将发出 72 个请求,随着输出的增加,我们的工作看起来会有点混乱。为了避免这种情况,我们将在每次迭代后清除输出,并用关于最*请求的信息替换它。为此,我们将使用 IPython 的core.display 模块中的clear_output() 函数。我们将clear_output()wait参数设置为True,等待替换当前输出,直到出现新的输出。

from IPython.core.display import clear_output
start_time = time()requests = 0
for _ in range(5):
# A request would go here
    requests += 1
    sleep(randint(1,3))
    current_time = time()
    elapsed_time = current_time - start_time
    print('Request: {}; Frequency: {} requests/s'.format(requests, requests/elapsed_time))
clear_output(wait = True)
Request: 5; Frequency: 0.6240351700607663 requests/s

上面的输出是循环运行后您将看到的输出。这是它运行时的样子

progress

为了监控状态代码,我们将设置程序,以便在出现问题时发出警告。一个成功请求由状态码 200 表示。如果状态代码不是 200,我们将使用warnings 中的warn() 抛出警告。

from warnings import warnwarn("Warning Simulation")
/Users/joshuadevlin/.virtualenvs/everday-ds/lib/python3.4/site-packages/ipykernel/__main__.py:3:
UserWarning: Warning Simulation app.launch_new_instance()

我们选择了警告而不是中断循环,因为即使一些请求失败,我们也很有可能收集到足够的数据。只有当请求的数量大于预期时,我们才会中断循环。

将一切拼凑在一起

现在让我们把我们到目前为止所做的一切拼凑起来!在下面的代码单元格中,我们从:

  • 重新声明列表变量,使它们再次变为空。
  • 准备环路监控。

然后,我们将:

  • 遍历years_url列表以改变 URL 的release_date参数。
  • 对于years_url中的每个元素,遍历pages列表来改变 URL 的page参数。
  • pages循环中发出GET请求(并给headers参数正确的值,以确保我们只获得英文内容)。
  • 将循环暂停 8 至 15 秒。
  • 如前所述,监控每个请求。
  • 对非 200 状态代码抛出警告。
  • 如果请求数量大于预期,则中断循环。
  • response的 HTML 内容转换为BeautifulSoup对象。
  • 从这个BeautifulSoup对象中提取所有电影容器。
  • 遍历所有这些容器。
  • 如果容器有 Metascore,则提取数据。
# Redeclaring the lists to store data in
names = []
years = []
imdb_ratings = []
metascores = []
votes = []

# Preparing the monitoring of the loop
start_time = time()
requests = 0

# For every year in the interval 2000-2017
for year_url in years_url:

    # For every page in the interval 1-4
    for page in pages:

        # Make a get request
        response = get('https://www.imdb.com/search/title?release_date=' + year_url +
        '&sort=num_votes,desc&page=' + page, headers = headers)

        # Pause the loop
        sleep(randint(8,15))

        # Monitor the requests
        requests += 1
        elapsed_time = time() - start_time
        print('Request:{}; Frequency: {} requests/s'.format(requests, requests/elapsed_time))
        clear_output(wait = True)

        # Throw a warning for non-200 status codes
        if response.status_code != 200:
            warn('Request: {}; Status code: {}'.format(requests, response.status_code))

        # Break the loop if the number of requests is greater than expected
        if requests > 72:
            warn('Number of requests was greater than expected.')
            break

        # Parse the content of the request with BeautifulSoup
        page_html = BeautifulSoup(response.text, 'html.parser')

        # Select all the 50 movie containers from a single page
        mv_containers = page_html.find_all('div', class_ = 'lister-item mode-advanced')

        # For every movie of these 50
        for container in mv_containers:
            # If the movie has a Metascore, then:
            if container.find('div', class_ = 'ratings-metascore') is not None:

                # Scrape the name
                name = container.h3.a.text
                names.append(name)

                # Scrape the year
                year = container.h3.find('span', class_ = 'lister-item-year').text
                years.append(year)

                # Scrape the IMDB rating
                imdb = float(container.strong.text)
                imdb_ratings.append(imdb)

                # Scrape the Metascore
                m_score = container.find('span', class_ = 'metascore').text
                metascores.append(int(m_score))

                # Scrape the number of votes
                vote = container.find('span', attrs = {'name':'nv'})['data-value']
                votes.append(int(vote))
Request:72; Frequency: 0.07928964663062842 requests/s

不错!刮痧似乎已经完美地工作。脚本运行了大约 16 分钟。

现在,让我们将数据合并到一个 pandas DataFrame中,以检查我们设法收集到了什么。如果一切如预期的那样,我们可以继续清理数据,为分析做好准备。

检查收集的数据

在下一个代码块中,我们:

  • 将数据合并成一只熊猫DataFrame
  • 打印一些关于新创建的DataFrame的信息。
  • 显示前 10 个条目。
movie_ratings = pd.DataFrame({'movie': names,
'year': years,
'imdb': imdb_ratings,
'metascore': metascores,
'votes': votes
})
print(movie_ratings.info())
movie_ratings.head(10)
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 2862 entries, 0 to 2861
Data columns (total 5 columns):
imdb 2862 non-null float64
metascore 2862 non-null int64
movie 2862 non-null object
votes 2862 non-null int64
year 2862 non-null object
dtypes: float64(1), int64(2), object(2)
memory usage: 111.9+ KB
None
imdb 中 金属芯 电影 投票
Zero Eight point five Sixty-seven 角斗士 One million sixty-one thousand and seventy-five (2000)
one Eight point five Eighty 纪念品 Nine hundred and nine thousand eight hundred and thirty-five (2000)
Two Eight point three Fifty-five 偷拐抢骗 Six hundred and forty-three thousand five hundred and eighty-eight (2000)
three Eight point four sixty-eight 梦想的安魂曲 Six hundred and seventeen thousand seven hundred and forty-seven (2000)
four Seven point four Sixty-four 《X 战警》 Four hundred and eighty-five thousand four hundred and eighty-five (2000)
five Seven point seven Seventy-three 抛弃 Four hundred and twenty-two thousand two hundred and fifty-one (2000)
six Seven point six Sixty-four 《美国精神病人》 Three hundred and eighty-three thousand six hundred and sixty-nine (2000)
seven Seven point two Sixty-two 牢不可破的 Two hundred and seventy-three thousand nine hundred and seven (2000)
eight Seven Seventy-three 《拜见岳父大人》 Two hundred and seventy-two thousand and twenty-three (2000)
nine Six point one Fifty-nine 教训:不可能 2 Two hundred and fifty-six thousand seven hundred and eighty-nine (2000)

info()的输出显示我们收集了超过 2000 部电影的数据。我们还可以看到,在我们的数据集中没有任何null值。

我已经在 IMDB 的网站上查看了前 10 部电影的评分。他们都是正确的。你可能想自己做同样的事情。

我们可以安全地继续清理数据。

清理刮除的数据

我们将根据两个目标清理收集到的数据:绘制 IMDB 和 Metascore 评级的分布,以及共享数据集。因此,我们的数据清理将包括:

  • 重新排序列。
  • 清理year列并将值转换为整数。
  • 检查极限额定值,以确定所有额定值是否都在预期区间内。
  • 标准化评级类型之一(或两者)以生成比较直方图。

让我们从重新排列各列开始:

movie_ratings = movie_ratings[['movie', 'year', 'imdb', 'metascore', 'votes']]
movie_ratings.head()
电影 imdb 中 金属芯 投票
Zero 角斗士 (2000) Eight point five Sixty-seven One million sixty-one thousand and seventy-five
one 纪念品 (2000) Eight point five Eighty Nine hundred and nine thousand eight hundred and thirty-five
Two 偷拐抢骗 (2000) Eight point three Fifty-five Six hundred and forty-three thousand five hundred and eighty-eight
three 梦想的安魂曲 (2000) Eight point four sixty-eight Six hundred and seventeen thousand seven hundred and forty-seven
four 《X 战警》 (2000) Seven point four Sixty-four Four hundred and eighty-five thousand four hundred and eighty-five

现在让我们将year列中的所有值转换成整数。

现在所有的值都是object类型的。为了避免转换时出现ValueErrors,我们希望值只由从 0 到 9 的数字组成。

让我们检查一下year列的唯一值。这有助于我们了解如何进行我们想要的转换。为了查看所有的唯一值,我们将使用unique()方法:

movie_ratings['year'].unique()
array(['(2000)', '(I) (2000)', '(2001)', '(I) (2001)', '(2002)',
'(I) (2002)', '(2003)', '(I) (2003)', '(2004)', '(I) (2004)',
'(2005)', '(I) (2005)', '(2006)', '(I) (2006)', '(2007)',
'(I) (2007)', '(2008)', '(I) (2008)', '(2009)', '(I) (2009)',
'(II) (2009)', '(2010)', '(I) (2010)', '(II) (2010)', '(2011)',
'(I) (2011)', '(IV) (2011)', '(2012)', '(I) (2012)', '(II) (2012)',
'(2013)', '(I) (2013)', '(II) (2013)', '(2014)', '(I) (2014)',
'(II) (2014)', '(III) (2014)', '(2015)', '(I) (2015)',
'(II) (2015)', '(VI) (2015)', '(III) (2015)', '(2016)',
'(II) (2016)', '(I) (2016)', '(IX) (2016)', '(V) (2016)', '(2017)',
'(I) (2017)', '(III) (2017)', '(IV) (2017)'], dtype=object)

从末尾向开头数,我们可以看到年份总是位于第五个字符到第二个字符之间。我们将使用.str() 方法来只选择那个区间。我们还将使用astype() 方法将结果转换为整数:

movie_ratings.loc[:, 'year'] = movie_ratings['year'].str[-5:-1].astype(int)

让我们直观地看到year列的前 3 个值,以便快速检查。我们还可以在输出的最后一行看到值的类型:

movie_ratings['year'].head(3)
0 2000
1 2000
2 2000
Name: year, dtype: int64

现在,我们将检查每种评级的最小值和最大值。我们可以通过熊猫的describe() 方法很快做到这一点。当在一个DataFrame上应用时,这个方法为DataFrame的每个数字列返回各种描述性统计数据。在下一行代码中,我们仅选择描述最小值和最大值的行,以及描述 IMDB 评级和 Metascores 的列。

movie_ratings.describe().loc[['min', 'max'], ['imdb', 'metascore']]
imdb 中 metascore
one point six Seven
最大 Nine One hundred

没有意外的异常值。

从上面的值中,您可以看到这两个评级具有不同的范围。为了能够在一张图上绘制这两种分布,我们必须将它们放在相同的比例上。让我们将imdb列标准化为 100 分制。

我们将每个 IMDB 评级乘以 10,然后通过查看前 3 行进行快速检查:

movie_ratings['n_imdb'] = movie_ratings['imdb'] * 10
movie_ratings.head(3)
电影 imdb 中 金属芯 投票 n_imdb
Zero 角斗士 Two thousand Eight point five Sixty-seven One million sixty-one thousand and seventy-five Eighty-five
one 纪念品 Two thousand Eight point five Eighty Nine hundred and nine thousand eight hundred and thirty-five Eighty-five
Two 偷拐抢骗 Two thousand Eight point three Fifty-five Six hundred and forty-three thousand five hundred and eighty-eight Eighty-three

不错!我们现在可以在本地保存这个数据集,所以我们可以更容易地与他人共享它。我已经在我的 GitHub 个人资料上公开分享了它。你还可以在其他地方共享数据集,比如 Kaggle ,或者 Dataworld 。

所以我们省省吧:

movie_ratings.to_csv('movie_ratings.csv')

顺便说一下,我强烈建议在退出(或重启)笔记本内核之前保存抓取的数据集。这样,您只需在恢复工作时导入数据集,而不必再次运行抓取脚本。如果你抓取成百上千的网页,这将变得非常有用。

最后,让我们绘制分布图!

绘制和分析分布

在下面的代码单元格中,我们:

  • 导入matplotlib.pyplot子模块。
  • 运行 Jupyter magic %matplotlib以激活 Jupyter 的 matplotlib 模式,并添加inline以在笔记本中显示我们的图形。
  • 用 3 个axes创建一个figure对象。
  • 绘制个人的每个非标准化评级的分布图ax
  • 在同一个ax上绘制两个等级的标准化分布。
  • 隐藏所有三个axes的顶部和右侧脊椎。
import matplotlib.pyplot as plt

fig, axes = plt.subplots(nrows = 1, ncols = 3, figsize = (16,4))
ax1, ax2, ax3 = fig.axes
ax1.hist(movie_ratings['imdb'], bins = 10, range = (0,10)) # bin range = 1
ax1.set_title('IMDB rating')
ax2.hist(movie_ratings['metascore'], bins = 10, range = (0,100)) # bin range = 10
ax2.set_title('Metascore')
ax3.hist(movie_ratings['n_imdb'], bins = 10, range = (0,100), histtype = 'step')
ax3.hist(movie_ratings['metascore'], bins = 10, range = (0,100), histtype = 'step')
ax3.legend(loc = 'upper left')
ax3.set_title('The Two Normalized Distributions')
for ax in fig.axes:
    ax.spines['top'].set_visible(False)
    ax.spines['right'].set_visible(False)
plt.show()

从 IMDB 直方图开始,我们可以看到大部分评分都在 6 到 8 之间。评分大于 8 的电影很少,评分小于 4 的就更少了。这表明非常好的电影和非常差的电影都很少见。

Metascore 评级的分布类似于正态分布——大多数评级是*均值,峰值约为 50。从这个峰值开始,频率逐渐向极限额定值降低。根据这种分布,非常好和非常差的电影确实少了,但并不像 IMDB 评级显示的那样少。

在对比图上,可以更清楚地看到 IMDB 分布高度偏向*均评级的较高部分,而 Metascore 评级的分布似乎更加*衡。

IMDB 分布出现这种偏差的原因可能是什么?一个假设是,许多用户倾向于使用二元方法来评估电影。如果他们喜欢这部电影,他们会给它 10 分。如果他们不喜欢这部电影,他们会给它一个很小的评级,或者他们懒得给这部电影评级。这是一个有趣的问题,值得更详细地探讨。

后续步骤

从请求单个网页的内容到分析 2000 多部电影的收视率,我们已经走过了漫长的道路。您现在应该知道如何抓取许多具有相同 HTML 和 URL 结构的网页。

在我们所学的基础上,下面是几个需要考虑的后续步骤:

  • 抓取不同时间和页面间隔的数据。
  • 搜集关于电影的额外数据。
  • 找个不同的网站刮点自己感兴趣的东西。例如,你可以搜集关于笔记本电脑的数据,看看价格如何随时间变化。

这个教程有帮助吗?

选择你的道路,不断学习有价值的数据技能。

arrow down leftarrow right downPython Tutorials

在我们的免费教程中练习 Python 编程技能。

Data science courses

通过我们的交互式浏览器数据科学课程,投入到 Python、R、SQL 等语言的学习中。

教程:使用 rvest 在 R 中进行 Web 抓取

原文:https://www.dataquest.io/blog/web-scraping-in-r-rvest/

April 13, 2020we'll do some web scraping in R with rvest to gather data about the weather

互联网上有很多数据集,你可以用它们来做你自己的个人项目。有时候你很幸运,你可以访问一个 API ,在那里你可以直接用 r 请求数据。其他时候,你就没那么幸运了,你不能以简洁的格式获得数据。当这种情况发生时,我们需要求助于网络抓取,这是一种通过在网站的 HTML 代码中找到我们想要分析的数据来获取数据的技术。

在本教程中,我们将介绍如何在 r 中进行网络抓取的基础知识。我们将从国家气象局网站抓取天气预报数据,并将其转换为可用的格式。

install.packages("Dataquest ")

从我们的R 课程简介开始学习 R——不需要信用卡!

SIGN UP

当我们找不到我们要找的数据时,网络抓取为我们提供了机会,并给了我们实际创建数据集所需的工具。由于我们使用 R 来进行 web 抓取,如果我们使用的站点更新了,我们可以简单地再次运行我们的代码来获得更新的数据集。

理解网页

在我们开始学习如何抓取网页之前,我们需要了解网页本身是如何构造的。

从用户的角度来看,网页上的文本、图像和链接都以一种美观易读的方式组织起来。但是网页本身是用特定的编码语言编写的,然后由我们的网络浏览器解释。当我们进行网络抓取时,我们需要处理网页本身的实际内容:浏览器解释之前的代码。

用于构建网页的主要语言称为超文本标记语言(HTML)、层叠样式表(CSS)和 Javascript。HTML 给出了网页的实际结构和内容。CSS 赋予网页风格和外观,包括字体和颜色等细节。Javascript 赋予了网页功能。

在本教程中,我们将主要关注如何使用 R web scraping 来读取组成网页的 HTML 和 CSS。

超文本标记语言

与 R 不同,HTML 不是一种编程语言。相反,它被称为标记语言——它描述了网页的内容和结构。HTML 使用标签来组织,这些标签被<>符号包围。不同的标签执行不同的功能。许多标签一起将形成并包含网页的内容。

最简单的 HTML 文档如下所示:

<html>
<head>

虽然上面是一个合法的 HTML 文档,但它没有文本或其他内容。如果我们将其保存为. html 文件,并使用 web 浏览器打开它,我们会看到一个空白页面。

注意单词html<>括号包围,这表明它是一个标签。为了给这个 HTML 文档添加更多的结构和文本,我们可以添加以下内容:

<head>
</head>
<body>
<p>
Here's a paragraph of text!
</p>
<p>
Here's a second paragraph of text!
</p>
</body>
</html>

这里我们添加了<head><body>标签,它们为文档增加了更多的结构。<p>标签是我们在 HTML 中用来指定段落文本的。

HTML 中有很多很多的标签,但是我们不可能在本教程中涵盖所有的标签。如果有兴趣,可以去这个网站看看。重要的是要知道标签有特定的名字(htmlbodyp等)。)来使它们在 HTML 文档中可识别。

请注意,每个标签都是“成对”的,即每个标签都伴随着另一个具有相似名称的标签。也就是说,开始标签<html>与另一个标签</html>配对,指示 HTML 文档的开始和结束。这同样适用于<body><p>

认识到这一点很重要,因为它允许标签互相嵌套。<主体>和<头部>标签嵌套在<html>内,<p>嵌套在<body>内。这种嵌套给了 HTML 一种“树状”结构:

当我们使用 R 进行网页抓取时,这种树状结构将告诉我们如何寻找某些标签,所以记住这一点很重要。如果一个标签中嵌套了其他标签,我们会将包含标签称为父标签*,并将其中的每个标签称为“子标签”。如果父标签中有多个子标签,则子标签统称为“兄弟”。这些父、子和兄弟的概念让我们了解了标签的层次结构。

半铸钢ˌ钢性铸铁(Cast Semi-Steel)

HTML 提供了网页的内容和结构,而 CSS 提供了网页应该如何设计的信息。没有 CSS,网页是非常简单的。这里有一个简单的没有 CSS 的 HTML 文档来演示这一点。

当我们说造型的时候,我们指的是一个宽,宽范围的东西。样式可以指特定 HTML 元素的颜色或它们的位置。像 HTML 一样,CSS 材料的范围如此之大,以至于我们无法涵盖语言中每一个可能的概念。如果你有兴趣,你可以在这里了解更多。

在我们深入研究 R web 抓取代码之前,我们需要了解的两个概念是id

首先说一下班级。如果我们在制作一个网站,我们经常会希望网站的相似元素看起来一样。例如,我们可能希望列表中的许多项目都以相同的颜色显示,红色。

我们可以通过将一些包含颜色信息的 CSS 直接插入到每一行文本的 HTML 标记中来实现,就像这样:

<p style=”color:red” >Text 1</p>
<p style=”color:red” >Text 2</p>
<p style=”color:red” >Text 3</p>

style文本表明我们正试图将 CSS 应用于<p>标签。在引号内,我们看到一个键值对“color:red”。color指的是<p>标签中文本的颜色,而红色描述了应该是什么颜色。

但是正如我们在上面看到的,我们已经多次重复了这个键值对。这并不理想——如果我们想改变文本的颜色,我们必须一行一行地改变。

我们可以用一个class选择器代替所有这些<p>标签中的style文本:

<p class=”red-text” >Text 1</p>
<p class=”red-text” >Text 2</p>
<p class=”red-text” >Text 3</p>

class选择器,我们可以更好地表明这些<p>标签在某种程度上是相关的。在一个单独的 CSS 文件中,我们可以创建红色文本类,并通过编写以下内容来定义它的外观:

.red-text {
    color : red;
}

将这两个元素组合成一个网页将产生与第一组红色<p>标签相同的效果,但是它允许我们更容易地进行快速更改。

当然,在本教程中,我们感兴趣的是网页抓取,而不是构建网页。但是当我们抓取网页时,我们经常需要选择一个特定的 HTML 标签类,所以我们需要了解 CSS 类如何工作的基础知识。

类似地,我们可能经常想要抓取使用 id 标识的特定数据。CSS ids 用于给单个元素一个可识别的名称,很像一个类如何帮助定义一类元素。

<p id=”special” >This is a special tag.</p>

如果一个 id 被附加到一个 HTML 标签上,当我们用 r 执行实际的 web 抓取时,它会使我们更容易识别这个标签。

如果您还不太理解类和 id,不要担心,当我们开始操作代码时,它会变得更加清晰。

有几个 R 库被设计用来获取 HTML 和 CSS,并且能够遍历它们来寻找特定的标签。我们将在本教程中使用的库是rvest

rvest 图书馆

由传奇人物哈德利·威克姆维护的 rvest图书馆,是一个让用户轻松从网页上抓取(“收获”)数据的图书馆。

rvesttidyverse库中的一个,所以它可以很好地与捆绑包中包含的其他库一起工作。rvest从 web 抓取库 BeautifulSoup 获取灵感,该库来自 Python。(相关:o ur BeautifulSoup Python 教程。

在 R 中抓取网页

为了使用rvest库,我们首先需要安装它并用library()函数导入它。

install.packages(“rvest”)
library(rvest)

为了开始解析网页,我们首先需要从包含它的计算机服务器请求数据。在 revest 中,服务于此目的的函数是read_html()函数。

read_html()接受一个 web URL 作为参数。让我们从前面的简单的无 CSS 页面开始,看看这个函数是如何工作的。

simple <- read_html("https://dataquestio.github.io/web-scraping-pages/simple.html")

read_html()函数返回一个列表对象,它包含我们前面讨论过的树状结构。

simple
{html_document}
<html>
[1] <head>\n<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">\n<title>A simple exa ...
[2] <body>\n        <p>Here is some simple content for this page.</p>\n    </body>

假设我们想要将包含在单个<p>标签中的文本存储到一个变量中。为了访问这段文本,我们需要弄清楚如何定位这段特定的文本。这通常是 CSS 类和 id 可以帮助我们的地方,因为优秀的开发人员通常会使 CSS 在他们的网站上高度具体化。

在这种情况下,我们没有这样的 CSS,但是我们知道我们想要访问的<p>标签是页面上唯一的同类标签。为了捕获文本,我们需要分别使用html_nodes()html_text()函数来搜索这个<p>标签并检索文本。下面的代码实现了这一点:

simple %>%
html_nodes("p") %>%
html_text()
"Here is some simple content for this page."

simple变量已经包含了我们试图抓取的 HTML,所以剩下的任务就是从其中搜索我们想要的元素。由于我们使用的是tidyverse,我们可以将 HTML 通过管道传输到不同的函数中。

我们需要将特定的 HTML 标签或 CSS 类传递给html_nodes()函数。我们需要<p>标签,所以我们将字符“p”传入函数。html_nodes()也返回一个列表,但是它返回 HTML 中的所有节点,这些节点具有您给它的特定 HTML 标签或 CSS class/id。一个节点是指树状结构上的一个点。

一旦我们拥有了所有这些节点,我们就可以将html_nodes()的输出传递给html_text()函数。我们需要得到< p >标签的实际文本,所以这个函数可以帮助我们。

这些功能共同构成了许多常见 web 抓取任务的主体。一般来说,R(或任何其他语言)中的 web 抓取可以归结为以下三个步骤:

  • 获取您想要抓取的网页的 HTML】
  • 决定你想阅读页面的哪一部分,并找出你需要什么 HTML/CSS 来选择它
  • 选择 HTML 并以你需要的方式进行分析

目标网页

在本教程中,我们将关注国家气象局的网站。假设我们对创建自己的天气应用程序感兴趣。我们需要天气数据本身来填充它。

天气数据每天都在更新,所以当我们需要时,我们将使用网络抓取从 NWS 网站获取这些数据。

出于我们的目的,我们将从旧金山获取数据,但是每个城市的网页看起来都一样,所以同样的步骤也适用于任何其他城市。旧金山页面的屏幕截图如下所示:

我们对天气预报和每天的温度特别感兴趣。每天都有白天预报和夜晚预报。既然我们已经确定了我们需要的网页部分,我们就可以在 HTML 中挖掘,看看我们需要选择什么标签或类来捕获这个特定的数据。

使用 Chrome 开发工具

令人欣慰的是,大多数现代浏览器都有一个工具,允许用户直接检查任何网页的 HTML 和 CSS。在 Google Chrome 和 Firefox 中,它们被称为开发者工具,在其他浏览器中也有类似的名称。对于本教程来说,最有用的工具是检查器。

你可以在浏览器的右上角找到开发者工具。如果你使用 Firefox,你应该可以看到开发者工具,如果你使用 Chrome,你可以通过View -> More Tools -> Developer Tools。这将在您的浏览器窗口中打开开发者工具:

我们之前处理的 HTML 是最基本的,但是你在浏览器中看到的大多数网页都极其复杂。开发者工具将使我们更容易挑选出我们想要抓取和检查 HTML 的网页的确切元素。

我们需要查看天气页面的 HTML 中的温度,所以我们将使用 Inspect 工具来查看这些元素。Inspect 工具将挑选出我们正在寻找的准确的 HTML,所以我们不必自己寻找!

通过点击元素本身,我们可以看到七天的天气预报包含在下面的 HTML 中。我们压缩了一些内容,使其更具可读性:

<div id="seven-day-forecast-container">
<ul id="seven-day-forecast-list" class="list-unstyled">
<li class="forecast-tombstone">
<div class="tombstone-container">
<p class="period-name">Tonight<br><br></p>
<p><img src="newimages/medium/nskc.png" alt="Tonight: Clear, with a low around 50\. Calm wind. " title="Tonight: Clear, with a low around 50\. Calm wind. " class="forecast-icon"></p>
<p class="short-desc" style="height: 54px;">Clear</p>
<p class="temp temp-low">Low: 50 °F</p></div>
</li>
# More elements like the one above follow, one for each day and night
</ul>
</div>

利用我们所学的知识

既然我们已经确定了我们需要在网页中定位哪些特定的 HTML 和 CSS,我们可以使用rvest来捕获它。

从上面的 HTML 来看,似乎每个温度都包含在类temp中。一旦我们有了所有这些标签,我们就可以从中提取文本。

forecasts <- read_html("https://forecast.weather.gov/MapClick.php?lat=37.7771&lon=-122.4196#.Xl0j6BNKhTY") %>%
    html_nodes(“.temp”) %>%
    html_text()

forecasts
[1] "Low: 51 °F" "High: 69 °F" "Low: 49 °F" "High: 69 °F"
[5] "Low: 51 °F" "High: 65 °F" "Low: 51 °F" "High: 60 °F"
[9] "Low: 47 °F"

使用这个代码,forecasts现在是一个与低温和高温相对应的字符串向量。

现在我们有了我们感兴趣的 R 变量的实际数据,我们只需要做一些常规的数据分析,将向量转换成我们需要的格式。例如:

library(readr)
parse_number(forecasts)
[1] 51 69 49 69 51 65 51 60 47

后续步骤

rvest库使得使用与tidyverse库相同的技术来执行 web 抓取变得简单方便。

本教程应该给你必要的工具来启动一个小型的 web 抓取项目,并开始探索更高级的 web 抓取过程。一些与网络抓取极其兼容的网站是体育网站、有股票价格甚至新闻文章的网站。

或者,你可以继续扩展这个项目。你还能为你的天气应用收集到哪些其他的天气预报元素?

如果你想了解更多关于这个主题的知识,请查看 Dataquest 在 R 课程中的交互式网页抓取,以及我们的API 和 R 网页抓取,它们将帮助你在大约 2 个月内掌握这些技能。

准备好提升你的 R 技能了吗?

我们的R 路数据分析师涵盖了您获得工作所需的所有技能,包括:

  • 使用 ggplot2 进行数据可视化
  • 使用 tidyverse 软件包的高级数据清理技能
  • R 用户的重要 SQL 技能
  • 统计和概率的基础知识
  • ...还有多得多的

没有要安装的东西,没有先决条件,也没有时间表。

Start learning for free!*

教程:使用漂亮的汤用 Python 进行网页抓取

原文:https://www.dataquest.io/blog/web-scraping-python-using-beautiful-soup/

March 30, 2021

学习如何用 Python 刮网页!

互联网绝对是一个巨大的数据来源——我们可以使用网络抓取和 Python 来访问这些数据!

事实上,网络抓取通常是我们获取数据的唯一方式。有很多信息是方便的 CSV 导出或易于连接的 API 所没有的。网站本身通常是有价值的数据来源——例如,考虑一下,如果你可以下载网络论坛上的每一篇帖子,你可以做什么样的分析。

要访问这些页面上的数据集,我们必须使用网络抓取。

如果你还是一个完全的初学者,不要担心!

在本教程中,我们将从头开始讲述如何使用 Python 进行 web 抓取,从一些常见问题的答案开始。

然后,我们将通过一个实际的网络抓取项目来工作,重点是天气数据。

web scraping weather data with python

我们将合作从网上搜集天气数据来支持天气应用程序。

但是在我们开始编写任何 Python 之前,我们必须先了解基础知识!如果你已经熟悉了网络抓取的概念,请随意浏览这些问题,直接进入教程!

网络抓取的基础:

Python 中的 Web 抓取是什么?

一些网站提供 CSV 格式的可下载数据集,或者通过应用编程接口 (API)访问。但是很多有有用数据的网站并没有提供这些便捷的选项。

例如,考虑一下国家气象局的网站。它包含美国每个地方的最新天气预报,但这些天气数据不能以 CSV 格式或通过 API 访问。必须在 NWS 网站上查看:

nws

如果我们想分析这些数据,或者下载到其他应用程序中使用,我们不会刻意复制粘贴所有数据。Web 抓取是一种让我们使用编程来完成繁重工作的技术。我们将编写一些代码来查看 NWS 站点,获取我们想要处理的数据,并以我们需要的格式输出。

在本教程中,我们将向您展示如何使用 Python 3 和美汤库来执行网页抓取。我们将从国家气象局抓取天气预报,然后使用熊猫库分析它们。

但是要明确的是,很多编程语言都可以用来抓取网页!例如,我们也在 R 中教授网页抓取。但是,对于本教程,我们将坚持使用 Python。

网页抓取是如何工作的?

当我们抓取网页时,我们编写代码向托管我们指定的页面的服务器发送请求。服务器将返回我们请求的页面的源代码——主要是 HTML。

到目前为止,我们基本上是在做与 web 浏览器相同的事情——发送带有特定 URL 的服务器请求,并请求服务器返回该页面的代码。

但是网络浏览器不同,我们的网络抓取代码不会解释页面的源代码,也不会直观地显示页面。相反,我们将编写一些自定义代码,过滤页面的源代码,寻找我们指定的特定元素,并提取我们指示它提取的任何内容。

例如,如果我们想从网页上显示的表中获取所有数据,我们的代码将按顺序执行以下步骤:

  1. 向服务器请求特定 URL 的内容(源代码)
  2. 下载返回的内容
  3. 识别我们想要的表格中的页面元素
  4. 提取这些元素并(如有必要)将其重新格式化成一个数据集,我们可以根据需要以任何方式进行分析或使用。

如果这一切听起来很复杂,不要担心! Python 和 Beautiful Soup 具有内置特性,旨在使这一点变得相对简单。

需要注意的一点是:从服务器的角度来看,通过 web 抓取请求页面与在 web 浏览器中加载页面是一样的。当我们使用代码提交这些请求时,我们可能会比普通用户更快地“加载”页面,从而很快耗尽网站所有者的服务器资源。

为什么要用 Python 做网页抓取?

如前所述,可以用许多编程语言进行网络抓取。

然而,最流行的方法之一是使用 Python 和漂亮的 Soup 库,正如我们将在本教程中所做的。

学会用 Python 做这件事意味着一旦你掌握了漂亮的汤的基础知识,就会有大量的教程、操作视频和一些示例代码来帮助你加深知识。

网络抓取合法吗?

不幸的是,这里没有现成的答案。一些网站明确允许网络抓取。还有的明确禁止。许多网站没有提供任何明确的指导。

在抓取任何网站之前,我们应该寻找一个条款和条件页面,看看是否有明确的规则。如果有,我们应该跟着他们。如果没有,那么这就变成了一个判断的问题。

请记住,虽然网络抓取消耗主机网站的服务器资源。如果我们只是一次抓取一页,这不会造成问题。但是如果我们的代码每 10 分钟抓取 1000 个页面,网站所有者很快就会为此付出昂贵的代价。

因此,除了遵循网站上发布的所有关于网络抓取的明确规则之外,遵循以下最佳实践也是一个好主意:

网页抓取最佳实践:

  • 不要刮得太频繁。
  • 考虑缓存你抓取的内容,这样它只被下载一次。
  • 使用像 time.sleep() 这样的函数在你的代码中构建暂停,以避免服务器因为太多太快的请求而不堪重负。

在本教程中,NWS 的数据是公开的,其条款没有禁止网络抓取,所以我们可以继续。

用正确的方法学习 Python。

从第一天开始,就在你的浏览器窗口中通过编写 Python 代码来学习 Python。这是学习 Python 的最佳方式——亲自看看我们 60 多门免费课程中的一门。

astronaut floating over code

尝试 Dataquest

网页的组成部分

在我们开始写代码之前,我们需要了解一点关于网页的结构。我们将使用网站的结构来编写代码,以获取我们想要抓取的数据,因此理解该结构是任何 web 抓取项目的重要第一步。
当我们访问网页时,我们的网络浏览器向网络服务器发出请求。这个请求被称为GET请求,因为我们从服务器获取文件。然后,服务器发回文件,告诉我们的浏览器如何为我们呈现页面。这些文件通常包括:

  • HTML —页面的主要内容。
  • CSS —用于添加样式,使页面看起来更好。
  • JS — Javascript 文件为网页增加了交互性。
  • 图像——图像格式,如 JPG 和 PNG ,允许网页显示图片。

在我们的浏览器接收到所有文件后,它会呈现页面并显示给我们。

为了很好地呈现页面,在幕后发生了很多事情,但是当我们抓取网页时,我们不需要担心其中的大部分。当我们执行网络抓取时,我们对网页的主要内容感兴趣,所以我们主要看 HTML。

超文本标记语言

超文本标记语言(HTML)是创建网页的语言。然而,HTML 不像 Python 那样是一种编程语言。它是一种标记语言,告诉浏览器如何显示内容。

HTML 有许多功能类似于你可能在像 Microsoft Word 这样的文字处理器中找到的功能——它可以使文本加粗,创建段落,等等。

如果你已经熟悉 HTML,请随意跳到本教程的下一部分。否则,让我们快速浏览一下 HTML,这样我们就能知道足够多的信息来有效地抓取。

HTML 由称为标签的元素组成。最基本的标签是<html>标签。这个标签告诉 web 浏览器里面的所有东西都是 HTML。我们可以用这个标签制作一个简单的 HTML 文档:

<html>
</html>

我们还没有向页面添加任何内容,所以如果我们在 web 浏览器中查看 HTML 文档,我们将看不到任何内容:

在一个html标签的内部,我们可以放置另外两个标签:head标签和body标签。

网页的主要内容放在body标签中。head标签包含关于页面标题的数据,以及其他在 web 抓取中通常没有用的信息:

<html>
<head>
</head>
<body>
</body>
</html>

我们还没有向页面添加任何内容(在body标签中),所以如果我们在浏览器中打开这个 HTML 文件,我们仍然看不到任何内容:

你可能已经注意到,我们把headbody标签放在了html标签里面。在 HTML 中,标签是嵌套的,可以放在其他标签中。

我们现在将第一个内容添加到页面中的一个p标签中。p标签定义了一个段落,标签内的任何文本都显示为一个单独的段落:

<html>
<head>
</head>
<body>
<p>
Here's a paragraph of text!
</p>
<p>
Here's a second paragraph of text!
</p>
</body>
</html>

在浏览器中呈现时,该 HTML 文件将如下所示:

下面是一段文字!

下面是第二段文字!

标签的常用名称取决于它们相对于其他标签的位置:

  • child —子标签是另一个标签中的标签。所以上面的两个p标签都是body标签的子标签。
  • parent —父标签是另一个标签所在的标签。上图中,html标签是body标签的父标签。
  • sibiling-咝咝声是嵌套在与另一个标签相同的父标签中的标签。例如,headbody是兄弟姐妹,因为他们都在html里面。两个p标签都是兄弟姐妹,因为它们都在body里面。

我们也可以给 HTML 标签添加属性来改变它们的行为。下面,我们将使用a标签添加一些额外的文本和超链接。

<html>
<head>
</head>
<body>
<p>
Here's a paragraph of text!
<a href="https://www.dataquest.io">Learn Data Science Online</a>
</p>
<p>
Here's a second paragraph of text!
<a href="https://www.python.org">Python</a> </p>
</body></html>

这将是这样的:

下面是一段文字!在线学习数据科学

下面是第二段文字! Python

在上面的例子中,我们添加了两个a标签。a标签是链接,告诉浏览器呈现另一个网页的链接。标签的href属性决定了链接的位置。

ap是极其常见的 html 标签。以下是其他一些例子:

  • div —表示页面的分区或区域。
  • b —加粗内部的任何文本。
  • i —斜体显示内部的任何文本。
  • table —创建一个表格。
  • form —创建一个输入表单。

如需标签的完整列表,请查看此处。

在我们进入实际的 web 抓取之前,让我们了解一下classid属性。这些特殊的属性赋予 HTML 元素名称,使它们在我们抓取时更容易交互。

一个元素可以有多个类,一个类可以在元素之间共享。每个元素只能有一个 id,一个 id 在一个页面上只能使用一次。类和 id 是可选的,并不是所有的元素都有。

我们可以在示例中添加类和 id:

<html>
<head>
</head>
<body>
<p class="bold-paragraph">
Here's a paragraph of text!
<a href="https://www.dataquest.io" id="learn-link">Learn Data Science Online</a>
</p>
<p class="bold-paragraph extra-large">
Here's a second paragraph of text!
<a href="https://www.python.org" class="extra-large">Python</a>
</p>
</body>
</html>

这将是这样的:

下面是一段文字!在线学习数据科学

下面是第二段文字! Python

如您所见,添加类和 id 根本不会改变标签的呈现方式。

请求库

现在我们已经了解了网页的结构,是时候进入有趣的部分了:抓取我们想要的内容!要抓取一个网页,我们需要做的第一件事就是下载该网页。我们可以使用 Python 请求库下载页面。

请求库将向 web 服务器发出一个GET请求,它将为我们下载给定网页的 HTML 内容。我们可以使用requests发出几种不同类型的请求,GET只是其中之一。如果你想了解更多,查看我们的 API 教程。

让我们试着下载一个简单的示例网站,https://dataquestio.github.io/web-scraping-pages/simple.html

我们需要首先导入requests库,然后使用 requests.get 方法下载页面:

import requests
page = requests.get("https://dataquestio.github.io/web-scraping-pages/simple.html")
page
<Response [200]>

运行我们的请求后,我们得到一个响应对象。这个对象有一个status_code属性,指示页面是否下载成功:

page.status_code
200

200的一个status_code表示页面下载成功。我们不会在这里详细讨论状态代码,但是以2开头的状态代码通常表示成功,以45开头的代码表示错误。

我们可以使用content属性打印出页面的 HTML 内容:

page.content
<!DOCTYPE html>
<html>
<head>
<title>A simple example page</title>
</head>
<body>
<p>Here is some simple content for this page.</p>
</body>
</html>

用 BeautifulSoup 解析页面

正如你在上面看到的,我们现在已经下载了一个 HTML 文档。

我们可以使用 BeautifulSoup 库来解析这个文档,并从p标签中提取文本。

我们首先必须导入这个库,并创建一个BeautifulSoup类的实例来解析我们的文档:

from bs4 import BeautifulSoup
soup = BeautifulSoup(page.content, 'html.parser')

我们现在可以打印出页面的 HTML 内容,格式很好,在BeautifulSoup对象上使用prettify方法。

print(soup.prettify())
<!DOCTYPE html>
<html>
    <head>
        <title>A simple example page</title>
    </head>
    <body>
        <p>Here is some simple content for this page.</p>
    </body>
</html>

严格来说,这一步不是必需的,我们也不会总是为此费心,但是查看美化过的 HTML 会有所帮助,这样可以更容易地看到嵌套标签的和的结构。
由于所有的标签都是嵌套的,我们可以一次移动一层。我们可以首先使用soupchildren属性选择页面顶层的所有元素。

注意,children返回一个列表生成器,所以我们需要对它调用list函数:

list(soup.children)
['html', 'n', <html> <head> <title>A simple example page</title> </head> <body> <p>Here is some simple content for this page.</p> </body> </html>]

上面告诉我们在页面的顶层有两个标签——初始的<!DOCTYPE html>标签和<html>标签。列表中还有一个换行符(n)。让我们看看列表中每个元素的类型:

[type(item) for item in list(soup.children)]
[bs4.element.Doctype, bs4.element.NavigableString, bs4.element.Tag]

正如我们所见,所有的项目都是BeautifulSoup对象:

  • 第一个是一个Doctype对象,它包含关于文档类型的信息。
  • 第二个是一个NavigableString,它表示在 HTML 文档中找到的文本。
  • 最后一项是一个Tag对象,它包含其他嵌套标签。

最重要的对象类型,也是我们最常处理的对象类型,是Tag对象。

对象允许我们浏览 HTML 文档,并提取其他标签和文本。你可以在这里了解更多关于各种BeautifulSoup物体的信息。

我们现在可以通过选择列表中的第三项来选择html标签及其子标签:

html = list(soup.children)[2]

children属性返回的列表中的每一项也是一个BeautifulSoup对象,所以我们也可以在html上调用children方法。

现在,我们可以在html标签中找到孩子:

list(html.children)
['n', <head> <title>A simple example page</title> </head>, 'n', <body> <p>Here is some simple content for this page.</p> </body>, 'n']

正如我们在上面看到的,这里有两个标签,headbody。我们想要提取p标签中的文本,所以我们将深入主体:

body = list(html.children)[3]

现在,我们可以通过找到 body 标签的子标签来获得p标签:

list(body.children)
['n', <p>Here is some simple content for this page.</p>, 'n']

我们现在可以分离出 p 标签:

p = list(body.children)[1]

一旦我们隔离了标签,我们可以使用get_text方法提取标签中的所有文本:

p.get_text()
'Here is some simple content for this page.'

一次查找一个标签的所有实例

我们上面所做的对于弄清楚如何导航一个页面是有用的,但是它需要很多命令来做一些相当简单的事情。

如果我们想提取一个标签,我们可以使用find_all方法,它会在页面上找到一个标签的所有实例。

soup = BeautifulSoup(page.content, 'html.parser')
soup.find_all('p')
[<p>Here is some simple content for this page.</p>]

注意,find_all返回一个列表,所以我们必须循环遍历,或者使用列表索引,来提取文本:

soup.find_all('p')[0].get_text()
'Here is some simple content for this page.'

如果您只想找到标签的第一个实例,您可以使用find方法,该方法将返回一个单独的BeautifulSoup对象:

soup.find('p')
<p>Here is some simple content for this page.</p>

按类别和 id 搜索标签

我们前面介绍了类和 id,但是可能不清楚它们为什么有用。

CSS 使用类和 id 来决定对哪些 HTML 元素应用特定的样式。但是当我们刮的时候,我们也可以用它们来指定我们想要刮的元素。

为了说明这一原则,我们将使用以下页面:

<html>
    <head>
        <title>A simple example page</title>
    </head>
    <body>
        <div>
            <p class="inner-text first-item" id="first">
                First paragraph.
            </p>
            <p class="inner-text">
                Second paragraph.
            </p>
        </div>
            <p class="outer-text first-item" id="second">
                <b>
                First outer paragraph.
                </b>
            </p>
            <p class="outer-text">
                <b>
                Second outer paragraph.
                </b>
            </p>
    </body>
</html>

我们可以通过 URL https://dataquestio.github.io/web-scraping-pages/ids_and_classes.html访问上面的文档。

让我们首先下载页面并创建一个BeautifulSoup对象:

page = requests.get("https://dataquestio.github.io/web-scraping-pages/ids_and_classes.html")
soup = BeautifulSoup(page.content, 'html.parser')
soup
<html>
<head>
<title>A simple example page
</title>
</head>
<body>
<div>
<p class="inner-text first-item" id="first">
First paragraph.
</p><p class="inner-text">
Second paragraph.
</p></div>
<p class="outer-text first-item" id="second"><b>
First outer paragraph.
</b></p><p class="outer-text"><b>
Second outer paragraph.
</b>
</p>
</body>
</html>

现在,我们可以使用find_all方法按类或 id 搜索条目。在下面的例子中,我们将搜索任何具有类别outer-textp标签:

soup.find_all('p', class_='outer-text')
[<p class="outer-text first-item" id="second"> <b> First outer paragraph. </b> </p>, <p class="outer-text"> <b> Second outer paragraph. </b> </p>]

在下面的例子中,我们将寻找任何具有类outer-text的标签:

soup.find_all(class_="outer-text")
<p class="outer-text first-item" id="second">
<b>
First outer paragraph.
</b>
</p>, <p class="outer-text">
<b>
Second outer paragraph.
</b>
</p>]

我们还可以通过 id 搜索元素:

soup.find_all(id="first")
[<p class="inner-text first-item" id="first">
First paragraph.
</p>]

使用 CSS 选择器

我们也可以使用 CSS 选择器来搜索条目。这些选择器是 CSS 语言允许开发人员指定 HTML 标签样式的方式。以下是一些例子:

  • p a —查找一个p标签内的所有a标签。
  • body p a —查找body标签内p标签内的所有a标签。
  • html body —查找一个html标签内的所有body标签。
  • p.outer-text —查找类别为outer-text的所有p标签。
  • p#first —查找 id 为first的所有p标签。
  • body p.outer-text —查找任何在body标签内带有outer-text类别的p标签。

你可以在这里了解更多关于 CSS 选择器的信息。

BeautifulSoup对象支持使用select方法通过 CSS 选择器搜索页面。我们可以使用 CSS 选择器来查找页面中位于像这样的div内的所有p标签:

soup.select("div p")
[<p class="inner-text first-item" id="first">
First paragraph.
</p>, <p class="inner-text">
Second paragraph.
</p>]

注意,上面的select方法返回了一个BeautifulSoup对象的列表,就像findfind_all一样。

下载天气数据

我们现在已经知道了足够多的信息,可以从国家气象局网站上提取当地天气信息了!

第一步,找到我们要刮的页面。我们将从本页中提取旧金山市中心的天气信息。

an image of the site we will use for our python web scraping

具体来说,让我们提取关于扩展预测的数据。

正如我们从图像中看到的,该页面包含了下周的详细天气预报信息,包括一天中的时间、温度以及天气状况的简要描述。

使用 Chrome DevTools 探索页面结构

我们需要做的第一件事是使用 Chrome Devtools 检查页面。如果你使用的是另一款浏览器, Firefox 和 Safari 都有对应的浏览器。

你可以通过点击View -> Developer -> Developer Tools来启动 Chrome 中的开发者工具。你应该在浏览器的底部看到一个面板,如下图所示。确保Elements面板高亮显示:

Chrome 开发者工具

元素面板将显示页面上的所有 HTML 标签,并允许您浏览它们。这是一个非常方便的功能!

通过右键单击页面上靠*“扩展预测”的位置,然后单击“检查”,我们将在元素面板中打开包含文本“扩展预测”的标签:

扩展预测文本

然后,我们可以在“元素”面板中向上滚动,找到包含与扩展预测相对应的所有文本的“最外层”元素。在这种情况下,它是一个 id 为seven-day-forecastdiv标签:

包含扩展预测项目的 div。

如果我们在控制台上单击并浏览 div,我们会发现每个预测项目(如“今晚”、“周四”和“周四晚上”)都包含在一个带有类tombstone-containerdiv中。

是时候开始刮了!

我们现在已经知道了足够多的信息,可以下载页面并开始解析它。在下面的代码中,我们将:

  • 下载包含预测的网页。
  • 创建一个BeautifulSoup类来解析页面。
  • 找到 id 为seven-day-forecastdiv,分配给seven_day
  • seven_day中,找到每个单独的预测项目。
  • 提取并打印第一个预测项目。
page = requests.get("https://forecast.weather.gov/MapClick.php?lat=37.7772&lon=-122.4168")
soup = BeautifulSoup(page.content, 'html.parser')
seven_day = soup.find(id="seven-day-forecast")
forecast_items = seven_day.find_all(class_="tombstone-container")
tonight = forecast_items[0]
print(tonight.prettify())
<div class="tombstone-container">
	<p class="period-name">
		Tonight
		<br>
		<br/>
		</br>
	</p>
	<p>
		<img alt="Tonight: Mostly clear, with a low around 49\. West northwest wind 12 to 17 mph decreasing to 6 to 11 mph after midnight. Winds could gust as high as 23 mph. " class="forecast-icon" src="newimages/medium/nfew.png" title="Tonight: Mostly clear, with a low around 49\. West northwest wind 12 to 17 mph decreasing to 6 to 11 mph after midnight. Winds could gust as high as 23 mph. "/>
	</p>
	<p class="short-desc">
		Mostly Clear
	</p>
	<p class="temp temp-low">
		Low: 49 °F
	</p>
</div>

从页面中提取信息

正如我们所看到的,预测项目tonight中包含了我们想要的所有信息。我们可以提取四条信息:

  • 预测项目的名称—在本例中为Tonight
  • 条件的描述—存储在imgtitle属性中。
  • 条件的简短描述—在本例中为Mostly Clear
  • 温度低——在这种情况下,49度。

我们将首先提取预测项目的名称、简短描述和温度,因为它们都是相似的:

period = tonight.find(class_="period-name").get_text()
short_desc = tonight.find(class_="short-desc").get_text()
temp = tonight.find(class_="temp").get_text()
print(period)
print(short_desc)
print(temp)
Tonight
Mostly Clear
Low: 49 °F

现在,我们可以从img标签中提取出title属性。为此,我们只需将BeautifulSoup对象视为一个字典,并将我们想要的属性作为一个键传入:

img = tonight.find("img")
desc = img['title']
print(desc)
Tonight: Mostly clear, with a low around 49\. West northwest wind 12 to 17 mph decreasing to 6 to 11 mph after midnight. Winds could gust as high as 23 mph.

从页面中提取所有信息

现在我们知道了如何提取每一条信息,我们可以将我们的知识与 CSS 选择器和列表理解结合起来一次提取所有内容

在下面的代码中,我们将:

  • seven_day中选择类别为tombstone-container的项目内类别为period-name的所有项目。
  • 使用 list comprehension 在每个BeautifulSoup对象上调用get_text方法。
period_tags = seven_day.select(".tombstone-container .period-name")
periods = [pt.get_text() for pt in period_tags]
periods
['Tonight',
'Thursday',
'ThursdayNight',
'Friday',
'FridayNight',
'Saturday',
'SaturdayNight',
'Sunday',
'SundayNight']

正如我们在上面看到的,我们的技术按顺序得到了每个周期的名称。

我们可以应用相同的技术来获得其他三个字段:

short_descs = [sd.get_text() for sd in seven_day.select(".tombstone-container .short-desc")]
temps = [t.get_text() for t in seven_day.select(".tombstone-container .temp")]
descs = [d["title"] for d in seven_day.select(".tombstone-container img")]print(short_descs)print(temps)print(descs)
['Mostly Clear', 'Sunny', 'Mostly Clear', 'Sunny', 'Slight ChanceRain', 'Rain Likely', 'Rain Likely', 'Rain Likely', 'Chance Rain']
['Low: 49 °F', 'High: 63 °F', 'Low: 50 °F', 'High: 67 °F', 'Low: 57 °F', 'High: 64 °F', 'Low: 57 °F', 'High: 64 °F', 'Low: 55 °F']
['Tonight: Mostly clear, with a low around 49\. West northwest wind 12 to 17 mph decreasing to 6 to 11 mph after midnight. Winds could gust as high as 23 mph. ', 'Thursday: Sunny, with a high near 63\. North wind 3 to 5 mph. ', 'Thursday Night: Mostly clear, with a low around 50\. Light and variable wind becoming east southeast 5 to 8 mph after midnight. ', 'Friday: Sunny, with a high near 67\. Southeast wind around 9 mph. ', 'Friday Night: A 20 percent chance of rain after 11pm. Partly cloudy, with a low around 57\. South southeast wind 13 to 15 mph, with gusts as high as 20 mph. New precipitation amounts of less than a tenth of an inch possible. ', 'Saturday: Rain likely. Cloudy, with a high near 64\. Chance of precipitation is 70%. New precipitation amounts between a quarter and half of an inch possible. ', 'Saturday Night: Rain likely. Cloudy, with a low around 57\. Chance of precipitation is 60%.', 'Sunday: Rain likely. Cloudy, with a high near 64.', 'Sunday Night: A chance of rain. Mostly cloudy, with a low around 55.']

将我们的数据整合成熊猫的数据框架

我们现在可以将这些数据组合成一个数据框架并进行分析。DataFrame 是可以存储表格数据的对象,使数据分析变得容易。如果你想了解更多关于熊猫的知识,请点击这里查看我们的免费课程。

为了做到这一点,我们将调用 DataFrame 类,并传入我们拥有的每个项目列表。我们将它们作为字典的一部分传入。

每个字典键将成为数据帧中的一列,每个列表将成为该列中的值:

import pandas as pd
weather = pd.DataFrame({
    "period": periods,
    "short_desc": short_descs,
    "temp": temps,
    "desc":descs
})
weather
desc 时期 肖特 _desc 临时雇员
Zero 今晚:基本晴朗,最低气温 49 度左右。W… 今晚 基本清楚 低:49 华氏度
one 星期四:晴,最高气温接* 63 度。北 wi… 星期四 快活的 高:63 华氏度
Two 周四晚上:大部分时间是晴朗的,周围有低气温… 周四晚上 基本清楚 低:50 华氏度
three 星期五:晴,最高气温接* 67 度。东南… 星期五 快活的 高:67 华氏度
four 周五晚上:20%的可能性下雨后… 星期五晚上 轻微偶然事件 低:57 华氏度
five 周六:可能会下雨。多云,东北风高… 星期六 可能会下雨 高:64 华氏度
six 周六晚上:可能会下雨。多云,有一个 l… 周六晚上 可能会下雨 低:57 华氏度
seven 周日:可能会下雨。多云,附*有高… 星期日 可能会下雨 高:64 华氏度
eight 周日晚上:可能会下雨。大部分时间多云… 周日晚上 偶然下雨 低:55 华氏度

我们现在可以对数据进行一些分析。例如,我们可以使用一个正则表达式和 Series.str.extract 方法来提取数字温度值:

temp_nums = weather["temp"].str.extract("(?Pd+)", expand=False)
weather["temp_num"] = temp_nums.astype('int')
temp_nums
0 49
1 63
2 50
3 67
4 57
5 64
6 57
7 64
8 55
Name: temp_num, dtype: object

然后我们可以找到所有高温和低温的*均值:

weather["temp_num"].mean()
58.444444444444443

我们也可以只选择夜间发生的行:

is_night = weather["temp"].str.contains("Low")
weather["is_night"] = is_night
is_night
0 True
1 False
2 True
3 False
4 True
5 False
6 True
7 False
8 True
Name: temp, dtype: bool
weather[is_night]
0 True
1 False
2 True
3 False
4 True
5 False
6 True
7 False
8 True
Name: temp, dtype: bool
desc 时期 肖特 _desc 临时雇员 临时 _num 见识
Zero 今晚:基本晴朗,最低气温 49 度左右。W… 今晚 基本清楚 低:49 华氏度 forty-nine 真实的
Two 周四晚上:大部分时间是晴朗的,周围有低气温… 周四晚上 基本清楚 低:50 华氏度 Fifty 真实的
four 周五晚上:20%的可能性下雨后… 星期五晚上 轻微偶然事件 低:57 华氏度 Fifty-seven 真实的
six 周六晚上:可能会下雨。多云,有一个 l… 周六晚上 可能会下雨 低:57 华氏度 Fifty-seven 真实的
eight 周日晚上:可能会下雨。大部分时间多云… 周日晚上 偶然下雨 低:55 华氏度 Fifty-five 真实的

这个网页抓取项目的下一步

如果你已经走到这一步,恭喜你!您现在应该对如何抓取网页和提取数据有了很好的理解。当然,还有很多东西要学!

如果你想走得更远,一个好的下一步是选择一个网站,自己尝试一些网络抓取。一些很好的收集数据的例子是:

  • 新闻文章
  • 体育比分
  • 天气预报
  • 股票价格
  • 在线零售商价格

你可能还想继续搜集国家气象局的信息,看看你还能从这个页面中提取出什么其他数据,或者关于你自己的城市。

或者,如果你想让你的网络抓取技能更上一层楼,你可以看看我们的互动课程,它涵盖了网络抓取的基础知识和使用 Python 连接 API。有了这两项技能,你将能够从网络上的站点收集大量独特而有趣的数据集!

用正确的方法学习 Python。

从第一天开始,就在你的浏览器窗口中通过编写 Python 代码来学习 Python。这是学习 Python 的最佳方式——亲自看看我们 60 多门免费课程中的一门。

astronaut floating over code

尝试 Dataquest

Python 教程:使用 Scrapy 进行 Web 抓取(8 个代码示例)

原文:https://www.dataquest.io/blog/web-scraping-with-scrapy/

May 24, 2022

在本 Python 教程中,我们将使用 Scrapy 浏览 web 抓取,并完成一个示例电子商务网站抓取项目。

互联网上有超过 40 兆字节的数据。不幸的是,其中很大一部分是非结构化的,不可机读。这意味着你可以通过网站访问数据,从技术上讲,是以 HTML 页面的形式。有没有一种更简单的方法,不仅可以访问这些网络数据,还可以以结构化的格式下载这些数据,使其变得机器可读,并随时可以获得见解?

这就是网页抓取和 Scrapy 可以帮助你的地方!Web 抓取是从网站中提取结构化数据的过程。Scrapy 是最流行的 web 抓取框架之一,如果你想学习如何从 web 上抓取数据,它是一个很好的选择。在本教程中,您将学习如何开始使用 Scrapy,还将实现一个示例项目来抓取一个电子商务网站。

我们开始吧!

先决条件

要完成本教程,您需要在您的系统上安装 Python,并且建议您具备 Python 编码的基础知识。

安装刮刀

为了使用 Scrapy,你需要安装它。幸运的是,通过 pip 有一个非常简单的方法。可以用pip install scrapy安装 Scrapy。你也可以在垃圾文件中找到其他安装选项。建议在 Python 虚拟环境中安装 Scrapy。

virtualenv env
source env/bin/activate
pip install scrapy

这个代码片段创建一个新的 Python 虚拟环境,激活它,并安装 Scrapy。

杂乱的项目结构

每当你创建一个新的 Scrapy 项目,你需要使用一个特定的文件结构,以确保 Scrapy 知道在哪里寻找它的每个模块。幸运的是,Scrapy 有一个方便的命令,可以帮助你用 Scrapy 的所有模块创建一个空的 Scrapy 项目:

scrapy startproject bookscraper

如果您运行此命令,将创建一个新的基于模板的 Scrapy 项目,如下所示:

📦bookscraper
 ┣ 📂bookscraper
 ┃ ┣ 📂spiders
 ┃ ┃ ┗ 📜bookscraper.py
 ┃ ┣ 📜items.py
 ┃ ┣ 📜middlewares.py
 ┃ ┣ 📜pipelines.py
 ┃ ┗ 📜settings.py
 ┗ 📜scrapy.cfg
```py

这是一个典型的 Scrapy 项目文件结构。让我们快速检查一下这些文件和文件夹,以便您理解每个元素的作用:

*   文件夹:这个文件夹包含了我们未来所有的用于提取数据的 Scrapy spider 文件。
*   这个文件包含了条目对象,行为类似于 Python 字典,并提供了一个抽象层来存储 Scrapy 框架中的数据。
*   (高级):如果你想修改 Scrapy 运行和向服务器发出请求的方式(例如,绕过 antibot 解决方案),Scrapy 中间件是有用的。对于简单的抓取项目,不需要修改中间件。
*   `pipelines` : Scrapy pipelines 是在你提取数据之后,你想要实现的额外的数据处理步骤。您可以清理、组织甚至丢弃这些管道中的数据。
*   `settings`:Scrapy 如何运行的一般设置,例如,请求之间的延迟、缓存、文件下载设置等。

在本教程中,我们集中在两个 Scrapy 模块:蜘蛛和项目。有了这两个模块,您可以实现简单有效的 web 抓取器,它可以从任何网站提取数据。

在您成功安装了 Scrapy 并创建了一个新的 Scrapy 项目之后,让我们来学习如何编写一个 Scrapy spider(也称为 scraper ),从电子商务商店中提取产品数据。

### 抓取逻辑

作为一个例子,本教程使用了一个专门为练习网页抓取而创建的网站:[书籍来抓取](http://books.toscrape.com)。在编写蜘蛛程序之前,看一下网站并分析蜘蛛访问和抓取数据的路径是很重要的。

![](https://github.com/OpenDocCN/geekdoc-ds-zh/raw/master/dataquest-blog/img/efbace7e35df08d75f36cc1f65039ca8.png)

我们将利用这个网站收集所有可用的书籍。正如你在网站上看到的,每个类别页面都有多个类别的书籍和多个项目。这意味着我们的 scraper 需要去每个类别页面,打开每本书的页面。

下面我们来分解一下刮刀在网站上需要做的事情:

1.  打开网站([http://books.toscrape.com/](http://books.toscrape.com/))。
2.  找到所有的分类网址(比如[这个](http://books.toscrape.com/catalogue/category/books/travel_2/index.html))。
3.  找到分类页面上所有书籍的网址(比如[这个](http://books.toscrape.com/catalogue/its-only-the-himalayas_981/index.html))。
4.  逐个打开每个 URL,提取图书数据。

在 Scrapy 中,我们必须将抓取的数据存储在`Item`类中。在我们的例子中,一个条目会有像标题、链接和发布时间这样的字段。让我们实施该项目!

### 零碎物品

创建一个新的 Scrapy 项目来存储抓取的数据。让我们称这个项目为`BookItem`并添加代表每本书的数据字段:

*   标题
*   价格
*   通用产品代码
*   图像 _url
*   全球资源定位器(Uniform Resource Locator)

在代码中,这是如何在 Scrapy 中创建新的项目类:

from scrapy import Item, Field
class BookItem(Item):
title = Field()
price = Field()
upc = Field()
image_url = Field()
url = Field()


在代码片段中可以看到,您需要导入两个 Scrapy 对象:`Item`和`Field`。

`Item`被用作`BookItem`的父类,所以 Scrapy 知道这个对象将在整个项目中用来存储和引用抓取的数据字段。

`Field`是作为项目类的一部分存储的对象,用于指示项目中的数据字段。

一旦你创建了`BookItem`类,你就可以继续在 Scrapy spider 上工作,处理抓取逻辑和提取。

### 刺痒蜘蛛

在名为`bookscraper.py`的`spiders`文件夹中创建一个新的 Python 文件

touch bookscraper.py


这个蜘蛛文件包含蜘蛛逻辑和抓取代码。为了确定该文件中需要包含哪些内容,让我们检查一下网站!

### 网站检查

网站检查是网页抓取过程中一个繁琐但重要的步骤。没有适当的检查,你就不知道如何有效地定位和提取网站上的数据。检查通常是使用浏览器的“inspect”工具或一些第三方浏览器插件来完成的,这些插件可以让您“深入查看”并分析网站的源代码。建议你在分析网站的时候关闭浏览器中的 JS 执行功能——这样你就可以像你的刺痒蜘蛛看到网站一样看到网站。

让我们回顾一下我们需要在网站的源代码中找到哪些 URL 和数据字段:

*   类别 URL
*   图书 URL
*   最后,预订数据字段

检查源代码以定位 HTML 中的类别 URL:

![](https://github.com/OpenDocCN/geekdoc-ds-zh/raw/master/dataquest-blog/img/79793bd45535218dd67712920d5ca066.png)

通过检查网站,您可以注意到类别 URL 存储在一个带有类`nav nav-list`的`ul` HTML 元素中。这是至关重要的信息,因为您可以使用这个 CSS 和周围的 HTML 元素来定位页面上的所有类别 URLs 这正是我们所需要的!

让我们记住这一点,并深入挖掘,找到其他潜在的 CSS 选择器,我们可以在我们的蜘蛛。检查 HTML 以查找图书页面 URL:

![](https://github.com/OpenDocCN/geekdoc-ds-zh/raw/master/dataquest-blog/img/2f6823e28334feba4ced0fd517d79484.png)

单个图书页面的 URL 位于带有 CSS 类`product pod`的`article` HTML 元素下。我们可以使用这个 CSS 规则,通过 scraper 找到图书页面的 URL。

最后,检查网站,找到图书页面上的单个数据字段:
![](https://github.com/OpenDocCN/geekdoc-ds-zh/raw/master/dataquest-blog/img/47edcb45efeaff498e398dc63f820050.png)

这一次稍微有点棘手,因为我们在页面上寻找多个数据字段,而不仅仅是一个。所以我们需要多个 CSS 选择器来找到页面上的每个字段。正如您在上面的截图中看到的,一些数据字段(如 UPC 和 price)可以在 HTML 表格中找到,但其他字段(如 title)在页面顶部的另一种 HTML 元素中。

在检查并找到我们需要的所有数据字段和 URL 定位器之后,您可以实现蜘蛛:

from scrapy.spiders import CrawlSpider, Rule
from scrapy.linkextractors import LinkExtractor
from bookscraper.items import BookItem

class BookScraper(CrawlSpider):
name = "bookscraper"
start_urls = ["http://books.toscrape.com/"]

rules = (
    Rule(LinkExtractor(restrict_css=".nav-list > li > ul > li > a"), follow=True),
    Rule(LinkExtractor(restrict_css=".product_pod > h3 > a"), callback="parse_book")
)

def parse_book(self, response):
    book_item = BookItem()

    book_item["image_url"] = response.urljoin(response.css(".item.active > img::attr(src)").get())
    book_item["title"] = response.css(".col-sm-6.product_main > h1::text").get()
    book_item["price"] = response.css(".price_color::text").get()
    book_item["upc"] = response.css(".table.table-striped > tr:nth-child(1) > td::text").get()
    book_item["url"] = response.url
    return book_item

让我们来分析一下这段代码中发生了什么:

1.  刺儿头会打开网站[http://books.toscrape.com/](http://books.toscrape.com/)。
2.  它将开始遍历由`.nav-list > li > ul > li > a` CSS 选择器定义的类别页面。
3.  它将使用这个 CSS 选择器:`.product_pod > h3 > a`开始遍历所有类别页面上的所有图书页面。
4.  最后,一旦打开一本书的页面,Scrapy 从页面中提取出`image_url`、`title`、`price`、`upc`和`url`数据字段,并返回`BookItem`对象。

### 运行蜘蛛

最后,我们需要测试我们的蜘蛛实际工作并抓取我们需要的所有数据。您可以使用`scrapy crawl`命令运行蜘蛛,并引用蜘蛛的名称(如蜘蛛代码中所定义的,而不是文件的名称!):

scrapy crawl bookscraper


运行这个命令后,你会看到 Scrapy 的实时输出,因为它正在抓取整个网站:

{'image_url': 'http://books.toscrape.com/media/cache/0f/76/0f76b00ea914ced1822d8ac3480c485f.jpg',
'price': '£12.61',
'title': 'The Third Wave: An Entrepreneur’s Vision of the Future',
'upc': '3bebf34ee9330cbd',
'url': 'http://books.toscrape.com/catalogue/the-third-wave-an-entrepreneurs-vision-of-the-future_862/index.html'}
2022-05-01 18:46:18 [scrapy.core.scraper] DEBUG: Scraped from <200 http://books.toscrape.com/catalogue/shoe-dog-a-memoir-by-the-creator-of-nike_831/index.html>
{'image_url': 'http://books.toscrape.com/media/cache/fc/21/fc21d144c7289e5b1cb133e01a925126.jpg',
'price': '£23.99',
'title': 'Shoe Dog: A Memoir by the Creator of NIKE',
'upc': '0e0dcc3339602b28',
'url': 'http://books.toscrape.com/catalogue/shoe-dog-a-memoir-by-the-creator-of-nike_831/index.html'}
2022-05-01 18:46:18 [scrapy.core.scraper] DEBUG: Scraped from <200 http://books.toscrape.com/catalogue/the-10-entrepreneur-live-your-startup-dream-without-quitting-your-day-job_836/index.html>
{'image_url': 'http://books.toscrape.com/media/cache/50/4b/504b1891508614ff9393563f69d66c95.jpg',
'price': '£27.55',
'title': 'The 10% Entrepreneur: Live Your Startup Dream Without Quitting Your '
'Day Job',
'upc': '56e4f9eab2e8e674',
'url': 'http://books.toscrape.com/catalogue/the-10-entrepreneur-live-your-startup-dream-without-quitting-your-day-job_836/index.html'}
2022-05-01 18:46:18 [scrapy.core.scraper] DEBUG: Scraped from <200 http://books.toscrape.com/catalogue/far-from-true-promise-falls-trilogy-2_320/index.html>
{'image_url': 'http://books.toscrape.com/media/cache/9c/aa/9caacda3ff43984447ee22712e7e9ca9.jpg',
'price': '£34.93',
'title': 'Far From True (Promise Falls Trilogy #2)',
'upc': 'ad15a9a139919918',
'url': 'http://books.toscrape.com/catalogue/far-from-true-promise-falls-trilogy-2_320/index.html'}
2022-05-01 18:46:18 [scrapy.core.scraper] DEBUG: Scraped from <200 http://books.toscrape.com/catalogue/the-travelers_285/index.html>
{'image_url': 'http://books.toscrape.com/media/cache/42/a3/42a345bdcb3e13d5922ff79cd1c07d0e.jpg',
'price': '£15.77',
'title': 'The Travelers',
'upc': '2b685187f55c5d31',
'url': 'http://books.toscrape.com/catalogue/the-travelers_285/index.html'}
2022-05-01 18:46:18 [scrapy.core.scraper] DEBUG: Scraped from <200 http://books.toscrape.com/catalogue/the-bone-hunters-lexy-vaughan-steven-macaulay-2_343/index.html>
{'image_url': 'http://books.toscrape.com/media/cache/8d/1f/8d1f11673fbe46f47f27b9a4c8efbf8a.jpg',
'price': '£59.71',
'title': 'The Bone Hunters (Lexy Vaughan & Steven Macaulay #2)',
'upc': '9c4d061c1e2fe6bf',
'url': 'http://books.toscrape.com/catalogue/the-bone-hunters-lexy-vaughan-steven-macaulay-2_343/index.html'}


### 结论

我希望这个快速的 Scrapy 教程可以帮助你开始 Scrapy 和网络抓取。网络抓取是一项非常有趣的学习技能,但是能够从网上下载大量数据来构建有趣的东西也是非常有价值的。Scrapy 有一个很棒的[社区](https://scrapy.org/community/),所以你可以确定,无论何时你在 scrapy 中遇到困难,你都可以在那里找到问题的答案,或者在 [Stack Overflow](https://stackoverflow.com/search?q=scrapy) 、 [Reddit](https://www.reddit.com/search/?q=scrapy) 或其他地方。刮的开心!

# 韦斯:“Dataquest 教会了我所有我需要知道的东西,而不需要 5 万美元的学位。”

> 原文:<https://www.dataquest.io/blog/wes-brooks/>

July 20, 2017

韦斯·布鲁克斯一直明白数据是商业成功的关键。“在我职业生涯的早期,我以数据驱动的心态建立了其他业务和我自己的业务。”

这让韦斯在广告公司 Cornett 找到了一份工作,在那里他为财富 500 强公司做营销活动。

“他们聘请我将同样的数据驱动的业务构建方法应用到他们客户的业务中。"

在那里,Wes 遇到了处理来自不同来源的数据的挑战。

“我们为我们的一个客户构建了一个数据湖,统一了许多不同的来源。这使我们能够在一个仪表板上为他们的营销团队汇总数据。”

dataquest game me 课程涵盖了我

需要学习,以及一条精心设计的路线让我到达那里。

“这需要一个工程师团队使用昂贵的软件来实现。一旦建成,我仍然不得不依靠我们的开发团队,因为我没有分析原始数据的经验。”

“这很快变得既耗时又昂贵。我决定我需要开始为自己学习数据科学。”

“起初,我依赖于个人书籍和课程,不得不制定自己的课程。所有的选择都让我感到麻痹。”

“我尝试过一些 MOOCs(大规模开放在线课程)。它们只是讲课的录像,看的时间太长了。视频会让你兴奋,但它们不利于学习细节。”

“Dataquest 教会了我所有我需要知道的东西,而不是 5 万美元的学位。这是自定进度的,我喜欢边做边学。”

韦斯解释了 Dataquest 如何消除试图自学的猜测。“它给了我一个涵盖我需要学习的所有内容的课程,以及一条通往那里的精心设计的路线。”

“Dataquest 基于项目的学习非常重要。他们给了我一个机会,让我在学习的过程中准确地应用我所学的东西。”

韦斯的努力得到了回报。他即将加入 Cru,这是一个基于国际信仰的非营利组织。“我将和大约 25,000 名员工一起加入他们的数据科学和分析团队。

“我将处理各种数据。网络分析,捐赠和更多。我很高兴能全职进入数据科学的角色,并为我相信的事业应用我的技能。”

当被问及是否会推荐 Dataquest 时,韦斯毫不犹豫。“绝对可以。它经济实惠,拥有您需要知道的一切,并结合了实时指导帮助。”

# 成为一名高级数据科学家意味着什么?

> 原文:<https://www.dataquest.io/blog/what-does-it-mean-to-be-a-senior-data-scientist/>

April 23, 2018

*这篇文章一部分是为我自己写的,基于不同人的谈话——它也受到了作为一名高级工程师的[的启发。我试图回答类似“我们对一名高级数据科学家有什么期望”这样的问题。](https://www.kitchensoap.com/2012/10/25/on-being-a-senior-engineer/)*

我的头衔是“高级数据科学家”,我经常开玩笑说我不知道这是什么意思。🙂

我将采用与上面关于工程师的帖子非常相似的观点,因为我觉得有很多重叠。我将从根本上关注分析师/工程师/研究人员的“成熟度”,因为我觉得头衔可能非常具有误导性,而且我已经坦率地看到“高级”数据科学家有相当不成熟的行为。

我提出了一些想法,这些不一定是有序的。

**1。高级数据科学家明白软件/机器学习有一个生命周期,所以花了很多时间来思考这个问题。**

技术债务、可维护性、系统设计、设计文档等。这些都是:

“我会错过什么?”

“这怎么行不通?”

"请你尽可能地戳穿我对这件事的想法,好吗?"

“即使它在技术上是合理的,对于组织的其他部门来说,操作、故障排除和扩展它是否足够容易理解?”

**2。资深数据科学家明白“数据”总是有缺陷的。这些缺陷可能是数据生成过程、数据偏差。**

我曾经作为候选人对一位资深数据科学家进行了一次技术面试,我对最后的问题有点困惑,这个问题是“如果数据是错的怎么办?”。这是一个合理的问题,也是我们应该思考的问题。

通常,我们观察到的许多种群不是随机抽样的,我们需要思考一下如何管理这种情况。我有趣地发现,初级数据科学家通常认为事实并非如此。

**3。高级数据科学家了解技术决策的“软”方面。**

我越来越多地看到工具的选择,并想知道它们的“感觉”方面。例如,“静态语言是最好的”或“我们应该使用 pytest 而不是 unittest”,这越来越多地是因为“品味”或“感觉”或“哲学”。这些都是完全合理的事情。例如,我喜欢 pytest 函数语法,但是我知道其他工程师喜欢其他工具——这没关系。

另一件事是,有时人们对来自特定供应商或特定生态系统的工具有不好的体验。举例来说,如果你在一家用 Zorg 编写软件的公司工作,你发现它难以置信地难以部署,并且这个项目是一个彻底的失败,那么如果在公司会议上提起它,你会对 Zorg 有一个情绪化的反应。工程师和数据科学家经常痴迷于理性,但是我们对架构和软件的感觉很重要。否则我们永远得不到我们需要的认同。我还没有读完,但是我尊敬的几位资深技术专家推荐的一本书是[Words of working](http://www.amazon.co.uk/Words-That-Work-What-People/dp/1401309291/ref=tmm_pap_swatch_0?_encoding=UTF8&qid=1521243459&sr=1-3)

由此得出的一个推论是,我们可以生产不被使用的机器学习模型。

**4。高级数据科学家关注影响和价值**

如果深度学习模型因为缺乏信任而无法投入生产,那么你就失败了。这不是为了满足你的求知欲,或者你对“简历驱动发展”的需求。考虑买入和你的价值时间很重要。埃里克·伯恩哈德森在推特上写道:

> 我认为这些天我了解机器学习的大部分价值来自于告诉人们为什么机器学习不能解决他们的问题

这非常重要,有时一个简单的规则引擎就可以了。有时只是一个 SQL 查询。使用合适的工具非常重要。这很复杂,而且通常没有一个“最佳”的解决方案,所有的解决方案都有权衡。

通常你可以用数据让事情变得简单。我喜欢问数据科学家的一个问题是‘你什么时候决定不使用 ML 的?’例如,几年前,我在$ OLD _ EMPLOYER 为一些分析建立了一个数据管道,节省了数千美元。一些分析管道涉及到为库存管理匹配文本。例如,库存名称将是相似的——因此使用模糊匹配或类似的东西似乎是很自然的。事实证明,这种算法太慢,不切实际。根据货币价值,有 100 个库存项目需要匹配,所以我只是在字典中对最常见的拼写错误/缩写进行了编码。

这是非常有价值的,也是比使用机器学习更可靠的解决方案。有时候自动化是你需要做的🙂有时是计数,有时是机器学习。

**5。资深数据科学家关心伦理**

最*,在数据科学和技术社区中,我们看到了讨论道德的必要性。学术界已经有一些关于这方面的有趣和值得一读的文献,我不会过多地卷入这些争论。

然而,作为一名在受监管的金融服务领域工作的高级数据科学家,我逐渐意识到,了解 GDPR 是我的工作,这是我们在讨论项目可行性时经常提到的事情,也是一个“风险因素”。忽视这一点是不成熟的,坦率地说是不道德和不专业的。

至少高级数据科学家应该阅读一些数据科学的道德准则,并对这些有自己的看法。理想的情况是,你应该有自己的道德准则,也许可以把它们强加给自己。当然,您应该在风险规划中考虑这一点,并考虑您可以访问哪些数据,以及如何集成安全性。不幸的是,这会增加时间,但是在客户信任和良好合规性方面做“正确的”事情通常需要更长的时间。正如我们在 Theranos 事件中看到的那样——“快速行动,打破常规”并不总是最好的座右铭。

鸣谢:感谢 Eoin Hurrell 和 Bertil Hatt 帮助充实了这些想法。我也很感激与朋友和同事的交谈,如埃迪·贝尔、米克·库尼、米克·克劳福德、伊恩·奥兹瓦尔德、达特·阮和弗拉西奥斯·瓦西里乌。我从大多数和我交谈过的人身上学到东西,如果我忘记了,很抱歉。最后还要感谢奥黛丽·索纳德,他不断提醒我‘算法做它们想做的事情’并不是一个充分的伦理解释,我应该更多地考虑这些问题。

*编者按:这原本是[贴在模特都是有启发性的和错误的](https://peadarcoyle.wordpress.com/2018/03/17/what-does-it-mean-to-be-a-senior-data-scientist/?utm_source=dataquest&utm_medium=blog)上,已经用 perlesson 转贴了。作者[皮德尔·科伊尔](https://www.linkedin.com/in/peadarcoyle/)是 Zopa 的高级数据科学家。*

# 什么是数据工程师?

> 原文:<https://www.dataquest.io/blog/what-is-a-data-engineer/>

January 25, 2017![data-engineer](https://github.com/OpenDocCN/geekdoc-ds-zh/raw/master/dataquest-blog/img/9fa1ec4fa19b96b928f771eae33654f4.png)

从帮助[汽车自动驾驶](https://www.wired.com/2017/01/human-problem-blocking-path-self-driving-cars/)到帮助[脸书在照片中给你贴标签](https://www.npr.org/sections/alltechconsidered/2016/05/18/477819617/facebooks-facial-recognition-software-is-different-from-the-fbis-heres-why),数据科学最*吸引了很多关注。数据科学家已经成为非常受欢迎的[,这是有充分理由的——一个熟练的数据科学家可以给企业带来难以置信的价值。但是数据工程师呢?他们是谁,他们是做什么的?](https://readwrite.com/2014/07/21/data-scientist-income-skills-jobs/)

数据科学家的能力取决于他们能接触到的数据。大多数公司在数据库和文本文件中以各种格式存储数据。这就是数据工程师的用武之地——他们构建管道,将数据转换成数据科学家可以使用的格式。数据工程师与数据科学家一样重要,但往往不太引人注目,因为他们往往离分析的最终产品更远。

一个很好的类比是赛车制造商和赛车手。车手获得了在赛道上飞驰的兴奋,以及在人群面前胜利的激动。但建设者得到了调整引擎的乐趣,试验不同的排气设置,并创造一个强大,稳健的机器。如果你是那种喜欢构建和调整系统的人,数据工程可能适合你。在本帖中,我们将探索数据工程师的日常工作,并讨论该角色所需的技能。

## 数据工程师角色

数据科学领域非常广泛,从清理数据到部署预测模型,无所不包。然而,很少有一个数据科学家日复一日地跨领域工作。数据科学家通常专注于几个领域,并由其他科学家和分析师组成的团队进行补充。

数据工程也是一个广阔的领域,但是任何一个数据工程师都不需要知道所有的技能。在这一节中,我们将勾勒出数据工程的大致轮廓,然后通过更具体的描述来说明具体的数据工程角色。

数据工程师将数据转换成对分析有用的格式。想象一下,你是一名数据工程师,正在研究优步的一个简单的竞争对手 Rebu。您的用户在他们的设备上有一个应用程序,通过该应用程序他们可以访问您的服务。他们通过你的应用程序请求搭车到目的地,然后被路由到一个司机那里,司机接他们并把他们放下。乘车结束后,他们被充电,并可以选择评价他们的司机。

为了维持这样的服务,您需要:

*   面向用户的移动应用
*   一款面向司机的手机应用
*   可以将用户的请求传递给司机,并处理其他细节(如更新支付信息)的服务器

这是一个展示交流的图表:

![ditaa_diagram_1-3](https://github.com/OpenDocCN/geekdoc-ds-zh/raw/master/dataquest-blog/img/79929396ad379e5eb6f577436975ec1d.png)

如你所料,这种系统会产生大量的数据。您将拥有几个不同的数据存储:

*   支持主应用程序的数据库。这包含用户和驱动程序信息。
*   服务器分析日志
    *   服务器访问日志。对于应用程序向服务器发出的每个请求,它们各占一行。
    *   服务器错误日志。这些包含了你的应用程序产生的所有服务器端错误。
*   应用分析日志
    *   应用程序事件日志。这些信息包含用户和司机在应用程序中采取的行动。例如,当他们点击一个按钮或者更新他们的支付信息时,你会记录下来。
    *   应用程序错误日志。这些包含应用程序中的错误信息。
*   游乐设备数据库。这包含用户/驾驶员对的单个游乐设备的信息,并包含游乐设备的状态信息。
*   客户服务数据库。这包含关于客户服务代理的客户交互的信息。它可以包括语音记录和电子邮件日志。

以下是显示数据源的更新图表:

![ditaa_diagram_2-2](https://github.com/OpenDocCN/geekdoc-ds-zh/raw/master/dataquest-blog/img/1f6dadc25c08734725f77c1b431bb702.png)

假设一位数据科学家想要分析用户使用您的服务的行为历史,并查看哪些行为与花费更多的用户相关联。为了让他们能够创建这些,您需要结合来自服务器访问日志和应用程序事件日志的信息。您需要:

*   定期从用户设备收集应用分析日志
*   将应用分析日志与引用用户的任何服务器日志条目相结合
*   创建一个 API 端点,返回任何用户的事件历史

为了解决这个问题,您需要创建一个管道,可以实时接收移动应用程序日志和服务器日志,解析它们,并将它们附加到特定用户。然后,您需要将解析后的日志存储在数据库中,这样 API 就可以方便地查询它们。您需要在负载*衡器后面启动几个服务器来处理传入的日志。

您将遇到的大多数问题都与可靠性和分布式系统有关。例如,如果您有数以百万计的设备要收集日志,并且需求不断变化(早上,您会收到大量日志,但午夜不会有这么多),那么您将需要一个能够自动增减服务器数量的系统。

![](https://github.com/OpenDocCN/geekdoc-ds-zh/raw/master/dataquest-blog/img/bd8eb20b51112a7779d658c99e81e07c.png)

在负载*衡器后运行服务器。服务器向负载*衡器注册,负载*衡器根据它们的繁忙程度向它们发送流量。这意味着可以根据需要添加或删除服务器。

粗略地说,数据管道中的操作包括以下阶段:

*   摄取—这包括收集所需的数据。
*   处理-这包括处理数据以获得您想要的最终结果。
*   存储—这包括存储最终结果以便快速检索。
*   访问—您需要启用工具或用户来访问管道的最终结果。

![](https://github.com/OpenDocCN/geekdoc-ds-zh/raw/master/dataquest-blog/img/04496cc3147c7119705c631b8bf8d539.png)

数据管道—输入数据通过一系列阶段转换为输出数据。

### 发现质量差的游乐设施

举一个更复杂的例子,假设一位数据科学家想要构建一个系统,找出所有因应用程序或驱动程序问题而提前结束的乘坐。一种方法是查看客户服务数据库,查看哪些游乐设备以问题结束,并使用一些关于游乐设备的数据分析他们的语言日志。

在数据科学家能够做到这一点之前,他们需要一种方法来将客户服务数据库中的日志与特定的乘坐进行匹配。作为一名数据工程师,您会希望创建一个 API 端点,允许数据科学家查询与特定游乐设备相关的所有客户服务消息。为此,您需要:

*   创建一个系统,从乘车数据库中提取数据,并计算出有关乘车的信息,例如乘车时间,以及目的地是否符合用户的初始请求。
*   将每次乘坐的统计数据与用户信息(如姓名和用户 id)结合起来。
*   从应用程序和服务器分析日志中提取在游乐设备时间段内与用户相关的错误信息。
*   查找用户的所有客户服务查询。
*   创建一些启发式规则来匹配乘坐和客户服务查询(一个简单的例子是客户服务查询总是关于前一次乘坐)
*   根据需要存储值,以确保 API 快速执行,即使是为了将来的乘坐。
*   创建一个 API,返回与特定游乐设备相关的所有客户服务信息。

一个熟练的数据工程师将能够建立一个管道,每次增加一个新的游乐设备时,执行上述每个步骤。这将确保 API 提供的数据总是最新的,并且数据科学家所做的任何分析都是有效的。

### 数据工程技能

数据工程师需要擅长:

*   构建分布式系统
*   创建可靠的管道
*   组合数据源
*   构建数据存储
*   与数据科学团队合作,为他们构建合适的解决方案

注意,上面我们没有提到任何工具。虽然像 Hadoop 和 Spark 这样的工具以及像 Scala 和 Python 这样的语言对于数据工程来说很重要,但是更重要的是要很好地理解这些概念并知道如何构建现实世界的系统。在这个数据工程系列中,我们将继续关注概念胜于工具。

## 数据工程角色

尽管数据工程师需要具备上面列出的技能,但数据工程师的日常工作会因他们工作的公司类型而有所不同。大体上,您可以将数据工程师分为几类:

*   通才
*   以管道为中心
*   以数据库为中心

让我们逐一了解这些类别。

### 通才

通才数据工程师通常在一个小团队中工作。没有数据工程师,数据分析师和科学论者就没有什么可分析的,这使得数据工程师成为数据科学团队中至关重要的第一个成员。

当数据工程师是公司中唯一关注数据的人时,他们通常最终不得不做更多的端到端工作。例如,一个通才数据工程师可能需要做从接收数据到处理数据再到最终分析的所有事情。这需要比大多数数据工程师更多的数据科学技能。然而,它还需要较少的系统架构知识——小团队和公司没有大量用户,因此规模工程并不重要。对于一个想转型到数据工程的数据科学家来说,这是一个很好的角色。

当我们假设的优步竞争对手 Rebu 规模较小时,可能会要求数据工程师创建一个仪表板,显示上个月每天的乘车次数,以及下个月的预测。

### 以管道为中心

在具有复杂数据科学需求的中型公司中,以管道为中心的数据工程师往往是必要的。以管道为中心的数据工程师将与数据科学家团队合作,将数据转换为有用的分析格式。这需要深入了解分布式系统和计算机科学。

随着 Rebu 的发展,以管道为中心的数据工程师可能会被要求创建一个工具,使数据科学家能够查询有关游乐设备的元数据,以便在预测算法中使用。

### 以数据库为中心

以数据库为中心的数据工程师专注于设置和填充分析数据库。这涉及到一些管道方面的工作,但更多的工作是调优数据库以进行快速分析和创建表模式。这涉及到将数据放入 T2 仓库的 ETL 工作。这种类型的数据工程师通常出现在拥有许多数据分析师的大型公司中,他们的数据分布在多个数据库中。

在 Rebu 接管世界后,以数据库为中心的数据工程师可能会设计一个分析数据库,然后创建脚本将信息从主应用程序数据库拉入分析数据库。

![](https://github.com/OpenDocCN/geekdoc-ds-zh/raw/master/dataquest-blog/img/4832be416c74740661b82d7519c73389.png)

数据仓库接收数据,然后使其他人可以方便地查询它。

## 数据工程技能

在这篇文章中,我们讨论了数据工程和在高层次实践它所需的技能。如果你对构建大规模系统感兴趣,或者对处理大量数据感兴趣,那么数据工程是一个很好的领域。

看到您的自动缩放数据管道突然处理流量高峰,或者开始使用具有数兆字节 RAM 的机器,可能会非常令人兴奋。构建一个只需少量调整就能工作数月或数年的健壮系统令人满意。

因为数据工程是关于学习处理规模和效率的,你自己很难找到好的实践材料。但是不要放弃希望——自学数据工程并在该领域找到工作是很有可能的。

我们最*在 Dataquest 推出了新的互动[数据工程路径](https://www.dataquest.io/path/data-engineer),旨在教你成为数据工程师所需的技能。如果你有兴趣,你可以[注册开始免费学习](https://www.dataquest.io)。

### 成为一名数据工程师!

现在就学习成为一名数据工程师所需的技能。注册一个免费帐户,访问我们的交互式 Python 数据工程课程内容。

[Sign up now!](https://app.dataquest.io/signup)

*(免费)*

![YouTube video player for ddM21fz1Tt0](https://github.com/OpenDocCN/geekdoc-ds-zh/raw/master/dataquest-blog/img/5a85348206993fc2a430506128b76684.png)

*[https://www.youtube.com/embed/ddM21fz1Tt0?rel=0](https://www.youtube.com/embed/ddM21fz1Tt0?rel=0)*

# 什么是数据科学?

> 原文:<https://www.dataquest.io/blog/what-is-data-science/>

May 23, 2019![](https://github.com/OpenDocCN/geekdoc-ds-zh/raw/master/dataquest-blog/img/58efaaf67f25ed37522b7713a35ef58d.png)

## 什么是数据科学?

数据科学是一个专注于从数据中获取洞察力的研究和实践领域。

数据科学的从业者使用编程技能、统计知识和机器学习技术来挖掘大型数据集,以获得可用于分析过去甚至预测未来的模式。

## 数学遇见编程:快速历史

为了更好地理解数据科学到底是什么,快速浏览一下它的来源会很有帮助。在许多方面,数据科学是已经存在了几十年的两个领域的合并的结果:统计学和计算机科学。

当然,几个世纪以来,统计学家一直在处理数字。但 20 世纪中期计算机科学的出现为统计学家提供了一种新的工具,可以比以前更快地分析数据。

早在 20 世纪 60 年代,像约翰·w·图基(John W. Tukey)这样的统计学家就在理论上探讨计算机如何能够给这个领域带来革命性的变化,但当时它们的影响微乎其微——它们只是太慢、太贵了。20 世纪 80 年代,个人电脑的兴起使数字数据收集成为可能,公司开始尽可能地收集数据。到了 20 世纪 90 年代,一些公司成功地利用这些数据来设计营销策略。分析这些新的数字数据既需要统计学家的统计知识,也需要计算机科学家的编程技能。

到 21 世纪初,部分由于互联网的出现,许多公司可以访问海量数据。与此同时,计算机的处理能力已经发展到可以对庞大的数据集进行复杂分析的程度,更先进的技术,如带有机器学习的预测分析,也即将实现。

企业和学术界都开始认识到拥有收集、处理和分析数字数据所需的编程技能的专家的价值*以及选择准确回答问题和获得有意义的见解所需的分析类型所需的统计技能。“数据科学”这个术语已经存在了几十年,成为描述这种技能融合的主流短语。*

## 数据科学家是做什么的?

在日常工作中,数据科学家通常负责数据发生的一切,从收集数据到分析数据并报告结果。尽管每项数据科学工作都不同,但这里有一种方法可以直观显示数据科学工作流,并提供了一些数据科学家在每个步骤中可能执行的典型任务的示例。

![what-is-data-science-workflow](https://github.com/OpenDocCN/geekdoc-ds-zh/raw/master/dataquest-blog/img/6bf5095bda48a60ff1928e4a0310ed20.png "what-is-data-science-workflow")

它是这样工作的:

1.  **捕捉数据。**例如:从公司数据库中提取数据,从网站上抓取数据,访问 API 等。
2.  **管理数据。**例如:正确存储数据,并且几乎总是会涉及到清理数据。
3.  **探索性分析。**例如:执行不同的分析并以各种方式可视化数据,以寻找模式、问题和更深入研究的机会。
4.  **最终分析。**例如:深入挖掘数据以回答特定的业务问题,并微调预测模型以获得最准确的结果。
5.  **举报。**例如:向管理层提交分析结果,这可能包括撰写报告、制作可视化效果以及根据分析结果提出建议。报告还可能意味着将分析结果插入到数据产品或仪表板中,以便其他团队成员或客户可以轻松地访问它。

尽管如此,数据科学家每天做的事情可能会有很大差异,这在很大程度上是因为不同的公司以不同的方式利用数据科学。

## 企业如何从雇佣数据科学家中获益

在最高级别,数据科学允许公司将数据转化为实际的商业价值。

例如,考虑一家专业电子商务零售商。这样的公司每天可能会有数万次浏览量和数百个订单。对于每次浏览量,它可以使用自动化工具收集大量关于访问者是谁以及他们在网站上采取了什么行动的数据。对于每一笔订单,他们的销售系统都可以轻松地收集关于实际客户的各种数据点。

然而,这些数据堆积得很快,它本身没有任何固有的值。为了从中获取价值,公司需要对其进行分析,寻找能够暗示未来商业战略和战术的模式和见解。这些数据提供的可操作性和前瞻性洞察越多,对公司的价值就越大。

因为公司可以收集许多不同类型的数据,所以数据科学家可以通过各种方式增加价值。以下是数据科学如何为全球企业增加价值的几个例子:

*   改善决策—数据科学为管理层提供了可操作的情报,领导者可以利用这些情报来制定短期和长期战略。
*   改善招聘——数据科学可以帮助更客观地评估候选人,根除低效和偏见。
*   预测未来-使用机器学习算法,数据科学家可以在数据中发现人类无法发现的模式,并以更高的准确度预测未来的结果。
*   提高针对性-数据科学可以帮助公司找到新的目标市场,更好地了解现有客户,更准确地预测客户的需求。
*   识别新的机会—通过探索数据和寻找模式,数据科学家可以识别新的业务机会,否则这些机会可能不会显而易见。
*   改善风险评估-数据科学通常可以在将风险想法付诸实施之前通过运行数字来“测试”这些想法,从而使公司能够避免潜在的代价高昂的风险和错误。
*   培养数据第一的文化-数据科学家或数据科学团队可以通过为他们提供仪表板等数据工具以及理解这些工具所需的培训,来帮助整个公司的每个团队进行基于数据的决策。

## 数据科学领域的可用职业

![data-set-dataset-data-science-projects](https://github.com/OpenDocCN/geekdoc-ds-zh/raw/master/dataquest-blog/img/21e6d0017207bca0d9408cf650027477.png "data-set-dataset-data-science-projects")

数据科学是一个快速发展的领域,合格的数据科学家 T2 非常吃香。

许多专业人士以数据分析师的身份进入该领域,这是一个技术技能门槛较低的入门级角色,一旦他们有了一点专业经验,就会晋升到数据科学家级别,尽管也有可能直接被聘为数据科学家。有关数据科学行业各种工作角色的更多详细信息,请查看[我们的数据科学职业选择指南](https://www.dataquest.io/blog/career-guide-data-science-options/)。

薪水各不相同,但在美国,数据分析师的*均年薪超过 65,000 美元(根据大约 2019 年 5 月的[实际上是](https://www.indeed.com/salaries/Data-Analyst-Salaries))。数据科学家的*均年薪超过 12 万美元。甚至更高级的职位,如高级数据科学家或机器学习工程师,也可以达到 14 万美元以上。

虽然看起来这么高的薪水需要在教育上进行大量的前期投资,但事实并非如此!你可以[在线学习数据科学所需的所有技能](https://www.dataquest.io/path/data-scientist/)。

#### 揭穿关于数据科学职业的神话

你不需要数学或编程背景。虽然这两者中的任何一个肯定会对*有所帮助*,但是完全有可能从头开始学习数据科学,除了你在高中学到的东西之外,不需要任何编程技能和数学知识。查看[我们的特色学生故事](https://www.dataquest.io/blog/topics/student-stories/),你会发现很多快乐就业的学生的例子,他们第一次来到 Dataquest 时很少或没有受过编程或统计培训。

你不需要花很多钱。大学学位要花费数十万美元,甚至训练营的费用也超过 1 万美元。但是在 Dataquest,你可以以更便宜的价格在线互动学习。 [88%的学生](https://www.dataquest.io/blog/dataquest-reviews-survey-2019/)表示 Dataquest 是他们学习数据科学的主要或唯一来源,96%的学生推荐 Dataquest 来改善你的职业机会。

不会花很长时间的。我们的大多数学生在不到一年的时间内达到了他们的学习目标(尽管许多人会继续学习更长时间,以保持他们的技能,并在我们添加新课程时学习新的技能),同时每周学习不到 10 个小时。当然,成为一名*伟大的*数据科学家需要时间和努力,但你可能会惊讶于你能多快学会完成伟大工作所需的所有基本技能。

## 如何学习数据科学

对数据科学感兴趣吗?现在你可以做一些事情来确保你有一个好的开始。

首先,**花点时间思考一下** ***为什么*** —是什么激励你学习数据科学?当然,这里有诱人的薪水,但是试着更深入一些,找到一些你感兴趣的数据。找到你想要回答的基于数据的问题,这个问题会推动你继续学习。

二、**通读** [**我们的数据科学职业指南**](https://www.dataquest.io/blog/data-science-career-guide/) 。它很长,但它是基于对数据科学家和数据科学招聘经理的数十次采访。它会让你很好地了解数据科学行业目前的状况,以及截至 2019 年招聘人员的需求。提前了解这一点将有助于你避免错误,并为你节省一些申请工作的时间,因为你已经有了一个很好的项目组合,并准备根据指南的建议进行。

第三,无论你在哪里学习,**确保你是通过做**来学习的。在 [Dataquest](https://www.dataquest.io) ,我们确保所有学生通过我们的交互式浏览器内编码环境应用我们教授的所有内容,因此您可以在学习的同时不断应用,并获得关于您的代码是否正常工作的反馈。但是不管你是选择在我们的网站上学习还是在其他地方学习,确保你经常应用你所学的东西,并且花大量的时间实际编写代码。很容易看一个小时的视频,*会觉得*你学到了一些东西,但你并没有真正学到任何有用的东西,除非你[自己应用你所学的东西](https://www.dataquest.io/blog/video-text-learn-data-science-online/)。

第四,**计划与您的同行和数据科学社区建立联系**。在 Dataquest,我们有一个学生可以加入的在线学习者社区,但您也可以查看 Twitter、Reddit 和其他社交网络上的数据科学社区。你可以建立一些很好的联系,并从社区中的其他人那里学到很多东西,数据科学领域的大多数人都非常开放和慷慨地分享他们的时间和专业知识。

准备好开始了吗?一个新的和令人兴奋的职业等待着你!您现在就可以在我们的免费入门课程中开始学习编码,我们也会为您提供一些其他优秀的数据科学资源。

# 数据科学到底需要学什么?看情况。

> 原文:<https://www.dataquest.io/blog/what-need-learn-data-science/>

February 6, 2020![data-science-paths-to-success](https://github.com/OpenDocCN/geekdoc-ds-zh/raw/master/dataquest-blog/img/97c6a61b69cecbd5032c67033e33feda.png)

如果你正在考虑数据科学领域的新职业,选项可能会同时感到有限和难以承受。

一方面,似乎整个行业可以归结为“数据分析师”、“数据工程师”和“数据科学家”,而你必须适应这三个角色中的一个。

另一方面,这可能会让人不知所措,因为你被告知要学的东西太多了。例如,请看这张关于学习数据科学的流行信息图:

![data-science-infographic](https://github.com/OpenDocCN/geekdoc-ds-zh/raw/master/dataquest-blog/img/8bc89e67d4baa58fca6aa7b41881a686.png "data-science-infographic")

这看起来很酷……但是也非常吓人和令人困惑!

酷炫的设计?绝对的。但这种形象可能会让有抱负的数据科学家心脏病发作,他们认为自己实际上必须知道所有这些才能在行业中找到工作。

这就是为什么 Insight 数据科学项目总监 Alise Otilia R .最*在 Twitter 上发了一条关于数据科学领域各种专业和角色的帖子,感觉像是一股新鲜空气。因此,我们采访了 Alise,以了解她对数据科学领域机会的更多看法。

## 你一定要什么都是专家吗?

“我的灵感(对于该主题)肯定是因为一些过渡到数据科学领域的人可能会被成为数据科学家必须学习的所有东西所淹没,”Alise 说,她在转到 Insight Data Science 之前是 Spirit Airlines 的数据科学家。

![Alise Otília Ramírez](https://github.com/OpenDocCN/geekdoc-ds-zh/raw/master/dataquest-blog/img/827f0eb50313194da39c39dca4dbcdaf.png "Alise Otília Ramírez")

图书馆与资讯学教育协会

“肯定有很多事情,”她说。这是一个非常跨学科的领域,所以计算机科学、编程、统计学、实验、商业、SQL——有太多的东西你必须在某种程度上了解。"

“但根据我作为数据科学家的面试经验,每一次面试都是完全不同的。有些是非常商业的,SQL 繁重的。其他的只是机器学习的案例研究。其他人则在现场,严格执行 CS 算法。”

“我开始意识到**这就是挫折的来源**,”她说。

“你觉得你必须成为所有这些不同领域的专家,老实说,这是不可能的。”

爱丽丝说,问题的一部分在于,公司对“数据科学家”到底是什么没有达成一致。对于那些刚刚进入这个领域的人来说,这些差异真的是势不可挡的,他们觉得自己必须在每份招聘广告上的每项技能上打勾,才能胜任其中任何一项。

## 向下钻,找到你的激情

在她的 Twitter 帖子中,Alise 列举了七个更具体的角色,这些角色都可以归入“数据科学家”的大类:

*   产品分析数据科学家
*   多面手
*   专科(NLP,计算机视觉等。)
*   机器学习工程师
*   数据操作工程师
*   数据科学产品经理
*   数据可视化器

爱丽丝说:“对我来说,**你真的必须找到你在**中的位置,每一个都有优势。“他们都有基本的核心技能,但我认为在确定最适合你的数据科学职业时,你必须倾向于你的优势。这有助于减轻你不得不学习所有东西的痛苦。”

例如,Alise 说,“我认为自己更像是一个基于产品分析的数据科学家。我只是喜欢商业方面。我喜欢能够影响企业的发展方向,与团队中的非技术人员交流。SQL 绝对是我的强项之一。实验对我来说一直很有趣,只是为了看看一个小小的改变如何影响用户的行为。”

她说,找到自己的激情很重要。这可以帮助你深入了解并专注于最适合你想要的特定角色的技能。她说,SQL 和沟通技能对于专注于产品分析的数据科学家来说至关重要,但对于机器学习工程师来说可能不那么重要。

“如果你能坐下来连续写八个小时的代码,那对你来说是很棒的一天,我会说类似这样的话(机器学习工程师)更适合你。”如果这是你的目标,那么你应该更加关注编程基础和软件工程实践。

同样的原则也适用于 Alise 列出的其他角色。例如,如果您热衷于数据可视化,您可能不需要掌握每种 ML 算法的复杂性,但是您需要熟悉您选择的语言中所有流行的可视化库,并且还需要一些设计和交流技能。

爱丽丝说,如果你的热情是维护生产机器学习模型的稳定性,那么你可能很适合 DataOps。为此,您不需要设计技能或深入的可视化库专业知识,但您可能希望学习 Docker 和 Apache Airflow 之类的工具。

换句话说:**虽然在数据科学中有很多东西要学,但你真的不需要为了成功而学习*所有的***。你不必被那张地铁图吓到。你只需要学习基础知识,然后学习与你实际想做的数据工作相关的核心技能。

如果你的激情符合多个类别呢?不要担心!“肯定有混血儿,”她说,“所以不要觉得你已经完全适应了这些(工作角色)中的任何一个。毫无疑问,角色可以是这些桶的任意组合。”

![data-science-find-your-passion](https://github.com/OpenDocCN/geekdoc-ds-zh/raw/master/dataquest-blog/img/8bf0521009a9d703d13068f88bcc9413.png "Passion-led-us-here")

找到你的激情将帮助你走向职业道路。

## 在数据科学求职中保持战略眼光

“绝对不要被现有的所有证书和课程弄得不知所措,”爱丽丝说。取而代之的是,“问自己一系列问题,比如你最初为什么对数据科学感兴趣?这个领域真正吸引你的是什么?”

“我认为首先抓住你对数据科学感兴趣的东西非常重要,”她说。"**一定要关注基本面**;你不必事事都是专家。知道自己的优势是什么。”

然后,她说,到了找工作的时候,要仔细阅读,要有策略。“不要只申请所有写着‘数据科学家’的职位,因为大多数角色不会明确地说这是机器学习或者这是纯粹的产品分析。”

“如果你知道你对哪种(数据角色)最感兴趣或更有激情,我真的会分析这份工作描述,”她说。即使职位名称是“数据科学家”,你也经常可以从职位描述的所需技能和其他元素中得到暗示,即他们是在寻找机器学习专家,数据 viz 向导,产品分析大师,还是在上述所有方面都有一点经验的多面手。

爱丽丝说:“找出那些被强调的关键词和短语。“它说你将有效地沟通吗?它是否说您将创建报告或指标?这类事情会帮助你确定这个角色真正适合哪个角色。”

爱丽丝说,如果你参加了面试,你可以通过问一些关于该职位日常职责的问题来深入了解这一点。“问问你会花多少时间编程,会花多少时间开会,会花多少时间做报告,会花多少时间做特别分析,会花多少时间学习机器。我认为这是一种很好的方式,可以了解你在这个职位上的时间会如何分配。”

"她说,如果你被答案所吸引,那么这份工作可能非常适合你. "但如果他们说你 80%的时间都在编程,但你真的不认为编程是你的主要优势之一,或者你真的对它不感兴趣,也许那就不是你的数据科学角色。"

“真正了解自己的定位是最重要的,然后你就可以更好地为这些角色和面试做准备。”

## 不确定从哪里开始?从小处着手

爱丽丝说,另一个需要考虑的事情是公司的规模。如果你不确定自己喜欢什么,从一家小公司开始可能会有好处。

“我认为公司的规模在很大程度上也是你将要做什么的一个指标。甚至对初创公司和中型公司来说,我想说的是,大多数情况下,你都是所有这些角色的混合体。”

![startup-vs-team-datascience](https://github.com/OpenDocCN/geekdoc-ds-zh/raw/master/dataquest-blog/img/8ed53d442f8fd3dc0a0d75cfdabf79b2.png "startup-vs-team-datascience")

你是在创业公司工作好,还是加入一个比较老牌的数据团队好?

“我知道我在 Spirit 的时候,只有两个数据科学家,包括我自己,”她说,“所以我们基本上是创建管道的数据工程师。我们是业务分析师和产品分析数据科学家,以及机器学习工程师。我们几乎是无所不包。”

她说,这种角色“肯定会导致精疲力竭,这在数据科学中是一件大事,而且肯定会很快发生”。“但如果你不知道自己到底适合哪里,也许这样的角色会很有趣。**你可以快速了解自己喜欢什么**和不喜欢什么。”

例如,她说,“我很快就知道机器学习工程师不适合我,但即使是数据工程和产品分析,我也喜欢这一部分。”

另一方面,如果你*确实*知道你想做什么,拥有更大数据团队的更大公司可能是最安全的赌注。“在拥有数百名数据科学家团队的大公司里,”她说,“你很可能只参与一个项目或一个模型的一小部分,所以你的总体角色会更具体一些。”

她说,你在找工作时也应该考虑行业。像科技这样已经充满数据科学家的行业可能是寻找特定角色的好地方,因为公司更有可能确切知道他们需要什么以及他们希望如何获得它。你还可能在拥有成熟数据科学团队的公司找到更好的指导机会。

另一方面,刚刚开始涉足数据科学的行业可能会提供更多的开放式角色,有更多的实验自由。“我有点喜欢进入一个没有太多数据科学家的领域或行业,”Alise 谈到她在航空业的起步时说,“只是因为我可以着陆并尝试很多东西,只是看看我在哪里更适合一点,而不是被限制在某个项目或某个应用程序或某项责任中。

"我喜欢独立自主,喜欢摆弄东西,喜欢自学."

![](https://github.com/OpenDocCN/geekdoc-ds-zh/raw/master/dataquest-blog/img/88043cf6cbae56218661846e027f57a7.png "lifelong-learning-data-science")

数据科学有很多东西要学!你不必什么都学,但你必须保持开放的心态,定期学习新事物和提高技能。

## 终身学习的重要性

Alise 说,有抱负的数据科学家需要知道的另一件重要事情是,学习数据技能并没有真正的终点。

她说:“在数据科学领域,你永远也不会站在一切事物的顶端。“这有点像电子邮件,就像当你试图减少到零,然后五分钟后,你刷新,又有 10 个。这就是我对数据科学的看法。这是一个你必须不断学习、阅读的领域。”

Alise 说:“我已经在这个领域工作了几年,每天都在学习新的东西。

然而,她补充道,“不要因为关注所有新出现的工具和技术而不知所措。专注于基本原则,因为这些原则永远不会改变,而这最终将是你在面试中受到的考验。”

想从爱丽丝那里得到更多的智慧吗?你可以在推特和 T2【LinkedIn】上找到她。她也乐于成为人们的导师,尤其是对数据科学感兴趣的女性。你可以在这里和她预约一个免费的约会,聊聊那个[。](https://calendly.com/aliseramirez)

想开始学习 Alise 所说的那些基础知识吗?

# 为数据可视化选择颜色时需要考虑什么

> 原文:<https://www.dataquest.io/blog/what-to-consider-when-choosing-colors-for-data-visualization/>

August 22, 2018*This post was written by [Lisa Charlotte Rost](https://twitter.com/lisacrost). Lisa is a designer at [Datawrapper](https://www.datawrapper.de/?utm_source=dataquest&utm_medium=crosspost). Based in Berlin, she organizes the Data Vis meetup and enjoys the few sunny days there. This article was [originally posted on Datawrapper](https://blog.datawrapper.de/colors/?utm_source=dataquest&utm_medium=crosspost), and has been reposted with perlesson.* Data Visualisation can be defined as representing numbers with shapes – and no matter what these shapes look like (areas, lines, dots), they need to have a color. Sometimes colors just make the shapes visible, sometimes they encode data or categories themselves. We’ll focus mostly on the latter in this article. But we’ll also take a general look at colors and what to consider when choosing them:

## 何时在数据可视化中使用颜色

在对你最重要的价值观进行编码时,考虑一下是否有比渐变颜色更好的选择。渐变颜色可以很好地显示图案,例如在 choropleth 地图上,但很难从中解读实际值,也很难看出值之间的差异。考虑用条形、位置(像点状图)甚至面积来显示你最重要的价值,并且只用颜色来显示类别。读者将能够更快地解读你的值:![full-180529_considercolor7](https://github.com/OpenDocCN/geekdoc-ds-zh/raw/master/dataquest-blog/img/c2cbc5a2f421e645acacbdf7bbf564a1.png) **如果你需要一个图表中有七种以上的颜色,考虑使用另一种图表类型或将类别组合在一起。**颜色让读者很容易区分数据中的类别,但尽量避免使用超过七种颜色。图表中代表数据的颜色越多,就越难快速阅读。您的读者需要经常查阅颜色键来理解图表中显示的内容。考虑使用另一种图表类型:![full-180529_considercolor6](https://github.com/OpenDocCN/geekdoc-ds-zh/raw/master/dataquest-blog/img/f872d4b0ac17a7d8884165eb995f83db.png)

## 如何做出更好的颜色选择

**Consider using the same color for the same variables.** If you just use one color for all your charts, using the same color is the best option to make your article not overly colorful. For example, it’s ok to show the unemployment rate in blue in the first chart and a few paragraphs later a GDP chart with the same color. However, if you use more than one color for your first chart, the colors in this chart will be “taken”. To not confuse readers and increase comparability, consider only using these colors again if you’re showing data about the same category/country/etc.: ![full-180529_considercolor1](https://github.com/OpenDocCN/geekdoc-ds-zh/raw/master/dataquest-blog/img/ce4c83542414d3bde4703d0c563e80cf.png) **Make sure to explain to readers what your colors encode.** Every visual mark that represents a value or variable should be explained: What does the height of your bar mean? What does the size of your circles on a symbol map represent? The same is true for colors. There are many ways to create a color key. Here are three of them: ![full-180529_considercolor2](https://github.com/OpenDocCN/geekdoc-ds-zh/raw/master/dataquest-blog/img/ade2ff6dc4911222263a496353de5919.png) **Consider the color grey as the most important color in Data Vis.** Using grey for less important elements in your chart makes your highlight colors (which should be reserved for your most important data points) stick out even more. Grey is also helpful for general context data, less important annotations, to show what’s unselected by the user, or to calm down the overall visual impression of your charts. Since grey can seem a bit cold, consider using it with a hint of color: Try a warm grey (grey+yellow/orange/red), or use another very light color as an alternative (e.g. super light yellow): ![full-180529_considercolor11](https://github.com/OpenDocCN/geekdoc-ds-zh/raw/master/dataquest-blog/img/7f017298bb2b38a2658fda220a8e186a.png) **Make sure your contrasts are high enough.** Caring about contrast means caring if your readers will be able to read your chart on their screen, even in low light and even if you use light colors like grey. This is especially important for text: The smaller the text, the higher its contrast to the background needs to be for it to be readable. The contrast ratio between background and foreground should be at least 2.5 for big text and at least 4 for small text. In addition to having a high contrast ratio, avoid complementary hues (e.g. red and green, orange and blue) and bright colors for backgrounds. Use this tool to test your color contrast, the brightness difference and if colors are “compliant”. ![full-180529_considercolor15](https://github.com/OpenDocCN/geekdoc-ds-zh/raw/master/dataquest-blog/img/3681e6a1d22d9711e1dd6aa27ab81326.png) **Consider where your colors appear in relation to each other.** The smaller the areas on your chart and the bigger the distance between them, the harder it is to compare them. Consider giving small points or lines a high contrast in their hue or brightness, to make them easily distinguishable. However, big areas can handle toned-down colors with little contrast; especially if there is no other (background)color between these areas: ![full-180529_considercolor5](https://github.com/OpenDocCN/geekdoc-ds-zh/raw/master/dataquest-blog/img/fd59a2f2903be4070ed472aaf33163a4.png) **Use intuitive colors.** When choosing a color palette, consider their meaning in the culture of your target audience. If possible, use colors that readers will associate with your data anyway, e.g. party colors: Republican = red, Democrats = blue; natural colors: forest = green, lake = blue; or learned colors: red = attention/stop/bad, green = good (to go). When it comes to color-encoding gender data, consider avoiding the stereotypical pink-blue combination. To not confuse your readers entirely, try a cold color for men (e.g. blue or purple) and a warmer color for women (e.g. yellow, orange or a warm green): ![full-180529_considercolor9](https://github.com/OpenDocCN/geekdoc-ds-zh/raw/master/dataquest-blog/img/cf02c5828a752babc4000e2d7b3d8f44.png) **Use light colors for low values and dark colors for high values.** When using color gradients, make sure that the bright colors represent low values, while the dark colors represent high values. This will be most intuitive for most readers: ![full-180529_considercolor13](https://github.com/OpenDocCN/geekdoc-ds-zh/raw/master/dataquest-blog/img/de40bc2272cd8c0ca811121893dbd1b8.png) **Don’t use a gradient color palette for categories and the other way round.** It might be tempting to use shades of one hue (e.g. blue) even for categories, to make your chart look less colorful. However, since many readers will associate dark colors with “more/high” and bright colors with “less/low”, such a color palette will imply a ranking of your categories. Use different hues (green, yellow, pink, etc.) for your categories to avoid that, and to be able to talk about these colors. Readers might be quicker at finding specific categories if you make their colors stand out with a different lightness or saturation, but note that your chart should explain why these colors stand out. If you find your chart too colorful, consider another chart type for your data. ![full-180529_considercolor12](https://github.com/OpenDocCN/geekdoc-ds-zh/raw/master/dataquest-blog/img/7ff5de0a214bbc7b59f2f39c48d11b43.png) **Use lightness to build gradients, not just hue.** When designing color gradients, lots of consideration is needed. If you’re unsure, use the Datawrapper defaults, the [ColorBrewer](https://colorbrewer2.org/#type=sequential&scheme=BuGn&n=3) palettes or these [Carto gradients](https://carto.com/carto-colors/). If you do want to try your hand at it: Don’t place more than two hues with the same lightness in your gradient, but design it from a bright color (e.g. white) to a dark color (e.g. dark blue) in a consistent way. Your gradient should work in black and white, too. Gradients with much variation in lightness (like rainbow scales) can confuse readers: ![full-180529_considercolor16](https://github.com/OpenDocCN/geekdoc-ds-zh/raw/master/dataquest-blog/img/7ddd99326c6a293e24c6a8fd1bd09a24.png) **Consider using two hues for a gradient, not just one.** This is the next step to make your map or chart even more decipherable. Readers will be able to distinguish the colors on the gradient better if they are encoded through lightness and (two or three carefully selected) hue: ![full-180529_considercolor17](https://github.com/OpenDocCN/geekdoc-ds-zh/raw/master/dataquest-blog/img/4427a1d90d0faeb507cb73140f827f1c.png) **Consider using diverging color gradients.** If you want to emphasize how a variable diverts from a baseline (say the national average), you may want to consider using a diverging palette. It’s important to use clearly distinguishable hues for both sides of the gradient. The center color should ideally be a light grey, not white: ![full-180529_considercolor18](https://github.com/OpenDocCN/geekdoc-ds-zh/raw/master/dataquest-blog/img/355791ec39f720387bf89337fbab3beb.png) **Consider color-blind people.** Using different lightnesses in your gradients and color palettes has the big advantage that readers with a color vision deficiency will still be able to distinguish your colors. There are [many](https://en.wikipedia.org/wiki/Color_blindness#Types) different types of color blindness: Use an [online tool](https://www.color-blindness.com/coblis-color-blindness-simulator/) or [Datawrapper’s automatic colorblind-check](https://blog.datawrapper.de/colorblind-check/) to make sure that color-blind users can distinguish the colors on your chart: ![full-180529_considercolor14](https://github.com/OpenDocCN/geekdoc-ds-zh/raw/master/dataquest-blog/img/00f24f8868d10754f36a715ccb132302.png)

## 想了解更多?

*   调色板的力量:为什么颜色是数据可视化的关键,以及如何使用它。*简明而全面地介绍了连续、发散和分类调色板,并附有简洁的插图。*
*   罗伯特·西蒙的《色彩的微妙之处》。*用六个部分详尽地介绍色彩理论。*
*   Elijah Meeks 的数据可视化颜色的 Viz 调色板。*Elijah 在和 Susie Lu 创建工具 [Viz Palette](http://projects.susielu.com/viz-palette) 时学到的数据可视化中的颜色使用规则,尤其是在文章的第二部分。*
*   安迪·基尔克《让灰色成为你最好的朋友》。*解释了灰色对于数据可视化的重要性。*
*   [彩虹色地图如何误导](https://eagereyes.org/basics/rainbow-color-map)罗伯特·科萨拉。*解释了流行的彩虹色标的缺点。*
*   [数据故事:卡伦·施洛斯的色彩](https://datastori.es/119-color-with-karen-schloss/)。*最*的播客是关于数据可视化中颜色使用的一般规则,特别是 Karen 对这个主题的研究。*

# Dataquest v1.63 的新特性:热键、自定义提示等等

> 原文:<https://www.dataquest.io/blog/whats-new-in-dataquest-v163/>

March 6, 2018

在 Dataquest,我们希望帮助您实现目标。当你的环境和你一起工作时,学习会更容易。我们很高兴地宣布一些新功能,将加快您的学习。

## 课程参考标签

借助我们新的课程参考标签,您可以轻松搜索我们的所有内容。你可以在课堂页面的顶部找到它,在问答和管理之间。

![Screen-Shot-2018-02-27-at-3.41.52-PM](https://github.com/OpenDocCN/geekdoc-ds-zh/raw/master/dataquest-blog/img/ac19c33a75b26791346dc876fe683c4b.png)

假设您正在完成我们的[电影评论指导项目](https://www.dataquest.io/course/statistics-fundamentals/),并想要查看如何使用 Jupyter 笔记本。只需点击“任务参考”并输入“jupyter”即可获得相关结果。

![Screen-Shot-2018-02-27-at-3.54.39-PM](https://github.com/OpenDocCN/geekdoc-ds-zh/raw/master/dataquest-blog/img/35683ac4bd0f39c325361e7888c90665.png)

或者,您可能想在不离开当前课程的情况下,看看我们是否有关于某个主题的内容。

例如,你在我们关于预测汽车价格的[机器学习指导项目](https://app.dataquest.io/m/155)中,你想知道我们在神经网络方面有什么。

![Screen-Shot-2018-02-27-at-3.40.14-PM](https://github.com/OpenDocCN/geekdoc-ds-zh/raw/master/dataquest-blog/img/e824ed688c627cd5d1e73900b882aa0b.png)

你试过参考标签吗?让我们知道你的想法!

## 自动完成

我们的课程现在有了自动完成功能——开始键入你需要的单词,它就会为你弹出来。这适用于一般术语:

![Screen-Shot-2018-02-27-at-4.08.18-PM](https://github.com/OpenDocCN/geekdoc-ds-zh/raw/master/dataquest-blog/img/a66b713ff553a2f3d5d44c45dc98210b.png)

对于特定于课程的单词:

![Screen-Shot-2018-02-27-at-4.14.27-PM](https://github.com/OpenDocCN/geekdoc-ds-zh/raw/master/dataquest-blog/img/6816a6ee676a212147b66b6109801a14.png)

## 自定义提示

故障排除(思维过程和最终结果)是数据科学的重要组成部分。然而,我们都知道可怕的错误通知是多么令人沮丧和害怕。

到目前为止,您的课程中出现的错误是这样的:

![before_medium](https://github.com/OpenDocCN/geekdoc-ds-zh/raw/master/dataquest-blog/img/cdbc70647918481a1e8df8cd8768324e.png)

这个错误消息并没有给你太多的工作。我们新的自定义提示通过提供更具体的反馈以及如何解决问题的建议,帮助您培养故障排除技能:

![after_medium](https://github.com/OpenDocCN/geekdoc-ds-zh/raw/master/dataquest-blog/img/b23fc4b13e077446650f59786b621d20.png)

## 热键

当你不必使用鼠标或触控板时,生活会更美好。我们已经更新了您在课程中使用的键盘快捷键列表:

*   `alt` + `enter`:运行代码
*   `alt` + `t`:切换焦点到编辑器
*   `alt` + `r`:重置课程
*   `alt` + `n`:下一步
*   `alt` + `b`:后退一步

注意,如果你在 Mac 上,你应该用 Command 代替 Alt。

其他新功能包括:

*   当你点击学习文本中的链接时,它们会在新的标签中打开。不要再因为脱离了课程而失去注意力,需要找到回来的路。
*   我们已经从课程末尾删除了调查问题,以便更顺利地过渡到下一课。

## 下一步我们应该建造什么?

来自用户的反馈对我们来说非常重要——我们很想知道您对我们下一步应该做什么的看法。如果你有想法,你可以[发电子邮件给我们](/cdn-cgi/l/email-protection#08606d646467486c697c69797d6d7b7c266167)或者在[推特](https://www.twitter.com/dataquestio)上联系我们!

# Dataquest v1.52 的新特性:R、SQL、Postgres 和一个更好的仪表板

> 原文:<https://www.dataquest.io/blog/whats-new-in-dataquest/>

January 23, 2018![We-ve-been-building_white](https://github.com/OpenDocCN/geekdoc-ds-zh/raw/master/dataquest-blog/img/1b3c299fe5165834495e67b30b6b5c33.png) At Dataquest, we are always working to improve your experience and expand our learning paths. We’re excited to announce several new courses and a few new features we think you’ll love.

## 新课程

*   我们新的 [R 基础课程](https://www.dataquest.io/course/introduction-to-data-analysis-in-r/)教你基本的语法和表达。您将使用数据结构构建自己的分数计算器,做出数据驱动的大学决策,并分析大学毕业数据。
*   在我们的 [SQL 基础课程](https://www.dataquest.io/path/sql-skills/)中,通过学习从单个表中选择、过滤和转换数据,从 Pandas 数据框架过渡到 SQL 表。您将熟悉 SQLite 文档,并使用 SQLite 和 Python 来分析 CIA Factbook 数据。
*   使用我们的 [SQL 中级课程](https://www.dataquest.io/course/sql-joins-relations/)来学习如何使用多表数据库。我们将带您从 SQL 中的连接数据,到表关系和规范化。我们的指导项目向您展示如何使用 SQL 和 pandas 回答业务问题,并设计您自己的数据库。
*   在[构建数据管道](https://www.dataquest.io/course/building-a-data-pipeline/)中,从头开始用 Python 构建管道。从 Python 函数式编程基础开始,然后转向更高级的概念,如闭包和装饰器。您将在指导项目中使用这些新技能来总结黑客新闻数据。
*   [优化 Postgres 数据库](https://www.dataquest.io/course/optimizing-postgres-databases-data-engineering)从探索 Postgres 内部开始。您将在此基础上学习调试、索引和数据库清空。
*   想参加一场 [Kaggle](https://www.kaggle.com/) 比赛?让我们的 [Kaggle 基础课程](https://www.dataquest.io/course/kaggle-fundamentals)帮助你。我们从基础开始,看看如何进行比赛和探索数据。在课程结束时,你将在 Kaggle 的泰坦尼克号比赛中创建和使用机器学习工作流。

## 新功能

我们的新议程功能可帮助您实现学习目标。首先,选择你的道路。

然后告诉我们您希望每周花多少时间学习 Dataquest。![Agenda_Pace_resized](https://github.com/OpenDocCN/geekdoc-ds-zh/raw/master/dataquest-blog/img/dcbf60b849edf08892a8842f8224d7a0.png)然后,我们会计算你能完成多少,并向你展示在这段时间内你需要在道路上取得进步的下一课。我们会根据您完成课程的速度以及您还剩下多少时间来实现您的周目标,重新计算您一周的时间表。![weeklygoals](https://github.com/OpenDocCN/geekdoc-ds-zh/raw/master/dataquest-blog/img/316e97f33b2beeb795f95cf5933808a4.png)其他新功能包括:

*   改进的仪表板导航
*   停止和重启正在运行的代码的能力
*   如果您没有登录,课程的可见性更高—访问者现在可以更好地了解 Dataquest 所提供的内容。

## 下一步我们应该建造什么?

来自用户的反馈对我们来说非常重要——我们很想知道您对我们下一步应该做什么的看法。如果你有一个想法,你可以

[给我们发电子邮件](/cdn-cgi/l/email-protection#fe969b929291be9a9f8a9f8f8b9b8d8ad09791)或在[推特上联系](https://www.twitter.com/dataquestio)!

# Dataquest v1.85 的新特性:外卖、中级 R 等

> 原文:<https://www.dataquest.io/blog/whats-new-v-185/>

May 25, 2018We’re always working on ways to help you learn and keep you motivated — from new features, to celebrating student stories. In this post, we give you a quick overview of what we’ve released recently, and what’s coming up.

## 外卖食品

编程由许多小的动作和概念组成,很难记住它们。不可避免地,你会疑惑,*“等等,`==`又是做什么的?”*(如果两个值相等,则返回`True`。)当然,有谷歌和文档,但你必须在一页页的信息中搜寻——这会分散你的注意力并占用时间。输入我们的外卖。当你学完我们的 [Python 编程:初级](https://www.dataquest.io/course/python-for-data-science-fundamentals/)和 [Python 编程:中级](https://www.dataquest.io/course/python-for-data-science-intermediate/)课程后,你会发现一些可下载的文档,其中包含一些有用的信息,例如:

*   如何打开文件
*   如何迭代一个列表
*   如何确定列表中是否存在某个值
*   If 语句语法

![list_operations_takeaways](https://github.com/OpenDocCN/geekdoc-ds-zh/raw/master/dataquest-blog/img/883ca995c7bc62f7eaf86b5c72df5d58.png)


An example from our List Operations lesson Takeaways doc


你可以把这些外卖放在身边,遇到卡顿的时候快速回头参考。虽然到目前为止它们只出现在 Python 初级和中级课程中,但是我们正在努力将它们添加到我们目录的其余部分。如果您还没有,也可以查看我们的备忘单:

*   [Python 数据科学基础知识](https://www.dataquest.io/blog/python-cheat-sheet/)
*   [Python 数据科学中级](https://www.dataquest.io/blog/data-science-python-cheat-sheet/)
*   [NumPy](https://www.dataquest.io/blog/numpy-cheat-sheet/)
*   [Python 正则表达式](https://www.dataquest.io/blog/regex-cheatsheet/)
*   熊猫

## r 编程:中级

我们将继续努力建设我们的 R 课程,希望你喜欢最新的课程: [R 编程:中级](https://www.dataquest.io/course/control-flow-iteration-and-functions-in-r/)。本课程涵盖了控制结构、功能、字符串和日期。

![if_statments_r](https://github.com/OpenDocCN/geekdoc-ds-zh/raw/master/dataquest-blog/img/bba800b6c5b19de1a4f280374c30cfb0.png)


If-statements in R


You’ll finish the course with a guided project that explores RStudio, from installing, to writing scripts and notebooks.

## 用户故事

我们在我们的[用户成功页面](https://www.dataquest.io/blog/topics/student-stories/)中添加了一些新故事:

*   [Eric](https://www.dataquest.io/blog/eric-salesdeandrade) 想要建立更强、更深入的数据技能。了解他如何从数据挖掘转向数据科学。
*   [Mike](https://www.dataquest.io/blog/mike-roberts) 没想到会爱上数据。他的故事告诉了每一个发现自己对前景感到厌倦的人。
*   克里斯蒂安的故事表明,找到一个你关心的项目是多么重要。他把今天的工作归功于 Dataquest。

您是否从事自己热爱的工作,并且觉得 Dataquest 对您有所帮助?

[让我们知道](/cdn-cgi/l/email-protection#cfa7aaa3a3a08fabaebbaebebaaabcbbe1a6a0)!

## 新团队成员

在 Dataquest,随着公司的发展,我们努力寻找合适的人才。我们寻找热爱教育的人,他们将在我们完全分散的团队中茁壮成长。我们很高兴向您介绍我们的新员工:

*   前端工程师 Shridhar Gupta:Shridhar 对伟大的设计和工程充满热情。他喜欢去新的地方旅行,喜欢在户外度过时光,喜欢摄影。
*   罗斯·马丁,数据科学家:在 Dataquest 之前,罗斯是一名生态系统生态学家。她热衷于帮助他人发展他们的数据科学技能并做出自己的发现。
*   内容运营兰德尔·霍尔(Randall Hall):兰德尔自称是数学和数据极客。
*   Ash Kestrel,后端工程师:Ash 有动画背景,喜欢用 Python 工作。
*   卡尔·泰勒,营销总监:卡尔喜欢旧书、俗气的俏皮话和美味的三明治。

## 其他更新

*   我们最*从 AWS 迁移到了 Google Cloud。非常感谢我们所有的学生对所涉及的停机时间的耐心。我们做出这一改变是为了提高网站速度和服务的可靠性,并使日常维护更容易。
*   我们几个人在五月中旬参加了 PyCon US。Python 社区非常热情,我们很高兴见到这么多对数据和编程充满热情的人。

## 下一步我们应该建造什么?

来自用户的反馈对我们来说非常重要——我们很想知道您对我们下一步应该做什么的看法。如果你有想法,你可以[发电子邮件给我们](/cdn-cgi/l/email-protection#5c34393030331c383d283d2d29392f28723533)或者在[推特](https://www.twitter.com/dataquestio)上联系我们!

# 1.10 的新功能:回答差异,改进的问答!

> 原文:<https://www.dataquest.io/blog/whats-new-v1-10-diffs-qa/>

November 23, 2016Along with our two new data visualization courses ([Exploratory Data Visualization](https://www.dataquest.io/course/exploratory-data-visualization/) and [Storytelling Through Data Visualization](https://www.dataquest.io/course/storytelling-data-visualization/)) our latest release includes two major features designed to make your life easier — enhanced Q&A and answer diffing.

## 简介:输出和变量差分

当你学习编码时,困在一个练习中而不知道哪里出了问题是令人沮丧的。我们的新变量和答案差异是在这里帮助。这项新功能:

*   向您展示我们正在检查哪些变量
*   告诉你哪些变量是正确的,哪些是错误的
*   向您展示您的变量和我们的解决方案之间的差异,为您提供如何修复代码的线索。

### 区别是什么?它是如何工作的?

在软件开发中

[diff](https://en.wikipedia.org/wiki/Diff_utility) 是一种比较两个文件的方法,可以看出它们有什么不同。如果你熟悉 Github,你会经常看到差异:![](https://github.com/OpenDocCN/geekdoc-ds-zh/raw/master/dataquest-blog/img/da6e39fa4c6de2354536f2679303a90c.png)Github 上的差异..上面截图中修改了两行。Github 显示删除了两行,添加了两行,并显示了更改的内容。Dataquest 中的差异以类似的方式工作。在你的代码下面,你会看到用红色或绿色标记的变量来表示它们是否正确。当你打开变量检查器时,你会得到一个更详细的外观:![](https://github.com/OpenDocCN/geekdoc-ds-zh/raw/master/dataquest-blog/img/372da548e06571261c2e11b925da7798.png)在 Dataquest 上区分一个列表。上面我们看到一个列表,用蓝色、红色和绿色显示项目。蓝色项目是正确的,绿色项目是缺失的,红色是需要移除的额外项目。这是我们 diffing 工具的第一个版本,我们计划随着时间的推移进行更多的改进。我们希望听到您的反馈!

## 我们社区问答的改进

我们的社区问答可以让你在不离开课堂的情况下获得其他学生的帮助。我们很高兴推出一些非常受欢迎的功能增强:

*   如果你犯了一个错误,编辑或删除你的问答。
*   一个集中的论坛页面,这样你可以一起查看所有的 Q & A。
*   问答中链接的自动检测。
*   [Markdown](https://commonmark.org/help/) 支持,所以可以问答。

## 1.10 版本中的新功能

今天版本中的完整功能列表如下:

*   新[探索性数据可视化](https://www.dataquest.io/course/exploratory-data-visualization/)课程。
*   全新[通过数据可视化讲述故事](https://www.dataquest.io/course/storytelling-data-visualization/)课程。
*   一个返工的[处理丢失数据](https://www.dataquest.io/course/pandas-fundamentals/)的教训。
*   问答中的降价支持
*   问答内容可以编辑和删除。
*   问答中的链接现在会被自动检测到。
*   对课程中错误的杂项修复。
*   杂项稳定性错误修复。

[立即体验新功能!](https://app.dataquest.io/dashboard)

## 即将推出

如果您对未来几周的内容感兴趣:

*   一个通知,让你知道新的课程,回答你的问题和为你的问题投票
*   统计学新课程
*   对代码运行速度和可靠性的更多改进。

# 1.14 版的新功能:数据工程路径和性能改进!

> 原文:<https://www.dataquest.io/blog/whats-new-v1-14-data-engineering-performance-improvements/>

March 2, 2017Our latest Dataquest release has over 20 new features, including many major performance improvements and the launch of our much-anticipated data engineering path.

## 新路径:数据工程

我们的第一道菜

[数据工程路径](https://www.dataquest.io/path/data-engineer/)到了!![](https://github.com/OpenDocCN/geekdoc-ds-zh/raw/master/dataquest-blog/img/d24ff601759f440d78431c36ff1f89be.png)数据工程是一个广阔的领域,包括:

*   使用大数据
*   构建分布式系统
*   创建可靠的管道
*   组合数据源
*   与数据科学团队合作,为他们构建合适的解决方案。

如果您想了解更多关于数据工程的信息,您可以阅读我们的指南:

[什么是数据工程师?](https://www.dataquest.io/blog/what-is-a-data-engineer/)这条新路线的第一门课程是[处理 pandas](https://www.dataquest.io/course/pandas-large-datasets) 中的大型数据集,它包括各种技术,扩展了我们现有的 [pandas 课程](https://www.dataquest.io/course/python-for-data-science-intermediate/),并向您展示如何使用 pandas 处理更大的数据集。

### 课程大纲

本课程包含五课,包括两个指导项目:

*   [优化数据帧内存占用](https://app.dataquest.io/m/163) **空闲**
*   [分块处理数据帧](https://app.dataquest.io/m/164)
*   [指导性项目:练习优化数据帧和分块处理](https://app.dataquest.io/m/165)
*   [用 SQLite 扩充熊猫](https://app.dataquest.io/m/166)
*   [引导项目:分析来自 Crunchbase 的创业融资交易](https://app.dataquest.io/m/167)

像我们所有的路径一样,数据工程路径将是一个持续的迭代展示——我们将在这一年中不断增加更多的经验和课程!

[免费开始我们新的数据工程之路!](https://app.dataquest.io/m/163)

## 性能改进

我们的技术团队一直在努力工作,他们继续让代码运行得更快,并提高我们各种课程类型的稳定性,让您的学习体验更好。

### 减少代码运行时间

基于我们在 9 月份所做的更改,我们已经将所有代码运行转移到基于 websockets,这使得代码运行时间减少了 21%。在过去的六个月中,我们的代码运行时间中位数从 4.2 秒减少到了 1.7 秒,减少了 59%。

![](https://github.com/OpenDocCN/geekdoc-ds-zh/raw/master/dataquest-blog/img/37f4e5e4c44f553fb96dd0ab833c587c.png)过去六个月代码运行次数。

### 控制台和命令行课程的稳定性改进

此外,我们重新设计了命令行课程和控制台的通信,这将提高这些课程的可靠性和稳定性。我们还引入了一个网络和容器状态面板,它将让您了解运行容器和连接的代码的状态。

![](https://github.com/OpenDocCN/geekdoc-ds-zh/raw/master/dataquest-blog/img/2920616f05f3be65916df653e26aa0aa.png)网络和容器状态面板。

## 1.14 版本中的新功能

今天版本中的完整功能列表如下:

*   启动我们新的[数据工程途径](https://www.dataquest.io/path/data-engineer/)的第一门课程。
*   改进了代码运行时间。
*   为运行容器的代码启用了池,以减少由于容器初始化而导致的等待时间。
*   改进了控制台和命令行课程的稳定性。
*   一个新的通知源,当有人回复或支持你的问答帖子时,它会显示给你。
*   将内部通信消息系统移到标题,使界面更整洁。
*   改进了我们支付页面的设计和功能。
*   添加了网络和容器状态面板,以帮助排除故障。
*   在命令行课程中添加了一个重置按钮,可以帮助你在卡住的时候将步骤恢复到正确的状态。
*   当您的付款由于错误的邮政编码而被拒绝时,我们现在会显示更详细的信息。
*   重写了我们的 [Python 基础](https://www.dataquest.io/course/python-for-data-science-fundamentals/)课程,使其更加清晰。
*   使我们的第一门课程——[Python 编程:初学者](https://www.dataquest.io/course/python-for-data-science-fundamentals/)——完全免费,并增加了两个指导项目。
*   添加了一个新的引导项目:[使用下载的数据](https://app.dataquest.io/m/220)。
*   删除了旧的数据可视化课程,该课程在 11 月被两个新课程所取代。
*   修正了不同课程中的其他小错误。
*   修正了一个错误,一些用户不能生成证书。
*   修正了一个用户不能更新电子邮件地址的错误。
*   为通过 facebook 注册的用户添加了一个输入电子邮件地址的提示。
*   修正了防火墙后的某些用户不能运行代码的错误。
*   修复了 Microsoft Edge v14 阻止用户登录和创建帐户的问题。
*   其他小的错误修复和稳定性改进。

## 即将推出

如果您对未来几周的内容感兴趣:

*   数据工程路径中的第二个课程
*   一门新的“机器学习基础”课程
*   能够在每节课中下载源数据集。
*   一个新的课程结束屏幕,让你更容易给我们反馈。
*   一个新的课程界面,等等!

一如既往,我们希望听到您对我们下一步应该构建什么的反馈。如果你有一个好主意,

给我们发电子邮件!

# v1.19 中的新功能:多屏幕、概念、数据集预览等!

> 原文:<https://www.dataquest.io/blog/whats-new-v1-19/>

May 2, 2017

我们的 1.19 版本包括旨在改善您的学习体验的新功能。

你可能注意到的第一件事是一个新的外观。我们做了一些设计调整,包括一个新的教训-文本字体,我们认为你会同意,使一切更容易阅读。v1.19 中的其他重大变化包括:

*   多屏显示,让您在课堂上更高效地工作。
*   概念,旨在帮助您将学习提高到一个新的水*。
*   课堂数据集预览和下载。

你可以在这篇文章的底部找到每一个新特性和错误修复的列表。

[立即体验新功能!](https://app.dataquest.io/login)

## 多银幕放映的

多屏将使你的学习体验更容易。并排设计基于我们指导项目的布局,旨在让您更快、更轻松地学习和探索。

![](https://github.com/OpenDocCN/geekdoc-ds-zh/raw/master/dataquest-blog/img/57b4114d5a07f8f7a5dffffad24c2b7d.png)

我们新的多屏界面

通过将说明和代码框放在一起,multiscreen 使您更容易完成每个步骤。此外,您可以方便地访问控制台,这样您就可以交互式地浏览数据,并尝试不同的方法。

最后,您的代码现在在您键入时就被保存了,所以如果您必须返回到上一步来检查某些东西,当您回来时,您所有的工作都在那里。

## 概念

我们很高兴发布“概念”的第一阶段。概念的想法来自于像您一样的用户的反馈,他们希望:

*   跟踪他们学到了什么技能。
*   查找教授概念的早期课程。
*   对某些话题做笔记。

![](https://github.com/OpenDocCN/geekdoc-ds-zh/raw/master/dataquest-blog/img/2bd19ecddb88cebec73e11a35d2f7fa9.png)

课程边栏中的概念

概念将在每个课程和项目的边栏中以及您的仪表板中提供。Concepts 将向您展示该课程中教授的内容,做笔记,以及查看教授该概念的其他课程和实践该概念的项目和挑战。

您还可以从 Dataquest 仪表盘的侧边栏访问概念,从而轻松查看您的进度,并找到具体的课程来回顾您所学的内容。

我们计划在这一年中扩展概念,最终在每个概念中提供练习——如果您对我们如何添加概念有其他想法,我们很乐意倾听。

## 数据集预览和下载

再次根据像您这样的用户的反馈,我们创建了一个数据集预览,以便您可以轻松检查您正在处理的数据的格式。

![](https://github.com/OpenDocCN/geekdoc-ds-zh/raw/master/dataquest-blog/img/ff912db53c3f704edc3d6cf16e11b8b7.png)

通过单击“数据集”选项卡,您可以预览以表格格式排列的前 5 行数据。

此外,您将能够轻松地在那里下载数据集,因此您可以在自己的计算机上扩展您的工作,使用您在课程中使用的数据的精确副本。

## 1.19 版中的新功能

今天版本中的完整功能列表如下:

*   我们更新了我们的设计,包括使我们的课内字体更容易阅读。
*   我们控制台的第一个版本在非 Python 课程中为您提供了一个 Python 控制台。现在 R 和 SQL 课程有了正确的控制台。
*   我们增加了向您展示进度的粒度。以前进步四舍五入到最接*的 5%,现在四舍五入到最接*的 1%。
*   当您在常规代码框中键入代码时,我们会保存您的代码,使您可以更轻松地在屏幕之间单击,而不会丢失工作。
*   修复了命令行项目在某些情况下缺少跳过提示和解决方案按钮的错误。
*   为我们新的顶级“plus”计划增加了一个试点计划,其中包括指导和项目评审。
*   增加了专属于 plus 用户的新“plus 项目”,以帮助建立投资组合。
*   让我们的定价页面更容易理解。
*   修正了一个错误,控制台提示符不一致加载。
*   增加了一个新的课程结束屏幕,这样你可以给出更详细的反馈,更容易看到你的进度。
*   将课程重置按钮移到进度边栏,以便您可以随时重置课程进度。
*   改进了内部错误报告,因此我们可以更快地识别和修复正在发生的问题。
*   修正了一个阻止某些用户加载 Dataquest 站点的错误。
*   控制台现在可以在所有屏幕类型上立即加载。
*   一个新的[机器学习基础](https://www.dataquest.io/course/machine-learning-fundamentals)课程,作为我们整个 ML 模块重写的一部分。
*   [算法和数据结构](https://www.dataquest.io/course/course-data-structures-fundamentals/),我们新[数据工程师之路](https://www.dataquest.io/path/data-engineer)的第三个课程。
*   跨*台对速度和可靠性的各种改进。
*   杂项课错误修正和错误修复。

[立即体验新功能!](https://app.dataquest.io/login)

## 即将推出

如果您对未来几个月的内容感兴趣:

*   更多的机器学习和数据工程课程。
*   课程目标。
*   能够设定学习目标并制定学习计划。
*   课内自动完成和文档。

一如既往,我们希望听到您对我们下一步应该构建什么的反馈。如果你有一个好主意,[给我们发电子邮件](/cdn-cgi/l/email-protection#ed8588818182ad898c998c9c98889e99c38482)或者简单地在下面评论!

# v1.29 新增功能:新的课程界面、PayPal 等!

> 原文:<https://www.dataquest.io/blog/whats-new-v1-29/>

August 30, 2017Our version 1.29 release is here and includes lots of new features to help enhance your learning experience. Over the past few months we’ve been tirelessly talking to students like you to learn how we can improve the lesson interface. With this release, we are unveiling the results of this hard work. Other big changes in 1.29 include:

*   改进了对错误答案的反馈
*   我们现在接受贝宝,所以国际学生可以更容易地订阅
*   面向数据科学家和数据工程师的四门新课程

你可以在这篇文章的底部找到每一个新特性和错误修复的列表。

[立即体验新功能!](https://app.dataquest.io/login)

## 新课界面

我们新的课程界面更加简化,让您能够专注于学习。

我们已经让你更容易在代码和指令之间滚动,因此导航课程更快。![instruction-pane-1](https://github.com/OpenDocCN/geekdoc-ds-zh/raw/master/dataquest-blog/img/d33c6910fc4fd357b53dd1ee9782dd0b.png)您告诉我们,分屏解决方案很难使用,因此我们移动了解决方案显示,以便更容易地将您的代码与答案进行比较。![solution-1](https://github.com/OpenDocCN/geekdoc-ds-zh/raw/master/dataquest-blog/img/4315606a88640c6f880fe69c798995cb.png)

更容易将左边的解决方案与您的代码进行比较

我们还移动了我们的问答,使其更容易访问,并将课程进度菜单移到了左侧。如果您对此设计有任何反馈,请告诉我们,因为我们店内还有很多!

## 改进了对错误答案的反馈

学习编码最困难的事情之一是理解你哪里出错了。我们改进了在您的代码不正确时给您的反馈。

![hints](https://github.com/OpenDocCN/geekdoc-ds-zh/raw/master/dataquest-blog/img/1d1a863c7c9692b66803d6975fc11129.png)

对错误答案的更好反馈。

改进包括:

*   显示我们在一个步骤中检查的所有变量的反馈
*   显示每个变量的变量状态的链接
*   不正确变量类型的显式提示
*   当你的代码有语法错误时,更好的消息传递

## 我们现在接受贝宝

我们最受欢迎的功能之一是允许 PayPal 作为一种支付方式,让你有更多的选择和我们一起学习。我们很高兴现在支持 PayPal 作为新用户的支付方式,使 Dataquest 更容易为国际学生所用。如果想利用这一点,你可以

[立即使用 PayPal 进行订购](https://www.dataquest.io/subscribe/)。

## 新课程

我们还在数据科学家和数据工程师道路上推出了四门新课程:

*   [机器学习的微积分](https://www.dataquest.io/course/calculus-for-machine-learning/):学习线性回归等中级机器学习技术所必需的微积分。
*   [用于机器学习的线性代数](https://www.dataquest.io/course/linear-algebra-for-machine-learning/):学习线性回归等中级机器学习技术所必需的线性代数。
*   [线性回归](https://www.dataquest.io/course/linear-regression-for-machine-learning):学习如何使用创建线性回归机器学习模型。
*   [递归和树](https://www.dataquest.io/course/recursion-and-tree-structures/):了解递归、树数据结构,以及如何使用它们来提高数据处理速度。

## 版本 1.29 中的新功能

今天版本中的完整功能列表如下:

*   全新的课程界面设计。
*   我们修复了一个错误,该错误导致通知中出现死链,通知中有人回答或投票支持您的问答问题/答案。
*   我们启动了 [Plus](https://www.dataquest.io/subscribe/) 计划,提供项目评审和指导。
*   改进了当你的代码不正确时我们给你的提示。
*   我们推出了我们的[帮助中心](https://support.dataquest.io/en)。
*   我们现在接受贝宝订购付款。
*   我们更新了仪表板设计,包括新的课程图标。
*   新[机器学习的微积分](https://www.dataquest.io/course/calculus-for-machine-learning/)课程。
*   新[机器学习线性代数](https://www.dataquest.io/course/linear-algebra-for-machine-learning/)课程。
*   新的[线性回归](https://www.dataquest.io/course/linear-regression-for-machine-learning)过程。
*   新的[递归和树](https://www.dataquest.io/course/recursion-and-tree-structures/)课程。
*   跨*台对速度和可靠性的各种改进。
*   杂项课错误修正和错误修复。

[立即体验新功能!](https://app.dataquest.io/login)

## 即将推出

如果您对未来几个月的内容感兴趣:

*   仪表板的更多改进,包括定制的学习日程
*   SQL 新课程
*   能够设定学习目标并制定学习计划
*   课内文档

一如既往,我们希望听到您对我们下一步应该构建什么的反馈。如果你有一个好主意,

给我们发电子邮件!

# Dataquest v1.9 的新特性:控制台、热键等等!

> 原文:<https://www.dataquest.io/blog/whats-new-v1-9-console-hotkeys/>

November 4, 2016Whenever you send us feedback or an ideas for a feature, we read and catalogue your suggestions. We then use this to help planning features and improvements for Dataquest. Today we’re excited to launch two of our most-requested features: **Hotkeys** and a **Python Console**.

## Python 控制台简介

你们中的许多人告诉我们,你们希望能够在学习课程的同时探索数据集。为了帮助您做到这一点,我们在课程界面中引入了 Python 控制台。使用控制台,您可以在学习课程的同时轻松浏览和清理数据,帮助您更快地工作并更好地理解数据集的内容:

![](https://github.com/OpenDocCN/geekdoc-ds-zh/raw/master/dataquest-blog/img/ae1ef4ba68531ca7ae241960e61d99c7.png) ![](https://github.com/OpenDocCN/geekdoc-ds-zh/raw/master/dataquest-blog/img/d5f3edfc5270ae798e44402adf0e0a92.png)

您还可以使用它来访问文档字符串,这样您就可以获得语法方面的帮助,而不必搜索文档:

![](https://github.com/OpenDocCN/geekdoc-ds-zh/raw/master/dataquest-blog/img/249292c81e8a79d16562d31a85164a85.png)![](https://github.com/OpenDocCN/geekdoc-ds-zh/raw/master/dataquest-blog/img/dedfcfb4c44e30821dacba0470083e97.png)

我们对这一特性感到兴奋,并计划随着时间的推移不断改进 Python 控制台。请让我们知道你的想法!

## 热键让您工作更快

我们提供了四种新的键盘快捷键,您可以使用它们来加快工作速度:

*   校验码:`alt` + `enter`。
*   将您的代码恢复到初始状态:`alt` + `r`
*   向前跳到下一步:`alt` + `p`
*   在代码框和控制台之间切换:`alt` + `t`

对于 mac 上的上述每个快捷键,请替换

`alt`同`option`。如果您忘记了快捷方式,您可以将鼠标悬停在按钮上来显示工具提示:

![](https://github.com/OpenDocCN/geekdoc-ds-zh/raw/master/dataquest-blog/img/690d9f40d08e856f76c3aae5d32d6591.png)![](https://github.com/OpenDocCN/geekdoc-ds-zh/raw/master/dataquest-blog/img/8bbd502e7e7cc0653744aaf265072b3a.png)

## 1.9 版本中的新功能

今天版本中的完整功能列表如下:

*   热键
*   Python 控制台
*   地块检查的改进
*   Dataquest 个人资料页面的全新设计— [立即查看](https://app.dataquest.io/login?target-url=%2Fprofile)(您需要登录)。
*   修正了一个导致 NumPy 对象答案检查问题的错误
*   各种性能改进和小的错误修复

立即体验新功能!

## 下一步我们应该建造什么?

来自用户的反馈对我们来说非常重要——我们很想知道您对我们下一步应该做什么的看法。如果你有一个想法,你可以

[给我们发电子邮件](/cdn-cgi/l/email-protection#345c5158585b745055405545415147401a5d5b)或[在 Twitter 上联系](https://twitter.com/dataquestio)!

# 为什么 SQL Server 消耗这么多内存

> 原文:<https://www.dataquest.io/blog/why-does-sql-server-consume-so-much-memory/>

December 1, 2021![header](https://github.com/OpenDocCN/geekdoc-ds-zh/raw/master/dataquest-blog/img/ab85da66fbefa79bbadcdf434c3bda53.png)

据该公司称,微软 SQL Server 是一个关系数据库管理系统(RDBMS)。在关系数据库中,所有的 SQL 服务器都使用 SQL 编程语言,这是众所周知的,并被广泛使用。微软创建了 [SQL Server 的 Transact-SQL 实现](https://www.dataquest.io/blog/sql-vs-t-sql/),也称为 t-SQL,由 SQL Server 使用。许多在 [SQL](https://www.dataquest.io/blog/python-pandas-databases/) 中找不到的编程结构也包含在 T-SQL 中。

总的来说,SQL Server 是微软数据*台的重要组成部分,因为它通过内存技术为任务关键型应用程序提供了令人兴奋的性能水*,通过 Excel 等知名工具更快地洞察和透视任何类型的数据,并提供了用于开发内部和基于云的解决方案的*台以及解决方案管理*台。

在撰写本文时,SQL Server 已经存在了 32 年。它是一个成熟的产品,几乎肯定不会满足每个开发团队的所有需求,但它确实涵盖了开发团队对数据访问的绝大多数需求。

我们日常使用的大量服务严重依赖于 SQL Server 数据库。这些服务包括金融机构、博彩公司、政府机构、医疗行业和知名网站。

## SQL server 的功能和优势

高可用性是 [SQL 服务器](https://en.wikipedia.org/wiki/Microsoft_SQL_Server)的标准特性,允许高正常运行时间和更快的切换。所有这些都是在不耗尽系统内存的情况下完成的。由于增加了内存功能,SQL Server 的数据库和分析引擎现在具有更大的灵活性和易用性。可能最显著的特征是它与服务器*台的微软服务器家族无缝集成。

## SQL server 使用了多少内存?

SQL Server 使用了我的服务器上大约 87.5%的 RAM。这导致了许多性能问题,包括速度慢。当我们开始调查这个问题时,我们可以发现许多死胡同。互联网上常见的解决方案是增加 SQL Server 的最大执行时间。在这方面已经做了很多工作,而且它显示。如果你[雇佣一个开发者](https://adevait.com),你可能会得到你需要的帮助。

SQL Server 将消耗所有可用内存。默认情况下,该数字对应于计算机上可用的数字内存总量。因此,你的看法是正确的。换句话说,如果您给 SQL Server 24 GB 内存,它会尽一切努力充分利用这些内存。当 SQL Server 和操作系统争用资源时,低性能是不可避免的副产品。

SQL Server 的缓冲池受到服务器配置中设置的最大服务器内存限制配置的限制(几乎是存储数据页和过程缓存的地方)。缓冲池不是 SQL Server 中唯一的内存工作线程;还有许多其他内存工作程序(适用于 2008 版 R2 和更高版本)。但是,这将始终是内存需求最高的应用程序。

Microsoft SQL Server 数据库引擎的缓冲池受服务器的最小和最大内存设置的限制。

## 服务器配置选项

通过为特定的 SQL Server 实例自定义最小和最大服务器内存选择,可以改变 SQL Server 内存管理器管理的内存量(以兆字节为单位)。SQL Server 的内存需求可以根据系统上的可用资源进行动态调整。

要设置固定的内存量,请按照下列步骤操作:

*   在对象资源管理器中右键单击服务器,并从上下文菜单中选择属性。
*   从下拉菜单中选择内存节点。
*   在“服务器内存选项”下,在相应的字段中输入所需的最小服务器内存和最大服务器内存。

如果希望 SQL Server 能够根据可用的系统资源动态更改其内存需求,请使用默认设置。在默认配置中,最小服务器内存设置为 0 兆字节,最大服务器内存默认设置为 2147483647 兆字节(MB)。

## 释放服务器

必要时,可以将 SQL 配置为只使用特定数量的 RAM。否则会消耗最大量的资源。这是在彻底调查服务器并确认 SQL Server 消耗了服务器总 10GB RAM 容量中的 9GB 后的标准过程。任何人都会将 SQL 可用的内存限制在 6GB,以避免服务器崩溃,并给它一些喘息的空间。此外,如果你看性能,你会注意到它是符合规定的。

## 最优消费

虽然前面的解决方案可能足以让我们摆脱困境,但很明显,理想的解决方案是找到一种方法来准确地确定每个数据库(DB)消耗多少内存,以便确定 SQL Server 实例需要多少内存。简单。只需要运行一个 SQL 查询就可以看到它。

根据我们对总 RAM 内存的观察,我们可以添加总的“db buffer MB ”,并查看 SQL 总共消耗了多少 RAM 内存。例如,如果我们将 SQL 限制降低到 9GB 并运行查询,我们可以看到数据库只消耗了 3GB 的 RAM 内存,这比上一个示例少得多。

结果,最初关于 SQL Server 内存消耗过多的假设被证明是正确的。尽管数据库只消耗 3GB 的内存,但如果我们将实例限制为 6GB,SQL Server 会设法充分利用可用内存。我们会谈论观察自己记忆的行为。

## 真实应用

这一结果最重要的方面是它可以投入实际使用。假设 SQL 只需要 3GB 的内存就能正常工作,那么是否可以将 SQL 的内存使用限制在 4GB,以避免浪费整个内存分配呢?是的。

在此图中,我们可以看到运行新查询后数据库的具体消耗。此外,如果我们定期运行这个查询并跟踪*均值,我们就可以确定消耗量是否会波动。

## 监控的重要性

在我们目前正在处理的这种情况下,监测的重要性立即变得显而易见。有了 [SQL Server 的 RAM 内存消耗](https://docs.microsoft.com/en-us/sql/relational-databases/performance-monitor/monitor-memory-usage?view=sql-server-ver15)的历史记录,我们可以计算出一个合理的*均值,然后设置可以分配多少内存的限制。

监控使我们能够开发一个确定最大值和最小值及其关系的尺度。如果超过这些阈值,或者如果应用程序消耗的内存超过预期,客户端和技术人员都不会收到警报电子邮件。

## 重要说明

将单个 SQL Server 实例的最大服务器内存增加到建议值以上,可能会迫使该 SQL Server 实例与同一服务器节点上运行的其他 SQL Server 实例争用内存。如果您将内存限制设置得太低,您可能会遇到严重的内存不足或性能问题。如果将最大服务器内存参数设置得太低,SQL Server 可能无法正常启动。

更改最大服务器内存参数后,如果您无法启动 SQL Server,请尝试使用-f 启动参数启动它,并将最大服务器内存参数恢复到以前的值。

# 为什么你应该学习数据工程

> 原文:<https://www.dataquest.io/blog/why-learn-data-engineering/>

October 16, 2019![why-learn-data-engineering](https://github.com/OpenDocCN/geekdoc-ds-zh/raw/master/dataquest-blog/img/1d63cc9b31314784a84444afbde1741d.png)

令人兴奋的消息:我们刚刚推出了一个完全改进的数据工程途径 ,为任何想成为数据工程师或学习一些数据工程技能的人提供从头开始的培训。

![YouTube video player for DU1PirFI21A](https://github.com/OpenDocCN/geekdoc-ds-zh/raw/master/dataquest-blog/img/0ebe79f9b8295cd3ea998d4b74e08ba3.png)

*[https://www.youtube.com/embed/DU1PirFI21A?feature=oembed](https://www.youtube.com/embed/DU1PirFI21A?feature=oembed)*

 *看起来很酷,对吧?但是它回避了一个问题:为什么首先要学习数据工程?

通常,数据科学团队由数据分析师、数据科学家和数据工程师组成。在[之前的一篇文章](https://www.dataquest.io/blog/data-analyst-data-scientist-data-engineer)中,我们已经讨论了这些角色之间的区别,但是在这里,让我们更深入地探讨成为一名数据工程师的一些优势。

数据工程师是连接公司或机构内所有数据生态系统的人。他们通过以下方式实现这一目标:

*   从应用程序和系统中访问、收集、审核和清理数据,使其处于可用状态
*   创建和维护高效的数据库
*   构建数据管道
*   监控和管理所有数据系统(可扩展性、安全性等)
*   以可扩展的方式实现数据科学家的成果

做上面列出的所有事情主要需要一种特殊的技能:**编程**。数据工程师是专门研究数据和数据技术的软件工程师。

这使得他们与数据科学家截然不同,后者当然有编程技能,但通常不是工程师。数据科学家将他们的工作(例如,推荐系统)交给数据工程师进行实际实施的情况并不少见。

虽然进行分析的是数据分析师和数据科学家,但通常是数据工程师在构建数据管道和其他必要的系统,以确保每个人都可以轻松访问他们需要的数据(并且没有人可以访问不应该访问的数据)。

强大的软件工程和编程基础使数据工程师能够构建数据团队及其公司成功所需的工具。或者,正如杰夫·马格努松所说:“我喜欢把它想象成乐高积木。工程师设计新的乐高积木,数据科学家以创造性的方式组装这些积木,以创造新的数据科学。”

这就引出了你想成为数据工程师的第一个原因:

## 1.为什么要学数据工程?这是数据科学的支柱

数据工程师站在数据策略的第一线,这样其他人就不需要站在第一线。他们是第一批处理进入公司系统的结构化和非结构化数据的人。它们是任何数据策略的基础。没有乐高积木,毕竟建不出乐高城堡。

![ai_hierarchy](https://github.com/OpenDocCN/geekdoc-ds-zh/raw/master/dataquest-blog/img/7642aa27756220b69a77b1862998af3f.png)

在上面的*数据科学需求层次*(由 [Monica Rogati](https://en.wikipedia.org/wiki/Monica_Rogati) 提出)中,数据工程师完全负责最下面的两行,与数据分析师和数据科学家共同负责倒数第三行。

为了更好地理解数据工程有多重要,想象一下上图中的金字塔被用作一个漏斗,并上下颠倒。数据被倒入这个漏斗的顶端,第一个接触到它的人是数据工程师。他们过滤、清理和引导数据的效率越高,随着数据沿着漏斗进一步流向其他团队成员,其他事情的效率也越高。

相反,如果数据工程师*不*高效,他们会成为漏斗中的一个阻碍,损害下游每个人的工作。例如,如果一个构建糟糕的数据管道最终向数据科学团队提供了不完整的数据,他们对这些数据执行的任何分析都可能是无用的。

这样,数据工程师就成了数据策略成果的倍增器。他们是站在数据分析师和数据科学家肩膀上的巨人。

拥有良好数据战略的公司组建团队的方式就是明证。据数据工程师、[大数据研究所](https://www.bigdatainstitute.io/)董事总经理 Jesse Anderson 称:

“一个常见的起点是每个数据科学家对应 2-3 名数据工程师。对于一些具有更复杂的数据工程要求的组织,每个数据科学家可以有 4-5 名数据工程师。”

## 2.这在技术上很有挑战性

数据分析师和科学家使用最多的 Python 函数之一是 read _ CSV——来自 [pandas](https://en.wikipedia.org/wiki/Pandas_(software)) 库。该函数将存储在文本文件中的表格数据读入 Python,以便可以对其进行浏览和操作。

如果您以前在 Python 中处理过数据,那么您可能非常习惯于输入如下内容:

```py
import pandas as pd df = pd.read_csv("a_text_file.csv")

简单方便吧?read_csv函数是软件工程本质的一个很好的例子:创建抽象的、广泛的、有效的和可伸缩的解决方案。

这是什么意思,它与学习数据工程有什么关系?让我们深入了解一下。

  • 抽象。当在计算机中读取文件时,一个非常复杂的过程在幕后发生。然而,我们对函数的使用非常简单,后台发生的事情是从用法中抽象出来的。你不需要理解read_csv在“引擎盖下”做什么来有效地使用它。
  • 广阔。这个函数还允许我们明确地选择在文本的文件表格数据中使用什么分隔符(例如,逗号、分号、制表符等等)。这使得它很容易与各种 CSV 风格一起使用,这对于数据科学家来说是一种享受。还有许多其他选择,允许数据从业者专注于他们的目标,而不必担心编程细节。
  • 高效。快速高效地工作,读入代码也很高效。
  • 可扩展。此功能包含的另一个选项允许我们按块读取文件,因此,如果文件太大而无法读入计算机的 RAM,则可以按块读取,这样用户就可以按文件大小来处理文件。

罗伯特·A·海因莱因据说说过“一个人的魔法是另一个人的工程”。正是数据工程师发挥了这种魔力,构建了像read_csv函数这样抽象、广泛、高效且可扩展的工具,这样团队的其他成员就可以专注于数据本身及其分析,而不必纠结于编程难题。

(同时,数据工程可能比数据科学需要更少的数学,所以如果你喜欢编程而不是数学,数据工程可能是一个理想的选择!)

3.这是值得的

让数据科学家的生活更轻松并不是激励数据工程师的唯一事情。不可否认,数据工程师正在对整个世界产生重大且日益增长的影响。

每天,我们都会创建 2.5 万亿字节的数据,今天的数据量之大使得数据工程师比以往任何时候都更加重要。 Business Insider 报告称,到 2025 年,物联网设备将超过 640 亿台,高于 2018 年的 100 亿台和 2017 年的 90 亿台。随着这种增长,来自更多来源的数据越来越多,因此对能够有效处理和传输这些数据的工程师的需求也越来越多。

这意味着数据工程师有大量不同的方式来追求他们的兴趣和深化他们的技能。为了让你了解这个世界有多大,这里列出了一些流行的数据工具和技术:亚马逊红移、亚马逊 S3 、阿帕奇卡珊德拉、阿帕奇 HBase 、阿帕奇卡夫卡、阿帕奇火花、阿帕奇动物园管理员、 Azure 、 ElephantDB Memcached ,微软 SQL Server , Mongo DB , Oracle 数据库, PostgreSQL , Redis , SQLite , Storm , SAP IQ , Teradata 和 Vertica 。

当然,数据工程师不需要知道所有这些,但是这个列表说明了在数据工程的世界里有多少事情要做。一旦你掌握了找工作的技能,你就有了很大的自由来选择你在做什么和使用什么工具。

由于数据工程师同时拥有数据和软件工程技能,他们也有能力构建各种产品。想为早期创业做贡献,还是想成为一名企业家并在某一天找到自己的事业?数据工程技能为您提供了构建优秀产品和分析这些产品性能所需的工具。你将能够实现和衡量你能想到的几乎任何事情的成功。

想要远程工作?根据 2019 年的未来劳动力报告,“未来三年,五分之二的全职员工将远程工作”。因此,如果办公室之外的工作适合你,数据工程可以帮助你实现这个目标。因为对数据工程师的需求很高,而且因为大部分工作可以远程完成,所以绝对有可能找到远程数据工程工作,或者作为短期数据工程项目的自由承包商为自己工作。

最后,数据工程师也有很多机会回馈社区。根据 2019 的 Stack Overflow 开发者调查,“Stack Overflow 上约 65%的职业开发者每年为开源项目贡献一次或更多”。由于你将拥有数据和工程技能,你将能够为数据科学社区开发新的酷工具。

4.报酬很高

你不应该只根据薪水来选择一份工作,但是不可否认薪水很重要!

根据 IBM 的 The Quant Crunch:对数据科学技能的需求如何扰乱就业市场,“机器学习技能的工作*均薪酬为 114,000 美元。广告上的数据科学家职位*均工资为 10.5 万美元,广告上的数据工程职位*均工资为 11.7 万美元。【着重强调另加。]

至于为什么,这并不奇怪。在 StackOverflow 的开发人员调查中,像 Python、SQL 和 shell 这样的数据工程技能经常被列为薪酬最高的技能。在撰写本文时,LinkedIn 上的搜索词数据科学家有大约 70,000 个结果,搜索词数据工程师有大约 112,500 个结果。在 GlassDoor 上,差异甚至更明显:数据科学家约为 22,500,而数据工程师约为 77,100(过滤了上个月发布的职位)。

不仅对数据工程师的需求很大,而且这种需求还在持续增长!截至 2019 年 6 月,数据工程师需求同比增长88%(来源)。

这还不是全部!据 Statista 称,“到 2027 年,全球大数据市场预计将增长至 1030 亿美元,是其 2019 年预期市场规模的两倍多”。

5.即使你不想成为数据工程师,它也是有价值的

即使你不想从事数据工程师的职业,如果你想从事数据科学方面的工作,掌握一些数据工程知识也会非常有用。好处是多方面的:

  • 作为一名数据从业者,您很有可能会定期被要求完成与其他工作角色有一些重叠的任务,包括数据工程。
  • 学习一种不同的看待事物的方式有利于你的理解,它给你一个机会去温习你可能有一段时间没用过的技能。
  • 拥有工程技能会让你更加自给自足。这可以极大地帮助你的职业生涯,因为你不再需要被封锁,等待别人为你做些什么。
  • 学习数据工程技能可以让你与数据工程师产生共鸣,更好地与他们沟通。这也将帮助你的团队,因为你可以成为连接你的团队和数据工程团队的桥梁。

成为一名数据工程师!

现在就学习成为一名数据工程师所需的技能。注册一个免费帐户,访问我们的交互式 Python 数据工程课程内容。

Sign up now!

(免费)

YouTube video player for ddM21fz1Tt0

https://www.youtube.com/embed/ddM21fz1Tt0?rel=0*

2022 年为什么要学 SQL(分析真实工作数据)

原文:https://www.dataquest.io/blog/why-learn-sql/

August 12, 2022Why learn SQL

为什么需要学习 SQL?

  • SQL 无处不在
  • SQL 的需求量很大,因为很多公司都在使用它
  • SQL 仍然是 2022 年最受欢迎的数据工作语言

SQL 真的很老了。好了,我说了。

SQL 可以追溯到 50 年前,当时 Edgar Codd(一位 IBM 计算机科学家)提出了一个新的系统来组织数据库中的数据。此后不久,SQL 诞生了。

从那以后,它在世界范围内被采用。

但是为什么在 2022 年,一个想要成为数据专业人员的人会学习这种“古老”的编程语言呢?为什么不花时间掌握 Python 或 R,或者专注于“更性感”的数据技能,如深度学习、Scala 或 Spark?

是的,了解 Python 或 R 等更通用语言的基础知识是至关重要的。然而,忽视 SQL 会让你更难找到数据方面的工作。

以下是 2022 年你绝对需要学习 SQL 的三个关键原因。

1.SQL 无处不在

几乎所有的大型科技公司都使用 SQL。优步、网飞、Airbnb——这样的例子不胜枚举。即使是已经建立了自己的高性能数据库系统的财富 500 强公司(脸书、谷歌、亚马逊)仍然经常使用 SQL 来查询数据和执行分析。

Companies using SQL

图片:StackShare.io

这也不仅仅是科技公司。几乎所有依赖大量数据的公司都使用 SQL。在 LinkedIn 上快速搜索工作会发现,更多的公司在寻找 SQL 技能,而不是 Python 或 R 技能。

SQL 可能很老,但它无处不在。

这里有一个真实的例子,说明 SQL 的重要性,来自我们以前的一个学生。。。

Vicknesh 得到了第一份数据分析师的工作,很快发现自己每天都在使用 SQL:“SQL 是如此的无孔不入;它渗透了这里的一切。这就像 SQL 语法在时间和空间中持续存在。一切都使用 SQL 或 SQL 的衍生物。”

vicknesh-quote-using-sql-job

维克内什完成了 Dataquest 的互动课程,这让他从一名化学老师变成了一名数据分析师。

2.SQL 是受欢迎的

如果你想得到一份数据方面的工作,你应该关注雇主真正想要的技能。为了搞清楚这一点,我们做了一些调查。

所有数据角色

为了证明 SQL 对于数据相关工作的重要性,我分析了 Indeed 上的 72,000 多个职位列表,查看了标题中带有“数据”的职位中提到的关键技能。

Data jobs listing SQL

正如我们所看到的,SQL 是数据领域所有工作中最受欢迎的技能,出现在 45.4%的职位发布中。

有意思的是,列 SQL 的数据作业比例一直在增加!多年来,我多次进行同样的分析,以下是我的发现:

  • 2017 年:35.7% (SQL #1)
  • 2021 年:42.7% (SQL #1)
  • 2022 年:45.4% (SQL #1)

数据分析师工作

如果你正在寻找数据方面的第一份工作,那么了解 SQL 更加重要。

数据领域的大多数入门级工作都是数据分析师角色,所以我也分析了所有这些角色:

Data analyst listings

对于数据分析师来说,SQL 再次成为最受欢迎的技能,在高达 61%的职位中出现。

对于 Indeed 上的数据分析师角色,SQL 显示如下:

  • 比 Python 多 1.7 倍
  • 比 R 大 2.5 倍
  • 比机器学习多 5.8 倍
  • 比火花多 22.5 倍

如果你想成为一名数据分析师,学习 SQL 应该是你的首要任务。

其他高级数据作业

事实上,即使您对更高级的角色感兴趣,SQL 技能仍然至关重要。

我对“数据科学家”和“数据工程师”的招聘信息进行了同样的分析。

数据工程师

Data engineer job listings

即使对于数据工程来说,SQL 也是顶级技能,在 73.4%的招聘信息中列出。

对于 Indeed 上的数据工程师角色,SQL 显示如下:

  • 略多于 Python
  • 比火花多 1.6 倍
  • 比机器学习多 3 倍

数据科学家

SQL and data science

SQL 并不是数据科学家最需要的技能,但它仍然占据了 64.7%的工作清单。

这意味着,即使你已经掌握了 Python,你也会错过五分之三的数据科学家工作机会,除非你的简历上也有 SQL 技能。

长话短说:是的,你需要学习 SQL 才能胜任数据科学行业的任何职位。

这不仅会让你更适合这些工作,还会让你从其他只关注“性感”事物的候选人中脱颖而出,比如 Python 中的机器学习。

如果你有兴趣成为一名数据分析师、数据工程师或数据科学家,Dataquest 提供一体化课程路径,旨在让你在不到一年的时间内从初学者到工作就绪。

3.SQL 仍然是数据工作的顶级语言

SQL 是整个技术行业中使用最多的语言之一!

根据 Stack Overflow 的 2022 年开发者调查,就受欢迎程度而言,SQL 甚至盖过了 Python。事实上,它是所有专业开发人员中第三受欢迎的编程语言:

Most used languages

但是我们特别关注数据科学领域的工作,所以让我们进一步过滤一下。

在 Stack Overflow 发布的完整数据集中这里我们可以看到,在与数据打交道的开发人员中(包括数据科学家、数据分析师、数据工程师等。),大约 70%的人使用 SQL,相比之下,61.7%的人使用 Python。

换句话说:根据参与 Stack Overflow 调查的 8786 名数据专业人士的说法,SQL 是数据科学中使用最多的语言。

尽管围绕 NOSQL、Hadoop 和其他技术有很多宣传,SQL 仍然是数据工作最流行的语言——也是各种开发人员最流行的语言之一。

YouTube video player for JFlukJudHrk

https://www.youtube.com/embed/JFlukJudHrk?feature=oembed

*## 那么,学习 SQL 最好的方法是什么呢?

既然我们知道了为什么我们应该学习 SQL ,显而易见的问题是如何学习?

网上有成千上万的 SQL 课程,但大多数都没有让你准备好在现实世界中使用 SQL。说明这一点的最佳方式是查看他们教您编写的查询:

The way most courses teach SQL

上面的查询演示了三个比较流行的在线学习网站在 SQL 课程结束时教授的 SQL 的复杂性。问题是现实世界的 SQL 看起来并不是这样的。现实世界中的 SQL 如下所示:

What SQL actually looks like

当您用数据回答业务问题时,您经常会编写 SQL 查询,这些查询需要组合来自许多表的数据,并将其转换成最终形式。

最终结果是学生们发现自己对自己想要的工作毫无准备,就像最*一个数据科学论坛上的帖子一样:

我们正在做什么

在 Dataquest,我们相信 SQL 能力是任何想从事数据工作的人的关键技能。

我们并不建议你学习 SQL 而不是 Python 或 R;相反,我们希望您将 SQL 作为第二语言来学习,并熟悉编写高级查询。

我们知道学习 SQL 对于数据科学非常重要,这也是我们提供大量交互式 SQL 课程的原因。这里有两条来自我们的数据分析师和数据科学家之路:

  • SQL 基础知识
  • SQL 中间:表关系和连接

我们的数据工程课程还包括几门独特的课程:

  • 面向数据工程师的 PostgreSQL】
  • 优化 PostgreSQL 数据库

我们还整理了一个可下载的 SQL 备忘单,作为 SQL 基础知识的有用参考。

我们编写互动课程,让我们的学生掌握他们需要的技能。你不会花时间看视频,相反,你将在几分钟内编写你的第一个查询,你将掌握最重要的数据技能。

虽然我们从零开始,但我们的课程超越了基础知识,因此您可以成为 SQL 高手。例如,上面的“真实”SQL 图像来自我们的 SQL 中级课程。

您可以注册并免费完成每门课程的第一课,我们鼓励您尝试这些课程,并让我们知道您的想法。

用正确的方法学习 SQL!

  • 编写真正的查询
  • 使用真实数据
  • 就在你的浏览器里!

当你可以 边做边学 的时候,为什么要被动的看视频讲座?

Sign up & start learning!

  • 编写真正的查询
  • 使用真实数据
  • 呆在你的浏览器里!

可以边做边学,为什么要被动看视频讲座?

我们热爱 SQL!

我希望我已经说服了您,掌握 SQL 是开始您的数据职业生涯的关键。虽然很容易被最新最棒的语言或框架分散注意力,但学习 SQL 将在你进入数据行业的道路上带来回报。

这可能是你学习的最重要的语言。*

学习 Bash 的 11 个理由(也称为命令行)

原文:https://www.dataquest.io/blog/why-learn-the-command-line/

April 21, 2021why learn command line data science

bash——基于 Unix 的操作系统的命令行语言——允许您像开发人员一样控制您的计算机。但是这不仅仅是软件开发人员的技能——学习 bash 对于任何使用数据的人来说都是有价值的。

什么是 Bash?

简而言之,Bash 就是 Unix 命令行界面(CLI)。您还会看到它被称为终端、命令行或 shell。它是一种命令语言,允许我们以一种比使用 GUI(图形用户界面)更有效和强大的方式在我们的计算机上处理文件。

从图形用户界面(GUI)切换到命令行界面可能会让人感到力不从心。虽然 Dataquest 让学习命令行变得非常简单,但是您可能会想:我为什么要费事呢?

以下是您应该学习 bash 并使用命令行的几个原因:

1.Bash 技能很受欢迎,而且报酬丰厚

根据 2020 年 Stack Overflow 的开发者调查,bash/shell(即 Linux 命令语言解释器家族)是第六大使用最多的语言,排在 Python 和 R 之前。根据调查,它还与比 Python 或 R 更高的工资有关。

它在最受欢迎的技术列表中排名很高(53.7%),在最令人恐惧的技术列表中排名较低(46%)。

虽然 StackOverflow 的调查涵盖了所有类型的软件开发人员和工程师,但命令行与数据科学家尤其相关,因为 Bash/Shell 与数据科学技术密切相关,如 Python、IPython/Jupyter、TensorFlow 和 PyTorch。这也得到了最*由 Python 软件基金会进行的 Python 开发者调查的支持。

2.命令行技能有助于构建可重复的数据流程

数据科学家的部分职责是确保某些信息定期可用,通常是每天可用。大多数时候,这些数据是以同样的方式获取、处理和显示的。

命令行非常适合这个目的,因为命令很容易自动化和复制。

考虑以下情况:

你的雇主决定投资数据分析。几名数据专业人员将加入该团队。您的任务是确保他们的机器具备启动所需的一切。

如果你能使用 CLI(命令语言解释器),你可以写一些脚本来自动安装、配置和测试一切。

如果你不能,你将不得不求助于一个图形用户界面,在几台机器上重复进行同样的鼠标点击动作。

这只是终端技能如何帮助提高数据科学流程的可扩展性和可重复性的一个例子。

3.学习 Bash 让你更灵活

在数据科学角色中,如果您可以使用终端,而不是依赖于通过 GUI 点击,您会经常发现您有更多的灵活性。

由于命令行是运行其他程序的程序(因此得名“shell”),程序之间的交互往往更容易在命令行中调整。

一旦掌握了 bash 命令,编写脚本就相对容易了,shell 脚本使得构建各种数据管道和工作流更加简单。

更广泛地说,知道如何使用 shell 为您提供了与计算机交互的第二种选择。

您可以随时使用 GUI,但是命令行可以在您需要的时候为您提供更直接的功能和控制。

4.使用文本文件更容易

文本文件是存储和处理数据最常用的方法之一。几乎所有的数据科学项目都会涉及到文本文件。因此,能够快速有效地处理文本文件对于数据科学家来说是一项非常有用的技能。

shell 拥有非常强大的文本处理工具,如 AWK 和 sed ,这些工具有助于熟悉文件并方便数据清理。

例如,下面的代码使用 AWK 打印名为 a_csv_file 的文件的第一列和第三列,其中第二个字段的值是 Dataquest,使用逗号作为字段分隔符。

awk 'BEGIN {FS=","} {if ($2=="Dataquest") {print $1 $3} } a_csv_file'

它只需要一行代码!

5.它不太耗费资源

当您使用有限的计算资源或者只是想最大化您的速度时,使用命令行实际上总是比使用 GUI 好,因为使用 GUI 意味着资源必须专用于呈现图形输出。

对于本地和远程工作都是如此。远程连接时,GUI 比终端消耗更多的带宽,浪费资源。

此外,当使用 GUI 时,延迟,即“刺激和响应之间的时间间隔”,将会更长,如果你试图控制比你实际移动落后一两秒的鼠标,这可能会特别令人沮丧。

如果您只是在命令行中键入,延迟可能会更低,也更容易处理,因为您知道在任何给定时间光标在哪里。

6.您需要关于云的命令行技能

云服务通常通过命令行界面连接和操作。

这对于深度学习等更高级的数据科学工作尤为重要,在深度学习中,您的本地计算资源可能不足以完成您想要执行的任务。引用 Nucleus Research 的这篇 2018 年的文章:

在去年的研究中,不到 10%的[深度学习]项目是在本地运行的。这一趋势已经加速,2018 年只有 4%的项目在内部运行。

根据同一篇文章,“今天 96%的深度学习正在云中运行。”

如果你有兴趣学习像深度学习这样的高级技术,命令行技能对于高效地将数据移入和移出云是必要的。

7.Unix Shell 技能可以很好地移植到其他 Shell

只有几个流行的 shell(bash,zsh,fish,ksh,tcsh,cmd,Windows PowerShell 等等。)而且它们的相似之处多于不同之处,很容易在它们之间切换。

例如,您在我们的命令行课程中学习的 bash 命令将在基于 Unix 的机器上工作,如 MAC 和 Linux 计算机。但是许多完全相同的命令也可以在 Windows 的命令提示符和/或 Windows PowerShell 中运行。

当您使用需要某种命令行界面的在线服务时,这种交叉兼容性特别有用。即使他们的系统不使用 bash,它也会使用一些足够相似的东西,这样你就能搞清楚了。

另一方面,GUI 有无限多种,学习一种并不一定有助于学习其他任何一种。

8.你打字的速度可能比你点击的速度还快

研究表明,使用鼠标会很快达到*稳状态,而使用键盘,尽管学习曲线很陡,但效率会更高。

251 名有经验的 Microsoft Word 用户接受了一份调查问卷,评估他们对最常见命令的选择。与我们的预期相反,大多数有经验的用户很少使用高效的键盘快捷键,而是喜欢使用图标工具栏。

第二项研究证实了键盘快捷键确实是最有效的方法。六名参与者使用菜单选择、图标工具栏和键盘快捷键执行常见命令。正如所料,键盘快捷键是最有效的。

换句话说:即使你觉得通过 GUI 工作很快,至少对于某些任务来说,你在命令行会更有效率。

9.审计和调试更加容易

因为在命令行上跟踪您的所有活动非常容易,所以审计和调试要容易得多。

您可以很容易地查看日志来跟踪您在 shell 中执行的每一个操作,而如果在使用 GUI 时一次误点击导致了一个错误,则很可能没有记录。

10.Unix Shell 随处可见

虽然它只内置在 Mac 和 Linux 机器上,但 Windows 用户仍然可以使用诸如 WSL 、 Cygwin 和 MinGW 这样的工具来享受乐趣。(如前所述,您将学习的许多 bash 命令都可以在 Windows 的原生 sell 选项中工作,比如命令提示符)。

这意味着你在这些课程中学到的命令行技能将可以在你遇到的几乎每台计算机上使用(包括你的个人机器,无论你使用什么操作系统)。

11.命令行比您想象的要简单

有一种误解,认为使用命令行需要您知道几百个命令。事实上,尽管有数百个命令可供使用,但您可能只需要其中很小一部分命令来完成大多数常见的数据科学任务。

还不服气?我们将为您奉上一句名言(免费!)本书Linux 命令行:

当我被要求解释 Windows 和 Linux 的区别时,我经常用一个玩具做类比。

Windows 就像一个游戏机。你去商店买一个全新的盒子。你把它带回家,打开它,玩它。漂亮的图形,可爱的声音。然而,过了一段时间,你厌倦了附带的游戏,所以你回到商店再买一个。这个循环一遍又一遍地重复。

最后,你回到商店,对柜台后面的人说,“我想要一个这样的游戏!”却被告知不存在这样的游戏,因为它没有“市场需求”。然后你说:“但是我只需要改变这一件事!”柜台后面的人说你不能换它。这些游戏都被密封在它们的盒子里。你发现你的玩具仅限于别人认为你需要的游戏。

另一方面,Linux 就像是世界上最大的安装程序。你打开它,它只是一大堆零件。有很多钢支柱,螺钉,螺母,齿轮,滑轮,电机,以及一些关于建造什么的建议。所以,你开始玩它。你建立了一个又一个建议。

过了一会儿,你会发现你对做什么有了自己的想法。你不必再去商店,因为你已经拥有了你需要的一切。竖立设置采取你的想象力的形状。它做你想做的。当然,你对玩具的选择是个人的事情,那么你会觉得哪种玩具更令人满意呢?

准备好学习命令行了吗?

现在您理解了为什么学习 bash 命令行界面是有价值的了,那么实际上您该如何去做呢?

最简单的方法是通过 Dataquest 的引导式交互式命令行课程,在您的浏览器中直接学习。您将学习高效工作所需的所有命令,在注册后的几分钟内编写真正的 bash 命令。

最棒的是?我们的三门课程都是免费试的!

现在就开始学习 Bash 吧!

注册一个 Dataquest 账户,免费尝试任何一门课程(或全部三门课程)。你将在不到五分钟的时间内编写你的第一个命令。

Elements of the Command Line

新手,从这里开始!

Text Processing in the Command LineCommand Line — Intermediate

你会学到什么?

在这两个命令行课程中,您将学习使用 Mac 和 Linux 机器上内置的 Unix 终端界面。别担心,我们还将为 Windows 用户提供充分利用内容所需的工具。

在第一门课程中,您将了解什么是命令行界面,为什么它在数据科学工作流中如此重要,以及如何通过向计算机发出指令(称为命令)来导航和管理计算机。您还将了解通配符,以及如何将它们与诸如lsmvcpmkdir等命令一起使用,以实现更快的搜索和工作流。

第二个课程集中在 shell 中的基本文本处理,使用像headcatcutgrep这样的命令。它涵盖了如何将这些命令组合起来,从更简单的构建块中创建强大的命令链。您还将了解到多用户系统和输出重定向的强大功能。

与所有 Dataquest 课程一样,这些新的命令行课程使用交互式命令行环境和答案检查,允许您直接在浏览器中应用和检查您学习的所有内容。

现在就开始学习 Bash 吧!

注册一个 Dataquest 账户,免费尝试任何一门课程(或全部三门课程)。你将在不到五分钟的时间内编写你的第一个命令。

Elements of the Command Line

新手,从这里开始!

Text Processing in the Command LineCommand Line — Intermediate

为什么要学习微软 Power BI?

原文:https://www.dataquest.io/blog/why-should-you-learn-microsoft-power-bi/

March 7, 2022Why You Should Learn Power BI

世界上的数据多得我们用不完。所有组织都被淹没在关于流程、产品、运营、客户的数据中,无所不包。如果它有数字足迹,那么它就在某个地方。

我们从消费者行为信息中学到的越多,我们就越能做出准确的预测。为此,我们需要合适的工具。这就是微软 Power BI 的用武之地。

微软数字商务智能

正如我们在这篇博客文章中提到的,Microsoft Power BI 是行业领先的商业智能*台,拥有超过 1.15 亿用户,并且还在不断增长。这是全球公司的首选数据解决方案。2020 年,电力 BI 市场价值超过 200 亿美元,预计到 2026 年将翻一番。对 Power BI 的需求是巨大的,而且还在增长。

毫不奇怪,数据分析、商业分析和商业智能领域的职业一直在蓬勃发展。有了商业分析技能,候选人可以在几乎任何领域找到工作,因为大多数公司需要数据专家来帮助他们做出更明智的决策。如果你在 LinkedIn 上快速搜索,你会发现在任何一天都有大约 200,000 个商业分析师职位。

因此,作为一名当前或未来的业务分析师,您很可能会遇到 Power BI。

什么是微软 Power BI?

那么微软 Power BI 的成功秘诀是什么呢?它是安全的,它在一个方便的地方集成了许多技术,并且很容易实现。这些因素使得它对许多公司来说是显而易见的。

本质上,Microsoft Power BI 是一个软件服务、应用和连接器的集合,它们协同工作,将您不相关的数据源转化为连贯的、视觉上身临其境的交互式见解。您可以将它连接到任何类型的数据源,并构建强大的数据可视化。

如果你查看微软 Power BI 网站,它会用大字告诉你,你可以“在数据和决策之间架起一座桥梁”重要的是,这不仅仅意味着你正在做的决定;这意味着你要负责制定决策。如果你有大量的数据,比如说,消费者的消费习惯,你需要创建一个报告,将这些习惯分解成可用的模式,产品部门可以研究这些模式来安排发布。。。这就是您使用 Microsoft Power BI 的目的。

用 Power BI 可以做什么?

Microsoft Power BI 有三个基本组件。将这些要素结合起来使用,您将成为一名高效的业务分析师。

动力中枢

Power Pivot 是“一种数据建模技术,可以让你创建数据模型,建立关系,并创建计算”(微软)。您可以使用 Power Pivot 处理大型数据集、建立关系和创建计算,所有这些都在一个位置完成,而且都在 Excel 中完成。

电源查询

Power Query 是微软的数据连接和数据准备技术,允许您访问来自数百个不同来源的数据,并根据您的需求进行调整。使用 Power Query 不需要任何代码,它提供了一个直观的、用户友好的界面。

权力观

该组件允许您实际调整数据。这是一种可视化技术,可以让你创建“图表、图形、地图和其他视觉效果,让你的数据栩栩如生”(微软)。您可以在 Excel、SharePoint、SQL Server 和 Power BI 中使用它。

应该自学 Power BI 吗?

自学如何使用 Microsoft Power BI 是一项艰巨的任务。你不能只在 YouTube 上花几个小时就认为自己是专家。微软 Power BI *台很直观,但仍然很复杂。你需要比基本面更多的东西来真正获得*台的好处。

我们也喜欢在黑暗中跌跌撞撞,撞上东西。这是一个非常。。。体验式学习。但是,如果您希望在当前或未来的工作中使用 Microsoft Power BI,并且希望尽早开始享受它的好处,我们建议您进行一些培训。

通过“使用 Microsoft Power BI 分析数据”技能途径学习

我们的“使用 Microsoft Power BI 分析数据”技能路线将带您在短短几个月内从完全的初学者变成完全的专家。

下面是你将会学到的东西:

  • 如何浏览 Power BI 界面
  • 如何清理、转换、加载和结构化数据
  • 如何设计数据模型
  • 如何利用有意义的视觉效果、仪表盘和报告创建引人注目的数据驱动型故事
  • 如何管理工作区
  • 如何进行商业分析

数据科学中的性别差距(以及你能做些什么)

原文:https://www.dataquest.io/blog/women-data-science-gender-gap/

April 10, 2019women-data-science-gender-gap

专业领域的性别差距正在缩小,尽管速度缓慢。女性现在占总劳动力的 48%。但是科技产业,尽管声称是革命性的,仍然落后。虽然数据科学行业比更广泛的科技行业稍微*衡一些,但它仍然是这个问题的一部分。截至 2019 年,只有大约 30%的专业数据相关职位由女性担任。

而且,那还只是一般就业的差距。在技术和数据科学领导角色和技术角色中,性别差距甚至更大。例如,尽管技术和数据相关工作的数量在过去几十年里激增,但即使在最大的科技公司,女性也没有获得任何接**等的代表权。

women-gender-gap-tech

图表来源:Statista.com

造成这个问题的原因是什么?更重要的是,从事数据科学行业的人能做些什么来解决这个问题呢?

儿童保育、老年人护理和带薪休假计划

我们看到担任领导职务的女性较少的最大原因之一是男女都没有带薪休假。历史上,妇女被视为儿童和老年亲属的主要照顾者。在过去的一个世纪里,越来越多的女性接受了教育并决定进入劳动力市场。但是当一个家庭有了一个孩子或者年老的亲戚生病时,通常仍然是妇女牺牲自己的职业来照顾孩子。

这种不*衡有文化原因,但也有金融原因。儿童保育和老年人护理费用昂贵,大多数家庭都无力支付全职专业护理费用。大多数公司不向男性提供带薪育儿假之类的福利,而且由于性别薪酬差距(我们很快就会谈到),女性通常比男性挣得少,所以对许多家庭来说,女性呆在家里更有经济意义。

为了改变这种动态,我们必须改变男人不能提供照顾的观念。公司需要更好地为男女员工提供带薪休假。

我们还需要让女性更容易告诉她们的公司她们将怀孕。许多女性害怕“母性惩罚”,即管理层会将她们的母性视为她们不太专注于职业的证据,并将她们排除在晋升机会之外。

公司应该为男性提供强制性的带薪休假(无论是出于陪产假还是照顾老人的原因),而那些有可选带薪休假福利的男性应该利用这些福利。让更多男性休育儿假或照顾老人假,将有助于消除支撑“母性惩罚”心态的偏见。

像美国电话电报公司、普华永道和家得宝这样的大公司已经看到了增加或扩大带薪休假政策的好处。其中最主要的好处是:提供带薪休假政策有助于留住人才。将“产假”扩大到“育儿假”不仅仅是多样性和包容性的胜利,它通常也是公司的财务胜利。

性别薪酬差距

性别薪酬差距是一件真实而可怕的事情。白人男性每挣 1 美元,白人女性仍然只挣 80 美分,对有色人种女性来说,统计数字更令人沮丧。

这种差距有多种原因。一个很大的问题是它是自我延续的。当雇主询问新员工以前的薪水时,如果一名女性的薪水过低,并且她诚实地回答,那么她的新雇主很可能也不会给她应得的薪水。

出于这个原因,美国一些州已经或正在努力将询问以前的薪水定为非法,这将防止公司在提出新的报价时受到女性过去薪水的影响。

然而,即使在没有强制要求的州,数据科学雇主也应该制定 it 公司政策而不是询问应聘者以前的薪水。录用应该基于工作职责和候选人的技能。不问以前的薪水将有助于结束这种自给自足的循环,即女性的薪水仍然低于男性,因为她们过去的薪水更低。

女性薪酬仍然低于男性同事的另一个原因是,女性不太可能要求加薪或谈判薪资提议。这与外部社会压力有关;如果女性谈论金钱或要求更高的薪水,她们有时会被管理层视为贪婪或过于咄咄逼人,而做同样事情的男性可能会被视为雄心勃勃。女性有时也不愿意要求加薪和升职,因为她们担心,如果她们已经休过(或以后打算休)产假或其他带薪假,会被视为要求太多。

为了改变这种情况,数据科学领域的男性(尤其是那些担任领导职务的男性)应该鼓励女性协商自己的薪酬,并应该反对女性要求获得与其技能价值相当的薪酬是“贪婪”或“咄咄逼人”的偏见。公司规定男女都必须休育儿假,如果他们有选择的话,也将有助于解决这个问题。

性别薪酬差距的另一个原因是,金钱在工作场所经常是一个禁忌话题。虽然女性普遍意识到性别工资差距的存在,但她们往往不知道自己的工资与同龄人相比如何。美国的员工很少互相讨论他们的工资(在某些情况下,管理层积极阻止分享工资数字,尽管公司禁止员工互相谈论工资通常是非法的)。

数据科学领域的男性可以通过主动与和他们职位相似的女性分享工资来帮助解决这个问题。女性还应该定期查看 Glassdoor 等在线资源,以了解她们技能的市场价值,以及员工分享的任何公司工资。可以通过职位和地点来搜索薪水,经常查看当地的*均水*(和具体的薪水报告)会让这个行业的女性更好地了解她们的技能价值,以及她们的薪水是否过低。

(在我们的职业指南中,我们为处于这一职位的人提供了一些关于薪资谈判的有用建议。

科学、技术、工程和数学领域的女性缺乏指导和领导力

数据科学领域女性性别差距的一大原因是,许多女性从小就不愿意从事 STEM 职业。这里有许多因素在起作用,包括在 STEM 中缺乏正面的女性榜样,经常将女性推离 STEM 工作的社会规范,以及几乎从出生就开始的待遇差异。

例如,对于一个年轻的男孩来说,收到像乐高、恐龙、科学套装这样的玩具是很常见的。通常这些类型的玩具和书籍都明确标明是面向男孩的。年轻女孩没有遵从自己天生的好奇心,而是从市场营销、礼物以及学校和家庭生活中获得信息,将她们推向“传统”的性别角色,并且不鼓励对 STEM 的兴趣。

这不是数据科学行业可以直接解决的问题,但有孩子的数据科学家应该努力让男女儿童从童年早期就接触与 STEM 相关的玩具和活动,并明确他们可以追求他们感兴趣的任何事情,无论这种事情传统上是否与某一性别相关。

为年轻女孩提供积极的榜样也非常重要。尽管历史上充满了激励女性在 STEM 工作的例子,但在我们的文化中,这些故事不像男性技术领导者和榜样的故事那样经常被讲述。我们需要教育我们的女儿和儿子了解像让·e·萨姆特、格蕾丝·赫柏这样的女性,以及一群非裔美国女性,她们在 20 世纪 40 年代实质上是美国宇航局的“人类计算机”。这些女性在科技世界的历史上所做的工作没有得到足够的赞扬,让孩子们知道聪明和见多识广很酷是很重要的,不管你是什么性别。

这种社会障碍也延续到青春期和成年早期。很难在看电视、读杂志或浏览社交媒体时不看到一些关于女人应该是什么样子的描述,或者女人 T2 应该是什么样子的描述。通常,这些描述强化了传统的性别角色和陈规定型观念。聪明、强大的女性也被描绘成孤独、好斗、贪婪、缺乏魅力等。同样,数据科学家无法直接解决这一问题,但我们都可以通过分享和支持更积极地展示 STEM 中的女性和担任领导角色的女性的电视节目、电影和书籍来帮助应对这一问题。

数据科学的资源和解决方案

我们还可以做其他事情,让女性更容易进入数据科学行业。在科学、技术、工程和数学领域为年轻女孩和妇女建立社区和资源,增加了技术领域的女性人数,并鼓励更多女孩攻读技术学位。如 少女养成计划 , TechGirlz , 内 , 少女们代号 , R 仕女们 , 皮拉迪斯 , #YesWeCode , 黑

让任何人都能更容易地获得 STEM 技能也很重要。Dataquest 是众多在线教育*台中的一个,它将数据科学技能放在任何有互联网连接的人的指尖上。与传统学位相比,这些*台需要的时间和资金要少得多,而且它们允许学生根据自己的条件学习数据科学技能。与以前相比,在线教育已经让更多的女性获得了数据科学技能,而且这一趋势可能会持续到未来。

在 STEM 领域拥有男性盟友也是一个巨大的帮助。除了已经提到的,男性盟友可以做更多的事情来支持 STEM 世界中的女性同行,包括:支持和放大女性,寻找女性在会议上发言(而不是参加全男性讨论小组),招聘女性候选人等等。

如果你是一家公司领导团队的一员,你有一个特别好的机会在缩小性别差距方面有所作为。当然,你应该根据男性和女性员工的技能向他们支付同等的薪酬,而且你应该努力招聘和鼓励 STEM 中的女性。你应该引入像富有同情心的编码这样专注于移情和多样性的演讲者和项目,在任何问题出现之前先发制人地教育领导和员工。你应该设立多元化和包容性首席官这样的领导角色,这有助于确保你的所有员工都感到受欢迎,并感到他们有发言权。你应该评估你的公司的性骚扰政策,以确保没有受害者感到羞耻,犯罪者得到适当的惩罚。

作为一个在技术领域的少数民族女性,我自己也是一个女儿的母亲,我希望在不久的将来有一天不再需要像这样的文章。在男性盟友、真正促进多样性和包容性的公司以及数据科学和 STEM 领域更强大的女性榜样的帮助下,我很高兴看到数据科学行业和更广泛的技术社区在未来几年可以取得的进步。

以下是一些与科技包容性和多样性相关的其他资源:

  • 数据科学和机器学习领域的女性资源
  • 统计数据:女性在科技领域的状况
  • 2019 年值得关注的*等趋势
  • 这些顶级公司如何获得包容性

如何写一篇对人们有实际帮助的训练营评论

原文:https://www.dataquest.io/blog/write-a-bootcamp-review-that-actually-helps-people/

January 18, 2018

编者按:这篇文章是与 SwitchUp 合作的一部分,这是一个研究和回顾技术学习项目的在线*台。Erica Freedman 是 SwitchUp 的内容和客户服务专家。

数据科学是一个快速发展的行业。从大学项目到为期一周的团队,很难决定从哪里开始。就像你当地餐馆橱窗上的“全美国最好的咖啡”标志一样,每个新兵训练营或学校网站都告诉你他们是游戏中最好的。根据 SwitchUp 的研究,“目前全球有超过 120 个面对面的训练营和数百个兼职和在线项目。”虽然选择可能是好事,但也可能令人望而生畏。

你如何确定你选择了正确的项目?

为了控制质量,学生们已经开始利用毕业生的评论和评级来剔除充斥市场的不尽如人意的项目。详细的评论让学生超越营销材料或宣传,并提供宝贵的第一手经验。当学生打算转行时,实际的观点往往是一个决定性的因素。它可以帮助学生了解从研究开始到技术职业的整个过程。

如果你是训练营的毕业生(或即将毕业的毕业生),你的观点可以帮助下一批学生“向前支付”,也可以给你的学校有益的反馈。回想一下当你试图找到最好的程序时,从那个角度写一篇评论。你希望在进入训练营之前看到或听到什么?

我们建议以下技巧来写一篇对未来学生有价值的评论。

权衡利弊

即使你的训练营是你一生中最完美的经历,也总有改进的空间。帮学生做调查,报道你项目经历的积极方面,同时用建设性的批评来*衡。这种反馈不仅有助于那些希望加入学校的人,也有助于学校本身。

在 SwitchUp,我们发现潜在的学生最感兴趣的是课程质量、教学人员和工作支持,所以一定要提到你对这些方面的想法。如果你的学校有多个校区,那么你需要列出你就读的校区,因为这些变量会随着校区的不同而变化。

谈谈你的完整经历:在训练营之前、之中和之后

你见过这样的评论:“太棒了!”或者“我讨厌它。”?虽然这些都是技术上的复习,但对准学生都没有帮助。是什么让训练营如此伟大?是老师吗?课程的长度?校园的位置?在考虑申请过程直至工作机会的时候,有太多的变量需要考虑。

当你写评论时,包括该项目如何帮助你沉浸在数据科学的世界中,以及它如何帮助你在毕业后取得成功。比如:前期工作是否给了你对数据科学行业有用的介绍?职业服务有没有帮助你在与你梦想中的公司的面试中胜出?完整的图片将向未来的新兵训练营成员展示该计划如何帮助他们学习编码以满足他们的职业目标。

讲述你的故事

SwitchUp 采访了许多训练营的学生。你的故事是什么?也许你是从一个完全不同的背景开始转向数据科学的。或者你从大学休学一个学期,仅仅是为了在训练营获得技能。无论情况如何,你的道路将向其他学生展示什么是可能的。

如果你没有数据科学、编码或计算机科学背景,这种观点尤其有用,因为许多训练营的学生来自不同的领域。你的故事将向未来的学生展示,只要他们有决心,他们也可以转向科技职业。

在哪里写你的评论

许多训练营的校友选择在 Quora 和 Medium 等网站或 SwitchUp 等评论网站上留下评论。

如果你有兴趣写一篇关于 Dataquest 的评论,请点击这里查看他们的 SwitchUp 评论页面。此外,一旦您提交经过验证的评论,您将自动获得 SwitchUp 的五张 $100 亚马逊礼品卡或一张 $500 亚马逊礼品卡大奖。本次抽奖活动将于三月结束,所以赶快行动吧!

如何用 R 写函数(附 18 个代码示例)

原文:https://www.dataquest.io/blog/write-functions-in-r/

June 15, 2022Using Functions in R

函数是 r 中必不可少的工具。下面是关于创建和调用函数的一些知识。

R 中的函数是最常用的对象之一。理解 R 函数的用途和语法以及知道如何创建或使用它们是非常重要的。在本教程中,我们将学习所有这些东西,甚至更多:什么是 R 函数,R 中存在哪些类型的函数,何时应该使用函数,最流行的内置函数,如何创建和调用用户定义的函数,如何在另一个函数中调用一个函数,以及如何嵌套函数。

R 中的函数是什么?

R 中的函数是一个对象,它包含多个相互关联的语句,每次调用函数时,这些语句都以预定义的顺序一起运行。R 中的函数可以是内置的,也可以由用户创建(用户自定义)。创建用户定义函数的主要目的是优化我们的程序,避免在特定项目中频繁执行的特定任务所使用的相同代码块的重复,防止我们出现与复制粘贴操作相关的不可避免且难以调试的错误,并使代码更具可读性。一个好的做法是,每当我们需要运行一组特定的命令两次以上时,就创建一个函数。

R 中的内置函数

R 中有许多有用的内置函数用于各种目的。一些最受欢迎的是:

  • min()max()mean()median()——相应地返回一个数字向量的最小值/最大值/*均值/中值
  • sum()–返回一个数值向量的和
  • range()–返回数值向量的最小值和最大值
  • abs()–返回一个数字的绝对值
  • str()–显示 R 对象的结构
  • print()–在控制台上显示一个 R 对象
  • ncol()–返回矩阵或数据帧的列数
  • length()–返回 R 对象(向量、列表等)中的项目数。)
  • nchar()–返回字符对象中的字符数
  • sort()–按升序或降序(decreasing=TRUE)对向量进行排序
  • exists()–根据变量是否在 R 环境中定义,返回TRUEFALSE

让我们来看看上面的一些函数的运行情况:

vector <- c(3, 5, 2, 3, 1, 4)

print(min(vector))
print(mean(vector))
print(median(vector))
print(sum(vector))
print(range(vector))
print(str(vector))
print(length(vector))
print(sort(vector, decreasing=TRUE))
print(exists('vector'))  ## note the quotation marks
[1] 1
[1] 3
[1] 3
[1] 18
[1] 1 5
 num [1:6] 3 5 2 3 1 4
NULL
[1] 6
[1] 5 4 3 3 2 1
[1] TRUE

在 R 中创建函数

虽然应用内置函数方便了许多常见的任务,但我们通常需要创建自己的函数来自动执行特定的任务。为了在 R 中声明一个用户定义的函数,我们使用关键字function。语法如下:

function_name <- function(parameters){
  function body 
}

以上,一个 R 函数的主要组成部分有:函数名函数参数函数体。让我们分别来看看它们。

函数名

这是函数对象的名称,它将在函数定义后存储在 R 环境中,并用于调用该函数。它应该简洁、清晰、有意义,这样阅读我们代码的用户就可以很容易理解这个函数到底是做什么的。例如,如果我们需要创建一个计算已知半径的圆的周长的函数,我们最好调用这个函数circumference,而不是function_1circumference_of_a_circle。(旁注:虽然我们通常在函数名中使用动词,但是如果一个名词非常具有描述性且明确,那么只使用这个名词也是可以的。)

功能参数

有时,它们被称为正式论点。函数参数是函数定义中的变量,放在括号内并用逗号分隔,每次我们调用函数时,这些变量将被设置为实际值(称为参数)。例如:

circumference <- function(r){
    2*pi*r
}
print(circumference(2))
[1] 12.56637

上面,我们使用公式\(C = 2\pi\)\(r\)创建了一个函数来计算半径已知的圆的周长,因此该函数只有唯一的参数r。定义函数后,我们用半径等于 2(因此,参数为 2)来调用它。

一个函数没有参数是可能的,尽管很少有用:

hello_world <- function(){
    'Hello, World!'
}
print(hello_world())
[1] "Hello, World!"

此外,一些参数可以在函数定义中设置为默认值(那些与典型情况相关的值),然后可以在调用函数时重置这些值。回到我们的circumference函数,我们可以将一个圆的默认半径设置为 1,那么如果我们在没有传递参数的情况下调用该函数,它将计算单位圆(即半径为 1 的圆)的周长。否则,它将使用提供的半径计算圆的周长:

circumference <- function(r=1){
    2*pi*r
}
print(circumference())
print(circumference(2))
[1] 6.283185
[1] 12.56637

功能体

函数体是花括号中的一组命令,每次我们调用函数时,这些命令都会按照预先定义的顺序运行。换句话说,在函数体中,我们放置了我们需要函数做的事情:

sum_two_nums <- function(x, y){
    x + y
}
print(sum_two_nums(1, 2))
[1] 3

注意,函数体中的语句(在上面的例子中——唯一的语句x + y)应该缩进 2 或 4 个空格,这取决于我们运行代码的 IDE,但重要的是在整个程序中保持缩进一致。虽然它不影响代码性能,也不是必须的,但它使代码更容易阅读。

如果函数体包含一个语句,可以去掉花括号。例如:

sum_two_nums <- function(x, y) x + y
print(sum_two_nums(1, 2))
[1] 3

正如我们从上面的例子中看到的,在 R 中,定义函数时通常不需要显式地包含 return 语句,因为 R 函数只是自动返回函数体中最后一个求值的表达式。然而,我们仍然可以使用语法return(expression_to_be_returned)在函数体内添加 return 语句。如果我们需要从一个函数返回多个结果,这是不可避免的。例如:

mean_median <- function(vector){
    mean <- mean(vector)
    median <- median(vector)
    return(c(mean, median))
}
print(mean_median(c(1, 1, 1, 2, 3)))
[1] 1.6 1.0

注意,在上面的 return 语句中,我们实际上返回了一个包含必要结果的向量,而不仅仅是由逗号分隔的变量(因为return()函数只能返回一个 R 对象)。除了向量,我们还可以返回一个列表,特别是如果返回的结果应该是不同的数据类型。

在 R 中调用函数

在上面所有的例子中,我们实际上已经多次调用了创建的函数。要做到这一点,我们只需在括号中输入点名称并添加必要的参数。在 R 中,函数参数可以通过位置、名称(所谓的命名参数)、混合基于位置和基于名称的匹配或者完全省略参数来传递。

如果我们通过位置传递参数,我们需要遵循函数中定义的相同的参数序列:

subtract_two_nums <- function(x, y){
    x - y
}
print(subtract_two_nums(3, 1))
[1] 2

在上面的例子中,x 等于 3,y–等于 1,反之亦然。

如果我们按名称传递参数,即明确指定函数中定义的每个参数取什么值,参数的顺序并不重要:

subtract_two_nums <- function(x, y){
    x - y
}
print(subtract_two_nums(x=3, y=1))
print(subtract_two_nums(y=1, x=3))
[1] 2
[1] 2

因为我们显式地指定了x=3y=1,我们可以将它们作为x=3, y=1y=1, x=3传递——结果是一样的。

有可能混合使用基于位置和基于名称的参数匹配。让我们来看一个根据女性的体重(公斤)、身高(厘米)和年龄(年)计算基础代谢率(基础代谢率)或每日卡路里消耗量的函数示例。将在函数中使用的公式是米夫林-圣杰尔方程:

calculate_calories_women <- function(weight, height, age){
    (10 * weight) + (6.25 * height) - (5 * age) - 161
}

现在,我们来计算一个 30 岁,体重 60 kg,身高 165 cm 的女性的热量。但是,对于年龄参数,我们将按名称传递参数,对于其他两个参数,我们将按位置传递参数:

print(calculate_calories_women(age=30, 60, 165))
[1] 1320.25

在上面这样的情况下(当我们混合使用按名称和按位置匹配时),命名的参数从整个连续的参数中提取并首先匹配,而其余的参数按位置匹配,即按照它们在函数定义中出现的相同顺序。然而,这种做法并不推荐,可能会导致混乱。

最后,我们可以完全省略一些(或全部)论点。如果我们在函数定义中将一些(或全部)参数设置为默认值,就会发生这种情况。让我们返回到我们的calculate_calories_women函数,将女性的默认年龄设置为 30 岁;

calculate_calories_women <- function(weight, height, age=30){
    (10 * weight) + (6.25 * height) - (5 * age) - 161
}
print(calculate_calories_women(60, 165))
[1] 1320.25

在上面的例子中,我们只给函数传递了两个参数,尽管它的定义中有三个参数。但是,由于当我们向函数传递两个参数时,其中一个参数被赋予了一个默认值,R 解释为第三个缺少的参数应该被设置为其默认值,并相应地进行计算,而不会抛出错误。

当调用一个函数时,我们通常将这个操作的结果赋给一个变量,以便以后使用:

circumference <- function(r){
    2*pi*r
}
circumference_radius_5 <- circumference(5)
print(circumference_radius_5)
[1] 31.41593

在其他函数中使用函数

在 R 函数的定义中,我们可以使用其他函数。我们之前已经见过这样的例子,当我们在用户定义的函数mean_median中使用内置的mean()median()函数时:

mean_median <- function(vector){
    mean <- mean(vector)
    median <- median(vector)
    return(c(mean, median))
}

也可以将调用一个函数的输出作为参数直接传递给另一个函数:

radius_from_diameter <- function(d){
    d/2
}

circumference <- function(r){
    2*pi*r
}

print(circumference(radius_from_diameter(4)))
[1] 12.56637

在上面这段代码中,我们首先创建了两个简单的函数:根据给定的直径计算圆的半径,根据给定的半径计算圆的周长。因为最初我们只知道一个圆的直径(等于 4),所以我们调用了circumference函数中的radius_from_diameter函数,首先根据提供的直径值计算半径,然后计算圆的周长。虽然这种方法在很多情况下很有用,但是我们应该小心使用,避免将太多的函数作为参数传递给其他函数,因为这会影响代码的可读性。

最后,函数可以嵌套,这意味着我们可以在另一个函数中定义一个新函数。比方说,我们需要一个将三个不相交圆的圆面积相加的函数:

sum_circle_ares <- function(r1, r2, r3){
    circle_area <- function(r){
        pi*r^2
    }
    circle_area(r1) + circle_area(r2) + circle_area(r3)
}

print(sum_circle_ares(1, 2, 3))
[1] 43.9823

上面,我们在sum_circle_ares函数中定义了circle_area函数。然后,我们在外部函数中调用内部函数三次(circle_area(r1)circle_area(r2)circle_area(r3)),以计算每个圆的面积,进一步对这些面积求和。现在,如果我们试图在sum_circle_ares函数之外调用circle_area函数,程序会抛出一个错误,因为内部函数存在并且只在定义它的函数内部工作:

print(circle_area(10))
Error in circle_area(10): could not find function "circle_area"
Traceback:

1\. print(circle_area(10))

当嵌套函数时,我们必须记住两件事:

  1. 与创建任何函数类似,内部函数应该在外部函数中至少使用 3 次。否则,创建它是不可行的。
  2. 如果我们希望能够独立于更大的函数使用函数,我们应该在更大的函数之外创建它,而不是嵌套这些函数。例如,如果我们要在sum_circle_ares函数之外使用circle_area函数,我们将编写以下代码:
circle_area <- function(r){
    pi*r^2
}

sum_circle_ares <- function(r1, r2, r3){
    circle_area(r1) + circle_area(r2) + circle_area(r3)
}

print(sum_circle_ares(1, 2, 3))
print(circle_area(10))
[1] 43.9823
[1] 314.1593

这里,我们再次在sum_circle_ares函数中使用了circle_area函数。然而,这一次,我们也能够在函数外部调用它,并得到结果而不是错误。

摘要

在本教程中,我们学习了与 r 中的函数相关的许多方面。

  • R 中函数的类型
  • 为什么以及何时我们需要创建一个函数
  • R 中一些最流行的内置函数以及它们的用途
  • 如何定义用户定义的函数
  • 功能的主要组成部分
  • 命名函数的最佳实践
  • 何时以及如何将功能参数设置为默认值
  • 函数体及其语法的细微差别
  • 何时应该在函数定义中显式包含 return 语句
  • 如何使用命名参数、位置参数或混合参数调用 R 函数
  • 当我们混合位置参数和命名参数时会发生什么——为什么这不是一个好的实践
  • 当我们可以省略一些(或全部)论证时
  • 如何在其他函数中应用函数
  • 如何将函数调用作为参数传递给另一个函数
  • 何时以及如何嵌套函数

掌握了这些技能和信息,您就可以开始在 r 中创建和使用函数了。

数据科学家 Yassine Alouini 如何保持他的技能

原文:https://www.dataquest.io/blog/yassine-alouini/

December 30, 2015

为了突出 Dataquest 如何改变人们的生活,我们已经开始了一个名为用户故事的新博客系列,在这里我们采访我们的用户,以了解他们的个人旅程以及我们如何帮助他们达到他们需要的目的。

在本帖中,我们采访了 Qucit 的数据科学家 Yassine Alouini。Yassine 通过自由职业进入数据科学领域,并在此过程中积累了一些令人印象深刻的技能。从分析数据、创建预测模型、制作数据管道到创建交互式可视化和 web 应用程序,他什么都做过。通过访问他的 Github 个人资料,你可以看到他目前正在进行的一些项目。

完成硕士学位后,你做了一段时间自由职业者。你是如何开始从事自由职业的?

老实说,我已经使用了多个场地来开始自由职业。我订阅了几个*台,在那里你可以创建个人资料,然后感兴趣的客户就会联系你。我也问过朋友,他们是否认识一些需要统计分析帮助的人。第二种方法是最成功的。

总的来说,最困难的事情是开始。一旦我找到了我的第一课,下一课就容易多了。我获得了更多的经验,并取得了一系列的成就。

你在短时间内从自由职业者变成了全职的数据科学职位。在这期间你需要学习什么技能?

当自由职业者时,我也在找工作。自由职业在三个不同的方面给了我很大的帮助:

  1. 我变得更加自信,知道在项目工作时如何问正确的问题。这项技能对于数据科学家来说非常有价值。
  2. 我学会了如何管理与客户的关系。这并不总是容易的,一路上我犯了一些错误。
  3. 我获得了新的技术技能。我了解了更多我已经知道的算法的细节,并尝试了新的算法(主要是计量经济学)。

与此同时,我参加了一些 Kaggle 挑战,阅读了大量关于机器学习、数据可视化和数据科学的知识。

你现在是 Qucit 的数据科学家。你使用的主要工具和技术是什么?

我主要使用这些 Python 库:用于机器学习和统计的 Pandas、Scikit-learn、Numpy 和 Statsmodels,用于可视化的 Matplotlib、Seaborn、Ggplot 和 Bokeh,用于与数据库交互的 SQLalchemy。总的来说,用于数据科学的 Python 生态系统非常优秀。我还使用 D3.js 进行动态数据可视化。在某个时候,我曾使用 AngularJS 进行 web 和移动(使用 ionic)开发。

工作时,我倾向于在 Jupyter 笔记本和 IPython 以及 Atom(一个文本编辑器)之间切换,前者用于探索阶段,后者用于生产代码。我也用 Jupyter 笔记本(幻灯片模式)做演示。Jupyter 笔记本的一个优点是你可以在同一个界面上使用不同的语言。您还可以在不离开笔记本的情况下与您的终端进行交互。这太棒了。

是什么让你决定开始使用 Dataquest?

我是在线课程的超级粉丝(我做自由职业者的时候做过很多 MOOCs ),我认为这是教育的未来。一天晚上,我在寻找一门新的数据科学课程,但我想要一些不同于通常 MOOC 体验的东西,一些不同于“你观看视频,然后接受挑战和测试”的东西。我想要更具互动性的东西。然后我遇到了 Dataquest。

在开始的时候(2015 年 4 月左右),我很轻松地使用了它,并接受了一些 Python 可视化的挑战,但我已经上瘾了。大约两个月前,我决定是时候进行付费订阅了。到目前为止,这是一次非常有益的经历🙂

Dataquest 对您的学习过程有什么帮助?

Dataquest 帮助我更深入地了解数据科学主题。例如,我使用 Matplotlib 已经有一段时间了,但直到最*才真正理解其内部原理。它还帮助我组织我的想法,并在处理数据时获得更多的信心。最后,我通常会想到它作为游戏提供的学习体验,到目前为止,这是一个非常愉快的体验。

你认为项目对学习数据科学的过程有多重要?

数据科学很难,如果只依赖理论,它会变得更难。一个人必须练习才能在这一行做得更好。事实上,通过项目学习是非常有益的。对于每个新项目,您都会遇到新的挑战(格式错误的数据、相关的特性、难以可视化的数据、过度拟合的算法等),并且您会立即获得可行的见解。

如果你是通过一个项目没有对抗地学习数据科学技术,那么你的体验是不完整的。

关于学习数据科学和找工作,你看到的最大误解是什么?

正如我上面所说,数据科学很难。学习它也很难,原因有很多,主要是因为该领域仍处于起步阶段,可用的工具仍在不断成熟。有些人假设你可以学习一些 Python 课程和几门 MOOCs,成为一名伟大的数据科学家。这当然与事实相去甚远。要想做好数据科学的交易,需要不断学习。

话虽如此,但人们千万不要认为靠自己是不可能学会的。学习的道路越来越清晰。越来越多的课程和资源可用。最后,该领域的许多杰出领导者正在为新一代数据科学指明道路。

学什么最让你兴奋?

最*几个月我读了很多关于 Spark 的书,想学习使用它。我注意到上面有一个 Dataquest 部分。我将尽快试用它。

除了 Apache Spark,我也对深度学习(特别是深度强化学习)越来越感兴趣。我已经开始玩递归神经网络了。到目前为止,我已经成功地建立了一个 GPU 云实例,其中包含了训练网络所需的一切。我计划用 Theano(或者更新的 TensorFlow)实现一个,并写一篇关于它的博客。

最后,在不了解所有细节的情况下,我使用了增强的梯度树(通常通过 xgboost 实现)来应对一些 Kaggle 挑战。因此,我计划学习更多关于这些元模型的知识。