Umgang mit der Nachbearbeitung einer mehrspaltigen CSV-Datei mit vielen (über 10.000) Zeilen:
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
Was ich bisher getan habe
Ich verwende folgenden awk
Code, integriert in eine bash
Funktion, die 1% (oberste Zeilen) aus der CSV nimmt und als neue CSV (enthalten, also mit reduzierter Zeilenanzahl) speichert:
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
}
Was ich jetzt tun möchte
Wie kann ich meinen Code ändern, awk
um einen selektiveren Filter auf die ursprüngliche CSV-Datei anzuwenden? Beispielsweise um die Daten basierend auf dem Wert (Gleitkommazahl) der 4. Spalte (in dG(rescored)
) zu filtern?
Beispielsweise muss ich den niedrigsten Wert (der immer in der zweiten Zeile steht, minForth = 0.421573
) als Referenz verwenden und alle Zeilen aus der CSV-Datei drucken, die $4
kleiner als ein ausgewählter Schwellenwert sind (sagen wir 20 % über minForth
):
$4<=(1+0.2)*min))'
Antwort1
Wenn Sie einfach alle Zeilen filtern möchten, bei denen das 4. Feld unter einem Schwellenwert liegt, awk
würde der folgende Befehl funktionieren:
awk -F',' -v margin=0.2 'FNR==2 {min=$4} FNR>1&&($4<=(1+margin)*min)' input.csv
oder, wenn Sie den Header auch in der gefilterten Ausgabe haben möchten:
awk -F',' -v margin=0.2 'FNR==2 {min=$4} FNR==1||($4<=(1+margin)*min)' input.csv
Dadurch wird der Feldtrenner auf gesetzt ,
(beachten Sie jedoch, dass es sich bei Ihrer Datei nicht um eine standardmäßige CSV-Datei handelt, da die Felder durch zusätzliche Leerzeichen getrennt sind) und eine Variable margin
mit dem Wert 0.2
in das awk
Programm importiert.
min
Innerhalb des Programms wird der Variablenwert auf den Wert in der 4. Spalte gesetzt, wenn wir uns in Zeile 2 befinden ( FNR==2
). Die aktuelle Zeile wird dann nur gedruckt, wenn wir uns entweder in Zeile 1 befinden (der Kopfzeile – falls gewünscht) oder wenn wir uns im Datenteil der Datei befinden und das 4. Feld kleiner als 1+margin
mal dem Mindestwert ist.
Antwort2
Dies ist ein ziemlich ausführliches Skript. Verwenden Sie keine Abkürzungen und drucken Sie keine Informationen aus stderr
. Was den sh-Teil betrifft, könnten Sie normalerweise Optionen hinzufügen, um die Werte „Globals“ oben festzulegen, sodass Sie zusätzlich zu den Argumenten auch mit Optionen aufrufen können. Beispiel:
my_script --max-factor 0.15 -p 20 --out-file foo.csv *.csv
Filtern Sie also nach rescored
und nach den Prozentsatz der Zeilen. Die ausführlichen Teile können offensichtlich gelöscht werden.
#!/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
Befehlszeilenoptionen
Gemäß Anfrage in den Kommentaren.
Um die Optionen zu erhalten, gibt es zahlreiche Möglichkeiten. Am einfachsten wären Positionsargumente. Zum Beispiel:
Usage: script percent margin <files ...>
Im Skript würde man dann sagen:
percent=$1
margin=$2
shift
shift
... loop files ...
Wenn man es etwas ausgefallener/flexibler haben möchte, könnte man so etwas machen;
Schreiben Sie zuerst eine help
Funktion. Es könnte etwa so aussehen. (Die Verwendung von basename
und $0
kann wahrscheinlich besprochen werden):
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"
}
Wobei man es normalerweise mit „as“ aufruft, print_help >&2
um auf stderr und nicht auf stdout zu drucken.
Oben help
wird eine halbstandardmäßige Methode verwendet. Es werden keine -abc
oder verwendet --foo=123
, aber jede Option und jedes Argument muss durch Leerzeichen getrennt sein.
Optional, kein Wortspiel beabsichtigt, schauen Sie sich Beiträge wie an
Eine einfache Möglichkeit für den Rest des Skripts mit einigen naiven Fehlerprüfungen könnte dann sein:
# 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
Man könnte mehr Validierung der Optionen implementieren, also sicherstellen, dass es sich um Zahlen usw. handelt.