tags:

views:

136

answers:

5

I am coding a survey that outputs a .csv file. Within this csv I have some entries that are space delimited, which represent multi-select questions (e.g. questions with more than one response). In the end I want to parse these space delimited entries into their own columns and create headers for them so i know where they came from.

For example I may start with this (note that the multiselect columns have an _M after them):

Q1, Q2_M, Q3, Q4_M
6, 1 2 88, 3, 3 5 99
6, , 3, 1 2

and I want to go to this:

Q1, Q2_M_1, Q2_M_2, Q2_M_88, Q3, Q4_M_1, Q4_M_2, Q4_M_3, Q4_M_5, Q4_M_99
6, 1, 1, 1, 3, 0, 0, 1, 1, 1
6,,,,3,1,1,0,0,0

I imagine this is a relatively common issue to deal with but I have not been able to find it in the R section. Any ideas how to do this in R after importing the .csv ? My general thoughts (which often lead to inefficient programs) are that I can: (1) pull column numbers that have the special suffix with grep() (2) loop through (or use an apply) each of the entries in these columns and determine the levels of responses and then create columns accordingly (3) loop through (or use an apply) and place indicators in appropriate columns to indicate presence of selection

I appreciate any help and please let me know if this is not clear.

+1  A: 

Hi,

I am not entirely sure what you trying to do respectively what your reasons are for coding like this. Thus my advice is more general – so just feel to clarify and I will try to give a more concrete response.

1) I say that you are coding the survey on your own, which is great because it means you have influence on your .csv file. I would NEVER use different kinds of separation in the same .csv file. Just do the naming from the very beginning, just like you suggested in the second block. Otherwise you might geht into trouble with checkboxes for example. Let's say someone checks 3 out of 5 possible answers, the next only checks 1 (i.e. "don't know") . Now it will be much harder to create a spreadsheet (data.frame) type of results view as opposed to having an empty field (which turns out to be an NA in R) that only needs to be recoded.

2) Another important question is whether you intend to do a panel survey(i.e longitudinal study asking the same participants over and over again) . That (among many others) would be a good reason to think about saving your data to a MySQL database instead of .csv . RMySQL can connect directly to the database and access its tables and more important its VIEWS. Views really help with survey data since you can rearrange the data in different views, conditional on many different needs.

3) Besides all the personal / opinion and experience, here's some (less biased) literature to get started: Complex Surveys: A Guide to Analysis Using R (Wiley Series in Survey Methodology

The book is comparatively simple and leaves out panel surveys but gives a lot of R Code and examples which should be a practical start.

To prevent re-inventing the wheel you might want to check LimeSurvey, a pretty decent (not speaking of the templates :) ) tool for survey conductors. Besides I TYPO3 CMS extensions pbsurvey and ke_questionnaire (should) work well too (only tested pbsurvey).

ran2
Thanks for mentioning #3 !!!
aL3xa
+1  A: 

Multiple choice items should always be coded as separate variables. That is, if you have 5 alternatives and multiple choice, you should code them as i1, i2, i3, i4, i5, i.e. each one is a binary variable (0-1). I see that you have values 3 5 99 for *Q4_M* variable in the first example. Does that mean that you have 99 alternatives in an item? Ouch...

First you should go on and create separate variables for each alternative in a multiple choice item. That is, do:

# note that I follow your example with Q4_M variable
dtf_ins <- as.data.frame(matrix(0, nrow = nrow(<initial dataframe>), ncol = 99))
# name vars appropriately
names(dtf_ins) <- paste("Q4_M_", 1:99, sep = "")

now you have a data.frame with 0s, so what you need to do is to get 1s in an appropriate position (this is a bit cumbersome), a function will do the job...

# first you gotta change spaces to commas and convert character variable to a numeric one
y <- paste("c(", gsub(" ", ", ", x), ")", sep = "")
z <- eval(parse(text = y))
# now you assing 1 according to indexes in z variable
dtf_ins[1, z] <- 1

And that's pretty much it... basically, you would like to reconsider creating a data.frame with *_M* variables, so you can write a function that does this insertion automatically. Avoid for loops!

Or, even better, create a matrix with logicals, and just do dtf[m] <- 1, where dtf is your multiple-choice data.frame, and m is matrix with logicals.

I would like to help you more on this one, but I'm recuperating after a looong night! =) Hope that I've helped a bit! =)

aL3xa
@aL3Xa: Rather than changing spaces to commas (in the CSV file?), it's better just to parse the space delimited values. See my answer. Otherwise, I agree.
Richie Cotton
That's right, `strsplit` with `as.numeric` is the right way!
aL3xa
+2  A: 

I agree with ran2 and aL3Xa that you probably want to change the format of your data to have a different column for each possible reponse. However, if you munging your dataset to a better format proves problematic, it is possible to do what you asked.

process_multichoice <- function(x) lapply(strsplit(x, " "), as.numeric)

q2 <- c("1 2 3 NA 4", "2 5")
processed_q2 <- process_multichoice(q2)
[[1]]
[1]  1  2  3 NA  4

[[2]]
[1] 2 5

The reason different columns for different responses are suggested is because it is still quite unpleasant trying to retrieve any statistics from the data in this form. Although you can do things like

# Number of reponses given
sapply(processed_q2, length)

#Frequency of each response
table(unlist(processed_q2), useNA = "ifany")

EDIT: One more piece of advice. Keep the code that processes your data separate from the code that analyses it. If you create any graphs, keep the code for creating them separate again. I've been down the road of mixing things together, and it isn't pretty. (Especially when you come back to the code six months later.)

Richie Cotton
Neat and simple... =)
aL3xa
A: 

I really like Richie Cotton's approach here, largely because dealing with this .csv file with a few space delimited entries is a bit of a hassle. You'll see about halfway through a code a mess of na.omit(), strsplit(), and reclassings to get it properly formatted. My code is pretty messy, but it should give the results you sought (although in one spot you asked for NULL entries and in another you asked for 0 entries)... someone with some more experience may be able to clean this up by replacing the for loops with functions from the apply family.

Here's the data I'm using:

> data
  Q1    Q2_M Q3    Q4_M
1  6  1 2 88  3  3 5 99
2  6          3     1 2

Here's the code:

data <- read.csv("data.csv")
observations <- nrow(data)
possible.answers <- list(NA, c(1, 2, 88), NA, c(1, 2, 3, 5, 99))
number.questions <- length(possible.answers) 
questions <- 1:number.questions

new.data <- NULL

for (i in 1:number.questions) {
    if (is.na(possible.answers[[i]])) new.data <- cbind(new.data, data[, i])
    else {
        temp <- matrix(0, nrow=observations, ncol=length(possible.answers[[i]]))
        for (j in 1:observations) {
            answers.observed <- as.numeric(na.omit(as.numeric(unlist(strsplit(as.character(data[j, i]), " ")))))
            for (k in 1:length(possible.answers[[i]])) {
                if (possible.answers[[i]][k] %in% answers.observed) temp[j, k] <- 1
            }
        }
        new.data <- cbind(new.data, temp)
    }
}

column.names <- NULL

for (i in 1:number.questions) {
    if (is.na(possible.answers[[i]])) column.names <- c(column.names, paste("Q", i, sep="_"))
    else column.names <- c(column.names, sapply(possible.answers[[i]], function(j) paste("Q", i, j, sep="_")))
}

colnames(new.data) <- column.names

Here are the results:

> new.data
     Q_1 Q_2_1 Q_2_2 Q_2_88 Q_3 Q_4_1 Q_4_2 Q_4_3 Q_4_5 Q_4_99
[1,]   6     1     1      1   3     0     0     1     1      1
[2,]   6     0     0      0   3     1     1     0     0      0

You can write these back to .csv if you'e like with write.table. HTH

richardh
A: 

Thanks for all the responses. I agree with most of you that this format is kind of silly but it is what I have to work with (survey is coded and going into use next week). This is what I came up with from all the responses. I am sure this is not the most elegant or efficient way to do it but I think it should work.

colnums <- grep("_M",colnames(dat))
responses <- nrow(dat)

for (i in colnums) {
  vec <- as.vector(dat[,i]) #turn into vector
  b <- lapply(strsplit(vec," "),as.numeric) #split up and turn into numeric
  c <- sort(unique(unlist(b))) #which values were used
  newcolnames <- paste(colnames(dat[i]),"_",c,sep="") #column names
  e <- matrix(nrow=responses,ncol=length(c)) #create new matrix for indicators
  colnames(e) <- newcolnames 
#next loop looks for responses and puts indicators in the correct places
  for (i in 1:responses) {
  e[i,] <- ifelse(c %in% b[[i]],1,0)
  }
  dat <- cbind(dat,e)
}

Suggestions for improvement are welcome.

scottyaz
What if an answer doesn't appear, but was possible? E.g., what if "D" was a choice, but no one selected it. You don't want to keep a record of that in the data set? It seems that options that no one picks are also telling and worth recording.
richardh