Hola, tengo un formato de archivo (TSV) como este
Name type Age Weight Height Xxx M 12,34,23 50,30,60,70 4,5,6,5.5 Yxx F 21,14,32 40,50,20,40 3,4,5,5.5Me gustaría agregar todos los valores en Edad, Peso y Altura y agregar una columna después de esto, luego también un porcentaje, como Total_Height/Total_Weight (awk '$0=$0"\t"(NR==1?"Percentage" :$8/$7)'). Tengo un gran conjunto de datos y no es posible hacerlo con Excel.
Me gusta esto
Name type Age Weight Height Total_Age Total_Weight Total_Height Percentage Xxx M 12,34,23 50,30,60,70 4,5,6,5.5 69 210 20.5 0.097 Yxx F 21,14,32 40,50,20,40 3,4,5,5.5 67 150 17.5 0.11Usando cualquier awk en cualquier shell en cada cuadro de Unix y sin crear nuevos campos en cada registro (lo cual es ineficiente ya que hace que awk reconstruya el registro cada vez que cambia un campo) y sin actualizar el registro de entrada (que es ineficiente como hace que awk vuelva a dividir el registro en campos cada vez que cambia el registro) y está diseñado para funcionar para cualquier cantidad de columnas de entrada de valor en cualquier orden:
$ cat tst.awk BEGIN { FS=OFS="\t" } { printf "%s%s", $0, OFS } NR==1 { for (i=3; i<=NF; i++) { printf "Total_%s%s", $i, OFS tags[i] = $i } print "Percentage" next } { delete tot for (i=3; i<=NF; i++) { tag = tags[i] n = split($i,vals,",") for (j in vals) { tot[tag] += vals[j] } printf "%s%s", tot[tag], OFS } printf "%0.3f%s", (tot["Weight"] ? tot["Height"] / tot["Weight"] : 0), ORS } $ awk -f tst.awk file Name type Age Weight Height Total_Age Total_Weight Total_Height Percentage Xxx M 12,34,23 50,30,60,70 4,5,6,5.5 69 210 20.5 0.098 Yxx F 21,14,32 40,50,20,40 3,4,5,5.5 67 150 17.5 0.117 $ awk -f tst.awk file | column -t Name type Age Weight Height Total_Age Total_Weight Total_Height Percentage Xxx M 12,34,23 50,30,60,70 4,5,6,5.5 69 210 20.5 0.098 Yxx F 21,14,32 40,50,20,40 3,4,5,5.5 67 150 17.5 0.117 Para mostrar las ventajas funcionales del enfoque anterior, imagine que necesita agregar más valores como ShoeSize y/o reorganizar el orden de las columnas, por ejemplo:
$ column -t file Name type ShoeSize Height Age Weight Xxx M 12,8,10 4,5,6,5.5 12,34,23 50,30,60,70 Yxx F 9,7,8 3,4,5,5.5 21,14,32 40,50,20,40 Ahora ejecute el script anterior y observe que se Total_ columnas para cada columna original y aún obtiene la misma columna de Percentage de Altura/Peso agregada al final:
$ awk -f tst.awk file | column -t Name type ShoeSize Height Age Weight Total_ShoeSize Total_Height Total_Age Total_Weight Percentage Xxx M 12,8,10 4,5,6,5.5 12,34,23 50,30,60,70 30 20.5 69 210 0.098 Yxx F 9,7,8 3,4,5,5.5 21,14,32 40,50,20,40 24 17.5 67 150 0.117Aquí hay un Ruby que es un poco más fácil para múltiples campos de datos como este:
ruby -F"\t" -lane ' if ($.==1) puts "Name\ttype\tAge\tWeight\tHeight\tTotal_Age\tTotal_Weight\tTotal_Height\tPercentage" next end fields=$F.clone $F.each{|f| fields.append(f.split(/,/).map(&:to_f).sum) if f[/^[\d,.]+$/] && f[/,/]} fields.append((fields[-1]/fields[-2]).round(3)) puts fields.join("\t")' file | column -tHuellas dactilares:
Name type Age Weight Height Total_Age Total_Weight Total_Height Percentage Xxx M 12,34,23 50,30,60,70 4,5,6,5.5 69.0 210.0 20.5 0.098 Yxx F 21,14,32 40,50,20,40 3,4,5,5.5 67.0 150.0 17.5 0.117 La ventaja aquí es que las sumas de columnas de n.nn,n.nn,... se agregan de manera flexible al final de la fila en el orden en que se encuentran.
Usaría la split de funciones de GNU AWK para esta tarea de la siguiente manera. Considere seguir un ejemplo simple, deje que el contenido de file.txt sea
Name type Age Weight Height Xxx M 12,34,23 50,30,60,70 4,5,6,5.5 Yxx F 21,14,32 40,50,20,40 3,4,5,5.5luego
awk 'BEGIN{OFS="\t"}NR==1{print "Age","Total"}NR>1{totalage=0;split($3,ages,",");for(a in ages){totalage+=ages[a]};print $3,totalage}' file.txtproducción
Age Total 12,34,23 69 21,14,32 67 Explicación: en primer lugar, informé a GNU AWK para usar la pestaña como separador de campo de salida ( OFS ), luego, para la primera línea, totalage encabezados, para cada línea siguiente: establezco el valor total en 0 , divido el contenido de la tercera columna en ages de matriz en , , transversal dicha matriz obtiene la suma de sus valores y luego print el contenido de la tercera columna y la suma. Tenga en cuenta que
Antes de dividir la cadena,
split()elimina cualquier elemento previamente existente en la matriz de matrices y seps .
Por lo tanto, no requiere reinicio a diferencia de la variable totalage .
(probado en gawk 4.2.1)
Con sus muestras mostradas, intente seguir el código.
awk ' FNR==1{ print $0,"Total_Age Total_Weight Total_Height Percentage" next } FNR>1{ totAge=totWeight=totHeight=0 split($3,tmp,",") for(i in tmp){ totAge+=tmp[i] } split($4,tmp,",") for(i in tmp){ totWeight+=tmp[i] } split($5,tmp,",") for(i in tmp){ totHeight+=tmp[i] } $(NF+1)=totAge $(NF+1)=totWeight $(NF+1)=totHeight $(NF+1)=$(NF-1)==0?"N/A":$NF/$(NF-1) } 1' Input_file | column -t O agregando una versión un poco corta del código awk anterior:
awk ' BEGIN{OFS="\t"} FNR==1{ print $0,"Total_Age Total_Weight Total_Height Percentage" next } FNR>1{ totAge=totWeight=totHeight=0 split($3,tmp,",") for(i in tmp){ totAge+=tmp[i] } split($4,tmp,",") for(i in tmp){ totWeight+=tmp[i] } split($5,tmp,",") for(i in tmp){ totHeight+=tmp[i] } $(NF+1)=totAge OFS totWeight OFS totHeight $0=$0 $(NF+1)=( $(NF-1)==0 ? "N/A" : $NF/$(NF-1) ) } 1' Input_file | column -t Explicación: la explicación simple sería tomar la suma de las columnas 3, 4 y 5 y asignarlas a la última columna de la línea. En consecuencia, agregue el valor de la columna que tiene el valor de división de la última y la segunda última columna según la solicitud de OP. Usando column -t para que se vea mejor en la salida.
Si tiene que hacer las mismas operaciones varias veces, también puede usar una función para sumar los valores de la matriz (dado que los valores son números separados por comas).
Reutilizando algunas partes de la respuesta de @ RavinderSingh13 y un gran agradecimiento a @ Ed Morton por tomarse el tiempo para brindar excelentes comentarios para mejorar el código:
awk ' function arraySum(field, sum,arr,i) { split(field,arr,",") for (i in arr) sum += arr[i] return sum } FNR==1{ print $0, "Total_Age", "Total_Weight", "Total_Height", "Percentage" next } NR > 1 { sumWeight = arraySum($4) sumHeight = arraySum($5) print $0, arraySum($3), sumWeight, sumHeight, (sumWeight ? sumHeight/sumWeight : 0) }' file | column -tProducción
Name type Age Weight Height Total_Age Total_Weight Total_Height Percentage Xxx M 12,34,23 50,30,60,70 4,5,6,5.5 69 210 20.5 0.097619 Yxx F 21,14,32 40,50,20,40 3,4,5,5.5 67 150 17.5 0.116667