1010from sklearn .decomposition import PCA
1111from sklearn .cluster import MiniBatchKMeans
1212
13- from log import Parameter
14-
15- def electrofacies (logs , curves , formations , n_clusters , log_scale = [], n_components = 0.85 ):
13+ def electrofacies (logs , formations , curves , n_clusters , log_scale = [], n_components = 0.85 , curve_name = 'FACIES' ):
14+ """
15+ Electrofacies function to group intervals by rock type. Also referred to as heterogenous rock analysis.
16+
17+ Parameters
18+ ----------
19+ logs : list of Log objects
20+ List of Log objects
21+ formations: list of formation names
22+ List of strings containg formation names which should be previously loaded into Log objects
23+ curves : list of curve names
24+ List of strings containing curve names as inputs in the electrofacies calculations
25+ n_clusters : int
26+ Number of clusters to group intervals. Number of electrofacies.
27+ log_scale : list of curve names
28+ List of string containing curve names which are preprocessed on a log scale. For example, deep
29+ resistivity separates better on a log scale, and is graph logarithmically when viewing data in
30+ a log viewer.
31+ n_components : int, float, None or string (default 0.85)
32+ Number of principal components to keep. If value is less than one, the number of principal
33+ components be the number required to exceed the explained variance.
34+ curve_name : str (default 'FACIES')
35+ Name of the new electrofacies curve.
36+
37+ Example
38+ -------
39+ >>> # loads sample Wolfcamp calculates electrofacies for that well
40+ >>> import petropy as ptr
41+ >>> log = ptr.log_data('WFMP') # reads sample Wolfcamp Log from las file
42+ >>> logs = [log]
43+ >>> f = ['WFMPA', 'WFMPB', 'WFMPC']
44+ >>> c = ['GR_N', 'RESDEEP_N', 'RHOB_N', 'NPHI_N', 'PE_N']
45+ >>> scale = ['RESDEEP_N']
46+ >>> logs = electrofacies(logs, f, c, 8, log_scale = scale)
47+
48+ >>> # loads logs from a list of paths and calculates electrofacies across the wells
49+ >>> import petropy as ptr
50+ >>> file_paths = ['path/to/log1.las', 'path/to/log2.las', 'path/to/log3.las']
51+ >>> logs = [ptr.Log(x) for x in file_paths] # create list of Log objects
52+ >>> tops_csv = 'path/to/tops.csv'
53+ >>> for log in logs:
54+ log.tops_from_csv(tops_csv) # add formation tops to wells
55+ >>> f = ['FORM1', 'FORM2'] # list of formation tops. If single formation use f = ['FORM']
56+ >>> c = ['GR_N', 'RESDEEP_N', 'RHOB_N', 'NPHI_N', 'PE_N']
57+ >>> scale = ['RESDEEP_N']
58+ >>> logs = electrofacies(logs, f, c, 8, log_scale = scale) # run electrofacies across logs in list
59+
60+ """
1661
1762 df = pd .DataFrame ()
1863
1964 for log in logs :
2065
21- if log .uwi == '' :
66+ if log .well [ 'UWI' ] is None :
2267 raise ValueError ('UWI required for log identification.' )
2368
24- log .curve_df ['UWI' ] = log .uwi
69+ log_df = log .df ()
70+ log_df ['UWI' ] = log .well ['UWI' ].value
71+ log_df ['DEPTH_INDEX' ] = np .arange (0 , len (log [0 ]))
2572
2673 for formation in formations :
2774 top = log .tops [formation ]
2875 bottom = log .next_formation_depth (formation )
29- df = df .append (log .curve_df [(log .curve_df .DEPTH >= top ) & (log .curve_df .DEPTH < bottom )])
30-
31- log .curve_df .drop (['UWI' ], 1 , inplace = True )
76+ depth_index = np .intersect1d (np .where (log [0 ] >= top )[0 ], np .where (log [0 ] < bottom )[0 ])
77+ df = df .append (log_df .iloc [depth_index ])
3278
3379 for s in log_scale :
3480 df [s ] = np .log (df [s ])
@@ -42,35 +88,47 @@ def electrofacies(logs, curves, formations, n_clusters, log_scale = [], n_compon
4288 components = pd .DataFrame (data = pc .transform (X ), index = df [not_null_rows ].index )
4389 components .columns = ['PC%i' % x for x in range (1 , pc .n_components_ + 1 )]
4490 components ['UWI' ] = df .loc [not_null_rows , 'UWI' ]
91+ components ['DEPTH_INDEX' ] = df .loc [not_null_rows , 'DEPTH_INDEX' ]
4592
4693 size = len (components ) // 20
4794 if size > 10000 :
4895 size = 10000
4996 elif size < 100 :
5097 size = 100
5198
52- facies = MiniBatchKMeans (n_clusters = n_clusters , batch_size = size ).fit_predict (components )
53-
54- df .loc [not_null_rows , 'FACIES' ] = facies
99+ df .loc [not_null_rows , curve_name ] = MiniBatchKMeans (n_clusters = n_clusters , batch_size = size ).fit_predict (components )
55100
56101 for log in logs :
57102
58- uwi = log .uwi
103+ uwi = log .well [ 'UWI' ]. value
59104
60105 for v , vector in enumerate (pc .components_ ):
61106 v += 1
62- column = 'PC%i' % v
107+ pc_curve = 'PC%i' % v
63108
64109 ### add eigenvector data to header ###
65110
66- pc_parameter = Parameter (name = column , unit = '' , des = 'Principal Component from electrofacies' )
67- log .curve_parameters .append (pc_parameter )
68- log .curve_values .append (column )
69- log .curve_df = log .curve_df .join (components [components .UWI == uwi ][column ])
70-
71- facies_parameter = Parameter (name = 'FACIES' , unit = '' , des = 'Electrofacies' )
72- log .curve_parameters .append (facies_parameter )
73- log .curve_values .append ('FACIES' )
74- log .curve_df = log .curve_df .join (df [df .UWI == uwi ]['FACIES' ])
111+ if pc_curve in log .keys ():
112+ data = log [pc_curve ]
113+ depth_index = components .loc [components .UWI == uwi , 'DEPTH_INDEX' ]
114+ data [depth_index ] = np .copy (components .loc [components .UWI == uwi , pc_curve ])
115+ else :
116+ data = np .empty (len (log [0 ]))
117+ data [:] = np .nan
118+ depth_index = components .loc [components .UWI == uwi , 'DEPTH_INDEX' ]
119+ data [depth_index ] = np .copy (components .loc [components .UWI == uwi , pc_curve ])
120+ log .add_curve (pc_curve , np .copy (data ),
121+ descr = 'Pricipal Component %i from electrofacies' % v )
122+
123+ if curve_name in log .keys ():
124+ data = log [curve_name ]
125+ depth_index = df .loc [df .UWI == uwi , 'DEPTH_INDEX' ]
126+ data [depth_index ] = df .loc [df .UWI == uwi , curve_name ]
127+ else :
128+ data = np .empty (len (log [0 ]))
129+ data [:] = np .nan
130+ depth_index = components .loc [components .UWI == uwi , 'DEPTH_INDEX' ]
131+ data [depth_index ] = np .copy (components .loc [components .UWI == uwi , pc_curve ])
132+ log .add_curve (curve_name , np .copy (data ), descr = 'Electrofacies' )
75133
76134 return logs
0 commit comments