Data Formulator は、Microsoft Research などの研究によって生まれた自然言語と UI 操作を組み合わせてデータ探索と可視化を行う、AI エージェント駆動のデータ可視化アプリケーションです。CSV、TSV、Excel、テキスト、画像、Web ページ、データベース、BI ツールなどからデータを取り込み、分析の質問、データ変換、チャート作成、レポート生成までを一つのキャンバス上で扱います。
今回、Data Formulator の進化型である Data Formulator 2 に搭載されている Data Threads という考え方が面白そうなので深く調べてみようと思います。
論文から見る Data Formulator 2 の進化
論文 Data Formulator 2: Iterative Creation of Data Visualizations, with AI Transforming Data Along the Way は、Data Formulator を「一回の指示でチャートを作るツール」から「データ変換とチャート設計を行き来しながら分析を進める反復型ツール」へ発展させた研究として位置づけています。
論文の問題意識は明確です。探索的データ分析では、最初から完成形のチャート仕様が分かっていることは少なく、ユーザーは仮説を立て、チャートを作り、気づきを得て、別の切り口へ分岐します。そのたびに、単なる可視化指定だけでなく、フィルター、集計、ランキング、ピボット、列派生、結合などのデータ変換が必要になります。従来の自然言語からチャートを作るツールは、複雑な意図を一つの長いテキストプロンプトで説明させがちで、反復、分岐、やり直しに弱いという課題がありました。
そこで Data Formulator 2 は 2 つの中核アイデアを導入しています。
-
マルチモーダルな Chart Builder: ユーザーはチャート種別や視覚チャネルを UI で指定し、必要に応じて自然言語で補足します。既存列をドラッグするだけでなく、まだ存在しない列名をチャネルに入力できる点が重要です。例えば
RankやRenewable Energy Percentageのような新しい列名を Y 軸に置くと、システムは「この列を作るためのデータ変換が必要」と解釈します。 -
Data Threads: 生成されたデータ、チャート、指示、コード履歴を、線形チャットではなく分岐可能な履歴として保持します。ユーザーは過去のチャートやデータを選び、そこから新しい分析枝を作れます。
この 2 つにより、Data Formulator 2 は 「チャート仕様」と「データ変換」を分離します。チャートの骨格は UI と Vega-Lite テンプレートから組み立て、データ変換は LLM が Python コードとして生成します。
何が変わったのか
最も大きな変化は、ユーザーの作業単位が「プロンプト」から「分析の履歴を持つデータとチャート」へ移ったことです。
テキストだけのチャット型ツールでは、ユーザーは「さっきのチャートのうち、この条件だけ変えて、ただし前々回の集計方法を使って」といった文脈を文章で説明する必要があります。Data Formulator 2 では、ユーザーが過去のチャートやデータノードを直接選ぶため、AI に渡すコンテキストが明示されます。これは、長い会話履歴の中からモデルが関連情報を探す方式よりも、分岐した探索に向いています。
もう一つの変化は、AI が担当する範囲の整理です。Data Formulator 2 では、現在の LLM はチャート仕様を自然言語だけから正確に編集するより、データ変換コード生成の方が得意であるという前提を置いています。そのため Data Formulator 2 は、チャートの見た目やエンコーディング指定は UI に寄せ、データ変換を AI に任せます。この分担により、ユーザーは UI の正確さと自然言語の柔軟さを同時に使えます。
Flint と Data Formulator の関係: チャートの描画技術
Microsoft Research の紹介記事「Flint: A visualization language for the AI era」 は、Flint を AI によるチャート作成のための可視化中間言語として説明し、Flint が Data Formulator の可視化を支えていると明記しています。両者は競合するアプリケーションではなく、Data Formulator が分析体験全体を提供し、Flint がその中のセマンティックなチャートコンパイル層を担うという関係です。
| 層 | 主な責務 |
|---|---|
| Data Formulator | データの取り込み、AI による変換、サンドボックス実行、Data Threads、ワークスペース、チャート編集 UI、結果の保存と検証 |
| Flint | データ、セマンティック型、チャート種別、視覚チャネルの対応を受け取り、軸、尺度、集計、書式、色、レイアウトを決定して描画ライブラリ固有の仕様へコンパイルする |
| Vega-Lite / ECharts / Chart.js | Flint が生成したネイティブ仕様を実際のチャートとして描画する |
Flint の重要な考え方は、LLM に複雑で長い Vega-Lite の設定を最初からすべて書かせないことです。代わりに LLM は、「どのデータを使うか」「各列が日付、金額、地域などの何を表すか」「どの種類のチャートを作り、どの列を軸や色に使うか」という簡潔な指示だけを作ります。Flint はこの指示を受け取り、描画に必要な詳しい Vega-Lite 仕様へ変換します。この指示一式を ChartAssemblyInput と呼び、内部ではデータを表す data、各列の意味を表す semantic_types、作りたいチャートを表す chart_spec の三つに分けて管理します。
Flint の解説は @matayuuu が詳しく解説しています。
Data Threads の詳細
Data Threads は、Data Formulator 2 の中でも特に重要な設計です。単なる「履歴パネル」ではなく、ユーザーが分析の文脈を選び、AI にどの過去結果を基準に次の変換を考えさせるかを指定するためのインターフェースです。
従来のチャット型 UI では、履歴は基本的に一本の時系列です。探索が一本道なら問題は小さいものの、実際の分析では「このチャートから別案を試す」「少し前のデータに戻って条件を変える」「失敗した指示だけを書き直す」といった分岐が頻繁に起こります。Data Threads は、この非線形な探索を、データとチャートを中心にした枝として表現します。
この図では、派生データ B から 2 つの枝が出ています。一つは「平均を中央値に変更する」枝、もう一つは「平均所得を追加する」枝です。Data Threads がない場合、ユーザーはチャット履歴の中で「どの 派生データ を指しているのか」「どの変換を再利用したいのか」を文章で説明する必要があります。Data Threads では、対象ノードを選ぶ操作そのものが文脈指定になります。
これは面白い考え方ですね。もっと内部まで理解したいところです。現実装では Data Threads のすべてが可視化されているわけではないため、まずはこの Data Threads を可視化しないといけません。今回私の方で以下のように実際の Data Threads をフロー形式で描画する画面を追加しました。
ノードとエッジの意味
Data Threads では、主な要素を次のように考えると理解しやすくなります。
| 要素 | 意味 | 例 |
|---|---|---|
| ルートテーブル | アップロード、貼り付け、URL、データベース、サンプルなどから読み込まれた元データ | california_housing.csv |
| 派生テーブル | AI 生成コードや更新処理によって作られた新しい表 | housing_price_by_age_band |
| Trigger | どの指示、どの入力表、どのチャート設定から派生テーブルができたかを表す変換イベント | 「平均ではなく中央値にして」 |
| チャートカード | あるテーブルをもとに作られた可視化成果物 | 折れ線グラフ、棒グラフ、ヒートマップ |
| レポートカード | テーブルやチャートから生成された説明・レポート成果物 | 分析レポート |
| 実行中カード | エージェントが思考中、コード実行中、確認待ちであることを示す状態表示 |
thinking、clarifying
|
Data Threads の各ノードはデータバージョンを中心に考えられ、可視化はそのデータから作られた成果物としてぶら下がります。現実装でも、DictTable の derive 情報、Trigger、チャート、レポート、エージェントの ドラフト状態を組み合わせて、タイムラインとして表示する実装になっています。
グローバル Data Threads とローカル Data Thread
Data Threads には 2 つの粒度があります。
| 種類 | 目的 | 使いどころ |
|---|---|---|
| グローバル Data Threads | ワークスペース全体の探索履歴を俯瞰する | 過去の枝を探す、別方向へ分岐する、以前のチャートを再利用する |
| ローカル Data Thread | 現在作業している枝だけを近くに表示する | 直前の指示を再実行する、追加入力する、最近の指示を修正して再実行する |
グローバルビューは「探索地図」です。ユーザーはチャートのサムネイル、データカード、指示カードを見ながら、どの分析枝に戻るかを選べます。一方、ローカルビューは「今触っている枝の作業台」です。直前の結果に対して、短い追加指示を出したり、やり直したりする用途に向いています。
この分離は、分析作業の 2 つのリズムに対応しています。遠くの枝へ戻るときは一覧性が必要ですが、直近の修正では大きな履歴パネルを見に行くより、現在の作業場所で素早く操作できる方が効率的です。
AI に渡す文脈を制御する仕組み
Data Threads の重要な効果は、AI に渡すコンテキストを整理可能であるという点です。現在の実装では、フロントエンド側で「注目しているテーブルの派生チェーン」を Rich Focused Thread として組み立てます。この Focused Thread には、各ステップのユーザー指示、表示用指示、エージェントの計画、生成テーブルの列、行数、チャート種別、エンコーディング、要約が含まれます。
バックエンド側では、この情報を次のように 2 段階で扱います。
| コンテキスト | 内容 | AI にとっての役割 |
|---|---|---|
| Focused Thread | 現在選ばれている枝の詳細な履歴 | 次の変換で直接再利用すべき文脈 |
| Other Threads | 他の枝の軽量な要約 | すでに試した方向や重複回避の参考情報 |
Data Threads は「すべてのチャット履歴を LLM に投げる」仕組みではなく、現在の作業枝を詳細に渡し、他の枝は軽く渡すことで、関連文脈を濃く、周辺文脈を薄く扱います。これは長い会話履歴で起こりやすい、関係ない過去発言への引きずられを減らすための設計となっています。
「コードの再利用」ではなく「計算の系譜の再利用」という考え方
Data Threads の価値は、過去に生成された表そのものだけを再利用することではありません。追加入力の際に AI が前回の会話履歴や生成コードを参照し、新しい目的に合わせてコードを書き換える設計となっています。
もし現在の派生テーブルだけを入力として次の変換を書くと、過去の変換の意図や元データの情報が失われることがあります。例えば「やっぱり 10 年刻みではなく 5 年刻みにして」と言われた場合、最終テーブルだけでは、どの列から年齢帯を作ったか、どの段階で住宅価格を集計したかが分かりにくい場合があります。Data Threads では、元データから現在の表に至る計算の系譜を持つため、AI は前のコードを修正する形で新しい表を作れます。
実装ではテーブルには派生元や生成コード、出力変数、コード署名などが保持され、エージェントとの対話には指示、計画、要約が残ります。Data Threads はそれらを、人間にも AI にも使える形で再構成する表示レイヤーです。
分岐、修正、追従の三つの操作
Data Threads における反復操作は大きく 3 つに分類できます。
| 操作 | 意味 | 例 |
|---|---|---|
| 分岐 | 過去のデータやチャートを起点に、別方向の分析を始める | 年齢帯別の平均価格チャートから、中央値分析と平均所得追加へ分ける |
| 修正 | 直前または過去の指示を書き直して、同じ位置の結果を改善する | 「中央値にして」だけでは曖昧だったので「住宅価格は中央値、所得は平均値にして」と明示する |
| 追従 | 現在の結果に短い追加指示を出して、さらに変換する | 「平均所得も追加して」 |
ユーザー調査では、参加者ごとに好みが分かれたと報告されています。枝を細かく分けてワークスペースを整理する人もいれば、長い一本のスレッドに追加入力を重ねる人もいました。Data Threads は、どちらか一方の作業スタイルを強制するのではなく、幅広い分析スタイルを許容するための仕組みです。
ユーザーは分岐元のチャートを選択して、スレッドを分岐させることができる。
Data Agent: コンテキストを分解
Data Agent はチャットの全会話をそのまま送るのではなく、現在選ばれているテーブルと Data Threads から、情報の重要度に応じて 3 層のコンテキストを作ります。
| 分類 | 主な中身 | 詳細度 | 目的 |
|---|---|---|---|
| Tier 1 | primary/other の root または anchored table | 高 | 現在利用できる入力データを理解する |
| Tier 2 | 選択中の派生チェーン | 高 | 直前までの計算意図、作成表、チャートを再利用する |
| Tier 3 | その他の派生チェーン | 低 | 既に試した方向を把握し、重複を避ける |
| Relevant Knowledge | 質問と表名に関連する rules/experiences | 中 | ユーザー固有の知識や過去の経験を参照する |
| alwaysApply rules | 常時適用ルールの全文 | 高 | 必須制約として system と user の両方へ注入する |
| 画像 | focused leaf のサムネイル、ユーザー添付画像 | 低解像度 | 現在の見た目や画像内情報を補助的に参照する |
Tier 1 に含まれる表コンテキスト
ブラウザーは全行データを送らず、表名を送ります。バックエンドの build_lightweight_table_context() がワークスペースから DataFrame を読み直し、LLM 用の要約を作ります。
この初期要約だけで不足する場合、LLM は inspect_source_data または explore を呼べます。値サンプルは各列の全体 distinct 値から選びますが、行サンプルは先頭行に制限されます。列数全体に対する一括上限はここでは設けられていないため、非常に横長な表ではコンテキストが大きくなる可能性があります。
System message と User message の内部構造
実際は、Agent Log 画面によって LLM に入力されたすべてのコンテキストを可視化できるようにしています。
実際に使ってみた課題
データ変換の系譜がグラフとして可視化できるのは素晴らしい発想だと思いました。これは既存の長いチャット履歴の蓄積だけでは見えない部分です。このチャートがどのデータで描画されたものなのか、どのような加工過程を経たものなのか。AI Agent の信頼性を担保するための重要な情報です。そういえば、Microsoft Purview に Lineage ってありましたよね。チャットスレッドがグラフに紐づく部分は、UI をさらに洗練させて分析全体を俯瞰することも可能かと思います。
あと Data Threads の本質的な問題とはズレますが、自然言語による指示と UI 構造化との境目で結構思い通りに描画できないことが多かったですね。私のようにすべての可視化を自分でコントロールしたい人には向いていないかもしれない😅 自然言語による指示と UI 構造化間のバランスが UX に直結してくるという感想です。
優秀なフロンティアモデルにすべてをぶち込んでしまえば何とかなってしまう時代から、コンテキストを適切なタイミングで必要なものだけを入力する時代となってきていますから、ニーズに応じて切り替えたり、必要な要素だけ抽出したりカスタマイズして独自のソリューションに生かせればいいかなと思います。個人的にはもう少し LLM 側の自由度を高めつつ、Data Threads と組み合わせてみたい気がします。
…ということで、じゃあ完全に LLM にチャートを描画させた場合はどのような UX になるのか、ということで私の方で右ペインに Python Chart ペインを追加しました。これにより、同じ質問で Data Formulator 2 vs LLM 直接描画の結果を比較することができます。Python Chart は Data Threads を使用していません。
同じ質問を、既存の Data Agent(左)か Python Chart(右)か、またはその両方か選べるようにカスタマイズしました。これにより結果を比較できます。
Python Chart の方はぶっちゃけプロンプト次第ではありますね… 一応「チャット履歴がある場合は、過去の指示、生成されたコード、および結果に基づいて、一貫性のある可視化を作成してください」って入れてあります。
Python Chart を何も考えずに使うと、このようにチャートの一貫性が保てませんからね。
Data Threads vs Python Chart: より直接的な DataFrame コンテキスト
Python Chart は Data Agent と異なり、Data Threads、Knowledge、function tools を使いません。読み込めた全 DataFrame を直接プロファイルし、必要であれば Python Chart 専用の会話履歴を追加します。
Python Chart の履歴は History スイッチが ON の場合だけ送られます。チャット履歴に入るのは表示文、生成コード、標準出力、実行エラーであり、画像、プレビュー、結果表、raw は含まれません。初回の Data context は全行ではありませんが、読み込めた全表について全列を列挙するため、表数や列数が多いほど大きくなります。
| 観点 | Data Agent | Python Chart |
|---|---|---|
| 入力表 | root/anchored table を詳細化 | 読み込めた全 DataFrame |
| 派生履歴 | Focused Thread と Other Threads | History ON の過去コードに含まれる場合のみ |
| 表サンプル | 最大 5 行・一表 1,000 文字 | 最大 6 行・明示的な一表文字数上限なし |
| 列情報 | app type、説明、値サンプル、数値統計 | pandas dtype、先頭 1,000 行の unique count |
| Knowledge/rules | あり | なし |
| 画像 | 現在のグラフと添付画像 | なし |
| function tools | 4 種類 | なし |
実践: 実際のデータセットで Data Threads の効果を比較する
Data Threads の効果は、一回だけチャートを作る課題よりも、同じ中間結果から追加入力と分岐を繰り返す課題で明確になります。実際のデータセットを使い、Data Formulator 2 の通常チャートと Python Chart を同じ質問で同時実行します。
この比較では、単純にどちらのチャートが美しいかを見るのではなく、冷静に次の点を観察します。
- 過去の集計条件を短い追加入力で維持できるか。
- 一つ前ではなく、任意の中間結果へ戻って別案を作れるか。
- どの元データと変換から結果ができたかを確認できるか。
- 分岐した複数案を失わず、並べて比較できるか。
- チャートだけでなく、チャートに渡された派生テーブルを検証できるか。
データセットは、以下の Demos から選ぶこともできますし、Scikit-learn 等から持ってきたデータをアップロードすることもできます。
Data Threads Mini UI
手っ取り早く Data Threads の機能だけを理解するために、デモ専用のシングル HTML UI 「Data Threads Mini UI」も開発しました。この UI では Data Threads をメインキャンバスに配置して、データの加工フェーズに応じて、どのような Data Thread が生成されているのかをビジュアルで理解できるようにしたものです。
デモなので実際に生成しているわけではありませんが、LLM に送信されるコンテキストのみに注目できるように設計しています。
GitHub








