Transposición de filas y columnas


18

Tengo un archivo con las líneas como a continuación.

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

¿Cómo puedo conseguir esto?

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


por favor, por favor no use awk, también podría lanzar una solución personalizada con perl o python o un lenguaje de programación real o usar tr / cut con múltiples pases para obtener lo que desea
Rudolf Olah

Respuestas:


14

Eche un vistazo a GNU datamash que se puede usar como datamash transpose. Una versión futura también admitirá tabulación cruzada (tablas dinámicas)


9

Además de lanzar una solución personalizada para transponer filas con columnas desde una línea de comando, la única herramienta que he visto que puede hacer esto es una herramienta llamada irónicamente transpose.

Instalación

Lamentablemente, no está en ningún repositorio, por lo que deberá descargarlo y compilarlo. Esto es bastante sencillo ya que no tiene bibliotecas adicionales de las que depende. Se puede lograr así:

$ gcc transpose.c -o transpose

Uso

Puede manejar archivos de texto sencillos con facilidad. Por ejemplo:

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

Se puede transponer usando este 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

Este comando es transposetransponer ( -t) y el separador de campo a utilizar es un espacio ( --fsep " ").

Su ejemplo

Como sus datos de muestra están en un formato un poco más complejo, debe tratarse en 2 fases. Primero necesitamos traducirlo a un formato que transposepueda manejar.

Al ejecutar este comando, los datos se colocarán en un formato más horizontal:

$ 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

Ahora solo necesitamos eliminar las ocurrencias secundarias del título1, título2, etc .:

$ 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

Ahora está en un formato que transposepuede manejar. El siguiente comando realizará la transposición completa:

$ 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

Podría usar awkpara procesar los datos pastey columnformatearlos.

Aquí supongo que title1es solo un ejemplo en su publicación, y que los datos no contienen :excepto como separador entre encabezado + datos.

nsignifica cuántas columnas para imprimir (deben coincidir con guiones 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")"

Si desea que sea más flexible y fácil de mantener, puede escribirlo como un script. Aquí hay un ejemplo usando bash wrapper para awky conectado a column. De esta manera, también podría hacer más verificaciones de datos, por ejemplo, asegurarse de que los encabezados sean correctos en todas las filas, etc.

Usado típicamente como:

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

Si encabezados siempre es más corta de datos entonces se podría también ahorrar ancho de cabecera, a continuación, printfcon %-*sy saltar columntodos juntos.

#!/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
¡Buena respuesta! @JoelDavis y yo hemos estado hackeando esto, ¡pero tu respuesta es excelente!
slm

7

Aquí hay una forma rápida de colocar el archivo en el formato que desee:

$ 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

Si desea los encabezados de columna:

$ 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

Cómo funciona el segundo comando

imprimiendo el banner
grep -Ev "^$|title5" sample.txt | sed 's/:.*//' | sort -u | tr '\n' '\t';
poniendo un retorno después de la pancarta en
echo
imprimir las filas de datos
grep -Ev "^$|title5" a | sed 's/title[0-9]://g' | paste - - - -

el comando pegar simplemente hizo mi trabajo. gracias por la respuesta ...
SK Venkat


3

Probablemente haya una forma más sucinta de formular esto, pero esto parece lograr el efecto general:

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

Las sedinvocaciones múltiples no se sienten bien (y estoy bastante seguro de que sed también puede hacer la traducción de la nueva línea), por lo que probablemente no sea la forma más directa de hacerlo. Además, esto elimina los posibles encabezados, pero puede generarlos manualmente una vez que tenga las filas / campos formateados correctamente.

Una mejor respuesta probablemente destile ese efecto a solo usar sedo awkhacer esto para que solo tenga una cosa a la vez. Pero estoy cansado, así que esto es lo que pude armar.


Joel: cometí el mismo error y lo noté, no quiere la columna del título 5 en la salida.
slm

Ah, bien corriendo por awk al final debería arreglar eso. Pero parece que Sukminder ha publicado una solución completa.
Bratchley

1

pastees probablemente tu mejor apuesta Puede extraer los bits relevantes con cut, grepy awkasí:

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

Si la quinta columna se debe eliminar, agregue awk 'NR%5'así:

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

Ahora columnate con paste:

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

Salida:

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

0

Solo por la parte de transposición, tuve un problema similar recientemente y usé:

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

Ajuste el fmt según sea necesario. Para cada línea de entrada, concatena cada campo en un elemento de matriz. Tenga en cuenta que la concatenación de cadenas awk está implícita: sucede cuando escribe dos cosas sin ningún operador.

Muestra de E / S:

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

salida:

       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

Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.