Как изменить порядок совпадений между двумя кадрами данных

Я был занят этим вопросом с прошлой ночи, и я не мог понять, как это сделать.

Что я хочу сделать, это сопоставить строки df1 с строками df2 и получить похожие теги

что я делаю, так это

# a function to arrange the data to have IDs for each string 
    normalize <- function(x, delim) {
      x <- gsub(")", "", x, fixed=TRUE)
      x <- gsub("(", "", x, fixed=TRUE)
      idx <- rep(seq_len(length(x)), times=nchar(gsub(sprintf("[^%s]",delim), "", as.character(x)))+1)
      names <- unlist(strsplit(as.character(x), delim))
      return(setNames(idx, names))
    }

# a function to arrange the second df  
lookup <- normalize(df2[,1], ",")

# a function to match them and give the IDs
process <- function(s) {
  lookup_try <- lookup[names(s)]
  found <- which(!is.na(lookup_try))
  pos <- lookup_try[names(s)[found]]
  return(paste(s[found], pos, sep="-"))
  #change the last line to "return(as.character(pos))" to get only the result as in the comment
}

тогда я получаю такие результаты как

res <- lapply(colnames(df1), function(x) process(normalize(df1[,x], ";")))

Это дает мне номер строки каждой строки из df1 и номер строки строки из df2, которая соответствует. поэтому вывод этих данных выглядит следующим образом:

> res
$s1
[1] "3-4" "4-1" "5-4"

$s2
[1] "2-4"  "3-15" "7-16"

Идентификаторы первых столбцов - это номер строки df2, который соответствует строкам в df1 Второй столбец "Нет" - это количество совпадений Третий столбец ID-col-n - это номер строки строки в df1, который соответствует этой строке + имя столбца четвертая строка из первого столбца df1, который соответствует этой строке пятый столбец - это строка второго столбца, которая соответствует этой строке и т.д.

Ответы

Ответ 1

В этом случае мне проще переключать данные в широкий формат и до слияния их с таблицей поиска.

Вы можете попробовать:

library(tidyr)
library(dplyr)
df1_tmp <- df1
df2_tmp <- df2
#add numerical id to df1_tmp to keep row information
df1_tmp$id <- seq_along(df1_tmp[,1])

#switch to wide and unnest rows with several strings
df1_tmp <- gather(df1_tmp,key="s_val",value="query_string",-id)
df1_tmp <- df1_tmp %>% 
        mutate(query_string = strsplit(as.character(query_string), ";")) %>% 
        unnest(query_string)


df2_tmp$IDs. <- gsub("[()]", "", df2_tmp$IDs.)

#add numerical id to df1_tmp to keep row information
df2_tmp$id <- seq_along(df2_tmp$IDs.)

#unnest rows with several strings
df2_tmp <- df2_tmp %>% 
        mutate(IDs. = strsplit(as.character(IDs.), ",")) %>% 
        unnest(IDs.)

res <- merge(df1_tmp,df2_tmp,by.x="query_string",by.y="IDs.")

res$ID_col_n <- paste(paste0(res$id.x,res$s_val))
res$total_id <- 1:nrow(res)
res <- spread(res,s_val,value=query_string,fill=NA)
res
#summarize to get required output 

res <- res %>% group_by(id.y) %>%
        mutate(No=n())  %>% group_by(id.y,No) %>%
        summarise_each(funs(paste(.[!is.na(.)],collapse=","))) %>% 
        select(-id.x,-total_id)

colnames(res)[colnames(res)=="id.y"]<-"IDs"

res$df1_colMatch_counts <- rowSums(res[,-(1:3)]!="")
df2_counts <- df2_tmp %>% group_by(id) %>% summarize(df2_string_counts=n())
res <- merge(res,df2_counts,by.x="IDs",by.y="id")
res


res

  IDs No    ID_col_n            s1     s2 df1_colMatch_counts df2_string_counts
1   1  1         4s1        P41182                          1                 2
2   2  1         4s1        P41182                          1                 2
3   3  1         4s1        P41182                          1                 2
4   4  3 2s2,3s1,5s1 Q9Y6Q9,Q09472 Q92831                   2                 4
5  15  1         3s2               P54612                   1                 5
6  16  1         7s2               O15143                   1                 7

Ответ 2

Для этого решения я использую универсальную вспомогательную функцию, предназначенную для ограничения ограничения rbind(), что он не может обрабатывать несогласованные имена столбцов. Вероятно, вы могли бы использовать любую функцию из Объединить два кадра данных по строкам (rbind), когда они имеют разные наборы столбцов, но я также написал свои собственные:

rbind.cn <- function(...,filler=NA) {
    ## note: must explicitly set proper S3 class; otherwise, filler would corrupt the column type in cases where a column is missing from the first df
    ## for example, logical NA (the default for filler) would nix factors, resulting in character after type promotion
    ## to do this, will use single-row temp df as first argument to rbind()
    ## note: tried zero-row to prevent need to excise afterward, but zero-row rbind() arguments are ignored for typing purposes
    l <- list(...);
    schema <- do.call(cbind,unname(lapply(l,function(df) df[1L,,drop=F]))); ## unname() is necessary, otherwise cbind() tries to be a good citizen and concats first df cell value found in lapply() names onto schema column names
    schema <- schema[unique(names(schema))];
    res <- do.call(rbind,c(list(schema),lapply(l,function(df) {
        cns.add <- names(schema)[!names(schema)%in%names(df)];
        do.call(cbind,c(
            list(df),
            setNames(rep(filler,length(cns.add)),cns.add),
            stringsAsFactors=F
        ));
    })))[-1L,,drop=F];
    ## fix up row names
    rns <- do.call(c,lapply(l,rownames));
    rownames(res) <- ifelse(grepl('^[0-9]+$',rns),seq_along(rns),rns);
    res;
};

Я также написал свою собственную функцию нормализации. Я думаю, что вы были на правильном пути, предварительно вычислив нормализованное представление обоих входных data.frames, но поскольку вы использовали индексирование с именем vector для соответствия идентификаторам, вы не обнаружили случаи дублированных имен в df2, поэтому ваш в результате отсутствуют дополнительные вхождения id P41182. Здесь моя функция нормализации:

## normalization function
## for each column, splits on sep and captures the id, row index, column index, and column name in a data.frame
normalize <- function(df,sep) {
    do.call(rbind,lapply(seq_along(df),function(ci) {
        l <- strsplit(gsub('[()]','',df[[ci]]),sep);
        cbind(
            do.call(rbind,lapply(seq_along(l),function(ri)
                if (length(l[[ri]]) > 0L)
                    data.frame(id=l[[ri]],ri=ri,stringsAsFactors=F)
            )),
            ci,
            cn=names(df)[ci]
        );
    }));
};

Здесь полное решение:

## normalize both data.frames
df1.norm <- normalize(df1,';');
df2.norm <- normalize(df2,',');

## join them on matching ids
df.match <- merge(df1.norm,df2.norm,'id',suffixes=c('.1','.2'));
df.match <- df.match[with(df.match,order(ri.2,cn.1,ri.1)),]; ## order by df2 row index, df1 column name, and finally df1 row index, as per required output

## aggregate and format as required
res <- do.call(rbind.cn,c(by(df.match,df.match$ri.2,function(x) {
    strCols <- aggregate(id~cn.1,x[c('id','cn.1')],paste,collapse=','); ## conveniently, automatically orders by the grouping column cn.1
    do.call(cbind,c(
        list(data.frame(IDs=x$ri.2[1L],No=nrow(x),`ID-col-n`=paste0(x$ri.1,x$cn.1,collapse=','),stringsAsFactors=F,check.names=F)),
        setNames(strCols$id,paste0('string-df1-',strCols$cn.1)),
        stringsAsFactors=F
    ));
}),filler='-'));

## order string-df1 columns
res <- res[c(1:3,order(as.integer(sub('.*?([0-9]+)$','\\1',names(res)[-1:-3])))+3L)];

И вот все промежуточные и конечные data.frames:

df1.norm;
##        id ri ci cn
## 1  Q9Y6W5  1  1 s1
## 2  Q9Y6U3  2  1 s1
## 3  Q9Y6Q9  3  1 s1
## 4  P41182  4  1 s1
## 5  Q9HCP0  4  1 s1
## 6  Q09472  5  1 s1
## 7  Q9Y6I3  6  1 s1
## 8  Q9Y6H1  7  1 s1
## 9  Q5T1J5  7  1 s1
## 10 Q16835  1  2 s2
## 11 P61809  2  2 s2
## 12 Q92831  2  2 s2
## 13 P41356  3  2 s2
## 14 P54612  3  2 s2
## 15 A41PH2  3  2 s2
## 16 P3R117  4  2 s2
## 17 P31908  5  2 s2
## 18 P54112  6  2 s2
## 19 O15143  7  2 s2

df2.norm;
##        id ri ci   cn
## 1  P41182  1  1 IDs.
## 2  P56524  1  1 IDs.
## 3  P41182  2  1 IDs.
## 4  Q9UQL6  2  1 IDs.
## 5  P41182  3  1 IDs.
## 6  Q8WUI4  3  1 IDs.
## 7  Q92793  4  1 IDs.
## 8  Q09472  4  1 IDs.
## 9  Q9Y6Q9  4  1 IDs.
## 10 Q92831  4  1 IDs.
## 11 P30561  5  1 IDs.
## 12 P53762  5  1 IDs.
## 13 Q15021  6  1 IDs.
## 14 Q9BPX3  6  1 IDs.
## 15 Q15003  6  1 IDs.
## 16 O95347  6  1 IDs.
## 17 Q9NTJ3  6  1 IDs.
## 18 Q92902  7  1 IDs.
## 19 Q9NQG7  7  1 IDs.
## 20 Q969F9  8  1 IDs.
## 21 Q9UPZ3  8  1 IDs.
## 22 Q86YV9  8  1 IDs.
## 23 Q92903  9  1 IDs.
## 24 Q96NY9  9  1 IDs.
## 25 Q91VB4 10  1 IDs.
## 26 P59438 10  1 IDs.
## 27 Q8BLY7 10  1 IDs.
## 28 Q92828 11  1 IDs.
## 29 Q13227 11  1 IDs.
## 30 O15379 11  1 IDs.
## 31 O75376 11  1 IDs.
## 32 O60907 11  1 IDs.
## 33 Q9BZK7 11  1 IDs.
## 34 P78537 12  1 IDs.
## 35 Q6QNY1 12  1 IDs.
## 36 Q6QNY0 12  1 IDs.
## 37 Q9NUP1 12  1 IDs.
## 38 Q96EV8 12  1 IDs.
## 39 Q8TDH9 12  1 IDs.
## 40 Q9UL45 12  1 IDs.
## 41 O95295 12  1 IDs.
## 42 O55102 13  1 IDs.
## 43 Q9CWG9 13  1 IDs.
## 44 Q5U5M8 13  1 IDs.
## 45 Q8VED2 13  1 IDs.
## 46 Q91WZ8 13  1 IDs.
## 47 Q8R015 13  1 IDs.
## 48 Q9R0C0 13  1 IDs.
## 49 Q9Z266 13  1 IDs.
## 50 P30561 14  1 IDs.
## 51 O08915 14  1 IDs.
## 52 P07901 14  1 IDs.
## 53 P11499 14  1 IDs.
## 54 Q8WMR7 15  1 IDs.
## 55 P67776 15  1 IDs.
## 56 P11493 15  1 IDs.
## 57 P54612 15  1 IDs.
## 58 P54613 15  1 IDs.
## 59 P61160 16  1 IDs.
## 60 P61158 16  1 IDs.
## 61 O15143 16  1 IDs.
## 62 O15144 16  1 IDs.
## 63 O15145 16  1 IDs.
## 64 P59998 16  1 IDs.
## 65 O15511 16  1 IDs.

df.match;
##       id ri.1 ci.1 cn.1 ri.2 ci.2 cn.2
## 2 P41182    4    1   s1    1    1 IDs.
## 4 P41182    4    1   s1    2    1 IDs.
## 3 P41182    4    1   s1    3    1 IDs.
## 8 Q9Y6Q9    3    1   s1    4    1 IDs.
## 6 Q09472    5    1   s1    4    1 IDs.
## 7 Q92831    2    2   s2    4    1 IDs.
## 5 P54612    3    2   s2   15    1 IDs.
## 1 O15143    7    2   s2   16    1 IDs.

res;
##   IDs No    ID-col-n string-df1-s1 string-df1-s2
## 1   1  1         4s1        P41182             -
## 2   2  1         4s1        P41182             -
## 3   3  1         4s1        P41182             -
## 4   4  3 3s1,5s1,2s2 Q9Y6Q9,Q09472        Q92831
## 5  15  1         3s2             -        P54612
## 6  16  1         7s2             -        O15143