Как мне определить, что вызывает сбои в моей функции R?

#r #performance #optimization #twitter #anonymize

Вопрос:

Я написал функцию для анонимизации имен в фрейме данных с заданным ключом, и она обходит, как только доходит до анонимизации очень многих имен, но я не понимаю, почему.

Рассматриваемый фрейм данных представляет собой набор из 4733 твитов, собранных через API Twitter, где каждая строка представляет собой твит с 32 столбцами данных. Имена должны быть анонимизированы независимо от того, в какой строке они отображаются, поэтому я бы не хотел ограничивать функцию просмотром только пары из этих 32 столбцов.

Ключ-это фрейм данных, содержащий 211121 пару реальных и поддельных имен, как реальных, так и поддельных, уникальных в фрейме данных. Функция сильно замедляется после анонимизации около 100 тысяч имен.

Функция выглядит следующим образом:

 pseudonymize <- function(df, key) {
  for(name in key$realNames) {
    df <- as.data.frame(apply(df, 2, function(column) gsub(name, key[key$realNames == name, 2], column)))
  }
}
 

Есть ли здесь какая-то очевидная вещь, которая могла бы вызвать замедление? У меня совсем нет опыта в оптимизации кода для скорости.

ПРАВКА1:

Вот несколько строк из фрейма данных, подлежащих анонимизации.

 "https://twitter.com/__jgil/statuses/825559753447313408","__jgil",0.000576911235261567,756,4,13,17,7,16,23,10,0.28166915052161,0.390123456790124,0.00271311644806025,0.474529795261862,0.00641025649383664,"@jadahung20 GIRL I am tooooooo salty tonight lolll","lolll","adjoint","anglais","indefini","anglais","anglais","non","iPhone, Twitter",4057,214,241,"Canada","Nouvelle-Ecosse","Middleton","indefini","Shari"
"https://twitter.com/__paigewhite/statuses/827988259573788673","__paigewhite",0,1917,0,8,8,0,9,9,16,0.143476044852192,0.162056634159209,0.000172947386274259,0,0,"@abbytutty_ i miss emily lololol _Ù÷â_Ù÷É","lololol","adjoint","anglais","indefini","anglais","anglais","non","iPhone, Twitter",8366,392,661,"Canada","Nouvelle-Ecosse","indefini","indefini","Shari"
"https://twitter.com/_brookehynes/statuses/821022926287884288","_brookehynes",0,1917,1,6,7,1,7,8,1,1,1,0.000196850793912616,0.00393656926735126,0.200000002980232,"@tdesj3 @belle lol yea doubt it.","lol","adjoint","indefini","anglais","anglais","anglais","non","iPhone, Twitter",1184,87,70,"Canada","Nouvelle-Ecosse","Halifax","indefini","Shari"
 

Вот несколько строк из ключа.

 "","realNames","fakeNames"
"1","________","Tajid_Pinkley"
"2","____________aho","Monica_Yujiri"
"3","___________ass","Alexander_Garay-Grajeda"
 

ПРАВКА2:

Я упростил DF только до двух столбцов, которые требовали бы анонимизации, и это значительно ускорило процесс, но он все равно выходит после того, как было сделано около 155 тысяч имен.

Как указано в комментариях, вот dput() выходные данные для первых трех строк DF, которые должны быть анонимизированы.

 structure(list(
  utilisateur = c("___Yeliab", "__courtlezz", "__courtlezz"),
  texte = c("@EmilyIsPro ik lol", "@NikkiErica21 there was a sighting in sunset ridge too. Keep Winnie and bob safe lol", "@NikkiErica21 lol yes _Ã231։")
  ),
  row.names = c(NA, 3L),
  class = "data.frame")
 

А вот dput() для первых трех строк ключа.

 structure(list(
  realNames = c("________", "____________aho", "___________ass"),
  fakeNames = c("Abhinav_Chang", "Caleb_Dunn-Sparks", "Taryn_Hunzicker")
  ),
  row.names = c(NA, 3L),
  class = "data.frame")
 

Комментарии:

1. Пожалуйста, поделитесь небольшим, воспроизводимым (копируемым/вставляемым!) образцом ввода.

2. Трудно сказать, не видя ваших структур данных, но вы выполняете много преобразований внутри цикла. apply преобразует фреймы данных в матрицы — вам, вероятно, вообще не следует их использовать. as.data.frame преобразуется обратно в фрейм данных. Действительно ли вам нужно преобразовывать свой объект в матрицу, а затем обратно в фрейм данных на каждой итерации? Если вы сможете перенести эти операции за пределы цикла-преобразовать все один раз-это будет происходить быстрее. И когда мы увидим входные данные, вам, возможно, вообще не понадобятся преобразования.

3. Кроме того, если вы не используете специальные символы регулярных выражений, использование fixed = TRUE аргумента будет gsub() намного быстрее. И могут быть варианты векторизации, так что вам вообще не нужен цикл…

4. Не могли бы вы поделиться данными, dput() чтобы включить всю информацию о классе и структуре? dput(df[1:3, ]) и dput(key([1:3]) было бы здорово.

Ответ №1:

Воздействие на данные в виде вектора, а не фрейма данных, будет намного эффективнее. Я столкнулся с некоторыми проблемами с кодировкой, поэтому преобразовал текст в UTF-8 с помощью iconv ; Если имена содержат символы, отличные от ASCII, это потребует некоторой обработки.

 key1 <- data.frame(
    realNames = c("________", "____________aho", "___________ass", 
        "___Yeliab", "__courtlezz", "NikkiErica21", "EmilyIsPro", "aho"),
    fakeNames = c("Abhinav_Chang", "Caleb_Dunn-Sparks", "Taryn_Hunzicker", 
        "A_A", "B_B", "C_C", "D_D", "E_E"),
    stringsAsFactors = FALSE
)

pseudonymize1 <- function(df, key) {
    mat <- as.matrix(df)
    dims <- attr(mat, which = "dim")
    cnam <- colnames(df)
    vec <- iconv(unclass(mat), from = "latin1", to = "UTF-8")
    for (name in split(key, f = seq_len(nrow(key)))) {
        vec <- gsub(
            vec, 
            pattern = name$realNames, 
            replacement = name$fakeNames, 
            fixed = TRUE)
    }
    mat <- vec
    attr(mat, which = "dim") <- dims
    df <- as.data.frame(mat, stringsAsFactors = FALSE)
    colnames(df) <- cnam
    df
}
pseudonymize1(df1, key1)
# utilisateur                                                                       texte
# 1         A_A                                                                 @D_D ik lol
# 2         B_B @C_C there was a sighting in sunset ridge too. Keep Winnie and bob safe lol
# 3         B_B                               @C_C lol yes _Ãu0083u0099Ãu0083·Ãu0083¢

library(microbenchmark)    
microbenchmark(
    pseudonymize(df1, key1),
    pseudonymize1(df1, key1)
)
# Unit: microseconds
#                     expr      min        lq     mean   median        uq      max neval cld
#  pseudonymize(df1, key1) 1842.554 1885.6750 2131.089 1994.755 2294.6850 3007.371   100   b
# pseudonymize1(df1, key1)  287.683  306.1905  333.678  314.950  339.8705  497.301   100  a 
 

Меня беспокоит, что 155 тысяч имен связаны с тем, что при поиске в виде регулярного выражения вы найдете имена, содержащиеся в других именах. Это может быть истинное имя в пределах истинного имени (например, Эмили в EmilyIsPro) или истинное имя в пределах ранее замененного поддельного имени. Вы захотите проверить это и рассмотреть возможность использования случайного хэша вместо поддельного имени, подобного имени.