如何将字符串字段分隔为 R 中两个不同的数字列
How to delimit a string field into two different numeric columns in R
我有一个数据框,它有一个文本字段,用于记录一个人在一个城市停留的时间。格式为 y year(s) m month(s)
,y 和 m 为数字。如果此人在城市居住时间少于一年,则该值只会采用 m months
格式
我想将此列转换为两个单独的数字列,一个显示生活的年数,另一个显示生活的月份。
这是我的数据框示例:
df <- structure(list(Time.in.current.role = c("1 year 1 month", "11
months",
"3 years 11 months", "1 year 1 month", "8 months"), City =
c("Philadelphia",
"Seattle", "Washington D.C.", "Ashburn", "Cork, Ireland")), .Names =
c("Time.in.current.role",
"City"), row.names = c(NA, 5L), class = "data.frame")
我的愿望数据框如下所示:
result <- structure(list(Year = c(1, 0, 3, 1, 0), Month = c(1, 11,
11,
1, 8), City = structure(c(3L, 4L, 5L, 1L, 2L), .Label = c("Ashburn",
"Cork, Ireland", "Philadelphia", "Seattle", "Washington D.C."
), class = "factor")), .Names = c("Year", "Month", "City"), row.names
= c(NA,
-5L), class = "data.frame")
我正在考虑使用 grep 来定位哪些行中包含子字符串 "year" 以及哪些行中包含子字符串 "month"。但在那之后,我在尝试获取与 "year" 或 "month".
适当关联的数字时遇到了麻烦
* 编辑 *
在我原来的 post 中,我忘了考虑可能只有 y year(s)
的情况。这是新的原始数据框和所需的数据框:
df <- structure(list(Time.in.current.role = c("1 year 1 month", "11
months",
"3 years 11 months", "1 year 1 month", "8 months", "2 years"),
City = c("Philadelphia", "Seattle", "Washington D.C.", "Ashburn",
"Cork, Ireland", "Washington D.C.")), .Names =
c("Time.in.current.role",
"City"), row.names = c(1L, 2L, 3L, 4L, 5L, 18L), class =
"data.frame")
result <- structure(list(Year = c(1, 0, 3, 1, 0, 2), Month = c(1, 11,
11,
1, 8, 0), City = structure(c(3L, 4L, 5L, 1L, 2L, 5L), .Label =
c("Ashburn",
"Cork, Ireland", "Philadelphia", "Seattle", "Washington D.C."
), class = "factor")), .Names = c("Year", "Month", "City"), row.names
= c(NA,
-6L), class = "data.frame")
您可以执行以下操作:
z = regmatches(x = df$Time.in.current.role, gregexpr("\d+", df$Time.in.current.role))
years = sapply(z, function(x){ifelse(length(x)==1, 0, x[1])})
months = sapply(z, function(x){ifelse(length(x)==1, x[1], x[2])})
这给出:
> years
[1] "1" "0" "3" "1" "0"
> months
[1] "1" "11" "11" "1" "8"
如果有一个或两个数字,此方法有效。如果只有一个,则假定它对应于月份。这不起作用的情况是,例如,"5 years"
.
在这种情况下,您可以执行以下操作:
m = regmatches(x = df$Time.in.current.role, gregexpr("\d+ m", df$Time.in.current.role))
y = regmatches(x = df$Time.in.current.role, gregexpr("\d+ y", df$Time.in.current.role))
y2 = sapply(y, function(x){ifelse(length(x)==0,0,gsub("\D+","",x))})
m2 = sapply(m, function(x){ifelse(length(x)==0,0,gsub("\D+","",x))})
示例:
> df
Time.in.current.role City
1 1 year 1 month Philadelphia
2 11 months Seattle
3 3 years 11 months Washington D.C.
4 1 year 1 month Ashburn
5 8 months Cork, Ireland
6 5 years Miami
> y2
[1] "1" "0" "3" "1" "0" "5"
> m2
[1] "1" "11" "11" "1" "8" "0"
另一种方法是使用包 splitstackshape
将列一分为二。为此,您首先需要使用 gsub 在年和月之间设置分隔符,然后删除所有字符,然后使用 cSplit
:
# replace delimiter year with ;
df$Time.in.current.role <- gsub("year", ";", df$Time.in.current.role)
# If no year was found add 0; at the beginning of the cell
df$Time.in.current.role[!grepl(";", df$Time.in.current.role)] <- paste0("0;", df$Time.in.current.role[!grepl(";", df$Time.in.current.role)])
# remove characters and whitespace
df$Time.in.current.role <- gsub("[[:alpha:]]|\s+", "", df$Time.in.current.role)
# Split column by ;
df <- splitstackshape::cSplit(df, "Time.in.current.role", sep = ";")
# Rename new columns
colnames(df)[2:3] <- c("Year", "Month")
df
City Year Month
1: Philadelphia 1 1
2: Seattle 0 11
3: Washington D.C. 3 11
4: Ashburn 1 1
5: Cork, Ireland 0 8
快速又肮脏的解决方案:
代码:
ym <- gsub("[^0-9|^ ]", "", df$Time.in.current.role)
ym <- gsub("^ | $", "", ym)
df$Year <- ifelse(
grepl(" ", ym),
gsub("([0-9]+) .+", "\1", ym),
0
)
df$Month <- gsub(".+ ([0-9]+)$", "\1", ym)
df$Time.in.current.role <- NULL
df
City Year Month
1 Philadelphia 1 1
2 Seattle 0 11
3 Washington D.C. 3 11
4 Ashburn 1 1
5 Cork, Ireland 0 8
字数:
- 首先删除不是数字或 space
的所有内容
- 删除字符串开头或结尾的所有 spaces
- 如果字符串包含两个数字则提取第一个作为年份,否则
year = 0
.
- 最后一个数字总是月份。
- 从 data.frame
中删除原始列
- 尽情享受
这定义了一个函数extr
(另请参阅末尾的替代定义),它将从其第一个参数中提取与第二个参数的捕获组的匹配,即与括号内正则表达式部分的匹配.然后将匹配转换为数字,或者如果未找到模式,则返回 0。
只有3行代码,在处理年和月的方式上具有令人愉悦的对称性,不仅可以处理年和月,还可以只处理年和月。它允许在 y 和 m 之前出现垃圾,例如问题示例数据中显示的 \n。
library(gsubfn)
extr <- function(x, pat) strapply(x, pat, as.numeric, empty = 0, simplify = TRUE)
transform(df, Year = extr(Time.in.current.role, "(\d+) +\W*y"),
Month = extr(Time.in.current.role, "(\d+) +\W*m"))
给出(对于问题中定义的数据框):
Time.in.current.role City Year Month
1 1 year 1 month Philadelphia 1 1
2 11 \nmonths Seattle 0 11
3 3 years 11 months Washington D.C. 3 11
4 1 year 1 month Ashburn 1 1
5 8 months Cork, Ireland 0 8
请注意,strapply
默认使用 tcl 正则表达式引擎,但如果 tcltk 在您的系统上不起作用,则使用这个稍长的 extr
版本,或者更好的方法是修复您的安装,因为tcltk 是一个基础包,如果它不起作用,你的 R 安装就坏了。
extr <- function(x, pat) {
sapply(strapply(x, pat, as.numeric), function(x) if (is.null(x)) 0 else x)
}
我有一个数据框,它有一个文本字段,用于记录一个人在一个城市停留的时间。格式为 y year(s) m month(s)
,y 和 m 为数字。如果此人在城市居住时间少于一年,则该值只会采用 m months
我想将此列转换为两个单独的数字列,一个显示生活的年数,另一个显示生活的月份。
这是我的数据框示例:
df <- structure(list(Time.in.current.role = c("1 year 1 month", "11
months",
"3 years 11 months", "1 year 1 month", "8 months"), City =
c("Philadelphia",
"Seattle", "Washington D.C.", "Ashburn", "Cork, Ireland")), .Names =
c("Time.in.current.role",
"City"), row.names = c(NA, 5L), class = "data.frame")
我的愿望数据框如下所示:
result <- structure(list(Year = c(1, 0, 3, 1, 0), Month = c(1, 11,
11,
1, 8), City = structure(c(3L, 4L, 5L, 1L, 2L), .Label = c("Ashburn",
"Cork, Ireland", "Philadelphia", "Seattle", "Washington D.C."
), class = "factor")), .Names = c("Year", "Month", "City"), row.names
= c(NA,
-5L), class = "data.frame")
我正在考虑使用 grep 来定位哪些行中包含子字符串 "year" 以及哪些行中包含子字符串 "month"。但在那之后,我在尝试获取与 "year" 或 "month".
适当关联的数字时遇到了麻烦* 编辑 *
在我原来的 post 中,我忘了考虑可能只有 y year(s)
的情况。这是新的原始数据框和所需的数据框:
df <- structure(list(Time.in.current.role = c("1 year 1 month", "11
months",
"3 years 11 months", "1 year 1 month", "8 months", "2 years"),
City = c("Philadelphia", "Seattle", "Washington D.C.", "Ashburn",
"Cork, Ireland", "Washington D.C.")), .Names =
c("Time.in.current.role",
"City"), row.names = c(1L, 2L, 3L, 4L, 5L, 18L), class =
"data.frame")
result <- structure(list(Year = c(1, 0, 3, 1, 0, 2), Month = c(1, 11,
11,
1, 8, 0), City = structure(c(3L, 4L, 5L, 1L, 2L, 5L), .Label =
c("Ashburn",
"Cork, Ireland", "Philadelphia", "Seattle", "Washington D.C."
), class = "factor")), .Names = c("Year", "Month", "City"), row.names
= c(NA,
-6L), class = "data.frame")
您可以执行以下操作:
z = regmatches(x = df$Time.in.current.role, gregexpr("\d+", df$Time.in.current.role))
years = sapply(z, function(x){ifelse(length(x)==1, 0, x[1])})
months = sapply(z, function(x){ifelse(length(x)==1, x[1], x[2])})
这给出:
> years
[1] "1" "0" "3" "1" "0"
> months
[1] "1" "11" "11" "1" "8"
如果有一个或两个数字,此方法有效。如果只有一个,则假定它对应于月份。这不起作用的情况是,例如,"5 years"
.
在这种情况下,您可以执行以下操作:
m = regmatches(x = df$Time.in.current.role, gregexpr("\d+ m", df$Time.in.current.role))
y = regmatches(x = df$Time.in.current.role, gregexpr("\d+ y", df$Time.in.current.role))
y2 = sapply(y, function(x){ifelse(length(x)==0,0,gsub("\D+","",x))})
m2 = sapply(m, function(x){ifelse(length(x)==0,0,gsub("\D+","",x))})
示例:
> df
Time.in.current.role City
1 1 year 1 month Philadelphia
2 11 months Seattle
3 3 years 11 months Washington D.C.
4 1 year 1 month Ashburn
5 8 months Cork, Ireland
6 5 years Miami
> y2
[1] "1" "0" "3" "1" "0" "5"
> m2
[1] "1" "11" "11" "1" "8" "0"
另一种方法是使用包 splitstackshape
将列一分为二。为此,您首先需要使用 gsub 在年和月之间设置分隔符,然后删除所有字符,然后使用 cSplit
:
# replace delimiter year with ;
df$Time.in.current.role <- gsub("year", ";", df$Time.in.current.role)
# If no year was found add 0; at the beginning of the cell
df$Time.in.current.role[!grepl(";", df$Time.in.current.role)] <- paste0("0;", df$Time.in.current.role[!grepl(";", df$Time.in.current.role)])
# remove characters and whitespace
df$Time.in.current.role <- gsub("[[:alpha:]]|\s+", "", df$Time.in.current.role)
# Split column by ;
df <- splitstackshape::cSplit(df, "Time.in.current.role", sep = ";")
# Rename new columns
colnames(df)[2:3] <- c("Year", "Month")
df
City Year Month
1: Philadelphia 1 1
2: Seattle 0 11
3: Washington D.C. 3 11
4: Ashburn 1 1
5: Cork, Ireland 0 8
快速又肮脏的解决方案:
代码:
ym <- gsub("[^0-9|^ ]", "", df$Time.in.current.role)
ym <- gsub("^ | $", "", ym)
df$Year <- ifelse(
grepl(" ", ym),
gsub("([0-9]+) .+", "\1", ym),
0
)
df$Month <- gsub(".+ ([0-9]+)$", "\1", ym)
df$Time.in.current.role <- NULL
df
City Year Month
1 Philadelphia 1 1
2 Seattle 0 11
3 Washington D.C. 3 11
4 Ashburn 1 1
5 Cork, Ireland 0 8
字数:
- 首先删除不是数字或 space 的所有内容
- 删除字符串开头或结尾的所有 spaces
- 如果字符串包含两个数字则提取第一个作为年份,否则
year = 0
. - 最后一个数字总是月份。
- 从 data.frame 中删除原始列
- 尽情享受
这定义了一个函数extr
(另请参阅末尾的替代定义),它将从其第一个参数中提取与第二个参数的捕获组的匹配,即与括号内正则表达式部分的匹配.然后将匹配转换为数字,或者如果未找到模式,则返回 0。
只有3行代码,在处理年和月的方式上具有令人愉悦的对称性,不仅可以处理年和月,还可以只处理年和月。它允许在 y 和 m 之前出现垃圾,例如问题示例数据中显示的 \n。
library(gsubfn)
extr <- function(x, pat) strapply(x, pat, as.numeric, empty = 0, simplify = TRUE)
transform(df, Year = extr(Time.in.current.role, "(\d+) +\W*y"),
Month = extr(Time.in.current.role, "(\d+) +\W*m"))
给出(对于问题中定义的数据框):
Time.in.current.role City Year Month
1 1 year 1 month Philadelphia 1 1
2 11 \nmonths Seattle 0 11
3 3 years 11 months Washington D.C. 3 11
4 1 year 1 month Ashburn 1 1
5 8 months Cork, Ireland 0 8
请注意,strapply
默认使用 tcl 正则表达式引擎,但如果 tcltk 在您的系统上不起作用,则使用这个稍长的 extr
版本,或者更好的方法是修复您的安装,因为tcltk 是一个基础包,如果它不起作用,你的 R 安装就坏了。
extr <- function(x, pat) {
sapply(strapply(x, pat, as.numeric), function(x) if (is.null(x)) 0 else x)
}