| # | |
| # Licensed to the Apache Software Foundation (ASF) under one or more | |
| # contributor license agreements. See the NOTICE file distributed with | |
| # this work for additional information regarding copyright ownership. | |
| # The ASF licenses this file to You under the Apache License, Version 2.0 | |
| # (the "License"); you may not use this file except in compliance with | |
| # the License. You may obtain a copy of the License at | |
| # | |
| # http://www.apache.org/licenses/LICENSE-2.0 | |
| # | |
| # Unless required by applicable law or agreed to in writing, software | |
| # distributed under the License is distributed on an "AS IS" BASIS, | |
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| # See the License for the specific language governing permissions and | |
| # limitations under the License. | |
| # | |
| import os | |
| import time | |
| import unittest | |
| from decimal import Decimal | |
| import pyspark.sql.functions as F | |
| from pyspark.errors import ( | |
| PySparkTypeError, | |
| PySparkValueError, | |
| ) | |
| from pyspark.sql import Row | |
| from pyspark.sql.types import ( | |
| DateType, | |
| DecimalType, | |
| IntegerType, | |
| StringType, | |
| StructField, | |
| StructType, | |
| TimestampNTZType, | |
| TimestampType, | |
| TimeType, | |
| ) | |
| from pyspark.testing import assertDataFrameEqual | |
| from pyspark.testing.sqlutils import ReusedSQLTestCase | |
| from pyspark.testing.utils import ( | |
| have_pandas, | |
| have_pyarrow, | |
| pandas_requirement_message, | |
| pyarrow_requirement_message, | |
| ) | |
| class DataFrameCreationTestsMixin: | |
| def test_create_str_from_dict(self): | |
| data = [ | |
| {"broker": {"teamId": 3398, "contactEmail": "abc.xyz@123.ca"}}, | |
| ] | |
| for schema in [ | |
| StructType([StructField("broker", StringType())]), | |
| "broker: string", | |
| ]: | |
| df = self.spark.createDataFrame(data, schema=schema) | |
| self.assertEqual( | |
| df.first().broker, | |
| """{'teamId': 3398, 'contactEmail': 'abc.xyz@123.ca'}""", | |
| ) | |
| def test_create_dataframe_from_array_of_long(self): | |
| import array | |
| data = [Row(longarray=array.array("l", [-9223372036854775808, 0, 9223372036854775807]))] | |
| df = self.spark.createDataFrame(data) | |
| self.assertEqual(df.first(), Row(longarray=[-9223372036854775808, 0, 9223372036854775807])) | |
| def test_create_dataframe_from_datetime_time(self): | |
| import datetime | |
| df = self.spark.createDataFrame( | |
| [ | |
| (datetime.time(1, 2, 3),), | |
| (datetime.time(4, 5, 6),), | |
| (datetime.time(7, 8, 9),), | |
| ], | |
| ["t"], | |
| ) | |
| self.assertIsInstance(df.schema["t"].dataType, TimeType) | |
| self.assertEqual(df.count(), 3) | |
| @unittest.skipIf(not have_pandas, pandas_requirement_message) | |
| def test_create_dataframe_from_pandas_with_timestamp(self): | |
| from datetime import datetime | |
| import pandas as pd | |
| pdf = pd.DataFrame( | |
| {"ts": [datetime(2017, 10, 31, 1, 1, 1)], "d": [pd.Timestamp.now().date()]}, | |
| columns=["d", "ts"], | |
| ) | |
| # test types are inferred correctly without specifying schema | |
| df = self.spark.createDataFrame(pdf) | |
| self.assertIsInstance(df.schema["ts"].dataType, TimestampType) | |
| self.assertIsInstance(df.schema["d"].dataType, DateType) | |
| # test with schema will accept pdf as input | |
| df = self.spark.createDataFrame(pdf, schema="d date, ts timestamp") | |
| self.assertIsInstance(df.schema["ts"].dataType, TimestampType) | |
| self.assertIsInstance(df.schema["d"].dataType, DateType) | |
| df = self.spark.createDataFrame(pdf, schema="d date, ts timestamp_ntz") | |
| self.assertIsInstance(df.schema["ts"].dataType, TimestampNTZType) | |
| self.assertIsInstance(df.schema["d"].dataType, DateType) | |
| @unittest.skipIf(have_pandas, "Required Pandas was found.") | |
| def test_create_dataframe_required_pandas_not_found(self): | |
| with self.quiet(): | |
| with self.assertRaisesRegex( | |
| ImportError, "(Pandas >= .* must be installed|No module named '?pandas'?)" | |
| ): | |
| from datetime import datetime | |
| import pandas as pd | |
| pdf = pd.DataFrame( | |
| {"ts": [datetime(2017, 10, 31, 1, 1, 1)], "d": [pd.Timestamp.now().date()]} | |
| ) | |
| self.spark.createDataFrame(pdf) | |
| # Regression test for SPARK-23360 | |
| @unittest.skipIf(not have_pandas, pandas_requirement_message) | |
| def test_create_dataframe_from_pandas_with_dst(self): | |
| from datetime import datetime | |
| import pandas as pd | |
| from pandas.testing import assert_frame_equal | |
| pdf = pd.DataFrame({"time": [datetime(2015, 10, 31, 22, 30)]}) | |
| df = self.spark.createDataFrame(pdf) | |
| assert_frame_equal(pdf, df.toPandas()) | |
| orig_env_tz = os.environ.get("TZ", None) | |
| try: | |
| tz = "America/Los_Angeles" | |
| os.environ["TZ"] = tz | |
| time.tzset() | |
| with self.sql_conf({"spark.sql.session.timeZone": tz}): | |
| df = self.spark.createDataFrame(pdf) | |
| assert_frame_equal(pdf, df.toPandas()) | |
| finally: | |
| del os.environ["TZ"] | |
| if orig_env_tz is not None: | |
| os.environ["TZ"] = orig_env_tz | |
| time.tzset() | |
| @unittest.skipIf(not have_pandas, pandas_requirement_message) | |
| def test_create_dataframe_from_pandas_with_day_time_interval(self): | |
| # SPARK-37277: Test DayTimeIntervalType in createDataFrame without Arrow. | |
| from datetime import timedelta | |
| import pandas as pd | |
| df = self.spark.createDataFrame(pd.DataFrame({"a": [timedelta(microseconds=123)]})) | |
| self.assertEqual(df.toPandas().a.iloc[0], timedelta(microseconds=123)) | |
| # test for SPARK-36337 | |
| def test_create_nan_decimal_dataframe(self): | |
| self.assertEqual( | |
| self.spark.createDataFrame(data=[Decimal("NaN")], schema="decimal").collect(), | |
| [Row(value=None)], | |
| ) | |
| def test_check_decimal_nan(self): | |
| data = [Row(dec=Decimal("NaN"))] | |
| schema = StructType([StructField("dec", DecimalType(), False)]) | |
| with self.assertRaises(PySparkValueError): | |
| self.spark.createDataFrame(data=data, schema=schema) | |
| def test_decimal_round(self): | |
| df = self.spark.createDataFrame([(Decimal(1.234),)], ["d"]) | |
| rounded = df.first().d | |
| self.assertEqual(rounded, Decimal("1.233999999999999986")) | |
| def test_invalid_argument_create_dataframe(self): | |
| with self.assertRaises(PySparkTypeError) as pe: | |
| self.spark.createDataFrame([(1, 2)], schema=123) | |
| self.check_error( | |
| exception=pe.exception, | |
| errorClass="NOT_EXPECTED_TYPE", | |
| messageParameters={ | |
| "expected_type": "list, None or StructType", | |
| "arg_name": "schema", | |
| "arg_type": "int", | |
| }, | |
| ) | |
| with self.assertRaises(PySparkTypeError) as pe: | |
| self.spark.createDataFrame(self.spark.range(1)) | |
| self.check_error( | |
| exception=pe.exception, | |
| errorClass="INVALID_TYPE", | |
| messageParameters={"arg_name": "data", "arg_type": "DataFrame"}, | |
| ) | |
| def test_partial_inference_failure(self): | |
| with self.assertRaises(PySparkValueError) as pe: | |
| self.spark.createDataFrame([(None, 1)]) | |
| self.check_error( | |
| exception=pe.exception, | |
| errorClass="CANNOT_DETERMINE_TYPE", | |
| messageParameters={}, | |
| ) | |
| @unittest.skipIf( | |
| not have_pandas or not have_pyarrow, | |
| pandas_requirement_message or pyarrow_requirement_message, | |
| ) | |
| def test_schema_inference_from_pandas_with_dict(self): | |
| # SPARK-47543: test for verifying if inferring `dict` as `MapType` work properly. | |
| import pandas as pd | |
| pdf = pd.DataFrame({"str_col": ["second"], "dict_col": [{"first": 0.7, "second": 0.3}]}) | |
| with self.sql_conf( | |
| { | |
| "spark.sql.execution.arrow.pyspark.enabled": True, | |
| "spark.sql.execution.arrow.pyspark.fallback.enabled": False, | |
| "spark.sql.execution.pandas.inferPandasDictAsMap": True, | |
| } | |
| ): | |
| sdf = self.spark.createDataFrame(pdf) | |
| self.assertEqual( | |
| sdf.withColumn("test", F.col("dict_col")[F.col("str_col")]).collect(), | |
| [Row(str_col="second", dict_col={"first": 0.7, "second": 0.3}, test=0.3)], | |
| ) | |
| # Empty dict should fail | |
| pdf_empty_struct = pd.DataFrame({"str_col": ["second"], "dict_col": [{}]}) | |
| with self.assertRaises(PySparkValueError) as pe: | |
| self.spark.createDataFrame(pdf_empty_struct) | |
| self.check_error( | |
| exception=pe.exception, | |
| errorClass="CANNOT_INFER_EMPTY_SCHEMA", | |
| messageParameters={}, | |
| ) | |
| # Dict has different types of values should fail | |
| pdf_different_type = pd.DataFrame( | |
| {"str_col": ["second"], "dict_col": [{"first": 0.7, "second": "0.3"}]} | |
| ) | |
| self.assertRaises( | |
| PySparkValueError, lambda: self.spark.createDataFrame(pdf_different_type) | |
| ) | |
| with self.sql_conf( | |
| { | |
| "spark.sql.execution.arrow.pyspark.enabled": False, | |
| "spark.sql.execution.pandas.inferPandasDictAsMap": True, | |
| } | |
| ): | |
| sdf = self.spark.createDataFrame(pdf) | |
| self.assertEqual( | |
| sdf.withColumn("test", F.col("dict_col")[F.col("str_col")]).collect(), | |
| [Row(str_col="second", dict_col={"first": 0.7, "second": 0.3}, test=0.3)], | |
| ) | |
| def test_empty_dataframe_with_struct_type(self): | |
| schema = StructType( | |
| [ | |
| StructField("name", StringType(), True), | |
| StructField("age", IntegerType(), True), | |
| ] | |
| ) | |
| df = self.spark.emptyDataFrame(schema) | |
| self.assertEqual(df.schema, schema) | |
| self.assertEqual(df.count(), 0) | |
| self.assertEqual(df.collect(), []) | |
| def test_empty_dataframe_with_ddl_string(self): | |
| df = self.spark.emptyDataFrame("name STRING, age INT") | |
| self.assertEqual(len(df.schema.fields), 2) | |
| self.assertEqual(df.schema.fields[0].name, "name") | |
| self.assertIsInstance(df.schema.fields[0].dataType, StringType) | |
| self.assertEqual(df.schema.fields[1].name, "age") | |
| self.assertIsInstance(df.schema.fields[1].dataType, IntegerType) | |
| self.assertEqual(df.count(), 0) | |
| self.assertEqual(df.collect(), []) | |
| def test_empty_schema(self): | |
| schema = StructType() | |
| for data in [[], [Row()]]: | |
| with self.subTest(data=data): | |
| sdf = self.spark.createDataFrame(data, schema) | |
| assertDataFrameEqual(sdf, data) | |
| @unittest.skipIf( | |
| not have_pandas or not have_pyarrow, | |
| pandas_requirement_message or pyarrow_requirement_message, | |
| ) | |
| def test_from_pandas_dataframe_with_zero_columns(self): | |
| """SPARK-55600: Test that row count is preserved when creating DataFrame from | |
| pandas with 0 columns but with explicit schema in classic Spark.""" | |
| import pandas as pd | |
| # Create a pandas DataFrame with 5 rows but 0 columns | |
| pdf = pd.DataFrame(index=range(5)) | |
| schema = StructType([]) | |
| # Test with Arrow optimization enabled | |
| with self.sql_conf( | |
| { | |
| "spark.sql.execution.arrow.pyspark.enabled": True, | |
| "spark.sql.execution.arrow.pyspark.fallback.enabled": False, | |
| } | |
| ): | |
| df = self.spark.createDataFrame(pdf, schema=schema) | |
| self.assertEqual(df.schema, schema) | |
| self.assertEqual(df.count(), 5) | |
| self.assertEqual(len(df.collect()), 5) | |
| # Test with Arrow optimization disabled | |
| with self.sql_conf({"spark.sql.execution.arrow.pyspark.enabled": False}): | |
| df = self.spark.createDataFrame(pdf, schema=schema) | |
| self.assertEqual(df.schema, schema) | |
| self.assertEqual(df.count(), 5) | |
| self.assertEqual(len(df.collect()), 5) | |
| class DataFrameCreationTests( | |
| DataFrameCreationTestsMixin, | |
| ReusedSQLTestCase, | |
| ): | |
| pass | |
| if __name__ == "__main__": | |
| from pyspark.testing import main | |
| main() |