【问题标题】:Calculate frequency of combination of strings计算字符串组合的频率
【发布时间】:2018-10-31 14:18:15
【问题描述】:

我有一个数字标识符列表,后跟一个或多个字符串。我想为每个标识符计算关联字符串的最长组合,这些字符串也存在于另一个标识符中。然后还有与每个标识符关联的字符串数。

例如

1   AAA    BBBA    ACA    CCD    ABADA
2   AAA    ACA     CCD
3   AAB    BBAC    DDAD
4   AAA    ACA     DDAD   CCD
5   AAA    ACA     DDAD   CCD

会导致:

ID  Longest Combo  Number of strings  
1         3               5
2         3               3
3         1               3
4         4               4
5         4               4

为了清楚起见对结果的解释:

1 - AAA, ACA and CCD present in 2 so longest combo is 3.
2 - AAA, ACA and CCD present in 1 so longest combo is 3.
3 - DDAD present in 4 and 5 so longest combo is 1.
4 - AAA, ACA, DDAD and CCD present in 5 so longest combo is 4.
5 - AAA, ACA, DDAD and CCD present in 4 so longest combo is 4.

通常我可以尝试自己一起破解一些东西,但是用这个碰了壁 - 甚至不知道从哪里开始。达到这一点的分析是在 awk 中进行的,因此 bash 是理想的,但可能这是一项更适合 R 的工作?

我试图根据这个问题Frequency of each unique combination in data frame 格式化我的数据,但没有成功。

任何帮助将不胜感激。

大约有 3000 个标识符,每个标识符包含 1-15 个字符串。

【问题讨论】:

  • 哇。当你问一个问题时,你会问一个doozie,doncha? XD
  • 这个文件有多大?
  • 只找到一个字符串,所以最长的组合是 1。对不起,我应该澄清最长的组合是字符串的数量而不是标识符。我已将文件的长度添加到原始文件中!
  • 我想通了并删除了我的愚蠢问题。 :)

标签: r awk


【解决方案1】:

这是一个原型awk 解决方案。请注意,由于对称性,您只需要 i<j 的 (i,j) 条目。

$ awk 'NR==FNR {for(i=2;i<=NF;i++) a[$1,$i]; size=$1; next} 
               {for(i=$1+1;i<=size;i++) 
                  {for(j=2;j<=NF;j++) 
                     if((i,$j) in a) {c[$1,i]++; list[$1,i]=list[$1,i] FS $j}
                   if(max[$1]<c[$1,i]) {max[$1]=c[$1,i]; maxM[$1]=i}} 
                   if(maxM[$1]) print $1,maxM[$1],"->",max[$1],list[$1,maxM[$1]]}' file{,}

1 2 -> 3  AAA ACA CCD
2 4 -> 3  AAA ACA CCD
3 4 -> 1  DDAD
4 5 -> 4  AAA ACA DDAD CCD

可以再简化一点,我猜匹配长度可以从列表大小计算出来。

说明 双通道算法,将每个记录 id 的所有元素存储在查找表中;还设置大小。在第二遍中,比较具有更高索引的所有行的当前行。求交集大小并记录最大值、对应行和匹配字段。

【讨论】:

  • 感谢您的解决方案。我假设'->'之后的数字是对应于最长组合的字段?如果在您的示例中是这样,我认为它应该是 3(AAA、ACA 和 CCD 出现在 1 和 2 中,但 DDAD 都不出现在任何一个中)。
  • 是的,换句话说,它是给定行索引之间的公共术语的数量。例如,对于 (1,2) -> 3,第 1 行和第 2 行之间有 3 个术语(因此由于对称性,第 2 行和第 1 行之间是相同的。此外,与其他具有 1 的行相比,它是最长的匹配项(可以有关系)。您的术语略有不同。当您说最长组合时,是指元素的数量还是匹配字符串的最大长度?例如对于(3,4),常见的元素数量为1(即DDAD) , 但它的长度是 4.
  • 对不起-我会尽力澄清!当我说最长组合时,我指的是元素的数量,字符串的长度对我来说并不重要。所以在 3,4 的情况下,它就像你说的那样是一个(DDAD)。
【解决方案2】:

不懂 R,不喜欢 awk。 bash 解决方案怎么样?呵呵

已编辑以包含 cmets。做了一些小的改动,发现了一个可能很重要的警告。请注意,您有一个 AAA 一个 AAAA,当前代码可能会给您一个错误匹配,因为 AAAA 匹配 *AAA*。如果是这样的话,换入新的(但仍然有注释的)行,它仍然可以工作,并且会处理这种可能性。

$: cat proc
#! /bin/env bash

# predeclare these as associative arrays - string-based dictionary lookups
declare -A cnt=() set=() lookup=() combos=() hitlst=() hitcnt=() hitid=()

while read -a lst # this declares lst as a normal array and loads each line in fields
do id=${lst[0]}                            # field 0 assigned as the id
   lst=( ${lst[@]:1} )                     # lst assigned itself MINUS the first field
   cnt[$id]=${#lst[@]}                     # count for this id assigned # of  remaining elements in lst
   set[$id]="${lst[@]}"                    # set for id assigned the space-delimited elements of lst
   for k in ${lst[@]}                      # this will iterate k as each item in lst
   do  lookup["$k"]="${lookup["$k"]} $id"  # additively assigns, space delimited, this id as having this string
   done
done<dataset                               # when done reading the file, each key has a list of which id's it's on

# now we analyze our accumulated data
for id1 in ${!cnt[@]}                      # for every id (cnt has how many strings for each, ${!cnt[@]} is all the ids)
do  for k in ${set["$id1"]}                # iterate k over each string id1 had
    do  for id2 in ${lookup["$k"]}         # iterate id2 over each id that had this string
        do  [[ "$id1" == "$id2" ]] && continue # skip when both id pointers are looking at the same record
            case "${set["$id2"]}" in       # look at the whole set of strings assigned to id2 as a space-delimited string
            ### ==>> CAVEAT: might needed to be "$k "*|*" $k "*|*" $k") so that AAA doesn't match AA
            ### "$k "*|*" $k "*|*" $k") hitlst["$id1 $id2"]="${hitlst["$id1 $id2"]} $k";
            *$k*) hitlst["$id1 $id2"]="${hitlst["$id1 $id2"]} $k"; # when the current key from $id1 is anywhere in it
                  (( hitcnt["$id1 $id2"]++ )) ;;                   # increment the matches per pair of ids
            esac
            if (( ${hitcnt["$id1 $id2"]}0 > ${hitcnt["$id1"]}0 ))  # if matches/pair > stored max for id1
            then hitcnt["$id1"]=${hitcnt["$id1 $id2"]}             # upgrade to the new max
                 hitid["$id1"]="$id2"                              # and remember to report which rec while we're at it
            fi
       done
    done
done

printf "%s\t%s\t%s\t%s\n" "ID" "Longest Combo" "#strings" "[List of strings] / [matches] / matching ID"
for id in ${!cnt[@]}   # again, for every id
do  id2=${hitid[$id]}  # and again, this is the rec that had the most matches in case you wanted it (not requested)
    # print the fields with some formatting to align them
    printf "%s\t%s\t%s\t%s\n" "$id" "${hitcnt[$id]}" "${cnt[$id]}" "[${set[$id]}] / [${hitlst["$id $id2"]# }] / $id2"
done

$: proc
 ID Longest Combo  #strings [List of strings] / [matches] / matching ID
  1             3         5 [AAA BBBA ACA CCD ABADA] / [AAA ACA CCD] / 2
  2             3         3 [AAA ACA CCD] / [AAA ACA CCD] / 1
  3             1         3 [AAB BBAC DDAD] / [DDAD] / 4
  4             4         4 [AAA ACA DDAD CCD] / [AAA ACA DDAD CCD] / 5
  5             4         4 [AAA ACA DDAD CCD] / [AAA ACA DDAD CCD] / 4

我添加了一些字段并调整了输出格式,但你明白了。只有 3k 记录,不应该花费 时间。

【讨论】:

  • 如果速度太慢,我会在 Perl 中完成。
  • 非常感谢!我今天花了一些时间玩它,它似乎完美地完成了这项工作(尽管手动验证很困难......)。运行时间完全可以接受,而且 bash 也很好!
  • 很高兴我能帮上忙!接受解决方案?如果您真的想抚摸我的自尊心,也请随意投票,哈哈。这是一个有趣的小项目。您是否需要视觉上对齐的输出,或者只是分隔?选项卡在屏幕上看起来像地狱,但更适合加载到电子表格......并且会摆脱那些 printf 中所有丑陋的格式化代码/
  • BTW,你是想让cmets解释一下逻辑,还是说够清楚?
  • 不,这很好,谢谢!我已经投了赞成票,但我没有足够的动力来展示它。不过我会接受!
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2016-05-18
  • 2017-04-19
  • 1970-01-01
  • 2023-03-25
  • 2011-10-06
  • 2019-08-07
  • 1970-01-01
相关资源
最近更新 更多