unire correttamente due file in base a 2 colonne in comune

Ho due file che sto cercando di unire / unire in base alle colonne 1 e 2 . Sembrano qualcosa del genere, con file1 ( 58210 linee) molto più corto di file2 ( 815530 linee) e mi piacerebbe trovare l’intersezione di questi due file in base ai campi 1 e 2 come indice:

file1 :

 2L 25753 33158 2L 28813 33158 2L 31003 33158 2L 31077 33161 2L 31279 33161 3L 32124 45339 3L 33256 45339 ... 

file2 :

 2L 20242 0.5 0.307692307692308 2L 22141 0.32258064516129 0.692307692307692 2L 24439 0.413793103448276 0.625 2L 24710 0.371428571428571 0.631578947368421 2L 25753 0.967741935483871 0.869565217391304 2L 28813 0.181818181818182 0.692307692307692 2L 31003 0.36 0.666666666666667 2L 31077 0.611111111111111 0.931034482758621 2L 31279 0.75 1 3L 32124 0.558823529411765 0.857142857142857 3L 33256 0.769230769230769 0.90625 ... 

Ho usato il seguente paio di comandi ma ho trovato un numero diverso di righe:

 awk 'FNR==NR{a[$1$2]=$3;next} {if($1$2 in a) print}' file1 file2 | wc -l awk 'FNR==NR{a[$1$2]=$3;next} {if($1$2 in a) print}' file2 file1 | wc -l 

Non sono sicuro del motivo per cui ciò accada, e ho provato a ordinare prima del confronto, nel caso avessi due linee doppie (basate sulle colonne 1 e 2 ) in uno dei due file, ma non sembra essere d’aiuto. (Anche eventuali approfondimenti sul perché questo è così apprezzato)

Come posso semplicemente unire i file in modo che solo le righe di file2 che hanno le colonne corrispondenti 1 e 2 in file1 vengano stampate, con la colonna 3 di file1 aggiunta, per apparire qualcosa del genere:

 2L 25753 0.967741935483871 0.869565217391304 33158 2L 28813 0.181818181818182 0.692307692307692 33158 2L 31003 0.36 0.666666666666667 33158 2L 31077 0.611111111111111 0.931034482758621 33161 2L 31279 0.75 1 33161 3L 32124 0.558823529411765 0.857142857142857 45339 3L 33256 0.769230769230769 0.90625 45339 

 awk 'NR==FNR{a[$1,$2]=$3;next} ($1,$2) in a{print $0, a[$1,$2]}' file1 file2 

Guarda:

 $ cat file1 2L 5753 33158 2L 8813 33158 2L 7885 33159 2L 1279 33159 2L 5095 33158 $ $ cat file2 2L 8813 0.6 1.2 2L 5762 0.4 0.5 2L 1279 0.5 0.9 $ $ awk 'NR==FNR{a[$1,$2]=$3;next} ($1,$2) in a{print $0, a[$1,$2]}' file1 file2 2L 8813 0.6 1.2 33158 2L 1279 0.5 0.9 33159 $ 

Se questo non è ciò che desideri, ti preghiamo di chiarire e pubblicare alcuni esempi di input / output più rappresentativi.

Versione commentata del codice precedente per fornire una spiegazione richiesta:

 awk ' # START SCRIPT # IF the number of records read so far across all files is equal # to the number of records read so far in the current file, a # condition which can only be true for the first file read, THEN NR==FNR { # populate array "a" such that the value indexed by the first # 2 fields from this record in file1 is the value of the third # field from the first file. a[$1,$2]=$3 # Move on to the next record so we don't do any processing intended # for records from the second file. This is like an "else" for the # NR==FNR condition. next } # END THEN # We only reach this part of the code if the above condition is false, # ie if the current record is from file2, not from file1. # IF the array index constructed from the first 2 fields of the current # record exist in array a, as would occur if these same values existed # in file1, THEN ($1,$2) in a { # print the current record from file2 followed by the value from file1 # that occurred at field 3 of the record that had the same values for # field 1 and field 2 in file1 as the current record from file2. print $0, a[$1,$2] } # END THEN ' file1 file2 # END SCRIPT 

Spero possa aiutare.

Se ti piace unire i file riga per riga, usa questo comando:

 join -o 1.2,1.3,2.4,2.5,1.4 <(cat -n file1) <(cat -n file2) 

Come hai aggiornato la domanda:

 join -o 1.1,2.2,2.3,1.2 <(sed 's/[[:space:]]\+/@/' file1|sort) \ <(sed 's/[[:space:]]\+/@/' file2|sort)|sed 's/@/\t/' 

Sostituisci prima il primo delimitatore in ogni riga con un carattere non spaziale e ordina entrambi i file di input. Quindi utilizzare join per rendere effettivo il join. Filtra la sua uscita per sostituire il carattere non spaziale con lo spazio.

Questo è l'output dei file come nella domanda:

 xyz]$ join -o 1.1,2.2,2.3,1.2 <(sed 's/[[:space:]]\+/@/' file1|sort) \ <(sed 's/[[:space:]]\+/@/' file2|sort)|sed 's/@/\t/' 2L 25753 0.967741935483871 0.869565217391304 33158 2L 28813 0.181818181818182 0.692307692307692 33158 2L 31003 0.36 0.666666666666667 33158 2L 31077 0.611111111111111 0.931034482758621 33161 2L 31279 0.75 1 33161 3L 32124 0.558823529411765 0.857142857142857 45339 3L 33256 0.769230769230769 0.90625 45339 

È ansible utilizzare il comando join ma è necessario creare un campo join singolo in ciascuna tabella dati. Supponendo di avere valori diversi da 2L nella colonna 1, questo codice dovrebbe funzionare indipendentemente dalla natura ordinata o non ordinata dei due file di input:

 tmp=${TMPDIR:-/tmp}/tmp.$$ trap "rm -f $tmp.?; exit 1" 0 1 2 3 13 15 awk '{print $1 ":" $2, $0}' file1 | sort > $tmp.1 awk '{print $1 ":" $2, $0}' file2 | sort > $tmp.2 join -o 2.2,2.3,2.4,2.5,1.4 $tmp.1 $tmp.2 rm -f $tmp.? trap 0 

Se si dispone di bash e di “sostituzione del processo” o se si sa che i dati sono già ordinati in modo appropriato, è ansible semplificare l’elaborazione.


Non sono del tutto sicuro del motivo per cui il tuo codice non funzionava, ma probabilmente userei a[$1,$2] per gli indici; ti darà meno problemi se alcuni dei tuoi valori di colonna 1 sono puri numerici e possono quindi essere confusi quando si concatenano le colonne 1 e 2. Ecco perché gli script di awk “creazione chiavi” utilizzavano due punti tra i campi.


Con file di dati revisionati come mostrato:

file1

 2L 5753 33158 2L 8813 33158 2L 7885 33158 2L 7885 33159 2L 1279 33158 2L 5095 33158 2L 3256 33158 2L 5372 33158 2L 7088 33161 2L 5762 33161 

file2

 2L 5095 0.666666666666667 1 2L 5372 0.5 0.925925925925926 2L 5762 0.434782608695652 0.580645161290323 2L 5904 0.571428571428571 0.869565217391304 2L 5974 0.434782608695652 0.694444444444444 2L 6353 0.785714285714286 0.84 2L 7088 0.590909090909091 0.733333333333333 2L 7885 0.714285714285714 0.864864864864865 2L 7902 0.642857142857143 0.810810810810811 2L 8263 0.833333333333333 0.787878787878788 

(Invariato dalla domanda).

Produzione

 2L 5095 0.666666666666667 1 33158 2L 5372 0.5 0.925925925925926 33158 2L 5762 0.434782608695652 0.580645161290323 33161 2L 7088 0.590909090909091 0.733333333333333 33161 2L 7885 0.714285714285714 0.864864864864865 33158 2L 7885 0.714285714285714 0.864864864864865 33159