Ho to run a function (many times) that changes variable (tibble) in global env

Viewed 43

I'm a newbie in R, so please have some patience and... tips are most welcome.

My goal is to create tibble that holds a "Full Name" (of a person, that may have 2 to 4 names) and his/her gender. I must start from a tibble that contains typical Male and Female names.

Below I present a minimum working example.

My problem: I can call get_name() multiple time (in 10.000 for loop!!) and get the right answer. But, I was looking for a more 'elegant' way of doing it. replicate() unfortunately returns a vector... which make it unusable.

My doubts: I know I have some (very few... right!!) issues, like the if statement, that is evaluated every time (which is redundant), but I don't find another way to do it. Any suggestion?

Any other suggestions about code struct are also welcome.

Thank you very much in advance for your help.

# Dummy name list
unit_names <- tribble(
  ~Women, ~Man,
  "fem1", "male1",
  "fem2", "male2", 
  "fem3", "male3",
  "fem4", "male4",
  "fem5", "male5",
  "fem6", NA,
  "fem7", NA
)

set.seed(12345) # seed for test

# Create a tibble with the full names
full_name <- tibble("Full Name" = character(), "Gender" = character() )

get_name <- function() {
  # Get the Number of 'Unit-names' to compose a 'Full-name'
  nbr_names <- sample(2:4, 1, replace = TRUE)
  # Randomize the Gender
  gender  <- sample(c("Women", "Man"), 1, replace = TRUE)
  if (gender == "Women") {
    lim_names <- sum( !is.na(unit_names$"Women"))
  } else {
    lim_names <- sum( !is.na(unit_names$"Man"))
  }

  # Sample the Fem/Man List names (may have duplicate)
  sample(unlist(unit_names[1:lim_names, gender]), nbr_names, replace = TRUE) %>%
    # Form a Full-name
    paste ( . , collapse = " ") %>%
    # Add it to the tibble (INCLUDE the Gender)
    add_row(full_name, "Full Name" = . , "Gender" = gender)
}

# How can I make 10k of this?
full_name <- get_name()
3 Answers

If you pass a larger number than 1 to sample this problem becomes easier to vectorise.

One thing that currently makes your problem much harder is the layout of your unit_names table: you are effectively treating male and female names as individually paired, but they clearly aren’t: hence they shouldn’t be in columns of the same table. Use a list of two vectors, for instance:

unit_names = list(
    Women = c("fem1", "fem2", "fem3", "fem4", "fem5", "fem6", "fem7"),
    Men = c("male1", "male2", "male3", "male4", "male5")
)

Then you can generate random names to your heart’s delight:

generate_names = function (n, unit_names) {
    name_length = sample(2 : 4, n, replace = TRUE)
    genders = sample(c('Women', 'Men'), n, replace = TRUE)
    names = Map(sample, unit_names[genders], name_length, replace = TRUE) %>%
        lapply(paste, collapse = ' ') %>%
        unlist()
    tibble(`Full name` = names, Gender = genders)
}

A note on style, unlike your function the above doesn’t use any global variables. Furthermore, don’t "quote" variable names (you do this in unit_names$"Women" and for the arguments of add_row). R allows this, but this is arguably a mistake in the language specification: these are not strings, they’re variable names, making them look like strings is misleading. You don’t quote your other variable names, after all. You do need to backtick-quote the `Full name` column name, since it contains a space. However, the use of backticks, rather than quotes, signifies that this is a variable name.

I am not 100% of what you are trying to get, but if I got it right...did you try with mutate at dplyr? For example: result= mutate(data.frame, concated_column = paste(column1, column2, column3, column4, sep = '_'))

With a LITTLE help from Konrad Rudolph, the following elegant (and vectorized ... and fast) solution that I was looking. map2 does the necessary trick.

Here is the full working example if someone needs it: (Just a side note: I kept the initial conversion from tibble to list because the data arrives to me as a tibble...)

Once again thanks to Konrad.

# Dummy name list
unit_names <- tribble(
  ~Women, ~Men,
  "fem1", "male1",
  "fem2", "male2", 
  "fem3", "male3",
  "fem4", "male4",
  "fem5", "male5",
  "fem6", NA,
  "fem7", NA
)
name_list <- list(
  Women = unit_names$Women[!is.na(unit_names$Women)],
  Men = unit_names$Men[!is.na(unit_names$Men)]
)

generate_names = function (n, name_list) {
  name_length = sample(2 : 4, n, replace = TRUE)
  genders = sample(c('Women', 'Men'), n, replace = TRUE)
  #names = lapply(name_list[genders], sample,  name_length) %>%
  names = map2(name_list[genders], name_length, sample) %>%
    lapply(paste, collapse = ' ') %>%
    unlist()
  tibble(`Full name` = names, Gender = genders)
}

full_name <- generate_names(10000, name_list)
Related