簡體   English   中英

從 Spark GroupedData 對象中選擇隨機項

[英]Choosing random items from a Spark GroupedData Object

我是在 Python 中使用 Spark 的新手,無法解決這個問題:在groupBy pyspark.sql.dataframe.DataFrame

df = sqlsc.read.json("data.json")
df.groupBy('teamId')

如何從每個結果組(按 teamId 分組)中選擇N個隨機樣本而不進行替換?

我基本上是在嘗試從每個團隊中選擇N個隨機用戶,也許一開始就使用groupBy是錯誤的?

好吧,這有點不對。 GroupedData並不是真正為數據訪問而設計的。 它只是描述了分組標准並提供了聚合方法。 有關更多詳細信息,請參閱我對在 Spark 中使用 groupBy 並返回 DataFrame 的回答。

這個想法的另一個問題是選擇N random samples 如果沒有數據的心理分組,這是一項很難並行實現的任務,並且當您在 DataFrame 上call DataFrame時不會發生這種情況:

至少有兩種方法可以處理這個問題:

  • 轉換為 RDD、 groupBy並執行本地采樣

    import random n = 3 def sample(iter, n): rs = random.Random() # We should probably use os.urandom as a seed return rs.sample(list(iter), n) df = sqlContext.createDataFrame( [(x, y, random.random()) for x in (1, 2, 3) for y in "abcdefghi"], ("teamId", "x1", "x2")) grouped = df.rdd.map(lambda row: (row.teamId, row)).groupByKey() sampled = sqlContext.createDataFrame( grouped.flatMap(lambda kv: sample(kv[1], n))) sampled.show() ## +------+---+-------------------+ ## |teamId| x1| x2| ## +------+---+-------------------+ ## | 1| g| 0.81921738561455| ## | 1| f| 0.8563875814036598| ## | 1| a| 0.9010425238735935| ## | 2| c| 0.3864428179837973| ## | 2| g|0.06233470405822805| ## | 2| d|0.37620872770129155| ## | 3| f| 0.7518901502732027| ## | 3| e| 0.5142305439671874| ## | 3| d| 0.6250620479303716| ## +------+---+-------------------+
  • 使用窗口函數

    from pyspark.sql import Window from pyspark.sql.functions import col, rand, rowNumber w = Window.partitionBy(col("teamId")).orderBy(col("rnd_")) sampled = (df .withColumn("rnd_", rand()) # Add random numbers column .withColumn("rn_", rowNumber().over(w)) # Add rowNumber over windw .where(col("rn_") <= n) # Take n observations .drop("rn_") # drop helper columns .drop("rnd_")) sampled.show() ## +------+---+--------------------+ ## |teamId| x1| x2| ## +------+---+--------------------+ ## | 1| f| 0.8563875814036598| ## | 1| g| 0.81921738561455| ## | 1| i| 0.8173912535268248| ## | 2| h| 0.10862995810038856| ## | 2| c| 0.3864428179837973| ## | 2| a| 0.6695356657072442| ## | 3| b|0.012329360826023095| ## | 3| a| 0.6450777858109182| ## | 3| e| 0.5142305439671874| ## +------+---+--------------------+

但恐怕兩者都會相當昂貴。 如果各個組的大小是平衡的並且相對較大,我會簡單地使用DataFrame.randomSplit

如果組數相對較少,則可以嘗試其他方法:

from pyspark.sql.functions import count, udf
from pyspark.sql.types import BooleanType
from operator import truediv

counts = (df
    .groupBy(col("teamId"))
    .agg(count("*").alias("n"))
    .rdd.map(lambda r: (r.teamId, r.n))
    .collectAsMap()) 

# This defines fraction of observations from a group which should
# be taken to get n values 
counts_bd = sc.broadcast({k: truediv(n, v) for (k, v) in counts.items()})

to_take = udf(lambda k, rnd: rnd <= counts_bd.value.get(k), BooleanType())

sampled = (df
    .withColumn("rnd_", rand())
    .where(to_take(col("teamId"), col("rnd_")))
    .drop("rnd_"))

sampled.show()

## +------+---+--------------------+
## |teamId| x1|                  x2|
## +------+---+--------------------+
## |     1|  d| 0.14815204548854788|
## |     1|  f|  0.8563875814036598|
## |     1|  g|    0.81921738561455|
## |     2|  a|  0.6695356657072442|
## |     2|  d| 0.37620872770129155|
## |     2|  g| 0.06233470405822805|
## |     3|  b|0.012329360826023095|
## |     3|  h|  0.9022527556458557|
## +------+---+--------------------+

在 Spark 1.5+ 中,您可以將sampleBy udf的調用:

df.sampleBy("teamId", counts_bd.value)

它不會為您提供確切的觀察次數,但在大多數情況下應該足夠好,只要每組的觀察次數足夠大以獲得適當的樣本。 您也可以以類似的方式在 RDD 上使用sampleByKey

我發現這是一個更多的數據框架,而不是進入 rdd 方式。

您可以使用window函數在組內創建排名,其中排名可以是隨機的以適合您的情況。 然后,您可以根據每個組所需的樣本數(N)進行過濾

window_1 = Window.partitionBy(data['teamId']).orderBy(F.rand())
data_1 = data.select('*', F.rank().over(window_1).alias('rank')).filter(F.col('rank') <= N).drop('rank')

這是使用 Pandas DataFrame.Sample方法的替代方法。 這使用 spark applyInPandas方法分發組,可從 Spark 3.0.0 獲得。 這允許您選擇每組的確切行數。

我已將argskwargs添加到函數中,以便您可以訪問DataFrame.Sample的其他參數。

def sample_n_per_group(n, *args, **kwargs):
    def sample_per_group(pdf):
        return pdf.sample(n, *args, **kwargs)
    return sample_per_group

df = spark.createDataFrame(
    [
        (1, 1.0), 
        (1, 2.0), 
        (2, 3.0), 
        (2, 5.0), 
        (2, 10.0)
    ],
    ("id", "v")
)

(df.groupBy("id")
   .applyInPandas(
        sample_n_per_group(2, random_state=2), 
        schema=df.schema
   )
)

要從文檔中了解非常大的組的限制:

此功能需要完全洗牌。 一個組的所有數據都將加載到內存中,因此如果數據傾斜並且某些組太大而無法放入內存,用戶應該注意潛在的 OOM 風險。

另請參閱: 如何從 PySpark DataFrame 中獲取隨機行?

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM