Spark Dataframe розрізняє стовпці з дубльованою назвою


82

Отож, як я знаю у Spark Dataframe, для кількох стовпців може бути однакове ім’я, як показано на знімку кадру даних:

[
Row(a=107831, f=SparseVector(5, {0: 0.0, 1: 0.0, 2: 0.0, 3: 0.0, 4: 0.0}), a=107831, f=SparseVector(5, {0: 0.0, 1: 0.0, 2: 0.0, 3: 0.0, 4: 0.0})),
Row(a=107831, f=SparseVector(5, {0: 0.0, 1: 0.0, 2: 0.0, 3: 0.0, 4: 0.0}), a=125231, f=SparseVector(5, {0: 0.0, 1: 0.0, 2: 0.0047, 3: 0.0, 4: 0.0043})),
Row(a=107831, f=SparseVector(5, {0: 0.0, 1: 0.0, 2: 0.0, 3: 0.0, 4: 0.0}), a=145831, f=SparseVector(5, {0: 0.0, 1: 0.2356, 2: 0.0036, 3: 0.0, 4: 0.4132})),
Row(a=107831, f=SparseVector(5, {0: 0.0, 1: 0.0, 2: 0.0, 3: 0.0, 4: 0.0}), a=147031, f=SparseVector(5, {0: 0.0, 1: 0.0, 2: 0.0, 3: 0.0, 4: 0.0})),
Row(a=107831, f=SparseVector(5, {0: 0.0, 1: 0.0, 2: 0.0, 3: 0.0, 4: 0.0}), a=149231, f=SparseVector(5, {0: 0.0, 1: 0.0032, 2: 0.2451, 3: 0.0, 4: 0.0042}))
]

Вище результат створюється шляхом приєднання до самого фрейму даних, ви можете бачити, що є 4стовпці як з двома, так aі з f.

Проблема полягає в тому, що коли я намагаюся зробити більше обчислень за допомогою aстовпця, я не можу знайти спосіб вибрати a, я спробую, df[0]і df.select('a')обидва повернули мені нижче помилки mesageage:

AnalysisException: Reference 'a' is ambiguous, could be: a#1333L, a#1335L.

Чи є в Spark API такий спосіб, що я можу знову відрізнити стовпці від дубльованих імен? чи, можливо, якимсь способом дозволити мені змінити назви стовпців?

Відповіді:


61

Я рекомендую вам змінити назви стовпців для вашого join.

df1.select(col("a") as "df1_a", col("f") as "df1_f")
   .join(df2.select(col("a") as "df2_a", col("f") as "df2_f"), col("df1_a" === col("df2_a"))

Отриманий DataFrameдоведетьсяschema

(df1_a, df1_f, df2_a, df2_f)

5
Можливо, вам доведеться виправити свою відповідь, оскільки лапки неправильно коригуються між назвами стовпців.
— Самех Шараф,

2
@SamehSharaf Я припускаю, що ти голосуєш за мою відповідь? Але відповідь насправді на 100% правильна - я просто використовую scala '-shorthand для вибору стовпців, тому насправді немає проблем з лапками.
— Glennie Helles Sindholt

31
@GlennieHellesSindholt, чесна думка. Це бентежить, оскільки відповідь позначена як pythonі pyspark.
— Хорхе Лейтао

Що робити, якщо кожен кадр даних містить понад 100 стовпців, і нам просто потрібно перейменувати одне і те саме ім’я стовпця? Звичайно, я не можу вручну ввести всі ці імена стовпців у пункті вибору
— bikashg

6
У цьому випадку ви могли б піти зdf1.withColumnRenamed("a", "df1_a")
— Гленні Хеллес Сіндгольт

100

Почнемо з деяких даних:

from pyspark.mllib.linalg import SparseVector
from pyspark.sql import Row

df1 = sqlContext.createDataFrame([
    Row(a=107831, f=SparseVector(
        5, {0: 0.0, 1: 0.0, 2: 0.0, 3: 0.0, 4: 0.0})),
    Row(a=125231, f=SparseVector(
        5, {0: 0.0, 1: 0.0, 2: 0.0047, 3: 0.0, 4: 0.0043})),
])

df2 = sqlContext.createDataFrame([
    Row(a=107831, f=SparseVector(
        5, {0: 0.0, 1: 0.0, 2: 0.0, 3: 0.0, 4: 0.0})),
    Row(a=107831, f=SparseVector(
        5, {0: 0.0, 1: 0.0, 2: 0.0, 3: 0.0, 4: 0.0})),
])

Існує кілька способів вирішити цю проблему. Перш за все, ви можете однозначно посилатися на стовпці дочірньої таблиці, використовуючи батьківські стовпці:

df1.join(df2, df1['a'] == df2['a']).select(df1['f']).show(2)

##  +--------------------+
##  |                   f|
##  +--------------------+
##  |(5,[0,1,2,3,4],[0...|
##  |(5,[0,1,2,3,4],[0...|
##  +--------------------+

Ви також можете використовувати псевдоніми таблиць:

from pyspark.sql.functions import col

df1_a = df1.alias("df1_a")
df2_a = df2.alias("df2_a")

df1_a.join(df2_a, col('df1_a.a') == col('df2_a.a')).select('df1_a.f').show(2)

##  +--------------------+
##  |                   f|
##  +--------------------+
##  |(5,[0,1,2,3,4],[0...|
##  |(5,[0,1,2,3,4],[0...|
##  +--------------------+

Нарешті, ви можете програмно перейменувати стовпці:

df1_r = df1.select(*(col(x).alias(x + '_df1') for x in df1.columns))
df2_r = df2.select(*(col(x).alias(x + '_df2') for x in df2.columns))

df1_r.join(df2_r, col('a_df1') == col('a_df2')).select(col('f_df1')).show(2)

## +--------------------+
## |               f_df1|
## +--------------------+
## |(5,[0,1,2,3,4],[0...|
## |(5,[0,1,2,3,4],[0...|
## +--------------------+

7
Дякуємо за ваше редагування, що ви показали стільки способів отримати правильний стовпець у цих неоднозначних випадках, я думаю, ваші приклади повинні входити до керівництва з програмування Spark. Я багато чому навчився!
— resec

невелика корекція: df2_r = **df2** .select(*(col(x).alias(x + '_df2') for x in df2.columns))замість df2_r = df1.select(*(col(x).alias(x + '_df2') for x in df2.columns)). В іншому - хороші речі
— Vzzarr,

Я згоден з цим, це повинно бути частиною керівництва з програмування Spark. Чисте золото. Я зміг остаточно розплутати джерело неоднозначності, вибираючи стовпці за старими іменами, перш ніж робити об’єднання. Рішення програмно доданих суфіксів до імен стовпців перед тим, як об'єднати всю неоднозначність wnet.
— Пабло Адамес,

26

Існує простіший спосіб, ніж написання псевдонімів для всіх стовпців, до яких ви приєднуєтесь, виконавши:

df1.join(df2,['a'])

Це працює, якщо ключ, до якого ви приєднуєтесь, однаковий в обох таблицях.

Див. Https://kb.databricks.com/data/join-two-dataframes-duplicated-columns.html


4
це фактична відповідь станом на Spark 2+
— Метт

2
А для Scala: df1.join (df2, послід ( "а"))
— mauriciojost

1
сторінку було перенесено на: kb.databricks.com/data/…
— bogdan.rusu

7

Ви можете використовувати def drop(col: Column)метод, щоб скинути дубльований стовпець, наприклад:

DataFrame:df1

+-------+-----+
| a     | f   |
+-------+-----+
|107831 | ... |
|107831 | ... |
+-------+-----+

DataFrame:df2

+-------+-----+
| a     | f   |
+-------+-----+
|107831 | ... |
|107831 | ... |
+-------+-----+

коли я приєднуюсь до df1 з df2, DataFrame буде таким, як показано нижче:

val newDf = df1.join(df2,df1("a")===df2("a"))

DataFrame:newDf

+-------+-----+-------+-----+
| a     | f   | a     | f   |
+-------+-----+-------+-----+
|107831 | ... |107831 | ... |
|107831 | ... |107831 | ... |
+-------+-----+-------+-----+

Тепер ми можемо скористатися def drop(col: Column)методом, щоб скинути дубльований стовпець 'a' або 'f', приблизно так:

val newDfWithoutDuplicate = df1.join(df2,df1("a")===df2("a")).drop(df2("a")).drop(df2("f"))

Чи буде такий підхід спрацьовувати, якщо ви виконуєте зовнішнє з'єднання і два стовпці мають різні відмінності?
— prafi

Можливо, ви не захочете відмовитися, якщо різні відносини з однією і тією ж схемою.
— thebluephantom

5

Покопавшись у API Spark, я виявив, що спочатку можу aliasстворити псевдонім для вихідного кадру даних, потім я використовую withColumnRenamedдля ручного перейменовування кожного стовпця на псевдонімі join.

Більш детально можна ознайомитись нижче в API Spark Dataframe API :

pyspark.sql.DataFrame.alias

pyspark.sql.DataFrame.withColumnRenamed

Однак я думаю, що це лише клопіткий спосіб вирішення проблеми, і мені цікаво, чи є кращий спосіб для мого запитання.


4

Ось як ми можемо об’єднати два кадри даних з однаковими іменами стовпців у PySpark.

df = df1.join(df2, ['col1','col2','col3'])

Якщо ви зробите printSchema()це після цього, ви зможете побачити, що повторювані стовпці були видалені.


3

Припустимо, що фрейми даних, до яких ви хочете приєднатися, є df1 і df2, і ви приєднуєтесь до них у стовпці 'a', тоді у вас є 2 методи

Спосіб 1

df1.join (df2, 'a', 'left_outer')

Це надзвичайний метод, і він настійно рекомендується.

Спосіб 2

df1.join (df2, df1.a == df2.a, 'left_outer'). drop (df2.a)


1

Це може бути не найкращим підходом, але якщо ви хочете перейменувати повторювані стовпці (після об’єднання), ви можете зробити це за допомогою цієї крихітної функції.

def rename_duplicate_columns(dataframe):
    columns = dataframe.columns
    duplicate_column_indices = list(set([columns.index(col) for col in columns if columns.count(col) == 2]))
    for index in duplicate_column_indices:
        columns[index] = columns[index]+'2'
    dataframe = dataframe.toDF(*columns)
    return dataframe

1

якщо лише стовпець ключів однаковий в обох таблицях, спробуйте використати такий спосіб (Підхід 1):

left. join(right , 'key', 'inner')

а не нижче (підхід 2):

left. join(right , left.key == right.key, 'inner')

Плюси використання підходу 1:

  • "ключ" відображатиметься лише один раз у кінцевому фреймі даних
  • простий у використанні синтаксис

Мінуси використання підходу 1:

  • допомога лише з ключовим стовпцем
  • Сценарії, в яких у випадку лівого приєднання, якщо планується використовувати нульовий підрахунок правої клавіші, це не спрацює. У цьому випадку потрібно перейменувати один із ключів, як зазначено вище.

0

Якщо у вас складніший варіант використання, ніж описаний у відповіді Гленні Хеллес Сіндгольт, наприклад, у вас є інші / декілька назв стовпців, що не є об'єднаними, і вони однакові, і ви хочете їх розрізнити при виборі, найкраще використовувати псевдоніми, наприклад:

df3 = df1.select("a", "b").alias("left")\
   .join(df2.select("a", "b").alias("right"), ["a"])\
   .select("left.a", "left.b", "right.b")

df3.columns
['a', 'b', 'b']
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.