Standard Data Profile
Executive Summary

Executive Summary

Profile of 12 columns and 48,895 rows

Rows
48895
Columns Profiled
12
Numeric Columns
9
Categorical Columns
3
Date Columns
0
Columns Flagged
7
Duplicate Rows
0
Overall Missing %
1.7
This dataset has 48,895 rows and 12 profiled columns (9 numeric, 3 categorical, 0 date). 5 columns are clean and 7 were flagged for review. Overall 1.7% of cells are missing; No duplicate rows were found. The strongest numeric relationship is between reviews per month and number of reviews, which move together (r = 0.55).
What this means

This export contains 48,895 listings with 12 columns (9 numeric, 3 categorical) and no duplicate rows. 7 of 12 columns are flagged for review—primarily extreme skew in price, minimum nights, and review counts, plus 20.6% missingness in reviews per month. The strongest numeric relationship is between reviews per month and number of reviews (r = 0.55), indicating they move together. Data quality is acceptable for profiling, though skewed columns will need transformation before statistical modeling.

Overview

Analysis Overview

Column-by-column profile of 12 columns and 48,895 rows.

N Rows48895
N Columns12
N Numeric9
N Flagged7
What this means

This dataset contains 48,895 Airbnb listings across 12 columns: 9 numeric, 3 categorical, and 0 date columns. The type mix is typical for property marketplaces—location (latitude, longitude), host identifier, availability, and review metrics alongside categorical attributes like neighbourhood and room type. Overall 1.7% of all cells are missing, concentrated in one column. Type detection completed successfully, enabling downstream analysis to use appropriate statistics: spread and outliers for numeric columns, distinct values and mode for categories. This profile is the foundation for spotting structural problems before modeling or aggregation.

Data Table

Column-by-Column Summary

Detected type, missingness, and a headline note for every column.

ColumnTypeMissing PCTDistinct Or MeanNote
neighbourhood groupcategorical05 distinctClean
neighbourhoodcategorical0221 distinctClean
room typecategorical03 distinctClean
pricenumeric0mean 152.7Extreme skew
minimum nightsnumeric0mean 7.03Extreme skew
number of reviewsnumeric0mean 23.27Extreme skew
reviews per monthnumeric20.6mean 1.373High missingness
calculated host listings countnumeric0mean 7.144Extreme skew
availability 365numeric0mean 112.8Clean
latitudenumeric0mean 40.73Near-zero variance
longitudenumeric0mean -73.95Near-zero variance
host idnumeric0mean 67,620,000Clean
What this means

The short answer

All 12 columns are populated; 5 pass quality checks and 7 require attention. The categorical columns (neighbourhood group, neighbourhood, room type) are complete with 5, 221, and 3 distinct values respectively. Numeric columns show two main issues: four columns exhibit extreme skew (price, minimum nights, number of reviews, calculated host listings count), and reviews per month carries 20.6% missingness.

The detail

Neighbourhood holds 221 distinct values across 5 neighbourhood groups. Room type has 3 categories. Price averages 152.7 with extreme skew; minimum nights averages 7.03 with skew 21.83; number of reviews averages 23.27; calculated host listings count averages 7.144. Reviews per month (mean 1.373) has 20.6% missing. Availability 365 (mean 112.8) is clean. Latitude (mean 40.73) and longitude (mean −73.95) show near-zero variance typical of geographic clustering. Host ID (mean 67,620,000) is clean.

What this can't tell you

Near-zero variance in latitude and longitude reflects the geographic bounds of a single city, not measurement error. This limits the usefulness of these columns for predictive models that require geographic spread. Consider whether location-based features (neighbourhood, neighbourhood group) are sufficient for your analysis, or whether a multi-city export would be needed.

Visualization

Missing Data by Column

Percentage of missing values in each profiled column.

What this means

The short answer

Only one column has missing data: reviews per month at 20.6%. All other 11 columns are complete. The 20.6% threshold is significant because it can bias any statistic or model trained on that column.

The detail

Reviews per month is the only column with gaps, at 20.6% missing. All remaining columns—neighbourhood group, neighbourhood, room type, price, minimum nights, number of reviews, calculated host listings count, availability 365, latitude, longitude, and host ID—have 0% missing values. The 20.6% figure represents roughly 10,000 listings without a reviews per month value.

What this can't tell you

The reason for reviews per month missingness is not evident from this export. It may reflect newly listed properties, inactive listings, or a data collection lag. To understand whether missingness is random or systematic (e.g., correlated with price or neighbourhood), consider a cross-tabulation of reviews per month missingness against room type and neighbourhood group.

Data Table

Numeric Distributions

Centre, spread, skew, and outlier counts for numeric columns.

ColumnMeanMedianSDMinMaxSkewOutliers
price152.7106240.2010,00019.122972
minimum nights7.03320.5111,25021.836607
number of reviews23.27544.5506293.696021
reviews per month1.3730.721.680.0158.53.131793
calculated host listings count7.144132.9513277.937081
availability 365112.845131.603650.760
latitude40.7340.720.0545340.540.910.24425
longitude-73.95-73.960.04616-74.24-73.711.282833
host id67,620,00030,793,81678,610,0002,438274,321,3131.211526
What this means

Five numeric columns exhibit extreme skew, with means and medians diverging sharply. Price ranges from 0 to 10,000 (mean 152.7, median 106, skew 19.12) with 2,972 outliers. Minimum nights ranges 1–1,250 (mean 7.03, median 3, skew 21.83) with 6,607 outliers—the most skewed column. Number of reviews ranges 0–629 (mean 23.27, median 5, skew 3.69) with 6,021 outliers. Calculated host listings count ranges 1–327 (mean 7.144, median 1, skew 7.93) with 7,081 outliers. Reviews per month ranges 0.01–58.5 (skew 3.13) with 1,793 outliers. Availability 365 is least skewed (skew 0.76, no outliers); latitude and longitude have minimal variance by design.

Data Table

Data Quality Flags

Columns with issues that need attention before analysis.

ColumnIssueDetail
priceExtreme skewStrongly skewed to the right (a long high-value tail) (skew 19.12) — the mean is pulled away from typical values.
minimum nightsExtreme skewStrongly skewed to the right (a long high-value tail) (skew 21.83) — the mean is pulled away from typical values.
number of reviewsExtreme skewStrongly skewed to the right (a long high-value tail) (skew 3.69) — the mean is pulled away from typical values.
reviews per monthHigh missingness20.6% of values are missing (10,052 of 48,895 rows).
reviews per monthExtreme skewStrongly skewed to the right (a long high-value tail) (skew 3.13) — the mean is pulled away from typical values.
calculated host listings countExtreme skewStrongly skewed to the right (a long high-value tail) (skew 7.933) — the mean is pulled away from typical values.
latitudeNear-zero varianceValues barely vary (standard deviation 0.05453 around a mean of 40.73).
longitudeNear-zero varianceValues barely vary (standard deviation 0.04616 around a mean of -73.95).
What this means

Seven columns require attention. Five columns show extreme skew (price, minimum nights, number of reviews, reviews per month, calculated host listings count), where the mean is pulled away from typical values by long high-value tails—these will need log or other transformation before parametric analysis. Reviews per month has 20.6% missingness (10,052 missing rows), biasing any direct statistic. Latitude and longitude have near-zero variance (sd 0.05453 and 0.04616 respectively), reflecting geographic clustering and limiting their use in variance-based models. Address these flags—especially skew and missingness—before using these columns as predictors or outcomes.

Rate this report Was this the answer you needed?
The exact source that produced this report — yours to keep, read, and re-run.
Download PDF
How this was computed method · R source · citation
The code that did it

Data Profile — Understand Any Dataset

The "what's in my data" EDA report. The user maps the columns they want profiled; the tool detects each column's type, computes the right summary statistics for that type, flags data-quality problems, and gives a dataset-level overview plus a peek at the strongest numeric correlation.

Why This Method?

Before any real analysis you need to know your data: how many rows and columns, which columns are numeric vs categorical vs dates, where values are missing, which columns are constant or identifier-like, and whether anything is skewed or full of outliers. This is the single most-demanded first step — it turns a raw upload into an understood dataset.

What This Analysis Covers

  • Per-column type detection and summary statistics
  • A missing-data map across every mapped column
  • Numeric distribution statistics (spread, skew, outliers)
  • A data-quality flag list (missingness, constants, ids, skew)

Standard Library

Platform standard-library module (LAT-1441): runs on ANY dataset via the semantic mapping {column_1..column_N}. All narrative is derived from the user's own column names and computed values.

suppressPackageStartupMessages(library(DT))
suppressPackageStartupMessages(library(htmlwidgets))
suppressPackageStartupMessages(library(arrow))
suppressPackageStartupMessages(library(knitr))
suppressPackageStartupMessages(library(rmarkdown))
suppressPackageStartupMessages(library(dplyr))
suppressPackageStartupMessages(library(tidyr))
suppressPackageStartupMessages(library(ggplot2))
suppressPackageStartupMessages(library(stringr))
suppressPackageStartupMessages(library(lubridate))
suppressPackageStartupMessages(library(broom))
suppressPackageStartupMessages(library(Matrix))
suppressPackageStartupMessages(library(cluster))
suppressPackageStartupMessages(library(data.table))

Helpers

Core Analysis Pipeline

compute_shared <- function(df, params, col_map = list()) {
  # === SHARED EXPORTS ===
  #   initial_rows/final_rows/rows_removed  $ row accounting (no rows removed)
  #   n_rows / k_cols                        $ dataset size
  #   profiles          $ list, one entry per column (name/type/stats/flags)
  #   n_numeric/n_categorical/n_date/n_other $ type counts
  #   overall_miss_pct  $ numeric — missing cells / total cells
  #   dup_rows          $ integer — duplicate rows across mapped columns
  #   n_flagged/n_clean $ integer — columns with/without quality issues
  #   column_summary_df $ data.frame(column, type, missing_pct, distinct_or_mean, note)
  #   missingness_df    $ data.frame(column, missing_pct)
  #   numeric_stats_df  $ data.frame(column, mean, median, sd, min, max, skew, outliers)
  #   quality_flags_df  $ data.frame(column, issue, detail)
  #   corr_peek         $ list(a,b,r) or NULL — strongest numeric pair
  #   metrics / json_output
  # === /SHARED EXPORTS ===

Step 1: Discover mapped columns

initial_rows <- nrow(df)
  col_cols <- grep("^column_[0-9]+$", names(df), value = TRUE)
  col_cols <- col_cols[order(as.integer(sub("^column_", "", col_cols)))]
  if (length(col_cols) < 1) {
    stop("Map at least one column to profile(column_1, column_2, ...).")
  }
  n_rows <- initial_rows
  if (n_rows < 5) {
    stop(sprintf("Data Profile needs at least 5 rows to profile; only %d supplied.",
                 n_rows))
  }
  disp_names <- setNames(humanize_semantic(col_cols, col_map), col_cols)

Step 2: Duplicate-row count across the mapped columns

dup_rows <- as.integer(sum(duplicated(df[, col_cols, drop = FALSE])))

Step 3: Profile every column

ID_FRAC <- 0.9          # unique / n_rows above this ⇒ identifier-like
  MISS_HI <- 20           # missing % above this ⇒ high-missingness flag
  SKEW_HI <- 2            # |skew| above this ⇒ extreme-skew flag
  NZV_CV  <- 0.01         # coefficient of variation below this ⇒ near-zero variance

  profiles <- list()
  numeric_vectors <- list()   # disp_name -> full-length numeric vector (NA where not numeric/missing)
  total_missing_cells <- 0

  for (sc in col_cols) {
    disp <- disp_names[[sc]]
    raw <- df[[sc]]
    s <- as.character(raw)
    miss <- is.na(raw) | is.na(s) | trimws(s) == ""
    n_miss <- sum(miss)
    total_missing_cells <- total_missing_cells + n_miss
    miss_pct <- 100 * n_miss / n_rows
    nonmiss <- s[!miss]
    n_nonmiss <- length(nonmiss)
    n_distinct <- length(unique(nonmiss))

    conv <- suppressWarnings(as.numeric(nonmiss))
    num_ok <- n_nonmiss > 0 && sum(!is.na(conv)) >= 0.95 * n_nonmiss
    is_id_card <- n_nonmiss > 0 && n_distinct > ID_FRAC * n_rows

Type detection (order matters): constant → numeric → date → id → categorical

ctype <- "categorical"
    if (n_distinct <= 1) {
      ctype <- "constant"
    } else if (num_ok) {
      numv0 <- conv[!is.na(conv)]
      if (length(numv0) > 0 && all(numv0 == round(numv0)) && is_id_card) {
        ctype <- "id"
      } else {
        ctype <- "numeric"
      }
    } else if (.looks_like_date(nonmiss)) {
      ctype <- "date"
    } else if (is_id_card) {
      ctype <- "id"
    } else {
      ctype <- "categorical"
    }

Per-type statistics

stat <- list(mean = NA_real_, median = NA_real_, sd = NA_real_,
                 min = NA_real_, max = NA_real_, iqr = NA_real_,
                 skew = NA_real_, outliers = NA_integer_,
                 top_level = NA_character_, top_share = NA_real_)
    if (ctype == "numeric") {
      numv <- conv[is.finite(conv)]
      full <- rep(NA_real_, n_rows)
      full[!miss] <- conv
      numeric_vectors[[disp]] <- full
      if (length(numv) >= 1) {
        qs <- suppressWarnings(stats::quantile(numv, c(0.25, 0.75), names = FALSE))
        iqr <- qs[2] - qs[1]
        stat$mean   <- mean(numv)
        stat$median <- stats::median(numv)
        stat$sd     <- if (length(numv) >= 2) stats::sd(numv) else 0
        stat$min    <- min(numv)
        stat$max    <- max(numv)
        stat$iqr    <- iqr
        stat$skew   <- .skewness(numv)
        lo <- qs[1] - 1.5 * iqr; hi <- qs[2] + 1.5 * iqr
        stat$outliers <- as.integer(sum(numv < lo | numv > hi))
      }
    } else if (n_nonmiss > 0) {
      tab <- sort(table(nonmiss), decreasing = TRUE)
      stat$top_level <- names(tab)[1]
      stat$top_share <- 100 * as.numeric(tab[1]) / n_nonmiss
    }

Quality flags for this column

flags <- list()
    if (miss_pct > MISS_HI) {
      flags[[length(flags) + 1]] <- list(
        issue = "High missingness",
        detail = sprintf("%s of values are missing(%s of %s rows).",
                         .fmt_pct(miss_pct), .fmt_num(n_miss), .fmt_num(n_rows)))
    }
    if (ctype == "constant") {
      only_val <- if (n_nonmiss > 0) nonmiss[1] else "(all missing)"
      flags[[length(flags) + 1]] <- list(
        issue = "Constant column",
        detail = sprintf("Only one distinct value(%s) — no information for analysis.",
                         only_val))
    }
    if (ctype == "id") {
      flags[[length(flags) + 1]] <- list(
        issue = "Identifier-like",
        detail = sprintf("%s distinct values across %s rows — looks like a row identifier, not a variable.",
                         .fmt_num(n_distinct), .fmt_num(n_rows)))
    }
    if (ctype == "numeric" && is.finite(stat$sd) && is.finite(stat$mean)) {
      cv <- if (abs(stat$mean) > 1e-9) stat$sd / abs(stat$mean) else if (stat$sd == 0) 0 else Inf
      if (stat$sd == 0 || (is.finite(cv) && cv < NZV_CV)) {
        flags[[length(flags) + 1]] <- list(
          issue = "Near-zero variance",
          detail = sprintf("Values barely vary(standard deviation %s around a mean of %s).",
                           .fmt_num(stat$sd), .fmt_num(stat$mean)))
      }
    }
    if (ctype == "numeric" && is.finite(stat$skew) && abs(stat$skew) > SKEW_HI) {
      dirn <- if (stat$skew > 0) "right(a long high-value tail)" else "left(a long low-value tail)"
      flags[[length(flags) + 1]] <- list(
        issue = "Extreme skew",
        detail = sprintf("Strongly skewed to the %s(skew %s) — the mean is pulled away from typical values.",
                         dirn, .fmt_num(stat$skew)))
    }

A single headline note for the summary table (highest-priority issue)

note <- if (length(flags) > 0) flags[[1]]$issue else "Clean"

distinct-or-mean cell for the summary table

dm <- if (ctype == "numeric") {
      paste0("mean ", .fmt_num(stat$mean))
    } else {
      paste0(.fmt_num(n_distinct), " distinct")
    }

    profiles[[sc]] <- list(
      sem = sc, disp = disp, type = ctype,
      n_missing = n_miss, miss_pct = miss_pct,
      n_distinct = n_distinct, stat = stat,
      flags = flags, note = note, distinct_or_mean = dm)
  }

Step 4: Type counts + overall missingness

types <- vapply(profiles, function(p) p$type, character(1))
  n_numeric     <- sum(types == "numeric")
  n_date        <- sum(types == "date")
  n_categorical <- sum(types %in% c("categorical", "id", "constant"))
  k_cols <- length(profiles)
  overall_miss_pct <- 100 * total_missing_cells / (n_rows * k_cols)

Step 6: Numeric correlation peek (strongest |Pearson| pair)

corr_peek <- NULL
  if (length(numeric_vectors) >= 2) {
    M <- do.call(cbind, numeric_vectors)
    colnames(M) <- names(numeric_vectors)
    Cc <- suppressWarnings(stats::cor(M, use = "pairwise.complete.obs"))
    Cc[!is.finite(Cc)] <- NA
    if (nrow(Cc) >= 2) diag(Cc) <- NA
    if (any(!is.na(Cc))) {
      mx <- max(abs(Cc), na.rm = TRUE)
      hit <- which(abs(Cc) == mx, arr.ind = TRUE)
      if (nrow(hit) >= 1) {
        i <- hit[1, 1]; j <- hit[1, 2]
        corr_peek <- list(a = rownames(Cc)[i], b = colnames(Cc)[j],
                          r = round(Cc[i, j], 3))
      }
    }
  }

Step 7: Metrics + json_output

metrics <- list(
    `Rows`               = n_rows,
    `Columns Profiled`   = k_cols,
    `Numeric Columns`    = as.integer(n_numeric),
    `Categorical Columns`= as.integer(n_categorical),
    `Date Columns`       = as.integer(n_date),
    `Columns Flagged`    = as.integer(n_flagged),
    `Duplicate Rows`     = dup_rows,
    `Overall Missing %`  = round(overall_miss_pct, 1)
  )

  quality_headline <- if (n_flagged == 0) {
    "no columns raised a data-quality flag"
  } else {
    sprintf("%s of %s %s flagged for review",
            .fmt_num(n_flagged), .fmt_num(k_cols),
            .plural(n_flagged, "column was", "columns were"))
  }
  corr_sentence <- if (!is.null(corr_peek)) {
    sprintf(" Strongest numeric relationship: %s and %s(r = %s).",
            corr_peek$a, corr_peek$b, .fmt_num(corr_peek$r))
  } else ""

  json_answer <- paste0(
    "Profiled ", .fmt_num(k_cols), " ", .plural(k_cols, "column", "columns"),
    " across ", .fmt_num(n_rows), " rows: ",
    .fmt_num(n_numeric), " numeric, ", .fmt_num(n_categorical),
    " categorical, ", .fmt_num(n_date), " date. ",
    "Overall ", .fmt_pct(overall_miss_pct), " of cells are missing; ",
    .fmt_num(dup_rows), " duplicate ", .plural(dup_rows, "row", "rows"), ". ",
    toupper(substring(quality_headline, 1, 1)), substring(quality_headline, 2),
    ".", corr_sentence)

  json_output <- list(
    answer = json_answer,
    cards = lapply(
      c("tldr", "overview", "column_summary", "missingness",
        "numeric_distributions", "data_quality_flags"),
      function(cid) list(id = cid, metrics = metrics))
  )

  list(
    initial_rows = initial_rows, final_rows = initial_rows, rows_removed = 0L,
    n_rows = n_rows, k_cols = k_cols,
    profiles = profiles, disp_names = disp_names,
    n_numeric = as.integer(n_numeric), n_categorical = as.integer(n_categorical),
    n_date = as.integer(n_date),
    overall_miss_pct = overall_miss_pct, dup_rows = dup_rows,
    n_flagged = as.integer(n_flagged), n_clean = as.integer(n_clean),
    column_summary_df = column_summary_df, missingness_df = missingness_df,
    numeric_stats_df = numeric_stats_df, quality_flags_df = quality_flags_df,
    corr_peek = corr_peek, metrics = metrics, json_output = json_output
  )
}
Your data has more stories to tell.Run any analysis on your own data — validated R modules, interactive reports, AI insights, and PDF export. 500 free credits on signup.
Try Free — No SignupSign Up Free

Cite this analysis

Report an Issue

Tell us what's wrong. You'll get a free re-run of this analysis so you can try again with different parameters. If the re-run still doesn't meet your expectations, we'll refund your credits.

Want to run this analysis on your own data? Upload CSV — Free Analysis See Pricing