Как вычесть значения определенного значения строки из всех других значений строки? - PullRequest
0 голосов
/ 10 июля 2019

Мой текущий рабочий файл выглядит так:

ID   Time   A_in   Time  B_in  Time  C_in
Ax   0.1    10     0.1   15    0.1   45  
By   0.2    12     0.2   35    0.2   30  
Cz   0.3    20     0.3   20    0.3   15  
Fr   0.4    35     0.4   15    0.4   05  
Exp  0.5    10     0.5   25    0.5   10

Мои интересующие столбцы - это те, которые имеют заголовок "_in".В этих столбцах я хочу вычесть значения всех элементов Row из элемента строки, которые начинаются с идентификатора «Exp».Давайте рассмотрим столбец A_in, где значение строки "Exp" равно 10. Поэтому я хочу вычесть 10 из всех других элементов этого A_in столбца

Мой любительский код такой (я его знаю)глупо)

#This part is grabbing all the values in ```Exp``` row
Exp=$( awk 'BEGIN{OFS="\t";
            PROCINFO["sorted_in"] = "@val_num_asc"}
    FNR==1 { for (n=2;n<=NF;n++) { if ($n ~ /_GasOut$/) cols[$n]=n; }}
    /Exp/ {
           for (c in cols){
           shift = $cols[c]
           printf shift" "
           }
       }

        ' File.txt |paste -sd " ") 
Exp_array=($Exp)

z=1
for i in "${Exp_array[@]}"
do
z=$(echo 2+$z | bc -l)
Exp_point=$i
awk  -vd="$Exp_point" -vloop="$z" -v  '
            BEGIN{OFS="\t";
            PROCINFO["sorted_in"] = "@val_num_asc"}
            function abs(x) {return x<0?-x:x}
            FNR==1 { for (n=2;n<=NF;n++) { if ($n ~ /_GasOut$/) cols[$n]=n; }}
        NR>2{
            $loop=abs($loop-d); print
            }
         ' File.txt
done

Мой первый желаемый результат - это

ID   Time   A_in   Time  B_in  Time  C_in
Ax   0.1    0.0    0.1   10    0.1   35  
By   0.2    02     0.2   10    0.2   20  
Cz   0.3    10     0.3   05    0.3   05  
Fr   0.4    25     0.4   10    0.4   05  
Exp  0.5    0.0    0.5   0.0   0.5  0.0

Теперь из каждого "_in" столбца я хочу найти соответствующий идентификатор 2 наименьших значений.Итак, мой второй желаемый результат -

A_in   B_in  C_in
Ax     Cz    Cz 
By     Exp   Fr 
Exp          Exp

Ответы [ 3 ]

2 голосов
/ 10 июля 2019

Perl на помощь!

#!/usr/bin/perl
use warnings;
use strict;
use feature qw{ say };

@ARGV = (@ARGV[0, 0]);  # Read the input file twice.

my @header = split ' ', <>;
my @in = grep $header[$_] =~ /_in$/, 0 .. $#header;
$_ = <> until eof;
my @exp = split;

my @min;
<>;
while (<>) {
    my @F = split;
    for my $i (@in) {
        $F[$i] = abs($F[$i] - $exp[$i]);
        @{ $min[$i] }[0, 1]
            = sort { $a->[0] <=> $b->[0] }
                   [$F[$i], $F[0]], grep defined, @{ $min[$i] // [] }
            unless eof;
    }
    say join "\t", @F;
}

print "\n";
say join "\t", @header[@in];
for my $index (0, 1) {
    for my $i (@in) {
        next unless $header[$i] =~ /_in$/;
        print $min[$i][$index][1], "\t";
    }
    print "\n";
}

Он читает файл дважды. При первом чтении он просто запоминает первую строку как массив @header, а последнюю строку как массив @exp.

Во втором чтении вычитается соответствующее значение exp из каждого столбца _in. Он также хранит два наименьших числа в массиве @min в позиции, соответствующей позиции столбца.

Форматирование чисел (т. Е. 0.0 вместо 0 и 02 вместо 2), оставленных в качестве упражнения для читателя. То же самое с перенаправлением вывода на несколько разных файлов.

1 голос
/ 10 июля 2019

После некоторого веселья и часа или двух я написал эту мерзость:

cat <<EOF >file
ID   Time   A_in   Time  B_in  Time  C_in
Ax   0.1    10     0.1   15    0.1   45  
By   0.2    12     0.2   35    0.2   30  
Cz   0.3    20     0.3   20    0.3   15  
Fr   0.4    35     0.4   15    0.4   05  
Exp  0.5    10     0.5   25    0.5   10
EOF
# fix stackoverflow formatting
# input file should be separated with tabs
<file tr -s ' ' | tr ' ' '\t' > file2
mv file2 inputfile

# read headers to an array
IFS=$'\t' read -r -a hdrs < <(head -n1 inputfile)

# exp line read into an array
IFS=$'\t' read -r -a exps < <(grep -m1 $'^Exp\t' inputfile)

# column count
colcnt="${#hdrs[@]}"
if [ "$colcnt" -eq 0 ]; then 
    echo >&2 "ERROR - must be at least one column"
    exit 1
fi

# numbers of those columns which headers have _in suffix
incolnums=$(
    paste <(
        printf "%s\n" "${hdrs[@]}"
    ) <(
        # puff, the numbers will start from zero cause bash indexes arrays from zero
        # but `cut` indexes fields from 1, so.. just keep in mind it's from 0
        seq 0 $((colcnt - 1))
    ) |
    grep $'_in\t' |
    cut -f2
)

# read the input file
{
    # preserve header line
    IFS= read -r hdrline
    ( IFS=$'\t'; printf "%s\n" "$hdrline" )

    # ok. read the file field by field
    # I think we could awk here
    while IFS=$'\t' read -a vals; do

        # for each column number with _in suffix
        while IFS= read -r incolnum; do

            # update the column value
            # I use bc for float calculations
            vals[$incolnum]=$(bc <<-EOF
                define abs(i) {
                    if (i < 0) return (-i)
                    return (i)
                }
                scale=2
                abs(${vals[$incolnum]} - ${exps[$incolnum]})
EOF
            )

        done <<<"$incolnums"

        # output the line
        ( IFS=$'\t'; printf "%s\n" "${vals[*]}" )

    done

} < inputfile > MyFirstDesiredOutcomeIsThis.txt

# ok so, first part done

{
    # output headers names with _in suffix
    printf "%s\n" "${hdrs[@]}" | 
    grep '_in$' |
    tr '\n' '\t' |
    # omg, fix tr, so stupid
    sed 's/\t$/\n/'

    # puff
    # output the corresponding ID of 2 smallest values of the specified column number
    # @arg: $1 column number
    tmpf() {
        # remove header line
        <MyFirstDesiredOutcomeIsThis.txt tail -n+2 |
        # extract only this column
        cut -f$(($1 + 1)) |
        # unique numeric sort and extract two smallest values
        sort -n -u | head -n2 |
        # now, well, extract the id's that match the numbers
        # append numbers with tab (to match the separator)
        # suffix numbers with dollar (to match end of line)
        sed 's/^/\t/; s/$/$/;' |
        # how good is grep at buffering(!)
        grep -f /dev/stdin <(
            <MyFirstDesiredOutcomeIsThis.txt tail -n+2 |
            cut -f1,$(($1 + 1))
        ) |
        # extract numbers only
        cut -f1
    }

    # the following is something like foldr $'\t' $(tmpf ...) for each $incolnums
    # we need to buffer here, we are joining the output column-wise
    output=""
    while IFS= read -r incolnum; do
        output=$(<<<$output paste - <(tmpf "$incolnum"))
    done <<<"$incolnums"

    # because with start with empty $output, paste inserts leading tabs
    # remove them ... and finally output $output
    <<<"$output" cut -f2-

}  > MySecondDesiredOutcomeIs.txt

# fix formatting to post it on stackoverflow
# files have tabs, and column will output them with space
# which is just enough
echo '==> MyFirstDesiredOutcomeIsThis.txt <=='
column -t -s$'\t' MyFirstDesiredOutcomeIsThis.txt
echo
echo '==> MySecondDesiredOutcomeIs.txt <=='
column -t -s$'\t' MySecondDesiredOutcomeIs.txt

Скрипт выведет:

==> MyFirstDesiredOutcomeIsThis.txt <==
ID   Time  A_in  Time  B_in  Time  C_in
Ax   0.1   0     0.1   10    0.1   35
By   0.2   2     0.2   10    0.2   20
Cz   0.3   10    0.3   5     0.3   5
Fr   0.4   25    0.4   10    0.4   5
Exp  0.5   0     0.5   0     0.5   0

==> MySecondDesiredOutcomeIs.txt <==
A_in  B_in  C_in
Ax    Cz    Cz
By    Exp   Fr
Exp         Exp

Написано и протестировано на tutorialspoint .

Я использую bash и core- / more-utils для работы с файлом. Сначала я определяю количество столбцов, заканчивающихся суффиксом _in. Затем я выбираю значение, сохраненное в строке Exp.

Затем я просто читаю файл строка за строкой, поле за полем, и для каждого поля, у которого есть номер столбца, заголовок которого заканчивается суффиксом _in, я вычитаю значение поля со значением поля из exp линия. Я думаю, что эта часть должна быть самой медленной (я использую обычный while IFS=$'\t' read -r -a vals), но умный awk сценарий может ускорить этот процесс. Это генерирует ваш «первый желаемый результат», как вы его назвали.

Затем мне нужно вывести только имена заголовков, заканчивающиеся суффиксом _in. Затем для каждого номера столбца, который заканчивается суффиксом _in, мне нужно определить 2 наименьших значения в столбце. Я использую обычный sort -n -u | head -n2. Тогда это немного сложно. Мне нужно извлечь идентификаторы, которые имеют одно из соответствующих 2 наименьших значений в таком столбце. Это работа для grep -f. Я подготовил правильное регулярное выражение для ввода, используя sed и позволил grep -f /dev/stdin выполнить работу по фильтрации.

0 голосов
/ 11 июля 2019

Пожалуйста, задавайте только 1 вопрос за раз. Вот как сделать первое, о чем вы спросили:

$ cat tst.awk
BEGIN   { OFS="\t" }
NR==FNR { if ($1=="Exp") split($0,exps); next }
FNR==1  { $1=$1; print; next }
{
    for (i=1; i<=NF; i++) {
        val = ( (i-1) % 2 ? $i : exps[i] - $i )
        printf "%s%s", (val < 0 ? -val : val), (i<NF ? OFS : ORS)
    }
}

$ awk -f tst.awk file file
ID      Time    A_in    Time    B_in    Time    C_in
0       0.1     0       0.1     10      0.1     35
0       0.2     2       0.2     10      0.2     20
0       0.3     10      0.3     5       0.3     5
0       0.4     25      0.4     10      0.4     5
0       0.5     0       0.5     0       0.5     0

Вышеописанное будет работать эффективно и надежно, используя любой awk в любой оболочке на каждом компьютере UNIX.

Если после прочтения этого перечитав предыдущие ответы на awk, которые вы получили и просмотрев справочную страницу awk, вам все еще нужна помощь по второму вопросу, о котором вы спрашивали, тогда задайте новый отдельный вопрос только об этом.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...