R2camtrapdp: スキーマ駆動ワークフロー(日本語)

library(R2camtrapdp)

概要

R2camtrapdp は、任意のスプレッドシート形式で保持したカメラトラップデータを Camera Trap Data Package (Camtrap DP) へ変換します。

本バージョンは スキーマ駆動 です。出力テーブルの構造・型・制約は、指定した Camtrap DP バージョンの公式 Frictionless table schema から読み取られます。 その結果、本パッケージは

旧来のヘルパ関数(create_deployments(), create_media(), create_observations())と R6_CamtrapDP クラスは名前・引数とも従来どおりで、 既存スクリプトはそのまま動作します。スキーマ駆動の挙動はその上に追加されています。

ネットワークについて。 テーブルを登録する(set_deployments() 等)と、 指定バージョンの table schema を初回に GitHub から取得し、以後はキャッシュします。 オフライン環境では local_schema = 引数でローカルのスキーマファイルを渡せます。

データ

本パッケージには、画像記録を伴う複数デプロイメントの例データが同梱されています。

# 複数デプロイメント・画像データ
data("Idep")   # デプロイメント表
data("Iobs")   # 観察表

Idep はデプロイメント(カメラ設置)1 件につき 1 行で、deploymentID, longitude, latitude, locationID, startDate/startTime, endDate/endTime, cameraID, cameraModel, Delay, Height, bait, setupBy などの列を持ちます。Iobs は観察 1 件につき 1 行で、機関/コレクション コード、filename, deploymentID, date/time, obsID, eventID, eventStart/eventEnd, object, genus, species, class, individualCount を持ちます。

1. バージョンの選択とスキーマの確認(任意)

パイプライン全体は選択したバージョンのスキーマで駆動されます。Camtrap DP の 1.0 / 1.0.1 / 1.0.2 はすべて対応しており、これらの table schema は列名・型・ 制約が共通であるため、実質的な差は 1.0.2 が欠損値トークン(NA, NaN, nan) を追加で認識する点のみです。任意バージョンのスキーマは TableSchema で直接確認 できます。

(注: 公式の 1.0 profile(メタデータの JSON Schema)には上流のバグ(不正な内部 $ref)があり、新しい Frictionless が拒否します。このため version = "1.0" を指定 すると警告が表示されます。validate_frictionless() が自動で回避しますが、1.0.1 以降の利用を推奨します。)

version <- "1.0.1"

dep_schema <- TableSchema$new("deployments", version = version)
dep_schema$field_names()           # スキーマが定義する全列
dep_schema$required_field_names()  # 必須(存在かつ非欠損)の列
dep_schema$empty_table()           # 型付き 0 行の「殻」テーブル

通常は手作業で確認する必要はありません(R6_CamtrapDP が適切なスキーマを自動取得・ キャッシュします)が、当該バージョンが何を要求するかの把握に役立ちます。

check_schema() は、スキーマ自体が well-formed な Frictionless Table Schema か (サポートされる type、型ごとに妥当な制約、定義済みフィールドを指す主キー・外部 キー)を確認します。新規・手編集のスキーマを採用する前の点検に有用です。

dep_schema$check_schema()

スキーマ内の外部(URL)参照

Camtrap DP の情報の一部は、機械検証可能な制約ではなく URL で指定されます。 具体的には意味マッピング(skos:exactMatch / broadMatch / narrowMatch による Darwin Core, Audubon Core 等の用語への対応)や、フィールドの description 内の参照 URL(例: fileMediatype の IANA メディアタイプ登録簿、individualSpeed の手法 DOI) です。本パッケージが強制するのは構造化された制約のみで、URL で参照される意味は 検証されません。バージョンや新フレーバーを採用する際にこうした指定を見落とさない よう、次で一覧化できます。

dep_schema$external_references()   # スキーマが宣言する全 URL(skos・description・schema URL)
dep_schema$semantic_only_fields()  # 意味が URL でのみ定義され、値域を機械検証できない列

external_references() は整形済みの表(resource, field, key, category, url)を返します。semantic_only_fields() は、参照先の権威に照らして手動確認が 必要な列を示します。パッケージ全体は datapackage$external_references() で一括 スキャンできます。

2. 3 つの中核テーブルの作成

デプロイメントの作成

デプロイメントデータ(Idep)を用い、従来どおりに作成します。 create_deployments() は結合済みの日時、または日付列・時刻列の別指定の両方を 受け付けます。

deployments <- create_deployments(
  deploymentID         = Idep$deploymentID,
  longitude            = Idep$longitude,
  latitude             = Idep$latitude,
  locationID           = Idep$locationID,
  deploymentStart_date = Idep$startDate,
  deploymentStart_time = Idep$startTime,
  deploymentEnd_date   = Idep$endDate,
  deploymentEnd_time   = Idep$endTime,
  cameraID             = Idep$cameraID,
  cameraModel          = Idep$cameraModel,
  cameraDelay          = Idep$Delay,
  cameraHeight         = Idep$Height,
  baitUse              = Idep$bait,
  setupBy              = Idep$setupBy)

create_deployments() は上記以外に次の引数も受け付けます: deploymentStart / deploymentEnd(結合済み日時。*_date / *_time の組の代わりに使用)、 locationNamecoordinateUncertaintycameraDepthcameraHeight と同時指定 不可)、cameraTiltcameraHeadingdetectionDistancetimestampIssuesfeatureTypehabitatdeploymentGroupsdeploymentTagsdeploymentCommentstz(タイムゾーン、既定 "Asia/Tokyo")。

メディアの作成

# メディア ID
mediaIDi <- paste(Iobs$institutionCode,
                  Iobs$collectionCode,
                  Iobs$locationID,
                  as.numeric(factor(Iobs$filename)),
                  sep = "_")

# ファイル情報
fileName      <- Iobs$filename
filetype      <- tolower(unlist(lapply(strsplit(fileName, "\\."), "[", 2)))
fileMediatype <- paste("image", filetype, sep = "/")
filePublic    <- !grepl("ヒト", fileName)   # ヒト画像は非公開にする

media <- create_media(
  mediaID        = mediaIDi,
  deploymentID   = Iobs$deploymentID,
  timestamp_date = Iobs$date,
  timestamp_time = Iobs$time,
  filePath       = "Image",
  filePublic     = filePublic,
  fileMediatype  = fileMediatype,
  captureMethod  = "activityDetection",
  fileName       = fileName)

create_media() は上記以外に次の引数も受け付けます: timestamp(結合済み日時。 timestamp_date / timestamp_time の代わり)、exifDatafavoritemediaCommentstzomitduplicate(重複 mediaID を除外。既定 TRUE)。

observationの作成

# イベント単位の観察
observationLevel <- "event"

# observationType はスキーマの enum 値のいずれか
observationType <- ifelse(Iobs$object == "hito", "human",
                   ifelse(Iobs$object == "none", "blank",
                   ifelse(Iobs$object == "unidentifiable", "unknown", "animal")))

# 学名
scientificName <- ifelse(is.na(Iobs$genus), Iobs$class, paste(Iobs$genus, Iobs$species))

# 一意の観察 ID
observationID <- paste(mediaIDi, Iobs$obsID, sep = "_")

observations <- create_observations(
  observationID             = observationID,
  deploymentID              = Iobs$deploymentID,
  eventID                   = Iobs$eventID,
  eventStart                = Iobs$eventStart,
  eventEnd                  = Iobs$eventEnd,
  observationLevel          = observationLevel,
  observationType           = observationType,
  scientificName            = scientificName,
  count                     = Iobs$individualCount,
  classificationMethod      = "human",
  classificationProbability = 1)

create_observations() は上記以外に次の引数も受け付けます: mediaIDeventStart_date / eventStart_timeeventEnd_date / eventEnd_time の組 (結合済み eventStart / eventEnd の代わり)、cameraSetupTypelifeStagesexbehaviorindividualIDindividualPositionRadiusindividualPositionAngleindividualSpeedbboxXbboxYbboxWidthbboxHeightclassifiedByclassificationTimestampobservationTagsobservationCommentstzomitduplicate

3. データパッケージの組み立て

R6 オブジェクトの作成(バージョン指定)

datapackage <- R6_CamtrapDP$new(version = "1.0.1")

ここで与える version が、検証および datapackage.json に使われるスキーマを 選択します。別の Camtrap DP リリースを対象にする場合は変更してください。

テーブルの登録(スキーマ検証付き)

set_deployments(), set_media(), set_observations() は名前を維持しつつ、 各々がスキーマ型への変換と、選択バージョンのスキーマに対する検証を行います。 問題は要約として表示され、validate = FALSE で表示を抑制できます。

datapackage$set_deployments(deployments)
datapackage$set_media(media)
datapackage$set_observations(observations)

(スキーマの取得、ファイル書き出し、分類検索、Python 呼び出しを伴うチャンクは、 本 vignette のビルド時には表示のみで実行されません。そのため出力は表示されません。)

検証の要約は、各問題について、ファイル・列・行・違反した規則・メッセージを示します。 例えば enum に反する値、minimum/maximum の範囲外の数値、必要な書式に一致しない 日時などです。列の型にすら適合しない値(例: number 列の非数値文字列)は、無言で NA に変換されるのではなく type エラーとして報告されます。

テーブル間リレーションの検査

外部キー(例: media.deploymentIDdeployments に、observations.mediaIDmedia に存在する必要がある)と主キーの一意性は、各テーブルのスキーマから読み取られ、 登録済みのテーブル間で検査されます。

datapackage$check_relations()

主キーまたは必須の外部キー列が、格納後のテーブルでまるごと欠損している場合 (多くは列名不一致で型変換が NA 補完したケース)、check_relations() は警告を出し データを指し示します(例: datapackage$data$observations has 'deploymentID' entirely missing ...)。 datapackage$data$<resource> を直接確認できます。

4. メタデータ

Camtrap DP は 5 つの必須メタデータ(contributors, project, spatial, temporal, taxonomic)と created を必要とします。さらに 6 つの任意プロパティがあります。 メタデータ関数は従来版から変更ありません。

どのメタデータが必須かを profile で確認する

必須メタデータ自体は、パッケージ profile(JSON Schema)から読み取られます。 metadata_requirements() は必須トップレベルプロパティ・それを設定するメソッド・ 現在の設定状況を一覧化し、check_metadata() は現在のオブジェクトを profile に 照合して不足(project.samplingDesign のようなネストしたキーを含む)を報告します。

datapackage$metadata_requirements()   # チェックリスト: property, required, set_with, currently_set
datapackage$check_metadata()          # 不足している必須メタデータを報告

これは Frictionless が行うメタデータ(profile)検証(§6)に相当する R 側の事前 チェックで、パッケージを書き出して Python を呼ぶ前に必須構造を確認できます。

必須メタデータ

貢献者(Contributors)

add_contributors()title, email, path, role, organization 列を持つ データフレームを取り込みます。rolecontact, principalInvestigator, rightsHolder, publisher, contributor のいずれかです。

cd <- data.frame(
  title        = c("Keita Fukasawa", "Kana Terayama"),
  email        = c("fukasawa@nies.go.jp", "terayama.kana@nies.go.jp"),
  path         = c("https://orcid.org/0000-0003-0272-9180",
                   "https://orcid.org/0000-0001-6935-7233"),
  role         = c("contact", "principalInvestigator"),
  organization = c("National Institute for Environmental Studies (NIES)",
                   "National Institute for Environmental Studies (NIES)"))
datapackage$add_contributors(cd)

プロジェクト(Project)

datapackage$set_project(
  title            = "DummyData",
  samplingDesign   = "simpleRandom",
  captureMethod    = "activityDetection",
  individualAnimals = FALSE,
  observationLevel = "event")

samplingDesignsimpleRandom, systematicRandom, clusteredRandom, experimental, targeted, opportunistic のいずれか、captureMethodactivityDetection または timeLapseobservationLevelmedia または event です。任意で id, acronym, description, path も指定できます。

空間・時間(Spatial and temporal)

set_st() はデプロイメントから空間・時間範囲を導出するため、 set_deployments() の後に呼び出します。

datapackage$set_st()

分類(Taxonomic)

set_taxon() は観察の一意な scientificName を列挙し、taxonID, taxonRank と 上位分類を分類データベース(既定は gbifitis / ncbi も可。taxadb::get_ids 参照)から取得します。CamtrapDP の taxonomic には taxonID(GBIF/IUCN 等の識別子・ URI)が必要なため、taxadb は R2camtrapdp の必須依存(パッケージと共に導入)です。 本処理にはインターネット接続も必要です。

datapackage$set_taxon()

マッチできない名前は taxonID = NA(出力では省略。<uri>NA のような不正値にはなりません)。 set_taxon() は、scientificName に不要な空白がある場合と、選択した DB に taxonID が 無い場合に警告を出すので、対象の名前を修正・確認できます。

作成日時(Created)

datapackage$update_created(tz = "Asia/Tokyo")

任意メタデータ

ライセンス(Licenses)

Camtrap DP はデータ用とメディア用に各 1 つ以上のライセンスを想定します。

datapackage$add_license(name = "CC-BY-4.0",
                        path = "http://creativecommons.org/licenses/by/4.0/",
                        scope = "data")
datapackage$add_license(name = "CC-BY-4.0",
                        path = "http://creativecommons.org/licenses/by/4.0/",
                        scope = "media")

関連識別子(Related identifiers)

datapackage$add_relatedIdentifiers(
  relationType          = "IsSupplementTo",
  relatedIdentifier     = "https://doi.org/xxxx",
  relatedIdentifierType = "DOI",
  resourceTypeGeneral   = "JournalArticle")

プロパティ・ソース・参考文献

datapackage$set_properties(
  name     = "dummy-nies",
  homepage = "https://www.nies.go.jp/biology/snapshot_japan/index.html")
datapackage$add_sources(title = "DummyData")
datapackage$add_references(reference = "DummyNIES https://doi.org/xxxxx")

カスタムリソース

set_custom() は追加のリソース(例: 個体数推定に使うデータ)をメタデータとして 付加します。3 つの中核テーブルを登録した後に呼び出します。

RD <- data.frame(id = seq_len(388), Time = sample(1:29, 388, replace = TRUE))
datapackage$set_custom(name = "rest",
                       description = "data for the REST method",
                       data = RD)

5. データパッケージの出力

# camtrapdp オブジェクトを返す
data_camtrapdp <- datapackage$out_camtrapdp()

# あるいは deployments.csv / media.csv / observations.csv + datapackage.json を書き出す
datapackage$out_camtrapdp(write = TRUE, directory = path)

書き出し時、CSV は全スキーマ列を含み、boolean は true/false(小文字)で出力され、 未設定のメタデータは省略されるため、空のプレースホルダが偽の検証エラーを引き起こす ことはありません。

6. Frictionless による検証

適合の事前チェック(Python を呼ぶ前に)

Python を実行する前に、パッケージがそもそも well-formed な Frictionless データ パッケージか、また CamtrapDP 形式かを R 側で確認できます。Frictionless が行う構造 チェックを R で再現するもので、新規・特殊なスキーマの問題を早期に発見できます。

datapackage$check_descriptor()        # 記述子+各 table schema の構造(Frictionless 仕様)
datapackage$check_camtrap_profile()   # profile が CamtrapDP でなければ警告

パッケージは Frictionless として妥当でも、CamtrapDP 形式とは限りません。 それは profile が CamtrapDP プロファイル(既定)かどうかで決まります。GeoJSON 妥当性や物理ファイル構造を含む権威ある判定は、下記の Frictionless 実行が担います。

Frictionless の実行

書き出したパッケージを公式スキーマに照らして、Python の Frictionless で確認できます。frictionless を導入した Python が必要です (pip install frictionless)。

issues <- datapackage$validate_frictionless(directory = path, python = "python")
ctdp_is_valid(issues)   # error が無ければ TRUE

注意 — path を上書きします。 validate_frictionless() は既定で write = TRUE のため、検証前に out_camtrapdp() を呼んで directory 内の datapackage.json と CSV を現在のオブジェクトの内容で上書きします。すでに ディスク上にある datapackage を上書きせず検証だけしたい場合は write = FALSE、 または R6 オブジェクト不要の検証専用関数を使ってください:

ctdp_validate_frictionless("path/to/existing/package", python = "python")

issues は問題 1 件につき 1 行の整形済み表で、source(ファイル)、field(列/ プロパティパス)、row(行)、違反した constraint、問題の value(実値)、 message を与えるため、エラー箇所を正確に把握できます。セルエラーの value は不正な セル値、メタデータ(profile)エラーの value は note 内のプロパティパス(例 contributors[].email)で datapackage.json を辿って解決した実値です。R 側のスキーマ検査・リレーション検査・メタデータ(profile)検査・ 適合事前チェック・(任意で)Frictionless レポートは、1 回の呼び出しで集約する こともできます。

datapackage$validate(relations = TRUE, metadata = TRUE, conformance = TRUE,
                     frictionless = TRUE, directory = path, python = "python")

7. 任意のスプレッドシートを直接変換する

上記のヘルパは変数を事前に命名済みであることを前提とします。独自の列名を持つ生の スプレッドシートがある場合は、ctdp_build_table() でマッピング・日時結合・型変換・ 検証を一括実行できます(任意バージョン対応)。

version    <- "1.0.1"
dep_schema <- TableSchema$new("deployments", version = version)

# 任意の列名+独自列を持つ生データの例
raw <- data.frame(
  station   = c("A01", "A02"),
  lat       = c(35.1, 36.2),
  lon       = c(139.5, 140.1),
  start_day = c("2023-04-01", "2023-04-02"),
  start_clk = c("09:00:00", "10:30:00"),
  end_day   = c("2023-05-01", "2023-05-02"),
  end_clk   = c("09:00:00", "10:30:00"),
  myNote    = c("独自列として保持", "これも保持"),
  stringsAsFactors = FALSE)

# マッピング:生データで使用されている列名とCamtrapDPのフィールド名との関係(マッピング)を作成
mapping <- c(station = "deploymentID", lat = "latitude", lon = "longitude")

built <- ctdp_build_table(
  dep_schema, raw, mapping = mapping,
  datetime_merges = list(
    list(date_col = "start_day", time_col = "start_clk", target = "deploymentStart"),
    list(date_col = "end_day",   time_col = "end_clk",   target = "deploymentEnd")))

ctdp_summarize_validation(built$issues)   # スキーマ上の問題
datapackage$set_deployments(built$data)   # 結果をパッケージへ投入

myNote のような独自列は保持されます。パッケージ書き出し時、独自列は datapackage.json 内のインライン拡張スキーマとして宣言されるため、Frictionless が 受理します。

8. 別のスキーマフレーバー(例: bioacoustics)

各テーブルは指し示したスキーマで駆動されるため、本パッケージは TDWG がホストする カメラトラップのスキーマに限定されません。別フレーバー——例えば Camtrap DP の bioacoustics 拡張——を対象にする には、テーブルおよび profile の URL を明示します。これらのスキーマは別リポジトリに あり、独自の列構成(例: cameraID の代わりに deviceID、さらに samplingFrequency, frequencyLow/frequencyHigh など)と、テーブルごとに異なる 日時書式(media / observations のイベント時刻は小数秒付き %Y-%m-%dT%H:%M:%S.%f%zdeployments の時刻は小数秒なし)を用います。スキーマ駆動 の検証はこれらすべてに自動的に適応します。生データの media / observations の タイムスタンプに小数秒が無い場合は、スキーマの %f 書式に合うよう .000 を自動 付与します。

set_properties() でフレーバーを一度指定すれば、あとは通常どおりテーブルを追加 できます(set_*() は設定済みの schema_urls を使うため、各呼び出しに schema = を渡す必要はありません)。

ba <- "https://raw.githubusercontent.com/camera-traps/bioacoustics/main/camtrap-dp/1.0.2/%s"

dp <- R6_CamtrapDP$new(version = "1.0.2")
dp$set_properties(
  version     = "1.0.2",
  profile     = sprintf(ba, "camtrap-dp-profile-acoustic.json"),
  schema_urls = list(
    deployments  = sprintf(ba, "deployments-table-schema-acoustic.json"),
    media        = sprintf(ba, "media-table-schema-acoustic.json"),
    observations = sprintf(ba, "observations-table-schema-acoustic.json")))

# 音響のタイムスタンプは、音響スキーマの書式に合わせて小数秒を付与する
dp$set_media(data.frame(
  mediaID = "m1", deploymentID = "D1",
  timestamp = "2023-04-01T09:05:00.000+0900",
  filePath = "audio/m1.wav", filePublic = TRUE, fileMediatype = "audio/wav",
  samplingFrequency = 48000L, channels = 1L,
  stringsAsFactors = FALSE))

カメラトラップの列を音響フレーバーへ対応づける

マッピングが必要なのは、名前が音響フィールドと異なる列だけです。すでに音響 フィールド名と同じ列(deploymentID, latitude, deploymentStart など)は自動的に 対応づけられ、マッピングは不要です。deployments では、カメラトラップの camera* 列が device* に改名され、カメラ専用列は音響に対応が無いため除外し、音響固有の列は データがあれば設定します。

library(dplyr)

# カメラトラップ deployments -> 音響 deployments(改名が必要な列だけ)
mapping <- c(
  cameraID      = "deviceID",
  cameraModel   = "deviceModel",
  cameraDelay   = "deviceDelay",
  cameraHeight  = "deviceHeight",
  cameraDepth   = "deviceDepth",
  cameraTilt    = "deviceTilt",
  cameraHeading = "deviceHeading")

dep_acoustic <- camtrap_deployments %>%
  select(-any_of(c("featureType", "timestampIssues")))   # カメラ専用: 音響に対応列なし

dp$set_deployments(dep_acoustic, mapping = mapping)

列の対応 — deployments:

カメラトラップの列 音響の列 対応
deploymentID, locationID, locationName, latitude, longitude, coordinateUncertainty, deploymentStart, deploymentEnd, setupBy, detectionDistance, baitUse, habitat, deploymentGroups, deploymentTags, deploymentComments 同名 マッピング不要
cameraID / cameraModel / cameraDelay / cameraHeight / cameraDepth / cameraTilt / cameraHeading deviceID / deviceModel / deviceDelay / deviceHeight / deviceDepth / deviceTilt / deviceHeading マップ
featureType, timestampIssues 除外
elevation, devicePlatform, recordingSchedule, locationType 音響固有(あれば設定)

observations で改名が必要なのは cameraSetupTypedeviceSetupType のみです (音響は frequencyLow / frequencyHigh / classificationConfirmation も追加)。 media に改名はなく、追加フィールド(duration, bitDepth, samplingFrequency, gain, channels)のみです。

フレーバーのスキーマも、他のスキーマと同様に確認できます。なお TableSchema$new("deployments", version = "1.0.2")url_template なしで 呼ぶとカメラトラップ用の deployments スキーマが読み込まれます。音響の要件を確認 するには音響 URL を渡してください。requirements() は各フィールドの型・書式・制約を 表で返します。

acoustic_dep <- TableSchema$new(
  "deployments", version = "1.0.2",
  url_template = sprintf(ba, "deployments-table-schema-acoustic.json"))
acoustic_dep$field_names()
acoustic_dep$required_field_names()
acoustic_dep$requirements()        # field / type / format / required / enum / min / max / pattern
acoustic_dep$external_references()

create_deployments(), create_media(), create_observations() はカメラ トラップのスキーマに合わせて作られています。別フレーバー(または将来版の新規列) では、create_*() ではなくスキーマ駆動の経路(ctdp_build_table() か、カスタム schema = を渡した set_*() メソッド)でテーブルを構築してください。