Можем ли мы использовать AWK и gsub() для обработки данных с несколькими двоеточиями: Как?

Вот пример данных:

Col_01:14 .... Col_20:25    Col_21:23432    Col_22:639142
Col_01:8  .... Col_20:25    Col_22:25134    Col_23:243344
Col_01:17 .... Col_21:75    Col_23:79876    Col_25:634534    Col_22:5    Col_24:73453
Col_01:19 .... Col_20:25    Col_21:32425    Col_23:989423
Col_01:12 .... Col_20:25    Col_21:23424    Col_22:342421    Col_23:7    Col_24:13424    Col_25:67
Col_01:3  .... Col_20:95    Col_21:32121    Col_25:111231

Как видите, некоторые из этих столбцов расположены не в том порядке...

Теперь я думаю, что правильный способ импортировать этот файл в фрейм данных - это предварительно обработать данные, чтобы вы могли вывести фрейм данных со значениями NaN, например.

Col_01 .... Col_20    Col_21    Col22    Col23    Col24    Col25
8      .... 25        NaN       25134    243344   NaN      NaN
17     .... NaN       75        2        79876    73453    634534
19     .... 25        32425     NaN      989423   NaN      NaN
12     .... 25        23424     342421   7        13424    67
3      .... 95        32121     NaN      NaN      NaN      111231

Решение было показано @JamesBrown здесь: Как предварительно обработать и загрузить файл tsv с большими данными в кадр данных python?

Используя указанный awk-скрипт:

BEGIN {
    PROCINFO["sorted_in"]="@ind_str_asc" # traversal order for for(i in a)                  
}
NR==1 {       # the header cols is in the beginning of data file
              # FORGET THIS: header cols from another file replace NR==1 with NR==FNR and see * below
    split($0,a," ")                  # mkheader a[1]=first_col ...
    for(i in a) {                    # replace with a[first_col]="" ...
        a[a[i]]
        printf "%6s%s", a[i], OFS    # output the header
        delete a[i]                  # remove a[1], a[2], ...
    }
    # next                           # FORGET THIS * next here if cols from another file UNTESTED
}
{
    gsub(/: /,"=")                   # replace key-value separator ": " with "="
    split($0,b,FS)                   # split record from ","
    for(i in b) {
        split(b[i],c,"=")            # split key=value to c[1]=key, c[2]=value
        b[c[1]]=c[2]                 # b[key]=value
    }
    for(i in a)                      # go thru headers in a[] and printf from b[]
        printf "%6s%s", (i in b?b[i]:"NaN"), OFS; print ""
}

И поместите заголовки в текстовый файл cols.txt

Col_01 Col_20 Col_21 Col_22 Col_23 Col_25

Теперь мой вопрос: как нам использовать awk, если у нас есть данные не column: value, а column: value1: value2: value3?

Мы хотели бы, чтобы запись в базе данных была value1: value2: value3

Вот новые данные:

Col_01:14:a:47 .... Col_20:25:i:z    Col_21:23432:6:b    Col_22:639142:4:x
Col_01:8:z .... Col_20:25:i:4    Col_22:25134:u:0    Col_23:243344:5:6
Col_01:17:7:z .... Col_21:75:u:q    Col_23:79876:u:0    Col_25:634534:8:1   

Мы по-прежнему заранее предоставляем столбцы с cols.txt

Как мы можем создать аналогичную структуру базы данных? Можно ли использовать gsub() для ограничения первого значения перед :, которое совпадает с заголовком?

РЕДАКТИРОВАТЬ: это не должно быть основано на awk. Любой язык подойдет естественно


person ShanZhengYang    schedule 05.10.2016    source источник
comment
@JamesBrown Я упомянул ваш опыт выше. Еще раз спасибо!   -  person ShanZhengYang    schedule 05.10.2016
comment
может быть проще, если вы сможете восстановить исходные данные с другим символом для расширенных значений. Может @ ? Удачи.   -  person shellter    schedule 05.10.2016
comment
@shellter Я не понимаю твоей логики --- что ты имеешь в виду? Разобрать каждое из значений по отдельности?   -  person ShanZhengYang    schedule 05.10.2016
comment
Можно ли полагаться на имена столбцов, начинающиеся с Col_?   -  person glenn jackman    schedule 05.10.2016
comment
Программа создает ваш файл данных. Измените его так, чтобы вместо вывода (например) Col_23: 79876:u:0 выводилось Col_23:78976@u@0. Тогда вы можете легко использовать split(fld,fldArr,":");split(FldArr[2],valArr,"@"). Удачи.   -  person shellter    schedule 05.10.2016
comment
@glennjackman Для этого примера да. На самом деле имена столбцов являются уникальными именами. Однако мы знаем их личность и знаем, что они будут в каждой строке.   -  person ShanZhengYang    schedule 05.10.2016
comment
@shelter Что, если бы расширенные значения имели определенный шаблон? То есть промежуточное значение всегда было либо A, либо Z, например. было либо Col_1:A:##, либо Col_3:Z:##   -  person ShanZhengYang    schedule 05.10.2016
comment
Следующий вопрос: значение col1 во 2-й строке. Из новых данных нет формата valeu1:value2:valeu3. Это опечатка или реалистичные данные?   -  person glenn jackman    schedule 05.10.2016
comment
@glennjackman Вы имеете в виду Col_01: 8: z? Это опечатка. Между двоеточиями нет пробелов. Реалистичные данные больше похожи на то, что все поля Col1 будут иметь формат Col1:string:number, где строка начинается с H. Col22 всегда будет иметь формат Col22:##:A:##, где это всегда буква A. Остальные могут быть field_value:number, парой ключ-значение.   -  person ShanZhengYang    schedule 05.10.2016


Ответы (4)


Вот еще вариант...

$ awk -v OFS='\t' '{for(i=1;i<NF;i+=2)                  # iterate over name: value pairs
                     {c=$i;                             # copy name in c to modify
                      sub(/:/,"",c);                    # remove colon
                      a[NR,c]=$(i+1);                   # collect data by row number, name
                      cols[c]}}                         # save name
                END{n=asorti(cols,icols);               # sort names
                    for(j=1;j<=n;j++) printf "%s", icols[j] OFS;   # print header 
                    print ""; 
                    for(i=1;i<=NR;i++)                  # print data
                      {for(j=1;j<=n;j++) 
                         {v=a[i,icols[j]];             
                          printf "%s", (v?v:"NaN") OFS} # replace missing data with NaN
                       print ""}}' file | column -t     # pipe to column for pretty print

Col_01   Col_20  Col_21     Col_22      Col_23      Col_25
14:a:47  25:i:z  23432:6:b  639142:4:x  NaN         NaN
8:z      25:i:4  NaN        25134:u:0   243344:5:6  NaN
17:7:z   NaN     75:u:q     NaN         79876:u:0   634534:8:1
person karakfa    schedule 05.10.2016
comment
Спасибо тебе за это! Рискуя неправильно понять: -v определил переменную OFS как разделитель табуляции, а переменная file и column - это путь к файлу и файлу columns.txt соответственно? Где вы определяете «столбцы»? - person ShanZhengYang; 05.10.2016
comment
-v предназначен для определения awk переменных, это устанавливает OFS, как вы поняли. file — это ваш входной файл, а column -t — это красиво оформленный вывод, который вы можете игнорировать. cols определяется с помощью cols[c], когда добавляется первый элемент c, аналогичный массиву данных a. - person karakfa; 05.10.2016
comment
Таким образом, для входного файла с разным количеством полей в строке (как в приведенном выше примере, например, Col_25 не появляется до третьей строки), это будет отслеживать все уникальные значения столбцов, преодолевая функцию сниффера для большего ввода файлов. функции. - person ShanZhengYang; 05.10.2016
comment
да, мы собираем имена в массив cols, чтобы обращаться к ним позже. - person karakfa; 05.10.2016
comment
Это отлично работает, за исключением неаккуратной ошибки с моей стороны, обсуждаемой ниже. Между полями нет пробелов, например. Col_01:14:a:47 правильно, а не Col_01: 14:a:47, поэтому есть некоторые столбцы, которые не работают во время моего теста. Это моя вина, как показывают приведенные выше данные. Какие будут поправки к вышесказанному? - person ShanZhengYang; 05.10.2016
comment
Я стесняюсь спросить об этом. Однако на самом деле я никогда не запускал AWK без BEGIN. Я могу запустить это в командной строке, но как преобразовать это в скрипт? Я следую межстрочному интервалу выше и работаю с awk -v OFS='\t' -f script.awk file, но есть раздражающие ошибки источника строки. (Извините, что беспокою вас этим...) - person ShanZhengYang; 06.10.2016
comment
скопируйте содержимое между одинарными кавычками (исключая кавычки) в файл script.awk. Междустрочный интервал не важен, так как я использовал ; в качестве разделителей выражений. - person karakfa; 06.10.2016
comment
Нашел ошибку --- извините, я волновался, что ошибся. Большое спасибо за помощь - person ShanZhengYang; 06.10.2016

У меня тоже был ответ Каракфы. Если имя столбца не отделено от значения пробелом (например, если у вас есть Col_01:14:a:47), вы можете сделать это (используя GNU awk для расширенной функции match)

  {
      for (i=1; i<=NF; i++) {
          match($i, /^([^:]+):(.*)/, m)
          a[NR,m[1]] = m[2]
          cols[m[1]]
     }
  }

Блок END такой же

person glenn jackman    schedule 05.10.2016
comment
Спасибо тебе за это! Очень n00b вопрос: я бы ожидал for(i=1; i<=NF; i++), но @karakfa использует i+=2. Что стоит за этим? - person ShanZhengYang; 05.10.2016
comment
Его ответ анализирует файл, в котором у вас есть Col_01: 14:a:47 (с пробелом, поэтому в awk он считается отдельным полем). Этот будет анализировать файл, в котором у вас есть Col_01:14:a:47 (без пробела) - person glenn jackman; 05.10.2016
comment
@glennjackson Теперь я понимаю. Да, это было небрежно с моей стороны. Благодарю вас! И вы оба определяете cols через columns.txt? Таким образом, вы должны выполнять приведенный выше скрипт, например awk awk -v OFS='\t' script.awk | cols.txt -t - person ShanZhengYang; 05.10.2016
comment
Вам нужно быть более точным, когда вы показываете нам, каковы ваши данные на самом деле. Как видите, это принципиально важно. - person glenn jackman; 05.10.2016
comment
Не только awk, любой анализ текста независимо от инструмента. - person glenn jackman; 05.10.2016
comment
Можно ли создать решение, которое работает со строками внутри файла, а не с самим файлом? Итак, строки представляют собой текст с разделителями табуляции, который я вывожу кусками/строками. awk -v OFS='\t' -f script.awk file работает (хотя и не масштабируется). awk -v OFS='\t' -f script.awk line, где line равно for line in file:, не работает. В противном случае мой единственный выход на данный момент, похоже, состоит в том, чтобы сохранить строки в текстовый файл, запустить awk, а затем отбросить. - person ShanZhengYang; 06.10.2016
comment
Вы можете использовать awk как фильтр: some_process | awk -v OFS="\t" -f script awk - person glenn jackman; 06.10.2016
comment
Я не уверен, что понимаю --- я думаю, что в качестве входных данных все еще нужен текстовый файл; ввод не может быть строками чтения из текстового файла --- возможно, есть возможность использовать строку, запустить awk, а затем получить вывод --- я понимаю, что это, конечно, неэффективно, но как будут читаться строки? Под фильтром вы подразумеваете непрерывную передачу строк и передачу в awk? Я думаю, что я пытаюсь это - person ShanZhengYang; 06.10.2016

Использование Lisp TXR реализация макросов парадигмы Awk:

(awk (:set ft #/-?\d+/)  ;; ft is "field tokenize" (no counterpart in Awk)
     (:let (tab (hash :equal-based)) (max-col 1) (width 8))
     ((ff (mapcar toint) (tuples 2))  ;; filter fields to int and shore up into pairs
      (set max-col (max max-col [find-max [mapcar first f]]))
      (mapdo (ado set [tab ^(,nr ,@1)] @2) f)) ;; stuff data into table
     (:end (let ((headings (mapcar (opip (format nil "Col~,02a")
                                         `@{@1 width}`)
                                   (range 1 max-col))))
             (put-line `@{headings " "}`))
           (each ((row (range 1 nr)))
             (let ((cols (mapcar (opip (or [tab ^(,row ,@1)] "NaN")
                                       `@{@1 width}`)
                                 (range 1 max-col))))
               (put-line `@{cols " "}`)))))

Меньшие образцы данных:

Col_01: 14  Col_04: 25    Col_06: 23432    Col_07: 639142
Col_02: 8   Col_03: 25    Col_05: 25134    Col_06: 243344
Col_01: 17
Col_06: 19  Col_07: 32425

Бежать:

$ txr reformat.tl data-small
Col01    Col02    Col03    Col04    Col05    Col06    Col07
14       NaN      NaN      25       NaN      23432    639142
NaN      8        25       NaN      25134    243344   NaN
17       NaN      NaN      NaN      NaN      NaN      NaN
NaN      NaN      NaN      NaN      NaN      19       32425

P.S. opip — это макрос, который загружается из op макрос для приложений с частичными функциями; opip неявно распределяет op в свои выражения аргументов, а затем связывает полученные функции вместе в функциональный конвейер: отсюда «op-pipe». В каждом элементе конвейера можно ссылаться на его собственные пронумерованные неявные аргументы: @1, @2, ..., если они отсутствуют, то частично примененная функция неявно получает передаваемый по конвейеру объект в качестве своего крайнего правого аргумента.

Синтаксис ^(,row ,@1) — это обратная цитата TXR Lisp. Обратная кавычка, которую основные диалекты Лиспа используют для обратной кавычки, уже используется для строковых квазикавычек. Это эквивалентно (list row @1): создайте список, состоящий из значения row и неявного, op/do сгенерированного аргумента функции @1. В качестве хеш-ключей используются списки из двух элементов, которые имитируют двумерный массив. Для этого хэш должен быть :equal-based. Списки (1 2) (1 2) не являются eql, если они представляют собой отдельные экземпляры, а не один и тот же объект; они сравниваются равными по функции equal.

person Kaz    schedule 05.10.2016

Просто ради интереса, какой-то непонятный перл

perl -aE'%l=%{{@F}};while(($k,$v)=each%l){$c{$k}=1;$a[$.]{$k}=$v}END{$,="\t";say@c=sort keys%c;for$i(1..$.){say map{$a[$i]{$_}//"NaN"}@c}}' input

(Вики сообщества, чтобы скрыть свой позор...)

Поиграл в гольф несколько символов:

perl -aE'while(@F){$c{$k=shift@F}=1;$data[$.]{$k}=shift@F}END{$,="\t";say@c=sort keys%c;for$i(1..$.){say map{$data[$i]{$_}//"NaN"}@c}}' input
person Community    schedule 05.10.2016
comment
Впечатляющие вещи! Мне это нравится :) - person ShanZhengYang; 06.10.2016