Spark Dataframe区分名称重复的列


80

因此,正如我在Spark Dataframe中所知道的那样,多个列可以具有相同的名称,如下面的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')都在错误信息下方返回了我:

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

无论如何,Spark API中是否可以再次将列与重复的名称区分开?还是让我更改列名的某种方法?

Answers:


58

我建议您更改的列名称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
由于列名之间的引号未正确调整,因此您可能需要修正答案。
Sameh Sharaf

2
@SamehSharaf我以为您是我的不赞成者?但是答案实际上是100%正确的-我只是使用scala '-shorthand进行列选择,因此引号实际上没有问题。
格兰妮

31
@GlennieHellesSindholt,很公平。这是令人困惑的,因为答案标记为pythonpyspark
豪尔赫·雷涛

如果每个数据框包含100多个列,而我们只需要重命名一个相同的列名怎么办?当然,不能在select子句中手动输入所有这些列名称
bikashg

5
在这种情况下,您可以选择df1.withColumnRenamed("a", "df1_a")
Glennie Helles Sindholt

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编程指南中。我学到了很多!
sec

小修正: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编程指南的一部分。纯金。在进行联接之前,我终于能够通过旧名称来解开歧义选择列的来源。在进行联接之前,以编程方式将后缀附加到列名的解决方案使所有歧义消失。
Pablo Adames

26

除了为要连接的所有列编写别名外,还有一种更简单的方法:

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

如果您要加入的键在两个表中都相同,则此方法有效。

参见 https://kb.databricks.com/data/join-two-dataframes-duplicated-columns.html


4
这是Spark 2+以来的实际答案
Matt

2
而对于斯卡拉:df1.join(DF2,SEQ( “A”))
mauriciojost


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)method删除重复的列'a'或'f',如下所示:

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

如果您正在执行外部联接并且两列具有一些不同的值,则这种方法行得通吗?
prafi

如果具有相同架构的不同关系,您可能不想删除。
thebluephantom

5

深入研究Spark API之后,我发现我可以先使用alias它为原始数据帧创建一个别名,然后再使用我withColumnRenamed来手动重命名别名上的每个列,这样做将join不会导致列名重复。

更详细的信息可以参考下面的Spark Dataframe API

pyspark.sql.DataFrame.alias

pyspark.sql.DataFrame.withColumnRenamed

但是,我认为这只是一个麻烦的解决方法,并且想知道是否有更好的方法可以解决我的问题。


4

这就是我们如何在PySpark中的相同列名上连接两个数据框。

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

如果执行printSchema()此操作,则可以看到重复的列已被删除。


3

假设要连接的DataFrame是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

如果您的用例比Glennie Helles Sindholt的答案中描述的更为复杂,例如您有其他/很少的非联接列名也相同,并且希望在选择别名时加以区分,例如:

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']
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.