Пробуждение для вычисления среднего значения, игнорирующего выбросы - для сегментированного файла
У меня есть файл данных (data.txt), который выглядит так,
0.01667 20.53
0.01667 6.35
0.01667 6.94
0.01667 7.07
0.01667 8.06
0.01667 8.10
0.01667 8.25
0.01667 8.71
0.01667 9.31
0.02500 20.19
0.02500 6.35
0.02500 6.92
0.02500 7.07
0.02500 8.08
0.02500 8.09
0.02500 8.24
0.02500 8.70
0.02500 9.26
0.03333 19.89
0.03333 6.33
0.03333 6.90
0.03333 7.07
0.03333 8.07
0.03333 8.09
0.03333 8.22
0.03333 8.70
0.03333 9.22
0.04167 19.65
0.04167 6.34
0.04167 6.87
0.04167 7.07
0.04167 8.03
0.04167 8.08
0.04167 8.19
0.04167 8.69
0.04167 9.19
0.05000 19.40
0.05000 6.32
0.05000 6.85
0.05000 7.06
0.05000 8.02
0.05000 8.09
0.05000 8.16
0.05000 8.71
0.05000 9.15
0.05833 19.12
0.05833 6.29
0.05833 6.84
0.05833 7.04
0.05833 8.01
0.05833 8.11
0.05833 8.16
0.05833 8.71
0.05833 9.11
0.06667 18.84
0.06667 6.29
0.06667 6.82
0.06667 7.05
0.06667 7.98
0.06667 8.11
0.06667 8.14
0.06667 8.71
0.06667 9.06
0.07500 18.57
0.07500 6.29
0.07500 6.80
0.07500 7.06
0.07500 7.97
0.07500 8.10
0.07500 8.13
0.07500 8.71
0.07500 9.02
Столбец 1 - это время, когда были проведены измерения в столбце 2. Мне нужно усреднить значения в столбце 2 для каждого времени, указанного в столбце 1, и вывести значение времени и среднее значение для этого времени. Я могу сделать Avearge, используя следующий код awk
awk '{if($1<0)$1=0}
{
sum[$1]+=$2
cnt[$1]++
}
END {
# print "Name" "\t" "sum" "\t" "cnt" "\t" "avg"
for (i in sum)
printf "%8.5f %6.2f %6d %6.3f\n", i, sum[i], cnt[i], sum[i]/cnt[i]
}' data.txt | sort -n -k1 > avgFile.txt
Обратите внимание, что я также вывожу некоторые другие вещи только для того, чтобы проверить, правильно ли я поступаю. Поскольку вы видите, что данные для каждого временного интервала содержат выбросы, мне нужно удалить их. Я пытаюсь выбрать, скажем, данные, собранные в 0,01667, в некоторый файл temp.txt, и у меня есть следующий код awk, который правильно удаляет выброс
awk 'BEGIN{CNT=0} {ROW[CNT]=$0;DATA[CNT]=$2;
TOTAL+=$2;CNT+=1;} END{for (i = 0;i < NR; i++){if ((sqrt((DATA[i]-(TOTAL/NR))^2))<((TOTAL/NR)*30/100))
{print ROW[i] ;}}}' temp.txt
Но мне нужно сделать это в исходном коде, чтобы я удалял этот выброс каждый раз, когда он есть, прежде чем вычислять среднее значение в столбце 2.
Любая помощь будет высоко оценена.
2 ответа
Это вычисляет средние значения, затем удаляет выбросы, а затем пересчитывает средние значения после удаления выбросов:
$ cat tst.awk
{
vals[$1][$2]
sum[$1] += $2
cnt[$1]++
}
END {
div = 0.3
for (time in vals) {
ave = sum[time] / cnt[time]
low = ave * (1 - div)
high = ave * (1 + div)
for (val in vals[time]) {
if ( (val < low) || (val > high) ) {
print "Deleting outlier", time, val | "cat>&2"
sum[time] -= val
cnt[time]--
}
}
}
for (time in vals) {
ave = (cnt[time] > 0 ? sum[time] / cnt[time] : 0)
print time, sum[time], cnt[time], ave
}
}
,
$ awk -f tst.awk file
0.05000 56.04 7 8.00571
0.07500 62.08 8 7.76
0.04167 56.12 7 8.01714
0.03333 56.27 7 8.03857
0.01667 56.44 7 8.06286
0.06667 55.87 7 7.98143
0.02500 56.36 7 8.05143
0.05833 55.98 7 7.99714
Deleting outlier 0.05000 6.32
Deleting outlier 0.05000 19.40
Deleting outlier 0.07500 18.57
Deleting outlier 0.04167 19.65
Deleting outlier 0.04167 6.34
Deleting outlier 0.03333 6.33
Deleting outlier 0.03333 19.89
Deleting outlier 0.01667 6.35
Deleting outlier 0.01667 20.53
Deleting outlier 0.06667 6.29
Deleting outlier 0.06667 18.84
Deleting outlier 0.02500 20.19
Deleting outlier 0.02500 6.35
Deleting outlier 0.05833 6.29
Deleting outlier 0.05833 19.12
Это то, что вы искали? Он использует GNU awk для настоящих 2-D массивов.
Хорошо, я сказал вам, когда у меня будет время, я напишу быстрый скрипт (оказалось, он не такой быстрый). Он удаляет выбросы и возвращает среднее значение очищенного массива. Вы можете реализовать стандартное отклонение, если это необходимо. Дайте мне знать, если у вас есть вопросы.
#!/bin/bash
## generic error/usage function
function usage {
local ecode=${2:-0}
test -n "$1" && printf "\n %s\n" "$1" >&2
cat >&2 << helpMessage
usage: ${0//*\//} datafile
This script will process a 2-column datafile to provide average,
mean and std. deviation for each time group of data while removing
outlying data from the calculation. The datafile format:
time value
0.01667 20.53 <- outlier
0.01667 6.35
0.01667 6.94
...
Options:
-h | --help program help (this file)
helpMessage
exit $ecode;
}
## function to calculate average of arguments
function average {
local sum=0
declare -i count=0
for n in $@; do
sum=$( printf "scale=6; %s+%s\n" "$sum" "$n" | bc )
((count++))
done
avg=$( printf "scale=6; %s/%s\n" "$sum" "$count" | bc )
printf "%s\n" "$avg"
}
## function to examine arguments a remove any outlier
# that is greater than 4 from the average.
# values without the outlier are returned to command line
function rmoutlier {
local avg=$(average $@)
local diff=0
for i in $@; do
diff=$( printf "scale=6; %s-%s\n" "$i" "$avg" | bc )
[ "${diff:0:1}" = '-' ] && diff="${diff:1}" # quick absolute value hack
[ "${diff:0:1}" = '.' ] && diff=0 # set any fractional 0
if [ $((${diff//.*/})) -lt 4 ]; then
clean+=( $i ) # if whole num diff < 4, keep
else
echo "->outlier: $i" >&2 # print outlier to stderr
fi
done
echo ${clean[@]} # return array
}
## respond to -h or --help
test "${1:1}" = 'h' || test "${1:2}" = 'help' && usage
## set variables
dfn="${1:-dat/outlier.dat}" # datafile (default dat/outlier.dat)
declare -a tmp # temporary array holding data for given time
ptime=0 # variable holding previous time (flag for 1st line)
## validate input filename
test -r "$dfn" || usage "Error: invalid input. File '$dfn' not found" 1
while read -r time data || [ -n "$data" ]; do # read all lines of data
if [ "$ptime" = 0 ] || [ "$ptime" = "$time" ]; then # if no change in time
tmp+=( $data ) # fill array with data
else
echo " time: $ptime data : '${tmp[@]}'" >&2 # output array to stderr
## process data
clean=( $(rmoutlier ${tmp[@]} ) ) # remove outlier
echo " time: $ptime clean: '${clean[@]}'" >&2 # output clean array
avgclean=$( average ${clean[@]} ) # average clean array
printf " avgclean: %s\n\n" "$avgclean" >&2 # output avg of clean array
unset tmp # reset variables for next time
unset clean
unset avgclean
tmp+=( $data ) # read first value for next time set
fi
ptime="$time" # save previous time for comparison
done <"$dfn"
## process final time block
echo " time: $ptime data : '${tmp[@]}'" >&2
## process data
clean=( $(rmoutlier ${tmp[@]} ) )
echo " time: $ptime clean: '${clean[@]}'" >&2
avgclean=$( average ${clean[@]} )
printf " avgclean: %s\n\n" "$avgclean" >&2
unset tmp
unset clean
unset avgclean
exit 0
использование:
./outlier.sh datafile
выход:
$ ./outlier.sh dat/outlier.dat
time: 0.01667 data : '20.53 6.35 6.94 7.07 8.06 8.10 8.25 8.71 9.31'
->outlier: 20.53
time: 0.01667 clean: '6.35 6.94 7.07 8.06 8.10 8.25 8.71 9.31'
avgclean: 7.848750
time: 0.02500 data : '20.19 6.35 6.92 7.07 8.08 8.09 8.24 8.70 9.26'
->outlier: 20.19
time: 0.02500 clean: '6.35 6.92 7.07 8.08 8.09 8.24 8.70 9.26'
avgclean: 7.838750
time: 0.03333 data : '19.89 6.33 6.90 7.07 8.07 8.09 8.22 8.70 9.22'
->outlier: 19.89
time: 0.03333 clean: '6.33 6.90 7.07 8.07 8.09 8.22 8.70 9.22'
avgclean: 7.825000
time: 0.04167 data : '19.65 6.34 6.87 7.07 8.03 8.08 8.19 8.69 9.19'
->outlier: 19.65
time: 0.04167 clean: '6.34 6.87 7.07 8.03 8.08 8.19 8.69 9.19'
avgclean: 7.807500
time: 0.05000 data : '19.40 6.32 6.85 7.06 8.02 8.09 8.16 8.71 9.15'
->outlier: 19.40
time: 0.05000 clean: '6.32 6.85 7.06 8.02 8.09 8.16 8.71 9.15'
avgclean: 7.795000
time: 0.05833 data : '19.12 6.29 6.84 7.04 8.01 8.11 8.16 8.71 9.11'
->outlier: 19.12
time: 0.05833 clean: '6.29 6.84 7.04 8.01 8.11 8.16 8.71 9.11'
avgclean: 7.783750
time: 0.06667 data : '18.84 6.29 6.82 7.05 7.98 8.11 8.14 8.71 9.06'
->outlier: 18.84
time: 0.06667 clean: '6.29 6.82 7.05 7.98 8.11 8.14 8.71 9.06'
avgclean: 7.770000
time: 0.07500 data : '18.57 6.29 6.80 7.06 7.97 8.10 8.13 8.71 9.02'
->outlier: 18.57
time: 0.07500 clean: '6.29 6.80 7.06 7.97 8.10 8.13 8.71 9.02'
avgclean: 7.760000
Приложение: время записи и среднее в файл
Ниже приведен обновленный фрагмент сценария, который выведет time
а также clean average
в файл (по умолчанию: dat/outlier.out). Только строки, содержащие "имя выходного файла" ofn
были изменены. (вы можете передать любое выходное имя файла в сценарий в качестве второго аргумента) usage:
было бы: outlier.sh input_file output_file
:
## set variables
dfn="${1:-dat/outlier.dat}" # datafile (default dat/outlier.dat)
ofn="${2:-dat/outlier.out}" # output file (default dat/outlier.out)
declare -a tmp # temporary array holding data for given time
ptime=0 # variable holding previous time (flag for 1st line)
:> "$ofn" # truncate output file
## validate input filename
test -r "$dfn" || usage "Error: invalid input. File '$dfn' not found" 1
while read -r time data || [ -n "$data" ]; do # read all lines of data
if [ "$ptime" = 0 ] || [ "$ptime" = "$time" ]; then # if no change in time
tmp+=( $data ) # fill array with data
else
echo " time: $ptime data : '${tmp[@]}'" >&2 # output array to stderr
printf " time: %s " "$ptime" >>"$ofn" # output array to file
## process data
clean=( $(rmoutlier ${tmp[@]} ) ) # remove outlier
echo "time: $ptime clean: '${clean[@]}'" >&2 # output clean array
avgclean=$( average ${clean[@]} ) # average clean array
printf " avgclean: %s\n\n" "$avgclean" >&2 # output avg of clean array
printf " avgclean: %s\n" "$avgclean" >>"$ofn" # output avg of clean array to file
unset tmp # reset variables for next time
unset clean
unset avgclean
tmp+=( $data ) # read first value for next time set
fi
ptime="$time" # save previous time for comparison
done <"$dfn"
outlier.out:
time: 0.01667 avgclean: 7.848750
time: 0.02500 avgclean: 7.838750
time: 0.03333 avgclean: 7.825000
time: 0.04167 avgclean: 7.807500
time: 0.05000 avgclean: 7.795000
time: 0.05833 avgclean: 7.783750
time: 0.06667 avgclean: 7.770000