
Build a FocusGroup object from a transcript or a session file
Source:R/transcript_import.R
focus_group_from_transcript.RdTurns 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; afocus_group_result; or aFocusGroup. A result or a session object is read as the fileformat.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.NULLkeeps 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. WhenNULL, anis_moderatorcolumn 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>