首页 > 解决方案 > 是否有任何命令可以在 Linux 中基于多列进行模糊匹配

问题描述

我有两个 csv 文件。文件 1

D,FNAME,MNAME,LNAME,GENDER,DOB,snapshot
2,66M,J,Rock,F,1995,201211.0
3,David,HM,Lee,M,,201211.0
6,66M,,Rock,F,,201211.0
0,David,H M,Lee,,1990,201211.0
3,Marc,H,Robert,M,2000,201211.0
6,Marc,M,Robert,M,,201211.0
6,Marc,MS,Robert,M,2000,201211.0
3,David,M,Lee,,1990,201211.0
5,Paul,ABC,Row,F,2008,201211.0
3,Paul,ACB,Row,,,201211.0
4,David,,Lee,,1990,201211.0
4,66,J,Rock,,1995,201211.0

文件 2

PID,FNAME,MNAME,LNAME,GENDER,DOB
S2,66M,J,Rock,F,1995
S3,David,HM,Lee,M,1990
S0,Marc,HM,Robert,M,2000
S1,Marc,MS,Robert,M,2000
S6,Paul,,Row,M,2008
S7,Sam,O,Baby,F,2018

我想要做的是使用人行横道文件,文件 2,根据列 FNAME、MNAME、LNAME、GENDER 和 DOB 来取消文件 1 中的那些观察的 PID。因为File 1的观察中对应的信息不完整,所以我正在考虑使用模糊匹配尽可能多的回退他们的PID(当然要考虑电平精度)。例如,文件 1 中 FNAME "Paul" 和 LNAME "Row" 的观测值应分配相同的 PID,因为文件 2 中只有一个类似的观测值。但对于 FNAME "Marc" 和 LNAME "Robert" 的观测值,Marc,MS,Robert,M,2000,201211.0应分配 PID“S1”、Marc,H,Robert,M,2000,201211.0PID“S0”和Marc,M,Robert,M,,201211.0“S0”或“S1”。

由于我想在保持高精度的同时尽可能多地补偿文件 1 的 PID,因此我考虑了三个步骤。首先,使用命令确保当且仅当 FNAME、MNAME、LNAME、GENDER 和 DOB 中的这些信息都完全匹配时,才能为文件 1 中的观察分配一个 PID。输出应该是

D,FNAME,MNAME,LNAME,GENDER,DOB,snapshot,PID
2,66M,J,Rock,F,1995,201211.0,S2
3,David,HM,Lee,M,,201211.0,
6,66M,,Rock,F,,201211.0,
0,David,H M,Lee,,1990,201211.0,
3,Marc,H,Robert,M,2000,201211.0,
6,Marc,M,Robert,M,,201211.0,
6,Marc,MS,Robert,M,2000,201211.0,
3,David,M,Lee,,1990,201211.0,
5,Paul,ABC,Row,F,2008,201211.0,
3,Paul,ACB,Row,,,201211.0,
4,David,,Lee,,1990,201211.0,
4,66,J,Rock,,1995,201211.0,

接下来,编写另一个命令,以保证 DOB 信息完全相同时,对 FNAME、MNAME、LNAME、GENDER 使用模糊匹配来回退文件 1 的观测值的 PID,该 PID 在第一步中未识别。所以通过这两个步骤的输出应该是

D,FNAME,MNAME,LNAME,GENDER,DOB,snapshot,PID
2,66M,J,Rock,F,1995,201211.0,S2
3,David,HM,Lee,M,,201211.0,
6,66M,,Rock,F,,201211.0,
0,David,H M,Lee,,1990,201211.0,S3
3,Marc,H,Robert,M,2000,201211.0,S0
6,Marc,M,Robert,M,,201211.0,
6,Marc,MS,Robert,M,2000,201211.0,S1
3,David,M,Lee,,1990,201211.0,S3
5,Paul,ABC,Row,F,2008,201211.0,S6
3,Paul,ACB,Row,,,201211.0,
4,David,,Lee,,1990,201211.0,S3
4,66,J,Rock,,1995,201211.0,S2

在最后一步中,使用新命令对所有相关列进行模糊匹配,即 FNAME、MNAME、LNAME、GENDER 和 DOB,以补偿剩余观测值的 PID。所以最终的输出预计是

D,FNAME,MNAME,LNAME,GENDER,DOB,snapshot,PID
2,66M,J,Rock,F,1995,201211.0,S2
3,David,HM,Lee,M,,201211.0,S3
6,66M,,Rock,F,,201211.0,S2
0,David,H M,Lee,,1990,201211.0,S3
3,Marc,H,Robert,M,2000,201211.0,S0
6,Marc,M,Robert,M,,201211.0,S1
6,Marc,MS,Robert,M,2000,201211.0,S1
3,David,M,Lee,,1990,201211.0,S3
5,Paul,ABC,Row,F,2008,201211.0,S6
3,Paul,ACB,Row,,,201211.0,S6
4,David,,Lee,,1990,201211.0,S3
4,66,J,Rock,,1995,201211.0,S2

我需要保持文件 1 的观察顺序,所以它必须是一种左连接。因为我的原始数据大小约为 100Gb,所以我想使用 Linux 来处理我的问题。但我不知道如何通过awkLinux 中的任何其他命令完成最后两个步骤。有没有人可以帮我一个忙?谢谢你。

标签: linuxjoinawklevenshtein-distance

解决方案


这是使用 GNU awk 的一个镜头(PROCINFO["sorted_in"]用于选择最合适的候选人)。file2它对每个字段的 ' 字段值进行哈希处理并将 附加PID到该值,例如field[2]["66M"]="S2"和 为每条记录file1计数PID匹配的数量并打印具有最大计数的那个:

BEGIN {
    FS=OFS=","
    PROCINFO["sorted_in"]="@val_num_desc"
}
NR==FNR {                                                      # file2
    for(i=1;i<=6;i++)                                          # fields 1-6
        if($i!="") {
        field[i][$i]=field[i][$i] (field[i][$i]==""?"":OFS) $1 # attach PID to value
    }
    next
}
{                                                               # file1
        for(i=1;i<=6;i++) {                                     # fields 1-6
            if($i in field[i]) {                                # if value matches
                split(field[i][$i],t,FS)                        # get PIDs
                for(j in t) {                                   # and
                    matches[t[j]]++                             # increase PID counts
                }
            } else {                                            # if no value match
                for(j in field[i])                              # for all field values
                    if($i~j || j~$i)                            # "go fuzzy" :D
                        matches[field[i][j]]+=0.5               # fuzzy is half a match
            }
        }
        for(i in matches) {                                     # the best match first
            print $0,i
            delete matches
            break                                               # we only want the best match
        }
}

输出:

D,FNAME,MNAME,LNAME,GENDER,DOB,snapshot,PID
2,66M,J,Rock,F,1995,201211.0,S2
3,David,HM,Lee,M,,201211.0,S3
6,66M,,Rock,F,,201211.0,S2
0,David,H M,Lee,,1990,201211.0,S3
3,Marc,H,Robert,M,2000,201211.0,S0
6,Marc,M,Robert,M,,201211.0,S1
6,Marc,MS,Robert,M,2000,201211.0,S1
3,David,M,Lee,,1990,201211.0,S3
5,Paul,ABC,Row,F,2008,201211.0,S6
3,Paul,ACB,Row,,,201211.0,S6
4,David,,Lee,,1990,201211.0,S3
4,66,J,Rock,,1995,201211.0,S2

这里的“模糊匹配”是幼稚的if($i~j || j~$i),但可以随意用任何近似匹配算法替换它,例如互联网上有一些 Levenshtein 距离算法的实现。罗塞塔似乎有一个。

您没有提到有多大file2,但如果它超出了您的内存容量,您可能需要考虑以某种方式拆分文件。

更新:将字段映射file1file2字段的版本(如评论中所述):

BEGIN {
    FS=OFS=","
    PROCINFO["sorted_in"]="@val_num_desc"
    map[1]=1                                                   # map file1 fields to file2 fields
    map[2]=3
    map[3]=4
    map[4]=2
    map[5]=5
    map[7]=6
}
NR==FNR {                                                      # file2
    for(i=1;i<=6;i++)                                          # fields 1-6
        if($i!="") {
        field[i][$i]=field[i][$i] (field[i][$i]==""?"":OFS) $1 # attach PID to value
    }
    next
}
{                                                              # file1
    for(i in map) {
        if($i in field[map[i]]) {                              # if value matches
            split(field[map[i]][$i],t,FS)                      # get PIDs
            for(j in t) {                                      # and
                matches[t[j]]++                                # increase PID counts
            }
        } else {                                               # if no value match
            for(j in field[map[i]])                            # for all field values
                if($i~j || j~$i)                               # "go fuzzy" :D
                    matches[field[map[i]][j]]+=0.5             # fuzzy is half a match
        }
    }
    for(i in matches) {                                        # the best match first
        print $0,i
        delete matches
        break                                                  # we only want the best match
    }
}

推荐阅读