Эффективный способ изменить набор данных от длинного к широкому - PullRequest
0 голосов
/ 18 апреля 2020

У меня есть медицинский набор данных, который выглядит следующим образом:

patient_id disease_id 
1111111111  DISEASE:1
1111111111  DISEASE:2
1111111111  DISEASE:3
1111111111  DISEASE:4
1111111111  DISEASE:5 
1111111111  DISEASE:6
1111111111  DISEASE:6
1111111112  DISEASE:1
1111111112  DISEASE:2
1111111112  DISEASE:4
1111111113  DISEASE:1
1111111113  DISEASE:5

, который мне нужно ввести в модель нейронной сети / случайного леса. Итак, единственное естественное представление данных, которое я подал в модели, было:

    patient_id   DISEASE:1  DISEASE:2  DISEASE:3  DISEASE:4  DISEASE:5  DISEASE:6  ...
    11111111111     1           1           1           1           1        1     ...  
    11111111112     1           1           0           1           0        0     ...    
    11111111113     1           0           0           0           1        0     ...

Но мой набор данных очень большой (~ 50 ГБ, 1,5 ГБ сжатый) и имеет тонны disease_id с, так что преобразование этих данных наиболее эффективным способом в R требует 11,7 ТБ места в сжатых в формате RD (я знаю это, потому что я разделил набор данных на 100 кусков, а изменение формы одного привело к 117 ГБ) тяжелый файл RDs, объединение 100 из них приведет к чему-то большему, чем 11,7 ТБ).

Теперь у меня есть 5 наборов данных, которые мне нужно объединить, поэтому я чувствую себя немного застрявшим. Мне нужно придумать более эффективное представление данных, но я не знаю как, поскольку я имею дело с категориальными переменными, которые потребуют кодирования 1-hot. Может кто-нибудь предложить какие-либо альтернативные способы борьбы с данными, подобными этим.

Ответы [ 2 ]

0 голосов
/ 18 апреля 2020

Учитывая размер ввода, который вы хотите выполнить потоковая обработка , а R не подходит для такой обработки, поэтому здесь мы используем простую программу gawk. gawk доступен в Rtools на Windows и изначально поставляется с большинством систем UNIX / Linux.

При первом проходе программа gawk создает ассоциативный массив disease от заболевания поле, то есть второе поле ввода. Предположительно число заболеваний намного меньше, чем длина файла, так что это, вероятно, вписывается в память.

Затем за второй проход он считывает каждую группу записей, соответствующих пациенту, предполагая, что все записи для пациента являются последовательными Для каждого пациента он выводит одну строку с идентификатором пациента и последовательностью 0 и 1, так что i-й указывает на отсутствие или наличие i-го заболевания.

FNR == 1 { next } # skip header on both passes

# first pass - create disease array
FNR == NR { 
  disease[$2] = 0;
  next;
}

# second pass - create and output flattened records
{ 
  if ($1 != prevkey && FNR > 2) {
    printf("%s ", prevkey);
    for(d in disease) printf("%d ", disease[d]);
    printf("\n");
    for(d in disease) disease[d] = 0;
  }  
  disease[$2] = 1;
  prevkey = $1;
}
END {
  if (FNR == NR) for(d in disease) {
    print d;
  } else {
    printf("%s ", prevkey);
    for(d in disease) printf("%d ", disease[d]);
    printf("\n");
  }
}

Если мы поместим вышеуказанный код gawk в model_mat .awk, тогда мы можем запустить его следующим образом - обратите внимание, что файл должен быть указан дважды - один раз для каждого из двух проходов:

gawk -f model_mat.awk disease.txt disease.txt

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

1111111111 1 1 1 1 1 1
1111111112 1 1 0 1 0 0
1111111113 1 0 0 0 1 0

Если мы запустим его только с одним аргументом blood.txt, тогда он будет выполнять только первый проход, а затем в конце перечислить болезни без дубликатов:

gawk -f model_mat.awk disease.txt

, что дает:

DISEASE:1
DISEASE:2
DISEASE:3
DISEASE:4
DISEASE:5
DISEASE:6

Список болезней

Альтернативой для перечисления болезней является UNIX конвейер, который перечисляет болезни без дубликатов и сортирует их. sed удаляет заголовок, cut занимает третье разделенное пробелами поле (это третье, потому что между двумя полями два пробела) и сортирует его по уникальным элементам.

sed 1d disease.txt | cut -f 3 -d " " | sort -u > diseases-sorted.txt

Сортировка и объединение

Утилита сортировки GNU может сортировать и объединять файлы, размер которых превышает объем памяти, и имеет параллельную опцию, чтобы ускорить ее. Также см. Бесплатную утилиту cmsort (только Windows).

csvfix

Ниже приведены некоторые сценарии, использующие бесплатную утилиту командной строки csvfix . Вам может потребоваться изменить кавычки в зависимости от процессора / оболочки командной строки, который вы используете, и вам нужно будет поместить каждый из них в одну строку или, соответственно, экранировать новую строку (backsla sh для bash, окружность для Windows cmd) , Для наглядности мы показали, что каждый конвейер распределен по отдельным строкам.

Первый конвейер ниже создает список заболеваний в одну колонку в болезнь-list.txt. Первая команда csvfix в нем удаляет заголовок, вторая команда csvfix извлекает второе поле (т.е. удаляет идентификатор пациента), а последняя команда csvfix уменьшает его до уникальных заболеваний.

Второй приведенный ниже конвейер создает файл с один ряд на пациента с идентификатором пациента, за которым следуют заболевания для этого пациента. Первая команда csvfix удаляет заголовок, вторая преобразует его в формат csv, а последняя команда csvfix выравнивает его.

csvfix remove -if "$line == 1" -smq disease.txt | 
  csvfix read_dsv -s " " -cm -f 2 | 
  csvfix uniq -smq > disease-list.txt

csvfix remove -if "$line == 1" -smq disease.txt | 
  csvfix read_dsv -s " " -cm -f 1,2 | 
  csvfix flatten -smq > flat.txt
0 голосов
/ 18 апреля 2020

Вы ставите интересный вопрос. Анализ этого объема данных с помощью R будет настоящим изменением.

Итак, я могу дать вам только общие советы. Во-первых, я думаю, вам нужно отделить оперативную память и дисковое хранилище. Использование Rds не поможет вам оценить эффективность изменения формы, но даст на диске меньше данных, чем csv.

Относительно эффективности преобразования

data.table

Если вы хотите использовать подход в памяти, я не вижу другой возможности, кроме использования data.table::dcast. В этом случае следуйте рекомендации @Ronak Shah:

library(data.table)
setDT(df)
df[, n := 1]
dcast(unique(df), patient_id~ disease_id, value.var = "n", fill = 0)

?data.table::dcast:

В духе data.table, он очень быстрый и эффективно использует память, что делает его хорошо подходит для обработки больших наборов данных в оперативной памяти. Что еще более важно, он способен обрабатывать очень большие данные довольно эффективно с точки зрения использования памяти.

Другие решения

С объемными данными, я думаю, в памяти не самый подходящий подход. Вы можете взглянуть на подходы к базам данных (особенно postgreSQL) или Spark.

Базы данных

У вас есть несколько вариантов использования postgreSQL в R. Одним из них является dbplyr : если вы знаете синтаксис tidyverse, вы найдете знакомые глаголы. Операция pivot немного сложнее для базы данных, чем стандартный R dataframe, но вы можете найти некоторые способы сделать это . У вас не будет проблем с поиском некоторых людей, более опытных в базах данных, чем я, которые могут дать вам очень интересные трюки.

Spark

Spark может быть очень хорошим кандидатом для выполнения изменение, если вы можете распределить свои задачи среди исполнителей в течение нескольких лет. Если вы работаете на персональном компьютере (в автономном режиме), вы все равно можете распараллеливать задачи между вашими ядрами, но не забудьте изменить параметр spark.memory.fraction сеанса, иначе я думаю, что у вас могут возникнуть проблемы out of memory. Я больше привык к pyspark, чем sparkR, но я думаю, логика c будет такой же.

Поскольку Spark 1.6, вы можете поворачивать свои данные ( ex: pyspark do c). Это позволяет преобразование wide в long. Что-то в этом духе (pyspark code)

df.withColumn("n", psf.lit(1)).pivot("patient_id").sum("n")

По поводу размера на диске

Вы используете Rds. У вас есть какой-то формат более сжатый, например fst. Файлы parquet также очень сжаты, возможно, это один из лучших вариантов хранения объемных данных. Вы можете прочитать их с помощью SparkR или с помощью arrow пакета

...