Сообщество - MS, Libreoffice & Google docs

MS, Libreoffice & Google docs

762 поста 14 932 подписчика

Популярные теги в сообществе:

15

Как я научил Google Календарь показывать возраст именинников

Если вы пользуетесь Гугл календарём, то стандартное напоминание выглядит как «ДР у Петра» и очень хорошо что Гугл теперь отображает и саму дату рождения — ещё несколько лет назад этого не было. Приходилось гадать — сколько лет‑то человеку?

Стандартное отображение Гугл календаря в 2025 году о дне рождения

Стандартное отображение Гугл календаря в 2025 году о дне рождения

Хотя задача упрощается и дата рождения уже перед глазами, а контакт можно открыть одним кликом, но всё равно приходится считать в уме — это круглая дата или нет?

В 2025 году с отображением даты рождения стало гораздо проще, но проблема стара как сам Google Calendar. В 2019 году я уже писал о попытках решить её разными способами: через громоздкие скрипты и старые методы Calendar API в 2022 году. Но многое из того давно сломалось, а Calendar API устарело.

Поэтому сейчас решил сделать через People API аккуратную автоматизацию, которая будет показывать в календаре не только «ДР у Ивана», но и сколько ему исполняется.

Результат работы скрипта

Результат работы скрипта

Что мы получим в итоге

На скриншоте выше — результат работы скрипта: в календаре вы видите не просто «ДР у Петра», а строку вида «Петр — 28 лет». Никаких подсчётов в уме, никаких переходов в карточку контакта — нужная информация появляется прямо в событии.

Главные фичи новой версии:

  • People API — работает в отличии от Calendar API.

  • CONFIG‑файл — меняете настройки без погружения в код. Даже если вы далеки от программирования, всё сводится к паре значений.

  • Русский язык — корректные склонения «год/года/лет», без костылей.

  • Телефон в описании — можно позвонить имениннику прямо из уведомления календаря, если номер есть в контактах. На скриншоте несуществующий для теста человек и телефона у него понятно нет.

И маленькая подготовка, чтобы всё завелось. Данные — это топливо: проверьте, что у контактов указан полный год рождения. Если года нет — скрипт не сможет вычислить возраст, и никакой магии не произойдёт.

Инструкция: «Копировать — Вставить — Забыть»

Самая «сложная» часть — это не написание кода, а преодоление страха перед пустым редактором. Мы пойдем по пути наименьшего сопротивления.

  1. Откройте script.google.com и нажмите большую кнопку «Создать проект».

  2. Удалите всё, что есть в редакторе и вставьте код, приведенный ниже.

/**
* @fileoverview Скрипт для Google Apps Script, который создает в Google Календаре события
* о днях рождения контактов с указанием их возраста.
*
* @VERSION 2.0
* @Author Mikhail Shardin
* @see https://shardin.name/
*/

// --- НАСТРОЙКИ СКРИПТА ---
const CONFIG = {
// ID календаря, в который будут добавляться события.
// Чтобы использовать календарь по умолчанию, оставьте 'default'.
// Чтобы найти ID другого календаря: зайдите в его настройки, раздел "Интеграция календаря".
CALENDAR_ID: 'default',

// Часовой пояс для корректного определения дат.
// Список часовых поясов: https://en.wikipedia.org/wiki/List_of_tz_database_time_zones
TIME_ZONE: 'Europe/Yekaterinburg',

// За сколько дней вперед создавать события о днях рождения.
// Например, 31 день — события будут созданы на ближайший месяц.
DAYS_AHEAD_TO_CREATE_EVENTS: 31,

// Местоположение для событий в календаре (необязательно).
EVENT_LOCATION: 'Пермь',

// Настройки уведомлений (в минутах до начала события).
// 0 = в момент начала (в 00:00), 900 = за 15 часов (в 9:00 предыдущего дня).
REMINDER_MINUTES: [0, 900],

// Настройки для файла логов на Google Диске.
LOG_FILE_SETTINGS: {
ENABLED: true, // Включить или выключить сохранение логов в файл
FILE_NAME_SUFFIX: '_BirthdayLogs.txt' // Суффикс для имени файла лога
}
};

// --- ГЛАВНАЯ ФУНКЦИЯ ---

/**
* Основная функция. Получает контакты и создает события в календаре для предстоящих дней рождения.
*/
function createBirthdayEvents() {
const calendar = getCalendar();
if (!calendar) return;

const {
startDate,
endDate
} = getDateRange();
logScriptRunDates(startDate, endDate);

const contacts = getAllContactsWithBirthdays();
if (contacts.length === 0) {
Logger.log('Не найдено контактов с указанной датой рождения.');
return;
}

Logger.log(`Найдено ${contacts.length} контактов с датой рождения. Начинаем обработку...`);

let eventsCreated = 0;
contacts.forEach(contact => {
const birthdayInfo = getBirthdayInfo(contact, startDate.getFullYear());

if (birthdayInfo.nextBirthday >= startDate && birthdayInfo.nextBirthday <= endDate) {
Logger.log(`-> День рождения "${birthdayInfo.name}" (${birthdayInfo.dateString}) попадает в диапазон.`);
deleteExistingEvents(calendar, birthdayInfo.name, birthdayInfo.nextBirthday);
createCalendarEvent(calendar, birthdayInfo);
eventsCreated++;
}
});

Logger.log(`Обработка завершена. Создано/обновлено событий: ${eventsCreated}.`);
if (CONFIG.LOG_FILE_SETTINGS.ENABLED) {
saveLogToDrive();
}
}

// --- ВСПОМОГАТЕЛЬНЫЕ ФУНКЦИИ ---

/**
* Получает объект календаря по ID из настроек.
* @Returns {CalendarApp.Calendar|null} Объект календаря или null в случае ошибки.
*/
function getCalendar() {
try {
const calId = CONFIG.CALENDAR_ID;
const calendar = calId === 'default' ?
CalendarApp.getDefaultCalendar() :
CalendarApp.getCalendarById(calId);

if (!calendar) {
throw new Error(`Календарь с ID "${calId}" не найден.`);
}
Logger.log(`Используется календарь: "${calendar.getName()}"`);
return calendar;
} catch (e) {
Logger.log(`Ошибка при получении календаря: ${e.message}`);
return null;
}
}

/**
* Определяет диапазон дат для поиска дней рождения.
* @Returns {{startDate: Date, endDate: Date}} Объект с начальной и конечной датами.
*/
function getDateRange() {
const startDate = new Date();
const endDate = new Date(startDate.getTime() + CONFIG.DAYS_AHEAD_TO_CREATE_EVENTS * 24 * 60 * 60 * 1000);
return {
startDate,
endDate
};
}

/**
* Логирует диапазон дат выполнения скрипта.
* @Param {Date} startDate Начальная дата.
* @Param {Date} endDate Конечная дата.
*/
function logScriptRunDates(startDate, endDate) {
Logger.log(`Скрипт запущен. Поиск дней рождения в диапазоне:`);
Logger.log(`С: ${Utilities.formatDate(startDate, CONFIG.TIME_ZONE, 'dd.MM.yyyy HH:mm')}`);
Logger.log(`По: ${Utilities.formatDate(endDate, CONFIG.TIME_ZONE, 'dd.MM.yyyy HH:mm')}`);
}

/**
* Получает все контакты пользователя, у которых указана дата рождения, используя People API.
* @Returns {Array<Object>} Массив объектов контактов.
*/
function getAllContactsWithBirthdays() {
const allContacts = [];
let pageToken = null;

try {
do {
const response = People.People.Connections.list('people/me', {
personFields: 'names,birthdays,phoneNumbers',
pageSize: 1000,
pageToken: pageToken
});

if (response.connections && response.connections.length > 0) {
const contactsWithBirthdays = response.connections.filter(person =>
person.birthdays && person.birthdays.some(b => b.date)
);
allContacts.push(...contactsWithBirthdays);
}
pageToken = response.nextPageToken;
} while (pageToken);

} catch (e) {
Logger.log(`Не удалось получить контакты через People API: ${e.message}`);
// Можно добавить отправку уведомления по email в случае критической ошибки
// MailApp.sendEmail('your-email@example.com', 'Ошибка в скрипте дней рождения', e.message);
}
return allContacts;
}

/**
* Извлекает и форматирует информацию о дне рождения контакта.
* @Param {Object} person Объект контакта из People API.
* @Param {number} currentYear Текущий год.
* @Returns {Object} Объект с информацией о дне рождения.
*/
function getBirthdayInfo(person, currentYear) {
const name = person.names && person.names.length > 0 ? person.names[0].displayName : '[Имя не указано]';
const birthdayData = person.birthdays[0].date;
const {
day,
month,
year
} = birthdayData;

const nextBirthday = new Date(currentYear, month - 1, day);
// Если день рождения в этом году уже прошел, берем следующий год
if (nextBirthday < new Date(new Date().setHours(0, 0, 0, 0))) {
nextBirthday.setFullYear(currentYear + 1);
}

const age = year ? nextBirthday.getFullYear() - year : null;
const ageText = age ? `${age} ${getAgePostfix(age)}` : '';
const mobilePhone = person.phoneNumbers ? person.phoneNumbers.find(p => p.type === 'mobile') : null;

return {
name,
year,
age,
ageText,
nextBirthday,
mobilePhone: mobilePhone ? mobilePhone.value : null,
dateString: `${day}.${month}${year ? '.' + year : ''}`
};
}

/**
* Удаляет старые события для этого же контакта на ту же дату.
* @Param {CalendarApp.Calendar} calendar Объект календаря.
* @Param {string} contactName Имя контакта.
* @Param {Date} eventDate Дата события.
*/
function deleteExistingEvents(calendar, contactName, eventDate) {
try {
const existingEvents = calendar.getEvents(eventDate, new Date(eventDate.getTime() + 1), {
search: contactName
});
if (existingEvents.length > 0) {
Logger.log(` Удаление ${existingEvents.length} старых событий для "${contactName}"...`);
existingEvents.forEach(event => event.deleteEvent());
}
} catch (e) {
Logger.log(` Ошибка при удалении старых событий: ${e.message}`);
}
}

/**
* Создает событие в календаре.
* @Param {CalendarApp.Calendar} calendar Объект календаря.
* @Param {Object} birthdayInfo Информация о дне рождения.
*/
function createCalendarEvent(calendar, birthdayInfo) {
const eventTitle = `${birthdayInfo.name} — день рождения${birthdayInfo.ageText ? ', ' + birthdayInfo.ageText : ''}`;
let description = `Сегодня ${birthdayInfo.name} празднует день рождения${birthdayInfo.ageText ? ` — ${birthdayInfo.ageText}` : ''}!\n\nС Днём Рождения! 🎂🎁🙂🎈💃🕺`;
if (birthdayInfo.mobilePhone) {
description += `\n\n☎️ ${birthdayInfo.mobilePhone}`;
}

try {
const event = calendar.createAllDayEvent(eventTitle, birthdayInfo.nextBirthday, {
description: description,
location: CONFIG.EVENT_LOCATION
});
CONFIG.REMINDER_MINUTES.forEach(minutes => event.addPopupReminder(minutes));
Logger.log(` ✅ Событие "${eventTitle}" успешно создано.`);
} catch (e) {
Logger.log(` ❌ Не удалось создать событие для "${birthdayInfo.name}": ${e.message}`);
}
}

/**
* Возвращает правильное окончание для возраста ("год", "года", "лет").
* @Param {number} age Возраст.
* @Returns {string} Слово с правильным окончанием.
*/
function getAgePostfix(age) {
const lastDigit = age % 10;
const lastTwoDigits = age % 100;

if (lastTwoDigits >= 11 && lastTwoDigits <= 19) {
return 'лет';
}
if (lastDigit === 1) {
return 'год';
}
if (lastDigit >= 2 && lastDigit <= 4) {
return 'года';
}
return 'лет';
}


// --- ФУНКЦИИ УПРАВЛЕНИЯ ТРИГГЕРАМИ И ЛОГАМИ ---

/**
* Создает или обновляет триггер для ежемесячного автоматического запуска скрипта.
* Запускается 1-го числа каждого месяца. Рекомендуется запустить эту функцию один раз вручную.
*/
function setupMonthlyTrigger() {
const functionName = 'createBirthdayEvents';
// Удаляем все предыдущие триггеры для этого проекта, чтобы избежать дублирования
ScriptApp.getProjectTriggers().forEach(trigger => ScriptApp.deleteTrigger(trigger));

// Создаем новый триггер, который будет запускать скрипт 1-го числа каждого месяца в 1-2 часа ночи
ScriptApp.newTrigger(functionName)
.timeBased()
.onMonthDay(1) // Запускать в 1-й день месяца
.atHour(1) // Указываем час запуска
.create();

Logger.log(`Триггер для функции "${functionName}" успешно создан. Он будет запускаться ежемесячно, 1-го числа.`);
}

/**
* Сохраняет журнал выполнения (логи) в текстовый файл на Google Диске.
*/
function saveLogToDrive() {
try {
const scriptFile = DriveApp.getFileById(ScriptApp.getScriptId());
const scriptName = scriptFile.getName();
const parentFolder = scriptFile.getParents().next() || DriveApp.getRootFolder();
const logFileName = `${scriptName}${CONFIG.LOG_FILE_SETTINGS.FILE_NAME_SUFFIX}`;

// Удаляем старый файл лога, если он существует
const oldLogs = parentFolder.getFilesByName(logFileName);
if (oldLogs.hasNext()) {
oldLogs.next().setTrashed(true);
}

// Создаем новый файл с текущими логами
parentFolder.createFile(logFileName, Logger.getLog());
Logger.log(`Логи сохранены в файл: "${logFileName}" в папке "${parentFolder.getName()}"`);
} catch (e) {
Logger.log(`Ошибка при сохранении лога на Диск: ${e.message}`);
}
}


// --- ТЕСТОВАЯ ФУНКЦИЯ ---

/**
* Тестовая функция. Находит и выводит в лог все контакты, у которых есть дата рождения.
* Ничего не создает и не изменяет.
*/
function test_listAllBirthdays() {
Logger.log("--- Начало теста: Поиск всех контактов с днями рождения ---");
const contacts = getAllContactsWithBirthdays();

if (contacts.length > 0) {
contacts.forEach(person => {
const name = person.names && person.names.length > 0 ? person.names[0].displayName : '[Имя не указано]';
const bday = person.birthdays[0].date;
Logger.log(`Найден контакт: ${name} (ДР: ${bday.day || '?'}.${bday.month || '?'}.${bday.year || '????'})`);
});
Logger.log(`--- Тест завершен. Всего найдено контактов с днем рождения: ${contacts.length} ---`);
} else {
Logger.log("--- Тест завершен. Контакты с днями рождения не найдены. ---");
}
}

  1. Обратите внимание на блок CONFIG в самом верху. Вам не нужно разбираться в логике скрипта. Хотите уведомление не в 00:00, а за 15 часов? Поменяйте цифру в REMINDER_MINUTES. Живете не на Урале? Впишите свой TIME_ZONE. Это как заполнить анкету — всё интуитивно понятно.

  2. При первом запуске нажмите кнопку «Выполнить» (треугольник Play) для функции createBirthdayEvents. И тут Google попытается вас защитить.

Вы увидите грозное окно: «Приложение не проверено». Не пугайтесь. Это стандартная процедура: Google предупреждает, что автор скрипта — неизвестный разработчик (то есть вы сами). Чтобы продолжить:

  • Нажмите Проверить разрешения.

  • Выберите аккаунт.

  • Нажмите Разрешить, чтобы дать скрипту доступ к вашим контактам и календарю.

Всё. Скрипт получил доступ к «топливу» и готов работать. Вы в любое время можете посмотреть список выданных вами разрешений на специальной странице и в один клик их отозвать.

Немного технических подробностей

Выбор People API не случаен: старый Contacts API устарел и перестал работать (API контактов был отключен 19 января 2022 г.). Новый интерфейс гарантирует, что интеграция не «отвалится» при очередном обновлении Google.

За гигиену календаря отвечает функция deleteExistingEvents. Она предотвращает создание дублей: перед записью скрипт проверяет и удаляет старую метку на этот день.

Для контроля добавлено логирование на Google Диск. Полный отчет о работе сохраняется в текстовый файл: так проще найти ошибку в данных контакта, не открывая консоль разработчика.

Автоматизация: «поставь и забудь»

Чтобы скрипт работал как настоящий ассистент, нужно один раз настроить триггер. Это автоматически запустит обновление событий без вашего участия.

Запустите функцию setupMonthlyTrigger или в интерфейсе Google Apps Script откройте «Триггеры», создайте новый и выберите функцию createBirthdayEvents. Затем укажите запуск 1-го числа каждого месяца. Календарь всегда заполнен на ближайшие 30 дней, без лишних ежедневных перезапусков и нагрузки на аккаунт.

Триггеры

Триггеры

Этический момент

Напоминание о возрасте — это не попытка «подсветить» лишние цифры, а всего лишь способ не попасть в неловкую ситуацию. Скрипт показывает возраст только вам, в вашем календаре, и нигде больше не всплывает. Использовать эту информацию или нет — целиком ваш выбор. Кому‑то приятно получить поздравление «с круглой датой», а кому‑то достаточно тёплых слов без упоминания цифр.

Заключение

Автоматизация напоминаний о днях рождения — это маленькое улучшение, которое экономит массу времени и снижает нагрузку на мозг.

Вместо ручных подсчётов и переходов по меню вы получаете готовую, аккуратную запись с возрастом и ключевыми данными контакта.

Благодаря People API решение работает стабильно, не зависит от устаревших сервисов и легко адаптируется под ваши привычки через простой CONFIG‑блок. Один раз настроили — и дальше календарь сам делает рутину за вас.

Автор: Михаил Шардин
🔗 Моя онлайн‑визитка
📢 Telegram «Умный Дом Инвестора»

25 ноября 2025

Показать полностью 2
20

Лайфхак для Microsoft Word

Ситуация когда случайно на печать отправляется не тот документ большого объема . Инструкции, шаблоны или еще что используемое в рутинной работе.
В условиях организации и обычными средствами предотвратить такие ситуации не вышло.
НО! Можно добавить примечания к документам, которые не должны печататься ни в коем случае. Тогда при печати или сохранении будет выскакивать такого типа окошко, мне было такой системы защиты в избытке:

Лайфхак для Microsoft Word
9

Конструктор финансовых моделей

Приветствую всех! Если кто решил провести выходные в обнимку с финансовым моделированием и экселем, то вот вам пост в сочувствие.

Архитектура

Идеология: Конструктор нацелен на максимальную свободу построения и расчета модели, подобно MS Excel. В дополнение к возможностям Excel здесь реализована возможность интеллектуальной настройки расчета элементов модели.

Интеграм – низкоуровневый конструктор баз данных и веб-приложений, на котором сконфигурирован данный прикладной конструктор моделей, все настройки и данные хранятся в таблицах Интеграма

Модель хранится в таблице Финмодель, у которой есть подчиненные таблицы: листы модели, панели (таблицы) на листах, строки (статьи) в панелях.

Исходные данные, в основном, хранятся в таблице Значения, но могут извлекаться из любой таблицы Интеграма, а также запрашиваться извне по АПИ в момент расчета модели..

Служебные запросы собирают данные по модели и основным исходным данным.

Пользовательские запросы собирают дополнительные исходные данные, выдавая их в формате, понятном модели.

Расчетные группы – основной элемент построения модели, из которого строятся её таблицы: строки и колонки. Расчетная группа задает правило формирования колонок таблиц.

Принцип построения: табличный процессор, таблицы в котором могут состоять из произвольного набора строк и колонок, которые пользователь выбирает из базы данных или рассчитывает на лету

Термины:

  • Старт – дата начала для расчета модели

  • Финиш – дата окончания расчета

  • Период – регулярность расчета показателей модели/листа/таблицы

  • Единица измерения – задается на уровне строки или значения

Общие сведения

Модель максимально близко повторяет типичный подход, применяемый пользователями в MS Excel и других электронных таблицах: листы с данными и связующие формулы.

Модель хранится в виде настроек в таблицах Интеграма.

На листе может быть несколько панелей, каждая из которых – это таблица с заданным строками:

У модели есть кнопка «ФМ», которая запускает расчет этой модели, в таблице моделей:

И на форме редактирования модели:

Строки таблиц модели

Строки таблиц имеют свои параметры: формулы, значения, форматы, уровень иерархии и т.п. Для каждой строки можно задать правила вычисления её содержимого.

Адресация данных

Здесь возможно следующее:

1. Сослаться на одноименное значение в таблице исходных данных

2. Сослаться на произвольное значение в таблице исходных данных

3. Записать формулу для вычисления из других строк

4. Жестко задать значение

5. Сделать ссылку на справочное значение

Как видно на картинке, адресация ячеек модели происходит путем упоминания ID или имени строки модели в квадратных скобках. В формулах строк можно написать любое математическое выражение с использованием имен или ID. Также можно применять различные функции, список которых легко расширить при необходимости, просто обратившись к команду Интеграма.

Все эти данные попадут на лист модели, когда мы её откроем:

В случае проблем с расчетом, ошибочные ячейки будут подсвечены и можно будет отследить источник ошибки, наведя курсор на подсвеченное место, и исправить её.

Исходные данные

Исходные значения могут храниться в любой таблице Интеграма, их можно получать оттуда запросами, указав имя запроса и имя нужного поля в формулах полей:

Запрос Капс вернет множество записей актуальных на период, указанный в модели. Через точку после имени запроса указывается имя поля, из которого необходимо брать данные в эту ячейку, например, Капс.влож будет использовать колонку влож из отчета Капс.

Первая колонка запроса – это дата актуальности значения, и модель разнесет все эти значения по соответствующим календарным срезам, просуммировав значения, если их несколько за период.

Повторяющиеся расчетные группы (РГ)

Существуют такие типы групп, и этот набор будет незначительно расширяться:

Повторяющаяся группа (простая)

Группа (RG) Repeating group повторяется столько раз, сколько периодов расчета укладывается в указанный для модели диапазон расчета. Период расчета (неделя, месяц, квартал, год) задаются на уровне самой модели, а также могут быть указаны для любой её панели:

В этой панели в колонке RG мы видим 2 повторяющиеся группы, одна из которых – это простая группа:

В первой колонке (N) указан порядковый номер группы на панели – так их увидит пользователь, а во второй колонке (RG) указан номер по порядку создания группы.

Для заданного периода (Год) в диапазоне модели (01.01.2025 – 31.12.2030) будет выведено 6 повторений группы, по одному на каждый год:

Группа Единицы измерения

Эта группа представлена единственной колонкой, в которой выводится найденная единица измерения для значения текущей строки. Единица измерения может быть указана на уровне строки модели (она идет с высшим приоритетом):

Также единицу измерения можно указать в таблице исходных данных:

Группа значений

Самая простая группа, для которой создается единственная колонка данных, заполняемая данными из самой строки или записанной в строке модели формулы.

Например, такая комбинация строк панели с её повторяющимися группами

Приведет к такому результату при расчете модели – см. Колонку Значение:

Произвольная группа

Вместо выбора стандартной группы можно указать запрос, который выдаст произвольный набор для создания колонок таблицы как источник для повторяющейся группы.

Например, если требуется создать матрицу строк выпускаемой продукции (строки таблицы) и отраслей, которые участвуют в её производстве (столбцы), то мы укажем запрос со списком отраслей как источник списка колонок для группы:

В строках панели при этом указывается список видов продукции:

В таблице исходных значений следует разместить известные значения для комбинаций отраслей и типов продуктов:

В результате, в модели мы увидим требуемую межотраслевую матрицу с этими значениями:

Пользовательские запросы

Для выборки исходных данных для модели пользователь может извлекать данные из любых таблиц системы посредством конструктора запросов.

Первая колонка запроса должна содержать дату актуальности данных, о остальные – сами данные.

Здесь начинается более продвинутая тема, и мы рассмотрим её в следующей статье. Спасибо, если кто вдруг досюда дочитал предыдущие 10 килосимволов.

Показать полностью 21
30

Быстрое выравнивание аномалий в ряду данных средствами Excel (линейная регрессия)

Иногда в динамических рядах встречаются значения, которые «выбиваются» из общей картины. Это могут быть ошибки учёта, резкие скачки из-за внешних факторов или просто нетипичные значения.

Рассмотрим пример данных за 2019–2025 годы:

Пример сведений с аномальными данными

Пример сведений с аномальными данными

Визуализация аномальных сведений

Визуализация аномальных сведений

Очевидно, что в 2022–2024 гг. значения выглядят аномальными (в приложенном к посту файле Excel выглядит ещё более наглядно).

🔧 Что делаем

Аналитик принимает решение, что ряд должен расти линейно. Значит, выбросы можно заменить на значения, рассчитанные по линейной регрессии.

В Excel для этого используется функция ПРЕДСКАЗ.ЛИНЕЙН().

Так как «правильные» точки ряда не идут подряд (нам нужны 2019, 2020, 2021 и 2025), применяем ВЫБОР:

=ОКРУГЛ(ПРЕДСКАЗ.ЛИНЕЙН(2022;
ВЫБОР({1;2;3;4};B13;B14;B15;B19);
ВЫБОР({1;2;3;4};A13;A14;A15;A19));
2)

👉 Формулу вставляем для каждого нужного года (2022–2024).

📈 Результат

После корректировки ряд стал выглядеть так:

Расчёт регрессии и применение к ряду данных

Расчёт регрессии и применение к ряду данных

Теперь динамика выглядит естественной, а тренд отражает реальную картину без искажений от аномалий.

💡 Такой приём полезен, когда нужно убрать «шум» и быстро выровнять ряд. Но важно помнить: иногда выбросы содержат ключевую информацию и требуют отдельного анализа.

P.S. Я стараюсь публиковать интересные примеры из практической деятельности аналитика. Если вам интересно, приглашаю к ознакомлению на канале.

Файл примера

Показать полностью 2
25

Инструмент для быстрой проверки отличий между файлами Excel (добавлен экспорт результата)

После публикации первой части и полученного опыта, а также волны хейта я немного доработал свой онлайн-инструмент для сравнения двух Excel-файлов.

Сначала немного о том, почему я сделал этот инструмент. Я работаю инженером данных и многое уже автоматизировал в своей работе. Сейчас я занимаюсь переносом разных скриптов из старых офлайн систем в облако. Чтобы сравнивать таблицы, есть разные инструменты, но речь не об этом. В них всё просто — можно преобразовать любые форматы и сравнить. Но появилась задача сравнивать Excel, который получается после работы скрипта, и CSV-файл, выгруженный из Databricks. У кого-то это могут быть также две таблицы из разных систем.

Есть несколько вариантов, как это сделать:

  • Для продвинутых хейтеров. Можно загрузить Excel в python через pandas и сравнить две таблицы, но там нужно правильно настраивать форматы, и это долго и не очень удобно. Ещё результат выводится в виде таблицы, и приходится самому искать отличия глазами.

  • Для тех, кто знает формулы в Excel и любит рутину. Можно открыть оба файла в Excel, привести даты к одному формату, прописать формулы для сравнения и так далее. Но это долго, и каждый раз для новых файлов нужно всё делать заново. Приходится создавать дополнительные таблицы с результатами.

В итоге я решил попробовать сделать сайт, который сравнивает файлы прямо в браузере, не загружая их никуда на сервер, а используя только память вашего компьютера. Мне встречались советы в прошлом посте сделать офлайн-инструмент — но, во-первых, в exe-файле может быть всё что угодно, и вы не всегда знаете, что именно он делает. А во-вторых, на работе часто запрещено устанавливать новое ПО.
У моего сайта нет сервера — весь код лежит в открытом GitHub, который подключён к домену.

Какие задачи сейчас решает мой инструмент:

  • Можно сравнить два Excel-файла или Excel с CSV.

  • Можно выбрать, какие листы (вкладки) сравнивать.

  • Все даты перед сравнением автоматически приводятся к одному формату.

  • Названия колонок приводятся к верхнему регистру, чтобы не было ошибок из-за разного регистра.

  • Порядок строк в файлах не важен — инструмент их сравнит правильно.

  • Колонки могут идти в разном порядке — это не проблема.

  • В конце показывается итоговая таблица с отличиями.

  • Есть подробная таблица с цветной подсветкой, где видно, в чём конкретно разница.

  • Можно исключать отдельные колонки из сравнения, чтобы проверить, именно ли в них проблема.

  • Теперь можно экспортировать (сохранить) итоговую таблицу с подсветкой различий.

Вот пример двух файлов, которые я сейчас сравню:

Как видно, даты там в разных форматах, а колонки — в разном порядке. Выбираем файлы, нажимаем «Сравнить» — и получаем результат:

Для меня это очень удобный инструмент, когда нужно быстро и без сложностей сравнить два Excel-файла, быстро увидеть отличия и отправить кому-то итоговую таблицу с подсветкой.

Заранее спасибо за ваши советы и комментарии.

Показать полностью 5
224

Как сравнить два Excel-файла и не сойти с ума (сделал мини инструмент, теперь 30 секунд)

Недавно у меня возникла задача, с которой, наверное, сталкивался каждый, кто хоть раз работал с таблицами. Я выгрузил из разных систем два Excel-файла, а точнее один из них был в CSV формате, вроде бы должны были быть похожими, но не совсем. Нужно было понять сходятся ли они или есть расхождения. Звучит как что-то простое, но когда я начал разбираться, стало понятно — это не 5 минут работы, а, скорее всего ни один час.

Что я пробовал, и почему это не помогло

Иногда если в файле мало строк, можно было сделать и глазами, но не в этот раз.

Обычно если нужно сравнить всего-то один столбец, то отлично подходит ВПР (VLOOKUP), но тут необходимо было сверить все столбцы.

Воспользоваться готовыми решениями не вариант, т.к. большинство из них загружают данные к себе на сервер, а мне хочется каждый раз проверять есть в файле личные данные.

Решение: сделал простую штуку, чтобы не страдать больше

Когда я понял, что готовых решений, которые бы меня устраивали, нет, я решил просто сделать своё. Также мне очень хотелось сделать подсветку расхождений, что не вглядываться в каждый столбец.

В первую очередь я делал этот инструмент для личных целей и пока он работает с небольшими файлами, т.к. их нужно загружать в оперативную память. Но может кому-то тоже окажется полезным - makspilot.com. Он очень простой: выбираешь два файла Excel или CSV, загружаешь их, нажимаешь «Сравнить» — и через секунду получаешь список всех отличий. Также можно исключить столбцы из сравнения.

Как сравнить два Excel-файла и не сойти с ума (сделал мини инструмент, теперь 30 секунд)

Буду рад отзывам или пожеланиям.

В будущем хочу еще использовать одну технологию, которая позволит работать с большими файлами.

Показать полностью 1
1

Ответ на пост «Учёт для тренера в Excel»2

У меня после перехода по ссылке на Гугл таблицу все открывается ,но через 10 сек вылетает ,подскажите как можно решить проблему ?

Отличная работа, все прочитано!