Repository: kinverarity1/lasio

Issue:
:1 :2 should only be appended to mnemonics when using LASFile.read
**Describe the bug**
Confusing overloaded behaviour with `LASFile.__setitem__` which creates duplicate mnemonics with appended `:1`, `:2` when trying to update curves.

**To Reproduce**
```
>>> import lasio.examples
>>> las = lasio.examples.open("sample.las")
>>> print(las.curves)
Mnemonic  Unit  Value  Description
--------  ----  -----  -----------
DEPT      M            1  DEPTH
DT        US/M         2  SONIC TRANSIT TIME
RHOB      K/M3         3  BULK DENSITY
NPHI      V/V          4   NEUTRON POROSITY
SFLU      OHMM         5  RXO RESISTIVITY
SFLA      OHMM         6  SHALLOW RESISTIVITY
ILM       OHMM         7  MEDIUM RESISTIVITY
ILD       OHMM         8  DEEP RESISTIVITY
>>> las["ILD_MSM"] = 100 / las["ILD"]
>>> print(las.curves)
Mnemonic  Unit  Value  Description
--------  ----  -----  -----------
DEPT      M            1  DEPTH
DT        US/M         2  SONIC TRANSIT TIME
RHOB      K/M3         3  BULK DENSITY
NPHI      V/V          4   NEUTRON POROSITY
SFLU      OHMM         5  RXO RESISTIVITY
SFLA      OHMM         6  SHALLOW RESISTIVITY
ILM       OHMM         7  MEDIUM RESISTIVITY
ILD       OHMM         8  DEEP RESISTIVITY
ILD_MSM
>>> # Whoops, typo, let's do that again
>>> las["ILD_MSM"] = 1000 / las["ILD"]
>>> print(las.curves)
Mnemonic   Unit  Value  Description
--------   ----  -----  -----------
DEPT       M            1  DEPTH
DT         US/M         2  SONIC TRANSIT TIME
RHOB       K/M3         3  BULK DENSITY
NPHI       V/V          4   NEUTRON POROSITY
SFLU       OHMM         5  RXO RESISTIVITY
SFLA       OHMM         6  SHALLOW RESISTIVITY
ILM        OHMM         7  MEDIUM RESISTIVITY
ILD        OHMM         8  DEEP RESISTIVITY
ILD_MSM:1
ILD_MSM:2
```

**Expected behavior**
This line:

```
>>> las["ILD_MSM"] = 1000 / las["ILD"]
>>> print(las)
```

Should have resulted in this:

```
Mnemonic  Unit  Value  Description
--------  ----  -----  -----------
DEPT      M            1  DEPTH
DT        US/M         2  SONIC TRANSIT TIME
RHOB      K/M3         3  BULK DENSITY
NPHI      V/V          4   NEUTRON POROSITY
SFLU      OHMM         5  RXO RESISTIVITY
SFLA      OHMM         6  SHALLOW RESISTIVITY
ILM       OHMM         7  MEDIUM RESISTIVITY
ILD       OHMM         8  DEEP RESISTIVITY
ILD_MSM
```

Not this:

```
Mnemonic   Unit  Value  Description
--------   ----  -----  -----------
DEPT       M            1  DEPTH
DT         US/M         2  SONIC TRANSIT TIME
RHOB       K/M3         3  BULK DENSITY
NPHI       V/V          4   NEUTRON POROSITY
SFLU       OHMM         5  RXO RESISTIVITY
SFLA       OHMM         6  SHALLOW RESISTIVITY
ILM        OHMM         7  MEDIUM RESISTIVITY
ILD        OHMM         8  DEEP RESISTIVITY
ILD_MSM:1
ILD_MSM:2
```

**Software versions (please complete the following information):**
 - lasio version (`import lasio; print(lasio.__version__)`) `master` branch

**Additional context**
The addition of `:1`, `:2` etc should only happen when using `LASFile.read`


Discussion:

Patch:
diff --git a/docs/source/pandas.rst b/docs/source/pandas.rst
index 607f4a3..3f2b546 100644
--- a/docs/source/pandas.rst
+++ b/docs/source/pandas.rst
@@ -28,46 +28,8 @@ for the dataframe's index. See below for an example using this LAS file:
     >>> import lasio.examples
     >>> las = lasio.examples.open('6038187_v1.2.las')
     >>> df = las.df()
-    >>> print(df)
-              CALI   DFAR  DNEAR     GAMN  NEUT       PR     SP     COND
-    DEPT
-    0.05    49.765  4.587  3.382      NaN   NaN      NaN    NaN      NaN
-    0.10    49.765  4.587  3.382 -2324.28   NaN  115.508 -3.049 -116.998
-    0.15    49.765  4.587  3.382 -2324.28   NaN  115.508 -3.049 -116.998
-    0.20    49.765  4.587  3.382 -2324.28   NaN  115.508 -3.049 -116.998
-    0.25    49.765  4.587  3.382 -2324.28   NaN  115.508 -3.049 -116.998
-    ...        ...    ...    ...      ...   ...      ...    ...      ...
-    136.40  48.604    NaN    NaN      NaN   NaN      NaN    NaN      NaN
-    136.45  48.555    NaN    NaN      NaN   NaN      NaN    NaN      NaN
-    136.50  48.555    NaN    NaN      NaN   NaN      NaN    NaN      NaN
-    136.55  48.438    NaN    NaN      NaN   NaN      NaN    NaN      NaN
-    136.60 -56.275    NaN    NaN      NaN   NaN      NaN    NaN      NaN
-
-    [2732 rows x 8 columns]
-
-If you prefer the DEPT curve not to be set as the ``pandas.DataFrame`` index, then you can reset the index:
-
-.. code-block:: python
 
-    >>> df2 = las.df().reset_index()
-    >>> print(df2)
-            DEPT    CALI   DFAR  DNEAR     GAMN  NEUT       PR     SP     COND
-    0       0.05  49.765  4.587  3.382      NaN   NaN      NaN    NaN      NaN
-    1       0.10  49.765  4.587  3.382 -2324.28   NaN  115.508 -3.049 -116.998
-    2       0.15  49.765  4.587  3.382 -2324.28   NaN  115.508 -3.049 -116.998
-    3       0.20  49.765  4.587  3.382 -2324.28   NaN  115.508 -3.049 -116.998
-    4       0.25  49.765  4.587  3.382 -2324.28   NaN  115.508 -3.049 -116.998
-    ...      ...     ...    ...    ...      ...   ...      ...    ...      ...
-    2727  136.40  48.604    NaN    NaN      NaN   NaN      NaN    NaN      NaN
-    2728  136.45  48.555    NaN    NaN      NaN   NaN      NaN    NaN      NaN
-    2729  136.50  48.555    NaN    NaN      NaN   NaN      NaN    NaN      NaN
-    2730  136.55  48.438    NaN    NaN      NaN   NaN      NaN    NaN      NaN
-    2731  136.60 -56.275    NaN    NaN      NaN   NaN      NaN    NaN      NaN
-
-    [2732 rows x 9 columns]
-
-But let's continue with ``df``, which has DEPT set to the index. There are some 
-summary methods in pandas which are handy for data exploration:
+There are some summary methods handy for data exploration:
 
 .. code-block:: python
 
diff --git a/lasio/las.py b/lasio/las.py
index 676b3c3..11308e2 100644
--- a/lasio/las.py
+++ b/lasio/las.py
@@ -678,10 +678,17 @@ class LASFile(object):
                         key, value.mnemonic
                     )
                 )
-            self.append_curve_item(value)
+            if key in self.curves.keys():
+                ix = self.curves.keys().index(key)
+                self.replace_curve_item(ix, value)
+            else:
+                self.append_curve_item(value)
         else:
             # Assume value is an ndarray
-            self.append_curve(key, value)
+            if key in self.curves.keys():
+                self.update_curve(mnemonic=key, data=value)
+            else:
+                self.append_curve(key, value)
 
     def keys(self):
         """Return curve mnemonics."""
@@ -999,6 +1006,18 @@ class LASFile(object):
         assert isinstance(curve_item, CurveItem)
         self.curves.insert(ix, curve_item)
 
+    
+    def replace_curve_item(self, ix, curve_item):
+        """Replace a CurveItem.
+
+        Args:
+            ix (int): position to insert CurveItem i.e. 0 for start
+            curve_item (lasio.CurveItem)
+
+        """
+        self.delete_curve(ix=ix)
+        self.insert_curve_item(ix, curve_item)
+
     def add_curve(self, *args, **kwargs):
         """Deprecated. Use append_curve() or insert_curve() instead."""
         return self.append_curve(*args, **kwargs)
@@ -1049,6 +1068,32 @@ class LASFile(object):
             ix = self.curves.keys().index(mnemonic)
         self.curves.pop(ix)
 
+    def update_curve(self, mnemonic=None, ix=None, data=False, unit=False, descr=False, value=False):
+        """Update a curve.
+
+        Keyword Arguments:
+            ix (int): index of curve in LASFile.curves.
+            mnemonic (str): mnemonic of curve.
+            data (ndarray): new data array (False if no update desired)
+            unit (str): new value for unit (False if no update desired)
+            descr (str): new description (False if no update desired)
+            value (str/int/float etc): new value (False if no update desired)
+
+        The index takes precedence over the mnemonic.
+
+        """
+        if ix is None:
+            ix = self.curves.keys().index(mnemonic)
+        curve = self.curves[ix]
+        if data is not False:
+            curve.data = data
+        if unit is not False:
+            curve.unit = unit
+        if descr is not False:
+            curve.descr = descr
+        if value is not False:
+            curve.value = value
+
     @property
     def json(self):
         """Return object contents as a JSON string."""


Test patch:
diff --git a/tests/test_api.py b/tests/test_api.py
index 7850dbb..95ddaa5 100644
--- a/tests/test_api.py
+++ b/tests/test_api.py
@@ -8,6 +8,7 @@ import numpy as np
 import pytest
 
 import lasio
+import lasio.examples
 from lasio import read, las
 
 import logging
@@ -89,3 +90,17 @@ def test_data_attr():
     logger.debug("las.data = {}".format(las.data))
     # the .all() method assumes these are numpy ndarrays; that should be the case.
     assert (las.data == np.asarray([[1, 4, 7], [2, 5, 8], [3, 6, 9]])).all()
+
+def test_update_curve():
+    las = lasio.examples.open("sample.las")
+    las["NPHI"] = las["NPHI"] * 100
+    assert "NPHI" in las.keys()
+    assert not "NPHI:1" in las.keys()
+    assert not "NPHI:2" in las.keys()
+
+def test_replace_curve():
+    las = lasio.examples.open("sample.las")
+    las["NPHI"] = lasio.CurveItem("NPHI", "%", "Porosity", data=(las["NPHI"] * 100))
+    assert las.keys() == ["DEPT", "DT", "RHOB", "NPHI", "SFLU", "SFLA", "ILM", "ILD"]
+    assert (las["NPHI"] == [45, 45, 45]).all()
+    
\ No newline at end of file