Thêm phương trình đường hồi quy và R ^ 2 trên biểu đồ


228

Tôi tự hỏi làm thế nào để thêm phương trình đường hồi quy và R ^ 2 trên ggplot . Mã của tôi là:

library(ggplot2)

df <- data.frame(x = c(1:100))
df$y <- 2 + 3 * df$x + rnorm(100, sd = 40)
p <- ggplot(data = df, aes(x = x, y = y)) +
            geom_smooth(method = "lm", se=FALSE, color="black", formula = y ~ x) +
            geom_point()
p

Bất kỳ trợ giúp sẽ được đánh giá cao.


1
Đối với đồ họa mạng , xem latticeExtra::lmlineq().
Josh O'Brien

Câu trả lời:


234

Đây là một giải pháp

# GET EQUATION AND R-SQUARED AS STRING
# SOURCE: https://groups.google.com/forum/#!topic/ggplot2/1TgH-kG5XMA

lm_eqn <- function(df){
    m <- lm(y ~ x, df);
    eq <- substitute(italic(y) == a + b %.% italic(x)*","~~italic(r)^2~"="~r2, 
         list(a = format(unname(coef(m)[1]), digits = 2),
              b = format(unname(coef(m)[2]), digits = 2),
             r2 = format(summary(m)$r.squared, digits = 3)))
    as.character(as.expression(eq));
}

p1 <- p + geom_text(x = 25, y = 300, label = lm_eqn(df), parse = TRUE)

BIÊN TẬP. Tôi đã tìm ra nguồn từ nơi tôi chọn mã này. Đây là liên kết đến bài viết gốc trong các nhóm google ggplot2

Đầu ra


1
Nhận xét của @ JonasRaedle về việc có được các văn bản tìm kiếm tốt hơn với annotatechính xác trên máy của tôi.
IRTFM

2
Điều này trông không giống với đầu ra được đăng trên máy của tôi, nơi nhãn được ghi đè nhiều lần khi dữ liệu được gọi, dẫn đến văn bản nhãn dày và mờ. Truyền nhãn cho data.frame hoạt động đầu tiên (xem đề xuất của tôi trong một nhận xét bên dưới.
PatrickT

@PatrickT: loại bỏ aes(và tương ứng ). aeslà để ánh xạ các biến dataframe thành các biến trực quan - điều đó không cần thiết ở đây, vì chỉ có một thể hiện, vì vậy bạn có thể đặt tất cả trong geom_textcuộc gọi chính . Tôi sẽ chỉnh sửa điều này để trả lời.
nè 101

Vấn đề với giải pháp này dường như là, nếu tập dữ liệu lớn hơn (của tôi là 370000 quan sát) thì chức năng dường như không thành công. Tôi muốn giới thiệu giải pháp từ @kdauria, công việc tương tự, nhưng nhanh hơn nhiều.
Benjamin

3
cho những ai muốn giá trị r và p thay vì R2 và phương trình: eq <- thay thế (italic (r) ~ "=" ~ rvalue * "," ~ italic (p) ~ "=" ~ pvalue, list (rvalue = sprintf ("% .2f", ký (coef (m) [2]) * sqrt (tóm tắt (m) $ r.squared)), pvalue = format (tóm tắt (m) $ hệ số [2,4], chữ số = 2 )))
Jerry T

135

Tôi đã bao gồm một số liệu thống kê stat_poly_eq()trong gói ggpmisccho phép câu trả lời này:

library(ggplot2)
library(ggpmisc)
df <- data.frame(x = c(1:100))
df$y <- 2 + 3 * df$x + rnorm(100, sd = 40)
my.formula <- y ~ x
p <- ggplot(data = df, aes(x = x, y = y)) +
   geom_smooth(method = "lm", se=FALSE, color="black", formula = my.formula) +
   stat_poly_eq(formula = my.formula, 
                aes(label = paste(..eq.label.., ..rr.label.., sep = "~~~")), 
                parse = TRUE) +         
   geom_point()
p

nhập mô tả hình ảnh ở đây

Thống kê này hoạt động với bất kỳ đa thức nào không thiếu các thuật ngữ và hy vọng có đủ tính linh hoạt để nói chung là hữu ích. Có thể sử dụng nhãn R ^ 2 hoặc R ^ 2 đã điều chỉnh với bất kỳ công thức mô hình nào được trang bị lm (). Là một thống kê ggplot, nó hoạt động như mong đợi cả với các nhóm và các khía cạnh.

Gói 'ggpmisc' có sẵn thông qua CRAN.

Phiên bản 0.2.6 vừa được chấp nhận cho CRAN.

Nó giải quyết các bình luận của @shabbychef và @ MYaseen208.

@ MYaseen208 điều này cho thấy làm thế nào để thêm một chiếc mũ .

library(ggplot2)
library(ggpmisc)
df <- data.frame(x = c(1:100))
df$y <- 2 + 3 * df$x + rnorm(100, sd = 40)
my.formula <- y ~ x
p <- ggplot(data = df, aes(x = x, y = y)) +
   geom_smooth(method = "lm", se=FALSE, color="black", formula = my.formula) +
   stat_poly_eq(formula = my.formula,
                eq.with.lhs = "italic(hat(y))~`=`~",
                aes(label = paste(..eq.label.., ..rr.label.., sep = "~~~")), 
                parse = TRUE) +         
   geom_point()
p

nhập mô tả hình ảnh ở đây

@shabbychef Bây giờ có thể khớp các biến trong phương trình với các biến được sử dụng cho nhãn trục. Để thay thế x bằng say zy bằng h người ta sẽ sử dụng:

p <- ggplot(data = df, aes(x = x, y = y)) +
   geom_smooth(method = "lm", se=FALSE, color="black", formula = my.formula) +
   stat_poly_eq(formula = my.formula,
                eq.with.lhs = "italic(h)~`=`~",
                eq.x.rhs = "~italic(z)",
                aes(label = ..eq.label..), 
                parse = TRUE) + 
   labs(x = expression(italic(z)), y = expression(italic(h))) +          
   geom_point()
p

nhập mô tả hình ảnh ở đây

Là những biểu thức phân tách R bình thường, các chữ cái Hy Lạp bây giờ cũng có thể được sử dụng cả trong lhs và rhs của phương trình.

[2017-03-08] @elarry Chỉnh sửa để giải quyết chính xác hơn câu hỏi ban đầu, cho biết cách thêm dấu phẩy giữa phương trình và nhãn R2.

p <- ggplot(data = df, aes(x = x, y = y)) +
  geom_smooth(method = "lm", se=FALSE, color="black", formula = my.formula) +
  stat_poly_eq(formula = my.formula,
               eq.with.lhs = "italic(hat(y))~`=`~",
               aes(label = paste(..eq.label.., ..rr.label.., sep = "*plain(\",\")~")), 
               parse = TRUE) +         
  geom_point()
p

nhập mô tả hình ảnh ở đây

[2019-10-20] @ helen.h Tôi đưa ra các ví dụ dưới đây về việc sử dụng stat_poly_eq()với nhóm.

library(ggpmisc)
df <- data.frame(x = c(1:100))
df$y <- 20 * c(0, 1) + 3 * df$x + rnorm(100, sd = 40)
df$group <- factor(rep(c("A", "B"), 50))
my.formula <- y ~ x
p <- ggplot(data = df, aes(x = x, y = y, colour = group)) +
  geom_smooth(method = "lm", se=FALSE, formula = my.formula) +
  stat_poly_eq(formula = my.formula, 
               aes(label = paste(..eq.label.., ..rr.label.., sep = "~~~")), 
               parse = TRUE) +         
  geom_point()
p

p <- ggplot(data = df, aes(x = x, y = y, linetype = group)) +
  geom_smooth(method = "lm", se=FALSE, formula = my.formula) +
  stat_poly_eq(formula = my.formula, 
               aes(label = paste(..eq.label.., ..rr.label.., sep = "~~~")), 
               parse = TRUE) +         
  geom_point()
p

nhập mô tả hình ảnh ở đây

nhập mô tả hình ảnh ở đây

[2020-01-21] @Herman Có thể hơi phản trực giác ngay từ cái nhìn đầu tiên, nhưng để có được một phương trình duy nhất khi sử dụng nhóm, người ta cần tuân theo ngữ pháp của đồ họa. Hoặc hạn chế ánh xạ tạo nhóm thành các lớp riêng lẻ (hiển thị bên dưới) hoặc giữ ánh xạ mặc định và ghi đè lên nó bằng một giá trị không đổi trong lớp mà bạn không muốn nhóm (ví dụ:colour = "black" ).

Tiếp tục từ ví dụ trước.

p <- ggplot(data = df, aes(x = x, y = y)) +
  geom_smooth(method = "lm", se=FALSE, formula = my.formula) +
  stat_poly_eq(formula = my.formula, 
               aes(label = paste(..eq.label.., ..rr.label.., sep = "~~~")), 
               parse = TRUE) +         
  geom_point(aes(colour = group))
p

nhập mô tả hình ảnh ở đây

[2020-01-22] Để hoàn thiện một ví dụ với các khía cạnh, chứng tỏ rằng trong trường hợp này, những kỳ vọng về ngữ pháp của đồ họa được đáp ứng.

library(ggpmisc)
df <- data.frame(x = c(1:100))
df$y <- 20 * c(0, 1) + 3 * df$x + rnorm(100, sd = 40)
df$group <- factor(rep(c("A", "B"), 50))
my.formula <- y ~ x

p <- ggplot(data = df, aes(x = x, y = y)) +
  geom_smooth(method = "lm", se=FALSE, formula = my.formula) +
  stat_poly_eq(formula = my.formula, 
               aes(label = paste(..eq.label.., ..rr.label.., sep = "~~~")), 
               parse = TRUE) +         
  geom_point() +
  facet_wrap(~group)
p

nhập mô tả hình ảnh ở đây


1
Cần lưu ý rằng xytrong công thức đề cập đến xydữ liệu trong các lớp của âm mưu, và không nhất thiết phải là những người trong phạm vi tại thời điểm đó my.formulađược xây dựng. Vì vậy, công thức nên luôn luôn sử dụng biến x và y?
shabbychef

Điều đó rất đúng xyđề cập đến bất kỳ biến nào được ánh xạ tới các tính thẩm mỹ này. Đó cũng là kỳ vọng cho geom_smooth () và cách ngữ pháp của đồ họa hoạt động. Có thể rõ ràng hơn khi sử dụng các tên khác nhau trong khung dữ liệu nhưng tôi chỉ giữ chúng như trong câu hỏi ban đầu.
Pedro Aphalo 6/2/2016

Sẽ có thể trong phiên bản tiếp theo của ggpmisc. Cám ơn vì sự gợi ý!
Pedro Aphalo

3
Điểm tốt @elarry! Điều này có liên quan đến cách hoạt động của hàm parse () của R. Thông qua thử nghiệm và lỗi tôi thấy rằng aes(label = paste(..eq.label.., ..rr.label.., sep = "*plain(\",\")~"))đó là công việc.
Pedro Aphalo

1
@HermanToothrot Thông thường R2 được ưu tiên cho hồi quy, do đó không có r.label được xác định trước trong dữ liệu được trả về bởi stat_poly_eq(). Bạn cũng có thể sử dụng stat_fit_glance()từ gói 'ggpmisc', trả về R2 dưới dạng giá trị số. Xem các ví dụ trong trang trợ giúp và thay thế stat(r.squared)bằng sqrt(stat(r.squared)).
Pedro Aphalo

99

Tôi đã thay đổi một vài dòng của nguồn stat_smoothvà các hàm liên quan để tạo ra một hàm mới có thêm phương trình phù hợp và giá trị bình phương R. Điều này sẽ làm việc trên các lô khía cạnh quá!

library(devtools)
source_gist("524eade46135f6348140")
df = data.frame(x = c(1:100))
df$y = 2 + 5 * df$x + rnorm(100, sd = 40)
df$class = rep(1:2,50)
ggplot(data = df, aes(x = x, y = y, label=y)) +
  stat_smooth_func(geom="text",method="lm",hjust=0,parse=TRUE) +
  geom_smooth(method="lm",se=FALSE) +
  geom_point() + facet_wrap(~class)

nhập mô tả hình ảnh ở đây

Tôi đã sử dụng mã trong câu trả lời của @ Ramnath để định dạng phương trình. Các stat_smooth_funcchức năng không phải là rất mạnh mẽ, nhưng nó không phải là khó khăn để chơi đùa với nó.

https://gist.github.com/kdauria/524eade46135f6348140 . Hãy thử cập nhật ggplot2nếu bạn gặp lỗi.


2
Cảm ơn nhiều. Điều này không chỉ làm việc cho các khía cạnh, mà ngay cả đối với các nhóm. Tôi thấy nó rất hữu ích cho các hồi quy từng phần, ví dụ stat_smooth_func(mapping=aes(group=cut(x.val,c(-70,-20,0,20,50,130))),geom="text",method="lm",hjust=0,parse=TRUE), kết hợp với EvaluSmooths từ stackoverflow.com/questions/19735149/ Lỗi
Julian

1
@aelwan, thay đổi những dòng này: gist.github.com/kdauria/ Khăn tùy thích. Sau đó, sourcetoàn bộ tập tin trong kịch bản của bạn.
kdauria

1
@kdauria Điều gì xảy ra nếu tôi có một vài phương trình trong mỗi facet_wraps và tôi có y_values ​​khác nhau trong mỗi facet_wrap. Bất kỳ đề xuất làm thế nào để sửa chữa các vị trí của phương trình? Tôi đã thử nhiều tùy chọn của hjust, vjust và góc sử dụng ví dụ này dropbox.com/s/9lk9lug2nwgno2l/R2_facet_wrap.docx?dl=0 nhưng tôi không thể mang tất cả các phương trình cùng cấp trong mỗi facet_wrap
sáng bóng

3
@aelwan, vị trí của phương trình được xác định bởi các dòng này: gist.github.com/kdauria/iêu . Tôi đã thực hiện xposyposlập luận về chức năng trong Gist. Vì vậy, nếu bạn muốn tất cả các phương trình trùng nhau, chỉ cần đặt xposypos. Mặt khác, xposyposđược tính toán từ dữ liệu. Nếu bạn muốn một cái gì đó lạ hơn, không quá khó để thêm một số logic bên trong hàm. Ví dụ, có thể bạn có thể viết một hàm để xác định phần nào của biểu đồ có không gian trống nhất và đặt hàm ở đó.
kdauria

6
Tôi gặp phải lỗi với source_gist: Lỗi trong r_files [[which]]: loại đăng ký không hợp lệ 'bao đóng'. Xem bài đăng này để biết giải pháp: stackoverflow.com/questions/38345894/r-source-gist-not- làm việc
Matifou

73

Tôi đã sửa đổi bài đăng của Ramnath thành a) tạo ra chung chung hơn để nó chấp nhận mô hình tuyến tính làm tham số thay vì khung dữ liệu và b) hiển thị các phủ định phù hợp hơn.

lm_eqn = function(m) {

  l <- list(a = format(coef(m)[1], digits = 2),
      b = format(abs(coef(m)[2]), digits = 2),
      r2 = format(summary(m)$r.squared, digits = 3));

  if (coef(m)[2] >= 0)  {
    eq <- substitute(italic(y) == a + b %.% italic(x)*","~~italic(r)^2~"="~r2,l)
  } else {
    eq <- substitute(italic(y) == a - b %.% italic(x)*","~~italic(r)^2~"="~r2,l)    
  }

  as.character(as.expression(eq));                 
}

Cách sử dụng sẽ thay đổi thành:

p1 = p + geom_text(aes(x = 25, y = 300, label = lm_eqn(lm(y ~ x, df))), parse = TRUE)

17
Điều này có vẻ tuyệt vời! Nhưng tôi đang vẽ geom_point trên nhiều khía cạnh, trong đó df khác nhau dựa trên biến khía cạnh. Làm thế nào để làm điều đó?
bshor

24
Giải pháp của Jayden hoạt động khá tốt, nhưng kiểu chữ trông rất xấu. Tôi khuyên bạn nên thay đổi cách sử dụng thành: p1 = p + annotate("text", x = 25, y = 300, label = lm_eqn(lm(y ~ x, df)), colour="black", size = 5, parse=TRUE)chỉnh sửa: điều này cũng giải quyết mọi vấn đề bạn có thể gặp phải với các chữ cái hiển thị trong chú giải của bạn.
Jonas Raedle

1
@ Jonas, vì một số lý do tôi nhận được "cannot coerce class "lm" to a data.frame". Sự thay thế này hoạt động: df.labs <- data.frame(x = 25, y = 300, label = lm_eqn(df))p <- p + geom_text(data = df.labs, aes(x = x, y = y, label = label), parse = TRUE)
PatrickT

1
@PatrickT - Đó là thông báo lỗi bạn sẽ nhận được nếu bạn gọi lm_eqn(lm(...))bằng giải pháp của Ramnath. Bạn có thể đã thử cái này sau khi thử cái đó nhưng quên đảm bảo rằng bạn đã xác định lạilm_eqn
Hamy

@PatrickT: bạn có thể làm cho câu trả lời của bạn một câu trả lời riêng biệt không? Tôi sẽ rất vui khi bỏ phiếu cho nó!
JelenaČuklina

11

thực sự yêu giải pháp @Ramnath. Để cho phép sử dụng để tùy chỉnh công thức hồi quy (thay vì cố định là y và x dưới dạng tên biến) và thêm giá trị p vào bản in (như @Jerry T đã nhận xét), đây là mod:

lm_eqn <- function(df, y, x){
    formula = as.formula(sprintf('%s ~ %s', y, x))
    m <- lm(formula, data=df);
    # formating the values into a summary string to print out
    # ~ give some space, but equal size and comma need to be quoted
    eq <- substitute(italic(target) == a + b %.% italic(input)*","~~italic(r)^2~"="~r2*","~~p~"="~italic(pvalue), 
         list(target = y,
              input = x,
              a = format(as.vector(coef(m)[1]), digits = 2), 
              b = format(as.vector(coef(m)[2]), digits = 2), 
             r2 = format(summary(m)$r.squared, digits = 3),
             # getting the pvalue is painful
             pvalue = format(summary(m)$coefficients[2,'Pr(>|t|)'], digits=1)
            )
          )
    as.character(as.expression(eq));                 
}

geom_point() +
  ggrepel::geom_text_repel(label=rownames(mtcars)) +
  geom_text(x=3,y=300,label=lm_eqn(mtcars, 'hp','wt'),color='red',parse=T) +
  geom_smooth(method='lm')

nhập mô tả hình ảnh ở đây Thật không may, điều này không hoạt động với facet_wrap hoặc facet_grid.


Rất gọn gàng, tôi đã tham khảo ở đây . Làm rõ - mã của bạn có bị thiếu ggplot(mtcars, aes(x = wt, y = mpg, group=cyl))+trước geom_point () không? Một câu hỏi liên quan đến bán - nếu chúng ta đề cập đến hpwt trong aes()ggplot, thì chúng ta có thể lấy chúng để sử dụng trong cuộc gọi tới lm_eqn, vậy thì chúng ta chỉ phải viết mã ở một nơi? Tôi biết rằng chúng tôi có thể thiết lập xvar = "hp"trước cuộc gọi ggplot () và sử dụng xvar ở cả hai vị trí để thay thế hp , nhưng điều này cảm thấy như nó không cần thiết.
Mark Neal

9

Sử dụng ggpubr :

library(ggpubr)

# reproducible data
set.seed(1)
df <- data.frame(x = c(1:100))
df$y <- 2 + 3 * df$x + rnorm(100, sd = 40)

# By default showing Pearson R
ggscatter(df, x = "x", y = "y", add = "reg.line") +
  stat_cor(label.y = 300) +
  stat_regline_equation(label.y = 280)

nhập mô tả hình ảnh ở đây

# Use R2 instead of R
ggscatter(df, x = "x", y = "y", add = "reg.line") +
  stat_cor(label.y = 300, 
           aes(label = paste(..rr.label.., ..p.label.., sep = "~`,`~"))) +
  stat_regline_equation(label.y = 280)

## compare R2 with accepted answer
# m <- lm(y ~ x, df)
# round(summary(m)$r.squared, 2)
# [1] 0.85

nhập mô tả hình ảnh ở đây


Bạn đã thấy một cách lập trình gọn gàng để chỉ định một số cho label.y?
Mark Neal

@MarkNeal có thể lấy tối đa của y sau đó nhân với 0,8. label.y = max(df$y) * 0.8
zx8754

1
@MarkNeal điểm tốt, có thể gửi vấn đề theo yêu cầu tính năng tại GitHub ggpubr.
zx8754

1
Vấn đề về vị trí tự động được gửi tại đây
Mark Neal

1
@ zx8754, trong cốt truyện của bạn, nó được hiển thị rho chứ không phải R², có cách nào dễ dàng để hiển thị R² không?
matmar

5

Đây là mã đơn giản nhất cho mọi người

Lưu ý: Hiển thị Rho của Pearson chứ không phải R ^ 2.

library(ggplot2)
library(ggpubr)

df <- data.frame(x = c(1:100)
df$y <- 2 + 3 * df$x + rnorm(100, sd = 40)
p <- ggplot(data = df, aes(x = x, y = y)) +
        geom_smooth(method = "lm", se=FALSE, color="black", formula = y ~ x) +
        geom_point()+
        stat_cor(label.y = 35)+ #this means at 35th unit in the y axis, the r squared and p value will be shown
        stat_regline_equation(label.y = 30) #this means at 30th unit regresion line equation will be shown

p

Một ví dụ như vậy với tập dữ liệu của riêng tôi


Vấn đề tương tự như trên, trong cốt truyện của bạn, nó được hiển thị rho chứ không phải R²!
matmar

3

Lấy cảm hứng từ kiểu phương trình được cung cấp trong câu trả lời này , một cách tiếp cận chung hơn (nhiều hơn một yếu tố dự đoán + đầu ra latex làm tùy chọn) có thể là:

print_equation= function(model, latex= FALSE, ...){
    dots <- list(...)
    cc= model$coefficients
    var_sign= as.character(sign(cc[-1]))%>%gsub("1","",.)%>%gsub("-"," - ",.)
    var_sign[var_sign==""]= ' + '

    f_args_abs= f_args= dots
    f_args$x= cc
    f_args_abs$x= abs(cc)
    cc_= do.call(format, args= f_args)
    cc_abs= do.call(format, args= f_args_abs)
    pred_vars=
        cc_abs%>%
        paste(., x_vars, sep= star)%>%
        paste(var_sign,.)%>%paste(., collapse= "")

    if(latex){
        star= " \\cdot "
        y_var= strsplit(as.character(model$call$formula), "~")[[2]]%>%
            paste0("\\hat{",.,"_{i}}")
        x_vars= names(cc_)[-1]%>%paste0(.,"_{i}")
    }else{
        star= " * "
        y_var= strsplit(as.character(model$call$formula), "~")[[2]]        
        x_vars= names(cc_)[-1]
    }

    equ= paste(y_var,"=",cc_[1],pred_vars)
    if(latex){
        equ= paste0(equ," + \\hat{\\varepsilon_{i}} \\quad where \\quad \\varepsilon \\sim \\mathcal{N}(0,",
                    summary(MetamodelKdifEryth)$sigma,")")%>%paste0("$",.,"$")
    }
    cat(equ)
}

Các model số mong đợi một lmđối tượng, latexđối số là một boolean để yêu cầu một ký tự đơn giản hoặc một phương trình có dạng latex và ...đối số chuyển các giá trị của nó cho formathàm.

Tôi cũng đã thêm một tùy chọn để xuất nó dưới dạng latex để bạn có thể sử dụng chức năng này trong một lần đánh dấu như thế này:


```{r echo=FALSE, results='asis'}
print_equation(model = lm_mod, latex = TRUE)
```

Bây giờ sử dụng nó:

df <- data.frame(x = c(1:100))
df$y <- 2 + 3 * df$x + rnorm(100, sd = 40)
df$z <- 8 + 3 * df$x + rnorm(100, sd = 40)
lm_mod= lm(y~x+z, data = df)

print_equation(model = lm_mod, latex = FALSE)

Mã này mang lại: y = 11.3382963933174 + 2.5893419 * x + 0.1002227 * z

Và nếu chúng ta yêu cầu một phương trình latex, làm tròn các tham số thành 3 chữ số:

print_equation(model = lm_mod, latex = TRUE, digits= 3)

Sản lượng này: phương trình latex


0

Tôi có một nghi ngờ, làm thế nào để đưa một số liệu thống kê đáng chú ý của t.test cho bheta vào phương trình, sử dụng ggpmisc::stat_poly_eq() ?

Ví dụ: expression(hat(Y)== 0000*"**"+0000*"x"*"*"-0000*"x"^2*"**"~~~~"R"^2*":"~~0.000)

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.