How to add the results of lapply or for loop to specific rows - r

I have a database with 5 variables (columns). I want to subset the data frame, in order to check if a certain value of one specific column is present. If this is the case, then assign 1, else 0. Then paste the result (1 or 0) to a specific column of the restricted data frame, and then continue.
The data frame looks like the following:
## Year Month Product Supermarket Price
## 2015 1 67 1 10
## 2015 1 65 1 11
## 2015 1 69 1 15
## 2015 2 65 2 20
## 2015 2 67 2 25
## 2015 2 67 3 15
## 2015 2 69 3 12
Now I want to restrict for each Year, Month and Supermarket and check if Product = 65 is present. If it is, then to assign 1 for the rows restricted in a new variable (column). If not, to assign 0.
I have tried using lapply:
prueba <- function(x)
ifelse(any(base$Product == 65), 1, 0)
lapply(unique(base$Supermarket) & unique(base$Year) & unique(base$Month),
base$NewVar <- prueba)
but have the following result
Error in rep(value, length.out = nrows) :
attempt to replicate an object of type 'closure'
Next, I try to make a for loop:
for(i in unique(base$Supermarket)) {
for(j in unique(base$Year))
for(h in unique(base$Month)) {
try <- ifelse(any((filter(base, Supermarket == i, Year == j, Month == h))$Product == 65), 1, 0)
base[base$Supermarket == i && base$Year ==j && base$Month == h,]$NewVar <- try
}
}
}
And have the following results:
Error in if (nrow(try) == 0) { : argument has zero lenght
I shall say that the database has 50 million rows, so speed is an issue here (so I try to use lapply instead of for loop)
I do not how to obtain the proper result, which should be like the following:
## Year Month Product Supermarket Price NewVar
## 2015 1 67 1 10 1
## 2015 1 65 1 11 1
## 2015 1 69 1 15 1
## 2015 2 65 2 20 1
## 2015 2 67 2 25 1
## 2015 2 67 3 15 0
## 2015 2 69 3 12 0
Do not know how to solve the whole problem. When using lapply I get the "right" answer, but then could not paste the result to the right rows in the dataframe.
Thanks in advance.

For fast operation, try to use data.table or dplyr. With data.table, you can simply create the new variable with logic check grouped by the Year, Month and Supermarket variables(suppose your original data frame is called df):
library(data.table)
setDT(df)[, NewVar := as.numeric(65 %in% Product), .(Year, Month, Supermarket)]
df
# Year Month Product Supermarket Price NewVar
# 1: 2015 1 67 1 10 1
# 2: 2015 1 65 1 11 1
# 3: 2015 1 69 1 15 1
# 4: 2015 2 65 2 20 1
# 5: 2015 2 67 2 25 1
# 6: 2015 2 67 3 15 0
# 7: 2015 2 69 3 12 0
Or correspondingly using dplyr: df <- df %>% group_by(Year, Month, Supermarket) %>% mutate(NewVar = as.numeric(65 %in% Product))

## read data
base <- c(2015, 1, 67, 1, 10,
2015, 1, 65, 1, 11,
2015, 1, 69, 1, 15,
2015, 2, 65, 2, 20,
2015, 2, 67, 2, 25,
2015, 2, 67, 3, 15,
2015, 2, 69, 3, 12)
base <- data.frame(matrix(base, 7, byrow = TRUE))
names(base) <- c('Year', 'Month', 'Product', 'Supermarket', 'Price')
Made a couple changes to function. I changed the object to match input (x) and specified the third element (since column of interest is column 3)
## create function
prueba <- function(x) ifelse(x[3] == 65, 1, 0)
To apply this function to each row, use the apply() function with 1 (for rows) apply(x, 1, function).
base$new_var <- apply(base, 1, prueba)
base
## Year Month Product Supermarket Price new_var
## 1 2015 1 67 1 10 0
## 2 2015 1 65 1 11 1
## 3 2015 1 69 1 15 0
## 4 2015 2 65 2 20 1
## 5 2015 2 67 2 25 0
## 6 2015 2 67 3 15 0
## 7 2015 2 69 3 12 0
You could also create a new variable and conditionally enter '1' to relevant rows. This is the way I'd do it:
base$new_var <- 0
base$new_var[base$Product == 65] <- 1
base
## Year Month Product Supermarket Price new_var
## 1 2015 1 67 1 10 0
## 2 2015 1 65 1 11 1
## 3 2015 1 69 1 15 0
## 4 2015 2 65 2 20 1
## 5 2015 2 67 2 25 0
## 6 2015 2 67 3 15 0
## 7 2015 2 69 3 12 0

We can do this easily in base R
df1$NewVar <- with(df1, ave(Product, Year, Month, Supermarket,
FUN= function(x) 65 %in% x))
df1$NewVar
#[1] 1 1 1 1 1 0 0

Related

R - replace all values smaller than a specific value in a column with the nearest bigger value

I have a data frame like this one:
df <- data.frame(c(1,2,3,4,5,6,7), c(0,23,55,0,1,40,21))
names(df) <- c("a", "b")
a b
1 0
2 23
3 55
4 0
5 1
6 40
7 21
Now I want to replace all values smaller than 22 in column b with the nearest bigger value. Of course it is possible to use loops, but since I have quite big datasets this is way too slow.
The solution should look somewhat like this:
a b
1 23
2 23
3 55
4 55
5 40
6 40
7 40
Here is a tidyverse possibility (but note #phiver's comment on replacement ambiguities)
library(tidyverse);
df %>%
mutate(b = ifelse(b < 22, NA, b)) %>%
fill(b) %>%
fill(b, .direction = "up");
# a b
#1 1 23
#2 2 23
#3 3 55
#4 4 55
#5 5 55
#6 6 40
#7 7 40
Explanation: Replace values b < 22 with NA and then use fill to fill NAs with previous/following non-NA entries.
Sample data
df <- data.frame(a = c(1,2,3,4,5,6,7), b = c(0,23,55,0,1,40,21))
You can use zoo::rollapply :
library(zoo)
df$b <- rollapply(df$b,3,function(x)
if (x[2] < 22) min(x[x>22]) else x[2],
partial =T)
# df
# a b
# 1 1 23
# 2 2 23
# 3 3 55
# 4 4 55
# 5 5 40
# 6 6 40
# 7 7 40
In base R you could do this for the same output:
transform(df, b = sapply(seq_along(b),function(i)
if (b[i] < 22) {
bi <- c(b,Inf)[seq(i-1,i+1)]
min(bi[bi>=22])
} else b[i]))

How to use apply function once for each unique factor value

I'm trying on some commands on the R-studio built-in databse, ChickWeight. The data looks as follows.
weight Time Chick Diet
1 42 0 1 1
2 51 2 1 1
3 59 4 1 1
4 64 6 1 1
5 76 8 1 1
6 93 10 1 1
7 106 12 1 1
8 125 14 1 1
9 149 16 1 1
10 171 18 1 1
11 199 20 1 1
12 205 21 1 1
13 40 0 2 1
14 49 2 2 1
15 58 4 2 1
Now what I would like to do is to simply output the difference between the chicken-weight for the "Chick" column for time 0 and 21 (last time value). I.e the weight the chick has put on.
I've been trying tapply(ChickWeight$weight, ChickWeight$Chick, function(x) x[length(x)] - x[1]). But this of course applies the value to all rows.
How do I make it so that it applies only once for each unique Chick-value?
If we need a single value per each 'factor' column (assuming that 'Chick', and 'Diet' are the factor columns)
library(data.table)
setDT(df1)[, list(Diff= abs(weight[Time==21]-weight[Time==0])) ,.(Chick, Diet)]
and If we need to create a column
setDT(df1)[, Diff:= abs(weight[Time==21]-weight[Time==0]) ,.(Chick, Diet)]
I noticed that in the example Time = 21 is not found in the Chick No:2, may be in that case, we need one of the number
setDT(df1)[, {tmp <- Time %in% c(0,21)
list(Diff= if(sum(tmp)>1) abs(diff(weight[tmp])) else weight[tmp]) } ,
by = .(Chick, Diet)]
# Chick Diet Diff
#1: 1 1 163
#2: 2 1 40
If we are taking the difference of 'weight' based on the max and min 'Time' for each group
setDT(df1)[, list(Diff=weight[which.max(Time)]-
weight[which.min(Time)]), .(Chick, Diet)]
# Chick Diet Diff
#1: 1 1 163
#2: 2 1 18
Also, if the 'Time' is ordered
setDT(df1)[, list(Diff= abs(diff(weight[c(1L,.N)]))), by =.(Chick, Diet)]
Using by from base R
by(df1[1:2], df1[3:4], FUN= function(x) with(x,
abs(weight[which.max(Time)]-weight[which.min(Time)])))
#Chick: 1
#Diet: 1
#[1] 163
#------------------------------------------------------------
#Chick: 2
#Diet: 1
#[1] 18
Here's a solution using dplyr:
ChickWeight %>%
group_by(Chick = as.numeric(as.character(Chick))) %>%
summarise(weight_gain = last(weight) - first(weight), final_time = last(Time))
(First and last as suggested by #ulfelder.)
Note that ChickWeight$Chick is an ordered factor so without coercing it into numeric the final order looks odd.
Using base R:
ChickWeight$Chick <- as.numeric(as.character(ChickWeight$Chick))
tapply(ChickWeight$weight, ChickWeight$Chick, function(x) x[length(x)] - x[1])

randomly select rows based on limited random numbers

Seems simple but I can't figure it out.
I have a bunch of animal location data (217 individuals) as a single dataframe. I'm trying to randomly select X locations per individual for further analysis with the caveat that X is within the range of 6-156.
So I'm trying to set up a loop that first randomly selects a value within the range of 6-156 then use that value (say 56) to randomly extract 56 locations from the first individual animal and so on.
for(i in unique(ANIMALS$ID)){
sub<-sample(6:156,1)
sub2<-i([sample(nrow(i),sub),])
}
This approach didn't seem to work so I tried tweaking it...
for(i in unique(ANIMALS$ID)){
sub<-sample(6:156,1)
rand<-i[sample(1:nrow(i),sub,replace=FALSE),]
}
This did not work either.. Any suggestions or previous postings would be helpful!
Head of the datafile...ANIMALS is the name of the df, ID indicates unique individuals
> FID X Y MONTH DAY YEAR HOUR MINUTE SECOND ELKYR SOURCE ID animalid
1 0 510313 4813290 9 5 2008 22 30 0 342008 FG 1 1
2 1 510382 4813296 9 6 2008 1 30 0 342008 FG 1 1
3 2 510385 4813311 9 6 2008 2 0 0 342008 FG 1 1
4 3 510385 4813394 9 6 2008 3 30 0 342008 FG 1 1
5 4 510386 4813292 9 6 2008 2 30 0 342008 FG 1 1
6 5 510386 4813431 9 6 2008 4 1 0 342008 FG 1 1
Here's one way using mapply. This function takes two lists (or something that can be coerced into a list) and applies function FUN to corresponding elements.
# simulate some data
xy <- data.frame(animal = rep(1:10, each = 10), loc = runif(100))
# calculate number of samples for individual animal
num.samples.per.animal <- sample(3:6, length(unique(xy$animal)), replace = TRUE)
num.samples.per.animal
[1] 6 3 4 4 6 3 3 6 3 5
# subset random x number of rows from each animal
result <- do.call("rbind",
mapply(num.samples.per.animal, split(xy, f = xy$animal), FUN = function(x, y) {
y[sample(1:nrow(y), x),]
}, SIMPLIFY = FALSE)
)
result
animal loc
7 1 0.99483999
1 1 0.50951321
10 1 0.36505294
6 1 0.34058842
8 1 0.26489107
9 1 0.47418823
13 2 0.27213396
12 2 0.28087775
15 2 0.22130069
23 3 0.33646632
21 3 0.02395097
28 3 0.53079981
29 3 0.85287600
35 4 0.84534073
33 4 0.87370167
31 4 0.85646813
34 4 0.11642335
46 5 0.59624723
48 5 0.15379729
45 5 0.57046122
42 5 0.88799675
44 5 0.62171858
49 5 0.75014593
60 6 0.86915983
54 6 0.03152932
56 6 0.66128549
64 7 0.85420774
70 7 0.89262455
68 7 0.40829671
78 8 0.19073661
72 8 0.20648832
80 8 0.71778913
73 8 0.77883677
75 8 0.37647108
74 8 0.65339300
82 9 0.39957202
85 9 0.31188471
88 9 0.10900795
100 10 0.55282999
95 10 0.10145296
96 10 0.09713218
93 10 0.64900866
94 10 0.76099256
EDIT
Here is another (more straightforward) approach that also handles cases when number of rows is less than the number of samples that should be allocated.
set.seed(357)
result <- do.call("rbind",
by(xy, INDICES = xy$animal, FUN = function(x) {
avail.obs <- nrow(x)
num.rows <- sample(3:15, 1)
while (num.rows > avail.obs) {
message("Sample to be larger than available data points, repeating sampling.")
num.rows <- sample(3:15, 1)
}
x[sample(1:avail.obs, num.rows), ]
}))
result
I like Stackoverflow because I learn so much. #RomanLustrik provided a simple solution; mine is straight-froward as well:
# simulate some data
xy <- data.frame(animal = rep(1:10, each = 10), loc = runif(100))
newVec <- NULL #Create a blank dataFrame
for(i in unique(xy$animal)){
#Sample a number between 1 and 10 (or 6 and 156, if you need)
samp <- sample(1:10, 1)
#Determine which rows of dataFrame xy correspond with unique(xy$animal)[i]
rows <- which(xy$animal == unique(xy$animal)[i])
#From xy, sample samp times from the rows associated with unique(xy$animal)[i]
newVec1 <- xy[sample(rows, samp, replace = TRUE), ]
#append everything to the same new dataFrame
newVec <- rbind(newVec, newVec1)
}

remove rows based on substraction results

I have a large data set like this:
df <- data.frame(group = c(rep(1, 6), rep(5, 6)), score = c(30, 10, 22, 44, 6, 5, 20, 35, 2, 60, 14,5))
group score
1 1 30
2 1 10
3 1 22
4 1 44
5 1 6
6 1 5
7 5 20
8 5 35
9 5 2
10 5 60
11 5 14
12 5 5
...
I want to do a subtraction for each neighboring score within each group, if the difference is greater than 30, remove the smaller score. For example, within group 1, 30-10=20<30, 10-22=-12<30, 22-44=-22<30, 44-6=38>30 (remove 6), 44-5=39>30 (remove 5)... The expected output should look like this:
group score
1 1 30
2 1 10
3 1 22
4 1 44
5 5 20
6 5 35
7 5 60
...
Does anyone have idea about realizing this?
Like this?
repeat {
df$diff=unlist(by(df$score,df$group,function(x)c(0,-diff(x))))
if (all(df$diff<30)) break
df <- df[df$diff<30,]
}
df$diff <- NULL
df
# group score
# 1 1 30
# 2 1 10
# 3 1 22
# 4 1 44
# 7 5 20
# 8 5 35
# 10 5 60
This (seems...) to require an iterative approach, because the "neighboring score" changes after removal of a row. So before you remove 6, the difference 44 - 6 > 30, but 6 - 5 < 30. After you remove 6, the difference 44 - 5 > 30.
So this calculates difference between successive rows by group (using by(...) and diff(...)), and removes the appropriate rows, then repeats the process until all differences are < 30.
It's not elegant but it should work:
out = data.frame(group = numeric(), score=numeric())
#cycle through the groups
for(g in levels(as.factor(df$group))){
temp = subset(df, df$group==g)
#now go through the scores
left = temp$score[1]
for(s in seq(2, length(temp$score))){
if(left - temp$score[s] > 30){#Test the condition
temp$score[s] = NA
}else{
left = temp$score[s] #if condition not met then the
}
}
#Add only the rows without NAs to the out
out = rbind(out, temp[which(!is.na(temp$score)),])
}
There should be a way to do this using ave but carrying the last value when removing the next if the diff >30 is tricky! I'd appreciate the more elegant solution if there is one.
You can try
df
## group score
## 1 1 30
## 2 1 10
## 3 1 22
## 4 1 44
## 5 1 6
## 6 1 5
## 7 5 20
## 8 5 35
## 9 5 2
## 10 5 60
## 11 5 14
## 12 5 5
tmp <- df[!unlist(tapply(df$score, df$group, FUN = function(x) c(F, -diff(x) > 30), simplify = T)), ]
while (!identical(df, tmp)) {
df <- tmp
tmp <- df[!unlist(tapply(df$score, df$group, FUN = function(x) c(F, -diff(x) > 30), simplify = T)), ]
}
tmp
## group score
## 1 1 30
## 2 1 10
## 3 1 22
## 4 1 44
## 7 5 20
## 8 5 35
## 10 5 60

Summing up specific entries in subset group (R programming)

So basically I have this format of data:
ID Value
1 32
5 231
2 122
1 11
3 ...
2 ...
5 ...
6 ...
2 ...
1 33
. ...
. ...
. ...
I want to sum up the values with ID '1', but in a group of 5.
i.e.
In the first 5 entries, there are 2 entries with ID '1', so i get a sum 43,
and then in the next 5 entries, only one entry have ID '1', so i get 33.
and so on...
so at the end I want to get a array with all the sums, i.e. (43,33,......)
I can do it with for loop and tapply, but I think there must be a better way in R that doesnt need a for loop
Any help is much appreciated! Thank you very much!
Make a new column to reflect the groups of 5:
df = data.frame(
id = sample(1:5, size=98, replace=TRUE),
value = sample(1:98)
)
# This gets you a vector of 1,1,1,1, 2,2,2,2,2, 3, ...
groups = rep(1:(ceiling(nrow(df) / 5)), each=5)
# But it might be longer than the dataframe, so:
df$group = groups[1:nrow(df)]
Then it's pretty easy to get the sums within each group:
library(plyr)
sums = ddply(
df,
.(group, id),
function(df_part) {
sum(df_part$value)
}
)
Example output:
> head(df)
id value group
1 4 94 1
2 4 91 1
3 3 22 1
4 5 42 1
5 1 46 1
6 2 38 2
> head(sums)
group id V1
1 1 1 46
2 1 3 22
3 1 4 185
4 1 5 42
5 2 2 55
6 2 3 158
Something like this will do the job:
m <- matrix(d$Value, nrow=5)
# Remove unwanted elements
m[which(d$ID != 1)] <- 0
# Fix for short data
if ((length(d$Value) %/% 5) != 0)
m[(length(d$Value)+1):length(m)] <- 0
# The columns contain the groups of 5
colSums(m)
If you add a column to delineate groups, ddply() can work magic:
ID <- c(1, 5, 2, 1, 3, 2, 5, 6, 2, 1)
Value <- c(32, 231, 122, 11, 45, 34, 74, 12, 32, 33)
Group <- rep(seq(100), each=5)[1:length(ID)]
test.data <- data.frame(ID, Value, Group)
library(plyr)
output <- ddply(test.data, .(Group, ID), function(chunk) sum(chunk$Value))
> head(test.data)
ID Value Group
1 1 32 1
2 5 231 1
3 2 122 1
4 1 11 1
5 3 45 1
6 2 34 2
> head(output)
Group ID V1
1 1 1 47
2 1 2 125
3 1 3 49
4 1 5 237
5 2 1 36
6 2 2 74

Resources