I have survey data in which the same people are asked the same question during 6 different periods. Sometimes they answer (in which case we get a score from 1 to 10), sometimes they don’t (in which case the answer is 0).
In the end, I got a data frame that looks like this (the only difference being that in this example the answers are from 1 to 2, that’s just because it was easier to generate an adequate number of 0s that way for me):
period_1 <- sample(0:2, 100, replace=T)
period_2 <- sample(0:2, 100, replace=T)
period_3 <- sample(0:2, 100, replace=T)
period_4 <- sample(0:2, 100, replace=T)
period_5 <- sample(0:2, 100, replace=T)
period_6 <- sample(0:2, 100, replace=T)
df <- cbind(period_1, period_2, period_3, period_4, period_5, period_6)
head(df)
period_1 period_2 period_3 period_4 period_5 period_6
[1,] 0 2 1 1 0 1
[2,] 2 1 1 2 0 0
[3,] 1 0 2 0 1 1
[4,] 1 2 2 1 0 2
[5,] 1 1 2 2 0 2
[6,] 1 0 1 2 2 0
Now, I want to see the evolution of their answer over time. But with the current structure of the data frame, it is a bit awkward: I can’t just compare period 1 to period 2, for instance, because they didn’t all answer at period 1 (or 2).
Instead, what I would like would be a data frame which shows their first answer in one vector, no matter from which period that answer came from, and then the second answer, and so on…
In others words, get the first non-0 answer in survey_1, the second non-0 answer in survey_2, etc…
This is probably not the best solution, but it's the most simple one and it would work just fine for me.
This would allow me to turn this:
period_1 period_2 period_3 period_4 period_5 period_6
[1,] 0 2 1 1 0 1
[2,] 2 1 1 2 1 0
[3,] 1 0 2 0 1 1
Into this:
survey_1 survey_2 survey_3 survey_4 survey_5 survey_6
[1,] 2 1 1 1 0 0
[2,] 2 1 1 2 1 0
[3,] 1 2 1 1 0 0
But to be honest, I'm still a big newbie in R and programming in general and I don't even know where to begin with achieving this, and I've been stuck on this for some time now without making any progress toward a solution.
Can anyone offer me tips, or even a sample code, that would allow me to get to the desired result, please ?
Thank you !
We can use apply and order by whether an element is 0 or not for each row:
df[] <- t(apply(df, 1, function(x) x[order(x == 0)]))
Result:
period_1 period_2 period_3 period_4 period_5 period_6
[1,] 1 2 2 1 0 0
[2,] 2 2 1 0 0 0
[3,] 1 1 1 2 2 0
[4,] 2 2 1 2 1 0
[5,] 2 1 1 1 1 1
[6,] 2 2 1 1 0 0
Data:
df <- structure(c(0L, 2L, 1L, 2L, 2L, 0L, 1L, 0L, 1L, 2L, 1L, 2L, 0L,
2L, 1L, 1L, 1L, 2L, 2L, 0L, 2L, 2L, 1L, 1L, 2L, 0L, 2L, 1L, 1L,
1L, 1L, 1L, 0L, 0L, 1L, 0L), .Dim = c(6L, 6L), .Dimnames = list(
NULL, c("period_1", "period_2", "period_3", "period_4", "period_5",
"period_6")))
Related
I am quite a beginner in R but thanks to the community of Stackoverflow I am improving!
However, I am stuck with a problem:
I have a dataset with 5 variables:
id_house represents the id for each household
id_ind is an id which values 1 for the first individual in the household, 2 for the next, 3 for the third...
Indicator_tb_men which indicates if the first person has answered to the survey (1 = yes, 0 = no). All the other members of the household take the value 0.
id_house id_ind indicator_tb_men
1 1 1
1 2 0
2 1 1
3 1 0
3 2 0
3 3 0
4 1 1
5 1 0
I would like to delete all members of households where the first individual has not answered the survey.
So it would give:
id_house id_ind indicator_tb_men
1 1 1
1 2 0
2 1 1
4 1 1
Using dplyr here is one way :
library(dplyr)
df %>%
arrange(id_house, id_ind) %>%
group_by(id_house) %>%
filter(first(indicator_tb_men) != 0)
# id_house id_ind indicator_tb_men
# <int> <int> <int>
#1 1 1 1
#2 1 2 NA
#3 2 1 1
#4 4 1 1
data
df <- structure(list(id_house = c(1L, 1L, 2L, 3L, 3L, 3L, 4L, 5L),
id_ind = c(1L, 2L, 1L, 1L, 2L, 3L, 1L, 1L), indicator_tb_men = c(1L,
NA, 1L, 0L, NA, NA, 1L, 0L)), class = "data.frame", row.names = c(NA, -8L))
in base we can use nested logic
df[df$id_house %in% df$id_house[df$id_ind == 1 & df$indicator_tb_men == 1],]
id_house id_ind indicator_tb_men
1 1 1 1
2 1 2 NA
3 2 1 1
7 4 1 1
Data: Using Ronak Shah's data
I want to replace multiple columns of a data frame by one column each for each group whereas I also want to change the numbers. Example:
A1 A2 A3 A4 B1 B2 B3
1 1 1 0 1 1 0 0
2 1 0 1 1 0 1 1
3 1 1 1 1 0 1 1
4 0 0 1 0 0 0 1
5 0 0 0 0 0 1 0
I want to sort this data frame by it's headers meaning I only want one column "A" instead of 4 here and only column "B" instead of 3 here. The numbers should change with the following pattern: If you are in group "A2" and the observation has the number "1" it should be changed into a "2" instead. If you are in group "A3" and the observation has the number "1" it should be changed into a "3" instead. The end result should be that I want to contain the highest number in that specific column and row (if I have 3 "1"s in my row and group, the number which is going to replace all of them is going to be the one of the highest group)
If the number is 0 then nothing changes. Here is the result I'm looking for:
A B
1 4 1
2 4 3
3 4 3
4 3 3
5 0 2
How can I replace all of these groups by a single column each? (one column for each group)
So far I've tried a lot with the function unite(data= testdata, col= "A") for example, but doing this manually would take too long. There has to be a better way, right?
Thanks in advance!
You can do:
dat <- read.table(header=TRUE, text=
"A1 A2 A3 A4 B1 B2 B3
1 1 1 0 1 1 0 0
2 1 0 1 1 0 1 1
3 1 1 1 1 0 1 1
4 0 0 1 0 0 0 1
5 0 0 0 0 0 1 0")
myfu <- function(x) if (any(x)) max(which(x)) else 0
new <- data.frame(
A=apply(dat[, 1:4]==1, 1, myfu),
B=apply(dat[, 5:7]==1, 1, myfu))
new
A more general solution:
new2 <- data.frame(
A=apply(dat[, grepl("^A", names(dat))]==1, 1, myfu),
B=apply(dat[, grepl("^B", names(dat))]==1, 1, myfu))
new2
You can try the code like below
dfout <- as.data.frame(
lapply(
split.default(df, gsub("\\d+$", "", names(df))),
function(v) max.col(v, ties.method = "last") * +(rowSums(v) >= 1)
)
)
such that
> dfout
A B
1 4 1
2 4 3
3 4 3
4 3 3
5 0 2
Data
df <- structure(list(A1 = c(1L, 1L, 1L, 0L, 0L), A2 = c(1L, 0L, 1L,
0L, 0L), A3 = c(0L, 1L, 1L, 1L, 0L), A4 = c(1L, 1L, 1L, 0L, 0L
), B1 = c(1L, 0L, 0L, 0L, 0L), B2 = c(0L, 1L, 1L, 0L, 1L), B3 = c(0L,
1L, 1L, 1L, 0L)), class = "data.frame", row.names = c("1", "2",
"3", "4", "5"))
assuming your data is in a data.frame called df1 this works in Base-R
df1 <- t(df1)*as.numeric(regmatches(colnames(df1), regexpr("\\d+$", colnames(df1))))
df1 <- split(as.data.frame(df1),sub("\\d+$","",row.names(df1)))
df1 <- sapply(df1, apply, 2, max)
output:
> df1
A B
1 4 1
2 4 3
3 4 3
4 3 3
5 0 2
I have a database with 100 columns, but a minimal production of my data are as follows:
df1<=read.table(text="PG1S1AW KOM1S1zo PG2S2AW KOM2S2zo PG3S3AW KOM3S3zo PG4S4AW KOM4S4zo PG5S5AW KOM5S5zo
4 1 2 4 4 3 0 4 0 5
4 4 3 1 3 1 0 3 0 1
2 3 5 3 3 2 1 4 0 2
1 1 1 1 1 3 0 5 0 1
2 5 3 4 4 5 0 1 3 4", header=TRUE)
I want to get columns starting with KOM and PG which have a greater of 3 . So we need to have PG4, KOM4 and above. Put it simply, starting with PG and KOM have the same values which is 4 and greater.
The intended output is:
PG4S4AW KOM4S4zo PG5S5AW KOM5S5zo
0 4 0 5
0 3 0 1
1 4 0 2
0 5 0 1
0 1 3 4
I have used the following code, but it does not work for me:
df2<- df1%>% select(contains("KO"))
Thanks for your help.
It is not entirely clear about the patterns. We create a function (f1) to extract one or more digits (\\d+) that follows the 'KOM' or (|) 'PG' with str_extract (from stringr), convert to numeric ('v1'), similarly, extract numbers after the 'S' ('v2'). Do a check whether these values are same and if one of the value is greater than 3, wrap with which so that if there are any NAs resulting from str_extract would be removed as which gives the column index while removing any NAs. Use the function in select to select the columns that follow the pattern
library(dplyr)
library(stringr)
f1 <- function(nm) {
v1 <- as.numeric(str_extract(nm, "(?<=(KOM|PG))\\d+"))
v2 <- as.numeric(str_extract(nm, "(?<=S)\\d+"))
nm[which((v1 == v2) & (v1 > 3))]
}
df1 %>%
select(f1(names(.)))
# PG4S4AW KOM4S4zo PG5S5AW KOM5S5zo
#1 0 4 0 5
#2 0 3 0 1
#3 1 4 0 2
#4 0 5 0 1
#5 0 1 3 4
data
df1 <- structure(list(PG1S1AW = c(4L, 4L, 2L, 1L, 2L), KOM1S1zo = c(1L,
4L, 3L, 1L, 5L), PG2S2AW = c(2L, 3L, 5L, 1L, 3L), KOM2S2zo = c(4L,
1L, 3L, 1L, 4L), PG3S3AW = c(4L, 3L, 3L, 1L, 4L), KOM3S3zo = c(3L,
1L, 2L, 3L, 5L), PG4S4AW = c(0L, 0L, 1L, 0L, 0L), KOM4S4zo = c(4L,
3L, 4L, 5L, 1L), PG5S5AW = c(0L, 0L, 0L, 0L, 3L), KOM5S5zo = c(5L,
1L, 2L, 1L, 4L)), class = "data.frame", row.names = c(NA, -5L
))
Given your example data, you can just instead look for the numbers 4 or 5.
df1 %>%
select(matches("4|5"))
#> KO4S4AW KOM4S4zo KO5S5AW KOM5S5zo
#> 1 0 4 0 5
#> 2 0 3 0 1
#> 3 1 4 0 2
#> 4 0 5 0 1
#> 5 0 1 3 4
I would like to create variable "Time" which basically indicates the number of times variable ID showed up within each day minus 1. In other words, the count is lagged by 1 and the first time ID showed up in a day should be left blank. Second time the same ID shows up on a given day should be 1.
Basically, I want to create the "Time" variable in the example below.
ID Day Time Value
1 1 0
1 1 1 0
1 1 2 0
1 2 0
1 2 1 0
1 2 2 0
1 2 3 1
2 1 0
2 1 1 0
2 1 2 0
Below is the code I am working on. Have not been successful with it.
data$time<-data.frame(data$ID,count=ave(data$ID==data$ID, data$Day, FUN=cumsum))
We can do this with data.table. Convert the 'data.frame' to 'data.table' (setDT(df1)), grouped by 'ID', 'Day', we get the lag of sequence of rows (shift(seq_len(.N))) and assign (:=) it as "Time" column.
library(data.table)
setDT(df1)[, Time := shift(seq_len(.N)), .(ID, Day)]
df1
# ID Day Value Time
# 1: 1 1 0 NA
# 2: 1 1 0 1
# 3: 1 1 0 2
# 4: 1 2 0 NA
# 5: 1 2 0 1
# 6: 1 2 0 2
# 7: 1 2 1 3
# 8: 2 1 0 NA
# 9: 2 1 0 1
#10: 2 1 0 2
Or with base R
with(df1, ave(Day, Day, ID, FUN= function(x)
ifelse(seq_along(x)!=1, seq_along(x)-1, NA)))
#[1] NA 1 2 NA 1 2 3 NA 1 2
Or without the ifelse
with(df1, ave(Day, Day, ID, FUN= function(x)
NA^(seq_along(x)==1)*(seq_along(x)-1)))
#[1] NA 1 2 NA 1 2 3 NA 1 2
data
df1 <- structure(list(ID = c(1L, 1L, 1L, 1L, 1L, 1L, 1L, 2L, 2L, 2L),
Day = c(1L, 1L, 1L, 2L, 2L, 2L, 2L, 1L, 1L, 1L), Value = c(0L,
0L, 0L, 0L, 0L, 0L, 1L, 0L, 0L, 0L)), .Names = c("ID", "Day",
"Value"), row.names = c(NA, -10L), class = "data.frame")
I need some help to create a n-way frequency table.
I am using the code below:
tab <- table(VAR1,VAR2,VAR3)
finaltab <- ftable(tab,row.vars=c(2,3))
print(finaltab)
VAR1, VAR2 and VAR3 are all factor variables. By doing this, I produce the following table:
But since VAR2 and VAR3 have several categories, I got a lot of lines with "0" and I which to remove those lines to keep in which category of VAR2 only frequencies for the categories of VAR3 that really have frequency values, as follows:
Does anyone knows how to do it, either by subsetting the table I created first or using another function that doesn't return all levels of VAR3 in each VAR2 category but only those which actually have frequencies?
Contingency tables have the same number of rows in every category. If you
remove rows from one category you no longer have a table but a matrix.
t <- structure(c(0L, 0L, 1L, 1L, 1L, 0L, 1L, 1L, 1L, 0L, 0L, 0L, 0L, 1L, 0L, 1L, 1L, 1L), .Dim = c(3L, 2L, 3L), .Dimnames = structure(list(c("A", "B", "C"), c("A", "B"), c("A", "B", "C")), .Names = c("","", "")), class = "table")
> (ft <- ftable(t, row.vars=c(2,3)))
A B C
A A 0 0 1
B 1 1 1
C 0 1 0
B A 1 1 0
B 0 0 0
C 1 1 1
> ft[apply(ft, 1, any), ]
[,1] [,2] [,3]
[1,] 0 0 1
[2,] 1 1 1
[3,] 0 1 0
[4,] 1 1 0
[5,] 1 1 1
The unfortunate effect of subsetting the table is that names are lost. This
can be mitigated to some extent by coercing the table to a matrix before
taking the subset, but the printed output still isn't as pretty as that of a
contengency table.
> as.matrix(ft)[apply(ft, 1, any), ]
_ A B C
A_A 0 0 1
A_B 1 1 1
A_C 0 1 0
B_A 1 1 0
B_C 1 1 1