Utilice awk para seleccionar solo líneas con un campo menor que el umbral del archivo CSV

Utilice awk para seleccionar solo líneas con un campo menor que el umbral del archivo CSV

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 awkcódigo integrado en una bashfunció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 awkcó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 $4sea 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 awkcomando 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 margincon un valor de 0.2al awkprograma.

Dentro del programa, establecerá el minvalor 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+marginel 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 rescoredy 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 helpfunción. Podría ser algo así. (El uso de basenamey $0probablemente 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 >&2imprimir en stderr y no en stdout.

Con helplo anterior, use una forma semi estándar. No hace falta -abco --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.

información relacionada