2026年10月: LanceDB

書きたいこと

  • 画像・本文・属性情報をまとめて保存し、必要なデータを取り出せるLanceDBの便利さを紹介する

  • データの管理を支えるLanceフォーマットと、Pythonからの利用方法を伝える

  • pandas・Parquetからデータを登録し、必要な部分を取得してpandasでの分析や画像・本文の確認に利用する

  • Embeddingモデルの関連付けと日本語キーワード検索の準備を通じて、同じデータの取り出し方を広げる

  • 属性による絞り込み・キーワード検索・ベクトル検索を使い、画像・本文付きの結果を見比べる

  • S3などの保存先や、データ分析・Webアプリへの活用の広がりを紹介する

この記事で伝えたいこと

本記事では、画像・本文・属性情報をひとまとめに保存し、必要なデータをさまざまな方法で取り出せるLanceDBの便利さを紹介します。pandasでも扱える身近なデータを題材に、登録・取得とpandasでの利用から始め、キーワードや意味による検索へと使い方を広げます。普段のデータ管理・分析の延長で検索機能も活用できることを伝えます。

目次案

    1. データの管理と検索を支えるLanceDB

    • 1.1. 画像・本文・属性情報の一元管理

    • 1.2. LanceDBを支えるLanceフォーマット

    • 1.3. PythonからのLanceDBの利用

    1. 画像・本文・属性情報の登録と取得

    • 2.1. 実行環境と題材の準備

      • uv・Python 3.14・LanceDB 0.40.0で環境を用意し、5月の記事の写真12枚に説明文・カテゴリ・年を付けてDataFrameを作る

    • 2.2. pandas・Parquetからのデータ登録

      • DataFrameから画像本体を含むテーブルを作成し、Parquetからの登録は短い別経路として添える

    • 2.3. 必要なデータの取得とpandasとの連携

      • 条件と列を指定して取得し、DataFrameで集計する。選んだレコードの画像と本文を取り出して確認する

    1. キーワードと意味によるデータ検索

    • 3.1. Embeddingモデルの登録と列への関連付け

      • intfloat/multilingual-e5-base を登録して説明文の列と関連付け、ベクトルを自動で生成する

    • 3.2. 日本語キーワード検索の準備

      • ICUトークナイザーを指定して全文検索インデックスを作る。単語分割とベクトル化は別の処理であることを説明する

    • 3.3. キーワード検索とベクトル検索の実践

      • 同じ説明文を語句と意味で検索し、ID・本文・画像を並べて結果の違いを見比べる

    • 3.4. 属性による絞り込みと検索の組み合わせ

      • 2章で使ったカテゴリや年の条件を検索に加え、絞り込みの効き方を確認する

    1. データの保存先と活用の広がり

    • 保存先をS3などに変えられることと、データ分析・Webアプリでの利用へ広げられることを短く紹介する

    1. まとめ

    • 「まとめて保存し、必要な部分を取り出し、キーワードと意味でも検索する」流れを振り返り、手元のデータで試す入口を示す


寺田 学@terapyonです。2026年10月の「Python Monthly Topics」は、画像やテキストをまとめて保存し、検索にも利用できるデータベースのLanceDBを紹介します。

筆者は、独自に開発している研究用のベクトルDBで、データの保存にLanceフォーマットを使っています。当初は、一部のメタデータを表す数値の対応表(マッピングテーブル)をNumPyのオブジェクトとして持っていました。その時は管理が煩雑になりやすく、使うときにはデータをすべてメモリに読み込む必要がありました。Lanceへ移行してからは、データをひとまとまりとして扱え、必要な部分だけを読み出せるようになりました。このLanceを土台にしたデータベースがLanceDBで、筆者はPythonのプロジェクトからも利用しています。利用する中で見つけた不具合は、修正を送ってLance 13.0.0に取り込まれました。

写真に説明文や分類、検索用のベクトルを付けて扱うことは、pandasやParquet、DuckDB、NumPyでもできます。ただ、それらをひとつのテーブルにまとめておき、必要な列だけ読む、条件で絞り込む、キーワードや意味で検索する、といった取り出し方をそのテーブルに対してそのまま使えるのが便利な点です。

本記事では、写真と説明文、属性情報をLanceDBに登録し、pandasと連携しながら必要なデータを取り出すところから始めます。その後、キーワード検索とベクトル検索へと使い方を広げます。画像や文書に説明や分類を付けて管理している方や、手元のデータを検索にも使ってみたい方は、ぜひ試してみてください。

1. データの管理と検索を支えるLanceDB

まず、LanceDBで扱うデータの形と、それを支えるLanceフォーマット、Pythonからの使い方を見ていきます。

1.1. 画像・本文・属性情報の一元管理

LanceDBは、画像やテキスト、属性情報、検索用のベクトルを同じテーブルで扱えるデータベースです。各列の名前やデータ型はスキーマで定め、検索用のインデックスの作成や、行の追加・更新・削除、データのバージョン管理にも対応しています。詳しくは公式ドキュメントのテーブルの説明を参照してください。

今回は1枚の写真を1レコードとし、以下のような情報を持たせます。

情報

内容

ID

レコードを識別する値

画像

画像ファイルの本体を表すバイト列

本文

写真の内容を説明するテキスト

属性

カテゴリや年など、分類・絞り込みに使う情報

ベクトル

本文から生成する、意味による検索のための数値の配列

ベクトルは、本文の意味の近さで検索するために使います(2026年4月:Pythonによるベクトル検索の基礎と実践 ~Embedding、Vector DB~で紹介しました)。画像は、ファイルの保存先ではなく画像本体をテーブルに保存します。こうすると、属性で絞り込んだり検索したりしたレコードから、そのまま画像を取り出せます。なお、LanceDBには大きなデータを必要なときに読み込むための専用のBlob APIもありますが、本記事の写真は1枚100KB程度と小さいため、通常のバイナリ列を使います。違いは公式ドキュメントのマルチモーダルデータの説明にまとめられています。

1.2. LanceDBを支えるLanceフォーマット

LanceDBのデータは、導入で触れたLanceフォーマットで保存されます。LanceはLanceDBとは別のオープンソースプロジェクトで、LanceDBのオープンソース版と同じく、Apache License 2.0で公開されています。

Lanceは列指向の形式なので、写真のカテゴリを集計するときに画像本体まで読み出す必要はありません。一方で、検索結果の数件から画像や本文を取り出すような、行単位のランダムアクセスも重視して設計されています。筆者がLanceに興味を持ったのも、この両方を扱う点です。ただし、研究用のベクトルDBでの自分の用途では、アクセス性能に調整が必要な部分もありました。本記事では性能の比較ではなく、データの保存と利用の流れに着目します。

必要な列を使って集計し、選んだレコードの画像や本文を取り出す。こうした二つの使い方を支える保存形式がLanceであり、本記事ではLanceDBを通して利用していきます。

1.3. PythonからのLanceDBの利用

本記事では、LanceDBのオープンソース版をPythonから利用します。ローカルでの利用では、データベースサーバーを起動せず、保存先のディレクトリを指定して使い始められます。ElasticsearchのPythonクライアントのように起動済みのサーバーへ接続するのではなく、ライブラリとしてPythonの処理に組み込み、保存先のデータを直接扱います。保存先にはS3などのオブジェクトストレージも指定でき、これは後半で紹介します。

オープンソース版はApache License 2.0で公開されており、公式SDKにはPythonのほか、TypeScriptやRust向けのものがあります。分散処理や大規模な利用に向けては、複数のマシンで構成したクラスタで動くLanceDB Enterpriseも提供されています。

Pythonからは、辞書のリストやpandasのDataFrameでデータを登録し、条件や列を指定して取得した結果をDataFrameに変換できます。保存しているデータすべてではなく、必要な範囲だけをDataFrameにして、pandasで集計・可視化するという使い方です。次章では、写真と説明文を含むデータを用意し、pandas・Parquetからの登録と取得を確認していきます。

2. 画像・本文・属性情報の登録と取得

1章で示した1枚1レコードの構成を、実際にLanceDBへ登録します。この章では検索はまだ使わず、属性や列を指定して必要なデータを取り出すところまでを確認します。

2.1. 実行環境と題材の準備

本記事のコードは以下の環境で動作確認しています。

項目

バージョン

備考

Python

3.14.8

LanceDB

0.40.0

データの保存・取得

pandas

3.0.5

データの準備・集計

PyArrow

25.0.1

Parquetの読み込み(LanceDBの依存パッケージ)

Pillow

12.3.0

画像の表示

sentence-transformers

6.0.1

Embeddingの生成(3章)

uv を使う場合:

uvを使ったインストール
uv add lancedb pandas pillow sentence-transformers

pip を使う場合:

pipを使ったインストール
pip install lancedb pandas pillow sentence-transformers

PyArrowはLanceDBの、PyTorchはsentence-transformersの依存パッケージとして一緒にインストールされます。

題材には、2026年5月:Pythonマルチモーダル検索とANN 〜ベクトル検索の応用編〜で使った写真12枚を再利用します。以下のディレクトリ構成を前提としています。

ディレクトリ構成
project/
├── images/          # 写真の画像ファイル(12枚)
│   ├── europython-keynote-01.jpg
│   ├── europython-talk-01.jpg
│   └── ...
└── photos.csv       # 写真の情報をまとめたCSVファイル

photos.lancedb/ と photos.parquet が、2.2のコードを実行すると作成されます。

写真ごとのID・ファイル名・説明文・年・カテゴリは、以下のCSVファイルにまとめました。説明文と年は今回の説明用に付けた値で、年は実際の撮影年ではありません。

写真の情報をまとめたCSVファイル (photos.csv)
id,filename,text,year,category
0,europython-keynote-01.jpg,大きな会場のステージで登壇者が基調講演をしている。,2025,conference
1,europython-talk-01.jpg,Pythonの技術について発表者が講演している。,2026,conference
2,europython-panel.jpg,複数の登壇者がパネルディスカッションで議論している。,2026,conference
3,europython-social.jpg,イベントの交流会で参加者同士が会話を楽しんでいる。,2025,conference
4,waterside-park-ducks.jpg,池の水面で鴨が泳いでいる。,2026,park
5,waterside-park-flock.jpg,公園の水辺に鳥の群れが集まっている。,2025,park
6,skytree-04-27.jpg,街の建物の向こうに高い塔が見える。,2026,city
7,sluice-gate-01.jpg,川の水門と周囲の風景。,2025,river
8,europython-talk-02.jpg,技術イベントの会場で発表を聞いている。,2026,conference
9,europython-keynote-02.jpg,基調講演のステージと会場の様子。,2025,conference
10,waterside-park-pond.jpg,公園の池と周囲の緑が見える。,2026,park
11,sluice-gate-02.jpg,川の水門を別の場所から眺めた風景。,2025,river

このCSVファイルをpandasで読み込み、画像ファイルの本体をバイト列として image 列に加えます。

CSVファイルと画像からDataFrameを作る
from pathlib import Path
import pandas as pd

df = pd.read_csv("photos.csv")
# ファイル名ごとに画像を読み込み、バイト列をimage列に入れる
df["image"] = [Path("images", name).read_bytes() for name in df.loc[:, "filename"]]
print(df.drop(columns="image").head(3))  # 画像の列を除いて表示

実行結果は以下のようになります。

DataFrameの先頭3行(画像の列を除く)
   id                   filename                        text  year    category
0   0  europython-keynote-01.jpg   大きな会場のステージで登壇者が基調講演をしている。  2025  conference
1   1     europython-talk-01.jpg    Pythonの技術について発表者が講演している。  2026  conference
2   2       europython-panel.jpg  複数の登壇者がパネルディスカッションで議論している。  2026  conference

pandasのindexはLanceDBには保存されません。そのため、レコードを識別する値は id 列として明示的に持たせています。

2.2. pandas・Parquetからのデータ登録

作成したDataFrameを、そのままLanceDBのテーブルとして登録します。

DataFrameからテーブルを作成する
import lancedb

db = lancedb.connect("photos.lancedb")  # ① 保存先のディレクトリに接続
table = db.create_table("photos", data=df, mode="overwrite")  # ② テーブルを作成
print(table.count_rows())  # ③ 登録件数とスキーマを確認
print(table.schema)

実行すると、登録件数とスキーマが以下のように表示されます。

登録件数とスキーマ
12
id: int64
filename: large_string
text: large_string
year: int64
category: large_string
image: binary
  • ① connect() に指定したディレクトリ photos.lancedb に、テーブルのデータが保存されます。ディレクトリがなければ作成されます

  • ② mode="overwrite" は、同じ名前のテーブルがあれば作り直す指定で、コードを繰り返し実行できるようにしています。初回の実行時には、テーブルを新しく作る旨の WARN ログが表示されますが、問題はありません

  • ③ スキーマを見ると、DataFrameの列がそのままテーブルの列になり、画像は binary 型の列として保存されていることがわかります

筆者は普段、データの処理にはpandasを使っています。Parquetは、データをアーカイブしたいときやファイルサイズを減らしたいときに使うことがあります。こうしたParquetファイルからも、PyArrowで読み込んだテーブルをそのまま渡して登録できます。

Parquetファイルからテーブルを作成する
import pyarrow.parquet as pq

df.to_parquet("photos.parquet", index=False)  # 同じデータをParquetに書き出す
from_parquet = db.create_table(  # 読み込んだデータを別のテーブルとして登録
    "photos_from_parquet", data=pq.read_table("photos.parquet"), mode="overwrite"
)
print(from_parquet.count_rows())  # 12

以降は、pandasから登録した photos テーブルを使います。

2.3. 必要なデータの取得とpandasとの連携

登録したテーブルから、条件と列を指定してデータを取り出します。ここでは、カテゴリが park の写真について、IDやファイル名、説明文、年を取得します。

条件と列を指定して取得する
park = (
    table.search()
    .where("category = 'park'")  # ① 条件で行を絞り込む
    .select(["id", "filename", "text", "year"])  # ② 取り出す列を指定する
    .to_pandas()  # ③ DataFrameとして受け取る
)
print(park)

実行結果は以下のようになります。

カテゴリがparkの写真
   id                  filename                text  year
0   4  waterside-park-ducks.jpg       池の水面で鴨が泳いでいる。  2026
1   5  waterside-park-flock.jpg  公園の水辺に鳥の群れが集まっている。  2025
2  10   waterside-park-pond.jpg      公園の池と周囲の緑が見える。  2026
  • ① where() にはSQLの条件式を書きます

  • ② select() で取り出す列を指定します。画像の列を指定していないので、結果に画像は含まれません。必要な列だけを読み出せるという、1.2で説明したLanceの特徴をここで使っています

  • ③ to_pandas() で、結果をDataFrameとして受け取ります

取得した結果は通常のDataFrameなので、そのままpandasで集計できます。

取得した属性をpandasで集計する
meta = table.search().select(["category", "year"]).to_pandas()  # 属性の列だけ取得
# カテゴリと年ごとの件数を数える
print(meta.groupby(["category", "year"]).size().unstack(fill_value=0))

実行結果は以下のようになります。

カテゴリと年ごとの件数
year        2025  2026
category              
city           0     1
conference     3     3
park           1     2
river          2     0

集計の結果から気になる写真を見つけたら、そのレコードの本文と画像を取り出します。

選んだレコードの本文と画像を取り出す
from io import BytesIO
from PIL import Image

# ① id=4のレコードから本文と画像を取得する
row = table.search().where("id = 4").select(["text", "image"]).to_list()[0]
print(row["text"])  # 池の水面で鴨が泳いでいる。
img = Image.open(BytesIO(row["image"]))  # ② バイト列から画像を開く
img  # ③ 画像を表示する
  • ① to_list() は結果を辞書のリストで返します。id=4の1件だけなので、先頭の要素を使います

  • ② image 列には保存前の画像ファイルと同じバイト列が入っているため、BytesIO で包むとPillowで開けます

  • ③ Jupyter Notebookでは、最後の行で画像が表示されます(図1)

図1 id=4のレコードから取り出した画像

ここまでで、写真・説明文・属性を1つのテーブルに保存し、属性で絞り込んだ結果をpandasで集計して、選んだレコードから画像を取り出せました。ただ、「池の写真」を探すには、カテゴリや年といった属性だけでは足りません。次章では、説明文の内容を手がかりに、同じテーブルから写真を探します。

3. キーワードと意味によるデータ検索

2章では、カテゴリや年といった属性を条件にしてデータを取り出しました。この章では、説明文の内容を手がかりに、同じ写真のデータを探します。語句の一致で探すキーワード検索と、意味の近さで探すベクトル検索の2つを、同じテーブルに対して使います。

3.1. Embeddingモデルの登録と列への関連付け

ベクトル検索には、説明文をベクトルに変換するEmbeddingモデルが必要です。今回は、2026年4月:Pythonによるベクトル検索の基礎と実践 ~Embedding、Vector DB~と同じ多言語モデルの intfloat/multilingual-e5-base を使います。LanceDBには、Embeddingモデルを登録してテーブルの列と関連付ける仕組みがあります。関連付けておくと、データの登録時と検索時のベクトル化をLanceDBに任せられます。

このモデルは、検索対象の文書の先頭に「passage: 」、検索語の先頭に「query: 」を付けて使うことが推奨されています。LanceDBに用意されているsentence-transformers用のEmbedding関数は、文書と検索語を同じように処理するため、接頭辞を付ける処理を加えたクラスを作って登録します。

接頭辞を付けるEmbedding関数を登録する
from lancedb.embeddings import get_registry, register
from lancedb.embeddings.sentence_transformers import SentenceTransformerEmbeddings


@register("multilingual-e5")  # ① 名前を付けてレジストリに登録
class MultilingualE5(SentenceTransformerEmbeddings):
    name: str = "intfloat/multilingual-e5-base"

    def compute_source_embeddings(self, texts, *args, **kwargs):  # ② 保存する文書用
        texts = self.sanitize_input(texts)
        return self.generate_embeddings(["passage: " + text for text in texts])

    def compute_query_embeddings(self, query, *args, **kwargs):  # ③ 検索語用
        texts = self.sanitize_input(query)
        return self.generate_embeddings(["query: " + text for text in texts])


e5 = get_registry().get("multilingual-e5").create(normalize=True)  # ④ 関数を作成
  • ① @register() で付けた名前を使って、④で関数を作成します

  • ② データを登録するときに呼ばれ、説明文の先頭に「passage: 」を付けてベクトル化します。テーブルに保存される説明文は元のままです

  • ③ 検索するときに呼ばれ、検索語の先頭に「query: 」を付けてベクトル化します

  • ④ normalize=True で、ベクトルの長さを1にそろえます

次に、この関数をテーブルの列と関連付けます。LanceDBでは、Pydanticのモデルとしてテーブルのスキーマを定義できます。

Embedding関数と列を関連付けたテーブルを作る
from lancedb.pydantic import LanceModel, Vector


class Photo(LanceModel):
    id: int
    filename: str
    text: str = e5.SourceField()  # ① ベクトル化する元の列
    year: int
    category: str
    image: bytes
    vector: Vector(e5.ndims()) = e5.VectorField()  # ② ベクトルを入れる列


searchable = db.create_table("searchable_photos", schema=Photo, mode="overwrite")
searchable.add(df)  # ③ vector列を渡さずに登録
print(searchable.count_rows())
print(searchable.schema.field("vector"))

実行結果は以下のようになります。

登録件数とベクトルの列
12
pyarrow.Field<vector: fixed_size_list<item: float>[768]>
  • ① SourceField() を指定した text 列が、ベクトル化する元の列になります

  • ② VectorField() を指定した vector 列に、生成したベクトルが入ります。e5.ndims() はモデルが出力するベクトルの次元数で、今回は768です。このコードでは、e5.ndims() の呼び出し時にモデルを読み込み、キャッシュがなければモデル(約1.1GB)をダウンロードします

  • ③ 2章と同じDataFrameを add() で登録します。DataFrameに vector 列はありませんが、登録時に説明文から作られます

2章の photos テーブルはそのまま残し、ベクトルの列を持つ searchable_photos テーブルを別に作りました。元は同じDataFrameなので、IDや説明文、画像はそろっています。

3.2. 日本語キーワード検索の準備

キーワード検索(全文検索)では、本文を語に区切ってインデックスを作り、検索語と同じ語を含む行を探します。日本語は英語のように空白で語が区切られていないため、語に区切る処理が必要です。LanceDBでは、ICU(International Components for Unicode)のトークナイザーを指定すると、辞書を追加でインストールしなくても日本語を語に区切れます。

ICUを使った全文検索のインデックスを作る
from lancedb.index import FTS  # FTS: Full-Text Search(全文検索)

# ICUで本文を語に区切り、全文検索のインデックスを作る
searchable.create_index("text", config=FTS(base_tokenizer="icu"), replace=True)

replace=True は、インデックスがすでにあれば作り直す指定です。

キーワード検索は語の単位で一致を調べます。例えば、ICUは「水面」「水辺」「水門」をそれぞれ1つの語として区切るため、「水」で検索してもこれらの説明文は見つかりません。

3.3. キーワード検索とベクトル検索の実践

同じ検索語で、キーワード検索とベクトル検索の結果を比べます。search() に検索語を渡し、query_type に "fts"(全文検索)か "vector" を指定します。ベクトル検索では、3.1で登録した関数が検索語を自動でベクトル化します。

今回は12件と少ないため、ベクトル用のインデックスを作らず、全件のベクトルを調べます。データが多い場合は、ベクトル列にANN(近似最近傍探索)インデックスを作ることで、探索する候補を絞って高速に検索できますが、厳密な最近傍が得られるとは限りません。

キーワード検索とベクトル検索を比べる
for query in ["講演", "水の上を泳ぐ鳥"]:
    for query_type in ["fts", "vector"]:  # キーワード検索とベクトル検索
        found = (
            searchable.search(query, query_type=query_type)
            .select(["id", "text"])
            .limit(3)
            .to_pandas()
        )
        print(f"--- {query_type}: {query}")
        print(found.to_string(index=False))

LanceDB 0.40.0では、スコア列(_score・_distance)の自動追加について非推奨警告が出ますが、以下の結果を取得できます。(TODOメモ: ここはあとで方法を考える、将来方法がかわりそうなので・・。)

実行結果は以下のようになります。

キーワード検索とベクトル検索の結果
--- fts: 講演
 id                      text   _score
  9          基調講演のステージと会場の様子。 1.415293
  1  Pythonの技術について発表者が講演している。 1.319581
  0 大きな会場のステージで登壇者が基調講演をしている。 1.162367
--- vector: 講演
 id                      text  _distance
  9          基調講演のステージと会場の様子。   0.267774
  0 大きな会場のステージで登壇者が基調講演をしている。   0.281187
  8       技術イベントの会場で発表を聞いている。   0.310224
--- fts: 水の上を泳ぐ鳥
 id               text   _score
  5 公園の水辺に鳥の群れが集まっている。 1.912924
--- vector: 水の上を泳ぐ鳥
 id               text  _distance
  4      池の水面で鴨が泳いでいる。   0.286998
  5 公園の水辺に鳥の群れが集まっている。   0.308927
  7        川の水門と周囲の風景。   0.376811

_score はキーワード検索のスコアで、値が大きいほど一致の度合いが高い結果です。_distance はベクトルの距離で、値が小さいほど意味が近い結果です。2つは尺度が異なるため、数値どうしを比べることはできません。

「講演」では、キーワード検索は「講演」という語を含む3件を返しました。ベクトル検索も上位2件は同じ写真ですが、3件目には「講演」を含まない「技術イベントの会場で発表を聞いている。」が入っています。語が違っても、意味の近い説明文が見つかっています。

「水の上を泳ぐ鳥」では、違いがよりはっきりします。キーワード検索で見つかったのは、「鳥」という語を含むid=5の1件だけです。一方、ベクトル検索では「池の水面で鴨が泳いでいる。」(id=4)がもっとも近い結果になりました。説明文に「鳥」という語はありませんが、意味の近さで見つけられています。

今回のキーワード検索は、分割後のいずれかの語が一致すると結果を返します。ただし、「鴨」と「鳥」に加え、「泳ぐ」と「泳いでいる」も同じ語には正規化されないため、id=4には一致しません。

検索結果は、2章で扱ったレコードと同じものです。select() に image を加えれば、2.3と同じ方法で、見つけた写真をそのまま表示できます。

3.4. 属性による絞り込みと検索の組み合わせ

検索には、2章で使った属性の条件を組み合わせられます。ここでは「人前で技術の話をする」でベクトル検索をし、2026年の写真に絞り込みます。

ベクトル検索と年の条件を組み合わせる
found = (
    searchable.search("人前で技術の話をする", query_type="vector")
    .where("year = 2026")  # 2026年の写真に絞り込む
    .select(["id", "text", "year"])
    .limit(3)
    .to_pandas()
)
print(found.to_string(index=False))

実行結果は以下のようになります。

2026年の写真に絞り込んだベクトル検索の結果
 id                       text  year  _distance
  8        技術イベントの会場で発表を聞いている。  2026   0.278215
  1   Pythonの技術について発表者が講演している。  2026   0.330055
  2 複数の登壇者がパネルディスカッションで議論している。  2026   0.360671

絞り込まずに検索すると、3件目には2025年の「イベントの交流会で参加者同士が会話を楽しんでいる。」(id=3)が入ります。where() を加えたことで、2026年の写真の中から意味の近い3件が返りました。

ベクトル検索の where() は、初期値では検索の前に条件で絞り込みます(prefilter=True)。prefilter=False を指定すると、検索した後の結果を絞り込むため、指定した件数より少ない結果しか返らない場合があります。

ここまでで、2章で属性を指定して取り出したのと同じデータに対して、キーワード検索とベクトル検索を使い、属性による絞り込みも組み合わせられました。検索の結果も本文や画像と同じレコードなので、2章と同じようにpandasで扱ったり、画像を表示したりできます。

4. データの保存先と活用の広がり

ここまでは、ローカルのディレクトリにデータを保存してきました。lancedb.connect() には、s3://バケット名/パス のようなオブジェクトストレージのURIも指定できます。S3のほか、Google Cloud Storage(gs://)やAzure Blob Storage(az://)にも対応しており、認証情報は環境変数か storage_options で渡します。接続先を変えても、テーブルの作成や検索は同じAPIで扱えます。

LanceDBでは、2章で試したように必要な列だけを取り出せます。さらに、1.2で紹介したように、Lanceは行単位のランダムアクセスを重視した形式です。ファイルのどの範囲に必要なデータがあるかを特定し、その部分だけを読み出せるように設計されています(Lanceのファイル形式の仕様)。そのため、オブジェクトストレージに置いたデータでも、ファイル全体をダウンロードせずに、必要な列や行の分だけを読み込めます。この特徴が、クラウドストレージと相性の良い点です。

ただし、オブジェクトストレージはローカルのディスクに比べて、読み書きの遅延が大きくなります。保存先ごとの特徴や設定方法は、公式ドキュメントのストレージの説明と設定の説明にまとめられています。

活用の面では、データ分析とWebアプリの両方で、本記事の使い方をそのまま生かせます。データ分析では、2章のように条件や列を指定して必要な範囲だけをDataFrameにし、pandasで集計・可視化できます。Webアプリでは、3章の検索結果に本文や画像が含まれているため、見つけた写真を別のデータを探し直さずに画面へ表示できます。

筆者は、ログなどの大きなデータを、S3に置いたParquetとDuckDBで分析してきました。こうしたデータをLanceDBで扱うことも、今後試してみたいと考えています。

5. まとめ

本記事では、写真・説明文・属性情報をLanceDBの1つのテーブルにまとめ、以下の流れで取り出し方を広げました。

  • 登録と取得: pandasのDataFrameやParquetファイルから、画像本体を含むデータを登録しました。条件と列を指定して必要な部分だけを取り出し、pandasでの集計や画像の表示に使いました

  • 検索: E5をテーブルの列と関連付けてベクトルを自動で作り、ICUによる日本語のキーワード検索とベクトル検索を同じデータで比べました。属性による絞り込みも組み合わせられることを確認しました

  • 保存先: 必要な部分だけを読み出せるLanceの特徴から、S3などのオブジェクトストレージにも保存先を広げられることを紹介しました

導入で書いたように、どの処理もpandasやParquet、DuckDB、NumPyを組み合わせれば実現できます。それでも、データをひとつのテーブルにまとめておき、列の選択・条件による絞り込み・検索と取り出し方を変えながら同じレコードを使える点に、筆者はLanceDBの便利さを感じています。

まずは、手元のDataFrameを create_table() で登録し、必要な列だけを取り出すところから試してみてください。検索は、必要になったときに同じデータから始められます。

参考リンク