El procesamiento posterior de un archivo csv de varias columnas contenía muchas (más de 10000) líneas:
ID(Prot), ID(lig), ID(cluster), dG(rescored), dG(before), POP(before)
9000, lig662, 1, 0.421573, -7.8400, 153
10V2, lig807, 1, 0.42692, -8.0300, 149
3000, lig158, 1, 0.427342, -8.1900, 147
3001, lig158, 1, 0.427342, -8.1900, 147
10V2, lig342, 1, 0.432943, -9.4200, 137
10V1, lig807, 1, 0.434338, -8.0300, 147
4000, lig236, 1, 0.440377, -7.3200, 156
10V1, lig342, 1, 0.441205, -9.4200, 135
4000, lig497, 1, 0.442088, -7.7900, 148
9000, lig28, 1, 0.442239, -7.5200, 152
3001, lig296, 1, 0.444512, -7.8900, 146
10V2, lig166, 1, 0.447681, -7.1500, 157
....
4000, lig612, 1, 0.452904, -7.0200, 158
9000, lig123, 1, 0.461601, -6.8000, 160
10V1, lig166, 1, 0.463963, -7.1500, 152
10V1, lig369, 1, 0.465029, -7.3600, 148
lo que hice hasta ahora
Estoy usando el siguiente awk
código integrado en una bash
función, que toma el 1% (líneas superiores) del CSV y lo guarda como un nuevo CSV (que contiene, por lo tanto, un número reducido de líneas):
take_top44 () {
# Take the top lines from the initial CSV
awk -v lines="$(wc -l < original.csv)" '
BEGIN{
top=int(lines/100)
}
FNR>(top){exit}
1
' original.csv >> csv_with_top_lines.csv
}
lo que quiero hacer ahora
¿Cómo podría modificar mi awk
código para aplicar un filtro más selectivo en el CSV original? Por ejemplo, ¿para filtrar los datos en función del valor (número flotante) de la cuarta columna (en dG(rescored)
)?
Por ejemplo, necesito usar el valor más bajo (que siempre está en la segunda línea minForth = 0.421573
) como referencia e imprimir todas las líneas del CSV donde $4
sea menor que un umbral seleccionado (digamos, 20% por encima de minForth
):
$4<=(1+0.2)*min))'
Respuesta1
Si simplemente desea filtrar todas las líneas donde el cuarto campo está por debajo de un umbral, el siguiente awk
comando funcionaría:
awk -F',' -v margin=0.2 'FNR==2 {min=$4} FNR>1&&($4<=(1+margin)*min)' input.csv
o, si también desea tener el encabezado en la salida filtrada:
awk -F',' -v margin=0.2 'FNR==2 {min=$4} FNR==1||($4<=(1+margin)*min)' input.csv
Esto establecerá el separador de archivos en ,
(pero tenga en cuenta que su archivo no es CSV estándar ya que tiene espacios adicionales separando sus campos) e importará una variable margin
con un valor de 0.2
al awk
programa.
Dentro del programa, establecerá el min
valor de la variable en el valor de la cuarta columna si estamos en la línea 2 ( FNR==2
). Entonces solo imprimirá la línea actual si estamos en la línea 1 (el encabezado, si lo desea) o si estamos en la parte de datos del archivo y el cuarto campo es más pequeño que 1+margin
el valor mínimo.
Respuesta2
Este es un script bastante detallado: no utilice ningún atajo ni imprima información en stderr
. En cuanto a la parte sh, normalmente podría agregar opciones para establecer los valores "Globales" en la parte superior, de modo que pueda llamar con opciones además de argumentos. Es decir:
my_script --max-factor 0.15 -p 20 --out-file foo.csv *.csv
Entonces, mediante este filtrado rescored
y porcentaje de líneas. Obviamente, las partes detalladas se pueden eliminar.
#!/bin/sh
# Globals with defaults set
num_lines=0
max_percent_lines=10
max_factor=0.2
fn_in=""
# Default out. Optionally set to another file.
fn_out=/dev/stdout
# As /dev/null to quiet information
fn_err=/dev/stderr
get_num_lines()
{
num_lines=$(wc -l< "$1")
}
print_filtered()
{
awk \
-v num_lines="$num_lines" \
-v max_percent_lines="$max_percent_lines" \
-v max_factor="$max_factor" \
-v fn_err="$fn_err" \
'
BEGIN {
FS=", "
# Exclude header
max_line = (1 + num_lines / 100 * max_percent_lines)
# Truncate
max_line -= max_line % 1
printf "Lines : %d\n",
num_lines - 1 >>fn_err
printf "Line Max : %d (%d%%)\n",
max_line, max_percent_lines >>fn_err
}
NR == 2 {
max_rescored = ($4 + $4 * max_factor)
printf "Rescored Max: %f\n", max_rescored >>fn_err
}
NR > 1 {
print $0
}
NR >= max_line {
printf "Max Line : %d (Exit)\n", max_line >>fn_err
exit
}
$4 >= max_rescored && NR > 2 {
printf "Max Rescored: %f (Exit)\n", $4 >>fn_err
exit
}
' "$fn_in" >>"$fn_out"
}
# Here one could loop multiple input files
Opciones de línea de comando
Según solicitud en comentarios.
Para obtener las opciones existen numerosas formas. Los más simples serían los argumentos posicionales. Por ejemplo:
Usage: script percent margin <files ...>
En el guión se diría entonces:
percent=$1
margin=$2
shift
shift
... loop files ...
Si a uno le gusta ser un poco más sofisticado/flexible, podría hacer algo como esto;
Primero escribe una help
función. Podría ser algo así. (El uso de basename
y $0
probablemente pueda discutirse):
print_help() {
printf "Usage: %s [OPTIONS] <FILES ...>\n" "$(basename "$0")"
printf "\nSome description\n"
printf "\nOPTIONS\n"
printf " -p --percent-lines V Print percent of file. Default %s\n" "$max_percent_lines"
printf " -r --max-factor V Max rescored increase. Default %s\n" "$max_factor"
printf " -o --out-file V Output file. Default stdout\n"
printf " -q --quiet Silence information\n"
printf " -h --help This help\n"
printf " -- Everything after this is input files\n"
printf "\nEverything after first unrecognized option is treated as a file.\n"
}
Donde normalmente se llama para print_help >&2
imprimir en stderr y no en stdout.
Con help
lo anterior, use una forma semi estándar. No hace falta -abc
o --foo=123
, pero cada opción y argumento debe estar separado por espacios.
Opcionalmente, sin juego de palabras, consulte publicaciones como
Entonces, una forma sencilla para el resto del script, con algunas comprobaciones ingenuas de errores, podría ser:
# While not empty
while ! [ -z "$1" ]; do
case "$1" in
-h|--help)
print_help >&2
exit 1
;;
-p|--percent-lines)
shift
max_percent_lines="$1"
;;
-r|--max-factor)
shift
max_factor="$1"
;;
-o|--out-file)
shift
fn_out="$1"
;;
-q|--quiet)
fn_err="/dev/null"
;;
--)
break
;;
*)
break
;;
esac
# Next argument
shift
done
if ! [ -r "$1" ]; then
printf "Unable to read file: \`%s'\n" "$1" >&2
exit 1
fi
# Print header from first file
head -n 1 "$1" >>"$fn_out"
for fn_in in "$@"; do
printf "Processing '%s'\n" "$fn_in" >>"$fn_err"
if ! [ -r "$1" ]; then
printf "Unable to read file: \`%s'\n" "$1" >&2
exit 1
fi
get_num_lines
print_filtered
done
Se podría implementar una mayor validación de opciones, es decir, garantizar que sean números, etc.