TowardsDataScience 博客中文翻译 2019(一百零六)

原文:TowardsDataScience Blog

协议:CC BY-NC-SA 4.0

用自然语言处理和机器学习对 Reddit 帖子进行分类

原文:https://towardsdatascience.com/classifying-reddit-posts-with-natural-language-processing-and-machine-learning-695f9a576ecb?source=collection_archive---------17-----------------------

探索文本转换和分类建模过程。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

Photo by Kevin Ku on Unsplash

在我的上一篇文章中,我向你展示了我使用机器学习来预测房价的数据科学过程(如下)。

[## 使用机器学习来预测房价

在这篇文章中,我将向你展示我使用机器学习来预测房价的数据科学过程。之前…

towardsdatascience.com](/using-machine-learning-to-predict-home-prices-d5d534e42d38)

在这篇文章中,我将带你经历相同的过程,但使用自然语言处理(NLP)和分类建模来分类 Reddit 帖子,从 r/BabyBumpsr/月经。在我开始之前,让我们回顾一下数据科学流程(如下所述),然后是一个有趣的破冰者!

  • 定义问题
  • 收集和清理数据
  • 探索数据
  • 对数据建模
  • 评估模型
  • 回答问题

破冰者!

你能猜到这些帖子(如下图)来自哪个子网站吗?你的选择是r/baby pumpsr/月经。在评论中分享你的猜想吧!

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

定义问题

正如你可能已经猜到的,我的任务是使用机器学习来做你刚才试图做的事情!换句话说,创建一个分类模型,可以区分一篇文章属于两个子条目中的哪一个

这个问题的假设是,一个心怀不满的 Reddit 后端开发人员进入每篇帖子,用“(̿̿ĺ̯̿̿̿̿)".因此,在重新分配每个帖子的子编辑字段之前,任何子编辑链接都不会填充帖子。

你可能已经收集到了,r/BabyBumps 和 r/月经中的帖子肯定会有很多交叉。例如,想象一下女性在任一渠道谈论对食物的渴望、痉挛或情绪波动。我喜欢挑战,所以我特意挑选了两个密切相关的子编辑,因为我想看看我如何利用自然语言处理和机器学习来准确地将帖子重新分类到它们各自的子编辑中。

*破冰者的答案可以在下一节最后一句找到,但是继续读下去,看看你是不是比我构建的算法更聪明!

收集和清理数据

我的数据获取过程包括使用requests库循环通过请求,使用 Reddit 的 API 提取数据,这非常简单。要获得来自/r/月经的帖子,我只需添加。json 到 url 的末尾。Reddit 只为每个请求提供 25 个帖子,而我想要 1000 个,所以我重复了 40 次这个过程。我还在循环末尾使用了time.sleep()函数,以允许请求之间有一秒钟的间隔。

我的 for 循环输出了一个嵌套的 json 字典列表,我对其进行了索引,以提取我想要的功能,帖子文本标题,同时将它们添加到两个 Pandas 数据帧中,一个用于 r/baby bump 相关的帖子,另一个用于 r/月经相关的帖子。

在我的文章进入各自的数据框架后,我检查了重复值和空值,这两种情况都发生了。对于重复的值,我通过使用drop_duplicates() 函数将其删除。空值只出现在我的 Post Text 列中,当 Reddit 用户决定只使用标题字段时就会出现这种情况。我决定不删除空值,因为我不想在我的 Title 专长的附随行中丢失有价值的信息,所以我用唯一的任意文本填充空值。

在清理和连接我的数据之后,我的最终数据帧包含 1818 个文档(行)和 3 个特征(列)。第三个特性是我的目标,其中类 1 (r/BabyBumps)的类平衡为 54%,类 0(r/月经)的类平衡为 46%——破冰回答!

探索数据

我创建了一个单词云,因为它们很有趣,而且我们正在处理文本数据!

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

这个词云包含来自两个子编辑的 100 个词。我生成它是为了直观地了解单词的频率(越大/越粗的单词频率越高)以及它们在子编辑中的共性如何影响我的模型;或者,如果像“月经杯”这样的词/短语出现频率中等,并且很可能只出现在或主要出现在 r/月经帖子中,它可能会对我的模型有利。

对数据建模

我通过创建我的 Xy 并将我的数据分成训练集和测试集来开始我的建模过程。然后,我继续我的特征工程过程,为我的文章文本标题特征实例化两个 CountVectorizers。CountVectorizer 将文本文档(文本数据行)的集合转换为令牌计数的矩阵。我传递给它们的超参数是:

  • stop_words=‘english’ ( 帖子正文 & 标题)
  • strip_accents=‘ascii’ ( 帖子正文 & 标题)
  • ngram_range=(1,6),min_df=.03 ( Post Text )
  • ngram_range=(1,3),min_df=.01 ( Title )

停用字词删除英语中经常出现的字词。去除重音符号删除重音符号并执行其他字符规范化。Min_df 忽略文档频率严格低于给定阈值的术语。

一个 n-gram 就是一排 n 个单词组成的字符串。例如,如果您有一个包含“我爱我的猫”字样的文本文档—将 n-gram 范围设置为(1,2)将产生:“我爱|爱我的|我的猫”。拥有 n-gram 范围有助于为模型提供更多关于我输入的文本的上下文。

我假设将 Title 特性设置为(1,4)的 n 元语法范围将会清除噪声,并且通过添加更多的上下文对我的模型更有帮助。我仍然设置了一个温和的 min_df 来帮助清除任何额外的噪音。我对我的帖子文本特性做了类似的假设,尽管我给了它一个更高的 n-gram 范围,因为帖子文本往往更长。

这产生了 393 个特征,我把它们输入到下面列出的两个模型中。我构建了四个函数来运行每一对模型,grid 搜索了几个超参数,以找到最适合我的最终模型的超参数。

  • 逻辑回归

差异在于惩罚解算器参数。“newton-cg”、“lbfgs”和“sag”解算器仅处理 L2 惩罚(山脊正则化),而“liblinear”和“saga”处理 L1(套索正则化)。

  • 决策树和随机森林

这两个模型的差异是标准参数。一个被设置为“基尼”(基尼不纯),而另一个被设置为“熵”(信息增益)。

  • 多项式朴素贝叶斯

变量的差异是 fit_prior 参数,它决定是否学习类先验概率。如果为假,将使用统一的先验。一个设置为真,而另一个设置为假。

评估模型

我的第二个多项式朴素贝叶斯模型表现最好。最佳参数为 alpha = 0,fit_prior=False。在训练数据上的准确率为 92.4%,在未见过的数据上的准确率为 92.2%。这意味着我们的模型是轻微的,可能是不合理的过度拟合。这也意味着我们 92.2%的帖子会被我们的模型准确分类。

回答问题

考虑到收集的数据量很小,使用的特征量也很少,多项式朴素贝叶斯模型是最突出的。它很好地处理了看不见的数据,平衡了偏差和方差之间的权衡,是八个模型中最好的,所以我会用它来对 reddit 帖子进行重新分类。

然而,如果给我更多的时间和数据来回答这个问题,我会推荐两件事:1)花更多的时间在当前的功能上(例如设计一个单词长度功能)和 2)探索新的功能(例如 upvotes 或 post comments)。

查看一下 我的代码 (出于可读性和组织性的目的,我将其分成 3 个 Jupyter 笔记本——收集、清理/探索和建模)和 我的演示文稿 。一如既往,请评论反馈和问题。感谢阅读!

用机器学习对社会流动性进行分类

原文:https://towardsdatascience.com/classifying-social-mobility-with-machine-learning-fe6abcf0dcb2?source=collection_archive---------16-----------------------

使用管道和网格搜索交叉验证的 Python 中从头到尾的监督机器学习分类练习

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

Photo by Hannah Morgan on Unsplash

这篇博文将通过使用管道和网格搜索交叉验证,带您完成 Python 中的机器学习分类练习。作为一名数据科学家,我对数据如何帮助解决经济、社会和政治不平等问题深感兴趣,因此我决定使用一些基于社会的数据来解决这个分类问题。

我们正在创建的模型将采用特征(如收入水平、教育、种族构成等。)作为特征,尝试根据在这些位置长大的儿童的向上社会流动性,对美国各地的数千个人口普查区域进行分类。这种分类可能有助于确定低收入家庭的机会领域,并有助于为公共政策和资源分配提供信息。

我们将使用由哈佛大学研究人员和政策分析师组成的小组 Opportunity Insights 提供的数据。我使用的数据来自两个不同的数据集,来自他们名为机遇图谱:描绘社会流动性的童年根源的项目——我强烈建议对这个话题感兴趣的人查看一下。

在我们的机器学习过程中,我们将清理数据,进行一些特征工程,然后使用管道来找到最佳类型的监督模型。然后,我们将再次使用管道和网格搜索交叉验证来微调我们模型的超参数,以改进它。最后,我们将访问我们的模型的评分和结果,同时在看不见的数据上测试它。

开始吧!

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

Photo by Andrew Wulf on Unsplash

1.准备数据

我们所有的数据科学家都知道,数据在大多数情况下是一种混乱的东西!

我们的第一步是加载和清理数据,然后用一些特征工程为建模做准备。这往往是数据科学过程中最耗时的步骤,但是在您认为您的数据已经可以进行建模之前,应该进行良好的探索性数据分析。至少,您会希望查看每个列的内容和数据类型,检查任何空值和异常值,查看数据分布,并检查特性之间的相关性。对于大多数机器学习模型来说,标准化也是至关重要的(我发现对标准化的这种解释非常清晰和有用)。但是,在我们的流程中,我们将进一步将这一步骤纳入我们的管道中。

在我们的练习中,我们还将把这些特性连接到另一个数据框架,在那里我们将通过一些特性工程来计算我们的目标变量。

作为我的第一步,我喜欢在笔记本的开头加载所有我需要的库。我们将使用 Pandas、Matplotlib 和 Seaborn 进行一些可视化,并严重依赖于几个非常有用的 Scikit-Learn 库,它们涵盖了我们大多数的机器学习需求。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

Dataframe downloaded from Opportunity Insights

对于一个机器学习项目,我们有一个合理大小的数据集,超过 74K 行和 38 列,大部分是数字。所有栏目的完整描述可在人口普查区域的社区特征的自述文件中找到,但大部分是自我解释的。让我们看看所有的列。我们可以使用df.info()一次查看数据类型和空值,但是我喜欢用df.dtypes分别检查数据类型,然后用一个简单的计算检查每一列中 nan 的百分比:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

每一行都由人口普查区域、县和州标识,因此为了使标识符唯一,我们将把这三者组合成一个唯一的“ID”列。我们还将选择删除 nan 百分比高的列。

然后,我们想看看我们的数字数据的主要统计。因为我们有 38 列,如果我简单地调用df.describe(),熊猫不会显示所有的列。有几种方法可以解决这个问题,并能够可视化所有列的统计信息,但我个人认为最简单的方法是简单地分割 dataframe 并调用 describe 方法两次(或更多次)。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

最后,在深入特性工程之前,我将通过调用df.hist()来看一下总体分布。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

就规模而言,各列之间存在明显的差异,但如前所述,扩展将包括在我们的管道中。我们也没有任何分类特征,因此我将在我们的特征中执行的唯一工程是获取我们多年来测量的数据,这些数据不是基于份额的,并计算它们的百分比方差/变化,因为这在分析区域特征时对我们更有意义(这些年来区域是否有所改善,用什么方法来衡量?).

现在,我们将引入目标变量的数据。鉴于这是一个非常大的文件,我们将只加载数据集中必要的列按人口普查区域、种族、性别和父母收入百分比列出的所有结果。为了计算社会流动性,我们将按人口普查区域使用父母和子女的收入等级列,然后当父母和子女的收入差等于或小于零时,将它们标记为 0,当收入等级差为正时,将它们标记为 1(意味着向上的社会流动性)。在这之后,我将合并两个数据帧,通过惟一 ID 列匹配区域。

在我们的一些列中仍然有高达 3.6%的空值,但是因为每一行都代表美国的一个特定地理区域,所以我没有删除它们,而是用列中值替换它们。

最后,我们将看看相关性,以及我们的目标结果的平衡。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

Seaborn correlation matrix

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

我们的目标足够平衡,所以在分割数据之前,我们不需要做任何平衡方法。

将数据分为训练集、验证集和测试集

总是有必要将数据分成定型集和测试集,以便您可以使用一部分数据来定型模型,一部分数据来测试模型。我还喜欢将训练集进一步分为训练和验证,这样我就可以在微调模型时使用验证数据,而只在我觉得我的模型达到最佳状态后才保留看不见的测试数据。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

Checking percentage of outcomes in each dataset

太好了,我们现在有三个结果平衡的数据集可以训练了!

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

Photo by Markus Spiske on Unsplash

2.拟合、评估和调整模型

我们正在研究一个有监督的二元分类问题,因此我们可以使用几种不同类型的模型来得到我们的结果。我们也没有太大的数据集,这在处理成本方面释放了一些约束。因为我们有这样的自由,没有太多的计算需求,我们将通过几个不同类型的模型,看看他们的表现如何。

选择模型

我将创建一个管道,并为几种不同类型的模型运行一个普通模型,以查看每种模型如何处理我们的数据。我的管道将缩放数据,并在一个循环中训练每个模型。然后,我们将使用验证数据集来检查每个模型的总体准确性,以进行比较,并决定哪一个模型能够以最少的调整为我们的数据提供最佳结果。

这是我们的结果:

KNeighborsClassifier(algorithm='auto', leaf_size=30, metric='minkowski',
           metric_params=None, n_jobs=None, n_neighbors=5, p=2,
           weights='uniform')
model score: 0.8420469083155651
----------------
SVC(C=1.0, cache_size=200, class_weight=None, coef0=0.0,
  decision_function_shape='ovr', degree=3, gamma='auto_deprecated',
  kernel='rbf', max_iter=-1, probability=False, random_state=None,
  shrinking=True, tol=0.001, verbose=False)
model score: 0.8640511727078891
----------------
LinearSVC(C=1.0, class_weight=None, dual=True, fit_intercept=True,
     intercept_scaling=1, loss='squared_hinge', max_iter=1000,
     multi_class='ovr', penalty='l2', random_state=None, tol=0.0001,
     verbose=0)
model score: 0.8436673773987207
----------------
NuSVC(cache_size=200, class_weight=None, coef0=0.0,
   decision_function_shape='ovr', degree=3, gamma='auto_deprecated',
   kernel='rbf', max_iter=-1, nu=0.5, probability=False, random_state=None,
   shrinking=True, tol=0.001, verbose=False)
model score: 0.8354797441364605
----------------
DecisionTreeClassifier(class_weight=None, criterion='gini', max_depth=None,
            max_features=None, max_leaf_nodes=None,
            min_impurity_decrease=0.0, min_impurity_split=None,
            min_samples_leaf=1, min_samples_split=2,
            min_weight_fraction_leaf=0.0, presort=False, random_state=None,
            splitter='best')
model score: 0.8001705756929638
----------------
RandomForestClassifier(bootstrap=True, class_weight=None, criterion='gini',
            max_depth=None, max_features='auto', max_leaf_nodes=None,
            min_impurity_decrease=0.0, min_impurity_split=None,
            min_samples_leaf=1, min_samples_split=2,
            min_weight_fraction_leaf=0.0, n_estimators=10, n_jobs=None,
            oob_score=False, random_state=None, verbose=0,
            warm_start=False)
model score: 0.8484434968017057
----------------
AdaBoostClassifier(algorithm='SAMME.R', base_estimator=None,
          learning_rate=1.0, n_estimators=50, random_state=None)
model score: 0.8504904051172708
----------------
GradientBoostingClassifier(criterion='friedman_mse', init=None,
              learning_rate=0.1, loss='deviance', max_depth=3,
              max_features=None, max_leaf_nodes=None,
              min_impurity_decrease=0.0, min_impurity_split=None,
              min_samples_leaf=1, min_samples_split=2,
              min_weight_fraction_leaf=0.0, n_estimators=100,
              n_iter_no_change=None, presort='auto', random_state=None,
              subsample=1.0, tol=0.0001, validation_fraction=0.1,
              verbose=0, warm_start=False)
model score: 0.8581663113006397
----------------
XGBClassifier(base_score=0.5, booster='gbtree', colsample_bylevel=1,
       colsample_bytree=1, gamma=0, learning_rate=0.1, max_delta_step=0,
       max_depth=3, min_child_weight=1, missing=None, n_estimators=100,
       n_jobs=1, nthread=None, objective='binary:logistic', random_state=0,
       reg_alpha=0, reg_lambda=1, scale_pos_weight=1, seed=None,
       silent=True, subsample=1)
model score: 0.8575692963752666
----------------

我们的结果表明,我们可能会将非线性支持向量机视为分类问题的最佳模型。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

Photo by Scott Webb on Unsplash

通过管道和 CV 网格搜索进行超参数调谐

现在,让我们尝试通过对超参数进行一些调整来改善我们的模型结果。我现在想在这个模型上运行另一个管道,进行网格搜索和交叉验证,以测试 C 和 gamma 参数的一些值,并找到为我们的模型提供最佳结果的值。

我计时是因为这样的网格搜索需要一段时间来适应。在我的机器上,大约需要 36 分钟。例如,如果您需要重新启动内核,您可以做一件事来避免再次运行这样的长网格搜索,那就是保存它,然后重新加载它:

我们现在可以访问用grid_svm.best_params_找到的最佳参数。在我们的模型中,最佳值是C: 10, gamma: 0.01

评估模型

让我们用这些最佳参数来检查我们的模型的准确性分数,包括训练数据和验证数据。

对于训练数据,我们得到 0.8749173721133549 的分数,对于验证数据,我们得到 0.8622601279317698 的分数。这表明我们的模型没有过度拟合,并且我们的模型分类准确率略高于 86%。

我们还可以通过查看精确度、召回率和 F1 分数来进一步分析模型的性能。一个简单的方法是打印分类报告。我们也可以打印混淆矩阵来查看我们的真和假阳性和阴性的实际数字。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

Classification report from validation data

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

Confusion matrix from validation data

在这个图中,我们可以看到深蓝色的真正的负面和浅蓝色的真正的正面。浅色表示我们的分类模型出错的标签。这是绝对值,显示了我们的准确度、精确度、召回率和 F1 分数用于生成其值的数字。

在所有的超参数调整之后,我们还可以使用看不见的测试数据来确认我们的模型的性能。将分数与测试数据进行比较,我们得到一个0.8638782751091703的准确度分数。这是我们的分类报告和测试数据的混淆矩阵:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

Classification report from test data

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

Confusion matrix from test data

我们有类似的结果,所以我们可以确认我们的模型的性能。

当我们在处理一个社会项目时,由于人类和社会条件的复杂性,预计我们的模型将无法以近乎完美的精度预测/分类社会流动性。这意味着,除了他们成长环境的构成,许多不同的因素影响着孩子的结果。在社交领域,最大的挑战之一是能够收集正确的数据。例如,在孩子上学的过程中有一个激励性的老师,可能会产生相关的影响,甚至有助于预测积极的结果,但这几乎不可能用统计数据来衡量。

考虑到挑战和制约因素,我们认为我们的模型在正确分类方面仍然做得相当好,并且除了其他资源之外,还可以有助于识别社会流动性的机遇和挑战。

我希望这个过程对任何学习监督机器学习以及如何使用管道和网格搜索的人有所帮助!请留下您的评论,并在此使用完整的笔记本随时查看存储库。

基于统计文本分析和机器学习的空间组织命名实体分类

原文:https://towardsdatascience.com/classifying-spacys-org-named-entities-with-statistical-text-analysis-and-machine-learning-7e99ed7a58c3?source=collection_archive---------24-----------------------

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

介绍

最近,我们用 spaCy 标记了许多文本,以便提取组织名称。我们面临一个问题:许多用 spaCy 标记的实体根本不是有效的组织名称。实际上,这并不是空间本身的问题:所有提取的实体,乍一看,确实像组织名称。如果我们更多地训练空间模型,效果会更好。然而,这种方法需要一个大语料库的适当标记的数据,其中还应包括一个适当的背景。因此,我们需要一个更简单的解决方案来过滤掉错误的数据。

我们决定尝试解决这个问题的方法是基于简单的考虑:如果我们查看文本,可能会有一些在组织名称旁边使用更频繁的单词列表。比如像 *corporation,ltd,co,Llc,foundation,group,*等词汇。我们需要的是:
a)足够大的单词列表以获得更好的数据质量
b)确保这些单词确实出现在现实世界中

收集单词统计信息

为了测试我们的假设,我们收集了一些单独单词的使用频率,以及一些人工标注的公司名称。这里我们应该提到,我们实际上尝试了两种方法。第一个是直接计算一个特定的词在一个特定的命名实体中被提及的次数。然而,在我们用这种数据建立了模型之后,事实证明这种模型的表现并不好。它勉强超过了 50%的准确率阈值,召回率下降了近 40%。所以我们修改了这种方法,它确实显示了更好的结果。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

Statistics collecting schema

为了改进我们的方法,我们考虑了单词之间的距离。我们是这样做的:我们仍然收集一个词的频率,但我们也收集这个词的加权频率(权重)。重量的定义如下:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

Word weight from distance calculation formula

其中 dist 是以单词和命名实体之间的单词数度量的整数距离, α —参数,增加或降低单词在更高距离上的权重损失速度。在试验了一小组例子后,我们选择 α 为 15。

因此,如果一个词经常接近一个命名实体,它的加权频率就会相对较高,反之则较低。我们还决定忽略距离命名实体超过 30 个单词的单词,因为它们不太可能与该命名实体相关。下面是 Python 中的代码,我们用它收集了所有独立单词的统计数据。它可以稍微改进,但我们认为它对于一次性使用已经足够好了。

**import** re
**import** numpy **as** np**from** bs4 **import** BeautifulSoup
**from** IPython **import** display**def** **dist_weight**(x, alpha = 15):
    """you may choose different alpha and see what happens. 
    """
    **return** np.exp(-(x ** 2) / alpha)word_re = re.compile("(?:^|[\-\\|\,\.\(\)]|\s+)([a-zA-Z][a-zA-Z]+)") #regex for finding words in text**def** **append_words_stats**(words_stats, text, ne):
    words = [(text[match.span(1)[0] : match.span(1)[1]].lower(),
              match.span(1)[0],
              match.span(1)[1],
              idx) **for** idx, match **in** enumerate(word_re.finditer(text))] # find all words in text and their position

    **for** ne_match **in** re.finditer(ne[0].lower(), text.lower()): # for all positions of named entity in text
        ne_start = ne_match.span(0)[0]
        ne_word_num = **None** # There were cases when named entity was present in a text but was not a word in terms of the above regex.
       # Since we are not interested in absolute precision, we decided to bypass this case with such a simple way

        **for** word **in** words: 
            **if** word[1] <= ne_start < word[2]:
                ne_word_num = word[3]
                **break**

        **if** ne_word_num:        
            **for** word **in** words:
                dist = abs(ne_word_num - word[3])
                **if** dist > 30:
                    **continue**

                **if** word[0] **not** **in** words_stats:
                    words_stats[word[0]] = {
                        'org' : 0,
                        'org_f' : 0,
                        'non_org' : 0,
                        'non_org_f' : 0,
                    } ne_type = ("non_" **if** ne[1] == 0 **else** "") + "org"
                words_stats[word[0]][ne_type] += dist_weight(dist)
                words_stats[word[0]][ne_type + "_f"] += 1

words_stats = {}**for** idx, filename **in** enumerate(filenames):
    **with** open(filename, "r", encoding = 'utf-8') **as** file:
        soup = BeautifulSoup(file.read())

        **for** p **in** soup.find_all("p"):
            **for** ne **in** nes:
                **if** ne[0].lower() **in** p.text.lower():
                    append_words_stats(words_stats, p.text, ne)

注意,这里的nes是一个双元素元组列表,其中第一个元素是一个命名实体文本本身,第二个元素是一个二进制值,表示一个命名实体是否是一个组织名称(我们手工标记了它们)。filenames 是我们在搜索每个命名实体时保存的文件列表。

处理完所有文件后,我们将收集的统计数据转换成 pandas DataFrame — words_stats_df(参见下面的代码)。然后我们创建了两个新列,分别是 1000 次使用的单词的平均权重,以及组织和非组织的名称。然后,对于每个单词,我们计算一个值words_stats_df[“ir”] ——我们的好奇率。好奇率显示了某个词平均有多少次更接近组织,而不是非组织。当然,很多数据包含零,所以在计算新列时inf 或零会出现。为此,我们将计算值传递给一个带偏移量的 sigmoid 函数。因此,如果某个单词几乎同样可能与组织和非组织的名称一起使用,则其好奇率将接近 0.5。有趣的单词是那些好奇率接近 1 或 0 的单词。

**import** pandas **as** pdwords_stats_df = pd.DataFrame\
.from_dict(words_stats,
           orient = "index",
           columns = [
                       'org', 'org_f',
                       'non_org', 'non_org_f',
                     ])\
.reset_index()\
.rename(str, columns = {'index' : 'word'})words_stats_df["org_n"] = (words_stats_df["org"] / words_stats_df["org_f"]) * 1000                  
words_stats_df["non_org_n"] = (words_stats_df["non_org"] / words_stats_df["non_org_f"]) * 1000**def** **sigmoid**(x, alpha = 1, offset = 0):
    **return** 1 / (1 + np.exp(-alpha * (x - offset)))words_stats_df["ir"] = ((words_stats_df["org"] * words_stats_df["non_org_f"]) / \
                       (words_stats_df["org_f"] * words_stats_df["non_org"]))   \
                       .apply(**lambda** x: sigmoid(x, 5, 1))words_stats_df.sort_values("ir", ascending = **False**, inplace = **True**)

如果我们看一下收集的数据,我们可以看到:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

Collected statistics example

(为了更好地分析,我们收集了一些额外的统计数据)

这里有一些进步:我们可以看到,像*子公司、合并、公司、法律、有限公司、*等词真的在列表的顶部。这意味着,我们正朝着正确的方向前进。现在,有了这些数据,我们需要选择一个经常与组织名称一起使用的单词列表。一个小注意:这个列表应该是一个不带偏见的单词列表,例如,不能与公司的产品或活动有任何关系。否则,这些话将与其他组织不一致。

在简单浏览了这个数据集之后,我们选择了一个包含 100 个我们认为是组织特征的单词的列表,以及一个包含 26 个我们认为是非组织特征的单词的列表:

orgs_selected_words = [
    'merger', 'conglomerate', 'corp', 'subsidiary', 'corporation', 'stock', 'founder', 'merged', 'acquired', 'subsidiaries', 'legal', 'exchange' , 'ltd', 'group', 'co', 'largest', 'renamed', 'revenues', 'supplier', 'venture', 'member', 'interest', 'owns', 'property', 'country', 'inc', 'firm', 'industries', 'division', 'partnership', 'shares', 'owned', 'operations', 'name', 'investment', 'facilities', 'changed', 'manufactured', 'general', 'revenue', 'ownership', 'management', 'cash', 'meeting', 'ranked', 'separated', 'shareholder', 'interests', 'affiliates', 'engaged', 'parent', 'reserved', 'rights', 'patents', 'capitalization', 'enlarge', 'complaining', 'alleged', 'proceed', 'anticipates', 'mergers', 'acquirer', 'wholly', 'demerged', 'merge', 'handing', 'european',  'nasdaq', 'german', 'purchased', 'france', 'biggest', 'investments', 'commission', 'europe', 'managed', 'assets', 'fund', 'senior', 'deal', 'funds', 'traded', 'acquisitions', 'charges', 'subsequent', 'wealth', 'hired', 'leverage', 'journal', 'early', 'bank', 'working', 'ordered', 'world', 'employee', 'contact', 'share', 'firms', 'shortage', 'founded',    
]non_orgs_selected_words = [
    'consumer', 'home', 'buy', 'testing', 'reports', 'offering', 'offer', 'offers', 'special', 'reality', 'followed', 'failed', 'businesses', 'community', 'school', 'purchases', 'complex', 'detailed', 'buying', 'newer', 'events', 'enabled', 'alternative', 'advance', 'upcoming', 'releases',
]selected_words = orgs_selected_words + non_orgs_selected_words

收集训练\测试数据

现在我们有了一个选择单词的列表,我们需要为我们的最终模型收集训练和测试数据。这里有一些代码样本,也是用 Python 写的,我们用来收集数据。

为数据采集准备数据帧:

ws_df = pd.DataFrame()ws_df["ne_id"] = 0.
ws_df["is_org"] = 0.**for** selected_word **in** selected_words:
    ws_df[selected_word + "_w"] = 0.
    ws_df[selected_word + "_f"] = 0.**for** ne **in** nes:
    ws_df.loc[ne[2]] = 0
    ws_df.at[ne[2], "is_org"] = ne[1]ws_df["org_id"] = ws_df.index

这里的nes 是一个由三个元素组成的元组列表:第一个和第二个元素是相同的,与上面的nes相同,第三个元素是一个惟一的命名实体 id。

我们修改了第一个代码片段中的代码来收集数据,所以这里是唯一改变的地方:

**def** **append_selected_words_stats**(ws_df, text, selected_words, ne):
    words = [(text[match.span(1)[0] : match.span(1)[1]].lower(),
              match.span(1)[0],
              match.span(1)[1],
              idx,
              1 **if** text[match.span(1)[0] : match.span(1)[1]].lower() **in** selected_words **else** 0)
             **for** idx, match **in** enumerate(word_re.finditer(text))]

    # ...................

                    **if** dist > 30:
                        **continue**

                    ws_df.at[ne[2], word[0] + "_w"] += dist_weight(dist)
                    ws_df.at[ne[2], word[0] + "_f"] += 1**def** **collect_words_stats**(ws_df, filenames, nes):
    # ....................
                        append_selected_words_stats(ws_df, p.text, selected_words, ne) collect_words_stats(ws_df, train_filenames, nes)

收集数据后,我们将它标准化为各种类型的统计数据:

**def** **preprocess_ws**(ws_df):
    **for** stat_type **in** ["w", "f"]:
        stat_type_cols = [selected_word + "_" + stat_type **for** selected_word **in** selected_words] s = ws_df[stat_type_cols].sum(axis = 1) **for** stat_type_col **in** stat_type_cols:
            ws_df[stat_type_col] /= s

        ws_df.fillna(0, inplace = **True**)

    print(ws_df.values.shape)preprocess_ws(ws_df)

并分成训练\测试:

**from** sklearn.model_selection **import** train_test_splittrain_nes, test_nes = train_test_split(nes, test_size = 0.25)train_nes = [ne[2] **for** ne **in** train_nes]
test_nes  = [ne[2] **for** ne **in** test_nes]train_ws_df = ws_df[ws_df["ne_id"].isin(train_nes)]
test_ws_df = ws_df[ws_df["ne_id"].isin(test_nes)]

设计模型

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

Neural Network Architecture

实际上,在尝试机器学习方法之前,我们已经尝试了一个简单的带有手动权重计算的二元回归模型(使用线性回归分析公式)。但是这个模型没有给出任何好的结果,所以我们决定尝试一个更复杂的模型——神经网络。

首先,我们为模型准备了数据:

**from** sklearn.preprocessing **import** OneHotEncodercols_order = [selected_word + "_w" **for** selected_word **in** selected_words] + \
             [selected_word + "_f" **for** selected_word **in** selected_words]x_train = train_ws_df[cols_order].values
x_test  = test_ws_df[cols_order].valuesenc = OneHotEncoder()
enc.fit(train_ws_df["is_org"].values.reshape((-1, 1)))
y_train = enc.transform(train_ws_df["is_org"].values.reshape((-1, 1))).toarray()enc = OneHotEncoder()
enc.fit(test_ws_df["is_org"].values.reshape((-1, 1)))
y_test = enc.transform(test_ws_df["is_org"].values.reshape((-1, 1))).toarray()

然后,我们设计了模型(我们用一个*SGD* 优化器和一个binary_crossentropy 损失函数来编译它):

**from** keras.models **import** Model
**from** keras.layers **import** Input, Denseinput = Input((x_train.shape[1],))x = inputx = Dense(100, activation = 'relu')(x)
x = Dense(75, activation = 'sigmoid')(x)output = Dense(1, activation = 'sigmoid')(x)model = Model([input], [output])model.summary()"""
_________________________________________________________________
Layer (type)                 Output Shape              Param #   
=================================================================
input_1 (InputLayer)         (None, 252)               0         
_________________________________________________________________
dense_1 (Dense)              (None, 100)               25300     
_________________________________________________________________
dense_2 (Dense)              (None, 75)                7575      
_________________________________________________________________
dense_3 (Dense)              (None, 1)                 76        
=================================================================
Total params: 32,951
Trainable params: 32,951
Non-trainable params: 0
_________________________________________________________________
"""

事实上,我们尝试了一些模型架构。但是,从训练结果来看,测试数据存在某种阈值,模型无法通过。我们选择了过度配合最少的架构。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

在训练模型时,我们发现了一件有趣的事情:无论架构如何,模型都不会以恒定的学习速率收敛。只有当我们先用低学习率训练它几个时期(时期 1-100),然后用高学习率训练它几个时期(时期 100-200),再用低学习率训练它(时期 200-300)时,它才会收敛。如果我们从一开始就保持一个恒定的低或高的学习率,模型的准确性是稳定的,并不比随机猜测好。

分析结果

可能有几个主要的原因导致我们没有达到模型的任何真正高的精度或召回值。下面是对数据和结果进行简要分析后得出的结论:

  1. 精选单词小词典。在一些组织中,所选的单词只被使用了几次,而这个单词本身却和其他组织名称一起被频繁使用。因此,一本更大的精选词汇词典可能会有所帮助。它还必须包括更多的非组织通用词。
  2. 我们只关注美国的组织。这就是为什么我们只使用英语单词和搜索词。然而,有一些组织(几乎占总数的 7%)在他们的名字周围没有任何被选中的单词。这是因为这些组织返回的页面大多不是英文的。因此,这一因素可以通过扩大语言范围,翻译选定的单词或在其他语言中使用不同的单词来消除。
  3. 更多带标签的数据。我们只标记了大约 1600 条记录,其中大约 750 条是组织名称。包括如何在文本摘要中呈现组织名称的各种方式,这个数量可能不足以实现高模型质量。
  4. 不平衡的数据集。我们希望选择与组织的活动和产品没有直接关系的词。然而,这似乎不是我们最好的决定。例如,我们随机选择了一家主要从事银行和投资的大公司。因此,我们需要包含一些与此活动相关的词,以便从我们的模型中获得更好的结果。
  5. 总体数据更多。这一点值得怀疑,因为我们从大约 5.5GBs 的 HTML 页面中收集了 train\test 数据,这似乎很多。但是由于默认情况下 Bing 返回的搜索结果的数量,每个命名实体只有 8 个 HTML 页面。

尽管如此,对于非组织的较低召回的代价(在测试中接近 70%,这很可能通过解决上述问题来提高),我们得到了 75%的准确率。这比我们在没有机器学习模型(47%的准确率)的情况下仅使用这种方法所能实现的要好得多。

结论

模型的结果表明,所描述的方法可以应用于命名实体分类或过滤。当模型实现或命名实体标记不能处理像 HTML 页面这样的复杂情况时,这可能是有用的,在 HTML 页面中,不是所有的命名实体都有适当的文本上下文。

承认

我要感谢我的同事们安迪·博西米科拉·科兹连科维亚奇·博西亚历克斯·西姆基夫富有成效的讨论、合作和有益的建议,以及整个 MindCraft.ai 团队的持续支持。

清理复杂的数据集,以便使用推荐算法进行建模

原文:https://towardsdatascience.com/clean-a-complex-dataset-for-modelling-with-recommendation-algorithms-c977f7ba28b1?source=collection_archive---------6-----------------------

我对购物篮分析的看法——第 1 部分,共 3 部分

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

Photo by Tina Bosse on Unsplash

O 远景

最近我想学习一些新的东西,并挑战自己进行端到端的市场篮子分析。为了继续挑战自己,我决定将我的努力成果展示给数据科学界。还有什么论坛比我最喜欢的数据科学博客更适合我的第一个系列帖子呢!

这是三个岗位中的第一个,排列如下:

第 1 部分 :探索并清理适合用推荐算法建模的数据集 第 2 部分 :用 推荐器应用各种产品推荐模型 labR 包第 3 部分**

介绍

购物篮分析或 MBA (也称为亲和力分析)是一套数据挖掘和数据分析技术,用于了解客户的购物行为,并揭示他们购买的产品之间的关系。通过分析零售购物篮构成、交易数据或浏览历史等,这些方法可用于向客户推荐他们可能感兴趣的商品。这种推荐可以提高客户忠诚度,通过帮助客户更快地找到相关产品来提高转化率,并通过建议额外的项目或服务来促进交叉销售和向上销售。

推荐系统广泛应用于多个行业。在线零售商亚马逊使用这些方法向他们的客户推荐其他人经常一起购买的产品(“买了这件商品的客户也买了……”)。自 2015 年以来,每周一娱乐公司 Spotify 都会根据用户的消费历史,向他们的用户推荐 30 首他们从未听过的歌曲。2009 年,网飞举办了一场著名的竞赛(Netflix 奖),向改进电影推荐系统预测的团队颁发了 100 万美元的大奖。

购物篮分析还可应用于以下领域:

  • 晋升: MBA 可能会给出如何安排晋升日程的提示。当两件商品(比如手电筒&电池)经常一起购买时,零售商可能会决定只促销一件商品来促进另一件的销售。
  • ***店铺设计:*店铺布局可以设计成将经常同时购买的商品(如牛奶&麦片、洗发水&护发素、肉类&蔬菜等)放置在一起。)
  • ***忠诚度计划:*当一名顾客在一段时间内不活跃时,系统会提示他/她重新参与基于其购买历史的定制优惠活动。

数据

这个项目的数据来自 UCI 机器学习知识库 ,这是一个大型数据集的在线档案,包括各种各样的数据类型、分析任务和应用领域。

在这个项目中,我使用的是 在线零售 数据集,由伦敦南岸大学工程学院于 2015 年捐赠给 UCI。该数据集包含一家总部位于英国的注册在线零售公司在 2010 年 12 月 1 日至 2011 年 12 月 9 日之间发生的交易。该公司主要销售独特的全场合礼品,他们的许多客户是批发商。

我选择这个特殊的数据集是因为它的"现实生活"性质,这在我在描述字段中找到的许多手动输入的注释和调整代码中有所体现。甚至还有公司员工留下的个人评论,他们可能每天都在使用这个数据库。

有理由假设数据集直接来自公司的数据库,几乎没有改动。根据经验,这与分析师最有可能从他们要进行分析的客户那里接收数据的状态是一致的。

数据准备

*# Importing R libraries
library(data.table)           
library(readxl)               
library(tidyverse)
library(lubridate)
library(skimr)                
library(knitr)                
library(treemap)*

加载和检查数据

*# import raw data file and trim leading and trailing whitespaces
retail <- read_excel("../00_Data/Online Retail.xlsx", 
                        trim_ws = TRUE)*

该数据集由分布在 8 个变量中的 540,000 多个观察值组成。几个描述和几个客户 id丢失,还有一些奇怪的负数数量单价值得调查。同样值得注意的是 InvoiceDate 是 POSIXct 格式,从中可以提取购买日期和时间的信息。

*# First glance at the data
retail %>%  skim()
## Skim summary statistics
##  n obs: 541909 
##  n variables: 8 
## 
## -- Variable type:character ------------------
##     variable missing complete      n min max empty n_unique
##      Country       0   541909 541909   3  20     0       38
##  Description    1454   540455 541909   1  35     0     4211
##    InvoiceNo       0   541909 541909   6   7     0    25900
##    StockCode       0   541909 541909   1  12     0     4070
## 
## -- Variable type:numeric --------------------
##    variable missing complete      n     mean      sd        p0      p25
##  CustomerID  135080   406829 541909 15287.69 1713.6   12346    13953   
##    Quantity       0   541909 541909     9.55  218.08 -80995        1   
##   UnitPrice       0   541909 541909     4.61   96.76 -11062.06     1.25
##       p50      p75  p100
##  15152    16791    18287
##      3       10    80995
##      2.08     4.13 38970
## 
## -- Variable type:POSIXct --------------------
##     variable missing complete      n        min        max     median
##  InvoiceDate       0   541909 541909 2010-12-01 2011-12-09 2011-07-19
##  n_unique
##     23260*

取消

非常方便的 属性信息 告诉我们,如果 InvoiceNo 以字母‘C’开头,则表示取消

*retail %>% 
  filter(grepl("C", retail$InvoiceNo)) %>% 
  summarise(Total = n())## # A tibble: 1 x 1
##   Total
##   <int>
## 1  9288*

分析不需要取消,因此可以删除它们

*retail  <- retail %>% 
  filter(!grepl("C", retail$InvoiceNo))# CHECK: total row count - 532,621*

负数量

当按非正的数量过滤时,描述显示类似于一系列手动输入的注释(如“扔掉”、“卖不出去”、“损坏”、“损坏?”).鉴于单价也被全部设置为零,可以安全地假设这些是调整代码。

*retail %>% 
  filter(Quantity <= 0) %>% 
  group_by(Description, UnitPrice) %>% 
  summarise(count =n()) %>%
  arrange(desc(count)) %>% 
  ungroup()## # A tibble: 139 x 3
##    Description            UnitPrice count
##    <chr>                      <dbl> <int>
##  1 <NA>                           0   862
##  2 check                          0   120
##  3 damages                        0    45
##  4 damaged                        0    42
##  5 ?                              0    41
##  6 sold as set on dotcom          0    20
##  7 Damaged                        0    14
##  8 thrown away                    0     9
##  9 Unsaleable, destroyed.         0     9
## 10 ??                             0     7
## # ... with 129 more rows*

如果这是一个真实的项目,我通常会和提供数据的人一起检查这个假设。在这种情况下,我将简单地删除所有数量为非正的行。

*retail  <- retail %>% 
  filter(Quantity > 0)# CHECK: total row count - 531,285*

非产品库存代码

将我的注意力转向股票代码*,我注意到一些与产品无关的代码(“邮资”、“银行费用”、“礼券”等)。).*

*# Non-product related codes
stc <- c('AMAZONFEE', 'BANK CHARGES', 'C2', 'DCGSSBOY',     'DCGSSGIRL', 'DOT', 'gift_0001_', 'PADS', 'POST')retail %>%  
  filter(grepl(paste(stc, collapse="|"), StockCode))  %>% 
  group_by(StockCode, Description) %>% 
  summarise(count =n()) %>%
  arrange(desc(count)) %>% 
  ungroup()## # A tibble: 19 x 3
##    StockCode    Description                        count
##    <chr>        <chr>                              <int>
##  1 POST         POSTAGE                             1126
##  2 DOT          DOTCOM POSTAGE                       708
##  3 C2           CARRIAGE                             141
##  4 DCGSSGIRL    GIRLS PARTY BAG                       13
##  5 BANK CHARGES Bank Charges                          12
##  6 DCGSSBOY     BOYS PARTY BAG                        11
##  7 gift_0001_20 Dotcomgiftshop Gift Voucher £20.00     9
##  8 gift_0001_10 Dotcomgiftshop Gift Voucher £10.00     8
##  9 gift_0001_30 Dotcomgiftshop Gift Voucher £30.00     7
## 10 gift_0001_50 Dotcomgiftshop Gift Voucher £50.00     4
## 11 PADS         PADS TO MATCH ALL CUSHIONS             4
## 12 POST         <NA>                                   4
## 13 gift_0001_40 Dotcomgiftshop Gift Voucher £40.00     3
## 14 AMAZONFEE    AMAZON FEE                             2
## 15 C2           <NA>                                   1
## 16 DOT          <NA>                                   1
## 17 gift_0001_10 <NA>                                   1
## 18 gift_0001_20 to push order througha s stock was     1
## 19 gift_0001_30 <NA>                                   1*

这些都可以去掉。

*retail <- filter(retail, 
                 !grepl(paste(stc, collapse="|"), StockCode))# CHECK: total row count - 529,228*

描述

现在关注描述字段,有另外 50 个手动输入的注释需要删除。在一个案例中,一名员工甚至向他们的一名同事发泄他们的不满(“艾伦·霍奇不能管理这个部分”),包括拼写错误等等!

*# Additional adjustment codes to remove
descr <- c( "check", "check?", "?", "??", "damaged", "found", 
            "adjustment", "Amazon", "AMAZON", "amazon adjust", 
"Amazon Adjustment", "amazon sales", "Found", "FOUND",
"found box", "Found by jackie ","Found in w/hse","dotcom", 
"dotcom adjust", "allocate stock for dotcom orders ta", "FBA", "Dotcomgiftshop Gift Voucher £100.00", "on cargo order",
"wrongly sold (22719) barcode", "wrongly marked 23343",
"dotcomstock", "rcvd be air temp fix for dotcom sit", 
"Manual", "John Lewis", "had been put aside", 
"for online retail orders", "taig adjust", "amazon", 
"incorrectly credited C550456 see 47", "returned", 
"wrongly coded 20713", "came coded as 20713", 
"add stock to allocate online orders", "Adjust bad debt", 
"alan hodge cant mamage this section", "website fixed",
"did  a credit  and did not tick ret", "michel oops",
"incorrectly credited C550456 see 47", "mailout", "test",
"Sale error",  "Lighthouse Trading zero invc incorr", "SAMPLES",
"Marked as 23343", "wrongly coded 23343","Adjustment", 
"rcvd be air temp fix for dotcom sit", "Had been put aside." )*

过滤掉不需要的条目。

*retail <- retail %>% 
  filter(!Description %in% descr)# CHECK: total row count - 528,732*

最后但同样重要的是,在描述中还有大约 600 个 NAs。

*sum(is.na(retail$Description))
## [1] 584*

鉴于它们的数量很少(约占总数的 0.1%),我将只删除它们。

*retail <- retail %>% 
  filter(!is.na(Description))# CHECK: total row count - 528,148*

客户 ID

CustomerID 中仍然有大量的 NAs,我将保持原样。

*retail$CustomerID %>%  
  skim()
## 
## Skim summary statistics
## 
## -- Variable type:numeric --------------------
##  variable missing complete      n    mean      sd    p0   p25   p50   p75
##         .  131778   396370 528148 15301.6 1709.98 12346 13975 15159 16803
##   p100
##  18287*

正如我将在第二篇文章中讨论的,为了进行分析,我需要以用户项目的格式来安排数据,其中“用户”可以是客户,也可以是订单。鉴于订单几乎是客户的 5 倍,我将在分析中使用InvoiceNo来表示订单,这将产生更丰富的信息集。

*sapply(retail[ ,c('InvoiceNo','CustomerID')], 
               function(x) length(unique(x)))##  InvoiceNo CustomerID 
##      19792       4336*

最后润色

有几件家务事要处理,我准备走了!

*retail <- retail %>%
# Setting 'Description' and 'Country' as factors
  mutate(Description = as.factor(Description)) %>%
  mutate(Country = as.factor(Country)) %>% 
# Changing 'InvoiceNo' type to numeric
  mutate(InvoiceNo = as.numeric(InvoiceNo)) %>% 
# Extracting 'Date' and 'Time' from 'InvoiceDate'
  mutate(Date = as.Date(InvoiceDate)) %>% 
  mutate(Time = as.factor(format(InvoiceDate,"%H:%M:%S")))glimpse(retail)
## Observations: 528,148
## Variables: 10
## $ InvoiceNo   <dbl> 536365, 536365, 536365, 536365, 536365,... 
## $ StockCode   <chr> "85123A", "71053", "84406B", "84029G",... 
## $ Description <fct> WHITE HANGING HEART T-LIGHT HOLDER,...
## $ Quantity    <dbl> 6, 6, 8, 6, 6, 2, 6, 6, 6, 32, 6, 6, 8,... 
## $ InvoiceDate <dttm> 2010-12-01 08:26:00, 2010-12-01 08:26:00,. 
## $ UnitPrice   <dbl> 2.55, 3.39, 2.75, 3.39, 3.39, 7.65, ... 
## $ CustomerID  <dbl> 17850, 17850, 17850, 17850, 17850, ... 
## $ Country     <fct> United Kingdom, United Kingdom, ...
## $ Date        <date> 2010-12-01, 2010-12-01, 2010-12-01,... 
## $ Time        <fct> 08:26:00, 08:26:00, 08:26:00, 08:26:00, ...*

探索数据集

我现在准备看看数据集的不同特性。

人们更经常购买什么物品?

*retail %>% 
  group_by(Description) %>% 
  summarize(count = n()) %>% 
  top_n(10, wt = count) %>%
  arrange(desc(count)) %>% 
  ggplot(aes(x = reorder(Description, count), y = count))+
  geom_bar(stat = "identity", fill = "royalblue", colour = "blue") +
  labs(x = "", y = "Top 10 Best Sellers", title = "Most Ordered Products") +
  coord_flip() +
  theme_grey(base_size = 12)*

心形茶灯座是最受欢迎的单品。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

销量最高的 10 种产品约占公司总销量的 3%

*retail %>% 
  group_by(Description) %>% 
  summarize(count = n()) %>% 
  mutate(pct=(count/sum(count))*100) %>% 
  arrange(desc(pct)) %>% 
  ungroup() %>% 
  top_n(10, wt=pct)## # A tibble: 10 x 3
##    Description                        count   pct
##    <fct>                              <int> <dbl>
##  1 WHITE HANGING HEART T-LIGHT HOLDER  2327 0.441
##  2 JUMBO BAG RED RETROSPOT             2115 0.400
##  3 REGENCY CAKESTAND 3 TIER            2019 0.382
##  4 PARTY BUNTING                       1707 0.323
##  5 LUNCH BAG RED RETROSPOT             1594 0.302
##  6 ASSORTED COLOUR BIRD ORNAMENT       1489 0.282
##  7 SET OF 3 CAKE TINS PANTRY DESIGN    1399 0.265
##  8 PACK OF 72 RETROSPOT CAKE CASES     1370 0.259
##  9 LUNCH BAG  BLACK SKULL.             1328 0.251
## 10 NATURAL SLATE HEART CHALKBOARD      1263 0.239*

人们通常在一天中的什么时候购买?

*retail %>% 
  ggplot(aes(hour(hms(Time)))) + 
  geom_histogram(stat = "count",fill = "#E69F00", colour = "red") +
  labs(x = "Hour of Day", y = "") +
  theme_grey(base_size = 12)*

午餐时间是网上购物的首选时间,大多数订单发生在中午 12 点至下午 3 点之间。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

人们更经常在一周的哪一天购买?

*retail %>% 
  ggplot(aes(wday(Date, 
                  week_start = getOption("lubridate.week.start", 1)))) + 
  geom_histogram(stat = "count" , fill = "forest green", colour = "dark green") +
  labs(x = "Day of Week", y = "") +
  scale_x_continuous(breaks = c(1,2,3,4,5,6,7),
                     labels = c("Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun")) +
  theme_grey(base_size = 14)*

周四是订单高峰,周六没有订单处理。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

每位顾客购买多少件商品?

*retail %>% 
  group_by(InvoiceNo) %>% 
  summarise(n = mean(Quantity)) %>%
  ggplot(aes(x=n)) +
  geom_histogram(bins = 100000,fill = "purple",colour = "black") + 
  coord_cartesian(xlim=c(0,100)) +
  scale_x_continuous(breaks=seq(0,100,10)) +
  labs(x = "Average Number of Items per Purchase", y = "") +
  theme_grey(base_size = 14)*

大多数顾客通常会购买 2 到 15 件商品,高峰期是 2。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

每份订单的平均价值是多少?

*retail %>% 
  mutate(Value = UnitPrice * Quantity) %>% 
  group_by(InvoiceNo) %>% 
  summarise(n = mean(Value)) %>%
  ggplot(aes(x=n)) +
  geom_histogram(bins = 200000, fill="firebrick3", colour = "sandybrown") + 
  coord_cartesian(xlim=c(0,100)) +
  scale_x_continuous(breaks=seq(0,100,10)) +
  labs(x = "Average Value per Purchase", y = "") + 
  theme_grey(base_size = 14)*

大部分订单的价值低于 20,分布显示双峰,一个在 6,另一个更明显,在 17。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

他们向哪些国家出售商品?

*treemap(retail,
        index      = c("Country"),
        vSize      = "Quantity",
        title      = "",
        palette    = "Set2",
        border.col = "grey40")*

六分之五的订单来自英国。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

评论

项目的数据准备和可视化部分到此结束。到目前为止,我已经展示了如何处理一个“真实生活”的数据集来清理它,去掉不需要的元素,改变变量类型,并从现有的变量中创建新的变量。总之,我已经删除了取消*,删除了负的数量单价,删除了描述中的 NAs,并创建了两个新变量日期时间。总共丢弃了 13,761 行(大约是初始计数的 2.5%),数据集现在有 528,148 个观察值。*

代码库

完整的 R 代码可以在我的 GitHub 档案中找到:

参考

原载于 2019 年 3 月 13 日https://diegousai . io*。*

干净的机器学习代码

原文:https://towardsdatascience.com/clean-machine-learning-code-bd32bd0e9212?source=collection_archive---------18-----------------------

面向 ML 工艺的实用软件工程原理

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

为什么我们应该关心干净的机器学习代码(CMLC)?

查看我最新的(正在编写的)关于这个主题的书,其中有代码示例、深入的讨论等等!https://leanpub.com/cleanmachinelearningcode

机器学习(ML)管道毕竟是软件管道。它们充满了不必要的复杂性和重复。这是混合了厚厚的不透明性,刚性和粘性的设计。随着这些问题的出现,ML 故障正以前所未有的速度变得越来越重要。我们已经看到自动驾驶汽车在亚利桑那州撞上行人。我们了解了大规模翻译系统的性别偏见。我们看到了简单的面具如何侵入智能手机的面部识别系统。我们听说过其他“聪明”的系统做出了错误的决定(比如骑士资本)。现在是时候更多地谈论我们在机器学习工艺方面的责任了。

作为一个全球数据科学社区,我们建立的自主系统可能是昂贵的,危险的,甚至是致命的。除此之外,这种 5 到 10 年历史的老工艺缺乏经验。截至 2019 年,美国 40%的数据科学家的经验不足 5 年。作为一个社区,我们正在经历 ML 开发和使用的热潮。这就像 21 世纪初以前的软件工程繁荣一样。这种扩展通过一系列的构造、抽象、框架和工作流表现出来。众多的集成挑战让我们想起了老的软件问题。ML 软件工程实践中出现的一些问题是新的。但是大多数软件工程问题都带有历史的味道。回到软件工程的早期可以帮助解决今天的 ML 问题。

跟随干净代码方法的脚步,我们可以看到直接的相似之处。**我们构建的所有 ML 软件终究是软件。**将原原则中的“软件”替换为“机器学习组件”很有意思。它给老把戏带来了新的变化。

所以首先,干净的机器学习代码有什么好处?这里至少有两个。如果你对这些好处感兴趣,请继续阅读:

1.干净的代码降低了机器学习管道的变更成本。

2.干净的代码可以提高对机器学习管道变化的最佳响应能力。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

Clean Machine Learning Code driving motivation. Image from [1].

熟悉 Clean Code/Architecture 书籍的读者会注意到一件事。我是从那些文本中推断出来的。这是为了揭示 ML 和传统软件工程中的横切关注点。请去买书支持原作者。现在我们已经解决了这个问题,接下来是干净的机器学习代码的主要“改编”原则。

原则和华夫饼

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

我们真的不需要原则、规则、约束和华夫饼干机。嗯,也许是华夫饼机。但是接下来的几点是我们可以用来推广干净代码的抽象原则。这些技术有时似乎过于极端。然而,它们比多作者模型管道更容易理解。再加上 5 个不同的团队成员,每个人都有 10 个假设。让我们看看这些组件级的原则是什么,以及它们如何有益于实践。

1.松耦合

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

当两个机器学习组件中的至少一个使用另一个时,这两个机器学习组件是耦合的。这些 ML 组件相互了解得越少,它们之间的耦合就越松散。与强耦合组件相比,与其环境松散耦合的 ML 组件更容易被更改或替换。

2.高内聚力

内聚性是整体的机器学习元素属于一起的程度。想想花生酱和果冻。单个机器学习类中的方法和字段,以及 ML 组件的类,应该具有高内聚性。ML 类和 ML 组件中的高内聚导致更简单、更容易理解的机器学习代码结构和设计。这类似于单一责任原则,但在组件级别。出于同样的原因同时发生变化的事物应该归为一类。由于不同原因或在不同时间发生变化的 ML 组件应该分开保存。

3.变化是局部的

ML 软件系统通常需要长时间的维护、扩展和更改。保持局部变化降低了机器学习管道的相关成本和风险。保持 ML 组件的局部变化意味着在设计中存在变化不能跨越的边界。

4.这很容易去除

我们通常通过添加、扩展或更改组件功能来构建机器学习软件。然而,移除 ML 元素是重要的,以便机器学习流水线的整体设计可以保持尽可能简单。当一个 ML 模块变得太复杂时,它必须被移除并用一个或多个更简单的 ML 模块替换。

5.头脑大小的组件

把你的机器学习系统分解成数据/ML 组件,分解成你能记住的大小。目标是轻松预测变更的结果(依赖关系、控制流等)。机器学习课应该在 100 行左右。机器学习方法,如 transform、fit、predict 和 predict_proba 方法最多只能有 15 行。

能够孤立地测试机器学习组件是良好架构的标志。不能孤立地测试机器学习组件是裙带架构的标志。

为了实现上述目标,有哪些有用的软件设计原则?

CMLC 坚实的软件设计原则

“鲍勃叔叔”罗伯特·马丁在他的优秀著作中收集/提炼了坚实的软件设计原则。这些原则旨在为软件工程师提供实用的指导。它们作为强有力的指导方针,既适用于机器学习软件,也适用于传统软件。让我们看看当我们将它们应用到机器学习领域时会发生什么,好吗?

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

1.单一责任原则

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

一个机器学习组件应该有且只有一个改变的理由。

为了确定一个类/函数是否有许多职责,检查它所服务的参与者[4]。当不止一个参与者可能要求对该组件进行更改时,这是一个警告信号。组件很有可能包含不止一个责任。所有者需要将这个组件分成更细粒度的逻辑部分。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

在上图中,MLModel 类将不得不由于许多原因而改变。这里我们有拟合方法、输入数据的模式和度量发布。该类对数据处理、特征工程和模型选择的变化做出反应。所有者需要将这个类划分为机器学习子组件。

2.开闭原则(OCP)

您应该能够扩展机器学习组件的行为,而无需修改它。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

ML code evolution when using the open-closed principle

例如,在此图中,ModelManager 通过使用 SimpleFeaturizer 类启动。然而,在版本 2 中,它需要使用超级曝气机。由于紧密耦合,不可能更换超级曝气机。为了支持新的 SuperFeaturizer,需要更改 ModelManager 类。

这违反了开闭原则:旧代码不应该为了增加功能而改变。

解决方案是在接口/抽象类中抽象出特征。然后我们让依赖箭头指向抽象组件。改造后,简易气化器和超级气化器可以互换。ModelManager 可以使用其中任何一个,而不需要了解每个策略的内部。此外,这两个特征不需要了解模型管理器。这样,开发人员可以单独测试它们。在像 Python 这样更动态的语言中,没有构建接口的明确需求。除非使用“isinstance”方法进行严格的类型检查。重要的部分是将交互类设计成指向抽象。通过使模型管理器只知道接口,它可以使用任何一种类型的特征。这使得开发人员可以添加第三个 SuperDuperFeaturizer,而无需对 ModelManager 或 Featurizer 的其他子类进行任何更改。只要特征实现了共享接口,这就是真的。ModelManager 可以忽略具体子类的实现细节。硬连线组件通常使模型空间探索变得乏味、粘滞和缓慢。这有助于模型类型、架构和其他模型定制的实验速度。

我们希望减少需要更改的旧 ML 代码的数量,以添加新的 ML 代码。这是通过将整个 ML 管道划分成组件来实现的。然后,我们将这些组件排列成无环有向图。为了实现这一点,我们让依赖关系箭头指向抽象组件。这允许我们隔离我们想要保护不被改变和/或更稳定的组件。信息隐藏和方向控制保护稳定的 ML 组件。快速移动的 ML 组件还受益于可以随意改变。

3.利斯科夫替代原理

即将推出,仍在烹饪一个例子…

4.接口隔离原则(ISP)

使细粒度的机器学习组件接口成为特定于客户端的接口。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

上图是接口隔离策略的一个例子。这里,ML 组件 ImageFeaturizer 依赖于另一个组件 DataGenerator 的函数 gen_images。DataGenerator 还有许多其他不相关的操作:gen_text 和 gen_rows。

问题是在组件数据生成器中更改 gen_text 可能会导致需要更改组件 ImageFeaturizer。

即使 ImageFeaturizer 不关心数据生成器中的 gen_text 也是如此。

这一原则的目标是避免依赖有额外负担的组件。这个额外的功能可能会横向影响用户。用户可能需要实现不必要的函数来与他们的依赖项兼容。这条原则建议创建特定于客户端的接口。这被认为比一个适用于所有客户端的大型通用接口要好。

5.依赖倒置原则和依赖注入方法

依靠机器学习管道抽象,而不是机器学习管道具体化

依赖性箭头应该指向机器学习管道中的稳定组件。如果你期望一个不稳定的组件经常改变,那么保护那些需要依赖它的组件。控制设计方法的依赖反转有助于做到这一点。

这个原则允许插件架构。该应用程序可以通过外部插件进行扩展。这是通过创建接口来实现的。这些接口将应用程序核心组件与插件隔离开来。

以 ModelTrainer 类和各种机器学习库之间的交互为例。ModelTrainer 的目标是构建某种分类器。根据预测性能,我们可能需要不同的库。这里的候选者是 Scikit-Learn、Tensorflow 和 PyTorch。

该应用程序的核心目标不是:“让我们在项目中使用这些优秀的库之一”。**核心目标是构建给用户带来价值的模型/应用。**ML 库是不应该干扰应用核心的细节。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

依赖注入是执行这种依赖倒置的一种方法。它有助于将核心分类应用程序与 ML 库插件隔离开来。首先,我们让模型训练器依赖于模型构建器界面。这使得模型训练器能够互换使用任何模型构建器实现。然而,我们可以更进一步。ModelTrainer 是一个高级组件,它不应该知道低级关注点。这些低层次的问题存在于单独的实现中。为了解决这个问题,我们可以使用汇编程序。这在运行时将模型构建器实现与模型训练器“连接”起来。模型训练器可以忽略应用程序正在使用的特定模型构建器。这消除了模型训练器对模型构建器实现的依赖性。

对测试驱动的机器学习的影响(TDML)

所有这些设计工作都有一个主要的好处。您可以单独测试您的组件。我们添加的测试越多,我们获得的组件覆盖率就越多。这导致开发人员信任他们正在交互的代码。这不仅是为已建立的软件工程组件保留的。它也可以应用于机器学习组件。TDML 的关键之处在于它不能被用作事后的想法。测试必须在代码之前编写。在代码之后编写测试是一场失败的战斗。

机器学习技术债务

它与干净代码原则有什么关系

机器学习技术债务和干净代码指南之间的关系在[5]中进行了探讨。我们可以将常见的大型生产机器学习管道问题与我们介绍的干净代码概念相匹配。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

纠缠来源于 ML 数据和代码深度纠缠在一起。这导致违反 SRP 原则。SRP 违规的另一个表现是粘合代码组件的存在。这些在一个大的互联系统中带来了大量半相关的功能。

当部署的模型改变未来的训练分布时,隐藏的反馈循环出现。该模型影响客户的行动。然后,客户通过产生与过去不同的结果来做出反应。事实上,每次用这些新数据对模型进行重新训练,都允许外部变更源直接修改您的软件!。这明显违反了开闭原则。通过直接适应新数据,该模型可能会发生变化。一些可见的反馈循环可以通过迭代应用 OCP 来防止,然而一些隐藏的反馈更难检测。一个例子是“聚集特征”。每周汇总需要一段时间来适应新数据。模型将需要新版本的训练代码来处理 ml 管道中的这种变化。如果系统没有遵循 OCP 原则,变更将是昂贵的,开发人员可能会决定推迟变更。这种延迟加重了趋于复合的反馈回路。遵循 OCP 原理有助于更快地缓解这些反馈循环。

当组件提供通用接口时,未声明的消费者开始出现。这允许通用的无差别访问。ISP 指南鼓励开发人员构建特定于客户端的接口。这个原则导致了更好的接口,并降低了拥有未声明的消费者的风险。

管道丛林通常与胶码问题同时出现。这代表了应用程序如何在各种组件之间转换/移动数据。管道在最低的抽象层运行。这是实际数据被移动和转换的地方,我们最终得到非常严格的具体化。这意味着不修改代码就无法修改管道。这种折磨的目标是支持许多实验性的代码路径。为此,开发人员会创建重复的代码和不健康的依赖关系。OCP 和 ISP 的原则被违反,让一些工作。开发人员开始创建一个违背 DIP 原则的依赖关系网。依赖开始走向具体的实现。更稳定/抽象的类和构造的设计被推迟。该策略将特征工程步骤与模型选择步骤交织在一起。模型培训与模型发布。孤立地测试变得很困难。测试没有模型选择步骤的特征工程步骤变得棘手。在没有模型训练的情况下测试模型发布变得更加困难。开发人员可以通过使用 SRP、ISP、OCP 和 DIP 原则进行重构来补救管道丛林。

如果我们不自律,未来会怎样

如果我们不通过建立和应用原则和最佳实践来掌控我们的专业,这是一种可能的情况:

  1. 随着越来越多的新兴数据科学家和人工智能工程师加入并获得管理数百万条生命和数十亿美元的系统,机器学习劳动力的持续缺乏经验只会越来越多。
  2. 一个机器学习管道,某处,将是致命的。给所有参与机器学习的人带来坏名声。
  3. 立法者将不得不对我们建造的自动化系统采取行动。
  4. 当机器学习规则生效时,我们都将不得不遵循由非专家制定的规则。
  5. 我们需要自制的监管指南,TDML 也不是一个糟糕的选择。

我希望这次到干净代码世界的小小旅行有所帮助。你现在可以希望以此为起点,保持你的机器学习技能达到高标准。

感谢阅读。

请注意,这篇文章中表达的观点是我个人的,不一定是我雇主的观点。

我们在招人!如果您对此感兴趣,请查看我们在 Xandr 数据科学平台工程的空缺职位:https://Xandr . att . jobs/job/new-York/Data-Science-Platform-engineer-python-scikit-learn-tensor flow-keras-adtech-market place/25348/13937710

参考

  1. 鲍伯·马丁叔叔——干净的程序员:

https://www.youtube.com/watch?v=NeXQEJNWO5w

2.干净代码备忘单:

https://www . bbv . ch/images/bbv/pdf/downloads/V2 _ 清洁 _ 代码 _V3.pdf

3.Clean Coders 网站:

【https://cleancoders.com

4.罗伯特·马丁谈单一责任原则:

https://blog . clean coder . com/uncle-bob/2014/05/08/singlerepointibility principle . html

5.用 Python 进行全面的机器学习,图书

6.dboyliao 的 Python 代码示例:

https://github.com/dboyliao/SOLID

不用离开 Jupyter 就可以清理自己的模型数据

原文:https://towardsdatascience.com/clean-up-your-own-model-data-without-leaving-jupyter-bdbcc9001734?source=collection_archive---------16-----------------------

使用新的 Innotater 工具注释数据,以获得更好的预测模型。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

许多机器学习项目从 Jupyter 笔记本开始,笔记本的前几行加载训练数据。除了快速检查数据看起来是否正确之外,如果您需要离开笔记本来清理或注释您的数据,这可能会中断您的编程流程。

本文介绍了一个新的开源工具, Innotater ,它提供了一个交互式 Jupyter 小部件,允许开发人员在笔记本中直接内联注释他们的数据。拥有一种简单的方法来清理和扩充数据可以快速产生更好的预测模型。

我们将在计算机视觉任务中使用这种方法,首先,手动过滤掉本来就不应该进入我们数据集的图像,改进一个简单的蝴蝶分类器。

然后,我们使用 Innotater 在图像子集上绘制蝴蝶包围盒。这使我们可以训练一个边界框预测模型,对数据集中的每张图像运行该模型,这样我们就可以生成原始图像的裁剪和放大版本。然后,我们继续训练一个新的分类器,该分类器具有改进的准确性,因为该模型不需要考虑那么多不相关的背景图像。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

Innotater widget embedded in a Jupyter Notebook

本文的目标读者是对 Jupyter 笔记本中的计算机视觉模型有一些了解的读者,他们可能有兴趣了解用于注释图像的工具,以及用于通过手动绘制我们自己的边界框并编写一个新模型来放大该边界框来改进模型的“技巧”。

所有的代码都可以在 GitHub 的 Jupyter 笔记本上找到,所以你可以跟着做。它是用 Python 写的,使用了 fast.ai ,这是一个位于 PyTorch 之上的框架。Fast.ai 将事情保持在一个相当高的水平,并提供最佳实践默认值,因此深度学习代码很轻。

获取原始数据

构建一个分类器来识别两种不同的蝴蝶物种的任务是从 Bert Caramans 以前的关于数据科学的文章中借来的。他写了一个脚本,从 Flickr 上下载被标记为“看门人”或“草甸棕”的照片——这两种蝴蝶在志愿者出于保护目的试图统计野生蝴蝶数量时容易混淆。

我们的第一台笔记本电脑从 Flickr 下载图片。与通常的“每个类一个文件夹”的文件存储模型不同,我们实际上将所有图像放在一个文件夹中,并构建一个 CSV 文件,列出每个图像的假定类(基于 Flickr 标签)。这样做的原因是,我们可以扩展 CSV 来记录我们手动绘制的边界框,并且如果我们发现已经识别出不正确的物种,也可以很容易地改变图像的类别。我们可以在 Innotater 中做这些事情,然后轻松地将修改后的类和边界框保存回 CSV。

过滤和注释数据

Innotater 工具旨在快速简便地浏览您的图像,并在每张图像上标记重要事实或增加数据。

关于每张图片,我们需要注意以下几点:

  1. 分类正确吗?如果图像被分配了错误的物种标签,我们想很容易地改变它。
  2. 这张图片首先属于我们的数据集吗?在某些情况下,相册被贴上了蝴蝶种类的标签,但并不是所有的图片都是真正的蝴蝶。所以我们想把它们从数据集中移除。
  3. 蝴蝶周围的包围盒。我们不会对所有的图像都这样做,但是至少对一些图像,我们会在蝴蝶周围画一个紧密的边界框。

边界框将允许我们在以后的阶段建立一个更精确的模型,尽管为了建立我们的第一个简单的分类器,我们实际上只需要来自上面前两点的数据。

第二个笔记本用于执行这些步骤。请注意,在 GitHub 预览中你看不到 Innotater 小部件,所以希望下面的截图能让它活起来。我们使用 Pandas 来读取 CSV 并提取三个 NumPy 矩阵,这些矩阵将被输入到 Innotater 中。

classes是一个 0 或 1 的数字列表,指定数据集中的每个图像是(当前)看门人还是草地棕物种。

excludes也是一个 0 或 1 的数组。它开始时全是 0,但我们可以将一个条目变成 1,以便从我们的数据集中排除相应的图像。

bboxes是一个四列矩阵,包含我们对每张图片的包围盒的 x,y,w,h,其中(x,y)是盒子的左上角,w 是宽度,h 是高度。这些都是从 0 开始的,直到我们手动抽取任何盒子。

上面矩阵的每一行对应于我们从 CSV 加载的熊猫数据帧中的“文件名”列。

我们几乎准备好调用创新者了(在家里你也需要这样做!),但首先我们需要考虑我们浏览图片的顺序。

把事情混在一起

由于 CSV 的创建方式,我们在文件的开头有近 500 张草地棕色蝴蝶的图片,然后在后半部分有近 500 张看门蝴蝶的图片。因为我们并不期望在每一张图片上绘制边界框——可能只有 200 张左右——如果我们按照默认的顺序进行,那么这将会导致一个问题。如果我们在看到的每张图片上画出边界框,直到我们有足够的注释,那么我们将只在第一种蝴蝶的子集上有边界框。守门蝴蝶不会有!

因此,在 7 号单元格中,我们使用了一点 Python/NumPy 操作来创建一个名为indexes的新映射,它基于索引号指定了一个新的排序。新的排序显示第一个草地棕色,然后第一个看门人,然后第二个草地棕色,等等…

召唤创新者!

为了查看和编辑数据集的所有重要方面,这是我们使 Innotater 的用户界面出现的方式:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

Invoking the Innotater widget within a Jupyter Notebook

启动 Innotater 小部件的语法设计得简单而灵活。格式是Innotater(inputs, targets, indexes=indexes),其中输入和目标是特殊 Innotation 对象的数组(或者只是单个项目),这些对象实质上是数据集矩阵表示的包装器。一般来说,inputs将数据包装在你的数据科学问题的‘x’侧,不要期望被改动;targets是“y”方向,可能需要更改—例如,更改分类或输入边界框数据。

根据您提供的数据格式,Innotation 类是灵活的;您只需要确保为数据类型选择正确的 Innotation 子类。例如,图像本身(我们的机器学习任务的‘x’侧)只需要包装在一个 ImageInnotation 对象中,就像这样:ImageInnotation(filenames, path='./images')。如果您的文件名已经是绝对的或者相对于工作文件夹的,那么 path 参数是可选的,事实上您不需要提供文件名:您可以提供已经加载的矩阵,可能是使用 Open CV2 中的 open_image 导入的。

在目标端,我们使用BinaryClassInnotation(excludes)将 excludes 数组的 0 和 1 表示为每个图像旁边的复选框。excludes 变量实际上不在我们问题的“y”侧,但是我们希望能够编辑它,并且我们将使用它来过滤 excludes==1 的图像。

真正的“y”侧目标包括蝴蝶的分类,通过MultiClassInnotation(classes, classes=cats)变成了列表框组件。注意我们可以在这里再次使用 BinaryClassInnotation,因为我们只有两个类(0 或 1),但是一个复选框在两个不同的物种之间切换感觉不太合适(“选中 Gatekeeper 的复选框,取消选中其他物种”),如果我们希望在将来添加更多的物种,列表框方法可以扩展。classes 变量本身可以有多种形式:一个简单的 0 和 1 的 Python 列表、一个 NumPy 列向量或数据的二维一键编码。Innotater 会检查您的数据并相应地进行处理。

我们要使用的最有趣的 Innotation 类可能是BoundingBoxInnotation(bboxes),它最初显示为一个单独的文本框,我们可以在其中输入每个框的(x,y,w,h)形状,作为一个逗号分隔的数字列表。更好的是,它会自动连接到我们在inputs中提供的 ImageInnotation,这样我们就可以在图像上画出一个框,并自动设置我们的边界框坐标来代表我们画出的形状!

实例化小部件的完整代码是:

Innotater( 
    ImageInnotation(df['filename'], path=IMAGE_FOLDER, height=300, width=400),
    [BoundingBoxInnotation(bboxes),
     BinaryClassInnotation(excludes, name='Exclude'),
     MultiClassInnotation(classes, classes=cats, dropdown=**True**)
    ],
    indexes=indexes
)

使用“下一页/上一页”按钮,您可以浏览每个图像和绘制框,更改类别,或选中“排除”复选框。当你这样做的时候,底层的 Python 变量bboxesclassesexcludes将会立即更新。因此,在任何时候,在我们的笔记本中,我们都可以访问小部件下面的单元格 12,将更新后的变量设置回 Pandas 数据帧(变量名为df)并将 CSV 文件写入磁盘:

df[['x','y','w','h']] = bboxes
df['exclude'] = excludes
df['class'] = [cats[i] **for** i **in** classes]

*# And save the full Pandas data back to a CSV file*
df.to_csv(BUTTERFLIES_BBOXES_FILEPATH, index=**False**)

笔记本的设置方式意味着我们可以在不同的笔记本会话中返回,加载最新的 CSV 值,并继续注释。只要您在每个会话中显式保存 CSV,您就不必一次注释所有数据。

捕捉蝴蝶

现在我们已经检查并注释了我们的数据,让我们对它做些什么吧!本节涉及三个笔记本,在 butterflies GitHub repo 中编号为 3 到 5。

基础训练

首先,在 3 - Basic Train.ipynb 中,在 Innotater 中剔除任何被我们自己标记为‘排除’的图像后,我们只是训练一个基本的分类器模型。这是标准机器学习教程中的“猫或狗”。fast.ai 框架为我们做了这么多,以至于这里真的很少有机器学习代码。

大多数代码是从 fast.ai 示例中借用的样板文件。代码被注释以解释发生了什么:将 CSV 加载到 Pandas 数据帧中,使用该数据帧生成包含训练和测试数据集的“DataBunch”对象,然后使用该对象向预训练的 ResNet50 模型提供训练数据。它使用 Adam 优化器来训练冻结了 10 个时期的大多数现有层;然后模型被“解冻”,这样所有的层都可以在接下来的 5 个时期进行微调。

选择我们的验证集是这个项目中需要考虑的事情。出于验证的目的,保留 20%的数据集似乎是明智的,有一个 fast.ai 函数可以通过随机选择来做到这一点。但这导致了“数据泄漏”——验证集中的图像可能与训练集中的图像非常相似,从而允许模型通过抓住这些图像的不相关人工制品来“作弊”。发生这种情况是因为来自同一个 Flickr 相册的图像通常按顺序位于数据帧中。因此,更安全的方法是将每个类的前 80%的图像用于训练,剩下的 20%用于验证。这样,在最坏的情况下,我们只在训练集和验证集中拆分一个专辑。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

Training results in the basic model: accuracy at end of 5th epoch is 0.82

有了基本模型,我们最终达到了 82%的准确率。不算太差!

包围盒模型

这个项目的全部意图是看看绘制我们自己的边界框是否能帮助我们建立一个更好的模型。理论是,建立一个模型来预测紧密的边界框,准确地显示蝴蝶在图像中的特征,这意味着我们可以裁剪和放大蝴蝶本身,并有希望在放大的图像上明确地训练一个分类器。

对于一张新的看不见的蝴蝶照片,我们将分两个阶段运行我们的分类过程:首先,预测边界框,以便我们可以放大蝴蝶;其次,在放大的图像上运行我们的分类器。

这两个阶段是在最后的两个笔记本中开发的,在 GitHub 中编号为 4 和 5。

notebook 4 - BBox 的第一部分 Train and Generate.ipynb 的工作方式与 notebook 3 非常相似,它使用类似的基础设施来训练模型。在这种情况下,我们预测的是边界框,而不仅仅是“0 或 1”分类,所以这有点复杂。为了建立这个模型,我们首先删除所有没有画出边界框的图像——记住我们从来没有打算注释所有的图像。我们还必须编写自己的 fast.ai 类来处理边界框(在编写本文时,fast.ai 自己的基础设施还没有完全准备好)。

优化是类似的,但我们使用 L1 损失测量(目标和预测坐标之间的绝对水平和垂直距离的总和)来查看模型相对于我们的手动绘图的表现如何。这本笔记本展示了一些不同的尝试,试图获得更好的边界框预测——这比以前的笔记本有点混乱——但无论如何,到最后我们有了一些看起来合理的边界框。我们可以做得更好,这些盒子通常会剪掉重要的蝴蝶标记,这些标记可能对下一阶段的分类器有意义!无论如何,你当然可以尝试改进这一点,但让我们继续前进…

notebook 4 中的最后一个单元格将模型应用于 CSV 中的所有图像(除了那些标有excludes为 1 的图像),以便输出我们裁剪和缩放的图像。在训练时,我们只能利用那些存在边界框的图像,但现在我们已经训练了模型,我们可以将其应用于每一个图像,以获得完整的边界框预测集。代码遍历每张图像,并基于这些边界框坐标生成图像的“缩放”版本——每张新图像都有望包含一只漂亮的大蝴蝶。

我们最初的一组图像中,有一些蝴蝶出现在整个图像中相对较小的部分。这在边界中引入了许多噪声,并且由于我们的图像在我们的“基本训练模型”的预处理中被调整为 256 像素见方,如果我们在缩放的图像上再次训练,更多的蝴蝶本身应该会找到它进入神经网络层的方法。

缩放和裁剪训练

由于我们已经在 notebook 4 的末尾对所有图像执行了所有缩放和裁剪,您会发现 notebook5-zolled Cropped train . ipynb几乎是 notebook 3 的精确副本,除了它运行在新缩放的图像上(这些图像保存在“zolled”子文件夹中)。使用我们之前训练基本模型时使用的相同训练步骤似乎是公平的:我们希望能够比较模型,以查看在缩放图像上训练的模型是否表现更好。

在训练结束时,我们看到准确率为 84%(高于前一版本的 82%)。这绝对是朝着正确的方向前进!

结论

事实上,你可以在训练所有这些模型方面做得更好——我的目的从来不是教你成功地训练计算机视觉神经网络。单个结构更好的神经网络完全有可能模拟我们的组合模型中发生的一些“缩放”。输入数据本身是不可靠的,因为对于一个相对较小的数据集,我们只能希望 Flickr 用户都采取一致的方法来拍摄照片,然后标记并上传它们。

但我希望这个项目表明,Innotater 是一种有趣的方式,可以让你接触到数据,不仅可以清理你的数据集,还可以手动封装“人类洞察力”,否则可能无法进入你的建模过程。最终,在这个例子中,我们已经依靠人类来手动标记蝴蝶的种类,那么为什么不更进一步,首先教你的模型蝴蝶是什么样子的呢?

在编写本文时,图像、单个边界框和 listbox/checkbox 控件是 Innotater 数据唯一可用的包装器。它们的组合方式已经非常灵活了,但是当然,根据您的项目,您可能需要其他注释类型(可能是多个边界框和不同的形状)。请联系我们,描述您的数据所面临的问题,或者在尝试使用 Innotater 时遇到的限制,进一步的开发可以纳入您的想法和解决方案!更多细节可以在 Innotater GitHub 页面上找到。

带有函数式编程的 Cleaner R 代码

原文:https://towardsdatascience.com/cleaner-r-code-with-functional-programming-adc37931ef7a?source=collection_archive---------3-----------------------

介绍

由于工作转换,我最近成为了一名 R-to-Python 的转换者。然而,一些兼职项目让我每天在两种语言之间切换。除了让我感到头疼之外,这种日常的反复让我对编程范例有了很多思考。具体来说,我真的成了 r 中函数式编程范式的传播者。我想就什么是函数式编程(FP)以及它如何给你带来超能力(某种程度上)给出一点见解。

为什么重要?

r 在编程世界中有着独特的地位。全世界每天都有成千上万的人使用它来分析和处理数据。它的用户很少受过纯计算机科学的训练,而且在许多情况下,R 代码只运行一次。这种组合会导致 R 程序变得草率和低效。我们是怎么到这里的?推理通常是:如果它有效,它就有效,对吗?

如果这个“为什么改变?”心态听起来很熟悉,这篇博文是写给你的。事实上,r 是一种受丰富的数学理论影响的完整的(尽管是特定领域的)编程语言。学习 FP 的基础知识将帮助你写出更好的代码,从而使你成为一名更好的统计学家、数据科学家,或者当你读到这篇文章时,我们已经决定称自己为什么。

什么是函数式编程?

我不打算给出一个严格的定义。你可以去维基百科了解一下。

简单来说,FP 就是它听起来的样子。如果你不止一次地做某件事,它属于一个函数。在 FP 中,函数是你执行任务的主要方法。所有的动作都只是(通常是创造性的)你所写的函数的实现。

一旦你进入其中,优势就显而易见了。您的代码更容易修复和维护,因为您已经将代码分割成易于维护的部分。您的代码更容易阅读,因为如果您正确命名了所有内容,您的代码看起来就更接近普通英语。用函数调用代替长代码块也可以帮助你减少面条末日金字塔代码

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

Most bad code is not this delicious

好吧,我们怎样才能重新训练我们的大脑来适应这种甜蜜的 FP 呢?

第 0 步:学习基础知识

要想写出真正“功能性”的函数,必须是 。一个纯函数有两条规则:

  • 它必须是确定性的
    也就是说,每次你用相同的输入运行这个函数,它必须有相同的输出。每一个。单身。时间。"但是有随机成分的函数和统计过程呢?"你问?只需在函数内部设置一个种子,或者让种子成为函数的参数。无论如何,这对可再生科学很重要。
  • 这意味着你的功能不能触及或改变它之外的任何东西。这意味着你可能永远不应该使用全局赋值(<<-)操作符。奇怪的是,这也意味着print()函数不服从 FP。

第一步:抛弃循环

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

正如我的研究生导师曾经告诉我的,

如果你在 R 中写循环,你可能做错了。

(当然,他是在调试我的第三层嵌套for循环时告诉我的。)

但是…循环是如此的基本!为什么我们要尽量少用它们呢?原因有两个,第一个是针对 r 的。

整个语言已经矢量化了 即使你以前从未听说过这个词,你也已经知道了。向量化是你写这个的原因:

x <- 1:10
y <- 2 * x

代替

x <- 1:10
for (i in seq_along(x)) {
    y <- 2 * x[i]
}

循环很慢——使用适用!
apply()函数,以及类似的函数,是 R 的 FP 功能得以完全实现的基础。虽然在大多数语言中,循环和应用(通常称为“映射”)的速度是相同的,但是我们将会看到在 r 中使用 apples 会显著提高速度。

r 的基础有一些应用,但真正漂亮的是在purrr中发现的。稍后将详细介绍。

第二步:管道,潮汐,和更多的管道

如果你还没有听说过 Tidyverse,准备好去见你的新朋友吧。但首先,让我们来认识一下潮汐之星的 管操作员 :

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

管道(%>%)是由几个不同的包提供的操作符,但是最常见的是通过dplyrtidyverse来访问。哦,如果你觉得重复输入(%>%)很痛苦,RStudio 给你一个捷径:Ctrl-Shift-M。

那么,它是做什么的呢?简单地说,管道接受左边的内容,并将其作为右边内容的第一个参数。例如:

add <- function(x, y) x + y
3 %>% add(5) 
# 8

这似乎比简单地键入add(3, 5)更冗长,但是这允许您将复杂的操作写成 管道 :

3 %>%
  add(5) %>%
  add(1) %>%
  add(3) %>%
  add(7)

# 19

太琐碎?看看这个来自我的一个咨询项目的真实片段:

data_clean <- data_raw %>%
  isFinal() %>%
  dropLastFiling() %>%
  getAccStats() %>%
  getPctIncs() %>%
  capOrDrop(inc_vars, cap = 3)

你不需要看函数做什么就知道我在这里隐藏了很多复杂性。然而,你几乎可以用英语阅读:

  • 拿原始数据来说
  • 了解这是否是最后一次纳税申报
  • 删除每个组织的最后一个纳税申报
  • 获取会计统计数据
  • 获得年同比增长百分比
  • 在适当的时候去掉或限制这些变量(我使用 300%的上限)

没有这种模块化,代码几乎不可能调试。删除每个组织的最新纳税申报有问题吗?你必须通读数百行代码。在这里,您只需找到定义dropLastFiling的位置,并在那里修复它。此外,您可以更清楚地看到准备数据的步骤。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

现在,我们准备好开始做 Tidyverse 了。tidyverse实际上是包的集合,你可能不需要全部。我们需要的大部分东西实际上都包含在dplyr里。

总之,tidyverse充满了专门为常见数据操作任务构建的易于使用的函数。以下是一些最常用的方法:

select() —选择要保留(或删除)的列
filter() —选择要保留(或删除)的行
arrange() —按给定的行对数据进行排序
rename() —重命名列
mutate() —从现有列中创建新行
group_by() —组织数据,使其按某个分类变量
summarize()进行分组——类似于mutate(),但将group_by()中的数据折叠成汇总统计数据

示例:

mtcars %>%
  filter(am == 0) %>%         # Consider manual cars only
  group_by(cyl) %>%           # Group them by the number of cylinders
  summarize(                  # Get the mean and sd of fuel
    mean_mpg = mean(mpg),     # economy by cylinder
    sd_mpg = sd(mpg)
  ) %>%
  ungroup()                   # Undo effects of group_by()
                              # (Not always req, but good practice) 

# Output:
# A tibble: 3 x 3
#     cyl mean_mpg sd_mpg
#   <dbl>    <dbl>  <dbl>
# 1     4     22.9   1.45
# 2     6     19.1   1.63
# 3     8     15.0   2.77

第三步:熟悉应用程序和地图

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

The package purrr is short for “Pure R”. The third R was added for the cat mascot, I suppose.

我们的工具包中还有一个缺口:我们不允许使用循环,有些任务对我们来说还没有矢量化!数据分析师要做什么?

解决方法是使用 应用 (也叫 贴图 )。地图收集了一些事物,并对其中的每一个事物应用一些功能。这里有一张图直接取自 RStudio 的 purrr 小抄 (鸣谢: 玛拉·阿威克 ) :

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

旁注: *dplyr* 包实际上是从 applies 中得到它的名字的。 *dplyr* =数据+应用+ R.

purrr包里有多得离谱的地图可供选择。说真的,看看那张备忘单

例如,把所有的放在一起:假设我有一个字符串向量,我想提取每个字符串中最长的单词。没有矢量化的函数可以帮我做到这一点。我需要用空格字符分割字符串,得到最长的单词。为了获得戏剧性的效果,我还将字符串大写并粘贴在一起:

library(tidyverse)
library(purrr)

sentences <- c(
  "My head is not functional",
  "Programming is hard",
  "Too many rules"
)

getLongestWord <- function(words) {
  word_counts <- str_length(words)
  longest_word <- words[which.max(word_counts)]
  return(longest_word)
}

sentences %>% 
  toupper() %>% 
  str_split(' ') %>% 
  map_chr(getLongestWord) %>% 
  str_c(collapse = ' ')

# [1] "FUNCTIONAL PROGRAMMING RULES"

额外步骤:了解行话

在其他语言中,FP 的一些行话是内置的。具体来说,几乎每种语言都有三个高阶函数,不管是不是函数式的:map(我们已经介绍过)、reduce 和 filter。

高阶函数是这样一种函数,它要么接受一个函数作为参数,要么返回一个函数,或者两者都接受。

**在 R 中过滤很容易。**对于数据帧,我们可以使用使用tidyverse::filter。对于大多数其他事情,我们可以简单地使用 R 的向量化。然而,当所有其他的都失败时,基数 R 确实有一个Filter()函数。示例:

Filter(function(x) x %% 2 == 0, 1:10)
# [1]  2  4  6  8 10

同样,你可能永远也不会在 R 里用到 **Reduce()** 。但为了以防万一,它是这样工作的:Reduce()将接受一个集合和一个二元函数(即接受两个参数),并沿着该集合一次两个地连续应用该函数。示例:

wrap <- function(a, b) paste0("(", a, " ", b, ")")
Reduce(wrap, c("A", "B", "C", "D", "E"))
# [1] "((((A B) C) D) E)"

另一个广受欢迎的 FP 话题是 奉承 。Currying 是这样一种行为:获取一个带有许多参数的函数,然后将它分解成接受部分参数的函数。这些有时被称为 部分功能 。下面的例子使用了一个 函数工厂 来制作部分函数:

# Adder is a "function factory" - a function that makes new functions.
adder <- function(a) {
    return(function(b) a + b)
}

# Function factory pumping out new functions.
add3 <- adder(3)
add5 <- adder(5)

add3(add5(1))
# 9

你觉得这个概念很难理解吗?你并不孤单。为了使这更具可读性,functional库为您提供了一个显式的 currying builder:

library(functional)
add <- function(a, b) a + b
add3 <- Curry(add, a = 3)
add5 <- Curry(add, a = 5)

add3(add5(1))
# 9

附注:动词“currying”来自哈斯克尔·库里,著名的数学家/计算机科学家/宾夕法尼亚州立大学的研究员。

摘要

你觉得自己更聪明了吗?更厉害?准备好用你的新 FP 技能折磨你的数据了吗?以下是一些重要的要点:

  • 不再有循环!永远不会。
  • 任何时候你想使用一个循环,找到合适的应用/映射。
  • 尽可能将 Tidyverse 集成到您的工作流程中。
  • 当对一件事应用几个函数时,使用管道(%>%)(例如,在 Tidyverse 中操作一个数据帧)。

在编码时坚持这些心态可以大大减少难看的、难以维护的代码。将东西装入函数中可以给你留下干净、易读、模块化的馄饨代码。我将引用约翰·伍兹的一句名言:

编写代码时,始终要假设最终维护您代码的人是一个知道您住哪儿的暴力精神病患者。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

在 Python 中清理、分析和可视化测量数据

原文:https://towardsdatascience.com/cleaning-analyzing-and-visualizing-survey-data-in-python-42747a13c713?source=collection_archive---------0-----------------------

使用pandasmatplotlibseaborn从脏数据中产生可消化的见解的教程

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

如果你在 D2C 的一家初创公司从事数据工作,很有可能你会被要求至少查看一次调查数据。由于 SurveyMonkey 是最受欢迎的调查平台之一,它很有可能是 SurveyMonkey 的数据。

SurveyMonkey 导出数据的方式不一定适合开箱即用的分析,但它非常接近。在这里,我将展示几个您可能想问的关于调查数据的问题的例子,以及如何快速提取这些答案。我们甚至会编写一些函数,让我们在设计未来的问题时更加轻松。

我们将使用pandasmatplotlibseaborn来理解我们的数据。我使用了 Mockaroo 来生成这些数据;具体来说,对于调查问题字段,我使用“自定义列表”并在适当的字段中输入。您可以通过在random模块中使用random.choice来达到同样的效果,但是我发现让 Mockaroo 为我创建整个事情更容易。然后我调整了 Excel 中的数据,使其反映了 SurveyMonkey 导出的结构。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

Oh boy…here we go

你对此的第一反应可能是“啊。太恐怖了。”我的意思是,列名没有正确读入,有大量的 nan,而不是像 0/1 或 1/2/3/4/5 这样的数字表示,我们在每个单元格中都有实际的文本答案…我们真的应该用多索引读入吗?

但是不要担心,没有你想象的那么糟糕。在这篇文章中,我们将忽略多重索引。(反正没人真的喜欢和他们一起工作。)团队需要尽快得到这些见解——所以我们会想出一些简单的解决方案。

首先:我们被要求去发现这些问题的答案是如何随着年龄组而变化的。但是age只是一个年龄——我们没有年龄组的专栏!幸运的是,我们可以很容易地定义一个函数来创建一个。

但是如果我们像这样运行它,我们会得到一个错误!这是因为我们有第一行,它的年龄值是单词“年龄”而不是数字。由于第一步是将每个年龄转换为一个int,这将失败。

我们需要从 DataFrame 中删除该行,但是当我们以后重命名列时会用到它,所以我们将它保存为一个单独的变量。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

您会注意到,自从删除了headers,我们现在在单独查看调查数据时丢失了一些信息。理想情况下,您会有一份调查中提出的问题及其选项的列表,由任何想要分析的人提供给您。如果没有,您应该在文档或笔记中保留一个单独的方式来引用此信息,以便在工作时可以查看。

好了,现在让我们应用age_group函数来得到我们的age_group列。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

太好了。接下来,让我们将数据分成子集,只关注第一个问题。第一个问题的答案在不同的年龄段有什么不同?

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

太好了。我们现在有了变量中的答案。但是当我们绘制这些数据时,它看起来不会很好,因为错误命名的列。让我们编写一个快速函数来简化列的重命名:

还记得之前的headers吗?我们可以用它来创建我们的new_names_list进行重命名。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

它已经是一个数组了,所以我们可以直接传入它,或者为了可读性,我们可以先重命名它。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

看起来不是更好吗?别担心,我们差不多到了获得一些见解的部分了。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

请注意groupby和其他聚合函数是如何自动忽略 nan 的。这让我们的生活变得简单多了。

假设我们现在也不太关心分析 30 岁以下的客户,那么我们将只绘制其他年龄组。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

好吧,这很好,但是 60 岁以上的人群比其他人群多,所以很难进行公平的比较。我们该怎么办?我们可以在单独的图中标出每个年龄组,然后比较分布情况。

“但是等等,”你可能会想。“我真的不想为 4 个不同的情节写代码.”

当然不是!谁有时间做那个?让我们写另一个函数来完成它。

我相信是珍妮·布莱恩,在她精彩的演讲“代码的气味和感觉”中,首先向我透露了以下信息:

如果你发现自己复制并粘贴代码,只是改变了一些值,你真的应该写一个函数。

对于我来说,这是一个很好的指导,让我决定什么时候为某个东西写一个函数是值得的,什么时候不值得。我喜欢用的一个经验法则是,如果我要复制粘贴 3 次以上,我就写一个函数。

除了方便之外,这种方法还有其他好处,例如:

  • 减少出错的可能性(在复制和粘贴时,很容易意外忘记更改值)
  • 有助于提高代码的可读性
  • 构建您的个人功能工具箱
  • 迫使你在更高的抽象层次上思考

(所有这些都提高了你的编程技能,让需要阅读你代码的人更开心!)

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

Hooray, laziness!

当然,这是从均匀分布中产生的数据,因此我们不会期望在组之间看到任何显著差异。希望你自己的调查数据会更有趣。

接下来,让我们解决另一种形式的问题。在这项研究中,我们需要了解每个年龄组对特定福利的兴趣。令人高兴的是,这些问题实际上比前一种更容易处理。让我们来看看:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

看,因为这是一个小的数据帧,age_group已经被附加了,我们不需要添加它。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

酷毙了。现在我们有了子集化的数据,但这次我们不能像对另一个问题那样,只通过计数来汇总数据,上一个问题中的 nan 将被排除,以给出该响应的真实计数,但有了这个问题,我们将只获得每个年龄组的响应总数:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

这绝对不是我们想要的!问题的关键是了解不同年龄组的兴趣,我们需要保存这些信息。所有这些告诉我们的是每个年龄组中有多少人回答了这个问题。

那我们该怎么办?一种方法是用数字重新编码这些反应。但是,如果我们想在一个更精细的层次上保持这种关系呢?如果我们用数字编码,我们可以得到每个年龄组的兴趣水平的中位数和平均数。但是如果我们真正感兴趣的是每个年龄组中选择每个兴趣水平的人的具体百分比呢?在柱状图中传达这些信息会更容易,同时保留文本。

这就是我们接下来要做的。而且——你猜对了——是时候写另一个函数了。

**给新学员的快速提示:**大多数人不会明确地说出来,但让我明确一下可视化通常是如何实现的。总的来说是一个高度迭代的过程。即使是最有经验的数据科学家也不会不假思索地写出所有这些规格的图表。

一般来说,你从.plot(kind='bar')开始,或者类似的,取决于你想要的图,然后你改变大小,颜色映射,使用order=得到正确排序的组,指定标签是否应该旋转,设置 x 轴或 y 轴标签不可见,等等,取决于你认为对使用可视化的人来说什么是最好的。

所以不要被人们在制作剧情时看到的一长串代码吓倒。它们通常是在测试不同规范的几分钟内创建的,而不是一次性从头开始编写完美的代码。

现在,我们可以为按年龄组划分的每个福利绘制另一个 2x2。但是我们必须为所有 4 个好处这样做!再说一遍:谁有时间做这个?相反,我们将使用几个for循环来循环每个福利,以及每个福利中的每个年龄组。但是如果你感兴趣的话,我建议你把它重构为一个函数,如果你碰巧有很多这样格式的问题。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

成功!如果我们想导出每组单独的图,我们只需添加行plt.savefig('{}_interest_by_age.png'.format(benefit))matplotlib会自动保存每组图的漂亮清晰的渲染。

这使得其他团队的人使用你的发现变得特别容易;您可以简单地将它们导出到 plots 文件夹,人们可以浏览这些图像,并能够将它们拖放到 PowerPoint 演示文稿或其他报告中。

这些可以使用多一点的填充,所以如果我再做一次,我会稍微增加人物的允许高度。

让我们再举一个例子:如前所述,对收益进行数字编码。然后,我们可以生成不同利益之间的相关性热图。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

最后,我们将生成相关矩阵并绘制相关图。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

同样,由于数据是随机生成的,我们预计几乎没有相关性,这也是我们的发现。(有趣的是,SQL 教程与拖放功能略有负相关,这实际上是我们可能期望在真实数据中看到的!)

让我们做最后一种类型的图,一种与热图密切相关的图:簇图**。聚类图使相关性在分析调查响应时特别有用,因为它们使用层次聚类(在本例中)根据利益的密切相关程度将它们分组。因此,与其目测单个收益正相关或负相关的热图,当你有 10 个以上的收益时,这可能有点疯狂,该图将被分割成簇,这更容易查看。**

如果您熟悉层次聚类的数学细节,还可以轻松地更改计算中使用的关联类型。一些可用的选项是“单身”、“一般”和“病房”——我不会进入细节,但“病房”通常是开始时的安全赌注。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

长标签通常需要一点调整,所以我建议在使用集群映射之前将你的优势重命名为较短的名称。

对此的快速评估表明,聚类算法认为拖放功能和现成的公式聚集在一起,而自定义仪表板模板和 SQL 教程形成另一个集群。由于相关性如此之弱,你可以看到当利益链接在一起形成一个集群的“高度”非常高。(这意味着您可能不应该根据这一发现做出任何商业决策!)尽管关系很弱,但希望这个例子是说明性的。

我希望您喜欢这篇关于使用调查数据和编写函数来快速生成可视化结果的快速教程!如果你认为你知道一种更有效的做事方式,请在评论中告诉我——这正是我在需要尽快就个别问题提出见解时想到的方法。

用 SQL 清理和转换数据

原文:https://towardsdatascience.com/cleaning-and-transforming-data-with-sql-f93c4de0d2fc?source=collection_archive---------5-----------------------

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

了解如何使用 SQL 查询来准备、清理和转换用于分析的数据!

进行数据分析时,首先要执行的任务之一是创建干净的数据集。您从数据中获得的洞察力仅与数据本身一样好,因此毫不奇怪,分析专业人员大约 80%的时间都花在准备供分析使用的数据上。

SQL 可以帮助加速这项重要的任务。在本教程中,我们将讨论常用于从查询输出中清理、转换和删除重复数据的不同函数,这些数据可能不是我们想要的形式。这意味着您将了解:

  • CASE WHEN
  • COALESCE
  • NULLIF
  • LEAST / GREATEST
  • 铸造
  • DISTINCT

在本教程中,我们将使用下面的示例表employees来说明我们的函数是如何工作的:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

Our sample table, employees

这些数据被预加载到下一个技术沙箱中,供您试验和测试以下查询。在这里免费连接数据库

我们开始吧!

本教程改编自 Next Tech 的完整 SQL for Data Analysis 课程,其中包括浏览器内沙盒环境以及使用真实数据集的互动活动和挑战。你可以在这里开始学习这门课程

CASE WHEN

CASE WHEN是一个允许查询将列中的各种值映射到其他值的函数。CASE WHEN语句的一般格式是:

CASE
    WHEN condition1 THEN value1
    WHEN condition2 THEN value2
    ...
    WHEN conditionX THEN valueX
    ELSE else_value
END

这里,condition1condition2conditionX为布尔条件;value1value2valueX是映射布尔条件的值;并且else_value是如果布尔条件都不满足时映射的值。

对于每一行,程序从CASE WHEN语句的顶部开始,并评估第一个布尔条件。然后程序从第一个布尔条件开始运行每个布尔条件。对于从语句开始算起的第一个评估为 true 的条件,该语句将返回与该条件关联的值。如果没有一个语句评估为真,那么将返回与ELSE语句相关联的值。

例如,假设您想从employees表中返回雇员的所有行。此外,如果员工是在 2019-01-01 之后被雇用的,您可能希望添加一个列,将他们标记为New员工。否则,它会将该员工标记为Standard员工。该列将被称为employee_type。我们可以使用如下的CASE WHEN语句创建这个表:

SELECT
    *,
    CASE
        WHEN hire_date >= '2019-01-01' THEN 'New'
        ELSE 'Standard'
    END AS employee_type
FROM
    employees;

该查询将给出以下输出:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

Output from query using CASE WHEN

CASE WHEN语句有效地将雇用日期映射到描述雇员类型的字符串。使用CASE WHEN语句,您可以随意地映射值。

联合

另一个有用的技术是用标准值替换NULL值。这可以通过COALESCE功能轻松实现。COALESCE允许您列出任意数量的列和标量值,如果列表中的第一个值是NULL,它将尝试用第二个值填充它。COALESCE函数将继续在值列表中向下搜索,直到找到一个non-NULL值。如果COALESCE函数中的所有值都是NULL,则该函数返回NULL

为了说明COALESCE函数的简单用法,假设我们想要一个雇员姓名和职务的列表。然而,对于那些没有标题的,我们想改为写值'NO TITLE'。我们可以用COALESCE来完成这个要求:

SELECT
    first_name,
    last_name,
    COALESCE(title, 'NO TITLE') AS title
FROM
    employees;

该查询产生以下结果:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

Output from query using COALESCE

在处理创建默认值和避免NULL时,COALESCE总是有用的。

努里夫

NULLIF在某种意义上是COALESCE的反义词。NULLIF是一个双值函数,如果第一个值等于第二个值,将返回NULL

例如,假设我们想要一份雇员姓名和职务的列表。不过,这一次,我们想把标题'Honorable'换成NULL。这可以通过以下查询来完成:

SELECT
    first_name,
    last_name,
    NULLIF(title, 'Honorable') AS title
FROM
    employees;

这将从title列中删除所有提到的'Honorable',并给出以下输出:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

Output from query using NULLIF

最小/最大

对于数据准备来说,两个常用的函数是LEASTGREATEST函数。每个函数接受任意数量的值,并分别返回最小或最大的值。

这个变量的一个简单用法是替换过高或过低的值。例如,假设最低工资增加到 15 美元/小时,我们需要更改任何收入低于该水平的员工的工资。我们可以使用以下查询来创建它:

SELECT
    id,
    first_name,
    last_name,
    title,
    age,
    GREATEST(15, wage) as wage,
    hire_date
FROM
    employees;

该查询将给出以下输出:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

Output from query using GREATEST

如你所见,比尔·萨达特的工资从 12 美元涨到了 15 美元。

铸造

另一个有用的数据转换是在查询中更改列的数据类型。这通常是为了使用只适用于一种数据类型(如文本)的函数,同时处理不同数据类型(如数值)的列。

要更改列的数据类型,只需使用column::datatype格式,其中column是列名,而datatype是要将列更改为的数据类型。例如,要在查询中将employees表中的年龄更改为文本列,请使用以下查询:

SELECT
    first_name,
    last_name,
    age::TEXT
FROM
    employees;

这将把age列从整数转换成文本。现在,您可以将文本函数应用于这个转换后的列。还有最后一个条件:并非每种数据类型都可以转换为特定的数据类型。例如,datetime不能转换为浮点类型。如果您进行了意外的奇怪转换,您的 SQL 客户端将会抛出一个错误。

明显的

通常,在浏览数据集时,您可能会对确定一列或一组列中的唯一值感兴趣。这是关键字DISTINCT的主要用例。例如,如果您想知道employees表中所有唯一的名字,您可以使用以下查询:

SELECT
    DISTINCT first_name
FROM
    employees;

这将产生以下结果:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

Output from query using DISTINCT

您还可以对多个列使用DISTINCT来显示所有不同的列组合。

我希望你喜欢这篇关于 SQL 数据清理和转换的教程。这只是在数据分析中使用 SQL 的开始。如果你想了解更多,Next Tech 的 SQL for Data Analysis 课程包括:

  • 用于数据准备和清理的更多功能
  • 聚合函数和窗口函数
  • 导入和导出数据
  • 使用复杂数据类型的分析
  • 编写性能查询

这里可以免费上手!

使用 GeoPy 清理位置数据

原文:https://towardsdatascience.com/cleaning-location-data-with-geopy-51613bcd3c89?source=collection_archive---------27-----------------------

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

在本文发表时,Python 已经有超过 120,000 个库。随着我为了数据科学的需要而更深入地研究 Python,我发现自己安装了每一个我认为有用和/或有趣的库。这些图书馆中有一个是 GeoPy。正如其文档所述,GeoPy 是一个简单的库,旨在使用外部地理编码器和数据源对位置数据进行地理编码并获得位置坐标。数据清理问题的完美解决方案。

什么问题…和谁?

我目前正在参与一个三人项目,协助伊娃·默里安迪·克里贝尔完成他们的周一改造项目。如果你不知道那是什么,你一定要检查一下,然后投入进去(特别是如果你喜欢数据可视化的话)。Eva 或 Andy 将在每个星期天发送一个可视化数据和一篇文章。然后,参与者利用这些数据,使用他们选择的任何可视化工具创建他们自己的可视化(简称 viz)。然后,参与者在 Twitter 上分享他们的 viz 的链接和图像,并在 Data.World 上发布。最后,Eva 和 Andy 通过现场网络研讨会提供对这些可视化效果的反馈。

在这个项目中,我最棒的队友是罗伯特·克罗克和迈赫萨姆·拉扎·赫马尼。我和 Mehsam 的项目的一部分是收集包含他们 vizzes 的图片和链接的 tweets。Mehsam 收集了过去两年的推文,而我使用 tweepy 来获取最近的推文。从这些推文中,我提取了各种有用的信息,比如参与者的位置(如果有的话)。

从推文中提取的位置数据的问题之一(除了奇怪的格式问题)是它们是如何产生的不一致。这些不一致使得 Rob 很难使用 Tableau 来绘制参与者的位置…

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

Rob’s Tableau window showing the geographical detection issues.

因此,团队建议将位置更改为经纬度坐标。Rob 接着问 GeoPy 图书馆是否有帮助。那时我很高兴几天前下载了那个随机库。现在您将看到实现。如果你想查看代码,可以在我们的 GitHub 项目报告中找到。

清洗代码

# Import Libraries and Classes
import pandas as pd
import numpy as np
from geopy.geocoders import Nominatim
from geopy.exc import GeocoderTimedOut

GeoPy 提供各种地理编码器。然而,其中一些需要获取 API 密钥。我决定使用提名,因为这对我来说是最容易使用的,而且不需要 API 密匙。我们还将为一个函数导入 GeocoderTimedOut 错误,我们将使用该函数对我们的位置进行地理编码。

# Load Data
df=pd.read_csv('data.csv',encoding='latin1')

# Function used to geocode locations and override timeout error
def do_geocode(address):
    geopy = Nominatim()
    try:
        return geopy.geocode(address,exactly_one=True)
    except GeocoderTimedOut:
        return do_geocode(address)

# Creating Geocoded Location column
df['GeocodedLocation']=df['Location'].apply(lambda x: do_geocode(x) if x != None else None)

当时加载的数据有 1100 多个条目(相当于 3 周)。使用上面的函数,我们可以对每个位置进行地理编码(这是我从 stackoverflow.com 修改的函数)。地理编码器的参数为 exactly _ one = True,因为我们只希望返回一个地址。

如果没有这个功能(正如我所经历的),地理编码器将在地理编码过程中超时。上面的函数通过再次运行直到完成任务来防止超时。

为了存储地理编码的位置,使用 apply 属性和 lambda 函数创建了一个名为‘geocoded location’的列来应用 do_geocode 函数。一些 tweets 没有参与者的位置,所以我将让它返回 None,这样可以高效地创建坐标。

# Create the Latitude Column
lat=[]
for i in df['GeocodedLocation']:
    if i== None:
        lat.append(None)
    else:
        lat.append(i.latitude)
df['Latitude']=lat
df['Latitude'].astype('float')

# Create the Longitude Column
long=[]
for i in df['GeocodedLocation']:
    if i== None:
        long.append(None)
    else:
        long.append(i.longitude)
df['Longitude']=long
df['Longitude'].astype('float')

接下来,使用循环创建纬度和经度列。同样,我们将返回 None,以便 Tableau 可以将这些列中的数据识别为位置类型。每个坐标的数据类型都被转换成浮点数,所以不需要在 Tableau 中手工完成。

# Drop GeocodedLocation Column
df=df.drop(['GeocodedLocation'],axis=1)# Export Data to a csv
df.to_csv('data.csv', index=False)

然后我们将删除“GeocodedLocation”列,因为现在我们已经有了纬度和经度坐标列,保留它是多余的。然后数据被导出到一个 csv 文件中,这样就可以进行 Tableau 可视化了。

清洗的结果

现在我们有了我们的纬度和经度坐标,我们能够从 Tableau 开始,拥有一张几乎没有检测到任何位置的地图:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

Before the GeoPy changes.

去所有的地方好让 Rob 施展他的舞台魔术!

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

After the GeoPy changes! A lot better huh?

你可以看看 Rob 在他的 Tableau 公开简介中制作的仪表板草图。

感谢

再次感谢 Rob 让 GeoPy 的存在浮出水面,感谢他和 Mesum 调查这件事(也感谢他们是伟大的队友)。也感谢 GeoPy 的创造者们,他们制作的库解决了我们的问题。

还有一点

你一定要参加周一的化妆大赛。这是练习和提高数据可视化技能的好机会。你还可以获得额外的好处,让你的 viz 得到审查,并成为一个令人惊讶的支持社区的一部分。以下是改头换面星期一的所有链接:

1.改头换面星期一网站(在这里可以看到如何参与项目的主要网站)

2.#改头换面周一推特消息(忽略其他关于化妆之类的随机消息……)

3.数据。世界(周一改造的数据集与 viz 帖子放在一起)

期待您的参与:)

直到下一次,

约翰·德杰苏斯

原载于www.jddata22.com

清除熊猫数据帧中的缺失值

原文:https://towardsdatascience.com/cleaning-missing-values-in-a-pandas-dataframe-a88b3d1a66bf?source=collection_archive---------9-----------------------

使用 Python 可视化缺失数据的方法以及如何处理它们。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

Photo by Jan Kolar / VUI Designer on Unsplash

在分析数据时,您可能会遇到丢失的数据(也称为空值或 NaNs)。数据清理是数据分析管道的重要组成部分,确保数据清理干净将使您的分析更加有力。

确定缺失值

您需要导入的唯一库是 pandas:

import pandas as pd

如果您正在使用 Python 中的 pandas 库,并且经常处理具有缺失值的数据,并且需要更快地进行数据分析,那么这里有一个快速函数,它会输出一个数据帧,告诉您每列中有多少缺失值及其百分比:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

可读性稍差的版本,但是您可以将它复制粘贴到您的代码中:

def assess_NA(data):
    """
    Returns a pandas dataframe denoting the total number of NA values and the percentage of NA values in each column.
    The column names are noted on the index.

    Parameters
    ----------
    data: dataframe
    """
    # pandas series denoting features and the sum of their null values
    null_sum = data.isnull().sum()# instantiate columns for missing data
    total = null_sum.sort_values(ascending=False)
    percent = ( ((null_sum / len(data.index))*100).round(2) ).sort_values(ascending=False)

    # concatenate along the columns to create the complete dataframe
    df_NA = pd.concat([total, percent], axis=1, keys=['Number of NA', 'Percent NA'])

    # drop rows that don't have any missing data; omit if you want to keep all rows
    df_NA = df_NA[ (df_NA.T != 0).any() ]

    return df_NA

您需要传递的唯一参数是 dataframe 对象。这里有一个例子,我们在名为training的丢失数据的数据帧上调用函数assess_NA(),然后输出数据帧:df_NA

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

行表示数据框的要素,列提供关于缺失数据的信息。如果没有丢失的值,那么它将只输出一个空的数据帧。

清除丢失数据的方法

了解了这一点,您就可以更好地了解如何处理空值,例如:

  1. 删除
  2. 使用平均值、中值、0、假、真等进行估算

删除包含空值的行

这个方法是处理丢失值的一种简单但混乱的方式,因为除了删除这些值之外,它还可能删除不为空的数据。您可以在整个数据帧或特定列上调用dropna():

# Drop rows with null values
df = df.dropna(axis=0)# Drop column_1 rows with null values
df['column_1'] = df['column_1'].dropna(axis=0)

轴参数决定了函数将作用的尺寸。axis=0删除所有包含空值的行。axis=1做了几乎相同的事情,只是它删除了列。

输入空值

插补不是丢弃缺失数据的值,而是用另一个值替换这些值,通常是特定列的平均值或中值。使用任何一种都有好处。例如,如果该列有许多异常值,中位数可能会更有用,因为它更能抵抗这些异常值。通过这种方式,我们试图保留数据的某些方面。为此,我们可以在 dataframe 列上调用fillna()函数,并将mean()median()指定为参数:

# Impute with mean on column_1
df['column_1'] = df['column_1'].fillna( df['column_1'].mean() )# Impute with median on column_1
df['column_1'] = df['column_1'].fillna( df['column_1'].median() )

除了平均值和中值之外,在某些情况下,用 0 来输入缺失数据也是一个好主意:

# Impute with value 0 on column_1
df['column_1'] = df['column_1'].fillna(0)

知道在处理丢失的数据时采取什么途径很大程度上取决于领域知识和您的直觉告诉您的关于数据的信息。这是一步一步来的,通过使用数据集,你可以问一些有意义的问题。

用熊猫和 Regex 清理网络抓取的数据!(第一部分)

原文:https://towardsdatascience.com/cleaning-web-scraped-data-with-pandas-and-regex-part-i-a82a177af11b?source=collection_archive---------13-----------------------

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

Photo by JESHOOTS.COM on Unsplash

如何从网络搜集的数据中创造意义,用于数据分析和机器学习

||我||简介

既然我已经开始每天编程,我决定投资一台笔记本电脑,让我能够顺利地执行多项任务,并以我习惯的方式运行所有我想要的应用程序。

我有一台基于 Unix 的 Macbook,但当我在处理需要处理大量图像或视频的项目时,它的 M3 处理器就不适合我了。我也有一台工作用的 Windows 笔记本,配有不错的 i5,但在使用它之后,我意识到我的需求要高得多。

然而,我不打算随便花 2000 美元买一台性能好的机器。实际上,我想了解笔记本电脑的组件/规格(尺寸、存储、内存、处理器等)。)有助于在市场上以某一价格形成 Lapton 的总体表现。例如,我想推导出:

  • 笔记本电脑不同组件与其价格之间的关系。
  • 产品等级和特定组件之间的关系。
  • 不同品牌及其类似产品的比较。

我可以从搜集产品数据中获得许多见解,我最终将开发一种算法,在给定大量输入的情况下找到合适的笔记本电脑。

|| II ||网页抓取

多亏了一个朋友,我发现了谷歌 Chrome 上的“Web Scraper”扩展。这里可以找到
这个扩展帮我在亚马逊上收集了笔记本电脑所需的数据。

这个网页抓取器做了大多数其他人做的事情:它从页面来源收集我们想要的信息。网站并不总是让你很容易从他们的网页中提取数据,因此你需要清理提取的数据,然后才能使用它进行任何类型的分析。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

Photo by The Creative Exchange on Unsplash

那么,我所说的“清理”数据是什么意思呢?
通常,数据会有一些杂质,如 NaN(空)值、不必要的列、不可读的字符或空格(я或\s)。
在下面我的笔记本电脑数据集的例子中,你会看到我们所有的数据是如何存储在一个单独的列中的,我们需要将每组单词拆分到单独的列中。

对于**网络抓取,**你可以按照这个指南使用谷歌 Chrome 上的网络抓取扩展来提取亚马逊上的产品数据。
我将专门处理亚马逊上的产品数据,尽管我相信你可以从很多网站上搜集。重要的是为您的提取找到正确的 JSON 代码(如您在指南中所见)。
在进入下一部分之前, 学习如何抓取数据 。如果你已经有了一个 CSV 格式的数据,那就继续吧。

|| III ||导入库和数据

既然您已经将收集到的数据保存为 CSV 格式,那么让我们加载一个 Jupyter 笔记本并导入以下库:

#!pip install pandas, numpy, reimport pandas as pd
import numpy as np
import re #Regex

然后上传数据,用df = pd.read_csv('amazon.csv')读取。该表应该类似于下面的输出。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

Extracted Data — Not exactly clean, is it?

这是我对数据的看法:并非完全无用,因为它确实包含了所有需要的信息,尤其是在product_specification列中。但是,当我们查看该列的一个实例时,它看起来像是:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

看起来完全有可能为我的数据集中的每一项将这些组件分成不同的列。首先,我将分离我的数据,只把需要的列复制到一个新的 DataFrame 中。

data = df[['listing', 'listing-href', 'brand', 'review_count', 'sales_price', 'product_description', 'product_specification']].copy()

现在我们的专栏已经在data了,让我们开始清理过程。

|| IV ||打扫卫生

像数据科学家一样思考,而且…像玛丽·近藤一样思考

首先,我们必须删除所有包含 Null/NaN 值的行。这是因为我们不能在没有任何值的列上运行 regex 函数,我们最终会得到不匹配的索引值,并且以后很难将我们清理过的列合并到数据集。

data = data.dropna(how='any')
data = data.reset_index(drop=True)

现在我们已经删除了空值,可以开始清理数据集了。第一步是删除\s, \n, \t字符。正如你在上面的图片中看到的,我们的信息在\n人物中变得模糊不清。

data = data.replace(r'\n',' ', regex=True)
data = data.replace(r'\t','', regex=True)
data = data.replace(r'\s\s\s','', regex=True)

**注意:**我使用\s\s\s是因为经过一些实验,我注意到了一种三个空格的制表符的模式。但是,如果你没有这样的模式,你也可以只使用\s

|| V ||正则表达式

下一步也极其重要。我们必须删除不包含以下信息的特定行。唯一的问题是它会显著减小数据集的大小。然而,就我而言,我认为这比空值要好。空值必须通过均值、中值或众数来填充,这两种方法在这里都没有意义。因此,我将删除没有所需数据的行。

#Screen Size
data.drop(data[~data.product_specification.str.contains("Screen Size")].index, inplace=True)#Processor type
data.drop(data[~data.product_specification.str.contains("Processor (?<![a-zA-Z:])[-+]?\d*\.?\d+\s\w+",regex=True)].index, inplace=True)#Processor Speed
data.drop(data[~data.product_specification.str.contains("GHz (?:[A-Za-z]+\s){0,}[A-Za-z]+\w+",regex=True)].index, inplace=True)#RAM
data.drop(data[~data.product_specification.str.contains("RAM (\w+\s\w+){1,}",regex=True)].index, inplace=True)#Storage
data.drop(data[~data.product_specification.str.contains("Hard Drive (\w+\s\w+\s\w+){1,}",regex=True)].index, inplace=True)#Graphic Card
data.drop(data[~data.product_specification.str.contains("Chipset Brand (\w+){0,}",regex=True)].index, inplace=True)#Graphic Card Brand
data.drop(data[~data.product_specification.str.contains("Card Description (\w+){0,}",regex=True)].index, inplace=True)data = data.reset_index(drop=True)

在上面的代码行中,当我使用~操作符时,这个函数会做相反的事情。所以当我说~data.product_specification.str.contains时,我实际上是在做一个“不包含”函数,不会删除包含括号内值的字符串。
在括号内,我指定了我要寻找的正则表达式字符串。例如,如果我想在单词“卡片描述”后输入 0 个或更多单词,我可以说Card Description (\w+){0,}。在这之后的部分我会进一步解释。

现在我知道我的数据长度不会受到影响,我可以将product_specification列转换成 regex 功能的列表。

l = []
for i in data['product_specification']:
    l.append(i)

在这里,我将解释正则表达式语法以及如何从每一列中提取文本数据。在下面的代码行中,我提取了列表中每台笔记本电脑的屏幕尺寸,并将它们存储在一个名为“screen_size”的单独列表中。

screensize = []for words in l:
    screensize.append(re.findall(r"^Screen Size (?<![a-zA-Z:])[-+]?\d*\.?\d+", words))

screen_size = []
for sublist in screensize:
    for item in sublist:
        screen_size.append(item)

下图向我们展示了 regex 循环后 screen_size 的样子。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

如你所见,对于每台笔记本电脑,我都有单词“屏幕尺寸”以及存储在列表中的尺寸(15.6、17.3 等)。

因此,如果您仔细观察 regex 语法,您会发现我在单词“Screen Size”后面请求了一个浮点数

我们不能使用\d的原因是它们不是整数,而是浮点类型。因此,我必须指定将有一个“.”2 个整数之间带
\d*\.?\d+

让我们看另一个例子,提取每台笔记本电脑的处理器速度。

我们重复这些步骤,编写类似的行,同时更改 regex 语句。

processors = []for words in l:
    processors.append(re.findall(r"Processor (?<![a-zA-Z:])[-+]?\d*\.?\d+\s\w+", words))

processor = []
for sublist in processors:
    for item in sublist:
        processor.append(item)

下图向我们展示了 regex 循环后的processor的样子。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

现在,如果你看看这个和上一个的区别,你可以看到我在\d*\.?\d+后面加了\s\w+,因为现在我们在浮点数后面又多了一个词“GHz”。

因此,查看数据并了解您将为每个组件收集什么类型的信息非常重要。

我们需要为每个组件执行相同类型的循环。如果你在最后看我的要点,你会发现所有的例子都已经在里面了。

**注:**这只是第一部分!在即将发布的下一篇文章中,我会将所有这些列表存储到我的 DataFrame df中,并执行更多的清理操作,例如清理名称、替换不常用的类别、删除$符号和单位,如“GHz”或“GB”。

第一部分结束

下面,你可以找到我的笔记本,里面有清理这些数据的所有代码。

The “Cleaning” notebook.

请继续关注更多—第 2 部分即将推出。

关注 Rohan Gupta,了解其他数据科学内容和教程!

用熊猫清理网络抓取的数据(下)

原文:https://towardsdatascience.com/cleaning-web-scraped-data-with-pandas-part-ii-8520f47cc929?source=collection_archive---------32-----------------------

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

Photo by Oliver Hale on Unsplash

如何从网络搜集的数据中创造意义,用于数据分析和机器学习

这篇文章是我之前关于清理网络抓取数据的讨论的延续。您可以通过以下链接访问第一部分:

[## 用熊猫和 Regex 清理网络抓取的数据!(第一部分)

如何从网络搜集的数据中创造意义,用于数据分析和机器学习!第 2 部分—即将推出!

towardsdatascience.com](/cleaning-web-scraped-data-with-pandas-and-regex-part-i-a82a177af11b)

|| I ||数量与质量(数据)

正如我在之前的帖子中提到的,清理数据是机器学习的先决条件。测量数据的健全性也可以很好地表明模型的精确程度。说到网络抓取的数据,你经常会在清理的过程中丢失很多信息。那么应该是什么呢?数量还是质量?

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

Photo by JOSHUA COLEMAN on Unsplash

回答这个问题并不容易,因为它实际上取决于案例和数据科学家制定的流程。

如果您最终处理的数据需要较少的特异性来处理它的变量,那么您可以选择使用数量,并且应该可以使用数据清理方法,这些方法可以使用推理数据来替换值。

但是,如果您处理的数据确实需要特殊性。例如,在这种情况下,我们正在处理笔记本电脑数据。使用平均值、中间值或众数来替换我们数据中缺失的值是没有意义的,因为有多种类别的笔记本电脑具有不同的规格组合。例如,如果 i5 是列“处理器”的模式,您不能让所有缺少的值都等于 i5,因为这会扭曲数据,产生很大的偏差。

因此,您可以通过删除缺少值的行来清除数据。这将把数据从 440 个值减少到 153 个值。我知道这对机器学习模型来说不太好,但作为一名数据科学家,我明白我的结果质量将与我的数据质量密切相关。因此,保持整洁是最好的方法,否则我将不得不手动检查每一行缺失的数据来填补空白。
— ———‘废话少说——我们走吧 — — —

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

Photo by Smart on Unsplash

|| II ||映射并存储到数据框架中

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

在使用 regex 从每个字符串中提取出所需的文本片段以获得每个新数据列的列表后,我停止了第 1 部分。图中显示了每一列:

  • screen_size:屏幕尺寸
  • processor : GHz
  • processor_type:名称
  • ram : RAM
  • storage:数量(HDD/SSD)
  • chipset:显卡品牌
  • gc:显卡类型

获得这些列表后,我们现在可以将它们作为列添加到我们的数据框架中。我们只需将每个 DataFrame 列分配给其各自的 list 对象,然后映射每行的文本,这样就可以只保留相关的数据。下面的代码行是我如何为每一个专栏做这件事的例子。例如,在列表 screen_size 中,我们得到了整个字符串“屏幕尺寸 15.6”,而我们只想要“15.6”。我们也希望每行都这样,所以当我们使用映射函数去掉前 12 个字符:*“屏幕尺寸 15.6”→“15.6”。*同样,您应该能够理解不同列的其他示例。

#Screen Size
data['Screen_size']= screen_size
data['Screen_size']= data['Screen_size'].map(lambda x: str(x)[12:])#Processor Power
data['Processor_power'] = processor
data['Processor_power'] = data['Processor_power'].map(lambda x: str(x)[10:])#Processor Type
data['Processor_type'] = processortype
data['Processor_type'] = data['Processor_type'].map(lambda x: str(x)[5:-2])#RAM
data['RAM'] = ram
data['RAM'] = data['RAM'].map(lambda x: str(x)[:])#Storage & Storage Type
data['Storage'] = storage
data['Storage'] = data['Storage'].map(lambda x: str(x)[:])
data['Storage_type'] = data['Storage'].map(lambda x: x.split()[2])
data['Storage'] = data['Storage'].map(lambda x: x.split()[0] +  x.split()[1])#Graphics Card brand
data['Graphics_brand'] = chipset
data['Graphics_brand'] = data['Graphics_brand'].map(lambda x: str(x)[:])#Graphics Card Type
data['Graphics_type'] = gc
data['Graphics_type'] = data['Graphics_type'].map(lambda x: str(x)[:])

结果应该如下所示:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

clean-ish data…

|| III ||数值型数据是真正的数值型

接下来,我将把每个数字列转换成一个真正的数字列,允许它反映所包含的数据类型(整型或浮点型)。为此,我必须从销售价格列中删除’ $ '符号,然后将其转换为数字。对于屏幕尺寸,由于我们只有数字,我们只需要转换它。确保使用 **errors='coerce'**

salesprice = data['sales_price'].str.lstrip('$')
salesprice = pd.to_numeric(salesprice, errors='coerce')
salesprice.fillna(salesprice.mode()[0], inplace=True)
screensize = data['Screen_size']
screensize = pd.to_numeric(screensize, errors='coerce')

那很容易。现在让我向你展示用rstriplstrip做同样事情的另一种方法。这次我将使用列processor_powerram。我们希望在第一种情况下删除“GHz ”,在第二种情况下删除“GB”。

data['Processor_GHz'] = data['Processor_power'].str.rstrip('GHz')
data['RAM_GB'] = data['RAM'].str.rstrip(' GB')

上面几行应该已经在您的数据中创建了新的列,现在可以将这些列转换为数字。

|| IV ||保持分类数据的相关性

当你有分类数据时,最好少于 5 个类别,因为这会使你的数据不那么模糊。在 processor_type 列中,我可能不能拥有少于 5 个类别,但是我肯定可以将所有相似的对象移动到一个类别中。比如“AMD A4”和“AMD A6”都可以归入“AMD A 系列”。我还添加了一个名为“Other”的类别,包含该列中不常用的值。因此,下面几行:

#Removing "Intel" since we would come to know by the name.
processortypez = data['Processor_type'].str.lstrip(' Intel')pt = []
for i in processortypez:
    if i == 'core_m':
        i = 'M Series'
    elif i == 'AMD A4' or i =='AMD A6' or i=='Apple A6':
        i = 'AMD A Series'
    elif i == 'i7' or i == 'Core':
        i = 'Core i7'
    pt.append(i)data['Processor_type'] = pt
proz = data['Processor_type']pz = []
for i in proz:
    if i != 'Core i3' and i != 'Core i5' and i != 'Core i7' and i != 'Celeron' and i != 'Pentium' and i != 'AMD A Series' and i != 'AMD R Series':
        i = 'Other'
    pz.append(i)data['Processor_type'] = pz

我对“存储”列做了类似的操作,其中 1TB 是 1000GB,因为我将删除“GB”部分,所以我将有一个单一的度量单位,将所有 TB 转换为 GB。

strzz = data['Storage']
zz = []
for i in strzz:
    if i == '1TB':
        i = '1000GB'
    elif i == '2TB':
        i = '2000GB'
    elif i == 'FlashMemory':
        i = '64GB'
    zz.append(i)
data['Storage'] = zz
data['Storage_GB'] = data['Storage'].str.rstrip('GB')

我也对许多其他需要修复的列做了同样的事情。你可以在提供的要点(在底部)中找到所有完整的例子。

最后,我将删除不需要的列:

data = data.drop(columns=['Processor_power', 'RAM', 'Storage'])

已清理数据帧的快照:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

Clean and ready-to-use data.

我运行了一个快速散点图来查看数据,猜猜我发现了什么:

import seaborn as sbscatplot = sb.relplot(x="RAM", y="Screen_size", data=data)

下面你可以看到散点图。注意到什么奇怪的事了吗?

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

其中一个屏幕尺寸写着“156”!这不可能。它应该是 15.6,所以让我们继续快速更改它:

scrnsize2 = []
for i in screensize:
    if i == 156.0:
        i = 15.6
    scrnsize2.append(i)
df[screen_size]=scrnsize2

第二部分结束

感谢阅读。我希望你从这两部分中学到了有用的东西!下面,你可以找到我的笔记本,里面有清理这些数据的所有代码。

The “Cleaning” Notebook

关注 Rohan Gupta,了解其他数据科学内容和教程!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值