Fügen Sie die Regressionsliniengleichung und R ^ 2 im Diagramm hinzu


227

Ich frage mich, wie man die Regressionsgeradengleichung und R ^ 2 auf die ggplot. Mein Code lautet:

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

Jede Hilfe wird sehr geschätzt.


1
Für Gittergrafiken siehe latticeExtra::lmlineq().
Josh O'Brien

Antworten:


234

Hier ist eine Lösung

# 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)

BEARBEITEN. Ich habe die Quelle herausgefunden, aus der ich diesen Code ausgewählt habe. Hier ist der Link zum ursprünglichen Beitrag in den Google-Gruppen von ggplot2

Ausgabe


1
@ JonasRaedles Kommentar zum Erhalten besser aussehender Texte annotatewar auf meinem Computer korrekt.
IRTFM

2
Dies sieht nicht nach der auf meinem Computer veröffentlichten Ausgabe aus, bei der das Etikett so oft überschrieben wird, wie die Daten aufgerufen werden, was zu einem dicken und verschwommenen Etikettentext führt. Das Übergeben der Beschriftungen an einen data.frame funktioniert zuerst (siehe meinen Vorschlag in einem Kommentar unten.
PatrickT

@PatrickT: entferne das aes(und das entsprechende ). aesdient zum Zuordnen von Datenrahmenvariablen zu visuellen Variablen - dies wird hier nicht benötigt, da es nur eine Instanz gibt, sodass Sie alles in den Hauptaufruf geom_texteinfügen können. Ich werde dies in der Antwort bearbeiten.
naught101

Das Problem bei dieser Lösung scheint zu sein, dass die Funktion fehlschlägt, wenn der Datensatz größer ist (meiner waren 370000 Beobachtungen). Ich würde die Lösung von @kdauria empfehlen, die das gleiche tut, aber viel viel schneller.
Benjamin

3
für diejenigen, die r- und p-Werte anstelle von R2 und Gleichung wollen: Gleichung <- Ersatz (kursiv (r) ~ "=" ~ rWert * "," ~ kursiv (p) ~ "=" ~ pWert, Liste (rWert = sprintf) ("% .2f", Vorzeichen (coef (m) [2]) * sqrt (Zusammenfassung (m) $ r.squared)), pvalue = Format (Zusammenfassung (m) $ Koeffizienten [2,4], Ziffern = 2 )))
Jerry T

135

Ich habe eine Statistik stat_poly_eq()in mein Paket aufgenommen ggpmisc, die diese Antwort ermöglicht:

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

Geben Sie hier die Bildbeschreibung ein

Diese Statistik funktioniert mit jedem Polynom ohne fehlende Begriffe und hat hoffentlich genug Flexibilität, um allgemein nützlich zu sein. Die R ^ 2- oder angepassten R ^ 2-Beschriftungen können mit jeder mit lm () ausgestatteten Modellformel verwendet werden. Als ggplot-Statistik verhält sie sich sowohl bei Gruppen als auch bei Facetten wie erwartet.

Das 'ggpmisc'-Paket ist über CRAN erhältlich.

Version 0.2.6 wurde gerade in CRAN akzeptiert.

Es werden Kommentare von @shabbychef und @ MYaseen208 behandelt.

@ MYaseen208 Dies zeigt, wie man einen Hut hinzufügt .

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

Geben Sie hier die Bildbeschreibung ein

@shabbychef Jetzt ist es möglich, die Variablen in der Gleichung mit denen abzugleichen, die für die Achsenbeschriftungen verwendet werden. Um das x durch z und y durch h zu ersetzen, würde man verwenden:

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

Geben Sie hier die Bildbeschreibung ein

Als diese normalen R-analysierten Ausdrücke können griechische Buchstaben jetzt auch sowohl im lhs als auch im rhs der Gleichung verwendet werden.

[2017-03-08] @elarry Bearbeiten, um die ursprüngliche Frage genauer zu beantworten, und zeigen, wie ein Komma zwischen den Gleichungs- und R2-Bezeichnungen eingefügt wird.

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

Geben Sie hier die Bildbeschreibung ein

[2019-10-20] @ helen.h Ich gebe unten Beispiele für die Verwendung stat_poly_eq()mit Gruppierung.

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

Geben Sie hier die Bildbeschreibung ein

Geben Sie hier die Bildbeschreibung ein

[2020-01-21] @Herman Es mag auf den ersten Blick etwas kontraintuitiv sein, aber um eine einzige Gleichung zu erhalten, wenn man eine Gruppierung verwendet, muss man der Grammatik der Grafiken folgen. Beschränken Sie entweder die Zuordnung, mit der die Gruppierung erstellt wird, auf einzelne Ebenen (siehe unten) oder behalten Sie die Standardzuordnung bei und überschreiben Sie sie mit einem konstanten Wert in der Ebene, in der Sie die Gruppierung nicht möchten (z colour = "black". B. ).

Fortsetzung des vorherigen Beispiels.

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

Geben Sie hier die Bildbeschreibung ein

[2020-01-22] Der Vollständigkeit halber ein Beispiel mit Facetten, das zeigt, dass auch in diesem Fall die Erwartungen an die Grammatik von Grafiken erfüllt sind.

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

Geben Sie hier die Bildbeschreibung ein


1
Es ist zu beachten, dass sich das xund yin der Formel auf die xund yDaten in den Ebenen des Diagramms beziehen und nicht unbedingt auf diejenigen, die zu diesem Zeitpunkt im Umfang erstellt wurden my.formula. Daher sollte die Formel immer x- und y-Variablen verwenden?
Shabbychef

Es ist sehr wahr, dass xund ybeziehen Sie sich auf die Variablen, die dieser Ästhetik zugeordnet sind. Das ist die Erwartung auch für geom_smooth () und wie die Grammatik von Grafiken funktioniert. Es hätte klarer sein können, verschiedene Namen innerhalb des Datenrahmens zu verwenden, aber ich habe sie einfach wie in der ursprünglichen Frage beibehalten.
Pedro Aphalo

Wird in der nächsten Version von möglich sein ggpmisc. Danke für den Vorschlag!
Pedro Aphalo

3
Guter Punkt @elarry! Dies hängt damit zusammen, wie die parse () -Funktion von R funktioniert. Durch Versuch und Irrtum fand ich, dass aes(label = paste(..eq.label.., ..rr.label.., sep = "*plain(\",\")~"))das den Job macht.
Pedro Aphalo

1
@HermanToothrot Normalerweise wird R2 für eine Regression bevorzugt, daher enthält die von zurückgegebene Daten kein vordefiniertes r.label stat_poly_eq(). Sie können stat_fit_glance()auch aus dem Paket 'ggpmisc' verwenden, das R2 als numerischen Wert zurückgibt. Siehe Beispiele auf der Hilfeseite und ersetzen stat(r.squared)durch sqrt(stat(r.squared)).
Pedro Aphalo

99

Ich habe einige Zeilen der Quelle stat_smoothund verwandter Funktionen geändert , um eine neue Funktion zu erstellen, die die Anpassungsgleichung und den R-Quadrat-Wert hinzufügt. Dies funktioniert auch bei Facettenplots!

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)

Geben Sie hier die Bildbeschreibung ein

Ich habe den Code in @ Ramnaths Antwort verwendet, um die Gleichung zu formatieren. Die stat_smooth_funcFunktion ist nicht sehr robust, aber es sollte nicht schwer sein, damit herumzuspielen.

https://gist.github.com/kdauria/524eade46135f6348140 . Versuchen Sie zu aktualisieren, ggplot2wenn Sie eine Fehlermeldung erhalten.


2
Danke vielmals. Dieser funktioniert nicht nur für Facetten, sondern auch für Gruppen. Ich finde es sehr nützlich für stückweise Regressionen, z. B. stat_smooth_func(mapping=aes(group=cut(x.val,c(-70,-20,0,20,50,130))),geom="text",method="lm",hjust=0,parse=TRUE)in Kombination mit EvaluateSmooths von stackoverflow.com/questions/19735149/…
Julian

1
@aelwan, ändern Sie diese Zeilen: gist.github.com/kdauria/… wie Sie möchten . Dann sourcedie gesamte Datei in Ihrem Skript.
Kdauria

1
@kdauria Was ist, wenn ich in jedem von facet_wraps mehrere Gleichungen habe und in jedem von facet_wrap unterschiedliche y_values. Irgendwelche Vorschläge, wie man die Positionen der Gleichungen fixiert? Ich habe versucht , mehrere Möglichkeiten der hjust, VJust und Winkel mit diesem Beispiel dropbox.com/s/9lk9lug2nwgno2l/R2_facet_wrap.docx?dl=0 aber ich konnte nicht in jedem der facet_wrap alle Gleichungen auf dem gleichen Niveau bringen
glänzend

3
@aelwan, die Position der Gleichung wird durch folgende Linien bestimmt: gist.github.com/kdauria/… . Ich habe xposund yposArgumente der Funktion im Kern gemacht. Wenn Sie also möchten, dass sich alle Gleichungen überlappen, setzen Sie einfach xposund ypos. Ansonsten xposund yposwerden aus den Daten berechnet. Wenn Sie etwas ausgefalleneres wollen, sollte es nicht zu schwierig sein, der Funktion eine Logik hinzuzufügen. Zum Beispiel könnten Sie eine Funktion schreiben, um zu bestimmen, welcher Teil des Diagramms den meisten leeren Raum hat, und die Funktion dort platzieren.
Kdauria

6
Bei source_gist ist ein Fehler aufgetreten: Fehler in r_files [[which]]: Ungültiger tiefgestellter Typ 'Schließung'. Siehe diesen Beitrag für die Lösung: stackoverflow.com/questions/38345894/r-source-gist-not-working
Matifou

73

Ich habe Ramnaths Beitrag dahingehend geändert, dass a) generischer gemacht wird, sodass ein lineares Modell als Parameter anstelle des Datenrahmens akzeptiert wird und b) Negative angemessener angezeigt werden.

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));                 
}

Die Verwendung würde sich ändern zu:

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

17
Das sieht gut aus! Aber ich zeichne geom_points auf mehreren Facetten, wobei sich der df basierend auf der Facettenvariablen unterscheidet. Wie mache ich das?
bshor

24
Jaydens Lösung funktioniert ganz gut, aber die Schrift sieht sehr hässlich aus. Ich würde empfehlen, die Verwendung wie folgt zu ändern: p1 = p + annotate("text", x = 25, y = 300, label = lm_eqn(lm(y ~ x, df)), colour="black", size = 5, parse=TRUE)Bearbeiten: Dies behebt auch alle Probleme, die mit Buchstaben in Ihrer Legende auftreten können.
Jonas Raedle

1
@ Jonas, aus irgendeinem Grund bekomme ich "cannot coerce class "lm" to a data.frame". Diese Alternative funktioniert: df.labs <- data.frame(x = 25, y = 300, label = lm_eqn(df))und p <- p + geom_text(data = df.labs, aes(x = x, y = y, label = label), parse = TRUE)
PatrickT

1
@PatrickT - Das ist die Fehlermeldung, die Sie erhalten würden, wenn Sie lm_eqn(lm(...))mit Ramnaths Lösung anrufen würden . Sie haben es wahrscheinlich versucht, nachdem Sie es versucht haben, aber vergessen, sicherzustellen, dass Sie es neu definiert habenlm_eqn
Hamy

@PatrickT: Könnten Sie Ihre Antwort zu einer separaten Antwort machen? Ich würde gerne darüber abstimmen!
JelenaČuklina

11

Ich liebe die @ Ramnath-Lösung wirklich. Um die Anpassung der Regressionsformel zu ermöglichen (anstelle von y und x als Literalvariablennamen) und den p-Wert auch zum Ausdruck hinzuzufügen (wie @Jerry T kommentierte), ist hier der 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')

Geben Sie hier die Bildbeschreibung ein Leider funktioniert dies nicht mit facet_wrap oder facet_grid.


Sehr ordentlich, ich habe hier verwiesen . Eine Klarstellung - fehlt Ihr Code ggplot(mtcars, aes(x = wt, y = mpg, group=cyl))+vor dem geom_point ()? Eine halb verwandte Frage - wenn wir uns beziehen PS und wt in der aes()für ggplot, können wir dann greifen sie in dem Aufruf zu verwenden lm_eqn, so ist, dann haben wir nur an einem Ort zu Code? Ich weiß , wir setzen konnten xvar = "hp"vor dem ggplot () Aufruf und Nutzung xvar an beiden Standorten ersetzen PS , aber das fühlt sich an wie es sollte nicht notwendig sein.
Mark Neal

9

Verwenden von 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)

Geben Sie hier die Bildbeschreibung ein

# 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

Geben Sie hier die Bildbeschreibung ein


Haben Sie eine ordentliche programmatische Möglichkeit gesehen, eine Nummer für anzugeben label.y?
Mark Neal

@MarkNeal erhält vielleicht das Maximum von y und multipliziert dann mit 0,8. label.y = max(df$y) * 0.8
zx8754

1
@ MarkNeal gute Punkte, vielleicht Problem als Feature-Anfrage bei GitHub ggpubr einreichen.
zx8754

1
Problem bei der automatischen Lokalisierung hier
Mark Neal

1
@ zx8754, in Ihrem Plot wird Rho und nicht R² angezeigt. Gibt es eine einfache Möglichkeit, R² anzuzeigen?
März

5

Hier ist der einfachste Code für alle

Hinweis: Pearson's Rho und nicht R ^ 2 anzeigen.

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

Ein solches Beispiel mit meinem eigenen Datensatz


Gleiches Problem wie oben, in Ihrem Plot wird Rho und nicht R² angezeigt!
März

3

Inspiriert von dem in dieser Antwort angegebenen Gleichungsstil kann ein allgemeinerer Ansatz (mehr als ein Prädiktor + Latexausgabe als Option) sein:

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)
}

Das modelArgument erwartet ein lmObjekt, das latexArgument ist ein Boolescher ...Wert, um nach einem einfachen Zeichen oder einer latexformierten Gleichung zu fragen, und das Argument übergibt seine Werte an die formatFunktion.

Ich habe auch eine Option hinzugefügt, um es als Latex auszugeben, damit Sie diese Funktion in einem Rmarkdown wie diesem verwenden können:


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

Jetzt mit:

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)

Dieser Code ergibt: y = 11.3382963933174 + 2.5893419 * x + 0.1002227 * z

Und wenn wir nach einer Latexgleichung fragen, runden wir die Parameter auf 3 Stellen:

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

Dies ergibt: Latexgleichung


0

Ich habe Zweifel, wie man eine signifikante Statistik von t.test für bheta in die Gleichung setzt, indem man verwendet ggpmisc::stat_poly_eq()?

Ex: expression(hat(Y)== 0000*"**"+0000*"x"*"*"-0000*"x"^2*"**"~~~~"R"^2*":"~~0.000)

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.