<SYSTEM_TASK:>
Given the following text description, write Python code to implement the functionality described below step by step
<END_TASK>
<USER_TASK:>
Description:
 Wells
Wells are one of the fundamental objects in welly.
Well objects include collections of Curve objects. Multiple Well objects can be stored in a Project.
On this page, we take a closer look at the Well.
First, some preliminaries...
Step1:  Load a well from LAS
In the Quick Start guide you saw how to quickly create a Project from a well with
Step2:  There are a lot of problems here
Step3:  That's better!
Later on, we'll look at how we can go a step further, extracting the more accurate 
Well header
Metadata about the well is stored in its header attribute
Step4:  Important note
At present, the well's header contains a DataFrame with the entire LAS file header.
In a future release, only the well information from the WELL part of the file will be stored in the well's header. (The Params data goes into the well.location attribute, and the Curve data goes into Welly's Curve objects.)
Well location
The well's location contains the location info from PARAMS, and will also store the well's 3D positional information, if available.
Step5:  The CRS for this well is missing; we can add one if we know it
Step6:  Right now there's no position log — we need to load a deviation survey.
Step7:  Add deviation data to a well
Let's load another well
Step8:  The columns are MD, inclination, azimuth, and TVD.
Step9:  add_deviation wants only MD, inclination and azimuth, in that order. Given an array like that, it computes a position log.
Step10:  The columns in the position log are x offset, y offset, and TVD.
Step11:  Quick plot
welly produces matplotlib plots easily... but they aren't all that pretty. You can pass in an Axes object as ax, and you can embellish the plots by adding more matplotlib commands.
First, let's do the simplest thing possible
Step12:  Since we have a position log, it's worth plotting TVD as well (though it's almost the same as MD in this well).
Step14:  You can control the plotting style, but it requires a striplog.Legend. We find the easiest way to build one is with a CSV-like text string
Step15:  Export curves to data matrix
Make a NumPy array out of the Curves in the well
Step16:  You can use aliases here, and it's helpful to know which curve is which. You can also start and stop at new depths, to cut out the NaNs
Step17:  Export curves to pandas
You can always get the curve data as a DataFrame. The depth will be the index
Step18:  To get the UWI of the well as well, e.g. if you want to combine multiple wells
Step19:  If you have several wells, you can also use welly.Project.df() to do the concatenation for you.
Note that you can also use aliases with the DataFrame creation, or create a new 'basis' (depth in this case)
Step20:  Make an 'empty' well
Step21:  We can set the UWI and name of a well directly on the well object, but these are the only attributes of the well we can set in this way.
<ASSISTANT_TASK:>
Python Code:
import numpy as np
import matplotlib.pyplot as plt
import welly
welly.__version__
Explanation: Wells
Wells are one of the fundamental objects in welly.
Well objects include collections of Curve objects. Multiple Well objects can be stored in a Project.
On this page, we take a closer look at the Well.
First, some preliminaries...
End of explanation
from welly import Well
p129 = Well.from_las('https://geocomp.s3.amazonaws.com/data/P-129.LAS')
p129
Explanation: Load a well from LAS
In the Quick Start guide you saw how to quickly create a Project from a well with:
python
import welly
project = welly.from_las('path/to/well.las')
A welly.Project is a collection of welly.Well objects. But if you only have a single well, you may not need a Project; a Well object on its own will do. Then you could do this:
python
well, = welly.from_las('path/to/well.las')
The presence of the comma after well unpacks the single item into the welly variable. (This is a Python trick, it's not a Welly thing.)
Alternatively, you can use the Well.from_las() method to load a well by passing a filename as a str. This is really just a wrapper for lasio but instantiates a Header, Curves, etc.
End of explanation
remap = {
    'UWI': 'LIC',  # Commonly used unique name; not a true UWI.
    'KB': 'EKB',
    'TD': 'TDD',  # Driller's TD.
    'LATI': 'LOC',
    'LONG': 'UWI',
    'SECT': None,
    'TOWN': None,
    'LOC': None
}
p129 = Well.from_las('https://geocomp.s3.amazonaws.com/data/P-129.LAS', remap=remap)
p129
Explanation: There are a lot of problems here:
The Location is not stored correctly, with latitude stored in Location and Longitude stored in UWI (that's why it appears in the title row of this view).
There's less accurate Lat and Lon information stored in Section and Township; we should get rid of those.
There's no UWI, KB or TD, all of which would be useful to populate.
We can fix all this by 'remapping' some fields. This is done with a dictionary that maps a well's field to its location in the LAS file. For example, we can use the Well field ('Kennetcook #2') to as the UWI in our well with a mapping like: {'UWI': 'WELL'}. We can remove a bad item such as the Section name, by mapping to None:
End of explanation
p129.header
Explanation: That's better!
Later on, we'll look at how we can go a step further, extracting the more accurate 
Well header
Metadata about the well is stored in its header attribute:
End of explanation
p129.location
Explanation: Important note
At present, the well's header contains a DataFrame with the entire LAS file header.
In a future release, only the well information from the WELL part of the file will be stored in the well's header. (The Params data goes into the well.location attribute, and the Curve data goes into Welly's Curve objects.)
Well location
The well's location contains the location info from PARAMS, and will also store the well's 3D positional information, if available.
End of explanation
p129.location.crs = welly.CRS.from_epsg(2038)
p129.location
Explanation: The CRS for this well is missing; we can add one if we know it:
End of explanation
p129.location.position
Explanation: Right now there's no position log — we need to load a deviation survey.
End of explanation
import numpy as np
from welly import Well
p130 = Well.from_las('https://geocomp.s3.amazonaws.com/data/P-130.LAS')
dev = np.loadtxt('https://geocomp.s3.amazonaws.com/data/P-130_deviation_survey.csv',
                 delimiter=',', skiprows=1
                )
Explanation: Add deviation data to a well
Let's load another well:
End of explanation
dev[:5]
Explanation: The columns are MD, inclination, azimuth, and TVD.
End of explanation
p130.location.add_deviation(dev[:, :3], td=2618.3)
Explanation: add_deviation wants only MD, inclination and azimuth, in that order. Given an array like that, it computes a position log.
End of explanation
p130.location.position[:5]
p130.location.trajectory()
p130.location.plot_plan()
p130.location.plot_3d()
Explanation: The columns in the position log are x offset, y offset, and TVD.
End of explanation
p130.plot()
Explanation: Quick plot
welly produces matplotlib plots easily... but they aren't all that pretty. You can pass in an Axes object as ax, and you can embellish the plots by adding more matplotlib commands.
First, let's do the simplest thing possible:
End of explanation
tracks = ['MD', 'GR', 'RHOB', ['M2R1', 'M2R9'], 'TVD']
p130.plot(tracks=tracks)
Explanation: Since we have a position log, it's worth plotting TVD as well (though it's almost the same as MD in this well).
End of explanation
from striplog import Legend
curve_legend_csv = curve mnemonic, colour,    lw,  ls,    xlim, xscale
                      GR,             #ff0000,   1.0,  -, "0,200", linear
                      RHOB,           gray,      1.0, --,        , linear
                      M2R9,           darkgreen, 1.0,  -,        , log
                      M2R1,           lightgreen,1.0,  -,        , log
legend = Legend.from_csv(text=curve_legend_csv)
tracks = ['MD', 'GR', 'RHOB', ['M2R1', 'M2R9'], 'TVD']
p130.plot(tracks=tracks, legend=legend)
Explanation: You can control the plotting style, but it requires a striplog.Legend. We find the easiest way to build one is with a CSV-like text string:
End of explanation
p130.data_as_matrix()
Explanation: Export curves to data matrix
Make a NumPy array out of the Curves in the well:
End of explanation
alias = {'Gamma': ['GRC', 'GR', 'GRX'],
         'Density': ['RHOZ', 'RHOB'],
        }
X, depth, features = p130.data_as_matrix(keys=['Gamma', 'Density', 'DT'],
                                         alias=alias,
                                         start=1200, step=1,
                                         return_meta=True
                                        )
X.shape
depth
features
Explanation: You can use aliases here, and it's helpful to know which curve is which. You can also start and stop at new depths, to cut out the NaNs:
End of explanation
df = p130.df()
df.head()
df.GR.plot()
Explanation: Export curves to pandas
You can always get the curve data as a DataFrame. The depth will be the index:
End of explanation
df = p130.df(uwi=True)
df.head()
Explanation: To get the UWI of the well as well, e.g. if you want to combine multiple wells:
End of explanation
keys = ['CALI', 'Gamma', 'Density']
df = p130.df(keys=keys, alias=alias, rename_aliased=True)
df.head()
Explanation: If you have several wells, you can also use welly.Project.df() to do the concatenation for you.
Note that you can also use aliases with the DataFrame creation, or create a new 'basis' (depth in this case):
End of explanation
import welly
w = welly.Well()
w.header  # is empty
Explanation: Make an 'empty' well
End of explanation
w.uwi = 'foo'
w.uwi
w.header
Explanation: We can set the UWI and name of a well directly on the well object, but these are the only attributes of the well we can set in this way.
End of explanation
<END_TASK>