UDFs vetorizadas de Python

Este tópico apresenta as UDFs vetorizadas de Python.

Neste tópico:

Visão geral

As UDFs de Python vetorizadas permitem que você defina funções Python que recebem lotes de linhas de entrada como DataFrames do Pandas e retornam lotes de resultados como matrizes ou séries do Pandas. Você chama UDFs vetorizadas de Python da mesma forma que você chama outras UDFs de Python.

As vantagens de usar as UDFs vectorizadas Python em comparação com o padrão de processamento linha por linha incluem:

  • O potencial de melhor desempenho se seu código de Python operar eficientemente em lotes de linhas.

  • Menos lógica de transformação necessária se você estiver chamando bibliotecas que operam em DataFrames ou Arrays do Pandas.

Quando você utiliza UDFs vetorizadas de Python:

  • Você não precisa mudar a forma de escrever consultas usando UDFs de Python. Todos os lotes são tratados pelo framework da UDF e não pelo seu próprio código.

  • Como no caso de UDFs não vetorizadas, não há garantia de quais instâncias de seu código do manipulador verão quais lotes de entrada.

Introdução às UDFs vetorizadas de Python

Para criar uma UDF vetorizada de Python, use um dos mecanismos suportados para anotar sua função do manipulador.

Como usar o Decorator vectorized

O módulo _snowflake é exposto a UDFs de Python que são executadas dentro do Snowflake. Em seu código Python, importe o módulo _snowflake e use o decorador vectorized para especificar que seu manipulador espera receber um DataFrame do Pandas definindo o parâmetro input como pandas.DataFrame.

create function add_one_to_inputs(x number(10, 0), y number(10, 0))
returns number(10, 0)
language python
runtime_version = 3.8
packages = ('pandas')
handler = 'add_one_to_inputs'
as $$
import pandas
from _snowflake import vectorized

@vectorized(input=pandas.DataFrame)
def add_one_to_inputs(df):
  return df[0] + df[1] + 1
$$;
Copy

Como usar um atributo de função

Em vez de importar o módulo _snowflake e usar o decorador vectorized, você pode definir o atributo especial _sf_vectorized_input em sua função do manipulador.

create function add_one_to_inputs(x number(10, 0), y number(10, 0))
returns number(10, 0)
language python
runtime_version = 3.8
packages = ('pandas')
handler = 'add_one_to_inputs'
as $$
import pandas

def add_one_to_inputs(df):
  return df[0] + df[1] + 1

add_one_to_inputs._sf_vectorized_input = pandas.DataFrame
$$;
Copy

Como definir um tamanho alvo de lote

Chamadas para a função do manipulador do Python devem ser executadas dentro de um limite de tempo, que é de 180 segundos, e cada DataFrame passado como entrada para a função do manipulador pode conter atualmente até alguns milhares de linhas. Para de permanecer dentro do limite de tempo, você pode querer definir o tamanho alvo do lote para sua função do manipulador, que impõe um número máximo de linhas por DataFrame de entrada. Observe que a definição de um valor maior não garante que o Snowflake codifique lotes com o número especificado de linhas. Você pode definir o tamanho alvo do lote usando o decorador vectorized ou um atributo na função.

Nota

O uso de max_batch_size destina-se apenas como um mecanismo para limitar o número de linhas que o UDF pode lidar por lote único. Por exemplo, se o UDF for escrito de uma forma que só possa processar no máximo 100 linhas de cada vez, então o max_batch_size deve ser definido como 100. A configuração do max_batch_size não se destina a ser usada como um mecanismo para especificar tamanhos arbitrários de grandes lotes. Se o UDF for capaz de processar lotes de qualquer tamanho, é recomendável deixar este parâmetro sem ajustes.

Como usar o Decorator vectorized

Para definir o tamanho alvo do lote usando o decorador vectorized, passe um valor inteiro positivo para o argumento chamado max_batch_size.

Como exemplo, esta instrução cria uma UDF vetorizada de Python e limita cada Dataframe a um máximo de 100 linhas:

create function add_one_to_inputs(x number(10, 0), y number(10, 0))
returns number(10, 0)
language python
runtime_version = 3.8
packages = ('pandas')
handler = 'add_one_to_inputs'
as $$
import pandas
from _snowflake import vectorized

@vectorized(input=pandas.DataFrame, max_batch_size=100)
def add_one_to_inputs(df):
  return df[0] + df[1] + 1
$$;
Copy

Como usar um atributo de função

Para definir o tamanho alvo do lote usando um atributo de função, defina um valor inteiro positivo para o atributo _sf_max_batch_size em sua função do manipulador.

Como exemplo, esta instrução cria uma UDF vetorizada de Python e limita cada DataFrame a um máximo de 100 linhas:

create function add_one_to_inputs(x number(10, 0), y number(10, 0))
returns number(10, 0)
language python
runtime_version = 3.8
packages = ('pandas')
handler = 'add_one_to_inputs'
as $$
import pandas

def add_one_to_inputs(df):
  return df[0] + df[1] + 1

add_one_to_inputs._sf_vectorized_input = pandas.DataFrame
add_one_to_inputs._sf_max_batch_size = 100
$$;
Copy

Codificação do DataFrame

Lotes de argumentos para a UDF são codificados como arrays nos DataFramesde entrada do Pandas, e o número de linhas em cada DataFrame pode variar. Para obter mais informações, consulte Como definir um tamanho alvo de lote. Argumentos podem ser acessados no DataFrame pelo índice, ou seja, o primeiro argumento tem um índice de 0, o segundo tem um índice de 1, e assim por diante. O objeto Array ou Series do Pandas que o manipulador da UDF retorna deve ter o mesmo comprimento que o DataFrame de entrada.

Para ilustrar, suponha que você defina uma UDF vetorizada de Python da seguinte forma:

create or replace function add_inputs(x int, y float)
returns float
language python
runtime_version = 3.8
packages = ('pandas')
handler = 'add_inputs'
as $$
import pandas
from _snowflake import vectorized

@vectorized(input=pandas.DataFrame)
def add_inputs(df):
  return df[0] + df[1]
$$;
Copy

Essa UDF usa df[0] para acessar a array do Pandas para o primeiro argumento, e df[1] para o segundo. O df[0] + df[1] resulta em uma array do Pandas com as somas em pares dos elementos correspondentes das duas arrays. Depois de criar a UDF, você pode chamá-la com algumas linhas de entrada:

select add_inputs(x, y)
from (
  select 1 as x, 3.14::float as y union all
  select 2, 1.59 union all
  select 3, -0.5
);
+------------------+
| ADD_INPUTS(X, Y) |
|------------------|
|             4.14 |
|             3.59 |
|             2.5  |
+------------------+
Copy

Aqui a função add_inputs do Python recebe um DataFrame análogo a um criado com o seguinte código de Python:

>>> import pandas
>>> df = pandas.DataFrame({0: pandas.array([1, 2, 3]), 1: pandas.array([3.14, 1.59, -0.5])})
>>> df
   0     1
0  1  3.14
1  2  1.59
2  3 -0.50
Copy

A linha return df[0] + df[1] na função do manipulador resulta em uma array semelhante ao seguinte código de Python:

>>> df[0] + df[1]
0    4.14
1    3.59
2    2.50
dtype: float64
Copy

Suporte a tipos

UDFs vetorizadas de Python oferecem suporte aos seguintes tipos SQL para argumentos e valores de retorno. A tabela reflete como cada argumento SQL é codificado como uma array Pandas de um determinado dtype.

Tipo de SQL

dtype do Pandas

Notas

NUMBER

Int16, Int32 ou Int64 para NUMBER argumentos com uma escala de 0 que todos se encaixam em um tipo integer de 64 bits ou menor. Se o argumento não for anulável, int16, int32 ou int64 será usado. (Para UDTFs, Int16, Int32 ou Int64 sempre será usado.) . . object para argumentos com uma escala diferente de 0, ou para argumentos que não cabem dentro de um inteiro de 64 bits, onde elementos da array são codificados como valores decimal.Decimal. . . Para garantir um dtype de 16 bits, use uma precisão máxima de NUMBER 4. Para garantir um dtype de 32 bits, use uma precisão máxima de NUMBER 9. Para garantir um dtype de 64 bits, use uma precisão máxima de NUMBER 18.

Para garantir que um argumento de entrada para uma UDF seja interpretado como não anulável, passe uma coluna de uma tabela criada usando a restrição de coluna NOT NULL, ou use uma função como IFNULL no argumento.

FLOAT

float64

Valores NULL são codificados como valores NaN. Na saída, os valores NaN são interpretados como NULLs.

BOOLEAN

boolean para argumentos anuláveis ou bool para argumentos não anuláveis.

VARCHAR

string

Tanto o SQL do Snowflake quanto o Pandas representam cadeias de caracteres usando a codificação UTF-8.

BINARY

bytes

DATE

datetime64

Cada valor é codificado como um datetime64 sem componente de tempo. Valores NULL são codificados como numpy.timedelta('NaT').

VARIANT

object . . Cada valor é codificado como um dict, list, int, float, str ou bool.

Cada linha de variante é convertida dinamicamente para um tipo do Python para argumentos e vice-versa para valores de retorno. Os seguintes tipos são convertidos em cadeias de caracteres em vez de tipos nativos do Python: decimal, binary, date, time, timestamp_ltz, timestamp_ntz, timestamp_tz.

OBJECT

object . . Cada elemento é codificado como um dict.

ARRAY

object . . Cada elemento é codificado como uma lista.

TIME

timedelta64

Cada valor é codificado como uma diferença de tempo em relação a meia-noite. Valores NULL são codificados como numpy.timedelta64('NaT'). Quando usado como um tipo de retorno, os elementos da saída podem ser valores numpy.timedelta64 ou datetime.time na faixa [00:00:00, 23:59:59.999999999].

TIMESTAMP_LTZ

datetime64

Utiliza o fuso horário local para codificar cada valor como uma escala de nanossegundos numpy.datetime64 relativa ao UTC. Época Unix. Valores NULL são codificados como numpy.datetime64('NaT'). Quando usado como um tipo de retorno, os elementos da saída podem ser valores numpy.datetime64 ou valores ingênuos em relação ao fuso horário datetime.datetime ou pandas.Timestamp.

TIMESTAMP_NTZ

datetime64

Codifica cada valor como uma escala de nanossegundos numpy.datetime64. Valores NULL são codificados como numpy.datetime64('NaT'). Quando usado como um tipo de retorno, os elementos da saída podem ser valores numpy.datetime64 ou valores ingênuos em relação ao fuso horário datetime.datetime ou pandas.Timestamp.

TIMESTAMP_TZ

object

Codifica cada valor como uma escala de nanossegundos pandas.Timestamp. Valores NULL são codificados como pandas.NA. Quando usado como um tipo de retorno, os elementos da saída podem ser valores que levam em conta o fuso horário datetime.datetime ou pandas.Timestamp.

GEOGRAPHY

object

Formata cada valor como GeoJSON e depois o converte para um dict de Python.

Os seguintes tipos são aceitos como saída: Series ou array do Pandas, array do NumPy, list comum do Python e qualquer sequência iterável que contenha os tipos esperados descritos em Suporte a tipos. É eficiente usar Series e array do Pandas e array do NumPy onde o dtype for bool, boolean, int16, int32, int64, Int16, Int32, Int64 ou float64, porque eles expõem seu conteúdo como memoryviews. Isso significa que o conteúdo pode ser copiado em vez de cada valor ser lido sequencialmente.