Executive Summary
Profile of 12 columns and 48,895 rows
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.
Analysis Overview
Column-by-column profile of 12 columns and 48,895 rows.
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.
Column-by-Column Summary
Detected type, missingness, and a headline note for every column.
| Column | Type | Missing PCT | Distinct Or Mean | Note |
|---|---|---|---|---|
| neighbourhood group | categorical | 0 | 5 distinct | Clean |
| neighbourhood | categorical | 0 | 221 distinct | Clean |
| room type | categorical | 0 | 3 distinct | Clean |
| price | numeric | 0 | mean 152.7 | Extreme skew |
| minimum nights | numeric | 0 | mean 7.03 | Extreme skew |
| number of reviews | numeric | 0 | mean 23.27 | Extreme skew |
| reviews per month | numeric | 20.6 | mean 1.373 | High missingness |
| calculated host listings count | numeric | 0 | mean 7.144 | Extreme skew |
| availability 365 | numeric | 0 | mean 112.8 | Clean |
| latitude | numeric | 0 | mean 40.73 | Near-zero variance |
| longitude | numeric | 0 | mean -73.95 | Near-zero variance |
| host id | numeric | 0 | mean 67,620,000 | Clean |
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.
Missing Data by Column
Percentage of missing values in each profiled column.
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.
Numeric Distributions
Centre, spread, skew, and outlier counts for numeric columns.
| Column | Mean | Median | SD | Min | Max | Skew | Outliers |
|---|---|---|---|---|---|---|---|
| price | 152.7 | 106 | 240.2 | 0 | 10,000 | 19.12 | 2972 |
| minimum nights | 7.03 | 3 | 20.51 | 1 | 1,250 | 21.83 | 6607 |
| number of reviews | 23.27 | 5 | 44.55 | 0 | 629 | 3.69 | 6021 |
| reviews per month | 1.373 | 0.72 | 1.68 | 0.01 | 58.5 | 3.13 | 1793 |
| calculated host listings count | 7.144 | 1 | 32.95 | 1 | 327 | 7.93 | 7081 |
| availability 365 | 112.8 | 45 | 131.6 | 0 | 365 | 0.76 | 0 |
| latitude | 40.73 | 40.72 | 0.05453 | 40.5 | 40.91 | 0.24 | 425 |
| longitude | -73.95 | -73.96 | 0.04616 | -74.24 | -73.71 | 1.28 | 2833 |
| host id | 67,620,000 | 30,793,816 | 78,610,000 | 2,438 | 274,321,313 | 1.21 | 1526 |
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 Quality Flags
Columns with issues that need attention before analysis.
| Column | Issue | Detail |
|---|---|---|
| price | Extreme skew | Strongly skewed to the right (a long high-value tail) (skew 19.12) — the mean is pulled away from typical values. |
| minimum nights | Extreme skew | Strongly skewed to the right (a long high-value tail) (skew 21.83) — the mean is pulled away from typical values. |
| number of reviews | Extreme skew | Strongly skewed to the right (a long high-value tail) (skew 3.69) — the mean is pulled away from typical values. |
| reviews per month | High missingness | 20.6% of values are missing (10,052 of 48,895 rows). |
| reviews per month | Extreme skew | Strongly skewed to the right (a long high-value tail) (skew 3.13) — the mean is pulled away from typical values. |
| calculated host listings count | Extreme skew | Strongly skewed to the right (a long high-value tail) (skew 7.933) — the mean is pulled away from typical values. |
| latitude | Near-zero variance | Values barely vary (standard deviation 0.05453 around a mean of 40.73). |
| longitude | Near-zero variance | Values barely vary (standard deviation 0.04616 around a mean of -73.95). |
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.
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_rowsType 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
)
}