Transposition de lignes et de colonnes


18

J'ai un fichier avec les lignes comme ci-dessous.

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

Comment puis-je atteindre cet objectif?

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


s'il vous plaît s'il vous plaît s'il vous plaît ne pas utiliser awk, vous pourriez aussi bien rouler une solution personnalisée avec perl ou python ou un vrai langage de programmation ou utiliser tr / cut avec plusieurs passes pour obtenir ce que vous voulez
Rudolf Olah

Réponses:


14

Jetez un oeil à la datamash GNU qui peut être utilisée comme datamash transpose. Une future version prendra également en charge la tabulation croisée (tableaux croisés dynamiques)


9

En dehors de rouler une solution personnalisée pour transposer des lignes avec des colonnes à partir d'une ligne de commande, le seul outil que j'ai jamais vu qui puisse le faire est un outil appelé ironiquement transpose.

Installation

Malheureusement, il ne se trouve dans aucun dépôt, vous devrez donc le télécharger et le compiler. C'est assez simple car il n'a pas de bibliothèques supplémentaires dont il dépend. Cela peut être accompli comme suit:

$ gcc transpose.c -o transpose

Usage

Il peut facilement gérer des fichiers texte simples. Par exemple:

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

Peut être transposé à l'aide de cette commande:

$ 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

Cette commande consiste transposeà transposer ( -t) et le séparateur de champ à utiliser est un espace ( --fsep " ").

Votre exemple

Étant donné que vos exemples de données sont dans un format légèrement plus complexe, ils doivent être traités en 2 phases. Nous devons d'abord le traduire dans un format transposecompatible.

L'exécution de cette commande mettra les données dans un format plus convivial horizontalement:

$ 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

Il nous suffit maintenant de supprimer les occurrences secondaires du titre1, du titre2, 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

C'est maintenant dans un format qui transposepeut gérer. La commande suivante fera toute la transposition:

$ 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

Vous pouvez alors utiliser awkpour traiter les données pasteet columnles formater.

Ici, je suppose que ce title1n'est qu'un exemple dans votre message, et que les données ne contiennent :que comme séparateur entre en-tête + données.

nsignifie le nombre de colonnes à imprimer (doit correspondre aux tirets 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 vous voulez le rendre plus flexible et plus facile à entretenir, vous pouvez l'écrire sous forme de script. Voici un exemple d'utilisation de wrapper bash pour awket canalisé vers column. De cette façon, vous pouvez également faire plus de vérification de données, par exemple en vous assurant que les en-têtes sont corrects sur toutes les lignes, etc.

Utilisé généralement comme:

$ ./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 les en-têtes sont toujours plus courts que les données, vous pouvez également enregistrer les largeurs d'en-tête, puis printfavec %-*set sauter columntous ensemble.

#!/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
Bonne réponse! @JoelDavis et moi avons piraté cela, mais votre réponse est formidable!
slm

7

Voici un moyen rapide de mettre le fichier au format souhaité:

$ 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 vous voulez les en-têtes de colonne:

$ 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

Fonctionnement de la 2e commande

impression de la bannière
grep -Ev "^$|title5" sample.txt | sed 's/:.*//' | sort -u | tr '\n' '\t';
mettre un retour après la bannière
echo
impression des lignes de données
grep -Ev "^$|title5" a | sed 's/title[0-9]://g' | paste - - - -

La commande paste a simplement fait mon travail. merci pour la réponse ...
SK Venkat


3

Il existe probablement une façon plus succincte de formuler cela, mais cela semble produire l'effet général:

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

Les sedinvocations multiples ne se sentent pas bien (et je suis presque sûr que sed peut également faire la traduction de la nouvelle ligne), donc ce n'est probablement pas la façon la plus simple de le faire. En outre, cela supprime les en-têtes potentiels, mais vous pouvez les générer manuellement une fois que les lignes / champs sont correctement formatés.

Une meilleure réponse distillerait probablement cet effet en utilisant simplement sedou awken faisant ceci de sorte que vous n'ayez qu'une chose à faire à la fois. Mais je suis fatigué donc c'est ce que j'ai pu mettre en place.


Joel - J'ai fait la même erreur et je viens de le remarquer, il ne veut pas de la colonne title5 dans la sortie.
slm

Ah, bien traverser awk à la fin devrait résoudre ce problème. Mais il semble que Sukminder ait publié une solution complète.
Bratchley

1

pasteest probablement votre meilleur pari. Vous pouvez extraire les bits pertinents avec cut, grepet awkcomme ceci:

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

Si la 5ème colonne doit être supprimée, ajoutez awk 'NR%5'comme ceci:

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

Colonne maintenant avec paste:

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

Production:

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

0

Pour juste la partie transposée, j'ai eu un problème similaire récemment et j'ai utilisé:

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

Ajustez le fmt au besoin. Pour chaque ligne d'entrée, il concatène chaque champ sur un élément de tableau. Notez que la concaténation de chaînes awk est implicite: elle se produit lorsque vous écrivez deux choses sans aucun opérateur.

Exemple d'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

production:

       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

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.