-2

I have a complicated situation in Google Sheets and I don't know if it there is a possible solution for this in sheets. I have a list of records with various values both positive and negative, the sum of the positive numbers will always exceed the sum of the negative numbers by a large amount. These are the values in Column A and B

I need to do the following:

  1. sum entire column B (Values) to get the total
  2. using only the records which have positive values, get a list of which records to use to get me closest to the to the sum (the value should exceed the sum and the last record would be reduced to get me to the correct amount) as shown in Column D and E on the screenshot
  3. The return I need is the list of the record names and the values to to use for each record, column A will always have unique names

I am currently doing this manually by sorting and just selecting the data like I have done in Column D and E, but there are a lot of other steps that need to happen after this and I'd ideally like to automate this part.

Any solutions would be appreciated

Sample Data

I have researched this but can't find an exact solution. I have written basic apps-scripts in the past but I mostly use and prefer using formulas because of privacy but I am not finding something that fits this situation, so probably need a script

Here is a sample of the what the input data would look like so you can work with it

Record Name Value
Record 1 97867
Record 2 34653
Record 3 23456
Record 4 12132
Record 5 5778
Record 6 5688
Record 7 5675
Record 8 5456
Record 9 3423
Record 10 2435
Record 11 2235
Record 12 1232
Record 13 801
Record 14 342
Record 15 325
Record 16 324
Record 17 -21
Record 18 -34
Record 19 -3254
Record 20 -4356

The expected output would look something similar to this:

Record Name Value Use Record? Value if used
Record 1 97867 Yes 97867
Record 2 34653 Yes 34653
Record 3 23456 Yes 23456
Record 4 12132 Yes 12132
Record 5 5778 Yes 5778
Record 6 5688 Yes 5688
Record 7 5675 Yes 5675
Record 8 5456 Yes 5456
Record 9 3423 Yes 3423
Record 10 2435 Yes 29
Record 11 2235 No
Record 12 1232 No
Record 13 801 No
Record 14 342 No
Record 15 325 No
Record 16 324 No
Record 17 -21 No
Record 18 -34 No
Record 19 -3254 No
Record 20 -4356 No
3
  • Where did the 29 come from? Commented Feb 21 at 15:47
  • 1
    I guess the 29 is what that records was reduced to inorder to get the proper sum Commented Feb 21 at 15:52
  • Welcome to Stack Overflow. Are you just trying to get remaining inventory so that you can calculate COGS or capital gains? Commented Feb 21 at 22:19

4 Answers 4

2

Here's an option with formula, that would sort the data (if unsorted) and select the needed registers for the process:

=LET(totalSum,SUM(B2:B),
data,SORT(A2:B,2,0),
lastVal,REDUCE(totalSum,CHOOSECOLS(data,2),LAMBDA(a,v,IF(v<0,a,IF(a<v,a,a-v)))),
cumDiff,SCAN(totalSum,CHOOSECOLS(data,2),LAMBDA(a,v,SUM(a-v))),
position,XMATCH(lastVal,cumDiff),
VSTACK(
  FILTER(data,SEQUENCE(ROWS(data))<position),
  HSTACK(INDEX(data,position,1),lastVal)
))

The output would look like this:

Record 1    97867
Record 2    34653
Record 3    23456
Record 4    12132
Record 5    5778
Record 6    5688
Record 7    5675
Record 8    5456
Record 9    29
Total Sum   194157

To get exactly that display of data, as you shown, you can insert this table inside a formula and BYROW/MAP the registers to get those two columns. In C1 you can use:

=LET(totalSum,SUM(B2:B),
data,SORT(A2:B,2,0),
lastVal,REDUCE(totalSum,CHOOSECOLS(data,2),LAMBDA(a,v,IF(v<0,a,IF(a<v,a,a-v)))),
cumDiff,SCAN(totalSum,CHOOSECOLS(data,2),LAMBDA(a,v,SUM(a-v))),
position,XMATCH(lastVal,cumDiff),
result,VSTACK(
    FILTER(data,SEQUENCE(ROWS(data))<position),
    HSTACK(INDEX(data,position,1),lastVal),
    HSTACK("Total Sum",totalSum) 
  ),
VSTACK(HSTACK("Use?","Value if used"),
BYROW(A2:A,LAMBDA(row,IF(row="","",IF(ISERROR(XMATCH(row,CHOOSECOLS(result,1))),"No",HSTACK("Yes",VLOOKUP(row,result,2,0))))))))
Sign up to request clarification or add additional context in comments.

1 Comment

1

I chose not to fullfill your desires for a spreadsheet layout but here's a way to accomplish what you want in apps script:

Intial Data:

Record Name Value
Record 1 97,867.00
Record 2 34,653.00
Record 3 23,456.00
Record 4 12,132.00
Record 5 5,778.00
Record 6 5,688.00
Record 7 5,675.00
Record 8 5,456.00
Record 9 3,423.00
Record 10 2,435.00
Record 11 2,235.00
Record 12 1,232.00
Record 13 801.00
Record 14 342.00
Record 15 325.00
Record 16 324.00
Record 17 -21.00
Record 18 -34.00
Record 19 -3,254.00
Record 20 -4,356.00

Code:

function gettingSumRecords() {
  const ss = SpreadsheetApp.getActive()
  const sh = ss.getSheetByName("Sheet0");
  const osh = ss.getSheetByName("Sheet1");
  const vs = sh.getRange(2, 1, sh.getLastRow() - 1, 2).getValues().sort((a, b) => { return (b[1] - a[1]) }).filter(e => e[0] && e[1]);
  const sum = vs.reduce((a, r, i) => {
    a += Number(r[1]);
    return a;
  }, 0);
  const vObj = vs.reduce((a, [rec, v], i) => {
    if ((v + a.rttl) <= a.ts) {
      a.rttl += v;
      let d = a.ts - a.rttl;
      if (i == 0) {
        a.arr.push([rec, v, a.rttl, d])
      } else if (i > 0) {
        let lv = a.arr[a.arr.length - 1][3];
        if (v > 0 && v <= lv) {
          a.arr.push([rec, v, a.rttl, d])
        }
      }
    }
    return a;
  }, { rttl: 0, ts: sum, arr: [], hs:["Used Records","value","Running Total","Difference"] })
  //Logger.log(JSON.stringify(vs).replace(/],/g, '],\n'))
  //Logger.log(sum)
  //Logger.log(JSON.stringify(vObj.arr).replace(/],/g, '],\n'));
  osh.clearContents();
  vObj.arr.unshift(vObj.hs)
  osh.getRange(1,1,vObj.arr.length,vObj.arr[0].length).setValues(vObj.arr);
  const end = "is near";
}

Here's what my output looks like:

A B C D
1 Used Records value Running Total Difference
2 Record 1 97867 97867 96290
3 Record 2 34653 132520 61637
4 Record 3 23456 155976 38181
5 Record 4 12132 168108 26049
6 Record 5 5778 173886 20271
7 Record 6 5688 179574 14583
8 Record 7 5675 185249 8908
9 Record 8 5456 190705 3452
10 Record 9 3423 194128 29

1 Comment

Thank you very much for this, I have gone with a formula for the time being but I did test this and it works exactly as posted. I have a lot of other work to do on this and if at some point I switch to using Apps Script I will definitely include this. Thanks again
1

You can use Python to automate the process.

  1. fetch data from a Google sheet using Google Sheets API
  2. process it using DataFrame
  3. upload the updated data back to the sheet

Assuming the data is already sorted,

import re
import pandas as pd
import pygsheets

def convert_google_sheet_url(url):
    # Regular expression to match and capture the necessary part of the URL
    pattern = r'https://docs\.google\.com/spreadsheets/d/([a-zA-Z0-9-_]+)(/edit#gid=(\d+)|/edit.*)?'

    # Replace function to construct the new URL for CSV export
    # If gid is present in the URL, it includes it in the export URL, otherwise, it's omitted
    def replacement(m): return f'https://docs.google.com/spreadsheets/d/{m.group(1)}/export?' + (
        f'gid={m.group(3)}&' if m.group(3) else '') + 'format=csv'

    # Replace using regex
    new_url = re.sub(pattern, replacement, url)

    return new_url


def fetch_google_sheet_data(url):
    new_url = convert_google_sheet_url(url)
    return pd.read_csv(new_url)


def process_dataframe(df):
    SUM = df['Value'].sum().astype(int)
    sum_temp = 0
    i = 0

    df['Use Record?'] = 'No'
    df['Value if used'] = 0

    while sum_temp + df['Value'].iloc[i] <= SUM:
        sum_temp += df['Value'].iloc[i]
        df.at[i, 'Use Record?'] = 'Yes'
        df.at[i, 'Value if used'] = df['Value'].iloc[i]
        i += 1

    if sum_temp < SUM:
        df.at[i, 'Use Record?'] = 'Yes'
        df.at[i, 'Value if used'] = SUM - sum_temp

    return df


# this appends the new data to the original worksheet
def update_google_sheet(df, sheet_name, service_file):
    google_client = pygsheets.authorize(service_file=service_file)
    sheet = google_client.open(sheet_name)
    worksheet = sheet[0] # select the first sheet
    worksheet.set_dataframe(df, (1, 1)) # update the google sheet, starting at cell B2


if __name__ == "__main__":
    url = "https://docs.google.com/spreadsheets/d/1bqg5LmP1i2pnfNwhd0ifdISfgfCKzEvqrEK7n5ZNSb0/edit?gid=0#gid=0"
    private_key_path = 'causal-sky-451614-e4-85aa169c3d7c.json'
    df = fetch_google_sheet_data(url)
    df = process_dataframe(df)
    update_google_sheet(df, 'my_spreadsheet', private_key_path)

Comments

0

What is asked can be done with a plain vanilla spreadsheet formula. Use map(), like this:

=let(
  start, B2, end, B21,
  map(start:end, lambda(c., let(
    sumToEnd, sum(c.:end),
    if(sumToEnd < 0, hstack("No", iferror(ø)),
    if(sumToEnd - c. < 0, hstack("Yes", sumToEnd),
    hstack("Yes", if(sumToEnd - c. < 0, sumToEnd, c.))))
  )))
)

The formula will create the whole result table in one go. See map().

screenshot

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.