PySpark中的Scala案例类是什么?

时间:2016-05-10 19:35:20

标签: python apache-spark pyspark case-class

您如何在PySpark中使用和/或实现等效的案例类?

2 个答案:

答案 0 :(得分:16)

As mentioned Alex Hall与命名产品类型的实际等价物为namedtuple

the other answer中建议的Row不同,它有许多有用的属性:

  • 具有良好定义的形状,可以可靠地用于结构模式匹配:

    >>> from collections import namedtuple
    >>>
    >>> FooBar = namedtuple("FooBar", ["foo", "bar"])
    >>> foobar = FooBar(42, -42)
    >>> foo, bar = foobar
    >>> foo
    42
    >>> bar
    -42
    

    相比之下Rows are not reliable when used with keyword arguments

    >>> from pyspark.sql import Row
    >>>
    >>> foobar = Row(foo=42, bar=-42)
    >>> foo, bar = foobar
    >>> foo
    -42
    >>> bar
    42
    

    虽然如果用位置参数定义:

    >>> FooBar = Row("foo", "bar")
    >>> foobar = FooBar(42, -42)
    >>> foo, bar = foobar
    >>> foo
    42
    >>> bar
    -42
    

    保留订单。

  • 定义合适的类型

    >>> from functools import singledispatch
    >>> 
    >>> FooBar = namedtuple("FooBar", ["foo", "bar"])
    >>> type(FooBar)
    <class 'type'>
    >>> isinstance(FooBar(42, -42), FooBar)
    True
    

    并且可以在需要类型处理时使用,特别是单个:

    >>> Circle = namedtuple("Circle", ["x", "y", "r"])
    >>> Rectangle = namedtuple("Rectangle", ["x1", "y1", "x2", "y2"])
    >>>
    >>> @singledispatch
    ... def area(x):
    ...     raise NotImplementedError
    ... 
    ... 
    >>> @area.register(Rectangle)
    ... def _(x):
    ...     return abs(x.x1 - x.x2) * abs(x.y1 - x.y2)
    ... 
    ... 
    >>> @area.register(Circle)
    ... def _(x):
    ...     return math.pi * x.r ** 2
    ... 
    ... 
    >>>
    >>> area(Rectangle(0, 0, 4, 4))
    16
    >>> >>> area(Circle(0, 0, 4))
    50.26548245743669
    

    multiple发送:

    >>> from multipledispatch import dispatch
    >>> from numbers import Rational
    >>>
    >>> @dispatch(Rectangle, Rational)
    ... def scale(x, y):
    ...     return Rectangle(x.x1, x.y1, x.x2 * y, x.y2 * y)
    ... 
    ... 
    >>> @dispatch(Circle, Rational)
    ... def scale(x, y):
    ...     return Circle(x.x, x.y, x.r * y)
    ...
    ...
    >>> scale(Rectangle(0, 0, 4, 4), 2)
    Rectangle(x1=0, y1=0, x2=8, y2=8)
    >>> scale(Circle(0, 0, 11), 2)
    Circle(x=0, y=0, r=22)
    

    与第一个属性结合使用,可用于各种模式匹配场景。 namedtuples也支持标准继承和type hints

    Rows请勿:

    >>> FooBar = Row("foo", "bar")
    >>> type(FooBar)
    <class 'pyspark.sql.types.Row'>
    >>> isinstance(FooBar(42, -42), FooBar)  # Expected failure
    Traceback (most recent call last):
    ...
    TypeError: isinstance() arg 2 must be a type or tuple of types
    >>> BarFoo = Row("bar", "foo")
    >>> isinstance(FooBar(42, -42), type(BarFoo))
    True
    >>> isinstance(BarFoo(42, -42), type(FooBar))
    True
    
  • 提供高度优化的表示。与Row对象不同,元组不使用__dict__并为每个实例携带字段名称。因此,初始化的速度可以快一个数量级:

    >>> FooBar = namedtuple("FooBar", ["foo", "bar"])
    >>> %timeit FooBar(42, -42)
    587 ns ± 5.28 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)
    

    与不同的Row构造函数进行比较:

    >>> %timeit Row(foo=42, bar=-42)
    3.91 µs ± 7.67 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)
    >>> FooBar = Row("foo", "bar")
    >>> %timeit FooBar(42, -42)
    2 µs ± 25.4 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)
    

    并且内存效率显着提高(使用大规模数据时非常重要):

    >>> import sys
    >>> FooBar = namedtuple("FooBar", ["foo", "bar"])
    >>> sys.getsizeof(FooBar(42, -42))
    64
    

    与等效Row

    进行比较
    >>> sys.getsizeof(Row(foo=42, bar=-42))
    72
    

    最后,使用namedtuple

    ,属性访问速度提高了一个数量级
    >>> FooBar = namedtuple("FooBar", ["foo", "bar"])
    >>> foobar = FooBar(42, -42)
    >>> %timeit foobar.foo
    102 ns ± 1.33 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each)
    

    Row对象上的等效操作相比:

    >>> foobar = Row(foo=42, bar=-42)
    >>> %timeit foobar.foo
    2.58 µs ± 26.9 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)
    
  • 最后但并非最不重要的是{1}}在Spark SQL中得到了正确支持

    namedtuples

<强>摘要

应该很清楚>>> Record = namedtuple("Record", ["id", "name", "value"]) >>> spark.createDataFrame([Record(1, "foo", 42)]) DataFrame[id: bigint, name: string, value: bigint] actual product type的非常差的替代品,除非Spark API强制执行,否则应该避免使用。{/ p>

还应该清楚的是Row并不是要在案例类的替代品中考虑到它,它直接等同于pyspark.sql.Row - 类型,它与实际产品相差甚远,行为类似于org.apache.spark.sql.Row(取决于子类,添加了名称)。 Python和Scala实现都是作为外部代码和内部Spark SQL表示之间有用的,尽管很尴尬的接口而引入的。

另见

  • 如果不提及由MacroPy及其端口(Li Haoyi)开发的令人敬畏的MacroPy3,那将是一种耻辱:

    Seq[Any]

    附带了丰富的其他功能,包括但不限于高级模式匹配和简洁的lambda表达式语法。

  • Python dataclasses(Python 3.7 +)。

答案 1 :(得分:3)

如果您转到使用反思推断架构<}中的sql-programming-guide部分,您会看到case class被定义为

  

case类定义表的模式。使用反射读取case类的参数名称,并成为列的名称。案例类也可以嵌套或包含复杂类型,如序列或数组。

示例为

val sqlContext = new org.apache.spark.sql.SQLContext(sc)
import sqlContext.implicits._
case class Person(name: String, age: Int)
val people = sc.textFile("examples/src/main/resources/people.txt").map(_.split(",")).map(p => Person(p(0), p(1).trim.toInt)).toDF()

在同一部分中,如果切换到 python ,即 pyspark ,您将看到Row被使用并定义为

  

通过将键/值对列表作为kwargs传递给Row类来构造行。此列表的键定义表的列名称,通过查看第一行来推断类型。

示例为

from pyspark.sql import SQLContext, Row
sqlContext = SQLContext(sc)
lines = sc.textFile("examples/src/main/resources/people.txt")
parts = lines.map(lambda l: l.split(","))
people = parts.map(lambda p: Row(name=p[0], age=int(p[1])))
schemaPeople = sqlContext.createDataFrame(people)

因此,解释的结论是Row可用作 pyspark中的case class