Vektorisierte Python-UDFs

Dieses Thema bietet eine Einführung in vektorisierte Python-UDFs.

Unter diesem Thema:

Übersicht

Mit vektorisierten Python-UDFs können Sie Python-Funktionen definieren, mit denen Batches von Eingabezeilen als Pandas DataFrames empfangen und Batches von Ergebnissen als Pandas-Arrays oder Pandas Series zurückgeben werden. Vektorisierte Python-UDFs werden genauso aufgerufen wie andere Python-UDFs.

Die Verwendung von vektorisierten Python-UDFs bietet im Vergleich zur zeilenweisen Standardverarbeitung folgende Vorteile:

  • Potenziell bessere Performance, wenn der Python-Code effizient auf Batches von Zeilen ausgeführt wird

  • Weniger Transformationslogik durch den Aufruf von Bibliotheken, die Pandas DataFrames oder Pandas-Arrays verarbeiten.

Vektorisierte Python-UDFs bieten folgende Vorteile:

  • Das Schreiben von Abfragen muss für Python-UDFs nicht geändert werden. Die gesamte Batchverarbeitung wird vom UDF-Framework und nicht von Ihrem eigenen Code übernommen.

  • Wie bei nicht vektorisierten UDFs gibt es keine Garantie dafür, welche Instanzen Ihres Handler-Codes welche Batches von Eingaben verarbeiten werden.

Erste Schritte mit vektorisierten Python-UDFs

Um eine Python-UDF zu erstellen, verwenden Sie eines der unterstützten Verfahren zum Kommentieren Ihrer Handler-Funktion.

Verwenden des Decorator-Elements vectorized

Das Modul _snowflake ist für Python-UDFs zugänglich, die in Snowflake ausgeführt werden. Importieren Sie in Ihrem Python-Code das Modul _snowflake, und verwenden Sie das Decorator-Element vectorized, um anzugeben, dass Ihr Handler den Empfang eines Pandas DataFrame erwartet, indem Sie den Parameter input auf pandas.DataFrame setzen.

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

Verwenden von Funktionsattributen

Anstatt das _snowflake-Modul zu importieren und das Decorator-Element vectorized zu verwenden, können Sie das spezielle Attribut _sf_vectorized_input für Ihre Handler-Funktion festlegen.

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

Festlegen einer Zielbatchgröße

Aufrufe der Python-Handler-Funktion müssen innerhalb eines Zeitlimits von 180 Sekunden ausgeführt werden, wobei aber jeder DataFrame, der als Eingabe an die Handler-Funktion übergeben wird, derzeit bis zu einige tausend Zeilen enthalten. Um das Zeitlimit einzuhalten, sollten Sie für Ihre Handler-Funktion eine Zielbatchgröße festlegen, die eine maximale Anzahl von Zeilen pro Eingabe-DataFrame vorgibt. Beachten Sie, dass die Einstellung eines größeren Wertes nicht garantiert, dass Snowflake Batches mit der angegebenen Anzahl von Zeilen codieren wird. Sie können die Zielbatchgröße entweder mit dem Decorator-Element vectorized oder mit einem Attribut der Funktion festlegen.

Bemerkung

Die Verwendung von max_batch_size ist nur als Mechanismus zur Begrenzung der Anzahl der Zeilen gedacht, die eine UDF pro Batch verarbeiten kann. Wenn die UDF beispielsweise so geschrieben ist, dass nur maximal 100 Zeilen auf einmal verarbeitet werden können, dann muss max_batch_size auf 100 gesetzt werden. Die Einstellung max_batch_size ist nicht als Mechanismus zur Angabe beliebig großer Batchgrößen gedacht. Wenn die UDF in der Lage ist, Batches beliebiger Größe zu verarbeiten, empfiehlt es sich, diesen Parameter nicht zu setzen.

Verwenden des Decorator-Elements vectorized

Um die Zielbatchgröße mit dem Decorator-Element vectorized festzulegen, übergeben Sie einen positiven Ganzzahl-Wert für das Argument max_batch_size.

Mit der folgenden Anweisung wird beispielsweise eine vektorisierte Python-UDF erstellt, die jeden Datenframe auf maximal 100 Zeilen begrenzt:

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

Verwenden von Funktionsattributen

Um die Zielbatchgröße mit einem Funktionsattribut festzulegen, legen Sie einen positiven Ganzzahl-Wert für das Attribut _sf_max_batch_size der Handler-Funktion fest.

Mit der folgenden Anweisung wird beispielsweise eine vektoriesierte Python-UDF erstellt, die jeden DataFrame auf maximal 100 Zeilen begrenzt:

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

DataFrame-Codierung

Batches von Argumenten für die UDF werden als Arrays in der Pandas DataFrames-Eingabe codiert, wobei die Anzahl der Zeilen in jedem DataFrame variieren kann. Weitere Informationen dazu finden Sie unter Festlegen einer Zielbatchgröße. Auf die Argumente kann in dem DataFrame über deren Index zugegriffen werden, d. h. das erste Argument hat den Index 0, das zweite den Index 1 und so weiter. Das Pandas-Array oder die Pandas Series, die der UDF-Handler zurückgibt, muss die gleiche Länge haben wie die der Eingabe-DataFrame.

Angenommen, Sie definieren eine vektorisierte Python-UDF wie folgt:

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

Dieses UDF verwendet df[0] für den Zugriff auf das erste Argument des Pandas-Arrays und df[1] für das zweite. df[0] + df[1] ergibt ein Pandas-Array mit den paarweisen Summen der entsprechenden Elemente aus beiden Arrays. Nachdem Sie die UDF erstellt haben, können Sie diese mit einigen Eingabezeilen aufrufen:

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

m folgenden Beispiel erhält die Python-Funktion add_inputs einen DataFrame analog zu einem Datenframe, der mit dem folgenden Python-Code erstellt wurde:

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

Die Zeile return df[0] + df[1] in der Handler-Funktion ergibt ein Array ähnlich dem folgenden Python-Code:

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

Typunterstützung

Vektorisierte Python-UDFs unterstützen die folgenden SQL-Typen für Argumente und Rückgabewerte. Die Tabelle zeigt, wie jedes SQL-Argument als Pandas-Array eines bestimmten dtype kodiert wird.

SQL-Typ

Pandas-dtype

Anmerkungen

NUMBER

Int16, Int32 oder Int64 für NUMBER-Argumente mit einer Skala von 0, die alle in einen von 64-Bit-Ganzzahl-Typ oder kleiner passen. Wenn das Argument nicht nullwertfähig ist, wird stattdessen int16, int32 oder int64 verwendet. (Bei UDTFs wird immer Int16, Int32 oder Int64 verwendet.) . . object für Argumente mit einer anderen Dezimalstellenzahl als 0 oder für Argumente, die nicht in eine 64-Bit-Ganzzahl passen, wobei Array-Elemente als decimal.Decimal-Werte codiert werden. . . Um einen 16-Bit-dtype sicherzustellen, verwenden Sie eine maximale NUMBER-Genauigkeit von 4. Um einen 32-Bit-dtype sicherzustellen, verwenden Sie eine maximale NUMBER-Genauigkeit von 9. Um einen 64-Bit-dtype sicherzustellen, verwenden Sie eine maximale NUMBER-Genauigkeit von 18.

Um sicherzustellen, dass ein UDF-Eingabeargument als nullwertfähig interpretiert wird, übergeben Sie eine Spalte aus einer Tabelle, die mit der Spalteneinschränkung NOT NULL erstellt wurde, oder verwenden Sie auf dem Argument eine Funktion wie IFNULL.

FLOAT

float64

NULL-Werte werden als NaN-Werte codiert. In der Ausgabe werden die NaN-Werte als NULL-Werte interpretiert.

BOOLEAN

boolean für nullwertfähige Argumente oder bool für nicht nullwertfähige Argumente.

VARCHAR

string

Sowohl Snowflake SQL als auch Pandas stellen Zeichenfolgen mit UTF-8-Codierung dar.

BINARY

bytes

DATE

datetime64

Jeder Wert wird als datetime64 ohne Uhrzeitkomponente codiert. NULL-Werte werden als numpy.timedelta('NaT') codiert.

VARIANT

object . . Jeder Wert wird als dict, list, int, float, str oder bool codiert.

Jede Variant-Zeile wird für Argumente dynamisch in einen Python-Typ konvertiert und umgekehrt für Rückgabewerte. Die folgenden Typen werden in Zeichenfolgen und nicht in native Python-Typen konvertiert: decimal, binary, date, time, timestamp_ltz, timestamp_ntz, timestamp_tz.

OBJECT

object . . Jedes Element wird als dict-Typ codiert.

ARRAY

object . . Jedes Element ist als list-Typ codiert.

TIME

timedelta64

Jeder Wert wird als Offset von Mitternacht codiert. NULL-Werte werden als numpy.timedelta64('NaT') codiert. Bei Verwendung als Rückgabetyp können die Ausgabeelemente numpy.timedelta64- oder datetime.time-Werte im Bereich [00:00:00, 23:59:59.999999999] sein.

TIMESTAMP_LTZ

datetime64

Verwendet die lokale Zeitzone, um jeden Wert als numpy.datetime64 im Nanosekunden-Bereich relativ zur UTC-Unix-Epoche zu codieren. NULL-Werte werden als numpy.datetime64('NaT') codiert. Bei Verwendung als Rückgabetyp können die Ausgabeelemente numpy.datetime64-Werte oder Zeitzonen-„naive“ datetime.datetime-Werte oder pandas.Timestamp-Werte sein.

TIMESTAMP_NTZ

datetime64

Codiert jeden Wert als numpy.datetime64 im Nanosekunden-Bereich. NULL-Werte werden als numpy.datetime64('NaT') codiert. Bei Verwendung als Rückgabetyp können die Ausgabeelemente numpy.datetime64-Werte oder Zeitzonen-„naive“ datetime.datetime-Werte oder pandas.Timestamp-Werte sein.

TIMESTAMP_TZ

object

Codiert jeden Wert als pandas.Timestamp im Nanosekunden-Bereich. NULL-Werte werden als pandas.NA codiert. Bei Verwendung als Rückgabetyp können die Ausgabeelemente datetime.datetime- oder pandas.Timestamp-Zeitzonenwerte sein.

GEOGRAPHY

object

Formatiert jeden Wert als GeoJSON und wandelt ihn dann in einen Python-Wert vom Typ dict um.

Die folgenden Typen werden als Ausgabe akzeptiert: Pandas Series oder array, NumPy array, regulärer Python-list-Typ und jede iterierbare Sequenz, die die in Typunterstützung beschriebenen erwarteten Typen enthält. Es ist effizient, Pandas Series und array und NumPy array zu verwenden, wenn der dtype bool, boolean, int16, int32, int64, Int16, Int32, Int64 oder float64 ist, da sie ihren Inhalt als memoryviews ausgeben. Das bedeutet, dass der Inhalt kopiert werden kann und nicht jeder Wert nacheinander gelesen werden muss.