code: def npmle(
      left,
      right,
      tol=1e-7,
      weights=None,
      verbose=False,
      max_iter=1e5,
      optimize=False,
      fit_method="em",
  ):
      """
      left and right are closed intervals.
      TODO: extend this to open-closed intervals.
      """
      left, right = np.asarray(left), np.asarray(right)
  
      if weights is None:
          weights = np.ones_like(left)
  
      # perform a group by to get unique observations and weights
      df_ = (
          pd.DataFrame({"l": left, "r": right, "w": weights})
          .groupby(["l", "r"])
          .sum()
      )
      weights = df_["w"].values
      unique_obs = df_.index.values
  
      # create objects needed
      turnbull_intervals = create_turnbull_intervals(left, right)
      observation_intervals = create_observation_intervals(unique_obs)
      turnbull_lookup = create_turnbull_lookup(
          turnbull_intervals, observation_intervals
      )
  
      if fit_method == "em":
          p = expectation_maximization_fit(
              observation_intervals,
              turnbull_intervals,
              turnbull_lookup,
              weights,
              tol,
              max_iter,
              optimize,
              verbose,
          )
      elif fit_method == "scipy":
          p = scipy_minimize_fit(
              turnbull_lookup, turnbull_intervals, weights, tol, verbose
          )
  
      return p, turnbull_intervals
  
imports: from collections import defaultdict, namedtuple
  import warnings
  import numpy
  from numpy.linalg import norm
  import pandas
  from lifelines.exceptions import ConvergenceWarning
  from typing import *
label_desc: Use image node_id 11 for calling a global function with example usage: npmle(left, right, tol, weights, verbose, max_iter, optimize, fit_method) and returns: p,  turnbull_intervals