Replace values in one column with randomly generated values using group_by and mutate, while making sure every set of values is unique in R

huangapple go评论92阅读模式
英文:

Replace values in one column with randomly generated values using group_by and mutate, while making sure every set of values is unique in R

问题

  1. ifelse(!duplicated(data.new$ID), generateRandomString(), data.new$ID)
英文:

I am writing a function to replace ID's in one column with randomly generated ones. Suppose I have a dataset:

  1. df <- data.frame(ID = c(1, 1, 2, 2, 1, 3), Name = c("Joseph", "Joseph", "Leo", "Leo", "Joseph", "David"))
  1. ID Name
  2. 1 1 Joseph
  3. 2 1 Joseph
  4. 3 2 Leo
  5. 4 2 Leo
  6. 5 1 Joseph
  7. 6 3 David

The goal of the function is to group the dataset by ID and replace all unique ID's with randomly generated ones, like this:

  1. library(tidyverse)
  2. generateRandomString <- function() {
  3. sample(LETTERS, 1)
  4. }
  5. replaceUniqueID <- function(data, column_id) {
  6. name.id <- data[grep(column_id, colnames(data))]
  7. data.new <- data %>%
  8. group_by(data[grep(column_id, colnames(data))]) %>%
  9. mutate_at(column_id, funs(generateRandomString()))
  10. data.new
  11. }
  12. replaceUniqueID(df, "ID")
  13. # A tibble: 6 × 2
  14. # Groups: ID [3]
  15. ID Name
  16. <chr> <chr>
  17. 1 D Joseph
  18. 2 D Joseph
  19. 3 R Leo
  20. 4 R Leo
  21. 5 D Joseph
  22. 6 Q David

My problem is, I want the code to make sure every ID gets replaced with unique string, e.g. David and Leo can't both have D as a replacement for their respective ID's.

I tried to come up with an ifelse statement within mutate but was unable to figure out how to do that.

答案1

得分: 0

I will only translate the code part for you:

  1. 我认为你可以使用翻译词典来相对容易地解决这个问题。
  2. genRandomIDs <- function(ID, min_length = 2) {
  3. ID <- unique(ID)
  4. len <- max(min_length, length(ID) %/% 26 + 1)
  5. ltrs <- do.call(paste0,
  6. do.call(expand.grid, replicate(len, LETTERS, simplify=FALSE))
  7. )
  8. sample(ltrs, length(ID))
  9. }
  10. set.seed(42)
  11. IDdict <- df %>%
  12. distinct(ID) %>%
  13. mutate(newID = genRandomIDs(ID))
  14. IDdict
  15. # ID newID
  16. # 1 1 OV
  17. # 2 2 IM
  18. # 3 3 WF
  19. df %>%
  20. left_join(IDdict, by = "ID")
  21. # ID Name newID
  22. # 1 1 Joseph OV
  23. # 2 1 Joseph OV
  24. # 3 2 Leo IM
  25. # 4 2 Leo IM
  26. # 5 1 Joseph OV
  27. # 6 3 David WF

这是代码的翻译部分。

英文:

I think you can use a translation dictionary to solve this relatively easily.

  1. genRandomIDs &lt;- function(ID, min_length = 2) {
  2. ID &lt;- unique(ID)
  3. len &lt;- max(min_length, length(ID) %/% 26 + 1)
  4. ltrs &lt;- do.call(paste0,
  5. do.call(expand.grid, replicate(len, LETTERS, simplify=FALSE))
  6. )
  7. sample(ltrs, length(ID))
  8. }
  9. set.seed(42)
  10. IDdict &lt;- df %&gt;%
  11. distinct(ID) %&gt;%
  12. mutate(newID = genRandomIDs(ID))
  13. IDdict
  14. # ID newID
  15. # 1 1 OV
  16. # 2 2 IM
  17. # 3 3 WF
  18. df %&gt;%
  19. left_join(IDdict, by = &quot;ID&quot;)
  20. # ID Name newID
  21. # 1 1 Joseph OV
  22. # 2 1 Joseph OV
  23. # 3 2 Leo IM
  24. # 4 2 Leo IM
  25. # 5 1 Joseph OV
  26. # 6 3 David WF

Walk-through:

  • genRandomIDs is just a helper function that internally produces a vector of all n-long letter permutations (combined with paste0) and samples from them;
    • the do.call(expand.grid, ...) gives us a frame that expands on each len grouping of letters; that is, expand.grid(LETTERS[1:3],LETTERS[1:3],LETTERS[1:3]) gives us 3^3 permutations of three letters
    • the do.call(paste0, ...) takes that frame from expand.grid (which is really just a glorified list) and produces one string per "row".
  • distinct(ID) reduces your df to just one row per ID;
  • since we produce one newID for each unique ID, we now have a 1-to-1 mapping from old-to-new;
  • the left_join assigns the newID for each row (if you aren't familiar with merges/joins, see https://stackoverflow.com/q/1299871/3358272, https://stackoverflow.com/q/5706437/3358272)

Note: this does not really scale well: since we explode the possible combinations with expand.grid, for a min-length of 2 letters, we produce 676 (26^2) permutations, not a problem. 26^3 produces 17576 possible combinations, whether or not we have that many IDs to uniquify. 26^4 (4 letters) produces 456976, and its delay is "palpable". Five letters is over 11 million, which becomes "stupid" to try to scale to that length (assuming you have that many unique IDs or choose a string of that long.

However ... while inefficient, this method is guaranteed to give you unique newIDs. There are other ways that may be guaranteed at the expense of a (however small) increase in complexity).


Okay, the "increased complexity" here for a more efficient process:

  1. num2alpha &lt;- function(num, chr = letters, zero = &quot;&quot;, sep = &quot;&quot;) {
  2. len &lt;- length(chr)
  3. stopifnot(len &gt; 1)
  4. signs &lt;- ifelse(!is.na(num) &amp; sign(num) &lt; 0, &quot;-&quot;, &quot;&quot;)
  5. num &lt;- as.integer(abs(num))
  6. is0 &lt;- !is.na(num) &amp; num &lt; 1e-9
  7. # num[num &lt; 1] &lt;- NA
  8. out &lt;- character(length(num))
  9. mult &lt;- 0
  10. while (any(!is.na(num) &amp; num &gt; 0)) {
  11. not0 &lt;- !is.na(num) &amp; num &gt; 0
  12. out[not0] &lt;- paste0(chr[(num[not0] - 1) %% len + 1], sep, out[not0])
  13. num[not0] &lt;- (num[not0] - 1) %/% len
  14. }
  15. if (nzchar(sep)) out &lt;- sub(paste0(sep, &quot;$&quot;), &quot;&quot;, out)
  16. out[is0] &lt;- zero
  17. out[is.na(num)] &lt;- NA
  18. out[!is.na(out)] &lt;- paste0(signs[!is.na(out)], out[!is.na(out)])
  19. out
  20. }
  21. IDdict &lt;- df %&gt;%
  22. distinct(ID) %&gt;%
  23. mutate(newID = num2alpha(row_number()))
  24. IDdict
  25. # ID newID
  26. # 1 1 a
  27. # 2 2 b
  28. # 3 3 c
  29. df %&gt;%
  30. left_join(IDdict, by = &quot;ID&quot;)
  31. # ID Name newID
  32. # 1 1 Joseph a
  33. # 2 1 Joseph a
  34. # 3 2 Leo b
  35. # 4 2 Leo b
  36. # 5 1 Joseph a
  37. # 6 3 David c

The num2alpha works more efficiently (using lower-case here, easily changed by using num2alpha(.., chr=LETTERS)), though it is deterministic here. If you are at all concerned about that, then

  1. IDdict &lt;- df %&gt;%
  2. distinct(ID) %&gt;%
  3. mutate(newID = sample(num2alpha(row_number())))

will randomize them for you.

Note that this produces single-letter strings up through 26, then cycles through 2-digit and 3-digit. It also recognized negatives, and while the defatul

  1. num2alpha(c(-5, 0, NA, 1, 25:27, 51:53, 999999), zero=&quot;0&quot;)
  2. # [1] &quot;-e&quot; &quot;0&quot; NA &quot;a&quot; &quot;y&quot; &quot;z&quot; &quot;aa&quot; &quot;ay&quot; &quot;az&quot; &quot;ba&quot; &quot;bdwgm&quot;

(Note that this is not a simple base-converter, since we're ignoring "0"-values. Try num2alpha(14:16, c(1:9, LETTERS[1:6]), zero=&quot;0&quot;). Perhaps it can be made to be more general.)

huangapple
  • 本文由 发表于 2023年5月14日 22:33:16
  • 转载请务必保留本文链接:https://go.coder-hub.com/76248012.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定