Skip to contents

Turns a transcript that the package did not generate – a human focus group, a transcript exported from other software, or a saved CSV – or a session file the package wrote into a FocusGroup object whose conversation_log holds the turns. The returned object supports the analysis methods (analyze(), analyze_participation_balance(), analyze_topics(), analyze_tfidf(), analyze_readability(), analyze_themes()), the plotting methods, analyze_focus_group(), fg_turns(), and summarize_focus_group(), and it can be continued with run_focus_group_arms(). No model call is made: importing and the descriptive analyses run offline, and only the model-dependent methods (analyze_themes(), summarize()) contact a provider when you call them.

Usage

focus_group_from_transcript(
  data,
  speaker_col = "speaker",
  text_col = "text",
  topic = NULL,
  moderator_id = NULL,
  personas = NULL
)

Arguments

data

A data frame with one row per turn, in order; the path of a session file (ending in .jsonl); a character vector of its lines; a focus_group_result; or a FocusGroup. A result or a session object is read as the file format.focus_group_result() writes for it, so the object returned is always a new one, without model configurations or runners.

speaker_col

Character. Name of the column holding the speaker id. Default "speaker". For a data frame only.

text_col

Character. Name of the column holding the utterance text. Default "text". Missing text becomes an empty utterance, with a warning giving the number of such turns. For a data frame only.

topic

Character or NULL. The discussion topic, used by the model-dependent analyses and stored on the object. NULL keeps the topic of a session file and otherwise falls back to "Imported transcript".

moderator_id

Character or NULL. Exact speaker id to treat as the moderator. When NULL, an is_moderator column or the exact aliases described above are used.

personas

NULL, or a character vector of personas named by speaker id, which replace the placeholder or recorded persona of those speakers; a speaker a continued session lets speak needs one.

Value

A FocusGroup object with a populated conversation_log.

Details

One lightweight agent is created per speaker so that speaker-aware methods have a roster to report on. Unless personas or the file gives one, an agent carries a placeholder persona, and a session cannot be continued while a participant has it. Set moderator_id when the transcript identifies the moderator. When it is NULL and data has no is_moderator column, the moderator is the speaker whose id, ignoring case and surrounding spaces, is exactly one of "mod", "moderator", "facilitator", or "interviewer"; an id such as "Moderator 1" or "Modesto" does not match, and two matching speakers are an error. When no speaker is the moderator, a non-speaking "MOD" agent is added so the object is complete, a message says so, and every speaker is analyzed as a participant. Rows whose speaker is missing or blank are dropped with a warning; rows with speaker "System" are kept in the log but excluded from analyses, matching how the simulator treats its own roster message.

Columns that a transcript exported by this package carries are kept when data has them, so a saved transcript can be read back without losing its record: message_id, round, item, is_moderator, phase, response_id, finish_reason, sent_tokens, rec_tokens, total_tokens, duration_s, provider, model, timestamp, and the list-column metadata. A field that is absent takes these values: messages are numbered in order, is_moderator follows the moderator found as described above, round counts the moderator's turns (0 throughout when there is no moderator), item starts a new guide item at each moderator turn (0 before the first and for "System" rows), phase is "imported", metadata is an empty list, and the other fields are NA. In particular, token counts that the transcript does not supply are unknown (NA), not zero, and importing adds no model calls to the object's usage counters.

The speakers keep their labels: the object's handles are the speaker ids, as with handles = "ids" in FocusGroup, and every message's handle is its speaker id. A handle column in data is not read.

Supplied fields are checked rather than coerced. message_id must hold distinct positive whole numbers increasing down the rows; round, item, and the token counts nonnegative whole numbers; duration_s nonnegative numbers; and metadata lists or NULL. Numbers stored as text are accepted, and NA is allowed except in message_id. A value that fails these checks is an error naming the column and its rows.

An is_moderator column must hold TRUE or FALSE in every row, as logical values or as the text "TRUE" and "FALSE"; it must give each speaker a single role and mark at most one speaker; it must agree with moderator_id when both are given. A column that marks no speaker means the transcript has no moderator, and the aliases are not consulted. "System" cannot be the moderator. If a participant is already named "MOD", the placeholder moderator is named "MOD_2", or the next free suffix.

Timestamps may be date-times, dates, numbers of seconds since 1970-01-01 UTC, or ISO 8601 text such as "2026-03-01", "2026-03-01 14:30:00", or "2026-03-01T14:30:00.5-05:00". Text without an offset is read as UTC, and all timestamps are stored in UTC. A timestamp that cannot be read becomes NA, with one warning giving the number of such values.

Session files

A session file has one JSON object per line, each with a type; the file system.file("fg-session-spec.md", package = "FocusGroup") describes the format for a person or a language model converting a transcript. format.focus_group_result() writes the file of a result, which this function reads back with every setting and record: the roster with personas, styles, and handles; the guide; the turn-taking rule and its parameters; the settings, such as the token limits, the call-on points, and the templates; the summary; and the routing, probe, injection, and call records. A result written and read back gives the same file when written again.

Only message lines with speaker_id and text are required. Every problem in a file (a line that is not JSON or not an object, an unknown line type or field, a value of the wrong type, a missing text, message ids that do not increase, two moderators, a speaker not declared when participants are, an item outside the guide, a negative token count, a file of one JSON value written over several lines) is reported at once, by an error of class focusgroup_format_error whose problems field is a data frame of row (the line), field, and problem. A field whose name starts with x_ is one the format does not define: on a session or item line it is kept, without the prefix, in the metadata or the guide item, as format.focus_group_result() writes such fields; on other lines it is ignored. A byte-order mark and Windows line endings are accepted. A path that is not a .jsonl file, such as a CSV or RDS file, is refused: read it into R first.

A file without participant lines takes its speakers from the messages, with the moderator found as for a data frame. A message injected from an outside voice (metadata$injected) never makes its speaker a participant. A file that records no run, such as a converted human transcript, is read with these defaults: each moderator line becomes a guide item delivered as written, a generic_discussion item when a participant replies to it, a transition when none does, and the closing when it is the last line and unanswered; call-ons are off; the handles are the speaker ids; max_participant_responses is the longest run of participant turns between moderator lines; and the turn-taking rule is round robin, continuing from the last speaker. A moderator line that gives its phase gives its item that phase.

The record of a run made by FocusGroup 0.5.5 or earlier, saved without its session object, has no item lines and no flow_params. Its guide is rebuilt from the moderator's recorded lines, one item per guide line with the phase that line records, and every item is marked verbatim: a line that was delivered verbatim gives the item's text, while for a line the moderator's model generated the guide text is unknown, so a warning (of class focusgroup_guide_rebuilt) names those items and says that a continuation delivers their lines as recorded. The failure rule of "desire_based" is read from the session line's on_desire_failure.

See also

analyze_focus_group() to run the descriptive analyses in one call; run_focus_group() to simulate a discussion instead.

Examples

transcript <- data.frame(
  speaker = c("Chair", "Ana", "Ben", "Ana", "Chair", "Ben"),
  text = c(
    "Welcome, everyone. What do you make of the new library hours?",
    "They help working parents like me; evenings matter most.",
    "I see it differently. Mornings are now far too crowded.",
    "Crowded mornings still beat being locked out after work.",
    "Ben, say more about what changed in the mornings.",
    "Staff are stretched thin before noon, so lines are longer."
  )
)
# "Chair" is not one of the moderator aliases, so name the moderator.
fg <- focus_group_from_transcript(transcript, topic = "library hours",
                                  moderator_id = "Chair")
fg$analyze_participation_balance()$participation_stats
#> # A tibble: 3 × 7
#>   speaker_id messages words avg_words message_percentage word_percentage
#>   <chr>         <int> <int>     <dbl>              <dbl>           <dbl>
#> 1 Ana               2    18         9               33.3            31.0
#> 2 Ben               2    20        10               33.3            34.5
#> 3 Chair             2    20        10               33.3            34.5
#> # ℹ 1 more variable: called_on <int>