При левом соединении для отображения D3 возвращается ноль, что препятствует следующему шагу командной строки обработки данных (ndjson-join или jq)

Я делаю 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' с данными карты, поэтому обычно я сначала назначаю всем элементам count = 0 в dat1.ndjson, например, (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]

Но тут возникает проблема. Если я использую следующую команду, чтобы сложить все значения вместе, она вернет ошибку из-за этого нуля в третьей строке.

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.

1 ответ

Решение

Вот решение, которое состоит из нескольких частей:

  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. Решение

Во-первых, давайте будем проще. Если вы поместите приведенные выше определения для join а также joins в файле, скажем, d3.jq, за которым следует однострочная программа, указанная в преамбуле, тогда следующий вызов поможет, если ваш jq имеет INDEX:

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

Это предполагает, что вы используете оболочку, которая поддерживает подстановку процессов. Если нет, то вы можете сначала запустить "." программы отдельно, например, если у вас есть sponge:

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

С помощью include

Если ваш JQ поддерживает includeи если у вас есть вышеуказанные определения join в частной стандартной библиотеке, такой как ~/.jq/jq/jq.jq, ваша основная jq-программа становится двухслойной:

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);
Другие вопросы по тегам