Trasposizione di righe e colonne


18

Ho un file con le linee come di seguito.

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

Come posso raggiungere questo obiettivo?

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


per favore, per favore, per favore non usare awk, potresti anche lanciare una soluzione personalizzata con perl o python o un vero linguaggio di programmazione o usare tr / cut con più passaggi per ottenere quello che vuoi
Rudolf Olah,

Risposte:


14

Dai un'occhiata al datamash GNU che può essere usato come datamash transpose. Una versione futura supporterà anche la tabulazione incrociata (tabelle pivot)


9

Al di fuori del rotolamento di una soluzione personalizzata per trasporre righe con colonne da una riga di comando, l'unico strumento che abbia mai visto che può fare questo è uno strumento chiamato ironicamente transpose.

Installazione

Sfortunatamente non è presente in alcun repository, quindi dovrai scaricarlo e compilarlo. Questo è piuttosto semplice poiché non ha librerie aggiuntive da cui dipende. Può essere realizzato in questo modo:

$ gcc transpose.c -o transpose

uso

Può gestire file di testo semplici con facilità. Per esempio:

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

Può essere trasposto usando questo comando:

$ 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

Questo comando serve transposea trasporre ( -t) e il separatore di campo da usare è uno spazio ( --fsep " ").

Il tuo esempio

Poiché i dati di esempio sono in un formato leggermente più complesso, devono essere trattati in 2 fasi. Per prima cosa dobbiamo tradurlo in un formato che transposepossa essere gestito.

Eseguendo questo comando, i dati verranno inseriti in un formato più orizzontale:

$ 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

Ora non ci resta che rimuovere le occorrenze secondarie di title1, title2, ecc .:

$ 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

Ora è in un formato che transposepuò gestire. Il seguente comando eseguirà l'intera trasposizione:

$ 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

È quindi possibile utilizzare awkper elaborare i dati pastee columnformattarli.

Qui presumo title1sia solo un esempio nel tuo post e che i dati non contengano :se non come separatore tra intestazione + dati.

nindica quante colonne stampare (devono corrispondere ai trattini 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")"

Se vuoi renderlo più flessibile e facile da mantenere, puoi scriverlo come uno script. Ecco un esempio che utilizza bash wrapper per awke reindirizzato a column. In questo modo potresti anche fare più controlli sui dati come ad esempio assicurandoti che le intestazioni siano corrette su tutte le righe ecc.

Usato tipicamente come:

$ ./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

Se le intestazioni sono sempre più brevi dei dati, puoi anche salvare le larghezze delle intestazioni, quindi printfcon %-*se salta columntutti insieme.

#!/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
Bella risposta! @JoelDavis e io abbiamo hackerato questo, ma la tua risposta è formidabile!
slm

7

Ecco un modo rapido per mettere il file nel formato desiderato:

$ 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

Se vuoi le intestazioni di colonna:

$ 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

Come funziona il secondo comando

stampa del banner
grep -Ev "^$|title5" sample.txt | sed 's/:.*//' | sort -u | tr '\n' '\t';
inserendo un ritorno dopo il banner
echo
stampa delle file di dati
grep -Ev "^$|title5" a | sed 's/title[0-9]://g' | paste - - - -

Il comando incolla ha semplicemente fatto il mio lavoro. grazie per la risposta ...
SK Venkat,


3

C'è probabilmente un modo più conciso di formulare questo, ma questo sembra realizzare l'effetto generale:

[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 ~]$ 

Le sedinvocazioni multiple non sembrano corrette (e sono abbastanza sicuro che sed possa fare anche la traduzione della nuova riga), quindi probabilmente non è il modo più semplice per farlo. Inoltre, questo elimina le possibili intestazioni, ma è possibile generarle manualmente dopo aver formattato correttamente le righe / i campi.

Una risposta migliore probabilmente distillerebbe questo effetto semplicemente usando sedo awkper fare questo in modo da avere solo una cosa alla volta. Ma sono stanco, questo è ciò che sono stato in grado di mettere insieme.


Joel - Ho fatto lo stesso errore e l'ho appena notato, non vuole la colonna title5 nell'output.
slm

Ah, correndo attraverso awk, alla fine, dovrebbe risolverlo. Ma sembra che Sukminder abbia pubblicato una soluzione completa.
Bratchley,

1

pasteè probabilmente la tua scommessa migliore. È possibile estrarre i bit rilevanti con cut, grepe awkin questo modo:

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

Se la quinta colonna deve essere eliminata, aggiungi in awk 'NR%5'questo modo:

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

Ora colonnato con paste:

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

Produzione:

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

0

Solo per la parte trasposta, ho avuto un problema simile di recente e ho usato:

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]; }'

Regola l'fmt secondo necessità. Per ogni riga di input, concatena ogni campo su un elemento array. Si noti che la concatenazione di stringhe awk è implicita: accade quando si scrivono due cose senza alcun operatore.

I / O di esempio:

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

produzione:

       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

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.