Поделиться через


Создание признаков данных с помощью R и SQL Server (пошаговое руководство)

Применимо к: SQL Server 2016 (13.x) и более поздних версий

Проектирование данных — это важная составляющая машинного обучения. Прежде чем использовать данные для прогнозирующего моделирования, их зачастую требуется преобразовать. Если данные не имеют требуемых характеристик, их необходимо создать на основе существующих значений.

Для этой задачи моделирования вместо необработанных значений широты и долготы мест посадки и высадки желательно использовать значения расстояния в километрах между этими двумя местами. Чтобы создать такой признак, необходимо вычислить прямое линейное расстояние между двумя точками по формуле гаверсинуса.

На этом этапе вы узнаете о двух разных способах создания признаков на основе данных:

  • Использование пользовательской функции R
  • Использование пользовательской функции T-SQL в Transact-SQL

Целью является создание нового набора данных SQL Server, включающего в себя исходные столбцы и новый числовой признак — direct_distance.

Предварительные условия

Для этого этапа требуется продолжение сеанса R из предыдущих этапов этого пошагового руководства. В нем используются строки подключения и объекты источников данных, созданные на этих этапах. Для запуска скрипта используются следующие средства и пакеты.

  • Rgui.exe для выполнения команд R
  • Management Studio для выполнения T-SQL

Добавление признаков с помощью R

Язык R известен своими статистическими библиотеками с широкими и разнообразными возможностями, но вам все еще нужно создавать пользовательские преобразования данных.

Во-первых, давайте сделаем так, как привыкли пользователи R: получите данные на ноутбук, а затем запустите пользовательскую функцию R ComputeDist, которая вычисляет линейное расстояние между двумя точками, заданными значениями широты и долготы.

  1. Помните, что созданный ранее объект источника данных возвращает только первые 1000 строк. Итак, давайте определим запрос, который получает все данные.

    bigQuery <- "SELECT tipped, fare_amount, passenger_count,trip_time_in_secs,trip_distance, pickup_datetime, dropoff_datetime,  pickup_latitude, pickup_longitude,  dropoff_latitude, dropoff_longitude FROM nyctaxi_sample";
    
  2. Создайте новый объект источника данных с помощью запроса.

    featureDataSource <- RxSqlServerData(sqlQuery = bigQuery,colClasses = c(pickup_longitude = "numeric", pickup_latitude = "numeric", dropoff_longitude = "numeric", dropoff_latitude = "numeric", passenger_count  = "numeric", trip_distance  = "numeric", trip_time_in_secs  = "numeric", direct_distance  = "numeric"), connectionString = connStr);
    
    • RxSqlServerData может принимать либо запрос, состоящий из допустимого запроса SELECT, предоставленный в качестве аргумента для параметра sqlQuery, либо имя объекта таблицы, предоставленное в качестве параметра table.

    • Если необходимо выполнить выборку данных из таблицы, используйте параметр sqlQuery, определите параметры выборки с помощью предложения T-SQL TABLESAMPLE и задайте для аргумента rowBuffering значение FALSE.

  3. Выполните следующий код для создания пользовательской функции R. ComputeDist принимает две пары значений широты и долготы и вычисляет линейное расстояние между ними, возвращая расстояние в милях.

    env <- new.env();
    env$ComputeDist <- function(pickup_long, pickup_lat, dropoff_long, dropoff_lat){
      R <- 6371/1.609344 #radius in mile
      delta_lat <- dropoff_lat - pickup_lat
      delta_long <- dropoff_long - pickup_long
      degrees_to_radians = pi/180.0
      a1 <- sin(delta_lat/2*degrees_to_radians)
      a2 <- as.numeric(a1)^2
      a3 <- cos(pickup_lat*degrees_to_radians)
      a4 <- cos(dropoff_lat*degrees_to_radians)
      a5 <- sin(delta_long/2*degrees_to_radians)
      a6 <- as.numeric(a5)^2
      a <- a2+a3*a4*a6
      c <- 2*atan2(sqrt(a),sqrt(1-a))
      d <- R*c
      return (d)
    }
    
    • В первой строке определяется новая среда. В R среду можно использовать для инкапсуляции пространств имен в пакетах и отдельно. С помощью функции search() можно просмотреть среды, имеющиеся в рабочем пространстве. Чтобы просмотреть объекты в определенной среде, введите ls(<envname>).
    • Строки начиная с $env.ComputeDist содержат код, который определяет формулу гаверсинуса, вычисляющую ортодромическое расстояние между двумя точками на сфере.
  4. Определив функцию, вы примените ее к данным, чтобы создать столбец признаков direct_distance. Но перед преобразованием измените контекст вычислений на локальный.

    rxSetComputeContext("local");
    
  5. Вызовите функцию rxDataStep, чтобы получить данные о проектировании признаков, и примените функцию env$ComputeDist к данным в памяти.

    start.time <- proc.time();
    
    changed_ds <- rxDataStep(inData = featureDataSource,
    transforms = list(direct_distance=ComputeDist(pickup_longitude,pickup_latitude, dropoff_longitude, dropoff_latitude),
    tipped = "tipped", fare_amount = "fare_amount", passenger_count = "passenger_count",
    trip_time_in_secs = "trip_time_in_secs",  trip_distance="trip_distance",
    pickup_datetime = "pickup_datetime",  dropoff_datetime = "dropoff_datetime"),
    transformEnvir = env,
    rowsPerRead=500,
    reportProgress = 3);
    
    used.time <- proc.time() - start.time;
    print(paste("It takes CPU Time=", round(used.time[1]+used.time[2],2)," seconds, Elapsed Time=", round(used.time[3],2), " seconds to generate features.", sep=""));
    

    Однако стоит отметить несколько моментов, касающихся rxDataStep.

    В других источниках данных можно использовать аргументы varsToKeep и varsToDrop, но они не поддерживаются для источников данных SQL Server. Поэтому в этом примере мы использовали аргумент transforms, чтобы указать как сквозные столбцы, так и преобразованные. Кроме того, при выполнении в контексте вычислений SQL Server аргумент inData может принимать только источник данных SQL Server.

    Приведенный выше код может также выдавать предупреждающее сообщение при выполнении в больших наборах данных. Если произведение создаваемых строк и столбцов превышает заданное значение (по умолчанию — 3 000 000), rxDataStep возвращает предупреждение, а число строк в возвращенном кадре данных будет обрезано. Чтобы устранить это предупреждение, можно изменить аргумент maxRowsByCols в функции rxDataStep. Но если значение maxRowsByCols слишком велико, могут возникнуть проблемы при загрузке кадра данных в память.

  6. Кроме того, вы можете вызвать функцию rxGetVarInfo, чтобы проверить схему преобразованного нового источника данных.

    rxGetVarInfo(data = changed_ds);
    

Добавление признаков с помощью Transact-SQL

В этом упражнении вы узнаете, как выполнить ту же задачу с помощью функций SQL, а не пользовательских функций R.

Для запуска скрипта T-SQL перейдите в SQL Server Management Studio или другой редактор запросов.

  1. Используйте функцию SQL с именем fnCalculateDistance. Функция уже должна существовать в базе данных NYCTaxi_Sample. В обозревателе объектов убедитесь в том, что функция существует, перейдя по пути "Базы данных > NYCTaxi_Sample > Программируемость > Функции > Скалярные функции > dbo.fnCalculateDistance.

    Если функция не существует, создайте функцию в базе данных NYCTaxi_Sample, используя SQL Server Management Studio.

    CREATE FUNCTION [dbo].[fnCalculateDistance] (@Lat1 float, @Long1 float, @Lat2 float, @Long2 float)
    -- User-defined function calculates the direct distance between two geographical coordinates.
    RETURNS decimal(28, 10)
    AS
    BEGIN
      DECLARE @distance decimal(28, 10)
      -- Convert to radians
      SET @Lat1 = @Lat1 / 57.2958
      SET @Long1 = @Long1 / 57.2958
      SET @Lat2 = @Lat2 / 57.2958
      SET @Long2 = @Long2 / 57.2958
      -- Calculate distance
      SET @distance = (SIN(@Lat1) * SIN(@Lat2)) + (COS(@Lat1) * COS(@Lat2) * COS(@Long2 - @Long1))
      --Convert to miles
      IF @distance <> 0
      BEGIN
        SET @distance = 3958.75 * ATAN(SQRT(1 - POWER(@distance, 2)) / @distance);
      END
      RETURN @distance
    END
    
  2. В новом окне запросов Management Studio выполните следующую инструкцию Transact-SQL из любого приложения с поддержкой Transact-SQL, чтобы увидеть, как работает эта функция.

    USE nyctaxi_sample
    GO
    
    SELECT tipped, fare_amount, passenger_count,trip_time_in_secs,trip_distance, pickup_datetime, dropoff_datetime,
    dbo.fnCalculateDistance(pickup_latitude, pickup_longitude, dropoff_latitude, dropoff_longitude) as direct_distance, pickup_latitude, pickup_longitude,  dropoff_latitude, dropoff_longitude 
    FROM nyctaxi_sample
    
  3. Чтобы вставить значения непосредственно в новую таблицу (сначала необходимо создать ее), можно добавить предложение INTO, указав имя таблицы.

    USE nyctaxi_sample
    GO
    
    SELECT tipped, fare_amount, passenger_count, trip_time_in_secs, trip_distance, pickup_datetime, dropoff_datetime,
    dbo.fnCalculateDistance(pickup_latitude, pickup_longitude, dropoff_latitude, dropoff_longitude) as direct_distance, pickup_latitude, pickup_longitude, dropoff_latitude, dropoff_longitude
    INTO NewFeatureTable
    FROM nyctaxi_sample
    
  4. Вы также можете вызвать функцию SQL из кода R. Вернитесь в Rgui и сохраните запрос добавления признаков SQL в переменной R.

    featureEngineeringQuery = "SELECT tipped, fare_amount, passenger_count,
        trip_time_in_secs,trip_distance, pickup_datetime, dropoff_datetime,
        dbo.fnCalculateDistance(pickup_latitude, pickup_longitude,  dropoff_latitude, dropoff_longitude) as direct_distance,
        pickup_latitude, pickup_longitude,  dropoff_latitude, dropoff_longitude
        FROM nyctaxi_sample
        tablesample (1 percent) repeatable (98052)"
    

    Совет

    Запрос был изменен для того, чтобы уменьшить выборку данных и ускорить работу с этим пошаговым руководством. Если требуется получить все данные, можно удалить предложение TABLESAMPLE. Однако в зависимости от среды загрузка полного набора данных R может оказаться невозможной, что приведет к ошибке.

  5. Используйте приведенные ниже строки кода, чтобы вызвать функцию Transact-SQL из среды R и применить ее к данным, определенным в featureEngineeringQuery.

    featureDataSource = RxSqlServerData(sqlQuery = featureEngineeringQuery,
      colClasses = c(pickup_longitude = "numeric", pickup_latitude = "numeric",
        dropoff_longitude = "numeric", dropoff_latitude = "numeric",
        passenger_count  = "numeric", trip_distance  = "numeric",
        trip_time_in_secs  = "numeric", direct_distance  = "numeric"),
      connectionString = connStr)
    
  6. Теперь, когда признак создан, вызовите rxGetVarsInfo, чтобы получить сводку данных в таблице признаков.

    rxGetVarInfo(data = featureDataSource)
    

    Результаты

    Var 1: tipped, Type: integer
    Var 2: fare_amount, Type: numeric
    Var 3: passenger_count, Type: numeric
    Var 4: trip_time_in_secs, Type: numeric
    Var 5: trip_distance, Type: numeric
    Var 6: pickup_datetime, Type: character
    Var 7: dropoff_datetime, Type: character
    Var 8: direct_distance, Type: numeric
    Var 9: pickup_latitude, Type: numeric
    Var 10: pickup_longitude, Type: numeric
    Var 11: dropoff_latitude, Type: numeric
    Var 12: dropoff_longitude, Type: numeric
    

    Примечание

    Иногда может возникать ошибка наподобие следующей: Разрешение EXECUTE было отклонено для объекта "fnCalculateDistance" . В этом случае убедитесь в том, что используемое имя входа имеет разрешения на выполнение скриптов и создание объектов в базе данных, а не только в экземпляре. Проверьте схему для объекта fnCalculateDistance. Если объект был создан владельцем базы данных, а имя входа принадлежит роли db_datareader, то необходимо предоставить имени входа явные разрешения на запуск скрипта.

Сравнение функций R с функциями SQL

Помните этот фрагмент кода, используемый для времени в коде R?

start.time <- proc.time()
<your code here>
used.time <- proc.time() - start.time
print(paste("It takes CPU Time=", round(used.time[1]+used.time[2],2)," seconds, Elapsed Time=", round(used.time[3],2), " seconds to generate features.", sep=""))

Можно попробовать использовать его с примером пользовательской функции SQL, чтобы узнать, как долго выполняется преобразование данных при вызове функции SQL. Кроме того, попробуйте переключить контексты вычислений с помощью rxSetComputeContext и сравнить временные показатели.

Время может значительно различаться в зависимости от скорости сети и конфигурации оборудования. В проверенных нами конфигурациях функция Transact-SQL работала быстрее, чем пользовательская функция R. Поэтому на последующих этапах мы использовали для этих вычислений именно функцию Transact-SQL.

Совет

Зачастую формирование признаков с помощью Transact-SQL выполняется быстрее, чем с помощью R. Например, T-SQL содержит быстрые агрегатные и ранжирующие функции, которые можно применять в типичных сценариях вычислений для обработки и анализа данных, например для вычисления скользящих средних и процентилей. Выберите наиболее эффективный способ в зависимости от особенностей данных и поставленной задачи.

Дальнейшие действия