机器学习中实际数据与模拟数据的结合
使用蒙特卡罗方法从给定的样本数据中模拟更多的数据
使用机器学习,你需要大量的数据,所以有时将实际数据与模拟数据相结合是有意义的,以便在数据中找到正确的模式或用于模型建立。
Central limit 表示数据集越大越好,因为当样本量变得非常大时,总体均值接近样本均值。通过生成样本数据集的复制副本,我们可以估计不同人群的样本均值,以确保均值是稳定的。我们也可以在平均值的预测中估计方差(不确定性)。
为了从给定的样本数据中模拟数据,我们需要能够识别样本数据中的模式,还需要了解一些关于特征及其分布的知识。例如,如果我有一个男性身高的小样本,并且我知道身高遵循正态分布,我可以使用以下公式生成男性身高的数据集:
mu = mean(male_heights)sd = sd(male_heights)N = 1000simulated_data = rnorm(N, mu,sd)
本文将讨论如何使用蒙特卡罗(MC)模拟来生成样本数据集的近似复制副本。然后对这些数据块中的每一个进行训练。然后,通过对包括实际和模拟数据的数据块执行整体平均,计算模型的整体性能。
**简介:**预测贷款的状况是风险评估中的一个重要问题。银行或金融机构在向客户发放贷款之前,必须能够估计所涉及的风险。数据科学和预测分析在构建可用于预测贷款违约概率的模型中发挥着重要作用。在这个项目中,我们获得了包含 50000 个数据点的 loan_timing.csv 数据集。每个数据点代表一笔贷款,提供两个特征如下:
- 标题为“自发起以来的天数的列表示发起和数据收集日期之间经过的天数。
- 对于在收集数据之前销账的贷款,标题为“从发放到销账的天数的列表示发放和销账之间的天数。对于所有其他贷款,此栏为空白。
技术术语的定义
- **发放:**指借款人从贷款人处收到贷款的日期。
- **冲销(贷款违约)状态:**借款人定期还款,直到借款人在贷款期限结束前停止还款,通常是由于财务困难。这一事件被称为冲销,然后贷款被称为已冲销或处于违约状态。
- 当前或活跃状态:借款人在整个贷款期限内继续还款。至此,债务已全部还清。
- 贷款期限:贷款协议生效的期限,在此期限之前或结束时,贷款应被偿还或重新协商另一期限。在这个例子中,我们考虑一笔期限为 3 年的贷款。
**项目目标:**该项目的目标是使用数据科学的技术来估计这些贷款(在 loan_timing.csv 数据集中的 50,000 条客户记录)在 3 年贷款期限内将被冲销的比例。
本文的数据集和 R 代码可以从这个资源库下载:https://github . com/bot 13956/Monte _ Carlo _ Simulation _ Loan _ Status。
使用 R 的模型实现
1.导入必要的库
library(readr)
library(tidyverse)
library(broom)
library(caret)
2。导入数据集并为分析准备数据
df<-read_csv("loan_timing.csv",na="NA")names(df)=c("origination","chargeoff")**# partition data set into two: default (charged off ) and current**index<-which(!(df$chargeoff=="NA"))default<-df%>%slice(index)current<-df%>%slice(-index)
3。探索性数据分析
A)实际数据
**# Plot of days to charge-off vs. days since origination for defaulted loans using actual data**default%>%ggplot(aes(origination,chargeoff))+geom_point()+xlab('days since origination')+ ylab('days to charge-off')+ggtitle("days to charge-off vs. days since origination")+theme(plot.title = element_text(color="black", size=12, hjust=0.5, face="bold"),axis.title.x = element_text(color="black", size=12, face="bold"),axis.title.y = element_text(color="black", size=12, face="bold"),legend.title = element_blank())
B)模拟数据
**# Monte Carlo Simulation of Defaulted Loans**set.seed(2)N <- 3*365 # loan duration in daysdf_MC<-data.frame(u=round(runif(15500,0,N)),v=round(runif(15500,0,N)))df_MC<-df_MC%>%filter(v<=u)df_MC<-df_MC%>%filter(u<=730 & v<=730) #select loans within first 2 yearsdf_MC[1:nrow(default),]%>%ggplot(aes(u,v))+geom_point()+xlab('days since origination')+ylab('days to charge-off')+ggtitle("MC simulation of days to charge-off vs. days since origination")+theme(plot.title = element_text(color="black", size=12, hjust=0.5, face="bold"),axis.title.x = element_text(color="black", size=12, face="bold"),axis.title.y = element_text(color="black", size=12, face="bold"),legend.title = element_blank())
Actual and MC simulation of days to charge-off vs. days since origination.
因为存在与贷款冲销相关的随机性,我们看到 MC 模拟为违约贷款的分布提供了一个很好的近似。
**预测:**由于我们已经证明,在最初 2 年(即 0 至 730 天)中,可以使用 MC 模拟来近似计算待冲销天数和自发放以来天数之间的关系,因此我们可以使用 MC 模拟来预测在所有 3 年期限结束时将被冲销的贷款比例。
我们数据集中冲销贷款的总数是 3,305。这意味着目前有 46,695 笔贷款处于活跃状态。在这些活跃的贷款中,一定比例的贷款将在 3 年内违约。为了估计违约贷款的总比例,我们模拟了涵盖整个贷款期限(即 0 至 1095 天)的违约贷款的冲销和自发放以来的天数,然后通过适当的缩放,我们计算了在 3 年期限(即 1095 天)后将冲销的贷款比例。
**# Predicting fraction of these loans will have charged off by the time all of their 3-year term is finished.**set.seed(2)B<-1000fraction<-replicate(B, {df2<-data.frame(u=round(runif(50000,0,N)),v=round(runif(50000,0,N)))df2<-df2%>%filter(v<=u)b2<-(df2%>%filter(u<=730 & v<=730))total<-(nrow(df2)/nrow(b2))*nrow(default)100.0*(total/50000.0)})
mean(fraction)**# Histogram of total fraction of charged off loans**fdf<-data.frame(fraction=fraction)fdf%>%ggplot(aes(fraction))+geom_histogram(color="white",fill="skyblue")+xlab('fraction of charged off loans after 3-year term')+ylab('count')+ggtitle("Histogram of total fraction of charged off loans")+theme(
plot.title = element_text(color="black", size=12, hjust=0.5, face="bold"),
axis.title.x = element_text(color="black", size=12, face="bold"),
axis.title.y = element_text(color="black", size=12, face="bold"),
legend.title = element_blank()
)**# Calculate Confidence Interval of Percentage of Defaulted Loans after 3-year term**mean<-mean(fraction)sd<-sd(fraction)confidence_interval<-c(mean-2*sd, mean+2*sd)confidence_interval
通过创建 N = 1000 次随机试验,我们获得了 3 年期违约贷款比例的以下分布:
图 6:使用 N = 1000 个样本的 3 年期后冲销贷款比例直方图。
根据我们的计算,3 年贷款期限后将被冲销的贷款部分的 95%置信区间相应地为 14.8% +/- 0.2%。因此,如果发放贷款期限为 3 年的 50,000 笔贷款,这些贷款中大约有 15%将会违约。
**结论:**我们提出了一个基于 MC 模拟的简单模型,用于预测在 3 年贷款期限结束时违约的贷款比例。蒙特卡洛模拟是一种重要的方法,可用于规定分析中,规定在数据集本质上非常随机的情况下要采取的行动过程。
本文的数据集和 R 代码可以从这个资源库下载:https://github . com/bot 13956/Monte _ Carlo _ Simulation _ Loan _ Status。
组合熊猫数据帧:简单的方法
Cheatsheet 版本:何时使用 Concat,何时使用 Merge
当我第一次使用 Python 进行数据分析时,我真的不知道什么时候使用追加、连接、合并或连接。一开始,每当我试图合并两个数据框时,我都要用谷歌搜索。因此,我想在这里分享我的经验,并给出一个简单的结合数据框架的介绍。
拼接合并就够了
您可以使用 concat 和 merge 做任何事情,因此不需要在开始时使用 append 和 join。由于在某些情况下代码更短,所以使用追加和连接可以节省一些时间。基本上,您可以用 append 和 join 做的所有事情,也可以用 concat 和 merge 来做。这就是为什么我们将重点放在这两个命令上。
如果您尝试合并两个数据集,首先要做的是决定是使用 merge 还是 concat。找到正确答案有一个简单的规则。如果数据帧的内容与组合数据帧相关,则必须选择 merge,否则可以选择 concat:
让我们从 Concat 开始
你可以用上面的图片作为开始的备忘单。假设您在公司的数据科学部门工作,销售部门每月都会向您发送新的销售数据。上个月和本月的数据框架中有两列:
Fig 1: Df_sales on the left side and df_sales2 on the right side
第一列包含关于经销商的信息,第二列包含去年售出的单位数量。因此,数据集的结构每个月都是相同的,你只需将两个数据框架组合起来。在这种情况下,您需要使用 concat:
Fig 2: The first use of concat with axis=0
Ignore_index=True
用于创建新的索引。如果您不考虑它,您将保留原始索引。
在这种情况下,我们将两个数据帧组合在一起。如果您想并排组合两个数据帧,您必须用axis=1
指定命令:
Fig 3: Concat with axis=1
数据帧的内容与合并它们相关——如何使用 Merge
在第一个例子中,我们每个月都从销售部门收到具有相同数据结构的文件。现在想象一下,市场研究部门给你发送市场数据进行比较。对于每个经销商 ID,您还会收到市场数据,其中包括竞争对手的销售额:
Fig 4: The sales data on the left side and the market data on the right side
但是正如你在上面的图片中所看到的,你没有每个经销商所需要的市场数据。因此,您有三个选项来合并上面的数据:
1.您只保留两个数据帧的交集(这意味着索引从 0 到 9 的行):
一号和二号
2.您保留左侧或右侧数据帧的所有信息,而其他数据帧仅保留匹配信息:
1 号、2 号和 3 号或者 1 号、2 号和 4 号
3.您保留两个数据帧的所有信息:
1 号,2 号,3 号和 4 号
选项 1-两个数据帧的交集
你所在部门的老板要求从总份额中获得经销商销售额的份额。因此,他只需要经销商和市场销售可用的经销商 id。在这种情况下,您只需保留具有相同经销商 id 的数据集部分。您必须使用内部连接来保持两个数据帧的交集:
Fig 5: The intersection of the sales and market data
注意:如果不指定“how”参数,默认选项将是内部联接。
选项 2 —一个数据帧的全部内容和另一个数据帧的匹配部分
您从两个不同的部门接收数据集。销售部门的一个要求可能是,他们想要回他们的原始数据框架和匹配的市场数据:
Fig 6: The full content of the sales data and the matching part from the market data
在这种情况下,您必须执行左连接。只有在市场数据集中找到销售数据中的经销商 ID 时,才需要添加市场数据。
选项 3 —保留两个数据帧的所有信息
最后,市场部门希望在不丢失任何信息的情况下将两种数据框架结合起来:
Fig 7: The outer join
在生成的数据帧中,两个基本数据帧中没有一个值丢失。最后,外部连接就像一个左连接和一个右连接的组合。
结论和进一步阅读
我希望您喜欢这篇介绍,它有助于阐明 concat 和 merge 命令背后的逻辑。从一开始就有了备忘单,你应该能够决定你必须使用哪个命令。要了解更多信息,我可以推荐以下资源:
- 官方文件
[## 熊猫。DataFrame.merge - pandas 0.25.0 文档
用数据库样式的联接合并数据框架或命名系列对象。联接是在列或索引上完成的。如果加入…
pandas.pydata.org](https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.DataFrame.merge.html)
2.关于 concat 和 merge 区别的 stackoverflow 讨论
[## pandas 中 merge()和 concat()的区别
感谢贡献一个堆栈溢出的答案!请务必回答问题。提供详细信息并分享…
stackoverflow.com](https://stackoverflow.com/questions/38256104/differences-between-merge-and-concat-in-pandas)
3.关于归并和连接区别的讨论
假设我有两个这样的数据帧:left = pd。DataFrame({‘key1’: [‘foo ‘,’ bar’],’ lval’: [1,2]}) right =…
stackoverflow.com](https://stackoverflow.com/questions/22676081/what-is-the-difference-between-join-and-merge-in-pandas/37891437)
如果您喜欢中级和高级数据科学,并且还没有注册,请随时使用我的推荐链接加入社区。
结合 python 和 d3.js 创建动态可视化应用程序
视觉形式是催眠和逮捕,不像任何其他媒体。一幅画或一幅图像迫使眼睛看到整个画面,并呈现出一种不受时间限制的形式。难怪视觉在试图理解和解决复杂问题时有助于采用非线性视角。
另一方面,通过数据分析和编程来解决问题仍然在很大程度上植根于线性视角,因为它们涉及到逐步分解数据来理解和解决问题。然而,数据分析如果做得正确,可以让用户考虑到细枝末节的细节和特质,而这些细节和特质通常是在观察全局时被忽略的。将数据驱动方法与更加可视化的方法相结合,提供了一种结合线性和非线性视角的问题解决和分析的整体方法。在本文中,我通过一个详细的、可重复的例子解释了用户如何结合 python(一种强大的数据处理编程语言)和 d3.js(一种强大的生成可视化的语言)来创建一个可视化应用程序,为问题解决者提供有用的见解。
我将演示的是用户如何有效地在 python 中创建数据处理后端,同时在 d3.js 中维护可视化前端,以创建有效的应用程序。我们还将添加一些可控制的功能,以便前端和后端可以根据最终用户的输入有效地相互通信。我们将使用的 python 模块是“Flask ”,它将充当后端和前端之间的中介。我选择的 d3 可视化是 Mike Bostock 创建的可折叠条形图示例。可视化结构的创建将涉及到 html、js 和一些 jinja 代码的使用。
问题—
我们将使用来自 FAOSTAT 数据库的农业生产数据。粮农统计数据库提供了 213 个地区不同年份的几个变量的数据,按作物类型、肉类和鱼类分类。**我们将通过一个动态可视化应用程序,尝试了解和探索 FAOSTAT 数据在不同时间、不同国家间的汇总和分解情况。**您可以在这里找到用于本例的编辑过的数据集。我们将使用两个数据集,一个是按不同类型的作物、肉类和鱼类分列的产量,另一个是按相同类别分列的农业损失。这两个数据集都包含 213 个地区从 2010 年到 2013 年的数据。我们将创建一个应用程序,帮助用户使用可折叠的条形图可视化来比较任何类别或子类别的损失和产量。用户还应该能够选择任何国家和年份来创建这些国家的可视化。基本上,最终(编辑)的产品看起来像下面的图片和 gif,
See the gif below to see how the app works
This gif demonstrates the usage of the application on a local machine
第 1 部分:定义应用程序的结构
我们将要做的,是在一个 html 页面上创建一个前端,它将托管我们的可视化和 d3.js 脚本。我们将把包含在名为“application.py”的文件中的 python 代码中的数据发送到这个 html 页面。计算机上应用程序的结构如下。
#Application file structureroot/ static/
This folder contains your supplementary JavaScript files
/templates
This folder contains the index.html fileapplication.py (The main python file and data are hosted in the root folder)
下面是该应用程序的图示
Source: Author’s conception
第 1 部分:定义前端(html,d3.js)
首先,让我们设计前端,这将是一个基本的 html 页面(“index.html”),它将承载我们的 d3 可视化以及一个用户可以提交国家和年份选择的表单。最终的 html 在这里托管。我不会走一些基本的东西,如 css 和格式等。你可以直接从 html 中获取或者根据你的喜好定制。创建基本 html 页面的步骤如下,
1.获取所需的所有脚本
2.创建一个用户可以更改国家和年份选择的表单。
3.创建“div”元素来承载可视化效果
4.插入 d3 代码来创建图形。我们还将使用 jinja 代码定义 python 后端和 d3 之间的链接。
您需要有基本的 d3 版本( d3.v3.min.js) ,您可以使用这个命令将它放入 html,
<script src=”https://d3js.org/d3.v3.min.js"></script>
让我们首先创建用户可以提交国家和年份信息的表单。这可以通过一些 html 代码来完成,这些代码将生成一个用户可以提交请求的“表单”。请注意,下面指定的名称(如“Country_field”和“Year_field ”)非常重要,因为在 python 的后端会再次引用这些名称。
<form method=”post” ><input name=”Country_field” placeholder=”Enter country name” > <input type=”number” name=”Year_field” placeholder=”Enter Year” > <input type=”submit” value=”Submit Country and Year” > </form>
现在,我们将创建两个分区,一个在左边托管生产数据图,另一个在右边托管损失数据。分部还应显示所选的国家和年份。我们必须为图表指定 id,并且必须用 jinja 编写一些代码来获取国家名称和年份。jinja 代码基本上使用花括号{{}}从 python 访问数据。将类别分配给分部有助于以后轻松添加格式。同样的代码是,
<div class=”left-div” id=”graphDiv” style=”border: thin solid black”>
<p>
<b>
<i>
Production data by category and sub-categories in 1000 tonnes for {{CountryName}}(Click on the bars to explore sub-divisions)</b>
<br> <br>
FAO defines production as “Unless otherwise indicated, production is reported at the farm level for crop and livestock products (i.e. in the case of crops, excluding harvesting losses) and in terms of live weight for fish items.”
</i>
</p>
</div>
上面的代码产生了这个结果,
Production division in html
我们将不得不重复相同的代码来为损失数据创建另一个分部。之后,让我们定义 d3 函数来创建图表。我定义了两个函数,svg1 和 svg2 分别用于左侧和右侧的图。我使用的代码大部分来自 Mike Bostock 的例子这里。我们只会做一些改动。首先,在代码中定义 svg 对象的地方,我们必须引用我们的 graph div ids,以便函数将在我们上面所做的划分中创建图形。所以,在我们的例子中,
var svg1 = d3.select(“**#graphDiv**”)
.append(“svg”)
.attr(“width”, width + margin.left + margin.right)
.attr(“height”, height + margin.top + margin.bottom)
.append(“g”)
.attr(“transform”, “translate(“ + margin.left + “,” + margin.top + “)”);
现在,如上所述,后端数据处理器将在 python 中构建。因此,我们必须使用下面的代码将数据从 python 传递到 js 脚本。“/get-data”是一个函数,我们将在稍后的 python 代码中定义它。“d3.json”将以 json 格式读入数据。
d3.json(“**/get-data**”, function(error, root){
partition.nodes(root);
x.domain([0, root.value]).nice();
down(root, 0);});
最后,我们在代码中对条形的颜色做了一点小小的调整。我们希望生产图用绿色条,损失图用蓝色条。我们将通过改变下面代码中的颜色变量来改变颜色,
var color = d3.scale.ordinal()
.range([“green”, “#ccc”]);
第 3 部分:用 python (flask)创建后端
创建 python 文件的步骤要花费更多的时间。最终的申请文件可在这里获得。我们需要执行以下步骤,
1.导入必要的包,在 flask 中定义应用程序并创建一个数据存储。
2.创建代码以生成发送到主页前端的数据。
3.将数据转换为 d3 的 json 格式,并发送到前端
4.类似地,专门为产量和损失图定义函数。
好了,让我们把简单的事情解决掉。让我们获取包,定义 flask 应用程序并创建一个包含 4 个变量的数据存储函数。datastore 变量将有助于稍后在将数据传递给前端之前保存数据。我们将创建一个“CountryName”变量和一个“Year”变量,用户将通过表单将这两个变量发送到应用程序。我们将创建一个“Prod”变量来存储生产数据,并创建一个“Loss”变量来存储损失数据。
#1\. Import packages**from** flask **import** Flask, flash, redirect, render_template, request, session, abort,send_from_directory,send_file,jsonify
**import** pandas **as** pd
**import** json
*#2\. Declare application*app= Flask(__name__)
*#3\. Create datastore variable***class** DataStore():
CountryName=**None** Year=**None** Prod= **None** Loss=**None** data=DataStore()
现在,让我们定义应用程序的主页。我们首先必须定义到主页的路径和一个主页功能,该功能将为主页创建数据。基本上正在发生的是,当用户访问主页面时,主页功能将被调用。我们还将使用一个名为“request”的简单 flask 函数从前端“获取”数据。当请求数据时,请注意我们使用 html 中定义的 id,如“Country_field”和“Year_field”。我们还为国家/地区设置了默认值印度,并为年度设置了 2013。我们还将把这个请求的数据传递给我们的数据存储函数变量‘Year’和‘country name’(数据存储变量和其他变量之间的区别解释如下)。最后,我们将读入生产数据,并为我们的分析创建名为 CountryName 和 Year 的变量。请注意,这些是将传递给 html 的实际变量,而不是存储在 python 内部的变量。一个很好的想法是,数据存储是 python 的内部内存,它会随着时间不断更新。静态临时变量是在单个时间点创建的值,将被传递到前端。如上所述,可视化是为 1 个时间点创建的,因此使用临时变量。
#We are defining a route along with the relevant methods for the #route, in this case they are get and post.
@app.route(**“/”**,methods=[**“GET”**,**”POST”**])#We are defining a home page function below. We will get the #CountryName and the Year from the form we defined in the html **def** homepage():
data.CountryName = request.form.get(**‘Country_field’**,**’India’**)
data.Year = request.form.get(**‘Year_field’**, 2013)
data.CountryName=CountryName
df = pd.read_csv(**‘CropsFull.csv’**)
CountryName = data.CountryName
Year= data.Year
现在,我们将根据从表单中收到的值过滤数据(df)。这是 python 中一个简单的过滤器。我们还将只保留相关的列以供进一步处理。我将 Year 变量转换为整数,因为有时请求会返回一个字符串,这可能会导致 python 无法过滤数据。
*# Filter the data frame (df)* df = df[df.Country == CountryName]
df = df[df.Year == int(Year)]#Keep only relevant columns
df = df[[**“Category”**, **“Cat”**, **“value”**]]
现在,我们需要将这个数据帧转换成一个分层的 json。json 根据数据中的聚合类别进行分层,因此对于可视化很有用。我已经附上了相同的代码如下。但是我使用了 Andrew Heekin 的代码来创建嵌套的 jsons。代码可以在这里找到。我不会在这里深入讨论代码的细节。
df1 = df.groupby([**‘Category’**, **‘Cat’**])[**‘value’**].sum()
df1 = df1.reset_index()*#Lets create a dict* d = {**"name"**: **"flare"**, **"children"**: []}
**for** line **in** df1.values:
Category = line[0]
Cat = line[1]
value = line[2]
*# make a list of keys* keys_list = []
**for** item **in** d[**'children'**]:
keys_list.append(item[**'name'**])
*# if 'the_parent' is NOT a key in the flare.json yet, append it* **if not** Category **in** keys_list:
d[**'children'**].append({**"name"**: Category, **"children"**: [{**"name"**: Cat, **"size"**: value}]})
*# if 'the_parent' IS a key in the flare.json, add a new child to it* **else**:
d[**'children'**][keys_list.index(Category)] [**'children'**].append({**"name"**: Cat, **"size"**: value})
flare = d
现在,我们必须将这些数据转储为 json 格式。每次我们给它赋值的时候,我们都可以使用 json load 函数来加载它。如上所述,让我们将这些数据保存到一个临时变量‘Prod’中以传递给前端,并保存到一个名为‘data’的 python 内存变量中。从我们的数据存储函数。
#Dump data to json
flare = json.dumps(flare)#Save to datastore
data.Prod = json.loads(flare)#Save to temporary variable
Prod=data.Prod
我们将使用上述步骤处理损失数据。我不会在这里重复完整的代码。损失代码的最后一行是,
#Dump data to json
flare = json.dumps(flare)#Save to datastore
data.Loss = json.loads(flare)#Save to temporary variable
Loss = data.Loss
最后,让我们用一个 return 语句来结束我们的函数。我们将使用 flask 'render_template '函数将数据发送到我们的前端(index.html '文件。我们还将返回所有临时变量,如国家名称、年份、产量和损失数据
**return** render_template(**“index.html”**,CountryName=CountryName,Year=Year,Prod=Prod,Loss=Loss)
上面的代码向主页面发送数据。我们还必须编写另外两个函数来将生产和损失数据发送给我们的 js 函数。假设我们有一个数据存储来记录我们的生产和损耗数据,这应该相当简单。让我们定义一个名为“/get-data”的路由,并将我们的生产数据发送给它。请注意,该函数返回数据的“jsonified”版本。
@app.route(**“/get-data”**,methods=[**“GET”**,**”POST”**])
**def** returnProdData():
f=data.Prod
**return** jsonify(f)
我们将在名为’/get-loss-data '的路径上为丢失数据创建一个类似的函数。
@app.route(**“/get-loss-data”**,methods=[**“GET”**,**”POST”**])
**def** returnLossData():
g=data.Loss
**return** jsonify(g)
最后,让我们定义运行应用程序的代码,
**if** __name__ == **"__main__"**:
app.run(debug=**True**)
这就是了。你的申请准备好了!继续运行它!运行代码时,您应该会得到以下消息,其中包含一个指向本地驱动器上的应用程序的链接。
* Running on [http://127.0.0.1:5000/](http://127.0.0.1:5000/) (Press CTRL+C to quit)
这个应用程序很容易部署在服务器上。我已经在免费的 heroku 服务器上部署了它。同样可以在这里访问。请注意,我使用的是 heroku 的免费版本,所以加载时间有点慢(您可能需要刷新应用程序几次)。我还添加了 requirements.txt 和。gitignore 和 procfile,以防您想将它部署到 heroku 或任何其他服务器上。这段代码显然很容易适应您喜欢的其他 d3 可视化!
非常感谢 Mike Bostock 创造了像 d3 这样精彩的语言,感谢 Andrew Heekin 编写了生成分层 jsons 的代码。感谢 David Bohl 和 Aditya Kulkarni 的反馈和评论。
为了您的参考和方便,我在下面附上了 github 库和其他资源的链接。
1.链接到 github 项目-【https://github.com/kanishkan91/FAO-FBS-Data-Explorer
2.链接到 heroku 服务器上部署的应用程序-【https://faoexplorer-flask-d3.herokuapp.com/
3.链接到 Mike Bostocks 可折叠条形图示例-https://observablehq.com/@d3/hierarchical-bar-chart
4.链接到 Andrew Heekin 创建分层 json 的代码-https://github . com/andrewheekin/cs v2 flare . JSON/blob/master/cs v2 flare . JSON . py
结合卫星图像和机器学习来预测贫困
这是尼尔·让等人对同名论文的 5 分钟评论。这是这篇文章的视频版本:https://youtu.be/bW_-I2qYmEQ。
P 发展中国家的过度评估影响着这些国家的政府如何分配有限的资源来制定政策和开展研究。
Neal Jean 等人在他们的论文中声称,他们通过使用机器学习和结合卫星图像,开发了一种检测和预测贫困的方法。
我们如何衡量广大地理区域的经济活动水平?一种可能性是观察它们的夜间发光强度。这些地区的卫星夜灯亮度图像与它们的经济活动水平之间存在关联。
然而,作者观察到,夜灯方法本身无法检测低于国际贫困线的地区的经济活动。参见图 1–2。
Figure 1
Figure 2
通常,政府依靠调查来收集经济数据并采取行动。但是应用这些调查并不简单,而且费用很高。
这就是作者提出的方法的用武之地。他们提出了一种基于“迁移学习”技术的机器学习方法,有望解决这一缺点,并提供比单独考虑亮度强度更高的预测精度。
他们声称他们预测贫困的方法是准确的、廉价的和可扩展的。他们是如何实现这种预测能力的?通过结合调查和卫星数据,可以训练卷积神经网络或 CNN 来辨别白天卫星图像上的特征。
在他们的研究中,他们考虑了 4 个非洲发展中国家,他们估计数据的粒度是在家庭层面。
他们的方法包括什么?它包括三个阶段(见图 3):
第一阶段他们训练 CNN 从日光卫星图像中学习特征。例如,这些特征是经济活动(或缺乏经济活动)的证据,如城市地区、非城市地区、水和道路。
阶段 2 利用阶段 1 中获得的知识,CNN 适于被训练以估计夜间光强度。
第 3 阶段涉及将经济调查数据和 CNN 从日光图像中提取的图像特征相结合,以训练回归模型,该模型能够估计他们正在考虑的贫困指标。
Figure 3
作者声称,他们的“迁移学习模型”可以高精度地预测他们的贫困指标。那些指标是什么?/他们处理两个问题,它们是消费支出和资产财富(他们声称已经分别实现了高达 55%和 59%的可变性解释。)参见图 4 和图 5。
Figure 4
Figure 5
在我看来,这篇论文提出了一种增强的、准确的和负担得起的技术,世界各地的政府和组织可以用它来跟踪和瞄准发展中国家的贫困,以采取缓解行动。它展示了机器学习在帮助改善人们的生活条件方面有多么强大。
我的网站是:【https://www.georgelopez-portfolio.com/
所有数字由尼尔·吉恩等人提供,并由 g·洛佩兹改编。
— — — — — — — — — — — — — — — — — — — — — — — — — — — — — — —
亲爱的读者:
我想知道你认为还有哪些使用 AI/ML 的人道主义应用可以实现?
你可以留下回应的评论,我会很乐意阅读。谢了。
结合监督学习和非监督学习改进词向量
生成性预培训简介
Photo by Edward Ma on Unsplash
为了在 NLP 任务中获得最先进结果,研究人员尝试了大量方法让机器理解语言并解决下游任务,如文本蕴涵、语义分类。OpenAI 发布了一个新的模型,命名为生成式预训练(GPT)。
看完这篇文章,你会明白:
- 微调变压器 LM 设计
- 体系结构
- 实验
- 履行
- 拿走
微调变压器 LM 设计
这种方法包括两个步骤。首先,通过基于大量数据的无监督学习来训练模型。第二部分是使用目标数据集(领域数据)通过监督学习对上一步的模型进行微调。
无监督学习
不可否认,NLP 的无标签数据是无限的。拉德福德等人认为,利用无限的语料库有助于训练一个通用模型,就像 word2vec (单词嵌入)和 skip-thought (句子嵌入)。我们不需要考虑训练数据的数量,因为我们可以很容易地获得大量的语料。
然而,仍然有一个限制。尽管我们可以尽可能多地使用语料库,但在大多数情况下,语料库与我们的领域数据是脱节的。从我以前的工作中,我注意到我的领域数据中的大部分单词并不存在于大量现成的单词嵌入模型中。
拉德福德等人没有使用 RNN 架构,而是使用变压器架构来训练第一个模型。因为他们相信变压器架构能够捕捉更远距离的信号(语言特征)。限制是高计算时间。由于拉德福德等人使用了 12 个自我注意块和高维内部状态,即使使用 GPU 也需要几周时间来训练初始模型。
监督学习
之后,目标数据集(与之前的数据集相比,在大多数情况下,它应该是一个小数据集)将被用于通过监督学习来微调模型。
体系结构
【无监督学习模型(Transformer)】
序列对序列(又名 RNN)模型有一个局限性,我们需要定义固定长度的上下文向量,这损害了记忆很长句子的能力。与此同时,注意力机制应运而生。该架构被称为“变压器”,是多头自我关注。在注意机制家族中,我们有许多不同的注意,拉德福德等人决定使用自我注意。
- 多头是指使用具有不同参数多个自我注意来计算表示。想想看,我们希望有多个专家来帮助找到一个更好的结果。多头机制并行执行具有不同参数相同计算。来自不同注意块的计算结果将被连接并转换到期望的维度。
Photo by JOSHUA COLEMAN on Unsplash
- 注意正在利用 CNN 的优势。自注意不依赖于先前的信息,因此它可以并行运行以实现更低的计算时间。同时,单词是直接计算所有已定义的单词,而不仅仅是环境。它克服了 RNN 缺乏记忆长句子能力的缺点。自我注意是注意机制家族的成员之一。注意力输入是 Q(查询)、K(键)和 V(值)。与其他成员不同,所有输入(Q、K 和 V)都是相等的。
Photo by Ari Spada on Unsplash
关于变压器的细节,你可以查看这张纸。回到架构,输入特征是文本和文本的位置来计算单词向量。文本位置是指输入的单词位置。流程是:
- 文本和位置将被转换成一个矢量
- 传递给多头自我关注
- 结合步骤 1 和步骤 2 的结果并执行归一化
- 传递到全连接的前馈网络
- 结合步骤 3 和 4 的结果并执行归一化
最后,将多头(共 12 个自关注块)组合在一起计算向量。
Transformer architecture (Radford et al., 2018)
型号规格为:
- 总共 12 台变压器
- 自我注意中的 768 维状态
- 位置前馈网络中的 3072 维内部态。
- 使用 Adam 优化,最大学习率为 2.5e-4
- 64 个小批量的 100 个时代
- 辍学率为 0.1%
监督学习
在训练了上一步的模型后,这种受监督的微调过程有助于获得目标任务的向量。假设输入是带有标签的输入符号序列,我们可以从预先训练的模型中得到一个符号的向量。
Input Transformations for fine-tuning on different tasks (Radford et al., 2018)
实验
Experimental Result on Natural Language Inference Tasks ( Radford et al., 2018)
Experimental Result on Question Answering and Commonsense Reasoning Tasks ( Radford et al., 2018)
拿走
- 展示了针对特定领域数据的微调能力。
- BERT 的设计与该模型相似,而 BERT 进一步改进了该模型的局限性。
- 作者注意到这个架构设计没有进一步的改进。(在 github 中提到)
关于我
我是湾区的数据科学家。专注于数据科学、人工智能,尤其是 NLP 和平台相关领域的最新发展。你可以通过媒体博客、 LinkedIn 或 Github 联系我。
参考
拉德福德·a·纳拉辛汉·k·萨利曼斯·蒂姆,苏茨基弗一世…2018.通过生成性预训练提高语言理解。
瓦斯瓦尼 a,沙泽尔 n,帕尔马 n,乌兹科雷特 j,琼斯 L,戈麦斯 a n,凯泽 L…2017.你所需要的只是注意力。
来和我一起飞吧!
分析航空业 twitter 数据的自然语言处理项目。人们在推特上谈论的话题和需要改进的地方!
在数据科学训练营的最后 3 周,我们接近了我们的最终项目。很难决定一个项目不仅能让科学展览会上的个人感兴趣,还能让公司案例分析感兴趣。我们需要在网上收集可用的数据,这是我努力的方向。找到涉及航空业的标记数据是一个挑战,但这一切都归功于文本和 NLP!
这个最终项目的概述包括分析国内 8 大航空公司的 twitter 数据,并使用 NLP(自然语言处理)来确定每家航空公司需要改进的地方!我总是阅读关于什么是最好的航空公司的评论,这样我就能够看到和分析人们实际上在推特上谈论什么。作为我的“最小可行项目”,我想解决两个主要问题
- 当人们在推特上谈论一家航空公司时,他们大多是负面的吗?
- 人们大多在谈论什么话题?
目标:
- 使用主题建模对每家航空公司的不同兴趣领域进行分类。
- 使用情绪分析来查看正面和负面的推文。
- 可视化我的数据!
我的过程很简单,也很清晰。我首先使用 Twint 网络抓取工具收集数据。我收集了以下 8 家国内航空公司的数据:
主要航空公司:
- 美国联合航空公司
- 美国航空
- 达美航空
- 阿拉斯加航空公司
廉价航空公司:
- 西南航空公司(最大的廉价航空公司)
- 捷蓝航空
- 前沿(超低价)
- 精神(超低价)
然后,我使用 pandas 和 python 清理了我的数据,清除了所有标点符号和拼写错误的单词。根据推文长度评估 twitter 数据,并对数据进行情感分析。Vader 是我用来识别带有评级的正面和负面推文的工具。查看以下每条推文的{}评分。
反面例子:
- “对联合航空公司非常不耐烦” {‘neg’: 0.73,’ neu’: 0.27,’ pos’: 0.0,’ compound’: -0.7964}
- {‘neg’: 0.867,’ neu’: 0.133,’ pos’: 0.0,’ compound’: -0.7579}
正面例子:
- “太棒了,我真的很爱很爱很爱那里的美丽” {‘neg’: 0.0,’ neu’: 0.178,’ pos’: 0.822,’ compound’: 0.9753}
建模
航空公司项目的下一步是使用主题建模来评估大多数人正面和负面谈论的内容。我使用了 LDA 模型和代表潜在狄利克雷分配的可视化。
在自然语言处理中, LDA 是一种生成统计模型,它允许通过未观察到的组来解释观察集,从而解释为什么数据的某些部分是相似的。
我做了一个连贯性分数测试来评估所学主题的质量。你可以看到我们在 3 个主题上的最高一致性分数是 0.44。
t-SNE Cluster Analysis to visualize no overlap between topics.
3D t-SNE Cluster Analysis
LDA Visualization
上面是一个 LDA 可视化,以便看到每个主题的分离。
Word Clouds
查看每个话题的每个词云。我惊讶地看到话题 0 中有一串负面词汇,话题 1 中有中性/计时词汇,话题 2 中有正面词汇。这意味着我们可以清楚地区分这些推文的不同区域。所以现在让我们看看哪个航空公司在每个话题上的优势!
*我根据权重较大的单词重命名了主题
- 主题 0 =糟糕的客户服务
- 主题 1 =时间和延迟
- 话题 2 =绝佳体验
分析
你可以清楚地看到,大多数人对航空公司持负面态度。所以要考虑到偏见,这并不一定意味着他们都有糟糕的客户服务…这是推特上最常被提及的。但你可以看到,超低价航空公司被评为最差的客户服务,超过 50%。联合航空公司的推文在时间和延误方面是最高的,我很震惊地看到达美航空公司的客户服务推文比美国航空公司和联合航空公司还多。
更多视觉效果如下:
来吧,棉绒一点:用棉绒清理你的代码
你知道他们怎么说的,“内在才是最重要的。”虽然这可能是真的,但你仍然必须找到一种方式来表达里面的内容,以便人们能够理解它。您可能拥有您所编写过的功能最强的代码。这可能是优雅的,彻底的,万无一失的。但看起来还是这样:
def buy_me_dinner():
"lala"
print("yummy dinner")
def attack():
"oh"
print("I love you" to)from fileinput import input
suspects = input()[9:].split(',')
weapons = input()[8:].split(',')
rooms = input()[6:].split(',')clues = input()
for line in input():
clue = line.split(' ')
if clue[1] != 'the':
suspects.remove(" ".join(clue[1:]))
else:
thing = " ".join(clue[2:])
if thing in weapons:
weapons.remove(thing)
else:
rooms.remove(thing)
print(suspects[0])
print("in the " + rooms[0])
print("with the " + weapons[0])
这就是棉绒派上用场的地方。linter 是一种分析代码以标记风格或编程错误的工具。我今天使用的 linter 是 pycodestyle ,它基于 Python 的 Pep-8 风格指南。Linter 会给你一个发现错误的列表,这样你就可以一个一个的修复它们,并且希望在将来学习如何编写更好的代码来遵循你选择的风格指南。还有其他像布莱克这样的 linters,它会自动重新格式化你的代码,但是你不会通过这种方式学到任何东西,所以当开始使用 linters 时,最好使用一些冗长的东西。
让我们看看当我们用 pycodestyle lint 上面的代码时会发生什么。安装完成后,只需在终端上输入
pycodestyle <name of file to lint>
你会得到这样的回报:
messy.py:4:1: E302 expected 2 blank lines, found 0
messy.py:4:14: W291 trailing whitespace
messy.py:13:1: E303 too many blank lines (6)
messy.py:13:1: E402 module level import not at top of file
messy.py:14:1: W293 blank line contains whitespace
messy.py:15:6: E111 indentation is not a multiple of four
messy.py:15:6: E113 unexpected indentation
messy.py:16:2: E901 IndentationError: unindent does not match any outer indentation level
所以第 4 行的第一个字符在空行的数量上有问题,然后第 14 个字符后面有尾随空格,依此类推。在我们采纳了它的建议后,代码看起来是什么样的?有趣的是,有时修复一个错误会暴露另一个错误。但最终我们会得到这样的结果:
只要看看它,你就能知道 Pep-8 是如何格式化代码的。在导入库之后应该有 2 个空格,在声明一个函数之后应该有 2 个空格,并且在文件的末尾应该有一个新行。这些棉绒并不完美,有时你会发现他们没有发现的风格错误。此外,pycodestyle 不会发现功能错误。但是我可以用 mypy 检查这段代码中的某些类型的错误。mypy 的用法也一样。
mypy <name of file to check>
它会检查错误。对于上面的代码,它返回
messy.py:12: error: invalid syntax
它告诉我 print 语句中第 11 行的语法错误。使用像这样的简单工具可以使您的代码看起来有很大的不同,并减少调试时的挫败感。我听说过一些经理甚至不看没有经过测试的代码,所以养成这个习惯是个好习惯,除此之外,这也是了解你的代码应该是什么样子,以及你最常犯错误的地方的好方法。
快乐的编码冒险!
每个数据科学家都应该知道的命令行基础知识
使用命令行完成简单任务的指南
Photo by Almos Bechtold on Unsplash
如果您是一名数据科学家或正在学习数据科学,并且希望超越使用 jupyter 笔记本来编写生产就绪的代码,那么您可能需要使用命令行来完成一些任务。我发现生产数据科学工具和过程的文档通常假设您已经知道这些基础知识。然而,如果你没有计算机科学背景,那么你可能不知道如何从终端完成一些简单的任务。
我想写这篇简短的指南,介绍使用命令行执行简单任务的绝对基础知识。了解这些基础知识无疑会使您的工作流程更加高效,并且在使用生产系统时会有所帮助。
导航文件和目录
当你打开终端时,这通常是你会看到的。
~
符号是你的主目录的简写,所以这意味着你当前在这个目录中。如果你输入命令pwd
,它将显示你当前的工作目录,在我们的例子中看起来像这个/Users/myname
。
如果你想创建一个新目录,你可以输入下面的mkdir test
这将创建一个名为 test 的新目录。你现在可以使用cd
命令导航到这个目录。
您也可以通过键入..
来导航目录,这将带您返回一个目录。所以在我们的例子中,我们将返回到主目录。
使用文件和目录
接下来,让我们在测试目录中创建新的 python 文件。要创建一个文件,你可以输入这个命令touch test.py
。这将创建一个空白的 python 文件。ls
命令将一个目录的内容打印到终端,因此我们可以用它来检查我们的文件是否已经创建。
我们将使用名为 nano 的程序编辑该文件。要打开文件,你只需输入nano test.py
,一个新的标签将会打开,如下所示。
对于这个例子,我们将只做一个小的改变,所以我们将输入print('this is a test')
。要保存文件,您可以使用快捷键Ctrl+O
并退出程序Ctrl+X
。
现在我们已经编辑了文件,我们可以使用命令python test.py
运行它。执行简单的 python 代码,并在终端上显示“这是一次测试”。
移动和删除
下面我们快速新建一个目录mkdir new
来探究一下如何移动文件。有三种主要的方法你可以做到这一点。
正斜杠()前的【T1。/new)是父目录(test)的简写。
- 制作一个副本并移动副本以将原始文件保留在当前位置
cp test.py ./new
- 移动文件而不复制
mv test.py ./new
- 复制文件,并在新位置
cp test.py ./new/test_new.py
重命名文件
最后,要删除一个文件,我们可以使用rm test.py
。要删除一个空目录,您可以使用rmdir new
。删除包含一些文件的目录rm -rf new
。
本文涵盖了数据科学家可能需要从命令行完成的一些最基本的任务。如果你想探索一些更高级的命令行工具,我在这里写了另一个指南。
感谢阅读!
英国大选的命令行制图
为选举之夜制作英国选区地图。
本教程改编自 @mbostock 的 命令行制图教程 的步骤,展示如何使用免费开源 Javascript 工具轻松制作英国选举结果专题地图。然后,我还使用 Python/Pandas/GeoPandas 堆栈重复了数据管理部分,以比较每种方法的简便性。
这是我们将要做的观想:
UK General Election 2017 results: Winners by constituency.
这张专题地图通过根据获得最多选票的政党给每个议会选区着色,传达了上次英国选举的结果。在此过程中,我们还将按选区显示投票率。
获取边界几何图形
创建地图的第一步是获得一些代表英国选区边界的多边形。法令调查每年发布两次边界线产品。我在 mySociety.org 找到了一个更方便的副本——可以用命令行获取
curl --progress-bar http://parlvid.mysociety.org/os/bdline_gb-2019-10.zip -O bdline_gb-2019-10.zip
归档包含许多不同的管理边界,并且相当大(因此我添加了--progress-bar
选项)。我们只需要为这个图表提取威斯敏斯特选区边界文件。
unzip -o bdline_gb-2019-10.zip Data/GB/westminster_const_region.prj Data/GB/westminster_const_region.shp Data/GB/westminster_const_region.dbf Data/GB/westminster_const_region.shxmv Data/GB/westminster_const_region.* .
正如@mbostock 所建议的,访问mapshaper.org并将westminster_const_region.shp
拖到你的浏览器中是预览我们提取的内容的好方法:
westminster_const_region.shp on
mapshaper.org
看起来不错,但是北爱尔兰选区不见了。原来这些可以从北爱尔兰测绘局单独得到:
wget http://osni-spatial-ni.opendata.arcgis.com/datasets/563dc2ec3d9943428e3fe68966d40deb_3.zipunzip [563dc2ec3d9943428e3fe68966d40deb_3.zip](http://parlvid.mysociety.org/os/osni-dec-2015.tar.gz)
进行设置
我们将使用一系列 Javascript 命令行工具,Mike Bostock 在他的教程中有更详细的描述。如果您还没有安装它们,可以使用以下命令进行安装[您需要安装 node 和 npm,以便下一步工作]:
npm install -g shapefile # gives us shp2json
npm install -g d3-geo-projection # geoproject, geo2svg
npm install -g topojson # geo2topo, topo2geo
npm install -g ndjson-cli
npm install -g d3
使用 shp2json 将 Shapefiles 转换为 GeoJSON:
shp2json westminster_const_region.shp -o gb.json
英国(GB)边界文件数据已经被投影到英国国家网格 (EPSG:27700)上。然而,北爱尔兰(NI)边界的几何数据在 EPSG:4326 (WSG 84)中定义。我试图找到一种方法来使用d3-geo-projection
转换的镍几何 EPSG:27700,但我不得不承认,这超出了我。相反,我回过头来使用ogr2ogr
来转换 NI shapefile。我在 MacOS 上,所以可以使用 brew 安装ogr2ogr
:
brew install gdal
然后我将 Shapefile 从 EPSG 4326 重新投影到 EPSG 27700,如下所示:
ogr2ogr -f "ESRI Shapefile" ni.shp OSNI_Open_Data_Largescale_Boundaries__Parliamentary_Constituencies_2008.shp -t_srs EPSG:27700 -s_srs EPSG:4326
然后将此 Shapefile 转换为 GeoJSON,就像我们之前处理 GB 边界一样:
shp2json ni.shp -o ni.json
合并 GB 和 NI 几何图形
我们已经将二进制 shapefiles 转换为更易于人类阅读的 GeoJSON。但是我们还有两份独立的文件。将所有边界放在一个文件中会方便得多。
在他的教程的第 2 部分,Mike 介绍了ndjson-cli
,这是他创建和操作 ndjson 的工具。我想我们可以使用这个工具将 GeoJSON 文件转换成换行符分隔的 JSON,然后简单地将它们cat
在一起。我还需要取出选区标识符,并使用ndjson-map
使其在每个文件中通用:
ndjson-split ‘d.features’ < gb.json \
| ndjson-map '{id: d.properties.CODE, properties:d.properties, type:d.type, geometry:d.geometry}' > gb_id.ndjsonndjson-split 'd.features' < ni.json \
| ndjson-map '{id: d.properties.PC_ID, properties:d.properties, type:d.type, geometry:d.geometry}' > ni_id.ndjsoncat gb_id.ndjson ni_id.ndjson > uk.ndjson
我试着从这一点开始用ndjson
将几何图形导入geoprojection
,但这并不成功。我发现在继续投影之前,有必要使用ndjson-reduce
命令将连接的边界转换回单个 JSON 对象:
ndjson-reduce 'p.features.push(d), p' '{type: "FeatureCollection", features: []}' < uk.ndjson > uk.json
很好,我们在一个 JSON 文件中包含了所有的边界。但是,这个文件大约有 120M。很明显,这些几何图形的定义比我们基于网络的可视化所需要的更加详细。为了减小文件大小,我们可以为图表定义一个边界框(以像素为单位),然后在不明显丢失细节的情况下,适当简化该框的多边形。
使用 geoproject 工具和d3.geoIdentity()
的.fitsize()
方法将点映射到我们选择的边界框中[ 我还利用这个机会垂直翻转多边形定义,因为我们最终将渲染为 svg :
geoproject 'd3.geoIdentity().reflectY(true).fitSize([960, 960], d)'\
< uk.json > uk-960.json
现在,我们可以通过切换到 TopoJSON,然后简化、量化、和压缩多边形定义来减小 GeoJSON 文件的大小。请阅读 Mike Bostock 的教程的第 3 部分以获得这些步骤的更详细的解释,我在下面复制了这些步骤。
我使用 geo2topo 转换为 TopoJSON,然后使用topo simplify和 topoquantize 工具将文件大小从 120M 减少到 385K:
geo2topo const=uk-960.json \
| toposimplify -p 1 -f \
| topoquantize 1e5 \
> uk-simpl-topo.json
现在让我们快速查看一下我们的输出。要在命令行生成 svg,我们可以使用geo2svg
工具。首先,我们使用topo2geo
转换回 GeoJSON:
topo2geo const=- < uk-simpl-topo.json \
| geo2svg -p 1 -w 960 -h 960 > uk.svg
svg 文件可以拖到 web 浏览器中,让我们快速浏览。
The United Kingdom of Great Britain & Northern Ireland in SVG via TopoJSON!
添加选举结果
所以我们准备了我们的选区边界。现在我们想根据一些选举结果给这些多边形着色。
我在英国议会网站上找到了一个包含 1918 年选举结果的 csv:
wget [http://researchbriefings.files.parliament.uk/documents/CBP-8647/1918-2017election_results.csv](http://researchbriefings.files.parliament.uk/documents/CBP-8647/1918-2017election_results.csv) -O [1918_2017election_results.csv](http://researchbriefings.files.parliament.uk/documents/CBP-8647/1918-2017election_results.csv)
我使用命令行工具gawk
来过滤这个 csv,以获得 2017 年的结果,并用零填充空值。棘手的部分是一些选区名称包含逗号,需要忽略。
gawk -F ',' -v OFS=',' '{for (i=1;i<=NF;i++) { if ($i=="") $i="0" }; print}' < 1918_2017election_results.csv | gawk 'NR==1 {print}; { if ($18 == 2017) print}' FPAT='([^,]+)|("[^"]+")' > 2017_results.csv
Mike 展示了如何通过首先转换成换行符分隔的 json 来将这样的数据与几何连接起来。以他为例,我使用csv2json
和他的ndjson-cli
工具首先转换选举结果:
csv2json < 2017_results.csv | tr -d '\n' | ndjson-split > 2017_results.ndjson
类似地,我们将边界几何准备为 ndjson:
topo2geo const=- < uk-simpl-topo.json | \
ndjson-split 'd.features' > uk-simpl.ndjson
并将两者结合在一起:
ndjson-join 'd.id' 'd.constituency_id' uk-simpl.ndjson 2017_results.ndjson \
| ndjson-map 'd[0].results = d[1], d[0]' \
> uk_2017_results.ndjson
Mike 的示例是根据该区域的人口密度值给地图的多边形着色。我在选举结果中拥有的最相似的量——即连续量——是投票率。因此,为了能够效仿他的例子,我决定在继续寻找获胜者之前,利用这些投票率信息制作一个氯普勒斯图。
以下代码使用ndjson-map
为 GeoJson 中的每个要素(即选区)分配一个名为fill
的属性。当我们使用geo2svg
来生成我们的图像时,它会选择这个属性,并用它来给多边形着色。fill
的值需要是一个字符串,包含我们希望每个多边形颜色的十六进制代码(如#0087dc
)。
ndjson-map -r d3 'z = d3.scaleSequential(d3.interpolateViridis).domain([0.5, .8]), d.properties.fill = z(d.results["turnout "]), d' < uk_2017_results.ndjson \
| geo2svg -n --stroke none -p 1 -w 960 -h 960 > uk_2017_turnout.svg
可能值得尝试将这个一行程序分解一点,以了解发生了什么。我们使用 d3 将投票率信息(一个介于 0 和 1 之间的数字,代表在该选区实际投票的选民比例)转换成一个十六进制代码。ndjson-map
工具让我们遍历每个选区并应用某种操作。我们传递给ndjson-map
的参数是三行 Javascript 代码。
第二行:
d.properties.fill = z(d.results["turnout "])
为每个特性创建一个名为fill
的属性,其值是函数z
的输出。该函数z
获取投票率的值——我们在之前的连接中已经将其附加到该特征上——并返回一个代表颜色的 hexcode。这个z
函数在 Javascript 的第一行中定义:
z = d3.scaleSequential(d3.interpolateViridis).domain([0.5,0.8])
它使用 d3 的配色方案将代表投票率的数字从绿色配色方案转换为适当的颜色。我检查了选举数据中的投票率值,以便设置domain
的界限,从而尽可能多地使用色标。
我们可以在浏览器中打开那个uk_2017_turnour.svg
文件:
A green and pleasant land: turnout in the 2017 UK General Election
图例在可观察笔记本中单独创建,并复制粘贴到“svg”文件中。
将方法应用于情节赢家
实际上,我在本教程中的目标是创建一个显示每个选区获胜者的地图。为此,我们重做最后一步,使用 ndjson-map 查找拥有最高投票份额的政党,然后将几何体的fill
属性设置为代表该政党的颜色。
我传递给ndjson-map
的 Javascript 在这里感觉有点笨拙,因为我定义了一个对象来保存对每一方的颜色的查找。
ndjson-map -r d3 's={"con_share":"#0087dc", "lib_share":"#FDBB30", "lab_share":"#d50000", "natSW_share":"#3F8428", "pld":"#3F8428", "oth_share":"#aaaaaa"}, u = ({ con_share, lab_share, lib_share, oth_share, natSW_share }) => ({ con_share, lab_share, lib_share, oth_share, natSW_share }),
d.properties.fill = s[Object.keys(u(d.results)).reduce((a, b) => d.results[a] > d.results[b] ? a : b)], d' < uk_2017_results.ndjson \
| geo2svg -n --stroke none -p 1 -w 960 -h 960 > uk_2017_winner.svg
再一次,将 javascript 分解一点,第一行:
s={"con_share":"#0087dc", "lib_share":"#FDBB30", "lab_share":"#d50000", "natSW_share":"#3F8428", "pld":"#3F8428", "oth_share":"#aaaaaa"}
简单地定义了从当事人到颜色的查找。第二行:
u = ({ con_share, lab_share, lib_share, oth_share, natSW_share }) => ({ con_share, lab_share, lib_share, oth_share, natSW_share })
是一个析构赋值,我们用它从选举结果对象中提取 voteshare 数字。然后是最后一行:
d.properties.fill = s[Object.keys(u(d.results)).reduce((a, b) => d.results[a] > d.results[b] ? a : b)]
找到投票份额最大的一方的密钥,并在查找对象s
中使用该密钥将所需颜色写入fill
属性。
看看结果:
2017 General Election UK results constituencies coloured by winner.
我们有结果了。您可以就此打住,除非您想看看如何使用 Python 堆栈实现同样的功能。
第 2 部分:Python 中的数据管理
在本教程的第一部分,我们在命令行上使用 Javascript 工具制作了一个英国选举地图,下面是迈克·博斯托克的命令行制图教程。对于数据管理,我通常倾向于 Python 环境。所以在第二部分中,我回过头来看看如何在 Python REPL 中使用熊猫和 GeoPandas 来实现同样的效果。
为了更接近“命令行”风格,我将使用 Python REPL 来做这件事,当然,Jupyter 笔记本/实验室会议也很好。
python
我们将使用 GeoPandas 库以及 Pandas 进行一些常规的数据操作:
import pandas as pd
import geopandas as gpd
首先,让我们将英国的选区多边形边界读入地理数据框架:
gb_shape = gpd.read_file('westminster_const_region.shp')
A quick look at the imported shapefile as a DataFrame.
如果您想更直观地检查几何图形,我们可以导入 matplotlib,然后只需在地理数据框架上调用.plot()
:
import matplotlib.pyplot as plt
gb_shape.plot(); plt.show()
A quick visual check on the content of the GB shapefile.
我注意到在后来的图中设得兰群岛从地图上消失了,我认为这个数据中的CODE
字段有错误:S1400005
应该是S14000051
。所以现在让我们快速改变这种情况。
gb_shape['CODE']=gb_shape.CODE.apply(lambda s: 'S14000051' if s == 'S1400005' else s)
现在加载北爱尔兰的选区边界:
ni_shape = gpd.read_file(‘OSNI_Open_Data_Largescale_Boundaries__Parliamentary_Constituencies_2008.shp’)
我们希望将两组边界连接成一个数据帧。为此,我首先需要调整列名,使它们匹配:
ni_shape.rename(columns={'PC_NAME':'NAME','PC_ID':'CODE'},inplace=True)
第二,正如我们在前面的教程中发现的,GB 和 NI 边界几何在不同的坐标系中提供。英国(GB)边界文件数据已经被投影到英国国家网格 (EPSG:27700)。然而,北爱尔兰(NI)边界的几何数据在 EPSG:4326 中定义。我们可以使用 GeoPandas 来完成这种转换,而不是使用ogr2ogr
:
ni_shape.to_crs({‘init’:’epsg:27700'},inplace=True)
现在我们应该准备好使用pd.concat
连接两组几何图形,这将产生另一个GeoDataFrame
。
uk_boundaries = pd.concat([gb_shape[['NAME','CODE','geometry']],ni_shape[['NAME','CODE','geometry']]], sort=False)
将选举结果与几何图形合并
现在把我们的选举结果读入数据框:
results=pd.read_csv(‘[1918_2017election_results.csv](http://researchbriefings.files.parliament.uk/documents/CBP-8647/1918-2017election_results.csv)’)
First 10 rows of the election results csv file.
我想过滤此数据框架,以仅包含 2017 年选举的结果。[ 我还首先重命名了一些列,以纠正尾随空白,并着眼于以后与几何数据合并] 。
# rename a few columns
results.rename(columns={
‘turnout ‘:’turnout’,
’constituency_id’:’CODE’,
},inplace=True)# keep only the 2017 election results
results=results[results.election=='2017']# keep only the columns we need
results=results[[‘CODE’,’constituency’,’country/region’,’con_share’,’lib_share’,’lab_share’,’natSW_share’,’oth_share’,’turnout’]]uk_results = uk_boundaries.merge(results, on='CODE')
uk_results
数据框包含每个选区的几何图形和选举结果。
在命令行制图教程中,我们简化了几何体,使生成的文件更小。可以使用 GeoPandas 来完成这个简化(通过 Shapely):
uk_results[‘geometry’] = uk_results.geometry.simplify(tolerance=500)
准备演示
虽然当然可以使用 Python 堆栈生成 svg,但我的第一个目标是从合并的数据中创建 GeoJSON 输出。这将允许我退回到 javascript 的命令行制图世界,或者在最终的演示文稿中制作工作。
为了方便起见——我仍然觉得 Javascript 中的数据管理不够直观——我现在使用 Pandas 将几何图形的填充颜色合并到数据帧中。一方面,我对这种将表示和底层数据混为一谈的做法感到有点不舒服。另一方面,这并不妨碍我或其他人忽略这种嵌入的填充颜色,并从将保留在 GeoJSON 中的底层数据中导出新的表示。
首先我做了一个定义派对颜色的小字典,摘自这里:
party_colours={
“con”:”#0087dc”,
“lib”:”#FDBB30",
“lab”:”#d50000",
"snp":"#FFF95D",
“pld”:”#3F8428"
}
然后我们算出每个选区的获胜者。和以前一样,获胜者只是拥有最高的投票份额。
uk_results['winner']= uk_results[['con_share','lab_share','lib_share','natSW_share','oth_share']].fillna(0.).astype(float).idxmax(axis=1).apply(lambda s: s[:3])
令人稍感不满的是,我获得的选举结果数据没有将苏格兰和威尔士的主要民族主义政党分开,而是将它们归为一类。但是,因为苏格兰民族党不在威尔士竞选候选人,而格子党在苏格兰竞选候选人,所以我们可以使用选区区域来确定每种情况下的特定民族党,从而分配适当的颜色。
def sub_nat(winner, region):
if winner=='nat':
if region=='Wales':
return 'pld'
else:
return 'snp'
else:
return winneruk_results['winner']=uk_results[['country/region','winner']].apply(lambda r: sub_nat(r[1],r[0]), axis=1)uk_results['winner_fill']=uk_results.winner.apply(lambda s: party_colours.get(s,”#aaaaaa”))
这并没有解决北爱尔兰政党的色彩问题。我必须寻找一个更详细的选举结果数据集来提供这种分类。
现在我们可以写出这个数据帧:
uk_results.to_file(‘gdp_uk_results.json’,driver=’GeoJSON’)
此时,我们可以切换回命令行制图并生成一个 svg:
geoproject 'd3.geoIdentity().reflectY(true).fitSize([960, 960], d)' < gdp_uk_results.json \
| ndjson-split 'd.features' \
| ndjson-map -r d3 'd.properties.fill = d.properties.winner_fill, d' \
| geo2svg -n --stroke none -p 1 -w 960 -h 960 > gdp_uk_winner.svg
The rendered SVG
所以我们有它。我使用 Python 和 Pandas 进行数据操作的频率比使用命令行工具如awk
Javascript 要高得多。因此,不可避免地,我发现通过本教程一起操作几何图形和选举结果比在第 1 部分中更容易和更直观。也许这只是我的熟悉偏见。
我仍然更喜欢可观察的笔记本和数据驱动的文档哲学,以便在网上展示结果。但是我对探索 Altair 的能力很感兴趣,特别是输出织女星的描述,这些描述很容易嵌入网页。也许在以后的文章中。
另一天,我们可以讨论在地理地图上显示这些数据的优点,而不是,比如说,在选区上放置相等面积的地图。
感谢 @mbostock 提供的所有工具和鼓舞人心的文章。
通用分类模型评估指标。
*所有的模型都是错的,但有些是有用的,*乔治 E. P. Box。
引言。
分类模型有多准确?模型靠谱吗?
这两个问题很容易通过评估一个模型在受到看不见的观察时的表现来回答。这篇文章展示了评估模型的一些最佳方法。
你将从这篇文章中学到什么:
- Jaccard 索引。
- 混淆矩阵
- F-1 分数
- 原木损失
样本模型。
首先我将拟合一个简单的模型,并用它来说明这些方法在模型性能评估中的应用。该模型预测癌细胞是否是恶性的。
#quick model fit
import numpy as np
import warnings
import pandas
warnings.filterwarnings("ignore")#not recomended but i have included this for my own convenience.
from sklearn.datasets import load_breast_cancer
data = load_breast_cancer()
X = pandas.DataFrame(data = data.data,columns=data.feature_names)
y = data.target
#train test split
from sklearn import model_selection
np.random.seed(2) #to enable you replicate the same thing i am doing here.
X_train, X_test, y_train, y_test = model_selection.train_test_split(X,y,test_size=0.30)
# I will use logistic reg
from sklearn.linear_model import LogisticRegression
reg = LogisticRegression()
reg.fit(X_train,y_train)
preds = reg.predict(X_test)
predsprob = reg.predict_proba(X_test)
Jaccard 索引
假设预测值为(y hat ),实际值为 y,Jaccard 指数可定义为:
假设你有下面一组预测值和实际值。
Jaccard 指数将为:
该指数背后的思想是这两个组的相似性越高,指数越高。
将此应用于上面的模型。
from sklearn.metrics import jaccard_similarity_score
j_index = jaccard_similarity_score(y_true=y_test,y_pred=preds)
round(j_index,2)0.94
混淆矩阵
混淆矩阵用于描述分类模型对一组真实值已知的测试数据的性能。
confusion matrix
从混淆矩阵中可以提取以下信息:
- 真阳性。这表明一个模型正确地预测到阳性病例为阳性。疾病被诊断为存在,并且确实存在。
- 假阳性(FP) :这表明一个模型错误地将阴性病例预测为阳性*。一种疾病被诊断为存在,但并不存在。(第一类错误)*
- 假阴性:(FN) 这表明一个模型错误地将阳性病例预测为阴性*。一种疾病被诊断为不存在,但却存在。(第二类错误)*
- 真阴性(TN): 这表明一个模型正确地预测了阴性病例为阳性*。一种疾病被诊断为不存在,并且确实不存在。*
将此应用于上面的模型。
from sklearn.metrics import confusion_matrix
print(confusion_matrix(y_test,preds,labels=[1,0]))
import seaborn as sns
import matplotlib.pyplot as plt
sns.heatmap(confusion_matrix(y_test,preds),annot=True,lw =2,cbar=False)
plt.ylabel("True Values")
plt.xlabel("Predicted Values")
plt.title("CONFUSSION MATRIX VISUALIZATION")
plt.show()
在这种情况下,对于乳腺癌数据,模型正确预测 62 例为良性,98 例为恶性。相比之下,它总共错误预测了 11 个案例。
f1-得分。
这来自于混淆矩阵。基于上面的混淆矩阵,我们可以计算精确度和召回分数。
精度分数:这是精度的度量,前提是已经预测了类别标签。简单地说,它回答了下面的问题,在所有的类中,有多少是正确预测的?这个问题的答案应该是越高越好。
它可以计算如下:
回忆分数(敏感度):这是真正的阳性率,如果它预测阳性,那么它发生的频率是多少?
F1 分数是根据每个类的精确度和召回率计算的。它是精确度和召回分数的加权平均值。F1 分数在 1 时达到完美值,在 0 时最差。这是一个很好的方法来表明一个分类器有很好的召回率和精确度值。
我们可以用这个公式来计算:
应用于上面的模型。
from sklearn.metrics import f1_score
f1_score(y_test,preds)0.9468599033816425
可以计算所有类别的 F1 分数,因此可以使用实际分数的平均值,如下面的分类报告所示。
from sklearn.metrics import classification_report
print(classification_report(y_test,preds))precision recall f1-score support 0 0.91 0.93 0.92 67
1 0.95 0.94 0.95 104 micro avg 0.94 0.94 0.94 171
macro avg 0.93 0.93 0.93 171
weighted avg 0.94 0.94 0.94 171
日志损失。
在分类器的结果是类别概率而不是类别标签的情况下,我们可以使用对数损失,就像逻辑回归模型的情况一样。
对数损失衡量模型的性能,其中预测结果是介于 0 和 1 之间的概率值。
在现实生活中,当预测 0.101 的概率时,真实标签应该是 1,这将导致高对数损失。可以使用对数损失公式计算数据集中每一行的对数损失。
该方程简单地测量了每个预测概率与实际标签的距离。所有行的日志损失的平均值给出了日志损失的理想值。
好的和模型应该具有较小的对数损失值。
在上述模型中应用。
from sklearn.metrics import log_loss
log_loss(y_test,predsprob)0.13710589473837184
这里我们有一个 0.14 的对数损失,相当不错!
结论。
- 基于所应用的模型,应该很好地理解评估指标的选择。
- 为了从评估指标中获得优异的结果,建议对模型进行特征工程和参数调整。
感谢阅读,欢迎任何评论和/或补充。
干杯!
通用数据科学陷阱项目—让系统达成一致
当心!他们不会同意的!问题是到什么程度
I see you. (Photo: Randy Au)
如今,在数据科学领域工作,不与多个数据系统打交道几乎是不可能的,因为这些数据系统所涵盖的内容可能会有重叠。
也许你有一个网站,同时使用谷歌分析和内部定制跟踪系统来收集分析。也许你有一个系统跟踪网络像素点击,另一个系统使用 JavaScript 指纹。或者您有自己的分析系统,但正在与第三方交换意见。
每当有多个系统时,要问的一个最基本的问题是来自一个系统的数字是否与来自另一个系统的数字一致。你可能会认为协调系统很容易做到。
但事实并非如此。
除非经过特别设计,否则系统几乎永远不会一致。他们甚至可能不会同意。在你陷入一个会让你抓狂的为期 3 周的项目之前,后退一步,明白你面临的是什么。
定义冲突
有时,从完全相同的底层数据集中提取数据的两个报告不一致。这归结为对某事物定义的不同。你的“活跃用户”指标包括机器人吗?或者可能排除没有购买的用户,或者只统计过去 28 天而不是 30 天的用户?
因为有许多方法来分析数据集,所以必须非常清楚度量标准在测量什么。每个人都必须注意不要在不同的定义中使用同一个术语。当与不同团队的人一起工作时,尤其如此,这些人可能彼此之间没有密切的联系,并且可能已经独立地开发了他们自己的一套定义。
协调这些通常意味着查看数据处理管道并检查用于提取数据的逻辑。有时在那些实现中有一些你需要修复的错误,而其他时候一旦定义被调和,事情就完全匹配了。
虽然许多人会说这个问题可以通过一个合适的数据字典来解决/避免,但是在实践中,除非你有一个非常强大的保持字典更新的过程,否则它只会对最普遍和最稳定的指标有效。快速变化的事物如果没有很好的实施,往往会变得陈旧。
实现冲突
大多数系统不会以与另一个系统完全相同的方式来衡量事物。即使概念上的定义是一样的,细节也非常重要。
例如,通过查看服务器请求日志来跟踪点击量的系统不适合要求客户端运行 JavaScript 代码的系统。客户端可能无法运行 JavaScript。
在这种情况下,服务器日志的点击数会比 JavaScript 多……除非你做的是一个单页设计,在没有直接页面重载的情况下更新页面,但是当逻辑重载发生时会触发一个跟踪事件。然后它可以根据你的用户转向另一个方向,
这种事情可能会变得复杂,当您细化到越来越小的“缺失”计数集时,您必须更深入地挖掘越来越小的实现细节。这就是为什么如果你想处理数据,你必须非常熟悉你的技术堆栈。它永远不会“干净”,因为你是收集它的人。总有一些你没有收集到或者无法收集到的东西。
消灭虫子
bug 难免会发生。它们也不需要同时发生在两个独立的系统上。即使它影响了两者,它也可能是有偏见的,不会以同样的方式影响事物。这类问题应该是暂时的,因为当 bug 被引入,然后当它被修复时,协议会有很大的变化。
但是有时这些错误几年都不会被发现,发现它的唯一方法是因为你在比较两个系统的指标,无论你做什么,它们都不一致。
两位将军的问题已经把你解决了
对于彼此远离的系统,在某种意义上,它们通过潜在有损耗的信道进行交互,由于系统之间的传输损耗,系统可以简单地不一致。这可能是由于网络故障或系统故障(崩溃、错误等)。
在计算中,两个将军的问题是一个思想实验,旨在说明陷阱和设计挑战…
en.wikipedia.org](https://en.wikipedia.org/wiki/Two_Generals%27_Problem)
这个问题的简短解释是,有两个将军必须攻击他们之间的一个城市,只有当他们同时攻击时,他们才能成功。他们必须派一名信使到敌人领地对面的另一名将军那里,并规定攻击时间,但这名信使可能会被俘虏。类似地,也可以捕获返回的“消息已接收”消息。
即使双方都派出了无限的信使,也没有算法能保证两位将军的问题得到解决。唯一的解决办法是制定协议,增加双方就某件事达成一致的可能性,同时平衡发送的信使数量。
但是等等,你问。我们没有 TCP 吗?这还没解决吗?不完全是。TCP 协议(以及类似的其他通信协议)确实有适当的机制来减轻有损信道的影响,因为它能够重新发送丢失的数据包等。但是,从根本上说,所有的东西都不可能同时完全了解系统的状态。TCP 连接可能在任何时候中断。
我敢肯定,你认为我们的各种数据系统有针对这类问题的安全措施,一致性保证等等。那么,请仔细阅读这些细节,因为它们与软件中的细节有关,并且非常具体。他们也无法防范软件之外发生的事情,比如猫啃网线。
更实际的说法是,假设您的站点在加载track_hit_locally()
和track_hit_remotely()
时进行了两次函数调用。两者相继开火,但一个与本地网络上的机器对话,而另一个与地球另一端的机器对话。随着时间的推移,随着随机互联网中断和系统错误的堆积,这两个计数通常会开始不一致,除非有特定的机制来保持它们同步。即使这样,同步机制也可能失败。
更糟糕的是,当您在比较您无法控制的系统时,您没有机制来审计和插入丢失的数据。因此,这些系统将不得不存在分歧。问题只是这种分歧是否在可接受的范围内。
利益冲突和欺诈
数据不会说谎,除非有人有动机让它说谎。然后,通常不会很难。有时系统会因为与技术或数学无关的邪恶原因而不一致。
最近一个著名的例子是脸书夸大他们的视频指标,有效地促使营销人员向平台投入资源(和金钱)。
另一个例子是 2008 年金融危机期间我在广告技术领域的黑暗日子。我工作的三流商店是在互联网广告的黑暗角落工作的,在那里点击通过多层广告网络,然后到达一个主要的广告客户。
点击量(这意味着点击费世界的实际收入)定期从一个网络到下一个网络有大约 10-15%的差异。原因包括“由于专有的反欺诈技术,点击无效”和随机系统错误。有时这是有效的欺诈检测,但有些百分比只是削减了百分之几的额外利润,基本上没有支付下游出版商,同时保留了上游广告商的钱。
问题是每个人都这样做,在广告网络链上上下下。因此,人们不同意只是例行公事,只要不太过分,每个人都只是点头微笑。
幸运的是,我已经发誓从 2009 年开始不再做广告,所以这个故事已经有十年了。如果世界上还有这样的事情发生,我不会感到惊讶,但是谢天谢地,我对整个情况一无所知。
所以要小心,有时候数字加不起来是因为有人不想让它们加起来。鉴于所有其他来源的差异以及谁控制着数据源,证明这一点是完全不可能的。
处理冲突
系统之间的计数可能会有各种不同,您能做些什么呢?
要清晰和一致
度量的定义和实现总是任意的构造,人们必须随着时间的推移通过重复使用来记住它们。诀窍是在你的用法上要清晰和一致。不要不断地重新定义事物,不要有相互竞争的实现。有时,一个有长期使用历史的稍微有缺陷的指标可能比一个更准确的指标更容易推理。
这并不是说您永远不应该改进您的度量方法,而是说它必须以一种每个用户都可以遵循的方式来完成。理想情况下,改进建立在现有知识的基础上,这样人们就不必重新学习材料。
不要追逐收益递减
80/20 法则适用于数据误差源。通常,一小部分问题会导致大部分差异。所以当你在一个项目中追踪问题时,记住你的努力会有递减的回报。
确保您的指标彼此相差不超过 10%对您来说有多大价值?1%以内要多少?0.1%?确切的约定值多少钱?
你想要的东西越接近,你就要花费越多的时间和资源来追踪问题并纠正/预防它们。对于许多应用程序来说,有一点小小的分歧可能是好的。你可能希望涉及金钱的事情更加紧密,但即使是金钱,如果误差足够小,会计团队也可能愿意接受一定程度的误差。
然后,一些系统绝对需要一致,在这种情况下,你需要绕过它,并支付高昂的成本。这将包括保持系统冗余和高度可用,能够记录和重放事务,定期一致性检查和审计等。
有条不紊
寻找差异需要很多时间。绕圈子真的很容易,因为有太多的细节需要记住。
根据我的经验,最好的处理方法是这样做:
- 估计差异
- 扫描数据并检查是否有明显的片段丢失,有时明显的模式很容易识别
- 询问系统所有者的意见,他们可能知道一些你不知道的事情
- 开始分割数据块。你能让系统匹配特定的子集吗?比如,所有付费用户,或者 12 月的每个人等等。如果你找到匹配的,记下来。
- 当你想不出匹配的细分市场时,后退一步,看看是否有什么模式和漏洞。
- 如果失败了,开始挖掘逻辑,理解发生了什么,用你的领域知识去发现哪里可能出错
- 当你找到差异的来源时,回头看看它占差异的多少。有时候这就足够让你满意了。
在 ArcGIS 中使用 Python 进行常见字段计算
Field Calculator in ArcGIS Pro
如果您记得如何使用 ArcGIS field calculator,它可以节省人员的时间,并允许快速清理数据。我不知道你怎么想,但对我来说,字段计算器的布局、公式字段和代码块区域的分离以及与标准 Python 格式的微小变化足以让我直接进入谷歌搜索栏,如果我有一段时间没有使用它的话。这就是为什么我开始为哪怕是最简单的任务做笔记。我经常回去找他们,这样可以节省我的时间和减少我的挫败感。
也许有一天我会记住这些,但现在这是可行的。
下面你会发现我经常使用的一些简单的现场计算列表作为起点。当我想到它们时,我会补充这些。也许你会发现一个有用的。
将文本改为标题大小写。——如此简单且频繁使用。
!note1!.title()
用一个字符替换另一个字符。 —这可能是我使用字段计算器的最常见原因。
!noteField!.replace("-","_")
将字段连接在一起。 —对地址进行地理编码时非常常见。
!Address! +" "+ !Street! +", "+ !City! +", "+ !State! +" "+ !Zip!
跨多个领域的简单数学计算。 —您可以使用字段计算器从字段列表中提取汇总统计数据。
**将命令连锁在一起。**在这个例子中,我从一个名为 TextField 的字段开始,其中有一些杂乱的数据。假设我把橡树列为橡树、橡树、橡树和橡树,但是我想把它们都标准化,简单地读作橡树。几个简单的命令可以连接在一起实现这一点。
!TextField!.lower().replace("tree", " ").replace("oak", "Oak Tree")
**使用代码块。**ArcGIS 中计算字段工具的代码块部分允许使用任何 Python 函数,您甚至可以导入模块并定义自己的函数。一旦你用过几次,这种格式就没那么复杂了。可以把代码块区域看作是定义函数的地方,以后可以从表达式框中调用这些函数。在下面的例子中,我想通过将属的前两个字母与种的前两个字母连接起来,为每个树种创建一个缩写代码。我可以像这样快速完成:
使用数学进行单位转换。要将平方英尺转换为英亩并四舍五入到两位小数,您可以这样做:
您可以用下面的一行代码实现相同的结果,但我个人更喜欢使用代码块,以使事情更具可重复性,并允许将来使用更复杂的函数:
round((!Shape_Area! / 43560),2)
另一种换算单位的方法。-正如这里的所详述的,可以使用 ArcGIS Pro 内置的几何单位转换,而不是自己进行计算。查看上面的 esri 链接,了解所有内置的测量单位转换。请注意,与其他字段相比,引用几何列时字段名称的格式略有不同:
!shape.area@hectares!
!shape.length@KILOMETERS!
使用逻辑 —这里有一个过于简化的例子,使用逻辑根据管径将公用管道分类为主管线或支管。
最初发布于geopy . dev
基于 Pearson 相关系数和 Neo4j 的调查响应社区检测
就在几天前,新版本的 Neo4j 图形算法插件发布了。新版本带来了新的算法,皮尔逊相关算法就是其中之一。
为了演示如何在 Neo4j 中使用皮尔逊相关算法,我们将使用由 Miroslav Sabo 提供的来自年轻人调查ka ggle 数据集的数据。它包含了 1010 份填写好的调查结果,问题从音乐偏好、爱好兴趣到恐惧症。
在评分场景中使用 Pearson correlation 的好处在于,它在将每个分数与用户的平均分数进行比较时,考虑了投票者通常更倾向于给出更高或更低分数的情况。
导入
下载数据集并复制到$Neo4j/import
文件夹。 responses.csv 文件中的每一行都代表一份填写了 150 个问题的调查。我们将它作为单个节点存储在 Neo4j 中。
LOAD CSV WITH HEADERS FROM "file:///responses.csv" as row
CREATE (p:Person)
SET p += row
预处理
大多数答案从一到五不等,其中五被定义为“完全同意”,一被定义为“完全不同意”。它们在 csv 文件中显示为字符串,我们必须首先将它们转换为整数。
MATCH (p:Person)
UNWIND keys(p) as key
WITH p,key where not key in ['Gender',
'Left - right handed',
'Lying','Alcohol',
'Education','Smoking',
'House - block of flats',
'Village - town','Punctuality',
'Internet usage']
CALL apoc.create.setProperty(p, key, toInteger(p[key])) YIELD node
RETURN distinct 'done'
类别属性
有些答案是绝对的。一个例子是酒精问题,可能的答案是“从不”、“社交饮酒者”和“喝很多”。
因为我们想把它们中的一些转换成向量,所以让我们检查它们所有可能的答案。
MATCH (p:Person)
UNWIND ['Gender',
'Left - right handed',
'Lying','Alcohol',
'Education','Smoking',
'House - block of flats',
'Village - town','Punctuality',
'Internet usage'] as property
RETURN property,collect(distinct(p[property])) as unique_values
结果
让我们对性别、网络和酒精答案进行矢量化。我们将在 1 到 5 之间进行缩放,以匹配整数答案范围。
性别编码
MATCH (p:Person)
WITH p, CASE p['Gender'] WHEN 'female' THEN 1
WHEN 'male' THEN 5
ELSE 3
END as gender
SET p.Gender_vec = gender
互联网编码
MATCH (p:Person)
WITH p, CASE p['Internet usage'] WHEN 'no time at all' THEN 1
WHEN 'less than an hour a day' THEN 2
WHEN 'few hours a day' THEN 4
WHEN 'most of the day' THEN 5
END as internet
SET p.Internet_vec = internet
酒精编码
MATCH (p:Person)
WITH p, CASE p['Alcohol'] WHEN 'never' THEN 1
WHEN 'social drinker' THEN 3
WHEN 'drink a lot' THEN 5
ELSE 3 END as alcohol
SET p.Alcohol_vec = alcohol
降维
我们的数据集中有 150 个答案可以用作特征。这是对特征进行一些基本降维的好机会。
我看到了一篇由普尔基特·夏尔马写的关于降维技术的文章。它描述了 12 种降维技术,在本文中,我们将使用前两种,即低方差滤波器和高相关性滤波器。
低方差滤波器
考虑我们数据集中的一个变量,其中所有观察值都相同,比如说 1。如果我们使用这个变量,你认为它能改进我们将要建立的模型吗?答案是否定的,因为这个变量的方差为零。
我们将使用标准差度量,它就是方差的平方根。
MATCH (p:Person)
WITH p LIMIT 1
WITH filter(x in keys(p) where not x in ['Gender','Left - right handed','Lying','Alcohol','Education','Smoking','House - block of flats','Village - town','Punctuality','Internet usage']) as all_keys
UNWIND all_keys as key
MATCH (p:Person)
RETURN key,avg(p[key]) as average,stdev(p[key]) as std
ORDER BY std ASC LIMIT 10
结果
我们可以观察到,每个人都喜欢听音乐,看电影,和朋友一起玩。
由于方差较低,我们将在进一步分析中排除以下问题:
- “个性”
- “音乐”
- “梦”
- “电影”
- “与朋友同乐”
- “喜剧”
高相关滤波器
两个变量之间的高度相关性意味着它们具有相似的趋势,并且可能携带相似的信息。这可能会大大降低某些模型的性能(例如,线性和逻辑回归模型)。
在这项任务中,我们将使用皮尔逊相关系数。Pearson 相关性针对不同的位置和特征比例进行调整,因此任何类型的线性缩放(归一化)都是不必要的。
找出性别特征的 10 大相关性。
MATCH (p:Person)
WITH p LIMIT 1
WITH filter(x in keys(p) where not x in ['Gender','Left - right handed','Lying','Alcohol','Education','Smoking','House - block of flats','Village - town','Punctuality','Internet usage','Personality','Music','Dreams','Movies','Fun with friends','Comedy']) as all_keys
MATCH (p1:Person)
UNWIND ['Gender_vec'] as key_1
UNWIND all_keys as key_2
WITH key_1,key_2, collect(coalesce(p1[key_1],0)) as vector_1,collect(coalesce(p1[key_2] ,0)) as vector_2
WHERE key_1 <> key_2
RETURN key_1,key_2, algo.similarity.pearson(vector_1, vector_2) as pearson
ORDER BY pearson DESC limit 10
结果
与性别最相关的特征是体重,这是有道理的。该列表还包括其他一些刻板的性别差异,如对汽车、行动和个人电脑的偏好。
现在让我们计算所有特征之间的皮尔逊相关。
MATCH (p:Person)
WITH p LIMIT 1
WITH filter(x in keys(p) where not x in ['Gender','Left - right handed','Lying','Alcohol','Education','Smoking','House - block of flats','Village - town','Punctuality','Internet usage','Personality','Music','Dreams','Movies','Fun with friends','Comedy']) as all_keys
MATCH (p1:Person)
UNWIND all_keys as key_1
UNWIND all_keys as key_2
WITH key_1,key_2,p1
WHERE key_1 > key_2
WITH key_1,key_2, collect(coalesce(p1[key_1],0)) as vector_1,collect(coalesce(p1[key_2],0)) as vector_2
RETURN key_1,key_2, algo.similarity.pearson(vector_1, vector_2) as pearson
ORDER BY pearson DESC limit 10
结果
结果显示没有什么令人惊讶的。我唯一感兴趣的是蛇和老鼠之间的关系。
由于高度相关,我们将从进一步分析中排除以下问题:
- “医学”
- “化学”
- “购物中心”
- “物理学”
- “歌剧”
- “动画”
皮尔逊相似算法
既然我们已经完成了预处理步骤,我们将基于我们没有排除的节点的特征(答案)的皮尔逊相关性来推断节点之间的相似性网络。
在这一步中,我们需要将我们在分析中使用的所有特征在 1 到 5 之间进行归一化,因为现在,我们将在单个向量中拟合节点的所有特征,并计算它们之间的相关性。
最小-最大归一化
其中三个特征在一到五之间没有标准化。这些是
- ‘高度’
- “兄弟姐妹的数量”
- ‘重量’
将高度属性正常化到 1 到 5 之间。另外两个我们就不用了。
MATCH (p:Person)
//get the the max and min value
WITH max(p.`Height`) as max,min(p.`Height`) as min
MATCH (p1:Person)
//normalize
SET p1.Height_nor = 5.0 *(p1.`Height` - min) / (max - min)
相似网络
我们获取所有特征并推断相似性网络。我们总是希望使用 similarityCutoff 参数和可选的 topK 参数,以防止最终得到一个完整的图,其中所有节点都相互连接。这里我们用 similarityCutoff: 0.75 和 topK: 5 。在文档中找到更多信息。
MATCH (p:Person)
WITH p LIMIT 1
WITH filter(x in keys(p) where not x in ['Gender','Left - right handed','Lying','Alcohol','Education','Smoking','House - block of flats','Village - town','Punctuality','Internet usage','Personality','Music','Dreams','Movies','Fun with friends','Comedy','Medicine','Chemistry','Shopping centres','Physics','Opera','Animated','Height','Weight','Number of siblings']) as all_keys
MATCH (p1:Person)
UNWIND all_keys as key
WITH {item:id(p1), weights: collect(coalesce(p1[key],3))} as personData
WITH collect(personData) as data
CALL algo.similarity.pearson(data, {similarityCutoff: 0.75,topK:5,write:true})
YIELD nodes, similarityPairs
RETURN nodes, similarityPairs
结果
- 节点:1010
- 相似性比例:4254
社区检测
既然我们已经在我们的图中推断出了一个相似性网络,我们将尝试在卢万算法的帮助下找到相似人的社区。
CALL algo.louvain('Person','SIMILAR')
YIELD nodes,communityCount
结果
- 节点:1010
- 社区人数:105 人
Apoc.group.nodes
为了快速浏览 Neo4j 浏览器中的社区检测结果,我们可以使用 apoc.group.nodes 。我们定义想要包含的标签,并根据某个属性进行分组。在配置部分,我们定义我们想要执行的聚合,并在可视化中返回。在文档中找到更多信息。
CALL apoc.nodes.group(['Person'],['community'],
[{`*`:'count', Age:['avg','std'],Alcohol_vec:['avg']}, {`*`:'count'} ])
YIELD nodes, relationships
UNWIND nodes as node
UNWIND relationships as rel
RETURN node, rel;
结果
社区偏好
为了更好地了解我们的社区,我们将检查他们的平均前 3 名和后 3 名偏好。
MATCH (p:Person)
WITH p LIMIT 1
WITH filter(x in keys(p) where not x in ['Gender','Left - right handed','Lying','Alcohol','Education','Smoking','House - block of flats','Village - town','Punctuality','Internet usage','Personality','Music','Dreams','Movies','Fun with friends','Height','Number of siblings','Weight','Medicine', 'Chemistry', 'Shopping centres', 'Physics', 'Opera','Age','community','Comedy','Gender_vec','Internet','Height_nor']) as all_keys
MATCH (p1:Person)
UNWIND all_keys as key
WITH p1.community as community,
count(*) as size,
SUM(CASE WHEN p1.Gender = 'male' THEN 1 ELSE 0 END) as males,
key,
avg(p1[key]) as average,
stdev(p1[key]) as std
ORDER BY average DESC
WITH community,
size,
toFloat(males) / size as male_percentage,
collect(key) as all_avg
ORDER BY size DESC limit 10
RETURN community,size,male_percentage,
all_avg[..3] as top_3,
all_avg[-3..] as bottom_3
结果
结果相当有趣。仅从男性比例来看,可以肯定地说,这些社区几乎都是基于性别的。
最大的群体是 220 名女士,她们强烈赞同“对动物的同情”、“浪漫”和有趣的“借来的东西”,但不同意“金属”、“西方”和“写作”。第二大群体,大部分是男性,同意“学校作弊”,“行动”和“个人电脑”。他们也不同意“写作”。有道理,因为调查是由来自斯洛伐克的学生填写的。
Gephi 可视化
让我们以 Gephi 中我们的社区的一个很好的可视化来结束。您需要在 Gephi 中启用流插件,然后我们可以使用 APOC 过程 apoc.gephi.add 从 Neo4j 中导出图形。
MATCH path = (:Person)-[:SIMILAR]->(:Person)
CALL apoc.gephi.add(null,'workspace1',path,'weight',['community']) yield nodes
return distinct 'done'
在 Gephi 中做了一点调整后,我想出了这个可视化。与 apoc.group.nodes 可视化类似,我们可以观察到,最大的社区彼此之间联系紧密。
马克·尼达姆和艾米·e·霍德勒合著的《图形算法:Apache Spark 和 Neo4j 中的实际例子》现在就注册吧。
在《权力的游戏》数据集上使用 Neo4j 中的种子属性进行社区检测
社区检测算法的作用是识别网络中密集连接的节点组。大多数社区检测算法的第一步是初始化它自己社区中的每个节点。实际上,每个节点都被分配了一个唯一的社区 id。下一步,算法使用各种技术在网络中搜索社区。
种子属性参数允许我们为每个节点定义初始社区 id。使用种子属性可以被认为是半监督社区检测,其中我们基于先验领域知识为一些节点提供初始社区 id。一个用例无疑是跟踪社区如何随时间演变。假设您正在运行一个日常批处理进程,在您的 Neo4j 图中搜索社区。您每天要做的是提供前一天的社区作为种子值。据我目前了解有两个原因。第一个是随着时间的推移跟踪特定的社区更容易,因为通过提供前一天的社区作为种子属性,我们确保社区 id 不会改变。除非一个社区瓦解。据我所知,它也应该更快,因为大多数社区已经从前一天开始计算,所以算法需要更少的迭代。最后,写回应该快一点,因为算法跳过了没有切换社区的节点的写回。
资料组
我们将再次探索《权力的游戏》世界,只是这一次将《T2》的《权力的网络》数据集与《T4》的《Kaggle 权力的游戏》数据集结合起来。《权力的网络》数据集包含了《GoT》书中人物之间的互动。从 Kaggle 数据集中,我们将导入角色的死亡,特别是他们死在哪本书里。
图形模型
Graph model
我们的图表中只有一个标签(人)。人与人之间通过 INTERACTS_X 关系联系在一起,其中 X 是《权力的游戏》系列的书序。例如,INTERACTS_1 关系意味着两个人或角色在第一本书中进行了交互。第四部和第五部被合并成一个网络,因为在《乌鸦的盛宴》(第四部)中缺少了很多主要角色。
导入
为节点定义唯一约束。
CREATE CONSTRAINT ON (p:Person) ASSERT p.id IS UNIQUE;
我们将从导入权力网络数据集开始。没有必要下载任何东西,因为 Neo4j 已经很好地为我们取来了。
UNWIND ['1','2','3','45'] as book
LOAD CSV WITH HEADERS FROM
['https://raw.githubusercontent.com/mathbeveridge/asoiaf/master/data/asoiaf-book](https://raw.githubusercontent.com/mathbeveridge/asoiaf/master/data/asoiaf-book')' + book + '-edges.csv' as value
MERGE (source:Person{id:value.Source})
MERGE (target:Person{id:value.Target})
WITH source,target,value.weight as weight,book
CALL apoc.merge.relationship(source,'INTERACTS_' + book, {}, {weight:toFloat(weight)}, target) YIELD rel
RETURN distinct 'done'
人物的死亡可以作为一个得到的 Kaggle 数据集得到。我们必须在运行导入查询之前下载它。两个数据集中的字符名称并不完全相同。我做了一个简单的匹配,得到了 66%的匹配率,对于这个演示来说已经足够好了。
LOAD CSV WITH HEADERS FROM "file:///character-deaths.csv" as row
WITH row WHERE row.`Book of Death` IS NOT NULL
MATCH (p:Person)
WHERE p.id = replace(row.Name,' ','-')
SET p.death_book = toInteger(row.`Book of Death`)
Cypher 投影
如果你读过我之前的博文,你会发现图形加载器是 Neo4j 图形算法库中的关键组件之一。它将存储的 Neo4j 图投影到内存中的投影图,这更适合运行图形算法。有两种方法可以投影内存中的图形:
在这篇博文中,我们将使用 cypher projection 方法。它由三部分组成:
- Node cypher 语句:cypher 查询,返回我们想要考虑的节点及其属性的 id
- Relationship cypher 语句:返回关系的源和目标节点的 id 以及我们想要考虑的属性的 cypher 查询。属性通常用作权重
- 算法配置:要使用 cypher 投影,我们必须定义
graph:'cypher'
CALL algo.louvain(
// Node cypher statement
'MATCH (n:Node) RETURN id(n) as n, n.seedValue as seedValue',
// Relationship cypher statement
'MATCH (source:Node)-[:RELATIONSHIP]->(target:Node)
RETURN id(source) as source, id(target) as target',
// Algorithm configuration
{graph:'cypher, seedProperty:'seedValue', writeProperty:'community'})
此外,请记住官方文档中的以下段落。
只有在 node 语句中描述了源节点和目标节点时,relationship 语句中描述的关系才会被投影,否则它们将被忽略。
我们可能选择 cypher 投影而不是标签和关系类型投影的原因是,我们获得了 cypher 查询语言的附加值,以及在描述我们想要投影的子图时它的表达能力。它还允许我们投射一个虚拟的(非存储的)图形。
社区检测
首先,我们将在卢万模块化算法的帮助下,根据第一本书中的互动来搜索角色社区。
CALL algo.louvain(
// Filter out only person who
// interacted in the first book
'MATCH (p:Person)
WHERE (p)-[:INTERACTS_1]-()
RETURN id(p) as id'
,
'MATCH (p:Person)-[:INTERACTS_1]-(p1:Person)
RETURN id(p) as source, id(p1) as target'
,{graph:'cypher', direction:'BOTH', writeProperty:'community_1'})
为了这个分析,我决定通过书籍来追溯丹妮莉丝·坦格利安的社区演变。
MATCH (p:Person{id:'Daenerys-Targaryen'})
RETURN p.id as name, p.community_1 as community
我们将在 NeovisJS 的帮助下可视化结果。你可以在 GitHub 上找到代码。
Node color indicates community
丹妮莉丝的社区可以归类为埃索斯社区。它主要由多斯拉克人以及乔拉·莫尔蒙人和伊利里欧·莫帕蒂人组成。与维斯特洛的联系仅限于丹妮莉丝和她的哥哥韦赛里斯·坦格利安通过艾德·史塔克、劳勃·拜拉席恩和瓦里斯。第一本书还没开始,雷加·坦格利安就已经死了。有趣的是,雷加在泰温、珊莎、丹妮莉丝和艾德·史塔克等主要角色周围被提及,你们中的一些人知道这部电视剧后来发生了什么。
种子属性参数
在第二次迭代中,我们将使用第一本书的社区结果作为种子属性。我第一次试着从第二本书开始在网络上运行第二次迭代,但没有得到任何有见地的结果,因为很多人死了,社区变化很大。为了解决这个问题,我综合使用了第一部和第二部书中的网络,只过滤掉了第一部中死去的角色。
关于种子属性需要注意的一点是,它现在不处理空值。所有出现在第二本书,但没有出现在第一本书的新角色,从第一次迭代的社区 id 为空。在 Max de Marzi 的帮助下,我们提出了一个策略,该策略总是为具有空种子属性的节点分配一个唯一的社区 id。我们从第一次迭代中找到最高的社区 id,并添加内部节点 id,以始终保证没有种子属性的节点的唯一标识符。恰当地说,我们将把它命名为最大策略。
CALL algo.louvain('
// Max strategy
MATCH (s:Person) WITH max(s.community_1) as max
MATCH (p:Person)
// Has been mentioned in book 1 or 2
WHERE (p)-[:INTERACTS_1|:INTERACTS_2]-() AND
// Filter our characters who die in the first book
NOT coalesce(p.death_book,10) < 2
RETURN id(p) as id,coalesce(p.community_1, max + id(p)) as community_1
','
MATCH (p:Person)-[:INTERACTS_1|:INTERACTS_2]-(p1:Person)
RETURN id(p) as source, id(p1) as target',
{graph:'cypher',direction:'BOTH',writeProperty:'community_2',
seedProperty:'community_1'})
使用 NeovisJS 可视化的结果。
Node color indicates community
红色社区是第一本书中死去的角色的社区。我们可以看到丹妮莉丝的社区在第一本书中受到了相当大的打击,因为社区中的九个角色都死了。我们还可以注意到,艾德·史塔克和劳勃·拜拉席恩死了,这减少了社区与外部世界的互动,只有瓦里斯和一个新加入的 Baristan Selmy。此外,乔拉和杰奥·莫尔蒙被一起提及,即使他们从未在书中见过面,如果我没记错的话。
第三次迭代
下一步,我们将从第二部和第三部中寻找网络,并过滤掉前两部中死去的角色。
CALL algo.louvain('
// Max strategy
MATCH (s:Person) WITH max(s.community_2) as max
MATCH (p:Person)
// Has been mentioned in book 2 or 3
WHERE (p)-[:INTERACTS_2|:INTERACTS_3]-() AND
// Filter our characters who die in the first or second book
NOT coalesce(p.death_book,10) < 3
RETURN id(p) as id,coalesce(p.community_2, max + id(p)) as community_2
','
MATCH (p:Person)-[:INTERACTS_1|:INTERACTS_2]-(p1:Person)
RETURN id(p) as source, id(p1) as target',
{graph:'cypher',direction:'BOTH',writeProperty:'community_3',
seedProperty:'community_2'})
结果
Node color indicates community
在这个迭代中,死亡角色的群体被涂成绿色。尽管卓戈和劳勃·拜拉席恩已经死了,但他们还是在第二部和第三部中不断被提及。我们可以观察到一些有趣的角色加入到社区中,比如米桑代和达里奥·纳哈里斯。总的来说,这个社区变得更加孤立,因为丹妮莉丝除了通过巴利斯坦·赛尔弥之外,与维斯特洛没有任何交流。
第四次迭代
对于最后一次迭代,我们将基于第三、第四和第五本书中的互动来查看网络中的社区,并过滤掉前三本书中死去的角色。
CALL algo.louvain('
// Max strategy
MATCH (s:Person) WITH max(s.community_3) as max
MATCH (p:Person)
// Has been mentioned in book 3 or 45
WHERE (p)-[:INTERACTS_3|:INTERACTS_45]-() AND
// Filter our characters who die in the first three books
NOT coalesce(p.death_book,10) < 4
RETURN id(p) as id,coalesce(p.community_3, max + id(p)) as community_3
','
MATCH (p:Person)-[:INTERACTS_3|:INTERACTS_45]-(p1:Person)
RETURN id(p) as source, id(p1) as target',
{graph:'cypher',direction:'BOTH',writeProperty:'community_4',
seedProperty:'community_3'})
结果
Node color indicates community
我们可以观察到丹妮莉丝的社区在这次迭代中规模增加了很多。它还通过提利昂·兰尼斯特、多兰·马爹利和瑟曦·兰尼斯特等人物与外部世界建立了大量联系。
结论
在我们进行下一次迭代之前,我们将不得不为第六本书多等一会儿。希望,很快:)
种子属性是对社区检测算法的一个很好的补充。除了 Louvain 算法,连接组件和标签传播算法也支持种子属性。
和往常一样,所有代码都可以在 GitHub 上获得:
社区论坛与数据科学相遇
分析论坛成员的活动、帖子和行为
Photo by Volodymyr Hryshchenko on Unsplash
总结 作为一名对数据科学充满热情的社区建设者和战略家,我发现数据科学技术的使用加深了我对所管理社区的理解,使我能够做出更好的战略和运营决策。在本文中,我旨在举例说明数据科学的使用如何使社区管理者能够更好地了解他们的社区,并提高成员的参与度。
Liferay 是一家开源技术公司,也是我的客户,它主办了 Liferay 社区论坛。这些都是非常活跃的公共论坛,注册会员超过 150,000 人。论坛主要是开发者之间关于 Liferay 技术的交流。在这篇文章中,我分析了 2007 年至 2017 年间论坛成员的活动、帖子和行为。本着开源精神,Liferay 允许我发表对他们过去 10 年成员活动的分析。
数据描述
为了开始我的数据分析(使用 Python),我从所有论坛数据的原始文件开始。通过一些数据清理,我创建了一个包含每个论坛消息细节的文件。文件中的每一行都是唯一的消息,包含以下字段:
- UserId:编写消息的用户
- CategoryID:消息出现的论坛类别,例如“门户框架”
- ThreadId:线程的 Id(包括最初的帖子和所有回复)
- 主题和正文:消息的内容
- 创建日期:消息的日期和时间
我使用命令 forums.tail(5)可视化了文件的最后 5 行:
让我们研究一下这些数据,以了解更多关于社区成员之旅的信息。
第一部分.会员之旅
使用函数和图探索数据:分析成员寿命
对于开发人员或客户的社区,了解成员随时间的变化可以让社区经理了解用户如何发展以及如何在不同阶段最好地吸引他们。
在这里,我看到了论坛中成员之间的典型互动。为了重现单个讨论线程的问题和答案,我使用了
forums.loc[forums.threadId==103912609].head(5)
通过“threadId”进行过滤会产生以下结果:
在这个讨论中,很明显,用户(41397961)正在纠结于一个技术问题:“服务构建器,构建失败”。一些有用的成员要求澄清并提供建议(在这种情况下,可能没有用)。看来有经验的会员正在努力帮助新手。事实上,论坛的一个中心目标是帮助会员在从新手到专家的旅程中前进。
正如 Liferay,Inc .的开发者倡导者 Jamie Sammons 所说:
“大多数贡献者通常从 Liferay 社区开始,作为消费者阅读文档、论坛和 Slack。对于许多人来说,积极参与的第一步是通过询问特定于其环境的问题在论坛中获得帮助。大多数开始帮助他人的贡献者通常是这样开始的,然后当他们觉得自己正在学习诀窍时,有时甚至会觉得有义务帮助他人来回报他们所获得的支持。”
了解会员之旅的另一个方面是确定用户的寿命。典型的会员是只发一次帖子,还是长时间保持活跃?我使用 Python 中强大的“分组”功能来创建这个数据框。你可以在 GitHub 中看到细节,下面出现了我的结果的一个例子。
利用这些数据,观察所有成员寿命的频率分布是很有趣的。与社区中的整体用户寿命相比,有多少用户的寿命只有 1 天?这就是可视化有帮助的地方。下面的图表显示,近 800 名成员的寿命为 1 天,此后频率大幅下降。
上面的图中没有显示的是 12,000 名会员——占发帖会员总数的 42%——他们的寿命为 0 天!(我将 Y 访问设置为最大 1,000,因此没有显示 12,000 成员数据点)。寿命为 0 天意味着第一条和最后一条消息在同一天;有可能这些成员只是发布了一条消息,然后就再也没有回到论坛。
利用数据:社区经理的含义
有了这些数据,社区管理者可以实现一个战略性的双重目标:增加成员的生命周期,并最小化生命周期为 0 天的成员数量。社区经理可以在一个成员写完一篇文章后发起一个特别的推广活动,并向新成员发送个性化消息,鼓励他们继续参与。或许他们甚至可以在“游戏化”系统中奖励新会员积分。
可视化数据:了解资深社区成员的寿命
我也有兴趣了解老会员的典型寿命。因此,我把上图中的时间尺度从几天改为几年,以关注退伍军人和他们在社区中的寿命。
lifespan_years = lifespan_days//365
freqyear = lifespan_years.value_counts()
plt.plot(freqyear,'.');
plt.axis([0, 12, 0,2000]);
plt.xlabel('period from first to last message (YEARS)')
plt.ylabel('member counts')
plt.title('Length of Active Membership')
plt.grid(True)
plt.show()
该图表显示,1800 名会员的论坛寿命为 1-2 年,从第 1 年到第 2 年和第 2 年到第 3 年急剧下降。2 或 4 年后见底并不奇怪,因为开发人员通常有持续 2-4 年的 Liferay 项目,之后他们会转向其他项目或公司。
正如 Liferay,Inc .的开发者倡导者 Jamie Sammons 所说:
“大多数情况下,开发人员为一家咨询公司工作,然后转向一个新项目,或者开发人员可能会彻底跳槽。在少数情况下,开发人员为 SI 或合作伙伴公司工作,由于业务良好,他们的工作量很大,根本没有时间做出贡献。”
利用从数据中获得的洞察力:社区管理含义
当以前活跃的成员在活跃两年多后变得安静时,他可以获得特殊的个人拓展,例如:“发生了什么?我们能为你的下一个项目提供帮助吗?”此外,这些成员可以被邀请参加更高层次的讨论(例如,项目或公司的未来路线图),而不是代码 Q &对话。
其他行为分析主题
使用会员登录信息可以收集到更多的信息。例如,可以根据 90%的注册者的登录时间戳,对他们进行分析,这些注册者根本没有发帖。这些定期登录的安静的“潜伏者”可以从真正不活跃的人中分离出来,他们的行为被分析以鼓励更高水平的参与。通过将论坛记录与其他信息源(如吉拉或 CRM 数据)相结合,可以收集到其他有趣的信息,从而实现对论坛成员跨平台相关活动的 360 度视图。分析社区中流行的发帖时间也可能很有见地,以便营销和传播团队可以确定在论坛中发帖的最佳时间(请记住,国际社区和时区可能会使这种分析变得棘手)。最后,了解计划的影响可能是有趣的。比如软件新版本发布后会员活跃度有没有提高?
分析社区成员在论坛上的活动有助于改善客户旅程和成员参与度。通过分析论坛帖子的实际内容,可以获得更高层次的洞察力。
第二部分。使用自然语言处理(NLP)了解社区
词汇云——理解对话主题
单个用户在一段时间内发布的实际内容可用于构建成员档案词云,组成词云的数据可用于从根本上改善成员在社区中的体验。例如,关于通过词云识别的主题的帖子可以出现在成员的活动提要中,使提要更相关和更有吸引力。
对于下面的单词云,我使用了标准的阻止列表(程序将忽略的单词列表),它保留了诸如“登录”和“社区”之类的单词。为了降低语料库中所有常用词的重要性(“login”是 Liferay 论坛中的常用词),我可以应用“词频-逆文档频率”(TFIDF)。
发布了 887 条消息的单个成员的 Word cloud:
from wordcloud import WordCloud
text = resultdef generate_wordcloud(text):
wordcloud = WordCloud( relative_scaling = 0.5,
stopwords = {'re','to', 'of','for','the','is','Liferay','in','and','on','from','with'}
).generate(text)
plt.imshow(wordcloud)
plt.axis("off")
plt.show()generate_wordcloud(text)
字数和三元模型——确定引发讨论的主要话题
整个社区都在谈论什么,成员们是如何互动的?下面的图表包含了两个 Liferay 论坛类别(“公告”和“开发”)中每个讨论主题行的字数。
对主题中的单词进行标记后,我们可以列出每个类别中的前 10 个单词:
devWords = [word **for** word **in** words **if** word **not** **in** stoplist]
fdist_dev = nltk.FreqDist(devWords)
dev_common = fdist_dev.most_common(10)
然后,我们可以构建一个数据框架来比较不同类别的常用词:
dfWord = pd.DataFrame(
{'Development': dev_common,
'Announcements': announce_common
})
dfWord.index += 1
dfWord
“!”公告类别在庆祝成功的专业团体中更常见(例如,“祝贺销售成功!).在 Liferay 论坛的情况下,对话类似于典型的“信息传播”社区(具有诸如“已发布”和“可用”的关键字)。
那个“?”出现在开发类别中的单词“how”对于这个社区来说是典型的,其中主要的用例是成员向其他成员询问技术或产品相关的问题。这种“问答”内容结构可以通过查看下面最常见的三元模型(3 个单词的组合)来确认。
发展类别三元模型:
tgs = nltk.trigrams(words)
fdistT = nltk.FreqDist(tgs)
fdistT.most_common(5)
关于数据和 NLP 分析的注释
- 对于这种类型的分析,请确保字符如“?”还有“!”被考虑(即不包括在禁止列表中)
- 公告类别比开发类别小得多,因此字数也少得多(但相对排名是最重要的)。
- 在 GitHub 上,我展示了将 1000 个主题行转换成单词列表的步骤(组块、标记化等)。
关键词分析 NLP 如何提高社区管理的效率
NLP 也可以帮助日常的,但基本的,社区管理任务。例如,技术论坛中的一个常见挑战是获得社区成员提出的所有问题的良好答案(从而培养社区内的信任和参与。)
Liferay 使用的典型方法是指派专家负责每个论坛类别。然而,在 Liferay 的案例中,近 40%的 Liferay 信息属于一个类别,“开发”,指派专家来涵盖这整个包罗万象的类别是不切实际的。为了解决这个问题,我们可以在这个类别中寻找一些讨论的关键主题,这些主题可以分离出来,形成更小、更易于管理的讨论类别。关键字分析在这里非常有用,但在这种情况下,字符和短词没有帮助,所以我过滤了较长的词:
long_words = [word for word in words if
len(word) > 2 and word not in stoplist]
fdistLong = nltk.FreqDist(long_words)
fdistLong.most_common(50)
显然,这里有一些很好的候选主题,可以从拥挤的“发展”类别中分离出来:
1.Portlet
2。JSP
3。建造者
4。主题
5。数据库ˌ资料库
二元模型(两个词的组合)揭示了其他好的潜在主题,包括“服务构建器”、“定制 portlet”和“文档库”。了解热门话题及其随时间的变化趋势也很有帮助。例如,在下面的分散图中,很明显,当“6.1”(推测是 Liferay 的一个版本)出来时,社区热烈地讨论了它——然后讨论安静了下来。随着“6.2”的发布,围绕“6.1”和“6.2”的讨论也有所重叠。“6.1”和“6.2”的代码和色散图如下所示。
mytext = nltk.Text(words)
mytext.dispersion_plot(["6.1","6.2"])
以上功能和可视化只是冰山一角;通过分析用户行为和内容,可以发现更多的东西。唉,使用最新的人工智能工具进行有意义的分析,尤其是“非结构化”分析(系统事先不知道它在寻找什么)需要大量数据。即便如此,对小数据集进行相对基本但经过深思熟虑的分析,可以显著改善社区管理。了解成员的行为及其内容可以增强社区管理者有效服务其社区的能力,增加其社区的相关性,进而增加参与度、成员数和影响力。
注:样本数据中未出现姓名或个人详细信息。最初的论坛帖子和成员简介是公开的,所以这是一个额外的警告步骤。
伦敦自行车共享网络的社区结构、互动和动态
通过无监督机器学习获得对共享自行车系统的运营洞察力
几乎每个大城市都有自行车共享计划。所有的自行车都连接在一起,产生无数的数据。大多数运营商提供的数据侧重于站点/自行车可用性。然而,伦敦运输(TfL)公司在其开放数据平台上发布他们的自行车运动(桑坦德 BSS ),使用自行车 ID,让您可以跟踪网络中每辆自行车的运动。😮
这为我们如何分析数据提供了新的可能性。在这篇文章中,我们将探讨如何使用数据挖掘技术来理解伦敦 BSS 中的社区。
什么是社区?
一个社区可以有很多含义。剑桥词典将其定义为:
居住在某一特定地区的人或由于共同的兴趣、社会团体或国籍而被视为一个整体的人
就运输系统而言,这与我们想要做的并不太远。也就是说,我们希望将传输网络的复杂性降低到行为相似的单元或集群中。在这里,我们将站分组,以更好地了解系统如何工作,并能够做出更好的操作决策。
使用 R 和其他工具,我们将简化来自伦敦数据存储的起点-终点数据。
导入和过滤我们的数据
我们选择的数据集已经被提取并复制到我们的工作目录中。我们选择的时间段是 2014 年 6 月至 7 月,包括 150 万次旅行。在通过我们的社区检测算法之前,我们需要采取一些步骤。
为了加载它,我使用了来自data.table
的优秀的fread()
函数。此外,我们需要删除缺失或等于零的自行车 id 和终点站。我们删除了仅作为目的地(维修店)和周末存在的加油站,这需要进行单独的分析。
社区检测算法
有许多社区检测算法,其中大多数使用模块性的度量来检测集群。但是,在本分析中,我们将把它与最先进的社区检测算法 信息图 进行比较,后者改进了基于模块化的方法,放宽了关键假设:
- 无向信息
- 分辨率限制
- 网络形成的过程
这些将被证明是非常相关的运输集群的准确检测。
我们将比较以下方法:
- 信息图
- 勒芬
- 贪婪的
- 随机漫步
所有这些都可以在伟大的igraph
库中运行。在分析时,Infomap 的二进制文件的最新版本还没有实现,我们决定直接使用二进制文件。
要将信息图算法作为系统命令运行,我们可以使用以下命令。注意它只在 MacOSX 上测试过,但是如果你在正确的文件夹中有 Infomap 的二进制文件,它应该可以工作。使用 v0.19.3 版本完成了信息图社区检测。关于如何在您的机器上运行信息图的说明可以在这里找到。
结果
Infomap 在发现伦敦网络中已知的物理结构方面优于基于模块性的社区检测算法(如金丝雀码头和海德公园)。
这些算法都没有给出关于车站位置的信息。但是所有的结果都非常依赖于空间。
Comparison of the community clustering algorithms
鉴于中部/东部社区的规模,我们怀疑其中可能有隐藏的社区。与此同时,金丝雀码头和海德公园似乎大多自成一体。
Average cluster location and connections to the other clusters.
使用 Gephi 软件创建的信息图聚类的独立可视化。它还显示了研究期间不同站点之间的行程。
Results of the Infomap algorithm using Gephi.
奖励回合:动态分析
作为额外的分析,我们仅使用 *Infomap 执行动态社区检测。*分析表明,社区具有时间依赖性,社区受空间、时间和位置的驱动。
Community dynamics
如果我们要扩大分析数据的时间范围,那么缺少夜间出行就不可能找到可能改变的模式。早上和下午高峰的通勤模式也可能导致极端的结果,即最佳聚类数只有一个。
结论
这种方法使我们能够深入了解网络的运行方式。它可以帮助决策者改善网络和物流运营商优化他们的自行车运动。我们能够找到传统方法无法找到的地理位置偏远或使用模式一致的专业社区。
承认
这篇文章是下一篇文章的姊妹篇
费尔南多·穆尼奥斯·门德斯、康斯坦丁·克莱默、汉克和斯蒂芬·贾维斯。2018.伦敦自行车共享网络的社区结构、互动和动态。在 2018 年 ACM 国际联合会议和 2018 年普适计算和可穿戴计算机国际研讨会(UbiComp '18)的会议录中。美国纽约州纽约市美国计算机学会,1015–1023。https://doi.org/10.1145/3267305.3274156
你可以在这里找到预印本,在这里找到一些用于生成图和结果的代码。
使用 Pandas 比较不同 Excel 文件中的列值
Image Courtesy of Joshua Hoehne via Unsplash
熊猫为列匹配
通常,我们可能希望比较不同 Excel 文件中的列值,以搜索匹配和/或相似性。使用 Python 中的 Pandas 库,这是一个简单的任务。为了证明这是如何可能的,本教程将集中在一个简单的遗传例子。不需要遗传学知识!
考虑以下情况:我有一些未知的 DNA 序列样本(由字母 A、T、G 和 c 组成的简单字符串)。对于这些未知的 DNA 序列,我会分配一个唯一的标识符。为了简单起见,第一个未知序列将被标识为 1,第二个被标识为 2,依此类推,如下所示。
在这个有些人为的例子中,我想比较我的未知序列和已知的参考序列。
简而言之,我的问题是,‘未知 _ 样本 _ 否’中的任何一个与我的‘引用 _ 序列 _ ID’匹配吗?如果有,它们与哪个(些)序列匹配。
为了开始回答这个问题,我首先导入 pandas 模块,并将相应的文件作为 csv 文件读取。注意:我将这些文件在 Excel 中保存为逗号分隔值文件(csv 文件),并使用 read_csv() 函数解析它们。严格来说,这不是必须的,但这是我喜欢的一种工作习惯。您也可以将 Excel 文件保留在原生的中。xlsx 扩展名,并使用 pandas.read_excel() 函数在这里保存一个步骤。
查询序列 DataFrame 被赋予了变量 A,而序列引用 DataFrame 被赋予了变量 b。
然后,我将两个数据帧中的两列都转换成 python 列表。为此,我使用了**。tolist()** 对特定数据帧的指定列执行方法。举例来说,数据帧 A 中的列“未知 _ 样本 _ 否”被转换成列表。我对分布在两个 Excel 文件中的其他三列中的每一列执行这个步骤,如下面的代码片段所示。
我想保留“未知 _ 样本 _ 编号”与其对应的“未知 _ 样本 _ 序列”之间的关联。此外,我还想保留‘Reference _ sequences _ ID’与其对应的‘Reference _ sequences’之间的关联。Python 提供了一种维护这种关联的简单方法。我把我的清单转换成两本字典。
我使用 zip 函数连接列表,然后使用 dict 函数将它们转换到适当分配的字典中。
为了确认字典已经被正确创建,我在我的终端上运行这个脚本。一个简短的验证检查告诉我一切都在按计划进行。例如,在 Ref_dict 中,“Reference_sequences_ID”键与其对应的“Reference_sequences”值正确匹配:{ ’ A ‘:’ aaaaagcgcgaggggga ‘,’ K’: 'GGGAGAGAGGG ‘,’ Y ‘:’ cggacgttt '……}
我现在可以把我的两本词典互相比较了。为此,我使用一个 for 循环来遍历我的“Unknown_dict ”,并使用一个嵌套的 for 循环来遍历 Ref_dict。在每次迭代时,我想知道 Unknown_dict 中的序列是否与 Ref_dict 中的任何序列匹配(有些会*,我故意包括了 8 个* 匹配)。为了检查匹配,我使用了来自 re 模块(正则表达式模块)的 re.search() 函数。
当有匹配时,我认为知道什么序列匹配,匹配出现在 DNA 序列中的什么位置,以及最重要的是什么‘未知 _ 样本 _ 编号’与哪个‘参考 _ 序列 _ ID’匹配将是有用的。
为了使事情更清楚,如果所有的匹配都被写到一个可以在 Excel 中打开的 csv 文件中,那将是非常理想的。理想情况下,目标是让任何想要解释数据的人都清楚,哪些序列匹配,匹配序列是什么,在哪里匹配。
为了实现这个目标,我写了一个名为“seq_match_compare.csv”的文件,当我打开 csv 文件时,所有我想要的信息都以一种可解释的方式出现了!
例如,我现在知道 Query_ID: 1 对应于 Ref_species A,这个匹配从序列中的位置 4 开始。
有多种方法可以比较两个不同 excel 文件中的列值。这里的方法将第一个文件的未知序列列中的每个序列与第二个文件的 Reference_sequences 列中的每个序列进行检查。
这种方式代表了一种简单的匹配和比较方式,如果我们想要分析任意数量的样品,这种方式提供了极大的可扩展性!
所有这些都是通过熊猫实现的!