原文地址:https://www.pilosa.com/docs/latest/examples/

位图数据库:Pilosa查询十亿级出租车搭乘数据案例

简单说明 Introduction

纽约市发布了一个非常详细的包含了超过10亿条出租车搭乘数据的集合。该数据已经成为科技博客分析的热门目标,并且已经得到了很好的研究。出于这个原因,我们认为将这些数据导入Pilosa,以便确定同一数据集情况下与其他数据存储和技术进行比较。

一般来说,传输(Transportation)是Pilosa的值得关注的用例,因为它通常涉及多个不同的数据源,以及高速率,实时和极大量的数据(特别是如果想得出合理的结论)。

我们编写了一个工具来帮助将NYC(纽约市)出租车数据导入Pilosa这个工具是PDK(Pilosa开发工具包)的一部分,并利用了许多可重复使用的模块,这些模块也可以帮助您导入其他数据。 接下来,我们将逐步解释整个过程。

初始设置之后,PDK导入工具会执行我们定义Pilosa架构所需的一切,相应地将数据映射到位图,然后将其导入Pilosa

数据模型 Data Model

纽约出租车数据由以下列出的许多csv文件组成:http://www.nyc.gov/html/tlc/html/about/trip_record_data.shtml。 这些数据文件大约有20列,其中大约一半与我们正在研究的基准查询相关:

  • 距离(Distance): miles(英里), floating point(浮点值)
  • 车费(Fare): dollars(美元), floating point(浮点值)
  • 乘客人数(Number of passengers): integer(整数值)
  • 下车位置(Dropoff location): latitude and longitude(经纬度), floating point(浮点值)
  • 上车位置(Pickup location): latitude and longitude(经纬度), floating point(浮点值)
  • 下车时间(Dropoff time): timestamp(时间戳)
  • 上车时间(Pickup time): timestamp(时间戳)

注意:下面表格中的row ID是指记录的取值范围,不要理解成MySQL等数据库的rowID。

我们导入这些字段,从每个字段创建一个或多个Pilosa字段:

字段(Field) 映射(Mapping)
cab_type(出租车类型) 直接映射整数枚举值 → row ID
dist_miles(距离) 四舍五入round(dist) → row ID
total_amount_dollars(总金额) 四舍五入round(dist) → row ID
passenger_count(乘车人数) 直接映射整数值 → row ID
drop_grid_id(下车位置网格ID) (lat, lon) → 100x100矩形分割网格 → cell(格子) ID
drop_year 年份year(timestamp) → row ID
drop_month 月份month(timestamp) → row ID
drop_day 该月第几天day(timestamp) → row ID
drop_time(下车时间) 该天中的时间映射到48个半小时组成的桶中
pickup_grid_id(下车位置网格ID) (lat, lon) → 100x100矩形分割网格 → cell ID
pickup_year year(timestamp) → row ID
pickup_month month(timestamp) → row ID
pickup_day day(timestamp) → row ID
pickup_time(上车时间) time of day mapped to one of 48 half-hour buckets → row ID

我们还创建了两个附加字段表示持续时间和每一次乘坐的平均速度:

字段(Field) 映射(Mapping)
duration_minutes(持续时间) round(drop_timestamp - pickup_timestamp) → row ID
speed_mph() round(dist_miles ÷ (drop_timestamp - pickup_timestamp)) → row ID

映射Mapping

我们要使用的每个列(column)都必须根据某些规则映射到字段(fields)row ID的组合。 有很多方法可以实现这个映射,出租车数据集为我们提供了一个可能性的很好的描述。

0列(colums) --> 1字段(field)

cab_type: 每一行表示一个出租车的类型,在一行数据中有一些bit位来表示一次乘车中使用的出租车类型。 这是一个简单的枚举映射,例如黄色yellow=0,绿色green=1等。这个字段值的位宽(bits)由数据源确定。也就是说,我们有几个数据来源(NYC出租车-黄色, NYC出租车-绿色,Uber汽车),对于每一个数据来源,要设置不同的cab_type常量值。

1列(colums) --> 1字段(field)

以下三个字段以简单的直接方式从原始数据的单个列进行映射。

dist_miles:每一行值(row ID)表示乘车的距离,这个映射关系很简单:例如行值1(row 1)表示乘车距离在[0.5,1.5]这个区间内。也就是说,我们将距离这个浮点值舍入为整数并将其直接用作row ID。通常,从浮点值到row ID的映射可以是任意的。

舍入映射实现简洁,简化了导入和分析。并且,它是人类可读的。 我们会看到这种模式多次使用。

PDK使用中,我们定义了一个Mapper,它是一个只返回整数row ID的函数。 PDK具有许多预定义的映射器,可以使用一些参数进行描述。 其中之一是LinearFloatMapper。在下面代码中它将线性函数应用于输入,并将其转换为整数,因此隐式处理舍入。

lfm := pdk.LinearFloatMapper{
Min: -0.5,
Max: 3600.5,
Res: 3601,
}

MinMax定义线性函数,Res确定输出row ID的最大允许值。我们选择这些值以产生一个舍入到最接近的整数的行为。其他预定义的映射器也有自己的特定参数,通常是两个或三个。

这个映射函数(mapping function)是核心操作,但我们需要一些其他部分来定义整个过程,它封装在BitMapper对象中。此对象定义要使用的输入数据源的哪些字段(Field),如何解析它们(Parsers),要使用的映射(Mapper)以及要使用的字段的名称(Frame)。TODO更新,这是合理的。

pdk.BitMapper{
Frame: "dist_miles",
Mapper: lfm,
Parsers: []pdk.Parser{pdk.FloatParser{}},
Fields: []int{fields["trip_distance"]},
},

这些相同的对象在JSON定义文件中表示:

{
"Fields": {
"Trip_distance": 10
},
"Mappers": [
{
"Name": "lfm0",
"Min": -0.5,
"Max": 3600.5,
"Res": 3600
}
],
"BitMappers": [
{
"Frame": "dist_miles",
"Mapper": {
"Name": "lfm0"
},
"Parsers": [
{"Name": "FloatParser"}
],
"Fields": "Trip_distance"
}
]
}

在这里,我们定义一个Mappers列表,每个Mappers都包含一个名称,我们将在稍后的BitMappers列表中用它来引用mapper。我们也可以使用Parsers进行此操作,但默认情况下可以使用一些不需要配置的简单解析器。 我们还有一个Fields列表,它只是一个字段名称(在源数据中)到列索引(在Pilosa中)的映射。 我们使用的BitMapper定义这些名字让人可读。

total_amount_dollars:在这里,我们再次使用舍入映射,因此每行代表一次行程的总成本,该成本将舍入映射为row ID。 BitMapper定义与前一个定义非常相似。

passenger_count:此列包含小整数,因此我们使用最简单的映射之一:列值是就是row ID。

1列(colums) --> 多字段(multiple field)

使用复合数据类型(如时间戳timestamp)时,有很多映射选项。 在这种情况下,我们希望看到有趣的周期性趋势。因此,我们希望以一种允许我们在分析过程中独立查看它们的方式对时间的循环分量进行编码。

我们通过将时间数据存储在每个时间戳的四个单独字段中来实现此目的:对于年year``月month``日day时间time各一个。 前三个是直接映射的。例如,乘坐的日期2015/06/24将在字段year的第2015行,字段month的第6行和字段day的第24行中设置。

我们可能会在几小时、几分钟和几秒钟内继续使用这种模式,但是我们在这里没有太多使用这种精度,所以我们使用bucketing方法。 也就是说,我们选择一个分辨率(30分钟),将日期划分为该大小的桶,并为每个桶创建一行。 因此,6:45 AM时间在time_of_day字段的第13行中设置了bit位(位图中)。

我们针对每个感兴趣的时间戳执行所有这些操作,一个用于上车时间,一个用于下车时间。 这为我们提供了两个时间戳的总字段:pickup_yearpickup_monthpickup_daypickup_timedrop_yeardrop_monthdrop_daydrop_time

多列(multiple colums) --> 1字段(field)

乘坐数据还包含地理定位数据:上车和下车的纬度和经度。 我们只是希望能够生成乘坐位置的粗略概述热图,因此我们使用网格映射。 我们将感兴趣的区域划分为经纬度空间中的100x100矩形网格,使用单个整数标记此网格中的每个单元格,并使用该整数作为row ID。

我们为每个感兴趣的位置做了所有这些,一个用于上车,一个用于下车。 这为两个位置提供了两个字段:pickup_grid_iddrop_grid_id

同样,位置数据有许多映射选项。 例如,我们可能会转换为不同的坐标系,应用投影或将位置聚合到实际区域(如邻域neighborhoods)。 这里,简单的方法就足够了。

复合映射(Complex mappings)

我们还期望寻找行程持续时间和速度的趋势,因此我们希望在导入过程中捕获此信息。

对于字段duration_minutes我们使用round((drop_timestamp - pickup_timestamp).minutes)计算row ID

对于字段speed_mph我们使用round(dist_miles / (drop_timestamp - pickup_timestamp).minutes)计算row ID`。

这些映射计算很简单,但由于它们需要对多列进行算术运算,因此在PDK中提供的基本映射器中捕获它们有点过于复杂。 相反,我们定义自定义映射器来完成工作:

durm := pdk.CustomMapper{
Func: func(fields ...interface{}) interface{} {
start := fields[0].(time.Time)
end := fields[1].(time.Time)
return end.Sub(start).Minutes()
},
Mapper: lfm,
}

导入处理

设计这个架构和映射后,我们可以使用PDK导入工具读取的JSON定义文件捕获它。

运行pdk taxi会根据此文件中的信息运行导入。 有关更多详细信息,请参阅PDK部分,或查看代码本身。

查询

现在我们可以运行一些示例查询。

可以在一个PQL调用对cab类型进行检索、排序、计数。

TopN(cab_type)
{"results":[[{"id":1,"count":1992943},{"id":0,"count":7057}]]}

可以使用类似的调用检索高流量位置ID。 这些ID对应于经纬度,可以从生成ID的映射中反算。

TopN(pickup_grid_id)
{"results":[[{"id":5060,"count":40620},{"id":4861,"count":38145},{"id":4962,"count":35268},...]]}

每个passenger_count(乘客计数)的total_amount(总金额)的平均值可以通过一些后处理来计算。我们使用少量的TopN调用来检索passenger_count的行程计数, 然后使用这些计数来计算平均值。

queries = ''
pcounts = range(10)
for i in pcounts:
queries += "TopN(Row(passenger_count=%d), total_amount_dollars)" % i
resp = requests.post(qurl, data=queries) average_amounts = []
for pcount, topn in zip(pcounts, resp.json()['results']):
wsum = sum([r['count'] * r['key'] for r in topn])
count = sum([r['count'] for r in topn])
average_amounts.append(float(wsum)/count)

注意,BSI-powered Sum查询现在提供了这种查询的替代方法。

有关更多示例和详细信息,请参阅此ipython notebook

Pilosa文档翻译(三)示例的更多相关文章

  1. 安卓图表引擎AChartEngine(三) - 示例源码折线图、饼图和柱状图

    折线图: package org.achartengine.chartdemo.demo.chart; import java.util.ArrayList; import java.util.Lis ...

  2. Pilosa文档翻译(一)导言、安装

    目录 导言 安装 安装在MacOS 使用HomeBrew 下载二进制文件 从源码构建 使用Docker 安装在Linux 下载二进制文件 从源码构建 使用Docker 接下来是什么? 导言 原文地址 ...

  3. Pilosa文档翻译(二)入门指南

    目录 开始 Pilosa 简单项目 创建架构(Create the Schema) 从CVS文件导入数据 做一些查询(Queries) 接下来做什么? Pilosa支持默认使用JSON的HTTP接口. ...

  4. Orchard官方文档翻译(三) 通过zip文件手动安装Orchard

    原文地址:http://docs.orchardproject.net/Documentation/Manually-installing-Orchard-zip-file 想要查看文档目录请用力点击 ...

  5. Log4j官方文档翻译(三、配置)

    之前的章节介绍了log4j的核心组件,本章将会通过配置文件介绍一下核心组建的配置. 主要在配置文件中配置log4j的日志级别,定义appender.layout等. log4j.properties是 ...

  6. Android官方文档翻译 三 1.1Creating an Android Project

    Creating an Android Project 创建一个Android项目 An Android project contains all the files that comprise th ...

  7. 安卓图表引擎AChartEngine(四) - 源码示例 嵌入Acitivity中的折线图

    前面几篇博客中都是调用ChartFactory.get***Intent()方法,本节讲的内容调用ChartFactory.get***View()方法,这个方法调用的结果可以嵌入到任何一个Activ ...

  8. 安卓图表引擎AChartEngine(二) - 示例源码概述和分析

    首先看一下示例中类之间的关系: 1. ChartDemo这个类是整个应用程序的入口,运行之后的效果显示一个list. 2. IDemoChart接口,这个接口定义了三个方法, getName()返回值 ...

  9. 【UNIX网络编程第三版】阅读笔记(一):代码环境搭建

    粗略的阅读过<TCP/IP详解>和<计算机网络(第五版)>后,开始啃这本<UNIX网络编程卷一:套接字联网API>,目前linux下的编程不算太了解,在阅读的过程中 ...

随机推荐

  1. day46 html

    老师的笔记: day46 课程安排 HTML CSS JS基础 DOM操作 jQuery Bootstrap pymysql Django基础 项目实战 Vue.js 今日概要: "PUT ...

  2. laravel启动过程简单解析

    :first-child{margin-top:0!important}img.plugin{box-shadow:0 1px 3px rgba(0,0,0,.1);border-radius:3px ...

  3. 上传前端webuploader

    多文件上传时,是有几个文件调用几次方法. 可以设置为单线程.

  4. Jenkins部署码云SpringBoot项目到远程服务器

    本文是上一篇文章的后续,上一篇只是利用Jenkins部署项目到本地,并启动,本文是将项目部署到远程服务器并执行. 1.环境准备 1.1 安装插件 上一篇文章已经介绍了需要安装的应用及插件,这一篇还需要 ...

  5. SpringBoot整合mongoDB

    MongoDB 是一个介于关系数据库和非关系数据库之间的产品,是非关系数据库当中功能最丰富,最像关系数据库的. 这一片文章介绍一个springboot整合mongodb,如果你了解整合mysql之类的 ...

  6. BZOJ.2159.Crash的文明世界(斯特林数 树形DP)

    BZOJ 洛谷 挺套路但并不难的一道题 \(Description\) 给定一棵\(n\)个点的树和\(K\),边权为\(1\).对于每个点\(x\),求\(S(x)=\sum_{i=1}^ndis( ...

  7. Linux下redis 的部署、主从与集群

    老男孩Python全栈6期——redis--------------------------Linux 操作系统 默认的内存管理机制RSS:page cache:anno page:Linux操作系统 ...

  8. Leetcode 记录(1~100)

    5.回文串 几种方法: 暴力:枚举每一个字串,判断是否为回文串,复杂度O(n^3),暴力月莫不可取 dp:区间dp思想,O(n^2) 中心扩展:找每一个字符,然后往两边扩展,O(n^2) manach ...

  9. BZOJ4437 : [Cerc2015]Looping Labyrinth

    从$(0,0)$开始BFS$2\times10^6$步,那么迷宫的形状有三种: 1.走不完$2\times10^6$步,直接判定即可. 2.可以走到$(n,0)$以及$(0,m)$,那么直接把询问点平 ...

  10. input输入框只能输入数字和 小数点后两位

    //input输入框只能输入数字和 小数点后两位 function num(obj,val){ obj.value = obj.value.replace(/[^\d.]/g,"" ...