Repository: dask/dask

Issue:
Weird results in delta computation between Dataframes
**What happened**:
While computing the delta of two dataframes I noticed that added rows were not taken into account. I started playing and discovered that by converting the Dask dataframe to Pandas the expected output is given. Moreover, if the new rows are added to the start of the file (and not the end as it is now) the delta is computed correctly. 

**What you expected to happen**:
Dask should compute correctly the added rows from the dataframe. Moreover, the code should output the same result using pandas or dask since it's the same function. 

**Minimal Complete Verifiable Example**:

```python
import random
import sys
from shutil import copyfile

import pandas as pd
from dask import dataframe as dd
from dask.dataframe import DataFrame

# Create random CSV
with open('old.csv','w') as f:
    for _ in range(int(sys.argv[1])):
        f.write(f'{random.randrange(1, 10**9):09}, {random.randrange(1, 10**9):09}, {random.randrange(1, 10**9):09}\n')

copyfile('old.csv', 'new.csv')

# Add three new rows
with open('new.csv', 'a') as f:
    for _ in range(3):
        f.write(f'{random.randrange(1, 10**9):09}, {random.randrange(1, 10**9):09}, {random.randrange(1, 10**9):09}\n')


def process_delta_individually(old_df: DataFrame, new_df: DataFrame, merge_type: str):
    diff_df = old_df.merge(new_df, indicator=True, how='outer').loc[lambda x: x['_merge'] == merge_type]
    print(f'Difference count {merge_type} with dask: {len(diff_df.index)}')
    old_pandas = old_df.compute()
    new_pandas = new_df.compute()
    diff_df_pandas = old_pandas.merge(new_pandas, indicator=True, how='outer').loc[lambda x: x['_merge'] == merge_type] 
    print(f'Difference count {merge_type} with pandas: {len(diff_df_pandas.index)}')


new_df = dd.read_csv('new.csv', header=None, delimiter=',', dtype=str, blocksize=None)
old_df = dd.read_csv('old.csv', header=None, delimiter=',', dtype=str, blocksize=None)
process_delta_individually(old_df, new_df, 'right_only')
```

```
python test.py 2
Difference count right_only with dask: 3
Difference count right_only with pandas: 3
```

```
python test.py 10
Difference count right_only with dask: 0
Difference count right_only with pandas: 3
```

**Environment**:

- Dask version: 2021.7.0
- Python version: 3.9.4
- Operating System: macOS Big Sur
- Install method (conda, pip, source): pip



Discussion:
Thanks for writing this up. I can reproduce. I will try to take a closer look next week.
Ok so the issue boils down to the way the name of the `read-csv` task is defined in the `DataFrameIOLayer` 

```python
    label = "read-csv-"
    name = label + tokenize(reader, columns, enforce, head, blocksize)
```
[code](https://github.com/dask/dask/blob/8d49c3f1491b4aaccb6560814718f498abf63335/dask/dataframe/io/csv.py#L381-382). 

You can see that the tokenize function depends on `reader`, `columns`, `enforce`, `head` and `blockwise`. As soon as the number of lines in `old.csv` exceeds the number read in by head, all of these fields are identical for `old.csv` and `new.csv`. 

The impact of this is that dask thinks that your two `read-csv` tasks are trying to do exactly the same thing. So when they are both included in the same task graph (which is what happens when you call `merge` on them), one of the tasks gets optimized away. I am opening a PR that resolves this.

Resolving patch:
diff --git a/dask/dataframe/io/csv.py b/dask/dataframe/io/csv.py
--- a/dask/dataframe/io/csv.py
+++ b/dask/dataframe/io/csv.py
@@ -292,6 +292,7 @@ def text_blocks_to_pandas(
     specified_dtypes=None,
     path=None,
     blocksize=None,
+    urlpath=None,
 ):
     """Convert blocks of bytes to a dask.dataframe
 
@@ -379,7 +380,7 @@ def text_blocks_to_pandas(
 
     # Create Blockwise layer
     label = "read-csv-"
-    name = label + tokenize(reader, columns, enforce, head, blocksize)
+    name = label + tokenize(reader, urlpath, columns, enforce, head, blocksize)
     layer = DataFrameIOLayer(
         name,
         columns,
@@ -616,6 +617,7 @@ def read_pandas(
         specified_dtypes=specified_dtypes,
         path=path,
         blocksize=blocksize,
+        urlpath=urlpath,
     )
 
 


Test patch:
diff --git a/dask/dataframe/io/tests/test_csv.py b/dask/dataframe/io/tests/test_csv.py
--- a/dask/dataframe/io/tests/test_csv.py
+++ b/dask/dataframe/io/tests/test_csv.py
@@ -1666,3 +1666,37 @@ def test_csv_parse_fail(tmpdir):
         dd.read_csv(path, sample=13)
     df = dd.read_csv(path, sample=13, sample_rows=1)
     assert_eq(df, expected)
+
+
+def test_csv_name_should_be_different_even_if_head_is_same(tmpdir):
+    # https://github.com/dask/dask/issues/7904
+    import random
+    from shutil import copyfile
+
+    old_csv_path = os.path.join(str(tmpdir), "old.csv")
+    new_csv_path = os.path.join(str(tmpdir), "new_csv")
+
+    # Create random CSV
+    with open(old_csv_path, "w") as f:
+        for _ in range(10):
+            f.write(
+                f"{random.randrange(1, 10**9):09}, {random.randrange(1, 10**9):09}, {random.randrange(1, 10**9):09}\n"
+            )
+
+    copyfile(old_csv_path, new_csv_path)
+
+    # Add three new rows
+    with open(new_csv_path, "a") as f:
+        for _ in range(3):
+            f.write(
+                f"{random.randrange(1, 10**9):09}, {random.randrange(1, 10**9):09}, {random.randrange(1, 10**9):09}\n"
+            )
+
+    new_df = dd.read_csv(
+        new_csv_path, header=None, delimiter=",", dtype=str, blocksize=None
+    )
+    old_df = dd.read_csv(
+        old_csv_path, header=None, delimiter=",", dtype=str, blocksize=None
+    )
+
+    assert new_df.dask.keys() != old_df.dask.keys()