Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,7 @@ class HuggingFaceIrisLogisticRegressionOpDesc
override def generateStandaloneCode(): String = {
val lengthLit = pyStringLiteral(petalLengthCmAttribute)
val widthLit = pyStringLiteral(petalWidthCmAttribute)
// The operator's guard: pd.isna answers for a JSON frame's NaN and a Tuple's None.
s"""import numpy as np
|import torch
|import torch.nn as nn
Expand All @@ -159,8 +160,6 @@ class HuggingFaceIrisLogisticRegressionOpDesc
|_classes = []
|_probs = []
|for _length, _width in zip(out1df[$lengthLit], out1df[$widthLit]):
| # The operator's guard: pandas answers for the NaN a frame read from
| # JSON holds and for the None a Tuple hands over.
| if pd.isna(_length) or pd.isna(_width):
| _classes.append(None)
| _probs.append(None)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,7 @@ class HuggingFaceSentimentAnalysisOpDesc
val positiveLit = pyStringLiteral(resultAttributePositive)
val neutralLit = pyStringLiteral(resultAttributeNeutral)
val negativeLit = pyStringLiteral(resultAttributeNegative)
// An empty cell, which the tokenizer rejects, keeps its row with the scores left empty.
s"""from transformers import AutoModelForSequenceClassification
|from transformers import AutoTokenizer, AutoConfig
|import numpy as np
Expand All @@ -137,10 +138,6 @@ class HuggingFaceSentimentAnalysisOpDesc
|for _col in ($positiveLit, $neutralLit, $negativeLit):
| out1df[_col] = 0.0
|for _idx, _text in out1df[${pyStringLiteral(attribute)}].items():
| # An empty cell reaches the frame as None, or as NaN once a column holds
| # nothing else, and the tokenizer rejects both. Keep the row and leave the
| # scores empty rather than ending the run over a value the model has
| # nothing to say about.
| if pd.isna(_text) or (isinstance(_text, str) and not _text.strip()):
| for _col in ($positiveLit, $neutralLit, $negativeLit):
| out1df.at[_idx, _col] = None
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -100,17 +100,14 @@ class HuggingFaceSpamSMSDetectionOpDesc
val attributeLit = pyStringLiteral(attribute)
val spamLit = pyStringLiteral(resultAttributeSpam)
val probabilityLit = pyStringLiteral(resultAttributeProbability)
// An empty cell, which the pipeline rejects, keeps its row with the results left empty.
s"""from transformers import pipeline
|import pandas as pd
|
|_pipeline = pipeline("text-classification", model="mrm8488/bert-tiny-finetuned-sms-spam-detection")
|out1df = in1df.copy()
|
|def _classify(_t):
| # An empty cell reaches the frame as None, or as NaN once a column holds
| # nothing else, and the pipeline rejects both. Keep the row and leave the
| # results empty rather than ending the run over a value the model has
| # nothing to say about.
| if pd.isna(_t) or (isinstance(_t, str) and not _t.strip()):
| return None
| return _pipeline(_t)[0]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,7 @@ class HuggingFaceTextSummarizationOpDesc
// once, generate a summary per row, and add the STRING result column to
// produce out1df.
override def generateStandaloneCode(): String = {
// An empty cell, which the tokenizer rejects, keeps its row with the summary left empty.
s"""from transformers import BertTokenizerFast, EncoderDecoderModel
|import pandas as pd
|import torch
Expand All @@ -110,10 +111,6 @@ class HuggingFaceTextSummarizationOpDesc
|out1df = in1df.copy()
|_summaries = []
|for _text in out1df[${pyStringLiteral(attribute)}]:
| # An empty cell reaches the frame as None, or as NaN once a column holds
| # nothing else, and the tokenizer rejects both. Keep the row and leave the
| # summary empty rather than ending the run over a value the model has
| # nothing to say about.
| if pd.isna(_text) or (isinstance(_text, str) and not _text.strip()):
| _summaries.append(None)
| continue
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -300,6 +300,8 @@ class MachineLearningScorerOpDesc extends PythonOperatorDescriptor with Standalo

override def generateStandaloneCode(): String = {
val isRegressionStr = if (isRegression) "True" else "False"
// Rows come back Class first, then the metrics in the order picked, as the operator declares.
// The drop goes to its own name, and an empty result raises rather than scoring nothing.
s"""import pandas as pd
|from sklearn.metrics import accuracy_score, precision_score, recall_score, f1_score, mean_squared_error, root_mean_squared_error, mean_absolute_error, r2_score
|
Expand All @@ -320,9 +322,6 @@ class MachineLearningScorerOpDesc extends PythonOperatorDescriptor with Standalo
|
| labels = ['class_' + str(label) if type(label) != str else label for label in labels]
| result['Class'] = labels
| # Class first and the metrics in the order they were picked, which is
| # the column order this operator declares and so the order of the rows
| # the workflow shows.
| return pd.DataFrame(result)[['Class'] + metric_list]
|
|def regression_metrics(y_true, y_pred, metric_list):
Expand All @@ -332,14 +331,9 @@ class MachineLearningScorerOpDesc extends PythonOperatorDescriptor with Standalo
| result[metric] = metrics_func[metric](y_true, y_pred)
| return pd.DataFrame(result, index=[0])
|
|# Bound to a name of its own: the same frame can feed another branch of the
|# plan, which must still see every row.
|scored_df = in1df.dropna(subset=[${pyStringLiteral(actualValueColumn)}, ${pyStringLiteral(
predictValueColumn
)}])
|# Nothing survived the drop. The metrics answer that badly and each in its own
|# way: the regression ones raise from inside scikit-learn, and the
|# classification ones return a NaN score, which reads as a result.
|if scored_df.empty:
| raise ValueError("No rows left to score: every row is missing the actual value, the predicted value, or both.")
|y_true = scored_df[${pyStringLiteral(actualValueColumn)}]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@ $reportMissingKept
val narrowTrain = dropNonFeatureColumns("X_train", "")
val narrowTest = dropNonFeatureColumns("X_test", "")

// Both frames drop the rows the operator drops, into local names, and each reports its skips.
s"""${getImportStatements}
|from sklearn.metrics import accuracy_score, f1_score, precision_score, recall_score
|from sklearn.pipeline import make_pipeline
Expand All @@ -95,10 +96,6 @@ $reportMissingKept
|import numpy as np
|import pandas as pd
|
|# The same rows the operator drops, and on both frames: the model is fitted
|# on one and scored on the other, so narrowing only one side would fit and
|# score on different data. A local name rather than a reassignment, since
|# the input variable belongs to whichever operator produced it.
|_train = ${dropMissingRowsStandalone("in1df")}
|if len(_train) < len(in1df):
| print("Skipped", len(in1df) - len(_train), "of", len(in1df), "rows with missing values")
Expand All @@ -108,8 +105,6 @@ $reportMissingKept
|model = make_pipeline(${vectorizerStage(c => pyStringLiteral(c))}$tfidfPart$estimator()).fit(X_train, Y_train)
|
|_test = ${dropMissingRowsStandalone("in2df")}
|# The operator runs once per port, so it says this for the table it scores
|# as well as for the one it fits.
|if len(_test) < len(in2df):
| print("Skipped", len(in2df) - len(_test), "of", len(in2df), "rows with missing values")
|Y_test = _test[$targetLit]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -104,16 +104,13 @@ class SklearnLinearRegressionOpDesc

override def generateStandaloneCode(): String = {
val targetLit = pyStringLiteral(target)
// Both frames drop the rows the operator drops, into local names, and each reports its skips.
s"""from sklearn.metrics import mean_absolute_error, r2_score
|from sklearn.pipeline import make_pipeline
|from sklearn.linear_model import LinearRegression
|from sklearn.preprocessing import PolynomialFeatures
|import pandas as pd
|
|# The same rows the operator drops, and on both frames: the model is fitted
|# on one and scored on the other, so narrowing only one side would fit and
|# score on different data. Local names rather than reassignments, since the
|# input variables belong to whichever operators produced them.
|_train = in1df.dropna()
|if len(_train) < len(in1df):
| print("Skipped", len(in1df) - len(_train), "of", len(in1df), "rows with missing values")
Expand All @@ -127,8 +124,6 @@ class SklearnLinearRegressionOpDesc
|model = pipeline.fit(X_train, Y_train)
|
|_test = in2df.dropna()
|# The operator runs once per port, so it says this for the table it scores
|# as well as for the one it fits.
|if len(_test) < len(in2df):
| print("Skipped", len(in2df) - len(_test), "of", len(in2df), "rows with missing values")
|Y_test = _test[$targetLit]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -132,28 +132,18 @@ class SklearnTestingOpDesc
val isRegressionStr = if (isRegression) "True" else "False"
val modelLit = pyStringLiteral(model)
val targetLit = pyStringLiteral(target)
// Drops on every column as the executor does, since a fitted model hides its estimator.
// Each row's model scores the frame itself, kept two-dimensional for predict.
s"""from sklearn.metrics import accuracy_score, f1_score, precision_score, recall_score, root_mean_squared_error, mean_absolute_error, r2_score
|
|out1df = in1df.copy()
|# The same drop the executor makes before it scores: the model arrives
|# already fitted, so this operator cannot ask which estimator it holds and
|# drops on every column to be safe. Scoring the rows it skips would answer
|# with a number the run never reported, or fail inside scikit-learn.
|rows_read = len(in2df)
|scored_df = in2df.dropna()
|if len(scored_df) < rows_read:
| print("Skipped", rows_read - len(scored_df), "of", rows_read, "rows with missing values")
|Y = scored_df[$targetLit]
|X = scored_df.drop($targetLit, axis=1)
|${narrowToFittableColumns("X", "")}
|# predict wants (n_samples, n_features). Squeezing the frame first collapsed
|# a single feature, or a single surviving row, to one dimension, which
|# scikit-learn rejects.
|#
|# The model port carries one model per row and the executor scores each of
|# them on its own, so a row has to be given the score of the model it holds.
|# One score written down the column would report the first model's answer
|# for all of them.
|predictions = [_model.predict(X) for _model in in1df[$modelLit]]
|if $isRegressionStr:
| out1df["R2"] = [r2_score(Y, _p) for _p in predictions]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -74,15 +74,13 @@ $reportMissingKept
val modelNameLit = pyStringLiteral(getUserFriendlyModelName)
val narrowX = dropNonFeatureColumns("X", "")

// The rows the operator drops, into a local name, since the input belongs to its producer.
s"""${getImportStatements}
|from sklearn.pipeline import make_pipeline
|from sklearn.compose import ColumnTransformer
|from sklearn.feature_extraction.text import CountVectorizer, TfidfTransformer
|import pandas as pd
|
|# The same rows the operator drops. A local name rather than a
|# reassignment, since the input variable belongs to whichever operator
|# produced it.
|_train = ${dropMissingRowsStandalone("in1df")}
|if len(_train) < len(in1df):
| print("Skipped", len(in1df) - len(_train), "of", len(in1df), "rows with missing values")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -152,10 +152,8 @@ class UnnestStringOpDesc extends FlatMapOpDesc with StandaloneCodeGenerator {
.flatMap(schema => scala.util.Try(schema.getAttribute(attribute)).toOption)
.map(_.getType)
val column = renderedAsText(s"out1df[$attributeLit]", declared)
s"""# Nothing in the column unnests to nothing, the way the operator answers a null
|# field with no rows at all. Dropped before the split rather than after: the
|# rendering would turn the empty cell into text and unnest that.
|out1df = in1df[in1df[$attributeLit].notna()].copy()
// A null unnests to no rows, so it is dropped before the rendering turns it into text.
s"""out1df = in1df[in1df[$attributeLit].notna()].copy()
|out1df[$resultLit] = $column.str.split($delim, regex=True)
|out1df = out1df.explode($resultLit, ignore_index=True)
|out1df = out1df[(out1df[$resultLit].notna()) & (out1df[$resultLit] != "")].reset_index(drop=True)""".stripMargin
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,7 @@ class IcicleChartOpDesc extends PythonOperatorDescriptor with PlotlyStandaloneCo
// reason for "no chart" is where the reader looks for the chart — printing it
// to the terminal alone left that file absent. render_error's continuation
// line keeps the runtime path's indentation, since the HTML is triple-quoted
// and those spaces reach the browser.
// and those spaces reach the browser. chart_df is a copy: the input can feed another branch.
s"""def render_error(error_msg):
| return '''<h1>Icicle chart is not available.</h1>
| <p>Reason is: {} </p>
Expand All @@ -161,8 +161,6 @@ class IcicleChartOpDesc extends PythonOperatorDescriptor with PlotlyStandaloneCo
|if in1df.empty:
| fail("input table is empty.")
|else:
| # On a copy: the same frame can feed another branch of the plan, and
| # both the assignment and the drop below would otherwise reach it.
| chart_df = in1df.copy()
| chart_df[$valueLit] = chart_df[chart_df[$valueLit] > 0][$valueLit]
| chart_df = chart_df.dropna(subset=[$attributes])
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -180,7 +180,8 @@ class BarChartOpDesc extends PythonOperatorDescriptor with PlotlyStandaloneCode
// reason for "no chart" is where the reader looks for the chart — printing it
// to the terminal alone left that file absent. render_error's continuation
// line keeps the runtime path's indentation, since the HTML is triple-quoted
// and those spaces reach the browser.
// and those spaces reach the browser. Empty is checked before the dropna, since an empty
// table read back from JSONL has no columns to name, and chart_df keeps the input whole.
s"""def render_error(error_msg):
| return '''<h1>Bar chart is not available.</h1>
| <p>Reason is: {} </p>
Expand All @@ -194,13 +195,8 @@ class BarChartOpDesc extends PythonOperatorDescriptor with PlotlyStandaloneCode
|if $fieldsLit == $valueLit:
| fail("Fields should not have the same value.")
|elif in1df.empty:
| # Checked before the dropna, unlike the runtime path: an empty table read
| # back from JSONL carries no columns at all, so naming them in `subset`
| # would raise a KeyError instead of reporting the empty table.
| fail("Table should not have any empty/null values or fields.")
|else:
| # Bound to a name of its own: the same frame can feed another branch
| # of the plan, which must still see every row.
| chart_df = in1df.dropna(subset=[$valueLit, $fieldsLit])
| if chart_df.empty:
| fail("Table should not have any empty/null values or fields.")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -171,7 +171,7 @@ class BoxViolinPlotOpDesc extends PythonOperatorDescriptor with PlotlyStandalone
// reason for "no chart" is where the reader looks for the chart — printing it
// to the terminal alone left that file absent. render_error's continuation
// line keeps the runtime path's indentation, since the HTML is triple-quoted
// and those spaces reach the browser.
// and those spaces reach the browser. chart_df keeps the input whole for other branches.
s"""def render_error(error_msg):
| return '''<h1>Box/Violin Plot is not available.</h1>
| <p>Reason is: {} </p>
Expand All @@ -185,8 +185,6 @@ class BoxViolinPlotOpDesc extends PythonOperatorDescriptor with PlotlyStandalone
|if in1df.empty:
| fail("input table is empty.")
|else:
| # Bound to a name of its own: the same frame can feed another branch
| # of the plan, which must still see every row.
| chart_df = in1df.dropna(subset=[$valueLit])
| if chart_df.empty:
| fail("value column contains only non-positive numbers or nulls.")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -181,7 +181,8 @@ class BubbleChartOpDesc extends PythonOperatorDescriptor with PlotlyStandaloneCo
// to the terminal alone left that file absent. The heading is the
// runtime path's, TreeMap and all, so both paths say the same thing.
// render_error's continuation line keeps the runtime path's indentation, since
// the HTML is triple-quoted and those spaces reach the browser.
// the HTML is triple-quoted and those spaces reach the browser. chart_df keeps the
// input whole for other branches.
s"""def render_error(error_msg):
| return '''<h1>TreeMap is not available.</h1>
| <p>Reasons are: {} </p>
Expand All @@ -195,8 +196,6 @@ class BubbleChartOpDesc extends PythonOperatorDescriptor with PlotlyStandaloneCo
|if in1df.empty:
| fail("Input table is empty.")
|else:
| # Bound to a name of its own: the same frame can feed another branch
| # of the plan, which must still see every row.
| chart_df = in1df.dropna(subset=[$xLit, $yLit, $zLit])
| if chart_df.empty:
| fail("No valid rows left (every row has at least 1 missing value).")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -268,7 +268,8 @@ class BulletChartOpDesc extends PythonOperatorDescriptor with PlotlyStandaloneCo
.mkString("[", ", ", "]")

// render_error's continuation line keeps the runtime path's indentation — the
// HTML is triple-quoted, so those spaces reach the browser.
// HTML is triple-quoted, so those spaces reach the browser. The JSON holds one chart
// per row, and a lone figure is written as one object, as every one-chart operator does.
s"""def render_error(error_msg):
| return '''<h1>Bullet chart is not available.</h1>
| <p>Reason: {} </p>'''.format(error_msg)
Expand Down Expand Up @@ -353,9 +354,6 @@ class BulletChartOpDesc extends PythonOperatorDescriptor with PlotlyStandaloneCo
| html_chunks.append(html_chunk)
| except Exception as e:
| html_chunks.append(render_error(f"Error generating bullet chart: {str(e)}"))
| # One chart per row, so the file holds the whole sequence.
| # A single figure still writes as a lone object, which is
| # what every one-chart operator writes.
| if len(figs) == 1:
| figs[0].write_json(outputJson)
| elif figs:
Expand Down
Loading
Loading