R: How do I split an email into parts when the emails have multiple domain endings?

Viewed 248

I'm trying to analyze a list of emails stored inside a dataframe (data$Email.Address) and I want to start by splitting the emails into parts, so that example1@gmail.com, example2@outlook.org, and example3@comcast.net end up like this:

   email                 firstpart secondpart thirdpart

1  example1@gmail.com    example1  gmail      com
2  example2@outlook.org  example2  outlook    org
3  example3@comcast.net  example3  comcast    net

With my current code, however, I can't all match all strings — since some include domains like (some-url.com) or (us.army.mil). This means that example4@us.army.mil shows up as:

    email                  firstpart secondpart thirdpart
4   example4@us.army.mil   example4  us         army

My goal is to read "some-url" or "us.army" as the second part, and "com" and "mil" as the third parts, so that is shows up like this:

    email                  firstpart secondpart thirdpart
4   example4@us.army.mil   example4  us.army    mil

Here's the code I have:

library(tidyverse)
library(dplyr)
library(stringr)
library(rebus)

email_pattern <- capture(one_or_more(WRD)) %R%
  "@" %R% capture(one_or_more(x = WRD)) %R% 
  DOT %R% capture(one_or_more(WRD)) 

#Split the emails into parts based on the pattern
email_parts <- str_match(data$Email.Address, pattern = email_pattern)

How can I change the code so that all the domains can be read? Thank you!

4 Answers

Using stringi and data.table's tstrsplit():

library(stringi)
library(data.table)
df[paste0("part", 1:3)] <- 
  tstrsplit(stri_replace_last(df$email, fixed = ".", "@"), split = "@")

                 email    part1   part2 part3
1   example1@gmail.com example1   gmail   com
2 example2@outlook.org example2 outlook   org
3 example3@comcast.net example3 comcast   net
4 example4@us.army.mil example4 us.army   mil

Reproducible data (please provide yourself next time):

df <- data.frame(
  email = c(
    "example1@gmail.com", "example2@outlook.org", "example3@comcast.net", "example4@us.army.mil"
  )
)

You could also use regex capture groups with tidyr::extract().

library(tidyr)

df %>%
  extract(email, c("firstpart", "secondpart", "thirdpart"), 
          "([A-Za-z0-9_.]+)@([a-z.]+)\\.([a-z]+)$", remove = FALSE)

                 email firstpart secondpart thirdpart
1   example1@gmail.com  example1      gmail       com
2 example2@outlook.org  example2    outlook       org
3 example3@comcast.net  example3    comcast       net
4 example4@us.army.mil  example4    us.army       mil

Here is the breakdown of groups--denoted by parentheses.

The first ([A-Za-z0-9_.]+) captures everything that include those characters before @ consecutively. Here you need to be explicit about which characters can be included. You could also probably use (.+?) or ([[:print:]]?) here, but I prefer being explicit.

The second ([a-z.]+) captures all lowercase letters and periods until the last period in the email address (denoted by the \\. not in parentheses).

The last ([a-z]+)$ captures all lowercase letters until the end of the string.

Here's a base R solution using sindri_baldur's example data:

df <- data.frame(email = c("example1@gmail.com", "example2@outlook.org", "example3@comcast.net", "example4@us.army.mil"))
df$firstpart <- sapply(strsplit(df$email, "@"), function (x) {
  x[1]
})
df$secondpart <- strsplit(sapply(strsplit(df$email, "@"), function (x) {
  x[2]
}), "[.][[:alpha:]]+$")
df$thirdpart <- sapply(seq_len(nrow(df)), function (x) {
  gsub(paste0(df$firstpart[x], "@", df$secondpart[x], "."), "", df$email[x])
})
df
#                  email firstpart secondpart thirdpart
# 1   example1@gmail.com  example1      gmail       com
# 2 example2@outlook.org  example2    outlook       org
# 3 example3@comcast.net  example3    comcast       net
# 4 example4@us.army.mil  example4    us.army       mil

I see you've found some valid answers but thought I would bring it back to your original dplyr and tidyr work flow. I think the key regex you were missing was "\\.(?=[^.]+$)" (grab what's after the last period).

Since I'm late to the show and I served I added a fifth whimsical entry to your play data frame...

library(dplyr)
library(tidyr)

df <- data.frame(
   email = c(
      "example1@gmail.com", 
      "example2@outlook.org", 
      "example3@comcast.net", 
      "example4@us.army.mil",
      "example5@us.paratrooper.fort-benning.army.mil"
   )
)

df %>% 
   separate(email, "@", into = c("userid", "domain")) %>%
   separate(
      col = domain,
      into = c("secondpart", "thirdpart"),
      sep = "\\.(?=[^.]+$)")
#>     userid                       secondpart thirdpart
#> 1 example1                            gmail       com
#> 2 example2                          outlook       org
#> 3 example3                          comcast       net
#> 4 example4                          us.army       mil
#> 5 example5 us.paratrooper.fort-benning.army       mil
Related