> ## Documentation Index
> Fetch the complete documentation index at: https://docs.woodwide.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Factor Analysis

> Discover the latent factors that explain variance in your data.

Factor analysis models discover the latent factors (principal components) that explain variance in your data and generate human-readable descriptions for each factor. Unlike other model types, the output has one row per *factor*, not per input instance.

## Training

Training fits the model to your data, learning a representation that can be used to extract factors. No `label_column` is needed. No validation metrics are computed for factor models.

<CodeGroup>
  ```python Python theme={null}
  import os, time, requests

  api_key = os.getenv("WOODWIDE_API_KEY")
  base_url = "https://api.woodwide.ai"
  headers = {"Authorization": f"Bearer {api_key}"}

  # Upload data
  with open("survey_responses.csv", "rb") as f:
      resp = requests.post(
          f"{base_url}/datasets",
          headers=headers,
          files={"file": ("survey_responses.csv", f, "text/csv")},
          data={"dataset_name": "survey_data"},
      )
  dataset_id = resp.json()["dataset"]["id"]

  # Train a factor analysis model
  resp = requests.post(
      f"{base_url}/models/train",
      headers=headers,
      json={
          "model_name": "survey_factors",
          "model_type": "factors",
          "dataset_id": dataset_id,
      },
  )
  model_id = resp.json()["model"]["id"]

  # Wait for training
  while True:
      model = requests.get(
          f"{base_url}/models/{model_id}", headers=headers
      ).json()
      if model["status"] == "ready":
          break
      time.sleep(5)
  ```

  ```python Python (SDK) theme={null}
  import time
  from pathlib import Path
  from woodwide import WoodWide

  client = WoodWide()

  dataset = client.datasets.create(
      file=Path("survey_responses.csv"),
      dataset_name="survey_data",
      override=True,
  )

  model = client.models.train(
      model_type="factors",
      dataset_id=dataset.id,
  )
  model_id = model.id

  while True:
      model = client.models.retrieve(model_id)
      if model.status in {"ready", "failed"}:
          break
      time.sleep(5)

  if model.status == "failed":
      raise RuntimeError("Training failed")
  ```

  ```javascript JavaScript theme={null}
  const fs = require("fs");
  const FormData = require("form-data");

  const apiKey = process.env.WOODWIDE_API_KEY;
  const baseUrl = "https://api.woodwide.ai";
  const headers = { Authorization: `Bearer ${apiKey}` };

  // Upload data
  const uploadForm = new FormData();
  uploadForm.append("file", fs.createReadStream("survey_responses.csv"), "survey_responses.csv");
  uploadForm.append("dataset_name", "survey_data");

  const uploadResp = await fetch(`${baseUrl}/datasets`, {
    method: "POST",
    headers: { ...headers, ...uploadForm.getHeaders() },
    body: uploadForm,
  });
  const { dataset: { id: datasetId } } = await uploadResp.json();

  // Train a factor analysis model
  const trainResp = await fetch(`${baseUrl}/models/train`, {
    method: "POST",
    headers: { ...headers, "Content-Type": "application/json" },
    body: JSON.stringify({
      model_name: "survey_factors",
      model_type: "factors",
      dataset_id: datasetId,
    }),
  });
  const { model: { id: modelId } } = await trainResp.json();

  // Wait for training
  while (true) {
    const modelResp = await fetch(`${baseUrl}/models/${modelId}`, { headers });
    const model = await modelResp.json();
    if (model.status === "ready") break;
    await new Promise((r) => setTimeout(r, 5000));
  }
  ```

  ```bash curl theme={null}
  export WOODWIDE_API_KEY="your_api_key_here"
  export BASE_URL="https://api.woodwide.ai"

  # Upload data
  DATASET_ID=$(curl -s -X POST "$BASE_URL/datasets" \
    -H "Authorization: Bearer $WOODWIDE_API_KEY" \
    -F "file=@survey_responses.csv" \
    -F "dataset_name=survey_data" | jq -r '.dataset.id')

  # Train a factor analysis model
  MODEL_ID=$(curl -s -X POST "$BASE_URL/models/train" \
    -H "Authorization: Bearer $WOODWIDE_API_KEY" \
    -H "Content-Type: application/json" \
    -d "{
      \"model_name\": \"survey_factors\",
      \"model_type\": \"factors\",
      \"dataset_id\": \"$DATASET_ID\"
    }" | jq -r '.model.id')

  # Wait for training
  while true; do
    STATUS=$(curl -s "$BASE_URL/models/$MODEL_ID" \
      -H "Authorization: Bearer $WOODWIDE_API_KEY" | jq -r '.status')

    if [ "$STATUS" = "ready" ]; then break; fi
    if [ "$STATUS" = "failed" ]; then echo "Training failed."; exit 1; fi

    sleep 5
  done
  ```
</CodeGroup>

## Inference

Run inference to discover factors in a dataset. The factors and their descriptions are generated based on the *inference dataset*, using the representation learned during training. This means you can run factor analysis on different datasets to understand their structure through the lens of the trained model.

Running inference on the training data itself is the most common use case -- it tells you what latent factors explain your training data. Running on new data reveals how those factors manifest in a different dataset.

The number of factors is automatically determined to capture at least 90% of the variance (up to 10 factors).

<CodeGroup>
  ```python Python theme={null}
  with open("survey_responses.csv", "rb") as f:
      resp = requests.post(
          f"{base_url}/models/{model_id}/infer",
          headers=headers,
          files={"file": ("survey_responses.csv", f, "text/csv")},
          data={"output_type": "json"},
      )

  results = resp.json()["data"]
  print(results)
  ```

  ```python Python (SDK) theme={null}
  # Uses client and model_id from the training example above.
  from pathlib import Path

  result = client.models.infer(
      model_id,
      file=Path("survey_responses.csv"),
      output_type="json",
  )
  print(result["data"])
  ```

  ```javascript JavaScript theme={null}
  const form = new FormData();
  form.append("file", fs.createReadStream("survey_responses.csv"), "survey_responses.csv");
  form.append("output_type", "json");

  const resp = await fetch(`${baseUrl}/models/${modelId}/infer`, {
    method: "POST",
    headers: { ...headers, ...form.getHeaders() },
    body: form,
  });

  const { data: results } = await resp.json();
  console.log(results);
  ```

  ```bash curl theme={null}
  curl -s -X POST "$BASE_URL/models/$MODEL_ID/infer" \
    -H "Authorization: Bearer $WOODWIDE_API_KEY" \
    -F "file=@survey_responses.csv" \
    -F "output_type=json" | jq '.data'
  ```
</CodeGroup>

See [Output Formats](/api-reference/inference#factor-analysis) for the full output schema.
