注: 以下の翻訳の正確性は検証されていません。AIPを利用して英語版の原文から機械的に翻訳されたものです。
PySpark は、Apache Spark バックエンドとやり取りしてデータを高速に処理できるラッパー言語です。Spark は、サーバーの分散ネットワーク上で非常に大きなデータセットを処理でき、正しく使用するとパフォーマンスと信頼性が大幅に向上します。ただし、PySpark の構文は Spark の JVM 由来の設計を引き継いでおり、なじみのないコードパターンを採用しているため、経験豊富な Python 開発者にとっても難しい面があります。
この PySpark コードスタイルガイドは、特定の方針に基づき、これまでに扱った PySpark リポジトリで最も頻繁に見られるトピックをもとに、よくある状況と、それに対応するベストプラクティスを紹介します。
一貫したコードスタイルを徹底するため、主要なリポジトリではそれぞれ、同じ設定で Pylint ↗ を有効にする必要があります。このドキュメントに記載されたルールに合わせるため、Pylint に追加できる PySpark 固有のチェッカー ↗を提供しています。Python リポジトリ向けの組み込み Pylint プラグインの詳細については、スタイルチェックの有効化に関するドキュメントを参照してください。
PySpark 固有の事項に加えて、クリーンコードの一般的なプラクティスも PySpark リポジトリでは重要です。Google の PyGuide ↗ は、よい出発点になります。
Copied!1 2 3 4 5# 悪い例 df = df.select(F.lower(df1.colA), F.upper(df2.colB)) # 良い例 df = df.select(F.lower(F.col("colA")), F.upper(F.col("colB")))
推奨する方法は、より複雑で長く、余計な記述が多いように見えるかもしれません。その見方は正しく、実際には F.col() をまったく使わずに済むのが最善です。ただし、F.col() の使用、または代替となる明示的な選択が避けられない場合もあります。それでも、1つ目の例より2つ目の例を優先するのには、十分な理由があります。
1つ目のケースのように列を明示的に指定すると、データフレーム名とスキーマの両方がデータフレーム変数に明示的に結び付けられます。そのため、df1 を削除したり名前を変更したりすると、参照 df1.colA が機能しなくなります。
一方、F.col("colA") は常に、操作対象のデータフレームにある “colA” という名前の列を参照します。この例では、そのデータフレームの名前がたまたま df です。ほかのデータフレームの状態をまったく追跡する必要がないため、コードの処理がより局所的になり、デバッグが難しいことの多い「離れた箇所での不可解なインタラクション」が起こりにくくなります。
1つ目のケースを避けるべき理由は、ほかにもあります。
df1["colA"] を書く手間は F.col(“colA”) と変わりません。F.col("prod_status") == 'Delivered' のような抽象的な式を変数に代入すれば、複数のデータフレームで再利用できますが、df.prod_status == 'Delivered' は常に df に結び付いています。幸い、通常は F.col() を使った複雑な式は必要ありません。例外は F.lower、F.upper、…およびこちら ↗のみです。
状況によっては、複数のデータフレームの列にアクセスでき、列名が重複していることがあります。よくある例は、df.join(df2, on=(df.key == df2.key), how='left') のような照合式です。
このような場合は、データフレームを使って列を直接参照してもかまいません。また、データフレームのエイリアスを使って結合の曖昧さを解消することもできます(詳細は、このガイドの結合セクションを参照してください)。
DataFrame オブジェクトではなく Column オブジェクトを操作するトランスフォームのロジックを記述するときは、DataFrame オブジェクトを繰り返し操作するのではなく、Column オブジェクトを使います。この方法により、バグを減らし、コードを再利用できるようになります。
Column オブジェクトを定義するトランスフォームの先頭で、入力 DataFrame から必要な Column オブジェクトを定義します。DataFrame メソッドを繰り返し呼び出すのではなく、これらのオブジェクトにロジックを適用します。
Copied!1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17# 悪い例 - DataFrame を繰り返し操作しています df = df.withColumn('status_lower', F.lower(df.status)) df = df.withColumn('is_active', df.status_lower == 'active') df = df.withColumn('category', F.when(df.is_active, 'A').otherwise('B')) # 良い例 - 列を抽出して操作し、最後に追加します status_col = F.col('status') status_lower = F.lower(status_col) is_active = status_lower == 'active' category = F.when(is_active, F.lit('A')).otherwise(F.lit('B')) df = df.select( '*', status_lower.alias('status_lower'), is_active.alias('is_active'), category.alias('category') )
DataFrame 全体を受け取り、返すヘルパー関数を書くのではなく、Column オブジェクトを入力として受け取り、Column オブジェクトを返す関数を書きます。
Copied!1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16# 悪い例 - ヘルパー関数が DataFrame を操作しています def add_derived_columns(df): df = df.withColumn('full_name', F.concat(df.first_name, F.lit(' '), df.last_name)) df = df.withColumn('name_length', F.length(df.full_name)) return df # 良い例 - ヘルパー関数が Column を操作しています def make_full_name(first_name_col, last_name_col): return F.concat(first_name_col, F.lit(' '), last_name_col) def name_length(name_col): return F.length(name_col) # 使用方法 full_name = make_full_name(F.col('first_name'), F.col('last_name')) df = df.select('*', full_name.alias('full_name'), name_length(full_name).alias('name_length'))
トランスフォームの処理中に中間列を DataFrame に追加することは避けます。計算済みの Column オブジェクトを DataFrame に追加して最終的な名前を付けるのは、できる限り最後のタイミング、理想的には出力データセットを書き込む直前にします。
DataFrame 上の既存の列を同じ名前で上書きしないでください。これにより、列の意図しない再利用やシャドーイングによる混乱や、気付きにくいバグを減らせます。
Copied!1 2 3 4 5 6 7 8# 悪い例 - 'amount' 列を上書きしています df = df.withColumn('amount', F.col('amount') * 100) df = df.withColumn('amount', F.round(F.col('amount'), 2)) # これはどの 'amount' ですか? # 良い例 - 異なる名前を使うか、Column オブジェクトを操作します amount_cents = F.col('amount') * 100 amount_rounded = F.round(amount_cents, 2) df = df.select('*', amount_rounded.alias('amount_cents'))
列を変換する関数を書くときは、列名を文字列として渡すのではなく、必ず実際の Column オブジェクトを渡します。
Copied!1 2 3 4 5 6 7 8 9 10# 悪い例 - 列名を文字列として渡しています def normalize_column(df, column_name): return df.withColumn(column_name, F.lower(F.col(column_name))) # 良い例 - Column オブジェクトを渡しています def normalize(col): return F.lower(col) # 使用方法 df = df.select('*', normalize(F.col('status')).alias('status_normalized'))
これらのルールに従うと、DataFrame レベルの操作を最上位に保ち、再利用可能で組み合わせられるロジックを Column レベルの関数として実装するスタイルにつながります。
一般に、Spark では for ループは非効率です。大まかに言えば、Spark は遅延評価を行い、一度に1つの for ループしか処理しないためです。ループのすべての部分を一度に処理できる場合、これによって実行時間が長くなり、ドライバーのメモリー不足エラー(OOM)が発生することがあります。データセットのすべての列名を大文字から小文字に変更する場合は、以下の1つ目の例(# bad と記載)の代わりに、リスト内包表記を使うことを推奨します(# good と記載された2つ目の例のように)。
Copied!1 2 3# 悪い例 for colm in df.columns: df = df.withColumnRenamed(colm, colm.lower())
Copied!1 2 3 4# 良い例 df = df.select( *[F.col(colm).alias(colm.lower()) for colm in df.columns] )
# good の例のようにリスト内包表記を使うと、同じ目的の結果を得ながら、前述したパフォーマンスの低下やクエリプランの問題を回避できます。
.filter() や F.when() の中に記述されることの多い論理演算は、読みやすくする必要があります。関数のチェーンと同じルールを適用し、同じコードブロック内の論理式を最大3個の式に抑えます。それより長くなる場合、多くはコードを簡素化したり、外に切り出したりできる兆候です。複雑な論理演算を変数や関数に切り出すと、コードを読みやすく、その動作を理解しやすくなり、バグの削減にもつながります。
Copied!1 2 3 4# 悪い例 F.when( (df.prod_status == 'Delivered') | (((F.datediff(df.deliveryDate_actual, df.current_date) < 0) & ((df.currentRegistration.rlike('.+')) | ((F.datediff(df.deliveryDate_actual, df.current_date) < 0) & (df.originalOperator.rlike('.+') | df.currentOperator.rlike('.+')))))), 'In Service')
上記のコードは、さまざまな方法で簡素化できます。まず、ロジックの各ステップを、名前を付けた少数の変数にまとめることに注目します。PySpark では、式を丸括弧で囲む必要があります。これが論理演算をグループ化するための丸括弧と混在すると、読みやすさが損なわれることがあります。たとえば、上記のコードには冗長な (F.datediff(df.deliveryDate_actual, df.current_date) < 0) がありますが、非常に見つけにくいため、元の作成者は気付きませんでした。
Copied!1 2 3 4 5 6 7# より良い例 has_operator = (df.originalOperator.rlike('.+') | df.currentOperator.rlike('.+')) delivery_date_passed = (F.datediff(df.deliveryDate_actual, df.current_date) < 0) has_registration = (df.currentRegistration.rlike('.+')) is_delivered = (df.prod_status == 'Delivered') F.when(is_delivered | (delivery_date_passed & (has_registration | has_operator)), 'In Service')
上記の例は読みやすくなり、冗長な式も取り除かれています。演算の数を減らすことで、さらに改善できます。
Copied!1 2 3 4 5 6 7 8# 良い例 has_operator = (df.originalOperator.rlike('.+') | df.currentOperator.rlike('.+')) delivery_date_passed = (F.datediff(df.deliveryDate_actual, df.current_date) < 0) has_registration = (df.currentRegistration.rlike('.+')) is_delivered = (df.prod_status == 'Delivered') is_active = (has_registration | has_operator) F.when(is_delivered | (delivery_date_passed & is_active), 'In Service')
F.when の式が簡潔で読みやすくなり、このコードをレビューする人なら誰でも、意図した動作を理解できるようになった点に注目してください。読み手は、個々の式にエラーがあると疑う場合にのみ、その式を確認すれば済みます。また、コードに単体テストがあり、ロジックの各部分を関数として抽象化したい場合にも、それぞれの部分をテストしやすくなります。
最後の例には、まだコードの重複が残っています。その重複を取り除く方法は、読み手の演習として残しておきます。
select 文を使用してスキーマ契約を指定するPySpark トランスフォームの冒頭、または値を返す前に select を実行することは、優れたプラクティスとされています。この select 文は、入力と出力で期待されるデータフレームのスキーマについて、コードの読み手とコードの両方に対する契約を指定します。
select は、トランスフォームの次のステップでデータフレームを利用できるように準備するクリーニング操作と捉える必要があります。
select 文は常にできるだけシンプルに保つことを目指します。一般的な SQL の慣例に従い、選択する列ごとに spark.sql.function の関数を最大 1個まで使用し、必要に応じて .alias() で意味のある名前を付けることができます。ただし、このような使い方は最小限にとどめてください。同じ select 内でこうした使用が 3個を超える場合は、clean_<dataframe name>() のような別の関数にリファクタリングして、操作をカプセル化します。
複数のデータフレームを含む式や、.when() のような条件付き操作は、select 内で決して使用しないでください。
Copied!1 2 3 4 5 6 7 8 9 10# 悪い例 aircraft = aircraft.select( 'aircraft_id', 'aircraft_msn', F.col('aircraft_registration').alias('registration'), 'aircraft_type', F.col('number_of_economy_seats').cast('long'), 'operator_code', F.col('number_of_business_seats').cast('long'), )
同じ種類の操作をまとめます。個々の列はすべて先頭に列挙し、spark.sql.function の関数呼び出しは別々の行に記述します。
Copied!1 2 3 4 5 6 7# 良い例 aircraft = aircraft.select( 'aircraft_id', 'aircraft_msn', 'aircraft_type', 'operator_code', F.col('aircraft_registration').alias('registration'), F.col('number_of_economy_seats').cast('long'), F.col('number_of_business_seats').cast('long'), )
select() 文は、その性質上、データフレームのスキーマを再定義するため、新旧の列の追加や除外、および既存の列の再定義を自然にサポートします。こうした操作をすべて1つの文にまとめると、最終的なスキーマを特定しやすくなり、コードの可読性が向上します。また、コードも少し簡潔になります。
withColumnRenamed() を呼び出す代わりに、エイリアスを使用します。
Copied!1 2 3 4 5# 悪い例 df.select('key', 'comments').withColumnRenamed('comments', 'num_comments') # 良い例 df.select('key', F.col('comments').alias('num_comments'))
withColumn() を使用してデータ型を再定義する代わりに、select 内でキャストします。
Copied!1 2 3 4 5# 悪い例 df.select('comments').withColumn('comments', F.col('comments').cast('double')) # 良い例 df.select(F.col('comments').cast('double'))
ただし、シンプルに保ちます。
Copied!1 2 3 4 5 6 7 8 9 10 11# 悪い例 df.select( ((F.coalesce(F.unix_timestamp('closed_at'), F.unix_timestamp()) - F.unix_timestamp('created_at')) / 86400).alias('days_open') ) # 良い例 df.withColumn( 'days_open', (F.coalesce(F.unix_timestamp('closed_at'), F.unix_timestamp()) - F.unix_timestamp('created_at')) / 86400 )
使用しない列を select 文に含めることは避け、代わりに列を明示的に選択します。これは .drop() を使用するよりも推奨される方法です。スキーマが変更されても、予期しない列によってデータフレームが肥大化することを防げるためです。ただし、列の削除がどのような場合でも推奨されないというわけではありません。たとえば、結合では冗長な列が追加されることが多いため、結合後に列を削除するのは一般的に適切です。
最後に、select 文で新しい列を追加する代わりに、.withColumn() を使用することを推奨します。
スキーマを満たすために空の列を追加する必要がある場合は、必ず F.lit(None) を使用してその列に値を設定します。空の文字列や、空の値を示すほかの文字列(NA など)は決して使用しないでください。
この方法は意味的に正しいだけでなく、空の文字列、null、'NA' などをそれぞれ確認する必要がなく、isNull のようなユーティリティを引き続き使用できるという実用上の理由もあります。
Copied!1 2 3 4 5 6 7 8# 悪い例 df = df.withColumn('foo', F.lit('')) # 悪い例 df = df.withColumn('foo', F.lit('NA')) # 良い例 - `None` にはデータ型がないため、キャストが必要なことに注意してください。想定される用途に基づいて適切なデータ型を選択します。 df = df.withColumn('foo', F.lit(None).cast('string'))
コメントはコードに関する有用なインサイトを提供できますが、多くの場合、コードをリファクタリングして可読性を向上させる方が価値があります。コードはそれ自体で読みやすいものであるべきです。コメントを使ってロジックをステップごとに説明している場合は、コードをリファクタリングする必要があります。
Copied!1 2 3 4 5 6# 悪い例 # タイムスタンプ列をキャストします cols = ["start_date", "delivery_date"] for c in cols: df = df.withColumn(c, F.from_unixtime(df[c] / 1000).cast(TimestampType()))
上の例では、それらの列がタイムスタンプにキャストされていることがわかります。このコメントにはあまり価値がありません。さらに、詳しいコメントであっても、コードにすでに含まれている情報を説明するだけなら、役に立たない場合があります。たとえば、次のようなコメントです。
Copied!1 2 3 4 5 6# 悪い例 # 各列を順に処理し、ミリ秒なので1000を除去してタイムスタンプにキャストします cols = ["start_date", "delivery_date"] for c in cols: df = df.withColumn(c, F.from_unixtime(df[c] / 1000).cast(TimestampType()))
記述したロジックを説明するだけのコメントを残すのではなく、コードを書く際の判断の「理由」を説明し、背景を伝えるコメントを残すことを目指します。これは PySpark では特に重要です。読み手はコードを理解できても、PySpark トランスフォームに入力されるデータの背景を知らないことが多いためです。わずかなロジックであっても、正しい動作を理解するためにデータを何時間も調べた結果かもしれません。そのような場合、根拠を説明するコメントは特に価値があります。
Copied!1 2 3 4 5 6 7 8# 良い例 # このデータセットの利用者は日付ではなくタイムスタンプを想定しており、 # 元のデータソースがこれらをミリ秒として保存しているため、時間を1000で調整する必要があります。 # ドキュメントには実際は日付であると記載されているものの、ミリ秒で保存されています。 cols = ["start_date", "delivery_date"] for c in cols: df = df.withColumn(c, F.from_unixtime(df[c] / 1000).cast(TimestampType()))
PySpark のネイティブ関数で同じロジックを表現できる場合は、UDF の使用を避けてください。UDF は大幅に遅いためです。使用しなければならない場合は、実装のガイダンスと例についてユーザー定義関数を参照してください。
次のような、Spark ドライバーにデータを収集する関数の使用は常に避けてください。
DataFrame.collect()DataFrame.first()DataFrame.head(...)DataFrame.take(...)DataFrame.show(...)これらの関数を使用すると、Spark のような分散フレームワークの利点が失われ、パフォーマンスの低下やメモリー不足エラーにつながります。これらの関数の代わりに、次を使用することを強く推奨します。
結合には注意が必要です。左結合を実行し、右側にあるキーに対して複数の一致がある場合、その行は一致した数だけ複製されます。これは「結合の爆発」と呼ばれ、データセットのサイズを大幅に肥大化させる可能性があります。行が増えることを想定している場合を除き、結合に使用するキーが一意であることを、常に前提に立ち返って再確認してください。
不適切な結合は、デバッグが難しい多くの問題の原因になります。デフォルト値(inner)を使用する場合でも how を明示的に指定するなど、役立つ方法があります。
Copied!1 2 3 4 5 6 7 8# 悪い例 flights = flights.join(aircraft, 'aircraft_id') # これも悪い例 flights = flights.join(aircraft, 'aircraft_id', 'inner') # 良い例 flights = flights.join(aircraft, 'aircraft_id', how='inner')
また、right 結合も避けてください。right 結合を使用しようとしている場合は、データフレームの順序を入れ替えて、代わりに left 結合を使用します。操作対象のデータフレームが結合の中心となるため、こちらの方が直感的です。
Copied!1 2 3 4 5# 悪い例 flights = aircraft.join(flights, 'aircraft_id', how='right') # 良い例 flights = flights.join(aircraft, 'aircraft_id', how='left')
データフレームを結合する際は、出力で列を重複させる式を使用しないでください。
Copied!1 2 3 4 5# 悪い例 - 出力で aircraft_id 列が重複します output = flights.join(aircraft, flights.aircraft_id == aircraft.aircraft_id, how='inner') # 良い例 output = flights.join(aircraft, 'aircraft_id', how='inner')
名前の衝突を避けるために、すべての列の名前を変更することは避けてください。代わりに、データフレーム全体にエイリアスを付け、そのエイリアスを使用して最終的に必要な列を選択できます。
Copied!1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25# 悪い例 columns = ["start_time", "end_time", "idle_time", "total_time"] for col in columns: flights = flights.withColumnRenamed(col, 'flights_' + col) parking = parking.withColumnRenamed(col, 'parking_' + col) flights = flights.join(parking, on="flight_code", how="left") flights = flights.select( F.col("flights_start_time").alias("flight_start_time"), F.col("flights_end_time").alias("flight_end_time"), F.col("parking_total_time").alias("client_parking_total_time") ) # 良い例 flights = flights.alias("flights") parking = parking.alias("parking") flights = flights.join(parking, on="flight_code", how="left") flights = flights.select( F.col("flights.start_time").alias("flight_start_time"), F.col("flights.end_time").alias("flight_end_time"), F.col("parking.total_time").alias("client_parking_total_time") )
ただし、このような場合には、次の点に注意してください。
結合について最後に、.dropDuplicates() や .distinct() を安易な対処として使用しないでください。予期しない重複行が見つかった場合、その重複行が現れる根本的な理由がほぼ必ずあります。.dropDuplicates() を追加しても、この問題を覆い隠すだけであり、実行時間のオーバーヘッドも増加します。
式のチェーンには議論の余地がありますが、使用にあたっては一定の制限を設けることを推奨します。この推奨の根拠については、このセクションの結論を参照してください。
種類の異なる式をチェーンして、複数行にわたる式にすることは避けてください。特に、動作やコンテキストが異なる場合は注意が必要です。たとえば、列の作成や結合を、選択や絞り込みと混在させる場合が該当します。
Copied!1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30# 悪い例 df = ( df .select("a", "b", "c", "key") .filter(df.a == "truthiness") .withColumn("boverc", df.b / df.c) .join(df2, "key", how="inner") .join(df3, "key", how="left") .drop('c') ) # より良い例(ステップに分けます) # 第1段階:必要なデータを選択して絞り込みます # 第2段階:必要な列を作成します # 第3段階:ほかの DataFrame と結合します df = ( df .select("a", "b", "c", "key") .filter(df.a == "truthiness") ) df = df.withColumn("boverc", df.b / df.c) df = ( df .join(df2, "key", how="inner") .join(df3, "key", how="left") .drop('c') )
式のグループをそれぞれ独立した論理的なコードブロックに分けると、読みやすくなり、関連するロジックを見つけやすくなります。
たとえば、以下のコードを読む人は、データフレームが代入されている df = df... の箇所に目を向ける可能性が高いでしょう。
Copied!1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16# 悪い例 df = ( df .select('foo', 'bar', 'foobar', 'abc') .filter(df.abc == 123) .join(another_table, 'some_field') ) # より良い例 df = ( df .select('foo', 'bar', 'foobar', 'abc') .filter(F.col('abc') == 123) ) df = df.join(another_table, 'some_field', how='inner')
式をチェーンすることには正当な理由もあります。通常、こうしたチェーンは不可分なロジックのステップを表しており、問題ありません。コードの読みやすさを保つため、同じブロック内でチェーンする式の数に上限を設けるルールを適用します。チェーンは最大3〜5文にとどめることを推奨します。
チェーンが長くなっている場合や、変数のサイズが原因で問題が生じている場合は、ロジックを別の関数に抽出することを検討してください。
Copied!1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29# 悪い例 customers_with_shipping_address = ( customers_with_shipping_address .select("a", "b", "c", "key") .filter(F.col("a") == "truthiness") .withColumn("boverc", F.col("b") / F.col("c")) .join(df2, "key", how="inner") ) # これも悪い例 customers_with_shipping_address = customers_with_shipping_address.select("a", "b", "c", "key") customers_with_shipping_address = customers_with_shipping_address.filter(df.a == "truthiness") customers_with_shipping_address = customers_with_shipping_address.withColumn("boverc", F.col("b") / F.col("c")) customers_with_shipping_address = customers_with_shipping_address.join(df2, "key", how="inner") # より良い例 def join_customers_with_shipping_address(customers, df_to_join): customers = ( customers .select("a", "b", "c", "key") .filter(df.a == "truthiness") ) customers = customers.withColumn("boverc", F.col("b") / F.col("c")) customers = customers.join(df_to_join, "key", how="inner") return customers
実際、3文を超えるチェーンは、すでにカプセル化された独立したロジックのブロックであるため、適切な名前を付けた別の関数に切り出す有力な候補です。
チェーンにこうした制限を設ける理由はいくつかあります。
式をチェーンできるのは、PySpark が、JVM 言語に由来する Spark をもとに開発されたためです。そのため、いくつかの設計パターン、具体的にはチェーン可能性が引き継がれました。
ただし、Python は複数行の式を自然には扱えず、明示的な改行を指定するか、式を括弧で囲むかのどちらかしか方法がありません。明示的な改行を指定する必要があるのは、チェーンがルートノードで行われる場合だけです。以下に例を示します。
Copied!1 2 3 4 5 6 7 8 9# `\` が必要です df = df.filter(F.col('event') == 'executing')\ .filter(F.col('has_tests') == True)\ .drop('has_tests') # ルートノードの下のチェーンには `\` は不要です df = df.withColumn('safety', F.when(F.col('has_tests') == True, 'is safe') .when(F.col('has_executed') == True, 'no tests but runs') .otherwise('not safe'))
一貫性を保つため、式全体を括弧で囲み、\ の使用は避けてください。
Copied!1 2 3 4 5 6 7 8 9 10 11 12# 悪い例 df = df.filter(F.col('event') == 'executing')\ .filter(F.col('has_tests') == True)\ .drop('has_tests') # 良い例 df = ( df .filter(F.col('event') == 'executing') .filter(F.col('has_tests') == True) .drop('has_tests') )
.otherwise(value) を使用することは避けてください。キーのリストを値のリストに対応付ける際に、不明なキーがいくつも現れた場合、otherwise を使用すると、それらがすべて同じ値に置き換えられ、見えなくなります。types と functions に対する from pyspark.sql import types as T, functions as F があります。