//  [python]
def train_model(X, y, model_name="basic", region_vector=None, scoring='f1'):
    logger.info(f"Start train, base_model={args.base_model}")

    ## two types of features
    numeric_columns = list(X.select_dtypes(include=[np.number]).columns)
    cat_columns = list(X.select_dtypes(include=['object']).columns)
    logger.debug(f"numeric_columns={numeric_columns}, cat_columns={cat_columns}")

    ## compute weights
    if "_w" in model_name.lower() or "_w_seq" in model_name.lower():
        ## compute sample weights
        sample_weight = compute_sample_weights(region_vector)
    else:
        sample_weight = None

    ## compose models
    simple_imp = SimpleImputer(missing_values=np.nan, strategy='constant')
    ohe = OneHotEncoder(categories=[dna_seq_order] * k_mer_k, handle_unknown='ignore', sparse=False)
    if args.base_model.lower() == 'rf':
        # Make column transformer with one-hot encoder and simple imputer
        column_transform = make_column_transformer(
            (simple_imp, numeric_columns),
            (ohe, cat_columns),
            remainder='passthrough')

        default_rf_params.update({'random_state': args.random_state})
        logger.debug(f"\n\nDefault params={default_rf_params}")
        clf_model = RandomForestClassifier(**default_rf_params)
        gridcv_params = gridcv_rf_params
    elif args.base_model.lower() == 'xgboost':
        # Make column transformer with one-hot encoder and simple imputer
        column_transform = make_column_transformer(
            (ohe, cat_columns),
            remainder='passthrough')

        default_xgboost_params.update({'random_state': args.random_state})
        logger.debug(f"\n\nDefault params={default_xgboost_params}")
        clf_model = XGBClassifier(**default_xgboost_params)
        gridcv_params = gridcv_xgboost_params
    else:
        raise Exception(f"params base_model={args.base_model} not support")
    logger.debug(f"\nSearch gridcv_params={gridcv_params}")

    # Pipeline
    pipe = Pipeline([('prep', column_transform), ('cls', clf_model)])
    logger.debug(f"pipeline={pipe}")

    mm = RandomizedSearchCV(pipe, gridcv_params, n_iter=args.niter,
                            random_state=args.random_state, n_jobs=args.processors,
                            cv=StratifiedKFold(n_splits=args.cv, shuffle=True,
                                               random_state=args.random_state),
                            scoring=scoring, verbose=10, refit=True,
                            return_train_score=True)

    # mm = HalvingRandomSearchCV(pipe, gridcv_params, n_iter=args.niter,
    #                         random_state=args.random_state, n_jobs=args.processors,
    #                         cv=StratifiedKFold(n_splits=args.cv, shuffle=True,
    #                                            random_state=args.random_state),
    #                         scoring='f1', verbose=10, refit=True,
    #                         return_train_score=True)

    if sample_weight is not None:
        logger.info(f"Fit with sample_weight={sample_weight}")
        mm.fit(X, y, cls__sample_weight=list(sample_weight))
    else:
        logger.info(f"Fit without sample weight")
        mm.fit(X, y)
    sys.stdout.flush()
    sys.stderr.flush()
    logger.debug(f"best_params={mm.best_params_}")
    logger.debug(f"best_score={mm.best_score_}")

    ## save cv results
    perf_df = pd.DataFrame(mm.cv_results_)
    outfn = f'NA12878_{train_chrs}_{report_mm_name}_cv_results.xlsx'
    perf_df.to_excel(os.path.join(args.o, outfn))
    logger.info(f"save to {outfn}")

    ## save model
    outfn = f'NA12878_{train_chrs}_{report_mm_name}_model.pkl'
    joblib.dump(mm, os.path.join(args.o, outfn))
    logger.info(f"save model to {outfn}")

    return mm