ropensci / qualtRics

Download ⬇️ Qualtrics survey data directly into R!
https://docs.ropensci.org/qualtRics
Other
216 stars 70 forks source link

Automating downloading of complete QSF library from Qualtrics user library #299

Open yuvso opened 1 year ago

yuvso commented 1 year ago

Hi, first of all - thanks for your lovely qualtRics package. It is very useful!

I've encountered a problem, I need to download all my qualtrics surveys - data, metadata and QSF files - from my user account.

I used qualtRics to automate downloading and saving of both survey data and metadata successfully. However - I have not found a way through qualtRics script to fetch the list, and download QSF files.

Obviously this should not be a problem, as QSF are equivalent to surveyIDs and since identification and opening a connection is already happening through "fetch survey" command.

Thank you for any help! Yuval

jmobrien commented 1 year ago

Hi @yuvso,

Question to help me figure out what you need--what do you need the QSF's for?

The function fetch_description() contains all the same content as what's in a QSF, just in a slightly different configuration. So, if you just need an archive of survey contents/configuration, or if you want to use that content information as metadata for, say, making codebooks, variable labels, or other documentation, then fetch_description() has all that you need. (I myself have done exactly that in a big project, and it worked well.)

If you need the QSF's as QSF's--for, say, other people to upload using the web interface, the qualtRics package doesn't yet have the ability to get them. That said, the API endpoint that fetch_description() uses actually can provide QSFs. For some reason, though, Qualtrics decided on this alternative format as the default, possibly because it integrates better with other tools for developing surveys directly via the API (though I'm not sure about that)

While we don't yet have the QSF option added to fetch_description(), it might be something that could be added if people want it. @juliasilge, thoughts on something like a fetch_qsf() function? Seems like it could just be an extension of the existing fetch_description() that uses the format = qsf parameter in the call, and which outputs the raw JSON for saving (rather than converting it to an R list like fetch_description() does).

juliasilge commented 1 year ago

Could this be an argument to the existing fetch_description() function itself? Maybe after the dots? The function would still return a list in both cases, but with format = "qsf" or similar it would be a slightly different list.

jmobrien commented 1 year ago

I could see both being workable.

Possibly, yes. I think the relevant considerations are the quite different output we'd want (R list vs JSON). Also, we might want to add a few more arguments--maybe one for writing directly to a file† vs stdout, maybe also one for prettifying the JSON beforehand so it's human-readable. Definitely something that could work inside fetch_description(), still, though. I know there's some point where requiring users to know about two functions becomes more intuitive than requiring them to know two different ways to work the args in one function, but we might be in judgment-call territory on this.

†(Bringing up direct writing because writing JSON direct to file needs something like writeLines([jsonstring], con = file([filepath])), which is likely not intuitive for users only familiar with the read/write tools for tabular data.)

I actually already made a super-quick fetch_qsf() as an example, but paused it because format = "qsf" is a query parameter so I needed to finish PR #301 instead (and also because @dsen6644 needed it). If #301 can be made ready to merge I can post up the QSF PR as a draft for easier discussion.

yuvso commented 1 year ago

Hi @yuvso,

Question to help me figure out what you need--what do you need the QSF's for?

The function fetch_description() contains all the same content as what's in a QSF, just in a slightly different configuration. So, if you just need an archive of survey contents/configuration, or if you want to use that content information as metadata for, say, making codebooks, variable labels, or other documentation, then fetch_description() has all that you need. (I myself have done exactly that in a big project, and it worked well.)

If you need the QSF's as QSF's--for, say, other people to upload using the web interface, the qualtRics package doesn't yet have the ability to get them. That said, the API endpoint that fetch_description() uses actually can provide QSFs. For some reason, though, Qualtrics decided on this alternative format as the default, possibly because it integrates better with other tools for developing surveys directly via the API (though I'm not sure about that)

While we don't yet have the QSF option added to fetch_description(), it might be something that could be added if people want it. @juliasilge, thoughts on something like a fetch_qsf() function? Seems like it could just be an extension of the existing fetch_description() that uses the format = qsf parameter in the call, and which outputs the raw JSON for saving (rather than converting it to an R list like fetch_description() does).

yuvso commented 1 year ago

Hi @yuvso,

Question to help me figure out what you need--what do you need the QSF's for?

The function fetch_description() contains all the same content as what's in a QSF, just in a slightly different configuration. So, if you just need an archive of survey contents/configuration, or if you want to use that content information as metadata for, say, making codebooks, variable labels, or other documentation, then fetch_description() has all that you need. (I myself have done exactly that in a big project, and it worked well.)

If you need the QSF's as QSF's--for, say, other people to upload using the web interface, the qualtRics package doesn't yet have the ability to get them. That said, the API endpoint that fetch_description() uses actually can provide QSFs. For some reason, though, Qualtrics decided on this alternative format as the default, possibly because it integrates better with other tools for developing surveys directly via the API (though I'm not sure about that)

While we don't yet have the QSF option added to fetch_description(), it might be something that could be added if people want it. @juliasilge, thoughts on something like a fetch_qsf() function? Seems like it could just be an extension of the existing fetch_description() that uses the format = qsf parameter in the call, and which outputs the raw JSON for saving (rather than converting it to an R list like fetch_description() does).

Well, this is the proprietary method of Qualtrics' migration and re-installation of a specific survey. It's best way I know of, to back survey's up in most complete manner.

jmobrien commented 1 year ago

@yuvso, thanks. That's what I assumed at a default. It does seem like something people might commonly want, and so something we should consider supporting.

I'm currently experimenting in draft #302 with how we might support getting QSFs easily through the API. The new function for that (for now, approach isn't decided) is fetch_qsf(). If you're interested in trying it out (with the understanding that it's still not officially a part of the package), the draft version can be installed with remotes::install_github("ropensci/qualtRics", ref = "0060364").

If you do try it on your use case, we'd love feedback on any issues, ideas, or suggestions that you discover. You're also welcome to submit PRs for code changes directly if that interests you.

yuvso commented 1 year ago

Thanks!

Have tried reinstalling qualtRics and running fetch_qsf().

This is what I get:

Error in get0(oNam, envir = ns) : lazy-load database '/Library/Frameworks/R.framework/Versions/4.0/Resources/library/stringr/R/stringr.rdb' is corrupt In addition: Warning messages: 1: In get0(oNam, envir = ns) : restarting interrupted promise evaluation 2: In get0(oNam, envir = ns) : Error in get0(oNam, envir = ns) : lazy-load database '/Library/Frameworks/R.framework/Versions/4.0/Resources/library/stringr/R/stringr.rdb' is corrupt

Yuval

On 8 Dec 2022, at 16:29, jmobrien @.***> wrote:

@yuvso https://github.com/yuvso, thanks. That's what I assumed at a default. It does seem like something people might commonly want, and so something we should consider supporting.

I'm currently experimenting in draft #302 https://github.com/ropensci/qualtRics/pull/302 with how we might support getting QSFs easily through the API. The new function for that (for now, approach isn't decided) is fetch_qsf(). If you're interested in trying it out (with the understanding that it's still not officially a part of the package), the draft version can be installed with remotes::install_github("ropensci/qualtRics", ref = "0060364").

If you do try it on your use case, we'd love feedback on any issues, ideas, or suggestions that you discover. You're also welcome to submit PRs for code changes directly if that interests you.

— Reply to this email directly, view it on GitHub https://github.com/ropensci/qualtRics/issues/299#issuecomment-1342823502, or unsubscribe https://github.com/notifications/unsubscribe-auth/A4TTTYDE4N3E4KZQ7AF4EDDWMHWE7ANCNFSM6AAAAAASWRNGXI. You are receiving this because you were mentioned.

juliasilge commented 1 year ago

@yuvso I would restart R, install stringr, restart R, and then try again. Make sure you are not saving your workspace in between.

yuvso commented 1 year ago

Now fetching works fine. What is the best way to save it to QSF file locally? I’m not sure about handling JSON formats in R environment.

Thanks, Yuval

On 8 Dec 2022, at 18:28, Julia Silge @.***> wrote:

@yuvso https://github.com/yuvso I would restart R, install stringr, restart R, and then try again.

— Reply to this email directly, view it on GitHub https://github.com/ropensci/qualtRics/issues/299#issuecomment-1342982383, or unsubscribe https://github.com/notifications/unsubscribe-auth/A4TTTYHBWLPZRPSDBU4WHJDWMIEEDANCNFSM6AAAAAASWRNGXI. You are receiving this because you were mentioned.

juliasilge commented 1 year ago

I typically use jsonlite::write_json().

jmobrien commented 1 year ago

@juliasilge, weirdly enough jsonlite::write_json() actually won't write out a "json" object as JSON--it just treats it as a character vector and then writes the contents as an element in a JSON array. If you then read it back in with read_json(), you just get a string of unparsed JSON.

The only way I've figured out so far is to write the lines out manually, like below using writeLines() to a file connection:

 # Make connection to file:
  connection <-
      file(filepath)

# Write lines of JSON to file:
 writeLines(
    text =  [downloadedJSON], 
    con = connection
)

close(connection)

But, since I think you're right @yuvso to expect that most of the time people will want to save rather than work with the JSON in R , in the fetch_qsf() test function there are two arguments save and file to do this more directly (the code above is adapted from that function). Basically, this:

fetch_qsf([surveyID], save = TRUE file = "survey.qsf")

should (if everything's working) create the appropriate file called survey.qsf in your working directory.

There might be other ways to save, but I'm still learning.

If you needed to do a bunch, I suppose you could set up something with these tools using all_surveys(), purrr::map() and fetch_qsf() to do it all together.

jmobrien commented 1 year ago

Just for fun, something like the below would, I think, save QSF's for every survey in your all_surveys() list (assuming you had permission to get that metadata for each one, so no errors):

# List all surveys:
qualtRics::all_surveys() |>
  # Create potential filenames for each combining survey id and survey name:
  dplyr::mutate(
    file = glue::glue("{surveyID} - {title}.qsf", surveyID = id, title = name)
  ) |> 
  # Take just the key parts:
  dplyr::select(surveyID = id, file) |> 
  # Cycle through list, saving each to the new filename
  purrr::pwalk(fetch_qsf, save = TRUE)

I haven't actually tested the above, because it would run hundreds of API calls for me and make tons of files. But I think it illustrates an idea of a how this might integrate qualtRics tools into an archival workflow.

jmobrien commented 1 year ago

@juliasilge now I'm wondering whether we should have renamed the "id" column in all_surveys() output to "surveyID". If we had, its results would feed more easily into other tools in the package.

There are only two hard things in Computer Science: cache invalidation and naming things. — Phil Karlton

yuvso commented 1 year ago

I've run the latest, seems to be an error within glue::glue()

Error in eval(parse(text = text, keep.source = FALSE), envir) : object '.x' not found

jmobrien commented 1 year ago

My mistake @yuvso--I restructured that right before posting it, and forgot to change those .x's and .y's to what they should be. (.x/.y is something used inside the purrr::map/walk functions, but that glue call isn't inside there anymore).

I edited the code above, and it seems to be working now. You're welcome to try it again if you like. But mainly it's still just an illustration, and/or maybe something that could go into examples/vignettes for this functionality.

yuvso commented 1 year ago

Thanks! This look great, I've tested your latest version and it worked like a charm.

saritpery commented 11 months ago

This is great! Thank you so much for adding this function. I have an issue with some of the surveys, where a manual creation of a qsf format works great (meaning it restores when I wish), but the fetch_fsq function generated a similar qsf file, that can't be restored and generates: Error parsing file: The file does not appear to be a valid survey.