Identifying, extracting and counting patterns in sequences - r

Hello lovely and nice people of SO, I'm working with a data-frame that contains only two columns one column corresponds to a Unique ID generated by a Virtual Machine and the second column contains a name but this particularly column may also contain the string "ERROR" and the objective is to create a script that will allow us to identify every time the string "ERROR" is found and capture the last and following names around it and also the unique ID assigned to the string "ERROR", to illustrate lets look at the following example:
If I have this data
ID
NAMES
1
James
3
ERROR
6
Keras
88
Kelly
53
Micheal
55
ERROR
7
Cindy
834
Keras
Then we would like to have come up with the following list:
ID
NAMES
3
James-Keras
55
Micheal-Cindy
This is because the first string "ERROR" found had an ID of 3 and was between the names James (before ERROR) and Keras (After ERROR) the next "ERROR" had an ID of 55 and was between Micheal and Cindy what if "ERROR" is a the top of the list or the bottom then we should only include whatever name we find it is ok to have lets say " NA-NAME" is ERROR was found at the top...
But here is where it gets tricky if we ever run into a sequence with consecutive strings "ERROR" we should always use as a "guide" the very last one in descending order for instance:
If I have this data set
ID
NAMES
1
James
3
ERROR
6
ERROR
88
ERROR
53
Jude
55
ERROR
7
Cindy
834
Keras
then we will want to have
ID
NAMES
88
James-Jude
55
Jude-Cindy
and this is because the string ERROR was repeated 3 times consecutively but the last one was at ID 88 so that means that we'll take that as a reference and record the names before and after it, another way of seeing this is to view the strings "ERROR" as a block so we'll record the names before and after each block of strings "ERROR"
Thank you so much to everyone that is trying to help me out I'd really appreciate if you can reference a book or functions that could help me out thank you so much.

We may create a function to do this
f1 <- function(dat) {
subdat1 <- subset(dat, !duplicated(with(rle(NAMES == "ERROR"),
rep(seq_along(values), lengths)), fromLast = TRUE))
subdat2 <- subset(dat, !duplicated(with(rle(NAMES == "ERROR"),
rep(seq_along(values), lengths))))
ind <- which(subdat1$NAMES == "ERROR")
do.call(rbind, lapply(ind[c(TRUE, diff(ind) > 1)], function(i)
data.frame(ID = subdat1$ID[i],NAMES = paste(subdat1$NAMES[i-1],
subdat2$NAMES[i+1], sep="-"))))
}
-testing
> f1(df1)
ID NAMES
1 3 James-Keras
2 55 Micheal-Cindy
> f1(df2)
ID NAMES
1 88 James-Jude
2 55 Jude-Cindy
data
df1 <- structure(list(ID = c(1L, 3L, 6L, 88L, 53L, 55L, 7L, 834L), NAMES = c("James",
"ERROR", "Keras", "Kelly", "Micheal", "ERROR", "Cindy", "Keras"
)), class = "data.frame", row.names = c(NA, -8L))
df2 <- structure(list(ID = c(1L, 3L, 6L, 88L, 53L, 55L, 7L, 834L), NAMES = c("James",
"ERROR", "ERROR", "ERROR", "Jude", "ERROR", "Cindy", "Keras")),
class = "data.frame", row.names = c(NA,
-8L))

Related

Paste value from for loop into data frame R

I have two dataframes in R, recurrent and L1HS. I am trying to find a way to do this:
If a sequence in recurrent matches sequence in L1HS, paste a value from a column in recurrent into new column in L1HS.
The recurrent dataframe looks like this:
> head(recurrent)
chr start end X Y level unique
1: chr4 56707846 56708347 0 38 03 chr4_56707846_56708347
2: chr1 20252181 20252682 0 37 03 chr1_20252181_20252682
3: chr2 224560903 224561404 0 37 03 chr2_224560903_224561404
4: chr5 131849595 131850096 0 36 03 chr5_131849595_131850096
5: chr7 46361610 46362111 0 36 03 chr7_46361610_46362111
6: chr1 20251169 20251670 0 36 03 chr1_20251169_20251670
The L1HS dataset contains many columns containing genetic sequence basepairs and a column "Sequence" that should hopefully have some matches with "unique" in the recurrent data frame, like so:
> head(L1HS$Sequence)
"chr1_35031657_35037706"
"chr1_67544575_67550598"
"chr1_81404889_81410942"
"chr1_84518073_84524089"
"chr1_87144764_87150794"
I know how to search for matches using
test <- recurrent$unique %in% L1HS$Sequence
to get the Booleans:
> head(test)
[1] FALSE FALSE FALSE FALSE FALSE FALSE
But I have a couple of problems from here. If the sequence is found, I want to copy the "level" value from the recurrent dataset to the L1HS dataset in a new column. For example, if the sequence "chr4_56707846_56708347" from the recurrent data was found in the full-length data, I'd like the full-length data frame to look like:
Sequence level other_columns
chr4_56707846_56708347 03 gggtttcatgaccc....
I was thinking of trying something like:
for (i in L1HS){
if (recurrent$unique %in% L1HS$Sequence{
L1HS$level <- paste(recurrent$level[i])}
}
but of course this isn't working and I can't figure it out.
I am wondering what the best approach is here! I'm wondering if merge/intersect/apply might be easier/better, or just what best practice might look like for a somewhat simple question like this. I've found some similar examples for Python/pandas, but am stuck here.
Thanks in advance!
You can do a simple left_join to add level to L1HS with dplyr.
library(dplyr)
L1HS %>%
left_join(., recurrent %>% select(unique, level), by = c("Sequence" = "unique"))
Or with merge:
merge(x=L1HS,y=recurrent[, c("unique", "level")], by.x = "Sequence", by.y = "unique",all.x=TRUE)
Output
Sequence level
1 chr1_35031657_35037706 4
2 chr1_67544575_67550598 2
3 chr1_81404889_81410942 NA
4 chr1_84518073_84524089 3
5 chr1_87144764_87150794 NA
*Note: This will still retain all the columns in L1HS. I just didn't create any additional columns in the example data below.
Data
recurrent <- structure(list(chr = c("chr4", "chr1", "chr2", "chr5", "chr7",
"chr1"), start = c(56707846L, 20252181L, 224560903L, 131849595L,
46361610L, 20251169L), end = c(56708347L, 20252682L, 224561404L,
131850096L, 46362111L, 20251670L), X = c(0L, 0L, 0L, 0L, 0L,
0L), Y = c(38L, 37L, 37L, 36L, 36L, 36L), level = c(3L, 2L, 3L,
3L, 3L, 4L), unique = c("chr4_56707846_56708347", "chr1_67544575_67550598",
"chr2_224560903_224561404", "chr5_131849595_131850096", "chr1_84518073_84524089",
"chr1_35031657_35037706")), class = "data.frame", row.names = c(NA,
-6L))
L1HS <- structure(list(Sequence = c("chr1_35031657_35037706", "chr1_67544575_67550598",
"chr1_81404889_81410942", "chr1_84518073_84524089", "chr1_87144764_87150794"
)), class = "data.frame", row.names = c(NA, -5L))

Merging two columns into one based on value

I have a dataset with two columns containing the following: an indicator number and a hashcode
The only problem is that the columns have the same name, but the value can switch columns.
Now I want to merge the columns and keep the number (I don't care about the hashcode)
I saw this question: Merge two columns into one in r
and I tried the coalesce() function, but that is only for having NA values. Which I don't have. I looked at the unite function, but according to the cheat sheet documentation documentation here that doesn't what I'm looking for
My next try was the filter_at and other filter functions from the dplyr package Documentation here
But that only leaves 150 data points while at the start I have 61k data points.
Code of filter_at I tried:
data <- filter_at(data,vars("hk","hk_1"),all_vars(.>0))
I assumed that a #-string shall not be greater than 0, which seems to be true, but it removes more than intented.
I would like to keep hk or hk_1 value which is a number. The other one (the hash) can be removed. Then I want a new column which only contains those numbers.
Sample data
My data looks like this:
HK|HK1
190|#SP0839
190|#SP0340
178|#SP2949
#SP8390|177
#SP2240|212
What I would like to see:
HK
190
190
178
177
212
I hope this provides an insight into the data. There are more columns like description, etc which makes that 190 at the start are not doubles.
We can replace all the values that start with "#" to NA and then use coalesce to select non-NA value between HK and HK1.
library(dplyr)
df %>%
mutate_all(~as.character(replace(., grepl("^#", .), NA))) %>%
mutate(HK = coalesce(HK, HK1)) %>%
select(HK)
# HK
#1 190
#2 190
#3 178
#4 177
#5 212
data
df <- structure(list(HK = structure(c(4L, 4L, 3L, 2L, 1L), .Label = c("#SP2240",
"#SP8390", "178", "190"), class = "factor"), HK1 = structure(c(2L,
1L, 3L, 4L, 5L), .Label = c("#SP0340", "#SP0839", "#SP2949",
"177", "212"), class = "factor")), class = "data.frame", row.names = c(NA, -5L))

Regex expression exceptions in subsetting data with grepl

I'm trying to subset data in R by certain characters in a field and cannot find the correct regex logic to get what I need. I need to subset records for which the ID contains either:
Just "AB"
"AB" and "ABC"
But NOT fields with ONLY "ABC"
These patterns fall within any part of the field (beginning, middle, end) in this data set and have no certain separators.
Example dataset TEST:
Record ID value
1 blueAB_ABC 7
2 green_ABCblue 9
3 ABC_green 45
4 green_AB 23
5 CD_red 45
So for this example I would want to subset records 1 and 4.
I've gotten as far as returning those with just AB and excluding ABC, but cannot seem to find the proper regex to get all with "AB" and potentially "ABC".
AB_set <- subset(TEST, grepl("*AB", ID) & !grepl("*ABC", ID) )
Record ID value
4 green_AB 23
What I'm hoping to get:
Record ID value
1 blueAB_ABC 7
4 green_AB 23
EDIT: Just to clarify, I updated the dataset to show that the pattern in question may fall next to other characters than an underscore, or may not necessarily occur at the beginning/end (as previously noted, "no certain separators").
You can get this by specifying that "AB" should be surrounded by either underscore or a word boundary.
df[grepl("(\\b|_)AB(\\b|_)", df$ID),]
Record ID value
1 1 blue_AB_ABC 7
4 4 green_AB 23
"ABC" is not needed because "AB" is always required to be matched. The following matches AB only if it is surrounded by underscore or it starts or ends an ID:
AB_set <- subset(TEST, grepl("(^|_)AB(_|$)", TEST$ID))
Result:
Record ID value
1 1 blue_AB_ABC 7
4 4 green_AB 23
Data:
TEST = structure(list(Record = 1:5, ID = structure(c(2L, 5L, 1L, 4L,
3L), .Label = c("ABC_green", "blue_AB_ABC", "CD_red", "green_AB",
"green_ABC_blue"), class = "factor"), value = c(7L, 9L, 45L,
23L, 45L)), .Names = c("Record", "ID", "value"), class = "data.frame", row.names = c(NA,
-5L))

split dataset by day and save it as data frame

I have a dataset with 2 months of data (month of Feb and March). Can I know how can I split the data into 59 subsets of data by day and save it as data frame (28 days for Feb and 31 days for Mar)? Preferably to save the data frame in different name according to the date, i.e. 20140201, 20140202 and so forth.
df <- structure(list(text = structure(c(4L, 6L, 5L, 2L, 8L, 1L), .Label = c(" Terpilih Jadi Maskapai dengan Pelayanan Kabin Pesawat cont",
"booking number ZEPLTQ I want to cancel their flight because they can not together with my wife and kids",
"Can I change for the traveler details because i choose wrongly for the Mr or Ms part",
"cant do it with cards either", "Coming back home AK", "gotta try PNNL",
"Jadwal penerbangan medanjktsblm tangalmasi ada kah", "Me and my Tart would love to flyLoveisintheAir",
"my flight to Bangkok onhas been rescheduled I couldnt perform seat selection now",
"Pls checks his case as money is not credited to my bank acctThanks\n\nCASLTP",
"Processing fee Whatt", "Tacloban bound aboardto get them boats Boats boats boats Tacloban HeartWork",
"thanks I chatted with ask twice last week and told the same thing"
), class = "factor"), created = structure(c(1L, 1L, 2L, 2L, 3L,
3L), .Label = c("1/2/2014", "2/2/2014", "5/2/2014", "6/2/2014"
), class = "factor")), .Names = c("text", "created"), row.names = c(NA,
6L), class = "data.frame")
You don't need to output multiple dataframes. You only need to select/subset them by year&month of the 'created' field. So here are two ways do do that: 1. is simpler if you don't plan on needing any more date-arithmetic
# 1. Leave 'created' a string, just use text substitution to extract its month&date components
df$created_mthyr <- gsub( '([0-9]+/)[0-9]+/([0-9]+)', '\\1\\2', df$created )
# 2. If you need to do arbitrary Date arithmetic, convert 'created' field to Date object
# in this case you need an explicit format-string
df$created <- as.Date(df$created, '%M/%d/%Y')
# Now you can do either a) split
split(df, df$created_mthyr)
# specifically if you want to assign the output it creates to 3 dataframes:
df1 <- split(df, df$created_mthyr)[[1]]
df2 <- split(df, df$created_mthyr)[[2]]
df5 <- split(df, df$created_mthyr)[[3]]
# ...or else b) do a Split-Apply-Combine and perform arbitrary command on each separate subset. This is very powerful. See plyr/ddply documentation for examples.
require(plyr)
df1 <- dlply(df, .(created_mthyr))[[1]]
df2 <- dlply(df, .(created_mthyr))[[2]]
df5 <- dlply(df, .(created_mthyr))[[3]]
# output looks like this - strictly you might not want to keep 'created','created_mthyr':
> df1
# text created created_mthyr
#1 cant do it with cards either 1/2/2014 1/2014
#2 gotta try PNNL 1/2/2014 1/2014
> df2
#3
#Coming back home AK
#4 booking number ZEPLTQ I want to cancel their flight because they can not together with my wife and kids
# created created_mthyr
#3 2/2/2014 2/2014
#4 2/2/2014 2/2014

Calculating subtotals (sum, stdev, average etc)

I have been searching for this for a while, but haven't been able to find a clear answer so far. Probably have been looking for the wrong terms, but maybe somebody here can quickly help me. The question is kind of basic.
Sample data set:
set <- structure(list(VarName = structure(c(1L, 5L, 4L, 2L, 3L),
.Label = c("Apple/Blue/Nice",
"Apple/Blue/Ugly", "Apple/Pink/Ugly", "Kiwi/Blue/Ugly", "Pear/Blue/Ugly"
), class = "factor"), Color = structure(c(1L, 1L, 1L, 1L, 2L), .Label = c("Blue",
"Pink"), class = "factor"), Qty = c(45L, 34L, 46L, 21L, 38L)), .Names = c("VarName",
"Color", "Qty"), class = "data.frame", row.names = c(NA, -5L))
This gives a data set like:
set
VarName Color Qty
1 Apple/Blue/Nice Blue 45
2 Pear/Blue/Ugly Blue 34
3 Kiwi/Blue/Ugly Blue 46
4 Apple/Blue/Ugly Blue 21
5 Apple/Pink/Ugly Pink 38
What I would like to do is fairly straight forward. I would like to sum (or averages or stdev) the Qty column. But, also I would like to do the same operation under the following conditions:
VarName includes "Apple"
VarName includes "Ugly"
Color equals "Blue"
Anybody that can give me a quick introduction on how to perform this kind of calculations?
I am aware that some of it can be done by the aggregate() function, e.g.:
aggregate(set[3], FUN=sum, by=set[2])[1,2]
However, I believe that there is a more straight forward way of doing this then this. Are there some filters that can be added to functions like sum()?
The easiest way to to split up your VarName column, then subsetting becomes very easy. So, lets create an object were varName has been separated:
##There must(?) be a better way than this. Anyone?
new_set = t(as.data.frame(sapply(as.character(set$VarName), strsplit, "/")))
Brief explanation:
We use as.character because set$VarName is a factor
sapply takes each value in turn and applies strplit
The strsplit function splits up the elements
We convert to a data frame
Transpose to get the correct rotation
Next,
##Convert to a data frame
new_set = as.data.frame(new_set)
##Make nice rownames - not actually needed
rownames(new_set) = 1:nrow(new_set)
##Add in the Qty column
new_set$Qty = set$Qty
This gives
R> new_set
V1 V2 V3 Qty
1 Apple Blue Nice 45
2 Pear Blue Ugly 34
3 Kiwi Blue Ugly 46
4 Apple Blue Ugly 21
5 Apple Pink Ugly 38
Now all the operations are as standard. For example,
##Add up all blue Qtys
sum(new_set[new_set$V2 == "Blue",]$Qty)
[1] 146
##Average of Blue and Ugly Qtys
mean(new_set[new_set$V2 == "Blue" & new_set$V3 == "Ugly",]$Qty)
[1] 33.67
Once it's in the correct form, you can use ddply which does every you want (and more)
library(plyr)
##Split the data frame up by V1 and take the mean of Qty
ddply(new_set, .(V1), summarise, m = mean(Qty))
##Split the data frame up by V1 & V2 and take the mean of Qty
ddply(new_set, .(V1, V2), summarise, m = mean(Qty))
Is this what you're looking for?
# sum for those including 'Apple'
apple <- set[grep('Apple', set[, 'VarName']), ]
aggregate(apple[3], FUN=sum, by=apple[2])
Color Qty
1 Blue 66
2 Pink 38
# sum for those including 'Ugly'
ugly <- set[grep('Ugly', set[, 'VarName']), ]
aggregate(ugly[3], FUN=sum, by=ugly[2])
Color Qty
1 Blue 101
2 Pink 38
# sum for Color==Blue
sum(set[set[, 'Color']=='Blue', 3])
[1] 146
The last sum could be done by using subset
sum(subset(set, Color=='Blue')[,3])

Resources