Sparkでデータを読み込んで処理するために使用しているPythonクラスがあります。行う必要があるさまざまなことの中で、Sparkデータフレームのさまざまな列から派生したダミー変数のリストを生成しています。私の問題は、必要なことを達成するためにユーザー定義関数を適切に定義する方法がわからないことです。
I doは、現在、基礎となるデータフレームRDDにマッピングされたときに問題の半分を解決するメソッドを持っています(これはより大きな_data_processor
_クラスのメソッドであることを思い出してください):
_def build_feature_arr(self,table):
# this dict has keys for all the columns for which I need dummy coding
categories = {'gender':['1','2'], ..}
# there are actually two differnt dataframes that I need to do this for, this just specifies which I'm looking at, and grabs the relevant features from a config file
if table == 'users':
iter_over = self.config.dyadic_features_to_include
Elif table == 'activty':
iter_over = self.config.user_features_to_include
def _build_feature_arr(row):
result = []
row = row.asDict()
for col in iter_over:
column_value = str(row[col]).lower()
cats = categories[col]
result += [1 if column_value and cat==column_value else 0 for cat in cats]
return result
return _build_feature_arr
_
基本的に、これは、指定されたデータフレームに対して、指定された列のカテゴリ変数値を取得し、これらの新しいダミー変数の値のリストを返します。つまり、次のコードを意味します。
_data = data_processor(init_args)
result = data.user_data.rdd.map(self.build_feature_arr('users'))
_
次のようなものを返します。
_In [39]: result.take(10)
Out[39]:
[[1, 0, 0, 0, 1, 0],
[1, 0, 0, 1, 0, 0],
[1, 0, 0, 0, 0, 0],
[1, 0, 1, 0, 0, 0],
[1, 0, 0, 1, 0, 0],
[1, 0, 0, 1, 0, 0],
[0, 1, 1, 0, 0, 0],
[1, 0, 1, 1, 0, 0],
[1, 0, 0, 1, 0, 0],
[1, 0, 0, 0, 0, 1]]
_
これは、私が望むダミー変数のリストを生成するという点ではまさに私が望むものですが、ここに私の質問があります:(a)Spark SQLクエリで使用できる同様の機能を持つUDFを作成するにはどうすればよいですか(または他の方法、私は思う)、または(b)上記のマップから得られたRDDを取得し、user_dataデータフレームに新しい列として追加しますか?
いずれにせよ、私がする必要があるのは、user_dataからの列を含む新しいデータフレームと、上記の関数(または機能的に同等のもの)の出力を含む新しい列(_feature_array
_と呼びましょう)を生成することです。
Spark> = 2.3、> = 3.0
Spark 2.3 OneHotEncoder
はOneHotEncoderEstimator
の代わりに廃止されています。最近のリリースを使用する場合は、encoder
コードを変更してください
from pyspark.ml.feature import OneHotEncoderEstimator
encoder = OneHotEncoderEstimator(
inputCols=["gender_numeric"],
outputCols=["gender_vector"]
)
Spark 3.0では、このバリアントはOneHotEncoder
に名前が変更されました。
from pyspark.ml.feature import OneHotEncoder
encoder = OneHotEncoder(
inputCols=["gender_numeric"],
outputCols=["gender_vector"]
)
さらに、StringIndexer
が拡張され、複数の入力列をサポートします。
StringIndexer(inputCols=["gender"], outputCols=["gender_numeric"])
スパーク<2.3
さて、UDFを書くことはできますが、なぜそうするのでしょうか?このカテゴリのタスクを処理するために設計されたツールは、すでにかなりあります。
from pyspark.sql import Row
from pyspark.ml.linalg import DenseVector
row = Row("gender", "foo", "bar")
df = sc.parallelize([
row("0", 3.0, DenseVector([0, 2.1, 1.0])),
row("1", 1.0, DenseVector([0, 1.1, 1.0])),
row("1", -1.0, DenseVector([0, 3.4, 0.0])),
row("0", -3.0, DenseVector([0, 4.1, 0.0]))
]).toDF()
まず、StringIndexer
。
from pyspark.ml.feature import StringIndexer
indexer = StringIndexer(inputCol="gender", outputCol="gender_numeric").fit(df)
indexed_df = indexer.transform(df)
indexed_df.drop("bar").show()
## +------+----+--------------+
## |gender| foo|gender_numeric|
## +------+----+--------------+
## | 0| 3.0| 0.0|
## | 1| 1.0| 1.0|
## | 1|-1.0| 1.0|
## | 0|-3.0| 0.0|
## +------+----+--------------+
次のOneHotEncoder
:
from pyspark.ml.feature import OneHotEncoder
encoder = OneHotEncoder(inputCol="gender_numeric", outputCol="gender_vector")
encoded_df = encoder.transform(indexed_df)
encoded_df.drop("bar").show()
## +------+----+--------------+-------------+
## |gender| foo|gender_numeric|gender_vector|
## +------+----+--------------+-------------+
## | 0| 3.0| 0.0|(1,[0],[1.0])|
## | 1| 1.0| 1.0| (1,[],[])|
## | 1|-1.0| 1.0| (1,[],[])|
## | 0|-3.0| 0.0|(1,[0],[1.0])|
## +------+----+--------------+-------------+
VectorAssembler
:
from pyspark.ml.feature import VectorAssembler
assembler = VectorAssembler(
inputCols=["gender_vector", "bar", "foo"], outputCol="features")
encoded_df_with_indexed_bar = (vector_indexer
.fit(encoded_df)
.transform(encoded_df))
final_df = assembler.transform(encoded_df)
bar
にカテゴリ変数が含まれている場合、VectorIndexer
を使用して必要なメタデータを設定できます。
from pyspark.ml.feature import VectorIndexer
vector_indexer = VectorIndexer(inputCol="bar", outputCol="bar_indexed")
しかし、ここではそうではありません。
最後に、パイプラインを使用してすべてをラップできます。
from pyspark.ml import Pipeline
pipeline = Pipeline(stages=[indexer, encoder, vector_indexer, assembler])
model = pipeline.fit(df)
transformed = model.transform(df)
間違いなく、すべてをゼロから書くよりもはるかに堅牢でクリーンなアプローチです。特に、異なるデータセット間で一貫したエンコードが必要な場合、いくつかの注意事項があります。 StringIndexer
およびVectorIndexer
の公式ドキュメントで詳細を読むことができます。
比較可能な出力を取得する別の方法は、RFormula
which :
RFormula
は、フィーチャのベクトル列とラベルのdouble列またはstring列を生成します。式がRで線形回帰に使用される場合と同様に、文字列入力列はワンホットエンコードされ、数値列は倍精度にキャストされます。ラベル列が文字列型の場合、最初にStringIndexer
でdoubleに変換されます。ラベル列がDataFrameに存在しない場合、出力ラベル列は式で指定された応答変数から作成されます。
from pyspark.ml.feature import RFormula
rf = RFormula(formula="~ gender + bar + foo - 1")
final_df_rf = rf.fit(df).transform(df)
ご覧のとおり、はるかに簡潔ですが、作成が難しくなるとカスタマイズがあまりできません。それでも、このような単純なパイプラインの結果は同じになります。
final_df_rf.select("features").show(4, False)
## +----------------------+
## |features |
## +----------------------+
## |[1.0,0.0,2.1,1.0,3.0] |
## |[0.0,0.0,1.1,1.0,1.0] |
## |(5,[2,4],[3.4,-1.0]) |
## |[1.0,0.0,4.1,0.0,-3.0]|
## +----------------------+
final_df.select("features").show(4, False)
## +----------------------+
## |features |
## +----------------------+
## |[1.0,0.0,2.1,1.0,3.0] |
## |[0.0,0.0,1.1,1.0,1.0] |
## |(5,[2,4],[3.4,-1.0]) |
## |[1.0,0.0,4.1,0.0,-3.0]|
## +----------------------+
あなたの質問について:
Spark SQLクエリ(または他の方法で、私が推測する)
それは他のようなUDFです。サポートされているタイプを使用し、それ以上はすべて正常に機能することを確認してください。
上記のマップから得られたRDDを取得し、user_dataデータフレームに新しい列として追加しますか?
from pyspark.ml.linalg import VectorUDT
from pyspark.sql.types import StructType, StructField
schema = StructType([StructField("features", VectorUDT(), True)])
row = Row("features")
result.map(lambda x: row(DenseVector(x))).toDF(schema)
注:
Spark 1.x replace pyspark.ml.linalg
with pyspark.mllib.linalg
。