Databricks ( Spark ) にて 引用符 ( sep ) 内に 区切り文字 ( lineSep ) が含まれる CSV ファイルを取り込む際には  `multiLine` を `true` に設定すべき

## 概要

Databricks ( Spark ) にて 引用符 ( sep ) 内に 区切り文字 ( lineSep ) が含まれる CSV ファイルを取り込む場合には `multiLine` を `true` に設定する必要があるようです。

引用符 ( sep ) 内に 区切り文字 ( lineSep ) が含まれる CSV ファイルとは、カラム値が `"` で囲まれており、その値内で `\r` が入っているファイルのことです。

```text
id,commnet
1,"test 
a,test
btest"
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/24031/bd5d1621-ced9-dc05-72e3-3e80ec98f338.png)

私がさわってきたツールではデフォルトで１つの値として認識してくれることが多かったのですが、Spark では異なるようです。下記に、Pandas での実行例と Spark での実行例を記載しております。Pandas では 1 行のデータフレームとなっておりますが、Spark では 3 行のデータフレームとなっております。

**Pandas での実行例**

```python
import pandas as pd
 
path_for_py = f"/dbfs{path}"
 
pdf = pd.read_csv(path)
pdf.display()
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/24031/5618484f-5f87-1822-6a9b-44dfb7bae526.png)

**Spark での実行例**

```python
df = spark.read.format("csv").option("header", "true").load(path)
 
df.display()
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/24031/e02b0b6d-2265-e966-b856-014fb166ff03.png)

対応方法としては、`multiLine`を `true`に設定すればいいようです。

> Parse one record, which may span multiple lines, per file. CSV built-in functions ignore this option.

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/24031/a781296d-f63b-1ec5-8e22-3eb824751429.png)

引用元：[CSV Files - Spark 3.3.1 Documentation (apache.org)](https://spark.apache.org/docs/latest/sql-data-sources-csv.html#data-source-option)

```python
df = (
    spark.read.format("csv")
    .option("multiLine", "true")
    .option("header", "true")
    .load(path)
)
 
df.display()
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/24031/6f6319f6-8805-b275-cb26-981bc47cf82b.png)


## 検証コード
### 1. サンプルファイルを配置

```python
import inspect
from pyspark.sql import DataFrame, SparkSession
from pyspark.dbutils import DBUtils
 
 
def put_files_to_storage(
    path,
    content,
    is_overwrite=True,
):
    # 最初の行と最後の行を削除
    content = inspect.cleandoc(content)
 
    spark = SparkSession.getActiveSession()
    dbutils = DBUtils(spark)
    return dbutils.fs.put(path, content, is_overwrite)

path = "/test/test.csv"
 
csv_data_rows = """
id,commnet
1,"test 
a,test
btest"
"""

put_files_to_storage(path, csv_data_rows)
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/24031/ed39590f-31cc-7c89-8a95-de12a5ff9fbc.png)

### 2. Pandas での実行例

```python
import pandas as pd
 
path_for_py = f"/dbfs{path}"
 
pdf = pd.read_csv(path)
pdf.display()
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/24031/cf42620d-8866-d937-3bd8-6acc90fd233f.png)

### 3. Spark (デフォルト) での実行例

```python
df = spark.read.format("csv").option("header", "true").load(path)
 
df.display()
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/24031/7f7a52c2-6a5c-d279-1f6a-992ed7787fac.png)

### 4. Spark (`multiLine`を`true`に設定) での実行例

```python
df = (
    spark.read.format("csv")
    .option("multiLine", "true")
    .option("header", "true")
    .load(path)
)
 
df.display()
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/24031/7540b27e-4db4-485b-d7dc-b3b912d3fdff.png)

### 5. リソースを削除

```python
dbutils.fs.rm(path,True)
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/24031/cb1f2c2e-f125-5feb-4176-c4a03b0efa3b.png)