Extract certain part of name in string - r

Im trying to extract particular part of names in a column of DF
DF
a b
a.b.c_tot 1
b.c.d_tot 2
d.e.g_tot 3
I need to extract letter between . and _tot, so that
DF
a b c
a.b.c_tot 1 c
b.c.d_tot 2 d
d.e.g_tot 3 g
I suppose it could be done with sub as i have learnt today how to extract the letter before first ., but how to extract "middle" part of the name?
I was reading sub explanation and help but all my trials results in just copying full name of a to c.
Thank you for any tips.

We can call sub() to match the entire string, starting with (1) any number of any characters, then (2) a literal dot, then (3) use a capture group to capture the following character, then (4) a literal _tot. We can then use the \1 backreference atom (with the backslash properly backslash-escaped as per R's string encoding rules) to replace the entire string with the captured character.
DF$c <- sub('^.*\\.(.)_tot$','\\1',DF$a);
DF;
## a b c
## 1 a.b.c_tot 1 c
## 2 b.c.d_tot 2 d
## 3 d.e.g_tot 3 g
Yes, I see the problem; if DF$a were to contain values that do not match the expected pattern, the sub() call would pass them through to the new DF$c column. Here's a hacky solution using the Perl branch reset feature:
DF <- data.frame(a=c('a.b.c_tot','b.c.d_tot','d.e.g_tot','non-matching'),b=c(1L,2L,3L,4L),stringsAsFactors=F);
DF$c <- sub(perl=T,'(?|^.*\\.(.)_tot$|^.*$())','\\1',DF$a);
DF;
## a b c
## 1 a.b.c_tot 1 c
## 2 b.c.d_tot 2 d
## 3 d.e.g_tot 3 g
## 4 non-matching 4
Here's a better solution, involving storing the regex in a variable in advance, and using grepl() and replace() to replace non-matching values with NA prior to calling sub():
re <- '^.*\\.(.)_tot$';
DF$c <- sub(re,'\\1',replace(DF$a,!grepl(re,DF$a),NA));
DF;
## a b c
## 1 a.b.c_tot 1 c
## 2 b.c.d_tot 2 d
## 3 d.e.g_tot 3 g
## 4 non-matching 4 <NA>

Use regexpr and regmatches with a lookbehind and lookahead regex.
x <- c("a.b.c_tot", "b.c.d_tot", "d.e.g_tot")
regmatches(x, regexpr("(?<=\\.).(?=_tot)", x, perl = TRUE))
#[1] "c" "d" "g"

We can use str_extract
library(stringr)
DF$c <- str_extract(DF$a, "\\w(?=_tot)")
DF$c
#[1] "c" "d" "g"

Related

How to find the matched names in different datasets' columns?

I have 2 datasets with columns having the same names.
a:
A B C
1 2 3
5 6 7
b:
B E A
2 3 4
9 1 2
How can I find the column indices with the matched names?
I have tried converting them from wide to long format by using gather() respectively and matching both datasets with match(a,b). It didn't work.
#Find common column names in the two dataframes
intersect(names(a), names(b))
#[1] "A" "B"
#Find the column number in a which is present in b
which(names(a) %in% names(b))
#[1] 1 2
#find the column number in b which is present in a
which(names(b) %in% names(a))
#[1] 1 3
I personally like to use grep for this
grep(pattern = paste(names(a), collapse = "|") , x = names(b))

Search Position of a word in String

I need to do a small exercise in R and I need to know how many times that one specific word appears in string and wherein the position of each of the words.
I have this:
string = 'a b a b c d a a g'
splitstring = strsplit(string, ' ')
sapply(gregexpr("a", splitstring, fixed= TRUE), function(x) sum(x>-1))
My output is: [1] 4, so I have four 'a' in my string and now I wanted to know their position.
gregexpr gives you the positions:
gregexpr("a", string, f=T)[[1]]
# [1] 1 5 13 15
One solution is using the stringr packages location function as follows:
library(stringr)
string = 'a b a b c d a a g'
l <- str_locate_all(string, 'a')
l
This gives an output in the form of a list of matrix(ces) of all start and end positions as follows:
[[1]]
start end
[1,] 1 1
[2,] 5 5
[3,] 13 13
[4,] 15 15
If you want to extract just the start positions, you can do as follows:
l[[1]][, 'start']
[1] 1 5 13 15

match values in dataframes with values in a column

I have two data.frames that looks like these ones:
>df1
V1
a
b
c
d
e
>df2
V1 V2
1 a,k,l
2 c,m,n
3 z,b,s
4 l,m,e
5 t,r,d
I would like to match the values in df1$V1 with those from df2$V2and add a new column to df1 that corresponds to the matching and to the value of df2$V1, the desire output would be:
>df1
V1 V2
a 1
b 3
c 2
d 5
e 4
I've tried this approach but only works if df2$V2 contains just one element:
match(as.character(df1[,1]), strsplit(as.character(df2[,2], ",")) -> idx
df1$V2 <- df2[idx,1]
Many thanks
You can just use grep, which will return the position of the string found:
sapply(df1$V1, grep, x = df2$V2)
# a b c d e
# 1 3 2 5 4
If you expect repeats, you can use paste.
Let's modify your data so that there is a repeat:
df2$V2[3] <- "z,b,s,a"
And modify the solution accordingly:
sapply(df1$V1, function(z) paste(grep(z, x = df2$V2), collapse = ";"))
# a b c d e
# "1;3" "3" "2" "5" "4"
Similar to Tyler's answer, but in base using stack:
df.stack <- stack(setNames(strsplit(as.character(df2$V2), ","), df2$V1))
transform(df1, V2=df.stack$ind[match(V1, df.stack$values)])
produces:
V1 V2
1 a 1
2 b 3
3 c 2
4 d 5
5 e 4
One advantage of splitting over grep is that with grep you run the risk of searching for a and matching things like alabama, etc. (though you can be careful with the patterns to mitigate this (i.e. include word boundaries, etc.).
Note this will only find the first matching value.
Here's an approach:
library(qdap)
key <- setNames(strsplit(as.character(df2$V2), ","), df2$V1)
df1$V2 <- as.numeric(df1$V1 %l% key)
df1
## V1 V2
## 1 a 1
## 2 b 3
## 3 c 2
## 4 d 5
## 5 e 4
First we used strsplit to create a named list. Then we used qdap's lookup operator %l% to match values and create a new column (I converted to numeric though this may not be necessary).

Matching without replacement by id in R

In R, I can easily match unique identifiers using the match function:
match(c(1,2,3,4),c(2,3,4,1))
# [1] 4 1 2 3
When I try to match non-unique identifiers, I get the following result:
match(c(1,2,3,1),c(2,3,1,1))
# [1] 3 1 2 3
Is there a way to match the indices "without replacement", that is, each index appearing only once?
othermatch(c(1,2,3,1),c(2,3,1,1))
# [1] 3 1 2 4 # note the 4 where there was a 3 at the end
you're looking for pmatch
pmatch(c(1,2,3,1),c(2,3,1,1))
# [1] 3 1 2 4
A more naive approach -
library(data.table)
a <- data.table(p = c(1,2,3,1))
a[,indexa := .I]
b <- data.table(q = c(2,3,1,1))
b[,indexb := .I]
setkey(a,p)
setkey(b,q)
# since they are permutation, therefore cbinding the ordered vectors should return ab with ab[,p] = ab[,q]
ab <- cbind(a,b)
setkey(ab,indexa)
ab[,indexb]
#[1] 3 1 2 4

Renaming duplicate strings in R

I have an R dataframe that has two columns of strings. In one of the columns (say, Column1) there are duplicate values. I need to relabel that column so that it would have the duplicated strings renamed with ordered suffixes, like in the Column1.new
Column1 Column2 Column1.new
1 A 1_1
1 B 1_2
2 C 2_1
2 D 2_2
3 E 3
4 F 4
Any ideas of how to do this would be appreciated.
Cheers,
Antti
Let's say your data (ordered by Column1) is within an object called tab. First create a run length object
c1.rle <- rle(tab$Column1)
c1.rle
##lengths: int [1:4] 2 2 1 1
##values : int [1:4] 1 2 3 4
That gives you values of Column1 and the according number of appearences of each element. Then use that information to create the new column with unique identifiers:
tab$Column1.new <- paste0(rep(c1.rle$values, times = c1.rle$lengths), "_",
unlist(lapply(c1.rle$lengths, seq_len)))
Not sure, if this is appropriate in your situation, but you could also just paste together Column1 and Column2, to create an unique identifier...
May be a little more of a workaround, but parts of this may be more useful and simpler for someone with not quite the same needs. make.names with the unique=T attribute adds a dot and numbers names that are repeated:
x <- make.names(tab$Column1,unique=T)
> print(x)
[1] "X1" "X1.1" "X2" "X2.1" "X3" "X4"
This might be enough for some folks. Here you can then grab the first entries of elements that are repeated, but not elements that are not repeated, then add a .0 to the end.
y <- rle(tab$Column1)
tmp <- !duplicated(tab$Column1) & (tab$Column1 %in% y$values[y$lengths>1])
x[tmp] <- str_replace(x[tmp],"$","\\.0")
> print(x)
[1] "X1.0" "X1.1" "X2.0" "X2.1" "X3" "X4"
Replace the dots and remove the X
x <- str_replace(x,"X","")
x <- str_replace(x,"\\.","_")
> print(x)
[1] "1_0" "1_1" "2_0" "2_1" "3" "4"
Might be good enough for you. But if you want the indexing to start at 1, grab the numbers, add one then put them back.
z <- str_match(x,"_([0-9]*)$")[,2]
z <- as.character(as.numeric(z)+1)
x <- str_replace(x,"_([0-9]*)$",paste0("_",z))
> print(x)
[1] "1_1" "1_2" "2_1" "2_2" "3" "4"
Like I said, more of a workaround here, but gives some options.
d <- read.table(text='Column1 Column2
1 A
1 B
2 C
2 D
3 E
4 F', header=TRUE)
transform(d,
Column1.new = ifelse(duplicated(Column1) | duplicated(Column1, fromLast=TRUE),
paste(Column1, ave(Column1, Column1, FUN=seq_along), sep='_'),
Column1))
# Column1 Column2 Column1.new
# 1 1 A 1_1
# 2 1 B 1_2
# 3 2 C 2_1
# 4 2 D 2_2
# 5 3 E 3
# 6 4 F 4
#Cão answer only with base R:
x=read.table(text="
Column1 Column2 #Column1.new
1 A #1_1
1 B #1_2
2 C #2_1
2 D #2_2
3 E #3
4 F #4", stringsAsFactors=F, header=T)
string<-x$Column1
mstring <- make.unique(as.character(string) )
mstring<-sub("(.*)(\\.)([0-9]+)","\\1_\\3",mstring)
y <- rle(string)
tmp <- !duplicated(string) & (string %in% y$values[y$lengths>1])
mstring[tmp]<-gsub("(.*)","\\1_0", mstring[tmp])
end <- sub(".*_([0-9]+)","\\1",grep("_([0-9]*)$",mstring,value=T) )
beg <- sub("(.*_)[0-9]+","\\1",grep("_([0-9]*)$",mstring,value=T) )
newend <- as.numeric(end)+1
mstring[grep("_([0-9]*)$",mstring)]<-paste0(beg,newend)
x$Column1New<-mstring
x
It's a very old post, and I am probably missing something obvious, but what is wrong with(?):
tab$Column1 <- make.unique(tab$Column1.sep="_")
Albeit I believe this requires character input.

Resources