Transposição de linhas e colunas

18

Eu tenho um arquivo com as linhas abaixo.

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

Como posso conseguir isso?

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

Dens
fonte
por favor, por favor, por favor, não use awk, assim como você pode rolar uma solução personalizada com perl ou python ou uma linguagem de programação real ou uso tr / corte com vários passes para conseguir o que você quer
Rudolf Olah

Respostas:

14

Dê uma olhada no GNU datamash que pode ser usado como datamash transpose. Uma versão futura também suportará tabulação cruzada (tabelas dinâmicas)

Pádraig Brady
fonte
9

Fora a implementação de uma solução personalizada para transpor linhas com colunas de uma linha de comando, a única ferramenta que eu já vi que pode fazer isso é uma ferramenta chamada ironicamente transpose.

Instalação

Infelizmente, ele não está em nenhum repositório, portanto você precisará fazer o download e compilá-lo. Isso é bem direto, pois não possui bibliotecas adicionais das quais depende. Isso pode ser realizado da seguinte maneira:

$ gcc transpose.c -o transpose

Uso

Ele pode lidar com arquivos de texto simples com facilidade. Por exemplo:

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

Pode ser transposto 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 é transposepara transpose ( -t) e o separador de campos a ser usado é um espaço ( --fsep " ").

Seu exemplo

Como os dados da amostra estão em um formato um pouco mais complexo, eles precisam ser tratados em duas fases. Primeiro, precisamos traduzi-lo para um formato que transposepossa lidar.

A execução deste comando colocará os dados em um formato mais horizontalmente amigável:

$ 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

Agora só precisamos remover as ocorrências secundárias do 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

Agora está em um formato que transposepode lidar. O comando a seguir fará toda a transposição:

$ 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
slm
fonte
8

Você pode usar awkpara processar os dados pastee columnformatá-los.

Aqui, suponho que title1seja apenas um exemplo em sua postagem, e que os dados não contenham, :exceto como separador entre o cabeçalho + os dados.

nsignifica quantas colunas imprimir (deve corresponder traços 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 você quiser torná-lo mais flexível e fácil de manter, você pode escrevê-lo como um script. Aqui está um exemplo usando o bash wrapper para awke canalizado para column. Dessa forma, você também pode fazer mais verificações de dados, como, por exemplo, garantir que os cabeçalhos estejam corretos em todas as linhas, etc.

Usado tipicamente 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

Se os cabeçalhos sempre forem mais curtos que os dados, você também poderá salvar as larguras dos cabeçalhos, printfcom %-*se pular 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")"
Runium
fonte
1
Boa resposta! @JoelDavis e eu estamos hackeando isso, mas sua resposta é fantástica!
slm
7

Aqui está uma maneira rápida de colocar o arquivo no formato desejado:

$ 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 você deseja os cabeçalhos das colunas:

$ 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

Como o 2º comando funciona

imprimindo o banner
grep -Ev "^$|title5" sample.txt | sed 's/:.*//' | sort -u | tr '\n' '\t';
retornando após o banner em
echo
imprimindo as linhas de dados
grep -Ev "^$|title5" a | sed 's/title[0-9]://g' | paste - - - -
slm
fonte
O comando colar simplesmente fez meu trabalho. obrigado pela resposta ...
SK Venkat
3

Provavelmente existe uma maneira mais sucinta de formular isso, mas isso parece alcançar o efeito geral:

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

Múltiplas sedinvocações não parecem certas (e tenho certeza que o sed também pode fazer a tradução de novas linhas), portanto, provavelmente não é a maneira mais direta de fazê-lo. Além disso, isso remove os possíveis cabeçalhos, mas você pode gerá-los manualmente quando tiver as linhas / campos formatados corretamente.

Uma resposta melhor provavelmente destilaria esse efeito usando apenas sedou awkfazendo isso, para que você só tenha uma coisa acontecendo de cada vez. Mas estou cansado, então foi isso que consegui montar.

Bratchley
fonte
Joel - Eu cometi o mesmo erro e acabei de perceber, ele não quer a coluna title5 na saída.
Slm
Ah, finalmente, atravessar o awk deve consertar isso. Mas parece que a Sukminder postou uma solução completa.
Bratchley
1

pasteprovavelmente é sua melhor aposta. Você pode extrair os bits relevantes com cut, grepe awkassim:

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

Se a 5ª coluna for eliminada, acrescente awk 'NR%5' seguinte forma:

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

Agora colabore com paste:

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

Resultado:

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

Apenas para a parte de transposição, tive um problema semelhante recentemente e usei:

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 o fmt conforme necessário. Para cada linha de entrada, concatena cada campo em um elemento da matriz. Observe que a concatenação de strings awk está implícita: isso acontece quando você escreve duas coisas sem nenhum operador.

E / S de amostra:

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

resultado:

       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
Peter Cordes
fonte