我试着使用 Apache Arrow
听说Apache Arrow很不错,所以我立刻开始研究了一下。
Apache Arrow是什么?
→ Apache Arrow 的官方网站链接:https://arrow.apache.org
目标是在语言(项目)之间优化和加速数据交换(大致如此,可能有所不同)
顺便提一下,最近参与的一个项目中,我们用Ruby整理数据,然后用Python进行机器学习。在那个时候,我们将Ruby端整理好的数据写入到文件中,然后在Python端读取该文件进行处理,将结果写入文件,最后在Ruby端读取并在Javascript(浏览器)中进行图形化处理。
在这种场合可以使用吗?
所以,我试试看。
用 Ruby 语言处理 Apache Arrow
由于宝石(Gem)中存在,因此进行安装。
source "https://rubygems.org"
gem "rake"
gem "red-arrow"
$ bundle install --path vendor/bundler
安装完成了。
赶快来试试吧。
# -*- coding: utf-8 -*-
require "arrow"
class RedArrowTest
# ------------------------------------------------
def read(streamname)
input = Arrow::MemoryMappedInputStream
input.open(streamname) do |inp|
reader = Arrow::RecordBatchStreamReader.new(inp)
fields = reader.schema.fields
p "fields: #{fields.collect{|c| c.name }}"
reader.each do |r|
p [r]
end
end
end
def readfile(filename)
input = Arrow::MemoryMappedInputStream
input.open(filename) do |inp|
reader = Arrow::RecordBatchFileReader.new(inp)
fields = reader.schema.fields
p "fileds: #{fields.collect{|c| c.name }}"
reader.each do |r|
p [r]
end
end
end
# ------------------------------------------------
def write(streamname, schema, columns)
output = Arrow::FileOutputStream
output.open(streamname, false) do |oup|
writer = Arrow::RecordBatchStreamWriter.open(oup, schema) do |wrt|
recordbatch = Arrow::RecordBatch.new(schema, columns[0].count, columns)
wrt.write_record_batch(recordbatch)
end
end
end
def writefile(filename, schema, columns)
output = Arrow::FileOutputStream
output.open(filename, false) do |oup|
writer = Arrow::RecordBatchFileWriter.open(oup, schema) do |wrt|
recordbatch = Arrow::RecordBatch.new(schema, columns[0].count, columns)
wrt.write_record_batch(recordbatch)
end
end
end
# ------------------------------------------------
# sample data
def createdata
fields = [ Arrow::Field.new("name", :string),
Arrow::Field.new("age", :int8) ]
schema = Arrow::Schema.new(fields)
names = ["taro", "jiro", "hanako", "tsukiko", "saburo"]
ages = [26, 21, 24, 17, 10]
columns = [ Arrow::StringArray.new(names),
Arrow::Int8Array.new(ages) ]
return schema, columns
end
end
# ----------------------------------------------------------------
# run
ra = RedArrowTest.new
# create sample data :p
schema, columns = ra.createdata
ra.write("hoge", schema, columns)
ra.read("hoge")
#ra.writefile("hoge", schema, columns)
#ra.readfile("hoge")
看起来有通过文件和内存两种方式。
→ read、write是通过内存操作,readfile、writefile是通过文件操作。
但不管怎样,这两种方式最终都会生成文件。。。
内容似乎有点不同,但是,暂时还是先使用内存方式吧。
开始执行吧。
$ bundle exec ruby test.rb
"fields: [\"name\", \"age\"]"
[#<Arrow::RecordBatch:0x7feaf2057500 ptr=0x7feaf30f0f90 name: [
"taro",
"jiro",
"hanako",
"tsukiko",
"saburo"
]
age: [
26,
21,
24,
17,
10
]
>]
成功了。
实际上,在当前目录下创建了一个名为hoge的文件,并且它看起来是存在的。
用Javascript实现Apache Arrow。
既然可以在语言之间交换数据,那么我会尝试用 JavaScript 读取由 Ruby 创建的数据。
Node.js 是一种以 JavaScript 编写的开放源代码的跨平台 JavaScript 运行环境。
我曾经看到写着可以在JavaScript中使用,所以以为它也可以轻松地在浏览器端使用,但似乎完全不是这样。
首先,我们要安装 Node.js。
由于我使用的是 Mac,所以我会通过 Homebrew 进行安装。
有一种名为 nodebrew 的工具可以管理 Node.js 的各种包和其他内容。我们可以使用它来安装 Apache Arrow。
$ brew install nodebrew
$ mkdir -p ~/.nodebrew/src
$ nodebrew install-binary latest ← nodebrew install latest でもいいらしい。
install-binary はバイナリをインストール。install はソースからインストールらしい。。
$ nodebrew list
v11.1.0
current: none
好的,已經安裝完成。
不過現在還不能使用,需要進行設定。
$ nodebrew use 11.1.0
use v11.1.0
将路径添加到~/.bash_profile中
# Node.js
export PATH="$HOME/.nodebrew/current/bin:$PATH"
暂时,我们已成功安装了 Node.js
我尝试使其能在浏览器端使用。
我在互联网上做了各种搜索,但没有找到直接在浏览器端使用的方法。作为替代,有人通过在Node.js上建立服务器,然后使用jQuery访问该服务器的方式来传递数据,所以我决定模仿这种方法。
用的框架
使用 Node.js 的包管理工具是 npm。通过它可以安装各种包。
看起来需要在当前目录下创建一个名为 node_modules 的文件夹,然后把相关文件保存在里面。
因此,首先我们需要进入项目文件夹然后进行安装。
$ cd appfolder
$ npm install express
让我们也安装Apache Arrow。
$ npm install apache-arrow
建立服务器
"use strict"
// apache-arrow service :p
const express = require("express");
const port = 8010;
const app = express();
app.use(express.static("./public"));
// allow CrossSiteScript
app.use(function(req, res, next) {
res.header("Access-Control-Allow-Origin", "*");
res.header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept");
next();
});
// say hello
app.get("/", (req, res) => {
res.send("Hello, world!");
});
// arrow
app.get("/input", (req, res) => {
const fs = require("fs");
const aa = require("apache-arrow");
const path = "./";
const encoding = "binary";
var filename = req.query.n;
//console.log("filename: " + filename);
// result data
var result = {};
if(filename != undefined){
try {
var adata = fs.readFileSync(path + filename);
var table = aa.Table.from([adata]);
var schema = table.schema;
var fldnames = new Array();
for(var i = 0; i < schema.fields.length; i++){
fldnames.push(schema.fields[i].name);
result[schema.fields[i].name] = new Array();
}
for(var j = 0; j < fldnames.length; j++){
var column = table.getColumn(fldnames[j]);
for(var i = 0; i < column.length; i++){
result[fldnames[j]].push(column.get(i));
//console.log(column.get(i));
}
}
} catch(ex){
// nop :p
}
}
res.json(result);
});
// run service!
app.listen(port);
我会让它跑起来。
$ node arrow_service.js

通过Ajax访问
我們將使用jQuery進行訪問。
首先,在application.js中創建一個函數。
function arrowInput(arrowname) {
var result;
$.ajax({ type: "GET",
url: "http://localhost:8010/input",
data: { n: arrowname },
async: false,
dataType: "json",
success: function(data){
result = data;
}
});
return result;
}
这次我们将Ajax从异步改为同步。在这方面,根据项目需要进行一些调整试试看。
然后我们会开始制作HTML部分。
<html>
<head>
<title>Red Arrow Test</title>
<script type="text/javascript" src="/javascripts/jquery-3.2.1.min.js"></script>
<script type="text/javascript" src="/javascripts/application.js"></script>
</head>
<body>
<h3>Red Arrow Test</h3>
<script type="text/javascript">
var data = arrowInput("hoge");
console.log(data);
</script>
</body>
</html>
让我们试着执行一下吧。

哦,看起来挺不错的。
总结
我使用Apache Arrow在Ruby中创建的数据可以通过Javascript(浏览器端)获取。
虽然我不确定这是否是正确的用法,但数据交互确实变得更加容易了。我打算再稍微等一下看看。
不过,关于删除不再需要的数据的时机(垃圾收集),这个手动的方式怎么样呢?
追加修改
我修改了上述的Ruby程序中的read方法。
此外,我还创建了一个方法来将其转换为Arrow格式的数据,因此我会添加进去。
我已经修改了代码,使其能够通过哈希返回读取的数据。
class RedArrowTest
def read(streamname)
result = Hash.new
input = Arrow::MemoryMappedInputStream
input.open(streamname) do |inp|
reader = Arrow::RecordBatchStreamReader.new(inp)
fields = reader.schema.fields
reader.each do |r|
fields.each do |fld|
result[fld.name] = Array.new if result[fld.name].nil?
result[fld.name] += r.send(fld.name).collect{|c| c}
end
end
end
return result
end
end
执行结果如下。
$ bundle exec ruby test.rb
{"name"=>["taro", "jiro", "hanako", "tsukiko", "saburo"], "age"=>[26, 21, 24, 17, 10]}
下一步是创建一个从哈希数据生成Arrow格式数据的方法。
class RedArrowTest
# data -> { fieldname1 => Array, fieldname2 => Array, ... }
def create_arrow_data(data)
fldnames = data.keys
fldtypes = Array.new
fldnames.each do |fname|
v = data[fname][0]
case v.class.to_s
when "Integer"
ma = data[fname].max
mi = data[fname].min
ftype = :int64
if (mi >= 0 && ma <= 0xffffffff) ||
(mi >= (0x8000000 * -1) && ma <= 0x7fffffff)
ftype = :int32
end
if (mi >= 0 && ma <= 0xffff) || (mi >= (0x8000 * -1) && ma <= 0x7fff)
ftype = :int16
end
if (mi >= 0 && ma <= 0xff) || (mi >= (0x80 * -1) && ma <= 0x7f)
ftype = :int8
end
when "Date"
ftype = :date32
when "Time", "DateTime"
ftype = :timestamp
when "Float"
ftype = :float
when "TrueClass", "FalseClass"
ftype = :boolean
else
ftype = :string
end
fldtypes.push(ftype)
end
# create schema & columns
fields = Array.new
columns = Array.new
fldnames.each_with_index do |fname, i|
if fldtypes[i] == :timestamp
fields.push(Arrow::Field.new(fname.to_s,
Arrow::TimestampDataType.new(:second)))
obj = eval("Arrow::#{fldtypes[i].to_s.to_camel}Array.new(:second, data[fname])")
else
fields.push(Arrow::Field.new(fname.to_s, fldtypes[i]))
obj = eval("Arrow::#{fldtypes[i].to_s.to_camel}Array.new(data[fname])")
end
columns.push(obj)
end
schema = Arrow::Schema.new(fields)
return schema, columns
end
end
以下是执行结果。
# 以下のように修正してね。
ra = RedArrowTest.new
data = { :string => ["taro", "jiro", "saburo"],
:integer => [24, 21, 100024],
:time => [Time.now, Time.now, Time.now],
:date => [Date.new(2018, 11, 6), Date.new(2018, 11, 7), Date.new],
:float => [12.0, 23.2, 100024.23],
:boolean => [true, false, false]
}
schema, columns = ra.create_arrow_data(data)
ra.write("tara", schema, columns)
p ra.read("tara")
$ bundle exec ruby test.rb
{"string"=>["taro", "jiro", "saburo"], "integer"=>[24, 21, 100024], "time"=>[2018-11-07 09:34:41 +0900, 2018-11-07 09:34:41 +0900, 2018-11-07 09:34:41 +0900], "date"=>[#<Date: 2018-11-06 ((2458429j,0s,0n),+0s,2299161j)>, #<Date: 2018-11-07 ((2458430j,0s,0n),+0s,2299161j)>, #<Date: -4712-01-01 ((0j,0s,0n),+0s,2299161j)>], "float"=>[12.0, 23.200000762939453, 100024.2265625], "boolean"=>[true, false, false]}
听起来不错呢。先试试在Javascript(浏览器端)确认一下吧。
<h3>Red Arrow Test</h3>
<script type="text/javascript">
var data = arrowInput("tara"); ← tara に変更
console.log(data);
</script>

暂时就这样吧。