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.