You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何用ggplot修正/高亮时间序列图中非代表性峰值

Got it, let's work through this problem. You're dealing with a date (2020-09-10) that has way too few samples, making its mean score look like an unrepresentative peak—and you don't want to delete the real data. Below are two practical, ggplot2-based approaches to highlight this problematic point and make your trend clearer:


Approach 1: Highlight the Low-Sample Date + Add a "Normalized" Trend Line

This method keeps your original mean/confidence interval data, but visually flags the low-sample date and adds a trend line that ignores the outlier to show the expected pattern.

First, we'll add a flag to our emmeans data to identify the low-sample date, then adjust the plot to style it differently and add a smoothed trend excluding that point:

# First, calculate sample sizes per date to identify the low-n date
sample_sizes <- actual_df %>% 
  count(date) %>% 
  mutate(is_low_n = n < 50) # Adjust threshold based on your typical sample sizes

# Merge sample size flag with emmeans data
emmeans_with_flags <- emmeans_fit_data %>% 
  as_tibble() %>% 
  left_join(sample_sizes, by = "date")

# Plot with highlights and normalized trend
emmeans_with_flags %>% 
  ggplot(aes(x = date, y = emmean)) +
  # Add a smoothed trend line that skips the low-sample date
  geom_smooth(data = . %>% filter(!is_low_n), 
              method = "lm", se = FALSE, color = "#999999", linetype = "dashed", lwd = 0.8) +
  # Original line (breaks at the low-sample date to show raw trend)
  geom_line(color = "#1a476f", group = 1, lwd = 1) +
  # Error bars - style low-sample one differently
  geom_errorbar(aes(ymin = lower.CL, ymax = upper.CL, color = is_low_n), 
                alpha = 0.5, width = 0.2) +
  # Points - make low-sample one stand out with a white fill
  geom_point(aes(fill = is_low_n), size = 4, shape = 21, color = "black") +
  # Text labels for all daily means
  geom_text(aes(label = paste0(round(100*emmean, 1), "%")), 
            vjust = -4, hjust = 0.5, size = 3.5) +
  # Add an annotation explaining the low-sample date
  annotate("text", x = as.Date("2020-09-10"), y = max(emmeans_with_flags$upper.CL) + 0.05,
           label = "Low sample size (n=15)", color = "#90353b", size = 3) +
  # Scale adjustments
  scale_y_continuous(labels = function(x) paste0(100*x, "%"), limits = c(0, 1)) +
  scale_color_manual(values = c("#1a476f", "#90353b")) +
  scale_fill_manual(values = c("#1a476f", "#ffffff")) +
  # Theme tweaks
  labs(title = "Mood Over Time", y = "Level of Happiness") +
  theme(plot.title = element_text(hjust = 0.5, size = 14),
        axis.text.x = element_text(angle = -60, hjust = 0),
        axis.title.x = element_blank(),
        legend.position = "none")

What this does:

  • The dashed gray line shows the expected trend if we exclude the low-sample date, so viewers can easily compare the outlier to the typical pattern
  • The low-sample date's error bar and point are styled with a red color and white fill to immediately draw attention
  • We add a direct annotation explaining why this point is unusual
  • All original data is retained—no deletion required

Approach 2: Use Smoothing Models to Reduce Outlier Impact

If you want a single, cohesive trend line that naturally downweights the low-sample point (instead of splitting the line), you can use a local regression (loess) or generalized additive model (GAM) directly on your raw data, while still overlaying the daily means.

Here's how to do it with geom_smooth():

# First, convert date to numeric for smoother fitting (optional but helps with loess)
actual_df <- actual_df %>% 
  mutate(date_num = as.numeric(date))

# Plot raw-based smooth + daily means
actual_df %>% 
  ggplot(aes(x = date, y = score)) +
  # Add a loess smooth (automatically downweights low-n points since they contribute less to the local fit)
  geom_smooth(method = "loess", se = TRUE, color = "#1a476f", fill = "#1a476f", alpha = 0.2) +
  # Overlay the daily means from emmeans
  geom_point(data = emmeans_with_flags, aes(y = emmean), size = 3, color = "#90353b") +
  geom_errorbar(data = emmeans_with_flags, aes(y = emmean, ymin = lower.CL, ymax = upper.CL),
                color = "#90353b", width = 0.2, alpha = 0.5) +
  # Label the low-sample point with its sample size for clarity
  geom_text(data = emmeans_with_flags %>% filter(is_low_n), 
            aes(y = emmean, label = paste0(round(100*emmean, 1), "% (n=15)")),
            vjust = -2, hjust = 0.5, color = "#90353b", size = 3.5) +
  # Scale adjustments
  scale_y_continuous(labels = function(x) paste0(100*x, "%")) +
  # Theme tweaks
  labs(title = "Mood Over Time (Smoothed Trend)", y = "Level of Happiness") +
  theme(plot.title = element_text(hjust = 0.5, size = 14),
        axis.text.x = element_text(angle = -60, hjust = 0),
        axis.title.x = element_blank())

What this does:

  • The loess smooth line uses all raw data but gives less weight to the date with only 15 samples, so the overall trend isn't distorted by the outlier
  • We still show the actual daily means and confidence intervals, so viewers can see the raw summary stats alongside the smoothed trend
  • We directly label the low-sample point with its sample size to contextualize the unusual value

Both approaches keep your real data intact while making the visualization far more interpretable. You can tweak colors, sample size thresholds, and smoothing methods to match your specific needs!

内容的提问来源于stack exchange,提问作者Emman

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.09 10:38:10