Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Feat] restore get metadata function for LLM #146

Merged
merged 2 commits into from
Jul 2, 2024
Merged
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
58 changes: 58 additions & 0 deletions geoda-ai/src/ai/assistant/callbacks/callback-box.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import {BoxplotDataProps, CreateBoxplotProps, createBoxplot} from '@/utils/plots/boxplot-utils';
import {DataContainerInterface} from '@kepler.gl/utils';
import {createErrorResult, ErrorOutput} from '../custom-functions';
import {getColumnData} from '@/utils/data-utils';
import {CHAT_COLUMN_DATA_NOT_FOUND} from '@/constants';
import {generateRandomId} from '@/utils/ui-utils';

export type BoxplotOutput = {
type: 'boxplot';
name: string;
result: {
id: string;
variables: string[];
boundIQR: number;
boxplot: BoxplotDataProps['boxData'];
};
data: BoxplotDataProps;
};

export type BoxplotFunctionProps = {
variableNames: string[];
boundIQR: number;
};

export function boxplotCallback(
{variableNames, boundIQR}: BoxplotFunctionProps,
{dataContainer}: {dataContainer: DataContainerInterface}
): BoxplotOutput | ErrorOutput {
// get data from variable
const data: CreateBoxplotProps['data'] = variableNames.reduce(
(prev: CreateBoxplotProps['data'], cur: string) => {
const values = getColumnData(cur, dataContainer);
prev[cur] = values;
return prev;
},
{}
);

// check column data is empty
if (!data || Object.keys(data).length === 0) {
return createErrorResult(CHAT_COLUMN_DATA_NOT_FOUND);
}

// call boxplot function
const boxplot = createBoxplot({data, boundIQR: boundIQR || 1.5});

return {
type: 'boxplot',
name: 'Boxplot',
result: {
id: generateRandomId(),
variables: variableNames,
boundIQR,
boxplot: boxplot.boxData
},
data: boxplot
};
}
62 changes: 62 additions & 0 deletions geoda-ai/src/ai/assistant/callbacks/callback-bubble.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import {getColumnData} from '@/utils/data-utils';
import {createErrorResult, ErrorOutput} from '../custom-functions';
import {CHAT_COLUMN_DATA_NOT_FOUND} from '@/constants';
import {generateRandomId} from '@/utils/ui-utils';
import {DataContainerInterface} from '@kepler.gl/utils';

export type BubbleChartOutput = {
type: 'bubble';
name: string;
result: {
id: string;
variableX: string;
variableY: string;
variableSize: string;
variableColor?: string;
};
};

type BubbleCallbackProps = {
variableX: string;
variableY: string;
variableSize: string;
variableColor?: string;
};

export function bubbleCallback(
{variableX, variableY, variableSize, variableColor}: BubbleCallbackProps,
{dataContainer}: {dataContainer: DataContainerInterface}
): BubbleChartOutput | ErrorOutput {
const columnDataX = getColumnData(variableX, dataContainer);
const columnDataY = getColumnData(variableY, dataContainer);
const columnDataSize = getColumnData(variableSize, dataContainer);
const columnDataColor = variableColor ? getColumnData(variableColor, dataContainer) : undefined;

// Check if both variables' data are successfully accessed
if (
!columnDataX ||
columnDataX.length === 0 ||
!columnDataY ||
columnDataY.length === 0 ||
!columnDataSize ||
columnDataSize.length === 0
) {
return createErrorResult(CHAT_COLUMN_DATA_NOT_FOUND);
}

if (variableColor && (!columnDataColor || columnDataColor.length === 0)) {
return createErrorResult(CHAT_COLUMN_DATA_NOT_FOUND);
}

return {
type: 'bubble',
name: 'Bubble Chart Data',
result: {
id: generateRandomId(),
variableX,
variableY,
variableSize,
variableColor
}
};
}
51 changes: 51 additions & 0 deletions geoda-ai/src/ai/assistant/callbacks/callback-histogram.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import {DataContainerInterface} from '@kepler.gl/utils';
import {createErrorResult, ErrorOutput} from '../custom-functions';
import {getColumnData} from '@/utils/data-utils';
import {HistogramDataProps, createHistogram} from '@/utils/plots/histogram-utils';
import {CHAT_COLUMN_DATA_NOT_FOUND} from '@/constants';
import {generateRandomId} from '@/utils/ui-utils';

export type HistogramOutput = {
type: 'histogram';
name: string;
result: {
id: string;
variableName: string;
numberOfBins: number;
histogram: Array<Omit<HistogramDataProps, 'items'>>;
};
data: HistogramDataProps[];
};

type HistogramCallbackProps = {
k: number;
variableName: string;
};

export function histogramCallback(
{k, variableName}: HistogramCallbackProps,
{dataContainer}: {dataContainer: DataContainerInterface}
): HistogramOutput | ErrorOutput {
// get column data
const columnData = getColumnData(variableName, dataContainer);

// check column data is empty
if (!columnData || columnData.length === 0) {
return createErrorResult(CHAT_COLUMN_DATA_NOT_FOUND);
}

// call histogram function
const histogram = createHistogram(columnData, k);

return {
type: 'histogram',
name: 'Histogram',
result: {
id: generateRandomId(),
variableName,
numberOfBins: k,
histogram
},
data: histogram
};
}
105 changes: 105 additions & 0 deletions geoda-ai/src/ai/assistant/callbacks/callback-localmoran.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
import {localMoran} from 'geoda-wasm';

import {WeightsProps} from '@/actions';
import {createErrorResult, ErrorOutput} from '../custom-functions';
import {CHAT_FIELD_NAME_NOT_FOUND, CHAT_WEIGHTS_NOT_FOUND} from '@/constants';
import {
checkIfFieldNameExists,
getColumnDataFromKeplerLayer,
isNumberArray
} from '@/utils/data-utils';

export type UniLocalMoranOutput = {
type: 'lisa';
name: string;
result: {
significanceThreshold: number;
permutations: number;
variableName: string;
weightsID: string;
numberOfHighHighClusters: number;
numberOfLowLowClusters: number;
numberOfHighLowClusters: number;
numberOfLowHighClusters: number;
numberOfIsolatedClusters: number;
globalMoranI: number;
};
data: any;
};

type UniLocalMoranCallbackProps = {
variableName: string;
weightsID: string;
permutations?: number;
significanceThreshold?: number;
};

export async function univariateLocalMoranCallback(
{
variableName,
weightsID,
permutations = 999,
significanceThreshold = 0.05
}: UniLocalMoranCallbackProps,
{tableName, visState, weights}: {tableName: string; visState: any; weights: WeightsProps[]}
): Promise<UniLocalMoranOutput | ErrorOutput> {
// get weights using weightsID
let selectWeight = weights.find((w: WeightsProps) => w.weightsMeta.id === weightsID);
if (weights.length === 0) {
return createErrorResult(CHAT_WEIGHTS_NOT_FOUND);
}
if (!selectWeight) {
// using last weights if weightsID is not found
selectWeight = weights[weights.length - 1];
}

// get table name from geodaState
if (!tableName) {
return createErrorResult('Error: table name is empty');
}
if (!checkIfFieldNameExists(tableName, variableName, visState)) {
return createErrorResult(
`${CHAT_FIELD_NAME_NOT_FOUND} For example, run local moran analysis using variable HR60 and KNN weights with k=4.`
);
}
// get column data
const columnData = await getColumnDataFromKeplerLayer(tableName, variableName, visState.datasets);
if (!columnData || columnData.length === 0) {
return createErrorResult('Error: column data is empty');
}
// check the type of columnData is an array of numbers
if (!isNumberArray(columnData)) {
return createErrorResult('Error: column data is not an array of numbers');
}
// run LISA analysis
const lm = await localMoran({
data: columnData,
neighbors: selectWeight?.weights,
permutation: permutations
});
// get cluster values using significant cutoff
const clusters = lm.pValues.map((p: number, i) => {
if (p > significanceThreshold) {
return 0;
}
return lm.clusters[i];
});

return {
type: 'lisa',
name: 'Local Moran',
result: {
significanceThreshold,
permutations,
variableName,
weightsID,
numberOfHighHighClusters: clusters.filter(c => c === 1).length,
numberOfLowLowClusters: clusters.filter(c => c === 2).length,
numberOfHighLowClusters: clusters.filter(c => c === 3).length,
numberOfLowHighClusters: clusters.filter(c => c === 4).length,
numberOfIsolatedClusters: clusters.filter(c => c === 5).length,
globalMoranI: lm.lisaValues.reduce((a, b) => a + b, 0) / lm.lisaValues.length
},
data: lm
};
}
5 changes: 3 additions & 2 deletions geoda-ai/src/ai/assistant/callbacks/callback-map.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,10 @@ import {
getColumnDataFromKeplerLayer,
isNumberArray
} from '@/utils/data-utils';
import {createErrorResult, CustomFunctionContext, ErrorOutput} from '../custom-functions';
import {createErrorResult, ErrorOutput} from '../custom-functions';
import {CHAT_COLUMN_DATA_NOT_FOUND, CHAT_FIELD_NAME_NOT_FOUND, MappingTypes} from '@/constants';
import {createMapBreaks} from '@/utils/mapping-functions';
import {VisState} from '@kepler.gl/schemas';

type CreateMapCallbackProps = {
method:
Expand All @@ -29,7 +30,7 @@ type MapCallbackOutput = {

export async function createMapCallback(
{method, variableName, k = 5, hinge}: CreateMapCallbackProps,
{tableName, visState}: CustomFunctionContext
{tableName, visState}: {tableName: string; visState: VisState}
): Promise<MapCallbackOutput | ErrorOutput> {
if (!checkIfFieldNameExists(tableName, variableName, visState)) {
return createErrorResult(
Expand Down
55 changes: 55 additions & 0 deletions geoda-ai/src/ai/assistant/callbacks/callback-metadata.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import {getKeplerLayer} from '@/utils/data-utils';
import {ErrorOutput, createErrorResult} from '../custom-functions';
import {VisState} from '@kepler.gl/schemas';

type MetaDataCallbackOutput = {
type: 'metadata';
name: string;
result: {
numberOfRows: number;
numberOfColumns: number;
columnNames: string[];
columnDataTypes: string[];
};
};

export async function getMetaDataCallback(
dataName: string,
{tableName, visState}: {tableName: string; visState: VisState}
): Promise<MetaDataCallbackOutput | ErrorOutput> {
if (!tableName) {
return createErrorResult('Error: table name is empty');
}

// get kepler.gl layer using tableName
const keplerLayer = getKeplerLayer(tableName, visState);
if (!keplerLayer) {
return createErrorResult('Error: layer is empty');
}

const dataContainer = keplerLayer.dataContainer;
if (!dataContainer) {
return createErrorResult('Error: data container is empty');
}

const columnNames: string[] = [];
const columnDataTypes: string[] = [];
for (let i = 0; i < dataContainer.numColumns(); i++) {
const field = dataContainer.getField?.(i);
if (field) {
columnNames.push(field.name);
columnDataTypes.push(field.type);
}
}

return {
type: 'metadata',
name: 'metadata',
result: {
numberOfRows: dataContainer.numRows(),
numberOfColumns: dataContainer.numColumns(),
columnNames,
columnDataTypes
}
};
}
Loading
Loading