Transponowanie wierszy i kolumn


18

Mam plik z liniami jak poniżej.

title1:A1
title2:A2
title3:A3
title4:A4
title5:A5

title1:B1
title2:B2
title3:B3
title4:B4
title5:B5

title1:C1
title2:C2
title3:C3
title4:C4
title5:C5

title1:D1
title2:D2
title3:D3
title4:D4
title5:D5

Jak mogę to osiągnąć?

title1    title2     title3    title4
A1         A2         A3         A4
B1         B2         B3         B4
C1         C2         C3         C4
D1         D2         D3         D4


proszę, proszę, nie używaj awk, równie dobrze możesz rzucić niestandardowe rozwiązanie z perl lub pythonem lub prawdziwym językiem programowania albo użyć tr / cut z wieloma przejściami, aby uzyskać to, czego chcesz
Rudolf Olah

Odpowiedzi:


14

Spójrz na GNU datamash, którego można używać jak datamash transpose. Przyszła wersja będzie również obsługiwać tabelę krzyżową (tabele przestawne)


9

Poza toczeniem niestandardowego rozwiązania do transponowania wierszy z kolumnami z wiersza poleceń jedynym narzędziem, jakie kiedykolwiek widziałem, które może to zrobić, jest narzędzie o nazwie ironicznie transpose.

Instalacja

Niestety nie ma go w repozytorium, więc musisz go pobrać i skompilować. Jest to dość proste, ponieważ nie ma żadnych dodatkowych bibliotek, od których jest zależny. Można to osiągnąć w następujący sposób:

$ gcc transpose.c -o transpose

Stosowanie

Z łatwością obsługuje proste pliki tekstowe. Na przykład:

$ cat simple.txt 
X column1 column2 column3
row1 0 1 2
row2 3 4 5
row3 6 7 8
row4 9 10 11

Można transponować za pomocą tego polecenia:

$ transpose -t --fsep " " simple.txt 
X row1 row2 row3 row4
column1 0 3 6 9
column2 1 4 7 10
column3 2 5 8 11

To polecenie służy transposedo transpozycji ( -t), a separatorem pól jest spacja ( --fsep " ").

Twój przykład

Ponieważ przykładowe dane są w nieco bardziej złożonym formacie, należy się nimi zająć w 2 etapach. Najpierw musimy przetłumaczyć go na format, z którym transposemożna sobie poradzić.

Uruchomienie tego polecenia spowoduje umieszczenie danych w bardziej przyjaznym dla formatu formacie:

$ sed 's/:/ /; /^$/d' sample.txt \
    | sort | paste - - - - -
title1 A1   title1 B1   title1 C1   title1 D1   title2 A2
title2 B2   title2 C2   title2 D2   title3 A3   title3 B3
title3 C3   title3 D3   title4 A4   title4 B4   title4 C4
title4 D4   title5 A5   title5 B5   title5 C5   title5 D5

Teraz musimy tylko usunąć wtórne wystąpienia tytułu 1, tytułu 2 itd .:

$ sed 's/:/ /; /^$/d' sample.txt \
    | sort | paste - - - - - | sed 's/\ttitle[0-9] / /g'
title1 A1 B1 C1 D1 A2
title2 B2 C2 D2 A3 B3
title3 C3 D3 A4 B4 C4
title4 D4 A5 B5 C5 D5

Jest teraz w formacie, z którym transposemożna sobie poradzić. Następujące polecenie wykona całą transpozycję:

$ sed 's/:/ /; /^$/d' sample.txt \
    | sort | paste - - - - - | sed 's/\ttitle[0-9] / /g' \
    | transpose -t --fsep " "
title1 title2 title3 title4
A1 B2 C3 D4
B1 C2 D3 A5
C1 D2 A4 B5
D1 A3 B4 C5
A2 B3 C4 D5

8

Możesz wtedy użyć awkdo przetworzenia danych pastei columnsformatowania ich.

Tutaj zakładam, że title1jest to tylko przykład w twoim poście i że dane nie zawierają, :z wyjątkiem separatora między nagłówkiem + danymi.

noznacza liczbę kolumn do wydrukowania (powinny pasować do myślników paste).

awk -F":" -v n=4 \
'BEGIN { x=1; c=0;} 
 ++c <= n && x == 1 {print $1; buf = buf $2 "\n";
     if(c == n) {x = 2; printf buf} next;}
 !/./{c=0;next}
 c <=n {printf "%s\n", $2}' datafile | \
 paste - - - - | \
 column -t -s "$(printf "\t")"

Jeśli chcesz uczynić go bardziej elastycznym i łatwym w utrzymaniu, możesz napisać go jako skrypt. Oto przykład użycia otoki bash awki potoku do column. W ten sposób możesz również sprawdzić więcej danych, np. Upewnić się, że nagłówki są poprawne we wszystkich wierszach itp.

Używany zwykle jako:

$ ./trans -f data -c 4
title one  title two  title three  title four
A1         A2         A3           A4
B1         B2         B3           B4
C1         C2         C3           C4
D1         D2         D3           D4

Jeśli nagłówki zawsze są krótsze niż dane, możesz również zapisać szerokości nagłówków, a następnie za printfpomocą %-*si pomiń columnwszystkie razem.

#!/bin/bash

trans()
{
    awk -F":" -v ncol="$1" '
    BEGIN {
        level = 1 # Run-level.
        col   = 1 # Current column.
        short = 0 # If requested to many columns.
    }
    # Save headers and data for row one.
    level == 1 {
        head[col] = $1
        data[col] = $2
        if (++col > ncol) { # We have number of requested columns.
            level = 2
        } else if ($0 == "") { # If request for more columns then available.
            level = 2
            ncol  = col - 2
            short = 1
        } else {
            next
        }
    }
    # Print headers and row one.
    level == 2 {
        for (i = 1; i <= ncol; ++i)
            printf("%s\t", head[i])
        print ""
        for (i = 1; i <= ncol; ++i)
            printf("%s\t", data[i])
        level = 3
        col = ncol + 1
        if (!short)
            next
    }
    # Empty line, new row.
    ! /./ { print ""; col = 1; next }
    # Next cell.
    col > ncol {next}
    {
        printf "%s%s", $2, (col <= ncol) ? "\t" : ""
        ++col
    }
    END {print ""}
    ' "$2"
}

declare -i ncol=4  # Columns defaults to four.
file=""            # Data file (or pipe).

while [[ -n "$1" ]]; do
    case "$1" in
    "-c") ncol="$2"; shift;;
    "-f") file="$2"; shift;;
    *) printf "Usage: %s [-c <columns>] [-f <file> | pipe]\n" \
        "$(basename $0)" >&2;
        exit;;
    esac
    shift
done

trans "$ncol" "$file" | column -t -s "$(printf "\t")"

1
Niezła odpowiedź! @JoelDavis i ja hakujemy to, ale twoja odpowiedź jest wspaniała!
slm

7

Oto szybki sposób na umieszczenie pliku w wybranym formacie:

$ grep -Ev "^$|title5" sample.txt | sed 's/title[0-9]://g' | paste - - - -
A1  A2  A3  A4
B1  B2  B3  B4
C1  C2  C3  C4
D1  D2  D3  D4

Jeśli chcesz nagłówki kolumn:

$ grep -Ev "^$|title5" sample.txt | sed 's/:.*//' | sort -u | tr '\n' '\t'; \
    echo ""; \
    grep -Ev "^$|title5" a | sed 's/title[0-9]://g' | paste - - - -
title1  title2  title3  title4  
A1      A2      A3      A4
B1      B2      B3      B4
C1      C2      C3      C4
D1      D2      D3      D4

Jak działa drugie polecenie

drukowanie banera
grep -Ev "^$|title5" sample.txt | sed 's/:.*//' | sort -u | tr '\n' '\t';
umieszczenie zwrotu po banerze
echo
drukowanie wierszy danych
grep -Ev "^$|title5" a | sed 's/title[0-9]://g' | paste - - - -

polecenie wklejania po prostu wykonało moją pracę. dzięki za odpowiedź ...
SK Venkat


3

Prawdopodobnie istnieje bardziej zwięzły sposób sformułowania tego, ale wydaje się, że osiąga to ogólny efekt:

[jadavis84@localhost ~]$ sed 's/^title[2-9]://g' file.txt | tr '\n' '\t' | sed 's/title1:/\n/g' ; echo

A1  A2  A3  A4  A5      
B1  B2  B3  B4  B5      
C1  C2  C3  C4  C5      
D1  D2  D3  D4  D5  
[jadavis84@localhost ~]$ 

Wiele sedwywołań nie jest w porządku (i jestem pewien, że sed również potrafi wykonać tłumaczenie nowej linii), więc prawdopodobnie nie jest to najprostszy sposób na zrobienie tego. Powoduje to również usunięcie potencjalnych nagłówków, ale możesz je wygenerować ręcznie, gdy poprawnie sformatujesz wiersze / pola.

Lepsza odpowiedź prawdopodobnie ograniczyłaby ten efekt do samego użycia sedlub awkzrobienia tego, abyś miał tylko jedną rzecz na raz. Ale jestem zmęczony, więc udało mi się to połączyć.


Joel - popełniłem ten sam błąd i właśnie to zauważyłem, nie chce kolumny tytuł5 w wynikach.
slm

Ach, dobre przejście przez awk na końcu powinno to naprawić. Ale wygląda na to, że Sukminder opublikował kompletne rozwiązanie.
Bratchley,

1

pastejest prawdopodobnie najlepszym wyborem. Można wyodrębnić odpowiednie bity z cut, grepi awkjak to:

(awk 'NR==1' RS= infile | cut -d: -f1; cut -sd: -f2 infile)

Jeśli piąta kolumna powinna zostać wyeliminowana, dodaj w awk 'NR%5'ten sposób:

(awk 'NR==1' RS= infile | cut -d: -f1; cut -sd: -f2 infile) | awk 'NR%5'

Teraz uporządkuj za pomocą paste:

(awk 'NR==1' RS= infile | cut -d: -f1; cut -sd: -f2 infile) | awk 'NR%5' | paste - - - -

Wynik:

title1  title2  title3  title4
A1  A2  A3  A4
B1  B2  B3  B4
C1  C2  C3  C4
D1  D2  D3  D4

0

Tylko w przypadku części transponowanej miałem ostatnio podobny problem i użyłem:

awk -v fmt='\t%4s'  '{ for(i=1;i<=NF;i++){ a[i]=a[i] sprintf(fmt, $i); } } END { for (i in a) print a[i]; }'

Dostosuj fmt w razie potrzeby. Dla każdego wiersza wejściowego konkatenuje każde pole z elementem tablicy. Zauważ, że łączenie łańcuchów awk jest niejawne: dzieje się tak, gdy piszesz dwie rzeczy bez żadnego operatora.

Przykładowe I / O:

i       mark    accep   igna    utaal   bta
-22     -10     -10     -20     -10     -10
-21     -10     -10     -20     -10     -10
-20     -10     -10     -20     -10     -10
-19     -10     0       -10     -10     -10
-18     0       0       -10     0       0
-12     0       0       -10     0       0
-11     0       0       -10     0       0
-10     0       0       -10     0       0

wynik:

       i     -22     -21     -20     -19     -18     -12     -11     -10
    mark     -10     -10     -10     -10       0       0       0       0
    accep    -10     -10     -10       0       0       0       0       0
    igna     -20     -20     -20     -10     -10     -10     -10     -10
    utaal    -10     -10     -10     -10       0       0       0       0
     bta     -10     -10     -10     -10       0       0       0       0

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.