2016-04-29 3 views
0

Надеюсь, вы сможете помочь. Я совершенно новичок в g) awk, и я боролся с ним в течение последних двух недель.Транспонирование данных на основе уникального ID - awk

Мой первоначальный файл выглядит следующим образом: есть столбец с уникальным идентификатором и другой с уникальными именами. Последующие столбцы представляют собой различные курсы, и каждое поле содержит (если не пусто) знак для каждого курса и для каждого ученика. Таким образом, каждый студент имеет только один знак для каждого курса:

Id Name  Course1 Course2 Course3 Course4 Course5 
1 John   55 
2 George           63 
4 Alex       64 
1 John         74 
3 Emma   63 
2 George    64 
4 Alex         60 
2 George   29     
3 Emma           69 
1 John     67 
3 Emma     80 
4 Alex   57 
2 George         91 
1 John       81 
1 John           34 
3 Emma       75 
2 George      89 
4 Alex           49 
3 Emma         78 
4 Alex     69 
5 TERRY     67 
6 HELEN       39 

Это то, что я хочу достичь - транспонирование данных, то есть знаки, на основе уникального идентификатора и поместить знаки ниже каждого соответствующего курса, как показано ниже:

Id Name  Course1 Course2 Course3 Course4 Course5 
1 John   55  69  64  60  49 
2 George  29  64  89  91  63 
3 Emma   63  80  75  78  69 
4 Alex   57  69  64  60  49 
5 TERRY     67 
6 HELLEN       39 

Это то, что мне удалось получить до сих пор:

Id Name  Course1 Course2 Course3 Course4 Course5 
1 John   55    
2 George  29    
3 Emma   63    
4 Alex   57  
5 TERRY 
6 HELLEN  
1 John     69    
2 George    64    
3 Emma     80    
4 Alex     69    
5 TERRY     67 
6 HELLEN 
1 John       64 
2 George       89 
3 Emma       75 
4 Alex       64 
5 TERRY 
6 HELLEN       39 
             ...and so on 

Это действительно немного сложно для меня, чтобы достичь на основе того, что я уже знаю на AWK (обратите внимание, я не заинтересован в SED/Perl и т.д. основанные на решениях). Если вам нужна помощь (желательно НЕ один лайнер), я могу попросить вас немного описать, поскольку меня интересует решение так же, как и в самом методе.

Любая помощь будет очень оценена.

EDIT Вот код, который я написал, чтобы достигнуть последней стадии (и где я застрял)

#!/bin/bash 

files3="*.csv" 
for j in $files3 
do 
    #echo "processing $j..." 
    fi13=$(awk -F" " '(NR==1){field13=$13;}{print field13}' ./work1/test1YA.csv) 
    fi14=$(awk -F" " '(NR==1){field14=$14;}{print field14}' ./work1/test1YA.csv) 
    fi15=$(awk -F" " '(NR==1){field15=$15;}{print field15}' ./work1/test1YA.csv) 
    fi16=$(awk -F" " '(NR==1){field16=$16;}{print field16}' ./work1/test1YA.csv) 

# awk -F" " 'BEGIN{OFS=" ";RS="\n"}{print $1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12}' "$j" >> ./work1/test2YA.csv 
    awk -F" " -v f13="$fi13" -v f14="$fi14" -v f15="$fi15" -v f16="$fi16" '{if($13==f13){$13=$6;$14=$15=$16=""}if($13==f14){$14=$6;$13=$15=$16=""}if($13==f15){$15=$6;$13=$14=$16=""}if($13==f16){$16=$6;$13=$14=$15=""}{print $1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13,$14,$15,$16}}' "$j" >> ./work1/test2YA.csv 

done; 

awk -F" " 'BEGIN{print "ID","Title","FirstName","MiddleName","LastName","FinalMarks","Status","Username","Campus","Code","Programme","Year","course1","course2","course3","course4"}{print}' ./work1/test2YA.csv >> ./work1/test3YA.csv 
+0

Можете ли вы вставить код, создающий ваше решение на полпути? – choroba

ответ

1

Вот решение для Gnu AWK:

course.awk

BEGIN { # setup field width for constant field splitting 
     FIELDWIDTHS = "2 2 12 7 1 7 1 7 1 7 1 7" 
     # setup sort order (by id) 
     PROCINFO["sorted_in"] = "@ind_num_asc" 
     } 

NR == 1 { # print header 
      print 
      next 
     } 

     { 
     # add ids to names 
     names[ $1 ] = $3 

     # store under id and course number the mark if it is present 
     for(c = 1; c <= 5; c++) { 
      field = 2+ (c*2) 
      if($(field) !~ /^ *$/) { 
      marks[ $1, c ] = $(field) 
      } 
     } 
     } 

END { 
     # output 
     for(id in names) { 
      printf("%-4s%-12s%7s %7s %7s %7s %7s\n",id, names[ id ], marks[ id, 1], marks[ id, 2], marks[ id, 3], marks[ id, 4], marks[ id, 5]) 
     } 
     } 

Используйте его следующим образом: awk -f course.awk your_file.

Тот факт, что вход не табуляцией, но имеет фиксированную ширину столбцов сделайте немного unelegant:

  • использование FIELDWIDTHS и %Ns где N- получен из FIELDWIDTHS
  • FIELDWIDTHS принимают учитывайте пустой столбец между идентификатором и именем, курсом1 и курсом2, ...
  • проверить наличие метки: if($(field) !~ /^ *$/) проверяет, не содержит ли поле целиком пробелы.
+0

Отлично! Решение. – Firefly

+0

Спасибо всем за быстрый и подробный ответ. Я принимаю ответ @Lars Fiscer, потому что он первым ответил с комментариями, и я узнал о FIELDWIDTHS и PROCINFO (кажется, я не могу «проголосовать» за принятый ответ, поскольку моя репутация слишком низкая или «подталкивает» другие ответы, поскольку они были очень полезны)! Мне пришлось немного изменить ответ для моих нужд (мой исходный файл табулирован, и мне не нужны поля, procinfo и printf). Снова много спасибо всем. Мило с твоей стороны. – Yiannis

+0

ОК, я нашел, как принять ответ ... – Yiannis

0

Это может быть приближение в AWK:

NR==1{ 
    for(x=1;x<=NF;x++) 
    { 
     head=head $x"\t"; 
    } 
    print head 
} 
NR>1{ 
    for(i=3;i<=NF;i++) 
    { 
     students[$1"\t"$2]=students[$1"\t"$2] "\t"$i; 
    } 
} 
END{ 
    for (stu in students) 
    { 
     print stu,students[stu]; 
    } 
} 

Id  Name Course1 Course2 Course3 Course4 Course5 
5  TERRY 67 
4  Alex 64  60  57  49  69 
1  John 55  74  67  81  34 
6  HELEN 39 
3  Emma 63  69  80  75  78 
2  George 63  64  29  91  89 
+0

Логика кажется неправильной на основе вывода. Проверьте ТЕРРИ и ХЕЛЕН. – karakfa

+0

@karakfa Спасибо, я знаю, что оценка Хелен должна быть в колонке «курс3» и в баллах Терри в колонке «курс2». Вот почему я сказал, что это приближение. – Firefly

0

же идеи, возможно, проще

$ awk 'BEGIN{ FIELDWIDTHS="16 8 8 8 8 8"} 
     NR==1{print;next} 
     NR>1{keys[$1]; 
      for(i=2;i<=6;i++) 
       {gsub(" ","",$i); 
       if($i) a[$1,i]=$i}} 
     END{for(k in keys) 
       {printf "%16s",k; 
       for(i=2;i<=6;i++) printf "%-8s",a[k,i]; 
       print ""}}' file 


Id Name  Course1 Course2 Course3 Course4 Course5 
3 Emma  63  80  75  78  69 
4 Alex  57  69  64  60  49 
6 HELEN      39 
5 TERRY    67 
1 John  55  67  81  74  34 
2 George  29  64  89  91  63 

вы можете сортировать вывод, а также по трубопроводу к sort -n

... | sort -n 

Id Name  Course1 Course2 Course3 Course4 Course5 
1 John  55  67  81  74  34 
2 George  29  64  89  91  63 
3 Emma  63  80  75  78  69 
4 Alex  57  69  64  60  49 
5 TERRY    67 
6 HELEN      39 
0

С GNU awk для FIELDWIDTHS, 2D-массивов и sorted_in:

$ cat tst.awk 
NR==1 { 
    print 
    split($0,f,/\S+\s*/,s) 
    for (i=1;i in s;i++) { 
     w[i] = length(s[i]) 
     FIELDWIDTHS = FIELDWIDTHS (i>1?" ":"") w[i] 
    } 
    next 
} 
{ 
    sub(/\s*$/," ") 
    for (i=1;i<=NF;i++) { 
     if ($i ~ /\S/) { 
      val[$1][i] = $i 
     } 
    } 
} 
END { 
    PROCINFO["sorted_in"] = "@ind_num_asc" 
    for (id in val) { 
     for (i=1;i<=NF;i++) { 
      printf "%*s", w[i], val[id][i] 
     } 
     print "" 
    } 
} 

.

$ awk -f tst.awk file 
Id Name  Course1 Course2 Course3 Course4 Course5 
1 John   55  67  81  74  34 
2 George   29  64  89  91  63 
3 Emma   63  80  75  78  69 
4 Alex   57  69  64  60  49 
5 TERRY     67 
6 HELEN       39 
0

Вот мой пример. Это работает в обычном awk (не использует FIELDWIDTHS), и он автоматически настраивается на разные количества полей (т. Е. Добавляет столбец Course7, и вы должны быть в порядке). Кроме того, вы можете указать его на несколько файлов, и он должен обрабатывать каждый отдельно.

#!/usr/bin/awk -f 

# Initialize variables on the first record of each input file 
# (and also print the header) 
# 
FNR <= 1 { 
    print 
    delete name 
    delete score 
    next 
} 

# Process each line. 
# 
{ 
    id = substr($0, 0, 16) # 
    name[id]     # Store the unique identifier in an array 
    pos = 0     # 

    # Step through the score fields until we hit the end of the line, 
    # storing scores in another array. 
    do { 
    score[id, pos] += substr($0,17+pos*8,8) +0 
    printf("id='%s' pos=%s value=%s total=%s\n", id, pos, substr($0,17+pos*8,8)+0, score[id, pos]); 
    } while (17+(++pos)*8 < length()) 
} 

# Keep track of our maximum number of fields 
pos>max { max=pos } 

# Finally, generate our (randomly sorted) output. 
END { 
    for (id in name) {  # Step through the records... 
    printf("%-12s", id); 
    for (i=0; i<max; i++) { # Step through the fields... 
     if (score[id, i]==0) score[id, i]="" 
     printf("%-8s", score[id, i]); 
    } 
    printf("\n") 
    } 
} 

Это немного длинный, но я думаю, что легче понять, что он делает.

Смежные вопросы