Fusionar columnas dentro de un archivo según el encabezado de la columna

Fusionar columnas dentro de un archivo según el encabezado de la columna

Quiero fusionar diferentes columnas dentro de mi archivo que comparten el mismo encabezado de columna. El archivo tiene este aspecto y puede estar separado por tabulaciones o por otra cosa:

AaBbN    CcDdEeN    FfN     AaBbN    FfN
1        5          4   
3        1          2
2        NA         1
                            1        3
                            3        2
                            NA       4

Entonces hay números o la cadena "NA" en los campos. El resultado se vería así:

AaBbN    CcDdEeN    FfN
1        5          4
3        1          2
2        NA         1
1                   3
3                   2
NA                  4

Hay muchas columnas que no están ordenadas, por lo que los encabezados de los títulos deberían leerse automáticamente en lugar de especificarlos manualmente. También hay muchos campos vacíos. He estado investigando los comandos pastey joinpara hacer el trabajo. Especialmente joinparece hacer lo que necesito, excepto que funciona con archivos separados, mientras que mis columnas están dentro del mismo archivo.

Entonces intenté separar las columnas en archivos separados y luego combinarlos con join. Usé un awkcomando que derivé de aquí:

https://www.unix.com/unix-for-dummies-questions-and-answers/204303-splitting-up-text-file-into-multiple-files-columns.html

awk ' { for( i = 1; i <= NF; i++ ) printf( "%s\n", $(i) ) >i ".txt"; } ' file.txt

lo que me da columnas separadas, pero aquí me encontré con el primer problema. Todas las columnas con espacios vacíos entre el encabezado y los datos no se procesaron correctamente. En cambio, en estos archivos sólo estaba presente el encabezado de la columna.

Mi segundo problema es join: Cuando intento volver a fusionar archivos, obtengo errores porque la entrada no está ordenada, lo cual, por supuesto, es imposible de hacer. Cualquier clasificación destruiría la relación que cuido.

Así que aquí estoy en un callejón sin salida. ¿Existe una forma más conveniente de fusionar las columnas directamente dentro de un archivo?


Editar:

La solución de AdminBees es lo que más se acerca a la solución del problema, pero el resultado no es del todo correcto. Aquí está el resultado del script awk aplicado al ejemplo anterior. Me aseguré de que todas las entradas estén separadas por tabulaciones sed -i "s/[[:space:]]/ /g"(pestaña insertada con CTRL+V y TAB).

AaBbN   CcDdEeN FfN     FfN
1   5   4   

3   1   2

2   NA  1

            1
            3
            NA

Respuesta1

Si su entrada está separada por tabulaciones:

awk -F"\t" '
NR == 1 {for (i=1; i<=NF; i++)  COL[i] = $i
        }
        {for (i=1; i<=NF; i++) OUT[NR, COL[i]] = $i
        }
END     {for (n=1; n<=NR; n++)  {split ("", DUP)
                                 for (i=1; i<=NF; i++)  if (!DUP[COL[i]]++) printf "%s" FS, OUT[n,COL[i]]
                                 printf RS
                                }
        }
' file
A   B   C   
1   5   4   
3   1   2   
2   2   1   
1       3   
3       2   
1       4   

Guarda los encabezados de las columnas para usarlos como índices parciales más adelante, luego, para cada línea, recopila valores en una matriz indexada por el número de línea y el índice parcial del encabezado. En la ENDsección, imprime esa matriz en la secuencia original cuidando los encabezados de columna duplicados.

El manejo de duplicados puede convertirse en un esfuerzo importante para estructuras de archivos más complejas.

Respuesta2

para la entrada separada por tabulaciones.

lea el encabezado y los números de las columnas correspondientes en una matriz donde aparecían en el archivo de entrada; luego, dividir el archivo de entrada en cada columna en el mismo nombre de archivo headerName.txt que tiene el mismo nombre de encabezado. después de todo pegarlos juntos ycolumnComando utilizado para embellecer la salida.

awk -F'\t' '
    ## find all the column number(s) when same header found and store in `h` array
    ## key is the column number and value is header name. for an example:
    ## for the header value 'A', keys will be columns 1 &4
    NR==1{ while (++i<=NF) h[i]=$i; next; }

         { for (i=1; i<=NF; i++) {

    ## save the field content to a file which its key column matches with the column 
    ## number of the current field. for an example:
    ## for the first field in column 1; the column number is 1, and so 1 is the key  
    ## column for header value A, so this will be written to "A.txt" filename
    ## only if it was not empty.
               if ($i!=""){ print $i> h[i]".txt" };
         }; }

    ## at the end paste those all files and beautify output with `column` command.
    ## number of .txt files above is limit to the number of uniq headers in your input. 
END{ system("paste *.txt |column \011 -tn") }' infile

comando sin comentarios:

awk -F'\t' '
    NR==1{ while (++i<=NF) h[i]=$i; next; }
         { for (i=1; i<=NF; i++) {
               if ($i!=""){ print $i> h[i]".txt" };
         }; }
END{ system("paste *.txt |column \011 -tn") }' infile

Respuesta3

Un enfoque ligeramente diferente que no requiere "almacenar en búfer" todo el archivo:

Guión AWK colmerge.awk:

FNR==1{
    for (i=1; i<=NF; i++)
    {
    hdr[i]=$i;
    if (map[$i]==0) {map[$i]=i; uniq_hdr[++u]=$i; printf("%s",$i);}
    if (i==NF) printf("%s",ORS); else printf("%s",OFS);
    }
}

FNR>1{
    delete linemap;
    for (i=1; i<=NF; i++) if ($i!="") linemap[hdr[i]]=$i;
    for (i=1; i<=u; i++)
    {
    printf("%s",linemap[uniq_hdr[i]]);
    if (i==u) printf("%s",ORS); else printf("%s",OFS);
    }
}

Usar como

awk -F'\t' -v OFS='\t' -f colmerge.awk file

Esto reunirá todos los encabezados e identificará los encabezados "únicos" y su primera aparición en la línea 1, y para cada línea sucesiva creará un mapa entre los encabezados y los valores no vacíos, que luego se imprimirá en el orden de los encabezados "únicos". como se identificó al procesar la primera línea.

Sin embargo, esto sólo funciona si su archivo de entrada está separado por tabulaciones, ya que esta es la única manera de detectar de manera confiable campos "vacíos".

Tenga en cuenta también que es posible que la deletedeclaración para toda la matriz linemapno sea compatible con todas las implementaciones (sin embargo , awkdebería funcionar en y ).gawkmawknawk

información relacionada