Использование вложенных циклов в bash для обработки огромных наборов данных - PullRequest
0 голосов
/ 30 октября 2018

В настоящее время я работаю с большими наборами данных (обычно по 10 ГБ для каждого), которые не позволяют мне использовать R (RStudio) и работать с фреймами данных, как я привык.

Чтобы справиться с ограниченным объемом памяти (и мощностью процессора), я пытался обработать эти файлы с помощью Julia и Bash (Shell Script).

У меня следующий вопрос: я сцепил свои файлы (у меня более или менее 1 миллиона отдельных файлов, слитых в один большой файл), и я хотел бы обработать эти большие файлы следующим образом: допустим, у меня есть что-то как:

id,latitude,longitude,value
18,1,2,100
18,1,2,200
23,3,5,132
23,3,5,144
23,3,5,150

Я хотел бы обработать мой файл, сказав, что для id = 18, вычислите max (200), min (100) или некоторые другие свойства, затем перейдите к следующему id и сделайте то же самое. Я предполагаю, что какой-то вложенный цикл в bash сработает, но у меня возникают проблемы, когда я делаю это элегантно, ответы, найденные в интернете, пока не помогают. Я не могу обработать его в Юлии, потому что он слишком большой / тяжелый, поэтому я ищу ответы в основном на bash.

Однако я хотел сделать это, потому что думал, что будет быстрее обработать огромный файл, чем открывать файл, вычислять, закрывать файл и снова и снова переходить к следующему. Я совсем не уверен, хотя!

Наконец, какой из них лучше использовать? Юля или Баш? Или что-то еще?

Спасибо!

Ответы [ 4 ]

0 голосов
/ 30 октября 2018

Bash определенно не лучший вариант. (Фортран, детка!)

В любом случае, на любой язык вы можете перевести следующее.

#!/bin/bash

function postprocess(){
     # Do whatever statistics you want on the arrays.
     echo "id: $last_id"
     echo "lats: ${lat[@]}"
     echo "lons: ${lon[@]}"
     echo "vals: ${val[@]}"
}

# Set dummy start variable
last_id="not a valid id"
count=0

while read line; do
  id=$( echo $line | cut -d, -f1 )

  # Ignore first line
  [ "$id" == "id" ] && continue

  # If this is a new id, post-process the old one
  if [ $id -ne $last_id -a $count -ne 0 ] 2> /dev/null; then
     # Do post processing of data
     postprocess

     # Reset counter
     count=0

     # Reset value arrays
     unset lat
     unset lon
     unset val
  fi

  # Increment counter
  (( count++ ))

  # Set last_id
  last_id=$id

  # Get values into arrays
  lat+=($( echo $line | cut -d, -f2 ))
  lon+=($( echo $line | cut -d, -f3 ))
  val+=($( echo $line | cut -d, -f4 ))

done < test.txt

[ $count -gt 0 ] && postprocess
0 голосов
/ 30 октября 2018

Для такого рода проблем я бы с осторожностью использовал bash, потому что он не подходит для построчной обработки. И awk слишком линейно ориентирован для такой работы, что усложняет код.

Что-то подобное в perl может сделать эту работу, с циклом циклов, объединяющих строки по их полю id.

IT070137 ~/tmp $ cat foo.pl
#!/usr/bin/perl -w

use strict;

my ($id, $latitude, $longitude, $value) = read_data();
while (defined($id)) {
    my $group_id = $id;
    my $min = $value;
    my $max = $value;
    ($id, $latitude, $longitude, $value) = read_data();

    while (defined($id) && $id eq $group_id) {
        if ($value < $min) {
            $min = $value;
        }
        if ($value > $max) {
            $max = $value;
        }
        ($id, $latitude, $longitude, $value) = read_data();
    }

    print $group_id, " ", $min, " ", $max, "\n";
}

sub read_data {
    my $line = <>;
    if (!defined($line)) {
        return (undef, undef, undef, undef);
    }
    chomp($line);
    my ($id, $latitude, $longitude, $value) = split(/,/, $line);
    return ($id, $latitude, $longitude, $value);
}
IT070137 ~/tmp $ cat foo.txt
id,latitude,longitude,value
18,1,2,100
18,1,2,200
23,3,5,132
23,3,5,144
23,3,5,150
IT070137 ~/tmp $ perl -w foo.pl foo.txt
id value value
18 100 200
23 132 150

Или, если вы предпочитаете Python:

#!/usr/bin/python -tt

from __future__ import print_function

import fileinput


def main():
    data = fileinput.input()

    (id, lattitude, longitude, value) = read(data)
    while id:
        group_id = id
        min = value
        (id, lattitude, longitude, value) = read(data)

        while id and group_id == id:
            if value < min:
                min = value
            (id, lattitude, longitude, value) = read(data)

        print(group_id, min)


def read(data):
    line = data.readline()

    if line == '':
        return (None, None, None, None)

    line = line.rstrip()
    (id, lattitude, longitude, value) = line.split(',')
    return (id, lattitude, longitude, value)


main()
0 голосов
/ 30 октября 2018

Юлия или Баш?

Если вы говорите об использовании простого bash, а не о некоторых командах, которые могли бы быть выполнены в любой другой оболочке, тогда ответ, очевидно, - Юлия. Обычный удар на несколько медленнее, чем у Юлии.

Однако я бы порекомендовал использовать существующий инструмент вместо написания собственного.

GNU datamash может быть тем, что вам нужно. Вы можете вызвать это из bash или любой другой оболочки.

для id = 18, вычислите max (200), min (100) [...], затем перейдите к следующему id и сделайте то же самое

С datamash вы можете использовать следующую команду bash

< input.csv datamash -Ht, -g 1 min 4 max 4

Что будет печатать

GroupBy(id),min(value),max(value)
18,100,200
23,132,150
0 голосов
/ 30 октября 2018

Циклы в bash медленные, я думаю, что Джулия намного лучше подходит в этом случае. Вот что я бы сделал:

  1. (в идеале) конвертировать ваши данные в двоичный формат, например NetCDF или HDF5.
  2. загрузка фрагмента данных (например, 100 000 строк, не всех, если только все данные не хранятся в ОЗУ) и выполнение мин / макс для идентификатора, как вы предлагаете
  3. перейти к следующему чанку и обновить мин / макс для каждого идентификатора

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

По моему мнению, объем памяти julia (по сравнению с bash), вероятно, довольно мал, учитывая размер проблемы.

Обязательно прочитайте советы по производительности в Julia и, в частности, поместите «горячие петли» в функции, а не в глобальную область видимости. https://docs.julialang.org/en/v1/manual/performance-tips/index.html

В качестве альтернативы, такие операции также могут выполняться с конкретными запросами в базе данных SQL.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...