Продолжение — соответствие GLM (логистическая регрессия) SQL

Как заявил Томас Грейф в: подгонка GLM (логистическая регрессия) к SQL

Оригинальный вопрос:

Мы часто оцениваем данные в базе данных напрямую для простых моделей, таких как линейная или логистическая регрессия. Всегда немного сложно правильно перевести все коэффициенты из R в SQL. Я подумал, что могу сделать некоторый перевод R в SQL для результата glm. Для числовых переменных это довольно просто:

library(rpart)

fit <- glm(Kyphosis ~ ., data = kyphosis, family = binomial())

coefs <- fit$coef[2:length(fit$coef)]
expr <- paste0('1/(1 + exp(-(',fit$coef[1], '+', paste0('(', 
           coefs, '*', names(coefs), ')', collapse = '+'),')))')

print(expr)

a <- with(kyphosis, eval(parse(text = expr)))
b <- predict(fit, kyphosis, type = 'response')
names(b) <- NULL
all.equal(a, b)

Сгенерированное выражение:

1/(1 + exp(-(-2.03693352129613+      (0.0109304821420485*Age)+   (0.410601186932733*Number)+(-0.206510049753697*Start)))).

Есть ли способ заставить это работать для факторных переменных? Я хотел бы поставить коэффициенты в случае ... когда ... тогда ... в конце предложения. Предположим, у нас есть следующая модель:

kyphosis$factor_variable <- rep(LETTERS[1:5],20)[1:81]
fit <- glm(Kyphosis ~ ., data = kyphosis, family = binomial())

Я просматриваю структуру фита, но не вижу ничего полезного. Единственный вариант для анализа имен (fit $ coef)?

Вот ссылка на лучший ответ на данный момент... https://stackoverflow.com/a/33659431/6497137

Возможное решение

glm_to_sql <- function(glmmodel) {
  xlev <- data.frame(unlist(glmmodel$xlevels))
  xlev$xlevrowname <- rownames(xlev)
  rownames(xlev) <- NULL
  colnames(xlev)[1] <- "xlevel"
  if (nrow(xlev)==0){xlev <- data.frame(xlevrowname=character(0), xlevel=character(0), stringsAsFactors=F)}

  modcoeffs <- data.frame(unlist(glmmodel$coefficients))
  modcoeffs$coeffname <- rownames(modcoeffs)
  rownames(modcoeffs) <- NULL
  colnames(modcoeffs)[1] <- "coeffvalue"

  coeffmatrix <- sqldf("select a.*,b.*,'' as sqlstr, 
                       substr(coeffname,1,instr(coeffname, xlevel)-1) as varname 
                       from modcoeffs a left join xlev b on coeffname like '%' || xlevel and xlevrowname like substr(coeffname,1,instr(coeffname, xlevel)-1) || '%'")

  for (i in 1:nrow(coeffmatrix)) {
    if(coeffmatrix$coeffname[i] == "(Intercept)") 
    {
      coeffmatrix$sqlstr[i] <- coeffmatrix$coeffvalue[i]
    } else if (is.na(coeffmatrix$xlevel[i]) ) {    
      coeffmatrix$sqlstr[i] <- paste("(",coeffmatrix$coeffvalue[i],"*",coeffmatrix$coeffname[i],")")
    } else {
      coeffmatrix$sqlstr[i] <- paste("(case when ",coeffmatrix$varname[i],"='",coeffmatrix$xlevel[i], "' THEN ",coeffmatrix$coeffvalue[i]," ELSE 0 END)",sep="")
    }

    if (i==1){x.sql0 <- coeffmatrix$sqlstr[i]} else {x.sql0 <- paste(x.sql0,"+",coeffmatrix$sqlstr[i])}
  }

  if (glmmodel$family$link == "logit") {
    x.sql <- paste("1/(1 + exp(-(",x.sql0,")))")  
  } else if (glmmodel$family$link == "identity") {
    x.sql <- x.sql0
  }

  return(x.sql)
}

Проблема

Соединение sqldf не идеально:

where varname is null or length(varname) >0 ## additional filter  

Это не избавит вас от всех углов. Если переменная (например, человек) оканчивается на «n», а другая переменная (например, выживший) равна y/n, тогда она вычтет «n» из человека и соединит его со всеми другими переменными y/n.

У кого-нибудь есть потенциальное обходное решение?

РЕДАКТИРОВАТЬ: пример

library(sqldf)
ID <- seq(1,50,  1)

cabin <- as.numeric(as.character((seq(1,25.5,  .5))))

str(cabin)

Defect <-     c(1,0,1,0,0,1,0,1,0,1,0,1,0,0,0,0,1,0,0,1,0,1,0,1,0,1,1,0,0,0,0,0,0,1,0,1,0,1,1,0,0,0,1,0,1,0,0,0,0,0)

Pre_register <- c("Y", "N", "Y", "N", "N", "Y", "N", "N", "Y", "N", "N", "Y", "N", "N", "Y",
             "N", "Y", "N", "N", "Y", "N", "N", "Y", "N", "N", "Y", "N", "N", "Y",
             "Y", "N", "N", "Y", "N", "N", "Y", "N", "N", "Y", "N", "N", "Y", 
             "Y", "N", "N", "Y", "N", "N", "Y", "N")

length(Pre_register)
length(cabin)
length(ID)

x <- data.frame(cbind(ID, cabin, Pre_register, Defect))

x$cabin <- as.numeric(as.character(x$cabin))

str(x)

glm_ex <- glm(Defect ~ cabin + Pre_register ,
           data=x,
           family=binomial(link="logit"))

summary(glm_ex)

И вот результат:

> glm_to_sql(glm_ex)

[1] "1/(1 + exp(-( 0,97216 + (случай, когда FLT_REV_Jan_Sep_2015='Y' THEN Round(-1.95327, 3) ELSE 0 END) + (случай, когда ='N' THEN Round(- 1.93112, 3) ИНАЧЕ 0 КОНЕЦ) )))"

Обратите внимание, что в операторе case есть пробел, равный «N». Эта часть неверна и связана с логикой glm_to_sql.

Это соединение, в котором кабина оканчивается на "n", перепутанную с Y/N. Это гораздо меньший пример.

РЕДАКТИРОВАТЬ2:

Проходим через glm_to_sql:

xlev <- data.frame(unlist(glm_ex$xlevels))

xlev$xlevrowname <- rownames(xlev)

rownames(xlev) <- NULL

colnames(xlev)[1] <- "xlevel"

if (nrow(xlev)==0){xlev <- data.frame(xlevrowname=character(0), xlevel=character(0), stringsAsFactors=F)}

xlev

modcoeffs <- data.frame(unlist(glm_ex$coefficients))

modcoeffs$coeffname <- rownames(modcoeffs)

rownames(modcoeffs) <- NULL

colnames(modcoeffs)[1] <- "coeffvalue"

modcoeffs

Вот где существует проблема:

coeffmatrix <- sqldf("select a.*,b.*,'' as sqlstr, 
                   substr(coeffname,1,instr(coeffname, xlevel)-1) as varname 
                 from modcoeffs a left join xlev b on coeffname like '%' || xlevel and xlevrowname like substr(coeffname,1,instr(coeffname, xlevel)-1) || '%'")

Выход:

   coeffvalue     coeffname xlevel   xlevrowname sqlstr      varname
1 -0.51243845   (Intercept)   <NA>          <NA>                <NA>
2 -0.04240967         cabin      N Pre_register1                    
3  1.17625756 Pre_registerY      Y Pre_register2        Pre_register

Проблема существует в строке 2 вывода - кабина ассоциируется с Y/N из уровней Pre_register Y/N, а кабина, оканчивающаяся на букву n, превращается в уровень.


person Ryan John    schedule 27.10.2016    source источник
comment
Вы уверены, что проблема с углом в вашем примере действительно проблема? Насколько вы знакомы с тем, как R обрабатывает кодирование и оценку уровней факторов в glm? Я не вижу демонстрации того, что это терпит неудачу.   -  person IRTFM    schedule 27.10.2016
comment
Попробуйте запустить приведенный выше пример - помогает? Я считаю, что это угловой пример.   -  person Ryan John    schedule 28.10.2016
comment
Код для создания этого вывода будет glm_to_sql(glm_ex), и вам сначала нужно будет загрузить библиотеку sqldf. Для числовой переменной не должно быть оператора CASE. Значение числовой переменной следует умножить на ее коэффициент. И, пожалуй, самое главное НИКОГДА не используйте конструкцию data.frame(cbind(...))   -  person IRTFM    schedule 29.10.2016
comment
Спасибо за помощь! Пожалуйста, извините мой плохой пример, пытающийся смоделировать какой-то код. Я также забыл включить в свой пример загрузку различных пакетов. Они уже были загружены, когда я запускал этот пример. Как вы указали, оператор case не должен применяться к числовой переменной. Это проблема. Я считаю, что этот фрагмент кода glm_to_sql нуждается в настройке, и я просил помощи.   -  person Ryan John    schedule 31.10.2016
comment
Какую базу данных вы используете?   -  person Hong Ooi    schedule 31.10.2016
comment
42 - Объясняет ли это, где существует проблема? Я пытаюсь продемонстрировать, как это терпит неудачу.   -  person Ryan John    schedule 02.11.2016


Ответы (2)


Поскольку вы упомянули, что используете Teradata, есть простой способ сделать это, хотя он может быть неприменим к вам. Просто запустите код подсчета очков в R прямо на своем сервере.

# fit the logistic regression model (or any other model)
modLR <- glm(Kyphosis ~ Age + Number + Start, data=kyphosis,
             family=binomial)

connStr <- "insert_ODBC_connection_string_here"

# input and output tables
inTbl <- RxTeradata("input_table_name", connectionString=connStr)
outTbl <- RxTeradata("output_table_name", connectionString=connStr)

# set the compute context to in-DB
ccTD <- RxInTeradata(connectionString=connStr)
rxSetComputeContext(ccTD)

# do the scoring
rxDataStep(inTbl, outTbl,
           transforms=list(
               pred=predict(.modLR, data.frame(Age, Number, Start))
           ),
           transformObjects=list(.modLR=modLR),
           transformPackages="stats")  # or rpart, randomForest, gbm, etc

Это соответствует модели на вашем локальном рабочем столе/ноутбуке, а затем отправляет ее в процесс R, работающий на сервере. Оценка происходит полностью на сервере, без перемещения данных обратно на ваш рабочий стол.

Если модель включает факторы, с этим можно (относительно) легко справиться, создав фактор как часть вызова прогноза:

rxDataStep(inTbl, outTbl,
           transforms=list(
               pred=predict(.modLR,
                   data.frame(Age, Number=factor(Number, levels=2:10), Start))
           ),
           transformObjects=list(.modLR=modLR))

Настройка так, чтобы метаданные, такие как уровни факторов и т. д., обрабатывались правильно, немного утомительна; Я опустил детали, но, надеюсь, вы видите, как это будет сделано.

Для этого требуется, чтобы на вашем устройстве Teradata был установлен сервер Revolution/Microsoft R Server. Поскольку вы задаете этот вопрос, я подозреваю, что служба MRS не установлена ​​(или вы бы уже использовали ее). Тем не менее, я помещаю это здесь, потому что это может помочь кому-то еще с Teradata, кто увидит этот вопрос.

Это же решение, естественно, работает и с Microsoft SQL Server. Мы поддерживали Teradata, когда Revo была независимой компанией, и эта поддержка не прекращается после приобретения.

Раскрытие информации: я работаю в Microsoft.

person Hong Ooi    schedule 31.10.2016
comment
Это выглядит как отличное решение, если бы у нас был установлен R на нашем поле Teradata, но я никак не могу получить к нему доступ :) Я пытаюсь найти решение, в котором я могу создать некоторый SQL и передать его другому коллеге/ группа для выполнения. - person Ryan John; 31.10.2016

Здравствуйте, еще один вариант заключается в том, что вы можете создать свой GLM с помощью R и сгенерировать эквивалентный исходный код с помощью пакета glm.deploy, https://cran.r-project.org/web/packages/glm.deploy/index.html вы можете сгенерировать код GLM на C или JAVA и перевести его на SQL проще или создать пользовательскую функцию для конкретной СУБД

person OCastro    schedule 06.09.2018