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 paste
y join
para hacer el trabajo. Especialmente join
parece 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 awk
comando que derivé de aquí:
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 END
secció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 ycolumn
Comando 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 delete
declaración para toda la matriz linemap
no sea compatible con todas las implementaciones (sin embargo , awk
debería funcionar en y ).gawk
mawk
nawk