1

我正在州一级进行 D3 映射。这是我在数据处理中遇到的一个问题。比如地图数据是这样的,(dat1.ndjson)

{state: a, code: aa}
{state: b, code: bb}
{state: c, code: cc}

但通常我们掌握的信息并不完整,比如南极洲通常没有信息,但我们在测绘时仍然需要绘制它的轮廓。信息数据就像,(dat2.ndjson)

{state: a, code: aa, count: 1}
{state: b, code: bb, count: 2}

因此,当我尝试对这两个数据进行左连接时,它将返回 (dat3.ndjson)

[{state: a, code: aa},{state: a, code: aa, count: 1}]
[{state: b, code: bb},{state: b, code: bb, count: 2}]
[{state: c, code: cc},null]

这是由

ndjson-join --left 'd.code' dat1.ndjson dat2.ndjson < merge.ndjson

目的是把这个'count'信息连接到map数据上,所以通常我会先在dat1.ndjson中给所有的item赋一个count=0,像这样,(dat11.ndjson)

{state: a, code: aa, count: 0}
{state: b, code: bb, count: 0}
{state: c, code: cc, count: 0}

然后像我之前展示的那样使用这个左连接方法来得到这样的东西,(dat33.ndjson)

[{state: a, code: aa, count: 0},{state: a, code: aa, count: 1}]
[{state: b, code: bb, count: 0},{state: b, code: bb, count: 2}]
[{state: c, code: cc, count: 0},null]

但问题来了。如果我使用以下命令将所有值相加,则会由于第三行中的 null 而返回错误。

ndjson-map '{state: d[0].state, code: d[0].code, count: d[0].count + 
d[1].count}' < dat33.ndjson > merge.ndjson

现在我必须在 R 中进行此数据处理,这需要花费大量时间,因为我需要在 .ndjson 和 .csv 之间进行转换。所以我正在寻找一种更好的方法来做到这一点。我认为可能有一些方法可以使用“ndjson-cli”、“jq”或“awk”和“sed”等。

有人有想法吗?谢谢!:)

E.

4

1 回答 1

0

这是一个包含几个部分的解决方案:

  1. 将您的输入转换为有效的 JSON。
  2. 用于执行连接的库函数。
  3. 假设您的 jq 版本足够新,则运行 jq 以产生所需的输出。
  4. 如果您只能访问 jq 1.5,该怎么办

为了说明一旦解决了绒毛问题,一切都是多么简单,这里是“主要” jq 程序:

join(.state) | .count //= 0

实际上,这表示:使用 .state 作为连接键执行连接,然后确保设置了 .count。

上述单行代码的输出将是 NDJSON:

{"state":"a","code":"aa","count":1}
{"state":"b","code":"bb","count":2}
{"state":"c","code":"cc","count":0}

第 1 部分:dat1.json 和 dat2.json

我将假设您可以从输入中生成有效的 JSON。对于示例数据,我使用了sed

for i in 1 2 ; do
  sed -e 's/state/"state"/' -e 's/code/"code"/' -e 's/count/"count"/' \
      -e 's/ \([a-z]*\)\([,}]\)/ "\1"\2/g' dat$i.ndjson > dat$i.json
done

无论如何,以下假设您有两个文件,dat1.json 和 dat2.json,其中包含有效的 JSON 流。

第2部分:join

这是一个用于生成连接的小型过滤器库:第一个用于流,其他用于数组。这些定义假设您的 jq 具有INDEX/2. 如果不是这样,请参阅第 4 部分。

def joins(s1; s2; filter1; filter2):
  # combine two dictionaries using `add`
    def merge: . as $in
    | reduce (add|keys_unsorted[]) as $k ({}; .[$k] = ([$in[] | .[$k]] | add));
  [INDEX(s1; filter1 | select(. != null)), INDEX(s2; filter2 | select(. != null))]
  | merge[] ;

def join(filter1; filter2):
  joins(.[0][]; .[1][]; filter1; filter2);

def join(f): join(f; f);

第 3 部分。解决方案

首先,让我们保持简单。如果您将上述定义放在一个文件中joinjoins例如 d3.jq,然后是序言中给出的单行程序,那么假设您的 jq 具有以下调用,那么下面的调用就可以解决问题INDEX

jq -c -s -f d3.jq <(jq -s . dat1.json) <(jq -s . dat2.json)

这假设您使用的是支持进程替换的 shell。如果没有,那么您可以先运行“。” 单独程序,例如,如果您有sponge

 for i in 1 2 ; do jq -s . dat$i.json | sponge dat$i.json ; done

使用include

如果您的 jq 支持,并且如果您在私有标准库(例如 ~/.jq/jq/jq.jq )中include有上述定义,那么您的主 jq 程序将成为两行:join

include "jq";
join(.state) | .count //= 0'

这意味着您可以省去 d3.jq 并使用调用:

jq -c -s 'include "jq"; join(.state)|.count //= 0' \
   <(jq -s . dat1.json) <(jq -s . dat2.json)

第 4 部分:INDEX

这是INDEX最新版本的 jq 提供的副本。您可以将这些定义添加到 d3.jq(在程序的“主要”部分之前),或者添加到您的库文件中,等等:

def INDEX(stream; idx_expr):
  reduce stream as $row ({};
    .[$row|idx_expr|
      if type != "string" then tojson
      else .
      end] |= $row);

def INDEX(idx_expr): INDEX(.[]; idx_expr);
于 2017-12-09T12:15:57.360 回答