--- title: "Focus Group Analysis" date: "`r Sys.Date()`" output: rmarkdown::html_vignette vignette: > %\VignetteIndexEntry{Focus Group Analysis} %\VignetteEngine{knitr::rmarkdown} %\VignetteEncoding{UTF-8} --- ```{r setup, include = FALSE} knitr::opts_chunk$set( collapse = TRUE, comment = "#>" ) library(FocusGroup) ``` # Analyze simulated and imported transcripts How much did each speaker contribute? Which terms and topics distinguished their turns? How did moderator questions organize the discussion, and where do readability, recurring themes, or participation patterns differ? FocusGroup addresses these questions for simulated sessions and imported transcripts. Descriptive analysis runs offline. Thematic analysis and model summaries are separate, opt-in model tasks. A live session or requested model analysis uses LLMR, requires a provider key in its usual environment variable, and receives an explicit model configuration. ## Run a focus group ```{r create_fg, eval = FALSE} config <- LLMR::llm_config( provider = "openai", model = "gpt-4o-mini", temperature = 0.7 ) result <- run_focus_group( topic = "Impact of Social Media on Mental Health", n_participants = 5, guide = c( Opening = 1, Icebreaker = 1, Engagement = 2, Exploration = 3, Closing = 1 ), flow = "desire_based", config = config, seed = 110, message_mode = "roleflip" ) print(result) print(result$focus_group) ``` The returned `focus_group_result` has six returned components: `focus_group`, `transcript`, `summary`, `participants`, `usage`, and `metadata`. ```{r view_transcript, eval = FALSE} head(result$transcript) result$participants result$usage ``` Each transcript row has a unique `message_id`. `round` identifies the moderator cycle, so a moderator question and its participant responses share a round. `phase` retains the guide phase. Generated rows also carry provider response metadata and token counts. ## Run descriptive analysis offline `analyze_focus_group()` runs descriptive analyses without a model unless the caller opts in by supplying `config`. ```{r offline_analysis, eval = FALSE} analysis <- analyze_focus_group( result, num_topics = 4, include_plots = TRUE ) print(analysis) analysis$basic_stats analysis$tfidf analysis$readability analysis$issues ``` The `focus_group_analysis` returned components are `basic_stats`, `topics`, `tfidf`, `readability`, `themes`, `model_summary`, `plots`, and `issues`. A component that cannot be computed is an empty component with consistent columns where it is tabular. For example, `plots` is an empty list if ggplot2 is unavailable. `issues` identifies an optional analysis that could not run and gives its reason. Individual descriptive analyses remain available as methods on the underlying `FocusGroup` object: ```{r individual_analysis, eval = FALSE} readability <- result$focus_group$analyze_readability() participation <- result$focus_group$analyze_participation_balance() questions <- result$focus_group$analyze_question_patterns() readability participation$participation_stats questions$question_patterns ``` ## Opt in to model analysis Thematic analysis and model summaries run only when an explicit `config` is passed to `analyze_focus_group()`. ```{r model_analysis, eval = FALSE} model_analysis <- analyze_focus_group( result, num_topics = 4, config = config ) cat(model_analysis$themes) cat(model_analysis$model_summary) ``` If a requested model analysis fails, the provider condition is raised. The failure is not converted to a missing field or omitted from the result. ## Import and analyze an existing transcript Importing a transcript creates no model output. Pass `moderator_id` when the moderator is known. If it is omitted, the importer uses the documented substring fallback on speaker identifiers. ```{r import, eval = FALSE} transcript <- data.frame( speaker = c("Facilitator", "Ana", "Ben", "Ana"), text = c( "What should change about the library hours?", "Evening hours help working parents.", "Morning crowding has become difficult.", "Both schedules need enough staff." ) ) imported <- focus_group_from_transcript( transcript, topic = "Library hours", moderator_id = "Facilitator" ) imported_analysis <- analyze_focus_group(imported, include_plots = FALSE) print(imported_analysis) ``` ## Construct the objects directly The R6 interface exposes the parts assembled by `run_focus_group()`. ```{r direct, eval = FALSE} agents <- create_agents( n_participants = 3, demographics = data.frame( age = c(25, 35, 45), gender = c("male", "female", "non-binary"), education = c("high school", "bachelor's", "master's"), stringsAsFactors = FALSE ), config = config ) flow <- create_conversation_flow( mode = "desire_based", agents = agents, moderator_id = "MOD" ) fg <- FocusGroup$new( topic = "Impact of Social Media on Mental Health", purpose = "Explore perspectives and experiences related to social media.", agents = agents, moderator_id = "MOD", turn_taking_flow = flow, admin_config = config ) ``` Built-in flows are available only through `create_conversation_flow()`. `ConversationFlow` is the base class for custom turn-taking rules. ## Interpretation A generated transcript is evidence about the specified simulation, not about a population. Analysis should retain the guide, participant records, model configuration, and message construction recorded with the result.