PyODPS DataFrame, yerleşik hesaplamaları kullanıcı tanımlı işlevler (UDF) ve üçüncü taraf Python paketleriyle genişletmenize olanak tanır. Bu konu; map ile öğe düzeyinde eşleme, apply ile satır düzeyi dönüşümler ve özel toplamlar, UDF'ler içindeki kaynak referansları ile üçüncü taraf paketlerin nasıl yükleneceği ve yapılandırılacağı konularını kapsar.
Ön koşullar
Başlamadan önce şunlara sahip olduğunuzdan emin olun:
MaxCompute tablosundan veya pandas DataFrame'den oluşturulmuş bir DataFrame nesnesi
MaxCompute projenizde etkinleştirilmiş Python UDF desteği (Python işlevleriyle
mapveapplyiçin gereklidir)
Alibaba Cloud genel hizmetleri Python UDF'yi desteklemez. Projeniz Python UDF'leri desteklemiyorsa map yöntemi ve buna bağlı yerleşik işlevler kullanılamaz.
Bilinen sınırlamalar
| Sınırlama | Ayrıntılar |
|---|---|
| Desteklenmeyen türler | map ve apply yöntemleri giriş veya çıkış olarak LIST ya da DICT türlerini kabul etmez. |
| Önceden yüklenmiş ikili kitaplık | C kodu içeren tek önceden yüklenmiş üçüncü taraf kitaplık NumPy'dir. Diğer tüm ikili kitaplıkların açıkça yüklenmesi gerekir. |
| Python 2/3 uyumluluğu | Python sürümleri arasındaki bayt kodu farklılıkları nedeniyle, Python 3'e özel sözdizimi kullanan kodlar (örneğin yield from) Python 2.7 çalıştıran bir MaxCompute Worker'da başarısız olabilir. Kodunuzu Python 3'teki MapReduce API'sini kullanarak üretim ortamına dağıtmadan önce doğru çalıştığını doğrulayın. |
| İkili paketler için derleme platformu | macOS veya Windows üzerinde oluşturulan Wheel dosyaları MaxCompute'da kullanılamaz. İkili paketleri bir Linux kabuğunda oluşturun. |
Bir sütuna UDF uygulama
Her öğede bir UDF çağırmak için bir Sequence nesnesinde map yöntemini kullanın.
>>> iris.sepallength.map(lambda x: x + 1).head(5)
sepallength
0 6.1
1 5.9
2 5.7
3 5.6
4 6.0
map işleminden sonra Sequence türü değişirse yeni türü açıkça belirtin:
>>> iris.sepallength.map(lambda x: 't' + str(x), 'string').head(5)
sepallength
0 t5.1
1 t4.9
2 t4.7
3 t4.6
4 t5.0
Kapanış değişken yakalama hatalarından kaçının
Bir UDF kapanış içerdiğinde, yakalanan değişkende yapılan harici değişiklikler işlevin davranışını etkiler. Aşağıdaki kod istenmeyen bir sonuç üretir; dfs içindeki her SequenceExpr sonunda df.sepal_length + 9 olur:
>>> dfs = []
>>> for i in range(10):
>>> dfs.append(df.sepal_length.map(lambda x: x + i))
Bu sorunu çözmek için lambda'yı dış bir işlevden döndürün veya functools.partial kullanın:
# Option 1: use a factory function
>>> dfs = []
>>> def get_mapper(i):
>>> return lambda x: x + i
>>> for i in range(10):
>>> dfs.append(df.sepal_length.map(get_mapper(i)))
# Option 2: use functools.partial
>>> import functools
>>> dfs = []
>>> for i in range(10):
>>> dfs.append(df.sepal_length.map(functools.partial(lambda v, x: x + v, i)))
Mevcut UDF'leri kullanma
Mevcut bir UDF'yi çağırmak için map öğesine bir işlev adı (dize) veya Function nesnesi iletin. Ayrıntılar için bkz. İşlevler.
Sayaçlarla yürütmeyi izleme
Bir UDF içinden sayaçlara erişmek için get_execution_context kullanın. Sayaç değerleri LogView'un JSONSummary bölümünde görünür.
from odps.udf import get_execution_context
def h(x):
ctx = get_execution_context()
counters = ctx.get_counters()
counters.get_counter('df', 'add_one').increment(1)
return x + 1
df.field.map(h)
Bir satıra UDF uygulama
Her satırda bir UDF çağırmak için axis=1 ile apply kullanın. UDF bir seferde bir satır alır; alan değerlerini nitelik adına veya dizine göre alın.
Satır başına tek değer döndürme
Bir Sequence döndürmek için reduce=True ayarlayın. Çıkış türünü types parametresiyle belirtin (varsayılan STRING'dir).
>>> iris.apply(lambda row: row.sepallength + row.sepalwidth, axis=1, reduce=True, types='float').rename('sepaladd').head(3)
sepaladd
0 8.6
1 7.9
2 7.9
yield kullanarak birden çok satır döndürme
Giriş satırı başına birden çok satır yaymak için reduce=False ayarlayın ve yield kullanın. Çıkış alan adlarını ve türlerini names ve types ile belirtin.
>>> iris.count()
150
>>> def handle(row):
>>> yield row.sepallength - row.sepalwidth, row.sepallength + row.sepalwidth
>>> yield row.petallength - row.petalwidth, row.petallength + row.petalwidth
>>> iris.apply(handle, axis=1, names=['iris_add', 'iris_sub'], types=['float', 'float']).count()
300
Çağrı noktalarında tekrar etmekten kaçınmak için çıkış şemasını doğrudan işlev üzerinde açıklamalı olarak belirtin:
>>> from odps.df import output
>>> @output(['iris_add', 'iris_sub'], ['float', 'float'])
>>> def handle(row):
>>> yield row.sepallength - row.sepalwidth, row.sepallength + row.sepalwidth
>>> yield row.petallength - row.petalwidth, row.petallength + row.petalwidth
>>> iris.apply(handle, axis=1).count()
300
Eşdeğer: yalnızca map içeren map_reduce
Yalnızca map modundaki map_reduce, axis=1 ile apply işlevine eşdeğerdir:
>>> iris.map_reduce(mapper=handle).count()
300
Mevcut bir UDTF kullanma
MaxCompute'da mevcut bir kullanıcı tanımlı tablo değerli işlevi (UDTF) çağırmak için adını dize olarak iletin:
>>> iris['name', 'sepallength'].apply('your_func', axis=1, names=['name2', 'sepallength2'], types=['string', 'float'])
Satır çıkışını lateral view ile birleştirme
reduce=False olduğunda, UDF çıktısını orijinal sütunlarla lateral view kullanarak birleştirin; bu durum toplamalar için kullanışlıdır:
>>> from odps.df import output
>>> @output(['iris_add', 'iris_sub'], ['float', 'float'])
>>> def handle(row):
>>> yield row.sepallength - row.sepalwidth, row.sepallength + row.sepalwidth
>>> yield row.petallength - row.petalwidth, row.petallength + row.petalwidth
>>> iris[iris.category, iris.apply(handle, axis=1)]
Bir sütuna özel toplamlar uygulama
Tüm Sequence nesneleri üzerinde özel bir toplama sınıfını geçirmek için axis=0 (veya axis bağımsız değişkeni olmadan) ile apply kullanın. Sınıf buffer, __call__, merge ve getvalue uygulamalıdır.
class Agg(object):
def buffer(self):
return [0.0, 0]
def __call__(self, buffer, val):
buffer[0] += val
buffer[1] += 1
def merge(self, buffer, pbuffer):
buffer[0] += pbuffer[0]
buffer[1] += pbuffer[1]
def getvalue(self, buffer):
if buffer[1] == 0:
return 0.0
return buffer[0] / buffer[1]
>>> iris.exclude('name').apply(Agg)
sepallength_aggregation sepalwidth_aggregation petallength_aggregation petalwidth_aggregation
0 5.843333 3.054 3.758667 1.198667
UDF'lerde MaxCompute kaynaklarını okuma
UDF'ler MaxCompute kaynaklarını — dosya kaynakları ve tablo kaynakları — okuyabilir veya bir Collection nesnesini kaynak olarak referans alabilir. Kaynakların satır başına değil, başlatma sırasında bir kez yüklenmesi için UDF'yi bir kapanış veya çağrılabilir sınıfla sarın.
Kaynakları her işlev çağrısında değil, kapanış içinde yüklemek yineleyen başlatma ek yükünü önler; örneğin, arama tabloları veya model yapıtları yüklenirken.
Dosya ve koleksiyon kaynaklarıyla satır düzeyi UDF
>>> file_resource = o.create_resource('pyodps_iris_file', 'file', file_obj='Iris-setosa')
>>> iris_names_collection = iris.distinct('name')[:2]
>>> iris_names_collection
sepallength
0 Iris-setosa
1 Iris-versicolor
>>> def myfunc(resources): # resources are passed in by calling order
>>> names = set()
>>> fileobj = resources[0] # file resources are represented by a file-like object
>>> for l in fileobj:
>>> names.add(l)
>>> collection = resources[1]
>>> for r in collection:
>>> names.add(r.name) # retrieve values by field name or offset
>>> def h(x):
>>> if x in names:
>>> return True
>>> else:
>>> return False
>>> return h
>>> df = iris.distinct('name')
>>> df = df[df.name,
>>> df.name.map(myfunc, resources=[file_resource, iris_names_collection], rtype='boolean').rename('isin')]
>>> df
name isin
0 Iris-setosa True
1 Iris-versicolor True
2 Iris-virginica False
Bölümlenmiş tablolar okunurken bölüm alanları dahil edilmez.
Yerel DataFrame'i kaynak olarak kullanan satır düzeyi UDF
Yerel değişkenler, yürütme sırasında MaxCompute'da kaynak olarak referans alınabilir. Aşağıdaki örnekte stop_words, yürütücünün UDF'ye kaynak olarak ilettiği yerel bir DataFrame'dir:
>>> words_df
sentence
0 Hello World
1 Hello Python
2 Life is short I use Python
>>> import pandas as pd
>>> stop_words = DataFrame(pd.DataFrame({'stops': ['is', 'a', 'I']}))
>>> @output(['sentence'], ['string'])
>>> def filter_stops(resources):
>>> stop_words = set([r[0] for r in resources[0]])
>>> def h(row):
>>> return ' '.join(w for w in row[0].split() if w not in stop_words),
>>> return h
>>> words_df.apply(filter_stops, axis=1, resources=[stop_words])
sentence
0 Hello World
1 Hello Python
2 Life short use Python
Satır işlemleri için (axis=1), kaynakları yüklemek üzere işlev kapanışı veya çağrılabilir sınıf kullanın. Sütun toplamları için bunun yerine__init__yöntemini kullanın.
Üçüncü taraf Python kitaplıklarını yükleme
MaxCompute, Python paketlerinin .whl, .egg, .zip ve .tar.gz biçimlerinde yüklenmesini destekler. Tüm bağımlılıklar açıkça belirtilmelidir; bir bağımlılığın atlanması çalışma zamanında içe aktarma hatalarına neden olur.
Paket türüne göre yükleme yolunu seçin:
| Paket türü | Yükleme yöntemi | Notlar |
|---|---|---|
| Önceden yüklenmiş | Gerek yok | Yalnızca NumPy |
| Saf Python (derlenmiş kod yok, dosya işlemi yok) | .whl dosya kaynağı olarak yükleyin |
python-dateutil, pytz, six gibi paketler için çalışır. MaxCompute'in sonraki sürümleri dosya işlemleri içeren paketleri de destekler. |
| İkili (derlenmiş C uzantıları) | Arşiv kaynağı olarak .zip arşivi yükleyin, yalıtımı etkinleştirin |
cp27-cp27m-manylinux1_x86_64 platform etiketi gerekir; Linux üzerinde derleyin |
Saf Python paketleri
Varsayılan olarak PyODPS, saf Python kodu içeren ancak dosya işlemi olmayan üçüncü taraf kitaplıkları destekler. Aşağıdaki örnek python-dateutil ve bunun bağımlılığı olan six'i yükler.
Adım 1: Paketi ve bağımlılıklarını indirin. Paketler Linux için oluşturulmalıdır.
$ pip download python-dateutil -d /to/path/
Bu işlem six-1.10.0-py2.py3-none-any.whl ve python_dateutil-2.5.3-py2.py3-none-any.whl dosyalarını indirir.
Adım 2: Her iki dosyayı da create_resource kullanarak kaynak olarak yükleyin.
# Make sure that file name extensions are correct.
>>> odps.create_resource('six.whl', 'file', file_obj=open('six-1.10.0-py2.py3-none-any.whl', 'rb'))
>>> odps.create_resource('python_dateutil.whl', 'file', file_obj=open('python_dateutil-2.5.3-py2.py3-none-any.whl', 'rb'))
Adım 3: Kitaplıkları kullanın. Bunları options.df.libraries aracılığıyla genel olarak veya libraries parametresiyle yürütme başına belirtin.
# Global configuration (applies to all subsequent DataFrame operations in this session)
>>> from odps import options
>>> def get_year(t):
>>> from dateutil.parser import parse
>>> return parse(t).strftime('%Y')
>>> options.df.libraries = ['six.whl', 'python_dateutil.whl']
>>> df.datestr.map(get_year)
datestr
0 2016
1 2015
# Per-execution configuration (applies only to this call)
>>> def get_year(t):
>>> from dateutil.parser import parse
>>> return parse(t).strftime('%Y')
>>> df.datestr.map(get_year).execute(libraries=['six.whl', 'python_dateutil.whl'])
datestr
0 2016
1 2015
İkili paketler (derlenmiş kod içeren)
Derlenmiş C uzantıları içeren paketler (SciPy veya pandas gibi) ek adımlar gerektirir:
.whldosyasıcp27-cp27m-manylinux1_x86_64platform etiketini kullanmalıdır.Dosyayı,
.whluzantısı.zipolarak yeniden adlandırılarak arşiv kaynağı olarak yükleyin.odps.isolation.session.enabledeğeriniTrueolarak ayarlayın veya proje ayarlarınızdaisolationözelliğini etkinleştirin.
# Upload the binary package as an archive with the .zip extension.
>>> odps.create_resource('scipy.zip', 'archive', file_obj=open('scipy-0.19.0-cp27-cp27m-manylinux1_x86_64.whl', 'rb'))
# If isolation is already enabled in your project, the following option is optional.
>>> options.sql.settings = {'odps.isolation.session.enable': True}
>>> def psi(value):
>>> # Import the third-party library inside the function to avoid errors
>>> # caused by structural differences between operating systems.
>>> from scipy.special import psi
>>> return float(psi(value))
>>> df.float_col.map(psi).execute(libraries=['scipy.zip'])
Kaynaktan ikili bir paket oluşturmak için aşağıdaki komutu bir Linux kabuğunda çalıştırın. macOS veya Windows üzerinde oluşturulan Wheel dosyaları MaxCompute ile uyumlu değildir.
python setup.py bdist_wheel
MaxCompute konsolu üzerinden yükleme
PyODPS API'sine alternatif olarak, paketleri MaxCompute konsolunda add archive kullanarak yükleyin.
Adım 1: Her bağımlılık için doğru paket dosyasını belirleyin.
Çoğu paket birden çok platform için .whl dosyaları sağlar. İkili paketler için adında cp27-cp27m-manylinux1_x86_64 geçen dosyayı bulun. Saf Python paketleri için herhangi bir py2.py3-none-any wheel çalışır.
Adım 2: Gerekli tüm bağımlılıkları doğrulayın. Aşağıdaki tabloda yaygın paketlerin bağımlılıkları listelenmiştir.
| Paket | Bağımlılıklar |
|---|---|
| pandas | NumPy, python-dateutil, pytz, six |
| SciPy | NumPy |
| scikit-learn | NumPy, SciPy |
NumPy zaten önceden yüklenmiştir. Yalnızca python-dateutil, pytz, pandas, SciPy, scikit-learn ve six'i yükleyin.
Adım 3: Paket dosyalarını indirin. Aşağıdaki tabloda her paket için indirilecek belirli dosyalar listelenmiştir.
| Paket | İndirilecek dosya | Kaynak adını yükle |
|---|---|---|
| python-dateutil | python-dateutil-2.6.0.zip | python-dateutil.zip |
| pytz | pytz-2017.2.zip | pytz.zip |
| six | six-1.11.0.tar.gz | six.tar.gz |
| pandas | pandas-0.20.2-cp27-cp27m-manylinux1_x86_64.zip | pandas.zip |
| SciPy | scipy-0.19.0-cp27-cp27m-manylinux1_x86_64.zip | scipy.zip |
| scikit-learn | scikit_learn-0.18.1-cp27-cp27m-manylinux1_x86_64.zip | sklearn.zip |
Adım 4: Her dosyayı yükleyin. İkili paketler (pandas, SciPy, scikit-learn) için yüklemeden önce .whl uzantısını .zip olarak yeniden adlandırın.
add archive python-dateutil.zip;
add archive pandas.zip;
Yürütme için kitaplıkları belirtme
Oturum için genel olarak kitaplıkları ayarlamak üzere options.df.libraries kullanın veya bunu tek bir çağrıyla sınırlamak için libraries parametresini doğrudan bir yürütme yöntemine iletin.
# Global: applies to all subsequent DataFrame operations in this session
>>> from odps import options
>>> options.df.libraries = ['six.whl', 'python_dateutil.whl']
# Local: applies only to this execution call
>>> df.apply(my_func, axis=1).to_pandas(libraries=['six.whl', 'python_dateutil.whl'])